/ src / inventory.lua
inventory.lua
  1  --[[
  2      ToasterGen Spin
  3  
  4      Copyright (C) 2025 Clifton Toaster Reid <cliftontreid@duck.com>
  5  
  6      This library is free software: you can redistribute it and/or modify
  7      it under the terms of the GNU Lesser General Public License as published by
  8      the Free Software Foundation, either version 3 of the License, or
  9      (at your option) any later version.
 10  ]]
 11  
 12  ---@class IvMnager
 13  ---@field addItemToPlayer fun(direction: "top"|"bottom"|"left"|"right"|"front"|"back", item: table): number
 14  ---@field removeItemFromPlayer fun(direction: "top"|"bottom"|"left"|"right"|"front"|"back", item: table): number
 15  ---@field getArmor fun(): table
 16  ---@field getOwner fun(): string
 17  ---@field isPlayerEquipped fun(): boolean
 18  ---@field isWearing fun(slot: number): boolean
 19  ---@field getItemInHand fun(): table
 20  ---@field getItemInOffHand fun(): table
 21  ---@field getFreeSlot fun(): number
 22  ---@field isSpaceAvailable fun(): boolean
 23  ---@field getEmptySpace fun(): number
 24  
 25  local money = "numismatics:sprocket"
 26  local invside = "bottom"
 27  
 28  local Logger = require("src.log")
 29  local Tracer = require("src.trace")
 30  
 31  ---@type table<number, IvMnager>
 32  local invManagers = {}
 33  
 34  local inv = {}
 35  
 36  ---Gets the amount of money (emeralds) a player has in their off-hand
 37  ---@param idx number The player index in invManagers
 38  ---@param parentId string|nil The parent trace ID for logging
 39  ---@return number|nil The count of money items, or nil if player not found, no item in off-hand, or item is not money
 40  local function getMoneyInPlayer(idx, parentId)
 41  	local tr = Tracer.new()
 42  	tr:setName("inventory.getMoneyInPlayer")
 43  	tr:addTag("idx", string.format("%d", idx))
 44  	if parentId then
 45  		tr:setParentId(parentId)
 46  	end
 47  
 48  	local player = invManagers[idx + 1]
 49  	if not player then
 50  		Logger.debug("Player %d not found", idx)
 51  		return nil
 52  	end
 53  
 54  	local success, item = pcall(function()
 55  		local ptr = Tracer.new()
 56  		ptr:setName("getItemInOffHand")
 57  		ptr:addTag("idx", string.format("%d", idx))
 58  		ptr:setParentId(tr.traceId)
 59  
 60  		local hand = player:getItemInOffHand()
 61  
 62  		ptr:addAnnotation(hand.name or "nil")
 63  		Tracer.addSpan(ptr:endSpan())
 64  		return hand
 65  	end)
 66  
 67  	---@type number | nil
 68  	local amount = item.count or 0
 69  
 70  	if not success or not item then
 71  		Logger.debug("No item in off-hand for player %d", idx)
 72  		tr:addAnnotation("No item in off-hand")
 73  		amount = nil
 74  	end
 75  	if item.name ~= money then
 76  		Logger.debug("Item in off-hand is not money: %s", item.name)
 77  		tr:addAnnotation("Item in off-hand is not money")
 78  		amount = nil
 79  	end
 80  	Tracer.addSpan(tr:endSpan())
 81  	return amount
 82  end
 83  
 84  ---Removes a specified amount of money from a player
 85  ---@param idx number The player index in invManagers
 86  ---@param amount number The amount of money to take from the player
 87  ---@param parentId string|nil The parent trace ID for logging
 88  ---@return number|nil The amount taken if successful, nil if player not found or has insufficient funds
 89  local function takeMoneyFromPlayer(idx, amount, parentId)
 90  	local tr = Tracer.new()
 91  	tr:setName("inventory.takeMoneyFromPlayer")
 92  	tr:addTag("idx", string.format("%d", idx))
 93  	tr:addTag("amount", string.format("%d", amount))
 94  	if parentId then
 95  		tr:setParentId(parentId)
 96  	end
 97  	if amount <= 0 then
 98  		Logger.debug("Amount must be greater than 0")
 99  		tr:addAnnotation("Amount must be greater than 0")
