Initial commit

This commit is contained in:
Ciaran Gultnieks 2014-03-26 19:10:30 +00:00
commit 80bbb57df2
52 changed files with 3442 additions and 0 deletions

168
README.md Normal file
View File

@ -0,0 +1,168 @@
Railcarts Mod
-------------
This is a Minetest mod for rails and carts, which is inspired by the
Monorail mod by Sapier, and also on the Carts mod by PilzAdam, yet
completely different to both. The design goals of this mod are different
and there are also various fundamental differences, such as the player
interaction controls and default behaviours of rails and other components.
In particular, note the absence of "power rails".
Additionally, this mod will probably generate FAR MORE network traffic
than those two mods. You have been warned.
It is also incompatible with both of those mods - you can run it at the
same time, but each of the three mods will only recognise its own carts
and specialised rail/control items.
Author: Ciaran Gultnieks
License: LGPL
data_storage.lua, by Sapier, taken from the monorail mod.
Textures/Models (both in big need of improvement):
WTFPL - PilzAdam, Sapier, Ciaran Gultnieks
Cart
----
Carts are placed on the rails with right-click. Right-clicking a cart lets
you board it. When you're in a cart, right-clicking it lets you get out.
If a situation arises where the cart is going to continue, but the player
will collide with a block above the track, the player is automatically
ejected from the cart.
Left-clicking a cart pushes it. Left-clicking with the sneak key held down
removes it from the rails (back into your inventory).
The whole cart system is really intended to be fully automated - i.e. you
select a destination and board a cart, and get dropped out when you
arrive at your destination. However, the standard move of pushing the
cart and jumping in is supported, as is punching the cart while you're in
it to control its movement.
Cargo Cart
----------
A Cargo Cart is effectively a chest on wheels. You can't ride in it.
When below a Hopper, the cart will get loaded with items until it's full,
unless it's above an AutoLauncher, in which case it will unload into the
hopper, unless that's full.
Boring Cart
-----------
A boring cart is for boring tunnels, laying rails, building bridges, etc.
It's mostly like a cargo cart, but it has two inventories - one for stuff
it digs up, and one for materials it uses.
In the materials inventory go:
* rails
* wood for bridging
* a mese pickaxe if you want it to dig
Additionally on the right-click form you will find controls for setting
the direction (level, down or up), and enabling/disabling the various
functions.
It operates when it reaches the end of the rails it's on. Be careful
with that! (Generally, it's a good idea to have failsafe limits of
operation set up using digital control rails (see below) and lua
controllers, such that boring carts are restricted from leaving the
particular part of the rail network they're working on.
Switching Rails
---------------
Rail switch junctions are changed from their default direction to the
opposite with the application of a mesecons signal.
To permanently switch a junction, burying a mesecons:switch beside it
and turning it on is a good option. Covering the switch with a slab to
prevent it being accidentally changed is also a good idea.
If using automated rail switching (a common example is with the combination
of a digital control rail to detect and identify a cart, and a lua controller
wired to the switch to change it) be aware that if MESECONS_GLOBALSTEP is set
to true (the default) there can be a significant delay before the rail
actually switches - sometimes after the cart has gone past. I just set it to
false.
Control Rail
------------
Control rails alter the behaviour of a cart that passes over. They are
normally switched on, but can be switched off with a mesecons signal.
Right-clicking brings up a dialog where you can set what they do. Any of the
following are valid:
* "none" - has no effect
* "maxspeed" - set the cart to its maximum possible speed
* "speedup" - go faster (1m/s more)
* "slowdown" - go slower
* "stop" - these are quite obvious
* "reverse" - aren't they
* "tag <tag>" - set the 'tag' on the cart - each cart has one tag, which can
be read back by Digital Control Rails. They're good for many things,
including setting cart destinations when they will travel over a large
network with many junctions.
* "speed <n>" - set the cart's speed to the given value
Digital Control Rail
--------------------
A digital control rail is a digilines-compatible rail, that sets cart controls
to the same specification as a Control Rail, but via a digiline message rather
than being a fixed setting.
In addition, it sends a digilines event when a cart passes over it,
which includes the speed, direction, type of cart, and tag.
Right-click to set the channel.
Cart Detector
-------------
A cart detector emits a mesecons signal for two seconds when a cart
passes by. It is only ever triggered once per cart.
It can be placed below the rail, in which case it will trigger for
carts passing both ways, or it can be placed to the side of the
rail, in which case it will only trigger for carts who pass with
the detector on their left.
Launcher
--------
Launchers are intended to be placed at the end of a track. Applying mesecons
power will cause an adjacent cart to be accelerated away down the track.
Autolauncher
------------
An autolauncher a) operates like a chest, although it only makes sense to
put carts in it, b) when there are rails leading away from it, or below
it, (with no cart there) and there is a cart inside, puts a cart on the
rails, c) when there is a cart adjacent to it, and there is a player on
board, accelerates that cart away down the track, d) grabs a cart when is
stationary with no player or cargo inside, on top of it, or below and
adjacent, e) if there is one below the rails, and the cart has a player
in, the player is ejected (and cargo is unloaded, see Hoppers/Cargo Carts)
Hopper
------
A hopper is for loading and unloading cargo carts. It should be placed
above the rails, with either:
* An Autolauncher below the rails, for unloading (after which, the cart
will be grabbed by the Autolauncher, and can then be sent elsewhere)
* An Autolauncher adjacent, for loading (in which case, once the cart
is loaded, it will set off, to be replaced with another for the next
batch of goods.
This might not make sense until you try it.
Pipeworks tubes connect to the top for filling the hopper. Filters are
used to empty it.

298
boringcart.lua Normal file
View File

@ -0,0 +1,298 @@
local dbg
if moddebug then dbg=moddebug.dbg("railcarts") else dbg={v1=function() end,v2=function() end,v3=function() end} end
boringcart = {}
-- A table the indexes players against the last boring cart they right
-- clicked, so we can know which cart to apply changes to when form
-- fields are clicked. This seems mighty messy, but I don't see a better
-- way of doing it.
-- A hidden form field could work (if such a thing was possible) but in
-- any case, that would allow a client to subvert the contents and apply
-- changes to a different cart!
boringcart.lastcartformbyplayer = {}
minetest.register_on_player_receive_fields(
function(player, formname, fields)
if formname ~= "railcarts:boring_cart" then return end
if fields.quit then return end
local playername = player:get_player_name()
dbg.v3("Recieved boring cart form submit from "..playername.." with "..dump(fields))
local cart = boringcart.lastcartformbyplayer[playername]
if not cart then return end
if fields.dig then
if fields.dig == "true" then
cart.dig = true
else
cart.dig = false
end
end
if fields.bridge then
if fields.bridge == "true" then
cart.bridge = true
else
cart.bridge = false
end
end
if fields.lay then
if fields.lay == "true" then
cart.lay = true
else
cart.lay = false
end
end
if fields.slope then
if fields.slope == "Level" then
cart.digslope = 2
elseif fields.slope == "Up" then
cart.digslope = 1
elseif fields.slope == "Down" then
cart.digslope = 3
end
end
dbg.v1(playername.." set boring cart parameters dig:"..tostring(cart.dig)..
",bridge:"..tostring(cart.bridge)..",lay:"..
tostring(cart.lay)..",slope:"..cart.digslope)
boringcart.show_form(cart, player)
end
)
function boringcart.show_form(self, player)
local playername = player:get_player_name()
boringcart.lastcartformbyplayer[playername] = self
minetest.show_formspec(playername, "railcarts:boring_cart",
"size[10,12;]"..
"label[0,0;Boring cart cargo:]" ..
"list[detached:" .. self.inventoryname .. ";main;0,1;4,3;]"..
"label[6,0;Boring cart materials:]" ..
"list[detached:" .. self.inventoryname .. ";materials;6,1;4,3;]"..
"list[current_player;main;1,8;8,4;]"..
"checkbox[0,4;dig;Dig;"..tostring(self.dig).."]"..
"checkbox[3,4;bridge;Bridge;"..tostring(self.bridge).."]"..
"checkbox[6,4;lay;Lay;"..tostring(self.lay).."]"..
"dropdown[0,5;3;slope;Up,Level,Down;"..self.digslope.."]")
end
function boringcart.on_end_of_rails(self, current_state, axis, oaxis, xz)
local laypos = poscopy(current_state.pos)
laypos[axis] = laypos [axis] + xz[axis]
-- Have we got a mese pick? Without one, there will be no digging.
local has_mesepick = self.inventory:contains_item("materials", "default:pick_mese 1")
local digposl
if self.dig and has_mesepick then
digposl = {}
local nn
nn = poscopy(laypos)
nn[oaxis] = nn[oaxis] - 1
if self.digslope ~= 1 then
table.insert(digposl, nn)
end
nn = poscopy(nn)
nn.y = nn.y + 1
table.insert(digposl, nn)
nn = poscopy(nn)
nn.y = nn.y + 1
table.insert(digposl, nn)
nn = poscopy(laypos)
nn[oaxis] = nn[oaxis] + 1
if self.digslope ~= 1 then
table.insert(digposl, nn)
end
nn = poscopy(nn)
nn.y = nn.y + 1
table.insert(digposl, nn)
nn = poscopy(nn)
nn.y = nn.y + 1
table.insert(digposl, nn)
nn = poscopy(laypos)
if self.digslope ~= 1 then
table.insert(digposl, nn)
end
nn = poscopy(nn)
nn.y = nn.y + 1
table.insert(digposl, nn)
nn = poscopy(nn)
nn.y = nn.y + 1
table.insert(digposl, nn)
if self.digslope == 3 then
nn = poscopy(laypos)
nn.y = nn.y - 1
table.insert(digposl, nn)
nn = poscopy(nn)
nn[oaxis] = nn[oaxis] + 1
table.insert(digposl, nn)
nn = poscopy(nn)
nn[oaxis] = nn[oaxis] - 2
table.insert(digposl, nn)
end
if self.digslope ~= 2 then
-- Extra space above on slopes
nn = poscopy(laypos)
nn.y = nn.y + 3
table.insert(digposl, nn)
nn = poscopy(nn)
nn[oaxis] = nn[oaxis] + 1
table.insert(digposl, nn)
nn = poscopy(nn)
nn[oaxis] = nn[oaxis] - 2
table.insert(digposl, nn)
if self.digslope == 1 then
-- More extra space above on up slopes
nn = poscopy(laypos)
nn.y = nn.y + 4
table.insert(digposl, nn)
nn = poscopy(nn)
nn[oaxis] = nn[oaxis] + 1
table.insert(digposl, nn)
nn = poscopy(nn)
nn[oaxis] = nn[oaxis] - 2
table.insert(digposl, nn)
end
end
end
if digposl then
for _, digpos in ipairs(digposl) do
dignode = minetest.get_node(digpos)
if dignode.name ~= "air" then
minetest.set_node(digpos, {name="air"})
-- TODO api docs say get_node_drops will be removed - why and to be replaced by what?
-- TODO also, can we calculate the right tool wear here?
local itemstacks = minetest.get_node_drops(dignode.name, "default:pick_mese")
local full = false
for _, item in ipairs(itemstacks) do
if self.inventory:room_for_item("main", item) then
self.inventory:add_item("main", item)
else
minetest.item_drop(item, "", digpos)
full = true
end
end
dbg.v2("Boring cart dug "..dignode.name.. " at "..postostr(digpos))
if full then
dbg.v2("Boring cart full at "..postostr(digpos))
return false
end
return true
end
end
end
if self.digslope == 3 then
laypos.y = laypos.y - 1
elseif self.digslope == 1 then
laypos.y = laypos.y + 1
end
laynode = minetest.get_node(laypos)
if laynode.name ~= "air" then
dbg.v2("Boring cart blocked by "..laynode.name.. " at "..postostr(laypos))
return false
end
local laybelowpos = poscopy(laypos)
laybelowpos.y = laybelowpos.y -1
lbnode = minetest.get_node(laybelowpos)
if minetest.registered_nodes[lbnode.name].walkable then
if not self.lay then
dbg.v2("Boring cart at end of rails, ready to lay, but disabled at "..postostr(laypos))
return false
end
if self.inventory:remove_item("materials", "default:rail 1"):get_count() == 1 then
minetest.place_node(laypos, {name="default:rail"})
dbg.v2("Cart laid rail at "..postostr(laypos))
return true
else
dbg.v2("Boring cart out of rails at "..postostr(laypos))
return false
end
end
if lbnode.name == "air" then
if self.bridge then
if self.inventory:remove_item("materials", "default:wood 1"):get_count() == 1 then
dbg.v2("Cart laid bridge at "..postostr(laypos))
minetest.place_node(laypos, {name="default:wood"})
return true
else
dbg.v2("Boring cart needs to bridge but has no wood at "..postostr(laypos))
return false
end
else
dbg.v2("Boring cart cannot lay track on "..lbnode.name.." at "..postostr(laypos))
return false
end
end
dbg.v2("Boring cart doesn't know what to do with "..lbnode.name.." below and in front")
return false
end
minetest.register_entity("railcarts:boring_cart_ent", {
physical = true,
collisionbox = {-0.5,-0.5,-0.5, 0.5,0.5,0.5},
visual = "mesh",
textures = { "railcarts_tex_boringcart.png" },
mesh= "railcarts_transport_cart.x",
visual_size = {x=1,y=1,z=1},
groups = { immortal=1, },
carttype = "boring",
getitem = "railcarts:boring_cart",
on_step = cartbase.on_step_handler,
on_punch = cartbase.on_punch_handler,
on_activate = cartbase.on_activate_handler,
get_staticdata = cartbase.get_staticdata_handler,
on_end_of_rails = boringcart.on_end_of_rails,
on_rightclick = function(self, clicker)
boringcart.show_form(self, clicker)
return true
end
})
minetest.register_craftitem("railcarts:boring_cart", {
description = "Boring Cart",
image = minetest.inventorycube("railcarts_inv_transportcart_top.png",
"railcarts_inv_cart_side.png",
"railcarts_inv_cart_side.png"),
on_place = function(item, placer, pointed_thing)
return cartbase.place_cart(item, pointed_thing, "railcarts:boring_cart_ent")
end
})
minetest.register_craft({
output = "railcarts:boring_cart",
recipe = {
{"", "", ""},
{"default:steel_ingot", "default:mese_crystal", "default:steel_ingot"},
{"default:steel_ingot", "default:steel_ingot", "default:steel_ingot"},
},
})

54
cargocart.lua Normal file
View File

@ -0,0 +1,54 @@
local dbg
if moddebug then dbg=moddebug.dbg("railcarts") else dbg={v1=function() end,v2=function() end,v3=function() end} end
minetest.register_entity("railcarts:cargo_cart_ent", {
physical = true,
collisionbox = {-0.5,-0.5,-0.5, 0.5,0.5,0.5},
visual = "mesh",
textures = { "railcarts_tex_transportcart.png" },
mesh= "railcarts_transport_cart.x",
visual_size = {x=1,y=1,z=1},
groups = { immortal=1, },
carttype = "cargo",
getitem = "railcarts:cargo_cart",
on_step = cartbase.on_step_handler,
on_punch = cartbase.on_punch_handler,
on_activate = cartbase.on_activate_handler,
get_staticdata = cartbase.get_staticdata_handler,
on_rightclick = function(self,clicker)
local playername = clicker:get_player_name()
minetest.show_formspec(playername, "cargo_cart_formspec",
"size[8,9;]"..
"label[0,0;Cargo cart content:]" ..
"list[detached:" .. self.inventoryname .. ";main;2,1;4,3;]"..
"list[current_player;main;0,5;8,4;]")
return true
end
})
minetest.register_craftitem("railcarts:cargo_cart", {
description = "Cargo Cart",
image = minetest.inventorycube("railcarts_inv_transportcart_top.png",
"railcarts_inv_cart_side.png",
"railcarts_inv_cart_side.png"),
on_place = function(item, placer, pointed_thing)
return cartbase.place_cart(item, pointed_thing, "railcarts:cargo_cart_ent")
end
})
minetest.register_craft({
output = "railcarts:cargo_cart",
recipe = {
{"", "", ""},
{"default:steel_ingot", "default:chest", "default:steel_ingot"},
{"default:steel_ingot", "default:steel_ingot", "default:steel_ingot"},
},
})

52
cart.lua Normal file
View File

@ -0,0 +1,52 @@
minetest.register_entity("railcarts:cart_ent", {
physical = true,
collisionbox = {-0.5,-0.5,-0.5, 0.5,0.5,0.5},
visual = "mesh",
textures = { "railcarts_tex_cart.png" },
mesh= "railcarts_cart.x",
visual_size = {x=1,y=1,z=1},
groups = { immortal=1, },
carttype = "passenger",
getitem = "railcarts:cart",
on_activate = cartbase.on_activate_handler,
get_staticdata = cartbase.get_staticdata_handler,
on_step = cartbase.on_step_handler,
on_punch = cartbase.on_punch_handler,
on_rightclick = function(self, clicker)
if not self.linkedplayer then
self.linkedplayer = clicker
clicker:set_attach(self.object, "", {x=0,y=0,z=0}, {x=0,y=0,z=0})
cartbase.setdirection(self, nil, self.direction)
else
self.linkedplayer = nil
clicker:set_detach()
end
end
})
minetest.register_craftitem("railcarts:cart", {
description = "Cart",
image = minetest.inventorycube("railcarts_inv_cart_top.png",
"railcarts_inv_cart_side.png",
"railcarts_inv_cart_side.png"),
on_place = function(item, placer, pointed_thing)
return cartbase.place_cart(item, pointed_thing, "railcarts:cart_ent")
end
})
minetest.register_craft({
output = "railcarts:cart",
recipe = {
{"", "", ""},
{"default:steel_ingot", "", "default:steel_ingot"},
{"default:steel_ingot", "default:steel_ingot", "default:steel_ingot"},
},
})

835
cartbase.lua Normal file
View File

@ -0,0 +1,835 @@
local dbg
if moddebug then dbg=moddebug.dbg("railcarts") else dbg={v1=function() end,v2=function() end,v3=function() end} end
cartbase = {}
--- Set a new direction
-- This updates the passed in state, but also sets the cart's orientation and
-- modifies the linked player's view.
-- @param state The current cart state, which will be updated.
-- Only the direction component is relevant.
-- If nil, the player's direction is set directly rather
-- than amended (e.g. boarding cart)
-- @param newdir The new direction
function cartbase.setdirection(self, state, newdir)
self.object:setyaw(direction_to_yaw(newdir))
if self.linkedplayer then
if state then
local dd = newdir - state.direction
self.addplayeryaw = self.addplayeryaw - dd * (math.pi / 2)
else
self.linkedplayer:set_look_yaw(direction_to_yaw(newdir))
end
end
if state then
state.direction = newdir
else
self.direction = newdir
end
end
--- Determine if the cart is empty
-- Empty means it can be picked up, which is not the case if there's
-- someone riding in it, or cargo on board, etc.
-- @return True if it's empty
function cartbase.is_empty(self)
if self.linkedplayer ~= nil then return false end
if self.carttype == "cargo" then
if not self.inventory:is_empty("main") then return false end
elseif self.carttype == "boring" then
if not self.inventory:is_empty("main") then return false end
if not self.inventory:is_empty("materials") then return false end
end
return true
end
--- Handle step processing for a cart
--@param self The cart
--@param dtime Time since last call, in seconds
function cartbase.on_step_handler(self, dtime)
if self.skipnextstep then
self.skipnextstep = false
self.dtime_debt = self.dtime_debt + dtime
return
end
-- No need to do any processing in a wait state.
self.loadwait = self.loadwait + dtime
if self.wait > 0 then
self.wait = self.wait - dtime
if self.wait >= 0 then return end
-- Note, adding the negative remainder - we now process just the
-- portion of the step left after the wait finished.
dtime = dtime + self.wait
self.wait = 0
end
-- Add on 'dtime debt' (caused by us skipping slices on a previous
-- step)
dtime = dtime + self.dtime_debt
self.dtime_debt = 0
self.lastmove = self.lastmove + dtime
local oldpos
if self.pos then
oldpos = {x=self.pos.x, y=self.pos.y, z=self.pos.z}
end
-- Keep track of this over all step slices and do it once, otherwise
-- it goes wrong if it happens twice (i.e. very fast over two close
-- together curves)
self.addplayeryaw = 0
-- Maximum length of time processed in a singleslice. This is based on
-- the maximum cart speed and ensures it doesn't move more than half (or
-- exactly!) a node length in one slice call.
-- Theoretically, the speed could increase during the first slice and
-- make the movement too large in the second, but I'm not worrying about
-- that for now. TODO
local max_update_time = 1
if self.speed > 0 then
max_update_time = 0.45 / self.speed
end
-- Do as many slices as we need to to keep the maximum distance moved
-- below the threshold
local nomore = false
while(dtime > max_update_time and not nomore) do
nomore = cartbase.update(self, max_update_time)
dtime = dtime - max_update_time
end
if dtime >= 0 and not nomore then
cartbase.update(self, dtime)
dtime = 0
end
if nomore then
-- TODO - seeing if skipping an extra step resolves the mesecons
-- delay which occasionally causes rails to not be switched
-- in time
self.skipnextstep = true
end
self.dtime_debt = dtime
if self.addplayeryaw ~= 0 and self.linkedplayer then
local curyaw = self.linkedplayer:get_look_yaw()
if curyaw then
-- Correct for api insanity
curyaw = curyaw - math.pi / 2
curyaw = curyaw + self.addplayeryaw
local c = math.pi * 2
while curyaw > c do curyaw = curyaw - c end
while curyaw < 0 do curyaw = curyaw + c end
self.linkedplayer:set_look_yaw(curyaw)
end
end
-- Call setpos if the position has changed
if not oldpos or not v3equal(self.pos, oldpos) then
self.object:setpos(self.pos)
end
end
--- Handle update for a cart.
--@param self The cart
--@param dtime Time since last call, in seconds, which should always be within
-- low enough that the cart doesn't >= 0.5 nodes.
--@return True if something happened which means no further step slices should
-- be processed during this step. (Currently, this is specifically only
-- when a digiline message is sent, because a common usage of that is
-- to pass information to a luacontroller, which then uses mesecons to
-- switch a rail. This will not necessarily have happened until the next
-- server step - and hopefully by then!)
function cartbase.update(self, dtime)
local no_more_slices = false
-- A little hack to stop double-grabbing. After self.object:remove() this
-- handler can still get called, because of our step-slicing.
if self.dead then return no_more_slices end
local state = {}
if not self.pos then
self.pos = self.object:getpos()
end
state.pos = self.pos
-- state.nodepos is the rounded node position, i.e. the centre of the node box
state.nodepos = v3posround(state.pos)
state.direction = self.direction
state.speed = self.speed
state.railstatus = get_railstatus(state.pos, state.direction, state.speed)
-- Stop here if we're on an unloaded block. Hopefully it will load and we can just carry on.
if is_unloaded(state.railstatus.railtype) then
if self.speed == 0 then
-- This can happen at speed 0, because we're inactive. Could have
-- been placed by an active autolauncher, near the boundary with
-- an inactive block, for example.
self.wait = 2
else
-- It shouldn't ever happen when moving though, so long as we have
-- autonomous support.
dbg.v2("Waiting for current block load ("..state.railstatus.railtype..") at "..postostr(state.pos))
self.wait = 1
end
return no_more_slices
end
-- Eject a player if necessary
if (state.railstatus.eject or state.railstatus.onautolauncher)
and self.linkedplayer ~= nil then
dbg.v1("Ejecting player from cart")
self.linkedplayer:set_detach()
self.linkedplayer:setpos(state.pos)
self.linkedplayer = nil
end
-- Grab the cart into something's inventory if necessary
-- (only allowed when there is no passenger or cargo)
if state.railstatus.grab and cartbase.is_empty(self) then
dbg.v1("Node "..postostr(state.railstatus.grab).." grabbing "..self.carttype.." cart")
local meta = minetest.get_meta(state.railstatus.grab)
local inv = meta:get_inventory()
if self.name == "railcarts:cart_ent" then
inv:add_item("main", ItemStack("railcarts:cart"))
elseif self.name == "railcarts:boring_cart_ent" then
inv:add_item("main", ItemStack("railcarts:boring_cart"))
else
inv:add_item("main", ItemStack("railcarts:cargo_cart"))
end
self.object:remove()
self.dead = true
return no_more_slices
end
-- Set speed/direction from launcher
if state.railstatus.launch then
cartbase.setdirection(self, state, state.railstatus.launch)
state.speed = LAUNCH_CART_SPEED
elseif state.railstatus.autolaunch and self.linkedplayer ~= nil then
cartbase.setdirection(self, state, state.railstatus.autolaunch)
state.speed = LAUNCH_CART_SPEED
end
-- Handle loading/unloading from/to a hopper
if (self.carttype == "cargo" or self.carttype == "boring") and state.railstatus.hopper and self.speed == 0 then
local hpos = state.railstatus.hopper
local meta = minetest.get_meta(hpos)
if meta then
local frominv = meta:get_inventory()
local toinv = self.inventory
local desc = "load"
-- For now at least, loading is the default, and unloading happens
-- when there is an autolauncher below.
if state.railstatus.onautolauncher then
local t = frominv
frominv = toinv
toinv = t
desc = "unload"
end
if frominv:is_empty("main") then
if self.loadwait > 600 and state.railstatus.autolaunch and desc == "load" and not toinv:is_empty("main") then
cartbase.setdirection(self, state, state.railstatus.autolaunch)
state.speed = LAUNCH_CART_SPEED
dbg.v3("Cart at "..postostr(hpos).." waited 10 minutes for more cargo - launching")
else
if toinv:is_empty("main") then
self.loadwait = 0
dbg.v3("Hopper and cart at "..postostr(hpos).." are empty, waiting.")
else
dbg.v3("Hopper/cart at "..postostr(hpos).." is empty, waiting (for "..math.floor(self.loadwait).."s)")
end
self.wait = 5
end
else
self.loadwait = 0
for i, stack in ipairs(frominv:get_list("main")) do
if stack:get_name() ~= "" then
if toinv:room_for_item("main", stack) then
-- Important to overwrite the slot here, not remove_item,
-- otherwise if there are two items the same with metadata
-- the wrong one can be removed.
frominv:set_stack("main", i, ItemStack(nil))
toinv:add_item("main", stack)
dbg.v1("Cart "..desc.."ed "..stack:get_count().." "..stack:get_name().." at "..postostr(hpos))
-- Counts as a move, for the purposes of remaining
-- active
self.lastmove = 0
self.wait = 5
break
else
if desc == "load" then
if state.railstatus.autolaunch then
cartbase.setdirection(self, state, state.railstatus.autolaunch)
state.speed = LAUNCH_CART_SPEED
dbg.v3("Cart at "..postostr(hpos).." is full - launching")
else
dbg.v3("Cart at "..postostr(hpos).." is full")
self.wait = 30
end
else
dbg.v3("Hopper at "..postostr(hpos).." is full, can't"..desc)
self.wait = 30
end
end
break
end
end
end
end
else
self.loadwait = 0
end
-- For safety - should never happen...
if state.railstatus.railtype == "inv" and state.speed ~= 0 then
dbg.v1("Stopping cart, it's off the rails at "..postostr(self.pos).." "..postostr(v3posround(self.pos)))
state.speed = 0
end
-- Move the cart
if state.speed > 0 then
-- Get the status of the rails at our next location
cartbase.get_next_railstatus(self, state, false)
local drop = false
if state.nextrailstatus.railtype == "inv" then
-- If there's no next rail, maybe there's a drop (a.k.a a downward slope)
cartbase.get_next_railstatus(self, state, true)
if state.nextrailstatus.railtype ~= "inv" then drop = true end
end
-- Stop here if we're entering an unloaded block. Hopefully it will load
-- and we can just carry on
if is_unloaded(state.nextrailstatus.railtype) then
dbg.v2("Waiting for next block load ("..state.nextrailstatus.railtype..") at "..
postostr(state.nextrailstatus.unloadedpos)..
" : block = "..
math.floor(state.nextrailstatus.unloadedpos.x / 16)..","..
math.floor(state.nextrailstatus.unloadedpos.y / 16)..","..
math.floor(state.nextrailstatus.unloadedpos.z / 16))
return no_more_slices
end
local movedist = state.speed * dtime
local xz = direction_to_xz(state.direction)
local axis, oaxis
if xz.x ~= 0 then
axis = "x"
oaxis = "z"
else
axis = "z"
oaxis = "x"
end
local movein = false
if (xz[axis] > 0 and state.pos[axis] < state.nodepos[axis]) or
(xz[axis] < 0 and state.pos[axis] > state.nodepos[axis]) then
movein = true
end
dbg.v3("Cart on "..state.railstatus.railtype.." at "..postostr(state.pos)..
", dir:"..state.direction.." speed:"..state.speed..
" next rail:"..state.nextrailstatus.railtype..
" "..(drop and "(drop)" or "")..
" "..(movein and "(movein)" or "(moveout)"))
state.pos[axis] = state.pos[axis] + movedist * xz[axis]
if movein then
if (xz[axis] > 0 and state.pos[axis] >= state.nodepos[axis]) or
(xz[axis] < 0 and state.pos[axis] <= state.nodepos[axis]) then
-- We were moving in to the centre, but now we've moved past it
local past = math.abs(state.pos[axis] - state.nodepos[axis])
-- Trigger a detector?
if state.railstatus.detector then
local detpos = state.railstatus.detector
minetest.add_node(detpos, {name="railcarts:cart_detector_on"})
mesecon:receptor_on(detpos, mesecon.rules.default)
dbg.v2("Triggered cart detector at "..postostr(newpos))
end
-- Various control things. These are applied only at the centre
-- of the node such that they only occur once.
local con = state.railstatus.control
if con then
if con == "maxspeed" then
dbg.v2("Max speed on control rail")
state.speed = MAXIMUM_CART_SPEED
elseif con == "speedup" then
dbg.v2("Accelerating on control rail")
state.speed = state.speed + 1
elseif con == "slowdown" then
dbg.v2("Decelerating on control rail")
state.speed = state.speed - 1
elseif con == "stop" then
dbg.v2("Stopping on control rail")
state.speed = 0
elseif con == "reverse" then
dbg.v2("Reversing on control rail")
cartbase.setdirection(self, state,
direction_reverse(state.direction))
-- Move back to prevent re-trigger on the way back
state.pos[axis] = state.pos[axis] - past
elseif string.sub(con, 0, 6) == "speed " then
local newspeed = tonumber(string.sub(con, 7))
if newspeed then
state.speed = newspeed
dbg.v2("Setting speed to "..newspeed.." on control rail")
else
dbg.v1("Invalid speed from control rail")
end
elseif string.sub(con, 0, 4) == "tag " then
self.tag = string.sub(con, 5)
dbg.v2("Cart tagged '"..self.tag.."'")
elseif con == "none" then
-- doesn't do anything
else
dbg.v1("Invalid control rail setting")
end
-- Cap speed appropriately
if state.speed < 0 then state.speed = 0 end
if state.speed > MAXIMUM_CART_SPEED then state.speed = MAXIMUM_CART_SPEED end
end
-- Send digiline signal if required
if digiline and state.railstatus.digiline then
local dpos = state.railstatus.digiline
local channel = minetest.get_meta(dpos):get_string("channel")
if channel and channel ~= "" then
msg = {}
msg.tag = self.tag
msg.speed = self.speed
msg.direction = self.direction
if self.linkedplayer then
msg.passenger = self.linkedplayer:get_player_name()
else
msg.passenger = nil
end
msg.carttype = self.carttype
dbg.v2("Sending digiline message on channel "..channel)
digiline:receptor_send(dpos, digiline.rules.default, channel, msg)
no_more_slices = true
end
end
if state.nextrailstatus.railtype == "inv" then
-- End of rails, so stop the cart (or do a custom action)
state.pos[axis] = state.nodepos[axis]
state.speed = 0
if self.on_end_of_rails then
if self.on_end_of_rails(self, state, axis, oaxis, xz) then
self.wait = 2
state.speed = 1
-- Back up a bit so we come back here for the next action
state.pos[axis] = state.pos[axis] - (0.4 * xz[axis])
end
else
dbg.v2("Cart reached end of rails and stopped at "..postostr(state.pos))
end
elseif is_curve(state.railstatus.railtype) then
state.pos[axis] = state.nodepos[axis]
local newdir
if state.railstatus.railtype == "x+" then
if axis == "x" then
newdir = 2
past = -past
else
newdir = 1
end
elseif state.railstatus.railtype == "x-" then
if axis == "x" then
newdir = 2
past = -past
else
newdir = 3
past = -past
end
elseif state.railstatus.railtype == "z+" then
if axis == "x" then
newdir = 0
else
newdir = 1
end
else
if axis == "x" then
newdir = 0
else
newdir = 3
past = -past
end
end
cartbase.setdirection(self, state, newdir)
state.pos[oaxis] = state.pos[oaxis] + past
dbg.v2("Cart turned a corner")
end
end
else
--Moving out
local newnodepos = v3posround(state.pos)
if state.nodepos[axis] ~= newnodepos[axis] then
dbg.v3("New nodepos "..newnodepos[axis]..", old "..state.nodepos[axis])
-- We've arrived in the new node
if drop then
state.pos.y = state.pos.y - 1
elseif state.direction == state.railstatus.slope then
state.pos.y = state.pos.y + 1
end
end
end
end
-- Set ourselves autonomous if necessary (requires minetest patch)
if state.speed > 0 then
self.lastmove = 0
end
local autonomous = 0
if self.lastmove < 10 then
autonomous = 1
end
self.object:set_autonomous(autonomous)
-- Write things back. Ultimately the position will get 'broken' when core
-- turns it from a double to a float. We don't care, because we use our
-- own internally stored position as the reference.
self.pos = state.pos
self.speed = state.speed
self.direction = state.direction
return no_more_slices
end
--- Get the status of the rails at the next node
--@param state The current state, which will have a next_railstatus
-- field added. (And only railstatus, pos, speed and direction are relevant)
--@param below True to look below instead of at the same level
function cartbase.get_next_railstatus(self, state, below)
if state.speed == 0 then
state.nextrailstatus = {railtype="inv"}
return
end
local xz, nexty
if below then
nexty = -1
else
if state.railstatus.slope then
if state.railstatus.slope == state.direction then
nexty = 1
else
nexty = 0
end
else
nexty = 0
end
end
if is_curve(state.railstatus.railtype) then
if state.railstatus.railtype == "x+" then
if state.direction == 0 or state.direction == 1 then
xz = {x=1,z=0}
else
xz = {x=0,z=-1}
end
elseif state.railstatus.railtype == "x-" then
if state.direction == 0 or state.direction == 3 then
xz = {x=-1,z=0}
else
xz = {x=0,z=-1}
end
elseif state.railstatus.railtype == "z+" then
if state.direction == 2 or state.direction == 1 then
xz = {x=1,z=0}
else
xz = {x=0,z=1}
end
else
if state.direction == 2 or state.direction == 3 then
xz = {x=-1,z=0}
else
xz = {x=0,z=1}
end
end
else
xz = direction_to_xz(state.direction)
end
nextpos = v3posround(state.pos)
nextpos.x = nextpos.x + xz.x
nextpos.y = nextpos.y + nexty
nextpos.z = nextpos.z + xz.z
dbg.v3("Checking nextpos "..postostr(nextpos))
state.nextrailstatus = get_railstatus(nextpos)
end
--- Handle cart being punched by a player
--
function cartbase.punch_move(self, own_pos, hitterpos)
self.wait = 0
local railstatus = get_railstatus(self.pos)
-- Only when on rails...
if railstatus.railtype == "inv" then return end
local xd = own_pos.x - hitterpos.x
local zd = own_pos.z - hitterpos.z
dbg.v2("Player punched cart with xd="..xd..",zd="..zd)
local newdir
if railstatus.railtype == "x" then
if xd < 0 then
newdir = 3
else
newdir = 1
end
elseif railstatus.railtype == "z" then
if zd < 0 then
newdir = 2
else
newdir = 0
end
elseif railstatus.railtype == "x+" then
if math.abs(zd) > math.abs(xd) and zd < 0 then
newdir = 2
elseif math.abs(xd) > math.abs(zd) and xd > 0 then
newdir = 1
end
end
-- TODO - the rest of the curves!
if newdir then
-- Punching a moving cart in the same direction speeds it up
if self.speed ~= 0 then
if newdir == self.direction then
self.speed = self.speed + 1
if self.speed > MAXIMUM_CART_SPEED then
self.speed = MAXIMUM_CART_SPEED
end
else
-- otherwise it just stops...
self.speed = 0
end
return
end
state = {direction=newdir, speed=1, railstatus=railstatus, pos=self.pos}
cartbase.get_next_railstatus(self, state, false)
if state.nextrailstatus.railtype == "inv" then
-- check below, so we can push down a hill
cartbase.get_next_railstatus(self, state, true)
end
if state.nextrailstatus.railtype ~= "inv" and
not is_unloaded(state.nextrailstatus.railtype) then
cartbase.setdirection(self, nil, newdir)
self.speed = 1
end
end
end
--- Handler for on_punch
-- @@param hitter The player that hit it
function cartbase.on_punch_handler(self, hitter)
if hitter:get_player_control().sneak and cartbase.is_empty(self) then
hitter:get_inventory():add_item("main", self.getitem)
self.object:remove()
else
local own_pos = self.object:getpos()
local hitterpos = hitter:getpos()
cartbase.punch_move(self,own_pos,hitterpos)
end
return true
end
--- Handler for on_activate
-- @param staticdata Saved data if restoring a deactivated cart
function cartbase.on_activate_handler(self, staticdata)
self.direction = 0
self.speed = 0
self.pos = nil
self.tag = nil
self.wait = 0
self.loadwait = 0
self.skipnextstep = false
self.lastmove = 0
self.dtime_debt = 0
-- These are parameters for the boring cart only...
self.dig = false
self.bridge = false
self.lay = false
self.digslope = 2
self.object:set_armor_groups(self.groups)
-- Set up detached inventories if required
if self.carttype == "cargo" or self.carttype == "boring" then
-- Unique name needed for detached inventory
-- TODO - is there a better way of getting this unique ID?
local uid
for k, v in pairs(minetest.luaentities) do
if v == self then
uid = k
break
end
end
if not uid then
dbg.v1("WARNING - unique ID was not generated - cart inventories may be shared")
uuid = tostring(math.random())
end
self.inventoryname = uid
dbg.v3("Creating new cart, inventory name is "..self.inventoryname)
self.inventory = minetest.create_detached_inventory(self.inventoryname, nil)
self.inventory:set_size("main",12)
if self.carttype == "boring" then
self.inventory:set_size("materials",12)
end
end
local restored = minetest.deserialize(staticdata)
if restored ~= nil then
if restored.pos then
self.pos = restored.pos
else
self.pos = self.object:getpos()
end
if restored.speed then
self.speed = restored.speed
end
if restored.direction then
self.direction = restored.direction
end
if restored.tag then
self.tag = restored.tag
end
if restored.wait then
self.wait = restored.wait
end
if restored.loadwait then
self.loadwait = restored.loadwait
end
if restored.dig then
self.dig = restored.dig
end
if restored.bridge then
self.bridge = restored.bridge
end
if restored.lay then
self.lay = restored.lay
end
if restored.digslope then
self.digslope = restored.digslope
end
local inv_main
if restored.stacks then
-- TODO legacy support, can remove in a bit
inv_main = restored.stacks
end
inv_main = restored.inv_main
if inv_main then
for i=1,#inv_main,1 do
self.inventory:set_stack("main",i,inv_main[i])
end
end
local inv_materials = restored.inv_materials
if inv_materials then
for i=1,#inv_materials,1 do
self.inventory:set_stack("materials",i,inv_materials[i])
end
end
end
self.object:set_armor_groups(self.groups)
end
--- Get static data for saving state on deactivation
function cartbase.get_staticdata_handler(self)
local tostore = {}
tostore.pos = self.pos
tostore.speed = self.speed
tostore.direction = self.direction
tostore.tag = self.tag
tostore.wait = self.wait
tostore.loadwait = self.loadwait
if self.carttype == "cargo" or self.carttype == "boring" then
tostore.inv_main = cartbase.get_inv(self, "main")
end
if self.carttype == "boring" then
tostore.inv_materials = cartbase.get_inv(self, "materials")
tostore.dig = self.dig
tostore.bridge = self.bridge
tostore.lay = self.lay
tostore.digslope = self.digslope
end
return minetest.serialize(tostore)
end
function cartbase.get_inv(self, name)
local st = {}
local list = self.inventory:get_list(name)
for i=1,#list,1 do
table.insert(st,list[i]:to_string())
end
return st
end
function cartbase.place_cart(item, pointed_thing, ent)
if pointed_thing.type == "node" then
local pos = pointed_thing.above
pos.y = pos.y - 1
local railtype = get_railstatus(pos).railtype
if railtype ~= "inv" then
local obj = minetest.add_entity(pos, ent)
if obj then
if railtype == "x" then
obj:setyaw(0)
elseif railtype == "z" then
obj:setyaw(0 + math.pi/2)
end
item:take_item()
end
end
end
return item
end

