Page 1 of 1

Extra entity data in blueprint tags not overridden on ghost replacement

Posted: Mon Jun 12, 2023 10:51 am
by zysnarch
My mod adds a custom entity with its extra data stored in blueprint tags (saved via on_player_setup_blueprint, restored in on_built_entity). This works great except for one case: if a player places a blueprint ghost, then places a different ghost on top of that ghost, the tags given when the ghost is "made real" are the tags from the first ghost. Further, on_built_entity is called for the first ghost placement (and for the subsequent event when the entity itself is built from the ghost) - but *not* when the player places a different ghost on top of the first one. I can't find a way to even be notified that a "ghost replacement" has occurred.

In case that was confusing, here's the specific use case I have:
1. Player copies my custom entity - a chest with an "iron-plate" filter which is then stored in blueprint tags.
2. Player pastes blueprint ghost somewhere.
3. Player copies another entity, say chest with "copper-plate" filter.
4. Player pastes blueprint2 on top of the first.
5. Player builds entity.

What I want to happen in step 5 is the entity is built with the "copper-plate" filter. Instead, it's built with the "iron-plate" filter because the tags are from the first blueprint.

Re: Extra entity data in blueprint tags not overridden on ghost replacement

Posted: Mon Jun 12, 2023 10:45 pm
by zysnarch
I have found a solution to my problem. It would be nice if there were a simpler way (hoping I'm missing something), or if the engine simply replaced the ghost tags automatically, but at least there I have a solution. The method I've found is to use the on_pre_build event to know when a blueprint is about to be placed on top of existing ghosts. Then a bunch of math is necessary to find the actual in-game positions of the entities I want to override, and destroy those entities before the game places the new ones. Here's what the code looks like (with liberal use of Factorio-Stdlib mod):

Code: Select all

function rotateAndFlip(pos, dir, flipH, flipV)
  if flipH then pos.x = -pos.x end
  if flipV then pos.y = -pos.y end
  if dir == defines.direction.north then return pos end
  if dir == defines.direction.west then return Position.construct(pos.y, -pos.x) end
  if dir == defines.direction.south then return Position.construct(-pos.x, -pos.y) end
  if dir == defines.direction.east then return Position.construct(-pos.y, pos.x) end
  game.print("Warning: unexpected blueprint rotation " .. dir ". Chest filters will be incorrect.")
  return pos
end

script.on_event(defines.events.on_pre_build, function(event)
  local player = game.players[event.player_index]
  if not player.is_cursor_blueprint() or global.lastPreBuildTick == event.tick then return end
  global.lastPreBuildTick = event.tick

  -- Find the blueprint's bounding box.
  local positions = {}
  table.each(player.get_blueprint_entities(), function(v) table.insert(positions, Position.new(v.position)) end)
  local leftTop = Position.min_xy(positions)
  local rightBottom = Position.max_xy(positions)
  local bbox = Area.new{leftTop, rightBottom}
  local negCenter = bbox:center():flip()
  local bboxSize = bbox:offset(negCenter)  -- `bbox - bbox.center`
  -- Maybe rotate the bbox.
  local area = bboxSize:offset(event.position):ceil()
  if event.direction == defines.direction.east or event.direction == defines.direction.west then
    area = area:flip()
  end

  -- Find the positions where the blueprint *would* place the relevant entities.
  local bpEntityPositions = {}
  table.each(player.get_blueprint_entities(), function(v)
    if v.name == entityName then
      local bpPos = rotateAndFlip(Position.new(v.position):add(negCenter), event.direction, event.flip_horizontal, event.flip_vertical)
      local pos = bpPos:add(event.position):center()
      table.insert(bpEntityPositions, pos)
    end
  end)