How to replace entities on tiles in a migration

Place to get help with not working mods / modding interface.
AlmightyCrumpet
Inserter
Inserter
Posts: 23
Joined: Sun Mar 26, 2017 10:16 am
Contact:

How to replace entities on tiles in a migration

Post by AlmightyCrumpet »

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
Last edited by AlmightyCrumpet on Mon May 22, 2023 4:48 pm, edited 1 time in total.
User avatar
DaveMcW
Smart Inserter
Smart Inserter
Posts: 3724
Joined: Tue May 13, 2014 11:06 am
Contact:

Re: How to replace entities on tiles in a migration

Post by DaveMcW »

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.
AlmightyCrumpet
Inserter
Inserter
Posts: 23
Joined: Sun Mar 26, 2017 10:16 am
Contact:

Re: How to replace entities on tiles in a migration

Post by AlmightyCrumpet »

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?
Xorimuth
Filter Inserter
Filter Inserter
Posts: 708
Joined: Sat Mar 02, 2019 9:39 pm
Contact:

Re: How to replace entities on tiles in a migration

Post by Xorimuth »

AlmightyCrumpet wrote: Sun May 21, 2023 8:46 pm 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?
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?
My mods
Content: Lunar Landings | Freight Forwarding | Spidertron Patrols | Spidertron Enhancements | Power Overload
QoL: Factory Search | Module Inserter Simplified | Wire Shortcuts X | Ghost Warnings
AlmightyCrumpet
Inserter
Inserter
Posts: 23
Joined: Sun Mar 26, 2017 10:16 am
Contact:

Re: How to replace entities on tiles in a migration

Post by AlmightyCrumpet »

Xorimuth wrote: Mon May 22, 2023 4:30 pm 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?
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.
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.
User avatar
DaveMcW
Smart Inserter
Smart Inserter
Posts: 3724
Joined: Tue May 13, 2014 11:06 am
Contact:

Re: How to replace entities on tiles in a migration

Post by DaveMcW »

AlmightyCrumpet wrote: Mon May 22, 2023 3:42 pm 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?
You can use migrations.lua, you don't need control.lua.

The reason to use lua is it offers more functionality than json.
AlmightyCrumpet
Inserter
Inserter
Posts: 23
Joined: Sun Mar 26, 2017 10:16 am
Contact:

Re: How to replace entities on tiles in a migration

Post by AlmightyCrumpet »

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
This is about as far as I have gotten for a .lua migration script.
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?
Pi-C
Smart Inserter
Smart Inserter
Posts: 1742
Joined: Sun Oct 14, 2018 8:13 am
Contact:

Re: How to replace entities on tiles in a migration

Post by Pi-C »

AlmightyCrumpet wrote: Sun Jun 04, 2023 8:25 pm

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}
Where should the tiles be found? You must specify a surface here:

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}

Code: Select all

-- loop through and for each tile that is a logistics floor tile
if oldSurfaceTiles ~= nil
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 next(oldSurfaceTiles)

Code: Select all

then
	for i, oldTile in ipairs(oldSurfaceTiles)
	do
		local position = oldtile.position
Typo in variable name! Use "oldTile.position" instead of "oldtile.position"!

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
Again, specify where the entity should be found!

Code: Select all

		
		local widget = oldSurface.find_entity("powered-floor-widget", oldTile.position)

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
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.
A good mod deserves a good changelog. Here's a tutorial (WIP) about Factorio's way too strict changelog syntax!
AlmightyCrumpet
Inserter
Inserter
Posts: 23
Joined: Sun Mar 26, 2017 10:16 am
Contact:

Re: How to replace entities on tiles in a migration

Post by AlmightyCrumpet »

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.

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
Pi-C
Smart Inserter
Smart Inserter
Posts: 1742
Joined: Sun Oct 14, 2018 8:13 am
Contact:

Re: How to replace entities on tiles in a migration

Post by Pi-C »

AlmightyCrumpet wrote: Fri Jun 09, 2023 9:38 am Thank you for your help.
You're welcome!
I'm not certain on how to delete the existing entity when/if I find it.
Check out LuaEntity.destroy()!

