Page 1 of 1
How to match blueprint entities to actual entities?
Posted: Tue Sep 10, 2019 3:39 pm
by mrvn
I'm trying to use the new LuaItemStack.set_blueprint_entity_tag() function to save lua data from entities in a blueprint.
For that I catch the defines.events.on_player_setup_blueprint event which lets me iterate over the blueprinted entities and set tags.
The problem I face is that LuaItemStack.get_blueprint_entities() returns an array of dummy entities that will be included in the blueprint. It does not give the actual entities that are being blueprinted. I need the original entities to extract the lua data from them and store that into tags.
The event contains the area the blueprint is being taken of. So I have at least some idea where to look for the original entities. But how do I match them up? I tried player.surface.find_entities(event.area) but that doesn't really help as the blueprint will be cropped to fit the selected entities and doesn't match the dimension of the area of the event.
Attached a minimal example of taking a blueprint and adding tags with some debug logging. I always set the tag to 23 but, just as an example, I want to save the original entities unit_number.
Re: How to match blueprint entities to actual entities?
Posted: Tue Sep 10, 2019 4:14 pm
by eradicator
Theorycrafting: The center of the blueprint should be at the center of the bounding box covering the outermost entities in the blueprint. So:
local t,b,l,r = 0,0,0,0
for entity in find_entities_filtered do if entity.pos.x > r then r = entity.pos.x
--etcpp for all four edges
local center = center({{t,l}{r,b}})
then you can substract the center from each entity's position and *should* have the same offset that the blueprint uses for each entity.
But there are edge cases, and official support would be nicer:
viewtopic.php?f=28&t=75369
Re: How to match blueprint entities to actual entities?
Posted: Wed Sep 11, 2019 1:02 am
by mrvn
eradicator wrote: ↑Tue Sep 10, 2019 4:14 pm
Theorycrafting: The center of the blueprint should be at the center of the bounding box covering the outermost entities in the blueprint. So:
local t,b,l,r = 0,0,0,0
for entity in find_entities_filtered do if entity.pos.x > r then r = entity.pos.x
--etcpp for all four edges
local center = center({{t,l}{r,b}})
then you can substract the center from each entity's position and *should* have the same offset that the blueprint uses for each entity.
But there are edge cases, and official support would be nicer:
viewtopic.php?f=28&t=75369
Are you sure it's the position and not the collision box or selection box? If you don't know I will try all three tomorrow and see which one it is.
As for the corner cases of having entities with the same name at the same position I believe that is usually a non issue. If one of the entities is in the blueprint the other will be too. And as creator of those entities (who else would set a tag on them?) you should know how to handle that case. e.g. use the entities direction to separate them or simply tag them both.
Re: How to match blueprint entities to actual entities?
Posted: Wed Sep 11, 2019 9:41 am
by eradicator
mrvn wrote: ↑Wed Sep 11, 2019 1:02 am
Are you sure it's the position and not the collision box or selection box? If you don't know I will try all three tomorrow and see which one it is.
Absoluetely not. I just wrote down a theory about a rough approach. Haven't done any testing yet. But i think that the position is the most likely candidate.
mrvn wrote: ↑Wed Sep 11, 2019 1:02 am
As for the corner cases of having entities with the same name at the same position I believe that is usually a non issue.
It would only affect the author of those entities yes. If that's not you then lucky you ;).
Re: How to match blueprint entities to actual entities?
Posted: Wed Sep 11, 2019 1:49 pm
by mrvn
It looks like the bounding box is used. It's not the position for sure.
The center seems to be also aligned to the grid of the entities involved. That means usually in the center of a tile. Except rail road entities have a 2x2 grid and then it changes to the center of that.
Also ghosts seem to appear as full entities in the BP.
Re: How to match blueprint entities to actual entities?
Posted: Wed Sep 11, 2019 3:50 pm
by mrvn
Tiles further change the center of a blueprint. But I think I can handle all cases now. I made a small example mod. Feel free to use it in your own mods under whatever (sensible) license you wish:
https://mods.factorio.com/mod/example-entity-with-tags
https://github.com/mrvn/factorio-exampl ... -with-tags
Re: How to match blueprint entities to actual entities?
Posted: Wed Sep 11, 2019 4:11 pm
by eradicator
Hmhm. So bounding boxes for entities, position for tiles, and a bit of magic to compensate for rail alignment? Only slightly more annoying than expected. I'm really hoping for official support to happen, and i really want a unit_number safe solution just so i don't have to bother thinking about edge-cases all the time.
Also control.lua states that it is GPL while the mod portal lists it as Unlicense. Also i've heared rumors that Unlicense might be ineffective under German law. #FunWithLicenses
Re: How to match blueprint entities to actual entities?
Posted: Wed Sep 11, 2019 4:22 pm
by mrvn
eradicator wrote: ↑Wed Sep 11, 2019 4:11 pm
Also control.lua states that it is GPL while the mod portal lists it as Unlicense. Also i've heared rumors that Unlicense might be ineffective under German law. #FunWithLicenses
Code: Select all
* Permission granted to relicense when used in a larger project.
The mod portal doesn't have a choice for that and PD seems to cover it best. It's an example, I don't consider it copyrightable material as that would defeat the purpose of it being an example for others to use. Relicense it to whatever you need and then it's on your head.
Re: How to match blueprint entities to actual entities?
Posted: Sun Nov 17, 2019 9:53 pm
by Cobaltur
Hi mrvn,
you demo works very well (22 Download at the moment).
I got no larger project
MathCoProcessor a combinator to store the functional behavior
I integrated it with minor so it can even reuse even more easily. It looks nice.
Here are the 3 ingredients if anyone is interested:
1) the callback to mrvn script to write the custom setting
Code: Select all
local blueprint = require "tag_support"
blueprint.blueprintCallback = function(bp, bp_entity, entity)
if entity.name == "math-allInOne" then
local myEntity = getEntity(entity)
bp.set_blueprint_entity_tag(bp_entity.entity_number, "mathOp", myEntity.mathOp)
end
end
2) a) read the blueprint on placing the entity
Code: Select all
local function onPlaceEntity(event)
if event.tags and event.tags.mathOp then
-- build by blueprint -> so copy data
mathOp = event.tags.mathOp or mathOp
end
table.insert(...)
end
script.on_event(defines.events.on_built_entity, onPlaceEntity)
script.on_event(defines.events.on_robot_built_entity, onPlaceEntity)
my complete placing routine also handles my migration of deprecated recipes/items it deprecated blueprints.
Code: Select all
local function onPlaceEntity(event)
-- we have deprecated items in chests and in blueprints
-- sp this script will replace them by on demand
local migratedEntity = Migration.migrateEntity(event.created_entity)
if migratedEntity ~= nil then
table.insert(global.math_combinators, migratedEntity)
end
if event.created_entity and event.created_entity.valid and includedEntityTypes[event.created_entity.name] then
event.created_entity.rotatable = true
local mathOp = "max" -- default, should not happens
if event.tags and event.tags.mathOp then
-- build by blueprint -> so copy data
mathOp = event.tags.mathOp or mathOp
end
table.insert(global.math_combinators, {comb = event.created_entity, mathOp = mathOp})
end
end
3) mrvn adaptod
script "tag_support"
Code: Select all
--[[ example-entity-with-tags
* Copyright (c) 2019 Goswin von Brederlow
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Example code showing how to use entity-with-tags in blueprints.
*
* Permission granted to relicense when used in a larger project.
--]]
-- adapted script to credit author and to easy reuse
-- there is a callback that needs to be installed
-- blueprintCallback = function (itemstack,bp_entity,entity)
local lib = {}
rail_types = {}
rail_types["straight-rail"] = true
rail_types["curved-rail"] = true
rail_types["rail-signal"] = true
rail_types["rail-chain-signal"] = true
rail_types["train-stop"] = true
local function de_ghost_entity(entity)
local name = entity.name
if (name == "entity-ghost") then
return entity.ghost_name, entity.ghost_type
else
return name, entity.prototype.type
end
end
function lib.player_setup_blueprint(event)
-- log("player_setup_blueprint")
-- log(serpent.block(event))
local player = game.players[event.player_index]
local itemstack = player.blueprint_to_setup
if not itemstack or not itemstack.valid_for_read then
itemstack = player.cursor_stack
end
if not itemstack or not itemstack.valid_for_read then
return
end
local entities = itemstack.get_blueprint_entities()
if not entities then
return
end
-- map blueprint entities to real entities
-- bounding box for all entities
local included_entities = {}
for _, entity in ipairs(entities) do
included_entities[entity.name] = true
end
local map_entities = player.surface.find_entities(event.area)
local min_x = 2147483647
local min_y = 2147483647
local max_x = -2147483648
local max_y = -2147483648
local has_rails = false
for _, entity in ipairs(map_entities) do
local name, proto_type = de_ghost_entity(entity)
if (not event.alt) or included_entities[name] then
if rail_types[proto_type] then
has_rails = true
end
local box = entity.bounding_box
if box.left_top.x < min_x then
min_x = box.left_top.x
end
if box.left_top.y < min_y then
min_y = box.left_top.y
end
if box.right_bottom.x > max_x then
max_x = box.right_bottom.x
end
if box.right_bottom.y > max_y then
max_y = box.right_bottom.y
end
end
end
-- bounding box for all tiles
local tiles = itemstack.get_blueprint_tiles()
local included_tiles = {}
local map_tiles = {}
if tiles then
for _, tile in ipairs(tiles) do
included_tiles[tile.name] = true
end
map_tiles = player.surface.find_tiles_filtered {area = event.area}
for _, tile in ipairs(map_tiles) do
local name = tile.name
if tile.prototype.can_be_part_of_blueprint and tile.prototype.items_to_place_this then
if name == "tile-ghost" then
name = tile.prototype.name
end
if (not event.alt) or included_tile[name] then
local pos = tile.position
-- log("tile " .. name .. " at (" .. tile.position.x .. ", " .. tile.position.y .. ")")
if pos.x < min_x then
min_x = pos.x
end
if pos.y < min_y then
min_y = pos.y
end
if pos.x + 1 > max_x then
max_x = pos.x + 1
end
if pos.y + 1 > max_y then
max_y = pos.y + 1
end
end
end
end
end
local cx, cy
if has_rails then
cx = math.floor((math.floor(min_x) + math.ceil(max_x)) / 4) * 2 + 1
cy = math.floor((math.floor(min_y) + math.ceil(max_y)) / 4) * 2 + 1
else
cx = math.floor((math.floor(min_x) + math.ceil(max_x)) / 2) + 0.5
cy = math.floor((math.floor(min_y) + math.ceil(max_y)) / 2) + 0.5
end
-- log("estimated blueprint center: (" .. min_x .. ", " .. min_y .. ")-(" .. max_x .. ", " .. max_y .. ") ==> (" .. cx .. ", " .. cy .. ")")
-- match entities
local cache = {}
for i, entity in ipairs(entities) do
-- log("bp entity " .. i .. ": " .. entity.name .. " at (" .. entity.position.x .. ", " .. entity.position.y .. ")")
cache[entity.position.x .. "_" .. entity.position.y .. "_" .. entity.name] = entity
end
for i, entity in ipairs(map_entities) do
local name, proto_type = de_ghost_entity(entity)
if included_entities[name] then
-- log("map entity " .. i .. ": " .. entity.name .. "[" .. name .. "]" .. " at (" .. entity.position.x - cx .. ", " .. entity.position.y - cy .. ")")
local bp_entity = cache[entity.position.x - cx .. "_" .. entity.position.y - cy .. "_" .. name]
if bp_entity then
-- #moved to callback handler
-- if entity.name == "math-allInOne" then
-- -- set tag for our example tag-chest
-- itemstack.set_blueprint_entity_tag(bp_entity.entity_number, "mathOp", "entity.unit_number")
-- end
lib.blueprintCallback(itemstack, bp_entity, entity)
else
log(
"ERROR: map entity not found in blueprint: " ..
entity.name ..
"[" .. name .. "]" .. " at (" .. entity.position.x - cx .. ", " .. entity.position.y - cy .. ")"
)
end
end
end
end
-- function entity_built(event)
-- local entity = event.created_entity
-- -- log("entity_built: " .. entity.name)
-- if entity and entity.name == "tag-chest" then
-- -- log(" is tag-chest")
-- local tags = event.tags
-- if tags then
-- -- log(" with tags")
-- local data = tags["example-tag"]
-- if data then
-- -- log(" Entity build with example example-tag " .. data)
-- else
-- -- log(" example-tag missing")
-- end
-- else
-- -- log(" tags missing")
-- end
-- end
-- end
return lib