DEV Community

John
John

Posted on

Dev Log 37 - Consolidated Update

Consolidated Update
Date: 15 November 2025

Hi, to anyone still reading my logs, this log entry is a consolidation of some recent personal dev logs , consolidated as a single log for myself, so not much of a fun or exciting read.

Fixed discard/resurrection and opener-swap bugs;
Hardened inventory/registry/loot flows;
Centralized shrine-safe equip checks;
Rewired and polished Use/Loot/Inventory/Gear UI;
Implemented Options + Audio UI and Credits polish;
Restored tooltip pipeline compatibility;
Added defensive guards, logging, and tests.

Discard flow: true removal of runtime instances; no resurrection from loot or saves.

InventoryStateManager: unregister-by-reference, compatibility shims, TryGetLiveItemByReference.

Loot pickup: always instantiate fresh runtime clones and register them before adding to inventory.

Equip logic: all entry points use TryEquipInventoryItemIfSlotEmpty to prevent Hands overwrite; tooltip remains open on blocked equip.

Open flow: exact-slot replacement for opened items; apply opener damage only (no removal); preserve state copy from sealed → opened item.

UI robustness: defensive ExecuteInventoryAction; OpenOptionSlot UI no auto-invoke; Use/Loot/Inventory panels relabeled and rewired.

Options & Audio UI: OptionsScene + in-game panel; ScreenFader/SceneSkipBlocker integration for Title Menu; functioning Master/Music/SFX sliders.

Credits screen: new unique background, prefab-safe layout and polish.

Tooltip pipeline: merged missing members into InventoryItem, ensured hydration and prefab bindings restored.

Many minor bugs and runtime NREs fixed; added lots of logging / debuggers and probe points.

Conversation summary (diagnosis → fixes)

ItemFactory crash due to null registry and opener (knife types) disappearing when opening sealed items.

Discarded items reappearing when taking loot.

Tooltip fields/labels not showing after restoring backup → compile errors and prefab binding mismatches.

Diagnosis and result

Crash: PlayerInventoryManager.itemRegistry could be null; ItemFactory used it → NRE.

Knife disappearance: ambiguous ID-based removals or incorrect slot replacement removed openers; asset-reference hydration issues allowed the wrong instance to be removed.

Discard resurrection: discarded backing assets remained referenced (registry, gear.ContainedAssets or saves) and loot/pickup code reused those references instead of creating fresh instances.

Tooltip regressions: InventoryItem in current project lacked fields/methods used by other advanced systems; prefab bindings inconsistent between backup and main project.

Files inspected and iteratively edited

ItemFactory: add null-registry guards and ensure fresh clone creation via Instantiate or ItemFactory.Create.

OpenPanelUI.cs: canonical swap now uses ItemFactory.CS - Create, copies sealed→opened state, replaces exact gear slot, applies opener damage without removing opener, refreshes UI.

ItemOpenerResolver.cs: verified it applies durability only; validated special-case logic (charcoal, tin can).

PlayerInventoryManager.cs: added instance-based removal overloads (RemoveItem(InventoryItem), RemoveItem(Object)), InsertItemAt/ReplaceItemInPlace, TryGetGearAndIndex, GetSlotIndex; destroyed runtime clones on discard and improved logging.

InventoryStateManager.cs: added UnregisterItem(InventoryItem), restored MarkItemAsEquipped overloads, TryGetLiveItemByReference, and defensive RegisterItem behavior.

UsePanelIconRegistry.cs: corrected syntax, added missing keys (slice, repair) and returned clean registry.

OpenOptionSlotUIManager: Initialize shows target (sealed container) and attaches single click listener (no auto invoke).

ItemTooltipManager: ExecuteInventoryAction rewritten defensively; preserves attach, discard, equip, use, open, fill, pour, eat, drink flows.

LootTooltipManager & LootButtonActionManager: patched to use TryEquipInventoryItemIfSlotEmpty and to instantiate fresh clones when taking loot.

InventorySlotUI: ensured LoadItem/HasItem/GetAsset behave reliably with runtime clones.

UI wiring: updated Use/Loot/Inventory/Gear button labels and logic to reflect new guard flows.

Concrete code patterns applied

