From 0555c550f1c04f643771cb676b8d74b24a444eef Mon Sep 17 00:00:00 2001 From: 1F616EMO Date: Wed, 17 Jun 2026 20:00:11 +0800 Subject: [PATCH] Add Creative Generators Creative Generators can be interacted through formspec, mesecons and digilines in the same way as supply converters. However, it does not take any power from the top, and can supply power up to 2147483647 EU, which is the upper 32-bit signed integer limit. --- technic/machines/creative_generator.lua | 192 ++++++++++++++++++ technic/machines/init.lua | 1 + technic/spec/creative_generator_spec.lua | 79 +++++++ .../technic_creative_generator_overlay.png | Bin 0 -> 112 bytes 4 files changed, 272 insertions(+) create mode 100644 technic/machines/creative_generator.lua create mode 100644 technic/spec/creative_generator_spec.lua create mode 100644 technic/textures/technic_creative_generator_overlay.png diff --git a/technic/machines/creative_generator.lua b/technic/machines/creative_generator.lua new file mode 100644 index 00000000..07d67704 --- /dev/null +++ b/technic/machines/creative_generator.lua @@ -0,0 +1,192 @@ +-- technic/technic/register/creative_generator.lua +-- Creative-only node that feeds power into the grid for free +-- Copyright (c) 2026 Technic Developers +-- SPDX-License-Identifier: LGPL-2.1-or-later + +local S = technic.getter +local digilines_exists = core.get_modpath("digilines") and true or false + +local function set_creative_generator_formspec(meta) + local formspec = "size[5,2.25]" .. + "field[0.3,0.5;2,1;power;" .. S("Output Power") .. ";${power}]" + if digilines_exists then + formspec = formspec .. + "field[2.3,0.5;3,1;channel;" .. S("Digiline Channel") .. ";${channel}]" + end + -- The names for these toggle buttons are explicit about which + -- state they'll switch to, so that multiple presses (arising + -- from the ambiguity between lag and a missed press) only make + -- the single change that the user expects. + if meta:get_int("mesecon_mode") == 0 then + formspec = formspec .. "button[0,1;5,1;mesecon_mode_1;" .. S("Ignoring Mesecon Signal") .. "]" + else + formspec = formspec .. "button[0,1;5,1;mesecon_mode_0;" .. S("Controlled by Mesecon Signal") .. "]" + end + if meta:get_int("enabled") == 0 then + formspec = formspec .. "button[0,1.75;5,1;enable;" .. S("@1 Disabled", S("Creative Generator")) .. "]" + else + formspec = formspec .. "button[0,1.75;5,1;disable;" .. S("@1 Enabled", S("Creative Generator")) .. "]" + end + meta:set_string("formspec", formspec) +end + +local def = { + description = S("Creative Generator"), + tiles = { + "technic_hv_generator_top.png", + "technic_machine_bottom.png^technic_cable_connection_overlay.png", + "technic_hv_generator_side.png^technic_creative_generator_overlay.png", + "technic_hv_generator_side.png^technic_creative_generator_overlay.png", + "technic_hv_generator_side.png^technic_creative_generator_overlay.png", + "technic_hv_generator_side.png^technic_creative_generator_overlay.png" + }, + groups = { + snappy = 2, + choppy = 2, + oddly_breakable_by_hand = 2, + technic_machine = 1, + technic_all_tiers = 1, + axey = 2, + handy = 1, + }, + connect_sides = { "bottom" }, + sounds = technic.sounds.node_sound_wood_defaults(), + drop = "", +} + +def.on_construct = function(pos) + local meta = core.get_meta(pos) + meta:set_string("infotext", S("Creative Generator")) + meta:set_int("power", 100000) + meta:set_int("enabled", 1) + meta:set_int("mesecon_mode", 0) + meta:set_int("mesecon_effect", 0) + set_creative_generator_formspec(meta) +end + +def.on_receive_fields = function(pos, formname, fields, sender) + if not sender or core.is_protected(pos, sender:get_player_name()) then + return + end + local meta = core.get_meta(pos) + local power = nil + if fields.power then + power = tonumber(fields.power) or 0 + power = math.max(power, 0) + power = math.min(power, 2147483647) + power = math.floor(power) + if power == meta:get_int("power") then power = nil end + end + if power then meta:set_int("power", power) end + if fields.channel then meta:set_string("channel", fields.channel) end + if fields.enable then meta:set_int("enabled", 1) end + if fields.disable then meta:set_int("enabled", 0) end + if fields.mesecon_mode_0 then meta:set_int("mesecon_mode", 0) end + if fields.mesecon_mode_1 then meta:set_int("mesecon_mode", 1) end + set_creative_generator_formspec(meta) +end + +if core.get_modpath("mesecons") then + def.mesecons = { + effector = { + action_on = function(pos, node) + core.get_meta(pos):set_int("mesecon_effect", 1) + end, + action_off = function(pos, node) + core.get_meta(pos):set_int("mesecon_effect", 0) + end + } + } +end + +if digilines_exists then + def.digilines = { + receptor = { + rules = technic.digilines.rules, + action = function() end + }, + effector = { + rules = technic.digilines.rules, + action = function(pos, node, channel, msg) + if type(msg) ~= "string" then + return + end + local meta = core.get_meta(pos) + if channel ~= meta:get_string("channel") then + return + end + msg = msg:lower() + if msg == "get" then + digilines.receptor_send(pos, technic.digilines.rules, channel, { + enabled = meta:get_int("enabled"), + power = meta:get_int("power"), + mesecon_mode = meta:get_int("mesecon_mode") + }) + return + elseif msg == "off" then + meta:set_int("enabled", 0) + elseif msg == "on" then + meta:set_int("enabled", 1) + elseif msg == "toggle" then + local onn = meta:get_int("enabled") + onn = 1 - onn -- Mirror onn with pivot 0.5, so switch between 1 and 0. + meta:set_int("enabled", onn) + elseif msg:sub(1, 5) == "power" then + local power = tonumber(msg:sub(7)) + if not power then + return + end + power = math.max(power, 0) + power = math.min(power, 2147483647) + power = math.floor(power) + meta:set_int("power", power) + elseif msg:sub(1, 12) == "mesecon_mode" then + meta:set_int("mesecon_mode", tonumber(msg:sub(14))) + else + return + end + set_creative_generator_formspec(meta) + end + }, + } +end + +def.technic_run = function(pos, node, run_stage) + -- run only in producer stage. + if run_stage == technic.receiver then + return + end + + -- Machine information + local machine_name = S("Creative Generator") + local meta = core.get_meta(pos) + local enabled = meta:get_int("enabled") == 1 and + (meta:get_int("mesecon_mode") == 0 or meta:get_int("mesecon_effect") ~= 0) + + local demand = enabled and meta:get_int("power") or 0 + + local pos_down = vector.offset(pos, 0, -1, 0) + local name_down = core.get_node(pos_down).name + + local to = technic.get_cable_tier(name_down) + + if to then + meta:set_int(to .. "_EU_demand", 0) + meta:set_int(to .. "_EU_supply", demand) + meta:set_string("infotext", S("@1 (@2 @3)", machine_name, + technic.EU_string(demand), to)) + else + meta:set_string("infotext", S("@1 Has Bad Cabling", machine_name)) + if to then + meta:set_int(to .. "_EU_supply", 0) + end + return + end +end +def.technic_on_disable = def.technic_run + +core.register_node("technic:creative_generator", def) + +for tier, machines in pairs(technic.machines) do + technic.register_machine(tier, "technic:creative_generator", technic.producer) +end diff --git a/technic/machines/init.lua b/technic/machines/init.lua index a26c510c..28e49731 100644 --- a/technic/machines/init.lua +++ b/technic/machines/init.lua @@ -41,6 +41,7 @@ dofile(path.."/switching_station_globalstep.lua") dofile(path.."/power_monitor.lua") dofile(path.."/supply_converter.lua") +dofile(path.."/creative_generator.lua") dofile(path.."/other/init.lua") diff --git a/technic/spec/creative_generator_spec.lua b/technic/spec/creative_generator_spec.lua new file mode 100644 index 00000000..acb456e0 --- /dev/null +++ b/technic/spec/creative_generator_spec.lua @@ -0,0 +1,79 @@ +require("mineunit") +--[[ + Technic network unit tests. + Execute mineunit at technic source directory. +--]] + +-- Load complete technic mod +fixture("technic") +sourcefile("init") + +describe("Creative Generator using", function() + + -- Execute on mods loaded callbacks to finish loading. + mineunit:mods_loaded() + -- Tell mods that 1 minute passed already to execute all weird core.after hacks. + mineunit:execute_globalstep(60) + world.set_default_node("air") + + -- Execute multiple globalsteps: run_network(times = 1, dtime = 1) + local run_network = spec_utility.run_globalsteps + + describe("network building", function() + + local player = Player("SX") + local gen_pos = {x=101,y=951,z=100} + local def = core.registered_items["technic:creative_generator"] + local on_receive_fields = def.on_receive_fields + + world.layout({ + {{x=100,y=950,z=100}, "technic:hv_cable"}, + {{x=101,y=950,z=100}, "technic:hv_cable"}, + {{x=100,y=951,z=100}, "technic:switching_station"}, + {gen_pos, "technic:creative_generator"}, + }) + + -- Build network + local id = technic.pos2network(gen_pos) + local net = technic.networks[id] + + it("generates energy", function() + -- Build the network so it includes the creative generator and + -- execute one globalstep to collect supply values from machines. + run_network(1) + assert.equals(100000, net.supply) + end) + + it("generates more energy on request", function() + local meta = core.get_meta(gen_pos) + meta:set_int("power", 20260617) + run_network(1) + assert.equals(20260617, net.supply) + end) + + it("pauses on formspec request", function() + on_receive_fields(gen_pos, "", { disable = true }, player) + run_network(1) + assert.equals(0, net.supply) -- no power sources + end) + + it("resumes on formspec request", function() + on_receive_fields(gen_pos, "", { enable = true }, player) + run_network(1) + assert.equals(20260617, net.supply) -- back to normal + end) + + it("maintains a minimum power", function() + on_receive_fields(gen_pos, "", { power = "-1" }, player) + run_network(1) + assert.equals(0, net.supply) -- math.max(power, 0) + end) + + it("maintains a maximum power", function() + on_receive_fields(gen_pos, "", { power = tostring(2147483647 + 10) }, player) + run_network(1) + assert.equals(2147483647, net.supply) -- math.min(power, 2147483647), s32 limit + end) + + end) +end) \ No newline at end of file diff --git a/technic/textures/technic_creative_generator_overlay.png b/technic/textures/technic_creative_generator_overlay.png new file mode 100644 index 0000000000000000000000000000000000000000..f226284b6c3e2fb327a087e68af23301071b55d4 GIT binary patch literal 112 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61SBU+%rFB|Y$ZW{!9W@a@|Lkr1X8M=E{-7; zjL8x=AGqY`$UGEsWcc%H!;8kxg)0@@nN$6g-nzopr E08ASm%K!iX literal 0 HcmV?d00001