98
controlrail.lua Normal file
View File

@ -0,0 +1,98 @@
local dbg
if moddebug then dbg=moddebug.dbg("railcarts") else dbg={v1=function() end,v2=function() end,v3=function() end} end
minetest.register_craft({
output = "railcarts:controlrail 12",
recipe = {
{"default:steel_ingot", "", "default:steel_ingot"},
{"default:steel_ingot", "default:stick", "default:steel_ingot"},
{"default:steel_ingot", "default:mese_crystal_fragment", "default:steel_ingot"},
}
})
minetest.register_node("railcarts:controlrail", {
description = "Control Rail",
drawtype = "raillike",
tiles = {"railcarts_pa_carts_rail_pwr.png",
"railcarts_pa_carts_rail_curved_pwr.png",
"railcarts_pa_carts_rail_t_junction_pwr.png",
"railcarts_pa_carts_rail_crossing_pwr.png"},
inventory_image = "railcarts_pa_carts_rail_pwr.png",
wield_image = "railcarts_pa_carts_rail_pwr.png",
paramtype = "light",
is_ground_content = true,
walkable = false,
selection_box = {
type = "fixed",
fixed = {-1/2, -1/2, -1/2, 1/2, -1/2+1/16, 1/2},
},
groups = {bendy=2,snappy=1,dig_immediate=2,attached_node=1,rail=1,connect_to_raillike=1},
mesecons = {
effector = {
action_on = function(pos, node)
node.name = "railcarts:controlrail_off"
minetest.swap_node(pos, node)
end
}
},
on_construct = function(pos)
local meta = minetest.get_meta(pos)
meta:set_string("formspec",
"field[fn;Control;${infotext}]")
meta:set_string("infotext", "speedup")
end,
on_receive_fields = function(pos, formname, fields, sender)
if fields and fields.fn then
dbg.v2("Control rail at "..postostr(pos).." set to "..fields.fn.." by "..(sender:get_player_name() or ""))
local meta = minetest.get_meta(pos)
meta:set_string("infotext", fields.fn)
end
end
})
minetest.register_node("railcarts:controlrail_off", {
description = "Control Rail",
drawtype = "raillike",
tiles = {"railcarts_pa_carts_rail_brk.png",
"railcarts_pa_carts_rail_curved_brk.png",
"railcarts_pa_carts_rail_t_junction_brk.png",
"railcarts_pa_carts_rail_crossing_brk.png"},
drop = 'railcarts:controlrail 1',
paramtype = "light",
is_ground_content = true,
walkable = false,
selection_box = {
type = "fixed",
fixed = {-1/2, -1/2, -1/2, 1/2, -1/2+1/16, 1/2},
},
groups = {bendy=2,snappy=1,dig_immediate=2,attached_node=1,rail=1,connect_to_raillike=1},
mesecons = {
effector = {
action_off = function(pos, node)
node.name = "railcarts:controlrail"
minetest.swap_node(pos, node)
end
}
},
on_construct = controlrail_construct,
on_receive_fields = controlrail_receive
})
function controlrail_construct(pos)
local meta = minetest.get_meta(pos)
meta:set_string("formspec",
"field[fn;Control;${infotext}]")
meta:set_string("infotext", "speedup")
end
function controlrail_receive(pos, formname, fields, sender)
if fields and fields.fn then
dbg.v2("Control rail at "..postostr(pos).." set to "..fields.fn.." by "..(sender:get_player_name() or ""))
local meta = minetest.get_meta(pos)
meta:set_string("infotext", fields.fn)
end
end

