Unity: 2022.3.4 - AC: 1.82.2
Hello,
I'm attempting to modify the player's stats (AC global vars) via item consumption (item use interactions) and wearing equipment (semi-modified version of the AC equipment system offered in the downloads page).
My first question is related to the OnContainerRemove event. I'm experiencing an issue where upon removing an item from the equipment container, InvInstance.IsValid() is returning false on the containerItem, preventing me from using GetProperty(0).GetValue() in order to remove the necessary stats from the player (Popup property type on the item).
I use OnContainerAdd to find the item's properties, and modify the AC global variables accordingly, and figured I would do the opposite when an equipment item is removed or replaced. I tested OnContainerRemove with and without checking for IsValid and saw different behavior, but I don't want to flood all of my findings in one post.
For context, the player's inventory and equipment are on the same UI screen. If the player clicks a consumable, it gets consumed, stats modified, ui updated. If the player clicks an equipment item, the item is selected, cursor becomes item's active texture, equipment can be placed into gear container, stats applied, ui updated. Only once the item is attempted to move back to the inventory is when I get an error.
GearContainerCalculator.cs (on the Player Prefab root containers object)
...
private void OnEnable()
{
EventManager.OnContainerAdd += OnContainerAdd;
EventManager.OnContainerRemove += OnContainerRemove;
}
private void OnDisable()
{
EventManager.OnContainerAdd -= OnContainerAdd;
EventManager.OnContainerRemove -= OnContainerRemove;
}
private void OnContainerAdd(Container container, InvInstance containerItem)
{
if (!KickStarter.kickStarter.HasInitialisedAC) return;
if (InvInstance.IsValid(containerItem))
{
string itemsPostiveTrait = containerItem.GetProperty(0).GetValue();
string itemsNegativeTrait = containerItem.GetProperty(1).GetValue();
string posGlobalVarName = "player." + itemsPostiveTrait;
string negGlobalVarName = "player." + itemsNegativeTrait;
if (!string.IsNullOrEmpty(itemsPostiveTrait) && DoesGVarExist(posGlobalVarName))
{
GlobalVariables.GetVariable(posGlobalVarName).IntegerValue += 2;
}
if(!string.IsNullOrEmpty(itemsNegativeTrait) && DoesGVarExist(negGlobalVarName))
{
GlobalVariables.GetVariable(negGlobalVarName).IntegerValue -= 2;
}
}
else
{
Debug.LogError($"Item: {containerItem.ItemLabel} is not valid");
}
}
private void OnContainerRemove(Container container, InvInstance containerItem)
{
if (!KickStarter.kickStarter.HasInitialisedAC) return;
string itemsPostiveTrait = containerItem.GetProperty(0).GetValue(); //null-ref's here
string itemsNegativeTrait = containerItem.GetProperty(1).GetValue();
string posGlobalVarName = "player." + itemsPostiveTrait;
string negGlobalVarName = "player." + itemsNegativeTrait;
if (!string.IsNullOrEmpty(itemsPostiveTrait) && DoesGVarExist(posGlobalVarName))
{
GlobalVariables.GetVariable(posGlobalVarName).IntegerValue -= 2;
}
if (!string.IsNullOrEmpty(itemsNegativeTrait) && DoesGVarExist(negGlobalVarName))
{
GlobalVariables.GetVariable(negGlobalVarName).IntegerValue += 2;
}
}
private bool DoesGVarExist(string statName)
{
try
{
string variable = GlobalVariables.GetVariable(statName).label;
}
catch
{
return false;
}
return true;
}
...
My second question is related, but I can make a new discussion post if needed. Using this method, I'm unable to come up with a viable solution to account for any "Stored items" within the player prefab's equipment containers. The player prefab has one equipment item already stored upon launch, which means that item's properties have not yet made an effect on the players stats since it was neither Added or Removed from a container. Is there a simpler solution that AC offers to get around this? I couldn't find a way to access the containers "stored items" to attempt to initialize their stats (maybe during the OnSpawn event). I also tried to force check all containers during the OnInitializeScene event, but then the equipment's stats stack everytime the player enters a new scene. I could just be getting a bit lost on this one.
It looks like you're new here. If you want to get involved, click one of these buttons!
Comments
Can you share the full stacktrace of the error you're getting?
This is the approach I'd take. I'll look into the IsValid issue - thanks for the repot - but the robust way would be to have a function that recaulculates all stats from scratch, as opposed to just those added. This can then be called at the appropriate times - i.e. when the game begins, when items are added/removed, etc.
When you say the stats stack: are you resetting stat values beforehand? If you are able to reset your variables to the values they'd take without accounting for equipment / items, then you can recalculate their effects and add them on at any time. The event hooks would just be a means to do this catch-all recalculation.
Thanks for looking into this for me.
I agree. At first I actually just used the stat calculator you provided in the equipment example, which worked great for gear containers. However, the issue came down to when I would "use" consumables. Since consumables modify the global vars directly through use interactions, the newly modified stats were only staying active while the menu remained open. As soon as the menu closed, all stats would get overridden and only account for gear. The more I tried to write my own solution, the further I got away from your example code, which led me to now - asking about OnContainerAdd/Remove.
I know there's a lot of details that I may need to cover here for assistance, but please tell me anything specifically I can explain further for clarity. I believe that the way you could recreate this is by using the equipment example and creating a new inventory item that modifies stats on use. Here's an image of what I currently have setup for one of my consumable items:

Here's the stack trace:
NullReferenceException: Object reference not set to an instance of an object ExitNode_GearContainerCalculator.OnContainerRemove (AC.Container container, AC.InvInstance containerItem) (at Assets/Exit-Node-AC/Scripts/ExitNode_GearContainerCalculator.cs:71) AC.EventManager.Call_OnChangeInventory (AC.InvCollection invCollection, AC.InvInstance invInstance, AC.InventoryEventType inventoryEventType, System.Int32 amountOverride) (at Assets/AdventureCreator/Scripts/Managers/EventManager.cs:1723) AC.InvInstance.CreateTransferInstance () (at Assets/AdventureCreator/Scripts/Inventory/InvInstance.cs:308) AC.InvCollection.Insert (AC.InvInstance addInstance, System.Int32 index, AC.OccupiedSlotBehaviour occupiedSlotBehaviour, System.Boolean matchPropertiesWhenMerging) (at Assets/AdventureCreator/Scripts/Inventory/InvCollection.cs:606) AC.MenuInventoryBox.HandleDefaultClick (AC.MouseState _mouseState, System.Int32 _slot) (at Assets/AdventureCreator/Scripts/Menu/Menu classes/MenuInventoryBox.cs:1329) AC.RuntimeInventory.ProcessInventoryBoxClick (AC.Menu _menu, AC.MenuInventoryBox inventoryBox, System.Int32 _slot, AC.MouseState _mouseState) (at Assets/AdventureCreator/Scripts/Inventory/RuntimeInventory.cs:1125) AC.MenuInventoryBox.ProcessClick (AC.Menu _menu, System.Int32 _slot, AC.MouseState _mouseState) (at Assets/AdventureCreator/Scripts/Menu/Menu classes/MenuInventoryBox.cs:2548) AC.MenuElement.ProcessClickUI (AC.Menu _menu, System.Int32 _slot, AC.MouseState _mouseState) (at Assets/AdventureCreator/Scripts/Menu/Menu classes/MenuElement.cs:312) AC.MenuInventoryBox.ProcessClickUI (AC.Menu _menu, System.Int32 _slot, AC.MouseState _mouseState) (at Assets/AdventureCreator/Scripts/Menu/Menu classes/MenuInventoryBox.cs:352) AC.MenuElement.ProcessClickUI (UnityEngine.EventSystems.PointerEventData data, AC.Menu _menu, System.Int32 _slotIndex) (at Assets/AdventureCreator/Scripts/Menu/Menu classes/MenuElement.cs:295) AC.MenuElement+<>c__DisplayClass39_0.<CreateUIEvent>b__0 (UnityEngine.EventSystems.BaseEventData eventData) (at Assets/AdventureCreator/Scripts/Menu/Menu classes/MenuElement.cs:252) UnityEngine.Events.InvokableCall`1[T1].Invoke (T1 args0) (at <17484a9af6b944dea5cd9be4dbb0da2c>:0) UnityEngine.Events.UnityEvent`1[T0].Invoke (T0 arg0) (at <17484a9af6b944dea5cd9be4dbb0da2c>:0) UnityEngine.EventSystems.EventTrigger.Execute (UnityEngine.EventSystems.EventTriggerType id, UnityEngine.EventSystems.BaseEventData eventData) (at ./Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/EventTrigger.cs:218) UnityEngine.EventSystems.EventTrigger.OnPointerClick (UnityEngine.EventSystems.PointerEventData eventData) (at ./Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/EventTrigger.cs:275) UnityEngine.EventSystems.ExecuteEvents.Execute (UnityEngine.EventSystems.IPointerClickHandler handler, UnityEngine.EventSystems.BaseEventData eventData) (at ./Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/ExecuteEvents.cs:57) UnityEngine.EventSystems.ExecuteEvents.Execute[T] (UnityEngine.GameObject target, UnityEngine.EventSystems.BaseEventData eventData, UnityEngine.EventSystems.ExecuteEvents+EventFunction`1[T1] functor) (at ./Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/ExecuteEvents.cs:272) UnityEngine.EventSystems.EventSystem:Update() (at ./Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/EventSystem.cs:530)
Can I see your implementation of the OnContainerRemove event, including line 71?
Is this a case when any amount of a given item is removed, or only when all items are removed in a given slot?
It'x worth mentioning, however, that the OnContainerRemove event can be replaced with the OnInventoryRemove_Alt event. This is because you can read the InvInstance class's GetSourceContainer function to get which Container it belongs too.
Regarding stat changes: if you wanted to keep the "recalculate everything" approach, you could use a separate pair of variables ("default" and "modifier"), with consumed items affecting the latter.
Calculating a stat would then be a case of:
This way, you can run the calculation at any time without the effects of it being compounded.
If you're handling the calculation through script, you could also remove the "default" variable just be accessing (through code) the stat variable's default variable from within the Variables MAnager.
I have recorded a quick video to hopefully make it easier for you to see exactly what I'm experiencing regarding removing items from the gear container slots:

To your point, though, I do agree that I may not even need to spend too much time worrying about OnContainerRemove's behavior specifically, and just focus on building an approach where all stats are recalculated.
That being said, I'm trying to wrap my head around how I'd achieve this. So if I'm understanding correctly, let's assume I'd first modify my GearCalculator script back to something similar to what you provided in the EquipmentExample package. I'd also create a pairing global variable to, for example, PERCEPTION called PERCEPTION_MODIFIER, but for all of the player's stats.
Then whenever I want any event to update the player's stats, I'd call the UpdateVariable function that modifies the necessary "_modifier" variable that matches whatever gear's found equipped. Then the core stat would be updated to match the sum of "stat gvar" and "stat_modifier gvar".
Does this sound like I have the right idea?
Here's a copy of my full stat calculator script as can be seen in the video:
So, it should be that you can do without the modifier/base variables that I mentioned earlier - you can calculate the modifier in script, and the base value can be taken from the "default" value assigned in the Manager.
Something like this for your UpdateVariable function:
First, I just want to say how much I appreciate the quality of the support you offer to people here. I deal with a lot of technical support in my life, and you are by far one of the most helpful and patient. Thank you very much.
Now for the stat calculation, your suggestion definitely works. That makes total sense now by what you suggested in your last post. However, I'm experiencing another issue with consumables reverting the global stats they affect in their actionlists. Regardless of where the stats are (gear doesn't seem to make a difference), I'll use an item (consumable), and modify the necessary stats via the Variable > Set action type. Then it seems without fail, either when the menu opened or hovering over a separate inventory item ui element (gear element as well), the variables effected by consumables are reverted.
I double checked I didn't have some loose "OnHoverItem" event hidden somewhere that I'd accidentally written, but I confirmed this behavior's in the EquipmentExample package as well. I just added a new item category, a new item, and an onUse actionlist to modify the "weight" and "armour" stats. And hovering over a ui element reverts it.
Is this a behavior I may have incorrectly set in my settings somewhere, or could this be an actual bug? Please let me know if there's anything I can provide to assist.
It may be a bug, in that case? Could you provide a breakdown of exactly what's needed to recreate such an issue, starting from an unmodified import of the example package? I'll attempt a recreation and see what happens.
Sure thing. I made a fresh unity project (2022.3.19). Imported AC (1.82.4). Imported Equipment package and opened Equipment sample scene.
Hit play, keep an eye on global variables, then use the consumable item. Weight and Armour will change, but be reverted as soon as you hover over another element, or the inventory/equipment menu's open next.
Thanks for the steps.
The Equipment variables you're affecting here are updated as part of the stat calculation code, so you should avoid setting those values directly.
For consumables, sounds like you would need to have a "Base" variable after all, e.g. "Base weight", that consumables would affect instead, and then get incorporated into the final Weight calculation. Apologies if it took me a little while to understand your intent here.
I'll have a look at the Equipment System package and see if something along these lines can be incorporated into it officially - at least in a basic way.
Yes, something like this as a replacement for the StatCalculator script's Stat class should do it:
Thanks again for the specifics on scripting. It seems though that offsetValue is never actually getting modified. I'm trying to fix it myself, but I can't quite determine where it would fit into the script.
Correct me if I'm misunderstanding the logic, please:
I'm going by the steps you shared for recreating an issue with the unmodified Equipment packager here:
An updated set of steps:
-- Name: Consumable Item
-- Category: Consumable
-- Carry on Start
-- Can carry multiple: 20 on start
-- Set Main graphic
-- Created "Use" interaction:
-- Variable - Set; Global, "Equipment/Weight offset", Entered Here, Increase By Value: "3"
-- Variable - Set; Global, "Equipment/Armour offset", "Entered Here, Increase By Value: "-7"
-- Inventory - Add or Remove; Remove, Specific Item, "Consumable Item", Set amount, Reduce by: 1
One current issue, however, is that this won't trigger the stat recalculation - so you'd need to re-open the Equipment menu to see the effects of this on the base variables. Try the above first, though, and see if that's along the right lines.
Ah right, I understand now. Okay, I started a fresh instance of the Equipment Example again and followed your steps and everything works as expected with equipped gear effecting only the base variables, and the consumables effecting the offset variables.
To clarify, this is using the UpdateVariable function from the example package. Using the modified script you provided last on the 24th does still show the behavior of the base variables changing when hovering over a new ui element. Wasn't sure if you intended for me to follow these steps with the modified function, so I tried both.
Let's stick with the updated package, if that's the one working best.
I've updated it once more to automatically recalculate stats if any of the "offset" variables are affected, so you shouldn't have to re-open the menu to see their effects now.
Okay yeah, that works great. Just a heads up the updated version of the package - the serialized list of Stats on the player prefab has "Equipment/Weight" as both base variables. I missed it at first and was really confused by the math lol, but simple fix.
I really appreciate all the effort with this one though, man. From here I can move onto the next stages of development I've been excited to tackle. Just a question though, can you explain the difference and use cases between the KickStarter' variablesManager and the GlobalVariables GetVariable? I'm guessing the kickstarter's better to read from during runtime for whatever reason?
VariablesManager refers to the original set of variables defined in Edit mode. At runtime, copies of them are made that can then be modified in-game without affecting the originals. These are accessed through GlobalVariables.