Discard pipeline:

PlayerInventoryManager.RemoveItem(instance) removes from gear.ContainedAssets, destroys runtime asset clone, RefreshInventoryState(), OnInventoryChanged, InventoryStateManager.UnregisterItem(item).

Loot pickup:

itemRegistry.GetCloneByID(template) + Object.Instantiate(template) → register new InventoryItem → AddItemToFirstFreeSlot(newItem, runtimeAsset).

Open swap:

Hydrate sealed asset reference → find exact gear slot (gear + index) → ReplaceItemInPlace(openedAsset) → ItemOpenerResolver.ApplyOpenerDamage(opener) → PlayerInventoryManager.UpdateItem(opener) → InventoryStateManager.Register/Unregister as needed.

Equip:

All equip calls go through PlayerEquipManager.TryEquipInventoryItemIfSlotEmpty(item). Failure shows warning and leaves tooltip open.

UI safety:

OpenOptionSlotUIManager shows correct target and waits for explicit click.

ItemTooltipManager guards slot/asset/injectable/item presence and logs failure points.

Why the final fix stopped the knife disappearing

Replaced ambiguous ID-based removals with instance/slot-specific ReplaceItemInPlace; asset hydration ensures the slot contains the expected backing asset; opener damage reduces durability only and never removes the opener; this prevents accidental removal of the opener or another instance.

Tooltip pipeline rescue (backup → main merge)
Problem context

Tooltip fields (descriptions, use labels, temperature, contamination) displayed in backup but not in main project.

Restoring backup into main project produced compile errors because the backup InventoryItem lacked newer fields/methods current systems required.

Prefab and scene bindings mismatched, causing hydration failures and CS0117/CS1061 compile errors.

Root causes identified

Missing InventoryItem members (UseButton1Label…UseButton5Label, TemperatureCelsius, ContaminationLevel, GetUseLabel(int)).

Tooltip prefab bindings unassigned or different between projects.

Some code was operating on stale prefab references instead of freshly instantiated, hydrated runtime clones.

Concrete fixes applied

Restored and merged missing InventoryItem fields:

UseButton1Label … UseButton5Label

TemperatureCelsius

ContaminationLevel

Added accessor GetUseLabel(int index)

Ensured tooltip UI reads from hydrated InventoryItem instances (not from template assets).

Added validator logs to confirm tooltip UI bindings are assigned and active at runtime.

Compared backup and main project, merged required members into current InventoryItem, then updated backups with the fixed state.

Why this approach worked

The backup proved the core logic; missing members in the current InventoryItem made advanced systems fail. Merging required members preserved new features while restoring compatibility. Validator logs exposed null bindings so they were fixed surgically.

Recommended follow-ups (TO-DO)

GitHub update - Commit and tag this working state (e.g., vTooltipRelic-YYYYMMDD).

Export working tooltip prefab and InventoryItem.cs as portable relics.

Tests performed (representative)

Discard lifecycle: discard B → loot with B picked up → created fresh instance; discarded instance never returns; save/load confirmed none-resurrection.

Equip protection: attempts from loot/inventory when Hands occupied blocked and tooltip retained.

Open flow: container→open and opener→open validated; no auto-selection; exact-slot replacement; opener persistence and durability decrement verified.

Tooltip pipeline: restored tooltip fields displayed correctly; no compile errors after merge; prefab bindings validated at runtime.

Options/Audio: Title Menu → OptionsScene via fader and blocker; in-game panel toggle now correctly , with resolved EventSystem conflicts; sliders update AudioManager live and working.

UI stability: repeated interactions produced no NREs after defensive patches.

Logs, probes and instrumentation added
Ghost item detection in PlayerInventoryManager.LogGhostItems.

ReplaceItemInPlace probes: log gear slot, index, expected vs actual asset reference before replacement.

InventoryStateManager logs for RegisterItem, UnregisterItem, RemoveItem.

ItemTooltipManager added explicit logs for missing slot/asset/injectable/item to pinpoint NRE sources.

Tooltip Validator helper logs extended to confirm live bindings are assigned and active.

Implementation notes & caveats
Only destroy runtime clones; do not destroy template ScriptableObjects from ItemRegistry — mark runtime clones at creation or track them.