109
data_storage.lua Normal file
View File

@ -0,0 +1,109 @@
-------------------------------------------------------------------------------
-- Monorail Mod by Sapier
--
-- You may copy, use, modify or do nearly anything except removing this
-- copyright notice.
-- And of course you are NOT allow to pretend you have written it.
--
--! @file data_storage.lua
--! @brief generic functions used in many different places
--! @copyright Sapier
--! @author Sapier
--! @date 2013-02-04
--!
-- Contact sapier a t gmx net
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
-- name: railcarts_get_current_time()
--
--! @brief alias to get current time
--
--! @return current time in seconds
-------------------------------------------------------------------------------
function railcarts_get_current_time()
return os.time(os.date('*t'))
--return minetest.get_time()
end
-------------------------------------------------------------------------------
-- name: railcarts_global_data_store(value)
--
--! @brief save data and return unique identifier
--
--! @param value to save
--
--! @return unique identifier
-------------------------------------------------------------------------------
railcarts_global_data_identifier = 0
railcarts_global_data = {}
railcarts_global_data.cleanup_index = 0
railcarts_global_data.last_cleanup = railcarts_get_current_time()
function railcarts_global_data_store(value)
local current_id = railcarts_global_data_identifier
railcarts_global_data_identifier = railcarts_global_data_identifier + 1
railcarts_global_data[current_id] = {
value = value,
added = railcarts_get_current_time(),
}
return current_id
end
-------------------------------------------------------------------------------
-- name: railcarts_global_data_store(value)
--
--! @brief pop data from global store
--
--! @param id to pop
--
--! @return stored value
-------------------------------------------------------------------------------
function railcarts_global_data_get(id)
local dataid = tonumber(id)
if dataid == nil or
railcarts_global_data[dataid] == nil then
return nil
end
local retval = railcarts_global_data[dataid].value
railcarts_global_data[dataid] = nil
return retval
end
-------------------------------------------------------------------------------
-- name: railcarts_global_data_store(value)
--
--! @brief pop data from global store
--
--! @param id to pop
--
--! @return stored value
-------------------------------------------------------------------------------
function railcarts_global_data_cleanup(id)
if railcarts_global_data.last_cleanup + 500 < railcarts_get_current_time() then
for i=1,50,1 do
if railcarts_global_data[railcarts_global_data.cleanup_index] ~= nil then
if railcarts_global_data[railcarts_global_data.cleanup_index].added <
railcarts_get_current_time() - 300 then
railcarts_global_data[railcarts_global_data.cleanup_index] = nil
end
railcarts_global_data.cleanup_index = railcarts_global_data.cleanup_index +1
if railcarts_global_data.cleanup_index > #railcarts_global_data then
railcarts_global_data.cleanup_index = 0
break
end
end
end
railcarts_global_data.last_cleanup = railcarts_get_current_time()
end
end

