---The main file needed to register create new inventory types. -- Helps you register inventories that work with the rest of artery --@shared inventory.lua --@alias inv --[[ Public functions: RegisterInventory(tbl_inventory) ::nil Registers a new inventory prototype, see below CreateInventory(string_name) ::table_inventory Creates a new inventory be sure to set the .owner and .id fields! CreateInventoryFromData(string_name,string_data,entity_owner)::table_inventory) Just deserializes an inventory. You still need to set .owner and .id! DeriveInventory(string_name) ::table_inventory Creates a new inventory from an old, allows for heiarchy. Inventories have the following structure field returns inv.Name ::string The name! inv:FindPlaceFor(item) ::table_position or nil Finds a place for the item inv:CanFitIn(table_position,item) ::boolean Check if the item can fit in the position inv:Put(table_position,item) ::nil Put an item in at the position inv:Has(string_or_compare_func) ::table_position or nil find an item in the inventory inv:Remove(position) ::table_item Remove an item from the position inv:Get(position) ::table_item Get the item at a position inv:Serialize() ::string Serialize the item to store it in a db inv:DeSerialize(str) ::table_inventory recreate the item from data in serialize The above fields must be defined for new inventories. ----------------------------------------------------- The below are automatically made if they do not exist. inv:AddObserver(tbl_other) ::number_id Whenever put or remove is called on this inventory, tbl_other's put() and remove() is also called, for easy networking to whoever needs it inv:RemoveObserver(number_id) ::nil Removes an observer from the inventory ------------------------------------------------------ These fields should be defined when an inventory is created, before it can be used inv.Owner ::entity inv.id ::number The table used for "position" is not defined, and may vary from inventory to inventory, but a single inventory type should only use a single type of position. The AddObserver will call tbl_other's Put() and Remove() methods(same arguments as the inventory) when this inventories Put() and Remove() methods are called. This will hopefully make it easy to have multiple players viewing the same inventory, and all get updates. Serialize() should take this inventories contents and return a string that it can recreate this inventory from. DeSerialize should create a self.Copy() with the appropriate fields set. Take advantage of the fact that items must also have Serialize() and DeSerialize() methods. ]] local inv = {} local log = nrequire("log.lua") --Creates a partial copy of a table(tables are copied, functions are not) local function TableCopy(tbl) local ntbl = {} for k,v in pairs(tbl) do if type(v) == "table" then ntbl[k] = TableCopy(v) else ntbl[k] = v end end return ntbl end --Holds different types of inventories to be made local inventories = {} --Master list local function DefaultAddObserver(self,tbl) assert(tbl ~= nil,"Cannot add a nil observer!") for k,v in pairs({"Put","Remove"}) do assert(tbl[v] ~= nil,"Cannot add an observer without a " .. v .. "! observer was:\n\t" .. table.concat(table.GetKeys(tbl),"\n\t")) end if self.observers == nil then self.observers = {} end self.observers[#self.observers + 1] = tbl return #self.observers end local function DefaultRemoveObserver(self,observer_id) for i = observer_id, #self.observers do self.observers[i] = self.observers[i + 1] end end local function SetDefaultObservers(tbl) log.debug("Setting default observers on ", tbl.Name) tbl.AddObserver = DefaultAddObserver tbl.RemoveObserver = DefaultRemoveObserver --Call the observer's puts as well as this inventory's put. local oldput,oldremove = tbl.Put,tbl.Remove tbl.Put = function(self,position,item) for k,v in pairs(self.observers) do v:Put(position,item) end return oldput(self,position,item) end tbl.Remove = function(self,position) for k,v in pairs(self.observers) do v:Remove(position) end return oldremove(self,position) end end local required_fields = { {"Name","string"}, {"FindPlaceFor","function"}, {"CanFitIn","function"}, {"Put","function"}, {"Has","function"}, {"Remove","function"}, {"Get","function"}, {"Serialize","function"}, {"DeSerialize","function"}, } ---Registers a new inventory. -- Register a new inventory prototype from the table passed in --@see invtbl --@tparam invtbl tbl The table inventory function inv.RegisterInventory(tbl) assert(type(tbl) == "table", "Attempted to register inventory that was not a table") for k,v in pairs(required_fields) do assert(tbl[v[1]] ~= nil, string.format("Attempted to register inventory without field %q",v[1])) assert(type(tbl[v[1]]) == v[2], string.format("Attempted to register inventory with field %q of type %q when it should have been %q",v[1],type(tbl[v[1]]),v[2])) end --assert(inventories[tbl.Name] == nil, -- string.format("Attempted to register 2 inventories with the same name: %q", tbl.Name)) if inventories[tbl.Name] ~= nil then log.warn(string.format("Registered 2 inventories with the same name %q, overwriteing",tbl.Name)) end assert((tbl.AddObserver == nil and tbl.RemoveObserver == nil) or (tbl.AddObserver ~= nil and tbl.RemoveObserver ~= nil), "AddObserver and RemoveObserver must be defined in pairs") if tbl.AddObserver == nil then SetDefaultObservers(tbl) end inventories[tbl.Name] = tbl log.info("Registered inventory: " .. tbl.Name) --print("Registered inventory: " .. tbl.Name) end ---Create an inventory. -- Creates a new inventory that you can give to an item. -- Consider carefully if you want to use this method, or the CreateInventoryFromData method. --@see invtbl --@tparam string name The name of the inventory type when it was registered function inv.CreateInventory(name,initstr) --print("Createing inventory", name) initstr = initstr or "" assert(inventories[name] ~= nil, string.format("Tried to create a copy of inventory that does not exist:%s\nValid inventories are:\n\t%s",name,table.concat(table.GetKeys(inventories),"\n\t"))) local ret_m = { __index = inventories[name] } local ret = {} setmetatable(ret,ret_m) ret:DeSerialize(initstr) ret.observers = {} return ret end ---Creates an inventory from data. -- This is what you useually want to use when createing inventories --@tparam string name The name of the inventory type you want to instanciate --@tparam string data The data the inventory needs to deserialize, most inventories will work if this is empty --@tparam entity owner The owning entity of this inventory, does not have to be a player (ex when createing crates players can put items into) function inv.CreateInventoryFromData(name,data,owner) local tinv = inv.CreateInventory(name) tinv.Owner = owner --print("tinv was", tinv) --PrintTable(tinv) local ret = tinv:DeSerialize(data) print("is now",ret) PrintTable(ret) assert(ret != nil, "Failed to create inventory " .. name .. ", returned nil") assert(ret.Owner != nil, "Creating inventory with no owner!") assert(ret.Owner:IsValid(), "Owner of inventory was not valid!") assert(ret.Owner != Entity(0), "Owner was worldspawn! something went wrong!") return ret end --- Prints all inventories -- Prints all inventories known to the game --@usage artery_printinventories --@concommand artery_printinventories concommand.Add("artery_printinventories", function(ply,cmd,args) PrintTable(inventories) end) if SERVER then concommand.Add("artery_printmyinventories",function(ply,cmd,args) print("Inventories of:",ply:Nick()) PrintTable(ply.data.inventories) end) end ---To Be Depriciated. function inv.DeriveInventory(name) error("inv.DeriveInventory called") while inventories[name] == nil do coroutine.yield() end --Create a shallow copy local ret = {} for k,v in pairs(inventories[name]) do ret[k] = v end return ret end return inv