created git repository

master
orwell96 2016-05-29 20:27:30 +02:00
commit 2c864564d2
74 changed files with 3608 additions and 0 deletions

138
debugitems.lua Normal file
View File

@ -0,0 +1,138 @@
minetest.register_tool("advtrains:1",
{
description = "1",
groups = {cracky=1}, -- key=name, value=rating; rating=1..3.
inventory_image = "drwho_screwdriver.png",
wield_image = "drwho_screwdriver.png",
stack_max = 1,
range = 7.0,
on_use = function(itemstack, user, pointed_thing)
pos1=pointed_thing.under
end,
})
minetest.register_tool("advtrains:2",
{
description = "2",
groups = {cracky=1}, -- key=name, value=rating; rating=1..3.
inventory_image = "drwho_screwdriver.png",
wield_image = "drwho_screwdriver.png",
stack_max = 1,
range = 7.0,
on_use = function(itemstack, user, pointed_thing)
pos2=pointed_thing.under
end,
})
minetest.register_tool("advtrains:3",
{
description = "3",
groups = {cracky=1}, -- key=name, value=rating; rating=1..3.
inventory_image = "drwho_screwdriver.png",
wield_image = "drwho_screwdriver.png",
stack_max = 1,
range = 7.0,
on_use = function(itemstack, user, pointed_thing)
pos3=pointed_thing.under
end,
})
minetest.register_tool("advtrains:4",
{
description = "4",
groups = {cracky=1}, -- key=name, value=rating; rating=1..3.
inventory_image = "drwho_screwdriver.png",
wield_image = "drwho_screwdriver.png",
stack_max = 1,
range = 7.0,
on_use = function(itemstack, user, pointed_thing)
pos4=pointed_thing.under
end,
})
core.register_chatcommand("mad", {
params="",
description="",
privs={},
func = function(name, param)
return true, advtrains.get_wagon_yaw(pos1, pos2, pos3, pos4, tonumber(param))*360/(2*math.pi)
end,
})
advtrains.firstobject=nil
minetest.register_tool("advtrains:connect",
{
description = "connect wagons tool",
groups = {cracky=1}, -- key=name, value=rating; rating=1..3.
inventory_image = "drwho_screwdriver.png",
wield_image = "drwho_screwdriver.png",
stack_max = 1,
range = 7.0,
on_place = function(itemstack, placer, pointed_thing)
end,
--[[
^ Shall place item and return the leftover itemstack
^ default: minetest.item_place ]]
on_use = function(itemstack, user, pointed_thing)
if pointed_thing.type=="object" then
local luaent=pointed_thing.ref:get_luaentity()
if luaent and luaent.is_wagon then
if advtrains.firstobject then
minetest.chat_send_all("connect second object "..luaent.unique_id)
advtrains.connect_wagons(luaent, advtrains.firstobject)
minetest.chat_send_all("done")
advtrains.firstobject=nil
else
advtrains.firstobject=luaent
minetest.chat_send_all("connect first object "..luaent.unique_id)
end
end
end
end,
--[[
^ default: nil
^ Function must return either nil if no item shall be removed from
inventory, or an itemstack to replace the original itemstack.
e.g. itemstack:take_item(); return itemstack
^ Otherwise, the function is free to do what it wants.
^ The default functions handle regular use cases.
]]
})
minetest.register_tool("advtrains:tttool",
{
description = "traintester tool",
groups = {cracky=1}, -- key=name, value=rating; rating=1..3.
inventory_image = "drwho_screwdriver.png",
wield_image = "drwho_screwdriver.png",
stack_max = 1,
range = 7.0,
on_place = function(itemstack, placer, pointed_thing)
end,
--[[
^ Shall place item and return the leftover itemstack
^ default: minetest.item_place ]]
on_use = function(itemstack, user, pointed_thing)
if pointed_thing.type=="object" then
local luaent=pointed_thing.ref:get_luaentity()
if luaent and luaent.is_wagon then
minetest.chat_send_all("wagon yaw is "..pointed_thing.ref:getyaw())
minetest.chat_send_all("trains last yaw is set to "..luaent:train().last_front_yaw)
minetest.chat_send_all("end report")
end
else
minetest.chat_send_all(dump(minetest.get_node(pointed_thing.under)))
local c1, c2=advtrains.get_track_connections(minetest.get_node(pointed_thing.under).name, minetest.get_node(pointed_thing.under).param2)
minetest.chat_send_all(c1.." <-> "..c2)
end
end,
--[[
^ default: nil
^ Function must return either nil if no item shall be removed from
inventory, or an itemstack to replace the original itemstack.
e.g. itemstack:take_item(); return itemstack
^ Otherwise, the function is free to do what it wants.
^ The default functions handle regular use cases.
]]
}
)

138
debugitems.lua~ Normal file
View File

@ -0,0 +1,138 @@
minetest.register_tool("advtrains:1",
{
description = "1",
groups = {cracky=1}, -- key=name, value=rating; rating=1..3.
inventory_image = "drwho_screwdriver.png",
wield_image = "drwho_screwdriver.png",
stack_max = 1,
range = 7.0,
on_use = function(itemstack, user, pointed_thing)
pos1=pointed_thing.under
end,
})
minetest.register_tool("advtrains:2",
{
description = "2",
groups = {cracky=1}, -- key=name, value=rating; rating=1..3.
inventory_image = "drwho_screwdriver.png",
wield_image = "drwho_screwdriver.png",
stack_max = 1,
range = 7.0,
on_use = function(itemstack, user, pointed_thing)
pos2=pointed_thing.under
end,
})
minetest.register_tool("advtrains:3",
{
description = "3",
groups = {cracky=1}, -- key=name, value=rating; rating=1..3.
inventory_image = "drwho_screwdriver.png",
wield_image = "drwho_screwdriver.png",
stack_max = 1,
range = 7.0,
on_use = function(itemstack, user, pointed_thing)
pos3=pointed_thing.under
end,
})
minetest.register_tool("advtrains:4",
{
description = "4",
groups = {cracky=1}, -- key=name, value=rating; rating=1..3.
inventory_image = "drwho_screwdriver.png",
wield_image = "drwho_screwdriver.png",
stack_max = 1,
range = 7.0,
on_use = function(itemstack, user, pointed_thing)
pos4=pointed_thing.under
end,
})
core.register_chatcommand("mad", {
params="",
description="",
privs={},
func = function(name, param)
return true, advtrains.get_wagon_yaw(pos1, pos2, pos3, pos4, tonumber(param))*2*math.pi/360
end,
})
advtrains.firstobject=nil
minetest.register_tool("advtrains:connect",
{
description = "connect wagons tool",
groups = {cracky=1}, -- key=name, value=rating; rating=1..3.
inventory_image = "drwho_screwdriver.png",
wield_image = "drwho_screwdriver.png",
stack_max = 1,
range = 7.0,
on_place = function(itemstack, placer, pointed_thing)
end,
--[[
^ Shall place item and return the leftover itemstack
^ default: minetest.item_place ]]
on_use = function(itemstack, user, pointed_thing)
if pointed_thing.type=="object" then
local luaent=pointed_thing.ref:get_luaentity()
if luaent and luaent.is_wagon then
if advtrains.firstobject then
minetest.chat_send_all("connect second object "..luaent.unique_id)
advtrains.connect_wagons(luaent, advtrains.firstobject)
minetest.chat_send_all("done")
advtrains.firstobject=nil
else
advtrains.firstobject=luaent
minetest.chat_send_all("connect first object "..luaent.unique_id)
end
end
end
end,
--[[
^ default: nil
^ Function must return either nil if no item shall be removed from
inventory, or an itemstack to replace the original itemstack.
e.g. itemstack:take_item(); return itemstack
^ Otherwise, the function is free to do what it wants.
^ The default functions handle regular use cases.
]]
})
minetest.register_tool("advtrains:tttool",
{
description = "traintester tool",
groups = {cracky=1}, -- key=name, value=rating; rating=1..3.
inventory_image = "drwho_screwdriver.png",
wield_image = "drwho_screwdriver.png",
stack_max = 1,
range = 7.0,
on_place = function(itemstack, placer, pointed_thing)
end,
--[[
^ Shall place item and return the leftover itemstack
^ default: minetest.item_place ]]
on_use = function(itemstack, user, pointed_thing)
if pointed_thing.type=="object" then
local luaent=pointed_thing.ref:get_luaentity()
if luaent and luaent.is_wagon then
minetest.chat_send_all("wagon yaw is "..pointed_thing.ref:getyaw())
minetest.chat_send_all("trains last yaw is set to "..luaent:train().last_front_yaw)
minetest.chat_send_all("end report")
end
else
minetest.chat_send_all(dump(minetest.get_node(pointed_thing.under)))
local c1, c2=advtrains.get_track_connections(minetest.get_node(pointed_thing.under).name, minetest.get_node(pointed_thing.under).param2)
minetest.chat_send_all(c1.." <-> "..c2)
end
end,
--[[
^ default: nil
^ Function must return either nil if no item shall be removed from
inventory, or an itemstack to replace the original itemstack.
e.g. itemstack:take_item(); return itemstack
^ Otherwise, the function is free to do what it wants.
^ The default functions handle regular use cases.
]]
}
)

215
helpers.lua Normal file
View File

@ -0,0 +1,215 @@
--advtrains by orwell96, see readme.txt
local print=function(t) minetest.log("action", t) minetest.chat_send_all(t) end
function advtrains.dirCoordSet(coord, dir)
local x=0
local z=0
--local dir=(dirx+2)%8
if(dir==6) then
x=-1
elseif (dir==7) then
x=-1
z=1
elseif (dir==0) then
z=1
elseif (dir==1) then
z=1
x=1
elseif (dir==2) then
x=1
elseif (dir==3) then
x=1
z=-1
elseif (dir==4) then
z=-1
elseif (dir==5) then
z=-1
x=-1
else
error("advtrains: in helpers.lua/dirCoordSet() given dir="..(dir or "nil"))
end
return {x=coord.x+x, y=coord.y, z=coord.z+z}
end
function advtrains.dirToCoord(dir)
return advtrains.dirCoordSet({x=0, y=0, z=0}, dir)
end
function advtrains.maxN(list, expectstart)
local n=expectstart or 0
while list[n] do
n=n+1
end
return n-1
end
function advtrains.minN(list, expectstart)
local n=expectstart or 0
while list[n] do
n=n-1
end
return n+1
end
--vertical_transmit:
--[[
rely1, rely2 tell to which height the connections are pointed to. 1 means it will go up the next node
]]
function advtrains.conway(midreal, prev, drives_on)--in order prev,mid,return
local mid=advtrains.round_vector_floor_y(midreal)
if(not advtrains.is_track_and_drives_on(minetest.get_node(mid).name, drives_on)) then
--print("[advtrains]in conway: no rail, returning!")
return nil
end
if(not prev or not advtrains.is_track_and_drives_on(minetest.get_node(advtrains.round_vector_floor_y(prev)).name, drives_on)) then
--print("[advtrains]in conway: no prev rail, there should be an initial path!, returning!")
return nil
end
local midnode=minetest.get_node_or_nil(mid)
if not midnode then --print("[advtrains][conway] midnode is ignore")
return nil
end
local middir1, middir2, midrely1, midrely2=advtrains.get_track_connections(midnode.name, midnode.param2)
local next, chkdir, chkrely, y_offset
y_offset=0
--print("[advtrains] in order mid1,mid2",middir1,middir2)
--try if it is dir1
local cor1=advtrains.dirCoordSet(mid, middir2)--<<<<
if math.floor(cor1.x+0.5)==math.floor(prev.x+0.5) and math.floor(cor1.z+0.5)==math.floor(prev.z+0.5) then--this was previous
next=advtrains.dirCoordSet(mid, middir1)
if midrely1>=1 then
next.y=next.y+1
--print("[advtrains]found midrely1 to be >=1: next is now "..(next and minetest.pos_to_string(next) or "nil"))
y_offset=1
end
chkdir=middir1
chkrely=midrely1
--print("[advtrains]dir2 applied next pos:",minetest.pos_to_string(next),"(chkdir is ",chkdir,")")
end
--dir2???
local cor2=advtrains.dirCoordSet(mid, middir1)--<<<<
if math.floor(cor2.x+0.5)==math.floor(prev.x+0.5) and math.floor(cor2.z+0.5)==math.floor(prev.z+0.5) then
next=advtrains.dirCoordSet(mid, middir2)--dir2 wird überprüft, alles gut.
if midrely2>=1 then
next.y=next.y+1
--print("[advtrains]found midrely2 to be >=1: next is now "..(next and minetest.pos_to_string(next) or "nil"))
y_offset=1
end
chkdir=middir2
chkrely=midrely2
--print("[advtrains] dir2 applied next pos:",minetest.pos_to_string(next),"(chkdir is ",chkdir,")")
end
--print("[advtrains]dir applied next pos: "..(next and minetest.pos_to_string(next) or "nil").."(chkdir is "..(chkdir or "nil")..", y-offset "..y_offset..")")
--is there a next
if not next then
print("[advtrains]in conway: no next rail(nil), returning!")
return nil
end
local nextnode=minetest.get_node_or_nil(advtrains.round_vector_floor_y(next))
if not nextnode then print("[advtrains][conway] nextnode is ignore")
return nil
end
--is it a rail?
if(not advtrains.is_track_and_drives_on(nextnode.name, drives_on)) then
print("[advtrains]in conway: next "..minetest.pos_to_string(next).." not a rail, trying one node below!")
next.y=next.y-1
y_offset=y_offset-1
nextnode=minetest.get_node_or_nil(advtrains.round_vector_floor_y(next))
if not nextnode then --print("[advtrains][conway] nextnode is ignore")
return nil
end
if(not advtrains.is_track_and_drives_on(nextnode.name, drives_on)) then
print("[advtrains]in conway: one below "..minetest.pos_to_string(next).." is not a rail either, returning!")
return nil
end
end
--print("[advtrains]trying to find if rail connects: "..(next and minetest.pos_to_string(next) or "nil").."(chkdir is "..(chkdir or "nil")..", y-offset "..y_offset..")")
local nextdir1, nextdir2, nextrely1, nextrely2, nextrailheight=advtrains.get_track_connections(nextnode.name, nextnode.param2)
--is this next rail connecting to the mid?
if not ( (((nextdir1+4)%8)==chkdir and nextrely1==chkrely-y_offset) or (((nextdir2+4)%8)==chkdir and nextrely2==chkrely-y_offset) ) then
print("[advtrains]in conway: next "..minetest.pos_to_string(next).." not connecting, trying one node below!")
next.y=next.y-1
y_offset=y_offset-1
nextdir1, nextdir2, nextrely1, nextrely2, nextrailheight=advtrains.get_track_connections(nextnode.name, nextnode.param2)
if not ( (((nextdir1+4)%8)==chkdir and nextrely1==chkrely) or (((nextdir2+4)%8)==chkdir and nextrely2==chkrely) ) then
print("[advtrains]in conway: next "..minetest.pos_to_string(next).." rail not connecting, returning!")
--print("[advtrains] in order mid1,2,next1,2,chkdir "..middir1.." "..middir2.." "..nextdir1.." "..nextdir2.." "..chkdir)
return nil
end
end
--print("[advtrains]conway found rail.")
return vector.add(advtrains.round_vector_floor_y(next), {x=0, y=nextrailheight, z=0}), chkdir
end
function advtrains.round_vector_floor_y(vec)
return {x=math.floor(vec.x+0.5), y=math.floor(vec.y), z=math.floor(vec.z+0.5)}
end
function advtrains.yawToDirection(yaw, conn1, conn2)
if not conn1 or not conn2 then
error("given nil to yawToDirection: conn1="..(conn1 or "nil").." conn2="..(conn1 or "nil"))
end
local yaw1=math.pi*(conn1/4)
local yaw2=math.pi*(conn2/4)
if advtrains.minAngleDiffRad(yaw, yaw1)<advtrains.minAngleDiffRad(yaw, yaw2) then--change to > if weird behavior
return conn2
else
return conn1
end
end
function advtrains.minAngleDiffRad(r1, r2)
local try1=r2-r1
local try2=(r2+2*math.pi)-r1
local try3=r2-(r1+2*math.pi)
if math.min(math.abs(try1), math.abs(try2), math.abs(try3))==math.abs(try1) then
return try1
end
if math.min(math.abs(try1), math.abs(try2), math.abs(try3))==math.abs(try2) then
return try2
end
if math.min(math.abs(try1), math.abs(try2), math.abs(try3))==math.abs(try3) then
return try3
end
end
function advtrains.dumppath(path)
if not path then print("dumppath: no path(nil)") return end
local min=advtrains.minN(path)
local max=advtrains.maxN(path)
for i=min, max do print("["..i.."] "..(path[i] and minetest.pos_to_string(path[i]) or "nil")) end
end
function advtrains.merge_tables(a, ...)
local new={}
for _,t in ipairs({a,...}) do
for k,v in pairs(t) do new[k]=v end
end
return new
end
function advtrains.yaw_from_3_positions(prev, curr, next)
local pts=minetest.pos_to_string
--print("p3 "..pts(prev)..pts(curr)..pts(next))
local prev2curr=math.atan2((curr.x-prev.x), (prev.z-curr.z))
local curr2next=math.atan2((next.x-curr.x), (curr.z-next.z))
--print("y3 "..(prev2curr*360/(2*math.pi)).." "..(curr2next*360/(2*math.pi)))
return prev2curr+(advtrains.minAngleDiffRad(prev2curr, curr2next)/2)
end
function advtrains.get_wagon_yaw(front, first, second, back, pct)
local pts=minetest.pos_to_string
--print("p "..pts(front)..pts(first)..pts(second)..pts(back))
local y2=advtrains.yaw_from_3_positions(second, first, front)
local y1=advtrains.yaw_from_3_positions(back, second, first)
--print("y "..(y1*360/(2*math.pi)).." "..(y2*360/(2*math.pi)))
return y1+advtrains.minAngleDiffRad(y1, y2)*pct
end