70
defaultrail.lua Normal file
View File

@ -0,0 +1,70 @@
local dbg
if moddebug then dbg=moddebug.dbg("railcarts") else dbg={v1=function() end,v2=function() end,v3=function() end} end
local railrules = {
{x = 0, y = -1, z = 0},
{x = -1, y = 0, z = 0},
{x = 1, y = 0, z = 0},
{x = 0, y = 0, z = 1},
{x = 0, y = 0, z = -1},
{x = -1, y = -1, z = 0},
{x = 1, y = -1, z = 0},
{x = 0, y = -1, z = 1},
{x = 0, y = -1, z = -1},
}
minetest.register_node(":default:rail", {
description = "Rail",
drawtype = "raillike",
tiles = {"default_rail.png", "default_rail_curved.png", "default_rail_t_junction.png", "default_rail_crossing.png"},
inventory_image = "default_rail.png",
wield_image = "default_rail.png",
paramtype = "light",
is_ground_content = true,
walkable = false,
selection_box = {
type = "fixed",
fixed = {-1/2, -1/2, -1/2, 1/2, -1/2+1/16, 1/2},
},
groups = {bendy=2,snappy=1,dig_immediate=2,attached_node=1,rail=1,connect_to_raillike=1},
mesecons = {
effector = {
rules = railrules,
action_on =
function(pos, node)
node.name = "railcarts:rail_switched"
minetest.swap_node(pos, node)
dbg.v2("Rails switched at "..postostr(pos))
end
}
}
})
minetest.register_node("railcarts:rail_switched", {
description = "Rail",
drawtype = "raillike",
drop = 'default:rail 1',
tiles = {"default_rail.png", "default_rail_curved.png", "railcarts_rail_t_junction_switched.png", "default_rail_crossing.png"},
paramtype = "light",
is_ground_content = true,
walkable = false,
selection_box = {
type = "fixed",
fixed = {-1/2, -1/2, -1/2, 1/2, -1/2+1/16, 1/2},
},
groups = {bendy=2,snappy=1,dig_immediate=2,attached_node=1,rail=1,connect_to_raillike=1},
mesecons = {
effector = {
rules = railrules,
action_off =
function(pos, node)
node.name = "default:rail"
minetest.swap_node(pos, node)
dbg.v2("Rails switched back at "..postostr(pos))
end
}
}
})

5
depends.txt Normal file
View File

@ -0,0 +1,5 @@
default
mesecons
digilines
pipeworks
moddebug?

7
description.txt Normal file
View File

@ -0,0 +1,7 @@
Version 1.0.0
Railcarts mod - carts and rails, designed for automation and autonomy
-Passenger cart
-Cargo cart
-Boring cart (tunnel and rail construction)
-Mesecon integration (optional)
-Digiline integration (optional)

47
detector.lua Normal file
View File

@ -0,0 +1,47 @@
local dbg
if moddebug then dbg=moddebug.dbg("railcarts") else dbg={v1=function() end,v2=function() end,v3=function() end} end
minetest.register_craft({
output = "railcarts:cart_detector",
recipe = {
{"", "", ""},
{"default:mese_crystal", "", "default:mese_crystal"},
{"default:mese_crystal", "default:mese_crystal", "default:mese_crystal"},
},
}
)
minetest.register_node("railcarts:cart_detector", {
description = "Cart Detector",
tiles = {"railcarts_cart_detector.png"},
is_ground_content = true,
groups = {cracky=3, mesecon=2},
drop = 'railcarts:cart_detector 1',
mesecons = { receptor = {
state = "off"
} }
})
minetest.register_node("railcarts:cart_detector_on", {
description = "Cart Detector",
tiles = {"railcarts_cart_detector.png"},
is_ground_content = true,
groups = {cracky=3, mesecon=2},
drop = 'railcarts:cart_detector 1',
mesecons = { receptor = {
state = "on"
} }
})
minetest.register_abm({
nodenames = {"railcarts:cart_detector_on"},
interval = 2,
chance = 1,
action = function(pos, node, active_object_count, active_object_count_wider)
minetest.add_node(pos,{name="railcarts:cart_detector"})
mesecon:receptor_off(pos, mesecon.rules.default)
dbg.v2("Cart detector off at: "..postostr(pos))
end
})

50
digicontrol.lua Normal file
View File

@ -0,0 +1,50 @@
minetest.register_craft({
output = "railcarts:digicontrol 12",
recipe = {
{"default:steel_ingot", "digilines:wire_std_00000000", "default:steel_ingot"},
{"default:steel_ingot", "default:stick", "default:steel_ingot"},
{"default:steel_ingot", "default:mese_crystal_fragment", "default:steel_ingot"},
}
})
minetest.register_node("railcarts:digicontrol", {
description = "Digital Control Rail",
drawtype = "raillike",
tiles = {"railcarts_pa_carts_rail_digi.png",
"railcarts_pa_carts_rail_curved_digi.png",
"railcarts_pa_carts_rail_t_junction_digi.png",
"railcarts_pa_carts_rail_crossing_digi.png"},
inventory_image = "railcarts_pa_carts_rail_digi.png",
wield_image = "railcarts_pa_carts_rail_digi.png",
paramtype = "light",
is_ground_content = true,
walkable = false,
selection_box = {
type = "fixed",
fixed = {-1/2, -1/2, -1/2, 1/2, -1/2+1/16, 1/2},
},
groups = {bendy=2,snappy=1,dig_immediate=2,attached_node=1,rail=1,connect_to_raillike=1},
on_construct = function(pos)
local meta = minetest.get_meta(pos)
meta:set_string("formspec", "field[channel;Channel;${channel}]")
meta:set_string("infotext", "none")
end,
on_receive_fields = function(pos, formname, fields, sender)
local meta = minetest.get_meta(pos)
meta:set_string("channel", fields.channel)
end,
digiline =
{
receptor = {},
effector = {
action = function(pos, node, channel, msg)
local setchan = minetest.get_meta(pos):get_string("channel")
if setchan ~= channel then return end
local meta = minetest.env:get_meta(pos)
meta:set_string("infotext", msg)
end
},
},
})

