From c3d04606a3b4da5181ce3467cb9e77d6a4e61b04 Mon Sep 17 00:00:00 2001 From: cheapie Date: Fri, 19 Apr 2024 11:24:33 -0500 Subject: Initial dispatcher work (not yet functional) --- controller.lua | 11 + controllerfw.lua | 42 +- dispatcher.lua | 436 +++++++++++++++++++++ dispatcherfw.lua | 281 +++++++++++++ init.lua | 1 + .../celevator_dispatcher_front_bottom_open.png | Bin 0 -> 1794 bytes textures/celevator_dispatcher_front_top.png | Bin 0 -> 7770 bytes textures/celevator_dispatcher_front_top_open.png | Bin 0 -> 1539 bytes .../celevator_dispatcher_front_top_open_lside.png | Bin 0 -> 8357 bytes 9 files changed, 768 insertions(+), 3 deletions(-) create mode 100644 dispatcher.lua create mode 100644 dispatcherfw.lua create mode 100644 textures/celevator_dispatcher_front_bottom_open.png create mode 100644 textures/celevator_dispatcher_front_top.png create mode 100644 textures/celevator_dispatcher_front_top_open.png create mode 100644 textures/celevator_dispatcher_front_top_open_lside.png diff --git a/controller.lua b/controller.lua index ce5c666..287983e 100644 --- a/controller.lua +++ b/controller.lua @@ -528,6 +528,17 @@ function celevator.controller.finish(pos,mem,changedinterrupts) if (mem.copformspec ~= oldmem.copformspec or mem.switchformspec ~= oldmem.switchformspec) and drivetype then minetest.after(0.25,celevator.drives[drivetype].updatecopformspec,drivepos) 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.dispatcherpos then + celevator.dispatcher.run(destinfo.dispatcherpos,{ + type = "controllermsg", + source = mem.carid, + channel = message.channel, + msg = message.message, + }) + end + end meta:set_string("mem",minetest.serialize(mem)) if node.name == "celevator:controller_open" then meta:set_string("formspec",mem.formspec or "") end meta:set_string("formspec_hidden",mem.formspec or "") diff --git a/controllerfw.lua b/controllerfw.lua index 4fb93b3..e768efd 100644 --- a/controllerfw.lua +++ b/controllerfw.lua @@ -2,6 +2,16 @@ local pos,event,mem = ... local changedinterrupts = {} +mem.messages = {} + +local function send(carid,channel,message) + table.insert(mem.messages,{ + carid = carid, + channel = channel, + message = message, + }) +end + local function fault(ftype,fatal) if fatal then mem.fatalfault = true end if not mem.activefaults then mem.activefaults = {} end @@ -527,6 +537,31 @@ elseif event.type == "cartopbox" then pos = math.floor(mem.drive.status.apos)-1 }) end +elseif event.type == "dispatchermsg" then + if event.channel == "pairrequest" and mem.screenstate == "oobe_dispatcherconnect" then + mem.params.floornames = event.msg.floornames + mem.params.floorheights = event.msg.floorheights + mem.activefaults = {} + mem.faultlog = {} + mem.fatalfault = false + mem.state = "configured" + mem.screenstate = "status" + mem.screenpage = 1 + mem.carstate = "bfdemand" + if mem.doorstate == "closed" then + drivecmd({ + command = "setmaxvel", + maxvel = mem.params.contractspeed, + }) + drivecmd({command = "resetpos"}) + interrupt(0.1,"checkdrive") + mem.carmotion = true + juststarted = true + else + close() + end + send(event.source,"pairok",mem) + end end local oldstate = mem.carstate @@ -787,10 +822,11 @@ elseif mem.screenstate == "oobe_groupmode" then fs("button[1,3;2,1;simplex;Simplex]") fs("label[1,4.5;This will be the only elevator in the group. Hall calls will be handled by this controller.]") fs("button[1,6;2,1;group;Group]") - fs("label[1,7.5;This elevator will participate in a group with others. Hall calls will be handled by a dispatcher. (not implemented)]") + fs("label[1,7.5;This elevator will participate in a group with others. Hall calls will be handled by a dispatcher.]") elseif mem.screenstate == "oobe_dispatcherconnect" then - fs("button[1,10;2,1;back;< Back]") - fs("label[1,1;Not yet implemented. Press Back.]") + fs("button[1,10;2,1;back;< Cancel]") + fs("label[1,1;Waiting for connection from dispatcher...]") + fs(string.format("label[1,1.5;This controller's car ID is: %d]",mem.carid)) elseif mem.screenstate == "oobe_floortable" or mem.screenstate == "floortable" then if mem.screenstate == "oobe_floortable" then fs("label[1,1;Enter details of all floors this elevator will serve, then press Done.]") 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, +}) diff --git a/dispatcherfw.lua b/dispatcherfw.lua new file mode 100644 index 0000000..15650df --- /dev/null +++ b/dispatcherfw.lua @@ -0,0 +1,281 @@ +local pos,event,mem = ... + +local changedinterrupts = {} + +local function interrupt(time,iid) + mem.interrupts[iid] = time + changedinterrupts[iid] = true +end + +mem.messages = {} + +local function send(carid,channel,message) + table.insert(mem.messages,{ + carid = carid, + channel = channel, + message = message, + }) +end + +mem.formspec = "" + +local function fs(element) + mem.formspec = mem.formspec..element +end + +if event.type == "program" then + mem.carstatus = {} + mem.screenstate = "oobe_welcome" + mem.editingfloor = 1 + mem.screenpage = 1 + mem.editingconnection = 1 + mem.newconncarid = 0 + if not mem.params then + mem.params = { + carids = {}, + floorheights = {5,5,5}, + floornames = {"1","2","3"}, + floorsserved = {}, + } + end +elseif event.type == "ui" then + local fields = event.fields + if mem.screenstate == "oobe_welcome" then + if fields.license then + mem.screenstate = "oobe_license" + elseif fields.next then + mem.screenstate = "oobe_floortable" + end + elseif mem.screenstate == "oobe_license" then + if fields.back then + mem.screenstate = "oobe_welcome" + end + elseif mem.screenstate == "oobe_floortable" or mem.screenstate == "floortable" then + local exp = event.fields.floor and minetest.explode_textlist_event(event.fields.floor) or {} + if event.fields.back then + mem.screenstate = "oobe_welcome" + elseif event.fields.next then + mem.screenstate = (mem.screenstate == "oobe_floortable" and "oobe_connections" or "parameters") + mem.screenpage = 1 + elseif exp.type == "CHG" then + mem.editingfloor = #mem.params.floornames-exp.index+1 + elseif exp.type == "DCL" then + mem.editingfloor = #mem.params.floornames-exp.index+1 + mem.screenstate = (mem.screenstate == "oobe_floortable" and "oobe_floortable_edit" or "floortable_edit") + elseif event.fields.edit then + mem.screenstate = (mem.screenstate == "oobe_floortable" and "oobe_floortable_edit" or "floortable_edit") + elseif event.fields.add then + table.insert(mem.params.floorheights,5) + table.insert(mem.params.floornames,tostring(#mem.params.floornames+1)) + elseif event.fields.remove then + table.remove(mem.params.floorheights,mem.editingfloor) + table.remove(mem.params.floornames,mem.editingfloor) + mem.editingfloor = math.max(1,mem.editingfloor-1) + elseif event.fields.moveup then + local height = mem.params.floorheights[mem.editingfloor] + local name = mem.params.floornames[mem.editingfloor] + table.remove(mem.params.floorheights,mem.editingfloor) + table.remove(mem.params.floornames,mem.editingfloor) + table.insert(mem.params.floorheights,mem.editingfloor+1,height) + table.insert(mem.params.floornames,mem.editingfloor+1,name) + mem.editingfloor = mem.editingfloor + 1 + elseif event.fields.movedown then + local height = mem.params.floorheights[mem.editingfloor] + local name = mem.params.floornames[mem.editingfloor] + table.remove(mem.params.floorheights,mem.editingfloor) + table.remove(mem.params.floornames,mem.editingfloor) + table.insert(mem.params.floorheights,mem.editingfloor-1,height) + table.insert(mem.params.floornames,mem.editingfloor-1,name) + mem.editingfloor = mem.editingfloor - 1 + end + elseif mem.screenstate == "oobe_floortable_edit" or mem.screenstate == "floortable_edit" then + if event.fields.back or event.fields.save then + mem.screenstate = (mem.screenstate == "oobe_floortable_edit" and "oobe_floortable" or "floortable") + local height = tonumber(event.fields.height) + if height then + height = math.floor(height+0.5) + mem.params.floorheights[mem.editingfloor] = math.max(0,height) + end + mem.params.floornames[mem.editingfloor] = string.sub(event.fields.name,1,256) + end + elseif mem.screenstate == "oobe_connections" or mem.screenstate == "connections" then + local exp = event.fields.connection and minetest.explode_textlist_event(event.fields.connection) or {} + if event.fields.back then + mem.screenstate = "oobe_floortable" + elseif event.fields.next and #mem.params.carids > 0 then + mem.screenstate = (mem.screenstate == "oobe_connections" and "status" or "parameters") + mem.screenpage = 1 + elseif exp.type == "CHG" then + mem.editingconnection = #mem.params.carids-exp.index+1 + elseif exp.type == "DCL" then + mem.editingconnection = #mem.params.carids-exp.index+1 + mem.screenstate = (mem.screenstate == "oobe_connections" and "oobe_connection" or "connection") + elseif event.fields.edit then + mem.screenstate = (mem.screenstate == "oobe_connections" and "oobe_connection" or "connection") + elseif event.fields.add then + mem.newconnfloors = {} + for i in ipairs(mem.params.floornames) do + mem.newconnfloors[i] = true + end + mem.screenstate = (mem.screenstate == "oobe_connections" and "oobe_newconnection" or "newconnection") + elseif event.fields.remove then + mem.carstatus[mem.params.carids[mem.editingconnection]] = nil + table.remove(mem.params.carids,mem.editingconnection) + mem.editingconnection = math.max(1,mem.editingconnection-1) + end + elseif mem.screenstate == "oobe_newconnection" or mem.screenstate == "newconnection" then + local exp = event.fields.floors and minetest.explode_textlist_event(event.fields.floors) or {} + if event.fields.back then + mem.screenstate = (mem.screenstate == "oobe_newconnection" and "oobe_connections" or "connections") + elseif event.fields.connect and fields.carid and tonumber(fields.carid) then + mem.screenstate = (mem.screenstate == "oobe_newconnection" and "oobe_connecting" or "connecting") + local floornames = {} + local floorheights = {} + for i=1,#mem.params.floornames,1 do + if mem.newconnfloors[i] then + table.insert(floornames,mem.params.floornames[i]) + table.insert(floorheights,mem.params.floorheights[i]) + elseif #floornames > 0 then + floorheights[#floorheights] = floorheights[#floorheights]+mem.params.floorheights[i] + end + end + send(tonumber(fields.carid),"pairrequest",{ + dispatcherid = mem.carid, + floornames = floornames, + floorheights = floorheights, + }) + interrupt(3,"connecttimeout") + elseif exp.type == "CHG" then + local floor = #mem.params.floornames-exp.index+1 + mem.newconnfloors[floor] = not mem.newconnfloors[floor] + end + elseif mem.screenstate == "oobe_connectionfailed" or mem.screenstate == "connectionfailed" then + if fields.back then + mem.screenstate = (mem.screenstate == "oobe_connectionfailed" and "oobe_newconnection" or "newconnection") + end + end +elseif event.iid == "connecttimeout" then + if mem.screenstate == "oobe_connecting" then + mem.screenstate = "oobe_connectionfailed" + elseif mem.screenstate == "connecting" then + mem.screenstate = "connectionfailed" + end +elseif event.channel == "pairok" then + if mem.screenstate == "oobe_connecting" or mem.screenstate == "connecting" then + interrupt(nil,"connecttimeout") + mem.screenstate = (mem.screenstate == "oobe_connecting" and "oobe_connections" or "connections") + mem.carstatus[event.source] = { + upcalls = {}, + dncalls = {}, + carcalls = {}, + position = event.msg.drive.status.apos or 0, + state = event.msg.carstate, + } + mem.params.floorsserved[event.source] = mem.newconnfloors + table.insert(mem.params.carids,event.source) + end +end + +fs("formspec_version[6]") +fs("size[16,12]") +fs("background9[0,0;16,12;celevator_fs_bg.png;true;3]") + +if mem.screenstate == "oobe_welcome" then + fs("image[6,1;4,2;celevator_logo.png]") + fs("label[1,4;Welcome to your new MTronic XT elevator dispatcher!]") + fs("label[1,4.5;Before continuing, make sure you have at least two controllers in group operation mode and ready to connect.]") + fs("label[1,5.5;Press Next to begin.]") + fs("button[1,10;2,1;license;License Info]") + fs("button[13,10;2,1;next;Next >]") +elseif mem.screenstate == "oobe_license" then + local licensefile = io.open(minetest.get_modpath("celevator")..DIR_DELIM.."COPYING") + local license = minetest.formspec_escape(licensefile:read("*all")) + licensefile:close() + fs("textarea[1,1;14,8;license;This applies to the whole celevator mod\\, not just this dispatcher:;"..license.."]") + fs("button[7,10.5;2,1;back;OK]") +elseif mem.screenstate == "oobe_floortable" or mem.screenstate == "floortable" then + if mem.screenstate == "oobe_floortable" then + fs("label[1,1;Enter details of all floors this group will serve, then press Next.]") + fs("label[1,1.3;Include all floors served by any car in the group, even if not served by all cars.]") + fs("button[1,10;2,1;back;< Back]") + fs("button[13,10;2,1;next;Next >]") + else + fs("label[1,1;EDIT FLOOR TABLE]") + fs("button[1,10;2,1;next;Done]") + end + fs("textlist[1,2;6,7;floor;") + for i=#mem.params.floornames,1,-1 do + fs(minetest.formspec_escape(string.format("%d - Height: %d - PI: %s",i,mem.params.floorheights[i],mem.params.floornames[i]))..(i==1 and "" or ",")) + end + fs(";"..tostring(#mem.params.floornames-mem.editingfloor+1)..";false]") + if #mem.params.floornames < 100 then fs("button[8,2;2,1;add;New Floor]") end + fs("button[8,3.5;2,1;edit;Edit Floor]") + if #mem.params.floornames > 2 then fs("button[8,5;2,1;remove;Remove Floor]") end + if mem.editingfloor < #mem.params.floornames then fs("button[8,6.5;2,1;moveup;Move Up]") end + if mem.editingfloor > 1 then fs("button[8,8;2,1;movedown;Move Down") end +elseif mem.screenstate == "oobe_floortable_edit" or mem.screenstate == "floortable_edit" then + if mem.screenstate == "oobe_floortable_edit" then + fs("button[7,10.5;2,1;back;OK]") + fs("label[1,5;The Floor Height is the distance (in meters/nodes) from the floor level of this floor to the floor level of the next floor.]") + fs("label[1,5.5;(not used at the highest floor)]") + fs("label[1,6.5;The Floor Name is how the floor will be displayed on the position indicators.]") + else + fs("button[7,10.5;2,1;save;Save]") + end + fs("label[1,1;Editing floor "..tostring(mem.editingfloor).."]") + fs("field[1,3;3,1;height;Floor Height;"..tostring(mem.params.floorheights[mem.editingfloor]).."]") + fs("field[5,3;3,1;name;Floor Name;"..minetest.formspec_escape(mem.params.floornames[mem.editingfloor]).."]") +elseif mem.screenstate == "oobe_connections" or mem.screenstate == "connections" then + if mem.screenstate == "oobe_connections" then + fs("label[1,1;Connect to each car in the group, then click Done.]") + fs("button[1,10;2,1;back;< Back]") + if #mem.params.carids > 0 then fs("button[13,10;2,1;next;Done >]") end + else + fs("label[1,1;EDIT CONNECTIONS]") + if #mem.params.carids > 0 then fs("button[1,10;2,1;next;Done]") end + end + if #mem.params.carids > 0 then + fs("textlist[1,2;6,7;connection;") + for i=#mem.params.carids,1,-1 do + fs(string.format("Car %d - ID #%d",i,mem.params.carids[i])..(i==1 and "" or ",")) + end + fs(";"..tostring(#mem.params.floornames-mem.editingfloor+1)..";false]") + else + fs("label[1,2;No Connections]") + end + if #mem.params.carids < 16 then fs("button[8,2;2,1;add;New Connection]") end + if #mem.params.carids > 0 then fs("button[8,3.5;2,1;edit;Edit Connection]") end + if #mem.params.carids > 0 then fs("button[8,5;2,1;remove;Remove Connection]") end +elseif mem.screenstate == "oobe_newconnection" or mem.screenstate == "newconnection" then + local numfloors = 0 + for _,v in ipairs(mem.newconnfloors) do + if v then numfloors = numfloors + 1 end + end + if mem.screenstate == "oobe_newconnection" then + fs("label[1,1;Enter the car ID and select the floors served (click them to toggle), then click Connect.]") + fs("label[1,1.3;You must select at least two floors.]") + fs("button[1,10;2,1;back;< Back]") + if numfloors >= 2 then fs("button[13,10;2,1;connect;Connect >]") end + else + fs("label[1,1;NEW CONNECTION]") + fs("button[1,10;2,1;back;< Back]") + if numfloors >= 2 then fs("button[13,10;2,1;connect;Connect >]") end + end + fs("textlist[8,2;6,7;floors;") + for i=#mem.params.floornames,1,-1 do + fs(string.format("%s - %s",minetest.formspec_escape(mem.params.floornames[i]),mem.newconnfloors[i] and "YES" or "NO")..(i==1 and "" or ",")) + end + fs(";0;false]") + fs("field[2,3;4,1;carid;Car ID;]") +elseif mem.screenstate == "oobe_connecting" or mem.screenstate == "connecting" then + fs("label[1,1;Connecting to controller...]") +elseif mem.screenstate == "oobe_connectionfailed" or mem.screenstate == "connectionfailed" then + fs("label[4,4;Connection timed out!]") + fs("label[4,5;Make sure the car ID is correct and]") + fs("label[4,5.5;that the controller is ready to pair.]") + fs("button[1,10;2,1;back;< Back]") +end + +mem.infotext = string.format("ID: %d",mem.carid) + +return pos,mem,changedinterrupts diff --git a/init.lua b/init.lua index 855b6a9..7e004c3 100644 --- a/init.lua +++ b/init.lua @@ -8,6 +8,7 @@ local components = { "callbuttons", "pilantern", "fs1switch", + "dispatcher", } for _,v in ipairs(components) do diff --git a/textures/celevator_dispatcher_front_bottom_open.png b/textures/celevator_dispatcher_front_bottom_open.png new file mode 100644 index 0000000..67cf132 Binary files /dev/null and b/textures/celevator_dispatcher_front_bottom_open.png differ diff --git a/textures/celevator_dispatcher_front_top.png b/textures/celevator_dispatcher_front_top.png new file mode 100644 index 0000000..ffd46a8 Binary files /dev/null and b/textures/celevator_dispatcher_front_top.png differ diff --git a/textures/celevator_dispatcher_front_top_open.png b/textures/celevator_dispatcher_front_top_open.png new file mode 100644 index 0000000..eb6be8f Binary files /dev/null and b/textures/celevator_dispatcher_front_top_open.png differ diff --git a/textures/celevator_dispatcher_front_top_open_lside.png b/textures/celevator_dispatcher_front_top_open_lside.png new file mode 100644 index 0000000..b2336a6 Binary files /dev/null and b/textures/celevator_dispatcher_front_top_open_lside.png differ -- cgit v1.2.3