local pos,event,mem = ... local changedinterrupts = {} local function interrupt(time,iid) mem.interrupts[iid] = time changedinterrupts[iid] = true end mem.messages = {} local function getpos(carid) local floormap = {} local floorheights = {} for i=1,#mem.params.floornames,1 do if mem.params.floorsserved[carid][i] then table.insert(floormap,i) table.insert(floorheights,mem.params.floorheights[i]) elseif #floorheights > 0 then floorheights[#floorheights] = floorheights[#floorheights]+mem.params.floorheights[i] end end local ret = 0 local searchpos = mem.carstatus[carid].position for k,v in ipairs(floorheights) do ret = ret+v if ret > searchpos then return floormap[k] end end return 1 end local function getdpos(carid) local floormap = {} local floorheights = {} for i=1,#mem.params.floornames,1 do if mem.params.floorsserved[carid][i] then table.insert(floormap,i) table.insert(floorheights,mem.params.floorheights[i]) elseif #floorheights > 0 then floorheights[#floorheights] = floorheights[#floorheights]+mem.params.floorheights[i] end end local ret = 0 local searchpos = mem.carstatus[carid].target for k,v in ipairs(floorheights) do ret = ret+v if ret > searchpos then return floormap[k] end end return 1 end local function cartorealfloor(carid,floor) if type(floor) == "table" then local ret = {} for i in pairs(floor) do ret[cartorealfloor(carid,i)] = true end return ret end local map = {} for i=1,#mem.params.floornames,1 do if mem.params.floorsserved[carid][i] then table.insert(map,i) end end return map[floor] end local function realtocarfloor(carid,floor) if type(floor) == "table" then local ret = {} for i in pairs(floor) do ret[realtocarfloor(carid,i)] = true end return ret end local map = {} for i=1,#mem.params.floornames,1 do if mem.params.floorsserved[carid][i] then table.insert(map,i) end end local pmap = {} for k,v in pairs(map) do pmap[v] = k end return pmap[floor] end local function send(carid,channel,message) table.insert(mem.messages,{ carid = carid, channel = channel, message = message, }) end local function getnextcallabove(carid,dir,startpos,carcalls,upcalls,dncalls) for i=(startpos or getpos(carid)),#mem.params.floorheights,1 do if not dir then if carcalls[i] then return i,"car" elseif upcalls[i] then return i,"up" elseif dncalls[i] then return i,"down" end elseif dir == "up" then if carcalls[i] then return i,"car" elseif upcalls[i] then return i,"up" end elseif dir == "down" then if carcalls[i] then return i,"car" elseif dncalls[i] then return i,"down" end end end end local function getnextcallbelow(carid,dir,startpos,carcalls,upcalls,dncalls) for i=(startpos or getpos(carid)),1,-1 do if not dir then if carcalls[i] then return i,"car" elseif upcalls[i] then return i,"up" elseif dncalls[i] then return i,"down" end elseif dir == "up" then if carcalls[i] then return i,"car" elseif upcalls[i] then return i,"up" end elseif dir == "down" then if carcalls[i] then return i,"car" elseif dncalls[i] then return i,"down" end end end end local function getlowestupcall(upcalls) for i=1,#mem.params.floornames,1 do if upcalls[i] then return i end end end local function gethighestdowncall(dncalls) for i=#mem.params.floornames,1,-1 do if dncalls[i] then return i end end end local function gettarget(floor) local target = 0 if floor == 1 then return 0 end for i=1,floor-1,1 do target = target+mem.params.floorheights[i] end return target end local function predictnextstop(carid,startpos,direction,carcalls,upcalls,dncalls) if direction == "up" then if getnextcallabove(carid,"up",startpos,carcalls,upcalls,dncalls) then return getnextcallabove(carid,"up",startpos,carcalls,upcalls,dncalls),"up" elseif gethighestdowncall(dncalls) then return gethighestdowncall(dncalls),"down" elseif getlowestupcall(upcalls) then return getlowestupcall(upcalls),"up" elseif getnextcallbelow(carid,"down",startpos,carcalls,upcalls,dncalls) then return getnextcallbelow(carid,"down",startpos,carcalls,upcalls,dncalls),"down" else return end elseif direction == "down" then if getnextcallbelow(carid,"down",startpos,carcalls,upcalls,dncalls) then return getnextcallbelow(carid,"down",startpos,carcalls,upcalls,dncalls),"down" elseif getlowestupcall(upcalls) then return getlowestupcall(upcalls),"up" elseif gethighestdowncall(dncalls) then return gethighestdowncall(dncalls),"down" elseif getnextcallabove(carid,"up",startpos,carcalls,upcalls,dncalls) then return getnextcallabove(carid,nil,startpos,carcalls,upcalls,dncalls),"up" else return end else if getnextcallabove(carid,"up",startpos,carcalls,upcalls,dncalls) then return getnextcallabove(carid,nil,startpos,carcalls,upcalls,dncalls),"up" elseif getnextcallbelow(carid,"down",startpos,carcalls,upcalls,dncalls) then return getnextcallbelow(carid,"down",startpos,carcalls,upcalls,dncalls),"down" elseif getlowestupcall(upcalls) then return getlowestupcall(upcalls),"up" elseif gethighestdowncall(dncalls) then return gethighestdowncall(dncalls),"down" end end end local function estimatetraveltime(carid,src,dest) local srcpos = gettarget(src) local dstpos = gettarget(dest) local estimate = math.abs(srcpos-dstpos) estimate = estimate/mem.carstatus[carid].contractspeed estimate = estimate+(mem.carstatus[carid].contractspeed*2) return estimate end local function buildstopsequence(carid,startfloor,direction,target,targetdir) local carcalls = cartorealfloor(carid,mem.carstatus[carid].carcalls) local upcalls = cartorealfloor(carid,mem.carstatus[carid].upcalls) local dncalls = cartorealfloor(carid,mem.carstatus[carid].dncalls) if targetdir == "up" then upcalls[target] = true elseif targetdir == "down" then dncalls[target] = true end local carpos = startfloor local sequence = {} local vel = mem.carstatus[carid].vel if vel > 0 then direction = "up" elseif vel < 0 then direction = "down" end repeat local src = carpos carpos,direction = predictnextstop(carid,carpos,direction,carcalls,upcalls,dncalls) carcalls[carpos] = nil if direction == "up" then upcalls[carpos] = nil elseif direction == "down" then dncalls[carpos] = nil end table.insert(sequence,{ src = src, dest = carpos, }) until (carpos == target and direction == targetdir) or #sequence > 100 return sequence end local function calculateeta(carid,floor,direction) local sequence = buildstopsequence(carid,getpos(carid),mem.carstatus[carid].direction,floor,direction) local eta = 0 for k,v in ipairs(sequence) do eta = eta+estimatetraveltime(carid,v.src,v.dest) if k < #sequence then eta = eta+mem.carstatus[carid].doortimer+9 end end return eta 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 mem.upcalls = {} mem.dncalls = {} mem.assignedup = {} mem.assigneddn = {} mem.upeta = {} mem.dneta = {} 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 for _,carid in ipairs(mem.params.carids) do local floornames = {} local floorheights = {} for i=1,#mem.params.floornames,1 do if mem.params.floorsserved[carid][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(carid,"newfloortable",{ floornames = floornames, floorheights = floorheights, }) end mem.screenstate = (mem.screenstate == "oobe_floortable" and "oobe_connections" or "menu") 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 "menu") 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") mem.newconnfloors = mem.params.floorsserved[mem.params.carids[mem.editingconnection]] 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",{ 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_connection" or mem.screenstate == "connection" 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_connection" and "oobe_connections" or "connections") elseif event.fields.save then mem.screenstate = (mem.screenstate == "oobe_connection" and "oobe_connections" or "connections") 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(mem.params.carids[mem.editingconnection],"newfloortable",{ floornames = floornames, floorheights = floorheights, }) 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 elseif mem.screenstate == "status" then for k,v in pairs(fields) do if string.sub(k,1,7) == "carcall" then local car = tonumber(string.sub(k,8,9)) local floor = tonumber(string.sub(k,10,-1)) if v and car and floor then local carid = mem.params.carids[car] send(carid,"carcall",realtocarfloor(carid,floor)) end elseif string.sub(k,1,6) == "upcall" then local floor = tonumber(string.sub(k,7,-1)) if v and floor and not mem.upcalls[floor] then mem.upcalls[floor] = true mem.upeta[floor] = 0 interrupt(0,"run") end elseif string.sub(k,1,6) == "dncall" then local floor = tonumber(string.sub(k,7,-1)) if v and floor and not mem.dncalls[floor] then mem.dncalls[floor] = true mem.dneta[floor] = 0 interrupt(0,"run") end end end if fields.scrollup and (mem.screenpage-1)*10+1 < #mem.params.floornames then mem.screenpage = mem.screenpage + 1 elseif fields.scrolldown and mem.screenpage > 1 then mem.screenpage = mem.screenpage - 1 elseif fields.menu then mem.screenstate = "menu" end elseif mem.screenstate == "menu" then if fields.back then mem.screenstate = "status" elseif fields.floortable then mem.screenstate = "floortable" elseif fields.connections then mem.screenstate = "connections" 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] = { groupupcalls = {}, groupdncalls = {}, swingupcalls = {}, swingdncalls = {}, upcalls = {}, dncalls = {}, carcalls = {}, doorstate = event.msg.doorstate, position = event.msg.drive.status.apos or 0, target = event.msg.drive.status.dpos or 0, state = event.msg.carstate, direction = event.msg.direction, vel = event.msg.drive.status.vel or 0, contractspeed = event.msg.params.contractspeed, doortimer = event.msg.params.doortimer, } mem.params.floorsserved[event.source] = mem.newconnfloors table.insert(mem.params.carids,event.source) end elseif event.channel == "status" then mem.carstatus[event.source] = { groupupcalls = event.msg.groupupcalls, groupdncalls = event.msg.groupdncalls, swingupcalls = event.msg.swingupcalls, swingdncalls = event.msg.swingdncalls, upcalls = event.msg.upcalls, dncalls = event.msg.dncalls, carcalls = event.msg.carcalls, doorstate = event.msg.doorstate, position = event.msg.drive.status.apos or 0, target = event.msg.drive.status.dpos or 0, state = event.msg.carstate, direction = event.msg.direction, vel = event.msg.drive.status.vel, contractspeed = event.msg.params.contractspeed, doortimer = event.msg.params.doortimer, } if event.msg.carstate == "normal" and event.msg.doorstate == "opening" then local floor = getpos(event.source) if event.msg.direction == "up" then mem.upcalls[floor] = nil elseif event.msg.direction == "down" then mem.dncalls[floor] = nil end end elseif event.type == "abm" or event.iid == "run" and (mem.screenstate == "status" or mem.screenstate == "menu") then interrupt(1.5,"run") if not mem.upcalls then mem.upcalls = {} end if not mem.dncalls then mem.dncalls = {} end if not mem.upeta then mem.upeta = {} end if not mem.dneta then mem.dneta = {} end if not mem.assignedup then mem.assignedup = {} end if not mem.assigneddn then mem.assigneddn = {} end local unassignedup = table.copy(mem.upcalls) local unassigneddn = table.copy(mem.dncalls) for _,carid in ipairs(mem.params.carids) do for floor in pairs(mem.carstatus[carid].groupupcalls) do unassignedup[cartorealfloor(carid,floor)] = nil end for floor in pairs(mem.carstatus[carid].groupdncalls) do unassigneddn[cartorealfloor(carid,floor)] = nil end end for i in pairs(unassignedup) do local eligiblecars = {} for _,carid in pairs(mem.params.carids) do if mem.carstatus[carid].state == "normal" and mem.params.floorsserved[carid][i] then local serveshigher = false for floor,served in pairs(mem.params.floorsserved[carid]) do if floor > i and served then serveshigher = true break end end if serveshigher then eligiblecars[carid] = true end end end local besteta = 999 local bestcar for carid in pairs(eligiblecars) do local eta = calculateeta(carid,i,"up") if eta < besteta then besteta = eta bestcar = carid end end mem.upeta[i] = besteta if bestcar then send(bestcar,"groupupcall",realtocarfloor(bestcar,i)) mem.assignedup[i] = bestcar else mem.upcalls[i] = nil end end for i in pairs(mem.assignedup) do if mem.upcalls[i] and mem.upeta[i] then local eligiblecars = {} local permanent = false for _,carid in pairs(mem.params.carids) do if getdpos(carid) == i and mem.carstatus[carid].direction == "up" then permanent = true end if mem.carstatus[carid].state == "normal" and mem.params.floorsserved[carid][i] then local serveshigher = false for floor,served in pairs(mem.params.floorsserved[carid]) do if floor > i and served then serveshigher = true break end end if serveshigher then eligiblecars[carid] = true end end end if not permanent then local besteta = 999 local bestcar for carid in pairs(eligiblecars) do local eta = calculateeta(carid,i,"up") if eta < besteta then besteta = eta bestcar = carid end end if mem.upeta[i]-besteta > 15 and mem.upeta[i]/besteta > 2 then send(mem.assignedup[i],"groupupcancel",realtocarfloor(mem.assignedup[i],i)) mem.upeta[i] = besteta send(bestcar,"groupupcall",realtocarfloor(bestcar,i)) mem.assignedup[i] = bestcar end end end end for floor,carid in pairs(mem.assignedup) do mem.upeta[floor] = calculateeta(carid,floor,"up") end for i in pairs(unassigneddn) do local eligiblecars = {} local permanent = false for _,carid in pairs(mem.params.carids) do if getdpos(carid) == i and mem.carstatus[carid].direction == "down" then permanent = true end if mem.carstatus[carid].state == "normal" and mem.params.floorsserved[carid][i] then local serveslower = false for floor,served in pairs(mem.params.floorsserved[carid]) do if floor < i and served then serveslower = true break end end if serveslower then eligiblecars[carid] = true end end end if not permanent then local besteta = 999 local bestcar for carid in pairs(eligiblecars) do local eta = calculateeta(carid,i,"down") if eta < besteta then besteta = eta bestcar = carid end end mem.dneta[i] = besteta if bestcar then send(bestcar,"groupdncall",realtocarfloor(bestcar,i)) mem.assigneddn[i] = bestcar else mem.dncalls[i] = nil end end end for i in pairs(mem.assigneddn) do if mem.dncalls[i] and mem.dneta[i] then local eligiblecars = {} for _,carid in pairs(mem.params.carids) do if mem.carstatus[carid].state == "normal" and mem.params.floorsserved[carid][i] then local serveslower = false for floor,served in pairs(mem.params.floorsserved[carid]) do if floor < i and served then serveslower = true break end end if serveslower then eligiblecars[carid] = true end end end local besteta = 999 local bestcar for carid in pairs(eligiblecars) do local eta = calculateeta(carid,i,"down") if eta < besteta then besteta = eta bestcar = carid end end if mem.dneta[i]-besteta > 15 and mem.dneta[i]/besteta > 2 then send(mem.assigneddn[i],"groupdncancel",realtocarfloor(mem.assigneddn[i],i)) mem.dneta[i] = besteta send(bestcar,"groupdncall",realtocarfloor(bestcar,i)) mem.assigneddn[i] = bestcar end end end for floor,carid in pairs(mem.assigneddn) do mem.dneta[floor] = calculateeta(carid,floor,"down") end interrupt(0.5,"getstatus") elseif event.iid == "getstatus" then for _,carid in ipairs(mem.params.carids) do send(carid,"getstatus") end elseif event.type == "callbutton" then if event.dir == "up" and event.landing >= 1 and event.landing < #mem.params.floornames then mem.upcalls[event.landing] = true elseif event.dir == "down" and event.landing > 1 and event.landing <= #mem.params.floornames then mem.dncalls[event.landing] = true end elseif event.type == "fs1switch" then mem.fs1switch = event.state mem.fs1led = event.state for _,carid in ipairs(mem.params.carids) do send(carid,"fs1switch",event.state) end end if not (mem.screenstate == "status" or mem.screenstate == "menu") then mem.upcalls = {} mem.dncalls = {} end fs("formspec_version[6]") fs("size[20,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.carids-mem.editingconnection+1)..";false]") else fs("label[1,2;No Connections]") end if #mem.params.carids < 16 then fs("button[8,2;3,1;add;New Connection]") end if #mem.params.carids > 0 then fs("button[8,3.5;3,1;edit;Edit Connection]") end if #mem.params.carids > 0 then fs("button[8,5;3,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_connection" or mem.screenstate == "connection" 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;save;Save >]") end else fs("label[1,1;EDIT CONNECTION]") fs("button[1,10;2,1;back;< Back]") if numfloors >= 2 then fs("button[13,10;2,1;save;Save >]") 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("label[2,3;Car ID: "..mem.params.carids[mem.editingconnection].."]") 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]") elseif mem.screenstate == "status" then if not mem.screenpage then mem.screenpage = 1 end fs("label[1,1;GROUP DISPLAY]") fs("box[1.5,1.5;0.1,10;#AAAAAAFF]") fs("box[18.5,1.5;0.1,10;#AAAAAAFF]") fs("label[0.55,11.5;UP]") fs("label[18.85,11.5;DOWN]") fs("button[15,0.5;2,1;menu;Menu]") fs("style_type[image_button;font=mono;font_size=*0.75]") for car=1,#mem.params.carids,1 do local xp = 1.7+(car-1) local carid = mem.params.carids[car] local carstate = mem.carstatus[carid].state fs(string.format("label[%f,11;CAR %d]",xp,car)) fs(string.format("label[%f,11.35;%s]",xp+0.1,minetest.colorize("#ff5555",(carstate == "normal" and " IN" or "OUT")))) end local lowestfloor = (mem.screenpage-1)*10+1 for i=1,math.min(10,#mem.params.floornames-lowestfloor+1),1 do local yp = 9.75-0.8*(i-1) local floor = i+lowestfloor-1 fs(string.format("label[0.9,%f;%s]",yp+0.35,mem.params.floornames[floor])) local uplabel = "" if mem.upcalls[floor] then uplabel = minetest.colorize("#55FF55",math.floor(mem.upeta[floor] or 0)) end if floor < #mem.params.floornames then fs(string.format("image_button[0.15,%f;0.75,0.75;celevator_fs_bg.png;upcall%d;%s]",yp,floor,uplabel)) end fs(string.format("label[18.65,%f;%s]",yp+0.35,mem.params.floornames[floor])) local dnlabel = "" if mem.dncalls[floor] then dnlabel = minetest.colorize("#FF5555",math.floor(mem.dneta[floor] or 0)) end if floor > 1 then fs(string.format("image_button[19.1,%f;0.75,0.75;celevator_fs_bg.png;dncall%d;%s]",yp,floor,dnlabel)) end for car=1,#mem.params.carids,1 do local xp = 1.7+(car-1) local carid = mem.params.carids[car] local carfloor = realtocarfloor(carid,floor) if carfloor then local ccdot = mem.carstatus[carid].carcalls[carfloor] and "*" or "" local groupup = mem.carstatus[carid].groupupcalls[carfloor] and minetest.colorize("#55FF55","^") or "" local swingup = mem.carstatus[carid].swingupcalls[carfloor] and minetest.colorize("#FFFF55","^") or "" local swingdn = mem.carstatus[carid].swingdncalls[carfloor] and minetest.colorize("#FFFF55","v") or "" local groupdn = mem.carstatus[carid].groupdncalls[carfloor] and minetest.colorize("#FF5555","v") or "" ccdot = groupup..swingup..ccdot..swingdn..groupdn if getpos(carid) == floor then local cargraphics = { open = "\\[ \\]", opening = "\\[< >\\]", closing = "\\[> <\\]", closed = "\\[ | \\]", testtiming = "\\[ | \\]", } ccdot = cargraphics[mem.carstatus[carid].doorstate] if mem.carstatus[carid].direction == "up" then ccdot = minetest.colorize("#55FF55",ccdot) elseif mem.carstatus[carid].direction == "down" then ccdot = minetest.colorize("#FF5555",ccdot) end end fs(string.format("image_button[%f,%f;0.75,0.75;celevator_fs_bg.png;carcall%02d%d;%s]",xp,yp,car,floor,ccdot)) end end end if lowestfloor > 1 then fs("image_button[6,0.5;0.75,0.75;celevator_menu_arrow.png^\\[transformFY;scrolldown;;false;false;celevator_menu_arrow.png^\\[transformFY]") end if lowestfloor+9 < #mem.params.floornames then fs("image_button[5,0.5;0.75,0.75;celevator_menu_arrow.png;scrollup;;false;false;celevator_menu_arrow.png]") end elseif mem.screenstate == "menu" then fs("label[1,1;MAIN MENU]") fs("button[1,3;3,1;floortable;Edit Floor Table]") fs("button[1,4.5;3,1;connections;Edit Connections]") fs("button[1,10;3,1;back;< Back]") end mem.infotext = string.format("ID: %d",mem.carid) return pos,mem,changedinterrupts