diff options
Diffstat (limited to 'dispatcher.lua')
-rw-r--r-- | dispatcher.lua | 436 |
1 files changed, 436 insertions, 0 deletions
diff --git a/dispatcher.lua b/dispatcher.lua new file mode 100644 index 0000000..af29a84 --- /dev/null +++ b/dispatcher.lua @@ -0,0 +1,436 @@ +celevator.dispatcher = {} + +celevator.dispatcher.iqueue = minetest.deserialize(celevator.storage:get_string("dispatcher_iqueue")) or {} + +celevator.dispatcher.equeue = minetest.deserialize(celevator.storage:get_string("dispatcher_equeue")) or {} + +celevator.dispatcher.running = {} + +local fw,err = loadfile(minetest.get_modpath("celevator")..DIR_DELIM.."dispatcherfw.lua") +if not fw then error(err) end + +minetest.register_chatcommand("celevator_reloaddispatcher",{ + params = "", + description = "Reload celevator dispatcher firmware from disk", + privs = {server = true}, + func = function() + local newfw,loaderr = loadfile(minetest.get_modpath("celevator")..DIR_DELIM.."dispatcherfw.lua") + if newfw then + fw = newfw + return true,"Firmware reloaded successfully" + else + return false,loaderr + end + end, +}) + +local function after_place(pos,placer) + local node = minetest.get_node(pos) + local toppos = {x=pos.x,y=pos.y + 1,z=pos.z} + local topnode = minetest.get_node(toppos) + local placername = placer:get_player_name() + if topnode.name ~= "air" then + if placer:is_player() then + minetest.chat_send_player(placername,"Can't place cabinet - no room for the top half!") + end + minetest.set_node(pos,{name="air"}) + return true + end + if minetest.is_protected(toppos,placername) and not minetest.check_player_privs(placername,{protection_bypass=true}) then + if placer:is_player() then + minetest.chat_send_player(placername,"Can't place cabinet - top half is protected!") + minetest.record_protection_violation(toppos,placername) + end + minetest.set_node(pos,{name="air"}) + return true + end + node.name = "celevator:dispatcher_top" + minetest.set_node(toppos,node) +end + +local function ondestruct(pos) + pos.y = pos.y + 1 + local topnode = minetest.get_node(pos) + local dispatchertops = { + ["celevator:dispatcher_top"] = true, + ["celevator:dispatcher_top_open"] = true, + } + if dispatchertops[topnode.name] then + minetest.set_node(pos,{name="air"}) + end + celevator.dispatcher.equeue[minetest.hash_node_position(pos)] = nil + celevator.storage:set_string("dispatcher_equeue",minetest.serialize(celevator.dispatcher.equeue)) + local carid = minetest.get_meta(pos):get_int("carid") + if carid ~= 0 then celevator.storage:set_string(string.format("car%d",carid),"") end +end + +local function onrotate(controllerpos,node,user,mode,new_param2) + if not minetest.global_exists("screwdriver") then + return false + end + local ret = screwdriver.rotate_simple(controllerpos,node,user,mode,new_param2) + minetest.after(0,function(pos) + local newnode = minetest.get_node(pos) + local param2 = newnode.param2 + pos.y = pos.y + 1 + local topnode = minetest.get_node(pos) + topnode.param2 = param2 + minetest.set_node(pos,topnode) + end,controllerpos) + return ret +end + +local function handlefields(pos,_,fields,sender) + local playername = sender and sender:get_player_name() or "" + local event = {} + event.type = "ui" + event.fields = fields + event.sender = playername + celevator.dispatcher.run(pos,event) +end + +minetest.register_node("celevator:dispatcher",{ + description = "Elevator Dispatcher", + groups = { + cracky = 1, + }, + paramtype = "light", + paramtype2 = "facedir", + drawtype = "nodebox", + node_box = { + type = "fixed", + fixed = { + {-0.5,-0.5,0,0.5,0.5,0.5}, + }, + }, + selection_box = { + type = "fixed", + fixed = { + {-0.5,-0.5,0,0.5,1.5,0.5}, + }, + }, + tiles = { + "celevator_cabinet_sides.png", + "celevator_cabinet_sides.png", + "celevator_cabinet_sides.png", + "celevator_cabinet_sides.png", + "celevator_cabinet_sides.png", + "celevator_cabinet_front_bottom.png", + }, + after_place_node = after_place, + on_destruct = ondestruct, + on_rotate = onrotate, + on_receive_fields = handlefields, + on_construct = function(pos) + local meta = minetest.get_meta(pos) + meta:set_string("mem",minetest.serialize({})) + meta:mark_as_private("mem") + local event = {} + event.type = "program" + local carid = celevator.storage:get_int("maxcarid")+1 + meta:set_int("carid",carid) + celevator.storage:set_int("maxcarid",carid) + celevator.storage:set_string(string.format("car%d",carid),minetest.serialize({dispatcherpos=pos,callbuttons={},fs1switches={}})) + celevator.dispatcher.run(pos,event) + end, + on_punch = function(pos,node,puncher) + if not puncher:is_player() then + return + end + local name = puncher:get_player_name() + if minetest.is_protected(pos,name) and not minetest.check_player_privs(name,{protection_bypass=true}) then + minetest.chat_send_player(name,"Can't open cabinet - cabinet is locked.") + minetest.record_protection_violation(pos,name) + return + end + node.name = "celevator:dispatcher_open" + minetest.swap_node(pos,node) + local meta = minetest.get_meta(pos) + meta:set_string("formspec",meta:get_string("formspec_hidden")) + pos.y = pos.y + 1 + node = minetest.get_node(pos) + node.name = "celevator:dispatcher_top_open" + minetest.swap_node(pos,node) + minetest.sound_play("doors_steel_door_open",{ + pos = pos, + gain = 0.5, + max_hear_distance = 10 + },true) + end, +}) + +minetest.register_node("celevator:dispatcher_open",{ + description = "Dispatcher (door open - you hacker you!)", + groups = { + cracky = 1, + not_in_creative_inventory = 1, + }, + paramtype = "light", + paramtype2 = "facedir", + drawtype = "nodebox", + drop = "celevator:dispatcher", + node_box = { + type = "fixed", + fixed = { + {-0.5,-0.5,0,0.5,0.5,0.5}, + {-0.5,-0.5,-0.5,-0.45,0.5,0}, + {0.45,-0.5,-0.5,0.5,0.5,0}, + }, + }, + selection_box = { + type = "fixed", + fixed = { + {-0.5,-0.5,-0.5,0.5,1.5,0.5}, + }, + }, + tiles = { + "celevator_cabinet_sides.png", + "celevator_cabinet_sides.png", + "celevator_cabinet_front_bottom_open_rside.png", + "celevator_cabinet_front_bottom_open_lside.png", + "celevator_cabinet_sides.png", + { + name="celevator_dispatcher_front_bottom_open.png", + animation={type="vertical_frames", aspect_w=32, aspect_h=32, length=2}, + } + }, + after_place_node = after_place, + on_destruct = ondestruct, + on_rotate = onrotate, + on_receive_fields = handlefields, + on_punch = function(pos,node,puncher) + if not puncher:is_player() then + return + end + node.name = "celevator:dispatcher" + minetest.swap_node(pos,node) + local meta = minetest.get_meta(pos) + meta:set_string("formspec","") + pos.y = pos.y + 1 + node = minetest.get_node(pos) + node.name = "celevator:dispatcher_top" + minetest.swap_node(pos,node) + minetest.sound_play("doors_steel_door_close",{ + pos = pos, + gain = 0.5, + max_hear_distance = 10 + },true) + end, +}) + +minetest.register_node("celevator:dispatcher_top",{ + description = "Dispatcher (top section - you hacker you!)", + groups = { + not_in_creative_inventory = 1, + }, + drop = "", + paramtype = "light", + paramtype2 = "facedir", + drawtype = "nodebox", + node_box = { + type = "fixed", + fixed = { + {-0.5,-0.5,0,0.5,0.5,0.5}, + }, + }, + selection_box = { + type = "fixed", + fixed = { + {0,0,0,0,0,0}, + }, + }, + tiles = { + "celevator_cabinet_sides.png", + "celevator_cabinet_sides.png", + "celevator_cabinet_sides.png", + "celevator_cabinet_sides.png", + "celevator_cabinet_sides.png", + "celevator_dispatcher_front_top.png", + }, +}) + +minetest.register_node("celevator:dispatcher_top_open",{ + description = "Dispatcher (top section, open - you hacker you!)", + groups = { + not_in_creative_inventory = 1, + }, + drop = "", + paramtype = "light", + paramtype2 = "facedir", + drawtype = "nodebox", + node_box = { + type = "fixed", + fixed = { + {-0.5,-0.5,0,0.5,0.5,0.5}, + {-0.5,-0.5,-0.5,-0.45,0.5,0}, + {0.45,-0.5,-0.5,0.5,0.5,0}, + }, + }, + selection_box = { + type = "fixed", + fixed = { + {0,0,0,0,0,0}, + }, + }, + tiles = { + "celevator_cabinet_sides.png", + "celevator_cabinet_sides.png", + "celevator_cabinet_front_top_open_rside.png", + "celevator_dispatcher_front_top_open_lside.png", + "celevator_cabinet_sides.png", + "celevator_dispatcher_front_top_open.png", + }, +}) + +function celevator.dispatcher.isdispatcher(pos) + local node = celevator.get_node(pos) + return (node.name == "celevator:dispatcher" or node.name == "celevator:dispatcher_open") +end + +function celevator.dispatcher.finish(pos,mem,changedinterrupts) + if not celevator.dispatcher.isdispatcher(pos) then + return + else + local meta = minetest.get_meta(pos) + local carid = meta:get_int("carid") + local carinfo = minetest.deserialize(celevator.storage:get_string(string.format("car%d",carid))) + local carinfodirty = false + if not carinfo then + minetest.log("error","[celevator] [controller] Bad car info for dispatcher at "..minetest.pos_to_string(pos)) + return + end + local node = celevator.get_node(pos) + local oldmem = minetest.deserialize(meta:get_string("mem")) or {} + local oldupbuttonlights = oldmem.upcalls or {} + local olddownbuttonlights = oldmem.dncalls or {} + local newupbuttonlights = mem.upcalls or {} + local newdownbuttonlights = mem.dncalls or {} + local callbuttons = carinfo.callbuttons + for _,button in pairs(callbuttons) do + if oldupbuttonlights[button.landing] ~= newupbuttonlights[button.landing] then + celevator.callbutton.setlight(button.pos,"up",newupbuttonlights[button.landing]) + end + if olddownbuttonlights[button.landing] ~= newdownbuttonlights[button.landing] then + celevator.callbutton.setlight(button.pos,"down",newdownbuttonlights[button.landing]) + end + end + local oldfs1led = oldmem.fs1led + local newfs1led = mem.fs1led + local fs1switches = carinfo.fs1switches or {} + if oldfs1led ~= newfs1led then + for _,fs1switch in pairs(fs1switches) do + celevator.fs1switch.setled(fs1switch.pos,newfs1led) + end + end + for _,message in ipairs(mem.messages) do + local destinfo = minetest.deserialize(celevator.storage:get_string(string.format("car%d",message.carid))) + if destinfo and destinfo.controllerpos then + celevator.controller.run(destinfo.controllerpos,{ + type = "dispatchermsg", + source = mem.carid, + channel = message.channel, + msg = message.message, + }) + end + end + meta:set_string("mem",minetest.serialize(mem)) + if node.name == "celevator:dispatcher_open" then meta:set_string("formspec",mem.formspec or "") end + meta:set_string("formspec_hidden",mem.formspec or "") + meta:set_string("infotext",mem.infotext or "") + local hash = minetest.hash_node_position(pos) + if not celevator.dispatcher.iqueue[hash] then celevator.dispatcher.iqueue[hash] = mem.interrupts end + for iid in pairs(changedinterrupts) do + celevator.dispatcher.iqueue[hash][iid] = mem.interrupts[iid] + end + celevator.storage:set_string("dispatcher_iqueue",minetest.serialize(celevator.dispatcher.iqueue)) + celevator.dispatcher.running[hash] = nil + if #celevator.dispatcher.equeue[hash] > 0 then + local event = celevator.dispatcher.equeue[hash][1] + table.remove(celevator.dispatcher.equeue[hash],1) + celevator.storage:set_string("dispatcher_equeue",minetest.serialize(celevator.dispatcher.equeue)) + celevator.dispatcher.run(pos,event) + end + if carinfodirty then + celevator.storage:set_string(string.format("car%d",carid),minetest.serialize(carinfo)) + end + end +end + +function celevator.dispatcher.run(pos,event) + if not celevator.dispatcher.isdispatcher(pos) then + return + else + local hash = minetest.hash_node_position(pos) + if not celevator.dispatcher.equeue[hash] then + celevator.dispatcher.equeue[hash] = {} + celevator.storage:set_string("dispatcher_equeue",minetest.serialize(celevator.dispatcher.equeue)) + end + if celevator.dispatcher.running[hash] then + table.insert(celevator.dispatcher.equeue[hash],event) + celevator.storage:set_string("dispatcher_equeue",minetest.serialize(celevator.dispatcher.equeue)) + if #celevator.dispatcher.equeue[hash] > 5 then + local message = "[celevator] [dispatcher] Async process for dispatcher at %s is falling behind, %d events in queue" + minetest.log("warning",string.format(message,minetest.pos_to_string(pos),#celevator.dispatcher.equeue[hash])) + end + return + end + celevator.dispatcher.running[hash] = true + local meta = minetest.get_meta(pos) + local mem = minetest.deserialize(meta:get_string("mem")) + if not mem then + minetest.log("error","[celevator] [controller] Failed to load dispatcher memory at "..minetest.pos_to_string(pos)) + return + end + mem.interrupts = celevator.dispatcher.iqueue[minetest.hash_node_position(pos)] or {} + mem.carid = meta:get_int("carid") + minetest.handle_async(fw,celevator.dispatcher.finish,pos,event,mem) + end +end + +function celevator.dispatcher.handlecallbutton(dispatcherpos,landing,dir) + local event = { + type = "callbutton", + landing = landing, + dir = dir, + } + celevator.dispatcher.run(dispatcherpos,event) +end + +function celevator.controller.handlefs1switch(dispatcherpos,on) + local event = { + type = "fs1switch", + state = on, + } + celevator.dispatcher.run(dispatcherpos,event) +end + +function celevator.dispatcher.checkiqueue(dtime) + for hash,iqueue in pairs(celevator.dispatcher.iqueue) do + local pos = minetest.get_position_from_hash(hash) + for iid,time in pairs(iqueue) do + iqueue[iid] = time-dtime + if iqueue[iid] < 0 then + iqueue[iid] = nil + local event = {} + event.type = "interrupt" + event.iid = iid + celevator.dispatcher.run(pos,event) + end + end + end +end + +minetest.register_globalstep(celevator.dispatcher.checkiqueue) + +minetest.register_abm({ + label = "Run otherwise idle dispatchers if a user is nearby", + nodenames = {"celevator:dispatcher","celevator:dispatcher_open"}, + interval = 1, + chance = 1, + action = function(pos) + local event = { + type = "abm" + } + celevator.dispatcher.run(pos,event) + end, +}) |