215
helpers.lua~ Normal file
View File

@ -0,0 +1,215 @@
--advtrains by orwell96, see readme.txt
local print=function(t) minetest.log("action", t) minetest.chat_send_all(t) end
function advtrains.dirCoordSet(coord, dir)
local x=0
local z=0
--local dir=(dirx+2)%8
if(dir==6) then
x=-1
elseif (dir==7) then
x=-1
z=1
elseif (dir==0) then
z=1
elseif (dir==1) then
z=1
x=1
elseif (dir==2) then
x=1
elseif (dir==3) then
x=1
z=-1
elseif (dir==4) then
z=-1
elseif (dir==5) then
z=-1
x=-1
else
error("advtrains: in helpers.lua/dirCoordSet() given dir="..(dir or "nil"))
end
return {x=coord.x+x, y=coord.y, z=coord.z+z}
end
function advtrains.dirToCoord(dir)
return advtrains.dirCoordSet({x=0, y=0, z=0}, dir)
end
function advtrains.maxN(list, expectstart)
local n=expectstart or 0
while list[n] do
n=n+1
end
return n-1
end
function advtrains.minN(list, expectstart)
local n=expectstart or 0
while list[n] do
n=n-1
end
return n+1
end
--vertical_transmit:
--[[
rely1, rely2 tell to which height the connections are pointed to. 1 means it will go up the next node
]]
function advtrains.conway(midreal, prev, drives_on)--in order prev,mid,return
local mid=advtrains.round_vector_floor_y(midreal)
if(not advtrains.is_track_and_drives_on(minetest.get_node(mid).name, drives_on)) then
--print("[advtrains]in conway: no rail, returning!")
return nil
end
if(not prev or not advtrains.is_track_and_drives_on(minetest.get_node(advtrains.round_vector_floor_y(prev)).name, drives_on)) then
--print("[advtrains]in conway: no prev rail, there should be an initial path!, returning!")
return nil
end
local midnode=minetest.get_node_or_nil(mid)
if not midnode then --print("[advtrains][conway] midnode is ignore")
return nil
end
local middir1, middir2, midrely1, midrely2=advtrains.get_track_connections(midnode.name, midnode.param2)
local next, chkdir, chkrely, y_offset
y_offset=0
--print("[advtrains] in order mid1,mid2",middir1,middir2)
--try if it is dir1
local cor1=advtrains.dirCoordSet(mid, middir2)--<<<<
if math.floor(cor1.x+0.5)==math.floor(prev.x+0.5) and math.floor(cor1.z+0.5)==math.floor(prev.z+0.5) then--this was previous
next=advtrains.dirCoordSet(mid, middir1)
if midrely1>=1 then
next.y=next.y+1
--print("[advtrains]found midrely1 to be >=1: next is now "..(next and minetest.pos_to_string(next) or "nil"))
y_offset=1
end
chkdir=middir1
chkrely=midrely1
--print("[advtrains]dir2 applied next pos:",minetest.pos_to_string(next),"(chkdir is ",chkdir,")")
end
--dir2???
local cor2=advtrains.dirCoordSet(mid, middir1)--<<<<
if math.floor(cor2.x+0.5)==math.floor(prev.x+0.5) and math.floor(cor2.z+0.5)==math.floor(prev.z+0.5) then
next=advtrains.dirCoordSet(mid, middir2)--dir2 wird überprüft, alles gut.
if midrely2>=1 then
next.y=next.y+1
--print("[advtrains]found midrely2 to be >=1: next is now "..(next and minetest.pos_to_string(next) or "nil"))
y_offset=1
end
chkdir=middir2
chkrely=midrely2
--print("[advtrains] dir2 applied next pos:",minetest.pos_to_string(next),"(chkdir is ",chkdir,")")
end
--print("[advtrains]dir applied next pos: "..(next and minetest.pos_to_string(next) or "nil").."(chkdir is "..(chkdir or "nil")..", y-offset "..y_offset..")")
--is there a next
if not next then
print("[advtrains]in conway: no next rail(nil), returning!")
return nil
end
local nextnode=minetest.get_node_or_nil(advtrains.round_vector_floor_y(next))
if not nextnode then print("[advtrains][conway] nextnode is ignore")
return nil
end
--is it a rail?
if(not advtrains.is_track_and_drives_on(nextnode.name, drives_on)) then
print("[advtrains]in conway: next "..minetest.pos_to_string(next).." not a rail, trying one node below!")
next.y=next.y-1
y_offset=y_offset-1
nextnode=minetest.get_node_or_nil(advtrains.round_vector_floor_y(next))
if not nextnode then --print("[advtrains][conway] nextnode is ignore")
return nil
end
if(not advtrains.is_track_and_drives_on(nextnode.name, drives_on)) then
print("[advtrains]in conway: one below "..minetest.pos_to_string(next).." is not a rail either, returning!")
return nil
end
end
--print("[advtrains]trying to find if rail connects: "..(next and minetest.pos_to_string(next) or "nil").."(chkdir is "..(chkdir or "nil")..", y-offset "..y_offset..")")
local nextdir1, nextdir2, nextrely1, nextrely2, nextrailheight=advtrains.get_track_connections(nextnode.name, nextnode.param2)
--is this next rail connecting to the mid?
if not ( (((nextdir1+4)%8)==chkdir and nextrely1==chkrely-y_offset) or (((nextdir2+4)%8)==chkdir and nextrely2==chkrely-y_offset) ) then
print("[advtrains]in conway: next "..minetest.pos_to_string(next).." not connecting, trying one node below!")
next.y=next.y-1
y_offset=y_offset-1
nextdir1, nextdir2, nextrely1, nextrely2, nextrailheight=advtrains.get_track_connections(nextnode.name, nextnode.param2)
if not ( (((nextdir1+4)%8)==chkdir and nextrely1==chkrely) or (((nextdir2+4)%8)==chkdir and nextrely2==chkrely) ) then
print("[advtrains]in conway: next "..minetest.pos_to_string(next).." rail not connecting, returning!")
--print("[advtrains] in order mid1,2,next1,2,chkdir "..middir1.." "..middir2.." "..nextdir1.." "..nextdir2.." "..chkdir)
return nil
end
end
--print("[advtrains]conway found rail.")
return vector.add(advtrains.round_vector_floor_y(next), {x=0, y=nextrailheight, z=0}), chkdir
end
function advtrains.round_vector_floor_y(vec)
return {x=math.floor(vec.x+0.5), y=math.floor(vec.y), z=math.floor(vec.z+0.5)}
end
function advtrains.yawToDirection(yaw, conn1, conn2)
if not conn1 or not conn2 then
error("given nil to yawToDirection: conn1="..(conn1 or "nil").." conn2="..(conn1 or "nil"))
end
local yaw1=math.pi*(conn1/4)
local yaw2=math.pi*(conn2/4)
if advtrains.minAngleDiffRad(yaw, yaw1)<advtrains.minAngleDiffRad(yaw, yaw2) then--change to > if weird behavior
return conn2
else
return conn1
end
end
function advtrains.minAngleDiffRad(r1, r2)
local try1=r2-r1
local try2=(r2+2*math.pi)-r1
local try3=r2-(r1+2*math.pi)
if math.min(math.abs(try1), math.abs(try2), math.abs(try3))==math.abs(try1) then
return try1
end
if math.min(math.abs(try1), math.abs(try2), math.abs(try3))==math.abs(try2) then
return try2
end
if math.min(math.abs(try1), math.abs(try2), math.abs(try3))==math.abs(try3) then
return try3
end
end
function advtrains.dumppath(path)
if not path then print("dumppath: no path(nil)") return end
local min=advtrains.minN(path)
local max=advtrains.maxN(path)
for i=min, max do print("["..i.."] "..(path[i] and minetest.pos_to_string(path[i]) or "nil")) end
end
function advtrains.merge_tables(a, ...)
local new={}
for _,t in ipairs({a,...}) do
for k,v in pairs(t) do new[k]=v end
end
return new
end
function advtrains.yaw_from_3_positions(prev, curr, next)
local pts=minetest.pos_to_string
print("p3 "..pts(prev)..pts(curr)..pts(next))
local prev2curr=math.atan2((curr.x-prev.x), (prev.z-curr.z))
local curr2next=math.atan2((next.x-curr.x), (curr.z-next.z))
print("y3 "..(prev2curr*360/(2*math.pi)).." "..(curr2next*360/(2*math.pi)))
return prev2curr+(advtrains.minAngleDiffRad(prev2curr, curr2next)/2)
end
function advtrains.get_wagon_yaw(front, first, second, back, pct)
local pts=minetest.pos_to_string
print("p "..pts(front)..pts(first)..pts(second)..pts(back))
local y2=advtrains.yaw_from_3_positions(second, first, front)
local y1=advtrains.yaw_from_3_positions(back, second, first)
print("y "..(y1*360/(2*math.pi)).." "..(y2*360/(2*math.pi)))
return y1+advtrains.minAngleDiffRad(y1, y2)*pct
end

19
init.lua Normal file
View File

@ -0,0 +1,19 @@
--advtrains
advtrains={}
advtrains.modpath = minetest.get_modpath("advtrains")
print=function(text)
minetest.log("action", tostring(text) or "<non-string>")
end
dofile(advtrains.modpath.."/helpers.lua");
dofile(advtrains.modpath.."/debugitems.lua");
dofile(advtrains.modpath.."/trainlogic.lua");
dofile(advtrains.modpath.."/trainhud.lua")
dofile(advtrains.modpath.."/trackplacer.lua")
dofile(advtrains.modpath.."/tracks.lua")
dofile(advtrains.modpath.."/wagons.lua")

19
init.lua~ Normal file
View File

@ -0,0 +1,19 @@
--advtrains
advtrains={}
advtrains.modpath = minetest.get_modpath("advtrains")
print=function(text)
minetest.log("action", tostring(text) or "<non-string>")
end
dofile(advtrains.modpath.."/helpers.lua");
dofile(advtrains.modpath.."/debugitems.lua");
dofile(advtrains.modpath.."/trainlogic.lua");
dofile(advtrains.modpath.."/trainhud.lua")
dofile(advtrains.modpath.."/trackplacer.lua")
dofile(advtrains.modpath.."/tracks.lua")
dofile(advtrains.modpath.."/vehicles.lua")

BIN
models/locomotive.b3d Normal file

Binary file not shown.

BIN
models/locomotive.blend Normal file

Binary file not shown.

BIN
models/magnet_track.blend Normal file

Binary file not shown.

BIN
models/newlocomotive.b3d Normal file

Binary file not shown.

BIN
models/newlocomotive.blend Normal file

Binary file not shown.

BIN
models/newlocomotive.blend1 Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

BIN
models/newwagon.blend Normal file

Binary file not shown.

BIN
models/newwagon.blend1 Normal file

Binary file not shown.

BIN
models/newwagon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

BIN
models/trackplane.b3d Normal file

Binary file not shown.

BIN
models/trackplane.blend Normal file

Binary file not shown.

BIN
models/trackvertical1.b3d Normal file

Binary file not shown.

BIN
models/trackvertical1.blend Normal file

Binary file not shown.

Binary file not shown.

BIN
models/trackvertical1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

BIN
models/trackvertical2.b3d Normal file

Binary file not shown.

BIN
models/trackvertical2.blend Normal file

Binary file not shown.

BIN
models/trackvertical2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

BIN
models/wagon.b3d Normal file

Binary file not shown.

121
pseudoload.lua Normal file
View File

@ -0,0 +1,121 @@
--pseudoload.lua
--responsible for keeping up a database of all rail nodes existant in the world, regardless of whether the mapchunk is loaded.
advtrains.trackdb={}
--trackdb[y][x][z]={conn1, conn2, rely1, rely2, railheight}
--serialization format:
--(2byte x)(2byte y)(2byte z)(4bits conn1, 4bits conn2)[(plain rely1)|(plain rely2)|(plain railheight)]\n
--[] may be missing if 0,0,0
--load initially
advtrains.pl_fpath=minetest.get_worldpath().."/advtrains_trackdb"
local file, err = io.open(advtrains.pl_fpath, "r")
if not file then
local er=err or "Unknown Error"
print("[advtrains]Failed loading advtrains trackdb save file "..er)
else
--custom format to save memory
while true do
local xbytes=file:read(2)
if not xbytes then
break --eof reached
end
local ybytes=file:read(2)
local zbytes=file:read(2)
local x=(string.byte(xbytes[1])-128)*256+(string.byte(xbytes[2]))
local y=(string.byte(ybytes[1])-128)*256+(string.byte(ybytes[2]))
local z=(string.byte(zbytes[1])-128)*256+(string.byte(zbytes[2]))
local conn1=string.byte(file:read(1))
local conn1=string.byte(file:read(1))
if not advtrains.trackdb[y] then advtrains.trackdb[y]={} end
if not advtrains.trackdb[y][x] then advtrains.trackdb[y][x]={} end
local rest=file.read("*l")
if rest~="" then
local rely1, rely2, railheight=string.match(rest, "([^|]+)|([^|]+)|([^|]+)")
if rely1 and rely2 and railheight then
advtrains.trackdb[y][x][z]={
conn1=conn1, conn2=conn2,
rely1=rely1, rely2=rely2,
railheight=railheight
}
else
advtrains.trackdb[y][x][z]={
conn1=conn1, conn2=conn2
}
end
else
advtrains.trackdb[y][x][z]={
conn1=conn1, conn2=conn2
}
end
end
file:close()
end
function advtrains.save_trackdb()
local file, err = io.open(advtrains.pl_fpath, "w")
if not file then
local er=err or "Unknown Error"
print("[advtrains]Failed saving advtrains trackdb save file "..er)
else
--custom format to save memory
for x,txl in pairs(advtrains.trackdb) do
for y,tyl in pairs(txl) do
for z,rail in pairs(tyl) do
file:write(string.char(math.floor(x/256)+128)..string.char((x%256)))
file:write(string.char(math.floor(y/256)+128)..string.char((y%256)))
file:write(string.char(math.floor(z/256)+128)..string.char((z%256)))
file:write(string.char(rail.conn1))
file:write(string.char(rail.conn2))
if (rail.rely1 and rail.rely1~=0) or (rail.rely2 and rail.rely2~=0) or (rail.railheight and rail.railheight~=0) then
file:write(rail.rely1.."|"..rail.rely2.."|"..rail.railheight)
end
file:write("\n")
end
end
end
file:close()
end
end
--get_node with pseudoload.
--returns:
--true, conn1, conn2, rely1, rely2, railheight in case everything's right.
--false if it's not a rail or the train does not drive on this rail, but it is loaded or
--nil if the node is neither loaded nor in trackdb
--the distraction between false and nil will be needed only in special cases.(train initpos)
function advtrains.get_rail_info_at(pos, traintype)
local node=minetest.get_node_or_nil(pos)
if not node then
--try raildb
local rdp=vector.round(rdp)
local dbe=advtrains.trackdb[rdp.y][rdp.x][rdp.z]
if dbe then
return true, dbe.conn1, dbe.conn2, dbe.rely1 or 0, dbe.rely2 or 0, dbe.railheight or 0
else
return nil
end
end
local nodename=node.name
if(not advtrains.is_track_and_drives_on(nodename, advtrains.all_traintypes[traintype].drives_on)) then
return false
end
local conn1, conn2, rely1, rely2, railheight=advtrains.get_track_connections(node.name, node.param2)
--already in trackdb?
local rdp=vector.round(rdp)
if not advtrains.trackdb[rdp.y][rdp.x][rdp.z] then--TODO is this write prevention here really needed?
advtrains.trackdb[rdp.y][rdp.x][rdp.z]={
conn1=conn1, conn2=conn2,
rely1=rely1, rely2=rely2,
railheight=railheight
}
end
return true, conn1, conn2, rely1, rely2, railheight
end

121
pseudoload.lua~ Normal file
View File

