z_mirror/Krastorio2

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

Caleb HeuerFix crashes related to minable/active entity flag changes1e9ba188

trunk
7.8 KiB243 linesraw
1local flib_bounding_box = require("__flib__.bounding-box")
2
3--- @alias k2.ResourceRestrictions.Forces table<ForceIndex, k2.ResourceRestrictions.ForceData?>
4
5--- @class k2.ResourceRestrictions.ForceData
6--- @field disabled_mining_drills table<UnitNumber, k2.ResourceRestrictions.DrillData>
7--- @field force LuaForce
8--- @field restricted_resources Set<string>
9
10--- @class k2.ResourceRestrictions.DrillData
11--- @field drill LuaEntity
12--- @field icon LuaRenderObject
13
14--- @return k2.ResourceRestrictions.Forces
15local function get_forces()
16  if not storage.resource_restrictions then
17    --- @type k2.ResourceRestrictions.Forces
18    storage.resource_restrictions = {}
19  end
20  return storage.resource_restrictions
21end
22
23--- @param force LuaForce
24local function get_force_data(force)
25  local forces = get_forces()
26  local force_index = force.index
27  local force_data = forces[force_index]
28  if not force_data then
29    force_data = {
30      disabled_mining_drills = {},
31      force = force,
32      restricted_resources = {},
33    }
34    forces[force_index] = force_data
35  end
36  return force_data
37end
38
39--- @param force_data k2.ResourceRestrictions.ForceData
40--- @param drill LuaEntity
41--- @return LuaEntityPrototype? restricted_resource
42local function get_first_restricted_resource(force_data, drill)
43  local radius = drill.prototype.get_mining_drill_radius(drill.quality)
44  --- @cast radius -?
45  local area = flib_bounding_box.from_dimensions(drill.position, radius * 2, radius * 2)
46  local targets = drill.surface.find_entities_filtered({ type = "resource", area = area })
47  local drill_categories = drill.prototype.resource_categories or {}
48  local restrictions = force_data.restricted_resources
49  for i = 1, #targets do
50    local target = targets[i]
51    if drill_categories[target.prototype.resource_category] and restrictions[target.name] then
52      return target.prototype
53    end
54  end
55end
56
57--- @param force_data k2.ResourceRestrictions.ForceData
58--- @param drill LuaEntity
59local function update_drill(force_data, drill)
60  local disabled_mining_drills = force_data.disabled_mining_drills
61
62  local unit_number = drill.unit_number
63  --- @cast unit_number -?
64
65  local drill_data = disabled_mining_drills[unit_number]
66
67  local restriction = get_first_restricted_resource(force_data, drill)
68  if restriction then
69    if not drill_data then
70      drill.disabled_by_script = true
71      drill.custom_status = {
72        diode = defines.entity_status_diode.red,
73        label = { "entity-status.kr-unable-to-mine", restriction.localised_name },
74      }
75      disabled_mining_drills[unit_number] = {
76        drill = drill,
77        icon = rendering.draw_sprite({
78          sprite = "utility/resources_depleted_icon",
79          target = drill,
80          surface = drill.surface,
81          x_scale = 0.5,
82          y_scale = 0.5,
83          blink_interval = 30,
84        }),
85      }
86    end
87  else
88    if drill_data then
89      drill.disabled_by_script = false
90      drill.custom_status = nil
91      if drill_data.icon.valid then
92        drill_data.icon.destroy()
93      end
94      disabled_mining_drills[unit_number] = nil
95    end
96  end
97end
98
99--- @param force_data k2.ResourceRestrictions.ForceData
100local function refresh_all_drills(force_data)
101  for _, drill_data in pairs(force_data.disabled_mining_drills) do
102    if drill_data.icon.valid then
103      drill_data.icon.destroy()
104    end
105  end
106  force_data.disabled_mining_drills = {}
107  for _, surface in pairs(game.surfaces) do
108    for _, drill in pairs(surface.find_entities_filtered({ type = "mining-drill", force = force_data.force })) do
109      update_drill(force_data, drill)
110    end
111  end
112end
113
114local unlocks = prototypes.mod_data["kr-resource-restrictions"].data --[[ @as table<data.TechnologyID, data.EntityID[]?> ]]
115
116--- @param force_data k2.ResourceRestrictions.ForceData
117--- @param technology LuaTechnology
118--- @param restrict boolean
119local function process_effects(force_data, technology, restrict)
120  local technology_unlocks = unlocks[technology.name]
121  if not technology_unlocks then
122    return
123  end
124
125  local restricted_resources = force_data.restricted_resources
126  for _, resource_name in pairs(technology_unlocks) do
127    restricted_resources[resource_name] = restrict or nil
128  end
129
130  if restrict then
131    -- The on_research_reversed event is very rare, and never happens during regular gameplay, so it is acceptable to have a lag spike in this case.
132    refresh_all_drills(force_data)
133  else
134    for _, drill_data in pairs(force_data.disabled_mining_drills) do
135      update_drill(force_data, drill_data.drill)
136    end
137  end
138end
139
140--- @param force LuaForce
141local function refresh_force(force)
142  force.mining_with_fluid = true
143  local force_data = get_force_data(force)
144  for _, technology in pairs(force.technologies) do
145    if not technology.researched then
146      -- TODO: This is doing a full scan of all surfaces every time!
147      process_effects(force_data, technology, true)
148    end
149  end
150end
151
152--- @param e EntityBuiltEvent
153local function on_entity_built(e)
154  local entity = e.entity or e.destination
155  if not entity.valid or entity.type ~= "mining-drill" then
156    return
157  end
158  update_drill(get_force_data(entity.force --[[@as LuaForce]]), entity)
159end
160
161--- @param e EntityDestroyedEvent
162local function on_entity_destroyed(e)
163  local entity = e.entity
164  if not entity.valid or entity.type ~= "mining-drill" then
165    return
166  end
167
168  -- The icon will get destroyed automatically because its target is the mining drill.
169  get_force_data(e.entity.force --[[@as LuaForce]]).disabled_mining_drills[entity.unit_number] = nil
170end
171
172--- @param e EventData.on_research_finished
173local function on_research_finished(e)
174  process_effects(get_force_data(e.research.force), e.research, false)
175end
176
177--- @param e EventData.on_research_finished
178local function on_research_reversed(e)
179  process_effects(get_force_data(e.research.force), e.research, true)
180end
181
182--- @param e EventData.on_force_created
183local function on_force_created(e)
184  refresh_force(e.force)
185end
186
187--- @param e EventData.on_force_reset
188local function on_force_reset(e)
189  refresh_force(e.force)
190end
191
192--- @param e EventData.on_forces_merging
193local function on_forces_merging(e)
194  local source_data = get_force_data(e.source)
195  for _, drill_data in pairs(source_data.disabled_mining_drills) do
196    if drill_data.icon.valid then
197      drill_data.icon.destroy()
198    end
199  end
200  get_forces()[e.source.index] = nil
201  refresh_force(e.destination)
202end
203
204--- @param e EventData.on_technology_effects_reset
205local function on_technology_effects_reset(e)
206  refresh_force(e.force)
207end
208
209local function refresh()
210  storage.resource_restrictions = nil
211  for _, force in pairs(game.forces) do
212    refresh_force(force)
213    refresh_all_drills(get_force_data(force))
214  end
215end
216
217--- @class MiningDrillRestrictionsHandler: event_handler
218local handler = {}
219
220handler.on_init = refresh
221handler.on_configuration_changed = refresh
222
223handler.events = {
224  [defines.events.on_built_entity] = on_entity_built,
225  [defines.events.on_entity_cloned] = on_entity_built,
226  [defines.events.on_entity_died] = on_entity_destroyed,
227  [defines.events.on_force_created] = on_force_created,
228  [defines.events.on_force_reset] = on_force_reset,
229  [defines.events.on_forces_merging] = on_forces_merging,
230  [defines.events.on_player_mined_entity] = on_entity_destroyed,
231  [defines.events.on_research_finished] = on_research_finished,
232  [defines.events.on_research_reversed] = on_research_reversed,
233  [defines.events.on_robot_built_entity] = on_entity_built,
234  [defines.events.on_robot_mined_entity] = on_entity_destroyed,
235  [defines.events.on_space_platform_built_entity] = on_entity_built,
236  [defines.events.on_space_platform_mined_entity] = on_entity_destroyed,
237  [defines.events.on_technology_effects_reset] = on_technology_effects_reset,
238  [defines.events.script_raised_built] = on_entity_built,
239  [defines.events.script_raised_destroy] = on_entity_destroyed,
240  [defines.events.script_raised_revive] = on_entity_built,
241}
242
243return handler