InventoryStateManager is keyed by ItemID for compatibility; UnregisterItem removes by reference first then by ID fallback.

Ensure persistence respects unique instance identification if you want save-level exclusion of removed instances.

Audit other systems for use of InventoryStateManager.GetLiveItem for construction — they must instead use ItemFactory/Create or Instantiate templates.

updated code ( personal reference )

Itemtooltipmanager

using Game.Inventory;
using Game.Items;
using Game.Player;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using static UnityEditor.Progress;

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 ItemLiquidVolumeText;
    public TextMeshProUGUI ItemGasVolumeText;

    [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("Use Panel Button Icons")]
    public Image Button1Icon;
    public Image Button2Icon;
    public Image Button3Icon;
    public Image Button4Icon;
    public Image Button5Icon;

    [Header("Panel References")]
    [SerializeField] private UsePanelManager usePanelManager;

    [Header("Liquid Info Panel")]
    public GameObject LiquidInfoPanel;
    public TextMeshProUGUI LiquidTypeText;
    public TextMeshProUGUI ContaminationValueText;
    public TextMeshProUGUI TemperatureValueText;

    private InventoryItem currentItem;
    private InventorySlotUI activeSlot;

    private Dictionary<int, Button> buttonMap;
    private Dictionary<int, TextMeshProUGUI> labelMap;
    private Dictionary<int, Image> iconMap;

    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 }
        };

        iconMap = new()
        {
            { 1, Button1Icon },
            { 2, Button2Icon },
            { 3, Button3Icon },
            { 4, Button4Icon },
            { 5, Button5Icon }
        };

        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 = "";
            iconMap[kvp.Key].enabled = false;
        }

        LiquidInfoPanel?.SetActive(false);
        ItemLiquidVolumeText?.gameObject.SetActive(false);
        ItemGasVolumeText?.gameObject.SetActive(false);
    }

    public void ShowTooltip(InventoryItem item, InventorySlotUI slot = null)
    {
        if (panel == null || item == null || !item.IsValid())
        {
            HideTooltip();
            return;
        }

        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 = !string.IsNullOrEmpty(item.DisplayName)
            ? item.DisplayName
            : "[Unnamed Item]";

        ItemDescriptionText.text = item.GetDescription();
        ItemLoreTagText.text = item.GetLoreTag();

        ItemWeightLabelText.text = "Weight";
        ItemWeightLabelText.color = ItemConditionResolver.GetWeightColor(item.GetWeight());
        ItemWeightValueText.text = $"{item.GetWeight():0.00} kg";
        ItemWeightValueText.color = ItemConditionResolver.GetWeightColor(item.GetWeight());

        UpdateUses(item);
        InjectInventoryButtons(item);

        bool hasLiquid = item.CurrentVolumeML > 0 || item.MaxVolumeML > 0;
        bool hasGas = item.CurrentGasVolumeML > 0 || item.MaxGasVolumeML > 0;

        if (hasLiquid)
        {
            ItemLiquidVolumeText.text = $"Volume {item.CurrentVolumeML}/{item.MaxVolumeML}ml";
            ItemLiquidVolumeText.gameObject.SetActive(true);

            if (item.CurrentVolumeML > 0)
            {
                LiquidInfoPanel?.SetActive(true);
                LiquidTypeText.text = item.CurrentLiquidType ?? "";
                ContaminationValueText.text = item.ContaminationTag ?? "";
                TemperatureValueText.text = item.LiquidTemperatureCelsius > 0
                    ? $"{item.LiquidTemperatureCelsius:0.#}°C"
                    : "";
            }
            else
            {
                LiquidInfoPanel?.SetActive(false);
            }
        }
        else
        {
            ItemLiquidVolumeText.text = "";
            ItemLiquidVolumeText.gameObject.SetActive(false);
            LiquidInfoPanel?.SetActive(false);
        }

        if (hasGas)
        {
            ItemGasVolumeText.text = $"Volume {item.CurrentGasVolumeML}/{item.MaxGasVolumeML}ml";
            ItemGasVolumeText.gameObject.SetActive(true);
        }
        else
        {
            ItemGasVolumeText.text = "";
            ItemGasVolumeText.gameObject.SetActive(false);
        }

        Debug.Log($"[Worm] 🧪 Tooltip data: Liquid={item.CurrentVolumeML}/{item.MaxVolumeML}, Gas={item.CurrentGasVolumeML}/{item.MaxGasVolumeML}");
    }

    public void RebindTooltip(InventoryItem item, InventorySlotUI slot)
    {
        if (item == null || slot == null || string.IsNullOrEmpty(item.ItemID))
        {
            Debug.LogWarning("[Worm] ❌ RebindTooltip failed.");
            return;
        }

        currentItem = item;
        activeSlot = slot;
        ShowTooltip(item, slot);
        Debug.Log($"[Worm] 🔁 Tooltip rebound for item: {item.ItemID}, slot: {slot.name}");
    }
    private void ExecuteInventoryAction(int slot)
    {
        if (currentItem == null)
        {
            Debug.LogWarning("[Tenchu] currentItem is null.");
            return;
        }

        string action = currentItem.GetButtonLabel(slot, "inventory");
        if (string.IsNullOrEmpty(action))
        {
            Debug.LogWarning("[Tenchu] Action label is empty.");
            return;
        }

        Debug.Log($"[Tenchu] Slot {slot} triggered action: {action}");

        switch (action.ToLowerInvariant())
        {
            case "attach":
                UniversalAttachmentManager.Instance?.TryAttach(currentItem.assetReference);
                HideTooltip();
                break;

            case "discard":
                if (currentItem == null)
                {
                    Debug.LogWarning("[Tenchu] ❌ Discard failed: currentItem is null.");
                    return;
                }

                bool removed = false;

                // Prefer removal by live instance if possible
                if (PlayerInventoryManager.Instance != null)
                {
                    removed = PlayerInventoryManager.Instance.RemoveItem(currentItem);
                    if (!removed)
                        removed = PlayerInventoryManager.Instance.RemoveItem(currentItem.ItemID);
                }
                else
                {
                    Debug.LogWarning("[Tenchu] ❌ Discard failed: PlayerInventoryManager.Instance is null.");
                }

                // Ensure global registry no longer references the instance
                if (InventoryStateManager.Instance != null)
                {
                    InventoryStateManager.Instance.UnregisterItem(currentItem);
                }
                else
                {
                    Debug.LogWarning("[Tenchu] ⚠️ InventoryStateManager.Instance is null; cannot unregister item.");
                }

                if (removed)
                {
                    if (activeSlot != null)
                    {
                        activeSlot.ClearSlot();
                        Debug.Log($"[Tenchu] 🧼 Cleared slot: {activeSlot.name}");
                    }

                    HideTooltip();
                    Debug.Log($"[Tenchu] 🗑️ Discarded item: {currentItem.ItemID}");
                }
                else
                {
                    Debug.LogWarning($"[Tenchu] ⚠️ Failed to remove item '{currentItem.ItemID}' from PlayerInventoryManager.");
                    // Still hide tooltip and attempt UI clear to avoid stale UI state
                    if (activeSlot != null)
                    {
                        activeSlot.ClearSlot();
                        Debug.Log($"[Tenchu] 🧼 Cleared slot (fallback): {activeSlot.name}");
                    }
                    HideTooltip();
                }
                break;

            case "equip":
                bool equipped = PlayerEquipManager.Instance?.TryEquipInventoryItemIfSlotEmpty(currentItem) ?? false;
                if (!equipped)
                {
                    Debug.LogWarning($"[Tooltip] 🚫 Equip blocked for '{currentItem.ItemID}' — slot may be occupied.");
                    PlayerEquipManager.Instance?.ShowEquipWarning("Hands slot is occupied. Unequip first.");
                }
                HideTooltip();
                break;

            case "use":
            case "fill":
            case "empty":
            case "combine":
            case "place":
            case "pour":
            case "eat":
            case "drink":
                usePanelManager?.OpenPanel(currentItem, activeSlot);
                break;

            case "open":
                Debug.Log($"[Tenchu] 🧿 Opening ritual triggered for '{currentItem.ItemID}'");
                OpenPanelManager.Instance?.ShowOpenersFor(currentItem);
                break;

            default:
                Debug.LogWarning($"[Tenchu] Unhandled action: {action}");
                break;
        }
    }

    private void InjectInventoryButtons(InventoryItem item)
    {
        foreach (var kvp in buttonMap)
        {
            kvp.Value.gameObject.SetActive(false);
            labelMap[kvp.Key].text = "";
            iconMap[kvp.Key].enabled = false;
        }

        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 : "";

        if (iconMap.TryGetValue(slot, out var icon))
        {
            var sprite = UsePanelIconRegistry.GetIcon(labelText);
            icon.sprite = sprite;
            icon.enabled = sprite != null;
        }
    }

    public void UpdateUses(InventoryItem item)
    {
        string conditionLabel = item.GetConditionLabel();
        Color conditionColor = ItemConditionResolver.GetConditionColorByDurability(item.Durability);

        ItemConditionLabelText.text = $"Condition: {conditionLabel}";
        ItemConditionLabelText.color = conditionColor;

        ItemDurabilityValueText.text = $"Durability: {item.Durability}/100";
        ItemDurabilityValueText.color = conditionColor;

        if (item.ZeroUseMode == ZeroUseMode.UntilDurabilityZero)
        {
            ItemUsesText.gameObject.SetActive(false);
        }
        else if (item.RemainingUses > 0)
        {
            float percent = item.MaxUses > 0 ? (float)item.RemainingUses / item.MaxUses : 0f;
            Color useColor = ItemConditionResolver.GetUseColorByPercentage(percent);

            ItemUsesText.text = $"Uses: {item.RemainingUses}";
            ItemUsesText.color = useColor;
            ItemUsesText.gameObject.SetActive(true);
        }
        else
        {
            switch (item.ZeroUseMode)
            {
                case ZeroUseMode.Unlimited:
                    ItemUsesText.text = "Uses: Unlimited";
                    ItemUsesText.color = ItemConditionResolver.GetUseColorByPercentage(1.0f);
                    ItemUsesText.gameObject.SetActive(true);
                    break;
                case ZeroUseMode.Zero:
                    ItemUsesText.text = "Uses: 0";
                    ItemUsesText.color = ItemConditionResolver.GetUseColorByPercentage(0f);
                    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
        };
    }

    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 = "";
            iconMap[kvp.Key].enabled = false;
        }

        ItemLiquidVolumeText.text = "";
        ItemLiquidVolumeText.gameObject.SetActive(false);

        ItemGasVolumeText.text = "";
        ItemGasVolumeText.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;
        ItemLiquidVolumeText.enabled = visible;
        ItemGasVolumeText.enabled = visible;
    }
}

