z_mirror/EditorExtensions

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

Patrick 'Quezler' MounierMerge branch 'master' into patch-1e65a4b6

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