Add bits that are done so far

This includes:
* Controller (runs and responds to calls placed on the screen, parameter editing and switches work)
* Null Drive (simulates motion so the controller can run, no actual movement yet)
* Call Buttons (lights can be toggled with right-click, no communication yet)
This commit is contained in:
cheapie 2023-08-04 11:25:45 -05:00
commit bbdf947d7c
32 changed files with 1607 additions and 0 deletions

12
.luacheckrc Normal file
View File

@ -0,0 +1,12 @@
max_line_length = 200
globals = {
"celevator",
}
read_globals = {
"DIR_DELIM",
"vector",
"screwdriver",
"minetest",
}

22
COPYING Normal file
View File

@ -0,0 +1,22 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

152
callbuttons.lua Normal file
View File

@ -0,0 +1,152 @@
local function makebuttontex(dir,upon,downon)
local tex = "[combine:64x64:0,0=celevator_cabinet_sides.png:32,0=celevator_cabinet_sides.png:0,32=celevator_cabinet_sides.png:32,32=celevator_cabinet_sides.png:22,24=celevator_callbutton_panel.png"
if dir == "up" then
tex = tex..":24,35=celevator_callbutton_up.png"
if upon then
tex = tex..":33,36=celevator_callbutton_light.png"
end
elseif dir == "down" then
tex = tex..":24,35=celevator_callbutton_down.png"
if downon then
tex = tex..":33,36=celevator_callbutton_light.png"
end
elseif dir == "both" then
tex = tex..":24,28=celevator_callbutton_up.png:24,43=celevator_callbutton_down.png"
if upon then
tex = tex..":33,29=celevator_callbutton_light.png"
end
if downon then
tex = tex..":33,44=celevator_callbutton_light.png"
end
end
return(tex)
end
local validstates = {
{"up",false,false,"Up"},
{"up",true,false,"Up"},
{"down",false,false,"Down"},
{"down",false,true,"Down"},
{"both",false,false,"Up and Down"},
{"both",true,false,"Up and Down"},
{"both",false,true,"Up and Down"},
{"both",true,true,"Up and Down"},
}
local function setlight(pos,dir,newstate)
local node = minetest.get_node(pos)
if minetest.get_item_group(node.name,"_celevator_callbutton") ~= 1 then return end
if dir == "up" then
if minetest.get_item_group(node.name,"_celevator_callbutton_has_up") ~= 1 then return end
local lit = minetest.get_item_group(node.name,"_celevator_callbutton_up_lit") == 1
if lit == newstate then return end
local newname = "celevator:callbutton_"
if minetest.get_item_group(node.name,"_celevator_callbutton_has_down") == 1 then
newname = newname.."both"
else
newname = newname.."up"
end
if newstate then newname = newname.."_upon" end
if minetest.get_item_group(node.name,"_celevator_callbutton_down_lit") == 1 then
newname = newname.."_downon"
end
node.name = newname
minetest.swap_node(pos,node)
elseif dir == "down" then
if minetest.get_item_group(node.name,"_celevator_callbutton_has_down") ~= 1 then return end
local lit = minetest.get_item_group(node.name,"_celevator_callbutton_down_lit") == 1
if lit == newstate then return end
local newname = "celevator:callbutton_"
if minetest.get_item_group(node.name,"_celevator_callbutton_has_up") == 1 then
newname = newname.."both"
else
newname = newname.."down"
end
if minetest.get_item_group(node.name,"_celevator_callbutton_up_lit") == 1 then
newname = newname.."_upon"
end
if newstate then newname = newname.."_downon" end
node.name = newname
minetest.swap_node(pos,node)
end
end
local function disambiguatedir(pos,player)
if player and not player.is_fake_player then
local eyepos = vector.add(player:get_pos(),vector.add(player:get_eye_offset(),vector.new(0,1.5,0)))
local lookdir = player:get_look_dir()
local distance = vector.distance(eyepos,pos)
local endpos = vector.add(eyepos,vector.multiply(lookdir,distance+1))
local ray = minetest.raycast(eyepos,endpos,true,false)
local pointed,button,hitpos
repeat
pointed = ray:next()
if pointed and pointed.type == "node" then
local node = minetest.get_node(pointed.under)
if node.name and (minetest.get_item_group(node.name,"_celevator_callbutton") == 1) then
button = pointed.under
hitpos = vector.subtract(pointed.intersection_point,button)
end
end
until button or not pointed
if not hitpos then return end
hitpos.y = -1*hitpos.y
hitpos.y = math.floor((hitpos.y+0.5)*64+0.5)+1
return hitpos.y >= 40 and "down" or "up"
end
end
for _,state in ipairs(validstates) do
local boringside = "[combine:64x64:0,0=celevator_cabinet_sides.png:32,0=celevator_cabinet_sides.png:0,32=celevator_cabinet_sides.png:32,32=celevator_cabinet_sides.png"
local nname = "celevator:callbutton_"..state[1]
local dropname = nname
if state[2] then nname = nname.."_upon" end
if state[3] then nname = nname.."_downon" end
local idle = not (state[2] or state[3])
local description = string.format("%s Call Button%s",state[4],(idle and "" or " (on state, you hacker you!)"))
minetest.register_node(nname,{
description = description,
groups = {
dig_immediate = 2,
not_in_creative_inventory = (idle and 0 or 1),
_celevator_callbutton = 1,
_celevator_callbutton_has_up = (state[1] == "down" and 0 or 1),
_celevator_callbutton_has_down = (state[1] == "up" and 0 or 1),
_celevator_callbutton_up_lit = (state[2] and 1 or 0),
_celevator_callbutton_down_lit = (state[3] and 1 or 0),
},
drop = dropname,
tiles = {
boringside,
boringside,
boringside,
boringside,
boringside,
makebuttontex(state[1],state[2],state[3])
},
paramtype = "light",
paramtype2 = "facedir",
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = {
{-0.5, -0.5, -0.5, 0.5, 0.5, 0.5 },
{-0.16, -0.37,-0.59, 0.17, 0.13,-0.5 },
},
},
on_rightclick = function(pos,_,clicker)
if state[1] == "up" then
setlight(pos,"up",not state[2])
elseif state[1] == "down" then
setlight(pos,"down",not state[3])
elseif state[1] == "both" then
local dir = disambiguatedir(pos,clicker)
if dir == "up" then
setlight(pos,"up",not state[2])
elseif dir == "down" then
setlight(pos,"down",not state[3])
end
end
end,
})
end