100  		Tracer.addSpan(tr:endSpan())
101  		return nil
102  	end
103  
104  	local player = invManagers[idx + 1]
105  	if not player then
106  		tr:addAnnotation("Player not found")
107  		Logger.debug("Player %d not found", idx)
108  		Tracer.addSpan(tr:endSpan())
109  		return nil
110  	end
111  
112  	local count = getMoneyInPlayer(idx, tr.traceId) -- Pass the traceId here
113  	if not count or count < amount then
114  		tr:addAnnotation("Not enough money")
115  		Logger.debug("Not enough money in player %d: %d < %d", idx, count or 0, amount)
116  		Tracer.addSpan(tr:endSpan())
117  		return nil
118  	end
119  
120  	local success, result = pcall(function()
121  		local ptr = Tracer.new()
122  		ptr:setName("removeItemFromPlayer")
123  		ptr:addTag("idx", string.format("%d", idx))
124  		ptr:addTag("amount", string.format("%d", amount))
125  		ptr:setParentId(tr.traceId)
126  
127  		local removedCount = player.removeItemFromPlayer(invside, {
128  			name = money,
129  			count = amount,
130  		})
131  
132  		ptr:addAnnotation(string.format("Removed %d", removedCount or -1))
133  		Tracer.addSpan(ptr:endSpan())
134  		return removedCount
135  	end)
136  
137  	---@type number | nil
138  	local retres = amount
139  
140  	if not success then
141  		Logger.debug("Failed to take money from player %d", idx)
142  		tr:addAnnotation("Failed to take money")
143  		retres = nil
144  	end
145  	if result ~= amount then
146  		Logger.debug("Failed to take %d money from player %s, took %d", amount, inv.getPlayer(idx), result)
147  		tr:addAnnotation(string.format("Failed to take %d money", amount))
148  		retres = nil
149  	end
150  	Logger.debug("Successfully took %d money from player %s", amount, inv.getPlayer(idx))
151  	return retres
152  end
153  
154  ---Adds a specified amount of money to a player
155  ---@param idx number The player index in invManagers
156  ---@param amount number The amount of money to add to the player
157  ---@param parentId string|nil The parent trace ID for logging
158  ---@return number|nil The amount added if successful, nil if player not found or adding fails
159  local function addMoneyToPlayer(idx, amount, parentId)
160  	local tr = Tracer.new()
161  	tr:setName("inventory.addMoneyToPlayer")
162  	tr:addTag("idx", string.format("%d", idx))
163  	tr:addTag("amount", string.format("%d", amount))
164  	if parentId then
165  		tr:setParentId(parentId)
166  	end
167  	if amount <= 0 then
168  		Logger.debug("Amount must be greater than 0")
169  		tr:addAnnotation("Amount must be greater than 0")
170  		Tracer.addSpan(tr:endSpan())
171  		return nil
172  	end
173  
174  	local player = invManagers[idx + 1]
175  	if not player then
176  		Logger.debug("Player %d not found", idx)
177  		tr:addAnnotation("Player not found")
178  		Tracer.addSpan(tr:endSpan())
179  		return nil
180  	end
181  
182  	local success, res = pcall(function()
183  		local ptr = Tracer.new()
184  		ptr:setName("addItemToPlayer")
185  		ptr:addTag("idx", string.format("%d", idx))
186  		ptr:addTag("amount", string.format("%d", amount))
187  		ptr:setParentId(tr.traceId)
188  
189  		local gave = player.addItemToPlayer(invside, {
190  			name = money,
191  			count = amount,
192  		})
193  
194  		ptr:addAnnotation(string.format("Added %d", gave or -1))
195  		Tracer.addSpan(ptr:endSpan())
196  
197  		return gave
198  	end)
199  
200  	---@type number | nil
201  	local retres = amount -- Initialize with the intended return value
202  
203  	if not success then
204  		Logger.debug("Failed to add money to player %d", idx)
205  		tr:addAnnotation("Failed to add money")
206  		retres = nil
207  	elseif res ~= amount then
208  		Logger.debug("Added %d money to player %d, but expected %d", res or -1, idx, amount)
209  		tr:addAnnotation(string.format("Failed to add %d money, added %d", amount, res or -1))
210  		-- take back the money given
211  		local takeBackSuccess = pcall(function()
212  			local ptr = Tracer.new()
213  			ptr:setName("removeItemFromPlayer (rollback)")
214  			ptr:addTag("idx", string.format("%d", idx))
215  			ptr:addTag("amount", string.format("%d", res or -1))
216  			ptr:setParentId(tr.traceId)
217  
218  			player.removeItemFromPlayer(invside, {
219  				name = money,
220  				count = res,
221  			})
222  
223  			ptr:addAnnotation("Rollback attempted")
224  			Tracer.addSpan(ptr:endSpan())
225  		end)
226  		if not takeBackSuccess then
227  			Logger.debug("Failed to take back money from player %d", idx)
228  			tr:addAnnotation("Rollback failed")
229  		end
230  		-- Even if takeBackSuccess fails, we still return nil
231  		retres = nil
232  	else
233  		Logger.debug("Added %d money to player %d", amount, idx)
234  		tr:addAnnotation(string.format("Successfully added %d money", amount))
235  	end
236  
237  	Tracer.addSpan(tr:endSpan())
238  	return retres
239  end
240  
241  ---Gets a player's ownership information
242  ---@param idx number The player index in invManagers
243  ---@param parentId string|nil The parent trace ID for logging
244  ---@return string|nil The player owner identifier, or nil if player not found
245  local function getPlayer(idx, parentId)
246  	local tr = Tracer.new()
247  	tr:setName("inventory.getPlayer")
248  	tr:addTag("idx", string.format("%d", idx))
249  	if parentId then
250  		tr:setParentId(parentId)
251  	end
252  
253  	local player = invManagers[idx + 1]
254  	if not player then
255  		Logger.debug("Player %d not found", idx)
256  		tr:addAnnotation("Player not found")
257  		Tracer.addSpan(tr:endSpan())
258  		return nil
259  	end
260  
261  	local success, own = pcall(function()
262  		local ptr = Tracer.new()
263  		ptr:setName("getOwner")
264  		ptr:addTag("idx", string.format("%d", idx))
265  		ptr:setParentId(tr.traceId)
266  
267  		local owner = player.getOwner()
268  
269  		ptr:addAnnotation(owner or "nil")
270  		Tracer.addSpan(ptr:endSpan())
271  		return owner
272  	end)
273  
274  	if not success or not own then
275  		Logger.debug("Failed to get owner for player %d", idx)
276  		tr:addAnnotation("Failed to get owner")
277  		Tracer.addSpan(tr:endSpan())
278  		return nil
279  	end
280  
281  	tr:addAnnotation(string.format("Owner: %s", own))
282  	Tracer.addSpan(tr:endSpan())
283  	return own
284  end
285  
286  ---Finds the first iv manager that is owned by player
287  ---@param player string The player identifier
288  ---@param parentId string|nil The parent trace ID for logging
289  ---@return number|nil The index of the iv manager, or nil if not found
290  local function findPlayer(player, parentId)
291  	local tr = Tracer.new()
292  	tr:setName("inventory.findPlayer")
293  	tr:addTag("player", player)
294  	if parentId then
295  		tr:setParentId(parentId)
296  	end
297  
298  	for idx, iv in pairs(invManagers) do
299  		local success, owner = pcall(function()
300  			local ptr = Tracer.new()
301  			ptr:setName("getOwner")
302  			ptr:addTag("idx", string.format("%d", idx))
303  			ptr:setParentId(tr.traceId)
304  
305  			local own = iv.getOwner()
306  
307  			ptr:addAnnotation(own or "nil")
308  			Tracer.addSpan(ptr:endSpan())
309  			return own
310  		end)
311  		if success and owner == player then
312  			Logger.debug("Found player %s at index %d", player, idx)
313  			tr:addAnnotation(string.format("Player found at index %d", idx))
314  			Tracer.addSpan(tr:endSpan())
315  			return idx
316  		elseif not success then
317  			Logger.debug("Failed to get owner for index %d", idx)
318  			tr:addAnnotation(string.format("Failed getOwner for index %d", idx))
319  		end
320  	end
321  
322  	Logger.debug("Player %s not found", player)
323  	tr:addAnnotation("Player not found")
324  	Tracer.addSpan(tr:endSpan())
325  	return nil
326  end
327  
328  local function init(config)
329  	if config == nil then
330  		error("Config is nil")
331  		return
332  	end
333  
334  	-- assign the table<number, IvMnager> to invManagers using the config's
335  	-- table<number, string> as the key and the peripheral.wrap function to turn the
336  	-- string into a IvMnager
337  
338  	for idx, name in pairs(config) do
339  		local player = peripheral.wrap(name)
340  		if player then
341  			invManagers[idx] = player
342  		else
343  			error("Peripheral " .. name .. " not found")
344  		end
345  	end
346  end
347  
348  inv.getMoneyInPlayer = getMoneyInPlayer
349  inv.takeMoneyFromPlayer = takeMoneyFromPlayer
350  inv.addMoneyToPlayer = addMoneyToPlayer
351  inv.getPlayer = getPlayer
352  inv.findPlayer = findPlayer
353  inv.init = init
354  
355  return inv