--- @param box1 BoundingBox --- @param box2 BoundingBox local function bounding_boxes_equal(box1, box2) return box1.left_top.x == box2.left_top.x and box1.left_top.y == box2.left_top.y and box1.right_bottom.x == box2.right_bottom.x and box1.right_bottom.y == box2.right_bottom.y end --- @class common local common = {} --- Builds an entity over the top of an existing entity. --- @param player LuaPlayer --- @param entity LuaEntity --- @param new_prototype LuaEntityPrototype --- @param new_quality LuaQualityPrototype --- @return LuaEntity? function common.build(player, entity, new_prototype, new_quality) local item_spec = common.get_item_to_place(player, new_prototype, new_quality) if not item_spec then return nil end local bounding_box = entity.bounding_box local surface = entity.surface local position = entity.position local direction = entity.direction local quality = entity.quality local force = entity.force local new_entity = player.surface.create_entity({ name = new_prototype.name, position = entity.position, direction = entity.direction, quality = new_quality, force = entity.force, fast_replace = true, player = player, character = player.character, raise_built = true, create_build_effect_smoke = true, spawn_decorations = true, move_stuck_players = true, item = item_spec.item, register_plant = true, --- Undergound belt type = entity.type == "underground-belt" and entity.belt_to_ground_type or nil, }) if not new_entity or not new_entity.valid then -- Some mods will immediately replace the entity with a different one. Let's see if we can detect this. for _, replaced_entity in pairs(surface.find_entities_filtered({ direction = direction, position = position, quality = quality.name, force = force, })) do if replaced_entity.type ~= "entity-ghost" and replaced_entity.type ~= "tile-ghost" and bounding_boxes_equal(replaced_entity.bounding_box, bounding_box) then new_entity = replaced_entity break else return end end end item_spec.item.count = item_spec.item.count - item_spec.count player.play_sound({ path = "entity-build/" .. new_entity.name, position = new_entity.position, }) return new_entity end --- @param player LuaPlayer --- @param entity LuaEntity --- @param new_prototype LuaEntityPrototype --- @return boolean function common.check_can_place_entity(player, entity, new_prototype) -- TODO: Remote interface for mods to conditionally decide if placement is allowed. if not player.can_place_entity({ name = new_prototype.name, position = entity.position, direction = entity.direction, }) then storage.recheck_on_move[player.index] = true return false end return true end --- @param item LuaItemStack --- @param name string --- @param quality LuaQualityPrototype --- @param min_count uint? --- @param max_count uint? local function does_item_match(item, name, quality, min_count, max_count) min_count = min_count or 0 max_count = max_count or math.huge return item.valid_for_read and item.name == name and item.quality == quality and item.count >= min_count and item.count <= max_count end --- Returns the first LuaItemStack with the given name and quality. If count is provided, the returned item stack will --- contain at least count items. --- @param player LuaPlayer --- @param name string --- @param quality LuaQualityPrototype --- @param min_count uint? --- @param max_count uint? --- @return fun(): LuaItemStack? function common.get_item(player, name, quality, min_count, max_count) local cursor_stack_used = false local cursor_stack = player.cursor_stack local inventory = player.get_main_inventory() local i = 0 return function() if not cursor_stack_used then cursor_stack_used = true if cursor_stack and does_item_match(cursor_stack, name, quality, min_count, max_count) then return cursor_stack end end if not inventory then return end while i <= #inventory do i = i + 1 local stack = inventory[i] if does_item_match(stack, name, quality, min_count, max_count) then return stack end end end end --- Returns the first LuaItemStack that can build the given entity and quality. --- @param player LuaPlayer --- @param prototype LuaEntityPrototype --- @param quality LuaQualityPrototype --- @return {item: LuaItemStack, count: uint}? function common.get_item_to_place(player, prototype, quality) for _, item_to_place in pairs(prototype.items_to_place_this or {}) do local item = common.get_item(player, item_to_place.name, quality, item_to_place.count)() if item then return { item = item, count = item_to_place.count } end end end return common