Inventory Access Rules - rebel1324/NutScript GitHub Wiki
Access Rules
Inventories typically have some constraints. For example, inventories usually have some sort of maximum number of items it can hold. The inventory class we made so far does not have any constraints, so it can hold an arbitrary number of items.
Recall in the beginning of the tutorial, the end result would be a simple inventory with a maximum capacity. In this part, we will go over how to make sure that MyInventory
inventories can only hold a finite number of items using access rules. This will build on the code from the previous part.
Definition
An access rule is a function that takes as input an inventory instance, a string describing an inventory action, and a context. A context is a table which will hold some information about the action. This function then returns one of the following:
true
false
- nothing (
nil
)
If an access rule returns true
, then the action is allowed. However, if the access rule returns false
, then the action is not allowed. If the action does not return anything (nil
), then the decision is left to some other code (e.g. other access rules, or the code responsible for running the action rules).
If an access rule returns false
, the function may also return a string containing the reason why the action was denied as a second return value.
Example:
Since we want to limit the number of items in our MyInventory
instances, we should have a rule that stops items from being added if the inventory is at capacity. So, when an item is being added (i.e. action
is "add"), then we should return false
if there are too many items. Otherwise, we can return true
since the inventory is not at capacity.
MAX_ITEMS = 10
function CanAddItemIfNotAtCapacity(inventory, action, context)
if (action == "add") then
return table.Count(inventory:getItems()) < MAX_ITEMS
end
end
Adding Access Rules to an Inventory
To a Single Instance
Given an inventory instance inventory
and an access rule rule
, we can add the access rule to inventory
using the addAccessRule
method. So, we would write inventory:addAccessRule(rule)
to make it so the next time an access check if being done, rule
will run.
Access rules are ran in the order they are added.
By default, the addAccessRule
method will add the given access rule to the end of the list of existing access rules. If you wish to change the order, the second argument to addAccessRule
is a positive integer. The smaller the number, the higher priority the access rule. So, if the inventory
had existing access rules, inventory:addAccessRule(rule)
will cause rule
to run after all the existing access rules. But, inventory:addAccessRule(rule, 1)
will make it so rule
runs first, before all the other access rules.
To all Instances of a Class
If you wish to add the access rule to all inventory instances of a certain class, you can use the configure
method on the inventory class. From the previous parts of this tutorial, we have our MyInventory
class. So, if we wanted to add the CanMaybeAddItem
access rule from above to the MyInventory
class, we would add the following code to our class:
function MyInventory:configure()
self:addAccessRule(CanAddItemIfNotAtCapacity)
end
Checking for Access
Now that we have an access rule, we need to actually call it. Since we want to control when items are being added to our inventory, we should somehow call the access rules in our add
method. From the last part, we have
function MyInventory:add(itemType)
return nut.item.instance(itemType)
:next(function(item)
-- Note we do not use the colon syntax since we do not want to
-- pass in self.BaseClass as the first argument!
return self.BaseClass.add(self, item)
end)
end
To call access rules, we use the canAccess
method. Since our access rule checked if action
is "add", we will pass in "add" as the first argument. Then, we pass in a table as the second argument. This table is the context
for the access rules.
The canAccess
method returns
true
if an access rule returnstrue
false
if an access rule returnsfalse
nil
otherwise (either no access rules exist or none of them returned anything)
The second return value may be a string containing the reason why an access rule returned false
, if the first return value is false
.
So, if we cannot access the inventory, we should not add the item. Thus, we have:
function MyInventory:add(itemInstanceOrType)
-- If the first argument is an item, just delegate to the original `add` method.
if (nut.item.isItem(itemInstanceOrType)) then
return self.BaseClass.add(self, itemInstanceOrType)
end
local itemType = itemInstanceOrType
-- If we cannot add the item, just return an error.
local itemTable = nut.item.list[itemType]
assert(itemTable, tostring(itemType).." is not a valid item")
local context = { item = itemTable }
local canAccess, reason = self:canAccess("add", context)
if (not canAccess) then
return deferred.resolve({ err = reason })
end
return nut.item.instance(itemType)
:next(function(item)
-- Note we do not use the colon syntax since we do not want to
-- pass in self.BaseClass as the first argument!
return self.BaseClass.add(self, item)
end)
end
Now, we have an inventory that can only hold a limited number of items! As an exercise, you should test this.
Standard Actions
The framework itself has some standard access rules that you should support.
add
The "add" action is for when an item is being added to the inventory using the add
method. The context contains at least the following keys:
Key | Description |
---|---|
item |
An item table (either an instance or the of the values in nut.item.list ) corresponding to the item being added. |
repl
The "repl" action is used to determine which players are allowed to see inventory changes on the client side. For example, if an item is added to the inventory, only players who pass the "repl" check may receive the event client-side. The context contains at least the following keys:
Key | Description |
---|---|
client |
The player that is being checked. Returning true means the player will have state changes replicated client-side. |
Removing Access Rules
You may want some of your access rules to be temporary. You can use the Inventory:removeAccessRule(rule)
method. Given a reference to an access rule added before, it removes the access rule from the inventory. For example, you may add an access rule somewhere like:
function AllowEverything(inventory, action, context)
return true
end
-- With some inventory instance:
someInventory:addAccessRule(AllowEverything)
Later on, you can remove the access rule using:
someInventory:removeAccessRule(AllowEverything)
If you are going to create a temporary function for your access rule, it is important you save a reference to it somewhere so you can remove the access rules later on. Access rules are removed by value, so the argument for removeAccessRule
must be the same value as the one passed into addAccessRule
.