--[[ Reactor fitness check: 8x8x8 area surrounding the core must either contain.. Water source nodes Neutron Absorber (with active medium) Fluid Port (with COLD coolant available) Also acceptable nodes: Any fluid transfer conduit Any reactor component Unacceptable nodes (These raise heat INSTANTLY!): Lava source Hot coolant ..in order to keep the heat below critical. Any other detected node will either be MOLTEN or ACTIVATED (TODO) (you don't want this!) Reactor core will be replaced by a molten core when the heat reaches 100%. All components and fuel will be lost! ]] local AREA_SIZE = 8 local function calculate_fitness(pos) -- Calculate the heat sink percentage -- Amount of nodes we shall count down from local add = {x = (AREA_SIZE) / 2, y = (AREA_SIZE) / 2, z = (AREA_SIZE) / 2} local minp = vector.subtract(pos, add) local maxp = vector.add(pos, add) -- Get the vmanip mapgen object and the nodes and VoxelArea local manip = minetest.get_voxel_manip() local e1, e2 = manip:read_from_map(minp, maxp) local area = VoxelArea:new{MinEdge=e1, MaxEdge=e2} local data = manip:get_data() local ids = { c_water = minetest.get_content_id("default:water_source"), c_lava = minetest.get_content_id("default:lava_source"), } local excession = 0 local hu = 0 local nodes = 0 for i in area:iter( minp.x, minp.y, minp.z, maxp.x, maxp.y, maxp.z ) do nodes = nodes + 1 if data[i] == ids["c_water"] then hu = hu - 1 elseif data[i] == ids["c_lava"] then hu = hu + 1 else local dp = minetest.get_name_from_content_id(data[i]) if excession <= 16 and (ele.helpers.get_item_group(dp, "ele_reactor_component") or ele.helpers.get_item_group(dp, "ele_neutron_absorbant") or ele.helpers.get_item_group(dp, "fluid_transport_source") or ele.helpers.get_item_group(dp, "fluid_transport") or ele.helpers.get_item_group(dp, "tube") or ele.helpers.get_item_group(dp, "tubedevice")) then hu = hu - 1 excession = excession + 1 elseif ele.helpers.get_item_group(dp, "hot") then hu = hu + 1 else hu = hu + 1 end end end hu = nodes + hu return 100 - math.floor(100 * hu / nodes), hu end local function fuel_after_depletion(inv, power) local fuel_count = 0 local change = false local list = inv:get_list("fuel") for i,stack in pairs(list) do local sname = stack:get_name() if ele.helpers.get_item_group(sname, "fissile_fuel") then local stdef = minetest.registered_items[sname] if stdef.fissile_count then local meta = stack:get_meta() local fscount = meta:get_int("fission_count") if fscount < stdef.fissile_count then fscount = fscount + 1 fuel_count = fuel_count + 1 meta:set_int("fission_count", fscount) meta:set_string("description", ("%s\nDepleted: %d "):format(stdef.description, math.floor(100 * fscount / stdef.fissile_count)).." %") else stack = ItemStack("elepower_nuclear:fuel_rod_depleted") end list[i] = stack change = true end end end if change then inv:set_list("fuel", list) end return fuel_count end local function can_dig(pos, player) local meta = minetest.get_meta(pos) local inv = meta:get_inventory() return inv:is_empty("fuel") end local function allow_metadata_inventory_put(pos, listname, index, stack, player) if minetest.is_protected(pos, player:get_player_name()) then return 0 end if not ele.helpers.get_item_group(stack:get_name(), "fissile_fuel") then return 0 end return stack:get_count() end local function allow_metadata_inventory_move(pos, from_list, from_index, to_list, to_index, count, player) local meta = minetest.get_meta(pos) local inv = meta:get_inventory() local stack = inv:get_stack(from_list, from_index) return allow_metadata_inventory_put(pos, to_list, to_index, stack, player) end local function get_core_formspec(heat, power) local status = "Activate by extracting the control rods" if heat > 80 then status = "!!! TEMPERATURE CRITICAL !!!" elseif heat > 90 then status = "!!! REACTOR CRITICAL !!!" elseif heat > 95 then status = "!!! REACTOR MELTDOWN IMMINENT !!!" elseif power > 0 then status = "Active reaction chain" end return "size[8,8.5]".. default.gui_bg.. default.gui_bg_img.. default.gui_slots.. "list[context;fuel;2.5,0;3,3;]".. "list[current_player;main;0,4.25;8,1;]".. "list[current_player;main;0,5.5;8,3;8]".. ele.formspec.create_bar(0, 0, power, "#ff0000", true).. ele.formspec.create_bar(0.5, 0, heat, "#ffdd11", true).. "tooltip[0,0;0.25,2.5;Power: "..power.."%]".. "tooltip[0.5,0;0.25,2.5;Heat: "..heat.."%]".. "label[0,3.75;".. status .."]".. "listring[current_player;main]".. "listring[context;fuel]".. "listring[current_player;main]".. default.get_hotbar_bg(0, 4.25) end local function get_controller_formspec(rod_pos, selected) -- TODO: Reactor-dependent rod count local rods = 4 local ctrls = {} for num, depth in pairs(rod_pos) do local xoffset = (num / rods) * 8 local sel = "" if num == selected then sel = " <- " end local fspc = ("label[%d,0;%s]"):format(xoffset - 0.25, depth .. " %" .. sel) fspc = fspc .. ele.formspec.create_bar(xoffset - 1, 0.5, 100 - depth, "#252625", true) table.insert(ctrls, fspc) end return "size[8,8.5]".. default.gui_bg.. default.gui_bg_img.. default.gui_slots.. table.concat( ctrls, "" ).. "button[0,3.5;1.5,0.5;next;Next]".. "button[1.5,3.5;1.5,0.5;prev;Previous]".. "button[3.25,3.5;1.5,0.5;stop;SCRAM]".. "button[5,3.5;1.5,0.5;up;Raise]".. "button[6.5,3.5;1.5,0.5;down;Lower]".. "tooltip[next;Select the next control rod]".. "tooltip[prev;Select the previous control rod]".. "tooltip[stop;Drops all the rods into the reactor core, instantly stopping it]".. "tooltip[up;Raise selected control rod]".. "tooltip[down;Lower selected control rod]".. "list[current_player;main;0,4.25;8,1;]".. "list[current_player;main;0,5.5;8,3;8]".. "listring[current_player;main]".. default.get_hotbar_bg(0, 4.25) end local function get_port_formspec(cool, hot) return "size[8,8.5]".. default.gui_bg.. default.gui_bg_img.. default.gui_slots.. ele.formspec.fluid_bar(0, 0, cool).. ele.formspec.fluid_bar(7, 0, hot).. "list[current_player;main;0,4.25;8,1;]".. "list[current_player;main;0,5.5;8,3;8]".. "listring[current_player;main]".. default.get_hotbar_bg(0, 4.25) end local function reactor_core_timer(pos) local meta = minetest.get_meta(pos) local inv = meta:get_inventory() local headless = false local fuel_reactor = 0 -- SAFEGUARD: Expect a controller to be above the core local controller_pos = {x = pos.x, y = pos.y + 1, z = pos.z} local controller_node = minetest.get_node_or_nil(controller_pos) if not controller_node or controller_node.name ~= "elepower_nuclear:fission_controller" then -- Don't do anything without a head headless = true end local controller = minetest.get_meta(controller_pos) -- Only read area ever so often -- Calculate the absorbance of heat around the core local hp = meta:get_int("absorbance") local absorb_tick = meta:get_int("absorb_tick") if absorb_tick > 10 then hp = calculate_fitness(pos) absorb_tick = 0 else absorb_tick = absorb_tick + 1 end meta:set_int("absorb_tick", absorb_tick) meta:set_int("absorbance", hp) -- Get reactor power setting local power_setting_target = controller:get_int("setting") local power_setting = meta:get_int("setting") -- Do nothing if headless then power_setting = 0 else if not (power_setting_target == 0 and power_setting == 0) then -- Decrease or increase power if power_setting_target > power_setting then power_setting = power_setting + 1 elseif power_setting_target < power_setting then power_setting = power_setting - 5 end if power_setting < 0 then power_setting = 0 elseif power_setting > 100 then power_setting = 100 end end end -- Deplete fuel if power_setting > 0 then fuel_reactor = fuel_after_depletion(inv, power_setting) if fuel_reactor == 0 then -- Enforce zero power setting when no fuel present power_setting = 0 end end -- Set power setting meta:set_int("setting", power_setting) -- Get reactor heat local heat = meta:get_int("heat") -- Calculate heat -- I dont really know what I'm doing here, just playing around with the numbers -- to get something workable if power_setting > 5 then local ceiling = math.floor(power_setting / 2) if heat > ceiling and hp > 75 then -- Remove heat when the heat goes above power setting and there's sufficient coolant heat = heat + math.floor(((74 - hp)/2)/ceiling) else -- Heat up the reactor by the amount of fuel -- If the reactor coolant is insufficient, add that factor to play heat = heat + fuel_reactor + math.floor(80 * (1-(hp/100))) -- Catch up to the power setting if heat < ceiling then heat = heat + math.floor((ceiling - heat) / 2) end end elseif heat > 0 then heat = heat + math.floor((-hp)/4) end if heat >= 100 then minetest.set_node(pos, {name = "elepower_nuclear:corium_source"}) return false end if heat < 0 then heat = 0 end -- Nothing left to do in this timer, exit if power_setting == 0 and heat == 0 then meta:set_int("heat", heat) meta:set_string("formspec", get_core_formspec(heat, power_setting)) return false end -- Expect a fluid port below the core -- TODO: Allow multiple fluid ports in the core's affected area local fluid_port_pos = {x = pos.x, y = pos.y - 1, z = pos.z} local fluid_port_node = minetest.get_node_or_nil(fluid_port_pos) if fluid_port_node ~= nil and fluid_port_node.name == "elepower_nuclear:reactor_fluid_port" then local fpmeta = minetest.get_meta(fluid_port_pos) -- Calculate how much heat is given to the fluid port local burst_strength = math.max(math.floor((heat / 100) * 64), 1) if fpmeta:get_int("burst") == 0 and heat > 0 then fpmeta:set_int("burst", burst_strength) minetest.get_node_timer(fluid_port_pos):start(1.0) heat = heat - burst_strength end end meta:set_int("heat", heat) meta:set_string("formspec", get_core_formspec(heat, power_setting)) return true end local function reactor_controller_timer(pos) local meta = minetest.get_meta(pos) local settings = {} local averg = 0 for i = 1, 4 do table.insert(settings, meta:get_int("c" .. i)) averg = averg + settings[i] end meta:set_int("setting", 100 - (averg / 4)) meta:set_string("formspec", get_controller_formspec(settings, meta:get_int("selected"))) -- Ping the core local core_pos = {x = pos.x, y = pos.y - 1, z = pos.z} local core_node = minetest.get_node_or_nil(core_pos) if core_node and core_node.name == "elepower_nuclear:fission_core" then local timer = minetest.get_node_timer(core_pos) if not timer:is_started() then timer:start(1.0) end end return false end local function reactor_controller_manage(pos, formname, fields, sender) if sender and sender ~= "" and minetest.is_protected(pos, sender:get_player_name()) then return end local meta = minetest.get_meta(pos) local selected = meta:get_int("selected") local change = false if fields["next"] then selected = selected + 1 if selected > 4 then selected = 1 end meta:set_int("selected", selected) change = true elseif fields["prev"] then selected = selected - 1 if selected == 0 then selected = 4 end meta:set_int("selected", selected) change = true elseif fields["stop"] then for i = 1, 4 do meta:set_int("c" .. i, 100) end change = true elseif fields["up"] then local sl = meta:get_int("c"..selected) sl = sl - 10 if sl <= 0 then sl = 0 end meta:set_int("c"..selected, sl) change = true elseif fields["down"] then local sl = meta:get_int("c"..selected) sl = sl + 10 if sl >= 100 then sl = 100 end meta:set_int("c"..selected, sl) change = true end if change then minetest.get_node_timer(pos):start(0.2) end end local function reactor_port_timer(pos) local refresh = false local meta = minetest.get_meta(pos) local cool = fluid_lib.get_buffer_data(pos, "cool") local hot = fluid_lib.get_buffer_data(pos, "hot") local heat_burst = meta:get_int("burst") if heat_burst > 0 then -- Convert a bucket of cold coolant into hot coolant local heat_take = math.floor(cool.capacity * (heat_burst/100)) local coolant = heat_take if coolant > cool.amount then coolant = cool.amount end if hot.amount + coolant > hot.capacity and hot.amount < hot.capacity then coolant = hot.capacity - hot.amount end if coolant > 0 and hot.amount + coolant < hot.capacity then meta:set_int("burst", 0) cool.amount = cool.amount - coolant hot.amount = hot.amount + coolant refresh = true meta:set_string("cool_fluid", "elepower_nuclear:coolant_source") meta:set_string("hot_fluid", "elepower_nuclear:hot_coolant_source") meta:set_int("cool_fluid_storage", cool.amount) meta:set_int("hot_fluid_storage", hot.amount) end end meta:set_string("formspec", get_port_formspec(cool, hot)) return refresh end -- Reactor Core ele.register_base_device("elepower_nuclear:fission_core", { description = "Fission Reactor Core", groups = { cracky = 3, ele_reactor_core = 1, ele_reactor_component = 1, }, tiles = { "elenuclear_fission_core_top.png", "elepower_lead_block.png", "elenuclear_fission_core_side.png", "elenuclear_fission_core_side.png", "elenuclear_fission_core_side.png", "elenuclear_fission_core_side.png", }, on_timer = reactor_core_timer, on_construct = function (pos) local meta = minetest.get_meta(pos) local inv = meta:get_inventory() inv:set_size("fuel", 9) meta:set_int("absorb_tick", 11) meta:set_string("formspec", get_core_formspec(0,0,false)) end, can_dig = can_dig, allow_metadata_inventory_put = allow_metadata_inventory_put, allow_metadata_inventory_move = allow_metadata_inventory_move, allow_metadata_inventory_take = ele.default.allow_metadata_inventory_take, on_metadata_inventory_move = ele.default.metadata_inventory_changed, on_metadata_inventory_put = ele.default.metadata_inventory_changed, on_metadata_inventory_take = ele.default.metadata_inventory_changed, }) -- Reactor Control ele.register_base_device("elepower_nuclear:fission_controller", { description = "Fission Control Module\nPlace me on top of a Fission Reactor Core", groups = { cracky = 3, ele_reactor_component = 1, }, tiles = { "elenuclear_fission_core_top.png", "elepower_lead_block.png", "elenuclear_fission_controller_side.png", "elenuclear_fission_controller_side.png", "elenuclear_fission_controller_side.png", "elenuclear_fission_controller_side.png", }, on_timer = reactor_controller_timer, on_receive_fields = reactor_controller_manage, on_construct = function (pos) local meta = minetest.get_meta(pos) local inv = meta:get_inventory() meta:set_int("c1", 100) meta:set_int("c2", 100) meta:set_int("c3", 100) meta:set_int("c4", 100) meta:set_int("setting", 0) meta:set_int("selected", 1) meta:set_string("formspec", get_controller_formspec({100, 100, 100, 100}, 1)) end }) -- Reactor Fluid Port ele.register_base_device("elepower_nuclear:reactor_fluid_port", { description = "Reactor Fluid Port\nPlace me on the bottom of a Fission Reactor Core", groups = { cracky = 3, ele_reactor_component = 1, ele_port = 1, fluid_container = 1, }, tiles = { "elenuclear_machine_top.png", "elepower_lead_block.png", "elenuclear_machine_side.png^elepower_power_port.png", "elenuclear_machine_side.png^elepower_power_port.png", "elenuclear_machine_side.png^elepower_power_port.png", "elenuclear_machine_side.png^elepower_power_port.png", }, on_timer = reactor_port_timer, on_construct = function (pos) local meta = minetest.get_meta(pos) meta:set_string("cool_fluid", "elepower_nuclear:coolant_source") meta:set_string("hot_fluid", "elepower_nuclear:hot_coolant_source") meta:set_string("formspec", get_port_formspec()) end, fluid_buffers = { cool = { capacity = 16000, accepts = {"default:water_source", "elepower_nuclear:coolant_source"}, drainable = false, }, hot = { capacity = 16000, accepts = {"elepower_nuclear:hot_coolant_source"}, drainable = true, } }, }) -- Load reactor cores minetest.register_lbm({ label = "Refresh Reactors on load", name = "elepower_nuclear:fission_core", nodenames = {"elepower_nuclear:fission_core"}, run_at_every_load = true, action = ele.helpers.start_timer, })