z_mirror/EditorExtensions

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

Caleb HeuerEnsure consistency of testing lab state on configuration changed22e08fb

trunk
11.9 KiB414 linesraw
1--- @class LabState
2--- @field lab StateData
3--- @field normal StateData
4--- @field player LuaPlayer
5--- @field refresh boolean?
6
7--- @class StateData
8--- @field force LuaForce
9--- @field position MapPosition
10--- @field surface LuaSurface
11--- @field vehicle LuaEntity?
12--- @field is_driver boolean?
13
14--- @alias LabSetting
15--- | "force"
16--- | "off"
17--- | "personal"
18
19local empty_map_gen_settings = {
20  default_enable_all_autoplace_controls = false,
21  property_expression_names = { cliffiness = 0 },
22  autoplace_settings = {
23    tile = { settings = { ["out-of-map"] = { frequency = "normal", size = "normal", richness = "normal" } } },
24  },
25  starting_area = "none",
26}
27
28--- @param player LuaPlayer
29--- @return string
30local function get_testing_lab_key(player)
31  local lab_setting = player.mod_settings["ee-testing-lab"].value --[[@as LabSetting]]
32  if lab_setting == "personal" then
33    return tostring(player.index)
34  elseif game.forces["EE_TESTSURFACE_shared"] then
35    return "shared"
36  else
37    -- gsub has two return values, but we only care about the first one.
38    local key = string.gsub(player.force.name, "EE_TESTFORCE_", "")
39    return key
40  end
41end
42
43--- @param player LuaPlayer
44--- @return string
45local function get_testing_force_name(player)
46  return "EE_TESTFORCE_" .. get_testing_lab_key(player)
47end
48
49--- @param player LuaPlayer
50--- @return string
51local function get_testing_surface_name(player)
52  return "EE_TESTSURFACE_" .. get_testing_lab_key(player)
53end
54
55--- @param player LuaPlayer
56--- @return LabState?
57local function create_lab(player)
58  local force_name = get_testing_force_name(player)
59  local surface_name = get_testing_surface_name(player)
60
61  local surface = game.get_surface(surface_name)
62  if not surface then
63    surface = game.create_surface(surface_name, empty_map_gen_settings)
64    for force_name, force in pairs(game.forces) do
65      if not string.find(force_name, "EE_TESTFORCE_") then
66        force.set_surface_hidden(surface, true)
67      end
68    end
69    player.force.set_surface_hidden(surface, true)
70    if not surface then
71      player.print("Could not create test surface")
72      return
73    end
74    -- Lab conditions
75    surface.generate_with_lab_tiles = true
76    surface.freeze_daytime = true
77    surface.show_clouds = false
78    surface.daytime = 0
79    surface.ignore_surface_conditions = true
80    -- Warn the player about the passage of time
81    player.print({ "message.ee-time-passes-in-lab" })
82  end
83
84  local force = game.forces[force_name]
85  if not game.forces[force_name] then
86    if #game.forces == 64 then
87      player.print(
88        "Cannot create a testing lab force. Factorio only supports up to 64 forces at once. Please use a shared lab."
89      )
90      return
91    end
92
93    force = game.create_force(force_name)
94    if settings.global["ee-testing-lab-match-research"].value then
95      -- Sync research techs with the parent force
96      for name, tech in pairs(player.force.technologies) do
97        force.technologies[name].researched = tech.researched
98      end
99    else
100      force.research_all_technologies()
101    end
102  end
103
104  return {
105    lab = { force = force, position = { x = 0, y = 0 }, surface = surface },
106    normal = { force = player.force, position = player.position, surface = player.surface },
107    player = player,
108  }
109end
110
111--- @param player LuaPlayer
112--- @param to_state StateData
113local function transfer_player(player, to_state)
114  if not to_state.force.valid or not to_state.surface.valid then
115    return
116  end
117
118  -- Change force first to avoid spilling items into the real world on inventory size change - see #143
119  player.force = to_state.force
120  player.teleport(to_state.position, to_state.surface)
121end
122
123--- @param player LuaPlayer
124local function enter_lab(player)
125  local lab_state = storage.testing_lab_state[player.index]
126  if not lab_state then
127    return
128  end
129
130  local normal_data = lab_state.normal
131  normal_data.force = player.force --[[@as LuaForce]]
132  normal_data.position = player.position
133  normal_data.surface = player.surface
134
135  transfer_player(player, lab_state.lab)
136end
137
138--- @param lab_state LabState
139local function exit_lab(lab_state)
140  local lab_data = lab_state.lab
141  if lab_state.player.surface == lab_data.surface then
142    lab_data.position = lab_state.player.position
143  end
144
145  transfer_player(lab_state.player, lab_state.normal)
146end
147
148--- @param player LuaPlayer
149--- @return LabSetting
150local function get_lab_setting(player)
151  return player.mod_settings["ee-testing-lab"].value --[[@as LabSetting]]
152end
153
154--- @param e EventData.on_pre_player_toggled_map_editor
155local function on_pre_player_toggled_map_editor(e)
156  local player = game.get_player(e.player_index)
157  if not player then
158    return
159  end
160
161  local lab_setting = get_lab_setting(player)
162  if lab_setting == "off" then
163    return
164  end
165
166  local in_editor = player.physical_controller_type == defines.controllers.editor
167  local lab_state = storage.testing_lab_state[e.player_index]
168  if not lab_state and not in_editor then
169    lab_state = create_lab(player)
170    storage.testing_lab_state[e.player_index] = lab_state
171  end
172  if not lab_state then
173    return
174  end
175
176  local current_state = in_editor and lab_state.lab or lab_state.normal
177  current_state.vehicle = player.vehicle
178  current_state.is_driver = player.driving
179
180  if in_editor then
181    exit_lab(lab_state)
182  end
183end
184
185--- @param player LuaPlayer
186local function sync_vehicle_state(player)
187  local lab_state = storage.testing_lab_state[player.index]
188  if not lab_state then
189    return
190  end
191
192  local in_editor = player.physical_controller_type == defines.controllers.editor
193  local new_state = in_editor and lab_state.lab or lab_state.normal
194
195  local vehicle = new_state.vehicle
196  if not vehicle or not vehicle.valid then
197    return
198  end
199
200  if new_state.is_driver and not vehicle.get_driver() then
201    new_state.vehicle.set_driver(player)
202  elseif not new_state.is_driver and not vehicle.get_passenger() then
203    new_state.vehicle.set_passenger(player)
204  end
205end
206
207--- @param e EventData.on_player_toggled_map_editor
208local function on_player_toggled_map_editor(e)
209  local player = game.get_player(e.player_index)
210  if not player then
211    return
212  end
213
214  if get_lab_setting(player) == "off" then
215    return
216  end
217
218  if player.physical_controller_type == defines.controllers.editor then
219    enter_lab(player)
220  end
221
222  sync_vehicle_state(player)
223end
224
225--- @param e EventData.on_force_reset
226local function on_force_reset(e)
227  local parent_force = e.force
228  if string.find(parent_force.name, "EE_TESTFORCE_") then
229    return
230  end
231  if not settings.global["ee-testing-lab-match-research"].value then
232    return
233  end
234
235  local testing_force = game.forces["EE_TESTFORCE_" .. parent_force.name]
236  if not testing_force then
237    return
238  end
239
240  for name, tech in pairs(parent_force.technologies) do
241    testing_force.technologies[name].researched = tech.researched
242  end
243  testing_force.reset_technology_effects()
244end
245
246--- @param e EventData.on_research_reversed
247local function on_research_reversed(e)
248  local parent_force = e.research.force
249  if string.find(parent_force.name, "EE_TESTFORCE_") then
250    return
251  end
252  if not settings.global["ee-testing-lab-match-research"].value then
253    return
254  end
255
256  local testing_force = game.forces["EE_TESTFORCE_" .. parent_force.name]
257  if testing_force then
258    testing_force.technologies[e.research.name].researched = false
259  end
260
261  for i in pairs(parent_force.players) do
262    local testing_force = game.forces["EE_TESTFORCE_" .. i]
263    if testing_force then
264      testing_force.technologies[e.research.name].researched = false
265    end
266  end
267end
268
269--- @param e EventData.on_research_finished
270local function on_research_finished(e)
271  local parent_force = e.research.force
272  if string.find(parent_force.name, "EE_TESTFORCE_") then
273    return
274  end
275  if not settings.global["ee-testing-lab-match-research"].value then
276    return
277  end
278
279  local testing_force = game.forces["EE_TESTFORCE_" .. parent_force.name]
280  if testing_force then
281    testing_force.technologies[e.research.name].researched = true
282  end
283
284  for i in pairs(parent_force.players) do
285    local testing_force = game.forces["EE_TESTFORCE_" .. i]
286    if testing_force then
287      testing_force.technologies[e.research.name].researched = true
288    end
289  end
290end
291
292local function on_match_research_setting_changed()
293  for _, force in pairs(game.forces) do
294    local _, _, force_key = string.find(force.name, "EE_TESTFORCE_(.*)")
295    if not force_key then
296      goto continue
297    end
298    if not settings.global["ee-testing-lab-match-research"].value then
299      force.research_all_technologies()
300      goto continue
301    end
302
303    local parent_force
304    local force_key_num = tonumber(force_key) --- @cast force_key_num uint?
305    if force_key_num then
306      local player = game.get_player(force_key_num) --[[@as LuaPlayer]]
307      parent_force = remote.call("EditorExtensions", "get_player_proper_force", player)
308    else
309      parent_force = game.forces[force_key]
310    end
311    if parent_force then
312      -- Sync research techs with the parent force
313      for name, tech in pairs(parent_force.technologies) do
314        force.technologies[name].researched = tech.researched
315      end
316    end
317    force.reset_technology_effects()
318    ::continue::
319  end
320end
321
322--- @param e EventData.on_runtime_mod_setting_changed
323local function on_testing_lab_setting_changed(e)
324  local lab_state = storage.testing_lab_state[e.player_index]
325  if lab_state then
326    lab_state.refresh = true
327  end
328end
329
330--- @param e EventData.on_runtime_mod_setting_changed
331local function on_runtime_mod_setting_changed(e)
332  if e.setting == "ee-testing-lab-match-research" then
333    on_match_research_setting_changed()
334  elseif e.setting == "ee-testing-lab" then
335    on_testing_lab_setting_changed(e)
336  end
337end
338
339local testing_lab = {}
340
341testing_lab.on_init = function()
342  --- @type table<uint, LabState?>
343  storage.testing_lab_state = {}
344end
345
346function testing_lab.on_configuration_changed()
347  for player_index, state in pairs(storage.testing_lab_state) do
348    if not state.player.valid then
349      storage.testing_lab_state[player_index] = nil
350      goto continue
351    end
352
353    if not state.normal.force.valid then
354      state.normal.force = game.forces.player
355    end
356    if not state.normal.surface.valid then
357      state.normal.surface = game.surfaces.nauvis
358    end
359    if not state.lab.force.valid then
360      local testing_force = game.forces[get_testing_force_name(state.player)]
361      if not testing_force then
362        storage.testing_lab_state[player_index] = nil
363        goto continue
364      end
365      state.lab.force = testing_force
366    end
367    if not state.lab.surface.valid then
368      local testing_surface = game.surfaces[get_testing_surface_name(state.player)]
369      if not testing_surface then
370        storage.testing_lab_state[player_index] = nil
371        goto continue
372      end
373      state.lab.surface = testing_surface
374    end
375
376    ::continue::
377  end
378end
379
380testing_lab.events = {
381  [defines.events.on_force_reset] = on_force_reset,
382  [defines.events.on_player_toggled_map_editor] = on_player_toggled_map_editor,
383  [defines.events.on_pre_player_toggled_map_editor] = on_pre_player_toggled_map_editor,
384  [defines.events.on_research_finished] = on_research_finished,
385  [defines.events.on_research_reversed] = on_research_reversed,
386  [defines.events.on_runtime_mod_setting_changed] = on_runtime_mod_setting_changed,
387}
388
389testing_lab.add_remote_interface = function()
390  remote.add_interface("EditorExtensions", {
391    --- Get the force that the player is actually on, ignoring the testing lab force.
392    --- @param player LuaPlayer
393    --- @return ForceID
394    get_player_proper_force = function(player)
395      if not player or not player.valid then
396        error("Did not pass a valid LuaPlayer")
397      end
398      local in_editor = player.physical_controller_type == defines.controllers.editor
399      local ts_setting = player.mod_settings["ee-testing-lab"].value
400      if
401        ts_setting == "off"
402        or not in_editor
403        or not storage.testing_lab_state
404        or not storage.testing_lab_state[player.index]
405      then
406        return player.force
407      end
408
409      return storage.testing_lab_state[player.index].normal.force
410    end,
411  })
412end
413
414return testing_lab