Rebased and tuned version of Calinou's original pull request.
'm_view_bobbing_speed' controls the frequency of view bobbing,
hand bobbing and footsteps, it was limited to a maximum of 40 (walking
frequency) so did not increase if player speed was modified by a
'speed buff', a sprinting mod or modified in .conf or advanced
settngs.
This commit raises the limit to 70 which is suitable for sprinting.
Adds several new ways that the plantlike drawtype mesh can be changed.
This requires paramtype2 = "meshoptions" to be set in the node
definition. The drawtype for these nodes should be "plantlike".
These modifications are all done using param2. This field is now
a complex bitfield that allows some or more of the combinations to
be chosen, and the mesh draw code will choose the options based as
neeeded for each plantlike node.
bit layout:
bits 0, 1 and 2 (values 0x1 through 0x7) are for choosing the plant
mesh shape:
0 - ordinary plantlike plant ("x" shaped)
1 - ordinary plant, but rotated 45 degrees ("+" shaped)
2 - a plant with 3 faces ("*" shaped)
3 - a plant with 4 faces ("#" shaped)
4 - a plant with 4 faces ("#" shaped, leaning outwards)
5 through 7 are unused and reserved for future mesh shapes.
bit 3 (0x8) causes the plant to be randomly offset in the x,z
plane. The plant should fall within the 1x1x1 nodebox if regularly
sized.
bit 4 (0x10) causes the plant mesh to grow by sqrt(2), and will cause
the plant mesh to fill out 1x1x1, and appear slightly larger. Texture
makers will want to make their plant texture 23x16 pixels to have the
best visual fit in 1x1x1 size.
bit 5 (0x20) causes each face of the plant to have a slight negative
Y offset in position, descending up to 0.125 downwards into the node
below. Because this is per face, this causes the plant model to be
less symmetric.
bit 6 (0x40) through bit 7 (0x80) are unused and reserved for
future use.
!(https://youtu.be/qWuI664krsI)
Disable the ability to connect to old servers by default to
improve password security.
If people still want to connect to old (0.4.12 and earlier)
servers, they can flip the send_pre_v25_init setting.
Add the ability to detect if we've tried to connect
to a server which only supports the pre v25 init protocol,
and show an apropriate error message. Most times the error
will already be catched at the serverlist level, the
detection mechanism only acts as last resort, because the
"Connection timed out" error message that would be shown
otherwise would be very confusing.
Automatic "fixing" of this condition is not desired,
as it would allow for downgrade attacks.
As already 161 of the 167 servers on the serverlist
support the new srp based auth protocol (> 96%),
the breakage should be minimal.
Follow up of commit
af30183124d40a969040d7de4b3a487feec466e4 "Add option to not send pre v25 init packet"
Also change the pessimistic assumption of masterlist
server versions to optimistic, in order to avoid buggy
behaviour (favourites not in the serverlist would be
denied to connect to, etc).
Fixes #4446: a syntax error that happens if something else (such as
<iomanip> or <locale> in certain libstdc++ versions) includes
<libintl.h> later, which contains a function declaration for gettext
that gets mangled by the macro.
See the added comment in gettext.h and the discussion in #4446/#4452
for details.
Avoids unreachable code linter warning by moving generation code
(of minetest.conf.example and settings_translation_file.cpp)
out of dlg_settings_advanced.
Due to passing the settings, also it avoids reading the settings file twice.
Instead of activating the code by changing the active if-clauses,
its activation is now done by uncommenting the loadfile() statement.
If compiling according to a C++ version before C++11, then define
std::to_string ourselves.
Add a to_wstring version as well
As std::to_string() for floating point types uses %.6f as floating
point format converter, instead of %G, it needs special care.
To preserve ftos() behavior (which is expected to use the %G format
converter), it no longer uses to_string().
If a mod is disabled, or upgraded without sufficient backward compatibility,
then entities it has put into the world become unknown, and continue moving
around, but are completely unrecognisable.
This change allows the player to see their type, and therefore which mod is
or was responsible.
previously function used tonumber which returned float
this caused errors in large numbers and resulted in
obj-def-handlers being invalid when retrived from lua tables in c
* Move included json code to jsoncpp subdirectory
This is needed to avoid having to specify the minetest src directory
as a system include when fixing the json includes.
* Fix json includes
They used "", so that the compiler searches the project's directory
first. The result was that when compiling with a system jsoncpp,
the project's own version of json.h was still included, instead of
the system version.
The includes now use <>, so a system location, or one specified with
'-Ilocation' is searched only.
* Fix for jsoncpp deprecated function warning
When compiling with a newer version of jsoncpp (and
ENABLE_SYSTEM_JSONCPP=true), jsoncpp emits a warning
about a deprecated function that minetest uses.
(#4430)
This fallback to std::map & std::set for older compilers
Use UNORDERED_SET as an example in decoration and ore biome sets
Use UNORDERED_MAP as an example in nameidmapping
Fix height check comparison from '>=' to '>'.
Fix getHeight() for schematic decorations to account for
'deco place center y' flag and for how normal placement
sinks schematic 1 node into the ground.
Jungletrees were not being placed at y = 46, y = 47 despite
having an acceptable 16 nodes of height above ground surface.
When the 'flat' and 'trees' flags were moved into mgv6_spflags they
were left in mg_flags in an attempt to support old mgv6 worlds. However
their appearence in mg_flags causes confusion, also, later, old-world
support was found to be broken for mgv6 worlds with 'notrees'.
This commit cleans up the mess and comes a month after a thread warning
of the change, and explaining the required action, was posted in the
news subforum. Only old mgv6 worlds with 'flat' or 'notrees' are
affected, a small minority of worlds, the required action being
correctly setting these flags in mgv6_spflags.
Disable a section of the 'map settings manager' unit test which is to
be changed as it is causing problems for pull requests.
l_request_insecure_environment didn't ignore all whitespace in the
secure.trusted_mods config option.
Replaces std::remove with std::remove_if and the isspace function.
* Update README.txt
As of Ubuntu 16.10, libpng12-dev is replaced with libpng12-0, since libpng12-0 is available in 16.04 repositories, I think that needed to be changed.
Also I changed 'git-core' to 'git' for fedora users, keep it consistent with the ubuntu/debian instructions.
* Update README.txt
Instead of trying to manually parse the output of 'git show' which can be different across different git configurations, properly use the 'git rev-parse' command that is intended for this purpose.
This little update is due to a packaging problem when uploading on play store. They don't permit to re upload an APK with same version code.
This case was a fail on openssl packaged version which was old and rejected by Google but they don't remove the APK then i should increase the version code to permit having it on play store
core.check_player_privs accepts as first argument a name or player object, but just tested for a string.
This caused crashes inside builtin, when being passed any unexpected types.
This provides a better (duck-typing like) test, better error reporting.
-- wagon.lua-- Holds all logic related to wagons-- From now on, wagons are, just like trains, just entries in a table-- All data that is static is stored in the entity prototype (self).-- A copy of the entity prototype is always available inside wagon_prototypes-- All dynamic data is stored in the (new) wagons table-- An entity is ONLY spawned by update_trainpart_properties when it finds it useful.-- Only data that are only important to the entity itself are stored in the luaentity-- TP delay when getting off wagonlocal GETOFF_TP_DELAY =0.5local IGNORE_WORLD = advtrains.IGNORE_WORLD
advtrains.wagons = {}
advtrains.wagon_prototypes = {}
advtrains.wagon_objects = {}local unload_wgn_range = advtrains.wagon_load_range +32function advtrains.wagon_outside_range(pos)-- returns true if the object is outside of unload_wgn_range of any playerreturn not advtrains.position_in_range(pos, unload_wgn_range)endlocal setting_show_ids = minetest.settings:get_bool("advtrains_show_ids")--function advtrains.create_wagon(wtype, owner)local new_id=advtrains.random_id()while advtrains.wagons[new_id]do new_id=advtrains.random_id()endlocal wgn = {}
wgn.type= wtype
wgn.seatp = {}
wgn.owner = owner
wgn.id = new_id
---wgn.train_id = train_id --- will get this via update_trainpart_properties
advtrains.wagons[new_id] = wgn
--atdebug("Created new wagon:",wgn)return new_id
endlocal functionmake_inv_name(uid)return"detached:advtrains_wgn_"..uid
endlocal wagon={
collisionbox = {-0.5,-0.5,-0.5,0.5,0.5,0.5},--physical = true,
visual ="mesh",
mesh ="wagon.b3d",
visual_size = {x=1, y=1},
textures = {"black.png"},
is_wagon=true,
wagon_span=1,--how many index units of space does this wagon consume
wagon_width=3,-- Wagon width in meters
has_inventory=false,
static_save=false,}function wagon:train()local data = advtrains.wagons[self.id]return advtrains.trains[data.train_id]endfunction wagon:on_activate(sd_uid, dtime_s)if sd_uid~=""then--destroy when loaded from static block.
self.object:remove()returnend
self.object:set_armor_groups({immortal=1})endlocal functioninvcallback(id, pname, rtallow, rtfail)local data = advtrains.wagons[id]if data and advtrains.check_driving_couple_protection(pname, data.owner, data.whitelist)thenreturn rtallow
endreturn rtfail
endfunction wagon:set_id(wid)
self.id = wid
self.initialized =truelocal data = advtrains.wagons[self.id]
advtrains.wagon_objects[self.id] = self.object
--atdebug("Created wagon entity:",self.name," w_id",wid," t_id",data.train_id)if self.has_inventory then--to be used laterlocal inv=minetest.get_inventory({type="detached", name="advtrains_wgn_"..self.id})-- create inventory, if not yet createdif not inv then
inv=minetest.create_detached_inventory("advtrains_wgn_"..self.id, {
allow_move =function(inv, from_list, from_index, to_list, to_index, count, player)returninvcallback(wid, player:get_player_name(), count,0)end,
allow_put =function(inv, listname, index, stack, player)returninvcallback(wid, player:get_player_name(), stack:get_count(),0)end,
allow_take =function(inv, listname, index, stack, player)returninvcallback(wid, player:get_player_name(), stack:get_count(),0)end})if data.ser_inv then
advtrains.deserialize_inventory(data.ser_inv, inv)endif self.inventory_list_sizes thenfor lst, siz inpairs(self.inventory_list_sizes)do
inv:set_size(lst, siz)endendendend
self.door_anim_timer=0
self.door_state=0
minetest.after(0.2,function() self:reattach_all()end)if self.set_textures then
self:set_textures(data)endif self.custom_on_activate then
self:custom_on_activate()endendfunction wagon:get_staticdata()return"STATIC"endfunction wagon:ensure_init()-- Note: A wagon entity won't exist when there's no train, because the train is-- the thing that actually creates the entity-- Train not being set just means that this will happen as soon as the train calls update_trainpart_properties.if self.initialized and self.id thenlocal data = advtrains.wagons[self.id]if data and data.train_id and self:train()thenif self.noninitticks then self.noninitticks=nil endreturn trueendendif not self.noninitticks thenatwarn("wagon",self.id,"uninitialized init=",self.initialized)
self.noninitticks=0end
self.noninitticks=self.noninitticks+1if self.noninitticks>20thenatwarn("wagon",self.id,"uninitialized, removing")
self:destroy()else
self.object:set_velocity({x=0,y=0,z=0})endreturn falseendfunction wagon:train()local data = advtrains.wagons[self.id]return advtrains.trains[data.train_id]end-- Remove the wagonfunction wagon:on_punch(puncher, time_from_last_punch, tool_capabilities, direction)if not self:ensure_init()then return endlocal data = advtrains.wagons[self.id]if not puncher or not puncher:is_player()thenreturnendif data.owner and puncher:get_player_name()~=data.owner and(not minetest.check_player_privs(puncher, {train_admin =true}))then
minetest.chat_send_player(puncher:get_player_name(),attrans("This wagon is owned by @1, you can't destroy it.", data.owner));returnendif self.custom_may_destroy thenif not self.custom_may_destroy(self, puncher, time_from_last_punch, tool_capabilities, direction)thenreturnendendlocal itemstack = puncher:get_wielded_item()-- WARNING: This part of the API is guaranteed to change! DO NOT USE!if self.set_livery and itemstack:get_name() =="bike:painter"then
self:set_livery(puncher, itemstack, data)returnend-- check whether wagon has an inventory. Is is empty?if self.has_inventory thenlocal inv=minetest.get_inventory({type="detached", name="advtrains_wgn_"..self.id})if not inv then-- inventory is not initialized when wagon was never loaded - should never happenatwarn("Destroying wagon with inventory, but inventory is not found? Shouldn't happen!")returnendfor listname, _ inpairs(inv:get_lists())doif not inv:is_empty(listname)then
minetest.chat_send_player(puncher:get_player_name(),attrans("The wagon's inventory is not empty!"));returnendendendif#(self:train().trainparts)>1then
minetest.chat_send_player(puncher:get_player_name(),attrans("Wagon needs to be decoupled from other wagons in order to destroy it."));returnendlocal pc=puncher:get_player_control()if not pc.sneak then
minetest.chat_send_player(puncher:get_player_name(),attrans("Warning: If you destroy this wagon, you only get some steel back! If you are sure, hold Sneak and left-click the wagon."))returnendif not self:destroy()then return endlocal inv = puncher:get_inventory()for _,item inipairs(self.drops or{self.name})do
inv:add_item("main", item)endendfunction wagon:destroy()--some rules:-- you get only some items back-- single left-click shows warning-- shift leftclick destroys-- not when a driver is insideif self.id thenlocal data = advtrains.wagons[self.id]if not data thenatwarn("wagon:destroy(): data is not set!")returnendif self.custom_on_destroy then
self.custom_on_destroy(self)endfor seat,_ inpairs(data.seatp)do
self:get_off(seat)endif data.train_id and self:train()then
advtrains.remove_train(data.train_id)
advtrains.wagons[self.id]=nilif self.discouple then self.discouple.object:remove()end--will have no effect on unloaded objectsendend--atdebug("[wagon ", self.id, "]: destroying")
self.object:remove()return trueendfunction wagon:is_driver_stand(seat)if self.seat_groups thenreturn(seat.driving_ctrl_access or self.seat_groups[seat.group].driving_ctrl_access)elsereturn seat.driving_ctrl_access
endendfunction wagon:on_step(dtime)if not self:ensure_init()then return endif advtrains.is_no_action()then
self.object:remove()returnendlocal t=os.clock()local pos = self.object:get_pos()local data = advtrains.wagons[self.id]if not pos then--atdebug("["..self.id.."][fatal] missing position (object:getpos() returned nil)")returnendif not data.seatp then
data.seatp={}endif not self.seatpc then
self.seatpc={}endlocal train=self:train()local is_in_loaded_area = advtrains.is_node_loaded(pos)--custom on_step functionif self.custom_on_step then
self:custom_on_step(dtime, data, train)end--driver controlfor seatno, seat inipairs(self.seats)dolocal pname=data.seatp[seatno]local driver=pname and minetest.get_player_by_name(pname)local has_driverstand = pname and advtrains.check_driving_couple_protection(pname, data.owner, data.whitelist)
has_driverstand = has_driverstand and self:is_driver_stand(seat)if has_driverstand and driver then
advtrains.update_driver_hud(driver:get_player_name(), self:train(), data.wagon_flipped)elseif driver then--only show the inside textlocal inside=self:train().text_inside or""
advtrains.set_trainhud(driver:get_player_name(), inside)endif driver and driver:get_player_control_bits()~=self.seatpc[seatno]thenlocal pc=driver:get_player_control()
self.seatpc[seatno]=driver:get_player_control_bits()if has_driverstand then--regular driver stand controls
advtrains.on_control_change(pc, self:train(), data.wagon_flipped)--bordcomif pc.sneak and pc.jump then
self:show_bordcom(data.seatp[seatno])end--sound horn when requiredif self.horn_sound and pc.aux1 and not pc.sneak and not self.horn_handle then
self.horn_handle = minetest.sound_play(self.horn_sound, {
object = self.object,
gain =1.0,-- default
max_hear_distance =128,-- default, uses an euclidean metric
loop =true,})elseif not pc.aux1 and self.horn_handle then
minetest.sound_stop(self.horn_handle)
self.horn_handle =nilendelse-- If on a passenger seat and doors are open, get off when W or D pressed.local pass = data.seatp[seatno]and minetest.get_player_by_name(data.seatp[seatno])if pass and self:train().door_open~=0thenlocal pc=pass:get_player_control()if pc.up or pc.down then
self:get_off(seatno)endendendif pc.aux1 and pc.sneak then
self:get_off(seatno)endendend--check infotextlocal outside=train.text_outside or""if setting_show_ids then
outside = outside .."\nT:".. data.train_id .." W:".. self.id .." O:".. data.owner
end--show off-track information in outside text instead of notifying the whole server about thisif train.off_track then
outside = outside .."\n!!! Train off track !!!"end-- liquid container: display liquid contents in infotextif self.techage_liquid_capacity thenif data.techage_liquid and data.techage_liquid.name then
outside = outside .."\nLiquid: "..data.techage_liquid.name..", "..data.techage_liquid.amount.." units"else
outside = outside .."\nLiquid: empty"endendif self.infotext_cache~=outside then
self.object:set_properties({infotext=outside})
self.infotext_cache=outside
endlocal fct=data.wagon_flipped and-1or1--door animationif self.doors thenif(self.door_anim_timer or0)<=0thenlocal dstate = (train.door_open or0) * fct
if dstate ~= self.door_state thenlocal at
--meaning of the train.door_open field:-- -1: left doors (rel. to train orientation)-- 0: closed-- 1: right doors--this code produces the following behavior:-- if changed from 0 to +-1, play open anim. if changed from +-1 to 0, play close.-- if changed from +-1 to -+1, first close and set 0, then it will detect state change again and run open.if self.door_state ==0thenif self.doors.open.sound then minetest.sound_play(self.doors.open.sound, {object = self.object})end
at=self.doors.open[dstate]
self.object:set_animation(at.frames, at.speed or15, at.blend or0,false)
self.door_state = dstate
elseif self.doors.close.sound then minetest.sound_play(self.doors.close.sound, {object = self.object})end
at=self.doors.close[self.door_state or1]--in case it has not been set yet
self.object:set_animation(at.frames, at.speed or15, at.blend or0,false)
self.door_state =0end
self.door_anim_timer = at.time
endelse
self.door_anim_timer = (self.door_anim_timer or0) - dtime
endend--for path to be available. if not, skip stepif not train.path or train.no_step then
self.object:set_velocity({x=0, y=0, z=0})
self.object:set_acceleration({x=0, y=0, z=0})returnendif not data.pos_in_train thenreturnend-- Calculate new position, yaw and direction vector-- note: "index" is needed to be the center index, required by door codelocal index = advtrains.path_get_index_by_offset(train, train.index, -data.pos_in_train)local pos, yaw, npos, npos2, vdir
-- use new position logic?if self.wheel_positions then-- request two positions, calculate difference and yaw from this-- depending on flipstate, need to invert wheel pos indices -> wheelpos * fctlocal index1 = advtrains.path_get_index_by_offset(train, index, self.wheel_positions[1] * fct)local index2 = advtrains.path_get_index_by_offset(train, index, self.wheel_positions[2] * fct)local pos1 = advtrains.path_get_interpolated(train, index1)local pos2 = advtrains.path_get_interpolated(train, index2)
npos = advtrains.path_get(train,atfloor(index))-- need npos just for node loaded check-- calculate center of 2 positions and vdir vector-- if wheel positions are asymmetric, needs to weight by the difference!local fact = self.wheel_positions[1] / (self.wheel_positions[1]-self.wheel_positions[2])
pos = {x=pos1.x-(pos1.x-pos2.x)*fact, y=pos1.y-(pos1.y-pos2.y)*fact, z=pos1.z-(pos1.z-pos2.z)*fact}if data.wagon_flipped then
vdir = vector.normalize(vector.subtract(pos2, pos1))else
vdir = vector.normalize(vector.subtract(pos1, pos2))end
yaw = math.atan2(-vdir.x, vdir.z)else--old position logic (for small wagons): use center index and just get position
pos, yaw, npos, npos2 = advtrains.path_get_interpolated(train, index)
vdir = vector.normalize(vector.subtract(npos2, npos))end--automatic get_on--needs to know index and pathif train.velocity==0and self.door_entry and train.door_open and train.door_open~=0then--using the mapping created by the trainlogic globalstepfor i, ino inipairs(self.door_entry)do--fct is the flipstate flag from door animation abovelocal aci = advtrains.path_get_index_by_offset(train, index, ino*fct)local ix1, ix2 = advtrains.path_get_adjacent(train, aci)-- the two wanted positions are ix1 and ix2 + (2nd-1st rotated by 90deg)-- (x z) rotated by 90deg is (-z x) (http://stackoverflow.com/a/4780141)local add = { x = (ix2.z-ix1.z)*train.door_open, y =0, z = (ix1.x-ix2.x)*train.door_open }local pts1=vector.round(vector.add(ix1, add))local pts2=vector.round(vector.add(ix2, add))if minetest.get_item_group(minetest.get_node(pts1).name,"platform")>0thenlocal ckpts={
pts1,
pts2,
vector.add(pts1, {x=0, y=1, z=0}),
vector.add(pts2, {x=0, y=1, z=0}),}for _,ckpos inipairs(ckpts)dolocal cpp=minetest.pos_to_string(ckpos)if advtrains.playersbypts[cpp]then
self:on_rightclick(advtrains.playersbypts[cpp])endendendendend--checking for environment collisions(a 3x3 cube around the center)if not IGNORE_WORLD and is_in_loaded_area and not train.recently_collided_with_env thenlocal collides=falselocal exh = self.extent_h or1local exv = self.extent_v or2for x=-exh,exh dofor y=0,exv dofor z=-exh,exh dolocal node=minetest.get_node_or_nil(vector.add(npos, {x=x, y=y, z=z}))if(advtrains.train_collides(node))then
collides=trueendendendendif collides then-- screw collision mercy
train.recently_collided_with_env=true
train.velocity=0
advtrains.atc.train_reset_command(train)endend-- Spawn discouple object when train stands, in all other cases remove it.-- FIX: Need to do this after the yaw calculationif train.velocity==0and is_in_loaded_area and data.pos_in_trainparts and data.pos_in_trainparts>1thenif not self.discouple or not self.discouple.object:get_yaw()thenatprint(self.id,"trying to spawn discouple")local dcpl_pos = vector.add(pos, {y=0, x=-math.sin(yaw)*self.wagon_span, z=math.cos(yaw)*self.wagon_span})local object=minetest.add_entity(dcpl_pos,"advtrains:discouple")if object thenlocal le=object:get_luaentity()
le.wagon=self
--box is hidden when attached, so unuseful.--object:set_attach(self.object, "", {x=0, y=0, z=self.wagon_span*10}, {x=0, y=0, z=0})
self.discouple=le
endendelseif self.discouple and self.discouple.object:get_yaw()then
self.discouple.object:remove()atprint(self.id," removing discouple")endend-- object yaw (corrected by flipstate)local oyaw = yaw
if data.wagon_flipped then
oyaw = yaw + math.pi
end--FIX: use index of the wagon, not of the train.local velocity = train.velocity * advtrains.global_slowdown
local acceleration = (train.acceleration or0) * (advtrains.global_slowdown*advtrains.global_slowdown)local velocityvec = vector.multiply(vdir, velocity)local accelerationvec = vector.multiply(vdir, acceleration)-- this timer runs off every 2 seconds.
self.updatepct_timer=(self.updatepct_timer or0)-dtime
local updatepct_timer_elapsed = self.updatepct_timer<=0if updatepct_timer_elapsed then--restart timer
self.updatepct_timer=2-- perform checks that are not frequently needed-- unload entity if out of range (because relevant pr won't be merged in engine)-- This is a WORKAROUND!local players_in =falsefor sno,pname inpairs(data.seatp)doif minetest.get_player_by_name(pname)then-- Fix: If the RTT is too high, a wagon might be recognized out of range even if a player sits in it-- (client updates position not fast enough)
players_in =truebreakendendif not players_in thenif advtrains.wagon_outside_range(pos)then--atdebug("wagon",self.id,"unloading (too far away)")-- Workaround until minetest engine deletes attached soundsif self.sound_loop_handle then
minetest.sound_stop(self.sound_loop_handle)end
self.object:remove()endendendif not self.old_velocity_vector
or not vector.equals(velocityvec, self.old_velocity_vector)or not self.old_acceleration_vector
or not vector.equals(accelerationvec, self.old_acceleration_vector)or self.old_yaw~=oyaw
or updatepct_timer_elapsed then--only send update packet if something changed
self.object:set_pos(pos)
self.object:set_velocity(velocityvec)
self.object:set_acceleration(accelerationvec)if#self.seats >0and self.old_yaw ~= oyaw thenif not self.player_yaw then
self.player_yaw = {}endif not self.old_yaw then
self.old_yaw=oyaw
endfor _,name inpairs(data.seatp)dolocal p = minetest.get_player_by_name(name)if p thenif not self.turning then-- save player looking direction offset
self.player_yaw[name] = p:get_look_horizontal()-self.old_yaw
end-- set player looking direction using calculated offset
p:set_look_horizontal((self.player_yaw[name]or0)+oyaw)endend
self.turning =trueelseif self.old_yaw == oyaw then-- train is no longer turning
self.turning =falseendif self.object.set_rotation thenlocal pitch = math.atan2(vdir.y, math.hypot(vdir.x, vdir.z))if data.wagon_flipped then
pitch = -pitch
end
self.object:set_rotation({x=pitch, y=oyaw, z=0})else
self.object:set_yaw(oyaw)endif self.update_animation then
self:update_animation(train.velocity, self.old_velocity)endif self.custom_on_velocity_change then
self:custom_on_velocity_change(train.velocity, self.old_velocity or0, dtime)end-- remove discouple object, because it will be in a wrong locationif not updatepct_timer_elapsed and self.discouple then
self.discouple.object:remove()endend
self.old_velocity_vector=velocityvec
self.old_velocity = train.velocity
self.old_acceleration_vector=accelerationvec
self.old_yaw=oyaw
atprintbm("wagon step", t)endfunction wagon:on_rightclick(clicker)if not self:ensure_init()then return endif not clicker or not clicker:is_player()thenreturnendlocal data = advtrains.wagons[self.id]local pname=clicker:get_player_name()local no=self:get_seatno(pname)if no thenif self.seat_groups thenlocal poss={}local sgr=self.seats[no].group
for _,access inipairs(self.seat_groups[sgr].access_to)doif self:check_seat_group_access(pname, access)then
poss[#poss+1]={name=self.seat_groups[access].name, key="sgr_"..access}endendif self.has_inventory and self.get_inventory_formspec and advtrains.check_driving_couple_protection(pname, data.owner, data.whitelist)then
poss[#poss+1]={name=attrans("Show Inventory"), key="inv"}endif self.seat_groups[sgr].driving_ctrl_access and advtrains.check_driving_couple_protection(pname, data.owner, data.whitelist)then
poss[#poss+1]={name=attrans("Onboard Computer"), key="bordcom"}endif data.owner==pname then
poss[#poss+1]={name=attrans("Wagon properties"), key="prop"}endif not self.seat_groups[sgr].require_doors_open or self:train().door_open~=0then
poss[#poss+1]={name=attrans("Get off"), key="off"}elseif clicker:get_player_control().sneak then
poss[#poss+1]={name=attrans("Get off (forced)"), key="off"}else
poss[#poss+1]={name=attrans("(Doors closed)"), key="dcwarn"}endendif#poss==0then--can't do anything.elseif#poss==1then
self:seating_from_key_helper(pname, {[poss[1].key]=true}, no)elselocal form ="size[5,"..1+(#poss).."]"for pos,ent inipairs(poss)do
form = form .."button_exit[0.5,"..(pos-0.5)..";4,1;"..ent.key..";"..ent.name.."]"end
minetest.show_formspec(pname,"advtrains_seating_"..self.id, form)endelse
self:get_off(no)endelse--do not attach if already on a trainif advtrains.player_to_train_mapping[pname]then return endif self.seat_groups thenif#self.seats==0thenif self.has_inventory and self.get_inventory_formspec and advtrains.check_driving_couple_protection(pname, data.owner, data.whitelist)then
minetest.show_formspec(pname,"advtrains_inv_"..self.id, self:get_inventory_formspec(pname,make_inv_name(self.id)))endreturnendlocal doors_open = self:train().door_open~=0or clicker:get_player_control().sneak
local allow, rsn=false,"Wagon has no seats!"for _,sgr inipairs(self.assign_to_seat_group)do
allow, rsn = self:check_seat_group_access(pname, sgr)if allow thenfor seatid, seatdef inipairs(self.seats)doif seatdef.group==sgr thenif(not self.seat_groups[sgr].require_doors_open or doors_open)thenif not data.seatp[seatid]then
self:get_on(clicker, seatid)returnelse
rsn="Wagon is full."endelse
rsn="Doors are closed! (try holding sneak key!)"endendendendend
minetest.chat_send_player(pname,attrans("Can't get on: "..rsn))else
self:show_get_on_form(pname)endendendfunction wagon:get_on(clicker, seatno)local data = advtrains.wagons[self.id]if not data.seatp then data.seatp={}endif not self.seatpc then self.seatpc={}end--player controls in driver standsif not self.seats[seatno]then return endlocal oldno=self:get_seatno(clicker:get_player_name())if oldno thenatprint("get_on: clearing oldno",seatno)
advtrains.player_to_train_mapping[clicker:get_player_name()]=nil
advtrains.clear_driver_hud(clicker:get_player_name())
data.seatp[oldno]=nilendif data.seatp[seatno]and data.seatp[seatno]~=clicker:get_player_name()thenatprint("get_on: throwing off",data.seatp[seatno],"from seat",seatno)
self:get_off(seatno)endatprint("get_on: attaching",clicker:get_player_name())
data.seatp[seatno] = clicker:get_player_name()
self.seatpc[seatno] = clicker:get_player_control_bits()
advtrains.player_to_train_mapping[clicker:get_player_name()]=data.train_id
clicker:set_attach(self.object,"", self.seats[seatno].attach_offset, {x=0,y=0,z=0})
clicker:set_eye_offset(self.seats[seatno].view_offset, self.seats[seatno].view_offset)endfunction wagon:get_off_plr(pname)local no=self:get_seatno(pname)if no then
self:get_off(no)endendfunction wagon:get_seatno(pname)local data = advtrains.wagons[self.id]for no, cont inpairs(data.seatp)doif cont==pname thenreturn no
endendreturn nilendfunction wagon:get_off(seatno)local data = advtrains.wagons[self.id]if not data.seatp[seatno]then return endlocal pname = data.seatp[seatno]local clicker = minetest.get_player_by_name(pname)
advtrains.player_to_train_mapping[pname]=nil
advtrains.clear_driver_hud(pname)
data.seatp[seatno]=nil
self.seatpc[seatno]=nilif clicker thenatprint("get_off: detaching",clicker:get_player_name())
clicker:set_detach()
clicker:set_eye_offset({x=0,y=0,z=0}, {x=0,y=0,z=0})local train=self:train()--code as in step - automatic get onif self.door_entry and train.door_open and train.door_open~=0and train.velocity==0and train.index and train.path thenlocal index = advtrains.path_get_index_by_offset(train, train.index, -data.pos_in_train)for i, ino inipairs(self.door_entry)do--atdebug("using door-based",i,ino)local fct=data.wagon_flipped and-1or1local aci = advtrains.path_get_index_by_offset(train, index, ino*fct)local ix1, ix2 = advtrains.path_get_adjacent(train, aci)local d = train.door_open
if self.wagon_width then
d = d * math.floor(self.wagon_width/2)end-- the two wanted positions are ix1 and ix2 + (2nd-1st rotated by 90deg)-- (x z) rotated by 90deg is (-z x) (http://stackoverflow.com/a/4780141)local add = { x = (ix2.z-ix1.z)*d, y =0, z = (ix1.x-ix2.x)*d }local oadd = { x = (ix2.z-ix1.z)*(d+train.door_open), y =1, z = (ix1.x-ix2.x)*(d+train.door_open)}local platpos=vector.round(vector.add(ix1, add))local offpos=vector.round(vector.add(ix1, oadd))--atdebug("platpos:", platpos, "offpos:", offpos)if minetest.get_item_group(minetest.get_node(platpos).name,"platform")>0then
minetest.after(GETOFF_TP_DELAY,function() clicker:set_pos(offpos)end)--atdebug("tp",offpos)returnend--atdebug("nope")endend--if not door_entry, or paths missing, fall back to old method--atdebug("using fallback")local objpos=advtrains.round_vector_floor_y(self.object:getpos())local yaw=self.object:getyaw()local isx=(yaw < math.pi/4)or(yaw >3*math.pi/4and yaw <5*math.pi/4)or(yaw >7*math.pi/4)local offp
--abuse helper functionfor _,r inipairs({-1,1})do--atdebug("offset",r)local p=vector.add({x=isx and r or0, y=0, z=not isx and r or0}, objpos)
offp=vector.add({x=isx and r*2or0, y=1, z=not isx and r*2or0}, objpos)--atdebug("platpos:", p, "offpos:", offp)if minetest.get_item_group(minetest.get_node(p).name,"platform")>0then
minetest.after(GETOFF_TP_DELAY,function() clicker:set_pos(offp)end)--atdebug("tp",offp)returnendend--atdebug("nope")endendfunction wagon:show_get_on_form(pname)if not self.initialized then return endlocal data = advtrains.wagons[self.id]if#self.seats==0thenif self.has_inventory and self.get_inventory_formspec and advtrains.check_driving_couple_protection(pname, data.owner, data.whitelist)then
minetest.show_formspec(pname,"advtrains_inv_"..self.id, self:get_inventory_formspec(pname,make_inv_name(self.id)))endreturnendlocal form, comma="size[5,8]label[0.5,0.5;"..attrans("Select seat:").."]textlist[0.5,1;4,6;seat;",""for seatno, seattbl inipairs(self.seats)dolocal addtext, colorcode="",""if data.seatp and data.seatp[seatno]then
colorcode="#FF0000"
addtext=" ("..data.seatp[seatno]..")"end
form=form..comma..colorcode..seattbl.name..addtext
comma=","end
form=form..";0,false]"if self.has_inventory and self.get_inventory_formspec then
form=form.."button_exit[1,7;3,1;inv;"..attrans("Show Inventory").."]"end
minetest.show_formspec(pname,"advtrains_geton_"..self.id, form)endfunction wagon:show_wagon_properties(pname)--[[ fields: field: driving/couple whitelist button: save ]]local data = advtrains.wagons[self.id]local form="size[5,5]"
form=form.."label[0.2,0;"..attrans("This Wagon ID")..": "..self.id.."]"
form = form .."field[0.5,1;4.5,1;whitelist;Allow these players to access your wagon:;"..minetest.formspec_escape(data.whitelist or"").."]"
form = form .."field[0.5,2;4.5,1;roadnumber;Wagon road number:;"..minetest.formspec_escape(data.roadnumber or"").."]"local fc =""if data.fc then
fc =table.concat(data.fc,"!")end
form = form .."field[0.5,3;4.5,1;fc;Freight Code:;"..fc.."]"if data.fc thenif not data.fcind then data.fcind =1endif data.fcind >1then
form=form.."button[0.5,3.5;1,1;fcp;prev FC]"end
form=form.."label[1.5,3.5;Current FC:]"local cur = data.fc[data.fcind]or""
form=form.."label[1.5,3.75;"..minetest.formspec_escape(cur).."]"
form=form.."button[3.5,3.5;1,1;fcn;next FC]"end
form=form.."button_exit[0.5,4.5;4,1;save;"..attrans("Save wagon properties").."]"
minetest.show_formspec(pname,"advtrains_prop_"..self.id, form)end--BordComlocal functioncheckcouple(ent)if not ent or not ent:getyaw()thenreturn nilendlocal le = ent:get_luaentity()if not le or not le.is_couple thenreturn nilendreturn le
endlocal functionchecklock(pname, own1, own2, wl1, wl2)return advtrains.check_driving_couple_protection(pname, own1, wl1)or advtrains.check_driving_couple_protection(pname, own2, wl2)endlocal functionsplit(str, sep)local fields = {}local pattern =string.format("([^%s]+)", sep)
str:gsub(pattern,function(c) fields[#fields+1] = c end)return fields
endfunction wagon.set_fc(data, fcstr)
data.fc =split(fcstr,"!")if not data.fcind then
data.fcind =1elseif data.fcind > #data.fc then
data.fcind = #data.fc
endendfunction wagon.prev_fc(data)if data.fcind >1then
data.fcind = data.fcind -1endif data.fcind ==1and data.fcrev then
data.fcrev =nilendendfunction wagon.next_fc(data)if not data.fc then return endif data.fcrev then
wagon.prev_fc(data)returnendif data.fcind < #data.fc then
data.fcind = data.fcind +1else
data.fcind =1endif data.fcind == #data.fc and data.fc[data.fcind] =="?"then
data.fcrev =true
wagon.prev_fc(data)returnendendfunction advtrains.get_cur_fc(data)if not( data.fc and data.fcind )thenreturn""endreturn data.fc[data.fcind]or""endfunction advtrains.step_fc(data)
wagon.next_fc(data)endfunction wagon:show_bordcom(pname)if not self:train()then return endlocal train = self:train()local data = advtrains.wagons[self.id]local linhei
local form ="size[11,9]label[0.5,0;AdvTrains Boardcom v0.1 | "..attrans("Train ID")..": "..(minetest.formspec_escape(train.id or"")).."]"
form=form.."textarea[0.5,1.5;7,1;text_outside;"..attrans("Text displayed outside on train")..";"..(minetest.formspec_escape(train.text_outside or"")).."]"
form=form.."textarea[0.5,3;7,1;text_inside;"..attrans("Text displayed inside train")..";"..(minetest.formspec_escape(train.text_inside or"")).."]"
form=form.."field[7.5,1.75;3,1;line;"..attrans("Line")..";"..(minetest.formspec_escape(train.line or"")).."]"
form=form.."field[7.5,3.25;3,1;routingcode;"..attrans("Routingcode")..";"..(minetest.formspec_escape(train.routingcode or"")).."]"--row 5 : train overview and autocouplingif train.velocity==0then
form=form.."label[0.5,4;Train overview /coupling control:]"
linhei=5local pre_own, pre_wl, owns_any =nil,nil, minetest.check_player_privs(pname,"train_admin")for i, tpid inipairs(train.trainparts)dolocal ent = advtrains.wagons[tpid]if ent thenlocal roadnumber = ent.roadnumber or""
form = form ..string.format("button[%d,%d;%d,%d;%s;%s]", i, linhei,1,0.2,"wgprp"..i, roadnumber)local ename = ent.type
form = form .."item_image["..i..","..(linhei+0.5)..";1,1;"..ename.."]"if i~=1thenifchecklock(pname, ent.owner, pre_own, ent.whitelist, pre_wl)then
form = form .."image_button["..(i-0.5)..","..(linhei+1.5)..";1,1;advtrains_discouple.png;dcpl_"..i..";]"endendif i == data.pos_in_trainparts then
form = form .."box["..(i-0.1)..","..(linhei+0.4)..";1,1;green]"end
pre_own = ent.owner
pre_wl = ent.whitelist
owns_any = owns_any or(not ent.owner or ent.owner==pname)endendif train.movedir==1then
form = form .."label["..(#train.trainparts+1)..","..(linhei)..";-->]"else
form = form .."label[0.5,"..(linhei)..";<--]"end--check cpl_eid_front and _back of trainlocal couple_front =checkcouple(train.cpl_front)local couple_back =checkcouple(train.cpl_back)if couple_front then
form = form .."image_button[0.5,"..(linhei+1)..";1,1;advtrains_couple.png;cpl_f;]"endif couple_back then
form = form .."image_button["..(#train.trainparts+0.5)..","..(linhei+1)..";1,1;advtrains_couple.png;cpl_b;]"endelse
form=form.."label[0.5,4.5;Train overview / coupling control is only shown when the train stands.]"end
form = form .."button[0.5,8;3,1;save;Save]"-- Interlocking functionality: If the interlocking module is loaded, you can set the signal aspect-- from inside the trainif advtrains.interlocking and train.lzb and#train.lzb.checkpoints >0thenlocal i=1while train.lzb.checkpoints[i]dolocal oci = train.lzb.checkpoints[i]if oci.udata and oci.udata.signal_pos thenif advtrains.interlocking.db.get_sigd_for_signal(oci.udata.signal_pos)then
form = form .."button[4.5,8;5,1;ilrs;Remote Routesetting]"breakendend
i=i+1endif train.ars_disable then
form = form .."button[4.5,7;5,1;ilarsenable;Clear 'Disable ARS' flag]"endend
minetest.show_formspec(pname,"advtrains_bordcom_"..self.id, form)endfunction wagon:handle_bordcom_fields(pname, formname, fields)local data = advtrains.wagons[self.id]local seatno=self:get_seatno(pname)if not seatno or not self.seat_groups[self.seats[seatno].group].driving_ctrl_access or not advtrains.check_driving_couple_protection(pname, data.owner, data.whitelist)thenreturnendlocal train = self:train()if not train then return endif fields.text_outside thenif fields.text_outside~=""then
train.text_outside=fields.text_outside
else
train.text_outside=nilendendif fields.text_inside thenif fields.text_inside~=""then
train.text_inside=fields.text_inside
else
train.text_inside=nilendendif fields.line thenif fields.line~=""thenif fields.line ~= train.line then
train.line=fields.line
minetest.after(0, advtrains.invalidate_path, train.id)endelse
train.line=nilendendif fields.routingcode thenif fields.routingcode~=""thenif fields.routingcode ~= train.routingcode then
train.routingcode=fields.routingcode
minetest.after(0, advtrains.invalidate_path, train.id)endelse
train.routingcode=nilendendfor i, tpid inipairs(train.trainparts)doif fields["dcpl_"..i]then
advtrains.safe_decouple_wagon(tpid, pname)elseif fields["wgprp"..i]thenfor _,wagon inpairs(minetest.luaentities)doif wagon.is_wagon and wagon.initialized and wagon.id==tpid and data.owner==pname then
wagon:show_wagon_properties(pname)returnendendendend--check cpl_eid_front and _back of trainlocal couple_front =checkcouple(train.cpl_front)local couple_back =checkcouple(train.cpl_back)if fields.cpl_f and couple_front then
couple_front:on_rightclick(pname)endif fields.cpl_b and couple_back then
couple_back:on_rightclick(pname)end-- Interlocking functionality: If the interlocking module is loaded, you can set the signal aspect-- from inside the trainif advtrains.interlocking thenif fields.ilrs and train.lzb and#train.lzb.checkpoints >0thenlocal i=1while train.lzb.checkpoints[i]dolocal oci = train.lzb.checkpoints[i]if oci.udata and oci.udata.signal_pos thenlocal sigd = advtrains.interlocking.db.get_sigd_for_signal(oci.udata.signal_pos)if sigd then
advtrains.interlocking.show_signalling_form(sigd, pname)returnendend
i=i+1endendif fields.ilarsenable then
advtrains.interlocking.ars_set_disable(train,false)endendif not fields.quit then
self:show_bordcom(pname)endend
minetest.register_on_player_receive_fields(function(player, formname, fields)local uid=string.match(formname,"^advtrains_geton_(.+)$")if uid thenfor _,wagon inpairs(minetest.luaentities)doif wagon.is_wagon and wagon.initialized and wagon.id==uid thenlocal data = advtrains.wagons[wagon.id]if fields.inv thenif wagon.has_inventory and wagon.get_inventory_formspec then
minetest.show_formspec(player:get_player_name(),"advtrains_inv_"..uid, wagon:get_inventory_formspec(player:get_player_name(),make_inv_name(uid)))endelseif fields.seat thenlocal val=minetest.explode_textlist_event(fields.seat)if val and val.type~="INV"and not data.seatp[player:get_player_name()]then--get on
wagon:get_on(player, val.index)--will work with the new close_formspec functionality. close exactly this formspec.
minetest.show_formspec(player:get_player_name(), formname,"")endendendendend
uid=string.match(formname,"^advtrains_seating_(.+)$")if uid thenfor _,wagon inpairs(minetest.luaentities)doif wagon.is_wagon and wagon.initialized and wagon.id==uid thenlocal pname=player:get_player_name()local no=wagon:get_seatno(pname)if no thenif wagon.seat_groups then
wagon:seating_from_key_helper(pname, fields, no)endendendendend
uid=string.match(formname,"^advtrains_prop_(.+)$")if uid thenlocal pname=player:get_player_name()local data = advtrains.wagons[uid]if pname~=data.owner and not minetest.check_player_privs(pname, {train_admin =true})thenreturn trueendif fields.save or not fields.quit thenif fields.whitelist then
data.whitelist = fields.whitelist
endif fields.roadnumber then
data.roadnumber = fields.roadnumber
endif fields.fc then
wagon.set_fc(data, fields.fc)endif fields.fcp then
wagon.prev_fc(data)
wagon.show_wagon_properties({id=uid}, pname)endif fields.fcn then
advtrains.step_fc(data)
wagon.show_wagon_properties({id=uid}, pname)endendend
uid=string.match(formname,"^advtrains_bordcom_(.+)$")if uid thenfor _,wagon inpairs(minetest.luaentities)doif wagon.is_wagon and wagon.initialized and wagon.id==uid then
wagon:handle_bordcom_fields(player:get_player_name(), formname, fields)endendend
uid=string.match(formname,"^advtrains_inv_(.+)$")if uid thenlocal pname=player:get_player_name()local data = advtrains.wagons[uid]if fields.prop and data.owner==pname thenfor _,wagon inpairs(minetest.luaentities)doif wagon.is_wagon and wagon.initialized and wagon.id==uid and data.owner==pname then
wagon:show_wagon_properties(pname)--wagon:handle_bordcom_fields(player:get_player_name(), formname, fields)endendendendend)function wagon:seating_from_key_helper(pname, fields, no)local data = advtrains.wagons[self.id]local sgr=self.seats[no].group
for _,access inipairs(self.seat_groups[sgr].access_to)doif fields["sgr_"..access]and self:check_seat_group_access(pname, access)thenfor seatid, seatdef inipairs(self.seats)doif seatdef.group==access and not data.seatp[seatid]then
self:get_on(minetest.get_player_by_name(pname), seatid)returnendendendendif fields.inv and self.has_inventory and self.get_inventory_formspec then
minetest.close_formspec(pname,"advtrains_seating_"..self.id)
minetest.show_formspec(pname,"advtrains_inv_"..self.id, self:get_inventory_formspec(pname,make_inv_name(self.id)))endif fields.prop and data.owner==pname then
minetest.close_formspec(pname,"advtrains_seating_"..self.id)
self:show_wagon_properties(pname)endif fields.bordcom and self.seat_groups[sgr].driving_ctrl_access and advtrains.check_driving_couple_protection(pname, data.owner, data.whitelist)then
minetest.close_formspec(pname,"advtrains_seating_"..self.id)
self:show_bordcom(pname)endif fields.dcwarn then
minetest.chat_send_player(pname,attrans("Doors are closed! Use Sneak+rightclick to ignore the closed doors and get off!"))endif fields.off then
self:get_off(no)endendfunction wagon:check_seat_group_access(pname, sgr)local data = advtrains.wagons[self.id]if self.seat_groups[sgr].driving_ctrl_access and not(advtrains.check_driving_couple_protection(pname, data.owner, data.whitelist))thenreturn false,"Not allowed to access a driver stand!"endif self.seat_groups[sgr].driving_ctrl_access then
advtrains.log("Drive", pname, self.object:getpos(), self:train().text_outside)endreturn trueendfunction wagon:reattach_all()local data = advtrains.wagons[self.id]if not data.seatp then data.seatp={}endfor seatno, pname inpairs(data.seatp)dolocal p=minetest.get_player_by_name(pname)if p then
self:get_on(p ,seatno)endendendfunction advtrains.safe_decouple_wagon(w_id, pname, try_run)if not minetest.check_player_privs(pname,"train_operator")then
minetest.chat_send_player(pname,"Missing train_operator privilege")return falseendlocal data = advtrains.wagons[w_id]local dpt = data.pos_in_trainparts
if not dpt or dpt <=1thenreturn falseendlocal train = advtrains.trains[data.train_id]local owid = train.trainparts[dpt-1]local owdata = advtrains.wagons[owid]if not owdata thenreturnendif notchecklock(pname, data.owner, owdata.owner, data.whitelist, owdata.whitelist)then
minetest.chat_send_player(pname,"Not allowed to do this.")return falseendif try_run thenreturn trueend
advtrains.log("Discouple", pname, train.last_pos, train.text_outside)
advtrains.split_train_at_wagon(w_id)return trueendfunction advtrains.get_wagon_prototype(data)local wt = data.typeif not wt then-- LEGACY: Field was called "entity_name" in previous versions
wt = data.entity_name
data.type= data.entity_name
data.entity_name =nilendif not wt or not advtrains.wagon_prototypes[wt]thenatwarn("Unable to load wagon type",wt,", using placeholder")
wt="advtrains:wagon_placeholder"endreturn wt, advtrains.wagon_prototypes[wt]endfunction advtrains.standard_inventory_formspec(self, pname, invname)--[[minetest.chat_send_player(pname, string.format("self=%s, pname=%s, invname=%s", self, pname, invname)) for k,v in pairs(self) do minetest.chat_send_player(pname, string.format("%s=%s", k,v)) end minetest.chat_send_player(pname, string.format("***%s***", self.object:get_pos()))--]]local data = advtrains.wagons[self.id]local r ="size[8,11]".."list["..invname..";box;0,0;8,3;]"if data.owner==pname then
r = r .."button_exit[0,9;4,1;prop;"..attrans("Wagon properties").."]"end
r = r .."list[current_player;main;0,5;8,4;]".."listring[]"return r
endfunction advtrains.register_wagon(sysname_p, prototype, desc, inv_img, nincreative)local sysname = sysname_p
if notstring.match(sysname,":")then
sysname ="advtrains:"..sysname_p
endsetmetatable(prototype, {__index=wagon})
minetest.register_entity(":"..sysname,prototype)
advtrains.wagon_prototypes[sysname] = prototype
--group classification to make recipe searching easierlocal wagon_groups = { not_in_creative_inventory = nincreative and1or0}if prototype.is_locomotive then wagon_groups['at_loco'] =1endif prototype.seat_groups thenif prototype.seat_groups.dstand then wagon_groups['at_control'] =1endif prototype.seat_groups.pass then wagon_groups['at_pax'] =1endendif prototype.has_inventory then wagon_groups['at_freight'] =1end
minetest.register_craftitem(":"..sysname, {
description = desc,
inventory_image = inv_img,
wield_image = inv_img,
stack_max =1,
groups = wagon_groups,
on_place =function(itemstack, placer, pointed_thing)if not pointed_thing.type=="node"thenreturnendlocal pname = placer:get_player_name()local node=minetest.get_node_or_nil(pointed_thing.under)if not node thenatprint("[advtrains]Ignore at placer position")return itemstack endlocal nodename=node.name
if(not advtrains.is_track_and_drives_on(nodename, prototype.drives_on))thenatprint("no track here, not placing.")return itemstack
endif not minetest.check_player_privs(placer, {train_operator =true})then
minetest.chat_send_player(pname,"You don't have the train_operator privilege.")return itemstack
endif not minetest.check_player_privs(placer, {train_admin =true})and minetest.is_protected(pointed_thing.under, placer:get_player_name())thenreturn itemstack
endlocal tconns=advtrains.get_track_connections(node.name, node.param2)local yaw = placer:get_look_horizontal()local plconnid = advtrains.yawToClosestConn(yaw, tconns)local prevpos = advtrains.get_adjacent_rail(pointed_thing.under, tconns, plconnid, prototype.drives_on)if not prevpos then
minetest.chat_send_player(pname,"The track you are trying to place the wagon on is not long enough!")returnendlocal wid = advtrains.create_wagon(sysname, pname)local id=advtrains.create_new_train_at(pointed_thing.under, plconnid,0, {wid})if not advtrains.is_creative(pname)then
itemstack:take_item()endreturn itemstack
end,})end-- Placeholder wagon. Will be spawned whenever a mod is missing
advtrains.register_wagon("advtrains:wagon_placeholder", {
visual="sprite",
textures = {"advtrains_wagon_placeholder.png"},
collisionbox = {-0.3,-0.3,-0.3,0.3,0.3,0.3},
visual_size = {x=0.7, y=0.7},
initial_sprite_basepos = {x=0, y=0},
drives_on = advtrains.all_tracktypes,
max_speed =5,
seats = {},
seat_groups = {},
assign_to_seat_group = {},
wagon_span=1,
drops={},},"Wagon placeholder","advtrains_wagon_placeholder.png",true)-- Helper function to retrieve the wagon at a certain position in a train, given its train ID and the desired index within that train's path---- Returns: wagon_num, wagon_id, wagon_data, offset_from_center-- wagon_num: The n'th wagon in the train (index into "trainparts" table)-- wagon_id: The wagon ID. Obtain wagon data from advtrains.wagons[wagon_id], and subsequently the wagon prototype via advtrains.get_wagon_prototype(data)-- offset_from_center: The offset (an absolute distance value) from the center point of the wagon. Positive is towards the end of the train, negative towards the start. (note that this is inverse to the counting direction of the index!)----[[ To get the wagon standing at a certain world position, you first need to retrieve the index via the occupation table, as follows: local trains = advtrains.occ.get_trains_at(pos) for train_id, index in pairs(trains) do local wagon_num, wagon_id, wagon_data, offset_from_center = advtrains.get_wagon_at_index(train_id, index) if wagon_num then ... end end]]--function advtrains.get_wagon_at_index(train_id, w_index)local train = advtrains.trains[train_id]if not train thenerror("Passed train id "..train_id.." doesnt exist")end-- ensure init - always required
advtrains.train_ensure_init(train_id, train)-- Use path dist to determine the offset from the start of the trainlocal dstart = advtrains.path_get_path_dist_fractional(train, train.index)local dtarget = advtrains.path_get_path_dist_fractional(train, w_index)local dist_from_start = dstart - dtarget -- NOTE: dist_from_start is supposed to be positive, but dtarget will be smaller than dstart-- if dist_from_start is <0, we are outside of trainif dist_from_start <0thenreturn nilend-- scan over wagons to see if dist_from_start falls into its windowlocal start_pos =0local center_pos
local end_pos
local i =1while train.trainparts[i]dolocal w_id = train.trainparts[i]-- get wagon prototype to retrieve wagon spanlocal wdata = advtrains.wagons[w_id]if wdata thenlocal wtype, wproto = advtrains.get_wagon_prototype(wdata)local wagon_span = wproto.wagon_span
-- determine center and end pos
center_pos = start_pos + wagon_span
end_pos = center_pos + wagon_span
if start_pos <= dist_from_start and dist_from_start < end_pos then-- Found the correct wagon in the train!local offset_from_center = dist_from_start - center_pos
return i, w_id, wdata, offset_from_center
end-- go on
start_pos = end_pos
elseerror("Wagon "..w_id.." from train "..train_id.." doesnt exist!")end
i = i +1end-- nothing found, dist must be further backreturn nilend