61
direction.lua Normal file
View File

@ -0,0 +1,61 @@
-- Directions are used throughout to specify movement along an axis. A
-- direction is an integer from 0 to 3, defined as follows:
--
-- 0 = +z
-- 1 = +x
-- 2 = +z
-- 3 = +x
--
-- Thus, incrementing the direction is a clockwise rotation, decrementing
-- is an anticlockwise one.
--
-- This file provides utility functions for converting and manipulating
-- them.
--- Convert a direction to an xz vector of length 1
--@param direction A direction, 0-3
--@return {x,z}
function direction_to_xz(direction)
if direction == 0 then return {x=0,z=1} end
if direction == 1 then return {x=1,z=0} end
if direction == 2 then return {x=0,z=-1} end
return {x=-1,z=0}
end
--- Convert an xz vector to a direction.
-- @param xz An xz vector, which had better be one of those returned by
-- direction_to_xz, or at least a multiple of, otherwise the results will
-- be nonsensical.
-- @return The direction
function xz_to_direction(xz)
if xz.z > 0 then return 0 end
if xz.x > 0 then return 1 end
if xz.z < 0 then return 2 end
return 3
end
--- Get the yaw for a direction
-- @param direction The direction
-- @return The yaw in radians
function direction_to_yaw(direction)
if direction == 3 then
direction = 1
elseif direction == 1 then
direction = 3
end
return direction * (math.pi / 2)
end
--- Get the opposite of a direction
-- @param direction The direction
-- @return The opposite direction
function direction_reverse(direction)
if direction == 0 then return 2 end
if direction == 1 then return 3 end
if direction == 2 then return 0 end
return 1
end

43
hopper.lua Normal file
View File

@ -0,0 +1,43 @@
minetest.register_craft({
output = "railcarts:hopper 1",
recipe = {
{"default:steel_ingot", "", "default:steel_ingot"},
{"default:steel_ingot", "", "default:steel_ingot"},
{"", "default:steel_ingot", ""},
}
})
local hopper = pipeworks.clone_node("default:chest")
hopper.description = "Hopper"
hopper.tiles[1] = "pipeworks_filter_side.png"
hopper.tiles[2] = "pipeworks_filter_side.png"
hopper.tiles[3] = "railcarts_hopper_side.png"
hopper.tiles[4] = "railcarts_hopper_side.png"
hopper.tiles[5] = "railcarts_hopper_side.png"
hopper.tiles[6] = "railcarts_hopper_side.png"
hopper.groups.tubedevice = 1
hopper.groups.tubedevice_receiver = 1
hopper.tube = {
insert_object = function(pos, node, stack, direction)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
return inv:add_item("main", stack)
end,
can_insert = function(pos, node, stack, direction)
if direction.y ~= -1 then return false end
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
return inv:room_for_item("main", stack)
end,
input_inventory = "main",
connect_sides = {top=1}
}
hopper.after_place_node = function(pos)
pipeworks.scan_for_tube_objects(pos)
end
hopper.after_dig_node = function(pos)
pipeworks.scan_for_tube_objects(pos)
end
minetest.register_node("railcarts:hopper", hopper)

23
init.lua Normal file
View File

@ -0,0 +1,23 @@
local version = "1.0.0"
LAUNCH_CART_SPEED = 2
MAXIMUM_CART_SPEED = 7
local railcarts_modpath = minetest.get_modpath("railcarts")
dofile(railcarts_modpath .. "/util.lua")
dofile(railcarts_modpath .. "/direction.lua")
dofile(railcarts_modpath .. "/defaultrail.lua")
dofile(railcarts_modpath .. "/controlrail.lua")
dofile(railcarts_modpath .. "/digicontrol.lua")
dofile(railcarts_modpath .. "/launchers.lua")
dofile(railcarts_modpath .. "/detector.lua")
dofile(railcarts_modpath .. "/hopper.lua")
dofile(railcarts_modpath .. "/rail.lua")
dofile(railcarts_modpath .. "/cartbase.lua")
dofile(railcarts_modpath .. "/cargocart.lua")
dofile(railcarts_modpath .. "/boringcart.lua")
dofile(railcarts_modpath .. "/cart.lua")
print("railcarts mod " .. version .. " loaded")

154
items.lua Normal file
View File

@ -0,0 +1,154 @@
minetest.register_craft({
output = "railcarts:cart",
recipe = {
{"", "", ""},
{"default:steel_ingot", "", "default:steel_ingot"},
{"default:steel_ingot", "default:steel_ingot", "default:steel_ingot"},
},
})
minetest.register_craft({
output = "railcarts:cargo_cart",
recipe = {
{"", "", ""},
{"default:steel_ingot", "default:chest", "default:steel_ingot"},
{"default:steel_ingot", "default:steel_ingot", "default:steel_ingot"},
},
})
-- TODO : get rid of these when conversion is no longer needed
minetest.register_alias("railcarts:powerrail", "railcarts:controlrail")
minetest.register_alias("railcarts:powerrail_off", "railcarts:controlrail_off")
minetest.register_alias("railcarts:brakerail", "railcarts:controlrail")
minetest.register_alias("railcarts:brakerail_off", "railcarts:controlrail_off")
minetest.register_alias("railcarts:transport_cart", "railcarts:cargo_cart")
minetest.register_alias("railcarts:freight_cart", "railcarts:cargo_cart")
minetest.register_craftitem("railcarts:cart", {
description = "Cart",
image = minetest.inventorycube("railcarts_inv_cart_top.png",
"railcarts_inv_cart_side.png",
"railcarts_inv_cart_side.png"),
on_place = function(item, placer, pointed_thing)
if pointed_thing.type == "node" then
local pos = pointed_thing.above
pos.y = pos.y - 1
local railtype = get_railstatus(pos).railtype
if railtype ~= "inv" then
minetest.env:add_entity(pos,"railcarts:cart_ent")
if new_object ~= nil then
if railtype == "x" then
new_object:setyaw(0)
elseif railtype == "z" then
new_object:setyaw(0 + math.pi/2)
end
end
item:take_item()
end
end
return item
end
})
minetest.register_craftitem("railcarts:cargo_cart", {
description = "Cargo Cart",
image = minetest.inventorycube("railcarts_inv_transportcart_top.png",
"railcarts_inv_cart_side.png",
"railcarts_inv_cart_side.png"),
on_place = function(item, placer, pointed_thing)
if pointed_thing.type == "node" then
local pos = pointed_thing.above
pos.y = pos.y - 1
local railtype = get_railstatus(pos).railtype
if railtype ~= "inv" then
minetest.env:add_entity(pos,"railcarts:cargo_cart_ent")
if new_object ~= nil then
if railtype == "x" then
new_object:setyaw(0)
elseif railtype == "z" then
new_object:setyaw(0 + math.pi/2)
end
end
item:take_item()
end
end
return item
end
})
local railrules = {
{x = 0, y = -1, z = 0},
{x = -1, y = 0, z = 0},
{x = 1, y = 0, z = 0},
{x = 0, y = 0, z = 1},
{x = 0, y = 0, z = -1},
{x = -1, y = -1, z = 0},
{x = 1, y = -1, z = 0},
{x = 0, y = -1, z = 1},
{x = 0, y = -1, z = -1},
}
minetest.register_node(":default:rail", {
description = "Rail",
drawtype = "raillike",
tiles = {"default_rail.png", "default_rail_curved.png", "default_rail_t_junction.png", "default_rail_crossing.png"},
inventory_image = "default_rail.png",
wield_image = "default_rail.png",
paramtype = "light",
is_ground_content = true,
walkable = false,
selection_box = {
type = "fixed",
fixed = {-1/2, -1/2, -1/2, 1/2, -1/2+1/16, 1/2},
},
groups = {bendy=2,snappy=1,dig_immediate=2,attached_node=1,rail=1,connect_to_raillike=1},
mesecons = {
effector = {
rules = railrules,
action_on =
function(pos, node)
node.name = "railcarts:rail_switched"
minetest.swap_node(pos, node)
debug.v2("Rails switched at "..postostr(pos))
end
}
}
})
minetest.register_node("railcarts:rail_switched", {
description = "Rail",
drawtype = "raillike",
drop = 'default:rail 1',
tiles = {"default_rail.png", "default_rail_curved.png", "railcarts_rail_t_junction_switched.png", "default_rail_crossing.png"},
paramtype = "light",
is_ground_content = true,
walkable = false,
selection_box = {
type = "fixed",
fixed = {-1/2, -1/2, -1/2, 1/2, -1/2+1/16, 1/2},
},
groups = {bendy=2,snappy=1,dig_immediate=2,attached_node=1,rail=1,connect_to_raillike=1},
mesecons = {
effector = {
rules = railrules,
action_off =
function(pos, node)
node.name = "default:rail"
minetest.swap_node(pos, node)
debug.v2("Rails switched back at "..postostr(pos))
end
}
}
})

150
launchers.lua Normal file
View File

@ -0,0 +1,150 @@
local dbg
if moddebug then dbg=moddebug.dbg("railcarts") else dbg={v1=function() end,v2=function() end,v3=function() end} end
minetest.register_craft({
output = 'node "railcarts:launcher_off" 5',
recipe = {
{"default:cobble", "default:mese_crystal_fragment","default:cobble"},
{"default:mese_crystal_fragment", "default:glass", "default:mese_crystal_fragment"},
{"default:cobble", "default:mese_crystal_fragment","default:cobble"},
}
})
minetest.register_craft({
output = 'node "railcarts:cart_detector_off" 5',
recipe = {
{"default:cobble", "default:glass","default:cobble"},
{"default:glass", "default:mese_crystal_fragment", "default:glass"},
{"default:cobble", "default:glass","default:cobble"},
}
})
minetest.register_craft({
output = 'node "railcarts:autolauncher" 2',
recipe = {
{"default:cobble", "default:mese_crystal_fragment","default:cobble"},
{"default:mese_crystal_fragment", "default:chest", "default:mese_crystal_fragment"},
{"default:cobble", "default:mese_crystal_fragment","default:cobble"},
}
})
minetest.register_craft({
output = 'node "railcarts:launcher_off" 2',
recipe = {
{"default:cobble", "default:mese_crystal_fragment","default:cobble"},
{"default:mese_crystal_fragment", "default:sand", "default:mese_crystal_fragment"},
{"default:cobble", "default:mese_crystal_fragment","default:cobble"},
}
})
minetest.register_node("railcarts:launcher_off", {
description = "Cart Launcher",
tiles ={"railcarts_launcher_off.png"},
is_ground_content = true,
groups = {cracky=3, mesecon=2},
mesecons = { conductor = {
state = "off",
onstate = "railcarts:launcher_on",
} }
})
minetest.register_node("railcarts:launcher_on", {
description = "Cart Launcher",
tiles ={"railcarts_launcher_on.png"},
is_ground_content = true,
groups = {cracky=3, mesecon=2},
drop = 'railcarts:launcher_off 1',
mesecons = { conductor = {
state = "on",
offstate = "railcarts:launcher_off",
} }
})
minetest.register_node("railcarts:autolauncher", {
description = "Cart Autolauncher",
tiles ={"railcarts_autolauncher.png"},
is_ground_content = true,
groups = {cracky=3},
drop = 'railcarts:autolauncher 1',
on_construct = function(pos)
local meta = minetest.get_meta(pos)
meta:set_string("formspec",
"size[8,8]"..
"list[current_name;main;0,0;8,3;]"..
"list[current_player;main;0,4;8,4;]")
meta:set_string("infotext", "Autolauncher")
local inv = meta:get_inventory()
inv:set_size("main", 24)
end,
can_dig = function(pos,player)
local meta = minetest.get_meta(pos);
local inv = meta:get_inventory()
return inv:is_empty("main")
end,
})
minetest.register_abm({
nodenames = {"railcarts:autolauncher"},
neighbors = {},
interval = 5.0,
chance = 1,
action =
function(pos, node, active_object_count, active_object_count_wider)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
tests = {{x=pos.x+1,y=pos.y,z=pos.z},
{x=pos.x-1,y=pos.y,z=pos.z},
{x=pos.x,y=pos.y,z=pos.z+1},
{x=pos.x,y=pos.y,z=pos.z-1},
{x=pos.x,y=pos.y-1,z=pos.z}}
for _, testpos in ipairs(tests) do
if is_rail(minetest.get_node(testpos)) then
-- Check there's not a cart (or anything else, like a mob) in the way
-- TODO: I think the block can be loaded, but the objects not active,
-- in which case this will drop another cart on top of an existing
-- one ad infinitum - needs fixing!
already = minetest.get_objects_inside_radius(testpos, 1)
if table.maxn(already) == 0 then
-- Get a cart from the autolauncher's inventory
local stack = inv:remove_item("main", ItemStack("railcarts:cart"))
local placecart = "railcarts:cart_ent"
if stack:is_empty() then
stack = inv:remove_item("main", ItemStack("railcarts:cargo_cart"))
if stack:is_empty() then
stack = inv:remove_item("main", ItemStack("railcarts:boring_cart"))
if stack:is_empty() then
dbg.v1("Autolauncher at "..postostr(pos).." is empty")
return
end
placecart = "railcarts:boring_cart_ent"
else
placecart = "railcarts:cargo_cart_ent"
end
end
dbg.v1("Autolauncher placing "..placecart.." at"..postostr(testpos))
local object = minetest.add_entity(testpos, placecart)
if object then
local ent = object:get_luaentity()
local newdir = xz_to_direction({x=testpos.x-pos.x, z=testpos.z-pos.z})
current_state = {direction=newdir}
cartbase.setdirection(ent, current_state, newdir)
ent.direction = newdir
end
end
end
end
end,
})

