z_mirror/EvenDistributionLite

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

Caleb HeuerFailed simulation - can't simulate fast transfersf3a51e3

simulation
5.9 KiB213 linesraw
1do
2	-- Don't load if sim scenario has already loaded this (in another lua state)
3	local modloader = remote.interfaces["modloader"]
4	if modloader and modloader[script.mod_name] then
5		return
6	end
7end
8
9local bounding_box = require("__flib__/bounding-box")
10
11--- @class DragState
12--- @field entities table<uint, EntityData>
13--- @field item_name string
14--- @field last_tick uint
15--- @field num_entities integer
16--- @field player LuaPlayer
17
18--- @class EntityData
19--- @field count uint
20--- @field entity LuaEntity
21--- @field label uint64
22
23--- @class LastSelectedState
24--- @field cursor_count uint
25--- @field entity LuaEntity
26--- @field item_count uint
27--- @field item_name string
28--- @field tick uint
29
30--- @param player LuaPlayer
31--- @param item_name string
32--- @return uint
33local function get_item_count(player, item_name)
34	local count = 0
35	local cursor_stack = player.cursor_stack
36	if cursor_stack and cursor_stack.valid_for_read and cursor_stack.name == item_name then
37		count = count + cursor_stack.count
38	end
39	local main_inventory = player.get_main_inventory()
40	if main_inventory and main_inventory.valid then
41		count = count + main_inventory.get_item_count(item_name)
42	end
43	return count --[[@as uint]]
44end
45
46script.on_init(function()
47	--- @type table<uint, DragState>
48	global.drag = {}
49	--- @type table<uint, LastSelectedState>
50	global.last_selected = {}
51end)
52
53script.on_event(defines.events.on_selected_entity_changed, function(e)
54	local player = game.get_player(e.player_index)
55	if not player or not player.valid then
56		return
57	end
58
59	local selected = player.selected
60	local cursor_stack = player.cursor_stack
61	if not selected or not cursor_stack or not cursor_stack.valid_for_read then
62		global.last_selected[e.player_index] = nil
63		return
64	end
65
66	global.last_selected[e.player_index] = {
67		cursor_count = cursor_stack.count,
68		entity = selected,
69		item_count = get_item_count(player, cursor_stack.name),
70		item_name = cursor_stack.name,
71		tick = game.tick,
72	}
73end)
74
75script.on_event(defines.events.on_player_fast_transferred, function(e)
76	if not e.from_player then
77		return
78	end
79
80	local entity = e.entity
81	if not entity.valid then
82		return
83	end
84
85	local selected_state = global.last_selected[e.player_index]
86	if not selected_state or selected_state.tick ~= game.tick or selected_state.entity ~= entity then
87		return
88	end
89
90	local player = game.get_player(e.player_index)
91	if not player or not player.valid then
92		return
93	end
94
95	local cursor_stack = player.cursor_stack
96	if not cursor_stack then
97		return
98	end
99
100	local drag_state = global.drag[e.player_index]
101	if not drag_state then
102		--- @type DragState
103		drag_state = {
104			entities = {},
105			item_name = selected_state.item_name,
106			last_tick = game.tick,
107			num_entities = 0,
108			player = player,
109		}
110		global.drag[e.player_index] = drag_state
111	end
112
113	local new_count = get_item_count(player, drag_state.item_name)
114	local inserted = selected_state.item_count - new_count --[[@as uint]]
115	if inserted == 0 then
116		return
117	end
118
119	-- Remove items from the destination and put them back into the player
120	entity.remove_item({ name = drag_state.item_name, count = inserted })
121	local new_cursor_count = cursor_stack.valid_for_read and cursor_stack.count or 0
122	local cursor_delta = selected_state.cursor_count - new_cursor_count
123	if cursor_delta > 0 then
124		cursor_stack.set_stack({ name = drag_state.item_name, count = selected_state.cursor_count })
125	end
126	new_count = new_count + cursor_delta
127	if new_count < selected_state.item_count then
128		player.insert({ name = drag_state.item_name, count = selected_state.item_count - new_count })
129	end
130
131	drag_state.last_tick = game.tick
132
133	-- Add entity data if needed
134	local entity_data = drag_state.entities[entity.unit_number]
135	if not entity_data then
136		local label = rendering.draw_text({
137			color = { r = 1, g = 1, b = 1 },
138			only_in_alt_mode = true,
139			players = { e.player_index },
140			surface = entity.surface,
141			target = entity,
142			text = "",
143		})
144		entity_data = {
145			count = 0,
146			entity = entity,
147			label = label,
148		}
149		drag_state.entities[entity.unit_number] = entity_data
150
151		drag_state.num_entities = drag_state.num_entities + 1
152	end
153
154	-- Destroy flying text from transfer
155	local flying_text = entity.surface.find_entities_filtered({
156		name = "flying-text",
157		area = bounding_box.recenter_on(entity.prototype.selection_box, entity.position),
158	})[1]
159	if flying_text then
160		flying_text.destroy()
161	end
162
163	-- Update all item counts
164	local base = math.floor(selected_state.item_count / drag_state.num_entities) --[[@as uint]]
165	local remainder = selected_state.item_count % drag_state.num_entities
166	for _, entity_data in pairs(drag_state.entities) do
167		local count = base
168		if remainder > 0 then
169			count = count + 1
170			remainder = remainder - 1
171		end
172		entity_data.count = count
173		rendering.set_text(entity_data.label, count)
174	end
175end)
176
177--- @param data DragState
178local function finish_drag(data)
179	if not data.player.valid then
180		return
181	end
182	local item_name = data.item_name
183	local item_localised_name = game.item_prototypes[item_name].localised_name
184	for _, entity_data in pairs(data.entities) do
185		rendering.destroy(entity_data.label)
186		if entity_data.count == 0 then
187			goto continue
188		end
189		local inserted = entity_data.entity.insert({ name = item_name, count = entity_data.count })
190		local entity = entity_data.entity
191		entity.surface.create_entity({
192			name = "flying-text",
193			-- Color yellow if inventory limit was reached
194			color = inserted == entity_data.count and { r = 1, g = 1, b = 1 } or { r = 1, g = 1 },
195			position = entity.position,
196			render_player_index = data.player.index,
197			text = { "", "-", inserted, " ", item_localised_name },
198		})
199		if inserted > 0 then
200			data.player.remove_item({ name = item_name, count = inserted })
201		end
202		::continue::
203	end
204end
205
206script.on_event(defines.events.on_tick, function()
207	for player_index, drag_data in pairs(global.drag) do
208		if drag_data.last_tick + 60 <= game.tick then
209			global.drag[player_index] = nil
210			finish_drag(drag_data)
211		end
212	end
213end)