diff --git a/Assets/PerfectWorld/Scripts/Managers/EC_Inventory.cs b/Assets/PerfectWorld/Scripts/Managers/EC_Inventory.cs index 0b455810fd..04fa47906f 100644 --- a/Assets/PerfectWorld/Scripts/Managers/EC_Inventory.cs +++ b/Assets/PerfectWorld/Scripts/Managers/EC_Inventory.cs @@ -81,6 +81,7 @@ namespace BrewMonster.Scripts var old = m_aItems[iSlot]; m_aItems[iSlot] = pItem; + EventBus.Publish(new InventoryChangedEvent()); return old; } @@ -92,6 +93,7 @@ namespace BrewMonster.Scripts } m_aItems[iSlot] = pItem; + EventBus.Publish(new InventoryChangedEvent()); } public EC_IvtrItem GetItem(int iSlot, bool bRemove = false) @@ -144,6 +146,7 @@ namespace BrewMonster.Scripts m_aItems[iSlot] = newItem; piLastSlot = iSlot; piLastAmount = iAmount; + EventBus.Publish(new InventoryChangedEvent()); return true; } if (slotItem.GetTemplateID() != tid) @@ -156,6 +159,7 @@ namespace BrewMonster.Scripts slotItem.AddAmount(add); piLastSlot = iSlot; piLastAmount = slotItem.GetCount(); + EventBus.Publish(new InventoryChangedEvent()); return true; } @@ -179,6 +183,7 @@ namespace BrewMonster.Scripts { piLastSlot = i; piLastAmount = slotItem.GetCount(); + EventBus.Publish(new InventoryChangedEvent()); return true; } } @@ -201,6 +206,7 @@ namespace BrewMonster.Scripts m_aItems[firstEmpty] = newItem; piLastSlot = firstEmpty; piLastAmount = iAmount; + EventBus.Publish(new InventoryChangedEvent()); return true; } @@ -240,6 +246,7 @@ namespace BrewMonster.Scripts } RemoveItem(iSrc, iAmount); + EventBus.Publish(new InventoryChangedEvent()); return true; } @@ -258,6 +265,7 @@ namespace BrewMonster.Scripts if (newCount <= 0) m_aItems[iSlot] = null; + EventBus.Publish(new InventoryChangedEvent()); return true; } @@ -352,4 +360,10 @@ namespace BrewMonster.Scripts } } } + + /// + /// Fired when any client-side inventory pack mutates (add/remove/move/merge). + /// Used by UI (quickbar HP/MP auto-pick) to re-evaluate best potions. + /// + public struct InventoryChangedEvent { } } diff --git a/Assets/PerfectWorld/Scripts/UI/GamePlay/AUIImageHPMPItem.cs b/Assets/PerfectWorld/Scripts/UI/GamePlay/AUIImageHPMPItem.cs index 4749f99444..4befe3fcc4 100644 --- a/Assets/PerfectWorld/Scripts/UI/GamePlay/AUIImageHPMPItem.cs +++ b/Assets/PerfectWorld/Scripts/UI/GamePlay/AUIImageHPMPItem.cs @@ -7,6 +7,30 @@ namespace BrewMonster.Assets.PerfectWorld.Scripts.UI.GamePlay public class AUIImageHPMPItem : AUIImagePicture { [SerializeField] TMP_Text m_TextAmount; + private int _lastTemplateId = int.MinValue; + private int _lastCount = int.MinValue; + + public bool TrySetTemplateId(int templateId) + { + if (_lastTemplateId == templateId) + return false; + _lastTemplateId = templateId; + return true; + } + + public bool TrySetCount(int count) + { + if (_lastCount == count) + return false; + _lastCount = count; + return true; + } + + public void ResetRenderCache() + { + _lastTemplateId = int.MinValue; + _lastCount = int.MinValue; + } public void SetText(string text) { if(m_TextAmount != null) diff --git a/Assets/PerfectWorld/Scripts/UI/GamePlay/CdlgQuickBar.cs b/Assets/PerfectWorld/Scripts/UI/GamePlay/CdlgQuickBar.cs index e79983f0ad..484641b086 100644 --- a/Assets/PerfectWorld/Scripts/UI/GamePlay/CdlgQuickBar.cs +++ b/Assets/PerfectWorld/Scripts/UI/GamePlay/CdlgQuickBar.cs @@ -23,8 +23,34 @@ namespace BrewMonster [SerializeField] AUIImageHPMPItem HpItemButton = new AUIImageHPMPItem(); [SerializeField] AUIImageHPMPItem MpItemButton = new AUIImageHPMPItem(); + private bool _hpmpReselectDirty = true; + int currentListIndex = 0; CECSkill assignedSkill = null; + public override void OnEnable() + { + base.OnEnable(); + EventBus.Subscribe(OnInventoryChanged); + EventBus.Subscribe(OnHostLevelUp); + _hpmpReselectDirty = true; + } + + public override void OnDisable() + { + EventBus.Unsubscribe(OnInventoryChanged); + EventBus.Unsubscribe(OnHostLevelUp); + base.OnDisable(); + } + + private void OnInventoryChanged(InventoryChangedEvent _) + { + _hpmpReselectDirty = true; + } + + private void OnHostLevelUp(CECHostPlayer.HostPlayerLevelUpUIEvent _) + { + _hpmpReselectDirty = true; + } /// /// Apply for a license remove later /// @@ -59,7 +85,7 @@ namespace BrewMonster AUIImagePicture pCell; CECSkill pSkill = new CECSkill(-1, -1); AUIClockIcon pClock; - int iIconFile, nMax = 0; + int iIconFile = 0, nMax = 0; int nCurPanel9 = GetCurPanel1(); int nCurPanel8 = GetCurPanel2(); @@ -444,6 +470,9 @@ namespace BrewMonster if (pHost == null) return; + bool forceReselect = _hpmpReselectDirty; + _hpmpReselectDirty = false; + // Pick the first level-usable medicine with the lowest required level. // Prefer highest heal amount first, then lowest required level. // 优先选择“回复量最高”的药品,其次选择需求等级最低的 (Prefer max heal, tie-break by level). @@ -519,6 +548,12 @@ namespace BrewMonster return item != null; } + static int GetHealScore(EC_IvtrMedicine med, int majorTypeId) + { + if (med == null) return 0; + return majorTypeId == 1794 ? med.GetHpAddTotal() : med.GetMpAddTotal(); + } + void ApplyItemToButton(AUIImageHPMPItem button, CECShortcut sc, EC_IvtrItem item) { if (button == null) @@ -526,23 +561,39 @@ namespace BrewMonster if (sc == null || item == null) { + button.ResetRenderCache(); button.Clear(); button.SetText(string.Empty); return; } - button.SetDataPtr(sc); - // Inventory UI uses templateId -> ResolveItemIconSprite, which is more reliable than string icon keys here. - // 背包UI使用 templateId -> ResolveItemIconSprite,这里也用同一套更稳定 (use same path as Inventory UI). - var sprite = EC_IvtrItemUtils.Instance.ResolveItemIconSprite(item.GetTemplateID()); - if (sprite != null) - button.SetImage(sprite); - else - button.Clear(); + // Skill-slot logic pattern: if the shortcut ptr hasn't changed and we're not forced to reselect, + // avoid re-applying icon/text every frame. + // 复用技能格的思路:快捷键指针没变且非强制刷新时,不要每帧重刷图标/数量。 + if (button.GetDataPtr() != sc) + { + button.SetDataPtr(sc); + button.ResetRenderCache(); + } + + int tid = item.GetTemplateID(); + if (button.TrySetTemplateId(tid)) + { + // Inventory UI path is more reliable than icon-file string matching. + // 背包UI的路径更稳定:按模板ID解析Sprite,而不是匹配图标文件名字符串 + var sp = EC_IvtrItemUtils.Instance.ResolveItemIconSprite(tid); + if (sp != null) + button.SetImage(sp); + else + button.Clear(); + } // Amount text / 数量文本 int count = Mathf.Max(0, item.GetCount()); - button.SetText(count > 0 ? count.ToString() : string.Empty); + if (button.TrySetCount(count)) + { + button.SetText(count > 0 ? count.ToString() : string.Empty); + } // Cooldown overlay / 冷却遮罩 var clock = button.GetClockIcon(); @@ -566,9 +617,13 @@ namespace BrewMonster // Interactable state should reflect usability. // 可交互状态应该反映物品是否可使用 - bool usable = item.CheckUseCondition(); + // Mirror host-side restrictions like `UseItemInPack` uses (CANDO_USEITEM etc.) + // 同步宿主侧限制(例如 CANDO_USEITEM),与 UseItemInPack 的限制保持一致 + bool hostCanUseItem = pHost.CanDo(CECHostPlayer.ActionCanDo.CANDO_USEITEM); + bool usable = hostCanUseItem && item.CheckUseCondition() && !item.IsFrozen(); bool cooling = (cool > 0 && max > 0); - button.SetInteract(usable && !cooling); + bool interact = usable && !cooling; + button.SetInteract(interact); } void EnsureAssignedAndRefresh(AUIImageHPMPItem button, int majorTypeId) @@ -578,15 +633,50 @@ namespace BrewMonster CECShortcut sc = button.GetDataPtr(); + // Resolve current shortcut item (if any). + bool hasCurrent = TryGetShortcutItem(pHost, sc, out EC_IvtrItem item); + + // Find best candidate (used both for initial assign and reselect-on-change). + bool hasBest = TryFindBestMedicine(pHost, majorTypeId, out int bestPack, out int bestSlot, out EC_IvtrMedicine bestMed); + // If there is no valid shortcut item, auto assign the best available potion. - if (!TryGetShortcutItem(pHost, sc, out EC_IvtrItem item)) + if (!hasCurrent) { - if (TryFindBestMedicine(pHost, majorTypeId, out int pack, out int slot, out EC_IvtrMedicine med)) + if (hasBest) { var newSc = new CECSCItem(); - newSc.Init(pack, slot, med); + newSc.Init(bestPack, bestSlot, bestMed); sc = newSc; - item = med; + item = bestMed; + hasCurrent = true; + } + } + else if (forceReselect && hasBest) + { + // Upgrade to best potion when inventory changes or level changes. + // 背包变化或升级时,自动切换到“最优药品” + var curMed = item as EC_IvtrMedicine; + if (curMed == null || curMed.GetMajorTypeId() != majorTypeId || !curMed.CheckUseCondition()) + { + var newSc = new CECSCItem(); + newSc.Init(bestPack, bestSlot, bestMed); + sc = newSc; + item = bestMed; + } + else + { + int curHeal = GetHealScore(curMed, majorTypeId); + int bestHeal = GetHealScore(bestMed, majorTypeId); + int curReq = curMed.GetLevelReq(); + int bestReq = bestMed.GetLevelReq(); + + if (bestHeal > curHeal || (bestHeal == curHeal && bestReq < curReq)) + { + var newSc = new CECSCItem(); + newSc.Init(bestPack, bestSlot, bestMed); + sc = newSc; + item = bestMed; + } } }