z_mirror/BlueprintTools

git clone https://git.raiguard.me/z_mirror/BlueprintTools

Caleb HeuerFix planner pipette not accounting for qualitycd858de

trunk
8.0 KiB264 linesraw
1local util = require("scripts.util")
2
3--- @class EntityPrototypeAndQualityName
4--- @field entity LuaEntityPrototype
5--- @field quality string
6
7---@param index uint
8---@return LuaItemStack?
9local function get_cursor_stack(index)
10  local player = game.get_player(index) --[[@as LuaPlayer]]
11  local cursor = player.cursor_stack
12  if not (cursor and cursor.valid_for_read) then
13    return
14  end
15  return cursor
16end
17
18---@param event EventData.CustomInputEvent
19---@return EntityPrototypeAndQualityName?
20local function get_selected_id(event)
21  local selected = event.selected_prototype
22  if not selected then
23    return
24  end
25
26  if selected.base_type == "entity" then
27    --- @cast selected.quality -?
28    local player = game.get_player(event.player_index) --[[@as LuaPlayer]]
29    local selected_entity = player.selected
30    if not selected_entity then
31      return { entity = prototypes.entity[selected.name], quality = selected.quality }
32    end
33    if selected_entity.type == "entity-ghost" and selected_entity.ghost_prototype.type ~= "tile" then
34      return {
35        entity = selected_entity.ghost_prototype --[[@as LuaEntityPrototype]],
36        quality = selected.quality,
37      }
38    end
39    return { entity = selected_entity.prototype, quality = selected.quality }
40
41    -- _CodeGreen: I couldn't find anything in game where I could click on an item or recipe that actually had a CustomInputEvent fire
42    -- But the code to handle them is here, I guess
43  elseif selected.base_type == "item" then
44    --- @cast selected.quality -?
45    local item = prototypes.item[selected.name]
46    local place_result = item.place_result
47    if not place_result then
48      return
49    end
50    return { entity = place_result, quality = selected.quality }
51  elseif selected.base_type == "recipe" then
52    --- @cast selected.quality -?
53    --- @type LuaRecipePrototype
54    local recipe = prototypes.recipe[selected.name]
55    local main_product = recipe.main_product
56    local products = main_product and { main_product } or recipe.products
57    local item_prototypes = prototypes.item
58    for _, product in pairs(products) do
59      if product.type ~= "item" then
60        goto continue
61      end
62      --- @cast product ItemProduct
63      --- @type LuaItemPrototype
64      local item = item_prototypes[product.name]
65      local place_result = item.place_result
66      if place_result then
67        return { entity = place_result, quality = selected.quality }
68      end
69      ::continue::
70    end
71  end
72end
73
74---@param cursor LuaItemStack
75---@param from string
76--- @param quality string?
77---@param to string?
78---@return LocalisedString?
79local function add_upgrade_filter(cursor, from, quality, to)
80  if not to then
81    return "message.bpt-entity-cannot-upgrade"
82  end
83
84  local first_index
85  for i = 1, cursor.mapper_count do
86    local mapper = cursor.get_mapper(i, "from")
87    if not mapper then
88      first_index = first_index or i
89      goto continue
90    end
91
92    if mapper.type == "entity" and mapper.name == from and mapper.quality == quality then
93      local to_mapper = cursor.get_mapper(i, "to")
94      if to_mapper and to_mapper.name == to and to_mapper.quality == quality then
95        return "message.bpt-filter-already-present"
96      end
97      cursor.set_mapper(i, "to", { type = "entity", name = to, quality = quality, comparator = quality and "=" or nil })
98      return
99    end
100
101    ::continue::
102  end
103
104  if not first_index then
105    first_index = cursor.mapper_count + 1
106  end
107  cursor.set_mapper(
108    first_index,
109    "from",
110    { type = "entity", name = from, quality = quality, comparator = quality and "=" or nil }
111  )
112  cursor.set_mapper(
113    first_index,
114    "to",
115    { type = "entity", name = to, quality = quality, comparator = quality and "=" or nil }
116  )
117end
118
119---@param cursor LuaItemStack
120---@param id EntityPrototypeAndQualityName
121---@return LocalisedString?
122local function add_filter(cursor, id)
123  if cursor.is_deconstruction_item then
124    local first_empty = 0
125    local filters = cursor.entity_filters
126    for i = 1, cursor.entity_filter_count do
127      local filter = filters[i]
128      if filter and filter.name == id.entity.name and filter.quality == id.quality then
129        return "message.bpt-filter-already-present"
130      elseif not filter and first_empty == 0 then
131        first_empty = i
132      end
133    end
134    if first_empty == 0 then
135      first_empty = cursor.entity_filter_count + 1
136    end
137    --- @diagnostic disable-next-line: param-type-mismatch
138    if not cursor.set_entity_filter(first_empty, { name = id.entity, quality = id.quality }) then
139      return "message.bpt-unable-to-add-filter"
140    end
141  elseif cursor.is_upgrade_item then
142    local next_upgrade = id.entity.next_upgrade
143    local to = next_upgrade and next_upgrade.name or storage.downgrades[id.entity.name]
144    return add_upgrade_filter(cursor, id.entity.name, id.quality, to)
145  end
146end
147
148--- @param event EventData.CustomInputEvent
149script.on_event("bpt-pipette-add", function(event)
150  local cursor = get_cursor_stack(event.player_index)
151  if not cursor then
152    return
153  end
154  local entity_and_quality = get_selected_id(event)
155  if not entity_and_quality then
156    return
157  end
158
159  local player = game.get_player(event.player_index) --[[@as LuaPlayer]]
160  local message = add_filter(cursor, entity_and_quality)
161  if message then
162    util.cursor_notification(player, { message }, "utility/cannot_build")
163  else
164    player.play_sound({ path = "utility/smart_pipette" })
165  end
166end)
167
168---@param cursor LuaItemStack
169---@param id EntityPrototypeAndQualityName
170---@return LocalisedString?
171local function remove_filter(cursor, id)
172  if cursor.is_deconstruction_item then
173    local filters = cursor.entity_filters
174    for i = 1, cursor.entity_filter_count do
175      local filter = filters[i]
176      if filter and filter.name == id.entity.name and filter.quality == id.quality then
177        cursor.set_entity_filter(i, nil)
178        return
179      end
180    end
181  elseif cursor.is_upgrade_item then
182    for i = 1, cursor.mapper_count do
183      local mapper = cursor.get_mapper(i, "from")
184      --- @cast mapper -?
185      if mapper.type == "entity" and mapper.name == id.entity.name and mapper.quality == id.quality then
186        cursor.set_mapper(i, "from", nil)
187        cursor.set_mapper(i, "to", nil)
188        return
189      end
190    end
191  end
192
193  return "message.bpt-filter-not-present"
194end
195
196--- @param event EventData.CustomInputEvent
197script.on_event("bpt-pipette-remove", function(event)
198  local cursor = get_cursor_stack(event.player_index)
199  if not cursor then
200    return
201  end
202  local id = get_selected_id(event)
203  if not id then
204    return
205  end
206
207  local player = game.get_player(event.player_index) --[[@as LuaPlayer]]
208  local message = remove_filter(cursor, id)
209  if message then
210    util.cursor_notification(player, { message }, "utility/cannot_build")
211  else
212    player.play_sound({ path = "utility/clear_cursor" })
213  end
214end)
215
216--- @param event EventData.CustomInputEvent
217script.on_event("bpt-pipette-downgrade", function(event)
218  local cursor = get_cursor_stack(event.player_index)
219  if not cursor then
220    return
221  end
222  if not cursor.is_upgrade_item then
223    return
224  end
225
226  local id = get_selected_id(event)
227  if not id then
228    return
229  end
230
231  local downgrade = storage.downgrades[id.entity.name]
232  local next_upgrade = id.entity.next_upgrade
233  local to = downgrade or (next_upgrade and next_upgrade.name)
234
235  local player = game.get_player(event.player_index) --[[@as LuaPlayer]]
236  local message = add_upgrade_filter(cursor, id.entity.name, id.quality, to)
237  if message then
238    if message == "message.bpt-entity-cannot-upgrade" then
239      message = "message.bpt-entity-cannot-downgrade"
240    end
241    util.cursor_notification(player, { message }, "utility/cannot_build")
242  else
243    player.play_sound({ path = "utility/smart_pipette" })
244  end
245end)
246
247local lib = {}
248
249function lib.cache_downgrades()
250  ---@type table<string, string>
251  local downgrades = {}
252  storage.downgrades = downgrades
253
254  for name, entity in pairs(prototypes.entity) do
255    --- @cast name string
256    --- @cast entity LuaEntityPrototype
257    local next_upgrade = entity.next_upgrade
258    if next_upgrade then
259      downgrades[next_upgrade.name] = name
260    end
261  end
262end
263
264return lib