@ -0,0 +1,121 @@
--pseudoload.lua
--responsible for keeping up a database of all rail nodes existant in the world, regardless of whether the mapchunk is loaded.
advtrains.trackdb={}
--trackdb[y][x][z]={conn1, conn2, rely1, rely2, railheight}
--serialization format:
--(2byte x)(2byte y)(2byte z)(4bits conn1, 4bits conn2)[(plain rely1)|(plain rely2)|(plain railheight)]\n
--[] may be missing if 0,0,0
--load initially
advtrains.pl_fpath=minetest.get_worldpath().."/advtrains_trackdb"
local file, err = io.open(advtrains.pl_fpath, "r")
if not file then
local er=err or "Unknown Error"
print("[advtrains]Failed loading advtrains trackdb save file "..er)
else
--custom format to save memory
while true do
local xbytes=file:read(2)
if not xbytes then
break --eof reached
end
local ybytes=file:read(2)
local zbytes=file:read(2)
local x=(string.byte(xbytes[1])-128)*256+(string.byte(xbytes[2]))
local y=(string.byte(ybytes[1])-128)*256+(string.byte(ybytes[2]))
local z=(string.byte(zbytes[1])-128)*256+(string.byte(zbytes[2]))
local conn1=string.byte(file:read(1))
local conn1=string.byte(file:read(1))
if not advtrains.trackdb[y] then advtrains.trackdb[y]={} end
if not advtrains.trackdb[y][x] then advtrains.trackdb[y][x]={} end
local rest=file.read("*l")
if rest~="" then
local rely1, rely2, railheight=string.match(rest, "([^|]+)|([^|]+)|([^|]+)")
if rely1 and rely2 and railheight then
advtrains.trackdb[y][x][z]={
conn1=conn1, conn2=conn2,
rely1=rely1, rely2=rely2,
railheight=railheight
}
else
advtrains.trackdb[y][x][z]={
conn1=conn1, conn2=conn2
}
end
else
advtrains.trackdb[y][x][z]={
conn1=conn1, conn2=conn2
}
end
end
file:close()
end
function advtrains.save_trackdb()
local file, err = io.open(advtrains.pl_fpath, "w")
if not file then
local er=err or "Unknown Error"
print("[advtrains]Failed saving advtrains trackdb save file "..er)
else
--custom format to save memory
for x,txl in pairs(advtrains.trackdb) do
for y,tyl in pairs(txl) do
for z,rail in pairs(tyl) do
file:write(string.char(math.floor(x/256)+128)..string.char((x%256)))
file:write(string.char(math.floor(y/256)+128)..string.char((y%256)))
file:write(string.char(math.floor(z/256)+128)..string.char((z%256)))
file:write(string.char(rail.conn1))
file:write(string.char(rail.conn2))
if (rail.rely1 and rail.rely1~=0) or (rail.rely2 and rail.rely2~=0) or (rail.railheight and rail.railheight~=0) then
file:write(rail.rely1.."|"..rail.rely2.."|"..rail.railheight)
end
file:write("\n")
end
end
end
file:close()
end
end
--get_node with pseudoload.
--returns:
--true, conn1, conn2, rely1, rely2, railheight in case everything's right.
--false if it's not a rail or the train does not drive on this rail, but it is loaded or
--nil if the node is neither loaded nor in trackdb
--the distraction between false and nil will be needed only in special cases.(train initpos)
function advtrains.get_rail_at(pos, traintype)
local node=minetest.get_node_or_nil(pos)
if not node then
--try raildb
local rdp=vector.round(rdp)
local dbe=advtrains.trackdb[rdp.y][rdp.x][rdp.z]
if dbe then
return true, dbe.conn1, dbe.conn2, dbe.rely1 or 0, dbe.rely2 or 0, dbe.railheight or 0
else
return nil
end
end
local nodename=node.name
if(not advtrains.is_track_and_drives_on(nodename, advtrains.all_traintypes[traintype].drives_on)) then
return false
end
local conn1, conn2, rely1, rely2, railheight=advtrains.get_track_connections(node.name, node.param2)
--already in trackdb?
local rdp=vector.round(rdp)
if not advtrains.trackdb[rdp.y][rdp.x][rdp.z] then--TODO is this write prevention here really needed?
advtrains.trackdb[rdp.y][rdp.x][rdp.z]={
conn1=conn1, conn2=conn2,
rely1=rely1, rely2=rely2,
railheight=railheight
}
end
return true, conn1, conn2, rely1, rely2, railheight
end

27
readme.txt Normal file
View File

@ -0,0 +1,27 @@
Hi
Since there are no trains mods out there that satisfied my needs, I decided to write my own.
Until now are supported:
- tracks and switches, based on angles of 45 degrees
- wagons (atm they all look like very simple locomotives in different colors) that drive on these rails and collide with nodes in the environment(they need 3x3x3 space)
- conecting/disconnecting of wagons/trains
Planned features:
- trains will only move if a locomotive is in train
- locomotives need coal to drive (and water...)
- more types of trains and rails(electric, diesel, maglevs...)
- better controls
- cool models for trains and rails
- an API, because API's are cool.
(I will probably split trains api and actual trains into two mods, to allow for extensions to be enabled individually)
At the moment, you can try around with the trains. There are some debug messages that shouldn't disturb you. Note that anything may change in future releases.
- Use the Track(placer) item to place tracks. In most cases it will adjust rails in the direction you need them.
- use the trackworker tool to rotate tracks(right-click) and to change normal rails into switches(left-click)
- to overcome heights you need the rails with the strange gravel texture in the background, place them and you will understand.
- place any of the wagons in different colors by picking the item and placing it on a track.
- right-click a wagon to sit onto it.
- right-click a wagon while holding W / S to accelerate/decelerate the train. This will fail if the train can't move in the desired direction.
- drive two trains together and they will connect automatically.
- right-click a wagon while holding sneak key to split a train at this wagon
- right-click a wagon while holding aux1 to print useful(TM) information to the console
Have fun!

24
readme.txt~ Normal file
View File

@ -0,0 +1,24 @@
Hi
Since there are no trains mods out there that satisfied my needs, I decided to write my own.
Until now are supported:
- tracks and switches, based on angles of 45 degrees
- wagons (atm they all look like very simple locomotives in different colors) that drive on these rails and collide with nodes in the environment(they need 3x3x3 space)
- conecting/disconnecting of wagons/trains
Planned features:
- trains will only move if a locomotive is in train
- locomotives need coal to drive (and water...)
- more types of trains and rails(electric, diesel, maglevs...)
- better controls
- cool models for trains and rails
At the moment, you can try around with the trains. There are some debug messages that shouldn't disturb you. Note that anything may change in future releases.
- Use the Track(placer) item to place tracks. In most cases it will adjust rails in the direction you need them.
- use the trackworker tool to rotate tracks(right-click) and to change normal rails into switches(left-click)
- place any of the wagons in different colors by picking the item and placing it on a track.
- right-click a wagon to sit onto it.
- right-click a wagon while holding W / S to accelerate/decelerate the train. This will fail if the train can't move in the desired direction.
- drive two trains together and they will connect automatically.
- right-click a wagon while holding sneak key to split a train at this wagon
- right-click a wagon while holding aux1 to print useful(TM) information to the console
Have fun!

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

BIN
textures/black.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 908 B

BIN
textures/blue.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 B

BIN
textures/green.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 527 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 552 B

BIN
textures/nope/track_std.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 414 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 526 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 491 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
textures/red.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
textures/track_cr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
textures/track_cr_45.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
textures/track_curve_45.xcf Normal file

Binary file not shown.

BIN
textures/track_placer.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 621 B

BIN
textures/track_st.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 621 B

BIN
textures/track_st_45.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
textures/track_swl_cr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
textures/track_swl_st.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
textures/track_swr_cr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
textures/track_swr_st.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
textures/track_vert1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
textures/track_vert2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
textures/yellow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

200
trackplacer.lua Normal file
View File

@ -0,0 +1,200 @@
--trackplacer.lua
--holds code for the track-placing system. the default 'track' item will be a craftitem that places rails as needed. this will neither place or change switches nor place vertical rails.
--keys:conn1_conn2 (example:1_4)
--values:{name=x, param2=x}
advtrains.trackplacer_dir_to_node_mapping={}
--keys are nodenames!
advtrains.trackplacer_modified_rails={}
function advtrains.trackplacer_register(nodename, conn1, conn2)
for i=0,3 do
advtrains.trackplacer_dir_to_node_mapping[((conn1+2*i)%8).."_"..((conn2+2*i)%8)]={name=nodename, param2=i}
advtrains.trackplacer_dir_to_node_mapping[((conn2+2*i)%8).."_"..((conn1+2*i)%8)]={name=nodename, param2=i}
end
advtrains.trackplacer_modified_rails[nodename]=true
end
function advtrains.find_adjacent_tracks(pos)--TODO vertical calculations(check node below)
local conn1=0
while conn1<8 and not advtrains.is_track_and_drives_on(minetest.get_node(advtrains.dirCoordSet(pos, conn1)).name, advtrains.all_tracktypes) do
conn1=conn1+1
end
if conn1>=8 then
return nil, nil
end
local conn2=0
while conn2<8 and not advtrains.is_track_and_drives_on(minetest.get_node(advtrains.dirCoordSet(pos, conn2)).name, advtrains.all_tracktypes) or conn2==conn1 do
conn2=conn2+1
end
if conn2>=8 then
return conn1, nil
end
return conn1, conn2
end
function advtrains.placetrack(pos)
local conn1, conn2=advtrains.find_adjacent_tracks(pos)
if not conn1 and not conn2 then
minetest.set_node(pos, {name="advtrains:track_st"})
elseif conn1 and not conn2 then
local node1=minetest.get_node(advtrains.dirCoordSet(pos, conn1))
local node1_conn1, node1_conn2=advtrains.get_track_connections(node1.name, node1.param2)
local node1_backconnects=(conn1+4)%8==node1_conn1 or (conn1+4)%8==node1_conn2
if not node1_backconnects and advtrains.trackplacer_modified_rails[node1.name] then
--check if this rail has a dangling connection
--TODO possible problems on |- situation
if not advtrains.is_track_and_drives_on(minetest.get_node(advtrains.dirCoordSet(pos, node1_conn1)).name, advtrains.all_tracktypes) then
if advtrains.trackplacer_dir_to_node_mapping[node1_conn1.."_"..((conn1+4)%8)] then
minetest.set_node(advtrains.dirCoordSet(pos, conn1), advtrains.trackplacer_dir_to_node_mapping[node1_conn1.."_"..((conn1+4)%8)])
end
elseif not advtrains.is_track_and_drives_on(minetest.get_node(advtrains.dirCoordSet(pos, node1_conn2)).name, advtrains.all_tracktypes) then
if advtrains.trackplacer_dir_to_node_mapping[node1_conn2.."_"..((conn1+4)%8)] then
minetest.set_node(advtrains.dirCoordSet(pos, conn1), advtrains.trackplacer_dir_to_node_mapping[node1_conn2.."_"..((conn1+4)%8)])
end
end
end
--second end will be free. place standard rail
if conn1%2==1 then
minetest.set_node(pos, {name="advtrains:track_st_45", param2=(conn1-1)/2})
else
minetest.set_node(pos, {name="advtrains:track_st", param2=conn1/2})
end
elseif conn1 and conn2 then
if not advtrains.trackplacer_dir_to_node_mapping[conn1.."_"..conn2] then
minetest.set_node(pos, {name="advtrains:track_st"})
return
end
local node1=minetest.get_node(advtrains.dirCoordSet(pos, conn1))
local node1_conn1, node1_conn2=advtrains.get_track_connections(node1.name, node1.param2)
local node1_backconnects=(conn1+4)%8==node1_conn1 or (conn1+4)%8==node1_conn2
if not node1_backconnects and advtrains.trackplacer_modified_rails[node1.name] then
--check if this rail has a dangling connection
--TODO possible problems on |- situation
if not advtrains.is_track_and_drives_on(minetest.get_node(advtrains.dirCoordSet(pos, node1_conn1)).name, advtrains.all_tracktypes) then
if advtrains.trackplacer_dir_to_node_mapping[node1_conn1.."_"..((conn1+4)%8)] then
minetest.set_node(advtrains.dirCoordSet(pos, conn1), advtrains.trackplacer_dir_to_node_mapping[node1_conn1.."_"..((conn1+4)%8)])
end
elseif not advtrains.is_track_and_drives_on(minetest.get_node(advtrains.dirCoordSet(pos, node1_conn2)).name, advtrains.all_tracktypes) then
if advtrains.trackplacer_dir_to_node_mapping[node1_conn2.."_"..((conn1+4)%8)] then
minetest.set_node(advtrains.dirCoordSet(pos, conn1), advtrains.trackplacer_dir_to_node_mapping[node1_conn2.."_"..((conn1+4)%8)])
end
end
end
local node2=minetest.get_node(advtrains.dirCoordSet(pos, conn2))
local node2_conn1, node2_conn2=advtrains.get_track_connections(node2.name, node2.param2)
local node2_backconnects=(conn2+4)%8==node2_conn1 or (conn2+4)%8==node2_conn2
if not node2_backconnects and advtrains.trackplacer_modified_rails[node2.name] then
--check if this rail has a dangling connection
--TODO possible problems on |- situation
if not advtrains.is_track_and_drives_on(minetest.get_node(advtrains.dirCoordSet(pos, node2_conn1)).name, advtrains.all_tracktypes) then
if advtrains.trackplacer_dir_to_node_mapping[node2_conn1.."_"..((conn2+4)%8)] then
minetest.set_node(advtrains.dirCoordSet(pos, conn2), advtrains.trackplacer_dir_to_node_mapping[node2_conn1.."_"..((conn2+4)%8)])
end
elseif not advtrains.is_track_and_drives_on(minetest.get_node(advtrains.dirCoordSet(pos, node2_conn2)).name, advtrains.all_tracktypes) then
if advtrains.trackplacer_dir_to_node_mapping[node2_conn2.."_"..((conn1+4)%8)] then
minetest.set_node(advtrains.dirCoordSet(pos, conn2), advtrains.trackplacer_dir_to_node_mapping[node2_conn2.."_"..((conn2+4)%8)])
end
end
end
minetest.set_node(pos, advtrains.trackplacer_dir_to_node_mapping[conn1.."_"..conn2])
end
end
advtrains.trackworker_cycle_nodes={
["swr_cr"]="st",
["swr_st"]="st",
["st"]="cr",
["cr"]="swl_st",
["swl_cr"]="swr_cr",
["swl_st"]="swr_st",
}
function advtrains.register_track_placer(nnprefix, imgprefix, dispname)
minetest.register_craftitem(nnprefix.."_placer",{
description = dispname,
inventory_image = imgprefix.."_placer.png",
wield_image = imgprefix.."_placer.png",
on_place = function(itemstack, placer, pointed_thing)
if pointed_thing.type=="node" then
local pos=pointed_thing.above
if minetest.registered_nodes[minetest.get_node(pos).name] and minetest.registered_nodes[minetest.get_node(pos).name].buildable_to then
advtrains.placetrack(pos, nnprefix)
itemstack:take_item()
end
end
return itemstack
end,
})
end
minetest.register_craftitem("advtrains:trackworker",{
description = "Track Worker Tool\n\nLeft-click: change rail type (straight/curve/switch)\nRight-click: rotate rail",
groups = {cracky=1}, -- key=name, value=rating; rating=1..3.
inventory_image = "advtrains_trackworker.png",
wield_image = "advtrains_trackworker.png",
stack_max = 1,
on_place = function(itemstack, placer, pointed_thing)
if pointed_thing.type=="node" then
local pos=pointed_thing.under
local node=minetest.get_node(pos)
if not advtrains.is_track_and_drives_on(minetest.get_node(pos).name, advtrains.all_tracktypes) then return end
if advtrains.is_train_at_pos(pos) then return end
local nodeprefix, railtype=string.match(node.name, "^(.-)_(.+)$")
local basename=string.match(railtype, "^(.+)_45$")
if basename then
if not advtrains.trackworker_cycle_nodes[basename] then
print("[advtrains]rail not workable by trackworker")
return
end
minetest.set_node(pos, {name=nodeprefix.."_"..basename, param2=(node.param2+1)%4})
return
else
if not advtrains.trackworker_cycle_nodes[railtype] then
print("[advtrains]rail not workable by trackworker")
return
end
minetest.set_node(pos, {name=nodeprefix.."_"..railtype.."_45", param2=node.param2})
end
end
end,
on_use=function(itemstack, user, pointed_thing)
if pointed_thing.type=="node" then
local pos=pointed_thing.under
local node=minetest.get_node(pos)
if not advtrains.is_track_and_drives_on(minetest.get_node(pos).name, advtrains.all_tracktypes) then return end
if advtrains.is_train_at_pos(pos) then return end
local nodeprefix, railtype=string.match(node.name, "^(.-)_(.+)$")
local basename=string.match(railtype, "^(.+)_45$")
if basename then
if not advtrains.trackworker_cycle_nodes[basename] then
print("[advtrains]trackworker does not know what to set here...")
return
end
print(advtrains.trackworker_cycle_nodes[basename].."_45")
minetest.set_node(pos, {name=nodeprefix.."_"..advtrains.trackworker_cycle_nodes[basename].."_45", param2=node.param2})
return
else
if not advtrains.trackworker_cycle_nodes[railtype] then
print("[advtrains]trackworker does not know what to set here...")
return
end
minetest.set_node(pos, {name=nodeprefix.."_"..advtrains.trackworker_cycle_nodes[railtype], param2=node.param2})
end
--invalidate trains
for k,v in pairs(advtrains.trains) do
v.restore_add_index=v.index-math.floor(v.index+0.5)
v.path=nil
v.index=nil
end
end
end,
})

200
trackplacer.lua~ Normal file
View File

