built on 18/06/2020 23:23:53

This commit is contained in:
Joachim Stolberg 2020-06-18 23:23:53 +02:00
parent 15d227f61c
commit 76bc68f37a
81 changed files with 1819 additions and 1393 deletions

View File

@ -48,6 +48,8 @@ The mod features are:
- Minecarts run through unloaded areas (only the stations/hopper have to be loaded)
- Extra Minecart privs for rail workers
- Ingame documentation (German and English), based on the mod "doc"
- API to register carts from other mods
- chat command '/mycart <num>' to output cart state and location
Technical Background
@ -67,18 +69,21 @@ Introduction
2. Place a Railway Buffer at both endpoints. (buffers are always needed,
they store the route and timing information)
3. Give both Railway Buffers unique station names, like Oxford and Cambridge
4. Drive from buffer to buffer in both directions using a Minecart(!) to record the
4. Place a Minecart at a buffer and give it a cart number (1..999)
5. Drive from buffer to buffer in both directions using the Minecart(!) to record the
routes (use 'right-left' keys to control the Minecart)
5. Punch the buffers to check the connection data (e.g. "Oxford: connected to Cambridge")
6. Optional: Configure the Minecart stop time in one or both buffers. The Minecart
6. Punch the buffers to check the connection data (e.g. "Oxford: connected to Cambridge")
7. Optional: Configure the Minecart stop time in one or both buffers. The Minecart
will then start automatically after the configured time
7. Optional: Protect your rail network with the Protection Landmarks (one Landmark
8. Optional: Protect your rail network with the Protection Landmarks (one Landmark
at least every 16 nodes/meters)
8. Place a Minecart in front of the buffer and check whether it starts after the
9. Place a Minecart in front of the buffer and check whether it starts after the
configured time
9. Drop items into the Minecart and punch the cart to start it, or "sneak+click" the
Minecart to get the items back
10. Dig the empty cart with a second "sneak+click" (as usual)
10. Check the cart state via the chat command: /mycart <num>
'<num>' is the cart number
11. Drop items into the Minecart and punch the cart to start it, or "sneak+click" the
Minecart to get the items back
12. Dig the empty cart with a second "sneak+click" (as usual)
Hopper
@ -107,4 +112,6 @@ History
2020-02-24 v1.02 Hopper improved
2020-03-05 v1.03 Hopper again improved
2020-03-28 v1.04 cart unloading bugfix
2020-05-14 v1.05 API changed to be able to register carts
2020-06-14 v1.06 API changed and chat command added

View File

@ -1,450 +0,0 @@
--[[
Minecart
========
Copyright (C) 2019-2020 Joachim Stolberg
MIT
See license.txt for more information
Cart API for external cart definitions on a node based model
]]--
-- for lazy programmers
local M = minetest.get_meta
local S = minecart.S
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local S2P = minetest.string_to_pos
-- register cart here, because entity is already registered
minecart.register_cart_names("minecart:cart", "minecart:cart")
function minecart.register_cart_entity(entity_name, node_name, entity_def)
entity_def.velocity = {x=0, y=0, z=0} -- only used on punch
entity_def.old_dir = {x=1, y=0, z=0} -- random value to start the cart on punch
entity_def.old_pos = nil
entity_def.old_switch = 0
entity_def.node_name = node_name
minetest.register_entity(entity_name, entity_def)
-- register node for punching
minecart.register_cart_names(node_name, entity_name)
end
local function switch_to_node(pos, node_name, owner, param2, cargo)
local node = minetest.get_node(pos)
local rail = node.name
local ndef = minetest.registered_nodes[node_name]
if ndef then
node.name = node_name
node.param2 = param2
minetest.add_node(pos, node)
M(pos):set_string("removed_rail", rail)
M(pos):set_string("owner", owner)
if ndef.after_place_node then
ndef.after_place_node(pos)
end
if cargo and ndef.set_cargo then
ndef.set_cargo(pos, cargo)
end
end
end
function minecart.node_on_place(itemstack, placer, pointed_thing, node_name)
local under = pointed_thing.under
local node = minetest.get_node(under)
local udef = minetest.registered_nodes[node.name]
if udef and udef.on_rightclick and
not (placer and placer:is_player() and
placer:get_player_control().sneak) then
return udef.on_rightclick(under, node, placer, itemstack,
pointed_thing) or itemstack
end
if not pointed_thing.type == "node" then
return
end
local owner = placer:get_player_name()
local param2 = minetest.dir_to_facedir(placer:get_look_dir())
if carts:is_rail(pointed_thing.under) then
switch_to_node(pointed_thing.under, node_name, owner, param2)
elseif carts:is_rail(pointed_thing.above) then
switch_to_node(pointed_thing.above, node_name, owner, param2)
else
return
end
minetest.sound_play({name = "default_place_node_metal", gain = 0.5},
{pos = pointed_thing.above})
if not (creative and creative.is_enabled_for
and creative.is_enabled_for(placer:get_player_name())) then
itemstack:take_item()
end
return itemstack
end
function minecart.node_on_punch(pos, node, puncher, pointed_thing, entity_name, dir)
local ndef = minetest.registered_nodes[node.name]
local cargo = {}
-- Player digs cart by sneak-punch
if puncher and puncher:get_player_control().sneak then
-- Pick up cart
if ndef.can_dig and ndef.can_dig(pos, puncher) then
local inv = puncher:get_inventory()
if not (creative and creative.is_enabled_for
and creative.is_enabled_for(puncher:get_player_name()))
or not inv:contains_item("main", node.name) then
local leftover = inv:add_item("main", node.name)
-- If no room in inventory add a replacement cart to the world
if not leftover:is_empty() then
minetest.add_item(pos, leftover)
end
end
node.name = M(pos):get_string("removed_rail")
if node.name == "" then
node.name = "carts:rail"
end
minetest.remove_node(pos)
minetest.add_node(pos, node)
end
return
end
-- start cart
node.name = M(pos):get_string("removed_rail")
if node.name ~= "" then
if ndef.get_cargo then
cargo = ndef.get_cargo(pos)
end
minetest.add_node(pos, node)
local obj = minetest.add_entity(pos, entity_name)
local owner = puncher and puncher:get_player_name()
minecart.add_cart_to_monitoring(obj, owner, cargo)
obj:punch(puncher or obj, 1, {
full_punch_interval = 1.0,
damage_groups = {fleshy = 1},
}, dir)
end
end
function minecart:on_activate(staticdata, dtime_s)
self.object:set_armor_groups({immortal=1})
end
function minecart:on_punch(puncher, time_from_last_punch, tool_capabilities, direction)
--print("on_punch", direction)
local pos = self.object:get_pos()
local vel = self.object:get_velocity()
local stopped = vector.equals(vel, {x=0, y=0, z=0})
-- running carts can't be punched
if not stopped then
return
end
if not self.railtype then
local node = minetest.get_node(pos).name
self.railtype = minetest.get_item_group(node, "connect_to_raillike")
end
-- Punched by non-authorized player
if puncher and self.owner and self.owner ~= puncher:get_player_name()
and not minetest.check_player_privs(puncher:get_player_name(), "minecart") then
return
end
-- Punched by non-player
if not puncher or not puncher:is_player() then
local cart_dir = carts:get_rail_direction(pos, direction, nil, nil, self.railtype)
if vector.equals(cart_dir, {x=0, y=0, z=0}) then
return
end
self.velocity = vector.multiply(cart_dir, 2)
self.punched = true
return
end
-- Player digs cart by sneak-punch
if puncher:get_player_control().sneak then
if self.sound_handle then
minetest.sound_stop(self.sound_handle)
end
-- Pick up cart
local node_name = self.node_name or "minecart:cart"
local inv = puncher:get_inventory()
if not (creative and creative.is_enabled_for
and creative.is_enabled_for(puncher:get_player_name()))
or not inv:contains_item("main", node_name) then
local leftover = inv:add_item("main", node_name)
-- If no room in inventory add a replacement cart to the world
if not leftover:is_empty() then
minetest.add_item(self.object:get_pos(), leftover)
end
end
minecart.on_dig(self)
self.object:remove()
return
end
local punch_dir = carts:velocity_to_dir(puncher:get_look_dir())
punch_dir.y = 0
local cart_dir = carts:get_rail_direction(pos, punch_dir, nil, nil, self.railtype)
if vector.equals(cart_dir, {x=0, y=0, z=0}) then
return
end
self.velocity = vector.multiply(cart_dir, 2)
self.old_dir = cart_dir
self.punched = true
end
-- sound refresh interval = 1.0sec
local function rail_sound(self, dtime)
if not self.sound_ttl then
self.sound_ttl = 1.0
return
elseif self.sound_ttl > 0 then
self.sound_ttl = self.sound_ttl - dtime
return
end
self.sound_ttl = 1.0
if self.sound_handle then
local handle = self.sound_handle
self.sound_handle = nil
minetest.after(0.2, minetest.sound_stop, handle)
end
local vel = self.object:get_velocity() or {x=0, y=0, z=0}
local speed = vector.length(vel)
if speed > 0 then
self.sound_handle = minetest.sound_play(
"carts_cart_moving", {
object = self.object,
gain = (speed / carts.speed_max) / 2,
loop = true,
})
end
end
local function get_railparams(pos)
local node = minetest.get_node(pos)
return carts.railparams[node.name] or {}
end
local function rail_on_step(self, dtime)
local vel = self.object:get_velocity()
local pos = self.object:get_pos()
if self.punched then
minecart.start_run(self, pos, vel, self.driver)
vel = vector.add(vel, self.velocity)
self.object:set_velocity(vel)
self.old_dir.y = 0
elseif vector.equals(vel, {x=0, y=0, z=0}) then
if minecart.get_route_key(pos) then
local cargo = minecart.stopped(self, pos)
local param2 = minetest.dir_to_facedir(self.old_dir)
switch_to_node(vector.round(pos), self.node_name, self.owner, param2, cargo)
minecart.on_dig(self)
self.object:remove()
end
return
end
-- cart position correction on slopes
local rot = self.object:get_rotation()
if rot.x ~= 0 then
pos.y = pos.y - 0.5
end
local cart_dir = carts:velocity_to_dir(vel)
local same_dir = vector.equals(cart_dir, self.old_dir)
local update = {}
if self.old_pos and not self.punched and same_dir then
local flo_pos = vector.round(pos)
local flo_old = vector.round(self.old_pos)
if vector.equals(flo_pos, flo_old) then
-- Do not check one node multiple times
return
end
end
local ctrl, player
local stop_wiggle = false
if self.old_pos and same_dir then
-- Detection for "skipping" nodes (perhaps use average dtime?)
-- It's sophisticated enough to take the acceleration in account
local acc = self.object:get_acceleration()
local distance = dtime * (vector.length(vel) + 0.5 * dtime * vector.length(acc))
local new_pos, new_dir = carts:pathfinder(
pos, self.old_pos, self.old_dir, distance, ctrl,
self.old_switch, self.railtype
)
if new_pos then
-- No rail found: set to the expected position
pos = new_pos
update.pos = true
cart_dir = new_dir
end
elseif self.old_pos and self.old_dir.y ~= 1 and not self.punched then
-- Stop wiggle
stop_wiggle = true
end
local railparams
-- dir: New moving direction of the cart
-- switch_keys: Currently pressed L/R key, used to ignore the key on the next rail node
local dir, switch_keys = carts:get_rail_direction(
pos, cart_dir, ctrl, self.old_switch, self.railtype
)
------------------------------- changed
if switch_keys then
minecart.set_junction(self, pos, dir, switch_keys)
else
dir, switch_keys = minecart.get_junction(self, pos, dir)
end
------------------------------- changed
local dir_changed = not vector.equals(dir, self.old_dir)
local new_acc = {x=0, y=0, z=0}
if stop_wiggle or vector.equals(dir, {x=0, y=0, z=0}) then
vel = {x = 0, y = 0, z = 0}
local pos_r = vector.round(pos)
if not carts:is_rail(pos_r, self.railtype)
and self.old_pos then
pos = self.old_pos
elseif not stop_wiggle then
pos = pos_r
else
pos.y = math.floor(pos.y + 0.5)
end
update.pos = true
update.vel = true
else
-- Direction change detected
if dir_changed then
vel = vector.multiply(dir, math.abs(vel.x + vel.z))
update.vel = true
if dir.y ~= self.old_dir.y then
pos = vector.round(pos)
update.pos = true
end
end
-- Center on the rail
if dir.z ~= 0 and math.floor(pos.x + 0.5) ~= pos.x then
pos.x = math.floor(pos.x + 0.5)
update.pos = true
end
if dir.x ~= 0 and math.floor(pos.z + 0.5) ~= pos.z then
pos.z = math.floor(pos.z + 0.5)
update.pos = true
end
-- Slow down or speed up..
local acc = dir.y * -4.0
-- Get rail for corrected position
railparams = get_railparams(pos)
-- no need to check for railparams == nil since we always make it exist.
local speed_mod = railparams.acceleration
if speed_mod and speed_mod ~= 0 then
-- Try to make it similar to the original carts mod
acc = acc + speed_mod
else
-- Handbrake or coast
if ctrl and ctrl.down then
acc = acc - 3
else
acc = acc - 0.4
end
end
new_acc = vector.multiply(dir, acc)
end
-- Limits
local max_vel = carts.speed_max
for _, v in pairs({"x","y","z"}) do
if math.abs(vel[v]) > max_vel then
vel[v] = carts:get_sign(vel[v]) * max_vel
new_acc[v] = 0
update.vel = true
end
end
self.object:set_acceleration(new_acc)
self.old_pos = vector.round(pos)
if not vector.equals(dir, {x=0, y=0, z=0}) and not stop_wiggle then
self.old_dir = vector.new(dir)
end
self.old_switch = switch_keys
if self.punched then
self.punched = false
update.vel = true
end
railparams = railparams or get_railparams(pos)
if not (update.vel or update.pos) then
return
end
local yaw = 0
if self.old_dir.x < 0 then
yaw = math.pi/2*3
elseif self.old_dir.x > 0 then
yaw = math.pi/2
elseif self.old_dir.z < 0 then
yaw = math.pi
end
--self.object:set_yaw(yaw * math.pi)
local pitch = 0
if self.old_dir.z ~= 0 then
if dir.y == -1 then
pitch = -math.pi/4
elseif dir.y == 1 then
pitch = math.pi/4
end
else
if dir.y == -1 then
pitch = math.pi/4
elseif dir.y == 1 then
pitch = -math.pi/4
end
end
self.object:set_rotation({x = pitch, y = yaw, z = 0})
-- cart position correction on slopes
if pitch ~= 0 then
pos.y = pos.y + 0.5
update.pos = true
end
if update.vel then
self.object:set_velocity(vel)
end
if update.pos then
if dir_changed then
self.object:set_pos(pos)
else
self.object:move_to(pos)
end
end
end
function minecart:on_step(dtime)
rail_on_step(self, dtime)
rail_sound(self, dtime)
end

View File

@ -1,468 +0,0 @@
local S = minecart.S
local cart_entity = {
initial_properties = {
physical = false, -- otherwise going uphill breaks
collisionbox = {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5},
visual = "mesh",
mesh = "carts_cart.b3d",
visual_size = {x=1, y=1},
textures = {"carts_cart.png^minecart_cart.png"},
static_save = false,
},
------------------------------------ changed
owner = nil,
------------------------------------ changed
driver = nil,
punched = false, -- used to re-send velocity and position
velocity = {x=0, y=0, z=0}, -- only used on punch
old_dir = {x=1, y=0, z=0}, -- random value to start the cart on punch
old_pos = nil,
old_switch = 0,
railtype = nil,
attached_items = {}
}
function cart_entity:on_rightclick(clicker)
if not clicker or not clicker:is_player() then
return
end
local player_name = clicker:get_player_name()
if self.driver and player_name == self.driver then
self.driver = nil
carts:manage_attachment(clicker, nil)
elseif not self.driver then
self.driver = player_name
carts:manage_attachment(clicker, self.object)
-- player_api does not update the animation
-- when the player is attached, reset to default animation
player_api.set_animation(clicker, "stand")
end
end
function cart_entity:on_activate(staticdata, dtime_s)
self.object:set_armor_groups({immortal=1})
end
-- 0.5.x and later: When the driver leaves
function cart_entity:on_detach_child(child)
if child and child:get_player_name() == self.driver then
self.driver = nil
end
end
local function add_cargo_to_player_inv(self, pos, puncher)
local added = false
local inv = puncher:get_inventory()
for _, obj in pairs(minetest.get_objects_inside_radius(pos, 1)) do
local entity = obj:get_luaentity()
if not obj:is_player() and entity and entity.name == "__builtin:item" then
obj:remove()
local item = ItemStack(entity.itemstring)
local leftover = inv:add_item("main", item)
if leftover:get_count() > 0 then
minetest.add_item(pos, leftover)
end
added = true
end
end
return added
end
function cart_entity:on_punch(puncher, time_from_last_punch, tool_capabilities, direction)
local pos = self.object:get_pos()
local vel = self.object:get_velocity()
if not self.railtype or vector.equals(vel, {x=0, y=0, z=0}) then
local node = minetest.get_node(pos).name
self.railtype = minetest.get_item_group(node, "connect_to_raillike")
end
-- Punched by non-player
if not puncher or not puncher:is_player() then
local cart_dir = carts:get_rail_direction(pos, direction, nil, nil, self.railtype)
if vector.equals(cart_dir, {x=0, y=0, z=0}) then
return
end
self.velocity = vector.multiply(cart_dir, 2)
self.punched = true
return
end
------------------------------------ changed
-- Punched by non-authorized player
if puncher and self.owner and self.owner ~= puncher:get_player_name()
and not minetest.check_player_privs(puncher:get_player_name(), "minecart") then
return
end
------------------------------------ changed
-- Player digs cart by sneak-punch
if puncher:get_player_control().sneak then
if self.sound_handle then
minetest.sound_stop(self.sound_handle)
end
-- Detach driver and items
if self.driver then
if self.old_pos then
self.object:set_pos(self.old_pos)
end
local player = minetest.get_player_by_name(self.driver)
carts:manage_attachment(player, nil)
end
------------------------------------ changed
if add_cargo_to_player_inv(self, pos, puncher) then
return
end
------------------------------------ changed
-- Pick up cart
local inv = puncher:get_inventory()
if not (creative and creative.is_enabled_for
and creative.is_enabled_for(puncher:get_player_name()))
or not inv:contains_item("main", "minecart:cart") then
local leftover = inv:add_item("main", "minecart:cart")
-- If no room in inventory add a replacement cart to the world
if not leftover:is_empty() then
minetest.add_item(self.object:get_pos(), leftover)
end
end
------------------------------------ changed
minecart.on_dig(self)
------------------------------------ changed
self.object:remove()
return
end
------------------------------------ changed
minecart.start_recording(self, pos, vel, puncher)
------------------------------------ changed
-- Player punches cart to alter velocity
if puncher:get_player_name() == self.driver then
if math.abs(vel.x + vel.z) > carts.punch_speed_max then
return
end
end
local punch_dir = carts:velocity_to_dir(puncher:get_look_dir())
punch_dir.y = 0
local cart_dir = carts:get_rail_direction(pos, punch_dir, nil, nil, self.railtype)
if vector.equals(cart_dir, {x=0, y=0, z=0}) then
return
end
local punch_interval = 1
if tool_capabilities and tool_capabilities.full_punch_interval then
punch_interval = tool_capabilities.full_punch_interval
end
time_from_last_punch = math.min(time_from_last_punch or punch_interval, punch_interval)
local f = 2 * (time_from_last_punch / punch_interval)
------------------------------------ changed
if vector.equals(vel, {x=0, y=0, z=0}) then
self.velocity = vector.multiply(cart_dir, f)
else
self.velocity = {x=0, y=0, z=0}
end
------------------------------------ changed
self.old_dir = cart_dir
self.punched = true
end
local function rail_on_step_event(handler, obj, dtime)
if handler then
handler(obj, dtime)
end
end
-- sound refresh interval = 1.0sec
local function rail_sound(self, dtime)
if not self.sound_ttl then
self.sound_ttl = 1.0
return
elseif self.sound_ttl > 0 then
self.sound_ttl = self.sound_ttl - dtime
return
end
self.sound_ttl = 1.0
if self.sound_handle then
local handle = self.sound_handle
self.sound_handle = nil
minetest.after(0.2, minetest.sound_stop, handle)
end
local vel = self.object:get_velocity()
local speed = vector.length(vel)
if speed > 0 then
self.sound_handle = minetest.sound_play(
"carts_cart_moving", {
object = self.object,
gain = (speed / carts.speed_max) / 2,
loop = true,
})
end
end
local function get_railparams(pos)
local node = minetest.get_node(pos)
return carts.railparams[node.name] or {}
end
local v3_len = vector.length
local function rail_on_step(self, dtime)
local vel = self.object:get_velocity()
------------------------------------ changed
local pos = self.object:get_pos()
minecart.store_next_waypoint(self, pos, vel)
------------------------------------ changed
if self.punched then
------------------------------- changed
minecart.start_run(self, pos, vel, self.driver)
------------------------------- changed
vel = vector.add(vel, self.velocity)
self.object:set_velocity(vel)
self.old_dir.y = 0
elseif vector.equals(vel, {x=0, y=0, z=0}) then
------------------------------- changed
minecart.stopped(self, pos)
------------------------------- changed
return
end
--local pos = self.object:get_pos()
local cart_dir = carts:velocity_to_dir(vel)
local same_dir = vector.equals(cart_dir, self.old_dir)
local update = {}
if self.old_pos and not self.punched and same_dir then
local flo_pos = vector.round(pos)
local flo_old = vector.round(self.old_pos)
if vector.equals(flo_pos, flo_old) then
-- Do not check one node multiple times
return
end
end
local ctrl, player
-- Get player controls
if self.driver then
player = minetest.get_player_by_name(self.driver)
if player then
ctrl = player:get_player_control()
end
end
local stop_wiggle = false
if self.old_pos and same_dir then
-- Detection for "skipping" nodes (perhaps use average dtime?)
-- It's sophisticated enough to take the acceleration in account
local acc = self.object:get_acceleration()
local distance = dtime * (v3_len(vel) + 0.5 * dtime * v3_len(acc))
local new_pos, new_dir = carts:pathfinder(
pos, self.old_pos, self.old_dir, distance, ctrl,
self.old_switch, self.railtype
)
if new_pos then
-- No rail found: set to the expected position
pos = new_pos
update.pos = true
cart_dir = new_dir
end
elseif self.old_pos and self.old_dir.y ~= 1 and not self.punched then
-- Stop wiggle
stop_wiggle = true
end
local railparams
-- dir: New moving direction of the cart
-- switch_keys: Currently pressed L/R key, used to ignore the key on the next rail node
local dir, switch_keys = carts:get_rail_direction(
pos, cart_dir, ctrl, self.old_switch, self.railtype
)
------------------------------- changed
if switch_keys then
minecart.set_junction(self, pos, dir, switch_keys)
else
dir, switch_keys = minecart.get_junction(self, pos, dir)
end
------------------------------- changed
local dir_changed = not vector.equals(dir, self.old_dir)
local new_acc = {x=0, y=0, z=0}
if stop_wiggle or vector.equals(dir, {x=0, y=0, z=0}) then
vel = {x = 0, y = 0, z = 0}
local pos_r = vector.round(pos)
if not carts:is_rail(pos_r, self.railtype)
and self.old_pos then
pos = self.old_pos
elseif not stop_wiggle then
pos = pos_r
else
pos.y = math.floor(pos.y + 0.5)
end
update.pos = true
update.vel = true
else
-- Direction change detected
if dir_changed then
vel = vector.multiply(dir, math.abs(vel.x + vel.z))
update.vel = true
if dir.y ~= self.old_dir.y then
pos = vector.round(pos)
update.pos = true
end
end
-- Center on the rail
if dir.z ~= 0 and math.floor(pos.x + 0.5) ~= pos.x then
pos.x = math.floor(pos.x + 0.5)
update.pos = true
end
if dir.x ~= 0 and math.floor(pos.z + 0.5) ~= pos.z then
pos.z = math.floor(pos.z + 0.5)
update.pos = true
end
-- Slow down or speed up..
local acc = dir.y * -4.0
-- Get rail for corrected position
railparams = get_railparams(pos)
-- no need to check for railparams == nil since we always make it exist.
local speed_mod = railparams.acceleration
if speed_mod and speed_mod ~= 0 then
-- Try to make it similar to the original carts mod
acc = acc + speed_mod
else
-- Handbrake or coast
if ctrl and ctrl.down then
acc = acc - 3
else
acc = acc - 0.4
end
end
new_acc = vector.multiply(dir, acc)
end
-- Limits
local max_vel = carts.speed_max
for _, v in pairs({"x","y","z"}) do
if math.abs(vel[v]) > max_vel then
vel[v] = carts:get_sign(vel[v]) * max_vel
new_acc[v] = 0
update.vel = true
end
end
self.object:set_acceleration(new_acc)
self.old_pos = vector.round(pos)
if not vector.equals(dir, {x=0, y=0, z=0}) and not stop_wiggle then
self.old_dir = vector.new(dir)
end
self.old_switch = switch_keys
if self.punched then
-- Collect dropped items
------------------------------- changed
minecart.attach_cargo(self, pos)
------------------------------- changed
self.punched = false
update.vel = true
end
railparams = railparams or get_railparams(pos)
if not (update.vel or update.pos) then
rail_on_step_event(railparams.on_step, self, dtime)
return
end
local yaw = 0
if self.old_dir.x < 0 then
yaw = 0.5
elseif self.old_dir.x > 0 then
yaw = 1.5
elseif self.old_dir.z < 0 then
yaw = 1
end
self.object:set_yaw(yaw * math.pi)
local anim = {x=0, y=0}
if dir.y == -1 then
anim = {x=1, y=1}
elseif dir.y == 1 then
anim = {x=2, y=2}
end
self.object:set_animation(anim, 1, 0)
if update.vel then
self.object:set_velocity(vel)
end
if update.pos then
if dir_changed then
self.object:set_pos(pos)
else
self.object:move_to(pos)
end
end
-- call event handler
rail_on_step_event(railparams.on_step, self, dtime)
end
function cart_entity:on_step(dtime)
rail_on_step(self, dtime)
rail_sound(self, dtime)
end
minetest.register_entity("minecart:cart", cart_entity)
minetest.register_craftitem("minecart:cart", {
description = S("Minecart (Sneak+Click to pick up)"),
inventory_image = minetest.inventorycube("carts_cart_top.png", "carts_cart_side.png^minecart_logo.png", "carts_cart_side.png^minecart_logo.png"),
wield_image = "carts_cart_side.png",
on_place = function(itemstack, placer, pointed_thing)
local under = pointed_thing.under
local node = minetest.get_node(under)
local udef = minetest.registered_nodes[node.name]
if udef and udef.on_rightclick and
not (placer and placer:is_player() and
placer:get_player_control().sneak) then
return udef.on_rightclick(under, node, placer, itemstack,
pointed_thing) or itemstack
end
if not pointed_thing.type == "node" then
return
end
if carts:is_rail(pointed_thing.under) then
------------------------------- changed
local cart = minetest.add_entity(pointed_thing.under, "minecart:cart")
minecart.add_cart_to_monitoring(cart, placer:get_player_name())
------------------------------- changed
elseif carts:is_rail(pointed_thing.above) then
------------------------------- changed
local cart = minetest.add_entity(pointed_thing.above, "minecart:cart")
minecart.add_cart_to_monitoring(cart, placer:get_player_name())
------------------------------- changed
else
return
end
minetest.sound_play({name = "default_place_node_metal", gain = 0.5},
{pos = pointed_thing.above})
if not (creative and creative.is_enabled_for
and creative.is_enabled_for(placer:get_player_name())) then
itemstack:take_item()
end
return itemstack
end,
})
minetest.register_craft({
output = "minecart:cart",
recipe = {
{"default:steel_ingot", "default:cobble", "default:steel_ingot"},
{"default:steel_ingot", "default:steel_ingot", "default:steel_ingot"},
},
})

416
minecart/cart_lib1.lua Normal file
View File

@ -0,0 +1,416 @@
--[[
Minecart
========
Copyright (C) 2019-2020 Joachim Stolberg
MIT
See license.txt for more information
Cart library functions (level 1)
]]--
-- Notes:
-- 1) Only the owner can punch der cart
-- 2) Only the owner can start the recording
-- 3) But any player can act as cargo, cart punched by owner or buffer
-- for lazy programmers
local M = minetest.get_meta
local S = minecart.S
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local S2P = minetest.string_to_pos
local MP = minetest.get_modpath("minecart")
local api = {}
function api:init(is_node_cart)
local lib
if is_node_cart then
lib = dofile(MP.."/cart_lib2n.lua")
else
lib = dofile(MP.."/cart_lib2e.lua")
end
-- add lib to local api
for k,v in pairs(lib) do
api[k] = v
end
end
-- Player get on / off
function api:on_rightclick(clicker)
if not clicker or not clicker:is_player() then
return
end
local player_name = clicker:get_player_name()
if self.driver and player_name == self.driver then
self.driver = nil
carts:manage_attachment(clicker, nil)
elseif not self.driver then
self.driver = player_name
carts:manage_attachment(clicker, self.object)
-- player_api does not update the animation
-- when the player is attached, reset to default animation
player_api.set_animation(clicker, "stand")
end
end
function api:on_activate(staticdata, dtime_s)
self.object:set_armor_groups({immortal=1})
end
function api:on_detach_child(child)
if child and child:get_player_name() == self.driver then
self.driver = nil
end
end
function api:on_punch(puncher, time_from_last_punch, tool_capabilities, direction)
local pos = self.object:get_pos()
local vel = self.object:get_velocity()
local stopped = vector.equals(vel, {x=0, y=0, z=0})
local is_minecart = self.node_name == nil
local node_name = self.node_name or "minecart:cart"
local puncher_name = puncher and puncher:is_player() and puncher:get_player_name()
local puncher_is_owner = puncher_name and (not self.owner or self.owner == "" or
puncher_name == self.owner or
minetest.check_player_privs(puncher_name, "minecart"))
local puncher_is_driver = self.driver and self.driver == puncher_name
local sneak_punch = puncher_name and puncher:get_player_control().sneak
local no_cargo = next(self.cargo or {}) == nil
-- driver wants to leave/remove the empty Minecart by sneak-punch
if is_minecart and sneak_punch and puncher_is_driver and no_cargo then
if puncher_is_owner then
api.remove_cart(self, pos, puncher)
end
carts:manage_attachment(puncher, nil)
return
end
-- running carts can't be punched or removed from external
if not stopped then
return
end
-- Punched by non-authorized player
if puncher_name and not puncher_is_owner then
minetest.chat_send_player(puncher_name, S("[minecart] Cart is protected by ")..(self.owner or ""))
return
end
if not self.railtype then
local node = minetest.get_node(pos).name
self.railtype = minetest.get_item_group(node, "connect_to_raillike")
end
-- Punched by non-player
if not puncher_name then
local cart_dir = carts:get_rail_direction(pos, direction, nil, nil, self.railtype)
if vector.equals(cart_dir, {x=0, y=0, z=0}) then
return
end
self.velocity = vector.multiply(cart_dir, 2)
self.punched = true
api.load_cargo(self, pos)
minecart.start_cart(pos, self.myID)
return
end
-- Sneak-punched by owner
if sneak_punch then
-- Unload the cargo
if api.add_cargo_to_player_inv(self, pos, puncher) then
return
end
-- detach driver
if self.driver then
carts:manage_attachment(puncher_name, nil)
end
-- Pick up cart
api.remove_cart(self, pos, puncher)
return
end
-- Cart with driver punched to start recording
if puncher_is_driver then
minecart.start_recording(self, pos, vel, puncher)
else
minecart.start_cart(pos, self.myID)
end
api.load_cargo(self, pos)
-- Normal punch by owner to start the cart
local punch_dir = carts:velocity_to_dir(puncher:get_look_dir())
punch_dir.y = 0
local cart_dir = carts:get_rail_direction(pos, punch_dir, nil, nil, self.railtype)
if vector.equals(cart_dir, {x=0, y=0, z=0}) then
return
end
self.velocity = vector.multiply(cart_dir, 2)
self.old_dir = cart_dir
self.punched = true
end
local function rail_on_step_event(handler, obj, dtime)
if handler then
handler(obj, dtime)
end
end
-- sound refresh interval = 1.0sec
local function rail_sound(self, dtime)
if not self.sound_ttl then
self.sound_ttl = 1.0
return
elseif self.sound_ttl > 0 then
self.sound_ttl = self.sound_ttl - dtime
return
end
self.sound_ttl = 1.0
if self.sound_handle then
local handle = self.sound_handle
self.sound_handle = nil
minetest.after(0.2, minetest.sound_stop, handle)
end
if not self.stopped then
local vel = self.object:get_velocity() or {x=0, y=0, z=0}
local speed = vector.length(vel)
self.sound_handle = minetest.sound_play(
"carts_cart_moving", {
object = self.object,
gain = (speed / carts.speed_max) / 2,
loop = true,
})
end
end
local function get_railparams(pos)
local node = minetest.get_node(pos)
return carts.railparams[node.name] or {}
end
local v3_len = vector.length
local function rail_on_step(self, dtime)
local vel = self.object:get_velocity()
local pos = self.object:get_pos()
local rot = self.object:get_rotation()
local stopped = minecart.stopped(vel) and rot.x == 0
local is_minecart = self.node_name == nil
local recording = is_minecart and self.driver == self.owner
-- cart position correction on slopes
if rot.x ~= 0 then
pos.y = pos.y - 0.5
end
if self.punched then
vel = vector.add(vel, self.velocity)
self.object:set_velocity(vel)
self.old_dir.y = 0
self.stopped = false
elseif stopped and not self.stopped then
local param2 = minetest.dir_to_facedir(self.old_dir)
api.stop_cart(pos, self, self.node_name or "minecart:cart", param2)
if recording then
minecart.stop_recording(self, pos, vel, self.driver)
end
api.unload_cargo(self, pos)
self.stopped = true
self.object:set_velocity({x=0, y=0, z=0})
self.object:set_acceleration({x=0, y=0, z=0})
return
elseif stopped then
return
end
if recording then
minecart.store_next_waypoint(self, pos, vel)
end
local cart_dir = carts:velocity_to_dir(vel)
local same_dir = vector.equals(cart_dir, self.old_dir)
local update = {}
if self.old_pos and not self.punched and same_dir then
local flo_pos = vector.round(pos)
local flo_old = vector.round(self.old_pos)
if vector.equals(flo_pos, flo_old) then
-- Do not check one node multiple times
return
end
end
local ctrl, player
-- Get player controls
if recording then
player = minetest.get_player_by_name(self.driver)
if player then
ctrl = player:get_player_control()
end
end
local railparams
-- dir: New moving direction of the cart
-- switch_keys: Currently pressed L/R key, used to ignore the key on the next rail node
local dir, switch_keys = carts:get_rail_direction(
pos, cart_dir, ctrl, self.old_switch, self.railtype
)
-- handle junctions
if switch_keys then -- recording
minecart.set_junction(self, pos, dir, switch_keys)
else -- normal run
dir, switch_keys = minecart.get_junction(self, pos, dir)
end
local dir_changed = not vector.equals(dir, self.old_dir)
local new_acc = {x=0, y=0, z=0}
if vector.equals(dir, {x=0, y=0, z=0}) then
vel = {x = 0, y = 0, z = 0}
local pos_r = vector.round(pos)
if not carts:is_rail(pos_r, self.railtype)
and self.old_pos then
pos = self.old_pos
else
pos = pos_r
end
update.pos = true
update.vel = true
else
-- Direction change detected
if dir_changed then
vel = vector.multiply(dir, math.abs(vel.x + vel.z))
update.vel = true
if dir.y ~= self.old_dir.y then
pos = vector.round(pos)
update.pos = true
end
end
-- Center on the rail
if dir.z ~= 0 and math.floor(pos.x + 0.5) ~= pos.x then
pos.x = math.floor(pos.x + 0.5)
update.pos = true
end
if dir.x ~= 0 and math.floor(pos.z + 0.5) ~= pos.z then
pos.z = math.floor(pos.z + 0.5)
update.pos = true
end
-- Slow down or speed up..
local acc = dir.y * -2.0
-- Get rail for corrected position
railparams = get_railparams(pos)
-- no need to check for railparams == nil since we always make it exist.
local speed_mod = railparams.acceleration
if speed_mod and speed_mod ~= 0 then
-- Try to make it similar to the original carts mod
acc = acc + speed_mod
else
acc = acc - 0.4
end
new_acc = vector.multiply(dir, acc)
end
-- Limits
local max_vel = carts.speed_max
for _, v in pairs({"x","y","z"}) do
if math.abs(vel[v]) > max_vel then
vel[v] = carts:get_sign(vel[v]) * max_vel
new_acc[v] = 0
update.vel = true
end
end
self.object:set_acceleration(new_acc)
self.old_pos = vector.round(pos)
if not vector.equals(dir, {x=0, y=0, z=0}) then
self.old_dir = vector.new(dir)
end
self.old_switch = switch_keys
if self.punched then
self.punched = false
update.vel = true
end
railparams = railparams or get_railparams(pos)
if not (update.vel or update.pos) then
rail_on_step_event(railparams.on_step, self, dtime)
return
end
local yaw = 0
if self.old_dir.x < 0 then
yaw = math.pi/2*3
elseif self.old_dir.x > 0 then
yaw = math.pi/2
elseif self.old_dir.z < 0 then
yaw = math.pi
end
--self.object:set_yaw(yaw * math.pi)
local pitch = 0
if self.old_dir.z ~= 0 then
if dir.y == -1 then
pitch = -math.pi/4
elseif dir.y == 1 then
pitch = math.pi/4
end
else
if dir.y == -1 then
pitch = math.pi/4
elseif dir.y == 1 then
pitch = -math.pi/4
end
end
self.object:set_rotation({x = pitch, y = yaw, z = 0})
-- cart position correction on slopes
if pitch ~= 0 then
pos.y = pos.y + 0.5
update.pos = true
vel = vector.divide(vel, 2)
update.vel = true
elseif self.old_pitch ~= 0 then
vel = vector.multiply(vel, 2)
update.vel = true
end
self.old_pitch = pitch
if update.vel then
self.object:set_velocity(vel)
end
if update.pos then
if dir_changed then
self.object:set_pos(pos)
else
self.object:move_to(pos)
end
end
-- call event handler
rail_on_step_event(railparams.on_step, self, dtime)
end
function api:on_step(dtime)
rail_on_step(self, dtime)
rail_sound(self, dtime)
end
return api