Enter fullscreen mode Exit fullscreen mode

Inventorystatemanager

using Game.Inventory;
using System.Collections.Generic;
using UnityEngine;

public class InventoryStateManager : MonoBehaviour
{
    public static InventoryStateManager Instance { get; private set; }

    // Keyed by item.ItemID for backwards compatibility, but we also support removal by reference.
    private Dictionary<string, InventoryItem> liveItems = new();

    private void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
            Debug.Log("[Worm] 🧬 InventoryStateManager initialized.");
        }
        else
        {
            Destroy(gameObject);
        }
    }

    public void RegisterItem(InventoryItem item)
    {
        if (item == null || string.IsNullOrEmpty(item.ItemID))
        {
            Debug.LogWarning("[Worm] ❌ Attempted to register null or invalid item.");
            return;
        }

        // If there's already a registered instance for this ItemID, warn if it's a different reference.
        if (liveItems.TryGetValue(item.ItemID, out var existing))
        {
            if (!ReferenceEquals(existing, item))
            {
                Debug.LogWarning($"[Worm] ⚠️ RegisterItem: replacing existing registered instance for ID '{item.ItemID}'. Existing instance will be overwritten.");
            }
        }

        liveItems[item.ItemID] = item;
        Debug.Log($"[Worm] ✅ Registered item: {item.ItemID} | Volume={item.CurrentVolumeML} | LiquidType={item.CurrentLiquidType}");
    }

    public bool IsItemRegistered(string itemID)
    {
        return !string.IsNullOrEmpty(itemID) && liveItems.ContainsKey(itemID);
    }

    public InventoryItem GetLiveItem(string itemID)
    {
        if (string.IsNullOrEmpty(itemID))
        {
            Debug.LogWarning("[Worm] ❌ GetLiveItem called with null or empty ID.");
            return null;
        }

        if (liveItems.TryGetValue(itemID, out var item))
        {
            Debug.Log($"[Worm] 🧪 Retrieved live item: {itemID} | Volume={item.CurrentVolumeML} | LiquidType={item.CurrentLiquidType}");
            return item;
        }

        // Silent null is fine for normal flow; keep warning for debug visibility
        Debug.LogWarning($"[Worm] ⚠️ No live item found for ID: {itemID}");
        return null;
    }

    // Restore compatibility: mark a registered item as equipped (by ID)
    public void MarkItemAsEquipped(string itemID)
    {
        if (string.IsNullOrEmpty(itemID))
        {
            Debug.LogWarning("[Worm] ❌ MarkItemAsEquipped called with null or empty ID.");
            return;
        }

        if (liveItems.TryGetValue(itemID, out var item))
        {
            item.IsEquipped = true;
            Debug.Log($"[Worm] 🧬 Item '{itemID}' marked as equipped.");
        }
        else
        {
            Debug.LogWarning($"[Worm] ❌ Item '{itemID}' not found in registry when marking equipped.");
        }
    }

    // Overload: mark as equipped by instance reference
    public void MarkItemAsEquipped(InventoryItem item)
    {
        if (item == null)
        {
            Debug.LogWarning("[Worm] ❌ MarkItemAsEquipped called with null item.");
            return;
        }

        // Prefer reference-based lookup
        if (TryGetLiveItemByReference(item, out var registered))
        {
            registered.IsEquipped = true;
            Debug.Log($"[Worm] 🧬 Item '{registered.ItemID}' marked as equipped by reference.");
            return;
        }

        // Fallback by ID
        MarkItemAsEquipped(item.ItemID);
    }

    // New: Unregister by InventoryItem instance (preferred)
    public void UnregisterItem(InventoryItem item)
    {
        if (item == null)
        {
            Debug.LogWarning("[Worm] ❌ UnregisterItem called with null.");
            return;
        }

        // Try remove by reference first
        string keyToRemove = null;
        foreach (var kvp in liveItems)
        {
            if (ReferenceEquals(kvp.Value, item))
            {
                keyToRemove = kvp.Key;
                break;
            }
        }

        if (!string.IsNullOrEmpty(keyToRemove))
        {
            liveItems.Remove(keyToRemove);
            Debug.Log($"[Worm] 🗑️ Unregistered live item by reference: {keyToRemove}");
            return;
        }

        // Fallback: remove by matching ItemID if present
        if (!string.IsNullOrEmpty(item.ItemID) && liveItems.Remove(item.ItemID))
        {
            Debug.Log($"[Worm] 🗑️ Unregistered live item by ID fallback: {item.ItemID}");
            return;
        }

        Debug.LogWarning($"[Worm] ⚠️ UnregisterItem: item '{item.ItemID}' not found in registry.");
    }

    // Keep RemoveItem by ID for callers that only have itemID
    public void RemoveItem(string itemID)
    {
        if (string.IsNullOrEmpty(itemID))
        {
            Debug.LogWarning("[Worm] ❌ RemoveItem called with null or empty ID.");
            return;
        }

        if (liveItems.Remove(itemID))
        {
            Debug.Log($"[Worm] 🗑️ Removed item: {itemID} from live registry.");
        }
        else
        {
            Debug.LogWarning($"[Worm] ⚠️ Item '{itemID}' not found in live registry.");
        }
    }

    public Dictionary<string, InventoryItem> GetAllLiveItems()
    {
        return liveItems;
    }

    public void ClearAll()
    {
        liveItems.Clear();
        Debug.Log("[Worm] 🧹 InventoryStateManager cleared all live items.");
    }

    // Helper: attempt to find a live item by exact instance reference
    public bool TryGetLiveItemByReference(InventoryItem instance, out InventoryItem found)
    {
        found = null;
        if (instance == null) return false;
        foreach (var kvp in liveItems)
        {
            if (ReferenceEquals(kvp.Value, instance))
            {
                found = kvp.Value;
                return true;
            }
        }
        return false;
    }
}