@ -0,0 +1,200 @@
--trackplacer.lua
--holds code for the track-placing system. the default 'track' item will be a craftitem that places rails as needed. this will neither place or change switches nor place vertical rails.
--keys:conn1_conn2 (example:1_4)
--values:{name=x, param2=x}
advtrains.trackplacer_dir_to_node_mapping={}
--keys are nodenames!
advtrains.trackplacer_modified_rails={}
function advtrains.trackplacer_register(nodename, conn1, conn2)
for i=0,3 do
advtrains.trackplacer_dir_to_node_mapping[((conn1+2*i)%8).."_"..((conn2+2*i)%8)]={name=nodename, param2=i}
advtrains.trackplacer_dir_to_node_mapping[((conn2+2*i)%8).."_"..((conn1+2*i)%8)]={name=nodename, param2=i}
end
advtrains.trackplacer_modified_rails[nodename]=true
end
function advtrains.find_adjacent_tracks(pos)--TODO vertical calculations(check node below)
local conn1=0
while conn1<8 and not advtrains.is_track_and_drives_on(minetest.get_node(advtrains.dirCoordSet(pos, conn1)).name, advtrains.all_tracktypes) do
conn1=conn1+1
end
if conn1>=8 then
return nil, nil
end
local conn2=0
while conn2<8 and not advtrains.is_track_and_drives_on(minetest.get_node(advtrains.dirCoordSet(pos, conn2)).name, advtrains.all_tracktypes) or conn2==conn1 do
conn2=conn2+1
end
if conn2>=8 then
return conn1, nil
end
return conn1, conn2
end
function advtrains.placetrack(pos)
local conn1, conn2=advtrains.find_adjacent_tracks(pos)
if not conn1 and not conn2 then
minetest.set_node(pos, {name="advtrains:track_st"})
elseif conn1 and not conn2 then
local node1=minetest.get_node(advtrains.dirCoordSet(pos, conn1))
local node1_conn1, node1_conn2=advtrains.get_track_connections(node1.name, node1.param2)
local node1_backconnects=(conn1+4)%8==node1_conn1 or (conn1+4)%8==node1_conn2
if not node1_backconnects and advtrains.trackplacer_modified_rails[node1.name] then
--check if this rail has a dangling connection
--TODO possible problems on |- situation
if not advtrains.is_track_and_drives_on(minetest.get_node(advtrains.dirCoordSet(pos, node1_conn1)).name, advtrains.all_tracktypes) then
if advtrains.trackplacer_dir_to_node_mapping[node1_conn1.."_"..((conn1+4)%8)] then
minetest.set_node(advtrains.dirCoordSet(pos, conn1), advtrains.trackplacer_dir_to_node_mapping[node1_conn1.."_"..((conn1+4)%8)])
end
elseif not advtrains.is_track_and_drives_on(minetest.get_node(advtrains.dirCoordSet(pos, node1_conn2)).name, advtrains.all_tracktypes) then
if advtrains.trackplacer_dir_to_node_mapping[node1_conn2.."_"..((conn1+4)%8)] then
minetest.set_node(advtrains.dirCoordSet(pos, conn1), advtrains.trackplacer_dir_to_node_mapping[node1_conn2.."_"..((conn1+4)%8)])
end
end
end
--second end will be free. place standard rail
if conn1%2==1 then
minetest.set_node(pos, {name="advtrains:track_st_45", param2=(conn1-1)/2})
else
minetest.set_node(pos, {name="advtrains:track_st", param2=conn1/2})
end
elseif conn1 and conn2 then
if not advtrains.trackplacer_dir_to_node_mapping[conn1.."_"..conn2] then
minetest.set_node(pos, {name="advtrains:track_st"})
return
end
local node1=minetest.get_node(advtrains.dirCoordSet(pos, conn1))
local node1_conn1, node1_conn2=advtrains.get_track_connections(node1.name, node1.param2)
local node1_backconnects=(conn1+4)%8==node1_conn1 or (conn1+4)%8==node1_conn2
if not node1_backconnects and advtrains.trackplacer_modified_rails[node1.name] then
--check if this rail has a dangling connection
--TODO possible problems on |- situation
if not advtrains.is_track_and_drives_on(minetest.get_node(advtrains.dirCoordSet(pos, node1_conn1)).name, advtrains.all_tracktypes) then
if advtrains.trackplacer_dir_to_node_mapping[node1_conn1.."_"..((conn1+4)%8)] then
minetest.set_node(advtrains.dirCoordSet(pos, conn1), advtrains.trackplacer_dir_to_node_mapping[node1_conn1.."_"..((conn1+4)%8)])
end
elseif not advtrains.is_track_and_drives_on(minetest.get_node(advtrains.dirCoordSet(pos, node1_conn2)).name, advtrains.all_tracktypes) then
if advtrains.trackplacer_dir_to_node_mapping[node1_conn2.."_"..((conn1+4)%8)] then
minetest.set_node(advtrains.dirCoordSet(pos, conn1), advtrains.trackplacer_dir_to_node_mapping[node1_conn2.."_"..((conn1+4)%8)])
end
end
end
local node2=minetest.get_node(advtrains.dirCoordSet(pos, conn2))
local node2_conn1, node2_conn2=advtrains.get_track_connections(node2.name, node2.param2)
local node2_backconnects=(conn2+4)%8==node2_conn1 or (conn2+4)%8==node2_conn2
if not node2_backconnects and advtrains.trackplacer_modified_rails[node2.name] then
--check if this rail has a dangling connection
--TODO possible problems on |- situation
if not advtrains.is_track_and_drives_on(minetest.get_node(advtrains.dirCoordSet(pos, node2_conn1)).name, advtrains.all_tracktypes) then
if advtrains.trackplacer_dir_to_node_mapping[node2_conn1.."_"..((conn2+4)%8)] then
minetest.set_node(advtrains.dirCoordSet(pos, conn2), advtrains.trackplacer_dir_to_node_mapping[node2_conn1.."_"..((conn2+4)%8)])
end
elseif not advtrains.is_track_and_drives_on(minetest.get_node(advtrains.dirCoordSet(pos, node2_conn2)).name, advtrains.all_tracktypes) then
if advtrains.trackplacer_dir_to_node_mapping[node2_conn2.."_"..((conn1+4)%8)] then
minetest.set_node(advtrains.dirCoordSet(pos, conn2), advtrains.trackplacer_dir_to_node_mapping[node2_conn2.."_"..((conn2+4)%8)])
end
end
end
minetest.set_node(pos, advtrains.trackplacer_dir_to_node_mapping[conn1.."_"..conn2])
end
end
advtrains.trackworker_cycle_nodes={
["swr_cr"]="st",
["swr_st"]="st",
["st"]="cr",
["cr"]="swl_st",
["swl_cr"]="swr_cr",
["swl_st"]="swr_st",
}
function advtrains.register_track_placer(nnprefix, imgprefix, dispname)
minetest.register_craftitem(nnprefix.."_placer",{
description = dispname,
inventory_image = imgprefix.."_placer.png",
wield_image = imgprefix.."_placer.png",
on_place = function(itemstack, placer, pointed_thing)
if pointed_thing.type=="node" then
local pos=pointed_thing.above
if minetest.registered_nodes[minetest.get_node(pos).name] and minetest.registered_nodes[minetest.get_node(pos).name].buildable_to then
advtrains.placetrack(pos, nnprefix)
itemstack:take_item()
end
end
return itemstack
end,
})
end
minetest.register_craftitem("advtrains:trackworker",{
description = "Track Worker Tool\n\nLeft-click: change rail type (straight/curve/switch)\nRight-click: rotate rail",
groups = {cracky=1}, -- key=name, value=rating; rating=1..3.
inventory_image = "advtrains_trackworker.png",
wield_image = "advtrains_trackworker.png",
stack_max = 1,
on_place = function(itemstack, placer, pointed_thing)
if pointed_thing.type=="node" then
local pos=pointed_thing.under
local node=minetest.get_node(pos)
if not advtrains.is_track_and_drives_on(minetest.get_node(pos).name, advtrains.all_tracktypes) then return end
if advtrains.is_train_at_pos(pos) then return end
local nodeprefix, railtype=string.match(node.name, "^(.-)_(.+)$")
local basename=string.match(railtype, "^(.+)_45$")
if basename then
if not advtrains.trackworker_cycle_nodes[basename] then
print("[advtrains]rail not workable by trackworker")
return
end
minetest.set_node(pos, {name=nodeprefix.."_"..basename, param2=(node.param2+1)%4})
return
else
if not advtrains.trackworker_cycle_nodes[railtype] then
print("[advtrains]rail not workable by trackworker")
return
end
minetest.set_node(pos, {name=nodeprefix.."_"..railtype.."_45", param2=node.param2})
end
end
end,
on_use=function(itemstack, user, pointed_thing)
if pointed_thing.type=="node" then
local pos=pointed_thing.under
local node=minetest.get_node(pos)
if not advtrains.is_track_and_drives_on(minetest.get_node(pos).name, advtrains.all_tracktypes) then return end
if advtrains.is_train_at_pos(pos) then return end
local nodeprefix, railtype=string.match(node.name, "^(.-)_(.+)$")
local basename=string.match(railtype, "^(.+)_45$")
if basename then
if not advtrains.trackworker_cycle_nodes[basename] then
print("[advtrains]trackworker does not know what to set here...")
return
end
print(advtrains.trackworker_cycle_nodes[basename].."_45")
minetest.set_node(pos, {name=nodeprefix.."_"..advtrains.trackworker_cycle_nodes[basename].."_45", param2=node.param2})
return
else
if not advtrains.trackworker_cycle_nodes[railtype] then
print("[advtrains]trackworker does not know what to set here...")
return
end
minetest.set_node(pos, {name=nodeprefix.."_"..advtrains.trackworker_cycle_nodes[railtype], param2=node.param2})
end
--invalidate trains
for k,v in pairs(advtrains.trains) do
v.restore_add_index=v.index-math.floor(v.index+0.5)
v.path=nil
v.index=nil
end
end
end,
})

152
tracks.lua Normal file
View File

@ -0,0 +1,152 @@
--advtrains by orwell96, see readme.txt
--[[TracksDefinition
nodename_prefix
texture_prefix
description
straight={}
straight45={}
curve={}
curve45={}
lswitchst={}
lswitchst45={}
rswitchst={}
rswitchst45={}
lswitchcr={}
lswitchcr45={}
rswitchcr={}
rswitchcr45={}
vert1={
--you'll probably want to override mesh here
}
vert2={
--you'll probably want to override mesh here
}
]]--
advtrains.all_tracktypes={}
function advtrains.register_tracks(tracktype, def)
local function make_switchfunc(suffix_target)
return function(pos, node)
if advtrains.is_train_at_pos(pos) then return end
advtrains.invalidate_all_paths()
minetest.set_node(pos, {name=def.nodename_prefix.."_"..suffix_target, param2=node.param2})
end
end
local function make_overdef(img_suffix, conn1, conn2, switchfunc)
return {
tiles = {def.texture_prefix.."_"..img_suffix..".png"},
inventory_image = def.texture_prefix.."_"..img_suffix..".png",
wield_image = def.texture_prefix.."_"..img_suffix..".png",
connect1=conn1,
connect2=conn2,
on_rightclick=switchfunc,
}
end
local common_def={
description = def.description,
drawtype = "mesh",
mesh = "trackplane.b3d",
paramtype="light",
paramtype2="facedir",
walkable = false,
selection_box = {
type = "fixed",
fixed = {-1/2, -1/2, -1/2, 1/2, -1/2+1/16, 1/2},
},
groups = {
attached_node=1,
["advtrains_track_"..tracktype]=1,
dig_immediate=2,
not_in_creative_inventory=1,
},
rely1=0,
rely2=0,
railheight=0,
drop="advtrains:placetrack_"..tracktype,
--on_rightclick=function(pos, node, clicker)
-- minetest.set_node(pos, {name=node.name, param2=(node.param2+1)%4})
--end
can_dig=function(pos)
return not advtrains.is_train_at_pos(pos)
end,
after_dig_node=function()
advtrains.invalidate_all_paths()
end
}
minetest.register_node(def.nodename_prefix.."_st", advtrains.merge_tables(common_def, make_overdef("st", 0, 4), def.straight or {}))
minetest.register_node(def.nodename_prefix.."_st_45", advtrains.merge_tables(common_def, make_overdef("st_45", 1, 5), def.straight45 or {}))
minetest.register_node(def.nodename_prefix.."_cr", advtrains.merge_tables(common_def, make_overdef("cr", 0, 3), def.curve or {}))
minetest.register_node(def.nodename_prefix.."_cr_45", advtrains.merge_tables(common_def, make_overdef("cr_45", 1, 4), def.curve45 or {}))
advtrains.trackplacer_register(def.nodename_prefix.."_st", 0, 4)
advtrains.trackplacer_register(def.nodename_prefix.."_st_45", 1, 5)
advtrains.trackplacer_register(def.nodename_prefix.."_cr", 0, 3)
advtrains.trackplacer_register(def.nodename_prefix.."_cr_45", 1, 4)
minetest.register_node(def.nodename_prefix.."_swl_st", advtrains.merge_tables(common_def, make_overdef("swl_st", 0, 4, make_switchfunc("swl_cr")), def.lswitchst or {}))
minetest.register_node(def.nodename_prefix.."_swl_st_45", advtrains.merge_tables(common_def, make_overdef("swl_st_45", 1, 5, make_switchfunc("swl_cr_45")), def.lswitchst45 or {}))
minetest.register_node(def.nodename_prefix.."_swl_cr", advtrains.merge_tables(common_def, make_overdef("swl_cr", 0, 3, make_switchfunc("swl_st")), def.lswitchcr or {}))
minetest.register_node(def.nodename_prefix.."_swl_cr_45", advtrains.merge_tables(common_def, make_overdef("swl_cr_45", 1, 4, make_switchfunc("swl_st_45")), def.lswitchcr45 or {}))
minetest.register_node(def.nodename_prefix.."_swr_st", advtrains.merge_tables(common_def, make_overdef("swr_st", 0, 4, make_switchfunc("swr_cr")), def.rswitchst or {}))
minetest.register_node(def.nodename_prefix.."_swr_st_45", advtrains.merge_tables(common_def, make_overdef("swr_st_45", 1, 5, make_switchfunc("swr_cr_45")), def.rswitchst45 or {}))
minetest.register_node(def.nodename_prefix.."_swr_cr", advtrains.merge_tables(common_def, make_overdef("swr_cr", 0, 5, make_switchfunc("swr_st")), def.rswitchcr or {}))
minetest.register_node(def.nodename_prefix.."_swr_cr_45", advtrains.merge_tables(common_def, make_overdef("swr_cr_45", 1, 6, make_switchfunc("swr_st_45")), def.rswitchcr45 or {}))
minetest.register_node(def.nodename_prefix.."_vert1", advtrains.merge_tables(common_def, make_overdef("vert1", 0, 4), {
mesh = "trackvertical1.b3d",
rely1=0,
rely2=0.5,
railheight=0.25,
description = def.description.." (vertical track lower node)",
}, def.vert1 or {}))
minetest.register_node(def.nodename_prefix.."_vert2", advtrains.merge_tables(common_def, make_overdef("vert2", 0, 4), {
mesh = "trackvertical2.b3d",
rely1=0.5,
rely2=1,
railheight=0.75,
description = def.description.." (vertical track lower node)",
},def.vert2 or {}))
advtrains.register_track_placer(def.nodename_prefix, def.texture_prefix, def.description)
table.insert(advtrains.all_tracktypes, tracktype)
end
advtrains.register_tracks("regular", {
nodename_prefix="advtrains:track",
texture_prefix="track",
description="Regular Train Track",
})
function advtrains.is_track_and_drives_on(nodename, drives_on)
if not minetest.registered_nodes[nodename] then
return false
end
local nodedef=minetest.registered_nodes[nodename]
for k,v in ipairs(drives_on) do
if nodedef.groups["advtrains_track_"..v] then
return true
end
end
return false
end
function advtrains.get_track_connections(name, param2)
local nodedef=minetest.registered_nodes[name]
if not nodedef then print("[advtrains] get_track_connections couldn't find nodedef for nodename "..(name or "nil")) return 0,4 end
local noderot=param2
if not param2 then noderot=0 end
if noderot > 3 then print("[advtrains] get_track_connections: rail has invaild param2 of "..noderot) noderot=0 end
return (nodedef.connect1 + 2 * noderot)%8, (nodedef.connect2 + 2 * noderot)%8, nodedef.rely1 or 0, nodedef.rely2 or 0, nodedef.railheight or 0
end

150
tracks.lua~ Normal file
View File

