elepower/elepower_nuclear/machines/fission_reactor.lua
2019-01-27 18:41:49 +02:00

580 lines
16 KiB
Lua

--[[
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)..
"label[0,3;Power: \t"..power.."%]"..
"label[0,3.25;Heat: \t"..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 = function (pos)
minetest.get_node_timer(pos):start(1.0)
end,
})