BIN
models/cart.blend Normal file

Binary file not shown.

BIN
models/railcarts_cart.b3d Normal file

Binary file not shown.

339
models/railcarts_cart.x Normal file
View File

@ -0,0 +1,339 @@
xof 0303txt 0032
Frame Root {
FrameTransformMatrix {
1.000000, 0.000000, 0.000000, 0.000000,
0.000000, 0.000000, 1.000000, 0.000000,
0.000000, 1.000000,-0.000000, 0.000000,
0.000000, 0.000000, 0.000000, 1.000000;;
}
Frame Cube {
FrameTransformMatrix {
5.000000, 0.000000,-0.000000, 0.000000,
-0.000000, 3.535534, 3.535534, 0.000000,
0.000000,-3.535534, 3.535534, 0.000000,
0.000000,-3.000000, 3.000000, 1.000000;;
}
Mesh { //Cube_001 Mesh
72;
-1.000000; 1.000000;-1.000000;,
-1.000000;-1.000000;-1.000000;,
1.000000;-1.000000;-1.000000;,
1.000000; 1.000000;-1.000000;,
-0.833334;-1.000000; 1.000000;,
-1.000000;-1.000000; 1.000000;,
-1.000000;-0.833333; 1.000000;,
-0.833334;-0.833333; 1.000000;,
-1.000000;-1.000000;-1.000000;,
-1.000000;-1.000000; 1.000000;,
0.999999;-1.000001; 1.000000;,
1.000000;-1.000000;-1.000000;,
0.999999;-1.000001; 1.000000;,
0.833332;-1.000000; 1.000000;,
0.833333;-0.833334; 1.000000;,
1.000000;-0.833334; 1.000000;,
0.833332;-1.000000; 1.000000;,
-0.833334;-1.000000; 1.000000;,
-0.833334;-0.833333; 1.000000;,
0.833333;-0.833334; 1.000000;,
1.000000; 0.833333; 1.000000;,
0.833334; 0.833333; 1.000000;,
0.833334; 1.000000; 1.000000;,
1.000000; 0.999999; 1.000000;,
1.000000;-0.833334; 1.000000;,
0.833333;-0.833334; 1.000000;,
0.833334; 0.833333; 1.000000;,
1.000000; 0.833333; 1.000000;,
0.833334; 0.833333; 1.000000;,
-0.833333; 0.833333; 1.000000;,
-0.833333; 1.000000; 1.000000;,
0.833334; 1.000000; 1.000000;,
0.833334; 0.833333;-0.800000;,
-0.833333; 0.833333;-0.800000;,
-0.833333; 0.833333; 1.000000;,
0.833334; 0.833333; 1.000000;,
-0.833333; 0.833333; 1.000000;,
-1.000000; 0.833333; 1.000000;,
-1.000000; 1.000000; 1.000000;,
-0.833333; 1.000000; 1.000000;,
-0.833334;-0.833333; 1.000000;,
-1.000000;-0.833333; 1.000000;,
-1.000000; 0.833333; 1.000000;,
-0.833333; 0.833333; 1.000000;,
0.833333;-0.833334;-0.800000;,
-0.833334;-0.833333;-0.800000;,
-0.833333; 0.833333;-0.800000;,
0.833334; 0.833333;-0.800000;,
-0.833333; 0.833333;-0.800000;,
-0.833334;-0.833333;-0.800000;,
-0.833334;-0.833333; 1.000000;,
-0.833333; 0.833333; 1.000000;,
-0.833334;-0.833333;-0.800000;,
0.833333;-0.833334;-0.800000;,
0.833333;-0.833334; 1.000000;,
-0.833334;-0.833333; 1.000000;,
0.833333;-0.833334;-0.800000;,
0.833334; 0.833333;-0.800000;,
0.833334; 0.833333; 1.000000;,
0.833333;-0.833334; 1.000000;,
-1.000000; 1.000000;-1.000000;,
-1.000000; 1.000000; 1.000000;,
-1.000000;-1.000000; 1.000000;,
-1.000000;-1.000000;-1.000000;,
-1.000000; 1.000000; 1.000000;,
-1.000000; 1.000000;-1.000000;,
1.000000; 1.000000;-1.000000;,
1.000000; 0.999999; 1.000000;,
1.000000;-1.000000;-1.000000;,
0.999999;-1.000001; 1.000000;,
1.000000; 0.999999; 1.000000;,
1.000000; 1.000000;-1.000000;;
18;
4;0;1;2;3;,
4;4;5;6;7;,
4;8;9;10;11;,
4;12;13;14;15;,
4;16;17;18;19;,
4;20;21;22;23;,
4;24;25;26;27;,
4;28;29;30;31;,
4;32;33;34;35;,
4;36;37;38;39;,
4;40;41;42;43;,
4;44;45;46;47;,
4;48;49;50;51;,
4;52;53;54;55;,
4;56;57;58;59;,
4;60;61;62;63;,
4;64;65;66;67;,
4;68;69;70;71;;
MeshNormals { //Cube_001 Normals
72;
0.000000; 0.000000;-1.000000;,
0.000000; 0.000000;-1.000000;,
0.000000; 0.000000;-1.000000;,
0.000000; 0.000000;-1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
-0.000000;-1.000000;-0.000000;,
-0.000000;-1.000000;-0.000000;,
-0.000000;-1.000000;-0.000000;,
-0.000000;-1.000000;-0.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
-0.000000;-1.000000; 0.000000;,
-0.000000;-1.000000; 0.000000;,
-0.000000;-1.000000; 0.000000;,
-0.000000;-1.000000; 0.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
1.000000;-0.000000; 0.000000;,
1.000000;-0.000000; 0.000000;,
1.000000;-0.000000; 0.000000;,
1.000000;-0.000000; 0.000000;,
0.000000; 1.000000; 0.000000;,
0.000000; 1.000000; 0.000000;,
0.000000; 1.000000; 0.000000;,
0.000000; 1.000000; 0.000000;,
-1.000000; 0.000000; 0.000000;,
-1.000000; 0.000000; 0.000000;,
-1.000000; 0.000000; 0.000000;,
-1.000000; 0.000000; 0.000000;,
-1.000000; 0.000000;-0.000000;,
-1.000000; 0.000000;-0.000000;,
-1.000000; 0.000000;-0.000000;,
-1.000000; 0.000000;-0.000000;,
0.000000; 1.000000; 0.000000;,
0.000000; 1.000000; 0.000000;,
0.000000; 1.000000; 0.000000;,
0.000000; 1.000000; 0.000000;,
1.000000;-0.000000; 0.000000;,
1.000000;-0.000000; 0.000000;,
1.000000;-0.000000; 0.000000;,
1.000000;-0.000000; 0.000000;;
18;
4;0;1;2;3;,
4;4;5;6;7;,
4;8;9;10;11;,
4;12;13;14;15;,
4;16;17;18;19;,
4;20;21;22;23;,
4;24;25;26;27;,
4;28;29;30;31;,
4;32;33;34;35;,
4;36;37;38;39;,
4;40;41;42;43;,
4;44;45;46;47;,
4;48;49;50;51;,
4;52;53;54;55;,
4;56;57;58;59;,
4;60;61;62;63;,
4;64;65;66;67;,
4;68;69;70;71;;
} //End of Cube_001 Normals
MeshMaterialList { //Cube_001 Material List
1;
18;
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0;;
Material Material {
0.640000; 0.640000; 0.640000; 1.000000;;
96.078431;
0.500000; 0.500000; 0.500000;;
0.000000; 0.000000; 0.000000;;
TextureFilename {"cart.png";}
}
} //End of Cube_001 Material List
MeshTextureCoords { //Cube_001 UV Coordinates
72;
0.000000; 0.500000;,
0.500000; 0.500000;,
0.500000; 1.000000;,
0.000000; 1.000000;,
0.031250; 0.500000;,
-0.000000; 0.500000;,
-0.000000; 0.468750;,
0.031250; 0.468750;,
0.500000; 0.500000;,
0.500000; 0.000000;,
1.000000; 0.000000;,
1.000000; 0.500000;,
0.468750; 0.468750;,
0.500000; 0.468750;,
0.500000; 0.500000;,
0.468750; 0.500000;,
0.031250; 0.468750;,
0.468750; 0.468750;,
0.468750; 0.500000;,
0.031250; 0.500000;,
0.468750; 0.000000;,
0.500000; 0.000000;,
0.500000; 0.031250;,
0.468750; 0.031250;,
0.468750; 0.031250;,
0.500000; 0.031250;,
0.500000; 0.468750;,
0.468750; 0.468750;,
0.468750; 0.031250;,
0.031250; 0.031250;,
0.031250; 0.000000;,
0.468750; 0.000000;,
1.000000; 0.500000;,
0.500000; 0.500000;,
0.500000; 0.000000;,
1.000000; 0.000000;,
0.031250; 0.031250;,
0.000000; 0.031250;,
0.000000; 0.000000;,
0.031250; 0.000000;,
0.031250; 0.468750;,
-0.000000; 0.468750;,
0.000000; 0.031250;,
0.031250; 0.031250;,
0.000000; 0.500000;,
0.500000; 0.500000;,
0.500000; 1.000000;,
0.000000; 1.000000;,
1.000000; 0.500000;,
0.500000; 0.500000;,
0.500000; 0.000000;,
1.000000; 0.000000;,
1.000000; 0.500000;,
0.500000; 0.500000;,
0.500000; 0.000000;,
1.000000; 0.000000;,
1.000000; 0.500000;,
0.500000; 0.500000;,
0.500000; 0.000000;,
1.000000; 0.000000;,
0.500000; 0.500000;,
0.500000; 0.000000;,
1.000000; 0.000000;,
1.000000; 0.500000;,
1.000000; 0.000000;,
1.000000; 0.500000;,
0.500000; 0.500000;,
0.500000; 0.000000;,
0.500000; 0.500000;,
0.500000; 0.000000;,
1.000000; 0.000000;,
1.000000; 0.500000;;
} //End of Cube_001 UV Coordinates
} //End of Cube_001 Mesh
} //End of Cube
} //End of Root Frame
AnimationSet {
Animation {
{Cube}
AnimationKey { //Position
2;
4;
0;3; 0.000000, 0.000000, 0.000000;;,
1;3; 0.000000, 3.000000, 3.000000;;,
2;3; 0.000000,-3.000000, 3.000000;;,
3;3; 0.000000,-3.000000, 3.000000;;;
}
AnimationKey { //Rotation
0;
4;
0;4; -1.000000, 0.000000, 0.000000, 0.000000;;,
1;4; -0.923880,-0.382683,-0.000000, 0.000000;;,
2;4; -0.923880, 0.382683, 0.000000, 0.000000;;,
3;4; -0.923880, 0.382683, 0.000000, 0.000000;;;
}
AnimationKey { //Scale
1;
4;
0;3; 5.000000, 5.000000, 5.000000;;,
1;3; 5.000000, 5.000000, 5.000000;;,
2;3; 5.000000, 5.000000, 5.000000;;,
3;3; 5.000000, 5.000000, 5.000000;;;
}
}
} //End of AnimationSet

Binary file not shown.

View File

