done skill combo

This commit is contained in:
VDH
2026-01-27 18:04:32 +07:00
parent 7d9f75de18
commit c22ca07a0f
22 changed files with 2026 additions and 901 deletions
@@ -17,9 +17,9 @@ namespace PerfectWorld.Scripts.Managers
/// <summary>
/// Arrow item class (cac loai mui ten)
/// </summary>
public class EC_IvtrArrow : EC_IvtrEquip
public class CECIvtrArrow : EC_IvtrEquip
{
protected IVTR_ESSENCE_ARROW m_Essence; // Arrow essence data
protected IVTR_ESSENCE_ARROW m_Essence; // Arrow essence data
// Data in database
protected PROJECTILE_TYPE m_pDBType;
@@ -29,30 +29,30 @@ namespace PerfectWorld.Scripts.Managers
/// </summary>
/// <param name="tid">Template id</param>
/// <param name="expire_date">Expire date</param>
public EC_IvtrArrow(int tid, int expire_date) : base(tid, expire_date)
public CECIvtrArrow(int tid, int expire_date) : base(tid, expire_date)
{
m_iCID = (int)InventoryClassId.ICID_ARROW;
m_iCID = (int)InventoryClassId.ICID_ARROW;
m_Essence = new IVTR_ESSENCE_ARROW();
// Get database data
elementdataman pDB = ElementDataManProvider.GetElementDataMan();
DATA_TYPE DataType = DATA_TYPE.DT_INVALID;
m_pDBEssence = (PROJECTILE_ESSENCE)pDB.get_data_ptr((uint)tid, ID_SPACE.ID_SPACE_ESSENCE, ref DataType);
m_pDBType = (PROJECTILE_TYPE)pDB.get_data_ptr((uint)m_pDBEssence.type, ID_SPACE.ID_SPACE_ESSENCE, ref DataType);
m_pDBEssence = (PROJECTILE_ESSENCE)pDB.get_data_ptr((uint)tid, ID_SPACE.ID_SPACE_ESSENCE, ref DataType);
m_pDBType = (PROJECTILE_TYPE)pDB.get_data_ptr((uint)m_pDBEssence.type, ID_SPACE.ID_SPACE_ESSENCE, ref DataType);
m_iPileLimit = m_pDBEssence.pile_num_max;
m_iPrice = m_pDBEssence.price;
m_iShopPrice = m_pDBEssence.shop_price;
m_iProcType = (int)m_pDBEssence.proc_type;
m_i64EquipMask = EC_IvtrType.EQUIP_MASK64_PROJECTILE;
m_iPileLimit = m_pDBEssence.pile_num_max;
m_iPrice = m_pDBEssence.price;
m_iShopPrice = m_pDBEssence.shop_price;
m_iProcType = (int)m_pDBEssence.proc_type;
m_i64EquipMask = EC_IvtrType.EQUIP_MASK64_PROJECTILE;
}
public EC_IvtrArrow(EC_IvtrArrow other) : base(other)
public CECIvtrArrow(CECIvtrArrow other) : base(other)
{
m_pDBType = other.m_pDBType;
m_pDBEssence = other.m_pDBEssence;
m_Essence = other.m_Essence;
m_pDBType = other.m_pDBType;
m_pDBEssence = other.m_pDBEssence;
m_Essence = other.m_Essence;
}
public override bool SetItemInfo(byte[] pInfoData, int iDataLen)
@@ -67,11 +67,11 @@ namespace PerfectWorld.Scripts.Managers
CECDataReader dr = new CECDataReader(pInfoData, iDataLen);
// Skip equip requirements and endurance
dr.Offset(5 * sizeof (int), CECDataReader.SEEK_CUR);
dr.Offset(5 * sizeof(int), CECDataReader.SEEK_CUR);
int iEssenceSize = dr.ReadInt();
//ASSERT(iEssenceSize == sizeof (IVTR_ESSENCE_ARROW));
m_Essence = new IVTR_ESSENCE_ARROW(dr.ReadData(iEssenceSize));
}
catch (Exception e)
@@ -105,6 +105,8 @@ namespace PerfectWorld.Scripts.Managers
}
return base.GetName(); // Fallback to base class method
}
public IVTR_ESSENCE_ARROW GetEssence() { return m_Essence; }
public PROJECTILE_TYPE GetDBSubType() { return m_pDBType; }
// Get item description text
protected override string GetNormalDesc(bool bRepair)
@@ -128,22 +130,22 @@ namespace PerfectWorld.Scripts.Managers
AddDescText(namecol, true, pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_NAMENUMBER), GetName(), m_iCount);
else
AddDescText(namecol, true, pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_NAME), GetName());
AddIDDescText();
AddExpireTimeDesc();
// Weapon requirement
AddDescText(white, true, pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_WEAPONREQ), m_Essence.iWeaponReqLow,
AddDescText(white, true, pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_WEAPONREQ), m_Essence.iWeaponReqLow,
m_Essence.iWeaponReqHigh, m_pDBType.Name);
// Damage enhance
if (m_pDBEssence.damage_enhance != 0)
if (m_pDBEssence.damage_enhance != 0)
{
AddDescText(-1, false, pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_ADDPHYDAMAGE));
AddDescText(-1, true, " %+d", m_pDBEssence.damage_enhance);
}
// Add addon properties
if (strAddon.Length > 0)
m_strDesc += strAddon;
@@ -153,7 +155,7 @@ namespace PerfectWorld.Scripts.Managers
// Suite description
AddSuiteDesc();
// Extend description
AddExtDescText();
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: f479e02d3a184f94bb046bc6cbf64ba1
@@ -67,7 +67,7 @@ namespace PerfectWorld.Scripts.Managers
/// <summary>
/// Weapon item class (cac loai vu khi)
/// </summary>
public class EC_IvtrWeapon : EC_IvtrEquip
public class CECIvtrWeapon : EC_IvtrEquip
{
//Attributes
//Weapon essence data
@@ -81,7 +81,7 @@ namespace PerfectWorld.Scripts.Managers
/// Constructor for weapon item (cac loai vu khi)
/// </summary>
/// <param name="tid">Template id</param>
public EC_IvtrWeapon(int tid, int expire_date) : base(tid, expire_date)
public CECIvtrWeapon(int tid, int expire_date) : base(tid, expire_date)
{
m_iCID = (int)InventoryClassId.ICID_WEAPON;
elementdataman pDB = ElementDataManProvider.GetElementDataMan();
@@ -99,7 +99,7 @@ namespace PerfectWorld.Scripts.Managers
RepairFee = m_pDBEssence.repairfee;
ReputationReq = m_pDBEssence.require_reputation;
}
public EC_IvtrWeapon(EC_IvtrWeapon other) : base(other)
public CECIvtrWeapon(CECIvtrWeapon other) : base(other)
{
m_pDBEssence = other.m_pDBEssence;
m_pDBMajorType = other.m_pDBMajorType;
@@ -436,5 +436,45 @@ namespace PerfectWorld.Scripts.Managers
{
return m_Essence.weapon_level;
}
// Clone item
public override EC_IvtrItem Clone()
{
return new CECIvtrWeapon(this);
}
// Get equipment type
public virtual int GetEquipmentType()
{
return 0; // EQUIP_WEAPON = 0
}
// The weapon is range weapon ?
public bool IsRangeWeapon()
{
return m_Essence.weapon_type == (int)WEAPON_TYPE.WEAPON_TYPE_RANGE;
}
// Get essence data
public IVTR_ESSENCE_WEAPON GetEssence()
{
return m_Essence;
}
// Get database data
public WEAPON_MAJOR_TYPE GetDBMajorType()
{
return m_pDBMajorType;
}
public WEAPON_SUB_TYPE GetDBSubType()
{
return m_pDBSubType;
}
public WEAPON_ESSENCE GetDBEssence()
{
return m_pDBEssence;
}
}
}
@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: d252cb1fcb2e946688fd6836548fd0d4
@@ -42,7 +42,7 @@ namespace BrewMonster.Scripts.Managers
if (edm == null) return CacheAndReturn(templateId, "");
uint id = unchecked((uint)templateId);
DATA_TYPE dATA_TYPE = default;
object data = edm.get_data_ptr(id, ID_SPACE.ID_SPACE_ESSENCE,ref dATA_TYPE);
object data = edm.get_data_ptr(id, ID_SPACE.ID_SPACE_ESSENCE, ref dATA_TYPE);
string name = ExtractNameFromElement(data);
if (string.IsNullOrEmpty(name))
{
@@ -107,15 +107,15 @@ namespace BrewMonster.Scripts.Managers
private Sprite LoadIconSpriteByKey(string key)
{
if (string.IsNullOrEmpty(key)) return null;
// Load multi-sprite atlas if not already loaded
if (_multiSpriteAtlas == null)
{
LoadMultiSpriteAtlas();
}
if (_multiSpriteAtlas == null) return null;
// Try to find sprite by name in the atlas
if (_spriteNameToIndexCache.TryGetValue(key, out var index))
{
@@ -124,7 +124,7 @@ namespace BrewMonster.Scripts.Managers
return _multiSpriteAtlas[index];
}
}
// Fallback: try to find by name directly in the atlas
foreach (var sprite in _multiSpriteAtlas)
{
@@ -133,7 +133,7 @@ namespace BrewMonster.Scripts.Managers
return sprite;
}
}
// Try lowercase/uppercase variants as fallback
foreach (var sprite in _multiSpriteAtlas)
{
@@ -143,10 +143,10 @@ namespace BrewMonster.Scripts.Managers
return sprite;
}
}
return null;
}
private void LoadMultiSpriteAtlas()
{
try
@@ -156,7 +156,7 @@ namespace BrewMonster.Scripts.Managers
if (atlasSprites != null && atlasSprites.Length > 0)
{
_multiSpriteAtlas = atlasSprites;
// Build name-to-index cache for faster lookups
_spriteNameToIndexCache.Clear();
for (int i = 0; i < atlasSprites.Length; i++)
@@ -166,7 +166,7 @@ namespace BrewMonster.Scripts.Managers
_spriteNameToIndexCache[atlasSprites[i].name] = i;
}
}
}
else
{
@@ -280,7 +280,7 @@ namespace BrewMonster.Scripts.Managers
{
if (data == null) return "";
var t = data.GetType();
// Debug: Log all available fields and properties
// Debug.Log($"[Inventory] Data type: {t.Name}");
var fields = t.GetFields(BindingFlags.Public | BindingFlags.Instance);
@@ -301,7 +301,7 @@ namespace BrewMonster.Scripts.Managers
// Debug.Log($"[Inventory] Method: {m.Name} ({m.ReturnType.Name})");
// }
// }
// Prefer decoding the raw fields first to control encoding (Unicode for Vietnamese),
// then fall back to any string properties if needed.
var fieldName = t.GetField("name", BindingFlags.Public | BindingFlags.Instance);
@@ -313,7 +313,7 @@ namespace BrewMonster.Scripts.Managers
{
var rawData = string.Join(",", arr.Take(Math.Min(10, arr.Length)));
}
// Vietnamese names are stored as wide chars; decode as Unicode first.
var s = ByteToStringUtils.UshortArrayToUnicodeString(arr);
// Debug log to see what we're getting
@@ -476,62 +476,62 @@ namespace BrewMonster.Scripts.Managers
return hex;
}
public int GetPileLimit(int templateId)
{
if (templateId <= 0) return 1;
if (_pileLimitCache.TryGetValue(templateId, out var cached)) return cached;
int limit = 1;
try
{
var edm = ElementDataManProvider.GetElementDataMan();
if (edm != null)
{
uint id = unchecked((uint)templateId);
DATA_TYPE dt = DATA_TYPE.DT_INVALID;
object data = edm.get_data_ptr(id, ID_SPACE.ID_SPACE_ESSENCE, ref dt);
limit = ExtractPileLimitFromElement(data);
}
}
catch { }
if (limit <= 0) limit = 1;
_pileLimitCache[templateId] = limit;
return limit;
}
public int GetPileLimit(int templateId)
{
if (templateId <= 0) return 1;
if (_pileLimitCache.TryGetValue(templateId, out var cached)) return cached;
int limit = 1;
try
{
var edm = ElementDataManProvider.GetElementDataMan();
if (edm != null)
{
uint id = unchecked((uint)templateId);
DATA_TYPE dt = DATA_TYPE.DT_INVALID;
object data = edm.get_data_ptr(id, ID_SPACE.ID_SPACE_ESSENCE, ref dt);
limit = ExtractPileLimitFromElement(data);
}
}
catch { }
if (limit <= 0) limit = 1;
_pileLimitCache[templateId] = limit;
return limit;
}
private int ExtractPileLimitFromElement(object data)
{
if (data == null) return 1;
var t = data.GetType();
// Common field/property names across item essences
string[] names = new[]
{
"pilelimit", "pile_limit", "pileLimit", "stack", "stack_max", "stackMax", "max_stack", "maxStack"
};
foreach (var name in names)
{
var f = t.GetField(name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
if (f != null && (f.FieldType == typeof(int) || f.FieldType == typeof(uint) || f.FieldType == typeof(short) || f.FieldType == typeof(ushort) || f.FieldType == typeof(byte)))
{
try
{
var val = f.GetValue(data);
int limit = Convert.ToInt32(val);
if (limit > 0) return limit;
}
catch { }
}
var p = t.GetProperty(name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
if (p != null && (p.PropertyType == typeof(int) || p.PropertyType == typeof(uint) || p.PropertyType == typeof(short) || p.PropertyType == typeof(ushort) || p.PropertyType == typeof(byte)))
{
try
{
var val = p.GetValue(data, null);
int limit = Convert.ToInt32(val);
if (limit > 0) return limit;
}
catch { }
}
}
private int ExtractPileLimitFromElement(object data)
{
if (data == null) return 1;
var t = data.GetType();
// Common field/property names across item essences
string[] names = new[]
{
"pilelimit", "pile_limit", "pileLimit", "stack", "stack_max", "stackMax", "max_stack", "maxStack"
};
foreach (var name in names)
{
var f = t.GetField(name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
if (f != null && (f.FieldType == typeof(int) || f.FieldType == typeof(uint) || f.FieldType == typeof(short) || f.FieldType == typeof(ushort) || f.FieldType == typeof(byte)))
{
try
{
var val = f.GetValue(data);
int limit = Convert.ToInt32(val);
if (limit > 0) return limit;
}
catch { }
}
var p = t.GetProperty(name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
if (p != null && (p.PropertyType == typeof(int) || p.PropertyType == typeof(uint) || p.PropertyType == typeof(short) || p.PropertyType == typeof(ushort) || p.PropertyType == typeof(byte)))
{
try
{
var val = p.GetValue(data, null);
int limit = Convert.ToInt32(val);
if (limit > 0) return limit;
}
catch { }
}
}
return 1;
}
}
@@ -703,6 +703,7 @@ namespace BrewMonster.Scripts.Managers
public string m_strDesc = ""; // Item description
public bool m_bIsInNPCPack; // true, this item is in NPC package
public bool m_bLocalDetailData; // true, data from GetDetailDataFromLocal
public int m_iCurEndurance; // Current endurance
public EC_Inventory m_pDescIvtr; // Inventory only used to get item description
@@ -735,6 +736,8 @@ namespace BrewMonster.Scripts.Managers
m_bIsInNPCPack = false;
m_bLocalDetailData = false;
m_pDescIvtr = null;
m_iCurEndurance = 0;
}
public EC_IvtrItem(int tid, int expire_date)
@@ -808,13 +811,13 @@ namespace BrewMonster.Scripts.Managers
DATA_TYPE DataType = DATA_TYPE.DT_INVALID;
object data = ElementDataManProvider.GetElementDataMan().get_data_ptr((uint)tid, ID_SPACE.ID_SPACE_ESSENCE, ref DataType);
//Debug.Log("Create item data: DataType: " + DataType);
switch(DataType)
switch (DataType)
{
case DATA_TYPE.DT_WEAPON_ESSENCE:
pItem = new EC_IvtrWeapon(tid, expire_date);
pItem = new CECIvtrWeapon(tid, expire_date);
break;
case DATA_TYPE.DT_PROJECTILE_ESSENCE:
pItem = new EC_IvtrArrow(tid, expire_date);
pItem = new CECIvtrArrow(tid, expire_date);
break;
case DATA_TYPE.DT_ARMOR_ESSENCE:
pItem = new EC_IvtrArmor(tid, expire_date);
@@ -1258,6 +1261,7 @@ namespace BrewMonster.Scripts.Managers
#endregion
#region Simple property-style accessors (1:1 with C++)
public int GetCurEndurance() { return m_iCurEndurance; }
public int GetClassID() => m_iCID;
public int GetTemplateID() => m_tid;
@@ -1342,7 +1346,7 @@ namespace BrewMonster.Scripts.Managers
{
//itemdataman* pItemDataMan = g_pGame->GetItemDataMan();
object pData_temp = itemdataman.get_item_for_sell((uint)m_tid);
if(pData_temp == null)
if (pData_temp == null)
{
SetItemInfo(null, 0);
SetLocalProps();
@@ -1412,34 +1416,34 @@ namespace BrewMonster.Scripts.Managers
}
// use specific color for the item price
if((int)DescriptipionMsg.ITEMDESC_COL_WHITE == col)
if ((int)DescriptipionMsg.ITEMDESC_COL_WHITE == col)
{
if( m_iPrice >= 100000000) // 100 million
if (m_iPrice >= 100000000) // 100 million
col = (int)DescriptipionMsg.ITEMDESC_COL_GREEN;
else if ( m_iPrice >= 10000000) // 10 million
else if (m_iPrice >= 10000000) // 10 million
col = (int)DescriptipionMsg.ITEMDESC_COL_DARKGOLD;
else if ( m_iPrice >= 1000000) // 1 million
else if (m_iPrice >= 1000000) // 1 million
col = (int)DescriptipionMsg.ITEMDESC_COL_YELLOW;
}
CECStringTab pDescTab = EC_Game.GetItemDesc();
if (m_iScaleType == (int)ScaleType.SCALE_OFFLINESHOP)
{
AddDescText(col, false, pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_PRICE));
string s1,s2;
string s1, s2;
BuildPriceNumberStr(m_iPrice, out s1);
if (GetCount()>1)
if (GetCount() > 1)
{
s2 = (m_iPrice * (long)GetCount()).ToString();
s2 = (m_iPrice * (long)GetCount()).ToString();
AddDescText(-1, false, " %s (%s)", s1, s2);
}
else
AddDescText(-1, false, " %s", s1);
AddDescText(-1, false, " %s", s1);
}
else if (m_iScaleType == (int)ScaleType.SCALE_BOOTH || m_tid == 21652) // 21651: yinpiao
{
string s1;
string s1;
BuildPriceNumberStr(m_iPrice, out s1);
AddDescText(col, false, pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_UNITPRICE));
@@ -1453,7 +1457,7 @@ namespace BrewMonster.Scripts.Managers
AddDescText(col, false, pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_PRICE));
AddDescText(-1, false, " %s (%s)", s1, s2);
}
}
else
{
string s1;
@@ -1644,7 +1648,7 @@ namespace BrewMonster.Scripts.Managers
var pDescTab = EC_Game.GetItemDesc();
// Note: ITEMDESC_COL2_BRIGHTBLUE constant - adjust based on actual string table / 注意:ITEMDESC_COL2_BRIGHTBLUE常量 - 根据实际字符串表调整
int green = (int)DescriptipionMsg.ITEMDESC_COL2_BRIGHTBLUE; // ITEMDESC_COL2_BRIGHTBLUE placeholder - adjust this value
if (m_iCID != (int)InventoryClassId.ICID_GOBLIN) // goblin does not need to display these special properties / 地精不需要显示这些特殊属性
{
// Exact C++ logic: (PROC_NO_USER_TRASH) || (!PROC_BINDING && (PROC_DROPWHENDIE || ...))
@@ -1675,7 +1679,7 @@ namespace BrewMonster.Scripts.Managers
m_strDesc += "\\r";
if (pDescTab != null && pDescTab.IsInitialized())
{
string desc = pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_DEAD_PROTECT);
string desc = pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_DEAD_PROTECT);
if (!string.IsNullOrEmpty(desc))
m_strDesc += desc;
}
@@ -1685,7 +1689,7 @@ namespace BrewMonster.Scripts.Managers
m_strDesc += "\\r";
if (pDescTab != null && pDescTab.IsInitialized())
{
string desc = pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_NO_DROP);
string desc = pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_NO_DROP);
if (!string.IsNullOrEmpty(desc))
m_strDesc += desc;
}
@@ -1695,7 +1699,7 @@ namespace BrewMonster.Scripts.Managers
m_strDesc += "\\r";
if (pDescTab != null && pDescTab.IsInitialized())
{
string desc = pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_NO_TRADE);
string desc = pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_NO_TRADE);
if (!string.IsNullOrEmpty(desc))
m_strDesc += desc;
}
@@ -1705,7 +1709,7 @@ namespace BrewMonster.Scripts.Managers
m_strDesc += "\\r";
if (pDescTab != null && pDescTab.IsInitialized())
{
string desc = pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_NO_PLAYER_TRADE);
string desc = pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_NO_PLAYER_TRADE);
if (!string.IsNullOrEmpty(desc))
m_strDesc += desc;
}
@@ -1715,7 +1719,7 @@ namespace BrewMonster.Scripts.Managers
m_strDesc += "\\r";
if (pDescTab != null && pDescTab.IsInitialized())
{
string desc = pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_LEAVE_SCENE_DISAPEAR);
string desc = pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_LEAVE_SCENE_DISAPEAR);
if (!string.IsNullOrEmpty(desc))
m_strDesc += desc;
}
@@ -1725,7 +1729,7 @@ namespace BrewMonster.Scripts.Managers
m_strDesc += "\\r";
if (pDescTab != null && pDescTab.IsInitialized())
{
string desc = pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_USE_AFTER_PICK_UP);
string desc = pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_USE_AFTER_PICK_UP);
if (!string.IsNullOrEmpty(desc))
m_strDesc += desc;
}
@@ -1735,7 +1739,7 @@ namespace BrewMonster.Scripts.Managers
m_strDesc += "\\r";
if (pDescTab != null && pDescTab.IsInitialized())
{
string desc = pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_DROP_WHEN_DEAD);
string desc = pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_DROP_WHEN_DEAD);
if (!string.IsNullOrEmpty(desc))
m_strDesc += desc;
}
@@ -1745,7 +1749,7 @@ namespace BrewMonster.Scripts.Managers
m_strDesc += "\\r";
if (pDescTab != null && pDescTab.IsInitialized())
{
string desc = pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_DROP_WHEN_OFFLINE);
string desc = pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_DROP_WHEN_OFFLINE);
if (!string.IsNullOrEmpty(desc))
m_strDesc += desc;
}
@@ -1755,7 +1759,7 @@ namespace BrewMonster.Scripts.Managers
m_strDesc += "\\r";
if (pDescTab != null && pDescTab.IsInitialized())
{
string desc = pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_UNREPAIRABLE);
string desc = pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_UNREPAIRABLE);
if (!string.IsNullOrEmpty(desc))
m_strDesc += desc;
}
@@ -1765,7 +1769,7 @@ namespace BrewMonster.Scripts.Managers
m_strDesc += "\\r";
if (pDescTab != null && pDescTab.IsInitialized())
{
string desc = pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_NO_USER_TRASH);
string desc = pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_NO_USER_TRASH);
if (!string.IsNullOrEmpty(desc))
m_strDesc += desc;
}
@@ -1806,10 +1810,10 @@ namespace BrewMonster.Scripts.Managers
protected void AddIDDescText()
{
// Optional: show internal id for debugging
#if UNITY_EDITOR
AddDescText(0, true, "ID: {0}", m_tid);
#endif
#if UNITY_EDITOR
AddDescText(0, true, "ID: {0}", m_tid);
#endif
}
protected void AddBindDescText()
@@ -1944,7 +1948,7 @@ namespace BrewMonster.Scripts.Managers
public string GetExtendedDescText()
{
string result = string.Empty;
// Get extended description from item_ext_desc.txt using tid / 使用tid从item_ext_desc.txt获取扩展描述
string szExtDesc = TryGetItemExtDesc();
// Note: Original C++ had early return commented out / 注意:原始C++代码的早期返回被注释掉了
@@ -51,25 +51,25 @@ namespace BrewMonster.Scripts.Managers
SIZE_GENERALCARD_EQUIPIVTR = SIZE_ALL_EQUIPIVTR - EQUIPIVTR_GENERALCARD1,
}
#region Inventory Essence Struct
#pragma pack(1)
#pragma pack(1)
public struct IVTR_ESSENCE_WEAPON
{
public short weapon_type;
public short weapon_dealy;
public int weapon_class;
public int weapon_level;
public int require_projectile; // Ҫҩ
public int damage_low; // Сֵ
public int damage_high; // ֵ
public int magic_damage_low; // ħ
public int magic_damage_high; // ħ
// public int attack; //
public int attack_speed;
public float attack_range;
public float attack_short_range;
public IVTR_ESSENCE_WEAPON( byte[] data)
public short weapon_type;
public short weapon_dealy;
public int weapon_class;
public int weapon_level;
public int require_projectile; // Ҫҩ
public int damage_low; // Сֵ
public int damage_high; // ֵ
public int magic_damage_low; // ħ
public int magic_damage_high; // ħ
// public int attack; //
public int attack_speed;
public float attack_range;
public float attack_short_range;
public IVTR_ESSENCE_WEAPON(byte[] data)
{
CECDataReader dr = new (data, data.Length);
CECDataReader dr = new(data, data.Length);
weapon_type = dr.ReadShort();
weapon_dealy = dr.ReadShort();
weapon_class = dr.ReadInt();
@@ -86,14 +86,14 @@ namespace BrewMonster.Scripts.Managers
};
public struct IVTR_ESSENCE_ARROW
{
public int dwBowMask;
public int iDamage;
public int iDamageScale;
public int iWeaponReqLow;
public int iWeaponReqHigh;
public int dwBowMask;
public int iDamage;
public int iDamageScale;
public int iWeaponReqLow;
public int iWeaponReqHigh;
public IVTR_ESSENCE_ARROW(byte[] data)
{
CECDataReader dr = new (data, data.Length);
CECDataReader dr = new(data, data.Length);
dwBowMask = dr.ReadInt();
iDamage = dr.ReadInt();
iDamageScale = dr.ReadInt();
@@ -103,20 +103,20 @@ namespace BrewMonster.Scripts.Managers
};
public struct IVTR_ESSENCE_DECORATION
{
public int damage;
public int magic_damage;
public int defense;
public int armor;
public int[] resistance;
public int damage;
public int magic_damage;
public int defense;
public int armor;
public int[] resistance;
public IVTR_ESSENCE_DECORATION(byte[] data)
{
CECDataReader dr = new (data, data.Length);
CECDataReader dr = new(data, data.Length);
damage = dr.ReadInt();
magic_damage = dr.ReadInt();
defense = dr.ReadInt();
armor = dr.ReadInt();
resistance = new int[InventoryConst.NUM_MAGICCLASS];
for(int i = 0; i < InventoryConst.NUM_MAGICCLASS; i++)
for (int i = 0; i < InventoryConst.NUM_MAGICCLASS; i++)
{
resistance[i] = dr.ReadInt();
}
@@ -134,12 +134,12 @@ namespace BrewMonster.Scripts.Managers
{
Debug.Log("IVTR_ESSENCE_ARMOR: data.Length: " + data.Length);
resistance = new int[InventoryConst.NUM_MAGICCLASS];
CECDataReader dr = new (data, data.Length);
CECDataReader dr = new(data, data.Length);
defense = dr.ReadInt();
armor = dr.ReadInt();
mp_enhance = dr.ReadInt();
hp_enhance = dr.ReadInt();
for(int i = 0; i < InventoryConst.NUM_MAGICCLASS; i++)
for (int i = 0; i < InventoryConst.NUM_MAGICCLASS; i++)
{
resistance[i] = dr.ReadInt();
}
@@ -152,7 +152,7 @@ namespace BrewMonster.Scripts.Managers
public ushort gender;
public IVTR_ESSENCE_FASHION(byte[] data)
{
CECDataReader dr = new (data, data.Length);
CECDataReader dr = new(data, data.Length);
require_level = dr.ReadInt();
color = dr.ReadUShort();
gender = dr.ReadUShort();
@@ -160,18 +160,18 @@ namespace BrewMonster.Scripts.Managers
};
public struct IVTR_ESSENCE_FLYSWORD
{
public int cur_time;
public int max_time;
public short require_level;
public char level;
public char improve_level;
public int profession;
public int time_per_element;
public float speed_increase;
public float speed_increase2;
public int cur_time;
public int max_time;
public short require_level;
public char level;
public char improve_level;
public int profession;
public int time_per_element;
public float speed_increase;
public float speed_increase2;
public IVTR_ESSENCE_FLYSWORD(byte[] data)
{
CECDataReader dr = new (data, data.Length);
CECDataReader dr = new(data, data.Length);
cur_time = dr.ReadInt();
max_time = dr.ReadInt();
require_level = dr.ReadShort();
@@ -193,46 +193,46 @@ namespace BrewMonster.Scripts.Managers
};
public struct IVTR_ESSENCE_AUTOHP
{
public int hp_left;
public int hp_left;
public float trigger;
public IVTR_ESSENCE_AUTOHP(byte[] data)
{
CECDataReader dr = new (data, data.Length);
CECDataReader dr = new(data, data.Length);
hp_left = dr.ReadInt();
trigger = dr.ReadFloat();
}
};
public struct IVTR_ESSENCE_AUTOMP
{
public int mp_left;
public int mp_left;
public float trigger;
public IVTR_ESSENCE_AUTOMP(byte[] data)
{
CECDataReader dr = new (data, data.Length);
CECDataReader dr = new(data, data.Length);
mp_left = dr.ReadInt();
trigger = dr.ReadFloat();
}
};
public struct IVTR_ESSENCE_PETEGG
{
public int req_level;
public int req_class;
public int honor_point;
public int pet_tid;
public int pet_vis_tid;
public int pet_egg_tid;
public int pet_class;
public short level;
public ushort color;
public int exp;
public int skill_point;
public ushort name_len;
public ushort skill_count;
public int req_level;
public int req_class;
public int honor_point;
public int pet_tid;
public int pet_vis_tid;
public int pet_egg_tid;
public int pet_class;
public short level;
public ushort color;
public int exp;
public int skill_point;
public ushort name_len;
public ushort skill_count;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
public ushort[] name;
public ushort[] name;
public IVTR_ESSENCE_PETEGG(byte[] data)
{
CECDataReader dr = new (data, data.Length);
CECDataReader dr = new(data, data.Length);
req_level = dr.ReadInt();
req_class = dr.ReadInt();
honor_point = dr.ReadInt();
@@ -247,7 +247,7 @@ namespace BrewMonster.Scripts.Managers
name_len = dr.ReadUShort();
skill_count = dr.ReadUShort();
name = new ushort[8];
for(int i = 0; i < 8; i++)
for (int i = 0; i < 8; i++)
{
name[i] = dr.ReadUShort();
}
@@ -270,14 +270,14 @@ namespace BrewMonster.Scripts.Managers
public short agility;
public short vitality;
public short energy;
public short total_genius;
public short[] genius ;
public short total_genius;
public short[] genius;
public short refine_level;
public int stamina;
public int status_value;
public int stamina;
public int status_value;
public _GOBLIN_DATA(byte[] data)
{
CECDataReader dr = new (data, data.Length);
CECDataReader dr = new(data, data.Length);
exp = dr.ReadUInt();
level = dr.ReadShort();
total_attribute = dr.ReadShort();
@@ -287,7 +287,7 @@ namespace BrewMonster.Scripts.Managers
energy = dr.ReadShort();
total_genius = dr.ReadShort();
genius = new short[5];
for(int i = 0; i < 5; i++)
for (int i = 0; i < 5; i++)
{
genius[i] = dr.ReadShort();
}
@@ -301,7 +301,7 @@ namespace BrewMonster.Scripts.Managers
public int skill_cnt;
public IVTR_ESSENCE_GOBLIN(byte[] data)
{
CECDataReader dr = new (data, data.Length);
CECDataReader dr = new(data, data.Length);
// Calculate size manually: uint(4) + 7*short(14) + short[5](10) + short(2) + 2*int(8) = 40 bytes
const int GOBLIN_DATA_SIZE = 40;
this.data = new _GOBLIN_DATA(dr.ReadData(GOBLIN_DATA_SIZE));
@@ -357,8 +357,8 @@ namespace BrewMonster.Scripts.Managers
// int exp;
// int rebirth_times;
};
#pragma pack()
#pragma pack()
#endregion
public static class EC_IvtrType
{
@@ -728,9 +728,9 @@ namespace BrewMonster.Scripts.Managers
{
if (sub.id != armorSubTypeId) continue;
uint mask = sub.equip_mask;
// Check finger slots first - try to find an empty one
if ((mask & (1u << (int)IndexOfIteminEquipmentInventory.EQUIPIVTR_FINGER1)) != 0 ||
if ((mask & (1u << (int)IndexOfIteminEquipmentInventory.EQUIPIVTR_FINGER1)) != 0 ||
(mask & (1u << (int)IndexOfIteminEquipmentInventory.EQUIPIVTR_FINGER2)) != 0)
{
var availableFingerSlot = GetAvailableFingerSlot();
@@ -739,7 +739,7 @@ namespace BrewMonster.Scripts.Managers
return availableFingerSlot;
}
}
// For other slots, return the first matching one (original behavior)
if ((mask & (1u << (int)IndexOfIteminEquipmentInventory.EQUIPIVTR_HEAD)) != 0) return IndexOfIteminEquipmentInventory.EQUIPIVTR_HEAD;
if ((mask & (1u << (int)IndexOfIteminEquipmentInventory.EQUIPIVTR_SHOULDER)) != 0) return IndexOfIteminEquipmentInventory.EQUIPIVTR_SHOULDER;
@@ -764,9 +764,9 @@ namespace BrewMonster.Scripts.Managers
{
if (sub.id != decorationSubTypeId) continue;
uint mask = sub.equip_mask;
// Check finger slots first - try to find an empty one
if ((mask & (1u << (int)IndexOfIteminEquipmentInventory.EQUIPIVTR_FINGER1)) != 0 ||
if ((mask & (1u << (int)IndexOfIteminEquipmentInventory.EQUIPIVTR_FINGER1)) != 0 ||
(mask & (1u << (int)IndexOfIteminEquipmentInventory.EQUIPIVTR_FINGER2)) != 0)
{
var availableFingerSlot = GetAvailableFingerSlot();
@@ -775,7 +775,7 @@ namespace BrewMonster.Scripts.Managers
return availableFingerSlot;
}
}
// For other slots, return the first matching one (original behavior)
if ((mask & (1u << (int)IndexOfIteminEquipmentInventory.EQUIPIVTR_NECK)) != 0) return IndexOfIteminEquipmentInventory.EQUIPIVTR_NECK;
if ((mask & (1u << (int)IndexOfIteminEquipmentInventory.EQUIPIVTR_WAIST)) != 0) return IndexOfIteminEquipmentInventory.EQUIPIVTR_WAIST;
@@ -801,19 +801,25 @@ namespace BrewMonster.Scripts.Managers
{
return IndexOfIteminEquipmentInventory.EQUIPIVTR_FINGER1;
}
// Check if FINGER2 slot is empty
var finger2Item = equipInv?.GetItem((int)IndexOfIteminEquipmentInventory.EQUIPIVTR_FINGER2, false);
if (finger2Item == null)
{
return IndexOfIteminEquipmentInventory.EQUIPIVTR_FINGER2;
}
// Both slots are occupied, return FINGER1 as fallback (original behavior)
return IndexOfIteminEquipmentInventory.EQUIPIVTR_FINGER1;
}
}
public enum InventoryType
// Weapon type
public enum WeaponType
{
WEAPONTYPE_MELEE = 0,
WEAPONTYPE_RANGE = 1,
};
public enum InventoryType
{
IVTRTYPE_PACK = 0, // Normal pack
IVTRTYPE_EQUIPPACK, // Equipment
@@ -194,7 +194,6 @@ namespace BrewMonster
// iTotalTime: total cooling time, 0 means to use cooling time in database
public void StartCooling(int iTotalTime, int iStartCnt)
{
BMLogger.LogError($"StartCooling iTotalTime={iTotalTime}, iStartCnt={iStartCnt}");
m_iCoolTime = iTotalTime != 0 ? iTotalTime : GetCoreCoolingTime();
m_iCoolCnt = iStartCnt;
m_bCooling = true;
@@ -302,6 +301,7 @@ namespace BrewMonster
{
return m_pSkillCore != null ? m_pSkillCore.GetCoolingTime() : 0;
}
public int GetComboSkPreSkill() { return m_pSkillCore.GetComboSkPreSkill(); }
public int GetExecuteTime()
{
@@ -68,6 +68,10 @@ namespace BrewMonster
NotifyObservers(&change);*/
}
}
public ComboSkillState GetComboSkillState()
{
return m_comboSkillState;
}
void SetActiveComboSkills(Dictionary<uint, int> dic)
{
Dictionary<uint, int> newList = new Dictionary<uint, int>();
@@ -112,6 +116,18 @@ namespace BrewMonster
}
}
}
public bool IsActiveComboSkill(uint skillID)
{
for (int i = 0; i < m_activeSkills.Count; i++)
{
if (m_activeSkills[i] == skillID && m_skillTimeOut[i] == false)
{
return true;
}
}
return false;
}
public bool IsComboPreSkill(uint skillID)
{
return m_preSkillSet.Contains(skillID);
@@ -951,6 +951,8 @@ namespace CSNetwork
else if (ISNPCID(pCmd3.caster))
EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_ENCHANTRESULT, MANAGER_INDEX.MAN_NPC, 0, pDataBuf, pCmdHeader);
break;
case CommandID.HOST_STOP_SKILL:
case CommandID.SELF_SKILL_INTERRUPTED:
case CommandID.SKILL_PERFORM:
EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_CASTSKILL, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader);
break;
@@ -759,7 +759,7 @@ namespace BrewMonster
#region Placeholder Classes
// These classes are referenced but not defined in the provided files
// They should be implemented separately based on EC_Shortcut.h/cpp
[Serializable]
public class CECShortcut
{
protected int m_iSCType;
@@ -2,13 +2,16 @@ using BrewMonster.Network;
using BrewMonster.Scripts;
using BrewMonster.Scripts.Skills;
using CSNetwork.GPDataType;
using System;
using System.Diagnostics;
using UnityEngine;
namespace BrewMonster
{
/// <summary>
/// Shortcut representing a combo skill group.
/// </summary>
[Serializable]
public class CECSCSkillGrp : CECShortcut
{
private int m_iGroupIdx;
@@ -100,8 +100,8 @@ namespace BrewMonster.Scripts.Skills
public int move_env; //ƶ // Movement environment
public bool is_combat; //Ƿս״̬ // Whether in combat state
public int hp; //ǰhp // Current HP
public int max_hp; //hp // Maximum HP
// public ComboSkillState combo_state; // // Combo skill state
public int max_hp; //hp // Maximum HP
public ComboSkillState combo_state; // // Combo skill state
};
public struct GoblinUseRequirement
@@ -242,11 +242,11 @@ namespace BrewMonster.Scripts.Skills
}
// ѧϰ󾳽ȼ?
public virtual int GetRequiredRealmLevel() { return 0; }
public virtual Dictionary<uint, int> GetRequiredSkill() => new Dictionary<uint, int>();
public virtual int GetShowOrder() { return 0; }
public virtual int SetLevel(int level) { return 0; }
public static int SetLevel(uint id, int level)
@@ -398,13 +398,13 @@ namespace BrewMonster.Scripts.Skills
skill = Skill.Create(id, ilevel);
if (skill == null)
return 5;
int ret = 0;
SkillWrapper wrapper = SkillWrapper.Instance;
if (wrapper.IsOverridden(id))
return 11;
int srank, prank;
srank = skill.GetRank();
prank = info.rank;
@@ -424,17 +424,17 @@ namespace BrewMonster.Scripts.Skills
ret = 4;
else if (info.level < skill.GetRequiredLevel())
ret = 2;
if (ret != 0)
{
return ret;
}
if (info.sp < skill.GetRequiredSp())
ret = 1;
else if (info.money < skill.GetRequiredMoney())
ret = 6;
var pre_skills = skill.GetRequiredSkill();
foreach (var kvp in pre_skills)
{
@@ -454,69 +454,70 @@ namespace BrewMonster.Scripts.Skills
if (ability > 0 && wrapper.GetAbility(id) < ability)
ret = 10;
}
if (info.realm_level < skill.GetRequiredRealmLevel())
ret = 12;
return ret;
}
// 0:ɹ 1:SP 2:
// 3: 4:ܸ 5:ID
// 6:Ǯ 7:С 8:ûм
// 9:ȼ 10:޲ 11:ְҵƥ
// 12:޲ְҵƥ
// 3: 4:ܸ 5:ID
// 6:Ǯ 7:С 8:ûм
// 9:ȼ 10:޲ 11:ְҵƥ
// 12:޲ְҵƥ
public static int GoblinLearn(uint id, GoblinRequirement info, int level)
{
Skill s = Skill.Create(id, level);
if(s == null)
if (s == null)
return 5;
if(level<1 || level> s.GetMaxLevel())
if (level < 1 || level > s.GetMaxLevel())
return 3;
if(s.GetCls() != 258)
if (s.GetCls() != 258)
return 7;
int ret = 0;
int[] iReqGen = new int[5] {0, 0, 0, 0, 0};
int[] iReqGen = new int[5] { 0, 0, 0, 0, 0 };
int iReqLevel = s.GetRequiredLevel();
// iReqLevelΪ7λλΪȼǰ5λΪλΪ
int iLevelRequirement = iReqLevel%100;
if(info.level < iLevelRequirement)
int iLevelRequirement = iReqLevel % 100;
if (info.level < iLevelRequirement)
return 9;
iReqLevel /= 100;
int i;
for(i=0;i<5;i++)
for (i = 0; i < 5; i++)
{
iReqGen[4-i] = iReqLevel%10;
iReqGen[4 - i] = iReqLevel % 10;
iReqLevel /= 10;
}
for(i=0;i<5;i++)
for (i = 0; i < 5; i++)
{
if(info.genius[i] < iReqGen[4-i])
if (info.genius[i] < iReqGen[4 - i])
return 2;
}
if(info.sp < s.GetRequiredSp())
if (info.sp < s.GetRequiredSp())
ret = 1;
//else if(info.money<s->GetRequiredMoney(id, level))
// ret = 6;
if(info.mp < s.GetMpCost() &&
if (info.mp < s.GetMpCost() &&
((s.GetCls() != 0) && (((1 << info.profession) & s.GetCls()) == 0)))
ret = 12;
else if(info.mp < s.GetMpCost())
else if (info.mp < s.GetMpCost())
ret = 10;
else if((s.GetCls() != 0) && (((1 << info.profession) & s.GetCls()) == 0))
else if ((s.GetCls() != 0) && (((1 << info.profession) & s.GetCls()) == 0))
ret = 11;
return ret;
}
public virtual int GetComboSkPreSkill() { return 0; }
public static int GetComboSkPreSkill(uint id)
{
SkillStub s = SkillStub.GetStub(id);
SkillStub s = SkillStub.GetStub(id);
if (s != null)
{
return s.combosk_preskill;
@@ -0,0 +1,820 @@
# Flow: CreateSkillGroupShortcut - C++ Implementation
## Overview
This document describes the complete flow for creating a skill combo group shortcut in the C++ codebase. A skill group shortcut (`CECSCSkillGrp`) represents a combo skill sequence that can be placed in the quick bar and executed with a single click.
---
## Entry Points
### 1. **From UI Drag & Drop** (DlgDragDrop.cpp:603)
When a user drags a combo skill icon from the combo skill panel to a shortcut slot:
```cpp
// DlgDragDrop.cpp:595-604
if (strstr(pObjSrc->GetName(), "Img_ConSkill")) // Combo skill icon
{
int nCombo = pObjSrc->GetData(); // Get combo group index (1-based)
int iSlot = atoi(pObjOver->GetName() + strlen("Item_")); // Get target slot (1-based)
CECShortcutSet *pSCS = CECGameUIMan::GetSCSByDlg(pDlgOver->GetName());
if (!pSCS->GetShortcut(iSlot-1) || !g_pGame->GetConfigs()->GetGameSettings().bLockQuickBar)
pSCS->CreateSkillGroupShortcut(iSlot - 1, nCombo - 1); // Convert to 0-based
}
```
**Flow:**
1. User drags combo skill icon (`Img_ConSkill`) to shortcut slot
2. Extract combo group index from source object data (1-based)
3. Extract target slot number from destination object name (1-based)
4. Get the appropriate `CECShortcutSet` based on dialog
5. Check if slot is empty or quick bar is unlocked
6. Call `CreateSkillGroupShortcut` with 0-based indices
---
## Detailed Analysis: UI Drag & Drop Flow
### **Step 1: Combo Skill Icon Creation & Data Initialization**
**Location:** `DlgSkillSubOther.cpp:71-99` - `UpdateComboSkill()`
This function is called when the skill dialog is shown (`OnShowDialog()` at line 193) and creates/updates all combo skill icons:
```cpp
// DlgSkillSubOther.cpp:71-99
void CDlgSkillSubOther::UpdateComboSkill() {
EC_VIDEO_SETTING setting = GetGame()->GetConfigs()->GetVideoSettings();
// Loop through all possible combo skill groups (typically 8-12)
for(int i = 0; i < EC_COMBOSKILL_NUM; i++)
{
// Create icon name: "Img_ConSkill01", "Img_ConSkill02", etc.
AString strName;
strName.Format("Img_ConSkill%02d", i + 1);
// Get the image picture object from dialog
PAUIIMAGEPICTURE pImage = static_cast<PAUIIMAGEPICTURE>(GetDlgItem(strName));
if( pImage )
{
// Check if this combo skill group is configured (has an icon)
if( setting.comboSkill[i].nIcon != 0 )
{
// Set the icon image from sprite sheet
pImage->SetCover(
GetGameUIMan()->m_pA2DSpriteIcons[CECGameUIMan::ICONS_SKILLGRP],
setting.comboSkill[i].nIcon + 1);
// ⭐ KEY: Store the combo group index (1-based) in the icon's data
// i is 0-based (0, 1, 2, ...), so i+1 is 1-based (1, 2, 3, ...)
pImage->SetData(i + 1);
// Set a flag pointer (just indicates this is a valid combo skill)
pImage->SetDataPtr((void*)1);
// Set tooltip/hint text
ACString strText;
strText.Format(GetStringFromTable(804), i);
pImage->SetHint(strText);
}
else
{
// Empty/invalid combo skill - clear the icon
pImage->SetCover(NULL, -1);
pImage->SetData(0); // No data = invalid
pImage->SetDataPtr(NULL);
pImage->SetHint(_AL(""));
}
}
}
}
```
**Key Points:**
- **Icon Naming:** Icons are named `"Img_ConSkill%02d"` where `%02d` is the 1-based index (01, 02, 03, ...)
- **Data Storage:** The combo group index is stored via `SetData(i + 1)`:
- `i` is 0-based array index (0, 1, 2, ...)
- `i + 1` is 1-based group number (1, 2, 3, ...)
- This 1-based value is what gets retrieved during drag & drop
- **Validation:** If `nIcon == 0`, the combo skill doesn't exist, so `SetData(0)` marks it as invalid
- **Icon Source:** Icon image comes from `ICONS_SKILLGRP` sprite sheet at index `nIcon + 1`
---
### **Step 2: User Initiates Drag Operation**
**Location:** `DlgSkillSubOther.cpp:219-232` - `OnEventLButtonDownCombo()`
When user clicks and holds on a combo skill icon:
```cpp
// DlgSkillSubOther.cpp:219-232
void CDlgSkillSubOther::OnEventLButtonDownCombo(WPARAM wParam, LPARAM lParam, AUIObject * pObj) {
// ⭐ Validation: Check if icon has valid data (combo skill exists)
if( pObj->GetData() == 0 )
return; // No combo skill configured, can't drag
// Get viewport coordinates
A3DVIEWPORTPARAM *p = m_pA3DEngine->GetActiveViewport()->GetParam();
POINT pt =
{
GET_X_LPARAM(lParam) - p->X,
GET_Y_LPARAM(lParam) - p->Y,
};
// Store drag start position
GetGameUIMan()->m_ptLButtonDown = pt;
// ⭐ Start drag & drop operation
// This passes the source object (pObj) which contains the data
GetGameUIMan()->InvokeDragDrop(this, pObj, pt);
}
```
**Key Points:**
- **Event Mapping:** The event is mapped via `AUI_ON_EVENT("Img_ConSkill*", WM_LBUTTONDOWN, OnEventLButtonDownCombo)` (line 19)
- **Validation:** Checks `GetData() == 0` to ensure combo skill exists before allowing drag
- **Data Preservation:** The `pObj` (icon object) is passed to `InvokeDragDrop()`, preserving the data stored in Step 1
---
### **Step 3: Drag & Drop Processing**
**Location:** `DlgDragDrop.cpp:79-127` - `OnEventLButtonUp()`
When user releases mouse button, the drag & drop system processes the drop:
```cpp
// DlgDragDrop.cpp:79-127 (simplified)
void CDlgDragDrop::OnEventLButtonUp(WPARAM wParam, LPARAM lParam, AUIObject *pObj)
{
// Get the source object that was being dragged
PAUIOBJECT pObjSrc = (PAUIOBJECT)pItem->GetDataPtr("ptr_AUIObject");
if( !pObjSrc )
return;
// Get destination (where mouse was released)
PAUIDIALOG pDlgOver;
PAUIOBJECT pObjOver;
// ... hit test to find what's under the mouse ...
GetGameUIMan()->HitTest(x, y, &pDlgOver, &pObjOver, this);
// Route to appropriate handler based on source dialog
// ... routing logic ...
// For skill-related drags, calls:
OnSkillDragDrop(pDlgSrc, pObjSrc, pDlgOver, pObjOver, pIvtrSrc);
}
```
---
### **Step 4: Skill Drag & Drop Handler**
**Location:** `DlgDragDrop.cpp:589-627` - `OnSkillDragDrop()`
This is where the combo skill icon data is extracted and used:
```cpp
// DlgDragDrop.cpp:589-627
void CDlgDragDrop::OnSkillDragDrop(PAUIDIALOG pDlgSrc, PAUIOBJECT pObjSrc,
PAUIDIALOG pDlgOver, PAUIOBJECT pObjOver, CECIvtrItem* pIvtrSrc)
{
// Check if dropping on quick bar dialog
if( pDlgOver && strstr(pDlgOver->GetName(), "Win_Quickbar")
&& pObjOver && strstr(pObjOver->GetName(), "Item_") )
{
// ⭐ Check if source is a combo skill icon
if( strstr(pObjSrc->GetName(), "Img_ConSkill") )
{
// ⭐ Extract combo group index from icon's stored data (1-based)
int nCombo = pObjSrc->GetData(); // Returns the value set in UpdateComboSkill()
// Extract target slot number from destination object name
// Destination name format: "Item_1", "Item_2", etc. (1-based)
int iSlot = atoi(pObjOver->GetName() + strlen("Item_")); // "Item_1" -> 1
// Get the shortcut set for this dialog
CECShortcutSet *pSCS = CECGameUIMan::GetSCSByDlg(pDlgOver->GetName());
// Check if slot is empty or quick bar is unlocked
if( !pSCS->GetShortcut(iSlot-1) ||
!g_pGame->GetConfigs()->GetGameSettings().bLockQuickBar )
{
// ⭐ Create skill group shortcut
// Convert both indices to 0-based:
// - iSlot: 1-based -> 0-based (slot 1 -> index 0)
// - nCombo: 1-based -> 0-based (group 1 -> index 0)
pSCS->CreateSkillGroupShortcut(iSlot - 1, nCombo - 1);
}
}
// Handle regular skill drag (not combo)
else
{
CECSkill *pSkill = (CECSkill *)pObjSrc->GetDataPtr("ptr_CECSkill");
// ... create regular skill shortcut ...
}
}
}
```
**Key Points:**
- **Icon Identification:** `strstr(pObjSrc->GetName(), "Img_ConSkill")` identifies combo skill icons
- **Data Retrieval:** `pObjSrc->GetData()` retrieves the 1-based group index stored in Step 1
- **Slot Extraction:** Destination slot is extracted from object name `"Item_N"` where N is 1-based
- **Index Conversion:** Both indices are converted from 1-based to 0-based before calling `CreateSkillGroupShortcut()`
---
### **Complete Data Flow Diagram**
```
┌─────────────────────────────────────────────────────────────┐
│ STEP 1: Icon Creation (UpdateComboSkill) │
│ Location: DlgSkillSubOther.cpp:71-99 │
└──────────────────────┬──────────────────────────────────────┘
┌──────────────────────────────────────┐
│ For each combo skill group (i=0..N): │
│ │
│ 1. Create icon name: │
│ "Img_ConSkill%02d" (i+1) │
│ │
│ 2. If combo exists (nIcon != 0): │
│ - Set icon image │
│ - ⭐ SetData(i + 1) │
│ (stores 1-based group index) │
│ - SetDataPtr((void*)1) │
│ - Set hint text │
└──────────────────┬───────────────────┘
┌──────────────────────────────────────┐
│ Icon Object Created │
│ - Name: "Img_ConSkill01", etc. │
│ - Data: 1, 2, 3, ... (1-based) │
│ - DataPtr: (void*)1 │
│ - Icon: Sprite sheet image │
└──────────────────┬───────────────────┘
│ (User clicks and drags)
┌─────────────────────────────────────────────────────────────┐
│ STEP 2: Drag Initiation (OnEventLButtonDownCombo) │
│ Location: DlgSkillSubOther.cpp:219-232 │
└──────────────────────┬──────────────────────────────────────┘
┌──────────────────────────────────────┐
│ 1. Validate: GetData() != 0 │
│ 2. Get mouse position │
│ 3. InvokeDragDrop(this, pObj, pt) │
│ (pObj contains the data!) │
└──────────────────┬───────────────────┘
│ (User drags to quick bar)
┌─────────────────────────────────────────────────────────────┐
│ STEP 3: Drop Processing (OnEventLButtonUp) │
│ Location: DlgDragDrop.cpp:79-127 │
└──────────────────────┬──────────────────────────────────────┘
┌──────────────────────────────────────┐
│ 1. Get source object (pObjSrc) │
│ 2. Hit test to find destination │
│ 3. Route to OnSkillDragDrop() │
└──────────────────┬───────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ STEP 4: Skill Drag Handler (OnSkillDragDrop) │
│ Location: DlgDragDrop.cpp:589-627 │
└──────────────────────┬──────────────────────────────────────┘
┌──────────────────────────────────────┐
│ 1. Check: strstr(name, "Img_ConSkill")│
│ 2. ⭐ Extract: nCombo = GetData() │
│ (retrieves 1-based group index) │
│ 3. Extract: iSlot from "Item_N" │
│ 4. Get shortcut set │
│ 5. Validate slot/quick bar │
│ 6. CreateSkillGroupShortcut( │
│ iSlot-1, nCombo-1) │
│ (convert to 0-based) │
└──────────────────┬───────────────────┘
┌──────────────────────────────────────┐
│ CreateSkillGroupShortcut() │
│ Creates CECSCSkillGrp with │
│ group index (0-based) │
└──────────────────────────────────────┘
```
---
### **Data Storage Summary**
| Component | Storage Method | Value Type | Example |
|-----------|---------------|------------|---------|
| **Combo Group Index** | `AUIObject::SetData(int)` | 1-based integer | 1, 2, 3, ... |
| **Icon Name** | Object name | String pattern | "Img_ConSkill01", "Img_ConSkill02" |
| **Icon Image** | `SetCover()` | Sprite sheet index | `nIcon + 1` |
| **Validation Flag** | `SetDataPtr((void*)1)` | Pointer flag | `(void*)1` = valid, `NULL` = invalid |
**Important Notes:**
1. **The icon DOES contain the data needed** - it's stored via `SetData(i + 1)` during icon creation
2. **The data is 1-based** - stored as 1, 2, 3, ... but converted to 0-based (0, 1, 2, ...) when creating the shortcut
3. **Data is validated** - `GetData() == 0` means no combo skill, so drag is prevented
4. **Icon name pattern** - `"Img_ConSkill*"` is used to identify combo skill icons during drag & drop
---
### 2. **From Config Data Loading** (EC_ShortcutSet.cpp:600-616)
When loading shortcut configuration from server/save file:
```cpp
// EC_ShortcutSet.cpp:600-616
case CECShortcut::SCT_SKILLGRP:
{
if (dwVer >= 3) // Version check - skill groups added in version 3
{
int iGroupIdx = *(int*)pData; // Read group index from config data
pData += sizeof(int);
if (iGroupIdx >= 0)
CreateSkillGroupShortcut(iSlot, iGroupIdx);
}
else
{
ASSERT(0);
return false;
}
break;
}
```
**Flow:**
1. Read shortcut type from config data
2. If type is `SCT_SKILLGRP` and version >= 3:
- Read group index from binary data
- Validate group index (>= 0)
- Call `CreateSkillGroupShortcut`
---
### 3. **From AssignSkillGrpShortcut** (EC_HostPlayer.cpp:7934-7941)
When assigning skill group shortcuts from configuration array:
```cpp
// EC_HostPlayer.cpp:7934-7941
void CECHostPlayer::AssignSkillGrpShortcut(
const std::vector<SkillGrpShortCutConfig> & skillGrpSCConfigArray,
CECShortcutSet** aSCSets)
{
std::vector<SkillGrpShortCutConfig>::const_iterator it;
for (it = skillGrpSCConfigArray.begin(); it != skillGrpSCConfigArray.end(); it++)
{
if(it->groupIndex != -1) // -1 means invalid/empty
aSCSets[it->setNum]->CreateSkillGroupShortcut(it->slotNum, it->groupIndex);
}
}
```
**Flow:**
1. Iterate through skill group shortcut configuration array
2. For each valid config (groupIndex != -1):
- Get the appropriate shortcut set by `setNum`
- Call `CreateSkillGroupShortcut` with slot and group index
**Note:** Before calling this, `ConvertSkillGrpShortcut` is called to validate combo skills exist:
```cpp
// EC_HostPlayer.cpp:7915-7923
void CECHostPlayer::ConvertSkillGrpShortcut(std::vector<SkillGrpShortCutConfig> & skillGrpSCConfigArray)
{
std::vector<SkillGrpShortCutConfig>::iterator it;
for (it = skillGrpSCConfigArray.begin(); it != skillGrpSCConfigArray.end(); it++)
{
EC_VIDEO_SETTING vs = g_pGame->GetConfigs()->GetVideoSettings();
if (vs.comboSkill[it->groupIndex].nIcon == 0) // Combo skill doesn't exist
it->groupIndex = -1; // Mark as invalid
}
}
```
---
## Core Implementation
### **CreateSkillGroupShortcut** (EC_ShortcutSet.cpp:135-150)
```cpp
// EC_ShortcutSet.cpp:135-150
bool CECShortcutSet::CreateSkillGroupShortcut(int iSlot, int iGroupIdx)
{
// 1. Create new CECSCSkillGrp object
CECSCSkillGrp* pSkillGrpSC = new CECSCSkillGrp;
if (!pSkillGrpSC)
return false;
// 2. Initialize with group index
if (!pSkillGrpSC->Init(iGroupIdx))
{
delete pSkillGrpSC;
a_LogOutput(1, "CECShortcutSet::CreateSkillGroupShortcut, Failed to initialize skill group shortcut");
return false;
}
// 3. Set shortcut in slot (replaces any existing shortcut at this slot)
SetShortcut(iSlot, pSkillGrpSC);
return true;
}
```
**Flow:**
1. **Allocate** new `CECSCSkillGrp` object
2. **Initialize** with group index
3. **Set** shortcut in the specified slot (automatically releases old shortcut if exists)
---
## CECSCSkillGrp Class
### **Class Definition** (EC_Shortcut.h:282-309)
```cpp
class CECSCSkillGrp : public CECShortcut
{
public:
CECSCSkillGrp();
CECSCSkillGrp(const CECSCSkillGrp& src);
virtual ~CECSCSkillGrp() {}
// Initialize object
bool Init(int iGroupIdx);
// Virtual functions from CECShortcut
virtual CECShortcut* Clone();
virtual bool Execute(); // Executes the combo skill
virtual const char* GetIconFile();
virtual const wchar_t* GetDesc();
virtual int GetCoolTime(int* piMax=NULL);
private:
int m_iGroupIdx; // Combo skill group index (0-based)
CECString m_strDesc; // Description text
};
```
### **Initialization** (EC_Shortcut.cpp:696-703)
```cpp
// EC_Shortcut.cpp:696-703
bool CECSCSkillGrp::Init(int iGroupIdx)
{
m_iGroupIdx = iGroupIdx; // Store group index
// Format description: "Skill Group {index}"
CECStringTab* pStrTab = g_pGame->GetItemDesc();
m_strDesc.Format(pStrTab->GetWideString(CMDDESC_SKILLGROUP), m_iGroupIdx);
return true;
}
```
**Flow:**
1. Store group index
2. Format description string from string table
3. Return success
### **Execution** (EC_Shortcut.cpp:712-717)
```cpp
// EC_Shortcut.cpp:712-717
bool CECSCSkillGrp::Execute()
{
CECHostPlayer* pHost = g_pGame->GetGameRun()->GetHostPlayer();
pHost->ApplyComboSkill(m_iGroupIdx); // Apply combo skill with stored group index
return true;
}
```
**Flow:**
1. Get host player instance
2. Call `ApplyComboSkill` with the stored group index
3. This triggers the combo skill sequence execution
---
## ApplyComboSkill Flow
### **ApplyComboSkill** (EC_HostPlayer.cpp:7710-7737)
```cpp
// EC_HostPlayer.cpp:7710-7737
bool CECHostPlayer::ApplyComboSkill(int iGroup, bool bIgnoreAtkLoop, int iForceAtk)
{
a_LogOutput(1, "HoangDEv: ApplyComboSkill - Applying combo skill, group=%d, target=%d, ignoreAtkLoop=%d, forceAtk=%d",
iGroup, m_idSelTarget, bIgnoreAtkLoop, iForceAtk);
// 1. Clear current combo skill if exists
ClearComboSkill();
// 2. Create new combo skill object
if (!(m_pComboSkill = new CECComboSkill))
{
ASSERT(0);
return false;
}
// 3. Initialize combo skill
bool bForceAttack = (iForceAtk != 0);
if (!(m_pComboSkill->Init(this, iGroup, m_idSelTarget, bForceAttack, bIgnoreAtkLoop)))
{
delete m_pComboSkill;
m_pComboSkill = NULL;
return false;
}
// 4. Start combo skill execution
m_pComboSkill->Continue(m_bMelee);
return true;
}
```
**Flow:**
1. **Clear** any existing combo skill
2. **Create** new `CECComboSkill` object
3. **Initialize** with:
- Host player (this)
- Group index
- Target ID
- Force attack flag
- Ignore attack loop flag
4. **Start** combo skill execution with `Continue()`
---
## Combo Skill Data Structure
### **EC_COMBOSKILL Structure**
Combo skills are stored in `EC_VIDEO_SETTING`:
```cpp
// From configs/video settings
struct EC_COMBOSKILL
{
int nIcon; // Icon ID (0 = empty/invalid)
int idSkill[EC_COMBOSKILL_LEN]; // Array of skill IDs in combo sequence
// ... other fields
};
EC_VIDEO_SETTING vs;
vs.comboSkill[EC_COMBOSKILL_NUM]; // Array of combo skill groups
```
**Constants:**
- `EC_COMBOSKILL_NUM`: Number of combo skill groups (typically 8-12)
- `EC_COMBOSKILL_LEN`: Maximum number of skills in a combo sequence
### **Combo Skill Initialization** (EC_ComboSkill.cpp:74-92)
```cpp
// EC_ComboSkill.cpp:74-92
bool CECComboSkill::Init(CECHostPlayer* pHost, int iGroup, int idTarget,
bool bForceAttack, bool bIgnoreAtkLoop)
{
// 1. Validate group index
if (iGroup < 0 || iGroup >= EC_COMBOSKILL_NUM)
{
ASSERT(0);
return false;
}
// 2. Store parameters
m_pHost = pHost;
m_iGroup = iGroup;
m_iCursor = 0; // Start at first skill in combo
m_bStop = false;
m_idTarget = idTarget;
m_bForceAtk = bForceAttack;
m_bIgnoreAtkLoop = bIgnoreAtkLoop;
// 3. Load combo skill data from config
CECConfigs* pCfg = g_pGame->GetConfigs();
m_cs = pCfg->GetVideoSettings().comboSkill[iGroup];
// 4. Find loop start position (if any)
// ... (finds SID_LOOPSTART marker)
return true;
}
```
---
## Complete Flow Diagram
```
┌─────────────────────────────────────────────────────────────┐
│ USER ACTION / CONFIG LOAD │
└──────────────────────┬────────────────────────────────────┘
┌──────────────────────────────┐
│ Entry Point Selection │
└──────────┬───────────────────┘
┌──────────┴──────────┐
│ │
▼ ▼
┌──────────────┐ ┌──────────────────────┐
│ UI Drag&Drop │ │ LoadConfigData │
│ (DlgDragDrop)│ │ (EC_ShortcutSet) │
└──────┬───────┘ └──────────┬───────────┘
│ │
└──────────┬───────────┘
┌─────────────────────────────┐
│ CreateSkillGroupShortcut │
│ (EC_ShortcutSet) │
└──────────┬──────────────────┘
┌─────────────────────────────┐
│ new CECSCSkillGrp │
└──────────┬──────────────────┘
┌─────────────────────────────┐
│ CECSCSkillGrp::Init(iGroup) │
│ - Store group index │
│ - Format description │
└──────────┬──────────────────┘
┌─────────────────────────────┐
│ SetShortcut(iSlot, pSC) │
│ - Replace old shortcut │
└──────────┬──────────────────┘
┌─────────────────────────────┐
│ SHORTCUT CREATED │
│ Ready for execution │
└─────────────────────────────┘
│ (When user clicks shortcut)
┌─────────────────────────────┐
│ CECSCSkillGrp::Execute() │
└──────────┬──────────────────┘
┌─────────────────────────────┐
│ ApplyComboSkill(iGroup) │
│ (EC_HostPlayer) │
└──────────┬─────────────────┘
┌─────────────────────────────┐
│ new CECComboSkill │
└──────────┬──────────────────┘
┌─────────────────────────────┐
│ CECComboSkill::Init() │
│ - Load combo data from config│
│ - Set cursor to start │
└──────────┬──────────────────┘
┌─────────────────────────────┐
│ CECComboSkill::Continue() │
│ - Execute first skill │
│ - Chain to next skills │
└─────────────────────────────┘
```
---
## Key Data Structures
### **SkillGrpShortCutConfig**
```cpp
struct SkillGrpShortCutConfig
{
int setNum; // Shortcut set number (which quick bar)
int slotNum; // Slot index within the set (0-based)
int groupIndex; // Combo skill group index (0-based, -1 = invalid)
};
```
### **CECSCSkillGrp Members**
```cpp
class CECSCSkillGrp : public CECShortcut
{
int m_iGroupIdx; // Combo skill group index (0-based)
CECString m_strDesc; // Description: "Skill Group {index}"
};
```
---
## Validation & Error Handling
### **Group Index Validation**
1. **In CreateSkillGroupShortcut:**
- No explicit validation (assumes caller validates)
- `Init()` may fail if group index is invalid
2. **In ConvertSkillGrpShortcut:**
```cpp
if (vs.comboSkill[it->groupIndex].nIcon == 0)
it->groupIndex = -1; // Mark as invalid
```
3. **In CECComboSkill::Init:**
```cpp
if (iGroup < 0 || iGroup >= EC_COMBOSKILL_NUM)
{
ASSERT(0);
return false;
}
```
### **Slot Validation**
- Slot index must be within bounds of shortcut set size
- `SetShortcut()` handles slot bounds checking internally
---
## Related Functions
### **ClearComboSkill** (EC_HostPlayer.cpp:7740-7747)
```cpp
void CECHostPlayer::ClearComboSkill()
{
if (m_pComboSkill)
{
delete m_pComboSkill;
m_pComboSkill = NULL;
}
}
```
### **GetGroupIndex** (EC_Shortcut.h)
```cpp
int GetGroupIndex() const { return m_iGroupIdx; }
```
Used to retrieve the group index from a shortcut for UI display or validation.
---
## Summary
**CreateSkillGroupShortcut Flow:**
1. **Entry Points:**
- UI drag & drop operation
- Config data loading from server/save
- Programmatic assignment from config array
2. **Core Process:**
- Create `CECSCSkillGrp` object
- Initialize with group index
- Store in shortcut set at specified slot
3. **Execution:**
- When shortcut is clicked, `Execute()` is called
- Calls `ApplyComboSkill()` on host player
- Creates and initializes `CECComboSkill` object
- Starts combo skill sequence execution
4. **Data Source:**
- Combo skill definitions stored in `EC_VIDEO_SETTING.comboSkill[]`
- Each combo group contains array of skill IDs
- Group index (0-based) references position in array
---
## Conversion Notes for C#
When converting to C#:
- Replace `new`/`delete` with C# object allocation
- Use `List<>` instead of `std::vector`
- Use `string` instead of `CECString`
- Use nullable types for optional values
- Use `IDisposable` pattern for cleanup
- Consider using events/delegates for shortcut execution
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: f3fdcd3d58e16de40b0541be0d654c69
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -89,6 +89,7 @@ namespace BrewMonster.Scripts.Skills
return 0;
return 1;
}
public override int GetComboSkPreSkill() { return stub.combosk_preskill; }
public override byte GetType() { return stub.type; }
public override int GetCommonCoolDown() { return stub.commoncooldown; }
@@ -0,0 +1,540 @@
using System.Collections.Generic;
using BrewMonster;
using BrewMonster.Scripts.Skills;
using BrewMonster.UI;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using BrewMonster.Assets.PerfectWorld.Scripts.UI.GamePlay;
using BrewMonster.Network;
namespace BrewMonster.UI
{
/// <summary>
/// Skill dialog sub-other panel - displays combo skills, fixed skills, item skills, and produce skills
/// Converted from DlgSkillSubOther.cpp/h
/// </summary>
[DisallowMultipleComponent]
public class CDlgSkillSubOther : AUIDialog
{
private const int ITEM_SKILL_MAX_COUNT = 8;
private const int FIXED_SKILL_MAX_COUNT = 4;
[Header("Combo Skill Images")]
[SerializeField] private List<AUIImagePicture> m_comboSkillImages = new List<AUIImagePicture>();
[Header("Fixed Skill Components")]
[SerializeField] private List<AUIImagePicture> m_fixedImgPics = new List<AUIImagePicture>();
[SerializeField] private List<TextMeshProUGUI> m_fixedTxts = new List<TextMeshProUGUI>();
[Header("Item Skill Images")]
[SerializeField] private List<AUIImagePicture> m_itemSkillImages = new List<AUIImagePicture>();
[Header("Produce Skill Components")]
[SerializeField] private List<AUIImagePicture> m_produceImgIcons = new List<AUIImagePicture>();
[SerializeField] private List<TextMeshProUGUI> m_produceNameLabels = new List<TextMeshProUGUI>();
[SerializeField] private List<TextMeshProUGUI> m_produceSkilledTxtLabels = new List<TextMeshProUGUI>();
[SerializeField] private List<TextMeshProUGUI> m_produceSkilledExpLabels = new List<TextMeshProUGUI>();
[SerializeField] private List<TextMeshProUGUI> m_produceLevelLabels = new List<TextMeshProUGUI>();
[Header("Buttons")]
[SerializeField] private Button m_btnEdit;
[SerializeField] private Button m_btnDelete;
[SerializeField] private Button m_btnNew;
private int m_nComboSelect = 0;
private readonly List<uint> m_fixedSkills = new List<uint>();
private readonly List<uint> m_produceSkills = new List<uint>();
private void Awake()
{
// Initialize fixed skills - 167 is return skill
m_fixedSkills.Add(167);
// Initialize produce skills - respectively: weapon crafting, armor crafting, accessory crafting, alchemy
m_produceSkills.Add(158);
m_produceSkills.Add(159);
m_produceSkills.Add(160);
m_produceSkills.Add(161);
// Setup button listeners
if (m_btnEdit != null)
m_btnEdit.onClick.AddListener(OnCommandEdit);
if (m_btnDelete != null)
m_btnDelete.onClick.AddListener(OnCommandDelete);
if (m_btnNew != null)
m_btnNew.onClick.AddListener(OnCommandNew);
}
public override void OnShowDialogue()
{
base.OnShowDialogue();
Debug.Log("CDlgSkillSubOther::OnShowDialog()");
UpdateComboSkill();
UpdateFixedSkill();
UpdateItemSkill();
UpdateProduceSkill();
}
/*public override bool Render()
{
if (!base.Render())
return false;
if (!gameObject.activeInHierarchy)
return true;
// Item skills and produce skills may change, update them
UpdateItemSkill();
UpdateProduceSkill();
// Update fixed skill cooldowns
CECHostPlayer host = GetHostPlayer();
for (int i = 0; i < m_fixedSkills.Count && i < m_fixedImgPics.Count; i++)
{
if (m_fixedImgPics[i] != null && host != null)
{
CECSkill pSkill = host.GetPositiveSkillByID(m_fixedSkills[i]);
UpdateImagePictureCD(m_fixedImgPics[i], pSkill);
}
}
// Update item skill cooldowns
int equipSkillNum = host != null ? host.GetEquipSkillNum() : 0;
for (int i = 0; i < equipSkillNum && i < ITEM_SKILL_MAX_COUNT && i < m_itemSkillImages.Count; i++)
{
if (m_itemSkillImages[i] != null && host != null)
{
CECSkill pSkill = host.GetEquipSkillByIndex(i);
UpdateImagePictureCD(m_itemSkillImages[i], pSkill);
}
}
return true;
}*/
// Edit combo skill - called from DlgSkill
public void OnCommandEdit()
{
// TODO: Implement DlgSkillEdit equivalent
// GetGameUIMan()->m_pDlgSkillEdit->SetData(m_nComboSelect);
// GetGameUIMan()->m_pDlgSkillEdit->Show(true);
Debug.Log($"OnCommandEdit: combo select = {m_nComboSelect}");
}
// New combo skill - called from DlgSkill
public void OnCommandNew()
{
// TODO: Implement DlgSkillEdit equivalent
// GetGameUIMan()->m_pDlgSkillEdit->SetData(0);
// GetGameUIMan()->m_pDlgSkillEdit->Show(true);
Debug.Log("OnCommandNew");
}
// Delete combo skill - called from DlgSkill
public void OnCommandDelete()
{
if (m_nComboSelect < 0 || m_nComboSelect > EC_ConfigConstants.EC_COMBOSKILL_NUM)
return;
CECConfigs configs = EC_Game.GetConfigs();
if (configs == null)
return;
EC_VIDEO_SETTING setting = configs.GetVideoSettings();
setting.comboSkill[m_nComboSelect - 1].nIcon = 0;
m_nComboSelect = 0;
configs.SetVideoSettings(setting);
UpdateComboSkill();
}
// Helper dictionary to store combo skill data
private Dictionary<AUIImagePicture, uint> m_comboSkillData = new Dictionary<AUIImagePicture, uint>();
// Helper dictionary to store skill data for images
private Dictionary<AUIImagePicture, CECSkill> m_skillData = new Dictionary<AUIImagePicture, CECSkill>();
// Update combo skill icons - called from DlgSkill
public void UpdateComboSkill()
{
CECConfigs configs = EC_Game.GetConfigs();
if (configs == null)
return;
EC_VIDEO_SETTING setting = configs.GetVideoSettings();
CECGameUIMan gameUIMan = GetGameUIMan();
for (int i = 0; i < EC_ConfigConstants.EC_COMBOSKILL_NUM; i++)
{
AUIImagePicture pImage = m_comboSkillImages[i];
if (pImage != null)
{
if (setting.comboSkill[i].nIcon != 0)
{
// TODO: Set icon from sprite sheet
//pImage->SetCover(GetGameUIMan()->m_pA2DSpriteIcons[CECGameUIMan::ICONS_SKILLGRP],
// setting.comboSkill[i].nIcon + 1);
m_comboSkillData[pImage] = (uint)(i + 1);
//pImage.SetDataPtr(new object(), "ptr_Valid"); // Equivalent to (void*)1
string hintText = GetStringFromTable(804);
if (!string.IsNullOrEmpty(hintText))
{
hintText = string.Format(hintText, i);
// TODO: Set hint text
// pImage->SetHint(hintText);
}
}
else
{
// TODO: Clear icon
// pImage->SetCover(NULL, -1);
m_comboSkillData[pImage] = 0;
pImage.SetDataPtr(null);
// pImage->SetHint("");
}
}
}
}
// Update fixed skill display
public void UpdateFixedSkill()
{
// First hide all fixed skill components
/*for (int i = 0; i < FIXED_SKILL_MAX_COUNT; i++)
{
if (i < m_fixedImgPics.Count && m_fixedImgPics[i] != null)
m_fixedImgPics[i].gameObject.SetActive(false);
if (i < m_fixedTxts.Count && m_fixedTxts[i] != null)
m_fixedTxts[i].gameObject.SetActive(false);
}
Debug.Assert(m_fixedSkills.Count <= FIXED_SKILL_MAX_COUNT, "Fixed skills count exceeds max");
CECHostPlayer host = GetHostPlayer();
for (int i = 0; i < m_fixedSkills.Count; i++)
{
if (i < m_fixedImgPics.Count && m_fixedImgPics[i] != null)
m_fixedImgPics[i].gameObject.SetActive(true);
if (i < m_fixedTxts.Count && m_fixedTxts[i] != null)
m_fixedTxts[i].gameObject.SetActive(true);
CECSkill pSkill = host?.GetPositiveSkillByID(m_fixedSkills[i]);
if (pSkill != null)
{
SetImage(m_fixedImgPics[i], pSkill);
if (i < m_fixedTxts.Count && m_fixedTxts[i] != null)
m_fixedTxts[i].text = pSkill.GetNameDisplay();
}
else
{
if (i < m_fixedImgPics.Count && m_fixedImgPics[i] != null)
{
m_fixedImgPics[i].gameObject.SetActive(false);
SetImage(m_fixedImgPics[i], null);
}
if (i < m_fixedTxts.Count && m_fixedTxts[i] != null)
m_fixedTxts[i].gameObject.SetActive(false);
}
}*/
}
// Update item skill display
public void UpdateItemSkill()
{
CECHostPlayer host = GetHostPlayer();
if (host == null)
return;
int equipSkillNum = host.GetEquipSkillNum();
for (int i = 0; i < ITEM_SKILL_MAX_COUNT && i < m_itemSkillImages.Count; i++)
{
AUIImagePicture pImgPic = m_itemSkillImages[i];
if (pImgPic != null)
{
if (i < equipSkillNum)
{
CECSkill pSkill = host.GetEquipSkillByIndex(i);
SetImage(pImgPic, pSkill);
}
else
{
SetImage(pImgPic, null);
}
}
}
}
// Update produce skill display
public void UpdateProduceSkill()
{
CECHostPlayer host = GetHostPlayer();
if (host == null)
return;
for (int i = 0; i < m_produceSkills.Count; i++)
{
AUIImagePicture imgIcon = i < m_produceImgIcons.Count ? m_produceImgIcons[i] : null;
TextMeshProUGUI lblName = i < m_produceNameLabels.Count ? m_produceNameLabels[i] : null;
TextMeshProUGUI lblSkilledTxt = i < m_produceSkilledTxtLabels.Count ? m_produceSkilledTxtLabels[i] : null;
TextMeshProUGUI lblSkilledExp = i < m_produceSkilledExpLabels.Count ? m_produceSkilledExpLabels[i] : null;
TextMeshProUGUI lblLevel = i < m_produceLevelLabels.Count ? m_produceLevelLabels[i] : null;
if (imgIcon != null)
imgIcon.gameObject.SetActive(true);
if (lblName != null)
lblName.gameObject.SetActive(true);
//CECSkill pSkill = host.GetPassiveSkillByID(m_produceSkills[i]);
/* if (pSkill == null)
{
if (lblSkilledTxt != null)
lblSkilledTxt.gameObject.SetActive(false);
if (lblSkilledExp != null)
lblSkilledExp.gameObject.SetActive(false);
if (lblLevel != null)
lblLevel.gameObject.SetActive(false);
if (imgIcon != null)
{
// Set gray color
Image img = imgIcon.GetComponent<Image>();
if (img != null)
img.color = new Color(0.5f, 0.5f, 0.5f, 1f); // RGB(128, 128, 128)
}
CECSkill tmpSkill = new CECSkill(m_produceSkills[i], 1);
SetImage(imgIcon, tmpSkill);
if (lblName != null)
lblName.text = tmpSkill.GetNameDisplay();
}
else
{
if (lblSkilledTxt != null)
lblSkilledTxt.gameObject.SetActive(true);
if (lblSkilledExp != null)
lblSkilledExp.gameObject.SetActive(true);
if (lblLevel != null)
lblLevel.gameObject.SetActive(true);
if (imgIcon != null)
{
// Set white color
Image img = imgIcon.GetComponent<Image>();
if (img != null)
img.color = Color.white; // RGB(255, 255, 255)
}
SetImage(imgIcon, pSkill);
if (lblName != null)
lblName.text = pSkill.GetNameDisplay();*/
/* int maxAbility = ElementSkill.GetMaxAbility(m_produceSkills[i], pSkill.GetSkillLevel());
int ability = ElementSkill.GetAbility(m_produceSkills[i]);*/
/* if (lblSkilledExp != null)
lblSkilledExp.text = $"{ability}/{maxAbility}";*/
/*if (lblLevel != null)
{
string levelFormat = GetStringFromTable(11323);
if (!string.IsNullOrEmpty(levelFormat))
lblLevel.text = string.Format(levelFormat, pSkill.GetSkillLevel());
}*/
//}
}
}
// Get combo skill data for an image
private uint GetComboSkillData(AUIImagePicture pImage)
{
if (pImage == null)
return 0;
return m_comboSkillData.TryGetValue(pImage, out uint data) ? data : 0;
}
// Handle combo skill icon click for drag - called from DlgSkill
public void OnEventLButtonDownCombo(AUIImagePicture pObj)
{
uint data = GetComboSkillData(pObj);
if (pObj == null || data == 0)
return;
// TODO: Implement drag and drop
// A3DVIEWPORTPARAM *p = m_pA3DEngine->GetActiveViewport()->GetParam();
// POINT pt = { GET_X_LPARAM(lParam) - p->X, GET_Y_LPARAM(lParam) - p->Y };
// GetGameUIMan()->m_ptLButtonDown = pt;
// GetGameUIMan()->InvokeDragDrop(this, pObj, pt);
Debug.Log($"OnEventLButtonDownCombo: combo skill {data}");
}
// Handle fixed skill icon click for drag
public void OnEventLButtonDownFixed(AUIImagePicture pObj)
{
if (pObj == null)
return;
/* CECSkill skill = GetSkillFromImage(pObj);
if (skill == null)
return;*/
// TODO: Implement drag and drop
// GetGameUIMan()->m_ptLButtonDown = ...;
// GetGameUIMan()->InvokeDragDrop(this, pObj, GetGameUIMan()->m_ptLButtonDown);
Debug.Log("OnEventLButtonDownFixed");
}
// Handle item skill icon click for drag
public void OnEventLButtonDownItem(AUIImagePicture pObj)
{
if (pObj == null)
return;
/* CECSkill skill = GetSkillFromImage(pObj);
if (skill == null)
return;*/
// TODO: Implement drag and drop
// GetGameUIMan()->m_ptLButtonDown = ...;
// GetGameUIMan()->InvokeDragDrop(this, pObj, GetGameUIMan()->m_ptLButtonDown);
Debug.Log("OnEventLButtonDownItem");
}
// Select combo skill - called from DlgSkill
public void SelectComboSkill(int n)
{
if (n < 1 || n > m_comboSkillImages.Count)
return;
if (m_nComboSelect == n)
{
// Deselect
AUIImagePicture pImage = m_comboSkillImages[n - 1];
if (pImage != null)
{
Image img = pImage.GetComponent<Image>();
if (img != null)
img.color = Color.white; // RGB(255, 255, 255)
}
m_nComboSelect = 0;
}
else
{
// Deselect previous
if (m_nComboSelect != 0 && m_nComboSelect <= m_comboSkillImages.Count)
{
AUIImagePicture pImage = m_comboSkillImages[m_nComboSelect - 1];
if (pImage != null)
{
Image img = pImage.GetComponent<Image>();
if (img != null)
img.color = Color.white; // RGB(255, 255, 255)
}
}
// Select new
m_nComboSelect = n;
AUIImagePicture pNewImage = m_comboSkillImages[n - 1];
if (pNewImage != null)
{
Image img = pNewImage.GetComponent<Image>();
if (img != null)
img.color = new Color(0.627f, 0.627f, 0.627f, 1f); // RGB(160, 160, 160)
}
}
}
// Set image for an AUIImagePicture with a skill - called from DlgSkill
public void SetImage(AUIImagePicture pImage, CECSkill pSkill)
{
if (pImage == null)
return;
if (pSkill != null)
{
// TODO: Set icon from sprite sheet
// AString strFile;
// af_GetFileTitle(pSkill->GetIconFile(), strFile);
// strFile.MakeLower();
// pImage->SetCover(
// GetGameUIMan()->m_pA2DSpriteIcons[CECGameUIMan::ICONS_SKILL],
// GetGameUIMan()->m_IconMap[CECGameUIMan::ICONS_SKILL][strFile]);
// Store skill in dictionary for retrieval
m_skillData[pImage] = pSkill;
// Try to set as shortcut if possible
/* if (pSkill is CECShortcut shortcut)
pImage.SetDataPtr(shortcut, "ptr_CECSkill");*/
// TODO: Set hint
// pImage->SetHint(pSkill->GetDesc());
}
else
{
// TODO: Clear icon
// pImage->SetCover(NULL, -1);
m_skillData.Remove(pImage);
pImage.SetDataPtr(null);
// pImage->SetHint("");
}
}
// Get skill from image
/* private CECSkill GetSkillFromImage(AUIImagePicture pImage)
{
if (pImage == null)
return null;
// Try dictionary first
if (m_skillData.TryGetValue(pImage, out CECSkill skill))
return skill;
// Try shortcut
CECShortcut shortcut = pImage.GetDataPtr();
return shortcut as CECSkill;
}*/
// Update image picture cooldown display
private void UpdateImagePictureCD(AUIImagePicture pImgPic, CECSkill pSkill)
{
if (pImgPic == null)
return;
CECHostPlayer pHost = GetHostPlayer();
if (pHost == null)
return;
AUIClockIcon pClock = pImgPic.GetClockIcon();
if (pClock == null)
return;
Image img = pImgPic.GetComponent<Image>();
if (img == null)
return;
if (pSkill != null && pSkill.ReadyToCast() && pHost.GetPrepSkill() != pSkill)
{
if (pHost.CheckSkillCastCondition(pSkill) == 0)
img.color = Color.white; // RGB(255, 255, 255)
else
img.color = new Color(0.5f, 0.5f, 0.5f, 1f); // RGB(128, 128, 128)
}
else
{
// Set clock color
Image clockImg = pClock.GetClockIcon();
if (clockImg != null)
clockImg.color = new Color(0, 0, 0, 0.5f); // RGBA(0, 0, 0, 128)
}
if (pSkill != null && (pSkill.GetCoolingTime() > 0 || pHost.GetPrepSkill() == pSkill))
{
pClock.SetProgressRange(0, pSkill.GetCoolingTime());
if (pHost.GetPrepSkill() == pSkill)
pClock.SetProgressPos(0);
else
pClock.SetProgressPos(pSkill.GetCoolingTime() - pSkill.GetCoolingCnt());
}
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 27624565938535e4593764faffe78bbf
@@ -12,8 +12,8 @@ namespace BrewMonster.Assets.PerfectWorld.Scripts.UI.GamePlay
{
public class AUIImagePicture : MonoBehaviour
{
CECShortcut pSC;
[Header("AUIImagePicture")]
[SerializeField] CECShortcut pSC;
[SerializeField] Button skillbutton;
[SerializeField] protected Image skillImage;
[SerializeField] GameObject borderImage;
@@ -28,10 +28,11 @@ namespace BrewMonster.Assets.PerfectWorld.Scripts.UI.GamePlay
}
skillbutton.onClick.AddListener(Execute);
}
public void SetDataPtr(CECShortcut pvData, string strName)
public void SetDataPtr(CECShortcut pvData, string strName = null)
{
pSC = pvData;
}
public CECShortcut GetDataPtr() => pSC;
public void Execute()
{
if (pSC != null)
@@ -1,4 +1,6 @@
using BrewMonster.Assets.PerfectWorld.Scripts.UI.GamePlay;
//#define Applyforalicense
using BrewMonster.Assets.PerfectWorld.Scripts.UI.GamePlay;
using BrewMonster.Network;
using BrewMonster.Scripts;
using BrewMonster.Scripts.Skills;
@@ -23,6 +25,225 @@ namespace BrewMonster
/// Apply for a license remove later
/// </summary>
/// <returns></returns>
#if !Applyforalicense
public bool UpdateShortcuts()
{
CECShortcut pSC;
Image skillImage;
CECSCSkill pSCSkill;
int iIconFile, nMax;
AUIImagePicture pCell;
CECSkill pSkill = new CECSkill(-1, -1);
AUIClockIcon pClock;
int nCurPanel9 = GetCurPanel1();
int nCurPanel8 = GetCurPanel2();
CECHostPlayer pHost = EC_Game.GetGameRun().GetHostPlayer();
if (pHost == null) return false;
var a_pSCS = new List<CECShortcutSet>();
var a_pszPanel = new List<string>();
GetQuickBarNameAndSC(pHost, a_pszPanel, a_pSCS, nCurPanel9, nCurPanel8);
for (int i = 0; i <= 1/*(int)a_pSCS.Count*/; i++)
{
if (a_pSCS[i] == null)
continue;
//*//*CDlgQuickBar* pQuickBar = dynamic_cast<CDlgQuickBar*>(GetGameUIMan()->GetDialog(a_pszPanel[i]));
//if (!pQuickBar || !pQuickBar->IsShow()) continue;*//*
for (int j = 0; j < AUIImagePictureList.Count; j++)
{
pCell = AUIImagePictureList[j];
pSC = a_pSCS[i].GetShortcut(j);
pClock = pCell.GetClockIcon();
pClock.SetProgressRange(0, 1);
pClock.SetProgressPos(1);
if (pSC != null)
{
if (pSC.GetType() == (int)CECShortcut.ShortcutType.SCT_SKILL)
{
iIconFile = (int)EC_GAMEUI_ICONS.ICONS_SKILL;
pSCSkill = (CECSCSkill)pSC;
pSkill = pSCSkill.GetSkill();
if (false/*m_bDelGoblinSkillSC && GNET::ElementSkill::IsGoblinSkill(pSkill->GetSkillID())*/)
{
/* a_pSCS[i]->SetShortcut(j, NULL);
pSC = NULL;*/
}
else
{
if (pSkill != null && pSkill.ReadyToCast() && pHost.GetPrepSkill() != pSkill)
{
if (ElementSkill.IsGoblinSkill((uint)pSkill.GetSkillID()))
{
/* if (pHostGoblin && !pHostGoblin->CheckSkillCastCondition(pSkill))
{
pCell->SetColor(A3DCOLORRGB(255, 255, 255));
}
else
{
pCell->SetColor(A3DCOLORRGB(128, 128, 128));
}*/
}
else
{
if (pHost.CheckSkillCastCondition(pSkill) == 0)
{
//pCell.SetColor(A3DCOLORRGB(255, 255, 255));
}
else
{
//pCell.SetColor(A3DCOLORRGB(128, 128, 128));
}
}
}
/*else
pClock.SetColor(A3DCOLORRGBA(0, 0, 0, 128));*/
if (pSkill != null && (pSkill.GetCoolingTime() > 0 ||
pHost.GetPrepSkill() == pSkill))
{
pClock.SetProgressRange(0, pSkill.GetCoolingTime());
if (pHost.GetPrepSkill() == pSkill)
{
pClock.SetProgressPos(0);
}
else
{
pClock.SetProgressPos(pSkill.GetCoolingTime() - pSkill.GetCoolingCnt());
}
}
}
}
/*else if (pSC->GetType() == CECShortcut::SCT_ITEM)
{
iIconFile = CECGameUIMan::ICONS_INVENTORY;
pCell->SetColor(A3DCOLORRGB(255, 255, 255));
pSCItem = (CECSCItem*)pSC;
pIvtr = GetHostPlayer()->GetPack(pSCItem->GetInventory());
pItem = pIvtr->GetItem(pSCItem->GetIvtrSlot());
if (pItem && pItem->GetCoolTime(&nMax) > 0)
{
pClock->SetProgressRange(0, nMax);
pClock->SetProgressPos(nMax - pItem->GetCoolTime());
pClock->SetColor(A3DCOLORRGBA(0, 0, 0, 128));
}
if (pSCItem->GetInventory() == IVTRTYPE_EQUIPPACK)
pCell->SetColor(A3DCOLORRGBA(128, 128, 255, 128));
}
else if (pSC->GetType() == CECShortcut::SCT_PET)
{
pSCPet = (CECSCPet*)pSC;
CECPetData* pPet = pPetCorral->GetPetData(pSCPet->GetPetIndex());
iIconFile = CECGameUIMan::ICONS_INVENTORY;
pCell->SetColor(A3DCOLORRGB(255, 255, 255));
if (pPet)
{
// dead combat pet
if ((pPet->GetClass() == GP_PET_CLASS_COMBAT || pPet->GetClass() == GP_PET_CLASS_EVOLUTION) && pPet->GetHPFactor() == 0.0f)
{
pCell->SetColor(A3DCOLORRGB(128, 128, 128));
}
// current active pet
else if (pSCPet->IsActivePet())
{
pCell->SetColor(A3DCOLORRGB(255, 255, 0));
}
}
}
else if (pSC->GetType() == CECShortcut::SCT_AUTOFASHION)
{
iIconFile = CECGameUIMan::ICONS_SUITE;
fashionCoolTime = pHost->GetCoolTime(GP_CT_EQUIP_FASHION_ITEM, &fashionCoolTimeMax);
pCell->SetColor(A3DCOLORRGB(255, 255, 255));
if (fashionCoolTimeMax > 0)
{
pClock->SetProgressRange(0, fashionCoolTimeMax);
pClock->SetProgressPos(fashionCoolTimeMax - fashionCoolTime);
pClock->SetColor(A3DCOLORRGBA(0, 0, 0, 175));
}
}
else
{
iIconFile = CECGameUIMan::ICONS_ACTION;
if (pSC->GetType() == CECShortcut::SCT_COMMAND)
{
CECSCCommand* pCommandSC = (CECSCCommand*)pSC;
if (GetHostPlayer()->IsInvisible())
{
if (pCommandSC->GetCommandID() == CECSCCommand::CMD_STARTTRADE ||
pCommandSC->GetCommandID() == CECSCCommand::CMD_SELLBOOTH ||
pCommandSC->GetCommandID() == CECSCCommand::CMD_BINDBUDDY)
{
pCell->SetColor(A3DCOLORRGB(128, 128, 128));
}
else
{
pCell->SetColor(A3DCOLORRGB(255, 255, 255));
}
}
else
{
pCell->SetColor(A3DCOLORRGB(255, 255, 255));
}
}
if (pSC->GetCoolTime(&nMax) > 0)
{
pClock->SetProgressRange(0, nMax);
pClock->SetProgressPos(nMax - pSC->GetCoolTime());
pClock->SetColor(A3DCOLORRGBA(0, 0, 0, 128));
}
}*//**/
if (pSC != null)
{
if(pCell.GetDataPtr() == pSC)
{
continue;
}
pCell.SetDataPtr(pSC);
if (pSC.GetType() == (int)CECShortcut.ShortcutType.SCT_SKILLGRP)
{
EC_VIDEO_SETTING setting = EC_Game.GetConfigs().GetVideoSettings();
/* pCell.SetCover(GetGameUIMan()->m_pA2DSpriteIcons[CECGameUIMan::ICONS_SKILLGRP],
setting.comboSkill[((CECSCSkillGrp)pSC).GetGroupIndex()].nIcon + 1);
setting.comboSkill[((CECSCSkillGrp)pSC).GetGroupIndex()].nIcon + 1;*/
// fix later now haven't skill group icon yet
GetGameUIMan().SetCover(pCell, "unknown", EC_GAMEUI_ICONS.ICONS_SKILL);
}
else
{
if (pSkill != null)
{
pCell.gameObject.SetActive(true);
//BMLogger.Log("HoangDev: QuickBar Set Skill Icon: " + (uint)pSkill.GetSkillID() + " : " + ElementSkill.GetIcon((uint)pSkill.GetSkillID()));
var nameskill = ElementSkill.GetIcon((uint)pSkill.GetSkillID());
GetGameUIMan().SetCover(pCell, nameskill, EC_GAMEUI_ICONS.ICONS_SKILL);
}
/* af_GetFileTitle(pSC->GetIconFile(), strFile);
strFile.MakeLower();
pCell->SetCover(GetGameUIMan()->m_pA2DSpriteIcons[iIconFile],
GetGameUIMan()->m_IconMap[iIconFile][strFile]); */
}
}
}
else
{
/* pCell->SetCover(NULL, -1);
pCell->SetText(_AL(""));
pCell->SetDataPtr(NULL);
pCell->SetColor(A3DCOLORRGB(255, 255, 255)); */
}
}
}
return true;
}
#else
//public bool UpdateShortcuts()
//{
// CECShortcut pSC;
@@ -230,215 +451,7 @@ namespace BrewMonster
// }
// return true;
//}
public bool UpdateShortcuts()
{
CECShortcut pSC;
Image skillImage;
CECSCSkill pSCSkill;
int iIconFile, nMax;
AUIImagePicture pCell;
CECSkill pSkill = new CECSkill(-1, -1);
AUIClockIcon pClock;
int nCurPanel9 = GetCurPanel1();
int nCurPanel8 = GetCurPanel2();
CECHostPlayer pHost = EC_Game.GetGameRun().GetHostPlayer();
if (pHost == null) return false;
var a_pSCS = new List<CECShortcutSet>();
var a_pszPanel = new List<string>();
GetQuickBarNameAndSC(pHost, a_pszPanel, a_pSCS, nCurPanel9, nCurPanel8);
for (int i = 0; i <= 1/*(int)a_pSCS.Count*/; i++)
{
if (a_pSCS[i] == null)
continue;
//*//*CDlgQuickBar* pQuickBar = dynamic_cast<CDlgQuickBar*>(GetGameUIMan()->GetDialog(a_pszPanel[i]));
//if (!pQuickBar || !pQuickBar->IsShow()) continue;*//*
for (int j = 0; j < AUIImagePictureList.Count; j++)
{
pCell = AUIImagePictureList[j];
pSC = a_pSCS[i].GetShortcut(j);
pClock = pCell.GetClockIcon();
pClock.SetProgressRange(0, 1);
pClock.SetProgressPos(1);
if (pSC != null)
{
if (pSC.GetType() == (int)CECShortcut.ShortcutType.SCT_SKILL)
{
iIconFile = (int)EC_GAMEUI_ICONS.ICONS_SKILL;
pSCSkill = (CECSCSkill)pSC;
pSkill = pSCSkill.GetSkill();
if (false/*m_bDelGoblinSkillSC && GNET::ElementSkill::IsGoblinSkill(pSkill->GetSkillID())*/)
{
/* a_pSCS[i]->SetShortcut(j, NULL);
pSC = NULL;*/
}
else
{
if (pSkill != null && pSkill.ReadyToCast() && pHost.GetPrepSkill() != pSkill)
{
if (ElementSkill.IsGoblinSkill((uint)pSkill.GetSkillID()))
{
/* if (pHostGoblin && !pHostGoblin->CheckSkillCastCondition(pSkill))
{
pCell->SetColor(A3DCOLORRGB(255, 255, 255));
}
else
{
pCell->SetColor(A3DCOLORRGB(128, 128, 128));
}*/
}
else
{
if (pHost.CheckSkillCastCondition(pSkill) == 0)
{
//pCell.SetColor(A3DCOLORRGB(255, 255, 255));
}
else
{
//pCell.SetColor(A3DCOLORRGB(128, 128, 128));
}
}
}
/*else
pClock.SetColor(A3DCOLORRGBA(0, 0, 0, 128));*/
if (pSkill != null && (pSkill.GetCoolingTime() > 0 ||
pHost.GetPrepSkill() == pSkill))
{
pClock.SetProgressRange(0, pSkill.GetCoolingTime());
if (pHost.GetPrepSkill() == pSkill)
{
pClock.SetProgressPos(0);
}
else
{
pClock.SetProgressPos(pSkill.GetCoolingTime() - pSkill.GetCoolingCnt());
}
}
}
}
/*else if (pSC->GetType() == CECShortcut::SCT_ITEM)
{
iIconFile = CECGameUIMan::ICONS_INVENTORY;
pCell->SetColor(A3DCOLORRGB(255, 255, 255));
pSCItem = (CECSCItem*)pSC;
pIvtr = GetHostPlayer()->GetPack(pSCItem->GetInventory());
pItem = pIvtr->GetItem(pSCItem->GetIvtrSlot());
if (pItem && pItem->GetCoolTime(&nMax) > 0)
{
pClock->SetProgressRange(0, nMax);
pClock->SetProgressPos(nMax - pItem->GetCoolTime());
pClock->SetColor(A3DCOLORRGBA(0, 0, 0, 128));
}
if (pSCItem->GetInventory() == IVTRTYPE_EQUIPPACK)
pCell->SetColor(A3DCOLORRGBA(128, 128, 255, 128));
}
else if (pSC->GetType() == CECShortcut::SCT_PET)
{
pSCPet = (CECSCPet*)pSC;
CECPetData* pPet = pPetCorral->GetPetData(pSCPet->GetPetIndex());
iIconFile = CECGameUIMan::ICONS_INVENTORY;
pCell->SetColor(A3DCOLORRGB(255, 255, 255));
if (pPet)
{
// dead combat pet
if ((pPet->GetClass() == GP_PET_CLASS_COMBAT || pPet->GetClass() == GP_PET_CLASS_EVOLUTION) && pPet->GetHPFactor() == 0.0f)
{
pCell->SetColor(A3DCOLORRGB(128, 128, 128));
}
// current active pet
else if (pSCPet->IsActivePet())
{
pCell->SetColor(A3DCOLORRGB(255, 255, 0));
}
}
}
else if (pSC->GetType() == CECShortcut::SCT_AUTOFASHION)
{
iIconFile = CECGameUIMan::ICONS_SUITE;
fashionCoolTime = pHost->GetCoolTime(GP_CT_EQUIP_FASHION_ITEM, &fashionCoolTimeMax);
pCell->SetColor(A3DCOLORRGB(255, 255, 255));
if (fashionCoolTimeMax > 0)
{
pClock->SetProgressRange(0, fashionCoolTimeMax);
pClock->SetProgressPos(fashionCoolTimeMax - fashionCoolTime);
pClock->SetColor(A3DCOLORRGBA(0, 0, 0, 175));
}
}
else
{
iIconFile = CECGameUIMan::ICONS_ACTION;
if (pSC->GetType() == CECShortcut::SCT_COMMAND)
{
CECSCCommand* pCommandSC = (CECSCCommand*)pSC;
if (GetHostPlayer()->IsInvisible())
{
if (pCommandSC->GetCommandID() == CECSCCommand::CMD_STARTTRADE ||
pCommandSC->GetCommandID() == CECSCCommand::CMD_SELLBOOTH ||
pCommandSC->GetCommandID() == CECSCCommand::CMD_BINDBUDDY)
{
pCell->SetColor(A3DCOLORRGB(128, 128, 128));
}
else
{
pCell->SetColor(A3DCOLORRGB(255, 255, 255));
}
}
else
{
pCell->SetColor(A3DCOLORRGB(255, 255, 255));
}
}
if (pSC->GetCoolTime(&nMax) > 0)
{
pClock->SetProgressRange(0, nMax);
pClock->SetProgressPos(nMax - pSC->GetCoolTime());
pClock->SetColor(A3DCOLORRGBA(0, 0, 0, 128));
}
}*//**/
if (pSC != null)
{
if (pSC.GetType() == (int)CECShortcut.ShortcutType.SCT_SKILLGRP)
{
EC_VIDEO_SETTING setting = EC_Game.GetConfigs().GetVideoSettings();
/* pCell.SetCover(GetGameUIMan()->m_pA2DSpriteIcons[CECGameUIMan::ICONS_SKILLGRP],
setting.comboSkill[((CECSCSkillGrp)pSC).GetGroupIndex()].nIcon + 1);
setting.comboSkill[((CECSCSkillGrp)pSC).GetGroupIndex()].nIcon + 1;*/
// fix later now haven't skill group icon yet
GetGameUIMan().SetCover(pCell, "unknown", EC_GAMEUI_ICONS.ICONS_SKILL);
}
else
{
if (pSkill != null)
{
pCell.gameObject.SetActive(true);
//BMLogger.Log("HoangDev: QuickBar Set Skill Icon: " + (uint)pSkill.GetSkillID() + " : " + ElementSkill.GetIcon((uint)pSkill.GetSkillID()));
var nameskill = ElementSkill.GetIcon((uint)pSkill.GetSkillID());
GetGameUIMan().SetCover(pCell, nameskill, EC_GAMEUI_ICONS.ICONS_SKILL);
}
/* af_GetFileTitle(pSC->GetIconFile(), strFile);
strFile.MakeLower();
pCell->SetCover(GetGameUIMan()->m_pA2DSpriteIcons[iIconFile],
GetGameUIMan()->m_IconMap[iIconFile][strFile]); */
}
}
}
else
{
/* pCell->SetCover(NULL, -1);
pCell->SetText(_AL(""));
pCell->SetDataPtr(NULL);
pCell->SetColor(A3DCOLORRGB(255, 255, 255)); */
}
}
}
return true;
}
#endif
private void GetQuickBarNameAndSC(CECHostPlayer pHost, List<string> pszPanel, List<CECShortcutSet> pSCS, int panel9, int panel8)
{
string dlgName;
+106 -92
View File
@@ -3,7 +3,6 @@ using BrewMonster.Assets.PerfectWorld.Scripts.Players;
using BrewMonster.Managers;
using BrewMonster.Network;
using BrewMonster.PerfectWorld.Scripts.Vfx;
using BrewMonster.PerfectWorld.Scripts.Vfx;
using BrewMonster.Scripts;
using BrewMonster.Scripts.Managers;
using BrewMonster.Scripts.Skills;
@@ -22,6 +21,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using PerfectWorld.Scripts.Managers;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.UI;
@@ -691,8 +691,6 @@ namespace BrewMonster
private void OnMsgHstSetCoolTime(ECMSG Msg)
{
BMLogger.LogError("HoangDev : OnMsgHstSetCoolTime ");
cmd_set_cooldown pCmd = GPDataTypeHelper.FromBytes<cmd_set_cooldown>((byte[])Msg.dwParam1);
if (pCmd.cooldown_index < 0)
@@ -904,7 +902,7 @@ namespace BrewMonster
bool bActionStartSkill = false;
int iActionTime = 1000;
CECPlayerWrapper pWrapper = CECAutoPolicy.GetInstance().GetPlayerWrapper();
CECPlayerWrapper pWrapper = CECAutoPolicy.GetInstance().GetPlayerWrapper();
switch (Convert.ToInt32(Msg.dwParam2))
{
@@ -968,7 +966,7 @@ namespace BrewMonster
bActionStartSkill = true;
iActionTime = iWaitTime;
Debug.Log($"Cast skill({m_pCurSkill.GetSkillID()})");
// Special logging for return-to-town skill (167)
// 回城技能(167)的特殊日志
if (m_pCurSkill.GetSkillID() == ID_RETURNTOWN_SKILL)
@@ -999,12 +997,9 @@ namespace BrewMonster
}
case int value2 when value2 == CommandID.HOST_STOP_SKILL:
{
// Host stop skill
// 主角停止技能
m_pPrepSkill = null;
CECSkill
pSkillToMatch = m_pCurSkill; // 保存指针值,用在后面函数调用中 | Save pointer value for later function call
CECSkill pSkillToMatch = m_pCurSkill;
if (m_pCurSkill != null)
{
ClearComActFlagAllRankNodes(true);
@@ -1078,13 +1073,13 @@ namespace BrewMonster
// Print a notify message
// 打印提示消息
//EC_Game.GetGameRun().AddFixedMessage(FIXMSG_SKILLINTERRUPT);
//EC_Game.GetGameRun().AddFixedMessage(FIXMSG_SKILLINTERRUPT);
BMLogger.LogError("Skill interrupted!");
AP.AP_ActionEvent((int)AP_EVENT. AP_EVENT_STOPSKILL);
AP.AP_ActionEvent((int)AP_EVENT.AP_EVENT_STOPSKILL);
// 通知策略技能被打断 | Notify policy that skill is interrupted
CECAutoPolicy.GetInstance().SendEvent_SkillInterrupt(skill_id);
CECAutoPolicy.GetInstance().SendEvent_SkillInterrupt(skill_id);
break;
}
case int value2 when value2 == CommandID.OBJECT_CAST_INSTANT_SKILL:
@@ -1469,7 +1464,7 @@ namespace BrewMonster
CECUIManager.Instance.UpdateSkillRelatedUI();
}
}
public void AssignSkillGrpShortcut( List<SkillGrpShortCutConfig> skillGrpSCConfigArray, CECShortcutSet[] aSCSets)
public void AssignSkillGrpShortcut(List<SkillGrpShortCutConfig> skillGrpSCConfigArray, CECShortcutSet[] aSCSets)
{
for (int i = 0; i < skillGrpSCConfigArray.Count; i++)
{
@@ -2671,12 +2666,12 @@ namespace BrewMonster
// p1 是一个 byte[] 缓冲区;解析为 cmd_notify_hostpos 然后设置位置
byte[] buf = (byte[])Msg.dwParam1;
cmd_notify_hostpos pCmd = GPDataTypeHelper.FromBytes<cmd_notify_hostpos>(buf);
int idInst = pCmd.tag;
Vector3 vPos = new Vector3(pCmd.vPos.x, pCmd.vPos.y, pCmd.vPos.z);
int iLine = pCmd.line;
// Call Goto method to properly handle teleportation
// 调用 Goto 方法来正确处理传送
if (!Goto(idInst, vPos, iLine))
@@ -2685,7 +2680,7 @@ namespace BrewMonster
return;
}
}
// Return to a target town through skill
// 通过技能返回目标城镇
// This method implements the Goto logic from the original C++ code
@@ -2701,7 +2696,7 @@ namespace BrewMonster
// Debug.LogError($"CECHostPlayer::Goto, Failed to jump to instance {idInst}");
// return false;
// }
// Stop all current work and goto specified position
// 停止所有当前工作并转到指定位置
if (m_pWorkMan != null)
@@ -2710,33 +2705,33 @@ namespace BrewMonster
// 如果正在自动移动则停止
// Note: IsAutoMoving check would go here if available
// 注意:如果可用,IsAutoMoving 检查将放在这里
// Finish all work
// 完成所有工作
m_pWorkMan.FinishAllWork(true);
}
// Add a little height to ensure player's AABB won't embed with building
// 增加一点高度以确保玩家的 AABB 不会嵌入建筑物
vPos.y += 0.1f;
// Ensure we are not under ground (terrain height check would go here)
// 确保我们不会在地下(地形高度检查将放在这里)
// Note: Terrain height check is skipped for now as it requires world access
// 注意:暂时跳过地形高度检查,因为它需要世界访问
// Set position
// 设置位置
SetPos(vPos);
// Reset jump state if available
// 如果可用则重置跳跃状态
// ResetJump(); // Uncomment if ResetJump method exists
// Update camera if available
// 如果可用则更新相机
// UpdateFollowCamera(false, 10); // Uncomment if UpdateFollowCamera method exists
return true;
}
@@ -3713,10 +3708,10 @@ namespace BrewMonster
return false;
if (!bCombo)
//ClearComboSkill();
ClearComboSkill();
if (idSelTarget == 0)
idSelTarget = m_idSelTarget;
if (idSelTarget == 0)
idSelTarget = m_idSelTarget;
CECSkill pSkill = GetPositiveSkillByID(idSkill);
if (pSkill == null) pSkill = GetEquipSkillByID(idSkill);
@@ -3736,12 +3731,12 @@ namespace BrewMonster
return true;
}
//int iCon = CheckSkillCastCondition(pSkill);
//if (iCon)
//{
// ProcessSkillCondition(iCon);
// return false;
//}
int iCon = CheckSkillCastCondition(pSkill);
if (iCon != 0)
{
ProcessSkillCondition(iCon);
return false;
}
//// Get force attack flag
bool bForceAttack = false;
@@ -3751,20 +3746,20 @@ namespace BrewMonster
bForceAttack = iForceAtk > 0 ? true : false;
//// Check negative effect skill
//if (pSkill.GetType() == (int)skill_type.TYPE_ATTACK || pSkill.GetType() == (int)skill_type.TYPE_CURSE)
//{
// if (idSelTarget == m_PlayerInfo.cid)
// {
// // Host cannot spell negative effect magic to himself.
// EC_Game.GetGameRun().AddFixedChannelMsg(FIXMSG_TARGETWRONG, GP_CHAT_FIGHT);
// return false;
// }
// else if (idSelTarget != 0)
// {
// if (AttackableJudge(idSelTarget, bForceAttack) != 1)
// return false;
// }
//}
if (pSkill.GetType() == (int)skill_type.TYPE_ATTACK || pSkill.GetType() == (int)skill_type.TYPE_CURSE)
{
if (idSelTarget == m_PlayerInfo.cid)
{
// Host cannot spell negative effect magic to himself.
//EC_Game.GetGameRun().AddFixedChannelMsg(FIXMSG_TARGETWRONG, GP_CHAT_FIGHT);
return false;
}
else if (idSelTarget != 0)
{
if (AttackableJudge(idSelTarget, bForceAttack) != 1)
return false;
}
}
//// Check whether target type match
int idCastTarget = idSelTarget;
@@ -3876,7 +3871,7 @@ namespace BrewMonster
else if (iTargetType == 2)
iAliveFlag = 2;
/*CECObject pObject = EC_Game.GetGameRun().GetWorld().GetObject(idCastTarget, iAliveFlag);
/* CECObject pObject = EC_Game.GetGameRun().GetWorld().GetObject(idCastTarget, iAliveFlag);
if (!pObject)
return false;*/
}
@@ -3890,8 +3885,8 @@ namespace BrewMonster
if (!pSkill.IsInstant() && pSkill.GetType() != (int)Skilltype.TYPE_FLASHMOVE)
{
/* if (!NaturallyStopMoving())
return false; */ // Couldn't stop naturally, so cancel casting skill
if (!NaturallyStopMoving())
return false; // Couldn't stop naturally, so cancel casting skill
}
else if (pSkill.GetType() == (int)Skilltype.TYPE_FLASHMOVE)
{
@@ -3925,9 +3920,9 @@ namespace BrewMonster
{
bool bTraceOK = false;
bool bUseAutoPF = false;
/* CECPlayerWrapper pWrapper = CECAutoPolicy::GetInstance().GetPlayerWrapper();
if (CECAutoPolicy::GetInstance().IsAutoPolicyEnabled() && pWrapper.GetAttackError() >= 2)
bUseAutoPF = true;*/
CECPlayerWrapper pWrapper = CECAutoPolicy.GetInstance().GetPlayerWrapper();
if (CECAutoPolicy.GetInstance().IsAutoPolicyEnabled() && pWrapper.GetAttackError() >= 2)
bUseAutoPF = true;
if (idCastTarget == 0)
{
@@ -4134,7 +4129,24 @@ namespace BrewMonster
Buffer.BlockCopy(buffer, index + contentBytes, tail, 0, trailing);
}
}
public bool NaturallyStopMoving()
{
// if (!m_MoveCtrl.IsStop())
if (!IsPlayerMoving())
return true; // Host has been stopped
if (m_iMoveMode == (int)MoveMode.MOVE_FREEFALL || InSlidingState() || IsJumping())
return false; // Host couldn't stop naturally
if (!m_pWorkMan.IsStanding())
{
m_pWorkMan.FinishAllWork(true);
}
m_MoveCtrl.SendStopMoveCmd();
return true;
}
public bool CastSkill(int idTarget, bool bForceAttack, CECObject pTarget = null)
{
// Check if prep skill is valid, ready to cast, and not currently spelling magic
@@ -4393,7 +4405,7 @@ namespace BrewMonster
// 通过技能返回目标城镇
private bool ReturnToTargetTown(int idTarget, bool bCombo = false)
{
if (!CanDo(ActionCanDo.CANDO_SPELLMAGIC))
{
return false;
@@ -4407,7 +4419,7 @@ namespace BrewMonster
Debug.LogError("ReturnToTargetTown: Skill 167 not found");
return false;
}
if (!bCombo)
{
@@ -4443,7 +4455,7 @@ namespace BrewMonster
m_pPrepSkill = pSkill;
byte byPVPMask = glb_BuildPVPMask(false);
// Call c2s_CmdCastSkill with target parameter
// 使用目标参数调用 c2s_CmdCastSkill
int targets = 1;
@@ -4667,19 +4679,13 @@ namespace BrewMonster
}
}
// Check combo skill prerequisite (for night shadow continuous skills)
// Note: GetComboSkPreSkill and IsActiveComboSkill methods need to be implemented
// TODO: Implement GetComboSkPreSkill in ElementSkill/CECSkill
// TODO: Implement IsActiveComboSkill in CECComboSkillState
/*
if (pSkill.GetComboSkPreSkill() != 0)
{
if (!CECComboSkillState.Instance.IsActiveComboSkill(pSkill.GetSkillID()))
if (!CECComboSkillState.Instance.IsActiveComboSkill((uint)pSkill.GetSkillID()))
{
return 13; // Combo skill not active
}
}
*/
// Build UseRequirement info
UseRequirement Info = new UseRequirement();
@@ -4691,35 +4697,24 @@ namespace BrewMonster
Info.is_combat = IsFighting();
Info.hp = m_BasicProps.iCurHP;
Info.max_hp = m_ExtProps.bs.max_hp;
// Info.combo_state = CECComboSkillState.Instance.GetComboSkillState(); // TODO: Implement GetComboSkillState
Info.combo_state = CECComboSkillState.Instance.GetComboSkillState(); // TODO: Implement GetComboSkillState
// Get weapon's major class ID
// Equipment inventory slot constants from CECPlayer.IndexOfIteminEquipmentInventory
const int EQUIPIVTR_WEAPON = 0; // Weapon slot
const int EQUIPIVTR_PROJECTILE = 4; // Projectile slot (arrows)
// Get weapon's major class ID
int iReason = 0;
Info.weapon = 0;
Info.arrow = 0;
EC_IvtrItem pWeaponItem = m_pEquipPack.GetItem(EQUIPIVTR_WEAPON);
if (pWeaponItem != null && pWeaponItem is EC_IvtrEquip pWeaponEquip)
{
// Check if weapon has endurance
if (pWeaponEquip.CurEndurance > 0)
{
// TODO: Implement CanUseEquipment method to check level/class requirements
// For now, use the template ID as weapon type
Info.weapon = pWeaponItem.GetTemplateID();
}
}
CECIvtrWeapon pWeapon = (CECIvtrWeapon)m_pEquipPack.GetItem((int)IndexOfIteminEquipmentInventory.EQUIPIVTR_WEAPON);
if (pWeapon == null || pWeapon.GetCurEndurance() == 0)
Info.weapon = 0;
else if (!CanUseEquipment(pWeapon, ref iReason))
Info.weapon = (iReason == 5) ? (int)pWeapon.GetDBMajorType().id : 0;
else
Info.weapon = (int)pWeapon.GetDBMajorType().id;
// Get remaining arrow number
EC_IvtrItem pArrowItem = m_pEquipPack.GetItem(EQUIPIVTR_PROJECTILE);
if (pArrowItem != null)
CECIvtrArrow pArrow = (CECIvtrArrow)m_pEquipPack.GetItem((int)IndexOfIteminEquipmentInventory.EQUIPIVTR_PROJECTILE);
if (pArrow != null && CanUseProjectile(pArrow))
{
// TODO: Implement CanUseProjectile method to check requirements
// For now, use the item count
Info.arrow = pArrowItem.GetCount();
Info.arrow = pArrow.GetCount();
}
// Call ElementSkill Condition check
@@ -4760,7 +4755,26 @@ namespace BrewMonster
return iMsg >= 0;
}
public bool CanUseProjectile(CECIvtrArrow pArrow)
{
if (pArrow == null)
return false;
CECIvtrWeapon pWeapon = (CECIvtrWeapon)m_pEquipPack.GetItem((int)IndexOfIteminEquipmentInventory.EQUIPIVTR_WEAPON);
if (pWeapon == null)
return false;
IVTR_ESSENCE_WEAPON we = pWeapon.GetEssence();
if (we.weapon_type != (int)WeaponType.WEAPONTYPE_RANGE)
return false;
IVTR_ESSENCE_ARROW ae = pArrow.GetEssence();
if (we.require_projectile != (int)pArrow.GetDBSubType().id ||
we.weapon_level < ae.iWeaponReqLow || we.weapon_level > ae.iWeaponReqHigh)
return false;
return true;
}
bool CanSelectTarget(int idTarget)
{
if (idTarget == 0 || idTarget == this.GetCharacterID())
@@ -6885,7 +6899,7 @@ namespace BrewMonster
return true;
}
public void ClearComboSkill()
public void ClearComboSkill()
{
if (m_pComboSkill != null)
{
@@ -7166,7 +7180,7 @@ namespace BrewMonster
for (i = 0; i < m_pEquipPack.GetSize(); i++)
{
var pItem = m_pEquipPack.GetItem(i);
if(pItem == null)
if (pItem == null)
{
continue;
}
@@ -7277,11 +7291,11 @@ namespace BrewMonster
return m_pPetCorral;
}
public bool IsPlayerMoving()
public bool IsPlayerMoving()
{
return m_pWorkMan.IsMoving();
}
public CECComboSkill GetComboSkill() { return m_pComboSkill; }
public CECComboSkill GetComboSkill() { return m_pComboSkill; }
}
public struct SkillShortCutConfig
File diff suppressed because one or more lines are too long