492
controller.lua Normal file
View File

@ -0,0 +1,492 @@
celevator.controller = {}
celevator.controller.iqueue = minetest.deserialize(celevator.storage:get_string("controller_iqueue")) or {}
celevator.controller.equeue = minetest.deserialize(celevator.storage:get_string("controller_equeue")) or {}
celevator.controller.running = {}
local fw,err = loadfile(minetest.get_modpath("celevator")..DIR_DELIM.."controllerfw.lua")
if not fw then error(err) end
minetest.register_chatcommand("celevator_reloadcontroller",{
params = "",
description = "Reload celevator controller firmware from disk",
privs = {server = true},
func = function()
local newfw,loaderr = loadfile(minetest.get_modpath("celevator")..DIR_DELIM.."controllerfw.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:controller_top"
minetest.set_node(toppos,node)
end
local function ondestruct(pos)
pos.y = pos.y + 1
local topnode = minetest.get_node(pos)
local controllertops = {
["celevator:controller_top"] = true,
["celevator:controller_top_running"] = true,
["celevator:controller_top_open"] = true,
["celevator:controller_top_open_running"] = true,
}
if controllertops[topnode.name] then
minetest.set_node(pos,{name="air"})
end
celevator.controller.equeue[minetest.hash_node_position(pos)] = nil
celevator.storage:set_string("controller_equeue",minetest.serialize(celevator.controller.equeue))
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.controller.run(pos,event)
end
local function controllerleds(pos,running)
local toppos = vector.add(pos,vector.new(0,1,0))
local node = minetest.get_node(toppos)
local sparams = {
pos = toppos,
}
if node.name == "celevator:controller_top_open" and running then
node.name = "celevator:controller_top_open_running"
minetest.swap_node(toppos,node)
minetest.sound_play("celevator_controller_start",sparams,true)
elseif node.name == "celevator:controller_top" and running then
node.name = "celevator:controller_top_running"
minetest.swap_node(toppos,node)
minetest.sound_play("celevator_controller_start",sparams,true)
elseif node.name == "celevator:controller_top_open_running" and not running then
node.name = "celevator:controller_top_open"
minetest.swap_node(toppos,node)
minetest.sound_play("celevator_controller_stop",sparams,true)
elseif node.name == "celevator:controller_top_running" and not running then
node.name = "celevator:controller_top"
minetest.swap_node(toppos,node)
minetest.sound_play("celevator_controller_stop",sparams,true)
end
end
minetest.register_node("celevator:controller",{
description = "Controller",
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({}))
local event = {}
event.type = "program"
celevator.controller.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:controller_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)
if node.name == "celevator:controller_top_running" then
node.name = "celevator:controller_top_open_running"
else
node.name = "celevator:controller_top_open"
end
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:controller_open",{
description = "Controller (door open - you hacker you!)",
groups = {
cracky = 1,
not_in_creative_inventory = 1,
},
paramtype = "light",
paramtype2 = "facedir",
drawtype = "nodebox",
drop = "celevator:controller",
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",
"celevator_cabinet_front_bottom_open.png",
},
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:controller"
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)
if node.name == "celevator:controller_top_open_running" then
node.name = "celevator:controller_top_running"
else
node.name = "celevator:controller_top"
end
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:controller_top",{
description = "Controller (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_cabinet_front_top.png",
},
})
minetest.register_node("celevator:controller_top_running",{
description = "Controller (top section, car in motion - 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_cabinet_front_top.png",
},
})
minetest.register_node("celevator:controller_top_open",{
description = "Controller (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_cabinet_front_top_open_lside.png",
"celevator_cabinet_sides.png",
{
name="celevator_cabinet_front_top_open_stopped.png",
animation={type="vertical_frames", aspect_w=32, aspect_h=32, length=2},
}
},
})
minetest.register_node("celevator:controller_top_open_running",{
description = "Controller (top section, open, car in motion - 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_cabinet_front_top_open_lside.png",
"celevator_cabinet_sides.png",
{
name="celevator_cabinet_front_top_open_running.png",
animation={type="vertical_frames", aspect_w=32, aspect_h=32, length=2},
}
},
})
function celevator.controller.iscontroller(pos,call2)
local node = minetest.get_node(pos)
if node.name == "ignore" and not call2 then
minetest.forceload_block(pos)
return celevator.controller.iscontroller(pos,true)
elseif node.name == "celevator:controller" or node.name == "celevator:controller_open" then
return true
else
return false
end
end
function celevator.controller.finddrive(pos)
local node = minetest.get_node(pos)
local dir = minetest.facedir_to_dir(node.param2)
local drivepos = vector.add(pos,vector.new(0,1,0))
drivepos = vector.add(drivepos,vector.rotate_around_axis(dir,vector.new(0,-1,0),math.pi/2))
drivepos = vector.round(drivepos)
local drivename = minetest.get_node(drivepos).name
return drivepos,minetest.registered_nodes[drivename]._celevator_drive_type
end
function celevator.controller.finish(pos,mem)
if not celevator.controller.iscontroller(pos) then
return
else
local drivepos,drivetype = celevator.controller.finddrive(pos)
if drivetype then
for _,command in ipairs(mem.drive.commands) do
if command.command == "moveto" then
celevator.drives[drivetype].moveto(drivepos,command.pos)
elseif command.command == "setmaxvel" then
celevator.drives[drivetype].setmaxvel(drivepos,command.maxvel)
elseif command.command == "resetpos" then
celevator.drives[drivetype].resetpos(drivepos)
elseif command.command == "estop" then
celevator.drives[drivetype].estop(drivepos)
end
end
end
local meta = minetest.get_meta(pos)
local node = minetest.get_node(pos)
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 "")
meta:set_string("infotext",mem.infotext or "")
local hash = minetest.hash_node_position(pos)
celevator.controller.iqueue[hash] = mem.interrupts
celevator.storage:set_string("controller_iqueue",minetest.serialize(celevator.controller.iqueue))
controllerleds(pos,mem.showrunning)
celevator.controller.running[hash] = nil
if #celevator.controller.equeue[hash] > 0 then
local event = celevator.controller.equeue[hash][1]
table.remove(celevator.controller.equeue[hash],1)
celevator.storage:set_string("controller_equeue",minetest.serialize(celevator.controller.equeue))
celevator.controller.run(pos,event)
end
end
end
function celevator.controller.run(pos,event)
if not celevator.controller.iscontroller(pos) then
return
else
local hash = minetest.hash_node_position(pos)
if not celevator.controller.equeue[hash] then
celevator.controller.equeue[hash] = {}
celevator.storage:set_string("controller_equeue",minetest.serialize(celevator.controller.equeue))
end
if celevator.controller.running[hash] then
table.insert(celevator.controller.equeue[hash],event)
celevator.storage:set_string("controller_equeue",minetest.serialize(celevator.controller.equeue))
if #celevator.controller.equeue[hash] > 5 then
minetest.log("warning","[celevator] [controller] Async process for controller at %s is falling behind, %d events in queue",minetest.pos_to_string(pos),#celevator.controller.equeue[hash])
end
return
end
celevator.controller.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 controller memory at "..minetest.pos_to_string(pos))
mem = {}
end
mem.drive = {}
mem.drive.commands = {}
local drivepos,drivetype = celevator.controller.finddrive(pos)
if drivetype then
mem.drive.type = drivetype
mem.drive.status = celevator.drives[drivetype].getstatus(drivepos)
end
mem.interrupts = celevator.controller.iqueue[minetest.hash_node_position(pos)] or {}
minetest.handle_async(fw,celevator.controller.finish,pos,event,mem)
end
end
function celevator.controller.checkiqueue(dtime)
for hash,iqueue in pairs(celevator.controller.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.controller.run(pos,event)
end
end
end
end
minetest.register_globalstep(celevator.controller.checkiqueue)

707
controllerfw.lua Normal file
View File

@ -0,0 +1,707 @@
local pos,event,mem = ...
local function fault(ftype,fatal)
if fatal then mem.fatalfault = true end
if not mem.activefaults then mem.activefaults = {} end
if not mem.faultlog then mem.faultlog = {} end
if mem.activefaults[ftype] then return end
mem.activefaults[ftype] = true
table.insert(mem.faultlog,{ftype = ftype,timestamp = os.time()})
end
if not mem.drive.status then
fault("drivecomm",true)
mem.drive.status = {
apos = 0,
dpos = 0,
vel = 0,
maxvel = 0,
}
end
local juststarted = false
local modenames = {
normal = "Normal Operation",
uninit = "Uninitialized",
resync = "Position Sync - Floor",
bfdemand = "Position Sync - Terminal",
fault = "Fault",
stop = "Emergency Stop",
mrinspect = "Machine Room Inspection",
carinspect = "Car Top Inspection",
inspconflict = "Inspection Conflict",
fs1 = "Fire Service - Phase 1",
fs2 = "Fire Service - Phase 2",
indep = "Independent Service",
capture = "Captured",
test = "Test Mode",
}
local doorstates = {
open = "Open",
opening = "Opening",
closing = "Closing",
closed = "Closed",
testtiming = "Closed",
}
local faultnames = {
drivecomm = "Lost Communication With Drive",
}
local function drivecmd(command)
table.insert(mem.drive.commands,command)
end
local function interrupt(time,iid)
mem.interrupts[iid] = time
end
local function getpos()
local ret = 0
for k,v in ipairs(mem.params.floorheights) do
ret = ret+v
if ret > mem.drive.status.apos then return k end
end
return mem.params.floorheights[#mem.params.floorheights]
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 gotofloor(floor)
mem.carmotion = true
drivecmd({
command = "setmaxvel",
maxvel = mem.params.contractspeed
})
drivecmd({
command = "moveto",
pos = gettarget(floor)
})
interrupt(0,"checkdrive")
juststarted = true
end
local function getnextcallabove(dir)
for i=getpos(),#mem.params.floorheights,1 do
if not dir then
if mem.carcalls[i] then
return i,"car"
elseif mem.upcalls[i] then
return i,"up"
elseif mem.dncalls[i] then
return i,"down"
end
elseif dir == "up" then
if mem.carcalls[i] then
return i,"car"
elseif mem.upcalls[i] then
return i,"up"
end
elseif dir == "down" then
if mem.carcalls[i] then
return i,"car"
elseif mem.dncalls[i] then
return i,"down"
end
end
end
end
local function getnextcallbelow(dir)
for i=getpos(),1,-1 do
if not dir then
if mem.carcalls[i] then
return i,"car"
elseif mem.upcalls[i] then
return i,"up"
elseif mem.dncalls[i] then
return i,"down"
end
elseif dir == "up" then
if mem.carcalls[i] then
return i,"car"
elseif mem.upcalls[i] then
return i,"up"
end
elseif dir == "down" then
if mem.carcalls[i] then
return i,"car"
elseif mem.dncalls[i] then
return i,"down"
end
end
end
end
local function getlowestupcall()
for i=1,#mem.params.floornames,1 do
if mem.upcalls[i] then return i end
end
end
local function gethighestdowncall()
for i=#mem.params.floornames,1,-1 do
if mem.dncalls[i] then return i end
end
end
local function open()
--TODO: Door operator interface
mem.doorstate = "opening"
interrupt(2,"opened")
end
local function close()
--TODO: Door operator interface
mem.doorstate = "closing"
interrupt(2,"closed")
end
mem.formspec = ""
local function fs(element)
mem.formspec = mem.formspec..element
end
if event.type == "program" then
mem.carstate = "uninit"
mem.editingfloor = 1
mem.doorstate = "closed"
mem.carmotion = false
mem.carcalls = {}
mem.upcalls = {}
mem.dncalls = {}
mem.screenpage = 1
mem.scrollfollowscar = true
mem.controllerstopsw = false
mem.controllerinspectsw = false
mem.cartopinspectsw = false
mem.capturesw = false
mem.testsw = false
mem.activefaults = {}
mem.faultlog = {}
mem.fatalfault = false
if not mem.params then
mem.state = "unconfigured"
mem.screenstate = "oobe_welcome"
mem.params = {
contractspeed = 1,
floorheights = {5,5,5},
floornames = {"1","2","3"},
doortimer = 5,
groupmode = "simplex",
}
end
elseif event.type == "ui" then
if mem.screenstate == "oobe_welcome" then
if event.fields.license then
mem.screenstate = "oobe_license"
elseif event.fields.next then
mem.screenstate = "oobe_groupmode"
end
elseif mem.screenstate == "oobe_license" then
if event.fields.back then
mem.screenstate = "oobe_welcome"
end
elseif mem.screenstate == "oobe_groupmode" then
if event.fields.back then
mem.screenstate = "oobe_welcome"
elseif event.fields.simplex then
mem.screenstate = "oobe_floortable"
mem.params.groupmode = "simplex"
elseif event.fields.group then
mem.screenstate = "oobe_dispatcherconnect"
mem.params.groupmode = "group"
end
elseif mem.screenstate == "oobe_dispatcherconnect" then
if event.fields.back then
mem.screenstate = "oobe_groupmode"
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_groupmode"
elseif event.fields.next then
if mem.screenstate == "oobe_floortable" then
mem.activefaults = {}
mem.faultlog = {}
mem.fatalfault = false
end
mem.state = "configured"
mem.screenstate = (mem.screenstate == "oobe_floortable" and "status" or "parameters")
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
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 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 == "parameters" then
if event.fields.save then
mem.screenstate = "status"
local doortimer = tonumber(event.fields.doortimer)
if doortimer and doortimer > 0 and doortimer <= 30 then
mem.params.doortimer = doortimer
end
local contractspeed = tonumber(event.fields.contractspeed)
if contractspeed and contractspeed >= 0.1 and contractspeed <= 20 then
mem.params.contractspeed = contractspeed
end
elseif event.fields.floortable then
mem.screenstate = "floortable"
elseif event.fields.cancel then
mem.screenstate = "status"
end
elseif mem.screenstate == "status" then
for i=1,#mem.params.floornames,1 do
if event.fields[string.format("carcall%d",i)] and (mem.carstate == "normal" or mem.carstate == "test" or mem.carstate == "capture") then
mem.carcalls[i] = true
elseif event.fields[string.format("upcall%d",i)] and mem.carstate == "normal" and not mem.capturesw then
mem.upcalls[i] = true
elseif event.fields[string.format("downcall%d",i)] and mem.carstate == "normal" and not mem.capturesw then
mem.dncalls[i] = true
end
end
if event.fields.scrollup then
mem.screenpage = mem.screenpage + 1
mem.scrollfollowscar = false
elseif event.fields.scrolldown then
mem.screenpage = mem.screenpage - 1
mem.scrollfollowscar = false
elseif event.fields.scrollfollowscar then
mem.scrollfollowscar = (event.fields.scrollfollowscar == "true")
elseif event.fields.stopsw then
mem.controllerstopsw = not mem.controllerstopsw
elseif event.fields.inspectsw then
mem.controllerinspectsw = not mem.controllerinspectsw
elseif event.fields.capturesw then
mem.capturesw = not mem.capturesw
elseif event.fields.testsw then
mem.testsw = not mem.testsw
elseif event.fields.inspectup and mem.carstate == "mrinspect" and mem.doorstate == "closed" and getpos() < #mem.params.floornames then
mem.carmotion = true
juststarted = true
drivecmd({
command = "setmaxvel",
maxvel = 0.2,
})
drivecmd({
command = "moveto",
pos = math.floor(mem.drive.status.apos)+1
})
elseif event.fields.inspectdown and mem.carstate == "mrinspect" and mem.doorstate == "closed" and mem.drive.status.apos-1 >= 0 then
mem.carmotion = true
juststarted = true
drivecmd({
command = "setmaxvel",
maxvel = 0.2,
})
drivecmd({
command = "moveto",
pos = math.floor(mem.drive.status.apos)-1
})
elseif event.fields.parameters then
mem.screenstate = "parameters"
elseif event.fields.faults then
mem.screenstate = "faults"
end
elseif mem.screenstate == "faults" then
if event.fields.back then
mem.screenstate = "status"
elseif event.fields.clear then
mem.faultlog = {}
mem.activefaults = {}
mem.fatalfault = false
end
end
elseif event.iid == "opened" and mem.doorstate == "opening" then
mem.doorstate = "open"
if mem.carstate == "normal" then
interrupt(mem.params.doortimer,"close")
end
elseif event.iid == "close" and mem.doorstate == "open" then
close()
elseif event.iid == "closed" and (mem.doorstate == "closing" or mem.doorstate == "testtiming") then
mem.doorstate = "closed"
if mem.carstate == "bfdemand" then
drivecmd({
command = "setmaxvel",
maxvel = mem.params.contractspeed,
})
drivecmd({command = "resetpos"})
interrupt(0.1,"checkdrive")
mem.carmotion = true
juststarted = true
elseif mem.carstate == "resync" then
gotofloor(getpos())
interrupt(0.1,"checkdrive")
mem.carmotion = true
juststarted = true
end
end
local oldstate = mem.carstate
if mem.fatalfault then
mem.carstate = "fault"
drivecmd({command="estop"})
mem.carcalls = {}
mem.upcalls = {}
mem.dncalls = {}
mem.direction = nil
elseif mem.controllerstopsw or mem.screenstate == "floortable" or mem.screenstate == "floortable_edit" then
mem.carstate = "stop"
drivecmd({command="estop"})
mem.carcalls = {}
mem.upcalls = {}
mem.dncalls = {}
mem.direction = nil
elseif mem.controllerinspectsw and not mem.cartopinspectsw then
mem.carstate = "mrinspect"
mem.carcalls = {}
mem.upcalls = {}
mem.dncalls = {}
mem.direction = nil
if oldstate ~= "mrinspect" then drivecmd({command="estop"}) end
elseif mem.testsw then
mem.upcalls = {}
mem.dncalls = {}
mem.carstate = "test"
elseif mem.capturesw then
mem.upcalls = {}
mem.dncalls = {}
if not mem.direction then mem.carstate = "capture" end
else
if oldstate == "stop" or oldstate == "mrinspect" or oldstate == "fault" then
mem.carstate = "resync"
gotofloor(getpos())
elseif oldstate == "test" or oldstate == "capture" then
mem.carstate = "normal"
end
end
if mem.carmotion then
mem.carmotion = (mem.drive.status.vel ~= 0) or juststarted
if mem.carmotion then
interrupt(0.1,"checkdrive")
else
if mem.carstate == "normal" then
mem.carcalls[getpos()] = nil
if mem.direction == "up" then
mem.upcalls[getpos()] = nil
elseif mem.direction == "down" then
mem.dncalls[getpos()] = nil
end
if getpos() >= #mem.params.floornames then
mem.direction = "down"
elseif getpos() <= 1 then
mem.direction = "up"
end
open()
elseif mem.carstate == "test" then
mem.carcalls[getpos()] = nil
mem.doorstate = "testtiming"
interrupt(5,"closed")
if getpos() >= #mem.params.floornames then
mem.direction = "down"
elseif getpos() <= 1 then
mem.direction = "up"
end
elseif mem.carstate == "bfdemand" or mem.carstate == "resync" then
mem.carstate = "normal"
end
end
end
if (mem.carstate == "normal" or mem.carstate == "capture" or mem.carstate == "test") and mem.doorstate == "closed" and not mem.carmotion then
if mem.direction == "up" then
if getnextcallabove("up") then
mem.direction = "up"
gotofloor(getnextcallabove("up"))
elseif gethighestdowncall() then
mem.direction = "down"
gotofloor(gethighestdowncall())
elseif getlowestupcall() then
gotofloor(getlowestupcall())
elseif getnextcallbelow("down") then
mem.direction = "down"
gotofloor(getnextcallbelow("down"))
else
mem.direction = nil
end
elseif mem.direction == "down" then
if getnextcallbelow("down") then
gotofloor(getnextcallbelow("down"))
elseif getlowestupcall() then
mem.direction = "up"
gotofloor(getlowestupcall())
elseif gethighestdowncall() then
gotofloor(gethighestdowncall())
elseif getnextcallabove("up") then
mem.direction = "up"
gotofloor(getnextcallabove())
else
mem.direction = nil
end
else
if getnextcallabove("up") then
mem.direction = "up"
gotofloor(getnextcallabove())
elseif getnextcallbelow("down") then
mem.direction = "down"
gotofloor(getnextcallbelow("down"))
elseif getlowestupcall() then
mem.direction = "up"
gotofloor(getlowestupcall())
elseif gethighestdowncall() then
mem.direction = "down"
gotofloor(gethighestdowncall())
end
end
if mem.carstate == "normal" and mem.capturesw and not mem.direction then
mem.upcalls = {}
mem.dncalls = {}
mem.carstate = "capture"
elseif mem.carstate == "capture" and mem.direction then
mem.carstate = "normal"
end
end
if mem.scrollfollowscar and mem.screenstate == "status" then
mem.screenpage = math.floor((getpos()-1)/10)+1
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 controller!]")
fs("label[1,4.5;This setup wizard is designed to get your elevator up and running as quickly as possible.]")
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 controller:;"..license.."]")
fs("button[7,10.5;2,1;back;OK]")
elseif mem.screenstate == "oobe_groupmode" then
fs("button[1,10;2,1;back;< Back]")
fs("label[1,1;Select a group operation mode:]")
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)]")
elseif mem.screenstate == "oobe_dispatcherconnect" then
fs("button[1,10;2,1;back;< Back]")
fs("label[1,1;Not yet implemented. Press Back.]")
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.]")
fs("button[1,10;2,1;back;< Back]")
fs("button[13,10;2,1;next;Done >]")
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]")
fs("button[8,2;2,1;add;New Floor]")
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 == "status" then
fs("style_type[image_button;font=mono;font_size=*0.75]")
fs("box[12,2.5;0.1,9;#AAAAAAFF]")
fs("box[13.12,2.5;0.05,9;#AAAAAAFF]")
fs("box[14.12,2.5;0.05,9;#AAAAAAFF]")
fs("box[15.25,2.5;0.1,9;#AAAAAAFF]")
fs("label[12.5,2;UP]")
fs("label[13.38,2;CAR]")
fs("label[14.25,2;DOWN]")
local maxfloor = #mem.params.floornames
local bottom = (mem.screenpage-1)*10+1
for i=0,9,1 do
local ypos = 11-(i*0.9)
local floornum = bottom+i
if floornum > maxfloor then break end
fs(string.format("label[11.25,%f;%s]",ypos,mem.params.floornames[floornum]))
local ccdot = mem.carcalls[floornum] and "*" or ""
if getpos() == floornum then
local cargraphics = {
open = "\\[ \\]",
opening = "\\[< >\\]",
closing = "\\[> <\\]",
closed = "\\[ | \\]",
testtiming = "\\[ | \\]",
}
ccdot = cargraphics[mem.doorstate]
if mem.direction == "up" then
ccdot = minetest.colorize("#55FF55",ccdot)
elseif mem.direction == "down" then
ccdot = minetest.colorize("#FF5555",ccdot)
end
end
fs(string.format("image_button[13.25,%f;0.75,0.75;celevator_fs_bg.png;carcall%d;%s]",ypos-0.25,floornum,ccdot))
if floornum < maxfloor then
local arrow = mem.upcalls[floornum] and minetest.colorize("#55FF55","^") or ""
fs(string.format("image_button[12.25,%f;0.75,0.75;celevator_fs_bg.png;upcall%d;%s]",ypos-0.25,floornum,arrow))
end
if floornum > 1 then
local arrow = mem.dncalls[floornum] and minetest.colorize("#FF5555","v") or ""
fs(string.format("image_button[14.25,%f;0.75,0.75;celevator_fs_bg.png;downcall%d;%s]",ypos-0.25,floornum,arrow))
end
end
if maxfloor > 10 then
fs(string.format("checkbox[13,1.25;scrollfollowscar;Follow Car;%s]",tostring(mem.scrollfollowscar)))
if bottom+9 < maxfloor then
fs("image_button[12.75,0.25;0.75,0.75;celevator_menu_arrow.png;scrollup;;false;false;celevator_menu_arrow.png]")
end
if bottom > 1 then
fs("image_button[13.87,0.25;0.75,0.75;celevator_menu_arrow.png^\\[transformFY;scrolldown;;false;false;celevator_menu_arrow.png^\\[transformFY]")
end
end
fs("label[1,1;CAR STATUS]")
fs(string.format("label[1,2;%s]",modenames[mem.carstate]))
fs(string.format("label[1,2.5;Doors %s]",doorstates[mem.doorstate]))
fs(string.format("label[1,3;Position: %0.02fm Speed: %+0.02fm/s PI: %s]",mem.drive.status.apos,mem.drive.status.vel,minetest.formspec_escape(mem.params.floornames[getpos()])))
if #mem.faultlog > 0 then
fs("label[1,3.5;Fault(s) Active]")
else
fs("label[1,3.5;No Current Faults]")
end
fs("button[1,10;3,1;faults;Fault History]")
fs("button[4.5,10;3,1;parameters;Edit Parameters]")
fs("style[*;font=mono]")
local stopswimg = "celevator_toggle_switch.png"..(mem.controllerstopsw and "^\\[transformFY" or "")
fs(string.format("image_button[1,5;1,1.33;%s;stopsw;;false;false;%s]",stopswimg,stopswimg))
fs("label[1.3,4.75;RUN]")
fs("label[1.2,6.6;STOP]")
local captureswimg = "celevator_toggle_switch.png"..(mem.capturesw and "" or "^\\[transformFY")
fs(string.format("image_button[3,5;1,1.33;%s;capturesw;;false;false;%s]",captureswimg,captureswimg))
fs("label[3,4.75;CAPTURE]")
local testswimg = "celevator_toggle_switch.png"..(mem.testsw and "" or "^\\[transformFY")
fs(string.format("image_button[5,5;1,1.33;%s;testsw;;false;false;%s]",testswimg,testswimg))
fs("label[5.23,4.75;TEST]")
local inspectswimg = "celevator_toggle_switch.png"..(mem.controllerinspectsw and "" or "^\\[transformFY")
fs(string.format("image_button[1,8;1,1.33;%s;inspectsw;;false;false;%s]",inspectswimg,inspectswimg))
fs("label[1.05,7.75;INSPECT]")
fs("label[1.1,9.6;NORMAL]")
fs(string.format("image_button[3,8.25;1,1;%s;inspectup;;false;false;%s]","celevator_button_black.png","celevator_button_black.png"))
fs("label[3.4,7.75;UP]")
fs(string.format("image_button[5,8.25;1,1;%s;inspectdown;;false;false;%s]","celevator_button_black.png","celevator_button_black.png"))
fs("label[5.25,7.75;DOWN]")
elseif mem.screenstate == "parameters" then
fs("label[1,1;EDIT PARAMETERS]")
fs("button[1,10;3,1;save;Save]")
fs("button[4.5,10;3,1;cancel;Cancel]")
fs("button[8,10;3,1;floortable;Edit Floor Table]")
fs(string.format("field[1,3;3,1;doortimer;Door Dwell Timer;%0.1f]",mem.params.doortimer))
fs(string.format("field[1,5;3,1;contractspeed;Contract Speed (m/s);%0.1f]",mem.params.contractspeed))
elseif mem.screenstate == "faults" then
fs("label[1,1;FAULT HISTORY]")
if #mem.faultlog > 0 then
for i=0,9,1 do
if #mem.faultlog-i >= 1 then
local currfault = mem.faultlog[#mem.faultlog-i]
local date = os.date("*t",currfault.timestamp)
fs(string.format("label[1,%0.1f;%04d-%02d-%02d %02d:%02d:%02d - %s]",2+i,date.year,date.month,date.day,date.hour,date.min,date.sec,faultnames[currfault.ftype]))
end
end
else
fs("label[1,2;No Current Faults]")
end
fs("button[1,10;3,1;back;Back]")
fs("button[4.5,10;3,1;clear;Clear]")
end
local arrow = " "
if mem.drive.status.dpos > mem.drive.status.apos then
arrow = "^"
elseif mem.drive.status.dpos < mem.drive.status.apos then
arrow = "v"
end
mem.infotext = string.format("Floor %s %s - %s - Doors %s",mem.params.floornames[getpos()],arrow,modenames[mem.carstate],doorstates[mem.doorstate])
if mem.drive.type then
mem.showrunning = mem.drive.status.vel ~= 0
else
mem.showrunning = false
end
return pos,mem

206
drive_null.lua Normal file
View File

@ -0,0 +1,206 @@
celevator.drives.null = {
name = "Null Drive",
description = "Simulation only, no movement, for testing and demonstration",
nname = "celevator:drive_null",
soundhandles = {},
}
local function update_ui(pos)
local meta = minetest.get_meta(pos)
local apos = tonumber(meta:get_string("apos")) or 0
local status = "Idle"
local vel = tonumber(meta:get_string("vel")) or 0
if vel > 0 then
status = string.format("Running: Up, %0.02f m/s",vel)
elseif vel < 0 then
status = string.format("Running: Down, %0.02f m/s",math.abs(vel))
end
meta:set_string("infotext",string.format("Null Drive - %s - Position: %0.02f m",status,apos))
end
local function playbuzz(pos)
local hash = minetest.hash_node_position(pos)
if celevator.drives.null.soundhandles[hash] == "cancel" then return end
celevator.drives.null.soundhandles[hash] = minetest.sound_play("celevator_drive_run",{
pos = pos,
loop = true,
gain = 0.4,
})
end
local function startbuzz(pos)
local hash = minetest.hash_node_position(pos)
if celevator.drives.null.soundhandles[hash] == "cancel" then
celevator.drives.null.soundhandles[hash] = nil
return
end
if celevator.drives.null.soundhandles[hash] then return end
celevator.drives.null.soundhandles[hash] = "pending"
minetest.after(0.5,playbuzz,pos)
end
local function stopbuzz(pos)
local hash = minetest.hash_node_position(pos)
if not celevator.drives.null.soundhandles[hash] then return end
if celevator.drives.null.soundhandles[hash] == "pending" then
celevator.drives.null.soundhandles[hash] = "cancel"
end
if type(celevator.drives.null.soundhandles[hash]) ~= "string" then
minetest.sound_stop(celevator.drives.null.soundhandles[hash])
celevator.drives.null.soundhandles[hash] = nil
end
end
minetest.register_node("celevator:drive_null",{
description = celevator.drives.null.name,
groups = {
cracky = 1,
},
tiles = {
"celevator_cabinet_sides.png",
"celevator_cabinet_sides.png",
"celevator_cabinet_sides.png",
"celevator_cabinet_sides.png",
"celevator_cabinet_sides.png",
"celevator_drive_front.png",
},
paramtype = "light",
paramtype2 = "facedir",
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = {
{-0.4,-0.4,-0.1,0.4,0.4,0.5},
{-0.5,-0.3,0.4,-0.4,-0.22,0.32},
{-0.5,0.22,0.4,-0.4,0.3,0.32},
},
},
_celevator_drive_type = "null",
after_place_node = function(pos)
local meta = minetest.get_meta(pos)
meta:set_string("apos","0")
meta:set_string("dpos","0")
meta:set_string("vel","0")
meta:set_string("maxvel","0.2")
update_ui(pos)
end,
on_destruct = stopbuzz,
})
function celevator.drives.null.step(dtime)
local nulldrives_running = minetest.deserialize(celevator.storage:get_string("nulldrives_running")) or {}
local save = false
for i,hash in ipairs(nulldrives_running) do
save = true
local pos = minetest.get_position_from_hash(hash)
local node = minetest.get_node(pos)
local sound = false
if node.name == "ignore" then
minetest.forceload_block(pos,true)
elseif node.name ~= "celevator:drive_null" then
table.remove(nulldrives_running,i)
else
local meta = minetest.get_meta(pos)
local apos = tonumber(meta:get_string("apos")) or 0
local dpos = tonumber(meta:get_string("dpos")) or 0
local maxvel = tonumber(meta:get_string("maxvel")) or 0.2
local dremain = math.abs(dpos-apos)
local vel = maxvel
if dremain < 0.5 then vel = math.min(0.2,vel) end
local stepdist = vel*dtime
if dpos > apos then
local newpos = apos + stepdist
if newpos < dpos then
meta:set_string("apos",tostring(newpos))
meta:set_string("vel",vel)
sound = true
else
meta:set_string("apos",tostring(dpos))
meta:set_string("vel",0)
sound = false
end
elseif dpos < apos then
local newpos = apos - stepdist
if newpos > dpos then
meta:set_string("apos",tostring(newpos))
meta:set_string("vel",0-vel)
sound = true
else
meta:set_string("apos",tostring(dpos))
meta:set_string("vel",0)
sound = false
end
else
table.remove(nulldrives_running,i)
end
end
update_ui(pos)
if sound then
startbuzz(pos)
else
stopbuzz(pos)
end
end
if save then
celevator.storage:set_string("nulldrives_running",minetest.serialize(nulldrives_running))
end
end
minetest.register_globalstep(celevator.drives.null.step)
function celevator.drives.null.moveto(pos,target)
local meta = minetest.get_meta(pos)
meta:set_string("dpos",tostring(target))
local hash = minetest.hash_node_position(pos)
local nulldrives_running = minetest.deserialize(celevator.storage:get_string("nulldrives_running")) or {}
local running = false
for _,dhash in ipairs(nulldrives_running) do
if hash == dhash then
running = true
break
end
end
if not running then
table.insert(nulldrives_running,hash)
celevator.storage:set_string("nulldrives_running",minetest.serialize(nulldrives_running))
end
end
function celevator.drives.null.resetpos(pos)
celevator.drives.null.moveto(pos,0)
end
function celevator.drives.null.estop(pos)
local meta = minetest.get_meta(pos)
meta:set_string("dpos",meta:get_string("apos"))
meta:set_string("vel","0")
end
function celevator.drives.null.setmaxvel(pos,maxvel)
local meta = minetest.get_meta(pos)
meta:set_string("maxvel",tostring(maxvel))
end
function celevator.drives.null.rezero(pos)
celevator.drives.null.moveto(pos,0)
end
function celevator.drives.null.getstatus(pos,call2)
local node = minetest.get_node(pos)
if node.name == "ignore" and not call2 then
minetest.forceload_block(pos,true)
return celevator.drives.null.get_status(pos,true)
elseif node.name ~= "celevator:drive_null" then
minetest.log("error","[celevator] [null drive] Could not load drive status at "..minetest.pos_to_string(pos))
return
else
local meta = minetest.get_meta(pos)
local ret = {}
ret.apos = tonumber(meta:get_string("apos")) or 0
ret.dpos = tonumber(meta:get_string("dpos")) or 0
ret.vel = tonumber(meta:get_string("vel")) or 0
ret.maxvel = tonumber(meta:get_string("maxvel")) or 0.2
ret.neareststop = ret.apos
return ret
end
end

4
framework.lua Normal file
View File

@ -0,0 +1,4 @@
celevator = {
drives = {},
storage = minetest.get_mod_storage(),
}

10
init.lua Normal file
View File

@ -0,0 +1,10 @@
local components = {
"framework",
"drive_null",
"controller",
"callbuttons",
}
for _,v in ipairs(components) do
dofile(string.format("%s%s%s.lua",minetest.get_modpath("celevator"),DIR_DELIM,v))
end

2
mod.conf Normal file
View File

@ -0,0 +1,2 @@
name = celevator
description = WIP

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 598 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
textures/celevator_logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 676 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 681 B