@ -0,0 +1,339 @@
xof 0303txt 0032
Frame Root {
FrameTransformMatrix {
1.000000, 0.000000, 0.000000, 0.000000,
0.000000, 0.000000, 1.000000, 0.000000,
0.000000, 1.000000,-0.000000, 0.000000,
0.000000, 0.000000, 0.000000, 1.000000;;
}
Frame Cube {
FrameTransformMatrix {
5.000000, 0.000000,-0.000000, 0.000000,
-0.000000, 3.535534, 3.535534, 0.000000,
0.000000,-3.535534, 3.535534, 0.000000,
0.000000,-3.000000, 3.000000, 1.000000;;
}
Mesh { //Cube_001 Mesh
72;
-1.000000; 1.000000;-1.000000;,
-1.000000;-1.000000;-1.000000;,
1.000000;-1.000000;-1.000000;,
1.000000; 1.000000;-1.000000;,
-0.833334;-1.000000; 1.000000;,
-1.000000;-1.000000; 1.000000;,
-1.000000;-0.833333; 1.000000;,
-0.833334;-0.833333; 1.000000;,
-1.000000;-1.000000;-1.000000;,
-1.000000;-1.000000; 1.000000;,
0.999999;-1.000001; 1.000000;,
1.000000;-1.000000;-1.000000;,
0.999999;-1.000001; 1.000000;,
0.833332;-1.000000; 1.000000;,
0.833333;-0.833334; 1.000000;,
1.000000;-0.833334; 1.000000;,
0.833332;-1.000000; 1.000000;,
-0.833334;-1.000000; 1.000000;,
-0.833334;-0.833333; 1.000000;,
0.833333;-0.833334; 1.000000;,
1.000000; 0.833333; 1.000000;,
0.833334; 0.833333; 1.000000;,
0.833334; 1.000000; 1.000000;,
1.000000; 0.999999; 1.000000;,
1.000000;-0.833334; 1.000000;,
0.833333;-0.833334; 1.000000;,
0.833334; 0.833333; 1.000000;,
1.000000; 0.833333; 1.000000;,
0.833334; 0.833333; 1.000000;,
-0.833333; 0.833333; 1.000000;,
-0.833333; 1.000000; 1.000000;,
0.833334; 1.000000; 1.000000;,
0.833334; 0.833333;-0.800000;,
-0.833333; 0.833333;-0.800000;,
-0.833333; 0.833333; 1.000000;,
0.833334; 0.833333; 1.000000;,
-0.833333; 0.833333; 1.000000;,
-1.000000; 0.833333; 1.000000;,
-1.000000; 1.000000; 1.000000;,
-0.833333; 1.000000; 1.000000;,
-0.833334;-0.833333; 1.000000;,
-1.000000;-0.833333; 1.000000;,
-1.000000; 0.833333; 1.000000;,
-0.833333; 0.833333; 1.000000;,
0.833333;-0.833334;-0.800000;,
-0.833334;-0.833333;-0.800000;,
-0.833333; 0.833333;-0.800000;,
0.833334; 0.833333;-0.800000;,
-0.833333; 0.833333;-0.800000;,
-0.833334;-0.833333;-0.800000;,
-0.833334;-0.833333; 1.000000;,
-0.833333; 0.833333; 1.000000;,
-0.833334;-0.833333;-0.800000;,
0.833333;-0.833334;-0.800000;,
0.833333;-0.833334; 1.000000;,
-0.833334;-0.833333; 1.000000;,
0.833333;-0.833334;-0.800000;,
0.833334; 0.833333;-0.800000;,
0.833334; 0.833333; 1.000000;,
0.833333;-0.833334; 1.000000;,
-1.000000; 1.000000;-1.000000;,
-1.000000; 1.000000; 1.000000;,
-1.000000;-1.000000; 1.000000;,
-1.000000;-1.000000;-1.000000;,
-1.000000; 1.000000; 1.000000;,
-1.000000; 1.000000;-1.000000;,
1.000000; 1.000000;-1.000000;,
1.000000; 0.999999; 1.000000;,
1.000000;-1.000000;-1.000000;,
0.999999;-1.000001; 1.000000;,
1.000000; 0.999999; 1.000000;,
1.000000; 1.000000;-1.000000;;
18;
4;0;1;2;3;,
4;4;5;6;7;,
4;8;9;10;11;,
4;12;13;14;15;,
4;16;17;18;19;,
4;20;21;22;23;,
4;24;25;26;27;,
4;28;29;30;31;,
4;32;33;34;35;,
4;36;37;38;39;,
4;40;41;42;43;,
4;44;45;46;47;,
4;48;49;50;51;,
4;52;53;54;55;,
4;56;57;58;59;,
4;60;61;62;63;,
4;64;65;66;67;,
4;68;69;70;71;;
MeshNormals { //Cube_001 Normals
72;
0.000000; 0.000000;-1.000000;,
0.000000; 0.000000;-1.000000;,
0.000000; 0.000000;-1.000000;,
0.000000; 0.000000;-1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
-0.000000;-1.000000;-0.000000;,
-0.000000;-1.000000;-0.000000;,
-0.000000;-1.000000;-0.000000;,
-0.000000;-1.000000;-0.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
-0.000000;-1.000000; 0.000000;,
-0.000000;-1.000000; 0.000000;,
-0.000000;-1.000000; 0.000000;,
-0.000000;-1.000000; 0.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
0.000000;-0.000000; 1.000000;,
1.000000;-0.000000; 0.000000;,
1.000000;-0.000000; 0.000000;,
1.000000;-0.000000; 0.000000;,
1.000000;-0.000000; 0.000000;,
0.000000; 1.000000; 0.000000;,
0.000000; 1.000000; 0.000000;,
0.000000; 1.000000; 0.000000;,
0.000000; 1.000000; 0.000000;,
-1.000000; 0.000000; 0.000000;,
-1.000000; 0.000000; 0.000000;,
-1.000000; 0.000000; 0.000000;,
-1.000000; 0.000000; 0.000000;,
-1.000000; 0.000000;-0.000000;,
-1.000000; 0.000000;-0.000000;,
-1.000000; 0.000000;-0.000000;,
-1.000000; 0.000000;-0.000000;,
0.000000; 1.000000; 0.000000;,
0.000000; 1.000000; 0.000000;,
0.000000; 1.000000; 0.000000;,
0.000000; 1.000000; 0.000000;,
1.000000;-0.000000; 0.000000;,
1.000000;-0.000000; 0.000000;,
1.000000;-0.000000; 0.000000;,
1.000000;-0.000000; 0.000000;;
18;
4;0;1;2;3;,
4;4;5;6;7;,
4;8;9;10;11;,
4;12;13;14;15;,
4;16;17;18;19;,
4;20;21;22;23;,
4;24;25;26;27;,
4;28;29;30;31;,
4;32;33;34;35;,
4;36;37;38;39;,
4;40;41;42;43;,
4;44;45;46;47;,
4;48;49;50;51;,
4;52;53;54;55;,
4;56;57;58;59;,
4;60;61;62;63;,
4;64;65;66;67;,
4;68;69;70;71;;
} //End of Cube_001 Normals
MeshMaterialList { //Cube_001 Material List
1;
18;
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0;;
Material Material {
0.640000; 0.640000; 0.640000; 1.000000;;
96.078431;
0.500000; 0.500000; 0.500000;;
0.000000; 0.000000; 0.000000;;
TextureFilename {"cart.png";}
}
} //End of Cube_001 Material List
MeshTextureCoords { //Cube_001 UV Coordinates
72;
0.000000; 0.500000;,
0.500000; 0.500000;,
0.500000; 1.000000;,
0.000000; 1.000000;,
0.031250; 0.500000;,
-0.000000; 0.500000;,
-0.000000; 0.468750;,
0.031250; 0.468750;,
0.500000; 0.500000;,
0.500000; 0.000000;,
1.000000; 0.000000;,
1.000000; 0.500000;,
0.468750; 0.468750;,
0.500000; 0.468750;,
0.500000; 0.500000;,
0.468750; 0.500000;,
0.031250; 0.468750;,
0.468750; 0.468750;,
0.468750; 0.500000;,
0.031250; 0.500000;,
0.468750; 0.000000;,
0.500000; 0.000000;,
0.500000; 0.031250;,
0.468750; 0.031250;,
0.468750; 0.031250;,
0.500000; 0.031250;,
0.500000; 0.468750;,
0.468750; 0.468750;,
0.468750; 0.031250;,
0.031250; 0.031250;,
0.031250; 0.000000;,
0.468750; 0.000000;,
1.000000; 0.500000;,
0.500000; 0.500000;,
0.500000; 0.000000;,
1.000000; 0.000000;,
0.031250; 0.031250;,
0.000000; 0.031250;,
0.000000; 0.000000;,
0.031250; 0.000000;,
0.031250; 0.468750;,
-0.000000; 0.468750;,
0.000000; 0.031250;,
0.031250; 0.031250;,
0.000000; 0.500000;,
0.500000; 0.500000;,
0.500000; 1.000000;,
0.000000; 1.000000;,
1.000000; 0.500000;,
0.500000; 0.500000;,
0.500000; 0.000000;,
1.000000; 0.000000;,
1.000000; 0.500000;,
0.500000; 0.500000;,
0.500000; 0.000000;,
1.000000; 0.000000;,
1.000000; 0.500000;,
0.500000; 0.500000;,
0.500000; 0.000000;,
1.000000; 0.000000;,
0.500000; 0.500000;,
0.500000; 0.000000;,
1.000000; 0.000000;,
1.000000; 0.500000;,
1.000000; 0.000000;,
1.000000; 0.500000;,
0.500000; 0.500000;,
0.500000; 0.000000;,
0.500000; 0.500000;,
0.500000; 0.000000;,
1.000000; 0.000000;,
1.000000; 0.500000;;
} //End of Cube_001 UV Coordinates
} //End of Cube_001 Mesh
} //End of Cube
} //End of Root Frame
AnimationSet {
Animation {
{Cube}
AnimationKey { //Position
2;
4;
0;3; 0.000000, 0.000000, 0.000000;;,
1;3; 0.000000, 3.000000, 3.000000;;,
2;3; 0.000000,-3.000000, 3.000000;;,
3;3; 0.000000,-3.000000, 3.000000;;;
}
AnimationKey { //Rotation
0;
4;
0;4; -1.000000, 0.000000, 0.000000, 0.000000;;,
1;4; -0.923880,-0.382683,-0.000000, 0.000000;;,
2;4; -0.923880, 0.382683, 0.000000, 0.000000;;,
3;4; -0.923880, 0.382683, 0.000000, 0.000000;;;
}
AnimationKey { //Scale
1;
4;
0;3; 5.000000, 5.000000, 5.000000;;,
1;3; 5.000000, 5.000000, 5.000000;;,
2;3; 5.000000, 5.000000, 5.000000;;,
3;3; 5.000000, 5.000000, 5.000000;;;
}
}
} //End of AnimationSet

BIN
models/transport_cart.blend Normal file

Binary file not shown.

513
rail.lua Normal file
View File

