techage/basis/node_states.lua

529 lines
16 KiB
Lua
Raw Normal View History

2019-03-02 14:24:48 +03:00
--[[
TechAge
=======
Copyright (C) 2019 Joachim Stolberg
GPL v3
2019-03-02 14:24:48 +03:00
See LICENSE.txt for more information
A state model/class for TechAge nodes.
]]--
--[[
Node states:
+-----------------------------------+ +------------+
| | | |
| V V |
| +---------+ |
| | | |
| +---------| STOPPED | |
| | | | |
| button | +---------+ |
| | ^ |
2019-06-16 22:06:16 +03:00
button | V | button |
2019-03-02 14:24:48 +03:00
| +---------+ | | button
2019-06-16 22:06:16 +03:00
| +--------->| |---------+ |
| | power | RUNNING | |
| | +------| |---------+ |
| | | +---------+ | |
| | | ^ | | |
| | | | | | |
| | V | V V |
2019-03-02 14:24:48 +03:00
| +---------+ +----------+ +---------+ |
| | | | | | | |
2019-06-16 22:06:16 +03:00
+---| NOPOWER | | STANDBY/ | | FAULT |----------+
2019-03-02 14:24:48 +03:00
| | | BLOCKED | | |
+---------+ +----------+ +---------+
| cycle time operational needs power
+---------+------------+-------------+-------------
| RUNNING normal yes yes
| BLOCKED long yes no
| STANDBY long yes no
| NOPOWER long no no
| FAULT none no no
| STOPPED none no no
2019-03-09 21:26:15 +03:00
Node mem data:
"techage_state" - node state, like "RUNNING"
"techage_item_meter" - node item/runtime counter
"techage_countdown" - countdown to stadby mode
2019-03-02 14:24:48 +03:00
]]--
-- for lazy programmers
local S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local P = minetest.string_to_pos
local M = minetest.get_meta
--
-- TechAge machine states
--
techage.RUNNING = 1 -- in normal operation/turned on
techage.BLOCKED = 2 -- a pushing node is blocked due to a full destination inventory
techage.STANDBY = 3 -- nothing to do (e.g. no input items), or node (world) not loaded
techage.NOPOWER = 4 -- only for power consuming nodes, no operation
2019-05-21 14:15:13 +03:00
techage.FAULT = 5 -- any fault state (e.g. wrong source items), which can be fixed by the player
techage.STOPPED = 6 -- not operational/turned off
2019-03-02 14:24:48 +03:00
techage.StatesImg = {
"techage_inv_button_on.png",
"techage_inv_button_warning.png",
"techage_inv_button_standby.png",
2019-05-21 14:15:13 +03:00
"techage_inv_button_nopower.png",
"techage_inv_button_error.png",
"techage_inv_button_off.png",
2019-03-02 14:24:48 +03:00
}
-- Return state button image for the node inventory
function techage.state_button(state)
if state and state < 7 and state > 0 then
return techage.StatesImg[state]
end
return "techage_inv_button_off.png"
2019-03-02 14:24:48 +03:00
end
function techage.get_power_image(pos, mem)
local node = techage.get_node_lvm(pos)
local s = "3" -- electrical power
if string.find(node.name, "techage:ta2") then
s = "2" -- axles power
end
return "techage_inv_powerT"..s..".png"
end
2019-03-02 14:24:48 +03:00
-- State string based on button states
techage.StateStrings = {"running", "blocked", "standby", "nopower", "fault", "stopped"}
2019-03-02 14:24:48 +03:00
--
-- Local States
--
local RUNNING = techage.RUNNING
local BLOCKED = techage.BLOCKED
2019-03-02 14:24:48 +03:00
local STANDBY = techage.STANDBY
2019-05-21 14:15:13 +03:00
local NOPOWER = techage.NOPOWER
2019-03-02 14:24:48 +03:00
local FAULT = techage.FAULT
local STOPPED = techage.STOPPED
2019-03-02 14:24:48 +03:00
--
-- NodeStates Class Functions
--
techage.NodeStates = {}
local NodeStates = techage.NodeStates
local function can_start(pos, mem)
--if false, node goes in FAULT
return true
end
local function has_power(pos, mem)
--if false, node goes in NOPOWER
2019-03-02 14:24:48 +03:00
return true
end
2019-03-06 00:42:31 +03:00
local function swap_node(pos, name)
local node = techage.get_node_lvm(pos)
2019-03-06 00:42:31 +03:00
if node.name == name then
return
end
node.name = name
minetest.swap_node(pos, node)
end
-- true if node_timer should be executed
function techage.is_operational(mem)
local state = mem.techage_state or STOPPED
return state < NOPOWER
end
2019-08-17 00:44:11 +03:00
-- consumes power
function techage.needs_power(mem)
local state = mem.techage_state or STOPPED
return state < BLOCKED
end
function techage.needs_power2(state)
return state < BLOCKED
end
function techage.get_state_string(mem)
return techage.StateStrings[mem.techage_state or STOPPED]
2019-08-17 00:44:11 +03:00
end
2019-03-02 14:24:48 +03:00
function NodeStates:new(attr)
local o = {
-- mandatory
cycle_time = attr.cycle_time, -- for running state
standby_ticks = attr.standby_ticks, -- for standby state
-- optional
node_name_passive = attr.node_name_passive,
node_name_active = attr.node_name_active,
infotext_name = attr.infotext_name,
has_power = attr.has_power or has_power,
can_start = attr.can_start or can_start,
start_node = attr.start_node,
stop_node = attr.stop_node,
2019-03-02 14:24:48 +03:00
formspec_func = attr.formspec_func,
2019-06-09 16:02:17 +03:00
on_state_change = attr.on_state_change,
2019-03-02 14:24:48 +03:00
}
setmetatable(o, self)
self.__index = self
return o
end
function NodeStates:node_init(pos, mem, number)
mem.techage_state = STOPPED
2019-03-09 21:26:15 +03:00
M(pos):set_string("node_number", number)
2019-03-02 14:24:48 +03:00
if self.infotext_name then
M(pos):set_string("infotext", self.infotext_name.." "..number..": stopped")
2019-03-02 14:24:48 +03:00
end
2019-06-16 22:06:16 +03:00
mem.techage_item_meter = 0
2019-03-02 14:24:48 +03:00
if self.formspec_func then
M(pos):set_string("formspec", self.formspec_func(self, pos, mem))
2019-03-02 14:24:48 +03:00
end
end
-- to be used to re-start the timer outside of node_timer()
local function start_timer_delayed(pos, cycle_time)
local t = minetest.get_node_timer(pos)
t:stop()
if cycle_time > 0.9 then
minetest.after(0.1, t.start, t, cycle_time)
end
end
function NodeStates:stop(pos, mem)
2019-05-21 14:15:13 +03:00
local state = mem.techage_state or STOPPED
2019-06-16 22:06:16 +03:00
mem.techage_state = STOPPED
if self.stop_node then
self.stop_node(pos, mem, state)
2019-03-02 14:24:48 +03:00
end
2019-06-16 22:06:16 +03:00
if self.node_name_passive then
swap_node(pos, self.node_name_passive)
end
if self.infotext_name then
local number = M(pos):get_string("node_number")
M(pos):set_string("infotext", self.infotext_name.." "..number..": stopped")
end
if self.formspec_func then
mem.ta_state_tooltip = "stopped"
2019-06-16 22:06:16 +03:00
M(pos):set_string("formspec", self.formspec_func(self, pos, mem))
end
if self.on_state_change then
self.on_state_change(pos, state, STOPPED)
end
if minetest.get_node_timer(pos):is_started() then
minetest.get_node_timer(pos):stop()
end
return true
2019-03-02 14:24:48 +03:00
end
function NodeStates:start(pos, mem)
2019-05-21 14:15:13 +03:00
local state = mem.techage_state or STOPPED
if state ~= RUNNING and state ~= FAULT then
local res = self.can_start(pos, mem, state)
if res ~= true then
self:fault(pos, mem, res)
2019-03-02 14:24:48 +03:00
return false
end
if not self.has_power(pos, mem, state) then
self:nopower(pos, mem)
return false
end
2019-05-21 01:05:53 +03:00
mem.techage_state = RUNNING
if self.start_node then
self.start_node(pos, mem, state)
2019-03-02 14:24:48 +03:00
end
2019-05-02 00:01:14 +03:00
mem.techage_countdown = 1
2019-03-02 14:24:48 +03:00
if self.node_name_active then
2019-03-06 00:42:31 +03:00
swap_node(pos, self.node_name_active)
2019-03-02 14:24:48 +03:00
end
if self.infotext_name then
2019-03-09 21:26:15 +03:00
local number = M(pos):get_string("node_number")
M(pos):set_string("infotext", self.infotext_name.." "..number..": running")
2019-03-02 14:24:48 +03:00
end
if self.formspec_func then
mem.ta_state_tooltip = "running"
M(pos):set_string("formspec", self.formspec_func(self, pos, mem))
2019-03-02 14:24:48 +03:00
end
2019-03-10 13:36:00 +03:00
if minetest.get_node_timer(pos):is_started() then
minetest.get_node_timer(pos):stop()
2019-03-05 23:20:44 +03:00
end
2019-06-09 16:02:17 +03:00
if self.on_state_change then
self.on_state_change(pos, state, RUNNING)
end
start_timer_delayed(pos, self.cycle_time)
2019-03-02 14:24:48 +03:00
return true
end
return false
end
function NodeStates:standby(pos, mem)
2019-06-09 16:02:17 +03:00
local state = mem.techage_state or STOPPED
if state == RUNNING then
mem.techage_state = STANDBY
2019-03-02 14:24:48 +03:00
if self.node_name_passive then
2019-03-06 00:42:31 +03:00
swap_node(pos, self.node_name_passive)
2019-03-02 14:24:48 +03:00
end
if self.infotext_name then
2019-03-09 21:26:15 +03:00
local number = M(pos):get_string("node_number")
M(pos):set_string("infotext", self.infotext_name.." "..number..": standby")
2019-03-02 14:24:48 +03:00
end
if self.formspec_func then
mem.ta_state_tooltip = "standby"
M(pos):set_string("formspec", self.formspec_func(self, pos, mem))
2019-03-02 14:24:48 +03:00
end
2019-06-09 16:02:17 +03:00
if self.on_state_change then
self.on_state_change(pos, state, STANDBY)
end
start_timer_delayed(pos, self.cycle_time * self.standby_ticks)
2019-03-02 14:24:48 +03:00
return true
end
return false
end
-- special case of standby for pushing nodes
function NodeStates:blocked(pos, mem)
2019-06-09 16:02:17 +03:00
local state = mem.techage_state or STOPPED
if state == RUNNING then
mem.techage_state = BLOCKED
2019-03-02 14:24:48 +03:00
if self.node_name_passive then
2019-03-06 00:42:31 +03:00
swap_node(pos, self.node_name_passive)
2019-03-02 14:24:48 +03:00
end
if self.infotext_name then
2019-03-09 21:26:15 +03:00
local number = M(pos):get_string("node_number")
M(pos):set_string("infotext", self.infotext_name.." "..number..": blocked")
2019-03-02 14:24:48 +03:00
end
if self.formspec_func then
mem.ta_state_tooltip = "blocked"
M(pos):set_string("formspec", self.formspec_func(self, pos, mem))
2019-03-02 14:24:48 +03:00
end
2019-06-09 16:02:17 +03:00
if self.on_state_change then
self.on_state_change(pos, state, BLOCKED)
end
start_timer_delayed(pos, self.cycle_time * self.standby_ticks)
2019-03-02 14:24:48 +03:00
return true
end
return false
end
function NodeStates:nopower(pos, mem, err_string)
2019-06-09 16:02:17 +03:00
local state = mem.techage_state or RUNNING
if state ~= NOPOWER then
2019-05-21 14:15:13 +03:00
mem.techage_state = NOPOWER
if self.node_name_passive then
swap_node(pos, self.node_name_passive)
end
if self.infotext_name then
local number = M(pos):get_string("node_number")
M(pos):set_string("infotext", self.infotext_name.." "..number..": no power")
end
if self.formspec_func then
mem.ta_state_tooltip = err_string or "no power"
2019-05-21 14:15:13 +03:00
M(pos):set_string("formspec", self.formspec_func(self, pos, mem))
end
2019-06-09 16:02:17 +03:00
if self.on_state_change then
self.on_state_change(pos, state, NOPOWER)
end
start_timer_delayed(pos, self.cycle_time * self.standby_ticks)
2019-05-21 14:15:13 +03:00
return true
end
return false
end
function NodeStates:fault(pos, mem, err_string)
2019-06-09 16:02:17 +03:00
local state = mem.techage_state or STOPPED
err_string = err_string or "fault"
2019-06-09 16:02:17 +03:00
if state == RUNNING or state == STOPPED then
mem.techage_state = FAULT
2019-03-02 14:24:48 +03:00
if self.node_name_passive then
2019-03-06 00:42:31 +03:00
swap_node(pos, self.node_name_passive)
2019-03-02 14:24:48 +03:00
end
if self.infotext_name then
2019-03-09 21:26:15 +03:00
local number = M(pos):get_string("node_number")
M(pos):set_string("infotext", self.infotext_name.." "..number..": "..err_string)
2019-03-02 14:24:48 +03:00
end
if self.formspec_func then
mem.ta_state_tooltip = err_string or "fault"
M(pos):set_string("formspec", self.formspec_func(self, pos, mem))
2019-03-02 14:24:48 +03:00
end
2019-06-09 16:02:17 +03:00
if self.on_state_change then
self.on_state_change(pos, state, FAULT)
end
2019-03-02 14:24:48 +03:00
minetest.get_node_timer(pos):stop()
return true
end
return false
end
function NodeStates:get_state(mem)
2019-05-21 14:15:13 +03:00
return mem.techage_state or techage.STOPPED
2019-03-02 14:24:48 +03:00
end
-- keep the timer running?
function NodeStates:is_active(mem)
2019-05-21 14:15:13 +03:00
local state = mem.techage_state or STOPPED
return state < FAULT
2019-03-02 14:24:48 +03:00
end
2019-05-02 00:01:14 +03:00
function NodeStates:start_if_standby(pos)
local mem = tubelib2.get_mem(pos)
if mem.techage_state == STANDBY then
self:start(pos, mem)
end
end
2019-03-02 14:24:48 +03:00
-- To be called if node is idle.
-- If countdown reaches zero, the node is set to STANDBY.
function NodeStates:idle(pos, mem)
2019-05-21 14:15:13 +03:00
local countdown = (mem.techage_countdown or 0) - 1
mem.techage_countdown = countdown
2019-05-02 00:01:14 +03:00
if countdown <= 0 then
self:standby(pos, mem)
2019-03-02 14:24:48 +03:00
end
end
-- To be called after successful node action to raise the timer
-- and keep the node in state RUNNING
2019-06-16 22:06:16 +03:00
function NodeStates:keep_running(pos, mem, val, num_items)
2019-03-02 14:24:48 +03:00
-- set to RUNNING if not already done
if mem.techage_state ~= RUNNING then
self:start(pos, mem)
end
2019-06-16 22:06:16 +03:00
mem.techage_countdown = val or 4
mem.techage_item_meter = (mem.techage_item_meter or 0) + (num_items or 1)
2019-03-02 14:24:48 +03:00
end
-- Start/stop node based on button events.
-- if function returns false, no button was pressed
function NodeStates:state_button_event(pos, mem, fields)
2019-03-02 14:24:48 +03:00
if fields.state_button ~= nil then
2019-05-21 14:15:13 +03:00
local state = mem.techage_state or STOPPED
2019-03-02 14:24:48 +03:00
if state == STOPPED or state == STANDBY or state == BLOCKED then
if not self:start(pos, mem) and (state == STANDBY or state == BLOCKED) then
self:stop(pos, mem)
end
2019-05-21 14:15:13 +03:00
elseif state == RUNNING or state == FAULT or state == NOPOWER then
self:stop(pos, mem)
2019-03-02 14:24:48 +03:00
end
return true
end
return false
end
function NodeStates:get_state_button_image(mem)
2019-05-21 14:15:13 +03:00
local state = mem.techage_state or STOPPED
2019-03-02 14:24:48 +03:00
return techage.state_button(state)
end
function NodeStates:get_state_tooltip(mem)
local tp = mem.ta_state_tooltip or ""
return tp..";#0C3D32;#FFFFFF"
end
2019-03-02 14:24:48 +03:00
-- command interface
function NodeStates:on_receive_message(pos, topic, payload)
2019-05-12 15:45:32 +03:00
local mem = tubelib2.get_mem(pos)
2019-03-02 14:24:48 +03:00
if topic == "on" then
self:start(pos, tubelib2.get_mem(pos))
2019-03-02 14:24:48 +03:00
return true
elseif topic == "off" then
self:stop(pos, tubelib2.get_mem(pos))
2019-03-02 14:24:48 +03:00
return true
elseif topic == "state" then
local node = techage.get_node_lvm(pos)
2019-03-02 14:24:48 +03:00
if node.name == "ignore" then -- unloaded node?
2019-09-14 00:01:55 +03:00
return "unloaded"
2019-03-02 14:24:48 +03:00
end
2019-09-14 00:01:55 +03:00
return techage.get_state_string(tubelib2.get_mem(pos))
2019-06-16 22:06:16 +03:00
elseif topic == "counter" then
2019-05-21 14:15:13 +03:00
return mem.techage_item_meter or 1
2019-06-16 22:06:16 +03:00
elseif topic == "clear_counter" then
mem.techage_item_meter = 0
2019-03-02 14:24:48 +03:00
return true
elseif topic == "fuel" then
local inv = M(pos):get_inventory()
if inv:get_size("fuel") == 1 then
local stack = inv:get_stack("fuel", 1)
return stack:get_count()
end
return
else
return "unsupported"
2019-03-02 14:24:48 +03:00
end
end
2019-03-09 21:26:15 +03:00
-- repair corrupt node data
2019-03-02 14:24:48 +03:00
function NodeStates:on_node_load(pos, not_start_timer)
local mem = tubelib2.get_mem(pos)
2019-03-02 14:24:48 +03:00
2019-03-09 21:26:15 +03:00
-- Meta data corrupt?
local number = M(pos):get_string("node_number")
if number == "" then
2019-06-16 22:06:16 +03:00
minetest.log("warning", "[TA] Node at "..S(pos).." has no node_number")
local name = techage.get_node_lvm(pos).name
local number = techage.add_node(pos, name)
self:node_init(pos, mem, number)
2019-03-09 21:26:15 +03:00
return
2019-03-02 14:24:48 +03:00
end
2019-03-11 00:33:03 +03:00
-- wrong number and no dummy number?
if number ~= "-" then
local info = techage.get_node_info(number)
if not info or not info.pos or not vector.equals(pos, info.pos) then
2019-06-16 22:06:16 +03:00
if not info then
minetest.log("warning", "[TA] Node at "..S(pos).." has no info")
elseif not info.pos then
minetest.log("warning", "[TA] Node at "..S(pos).." has no info.pos")
elseif not vector.equals(pos, info.pos) then
minetest.log("warning", "[TA] Node at "..S(pos).." is pos ~= info.pos")
end
2019-03-11 00:33:03 +03:00
swap_node(pos, "techage:defect_dummy")
return
end
2019-03-10 15:53:53 +03:00
end
2019-03-02 14:24:48 +03:00
-- state corrupt?
2019-05-21 14:15:13 +03:00
local state = mem.techage_state or 0
2019-03-02 14:24:48 +03:00
if state == 0 then
if minetest.get_node_timer(pos):is_started() then
mem.techage_state = RUNNING
2019-03-02 14:24:48 +03:00
else
mem.techage_state = STOPPED
2019-03-02 14:24:48 +03:00
end
elseif state == RUNNING and not not_start_timer then
minetest.get_node_timer(pos):start(self.cycle_time)
elseif state == STANDBY then
minetest.get_node_timer(pos):start(self.cycle_time * self.standby_ticks)
elseif state == BLOCKED then
minetest.get_node_timer(pos):start(self.cycle_time * self.standby_ticks)
end
if self.formspec_func then
M(pos):set_string("formspec", self.formspec_func(self, pos, mem))
2019-03-02 14:24:48 +03:00
end
end
2019-03-09 21:26:15 +03:00
minetest.register_node("techage:defect_dummy", {
description = "Corrupted Node (to be replaced)",
tiles = {
"techage_filling_ta2.png^techage_frame_ta2.png",
"techage_filling_ta2.png^techage_frame_ta2.png",
2019-03-10 15:53:53 +03:00
"techage_filling_ta2.png^techage_frame_ta2.png^techage_appl_defect.png",
"techage_filling_ta2.png^techage_frame_ta2.png^techage_appl_defect.png",
"techage_filling_ta2.png^techage_frame_ta2.png^techage_appl_defect.png",
"techage_filling_ta2.png^techage_frame_ta2.png^techage_appl_defect.png",
2019-03-09 21:26:15 +03:00
},
drop = "",
groups = {cracky=2, crumbly=2, choppy=2, not_in_creative_inventory=1},
is_ground_content = false,
})