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