2016-05-29 11:27:30 -07:00
--atan2 counts angles clockwise, minetest does counterclockwise
2016-12-28 15:27:07 -08:00
minetest.register_privilege ( " train_place " , {
description = " Player can place trains on tracks not owned by player " ,
give_to_singleplayer = false ,
} ) ;
minetest.register_privilege ( " train_remove " , {
description = " Player can remove trains not owned by player " ,
give_to_singleplayer = false ,
} ) ;
2017-04-09 06:15:45 -07:00
minetest.register_privilege ( " train_operator " , {
description = " Player may operate trains and switch signals. Given by default. Revoke to prevent players from griefing automated subway systems. " ,
give_to_singleplayer = true ,
default = true ,
} ) ;
2016-12-28 15:27:07 -08:00
2016-05-29 11:27:30 -07:00
local wagon = {
collisionbox = { - 0.5 , - 0.5 , - 0.5 , 0.5 , 0.5 , 0.5 } ,
--physical = true,
visual = " mesh " ,
mesh = " wagon.b3d " ,
visual_size = { x = 3 , y = 3 } ,
textures = { " black.png " } ,
is_wagon = true ,
wagon_span = 1 , --how many index units of space does this wagon consume
2016-11-03 03:27:17 -07:00
has_inventory = false ,
2016-05-29 11:27:30 -07:00
}
function wagon : train ( )
return advtrains.trains [ self.train_id ]
end
2016-11-02 03:17:42 -07:00
--[[about 'initalized':
when initialized is false , the entity hasn ' t got any data yet and should wait for these to be set before doing anything
when loading an existing object ( with staticdata ) , it will be set
when instanciating a new object via add_entity , it is not set at the time on_activate is called .
then , wagon : initialize ( ) will be called
wagon will save only uid in staticdata , no serialized table
] ]
function wagon : on_activate ( sd_uid , dtime_s )
2017-01-23 12:36:38 -08:00
if sd_uid ~= " " then
--destroy when loaded from static block.
self.object : remove ( )
return
2016-05-29 11:27:30 -07:00
end
2017-01-23 12:36:38 -08:00
self.object : set_armor_groups ( { immortal = 1 } )
2016-05-30 10:58:09 -07:00
self.entity_name = self.name
2016-05-29 11:27:30 -07:00
end
function wagon : get_staticdata ( )
2017-04-29 10:44:43 -07:00
return advtrains.pcall ( function ( )
if not self : ensure_init ( ) then return end
atprint ( " [wagon " .. ( ( self.unique_id and self.unique_id ~= " " and self.unique_id ) or " no-id " ) .. " ]: saving to wagon_save " )
--serialize inventory, if it has one
if self.has_inventory then
local inv = minetest.get_inventory ( { type = " detached " , name = " advtrains_wgn_ " .. self.unique_id } )
self.ser_inv = advtrains.serialize_inventory ( inv )
end
--save to table before being unloaded
2018-01-07 10:00:43 -08:00
advtrains.wagon_save [ self.unique_id ] = advtrains.save_keys ( self , {
" seatp " , " owner " , " ser_inv " , " wagon_flipped " , " train_id "
} )
2017-04-29 10:44:43 -07:00
advtrains.wagon_save [ self.unique_id ] . entity_name = self.name
return self.unique_id
end )
2016-11-02 03:17:42 -07:00
end
--returns: uid of wagon
function wagon : init_new_instance ( train_id , properties )
2018-01-07 10:00:43 -08:00
local new_id = advtrains.random_id ( )
while advtrains.wagon_save [ new_id ] do new_id = advtrains.random_id ( ) end --ensure uniqueness
self.unique_id = new_id
2016-11-02 03:17:42 -07:00
self.train_id = train_id
for k , v in pairs ( properties ) do
if k ~= " name " and k ~= " object " then
self [ k ] = v
end
end
2016-11-03 03:27:17 -07:00
self : init_shared ( )
2016-11-02 03:17:42 -07:00
self.initialized = true
2017-01-04 12:34:18 -08:00
atprint ( " init_new_instance " .. self.unique_id .. " ( " .. self.train_id .. " ) " )
2016-11-02 03:17:42 -07:00
return self.unique_id
end
function wagon : init_from_wagon_save ( uid )
if not advtrains.wagon_save [ uid ] then
self.object : remove ( )
return
end
self.unique_id = uid
for k , v in pairs ( advtrains.wagon_save [ uid ] ) do
if k ~= " name " and k ~= " object " then
self [ k ] = v
end
end
if not self.train_id or not self : train ( ) then
self.object : remove ( )
return
end
2016-11-03 03:27:17 -07:00
self : init_shared ( )
2016-11-02 03:17:42 -07:00
self.initialized = true
2017-01-28 08:06:38 -08:00
minetest.after ( 0.2 , function ( ) self : reattach_all ( ) end )
2017-01-04 12:34:18 -08:00
atprint ( " init_from_wagon_save " .. self.unique_id .. " ( " .. self.train_id .. " ) " )
2016-11-02 03:17:42 -07:00
end
2016-11-03 03:27:17 -07:00
function wagon : init_shared ( )
if self.has_inventory then
local uid_noptr = self.unique_id .. " "
--to be used later
local inv = minetest.create_detached_inventory ( " advtrains_wgn_ " .. self.unique_id , {
allow_move = function ( inv , from_list , from_index , to_list , to_index , count , player )
return count
end ,
allow_put = function ( inv , listname , index , stack , player )
return stack : get_count ( )
end ,
allow_take = function ( inv , listname , index , stack , player )
return stack : get_count ( )
end
} )
if self.ser_inv then
advtrains.deserialize_inventory ( self.ser_inv , inv )
end
if self.inventory_list_sizes then
for lst , siz in pairs ( self.inventory_list_sizes ) do
inv : set_size ( lst , siz )
end
end
end
2017-01-18 10:03:27 -08:00
if self.doors then
self.door_anim_timer = 0
self.door_state = 0
end
if self.custom_on_activate then
self : custom_on_activate ( dtime_s )
end
2017-10-23 04:56:59 -07:00
-- reset line and infotext cache to update object properties on first call
self.line_cache = nil
self.infotext_cache = nil
2016-11-03 03:27:17 -07:00
end
2016-11-02 03:17:42 -07:00
function wagon : ensure_init ( )
2017-01-10 13:54:10 -08:00
if self.initialized then
if self.noninitticks then self.noninitticks = nil end
return true
end
if not self.noninitticks then self.noninitticks = 0 end
self.noninitticks = self.noninitticks + 1
if self.noninitticks > 20 then
self.object : remove ( )
else
self.object : setvelocity ( { x = 0 , y = 0 , z = 0 } )
end
2016-11-02 03:17:42 -07:00
return false
2016-05-29 11:27:30 -07:00
end
-- Remove the wagon
function wagon : on_punch ( puncher , time_from_last_punch , tool_capabilities , direction )
2017-04-29 10:44:43 -07:00
return advtrains.pcall ( function ( )
if not self : ensure_init ( ) then return end
if not puncher or not puncher : is_player ( ) then
2016-09-28 14:27:47 -07:00
return
end
2017-04-29 10:44:43 -07:00
if self.owner and puncher : get_player_name ( ) ~= self.owner and ( not minetest.check_player_privs ( puncher , { train_remove = true } ) ) then
minetest.chat_send_player ( puncher : get_player_name ( ) , attrans ( " This wagon is owned by @1, you can't destroy it. " , self.owner ) ) ;
return
end
2018-01-09 09:30:29 -08:00
if # ( self : train ( ) . trainparts ) > 1 then
minetest.chat_send_player ( puncher : get_player_name ( ) , attrans ( " Wagon needs to be decoupled from other wagons in order to destroy it. " ) ) ;
return
end
2017-04-29 10:44:43 -07:00
2018-01-09 09:30:29 -08:00
local 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. " ) )
return
end
2016-05-29 11:27:30 -07:00
2018-01-09 09:30:29 -08:00
if not self : destroy ( ) then return end
2016-09-28 14:27:47 -07:00
2018-01-09 09:30:29 -08:00
local inv = puncher : get_inventory ( )
for _ , item in ipairs ( self.drops or { self.name } ) do
inv : add_item ( " main " , item )
2016-09-28 14:27:47 -07:00
end
2017-04-29 10:44:43 -07:00
end )
2016-09-28 14:27:47 -07:00
end
function wagon : destroy ( )
--some rules:
-- you get only some items back
-- single left-click shows warning
-- shift leftclick destroys
-- not when a driver is inside
2016-10-22 11:30:10 -07:00
for _ , _ in pairs ( self.seatp ) do
return
end
2016-09-28 14:27:47 -07:00
2016-09-28 14:12:01 -07:00
if self.custom_may_destroy then
if not self.custom_may_destroy ( self , puncher , time_from_last_punch , tool_capabilities , direction ) then
return
end
end
if self.custom_on_destroy then
self.custom_on_destroy ( self , puncher , time_from_last_punch , tool_capabilities , direction )
end
2016-09-28 14:27:47 -07:00
2017-01-08 12:10:02 -08:00
atprint ( " [wagon " .. ( ( self.unique_id and self.unique_id ~= " " and self.unique_id ) or " no-id " ) .. " ]: destroying " )
2016-11-02 03:17:42 -07:00
2016-09-28 14:12:01 -07:00
self.object : remove ( )
2016-05-29 11:27:30 -07:00
2016-09-28 14:12:01 -07:00
table.remove ( self : train ( ) . trainparts , self.pos_in_trainparts )
advtrains.update_trainpart_properties ( self.train_id )
advtrains.wagon_save [ self.unique_id ] = nil
if self.discouple then self.discouple . object : remove ( ) end --will have no effect on unloaded objects
2016-10-22 11:30:10 -07:00
return true
2016-05-29 11:27:30 -07:00
end
2016-09-28 14:12:01 -07:00
2016-05-29 11:27:30 -07:00
function wagon : on_step ( dtime )
2017-04-29 10:44:43 -07:00
return advtrains.pcall ( function ( )
if not self : ensure_init ( ) then return end
local t = os.clock ( )
local pos = self.object : getpos ( )
if not pos then
atprint ( " [ " .. self.unique_id .. " ][fatal] missing position (object:getpos() returned nil) " )
return
end
2016-09-28 14:12:01 -07:00
2017-04-29 10:44:43 -07:00
self.entity_name = self.name
--is my train still here
if not self.train_id or not self : train ( ) then
atprint ( " [wagon " .. self.unique_id .. " ] missing train_id, destroying " )
self.object : remove ( )
return
end
if not self.seatp then
self.seatp = { }
end
if not self.seatpc then
self.seatpc = { }
end
2016-09-28 14:12:01 -07:00
2017-04-29 10:44:43 -07:00
--custom on_step function
if self.custom_on_step then
self : custom_on_step ( self , dtime )
end
2016-09-28 14:12:01 -07:00
2017-04-29 10:44:43 -07:00
--driver control
for seatno , seat in ipairs ( self.seats ) do
local driver = self.seatp [ seatno ] and minetest.get_player_by_name ( self.seatp [ seatno ] )
2018-01-09 09:30:29 -08:00
local has_driverstand = self.seatp [ seatno ] and minetest.check_player_privs ( self.seatp [ seatno ] , { train_operator = true } )
if self.seat_groups then
has_driverstand = has_driverstand and ( seat.driving_ctrl_access or self.seat_groups [ seat.group ] . driving_ctrl_access )
else
has_driverstand = has_driverstand and ( seat.driving_ctrl_access )
end
2017-04-29 10:44:43 -07:00
if has_driverstand and driver then
advtrains.update_driver_hud ( driver : get_player_name ( ) , self : train ( ) , self.wagon_flipped )
elseif driver then
--only show the inside text
local inside = self : train ( ) . text_inside or " "
advtrains.set_trainhud ( driver : get_player_name ( ) , inside )
2016-05-29 11:27:30 -07:00
end
2017-04-29 10:44:43 -07:00
if driver and driver : get_player_control_bits ( ) ~= self.seatpc [ seatno ] then
local 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 ( ) , self.wagon_flipped )
2017-12-06 04:23:55 -08:00
--sound horn when required
if 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 = nil
end
2017-01-18 10:03:27 -08:00
else
2017-04-29 10:44:43 -07:00
-- If on a passenger seat and doors are open, get off when W or D pressed.
local pass = self.seatp [ seatno ] and minetest.get_player_by_name ( self.seatp [ seatno ] )
if pass and self : train ( ) . door_open ~= 0 then
local pc = pass : get_player_control ( )
if pc.up or pc.down then
self : get_off ( seatno )
end
end
end
if pc.aux1 and pc.sneak then
self : get_off ( seatno )
2017-01-18 10:03:27 -08:00
end
end
end
2017-04-29 10:44:43 -07:00
--check infotext
local outside = self : train ( ) . text_outside or " "
2017-12-18 14:20:29 -08:00
2017-12-18 14:44:01 -08:00
local gp = self : train ( )
2017-12-18 14:20:29 -08:00
--show off-track information in outside text instead of notifying the whole server about this
2017-12-18 14:44:01 -08:00
local front_off_track = gp.max_index_on_track and gp.index and gp.index > gp.max_index_on_track
local back_off_track = gp.min_index_on_track and gp.end_index and gp.end_index < gp.min_index_on_track
2017-12-18 14:20:29 -08:00
if front_off_track or back_off_track then
outside = outside .. " \n !!! Train off track !!! "
end
2017-10-23 04:56:59 -07:00
if self.infotext_cache ~= outside then
2017-05-30 05:55:41 -07:00
self.object : set_properties ( { infotext = outside } )
2017-10-23 04:56:59 -07:00
self.infotext_cache = outside
2017-04-29 10:44:43 -07:00
end
local fct = self.wagon_flipped and - 1 or 1
2017-10-23 04:33:27 -07:00
--set line number
2017-10-23 04:56:59 -07:00
if self.name == " advtrains:subway_wagon " and gp.line and gp.line ~= self.line_cache then
local new_line_tex = " advtrains_subway_wagon.png^advtrains_subway_wagon_line " .. gp.line .. " .png "
self.object : set_properties ( {
textures = { new_line_tex } ,
} )
self.line_cache = gp.line
2017-10-23 05:02:40 -07:00
elseif self.line_cache ~= nil and gp.line == nil then
2017-10-23 04:56:59 -07:00
self.object : set_properties ( {
textures = self.textures ,
} )
self.line_cache = nil
2017-10-23 04:33:27 -07:00
end
2017-04-29 10:44:43 -07:00
--door animation
if self.doors then
if ( self.door_anim_timer or 0 ) <= 0 then
local dstate = ( gp.door_open or 0 ) * fct
if dstate ~= self.door_state then
local 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 == 0 then
2017-12-06 04:23:55 -08:00
if self.doors . open.sound then minetest.sound_play ( self.doors . open.sound , { object = self.object } ) end
2017-04-29 10:44:43 -07:00
at = self.doors . open [ dstate ]
self.object : set_animation ( at.frames , at.speed or 15 , at.blend or 0 , false )
self.door_state = dstate
else
2017-12-06 04:23:55 -08:00
if self.doors . close.sound then minetest.sound_play ( self.doors . close.sound , { object = self.object } ) end
2017-04-29 10:44:43 -07:00
at = self.doors . close [ self.door_state or 1 ] --in case it has not been set yet
self.object : set_animation ( at.frames , at.speed or 15 , at.blend or 0 , false )
self.door_state = 0
end
self.door_anim_timer = at.time
2016-06-09 12:50:11 -07:00
end
2017-04-29 10:44:43 -07:00
else
self.door_anim_timer = ( self.door_anim_timer or 0 ) - dtime
2016-06-09 12:50:11 -07:00
end
end
2017-04-29 10:44:43 -07:00
--DisCouple
if self.pos_in_trainparts and self.pos_in_trainparts > 1 then
2018-01-07 10:00:43 -08:00
if gp.velocity == 0 then
2017-04-29 10:44:43 -07:00
if not self.discouple or not self.discouple . object : getyaw ( ) then
local object = minetest.add_entity ( pos , " advtrains:discouple " )
if object then
local 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
else
atprint ( " Couldn't spawn DisCouple " )
2017-02-28 07:25:32 -08:00
end
2017-02-21 02:38:17 -08:00
end
2017-04-29 10:44:43 -07:00
else
if self.discouple and self.discouple . object : getyaw ( ) then
self.discouple . object : remove ( )
end
2017-02-08 15:11:28 -08:00
end
end
2017-04-29 10:44:43 -07:00
--for path to be available. if not, skip step
if not gp.path then
self.object : setvelocity ( { x = 0 , y = 0 , z = 0 } )
return
end
if not self.pos_in_train then
--why ever. but better continue next step...
advtrains.update_trainpart_properties ( self.train_id )
return
end
local index = advtrains.get_real_path_index ( self : train ( ) , self.pos_in_train )
--atprint("trainindex "..gp.index.." wagonindex "..index)
--automatic get_on
--needs to know index and path
if self.door_entry and gp.door_open and gp.door_open ~= 0 and gp.velocity == 0 then
--using the mapping created by the trainlogic globalstep
for i , ino in ipairs ( self.door_entry ) do
--fct is the flipstate flag from door animation above
local aci = index + ino * fct
local ix1 = gp.path [ math.floor ( aci ) ]
local ix2 = gp.path [ math.floor ( aci + 1 ) ]
-- 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 ) * gp.door_open , y = 0 , z = ( ix1.x - ix2.x ) * gp.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 " ) > 0 then
local ckpts = {
pts1 ,
pts2 ,
vector.add ( pts1 , { x = 0 , y = 1 , z = 0 } ) ,
vector.add ( pts2 , { x = 0 , y = 1 , z = 0 } ) ,
}
for _ , ckpos in ipairs ( ckpts ) do
local cpp = minetest.pos_to_string ( ckpos )
if advtrains.playersbypts [ cpp ] then
self : on_rightclick ( advtrains.playersbypts [ cpp ] )
end
2016-05-29 11:27:30 -07:00
end
end
end
end
2017-04-29 10:44:43 -07:00
--position recalculation
local first_pos = gp.path [ math.floor ( index ) ]
local second_pos = gp.path [ math.floor ( index ) + 1 ]
if not first_pos or not second_pos then
--atprint(" object "..self.unique_id.." path end reached!")
self.object : setvelocity ( { x = 0 , y = 0 , z = 0 } )
return
end
--checking for environment collisions(a 3x3 cube around the center)
if not gp.recently_collided_with_env then
local collides = false
2017-11-23 08:00:39 -08:00
local exh = self.extent_h or 1
local exv = self.extent_v or 2
for x =- exh , exh do
for y = 0 , exv do
for z =- exh , exh do
2017-04-29 10:44:43 -07:00
local node = minetest.get_node_or_nil ( vector.add ( first_pos , { x = x , y = y , z = z } ) )
if ( advtrains.train_collides ( node ) ) then
collides = true
end
end
end
end
if collides then
if self.collision_count and self.collision_count > 10 then
--enable collision mercy to get trains stuck in walls out of walls
--actually do nothing except limiting the velocity to 1
gp.velocity = math.min ( gp.velocity , 1 )
gp.tarvelocity = math.min ( gp.tarvelocity , 1 )
else
gp.recently_collided_with_env = true
gp.velocity = 2 * gp.velocity
gp.movedir =- gp.movedir
gp.tarvelocity = 0
self.collision_count = ( self.collision_count or 0 ) + 1
end
2017-01-18 11:51:47 -08:00
else
2017-04-29 10:44:43 -07:00
self.collision_count = nil
2017-01-18 11:51:47 -08:00
end
2017-04-29 10:44:43 -07:00
end
--FIX: use index of the wagon, not of the train.
local velocity = ( gp.velocity * gp.movedir ) / ( gp.path_dist [ math.floor ( index ) ] or 1 )
local acceleration = ( gp.last_accel or 0 ) / ( gp.path_dist [ math.floor ( index ) ] or 1 )
local factor = index - math.floor ( index )
local actual_pos = { x = first_pos.x - ( first_pos.x - second_pos.x ) * factor , y = first_pos.y - ( first_pos.y - second_pos.y ) * factor , z = first_pos.z - ( first_pos.z - second_pos.z ) * factor , }
local velocityvec = { x = ( first_pos.x - second_pos.x ) * velocity *- 1 , z = ( first_pos.z - second_pos.z ) * velocity *- 1 , y = ( first_pos.y - second_pos.y ) * velocity *- 1 }
local accelerationvec = { x = ( first_pos.x - second_pos.x ) * acceleration *- 1 , z = ( first_pos.z - second_pos.z ) * acceleration *- 1 , y = ( first_pos.y - second_pos.y ) * acceleration *- 1 }
--some additional positions to determine orientation
local aposfwd = gp.path [ math.floor ( index + 2 ) ]
local aposbwd = gp.path [ math.floor ( index - 1 ) ]
local yaw
if aposfwd and aposbwd then
yaw = advtrains.get_wagon_yaw ( aposfwd , second_pos , first_pos , aposbwd , factor ) + math.pi --TODO remove when cleaning up
2017-01-18 11:51:47 -08:00
else
2017-04-29 10:44:43 -07:00
yaw = math.atan2 ( ( first_pos.x - second_pos.x ) , ( second_pos.z - first_pos.z ) )
2016-05-29 11:27:30 -07:00
end
2017-04-29 10:44:43 -07:00
if self.wagon_flipped then
yaw = yaw + math.pi
2016-06-09 08:35:06 -07:00
end
2017-04-29 10:44:43 -07:00
self.updatepct_timer = ( self.updatepct_timer or 0 ) - dtime
if 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 ~= yaw
or self.updatepct_timer <= 0 then --only send update packet if something changed
self.object : setpos ( actual_pos )
self.object : setvelocity ( velocityvec )
self.object : setacceleration ( accelerationvec )
2017-11-23 10:59:21 -08:00
if # self.seats > 0 and self.old_yaw ~= yaw then
2018-01-07 10:00:43 -08:00
if not self.player_yaw then
self.player_yaw = { }
end
if not self.old_yaw then
self.old_yaw = yaw
end
for _ , name in pairs ( self.seatp ) do
local p = minetest.get_player_by_name ( name )
if p then
if 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 ] + yaw )
end
end
self.turning = true
2017-11-23 10:59:21 -08:00
elseif self.old_yaw == yaw then
2018-01-07 10:00:43 -08:00
-- train is no longer turning
self.turning = false
2017-11-23 10:59:21 -08:00
end
2017-04-29 10:44:43 -07:00
self.object : setyaw ( yaw )
self.updatepct_timer = 2
if self.update_animation then
2017-11-29 08:20:46 -08:00
self : update_animation ( gp.velocity , self.old_velocity )
end
if self.custom_on_velocity_change then
2018-01-07 11:52:15 -08:00
self : custom_on_velocity_change ( gp.velocity , self.old_velocity or 0 , dtime )
2017-04-29 10:44:43 -07:00
end
end
self.old_velocity_vector = velocityvec
2017-11-29 08:20:46 -08:00
self.old_velocity = gp.velocity
2017-04-29 10:44:43 -07:00
self.old_acceleration_vector = accelerationvec
self.old_yaw = yaw
atprintbm ( " wagon step " , t )
end )
2016-06-02 05:42:01 -07:00
end
function advtrains . get_real_path_index ( train , pit )
local pos_in_train_left = pit
local index = train.index
if pos_in_train_left > ( index - math.floor ( index ) ) * ( train.path_dist [ math.floor ( index ) ] or 1 ) then
pos_in_train_left = pos_in_train_left - ( index - math.floor ( index ) ) * ( train.path_dist [ math.floor ( index ) ] or 1 )
index = math.floor ( index )
while pos_in_train_left > ( train.path_dist [ index - 1 ] or 1 ) do
pos_in_train_left = pos_in_train_left - ( train.path_dist [ index - 1 ] or 1 )
index = index - 1
end
index = index - ( pos_in_train_left / ( train.path_dist [ index - 1 ] or 1 ) )
else
2016-08-21 07:49:48 -07:00
index = index - ( pos_in_train_left / ( train.path_dist [ math.floor ( index - 1 ) ] or 1 ) )
2016-06-02 05:42:01 -07:00
end
return index
2016-05-29 11:27:30 -07:00
end
2017-01-25 12:23:54 -08:00
function wagon : on_rightclick ( clicker )
2017-04-29 10:44:43 -07:00
return advtrains.pcall ( function ( )
if not self : ensure_init ( ) then return end
if not clicker or not clicker : is_player ( ) then
return
end
if clicker : get_player_control ( ) . aux1 then
--advtrains.dumppath(self:train().path)
--minetest.chat_send_all("at index "..(self:train().index or "nil"))
--advtrains.invert_train(self.train_id)
atprint ( dump ( self ) )
return
end
local pname = clicker : get_player_name ( )
local no = self : get_seatno ( pname )
if no then
if self.seat_groups then
local poss = { }
local sgr = self.seats [ no ] . group
for _ , access in ipairs ( self.seat_groups [ sgr ] . access_to ) do
if self : check_seat_group_access ( pname , access ) then
poss [ # poss + 1 ] = { name = self.seat_groups [ access ] . name , key = " sgr_ " .. access }
end
2017-01-25 12:23:54 -08:00
end
2017-04-29 10:44:43 -07:00
if self.has_inventory and self.get_inventory_formspec then
poss [ # poss + 1 ] = { name = attrans ( " Show Inventory " ) , key = " inv " }
end
2018-01-09 09:30:29 -08:00
if self.seat_groups [ sgr ] . driving_ctrl_access and minetest.check_player_privs ( pname , " train_operator " ) then
poss [ # poss + 1 ] = { name = attrans ( " Bord Computer " ) , key = " bordcom " }
end
2017-04-29 10:44:43 -07:00
if self.owner == pname then
poss [ # poss + 1 ] = { name = attrans ( " Wagon properties " ) , key = " prop " }
end
if not self.seat_groups [ sgr ] . require_doors_open or self : train ( ) . door_open ~= 0 then
poss [ # poss + 1 ] = { name = attrans ( " Get off " ) , key = " off " }
2017-01-25 12:36:17 -08:00
else
2017-04-29 10:44:43 -07:00
if 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 " }
end
2017-01-25 12:36:17 -08:00
end
2017-04-29 10:44:43 -07:00
if # poss == 0 then
--can't do anything.
elseif # poss == 1 then
self : seating_from_key_helper ( pname , { [ poss [ 1 ] . key ] = true } , no )
else
local form = " size[5, " .. 1 + ( # poss ) .. " ] "
for pos , ent in ipairs ( 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.unique_id , form )
2017-01-25 12:23:54 -08:00
end
2017-04-29 10:44:43 -07:00
else
self : get_off ( no )
2017-01-25 12:23:54 -08:00
end
else
2017-04-29 10:44:43 -07:00
--do not attach if already on a train
if advtrains.player_to_train_mapping [ pname ] then return end
if self.seat_groups then
if # self.seats == 0 then
if self.has_inventory and self.get_inventory_formspec then
minetest.show_formspec ( pname , " advtrains_inv_ " .. self.unique_id , self : get_inventory_formspec ( pname ) )
end
return
2017-01-25 12:23:54 -08:00
end
2017-04-29 10:44:43 -07:00
local doors_open = self : train ( ) . door_open ~= 0 or clicker : get_player_control ( ) . sneak
2018-01-09 09:30:29 -08:00
local allow , rsn = false , " unknown reason "
2017-04-29 10:44:43 -07:00
for _ , sgr in ipairs ( self.assign_to_seat_group ) do
2018-01-09 09:30:29 -08:00
allow , rsn = self : check_seat_group_access ( pname , sgr )
if allow then
2017-04-29 10:44:43 -07:00
for seatid , seatdef in ipairs ( self.seats ) do
2018-01-09 09:30:29 -08:00
if seatdef.group == sgr then
if ( not self.seat_groups [ sgr ] . require_doors_open or doors_open ) then
if not self.seatp [ seatid ] then
self : get_on ( clicker , seatid )
return
else
rsn = " Wagon is full. "
end
else
rsn = " Doors are closed! (try holding sneak key!) "
end
2017-04-29 10:44:43 -07:00
end
2017-01-25 12:23:54 -08:00
end
end
end
2018-01-09 09:30:29 -08:00
minetest.chat_send_player ( pname , attrans ( " Can't get on: " .. rsn ) )
2017-04-29 10:44:43 -07:00
else
self : show_get_on_form ( pname )
2017-01-25 12:23:54 -08:00
end
end
2017-04-29 10:44:43 -07:00
end )
2017-01-25 12:23:54 -08:00
end
2016-10-22 11:30:10 -07:00
function wagon : get_on ( clicker , seatno )
2017-02-08 15:11:28 -08:00
if not self.seatp then self.seatp = { } end
if not self.seatpc then self.seatpc = { } end --player controls in driver stands
2016-10-22 11:30:10 -07:00
if not self.seats [ seatno ] then return end
2017-01-25 12:23:54 -08:00
local oldno = self : get_seatno ( clicker : get_player_name ( ) )
if oldno then
atprint ( " get_on: clearing oldno " , seatno )
advtrains.player_to_train_mapping [ clicker : get_player_name ( ) ] = nil
advtrains.clear_driver_hud ( clicker : get_player_name ( ) )
self.seatp [ oldno ] = nil
end
2016-11-24 11:25:07 -08:00
if self.seatp [ seatno ] and self.seatp [ seatno ] ~= clicker : get_player_name ( ) then
2017-01-25 12:23:54 -08:00
atprint ( " get_on: throwing off " , self.seatp [ seatno ] , " from seat " , seatno )
2016-10-22 11:30:10 -07:00
self : get_off ( seatno )
end
2017-01-25 12:23:54 -08:00
atprint ( " get_on: attaching " , clicker : get_player_name ( ) )
2016-10-22 11:30:10 -07:00
self.seatp [ seatno ] = clicker : get_player_name ( )
2017-02-08 15:11:28 -08:00
self.seatpc [ seatno ] = clicker : get_player_control_bits ( )
2016-12-05 11:53:43 -08:00
advtrains.player_to_train_mapping [ clicker : get_player_name ( ) ] = self.train_id
2016-10-22 11:30:10 -07:00
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 )
end
function wagon : get_off_plr ( pname )
local no = self : get_seatno ( pname )
if no then
self : get_off ( no )
end
end
function wagon : get_seatno ( pname )
2016-12-04 14:18:28 -08:00
for no , cont in pairs ( self.seatp ) do
2016-10-22 11:30:10 -07:00
if cont == pname then
return no
end
end
return nil
end
function wagon : get_off ( seatno )
if not self.seatp [ seatno ] then return end
local pname = self.seatp [ seatno ]
local clicker = minetest.get_player_by_name ( pname )
2016-12-05 11:53:43 -08:00
advtrains.player_to_train_mapping [ pname ] = nil
2016-11-24 11:25:07 -08:00
advtrains.clear_driver_hud ( pname )
2017-02-28 07:25:32 -08:00
self.seatp [ seatno ] = nil
self.seatpc [ seatno ] = nil
2016-10-22 11:30:10 -07:00
if clicker then
2017-01-25 12:23:54 -08:00
atprint ( " get_off: detaching " , clicker : get_player_name ( ) )
2016-10-22 11:30:10 -07:00
clicker : set_detach ( )
clicker : set_eye_offset ( { x = 0 , y = 0 , z = 0 } , { x = 0 , y = 0 , z = 0 } )
2017-02-28 07:25:32 -08:00
local gp = self : train ( )
--code as in step - automatic get on
2017-04-05 04:35:39 -07:00
if self.door_entry and gp.door_open and gp.door_open ~= 0 and gp.velocity == 0 and gp.index and gp.path then
2017-02-28 07:25:32 -08:00
local index = advtrains.get_real_path_index ( gp , self.pos_in_train )
--using the mapping created by the trainlogic globalstep
for i , ino in ipairs ( self.door_entry ) do
local aci = index + ino * ( self.wagon_flipped and - 1 or 1 )
local ix1 = gp.path [ math.floor ( aci ) ]
local ix2 = gp.path [ math.floor ( aci + 1 ) ]
-- 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)
-- multiplied by 2 here, to place off on platform, y of add is 1.
local add = { x = ( ix2.z - ix1.z ) * gp.door_open , y = 0 , z = ( ix1.x - ix2.x ) * gp.door_open }
local oadd = { x = ( ix2.z - ix1.z ) * gp.door_open * 2 , y = 1 , z = ( ix1.x - ix2.x ) * gp.door_open * 2 }
local platpos = vector.round ( vector.add ( ix1 , add ) )
local offpos = vector.round ( vector.add ( ix1 , oadd ) )
atprint ( " platpos: " , platpos , " offpos: " , offpos )
if minetest.get_item_group ( minetest.get_node ( platpos ) . name , " platform " ) > 0 then
minetest.after ( 0.2 , function ( ) clicker : setpos ( offpos ) end )
return
end
end
2017-04-05 04:35:39 -07:00
else --if not door_entry, or paths missing, fall back to old method
2017-02-28 07:25:32 -08:00
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 / 4 and yaw < 5 * math.pi / 4 ) or ( yaw > 7 * math.pi / 4 )
--abuse helper function
for _ , r in ipairs ( { - 1 , 1 } ) do
local p = vector.add ( { x = isx and r or 0 , y = 0 , z = not isx and r or 0 } , objpos )
local offp = vector.add ( { x = isx and r * 2 or 0 , y = 1 , z = not isx and r * 2 or 0 } , objpos )
if minetest.get_item_group ( minetest.get_node ( p ) . name , " platform " ) > 0 then
minetest.after ( 0.2 , function ( ) clicker : setpos ( offp ) end )
return
end
2016-11-24 12:52:17 -08:00
end
end
2016-10-22 11:30:10 -07:00
end
end
function wagon : show_get_on_form ( pname )
if not self.initialized then return end
2016-11-03 03:27:17 -07:00
if # self.seats == 0 then
if self.has_inventory and self.get_inventory_formspec then
2017-01-03 09:48:00 -08:00
minetest.show_formspec ( pname , " advtrains_inv_ " .. self.unique_id , self : get_inventory_formspec ( pname ) )
2016-11-03 03:27:17 -07:00
end
return
end
2017-01-23 12:29:59 -08:00
local form , comma = " size[5,8]label[0.5,0.5; " .. attrans ( " Select seat: " ) .. " ]textlist[0.5,1;4,6;seat; " , " "
2016-10-22 11:30:10 -07:00
for seatno , seattbl in ipairs ( self.seats ) do
local addtext , colorcode = " " , " "
if self.seatp and self.seatp [ seatno ] then
colorcode = " #FF0000 "
addtext = " ( " .. self.seatp [ seatno ] .. " ) "
end
form = form .. comma .. colorcode .. seattbl.name .. addtext
comma = " , "
end
2016-11-03 03:27:17 -07:00
form = form .. " ;0,false] "
if self.has_inventory and self.get_inventory_formspec then
2017-01-23 12:29:59 -08:00
form = form .. " button_exit[1,7;3,1;inv; " .. attrans ( " Show Inventory " ) .. " ] "
2016-11-03 03:27:17 -07:00
end
minetest.show_formspec ( pname , " advtrains_geton_ " .. self.unique_id , form )
2016-10-22 11:30:10 -07:00
end
2017-01-27 14:43:01 -08:00
function wagon : show_wagon_properties ( pname )
2017-03-30 12:51:45 -07:00
local numsgr = 0
if self.seat_groups then
numsgr =# self.seat_groups
2017-01-27 14:43:01 -08:00
end
if not self.seat_access then
self.seat_access = { }
end
--[[
fields : seat access : empty : everyone
checkbox : lock couples
button : save
] ]
2017-03-30 12:51:45 -07:00
local form = " size[5, " .. ( numsgr * 1.5 + 7 ) .. " ] "
2017-01-27 14:43:01 -08:00
local at = 0
2017-03-30 12:51:45 -07:00
if self.seat_groups then
for sgr , sgrdef in pairs ( self.seat_groups ) do
local text = attrans ( " Access to @1 " , sgrdef.name )
form = form .. " field[0.5, " .. ( 0.5 + at * 1.5 ) .. " ;4,1;sgr_ " .. sgr .. " ; " .. text .. " ; " .. ( self.seat_access [ sgr ] or " " ) .. " ] "
at = at + 1
end
2017-01-27 14:43:01 -08:00
end
2017-01-28 08:06:38 -08:00
form = form .. " checkbox[0, " .. ( at * 1.5 ) .. " ;lock_couples; " .. attrans ( " Lock couples " ) .. " ; " .. ( self.lock_couples and " true " or " false " ) .. " ] "
2017-03-30 12:51:45 -07:00
if self : train ( ) then --just in case
form = form .. " field[0.5, " .. ( 1.5 + at * 1.5 ) .. " ;4,1;text_outside; " .. attrans ( " Text displayed outside on train " ) .. " ; " .. ( self : train ( ) . text_outside or " " ) .. " ] "
form = form .. " field[0.5, " .. ( 2.5 + at * 1.5 ) .. " ;4,1;text_inside; " .. attrans ( " Text displayed inside train " ) .. " ; " .. ( self : train ( ) . text_inside or " " ) .. " ] "
end
form = form .. " button_exit[0.5, " .. ( 3 + at * 1.5 ) .. " ;4,1;save; " .. attrans ( " Save wagon properties " ) .. " ] "
2017-01-27 14:43:01 -08:00
minetest.show_formspec ( pname , " advtrains_prop_ " .. self.unique_id , form )
end
2018-01-09 09:30:29 -08:00
function wagon : show_bordcom ( pname )
minetest.show_formspec ( pname , " advtrains_bordcom_ " .. self.unique_id , " field[irrel;Not yet implemented;We normally would show the bord computer now.] " )
end
2016-10-22 11:30:10 -07:00
minetest.register_on_player_receive_fields ( function ( player , formname , fields )
2017-04-29 10:44:43 -07:00
return advtrains.pcall ( function ( )
local uid = string.match ( formname , " ^advtrains_geton_(.+)$ " )
if uid then
for _ , wagon in pairs ( minetest.luaentities ) do
if wagon.is_wagon and wagon.initialized and wagon.unique_id == uid then
if fields.inv then
if 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 ( ) ) )
end
elseif fields.seat then
local val = minetest.explode_textlist_event ( fields.seat )
if val and val.type ~= " INV " and not wagon.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 , " " )
end
2016-11-03 03:27:17 -07:00
end
2016-10-22 11:30:10 -07:00
end
end
end
2017-04-29 10:44:43 -07:00
uid = string.match ( formname , " ^advtrains_seating_(.+)$ " )
if uid then
for _ , wagon in pairs ( minetest.luaentities ) do
if wagon.is_wagon and wagon.initialized and wagon.unique_id == uid then
local pname = player : get_player_name ( )
local no = wagon : get_seatno ( pname )
if no then
if wagon.seat_groups then
wagon : seating_from_key_helper ( pname , fields , no )
end
2017-01-25 12:23:54 -08:00
end
end
end
end
2017-04-29 10:44:43 -07:00
uid = string.match ( formname , " ^advtrains_prop_(.+)$ " )
if uid then
for _ , wagon in pairs ( minetest.luaentities ) do
if wagon.is_wagon and wagon.initialized and wagon.unique_id == uid then
local pname = player : get_player_name ( )
if pname ~= wagon.owner then
return true
2017-02-05 11:14:37 -08:00
end
2017-04-29 10:44:43 -07:00
if fields.save or not fields.quit then
for sgr , sgrdef in pairs ( wagon.seat_groups ) do
if fields [ " sgr_ " .. sgr ] then
local fcont = fields [ " sgr_ " .. sgr ]
wagon.seat_access [ sgr ] = fcont ~= " " and fcont or nil
end
2017-03-30 12:51:45 -07:00
end
2017-04-29 10:44:43 -07:00
if fields.lock_couples then
wagon.lock_couples = fields.lock_couples == " true "
end
if fields.text_outside then
if fields.text_outside ~= " " then
wagon : train ( ) . text_outside = fields.text_outside
else
wagon : train ( ) . text_outside = nil
end
2017-03-30 12:51:45 -07:00
end
2017-04-29 10:44:43 -07:00
if fields.text_inside then
if fields.text_inside ~= " " then
wagon : train ( ) . text_inside = fields.text_inside
else
wagon : train ( ) . text_inside = nil
end
2017-03-30 12:51:45 -07:00
end
2017-04-29 10:44:43 -07:00
2017-03-30 12:51:45 -07:00
end
2017-01-27 14:43:01 -08:00
end
end
end
2017-04-29 10:44:43 -07:00
end )
2016-10-22 11:30:10 -07:00
end )
2017-01-25 12:23:54 -08:00
function wagon : seating_from_key_helper ( pname , fields , no )
local sgr = self.seats [ no ] . group
for _ , access in ipairs ( self.seat_groups [ sgr ] . access_to ) do
if fields [ " sgr_ " .. access ] and self : check_seat_group_access ( pname , access ) then
for seatid , seatdef in ipairs ( self.seats ) do
if seatdef.group == access and not self.seatp [ seatid ] then
self : get_on ( minetest.get_player_by_name ( pname ) , seatid )
return
end
end
end
end
if fields.inv and self.has_inventory and self.get_inventory_formspec then
minetest.show_formspec ( player : get_player_name ( ) , " advtrains_inv_ " .. self.unique_id , wagon : get_inventory_formspec ( player : get_player_name ( ) ) )
end
if fields.prop and self.owner == pname then
self : show_wagon_properties ( pname )
end
2018-01-09 09:30:29 -08:00
if fields.bordcom and self.seat_groups [ sgr ] . driving_ctrl_access and minetest.check_player_privs ( pname , " train_operator " ) then
self : show_bordcom ( pname )
end
2017-01-25 12:36:17 -08:00
if fields.dcwarn then
2017-03-09 04:22:11 -08:00
minetest.chat_send_player ( pname , attrans ( " Doors are closed! Use Sneak+rightclick to ignore the closed doors and get off! " ) )
2017-01-25 12:36:17 -08:00
end
if fields.off then
2017-01-25 12:23:54 -08:00
self : get_off ( no )
end
end
function wagon : check_seat_group_access ( pname , sgr )
2018-01-09 09:30:29 -08:00
if self.seat_groups [ sgr ] . driving_ctrl_access and not minetest.check_player_privs ( pname , " train_operator " ) then
return false , " Missing train_operator privilege. "
end
2017-01-27 14:43:01 -08:00
if not self.seat_access then
return true
end
local sae = self.seat_access [ sgr ]
if not sae or sae == " " then
return true
end
for name in string.gmatch ( sae , " %S+ " ) do
if name == pname then
return true
end
end
2018-01-09 09:30:29 -08:00
return false , " Blacklisted by owner. "
2017-01-25 12:23:54 -08:00
end
2016-10-22 11:30:10 -07:00
function wagon : reattach_all ( )
2016-11-02 03:17:42 -07:00
if not self.seatp then self.seatp = { } end
2016-10-22 11:30:10 -07:00
for seatno , pname in pairs ( self.seatp ) do
local p = minetest.get_player_by_name ( pname )
if p then
self : get_on ( p , seatno )
end
end
end
2016-05-29 11:27:30 -07:00
2017-11-22 13:16:08 -08:00
function advtrains . register_wagon ( sysname_p , prototype , desc , inv_img )
local sysname = sysname_p
if not string.match ( sysname , " : " ) then
sysname = " advtrains: " .. sysname_p
end
2016-05-29 11:27:30 -07:00
setmetatable ( prototype , { __index = wagon } )
2017-11-22 13:16:08 -08:00
minetest.register_entity ( " : " .. sysname , prototype )
2016-05-29 11:27:30 -07:00
2017-11-22 13:16:08 -08:00
minetest.register_craftitem ( " : " .. sysname , {
2016-08-28 12:58:13 -07:00
description = desc ,
inventory_image = inv_img ,
wield_image = inv_img ,
2016-05-29 11:27:30 -07:00
stack_max = 1 ,
on_place = function ( itemstack , placer , pointed_thing )
2017-04-05 04:57:09 -07:00
return advtrains.pcall ( function ( )
2017-04-29 10:44:43 -07:00
if not pointed_thing.type == " node " then
return
end
2017-01-29 03:37:47 -08:00
2017-04-29 10:44:43 -07:00
local node = minetest.get_node_or_nil ( pointed_thing.under )
if not node then atprint ( " [advtrains]Ignore at placer position " ) return itemstack end
local nodename = node.name
if ( not advtrains.is_track_and_drives_on ( nodename , prototype.drives_on ) ) then
atprint ( " no track here, not placing. " )
return itemstack
end
if not minetest.check_player_privs ( placer , { train_place = true } ) and minetest.is_protected ( pointed_thing.under , placer : get_player_name ( ) ) then
minetest.record_protection_violation ( pointed_thing.under , placer : get_player_name ( ) )
return
end
2017-12-18 12:44:36 -08:00
local tconns = advtrains.get_track_connections ( node.name , node.param2 )
local yaw = placer : get_look_horizontal ( ) + ( math.pi / 2 )
local plconnid = advtrains.yawToClosestConn ( yaw , tconns )
local prevpos = advtrains.get_adjacent_rail ( pointed_thing.under , tconns , plconnid , prototype.drives_on )
if not prevpos then return end
local id = advtrains.create_new_train_at ( pointed_thing.under , prevpos )
2017-04-29 10:44:43 -07:00
2017-11-22 13:16:08 -08:00
local ob = minetest.add_entity ( pointed_thing.under , sysname )
2017-04-29 10:44:43 -07:00
if not ob then
atprint ( " couldn't add_entity, aborting " )
end
local le = ob : get_luaentity ( )
le.owner = placer : get_player_name ( )
local wagon_uid = le : init_new_instance ( id , { } )
advtrains.add_wagon_to_train ( le , id )
2017-06-07 03:53:52 -07:00
if not minetest.settings : get_bool ( " creative_mode " ) then
2017-04-29 10:44:43 -07:00
itemstack : take_item ( )
end
2016-05-29 11:27:30 -07:00
return itemstack
2017-04-29 10:44:43 -07:00
end )
2016-05-29 11:27:30 -07:00
end ,
} )
end
2016-06-09 08:35:06 -07:00
--[[
wagons can define update_animation ( self , velocity ) if they have a speed - dependent animation
this function will be called when the velocity vector changes or every 2 seconds .
] ]
2016-05-29 11:27:30 -07:00
2016-10-06 00:56:40 -07:00