Enter fullscreen mode Exit fullscreen mode

Usepanelmanager

using Game.Inventory;
using Game.Player;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using Game.UI;

public class UsePanelManager : MonoBehaviour
{
    public static UsePanelManager Instance { get; private set; }

    public bool IsOpen => panelRoot != null && panelRoot.activeInHierarchy;

    [Header("Panel Root")]
    [SerializeField] private GameObject panelRoot;

    [Header("UI References")]
    [SerializeField] private TMP_Text TitleText;
    [SerializeField] private Image BlackoutImage;
    [SerializeField] private Image ItemIcon;
    [SerializeField] private TMP_Text ItemName;

    [Header("Buttons")]
    [SerializeField] private Button Button2;
    [SerializeField] private Button Button3;
    [SerializeField] private Button Button4;
    [SerializeField] private Button Button5;
    [SerializeField] private Button Button6;

    [Header("Labels")]
    [SerializeField] private TMP_Text Button2Label;
    [SerializeField] private TMP_Text Button3Label;
    [SerializeField] private TMP_Text Button4Label;
    [SerializeField] private TMP_Text Button5Label;
    [SerializeField] private TMP_Text Button6Label;

    [Header("Icons")]
    [SerializeField] private Image Button2Icon;
    [SerializeField] private Image Button3Icon;
    [SerializeField] private Image Button4Icon;
    [SerializeField] private Image Button5Icon;
    [SerializeField] private Image Button6Icon;