150
minecart/cart_lib2e.lua Normal file
View File

@ -0,0 +1,150 @@
--[[
Minecart
========
Copyright (C) 2019-2020 Joachim Stolberg
MIT
See license.txt for more information
Cart library functions for entity based carts (level 2)
]]--
-- for lazy programmers
local M = minetest.get_meta
local S = minecart.S
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local S2P = minetest.string_to_pos
local MP = minetest.get_modpath("minecart")
local api = dofile(MP.."/cart_lib3.lua")
-- Add node, set metadata, and load carge
local function add_cart(pos, node_name, param2, owner, userID, cargo)
local obj = minetest.add_entity(pos, node_name)
local myID = api.get_object_id(obj)
if myID then
-- Copy item data to cart entity
local entity = obj:get_luaentity()
entity.owner = owner
entity.userID = userID
entity.cargo = cargo
entity.myID = myID
obj:set_nametag_attributes({color = "#FFFF00", text = owner..": "..userID})
minecart.add_to_monitoring(obj, myID, owner, userID)
return myID
else
print("Entity has no ID")
end
end
function api.stop_cart(pos, entity, node_name, param2)
-- Stop sound
if entity.sound_handle then
minetest.sound_stop(entity.sound_handle)
entity.sound_handle = nil
end
minecart.stop_cart(pos, entity.myID)
end
-- Player adds the node
function api.add_cart(itemstack, placer, pointed_thing, node_name)
local owner = placer:get_player_name()
local meta = placer:get_meta()
local param2 = minetest.dir_to_facedir(placer:get_look_dir())
local userID = 0
local cargo = {}
-- Add node
if carts:is_rail(pointed_thing.under) then
add_cart(pointed_thing.under, node_name, param2, owner, userID, cargo)
meta:set_string("cart_pos", P2S(pointed_thing.under))
elseif carts:is_rail(pointed_thing.above) then
add_cart(pointed_thing.above, node_name, param2, owner, userID, cargo)
meta:set_string("cart_pos", P2S(pointed_thing.above))
else
return
end
minetest.sound_play({name = "default_place_node_metal", gain = 0.5},
{pos = pointed_thing.above})
if not (creative and creative.is_enabled_for
and creative.is_enabled_for(placer:get_player_name())) then
itemstack:take_item()
end
minetest.show_formspec(owner, "minecart:userID_entity",
"size[4,3]" ..
"label[0,0;Enter cart number:]" ..
"field[1,1;3,1;userID;;]" ..
"button_exit[1,2;2,1;exit;Save]")
return itemstack
end
-- Player removes the node
function api.remove_cart(self, pos, player)
-- Add cart to player inventory
local inv = player:get_inventory()
if not (creative and creative.is_enabled_for
and creative.is_enabled_for(player:get_player_name()))
or not inv:contains_item("main", "minecart:cart") then
local leftover = inv:add_item("main", "minecart:cart")
-- If no room in inventory add a replacement cart to the world
if not leftover:is_empty() then
minetest.add_item(pos, leftover)
end
end
minecart.remove_from_monitoring(self.myID)
self.object:remove()
-- Stop sound
if self.sound_handle then
minetest.sound_stop(self.sound_handle)
self.sound_handle = nil
end
return true
end
function api.load_cargo(self, pos)
self.cargo = self.cargo or {}
for _, obj_ in pairs(minetest.get_objects_inside_radius(pos, 1)) do
local entity = obj_:get_luaentity()
if not obj_:is_player() and entity and entity.name == "__builtin:item" then
obj_:remove()
self.cargo[#self.cargo + 1] = entity.itemstring
end
end
end
function api.unload_cargo(self, pos)
-- Spawn loaded items again
for _,item in ipairs(self.cargo or {}) do
minetest.add_item(pos, ItemStack(item))
end
self.cargo = {}
end
-- in the case the owner punches the cart
function api.add_cargo_to_player_inv(self, pos, puncher)
local added = false
local inv = puncher:get_inventory()
for _, obj in pairs(minetest.get_objects_inside_radius(pos, 1)) do
local entity = obj:get_luaentity()
if not obj:is_player() and entity and entity.name == "__builtin:item" then
obj:remove()
local item = ItemStack(entity.itemstring)
local leftover = inv:add_item("main", item)
if leftover:get_count() > 0 then
minetest.add_item(pos, leftover)
end
added = true -- don't dig the cart
end
end
return added
end
return api

198
minecart/cart_lib2n.lua Normal file
View File

@ -0,0 +1,198 @@
--[[
Minecart
========
Copyright (C) 2019-2020 Joachim Stolberg
MIT
See license.txt for more information
Cart library functions for node based carts (level 2)
]]--
-- for lazy programmers
local M = minetest.get_meta
local S = minecart.S
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local S2P = minetest.string_to_pos
local MP = minetest.get_modpath("minecart")
local api = dofile(MP.."/cart_lib3.lua")
-- Add node, set metadata, and load carge
local function add_cart(pos, node_name, param2, owner, userID, cargo)
local ndef = minetest.registered_nodes[node_name]
local node = minetest.get_node(pos)
local meta = M(pos)
local rail = node.name
minetest.add_node(pos, {name = node_name, param2 = param2})
meta:set_string("removed_rail", rail)
meta:set_string("owner", owner)
meta:set_string("userID", userID)
meta:set_string("infotext", minetest.get_color_escape_sequence("#FFFF00")..owner..": "..userID)
if ndef.after_place_node then
ndef.after_place_node(pos)
end
if cargo and ndef.set_cargo then
ndef.set_cargo(pos, cargo)
end
end
-- called after punch cart
local function start_cart(pos, node_name, entity_name, puncher, dir)
-- Read node metadata
local ndef = minetest.registered_nodes[node_name]
if ndef then
local meta = M(pos)
local rail = meta:get_string("removed_rail")
local userID = meta:get_int("userID")
local cart_owner = meta:get_string("owner")
local cargo = ndef.get_cargo and ndef.get_cargo(pos) or {}
-- swap node to rail
minetest.remove_node(pos)
minetest.add_node(pos, {name = rail})
-- Add entity
local obj = minetest.add_entity(pos, entity_name)
-- Determine ID
local myID = api.get_object_id(obj)
if myID then
-- Copy metadata to cart entity
local entity = obj:get_luaentity()
entity.owner = cart_owner
entity.userID = userID
entity.cargo = cargo
entity.myID = myID
obj:set_nametag_attributes({color = "#ffff00", text = cart_owner..": "..userID})
minecart.add_to_monitoring(obj, myID, cart_owner, userID)
minecart.node_at_station(cart_owner, userID, nil)
-- punch cart to prevent the stopped handling
obj:punch(puncher or obj, 1, {
full_punch_interval = 1.0,
damage_groups = {fleshy = 1},
}, dir)
return myID
else
print("Entity has no ID")
end
end
end
function api.stop_cart(pos, entity, node_name, param2)
-- rail buffer reached?
if api.get_route_key(pos) then
-- Read entity data
local owner = entity.owner or ""
local userID = entity.userID or 0
local cargo = entity.cargo or {}
-- Remove entity
minecart.remove_from_monitoring(entity.myID)
minecart.node_at_station(owner, userID, pos)
entity.object:remove()
-- Add cart node
add_cart(pos, node_name, param2, owner, userID, cargo)
end
-- Stop sound
if entity.sound_handle then
minetest.sound_stop(entity.sound_handle)
entity.sound_handle = nil
end
end
-- Player adds the node
function api.add_cart(itemstack, placer, pointed_thing, node_name)
local owner = placer:get_player_name()
local meta = placer:get_meta()
local param2 = minetest.dir_to_facedir(placer:get_look_dir())
local userID = 0
local cargo = {}
-- Add node
if carts:is_rail(pointed_thing.under) then
add_cart(pointed_thing.under, node_name, param2, owner, userID, cargo)
meta:set_string("cart_pos", P2S(pointed_thing.under))
elseif carts:is_rail(pointed_thing.above) then
add_cart(pointed_thing.above, node_name, param2, owner, userID, cargo)
meta:set_string("cart_pos", P2S(pointed_thing.above))
else
return
end
minetest.sound_play({name = "default_place_node_metal", gain = 0.5},
{pos = pointed_thing.above})
if not (creative and creative.is_enabled_for
and creative.is_enabled_for(placer:get_player_name())) then
itemstack:take_item()
end
minetest.show_formspec(owner, "minecart:userID_node",
"size[4,3]" ..
"label[0,0;Enter cart number:]" ..
"field[1,1;3,1;userID;;]" ..
"button_exit[1,2;2,1;exit;Save]")
return itemstack
end
function api.node_on_punch(pos, node, puncher, pointed_thing, entity_name, dir)
local ndef = minetest.registered_nodes[node.name]
-- Player digs cart by sneak-punch
if puncher and puncher:get_player_control().sneak then
api.remove_cart(nil, pos, puncher)
return
end
start_cart(pos, node.name, entity_name, puncher, dir)
end
local function add_to_player_inventory(pos, player, node_name)
local inv = player:get_inventory()
if not (creative and creative.is_enabled_for
and creative.is_enabled_for(player:get_player_name()))
or not inv:contains_item("main", node_name) then
local leftover = inv:add_item("main", node_name)
-- If no room in inventory add a replacement cart to the world
if not leftover:is_empty() then
minetest.add_item(pos, leftover)
end
end
end
-- Player removes the node
function api.remove_cart(self, pos, player)
if self then -- cart is still an entity
add_to_player_inventory(pos, player, self.node_name or "minecart:cart")
minecart.remove_from_monitoring(self.myID)
self.object:remove()
else
local node = minetest.get_node(pos)
local ndef = minetest.registered_nodes[node.name]
if ndef.can_dig and ndef.can_dig(pos, player) then
add_to_player_inventory(pos, player, node.name)
node.name = M(pos):get_string("removed_rail")
if node.name == "" then
node.name = "carts:rail"
end
minetest.remove_node(pos)
minetest.add_node(pos, node)
end
end
end
function api.load_cargo()
-- nothing to load
end
function api.unload_cargo()
-- nothing to unload
end
function api.add_cargo_to_player_inv()
-- nothing to do
end
-- needed by minecart.punch_cart and node carts
minecart.node_on_punch = api.node_on_punch
return api

89
minecart/cart_lib3.lua Normal file
View File

@ -0,0 +1,89 @@
--[[
Minecart
========
Copyright (C) 2019-2020 Joachim Stolberg
MIT
See license.txt for more information
Cart library base functions (level 3)
]]--
-- for lazy programmers
local M = minetest.get_meta
local S = minecart.S
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local S2P = minetest.string_to_pos
local api = {}
function api.get_object_id(object)
for id, entity in pairs(minetest.luaentities) do
if entity.object == object then
return id
end
end
end
function api.get_route_key(pos, player_name)
local pos1 = minetest.find_node_near(pos, 1, {"minecart:buffer"})
if pos1 then
local meta = minetest.get_meta(pos1)
if player_name == nil or player_name == meta:get_string("owner") then
return P2S(pos1)
end
end
end
function api.get_station_name(pos)
local pos1 = minetest.find_node_near(pos, 1, {"minecart:buffer"})
if pos1 then
local name = M(pos1):get_string("name")
if name ~= "" then
return name
end
return "-"
end
end
function api.load_cart(pos, vel, item)
-- Add cart to map
local obj = minetest.add_entity(pos, item.entity_name or "minecart:cart", nil)
-- Determine ID
local myID = api.get_object_id(obj)
if myID then
-- Copy item data to cart entity
local entity = obj:get_luaentity()
entity.owner = item.owner or ""
entity.userID = item.userID or 0
entity.cargo = item.cargo or {}
entity.myID = myID
obj:set_nametag_attributes({color = "#FFFF00", text = entity.owner..": "..entity.userID})
-- Update item data
item.owner = entity.owner
item.cargo = nil
-- Start cart
obj:set_velocity(vel)
return myID
else
print("Entity has no ID")
end
end
function api.unload_cart(pos, vel, entity, item)
-- Copy entity data to item
item.cargo = entity.cargo
item.entity_name = entity.object:get_entity_name()
-- Remove entity from map
entity.object:remove()
-- Stop sound
if entity.sound_handle then
minetest.sound_stop(entity.sound_handle)
entity.sound_handle = nil
end
end
return api

View File

@ -26,13 +26,15 @@ local summary_doc = table.concat({
S("1. Place your rails and build a route with two endpoints. Junctions are allowed as long as each route has its own start and endpoint."),
S("2. Place a Railway Buffer at both endpoints (buffers are always needed, they store the route and timing information)."),
S("3. Give both Railway Buffers unique station names, like Oxford and Cambridge."),
S("4. Drive from buffer to buffer in both directions using a Minecart(!) to record the routes (use 'right-left' keys to control the Minecart)."),
S("5. Punch the buffers to check the connection data (e.g. 'Oxford: connected to Cambridge')."),
S("6. Optional: Configure the Minecart stop time in one or both buffers. The Minecart will then start automatically after the configured time."),
S("7. Optional: Protect your rail network with the Protection Landmarks (one Landmark at least every 16 nodes/meters)."),
S("8. Place a Minecart in front of the buffer and check whether it starts after the configured time."),
S("9. Drop items into the Minecart and punch the cart to start it, or 'sneak+click' the Minecart to get the items back."),
S("10. Dig the empty cart with a second 'sneak+click' (as usual)."),
S("4. Place a Minecart at a buffer and give it a cart number (1..999)"),
S("5. Drive from buffer to buffer in both directions using the Minecart(!) to record the routes (use 'right-left' keys to control the Minecart)."),
S("6. Punch the buffers to check the connection data (e.g. 'Oxford: connected to Cambridge')."),
S("7. Optional: Configure the Minecart stop time in one or both buffers. The Minecart will then start automatically after the configured time."),
S("8. Optional: Protect your rail network with the Protection Landmarks (one Landmark at least every 16 nodes/meters)."),
S("9. Place a Minecart in front of the buffer and check whether it starts after the configured time."),
S("10. Check the cart state via the chat command: /mycart <num>\n '<num>' is the cart number"),
S("11. Drop items into the Minecart and punch the cart to start it, or 'sneak+click' the Minecart to get the items back."),
S("12. Dig the empty cart with a second 'sneak+click' (as usual)."),
}, "\n")
local cart_doc = S("Primary used to transport items. You can drop items into the Minecart and punch the cart to get started. Sneak+click the cart to get the items back")

View File

@ -1,38 +1,79 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Generate a template file for translation purposes
# Script to generate the template file and update the translation files.
#
# Copyright (C) 2019 Joachim Stolberg
# LGPLv2.1+
#
# Copy the script into the mod root folder and adapt the last code lines to you needs.
from __future__ import print_function
import os, fnmatch, re, shutil
import os, fnmatch, re
pattern = re.compile(r'[ \.=^\t]S\("(.+?)"\)', re.DOTALL)
pattern_lua = re.compile(r'[ \.=^\t]S\("(.+?)"\)', re.DOTALL)
pattern_tr = re.compile(r'(.+?[^@])=(.+)')
def gen_template(templ_file, lkeyStrings):
lOut = []
lkeyStrings = list(set(lkeyStrings))
lkeyStrings.sort()
for s in lkeyStrings:
lOut.append("%s=" % s)
file(templ_file, "wt").write("\n".join(lOut))
open(templ_file, "wt").write("\n".join(lOut))
def read_strings(fname):
def read_lua_file_strings(lua_file):
lOut = []
text = file(fname).read()
for s in pattern.findall(text):
text = open(lua_file).read()
for s in pattern_lua.findall(text):
s = re.sub(r'"\.\.\s+"', "", s)
s = re.sub("@[^@=n]", "@@", s)
s = s.replace("\n", "@n")
s = s.replace("\\n", "@n")
s = s.replace("=", "@=")
lOut.append(s)
return lOut
def i18n(templ_file):
def inport_tr_file(tr_file):
dOut = {}
if os.path.exists(tr_file):
for line in open(tr_file, "r").readlines():
s = line.strip()
if s == "" or s[0] == "#":
continue
match = pattern_tr.match(s)
if match:
dOut[match.group(1)] = match.group(2)
return dOut
def generate_template(templ_file):
lOut = []
for root, dirs, files in os.walk('./'):
for name in files:
if fnmatch.fnmatch(name, "*.lua"):
fname = os.path.join(root, name)
print fname
lOut.extend(read_strings(fname))
found = read_lua_file_strings(fname)
print(fname, len(found))
lOut.extend(found)
lOut = list(set(lOut))
lOut.sort()
gen_template(templ_file, lOut)
return lOut
def update_tr_file(lNew, mod_name, tr_file):
lOut = ["# textdomain: %s\n" % mod_name]
if os.path.exists(tr_file):
shutil.copyfile(tr_file, tr_file+".old")
dOld = inport_tr_file(tr_file)
for key in lNew:
val = dOld.get(key, "")
lOut.append("%s=%s" % (key, val))
lOut.append("##### not used anymore #####")
for key in dOld:
if key not in lNew:
lOut.append("%s=%s" % (key, dOld[key]))
open(tr_file, "w").write("\n".join(lOut))
i18n("./locale/template.txt")
print "Done.\n"
data = generate_template("./locale/template.txt")
update_tr_file(data, "minecart", "./locale/minecart.de.tr")
print("Done.\n")

View File

@ -13,7 +13,7 @@
minecart = {}
-- Version for compatibility checks, see readme.md/history
minecart.version = 1.05
minecart.version = 1.06
minecart.hopper_enabled = minetest.settings:get_bool("minecart_hopper_enabled") ~= false
@ -23,11 +23,11 @@ minecart.S = minetest.get_translator("minecart")
local MP = minetest.get_modpath("minecart")
dofile(MP.."/storage.lua")
dofile(MP.."/lib.lua")
dofile(MP.."/routes.lua")
dofile(MP.."/cart_entity.lua")
dofile(MP.."/monitoring.lua")
dofile(MP.."/recording.lua")
dofile(MP.."/minecart.lua")
dofile(MP.."/buffer.lua")
dofile(MP.."/protection.lua")
dofile(MP.."/cart_api.lua")
if minecart.hopper_enabled then
dofile(MP.."/hopper.lua")

View File

@ -51,6 +51,11 @@ function minecart.register_cart_names(cart_name_stopped, cart_name_running)
end
end
function minecart.stopped(vel, tolerance)
tolerance = tolerance or 0.05
return math.abs(vel.x) < tolerance and math.abs(vel.z) < tolerance
end
local function is_air_like(name)
local ndef = minetest.registered_nodes[name]
if ndef and ndef.buildable_to then
@ -65,6 +70,17 @@ function minecart.get_next_node(pos, param2)
return pos2, node
end
local function get_cart_object(pos, radius)
for _, object in pairs(minetest.get_objects_inside_radius(pos, radius or 0.5)) do
if tValidCartEntities[object:get_entity_name()] then
local vel = object:get_velocity()
if vector.equals(vel, {x=0, y=0, z=0}) then -- still standing?
return object
end
end
end
end
-- check if cart can be pushed
function minecart.check_cart_for_pushing(pos, param2, radius)
local pos2 = param2 and vector.add(pos, param2_to_dir[param2]) or pos
@ -73,17 +89,7 @@ function minecart.check_cart_for_pushing(pos, param2, radius)
return true
end
for _, object in pairs(minetest.get_objects_inside_radius(pos2, radius or 0.5)) do
--print(object:get_entity_name(), tValidCartEntities[object:get_entity_name()])
if tValidCartEntities[object:get_entity_name()] then
local vel = object:get_velocity()
if vector.equals(vel, {x=0, y=0, z=0}) then -- still standing?
return true
end
end
end
return false
return get_cart_object(pos2, radius) ~= nil
end
-- check if cargo can be loaded
@ -139,7 +145,7 @@ function minecart.take_items(pos, param2, num)
local owner = M(pos):get_string("owner")
local inv = minetest.get_inventory({type="node", pos=npos})
if def and inv and (not def.allow_take or def.allow_take(npos, nil, owner)) then
if def and inv and def.take_listname and (not def.allow_take or def.allow_take(npos, nil, owner)) then
return minecart.inv_take_items(inv, def.take_listname, num)
else
local ndef = minetest.registered_nodes[node.name]
@ -155,7 +161,7 @@ function minecart.put_items(pos, param2, stack)
local owner = M(pos):get_string("owner")
local inv = minetest.get_inventory({type="node", pos=npos})
if def and inv and (not def.allow_put or def.allow_put(npos, stack, owner)) then
if def and inv and def.put_listname and (not def.allow_put or def.allow_put(npos, stack, owner)) then
local leftover = inv:add_item(def.put_listname, stack)
if leftover:get_count() > 0 then
return leftover
@ -206,14 +212,12 @@ function minecart.punch_cart(pos, param2, radius, dir)
return true
end
for _, object in pairs(minetest.get_objects_inside_radius(pos2, radius or 0.5)) do
if tValidCartEntities[object:get_entity_name()] then
object:punch(object, 1.0, {
full_punch_interval = 1.0,
damage_groups = {fleshy = 1},
}, dir)
break -- start only one cart
end
local obj = get_cart_object(pos2, radius)
if obj then
obj:punch(obj, 1.0, {
full_punch_interval = 1.0,
damage_groups = {fleshy = 1},
}, dir)
end
end
@ -230,6 +234,44 @@ function minecart.register_inventory(node_names, def)
end
end
function minecart.register_cart_entity(entity_name, node_name, entity_def)
entity_def.velocity = {x=0, y=0, z=0} -- only used on punch
entity_def.old_dir = {x=1, y=0, z=0} -- random value to start the cart on punch
entity_def.old_pos = nil
entity_def.old_switch = 0
entity_def.node_name = node_name
minetest.register_entity(entity_name, entity_def)
-- register node for punching
minecart.register_cart_names(node_name, entity_name)
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname == "minecart:userID_node" then
if fields.exit == "Save" or fields.key_enter == "true" then
local cart_pos = S2P(player:get_meta():get_string("cart_pos"))
local userID = tonumber(fields.userID) or 0
M(cart_pos):set_int("userID", userID)
M(cart_pos):set_string("infotext", minetest.get_color_escape_sequence("#FFFF00")..player:get_player_name()..": "..userID)
minecart.node_at_station(player:get_player_name(), userID, cart_pos)
end
return true
end
if formname == "minecart:userID_entity" then
if fields.exit == "Save" or fields.key_enter == "true" then
local cart_pos = S2P(player:get_meta():get_string("cart_pos"))
local obj = get_cart_object(cart_pos)
if obj then
local entity = obj:get_luaentity()
entity.userID = tonumber(fields.userID) or 0
obj:set_nametag_attributes({color = "#ffff00", text = entity.owner..": "..entity.userID})
minecart.update_userID(entity.myID, entity.userID)
end
end
return true
end
return false
end)
minecart.register_inventory({"default:chest", "default:chest_open"}, {
put = {
listname = "main",

View File

@ -1,16 +1,17 @@
# textdomain: minecart
1. Place your rails and build a route with two endpoints. Junctions are allowed as long as each route has its own start and endpoint.=1. Baue eine Schienenstrecke mit zwei Enden. Kreuzungen sind zulässig, solange jede Route ihre eigenen Start- und Endpunkte hat.
10. Dig the empty cart with a second 'sneak+click' (as usual).=10. Klicke erneut mit gedrückter Shift-Taste auf den Wagen, um diesen zu entfernen.
10. Check the cart state via the chat command: /mycart <num>@n '<num>' is the cart number=Prüfe den Status des Wagen mit dem Chat Kommando: /mycart <num>@n <num> ist die Wagennummer
11. Drop items into the Minecart and punch the cart to start it, or 'sneak+click' the Minecart to get the items back.=11: Lege Gegenstände in ein Wagen (Taste Q) und starte dann den Wagen durch Anklicken. Klicke mit gedrückter Shift-Taste auf den Wagen, um Gegenstände wieder auszuladen.
12. Dig the empty cart with a second 'sneak+click' (as usual).=10. Klicke erneut mit gedrückter Shift-Taste auf den Wagen, um diesen zu entfernen.
2. Place a Railway Buffer at both endpoints (buffers are always needed, they store the route and timing information).=2. Platziere einen Prellbock an beide Schienenenden (Prellböcke sind zwingend notwendig, sie speichern die Routen- und Zeit-Informationen).
3. Give both Railway Buffers unique station names, like Oxford and Cambridge.=3. Gib beiden Prellböcken eindeutige Stationsnamen wie: Stuttgart und München.
4. Drive from buffer to buffer in both directions using a Minecart(!) to record the routes (use 'right-left' keys to control the Minecart).=4. Um eine Route aufzuzeichnen, fahre die Route in beide Richtungen von Prellbock zu Prellbock mit einem Minecart Wagen(!). Nutze 'links-rechts' Tasten zur Steuerung.
5. Punch the buffers to check the connection data (e.g. 'Oxford: connected to Cambridge').=5. Schlage auf die Prellböcke um die Verbindungsdaten zu prüfen (bspw.: 'München: verbunden mit Stuttgart')
6. Optional: Configure the Minecart stop time in one or both buffers. The Minecart will then start automatically after the configured time.=6. Optional: Konfiguriere die Wagenwartezeit in einem oder in beiden Prellböcken. Der Wagen startet dann nach dieser Zeit automatisch.
7. Optional: Protect your rail network with the Protection Landmarks (one Landmark at least every 16 nodes/meters).=7. Optional: Schütze deine Schienen mit Hilfe der Meilensteine (ein Meilenstein mindestens alle 16 Blöcke).
8. Place a Minecart in front of the buffer and check whether it starts after the configured time.=8. Platziere einen Wagen direkt vor einem Prellbock und prüfe, ob er nach der konfigurierten Zeit startet.
9. Drop items into the Minecart and punch the cart to start it, or 'sneak+click' the Minecart to get the items back.=9: Lege Gegenstände in ein Wagen (Taste Q) und starte dann den Wagen durch Anklicken. Klicke mit gedrückter Shift-Taste auf den Wagen, um Gegenstände wieder auszuladen.
A minecart running through unloaded areas, mainly used for item transportation=Ein Wagen, welcher auch durch nicht geladene Kartenbereiche fährt, primär für den Transport von Gegenständen genutzt (Lore)
4. Place a Minecart at a buffer and give it a cart number (1..999)=4. Platziere einen Minecart Wagen an einem Prellbock und gib dem Wagen eine Wagennummer (1..999)
5. Drive from buffer to buffer in both directions using the Minecart(!) to record the routes (use 'right-left' keys to control the Minecart).=5. Um eine Route aufzuzeichnen, fahre die Route in beide Richtungen von Prellbock zu Prellbock mit dem Minecart Wagen(!). Nutze 'links-rechts' Tasten zur Steuerung.
6. Punch the buffers to check the connection data (e.g. 'Oxford: connected to Cambridge').=6. Schlage auf die Prellböcke um die Verbindungsdaten zu prüfen (bspw.: 'München: verbunden mit Stuttgart')
7. Optional: Configure the Minecart stop time in one or both buffers. The Minecart will then start automatically after the configured time.=7. Optional: Konfiguriere die Wagenwartezeit in einem oder in beiden Prellböcken. Der Wagen startet dann nach dieser Zeit automatisch.
8. Optional: Protect your rail network with the Protection Landmarks (one Landmark at least every 16 nodes/meters).=8. Optional: Schütze deine Schienen mit Hilfe der Meilensteine (ein Meilenstein mindestens alle 16 Blöcke).
9. Place a Minecart in front of the buffer and check whether it starts after the configured time.=9. Platziere einen Wagen direkt vor einem Prellbock und prüfe, ob er nach der konfigurierten Zeit startet.
Allow to dig/place rails in Minecart Landmark areas=Erlaubt dir, Schienen in Meilensteinbereichen zu setzen/zu entfernen
Minecart=Minecart
Minecart (Sneak+Click to pick up)=Minecart (Shift+Klick zum Entfernen des Carts)
@ -27,9 +28,10 @@ Summary=Zusammenfassung
Used as buffer on both rail ends. Needed to be able to record the cart routes=Preckblöcke müssen an beiden Schienenenden platziert sein, so dass Aufzeichnungen der Strecke gemacht werden können.
Used to load/unload Minecart. The Hopper can push/pull items to/from chests and drop/pickup items to/from Minecarts. To unload a Minecart place the hopper below the rail. To load the Minecart, place the hopper right next to the Minecart.=Um Wagen zu be- und entladen. Der Hopper kann Gegenstände aus Kisten Holen und legen, sowie diese in Wagen fallen lassen bzw. aus Wagen entnehmen. Um einen Wagen zu entladen, muss der Hopper unter die Schiene platziert werden. Um einen Wagen zu beladen, muss der Hopper direkt neben die Schiene platziert werden.
[minecart] Area is protected!=[minecart] Bereich ist geschützt!
[minecart] Please start at a Railway Buffer!=[minecart] Bitte starte beim Prellbock!
[minecart] Cart is protected by = Wagen ist geschützt durch
[minecart] Recording canceled!=[minecart] Aufzeichnung abgebrochen!
[minecart] Route stored!=[minecart] Strecke gespeichert
[minecart] Start route recording!=[minecart] Starte die Streckenaufzeichnung!
connected to=verbunden mit
##### not used anymore #####

View File

@ -1,13 +1,15 @@
1. Place your rails and build a route with two endpoints. Junctions are allowed as long as each route has its own start and endpoint.=
10. Dig the empty cart with a second 'sneak+click' (as usual).=
10. Check the cart state via the chat command: /mycart <num>@n '<num>' is the cart number=
11. Drop items into the Minecart and punch the cart to start it, or 'sneak+click' the Minecart to get the items back.=
12. Dig the empty cart with a second 'sneak+click' (as usual).=
2. Place a Railway Buffer at both endpoints (buffers are always needed, they store the route and timing information).=
3. Give both Railway Buffers unique station names, like Oxford and Cambridge.=
4. Drive from buffer to buffer in both directions using a Minecart(!) to record the routes (use 'right-left' keys to control the Minecart).=
5. Punch the buffers to check the connection data (e.g. 'Oxford: connected to Cambridge').=
6. Optional: Configure the Minecart stop time in one or both buffers. The Minecart will then start automatically after the configured time.=
7. Optional: Protect your rail network with the Protection Landmarks (one Landmark at least every 16 nodes/meters).=
8. Place a Minecart in front of the buffer and check whether it starts after the configured time.=
9. Drop items into the Minecart and punch the cart to start it, or 'sneak+click' the Minecart to get the items back.=
4. Place a Minecart at a buffer and give it a cart number (1..999)=
5. Drive from buffer to buffer in both directions using the Minecart(!) to record the routes (use 'right-left' keys to control the Minecart).=
6. Punch the buffers to check the connection data (e.g. 'Oxford: connected to Cambridge').=
7. Optional: Configure the Minecart stop time in one or both buffers. The Minecart will then start automatically after the configured time.=
8. Optional: Protect your rail network with the Protection Landmarks (one Landmark at least every 16 nodes/meters).=
9. Place a Minecart in front of the buffer and check whether it starts after the configured time.=
Allow to dig/place rails in Minecart Landmark areas=
Minecart=
Minecart (Sneak+Click to pick up)=
@ -24,7 +26,7 @@ Summary=
Used as buffer on both rail ends. Needed to be able to record the cart routes=
Used to load/unload Minecart. The Hopper can push/pull items to/from chests and drop/pickup items to/from Minecarts. To unload a Minecart place the hopper below the rail. To load the Minecart, place the hopper right next to the Minecart.=
[minecart] Area is protected!=
[minecart] Please start at a Railway Buffer!=
[minecart] Cart is protected by =
[minecart] Recording canceled!=
[minecart] Route stored!=
[minecart] Start route recording!=

84
minecart/minecart.lua Normal file
View File

@ -0,0 +1,84 @@
--[[
Minecart
========
Copyright (C) 2019-2020 Joachim Stolberg
MIT
See license.txt for more information
]]--
local S = minecart.S
local MP = minetest.get_modpath("minecart")
local lib = dofile(MP.."/cart_lib1.lua")
lib:init(false)
local cart_entity = {
initial_properties = {
physical = false, -- otherwise going uphill breaks
collisionbox = {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5},
visual = "mesh",
mesh = "carts_cart.b3d",
visual_size = {x=1, y=1},
textures = {"carts_cart.png^minecart_cart.png"},
static_save = false,
},
------------------------------------ changed
owner = nil,
------------------------------------ changed
driver = nil,
punched = false, -- used to re-send velocity and position
velocity = {x=0, y=0, z=0}, -- only used on punch
old_dir = {x=1, y=0, z=0}, -- random value to start the cart on punch
old_pos = nil,
old_switch = 0,
railtype = nil,
cargo = {},
on_rightclick = lib.on_rightclick,
on_activate = lib.on_activate,
on_detach_child = lib.on_detach_child,
on_punch = lib.on_punch,
on_step = lib.on_step,
}
minetest.register_entity("minecart:cart", cart_entity)
minecart.register_cart_names("minecart:cart", "minecart:cart")
minetest.register_craftitem("minecart:cart", {
description = S("Minecart (Sneak+Click to pick up)"),
inventory_image = minetest.inventorycube("carts_cart_top.png", "carts_cart_side.png^minecart_logo.png", "carts_cart_side.png^minecart_logo.png"),
wield_image = "carts_cart_side.png",
on_place = function(itemstack, placer, pointed_thing)
-- use cart as tool
local under = pointed_thing.under
local node = minetest.get_node(under)
local udef = minetest.registered_nodes[node.name]
if udef and udef.on_rightclick and
not (placer and placer:is_player() and
placer:get_player_control().sneak) then
return udef.on_rightclick(under, node, placer, itemstack,
pointed_thing) or itemstack
end
if not pointed_thing.type == "node" then
return
end
return lib.add_cart(itemstack, placer, pointed_thing, "minecart:cart")
end,
})
minetest.register_craft({
output = "minecart:cart",
recipe = {
{"default:steel_ingot", "default:cobble", "default:steel_ingot"},
{"default:steel_ingot", "default:steel_ingot", "default:steel_ingot"},
},
})

333
minecart/monitoring.lua Normal file
View File

@ -0,0 +1,333 @@
--[[
Minecart
========
Copyright (C) 2019-2020 Joachim Stolberg
MIT
See license.txt for more information
]]--
-- Some notes:
-- 1) Entity IDs are volatile. For each server restart all carts get new IDs.
-- 2) Monitoring is performed for entities only. Stopped carts in form of
-- real nodes need no monitoring.
-- 3) But nodes at startions have to call 'node_at_station' to be "visible"
-- for the chat commands
-- for lazy programmers
local M = minetest.get_meta
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local S2P = minetest.string_to_pos
local S = minecart.S
local MP = minetest.get_modpath("minecart")
local lib = dofile(MP.."/cart_lib3.lua")
local CartsOnRail = minecart.CartsOnRail -- from storage.lua
local get_route = minecart.get_route -- from storage.lua
local NodesAtStation = {}
--
-- Helper functions
--
local function calc_pos_and_vel(item)
if item.start_time and item.start_key then -- cart on recorded route
local run_time = minetest.get_gametime() - item.start_time
local waypoints = get_route(item.start_key).waypoints
local waypoint = waypoints[run_time]
if waypoint then
return S2P(waypoint[1]), S2P(waypoint[2])
end
end
if item.last_pos then
item.last_pos = vector.round(item.last_pos)
if carts:is_rail(item.last_pos, minetest.raillike_group("rail")) then
return item.last_pos, item.last_vel
end
item.last_pos.y = item.last_pos.y - 1
if carts:is_rail(item.last_pos, minetest.raillike_group("rail")) then
return item.last_pos, item.last_vel
end
end
return item.start_pos, {x=0, y=0, z=0}
end
--
-- Monitoring of cart entities
--
function minecart.add_to_monitoring(obj, myID, owner, userID)
local pos = vector.round(obj:get_pos())
CartsOnRail[myID] = {
start_key = lib.get_route_key(pos),
start_pos = pos,
owner = owner, -- needed for query API
userID = userID, -- needed for query API
stopped = true,
entity_name = obj:get_entity_name()
}
end
-- Called after cart number formspec is closed
function minecart.update_userID(myID, userID)
if CartsOnRail[myID] then
CartsOnRail[myID].userID = userID
end
end
-- When cart entity is removed
function minecart.remove_from_monitoring(myID)
if myID then
CartsOnRail[myID] = nil
end
end
-- For node carts at stations
function minecart.node_at_station(owner, userID, pos)
NodesAtStation[owner] = NodesAtStation[owner] or {}
NodesAtStation[owner][userID] = pos
end
function minecart.start_cart(pos, myID)
local item = CartsOnRail[myID]
if item and item.stopped then
item.stopped = false
item.start_pos = pos
-- cart started from a buffer?
local start_key = lib.get_route_key(pos)
if start_key then
item.start_time = minetest.get_gametime()
item.start_key = start_key
item.junctions = minecart.get_route(start_key).junctions
return true
end
end
return false
end
function minecart.stop_cart(pos, myID)
local item = CartsOnRail[myID]
if item and not item.stopped then
item.start_time = nil
item.start_key = nil
item.start_pos = nil
item.junctions = nil
item.stopped = true
return true
end
return false
end
local function monitoring()
local to_be_added = {}
for key, item in pairs(CartsOnRail) do
local entity = minetest.luaentities[key]
--print("Cart:", key, item.owner, item.myID, item.userID, item.stopped)
if entity then -- cart entity running
local pos = entity.object:get_pos()
local vel = entity.object:get_velocity()
if not minetest.get_node_or_nil(pos) then -- unloaded area
lib.unload_cart(pos, vel, entity, item)
item.stopped = minecart.stopped(vel)
end
-- store last pos from cart
item.last_pos, item.last_vel = pos, vel
else -- no cart running
local pos, vel = calc_pos_and_vel(item)
if pos and vel then
if minetest.get_node_or_nil(pos) then -- loaded area
local myID = lib.load_cart(pos, vel, item)
if myID then
item.stopped = minecart.stopped(vel)
to_be_added[myID] = table.copy(item)
CartsOnRail[key] = nil -- invalid old ID
end
end
item.last_pos, item.last_vel = pos, vel
else
CartsOnRail[key] = nil
end
end
end
-- table maintenance
for key,val in pairs(to_be_added) do
CartsOnRail[key] = val
end
minetest.after(1, monitoring)
end
-- delay the start to prevent cart disappear into nirvana
minetest.register_on_mods_loaded(function()
minetest.after(10, monitoring)
end)
--
-- API functions
--
-- Return a list of carts with current position and speed.
function minecart.get_cart_list()
local tbl = {}
for id, item in pairs(CartsOnRail) do
local pos, speed = calc_pos_and_vel(item)
tbl[#tbl+1] = {pos = pos, speed = speed, id = id}
end
return tbl
end
local function get_cart_pos(query_pos, cart_pos)
local dist = math.floor(vector.distance(cart_pos, query_pos))
local station = lib.get_station_name(cart_pos)
return station or dist
end
local function get_cart_state(name, userID)
for id, item in pairs(CartsOnRail) do
if item.owner == name and item.userID == userID then
return item.stopped and "stopped" or "running", item.last_pos
end
end
return nil, nil
end
minetest.register_chatcommand("mycart", {
params = "<cart-num>",
description = "Output cart state and position, or a list of carts, if no cart number is given.",
func = function(name, param)
local userID = tonumber(param)
local query_pos = minetest.get_player_by_name(name):get_pos()
if userID then
-- First check if it is a node cart at a station
local cart_pos = NodesAtStation[name] and NodesAtStation[name][userID]
if cart_pos then
local pos = get_cart_pos(query_pos, cart_pos)
return true, "Cart #"..userID.." stopped at "..pos.." "
end
-- Check all running carts
local state, cart_pos = get_cart_state(name, userID)
if state then
local pos = get_cart_pos(query_pos, cart_pos)
if type(pos) == "string" then
return true, "Cart #"..userID.." stopped at "..pos.." "
elseif state == "running" then
return true, "Cart #"..userID.." running "..pos.." m away "
else
return true, "Cart #"..userID.." stopped "..pos.." m away "
end
end
return false, "Cart #"..userID.." is unknown"
else
-- Output a list with all numbers
local tbl = {}
for userID, pos in pairs(NodesAtStation[name] or {}) do
tbl[#tbl + 1] = userID
end
for id, item in pairs(CartsOnRail) do
if item.owner == name then
tbl[#tbl + 1] = item.userID
end
end
return true, "List of carts: "..table.concat(tbl, ", ").." "
end
end
})
function minecart.cmnd_cart_state(name, userID)
-- First check if it is a node cart at a station
local pos = NodesAtStation[name] and NodesAtStation[name][userID]
if pos then
return "stopped"
end
return get_cart_state(name, userID)
end
function minecart.cmnd_cart_location(name, userID, query_pos)
-- First check if it is a node cart at a station
local station = NodesAtStation[name] and NodesAtStation[name][userID]
if station then
return station
end
local state, cart_pos = get_cart_state(name, userID)
if state then
return get_cart_pos(query_pos, cart_pos)
end
end
minetest.register_on_mods_loaded(function()
if minetest.global_exists("techage") then
techage.icta_register_condition("cart_state", {
title = "read cart state",
formspec = {
{
type = "digits",
name = "number",
label = "cart number",
default = "",
},
{
type = "label",
name = "lbl",
label = "Read state from one of your carts",
},
},
button = function(data, environ) -- default button label
local number = tonumber(data.number) or 0
return 'cart_state('..number..')'
end,
code = function(data, environ)
local s = 'minecart.cmnd_cart_state("%s", %u)'
local number = tonumber(data.number) or 0
return string.format(s, environ.owner, number), "~= 0"
end,
})
techage.icta_register_condition("cart_location", {
title = "read cart location",
formspec = {
{
type = "digits",
name = "number",
label = "cart number",
default = "",
},
{
type = "label",
name = "lbl",
label = "Read location from one of your carts",
},
},
button = function(data, environ) -- default button label
local number = tonumber(data.number) or 0
return 'cart_loc('..number..')'
end,
code = function(data, environ)
local s = 'minecart.cmnd_cart_location("%s", %u, env.pos)'
local number = tonumber(data.number) or 0
return string.format(s, environ.owner, number), "~= 0"
end,
})
techage.lua_ctlr.register_function("cart_state", {
cmnd = function(self, num)
num = tonumber(num) or 0
return minecart.cmnd_cart_state(self.meta.owner, num)
end,
help = " $cart_state(num)\n"..
" Read state from one of your carts.\n"..
' "num" is the cart number\n'..
' example: sts = $cart_state(2)'
})
techage.lua_ctlr.register_function("cart_location", {
cmnd = function(self, num)
num = tonumber(num) or 0
return minecart.cmnd_cart_location(self.meta.owner, num, self.meta.pos)
end,
help = " $cart_location(num)\n"..
" Read location from one of your carts.\n"..
' "num" is the cart number\n'..
' example: sts = $cart_location(2)'
})
end
end)

89
minecart/recording.lua Normal file
View File

@ -0,0 +1,89 @@
--[[
Minecart
========
Copyright (C) 2019-2020 Joachim Stolberg
MIT
See license.txt for more information
]]--
-- for lazy programmers
local M = minetest.get_meta
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local S2P = minetest.string_to_pos
local S = minecart.S
local MP = minetest.get_modpath("minecart")
local lib = dofile(MP.."/cart_lib3.lua")
local CartsOnRail = minecart.CartsOnRail -- from storage.lua
local get_route = minecart.get_route -- from storage.lua
--
-- Route recording
--
function minecart.start_recording(self, pos)
self.start_key = lib.get_route_key(pos, self.driver)
if self.start_key then
self.waypoints = {}
self.junctions = {}
self.recording = true
self.next_time = minetest.get_us_time() + 1000000
minetest.chat_send_player(self.driver, S("[minecart] Start route recording!"))
end
end
function minecart.store_next_waypoint(self, pos, vel)
if self.start_key and self.recording and self.driver and
self.next_time < minetest.get_us_time() then
self.next_time = minetest.get_us_time() + 1000000
self.waypoints[#self.waypoints+1] = {P2S(vector.round(pos)), P2S(vector.round(vel))}
elseif self.recording and not self.driver then
self.recording = false
self.waypoints = nil
self.junctions = nil
end
end
-- destination reached(speed == 0)
function minecart.stop_recording(self, pos, vel, puncher)
local dest_pos = lib.get_route_key(pos, self.driver)
if dest_pos then
if self.start_key ~= dest_pos then
local route = {
waypoints = self.waypoints,
dest_pos = dest_pos,
junctions = self.junctions,
}
minecart.store_route(self.start_key, route)
minetest.chat_send_player(self.driver, S("[minecart] Route stored!"))
else
minetest.chat_send_player(self.driver, S("[minecart] Recording canceled!"))
end
else
minetest.chat_send_player(self.driver, S("[minecart] Recording canceled!"))
end
self.recording = false
self.waypoints = nil
self.junctions = nil
end
function minecart.set_junction(self, pos, dir, switch_keys)
if self.junctions then
self.junctions[P2S(vector.round(pos))] = {dir, switch_keys}
end
end
function minecart.get_junction(self, pos, dir)
local junctions = CartsOnRail[self.myID] and CartsOnRail[self.myID].junctions
if junctions then
local data = junctions[P2S(vector.round(pos))]
if data then
return data[1], data[2]
end
end
return dir
end

View File

@ -1,291 +0,0 @@
--[[
Minecart
========
Copyright (C) 2019-2020 Joachim Stolberg
MIT
See license.txt for more information
]]--
-- for lazy programmers
local M = minetest.get_meta
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local S2P = minetest.string_to_pos
local S = minecart.S
local CartsOnRail = minecart.CartsOnRail
--
-- Helper functions
--
local function get_object_id(object)
for id, entity in pairs(minetest.luaentities) do
if entity.object == object then
return id
end
end
end
local function get_route_key(pos, player_name)
local pos1 = minetest.find_node_near(pos, 1, {"minecart:buffer"})
if pos1 then
local meta = minetest.get_meta(pos1)
if player_name == nil or player_name == meta:get_string("owner") then
return P2S(pos1)
end
end
end
--
-- Recording
--
function minecart.add_cart_to_monitoring(obj, owner, cargo)
--print("add_cart_to_monitoring", dump(cargo))
local self = obj:get_luaentity()
self.myID = get_object_id(obj)
self.owner = owner
local pos = self.object:get_pos()
CartsOnRail[self.myID] = {
start_key = get_route_key(pos),
start_pos = pos,
stopped = true,
owner = owner,
entity_name = self.name,
cargo = cargo,
}
return self.myID
end
function minecart.start_recording(self, pos, vel, puncher)
-- Player punches cart to start the trip
if puncher:get_player_name() == self.driver and vector.equals(vel, {x=0, y=0, z=0}) then
self.start_key = get_route_key(pos, self.driver)
if self.start_key then
self.waypoints = {}
self.junctions = {}
self.recording = true
self.next_time = minetest.get_us_time() + 1000000
minetest.chat_send_player(self.driver, S("[minecart] Start route recording!"))
end
end
end
function minecart.store_next_waypoint(self, pos, vel)
if self.start_key and self.recording and self.driver and
self.next_time < minetest.get_us_time() then
self.next_time = minetest.get_us_time() + 1000000
self.waypoints[#self.waypoints+1] = {P2S(vector.round(pos)), P2S(vector.round(vel))}
local dest_pos = get_route_key(pos, self.driver)
if vector.equals(vel, {x=0, y=0, z=0}) and dest_pos then
if self.start_key ~= dest_pos then
local route = {
waypoints = self.waypoints,
dest_pos = dest_pos,
junctions = self.junctions,
}
minecart.store_route(self.start_key, route)
minetest.chat_send_player(self.driver, S("[minecart] Route stored!"))
else
minetest.chat_send_player(self.driver, S("[minecart] Recording canceled!"))
end
self.recording = false
self.waypoints = nil
self.junctions = nil
end
elseif self.recording and not self.driver then
self.recording = false
self.waypoints = nil
self.junctions = nil
end
end
function minecart.set_junction(self, pos, dir, switch_keys)
local junctions = CartsOnRail[self.myID] and CartsOnRail[self.myID].junctions
if junctions then
if self.junctions then
self.junctions[minetest.pos_to_string(vector.round(pos))] = {dir, switch_keys}
end
end
end
function minecart.get_junction(self, pos, dir)
local junctions = CartsOnRail[self.myID] and CartsOnRail[self.myID].junctions
if junctions then
local data = junctions[minetest.pos_to_string(vector.round(pos))]
if data then
return data[1], data[2]
end
end
return dir
end
--
-- Normal operation
--
function minecart.start_run(self, pos, vel, driver)
if vector.equals(vel, {x=0, y=0, z=0}) then
local start_key = get_route_key(pos)
if not start_key then
if driver then -- Punched from inside the cart
-- Don't start the cart
self.velocity = {x=0, y=0, z=0}
minetest.chat_send_player(driver, S("[minecart] Please start at a Railway Buffer!"))
return
end
-- Add also carts without route to be able to restore last pos/vel
minetest.log("info", "[minecart] Cart "..self.myID.." started.")
--print("start_run", dump(CartsOnRail[self.myID]))
CartsOnRail[self.myID].stopped = false
else -- Add cart to monitoring
minetest.log("info", "[minecart] Cart "..self.myID.." started.")
--print("start_run", dump(CartsOnRail[self.myID]))
local item = CartsOnRail[self.myID]
item.start_time = minetest.get_gametime()
item.start_key = start_key
item.stopped = false
item.junctions = minecart.get_route(start_key).junctions
end
end
end
function minecart.attach_cargo(self, pos)
local data = CartsOnRail[self.myID]
if data then
data.attached_items = {}
for _, obj_ in pairs(minetest.get_objects_inside_radius(pos, 1)) do
local entity = obj_:get_luaentity()
if not obj_:is_player() and entity and entity.name == "__builtin:item" then
obj_:remove()
data.attached_items[#data.attached_items + 1] = entity.itemstring
end
end
end
end
function minecart.detach_cargo(self, pos, data)
-- Spawn loaded items again
if data.attached_items then
for _,item in ipairs(data.attached_items) do
minetest.add_item(pos, ItemStack(item))
end
end
end
function minecart.stopped(self, pos)
local data = CartsOnRail[self.myID]
if data and not data.stopped then
minecart.detach_cargo(self, pos, data)
data.stopped = true
data.start_key = get_route_key(pos)
data.start_pos = pos
data.start_time = nil
minetest.log("info", "[minecart] Cart "..self.myID.." stopped.")
if self.sound_handle then
minetest.sound_stop(self.sound_handle)
end
return data.cargo or {} -- for node based carts
end
end
function minecart.on_dig(self)
if self and self.myID then
CartsOnRail[self.myID] = nil
end
end
--
-- Monitoring
--
local function spawn_cart(pos, vel, item)
-- local node = minetest.get_node(pos)
-- if not minetest.tValidCarts[node.name] then
local pos2 = vector.round(pos)
if carts:is_rail(pos2) or carts:is_rail({x = pos2.x, y = pos2.y-1, z = pos2.z}) then
local obj = minetest.add_entity(pos, item.entity_name or "minecart:cart", nil)
obj:set_velocity(vel)
local id = minecart.add_cart_to_monitoring(obj, item.owner)
minetest.log("info", "[minecart] Cart "..id.." spawned again.")
return id
end
end
local function calc_pos_and_vel(item)
if item.start_time and item.start_key then
local run_time = minetest.get_gametime() - item.start_time
local waypoints = minecart.get_route(item.start_key).waypoints
local waypoint = waypoints[run_time]
if waypoint then
return minetest.string_to_pos(waypoint[1]), minetest.string_to_pos(waypoint[2])
end
end
if item.last_pos then
return item.last_pos, item.last_vel
end
return item.start_pos, {x=0, y=0, z=0}
end
local function monitoring()
local to_be_added = {}
for key, item in pairs(CartsOnRail) do
--print("Cart:", key, P2S(item.start_pos), item.owner)
if not item.recording then
local entity = minetest.luaentities[key]
if entity then -- cart in loaded area
local pos = entity.object:get_pos()
local vel = entity.object:get_velocity()
if not minetest.get_node_or_nil(pos) then -- in unloaded area
minetest.log("info", "[minecart] Cart "..key.." virtualized.")
if entity.sound_handle then
minetest.sound_stop(entity.sound_handle)
end
if vector.equals(vel, {x=0, y=0, z=0}) then
minecart.attach_cargo(entity, pos)
end
entity.object:remove()
end
-- store last pos from cart without route
item.last_pos, item.last_vel = pos, vel
else -- cart in unloaded area
local pos, vel = calc_pos_and_vel(item)
if pos and vel then
if minetest.get_node_or_nil(pos) then -- in loaded area
local id = spawn_cart(pos, vel, item)
if id then
to_be_added[id] = table.copy(item)
CartsOnRail[key] = nil
end
end
else
CartsOnRail[key] = nil
end
end
end
end
-- table maintenance
for key,val in pairs(to_be_added) do
CartsOnRail[key] = val
end
minetest.after(1, monitoring)
end
minetest.after(1, monitoring)
minecart.calc_pos_and_vel = calc_pos_and_vel
--
-- API function to get a list of cart data with current position and speed.
--
function minecart.get_cart_list()
local tbl = {}
for id, item in pairs(CartsOnRail) do
local pos, speed = calc_pos_and_vel(item)
tbl[#tbl+1] = {pos = pos, speed = speed, id = id}
end
return tbl
end
-- minecart.get_route_key(pos, player_name)
minecart.get_route_key = get_route_key

View File

@ -279,6 +279,9 @@ signs_bot.register_botcommand("pause", {
mem.steps = nil
return signs_bot.DONE
end
if mem.capa then
mem.capa = mem.capa + 1
end
return signs_bot.BUSY
end,
})
@ -289,6 +292,9 @@ signs_bot.register_botcommand("stop", {
num_param = 0,
description = I("Stop the robot."),
cmnd = function(base_pos, mem, slot)
if mem.capa then
mem.capa = mem.capa + 2
end
return signs_bot.DONE
end,
})

View File

@ -203,7 +203,7 @@ signs_bot.register_botcommand("place_above", {
local function dig_item(base_pos, robot_pos, param2, slot, route, level)
local pos1 = lib.dest_pos(robot_pos, param2, route)
pos1.y = pos1.y + level
pos1.y = pos1.y + (level or 0)
local node = lib.get_node_lvm(pos1)
local dug_name = lib.is_simple_node(node)
if not lib.not_protected(base_pos, pos1) then

View File

@ -233,6 +233,8 @@ local function move(mem, any_sensor)
activate_sensor(mem.robot_pos, (mem.robot_param2 + 1) % 4)
activate_sensor(mem.robot_pos, (mem.robot_param2 + 3) % 4)
end
elseif mem.capa then
mem.capa = mem.capa + 1
end
end

View File

@ -41,8 +41,10 @@ end
local function get_line_tokens(script)
local idx = 0
local lines = string.split(script or ""
, "\n", true)
script = script or ""
script = script:gsub("\r\n", "\n")
script = script:gsub("\r", "\n")
local lines = string.split(script, "\n", true)
return function()
while idx < #lines do
idx = idx + 1
@ -95,6 +97,7 @@ end
local function pass1(tokens)
local pc = 1
tSymbolTbl = {}
for _, token in ipairs(tokens) do
if token:find("%w+:") then
tSymbolTbl[token] = pc
@ -129,6 +132,9 @@ end
-- Commands
-------------------------------------------------------------------------------
local function register_command(cmnd_name, num_param, cmnd_func, check_func)
assert(num_param, cmnd_name..": num_param = "..dump(num_param))
assert(cmnd_func, cmnd_name..": cmnd_func = "..dump(cmnd_func))
assert(check_func or num_param == 0, cmnd_name..": check_func = "..dump(check_func))
lCmdLookup[#lCmdLookup + 1] = {num_param, cmnd_func, cmnd_name}
tCmdDef[cmnd_name] = {
num_param = num_param,
@ -174,6 +180,9 @@ register_command("call", 1,
mem.Stack[#mem.Stack + 1] = mem.pc + 2
mem.pc = addr - 2
return api.DONE
end,
function(addr)
return tSymbolTbl[addr..":"]
end
)
@ -192,6 +201,9 @@ register_command("jump", 1,
function(base_pos, mem, addr)
mem.pc = addr - 2
return api.DONE
end,
function(addr)
return tSymbolTbl[addr..":"]
end
)
@ -205,14 +217,19 @@ register_command("exit", 0,
-- API functions
-------------------------------------------------------------------------------
function api.register_command(cmnd_name, num_param, cmnd_func)
register_command(cmnd_name, num_param, cmnd_func)
function api.register_command(cmnd_name, num_param, cmnd_func, check_func)
register_command(cmnd_name, num_param, cmnd_func, check_func)
end
-- function returns: true/false, error_string, line-num
function api.check_script(script)
local tbl = {}
local num_token = 0
-- to fill the symbol table
local tokens = tokenizer(script)
pass1(tokens)
for idx, cmnd, param1, param2, param3 in get_line_tokens(script) do
if tCmdDef[cmnd] then
num_token = num_token + 1 + tCmdDef[cmnd].num_param
@ -222,7 +239,11 @@ function api.check_script(script)
param1 = tonumber(param1) or param1
param2 = tonumber(param2) or param2
param3 = tonumber(param3) or param3
if tCmdDef[cmnd].check and not tCmdDef[cmnd].check(param1, param2, param3) then
local num_param = (param1 and 1 or 0) + (param2 and 1 or 0) + (param3 and 1 or 0)
if tCmdDef[cmnd].num_param ~= num_param then
return false, I("Wrong number of parameters"), idx
end
if tCmdDef[cmnd].num_param > 0 and not tCmdDef[cmnd].check(param1, param2, param3) then
return false, I("Parameter error"), idx
end
elseif not cmnd:find("%w+:") then
@ -275,4 +296,4 @@ function api.reset_script(base_pos, mem)
mem.Stack = {}
end
return api
return api

View File

@ -141,6 +141,7 @@ end
-- Check rights before node is dug or inventory is used
function signs_bot.lib.not_protected(base_pos, pos)
local me = M(base_pos):get_string("owner")
minetest.load_area(pos)
if minetest.is_protected(pos, me) then
return false
end

View File

@ -35,7 +35,7 @@ Textures: CC BY-SA 3.0
### Dependencies
Required: default, doors, bucket, stairs, screwdriver, basic_materials, tubelib2, minecart, lcdlib, safer_lua
Recommended: signs_bot, hyperloop, compost, techpack_stairway, autobahn
Optional: unified_inventory, wielded_light, unifieddyes, lua-mashal, lsqlite3
Optional: unified_inventory, wielded_light, unifieddyes, lua-mashal, lsqlite3, moreores
The mods `default`, `doors`, `bucket`, `stairs`, and `screwdriver` are part of Minetest Game.
@ -88,6 +88,6 @@ to 'lsqlite3' and 'lua-marshal', but there is no way back, so:
- 2020-05-22 V0.08 * Support for 'lua-marshal' and 'lsqlite3' added
- 2020-05-31 V0.09 * TA4 tubes upgraded, manuals updated
- 2020-06-04 V0.10 * minor changes and bugfixes
- 2020-06-14 V0.11 * cart commands added for both controllers, support for moreores added
- 2020-06-17 V0.12 * Ethereal support added, manual correction, tin ingot recipe bugfix

View File

@ -42,7 +42,7 @@ local function can_dig(pos, player)
end
local function after_dig_node(pos, oldnode, oldmetadata, digger)
techage.remove_node(pos)
techage.remove_node(pos, oldnode, oldmetadata)
end
local function formspec2()

View File

@ -197,7 +197,7 @@ function techage.register_consumer(base_name, inv_name, tiles, tNode, validState
if crd.power_netw then
crd.power_netw:after_dig_node(pos)
end
techage.remove_node(pos)
techage.remove_node(pos, oldnode, oldmetadata)
techage.del_mem(pos)
end

View File

@ -436,7 +436,7 @@ local function on_rotate(pos, node, user, mode, new_param2)
end
local function after_dig_node(pos, oldnode, oldmetadata, digger)
techage.remove_node(pos)
techage.remove_node(pos, oldnode, oldmetadata)
convert_to_chest_again(pos, oldnode, digger)
end

View File

@ -18,6 +18,7 @@ local S = function(pos) if pos then return minetest.pos_to_string(pos) end end
--local M = minetest.get_meta
local NodeInfoCache = {}
local NumbersToBeRecycled = {}
local MP = minetest.get_modpath("techage")
local techage_use_sqlite = minetest.settings:get_bool('techage_use_sqlite', false)
@ -210,17 +211,19 @@ function techage.add_node(pos, name)
if item_handling_node(name) then
Tube:after_place_node(pos)
end
-- store position
return get_number(pos, true)
local key = minetest.hash_node_position(pos)
return NumbersToBeRecycled[key] or get_number(pos, true)
end
-- Function removes the node from the techage lists.
function techage.remove_node(pos)
local number = get_number(pos)
function techage.remove_node(pos, oldnode, oldmetadata)
local number = oldmetadata and oldmetadata.fields and oldmetadata.fields.node_number
number = number or get_number(pos)
if number then
local key = minetest.hash_node_position(pos)
NumbersToBeRecycled[key] = number
local ninfo = NodeInfoCache[number] or update_nodeinfo(number)
if ninfo then
backend.del_nodepos(number)
NodeInfoCache[number] = nil
if item_handling_node(ninfo.name) then
Tube:after_dig_node(pos)

View File

@ -80,6 +80,9 @@ end
local Version = storage:get_int("Version") or 0
local NextNumber = 0
if Version == 0 then
Version = 4
end
if Version == 3 then
Version = 4
NextNumber = storage:get_int("NextNumber")

View File

@ -17,6 +17,10 @@ local M = minetest.get_meta
local S = techage.S
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local S2P = minetest.string_to_pos
local MP = minetest.get_modpath("minecart")
local cart = dofile(MP.."/cart_lib1.lua")
cart:init(true)
local function formspec()
return "size[8,6]"..
@ -31,7 +35,8 @@ end
local function can_dig(pos, player)
local owner = M(pos):get_string("owner")
if owner ~= "" and owner ~= player:get_player_name() then
if owner ~= "" and (owner ~= player:get_player_name() or
not minetest.check_player_privs(player:get_player_name(), "minecart")) then
return false
end
local inv = minetest.get_meta(pos):get_inventory()
@ -91,12 +96,11 @@ minetest.register_node("techage:chest_cart", {
end,
on_place = function(itemstack, placer, pointed_thing)
return minecart.node_on_place(itemstack, placer, pointed_thing,
"techage:chest_cart")
return cart.add_cart(itemstack, placer, pointed_thing, "techage:chest_cart")
end,
on_punch = function(pos, node, puncher, pointed_thing)
minecart.node_on_punch(pos, node, puncher, pointed_thing, "techage:chest_cart_entity")
cart.node_on_punch(pos, node, puncher, pointed_thing, "techage:chest_cart_entity")
end,
set_cargo = function(pos, data)
@ -133,9 +137,9 @@ minecart.register_cart_entity("techage:chest_cart_entity", "techage:chest_cart",
visual_size = {x=0.66, y=0.66, z=0.66},
static_save = false,
},
on_activate = minecart.on_activate,
on_punch = minecart.on_punch,
on_step = minecart.on_step,
on_activate = cart.on_activate,
on_punch = cart.on_punch,
on_step = cart.on_step,
})
techage.register_node({"techage:chest_cart"}, {

View File

@ -19,6 +19,10 @@ local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local S2P = minetest.string_to_pos
local Pipe = techage.LiquidPipe
local liquid = techage.liquid
local MP = minetest.get_modpath("minecart")
local cart = dofile(MP.."/cart_lib1.lua")
cart:init(true)
local CAPACITY = 100
@ -110,8 +114,7 @@ minetest.register_node("techage:tank_cart", {
end,
on_place = function(itemstack, placer, pointed_thing)
return minecart.node_on_place(itemstack, placer, pointed_thing,
"techage:tank_cart")
return cart.add_cart(itemstack, placer, pointed_thing, "techage:tank_cart")
end,
on_punch = function(pos, node, puncher, pointed_thing)
@ -121,7 +124,7 @@ minetest.register_node("techage:tank_cart", {
if techage.liquid.is_container_empty(wielded_item) then
liquid.on_punch(pos, node, puncher, pointed_thing)
else
minecart.node_on_punch(pos, node, puncher, pointed_thing, "techage:tank_cart_entity")
cart.node_on_punch(pos, node, puncher, pointed_thing, "techage:tank_cart_entity")
end
end,
@ -170,9 +173,9 @@ minecart.register_cart_entity("techage:tank_cart_entity", "techage:tank_cart", {
visual_size = {x=0.66, y=0.66, z=0.66},
static_save = false,
},
on_activate = minecart.on_activate,
on_punch = minecart.on_punch,
on_step = minecart.on_step,
on_activate = cart.on_activate,
on_punch = cart.on_punch,
on_step = cart.on_step,
})
minetest.register_craft({

View File

@ -267,7 +267,7 @@ minetest.register_node("techage:ta4_doser", {
del_liquids(pos)
end,
after_dig_node = function(pos, oldnode, oldmetadata, digger)
techage.remove_node(pos)
techage.remove_node(pos, oldnode, oldmetadata)
Pipe:after_dig_node(pos)
techage.del_mem(pos)
end,

View File

@ -46,6 +46,7 @@ techage.Items = {
ta2_rinser = "techage:ta2_rinser_pas",
ta2_chest = "techage:chest_ta2",
ta2_forceload = "techage:forceload",
ta2_driveaxle = "techage:axle",
---------------------
techage_ta3 = "techage_ta3.png",
techage_ta31 = "techage_ta3b.png",

View File

@ -26,6 +26,7 @@ techage.manual_DE.aTitel = {
"3,TA2 Zylinder /Cylinder",
"3,TA2 Schwungrad / Flywheel",
"3,TA2 Dampfleitungen / Steam Pipe",
"3,TA2 Antriebsachsen / TA2 Drive Axle",
"2,Items schieben und sortieren",
"3,Röhren / TechAge Tube",
"3,TA2 Schieber / Pusher",
@ -233,6 +234,7 @@ techage.manual_DE.aText = {
"\n"..
"\n",
"Baborium wird nur im Untertagebau gewonnen. Baborium findet man nur in Stein in einer Höhe zwischen -250 und -340 Meter.\n"..
"Baborium kann nur im TA3 Industrieofen geschmolzen werden.\n"..
"\n"..
"\n"..
"\n",
@ -377,6 +379,10 @@ techage.manual_DE.aText = {
"\n"..
"\n"..
"\n",
"Die Antriebsachsen dienen zur Kraftübertragung von der Dampfmaschine zu anderen Maschinen. Die maximale Länge einer Antriebsachse beträgt 8 Blöcke. Über Getriebeboxen können auch größere Strecken überbrückt\\, sowie Abzweigungen und Richtungswechsel realisiert werden.\n"..
"\n"..
"\n"..
"\n",
"Um Gegenstände (Items) von einer Verarbeitungsstation zur nächsten weiter zu transportieren\\, werden Schieber und Röhren verwendet. Siehe Plan.\n"..
"\n"..
"\n"..
@ -1487,6 +1493,7 @@ techage.manual_DE.aItemName = {
"ta2_cylinder",
"ta2_flywheel",
"ta2_steampipe",
"ta2_driveaxle",
"",
"tube",
"ta2_pusher",
@ -1670,6 +1677,7 @@ techage.manual_DE.aPlanTable = {
"",
"",
"",
"",
"itemtransport",
"",
"",

View File

@ -26,6 +26,7 @@ techage.manual_EN.aTitel = {
"3,TA2 Cylinder",
"3,TA2 Flywheel",
"3,TA2 Steam Pipes",
"3,TA2 Drive Axle / TA2 Gearbox",
"2,Push and sort items",
"3,TechAge Tube",
"3,TA2 Pusher",
@ -224,7 +225,7 @@ techage.manual_EN.aText = {
" - Petroleum - is needed in TA3\n"..
" - Bauxite - an aluminum ore that is needed in TA4 to produce aluminum\n"..
"\n",
"Meridium is an alloy of steel and mesecons crystals. Meridium ingots can be made with the coal burner from steel andesecons crystals. Meridium glows in the dark. Tools made of Meridium also light up and are therefore very helpful in underground mining.\n"..
"Meridium is an alloy of steel and mesecons crystals. Meridium ingots can be made with the coal burner from steel and mesecons crystals. Meridium glows in the dark. Tools made of Meridium also light up and are therefore very helpful in underground mining.\n"..
"\n"..
"\n"..
"\n",
@ -233,6 +234,7 @@ techage.manual_EN.aText = {
"\n"..
"\n",
"Baborium is only extracted in underground mining. Baborium can only be found in stone at an altitude between -250 and -340 meters.\n"..
"Baborium can only be melted in the TA3 Industrial Furnace.\n"..
"\n"..
"\n"..
"\n",
@ -326,7 +328,7 @@ techage.manual_EN.aText = {
"\n"..
"\n"..
"\n",
"TA1 has its own metal alloy meridium. Meridium ingots can be made with the coal burner from steel and mesecons splinters. Meridium glows in the dark. Tools made of Meridium also light up and are therefore very helpful in underground mining.\n"..
"TA1 has its own metal alloy meridium. Meridium ingots can be made with the coal burner from steel and mesecons crystals. Meridium glows in the dark. Tools made of Meridium also light up and are therefore very helpful in underground mining.\n"..
"\n"..
"\n"..
"\n",
@ -377,6 +379,10 @@ techage.manual_EN.aText = {
"\n"..
"\n"..
"\n",
"The drive axles are used to transmit power from the steam engine to other machines. The maximum length of a drive axis is 8 blocks. With TA2 Gearboxes\\, larger distances can be bridged\\, and branches and changes of direction can be realized.\n"..
"\n"..
"\n"..
"\n",
"In order to transport objects from one processing station to the next\\, pushers and tubes are used. See plan.\n"..
"\n"..
"\n"..
@ -1477,6 +1483,7 @@ techage.manual_EN.aItemName = {
"ta2_cylinder",
"ta2_flywheel",
"ta2_steampipe",
"ta2_driveaxle",
"",
"tube",
"ta2_pusher",
@ -1660,6 +1667,7 @@ techage.manual_EN.aPlanTable = {
"",
"",
"",
"",
"itemtransport",
"",
"",

View File

@ -25,7 +25,6 @@ techage.furnace.register_recipe({
time = 8,
})
if techage.modified_recipes_enabled then
techage.furnace.register_recipe({
output = "default:bronze_ingot 4",
@ -148,3 +147,24 @@ techage.furnace.register_recipe({
},
time = 4,
})
if minetest.global_exists("moreores") then
if techage.modified_recipes_enabled then
minetest.clear_craft({output = "moreores:mithril_ingot"})
minetest.clear_craft({output = "moreores:silver_ingot"})
end
techage.furnace.register_recipe({
output = 'moreores:silver_ingot',
recipe = {'moreores:silver_lump'},
time = 2,
})
techage.furnace.register_recipe({
output = 'moreores:mithril_ingot',
recipe = {'moreores:mithril_lump'},
time = 5,
})
end

View File

@ -688,4 +688,3 @@ techage.icta_register_action("set_filter", {
return send_single_string(environ, data.number, "filter", payload)
end,
})

View File

@ -451,8 +451,8 @@ minetest.register_node("techage:ta4_icta_controller", {
return
end
minetest.node_dig(pos, node, puncher, pointed_thing)
techage.remove_node(pos)
minetest.node_dig(pos, node, puncher, pointed_thing)
end,
on_timer = on_timer,

View File

@ -110,8 +110,8 @@ minetest.register_node("techage:ta4_display", {
minetest.get_node_timer(pos):start(1)
end,
after_dig_node = function(pos)
techage.remove_node(pos)
after_dig_node = function(pos, oldnode, oldmetadata)
techage.remove_node(pos, oldnode, oldmetadata)
end,
on_timer = on_timer,
@ -157,8 +157,8 @@ minetest.register_node("techage:ta4_displayXL", {
minetest.get_node_timer(pos):start(2)
end,
after_dig_node = function(pos)
techage.remove_node(pos)
after_dig_node = function(pos, oldnode, oldmetadata)
techage.remove_node(pos, oldnode, oldmetadata)
end,
on_timer = on_timer,

View File

@ -56,8 +56,8 @@ minetest.register_node("techage:ta4_signaltower", {
end
end,
after_dig_node = function(pos)
techage.remove_node(pos)
after_dig_node = function(pos, oldnode, oldmetadata)
techage.remove_node(pos, oldnode, oldmetadata)
end,
paramtype = "light",

View File

@ -13,7 +13,7 @@
techage = {}
-- Version for compatibility checks, see readme.md/history
techage.version = 0.10
techage.version = 0.12
if minetest.global_exists("tubelib") then
minetest.log("error", "[techage] Techage can't be used together with the mod tubelib!")
@ -27,8 +27,8 @@ elseif minetest.global_exists("techpack") then
elseif minetest.global_exists("tubelib2") and tubelib2.version < 1.9 then
minetest.log("error", "[techage] Techage requires tubelib2 version 1.9 or newer!")
return
elseif minetest.global_exists("minecart") and minecart.version < 1.05 then
minetest.log("error", "[techage] Techage requires minecart version 1.05 or newer!")
elseif minetest.global_exists("minecart") and minecart.version < 1.06 then
minetest.log("error", "[techage] Techage requires minecart version 1.06 or newer!")
return
elseif minetest.global_exists("lcdlib") and lcdlib.version < 1.0 then
minetest.log("error", "[techage] Techage requires lcdlib version 1.0 or newer!")
@ -278,6 +278,7 @@ dofile(MP.."/lua_controller/sensorchest.lua")
dofile(MP.."/lua_controller/terminal.lua")
-- Items
dofile(MP.."/items/registered_nodes.lua")
dofile(MP.."/items/barrel.lua")
dofile(MP.."/items/baborium.lua")
dofile(MP.."/items/usmium.lua")

View File

@ -29,8 +29,7 @@ end
local function num_dirt(pos)
local pos1 = {x=pos.x-2, y=pos.y-1, z=pos.z-2}
local pos2 = {x=pos.x+2, y=pos.y+3, z=pos.z+2}
local nodes = minetest.find_nodes_in_area(pos1, pos2, {"default:dirt", "default:dirt_with_grass",
"default:dirt_with_dry_grass", "default:dirt_with_snow", "techage:dirt_with_ash"})
local nodes = minetest.find_nodes_in_area(pos1, pos2, techage.aAnyKindOfDirtBlocks)
return #nodes
end
@ -38,9 +37,18 @@ end
local function make_dirt_with_dry_grass(pos)
local pos1 = {x=pos.x-2, y=pos.y+3, z=pos.z-2}
local pos2 = {x=pos.x+2, y=pos.y+3, z=pos.z+2}
for _,p in ipairs(minetest.find_nodes_in_area(pos1, pos2, "default:dirt_with_grass")) do
for _,p in ipairs(minetest.find_nodes_in_area(pos1, pos2, {
"default:dirt_with_grass",
"default:dirt_with_coniferous_litter",
"default:dirt_with_rainforest_litter",
})) do
minetest.swap_node(p, {name = "default:dirt_with_dry_grass"})
end
if minetest.global_exists("ethereal") then
for _,p in ipairs(minetest.find_nodes_in_area(pos1, pos2, techage.aEtherealDirts)) do
minetest.swap_node(p, {name = "default:dirt_with_dry_grass"})
end
end
end
-- replace pile bottom nodes

View File

@ -35,9 +35,23 @@ local function push_items(pos, items)
minetest.add_item({x=pos.x, y=pos.y-0.4, z=pos.z}, items)
end
local function minecart_hopper_takeitem(pos, num)
for _, obj in pairs(minetest.get_objects_inside_radius({x=pos.x, y=pos.y-0.4, z=pos.z}, 0.2)) do
local entity = obj:get_luaentity()
if not obj:is_player() and entity and entity.name == "__builtin:item" then
obj:remove()
return ItemStack(entity.itemstring or "air")
end
end
end
local function minecart_hopper_untakeitem(pos, in_dir, stack)
push_items(pos, stack)
end
local function keep_running(pos, elapsed)
local inv = M(pos):get_inventory()
if swap_node(pos) then
local inv = M(pos):get_inventory()
local src, dst
if inv:contains_item("src", ItemStack("techage:basalt_gravel")) then
@ -53,6 +67,7 @@ local function keep_running(pos, elapsed)
push_items(pos, dst)
inv:remove_item("src", src)
end
local inv = M(pos):get_inventory()
return not inv:is_empty("src")
end
@ -127,6 +142,9 @@ for idx = 0,3 do
on_construct = idx == 3 and on_construct or nil,
on_punch = idx == 3 and on_punch or nil,
on_timer = keep_running,
minecart_hopper_takeitem = minecart_hopper_takeitem,
minecart_hopper_untakeitem = minecart_hopper_untakeitem,
paramtype = "light",
sounds = default.node_sound_wood_defaults(),

View File

@ -73,8 +73,5 @@ minecart.register_inventory(
end,
listname = "src",
},
take = {
listname = "src",
},
}
)

View File

@ -142,6 +142,9 @@ if techage.modified_recipes_enabled then
minetest.clear_craft({output = "default:steel_ingot"})
minetest.clear_craft({output = "fire:flint_and_steel"})
minetest.clear_craft({output = "bucket:bucket_empty"})
if minetest.global_exists("moreores") then
minetest.clear_craft({output = "moreores:silver_ingot"})
end
-- add again
minetest.register_craft({
@ -171,6 +174,23 @@ if techage.modified_recipes_enabled then
time = 8,
})
techage.ironage_register_recipe({
output = "default:tin_ingot 1",
recipe = {"default:tin_lump"},
heat = 4,
time = 2,
})
if minetest.global_exists("moreores") then
techage.ironage_register_recipe({
output = "moreores:silver_ingot 1",
recipe = {"moreores:silver_lump"},
heat = 5,
time = 2,
})
end
minetest.register_craft({
output = "fire:flint_and_steel",
recipe = {

View File

@ -43,9 +43,8 @@ minetest.register_ore({
y_max = -250,
})
minetest.register_craft({
type = 'cooking',
techage.furnace.register_recipe({
output = 'techage:baborium_ingot',
recipe = 'techage:baborium_lump',
cooktime = 5,
recipe = {'techage:baborium_lump'},
time = 3,
})

View File

@ -0,0 +1,39 @@
--[[
TechAge
=======
Copyright (C) 2019 Joachim Stolberg
GPL v3
See LICENSE.txt for more information
Collect data of registered nodes
]]--
techage.aEtherealDirts = {
"ethereal:fiery_dirt",
"ethereal:cold_dirt",
"ethereal:crystal_dirt",
"ethereal:gray_dirt",
"ethereal:mushroom_dirt",
"ethereal:prairie_dirt",
"ethereal:grove_dirt",
"ethereal:jungle_dirt",
"ethereal:bamboo_dirt",
}
techage.aAnyKindOfDirtBlocks = {}
minetest.register_on_mods_loaded(function()
for name, ndef in pairs(minetest.registered_nodes) do
if string.find(name, "dirt") and
ndef.drawtype == "normal" and
ndef.groups.crumbly and ndef.groups.crumbly > 0 then
techage.aAnyKindOfDirtBlocks[#techage.aAnyKindOfDirtBlocks + 1] = name
end
end
end)

View File

@ -161,7 +161,7 @@ minetest.register_node("techage:ta3_silo", {
end,
after_dig_node = function(pos, oldnode, oldmetadata, digger)
Pipe:after_dig_node(pos)
techage.remove_node(pos)
techage.remove_node(pos, oldnode, oldmetadata)
end,
liquid = tLiquid,
networks = tNetworks,
@ -206,7 +206,7 @@ minetest.register_node("techage:ta4_silo", {
end,
after_dig_node = function(pos, oldnode, oldmetadata, digger)
Pipe:after_dig_node(pos)
techage.remove_node(pos)
techage.remove_node(pos, oldnode, oldmetadata)
end,
liquid = tLiquid,
networks = tNetworks,

View File

@ -103,7 +103,7 @@ minetest.register_node("techage:ta3_tank", {
on_punch = liquid.on_punch,
after_dig_node = function(pos, oldnode, oldmetadata, digger)
Pipe:after_dig_node(pos)
techage.remove_node(pos)
techage.remove_node(pos, oldnode, oldmetadata)
end,
liquid = {
capa = CAPACITY,
@ -161,7 +161,7 @@ minetest.register_node("techage:oiltank", {
on_punch = liquid.on_punch,
after_dig_node = function(pos, oldnode, oldmetadata, digger)
Pipe:after_dig_node(pos)
techage.remove_node(pos)
techage.remove_node(pos, oldnode, oldmetadata)
end,
liquid = {
capa = CAPACITY * 4,
@ -209,7 +209,7 @@ minetest.register_node("techage:ta4_tank", {
on_punch = liquid.on_punch,
after_dig_node = function(pos, oldnode, oldmetadata, digger)
Pipe:after_dig_node(pos)
techage.remove_node(pos)
techage.remove_node(pos, oldnode, oldmetadata)
end,
liquid = {
capa = CAPACITY * 2,

View File

@ -140,7 +140,7 @@ local function techage_set_numbers(pos, numbers, player_name)
end
local function after_dig_node(pos, oldnode, oldmetadata, digger)
techage.remove_node(pos)
techage.remove_node(pos, oldnode, oldmetadata)
end
minetest.register_node("techage:ta3_button_off", {

View File

@ -73,7 +73,7 @@ local function techage_set_numbers(pos, numbers, player_name)
end
local function after_dig_node(pos, oldnode, oldmetadata, digger)
techage.remove_node(pos)
techage.remove_node(pos, oldnode, oldmetadata)
end
minetest.register_node("techage:ta3_cartdetector_off", {

View File

@ -177,8 +177,8 @@ minetest.register_node("techage:ta4_collector", {
on_timer = on_timer,
after_dig_node = function(pos)
techage.remove_node(pos)
after_dig_node = function(pos, oldnode, oldmetadata)
techage.remove_node(pos, oldnode, oldmetadata)
end,
paramtype = "light",

View File

@ -76,7 +76,7 @@ local function techage_set_numbers(pos, numbers, player_name)
end
local function after_dig_node(pos, oldnode, oldmetadata, digger)
techage.remove_node(pos)
techage.remove_node(pos, oldnode, oldmetadata)
techage.del_mem(pos)
end

View File

@ -78,8 +78,8 @@ for idx,pgn in ipairs(tPgns) do
end
end,
after_dig_node = function(pos)
techage.remove_node(pos)
after_dig_node = function(pos, oldnode, oldmetadata)
techage.remove_node(pos, oldnode, oldmetadata)
end,
paramtype = "light",

View File

@ -105,9 +105,9 @@ minetest.register_node("techage:ta3_doorcontroller", {
return res
end,
after_dig_node = function(pos)
after_dig_node = function(pos, oldnode, oldmetadata)
swap_door_nodes(pos, false)
techage.remove_node(pos)
techage.remove_node(pos, oldnode, oldmetadata)
techage.del_mem(pos)
end,

View File

@ -64,8 +64,8 @@ for idx,pgn in ipairs(tPgns) do
end
end,
after_dig_node = function(pos)
techage.remove_node(pos)
after_dig_node = function(pos, oldnode, oldmetadata)
techage.remove_node(pos, oldnode, oldmetadata)
end,
paramtype = "light",

View File

@ -207,8 +207,8 @@ minetest.register_node("techage:ta3_logic", {
return res
end,
after_dig_node = function(pos)
techage.remove_node(pos)
after_dig_node = function(pos, oldnode, oldmetadata)
techage.remove_node(pos, oldnode, oldmetadata)
techage.del_mem(pos)
end,

View File

@ -135,7 +135,7 @@ minetest.register_node("techage:ta3_nodedetector_off", {
end,
after_dig_node = function(pos, oldnode, oldmetadata, digger)
techage.remove_node(pos)
techage.remove_node(pos, oldnode, oldmetadata)
techage.del_mem(pos)
end,
@ -165,7 +165,7 @@ minetest.register_node("techage:ta3_nodedetector_on", {
end,
after_dig_node = function(pos, oldnode, oldmetadata, digger)
techage.remove_node(pos)
techage.remove_node(pos, oldnode, oldmetadata)
techage.del_mem(pos)
end,

View File

@ -133,7 +133,7 @@ minetest.register_node("techage:ta3_playerdetector_off", {
end,
after_dig_node = function(pos, oldnode, oldmetadata, digger)
techage.remove_node(pos)
techage.remove_node(pos, oldnode, oldmetadata)
techage.del_mem(pos)
end,
@ -168,7 +168,7 @@ minetest.register_node("techage:ta3_playerdetector_on", {
end,
after_dig_node = function(pos, oldnode, oldmetadata, digger)
techage.remove_node(pos)
techage.remove_node(pos, oldnode, oldmetadata)
techage.del_mem(pos)
end,
@ -224,7 +224,7 @@ minetest.register_node("techage:ta4_playerdetector_off", {
end,
after_dig_node = function(pos, oldnode, oldmetadata, digger)
techage.remove_node(pos)
techage.remove_node(pos, oldnode, oldmetadata)
techage.del_mem(pos)
end,
@ -273,7 +273,7 @@ minetest.register_node("techage:ta4_playerdetector_on", {
end,
after_dig_node = function(pos, oldnode, oldmetadata, digger)
techage.remove_node(pos)
techage.remove_node(pos, oldnode, oldmetadata)
techage.del_mem(pos)
end,

View File

@ -72,8 +72,8 @@ minetest.register_node("techage:ta3_repeater", {
return res
end,
after_dig_node = function(pos)
techage.remove_node(pos)
after_dig_node = function(pos, oldnode, oldmetadata)
techage.remove_node(pos, oldnode, oldmetadata)
techage.del_mem(pos)
end,

View File

@ -228,8 +228,8 @@ minetest.register_node("techage:ta3_sequencer", {
end
local nvm = techage.get_nvm(pos)
if not nvm.running then
minetest.node_dig(pos, node, puncher, pointed_thing)
techage.remove_node(pos)
minetest.node_dig(pos, node, puncher, pointed_thing)
techage.del_mem(pos)
end
end,

View File

@ -54,7 +54,7 @@ minetest.register_node("techage:signal_lamp_off", {
on_rightclick = switch_on,
after_dig_node = function(pos, oldnode, oldmetadata, digger)
techage.remove_node(pos)
techage.remove_node(pos, oldnode, oldmetadata)
if COLORED then
unifieddyes.after_dig_node(pos, oldnode, oldmetadata, digger)
end
@ -97,7 +97,7 @@ minetest.register_node("techage:signal_lamp_on", {
after_place_node = COLORED and unifieddyes.recolor_on_place or nil,
after_dig_node = function(pos, oldnode, oldmetadata, digger)
techage.remove_node(pos)
techage.remove_node(pos, oldnode, oldmetadata)
if COLORED then
unifieddyes.after_dig_node(pos, oldnode, oldmetadata, digger)
end

View File

@ -226,8 +226,8 @@ local function register_terminal(num, tiles, node_box, selection_box)
end
end,
after_dig_node = function(pos)
techage.remove_node(pos)
after_dig_node = function(pos, oldnode, oldmetadata)
techage.remove_node(pos, oldnode, oldmetadata)
end,
paramtype = "light",

View File

@ -175,8 +175,8 @@ minetest.register_node("techage:ta3_timer", {
on_timer = check_rules,
after_dig_node = function(pos)
techage.remove_node(pos)
after_dig_node = function(pos, oldnode, oldmetadata)
techage.remove_node(pos, oldnode, oldmetadata)
techage.del_mem(pos)
end,

View File

@ -533,8 +533,8 @@ minetest.register_node("techage:ta4_lua_controller", {
return
end
minetest.node_dig(pos, node, puncher, pointed_thing)
techage.remove_node(pos)
minetest.node_dig(pos, node, puncher, pointed_thing)
end,
on_timer = on_timer,

View File

@ -89,7 +89,7 @@ local function can_dig(pos, player)
end
local function after_dig_node(pos, oldnode, oldmetadata, digger)
techage.remove_node(pos)
techage.remove_node(pos, oldnode, oldmetadata)
end
local function formspec1()

View File

@ -86,8 +86,8 @@ minetest.register_node("techage:ta4_server", {
return
end
techage.del_mem(pos)
minetest.node_dig(pos, node, puncher, pointed_thing)
techage.remove_node(pos)
minetest.node_dig(pos, node, puncher, pointed_thing)
end,
on_timer = function(pos, elasped)

View File

@ -173,8 +173,8 @@ minetest.register_node("techage:ta4_terminal", {
end
end,
after_dig_node = function(pos)
techage.remove_node(pos)
after_dig_node = function(pos, oldnode, oldmetadata)
techage.remove_node(pos, oldnode, oldmetadata)
end,
paramtype = "light",

View File

@ -26,7 +26,7 @@ Since the levels build on each other, all ages have to be run through one after
## Iron Age (TA1)
1. Search and harvest cactus to make paper and craft the Techage Construction Board. This plan is the ingame manual for all four Techage phases
1. Search and harvest papyrus to make paper and craft the Techage Construction Board. This plan is the ingame manual for all four Techage phases
2. Cut trees and make wood out of them
3. Collect dirt for the charcoal burner to make charcoal
4. Go mining and seach for ores, or

View File

@ -65,6 +65,7 @@ Usmium kommt nur als Nuggets vor und kann nur beim Waschen von Kies mit der TA2/
### Baborium
Baborium wird nur im Untertagebau gewonnen. Baborium findet man nur in Stein in einer Höhe zwischen -250 und -340 Meter.
Baborium kann nur im TA3 Industrieofen geschmolzen werden.
[baborium|image]

View File

@ -50,7 +50,7 @@ Techage adds some new items to the game:
### Meridium
Meridium is an alloy of steel and mesecons crystals. Meridium ingots can be made with the coal burner from steel andesecons crystals. Meridium glows in the dark. Tools made of Meridium also light up and are therefore very helpful in underground mining.
Meridium is an alloy of steel and mesecons crystals. Meridium ingots can be made with the coal burner from steel and mesecons crystals. Meridium glows in the dark. Tools made of Meridium also light up and are therefore very helpful in underground mining.
[meridium|image]
@ -65,6 +65,8 @@ Usmium only occurs as nuggets and can only be obtained by washing gravel with th
### Baborium
Baborium is only extracted in underground mining. Baborium can only be found in stone at an altitude between -250 and -340 meters.
Baborium can only be melted in the TA3 Industrial Furnace.
[baborium|image]

View File

@ -98,6 +98,6 @@ Make sure that the boxes are "chest_locked", otherwise someone will steal the va
### Meridium
TA1 has its own metal alloy meridium. Meridium ingots can be made with the coal burner from steel and mesecons splinters. Meridium glows in the dark. Tools made of Meridium also light up and are therefore very helpful in underground mining.
TA1 has its own metal alloy meridium. Meridium ingots can be made with the coal burner from steel and mesecons crystals. Meridium glows in the dark. Tools made of Meridium also light up and are therefore very helpful in underground mining.
[meridium|image]

View File

@ -64,6 +64,12 @@ Teil der Dampfmaschine. Der Boiler muss mit dem Zylinder über die Dampfleitunge
[ta2_steampipe|image]
### TA2 Antriebsachsen / TA2 Drive Axle
Die Antriebsachsen dienen zur Kraftübertragung von der Dampfmaschine zu anderen Maschinen. Die maximale Länge einer Antriebsachse beträgt 8 Blöcke. Über Getriebeboxen können auch größere Strecken überbrückt, sowie Abzweigungen und Richtungswechsel realisiert werden.
[ta2_driveaxle|image]
## Items schieben und sortieren

View File

@ -65,6 +65,12 @@ Part of the steam engine. The boiler must be connected to the cylinder via the s
[ta2_steampipe|image]
### TA2 Drive Axle / TA2 Gearbox
The drive axles are used to transmit power from the steam engine to other machines. The maximum length of a drive axis is 8 blocks. With TA2 Gearboxes, larger distances can be bridged, and branches and changes of direction can be realized.
[ta2_driveaxle|image]
## Push and sort items
In order to transport objects from one processing station to the next, pushers and tubes are used. See plan.

View File

@ -25,6 +25,7 @@
- [TA2 Zylinder /Cylinder](./manual_ta2_DE.md#ta2-zylinder-cylinder)
- [TA2 Schwungrad / Flywheel](./manual_ta2_DE.md#ta2-schwungrad--flywheel)
- [TA2 Dampfleitungen / Steam Pipe](./manual_ta2_DE.md#ta2-dampfleitungen--steam-pipe)
- [TA2 Antriebsachsen / TA2 Drive Axle](./manual_ta2_DE.md#ta2-antriebsachsen--ta2-drive-axle)
- [Items schieben und sortieren](./manual_ta2_DE.md#items-schieben-und-sortieren)
- [Röhren / TechAge Tube](./manual_ta2_DE.md#röhren--techage-tube)
- [TA2 Schieber / Pusher](./manual_ta2_DE.md#ta2-schieber--pusher)

View File

@ -25,6 +25,7 @@
- [TA2 Cylinder](./manual_ta2_EN.md#ta2-cylinder)
- [TA2 Flywheel](./manual_ta2_EN.md#ta2-flywheel)
- [TA2 Steam Pipes](./manual_ta2_EN.md#ta2-steam-pipes)
- [TA2 Drive Axle / TA2 Gearbox](./manual_ta2_EN.md#ta2-drive-axle--ta2-gearbox)
- [Push and sort items](./manual_ta2_EN.md#push-and-sort-items)
- [TechAge Tube](./manual_ta2_EN.md#techage-tube)
- [TA2 Pusher](./manual_ta2_EN.md#ta2-pusher)

View File

@ -1,4 +1,4 @@
name = techage
depends = default,doors,tubelib2,basic_materials,bucket,stairs,screwdriver,minecart,lcdlib,safer_lua
optional_depends = unified_inventory,wielded_light,unifieddyes
description = Techage, go through 4 tech ages in search of wealth and power!
optional_depends = unified_inventory,wielded_light,unifieddyes,moreores, ethereal
description = Techage, go through 4 tech ages in search of wealth and power!

View File

@ -72,9 +72,9 @@ local function after_place_node(pos)
Cable:after_place_node(pos)
end
local function after_dig_node(pos, oldnode)
local function after_dig_node(pos, oldnode, oldmetadata)
Cable:after_dig_node(pos)
techage.remove_node(pos)
techage.remove_node(pos, oldnode, oldmetadata)
techage.del_mem(pos)
end

View File

@ -171,14 +171,14 @@ local function after_place_node(pos, placer)
Cable:after_place_node(pos)
end
local function after_dig_node(pos, oldnode)
local function after_dig_node(pos, oldnode, oldmetadata)
local hash = minetest.hash_node_position(pos)
if Rotors[hash] and Rotors[hash]:get_luaentity() then
Rotors[hash]:remove()
end
Rotors[hash] = nil
Cable:after_dig_node(pos)
techage.remove_node(pos)
techage.remove_node(pos, oldnode, oldmetadata)
techage.del_mem(pos)
end

View File

@ -12,6 +12,8 @@ read_globals = {
"minetest", "vector",
"ItemStack", "datastorage",
"hb",
}
files["callbacks.lua"].ignore = { "player", "draw_lite_mode" }

View File

@ -2,24 +2,24 @@
local item_names = {} -- [player_name] = { hud, dtime, itemname }
local dlimit = 3 -- HUD element will be hidden after this many seconds
local air_hud_mod = minetest.get_modpath("4air")
local hud_mod = minetest.get_modpath("hud")
local hudbars_mod = minetest.get_modpath("hudbars")
local function set_hud(player)
local player_name = player:get_player_name()
local off = {x=0, y=-70}
if air_hud_mod or hud_mod then
off.y = off.y - 20
elseif hudbars_mod then
off.y = off.y + 13
local off = {x=0, y=-65}
if hudbars_mod then
-- Assume no alignment (2 per line)
off.y = off.y - math.ceil(hb.hudbars_count / 2) * 25
else
off.y = off.y - 25
end
item_names[player_name] = {
hud = player:hud_add({
hud_elem_type = "text",
position = {x=0.5, y=1},
offset = off,
alignment = {x=0, y=0},
alignment = {x=0, y=-1},
number = 0xFFFFFF,
text = "",
}),