Adding the continuous burning property to a building?
Adding the continuous burning property to a building?
Hi, I'm trying to make a type of building for my mod that requires and consumes a certain "fuel" constantly to keep a bar from depleting, regardless of whether it is actually working on a recipe at that moment or not. I thought before that this would be pretty easy since the nuclear reactor is also 'always on' and will just continue to ask for fuel no matter if the temperature is at 1000 or not. But now I can't actually find out how this is done, let alone duplicate it. Is this possible at all, and if so how?
To clarify further:
- I'm aware this is usually avoided to make entities sleep and save UPS. The building I have planned will be strictly limited in number so I accept the tiny hit to UPS if they cannot sleep.
- The idea behind this building is it simulates a population that needs to be fed constantly. It starts 100% filled with food/fuel, and as long as the food/fuel bar is above 99%, it will operate, but if it drops below it stops crafting products and will gradually amass a huge food deficit until the food supply is sufficient again for a long time. I don't see an easier way of implementing this without the envisioned constant fuel consumption, but if anyone has a better idea to do it I'm all ears.
Update: I tried to use the reactor type instead of an assembler, and while that does burn the food continuously, I can't make it produce a recipe or anything but heat. Is there some way to make a reactor do recipes? Or do I have to make some contraption with hidden entities and link the temperature of the reactor to an assembler to switch said assembler on and off?
To clarify further:
- I'm aware this is usually avoided to make entities sleep and save UPS. The building I have planned will be strictly limited in number so I accept the tiny hit to UPS if they cannot sleep.
- The idea behind this building is it simulates a population that needs to be fed constantly. It starts 100% filled with food/fuel, and as long as the food/fuel bar is above 99%, it will operate, but if it drops below it stops crafting products and will gradually amass a huge food deficit until the food supply is sufficient again for a long time. I don't see an easier way of implementing this without the envisioned constant fuel consumption, but if anyone has a better idea to do it I'm all ears.
Update: I tried to use the reactor type instead of an assembler, and while that does burn the food continuously, I can't make it produce a recipe or anything but heat. Is there some way to make a reactor do recipes? Or do I have to make some contraption with hidden entities and link the temperature of the reactor to an assembler to switch said assembler on and off?
Re: Adding the continuous burning property to a building?
Okay update: I see now the usual invisible entity contraption is the only solution. So far I've got this after repairing some insanely broken code I found elsewhere on the forum:
Which successfully spawns an invisible reactor entity "housing-1-reactor" underneath every assembler "housing-1" and keeps track, and it deletes them successfully.
Having zero real experience with LUA this is stretching my abilities though. Could anyone give me pointers on how to periodically check the current heat value on the reactors, compare them to some number, and then disable their linked assembler if it drops below?
Code: Select all
-- call script.onevent, tell it we want to do something when an entity is built, and give another anonymous function that will contain the code to actually do stuff
script.on_event(defines.events.on_built_entity, function(event)
if event.created_entity.name == "housing-1" then -- if the entity that the player built was a house
-- insert into the global.housing1 table an entry with a 'key' of housing that is the housing-1 entity, and a key of housing-reactor that is a new housing-1-reactor entity at the same position
table.insert( global.housing1, { housing1=event.created_entity, housing1reactor=game.surfaces[1].create_entity{ name="housing-1-reactor", position=event.created_entity.position, force = game.forces.player}})
end
end)
script.on_event(defines.events.on_tick, function(event) -- will change it to check way less than once per second later on
if event.tick % 60 == 0 then
for index, thetable in ipairs(global.housing1) do -- loop through the housing1 table
-- if the player built housing no longer exists
if not thetable.housing1.valid then
-- safety check
if thetable.housing1reactor.valid then
thetable.housing1reactor.destroy() -- destroy reactor
table.remove(global.housing1, index) -- remove the current table (housing and reactor) from the glob.housing table.
end
end
end
end
end
)
Having zero real experience with LUA this is stretching my abilities though. Could anyone give me pointers on how to periodically check the current heat value on the reactors, compare them to some number, and then disable their linked assembler if it drops below?
Re: Adding the continuous burning property to a building?
Update 2:
I figured it out! It's probably a disgusting piece of code, it will probably kill the UPS, but it works! God, I'm so happy, I love life and from now on I love Lua as well.
I figured it out! It's probably a disgusting piece of code, it will probably kill the UPS, but it works! God, I'm so happy, I love life and from now on I love Lua as well.
Code: Select all
-- call script.onevent, tell it we want to do something when an entity is built, and give another anonymous function that will contain the code to actually do stuff
script.on_event(defines.events.on_built_entity, function(event)
if event.created_entity.name == "housing-1" then -- if the entity that the player built was a house
-- insert into the global.housing1 table an entry with a 'key' of housing that is the housing-1 entity, and a key of housing-reactor that is a new housing-1-reactor entity at the same position
table.insert( global.housing1, { housing1=event.created_entity, housing1reactor=game.surfaces[1].create_entity{ name="housing-1-reactor", position=event.created_entity.position, force = game.forces.player}})
end
end)
script.on_event(defines.events.on_tick, function(event) -- will change it to check way less than once per second later on
if event.tick % 60 == 0 then
for index, thetable in ipairs(global.housing1) do -- loop through the housing1 table
-- if the player built housing no longer exists
if not thetable.housing1.valid then
if thetable.housing1reactor.valid then
thetable.housing1reactor.destroy() -- destroy reactor
table.remove(global.housing1, index) -- remove the current table (housing and reactor) from the glob.housing table.
end
-- elseif reactor underneath house no longer exists, destroy the house above it
elseif not thetable.housing1reactor.valid then
if thetable.housing1.valid then
thetable.housing1.destroy() -- destroy reactor
table.remove(global.housing1, index) -- remove the current table (housing and reactor) from the glob.housing table.
end
-- now since both still exist, add elseif statement checking temperature is above 60%
elseif thetable.housing1reactor.temperature < 600 then
thetable.housing1reactor.damage(1, game.forces.enemy, "impact") -- I probably won't be keeping this in, but it's useful for visually tracking things
-- now reduce the heat in each reactor since there is no actual heat loss mechanic
thetable.housing1reactor.temperature = thetable.housing1reactor.temperature - 6 -- pay a little less when the housing is not operational, allowing an increase in temperature
thetable.housing1.crafting_progress = 0 -- you lose all crafting process
elseif thetable.housing1reactor.temperature > 600 then
thetable.housing1reactor.temperature = thetable.housing1reactor.temperature - 7 -- pay full maintenance when the housing is operational; it will still slowly increase
end
end
end
end
)
Re: Adding the continuous burning property to a building?
Some improvements:
There are more events you should listen to! They are raised with different arguments, so you must adjust the values.Code: Select all
script.on_event(defines.events.on_built_entity, function(event) if event.created_entity.name == "housing-1" then -- if the entity that the player built was a house -- insert into the global.housing1 table an entry with a 'key' of housing that is the housing-1 entity, and a key of housing-reactor that is a new housing-1-reactor entity at the same position table.insert( global.housing1, { housing1=event.created_entity, housing1reactor=game.surfaces[1].create_entity{ name="housing-1-reactor", position=event.created_entity.position, force = game.forces.player}}) end end)
About your function: You should make sure that housing and reactor are on the same surface (which is not guaranteed to be game.surfaces[1] as mods like Factorissimo or SE will create others).
Code: Select all
-- Look for invalid entities every CLEANUP_DELAY ticks
CLEANUP_DELAY = 60
function register_entities(event)
-- We use the same function for different events, so we must cover all different
-- key names to get the entity!
local entity = event.created_entity or -- on_built_entity/on_robot_built_entity
event.destination or -- on_entity_cloned
event.entity -- script_raised_*
local player = event.player_index and game.get_player(event.player_index)
local robot = event.robot
local force = (player and player.valid and player.force) or
(robot and robot.valid and robot.force) or
entity.force
local surface = entity.surface
if force and force.valid and surface and surface.valid then
-- If this is the first entity, activate on_tick and on_nth_tick!
if not next(global.housing1) then
local enable = true
toggle_tick_events(enable)
end
-- Create reactor
local reactor = surface.create_entity{
name = "housing-1-reactor",
position = entity.position,
force = force,
create_build_effect_smoke = false,
}
-- insert into the global.housing1 table an entry with a 'key' of housing that is the housing-1 entity, and a key of housing-reactor that is a new housing-1-reactor entity at the same position
table.insert( global.housing1, { housing1 = entity, housing1reactor = reactor })
end
end
Code: Select all
function cleanup_entities()
-- Removing elements from a table while looping over it with pairs() or ipairs() is
-- not reliable because it will mess with the iterator. Instead, check a copy of the
-- table and use the index to remove elements from the original.
local copy = table.deepcopy(global.housing1)
for index, thetable in ipairs(copy) do
if not (thetable.housing1 and thetable.housing1.valid) then
if (thetable.housing1reactor and thetable.housing1reactor.valid) then
thetable.housing1reactor.destroy() -- destroy reactor
table.remove(global.housing1, index) -- remove the current table (housing and reactor) from the glob.housing table.
end
-- elseif reactor underneath house no longer exists, destroy the house above it
elseif not (thetable.housing1reactor and thetable.housing1reactor.valid) then
if thetable.housing1.valid then
thetable.housing1.destroy() -- destroy reactor
table.remove(global.housing1, index) -- remove the current table (housing and reactor) from the glob.housing table.
end
end
end
-- If the last entity has been removed, cancel on_tick and on_nth_tick!
if not next(global.housing1) then
local enable = false
toggle_tick_events(enable)
end
end
function check_temperature(event)
for index, thetable in ipairs(global.housing1) do -- loop through the housing1 table
if thetable.housing1reactor.temperature < 600 then
-- I probably won't be keeping this in, but it's useful for visually tracking things
thetable.housing1reactor.damage(1, game.forces.enemy, "impact")
-- now reduce the heat in each reactor since there is no actual heat loss mechanic
thetable.housing1reactor.temperature = thetable.housing1reactor.temperature - 6 -- pay a little less when the housing is not operational, allowing an increase in temperature
thetable.housing1.crafting_progress = 0 -- you lose all crafting process
elseif thetable.housing1reactor.temperature > 600 then
thetable.housing1reactor.temperature = thetable.housing1reactor.temperature - 7 -- pay full maintenance when the housing is operational; it will still slowly increase
end
end
end
So far, there are just some functions that should be called in response to events. Now you need an event manager that will activate all necessary events:
Code: Select all
-- Enable/disable on_tick and on_nth_tick. Must be called from on_load and when the first entity has been built or the last entity has been removed.
function toggle_tick_events(enable)
if enable then
log("Activating on_tick!")
script.on_event(defines.events.on_tick, check_temperature)
log("Activating on_nth_tick!")
script.on_nth_tick(CLEANUP_DELAY, cleanup_entities)
else
log("TUrning off on_tick!")
script.on_event(defines.events.on_tick, nil)
log("TUrning off on_nth_tick!")
script.on_nth_tick(CLEANUP_DELAY, nil)
end
end
function init()
global.housing1 = global.housing1 or {}
end
script.on_init(init)
script.on_configuration_changed(function(event)
init()
cleanup_entities()
local enable = next(global.housing1) and true or false
toggle_tick_events(enable)
end)
script.on_load(function()
local enable = global.housing1 and next(global.housing1) and true or false
toggle_tick_events(enable)
end)
local build_events = {
"on_built_entity",
"on_robot_built_entity",
"on_entity_cloned",
"script_raised_built",
"script_raised_revive"
}
local event_filters = {
{filter = "name", name = "housing-1"},
}
for e, event in ipairs(build_events) do
log("Registering handler for "..event)
script.on_event(defines.events[event], register_entities, event_filters)
end
A good mod deserves a good changelog. Here's a tutorial (WIP) about Factorio's way too strict changelog syntax!
Re: Adding the continuous burning property to a building?
The logic for the clean-up function was wrong, better use this instead:
Code: Select all
function cleanup_entities()
-- Removing elements from a table while looping over it with pairs() or ipairs() is
-- not reliable because it will mess with the iterator. Instead, check a copy of the
-- table and use the index to remove elements from the original.
-- EDIT: Instead, move valid entities to temporary table and use that to replace
-- the original when done.
local tmp = {}
for index, thetable in ipairs(global.housing1) do
if not (thetable.housing1 and thetable.housing1.valid) then
log(index..": Invalid housing")
if (thetable.housing1reactor and thetable.housing1reactor.valid) then
thetable.housing1reactor.destroy() -- destroy reactor
--~ table.remove(global.housing1, index) -- remove the current table (housing and reactor) from the glob.housing table.
end
-- elseif reactor underneath house no longer exists, destroy the house above it
elseif not (thetable.housing1reactor and thetable.housing1reactor.valid) then
log(index..": Invalid reactor")
if thetable.housing1.valid then
thetable.housing1.destroy() -- destroy reactor
--~ table.remove(global.housing1, index) -- remove the current table (housing and reactor) from the glob.housing table.
end
else
log(index..": Keeping housing and reactor")
table.insert(tmp, thetable)
end
end
global.housing1 = table.deepcopy(tmp)
-- If the last entity has been removed, cancel on_tick and on_nth_tick!
if not next(global.housing1) then
local enable = false
toggle_tick_events(enable)
end
end
A good mod deserves a good changelog. Here's a tutorial (WIP) about Factorio's way too strict changelog syntax!
Re: Adding the continuous burning property to a building?
Thanks a lot! So bots placing entities I understand, but when does cloning happen for normal assembler type buildings? And script_raised is for interaction with other mods if I understand correctly?There are more events you should listen to! They are raised with different arguments, so you must adjust the values.
About your function: You should make sure that housing and reactor are on the same surface (which is not guaranteed to be game.surfaces[1] as mods like Factorissimo or SE will create others).
This will be part of an overhaul mod so it will never play nice with SE, and if something like Factorissimo ends up on the blacklist then that's not a big issue per se, but I will be sure to check the other surfaces as you suggest regardless.
Regarding your on_tick event, I'd only use it for checking the reactor temperature and clean up the table in on_nth_tick. That will save you from running the timer in Lua.
Nvm, just saw that on_nth_tick can be >60 just fine.
I'm not getting this to work yet but I will put some more time in it. Is all of this supposed to go into control.lua or do the functions go into the scripts folder and then you call them from control.lua?So far, there are just some functions that should be called in response to events. Now you need an event manager that will activate all necessary events:
Re: Adding the continuous burning property to a building?
Mods can use LuaEntity::clone, players can clone stuff in editor mode (enter '/editor' on the chat console):
Regarding your on_tick event, I'd only use it for checking the reactor temperature and clean up the table in on_nth_tick. That will save you from running the timer in Lua.
Generally speaking, whatever is done directly by the game runs faster that whatever mods can do: The game is programmed in C, while Lua code has to be interpreted and mods must get data from/send data to the game before it can be used. So when you run
Code: Select all
if (event.tick % n) == 0
The Lua vs. C stuff is also why I prefer using event.filters over things like "if event.entity.name == "housing-1". The latter may still be necessary if you want to run different code branches for different prototypes, but that's just for distinguishing between the different types of entities you care about, not for filtering out the types of entities you want to ignore.
Even better, you can pass on a list of values, so you can use several timers out of on_tick! In addition to performance reasons, keeping different things (those you want to run each tick vs. those you want to ignore most of the time) should make it easier to read the code.Nvm, just saw that on_nth_tick can be >60 just fine.
Code: Select all
script.on_nth_tick({60, 70, 80}, function(event)
local t = event.nth_tick
if t == 60 then
…
elseif t == 70 then
…
else
…
end
end)
You don't have too much code yet, so I'd say keep everything in control.lua. Once that is growing too long it may make sense to restructure things and put functions that belong together into different files:I'm not getting this to work yet but I will put some more time in it. Is all of this supposed to go into control.lua or do the functions go into the scripts folder and then you call them from control.lua?So far, there are just some functions that should be called in response to events. Now you need an event manager that will activate all necessary events:
Code: Select all
-- scripts.gui
local gui_stuff = {}
gui_stuff.create_gui = function(event)
…
end
gui_stuff.delete_gui = function(event)
…
end
return gui_stuff
-- scripts.entities
local entity = {}
entity.register = function(event)
…
end
entity.cleanup = function()
…
end
return entity
-- control.lua
my_gui = require("scripts.gui") -- Runs file "gui.lua" from folder "scripts" located at the toplevel of your mod and assigns the return value to "my_gui"
entities = require(scripts.entities")
script.on_event(defines.events.on_entity_built, entities.register)
A good mod deserves a good changelog. Here's a tutorial (WIP) about Factorio's way too strict changelog syntax!
Re: Adding the continuous burning property to a building?
Alright cheers. It's probably going to take me some time to integrate some of these tips but at least I added the bot/clone stuff and the nth_tick finally works and after testing it, it seems the current script uses very little CPU time even when there are 200 house entities on the map (and there are supposed to be 100-150 max regardless of base size) so it appears this won't be much of a problem.You don't have too much code yet, so I'd say keep everything in control.lua. Once that is growing too long it may make sense to restructure things and put functions that belong together into different files
Unfortunately it seems inserters lock on to a single building, so the combined house entity will require 2 inserters to feed it:
(Blender? What do I need a blender for?)
-
- Smart Inserter
- Posts: 2768
- Joined: Tue Apr 25, 2017 2:01 pm
- Contact:
Re: Adding the continuous burning property to a building?
Re: putting functions off into other files - You only really need to do this if the functions you are creating are also going to be called from yet other files (and even then, not really). Some people do like to put them off into other files anyway to keep the main one "clean", but personally, I find it easier to read the code when everything's together in one file. YMMV.
My Mods: Classic Factorio Basic Oil Processing | Sulfur Production from Oils | Wood to Oil Processing | Infinite Resources - Normal Yield | Tree Saplings (Redux) | Alien Biomes Tweaked | Restrictions on Artificial Tiles | New Gear Girl & HR Graphics
Re: Adding the continuous burning property to a building?
It depends. You may run get problems if local function A is calling local function B (so it must be defined before A) and B is calling local function C (so C must be defined before B), but C may also want to call A. That becomes a non-issue if A, B, and C are defined in one table: If you have functions my_gui.A, my_gui.B, and my_gui.C, it doesn't matter which one is defined first.FuryoftheStars wrote: ↑Tue Sep 26, 2023 11:35 pm Re: putting functions off into other files - You only really need to do this if the functions you are creating are also going to be called from yet other files
Right, if you make all your functions global, their order wouldn't matter either -- whether you keep those functions in one file or spread them over several files.(and even then, not really)
Autodrive used to have a monolithic control file that contained everything: creating/updating the GUI, handling of the cars, remote interface, and reacting to events. I collaborated and later inherited the mod, and because I really wasn't too familiar with the code, commented out old stuff for reference and rewrote/added to it, so the file was growing more and more. Let me tell you, it is not a pleasure to debug code if you have to scroll from the start to the end (and back) of a 3000+ lines file all the time! Restructuring the code by splitting it into several files was like going through hell, but IMHO it was absolutely worth it. Maintaining code and adding functions is so much easier now. But I agree, keeping everything in one file will probably make it easier to read the code if that file isn't too long.Some people do like to put them off into other files anyway to keep the main one "clean", but personally, I find it easier to read the code when everything's together in one file. YMMV.
A good mod deserves a good changelog. Here's a tutorial (WIP) about Factorio's way too strict changelog syntax!