    [Header("Use Sprite Registry")]
    [SerializeField] private UseSpriteRegistry useSpriteRegistry;

    [Header("Attachment Panel")]
    [SerializeField] private UniversalAttachmentPanel attachmentPanel;

    [Header("Story Node")]
    [SerializeField] private StoryNodeLoader storyNodeLoader;

    [Header("Close Button")]
    [SerializeField] private Button closeButton;

    private List<Button> buttons;
    private List<TMP_Text> labels;
    private List<Image> icons;

    private InventorySlotUI currentSlot;

    private void Awake()
    {
        Instance = this;
        buttons = new List<Button> { Button2, Button3, Button4, Button5, Button6 };
        labels = new List<TMP_Text> { Button2Label, Button3Label, Button4Label, Button5Label, Button6Label };
        icons = new List<Image> { Button2Icon, Button3Icon, Button4Icon, Button5Icon, Button6Icon };

        if (BlackoutImage != null)
            BlackoutImage.gameObject.SetActive(false);
    }

    public void OpenPanel(InventoryItem item, InventorySlotUI slot)
    {
        if (slot == null || item == null) return;

        if (panelRoot.activeInHierarchy)
        {
            HidePanel();
        }

        currentSlot = slot;

        panelRoot.SetActive(true);
        panelRoot.transform.localScale = Vector3.one;
        panelRoot.transform.SetAsLastSibling();

        var cg = panelRoot.GetComponent<CanvasGroup>();
        if (cg != null)
        {
            cg.alpha = 1f;
            cg.interactable = true;
            cg.blocksRaycasts = true;
        }

        HideAllButtons();
        UpdateItemDisplay(item);

        if (TitleText != null)
            TitleText.text = "Item Use Menu";

        if (BlackoutImage != null)
            BlackoutImage.gameObject.SetActive(true);

        // ✅ Only show contextual use actions (not Attach/Equip)
        for (int i = 1; i <= 5; i++)
        {
            string label = item.GetUseLabel(i);
            if (!string.IsNullOrEmpty(label) && i - 1 < buttons.Count)
            {
                if (label.Equals("Attach", System.StringComparison.OrdinalIgnoreCase) ||
                    label.Equals("Equip", System.StringComparison.OrdinalIgnoreCase))
                {
                    continue;
                }

                Sprite icon = GetIconForLabel(label);

                UnityEngine.Events.UnityAction action = () =>
                {
                    Debug.Log($"[UsePanelManager] Action triggered: {label} for item {item.ItemID}");

                    switch (label.ToLowerInvariant())
                    {
                        case "open":
                            if (ItemOpenerResolver.IsOpenerTool(item))
                            {
                                // Tool path → show containers, then close
                                OpenPanelManager.Instance?.ShowOpenersFor(item);
                                HidePanel();
                            }
                            else
                            {
                                // Container path → show tools, keep panel open
                                OpenPanelManager.Instance?.ShowOpenersFor(item);
                            }
                            break;

                        case "slice":
                            Debug.Log($"[UsePanelManager] Slice action placeholder for {item.ItemID}");
                            HidePanel();
                            break;

                        case "repair":
                            Debug.Log($"[UsePanelManager] Repair action placeholder for {item.ItemID}");
                            HidePanel();
                            break;

                        case "fill":
                        case "empty":
                        case "combine":
                        case "place":
                        case "pour":
                        case "eat":
                        case "drink":
                            InventoryStateManager.Instance?.RegisterItem(item);
                            HidePanel();
                            break;

                        default:
                            Debug.LogWarning($"[UsePanelManager] Unhandled action: {label}");
                            HidePanel();
                            break;
                    }
                };

                ShowButton(i - 1, label, icon, action);
            }
        }
    }

