technic-cd2025/technic/tools/multimeter.lua
SX 3df86dd24b Add power tool API, refactor existing tools
Cleanup deprecated metadata access for tools

Fix mining drill translations

Remove deprecated power tool functions

Drop charge value completely

Ensure use_RE_charge always uses at least 1 point

Use precalculated wear factor, remove negative set_wear hack...

Use technic_max_charge instead of max_charge in item definition, accept both

Log errors instead of failing with invalid power tool stack

Metadata migration for prospector

Cleanup tool definition max_charge, get_RE_charge log warning for unknown stacks
2022-06-09 22:29:48 +03:00

309 lines
12 KiB
Lua

local S = technic.getter
local remote_start_ttl = technic.config:get_int("multimeter_remote_start_ttl")
local max_charge = 50000
local power_usage = 100 -- Normal network reading uses this much energy
local rs_charge_multiplier = 100 -- Remote start energy requirement multiplier
local texture = "technic_multimeter.png"
local texture_logo = "technic_multimeter_logo.png"
local texture_bg9 = "technic_multimeter_bg.png"
local texture_button = "technic_multimeter_button.png"
local texture_button_pressed = "technic_multimeter_button_pressed.png"
local bgcolor = "#FFC00F"
local bgcolor_lcd = "#4B8E66"
local bghiglight_lcd = "#5CAA77"
local textcolor = "#101010"
--local bgcolor_button = "#626E41"
local form_width = 8
local form_height = 11.5
local btn_count = 3
local btn_spacing = 0.1
local btn_width = (form_width - ((btn_count + 1) * btn_spacing)) / btn_count
local open_formspecs = {}
local formspec_escape = minetest.formspec_escape
local function fmtf(n) return type(n) == "number" and ("%0.3f"):format(n) or n end
local function fs_x_pos(i) return (btn_spacing * i) + (btn_width * (i - 1)) end
local function create_button(index, y, h, name, label, exit, modifier)
local x = fs_x_pos(index)
local t1 = texture_button .. (modifier and formspec_escape(modifier) or "")
local t2 = texture_button_pressed .. (modifier and formspec_escape(modifier) or "")
local dimensions = ("%s,%s;%s,%s"):format(fmtf(x),fmtf(y),fmtf(btn_width),h)
local properties = ("%s;%s;%s;false;false;%s"):format(t1, name, label, t2)
return ("image_button%s[%s;%s]"):format(exit and "_exit" or "", dimensions, properties)
end
local formspec_format_string = "formspec_version[3]" ..
("size[%s,%s;]bgcolor[%s;both;]"):format(fmtf(form_width), fmtf(form_height), bgcolor) ..
("style_type[*;textcolor=%s;font_size=*1]"):format(textcolor) ..
("style_type[table;textcolor=%s;font_size=*1;font=mono]"):format(textcolor) ..
("style_type[label;textcolor=%s;font_size=*2]"):format(textcolor) ..
("background9[0,0;%s,%s;%s;false;3]"):format(fmtf(form_width), fmtf(form_height), texture_bg9) ..
("image[0.3,0.3;5.75,1;%s]"):format(texture_logo) ..
"label[0.6,1.5;Network %s]" ..
("field[%s,2.5;%s,0.8;net;Network ID:;%%s]"):format(fmtf(fs_x_pos(2)),fmtf(btn_width)) ..
create_button(3, "2.5", "0.8", "rs", "Remote start", false, "^[colorize:#10E010:125") ..
create_button(1, form_height - 0.9, "0.8", "wp", "Waypoint", true) ..
create_button(2, form_height - 0.9, "0.8", "up", "Update", false) ..
create_button(3, form_height - 0.9, "0.8", "exit", "Exit", true) ..
("tableoptions[border=false;background=%s;highlight=%s;color=%s]"):format(bgcolor_lcd,bghiglight_lcd,textcolor) ..
"tablecolumns[indent,width=0.2;text,width=13;text,width=13;text,align=center]" ..
("table[0.1,3.4;%s,%s;items;1,Property,Value,Unit%%s]"):format(fmtf(form_width - 0.2), fmtf(form_height - 4.4))
minetest.register_craft({
output = 'technic:multimeter',
recipe = {
{'basic_materials:copper_strip', 'technic:rubber', 'basic_materials:copper_strip'},
{'basic_materials:plastic_sheet', 'basic_materials:ic', 'basic_materials:plastic_sheet'},
{'technic:battery', 'basic_materials:ic', 'technic:copper_coil'},
}
})
local function use_charge(itemstack, multiplier)
return technic.use_RE_charge(itemstack, power_usage * (multiplier or 1))
end
local function async_itemstack_get(player, refstack)
local inv = player:get_inventory()
local invindex, invstack
if inv and refstack then
local invsize = inv:get_size('main')
local name = refstack:get_name()
local count = refstack:get_count()
local meta = refstack:get_meta()
for i=1,invsize do
local stack = inv:get_stack('main', i)
if stack:get_count() == count and stack:get_name() == name and stack:get_meta():equals(meta) then
-- This item stack seems very similar to one that were used originally, use this
invindex = i
invstack = stack
break
end
end
end
return inv, invindex, invstack
end
--[[ Base58
local alpha = {
"1","2","3","4","5","6","7","8","9","A","B","C","D","E","F","G","H",
"J","K","L","M","N","P","Q","R","S","T","U","V","W","X","Y","Z",
"a","b","c","d","e","f","g","h","i","j","k","m","n","o",
"p","q","r","s","t","u","v","w","x","y","z"
} --]]
-- Base36
local alpha = {
"0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F","G","H",
"I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"
}
local function base36(num)
if type(num) ~= "number" then return end
if num < 36 then return alpha[num + 1] end
local result = ""
while num ~= 0 do
result = alpha[(num % 36) + 1] .. result
num = math.floor(num / 36)
end
return result
end
-- Clean version of minetest.pos_to_string
local function v2s(v) return ("%s,%s,%s"):format(v.x,v.y,v.z) end
-- Size of hash table
local function count(t)
if type(t) ~= "table" then return 0 end
local c=0 for _ in pairs(t) do c=c+1 end return c
end
-- Percentage value as string
local function percent(val, max)
if type(val) ~= "number" or type(max) ~= "number" then return "" end
local p = (val / max) * 100
return p > 99.99 and "100" or ("%0.2f"):format(p)
end
-- Get network TTL
local function net_ttl(net) return type(net.timeout) == "number" and (net.timeout - minetest.get_us_time()) end
-- Microseconds to milliseconds
local function us2ms(val) return type(val) == "number" and (val / 1000) or 0 end
-- Microseconds to seconds
local function us2s(val) return type(val) == "number" and (val / 1000 / 1000) or 0 end
local function formspec(data)
local tablerows = ""
for _,row in ipairs(data.rows) do
tablerows = tablerows .. ",1" ..
"," .. formspec_escape(row[1] or "-") ..
"," .. formspec_escape(row[2] or "-") ..
"," .. formspec_escape(row[3] or "-")
end
local base36_net = base36(data.network_id) or "N/A"
return formspec_format_string:format(base36_net, base36_net, tablerows)
end
local function multimeter_inspect(itemstack, player, pos, fault)
local id = pos and technic.pos2network(pos)
local rows = {}
local data = { network_id = id, rows = rows }
local name = player:get_player_name()
if id and itemstack and not fault then
table.insert(rows, { "Ref. point", v2s(technic.network2pos(id)), "coord" })
table.insert(rows, { "Activated", technic.active_networks[id] and "yes" or "no", "active" })
local net = technic.networks[id]
if net then
table.insert(rows, { "Timeout", ("%0.1f"):format(us2s(net_ttl(net))), "s" })
table.insert(rows, { "Lag", ("%0.2f"):format(us2ms(net.lag)), "ms" })
table.insert(rows, { "Skip", net.skip, "cycles" })
table.insert(rows, {})
local PR = net.PR_nodes
local RE = net.RE_nodes
local BA = net.BA_nodes
local C = count(net.all_nodes)
table.insert(rows, { "Supply", net.supply, "EU" })
table.insert(rows, { "Demand", net.demand, "EU" })
table.insert(rows, { "Battery charge", net.battery_charge, "EU" })
table.insert(rows, { "Battery charge", percent(net.battery_charge, net.battery_charge_max), "%" })
table.insert(rows, { "Battery capacity", net.battery_charge_max, "EU" })
table.insert(rows, {})
table.insert(rows, { "Nodes", C, "count" })
table.insert(rows, { "Cables", C - #PR - #RE - #BA, "count" }) -- FIXME: Do not count PR+RE duplicates
table.insert(rows, { "Generators", #PR, "count" })
table.insert(rows, { "Consumers", #RE, "count" })
table.insert(rows, { "Batteries", #BA, "count" })
end
else
table.insert(rows, { "Operation failed", "", "" })
if not id then
table.insert(rows, {})
table.insert(rows, { "Bad contact", "No network", "Fault" })
end
if fault then table.insert(rows, {}) end
if fault == "battery" then
table.insert(rows, { "Recharge", "Insufficient charge", "Fault" })
elseif fault == "decode" then
table.insert(rows, { "Decoder error", "Net ID decode", "Fault" })
elseif fault == "switchload" then
table.insert(rows, { "Remote load error", "Load switching station", "Fault" })
elseif fault == "cableload" then
table.insert(rows, { "Remote load error", "Load ref. cable", "Fault" })
elseif fault == "protected" then
table.insert(rows, { "Protection error", "Area is protected", "Access" })
end
if not itemstack then
table.insert(rows, {})
table.insert(rows, { "Missing FLUTE", "FLUTE not found", "Fault" })
end
end
open_formspecs[name] = { pos = pos, itemstack = itemstack }
minetest.show_formspec(name, "technic:multimeter", formspec(data))
end
local function remote_start_net(player, pos)
local sw_pos = {x=pos.x,y=pos.y+1,z=pos.z}
-- Try to load switch network node with VoxelManip
local sw_node = technic.get_or_load_node(sw_pos) or minetest.get_node(sw_pos)
if sw_node.name ~= "technic:switching_station" then return "switchload" end
-- Try to load network node with VoxelManip
local tier = technic.sw_pos2tier(sw_pos, true)
if not tier then return "cableload" end
-- Check protections
if minetest.is_protected(pos, player:get_player_name()) then return "protected" end
-- All checks passed, start network
local network_id = technic.sw_pos2network(sw_pos) or technic.create_network(sw_pos)
technic.activate_network(network_id, remote_start_ttl)
end
local function async_itemstack_use_charge(itemstack, player, multiplier)
local fault = nil
local inv, invindex, invstack = async_itemstack_get(player, itemstack)
if not inv or not invindex or not use_charge(invstack, multiplier) then
-- Multimeter battery empty
fault = "battery"
elseif invstack then
inv:set_stack('main', invindex, invstack)
end
return invstack, fault
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= "technic:multimeter" then
-- Not our formspec, tell engine to continue with other registered handlers
return
end
local name = player:get_player_name()
local flute = open_formspecs[name]
if fields and name then
local pos = flute and flute.pos
if fields.up then
local itemstack = flute and flute.itemstack
local invstack, fault = async_itemstack_use_charge(itemstack, player)
multimeter_inspect(invstack, player, pos, fault)
return true
elseif fields.wp and pos then
local network_id = technic.pos2network(pos)
local encoded_net_id = base36(network_id)
if encoded_net_id then
local net_pos = technic.network2pos(network_id)
local id = player:hud_add({
hud_elem_type = "waypoint",
name = ("Network %s"):format(encoded_net_id),
text = "m",
number = 0xE0B020,
world_pos = net_pos
})
minetest.after(90, function() if player then player:hud_remove(id) end end)
end
elseif fields.rs and fields.net and fields.net ~= "" then
-- Use charge first before even attempting remote start
local itemstack = flute and flute.itemstack
local invstack, fault = async_itemstack_use_charge(itemstack, player, rs_charge_multiplier)
if not fault then
local net_id = tonumber(fields.net, 36)
local net_pos = net_id and technic.network2pos(net_id)
if net_pos then
fault = remote_start_net(player, net_pos)
else
fault = "decode"
end
end
multimeter_inspect(invstack, player, pos, fault)
return true
elseif fields.quit then
open_formspecs[name] = nil
end
end
-- Tell engine to skip rest of formspec handlers
return true
end)
local function check_node(pos)
local name = minetest.get_node(pos).name
if technic.machine_tiers[name] or technic.get_cable_tier(name) or name == "technic:switching_station" then
return name
end
end
technic.register_power_tool("technic:multimeter", {
description = S("Multimeter"),
inventory_image = texture,
wield_image = texture,
liquids_pointable = false,
max_charge = max_charge,
on_use = function(itemstack, player, pointed_thing)
local pos = minetest.get_pointed_thing_position(pointed_thing, false)
if pos and pointed_thing.type == "node" then
local name = check_node(pos)
if name then
if name == "technic:switching_station" then
-- Switching station compatibility shim
pos.y = pos.y - 1
end
open_formspecs[player:get_player_name()] = nil
multimeter_inspect(itemstack, player, pos, not use_charge(itemstack) and "battery")
end
end
return itemstack
end,
})