Merge pull request 'feature/hp-mp-button' (#312) from feature/hp-mp-button into develop

Reviewed-on: https://git.pthub.vn/Unity/perfect-world-unity/pulls/312
This commit is contained in:
namth
2026-04-08 09:00:42 +00:00
9 changed files with 1447 additions and 19 deletions
File diff suppressed because it is too large Load Diff
@@ -341,5 +341,15 @@ namespace BrewMonster.Scripts
m_aItems[i].Freeze(false);
}
}
public void GetAllItemsOfType<T>(out List<T> items) where T : EC_IvtrItem
{
items = new List<T>();
for (int i = 0; i < m_aItems.Length; i++)
{
if (m_aItems[i] != null)
if (m_aItems[i] is T item)
items.Add(item);
}
}
}
}
@@ -18,6 +18,13 @@ namespace BrewMonster.Scripts
protected MEDICINE_ESSENCE m_pDBEssence;
protected int m_iLevelReq;
// Expose data needed by UI/shortcut logic.
// 暴露给UI/快捷栏需要的数据 (Expose data needed by UI/quickbar).
public int GetMajorTypeId() => (int)m_pDBMajorType.id;
public int GetLevelReq() => m_iLevelReq;
public int GetHpAddTotal() => m_pDBEssence.hp_add_total;
public int GetMpAddTotal() => m_pDBEssence.mp_add_total;
/// <summary>
/// Create medicine item (cac loai thuoc)
/// </summary>
@@ -171,14 +178,16 @@ namespace BrewMonster.Scripts
}
// Check item use condition
protected bool CheckUseCondition()
public override bool CheckUseCondition()
{
CECHostPlayer pHost = CECGameRun.Instance.GetHostPlayer();
if (pHost == null)
return false;
if (pHost.GetMaxLevelSofar() < m_iLevelReq)
return false;
return true;
return IsUseable() && GetCount() > 0;
}
// Get drop model for shown
@@ -0,0 +1,28 @@
using UnityEngine;
using TMPro;
namespace BrewMonster.Assets.PerfectWorld.Scripts.UI.GamePlay
{
public class AUIImageHPMPItem : AUIImagePicture
{
[SerializeField] TMP_Text m_TextAmount;
public void SetText(string text)
{
if(m_TextAmount != null)
m_TextAmount.text = text;
}
/// <summary>
/// HP/MP item button should only execute its assigned shortcut.
/// It must NOT open the assign-skill UI when empty.
/// </summary>
public override void Execute()
{
if (pSC != null)
{
pSC.Execute();
}
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 9b2df34c4410f46b5865b5178bfe3058
@@ -52,7 +52,7 @@ namespace BrewMonster.Assets.PerfectWorld.Scripts.UI.GamePlay
}
}
public void Execute()
public virtual void Execute()
{
if (pSC != null )
{
@@ -20,8 +20,8 @@ namespace BrewMonster
//[SerializeField] List<Image> m_aSkillImage = new List<Image>();
//[SerializeField] List<Button> m_aSkillButton = new List<Button>();
[SerializeField] List<AUIImagePicture> AUIImagePictureList = new List<AUIImagePicture>();
public int m_nCurPanel1 = 1;
public int m_nCurPanel2 = 1;
[SerializeField] AUIImageHPMPItem HpItemButton = new AUIImageHPMPItem();
[SerializeField] AUIImageHPMPItem MpItemButton = new AUIImageHPMPItem();
int currentListIndex = 0;
CECSkill assignedSkill = null;
@@ -30,8 +30,8 @@ namespace BrewMonster
/// </summary>
/// <returns></returns>
// public int m_nCurPanel1 = 1;
// public int m_nCurPanel2 = 1;
public int m_nCurPanel1 = 1;
public int m_nCurPanel2 = 1;
public bool m_bShowAll1 = false;
public bool m_bShowAll2 = false;
public int m_nDisplayPanels1 = 0;
@@ -432,9 +432,170 @@ namespace BrewMonster
pCell.Clear();
}
}
UpdateHpMpItem();
return true;
}
public void UpdateHpMpItem()
{
const int HP_MAJOR_TYPE_ID = 1794;
const int MP_MAJOR_TYPE_ID = 1802;
CECHostPlayer pHost = EC_Game.GetGameRun().GetHostPlayer();
if (pHost == null)
return;
// 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).
static bool TryFindBestMedicine(CECHostPlayer host, int majorTypeId, out int pack, out int slot, out EC_IvtrMedicine med)
{
pack = -1;
slot = -1;
med = null;
int bestHeal = int.MinValue;
int bestReqLevel = int.MaxValue;
int[] packsToSearch = new int[]
{
InventoryConst.IVTRTYPE_PACK,
InventoryConst.IVTRTYPE_TASKPACK,
};
for (int p = 0; p < packsToSearch.Length; p++)
{
int curPack = packsToSearch[p];
EC_Inventory inv = host.GetPack(curPack);
if (inv == null)
continue;
int size = inv.GetSize();
for (int i = 0; i < size; i++)
{
EC_IvtrItem item = inv.GetItem(i);
if (item is not EC_IvtrMedicine m)
continue;
if (m.GetMajorTypeId() != majorTypeId)
continue;
if (!m.CheckUseCondition())
continue;
int heal = 0;
// HP potion: use hp_add_total; MP potion: use mp_add_total.
// 回血药用 hp_add_total;回蓝药用 mp_add_total。
if (majorTypeId == 1794)
heal = m.GetHpAddTotal();
else if (majorTypeId == 1802)
heal = m.GetMpAddTotal();
int req = m.GetLevelReq();
if (heal > bestHeal || (heal == bestHeal && req < bestReqLevel))
{
bestHeal = heal;
bestReqLevel = req;
pack = curPack;
slot = i;
med = m;
}
}
}
return med != null && pack >= 0 && slot >= 0;
}
static bool TryGetShortcutItem(CECHostPlayer host, CECShortcut sc, out EC_IvtrItem item)
{
item = null;
if (host == null || sc is not CECSCItem scItem)
return false;
EC_Inventory inv = host.GetPack(scItem.GetInventory());
if (inv == null)
return false;
item = inv.GetItem(scItem.GetIvtrSlot());
return item != null;
}
void ApplyItemToButton(AUIImageHPMPItem button, CECShortcut sc, EC_IvtrItem item)
{
if (button == null)
return;
if (sc == null || item == null)
{
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();
// Amount text / 数量文本
int count = Mathf.Max(0, item.GetCount());
button.SetText(count > 0 ? count.ToString() : string.Empty);
// Cooldown overlay / 冷却遮罩
var clock = button.GetClockIcon();
int max = -1;
int cool = 0;
if (clock != null)
{
cool = item.GetCoolTime(out max);
if (cool > 0 && max > 0)
{
clock.SetProgressRange(0, max);
clock.SetProgressPos(max - cool);
clock.SetColor(new Color32(0, 0, 0, 128));
}
else
{
clock.SetProgressRange(0, 1);
clock.SetProgressPos(1);
}
}
// Interactable state should reflect usability.
// 可交互状态应该反映物品是否可使用
bool usable = item.CheckUseCondition();
bool cooling = (cool > 0 && max > 0);
button.SetInteract(usable && !cooling);
}
void EnsureAssignedAndRefresh(AUIImageHPMPItem button, int majorTypeId)
{
if (button == null)
return;
CECShortcut sc = button.GetDataPtr();
// If there is no valid shortcut item, auto assign the best available potion.
if (!TryGetShortcutItem(pHost, sc, out EC_IvtrItem item))
{
if (TryFindBestMedicine(pHost, majorTypeId, out int pack, out int slot, out EC_IvtrMedicine med))
{
var newSc = new CECSCItem();
newSc.Init(pack, slot, med);
sc = newSc;
item = med;
}
}
ApplyItemToButton(button, sc, item);
}
EnsureAssignedAndRefresh(HpItemButton, HP_MAJOR_TYPE_ID);
EnsureAssignedAndRefresh(MpItemButton, MP_MAJOR_TYPE_ID);
}
private void GetQuickBarNameAndSC(CECHostPlayer pHost, List<string> pszPanel, List<CECShortcutSet> pSCS, int panel9, int panel8)
{
string dlgName;
@@ -30,6 +30,7 @@ namespace BrewMonster.UI
private const string SKILL_ICONLIST_NAME = "iconlist_skill_multisprite";
private const string ACTION_ICONLIST_NAME = "ActionIcon/iconlist_action_multisprite";
private const string INVENTORY_ICONLIST_NAME = "UI/IconSprites/iconlist_ivtrm_multisprite";
private const string STATE_ICONLIST_NAME = "iconlist_state";
public CDlgMiniMap m_pDlgMiniMap;
@@ -375,11 +376,25 @@ namespace BrewMonster.UI
m_IconMap[(byte)EC_GAMEUI_ICONS.ICONS_SKILL] = (SKILL_ICONLIST_NAME, Resources.LoadAll<Sprite>(SKILL_ICONLIST_NAME));
m_IconMap[(byte)EC_GAMEUI_ICONS.ICONS_ACTION] = (ACTION_ICONLIST_NAME, Resources.LoadAll<Sprite>(ACTION_ICONLIST_NAME));
m_IconMap[(byte)EC_GAMEUI_ICONS.ICONS_INVENTORY] = (INVENTORY_ICONLIST_NAME, Resources.LoadAll<Sprite>(INVENTORY_ICONLIST_NAME));
m_IconMap[(byte)EC_GAMEUI_ICONS.ICONS_STATE] = (STATE_ICONLIST_NAME, Resources.LoadAll<Sprite>(STATE_ICONLIST_NAME));
}
public void SetCover(AUIImagePictureBase pImgPic, string nameImage, EC_GAMEUI_ICONS iCONS_TYPE)
{
pImgPic.SetImage(m_IconMap[(byte)iCONS_TYPE].Item2.FirstOrDefault(s => s.name == nameImage));
if (pImgPic == null)
return;
if (m_IconMap == null || !m_IconMap.TryGetValue((byte)iCONS_TYPE, out var tuple) || tuple.Item2 == null)
{
pImgPic.Clear();
return;
}
var sprite = tuple.Item2.FirstOrDefault(s => s != null && s.name == nameImage);
if (sprite != null)
pImgPic.SetImage(sprite);
else
pImgPic.Clear();
}
/// <summary>Refresh team UI (Arrange Team dialog). Called after team join/leave/member data.</summary>
+10 -9
View File
@@ -2752,9 +2752,9 @@ RectTransform:
- {fileID: 4504331075840543341}
m_Father: {fileID: 1361524257611413148}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 469.781, y: -34.01085}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 179.9124, y: 68.0217}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &8804506040386004496
@@ -5515,9 +5515,9 @@ RectTransform:
- {fileID: 2027606699309904338}
m_Father: {fileID: 1361524257611413148}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 109.9562, y: -34.01085}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 179.9124, y: 68.0217}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &6741821173640675138
@@ -17685,6 +17685,7 @@ MonoBehaviour:
m_contentRootSkill: {fileID: 0}
m_scrollRect: {fileID: 0}
m_windowScale: 1
totalSPText: {fileID: 0}
m_isEvil: 0
--- !u!1 &7758409605357852732
GameObject:
@@ -19226,9 +19227,9 @@ RectTransform:
- {fileID: 911293677621153352}
m_Father: {fileID: 1361524257611413148}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 289.8686, y: -34.01085}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 179.9124, y: 68.0217}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &5623009994815814977