    private void HideAllButtons()
    {
        foreach (var btn in buttons)
        {
            if (btn != null)
            {
                btn.gameObject.SetActive(false);
                btn.onClick.RemoveAllListeners();
            }
        }

        foreach (var icon in icons)
        {
            if (icon != null)
            {
                icon.sprite = null;
                icon.gameObject.SetActive(false);
            }
        }
    }

    private void UpdateItemDisplay(InventoryItem item)
    {
        if (ItemIcon != null)
            ItemIcon.sprite = item?.Icon;

        if (ItemName != null)
            ItemName.text = item?.DisplayName ?? "Unknown Item";
    }

    private void ShowButton(int index, string label, Sprite icon, UnityEngine.Events.UnityAction action)
    {
        if (index < 0 || index >= buttons.Count) return;

        var btn = buttons[index];
        var lbl = labels[index];
        var icn = icons[index];

        if (btn != null)
        {
            btn.gameObject.SetActive(true);
            btn.onClick.RemoveAllListeners();
            btn.onClick.AddListener(action);
        }

        if (lbl != null)
            lbl.text = label;

        if (icn != null)
        {
            icn.sprite = icon;
            icn.enabled = icon != null;
            icn.gameObject.SetActive(icon != null);
        }
    }

    private Sprite GetIconForLabel(string label)
    {
        if (useSpriteRegistry == null || string.IsNullOrEmpty(label)) return null;
        return useSpriteRegistry.GetSprite(label);
    }