@ -0,0 +1,150 @@
--advtrains by orwell96, see readme.txt
--[[TracksDefinition
nodename_prefix
texture_prefix
description
straight={}
straight45={}
curve={}
curve45={}
lswitchst={}
lswitchst45={}
rswitchst={}
rswitchst45={}
lswitchcr={}
lswitchcr45={}
rswitchcr={}
rswitchcr45={}
vert1={
--you'll probably want to override mesh here
}
vert2={
--you'll probably want to override mesh here
}
]]--
advtrains.all_tracktypes={}
function advtrains.register_tracks(tracktype, def)
local function make_switchfunc(suffix_target)
return function(pos, node)
if advtrains.is_train_at_pos(pos) then return end
advtrains.invalidate_all_paths()
minetest.set_node(pos, {name=def.nodename_prefix.."_"..suffix_target, param2=node.param2})
end
end
local function make_overdef(img_suffix, conn1, conn2, switchfunc)
return {
tiles = {def.texture_prefix.."_"..img_suffix..".png"},
inventory_image = def.texture_prefix.."_"..img_suffix..".png",
wield_image = def.texture_prefix.."_"..img_suffix..".png",
connect1=conn1,
connect2=conn2,
on_rightclick=switchfunc,
}
end
local common_def={
description = def.description,
drawtype = "mesh",
mesh = "trackplane.b3d",
paramtype="light",
paramtype2="facedir",
walkable = false,
selection_box = {
type = "fixed",
fixed = {-1/2, -1/2, -1/2, 1/2, -1/2+1/16, 1/2},
},
groups = {
attached_node=1,
["advtrains_track_"..tracktype]=1,
dig_immediate=2,
not_in_creative_inventory=1,
},
rely1=0,
rely2=0,
railheight=0,
drop="advtrains:placetrack_"..tracktype,
--on_rightclick=function(pos, node, clicker)
-- minetest.set_node(pos, {name=node.name, param2=(node.param2+1)%4})
--end
can_dig=function(pos)
return not advtrains.is_train_at_pos(pos)
end,
after_dig_node=function()
advtrains.invalidate_all_paths()
end
}
minetest.register_node(def.nodename_prefix.."_st", advtrains.merge_tables(common_def, make_overdef("st", 0, 4), def.straight or {}))
minetest.register_node(def.nodename_prefix.."_st_45", advtrains.merge_tables(common_def, make_overdef("st_45", 1, 5), def.straight45 or {}))
minetest.register_node(def.nodename_prefix.."_cr", advtrains.merge_tables(common_def, make_overdef("cr", 0, 3), def.curve or {}))
minetest.register_node(def.nodename_prefix.."_cr_45", advtrains.merge_tables(common_def, make_overdef("cr_45", 1, 4), def.curve45 or {}))
advtrains.trackplacer_register(def.nodename_prefix.."_st", 0, 4)
advtrains.trackplacer_register(def.nodename_prefix.."_st_45", 1, 5)
advtrains.trackplacer_register(def.nodename_prefix.."_cr", 0, 3)
advtrains.trackplacer_register(def.nodename_prefix.."_cr_45", 1, 4)
minetest.register_node(def.nodename_prefix.."_swl_st", advtrains.merge_tables(common_def, make_overdef("swl_st", 0, 4, make_switchfunc("swl_cr")), def.lswitchst or {}))
minetest.register_node(def.nodename_prefix.."_swl_st_45", advtrains.merge_tables(common_def, make_overdef("swl_st_45", 1, 5, make_switchfunc("swl_cr_45")), def.lswitchst45 or {}))
minetest.register_node(def.nodename_prefix.."_swl_cr", advtrains.merge_tables(common_def, make_overdef("swl_cr", 0, 3, make_switchfunc("swl_st")), def.lswitchcr or {}))
minetest.register_node(def.nodename_prefix.."_swl_cr_45", advtrains.merge_tables(common_def, make_overdef("swl_cr_45", 1, 4, make_switchfunc("swl_st_45")), def.lswitchcr45 or {}))
minetest.register_node(def.nodename_prefix.."_swr_st", advtrains.merge_tables(common_def, make_overdef("swr_st", 0, 4, make_switchfunc("swr_cr")), def.rswitchst or {}))
minetest.register_node(def.nodename_prefix.."_swr_st_45", advtrains.merge_tables(common_def, make_overdef("swr_st_45", 1, 5, make_switchfunc("swr_cr_45")), def.rswitchst45 or {}))
minetest.register_node(def.nodename_prefix.."_swr_cr", advtrains.merge_tables(common_def, make_overdef("swr_cr", 0, 5, make_switchfunc("swr_st")), def.rswitchcr or {}))
minetest.register_node(def.nodename_prefix.."_swr_cr_45", advtrains.merge_tables(common_def, make_overdef("swr_cr_45", 1, 6, make_switchfunc("swr_st_45")), def.rswitchcr45 or {}))
minetest.register_node(def.nodename_prefix.."_vert1", advtrains.merge_tables(common_def, make_overdef("vert1", 0, 4), {
mesh = "trackvertical1.b3d",
rely1=0,
rely2=0.5,
railheight=0.25,
}, def.straight or {}))
minetest.register_node(def.nodename_prefix.."_vert2", advtrains.merge_tables(common_def, make_overdef("vert2", 0, 4), {
mesh = "trackvertical2.b3d",
rely1=0.5,
rely2=1,
railheight=0.75,
},def.straight45 or {}))
advtrains.register_track_placer(def.nodename_prefix, def.texture_prefix, def.description)
table.insert(advtrains.all_tracktypes, tracktype)
end
advtrains.register_tracks("regular", {
nodename_prefix="advtrains:track",
texture_prefix="track",
description="Regular Train Track",
})
function advtrains.is_track_and_drives_on(nodename, drives_on)
if not minetest.registered_nodes[nodename] then
return false
end
local nodedef=minetest.registered_nodes[nodename]
for k,v in ipairs(drives_on) do
if nodedef.groups["advtrains_track_"..v] then
return true
end
end
return false
end
function advtrains.get_track_connections(name, param2)
local nodedef=minetest.registered_nodes[name]
if not nodedef then print("[advtrains] get_track_connections couldn't find nodedef for nodename "..(name or "nil")) return 0,4 end
local noderot=param2
if not param2 then noderot=0 end
if noderot > 3 then print("[advtrains] get_track_connections: rail has invaild param2 of "..noderot) noderot=0 end
return (nodedef.connect1 + 2 * noderot)%8, (nodedef.connect2 + 2 * noderot)%8, nodedef.rely1 or 0, nodedef.rely2 or 0, nodedef.railheight or 0
end

56
trainhud.lua Normal file
View File

@ -0,0 +1,56 @@
advtrains.hud = {}
minetest.register_on_leaveplayer(function(player)
advtrains.hud[player:get_player_name()] = nil
end)
function advtrains.set_trainhud(name, text)
local hud = advtrains.hud[name]
local player=minetest.get_player_by_name(name)
if not hud then
hud = {}
advtrains.hud[name] = hud
hud.id = player:hud_add({
hud_elem_type = "text",
name = "ADVTRAINS",
number = 0xFFFFFF,
position = {x=0.5, y=0.7},
offset = {x=0, y=0},
text = text,
scale = {x=200, y=60},
alignment = {x=0, y=0},
})
hud.oldText=text
return
elseif hud.oldText ~= text then
player:hud_change(hud.id, "text", text)
hud.oldText=text
end
end
function advtrains.hud_train_format(train, flip)
local fct=1
if flip then fct=-1 end
if not train then return "" end
local max=advtrains.all_traintypes[train.traintype].max_speed or 10
local vel=math.ceil(train.velocity)*fct
local tvel=math.ceil(train.tarvelocity)*fct
local firstLine, secondLine
if vel<0 then
firstLine="Speed: <"..string.rep("_", vel+max)..string.rep("+", -vel).."|"..string.rep("_", max)..">"
else
firstLine="Speed: <"..string.rep("_", max).."|"..string.rep("+", vel)..string.rep("_", max-vel)..">"
end
if tvel<0 then
secondLine="Target: <"..string.rep("_", tvel+max)..string.rep("+", -tvel).."|"..string.rep("_", max)..">"
else
secondLine="Target: <"..string.rep("_", max).."|"..string.rep("+", tvel)..string.rep("_", max-tvel)..">"
end
if vel==0 then
return firstLine.."\n"..secondLine.."\nup for forward, down for backward, use to get off train. "
elseif vel<0 then
return firstLine.."\n"..secondLine.."\nPress up to decelerate, down to accelerate, sneak to stop."
elseif vel>0 then
return firstLine.."\n"..secondLine.."\nPress up to accelerate, down to decelerate, sneak to stop."
end
end

54
trainhud.lua~ Normal file
View File

@ -0,0 +1,54 @@
advtrains.hud = {}
minetest.register_on_leaveplayer(function(player)
advtrains.hud[player:get_player_name()] = nil
end)
function advtrains.set_trainhud(name, text)
local hud = advtrains.hud[name]
local player=minetest.get_player_by_name(name)
if not hud then
hud = {}
advtrains.hud[name] = hud
hud.id = player:hud_add({
hud_elem_type = "text",
name = "ADVTRAINS",
number = 0xFFFFFF,
position = {x=0.5, y=0.7},
offset = {x=0, y=0},
text = text,
scale = {x=200, y=60},
alignment = {x=0, y=0},
})
hud.oldText=text
return
elseif hud.oldText ~= text then
player:hud_change(hud.id, "text", text)
hud.oldText=text
end
end
function advtrains.hud_train_format(train)
if not train then return "" end
local max=advtrains.all_traintypes[train.traintype].max_speed or 10
local vel=math.ceil(train.velocity)
local tvel=math.ceil(train.tarvelocity)
local firstLine, secondLine
if vel<0 then
firstLine="Speed: <"..string.rep("_", vel+max)..string.rep("+", -vel).."|"..string.rep("_", max)..">"
else
firstLine="Speed: <"..string.rep("_", max).."|"..string.rep("+", vel)..string.rep("_", max-vel)..">"
end
if tvel<0 then
secondLine="Target: <"..string.rep("_", tvel+max)..string.rep("+", -tvel).."|"..string.rep("_", max)..">"
else
secondLine="Target: <"..string.rep("_", max).."|"..string.rep("+", tvel)..string.rep("_", max-tvel)..">"
end
if vel==0 then
return firstLine.."\n"..secondLine.."\nup for forward, down for backward, use to get off train. "
elseif vel<0 then
return firstLine.."\n"..secondLine.."\nPress up to decelerate, down to accelerate, sneak to stop."
elseif vel>0 then
return firstLine.."\n"..secondLine.."\nPress up to accelerate, down to decelerate, sneak to stop."
end
end

543
trainlogic.lua Normal file
View File