@ -0,0 +1,513 @@
local dbg
if moddebug then dbg=moddebug.dbg("railcarts") else dbg={v1=function() end,v2=function() end,v3=function() end} end
--- Determine whether the given node is a rail.
-- @param node The node to check (which can be nil)
-- @return true if it's a rail
function is_rail(node)
if node == nil then return false end
name = node.name
if name == "default:rail" or
name == "railcarts:controlrail" or
name == "railcarts:controlrail_off" or
name == "railcarts:digicontrol" or
name == "railcarts:rail_switched" then
return true
end
return false
end
--- Determine whether a railtype is 'unloaded'
-- @param railtype A railtype (as per get_rail_status)
-- @return true if unloaded
function is_unloaded(railtype)
if railtype == "unloaded" then return true end
if railtype == "unloaded-near" then return true end
return false
end
---Determine if a node is a grabber of carts
--@param node The node to check (which can be nil)
--@return True if it is
function is_grabber(node)
if node == nil then return false end
if node.name == "railcarts:autolauncher" then
return true
end
return false
end
---Get rail control information coming from a node.
-- Only switched on control rails are considered, since if they're switched
-- off they should behave exactly like a normal rail.
--@param node The node to check (which can be nil)
--@param pos The position to check
--@param status The status to update.
--The control field might be added, containing any of the specifications
--as described in the README.
-- The digiline field might be set to the pos that should send a signal
function get_railcontrol(node, pos, status)
if node and (node.name == "railcarts:controlrail"
or node.name == "railcarts:digicontrol") then
local meta = minetest.get_meta(pos)
if meta then
local it = meta:get_string("infotext")
if it and it ~= "" then
status.control = it
end
end
if node.name == "railcarts:digicontrol" then
status.digiline = pos
end
end
end
--- Determine if a node is a launcher
-- @param node The node (which can be nil)
-- @return True if it is
function is_launcher(node)
if node == nil then return false end
if node.name == "railcarts:launcher_on" then
return true
end
return false
end
--- Determine if a node is an autolauncher
-- @param node The node (which can be nil)
-- @return True if it is
function is_autolauncher(node)
if node == nil then return false end
if node.name == "railcarts:autolauncher" then
return true
end
return false
end
--- Get status of rails
-- @param fppos The position to check at
-- @param direction Movement direction (can be nil, in which case some checks
-- are not possible).
-- @param speed Movement speed (only relevant if direction is not nil)
-- checks can be completed.
-- @return A table which always contains railtype. One of:
-- "inv" - invalid position
-- "unloaded" or "unloaded-near" - entering as-yet-unloaded territory
-- use is_unloaded() to check this!
-- "x", "z" - straight track in that direction (can be sloped)
-- "x+", x-", "z+", "z-" - curve (see check_curve ireturn value)
-- It can also optionally contain:
-- unloadedpos, when railtype is unloaded or unloaded-near, is the position
-- that caused the unloaded status
-- slope, if the rail is sloped, the up direction, 0-3 (and railtype can only
-- be "x" or "z"
-- eject, which if true means any player in the cart should be
-- grab, which if it exists is the position of a node which should 'grab' the
-- cart into its inventory (i.e. an autolauncher)
-- launch, which is a launch direction
-- autolaunch, which is an autolaunch direction
-- control, usually nil, otherwise control information coming from the current
-- rail - values as per get_controlrail()
-- onautolauncher, which if true means there is an autolauncher below the rails
-- detectors, if present, is the pos for a cart detector to trigger
-- hopper, if present, is the pos for a hopper above the cart
-- only detected when stationary
function get_railstatus(fppos, direction, speed)
if fppos == nil then
dbg.v2("get_railstatus for nil pos!?")
return {railtype="inv"}
end
-- Use rounded position for all lookups and calculations in here
pos = v3posround(fppos)
local current_node = minetest.get_node(pos)
if current_node.name == "ignore" then
return {railtype="unloaded", unloadedpos=pos}
end
if not is_rail(current_node) then
return {railtype="inv"}
end
status = {}
-- Get all surrounding nodes. This builds a little cache of all the
-- surrounding nodes we might need to look at.
-- TODO: make this lazily get positions only when requested, because
-- sometimes we don't need them all
-- TODO: make this shared across a whole step (because getrailstatus
-- can be called twice, with overlap, within a step)
-- BUT!!! digirail triggers, mid-step, can change the rails,
-- although that shouldn't matter - but think about it!
-- TODO: make it less messy!
-- TODO: we're only storing the position for reading it back to debug
-- the 'unloaded' problem, we could lose it when that's fixed
local getsur = function(name, pos, surrounds)
surrounds[name] = minetest.get_node(pos)
surrounds[name.."_pos"] = pos
end
surrounds = {}
getsur("x_prev", {x=pos.x-1,y=pos.y,z=pos.z}, surrounds)
getsur("x_next", {x=pos.x+1,y=pos.y,z=pos.z}, surrounds)
getsur("z_prev", {x=pos.x,y=pos.y,z=pos.z-1}, surrounds)
getsur("z_next", {x=pos.x,y=pos.y,z=pos.z+1}, surrounds)
getsur("x_prev_above", {x=pos.x-1,y=pos.y+1,z=pos.z}, surrounds)
getsur("x_next_above", {x=pos.x+1,y=pos.y+1,z=pos.z}, surrounds)
getsur("z_prev_above", {x=pos.x,y=pos.y+1,z=pos.z-1}, surrounds)
getsur("z_next_above", {x=pos.x,y=pos.y+1,z=pos.z+1}, surrounds)
getsur("x_prev_below", {x=pos.x-1,y=pos.y-1,z=pos.z}, surrounds)
getsur("x_next_below", {x=pos.x+1,y=pos.y-1,z=pos.z}, surrounds)
getsur("z_prev_below", {x=pos.x,y=pos.y-1,z=pos.z-1}, surrounds)
getsur("z_next_below", {x=pos.x,y=pos.y-1,z=pos.z+1}, surrounds)
getsur("below", {x=pos.x,y=pos.y-1,z=pos.z}, surrounds)
getsur("above", {x=pos.x,y=pos.y+1,z=pos.z}, surrounds)
-- We need to know what all the surrounding blocks are to be able to move
-- properly (e.g. consider a curve or switch on a block boundary) so if
-- we don't have that information we need to wait.
for k, nn in pairs(surrounds) do
if string.sub(k, -4) ~= "_pos" then
if nn.name == "ignore" then
return {railtype="unloaded-near", unloadedpos=surrounds[k.."_pos"]}
end
end
end
get_railcontrol(current_node, pos, status)
local railtype
-- Check for slopes. The order of these is the same as the order of
-- priority used by the rail drawing code (as determined by experimenation)
if is_rail(surrounds.x_next_above) then
railtype = "x"
status.slope=1
elseif is_rail(surrounds.x_prev_above) then
railtype = "x"
status.slope=3
elseif is_rail(surrounds.z_prev_above) then
railtype = "z"
status.slope=2
elseif is_rail(surrounds.z_next_above) then
railtype = "z"
status.slope=0
end
-- Check for a crossing
if not railtype then
railtype = check_crossing(direction, surrounds)
end
-- Check for a switch
if (not railtype) and direction then
railtype = check_switch(current_node, pos, direction, surrounds)
end
-- Check for a curve
if not railtype then
railtype = check_curve(surrounds)
end
-- There can only be straight rails left
if (not railtype) and (is_rail(surrounds.x_prev) or is_rail(surrounds.x_prev_below) or
is_rail(surrounds.x_next) or is_rail(surrounds.x_next_below)) then
railtype = "x"
end
if (not railtype) and (is_rail(surrounds.z_prev) or is_rail(surrounds.z_prev_below) or
is_rail(surrounds.z_next) or is_rail(surrounds.z_next_below)) then
railtype = "z"
end
if not railtype then
railtype = "inv"
else
-- Check for hoppers
if is_hopper(surrounds.above) then
status.hopper = {x=pos.x,y=pos.y+1,z=pos.z}
end
if direction then
-- Check if a player should be ejected
if speed > 0 then
if direction == 1 and is_ejector(surrounds.x_next_above) and is_rail(surrounds.x_next) then
status.eject = true
elseif direction == 3 and is_ejector(surrounds.x_prev_above) and is_rail(surrounds.x_prev) then
status.eject = true
elseif direction == 0 and is_ejector(surrounds.z_next_above) and is_rail(surrounds.z_next) then
status.eject = true
elseif direction == 2 and is_ejector(surrounds.z_prev_above) and is_rail(surrounds.z_prev) then
status.eject = true
end
-- Check for detectors - below the cart, or to the left
local detpos
if is_detector(surrounds.below) then
detpos = {x=pos.x,y=pos.y-1,z=pos.z}
else
if direction == 0 and is_detector(surrounds.x_prev) then
detpos = {x=pos.x-1,y=pos.y,z=pos.z}
elseif direction == 1 and is_detector(surrounds.z_next) then
detpos = {x=pos.x,y=pos.y,z=pos.z+1}
elseif direction == 2 and is_detector(surrounds.x_next) then
detpos = {x=pos.x+1,y=pos.y,z=pos.z}
elseif direction == 1 and is_detector(surrounds.z_prev) then
detpos = {x=pos.x,y=pos.y,z=pos.z-1}
end
end
if detpos then
status.detector = detpos
end
end
if speed == 0 then
-- Check if the cart should be grabbed
if is_grabber(surrounds.x_next_above) then
status.grab = {x=pos.x+1,y=pos.y+1,z=pos.z}
elseif is_grabber(surrounds.x_prev_above) then
status.grab = {x=pos.x-1,y=pos.y+1,z=pos.z}
elseif is_grabber(surrounds.z_prev_above) then
status.grab = {x=pos.x,y=pos.y+1,z=pos.z-1}
elseif is_grabber(surrounds.z_next_above) then
status.grab = {x=pos.x,y=pos.y+1,z=pos.z+1}
elseif is_grabber(surrounds.below) then
status.grab = {x=pos.x,y=pos.y-1,z=pos.z}
end
-- Check for being next to a launcher (only stationary carts)
check_launcher(surrounds, status)
end
end
end
if surrounds.below and surrounds.below.name == "railcarts:autolauncher" then
status.onautolauncher = true
end
status.railtype = railtype
return status
end
--- Check if the given node should cause player ejection.
-- This is currently any walkable node.
-- @param node The node to check (can be nil)
function is_ejector(node)
if not node then return false end
nd = minetest.registered_nodes[node.name]
return nd.walkable
end
--- Check if the given node is a cart detector.
-- Only unactivated detectors are relevant.
-- @param node The node to check (can be nil)
function is_detector(node)
if not node then return false end
return node.name == "railcarts:cart_detector"
end
--- Check if the given node is a hopper.
-- @param node The node to check (can be nil)
function is_hopper(node)
if not node then return false end
return node.name == "railcarts:hopper"
end
--- Check for a launcher adjacent to a position, with a rail in the other
-- direction.
-- @param surrounds The surrounding nodes
-- @param result, into which launch and autolaunch fields are inserted if
-- necessary
function check_launcher(surrounds, result)
if is_rail(surrounds.x_prev) or is_rail(surrounds.x_prev_below) then
if is_launcher(surrounds.x_next) then result.launch = 3 end
if is_autolauncher(surrounds.x_next) then result.autolaunch = 3 end
end
if is_rail(surrounds.x_next) or is_rail(surrounds.x_next_below) then
if is_launcher(surrounds.x_prev) then result.launch = 1 end
if is_autolauncher(surrounds.x_prev) then result.autolaunch = 1 end
end
if is_rail(surrounds.z_prev) or is_rail(surrounds.z_prev_below) then
if is_launcher(surrounds.z_next) then result.launch = 2 end
if is_autolauncher(surrounds.z_next) then result.autolaunch = 2 end
end
if is_rail(surrounds.z_next) or is_rail(surrounds.z_next_below) then
if is_launcher(surrounds.z_prev) then result.launch = 0 end
if is_autolauncher(surrounds.z_prev) then result.autolaunch = 0 end
end
end
--- Check for a crossing at the given location.
-- @param direction The cart direction
-- @param surrounds The surrounding nodes
--
-- @return If there's no corssing at the current position, nil is returned.
-- Otherwise, "x" or "z" is returned.
function check_crossing(direction, surrounds)
if (is_rail(surrounds.z_prev) or is_rail(surrounds.z_prev_below)) and
(is_rail(surrounds.z_next) or is_rail(surrounds.z_next_below)) and
(is_rail(surrounds.x_prev) or is_rail(surrounds.x_prev_below)) and
(is_rail(surrounds.x_next) or is_rail(surrounds.x_next_below)) then
if direction == 1 or direction == 3 then
return "x"
else
return "z"
end
end
return nil
end
--- Check for a switch at the given location.
-- @param node The node to check at
-- @param pos The position of the node
-- @param direction The cart direction
-- @param surrounds The surrounding nodes
--
-- @return If there's no switch at the current position, nil is returned.
-- Otherwise, "x" or "z" is returned for a switch which is in a straight
-- configuration (according to the current direction), and for a curve
-- one of the "x+", "x-", "z+" or "z-" curve designations, as returned
-- by check_curve.
function check_switch(node, pos, direction, surrounds)
-- junction : Z-, Z+, X+
if (is_rail(surrounds.z_prev) or is_rail(surrounds.z_prev_below)) and
(is_rail(surrounds.z_next) or is_rail(surrounds.z_next_below)) and
(is_rail(surrounds.x_next) or is_rail(surrounds.x_next_below)) then
if node.name == "railcarts:rail_switched" then
if direction == 0 then
return "z"
else
return "z+"
end
else
if direction == 2 then
return "z"
else
return "x+"
end
end
end
-- junction : Z-, Z+, X-
if (is_rail(surrounds.z_prev) or is_rail(surrounds.z_prev_below)) and
(is_rail(surrounds.z_next) or is_rail(surrounds.z_next_below)) and
(is_rail(surrounds.x_prev) or is_rail(surrounds.x_prev_below)) then
if node.name == "railcarts:rail_switched" then
if direction == 2 then
return "z"
else
return "x-"
end
else
if direction == 0 then
return "z"
else
return "z-"
end
end
end
-- junction : X-, X+, Z-
if (is_rail(surrounds.x_prev) or is_rail(surrounds.x_prev_below)) and
(is_rail(surrounds.x_next) or is_rail(surrounds.x_next_below)) and
(is_rail(surrounds.z_prev) or is_rail(surrounds.z_prev_below)) then
if node.name == "railcarts:rail_switched" then
if direction == 1 then
return "x"
else
return "x+"
end
else
if direction == 3 then
return "x"
else
return "x-"
end
end
end
-- junction : X-, X+, Z-
if (is_rail(surrounds.x_prev) or is_rail(surrounds.x_prev_below)) and
(is_rail(surrounds.x_next) or is_rail(surrounds.x_next_below)) and
(is_rail(surrounds.z_next) or is_rail(surrounds.z_next_below)) then
if node.name == "railcarts:rail_switched" then
if direction == 3 then
return "x"
else
return "z-"
end
else
if direction == 1 then
return "x"
else
return "z+"
end
end
end
return nil
end
--- Check if the given railtype is a curve
-- @param railtype A railtype (see get_rail_status)
-- @return True if it's a curve
function is_curve(railtype)
if railtype == "x+" then return true end
if railtype == "x-" then return true end
if railtype == "z+" then return true end
if railtype == "z-" then return true end
return false
end
--- Check for a curve at the given position.
-- @param surrounds The surrounding nodes
-- @return Nil if there is no curve, otherwise one of four curve designations.
-- "x+" has rails at x+1 and z-1, "x-" has rails at x-1 and z-1, "z+" has rails
-- at x+1 and z+1, and "z-" has nodes at x-1 and z+1.
function check_curve(surrounds)
if (is_rail(surrounds.z_prev) or is_rail(surrounds.z_prev_below)) and
(is_rail(surrounds.x_next) or is_rail(surrounds.x_next_below)) then
return "x+"
end
if (is_rail(surrounds.x_prev) or is_rail(surrounds.x_prev_below)) and
(is_rail(surrounds.z_prev) or is_rail(surrounds.z_prev_below)) then
return "x-"
end
if (is_rail(surrounds.z_next) or is_rail(surrounds.z_next_below)) and
(is_rail(surrounds.x_prev) or is_rail(surrounds.x_prev_below)) then
return "z-"
end
if (is_rail(surrounds.x_next) or is_rail(surrounds.x_next_below)) and
(is_rail(surrounds.z_next) or is_rail(surrounds.z_next_below)) then
return "z+"
end
return nil
end

BIN
screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 730 B

BIN
textures/railcarts_cart.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 747 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 741 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 524 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 498 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 619 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 604 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 555 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 568 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 577 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 549 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 533 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 548 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 603 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 584 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 567 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 568 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 422 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 490 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 B

27
util.lua Normal file
View File

@ -0,0 +1,27 @@
--- Convert a pos to a human-readable string
-- @param pos The position
-- @return A readable string of the form (x,y,z)
function postostr(pos)
if pos == nil then return "(nil)" end
return "("..pos.x..","..pos.y..","..pos.z..")"
end
--- Get a copy of a position
-- @param pos The position
-- @return An identical position
function poscopy(pos)
return {x=pos.x,y=pos.y,z=pos.z}
end
--- Check if two 3d vectors are equal
function v3equal(v1, v2)
return v1.x == v2.x and v1.y == v2.y and v1.z == v2.z
end
function v3posround(v1)
local posround = function(n)
return math.floor(n+0.5)
end
return {x=posround(v1.x), y=posround(v1.y), z=posround(v1.z)}
end