    public void HidePanel()
    {
        if (panelRoot != null)
        {
            panelRoot.SetActive(false);

            var cg = panelRoot.GetComponent<CanvasGroup>();
            if (cg != null)
            {
                cg.blocksRaycasts = false;
                cg.interactable = false;
            }
        }

        if (BlackoutImage != null)
            BlackoutImage.gameObject.SetActive(false);

        currentSlot = null;

        ItemTooltipManager.Instance?.HideTooltip();
    }

    public void ManualClosePanel()
    {
        Debug.Log("[UsePanelManager] 🧹 ManualClosePanel triggered via inspector button");
        HidePanel();
    }

    public void ForceClosePanel()
    {
        Debug.Log("[UsePanelManager] 🧹 ForceClosePanel invoked — forcibly collapsing panel");

        try
        {
            if (panelRoot != null)
            {
                panelRoot.SetActive(false);

                var cg = panelRoot.GetComponent<CanvasGroup>();
                if (cg != null)
                {
                    cg.alpha = 0f;
                    cg.interactable = false;
                    cg.blocksRaycasts = false;
                }
            }

            if (BlackoutImage != null)
                BlackoutImage.gameObject.SetActive(false);

            currentSlot = null;

            ItemTooltipManager.Instance?.HideTooltip();

            Debug.Log("[UsePanelManager] ✅ Panel forcibly closed and state reset");
        }
        catch (System.Exception ex)
        {
            Debug.LogError($"[UsePanelManager] ❌ ForceClosePanel failed: {ex.Message}");
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

Top comments (0)