local util = require("scripts.util") --- @class EntityPrototypeAndQualityName --- @field entity LuaEntityPrototype --- @field quality string ---@param index uint ---@return LuaItemStack? local function get_cursor_stack(index) local player = game.get_player(index) --[[@as LuaPlayer]] local cursor = player.cursor_stack if not (cursor and cursor.valid_for_read) then return end return cursor end ---@param event EventData.CustomInputEvent ---@return EntityPrototypeAndQualityName? local function get_selected_id(event) local selected = event.selected_prototype if not selected then return end if selected.base_type == "entity" then --- @cast selected.quality -? local player = game.get_player(event.player_index) --[[@as LuaPlayer]] local selected_entity = player.selected if not selected_entity then return { entity = prototypes.entity[selected.name], quality = selected.quality } end if selected_entity.type == "entity-ghost" and selected_entity.ghost_prototype.type ~= "tile" then return { entity = selected_entity.ghost_prototype --[[@as LuaEntityPrototype]], quality = selected.quality, } end return { entity = selected_entity.prototype, quality = selected.quality } -- _CodeGreen: I couldn't find anything in game where I could click on an item or recipe that actually had a CustomInputEvent fire -- But the code to handle them is here, I guess elseif selected.base_type == "item" then --- @cast selected.quality -? local item = prototypes.item[selected.name] local place_result = item.place_result if not place_result then return end return { entity = place_result, quality = selected.quality } elseif selected.base_type == "recipe" then --- @cast selected.quality -? --- @type LuaRecipePrototype local recipe = prototypes.recipe[selected.name] local main_product = recipe.main_product local products = main_product and { main_product } or recipe.products local item_prototypes = prototypes.item for _, product in pairs(products) do if product.type ~= "item" then goto continue end --- @cast product ItemProduct --- @type LuaItemPrototype local item = item_prototypes[product.name] local place_result = item.place_result if place_result then return { entity = place_result, quality = selected.quality } end ::continue:: end end end ---@param cursor LuaItemStack ---@param from string --- @param quality string? ---@param to string? ---@return LocalisedString? local function add_upgrade_filter(cursor, from, quality, to) if not to then return "message.bpt-entity-cannot-upgrade" end local first_index for i = 1, cursor.mapper_count do local mapper = cursor.get_mapper(i, "from") if not mapper then first_index = first_index or i goto continue end if mapper.type == "entity" and mapper.name == from and mapper.quality == quality then local to_mapper = cursor.get_mapper(i, "to") if to_mapper and to_mapper.name == to and to_mapper.quality == quality then return "message.bpt-filter-already-present" end cursor.set_mapper(i, "to", { type = "entity", name = to, quality = quality, comparator = quality and "=" or nil }) return end ::continue:: end if not first_index then first_index = cursor.mapper_count + 1 end cursor.set_mapper( first_index, "from", { type = "entity", name = from, quality = quality, comparator = quality and "=" or nil } ) cursor.set_mapper( first_index, "to", { type = "entity", name = to, quality = quality, comparator = quality and "=" or nil } ) end ---@param cursor LuaItemStack ---@param id EntityPrototypeAndQualityName ---@return LocalisedString? local function add_filter(cursor, id) if cursor.is_deconstruction_item then local first_empty = 0 local filters = cursor.entity_filters for i = 1, cursor.entity_filter_count do local filter = filters[i] if filter and filter.name == id.entity.name and filter.quality == id.quality then return "message.bpt-filter-already-present" elseif not filter and first_empty == 0 then first_empty = i end end if first_empty == 0 then first_empty = cursor.entity_filter_count + 1 end --- @diagnostic disable-next-line: param-type-mismatch if not cursor.set_entity_filter(first_empty, { name = id.entity, quality = id.quality }) then return "message.bpt-unable-to-add-filter" end elseif cursor.is_upgrade_item then local next_upgrade = id.entity.next_upgrade local to = next_upgrade and next_upgrade.name or storage.downgrades[id.entity.name] return add_upgrade_filter(cursor, id.entity.name, id.quality, to) end end --- @param event EventData.CustomInputEvent script.on_event("bpt-pipette-add", function(event) local cursor = get_cursor_stack(event.player_index) if not cursor then return end local entity_and_quality = get_selected_id(event) if not entity_and_quality then return end local player = game.get_player(event.player_index) --[[@as LuaPlayer]] local message = add_filter(cursor, entity_and_quality) if message then util.cursor_notification(player, { message }, "utility/cannot_build") else player.play_sound({ path = "utility/smart_pipette" }) end end) ---@param cursor LuaItemStack ---@param id EntityPrototypeAndQualityName ---@return LocalisedString? local function remove_filter(cursor, id) if cursor.is_deconstruction_item then local filters = cursor.entity_filters for i = 1, cursor.entity_filter_count do local filter = filters[i] if filter and filter.name == id.entity.name and filter.quality == id.quality then cursor.set_entity_filter(i, nil) return end end elseif cursor.is_upgrade_item then for i = 1, cursor.mapper_count do local mapper = cursor.get_mapper(i, "from") --- @cast mapper -? if mapper.type == "entity" and mapper.name == id.entity.name and mapper.quality == id.quality then cursor.set_mapper(i, "from", nil) cursor.set_mapper(i, "to", nil) return end end end return "message.bpt-filter-not-present" end --- @param event EventData.CustomInputEvent script.on_event("bpt-pipette-remove", function(event) local cursor = get_cursor_stack(event.player_index) if not cursor then return end local id = get_selected_id(event) if not id then return end local player = game.get_player(event.player_index) --[[@as LuaPlayer]] local message = remove_filter(cursor, id) if message then util.cursor_notification(player, { message }, "utility/cannot_build") else player.play_sound({ path = "utility/clear_cursor" }) end end) --- @param event EventData.CustomInputEvent script.on_event("bpt-pipette-downgrade", function(event) local cursor = get_cursor_stack(event.player_index) if not cursor then return end if not cursor.is_upgrade_item then return end local id = get_selected_id(event) if not id then return end local downgrade = storage.downgrades[id.entity.name] local next_upgrade = id.entity.next_upgrade local to = downgrade or (next_upgrade and next_upgrade.name) local player = game.get_player(event.player_index) --[[@as LuaPlayer]] local message = add_upgrade_filter(cursor, id.entity.name, id.quality, to) if message then if message == "message.bpt-entity-cannot-upgrade" then message = "message.bpt-entity-cannot-downgrade" end util.cursor_notification(player, { message }, "utility/cannot_build") else player.play_sound({ path = "utility/smart_pipette" }) end end) local lib = {} function lib.cache_downgrades() ---@type table local downgrades = {} storage.downgrades = downgrades for name, entity in pairs(prototypes.entity) do --- @cast name string --- @cast entity LuaEntityPrototype local next_upgrade = entity.next_upgrade if next_upgrade then downgrades[next_upgrade.name] = name end end end return lib