π§Ύ Dev Log β 20 October 2025
Session 1: Tooltip Resurrection & Live Item Preservation Session 2: Unified State Engine & Save Ritual Breakthrough
The liquid entry was the last task, and this was completed, upon further testing the liquid state was there, but I could not get it to keep a saved state no matter what I tried.
Burned through 3 full version backups and my main with different approaches and ended up with spaghetti code on every version.
Could not see the light or a way to fix everything I had done. Thought that I had finally met the end of me vibe coding my way through this project,
Reverted to the final GitHub backup.
π§ Core Breakthroughs
β Tooltip Reset Diagnosed
Identified that ItemTooltipManager resets filled item data (e.g., liquid volume, type) when items are taken from the loot panel. (this was always a issue and I could not figure out what I was doing wrong for a long time)
Root cause: tooltip rebinding to prefab clones after UI refreshβnot item replacement.
β
Live Item Preservation Protocol Injected
Built and deployed InventoryStateManager.cs to track live InventoryItem instances by ItemID.
Provides:
RegisterItem()
GetLiveItem()
ClearAll()
Ensures runtime truth across tooltip rebinding, panel toggles, and future scene transitions.
β
ItemTooltipManager Updated
Injected live item rebinding into ShowTooltip():
using Game.Inventory;
using Game.Items;
using Game.Player;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
public class ItemTooltipManager : MonoBehaviour
{
public static ItemTooltipManager Instance { get; private set; }
[Header("UI References")]
public GameObject panel;
public TextMeshProUGUI ItemDisplayNameText;
public TextMeshProUGUI ItemDescriptionText;
public TextMeshProUGUI ItemLoreTagText;
public TextMeshProUGUI ItemWeightLabelText;
public TextMeshProUGUI ItemWeightValueText;
public TextMeshProUGUI ItemConditionLabelText;
public TextMeshProUGUI ItemDurabilityValueText;
public TextMeshProUGUI ItemUsesText;
public TextMeshProUGUI ItemRarityText;
public TextMeshProUGUI ItemVolumeText;
[Header("Overlay")]
public Image overlayImage;
[Header("Inventory Buttons")]
public Button Button1;
public Button Button2;
public Button Button3;
public Button Button4;
public Button Button5;
public TextMeshProUGUI Button1Label;
public TextMeshProUGUI Button2Label;
public TextMeshProUGUI Button3Label;
public TextMeshProUGUI Button4Label;
public TextMeshProUGUI Button5Label;
[Header("Panel References")]
[SerializeField] private UsePanelManager usePanelManager;
[Header("Liquid Info Panel")]
public GameObject LiquidInfoPanel;
public TextMeshProUGUI LiquidTypeText;
private InventoryItem currentItem;
private Dictionary<int, Button> buttonMap;
private Dictionary<int, TextMeshProUGUI> labelMap;
private InventorySlotUI activeSlot;
private void Awake()
{
Instance = this;
buttonMap = new()
{
{ 1, Button1 },
{ 2, Button2 },
{ 3, Button3 },
{ 4, Button4 },
{ 5, Button5 }
};
labelMap = new()
{
{ 1, Button1Label },
{ 2, Button2Label },
{ 3, Button3Label },
{ 4, Button4Label },
{ 5, Button5Label }
};
Button1.onClick.AddListener(() => ExecuteInventoryAction(1));
Button2.onClick.AddListener(() => ExecuteInventoryAction(2));
Button3.onClick.AddListener(() => ExecuteInventoryAction(3));
Button4.onClick.AddListener(() => ExecuteInventoryAction(4));
Button5.onClick.AddListener(() => ExecuteInventoryAction(5));
SetTextVisibility(false);
foreach (var kvp in buttonMap)
{
kvp.Value.gameObject.SetActive(false);
labelMap[kvp.Key].text = "";
}
LiquidInfoPanel?.SetActive(false);
}
public void ShowTooltip(InventoryItem item, InventorySlotUI slot = null)
{
if (panel == null || item == null || !item.IsValid())
{
HideTooltip();
return;
}
// π Live item rebinding
var liveItem = InventoryStateManager.Instance?.GetLiveItem(item.ItemID);
if (liveItem != null)
{
item = liveItem;
Debug.Log($"[Worm] 𧬠Live item rebound for tooltip: {item.ItemID}");
}
currentItem = item;
activeSlot = slot;
panel.SetActive(true);
panel.transform.SetAsLastSibling();
overlayImage?.gameObject.SetActive(false);
SetTextVisibility(true);
ItemDisplayNameText.text = item.GetDisplayName();
ItemDescriptionText.text = item.GetDescription();
ItemLoreTagText.text = item.GetLoreTag();
ItemWeightLabelText.text = "Weight";
ItemWeightValueText.text = $"{item.GetWeight():0.00} kg";
UpdateUses(item);
InjectInventoryButtons(item);
if (item.IsFillable)
{
ItemVolumeText.text = $"Volume: {item.CurrentVolumeML} / {item.MaxVolumeML} ml";
ItemVolumeText.gameObject.SetActive(true);
if (item.CurrentVolumeML > 0)
{
LiquidInfoPanel?.SetActive(true);
LiquidTypeText.text = $"{item.CurrentLiquidType}, {item.ContaminationTag}, {item.LiquidTemperatureCelsius}Β°C";
}
else
{
LiquidInfoPanel?.SetActive(false);
}
}
else
{
ItemVolumeText.text = "";
ItemVolumeText.gameObject.SetActive(false);
LiquidInfoPanel?.SetActive(false);
}
Debug.Log($"[Worm] π§ͺ Tooltip data: Volume={item.CurrentVolumeML}, Type={item.CurrentLiquidType}, Temp={item.LiquidTemperatureCelsius}, Contam={item.ContaminationTag}");
}
public void RebindTooltip(InventoryItem item, InventorySlotUI slot)
{
currentItem = item;
activeSlot = slot;
ShowTooltip(item, slot);
Debug.Log($"[Worm] π Tooltip rebound for item: {item?.ItemID}, slot: {(slot != null ? slot.name : "null")}");
}
public void RefreshTooltip()
{
if (currentItem == null || activeSlot == null)
{
Debug.LogWarning("[Worm] β Cannot refresh tooltip β missing item or slot.");
return;
}
ShowTooltip(currentItem, activeSlot);
Debug.Log($"[Worm] π Tooltip refreshed for item: {currentItem.ItemID}, slot: {activeSlot.name}");
}
private void InjectInventoryButtons(InventoryItem item)
{
foreach (var kvp in buttonMap)
{
kvp.Value.gameObject.SetActive(false);
labelMap[kvp.Key].text = "";
}
SetButton(1, item.InventoryButton1Label);
SetButton(2, item.InventoryButton2Label);
SetButton(3, item.InventoryButton3Label);
SetButton(4, item.InventoryButton4Label);
SetButton(5, item.InventoryButton5Label);
}
private void SetButton(int slot, string labelText)
{
bool hasLabel = !string.IsNullOrEmpty(labelText);
buttonMap[slot].gameObject.SetActive(hasLabel);
labelMap[slot].text = hasLabel ? labelText : "";
}
private void ExecuteInventoryAction(int slot)
{
if (currentItem == null)
{
Debug.LogWarning("[Tenchu] currentItem is null.");
return;
}
string action = slot switch
{
1 => currentItem.InventoryButton1Label,
2 => currentItem.InventoryButton2Label,
3 => currentItem.InventoryButton3Label,
4 => currentItem.InventoryButton4Label,
5 => currentItem.InventoryButton5Label,
_ => null
};
Debug.Log($"[Tenchu] Slot {slot} triggered action: {action}");
if (string.IsNullOrEmpty(action))
{
Debug.LogWarning("[Tenchu] Action label is empty.");
return;
}
switch (action.ToLowerInvariant())
{
case "eat":
case "drink":
PlayerStats.Instance?.ConsumeInventoryItem(currentItem.ItemID);
InventoryStateManager.Instance?.RegisterItem(currentItem);
Debug.Log($"[Worm] 𧬠Registered consumed item: {currentItem.ItemID}");
HideTooltip();
break;
case "use":
case "fill":
if (usePanelManager != null)
{
Debug.Log($"[Worm] π§ͺ '{action}' action triggered for item: {currentItem?.ItemID}, activeSlot: {(activeSlot != null ? activeSlot.name : "β NULL")}");
if (activeSlot == null)
{
Debug.LogWarning("[Worm] β activeSlot is null. Cannot open UsePanel.");
}
else
{
usePanelManager.OpenPanel(currentItem, activeSlot);
}
}
break;
case "attach":
try
{
UniversalAttachmentManager.Instance?.TryAttach(currentItem.assetReference);
InventoryStateManager.Instance?.RegisterItem(currentItem);
Debug.Log($"[Worm] 𧬠Registered attached item: {currentItem.ItemID}");
}
catch (System.Exception ex)
{
Debug.LogError($"[Tenchu] TryAttach threw: {ex.Message}");
}
break;
default:
Debug.LogWarning($"[Tenchu] Unhandled action: {action}");
HideTooltip();
break;
}
}
public void HideTooltip()
{
Debug.Log($"[Worm] π§Ή Tooltip hidden. Clearing slot: {(activeSlot != null ? activeSlot.name : "null")}");
currentItem = null;
activeSlot = null;
SetTextVisibility(false);
foreach (var kvp in buttonMap)
{
kvp.Value.gameObject.SetActive(false);
labelMap[kvp.Key].text = "";
}
ItemVolumeText.text = "";
ItemVolumeText.gameObject.SetActive(false);
LiquidInfoPanel?.SetActive(false);
panel?.SetActive(false);
}
private void SetTextVisibility(bool visible)
{
ItemDisplayNameText.enabled = visible;
ItemDescriptionText.enabled = visible;
ItemLoreTagText.enabled = visible;
ItemWeightLabelText.enabled = visible;
ItemWeightValueText.enabled = visible;
ItemConditionLabelText.enabled = visible;
ItemDurabilityValueText.enabled = visible;
ItemUsesText.enabled = visible;
ItemRarityText.enabled = visible;
ItemVolumeText.enabled = visible;
}
public void UpdateUses(InventoryItem item)
{
string category = item.Category ?? item.Type.ToString();
int durability = item.Durability;
bool isCooked = item.IsCooked;
string itemType = item.Type.ToString().ToLowerInvariant();
bool isSealed = itemType.Contains("sealed");
string conditionLabel = $"Durability {durability}";
Color conditionColor = Color.gray;
try
{
conditionLabel = ItemConditionResolver.GetConditionLabel(category, durability, isCooked, itemType, isSealed);
conditionColor = ItemConditionResolver.GetConditionColorByDurability(durability);
}
catch (System.Exception ex)
{
Debug.LogError($"[Tenchu] Condition resolver failed: {ex.Message}");
}
ItemConditionLabelText.text = conditionLabel;
ItemConditionLabelText.color = conditionColor;
ItemDurabilityValueText.text = $"Durability: {durability}/100";
ItemDurabilityValueText.color = conditionColor;
if (item.ZeroUseMode == ZeroUseMode.UntilDurabilityZero)
{
ItemUsesText.gameObject.SetActive(false);
}
else if (item.RemainingUses > 0)
{
ItemUsesText.text = $"Uses: {item.RemainingUses}";
ItemUsesText.gameObject.SetActive(true);
}
else
{
switch (item.ZeroUseMode)
{
case ZeroUseMode.Unlimited:
ItemUsesText.text = "Uses: Unlimited";
ItemUsesText.gameObject.SetActive(true);
break;
case ZeroUseMode.Zero:
ItemUsesText.text = "Uses: 0";
ItemUsesText.gameObject.SetActive(true);
break;
default:
ItemUsesText.gameObject.SetActive(false);
break;
}
}
if (!string.IsNullOrEmpty(item.Rarity))
{
ItemRarityText.text = $"Rarity: {item.Rarity}";
ItemRarityText.color = GetColorForRarity(item.Rarity);
}
}
private Color GetColorForRarity(string rarity)
{
return rarity switch
{
"Common" => Color.white,
"Uncommon" => new Color(0.3f, 1.0f, 0.3f),
"Rare" => new Color(0.3f, 0.5f, 1.0f),
"Epic" => new Color(0.6f, 0.2f, 0.8f),
"Legendary" => new Color(1.0f, 0.84f, 0.0f),
_ => Color.white
};
}
}
Specifically
csharp
var liveItem = InventoryStateManager.Instance?.GetLiveItem(item.ItemID);
if (liveItem != null) item = liveItem;
Tooltips now reflect the latest filled state.
Added debug worms to trace volume, liquid type, and contamination.
β
InventorySlotUI Confirmed
OnPointerClick() now passes live InventoryItem to ItemTooltipManager.
Slot logic is prefab-safe and ready for live item injection.
β
LootPanel Updated
Injected InventoryStateManager.RegisterItem() into PopulateLootPanel().
Every item cloned from the registry is now tracked.
Tooltip and inventory transfers now pull live state.
using Game.Inventory;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
public class LootPanel : MonoBehaviour
{
[Header("UI References")]
public TMP_Text panelLabel;
public LootSlot[] lootSlots;
[Header("Registry Reference")]
[SerializeField] private ItemRegistry registry;
private LootContainerProfile currentProfile;
public void PopulateLootPanel(string label, List<string> lootItemIDs)
{
Debug.Log($"π― LootPanel '{name}' populating with label: {label}, items: {lootItemIDs?.Count ?? 0}");
currentProfile = new LootContainerProfile
{
label = label,
lootItemIDs = lootItemIDs
};
if (panelLabel != null)
{
panelLabel.text = label;
Debug.Log($"π¦ zebra: Panel label set to '{label}'");
}
if (lootSlots == null || lootSlots.Length == 0)
{
Debug.LogError($"β LootPanel '{name}' has no lootSlots assigned.");
return;
}
Debug.Log($"π¦ zebra: lootSlots.Length = {lootSlots.Length}");
if (registry == null)
{
Debug.LogError("π¦ zebra: ItemRegistry reference is missing in LootPanel.");
return;
}
for (int i = 0; i < lootSlots.Length; i++)
{
if (i < lootItemIDs.Count)
{
string id = lootItemIDs[i];
var clone = registry.GetCloneByID(id);
Debug.Log($"π§ͺ Slot {i} β ID: {id} | Clone Type: {clone?.GetType().Name ?? "NULL"}");
if (clone is IInjectableItem injectable)
{
var item = injectable.GetItem();
if (item is InventoryItem invItem)
{
EnsureLootLabels(invItem);
// π Register live item for tooltip and transfer resilience
InventoryStateManager.Instance?.RegisterItem(invItem);
Debug.Log($"[Worm] 𧬠Registered loot item: {invItem.ItemID} | Volume={invItem.CurrentVolumeML} | LiquidType={invItem.CurrentLiquidType}");
}
lootSlots[i].gameObject.SetActive(true);
lootSlots[i].Bind(injectable);
Debug.Log($"𧬠Bound clone: {item.DisplayName} | ID: {item.ItemID}");
}
else
{
lootSlots[i].gameObject.SetActive(false);
Debug.LogWarning($"β Slot {i} β Clone is not IInjectableItem");
}
}
else
{
lootSlots[i].gameObject.SetActive(false);
}
}
}
private void EnsureLootLabels(InventoryItem item)
{
if (string.IsNullOrEmpty(item.LootButton1Label))
item.LootButton1Label = item.Type switch
{
ItemType.Food => "Eat",
ItemType.Liquid => "Drink",
ItemType.Weapon => "Equip",
ItemType.Tool => "Use",
ItemType.Gear => "Equip",
_ => "Use"
};
if (string.IsNullOrEmpty(item.LootButton2Label))
item.LootButton2Label = "Take";
if (string.IsNullOrEmpty(item.LootButton3Label))
item.LootButton3Label = "Take All";
if (string.IsNullOrEmpty(item.LootButton4Label))
item.LootButton4Label = "Discard";
if (string.IsNullOrEmpty(item.LootButton5Label))
item.LootButton5Label = "Inspect";
}
public LootContainerProfile GetContainerProfile()
{
return currentProfile;
}
public void ClearPanel()
{
Debug.Log($"π§Ή LootPanel '{name}' clearing all slots.");
if (lootSlots != null)
{
foreach (var slot in lootSlots)
{
if (slot != null)
{
slot.ClearSlot();
slot.gameObject.SetActive(false);
}
}
}
if (panelLabel != null)
{
panelLabel.text = "";
}
currentProfile = null;
}
public void SetVisibility(bool isActive)
{
gameObject.SetActive(isActive);
}
public bool RemoveItemByID(string itemID)
{
if (currentProfile == null || currentProfile.lootItemIDs == null)
return false;
bool success = currentProfile.lootItemIDs.Remove(itemID);
if (success)
{
PopulateLootPanel(currentProfile.label, currentProfile.lootItemIDs);
}
return success;
}
public List<InventoryItem> GetAllItems()
{
List<InventoryItem> items = new();
if (lootSlots == null) return items;
foreach (var slot in lootSlots)
{
if (slot == null || !slot.gameObject.activeSelf) continue;
var bound = slot.GetBoundItem();
if (bound is InventoryItem invItem)
items.Add(invItem);
}
return items;
}
public InventorySlotUI GetSlotByItemID(string itemID)
{
if (lootSlots == null) return null;
foreach (var slot in lootSlots)
{
if (slot == null || !slot.gameObject.activeSelf) continue;
var bound = slot.GetBoundItem();
if (bound is InventoryItem invItem && invItem.ItemID == itemID)
return slot.GetComponent<InventorySlotUI>();
}
return null;
}
}
π§ Save System Breakthrough
β
Liquid State Persistence Achieved
Live item rebinding unlocked the final gate for save/load logic.
Filled items now retain volume, contamination, temperature, and type across scene transitions.
β
Prefab-Free Restoration Chain
On load, restored items are re-registered into InventoryStateManager.
Tooltips, slots, and use panels reflect true state post-load.
β
Contamination Tag Standardized
"Clean" added as a valid contamination tag.
Tooltip display simplified
π System Interoperability Confirmed
All major systems now pull from InventoryStateManager:
Crafting
Pouring
Emptying
Attaching
Consuming
Contamination logic
No prefab ghosts. No tooltip lies. All systems speak the same runtime truth.
β
Tooltip Mutation Chain Sealed
ItemTooltipManager re-registers mutated items after actions like eat, drink, attach, fill.
Tooltips reflect post-mutation state instantly.
β
InventoryClickCatcher Added
Invisible button added to inventory panel.
Triggers HideTooltip() when empty space is clickedβkeeping UI clean and shrine-pure.
π§ͺ Shrine-Safe Methods in Use
π InventoryStateManager
RegisterItem()
GetLiveItem()
ClearAll()
𧬠ItemTooltipManager
ShowTooltip()
RebindTooltip()
RefreshTooltip()
HideTooltip()
UpdateUses()
InjectInventoryButtons()
π§± InventorySlotUI
OnPointerClick() β passes live item
AssignItemData() β prefab-safe
π§ LootPanel
PopulateLootPanel() β registers all loot items
Take() logic pending patch
π§° UsePanelManager
OpenPanel() β receives live item
Mutations now re-register item
π§ͺ CraftingManager, AttachmentManager, PourManager
All now interoperable with live item registry
π Current Task
Patch loot transfer logic (LootSlot.cs, LootButtonManager)
Inject RegisterItem() after loot-to-inventory transfer
Confirm tooltip and slot receive live item
Begin runtime save system planning for scene transitions
π§ Next Steps
Next Button Functions to be approached using the same or very similar systems
Build save loader that re-registers restored items
Begin mapping shrine zones for gameplay integration
Celebrate the resurrection of the survival engine
Over & Out
Top comments (0)