z_mirror/Tapeline

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

Caleb HeuerRevert "Fix game freezing when having a divisor size of 0"828e4a0

revert-47-freeze-fix
16.9 KiB558 linesraw
1--- @class Tape
2--- @field anchor MapPosition
3--- @field box BoundingBox
4--- @field cursor MapPosition
5--- @field drawing boolean
6--- @field editing boolean
7--- @field editing_box LuaEntity?
8--- @field move_drag_anchor MapPosition?
9--- @field tick_to_die MapTick
10--- @field settings TapeSettings
11--- @field id integer
12--- @field player LuaPlayer
13--- @field player_index uint
14--- @field surface LuaSurface
15--- @field background LuaRenderObject
16--- @field border LuaRenderObject
17--- @field label_north LuaRenderObject
18--- @field label_west LuaRenderObject
19--- @field lines LuaRenderObject[]
20
21local flib_bounding_box = require("__flib__.bounding-box")
22local flib_position = require("__flib__.position")
23local flib_table = require("__flib__.table")
24
25local tool = require("scripts.tool")
26
27--- @param player LuaPlayer
28--- @param position MapPosition
29--- @param include_temporary_tapes boolean
30local function get_tape_at_position(player, position, include_temporary_tapes)
31  --- @type Tape?
32  local result
33  for _, stored_tape in pairs(storage.tapes) do
34    if
35      (include_temporary_tapes or stored_tape.tick_to_die == math.huge)
36      and stored_tape.player == player
37      and stored_tape.surface == player.surface
38      and flib_bounding_box.contains_position(stored_tape.box, position)
39      and (not result or result.id < stored_tape.id)
40    then
41      result = stored_tape
42    end
43  end
44  return result
45end
46
47--- @param player LuaPlayer
48--- @param position MapPosition
49--- @param surface LuaSurface
50--- @return Tape
51local function new_tape(player, position, surface)
52  local id = storage.next_tape_id
53  storage.next_tape_id = id + 1
54  local box = flib_bounding_box.from_position(position, true)
55  local center = flib_bounding_box.center(box)
56  local width = flib_bounding_box.width(box)
57  local height = flib_bounding_box.height(box)
58
59  --- @type Tape
60  local self = {
61    anchor = position,
62    box = box,
63    cursor = position,
64    drawing = true,
65    editing = false,
66    id = id,
67    player = player,
68    player_index = player.index,
69    tick_to_die = math.huge,
70    settings = flib_table.deep_copy(storage.player_settings[player.index]),
71    surface = surface,
72    background = rendering.draw_rectangle({
73      color = player.mod_settings["tl-tape-background-color"].value --[[@as Color]],
74      filled = true,
75      players = { player },
76      surface = surface,
77      left_top = box.left_top,
78      right_bottom = box.right_bottom,
79      draw_on_ground = player.mod_settings["tl-draw-tape-on-ground"].value --[[@as boolean]],
80    }),
81    border = rendering.draw_rectangle({
82      color = player.mod_settings["tl-tape-border-color"].value --[[@as Color]],
83      filled = false,
84      width = player.mod_settings["tl-tape-line-width"].value --[[@as double]],
85      players = { player },
86      surface = surface,
87      left_top = box.left_top,
88      right_bottom = box.right_bottom,
89      draw_on_ground = player.mod_settings["tl-draw-tape-on-ground"].value --[[@as boolean]],
90    }),
91    label_north = rendering.draw_text({
92      text = tostring(width),
93      surface = surface,
94      target = { x = center.x, y = box.left_top.y },
95      color = player.mod_settings["tl-tape-label-color"].value --[[@as Color]],
96      scale = 2,
97      alignment = "center",
98      vertical_alignment = "bottom",
99      visible = width > 1,
100      players = { player },
101    }),
102    label_west = rendering.draw_text({
103      text = tostring(height),
104      surface = surface,
105      target = { x = box.left_top.x, y = center.y },
106      color = player.mod_settings["tl-tape-label-color"].value --[[@as Color]],
107      scale = 2,
108      alignment = "center",
109      vertical_alignment = "bottom",
110      orientation = 0.75,
111      visible = height > 1,
112      players = { player },
113    }),
114    lines = {},
115  }
116  storage.tapes[id] = self
117  tool.set(self.player)
118  return self
119end
120
121--- @param self Tape
122local function update_tape(self)
123  local line_width = self.player.mod_settings["tl-tape-line-width"].value --[[@as double]]
124
125  local box = self.box
126  local draw_on_ground = self.player.mod_settings["tl-draw-tape-on-ground"].value --[[@as boolean]]
127  self.background.color = self.player.mod_settings["tl-tape-background-color"].value --[[@as Color]]
128  self.background.left_top = box.left_top
129  self.background.right_bottom = box.right_bottom
130  self.background.draw_on_ground = draw_on_ground
131  self.border.color = self.player.mod_settings["tl-tape-border-color"].value --[[@as Color]]
132  self.border.left_top = box.left_top
133  self.border.right_bottom = box.right_bottom
134  self.border.draw_on_ground = draw_on_ground
135  self.border.width = line_width
136  local center = flib_bounding_box.center(box)
137  self.label_north.color = self.player.mod_settings["tl-tape-label-color"].value --[[@as Color]]
138  self.label_north.target = { x = center.x, y = box.left_top.y }
139  local width = flib_bounding_box.width(box)
140  self.label_north.text = tostring(width)
141  self.label_north.visible = width > 1
142  local height = flib_bounding_box.height(box)
143  self.label_west.color = self.player.mod_settings["tl-tape-label-color"].value --[[@as Color]]
144  self.label_west.target = { x = box.left_top.x, y = center.y }
145  self.label_west.text = height
146  self.label_west.visible = height > 1
147
148  local lines = self.lines
149  local i = 0
150
151  --- @param color Color
152  --- @param step_x integer
153  --- @param step_y integer?
154  local function draw_lines(color, step_x, step_y)
155    step_y = step_y or step_x
156    local from_x = self.anchor.x <= center.x and box.left_top.x or box.right_bottom.x
157    local from_y = self.anchor.y <= center.y and box.left_top.y or box.right_bottom.y
158    local to_x = self.anchor.x > center.x and box.left_top.x or box.right_bottom.x
159    local to_y = self.anchor.y > center.y and box.left_top.y or box.right_bottom.y
160
161    if flib_bounding_box.width(self.box) > 1 then
162      step_x = from_x <= to_x and step_x or -step_x
163      for x = from_x + step_x, to_x, step_x do
164        i = i + 1
165        local line = lines[i]
166        if line then
167          line.color = color
168          line.width = line_width
169          line.from = { x = x, y = from_y }
170          line.to = { x = x, y = to_y }
171          line.visible = true
172          line.draw_on_ground = draw_on_ground
173        else
174          line = rendering.draw_line({
175            color = color,
176            width = line_width,
177            from = { x = x, y = from_y },
178            to = { x = x, y = to_y },
179            surface = self.surface,
180            players = { self.player },
181            draw_on_ground = draw_on_ground,
182          })
183          lines[i] = line
184        end
185      end
186    end
187
188    if flib_bounding_box.height(self.box) > 1 then
189      step_y = from_y <= to_y and step_y or -step_y
190      for y = from_y + step_y, to_y, step_y do
191        i = i + 1
192        local line = lines[i]
193        if line then
194          line.color = color
195          line.width = line_width
196          line.from = { x = from_x, y = y }
197          line.to = { x = to_x, y = y }
198          line.visible = true
199          line.draw_on_ground = draw_on_ground
200        else
201          line = rendering.draw_line({
202            color = color,
203            width = line_width,
204            from = { x = from_x, y = y },
205            to = { x = to_x, y = y },
206            surface = self.surface,
207            players = { self.player },
208            draw_on_ground = draw_on_ground,
209          })
210          lines[i] = line
211        end
212      end
213    end
214  end
215
216  draw_lines(self.player.mod_settings["tl-tape-line-color-1"].value --[[@as Color]], 1)
217
218  if self.settings.mode == "subgrid" then
219    draw_lines(self.player.mod_settings["tl-tape-line-color-2"].value --[[@as Color]], self.settings.subgrid_size)
220    draw_lines(self.player.mod_settings["tl-tape-line-color-3"].value --[[@as Color]], self.settings.subgrid_size ^ 2)
221    draw_lines(self.player.mod_settings["tl-tape-line-color-4"].value --[[@as Color]], self.settings.subgrid_size ^ 3)
222  else
223    draw_lines(
224      self.player.mod_settings["tl-tape-line-color-2"].value --[[@as Color]],
225      flib_bounding_box.width(self.box) / self.settings.splits,
226      flib_bounding_box.height(self.box) / self.settings.splits
227    )
228    draw_lines(
229      self.player.mod_settings["tl-tape-line-color-3"].value --[[@as Color]],
230      flib_bounding_box.width(self.box) / 2,
231      flib_bounding_box.height(self.box) / 2
232    )
233  end
234
235  for j = i + 1, #lines do
236    lines[j].visible = false
237  end
238
239  self.border.bring_to_front()
240  if self.editing_box then
241    self.editing_box.destroy()
242  end
243  if self.editing then
244    self.editing_box = self.surface.create_entity({
245      name = "tl-highlight-box",
246      position = flib_bounding_box.center(self.box),
247      bounding_box = flib_bounding_box.resize(self.box, 0.3),
248      cursor_box_type = "electricity",
249      render_player_index = self.player_index,
250      blink_interval = 30,
251    })
252  end
253
254  tool.set(self.player, self)
255end
256
257--- @param self Tape
258--- @param position MapPosition
259--- @param constrained boolean
260local function resize_tape(self, position, constrained)
261  if constrained then
262    local delta = flib_position.abs(flib_position.sub(self.anchor, position))
263    if delta.x > delta.y then
264      position.y = self.anchor.y
265    else
266      position.x = self.anchor.x
267    end
268  end
269  local box = flib_bounding_box.from_position(self.anchor, true)
270  box = flib_bounding_box.expand_to_contain_position(box, position)
271  box = flib_bounding_box.ceil(box)
272  self.box = box
273  self.cursor = position
274  update_tape(self)
275end
276
277--- @param self Tape
278--- @param position MapPosition
279local function move_tape(self, position)
280  if not self.move_drag_anchor then
281    if flib_bounding_box.contains_position(self.box, position) then
282      self.move_drag_anchor = position
283    end
284    return
285  end
286  local delta = flib_position.sub(position, self.move_drag_anchor --[[@as MapPosition]])
287  self.anchor = flib_position.add(self.anchor, delta)
288  self.box = flib_bounding_box.move(self.box, delta)
289  self.move_drag_anchor = position
290  update_tape(self)
291end
292
293--- @param self Tape
294local function destroy_tape(self)
295  if self.background.valid then
296    self.background.destroy()
297  end
298  if self.border.valid then
299    self.border.destroy()
300  end
301  if self.label_north.valid then
302    self.label_north.destroy()
303  end
304  if self.label_west.valid then
305    self.label_west.destroy()
306  end
307  for _, line in pairs(self.lines) do
308    if line.valid then
309      line.destroy()
310    end
311  end
312
313  if self.editing_box and self.editing_box.valid then
314    self.editing_box.destroy()
315  end
316
317  if self.editing then
318    storage.editing[self.player_index] = nil
319  elseif self.drawing then
320    storage.drawing[self.player_index] = nil
321  else
322    storage.tapes[self.id] = nil
323  end
324end
325
326--- @param e EventData.on_built_entity
327local function on_built_entity(e)
328  local entity = e.entity
329  if not entity.valid then
330    return
331  end
332  local name = entity.name
333  if name == "entity-ghost" then
334    name = entity.ghost_name
335  end
336  if name ~= "tl-dummy-entity" then
337    return
338  end
339  local is_ghost = entity.name == "entity-ghost"
340  local position, surface = entity.position, entity.surface
341  entity.destroy()
342  local last_position = storage.last_position[e.player_index]
343  if last_position and flib_position.eq(position, last_position.position) and surface == last_position.surface then
344    return
345  end
346  storage.last_position[e.player_index] = { position = position, surface = surface }
347
348  local should_cancel = last_position and surface ~= last_position.surface
349
350  local editing = storage.editing[e.player_index]
351  if editing then
352    if should_cancel then
353      destroy_tape(editing)
354      return
355    end
356    move_tape(editing, position)
357    return
358  end
359  local drawing = storage.drawing[e.player_index]
360  local player = game.get_player(e.player_index) --[[@as LuaPlayer]]
361  if drawing then
362    if should_cancel then
363      destroy_tape(drawing)
364      return
365    end
366    resize_tape(drawing, position, not is_ghost)
367  else
368    drawing = new_tape(player, position, surface)
369    storage.drawing[e.player_index] = drawing
370  end
371end
372
373--- @param e EventData.on_player_selected_area|EventData.on_player_alt_selected_area
374local function on_player_selected_area(e)
375  local editing_tape = storage.editing[e.player_index]
376  if editing_tape then
377    editing_tape.move_drag_anchor = nil
378    return
379  end
380  local drawing_tape = storage.drawing[e.player_index]
381  if not drawing_tape then
382    return
383  end
384  storage.drawing[e.player_index] = nil
385  drawing_tape.drawing = false
386  if e.name == defines.events.on_player_selected_area then
387    local time_to_live = drawing_tape.player.mod_settings["tl-tape-clear-delay"].value --[[@as double]]
388    drawing_tape.tick_to_die = game.tick + time_to_live * 60
389  end
390  storage.tapes[drawing_tape.id] = drawing_tape
391end
392
393--- @param e EventData.on_tick
394local function on_tick(e)
395  for _, tape_data in pairs(storage.tapes or {}) do
396    if tape_data.tick_to_die > e.tick then
397      goto continue
398    end
399
400    destroy_tape(tape_data)
401
402    ::continue::
403  end
404end
405
406--- @param e EventData.CustomInputEvent
407local function on_edit_tape(e)
408  if storage.editing[e.player_index] then
409    return
410  end
411  local player = game.get_player(e.player_index) --[[@as LuaPlayer]]
412  local tape = get_tape_at_position(player, e.cursor_position, false)
413  if not tape then
414    return
415  end
416  tape.editing = true
417  storage.editing[e.player_index] = tape
418  storage.tapes[tape.id] = nil
419  update_tape(tape)
420end
421
422--- @param e EventData.CustomInputEvent
423local function on_delete_tape(e)
424  if storage.drawing[e.player_index] then
425    return
426  end
427
428  local player = game.get_player(e.player_index) --[[@as LuaPlayer]]
429
430  local editing = storage.editing[e.player_index]
431  if editing and flib_bounding_box.contains_position(editing.box, e.cursor_position) then
432    destroy_tape(editing)
433    return
434  end
435
436  local tape = get_tape_at_position(player, e.cursor_position, true)
437  if tape then
438    destroy_tape(tape)
439  end
440end
441
442--- @param e EventData.CustomInputEvent
443local function on_clear_cursor(e)
444  local tape = storage.editing[e.player_index]
445  if not tape then
446    return
447  end
448  tape.editing = false
449  storage.editing[e.player_index] = nil
450  storage.tapes[tape.id] = tape
451  update_tape(tape)
452end
453
454--- @param e EventData.CustomInputEvent
455local function on_change_mode(e)
456  local tape = storage.editing[e.player_index]
457  if tape then
458    tape.settings.mode = tape.settings.mode == "subgrid" and "split" or "subgrid"
459    update_tape(tape)
460    return
461  end
462  local settings = storage.player_settings[e.player_index]
463  settings.mode = settings.mode == "subgrid" and "split" or "subgrid"
464  local player = game.get_player(e.player_index)
465  --- @cast player -?
466  tool.set(player)
467end
468
469--- @param e EventData.CustomInputEvent
470local function on_change_divisor(e)
471  local delta = e.input_name == "tl-increase-divisor" and 1 or -1
472  local tape = storage.editing[e.player_index]
473  local settings = tape and tape.settings or storage.player_settings[e.player_index]
474  if not settings then
475    return
476  end
477  if settings.mode == "subgrid" then
478    settings.subgrid_size = math.max(0, settings.subgrid_size + delta)
479  else
480    settings.splits = math.max(2, settings.splits + delta)
481  end
482  if tape then
483    update_tape(tape)
484  else
485    local player = game.get_player(e.player_index)
486    --- @cast player -?
487    tool.set(player)
488  end
489end
490
491--- @param e EventData.on_runtime_mod_setting_changed
492local function on_runtime_mod_setting_changed(e)
493  if not string.find(e.setting, "^tl%-") then
494    return
495  end
496  local editing = storage.editing[e.player_index]
497  if editing then
498    update_tape(editing)
499  end
500  local drawing = storage.drawing[e.player_index]
501  if drawing then
502    update_tape(drawing)
503  end
504  for _, tape in pairs(storage.tapes) do
505    if tape.player.index == e.player_index then
506      update_tape(tape)
507    end
508  end
509end
510
511--- @param e EventData.on_player_removed
512local function on_player_removed(e)
513  local editing = storage.editing[e.player_index]
514  if editing then
515    destroy_tape(editing)
516  end
517  local drawing = storage.drawing[e.player_index]
518  if drawing then
519    destroy_tape(drawing)
520  end
521  for _, tape in pairs(storage.tapes) do
522    if tape.player_index == e.player_index then
523      destroy_tape(tape)
524    end
525  end
526end
527
528local tape = {}
529
530function tape.on_init()
531  --- @type table<uint, Tape>
532  storage.editing = {}
533  --- @type table<uint, Tape>
534  storage.drawing = {}
535  --- @type table<uint, {position: MapPosition, surface: LuaSurface}>
536  storage.last_position = {}
537  storage.next_tape_id = 1
538  --- @type table<uint, Tape>
539  storage.tapes = {}
540end
541
542tape.events = {
543  [defines.events.on_built_entity] = on_built_entity,
544  [defines.events.on_player_alt_selected_area] = on_player_selected_area,
545  [defines.events.on_player_selected_area] = on_player_selected_area,
546  [defines.events.on_tick] = on_tick,
547  ["tl-delete-tape"] = on_delete_tape,
548  ["tl-edit-tape"] = on_edit_tape,
549  ["tl-linked-clear-cursor"] = on_clear_cursor,
550  ["tl-next-mode"] = on_change_mode,
551  ["tl-previous-mode"] = on_change_mode,
552  ["tl-increase-divisor"] = on_change_divisor,
553  ["tl-decrease-divisor"] = on_change_divisor,
554  [defines.events.on_runtime_mod_setting_changed] = on_runtime_mod_setting_changed,
555  [defines.events.on_player_removed] = on_player_removed,
556}
557
558return tape