How to replace entities on tiles in a migration
-
- Inserter
- Posts: 23
- Joined: Sun Mar 26, 2017 10:16 am
- Contact:
How to replace entities on tiles in a migration
Hi,
I need some help with updating my mod, I am replacing the hidden entity that is placed when a particular Tile is built with a different hidden entity.
I have come to the conclusion that this needs to be done via a migration script.
I cannot simply do a replace all for the entity as it is used for other Tiles.
I need to be able to check the currently built Tiles in the game, if it matches the Tile I'm looking for, then at those X, Y coordinates I can find and delete the matching entity and replace it with the new entity.
Is this possible to do?
Edit:
Mod Link
https://mods.factorio.com/mod/PoweredFloorExtended
I need some help with updating my mod, I am replacing the hidden entity that is placed when a particular Tile is built with a different hidden entity.
I have come to the conclusion that this needs to be done via a migration script.
I cannot simply do a replace all for the entity as it is used for other Tiles.
I need to be able to check the currently built Tiles in the game, if it matches the Tile I'm looking for, then at those X, Y coordinates I can find and delete the matching entity and replace it with the new entity.
Is this possible to do?
Edit:
Mod Link
https://mods.factorio.com/mod/PoweredFloorExtended
Last edited by AlmightyCrumpet on Mon May 22, 2023 4:48 pm, edited 1 time in total.
Re: How to replace entities on tiles in a migration
You can use script.on_configuration_changed to migrate in control.lua. You need to keep your old data.lua entity names so they aren't deleted.
-
- Inserter
- Posts: 23
- Joined: Sun Mar 26, 2017 10:16 am
- Contact:
Re: How to replace entities on tiles in a migration
I'm confused, why would I migrate in the control.lua?
I only want this to run once, when updating to this version, so surely utilizing the migration functionality is ideal?
I only want this to run once, when updating to this version, so surely utilizing the migration functionality is ideal?
Re: How to replace entities on tiles in a migration
When you say "hidden entity", do you mean hidden tile? (https://lua-api.factorio.com/latest/Lua ... idden_tile). If so, have you tried using a JSON migration for this? (https://lua-api.factorio.com/latest/Migrations.html). It seems to me that it should apply to hidden_tile as well, and if it doesn't perhaps raise it as a bug/feature-request?AlmightyCrumpet wrote: ↑Sun May 21, 2023 8:46 pmHi,
I need some help with updating my mod, I am replacing the hidden entity that is placed when a particular Tile is built with a different hidden entity.
I have come to the conclusion that this needs to be done via a migration script.
I cannot simply do a replace all for the entity as it is used for other Tiles.
I need to be able to check the currently built Tiles in the game, if it matches the Tile I'm looking for, then at those X, Y coordinates I can find and delete the matching entity and replace it with the new entity.
Is this possible to do?
My mods
Content: Lunar Landings | Freight Forwarding | Spidertron Patrols | Spidertron Enhancements | Power Overload
QoL: Factory Search | Module Inserter Simplified | Wire Shortcuts X | Ghost Warnings
Content: Lunar Landings | Freight Forwarding | Spidertron Patrols | Spidertron Enhancements | Power Overload
QoL: Factory Search | Module Inserter Simplified | Wire Shortcuts X | Ghost Warnings
-
- Inserter
- Posts: 23
- Joined: Sun Mar 26, 2017 10:16 am
- Contact:
Re: How to replace entities on tiles in a migration
No not a hidden tile, the mod adds tiles and to give them the functionality, when they are built, a [hidden] entity with said functionality is placed there too.Xorimuth wrote: ↑Mon May 22, 2023 4:30 pmWhen you say "hidden entity", do you mean hidden tile? (https://lua-api.factorio.com/latest/Lua ... idden_tile). If so, have you tried using a JSON migration for this? (https://lua-api.factorio.com/latest/Migrations.html). It seems to me that it should apply to hidden_tile as well, and if it doesn't perhaps raise it as a bug/feature-request?
And as this entity is used for more than just the 1 Tile, a JSON migration would end up replacing said entity for more than the intended Tiles causing functionality to change for some Tiles that I wanted to stay the same.
I'll add a link to the mod in the post as it might add some context.
Re: How to replace entities on tiles in a migration
You can use migrations.lua, you don't need control.lua.AlmightyCrumpet wrote: ↑Mon May 22, 2023 3:42 pmI'm confused, why would I migrate in the control.lua?
I only want this to run once, when updating to this version, so surely utilizing the migration functionality is ideal?
The reason to use lua is it offers more functionality than json.
-
- Inserter
- Posts: 23
- Joined: Sun Mar 26, 2017 10:16 am
- Contact:
Re: How to replace entities on tiles in a migration
Code: Select all
-- get surface tiles
local oldSurfaceTiles = find_tiles_filtered {name = "logistics-powered-floor-tile", force = game.forces.player}
local newSurfaceTiles = find_tiles_filtered {name = "logistics-floor-tile", force = game.forces.player}
-- loop through and for each tile that is a logistics floor tile
if oldSurfaceTiles ~= nil
then
for i, oldTile in ipairs(oldSurfaceTiles)
do
local position = oldtile.position
-- check the same x/y coordinates for the existing entity on the tile
local widget = find_entity("powered-floor-widget", oldTile.position)
-- if it is only a 'power-floor-widget' then remove it and add a 'circuit-floor-widget' in it's place
end
else
for i, oldTile in ipairs(newSurfaceTiles)
do
local position = oldtile.position
-- check the same x/y coordinates for the existing entity on the tile
widget = find_entity("powered-floor-widget", oldTile.position)
-- if it is only a 'power-floor-widget' then remove it and add a 'circuit-floor-widget' in it's place
end
end
I've included both the new and old names for the tile matching as I'm not sure if they have already been replaced by this point by the json migration that I also have in this update.
I'm sure I've a ton of mistakes there already, but am I at least on the right tracks?
Re: How to replace entities on tiles in a migration
Where should the tiles be found? You must specify a surface here:AlmightyCrumpet wrote: ↑Sun Jun 04, 2023 8:25 pmCode: Select all
-- get surface tiles local oldSurfaceTiles = find_tiles_filtered {name = "logistics-powered-floor-tile", force = game.forces.player} local newSurfaceTiles = find_tiles_filtered {name = "logistics-floor-tile", force = game.forces.player}
Code: Select all
-- get surface tiles
local oldSurfaceTiles = oldSurface.find_tiles_filtered {name = "logistics-powered-floor-tile", force = game.forces.player}
local newSurfaceTiles = newSurface.find_tiles_filtered {name = "logistics-floor-tile", force = game.forces.player}
LuaSurface.find_tiles_filtered() will always return a table (which may be empty), never nil. The condition will always be true, you'd better check whether the table has any elements:Code: Select all
-- loop through and for each tile that is a logistics floor tile if oldSurfaceTiles ~= nil
Code: Select all
-- loop through and for each tile that is a logistics floor tile
if next(oldSurfaceTiles)
Typo in variable name! Use "oldTile.position" instead of "oldtile.position"!Code: Select all
then for i, oldTile in ipairs(oldSurfaceTiles) do local position = oldtile.position
Again, specify where the entity should be found!Code: Select all
-- check the same x/y coordinates for the existing entity on the tile local widget = find_entity("powered-floor-widget", oldTile.position) -- if it is only a 'power-floor-widget' then remove it and add a 'circuit-floor-widget' in it's place end
Code: Select all
local widget = oldSurface.find_entity("powered-floor-widget", oldTile.position)
For better readability, I'd change the variable name from oldTile to newTile. You should make widget a local variable, and find_entity is missing the surface again.Code: Select all
else for i, oldTile in ipairs(newSurfaceTiles) do local position = oldtile.position -- check the same x/y coordinates for the existing entity on the tile widget = find_entity("powered-floor-widget", oldTile.position) -- if it is only a 'power-floor-widget' then remove it and add a 'circuit-floor-widget' in it's place end end
A good mod deserves a good changelog. Here's a tutorial (WIP) about Factorio's way too strict changelog syntax!
-
- Inserter
- Posts: 23
- Joined: Sun Mar 26, 2017 10:16 am
- Contact:
Re: How to replace entities on tiles in a migration
Thank you for your help.
I have amended to what I think is almost complete, I just need a little more help.
I'm not certain on how to delete the existing entity when/if I find it.
I have amended to what I think is almost complete, I just need a little more help.
I'm not certain on how to delete the existing entity when/if I find it.
Code: Select all
-- get surface tiles
local surface = game.surface
local oldSurfaceTiles = surface.find_tiles_filtered {name = "logistics-powered-floor-tile", force = game.forces.player}
local newSurfaceTiles = surface.find_tiles_filtered {name = "logistics-floor-tile", force = game.forces.player}
-- loop through and for each tile that is a logistics floor tile
if next(oldSurfaceTiles)
then
for i, tile in ipairs(oldSurfaceTiles)
do
local position = tile.position
-- check the same x/y coordinates for the existing entity on the tile
local widget = surface.find_entity("powered-floor-widget", position)
-- if it is only a 'power-floor-widget' then remove it and add a 'circuit-floor-widget' in it's place
if widget.name == "powered-floor-widget"
then
-- Delete widget
widget.Delete
-- Add new widget
local newEntity = surface.create_entity {name = "circuit-floor-widget", position = position, force = game.forces.player}
newEntity.destructible = false
end
end
else
for i, tile in ipairs(newSurfaceTiles)
do
local position = tile.position
-- check the same x/y coordinates for the existing entity on the tile
local widget = surface.find_entity("powered-floor-widget", position)
-- if it is only a 'power-floor-widget' then remove it and add a 'circuit-floor-widget' in it's place
if widget.name == "powered-floor-widget"
then
-- Delete widget
widget.Delete
-- Add new widget
local newEntity = surface.create_entity {name = "circuit-floor-widget", position = position, force = game.forces.player}
newEntity.destructible = false
end
end
end
Re: How to replace entities on tiles in a migration
You're welcome!
Check out LuaEntity.destroy()!I'm not certain on how to delete the existing entity when/if I find it.
This doesn't work, there only is game.surfaces, not game.surface. Usually, there will be only one surface ("nauvis"), but mods may create new surfaces. Space Exploration will do this for the different planets etc., Factorissimo will add a surface for each of its buildings (which may contain more of these buildings/surfaces), and some mods like miniMAXIme may even create hidden surfaces that are not meant to be used by players. Anyway, you should loop over all surfaces.Code: Select all
-- get surface tiles local surface = game.surface
I wonder about the entity force:Code: Select all
-- loop through and for each tile that is a logistics floor tile if next(oldSurfaceTiles) then for i, tile in ipairs(oldSurfaceTiles) do … end else for i, tile in ipairs(newSurfaceTiles) do … end end
Usually, there will be only three forces in the game: "player", "enemy", and "neutral". But scenarios or mods may add more forces with players on them. So I guess it would be better to create the new entity for the same force the old entity belonged to.Code: Select all
newEntity = surface.create_entity{ name = "circuit-floor-widget", position = position, force = game.forces.player }
Also, you run more or less the same code in the if-branch and the else-branch, so you can shorten the code by using a function removing the old and creating the new entity:
Code: Select all
local function swap_entities(surface, tiles)
local position, newEntity, widget, force
for t, tile in pairs(tiles) do
position = tile.position
-- check the same x/y coordinates for the existing entity on the tile
widget = surface.find_entity("powered-floor-widget", position)
-- if it is only a 'power-floor-widget' then remove it and add a 'circuit-floor-widget' in it's place
if widget then
-- Store force and delete widget
force = widget.force
widget.destroy{raise_destroy = true}
-- Add new widget
newEntity = surface.create_entity{
name = "circuit-floor-widget",
position = position,
force = force,
raise_built = true,
}
if newEntity then
newEntity.destructible = false
end
end
end
end
-- get surface tiles
local oldSurfaceTiles, newSurfaceTiles
for s, surface in pairs(game.surfaces) do
-- Always look for the old tiles
oldSurfaceTiles = surface.find_tiles_filtered{name = "logistics-powered-floor-tile"}
-- Will only exist if oldSurfaceTiles is an empty table
newSurfaceTiles = not next(oldSurfaceTiles) and
surface.find_tiles_filtered{name = "logistics-floor-tile"}
-- loop through and for each tile that is a logistics floor tile
if newSurfaceTiles and next(newSurfaceTiles) then
swap_entities(surface, newSurfaceTiles)
elseif next(oldSurfaceTiles) then
swap_entities(surface, oldSurfaceTiles)
end
end
A good mod deserves a good changelog. Here's a tutorial (WIP) about Factorio's way too strict changelog syntax!
-
- Inserter
- Posts: 23
- Joined: Sun Mar 26, 2017 10:16 am
- Contact:
Re: How to replace entities on tiles in a migration
Would this need to be addressed? When these entities are created, they are given the same force 'game.forces.player'.Usually, there will be only three forces in the game: "player", "enemy", and "neutral". But scenarios or mods may add more forces with players on them. So I guess it would be better to create the new entity for the same force the old entity belonged to.
This doesn't work, there only is game.surfaces, not game.surface. Usually, there will be only one surface ("nauvis"), but mods may create new surfaces. Space Exploration will do this for the different planets etc., Factorissimo will add a surface for each of its buildings (which may contain more of these buildings/surfaces), and some mods like miniMAXIme may even create hidden surfaces that are not meant to be used by players. Anyway, you should loop over all surfaces.
And in the event there is only the default 1 surface, will attempting to loop through the 'surface' variable cause a code error?
I didn't see the second half of the code you gave that goes through the surfaces. It basically explains everything I was asking here.
I do have one last thing to ask, the
Code: Select all
widget.destroy{raise_destroy = true}
Thank you so much for your help.
Re: How to replace entities on tiles in a migration
I guess it wouldn't hurt to address this. You never know what other mods may do with your entities …AlmightyCrumpet wrote: ↑Fri Jun 09, 2023 11:28 amWould this need to be addressed? When these entities are created, they are given the same force 'game.forces.player'.Usually, there will be only three forces in the game: "player", "enemy", and "neutral". But scenarios or mods may add more forces with players on them. So I guess it would be better to create the new entity for the same force the old entity belonged to.
Correct. Unlike LuaEntity.order_deconstruction, LuaEntity.destroy() will immediately destroy the entity, if it is valid. (You don't need to check for widget.valid here because other mods didn't have a chance to modify the entity between the time you've got it as a search result and the time you destroy it. You should check for widget.valid when you're using a cached reference to the entity, e.g. if it was stored in your global table.)I do have one last thing to ask, theThis will just straight up delete the entity and not mark it for deconstruction right?Code: Select all
widget.destroy{raise_destroy = true}
A good mod deserves a good changelog. Here's a tutorial (WIP) about Factorio's way too strict changelog syntax!
-
- Inserter
- Posts: 23
- Joined: Sun Mar 26, 2017 10:16 am
- Contact:
Re: How to replace entities on tiles in a migration
Currently trying to test this migration and getting this error in response.
I thought these checks would assure that the collection isn't empty?
I believe that I can fix it with a nil check just before the swap entities call?
Edit: Image preview doesn't seem to be working so adding link
https://ibb.co/121RNZg
Edit 2:
I realised it was because I ordered the function after the local setup for the surface.
Still learning the ins and outs of lua
I thought these checks would assure that the collection isn't empty?
Code: Select all
-- loop through and for each tile that is a logistics floor tile
if oldSurfaceTiles and next(oldSurfaceTiles) then
swap_entities(surface, oldSurfaceTiles)
elseif next(newSurfaceTiles) then
swap_entities(surface, newSurfaceTiles)
end
end
Edit: Image preview doesn't seem to be working so adding link
https://ibb.co/121RNZg
Edit 2:
I realised it was because I ordered the function after the local setup for the surface.
Still learning the ins and outs of lua
Re: How to replace entities on tiles in a migration
In the code I provided, swap_entities was defined as a local function, so it's strange that the error message claims "attempt to call global swap_entities (a nil value)". Did you include the definition of the function swap_entities at all? Are you sure there is no typo in the function name? Is the function defined before you try to call it?AlmightyCrumpet wrote: ↑Fri Jun 09, 2023 3:17 pmCurrently trying to test this migration and getting this error in response.
I believe that I can fix it with a nil check just before the swap entities call?
A good mod deserves a good changelog. Here's a tutorial (WIP) about Factorio's way too strict changelog syntax!
-
- Inserter
- Posts: 23
- Joined: Sun Mar 26, 2017 10:16 am
- Contact:
Re: How to replace entities on tiles in a migration
Yes, see edit 2 in my last post.
Out of coding habit I ordered the variable definition of the surfaces to before the function, it's not an issue in languages like c# or c++ as things get defined before the code runs but it is very clear the lua needs the function to be defined before being called.
The migration runs without complaint but it is not actually deleting the widgets or replacing them though. I get the feeling that it is not finding the widgets after looping through the found tiles.
Out of coding habit I ordered the variable definition of the surfaces to before the function, it's not an issue in languages like c# or c++ as things get defined before the code runs but it is very clear the lua needs the function to be defined before being called.
The migration runs without complaint but it is not actually deleting the widgets or replacing them though. I get the feeling that it is not finding the widgets after looping through the found tiles.
Re: How to replace entities on tiles in a migration
Guess I missed the edit. By the way, when Factorio throws an error, you can just click into the window and copy the error message -- no need for a screenshot.AlmightyCrumpet wrote: ↑Fri Jun 09, 2023 3:45 pmYes, see edit 2 in my last post.
Out of coding habit I ordered the variable definition of the surfaces to before the function, it's not an issue in languages like c# or c++ as things get defined before the code runs but it is very clear the lua needs the function to be defined before being called.
The old prototypes are still defined? What happens if you replace the blank pictures of the widgets, so that they become visible? Can you see any of the entities?The migration runs without complaint but it is not actually deleting the widgets or replacing them though. I get the feeling that it is not finding the widgets after looping through the found tiles.
A good mod deserves a good changelog. Here's a tutorial (WIP) about Factorio's way too strict changelog syntax!
-
- Inserter
- Posts: 23
- Joined: Sun Mar 26, 2017 10:16 am
- Contact:
Re: How to replace entities on tiles in a migration
I gave the entities a selection box param so I could mouse over them in-game and yes they are there still. If I remove the tiling and place it down again it will put down the new widget as expected but the existing ones have not been removed.
And the the particular entity I am trying to replace doesn't actually have a name change, so there should be no issue with it not finding the old/new name as it is the same.
And the the particular entity I am trying to replace doesn't actually have a name change, so there should be no issue with it not finding the old/new name as it is the same.
-
- Inserter
- Posts: 23
- Joined: Sun Mar 26, 2017 10:16 am
- Contact:
Re: How to replace entities on tiles in a migration
I removed the check on widget before destroying and creating the new one and it is erroring on a nil value for widget so clearly it is not finding the entities during the search
Error while applying migration: Powered Floor Extended: poweredfloorextended.0.0.8.lua
...loorExtended__/migrations/poweredfloorextended.0.0.8.lua:24: attempt to index local 'widget' (a nil value)
stack traceback:
...loorExtended__/migrations/poweredfloorextended.0.0.8.lua:24: in function 'swap_entities'
...loorExtended__/migrations/poweredfloorextended.0.0.8.lua:55: in main chunk
Re: How to replace entities on tiles in a migration
I have a hunch what may be wrong there: tile.position is not the same as entity.position. In tile positions, x and y are integers, while in entity positions x and y are floating point numbers.
Now, surface.find_entity(entity, position) will only find an entity if it is exactly at the given position -- meaning its center must be exactly at {x, y}. What you really want is intersecting the tile with the entity's collision box. You'll need a filtered search again:
Now, surface.find_entity(entity, position) will only find an entity if it is exactly at the given position -- meaning its center must be exactly at {x, y}. What you really want is intersecting the tile with the entity's collision box. You'll need a filtered search again:
Code: Select all
widget = surface.find_entities_filtered{name = "powered-floor-widget", position = position, radius = 1}
A good mod deserves a good changelog. Here's a tutorial (WIP) about Factorio's way too strict changelog syntax!
-
- Inserter
- Posts: 23
- Joined: Sun Mar 26, 2017 10:16 am
- Contact:
Re: How to replace entities on tiles in a migration
YES!
Got it working!
Thank you so much!
This is how it ended up after your last reply
Got it working!
Thank you so much!
This is how it ended up after your last reply
Code: Select all
local function swap_entities(surface, tiles)
local position, newEntity, widget, force
for t, tile in pairs(tiles) do
position = tile.position
-- check the same x/y coordinates for the existing entity on the tile
Widgets = surface.find_entities_filtered{name = "powered-floor-widget", position = position, radius = 1}
-- if it is only a 'power-floor-widget' then remove it and add a 'circuit-floor-widget' in it's place
if next(Widgets) then
for w, widget in pairs(Widgets) do
if widget then
-- Store force and delete widget
force = widget.force
widget.destroy{raise_destroy = true}
-- Add new widget
newEntity = surface.create_entity{
name = 'circuit-floor-widget',
position = {position.x, position.y},
force = force,
raise_built = true,
}
IncludeControlWiresToNeighbors(newEntity, surface)
if newEntity then
newEntity.destructible = false
end
end
end
end
end
end