factoriolib / flib

A set of high-quality, commonly-used utilities for creating Factorio mods.
https://mods.factorio.com/mod/flib
MIT License
61 stars 15 forks source link

Blueprint manipulation donation #56

Open kryojenik opened 8 months ago

kryojenik commented 8 months ago

This may or may not be useful to this project, but I thought I'd donate it here in case you thought it may be. Attempting to work with blueprints (specifically of the ctrl-c variety being stamped on existing entities) is tough. We don't get many events with a lot of info. Just on_pre_build. So you pretty much have to do your own BP position calculations. These are a couple of functions that I put together to get a bounding box of all the BP entities, and how to find the center of where the BP will be placed. If helpful, feel free to use them freely in flib.

---@param entities BlueprintEntity[] # E.g. from LuaPlayer.get_blueprint_entities()
---@return BoundingBox # Box that contains all blueprint entities
---@return uint # The building grid size needed to build this blueprint
function M.get_blueprint_bounding_box(entities)
  local box = flib_box.from_position(entities[1].position, true)
  local names = {}
  for _, e in ipairs(entities) do
    names[e.name] = true
  end

  local name_filter = {}
  for k, _ in pairs(names) do
    table.insert(name_filter, k)
  end

  -- Define a bounding box the size of the blueprint to be placed
  local grid_size = 1
  ---@diagnostic disable-next-line:missing-fields
  local protos = game.get_filtered_entity_prototypes{{filter = "name", name = name_filter}}
  for _, entity in pairs(entities) do
    local collision_box = protos[entity.name].collision_box
    grid_size = math.max(grid_size, protos[entity.name].building_grid_bit_shift)
    box = flib_box.expand_to_contain_box(
      box,
      flib_box.from_dimensions(
        entity.position,
        flib_box.width(collision_box),
        flib_box.height(collision_box)
      )
    )
  end

  -- Expand bounding box to be full tiles based on the entity with the largest building grid size
  box.left_top.x = grid_size * math.floor(box.left_top.x / grid_size)
  box.left_top.y = grid_size * math.floor(box.left_top.y / grid_size)
  box.right_bottom.x = grid_size * math.ceil(box.right_bottom.x / grid_size)
  box.right_bottom.y = grid_size * math.ceil(box.right_bottom.y / grid_size)
  return box, grid_size
end
---@param box BoundingBox # Bounding box of blueprint to place
---@param pos MapPosition # Position to center new bounding box around
---@param grid_size uint? # Building grid size to base centering.  Default: 1
---@return MapPosition # Center position of where blueprint will be placed
function M.get_placed_blueprint_center(box, pos, grid_size)
  local grid_size = grid_size or 1
  local pos_x = pos.x or pos[1]
  local pos_y = pos.y or pos[2]
  pos_x = (flib_box.width(box) / grid_size) % 2 == 0
          and math.floor(pos_x / grid_size + .5 ) * grid_size
          or math.floor(pos_x / grid_size) * grid_size + grid_size / 2
  pos_y = (flib_box.height(box) / grid_size) % 2 == 0
          and math.floor(pos_y / grid_size + .5 ) * grid_size
          or math.floor(pos_y / grid_size) * grid_size + grid_size / 2
  if pos.x then
    return { x = pos_x, y = pos_y }
  else
    return { pos_x, pos_y }
  end
end
raiguard commented 6 months ago

I think it would be a good idea to have some blueprint functions. If you make a pull request we can get these added!