@ -0,0 +1,543 @@
--trainlogic.lua
--controls train entities stuff about connecting/disconnecting/colliding trains and other things
local print=function(t) minetest.log("action", t) minetest.chat_send_all(t) end
advtrains.train_accel_force=5--per second and divided by number of wagons
advtrains.train_brake_force=3--per second, not divided by number of wagons
advtrains.train_emerg_force=10--for emergency brakes(when going off track)
advtrains.audit_interval=10
advtrains.all_traintypes={}
function advtrains.register_train_type(name, drives_on, max_speed)
advtrains.all_traintypes[name]={}
advtrains.all_traintypes[name].drives_on=drives_on
advtrains.all_traintypes[name].max_speed=max_speed or 10
end
advtrains.trains={}
--load initially
advtrains.fpath=minetest.get_worldpath().."/advtrains"
local file, err = io.open(advtrains.fpath, "r")
if not file then
local er=err or "Unknown Error"
print("[advtrains]Failed loading advtrains save file "..er)
else
local tbl = minetest.deserialize(file:read("*a"))
if type(tbl) == "table" then
advtrains.trains=tbl
end
file:close()
end
advtrains.save = function()
--print("[advtrains]saving")
advtrains.invalidate_all_paths()
local datastr = minetest.serialize(advtrains.trains)
if not datastr then
minetest.log("error", "[advtrains] Failed to serialize train data!")
return
end
local file, err = io.open(advtrains.fpath, "w")
if err then
return err
end
file:write(datastr)
file:close()
end
minetest.register_on_shutdown(advtrains.save)
advtrains.save_and_audit_timer=advtrains.audit_interval
minetest.register_globalstep(function(dtime)
advtrains.save_and_audit_timer=advtrains.save_and_audit_timer-dtime
if advtrains.save_and_audit_timer<=0 then
--print("[advtrains] audit step")
--clean up orphaned trains
for k,v in pairs(advtrains.trains) do
advtrains.update_trainpart_properties(k)
if #v.trainparts==0 then
advtrains.trains[k]=nil
end
end
--save
advtrains.save()
advtrains.save_and_audit_timer=advtrains.audit_interval
end
--regular train step
for k,v in pairs(advtrains.trains) do
advtrains.train_step(k, v, dtime)
end
end)
function advtrains.train_step(id, train, dtime)
--TODO check for all vars to be present
--if not train.last_pos then advtrains.trains[id]=nil return end
if not advtrains.pathpredict(id, train) then
--print("pathpredict failed(returned false)")
train.velocity=0
train.tarvelocity=0
return
end
local path=advtrains.get_or_create_path(id, train)
if not path then
train.velocity=0
train.tarvelocity=0
--print("train has no path")
return
end
--apply off-track handling:
local front_off_track=train.max_index_on_track and train.index>train.max_index_on_track
local back_off_track=train.min_index_on_track and (train.index-train.trainlen)<train.min_index_on_track
if front_off_track and back_off_track then--allow movement in both directions
if train.tarvelocity>1 then train.tarvelocity=1 end
if train.tarvelocity<-1 then train.tarvelocity=-1 end
elseif front_off_track then--allow movement only backward
if train.tarvelocity>0 then train.tarvelocity=0 end
if train.tarvelocity<-1 then train.tarvelocity=-1 end
elseif back_off_track then--allow movement only forward
if train.tarvelocity>1 then train.tarvelocity=1 end
if train.tarvelocity<0 then train.tarvelocity=0 end
end
--move
if not train.velocity then
train.velocity=0
end
train.index=train.index and train.index+((train.velocity/(train.path_dist[math.floor(train.index)] or 1))*dtime) or 0
--check for collisions by finding objects
--front
local search_radius=4
local posfront=path[math.floor(train.index+1)]
if posfront then
local objrefs=minetest.get_objects_inside_radius(posfront, search_radius)
for _,v in pairs(objrefs) do
local le=v:get_luaentity()
if le and le.is_wagon and le.initialized and le.train_id~=id then
advtrains.try_connect_trains(id, le.train_id)
end
end
end
local posback=path[math.floor(train.index-(train.trainlen or 0)-1)]
if posback then
local objrefs=minetest.get_objects_inside_radius(posback, search_radius)
for _,v in pairs(objrefs) do
local le=v:get_luaentity()
if le and le.is_wagon and le.initialized and le.train_id~=id then
advtrains.try_connect_trains(id, le.train_id)
end
end
end
--handle collided_with_env
if train.recently_collided_with_env then
train.tarvelocity=0
if train.velocity==0 then
train.recently_collided_with_env=false--reset status when stopped
end
end
if train.locomotives_in_train==0 then
train.tarvelocity=0
end
--apply tarvel(but with physics in mind!)
if train.velocity~=train.tarvelocity then
local applydiff=0
local mass=#train.trainparts
local diff=math.abs(train.tarvelocity)-math.abs(train.velocity)
if diff>0 then--accelerating, force will be brought on only by locomotives.
--print("accelerating with default force")
applydiff=(math.min((advtrains.train_accel_force*train.locomotives_in_train*dtime)/mass, math.abs(diff)))
else--decelerating
if front_off_track or back_off_track or train.recently_collided_with_env then --every wagon has a brake, so not divided by mass.
--print("braking with emergency force")
applydiff=(math.min((advtrains.train_emerg_force*dtime), math.abs(diff)))
else
--print("braking with default force")
applydiff=(math.min((advtrains.train_brake_force*dtime), math.abs(diff)))
end
end
train.velocity=train.velocity+(applydiff*math.sign(train.tarvelocity-train.velocity))
end
end
--the 'leader' concept has been overthrown, we won't rely on MT's "buggy object management"
--structure of train table:
--[[
trains={
[train_id]={
trainparts={
[n]=wagon_id
}
path={path}
velocity
tarvelocity
index
trainlen
path_inv_level
last_pos |
last_dir | for pathpredicting.
no_connect_for_movements (index way counter for when not to connect again) TODO implement
}
}
--a wagon itself has the following properties:
wagon={
unique_id
train_id
pos_in_train (is index difference, including train_span stuff)
pos_in_trainparts (is index in trainparts tabel of trains)
}
inherited by metatable:
wagon_proto={
wagon_span
}
]]
--returns new id
function advtrains.create_new_train_at(pos, pos_prev, traintype)
local newtrain_id=os.time()..os.clock()
while advtrains.trains[newtrain_id] do newtrain_id=os.time()..os.clock() end--ensure uniqueness(will be unneccessary)
advtrains.trains[newtrain_id]={}
advtrains.trains[newtrain_id].last_pos=pos
advtrains.trains[newtrain_id].last_pos_prev=pos_prev
advtrains.trains[newtrain_id].traintype=traintype
advtrains.trains[newtrain_id].tarvelocity=0
advtrains.trains[newtrain_id].velocity=0
advtrains.trains[newtrain_id].trainparts={}
return newtrain_id
end
--returns false on failure. handle this case!
function advtrains.pathpredict(id, train)
--print("pos ",x,y,z)
--::rerun::
if not train.index then train.index=0 end
if not train.path or #train.path<2 then
if not train.last_pos then
--no chance to recover
print("[advtrains]train hasn't saved last-pos, removing train.")
advtrains.train[id]=nil
return false
end
local node=minetest.get_node_or_nil(advtrains.round_vector_floor_y(train.last_pos))
if not node then
--print("pathpredict:last_pos node "..minetest.pos_to_string(advtrains.round_vector_floor_y(train.last_pos)).." is nil. block probably not loaded")
return nil
end
local nodename=node.name
if(not advtrains.is_track_and_drives_on(nodename, advtrains.all_traintypes[train.traintype].drives_on)) then
advtrains.dumppath(train.path)
print("at index "..train.index)
print("[advtrains]no track here, (fail) removing train.")
advtrains.trains[id]=nil
return false
end
if not train.last_pos_prev then
--no chance to recover
print("[advtrains]train hasn't saved last-pos_prev, removing train.")
advtrains.trains[id]=nil
return false
end
local prevnode=minetest.get_node_or_nil(advtrains.round_vector_floor_y(train.last_pos_prev))
if not prevnode then
--print("pathpredict:last_pos_prev node "..minetest.pos_to_string(advtrains.round_vector_floor_y(train.last_pos_prev)).." is nil. block probably not loaded")
return nil
end
local prevnodename=prevnode.name
if(not advtrains.is_track_and_drives_on(prevnodename, advtrains.all_traintypes[train.traintype].drives_on)) then
print("[advtrains]no track at prev, (fail) removing train.")
advtrains.trains[id]=nil
return false
end
local conn1, conn2=advtrains.get_track_connections(nodename, node.param2)
train.index=(train.restore_add_index or 0)+(train.savedpos_off_track_index_offset or 0)
--restore_add_index is set by save() to prevent trains hopping to next round index. should be between -0.5 and 0.5
--savedpos_off_track_index_offset is set if train went off track. see below.
train.path={}
train.path_dist={}
train.path[0]=train.last_pos
train.path[-1]=train.last_pos_prev
train.path_dist[-1]=vector.distance(train.last_pos, train.last_pos_prev)
end
local maxn=advtrains.maxN(train.path)
while (maxn-train.index) < 2 do--pregenerate
--print("[advtrains]maxn conway for ",maxn,minetest.pos_to_string(path[maxn]),maxn-1,minetest.pos_to_string(path[maxn-1]))
local conway=advtrains.conway(train.path[maxn], train.path[maxn-1], advtrains.all_traintypes[train.traintype].drives_on)
if conway then
train.path[maxn+1]=conway
train.max_index_on_track=maxn
else
--do as if nothing has happened and preceed with path
--but do not update max_index_on_track
print("over-generating path max to index "..maxn+1)
train.path[maxn+1]=vector.add(train.path[maxn], vector.subtract(train.path[maxn], train.path[maxn-1]))
end
train.path_dist[maxn]=vector.distance(train.path[maxn+1], train.path[maxn])
maxn=advtrains.maxN(train.path)
end
local minn=advtrains.minN(train.path)
while (train.index-minn) < (train.trainlen or 0) + 2 do --post_generate. has to be at least trainlen.
--print("[advtrains]minn conway for ",minn,minetest.pos_to_string(path[minn]),minn+1,minetest.pos_to_string(path[minn+1]))
local conway=advtrains.conway(train.path[minn], train.path[minn+1], advtrains.all_traintypes[train.traintype].drives_on)
if conway then
train.path[minn-1]=conway
train.min_index_on_track=minn
else
--do as if nothing has happened and preceed with path
--but do not update min_index_on_track
print("over-generating path min to index "..minn-1)
train.path[minn-1]=vector.add(train.path[minn], vector.subtract(train.path[minn], train.path[minn+1]))
end
train.path_dist[minn-1]=vector.distance(train.path[minn], train.path[minn-1])
minn=advtrains.minN(train.path)
end
if not train.min_index_on_track then train.min_index_on_track=0 end
if not train.max_index_on_track then train.max_index_on_track=0 end
--make pos/yaw available for possible recover calls
if train.max_index_on_track<train.index then --whoops, train went too far. the saved position will be the last one that lies on a track, and savedpos_off_track_index_offset will hold how far to go from here
train.savedpos_off_track_index_offset=train.index-train.max_index_on_track
train.last_pos=train.path[train.max_index_on_track]
train.last_pos_prev=train.path[train.max_index_on_track-1]
--print("train is off-track (front), last positions kept at "..minetest.pos_to_string(train.last_pos).." / "..minetest.pos_to_string(train.last_pos_prev))
elseif train.min_index_on_track+1>train.index then --whoops, train went even more far. same behavior
train.savedpos_off_track_index_offset=train.index-train.min_index_on_track
train.last_pos=train.path[train.min_index_on_track+1]
train.last_pos_prev=train.path[train.min_index_on_track]
--print("train is off-track (back), last positions kept at "..minetest.pos_to_string(train.last_pos).." / "..minetest.pos_to_string(train.last_pos_prev))
else --regular case
train.savedpos_off_track_index_offset=nil
train.last_pos=train.path[math.floor(train.index+0.5)]
train.last_pos_prev=train.path[math.floor(train.index-0.5)]
end
return train.path
end
function advtrains.get_or_create_path(id, train)
if not train.path then return advtrains.pathpredict(id, train) end
return train.path
end
function advtrains.add_wagon_to_train(wagon, train_id, index)
local train=advtrains.trains[train_id]
if index then
table.insert(train.trainparts, index, wagon.unique_id)
else
table.insert(train.trainparts, wagon.unique_id)
end
--this is not the usual case!!!
--we may set initialized because the wagon has no chance to step()
wagon.initialized=true
advtrains.update_trainpart_properties(train_id)
end
function advtrains.update_trainpart_properties(train_id, invert_flipstate)
local train=advtrains.trains[train_id]
local rel_pos=0
local count_l=0
for i, w_id in ipairs(train.trainparts) do
for _,wagon in pairs(minetest.luaentities) do
if wagon.is_wagon and wagon.initialized and wagon.unique_id==w_id then
rel_pos=rel_pos+wagon.wagon_span
wagon.train_id=train_id
wagon.pos_in_train=rel_pos
wagon.pos_in_trainparts=i
wagon.old_velocity_vector=nil
if wagon.is_locomotive then
count_l=count_l+1
end
if invert_flipstate then
wagon.wagon_flipped = not wagon.wagon_flipped
end
rel_pos=rel_pos+wagon.wagon_span
end
end
end
train.trainlen=rel_pos
train.locomotives_in_train=count_l
end
function advtrains.split_train_at_wagon(wagon)
--get train
local train=advtrains.trains[wagon.train_id]
local pos_for_new_train=advtrains.get_or_create_path(wagon.train_id, train)[math.floor((train.index or 0)-wagon.pos_in_train-0.5)]
local pos_for_new_train_prev=advtrains.get_or_create_path(wagon.train_id, train)[math.floor((train.index or 0)-wagon.pos_in_train-1.5)]
--before doing anything, check if both are rails. else do not allow
if not pos_for_new_train then
print("split_train: pos_for_new_train not set")
return false
end
local node=minetest.get_node_or_nil(advtrains.round_vector_floor_y(pos_for_new_train))
if not node then
print("split_train: pos_for_new_train node "..minetest.pos_to_string(advtrains.round_vector_floor_y(pos_for_new_train)).." is nil. block probably not loaded")
return nil
end
local nodename=node.name
if(not advtrains.is_track_and_drives_on(nodename, advtrains.all_traintypes[train.traintype].drives_on)) then
print("split_train: pos_for_new_train "..minetest.pos_to_string(advtrains.round_vector_floor_y(pos_for_new_train_prev)).." is not a rail")
return false
end
if not train.last_pos_prev then
print("split_train: pos_for_new_train_prev not set")
return false
end
local prevnode=minetest.get_node_or_nil(advtrains.round_vector_floor_y(pos_for_new_train_prev))
if not node then
print("split_train: pos_for_new_train_prev node "..minetest.pos_to_string(advtrains.round_vector_floor_y(pos_for_new_train_prev)).." is nil. block probably not loaded")
return false
end
local prevnodename=prevnode.name
if(not advtrains.is_track_and_drives_on(prevnodename, advtrains.all_traintypes[train.traintype].drives_on)) then
print("split_train: pos_for_new_train_prev "..minetest.pos_to_string(advtrains.round_vector_floor_y(pos_for_new_train_prev)).." is not a rail")
return false
end
--create subtrain
local newtrain_id=advtrains.create_new_train_at(pos_for_new_train, pos_for_new_train_prev, train.traintype)
local newtrain=advtrains.trains[newtrain_id]
--insert all wagons to new train
for k,v in ipairs(train.trainparts) do
if k>=wagon.pos_in_trainparts then
table.insert(newtrain.trainparts, v)
train.trainparts[k]=nil
end
end
--update train parts
advtrains.update_trainpart_properties(wagon.train_id)--atm it still is the desierd id.
advtrains.update_trainpart_properties(newtrain_id)
train.tarvelocity=0
newtrain.velocity=train.velocity
newtrain.tarvelocity=0
end
--there are 4 cases:
--1/2. F<->R F<->R regular, put second train behind first
--->frontpos of first train will match backpos of second
--3. F<->R R<->F flip one of these trains, take the other as new train
--->backpos's will match
--4. R<->F F<->R flip one of these trains and take it as new parent
--->frontpos's will match
function advtrains.try_connect_trains(id1, id2)
local train1=advtrains.trains[id1]
local train2=advtrains.trains[id2]
if not train1 or not train2 then return end
if not train1.path or not train2.path then return end
if train1.traintype~=train2.traintype then
--TODO implement collision without connection
return
end
if #train1.trainparts==0 or #train2.trainparts==0 then return end
local frontpos1=train1.path[math.floor(train1.index+0.5)]
local backpos1=train1.path[math.floor(train1.index-(train1.trainlen or 2)+0.5)]
local frontpos2=train2.path[math.floor(train2.index+0.5)]
local backpos2=train2.path[math.floor(train2.index-(train1.trainlen or 2)+0.5)]
if not frontpos1 or not frontpos2 or not backpos1 or not backpos2 then return end
--case 1 (first train is front)
if vector.equals(frontpos2, backpos1) then
advtrains.do_connect_trains(id1, id2)
--case 2 (second train is front)
elseif vector.equals(frontpos1, backpos2) then
advtrains.do_connect_trains(id2, id1)
--case 3
elseif vector.equals(backpos2, backpos1) then
advtrains.invert_train(id2)
advtrains.do_connect_trains(id1, id2)
--case 4
elseif vector.equals(frontpos2, frontpos1) then
advtrains.invert_train(id1)
advtrains.do_connect_trains(id1, id2)
end
end
function advtrains.do_connect_trains(first_id, second_id)
local first_wagoncnt=#advtrains.trains[first_id].trainparts
local second_wagoncnt=#advtrains.trains[second_id].trainparts
for _,v in ipairs(advtrains.trains[second_id].trainparts) do
table.insert(advtrains.trains[first_id].trainparts, v)
end
--kick it like physics (with mass being #wagons)
local new_velocity=((advtrains.trains[first_id].velocity*first_wagoncnt)+(advtrains.trains[second_id].velocity*second_wagoncnt))/(first_wagoncnt+second_wagoncnt)
advtrains.trains[second_id]=nil
advtrains.update_trainpart_properties(first_id)
advtrains.trains[first_id].velocity=new_velocity
advtrains.trains[first_id].tarvelocity=0
end
function advtrains.invert_train(train_id)
local train=advtrains.trains[train_id]
local old_path=advtrains.get_or_create_path(train_id, train)
train.path={}
train.index=-train.index+train.trainlen
train.velocity=-train.velocity
train.tarvelocity=-train.tarvelocity
for k,v in pairs(old_path) do
train.path[-k]=v
end
local old_trainparts=train.trainparts
train.trainparts={}
for k,v in ipairs(old_trainparts) do
table.insert(train.trainparts, 1, v)--notice insertion at first place
end
advtrains.update_trainpart_properties(train_id, true)
end
function advtrains.is_train_at_pos(pos)
--print("istrainat: pos "..minetest.pos_to_string(pos))
local checked_trains={}
local objrefs=minetest.get_objects_inside_radius(pos, 2)
for _,v in pairs(objrefs) do
local le=v:get_luaentity()
if le and le.is_wagon and le.initialized and le.train_id and not checked_trains[le.train_id] then
--print("istrainat: checking "..le.train_id)
checked_trains[le.train_id]=true
local path=advtrains.get_or_create_path(le.train_id, le:train())
if path then
--print("has path")
for i=math.floor(le:train().index-le:train().trainlen+0.5),math.floor(le:train().index+0.5) do
if path[i] then
--print("has pathitem "..i.." "..minetest.pos_to_string(path[i]))
if vector.equals(advtrains.round_vector_floor_y(path[i]), pos) then
return true
end
end
end
end
end
end
return false
end
function advtrains.invalidate_all_paths()
--print("invalidating all paths")
for k,v in pairs(advtrains.trains) do
if v.index then
v.restore_add_index=v.index-math.floor(v.index+0.5)
end
v.path=nil
v.index=nil
v.min_index_on_track=nil
v.max_index_on_track=nil
end
end

542
trainlogic.lua~ Normal file
View File

@ -0,0 +1,542 @@
--trainlogic.lua
--controls train entities stuff about connecting/disconnecting/colliding trains and other things
local print=function(t) minetest.log("action", t) minetest.chat_send_all(t) end
advtrains.train_accel_force=5--per second and divided by number of wagons
advtrains.train_brake_force=3--per second, not divided by number of wagons
advtrains.train_emerg_force=10--for emergency brakes(when going off track)
advtrains.audit_interval=10
advtrains.all_traintypes={}
function advtrains.register_train_type(name, drives_on, max_speed)
advtrains.all_traintypes[name]={}
advtrains.all_traintypes[name].drives_on=drives_on
advtrains.all_traintypes[name].max_speed=max_speed or 10
end
advtrains.trains={}
--load initially
advtrains.fpath=minetest.get_worldpath().."/advtrains"
local file, err = io.open(advtrains.fpath, "r")
if not file then
local er=err or "Unknown Error"
print("[advtrains]Failed loading advtrains save file "..er)
else
local tbl = minetest.deserialize(file:read("*a"))
if type(tbl) == "table" then
advtrains.trains=tbl
end
file:close()
end
advtrains.save = function()
--print("[advtrains]saving")
advtrains.invalidate_all_paths()
local datastr = minetest.serialize(advtrains.trains)
if not datastr then
minetest.log("error", "[advtrains] Failed to serialize train data!")
return
end
local file, err = io.open(advtrains.fpath, "w")
if err then
return err
end
file:write(datastr)
file:close()
end
minetest.register_on_shutdown(advtrains.save)
advtrains.save_and_audit_timer=advtrains.audit_interval
minetest.register_globalstep(function(dtime)
advtrains.save_and_audit_timer=advtrains.save_and_audit_timer-dtime
if advtrains.save_and_audit_timer<=0 then
--print("[advtrains] audit step")
--clean up orphaned trains
for k,v in pairs(advtrains.trains) do
advtrains.update_trainpart_properties(k)
if #v.trainparts==0 then
advtrains.trains[k]=nil
end
end
--save
advtrains.save()
advtrains.save_and_audit_timer=advtrains.audit_interval
end
--regular train step
for k,v in pairs(advtrains.trains) do
advtrains.train_step(k, v, dtime)
end
end)
function advtrains.train_step(id, train, dtime)
--TODO check for all vars to be present
--if not train.last_pos then advtrains.trains[id]=nil return end
if not advtrains.pathpredict(id, train) then
--print("pathpredict failed(returned false)")
train.velocity=0
train.tarvelocity=0
return
end
local path=advtrains.get_or_create_path(id, train)
if not path then
train.velocity=0
train.tarvelocity=0
--print("train has no path")
return
end
--apply off-track handling:
local front_off_track=train.max_index_on_track and train.index>train.max_index_on_track
local back_off_track=train.min_index_on_track and (train.index-train.trainlen)<train.min_index_on_track
if front_off_track and back_off_track then--allow movement in both directions
if train.tarvelocity>1 then train.tarvelocity=1 end
if train.tarvelocity<-1 then train.tarvelocity=-1 end
elseif front_off_track then--allow movement only backward
if train.tarvelocity>0 then train.tarvelocity=0 end
if train.tarvelocity<-1 then train.tarvelocity=-1 end
elseif back_off_track then--allow movement only forward
if train.tarvelocity>1 then train.tarvelocity=1 end
if train.tarvelocity<0 then train.tarvelocity=0 end
end
--move
if not train.velocity then
train.velocity=0
end
train.index=train.index and train.index+((train.velocity/(train.path_dist[math.floor(train.index)] or 1))*dtime) or 0
--check for collisions by finding objects
--front
local search_radius=4
local posfront=path[math.floor(train.index+1)]
if posfront then
local objrefs=minetest.get_objects_inside_radius(posfront, search_radius)
for _,v in pairs(objrefs) do
local le=v:get_luaentity()
if le and le.is_wagon and le.initialized and le.train_id~=id then
advtrains.try_connect_trains(id, le.train_id)
end
end
end
local posback=path[math.floor(train.index-(train.trainlen or 0)-1)]
if posback then
local objrefs=minetest.get_objects_inside_radius(posback, search_radius)
for _,v in pairs(objrefs) do
local le=v:get_luaentity()
if le and le.is_wagon and le.initialized and le.train_id~=id then
advtrains.try_connect_trains(id, le.train_id)
end
end
end
--handle collided_with_env
if train.recently_collided_with_env then
train.tarvelocity=0
if train.velocity==0 then
train.recently_collided_with_env=false--reset status when stopped
end
end
if train.locomotives_in_train==0 then
train.tarvelocity=0
end
--apply tarvel(but with physics in mind!)
if train.velocity~=train.tarvelocity then
local applydiff=0
local mass=#train.trainparts
local diff=math.abs(train.tarvelocity)-math.abs(train.velocity)
if diff>0 then--accelerating, force will be brought on only by locomotives.
--print("accelerating with default force")
applydiff=(math.min((advtrains.train_accel_force*train.locomotives_in_train*dtime)/mass, math.abs(diff)))
else--decelerating
if front_off_track or back_off_track or train.recently_collided_with_env then --every wagon has a brake, so not divided by mass.
--print("braking with emergency force")
applydiff=(math.min((advtrains.train_emerg_force*dtime), math.abs(diff)))
else
--print("braking with default force")
applydiff=(math.min((advtrains.train_brake_force*dtime), math.abs(diff)))
end
end
train.velocity=train.velocity+(applydiff*math.sign(train.tarvelocity-train.velocity))
end
end
--the 'leader' concept has been overthrown, we won't rely on MT's "buggy object management"
--structure of train table:
--[[
trains={
[train_id]={
trainparts={
[n]=wagon_id
}
path={path}
velocity
tarvelocity
index
trainlen
path_inv_level
last_pos |
last_dir | for pathpredicting.
no_connect_for_movements (index way counter for when not to connect again) TODO implement
}
}
--a wagon itself has the following properties:
wagon={
unique_id
train_id
pos_in_train (is index difference, including train_span stuff)
pos_in_trainparts (is index in trainparts tabel of trains)
}
inherited by metatable:
wagon_proto={
wagon_span
}
]]
--returns new id
function advtrains.create_new_train_at(pos, pos_prev, traintype)
local newtrain_id=os.time()..os.clock()
while advtrains.trains[newtrain_id] do newtrain_id=os.time()..os.clock() end--ensure uniqueness(will be unneccessary)
advtrains.trains[newtrain_id]={}
advtrains.trains[newtrain_id].last_pos=pos
advtrains.trains[newtrain_id].last_pos_prev=pos_prev
advtrains.trains[newtrain_id].traintype=traintype
advtrains.trains[newtrain_id].tarvelocity=0
advtrains.trains[newtrain_id].velocity=0
advtrains.trains[newtrain_id].trainparts={}
return newtrain_id
end
--returns false on failure. handle this case!
function advtrains.pathpredict(id, train)
--print("pos ",x,y,z)
--::rerun::
if not train.index then train.index=0 end
if not train.path or #train.path<2 then
if not train.last_pos then
--no chance to recover
print("[advtrains]train hasn't saved last-pos, removing train.")
advtrains.train[id]=nil
return false
end
local node=minetest.get_node_or_nil(advtrains.round_vector_floor_y(train.last_pos))
if not node then
--print("pathpredict:last_pos node "..minetest.pos_to_string(advtrains.round_vector_floor_y(train.last_pos)).." is nil. block probably not loaded")
return nil
end
local nodename=node.name
if(not advtrains.is_track_and_drives_on(nodename, advtrains.all_traintypes[train.traintype].drives_on)) then
advtrains.dumppath(train.path)
print("at index "..train.index)
print("[advtrains]no track here, (fail) removing train.")
advtrains.trains[id]=nil
return false
end
if not train.last_pos_prev then
--no chance to recover
print("[advtrains]train hasn't saved last-pos_prev, removing train.")
advtrains.trains[id]=nil
return false
end
local prevnode=minetest.get_node_or_nil(advtrains.round_vector_floor_y(train.last_pos_prev))
if not prevnode then
--print("pathpredict:last_pos_prev node "..minetest.pos_to_string(advtrains.round_vector_floor_y(train.last_pos_prev)).." is nil. block probably not loaded")
return nil
end
local prevnodename=prevnode.name
if(not advtrains.is_track_and_drives_on(prevnodename, advtrains.all_traintypes[train.traintype].drives_on)) then
print("[advtrains]no track at prev, (fail) removing train.")
advtrains.trains[id]=nil
return false
end
local conn1, conn2=advtrains.get_track_connections(nodename, node.param2)
train.index=(train.restore_add_index or 0)+(train.savedpos_off_track_index_offset or 0)
--restore_add_index is set by save() to prevent trains hopping to next round index. should be between -0.5 and 0.5
--savedpos_off_track_index_offset is set if train went off track. see below.
train.path={}
train.path_dist={}
train.path[0]=train.last_pos
train.path[-1]=train.last_pos_prev
train.path_dist[-1]=vector.distance(train.last_pos, train.last_pos_prev)
end
local maxn=advtrains.maxN(train.path)
while (maxn-train.index) < 2 do--pregenerate
--print("[advtrains]maxn conway for ",maxn,minetest.pos_to_string(path[maxn]),maxn-1,minetest.pos_to_string(path[maxn-1]))
local conway=advtrains.conway(train.path[maxn], train.path[maxn-1], advtrains.all_traintypes[train.traintype].drives_on)
if conway then
train.path[maxn+1]=conway
train.max_index_on_track=maxn
else
--do as if nothing has happened and preceed with path
--but do not update max_index_on_track
print("over-generating path max to index "..maxn+1)
train.path[maxn+1]=vector.add(train.path[maxn], vector.subtract(train.path[maxn], train.path[maxn-1]))
end
train.path_dist[maxn]=vector.distance(train.path[maxn+1], train.path[maxn])
maxn=advtrains.maxN(train.path)
end
local minn=advtrains.minN(train.path)
while (train.index-minn) < (train.trainlen or 0) + 2 do --post_generate. has to be at least trainlen.
--print("[advtrains]minn conway for ",minn,minetest.pos_to_string(path[minn]),minn+1,minetest.pos_to_string(path[minn+1]))
local conway=advtrains.conway(train.path[minn], train.path[minn+1], advtrains.all_traintypes[train.traintype].drives_on)
if conway then
train.path[minn-1]=conway
train.min_index_on_track=minn
else
--do as if nothing has happened and preceed with path
--but do not update min_index_on_track
print("over-generating path min to index "..minn-1)
train.path[minn-1]=vector.add(train.path[minn], vector.subtract(train.path[minn], train.path[minn+1]))
end
train.path_dist[minn-1]=vector.distance(train.path[minn], train.path[minn-1])
minn=advtrains.minN(train.path)
end
if not train.min_index_on_track then train.min_index_on_track=0 end
if not train.max_index_on_track then train.max_index_on_track=0 end
--make pos/yaw available for possible recover calls
if train.max_index_on_track<train.index then --whoops, train went too far. the saved position will be the last one that lies on a track, and savedpos_off_track_index_offset will hold how far to go from here
train.savedpos_off_track_index_offset=train.index-train.max_index_on_track
train.last_pos=train.path[train.max_index_on_track]
train.last_pos_prev=train.path[train.max_index_on_track-1]
--print("train is off-track (front), last positions kept at "..minetest.pos_to_string(train.last_pos).." / "..minetest.pos_to_string(train.last_pos_prev))
elseif train.min_index_on_track+1>train.index then --whoops, train went even more far. same behavior
train.savedpos_off_track_index_offset=train.index-train.min_index_on_track
train.last_pos=train.path[train.min_index_on_track+1]
train.last_pos_prev=train.path[train.min_index_on_track]
--print("train is off-track (back), last positions kept at "..minetest.pos_to_string(train.last_pos).." / "..minetest.pos_to_string(train.last_pos_prev))
else --regular case
train.savedpos_off_track_index_offset=nil
train.last_pos=train.path[math.floor(train.index+0.5)]
train.last_pos_prev=train.path[math.floor(train.index-0.5)]
end
return train.path
end
function advtrains.get_or_create_path(id, train)
if not train.path then return advtrains.pathpredict(id, train) end
return train.path
end
function advtrains.add_wagon_to_train(wagon, train_id, index)
local train=advtrains.trains[train_id]
if index then
table.insert(train.trainparts, index, wagon.unique_id)
else
table.insert(train.trainparts, wagon.unique_id)
end
--this is not the usual case!!!
--we may set initialized because the wagon has no chance to step()
wagon.initialized=true
advtrains.update_trainpart_properties(train_id)
end
function advtrains.update_trainpart_properties(train_id, invert_flipstate)
local train=advtrains.trains[train_id]
local rel_pos=0
local count_l=0
for i, w_id in ipairs(train.trainparts) do
for _,wagon in pairs(minetest.luaentities) do
if wagon.is_wagon and wagon.initialized and wagon.unique_id==w_id then
rel_pos=rel_pos+wagon.wagon_span
wagon.train_id=train_id
wagon.pos_in_train=rel_pos
wagon.pos_in_trainparts=i
wagon.old_velocity_vector=nil
if wagon.is_locomotive then
count_l=count_l+1
end
if invert_flipstate then
wagon.wagon_flipped = not wagon.wagon_flipped
end
rel_pos=rel_pos+wagon.wagon_span
end
end
end
train.trainlen=rel_pos
train.locomotives_in_train=count_l
end
function advtrains.split_train_at_wagon(wagon)
--get train
local train=advtrains.trains[wagon.train_id]
local pos_for_new_train=advtrains.get_or_create_path(wagon.train_id, train)[math.floor((train.index or 0)-wagon.pos_in_train-0.5)]
local pos_for_new_train_prev=advtrains.get_or_create_path(wagon.train_id, train)[math.floor((train.index or 0)-wagon.pos_in_train-1.5)]
--before doing anything, check if both are rails. else do not allow
if not pos_for_new_train then
print("split_train: pos_for_new_train not set")
return false
end
local node=minetest.get_node_or_nil(advtrains.round_vector_floor_y(pos_for_new_train))
if not node then
print("split_train: pos_for_new_train node "..minetest.pos_to_string(advtrains.round_vector_floor_y(pos_for_new_train)).." is nil. block probably not loaded")
return nil
end
local nodename=node.name
if(not advtrains.is_track_and_drives_on(nodename, advtrains.all_traintypes[train.traintype].drives_on)) then
print("split_train: pos_for_new_train "..minetest.pos_to_string(advtrains.round_vector_floor_y(pos_for_new_train_prev)).." is not a rail")
return false
end
if not train.last_pos_prev then
print("split_train: pos_for_new_train_prev not set")
return false
end
local prevnode=minetest.get_node_or_nil(advtrains.round_vector_floor_y(pos_for_new_train_prev))
if not node then
print("split_train: pos_for_new_train_prev node "..minetest.pos_to_string(advtrains.round_vector_floor_y(pos_for_new_train_prev)).." is nil. block probably not loaded")
return false
end
local prevnodename=prevnode.name
if(not advtrains.is_track_and_drives_on(prevnodename, advtrains.all_traintypes[train.traintype].drives_on)) then
print("split_train: pos_for_new_train_prev "..minetest.pos_to_string(advtrains.round_vector_floor_y(pos_for_new_train_prev)).." is not a rail")
return false
end
--create subtrain
local newtrain_id=advtrains.create_new_train_at(pos_for_new_train, pos_for_new_train_prev, train.traintype)
local newtrain=advtrains.trains[newtrain_id]
--insert all wagons to new train
for k,v in ipairs(train.trainparts) do
if k>=wagon.pos_in_trainparts then
table.insert(newtrain.trainparts, v)
train.trainparts[k]=nil
end
end
--update train parts
advtrains.update_trainpart_properties(wagon.train_id)--atm it still is the desierd id.
advtrains.update_trainpart_properties(newtrain_id)
train.tarvelocity=0
newtrain.velocity=train.velocity
newtrain.tarvelocity=0
end
--there are 4 cases:
--1/2. F<->R F<->R regular, put second train behind first
--->frontpos of first train will match backpos of second
--3. F<->R R<->F flip one of these trains, take the other as new train
--->backpos's will match
--4. R<->F F<->R flip one of these trains and take it as new parent
--->frontpos's will match
function advtrains.try_connect_trains(id1, id2)
local train1=advtrains.trains[id1]
local train2=advtrains.trains[id2]
if not train1 or not train2 then return end
if not train1.path or not train2.path then return end
if train1.traintype~=train2.traintype then
--TODO implement collision without connection
return
end
if #train1.trainparts==0 or #train2.trainparts==0 then return end
local frontpos1=train1.path[math.floor(train1.index+0.5)]
local backpos1=train1.path[math.floor(train1.index-(train1.trainlen or 2)+0.5)]
local frontpos2=train2.path[math.floor(train2.index+0.5)]
local backpos2=train2.path[math.floor(train2.index-(train1.trainlen or 2)+0.5)]
if not frontpos1 or not frontpos2 or not backpos1 or not backpos2 then return end
--case 1 (first train is front)
if vector.equals(frontpos2, backpos1) then
advtrains.do_connect_trains(id1, id2)
--case 2 (second train is front)
elseif vector.equals(frontpos1, backpos2) then
advtrains.do_connect_trains(id2, id1)
--case 3
elseif vector.equals(backpos2, backpos1) then
advtrains.invert_train(id2)
advtrains.do_connect_trains(id1, id2)
--case 4
elseif vector.equals(frontpos2, frontpos1) then
advtrains.invert_train(id1)
advtrains.do_connect_trains(id1, id2)
end
end
function advtrains.do_connect_trains(first_id, second_id)
local first_wagoncnt=#advtrains.trains[first_id].trainparts
local second_wagoncnt=#advtrains.trains[second_id].trainparts
for _,v in ipairs(advtrains.trains[second_id].trainparts) do
table.insert(advtrains.trains[first_id].trainparts, v)
end
--kick it like physics (with mass being #wagons)
local new_velocity=((advtrains.trains[first_id].velocity*first_wagoncnt)+(advtrains.trains[second_id].velocity*second_wagoncnt))/(first_wagoncnt+second_wagoncnt)
advtrains.trains[second_id]=nil
advtrains.update_trainpart_properties(first_id)
advtrains.trains[first_id].velocity=new_velocity
advtrains.trains[first_id].tarvelocity=0
end
function advtrains.invert_train(train_id)
local train=advtrains.trains[train_id]
local old_path=advtrains.get_or_create_path(train_id, train)
train.path={}
train.index=-train.index+train.trainlen
train.velocity=-train.velocity
train.tarvelocity=-train.tarvelocity
for k,v in pairs(old_path) do
train.path[-k]=v
end
local old_trainparts=train.trainparts
train.trainparts={}
for k,v in ipairs(old_trainparts) do
table.insert(train.trainparts, 1, v)--notice insertion at first place
end
advtrains.update_trainpart_properties(train_id, true)
end
function advtrains.is_train_at_pos(pos)
--print("istrainat: pos "..minetest.pos_to_string(pos))
local checked_trains={}
local objrefs=minetest.get_objects_inside_radius(pos, 2)
for _,v in pairs(objrefs) do
local le=v:get_luaentity()
if le and le.is_wagon and le.initialized and le.train_id and not checked_trains[le.train_id] then
--print("istrainat: checking "..le.train_id)
checked_trains[le.train_id]=true
local path=advtrains.get_or_create_path(le.train_id, le:train())
if path then
--print("has path")
for i=math.floor(le:train().index-le:train().trainlen+0.5),math.floor(le:train().index+0.5) do
if path[i] then
--print("has pathitem "..i.." "..minetest.pos_to_string(path[i]))
if vector.equals(advtrains.round_vector_floor_y(path[i]), pos) then
return true
end
end
end
end
end
end
return false
end
function advtrains.invalidate_all_paths()
--print("invalidating all paths")
for k,v in pairs(advtrains.trains) do
if v.index then
v.restore_add_index=v.index-math.floor(v.index+0.5)
end
v.path=nil
v.index=nil
v.min_index_on_track=nil
v.max_index_on_track=nil
end
end

338
wagons.lua Normal file
View File

@ -0,0 +1,338 @@
--atan2 counts angles clockwise, minetest does counterclockwise
local print=function(t) minetest.log("action", t) minetest.chat_send_all(t) end
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
attach_offset={x=0, y=0, z=0},
view_offset={x=0, y=0, z=0},
}
function wagon:on_rightclick(clicker)
print("[advtrains] wagon rightclick")
if not clicker or not clicker:is_player() then
return
end
if not self.initialized then
print("[advtrains] not initiaalized")
return
end
if clicker:get_player_control().sneak then
advtrains.split_train_at_wagon(self)
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)
minetest.chat_send_all(dump(self:train()))
return
end
if self.driver and clicker == self.driver then
advtrains.set_trainhud(self.driver:get_player_name(), "")
self.driver = nil
clicker:set_detach()
clicker:set_eye_offset({x=0,y=0,z=0}, {x=0,y=0,z=0})
elseif not self.driver then
self.driver = clicker
clicker:set_attach(self.object, "", self.attach_offset, {x=0,y=0,z=0})
clicker:set_eye_offset(self.view_offset, self.view_offset)
end
end
function wagon:train()
return advtrains.trains[self.train_id]
end
function wagon:on_activate(staticdata, dtime_s)
print("[advtrains][wagon "..(self.unique_id or "no-id").."] activated")
self.object:set_armor_groups({immortal=1})
if staticdata then
local tmp = minetest.deserialize(staticdata)
if tmp then
self.unique_id=tmp.unique_id
self.train_id=tmp.train_id
self.wagon_flipped=tmp.wagon_flipped
end
end
self.old_pos = self.object:getpos()
self.old_velocity = self.velocity
self.initialized_pre=true
--same code is in on_step
--does this object already have an ID?
if not self.unique_id then
self.unique_id=os.time()..os.clock()--should be random enough.
end
--is my train still here
if not self.train_id or not self:train() then
if self.initialized then
print("[advtrains][wagon "..self.unique_id.."] missing train_id, destroying")
self.object:remove()
return
end
print("[advtrains][wagon "..self.unique_id.."] missing train_id, but not yet initialized, returning")
return
elseif not self.initialized then
self.initialized=true
end
advtrains.update_trainpart_properties(self.train_id)
end
function wagon:get_staticdata()
return minetest.serialize({
unique_id=self.unique_id,
train_id=self.train_id,
wagon_flipped=self.wagon_flipped,
})
end
-- Remove the wagon
function wagon:on_punch(puncher, time_from_last_punch, tool_capabilities, direction)
if not puncher or not puncher:is_player() then
return
end
self.object:remove()
if not self.initialized then return end
local inv = puncher:get_inventory()
if minetest.setting_getbool("creative_mode") then
if not inv:contains_item("main", "advtrains:locomotive") then
inv:add_item("main", "advtrains:locomotive")
end
else
inv:add_item("main", "advtrains:locomotive")
end
table.remove(self:train().trainparts, self.pos_in_trainparts)
advtrains.update_trainpart_properties(self.train_id)
return
end
function wagon:on_step(dtime)
local pos = self.object:getpos()
if not self.initialized_pre then
print("[advtrains] wagon stepping while not yet initialized_pre, returning")
self.object:setvelocity({x=0,y=0,z=0})
return
end
--does this object already have an ID?
if not self.unique_id then
self.unique_id=os.time()..os.clock()--should be random enough.
end
--is my train still here
if not self.train_id or not self:train() then
if self.initialized then
print("[advtrains][wagon "..self.unique_id.."] missing train_id, destroying")
self.object:remove()
return
end
print("[advtrains][wagon "..self.unique_id.."] missing train_id, but not yet initialized, returning")
return
elseif not self.initialized then
self.initialized=true
end
--driver control
if self.driver and self.is_locomotive then
if self.driver:get_player_control_bits()~=self.old_player_control_bits then
local pc=self.driver:get_player_control()
if pc.sneak then --stop
self:train().tarvelocity=0
elseif (not self.wagon_flipped and pc.up) or (self.wagon_flipped and pc.down) then --faster
self:train().tarvelocity=math.min(self:train().tarvelocity+1, advtrains.all_traintypes[self:train().traintype].max_speed or 10)
elseif (not self.wagon_flipped and pc.down) or (self.wagon_flipped and pc.up) then --slower
self:train().tarvelocity=math.max(self:train().tarvelocity-1, -(advtrains.all_traintypes[self:train().traintype].max_speed or 10))
elseif pc.aux1 then --slower
if math.abs(self:train().velocity)<=3 then
self.driver:set_detach()
self.driver:set_eye_offset({x=0,y=0,z=0}, {x=0,y=0,z=0})
advtrains.set_trainhud(self.driver:get_player_name(), "")
self.driver = nil
return--(don't let it crash because of statement below)
else
minetest.chat_send_player(self.driver:get_player_name(), "Can't get off driving train!")
end
end
self.old_player_control_bits=self.driver:get_player_control_bits()
end
advtrains.set_trainhud(self.driver:get_player_name(), advtrains.hud_train_format(self:train(), self.wagon_flipped))
end
local gp=self:train()
--for path to be available. if not, skip step
if not advtrains.get_or_create_path(self.train_id, gp) then
self.object:setvelocity({x=0, y=0, z=0})
return
end
local pos_in_train_left=self.pos_in_train+0
local index=gp.index
if pos_in_train_left>(index-math.floor(index))*(gp.path_dist[math.floor(index)] or 1) then
pos_in_train_left=pos_in_train_left - (index-math.floor(index))*(gp.path_dist[math.floor(index)] or 1)
index=math.floor(index)
while pos_in_train_left>(gp.path_dist[index-1] or 1) do
pos_in_train_left=pos_in_train_left - (gp.path_dist[index-1] or 1)
index=index-1
end
index=index-(pos_in_train_left/(gp.path_dist[index-1] or 1))
else
index=index-(pos_in_train_left*(gp.path_dist[math.floor(index-1)] or 1))
end
--print("trainindex "..gp.index.." wagonindex "..index)
--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
--print("[advtrains] 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
for x=-1,1 do
for y=0,2 do
for z=-1,1 do
local node=minetest.get_node_or_nil(vector.add(first_pos, {x=x, y=y, z=z}))
if node and minetest.registered_nodes[node.name] and minetest.registered_nodes[node.name].walkable then
collides=true
end
end
end
end
if collides then
gp.recently_collided_with_env=true
gp.velocity=-0.5*gp.velocity
gp.tarvelocity=0
end
end
local velocity=gp.velocity/(gp.path_dist[math.floor(gp.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}
--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
else
yaw=math.atan2((first_pos.x-second_pos.x), (second_pos.z-first_pos.z))
end
if self.wagon_flipped then
yaw=yaw+math.pi
end
self.updatepct_timer=(self.updatepct_timer or 0)-dtime
if true or not self.old_velocity_vector or not vector.equals(velocityvec, self.old_velocity_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:setyaw(yaw)
self.updatepct_timer=2
end
self.old_velocity_vector=velocityvec
self.old_yaw=yaw
end
function advtrains.register_wagon(sysname, traintype, prototype)
setmetatable(prototype, {__index=wagon})
minetest.register_entity("advtrains:"..sysname,prototype)
minetest.register_craftitem("advtrains:"..sysname, {
description = sysname,
inventory_image = prototype.textures[1],
wield_image = prototype.textures[1],
stack_max = 1,
on_place = function(itemstack, placer, pointed_thing)
if not pointed_thing.type == "node" then
return
end
local le=minetest.env:add_entity(pointed_thing.under, "advtrains:"..sysname):get_luaentity()
local node=minetest.env:get_node_or_nil(pointed_thing.under)
if not node then print("[advtrains]Ignore at placer position") return itemstack end
local nodename=node.name
if(not advtrains.is_track_and_drives_on(nodename, advtrains.all_traintypes[traintype].drives_on)) then
print("[advtrains]no trck here, not placing.")
return itemstack
end
local conn1=advtrains.get_track_connections(node.name, node.param2)
local id=advtrains.create_new_train_at(pointed_thing.under, advtrains.dirCoordSet(pointed_thing.under, conn1), traintype)
advtrains.add_wagon_to_train(le, id)
print(dump(advtrains.trains))
if not minetest.setting_getbool("creative_mode") then
itemstack:take_item()
end
return itemstack
end,
})
end
advtrains.register_train_type("steam", {"regular"})
--[[advtrains.register_wagon("blackwagon", "steam",{textures = {"black.png"}})
advtrains.register_wagon("bluewagon", "steam",{textures = {"blue.png"}})
advtrains.register_wagon("greenwagon", "steam",{textures = {"green.png"}})
advtrains.register_wagon("redwagon", "steam",{textures = {"red.png"}})
advtrains.register_wagon("yellowwagon", "steam",{textures = {"yellow.png"}})
]]
advtrains.register_wagon("newlocomotive", "steam",{
mesh="newlocomotive.b3d",
textures = {"advtrains_newlocomotive.png"},
is_locomotive=true,
attach_offset={x=5, y=10, z=-10},
view_offset={x=0, y=6, z=18},
visual_size = {x=1, y=1},
wagon_span=1.85,
collisionbox = {-1.0,-0.5,-1.0, 1.0,2.5,1.0},
})
advtrains.register_wagon("wagon_default", "steam",{
mesh="wagon.b3d",
textures = {"advtrains_wagon.png"},
attach_offset={x=0, y=10, z=0},
view_offset={x=0, y=6, z=0},
visual_size = {x=1, y=1},
wagon_span=1.8,
collisionbox = {-1.0,-0.5,-1.0, 1.0,2.5,1.0},
})
--[[
advtrains.register_wagon("wagontype1",{on_rightclick=function(self, clicker)
if clicker:get_player_control().sneak then
advtrains.disconnect_train_before_wagon(self)
return
end
--just debugging. look for first active wagon and attach to it.
for _,v in pairs(minetest.luaentities) do
if v.is_wagon and v.unique_id and v.unique_id~=self.unique_id then
self.train_id=v.unique_id
end
end
if not self.train_id then minetest.chat_send_all("not found") return end
minetest.chat_send_all(self.train_id.." found and attached.")
end})
]]

336
wagons.lua~ Normal file
View File

@ -0,0 +1,336 @@
--atan2 counts angles clockwise, minetest does counterclockwise
local print=function(t) minetest.log("action", t) minetest.chat_send_all(t) end
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
attach_offset={x=0, y=0, z=0},
view_offset={x=0, y=0, z=0},
}
function wagon:on_rightclick(clicker)
print("[advtrains] wagon rightclick")
if not clicker or not clicker:is_player() then
return
end
if not self.initialized then
print("[advtrains] not initiaalized")
return
end
if clicker:get_player_control().sneak then
advtrains.split_train_at_wagon(self)
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)
minetest.chat_send_all(dump(self:train()))
return
end
if self.driver and clicker == self.driver then
advtrains.set_trainhud(self.driver:get_player_name(), "")
self.driver = nil
clicker:set_detach()
clicker:set_eye_offset({x=0,y=0,z=0}, {x=0,y=0,z=0})
elseif not self.driver then
self.driver = clicker
clicker:set_attach(self.object, "", self.attach_offset, {x=0,y=0,z=0})
clicker:set_eye_offset(self.view_offset, self.view_offset)
end
end
function wagon:train()
return advtrains.trains[self.train_id]
end
function wagon:on_activate(staticdata, dtime_s)
print("[advtrains][wagon "..(self.unique_id or "no-id").."] activated")
self.object:set_armor_groups({immortal=1})
if staticdata then
local tmp = minetest.deserialize(staticdata)
if tmp then
self.unique_id=tmp.unique_id
self.train_id=tmp.train_id
end
end
self.old_pos = self.object:getpos()
self.old_velocity = self.velocity
self.initialized_pre=true
--same code is in on_step
--does this object already have an ID?
if not self.unique_id then
self.unique_id=os.time()..os.clock()--should be random enough.
end
--is my train still here
if not self.train_id or not self:train() then
if self.initialized then
print("[advtrains][wagon "..self.unique_id.."] missing train_id, destroying")
self.object:remove()
return
end
print("[advtrains][wagon "..self.unique_id.."] missing train_id, but not yet initialized, returning")
return
elseif not self.initialized then
self.initialized=true
end
advtrains.update_trainpart_properties(self.train_id)
end
function wagon:get_staticdata()
return minetest.serialize({
unique_id=self.unique_id,
train_id=self.train_id,
})
end
-- Remove the wagon
function wagon:on_punch(puncher, time_from_last_punch, tool_capabilities, direction)
if not puncher or not puncher:is_player() then
return
end
self.object:remove()
if not self.initialized then return end
local inv = puncher:get_inventory()
if minetest.setting_getbool("creative_mode") then
if not inv:contains_item("main", "advtrains:locomotive") then
inv:add_item("main", "advtrains:locomotive")
end
else
inv:add_item("main", "advtrains:locomotive")
end
table.remove(self:train().trainparts, self.pos_in_trainparts)
advtrains.update_trainpart_properties(self.train_id)
return
end
function wagon:on_step(dtime)
local pos = self.object:getpos()
if not self.initialized_pre then
print("[advtrains] wagon stepping while not yet initialized_pre, returning")
self.object:setvelocity({x=0,y=0,z=0})
return
end
--does this object already have an ID?
if not self.unique_id then
self.unique_id=os.time()..os.clock()--should be random enough.
end
--is my train still here
if not self.train_id or not self:train() then
if self.initialized then
print("[advtrains][wagon "..self.unique_id.."] missing train_id, destroying")
self.object:remove()
return
end
print("[advtrains][wagon "..self.unique_id.."] missing train_id, but not yet initialized, returning")
return
elseif not self.initialized then
self.initialized=true
end
--driver control
if self.driver and self.is_locomotive then
if self.driver:get_player_control_bits()~=self.old_player_control_bits then
local pc=self.driver:get_player_control()
if pc.sneak then --stop
self:train().tarvelocity=0
elseif (not self.wagon_flipped and pc.up) or (self.wagon_flipped and pc.down) then --faster
self:train().tarvelocity=math.min(self:train().tarvelocity+1, advtrains.all_traintypes[self:train().traintype].max_speed or 10)
elseif (not self.wagon_flipped and pc.down) or (self.wagon_flipped and pc.up) then --slower
self:train().tarvelocity=math.max(self:train().tarvelocity-1, -(advtrains.all_traintypes[self:train().traintype].max_speed or 10))
elseif pc.aux1 then --slower
if math.abs(self:train().velocity)<=3 then
self.driver:set_detach()
self.driver:set_eye_offset({x=0,y=0,z=0}, {x=0,y=0,z=0})
advtrains.set_trainhud(self.driver:get_player_name(), "")
self.driver = nil
return--(don't let it crash because of statement below)
else
minetest.chat_send_player(self.driver:get_player_name(), "Can't get off driving train!")
end
end
self.old_player_control_bits=self.driver:get_player_control_bits()
end
advtrains.set_trainhud(self.driver:get_player_name(), advtrains.hud_train_format(self:train(), self.wagon_flipped))
end
local gp=self:train()
--for path to be available. if not, skip step
if not advtrains.get_or_create_path(self.train_id, gp) then
self.object:setvelocity({x=0, y=0, z=0})
return
end
local pos_in_train_left=self.pos_in_train+0
local index=gp.index
if pos_in_train_left>(index-math.floor(index))*(gp.path_dist[math.floor(index)] or 1) then
pos_in_train_left=pos_in_train_left - (index-math.floor(index))*(gp.path_dist[math.floor(index)] or 1)
index=math.floor(index)
while pos_in_train_left>(gp.path_dist[index-1] or 1) do
pos_in_train_left=pos_in_train_left - (gp.path_dist[index-1] or 1)
index=index-1
end
index=index-(pos_in_train_left/(gp.path_dist[index-1] or 1))
else
index=index-(pos_in_train_left*(gp.path_dist[math.floor(index-1)] or 1))
end
--print("trainindex "..gp.index.." wagonindex "..index)
--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
--print("[advtrains] 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
for x=-1,1 do
for y=0,2 do
for z=-1,1 do
local node=minetest.get_node_or_nil(vector.add(first_pos, {x=x, y=y, z=z}))
if node and minetest.registered_nodes[node.name] and minetest.registered_nodes[node.name].walkable then
collides=true
end
end
end
end
if collides then
gp.recently_collided_with_env=true
gp.velocity=-0.5*gp.velocity
gp.tarvelocity=0
end
end
local velocity=gp.velocity/(gp.path_dist[math.floor(gp.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}
--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
else
yaw=math.atan2((first_pos.x-second_pos.x), (second_pos.z-first_pos.z))
end
if self.wagon_flipped then
yaw=yaw+math.pi
end
self.updatepct_timer=(self.updatepct_timer or 0)-dtime
if true or not self.old_velocity_vector or not vector.equals(velocityvec, self.old_velocity_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:setyaw(yaw)
self.updatepct_timer=2
end
self.old_velocity_vector=velocityvec
self.old_yaw=yaw
end
function advtrains.register_wagon(sysname, traintype, prototype)
setmetatable(prototype, {__index=wagon})
minetest.register_entity("advtrains:"..sysname,prototype)
minetest.register_craftitem("advtrains:"..sysname, {
description = sysname,
inventory_image = prototype.textures[1],
wield_image = prototype.textures[1],
stack_max = 1,
on_place = function(itemstack, placer, pointed_thing)
if not pointed_thing.type == "node" then
return
end
local le=minetest.env:add_entity(pointed_thing.under, "advtrains:"..sysname):get_luaentity()
local node=minetest.env:get_node_or_nil(pointed_thing.under)
if not node then print("[advtrains]Ignore at placer position") return itemstack end
local nodename=node.name
if(not advtrains.is_track_and_drives_on(nodename, advtrains.all_traintypes[traintype].drives_on)) then
print("[advtrains]no trck here, not placing.")
return itemstack
end
local conn1=advtrains.get_track_connections(node.name, node.param2)
local id=advtrains.create_new_train_at(pointed_thing.under, advtrains.dirCoordSet(pointed_thing.under, conn1), traintype)
advtrains.add_wagon_to_train(le, id)
print(dump(advtrains.trains))
if not minetest.setting_getbool("creative_mode") then
itemstack:take_item()
end
return itemstack
end,
})
end
advtrains.register_train_type("steam", {"regular"})
--[[advtrains.register_wagon("blackwagon", "steam",{textures = {"black.png"}})
advtrains.register_wagon("bluewagon", "steam",{textures = {"blue.png"}})
advtrains.register_wagon("greenwagon", "steam",{textures = {"green.png"}})
advtrains.register_wagon("redwagon", "steam",{textures = {"red.png"}})
advtrains.register_wagon("yellowwagon", "steam",{textures = {"yellow.png"}})
]]
advtrains.register_wagon("newlocomotive", "steam",{
mesh="newlocomotive.b3d",
textures = {"advtrains_newlocomotive.png"},
is_locomotive=true,
attach_offset={x=5, y=10, z=-10},
view_offset={x=0, y=6, z=18},
visual_size = {x=1, y=1},
wagon_span=1.85,
collisionbox = {-1.0,-0.5,-1.0, 1.0,2.5,1.0},
})
advtrains.register_wagon("wagon_default", "steam",{
mesh="wagon.b3d",
textures = {"advtrains_wagon.png"},
attach_offset={x=0, y=10, z=0},
view_offset={x=0, y=6, z=0},
visual_size = {x=1, y=1},
wagon_span=1.8,
collisionbox = {-1.0,-0.5,-1.0, 1.0,2.5,1.0},
})
--[[
advtrains.register_wagon("wagontype1",{on_rightclick=function(self, clicker)
if clicker:get_player_control().sneak then
advtrains.disconnect_train_before_wagon(self)
return
end
--just debugging. look for first active wagon and attach to it.
for _,v in pairs(minetest.luaentities) do
if v.is_wagon and v.unique_id and v.unique_id~=self.unique_id then
self.train_id=v.unique_id
end
end
if not self.train_id then minetest.chat_send_all("not found") return end
minetest.chat_send_all(self.train_id.." found and attached.")
end})
]]