Code: Select all

-- get surface tiles
local surface = game.surface
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

-- 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
I wonder about the entity force:

Code: Select all

      newEntity = surface.create_entity{
        name = "circuit-floor-widget",
        position = position,
        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.

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!
AlmightyCrumpet
Inserter
Inserter
Posts: 23
Joined: Sun Mar 26, 2017 10:16 am
Contact:

Re: How to replace entities on tiles in a migration

Post by AlmightyCrumpet »

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.
Would this need to be addressed? When these entities are created, they are given the same force 'game.forces.player'.
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.
So, if there are multiple surfaces. Will this return a collection/table when calling game.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}
This will just straight up delete the entity and not mark it for deconstruction right? These widget are made to not be deconstruct-able so that they can't accidentally be removed by marking an area with a deconstruction planner.

Thank you so much for your help.
Pi-C
Smart Inserter
Smart Inserter
Posts: 1742
Joined: Sun Oct 14, 2018 8:13 am
Contact:

Re: How to replace entities on tiles in a migration

Post by Pi-C »

AlmightyCrumpet wrote: Fri Jun 09, 2023 11:28 am
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.
Would this need to be addressed? When these entities are created, they are given the same force 'game.forces.player'.
I guess it wouldn't hurt to address this. You never know what other mods may do with your entities …
I do have one last thing to ask, the

Code: Select all

widget.destroy{raise_destroy = true}
This will just straight up delete the entity and not mark it for deconstruction right?
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.)
A good mod deserves a good changelog. Here's a tutorial (WIP) about Factorio's way too strict changelog syntax!
AlmightyCrumpet
Inserter
Inserter
Posts: 23
Joined: Sun Mar 26, 2017 10:16 am
Contact:

Re: How to replace entities on tiles in a migration

Post by AlmightyCrumpet »

Currently trying to test this migration and getting this error in response.
Image

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
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
Pi-C
Smart Inserter
Smart Inserter
Posts: 1742
Joined: Sun Oct 14, 2018 8:13 am
Contact:

Re: How to replace entities on tiles in a migration

Post by Pi-C »

AlmightyCrumpet wrote: Fri Jun 09, 2023 3:17 pm Currently 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?
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?
A good mod deserves a good changelog. Here's a tutorial (WIP) about Factorio's way too strict changelog syntax!
AlmightyCrumpet
Inserter
Inserter
Posts: 23
Joined: Sun Mar 26, 2017 10:16 am
Contact:

Re: How to replace entities on tiles in a migration

Post by AlmightyCrumpet »

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.
Pi-C
Smart Inserter
Smart Inserter
Posts: 1742
Joined: Sun Oct 14, 2018 8:13 am
Contact:

Re: How to replace entities on tiles in a migration

Post by Pi-C »

AlmightyCrumpet wrote: Fri Jun 09, 2023 3:45 pm 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.
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.
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.
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?
A good mod deserves a good changelog. Here's a tutorial (WIP) about Factorio's way too strict changelog syntax!
AlmightyCrumpet
Inserter
Inserter
Posts: 23
Joined: Sun Mar 26, 2017 10:16 am
Contact:

Re: How to replace entities on tiles in a migration

Post by AlmightyCrumpet »

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.
AlmightyCrumpet
Inserter
Inserter
Posts: 23
Joined: Sun Mar 26, 2017 10:16 am
Contact:

Re: How to replace entities on tiles in a migration

Post by AlmightyCrumpet »

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
Pi-C
Smart Inserter
Smart Inserter
Posts: 1742
Joined: Sun Oct 14, 2018 8:13 am
Contact:

Re: How to replace entities on tiles in a migration

Post by Pi-C »

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:

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!
AlmightyCrumpet
Inserter
Inserter
Posts: 23
Joined: Sun Mar 26, 2017 10:16 am
Contact:

Re: How to replace entities on tiles in a migration

Post by AlmightyCrumpet »

YES!
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
Post Reply

Return to “Modding help”