Merge pull request 'develop' (#32) from develop into master

Reviewed-on: #32
This commit is contained in:
Milan Meduna 2021-02-28 22:06:30 +01:00
commit af8973737c
372 changed files with 5630 additions and 2852 deletions

View file

@ -436,7 +436,11 @@ end, true)
minetest.register_globalstep(function(dtime) minetest.register_globalstep(function(dtime)
timer = timer + dtime timer = timer + dtime
if timer > armor.config.init_delay then if timer <= armor.config.init_delay then
return
end
timer = 0
for player, count in pairs(pending_players) do for player, count in pairs(pending_players) do
local remove = init_player_armor(player) == true local remove = init_player_armor(player) == true
pending_players[player] = count + 1 pending_players[player] = count + 1
@ -448,68 +452,52 @@ minetest.register_globalstep(function(dtime)
pending_players[player] = nil pending_players[player] = nil
end end
end end
timer = 0
end
end)
-- Fire Protection and water breathing, added by TenPlus1. -- water breathing protection, added by TenPlus1
if armor.config.water_protect == true then
if armor.config.fire_protect == true then
-- override hot nodes so they do not hurt player anywhere but mod
for _, row in pairs(armor.fire_nodes) do
if minetest.registered_nodes[row[1]] then
minetest.override_item(row[1], {damage_per_second = 0})
end
end
else
print (S("[3d_armor] Fire Nodes disabled"))
end
if armor.config.water_protect == true or armor.config.fire_protect == true then
minetest.register_globalstep(function(dtime)
armor.timer = armor.timer + dtime
if armor.timer < armor.config.update_time then
return
end
for _,player in pairs(minetest.get_connected_players()) do for _,player in pairs(minetest.get_connected_players()) do
local name = player:get_player_name() local name = player:get_player_name()
local pos = player:get_pos()
local hp = player:get_hp()
if not name or not pos or not hp then
return
end
-- water breathing
if armor.config.water_protect == true then
if armor.def[name].water > 0 and if armor.def[name].water > 0 and
player:get_breath() < 10 then player:get_breath() < 10 then
player:set_breath(10) player:set_breath(10)
end end
end end
-- fire protection
if armor.config.fire_protect == true then
local fire_damage = true
pos.y = pos.y + 1.4 -- head level
local node_head = minetest.get_node(pos).name
pos.y = pos.y - 1.2 -- feet level
local node_feet = minetest.get_node(pos).name
-- is player inside a hot node?
for _, row in pairs(armor.fire_nodes) do
-- check fire protection, if not enough then get hurt
if row[1] == node_head or row[1] == node_feet then
if fire_damage == true then
armor:punch(player, "fire")
last_punch_time[name] = minetest.get_gametime()
fire_damage = false
end end
if hp > 0 and armor.def[name].fire < row[2] then
hp = hp - row[3] * armor.config.update_time
player:set_hp(hp)
break
end
end
end
end
end
armor.timer = 0
end) end)
-- Fire Protection, added by TenPlus1.
if armor.config.fire_protect == true then
-- override any hot nodes that do not already deal damage
for _, row in pairs(armor.fire_nodes) do
if minetest.registered_nodes[row[1]] then
local damage = minetest.registered_nodes[row[1]].damage_per_second
if not damage or damage == 0 then
minetest.override_item(row[1], {damage_per_second = row[3]})
end
end
end
else
print ("[3d_armor] Fire Nodes disabled")
end
if armor.config.fire_protect == true then
minetest.register_on_player_hpchange(function(player, hp_change, reason)
if reason.type == "node_damage" and reason.node then
-- fire protection
if armor.config.fire_protect == true and hp_change < 0 then
local name = player:get_player_name()
for _,igniter in pairs(armor.fire_nodes) do
if reason.node == igniter[1] then
if armor.def[name].fire < igniter[2] then
armor:punch(player, "fire")
else
hp_change = 0
end
end
end
end
end
return hp_change
end, true)
end end

View file

@ -182,9 +182,11 @@ minetest.register_node(nodename, {
^- called when a train leaves the rail ^- called when a train leaves the rail
-- The following function is only in effect when interlocking is enabled: -- The following function is only in effect when interlocking is enabled:
on_train_approach = function(pos, train_id, train, index, lzbdata) on_train_approach = function(pos, train_id, train, index, has_entered, lzbdata)
^- called when a train is approaching this position, called exactly once for every path recalculation (which can happen at any time) ^- called when a train is approaching this position, called exactly once for every path recalculation (which can happen at any time)
^- This is called so that if the train would start braking now, it would come to halt about(wide approx) 5 nodes before the rail. ^- This is called so that if the train would start braking now, it would come to halt about(wide approx) 5 nodes before the rail.
^- has_entered: when true, the train is already standing on this node with its front tip, and the enter callback has already been called.
Possibly, some actions need not to be taken in this case. Only set if it's the very first node the train is standing on.
^- lzbdata should be ignored and nothing should be assigned to it ^- lzbdata should be ignored and nothing should be assigned to it
} }
}) })

View file

@ -72,6 +72,12 @@ function atc.send_command(pos, par_tid)
end end
else else
atwarn("ATC rail at", pos, ": Sending command failed: There's no train at this position. This seems to be a bug.") atwarn("ATC rail at", pos, ": Sending command failed: There's no train at this position. This seems to be a bug.")
-- huch
--local train = advtrains.trains[train_id_temp_debug]
--atlog("Train speed is",train.velocity,", have moved",train.dist_moved_this_step,", lever",train.lever)
--advtrains.path_print(train, atlog)
-- TODO track again when ATC bugs occur...
end end
else else
atwarn("ATC rail at", pos, ": Sending command failed: Entry for controller not found.") atwarn("ATC rail at", pos, ": Sending command failed: Entry for controller not found.")
@ -108,15 +114,12 @@ advtrains.atc_function = function(def, preset, suffix, rotation)
return { return {
after_place_node=apn_func, after_place_node=apn_func,
after_dig_node=function(pos) after_dig_node=function(pos)
return advtrains.pcall(function()
advtrains.invalidate_all_paths(pos) advtrains.invalidate_all_paths(pos)
advtrains.ndb.clear(pos) advtrains.ndb.clear(pos)
local pts=minetest.pos_to_string(pos) local pts=minetest.pos_to_string(pos)
atc.controllers[pts]=nil atc.controllers[pts]=nil
end)
end, end,
on_receive_fields = function(pos, formname, fields, player) on_receive_fields = function(pos, formname, fields, player)
return advtrains.pcall(function()
if advtrains.is_protected(pos, player:get_player_name()) then if advtrains.is_protected(pos, player:get_player_name()) then
minetest.record_protection_violation(pos, player:get_player_name()) minetest.record_protection_violation(pos, player:get_player_name())
return return
@ -124,27 +127,27 @@ advtrains.atc_function = function(def, preset, suffix, rotation)
local meta=minetest.get_meta(pos) local meta=minetest.get_meta(pos)
if meta then if meta then
if not fields.save then if not fields.save then
--maybe only the dropdown changed --[[--maybe only the dropdown changed
if fields.mode then if fields.mode then
meta:set_string("mode", idxtrans[fields.mode]) meta:set_string("mode", idxtrans[fields.mode])
if fields.mode=="digiline" then if fields.mode=="digiline" then
meta:set_string("infotext", attrans("ATC controller, mode @1\nChannel: @2", (fields.mode or "?"), meta:get_string("command")) ) meta:set_string("infotext", attrans("ATC controller, mode @1\nChannel: @2", fields.mode, meta:get_string("command")) )
else else
meta:set_string("infotext", attrans("ATC controller, mode @1\nCommand: @2", (fields.mode or "?"), meta:get_string("command")) ) meta:set_string("infotext", attrans("ATC controller, mode @1\nCommand: @2", fields.mode, meta:get_string("command")) )
end end
meta:set_string("formspec", atc.get_atc_controller_formspec(pos, meta)) meta:set_string("formspec", atc.get_atc_controller_formspec(pos, meta))
end end]]--
return return
end end
meta:set_string("mode", idxtrans[fields.mode]) --meta:set_string("mode", idxtrans[fields.mode])
meta:set_string("command", fields.command) meta:set_string("command", fields.command)
meta:set_string("command_on", fields.command_on) --meta:set_string("command_on", fields.command_on)
meta:set_string("channel", fields.channel) meta:set_string("channel", fields.channel)
if fields.mode=="digiline" then --if fields.mode=="digiline" then
meta:set_string("infotext", attrans("ATC controller, mode @1\nChannel: @2", (fields.mode or "?"), meta:get_string("command")) ) -- meta:set_string("infotext", attrans("ATC controller, mode @1\nChannel: @2", fields.mode, meta:get_string("command")) )
else --else
meta:set_string("infotext", attrans("ATC controller, mode @1\nCommand: @2", (fields.mode or "?"), meta:get_string("command")) ) meta:set_string("infotext", attrans("ATC controller, mode @1\nCommand: @2", "-", meta:get_string("command")) )
end --end
meta:set_string("formspec", atc.get_atc_controller_formspec(pos, meta)) meta:set_string("formspec", atc.get_atc_controller_formspec(pos, meta))
local pts=minetest.pos_to_string(pos) local pts=minetest.pos_to_string(pos)
@ -154,11 +157,10 @@ advtrains.atc_function = function(def, preset, suffix, rotation)
atc.send_command(pos) atc.send_command(pos)
end end
end end
end)
end, end,
advtrains = { advtrains = {
on_train_enter = function(pos, train_id) on_train_enter = function(pos, train_id)
atc.send_command(pos) atc.send_command(pos, train_id)
end, end,
}, },
} }
@ -256,6 +258,11 @@ local matchptn={
end end
return 1 return 1
end, end,
["A([01])"]=function(id, train, match)
if not advtrains.interlocking then return 2 end
advtrains.interlocking.ars_set_disable(train, match=="0")
return 2
end,
} }
eval_conditional = function(command, arrow, speed) eval_conditional = function(command, arrow, speed)

View file

@ -13,7 +13,6 @@ minetest.register_tool("advtrains:copytool", {
-- The front of the train is used as the start of the new train and it proceeds backwards from -- The front of the train is used as the start of the new train and it proceeds backwards from
-- the direction of travel. -- the direction of travel.
on_place = function(itemstack, placer, pointed_thing) on_place = function(itemstack, placer, pointed_thing)
return advtrains.pcall(function()
if ((not pointed_thing.type == "node") or (not placer.get_player_name)) then if ((not pointed_thing.type == "node") or (not placer.get_player_name)) then
return return
end end
@ -82,7 +81,6 @@ minetest.register_tool("advtrains:copytool", {
train.line = clipboard.line train.line = clipboard.line
return return
end)
end, end,
-- Copy: Take the pointed-at train and put it on the clipboard -- Copy: Take the pointed-at train and put it on the clipboard
on_use = function(itemstack, user, pointed_thing) on_use = function(itemstack, user, pointed_thing)

View file

@ -32,7 +32,6 @@ minetest.register_entity("advtrains:discouple", {
end, end,
get_staticdata=function() return "DISCOUPLE" end, get_staticdata=function() return "DISCOUPLE" end,
on_punch=function(self, player) on_punch=function(self, player)
return advtrains.pcall(function()
local pname = player:get_player_name() local pname = player:get_player_name()
if pname and pname~="" and self.wagon then if pname and pname~="" and self.wagon then
if advtrains.safe_decouple_wagon(self.wagon.id, pname) then if advtrains.safe_decouple_wagon(self.wagon.id, pname) then
@ -41,16 +40,14 @@ minetest.register_entity("advtrains:discouple", {
minetest.add_entity(self.object:getpos(), "advtrains:lockmarker") minetest.add_entity(self.object:getpos(), "advtrains:lockmarker")
end end
end end
end)
end, end,
on_step=function(self, dtime) on_step=function(self, dtime)
return advtrains.pcall(function()
if not self.wagon then if not self.wagon then
self.object:remove() self.object:remove()
return return
end end
--getyaw seems to be a reliable method to check if an object is loaded...if it returns nil, it is not. --getyaw seems to be a reliable method to check if an object is loaded...if it returns nil, it is not.
if not self.wagon.object:getyaw() then if not self.wagon.object:get_yaw() then
self.object:remove() self.object:remove()
return return
end end
@ -58,7 +55,6 @@ minetest.register_entity("advtrains:discouple", {
self.object:remove() self.object:remove()
return return
end end
end)
end, end,
}) })
@ -79,7 +75,6 @@ minetest.register_entity("advtrains:couple", {
is_couple=true, is_couple=true,
static_save = false, static_save = false,
on_activate=function(self, staticdata) on_activate=function(self, staticdata)
return advtrains.pcall(function()
if staticdata=="COUPLE" then if staticdata=="COUPLE" then
--couple entities have no right to exist further... --couple entities have no right to exist further...
atprint("Couple loaded from staticdata, destroying") atprint("Couple loaded from staticdata, destroying")
@ -87,11 +82,9 @@ minetest.register_entity("advtrains:couple", {
return return
end end
self.object:set_armor_groups({immmortal=1}) self.object:set_armor_groups({immmortal=1})
end)
end, end,
get_staticdata=function(self) return "COUPLE" end, get_staticdata=function(self) return "COUPLE" end,
on_rightclick=function(self, clicker) on_rightclick=function(self, clicker)
return advtrains.pcall(function()
if not self.train_id_1 or not self.train_id_2 then return end if not self.train_id_1 or not self.train_id_2 then return end
local pname=clicker local pname=clicker
@ -102,15 +95,12 @@ minetest.register_entity("advtrains:couple", {
else else
lockmarker(self.object) lockmarker(self.object)
end end
end)
end, end,
on_step=function(self, dtime) on_step=function(self, dtime)
return advtrains.pcall(function() if advtrains.wagon_outside_range(self.object:getpos()) then
if advtrains.outside_range(self.object:getpos()) then
self.object:remove() self.object:remove()
return return
end end
advtrains.atprint_context_tid=self.train_id_1
if not self.train_id_1 or not self.train_id_2 then atprint("Couple: train ids not set!") self.object:remove() return end if not self.train_id_1 or not self.train_id_2 then atprint("Couple: train ids not set!") self.object:remove() return end
local train1=advtrains.trains[self.train_id_1] local train1=advtrains.trains[self.train_id_1]
@ -155,14 +145,12 @@ minetest.register_entity("advtrains:couple", {
end end
local pos_median=advtrains.pos_median(tp1, tp2) local pos_median=advtrains.pos_median(tp1, tp2)
if not vector.equals(pos_median, self.object:getpos()) then if not vector.equals(pos_median, self.object:getpos()) then
self.object:setpos(pos_median) self.object:set_pos(pos_median)
end end
self.position_set=true self.position_set=true
end end
atprintbm("couple step", t) atprintbm("couple step", t)
advtrains.atprint_context_tid=nil advtrains.atprint_context_tid=nil
end)
end, end,
}) })
minetest.register_entity("advtrains:lockmarker", { minetest.register_entity("advtrains:lockmarker", {
@ -175,7 +163,6 @@ minetest.register_entity("advtrains:lockmarker", {
is_lockmarker=true, is_lockmarker=true,
static_save = false, static_save = false,
on_activate=function(self, staticdata) on_activate=function(self, staticdata)
return advtrains.pcall(function()
if staticdata=="COUPLE" then if staticdata=="COUPLE" then
--couple entities have no right to exist further... --couple entities have no right to exist further...
atprint("Couple loaded from staticdata, destroying") atprint("Couple loaded from staticdata, destroying")
@ -184,7 +171,6 @@ minetest.register_entity("advtrains:lockmarker", {
end end
self.object:set_armor_groups({immmortal=1}) self.object:set_armor_groups({immmortal=1})
self.life=5 self.life=5
end)
end, end,
get_staticdata=function(self) return "COUPLE" end, get_staticdata=function(self) return "COUPLE" end,
on_step=function(self, dtime) on_step=function(self, dtime)

View file

@ -20,6 +20,32 @@ minetest.register_craft({
{'', '', 'default:steel_ingot'}, {'', '', 'default:steel_ingot'},
}, },
}) })
--Wallmounted Signal
minetest.register_craft({
output = 'advtrains:signal_wall_r_off 2',
recipe = {
{'dye:red', 'default:steel_ingot', 'default:steel_ingot'},
{'', 'default:steel_ingot', ''},
{'dye:dark_green', 'default:steel_ingot', 'default:steel_ingot'},
},
})
--Wallmounted Signals can be converted into every orientation by shapeless crafting
minetest.register_craft({
output = 'advtrains:signal_wall_l_off',
type = "shapeless",
recipe = {'advtrains:signal_wall_r_off'},
})
minetest.register_craft({
output = 'advtrains:signal_wall_t_off',
type = "shapeless",
recipe = {'advtrains:signal_wall_l_off'},
})
minetest.register_craft({
output = 'advtrains:signal_wall_r_off',
type = "shapeless",
recipe = {'advtrains:signal_wall_t_off'},
})
--trackworker --trackworker
minetest.register_craft({ minetest.register_craft({

View file

@ -24,7 +24,10 @@ function advtrains.drb_dump(tid)
return return
end end
repeat repeat
atdebug(ringbufs[tid][ringbufcnt[tid]]) local t = ringbufs[tid][ringbufcnt[tid]]
if t then
atdebug(t)
end
ringbufcnt[tid]=ringbufcnt[tid]+1 ringbufcnt[tid]=ringbufcnt[tid]+1
if ringbufcnt[tid] > ringbuflen then if ringbufcnt[tid] > ringbuflen then
ringbufcnt[tid]=0 ringbufcnt[tid]=0

View file

@ -445,3 +445,28 @@ atdebug("pts",os.clock()-t1,"s")
]] ]]
-- Function to check whether a position is near (within range of) any player
function advtrains.position_in_range(pos, range)
if not pos then
return true
end
for _,p in pairs(minetest.get_connected_players()) do
if vector.distance(p:get_pos(),pos)<=range then
return true
end
end
return false
end
local active_node_range = tonumber(minetest.settings:get("active_block_range"))*16 + 16
-- Function to check whether node at position(pos) is "loaded"/"active"
-- That is, whether it is within the active_block_range to a player
if minetest.is_block_active then -- define function differently whether minetest.is_block_active is available or not
advtrains.is_node_loaded = minetest.is_block_active
else
function advtrains.is_node_loaded(pos)
if advtrains.position_in_range(pos, active_node_range) then
return true
end
end
end

View file

@ -26,15 +26,37 @@ minetest.log("action", "[advtrains] Loading...")
attrans = minetest.get_translator ("advtrains") attrans = minetest.get_translator ("advtrains")
--advtrains --advtrains
advtrains = {trains={}, player_to_train_mapping={}}
DUMP_DEBUG_SAVE = false -- =======================Development/debugging settings=====================
GENERATE_ATRICIFIAL_LAG = false -- DO NOT USE FOR NORMAL OPERATION
local DUMP_DEBUG_SAVE = false
-- dump the save files in human-readable format into advtrains_DUMP
local GENERATE_ATRICIFIAL_LAG = false
local HOW_MANY_LAG = 1.0
-- Simulate a higher server step interval, as it occurs when the server is on high load
advtrains.IGNORE_WORLD = false
-- Run advtrains without respecting the world map
-- - No world collision checks occur
-- - The NDB forcibly places all nodes stored in it into the world regardless of the world's content.
-- - Rails do not set the 'attached_node' group
-- This mode can be useful for debugging/testing a world without the map data available
-- In this case, choose 'singlenode' as mapgen
local NO_SAVE = false
-- Do not save any data to advtrains save files
-- ==========================================================================
-- Use a global slowdown factor to slow down train movements. Now a setting
advtrains.DTIME_LIMIT = tonumber(minetest.settings:get("advtrains_dtime_limit")) or 0.2
advtrains.SAVE_INTERVAL = tonumber(minetest.settings:get("advtrains_save_interval")) or 60
--Constant for maximum connection value/division of the circle --Constant for maximum connection value/division of the circle
AT_CMAX = 16 AT_CMAX = 16
advtrains = {trains={}, player_to_train_mapping={}}
-- get wagon loading range -- get wagon loading range
advtrains.wagon_load_range = tonumber(minetest.settings:get("advtrains_wagon_load_range")) advtrains.wagon_load_range = tonumber(minetest.settings:get("advtrains_wagon_load_range"))
if not advtrains.wagon_load_range then if not advtrains.wagon_load_range then
@ -61,25 +83,6 @@ local function reload_saves()
end) end)
end end
function advtrains.pcall(fun)
if no_action then return end
local succ, return1, return2, return3, return4=xpcall(fun, function(err)
atwarn("Lua Error occured: ", err)
atwarn(debug.traceback())
if advtrains.atprint_context_tid then
advtrains.path_print(advtrains.trains[advtrains.atprint_context_tid], atdebug)
atwarn(advtrains.trains[advtrains.atprint_context_tid].debug)
end
end)
if not succ then
reload_saves()
else
return return1, return2, return3, return4
end
end
advtrains.modpath = minetest.get_modpath("advtrains") advtrains.modpath = minetest.get_modpath("advtrains")
--Advtrains dump (special treatment of pos and sigd) --Advtrains dump (special treatment of pos and sigd)
@ -464,7 +467,8 @@ advtrains.avt_save = function(remove_players_from_wagons)
"trainparts", "recently_collided_with_env", "trainparts", "recently_collided_with_env",
"atc_brake_target", "atc_wait_finish", "atc_command", "atc_delay", "door_open", "atc_brake_target", "atc_wait_finish", "atc_command", "atc_delay", "door_open",
"text_outside", "text_inside", "line", "routingcode", "text_outside", "text_inside", "line", "routingcode",
"il_sections", "speed_restriction", "is_shunt", "points_split", "autocouple" "il_sections", "speed_restriction", "is_shunt",
"points_split", "autocouple", "ars_disable",
}) })
--then save it --then save it
tmp_trains[id]=v tmp_trains[id]=v
@ -560,14 +564,17 @@ end
--## MAIN LOOP ##-- --## MAIN LOOP ##--
--Calls all subsequent main tasks of both advtrains and atlatc --Calls all subsequent main tasks of both advtrains and atlatc
local init_load=false local init_load=false
local save_interval=60 local save_timer = advtrains.SAVE_INTERVAL
local save_timer=save_interval
advtrains.mainloop_runcnt=0 advtrains.mainloop_runcnt=0
advtrains.global_slowdown = 1
local t = 0 local t = 0
minetest.register_globalstep(function(dtime_mt) minetest.register_globalstep(function(dtime_mt)
return advtrains.pcall(function() if no_action then
-- the advtrains globalstep is skipped by command. Return immediately
return
end
advtrains.mainloop_runcnt=advtrains.mainloop_runcnt+1 advtrains.mainloop_runcnt=advtrains.mainloop_runcnt+1
--atprint("Running the main loop, runcnt",advtrains.mainloop_runcnt) --atprint("Running the main loop, runcnt",advtrains.mainloop_runcnt)
--call load once. see advtrains.load() comment --call load once. see advtrains.load() comment
@ -575,24 +582,30 @@ minetest.register_globalstep(function(dtime_mt)
advtrains.load() advtrains.load()
end end
local dtime local dtime = dtime_mt * advtrains.global_slowdown
if GENERATE_ATRICIFIAL_LAG then if GENERATE_ATRICIFIAL_LAG then
dtime = 0.2 dtime = HOW_MANY_LAG
if os.clock()<t then if os.clock()<t then
return return
end end
t = os.clock()+0.2 t = os.clock()+HOW_MANY_LAG
else
--limit dtime: if trains move too far in one step, automation may cause stuck and wrongly braking trains
dtime=dtime_mt
if dtime>0.2 then
atprint("Limiting dtime to 0.2!")
dtime=0.2
end end
-- if dtime is too high, decrease global slowdown
if advtrains.DTIME_LIMIT~=0 then
if dtime > advtrains.DTIME_LIMIT then
if advtrains.global_slowdown > 0.1 then
advtrains.global_slowdown = advtrains.global_slowdown - 0.05
else
advtrains.global_slowdown = advtrains.global_slowdown / 2
end
dtime = advtrains.DTIME_LIMIT
end
-- recover global slowdown slowly over time
advtrains.global_slowdown = math.min(advtrains.global_slowdown*1.02, 1)
end end
advtrains.mainloop_trainlogic(dtime) advtrains.mainloop_trainlogic(dtime,advtrains.mainloop_runcnt)
if advtrains_itm_mainloop then if advtrains_itm_mainloop then
advtrains_itm_mainloop(dtime) advtrains_itm_mainloop(dtime)
end end
@ -610,11 +623,10 @@ minetest.register_globalstep(function(dtime_mt)
local t=os.clock() local t=os.clock()
--save --save
advtrains.save() advtrains.save()
save_timer=save_interval save_timer = advtrains.SAVE_INTERVAL
atprintbm("saving", t) atprintbm("saving", t)
end end
end) end)
end)
--if something goes wrong in these functions, there is no help. no pcall here. --if something goes wrong in these functions, there is no help. no pcall here.
@ -642,12 +654,27 @@ function advtrains.save(remove_players_from_wagons)
atwarn("Instructed to save() but load() was never called!") atwarn("Instructed to save() but load() was never called!")
return return
end end
if advtrains.IGNORE_WORLD then
advtrains.ndb.restore_all()
end
if NO_SAVE then
return
end
if no_action then
atlog("[save] Saving requested externally, but Advtrains step is disabled. Not saving any data as state may be inconsistent.")
return
end
local t1 = os.clock()
advtrains.avt_save(remove_players_from_wagons) --saving advtrains. includes ndb at advtrains.ndb.save_data() advtrains.avt_save(remove_players_from_wagons) --saving advtrains. includes ndb at advtrains.ndb.save_data()
if atlatc then if atlatc then
atlatc.save() atlatc.save()
end end
atprint("[save_all]Saved advtrains save files") atlog("Saved advtrains save files, took",math.floor((os.clock()-t1) * 1000),"ms")
-- Cleanup actions
--TODO very simple yet hacky workaround for the "green signals" bug --TODO very simple yet hacky workaround for the "green signals" bug
advtrains.invalidate_all_paths() advtrains.invalidate_all_paths()
end end
@ -662,11 +689,9 @@ minetest.register_chatcommand("at_empty_seats",
description = "Detach all players, especially the offline ones, from all trains. Use only when no one serious is on a train.", -- Full description description = "Detach all players, especially the offline ones, from all trains. Use only when no one serious is on a train.", -- Full description
privs = {train_operator=true, server=true}, -- Require the "privs" privilege to run privs = {train_operator=true, server=true}, -- Require the "privs" privilege to run
func = function(name, param) func = function(name, param)
return advtrains.pcall(function()
atwarn("Data is being saved. While saving, advtrains will remove the players from trains. Save files will be reloaded afterwards!") atwarn("Data is being saved. While saving, advtrains will remove the players from trains. Save files will be reloaded afterwards!")
advtrains.save(true) advtrains.save(true)
reload_saves() reload_saves()
end)
end, end,
}) })
-- This chat command solves another problem: Trains getting randomly stuck. -- This chat command solves another problem: Trains getting randomly stuck.
@ -676,13 +701,36 @@ minetest.register_chatcommand("at_reroute",
description = "Delete all train routes, force them to recalculate", description = "Delete all train routes, force them to recalculate",
privs = {train_operator=true}, -- Only train operator is required, since this is relatively safe. privs = {train_operator=true}, -- Only train operator is required, since this is relatively safe.
func = function(name, param) func = function(name, param)
return advtrains.pcall(function()
advtrains.invalidate_all_paths() advtrains.invalidate_all_paths()
return true, "Successfully invalidated train routes" return true, "Successfully invalidated train routes"
end)
end, end,
}) })
minetest.register_chatcommand("at_disable_step",
{
params = "<yes/no>",
description = "Disable the advtrains globalstep temporarily",
privs = {server=true},
func = function(name, param)
if minetest.is_yes(param) then
-- disable everything, and turn off saving
no_action = true;
atwarn("The advtrains globalstep has been disabled. Trains are not moving, and no data is saved! Run '/at_disable_step no' to enable again!")
return true, "Disabled advtrains successfully"
elseif no_action then
atwarn("Re-enabling advtrains globalstep...")
reload_saves()
return true
else
return false, "Advtrains is already running normally!"
end
end,
})
advtrains.is_no_action = function()
return no_action
end
local tot=(os.clock()-lot)*1000 local tot=(os.clock()-lot)*1000
minetest.log("action", "[advtrains] Loaded in "..tot.."ms") minetest.log("action", "[advtrains] Loaded in "..tot.."ms")

View file

@ -4,20 +4,36 @@
--[[ --[[
Documentation of train.lzb table Documentation of train.lzb table
train.lzb = { train.lzb = {
trav = Current index that the traverser has advanced so far trav_index = Current index that the traverser has advanced so far
oncoming = table containing oncoming signals, in order of appearance on the path checkpoints = table containing oncoming signals, in order of index
{ {
pos = position of the point pos = position of the point
idx = where this is on the path index = where this is on the path
spd = speed allowed to pass speed = speed allowed to pass. nil = no effect
fun = function(pos, id, train, index, speed, lzbdata) callback = function(pos, id, train, index, speed, lzbdata)
-- Function that determines what to do on the train in the moment it drives over that point. -- Function that determines what to do on the train in the moment it drives over that point.
-- When spd==0, called instead when train has stopped in front
-- nil = no effect
lzbdata = {}
-- Table of custom data filled in by approach callbacks
-- Whenever an approach callback inserts an LZB checkpoint with changed lzbdata,
-- all consecutive approach callbacks will see these passed as lzbdata table.
udata = arbitrary user data, no official way to retrieve (do not use)
} }
trav_lzbdata = currently active lzbdata table at traverser index
} }
each step, for every item in "oncoming", we need to determine the location to start braking (+ some safety margin) The LZB subsystem keeps track of "checkpoints" the train will pass in the future, and has two main tasks:
and, if we passed this point for at least one of the items, initiate brake. 1. run approach callbacks, and run callbacks when passing LZB checkpoints
When speed has dropped below, say 3, decrease the margin to zero, so that trains actually stop at the signal IP. 2. keep track of the permitted speed at checkpoints, and make sure that the train brakes accordingly
The spd variable and travsht need to be updated on every aspect change. it's probably best to reset everything when any aspect changes To perform 2, it populates the train.path_speed table which is handled along with the path subsystem.
This table is used in trainlogic.lua/train_step_b() and applied to the velocity calculations.
Note: in contrast to node enter callbacks, which are called when the train passes the .5 index mark, LZB callbacks are executed on passing the .0 index mark!
If an LZB checkpoint has speed 0, the train will still enter the node (the enter callback will be called), but will stop at the 0.9 index mark (for details, see SLOW_APPROACH in trainlogic.lua)
The start point for the LZB traverser (and thus the first node that will receive an approach callback) is floor(train.index) + 1. This means, once the LZB checkpoint callback has fired,
this path node will not receive any further approach callbacks for the same approach situation
]] ]]
@ -45,41 +61,129 @@ function advtrains.set_lzb_param(par, val)
end end
end end
local function resolve_latest_lzbdata(ckp, index)
local i = #ckp
local ckpi
while i>0 do
ckpi = ckp[i]
if ckpi.index <= index and ckpi.lzbdata then
return ckpi.lzbdata
end
i=i-1
end
return {}
end
local function look_ahead(id, train) local function look_ahead(id, train)
local lzb = train.lzb
if lzb.zero_checkpoint then
-- if the checkpoints list contains a zero checkpoint, don't look ahead
-- in order to not trigger approach callbacks on the wrong path
return
end
local acc = advtrains.get_acceleration(train, 1) local acc = advtrains.get_acceleration(train, 1)
local vel = train.velocity -- worst-case: the starting point is maximum speed
local vel = train.max_speed or train.velocity
local brakedst = ( -(vel*vel) / (2*acc) ) * params.DST_FACTOR local brakedst = ( -(vel*vel) / (2*acc) ) * params.DST_FACTOR
local brake_i = advtrains.path_get_index_by_offset(train, train.index, brakedst + params.BRAKE_SPACE) --local brake_i = advtrains.path_get_index_by_offset(train, train.index, brakedst + params.BRAKE_SPACE)
-- worst case (don't use index_by_offset)
local brake_i = atfloor(train.index + brakedst + params.BRAKE_SPACE)
atprint("LZB: looking ahead up to ", brake_i)
--local aware_i = advtrains.path_get_index_by_offset(train, brake_i, AWARE_ZONE) --local aware_i = advtrains.path_get_index_by_offset(train, brake_i, AWARE_ZONE)
local lzb = train.lzb local trav = lzb.trav_index
local trav = lzb.trav -- retrieve latest lzbdata
if not lzb.trav_lzbdata then
lzb.trav_lzbdata = resolve_latest_lzbdata(lzb.checkpoints, trav)
end
--train.debug = lspd if lzb.trav_lzbdata.off_track then
--previous position was off track, do not scan any further
end
while trav <= brake_i do while trav <= brake_i and not lzb.zero_checkpoint do
trav = trav + 1
local pos = advtrains.path_get(train, trav) local pos = advtrains.path_get(train, trav)
-- check offtrack -- check offtrack
if trav > train.path_trk_f then if trav - 1 == train.path_trk_f then
table.insert(lzb.oncoming, { lzb.trav_lzbdata.off_track = true
pos = pos, advtrains.lzb_add_checkpoint(train, trav - 1, 0, nil, lzb.trav_lzbdata)
idx = trav-1,
spd = 0,
})
else else
-- run callbacks -- run callbacks
-- Note: those callbacks are defined in trainlogic.lua for consistency with the other node callbacks -- Note: those callbacks are defined in trainlogic.lua for consistency with the other node callbacks
advtrains.tnc_call_approach_callback(pos, id, train, trav, lzb.data) advtrains.tnc_call_approach_callback(pos, id, train, trav, lzb.trav_lzbdata)
end end
trav = trav + 1
end
lzb.trav_index = trav
end
advtrains.lzb_look_ahead = look_ahead
local function call_runover_callbacks(id, train)
if not train.lzb then return end
local i = 1
local idx = atfloor(train.index)
local ckp = train.lzb.checkpoints
while ckp[i] do
if ckp[i].index <= idx then
atprint("LZB: checkpoint run over: i=",ckp[i].index,"s=",ckp[i].speed)
-- call callback
local it = ckp[i]
if it.callback then
it.callback(it.pos, id, train, it.index, it.speed, train.lzb.lzbdata)
end
-- note: lzbdata is always defined as look_ahead was called before
table.remove(ckp, i)
else
i = i + 1
end
end
end end
lzb.trav = trav -- Flood-fills train.path_speed, based on this checkpoint
local function apply_checkpoint_to_path(train, checkpoint)
if not checkpoint.speed then
return
end
atprint("LZB: applying checkpoint: i=",checkpoint.index,"s=",checkpoint.speed)
if checkpoint.speed == 0 then
train.lzb.zero_checkpoint = true
end
-- make sure path exists until checkpoint
local pos = advtrains.path_get(train, checkpoint.index)
local brake_accel = advtrains.get_acceleration(train, 11)
-- start with the checkpoint index at specified speed
local index = checkpoint.index
local p_speed -- speed in path_speed
local c_speed = checkpoint.speed -- calculated speed at current index
while true do
p_speed = train.path_speed[index]
if (p_speed and p_speed <= c_speed) or index < train.index then
--we're done. train already slower than wanted at this position
return
end
-- insert calculated target speed
train.path_speed[index] = c_speed
-- calculate c_speed at previous index
advtrains.path_get(train, index-1)
local eldist = train.path_dist[index] - train.path_dist[index-1]
-- Calculate the start velocity the train would have if it had a end velocity of c_speed and accelerating with brake_accel, after a distance of eldist:
-- v0² = v1² - 2*a*s
c_speed = math.sqrt( (c_speed * c_speed) - (2 * brake_accel * eldist) )
index = index - 1
end
end end
--[[ --[[
@ -90,95 +194,74 @@ s = v0 * ------- + - * | ------- | = -----------
a 2 \ a / 2*a a 2 \ a / 2*a
]] ]]
local function apply_control(id, train) -- Removes all LZB checkpoints and restarts the traverser at the current train index
local lzb = train.lzb function advtrains.lzb_invalidate(train)
train.lzb = {
local i = 1 trav_index = atfloor(train.index) + 1,
while i<=#lzb.oncoming do checkpoints = {},
if lzb.oncoming[i].idx < train.index then }
local ent = lzb.oncoming[i]
if ent.fun then
ent.fun(ent.pos, id, train, ent.idx, ent.spd, lzb.data)
end end
table.remove(lzb.oncoming, i) -- LZB part of path_invalidate_ahead. Clears all checkpoints that are ahead of start_idx
-- in contrast to path_inv_ahead, doesn't complain if start_idx is behind train.index, clears everything then
function advtrains.lzb_invalidate_ahead(train, start_idx)
if train.lzb then
local idx = atfloor(start_idx)
local i = 1
while train.lzb.checkpoints[i] do
if train.lzb.checkpoints[i].index >= idx then
table.remove(train.lzb.checkpoints, i)
else else
i=i+1 i=i+1
end end
end end
train.lzb.trav_index = idx
for i, it in ipairs(lzb.oncoming) do -- FIX reset trav_lzbdata (look_ahead fetches these when required)
local a = advtrains.get_acceleration(train, 1) --should be negative train.lzb.trav_lzbdata = nil
local v0 = train.velocity -- re-apply all checkpoints to path_speed
local v1 = it.spd train.path_speed = {}
if v1 and v1 <= v0 then train.lzb.zero_checkpoint = false
local s = (v1*v1 - v0*v0) / (2*a) for _,ckp in ipairs(train.lzb.checkpoints) do
apply_checkpoint_to_path(train, ckp)
local st = s + params.ADD_SLOW
if v0 > 3 then
st = s + params.ADD_FAST
end
if v0<=0 then
st = s + params.ADD_STAND
end
local i = advtrains.path_get_index_by_offset(train, it.idx, -st)
--train.debug = dump({v0f=v0*f, aff=a*f*f,v0=v0, v1=v1, f=f, a=a, s=s, st=st, i=i, idx=train.index})
if i <= train.index then
-- Gotcha! Braking...
train.ctrl.lzb = 1
--train.debug = train.debug .. "BRAKE!!!"
return
end
i = advtrains.path_get_index_by_offset(train, i, -params.ZONE_ROLL)
if i <= train.index and v0>1 then
-- roll control
train.ctrl.lzb = 2
return
end
i = advtrains.path_get_index_by_offset(train, i, -params.ZONE_HOLD)
if i <= train.index and v0>1 then
-- hold speed
train.ctrl.lzb = 3
return
end end
end end
end end
train.ctrl.lzb = nil
end
local function invalidate(train)
train.lzb = {
trav = atfloor(train.index),
data = {},
oncoming = {},
}
end
function advtrains.lzb_invalidate(train)
invalidate(train)
end
-- Add LZB control point -- Add LZB control point
-- udata: User-defined additional data -- lzbdata: If you modify lzbdata in an approach callback, you MUST add a checkpoint AND pass the (modified) lzbdata into it.
function advtrains.lzb_add_checkpoint(train, index, speed, callback, udata) -- If you DON'T modify lzbdata, you MUST pass nil as lzbdata. Always modify the lzbdata table in place, never overwrite it!
-- udata: user-defined data, do not use externally
function advtrains.lzb_add_checkpoint(train, index, speed, callback, lzbdata, udata)
local lzb = train.lzb local lzb = train.lzb
local pos = advtrains.path_get(train, index) local pos = advtrains.path_get(train, index)
table.insert(lzb.oncoming, { local lzbdata_c = nil
if lzbdata then
-- make a shallow copy of lzbdata
lzbdata_c = {}
for k,v in pairs(lzbdata) do lzbdata_c[k] = v end
end
local ckp = {
pos = pos, pos = pos,
idx = index, index = index,
spd = speed, speed = speed,
fun = callback, callback = callback,
lzbdata = lzbdata_c,
udata = udata, udata = udata,
}) }
table.insert(lzb.checkpoints, ckp)
apply_checkpoint_to_path(train, ckp)
end end
advtrains.te_register_on_new_path(function(id, train) advtrains.te_register_on_new_path(function(id, train)
invalidate(train) advtrains.lzb_invalidate(train)
look_ahead(id, train) -- Taken care of in pre-move hook (see train_step_b)
--look_ahead(id, train)
end)
advtrains.te_register_on_invalidate_ahead(function(id, train, start_idx)
advtrains.lzb_invalidate_ahead(train, start_idx)
end) end)
advtrains.te_register_on_update(function(id, train) advtrains.te_register_on_update(function(id, train)
@ -186,6 +269,8 @@ advtrains.te_register_on_update(function(id, train)
atprint("LZB run: no path on train, skip step") atprint("LZB run: no path on train, skip step")
return return
end end
look_ahead(id, train) -- Note: look_ahead called from train_step_b before applying movement
apply_control(id, train) -- TODO: if more pre-move hooks are added, make a separate callback hook
--look_ahead(id, train)
call_runover_callbacks(id, train)
end, true) end, true)

View file

@ -223,7 +223,7 @@ end
function ndb.swap_node(pos, node, no_inval) function ndb.swap_node(pos, node, no_inval)
if minetest.get_node_or_nil(pos) then if advtrains.is_node_loaded(pos) then
minetest.swap_node(pos, node) minetest.swap_node(pos, node)
end end
ndb.update(pos, node) ndb.update(pos, node)
@ -246,7 +246,7 @@ function ndb.update(pos, pnode)
local resid = (nid * 4) + (l2b(node.param2 or 0)) local resid = (nid * 4) + (l2b(node.param2 or 0))
ndbset(pos.x, pos.y, pos.z, resid ) ndbset(pos.x, pos.y, pos.z, resid )
--atdebug("nodedb: updating node", pos, "stored nid",nid,"assigned",ndb_nodeids[nid],"resulting cid",resid) --atdebug("nodedb: updating node", pos, "stored nid",nid,"assigned",ndb_nodeids[nid],"resulting cid",resid)
minetest.after(0, advtrains.invalidate_all_paths, pos) advtrains.invalidate_all_paths_ahead(pos)
else else
--at this position there is no longer a node that needs to be tracked. --at this position there is no longer a node that needs to be tracked.
--atdebug("nodedb: updating node", pos, "cleared") --atdebug("nodedb: updating node", pos, "cleared")
@ -281,8 +281,9 @@ function advtrains.get_rail_info_at(pos, drives_on)
return true, conns, railheight return true, conns, railheight
end end
local IGNORE_WORLD = advtrains.IGNORE_WORLD
ndb.run_lbm = function(pos, node) ndb.run_lbm = function(pos, node)
return advtrains.pcall(function()
local cid=ndbget(pos.x, pos.y, pos.z) local cid=ndbget(pos.x, pos.y, pos.z)
if cid then if cid then
--if in database, detect changes and apply. --if in database, detect changes and apply.
@ -294,11 +295,12 @@ ndb.run_lbm = function(pos, node)
ndb.update(pos, node) ndb.update(pos, node)
else else
if (nodeid~=node.name or param2~=node.param2) then if (nodeid~=node.name or param2~=node.param2) then
atprint("nodedb: lbm replaced", pos, "with nodeid", nodeid, "param2", param2, "cid is", cid) --atprint("nodedb: lbm replaced", pos, "with nodeid", nodeid, "param2", param2, "cid is", cid)
minetest.swap_node(pos, {name=nodeid, param2 = param2}) local newnode = {name=nodeid, param2 = param2}
minetest.swap_node(pos, newnode)
local ndef=minetest.registered_nodes[nodeid] local ndef=minetest.registered_nodes[nodeid]
if ndef and ndef.on_updated_from_nodedb then if ndef and ndef.advtrains and ndef.advtrains.on_updated_from_nodedb then
ndef.on_updated_from_nodedb(pos, node) ndef.advtrains.on_updated_from_nodedb(pos, newnode)
end end
return true return true
end end
@ -309,7 +311,6 @@ ndb.run_lbm = function(pos, node)
ndb.update(pos, node) ndb.update(pos, node)
end end
return false return false
end)
end end
@ -326,6 +327,7 @@ minetest.register_lbm({
--used when restoring stuff after a crash --used when restoring stuff after a crash
ndb.restore_all = function() ndb.restore_all = function()
--atlog("Updating the map from the nodedb, this may take a while") --atlog("Updating the map from the nodedb, this may take a while")
local t1 = os.clock()
local cnt=0 local cnt=0
local dcnt=0 local dcnt=0
for y, ny in pairs(ndb_nodes) do for y, ny in pairs(ndb_nodes) do
@ -336,7 +338,7 @@ ndb.restore_all = function()
if node then if node then
local ori_ndef=minetest.registered_nodes[node.name] local ori_ndef=minetest.registered_nodes[node.name]
local ndbnode=ndb.get_node_raw(pos) local ndbnode=ndb.get_node_raw(pos)
if ori_ndef and ori_ndef.groups.save_in_at_nodedb then --check if this node has been worldedited, and don't replace then if (ori_ndef and ori_ndef.groups.save_in_at_nodedb) or IGNORE_WORLD then --check if this node has been worldedited, and don't replace then
if (ndbnode.name~=node.name or ndbnode.param2~=node.param2) then if (ndbnode.name~=node.name or ndbnode.param2~=node.param2) then
minetest.swap_node(pos, ndbnode) minetest.swap_node(pos, ndbnode)
--atlog("Replaced",node.name,"@",pos,"with",ndbnode.name) --atlog("Replaced",node.name,"@",pos,"with",ndbnode.name)
@ -351,16 +353,14 @@ ndb.restore_all = function()
end end
end end
end end
local text="Restore node database: Replaced "..cnt.." nodes, removed "..dcnt.." ghost nodes." local text="Restore node database: Replaced "..cnt.." nodes, removed "..dcnt.." ghost nodes. (took "..math.floor((os.clock()-t1) * 1000).."ms)"
atlog(text) atlog(text)
return text return text
end end
minetest.register_on_dignode(function(pos, oldnode, digger) minetest.register_on_dignode(function(pos, oldnode, digger)
return advtrains.pcall(function()
ndb.clear(pos) ndb.clear(pos)
end) end)
end)
function ndb.get_nodes() function ndb.get_nodes()
return ndb_nodes return ndb_nodes
@ -380,14 +380,12 @@ minetest.register_chatcommand("at_sync_ndb",
description = "Write node db back to map and find ghost nodes", -- Full description description = "Write node db back to map and find ghost nodes", -- Full description
privs = {train_operator=true}, privs = {train_operator=true},
func = function(name, param) func = function(name, param)
return advtrains.pcall(function() if os.time() < ptime+30 and not minetest.get_player_privs(name, "server") then
if os.time() < ptime+30 then
return false, "Please wait at least 30s from the previous execution of /at_restore_ndb!" return false, "Please wait at least 30s from the previous execution of /at_restore_ndb!"
end end
local text = ndb.restore_all() local text = ndb.restore_all()
ptime=os.time() ptime=os.time()
return true, text return true, text
end)
end, end,
}) })

View file

@ -196,7 +196,6 @@ function o.get_trains_over(ppos)
local r = {} local r = {}
local i = 1 local i = 1
while t[i] do while t[i] do
local train = advtrains.trains[t[i]]
local idx = t[i+1] local idx = t[i+1]
r[t[i]] = idx r[t[i]] = idx
i = i + 2 i = i + 2

View file

@ -13,16 +13,18 @@ minetest.override_item("mesecons_switch:mesecon_switch_off", {
mesecon.receptor_on(pos) mesecon.receptor_on(pos)
minetest.sound_play("mesecons_switch", {pos=pos}) minetest.sound_play("mesecons_switch", {pos=pos})
end, end,
on_updated_from_nodedb = function(pos, node)
mesecon.receptor_off(pos)
end,
advtrains = { advtrains = {
getstate = "off", getstate = "off",
setstate = function(pos, node, newstate) setstate = function(pos, node, newstate)
if newstate=="on" then if newstate=="on" then
advtrains.ndb.swap_node(pos, {name="mesecons_switch:mesecon_switch_on", param2=node.param2}) advtrains.ndb.swap_node(pos, {name="mesecons_switch:mesecon_switch_on", param2=node.param2})
if advtrains.is_node_loaded(pos) then
mesecon.receptor_on(pos) mesecon.receptor_on(pos)
end end
end
end,
on_updated_from_nodedb = function(pos, node)
mesecon.receptor_off(pos)
end, end,
}, },
}) })
@ -38,17 +40,19 @@ minetest.override_item("mesecons_switch:mesecon_switch_on", {
mesecon.receptor_off(pos) mesecon.receptor_off(pos)
minetest.sound_play("mesecons_switch", {pos=pos}) minetest.sound_play("mesecons_switch", {pos=pos})
end, end,
on_updated_from_nodedb = function(pos, node)
mesecon.receptor_on(pos)
end,
advtrains = { advtrains = {
getstate = "on", getstate = "on",
setstate = function(pos, node, newstate) setstate = function(pos, node, newstate)
if newstate=="off" then if newstate=="off" then
advtrains.ndb.swap_node(pos, {name="mesecons_switch:mesecon_switch_off", param2=node.param2}) advtrains.ndb.swap_node(pos, {name="mesecons_switch:mesecon_switch_off", param2=node.param2})
if advtrains.is_node_loaded(pos) then
mesecon.receptor_off(pos) mesecon.receptor_off(pos)
end end
end
end, end,
fallback_state = "off", fallback_state = "off",
on_updated_from_nodedb = function(pos, node)
mesecon.receptor_on(pos)
end,
}, },
}) })

View file

@ -19,6 +19,8 @@
-- When the day comes on that path!=node, these will only be set if this index represents a transition between rail nodes -- When the day comes on that path!=node, these will only be set if this index represents a transition between rail nodes
-- path_dist - The total distance of this path element from path element 0 -- path_dist - The total distance of this path element from path element 0
-- path_dir - The direction of this path item's transition to the next path item, which is the angle of conns[path_cn[i]].c -- path_dir - The direction of this path item's transition to the next path item, which is the angle of conns[path_cn[i]].c
-- path_speed- Populated by the LZB system. The maximum speed (velocity) permitted in the moment this path item is passed.
-- (this saves brake distance calculations every step to determine LZB control). nil means no limit.
--Variables: --Variables:
-- path_ext_f/b - how far path[i] is set -- path_ext_f/b - how far path[i] is set
-- path_trk_f/b - how far the path extends along a track. beyond those values, paths are generated in a straight line. -- path_trk_f/b - how far the path extends along a track. beyond those values, paths are generated in a straight line.
@ -52,6 +54,8 @@ function advtrains.path_create(train, pos, connid, rel_index)
[0] = advtrains.conn_angle_median(conns[mconnid].c, conns[connid].c) [0] = advtrains.conn_angle_median(conns[mconnid].c, conns[connid].c)
} }
train.path_speed = { }
train.path_ext_f=0 train.path_ext_f=0
train.path_ext_b=0 train.path_ext_b=0
train.path_trk_f=0 train.path_trk_f=0
@ -123,6 +127,7 @@ function advtrains.path_invalidate(train, ignore_lock)
train.path_cp = nil train.path_cp = nil
train.path_cn = nil train.path_cn = nil
train.path_dir = nil train.path_dir = nil
train.path_speed = nil
train.path_ext_f=0 train.path_ext_f=0
train.path_ext_b=0 train.path_ext_b=0
train.path_trk_f=0 train.path_trk_f=0
@ -131,6 +136,40 @@ function advtrains.path_invalidate(train, ignore_lock)
train.path_req_b=0 train.path_req_b=0
train.dirty = true train.dirty = true
--atdebug(train.id, "Path invalidated")
end
-- Keeps the path intact, but invalidates all path nodes from the specified index (inclusive)
-- onwards. This has the advantage that we don't need to recalculate the whole path, and we can do it synchronously.
function advtrains.path_invalidate_ahead(train, start_idx, ignore_when_passed)
if not train.path then
-- the path wasn't even initialized. Nothing to do
return
end
local idx = atfloor(start_idx)
--atdebug("Invalidate_ahead:",train.id,"start_index",start_idx,"cur_idx",train.index)
if(idx <= train.index - 0.5) then
if ignore_when_passed then
--atdebug("ignored passed")
return
end
advtrains.path_print(train, atwarn)
error("Train "+train.id+": Cannot path_invalidate_ahead start_idx="+idx+" as train has already passed!")
end
-- leave current node in path, it won't change. What might change is the path onward from here (e.g. switch)
local i = idx + 1
while train.path[i] do
advtrains.occ.clear_item(train.id, advtrains.round_vector_floor_y(train.path[i]))
i = i+1
end
train.path_ext_f=idx
train.path_trk_f=math.min(idx, train.path_trk_f)
-- callbacks called anyway for current node, because of LZB
advtrains.run_callbacks_invahead(train.id, train, idx)
end end
-- Prints a path using the passed print function -- Prints a path using the passed print function
@ -141,12 +180,12 @@ function advtrains.path_print(train, printf)
printf("path_print: Path is invalidated/inexistant.") printf("path_print: Path is invalidated/inexistant.")
return return
end end
printf("i: CP Position Dir CN Dist") printf("i: CP Position Dir CN Dist Speed")
for i = train.path_ext_b, train.path_ext_f do for i = train.path_ext_b, train.path_ext_f do
if i==train.path_trk_b then if i==train.path_trk_b then
printf("--Back on-track border here--") printf("--Back on-track border here--")
end end
printf(i,": ",train.path_cp[i]," ",train.path[i]," ",train.path_dir[i]," ",train.path_cn[i]," ",train.path_dist[i],"") printf(i,": ",train.path_cp[i]," ",train.path[i]," ",train.path_dir[i]," ",train.path_cn[i]," ",train.path_dist[i]," ",train.path_speed[i])
if i==train.path_trk_f then if i==train.path_trk_f then
printf("--Front on-track border here--") printf("--Front on-track border here--")
end end
@ -350,6 +389,7 @@ function advtrains.path_clear_unused(train)
train.path_ext_b = i + 1 train.path_ext_b = i + 1
end end
--[[ Why exactly are we clearing path from the front? This doesn't make sense!
for i = train.path_ext_f,train.path_req_f + PATH_CLEAR_KEEP,-1 do for i = train.path_ext_f,train.path_req_f + PATH_CLEAR_KEEP,-1 do
advtrains.occ.clear_item(train.id, advtrains.round_vector_floor_y(train.path[i])) advtrains.occ.clear_item(train.id, advtrains.round_vector_floor_y(train.path[i]))
train.path[i] = nil train.path[i] = nil
@ -358,14 +398,16 @@ function advtrains.path_clear_unused(train)
train.path_cn[i] = nil train.path_cn[i] = nil
train.path_dir[i+1] = nil train.path_dir[i+1] = nil
train.path_ext_f = i - 1 train.path_ext_f = i - 1
end end ]]
train.path_trk_b = math.max(train.path_trk_b, train.path_ext_b) train.path_trk_b = math.max(train.path_trk_b, train.path_ext_b)
train.path_trk_f = math.min(train.path_trk_f, train.path_ext_f) --train.path_trk_f = math.min(train.path_trk_f, train.path_ext_f)
train.path_req_f = math.ceil(train.index) train.path_req_f = math.ceil(train.index)
train.path_req_b = math.floor(train.end_index or train.index) train.path_req_b = math.floor(train.end_index or train.index)
end end
-- Scan the path of the train for position, without querying the occupation table
-- returns index, or nil if pos is not on the path
function advtrains.path_lookup(train, pos) function advtrains.path_lookup(train, pos)
local cp = advtrains.round_vector_floor_y(pos) local cp = advtrains.round_vector_floor_y(pos)
for i = train.path_ext_b, train.path_ext_f do for i = train.path_ext_b, train.path_ext_f do

View file

@ -45,3 +45,14 @@ advtrains_overrun_mode (Overrun mode) enum drop none,drop,normal
# When a wagon leaves this range + 32 nodes, it is unloaded # When a wagon leaves this range + 32 nodes, it is unloaded
# If unset, defaults to active_block_range*16 # If unset, defaults to active_block_range*16
advtrains_wagon_load_range (Wagon Entity Load/Unload Range) int 96 32 512 advtrains_wagon_load_range (Wagon Entity Load/Unload Range) int 96 32 512
# Simulation DTime Limit after which slow-down becomes effective
# When the dtime value (time since last server step) is higher than this value,
# advtrains applies a global slow-down factor to the dtime and to the velocity and
# acceleration of wagons to decrease server load.
# A value of 0 (default) disables this behavior.
advtrains_dtime_limit (DTime Limit for slow-down) float 0.2 0 5
# Time interval in seconds in which advtrains stores its save data to disk
# Nevertheless, advtrains saves all data when shutting down the server.
advtrains_save_interval (Save Interval) int 60 20 3600

View file

@ -12,22 +12,26 @@ end
local function aspect(b) local function aspect(b)
return { return {
main = { main = (not b) and 0, -- b ? false : 0
free = b, shunt = false,
speed = -1, proceed_as_main = true,
}, dst = false,
shunt = {
free = false,
proceed_as_main = true
},
dst = {
free = true,
speed = -1,
},
info = {} info = {}
} }
end end
local suppasp = {
main = {0, false},
dst = {false},
shunt = nil,
proceed_as_main = true,
info = {
call_on = false,
dead_end = false,
w_speed = nil,
}
}
for r,f in pairs({on={as="off", ls="green", als="red"}, off={as="on", ls="red", als="green"}}) do for r,f in pairs({on={as="off", ls="green", als="red"}, off={as="on", ls="red", als="green"}}) do
advtrains.trackplacer.register_tracktype("advtrains:retrosignal", "") advtrains.trackplacer.register_tracktype("advtrains:retrosignal", "")
@ -63,6 +67,7 @@ for r,f in pairs({on={as="off", ls="green", als="red"}, off={as="on", ls="red",
rules=advtrains.meseconrules, rules=advtrains.meseconrules,
["action_"..f.as] = function (pos, node) ["action_"..f.as] = function (pos, node)
advtrains.ndb.swap_node(pos, {name = "advtrains:retrosignal_"..f.as..rotation, param2 = node.param2}, true) advtrains.ndb.swap_node(pos, {name = "advtrains:retrosignal_"..f.as..rotation, param2 = node.param2}, true)
advtrains.interlocking.signal_on_aspect_changed(pos)
end end
}}, }},
on_rightclick=function(pos, node, player) on_rightclick=function(pos, node, player)
@ -74,12 +79,13 @@ for r,f in pairs({on={as="off", ls="green", als="red"}, off={as="on", ls="red",
advtrains.interlocking.show_ip_form(pos, pname) advtrains.interlocking.show_ip_form(pos, pname)
elseif advtrains.check_turnout_signal_protection(pos, player:get_player_name()) then elseif advtrains.check_turnout_signal_protection(pos, player:get_player_name()) then
advtrains.ndb.swap_node(pos, {name = "advtrains:retrosignal_"..f.as..rotation, param2 = node.param2}, true) advtrains.ndb.swap_node(pos, {name = "advtrains:retrosignal_"..f.as..rotation, param2 = node.param2}, true)
advtrains.interlocking.signal_on_aspect_changed(pos)
end end
end, end,
-- new signal API -- new signal API
advtrains = { advtrains = {
set_aspect = function(pos, node, asp) set_aspect = function(pos, node, asp)
if asp.main.free then if asp.main ~= 0 then
advtrains.ndb.swap_node(pos, {name = "advtrains:retrosignal_on"..rotation, param2 = node.param2}, true) advtrains.ndb.swap_node(pos, {name = "advtrains:retrosignal_on"..rotation, param2 = node.param2}, true)
else else
advtrains.ndb.swap_node(pos, {name = "advtrains:retrosignal_off"..rotation, param2 = node.param2}, true) advtrains.ndb.swap_node(pos, {name = "advtrains:retrosignal_off"..rotation, param2 = node.param2}, true)
@ -87,7 +93,8 @@ for r,f in pairs({on={as="off", ls="green", als="red"}, off={as="on", ls="red",
end, end,
get_aspect = function(pos, node) get_aspect = function(pos, node)
return aspect(r=="on") return aspect(r=="on")
end end,
supported_aspects = suppasp,
}, },
can_dig = can_dig_func, can_dig = can_dig_func,
}) })
@ -120,6 +127,7 @@ for r,f in pairs({on={as="off", ls="green", als="red"}, off={as="on", ls="red",
rules=advtrains.meseconrules, rules=advtrains.meseconrules,
["action_"..f.as] = function (pos, node) ["action_"..f.as] = function (pos, node)
advtrains.setstate(pos, f.als, node) advtrains.setstate(pos, f.als, node)
advtrains.interlocking.signal_on_aspect_changed(pos)
end end
}}, }},
on_rightclick=function(pos, node, player) on_rightclick=function(pos, node, player)
@ -131,12 +139,13 @@ for r,f in pairs({on={as="off", ls="green", als="red"}, off={as="on", ls="red",
advtrains.interlocking.show_ip_form(pos, pname) advtrains.interlocking.show_ip_form(pos, pname)
elseif advtrains.check_turnout_signal_protection(pos, player:get_player_name()) then elseif advtrains.check_turnout_signal_protection(pos, player:get_player_name()) then
advtrains.setstate(pos, f.als, node) advtrains.setstate(pos, f.als, node)
advtrains.interlocking.signal_on_aspect_changed(pos)
end end
end, end,
-- new signal API -- new signal API
advtrains = { advtrains = {
set_aspect = function(pos, node, asp) set_aspect = function(pos, node, asp)
if asp.main.free then if asp.main ~= 0 then
advtrains.ndb.swap_node(pos, {name = "advtrains:signal_on"..rotation, param2 = node.param2}, true) advtrains.ndb.swap_node(pos, {name = "advtrains:signal_on"..rotation, param2 = node.param2}, true)
else else
advtrains.ndb.swap_node(pos, {name = "advtrains:signal_off"..rotation, param2 = node.param2}, true) advtrains.ndb.swap_node(pos, {name = "advtrains:signal_off"..rotation, param2 = node.param2}, true)
@ -145,6 +154,7 @@ for r,f in pairs({on={as="off", ls="green", als="red"}, off={as="on", ls="red",
get_aspect = function(pos, node) get_aspect = function(pos, node)
return aspect(r=="on") return aspect(r=="on")
end, end,
supported_aspects = suppasp,
getstate = f.ls, getstate = f.ls,
setstate = function(pos, node, newstate) setstate = function(pos, node, newstate)
if newstate == f.als then if newstate == f.als then
@ -204,7 +214,7 @@ for r,f in pairs({on={as="off", ls="green", als="red"}, off={as="on", ls="red",
-- new signal API -- new signal API
advtrains = { advtrains = {
set_aspect = function(pos, node, asp) set_aspect = function(pos, node, asp)
if asp.main.free then if asp.main ~= 0 then
advtrains.ndb.swap_node(pos, {name = "advtrains:signal_wall_"..loc.."_on", param2 = node.param2}, true) advtrains.ndb.swap_node(pos, {name = "advtrains:signal_wall_"..loc.."_on", param2 = node.param2}, true)
else else
advtrains.ndb.swap_node(pos, {name = "advtrains:signal_wall_"..loc.."_off", param2 = node.param2}, true) advtrains.ndb.swap_node(pos, {name = "advtrains:signal_wall_"..loc.."_off", param2 = node.param2}, true)
@ -213,6 +223,7 @@ for r,f in pairs({on={as="off", ls="green", als="red"}, off={as="on", ls="red",
get_aspect = function(pos, node) get_aspect = function(pos, node)
return aspect(r=="on") return aspect(r=="on")
end, end,
supported_aspects = suppasp,
getstate = f.ls, getstate = f.ls,
setstate = function(pos, node, newstate) setstate = function(pos, node, newstate)
if newstate == f.als then if newstate == f.als then

View file

@ -275,7 +275,6 @@ function tp.register_track_placer(nnprefix, imgprefix, dispname, def)
groups={advtrains_trackplacer=1, digtron_on_place=1}, groups={advtrains_trackplacer=1, digtron_on_place=1},
liquids_pointable = def.liquids_pointable, liquids_pointable = def.liquids_pointable,
on_place = function(itemstack, placer, pointed_thing) on_place = function(itemstack, placer, pointed_thing)
return advtrains.pcall(function()
local name = placer:get_player_name() local name = placer:get_player_name()
if not name then if not name then
return itemstack, false return itemstack, false
@ -303,7 +302,6 @@ function tp.register_track_placer(nnprefix, imgprefix, dispname, def)
end end
end end
return itemstack, true return itemstack, true
end)
end, end,
}) })
end end
@ -317,7 +315,6 @@ minetest.register_craftitem("advtrains:trackworker",{
wield_image = "advtrains_trackworker.png", wield_image = "advtrains_trackworker.png",
stack_max = 1, stack_max = 1,
on_place = function(itemstack, placer, pointed_thing) on_place = function(itemstack, placer, pointed_thing)
return advtrains.pcall(function()
local name = placer:get_player_name() local name = placer:get_player_name()
if not name then if not name then
return return
@ -382,10 +379,8 @@ minetest.register_craftitem("advtrains:trackworker",{
advtrains.ndb.swap_node(pos, {name=nnprefix.."_"..suffix..modext[modpos+1], param2=node.param2}) advtrains.ndb.swap_node(pos, {name=nnprefix.."_"..suffix..modext[modpos+1], param2=node.param2})
end end
end end
end)
end, end,
on_use=function(itemstack, user, pointed_thing) on_use=function(itemstack, user, pointed_thing)
return advtrains.pcall(function()
local name = user:get_player_name() local name = user:get_player_name()
if not name then if not name then
return return
@ -430,7 +425,6 @@ minetest.register_craftitem("advtrains:trackworker",{
else else
atprint(name, dump(tp.tracks)) atprint(name, dump(tp.tracks))
end end
end)
end, end,
}) })

View file

@ -469,7 +469,7 @@ function advtrains.register_tracks(tracktype, def, preset)
tiles = {def.shared_texture or (def.texture_prefix.."_"..img_suffix..".png"), def.second_texture}, tiles = {def.shared_texture or (def.texture_prefix.."_"..img_suffix..".png"), def.second_texture},
groups = { groups = {
attached_node=1, attached_node = advtrains.IGNORE_WORLD and 0 or 1,
advtrains_track=1, advtrains_track=1,
["advtrains_track_"..tracktype]=1, ["advtrains_track_"..tracktype]=1,
save_in_at_nodedb=1, save_in_at_nodedb=1,

View file

@ -43,7 +43,7 @@ function advtrains.on_control_change(pc, train, flip)
else else
local act=false local act=false
if pc.jump then if pc.jump then
train.ctrl.user = 1 train.ctrl_user = 1
act=true act=true
end end
-- If atc command set, only "Jump" key can clear command. To prevent accidental control. -- If atc command set, only "Jump" key can clear command. To prevent accidental control.
@ -51,15 +51,15 @@ function advtrains.on_control_change(pc, train, flip)
return return
end end
if pc.up then if pc.up then
train.ctrl.user=4 train.ctrl_user=4
act=true act=true
end end
if pc.down then if pc.down then
if train.velocity>0 then if train.velocity>0 then
if pc.jump then if pc.jump then
train.ctrl.user = 0 train.ctrl_user = 0
else else
train.ctrl.user = 2 train.ctrl_user = 2
end end
act=true act=true
else else
@ -82,7 +82,7 @@ function advtrains.on_control_change(pc, train, flip)
end end
end end
if not act then if not act then
train.ctrl.user = nil train.ctrl_user = nil
end end
end end
end end
@ -186,6 +186,9 @@ function advtrains.hud_train_format(train, flip)
local tlev=train.lever or 1 local tlev=train.lever or 1
if train.velocity==0 and not train.active_control then tlev=1 end if train.velocity==0 and not train.active_control then tlev=1 end
if train.hud_lzb_effect_tmr then
tlev=1
end
local ht = {"[combine:440x110:0,0=(advtrains_hud_bg.png^[resize\\:440x110)"} local ht = {"[combine:440x110:0,0=(advtrains_hud_bg.png^[resize\\:440x110)"}
local st = {} local st = {}
@ -245,7 +248,7 @@ function advtrains.hud_train_format(train, flip)
if train.tarvelocity or train.atc_command then if train.tarvelocity or train.atc_command then
ht[#ht+1] = "10,10=(advtrains_hud_atc.png^[resize\\:30x30^[multiply\\:cyan)" ht[#ht+1] = "10,10=(advtrains_hud_atc.png^[resize\\:30x30^[multiply\\:cyan)"
end end
if train.ctrl.lzb then if train.hud_lzb_effect_tmr then
ht[#ht+1] = "50,10=(advtrains_hud_lzb.png^[resize\\:30x30^[multiply\\:red)" ht[#ht+1] = "50,10=(advtrains_hud_lzb.png^[resize\\:30x30^[multiply\\:red)"
end end
if train.is_shunt then if train.is_shunt then
@ -274,10 +277,10 @@ function advtrains.hud_train_format(train, flip)
ht[#ht+1] = sformat("%d,85=(advtrains_hud_arrow.png^[multiply\\:cyan^[transformFY^[makealpha\\:#000000)", 1+train.tarvelocity*11) ht[#ht+1] = sformat("%d,85=(advtrains_hud_arrow.png^[multiply\\:cyan^[transformFY^[makealpha\\:#000000)", 1+train.tarvelocity*11)
end end
local lzb = train.lzb local lzb = train.lzb
if lzb and lzb.oncoming then if lzb and lzb.checkpoints then
local oc = lzb.oncoming local oc = lzb.checkpoints
for i = 1, #oc do for i = 1, #oc do
local spd = oc[i].spd local spd = oc[i].speed
local c = not spd and "lime" or (type(spd) == "number" and (spd == 0) and "red" or "orange") or nil local c = not spd and "lime" or (type(spd) == "number" and (spd == 0) and "red" or "orange") or nil
if c then if c then
ht[#ht+1] = sformat("130,10=(advtrains_hud_bg.png^[resize\\:30x5^[colorize\\:%s)",c) ht[#ht+1] = sformat("130,10=(advtrains_hud_bg.png^[resize\\:30x5^[colorize\\:%s)",c)
@ -286,7 +289,7 @@ function advtrains.hud_train_format(train, flip)
ht[#ht+1] = sformat("%d,50=(advtrains_hud_arrow.png^[multiply\\:red^[makealpha\\:#000000)", 1+spd*11) ht[#ht+1] = sformat("%d,50=(advtrains_hud_arrow.png^[multiply\\:red^[makealpha\\:#000000)", 1+spd*11)
end end
local floor = math.floor local floor = math.floor
local dist = floor(((oc[i].idx or train.index)-train.index)) local dist = floor(((oc[i].index or train.index)-train.index))
dist = math.max(0, math.min(999, dist)) dist = math.max(0, math.min(999, dist))
for j = 1, 3, 1 do for j = 1, 3, 1 do
sevenseg(floor((dist/10^(3-j))%10), 119+j*11, 18, 4, 2, "[colorize\\:"..c) sevenseg(floor((dist/10^(3-j))%10), 119+j*11, 18, 4, 2, "[colorize\\:"..c)

View file

@ -36,6 +36,7 @@ end
local t_accel_all={ local t_accel_all={
[0] = -10, [0] = -10,
[1] = -3, [1] = -3,
[11] = -2, -- calculation base for LZB
[2] = -0.5, [2] = -0.5,
[4] = 0.5, [4] = 0.5,
} }
@ -43,19 +44,35 @@ local t_accel_all={
local t_accel_eng={ local t_accel_eng={
[0] = 0, [0] = 0,
[1] = 0, [1] = 0,
[11] = 0,
[2] = 0, [2] = 0,
[4] = 1.5, [4] = 1.5,
} }
local VLEVER_EMERG = 0
local VLEVER_BRAKE = 1
local VLEVER_LZBCALC = 11
local VLEVER_ROLL = 2
local VLEVER_HOLD = 3
local VLEVER_ACCEL = 4
-- How far in front of a whole index with LZB 0 restriction the train should come to a halt
-- value must be between 0 and 0.5, exclusively
local LZB_ZERO_APPROACH_DIST = 0.1
-- Speed the train temporarily approaches the stop point with
local LZB_ZERO_APPROACH_SPEED = 0.2
tp_player_tmr = 0 tp_player_tmr = 0
advtrains.mainloop_trainlogic=function(dtime) advtrains.mainloop_trainlogic=function(dtime, stepno)
--build a table of all players indexed by pts. used by damage and door system. --build a table of all players indexed by pts. used by damage and door system.
advtrains.playersbypts={} advtrains.playersbypts={}
for _, player in pairs(minetest.get_connected_players()) do for _, player in pairs(minetest.get_connected_players()) do
if not advtrains.player_to_train_mapping[player:get_player_name()] then if not advtrains.player_to_train_mapping[player:get_player_name()] then
--players in train are not subject to damage --players in train are not subject to damage
local ptspos=minetest.pos_to_string(vector.round(player:getpos())) local ptspos=minetest.pos_to_string(vector.round(player:get_pos()))
advtrains.playersbypts[ptspos]=player advtrains.playersbypts[ptspos]=player
end end
end end
@ -81,6 +98,7 @@ advtrains.mainloop_trainlogic=function(dtime)
for k,v in pairs(advtrains.trains) do for k,v in pairs(advtrains.trains) do
advtrains.atprint_context_tid=k advtrains.atprint_context_tid=k
--atprint("=== Step",stepno,"===")
advtrains.train_ensure_init(k, v) advtrains.train_ensure_init(k, v)
end end
@ -113,11 +131,10 @@ function advtrains.tp_player_to_train(player)
--set the player to the train position. --set the player to the train position.
--minetest will emerge the area and load the objects, which then will call reattach_all(). --minetest will emerge the area and load the objects, which then will call reattach_all().
--because player is in mapping, it will not be subject to dying. --because player is in mapping, it will not be subject to dying.
player:setpos(train.last_pos) player:set_pos(train.last_pos)
end end
end end
minetest.register_on_joinplayer(function(player) minetest.register_on_joinplayer(function(player)
return advtrains.pcall(function()
advtrains.hud[player:get_player_name()] = nil advtrains.hud[player:get_player_name()] = nil
advtrains.hhud[player:get_player_name()] = nil advtrains.hhud[player:get_player_name()] = nil
--independent of this, cause all wagons of the train which are loaded to reattach their players --independent of this, cause all wagons of the train which are loaded to reattach their players
@ -128,11 +145,9 @@ minetest.register_on_joinplayer(function(player)
end end
end end
end) end)
end)
minetest.register_on_dieplayer(function(player) minetest.register_on_dieplayer(function(player)
return advtrains.pcall(function()
local pname=player:get_player_name() local pname=player:get_player_name()
local id=advtrains.player_to_train_mapping[pname] local id=advtrains.player_to_train_mapping[pname]
if id then if id then
@ -147,7 +162,6 @@ minetest.register_on_dieplayer(function(player)
end end
end end
end) end)
end)
--[[ --[[
@ -190,6 +204,8 @@ end
function advtrains.get_acceleration(train, lever) function advtrains.get_acceleration(train, lever)
local acc_all = t_accel_all[lever] local acc_all = t_accel_all[lever]
if not acc_all then return 0 end
local acc_eng = t_accel_eng[lever] local acc_eng = t_accel_eng[lever]
local nwagons = #train.trainparts local nwagons = #train.trainparts
if nwagons == 0 then if nwagons == 0 then
@ -215,14 +231,16 @@ local function mkcallback(name)
assertt(func, "function") assertt(func, "function")
table.insert(callt, func) table.insert(callt, func)
end end
return callt, function(id, train) return callt, function(id, train, param1, param2, param3)
for _,f in ipairs(callt) do for _,f in ipairs(callt) do
f(id, train) f(id, train, param1, param2, param3)
end end
end end
end end
local callbacks_new_path, run_callbacks_new_path = mkcallback("new_path") local callbacks_new_path, run_callbacks_new_path = mkcallback("new_path")
local callbacks_invahead
callbacks_invahead, advtrains.run_callbacks_invahead = mkcallback("invalidate_ahead") -- (id, train, start_idx)
local callbacks_update, run_callbacks_update = mkcallback("update") local callbacks_update, run_callbacks_update = mkcallback("update")
local callbacks_create, run_callbacks_create = mkcallback("create") local callbacks_create, run_callbacks_create = mkcallback("create")
local callbacks_remove, run_callbacks_remove = mkcallback("remove") local callbacks_remove, run_callbacks_remove = mkcallback("remove")
@ -240,21 +258,25 @@ function advtrains.train_ensure_init(id, train)
end end
train.dirty = true train.dirty = true
if train.no_step then return nil end if train.no_step then
--atprint("in ensure_init: no_step set, train step ignored!")
return nil
end
assertdef(train, "velocity", 0) assertdef(train, "velocity", 0)
--assertdef(train, "tarvelocity", 0) --assertdef(train, "tarvelocity", 0)
assertdef(train, "acceleration", 0) assertdef(train, "acceleration", 0)
assertdef(train, "id", id) assertdef(train, "id", id)
assertdef(train, "ctrl", {})
if not train.drives_on or not train.max_speed then if not train.drives_on or not train.max_speed then
--atprint("in ensure_init: missing properties, updating!")
advtrains.update_trainpart_properties(id) advtrains.update_trainpart_properties(id)
end end
--restore path --restore path
if not train.path then if not train.path then
--atprint("in ensure_init: Needs restoring path...")
if not train.last_pos then if not train.last_pos then
atlog("Train",id,": Restoring path failed, no last_pos set! Train will be disabled. You can try to fix the issue in the save file.") atlog("Train",id,": Restoring path failed, no last_pos set! Train will be disabled. You can try to fix the issue in the save file.")
train.no_step = true train.no_step = true
@ -277,6 +299,8 @@ function advtrains.train_ensure_init(id, train)
local result = advtrains.path_create(train, train.last_pos, train.last_connid or 1, train.last_frac or 0) local result = advtrains.path_create(train, train.last_pos, train.last_connid or 1, train.last_frac or 0)
--atprint("in ensure_init: path_create result ",result)
if result==false then if result==false then
atlog("Train",id,": Restoring path failed, node at",train.last_pos,"is gone! Train will be disabled. You can try to place a rail at this position and restart the server.") atlog("Train",id,": Restoring path failed, node at",train.last_pos,"is gone! Train will be disabled. You can try to place a rail at this position and restart the server.")
train.no_step = true train.no_step = true
@ -304,6 +328,10 @@ function advtrains.train_ensure_init(id, train)
return true return true
end end
local function v_target_apply(v_targets, lever, vel)
v_targets[lever] = v_targets[lever] and math.min(v_targets[lever], vel) or vel
end
function advtrains.train_step_b(id, train, dtime) function advtrains.train_step_b(id, train, dtime)
if train.no_step or train.wait_for_path or not train.path then return end if train.no_step or train.wait_for_path or not train.path then return end
@ -311,25 +339,49 @@ function advtrains.train_step_b(id, train, dtime)
advtrains.path_get(train, atfloor(train.index + 2)) advtrains.path_get(train, atfloor(train.index + 2))
advtrains.path_get(train, atfloor(train.end_index - 1)) advtrains.path_get(train, atfloor(train.end_index - 1))
-- run pre-move hooks
-- TODO: if more pre-move hooks are added, make a separate callback hook
advtrains.lzb_look_ahead(id, train)
--[[ again, new velocity control:
There are two heterogenous means of control:
-> set a fixed acceleration and ignore speed (user)
-> steer towards a target speed, distance doesn't matter
-> needs to specify the maximum acceleration/deceleration values they are willing to accelerate/brake with
-> Reach a target speed after a certain distance (LZB, handled specially)
]]--
--- 3. handle velocity influences --- --- 3. handle velocity influences ---
local train_moves=(train.velocity~=0)
local tarvel_cap = train.speed_restriction local v0 = train.velocity
local sit_v_cap = train.max_speed -- Maximum speed in current situation (multiple limit factors)
-- The desired speed change issued by the active control (user or atc)
local ctrl_v_tar -- desired speed which should not be crossed by braking or accelerating
local ctrl_accelerating = false -- whether the train should accelerate
local ctrl_braking = false -- whether the train should brake
local ctrl_lever -- the lever value to use to calculate the acceleration
-- the final speed change after applying LZB
local v_cap -- absolute maximum speed
local v_tar -- desired speed which should not be crossed by braking or accelerating
local accelerating = false-- whether the train should accelerate
local braking = false -- whether the train should brake
local lever -- the lever value to use to calculate the acceleration
local train_moves = (v0 > 0)
if train.recently_collided_with_env then if train.recently_collided_with_env then
tarvel_cap=0
if not train_moves then if not train_moves then
train.recently_collided_with_env=nil--reset status when stopped train.recently_collided_with_env=nil--reset status when stopped
end end
end --atprint("in train_step_b: applying collided_with_env")
if train.locomotives_in_train==0 then sit_v_cap = 0
tarvel_cap=0 elseif train.locomotives_in_train==0 then
end --atprint("in train_step_b: applying no_locomotives")
sit_v_cap = 0
--- 3a. this can be useful for debugs/warnings and is used for check_trainpartload --- -- interlocking speed restriction
local t_info, train_pos=sid(id), advtrains.path_get(train, atfloor(train.index)) elseif train.speed_restriction then
if train_pos then --atprint("in train_step_b: applying interlocking speed restriction",train.speed_restriction)
t_info=t_info.." @"..minetest.pos_to_string(train_pos) sit_v_cap = train.speed_restriction
--atprint("train_pos:",train_pos)
end end
--apply off-track handling: --apply off-track handling:
@ -337,38 +389,47 @@ function advtrains.train_step_b(id, train, dtime)
local back_off_track=train.end_index<train.path_trk_b local back_off_track=train.end_index<train.path_trk_b
train.off_track = front_off_track or back_off_track train.off_track = front_off_track or back_off_track
if front_off_track then if back_off_track and (not v_cap or v_cap > 1) then
tarvel_cap=0 --atprint("in train_step_b: applying back_off_track")
end sit_v_cap = 1
if back_off_track then -- eventually overrides front_off_track restriction elseif front_off_track then
tarvel_cap=1 --atprint("in train_step_b: applying front_off_track")
sit_v_cap = 0
end end
-- Driving control rework:
--[[
Items are only defined when something is controlling them.
In order of precedence.
train.ctrl = {
lzb = restrictive override from LZB
user = User input from driverstand
atc = ATC command override (determined here)
}
The code here determines the precedence and writes the final control into train.lever
]]
--interpret ATC command and apply auto-lever control when not actively controlled --interpret ATC command and apply auto-lever control when not actively controlled
local trainvelocity = train.velocity local userc = train.ctrl_user
if userc then
if train.ctrl.user then --atprint("in train_step_b: ctrl_user active",userc)
advtrains.atc.train_reset_command(train) advtrains.atc.train_reset_command(train)
if userc >= VLEVER_ACCEL then
ctrl_accelerating = true
else else
ctrl_braking = true
end
ctrl_lever = userc
else
if train.atc_command then
if (not train.atc_delay or train.atc_delay<=0) and not train.atc_wait_finish then
advtrains.atc.execute_atc_command(id, train)
else
train.atc_delay=train.atc_delay-dtime
end
elseif train.atc_delay then
train.atc_delay = nil
end
local braketar = train.atc_brake_target local braketar = train.atc_brake_target
local emerg = false -- atc_brake_target==-1 means emergency brake (BB command) local emerg = false -- atc_brake_target==-1 means emergency brake (BB command)
if braketar == -1 then if braketar == -1 then
braketar = 0 braketar = 0
emerg = true emerg = true
end end
if braketar and braketar>=trainvelocity then --atprint("in train_step_b: ATC: brake state braketar=",braketar,"emerg=",emerg)
if braketar and braketar>=v0 then
--atprint("in train_step_b: ATC: brake target cleared")
train.atc_brake_target=nil train.atc_brake_target=nil
braketar = nil braketar = nil
end end
@ -380,111 +441,175 @@ function advtrains.train_step_b(id, train, dtime)
train.atc_wait_finish=nil train.atc_wait_finish=nil
end end
end end
if train.atc_command then
if (not train.atc_delay or train.atc_delay<=0) and not train.atc_wait_finish then
advtrains.atc.execute_atc_command(id, train)
else
train.atc_delay=train.atc_delay-dtime
end
elseif train.atc_delay then
train.atc_delay = nil
end
train.ctrl.atc = nil if train.tarvelocity and train.tarvelocity>v0 then
if train.tarvelocity and train.tarvelocity>trainvelocity then --atprint("in train_step_b: applying ATC ACCEL", train.tarvelocity)
train.ctrl.atc=4 ctrl_accelerating = true
end ctrl_lever = VLEVER_ACCEL
if train.tarvelocity and train.tarvelocity<trainvelocity then elseif train.tarvelocity and train.tarvelocity<v0 then
if (braketar and braketar<trainvelocity) then ctrl_braking = true
if (braketar and braketar<v0) then
if emerg then if emerg then
train.ctrl.atc = 0 --atprint("in train_step_b: applying ATC EMERG", train.tarvelocity)
ctrl_lever = VLEVER_EMERG
else else
train.ctrl.atc=1 --atprint("in train_step_b: applying ATC BRAKE", train.tarvelocity)
ctrl_v_tar = braketar
ctrl_lever = VLEVER_BRAKE
end end
else else
train.ctrl.atc=2 --atprint("in train_step_b: applying ATC ROLL", train.tarvelocity)
ctrl_v_tar = train.tarvelocity
ctrl_lever = VLEVER_ROLL
end end
end end
end end
--if tarvel_cap and train.tarvelocity and tarvel_cap<train.tarvelocity then --- 2b. look at v_target, determine the effective v_target and desired acceleration ---
-- train.tarvelocity=tarvel_cap --atprint("in train_step_b: Resulting control before LZB: accelerating",ctrl_accelerating,"braking",ctrl_braking,"lever", ctrl_lever, "target", ctrl_v_tar)
--end --train.debug = dump({tv_target,tv_lever})
local tmp_lever --atprint("in train_step_b: Current index",train.index,"end",train.end_index,"vel",v0)
--- 3a. calculate the acceleration required to reach the speed restriction in path_speed (LZB) ---
-- Iterates over the path nodes we WOULD pass if we were continuing with the current speed
-- and determines the MINIMUM of path_speed in this range.
-- Then, determines acceleration so that we can reach this 'overridden' target speed in this step (but short-circuited)
local lzb_next_zero_barrier -- if defined, train should not pass this point as it's a 0-LZB
local new_index_curr_tv -- pre-calculated new train index in lzb check
local lzb_v_cap -- the maximum speed that LZB dictates
for _, lev in pairs(train.ctrl) do local dst_curr_v = v0 * dtime
-- use the most restrictive of all control overrides new_index_curr_tv = advtrains.path_get_index_by_offset(train, train.index, dst_curr_v)
tmp_lever = math.min(tmp_lever or 4, lev) local i = atfloor(train.index)
end local psp
while true do
if not tmp_lever then psp = train.path_speed[i]
-- if there was no control at all, default to 3 if psp then
tmp_lever = 3 lzb_v_cap = lzb_v_cap and math.min(lzb_v_cap, psp) or psp
end if psp == 0 and not lzb_next_zero_barrier then
--atprint("in train_step_b: Found zero barrier: ",i)
if tarvel_cap and trainvelocity>tarvel_cap then lzb_next_zero_barrier = i - LZB_ZERO_APPROACH_DIST
tmp_lever = 0
end
train.lever = tmp_lever
--- 3a. actually calculate new velocity ---
if tmp_lever~=3 then
local accel = advtrains.get_acceleration(train, tmp_lever)
local vdiff = accel*dtime
-- This should only be executed when we are accelerating
-- I suspect that this causes the braking bugs
if tmp_lever == 4 then
-- ATC control exception: don't cross tarvelocity if
-- atc provided a target_vel
if train.tarvelocity then
local tvdiff = train.tarvelocity - trainvelocity
if tvdiff~=0 and math.abs(vdiff) > math.abs(tvdiff) then
--applying this change would cross tarvelocity
--atdebug("In Tvdiff condition, clipping",vdiff,"to",tvdiff)
--atdebug("vel=",trainvelocity,"tvel=",train.tarvelocity)
vdiff=tvdiff
end end
end end
if tarvel_cap and trainvelocity<=tarvel_cap and trainvelocity+vdiff>tarvel_cap then if i > new_index_curr_tv then
vdiff = tarvel_cap - train.velocity break
end
local mspeed = (train.max_speed or 10)
if trainvelocity+vdiff > mspeed then
vdiff = mspeed - trainvelocity
end end
i = i + 1
end end
if trainvelocity+vdiff < 0 then if lzb_next_zero_barrier and train.index < lzb_next_zero_barrier then
vdiff = - trainvelocity lzb_v_cap = LZB_ZERO_APPROACH_SPEED
end end
--atprint("in train_step_b: LZB calculation yields newindex=",new_index_curr_tv,"lzbtarget=",lzb_v_cap,"zero_barr=",lzb_next_zero_barrier,"")
train.acceleration=vdiff -- LZB HUD: decrement timer and delete when 0
train.velocity=train.velocity+vdiff if train.hud_lzb_effect_tmr then
--if train.ctrl.user then if train.hud_lzb_effect_tmr <=0 then
-- train.tarvelocity = train.velocity train.hud_lzb_effect_tmr = nil
--end
else else
train.hud_lzb_effect_tmr = train.hud_lzb_effect_tmr - 1
end
end
-- We now need to bring ctrl_*, sit_v_cap and lzb_v_cap together to determine the final controls.
local v_cap = sit_v_cap -- always defined, by default train.max_speed
if lzb_v_cap and lzb_v_cap < v_cap then
v_cap = lzb_v_cap
lever = VLEVER_BRAKE -- actually irrelevant, acceleration is not considered anyway unless v_tar is also set.
-- display LZB control override in the HUD
if lzb_v_cap <= v0 then
train.hud_lzb_effect_tmr = 1
-- This is to signal the HUD that LZB is active. This works as a timer to avoid HUD blinking
end
end
v_tar = ctrl_v_tar
-- if v_cap is smaller than the current speed, we need to brake in all cases.
if v_cap < v0 then
braking = true
lever = VLEVER_BRAKE
-- set v_tar to v_cap to not slow down any further than required.
-- unless control wants us to brake too, then we use control's v_tar.
if not ctrl_v_tar or ctrl_v_tar > v_cap then
v_tar = v_cap
end
else -- else, use what the ctrl says
braking = ctrl_braking
accelerating = ctrl_accelerating and not braking
lever = ctrl_lever
end
train.lever = lever
--atprint("in train_step_b: final control: accelerating",accelerating,"braking",braking,"lever", lever, "target", v_tar)
-- reset train acceleration when holding speed
if not braking and not accelerating then
train.acceleration = 0 train.acceleration = 0
end end
--- 3b. if braking, modify the velocity BEFORE the movement
if braking then
local dv = advtrains.get_acceleration(train, lever) * dtime
local v1 = v0 + dv
if v_tar and v1 < v_tar then
--atprint("in train_step_b: Braking: Hit v_tar!")
v1 = v_tar
end
if v1 > v_cap then
--atprint("in train_step_b: Braking: Hit v_cap!")
v1 = v_cap
end
if v1 < 0 then
--atprint("in train_step_b: Braking: Hit 0!")
v1 = 0
end
train.acceleration = (v1 - v0) / dtime
train.velocity = v1
--atprint("in train_step_b: Braking: New velocity",v1," (yields acceleration",train.acceleration,")")
-- make saved new_index_curr_tv invalid because speed has changed
new_index_curr_tv = nil
end
--- 4. move train --- --- 4. move train ---
-- if we have calculated the new end index before, don't do that again
if not new_index_curr_tv then
local dst_curr_v = train.velocity * dtime
new_index_curr_tv = advtrains.path_get_index_by_offset(train, train.index, dst_curr_v)
--atprint("in train_step_b: movement calculation (re)done, yields newindex=",new_index_curr_tv)
else
--atprint("in train_step_b: movement calculation reusing from LZB newindex=",new_index_curr_tv)
end
local idx_floor = math.floor(train.index) -- if the zeroappr mechanism has hit, go no further than zeroappr index
local pdist = (train.path_dist[idx_floor+1] - train.path_dist[idx_floor]) if lzb_next_zero_barrier and new_index_curr_tv > lzb_next_zero_barrier then
local distance = (train.velocity*dtime) / pdist --atprint("in train_step_b: Zero barrier hit, clipping to newidx_tv=",new_index_curr_tv, "zb_idx=",lzb_next_zero_barrier)
new_index_curr_tv = lzb_next_zero_barrier
--debugging code end
--train.debug = atdump(train.ctrl).."step_dist: "..math.floor(distance*1000) train.index = new_index_curr_tv
train.index=train.index+distance
recalc_end_index(train) recalc_end_index(train)
--atprint("in train_step_b: New index",train.index,"end",train.end_index,"vel",train.velocity)
--- 4a. if accelerating, modify the velocity AFTER the movement
if accelerating then
local dv = advtrains.get_acceleration(train, lever) * dtime
local v1 = v0 + dv
if v_tar and v1 > v_tar then
--atprint("in train_step_b: Accelerating: Hit v_tar!")
v1 = v_tar
end
if v1 > v_cap then
--atprint("in train_step_b: Accelerating: Hit v_cap!")
v1 = v_cap
end
train.acceleration = (v1 - v0) / dtime
train.velocity = v1
--atprint("in train_step_b: Accelerating: New velocity",v1," (yields acceleration",train.acceleration,")")
end
end end
function advtrains.train_step_c(id, train, dtime) function advtrains.train_step_c(id, train, dtime)
@ -527,7 +652,7 @@ function advtrains.train_step_c(id, train, dtime)
local collpos = advtrains.path_get(train, atround(collindex)) local collpos = advtrains.path_get(train, atround(collindex))
if collpos then if collpos then
local rcollpos=advtrains.round_vector_floor_y(collpos) local rcollpos=advtrains.round_vector_floor_y(collpos)
local is_loaded_area = minetest.get_node_or_nil(rcollpos) ~= nil local is_loaded_area = advtrains.is_node_loaded(rcollpos)
for x=-train.extent_h,train.extent_h do for x=-train.extent_h,train.extent_h do
for z=-train.extent_h,train.extent_h do for z=-train.extent_h,train.extent_h do
local testpos=vector.add(rcollpos, {x=x, y=0, z=z}) local testpos=vector.add(rcollpos, {x=x, y=0, z=z})
@ -609,8 +734,9 @@ local callbacks_enter_node, run_callbacks_enter_node = mknodecallback("enter")
local callbacks_leave_node, run_callbacks_leave_node = mknodecallback("leave") local callbacks_leave_node, run_callbacks_leave_node = mknodecallback("leave")
-- Node callback for approaching -- Node callback for approaching
-- Might be called multiple times, whenever path is recalculated -- Might be called multiple times, whenever path is recalculated. Also called for the first node the train is standing on, then has_entered is true.
-- signature is function(pos, id, train, index, lzbdata) -- signature is function(pos, id, train, index, has_entered, lzbdata)
-- has_entered: true if the "enter" callback has already been executed for this train in this location
-- lzbdata: arbitrary data (shared between all callbacks), deleted when LZB is restarted. -- lzbdata: arbitrary data (shared between all callbacks), deleted when LZB is restarted.
-- These callbacks are called in order of distance as train progresses along tracks, so lzbdata can be used to -- These callbacks are called in order of distance as train progresses along tracks, so lzbdata can be used to
-- keep track of a train's state once it passes this point -- keep track of a train's state once it passes this point
@ -666,16 +792,19 @@ end
function advtrains.tnc_call_approach_callback(pos, train_id, train, index, lzbdata) function advtrains.tnc_call_approach_callback(pos, train_id, train, index, lzbdata)
--atdebug("tnc approach",pos,train_id, lzbdata) --atdebug("tnc approach",pos,train_id, lzbdata)
local has_entered = atround(train.index) == index
local node = advtrains.ndb.get_node(pos) --this spares the check if node is nil, it has a name in any case local node = advtrains.ndb.get_node(pos) --this spares the check if node is nil, it has a name in any case
local mregnode=minetest.registered_nodes[node.name] local mregnode=minetest.registered_nodes[node.name]
if mregnode and mregnode.advtrains and mregnode.advtrains.on_train_approach then if mregnode and mregnode.advtrains and mregnode.advtrains.on_train_approach then
mregnode.advtrains.on_train_approach(pos, train_id, train, index, lzbdata) mregnode.advtrains.on_train_approach(pos, train_id, train, index, has_entered, lzbdata)
end end
-- call other registered callbacks -- call other registered callbacks
run_callbacks_approach_node(pos, train_id, train, index, lzbdata) run_callbacks_approach_node(pos, train_id, train, index, has_entered, lzbdata)
end end
-- === te callback definition for tnc node callbacks ===
advtrains.te_register_on_new_path(function(id, train) advtrains.te_register_on_new_path(function(id, train)
train.tnc = { train.tnc = {
@ -865,19 +994,12 @@ function advtrains.spawn_wagons(train_id)
atwarn("Train",train_id,"Wagon #",i,": Saved train ID",data.train_id,"did not match!") atwarn("Train",train_id,"Wagon #",i,": Saved train ID",data.train_id,"did not match!")
data.train_id = train_id data.train_id = train_id
end end
if not advtrains.wagon_objects[w_id] or not advtrains.wagon_objects[w_id]:getyaw() then if not advtrains.wagon_objects[w_id] or not advtrains.wagon_objects[w_id]:get_yaw() then
-- eventually need to spawn new object. check if position is loaded. -- eventually need to spawn new object. check if position is loaded.
local index = advtrains.path_get_index_by_offset(train, train.index, -data.pos_in_train) local index = advtrains.path_get_index_by_offset(train, train.index, -data.pos_in_train)
local pos = advtrains.path_get(train, atfloor(index)) local pos = advtrains.path_get(train, atfloor(index))
local spawn = false if advtrains.position_in_range(pos, ablkrng) then
for _,p in pairs(minetest.get_connected_players()) do
if vector.distance(p:get_pos(),pos)<=ablkrng then
spawn = true
end
end
if spawn then
--atdebug("wagon",w_id,"spawning") --atdebug("wagon",w_id,"spawning")
local wt = advtrains.get_wagon_prototype(data) local wt = advtrains.get_wagon_prototype(data)
local wagon = minetest.add_entity(pos, wt):get_luaentity() local wagon = minetest.add_entity(pos, wt):get_luaentity()
@ -1024,7 +1146,7 @@ end
function advtrains.train_check_couples(train) function advtrains.train_check_couples(train)
--atdebug("rechecking couples") --atdebug("rechecking couples")
if train.cpl_front then if train.cpl_front then
if not train.cpl_front:getyaw() then if not train.cpl_front:get_yaw() then
-- objectref is no longer valid. reset. -- objectref is no longer valid. reset.
train.cpl_front = nil train.cpl_front = nil
end end
@ -1032,7 +1154,7 @@ function advtrains.train_check_couples(train)
if not train.cpl_front then if not train.cpl_front then
-- recheck front couple -- recheck front couple
local front_trains, pos = advtrains.occ.get_occupations(train, atround(train.index) + CPL_CHK_DST) local front_trains, pos = advtrains.occ.get_occupations(train, atround(train.index) + CPL_CHK_DST)
if minetest.get_node_or_nil(pos) then -- if the position is loaded... if advtrains.is_node_loaded(pos) then -- if the position is loaded...
for tid, idx in pairs(front_trains) do for tid, idx in pairs(front_trains) do
local other_train = advtrains.trains[tid] local other_train = advtrains.trains[tid]
if not advtrains.train_ensure_init(tid, other_train) then if not advtrains.train_ensure_init(tid, other_train) then
@ -1054,7 +1176,7 @@ function advtrains.train_check_couples(train)
end end
end end
if train.cpl_back then if train.cpl_back then
if not train.cpl_back:getyaw() then if not train.cpl_back:get_yaw() then
-- objectref is no longer valid. reset. -- objectref is no longer valid. reset.
train.cpl_back = nil train.cpl_back = nil
end end
@ -1062,7 +1184,7 @@ function advtrains.train_check_couples(train)
if not train.cpl_back then if not train.cpl_back then
-- recheck back couple -- recheck back couple
local back_trains, pos = advtrains.occ.get_occupations(train, atround(train.end_index) - CPL_CHK_DST) local back_trains, pos = advtrains.occ.get_occupations(train, atround(train.end_index) - CPL_CHK_DST)
if minetest.get_node_or_nil(pos) then -- if the position is loaded... if advtrains.is_node_loaded(pos) then -- if the position is loaded...
for tid, idx in pairs(back_trains) do for tid, idx in pairs(back_trains) do
local other_train = advtrains.trains[tid] local other_train = advtrains.trains[tid]
if not advtrains.train_ensure_init(tid, other_train) then if not advtrains.train_ensure_init(tid, other_train) then
@ -1202,6 +1324,18 @@ function advtrains.invalidate_all_paths(pos)
advtrains.invalidate_path(id) advtrains.invalidate_path(id)
end end
end end
-- Calls invalidate_path_ahead on all trains occupying (having paths over) this node
-- Can be called during train step.
function advtrains.invalidate_all_paths_ahead(pos)
local tab = advtrains.occ.get_trains_over(pos)
for id,index in pairs(tab) do
local train = advtrains.trains[id]
advtrains.path_invalidate_ahead(train, index, true)
end
end
function advtrains.invalidate_path(id) function advtrains.invalidate_path(id)
--atdebug("Path invalidate:",id) --atdebug("Path invalidate:",id)
local v=advtrains.trains[id] local v=advtrains.trains[id]

View file

@ -10,24 +10,15 @@
-- TP delay when getting off wagon -- TP delay when getting off wagon
local GETOFF_TP_DELAY = 0.5 local GETOFF_TP_DELAY = 0.5
local IGNORE_WORLD = advtrains.IGNORE_WORLD
advtrains.wagons = {} advtrains.wagons = {}
advtrains.wagon_prototypes = {} advtrains.wagon_prototypes = {}
advtrains.wagon_objects = {} advtrains.wagon_objects = {}
local unload_wgn_range = advtrains.wagon_load_range + 32 local unload_wgn_range = advtrains.wagon_load_range + 32
function advtrains.outside_range(pos) -- returns true if the object is outside of unload_wgn_range of any player function advtrains.wagon_outside_range(pos) -- returns true if the object is outside of unload_wgn_range of any player
-- this is part of a workaround until mintest core devs decide to fix a bug with static_save=false. return not advtrains.position_in_range(pos, unload_wgn_range)
local outofrange = true
if not pos then
return true
end
for _,p in pairs(minetest.get_connected_players()) do
if vector.distance(p:get_pos(),pos)<=unload_wgn_range then
outofrange = false
break
end
end
return outofrange
end end
local setting_show_ids = minetest.settings:get_bool("advtrains_show_ids") local setting_show_ids = minetest.settings:get_bool("advtrains_show_ids")
@ -165,7 +156,7 @@ function wagon:ensure_init()
atwarn("wagon",self.id,"uninitialized, removing") atwarn("wagon",self.id,"uninitialized, removing")
self:destroy() self:destroy()
else else
self.object:setvelocity({x=0,y=0,z=0}) self.object:set_velocity({x=0,y=0,z=0})
end end
return false return false
end end
@ -177,7 +168,6 @@ end
-- Remove the wagon -- Remove the wagon
function wagon:on_punch(puncher, time_from_last_punch, tool_capabilities, direction) function wagon:on_punch(puncher, time_from_last_punch, tool_capabilities, direction)
return advtrains.pcall(function()
if not self:ensure_init() then return end if not self:ensure_init() then return end
local data = advtrains.wagons[self.id] local data = advtrains.wagons[self.id]
@ -234,7 +224,6 @@ function wagon:on_punch(puncher, time_from_last_punch, tool_capabilities, direct
for _,item in ipairs(self.drops or {self.name}) do for _,item in ipairs(self.drops or {self.name}) do
inv:add_item("main", item) inv:add_item("main", item)
end end
end)
end end
function wagon:destroy() function wagon:destroy()
--some rules: --some rules:
@ -279,11 +268,15 @@ function wagon:is_driver_stand(seat)
end end
function wagon:on_step(dtime) function wagon:on_step(dtime)
return advtrains.pcall(function()
if not self:ensure_init() then return end if not self:ensure_init() then return end
if advtrains.is_no_action() then
self.object:remove()
return
end
local t=os.clock() local t=os.clock()
local pos = self.object:getpos() local pos = self.object:get_pos()
local data = advtrains.wagons[self.id] local data = advtrains.wagons[self.id]
if not pos then if not pos then
@ -300,6 +293,8 @@ function wagon:on_step(dtime)
local train=self:train() local train=self:train()
local is_in_loaded_area = advtrains.is_node_loaded(pos)
--custom on_step function --custom on_step function
if self.custom_on_step then if self.custom_on_step then
self:custom_on_step(dtime, data, train) self:custom_on_step(dtime, data, train)
@ -409,8 +404,8 @@ function wagon:on_step(dtime)
--for path to be available. if not, skip step --for path to be available. if not, skip step
if not train.path or train.no_step then if not train.path or train.no_step then
self.object:setvelocity({x=0, y=0, z=0}) self.object:set_velocity({x=0, y=0, z=0})
self.object:setacceleration({x=0, y=0, z=0}) self.object:set_acceleration({x=0, y=0, z=0})
return return
end end
if not data.pos_in_train then if not data.pos_in_train then
@ -453,7 +448,7 @@ function wagon:on_step(dtime)
end end
--checking for environment collisions(a 3x3 cube around the center) --checking for environment collisions(a 3x3 cube around the center)
if not train.recently_collided_with_env then if not IGNORE_WORLD and is_in_loaded_area and not train.recently_collided_with_env then
local collides=false local collides=false
local exh = self.extent_h or 1 local exh = self.extent_h or 1
local exv = self.extent_v or 2 local exv = self.extent_v or 2
@ -477,9 +472,9 @@ function wagon:on_step(dtime)
--DisCouple --DisCouple
-- FIX: Need to do this after the yaw calculation -- FIX: Need to do this after the yaw calculation
if data.pos_in_trainparts and data.pos_in_trainparts>1 then if is_in_loaded_area and data.pos_in_trainparts and data.pos_in_trainparts>1 then
if train.velocity==0 then if train.velocity==0 then
if not self.discouple or not self.discouple.object:getyaw() then if not self.discouple or not self.discouple.object:get_yaw() then
atprint(self.id,"trying to spawn discouple") atprint(self.id,"trying to spawn discouple")
local dcpl_pos = vector.add(pos, {y=0, x=-math.sin(yaw)*self.wagon_span, z=math.cos(yaw)*self.wagon_span}) local dcpl_pos = vector.add(pos, {y=0, x=-math.sin(yaw)*self.wagon_span, z=math.cos(yaw)*self.wagon_span})
local object=minetest.add_entity(dcpl_pos, "advtrains:discouple") local object=minetest.add_entity(dcpl_pos, "advtrains:discouple")
@ -492,7 +487,7 @@ function wagon:on_step(dtime)
end end
end end
else else
if self.discouple and self.discouple.object:getyaw() then if self.discouple and self.discouple.object:get_yaw() then
self.discouple.object:remove() self.discouple.object:remove()
atprint(self.id," removing discouple") atprint(self.id," removing discouple")
end end
@ -500,8 +495,8 @@ function wagon:on_step(dtime)
end end
--FIX: use index of the wagon, not of the train. --FIX: use index of the wagon, not of the train.
local velocity = train.velocity local velocity = train.velocity * advtrains.global_slowdown
local acceleration = (train.acceleration or 0) local acceleration = (train.acceleration or 0) * (advtrains.global_slowdown*advtrains.global_slowdown)
local velocityvec = vector.multiply(vdir, velocity) local velocityvec = vector.multiply(vdir, velocity)
local accelerationvec = vector.multiply(vdir, acceleration) local accelerationvec = vector.multiply(vdir, acceleration)
@ -530,7 +525,7 @@ function wagon:on_step(dtime)
end end
end end
if not players_in then if not players_in then
if advtrains.outside_range(pos) then if advtrains.wagon_outside_range(pos) then
--atdebug("wagon",self.id,"unloading (too far away)") --atdebug("wagon",self.id,"unloading (too far away)")
-- Workaround until minetest engine deletes attached sounds -- Workaround until minetest engine deletes attached sounds
if self.sound_loop_handle then if self.sound_loop_handle then
@ -548,9 +543,9 @@ function wagon:on_step(dtime)
or self.old_yaw~=yaw or self.old_yaw~=yaw
or updatepct_timer_elapsed then--only send update packet if something changed or updatepct_timer_elapsed then--only send update packet if something changed
self.object:setpos(pos) self.object:set_pos(pos)
self.object:setvelocity(velocityvec) self.object:set_velocity(velocityvec)
self.object:setacceleration(accelerationvec) self.object:set_acceleration(accelerationvec)
if #self.seats > 0 and self.old_yaw ~= yaw then if #self.seats > 0 and self.old_yaw ~= yaw then
if not self.player_yaw then if not self.player_yaw then
@ -583,7 +578,7 @@ function wagon:on_step(dtime)
end end
self.object:set_rotation({x=pitch, y=yaw, z=0}) self.object:set_rotation({x=pitch, y=yaw, z=0})
else else
self.object:setyaw(yaw) self.object:set_yaw(yaw)
end end
if self.update_animation then if self.update_animation then
@ -604,11 +599,9 @@ function wagon:on_step(dtime)
self.old_acceleration_vector=accelerationvec self.old_acceleration_vector=accelerationvec
self.old_yaw=yaw self.old_yaw=yaw
atprintbm("wagon step", t) atprintbm("wagon step", t)
end)
end end
function wagon:on_rightclick(clicker) function wagon:on_rightclick(clicker)
return advtrains.pcall(function()
if not self:ensure_init() then return end if not self:ensure_init() then return end
if not clicker or not clicker:is_player() then if not clicker or not clicker:is_player() then
return return
@ -696,7 +689,6 @@ function wagon:on_rightclick(clicker)
self:show_get_on_form(pname) self:show_get_on_form(pname)
end end
end end
end)
end end
function wagon:get_on(clicker, seatno) function wagon:get_on(clicker, seatno)
@ -779,7 +771,7 @@ function wagon:get_off(seatno)
--atdebug("platpos:", platpos, "offpos:", offpos) --atdebug("platpos:", platpos, "offpos:", offpos)
if minetest.get_item_group(minetest.get_node(platpos).name, "platform")>0 then if minetest.get_item_group(minetest.get_node(platpos).name, "platform")>0 then
minetest.after(GETOFF_TP_DELAY, function() clicker:setpos(offpos) end) minetest.after(GETOFF_TP_DELAY, function() clicker:set_pos(offpos) end)
--atdebug("tp",offpos) --atdebug("tp",offpos)
return return
end end
@ -799,7 +791,7 @@ function wagon:get_off(seatno)
offp=vector.add({x=isx and r*2 or 0, y=1, z=not isx and r*2 or 0}, objpos) offp=vector.add({x=isx and r*2 or 0, y=1, z=not isx and r*2 or 0}, objpos)
--atdebug("platpos:", p, "offpos:", offp) --atdebug("platpos:", p, "offpos:", offp)
if minetest.get_item_group(minetest.get_node(p).name, "platform")>0 then if minetest.get_item_group(minetest.get_node(p).name, "platform")>0 then
minetest.after(GETOFF_TP_DELAY, function() clicker:setpos(offp) end) minetest.after(GETOFF_TP_DELAY, function() clicker:set_pos(offp) end)
--atdebug("tp",offp) --atdebug("tp",offp)
return return
end end
@ -996,10 +988,10 @@ function wagon:show_bordcom(pname)
-- Interlocking functionality: If the interlocking module is loaded, you can set the signal aspect -- Interlocking functionality: If the interlocking module is loaded, you can set the signal aspect
-- from inside the train -- from inside the train
if advtrains.interlocking and train.lzb and #train.lzb.oncoming > 0 then if advtrains.interlocking and train.lzb and #train.lzb.checkpoints > 0 then
local i=1 local i=1
while train.lzb.oncoming[i] do while train.lzb.checkpoints[i] do
local oci = train.lzb.oncoming[i] local oci = train.lzb.checkpoints[i]
if oci.udata and oci.udata.signal_pos then if oci.udata and oci.udata.signal_pos then
if advtrains.interlocking.db.get_sigd_for_signal(oci.udata.signal_pos) then if advtrains.interlocking.db.get_sigd_for_signal(oci.udata.signal_pos) then
form = form .. "button[4.5,8;5,1;ilrs;Remote Routesetting]" form = form .. "button[4.5,8;5,1;ilrs;Remote Routesetting]"
@ -1008,6 +1000,9 @@ function wagon:show_bordcom(pname)
end end
i=i+1 i=i+1
end end
if train.ars_disable then
form = form .. "button[4.5,7;5,1;ilarsenable;Clear 'Disable ARS' flag]"
end
end end
minetest.show_formspec(pname, "advtrains_bordcom_"..self.id, form) minetest.show_formspec(pname, "advtrains_bordcom_"..self.id, form)
@ -1080,10 +1075,11 @@ function wagon:handle_bordcom_fields(pname, formname, fields)
-- Interlocking functionality: If the interlocking module is loaded, you can set the signal aspect -- Interlocking functionality: If the interlocking module is loaded, you can set the signal aspect
-- from inside the train -- from inside the train
if fields.ilrs and advtrains.interlocking and train.lzb and #train.lzb.oncoming > 0 then if advtrains.interlocking then
if fields.ilrs and train.lzb and #train.lzb.checkpoints > 0 then
local i=1 local i=1
while train.lzb.oncoming[i] do while train.lzb.checkpoints[i] do
local oci = train.lzb.oncoming[i] local oci = train.lzb.checkpoints[i]
if oci.udata and oci.udata.signal_pos then if oci.udata and oci.udata.signal_pos then
local sigd = advtrains.interlocking.db.get_sigd_for_signal(oci.udata.signal_pos) local sigd = advtrains.interlocking.db.get_sigd_for_signal(oci.udata.signal_pos)
if sigd then if sigd then
@ -1094,6 +1090,10 @@ function wagon:handle_bordcom_fields(pname, formname, fields)
i=i+1 i=i+1
end end
end end
if fields.ilarsenable then
advtrains.interlocking.ars_set_disable(train, false)
end
end
if not fields.quit then if not fields.quit then
@ -1102,7 +1102,6 @@ function wagon:handle_bordcom_fields(pname, formname, fields)
end end
minetest.register_on_player_receive_fields(function(player, formname, fields) minetest.register_on_player_receive_fields(function(player, formname, fields)
return advtrains.pcall(function()
local uid=string.match(formname, "^advtrains_geton_(.+)$") local uid=string.match(formname, "^advtrains_geton_(.+)$")
if uid then if uid then
for _,wagon in pairs(minetest.luaentities) do for _,wagon in pairs(minetest.luaentities) do
@ -1187,7 +1186,6 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
end end
end end
end) end)
end)
function wagon:seating_from_key_helper(pname, fields, no) function wagon:seating_from_key_helper(pname, fields, no)
local data = advtrains.wagons[self.id] local data = advtrains.wagons[self.id]
local sgr=self.seats[no].group local sgr=self.seats[no].group
@ -1390,7 +1388,6 @@ function advtrains.register_wagon(sysname_p, prototype, desc, inv_img, nincreati
groups = { not_in_creative_inventory = nincreative and 1 or 0}, groups = { not_in_creative_inventory = nincreative and 1 or 0},
on_place = function(itemstack, placer, pointed_thing) on_place = function(itemstack, placer, pointed_thing)
return advtrains.pcall(function()
if not pointed_thing.type == "node" then if not pointed_thing.type == "node" then
return return
end end
@ -1428,8 +1425,6 @@ function advtrains.register_wagon(sysname_p, prototype, desc, inv_img, nincreati
itemstack:take_item() itemstack:take_item()
end end
return itemstack return itemstack
end)
end, end,
}) })
end end

View file

@ -19,38 +19,50 @@ local function get_over_function(speed, shunt)
if speed == 0 and minetest.settings:get_bool("at_il_force_lzb_halt") then if speed == 0 and minetest.settings:get_bool("at_il_force_lzb_halt") then
atwarn(id,"overrun LZB 0 restriction (red signal) ",pos) atwarn(id,"overrun LZB 0 restriction (red signal) ",pos)
-- Set train 1 index backward. Hope this does not lead to bugs... -- Set train 1 index backward. Hope this does not lead to bugs...
train.index = index - 0.5 --train.index = index - 0.5
train.velocity = 0 train.speed_restriction = 0
train.ctrl.lzb = 0
minetest.after(0, advtrains.invalidate_path, id) --TODO temporary
--advtrains.drb_dump(id)
--error("Debug: "..id.." triggered LZB-0")
else else
train.speed_restriction = speed train.speed_restriction = speed
train.is_shunt = shunt train.is_shunt = shunt
end end
--atdebug("train drove over IP: speed=",speed,"shunt=",shunt)
end end
end end
advtrains.tnc_register_on_approach(function(pos, id, train, index, lzbdata) advtrains.tnc_register_on_approach(function(pos, id, train, index, has_entered, lzbdata)
--atdebug(id,"IL ApprC",pos,index,lzbdata) --atdebug(id,"IL ApprC",pos,index,lzbdata)
--train.debug = advtrains.print_concat_table({train.is_shunt,"|",index,"|",lzbdata}) --train.debug = advtrains.print_concat_table({train.is_shunt,"|",index,"|",lzbdata})
local pts = advtrains.roundfloorpts(pos) local pts = advtrains.roundfloorpts(pos)
local cn = train.path_cn[index] local cn = train.path_cn[index]
local travsht = lzbdata.travsht local travsht = lzbdata.il_shunt
local travspd = lzbdata.il_speed
if travsht==nil then if travsht==nil then
travsht = train.is_shunt -- lzbdata has reset
travspd = train.speed_restriction
travsht = train.is_shunt or false
end end
local travspd = lzbdata.travspd
local travwspd = lzbdata.travwspd
-- check for signal -- check for signal
local asp, spos = il.db.get_ip_signal_asp(pts, cn) local asp, spos = il.db.get_ip_signal_asp(pts, cn)
-- do ARS if needed -- do ARS if needed
if spos then local ars_enabled = not train.ars_disable
-- Note on ars_disable:
-- Theoretically, the ars_disable flag would need to behave like the speed restriction field: it should be
-- stored in lzbdata and updated once the train drives over. However, for the sake of simplicity, it is simply
-- a value in the train. In this case, this is sufficient because once a train triggers ARS for the first time,
-- resetting the path does not matter to the set route and ARS doesn't need to be called again.
if spos and ars_enabled then
--atdebug(id,"IL Spos (ARS)",spos,asp) --atdebug(id,"IL Spos (ARS)",spos,asp)
local sigd = il.db.get_sigd_for_signal(spos) local sigd = il.db.get_sigd_for_signal(spos)
if sigd then if sigd then
@ -60,22 +72,22 @@ advtrains.tnc_register_on_approach(function(pos, id, train, index, lzbdata)
--atdebug("trav: ",pos, cn, asp, spos, "travsht=", lzb.travsht) --atdebug("trav: ",pos, cn, asp, spos, "travsht=", lzb.travsht)
local lspd local lspd
if asp then if asp then
--atdebug(id,"IL Signal",spos,asp) --atdebug(id,"IL Signal",spos, asp, lzbdata, "trainstate", train.speed_restriction, train.is_shunt)
local nspd = 0 local nspd = 0
--interpreting aspect and determining speed to proceed --interpreting aspect and determining speed to proceed
if travsht then if travsht then
--shunt move --shunt move
if asp.shunt.free then if asp.shunt then
nspd = SHUNT_SPEED_MAX nspd = SHUNT_SPEED_MAX
elseif asp.shunt.proceed_as_main and asp.main.free then elseif asp.proceed_as_main and asp.main ~= 0 then
nspd = asp.main.speed nspd = asp.main
travsht = false travsht = false
end end
else else
--train move --train move
if asp.main.free then if asp.main ~= 0 then
nspd = asp.main.speed nspd = asp.main
elseif asp.shunt.free then elseif asp.shunt then
nspd = SHUNT_SPEED_MAX nspd = SHUNT_SPEED_MAX
travsht = true travsht = true
end end
@ -89,25 +101,26 @@ advtrains.tnc_register_on_approach(function(pos, id, train, index, lzbdata)
end end
end end
local nwspd = asp.info.w_speed --atdebug("ns,ts", nspd, travspd)
if nwspd then
if nwspd == -1 then
travwspd = nil
else
travwspd = nwspd
end
end
--atdebug("ns,wns,ts,wts", nspd, nwspd, travspd, travwspd)
lspd = travspd lspd = travspd
if travwspd and (not lspd or lspd>travwspd) then
lspd = travwspd
end
local udata = {signal_pos = spos} local udata = {signal_pos = spos}
local callback = get_over_function(lspd, travsht) local callback = get_over_function(lspd, travsht)
advtrains.lzb_add_checkpoint(train, index, lspd, callback, udata) lzbdata.il_shunt = travsht
lzbdata.il_speed = travspd
--atdebug("new lzbdata",lzbdata)
advtrains.lzb_add_checkpoint(train, index, lspd, callback, lzbdata, udata)
end end
lzbdata.travsht = travsht
lzbdata.travspd = travspd
lzbdata.travwspd = travwspd
end) end)
-- Set the ars_disable flag to the value passed
-- Triggers a path invalidation if set to false
function advtrains.interlocking.ars_set_disable(train, value)
if value then
train.ars_disable = true
else
train.ars_disable = nil
minetest.after(0, advtrains.path_invalidate, train)
end
end

View file

@ -131,6 +131,37 @@ function ildb.load(data)
if data.npr_rails then if data.npr_rails then
advtrains.interlocking.npr_rails = data.npr_rails advtrains.interlocking.npr_rails = data.npr_rails
end end
--COMPATIBILITY to Signal aspect format
-- TODO remove in time...
for pts,tcb in pairs(track_circuit_breaks) do
for connid, tcbs in ipairs(tcb) do
if tcbs.routes then
for _,route in ipairs(tcbs.routes) do
if route.aspect then
-- transform the signal aspect format
local asp = route.aspect
if type(asp.main) == "table" then
atwarn("Transforming route aspect of signal",pts,"/",connid,"")
if asp.main.free then
asp.main = asp.main.speed
else
asp.main = 0
end
if asp.dst.free then
asp.dst = asp.dst.speed
else
asp.dst = 0
end
asp.proceed_as_main = asp.shunt.proceed_as_main
asp.shunt = asp.shunt.free
-- Note: info table not transferred, it's not used right now
end
end
end
end
end
end
end end
function ildb.save() function ildb.save()
@ -149,6 +180,7 @@ end
--[[ --[[
TCB data structure TCB data structure
{ {
-- This is the "A" side of the TCB
[1] = { -- Variant: with adjacent TCs. [1] = { -- Variant: with adjacent TCs.
ts_id = <id> -- ID of the assigned track section ts_id = <id> -- ID of the assigned track section
signal = <pos> -- optional: when set, routes can be set from this tcb/direction and signal signal = <pos> -- optional: when set, routes can be set from this tcb/direction and signal
@ -164,6 +196,7 @@ TCB data structure
routes = { <route definition> } -- a collection of routes from this signal routes = { <route definition> } -- a collection of routes from this signal
route_auto = <boolean> -- When set, we will automatically re-set the route (designated by routeset) route_auto = <boolean> -- When set, we will automatically re-set the route (designated by routeset)
}, },
-- This is the "B" side of the TCB
[2] = { -- Variant: end of track-circuited area (initial state of TC) [2] = { -- Variant: end of track-circuited area (initial state of TC)
ts_id = nil, -- this is the indication for end_of_interlocking ts_id = nil, -- this is the indication for end_of_interlocking
section_free = <boolean>, --this can be set by an exit node via mesecons or atlatc, section_free = <boolean>, --this can be set by an exit node via mesecons or atlatc,

View file

@ -6,10 +6,10 @@
local setaspect = function(pos, node, asp) local setaspect = function(pos, node, asp)
if not asp.main.free then if asp.main == 0 then
advtrains.ndb.swap_node(pos, {name="advtrains_interlocking:ds_danger"}) advtrains.ndb.swap_node(pos, {name="advtrains_interlocking:ds_danger"})
else else
if asp.dst.free and asp.main.speed == -1 then if asp.dst ~= 0 and asp.main == -1 then
advtrains.ndb.swap_node(pos, {name="advtrains_interlocking:ds_free"}) advtrains.ndb.swap_node(pos, {name="advtrains_interlocking:ds_free"})
else else
advtrains.ndb.swap_node(pos, {name="advtrains_interlocking:ds_slow"}) advtrains.ndb.swap_node(pos, {name="advtrains_interlocking:ds_slow"})
@ -22,18 +22,10 @@ local setaspect = function(pos, node, asp)
end end
local suppasp = { local suppasp = {
main = { main = {0, 6, -1},
free = nil, dst = {0, false},
speed = {6, -1}, shunt = false,
},
dst = {
free = nil,
speed = nil,
},
shunt = {
free = false,
proceed_as_main = true, proceed_as_main = true,
},
info = { info = {
call_on = false, call_on = false,
dead_end = false, dead_end = false,
@ -74,10 +66,7 @@ minetest.register_node("advtrains_interlocking:ds_free", {
supported_aspects = suppasp, supported_aspects = suppasp,
get_aspect = function(pos, node) get_aspect = function(pos, node)
return { return {
main = { main = -1,
free = true,
speed = -1,
}
} }
end, end,
}, },
@ -98,10 +87,7 @@ minetest.register_node("advtrains_interlocking:ds_slow", {
supported_aspects = suppasp, supported_aspects = suppasp,
get_aspect = function(pos, node) get_aspect = function(pos, node)
return { return {
main = { main = 6,
free = true,
speed = 6,
}
} }
end, end,
}, },

View file

@ -112,7 +112,8 @@ route = {
next = <sigd>, -- of the next (note: next) TCB on the route next = <sigd>, -- of the next (note: next) TCB on the route
locks = {<pts> = "state"} -- route locks of this route segment locks = {<pts> = "state"} -- route locks of this route segment
} }
terminal = terminal = <sigd>,
aspect = <signal aspect>,--note, might change in future
} }
The first item in the TCB path (namely i=0) is always the start signal of this route, The first item in the TCB path (namely i=0) is always the start signal of this route,
so this is left out. so this is left out.

View file

@ -129,7 +129,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
advtrains.interlocking.show_route_edit_form(pname, sigd, routeid) advtrains.interlocking.show_route_edit_form(pname, sigd, routeid)
end end
advtrains.interlocking.show_signal_aspect_selector(pname, suppasp, route.name, callback, route.aspect) advtrains.interlocking.show_signal_aspect_selector(pname, suppasp, route.name, callback, route.aspect or advtrains.interlocking.GENERIC_FREE)
return return
end end
if fields.delete then if fields.delete then

View file

@ -6,21 +6,6 @@ local function sigd_to_string(sigd)
return minetest.pos_to_string(sigd.p).." / "..lntrans[sigd.s] return minetest.pos_to_string(sigd.p).." / "..lntrans[sigd.s]
end end
local asp_generic_free = {
main = {
free = true,
speed = -1,
},
shunt = {
free = false,
},
dst = {
free = true,
speed = -1,
},
info = {}
}
local ildb = advtrains.interlocking.db local ildb = advtrains.interlocking.db
local ilrs = {} local ilrs = {}
@ -127,7 +112,7 @@ function ilrs.set_route(signal, route, try)
} }
if c_tcbs.signal then if c_tcbs.signal then
c_tcbs.route_committed = true c_tcbs.route_committed = true
c_tcbs.aspect = route.aspect or asp_generic_free c_tcbs.aspect = route.aspect or advtrains.interlocking.GENERIC_FREE
c_tcbs.route_origin = signal c_tcbs.route_origin = signal
advtrains.interlocking.update_signal_aspect(c_tcbs) advtrains.interlocking.update_signal_aspect(c_tcbs)
end end

View file

@ -3,29 +3,39 @@
--[[ --[[
Signal aspect table: Signal aspect table:
Note: All speeds are measured in m/s, aka the number of + signs in the HUD.
asp = { asp = {
main = { main = <int speed>,
free = <boolean>, -- Main signal aspect, tells state and permitted speed of next section
speed = <int km/h>, -- 0 = section is blocked
}, -- >0 = section is free, speed limit is this value
shunt = { -- -1 = section is free, maximum speed permitted
free = <boolean>, -- false/nil = Signal doesn't provide main signal information, retain current speed limit.
shunt = <boolean>,
-- Whether train may proceed as shunt move, on sight -- Whether train may proceed as shunt move, on sight
-- main aspect takes precedence over this -- main aspect takes precedence over this
-- When main==0, train switches to shunt move and is restricted to speed 6
proceed_as_main = <boolean>, proceed_as_main = <boolean>,
-- If an approaching train is a shunt move and "main.free" is set, -- If an approaching train is a shunt move and 'shunt' is false,
-- the train may proceed as a train move under the "main" aspect -- the train may proceed as a train move under the "main" aspect
-- if the main aspect permits it (i.e. main!=0)
-- If this is not set, shunt moves are NOT allowed to switch to -- If this is not set, shunt moves are NOT allowed to switch to
-- a train move, and must stop even if "main.free" is set. -- a train move, and must stop even if "main" would permit passing.
-- This is intended to be used for "Halt for shunt moves" signs. -- This is intended to be used for "Halt for shunt moves" signs.
}
dst = { dst = <int speed>,
free = <boolean>, -- Distant signal aspect, tells state and permitted speed of the section after next section
speed = <int km/h>, -- The character of these information is purely informational
} -- At this time, this field is not actively used
info = { -- 0 = section is blocked
-- >0 = section is free, speed limit is this value
-- -1 = section is free, maximum speed permitted
-- false/nil = Signal doesn't provide distant signal information.
-- the character of call_on and dead_end is purely informative
call_on = <boolean>, -- Call-on route, expect train in track ahead (not implemented yet) call_on = <boolean>, -- Call-on route, expect train in track ahead (not implemented yet)
dead_end = <boolean>, -- Route ends on a dead end (e.g. bumper) (not implemented yet) dead_end = <boolean>, -- Route ends on a dead end (e.g. bumper) (not implemented yet)
w_speed = <integer>, w_speed = <integer>,
-- "Warning speed restriction". Supposed for short-term speed -- "Warning speed restriction". Supposed for short-term speed
-- restrictions which always override any other restrictions -- restrictions which always override any other restrictions
@ -33,12 +43,6 @@ asp = {
-- (Example: german Langsamfahrstellen-Signale) -- (Example: german Langsamfahrstellen-Signale)
} }
} }
-- For "speed" and "w_speed" fields, a value of -1 means that the
-- restriction is lifted. If they are omitted, the value imposed at
-- the last aspect received remains valid.
-- The "dst" subtable can be completely omitted when no explicit dst
-- aspect should be signalled to the train. In this case, the last
-- signalled dst aspect remains valid.
== How signals actually work in here == == How signals actually work in here ==
Each signal (in the advtrains universe) is some node that has at least the Each signal (in the advtrains universe) is some node that has at least the
@ -60,10 +64,16 @@ advtrains = {
-- This function gets called whenever the signal should display -- This function gets called whenever the signal should display
-- a new or changed signal aspect. It is not required that -- a new or changed signal aspect. It is not required that
-- the signal actually displays the exact same aspect, since -- the signal actually displays the exact same aspect, since
-- some signals can not do this by design. -- some signals can not do this by design. However, it must
-- Example: pure shunt signals can not display a "main" aspect -- display an aspect that is at least as restrictive as the passed
-- aspect as far as it is capable of doing so.
-- Examples:
-- - pure shunt signals can not display a "main" aspect
-- and have no effect on train moves, so they will only ever -- and have no effect on train moves, so they will only ever
-- honor the shunt.free field for their aspect. -- honor the shunt.free field for their aspect.
-- - the german Hl system can only signal speeds of 40, 60
-- and 100 km/h, a speed of 80km/h should then be signalled
-- as 60 km/h instead.
-- In turn, it is not guaranteed that the aspect will fulfill the -- In turn, it is not guaranteed that the aspect will fulfill the
-- criteria put down in supported_aspects. -- criteria put down in supported_aspects.
-- If set_aspect is present, supported_aspects should also be declared. -- If set_aspect is present, supported_aspects should also be declared.
@ -87,51 +97,52 @@ advtrains = {
false: always shows "blocked", unchangable false: always shows "blocked", unchangable
true: always shows "free", unchangable true: always shows "free", unchangable
-- Any of the "speed" fields should contain a list of possible values -- Any of the "speed" fields should contain a list of possible values
-- to be set as restriction. If omitted, this signal should never -- to be set as restriction. If omitted, the value of the described
-- set the corresponding "speed" field in the aspect, which means -- field is always assumed to be false (no information)
-- that the previous speed limit stays valid -- A speed of 0 means that the signal can show a "blocked" aspect
-- (which is probably the case for most signals)
-- If the signal can signal "no information" on one of the fields
-- (thus false is an acceptable value), include false in the list
-- If your signal can only display a single speed (may it be -1), -- If your signal can only display a single speed (may it be -1),
-- always enclose that single value into a list. (such as {-1}) -- always enclose that single value into a list. (such as {-1})
main = { main = {<speed1>, ..., <speedn>} or nil,
free = <boolean/nil>, dst = {<speed1>, ..., <speedn>} or nil,
speed = {<speed1>, ..., <speedn>} or nil, shunt = <boolean/nil>,
},
dst = {
free = <boolean/nil>,
speed = {<speed1>, ..., <speedn>} or nil,
},
shunt = {
free = <boolean/nil>,
},
info = {
call_on = <boolean/nil>, call_on = <boolean/nil>,
dead_end = <boolean/nil>, dead_end = <boolean/nil>,
w_speed = {<speed1>, ..., <speedn>} or nil, w_speed = {<speed1>, ..., <speedn>} or nil,
}
}, },
Example for supported_aspects:
supported_aspects = {
main = {0, 6, -1}, -- can show either "Section blocked", "Proceed at speed 6" or "Proceed at maximum speed"
dst = {0, false}, -- can show only if next signal shows "blocked", no other information.
shunt = false, -- shunting by this signal is never allowed.
call_on = false,
dead_end = false,
w_speed = nil,
-- none of the information can be shown by the signal
},
get_aspect = function(pos, node) get_aspect = function(pos, node)
-- This function gets called by the train safety system. It -- This function gets called by the train safety system. It
should return the aspect that this signal actually displays, should return the aspect that this signal actually displays,
not preferably the input of set_aspect. not preferably the input of set_aspect.
-- For regular, full-featured light signals, they will probably -- For regular, full-featured light signals, they will probably
honor all entries in the original aspect, however, e.g. honor all entries in the original aspect, however, e.g.
simple shunt signals always return main.free=true regardless of simple shunt signals always return main=false regardless of
the set_aspect input because they can not signal "Halt" to the set_aspect input because they can not signal "Halt" to
train moves. train moves.
-- advtrains.interlocking.DANGER contains a default "all-danger" aspect. -- advtrains.interlocking.DANGER contains a default "all-danger" aspect.
-- If your signal does not cover certain sub-tables of the aspect, -- If your signal does not cover certain sub-tables of the aspect,
the following reasonable defaults are automatically assumed: the following reasonable defaults are automatically assumed:
main = { main = false (unchanged)
free = true, dst = false (unchanged)
} shunt = false (shunting not allowed)
dst = { info = {} (no further information)
free = true,
}
shunt = {
free = false,
proceed_as_main = false,
}
end, end,
} }
on_rightclick = advtrains.interlocking.signal_rc_handler on_rightclick = advtrains.interlocking.signal_rc_handler
@ -155,51 +166,37 @@ This function will query get_aspect to retrieve the new aspect.
]]-- ]]--
local DANGER = { local DANGER = {
main = { main = 0,
free = false, dst = false,
speed = 0, shunt = false,
},
shunt = {
free = false,
},
dst = {
free = false,
speed = 0,
},
info = {}
} }
advtrains.interlocking.DANGER = DANGER advtrains.interlocking.DANGER = DANGER
local function fillout_aspect(asp) advtrains.interlocking.GENERIC_FREE = {
if not asp.main then main = -1,
asp.main = { shunt = false,
free = true, dst = false,
}
elseif type(asp.main) ~= "table" then
asp.main = {
free = asp.main~=0,
speed = asp.main,
} }
local function convert_aspect_if_necessary(asp)
if type(asp.main) == "table" then
local newasp = {}
if asp.main.free then
newasp.main = asp.main.speed
else
newasp.main = 0
end end
if not asp.dst then if asp.dst and asp.dst.free then
asp.dst = { newasp.dst = asp.dst.speed
free = true, else
} newasp.dst = 0
end end
if not asp.shunt then newasp.proceed_as_main = asp.shunt.proceed_as_main
asp.shunt = { newasp.shunt = asp.shunt.free
free = false, -- Note: info table not transferred, it's not used right now
proceed_as_main = false, return newasp
}
elseif type(asp.shunt) ~= "table" then
asp.shunt = {
free = asp.shunt,
proceed_as_main = asp.proceed_as_main,
}
end
if not asp.info then
asp.info = {}
end end
return asp
end end
function advtrains.interlocking.update_signal_aspect(tcbs) function advtrains.interlocking.update_signal_aspect(tcbs)
@ -219,7 +216,7 @@ function advtrains.interlocking.signal_after_dig(pos)
end end
function advtrains.interlocking.signal_set_aspect(pos, asp) function advtrains.interlocking.signal_set_aspect(pos, asp)
fillout_aspect(asp) asp = convert_aspect_if_necessary(asp)
local node=advtrains.ndb.get_node(pos) local node=advtrains.ndb.get_node(pos)
local ndef=minetest.registered_nodes[node.name] local ndef=minetest.registered_nodes[node.name]
if ndef and ndef.advtrains and ndef.advtrains.set_aspect then if ndef and ndef.advtrains and ndef.advtrains.set_aspect then
@ -234,17 +231,17 @@ function advtrains.interlocking.signal_on_aspect_changed(pos)
if not ipts then return end if not ipts then return end
local ipos = minetest.string_to_pos(ipts) local ipos = minetest.string_to_pos(ipts)
local tns = advtrains.occ.get_trains_over(ipos) advtrains.invalidate_all_paths_ahead(ipos)
for id, sidx in pairs(tns) do
-- local train = advtrains.trains[id]
--if train.index <= sidx then
minetest.after(0, advtrains.invalidate_path, id)
--end
end
end end
function advtrains.interlocking.signal_rc_handler(pos, node, player, itemstack, pointed_thing) function advtrains.interlocking.signal_rc_handler(pos, node, player, itemstack, pointed_thing)
local pname = player:get_player_name() local pname = player:get_player_name()
local control = player:get_player_control()
if control.aux1 then
advtrains.interlocking.show_ip_form(pos, pname)
return
end
local sigd = advtrains.interlocking.db.get_sigd_for_signal(pos) local sigd = advtrains.interlocking.db.get_sigd_for_signal(pos)
if sigd then if sigd then
advtrains.interlocking.show_signalling_form(sigd, pname) advtrains.interlocking.show_signalling_form(sigd, pname)
@ -252,7 +249,16 @@ function advtrains.interlocking.signal_rc_handler(pos, node, player, itemstack,
local ndef = minetest.registered_nodes[node.name] local ndef = minetest.registered_nodes[node.name]
if ndef.advtrains and ndef.advtrains.set_aspect then if ndef.advtrains and ndef.advtrains.set_aspect then
-- permit to set aspect manually -- permit to set aspect manually
minetest.show_formspec(pname, "at_il_sigasp_"..minetest.pos_to_string(pos), "field[aspect;Set Aspect ('A' to assign IP);D0D0D]") local function callback(pname, aspect)
advtrains.interlocking.signal_set_aspect(pos, aspect)
end
local isasp = ndef.advtrains.get_aspect(pos, node)
advtrains.interlocking.show_signal_aspect_selector(
pname,
ndef.advtrains.supported_aspects,
"Set aspect manually", callback,
isasp)
else else
--static signal - only IP --static signal - only IP
advtrains.interlocking.show_ip_form(pos, pname) advtrains.interlocking.show_ip_form(pos, pname)
@ -260,45 +266,13 @@ function advtrains.interlocking.signal_rc_handler(pos, node, player, itemstack,
end end
end end
minetest.register_on_player_receive_fields(function(player, formname, fields)
local pname = player:get_player_name()
local pts = string.match(formname, "^at_il_sigasp_(.+)$")
local pos
if pts then pos = minetest.string_to_pos(pts) end
if pos and fields.aspect then
if fields.aspect == "A" then
advtrains.interlocking.show_ip_form(pos, pname)
return
end
local mfs, msps, dfs, dsps, shs = string.match(fields.aspect, "^([FD])([-0-9]+)([FD])([-0-9]+)([FD])$")
local asp = {
main = {
free = mfs=="F",
speed = tonumber(msps),
},
shunt = {
free = shs=="F",
},
dst = {
free = dfs=="F",
speed = tonumber(dsps),
},
info = {
call_on = false, -- Call-on route, expect train in track ahead
dead_end = false, -- Route ends on a dead end (e.g. bumper)
}
}
advtrains.interlocking.signal_set_aspect(pos, asp)
end
end)
-- Returns the aspect the signal at pos is supposed to show -- Returns the aspect the signal at pos is supposed to show
function advtrains.interlocking.signal_get_supposed_aspect(pos) function advtrains.interlocking.signal_get_supposed_aspect(pos)
local sigd = advtrains.interlocking.db.get_sigd_for_signal(pos) local sigd = advtrains.interlocking.db.get_sigd_for_signal(pos)
if sigd then if sigd then
local tcbs = advtrains.interlocking.db.get_tcbs(sigd) local tcbs = advtrains.interlocking.db.get_tcbs(sigd)
if tcbs.aspect then if tcbs.aspect then
return tcbs.aspect return convert_aspect_if_necessary(tcbs.aspect)
end end
end end
return DANGER; return DANGER;
@ -312,8 +286,7 @@ function advtrains.interlocking.signal_get_aspect(pos)
if ndef and ndef.advtrains and ndef.advtrains.get_aspect then if ndef and ndef.advtrains and ndef.advtrains.get_aspect then
local asp = ndef.advtrains.get_aspect(pos, node) local asp = ndef.advtrains.get_aspect(pos, node)
if not asp then asp = DANGER end if not asp then asp = DANGER end
fillout_aspect(asp) return convert_aspect_if_necessary(asp)
return asp
end end
return nil return nil
end end
@ -447,42 +420,45 @@ local players_aspsel = {}
suppasp: "supported_aspects" table suppasp: "supported_aspects" table
purpose: form title string purpose: form title string
callback: func(pname, aspect) called on form submit callback: func(pname, aspect) called on form submit
isasp: aspect currently set
]] ]]
function advtrains.interlocking.show_signal_aspect_selector(pname, p_suppasp, p_purpose, callback, p_isasp) function advtrains.interlocking.show_signal_aspect_selector(pname, p_suppasp, p_purpose, callback, isasp)
local suppasp = p_suppasp or { local suppasp = p_suppasp or {
main = {}, dst = {}, shunt = {}, info = {}, main = {0, -1}, dst = {false}, shunt = false, info = {},
} }
local purpose = p_purpose or "" local purpose = p_purpose or ""
local isasp = p_isasp and fillout_aspect(p_isasp)
local form = "size[7,5]label[0.5,0.5;Select Signal Aspect:]" local form = "size[7,5]label[0.5,0.5;Select Signal Aspect:]"
form = form.."label[0.5,1;"..purpose.."]" form = form.."label[0.5,1;"..purpose.."]"
form = form.."label[0.5,1.5;== Main Signal ==]" form = form.."label[0.5,1.5;== Main Signal ==]"
if suppasp.main.free == nil then
local st = 2
if isasp and not isasp.main.free then st=1 end
form = form.."dropdown[0.5,2;2;main_free;danger,free;"..st.."]"
end
if suppasp.main.speed then
local selid = 1 local selid = 1
if isasp and isasp.main.speed then local entries = {}
for idx, spv in ipairs(suppasp.main.speed) do for idx, spv in ipairs(suppasp.main) do
if spv == isasp.main.speed then local entry
if spv == 0 then
entry = "Halt"
elseif spv == -1 then
entry = "Continue at maximum speed"
elseif not spv then
entry = "Continue\\, speed limit unchanged (no info)"
else
entry = "Continue at speed of "..spv
end
-- hack: the crappy formspec system returns the label, not the index. save the index in it.
entries[idx] = idx.."| "..entry
if isasp and spv == (isasp.main or false) then
selid = idx selid = idx
break
end end
end end
end form = form.."dropdown[0.5,2;6;main;"..table.concat(entries, ",")..";"..selid.."]"
form = form.."label[2.3,1;Speed:]"
form = form.."dropdown[3,2;2;main_speed;"..table.concat(suppasp.main.speed, ",")..";"..selid.."]"
end
form = form.."label[0.5,3;== Shunting ==]" form = form.."label[0.5,3;== Shunting ==]"
if suppasp.shunt.free == nil then if suppasp.shunt == nil then
local st = 1 local st = 1
if isasp and isasp.shunt.free then st=2 end if isasp and isasp.shunt then st=2 end
form = form.."dropdown[0.5,3.5;2;shunt_free;---,allowed;"..st.."]" form = form.."dropdown[0.5,3.5;6;shunt_free;---,allowed;"..st.."]"
end end
form = form.."button_exit[0.5,4.5; 5,1;save;OK]" form = form.."button_exit[0.5,4.5; 5,1;save;OK]"
@ -507,12 +483,10 @@ local function usebool(sup, val, free)
return sup return sup
end end
end end
local function usespeed(sup, val)
if sup then -- other side of hack: extract the index
return tonumber(val) local function ddindex(val)
else return tonumber(string.match(val, "^(%d+)|"))
return nil
end
end end
-- TODO use non-hacky way to parse outputs -- TODO use non-hacky way to parse outputs
@ -523,17 +497,12 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
if psl then if psl then
if formname == "at_il_sigaspdia_"..psl.token then if formname == "at_il_sigaspdia_"..psl.token then
if fields.save then if fields.save then
local maini = ddindex(fields.main)
if not maini then return end
local asp = { local asp = {
main = { main = psl.suppasp.main[maini],
free = usebool(psl.suppasp.main.free, fields.main_free, "free"), dst = false,
speed = usespeed(psl.suppasp.main.speed, fields.main_speed), shunt = usebool(psl.suppasp.shunt, fields.shunt_free, "allowed"),
},
dst = {
free = true, speed = -1,
},
shunt = {
free = usebool(psl.suppasp.shunt.free, fields.shunt_free, "allowed"),
},
info = {} info = {}
} }
psl.callback(pname, asp) psl.callback(pname, asp)

View file

@ -118,6 +118,16 @@ minetest.register_node("advtrains_interlocking:tcb_node", {
end, end,
}) })
-- Crafting
minetest.register_craft({
output = 'advtrains_interlocking:tcb_node 4',
recipe = {
{'mesecons:wire_00000000_off', 'basic_materials:ic', 'mesecons:wire_00000000_off'},
},
})
minetest.register_on_punchnode(function(pos, node, player, pointed_thing) minetest.register_on_punchnode(function(pos, node, player, pointed_thing)
local pname = player:get_player_name() local pname = player:get_player_name()
if not minetest.check_player_privs(pname, "interlocking") then if not minetest.check_player_privs(pname, "interlocking") then
@ -558,7 +568,7 @@ local sig_pselidx = {}
-- Players having a signalling form open -- Players having a signalling form open
local p_open_sig_form = {} local p_open_sig_form = {}
function advtrains.interlocking.show_signalling_form(sigd, pname, sel_rte) function advtrains.interlocking.show_signalling_form(sigd, pname, sel_rte, called_from_form_update)
if not minetest.check_player_privs(pname, "train_operator") then if not minetest.check_player_privs(pname, "train_operator") then
minetest.chat_send_player(pname, "Insufficient privileges to use this!") minetest.chat_send_player(pname, "Insufficient privileges to use this!")
return return
@ -651,8 +661,11 @@ function advtrains.interlocking.show_signalling_form(sigd, pname, sel_rte)
p_open_sig_form[pname] = sigd p_open_sig_form[pname] = sigd
-- always a good idea to update the signal aspect -- always a good idea to update the signal aspect
if not called_from_form_update then
-- FIX prevent a callback loop
advtrains.interlocking.update_signal_aspect(tcbs) advtrains.interlocking.update_signal_aspect(tcbs)
end end
end
function advtrains.interlocking.update_player_forms(sigd) function advtrains.interlocking.update_player_forms(sigd)
for pname, tsigd in pairs(p_open_sig_form) do for pname, tsigd in pairs(p_open_sig_form) do
@ -763,7 +776,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
tcbs.route_auto = false tcbs.route_auto = false
end end
advtrains.interlocking.show_signalling_form(sigd, pname, sel_rte) advtrains.interlocking.show_signalling_form(sigd, pname, sel_rte, true)
return return
end end

View file

@ -23,6 +23,16 @@ local adefunc = function(def, preset, suffix, rotation)
advtrains.interlocking.npr_rails[pe] = nil advtrains.interlocking.npr_rails[pe] = nil
end, end,
on_receive_fields = function(pos, formname, fields, player) on_receive_fields = function(pos, formname, fields, player)
local pname = player:get_player_name()
if not minetest.check_player_privs(pname, {interlocking=true}) then
minetest.chat_send_player(pname, "Interlocking privilege required!")
return
end
if minetest.is_protected(pos, pname) then
minetest.chat_send_player(pname, "This rail is protected!")
minetest.record_protection_violation(pos, pname)
return
end
if fields.npr then if fields.npr then
local pe = advtrains.encode_pos(pos) local pe = advtrains.encode_pos(pos)
advtrains.interlocking.npr_rails[pe] = tonumber(fields.npr) advtrains.interlocking.npr_rails[pe] = tonumber(fields.npr)

View file

@ -169,7 +169,8 @@ local adefunc = function(def, preset, suffix, rotation)
show_stoprailform(pos, player) show_stoprailform(pos, player)
end, end,
advtrains = { advtrains = {
on_train_approach = function(pos,train_id, train, index) on_train_approach = function(pos,train_id, train, index, has_entered)
if has_entered then return end -- do not stop again!
if train.path_cn[index] == 1 then if train.path_cn[index] == 1 then
local pe = advtrains.encode_pos(pos) local pe = advtrains.encode_pos(pos)
local stdata = advtrains.lines.stops[pe] local stdata = advtrains.lines.stops[pe]
@ -184,6 +185,7 @@ local adefunc = function(def, preset, suffix, rotation)
local stn = advtrains.lines.stations[stdata.stn] local stn = advtrains.lines.stations[stdata.stn]
local stnname = stn and stn.name or "Unknown Station" local stnname = stn and stn.name or "Unknown Station"
train.text_inside = "Next Stop:\n"..stnname train.text_inside = "Next Stop:\n"..stnname
advtrains.interlocking.ars_set_disable(train, true)
end end
end end
end end
@ -201,7 +203,7 @@ local adefunc = function(def, preset, suffix, rotation)
local stnname = stn and stn.name or "Unknown Station" local stnname = stn and stn.name or "Unknown Station"
-- Send ATC command and set text -- Send ATC command and set text
advtrains.atc.train_set_command(train, "B0 W O"..stdata.doors..(stdata.kick and "K" or "").." D"..stdata.wait.." OC "..(stdata.reverse and "R" or "").."D"..(stdata.ddelay or 1) .. "S" ..(stdata.speed or "M"), true) advtrains.atc.train_set_command(train, "B0 W O"..stdata.doors..(stdata.kick and "K" or "").." D"..stdata.wait.." OC "..(stdata.reverse and "R" or "").."D"..(stdata.ddelay or 1) .. " A1 S" ..(stdata.speed or "M"), true)
train.text_inside = stnname train.text_inside = stnname
if tonumber(stdata.wait) then if tonumber(stdata.wait) then
minetest.after(tonumber(stdata.wait), function() train.text_inside = "" end) minetest.after(tonumber(stdata.wait), function() train.text_inside = "" end)

View file

@ -0,0 +1,440 @@
# Advtrains - Lua Automation features
This mod offers components that run LUA code and interface with each other through a global environment. It makes complex automated railway systems possible. The mod is sometimes abbreviated as 'LuaATC' or 'atlatc'. This stands for AdvTrainsLuaATC. This short name has been chosen for user convenience, since the name of this mod ('advtrains_luaautomation') is very long.
A probably more complete documentation of LuaATC is found on the [Advtrains Wiki](http://advtrains.de/wiki/doku.php?id=usage:atlatc:start)
## Privileges
To perform any operations using this mod (except executing operation panels), players need the "atlatc" privilege.
This privilege should never be granted to anyone except trusted administrators. Even though the LUA environment is sandboxed, it is still possible to DoS the server by coding infinite loops or requesting expotentially growing interrupts.
## Environments
Each active component is assigned to an environment where all atlac data is held. Components in different environments can't inferface with each other.
This system allows multiple independent automation systems to run simultaneously without polluting each other's environment.
- `/env_create <env_name>`:
Create environment with the given name. To be able to do anything, you first need to create an environment. Choose the name wisely, you can't change it afterwards without deleting the environment and starting again.
- `/env_setup <env_name>`:
Invoke the form to edit the environment's initialization code. For more information, see the section on active components. You can also delete an environment from here.
- `/env_subscribe <env_name>`, `/env_unsubscribe <env_name>`:
Subscribe or unsubscribe from log/error messages originating from this environment
- `/env_subscriptions [env_name]`:
List your subscriptions or players subscribed to an environment.
## Functions and variables
### General Functions and Variables
The following standard Lua libraries are available:
- `string`
- `math`
- `table`
- `os`
The following standard Lua functions are available:
- `assert`
- `error`
- `ipairs`
- `pairs`
- `next`
- `select`
- `tonumber`
- `tostring`
- `type`
- `unpack`
Any attempt to overwrite the predefined values results in an error.
### LuaAutomation Global Variables
- `S`
The variable 'S' contains a table which is shared between all components of the environment. Its contents are persistent over server restarts. May not contain functions, every other value is allowed.
- `F`
The variable 'F' also contains a table which is shared between all components of the environment. Its contents are discarded on server shutdown or when the init code gets re-run. Every data type is allowed, even functions.
The purpose of this table is not to save data, but to provide static value and function definitions. The table should be populated by the init code.
### LuaAutomation Global Functions
> Note: in the following functions, all parameters named `pos` designate a position. You can use the following:
> - a default Minetest position vector (eg. {x=34, y=2, z=-18})
> - the POS(34,2,-18) shorthand below.
> - A string, the passive component name. See 'passive component naming'.
- `POS(x,y,z)`
Shorthand function to create a position vector {x=?, y=?, z=?} with less characters.
- `getstate(pos)`
Get the state of the passive component at position `pos`.
- `setstate(pos, newstate)`
Set the state of the passive component at position `pos`.
- `is_passive(pos)`
Checks whether there is a passive component at the position pos (and/or whether a passive component with this name exists)
- `interrupt(time, message)`
Cause LuaAutomation to trigger an `int` event on this component after the given time in seconds with the specified `message` field. `message` can be of any Lua data type. Returns true. *Not available in init code.*
- `interrupt_safe(time, message)`
Like `interrupt()`, but does not add an interrupt and returns false when an interrupt (of any type) is already present for this component. Returns true when interrupt was successfully added.
- `interrupt_pos(pos, message)`
Immediately trigger an `ext_int` event on the active component at position pos. `message` is like in interrupt(). Use with care, or better **_don't use_**! Incorrect use can result in **_expotential growth of interrupts_**.
- `clear_interrupts()`
Removes any pending interrupts of this node.
- `digiline_send(channel, message)`
Make this active component send a digiline message on the specified channel.
Not available in init code.
#### Interlocking Route Management Functions
If `advtrains_interlocking` is enabled, the following aditional functions can be used:
- `can_set_route(pos, route_name)`
Returns whether it is possible to set the route designated by route_name from the signal at pos.
- `set_route(pos, route_name)`
Requests the given route from the signal at pos. Has the same effect as clicking "Set Route" in the signalling dialog.
- `cancel_route(pos)`
Cancels the route that is set from the signal at pos. Has the same effect as clicking "Cancel Route" in the signalling dialog.
- `get_aspect(pos)`
Returns the signal aspect of the signal at pos. A signal aspect has the following format:
```lua
asp = {
main = <int speed>,
-- Main signal aspect, tells state and permitted speed of next section
-- 0 = section is blocked
-- >0 = section is free, speed limit is this value
-- -1 = section is free, maximum speed permitted
-- false = Signal doesn't provide main signal information, retain current speed limit.
shunt = <boolean>,
-- Whether train may proceed as shunt move, on sight
-- main aspect takes precedence over this
-- When main==0, train switches to shunt move and is restricted to speed 8
proceed_as_main = <boolean>,
-- If an approaching train is a shunt move and 'shunt' is false,
-- the train may proceed as a train move under the "main" aspect
-- if the main aspect permits it (i.e. main!=0)
-- If this is not set, shunt moves are NOT allowed to switch to
-- a train move, and must stop even if "main" would permit passing.
-- This is intended to be used for "Halt for shunt moves" signs.
dst = <int speed>,
-- Distant signal aspect, tells state and permitted speed of the section after next section
-- The character of these information is purely informational
-- At this time, this field is not actively used
-- 0 = section is blocked
-- >0 = section is free, speed limit is this value
-- -1 = section is free, maximum speed permitted
-- false = Signal doesn't provide distant signal information.
-- the character of call_on and dead_end is purely informative
call_on = <boolean>, -- Call-on route, expect train in track ahead (not implemented yet)
dead_end = <boolean>, -- Route ends on a dead end (e.g. bumper) (not implemented yet)
w_speed = <integer>,
-- "Warning speed restriction". Supposed for short-term speed
-- restrictions which always override any other restrictions
-- imposed by "speed" fields, until lifted by a value of -1
-- (Example: german Langsamfahrstellen-Signale)
}
```
As of January 2020, the 'dst', 'call_on' and 'dead_end' fields are not used.
#### Lines
The advtrains_line_automation component adds a few contraptions that should make creating timeable systems easier.
Part of its functionality is also available in LuaATC:
- `rwt.*` - all Railway Time functions are included as documented in [the wiki](https://advtrains.de/wiki/doku.php?id=dev:lines:rwt)
- `schedule(rw_time, msg)`, `schedule_in(rw_dtime, msg)`
Schedules an event of type {type="schedule", schedule=true, msg=msg} at (resp. after) the specified railway time (which can be in any format). You can only schedule one event this way. (uses the new lines-internal scheduler)
Note: Using the lines scheduler is preferred over using `interrupt()`, as it's more performant and safer to use.
## Events
The event table is a variable created locally by the component being triggered. It is a table with the following format:
```lua
event = {
type = "<event type>",
<event type> = true,
--additional event-specific content
}
```
You can check the event type by using the following:
```lua
if event.type == "wanted" then
--do stuff
end
```
or
```lua
if event.wanted then
--do stuff
end
```
where `wanted` is the event type to check for.
See the "Active Components" section below for details on the various event types as not all of them are applicable to all components.
## Components
Atlac components introduce automation-capable components that fall within two categories:
- Active Components are components that are able to run Lua code, triggered by specific events.
- Passive Components can't perform actions themselves. Their state can be read and set by active components or manually by the player.
### Lua ATC Rails
Lua ATC rails are the only components that can actually interface with trains. The following event types are available to the Lua ATC rails:
- `{type="train", train=true, id="<train_id>"}`
* This event is fired when a train enters the rail. The field `id` is the unique train ID, which is 6-digit random numerical string.
* If the world contains trains from an older advtrains version, this string may be longer and contain a dot `.`
- `{type="int", int=true, msg=<message>}`
* Fired when an interrupt set by the `interrupt` function runs out. `<message>` is the message passed to the interrupt function.
* For backwards compatiblity reasons, `<message>` is also contained in an `event.message` variable.
- `{type="ext_int", ext_int=true, message=<message>}`
* Fired when another node called `interrupt_pos` on this position. `message` is the message passed to the interrupt_pos function.
- `{type="digiline", digiline=true, channel=<channel>, msg=<message>}`
* Fired when the controller receives a digiline message.
#### Basic Lua Rail Functions and Variables
In addition to the above environment functions, the following functions are available to whilst the train is in contact with the LuaATC rail:
- `atc_send(<atc_command>)`
Sends the specified ATC command to the train (a string) and returns true. If there is no train, returns false and does nothing. See [atc_command.txt](../atc_command.txt) for the ATC command syntax.
- `atc_reset()`
Resets the train's current ATC command. If there is no train, returns false and does nothing.
- `atc_arrow`
Boolean, true when the train is driving in the direction of the arrows of the ATC rail. Nil if there is no train.
- `atc_id`
Train ID of the train currently passing the controller. Nil if there's no train.
- `atc_speed`
Speed of the train, or nil if there is no train.
- `atc_set_text_outside(text)`
Set text shown on the outside of the train. Pass nil to show no text. `text` must be a string.
- `atc_set_text_inside(text)`
Set text shown to train passengers. Pass nil to show no text. `text` must be a string.
- `get_line()`
Returns the "Line" property of the train (a string).
This can be used to distinguish between trains of different lines and route them appropriately.
The interlocking system also uses this property for Automatic Routesetting.
- `set_line(line)`
Sets the "Line" property of the train (a string).
If the first digit of this string is a number (0-9), any subway wagons on the train (from advtrains_train_subway) will have this one displayed as line number
(where "0" is actually shown as Line 10 on the train)
- `get_rc()`
Returns the "Routingcode" property of the train (a string).
The interlocking system uses this property for Automatic Routesetting.
- `set_rc(routingcode)`
Sets the "Routingcode" property of the train (a string).
The interlocking system uses this property for Automatic Routesetting.
#### Shunting Functions and Variables
There are several functions available especially for shunting operations. Some of these functions make use of Freight Codes (FC) set in the Wagon Properties of each wagon and/or locomotive:
- `split_at_index(index, atc_command)`
Splits the train at the specified index, into a train with index-1 wagons and a second train starting with the index-th wagon. The `atc_command` specified is sent to the second train after decoupling. `"S0"` or `"B0"` is common to ensure any locomotives in the remaining train don't continue to move.
Example: train has wagons `"foo","foo","foo","bar","bar","bar"`
Command: `split_at_index(4,"S0")`
Result: first train (continues at previous speed): `"foo","foo","foo"`, second train (slows at S0): `"bar","bar","bar"`
- `split_at_fc(atc_command, len)`
Splits the train in such a way that all cars with non-empty current FC of the first part of the train have the same FC. The
`atc_command` specified is sent to the rear part, as with split_at_index. It returns the fc of the cars of the first part.
Example : Train has current FCs `"" "" "bar" "foo" "bar"`
Command: `split_at_fc(<atc_command>)`
Result: `train "" "" "bar"` and `train "foo" "bar"`
The function returns `"bar"` in this case.
The optional argument `len` specifies the maximum length for the
first part of the train.
Example: Train has current FCs `"foo" "foo" "foo" "foo" "bar" "bar"`
Command: `split_at_fc(<atc_command>,3)`
Result: `"foo" "foo" "foo"` and `"foo" "bar" "bar"`
The function returns `"foo"` in this case.
- `split_off_locomotive(command, len)`
Splits off the locomotives at the front of the train, which are
identified by an empty FC. `command` specifies the ATC command to be
executed by the rear half of the train. The optional argument `len` specifies the maximum length for the
first part of the train as above.
- `step_fc()`
Steps the FCs of all train cars forward. FCs are composed of codes
separated by exclamation marks (`!`), for instance
`"foo!bar!baz"`. Each wagon has a current FC, indicating its next
destination. Stepping the freight code forward, selects the next
code after the !. If the end of the string is reached, then the
first code is selected, except if the string ends with a question
mark (`?`), then the order is reversed.
- `train_length()`
returns the number of cars the train is composed of.
- `set_autocouple()`
Sets the train into autocouple mode. The train will couple to the next train it collides with.
- `unset_autocouple()`
Unsets autocouple mode
Deprecated:
- `set_shunt()`, `unset_shunt()`
deprecated aliases for set_autocouple() and unset_autocouple(), will be removed from a later release.
#### Interlocking
This additional function is available when advtrains_interlocking is enabled:
- `atc_set_disable_ars(boolean)`
Disables (true) or enables (false) the use of ARS for this train. The train will not trigger ARS (automatic route setting) on signals then.
Note: If you want to disable ARS from an approach callback, the call to `atc_set_disable_ars(true)` *must* happen during the approach callback, and may not be deferred to an interrupt(). Else the train might trigger an ARS before the interrupt fires.
#### Approach callbacks
The LuaATC interface provides a way to hook into the approach callback system, which is for example used in the TSR rails (provided by advtrains_interlocking) or the station tracks (provided by advtrains_lines). However, for compatibility reasons, this behavior needs to be explicitly enabled.
Enabling the receiving of approach events works by setting a variable in the local environment of the ATC rail, by inserting the following code:
```lua
__approach_callback_mode = 1
-- to receive approach callbacks only in arrow direction
-- or alternatively
__approach_callback_mode = 2
-- to receive approach callbacks in both directions
```
The following event will be emitted when a train approaches:
```lua
{type="approach", approach=true, id="<train_id>"}
```
Please note these important considerations when using approach callbacks:
- Approach events might be generated multiple times for the same approaching train. If you are using atc_set_lzb_tsr(), you need to call this function on every run of the approach callback, even if you issued it before for the same train.
- A reference to the train is available while executing this event, so that functions such as atc_send() or atc_set_text_outside() can be called. On any consecutive interrupts, that reference will no longer be available until the train enters the track ("train" event)
- Unlike all other callbacks, approach callbacks are executed synchronous during the train step. This may cause unexpected side effects when performing certain actions (such as switching turnouts, setting signals/routes) from inside such a callback. I strongly encourage you to only run things that are absolutely necessary at this point in time, and defer anything else to an interrupt(). Be aware that certain things might trigger unexpected behavior.
Operations that are safe to execute in approach callbacks:
- anything related only to the global environment (setting things in S)
- digiline_send()
- atc_set_text_*()
- atc_set_lzb_tsr() (see below)
In the context of approach callbacks, one more function is available:
- `atc_set_lzb_tsr(speed)`
Impose a Temporary Speed Restriction at the location of this rail, making the train pass this rail at the specified speed. (Causes the same behavior as the TSR rail)
#### Timetable Automation
The advtrains_line_automation component adds a few contraptions that should make creating timeable systems easier.
Part of its functionality is also available in LuaATC:
- `rwt.*`
All Railway Time functions are included as documented in https://advtrains.de/wiki/doku.php?id=dev:lines:rwt
- `schedule(rw_time, msg)`
- `schedule_in(rw_dtime, msg)`
Schedules the following event `{type="schedule", schedule=true, msg=msg}` at (resp. after) the specified railway time (which can be in any format). You can only schedule one event this way. Uses the new lines-internal scheduler.
### Operator panel
This simple node executes its actions when punched. It can be used to change a switch and update the corresponding signals or similar applications. It can also be connected to by the`digilines` mod.
The event fired is `{type="punch", punch=true}` by default. In case of an interrupt or a digiline message, the events are similar to the ones of the ATC rail.
### Init code
The initialization code is not a component as such, but rather a part of the whole environment. It can (and should) be used to make definitions that other components can refer to.
A basic example function to define behavior for trains in stations:
```lua
function F.station(station_name)
if event.train then
atc_send("B0WOL")
atc_set_text_inside(station_name)
interrupt(10,"depart")
end
if event.int and event.message="depart" then
atc_set_text_inside("") --an empty string clears the displayed text
atc_send("OCD1SM")
end
end
```
The corresponding Lua ATC Rail(s) would then contain the following or similar:
```lua
F.station("Main Station")
```
The init code is run whenever the F table needs to be refilled with data. This is the case on server startup and whenever the init code is changed and you choose to run it.
The event table of the init code is always `{type="init", init=true}` and can not be anything else.
Functions are run in the environment of the currently active node, regardless of where they were defined.
### Passive components
All passive components can be interfaced with the `setstate()` and `getstate()` functions (see above).
Each node below has been mapped to specific "states":
#### Signals
The red/green light signals `advtrains:signal_on/off` are interfaceable. Others such as `advtrains:retrosignal_on/off` are not. If advtrains_interlocking is enabled, trains will obey the signal if the influence point is set.
- "green" - Signal shows green light
- "red" - Signal shows red light
#### Switches/Turnouts
All default rail switches are interfaceable, independent of orientation.
- "cr" The switch is set in the direction that is not straight.
- "st" The switch is set in the direction that is straight.
The "Y" and "3-Way" switches have custom states. Looking from the convergence point:
- "l" The switch is set towards the left.
- "c" The switch is set towards the center (3-way only).
- "r" The switch is set towards the right.
#### Mesecon Switch
The Mesecon switch can be switched using LuaAutomation. Note that this is not possible on levers or protected mesecon switches, only the unprotected full-node 'Switch' block `mesecons_switch:mesecon_switch_on/off`.
- "on" - the switch is switched on.
- "off" - the switch is switched off.
#### Andrew's Cross
- "on" - it blinks.
- "off" - it does not blink.
#### Passive Component Naming
You can assign names to passive components using the Passive Component Naming tool.
Once you set a name for any component, you can reference it by that name in the `getstate()` and `setstate()` functions.
This way, you don't need to memorize positions.
Example: signal named `"Stn_P1_out"` at `(1,2,3)`
Use `setstate("Stn_P1_out", "green")` instead of `setstate(POS(1,2,3), "green")`
If `advtrains_interlocking` is enabled, PC-Naming can also be used to name interlocking signals for route setting via the `set_route()` functions.
**Important**: The "Signal Name" field in the signalling formspec is completely independent from PC-Naming and can't be used to look up the position. You need to explicitly use the PC-Naming tool.

View file

@ -1,288 +0,0 @@
#### Advtrains - Lua Automation features
This mod offers components that run LUA code and interface with each other through a global environment. It makes complex automated railway systems possible.
### atlatc
The mod is sometimes abbreviated as 'atlatc'. This stands for AdvTrainsLuaATC. This short name has been chosen for user convenience, since the name of this mod ('advtrains_luaautomation') is very long.
### Privilege
To perform any operations using this mod (except executing operation panels), players need the "atlatc" privilege.
This privilege should never be granted to anyone except trusted administrators. Even though the LUA environment is sandboxed, it is still possible to DoS the server by coding infinite loops or requesting expotentially growing interrupts.
### Active and passive
Active components are these who have LUA code running in them. They are triggered on specific events. Passive components are dumb, they only have a state and can be set to another state, they can't perform actions themselves.
### Environments
Each active component is assigned to an environment. This is where all data are held. Components in different environments can't inferface with each other.
This system allows multiple independent automation systems to run simultaneously without polluting each other's environment.
/env_create <env_name>
Create environment with the given name. To be able to do anything, you first need to create an environment. Choose the name wisely, you can't change it afterwards.
/env_setup <env_name>
Invoke the form to edit the environment's initialization code. For more information, see the section on active components. You can also delete an environment from here.
### Active components
The code of every active component is run on specific events which are explained soon. When run, every variable written that is not local and is no function or userdata is saved over code re-runs and over server restarts. Additionally, the following global variables are defined:
# event
The variable 'event' contains a table with information on the current event. How this table can look is explained below.
# S
The variable 'S' contains a table which is shared between all components of the environment. Its contents are persistent over server restarts. May not contain functions, every other value is allowed.
Example:
Component 1: S.stuff="foo"
Component 2: print(S.stuff)
-> foo
# F
The variable 'F' also contains a table which is shared between all components of the environment. Its contents are discarded on server shutdown or when the init code gets re-run. Every data type is allowed, even functions.
The purpose of this table is not to save data, but to provide static value and function definitions. The table should be populated by the init code.
# Standard Lua functions
The following standard Lua libraries are available:
string, math, table, os
The following standard Lua functions are available:
assert, error, ipairs, pairs, next, select, tonumber, tostring, type, unpack
Every attempt to overwrite any of the predefined values results in an error.
# LuaAutomation-specific global functions
POS(x,y,z)
Shorthand function to create a position vector {x=?, y=?, z=?} with less characters
In the following functions, all parameters named 'pos' designate a position. You can use either:
- a default Minetest position vector (like {x=34, y=2, z=-18})
- the POS(34,2,-18) shorthand
- A string, the passive component name. See 'passive component naming'.
getstate(pos)
Get the state of the passive component at position 'pos'. See section on passive components for more info.
pos can be either a position vector (created by POS()) or a string, the name of this passive component.
setstate(pos, newstate)
Set the state of the passive component at position 'pos'.
is_passive(pos)
Checks whether there is a passive component at the position pos (and/or whether a passive component with this name exists)
interrupt(time, message)
Cause LuaAutomation to trigger an 'int' event on this component after the given time in seconds with the specified 'message' field. 'message' can be of any Lua data type.
Not available in init code!
interrupt_pos(pos, message)
Immediately trigger an 'ext_int' event on the active component at position pos. 'message' is like in interrupt().
USE WITH CARE, or better don't use! Incorrect use can result in expotential growth of interrupts.
digiline_send(channel, message)
Make this active component send a digiline message on the specified channel.
Not available in init code!
-- The next 4 functions are available when advtrains_interlocking is enabled: --
can_set_route(pos, route_name)
Returns whether it is possible to set the route designated by route_name from the signal at pos.
set_route(pos, route_name)
Requests the given route from the signal at pos. Has the same effect as clicking "Set Route" in the signalling dialog.
cancel_route(pos)
Cancels the route that is set from the signal at pos. Has the same effect as clicking "Cancel Route" in the signalling dialog.
get_aspect(pos)
Returns the signal aspect of the signal at pos. A signal aspect has the following format:
aspect = {
main = { -- the next track section in line. Shows blocked for shunt routes
free = <boolean>,
speed = <int km/h>,
},
shunt = { -- whether a "shunting allowed" aspect should be shown
free = <boolean>,
}
dst = { -- the aspect of the next main signal on (at end of) route
free = <boolean>,
speed = <int km/h>,
}
info = {
call_on = <boolean>, -- Call-on route, expect train in track ahead
dead_end = <boolean>, -- Route ends on a dead end (e.g. bumper)
}
}
As of August 2018, only the aspect.main.free field is ever used by the interlocking system.
# Lines
The advtrains_line_automation component adds a few contraptions that should make creating timeable systems easier.
Part of its functionality is also available in LuaATC:
- rwt.* - all Railway Time functions are included as documented in https://advtrains.de/wiki/doku.php?id=dev:lines:rwt
- schedule(rw_time, msg)
- schedule_in(rw_dtime, msg)
Schedules an event of type {type="schedule", schedule=true, msg=msg} at (resp. after) the specified railway time.
(which can be in any format). You can only schedule one event this way. (uses the new lines-internal scheduler)
## Components and events
The event table is a table of the following format:
{
type = "<type>",
<type> = true,
... additional content ...
}
You can check for the event type by either using
if event.type == "wanted" then ...do stuff... end
or
if event.wanted then ...do stuff... end
(if 'wanted' is the event type to check for)
# Init code
The initialization code is not a component as such, but rather a part of the whole environment. It can (and should) be used to make definitions that other components can refer to.
Examples:
A function to define behavior for trains in subway stations:
function F.station()
if event.train then atc_send("B0WOL") end
if event.int and event.message="depart" then atc_send("OCD1SM") end
end
The init code is run whenever the F table needs to be refilled with data. This is the case on server startup and whenever the init code is changed and you choose to run it.
Functions are run in the environment of the currently active node, regardless of where they were defined. So, the 'event' table always reflects the state of the calling node.
The 'event' table of the init code is always {type="init", init=true}.
# ATC rails
The Lua-controlled ATC rails are the only components that can actually interface with trains. The following event types are generated:
{type="train", train=true, id="<train_id>"}
This event is fired when a train enters the rail. The field 'id' is the unique train ID, which is 6-digit random numerical string.
If the world contains trains from an older advtrains version, this string may be longer and contain a dot (.)
{type="int", int=true, msg=<message>}
Fired when an interrupt set by the 'interrupt' function runs out. 'message' is the message passed to the interrupt function.
For backwards compatiblity reasons, <message> is also contained in an event.message field.
{type="ext_int", ext_int=true, message=<message>}
Fired when another node called 'interrupt_pos' on this position. 'message' is the message passed to the interrupt_pos function.
{type="digiline", digiline=true, channel=<channel>, msg=<message>}
Fired when the controller receives a digiline message.
In addition to the default environment functions, the following functions are available:
atc_send(<atc_command>)
Sends the specified ATC command to the train and returns true. If there is no train, returns false and does nothing.
atc_reset()
Resets the train's current ATC command. If there is no train, returns false and does nothing.
atc_arrow
Boolean, true when the train is driving in the direction of the arrows of the ATC rail. Nil if there is no train.
atc_id
Train ID of the train currently passing the controller. Nil if there's no train.
atc_speed
Speed of the train, or nil if there is no train.
atc_set_text_outside(text)
Set text shown on the outside of the train. Pass nil to show no text.
atc_set_text_inside(text)
Set text shown to train passengers. Pass nil to show no text.
get_line()
Returns the "Line" property of the train (a string).
This can be used to distinguish between trains of different lines and route them appropriately.
The interlocking system also uses this property for Automatic Routesetting.
set_line(line)
Sets the "Line" property of the train (a string).
If the first digit of this string is a number (0-9), any subway wagons on the train will have this one displayed as line number
(where "0" is actually shown as Line 10 on the train)
get_rc()
Returns the "Routingcode" property of the train (a string).
The interlocking system uses this property for Automatic Routesetting.
set_rc(routingcode)
Sets the "Routingcode" property of the train (a string).
The interlocking system uses this property for Automatic Routesetting.
split_at_index(index, command)
Splits the train at the specified index, into a train with index-1 wagons and a second train starting with the index-th wagon.
command specifies an atc command to be sent to the second train after decoupling.
split_at_fc(command, len)
Splits the train in such a way that all cars with non-empty
current FC of the first part of the train have the same FC. The
command specified is sent to the rear part, as with
split_at_index. It returns the fc of the cars of the first part.
The optional argument len specifies the maximum length for the
first part of the train. Say, we have len=3, and the train has ""
"" "foo" "foo" "foo" "bar", then the first train part will be ""
"" "foo".
Example : Train has current FCs "" "" "foo" "bar" "foo"
Result: first train: "" "" "foo"; second train: "bar" "foo"
The command returns "foo" in this case
split_off_locomotive(command, len)
Splits off the locomotives at the front of the train, which are
identified by an empty FC. command specifies the command to be
executed by the rear half of the train.
The optional argument len specifies the maximum length for the
first part of the train. Say, we have len=3, and the train has ""
"" "foo" "foo" "foo" "bar", then the first train part will be ""
"" "foo".
step_fc()
Steps the FCs of all train cars forward. FCs are composed of codes
separated by exclamation marks (!), for instance
"foo!bar!baz". Each wagon has a current FC, indicating its next
destination. Stepping the freight code forward, selects the next
code after the !. If the end of the string is reached, then the
first code is selected, except if the string ends with a question
mark, then the order is reversed.
train_length()
returns the number of cars the train is composed of
set_autocouple()
Sets the train into autocouple mode
unset_autocouple()
Unsets autocouple mode
set_shunt(), unset_shunt()
deprecated aliases for set_autocouple() and unset_autocouple(), will be removed from a later release.
# Operator panel
This simple node executes its actions when punched. It can be used to change a switch and update the corresponding signals or similar applications.
The event fired is {type="punch", punch=true} by default. In case of an interrupt or a digiline message, the events are similar to the ones of the ATC rail.
### Passive components
All passive components can be interfaced with the setstate and getstate functions(see above).
Below, each apperance is mapped to the "state" of that node.
## Signals
The light signals are interfaceable, the analog signals are not.
"green" - Signal shows green light
"red" - Signal shows red light
## Switches
All default rail switches are interfaceable, independent of orientation.
"cr" - The switch is set in the direction that is not straight.
"st" - The switch is set in the direction that is straight.
## Mesecon Switch
The Mesecon switch can be switched using LuaAutomation. Note that this is not possible on levers, only the full-node 'Switch' block.
"on" - the switch is switched on
"off" - the switch is switched off
##Andrew's Cross
"on" - it blinks
"off" - it does not blink
### Passive component naming
You can assign names to passive components using the Passive Component Naming tool.
Once you set a name for any component, you can reference it by that name in the getstate() and setstate() functions, like this:
(Imagine a signal that you have named "Stn_P1_out" at position (1,2,3) )
setstate("Stn_P1_out", "green") instead of setstate(POS(1,2,3), "green")
This way, you don't need to memorize positions.
PC-Naming can also be used to name interlocking signals for route setting via the set_route() functions. IMPORTANT: The "Signal Name" set in the
signalling formspec is completely independent and can NOT be used to look up the position, you need to explicitly use the PCNaming tool.
--TODO: Ein paar mehr Codebeispiele wären schön, insbesondere mit os.date und so...

View file

@ -101,7 +101,7 @@ function ac.run_in_env(pos, evtdata, customfct_p)
end end
local meta local meta
if minetest.get_node_or_nil(pos) then if advtrains.is_node_loaded(pos) then
meta=minetest.get_meta(pos) meta=minetest.get_meta(pos)
end end
@ -109,8 +109,9 @@ function ac.run_in_env(pos, evtdata, customfct_p)
atwarn("LuaAutomation component at",ph,": Not an existing environment: "..(nodetbl.env or "<nil>")) atwarn("LuaAutomation component at",ph,": Not an existing environment: "..(nodetbl.env or "<nil>"))
return false return false
end end
local env = atlatc.envs[nodetbl.env]
if not nodetbl.code or nodetbl.code=="" then if not nodetbl.code or nodetbl.code=="" then
atwarn("LuaAutomation component at",ph,": No code to run! (insert -- to suppress warning)") env:log("warning", "LuaAutomation component at",ph,": No code to run! (insert -- to suppress warning)")
return false return false
end end
@ -121,13 +122,27 @@ function ac.run_in_env(pos, evtdata, customfct_p)
assert(t >= 0) assert(t >= 0)
atlatc.interrupt.add(t, pos, {type="int", int=true, message=imesg, msg=imesg}) --Compatiblity "message" field. atlatc.interrupt.add(t, pos, {type="int", int=true, message=imesg, msg=imesg}) --Compatiblity "message" field.
end end
customfct.interrupt_safe=function(t, imesg)
assertt(t, "number")
assert(t >= 0)
if atlatc.interrupt.has_at_pos(pos) then
return false
end
atlatc.interrupt.add(t, pos, {type="int", int=true, message=imesg, msg=imesg}) --Compatiblity "message" field.
return true
end
customfct.clear_interrupts=function()
atlatc.interrupt.clear_ints_at_pos(pos)
end
-- add digiline_send function, if digiline is loaded -- add digiline_send function, if digiline is loaded
if minetest.global_exists("digiline") then if minetest.global_exists("digiline") then
customfct.digiline_send=function(channel, msg) customfct.digiline_send=function(channel, msg)
assertt(channel, "string") assertt(channel, "string")
if advtrains.is_node_loaded(pos) then
digiline:receptor_send(pos, digiline.rules.default, channel, msg) digiline:receptor_send(pos, digiline.rules.default, channel, msg)
end end
end end
end
-- add lines scheduler if enabled -- add lines scheduler if enabled
if advtrains.lines and advtrains.lines.sched then if advtrains.lines and advtrains.lines.sched then
customfct.schedule = function(rwtime, msg) customfct.schedule = function(rwtime, msg)
@ -139,15 +154,20 @@ function ac.run_in_env(pos, evtdata, customfct_p)
end end
local datain=nodetbl.data or {} local datain=nodetbl.data or {}
local succ, dataout = atlatc.envs[nodetbl.env]:execute_code(datain, nodetbl.code, evtdata, customfct) local succ, dataout = env:execute_code(datain, nodetbl.code, evtdata, customfct)
if succ then if succ then
atlatc.active.nodes[ph].data=atlatc.remove_invalid_data(dataout) atlatc.active.nodes[ph].data=atlatc.remove_invalid_data(dataout)
else else
atlatc.active.nodes[ph].err=dataout atlatc.active.nodes[ph].err=dataout
atwarn("LuaAutomation ATC interface rail at",ph,": LUA Error:",dataout) env:log("error", "LuaATC component at",ph,": LUA Error:",dataout)
if meta then if meta then
meta:set_string("infotext", "LuaAutomation ATC interface rail, ERROR:"..dataout) meta:set_string("infotext", "LuaATC component, ERROR:"..dataout)
end end
--TODO temporary
--if customfct.atc_id then
-- advtrains.drb_dump(customfct.atc_id)
-- error("Debug: LuaATC error hit!")
--end
end end
if meta then if meta then
meta:set_string("formspec", ac.getform(pos, meta)) meta:set_string("formspec", ac.getform(pos, meta))

View file

@ -5,7 +5,10 @@
--Using subtable --Using subtable
local r={} local r={}
function r.fire_event(pos, evtdata) -- Note on appr_internal:
-- The Approach callback is a special corner case: the train is not on the node, and it is executed synchronized
-- (in the train step right during LZB traversal). We therefore need access to the train id and the lzbdata table
function r.fire_event(pos, evtdata, appr_internal)
local ph=minetest.pos_to_string(pos) local ph=minetest.pos_to_string(pos)
local railtbl = atlatc.active.nodes[ph] local railtbl = atlatc.active.nodes[ph]
@ -15,37 +18,34 @@ function r.fire_event(pos, evtdata)
return return
end end
local arrowconn = railtbl.arrowconn
if not arrowconn then
atwarn("LuaAutomation ATC interface rail at",ph,": Incomplete Data! Please visit position and click 'Save'!")
return
end
--prepare ingame API for ATC. Regenerate each time since pos needs to be known --prepare ingame API for ATC. Regenerate each time since pos needs to be known
--If no train, then return false. --If no train, then return false.
local train_id=advtrains.get_train_at_pos(pos)
local train, atc_arrow, tvel
if train_id then train=advtrains.trains[train_id] end
if train then
if not train.path then
--we happened to get in between an invalidation step
--delay
atlatc.interrupt.add(0,pos,evtdata)
return
end
local index = advtrains.path_lookup(train, pos)
local iconnid = 1 -- try to get the train from the event data
if index then -- This workaround is required because the callback is one step delayed, and a fast train may have already left the node.
iconnid = train.path_cn[index] -- Also used for approach callback
local train_id = evtdata._train_id
local atc_arrow = evtdata._train_arrow
local train, tvel
if train_id then
train=advtrains.trains[train_id]
-- speed
tvel=train.velocity
-- if still no train_id available, try to get the train at my position
else else
atwarn("ATC rail at", pos, ": Rail not on train's path! Can't determine arrow direction. Assuming +!") train_id=advtrains.get_train_at_pos(pos)
end if train_id then
atc_arrow = iconnid == 1 train=advtrains.trains[train_id]
advtrains.train_ensure_init(train_id, train)
-- look up atc_arrow
local index = advtrains.path_lookup(train, pos)
atc_arrow = (train.path_cn[index] == 1)
-- speed
tvel=train.velocity tvel=train.velocity
end end
end
local customfct={ local customfct={
atc_send = function(cmd) atc_send = function(cmd)
if not train_id then return false end if not train_id then return false end
@ -92,12 +92,13 @@ function r.fire_event(pos, evtdata)
advtrains.train_step_fc(train) advtrains.train_step_fc(train)
end, end,
set_shunt = function() set_shunt = function()
-- enable shunting mode
if not train_id then return false end if not train_id then return false end
train.autocouple = true train.is_shunt = true
end, end,
unset_shunt = function() unset_shunt = function()
if not train_id then return false end if not train_id then return false end
train.autocouple = nil train.is_shunt = nil
end, end,
set_autocouple = function () set_autocouple = function ()
if not train_id then return false end if not train_id then return false end
@ -150,13 +151,29 @@ function r.fire_event(pos, evtdata)
advtrains.trains[train_id].text_inside=text advtrains.trains[train_id].text_inside=text
return true return true
end, end,
atc_set_lzb_tsr = function(speed)
if not appr_internal then
error("atc_set_lzb_tsr() can only be used during 'approach' events!")
end
assert(tonumber(speed), "Number expected!")
local index = appr_internal.index
advtrains.lzb_add_checkpoint(train, index, speed, nil)
return true
end,
} }
-- interlocking specific
if advtrains.interlocking then
customfct.atc_set_ars_disable = function(value)
advtrains.interlocking.ars_set_disable(train, value)
end
end
atlatc.active.run_in_env(pos, evtdata, customfct) atlatc.active.run_in_env(pos, evtdata, customfct)
end end
if minetest.get_modpath("advtrains_train_track") ~= nil then
advtrains.register_tracks("default", { advtrains.register_tracks("default", {
nodename_prefix="advtrains_luaautomation:dtrack", nodename_prefix="advtrains_luaautomation:dtrack",
texture_prefix="advtrains_dtrack_atc", texture_prefix="advtrains_dtrack_atc",
@ -169,14 +186,42 @@ if minetest.get_modpath("advtrains_train_track") ~= nil then
return { return {
after_place_node = atlatc.active.after_place_node, after_place_node = atlatc.active.after_place_node,
after_dig_node = atlatc.active.after_dig_node, after_dig_node = atlatc.active.after_dig_node,
on_receive_fields = function(pos, ...) on_receive_fields = function(pos, ...)
atlatc.active.on_receive_fields(pos, ...) atlatc.active.on_receive_fields(pos, ...)
--set arrowconn (for ATC) --set arrowconn (for ATC)
local ph=minetest.pos_to_string(pos) local ph=minetest.pos_to_string(pos)
local _, conns=advtrains.get_rail_info_at(pos, advtrains.all_tracktypes) local _, conns=advtrains.get_rail_info_at(pos, advtrains.all_tracktypes)
atlatc.active.nodes[ph].arrowconn=conns[1].c atlatc.active.nodes[ph].arrowconn=conns[1].c
end, end,
advtrains = atlatc.active.trackdef_advtrains_defs,
advtrains = {
on_train_enter = function(pos, train_id, train, index)
--do async. Event is fired in train steps
atlatc.interrupt.add(0, pos, {type="train", train=true, id=train_id,
_train_id = train_id, _train_arrow = (train.path_cn[index] == 1)})
end,
on_train_approach = function(pos, train_id, train, index, has_entered, lzbdata)
-- Insert an event only if the rail indicated that it supports approach callbacks
local ph=minetest.pos_to_string(pos)
local railtbl = atlatc.active.nodes[ph]
-- uses a "magic variable" in the local environment of the node
-- This hack is necessary because code might not be prepared to get approach events...
if railtbl and railtbl.data and railtbl.data.__approach_callback_mode then
local acm = railtbl.data.__approach_callback_mode
local in_arrow = (train.path_cn[index] == 1)
if acm==2 or (acm==1 and in_arrow) then
local evtdata = {type="approach", approach=true, id=train_id, has_entered = has_entered,
_train_id = train_id, _train_arrow = in_arrow} -- reuses code from train_enter
-- This event is *required* to run synchronously, because it might set the ars_disable flag on the train and add LZB checkpoints,
-- although this is generally discouraged because this happens right in a train step
-- At this moment, I am not aware whether this may cause side effects, and I must encourage users not to do expensive calculations here.
r.fire_event(pos, evtdata, {train_id = train_id, train = train, index = index, lzbdata = lzbdata})
end
end
end,
},
luaautomation = { luaautomation = {
fire_event=r.fire_event fire_event=r.fire_event
}, },
@ -189,6 +234,6 @@ if minetest.get_modpath("advtrains_train_track") ~= nil then
} }
end, end,
}, advtrains.trackpresets.t_30deg_straightonly) }, advtrains.trackpresets.t_30deg_straightonly)
end
atlatc.rail = r atlatc.rail = r

View file

@ -43,12 +43,78 @@ core.register_chatcommand("env_create", {
privs = {atlatc=true}, privs = {atlatc=true},
func = function(name, param) func = function(name, param)
if not param or param=="" then return false, "Name required!" end if not param or param=="" then return false, "Name required!" end
if string.find(param, "[^a-zA-Z0-9-_]") then return false, "Invalid name (only common characters)" end
if atlatc.envs[param] then return false, "Environment already exists!" end if atlatc.envs[param] then return false, "Environment already exists!" end
atlatc.envs[param] = atlatc.env_new(param) atlatc.envs[param] = atlatc.env_new(param)
atlatc.envs[param].subscribers = {name}
return true, "Created environment '"..param.."'. Use '/env_setup "..param.."' to define global initialization code, or start building LuaATC components!" return true, "Created environment '"..param.."'. Use '/env_setup "..param.."' to define global initialization code, or start building LuaATC components!"
end, end,
}) })
core.register_chatcommand("env_subscribe", {
params = "<environment name>",
description = "Subscribe to the log of an Advtrains LuaATC environment",
privs = {atlatc=true},
func = function(name, param)
local env=atlatc.envs[param]
if not env then return false,"Invalid environment name!" end
for _,pname in ipairs(env.subscribers) do
if pname==name then
return false, "Already subscribed!"
end
end
table.insert(env.subscribers, name)
return true, "Subscribed to environment '"..param.."'."
end,
})
core.register_chatcommand("env_unsubscribe", {
params = "<environment name>",
description = "Unubscribe to the log of an Advtrains LuaATC environment",
privs = {atlatc=true},
func = function(name, param)
local env=atlatc.envs[param]
if not env then return false,"Invalid environment name!" end
for index,pname in ipairs(env.subscribers) do
if pname==name then
table.remove(env.subscribers, index)
return true, "Successfully unsubscribed!"
end
end
return false, "Not subscribed to environment '"..param.."'."
end,
})
core.register_chatcommand("env_subscriptions", {
params = "[environment name]",
description = "List Advtrains LuaATC environments you are subscribed to (no parameters) or subscribers of an environment (giving an env name).",
privs = {atlatc=true},
func = function(name, param)
if not param or param=="" then
local none=true
for envname, env in pairs(atlatc.envs) do
for _,pname in ipairs(env.subscribers) do
if pname==name then
none=false
minetest.chat_send_player(name, envname)
end
end
end
if none then
return false, "Not subscribed to any!"
end
return true
end
local env=atlatc.envs[param]
if not env then return false,"Invalid environment name!" end
local none=true
for index,pname in ipairs(env.subscribers) do
none=false
minetest.chat_send_player(name, pname)
end
if none then
return false, "No subscribers!"
end
return true
end,
})
minetest.register_on_player_receive_fields(function(player, formname, fields) minetest.register_on_player_receive_fields(function(player, formname, fields)

View file

@ -33,12 +33,12 @@ local env_proto={
self.sdata=data.sdata and atlatc.remove_invalid_data(data.sdata) or {} self.sdata=data.sdata and atlatc.remove_invalid_data(data.sdata) or {}
self.fdata={} self.fdata={}
self.init_code=data.init_code or "" self.init_code=data.init_code or ""
self.step_code=data.step_code or "" self.subscribers=data.subscribers or {}
end, end,
save = function(self) save = function(self)
-- throw any function values out of the sdata table -- throw any function values out of the sdata table
self.sdata = atlatc.remove_invalid_data(self.sdata) self.sdata = atlatc.remove_invalid_data(self.sdata)
return {sdata = self.sdata, init_code=self.init_code, step_code=self.step_code} return {sdata = self.sdata, init_code=self.init_code, subscribers=self.subscribers}
end, end,
} }
@ -50,14 +50,6 @@ local safe_globals = {
"tonumber", "tostring", "type", "unpack", "_VERSION" "tonumber", "tostring", "type", "unpack", "_VERSION"
} }
--print is actually minetest.chat_send_all()
--using advtrains.print_concat_table because it's cool
local function safe_print(t, ...)
local str=advtrains.print_concat_table({t, ...})
minetest.log("action", "[atlatc] "..str)
minetest.chat_send_all(str)
end
local function safe_date(f, t) local function safe_date(f, t)
if not f then if not f then
-- fall back to old behavior -- fall back to old behavior
@ -95,7 +87,6 @@ local mp=minetest.get_modpath("advtrains_luaautomation")
local static_env = { local static_env = {
--core LUA functions --core LUA functions
print = safe_print,
string = { string = {
byte = string.byte, byte = string.byte,
char = string.char, char = string.char,
@ -252,7 +243,6 @@ for _, name in pairs(safe_globals) do
static_env[name] = _G[name] static_env[name] = _G[name]
end end
--The environment all code calls get is a table that has set static_env as metatable. --The environment all code calls get is a table that has set static_env as metatable.
--In general, every variable is local to a single code chunk, but kept persistent over code re-runs. Data is also saved, but functions and userdata and circular references are removed --In general, every variable is local to a single code chunk, but kept persistent over code re-runs. Data is also saved, but functions and userdata and circular references are removed
--Init code and step code's environments are not saved --Init code and step code's environments are not saved
@ -265,6 +255,14 @@ local proxy_env={}
-- returns: true, fenv if successful; nil, error if error -- returns: true, fenv if successful; nil, error if error
function env_proto:execute_code(localenv, code, evtdata, customfct) function env_proto:execute_code(localenv, code, evtdata, customfct)
-- create us a print function specific for this environment
if not self.safe_print_func then
local myenv = self
self.safe_print_func = function(...)
myenv:log("info", ...)
end
end
local metatbl ={ local metatbl ={
__index = function(t, i) __index = function(t, i)
if i=="S" then if i=="S" then
@ -277,6 +275,8 @@ function env_proto:execute_code(localenv, code, evtdata, customfct)
return customfct[i] return customfct[i]
elseif localenv and localenv[i] then elseif localenv and localenv[i] then
return localenv[i] return localenv[i]
elseif i=="print" then
return self.safe_print_func
end end
return static_env[i] return static_env[i]
end, end,
@ -306,35 +306,39 @@ function env_proto:run_initcode()
if self.init_code and self.init_code~="" then if self.init_code and self.init_code~="" then
local old_fdata=self.fdata local old_fdata=self.fdata
self.fdata = {} self.fdata = {}
atprint("[atlatc]Running initialization code for environment '"..self.name.."'") --atprint("[atlatc]Running initialization code for environment '"..self.name.."'")
local succ, err = self:execute_code({}, self.init_code, {type="init", init=true}) local succ, err = self:execute_code({}, self.init_code, {type="init", init=true})
if not succ then if not succ then
atwarn("[atlatc]Executing InitCode for '"..self.name.."' failed:"..err) self:log("error", "Executing InitCode for '"..self.name.."' failed:"..err)
self.init_err=err self.init_err=err
if old_fdata then if old_fdata then
self.fdata=old_fdata self.fdata=old_fdata
atwarn("[atlatc]The 'F' table has been restored to the previous state.") self:log("warning", "The 'F' table has been restored to the previous state.")
end end
end end
end end
end end
function env_proto:run_stepcode()
if self.step_code and self.step_code~="" then -- log to environment subscribers. severity can be "error", "warning" or "info" (used by internal print)
local succ, err = self:execute_code({}, self.step_code, nil, {}) function env_proto:log(severity, ...)
if not succ then local text=advtrains.print_concat_table({"[atlatc "..self.name.." "..severity.."]", ...})
--TODO minetest.log("action", text)
end for _, pname in ipairs(self.subscribers) do
minetest.chat_send_player(pname, text)
end end
end end
-- env.subscribers table may be directly altered by callers.
--- class interface --- class interface
function atlatc.env_new(name) function atlatc.env_new(name)
local newenv={ local newenv={
name=name, name=name,
init_code="", init_code="",
step_code="", sdata={},
sdata={} subscribers={},
} }
setmetatable(newenv, {__index=env_proto}) setmetatable(newenv, {__index=env_proto})
return newenv return newenv
@ -351,11 +355,6 @@ function atlatc.run_initcode()
env:run_initcode() env:run_initcode()
end end
end end
function atlatc.run_stepcode()
for envname, env in pairs(atlatc.envs) do
env:run_stepcode()
end
end

View file

@ -16,6 +16,30 @@ function iq.save()
return {queue = queue, timer=timer} return {queue = queue, timer=timer}
end end
function iq.has_at_pos(pos)
for i=1,#queue do
local qe=queue[i]
if vector.equals(pos, qe.p) then
return true
end
end
return false
end
function iq.clear_ints_at_pos(pos)
local i=1
while i<=#queue do
local qe=queue[i]
if not qe then
table.remove(queue, i)
elseif vector.equals(pos, qe.p) and (qe.e.int or qe.e.ext_int) then
table.remove(queue, i)
else
i=i+1
end
end
end
function iq.add(t, pos, evtdata) function iq.add(t, pos, evtdata)
queue[#queue+1]={t=t+timer, p=pos, e=evtdata} queue[#queue+1]={t=t+timer, p=pos, e=evtdata}
run=true run=true
@ -23,13 +47,14 @@ end
function iq.mainloop(dtime) function iq.mainloop(dtime)
timer=timer + math.min(dtime, 0.2) timer=timer + math.min(dtime, 0.2)
for i=1,#queue do local i=1
while i<=#queue do
local qe=queue[i] local qe=queue[i]
if not qe then if not qe then
table.remove(queue, i) table.remove(queue, i)
i=i-1
elseif timer>qe.t then elseif timer>qe.t then
local pos, evtdata=queue[i].p, queue[i].e table.remove(queue, i)
local pos, evtdata=qe.p, qe.e
local node=advtrains.ndb.get_node(pos) local node=advtrains.ndb.get_node(pos)
local ndef=minetest.registered_nodes[node.name] local ndef=minetest.registered_nodes[node.name]
if ndef and ndef.luaautomation and ndef.luaautomation.fire_event then if ndef and ndef.luaautomation and ndef.luaautomation.fire_event then
@ -37,8 +62,8 @@ function iq.mainloop(dtime)
else else
atwarn("[atlatc][interrupt] Couldn't run event",evtdata.type,"on",pos,", something wrong with the node",node) atwarn("[atlatc][interrupt] Couldn't run event",evtdata.type,"on",pos,", something wrong with the node",node)
end end
table.remove(queue, i) else
i=i-1 i=i+1
end end
end end
end end

View file

@ -7,7 +7,7 @@ Displays
Mesecon Transmitter Mesecon Transmitter
Those passive components can also be used inside interlocking systems. Those passive components can also be used inside interlocking systems.
All passive components have a table called 'advtrains' in their node definition and have the group 'save_in_nodedb' set, so they work in unloaded chunks. All passive components have a table called 'advtrains' in their node definition and have the group 'save_in_at_nodedb' set, so they work in unloaded chunks.
Example for a switch: Example for a switch:
advtrains = { advtrains = {
getstate = function(pos, node) getstate = function(pos, node)

View file

@ -6,14 +6,14 @@
local setaspectf = function(rot) local setaspectf = function(rot)
return function(pos, node, asp) return function(pos, node, asp)
if not asp.main.free then if asp.main == 0 then
if asp.shunt.free then if asp.shunt then
advtrains.ndb.swap_node(pos, {name="advtrains_signals_ks:hs_shunt_"..rot, param2 = node.param2}) advtrains.ndb.swap_node(pos, {name="advtrains_signals_ks:hs_shunt_"..rot, param2 = node.param2})
else else
advtrains.ndb.swap_node(pos, {name="advtrains_signals_ks:hs_danger_"..rot, param2 = node.param2}) advtrains.ndb.swap_node(pos, {name="advtrains_signals_ks:hs_danger_"..rot, param2 = node.param2})
end end
else else
if asp.dst.free and asp.main.speed == -1 then if asp.dst ~= 0 and asp.main == -1 then
advtrains.ndb.swap_node(pos, {name="advtrains_signals_ks:hs_free_"..rot, param2 = node.param2}) advtrains.ndb.swap_node(pos, {name="advtrains_signals_ks:hs_free_"..rot, param2 = node.param2})
else else
advtrains.ndb.swap_node(pos, {name="advtrains_signals_ks:hs_slow_"..rot, param2 = node.param2}) advtrains.ndb.swap_node(pos, {name="advtrains_signals_ks:hs_slow_"..rot, param2 = node.param2})
@ -22,19 +22,12 @@ local setaspectf = function(rot)
end end
end end
local suppasp = { local suppasp = {
main = { main = {0, 6, -1},
free = nil, dst = {0, false},
speed = {6, -1}, shunt = nil,
},
dst = {
free = nil,
speed = nil,
},
shunt = {
free = nil,
proceed_as_main = true, proceed_as_main = true,
},
info = { info = {
call_on = false, call_on = false,
dead_end = false, dead_end = false,
@ -45,7 +38,7 @@ local suppasp = {
--Rangiersignal --Rangiersignal
local setaspectf_ra = function(rot) local setaspectf_ra = function(rot)
return function(pos, node, asp) return function(pos, node, asp)
if asp.shunt.free then if asp.shunt then
advtrains.ndb.swap_node(pos, {name="advtrains_signals_ks:ra_shuntd_"..rot, param2 = node.param2}) advtrains.ndb.swap_node(pos, {name="advtrains_signals_ks:ra_shuntd_"..rot, param2 = node.param2})
else else
advtrains.ndb.swap_node(pos, {name="advtrains_signals_ks:ra_danger_"..rot, param2 = node.param2}) advtrains.ndb.swap_node(pos, {name="advtrains_signals_ks:ra_danger_"..rot, param2 = node.param2})
@ -58,17 +51,11 @@ local setaspectf_ra = function(rot)
end end
local suppasp_ra = { local suppasp_ra = {
main = { main = { false },
free = true, dst = { false },
}, shunt = nil,
dst = {
free = nil,
speed = nil,
},
shunt = {
free = nil,
proceed_as_main = false, proceed_as_main = false,
},
info = { info = {
call_on = false, call_on = false,
dead_end = false, dead_end = false,
@ -90,9 +77,9 @@ for _, rtab in ipairs({
local rot = rtab.rot local rot = rtab.rot
for typ, prts in pairs({ for typ, prts in pairs({
danger = {asp = advtrains.interlocking.DANGER, n = "slow", ici=true}, danger = {asp = advtrains.interlocking.DANGER, n = "slow", ici=true},
slow = {asp = { main = { free = true, speed = 6 }, shunt = {proceed_as_main = true}} , n = "free"}, slow = {asp = { main = 6, proceed_as_main = true} , n = "free"},
free = {asp = { main = { free = true, speed = -1 }, shunt = {proceed_as_main = true}} , n = "shunt"}, free = {asp = { main = -1, proceed_as_main = true} , n = "shunt"},
shunt = {asp = { main = {free = false}, shunt = {free = true} } , n = "danger"}, shunt = {asp = { main = 0, shunt = true} , n = "danger"},
}) do }) do
minetest.register_node("advtrains_signals_ks:hs_"..typ.."_"..rot, { minetest.register_node("advtrains_signals_ks:hs_"..typ.."_"..rot, {
description = "Ks Main Signal", description = "Ks Main Signal",
@ -136,8 +123,8 @@ for _, rtab in ipairs({
--Rangiersignale: --Rangiersignale:
for typ, prts in pairs({ for typ, prts in pairs({
danger = {asp = { main = {free = true}, shunt = {free = false} }, n = "shuntd", ici=true}, danger = {asp = { main = false, shunt = false }, n = "shuntd", ici=true},
shuntd = {asp = { main = {free = true}, shunt = {free = true} } , n = "danger"}, shuntd = {asp = { main = false, shunt = true } , n = "danger"},
}) do }) do
minetest.register_node("advtrains_signals_ks:ra_"..typ.."_"..rot, { minetest.register_node("advtrains_signals_ks:ra_"..typ.."_"..rot, {
description = "Ks Shunting Signal", description = "Ks Shunting Signal",
@ -181,13 +168,14 @@ for _, rtab in ipairs({
--Schilder: --Schilder:
for typ, prts in pairs({ for typ, prts in pairs({
-- Speed restrictions: -- Speed restrictions:
["8"] = {asp = { main = {free = true, speed = 8}, shunt = {free = true} }, n = "12", ici=true}, ["8"] = {asp = { main = 8, shunt = true }, n = "12", ici=true},
["12"] = {asp = { main = {free = true, speed = 12}, shunt = {free = true} }, n = "16"}, ["12"] = {asp = { main = 12, shunt = true }, n = "16"},
["16"] = {asp = { main = {free = true, speed = 16}, shunt = {free = true} }, n = "e"}, ["16"] = {asp = { main = 16, shunt = true }, n = "e"},
-- Speed restriction lifted -- Speed restriction lifted
["e"] = {asp = { main = {free = true, speed = -1}, shunt = {free = true} }, n = "hfs"}, ["e"] = {asp = { main = -1, shunt = true }, n = "hfs"},
-- Halt for shunt moves: -- Halt for shunt moves:
["hfs"] = {asp = { main = {free = true}, shunt = {free = false} }, n = "8"}, ["hfs"] = {asp = { main = false, shunt = false }, n = "pam"},
["pam"] = {asp = { main = -1, shunt = false, proceed_as_main = true}, n = "8"},
}) do }) do
minetest.register_node("advtrains_signals_ks:sign_"..typ.."_"..rot, { minetest.register_node("advtrains_signals_ks:sign_"..typ.."_"..rot, {
description = "Signal Sign", description = "Signal Sign",
@ -211,7 +199,7 @@ for _, rtab in ipairs({
save_in_at_nodedb = 1, save_in_at_nodedb = 1,
not_in_creative_inventory = (rtab.ici and prts.ici) and 0 or 1, not_in_creative_inventory = (rtab.ici and prts.ici) and 0 or 1,
}, },
drop = "advtrains_signals_ks:sign_e_0", drop = "advtrains_signals_ks:sign_8_0",
inventory_image = "advtrains_signals_ks_sign_8.png", inventory_image = "advtrains_signals_ks_sign_8.png",
sounds = default.node_sound_stone_defaults(), sounds = default.node_sound_stone_defaults(),
advtrains = { advtrains = {
@ -253,3 +241,40 @@ for _, rtab in ipairs({
advtrains.trackplacer.add_worked("advtrains_signals_ks:mast","mast", "_"..rot) advtrains.trackplacer.add_worked("advtrains_signals_ks:mast","mast", "_"..rot)
end end
-- Crafting
minetest.register_craft({
output = "advtrains_signals_ks:hs_danger_0 2",
recipe = {
{'default:steel_ingot', 'dye:red', 'default:steel_ingot'},
{'dye:yellow', 'default:steel_ingot', 'dye:dark_green'},
{'default:steel_ingot', 'advtrains_signals_ks:mast_mast_0', 'default:steel_ingot'},
},
})
minetest.register_craft({
output = "advtrains_signals_ks:mast_mast_0 10",
recipe = {
{'default:steel_ingot'},
{'dye:cyan'},
{'default:steel_ingot'},
},
})
minetest.register_craft({
output = "advtrains_signals_ks:ra_danger_0 2",
recipe = {
{'dye:red', 'dye:white', 'dye:red'},
{'dye:white', 'default:steel_ingot', 'default:steel_ingot'},
{'default:steel_ingot', 'advtrains_signals_ks:mast_mast_0', 'default:steel_ingot'},
},
})
minetest.register_craft({
output = "advtrains_signals_ks:sign_8_0 2",
recipe = {
{'basic_materials:plastic_sheet', 'dye:black'},
{'default:stick', ''},
{'default:stick', ''},
},
})

View file

@ -1,5 +1,5 @@
-- Ks Signals for advtrains -- Ks Signals for advtrains
-- will implement the advtrains signal API (which does not exist yet) -- will implement the advtrains signal API
local function place_degrotate(pos, placer, itemstack, pointed_thing) local function place_degrotate(pos, placer, itemstack, pointed_thing)
local yaw = placer:get_look_horizontal() local yaw = placer:get_look_horizontal()

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 B

View file

@ -105,3 +105,45 @@ advtrains.register_wagon("wagon_wood", {
box=8*3, box=8*3,
}, },
}, S("Industrial wood wagon"), "advtrains_wagon_wood_inv.png") }, S("Industrial wood wagon"), "advtrains_wagon_wood_inv.png")
-- Craftings
minetest.register_craft({
output = 'advtrains:engine_industrial',
recipe = {
{'default:steelblock', 'default:steelblock', 'default:steelblock'},
{'advtrains:driver_cab', 'default:steelblock', 'default:steelblock'},
{'advtrains:wheel', '', 'advtrains:wheel'},
},
})
--Engine Industrial Big
minetest.register_craft({
output = 'advtrains:engine_industrial_big',
recipe = {
{'default:glass', 'default:steelblock', 'default:steelblock'},
{'advtrains:driver_cab', 'default:steelblock', 'default:steelblock'},
{'advtrains:wheel', 'advtrains:wheel', 'advtrains:wheel'},
},
})
--Industrial tank wagon
minetest.register_craft({
output = 'advtrains:wagon_tank',
recipe = {
{'default:steelblock', 'default:steel_ingot', 'default:steelblock'},
{'advtrains:steelblock', '', 'default:steelblock'},
{'advtrains:wheel', 'default:steelblock', 'advtrains:wheel'},
},
})
--Industrial wood wagon
minetest.register_craft({
output = 'advtrains:wagon_wood',
recipe = {
{'default:steel_ingot', '', 'default:steel_ingot'},
{'advtrains:steelblock', 'default:steelblock', 'default:steelblock'},
{'advtrains:wheel', '', 'advtrains:wheel'},
},
})

View file

@ -138,3 +138,25 @@ advtrains.register_wagon("wagon_japan", {
drops={"default:steelblock 4"}, drops={"default:steelblock 4"},
}, S("Japanese Train Wagon"), "advtrains_wagon_japan_inv.png") }, S("Japanese Train Wagon"), "advtrains_wagon_japan_inv.png")
-- Crafting
minetest.register_craft({
output = 'advtrains:engine_japan',
recipe = {
{'default:steelblock', 'default:steelblock', ''},
{'xpanes:pane_flat', 'default:steelblock', 'xpanes:pane_flat'},
{'advtrains:wheel', 'advtrains:wheel', 'advtrains:wheel'},
},
})
minetest.register_craft({
output = 'advtrains:wagon_japan',
recipe = {
{'default:steelblock', 'default:steelblock', 'default:steelblock'},
{'xpanes:pane_flat', 'default:steelblock', 'xpanes:pane_flat'},
{'advtrains:wheel', '', 'advtrains:wheel'},
},
})

View file

@ -298,6 +298,29 @@ advtrains.register_tracks("default", {
end end
}, advtrains.trackpresets.t_30deg_straightonly) }, advtrains.trackpresets.t_30deg_straightonly)
minetest.register_craft({
type="shapeless",
output = 'advtrains:dtrack_load_placer',
recipe = {
"advtrains:dtrack_placer",
"basic_materials:ic",
"default:chest"
},
})
minetest.register_craft({
type="shapeless",
output = 'advtrains:dtrack_unload_placer',
recipe = {
"advtrains:dtrack_load_placer",
},
})
minetest.register_craft({
type="shapeless",
output = 'advtrains:dtrack_load_placer',
recipe = {
"advtrains:dtrack_unload_placer",
},
})
if mesecon then if mesecon then
@ -318,10 +341,15 @@ if mesecon then
} }
}, },
advtrains = { advtrains = {
on_updated_from_nodedb = function(pos, node)
mesecon.receptor_off(pos, advtrains.meseconrules)
end,
on_train_enter=function(pos, train_id) on_train_enter=function(pos, train_id)
advtrains.ndb.swap_node(pos, {name="advtrains:dtrack_detector_on".."_"..suffix..rotation, param2=advtrains.ndb.get_node(pos).param2}) advtrains.ndb.swap_node(pos, {name="advtrains:dtrack_detector_on".."_"..suffix..rotation, param2=advtrains.ndb.get_node(pos).param2})
if advtrains.is_node_loaded(pos) then
mesecon.receptor_on(pos, advtrains.meseconrules) mesecon.receptor_on(pos, advtrains.meseconrules)
end end
end
} }
} }
end end
@ -343,10 +371,15 @@ if mesecon then
} }
}, },
advtrains = { advtrains = {
on_updated_from_nodedb = function(pos, node)
mesecon.receptor_on(pos, advtrains.meseconrules)
end,
on_train_leave=function(pos, train_id) on_train_leave=function(pos, train_id)
advtrains.ndb.swap_node(pos, {name="advtrains:dtrack_detector_off".."_"..suffix..rotation, param2=advtrains.ndb.get_node(pos).param2}) advtrains.ndb.swap_node(pos, {name="advtrains:dtrack_detector_off".."_"..suffix..rotation, param2=advtrains.ndb.get_node(pos).param2})
if advtrains.is_node_loaded(pos) then
mesecon.receptor_off(pos, advtrains.meseconrules) mesecon.receptor_off(pos, advtrains.meseconrules)
end end
end
} }
} }
end end

View file

@ -72,12 +72,15 @@ If the train drives in the 'wrong' direction, stop and reverse; independently ac
I<8 S8 ; I<8 S8 ;
If the train is slower than 8, accelerate to 8. If the train is slower than 8, accelerate to 8.
# ATC controller operation modes # Interlocking
static: Only give 1 static command.
mesecon: Give 2 different commands depending on if the controller is mesecon-powered or not With advtrains_interlocking, there's one more available command:
digiline: Don't give any commands by itself. When a train passes, a digiline message in the form of "[+/-][speed]" is sent on the set channel (where +/- means the same as with conditions). Any digiline message sent to the controller will be interpreted as ATC command and sent to the train.
** the latter two are not yet implemented. A0
Disable ARS on the train.
A1
Enable ARS on the train.
When disabled, the train will not trigger automatic route setting on signals based on ARS.
# Persistence # Persistence
ATC controllers that are configured as 'static' or 'mesecon' are persistent over mapblock unloads and will even command the train when the mapblock is unloaded. This is not possible with digilines since these do not work in unloaded mapchunks. ATC controllers that are configured as 'static' or 'mesecon' are persistent over mapblock unloads and will even command the train when the mapblock is unloaded. This is not possible with digilines since these do not work in unloaded mapchunks.

View file

@ -125,7 +125,7 @@ local get_ambience = function(player, tod, name)
-- get foot and head level nodes at player position -- get foot and head level nodes at player position
local pos = player:get_pos() local pos = player:get_pos() ; if not pos then return end
pos.y = pos.y + 1.4 -- head level pos.y = pos.y + 1.4 -- head level

View file

@ -113,7 +113,7 @@ for _, nodeclass in ipairs(NodeClass) do
paramtype = "light", paramtype = "light",
paramtype2 = "color", paramtype2 = "color",
is_ground_content = true, is_ground_content = true,
groups = {cracky=3, ud_param2_colorable = 1}, groups = {cracky=3, stone=1, ud_param2_colorable = 1},
sounds = default.node_sound_stone_defaults(), sounds = default.node_sound_stone_defaults(),
on_construct = unifieddyes.on_construct, on_construct = unifieddyes.on_construct,
on_dig = unifieddyes.on_dig on_dig = unifieddyes.on_dig
@ -131,7 +131,7 @@ for _, nodeclass in ipairs(NodeClass) do
paramtype = "light", paramtype = "light",
paramtype2 = "color", paramtype2 = "color",
is_ground_content = true, is_ground_content = true,
groups = {cracky=3, ud_param2_colorable = 1}, groups = {cracky=3, stone=2, ud_param2_colorable = 1},
sounds = default.node_sound_stone_defaults(), sounds = default.node_sound_stone_defaults(),
on_construct = unifieddyes.on_construct, on_construct = unifieddyes.on_construct,
on_dig = unifieddyes.on_dig on_dig = unifieddyes.on_dig
@ -149,7 +149,7 @@ for _, nodeclass in ipairs(NodeClass) do
paramtype = "light", paramtype = "light",
paramtype2 = "color", paramtype2 = "color",
is_ground_content = true, is_ground_content = true,
groups = {snappy=2,choppy=2,oddly_breakable_by_hand=2,flammable=3, ud_param2_colorable = 1}, groups = {snappy=2, choppy=2, wood=1, oddly_breakable_by_hand=2,flammable=3, ud_param2_colorable = 1},
sounds = default.node_sound_wood_defaults(), sounds = default.node_sound_wood_defaults(),
on_construct = unifieddyes.on_construct, on_construct = unifieddyes.on_construct,
on_dig = unifieddyes.on_dig on_dig = unifieddyes.on_dig
@ -169,7 +169,7 @@ minetest.register_node("blox:wood_tinted", {
paramtype = "light", paramtype = "light",
paramtype2 = "color", paramtype2 = "color",
is_ground_content = true, is_ground_content = true,
groups = {snappy=2,choppy=2,oddly_breakable_by_hand=2,flammable=3, ud_param2_colorable = 1}, groups = {snappy=2, choppy=2, wood=1, oddly_breakable_by_hand=2,flammable=3, ud_param2_colorable = 1},
sounds = default.node_sound_wood_defaults(), sounds = default.node_sound_wood_defaults(),
on_construct = unifieddyes.on_construct, on_construct = unifieddyes.on_construct,
on_dig = unifieddyes.on_dig on_dig = unifieddyes.on_dig
@ -182,7 +182,7 @@ minetest.register_node("blox:stone_square", {
paramtype = "light", paramtype = "light",
paramtype2 = "color", paramtype2 = "color",
is_ground_content = true, is_ground_content = true,
groups = {snappy=2,choppy=2,oddly_breakable_by_hand=2,flammable=3, ud_param2_colorable = 1}, groups = {snappy=2, choppy=2, stone=1, oddly_breakable_by_hand=2,flammable=3, ud_param2_colorable = 1},
sounds = default.node_sound_wood_defaults(), sounds = default.node_sound_wood_defaults(),
on_construct = unifieddyes.on_construct, on_construct = unifieddyes.on_construct,
on_dig = unifieddyes.on_dig on_dig = unifieddyes.on_dig
@ -195,7 +195,7 @@ minetest.register_node("blox:cobble_tinted", {
paramtype = "light", paramtype = "light",
paramtype2 = "color", paramtype2 = "color",
is_ground_content = true, is_ground_content = true,
groups = {snappy=2,choppy=2,oddly_breakable_by_hand=2,flammable=3, not_in_creative_inventory = 1, ud_param2_colorable = 1}, groups = {snappy=2, choppy=2, stone=2, oddly_breakable_by_hand=2,flammable=3, not_in_creative_inventory = 1, ud_param2_colorable = 1},
sounds = default.node_sound_wood_defaults(), sounds = default.node_sound_wood_defaults(),
on_construct = unifieddyes.on_construct, on_construct = unifieddyes.on_construct,
on_dig = unifieddyes.on_dig on_dig = unifieddyes.on_dig
@ -208,7 +208,7 @@ minetest.register_node("blox:stone_tinted", {
paramtype = "light", paramtype = "light",
paramtype2 = "color", paramtype2 = "color",
is_ground_content = true, is_ground_content = true,
groups = {snappy=2,choppy=2,oddly_breakable_by_hand=2,flammable=3, not_in_creative_inventory = 1, ud_param2_colorable = 1}, groups = {snappy=2, choppy=2, stone=1, oddly_breakable_by_hand=2,flammable=3, not_in_creative_inventory = 1, ud_param2_colorable = 1},
sounds = default.node_sound_wood_defaults(), sounds = default.node_sound_wood_defaults(),
on_construct = unifieddyes.on_construct, on_construct = unifieddyes.on_construct,
on_dig = unifieddyes.on_dig, on_dig = unifieddyes.on_dig,

View file

@ -63,24 +63,22 @@ local dry_grass = {
"default:dry_grass_5", "", "" "default:dry_grass_5", "", ""
} }
-- add all in-game flowers except waterlily -- loads mods then add all in-game flowers except waterlily
local flowers = {} local flowers = {}
minetest.after(0.1, function()
for node, def in pairs(minetest.registered_nodes) do for node, def in pairs(minetest.registered_nodes) do
if def.groups.flower and not node:find("waterlily") then if def.groups
and def.groups.flower
and not node:find("waterlily")
and not node:find("xdecor:potted_") then
flowers[#flowers + 1] = node flowers[#flowers + 1] = node
end end
end end
end)
-- add additional bakedclay flowers if enabled
if minetest.get_modpath("bakedclay") then
flowers[#flowers + 1] = "bakedclay:delphinium"
flowers[#flowers + 1] = "bakedclay:thistle"
flowers[#flowers + 1] = "bakedclay:lazarus"
flowers[#flowers + 1] = "bakedclay:mannagrass"
flowers[#flowers + 1] = ""
end
-- default biomes deco -- default biomes deco
local deco = { local deco = {

View file

@ -4,7 +4,7 @@ edited by TenPlus1
Features: Features:
- Items are destroyed by lava - Items are destroyed by lava
- Items are pushed along by flowing water (thanks to QwertyMine3) - Items are pushed along by flowing water (thanks to QwertyMine3 and Gustavo6046)
- Items are removed after 900 seconds or the time that is specified by - Items are removed after 900 seconds or the time that is specified by
remove_items in minetest.conf (-1 disables it) remove_items in minetest.conf (-1 disables it)
- Particle effects added - Particle effects added

View file

@ -1,8 +1,10 @@
-- Minetest: builtin/item_entity.lua -- Minetest: builtin/item_entity.lua
-- override ice to make slippery for 0.4.16 -- override ice to make slippery for 0.4.16
if not minetest.raycast then
minetest.override_item("default:ice", { minetest.override_item("default:ice", {
groups = {cracky = 3, puts_out_fire = 1, cools_lava = 1, slippery = 3}}) groups = {cracky = 3, puts_out_fire = 1, cools_lava = 1, slippery = 3}})
end
function core.spawn_item(pos, item) function core.spawn_item(pos, item)
@ -20,30 +22,37 @@ end
-- If item_entity_ttl is not set, enity will have default life time -- If item_entity_ttl is not set, enity will have default life time
-- Setting it to -1 disables the feature -- Setting it to -1 disables the feature
local time_to_live = tonumber(core.settings:get("item_entity_ttl")) or 900 local time_to_live = tonumber(core.settings:get("item_entity_ttl")) or 900
local gravity = tonumber(core.settings:get("movement_gravity")) or 9.81 local gravity = tonumber(core.settings:get("movement_gravity")) or 9.81
local destroy_item = core.settings:get_bool("destroy_item") ~= false local destroy_item = core.settings:get_bool("destroy_item") ~= false
-- water flow functions by QwertyMine3, edited by TenPlus1 -- localize some math functions
local abs = math.abs
local sqrt = math.sqrt
-- water flow functions by QwertyMine3, edited by TenPlus1 and Gustavo6046
local inv_roots = {[0] = 1}
local function to_unit_vector(dir_vector) local function to_unit_vector(dir_vector)
local inv_roots = {
[0] = 1,
[1] = 1,
[2] = 0.70710678118655,
[4] = 0.5,
[5] = 0.44721359549996,
[8] = 0.35355339059327
}
local sum = dir_vector.x * dir_vector.x + dir_vector.z * dir_vector.z local sum = dir_vector.x * dir_vector.x + dir_vector.z * dir_vector.z
local invr_sum
-- find inverse square root if possible
if inv_roots[sum] ~= nil then
invr_sum = inv_roots[sum]
else
-- not found, compute and save the inverse square root
invr_sum = 1.0 / sqrt(sum)
inv_roots[sum] = invr_sum
end
return { return {
x = dir_vector.x * inv_roots[sum], x = dir_vector.x * invr_sum,
y = dir_vector.y, y = dir_vector.y,
z = dir_vector.z * inv_roots[sum] z = dir_vector.z * invr_sum
} }
end end
@ -63,6 +72,11 @@ end
local function quick_flow_logic(node, pos_testing, direction) local function quick_flow_logic(node, pos_testing, direction)
local node_testing = node_ok(pos_testing) local node_testing = node_ok(pos_testing)
local param2 = node.param2
if not minetest.registered_nodes[node.name].groups.liquid then
param2 = 0
end
if minetest.registered_nodes[node_testing.name].liquidtype ~= "flowing" if minetest.registered_nodes[node_testing.name].liquidtype ~= "flowing"
and minetest.registered_nodes[node_testing.name].liquidtype ~= "source" then and minetest.registered_nodes[node_testing.name].liquidtype ~= "source" then
@ -71,17 +85,17 @@ local function quick_flow_logic(node, pos_testing, direction)
local param2_testing = node_testing.param2 local param2_testing = node_testing.param2
if param2_testing < node.param2 then if param2_testing < param2 then
if (node.param2 - param2_testing) > 6 then if (param2 - param2_testing) > 6 then
return -direction return -direction
else else
return direction return direction
end end
elseif param2_testing > node.param2 then elseif param2_testing > param2 then
if (param2_testing - node.param2) > 6 then if (param2_testing - param2) > 6 then
return direction return direction
else else
return -direction return -direction
@ -94,11 +108,7 @@ end
local function quick_flow(pos, node) local function quick_flow(pos, node)
if not minetest.registered_nodes[node.name].groups.liquid then local x, z = 0.0, 0.0
return {x = 0, y = 0, z = 0}
end
local x, z = 0, 0
x = x + quick_flow_logic(node, {x = pos.x - 1, y = pos.y, z = pos.z},-1) x = x + quick_flow_logic(node, {x = pos.x - 1, y = pos.y, z = pos.z},-1)
x = x + quick_flow_logic(node, {x = pos.x + 1, y = pos.y, z = pos.z}, 1) x = x + quick_flow_logic(node, {x = pos.x + 1, y = pos.y, z = pos.z}, 1)
@ -107,7 +117,6 @@ local function quick_flow(pos, node)
return to_unit_vector({x = x, y = 0, z = z}) return to_unit_vector({x = x, y = 0, z = z})
end end
-- END water flow functions
-- particle effects for when item is destroyed -- particle effects for when item is destroyed
@ -130,6 +139,19 @@ local function add_effects(pos)
}) })
end end
-- print vector, helpful when debugging
local function vec_print(head, vec)
print(head, vec.x, vec.y, vec.z)
end
local water_force = tonumber(minetest.settings:get("builtin_item.waterflow_force") or 1.6)
local water_drag = tonumber(minetest.settings:get("builtin_item.waterflow_drag") or 0.8)
local dry_friction = tonumber(minetest.settings:get("builtin_item.friction_dry") or 2.5)
local air_drag = tonumber(minetest.settings:get("builtin_item.air_drag") or 0.4)
local items_collect_on_slippery = tonumber(
minetest.settings:get("builtin_item.items_collect_on_slippery") or 1) ~= 0
core.register_entity(":__builtin:item", { core.register_entity(":__builtin:item", {
@ -144,14 +166,17 @@ core.register_entity(":__builtin:item", {
spritediv = {x = 1, y = 1}, spritediv = {x = 1, y = 1},
initial_sprite_basepos = {x = 0, y = 0}, initial_sprite_basepos = {x = 0, y = 0},
is_visible = false, is_visible = false,
infotext = "", infotext = ""
}, },
itemstring = "", itemstring = "",
moving_state = true, falling_state = true,
slippery_state = false, slippery_state = false,
waterflow_state = false,
age = 0, age = 0,
accel = {x = 0, y = 0, z = 0},
set_item = function(self, item) set_item = function(self, item)
local stack = ItemStack(item or self.itemstring) local stack = ItemStack(item or self.itemstring)
@ -177,6 +202,7 @@ core.register_entity(":__builtin:item", {
end end
local name1 = stack:get_meta():get_string("description") local name1 = stack:get_meta():get_string("description")
local name
if name1 == "" then if name1 == "" then
name = core.registered_items[itemname].description name = core.registered_items[itemname].description
@ -184,11 +210,14 @@ core.register_entity(":__builtin:item", {
name = name1 name = name1
end end
-- small random size bias to counter Z-fighting
local bias = math.random() * 1e-3
self.object:set_properties({ self.object:set_properties({
is_visible = true, is_visible = true,
visual = "wielditem", visual = "wielditem",
textures = {itemname}, textures = {itemname},
visual_size = {x = size, y = size}, visual_size = {x = size + bias, y = size + bias, z = size + bias},
collisionbox = {-size, -col_height, -size, size, col_height, size}, collisionbox = {-size, -col_height, -size, size, col_height, size},
selectionbox = {-size, -size, -size, size, size, size}, selectionbox = {-size, -size, -size, size, size, size},
automatic_rotate = 0.314 / size, automatic_rotate = 0.314 / size,
@ -201,11 +230,13 @@ core.register_entity(":__builtin:item", {
get_staticdata = function(self) get_staticdata = function(self)
return core.serialize({ local data = {
itemstring = self.itemstring, itemstring = self.itemstring,
age = self.age, age = self.age,
dropped_by = self.dropped_by dropped_by = self.dropped_by
}) }
return core.serialize(data)
end, end,
on_activate = function(self, staticdata, dtime_s) on_activate = function(self, staticdata, dtime_s)
@ -224,8 +255,6 @@ core.register_entity(":__builtin:item", {
end end
self.object:set_armor_groups({immortal = 1}) self.object:set_armor_groups({immortal = 1})
self.object:set_velocity({x = 0, y = 2, z = 0})
self.object:set_acceleration({x = 0, y = -gravity, z = 0})
self:set_item() self:set_item()
end, end,
@ -255,11 +284,23 @@ core.register_entity(":__builtin:item", {
-- Merge the remote stack into this one -- Merge the remote stack into this one
local pos = object:get_pos() local pos = object:get_pos()
pos.y = pos.y + ((total_count - count) / max_count) * 0.15 pos.y = pos.y + ((total_count - count) / max_count) * 0.15
self.object:move_to(pos) self.object:move_to(pos)
self.age = 0 -- Handle as new entity self.age = 0 -- Handle as new entity
-- Merge velocities
local vel_a = self.object:get_velocity()
local vel_b = object:get_velocity()
self.object:set_velocity({
x = (vel_a.x + vel_b.x) / 2,
y = (vel_a.y + vel_b.y) / 2,
z = (vel_a.z + vel_b.z) / 2
})
-- Merge stacks
own_stack:set_count(total_count) own_stack:set_count(total_count)
self:set_item(own_stack) self:set_item(own_stack)
@ -269,22 +310,10 @@ core.register_entity(":__builtin:item", {
return true return true
end, end,
on_step = function(self, dtime) step_update_node_state = function(self, moveresult, dtime)
local pos = self.object:get_pos() local pos = self.object:get_pos()
self.age = self.age + dtime
if time_to_live > 0 and self.age > time_to_live then
self.itemstring = ""
self.object:remove()
add_effects(pos)
return
end
-- get nodes every 1/4 second -- get nodes every 1/4 second
self.timer = (self.timer or 0) + dtime self.timer = (self.timer or 0) + dtime
@ -294,18 +323,36 @@ core.register_entity(":__builtin:item", {
self.def_inside = self.node_inside self.def_inside = self.node_inside
and core.registered_nodes[self.node_inside.name] and core.registered_nodes[self.node_inside.name]
self.node_under = minetest.get_node_or_nil({ -- get ground node for collision
x = pos.x, self.node_under = nil
y = pos.y + self.object:get_properties().collisionbox[2] - 0.05, self.falling_state = true
z = pos.z
}) if moveresult.touching_ground then
for _, info in ipairs(moveresult.collisions) do
if info.axis == "y" then
self.node_under = core.get_node(info.node_pos)
self.falling_state = false
break
end
end
end
self.def_under = self.node_under self.def_under = self.node_under
and core.registered_nodes[self.node_under.name] and core.registered_nodes[self.node_under.name]
self.timer = 0 self.timer = 0
end end
end,
step_node_inside_checks = function(self)
local pos = self.object:get_pos()
local node = self.node_inside local node = self.node_inside
local def = self.def_inside
-- Delete in 'ignore' nodes -- Delete in 'ignore' nodes
if node and node.name == "ignore" then if node and node.name == "ignore" then
@ -313,23 +360,22 @@ core.register_entity(":__builtin:item", {
self.itemstring = "" self.itemstring = ""
self.object:remove() self.object:remove()
return return true
end end
-- do custom step function -- item inside block, move to vacant space
local name = ItemStack(self.itemstring):get_name() or "" if def and (def.walkable == nil or def.walkable == true)
local custom = core.registered_items[name] and (def.collision_box == nil or def.collision_box.type == "regular")
and core.registered_items[name].dropped_step and (def.node_box == nil or def.node_box.type == "regular") then
if custom and custom(self, pos, dtime) == false then local npos = minetest.find_node_near(pos, 1, "air")
return -- skip further checks if false
if npos then
self.object:move_to(npos)
end end
local vel = self.object:get_velocity() self.node_inside = nil -- force get_node
local def = self.def_inside end
local is_slippery = false
local is_moving = (def and not def.walkable) or
vel.x ~= 0 or vel.y ~= 0 or vel.z ~= 0
-- destroy item when dropped into lava (if enabled) -- destroy item when dropped into lava (if enabled)
if destroy_item and def and def.groups and def.groups.lava then if destroy_item and def and def.groups and def.groups.lava then
@ -345,81 +391,156 @@ core.register_entity(":__builtin:item", {
add_effects(pos) add_effects(pos)
return true
end
return false
end,
step_check_slippery = function(self)
-- don't check for slippery ground if we're not on
-- any ground to begin with
local node = self.node_under
local def = self.def_under
if self.falling_state or not node then
self.slippery_state = false
return return
end end
-- water flowing if node and def and def.walkable then
if def and def.liquidtype == "flowing" then
local vec = quick_flow(pos, node)
local v = self.object:get_velocity()
self.object:set_velocity({x = vec.x, y = v.y, z = vec.z})
return
end
-- item inside block, move to vacant space
if def and (def.walkable == nil or def.walkable == true)
and (def.collision_box == nil or def.collision_box.type == "regular")
and (def.node_box == nil or def.node_box.type == "regular") then
local npos = minetest.find_node_near(pos, 1, "air")
if npos then
self.object:move_to(npos)
end
self.node_inside = nil -- force get_node
return
end
-- Switch locals to node under
node = self.node_under
def = self.def_under
-- Slippery node check
if def and def.walkable then
local slippery = core.get_item_group(node.name, "slippery") local slippery = core.get_item_group(node.name, "slippery")
is_slippery = slippery ~= 0 self.slippery_state = slippery ~= 0
end
end,
if is_slippery and (math.abs(vel.x) > 0.2 or math.abs(vel.z) > 0.2) then step_water_physics = function(self)
-- Horizontal deceleration local pos = self.object:get_pos()
local vel = self.object:get_velocity()
local node = self.node_inside
local def = self.def_inside
self.waterflow_state = def and def.liquidtype == "flowing"
if self.waterflow_state then
-- get flow velocity
local flow_vel = quick_flow(pos, node)
-- calculate flow force and drag
local flow_force_x = flow_vel.x * water_force
local flow_force_z = flow_vel.z * water_force
local flow_drag_x = (flow_force_x - vel.x) * water_drag
local flow_drag_z = (flow_force_z - vel.z) * water_drag
-- apply water force and friction
self.accel.x = self.accel.x + flow_force_x + flow_drag_x
self.accel.z = self.accel.z + flow_force_z + flow_drag_z
end
end,
step_air_drag_physics = function(self)
local vel = self.object:get_velocity()
-- apply air drag
if self.falling_state or (self.slippery_state and not self.waterflow_state) then
self.accel.x = self.accel.x - vel.x * air_drag
self.accel.z = self.accel.z - vel.z * air_drag
end
end,
step_gravity = function(self)
if self.falling_state then
self.accel.y = self.accel.y - gravity
end
end,
step_ground_friction = function(self)
-- don't apply ground friction when falling!
if self.falling_state then
return
end
local vel = self.object:get_velocity()
if self.slippery_state then
local node = self.node_under
-- apply slip factor (tiny friction that depends on the actual block type)
if (abs(vel.x) > 0.2 or abs(vel.z) > 0.2) then
local slippery = core.get_item_group(node.name, "slippery")
local slip_factor = 4.0 / (slippery + 4) local slip_factor = 4.0 / (slippery + 4)
self.object:set_acceleration({ self.accel.x = self.accel.x - vel.x * slip_factor
x = -vel.x * slip_factor, self.accel.z = self.accel.z - vel.z * slip_factor
y = 0,
z = -vel.z * slip_factor
})
elseif vel.y == 0 then
is_moving = false
end
end end
if self.moving_state == is_moving
and self.slippery_state == is_slippery then
return -- No further updates until moving state changes
end
self.moving_state = is_moving
self.slippery_state = is_slippery
if is_moving then
self.object:set_acceleration({x = 0, y = -gravity, z = 0})
else else
self.object:set_acceleration({x = 0, y = 0, z = 0}) self.accel.x = self.accel.x - vel.x * dry_friction
self.object:set_velocity({x = 0, y = 0, z = 0}) self.accel.z = self.accel.z - vel.z * dry_friction
end
end,
step_apply_forces = function(self)
self.object:set_acceleration(self.accel)
end,
step_check_timeout = function(self, dtime)
local pos = self.object:get_pos()
self.age = self.age + dtime
if time_to_live > 0 and self.age > time_to_live then
self.itemstring = ""
self.object:remove()
add_effects(pos)
return true
end end
--Only collect items if not moving return false
if is_moving then end,
step_check_custom_step = function(self, dtime, moveresult)
local pos = self.object:get_pos()
-- do custom step function
local name = ItemStack(self.itemstring):get_name() or ""
local custom = core.registered_items[name]
and core.registered_items[name].dropped_step
if custom and custom(self, pos, dtime, moveresult) == false then
return true -- skip further checks if false
end
return false
end,
step_try_collect = function(self)
local self_pos = self.object:get_pos()
-- Don't collect items if falling
if self.falling_state then
return
end
-- Check if we should collect items while sliding
if self.slippery_state and not items_collect_on_slippery then
return return
end end
@ -430,16 +551,19 @@ core.register_entity(":__builtin:item", {
return return
end end
local objects = core.get_objects_inside_radius(pos, 1.0) local objects = core.get_objects_inside_radius(self_pos, 1.0)
for k, obj in pairs(objects) do for _, obj in pairs(objects) do
local entity = obj:get_luaentity() local entity = obj:get_luaentity()
if entity and entity.name == "__builtin:item" then if entity and entity.name == "__builtin:item" and not entity.is_falling then
if self:try_merge_with(own_stack, obj, entity) then if self:try_merge_with(own_stack, obj, entity) then
-- item will be moved up due to try_merge_with
self.falling_state = true
own_stack = ItemStack(self.itemstring) own_stack = ItemStack(self.itemstring)
if own_stack:get_free_space() == 0 then if own_stack:get_free_space() == 0 then
@ -450,6 +574,41 @@ core.register_entity(":__builtin:item", {
end end
end, end,
on_step = function(self, dtime, moveresult)
-- reset acceleration
self.accel = {x = 0, y = 0, z = 0}
-- check item timeout
if self:step_check_timeout(dtime) then
return -- deleted, stop here
end
-- check custom step function
if self:step_check_custom_step(dtime, moveresult) then
return -- overriden
end
-- do general checks
self:step_update_node_state(moveresult, dtime)
if self:step_node_inside_checks() then
return -- destroyed
end
self:step_check_slippery()
-- do physics checks, then apply
self:step_water_physics()
self:step_ground_friction()
self:step_gravity()
self:step_apply_forces()
-- do item checks
self:step_try_collect()
end,
on_punch = function(self, hitter) on_punch = function(self, hitter)
local inv = hitter:get_inventory() local inv = hitter:get_inventory()

541
mods/builtin_item/init.lua_ Normal file
View file

@ -0,0 +1,541 @@
-- Minetest: builtin/item_entity.lua
-- override ice to make slippery for 0.4.16
if not minetest.raycast then
minetest.override_item("default:ice", {
groups = {cracky = 3, puts_out_fire = 1, cools_lava = 1, slippery = 3}})
end
function core.spawn_item(pos, item)
local stack = ItemStack(item)
local obj = core.add_entity(pos, "__builtin:item")
if obj then
obj:get_luaentity():set_item(stack:to_string())
end
return obj
end
-- If item_entity_ttl is not set, enity will have default life time
-- Setting it to -1 disables the feature
local time_to_live = tonumber(core.settings:get("item_entity_ttl")) or 900
local gravity = tonumber(core.settings:get("movement_gravity")) or 9.81
local destroy_item = core.settings:get_bool("destroy_item") ~= false
-- water flow functions by QwertyMine3, edited by TenPlus1
local inv_roots = {
[0] = 1
}
local function to_unit_vector(dir_vector)
local sum = dir_vector.x * dir_vector.x + dir_vector.z * dir_vector.z
local invr_sum = 0
-- find inverse square root if possible
if inv_roots[sum] ~= nil then
invr_sum = inv_roots[sum]
else
-- not found, compute and save the inverse square root
invr_sum = 1.0 / math.sqrt(sum)
inv_roots[sum] = invr_sum
end
return {
x = dir_vector.x * invr_sum,
y = dir_vector.y,
z = dir_vector.z * invr_sum
}
end
local function node_ok(pos)
local node = minetest.get_node_or_nil(pos)
if node and minetest.registered_nodes[node.name] then
return node
end
return minetest.registered_nodes["default:dirt"]
end
local function quick_flow_logic(node, pos_testing, direction)
local node_testing = node_ok(pos_testing)
local param2 = node.param2
if not minetest.registered_nodes[node.name].groups.liquid then
param2 = 0
end
if minetest.registered_nodes[node_testing.name].liquidtype ~= "flowing"
and minetest.registered_nodes[node_testing.name].liquidtype ~= "source" then
return 0
end
local param2_testing = node_testing.param2
if param2_testing < param2 then
if (param2 - param2_testing) > 6 then
return -direction
else
return direction
end
elseif param2_testing > param2 then
if (param2_testing - param2) > 6 then
return direction
else
return -direction
end
end
return 0
end
-- reciprocal of the length of an unit square's diagonal
local DIAG_WEIGHT = 2 / math.sqrt(2)
local function quick_flow(pos, node)
local x, z = 0.0, 0.0
x = x + quick_flow_logic(node, {x = pos.x - 1, y = pos.y, z = pos.z},-1)
x = x + quick_flow_logic(node, {x = pos.x + 1, y = pos.y, z = pos.z}, 1)
z = z + quick_flow_logic(node, {x = pos.x, y = pos.y, z = pos.z - 1},-1)
z = z + quick_flow_logic(node, {x = pos.x, y = pos.y, z = pos.z + 1}, 1)
return to_unit_vector({x = x, y = 0, z = z})
end
-- END water flow functions
-- particle effects for when item is destroyed
local function add_effects(pos)
minetest.add_particlespawner({
amount = 1,
time = 0.25,
minpos = pos,
maxpos = pos,
minvel = {x = -1, y = 2, z = -1},
maxvel = {x = 1, y = 4, z = 1},
minacc = {x = 0, y = 0, z = 0},
maxacc = {x = 0, y = 0, z = 0},
minexptime = 1,
maxexptime = 3,
minsize = 1,
maxsize = 4,
texture = "tnt_smoke.png",
})
end
local water_force = 0.8
local water_friction = 0.8
local dry_friction = 2.5
core.register_entity(":__builtin:item", {
initial_properties = {
hp_max = 1,
physical = true,
collide_with_objects = false,
collisionbox = {-0.3, -0.3, -0.3, 0.3, 0.3, 0.3},
visual = "wielditem",
visual_size = {x = 0.4, y = 0.4},
textures = {""},
spritediv = {x = 1, y = 1},
initial_sprite_basepos = {x = 0, y = 0},
is_visible = false,
infotext = "",
},
itemstring = "",
moving_state = true,
slippery_state = false,
age = 0,
set_item = function(self, item)
local stack = ItemStack(item or self.itemstring)
self.itemstring = stack:to_string()
if self.itemstring == "" then
return
end
local itemname = stack:is_known() and stack:get_name() or "unknown"
local max_count = stack:get_stack_max()
local count = math.min(stack:get_count(), max_count)
local size = 0.2 + 0.1 * (count / max_count) ^ (1 / 3)
local col_height = size * 0.75
local def = core.registered_nodes[itemname]
local glow = def and def.light_source
local c1, c2 = "",""
if not(stack:get_count() == 1) then
c1 = " x"..tostring(stack:get_count())
c2 = " "..tostring(stack:get_count())
end
local name1 = stack:get_meta():get_string("description")
local name
if name1 == "" then
name = core.registered_items[itemname].description
else
name = name1
end
self.object:set_properties({
is_visible = true,
visual = "wielditem",
textures = {itemname},
visual_size = {x = size, y = size},
collisionbox = {-size, -col_height, -size, size, col_height, size},
selectionbox = {-size, -size, -size, size, size, size},
automatic_rotate = 0.314 / size,
wield_item = self.itemstring,
glow = glow,
infotext = name .. c1 .. "\n(" .. itemname .. c2 .. ")"
})
end,
get_staticdata = function(self)
return core.serialize({
itemstring = self.itemstring,
age = self.age,
dropped_by = self.dropped_by
})
end,
on_activate = function(self, staticdata, dtime_s)
if string.sub(staticdata, 1, string.len("return")) == "return" then
local data = core.deserialize(staticdata)
if data and type(data) == "table" then
self.itemstring = data.itemstring
self.age = (data.age or 0) + dtime_s
self.dropped_by = data.dropped_by
end
else
self.itemstring = staticdata
end
self.object:set_armor_groups({immortal = 1})
self.object:set_velocity({x = 0, y = 2, z = 0})
self.object:set_acceleration({x = 0, y = -gravity, z = 0})
self:set_item()
end,
try_merge_with = function(self, own_stack, object, entity)
if self.age == entity.age then
return false -- Can not merge with itself
end
local stack = ItemStack(entity.itemstring)
local name = stack:get_name()
if own_stack:get_name() ~= name
or own_stack:get_meta() ~= stack:get_meta()
or own_stack:get_wear() ~= stack:get_wear()
or own_stack:get_free_space() == 0 then
return false -- Can not merge different or full stack
end
local count = own_stack:get_count()
local total_count = stack:get_count() + count
local max_count = stack:get_stack_max()
if total_count > max_count then
return false
end
-- Merge the remote stack into this one
local pos = object:get_pos()
pos.y = pos.y + ((total_count - count) / max_count) * 0.15
self.object:move_to(pos)
self.age = 0 -- Handle as new entity
own_stack:set_count(total_count)
self:set_item(own_stack)
entity.itemstring = ""
object:remove()
return true
end,
on_step = function(self, dtime, moveresult)
local pos = self.object:get_pos()
self.age = self.age + dtime
if time_to_live > 0 and self.age > time_to_live then
self.itemstring = ""
self.object:remove()
add_effects(pos)
return
end
-- get nodes every 1/4 second
self.timer = (self.timer or 0) + dtime
if self.timer > 0.25 or not self.node_inside then
self.node_inside = minetest.get_node_or_nil(pos)
self.def_inside = self.node_inside
and core.registered_nodes[self.node_inside.name]
-- get ground node for collision
self.node_under = nil
if moveresult.touching_ground then
for _, info in ipairs(moveresult.collisions) do
if info.axis == "y" then
self.node_under = core.get_node(info.node_pos)
break
end
end
end
self.def_under = self.node_under
and core.registered_nodes[self.node_under.name]
self.timer = 0
end
local node = self.node_inside
-- Delete in 'ignore' nodes
if node and node.name == "ignore" then
self.itemstring = ""
self.object:remove()
return
end
-- do custom step function
local name = ItemStack(self.itemstring):get_name() or ""
local custom = core.registered_items[name]
and core.registered_items[name].dropped_step
if custom and custom(self, pos, dtime) == false then
return -- skip further checks if false
end
local vel = self.object:get_velocity()
local def = self.def_inside
local is_slippery = false
local is_moving = (def and not def.walkable) or
vel.x ~= 0 or vel.y ~= 0 or vel.z ~= 0
-- destroy item when dropped into lava (if enabled)
if destroy_item and def and def.groups and def.groups.lava then
minetest.sound_play("builtin_item_lava", {
pos = pos,
max_hear_distance = 6,
gain = 0.5
})
self.itemstring = ""
self.object:remove()
add_effects(pos)
return
end
-- water flowing
if def and def.liquidtype == "flowing" then
-- force applies on acceleration over time, thus multiply
local force = water_force * dtime
-- friction applies on velocity over time, thus exponentiate
local friction = (1.0 + water_friction) ^ dtime
-- get flow velocity and current vel/acc state
local vec = quick_flow(pos, node)
local a = self.object:get_acceleration()
self.object:set_acceleration({
x = a.x + vec.x * force,
y = a.y,
z = a.z + vec.z * force
})
-- apply friction to prevent items going too fast, and also to make
-- water flow override previous horizontal momentum more quickly
local v = self.object:get_velocity()
-- adjust friction for going against the current
local v_horz = { x = v.x, y = 0, z = v.z }
local v_dir = to_unit_vector(v_horz)
local flow_dot = v_dir.x * vec.x + v_dir.y * vec.y
-- also maps flow_dot from [-1,0] to [0.5,2.5]
friction = 1.0 + ((friction - 1.0) * (flow_dot + 1.5))
self.object:set_velocity({
x = v.x / friction,
y = v.y / friction,
z = v.z / friction
})
return
end
-- item inside block, move to vacant space
if def and (def.walkable == nil or def.walkable == true)
and (def.collision_box == nil or def.collision_box.type == "regular")
and (def.node_box == nil or def.node_box.type == "regular") then
local npos = minetest.find_node_near(pos, 1, "air")
if npos then
self.object:move_to(npos)
end
self.node_inside = nil -- force get_node
return
end
-- Switch locals to node under
node = self.node_under
def = self.def_under
-- Slippery node check
if def and def.walkable then
local slippery = core.get_item_group(node.name, "slippery")
is_slippery = slippery ~= 0
if is_slippery and (math.abs(vel.x) > 0.2 or math.abs(vel.z) > 0.2) then
-- Horizontal deceleration
local slip_factor = 4.0 / (slippery + 4)
self.object:set_acceleration({
x = -vel.x * slip_factor,
y = 0,
z = -vel.z * slip_factor
})
elseif vel.y == 0 then
is_moving = false
end
end
if self.moving_state == is_moving
and self.slippery_state == is_slippery then
return -- No further updates until moving state changes
end
self.moving_state = is_moving
self.slippery_state = is_slippery
local a_curr = self.object:get_acceleration()
local v_curr = self.object:get_velocity()
if is_moving then
self.object:set_acceleration({
x = a_curr.x,
y = a_curr.y - gravity,
z = a_curr.z
})
else
self.object:set_acceleration({x = 0, y = 0, z = 0})
-- preserve *some* velocity so items don't get stuck on the very ledges
-- of nodes once they move just enough to leave the hitbox of flowing water
self.object:set_velocity({
x = v_curr.x / dry_friction,
y = v_curr.y / dry_friction,
z = v_curr.z / dry_friction
})
end
--Only collect items if not moving
if is_moving then
return
end
-- Collect the items around to merge with
local own_stack = ItemStack(self.itemstring)
if own_stack:get_free_space() == 0 then
return
end
local objects = core.get_objects_inside_radius(pos, 1.0)
for k, obj in pairs(objects) do
local entity = obj:get_luaentity()
if entity and entity.name == "__builtin:item" then
if self:try_merge_with(own_stack, obj, entity) then
own_stack = ItemStack(self.itemstring)
if own_stack:get_free_space() == 0 then
return
end
end
end
end
end,
on_punch = function(self, hitter)
local inv = hitter:get_inventory()
if inv and self.itemstring ~= "" then
local left = inv:add_item("main", self.itemstring)
if left and not left:is_empty() then
self:set_item(left)
return
end
end
self.itemstring = ""
self.object:remove()
end,
})

View file

@ -0,0 +1,40 @@
# Controls how much force should be exerted on
# dropped items when they are pushed by flowing water.
#
# The larger this number, the faster items become when
# carried by water.
waterflow_force (Force of water flow on dropped items) float 1.6
# Controls how much drag force should be exerted on
# dropped items when they are submerged in flowing water
# but have a different momentum vector than the flow velocity.
#
# The larger this number, the larger the resistance of water
# to the push of an item. In other words, items thrown at
# flowing water in a direction opposite that of the flow will
# be pushed more quickly in the other direction with higher drag,
# even if the actual flow force remains the same.
waterflow_drag (Drag of water flow on dropped items) float 0.8
# Controls how much friction force should be exerted on
# dropped items when they move horizontally on the
# ground.
#
# The larger this number, the quickier items will come to
# a halt horizontally after falling on the floor.
friction_dry (Friction of dry ground on dropped items) float 2.5
# Controls how much horizontal drag force should be exerted on
# dropped items when they move horizontally in air.
#
# The larger this number, the quickier horizontal velocity tends
# toward zero.
air_drag (Horizontal drag of air on falling items) float 0.4
# Allow items on the floor to collect even on slippery floors.
#
# Dropped items can 'collect', that is, to have their
# stacks merged if they're close enough and are of the same item
# type. By default they can do this even on a slippery floor, like
# ice, but this setting allows disabling that.
builtin_item.items_collect_on_slippery (Collect items on slippery ground) bool true

View file

@ -229,9 +229,9 @@ minetest.register_node("cottages:anvil", {
end end
minetest.after(2, function() minetest.after(2, function()
if( puncher ) then if( puncher ) then
puncher:hud_remove(hud1); if(hud1) then puncher:hud_remove(hud1); end
puncher:hud_remove(hud2); if(hud2) then puncher:hud_remove(hud2); end
puncher:hud_remove(hud3); if(hud3) then puncher:hud_remove(hud3); end
end end
end) end)

View file

@ -333,13 +333,13 @@ minetest.register_node("cottages:threshing_floor", {
minetest.after(2, function() minetest.after(2, function()
if( puncher ) then if( puncher ) then
puncher:hud_remove(hud1); if(hud1) then puncher:hud_remove(hud1); end
puncher:hud_remove(hud2); if(hud2) then puncher:hud_remove(hud2); end
puncher:hud_remove(hud3); if(hud3) then puncher:hud_remove(hud3); end
puncher:hud_remove(hud4); if(hud4) then puncher:hud_remove(hud4); end
puncher:hud_remove(hud5); if(hud5) then puncher:hud_remove(hud5); end
puncher:hud_remove(hud6); if(hud6) then puncher:hud_remove(hud6); end
puncher:hud_remove(hud0); if(hud0) then puncher:hud_remove(hud0); end
end end
end) end)
end, end,

View file

@ -13,7 +13,7 @@ This mod works by adding your new plant to the {growing=1} group and numbering t
### Changelog: ### Changelog:
- 1.46 - Added min/max default light settings, added lettuce and blackberries with food items (thanks OgelGames), added soya and vanilla (thanks Felfa), added tofu - 1.46 - Added min/max default light settings, added lettuce and blackberries with food items (thanks OgelGames), added soya and vanilla (thanks Felfa), added tofu, added salt crystals (thanks gorlock)
- 1.45 - Dirt and Hoes are more in line with default by using dry/wet/base, added cactus juice, added pasta, spaghetti, cabbage, korean bibimbap, code tidy - 1.45 - Dirt and Hoes are more in line with default by using dry/wet/base, added cactus juice, added pasta, spaghetti, cabbage, korean bibimbap, code tidy
options, onion soup added (thanks edcrypt), Added apple pie, added wild cotton to savanna options, onion soup added (thanks edcrypt), Added apple pie, added wild cotton to savanna
- 1.44 - Added 'farming_stage_length' in mod settings for speed of crop growth, also thanks to TheDarkTiger for translation updates - 1.44 - Added 'farming_stage_length' in mod settings for speed of crop growth, also thanks to TheDarkTiger for translation updates

View file

@ -33,7 +33,48 @@ minetest.register_node("farming:salt", {
selection_box = { selection_box = {
type = "fixed", type = "fixed",
fixed = {-0.25, -0.5, -0.25, 0.25, 0.3, 0.25} fixed = {-0.25, -0.5, -0.25, 0.25, 0.3, 0.25}
} },
-- special function to make salt crystals form inside water
dropped_step = function(self, pos, dtime)
self.ctimer = (self.ctimer or 0) + dtime
if self.ctimer < 15.0 then return end
self.ctimer = 0
local needed
if self.node_inside
and self.node_inside.name == "default:water_source" then
needed = 8
elseif self.node_inside
and self.node_inside.name == "default:river_water_source" then
needed = 9
end
if not needed then return end
local objs = core.get_objects_inside_radius(pos, 0.5)
if not objs or #objs ~= 1 then return end
local salt, ent = nil, nil
for k, obj in pairs(objs) do
ent = obj:get_luaentity()
if ent and ent.name == "__builtin:item"
and ent.itemstring == "farming:salt " .. needed then
obj:remove()
core.add_item(pos, "farming:salt_crystal")
return false -- return with no further action
end
end
end
}) })
minetest.register_craft({ minetest.register_craft({
@ -44,6 +85,40 @@ minetest.register_craft({
replacements = {{"bucket:bucket_water", "bucket:bucket_empty"}} replacements = {{"bucket:bucket_water", "bucket:bucket_empty"}}
}) })
--= Salt Crystal
minetest.register_node("farming:salt_crystal", {
description = ("Salt crystal"),
inventory_image = "farming_salt_crystal.png",
wield_image = "farming_salt_crystal.png",
drawtype = "plantlike",
visual_scale = 0.8,
paramtype = "light",
light_source = 1,
tiles = {"farming_salt_crystal.png"},
groups = { dig_immediate = 3, attached_node = 1},
sounds = default.node_sound_defaults(),
selection_box = {
type = "fixed",
fixed = {-0.25, -0.5, -0.25, 0.25, 0.3, 0.25}
},
})
minetest.register_craft({
type = "shapeless",
output = "farming:salt 9",
recipe = {"farming:salt_crystal", "farming:mortar_pestle"},
replacements = {{"farming:mortar_pestle", "farming:mortar_pestle"}}
})
minetest.register_craft({
output = "farming:salt_crystal",
recipe = {
{"farming:salt", "farming:salt", "farming:salt"},
{"farming:salt", "farming:salt", "farming:salt"},
{"farming:salt", "farming:salt", "farming:salt"}
}
})
--= Rose Water --= Rose Water
minetest.register_node("farming:rose_water", { minetest.register_node("farming:rose_water", {
@ -273,7 +348,6 @@ minetest.register_craftitem("farming:pasta", {
groups = {food_pasta = 1} groups = {food_pasta = 1}
}) })
if minetest.get_modpath("mobs_animal") or minetest.get_modpath("xanadu")then
minetest.register_craft({ minetest.register_craft({
output = "farming:pasta", output = "farming:pasta",
type = "shapeless", type = "shapeless",
@ -283,7 +357,7 @@ minetest.register_craft({
}, },
replacements = {{"group:food_mixing_bowl", "farming:mixing_bowl"}} replacements = {{"group:food_mixing_bowl", "farming:mixing_bowl"}}
}) })
else
minetest.register_craft({ minetest.register_craft({
output = "farming:pasta", output = "farming:pasta",
type = "shapeless", type = "shapeless",
@ -296,7 +370,6 @@ minetest.register_craft({
{"group:food_oil", "vessels:glass_bottle"} {"group:food_oil", "vessels:glass_bottle"}
} }
}) })
end
-- Spaghetti -- Spaghetti
@ -324,7 +397,6 @@ minetest.register_craftitem("farming:bibimbap", {
on_use = minetest.item_eat(8, "farming:bowl") on_use = minetest.item_eat(8, "farming:bowl")
}) })
if minetest.get_modpath("mobs_animal") or minetest.get_modpath("xanadu")then
minetest.register_craft({ minetest.register_craft({
output = "farming:bibimbap", output = "farming:bibimbap",
type = "shapeless", type = "shapeless",
@ -335,7 +407,7 @@ minetest.register_craft({
}, },
replacements = {{"group:food_skillet", "farming:skillet"}} replacements = {{"group:food_skillet", "farming:skillet"}}
}) })
else
minetest.register_craft({ minetest.register_craft({
output = "farming:bibimbap", output = "farming:bibimbap",
type = "shapeless", type = "shapeless",
@ -346,7 +418,6 @@ minetest.register_craft({
}, },
replacements = {{"group:food_skillet", "farming:skillet"}} replacements = {{"group:food_skillet", "farming:skillet"}}
}) })
end
-- Burger -- Burger

View file

@ -155,3 +155,6 @@ Created by Felfa (CC0)
farming_burger.png farming_burger.png
farming_soy*.png farming_soy*.png
farming_vanilla*.png farming_vanilla*.png
Created by gorlock (CC0)
farming_salt_crystal.png

View file

@ -59,7 +59,7 @@ Cornstarch=玉米淀粉
Bottle of Ethanol=一瓶乙醇 Bottle of Ethanol=一瓶乙醇
Cotton Seed=棉籽 Cotton Seed=棉籽
Cotton=棉花 Cotton=棉花
String=字符串 String=线
Cucumber=黄瓜 Cucumber=黄瓜
Garlic clove=蒜瓣 Garlic clove=蒜瓣
Garlic=大蒜 Garlic=大蒜

View file

@ -148,7 +148,7 @@ minetest.register_abm({
if minetest.registered_nodes[nn] if minetest.registered_nodes[nn]
and minetest.registered_nodes[nn].walkable and minetest.registered_nodes[nn].walkable
and minetest.get_item_group(nn, "plant") == 0 then and minetest.get_item_group(nn, "plant") == 0 then
minetest.set_node(pos, {name = "default:dirt"}) minetest.set_node(pos, {name = ndef.soil.base})
return return
end end

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 B

View file

@ -298,6 +298,21 @@ minetest.register_craft({
} }
}) })
minetest.register_craft({
type = "shapeless",
output = "default:cobble 8",
recipe = {
"gloopblocks:pumice",
"gloopblocks:pumice",
"gloopblocks:pumice",
"gloopblocks:pumice",
"gloopblocks:basalt",
"gloopblocks:basalt",
"gloopblocks:basalt",
"gloopblocks:basalt"
}
})
minetest.register_craft({ minetest.register_craft({
output = "gloopblocks:fence_steel 1", output = "gloopblocks:fence_steel 1",
recipe = { recipe = {

View file

@ -13,3 +13,10 @@ nyancat?
usesdirt? usesdirt?
worldedit? worldedit?
signs_lib? signs_lib?
bakedclay?
farming?
wool?
bushes_classic?
dryplants?
bedrock?
cottages?

View file

@ -12,942 +12,8 @@ gloopblocks = {}
local MP = minetest.get_modpath(minetest.get_current_modname()) local MP = minetest.get_modpath(minetest.get_current_modname())
local S, NS = dofile(MP.."/intllib.lua") local S, NS = dofile(MP.."/intllib.lua")
-- Nodes dofile(MP.."/main.lua")
dofile(MP.."/crafts.lua")
minetest.register_node("gloopblocks:rainbow_block_diagonal", { dofile(MP.."/lava-handling.lua")
description = S("Diagonal Rainbow Block"),
tiles = {"gloopblocks_rainbow_block.png"},
is_ground_content = true,
groups = {cracky=3},
sounds = default.node_sound_defaults(),
})
minetest.register_alias("gloopblocks:rainbow_block", "gloopblocks:rainbow_block_diagonal")
minetest.register_node("gloopblocks:rainbow_block_horizontal", {
description = S("Horizontal Rainbow Block"),
tiles = {
"gloopblocks_rainbow_horizontal.png^[transformR90",
"gloopblocks_rainbow_horizontal.png^[transformR90",
"gloopblocks_rainbow_horizontal.png"
},
paramtype = "light",
light_source = default.LIGHT_MAX,
paramtype2 = "facedir",
groups = {cracky = 2},
is_ground_content = false,
sounds = default.node_sound_defaults(),
})
minetest.register_node("gloopblocks:evil_block", {
description = S("Evil Block"),
tiles = {"gloopblocks_evil_block.png"},
light_source = 5,
is_ground_content = true,
groups = {cracky=2},
sounds = default.node_sound_stone_defaults(),
})
minetest.register_node("gloopblocks:basalt", {
description = S("Basalt"),
tiles = {"gloopblocks_basalt.png"},
groups = {cracky=2},
sounds = default.node_sound_stone_defaults(),
})
minetest.register_node("gloopblocks:pumice", {
description = S("Pumice"),
tiles = {"gloopblocks_pumice.png"},
groups = {cracky=3},
sounds = default.node_sound_stone_defaults(),
})
minetest.register_node("gloopblocks:pavement", {
description = S("Pavement"),
tiles = {"gloopblocks_pavement.png"},
groups = {cracky=3, oddly_breakable_by_hand=3},
sounds = default.node_sound_stone_defaults(),
})
minetest.register_node("gloopblocks:oerkki_block", {
drawtype = "nodebox",
description = S("Oerkki Block"),
paramtype = "light",
paramtype2 = "facedir",
tiles = {
"gloopblocks_oerkkiblock_tb.png",
"gloopblocks_oerkkiblock_tb.png",
"gloopblocks_oerkkiblock_sides.png",
"gloopblocks_oerkkiblock_sides.png",
"gloopblocks_oerkkiblock_sides.png",
"gloopblocks_oerkkiblock_front.png"
},
groups = {cracky=3, oddly_breakable_by_hand=3},
sounds = default.node_sound_stone_defaults(),
selection_box = {
type = "fixed",
fixed = { -0.5, -0.5, -0.5, 0.5, 0.5, 0.5 }
},
node_box = {
type = "fixed",
fixed = {
{-0.5, -0.5, -0.1875, 0.5, 0.5, 0.1875}, -- NodeBox1
{-0.5, -0.5, -0.5, -0.4375, 0.5, 0.5}, -- NodeBox2
{0.4375, -0.5, -0.5, 0.5, 0.5, 0.5}, -- NodeBox3
{-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5}, -- NodeBox4
{-0.5, -0.5, -0.5, 0.5, -0.4375, 0.5}, -- NodeBox5
{-0.5, -0.0625, -0.5, 0.5, 0.0625, 0.5}, -- NodeBox6
}
}
})
minetest.register_node("gloopblocks:stone_brick_mossy", {
description = S("Mossy Stone Brick"),
tiles = {"gloopblocks_stone_brick_mossy.png"},
groups = {cracky=3, stone=1},
sounds = default.node_sound_stone_defaults(),
})
minetest.register_node("gloopblocks:stone_mossy", {
description = S("Mossy Stone"),
tiles = {"gloopblocks_stone_mossy.png"},
groups = {cracky=3, stone=1},
sounds = default.node_sound_stone_defaults(),
drop = "default:mossycobble"
})
minetest.register_node("gloopblocks:cobble_road", {
description = S("Cobblestone Road Bed"),
tiles = {"gloopblocks_cobble_road.png"},
groups = {cracky=3, stone=1},
sounds = default.node_sound_stone_defaults(),
})
minetest.register_node("gloopblocks:cobble_road_mossy", {
description = S("Mossy Cobblestone Road Bed"),
tiles = {"gloopblocks_cobble_road_mossy.png"},
groups = {cracky=3, stone=1},
sounds = default.node_sound_stone_defaults(),
})
minetest.register_node("gloopblocks:scaffolding", {
description = S("Wooden Scaffold"),
drawtype = "allfaces",
paramtype = "light",
sunlight_propagates = true,
tiles = {"gloopblocks_scaffold.png"},
groups = {choppy=3, oddly_breakable_by_hand=3},
sounds = default.node_sound_wood_defaults(),
})
minetest.register_alias("moreblocks:oerkkiblock", "gloopblocks:oerkki_block")
minetest.register_alias("gloopblocks:obsidian", "default:obsidian")
-- Nodes imported from Usesdirt ================================================================================
if not minetest.get_modpath("usesdirt") then
local dirt_brick_tex = "default_dirt.png^gloopblocks_dirt_brick_overlay.png"
local dirt_cobble_tex = "default_cobble.png^(default_dirt.png^[mask:gloopblocks_dirt_cobble_mask.png)"
local dirt_stone_tex = "default_stone.png^(default_dirt.png^[mask:gloopblocks_dirt_stone_mask.png)"
local dirt_ladder_tex = "(default_dirt.png^[mask:gloopblocks_ladder_mask.png)^gloopblocks_ladder_overlay.png"
local dirt_brick_ladder_tex = "(("..dirt_brick_tex..")^[mask:gloopblocks_ladder_mask.png)^gloopblocks_ladder_overlay.png"
local dirt_cobble_ladder_tex = "(("..dirt_cobble_tex..")^[mask:gloopblocks_ladder_mask.png)^gloopblocks_ladder_overlay.png"
local dirt_stone_ladder_tex = "(("..dirt_stone_tex..")^[mask:gloopblocks_ladder_mask.png)^gloopblocks_ladder_overlay.png"
minetest.register_node(":usesdirt:dirt_brick", {
tiles = { dirt_brick_tex },
description = "Dirt Brick",
groups = {snappy=2,choppy=1,oddly_breakable_by_hand=2},
})
minetest.register_node(":usesdirt:dirt_brick_ladder", {
description = "Dirt Brick Ladder",
drawtype = "signlike",
tiles = { dirt_brick_ladder_tex },
inventory_image = dirt_brick_ladder_tex,
wield_image = dirt_brick_ladder_tex,
paramtype = "light",
paramtype2 = "wallmounted",
is_ground_content = true,
walkable = false,
climbable = true,
selection_box = {
type = "wallmounted",
--wall_top = = <default>
--wall_bottom = = <default>
--wall_side = = <default>
},
groups = {cracky=3, stone=2},
legacy_wallmounted = true,
})
minetest.register_craft({
output = 'usesdirt:dirt_brick_ladder 3',
recipe = {
{'usesdirt:dirt_brick', '', 'usesdirt:dirt_brick'},
{'usesdirt:dirt_brick', 'usesdirt:dirt_brick','usesdirt:dirt_brick'},
{'usesdirt:dirt_brick','','usesdirt:dirt_brick'},
}
})
default.register_fence(":usesdirt:dirt_brick_fence", {
description = "Dirt Brick Fence",
texture = dirt_brick_tex,
inventory_image = "default_fence_overlay.png^("..dirt_brick_tex..")^default_fence_overlay.png^[makealpha:255,126,126",
wield_image = "default_fence_overlay.png^("..dirt_brick_tex..")^default_fence_overlay.png^[makealpha:255,126,126",
material = "usesdirt:dirt_brick",
groups = {cracky=3, stone=2},
sounds = default.node_sound_stone_defaults(),
check_for_pole = true
})
if minetest.get_modpath("moreblocks") then
minetest.register_craft({
output = 'usesdirt:dirt_brick 24',
recipe = {
{'moreblocks:dirt_compressed', 'moreblocks:dirt_compressed', '' },
{'moreblocks:dirt_compressed', 'moreblocks:dirt_compressed', '' }
}
})
else
minetest.register_craft({
output = 'usesdirt:dirt_brick 6',
recipe = {
{'default:dirt', 'default:dirt', 'default:dirt'},
{'default:dirt', 'default:dirt', 'default:dirt'},
{'default:dirt', 'default:dirt', 'default:dirt'},
}
})
end
minetest.register_node(":usesdirt:dirt_ladder", {
description = "Dirt Ladder",
drawtype = "signlike",
tiles = { dirt_ladder_tex },
inventory_image = dirt_ladder_tex,
wield_image = dirt_ladder_tex,
paramtype = "light",
paramtype2 = "wallmounted",
is_ground_content = true,
walkable = false,
climbable = true,
selection_box = {
type = "wallmounted",
--wall_top = = <default>
--wall_bottom = = <default>
--wall_side = = <default>
},
groups = {snappy=2,choppy=2,oddly_breakable_by_hand=3},
legacy_wallmounted = true,
})
minetest.register_craft({
output = 'usesdirt:dirt_ladder 3',
recipe = {
{'usesdirt:dirt_brick', '', 'usesdirt:dirt_brick'},
{'usesdirt:dirt_brick', 'usesdirt:dirt_brick','usesdirt:dirt_brick'},
{'usesdirt:dirt_brick','','usesdirt:dirt_brick'},
}
})
default.register_fence(":usesdirt:dirt_fence", {
description = "Dirt Fence",
texture = "default_dirt.png",
inventory_image = "default_fence_overlay.png^default_dirt.png^default_fence_overlay.png^[makealpha:255,126,126",
wield_image = "default_fence_overlay.png^default_dirt.png^default_fence_overlay.png^[makealpha:255,126,126",
material = "default:dirt",
groups = {snappy=2,choppy=1,oddly_breakable_by_hand=3},
sounds = default.node_sound_dirt_defaults(),
check_for_pole = true
})
----
minetest.register_node(":usesdirt:dirt_cobble_stone", {
tiles = { dirt_cobble_tex },
description = "Dirt Cobble Stone",
is_ground_content = true,
groups = {cracky=3, stone=2},
})
minetest.register_craft({
output = '"usesdirt:dirt_cobble_stone" 3',
recipe = {
{'usesdirt:dirt_brick', 'usesdirt:dirt_brick', 'usesdirt:dirt_brick'},
{'usesdirt:dirt_brick', 'usesdirt:dirt_brick', 'usesdirt:dirt_brick'},
{'usesdirt:dirt_brick', 'usesdirt:dirt_brick', 'usesdirt:dirt_brick'},
}
})
minetest.register_node(":usesdirt:dirt_cobble_stone_ladder", {
description = "Dirt Cobble Stone Ladder",
drawtype = "signlike",
tiles = { dirt_cobble_ladder_tex },
inventory_image = dirt_cobble_ladder_tex,
wield_image = dirt_cobble_ladder_tex,
paramtype = "light",
paramtype2 = "wallmounted",
is_ground_content = true,
walkable = false,
climbable = true,
selection_box = {
type = "wallmounted",
--wall_top = = <default>
--wall_bottom = = <default>
--wall_side = = <default>
},
groups = {cracky=3, stone=2},
legacy_wallmounted = true,
})
minetest.register_craft({
output = 'usesdirt:dirt_cobble_stone_ladder 3',
recipe = {
{'usesdirt:dirt_cobble_stone', '', 'usesdirt:dirt_cobble_stone'},
{'usesdirt:dirt_cobble_stone', 'usesdirt:dirt_cobble_stone','usesdirt:dirt_cobble_stone'},
{'usesdirt:dirt_cobble_stone','','usesdirt:dirt_cobble_stone'},
}
})
default.register_fence(":usesdirt:dirt_cobble_stone_fence", {
description = "Dirt Cobble Stone Fence",
texture = dirt_cobble_tex,
inventory_image = "default_fence_overlay.png^("..dirt_cobble_tex..")^default_fence_overlay.png^[makealpha:255,126,126",
wield_image = "default_fence_overlay.png^("..dirt_cobble_tex..")^default_fence_overlay.png^[makealpha:255,126,126",
material = "usesdirt:dirt_cobble_stone",
groups = {cracky=3, stone=2},
sounds = default.node_sound_stone_defaults(),
check_for_pole = true
})
----
minetest.register_node(":usesdirt:dirt_stone", {
tiles = { dirt_stone_tex },
description = "Dirt Stone",
is_ground_content = true,
groups = {cracky=3, stone=2},
})
minetest.register_node(":usesdirt:dirt_stone_ladder", {
description = "Dirt Stone Ladder",
drawtype = "signlike",
tiles = { dirt_stone_ladder_tex },
inventory_image = dirt_stone_ladder_tex,
wield_image = dirt_stone_ladder_tex,
paramtype = "light",
paramtype2 = "wallmounted",
is_ground_content = true,
walkable = false,
climbable = true,
selection_box = {
type = "wallmounted",
--wall_top = = <default>
--wall_bottom = = <default>
--wall_side = = <default>
},
groups = {cracky=3, stone=2},
legacy_wallmounted = true,
})
minetest.register_craft({
output = 'usesdirt:dirt_stone_ladder 3',
recipe = {
{'usesdirt:dirt_stone', '', 'usesdirt:dirt_stone'},
{'usesdirt:dirt_stone', 'usesdirt:dirt_stone','usesdirt:dirt_stone'},
{'usesdirt:dirt_stone','','usesdirt:dirt_stone'},
}
})
default.register_fence(":usesdirt:dirt_stone_fence", {
description = "Dirt Stone Fence",
texture = dirt_stone_tex,
inventory_image = "default_fence_overlay.png^("..dirt_stone_tex..")^default_fence_overlay.png^[makealpha:255,126,126",
wield_image = "default_fence_overlay.png^("..dirt_stone_tex..")^default_fence_overlay.png^[makealpha:255,126,126",
material = "usesdirt:dirt_stone",
groups = {cracky=3, stone=2},
sounds = default.node_sound_stone_defaults(),
check_for_pole = true
})
end
-- Stairs/slabs defs, conversion of normal -> mossy items
if minetest.setting_getbool("gloopblocks_mossy_conversion") ~= false then
function gloopblocks_register_mossy_conversion(mossyobjects)
for i in ipairs(mossyobjects) do
minetest.register_abm({
nodenames = { mossyobjects[i][1] },
neighbors = {"default:water_source", "default:water_flowing"},
interval = 120,
chance = 50,
action = function(pos, node)
if minetest.find_node_near(pos, 2, "air") then
local fdir = node.param2
minetest.add_node(pos, {name = mossyobjects[i][2], param2 = fdir})
end
end,
})
end
end
end
if minetest.get_modpath("moreblocks") then
stairsplus:register_all("gloopblocks", "oerkki_block", "gloopblocks:oerkki_block", {
description = S("Oerkki Block"),
tiles = {
"gloopblocks_oerkkiblock_tb.png",
"gloopblocks_oerkkiblock_tb.png",
"gloopblocks_oerkkiblock_sides.png",
"gloopblocks_oerkkiblock_sides.png",
"gloopblocks_oerkkiblock_sides.png",
"gloopblocks_oerkkiblock_front.png"
},
groups = {cracky=2, not_in_creative_inventory=1},
sounds = default.node_sound_stone_defaults(),
sunlight_propagates = true,
})
stairsplus:register_all("gloopblocks", "stone_brick_mossy", "gloopblocks:stone_brick_mossy", {
description = S("Mossy Stone Brick"),
tiles = {"gloopblocks_stone_brick_mossy.png"},
groups = {cracky=1, not_in_creative_inventory=1},
sounds = default.node_sound_stone_defaults(),
sunlight_propagates = true,
})
stairsplus:register_all("gloopblocks", "stone_mossy", "gloopblocks:stone_mossy", {
description = S("Mossy Stone"),
tiles = {"gloopblocks_stone_mossy.png"},
groups = {cracky=1, not_in_creative_inventory=1},
sounds = default.node_sound_stone_defaults(),
sunlight_propagates = true,
})
stairsplus:register_all("gloopblocks", "cobble_road", "gloopblocks:cobble_road", {
description = S("Cobblestone Roadbed"),
tiles = {"gloopblocks_cobble_road.png"},
groups = {cracky=3, stone=1, not_in_creative_inventory=1},
sounds = default.node_sound_stone_defaults(),
sunlight_propagates = true,
})
stairsplus:register_all("gloopblocks", "cobble_road_mossy", "gloopblocks:cobble_road_mossy", {
description = S("Mossy Cobblestone Roadbed"),
tiles = {"gloopblocks_cobble_road_mossy.png"},
groups = {cracky=3, stone=1, not_in_creative_inventory=1},
sounds = default.node_sound_stone_defaults(),
sunlight_propagates = true,
})
stairsplus:register_all("gloopblocks", "pavement", "gloopblocks:pavement", {
description = S("Pavement"),
tiles = {"gloopblocks_pavement.png"},
groups = {cracky=2, not_in_creative_inventory=1},
sounds = default.node_sound_stone_defaults(),
sunlight_propagates = true,
})
stairsplus:register_all("gloopblocks", "rainbow_block", "gloopblocks:rainbow_block", {
description = S("Rainbow Block"),
tiles = {"gloopblocks_rainbow_block.png"},
groups = {cracky=3, not_in_creative_inventory=1},
sounds = default.node_sound_defaults(),
sunlight_propagates = true,
})
stairsplus:register_all("gloopblocks", "evil_block", "gloopblocks:evil_block", {
description = S("Evil Block"),
tiles = {"gloopblocks_evil_block.png"},
groups = {cracky=3, not_in_creative_inventory=1},
sounds = default.node_sound_defaults(),
light_source = 5,
sunlight_propagates = true,
})
stairsplus:register_all("gloopblocks", "basalt", "gloopblocks:basalt", {
description = S("Basalt"),
tiles = {"gloopblocks_basalt.png"},
groups = {cracky=2, not_in_creative_inventory=1},
sounds = default.node_sound_stone_defaults(),
sunlight_propagates = true,
})
stairsplus:register_all("gloopblocks", "pumice", "gloopblocks:pumice", {
description = S("Pumice"),
tiles = {"gloopblocks_pumice.png"},
groups = {cracky=3, not_in_creative_inventory=1},
sounds = default.node_sound_stone_defaults(),
sunlight_propagates = true,
})
stairsplus:register_all("gloopblocks", "gravel", "default:gravel", {
description = S("Gravel"),
tiles = {"default_gravel.png"},
groups = {crumbly = 2, falling_node = 1, not_in_creative_inventory=1},
sounds = default.node_sound_stone_defaults(),
sunlight_propagates = false,
})
if minetest.get_modpath("caverealms") then
stairsplus:register_all("caverealms", "glow_crystal", "caverealms:glow_crystal", {
description = S("Glow Crystal"),
tiles = {"caverealms_glow_crystal.png"},
groups = {cracky=3, not_in_creative_inventory=1},
sounds = default.node_sound_glass_defaults(),
light_source = 12,
use_texture_alpha = true,
paramtype="light",
sunlight_propagates = true,
})
stairsplus:register_all("caverealms", "glow_emerald", "caverealms:glow_emerald", {
description = S("Glow Emerald"),
tiles = {"caverealms_glow_emerald.png"},
groups = {cracky=3, not_in_creative_inventory=1},
sounds = default.node_sound_glass_defaults(),
light_source = 12,
use_texture_alpha = true,
paramtype="light",
sunlight_propagates = true,
})
stairsplus:register_all("caverealms", "glow_mese", "caverealms:glow_mese", {
description = S("Glow Mese"),
tiles = {"caverealms_glow_mese.png"},
groups = {cracky=3, not_in_creative_inventory=1},
sounds = default.node_sound_glass_defaults(),
light_source = 12,
use_texture_alpha = true,
paramtype="light",
sunlight_propagates = true,
})
end
-- ABMs for mossy objects
if minetest.setting_getbool("gloopblocks_mossy_conversion") ~= false then
gloopblocks_register_mossy_conversion({
{ "moreblocks:stair_cobble", "moreblocks:stair_mossycobble" },
{ "moreblocks:stair_cobble_inner", "moreblocks:stair_mossycobble_inner" },
{ "moreblocks:stair_cobble_outer", "moreblocks:stair_mossycobble_outer" },
{ "moreblocks:stair_cobble_half", "moreblocks:stair_mossycobble_half" },
{ "moreblocks:slab_cobble_quarter", "moreblocks:slab_mossycobble_quarter" },
{ "moreblocks:slab_cobble", "moreblocks:slab_mossycobble" },
{ "moreblocks:slab_cobble_three_quarter", "moreblocks:slab_mossycobble_three_quarter" },
{ "moreblocks:panel_cobble", "moreblocks:panel_mossycobble" },
{ "moreblocks:micro_cobble", "moreblocks:micro_mossycobble" },
{ "moreblocks:stair_cobble_alt", "moreblocks:stair_mossycobble_alt" },
{ "gloopblocks:cobble_road", "gloopblocks:cobble_road_mossy" },
{ "gloopblocks:stair_cobble_road", "gloopblocks:stair_cobble_road_mossy" },
{ "gloopblocks:slab_cobble_road", "gloopblocks:slab_cobble_road_mossy" },
{ "gloopblocks:stair_cobble_road", "gloopblocks:stair_cobble_road_mossy" },
{ "gloopblocks:stair_cobble_road_inner", "gloopblocks:stair_cobble_road_mossy_inner" },
{ "gloopblocks:stair_cobble_road_outer", "gloopblocks:stair_cobble_road_mossy_outer" },
{ "gloopblocks:stair_cobble_road_half", "gloopblocks:stair_cobble_road_mossy_half" },
{ "gloopblocks:slab_cobble_road_quarter", "gloopblocks:slab_cobble_road_mossy_quarter" },
{ "gloopblocks:slab_cobble_road", "gloopblocks:slab_cobble_road_mossy" },
{ "gloopblocks:slab_cobble_road_three_quarter", "gloopblocks:slab_cobble_road_mossy_three_quarter" },
{ "gloopblocks:panel_cobble_road", "gloopblocks:panel_cobble_road_mossy" },
{ "gloopblocks:micro_cobble_road", "gloopblocks:micro_cobble_road_mossy" },
{ "gloopblocks:stair_cobble_road_alt", "gloopblocks:stair_cobble_road_mossy_alt" },
{ "default:stonebrick", "gloopblocks:stone_brick_mossy" },
{ "default:stair_stonebrick", "gloopblocks:stair_stone_brick_mossy" },
{ "default:slab_stonebrick", "gloopblocks:slab_stone_brick_mossy" },
{ "moreblocks:stair_stonebrick", "gloopblocks:stair_stone_brick_mossy" },
{ "moreblocks:stair_stonebrick_inner", "gloopblocks:stair_stone_brick_mossy_inner" },
{ "moreblocks:stair_stonebrick_outer", "gloopblocks:stair_stone_brick_mossy_outer" },
{ "moreblocks:stair_stonebrick_half", "gloopblocks:stair_stone_brick_mossy_half" },
{ "moreblocks:slab_stonebrick_quarter", "gloopblocks:slab_stone_brick_mossy_quarter" },
{ "moreblocks:slab_stonebrick", "gloopblocks:slab_stone_brick_mossy" },
{ "moreblocks:slab_stonebrick_three_quarter", "gloopblocks:slab_stone_brick_mossy_three_quarter" },
{ "moreblocks:panel_stonebrick", "gloopblocks:panel_stone_brick_mossy" },
{ "moreblocks:micro_stonebrick", "gloopblocks:micro_stone_brick_mossy" },
{ "moreblocks:stair_stonebrick_alt", "gloopblocks:stair_stone_brick_mossy_alt" },
{ "default:stone", "gloopblocks:stone_mossy" },
{ "default:stair_stone", "gloopblocks:stair_stone_mossy" },
{ "default:slab_stone", "gloopblocks:slab_stone_mossy" },
{ "moreblocks:stair_stone", "gloopblocks:stair_stone_mossy" },
{ "moreblocks:stair_stone_inner", "gloopblocks:stair_stone_mossy_inner" },
{ "moreblocks:stair_stone_outer", "gloopblocks:stair_stone_mossy_outer" },
{ "moreblocks:stair_stone_half", "gloopblocks:stair_stone_mossy_half" },
{ "moreblocks:slab_stone_quarter", "gloopblocks:slab_stone_mossy_quarter" },
{ "moreblocks:slab_stone", "gloopblocks:slab_stone_mossy" },
{ "moreblocks:slab_stone_three_quarter", "gloopblocks:slab_stone_mossy_three_quarter" },
{ "moreblocks:panel_stone", "gloopblocks:panel_stone_mossy" },
{ "moreblocks:micro_stone", "gloopblocks:micro_stone_mossy" },
{ "moreblocks:stair_stone_alt", "gloopblocks:stair_stone_mossy_alt" },
})
end
elseif minetest.get_modpath("stairs") then
--stairs.register_stair(subname, recipeitem, groups, images, description, sounds)
-- stairs:xxxx_stone_mossy ; xxxx = stair or slab
stairs.register_stair_and_slab("stone_mossy", "gloopblocks:stone_mossy",
{cracky=3},
{"gloopblocks_stone_mossy.png"},
S("Mossy Stone Stair"),
S("Mossy Stone Slab"),
default.node_sound_stone_defaults())
-- stairs:xxxx_mossycobble
stairs.register_stair_and_slab("mossycobble", "default:mossycobble",
{cracky=3},
{"default_mossycobble.png"},
S("Mossy Cobble Stair"),
S("Mossy Cobble Slab"),
default.node_sound_stone_defaults())
-- stairs:xxxx_stone_brick_mossy
stairs.register_stair_and_slab("stone_brick_mossy", "gloopblocks:stone_brick_mossy",
{cracky=3},
{"gloopblocks_stone_brick_mossy.png"},
S("Mossy Stone Brick Stair"),
S("Mossy Stone Brick Slab"),
default.node_sound_stone_defaults())
-- stairs:xxxx_cobble_road
stairs.register_stair_and_slab("cobble_road", "gloopblocks:cobble_road",
{cracky=3},
{"gloopblocks_cobble_road.png"},
S("Cobble Roadbed Stair"),
S("Cobble Roadbed Slab"),
default.node_sound_stone_defaults())
-- stairs:xxxx_cobble_road_mossy
stairs.register_stair_and_slab("cobble_road_mossy", "gloopblocks:cobble_road_mossy",
{cracky=3},
{"gloopblocks_cobble_road_mossy.png"},
S("Mossy Cobble Roadbed Stair"),
S("Mossy Cobble Roadbed Slab"),
default.node_sound_stone_defaults())
-- stairs:xxxx_cement
stairs.register_stair_and_slab("cement", "gloopblocks:cement",
{cracky=2},
{"basic_materials_cement_block.png"},
S("Cement Stair"),
S("Cement Slab"),
default.node_sound_stone_defaults())
-- stairs:xxxx_pavement
stairs.register_stair_and_slab("pavement", "gloopblocks:pavement",
{cracky=3, oddly_breakable_by_hand=3},
{"gloopblocks_pavement.png"},
S("Pavement Stair"),
S("Pavement Slab"),
default.node_sound_stone_defaults())
stairs.register_stair_and_slab("basalt", "gloopblocks:basalt",
{cracky=2},
{"gloopblocks_basalt.png"},
S("Basalt Stair"),
S("Basalt Slab"),
default.node_sound_stone_defaults())
stairs.register_stair_and_slab("pumice", "gloopblocks:pumice",
{cracky=3},
{"gloopblocks_pumice.png"},
S("Pumice Stair"),
S("Pumice Slab"),
default.node_sound_stone_defaults())
stairs.register_stair_and_slab("rainbow_block", "gloopblocks:rainbow_block",
{cracky=3},
{"gloopblocks_rainbow_block.png"},
S("Rainbow Block Stair"),
S("Rainbow Block Slab"),
default.node_sound_defaults())
if minetest.setting_getbool("gloopblocks_mossy_conversion") ~= false then
gloopblocks_register_mossy_conversion({
{ "default:cobble", "default:mossycobble" },
{ "stairs:stair_cobble", "stairs:stair_mossycobble" },
{ "stairs:slab_cobble", "stairs:slab_mossycobble" },
{ "gloopblocks:cobble_road", "gloopblocks:cobble_road_mossy" },
{ "stairs:stair_cobble_road", "stairs:stair_cobble_road_mossy" },
{ "stairs:slab_cobble_road", "stairs:slab_cobble_road_mossy" },
{ "default:stonebrick", "gloopblocks:stone_brick_mossy" },
{ "stairs:stair_stonebrick", "stairs:stair_stone_brick_mossy" },
{ "stairs:slab_stonebrick", "stairs:slab_stone_brick_mossy" },
{ "default:stone", "gloopblocks:stone_mossy" },
{ "stairs:stair_stone", "stairs:stair_stone_mossy" },
{ "stairs:slab_stone", "stairs:slab_stone_mossy" },
})
end
minetest.register_alias("default:stair_mossycobble", "stairs:stair_mossycobble")
minetest.register_alias("default:slab_mossycobble", "stairs:slab_mossycobble")
minetest.register_alias("gloopblocks:stair_cobble_road", "stairs:stair_cobble_road")
minetest.register_alias("gloopblocks:slab_cobble_road", "stairs:slab_cobble_road")
minetest.register_alias("gloopblocks:stair_cobble_road_mossy", "stairs:stair_cobble_road_mossy")
minetest.register_alias("gloopblocks:slab_cobble_road_mossy", "stairs:slab_cobble_road_mossy")
minetest.register_alias("gloopblocks:stair_stone_brick_mossy", "stairs:stair_stone_brick_mossy")
minetest.register_alias("gloopblocks:slab_stone_brick_mossy", "stairs:slab_stone_brick_mossy")
minetest.register_alias("gloopblocks:stair_stone_mossy", "stairs:stair_stone_mossy")
minetest.register_alias("gloopblocks:slab_stone_mossy", "stairs:slab_stone_mossy")
minetest.register_alias("gloopblocks:stair_cement", "stairs:stair_cement")
minetest.register_alias("gloopblocks:slab_cement", "stairs:slab_cement")
minetest.register_alias("gloopblocks:stair_pavement", "stairs:stair_pavement")
minetest.register_alias("gloopblocks:slab_pavement", "stairs:slab_pavement")
minetest.register_alias("gloopblocks:stair_pumice", "stairs:stair_pumice")
minetest.register_alias("gloopblocks:slab_pumice", "stairs:slab_pumice")
minetest.register_alias("gloopblocks:stair_basalt", "stairs:stair_basalt")
minetest.register_alias("gloopblocks:slab_basalt", "stairs:slab_basalt")
minetest.register_alias("gloopblocks:stair_rainbow_block", "stairs:stair_rainbow_block")
minetest.register_alias("gloopblocks:slab_rainbow_block", "stairs:slab_rainbow_block")
end
-- Tools
minetest.register_tool("gloopblocks:pick_cement", {
description = S("Cement Pickaxe"),
inventory_image = "gloopblocks_cement_pick.png",
tool_capabilities = {
full_punch_interval = 1.0,
max_drop_level=1,
groupcaps={
cracky={times={[1]=3.50, [2]=1.40, [3]=0.90}, uses=25, maxlevel=2}
},
damage_groups = {fleshy=4},
},
})
minetest.register_tool("gloopblocks:shovel_cement", {
description = S("Cement Shovel"),
inventory_image = "gloopblocks_cement_shovel.png",
tool_capabilities = {
full_punch_interval = 1.0,
max_drop_level=1,
groupcaps={
crumbly={times={[1]=1.50, [2]=0.60, [3]=0.45}, uses=25, maxlevel=2}
},
damage_groups = {fleshy=4},
},
})
minetest.register_tool("gloopblocks:axe_cement", {
description = S("Cement Axe"),
inventory_image = "gloopblocks_cement_axe.png",
tool_capabilities = {
full_punch_interval = 1.0,
max_drop_level=1,
groupcaps={
choppy={times={[1]=3.00, [2]=1.30, [3]=0.80}, uses=25, maxlevel=2},
fleshy={times={[2]=1.20, [3]=0.65}, uses=30, maxlevel=1}
},
damage_groups = {fleshy=4},
},
})
minetest.register_tool("gloopblocks:sword_cement", {
description = S("Cement Sword"),
inventory_image = "gloopblocks_cement_sword.png",
tool_capabilities = {
full_punch_interval = 1.0,
max_drop_level=1,
groupcaps={
fleshy={times={[1]=1.60, [2]=0.80, [3]=0.40}, uses=15, maxlevel=2},
snappy={times={[2]=0.75, [3]=0.35}, uses=30, maxlevel=1},
choppy={times={[3]=0.80}, uses=30, maxlevel=0}
},
damage_groups = {fleshy=6},
}
})
minetest.register_tool("gloopblocks:pick_evil", {
description = S("Evil Pickaxe"),
inventory_image = "gloopblocks_evil_pick.png",
tool_capabilities = {
full_punch_interval = 1.0,
max_drop_level=3,
groupcaps={
cracky={times={[1]=0.10, [2]=0.10, [3]=0.10}, uses=10, maxlevel=2}
},
damage_groups = {fleshy=6},
},
})
minetest.register_tool("gloopblocks:shovel_evil", {
description = S("Evil Shovel"),
inventory_image = "gloopblocks_evil_shovel.png",
tool_capabilities = {
full_punch_interval = 1.0,
max_drop_level=3,
groupcaps={
crumbly={times={[1]=0.05, [2]=0.05, [3]=0.05}, uses=10, maxlevel=2}
},
damage_groups = {fleshy=6},
},
})
minetest.register_tool("gloopblocks:axe_evil", {
description = S("Evil Axe"),
inventory_image = "gloopblocks_evil_axe.png",
tool_capabilities = {
full_punch_interval = 1.0,
max_drop_level=3,
groupcaps={
choppy={times={[1]=0.15, [2]=0.15, [3]=0.15}, uses=10, maxlevel=2},
fleshy={times={[1]=0.15, [2]=0.15, [3]=0.15}, uses=10, maxlevel=2}
},
damage_groups = {fleshy=6},
},
})
minetest.register_tool("gloopblocks:sword_evil", {
description = S("Evil Sword"),
inventory_image = "gloopblocks_evil_sword.png",
tool_capabilities = {
full_punch_interval = 1.0,
max_drop_level=3,
groupcaps={
fleshy={times={[1]=0.20, [2]=0.20, [3]=0.20}, uses=10, maxlevel=2},
snappy={times={[1]=0.20, [2]=0.20, [3]=0.20}, uses=10, maxlevel=2},
choppy={times={[1]=0.20, [2]=0.20, [3]=0.20}, uses=10, maxlevel=2}
},
damage_groups = {fleshy=8},
}
})
-- Other items
minetest.register_craftitem("gloopblocks:evil_stick", {
description = S("Evil Stick"),
inventory_image = "gloopblocks_evil_stick.png",
})
-- define lava-cooling-based nodes and hook into the default lavacooling
-- functions to generate basalt, pumice, and obsidian
if minetest.setting_getbool("gloopblocks_lavacooling") ~= false then
minetest.register_node("gloopblocks:obsidian_cooled", {
description = S("Obsidian"),
tiles = {"default_obsidian.png"},
is_ground_content = true,
sounds = default.node_sound_stone_defaults(),
groups = {cracky=1, level=2, not_in_creative_inventory=1},
drop = "default:obsidian",
after_place_node = function(pos, placer, itemstack, pointed_thing)
minetest.add_node(pos, {name = "default:obsidian"})
end
})
minetest.register_node("gloopblocks:basalt_cooled", {
description = S("Basalt"),
tiles = {"gloopblocks_basalt.png"},
groups = {cracky=2, not_in_creative_inventory=1},
sounds = default.node_sound_stone_defaults(),
drop = "gloopblocks:basalt",
after_place_node = function(pos, placer, itemstack, pointed_thing)
minetest.add_node(pos, {name = "gloopblocks:basalt"})
end
})
minetest.register_node("gloopblocks:pumice_cooled", {
description = S("Pumice"),
tiles = {"gloopblocks_pumice.png"},
groups = {cracky=3, not_in_creative_inventory=1},
sounds = default.node_sound_stone_defaults(),
drop = "gloopblocks:pumice",
after_place_node = function(pos, placer, itemstack, pointed_thing)
minetest.add_node(pos, {name = "gloopblocks:pumice"})
end
})
local gloopblocks_search_nearby_nodes = function(pos, node)
if minetest.get_node({x=pos.x-1, y=pos.y, z=pos.z}).name == node then return true end
if minetest.get_node({x=pos.x+1, y=pos.y, z=pos.z}).name == node then return true end
if minetest.get_node({x=pos.x, y=pos.y-1, z=pos.z}).name == node then return true end
if minetest.get_node({x=pos.x, y=pos.y+1, z=pos.z}).name == node then return true end
if minetest.get_node({x=pos.x, y=pos.y, z=pos.z-1}).name == node then return true end
if minetest.get_node({x=pos.x, y=pos.y, z=pos.z+1}).name == node then return true end
return false
end
default.cool_lava = function(pos, node)
if node.name == "default:lava_source" then
if gloopblocks_search_nearby_nodes(pos,"default:water_source")
or gloopblocks_search_nearby_nodes(pos,"default:water_flowing") then
minetest.set_node(pos, {name="gloopblocks:obsidian_cooled"})
end
else -- Lava flowing
if gloopblocks_search_nearby_nodes(pos,"default:water_source") then
minetest.set_node(pos, {name="gloopblocks:basalt_cooled"})
elseif gloopblocks_search_nearby_nodes(pos,"default:water_flowing") then
minetest.set_node(pos, {name="gloopblocks:pumice_cooled"})
end
end
end
end
local fence_texture =
"default_fence_overlay.png^default_steel_block.png^default_fence_overlay.png^[makealpha:255,126,126"
minetest.register_node("gloopblocks:fence_steel", {
description = S("Steel Fence"),
drawtype = "fencelike",
tiles = {"default_steel_block.png"},
inventory_image = fence_texture,
wield_image = fence_texture,
paramtype = "light",
sunlight_propagates = true,
is_ground_content = false,
selection_box = {
type = "fixed",
fixed = {-1/7, -1/2, -1/7, 1/7, 1/2, 1/7},
},
groups = {choppy = 2, oddly_breakable_by_hand = 2 },
sounds = default.node_sound_stone_defaults(),
})
if minetest.get_modpath("worldedit") then
function gloopblocks.liquid_ungrief(pos1, pos2, name)
local count
local p1to2 = minetest.pos_to_string(pos1).." and "..minetest.pos_to_string(pos2)
local volume = worldedit.volume(pos1, pos2)
minetest.chat_send_player(name, "Cleaning-up lava/water griefing between "..p1to2.."...")
if volume > 1000000 then
minetest.chat_send_player(name, "This operation could affect up to "..volume.." nodes. It may take a while.")
end
minetest.log("action", name.." performs lava/water greifing cleanup between "..p1to2..".")
count = worldedit.replace(pos1, pos2, "default:lava_source", "air")
count = worldedit.replace(pos1, pos2, "default:lava_flowing", "air")
count = worldedit.replace(pos1, pos2, "default:water_source", "air")
count = worldedit.replace(pos1, pos2, "default:water_flowing", "air")
count = worldedit.replace(pos1, pos2, "default:river_water_source", "air")
count = worldedit.replace(pos1, pos2, "default:river_water_flowing", "air")
count = worldedit.replace(pos1, pos2, "gloopblocks:pumice_cooled", "air")
count = worldedit.replace(pos1, pos2, "gloopblocks:basalt_cooled", "air")
count = worldedit.replace(pos1, pos2, "gloopblocks:obsidian_cooled", "air")
count = worldedit.fixlight(pos1, pos2)
minetest.chat_send_player(name, "Operation completed.")
end
minetest.register_chatcommand("/liquid_ungrief", {
params = "[size]",
privs = {worldedit = true},
description = "Repairs greifing caused by spilling lava and water (and their \"cooling\" results)",
func = function(name, params)
local pos1 = worldedit.pos1[name]
local pos2 = worldedit.pos2[name]
if not pos1 or not pos2 then return end
gloopblocks.liquid_ungrief(pos1, pos2, name)
end
})
end
dofile(minetest.get_modpath("gloopblocks").."/crafts.lua")
minetest.register_alias("nyancat:nyancat_rainbow", "gloopblocks:rainbow_block_horizontal")
minetest.register_alias("default:nyancat_rainbow", "gloopblocks:rainbow_block_horizontal")
print(S("Gloopblocks Loaded!")) print(S("Gloopblocks Loaded!"))

View file

@ -0,0 +1,306 @@
-- Load support for intllib.
local MP = minetest.get_modpath(minetest.get_current_modname())
local S, NS = dofile(MP.."/intllib.lua")
-- define lava-cooling-based nodes and hook into the default lavacooling
-- functions to generate basalt, pumice, and obsidian
if minetest.setting_getbool("gloopblocks_lavacooling") ~= false then
minetest.register_node("gloopblocks:obsidian_cooled", {
description = S("Obsidian"),
tiles = {"default_obsidian.png"},
is_ground_content = true,
sounds = default.node_sound_stone_defaults(),
groups = {cracky=1, level=2, not_in_creative_inventory=1},
drop = "default:obsidian",
after_place_node = function(pos, placer, itemstack, pointed_thing)
minetest.add_node(pos, {name = "default:obsidian"})
end
})
minetest.register_node("gloopblocks:basalt_cooled", {
description = S("Basalt"),
tiles = {"gloopblocks_basalt.png"},
groups = {cracky=2, not_in_creative_inventory=1},
sounds = default.node_sound_stone_defaults(),
drop = "gloopblocks:basalt",
after_place_node = function(pos, placer, itemstack, pointed_thing)
minetest.add_node(pos, {name = "gloopblocks:basalt"})
end
})
minetest.register_node("gloopblocks:pumice_cooled", {
description = S("Pumice"),
tiles = {"gloopblocks_pumice.png"},
groups = {cracky=3, not_in_creative_inventory=1},
sounds = default.node_sound_stone_defaults(),
drop = "gloopblocks:pumice",
after_place_node = function(pos, placer, itemstack, pointed_thing)
minetest.add_node(pos, {name = "gloopblocks:pumice"})
end
})
local gloopblocks_search_nearby_nodes = function(pos, node)
if minetest.get_node({x=pos.x-1, y=pos.y, z=pos.z}).name == node then return true end
if minetest.get_node({x=pos.x+1, y=pos.y, z=pos.z}).name == node then return true end
if minetest.get_node({x=pos.x, y=pos.y-1, z=pos.z}).name == node then return true end
if minetest.get_node({x=pos.x, y=pos.y+1, z=pos.z}).name == node then return true end
if minetest.get_node({x=pos.x, y=pos.y, z=pos.z-1}).name == node then return true end
if minetest.get_node({x=pos.x, y=pos.y, z=pos.z+1}).name == node then return true end
return false
end
default.cool_lava = function(pos, node)
if node.name == "default:lava_source" then
if gloopblocks_search_nearby_nodes(pos,"default:water_source")
or gloopblocks_search_nearby_nodes(pos,"default:water_flowing") then
minetest.set_node(pos, {name="gloopblocks:obsidian_cooled"})
end
else -- Lava flowing
if gloopblocks_search_nearby_nodes(pos,"default:water_source") then
minetest.set_node(pos, {name="gloopblocks:basalt_cooled"})
elseif gloopblocks_search_nearby_nodes(pos,"default:water_flowing") then
minetest.set_node(pos, {name="gloopblocks:pumice_cooled"})
end
end
end
end
-- Allows lava to "bake" neighboring nodes (or reduce them to ashes)
-- disabled by default. You probably don't want this on a creative server :-P
if minetest.setting_getbool("gloopblocks_lava_damage") then
minetest.register_node("gloopblocks:ash_block", {
description = S("Block of ashes"),
tiles = {"gloopblocks_ashes.png"},
groups = {crumbly = 3},
sounds = default.node_sound_dirt_defaults(),
})
local cbox = {
type = "fixed",
fixed = { -0.5, -0.5, -0.5, 0.5, -0.125, 0.5}
}
minetest.register_node("gloopblocks:ash_pile", {
description = S("Pile of ashes"),
drawtype = "mesh",
mesh = "gloopblocks_ash_pile.obj",
tiles = {"gloopblocks_ashes.png"},
selection_box = cbox,
collision_box = cbox,
groups = {crumbly = 3},
sounds = default.node_sound_dirt_defaults(),
})
gloopblocks.lava_damage_nodes = {
["default:cactus"] = "gloopblocks:ash_block",
["default:coalblock"] = "gloopblocks:ash_block",
["default:desert_cobble"] = "default:desert_stone",
["default:desert_sandstone"] = "default:desert_sandstone_block",
["default:gravel"] = "default:cobble",
["default:ice"] = "default:snowblock",
["default:permafrost"] = "default:dirt",
["default:permafrost_with_moss"] = "default:dirt",
["default:sandstone"] = "default:sandstone_block",
["default:silver_sandstone"] = "default:silver_sandstone_block",
["default:snowblock"] = "default:water_source",
["basic_materials:cement_block"] = "basic_materials:concrete_block",
["bedrock:deepstone"] = "default:stone",
["building_blocks:hardwood"] = "default:coalblock",
["building_blocks:Tar"] = "gloopblocks:pavement",
["bushes:basket_empty"] = "gloopblocks:ash_pile",
["bushes:basket_blackberry"] = "gloopblocks:ash_pile",
["bushes:basket_blueberry"] = "gloopblocks:ash_pile",
["bushes:basket_gooseberry"] = "gloopblocks:ash_pile",
["bushes:basket_mixed_berry"] = "gloopblocks:ash_pile",
["bushes:basket_raspberry"] = "gloopblocks:ash_pile",
["bushes:basket_strawberry"] = "gloopblocks:ash_pile",
["caverealms:thin_ice"] = "default:water_source",
["castle_masonry:rubble"] = "default:desert_stone",
["usesdirt:dirt_stone"] = "default:stone",
["usesdirt:dirt_cobble_stone"] = "default:stone",
["wool:dark_grey"] = "gloopblocks:ash_pile"
}
gloopblocks.lava_damage_groups = {
["wood"] = "default:coalblock",
["tree"] = "default:coalblock",
["soil"] = "gloopblocks:basalt",
["leaves"] = "gloopblocks:ash_pile",
["fence"] = "gloopblocks:ash_pile",
["stone"] = "default:stone",
}
if minetest.get_modpath("cottages") then
gloopblocks.lava_damage_nodes["cottages:hay"] = "cottages:reet"
gloopblocks.lava_damage_nodes["cottages:hay_bale"] = "cottages:reet"
gloopblocks.lava_damage_nodes["cottages:hay_mat"] = "cottages:straw_mat"
gloopblocks.lava_damage_nodes["cottages:reet"] = "gloopblocks:ash_pile"
gloopblocks.lava_damage_nodes["cottages:roof_black"] = "gloopblocks:ash_pile"
gloopblocks.lava_damage_nodes["cottages:roof_brown"] = "gloopblocks:ash_pile"
gloopblocks.lava_damage_nodes["cottages:roof_red"] = "gloopblocks:ash_pile"
gloopblocks.lava_damage_nodes["cottages:roof_reet"] = "gloopblocks:ash_pile"
gloopblocks.lava_damage_nodes["cottages:roof_straw"] = "cottages:roof_reet"
gloopblocks.lava_damage_nodes["cottages:roof_wood"] = "gloopblocks:ash_pile"
gloopblocks.lava_damage_nodes["cottages:roof_connector_black"] = "gloopblocks:ash_pile"
gloopblocks.lava_damage_nodes["cottages:roof_connector_brown"] = "gloopblocks:ash_pile"
gloopblocks.lava_damage_nodes["cottages:roof_connector_red"] = "gloopblocks:ash_pile"
gloopblocks.lava_damage_nodes["cottages:roof_connector_reet"] = "gloopblocks:ash_pile"
gloopblocks.lava_damage_nodes["cottages:roof_connector_straw"] = "cottages:roof_connector_reet"
gloopblocks.lava_damage_nodes["cottages:roof_connector_wood"] = "gloopblocks:ash_pile"
gloopblocks.lava_damage_nodes["cottages:roof_flat_black"] = "gloopblocks:ash_pile"
gloopblocks.lava_damage_nodes["cottages:roof_flat_brown"] = "gloopblocks:ash_pile"
gloopblocks.lava_damage_nodes["cottages:roof_flat_red"] = "gloopblocks:ash_pile"
gloopblocks.lava_damage_nodes["cottages:roof_flat_reet"] = "gloopblocks:ash_pile"
gloopblocks.lava_damage_nodes["cottages:roof_flat_straw"] = "cottages:roof_flat_reet"
gloopblocks.lava_damage_nodes["cottages:roof_flat_wood"] = "gloopblocks:ash_pile"
gloopblocks.lava_damage_nodes["cottages:straw_ground"] = "cottages:loam"
gloopblocks.lava_damage_nodes["cottages:loam"] = "default:dirt"
gloopblocks.lava_damage_nodes["cottages:feldweg"] = "default:dirt"
gloopblocks.lava_damage_nodes["cottages:feldweg_crossing"] = "default:dirt"
gloopblocks.lava_damage_nodes["cottages:feldweg_curve"] = "default:dirt"
gloopblocks.lava_damage_nodes["cottages:feldweg_end"] = "default:dirt"
gloopblocks.lava_damage_nodes["cottages:feldweg_slope"] = "default:dirt"
gloopblocks.lava_damage_nodes["cottages:feldweg_slope_long"] = "default:dirt"
gloopblocks.lava_damage_nodes["cottages:feldweg_t_junction"] = "default:dirt"
end
if minetest.get_modpath("dryplants") then
gloopblocks.lava_damage_nodes["dryplants:wetreed"] = "dryplants:reed"
gloopblocks.lava_damage_nodes["dryplants:wetreed_slab"] = "dryplants:reed_slab"
gloopblocks.lava_damage_nodes["dryplants:wetreed_roof"] = "dryplants:reed_roof"
gloopblocks.lava_damage_nodes["dryplants:wetreed_roof_corner"] = "dryplants:reed_roof_corner"
gloopblocks.lava_damage_nodes["dryplants:wetreed_roof_corner_2"] = "dryplants:reed_roof_corner_2"
gloopblocks.lava_damage_nodes["dryplants:reed"] = "gloopblocks:ash_pile"
gloopblocks.lava_damage_nodes["dryplants:reed_slab"] = "gloopblocks:ash_pile"
gloopblocks.lava_damage_nodes["dryplants:reed_roof"] = "gloopblocks:ash_pile"
gloopblocks.lava_damage_nodes["dryplants:reed_roof_corner"] = "gloopblocks:ash_pile"
gloopblocks.lava_damage_nodes["dryplants:reed_roof_corner_2"] = "gloopblocks:ash_pile"
end
if minetest.get_modpath("wool") then
gloopblocks.lava_damage_groups["wool"] = "wool:dark_grey"
end
if minetest.get_modpath("bakedclay") then
gloopblocks.lava_damage_nodes["default:clay"] = "bakedclay:dark_grey"
gloopblocks.lava_damage_groups["bakedclay"] = "bakedclay:dark_grey"
else
gloopblocks.lava_damage_nodes["default:clay"] = "gloopblocks:basalt"
end
if minetest.get_modpath("moreblocks") then
gloopblocks.lava_damage_groups["sand"] = "moreblocks:coal_glass"
else
gloopblocks.lava_damage_groups["sand"] = "default:obsidian_glass"
end
if minetest.get_modpath("farming") then
gloopblocks.lava_damage_nodes["farming:soil_wet"] = "farming:soil"
end
gloopblocks.lava_neighbors = {
{ x=-1, y=-1, z=-1 },
{ x=-1, y=-1, z= 0 },
{ x=-1, y=-1, z= 1 },
{ x=-1, y= 0, z=-1 },
{ x=-1, y= 0, z= 0 },
{ x=-1, y= 0, z= 1 },
{ x=-1, y= 1, z=-1 },
{ x=-1, y= 1, z= 0 },
{ x=-1, y= 1, z= 1 },
{ x= 0, y=-1, z=-1 },
{ x= 0, y=-1, z= 0 },
{ x= 0, y=-1, z= 1 },
{ x= 0, y= 0, z=-1 },
-- { x= 0, y= 0, z= 0 }, -- will always be the lava node, so ignore this space
{ x= 0, y= 0, z= 1 },
{ x= 0, y= 1, z=-1 },
{ x= 0, y= 1, z= 0 },
{ x= 0, y= 1, z= 1 },
{ x= 1, y=-1, z=-1 },
{ x= 1, y=-1, z= 0 },
{ x= 1, y=-1, z= 1 },
{ x= 1, y= 0, z=-1 },
{ x= 1, y= 0, z= 0 },
{ x= 1, y= 0, z= 1 },
{ x= 1, y= 1, z=-1 },
{ x= 1, y= 1, z= 0 },
{ x= 1, y= 1, z= 1 },
}
minetest.register_abm({
nodenames = {"default:lava_source", "default:lava_flowing"},
interval = 5,
chance = 2,
action = function(pos, node, active_object_count, active_object_count_wider)
local r=gloopblocks.lava_neighbors[math.random(1, 26)]
local pos2 = {
x = pos.x + r.x,
y = pos.y + r.y,
z = pos.z + r.z
}
local newnode
local chknode = minetest.get_node(pos2)
local def = minetest.registered_items[chknode.name]
if gloopblocks.lava_damage_nodes[chknode.name] then
newnode = gloopblocks.lava_damage_nodes[chknode.name]
elseif def and def.drawtype == "plantlike" then
newnode = "air"
else
for group, new in pairs(gloopblocks.lava_damage_groups) do
if minetest.get_item_group(chknode.name, group) > 0 then
newnode = new
break
end
end
end
if newnode then
minetest.set_node(pos2, {name = newnode, param2 = chknode.param2})
end
end
})
end
if minetest.get_modpath("worldedit") then
function gloopblocks.liquid_ungrief(pos1, pos2, name)
local count
local p1to2 = minetest.pos_to_string(pos1).." and "..minetest.pos_to_string(pos2)
local volume = worldedit.volume(pos1, pos2)
minetest.chat_send_player(name, "Cleaning-up lava/water griefing between "..p1to2.."...")
if volume > 1000000 then
minetest.chat_send_player(name, "This operation could affect up to "..volume.." nodes. It may take a while.")
end
minetest.log("action", name.." performs lava/water greifing cleanup between "..p1to2..".")
count = worldedit.replace(pos1, pos2, "default:lava_source", "air")
count = worldedit.replace(pos1, pos2, "default:lava_flowing", "air")
count = worldedit.replace(pos1, pos2, "default:water_source", "air")
count = worldedit.replace(pos1, pos2, "default:water_flowing", "air")
count = worldedit.replace(pos1, pos2, "default:river_water_source", "air")
count = worldedit.replace(pos1, pos2, "default:river_water_flowing", "air")
count = worldedit.replace(pos1, pos2, "gloopblocks:pumice_cooled", "air")
count = worldedit.replace(pos1, pos2, "gloopblocks:basalt_cooled", "air")
count = worldedit.replace(pos1, pos2, "gloopblocks:obsidian_cooled", "air")
count = worldedit.fixlight(pos1, pos2)
minetest.chat_send_player(name, "Operation completed.")
end
minetest.register_chatcommand("/liquid_ungrief", {
params = "[size]",
privs = {worldedit = true},
description = "Repairs greifing caused by spilling lava and water (and their \"cooling\" results)",
func = function(name, params)
local pos1 = worldedit.pos1[name]
local pos2 = worldedit.pos2[name]
if not pos1 or not pos2 then return end
gloopblocks.liquid_ungrief(pos1, pos2, name)
end
})
end

839
mods/gloopblocks/main.lua Normal file
View file

@ -0,0 +1,839 @@
-- Load support for intllib.
local MP = minetest.get_modpath(minetest.get_current_modname())
local S, NS = dofile(MP.."/intllib.lua")
-- Nodes
minetest.register_node("gloopblocks:rainbow_block_diagonal", {
description = S("Diagonal Rainbow Block"),
tiles = {"gloopblocks_rainbow_block.png"},
is_ground_content = true,
groups = {cracky=3},
sounds = default.node_sound_defaults(),
})
minetest.register_alias("gloopblocks:rainbow_block", "gloopblocks:rainbow_block_diagonal")
minetest.register_node("gloopblocks:rainbow_block_horizontal", {
description = S("Horizontal Rainbow Block"),
tiles = {
"gloopblocks_rainbow_horizontal.png^[transformR90",
"gloopblocks_rainbow_horizontal.png^[transformR90",
"gloopblocks_rainbow_horizontal.png"
},
paramtype = "light",
light_source = default.LIGHT_MAX,
paramtype2 = "facedir",
groups = {cracky = 2},
is_ground_content = false,
sounds = default.node_sound_defaults(),
})
minetest.register_node("gloopblocks:evil_block", {
description = S("Evil Block"),
tiles = {"gloopblocks_evil_block.png"},
light_source = 5,
is_ground_content = true,
groups = {cracky=2},
sounds = default.node_sound_stone_defaults(),
})
minetest.register_node("gloopblocks:basalt", {
description = S("Basalt"),
tiles = {"gloopblocks_basalt.png"},
groups = {cracky=2},
sounds = default.node_sound_stone_defaults(),
})
minetest.register_node("gloopblocks:pumice", {
description = S("Pumice"),
tiles = {"gloopblocks_pumice.png"},
groups = {cracky=3},
sounds = default.node_sound_stone_defaults(),
})
minetest.register_node("gloopblocks:pavement", {
description = S("Pavement"),
tiles = {"gloopblocks_pavement.png"},
groups = {cracky=3, oddly_breakable_by_hand=3},
sounds = default.node_sound_stone_defaults(),
})
minetest.register_node("gloopblocks:oerkki_block", {
drawtype = "nodebox",
description = S("Oerkki Block"),
paramtype = "light",
paramtype2 = "facedir",
tiles = {
"gloopblocks_oerkkiblock_tb.png",
"gloopblocks_oerkkiblock_tb.png",
"gloopblocks_oerkkiblock_sides.png",
"gloopblocks_oerkkiblock_sides.png",
"gloopblocks_oerkkiblock_sides.png",
"gloopblocks_oerkkiblock_front.png"
},
groups = {cracky=3, oddly_breakable_by_hand=3},
sounds = default.node_sound_stone_defaults(),
selection_box = {
type = "fixed",
fixed = { -0.5, -0.5, -0.5, 0.5, 0.5, 0.5 }
},
node_box = {
type = "fixed",
fixed = {
{-0.5, -0.5, -0.1875, 0.5, 0.5, 0.1875}, -- NodeBox1
{-0.5, -0.5, -0.5, -0.4375, 0.5, 0.5}, -- NodeBox2
{0.4375, -0.5, -0.5, 0.5, 0.5, 0.5}, -- NodeBox3
{-0.5, 0.4375, -0.5, 0.5, 0.5, 0.5}, -- NodeBox4
{-0.5, -0.5, -0.5, 0.5, -0.4375, 0.5}, -- NodeBox5
{-0.5, -0.0625, -0.5, 0.5, 0.0625, 0.5}, -- NodeBox6
}
}
})
minetest.register_node("gloopblocks:stone_brick_mossy", {
description = S("Mossy Stone Brick"),
tiles = {"gloopblocks_stone_brick_mossy.png"},
groups = {cracky=3, stone=1},
sounds = default.node_sound_stone_defaults(),
})
minetest.register_node("gloopblocks:stone_mossy", {
description = S("Mossy Stone"),
tiles = {"gloopblocks_stone_mossy.png"},
groups = {cracky=3, stone=1},
sounds = default.node_sound_stone_defaults(),
drop = "default:mossycobble"
})
minetest.register_node("gloopblocks:cobble_road", {
description = S("Cobblestone Road Bed"),
tiles = {"gloopblocks_cobble_road.png"},
groups = {cracky=3, stone=1},
sounds = default.node_sound_stone_defaults(),
})
minetest.register_node("gloopblocks:cobble_road_mossy", {
description = S("Mossy Cobblestone Road Bed"),
tiles = {"gloopblocks_cobble_road_mossy.png"},
groups = {cracky=3, stone=1},
sounds = default.node_sound_stone_defaults(),
})
minetest.register_node("gloopblocks:scaffolding", {
description = S("Wooden Scaffold"),
drawtype = "allfaces",
paramtype = "light",
sunlight_propagates = true,
tiles = {"gloopblocks_scaffold.png"},
groups = {choppy=3, oddly_breakable_by_hand=3},
sounds = default.node_sound_wood_defaults(),
})
minetest.register_alias("moreblocks:oerkkiblock", "gloopblocks:oerkki_block")
minetest.register_alias("gloopblocks:obsidian", "default:obsidian")
-- Nodes imported from Usesdirt ================================================================================
if not minetest.get_modpath("usesdirt") then
local dirt_brick_tex = "default_dirt.png^gloopblocks_dirt_brick_overlay.png"
local dirt_cobble_tex = "default_cobble.png^(default_dirt.png^[mask:gloopblocks_dirt_cobble_mask.png)"
local dirt_stone_tex = "default_stone.png^(default_dirt.png^[mask:gloopblocks_dirt_stone_mask.png)"
local dirt_ladder_tex = "(default_dirt.png^[mask:gloopblocks_ladder_mask.png)^gloopblocks_ladder_overlay.png"
local dirt_brick_ladder_tex = "(("..dirt_brick_tex..")^[mask:gloopblocks_ladder_mask.png)^gloopblocks_ladder_overlay.png"
local dirt_cobble_ladder_tex = "(("..dirt_cobble_tex..")^[mask:gloopblocks_ladder_mask.png)^gloopblocks_ladder_overlay.png"
local dirt_stone_ladder_tex = "(("..dirt_stone_tex..")^[mask:gloopblocks_ladder_mask.png)^gloopblocks_ladder_overlay.png"
minetest.register_node(":usesdirt:dirt_brick", {
tiles = { dirt_brick_tex },
description = "Dirt Brick",
groups = {snappy=2,choppy=1,oddly_breakable_by_hand=2},
})
minetest.register_node(":usesdirt:dirt_brick_ladder", {
description = "Dirt Brick Ladder",
drawtype = "signlike",
tiles = { dirt_brick_ladder_tex },
inventory_image = dirt_brick_ladder_tex,
wield_image = dirt_brick_ladder_tex,
paramtype = "light",
paramtype2 = "wallmounted",
is_ground_content = true,
walkable = false,
climbable = true,
selection_box = {
type = "wallmounted",
--wall_top = = <default>
--wall_bottom = = <default>
--wall_side = = <default>
},
groups = {cracky=3, stone=2},
legacy_wallmounted = true,
})
minetest.register_craft({
output = 'usesdirt:dirt_brick_ladder 3',
recipe = {
{'usesdirt:dirt_brick', '', 'usesdirt:dirt_brick'},
{'usesdirt:dirt_brick', 'usesdirt:dirt_brick','usesdirt:dirt_brick'},
{'usesdirt:dirt_brick','','usesdirt:dirt_brick'},
}
})
default.register_fence(":usesdirt:dirt_brick_fence", {
description = "Dirt Brick Fence",
texture = dirt_brick_tex,
inventory_image = "default_fence_overlay.png^("..dirt_brick_tex..")^default_fence_overlay.png^[makealpha:255,126,126",
wield_image = "default_fence_overlay.png^("..dirt_brick_tex..")^default_fence_overlay.png^[makealpha:255,126,126",
material = "usesdirt:dirt_brick",
groups = {cracky=3, stone=2},
sounds = default.node_sound_stone_defaults(),
check_for_pole = true
})
if minetest.get_modpath("moreblocks") then
minetest.register_craft({
output = 'usesdirt:dirt_brick 24',
recipe = {
{'moreblocks:dirt_compressed', 'moreblocks:dirt_compressed', '' },
{'moreblocks:dirt_compressed', 'moreblocks:dirt_compressed', '' }
}
})
else
minetest.register_craft({
output = 'usesdirt:dirt_brick 6',
recipe = {
{'default:dirt', 'default:dirt', 'default:dirt'},
{'default:dirt', 'default:dirt', 'default:dirt'},
{'default:dirt', 'default:dirt', 'default:dirt'},
}
})
end
minetest.register_node(":usesdirt:dirt_ladder", {
description = "Dirt Ladder",
drawtype = "signlike",
tiles = { dirt_ladder_tex },
inventory_image = dirt_ladder_tex,
wield_image = dirt_ladder_tex,
paramtype = "light",
paramtype2 = "wallmounted",
is_ground_content = true,
walkable = false,
climbable = true,
selection_box = {
type = "wallmounted",
--wall_top = = <default>
--wall_bottom = = <default>
--wall_side = = <default>
},
groups = {snappy=2,choppy=2,oddly_breakable_by_hand=3},
legacy_wallmounted = true,
})
minetest.register_craft({
output = 'usesdirt:dirt_ladder 3',
recipe = {
{'usesdirt:dirt_brick', '', 'usesdirt:dirt_brick'},
{'usesdirt:dirt_brick', 'usesdirt:dirt_brick','usesdirt:dirt_brick'},
{'usesdirt:dirt_brick','','usesdirt:dirt_brick'},
}
})
default.register_fence(":usesdirt:dirt_fence", {
description = "Dirt Fence",
texture = "default_dirt.png",
inventory_image = "default_fence_overlay.png^default_dirt.png^default_fence_overlay.png^[makealpha:255,126,126",
wield_image = "default_fence_overlay.png^default_dirt.png^default_fence_overlay.png^[makealpha:255,126,126",
material = "default:dirt",
groups = {snappy=2,choppy=1,oddly_breakable_by_hand=3},
sounds = default.node_sound_dirt_defaults(),
check_for_pole = true
})
----
minetest.register_node(":usesdirt:dirt_cobble_stone", {
tiles = { dirt_cobble_tex },
description = "Dirt Cobble Stone",
is_ground_content = true,
groups = {cracky=3, stone=2},
})
minetest.register_craft({
output = '"usesdirt:dirt_cobble_stone" 3',
recipe = {
{'usesdirt:dirt_brick', 'usesdirt:dirt_brick', 'usesdirt:dirt_brick'},
{'usesdirt:dirt_brick', 'usesdirt:dirt_brick', 'usesdirt:dirt_brick'},
{'usesdirt:dirt_brick', 'usesdirt:dirt_brick', 'usesdirt:dirt_brick'},
}
})
minetest.register_node(":usesdirt:dirt_cobble_stone_ladder", {
description = "Dirt Cobble Stone Ladder",
drawtype = "signlike",
tiles = { dirt_cobble_ladder_tex },
inventory_image = dirt_cobble_ladder_tex,
wield_image = dirt_cobble_ladder_tex,
paramtype = "light",
paramtype2 = "wallmounted",
is_ground_content = true,
walkable = false,
climbable = true,
selection_box = {
type = "wallmounted",
--wall_top = = <default>
--wall_bottom = = <default>
--wall_side = = <default>
},
groups = {cracky=3, stone=2},
legacy_wallmounted = true,
})
minetest.register_craft({
output = 'usesdirt:dirt_cobble_stone_ladder 3',
recipe = {
{'usesdirt:dirt_cobble_stone', '', 'usesdirt:dirt_cobble_stone'},
{'usesdirt:dirt_cobble_stone', 'usesdirt:dirt_cobble_stone','usesdirt:dirt_cobble_stone'},
{'usesdirt:dirt_cobble_stone','','usesdirt:dirt_cobble_stone'},
}
})
default.register_fence(":usesdirt:dirt_cobble_stone_fence", {
description = "Dirt Cobble Stone Fence",
texture = dirt_cobble_tex,
inventory_image = "default_fence_overlay.png^("..dirt_cobble_tex..")^default_fence_overlay.png^[makealpha:255,126,126",
wield_image = "default_fence_overlay.png^("..dirt_cobble_tex..")^default_fence_overlay.png^[makealpha:255,126,126",
material = "usesdirt:dirt_cobble_stone",
groups = {cracky=3, stone=2},
sounds = default.node_sound_stone_defaults(),
check_for_pole = true
})
----
minetest.register_node(":usesdirt:dirt_stone", {
tiles = { dirt_stone_tex },
description = "Dirt Stone",
is_ground_content = true,
groups = {cracky=3, stone=2},
})
minetest.register_node(":usesdirt:dirt_stone_ladder", {
description = "Dirt Stone Ladder",
drawtype = "signlike",
tiles = { dirt_stone_ladder_tex },
inventory_image = dirt_stone_ladder_tex,
wield_image = dirt_stone_ladder_tex,
paramtype = "light",
paramtype2 = "wallmounted",
is_ground_content = true,
walkable = false,
climbable = true,
selection_box = {
type = "wallmounted",
--wall_top = = <default>
--wall_bottom = = <default>
--wall_side = = <default>
},
groups = {cracky=3, stone=2},
legacy_wallmounted = true,
})
minetest.register_craft({
output = 'usesdirt:dirt_stone_ladder 3',
recipe = {
{'usesdirt:dirt_stone', '', 'usesdirt:dirt_stone'},
{'usesdirt:dirt_stone', 'usesdirt:dirt_stone','usesdirt:dirt_stone'},
{'usesdirt:dirt_stone','','usesdirt:dirt_stone'},
}
})
default.register_fence(":usesdirt:dirt_stone_fence", {
description = "Dirt Stone Fence",
texture = dirt_stone_tex,
inventory_image = "default_fence_overlay.png^("..dirt_stone_tex..")^default_fence_overlay.png^[makealpha:255,126,126",
wield_image = "default_fence_overlay.png^("..dirt_stone_tex..")^default_fence_overlay.png^[makealpha:255,126,126",
material = "usesdirt:dirt_stone",
groups = {cracky=3, stone=2},
sounds = default.node_sound_stone_defaults(),
check_for_pole = true
})
end
-- Stairs/slabs defs, conversion of normal -> mossy items
if minetest.setting_getbool("gloopblocks_mossy_conversion") ~= false then
function gloopblocks_register_mossy_conversion(mossyobjects)
for i in ipairs(mossyobjects) do
minetest.register_abm({
nodenames = { mossyobjects[i][1] },
neighbors = {"default:water_source", "default:water_flowing"},
interval = 120,
chance = 50,
action = function(pos, node)
if minetest.find_node_near(pos, 2, "air") then
local fdir = node.param2
minetest.add_node(pos, {name = mossyobjects[i][2], param2 = fdir})
end
end,
})
end
end
end
if minetest.get_modpath("moreblocks") then
stairsplus:register_all("gloopblocks", "oerkki_block", "gloopblocks:oerkki_block", {
description = S("Oerkki Block"),
tiles = {
"gloopblocks_oerkkiblock_tb.png",
"gloopblocks_oerkkiblock_tb.png",
"gloopblocks_oerkkiblock_sides.png",
"gloopblocks_oerkkiblock_sides.png",
"gloopblocks_oerkkiblock_sides.png",
"gloopblocks_oerkkiblock_front.png"
},
groups = {cracky=2, not_in_creative_inventory=1},
sounds = default.node_sound_stone_defaults(),
sunlight_propagates = true,
})
stairsplus:register_all("gloopblocks", "stone_brick_mossy", "gloopblocks:stone_brick_mossy", {
description = S("Mossy Stone Brick"),
tiles = {"gloopblocks_stone_brick_mossy.png"},
groups = {cracky=1, not_in_creative_inventory=1},
sounds = default.node_sound_stone_defaults(),
sunlight_propagates = true,
})
stairsplus:register_all("gloopblocks", "stone_mossy", "gloopblocks:stone_mossy", {
description = S("Mossy Stone"),
tiles = {"gloopblocks_stone_mossy.png"},
groups = {cracky=1, not_in_creative_inventory=1},
sounds = default.node_sound_stone_defaults(),
sunlight_propagates = true,
})
stairsplus:register_all("gloopblocks", "cobble_road", "gloopblocks:cobble_road", {
description = S("Cobblestone Roadbed"),
tiles = {"gloopblocks_cobble_road.png"},
groups = {cracky=3, stone=1, not_in_creative_inventory=1},
sounds = default.node_sound_stone_defaults(),
sunlight_propagates = true,
})
stairsplus:register_all("gloopblocks", "cobble_road_mossy", "gloopblocks:cobble_road_mossy", {
description = S("Mossy Cobblestone Roadbed"),
tiles = {"gloopblocks_cobble_road_mossy.png"},
groups = {cracky=3, stone=1, not_in_creative_inventory=1},
sounds = default.node_sound_stone_defaults(),
sunlight_propagates = true,
})
stairsplus:register_all("gloopblocks", "pavement", "gloopblocks:pavement", {
description = S("Pavement"),
tiles = {"gloopblocks_pavement.png"},
groups = {cracky=2, not_in_creative_inventory=1},
sounds = default.node_sound_stone_defaults(),
sunlight_propagates = true,
})
stairsplus:register_all("gloopblocks", "rainbow_block", "gloopblocks:rainbow_block", {
description = S("Rainbow Block"),
tiles = {"gloopblocks_rainbow_block.png"},
groups = {cracky=3, not_in_creative_inventory=1},
sounds = default.node_sound_defaults(),
sunlight_propagates = true,
})
stairsplus:register_all("gloopblocks", "evil_block", "gloopblocks:evil_block", {
description = S("Evil Block"),
tiles = {"gloopblocks_evil_block.png"},
groups = {cracky=3, not_in_creative_inventory=1},
sounds = default.node_sound_defaults(),
light_source = 5,
sunlight_propagates = true,
})
stairsplus:register_all("gloopblocks", "basalt", "gloopblocks:basalt", {
description = S("Basalt"),
tiles = {"gloopblocks_basalt.png"},
groups = {cracky=2, not_in_creative_inventory=1},
sounds = default.node_sound_stone_defaults(),
sunlight_propagates = true,
})
stairsplus:register_all("gloopblocks", "pumice", "gloopblocks:pumice", {
description = S("Pumice"),
tiles = {"gloopblocks_pumice.png"},
groups = {cracky=3, not_in_creative_inventory=1},
sounds = default.node_sound_stone_defaults(),
sunlight_propagates = true,
})
stairsplus:register_all("gloopblocks", "gravel", "default:gravel", {
description = S("Gravel"),
tiles = {"default_gravel.png"},
groups = {crumbly = 2, falling_node = 1, not_in_creative_inventory=1},
sounds = default.node_sound_stone_defaults(),
sunlight_propagates = false,
})
if minetest.get_modpath("caverealms") then
stairsplus:register_all("caverealms", "glow_crystal", "caverealms:glow_crystal", {
description = S("Glow Crystal"),
tiles = {"caverealms_glow_crystal.png"},
groups = {cracky=3, not_in_creative_inventory=1},
sounds = default.node_sound_glass_defaults(),
light_source = 12,
use_texture_alpha = true,
paramtype="light",
sunlight_propagates = true,
})
stairsplus:register_all("caverealms", "glow_emerald", "caverealms:glow_emerald", {
description = S("Glow Emerald"),
tiles = {"caverealms_glow_emerald.png"},
groups = {cracky=3, not_in_creative_inventory=1},
sounds = default.node_sound_glass_defaults(),
light_source = 12,
use_texture_alpha = true,
paramtype="light",
sunlight_propagates = true,
})
stairsplus:register_all("caverealms", "glow_mese", "caverealms:glow_mese", {
description = S("Glow Mese"),
tiles = {"caverealms_glow_mese.png"},
groups = {cracky=3, not_in_creative_inventory=1},
sounds = default.node_sound_glass_defaults(),
light_source = 12,
use_texture_alpha = true,
paramtype="light",
sunlight_propagates = true,
})
end
-- ABMs for mossy objects
if minetest.setting_getbool("gloopblocks_mossy_conversion") ~= false then
gloopblocks_register_mossy_conversion({
{ "moreblocks:stair_cobble", "moreblocks:stair_mossycobble" },
{ "moreblocks:stair_cobble_inner", "moreblocks:stair_mossycobble_inner" },
{ "moreblocks:stair_cobble_outer", "moreblocks:stair_mossycobble_outer" },
{ "moreblocks:stair_cobble_half", "moreblocks:stair_mossycobble_half" },
{ "moreblocks:slab_cobble_quarter", "moreblocks:slab_mossycobble_quarter" },
{ "moreblocks:slab_cobble", "moreblocks:slab_mossycobble" },
{ "moreblocks:slab_cobble_three_quarter", "moreblocks:slab_mossycobble_three_quarter" },
{ "moreblocks:panel_cobble", "moreblocks:panel_mossycobble" },
{ "moreblocks:micro_cobble", "moreblocks:micro_mossycobble" },
{ "moreblocks:stair_cobble_alt", "moreblocks:stair_mossycobble_alt" },
{ "gloopblocks:cobble_road", "gloopblocks:cobble_road_mossy" },
{ "gloopblocks:stair_cobble_road", "gloopblocks:stair_cobble_road_mossy" },
{ "gloopblocks:slab_cobble_road", "gloopblocks:slab_cobble_road_mossy" },
{ "gloopblocks:stair_cobble_road", "gloopblocks:stair_cobble_road_mossy" },
{ "gloopblocks:stair_cobble_road_inner", "gloopblocks:stair_cobble_road_mossy_inner" },
{ "gloopblocks:stair_cobble_road_outer", "gloopblocks:stair_cobble_road_mossy_outer" },
{ "gloopblocks:stair_cobble_road_half", "gloopblocks:stair_cobble_road_mossy_half" },
{ "gloopblocks:slab_cobble_road_quarter", "gloopblocks:slab_cobble_road_mossy_quarter" },
{ "gloopblocks:slab_cobble_road", "gloopblocks:slab_cobble_road_mossy" },
{ "gloopblocks:slab_cobble_road_three_quarter", "gloopblocks:slab_cobble_road_mossy_three_quarter" },
{ "gloopblocks:panel_cobble_road", "gloopblocks:panel_cobble_road_mossy" },
{ "gloopblocks:micro_cobble_road", "gloopblocks:micro_cobble_road_mossy" },
{ "gloopblocks:stair_cobble_road_alt", "gloopblocks:stair_cobble_road_mossy_alt" },
{ "default:stonebrick", "gloopblocks:stone_brick_mossy" },
{ "default:stair_stonebrick", "gloopblocks:stair_stone_brick_mossy" },
{ "default:slab_stonebrick", "gloopblocks:slab_stone_brick_mossy" },
{ "moreblocks:stair_stonebrick", "gloopblocks:stair_stone_brick_mossy" },
{ "moreblocks:stair_stonebrick_inner", "gloopblocks:stair_stone_brick_mossy_inner" },
{ "moreblocks:stair_stonebrick_outer", "gloopblocks:stair_stone_brick_mossy_outer" },
{ "moreblocks:stair_stonebrick_half", "gloopblocks:stair_stone_brick_mossy_half" },
{ "moreblocks:slab_stonebrick_quarter", "gloopblocks:slab_stone_brick_mossy_quarter" },
{ "moreblocks:slab_stonebrick", "gloopblocks:slab_stone_brick_mossy" },
{ "moreblocks:slab_stonebrick_three_quarter", "gloopblocks:slab_stone_brick_mossy_three_quarter" },
{ "moreblocks:panel_stonebrick", "gloopblocks:panel_stone_brick_mossy" },
{ "moreblocks:micro_stonebrick", "gloopblocks:micro_stone_brick_mossy" },
{ "moreblocks:stair_stonebrick_alt", "gloopblocks:stair_stone_brick_mossy_alt" },
{ "default:stone", "gloopblocks:stone_mossy" },
{ "default:stair_stone", "gloopblocks:stair_stone_mossy" },
{ "default:slab_stone", "gloopblocks:slab_stone_mossy" },
{ "moreblocks:stair_stone", "gloopblocks:stair_stone_mossy" },
{ "moreblocks:stair_stone_inner", "gloopblocks:stair_stone_mossy_inner" },
{ "moreblocks:stair_stone_outer", "gloopblocks:stair_stone_mossy_outer" },
{ "moreblocks:stair_stone_half", "gloopblocks:stair_stone_mossy_half" },
{ "moreblocks:slab_stone_quarter", "gloopblocks:slab_stone_mossy_quarter" },
{ "moreblocks:slab_stone", "gloopblocks:slab_stone_mossy" },
{ "moreblocks:slab_stone_three_quarter", "gloopblocks:slab_stone_mossy_three_quarter" },
{ "moreblocks:panel_stone", "gloopblocks:panel_stone_mossy" },
{ "moreblocks:micro_stone", "gloopblocks:micro_stone_mossy" },
{ "moreblocks:stair_stone_alt", "gloopblocks:stair_stone_mossy_alt" },
})
end
elseif minetest.get_modpath("stairs") then
--stairs.register_stair(subname, recipeitem, groups, images, description, sounds)
-- stairs:xxxx_stone_mossy ; xxxx = stair or slab
stairs.register_stair_and_slab("stone_mossy", "gloopblocks:stone_mossy",
{cracky=3},
{"gloopblocks_stone_mossy.png"},
S("Mossy Stone Stair"),
S("Mossy Stone Slab"),
default.node_sound_stone_defaults())
-- stairs:xxxx_mossycobble
stairs.register_stair_and_slab("mossycobble", "default:mossycobble",
{cracky=3},
{"default_mossycobble.png"},
S("Mossy Cobble Stair"),
S("Mossy Cobble Slab"),
default.node_sound_stone_defaults())
-- stairs:xxxx_stone_brick_mossy
stairs.register_stair_and_slab("stone_brick_mossy", "gloopblocks:stone_brick_mossy",
{cracky=3},
{"gloopblocks_stone_brick_mossy.png"},
S("Mossy Stone Brick Stair"),
S("Mossy Stone Brick Slab"),
default.node_sound_stone_defaults())
-- stairs:xxxx_cobble_road
stairs.register_stair_and_slab("cobble_road", "gloopblocks:cobble_road",
{cracky=3},
{"gloopblocks_cobble_road.png"},
S("Cobble Roadbed Stair"),
S("Cobble Roadbed Slab"),
default.node_sound_stone_defaults())
-- stairs:xxxx_cobble_road_mossy
stairs.register_stair_and_slab("cobble_road_mossy", "gloopblocks:cobble_road_mossy",
{cracky=3},
{"gloopblocks_cobble_road_mossy.png"},
S("Mossy Cobble Roadbed Stair"),
S("Mossy Cobble Roadbed Slab"),
default.node_sound_stone_defaults())
-- stairs:xxxx_cement
stairs.register_stair_and_slab("cement", "gloopblocks:cement",
{cracky=2},
{"basic_materials_cement_block.png"},
S("Cement Stair"),
S("Cement Slab"),
default.node_sound_stone_defaults())
-- stairs:xxxx_pavement
stairs.register_stair_and_slab("pavement", "gloopblocks:pavement",
{cracky=3, oddly_breakable_by_hand=3},
{"gloopblocks_pavement.png"},
S("Pavement Stair"),
S("Pavement Slab"),
default.node_sound_stone_defaults())
stairs.register_stair_and_slab("basalt", "gloopblocks:basalt",
{cracky=2},
{"gloopblocks_basalt.png"},
S("Basalt Stair"),
S("Basalt Slab"),
default.node_sound_stone_defaults())
stairs.register_stair_and_slab("pumice", "gloopblocks:pumice",
{cracky=3},
{"gloopblocks_pumice.png"},
S("Pumice Stair"),
S("Pumice Slab"),
default.node_sound_stone_defaults())
stairs.register_stair_and_slab("rainbow_block", "gloopblocks:rainbow_block",
{cracky=3},
{"gloopblocks_rainbow_block.png"},
S("Rainbow Block Stair"),
S("Rainbow Block Slab"),
default.node_sound_defaults())
if minetest.setting_getbool("gloopblocks_mossy_conversion") ~= false then
gloopblocks_register_mossy_conversion({
{ "default:cobble", "default:mossycobble" },
{ "stairs:stair_cobble", "stairs:stair_mossycobble" },
{ "stairs:slab_cobble", "stairs:slab_mossycobble" },
{ "gloopblocks:cobble_road", "gloopblocks:cobble_road_mossy" },
{ "stairs:stair_cobble_road", "stairs:stair_cobble_road_mossy" },
{ "stairs:slab_cobble_road", "stairs:slab_cobble_road_mossy" },
{ "default:stonebrick", "gloopblocks:stone_brick_mossy" },
{ "stairs:stair_stonebrick", "stairs:stair_stone_brick_mossy" },
{ "stairs:slab_stonebrick", "stairs:slab_stone_brick_mossy" },
{ "default:stone", "gloopblocks:stone_mossy" },
{ "stairs:stair_stone", "stairs:stair_stone_mossy" },
{ "stairs:slab_stone", "stairs:slab_stone_mossy" },
})
end
minetest.register_alias("default:stair_mossycobble", "stairs:stair_mossycobble")
minetest.register_alias("default:slab_mossycobble", "stairs:slab_mossycobble")
minetest.register_alias("gloopblocks:stair_cobble_road", "stairs:stair_cobble_road")
minetest.register_alias("gloopblocks:slab_cobble_road", "stairs:slab_cobble_road")
minetest.register_alias("gloopblocks:stair_cobble_road_mossy", "stairs:stair_cobble_road_mossy")
minetest.register_alias("gloopblocks:slab_cobble_road_mossy", "stairs:slab_cobble_road_mossy")
minetest.register_alias("gloopblocks:stair_stone_brick_mossy", "stairs:stair_stone_brick_mossy")
minetest.register_alias("gloopblocks:slab_stone_brick_mossy", "stairs:slab_stone_brick_mossy")
minetest.register_alias("gloopblocks:stair_stone_mossy", "stairs:stair_stone_mossy")
minetest.register_alias("gloopblocks:slab_stone_mossy", "stairs:slab_stone_mossy")
minetest.register_alias("gloopblocks:stair_cement", "stairs:stair_cement")
minetest.register_alias("gloopblocks:slab_cement", "stairs:slab_cement")
minetest.register_alias("gloopblocks:stair_pavement", "stairs:stair_pavement")
minetest.register_alias("gloopblocks:slab_pavement", "stairs:slab_pavement")
minetest.register_alias("gloopblocks:stair_pumice", "stairs:stair_pumice")
minetest.register_alias("gloopblocks:slab_pumice", "stairs:slab_pumice")
minetest.register_alias("gloopblocks:stair_basalt", "stairs:stair_basalt")
minetest.register_alias("gloopblocks:slab_basalt", "stairs:slab_basalt")
minetest.register_alias("gloopblocks:stair_rainbow_block", "stairs:stair_rainbow_block")
minetest.register_alias("gloopblocks:slab_rainbow_block", "stairs:slab_rainbow_block")
end
-- Tools
minetest.register_tool("gloopblocks:pick_cement", {
description = S("Cement Pickaxe"),
inventory_image = "gloopblocks_cement_pick.png",
tool_capabilities = {
full_punch_interval = 1.0,
max_drop_level=1,
groupcaps={
cracky={times={[1]=3.50, [2]=1.40, [3]=0.90}, uses=25, maxlevel=2}
},
damage_groups = {fleshy=4},
},
})
minetest.register_tool("gloopblocks:shovel_cement", {
description = S("Cement Shovel"),
inventory_image = "gloopblocks_cement_shovel.png",
tool_capabilities = {
full_punch_interval = 1.0,
max_drop_level=1,
groupcaps={
crumbly={times={[1]=1.50, [2]=0.60, [3]=0.45}, uses=25, maxlevel=2}
},
damage_groups = {fleshy=4},
},
})
minetest.register_tool("gloopblocks:axe_cement", {
description = S("Cement Axe"),
inventory_image = "gloopblocks_cement_axe.png",
tool_capabilities = {
full_punch_interval = 1.0,
max_drop_level=1,
groupcaps={
choppy={times={[1]=3.00, [2]=1.30, [3]=0.80}, uses=25, maxlevel=2},
fleshy={times={[2]=1.20, [3]=0.65}, uses=30, maxlevel=1}
},
damage_groups = {fleshy=4},
},
})
minetest.register_tool("gloopblocks:sword_cement", {
description = S("Cement Sword"),
inventory_image = "gloopblocks_cement_sword.png",
tool_capabilities = {
full_punch_interval = 1.0,
max_drop_level=1,
groupcaps={
fleshy={times={[1]=1.60, [2]=0.80, [3]=0.40}, uses=15, maxlevel=2},
snappy={times={[2]=0.75, [3]=0.35}, uses=30, maxlevel=1},
choppy={times={[3]=0.80}, uses=30, maxlevel=0}
},
damage_groups = {fleshy=6},
}
})
minetest.register_tool("gloopblocks:pick_evil", {
description = S("Evil Pickaxe"),
inventory_image = "gloopblocks_evil_pick.png",
tool_capabilities = {
full_punch_interval = 1.0,
max_drop_level=3,
groupcaps={
cracky={times={[1]=0.10, [2]=0.10, [3]=0.10}, uses=10, maxlevel=2}
},
damage_groups = {fleshy=6},
},
})
minetest.register_tool("gloopblocks:shovel_evil", {
description = S("Evil Shovel"),
inventory_image = "gloopblocks_evil_shovel.png",
tool_capabilities = {
full_punch_interval = 1.0,
max_drop_level=3,
groupcaps={
crumbly={times={[1]=0.05, [2]=0.05, [3]=0.05}, uses=10, maxlevel=2}
},
damage_groups = {fleshy=6},
},
})
minetest.register_tool("gloopblocks:axe_evil", {
description = S("Evil Axe"),
inventory_image = "gloopblocks_evil_axe.png",
tool_capabilities = {
full_punch_interval = 1.0,
max_drop_level=3,
groupcaps={
choppy={times={[1]=0.15, [2]=0.15, [3]=0.15}, uses=10, maxlevel=2},
fleshy={times={[1]=0.15, [2]=0.15, [3]=0.15}, uses=10, maxlevel=2}
},
damage_groups = {fleshy=6},
},
})
minetest.register_tool("gloopblocks:sword_evil", {
description = S("Evil Sword"),
inventory_image = "gloopblocks_evil_sword.png",
tool_capabilities = {
full_punch_interval = 1.0,
max_drop_level=3,
groupcaps={
fleshy={times={[1]=0.20, [2]=0.20, [3]=0.20}, uses=10, maxlevel=2},
snappy={times={[1]=0.20, [2]=0.20, [3]=0.20}, uses=10, maxlevel=2},
choppy={times={[1]=0.20, [2]=0.20, [3]=0.20}, uses=10, maxlevel=2}
},
damage_groups = {fleshy=8},
}
})
-- Other items
minetest.register_craftitem("gloopblocks:evil_stick", {
description = S("Evil Stick"),
inventory_image = "gloopblocks_evil_stick.png",
})
local fence_texture =
"default_fence_overlay.png^default_steel_block.png^default_fence_overlay.png^[makealpha:255,126,126"
minetest.register_node("gloopblocks:fence_steel", {
description = S("Steel Fence"),
drawtype = "fencelike",
tiles = {"default_steel_block.png"},
inventory_image = fence_texture,
wield_image = fence_texture,
paramtype = "light",
sunlight_propagates = true,
is_ground_content = false,
selection_box = {
type = "fixed",
fixed = {-1/7, -1/2, -1/7, 1/7, 1/2, 1/7},
},
groups = {choppy = 2, oddly_breakable_by_hand = 2 },
sounds = default.node_sound_stone_defaults(),
})
minetest.register_alias("nyancat:nyancat_rainbow", "gloopblocks:rainbow_block_horizontal")
minetest.register_alias("default:nyancat_rainbow", "gloopblocks:rainbow_block_horizontal")

View file

@ -0,0 +1,197 @@
# Blender v2.73 (sub 0) OBJ File: 'anthill.blend'
# www.blender.org
o Cylinder_Cylinder.001
v 0.099056 -0.499969 -0.498228
v 0.038417 -0.200463 -0.141682
v 0.255808 -0.499933 -0.402046
v 0.095605 -0.174690 -0.147239
v 0.423075 -0.499913 -0.296918
v 0.102439 -0.169033 -0.075679
v 0.444026 -0.499843 -0.095234
v 0.125298 -0.217477 -0.063343
v 0.468682 -0.499958 0.074790
v 0.157655 -0.214352 0.001348
v 0.396548 -0.500000 0.246048
v 0.133778 -0.189245 0.108513
v 0.280708 -0.500000 0.383197
v 0.070517 -0.218946 0.104754
v 0.089852 -0.499943 0.434316
v 0.048523 -0.205247 0.128681
v -0.093309 -0.499902 0.467111
v -0.039037 -0.211895 0.149030
v -0.272965 -0.499875 0.396496
v -0.108297 -0.175918 0.104100
v -0.388317 -0.499877 0.239075
v -0.139068 -0.179051 0.073370
v -0.437531 -0.499999 0.063918
v -0.141812 -0.255882 0.005117
v -0.458429 -0.499805 -0.104397
v -0.189265 -0.217436 -0.065303
v -0.385597 -0.499914 -0.288584
v -0.112692 -0.207830 -0.096879
v -0.248347 -0.499927 -0.384586
v -0.083136 -0.202256 -0.170048
v -0.095346 -0.499958 -0.514449
v -0.023049 -0.216681 -0.204058
v 0.071880 -0.343843 -0.343933
v 0.189128 -0.354687 -0.277980
v 0.311273 -0.378789 -0.248498
v 0.296760 -0.346318 -0.056661
v 0.332231 -0.342427 0.044933
v 0.259921 -0.360316 0.147910
v 0.213270 -0.362883 0.253745
v 0.059007 -0.360067 0.351374
v -0.068448 -0.357957 0.335642
v -0.164888 -0.343166 0.232553
v -0.269761 -0.352370 0.140734
v -0.367168 -0.370891 0.062326
v -0.294491 -0.324099 -0.079712
v -0.276314 -0.352585 -0.236032
v -0.206169 -0.372829 -0.314307
v -0.065547 -0.371444 -0.355380
v 0.000709 -0.156135 -0.047193
vt 0.572002 0.826281
vt 0.535907 0.620231
vt 0.597591 0.625892
vt 0.604963 0.552988
vt 0.830215 0.729053
vt 0.629619 0.540419
vt 0.814561 0.533613
vt 0.664520 0.474514
vt 0.638766 0.365335
vt 0.774826 0.325198
vt 0.570532 0.369165
vt 0.724507 0.217375
vt 0.546808 0.344788
vt 0.452364 0.324057
vt 0.420641 0.133939
vt 0.377660 0.369831
vt 0.316619 0.238965
vt 0.344469 0.401138
vt 0.203502 0.332509
vt 0.341509 0.470674
vt 0.290325 0.542416
vt 0.176827 0.557096
vt 0.372919 0.574586
vt 0.196433 0.716353
vt 0.404798 0.649130
vt 0.469609 0.683778
vt 0.272092 0.796098
vt 0.770390 0.885486
vt 0.973405 0.572910
vt 0.591386 0.033412
vt 0.226599 0.867698
vt 0.423770 0.837943
vt 0.601314 0.983475
vt 0.078559 0.769893
vt 0.000000 0.582245
vt 0.098436 0.412390
vt 0.075624 0.232320
vt 0.200045 0.071942
vt 0.558116 0.117912
vt 0.922195 0.225217
vt 0.852821 0.430110
vt 0.698467 0.759089
vt 0.495235 0.523967
vt 0.391629 1.000000
vt 0.022541 0.410768
vt 0.797247 0.085491
vt 0.393825 0.000000
vt 0.950807 0.778383
vt 1.000000 0.399692
g Cylinder_Cylinder.001_None
s 1
f 33/1 2/2 4/3
f 4/3 6/4 35/5
f 35/5 6/4 8/6
f 36/7 8/6 10/8
f 10/8 12/9 38/10
f 12/9 14/11 39/12
f 39/12 14/11 16/13
f 16/13 18/14 41/15
f 18/14 20/16 42/17
f 20/16 22/18 43/19
f 43/19 22/18 24/20
f 24/20 26/21 45/22
f 26/21 28/23 46/24
f 46/24 28/23 30/25
f 32/26 2/2 33/1
f 47/27 30/25 32/26
f 3/28 7/29 15/30
f 29/31 47/27 48/32
f 48/32 33/1 1/33
f 27/34 46/24 47/27
f 25/35 45/22 46/24
f 44/36 45/22 25/35
f 21/37 43/19 44/36
f 42/17 43/19 21/37
f 41/15 42/17 19/38
f 15/30 40/39 41/15
f 39/12 40/39 15/30
f 11/40 38/10 39/12
f 37/41 38/10 11/40
f 7/29 36/7 37/41
f 35/5 36/7 7/29
f 3/28 34/42 35/5
f 33/1 34/42 3/28
f 4/3 2/2 49/43
f 2/2 32/26 49/43
f 32/26 30/25 49/43
f 30/25 28/23 49/43
f 28/23 26/21 49/43
f 26/21 24/20 49/43
f 24/20 22/18 49/43
f 22/18 20/16 49/43
f 20/16 18/14 49/43
f 18/14 16/13 49/43
f 16/13 14/11 49/43
f 14/11 12/9 49/43
f 12/9 10/8 49/43
f 10/8 8/6 49/43
f 8/6 6/4 49/43
f 6/4 4/3 49/43
f 34/42 33/1 4/3
f 34/42 4/3 35/5
f 36/7 35/5 8/6
f 37/41 36/7 10/8
f 37/41 10/8 38/10
f 38/10 12/9 39/12
f 40/39 39/12 16/13
f 40/39 16/13 41/15
f 41/15 18/14 42/17
f 42/17 20/16 43/19
f 44/36 43/19 24/20
f 44/36 24/20 45/22
f 45/22 26/21 46/24
f 47/27 46/24 30/25
f 48/32 32/26 33/1
f 48/32 47/27 32/26
f 29/31 31/44 1/33
f 23/45 27/34 29/31
f 27/34 23/45 25/35
f 15/30 19/38 21/37
f 11/40 13/46 15/30
f 15/30 17/47 19/38
f 3/28 23/45 29/31
f 29/31 1/33 3/28
f 3/28 5/48 7/29
f 7/29 9/49 11/40
f 23/45 15/30 21/37
f 15/30 23/45 3/28
f 15/30 7/29 11/40
f 31/44 29/31 48/32
f 31/44 48/32 1/33
f 29/31 27/34 47/27
f 27/34 25/35 46/24
f 23/45 44/36 25/35
f 23/45 21/37 44/36
f 19/38 42/17 21/37
f 17/47 41/15 19/38
f 17/47 15/30 41/15
f 13/46 39/12 15/30
f 13/46 11/40 39/12
f 9/49 37/41 11/40
f 9/49 7/29 37/41
f 5/48 35/5 7/29
f 5/48 3/28 35/5
f 1/33 33/1 3/28

Binary file not shown.

After

Width:  |  Height:  |  Size: 564 B

View file

@ -27,7 +27,7 @@ for _, c in ipairs(bookcolors) do
local color, hue = unpack(c) local color, hue = unpack(c)
local function book_dig(pos, node, digger) local function book_dig(pos, node, digger)
if minetest.is_protected(pos, digger:get_player_name()) then return end if not digger or minetest.is_protected(pos, digger:get_player_name()) then return end
local meta = minetest.get_meta(pos) local meta = minetest.get_meta(pos)
local data = minetest.serialize({ local data = minetest.serialize({
title = meta:get_string("title") or "", title = meta:get_string("title") or "",

View file

@ -162,7 +162,10 @@ if minetest.get_modpath("digilines") then
if puncher:get_player_control().sneak then if puncher:get_player_control().sneak then
local name = puncher:get_player_name() local name = puncher:get_player_name()
player_last_clicked[name] = pos player_last_clicked[name] = pos
local form = "field[channel;Channel;]" local form = "formspec_version[4]"..
"size[8,4]"..
"button_exit[3,2.5;2,0.5;proceed;Proceed]"..
"field[1.75,1.5;4.5,0.5;channel;Channel;]"
minetest.show_formspec(name, "homedecor:lamp_set_channel", form) minetest.show_formspec(name, "homedecor:lamp_set_channel", form)
end end
end end

View file

@ -141,7 +141,10 @@ if minetest.get_modpath("digilines") then
local name = puncher:get_player_name() local name = puncher:get_player_name()
player_last_clicked[name] = pos player_last_clicked[name] = pos
local meta = minetest.get_meta(pos) local meta = minetest.get_meta(pos)
local form = "field[channel;Channel;]" local form = "formspec_version[4]"..
"size[8,4]"..
"button_exit[3,2.5;2,0.5;proceed;Proceed]"..
"field[1.75,1.5;4.5,0.5;channel;Channel;]"
minetest.show_formspec(name, "ilights:set_channel", form) minetest.show_formspec(name, "ilights:set_channel", form)
end end
end end

View file

@ -201,7 +201,8 @@ function intllib.get_strings(modname, langcode)
local modpath = minetest.get_modpath(modname) local modpath = minetest.get_modpath(modname)
msgstr = { } msgstr = { }
for _, l in ipairs(get_locales(langcode)) do for _, l in ipairs(get_locales(langcode)) do
local t = intllib.load_strings(modpath.."/locale/"..l..".txt") or { } local t = intllib.load_strings(modpath.."/locale/"..modname.."."..l..".tr")
or intllib.load_strings(modpath.."/locale/"..l..".txt") or { }
for k, v in pairs(t) do for k, v in pairs(t) do
msgstr[k] = msgstr[k] or v msgstr[k] = msgstr[k] or v
end end

View file

@ -81,7 +81,7 @@ stepheight = 0.6,
return return
end end
local pos = self.object:get_pos() local pos = self.object:get_pos() ; if not pos then return end
minetest.add_item(pos, "mobs:egg") minetest.add_item(pos, "mobs:egg")

View file

@ -8,7 +8,7 @@ local use_cmi = minetest.global_exists("cmi")
mobs = { mobs = {
mod = "redo", mod = "redo",
version = "20210114", version = "20210206",
intllib = S, intllib = S,
invis = minetest.global_exists("invisibility") and invisibility or {} invis = minetest.global_exists("invisibility") and invisibility or {}
} }
@ -539,12 +539,10 @@ local ray_line_of_sight = function(self, pos1, pos2)
return true return true
end end
-- detect if using minetest 5.0 by searching for permafrost node
local is_50 = minetest.registered_nodes["default:permafrost"]
function mob_class:line_of_sight(pos1, pos2, stepsize) function mob_class:line_of_sight(pos1, pos2, stepsize)
if is_50 then -- only use if minetest 5.0 is detected if minetest.raycast then -- only use if minetest 5.0 is detected
return ray_line_of_sight(self, pos1, pos2) return ray_line_of_sight(self, pos1, pos2)
end end
@ -1354,11 +1352,15 @@ function mob_class:breed()
self.on_grown(self) self.on_grown(self)
else else
-- jump when fully grown so as not to fall into ground -- jump when fully grown so as not to fall into ground
self.object:set_velocity({ -- self.object:set_velocity({
x = 0, -- x = 0,
y = self.jump_height, -- y = self.jump_height,
z = 0 -- z = 0
}) -- })
local pos = self.object:get_pos() ; if not pos then return end
local ent = self.object:get_luaentity()
pos.y = pos.y + (ent.collisionbox[2] * -1) - 0.4
self.object:set_pos(pos)
end end
end end
@ -1458,6 +1460,8 @@ function mob_class:breed()
effect(pos, 15, "tnt_smoke.png", 1, 2, 2, 15, 5) effect(pos, 15, "tnt_smoke.png", 1, 2, 2, 15, 5)
end end
pos.y = pos.y + 0.5 -- spawn child a little higher
local mob = minetest.add_entity(pos, self.name) local mob = minetest.add_entity(pos, self.name)
local ent2 = mob:get_luaentity() local ent2 = mob:get_luaentity()
local textures = self.base_texture local textures = self.base_texture

View file

@ -1,4 +1,4 @@
name = mobs_sky name = mobs_bat
depends = default, mobs depends = default, mobs
optional_depends = optional_depends =
description = Adds bats into your world. description = Adds bats into your world.

View file

@ -89,8 +89,9 @@ circular_saw.names = {
} }
function circular_saw:get_cost(inv, stackname) function circular_saw:get_cost(inv, stackname)
local name = minetest.registered_aliases[stackname] or stackname
for i, item in pairs(inv:get_list("output")) do for i, item in pairs(inv:get_list("output")) do
if item:get_name() == stackname then if item:get_name() == name then
return circular_saw.cost_in_microblocks[i] return circular_saw.cost_in_microblocks[i]
end end
end end

View file

@ -19,6 +19,11 @@ minetest.register_node("woodsoils:dirt_with_leaves_1", {
sounds = default.node_sound_dirt_defaults({ sounds = default.node_sound_dirt_defaults({
footstep = {name="default_grass_footstep", gain=0.4}, footstep = {name="default_grass_footstep", gain=0.4},
}), }),
soil = {
base = "woodsoils:dirt_with_leaves_1",
dry = "farming:soil",
wet = "farming:soil_wet"
}
}) })
minetest.register_node("woodsoils:dirt_with_leaves_2", { minetest.register_node("woodsoils:dirt_with_leaves_2", {
@ -37,6 +42,11 @@ minetest.register_node("woodsoils:dirt_with_leaves_2", {
sounds = default.node_sound_dirt_defaults({ sounds = default.node_sound_dirt_defaults({
footstep = {name="default_grass_footstep", gain=0.4}, footstep = {name="default_grass_footstep", gain=0.4},
}), }),
soil = {
base = "woodsoils:dirt_with_leaves_2",
dry = "farming:soil",
wet = "farming:soil_wet"
}
}) })
minetest.register_node("woodsoils:grass_with_leaves_1", { minetest.register_node("woodsoils:grass_with_leaves_1", {
@ -55,6 +65,11 @@ minetest.register_node("woodsoils:grass_with_leaves_1", {
sounds = default.node_sound_dirt_defaults({ sounds = default.node_sound_dirt_defaults({
footstep = {name="default_grass_footstep", gain=0.4}, footstep = {name="default_grass_footstep", gain=0.4},
}), }),
soil = {
base = "woodsoils:grass_with_leaves_1",
dry = "farming:soil",
wet = "farming:soil_wet"
}
}) })
minetest.register_node("woodsoils:grass_with_leaves_2", { minetest.register_node("woodsoils:grass_with_leaves_2", {
@ -73,6 +88,11 @@ minetest.register_node("woodsoils:grass_with_leaves_2", {
sounds = default.node_sound_dirt_defaults({ sounds = default.node_sound_dirt_defaults({
footstep = {name="default_grass_footstep", gain=0.4}, footstep = {name="default_grass_footstep", gain=0.4},
}), }),
soil = {
base = "woodsoils:grass_with_leaves_2",
dry = "farming:soil",
wet = "farming:soil_wet"
}
}) })
-- For compatibility with older stuff -- For compatibility with older stuff

View file

@ -245,7 +245,7 @@ minetest.register_privilege("no_knockback", {
give_to_singleplayer = false}) give_to_singleplayer = false})
-- is player knock-back effect enabled? -- is player knock-back effect enabled?
if minetest.settings:get_bool("player_knockback") ~= false then if minetest.settings:get_bool("player_knockback") == true then
minetest.register_entity("playerplus:temp", { minetest.register_entity("playerplus:temp", {
physical = true, physical = true,

View file

@ -1,4 +1,4 @@
# If enabled players are knocked back when hit by mobs or other players # If enabled players are knocked back when hit by mobs or other players
player_knockback (Player knockback) bool true player_knockback (Player knockback) bool false
# If enabled the players use the old sneak glitch and movements # If enabled the players use the old sneak glitch and movements
old_sneak (Use old sneak glitch) bool false old_sneak (Use old sneak glitch) bool false

View file

@ -1,13 +1,13 @@
local S = protector.intllib local S = protector.intllib
local radius = (tonumber(minetest.setting_get("protector_radius")) or 5) local radius = (tonumber(minetest.settings:get("protector_radius")) or 5)
-- radius limiter (minetest cannot handle node volume of more than 4096000) -- radius limiter (minetest cannot handle node volume of more than 4096000)
if radius > 22 then radius = 22 end if radius > 22 then radius = 22 end
local hud = {} local hud = {}
local hud_timer = 0 local hud_timer = 0
local hud_interval = (tonumber(minetest.setting_get("protector_hud_interval")) or 5) local hud_interval = (tonumber(minetest.settings:get("protector_hud_interval")) or 5)
if hud_interval > 0 then if hud_interval > 0 then
minetest.register_globalstep(function(dtime) minetest.register_globalstep(function(dtime)

View file

@ -155,6 +155,7 @@ signs_lib.flip_walldir = {
-- Initialize character texture cache -- Initialize character texture cache
local ctexcache = {} local ctexcache = {}
local ctexcache_wide = {}
-- entity handling -- entity handling
@ -251,8 +252,11 @@ function signs_lib.set_obj_text(pos, text)
local text_ansi = Utf8ToAnsi(text) local text_ansi = Utf8ToAnsi(text)
local n = minetest.registered_nodes[minetest.get_node(pos).name] local n = minetest.registered_nodes[minetest.get_node(pos).name]
signs_lib.delete_objects(pos) signs_lib.delete_objects(pos)
-- only create sign entity for actual text
if text_ansi and text_ansi ~= "" then
signs_lib.spawn_entity(pos, signs_lib.make_sign_texture(split(text_ansi), pos) ) signs_lib.spawn_entity(pos, signs_lib.make_sign_texture(split(text_ansi), pos) )
end end
end
-- rotation -- rotation
@ -330,8 +334,10 @@ end
local TP = signs_lib.path .. "/textures" local TP = signs_lib.path .. "/textures"
-- Font file formatter -- Font file formatter
local CHAR_FILE = "%s_%02x.png" local CHAR_FILE = "%s_%02x.png"
local CHAR_FILE_WIDE = "%s_%s.png"
-- Fonts path -- Fonts path
local CHAR_PATH = TP .. "/" .. CHAR_FILE local CHAR_PATH = TP .. "/" .. CHAR_FILE
local CHAR_PATH_WIDE = TP .. "/" .. CHAR_FILE_WIDE
-- Lots of overkill here. KISS advocates, go away, shoo! ;) -- kaeza -- Lots of overkill here. KISS advocates, go away, shoo! ;) -- kaeza
@ -391,6 +397,7 @@ end
local function build_char_db(font_size) local function build_char_db(font_size)
local cw = {} local cw = {}
local cw_wide = {}
-- To calculate average char width. -- To calculate average char width.
local total_width = 0 local total_width = 0
@ -406,20 +413,32 @@ local function build_char_db(font_size)
end end
end end
for i = 1, #signs_lib.wide_character_codes do
local ch = signs_lib.wide_character_codes[i]
local w, h = signs_lib.read_image_size(CHAR_PATH_WIDE:format("signs_lib_font_"..font_size.."px", ch))
if w and h then
cw_wide[ch] = w
total_width = total_width + w
char_count = char_count + 1
end
end
local cbw, cbh = signs_lib.read_image_size(TP.."/signs_lib_color_"..font_size.."px_n.png") local cbw, cbh = signs_lib.read_image_size(TP.."/signs_lib_color_"..font_size.."px_n.png")
assert(cbw and cbh, "error reading bg dimensions") assert(cbw and cbh, "error reading bg dimensions")
return cw, cbw, cbh, (total_width / char_count) return cw, cbw, cbh, (total_width / char_count), cw_wide
end end
signs_lib.charwidth15, signs_lib.charwidth15,
signs_lib.colorbgw15, signs_lib.colorbgw15,
signs_lib.lineheight15, signs_lib.lineheight15,
signs_lib.avgwidth15 = build_char_db(15) signs_lib.avgwidth15,
signs_lib.charwidth_wide15 = build_char_db(15)
signs_lib.charwidth31, signs_lib.charwidth31,
signs_lib.colorbgw31, signs_lib.colorbgw31,
signs_lib.lineheight31, signs_lib.lineheight31,
signs_lib.avgwidth31 = build_char_db(31) signs_lib.avgwidth31,
signs_lib.charwidth_wide31 = build_char_db(31)
local sign_groups = {choppy=2, dig_immediate=2} local sign_groups = {choppy=2, dig_immediate=2}
local fences_with_sign = { } local fences_with_sign = { }
@ -455,7 +474,22 @@ local function char_tex(font_name, ch)
end end
end end
local function make_line_texture(line, lineno, pos, line_width, line_height, cwidth_tab, font_size, colorbgw) local function char_tex_wide(font_name, ch)
if ctexcache_wide[font_name..ch] then
return ctexcache_wide[font_name..ch], true
else
local exists, tex = file_exists(CHAR_PATH_WIDE:format(font_name, ch))
if exists then
tex = CHAR_FILE_WIDE:format(font_name, ch)
else
tex = CHAR_FILE:format(font_name, 0x5f)
end
ctexcache_wide[font_name..ch] = tex
return tex, exists
end
end
local function make_line_texture(line, lineno, pos, line_width, line_height, cwidth_tab, font_size, colorbgw, cwidth_tab_wide)
local width = 0 local width = 0
local maxw = 0 local maxw = 0
local font_name = "signs_lib_font_"..font_size.."px" local font_name = "signs_lib_font_"..font_size.."px"
@ -492,6 +526,27 @@ local function make_line_texture(line, lineno, pos, line_width, line_height, cwi
local word_l = #word local word_l = #word
local i = 1 local i = 1
while i <= word_l do while i <= word_l do
local wide_c
if "&#x" == word:sub(i, i + 2) then
local j = i + 3
local collected = ""
while j <= word_l do
local c = word:sub(j, j)
if c == ";" then
wide_c = collected
break
elseif c < "0" then
break
elseif "f" < c then
break
elseif ("9" < c) and (c < "a") then
break
else
collected = collected .. c
j = j + 1
end
end
end
local c = word:sub(i, i) local c = word:sub(i, i)
if c == "#" then if c == "#" then
local cc = tonumber(word:sub(i+1, i+1), 16) local cc = tonumber(word:sub(i+1, i+1), 16)
@ -499,6 +554,25 @@ local function make_line_texture(line, lineno, pos, line_width, line_height, cwi
i = i + 1 i = i + 1
cur_color = cc cur_color = cc
end end
elseif wide_c then
local w = cwidth_tab_wide[wide_c]
if w then
width = width + w + 1
if width >= (line_width - cwidth_tab[" "]) then
width = 0
else
maxw = math_max(width, maxw)
end
if #chars < MAX_INPUT_CHARS then
table.insert(chars, {
off = ch_offs,
tex = char_tex_wide(font_name, wide_c),
col = ("%X"):format(cur_color),
})
end
ch_offs = ch_offs + w
end
i = i + #wide_c + 3
else else
local w = cwidth_tab[c] local w = cwidth_tab[c]
if w then if w then
@ -578,6 +652,7 @@ function signs_lib.make_sign_texture(lines, pos)
local line_width local line_width
local line_height local line_height
local char_width local char_width
local char_width_wide
local colorbgw local colorbgw
local widemult = 1 local widemult = 1
@ -590,12 +665,14 @@ function signs_lib.make_sign_texture(lines, pos)
line_width = math.floor(signs_lib.avgwidth31 * def.chars_per_line) * (def.horiz_scaling * widemult) line_width = math.floor(signs_lib.avgwidth31 * def.chars_per_line) * (def.horiz_scaling * widemult)
line_height = signs_lib.lineheight31 line_height = signs_lib.lineheight31
char_width = signs_lib.charwidth31 char_width = signs_lib.charwidth31
char_width_wide = signs_lib.charwidth_wide31
colorbgw = signs_lib.colorbgw31 colorbgw = signs_lib.colorbgw31
else else
font_size = 15 font_size = 15
line_width = math.floor(signs_lib.avgwidth15 * def.chars_per_line) * (def.horiz_scaling * widemult) line_width = math.floor(signs_lib.avgwidth15 * def.chars_per_line) * (def.horiz_scaling * widemult)
line_height = signs_lib.lineheight15 line_height = signs_lib.lineheight15
char_width = signs_lib.charwidth15 char_width = signs_lib.charwidth15
char_width_wide = signs_lib.charwidth_wide15
colorbgw = signs_lib.colorbgw15 colorbgw = signs_lib.colorbgw15
end end
@ -604,7 +681,7 @@ function signs_lib.make_sign_texture(lines, pos)
local lineno = 0 local lineno = 0
for i = 1, #lines do for i = 1, #lines do
if lineno >= def.number_of_lines then break end if lineno >= def.number_of_lines then break end
local linetex, ln = make_line_texture(lines[i], lineno, pos, line_width, line_height, char_width, font_size, colorbgw) local linetex, ln = make_line_texture(lines[i], lineno, pos, line_width, line_height, char_width, font_size, colorbgw, char_width_wide)
table.insert(texture, linetex) table.insert(texture, linetex)
lineno = ln + 1 lineno = ln + 1
end end

View file

@ -203,6 +203,32 @@ local utf8_decode = {
[210] = {[144] = "\165", [145] = "\180"} [210] = {[144] = "\165", [145] = "\180"}
} }
local wide_character_codes = {
}
signs_lib.unicode_install = function(
numbers
)
local scope = utf8_decode
for i = 1,#numbers-2 do
if not scope[numbers[i]] then
scope[numbers[i]] = {}
end
scope = scope[numbers[i]]
end
scope[numbers[#numbers-1]] = "&#x" .. numbers[#numbers] .. ";"
table.insert(
wide_character_codes,
numbers[#numbers]
)
end
signs_lib.unicode_install({38,"26"})
dofile(signs_lib.path.."/nonascii-de.lua")
dofile(signs_lib.path.."/nonascii-fr.lua")
dofile(signs_lib.path.."/nonascii-pl.lua")
local nmdc = { local nmdc = {
[36] = "$", [36] = "$",
[124] = "|" [124] = "|"
@ -230,36 +256,33 @@ function AnsiToUtf8(s)
end end
function Utf8ToAnsi(s) function Utf8ToAnsi(s)
local a, j, r, b = 0, 0, "" local a, j, r, b, scope = 0, 0, ""
for i = 1, s and s:len() or 0 do for i = 1, s and s:len() or 0 do
b = s:byte(i) b = s:byte(i)
if b < 128 then if b == 0x26 then
r = r .. "&#x26;"
elseif b < 128 then
if nmdc[b] then if nmdc[b] then
r = r .. nmdc[b] r = r .. nmdc[b]
else else
r = r .. string.char(b) r = r .. string.char(b)
end end
elseif a == 2 then elseif scope then
a, j = a - 1, b if scope[b] then
elseif a == 1 then scope = scope[b]
--if j == nil or b == nil then return r end if "string" == type(scope) then
--print(j) r, scope = r .. scope
--print(b)
--local ansi = utf8_decode[j]
--if ansi == nil then return r end
--if ansi[b] == nil then return r end
if utf8_decode[j] then
if utf8_decode[j][b] then
a, r = a - 1, r .. utf8_decode[j][b]
end end
else
r, scope = r .. "_"
end end
elseif b == 226 then elseif utf8_decode[b] then
a = 2 scope = utf8_decode[b]
elseif b == 194 or b == 208 or b == 209 or b == 210 then
j, a = b, 1
else else
r = r .. "_" r = r .. "_"
end end
end end
return r return r
end end
signs_lib.wide_character_codes = wide_character_codes

View file

@ -10,7 +10,7 @@ signs_lib.path = minetest.get_modpath(minetest.get_current_modname())
local S, NS = dofile(signs_lib.path .. "/intllib.lua") local S, NS = dofile(signs_lib.path .. "/intllib.lua")
signs_lib.gettext = S signs_lib.gettext = S
dofile(signs_lib.path.."/api.lua")
dofile(signs_lib.path.."/encoding.lua") dofile(signs_lib.path.."/encoding.lua")
dofile(signs_lib.path.."/api.lua")
dofile(signs_lib.path.."/standard_signs.lua") dofile(signs_lib.path.."/standard_signs.lua")
dofile(signs_lib.path.."/compat.lua") dofile(signs_lib.path.."/compat.lua")

View file

@ -0,0 +1,7 @@
signs_lib.unicode_install({195,132,"00c4"})
signs_lib.unicode_install({195,150,"00d6"})
signs_lib.unicode_install({195,156,"00dc"})
signs_lib.unicode_install({195,159,"00df"})
signs_lib.unicode_install({195,164,"00e4"})
signs_lib.unicode_install({195,182,"00f6"})
signs_lib.unicode_install({195,188,"00fc"})

View file

@ -0,0 +1,16 @@
signs_lib.unicode_install({195,128,"00c0"})
signs_lib.unicode_install({195,134,"00c6"})
signs_lib.unicode_install({195,135,"00c7"})
signs_lib.unicode_install({195,136,"00c8"})
signs_lib.unicode_install({195,137,"00c9"})
signs_lib.unicode_install({195,138,"00ca"})
signs_lib.unicode_install({195,148,"00d4"})
signs_lib.unicode_install({195,153,"00d9"})
signs_lib.unicode_install({195,160,"00e0"})
signs_lib.unicode_install({195,166,"00e6"})
signs_lib.unicode_install({195,167,"00e7"})
signs_lib.unicode_install({195,168,"00e8"})
signs_lib.unicode_install({195,169,"00e9"})
signs_lib.unicode_install({195,170,"00ea"})
signs_lib.unicode_install({195,180,"00f4"})
signs_lib.unicode_install({195,185,"00f9"})

View file

@ -0,0 +1,16 @@
signs_lib.unicode_install({195,147,"00d3"})
signs_lib.unicode_install({195,179,"00f3"})
signs_lib.unicode_install({196,132,"0104"})
signs_lib.unicode_install({196,133,"0105"})
signs_lib.unicode_install({196,134,"0106"})
signs_lib.unicode_install({196,135,"0107"})
signs_lib.unicode_install({196,152,"0118"})
signs_lib.unicode_install({196,153,"0119"})
signs_lib.unicode_install({197,129,"0141"})
signs_lib.unicode_install({197,130,"0142"})
signs_lib.unicode_install({197,154,"015a"})
signs_lib.unicode_install({197,155,"015b"})
signs_lib.unicode_install({197,185,"0179"})
signs_lib.unicode_install({197,186,"017a"})
signs_lib.unicode_install({197,187,"017b"})
signs_lib.unicode_install({197,188,"017c"})

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 334 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 325 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 331 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 B

Some files were not shown because too many files have changed in this diff Show more