Merge pull request 'feature/skill-set-shortcut' (#145) from feature/skill-set-shortcut into develop

Reviewed-on: https://git.brew.monster/Unity/perfect-world-unity/pulls/145
This commit is contained in:
hoangvd
2026-02-02 03:44:46 +00:00
66 changed files with 9201 additions and 6566 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,454 @@
// EC_AutoPolicy.cs
// Converted from EC_AutoPolicy.h / EC_AutoPolicy.cpp
using BrewMonster.Assets.PerfectWorld.Scripts.Players;
using BrewMonster.Network;
using CSNetwork.GPDataType;
using System;
using System.Collections.Generic;
using System.IO;
public sealed class CECAutoPolicy
{
// policy type
public const int POLICY_AUTOKILLMONSTER = 0;
// system events
public const int EVENT_BEHURT = 1;
public const int EVENT_SKILLINTERRUPT = 2;
public const int EVENT_RETURNTOWNOK = 3;
public const int EVENT_CONFIGCHANGED = 4;
public struct CONFIG
{
public int attack_skill;
public bool attack_iscombo;
public int assist_skill;
public bool assist_iscombo;
public int nAssistInterval;
public int nTime;
public int iAutoPickMode; // 0 none, 1 all, 2 only money
public int nPetrolRadius;
public CONFIG(bool _ = true)
{
attack_skill = 0;
attack_iscombo = false;
assist_skill = 0;
assist_iscombo = false;
nAssistInterval = 60000;
nTime = 3600000;
iAutoPickMode = 0;
nPetrolRadius = 500;
}
}
private static readonly CECAutoPolicy _instance = new CECAutoPolicy();
public static CECAutoPolicy GetInstance() => _instance;
private CONFIG m_Config;
private CECPlayerWrapper m_pPlayer;
private string m_strCurPolicy;
private readonly CECCounter m_cntTick;
private uint m_dwKeepingTime;
private uint m_dwCurrentTime;
private CECAutoPolicy()
{
m_pPlayer = null;
m_cntTick = new CECCounter(); // 100ms => 10 ticks/sec
m_cntTick.SetPeriod(100);
m_dwKeepingTime = 0;
m_dwCurrentTime = 0;
m_strCurPolicy = string.Empty;
m_Config = new CONFIG();
}
public bool Init()
{
// load lua file list: configs/autopolicy/allfiles.txt
// (token-based in C++; here read whitespace tokens)
var listPath = Path.Combine("configs", "autopolicy", "allfiles.txt");
if (!File.Exists(listPath))
{
// a_LogOutput equivalent: adapt in your project
return false;
}
foreach (var line in File.ReadLines(listPath))
{
var s = line.Trim();
if (string.IsNullOrEmpty(s)) continue;
if (s.StartsWith("#") || s.StartsWith("//")) continue;
// token 0
var parts = s.Split((char[])null, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length == 0) continue;
var luaFile = Path.Combine("configs", "autopolicy", parts[0]);
LoadLuaFile(luaFile);
}
// Register player API: _InitPlayerAPI(g_LuaStateMan.GetAIState());
AutoPolicyLuaHooks.InitPlayerAPI?.Invoke();
return true;
}
public void Release()
{
m_pPlayer = null;
}
public void OnEnterWorld()
{
if (m_pPlayer == null)
m_pPlayer = new CECPlayerWrapper(EC_Game.GetGameRun().GetHostPlayer());
LoadConfigData();
}
public void OnLeaveWorld()
{
if (m_pPlayer != null)
{
SaveConfigData();
m_pPlayer = null;
}
SetCurPolicy(string.Empty);
m_dwCurrentTime = 0;
}
public void Tick(uint dwDeltaTime)
{
if (EC_Game.GetGameRun().GetGameState() != (int)GameState.GS_GAME)
return;
m_pPlayer?.Tick(dwDeltaTime);
if (m_cntTick.IncCounter(dwDeltaTime))
{
var args = new List<CScriptValue> { new CScriptValue((double)m_dwKeepingTime) };
var ret = new List<CScriptValue>();
CallLuaFunc("AIManager", "OnTick", args, ret);
m_cntTick.Reset();
m_dwKeepingTime = 0;
}
m_dwKeepingTime += dwDeltaTime;
if (IsAutoPolicyEnabled())
m_dwCurrentTime += dwDeltaTime;
}
public void Render()
{
// C++ draws debug text with A3DFont. Implement your own debug UI if needed.
// In C++ it uses: m_pPlayer.m_iAttackErrCnt / m_iPickupErrCnt / action queue list
}
public void StartPolicy(int policyType)
{
string[] policyMap =
{
"AutoKillMonster", // POLICY_AUTOKILLMONSTER
};
// FIXED: original C++ has a sizeof bug. Correct is policyMap.Length.
if (policyType < 0 || policyType >= policyMap.Length)
return;
string policyName = policyMap[policyType];
SetCurPolicy(policyName);
m_dwCurrentTime = 0;
if (m_pPlayer != null)
{
m_pPlayer.GetHostPlayer().ClearComboSkill();
m_pPlayer.SetOrigPos(m_pPlayer.GetHostPlayer().GetPos());
}
var pGameUI = EC_Game.GetGameRun().GetUIManager().GetInGameUIMan();
//pGameUI?.AddChatMessage(pGameUI.GetStringFromTable(10920), EC_GPDataType.GP_CHAT_MISC);
}
// Put this inside CECAutoPolicy class
private void SetCurPolicy(string strPolicy)
{
// normalize null
strPolicy ??= string.Empty;
// no change
if (string.Equals(m_strCurPolicy, strPolicy, StringComparison.Ordinal))
return;
// stop old policy (if any)
if (!string.IsNullOrEmpty(m_strCurPolicy))
{
// Try a few common Lua callbacks (depending on your lua side implementation).
// You can keep only the one that exists in your project.
try
{
CallLuaFunc("AIManager", "OnStopPolicy",
new List<CScriptValue> { new CScriptValue(m_strCurPolicy) }, null);
}
catch { /* ignore if function not exist */ }
try
{
CallLuaFunc("AIManager", "OnChangePolicy",
new List<CScriptValue> { new CScriptValue(m_strCurPolicy), new CScriptValue(strPolicy) }, null);
}
catch { /* ignore if function not exist */ }
}
// assign
m_strCurPolicy = strPolicy;
// start new policy (if any)
if (!string.IsNullOrEmpty(m_strCurPolicy))
{
try
{
CallLuaFunc("AIManager", "OnStartPolicy",
new List<CScriptValue> { new CScriptValue(m_strCurPolicy) }, null);
}
catch { /* ignore if function not exist */ }
}
}
public void StopPolicy()
{
SetCurPolicy(string.Empty);
m_pPlayer?.OnStopPolicy();
m_dwCurrentTime = 0;
var pGameUI = EC_Game.GetGameRun().GetUIManager().GetInGameUIMan();
//pGameUI?.AddChatMessage(pGameUI.GetStringFromTable(10921), ChatChannel.GP_CHAT_MISC);
}
public void SendEvent_BeHurt(int attacker)
{
var args = new List<CScriptValue>
{
new CScriptValue((double)EVENT_BEHURT),
new CScriptValue((double)attacker),
};
CallLuaFunc("AIManager", "OnEvent", args, null);
if (GPDataTypeHelper.ISNPCID(attacker) && m_pPlayer != null)
m_pPlayer.OnMonsterAttackMe(attacker);
}
public void SendEvent_SkillInterrupt(int skill_id)
{
}
public void SendEvent_ReturnTown()
{
var args = new List<CScriptValue>
{
new CScriptValue((double)EVENT_RETURNTOWNOK),
};
CallLuaFunc("AIManager", "OnEvent", args, null);
}
public void SendEvent_ConfigChanged()
{
var args = new List<CScriptValue>
{
new CScriptValue((double)EVENT_CONFIGCHANGED),
};
CallLuaFunc("AIManager", "OnEvent", args, null);
// C++: m_pPlayer->m_InvalidObj.clear();
// In C# wrapper keeps it protected; clear via StopPolicy-style reset or expose method if you want exact parity.
// If you need exact parity, add a method on wrapper to ClearInvalidObj().
}
public CECPlayerWrapper GetPlayerWrapper() => m_pPlayer;
public bool IsAutoPolicyEnabled() => !string.IsNullOrEmpty(m_strCurPolicy);
public string GetCurPolicy() => m_strCurPolicy;
public CONFIG GetConfigData() => m_Config;
public void SetConfigData(CONFIG data)
{
m_Config = data;
SaveConfigData();
}
public uint GetRemainTime()
{
if (m_Config.nTime > (int)m_dwCurrentTime)
return (uint)(m_Config.nTime - (int)m_dwCurrentTime);
return 0;
}
private bool LoadLuaFile(string filename)
{
// C++ uses LuaState lock + RegisterFile. Provide hooks.
return AutoPolicyLuaHooks.RegisterFile?.Invoke(filename) ?? false;
}
private void CallLuaFunc(string szTable, string szName, List<CScriptValue> args, List<CScriptValue> ret)
{
AutoPolicyLuaHooks.CallLua?.Invoke(szTable, szName, args, ret);
}
private bool LoadConfigData()
{
if (m_pPlayer == null) return false;
string strFile = Path.Combine("userdata", "autopolicy", $"{m_pPlayer.GetHostPlayer().GetCharacterID()}.ini");
if (!File.Exists(strFile))
{
SaveConfigData();
return false;
}
var ini = SimpleIni.Load(strFile);
m_Config.attack_skill = ini.GetInt("config", "attack_skill", 0);
m_Config.attack_iscombo = ini.GetBool("config", "attack_iscombo", false);
m_Config.assist_skill = ini.GetInt("config", "assist_skill", 0);
m_Config.assist_iscombo = ini.GetBool("config", "assist_iscombo", false);
m_Config.nAssistInterval = ini.GetInt("config", "assist_interval", 60000);
m_Config.nTime = ini.GetInt("config", "keeping_time", 3600000);
m_Config.iAutoPickMode = ini.GetInt("config", "autopick", 1);
m_Config.nPetrolRadius = ini.GetInt("config", "petrol_radius", 500);
return true;
}
private void SaveConfigData()
{
Directory.CreateDirectory(Path.Combine("userdata", "autopolicy"));
var ini = new SimpleIni();
ini.SetInt("config", "attack_skill", m_Config.attack_skill);
ini.SetBool("config", "attack_iscombo", m_Config.attack_iscombo);
ini.SetInt("config", "assist_skill", m_Config.assist_skill);
ini.SetBool("config", "assist_iscombo", m_Config.assist_iscombo);
ini.SetInt("config", "assist_interval", m_Config.nAssistInterval);
ini.SetInt("config", "keeping_time", m_Config.nTime);
ini.SetInt("config", "autopick", m_Config.iAutoPickMode);
ini.SetInt("config", "petrol_radius", m_Config.nPetrolRadius);
if (m_pPlayer != null)
{
string strFile = Path.Combine("userdata", "autopolicy", $"{m_pPlayer.GetHostPlayer().GetCharacterID()}.ini");
ini.Save(strFile);
}
}
}
// ===================== Hooks & tiny helpers =====================
// Keep these minimal; replace by your real Lua + script value types.
public static class AutoPolicyLuaHooks
{
// return true if file registered/loaded
public static Func<string, bool> RegisterFile;
// table, func, args, ret (ret can be null)
public static Action<string, string, List<CScriptValue>, List<CScriptValue>> CallLua;
public static Action InitPlayerAPI;
}
// Minimal script value
public readonly struct CScriptValue
{
public readonly object Value;
public CScriptValue(object v) { Value = v; }
}
// Very small INI helper (enough for this file)
public sealed class SimpleIni
{
private readonly Dictionary<string, Dictionary<string, string>> _data = new(StringComparer.OrdinalIgnoreCase);
public static SimpleIni Load(string path)
{
var ini = new SimpleIni();
string section = "";
foreach (var raw in File.ReadLines(path))
{
var line = raw.Trim();
if (line.Length == 0) continue;
if (line.StartsWith(";") || line.StartsWith("#") || line.StartsWith("//")) continue;
if (line.StartsWith("[") && line.EndsWith("]"))
{
section = line.Substring(1, line.Length - 2).Trim();
if (!ini._data.ContainsKey(section))
ini._data[section] = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
continue;
}
int eq = line.IndexOf('=');
if (eq <= 0) continue;
string key = line.Substring(0, eq).Trim();
string val = line.Substring(eq + 1).Trim();
if (!ini._data.TryGetValue(section, out var dict))
{
dict = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
ini._data[section] = dict;
}
dict[key] = val;
}
return ini;
}
public int GetInt(string section, string key, int def)
{
if (_data.TryGetValue(section, out var s) && s.TryGetValue(key, out var v) && int.TryParse(v, out var r))
return r;
return def;
}
public bool GetBool(string section, string key, bool def)
{
if (_data.TryGetValue(section, out var s) && s.TryGetValue(key, out var v))
{
if (v == "1") return true;
if (v == "0") return false;
if (bool.TryParse(v, out var r)) return r;
}
return def;
}
public void SetInt(string section, string key, int value) => Set(section, key, value.ToString());
public void SetBool(string section, string key, bool value) => Set(section, key, value ? "1" : "0");
private void Set(string section, string key, string value)
{
if (!_data.TryGetValue(section, out var s))
{
s = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
_data[section] = s;
}
s[key] = value;
}
public void Save(string path)
{
using var sw = new StreamWriter(path);
foreach (var sect in _data)
{
sw.WriteLine($"[{sect.Key}]");
foreach (var kv in sect.Value)
sw.WriteLine($"{kv.Key}={kv.Value}");
sw.WriteLine();
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: df3c9b50fb2b12f4889649c8666fa10e
@@ -1875,7 +1875,6 @@ namespace ModelRenderer.Scripts.GameData
// }
// return id;
//}
public object[] GetAllDataOfType(ID_SPACE idSpace, DATA_TYPE dataType)
{
List<object> results = new List<object>();
@@ -787,6 +787,11 @@ namespace BrewMonster
public void DefaultUserSettings(ref EC_SYSTEM_SETTING pss, ref EC_VIDEO_SETTING pvs, ref EC_GAME_SETTING pgs, ref EC_BLACKLIST_SETTING pbs, ref EC_COMPUTER_AIDED_SETTING pcas)
{
pss = new EC_SYSTEM_SETTING();
pvs = new EC_VIDEO_SETTING();
pgs = new EC_GAME_SETTING();
pbs = new EC_BLACKLIST_SETTING();
pcas = new EC_COMPUTER_AIDED_SETTING();
pss.Reset();
pvs.Reset();
pgs.Reset();
@@ -1075,7 +1080,7 @@ namespace BrewMonster
using (BinaryReader reader = new BinaryReader(ms))
{
uint dwVer = reader.ReadUInt32();
if (dwVer < 15)
{
DefaultUserConfigData();
@@ -1115,9 +1120,9 @@ namespace BrewMonster
// Note: Would need EC_Game reference
// g_pGame->GetA3DGFXExMan()->SetPriority(m_vs.nEffect);
// Send force attack to server
/* byte forceAttack = glb_BuildPVPMask(false);
byte refuseBless = glb_BuildRefuseBLSMask();
UnityGameSession.Instance.c2s_CmdNotifyForceAttack(forceAttack, refuseBless);*/
byte forceAttack = EC_Utility.glb_BuildPVPMask(false);
byte refuseBless = EC_Utility.glb_BuildRefuseBLSMask();
UnityGameSession.c2s_SendCmdNotifyForceAttack(forceAttack, refuseBless);
}
public void SetSceneLoadRadius(float fRadius)
@@ -0,0 +1,5 @@
using UnityEngine;
namespace BrewMonster
{
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 1c12399ff0e62e146b5951b866e5ab91
@@ -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
@@ -82,7 +82,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();
@@ -100,7 +100,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;
@@ -437,5 +437,41 @@ 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;
}
}
}
@@ -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
@@ -738,6 +739,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)
@@ -810,15 +813,14 @@ namespace BrewMonster.Scripts.Managers
var pItem = new EC_IvtrItem(tid, expire_date);
DATA_TYPE DataType = DATA_TYPE.DT_INVALID;
object data = ElementDataManProvider.GetElementDataMan().get_data_ptr((uint)tid, ID_SPACE.ID_SPACE_ESSENCE, ref DataType);
//Active this to log the data type of item when creating an item
//Debug.Log("Create item data: DataType: " + DataType + " tid: " + tid );
switch(DataType)
//Debug.Log("Create item data: DataType: " + 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);
@@ -1262,6 +1264,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;
@@ -1346,7 +1349,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();
@@ -1416,34 +1419,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));
@@ -1457,7 +1460,7 @@ namespace BrewMonster.Scripts.Managers
AddDescText(col, false, pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_PRICE));
AddDescText(-1, false, " %s (%s)", s1, s2);
}
}
else
{
string s1;
@@ -1648,7 +1651,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 || ...))
@@ -1679,7 +1682,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;
}
@@ -1689,7 +1692,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;
}
@@ -1699,7 +1702,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;
}
@@ -1709,7 +1712,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;
}
@@ -1719,7 +1722,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;
}
@@ -1729,7 +1732,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;
}
@@ -1739,7 +1742,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;
}
@@ -1749,7 +1752,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;
}
@@ -1759,7 +1762,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;
}
@@ -1769,7 +1772,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;
}
@@ -1810,10 +1813,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()
@@ -1948,7 +1951,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,18 +801,35 @@ 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;
}
}
// Weapon type
public enum WeaponType
{
WEAPONTYPE_MELEE = 0,
WEAPONTYPE_RANGE = 1,
};
public enum InventoryType
{
IVTRTYPE_PACK = 0, // Normal pack
IVTRTYPE_EQUIPPACK, // Equipment
IVTRTYPE_TASKPACK, // Task pack
IVTRTYPE_TRASHBOX, // Trash box
IVTRTYPE_TRASHBOX2, // Trash box - material box
IVTRTYPE_TRASHBOX3, // Trash box - fashion box
IVTRTYPE_ACCOUNT_BOX, // User account box
IVTRTYPE_GENERALCARD_BOX, // ¿¨Åưü¹ü
};
}
@@ -75,6 +75,7 @@ namespace BrewMonster
// CECSkill class
public class CECSkill
{
// Skill type enum
public enum SkillType
{
@@ -194,7 +195,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;
@@ -293,15 +293,28 @@ namespace BrewMonster
return string.Empty;
StringBuilder sb = new StringBuilder(1024);
var skillStr = l_SkillStr as SkillStr;
string result = m_pSkillCore.GetIntroduction(sb, 1024, skillStr);
return result;
m_pSkillCore.GetIntroduction(sb, l_SkillStr);
return sb.ToString();
}
public static bool GetDesc(int idSkill, int iLevel, string szText, int iBufLen)
{
if (szText == null || iBufLen == 0)
return false;
CECSkill pSkill = new CECSkill(idSkill, iLevel);
if (pSkill == null)
{
return false;
}
string sz = pSkill.GetDesc();
return true;
}
public int GetCoreCoolingTime()
{
return m_pSkillCore != null ? m_pSkillCore.GetCoolingTime() : 0;
}
public int GetComboSkPreSkill() { return m_pSkillCore.GetComboSkPreSkill(); }
public int GetExecuteTime()
{
@@ -1,3 +1,6 @@
using BrewMonster.Scripts.Skills;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using UnityEngine;
@@ -19,7 +22,19 @@ namespace BrewMonster
set => instance = value;
}
private Dictionary<uint, CECSkill> m_inherentSkillMap = new Dictionary<uint, CECSkill>();
private readonly Dictionary<uint, CECSkill> m_inherentSkillMap = new Dictionary<uint, CECSkill>();
private readonly List<uint> m_activeSkills = new();
private readonly List<uint> m_skillStayTime = new();
private readonly List<bool> m_skillTimeOut = new();
List<uint> m_preSkillSet = new List<uint>(); // 所有是其它技能前提的技能
uint m_dwStartTime; // 技能激活开始时间
public ComboSkillState m_comboSkillState { get; private set; }
public DateTime? ComboStateStartTime { get; private set; }
public IReadOnlyList<uint> ActiveComboSkills => m_activeSkills;
public IReadOnlyList<uint> SkillStayTimes => m_skillStayTime;
public IReadOnlyList<bool> SkillTimeouts => m_skillTimeOut;
public CECSkill GetInherentSkillByID(uint skillID)
{
@@ -32,5 +47,158 @@ namespace BrewMonster
return null;
}
}
public void SetComboSkillState(Dictionary<uint, int> skillDic, ref ComboSkillState state)
{
m_comboSkillState = state;
if (state.skillid != 0)
{
m_dwStartTime = (uint)Time.realtimeSinceStartup * 1000;
SetActiveComboSkills(skillDic);
/* CECComboSkillChange change(CECComboSkillChange::COMBOSKILL_ACTIVE);
NotifyObservers(&change);*/
}
else
{
m_dwStartTime = 0;
m_activeSkills.Clear();
m_skillStayTime.Clear();
m_skillTimeOut.Clear();
/*CECComboSkillChange change(CECComboSkillChange::COMBOSKILL_STOP);
NotifyObservers(&change);*/
}
}
public ComboSkillState GetComboSkillState()
{
return m_comboSkillState;
}
void SetActiveComboSkills(Dictionary<uint, int> dic)
{
Dictionary<uint, int> newList = new Dictionary<uint, int>();
FilterComboSkills(dic, newList);
m_activeSkills.Clear();
m_skillStayTime.Clear();
m_skillTimeOut.Clear();
List<uint> juniorSkills = new List<uint>();
int i;
foreach (var skill in newList)
{
int skillID = (int)skill.Key;
int skillTime = skill.Value;
m_activeSkills.Add((uint)skillID);
m_skillStayTime.Add((uint)skillTime);
m_skillTimeOut.Add(false);
if (m_inherentSkillMap.ContainsKey((uint)skillID))
{
juniorSkills.Add((uint)GetFirstChildRecursively(skillID));
}
else
{
juniorSkills.Add((uint)skillID);
}
}
// 冒泡排序:有后续技能的放在前面;最原始的初级技能id小的放在前面
int j;
for (i = m_activeSkills.Count - 1; i > 0; i--)
{
for (j = 0; j < i; j++)
{
bool bPreSkill1 = IsComboPreSkill(juniorSkills[j]);
bool bPreSkill2 = IsComboPreSkill(juniorSkills[j + 1]);
if ((!bPreSkill1 && bPreSkill2) || (bPreSkill1 == bPreSkill2 && juniorSkills[j] > juniorSkills[j + 1]))
{
EC_Utility.Swap(m_activeSkills, j, j + 1);
EC_Utility.Swap(m_skillStayTime, j, j + 1);
EC_Utility.Swap(juniorSkills, j, j + 1);
}
}
}
}
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);
}
int GetFirstChildRecursively(int iSkillID)
{
Dictionary<uint, int> vecJuniors = CECHostSkillModel.Instance.GetJunior(iSkillID);
if (vecJuniors.Count == 0)
{
return iSkillID;
}
else
{
foreach (var skill in vecJuniors)
{
int iComboPreSkill = ElementSkill.GetComboSkPreSkill(skill.Key);
if (iComboPreSkill != 0)
{
return GetFirstChildRecursively((int)skill.Key);
}
}
return iSkillID;
}
}
void FilterComboSkills(Dictionary<uint, int> inDic, Dictionary<uint, int> outDic)
{
outDic.Clear();
CECTaoistRank pCurTaoistRank = CECTaoistRank.GetTaoistRank(CECGameRun.Instance.GetHostPlayer().GetBasicProps().iLevel2);
CECHostSkillModel hostSkillModel = CECHostSkillModel.Instance;
uint i;
foreach (var skill in inDic)
{
uint uiSkillID = skill.Key;
if (!m_inherentSkillMap.ContainsKey(uiSkillID))
{
// 如果不是天生技能,则执行过滤
enumEvilGod skillEvilGod = hostSkillModel.GetSkillEvilGod((int)uiSkillID);
enumSkillLearnedState skillLearnState = hostSkillModel.GetSkillLearnedState((int)uiSkillID);
// 如果该技能已被覆盖,则去掉
if (enumSkillLearnedState.SKILL_OVERRIDDEN == skillLearnState)
{
continue;
}
// 如果是魔技能且人物修真为普通或仙,则去掉
if (enumEvilGod.SKILL_EVIL == skillEvilGod)
{
if (pCurTaoistRank.IsBaseRank() || pCurTaoistRank.IsGodRank())
{
continue;
}
}
// 如果是仙技能且人物修真为普通或魔,则去掉
if (enumEvilGod.SKILL_GOD == skillEvilGod)
{
if (pCurTaoistRank.IsBaseRank() || pCurTaoistRank.IsEvilRank())
{
continue;
}
}
// 如果是仙魔技能且未学习,则去掉
if (enumEvilGod.SKILL_GOD == skillEvilGod || skillEvilGod == enumEvilGod.SKILL_EVIL)
{
if (enumSkillLearnedState.SKILL_NOT_LEARNED == skillLearnState)
{
continue;
}
}
}
outDic[skill.Key] = skill.Value;
}
}
}
}
@@ -20,7 +20,7 @@ namespace BrewMonster
BMLogger.Log("CECTaoistRank OnPlay Reset");
initComplete = false;
CECTaoistRank[] s_allTaoistRanks = new CECTaoistRank[(int)ToaistRank.TotalRankCount];
}
}
#endif
public int GetID()
{
@@ -75,7 +75,10 @@ namespace BrewMonster
init();
return s_allTaoistRanks[(int)ToaistRank.BaseRankCount + (int)ToaistRank.GodRankCount - 1];
}
public bool IsBaseRank()
{
return !IsGodRank() && !IsEvilRank();
}
public bool IsEvilRank()
{
CECTaoistRank EvilRank;
@@ -1414,7 +1414,10 @@ namespace BrewMonster
}
// Get character ID
// Play Gfx on Models
public static int GetRealmLayer(int realmLevel) { return realmLevel != 0 ? (realmLevel + 9) / 10 : 0; }
public static int GetRealmSubLevel(int realmLevel) { return realmLevel != 0 ? (realmLevel % 10 != 0 ? realmLevel % 10 : 10) : 0; }
protected bool PlayGfx(string szPath, string szHook, float fScale /*1.0f*/, uint iShapeTypeMask /*(1<<PLAYERMODEL_MAJOR)*/, bool bForceNoRecord = false)
{
// bool bPlayed(false);
+3 -2
View File
@@ -208,7 +208,7 @@ public class CECNPC : CECObject
private void OnMsgNPCInvisible(ECMSG Msg)
{
cmd_object_invisible pCmd = GPDataTypeHelper.FromBytes< cmd_object_invisible>((byte[])Msg.dwParam1);
cmd_object_invisible pCmd = GPDataTypeHelper.FromBytes<cmd_object_invisible>((byte[])Msg.dwParam1);
if (pCmd.invisible_degree > 0)
{
@@ -1032,7 +1032,7 @@ public class CECNPC : CECObject
}
public void StopMoveTo(cmd_object_stop_move cmd)
{
BMLogger.LogMono(this,"CECNPC::StopMoveTo");
BMLogger.LogMono(this, "CECNPC::StopMoveTo");
if (IsDead())
return;
BMLogger.LogMono(this, "CECNPC::StopMoveTo not dead");
@@ -1517,6 +1517,7 @@ public class CECNPC : CECObject
m_pNPCModelPolicy.PlayModelAction(iAction, bRestart, null);
}
bool IsDisappearing() { return m_DisappearCnt.GetCounter() != 0 ? true : false; }
public int GetTemplateID() { return m_NPCInfo.tid; }
public float GetTouchRadius() { return m_fTouchRad; }
@@ -332,6 +332,16 @@ namespace CSNetwork.C2SCommand
var cmdBuf = SerializeCommand(CommandID.CAST_POS_SKILL, cmd);
return cmdBuf;
}
public static Octets CreateNotifyForceAttack(int iForceAttack, byte refuseBless)
{
var cmd = new cmd_notify_force_attack
{
force_attack = (byte)iForceAttack,
refuse_bless = refuseBless
};
var cmdBuf = SerializeCommand(CommandID.NOTIFY_FORCE_ATTACK, cmd);
return cmdBuf;
}
public static short FloatToFix8(float x)
{
@@ -1253,6 +1253,14 @@ namespace CSNetwork.GPDataType
public uint content_length;
public byte[] content;
};
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct cmd_combo_skill_prepare
{
public int skill_id;
public int timestamp;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
public int[] args;
};
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct cmd_host_start_attack
@@ -1643,7 +1651,7 @@ namespace CSNetwork.GPDataType
{
return (id & 0x80000000) != 0 && (id & 0x40000000) == 0;
}
public static string ReplacePercentD(string fmt, params object[] args)
public static string ReplacePercentD(string fmt, params object[] args)
{
if (string.IsNullOrEmpty(fmt) || args == null || args.Length == 0)
return fmt;
@@ -2271,30 +2279,31 @@ namespace CSNetwork.GPDataType
public byte is_active;
};
public struct cmd_reincarnation_tome_info
{
public int tome_exp;
public char tome_active; // 1
public int count;
public struct _entry
{
public int level;
public int timestamp;
public int exp;
};
{
public int tome_exp;
public char tome_active; // 1
public int count;
public struct _entry
{
public int level;
public int timestamp;
public int exp;
};
public _entry[] records;
public bool CheckValid(int buf_size, out int sz)
{ sz = 0;
if (count < 0)
return false;
sz = Marshal.SizeOf<cmd_reincarnation_tome_info>() - Marshal.SizeOf<_entry[]>();
sz += count * Marshal.SizeOf<_entry>();
return buf_size >= sz;
}
};
public bool CheckValid(int buf_size, out int sz)
{
sz = 0;
if (count < 0)
return false;
sz = Marshal.SizeOf<cmd_reincarnation_tome_info>() - Marshal.SizeOf<_entry[]>();
sz += count * Marshal.SizeOf<_entry>();
return buf_size >= sz;
}
};
// Pet type
public enum GP_PET_TYPE
{
{
GP_PET_CLASS_INVALID = -1,
GP_PET_CLASS_MOUNT = 0, //
GP_PET_CLASS_COMBAT, // ս
@@ -2304,13 +2313,13 @@ namespace CSNetwork.GPDataType
GP_PET_CLASS_EVOLUTION, //
GP_PET_CLASS_MAX,
};
public struct PetSkill
{
public int skill;
public int level;
}
}
public struct _evo_prop
{
public int r_attack;
@@ -2322,29 +2331,29 @@ namespace CSNetwork.GPDataType
}
public enum GP_PET_SKILL_NUM
{
GP_PET_SKILL_NUM = 8
GP_PET_SKILL_NUM = 8
};
public struct info_pet
{
public int honor_point; // øж
public int hunger; //
public int feed_time; // ϴιڵʱ
public int pet_tid; // ģID
public int pet_vis_tid; // ĿɼIDΪ0ʾɼID
public int pet_egg_tid; // ID
public int pet_class; // սͳ
public float hp_factor; // Ѫջʱʹã 0Ϊ
public short level; //
public ushort color; // ɫλΪ1ʾЧĿǰЧ
public int exp; // ﵱǰ
public int skill_point; // ʣܵ
public char is_bind; // Ƿ˺һһMask0x01 ˺һ0x02 Ѱɽ
public char unused; // ĿǰЧ
public ushort name_len; // ֳ ĿǰЧΪ߻
public char[] name; //
public PetSkill[] skills;
{
public int honor_point; // øж
public int hunger; //
public int feed_time; // ϴιڵʱ
public int pet_tid; // ģID
public int pet_vis_tid; // ĿɼIDΪ0ʾɼID
public int pet_egg_tid; // ID
public int pet_class; // սͳ
public float hp_factor; // Ѫջʱʹã 0Ϊ
public short level; //
public ushort color; // ɫλΪ1ʾЧĿǰЧ
public int exp; // ﵱǰ
public int skill_point; // ʣܵ
public char is_bind; // Ƿ˺һһMask0x01 ˺һ0x02 Ѱɽ
public char unused; // ĿǰЧ
public ushort name_len; // ֳ ĿǰЧΪ߻
public char[] name; //
public PetSkill[] skills;
public _evo_prop evo_prop;
public int[] reserved; // δ
public int[] reserved; // δ
public info_pet(bool isDefault = true)
{
honor_point = 0;
@@ -2364,7 +2373,8 @@ namespace CSNetwork.GPDataType
name_len = 0;
name = new char[16];
skills = new PetSkill[(int)GP_PET_SKILL_NUM.GP_PET_SKILL_NUM];
evo_prop = new _evo_prop{
evo_prop = new _evo_prop
{
r_attack = 0,
r_defense = 0,
r_hp = 0,
@@ -2374,6 +2384,17 @@ namespace CSNetwork.GPDataType
};
reserved = new int[10];
}
};
};
public enum REFUSE_BLESS_MASK : byte
{
REFUSE_NEUTRAL_BLESS = 0x0001, // ²»½ÓÊÜÖÐÐÔ×£¸£
REFUSE_NON_TEAMMATE_BLESS = 0x0002, // ²»½ÓÊܷǶÓÓÑ×£¸£
};
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct cmd_notify_force_attack
{
public byte force_attack;
public byte refuse_bless;
};
}
File diff suppressed because it is too large Load Diff
@@ -326,6 +326,10 @@ namespace BrewMonster.Network
public static void c2s_CmdNPCSevHeal()
{
}
public static void c2s_SendCmdNotifyForceAttack(int iForceAttack, byte refuseBless)
{
Instance._gameSession.c2s_SendCmdNotifyForceAttack(iForceAttack, refuseBless);
}
public static void c2s_CmdNPCSevAcceptTask(int idTask,int idStorage,int idRefreshItem)
{
@@ -10,15 +10,22 @@
*
* Copyright (c) 2005 Archosaur Studio, All Rights Reserved.
*/
using static BrewMonster.CECSCCommand.CommandID;
using static BrewMonster.Scripts.RoleExpression;
using static BrewMonster.Scripts.DescriptipionMsg;
using BrewMonster.Assets.PerfectWorld.Scripts.Skills;
using BrewMonster.Network;
using BrewMonster.Scripts;
using BrewMonster.Scripts.Skills;
using CSNetwork.C2SCommand;
using CSNetwork.GPDataType;
using CSNetwork.S2CCommand;
using System;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
using static BrewMonster.EC_Resource;
using static BrewMonster.IconResourceType;
namespace BrewMonster
{
@@ -123,7 +130,7 @@ namespace BrewMonster
/// <summary>
/// Create a skill group shortcut at specified position
/// </summary>
/* public bool CreateSkillGroupShortcut(int iSlot, int iGroupIdx)
public bool CreateSkillGroupShortcut(int iSlot, int iGroupIdx)
{
CECSCSkillGrp pSkillGrpSC = new CECSCSkillGrp();
if (pSkillGrpSC == null)
@@ -137,7 +144,7 @@ namespace BrewMonster
SetShortcut(iSlot, pSkillGrpSC);
return true;
}*/
}
/// <summary>
/// Create a pet shortcut at specified position
@@ -260,11 +267,6 @@ namespace BrewMonster
{
m_aShortcuts[iSlot] = null;
}
CECSCSkill skill;
if ((skill = pShortcut as CECSCSkill) != null)
{
BMLogger.LogError("SetShortcut: Setting shortcut at slot " + iSlot + $" skill = {skill.GetSkill().GetName()}");
}
m_aShortcuts[iSlot] = pShortcut;
}
@@ -636,100 +638,100 @@ namespace BrewMonster
break;
}
/* case CECShortcut.ShortcutType.SCT_ITEM:
{
int iPack = BitConverter.ToInt32(pDataBuf, offset);
offset += sizeof(int);
int iIvtrSlot = BitConverter.ToInt32(pDataBuf, offset);
offset += sizeof(int);
int idItem = BitConverter.ToInt32(pDataBuf, offset);
offset += sizeof(int);
/* case CECShortcut.ShortcutType.SCT_ITEM:
{
int iPack = BitConverter.ToInt32(pDataBuf, offset);
offset += sizeof(int);
int iIvtrSlot = BitConverter.ToInt32(pDataBuf, offset);
offset += sizeof(int);
int idItem = BitConverter.ToInt32(pDataBuf, offset);
offset += sizeof(int);
CECInventory pPack = pHost.GetPack(iPack);
if (pPack == null)
{
Debug.LogError("CECShortcutSet::LoadConfigData - Invalid inventory");
return false;
}
CECInventory pPack = pHost.GetPack(iPack);
if (pPack == null)
{
Debug.LogError("CECShortcutSet::LoadConfigData - Invalid inventory");
return false;
}
CECIvtrItem pItem = pPack.GetItem(iIvtrSlot);
if (pItem != null)
CreateItemShortcut(iSlot, iPack, iIvtrSlot, pItem);
CECIvtrItem pItem = pPack.GetItem(iIvtrSlot);
if (pItem != null)
CreateItemShortcut(iSlot, iPack, iIvtrSlot, pItem);
break;
}
break;
}*/
case CECShortcut.ShortcutType.SCT_SKILLGRP:
{
if (dwVer >= 3)
{
int iGroupIdx = BitConverter.ToInt32(pDataBuf, offset);
offset += sizeof(int);
case CECShortcut.ShortcutType.SCT_SKILLGRP:
{
if (dwVer >= 3)
{
int iGroupIdx = BitConverter.ToInt32(pDataBuf, offset);
offset += sizeof(int);
if (iGroupIdx >= 0)
CreateSkillGroupShortcut(iSlot, iGroupIdx);
}
else
{
Debug.LogError("CECShortcutSet::LoadConfigData - Invalid version for skill group");
return false;
}
break;
}
if (iGroupIdx >= 0)
CreateSkillGroupShortcut(iSlot, iGroupIdx);
}
else
{
Debug.LogError("CECShortcutSet::LoadConfigData - Invalid version for skill group");
return false;
}
break;
}
case CECShortcut.ShortcutType.SCT_PET:
{
if (dwVer >= 4)
{
int iPetIndex = BitConverter.ToInt32(pDataBuf, offset);
offset += sizeof(int);
/*case CECShortcut.ShortcutType.SCT_PET:
{
if (dwVer >= 4)
{
int iPetIndex = BitConverter.ToInt32(pDataBuf, offset);
offset += sizeof(int);
if (iPetIndex >= 0)
CreatePetShortcut(iSlot, iPetIndex);
}
else
{
Debug.LogError("CECShortcutSet::LoadConfigData - Invalid version for pet");
return false;
}
break;
}
if (iPetIndex >= 0)
CreatePetShortcut(iSlot, iPetIndex);
}
else
{
Debug.LogError("CECShortcutSet::LoadConfigData - Invalid version for pet");
return false;
}
break;
}
case CECShortcut.ShortcutType.SCT_AUTOFASHION:
{
if (dwVer >= 5)
{
int iAutoFashionIndex = BitConverter.ToInt32(pDataBuf, offset);
offset += sizeof(int);
case CECShortcut.ShortcutType.SCT_AUTOFASHION:
{
if (dwVer >= 5)
{
int iAutoFashionIndex = BitConverter.ToInt32(pDataBuf, offset);
offset += sizeof(int);
if (iAutoFashionIndex >= 0)
CreateAutoFashionShortcut(iSlot, iAutoFashionIndex);
}
else
{
Debug.LogError("CECShortcutSet::LoadConfigData - Invalid version for auto fashion");
return false;
}
break;
}
if (iAutoFashionIndex >= 0)
CreateAutoFashionShortcut(iSlot, iAutoFashionIndex);
}
else
{
Debug.LogError("CECShortcutSet::LoadConfigData - Invalid version for auto fashion");
return false;
}
break;
}
case CECShortcut.ShortcutType.SCT_SYSMODULE:
{
if (dwVer > 10)
{
int iSys = BitConverter.ToInt32(pDataBuf, offset);
offset += sizeof(int);
case CECShortcut.ShortcutType.SCT_SYSMODULE:
{
if (dwVer > 10)
{
int iSys = BitConverter.ToInt32(pDataBuf, offset);
offset += sizeof(int);
if (iSys >= 0)
CreateSystemModuleShortcut(iSlot, iSys);
}
else
{
Debug.LogError("CECShortcutSet::LoadConfigData - Invalid version for system module");
return false;
}
break;
}*/
if (iSys >= 0)
CreateSystemModuleShortcut(iSlot, iSys);
}
else
{
Debug.LogError("CECShortcutSet::LoadConfigData - Invalid version for system module");
return false;
}
break;
}*/
/* default:
//TODO: uncomment
@@ -759,7 +761,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;
@@ -781,25 +783,37 @@ namespace BrewMonster
{
return m_iSCType;
}
public virtual string GetDesc() { return ""; }
public virtual CECShortcut Clone()
{
return new CECShortcut();
}
public virtual bool Execute() { return true; }
public virtual int GetCoolTime(ref int piMax)
{
piMax = 0;
return 0;
}
public virtual string GetIconFile()
public virtual string GetIconFile()
{
return "";
// Return a default icon file name
return "unknown";
}
}
// Shortcut type
public enum ShortcutType
{
SCT_UNKNOWN = -1,
SCT_SKILL = 0,
SCT_ITEM,
SCT_COMMAND,
SCT_SKILLGRP,
SCT_PET,
SCT_AUTOFASHION,
SCT_SYSMODULE,
};
public class CECSCCommand : CECShortcut
{
private int m_iCommand; // Command ID
@@ -813,7 +827,7 @@ namespace BrewMonster
CMD_FINDTARGET,
CMD_ASSISTATTACK,
CMD_INVITETOTEAM,
CMD_LEAVETEAM,
CMD_LEAVETEAM,
CMD_KICKTEAMMEM,
CMD_FINDTEAM,
CMD_STARTTRADE,
@@ -829,25 +843,93 @@ namespace BrewMonster
}
public CECSCCommand(int iCommand)
{
m_iSCType = (int)ShortcutType.SCT_COMMAND;
m_iSCType = (int)ShortcutType.SCT_COMMAND;
m_iCommand = iCommand;
m_dwParam = 0;
m_dwParam = 0;
}
public override string GetDesc()
{
CECStringTab pDescTab = EC_Game.GetItemDesc();
string szDesc = ("");
switch ((CommandID)m_iCommand)
{
case CMD_SITDOWN: szDesc = pDescTab.GetWideString((int)CMDDESC_SITDOWN); break;
case CMD_WALKRUN: szDesc = pDescTab.GetWideString((int)CMDDESC_WALKRUN); break;
case CMD_NORMALATTACK: szDesc = pDescTab.GetWideString((int)CMDDESC_NORMALATTACK); break;
case CMD_FINDTARGET: szDesc = pDescTab.GetWideString((int)CMDDESC_FINDTARGET); break;
case CMD_ASSISTATTACK: szDesc = pDescTab.GetWideString((int)CMDDESC_ASSISTATTACK); break;
case CMD_INVITETOTEAM: szDesc = pDescTab.GetWideString((int)CMDDESC_INVITETOTEAM); break;
case CMD_LEAVETEAM: szDesc = pDescTab.GetWideString((int)CMDDESC_LEAVETEAM); break;
case CMD_KICKTEAMMEM: szDesc = pDescTab.GetWideString((int)CMDDESC_KICKTEAMMEM); break;
case CMD_FINDTEAM: szDesc = pDescTab.GetWideString((int)CMDDESC_FINDTEAM); break;
case CMD_STARTTRADE: szDesc = pDescTab.GetWideString((int)CMDDESC_STARTTRADE); break;
case CMD_SELLBOOTH: szDesc = pDescTab.GetWideString((int)CMDDESC_SELLBOOTH); break;
case CMD_BUYBOOTH: szDesc = pDescTab.GetWideString((int)CMDDESC_BUYBOOTH); break;
case CMD_INVITETOFACTION: szDesc = pDescTab.GetWideString((int)CMDDESC_INVITETOFACTION); break;
case CMD_FLY: szDesc = pDescTab.GetWideString((int)CMDDESC_FLY); break;
case CMD_PICKUP: szDesc = pDescTab.GetWideString((int)CMDDESC_PICKUP); break;
case CMD_GATHER: szDesc = pDescTab.GetWideString((int)CMDDESC_GATHER); break;
case CMD_RUSHFLY: szDesc = pDescTab.GetWideString((int)CMDDESC_RUSHFLY); break;
case CMD_BINDBUDDY: szDesc = pDescTab.GetWideString((int)CMDDESC_BINDBUDDY); break;
case CMD_PLAYPOSE:
{
switch ((RoleExpression)m_dwParam)
{
case ROLEEXP_WAVE: szDesc = pDescTab.GetWideString((int)FACEDESC_WAVEHAND); break;
case ROLEEXP_NOD: szDesc = pDescTab.GetWideString((int)FACEDESC_NOD); break;
case ROLEEXP_SHAKEHEAD: szDesc = pDescTab.GetWideString((int)FACEDESC_SHADEHEAD); break;
case ROLEEXP_SHRUG: szDesc = pDescTab.GetWideString((int)FACEDESC_SHRUG); break;
case ROLEEXP_LAUGH: szDesc = pDescTab.GetWideString((int)FACEDESC_LAUGH); break;
case ROLEEXP_ANGRY: szDesc = pDescTab.GetWideString((int)FACEDESC_ANGRY); break;
case ROLEEXP_STUN: szDesc = pDescTab.GetWideString((int)FACEDESC_FAINT); break;
case ROLEEXP_DEPRESSED: szDesc = pDescTab.GetWideString((int)FACEDESC_SAD); break;
case ROLEEXP_KISSHAND: szDesc = pDescTab.GetWideString((int)FACEDESC_KISSHAND); break;
case ROLEEXP_SHY: szDesc = pDescTab.GetWideString((int)FACEDESC_SHY); break;
case ROLEEXP_SALUTE: szDesc = pDescTab.GetWideString((int)FACEDESC_SALUTE); break;
case ROLEEXP_SITDOWN: szDesc = pDescTab.GetWideString((int)FACEDESC_SITDOWN); break;
case ROLEEXP_ASSAULT: szDesc = pDescTab.GetWideString((int)FACEDESC_CHARGE); break;
case ROLEEXP_THINK: szDesc = pDescTab.GetWideString((int)FACEDESC_THINK); break;
case ROLEEXP_DEFIANCE: szDesc = pDescTab.GetWideString((int)FACEDESC_CHALLENGE); break;
case ROLEEXP_VICTORY: szDesc = pDescTab.GetWideString((int)FACEDESC_WIN); break;
case ROLEEXP_GAPE: szDesc = pDescTab.GetWideString((int)FACEDESC_GAPE); break;
case ROLEEXP_KISS: szDesc = pDescTab.GetWideString((int)FACEDESC_KISS); break;
case ROLEEXP_FIGHT: szDesc = pDescTab.GetWideString((int)FACEDESC_FIGHT); break;
case ROLEEXP_ATTACK1: szDesc = pDescTab.GetWideString((int)FACEDESC_ATTACK1); break;
case ROLEEXP_ATTACK2: szDesc = pDescTab.GetWideString((int)FACEDESC_ATTACK2); break;
case ROLEEXP_ATTACK3: szDesc = pDescTab.GetWideString((int)FACEDESC_ATTACK3); break;
case ROLEEXP_ATTACK4: szDesc = pDescTab.GetWideString((int)FACEDESC_ATTACK4); break;
case ROLEEXP_DEFENCE: szDesc = pDescTab.GetWideString((int)FACEDESC_DEFENCE); break;
case ROLEEXP_FALL: szDesc = pDescTab.GetWideString((int)FACEDESC_FALL); break;
case ROLEEXP_FALLONGROUND: szDesc = pDescTab.GetWideString((int)FACEDESC_FALLONGROUND); break;
case ROLEEXP_LOOKAROUND: szDesc = pDescTab.GetWideString((int)FACEDESC_LOOKAROUND); break;
case ROLEEXP_DANCE: szDesc = pDescTab.GetWideString((int)FACEDESC_DANCE); break;
case ROLEEXP_FASHIONWEAPON: szDesc = pDescTab.GetWideString((int)FACEDESC_FASHIONWEAPON); break;
default:
break;
}
break;
}
default:
return ("");
}
return szDesc;
}
public CECSCCommand(CECSCCommand src)
{
m_iCommand = src.m_iCommand;
m_dwParam = src.m_dwParam;
m_dwParam = src.m_dwParam;
}
public const int CMD_PLAYPOSE = 1; // Example constant
// public override ShortcutType GetType() => ShortcutType.SCT_COMMAND;
public override CECShortcut Clone() => new CECSCCommand(this);
public int GetCommandID() => 0;
public int GetParam() => 0;
// Set / Get command parameter
public void SetParam(uint dwParam) { m_dwParam = dwParam; }
// Execute shortcut
public override bool Execute()
{
@@ -863,44 +945,112 @@ namespace BrewMonster
//
// if (bForbidCmd)
// {
// g_pGame->GetGameRun()->AddFixedMessage(FIXMSG_CMD_INCOOLTIME);
// g_pGame.GetGameRun().AddFixedMessage(FIXMSG_CMD_INCOOLTIME);
// return false;
// }
// }
switch (m_iCommand)
{
// case CMD_SITDOWN: pHost->CmdSitDown(!pHost->IsSitting()); break;
// case CMD_WALKRUN: pHost->CmdWalkRun(!pHost->GetWalkRunFlag()); break;
// case CMD_NORMALATTACK: pHost->CmdNormalAttack(); break;
// case CMD_FINDTARGET: pHost->CmdFindTarget(); break;
// case CMD_ASSISTATTACK: pHost->CmdAssistAttack(); break;
// case CMD_INVITETOTEAM: pHost->CmdInviteToTeam(); break;
// case CMD_LEAVETEAM: pHost->CmdLeaveTeam(); break;
// case CMD_KICKTEAMMEM: pHost->CmdKickTeamMember(); break;
// case CMD_FINDTEAM: pHost->CmdFindTeam(); break;
// case CMD_STARTTRADE: pHost->CmdStartTrade(); break;
// case CMD_SELLBOOTH: pHost->CmdSellBooth(); break;
// case CMD_BUYBOOTH: pHost->CmdBuyBooth(); break;
// case CMD_SITDOWN: pHost.CmdSitDown(!pHost.IsSitting()); break;
// case CMD_WALKRUN: pHost.CmdWalkRun(!pHost.GetWalkRunFlag()); break;
// case CMD_NORMALATTACK: pHost.CmdNormalAttack(); break;
// case CMD_FINDTARGET: pHost.CmdFindTarget(); break;
// case CMD_ASSISTATTACK: pHost.CmdAssistAttack(); break;
// case CMD_INVITETOTEAM: pHost.CmdInviteToTeam(); break;
// case CMD_LEAVETEAM: pHost.CmdLeaveTeam(); break;
// case CMD_KICKTEAMMEM: pHost.CmdKickTeamMember(); break;
// case CMD_FINDTEAM: pHost.CmdFindTeam(); break;
// case CMD_STARTTRADE: pHost.CmdStartTrade(); break;
// case CMD_SELLBOOTH: pHost.CmdSellBooth(); break;
// case CMD_BUYBOOTH: pHost.CmdBuyBooth(); break;
case (int)CommandID.CMD_PLAYPOSE: pHost.CmdStartPose((int)m_dwParam); break;
// case CMD_INVITETOFACTION: pHost->CmdInviteToFaction(); break;
// case CMD_INVITETOFACTION: pHost.CmdInviteToFaction(); break;
// case CMD_FLY:
// {
// // Èç¹ûÆï³ËÒª·ÉÐУ¬ÔòÕâЩ action switcher£¬·ñÔò CmdFly
// if (!pHost->GetActionSwitcher() || !pHost->GetActionSwitcher()->OnRideToFlyAction())
// pHost->CmdFly();
// if (!pHost.GetActionSwitcher() || !pHost.GetActionSwitcher().OnRideToFlyAction())
// pHost.CmdFly();
// break;
// }
// case CMD_PICKUP: pHost->CmdPickup(); break;
// case CMD_GATHER: pHost->CmdGather(); break;
// case CMD_RUSHFLY: pHost->CmdRushFly(); break;
// case CMD_BINDBUDDY: pHost->CmdBindBuddy(pHost->GetSelectedTarget()); break;
// case CMD_PICKUP: pHost.CmdPickup(); break;
// case CMD_GATHER: pHost.CmdGather(); break;
// case CMD_RUSHFLY: pHost.CmdRushFly(); break;
// case CMD_BINDBUDDY: pHost.CmdBindBuddy(pHost.GetSelectedTarget()); break;
default:
return false;
}
return true;
}
public override string GetIconFile()
{
string szIconFile = "";
switch ((CommandID)m_iCommand)
{
case CommandID.CMD_SITDOWN: szIconFile = res_IconFile((int)IconResourceType.RES_ICON_CMD_SITDOWN); break;
case CommandID.CMD_WALKRUN: szIconFile = res_IconFile((int)IconResourceType.RES_ICON_CMD_WALKRUN); break;
case CommandID.CMD_NORMALATTACK: szIconFile = res_IconFile((int)IconResourceType.RES_ICON_CMD_NORMALATTACK); break;
case CommandID.CMD_FINDTARGET: szIconFile = res_IconFile((int)IconResourceType.RES_ICON_CMD_FINDTARGET); break;
case CommandID.CMD_ASSISTATTACK: szIconFile = res_IconFile((int)IconResourceType.RES_ICON_CMD_ASSISTATTACK); break;
case CommandID.CMD_INVITETOTEAM: szIconFile = res_IconFile((int)IconResourceType.RES_ICON_CMD_INVITETOTEAM); break;
case CommandID.CMD_LEAVETEAM: szIconFile = res_IconFile((int)IconResourceType.RES_ICON_CMD_LEAVETEAM); break;
case CommandID.CMD_KICKTEAMMEM: szIconFile = res_IconFile((int)IconResourceType.RES_ICON_CMD_KICKTEAMMEM); break;
case CommandID.CMD_FINDTEAM: szIconFile = res_IconFile((int)IconResourceType.RES_ICON_CMD_FINDTEAM); break;
case CommandID.CMD_STARTTRADE: szIconFile = res_IconFile((int)IconResourceType.RES_ICON_CMD_STARTTRADE); break;
case CommandID.CMD_SELLBOOTH: szIconFile = res_IconFile((int)IconResourceType.RES_ICON_CMD_SELLBOOTH); break;
case CommandID.CMD_BUYBOOTH: szIconFile = res_IconFile((int)IconResourceType.RES_ICON_CMD_BUYBOOTH); break;
case CommandID.CMD_INVITETOFACTION: szIconFile = res_IconFile((int)IconResourceType.RES_ICON_CMD_INVITETOFACTION); break;
case CommandID.CMD_FLY: szIconFile = res_IconFile((int)IconResourceType.RES_ICON_CMD_FLY); break;
case CommandID.CMD_PICKUP: szIconFile = res_IconFile((int)IconResourceType.RES_ICON_CMD_PICKUP); break;
case CommandID.CMD_GATHER: szIconFile = res_IconFile((int)IconResourceType.RES_ICON_CMD_GATHER); break;
case CommandID.CMD_RUSHFLY: szIconFile = res_IconFile((int)IconResourceType.RES_ICON_CMD_RUSHFLY); break;
case CommandID.CMD_BINDBUDDY: szIconFile = res_IconFile((int)IconResourceType.RES_ICON_CMD_BINDBUDDY); break;
case CommandID.CMD_PLAYPOSE:
{
switch ((RoleExpression)m_dwParam)
{
case RoleExpression.ROLEEXP_WAVE: szIconFile = res_IconFile((int)IconResourceType.RES_ICON_CMD_EXP_WAVE); break;
case RoleExpression.ROLEEXP_NOD: szIconFile = res_IconFile((int)IconResourceType.RES_ICON_CMD_EXP_NOD); break;
case RoleExpression.ROLEEXP_SHAKEHEAD: szIconFile = res_IconFile((int)IconResourceType.RES_ICON_CMD_EXP_SHAKEHEAD); break;
case RoleExpression.ROLEEXP_SHRUG: szIconFile = res_IconFile((int)IconResourceType.RES_ICON_CMD_EXP_SHRUG); break;
case RoleExpression.ROLEEXP_LAUGH: szIconFile = res_IconFile((int)IconResourceType.RES_ICON_CMD_EXP_LAUGH); break;
case RoleExpression.ROLEEXP_ANGRY: szIconFile = res_IconFile((int)IconResourceType.RES_ICON_CMD_EXP_ANGRY); break;
case RoleExpression.ROLEEXP_STUN: szIconFile = res_IconFile((int)IconResourceType.RES_ICON_CMD_EXP_STUN); break;
case RoleExpression.ROLEEXP_DEPRESSED: szIconFile = res_IconFile((int)IconResourceType.RES_ICON_CMD_EXP_DEPRESSED); break;
case RoleExpression.ROLEEXP_KISSHAND: szIconFile = res_IconFile((int)IconResourceType.RES_ICON_CMD_EXP_KISSHAND); break;
case RoleExpression.ROLEEXP_SHY: szIconFile = res_IconFile((int)IconResourceType.RES_ICON_CMD_EXP_SHY); break;
case RoleExpression.ROLEEXP_SALUTE: szIconFile = res_IconFile((int)IconResourceType.RES_ICON_CMD_EXP_SALUTE); break;
case RoleExpression.ROLEEXP_SITDOWN: szIconFile = res_IconFile((int)IconResourceType.RES_ICON_CMD_EXP_SITDOWN); break;
case RoleExpression.ROLEEXP_ASSAULT: szIconFile = res_IconFile((int)IconResourceType.RES_ICON_CMD_EXP_ASSAULT); break;
case RoleExpression.ROLEEXP_THINK: szIconFile = res_IconFile((int)IconResourceType.RES_ICON_CMD_EXP_THINK); break;
case RoleExpression.ROLEEXP_DEFIANCE: szIconFile = res_IconFile((int)IconResourceType.RES_ICON_CMD_EXP_DEFIANCE); break;
case RoleExpression.ROLEEXP_VICTORY: szIconFile = res_IconFile((int)IconResourceType.RES_ICON_CMD_EXP_VICTORY); break;
case RoleExpression.ROLEEXP_GAPE: szIconFile = res_IconFile((int)IconResourceType.RES_ICON_CMD_EXP_GAPE); break;
case RoleExpression.ROLEEXP_KISS: szIconFile = res_IconFile((int)IconResourceType.RES_ICON_CMD_EXP_KISS); break;
case RoleExpression.ROLEEXP_FIGHT: szIconFile = res_IconFile((int)IconResourceType.RES_ICON_CMD_EXP_FIGHT); break;
case RoleExpression.ROLEEXP_ATTACK1: szIconFile = res_IconFile((int)IconResourceType.RES_ICON_CMD_EXP_ATTACK1); break;
case RoleExpression.ROLEEXP_ATTACK2: szIconFile = res_IconFile((int)IconResourceType.RES_ICON_CMD_EXP_ATTACK2); break;
case RoleExpression.ROLEEXP_ATTACK3: szIconFile = res_IconFile((int)IconResourceType.RES_ICON_CMD_EXP_ATTACK3); break;
case RoleExpression.ROLEEXP_ATTACK4: szIconFile = res_IconFile((int)IconResourceType.RES_ICON_CMD_EXP_ATTACK4); break;
case RoleExpression.ROLEEXP_DEFENCE: szIconFile = res_IconFile((int)IconResourceType.RES_ICON_CMD_EXP_DEFENCE); break;
case RoleExpression.ROLEEXP_FALL: szIconFile = res_IconFile((int)IconResourceType.RES_ICON_CMD_EXP_FALL); break;
case RoleExpression.ROLEEXP_FALLONGROUND: szIconFile = res_IconFile((int)IconResourceType.RES_ICON_CMD_EXP_FALLONGROUND); break;
case RoleExpression.ROLEEXP_LOOKAROUND: szIconFile = res_IconFile((int)IconResourceType.RES_ICON_CMD_EXP_LOOKAROUND); break;
case RoleExpression.ROLEEXP_DANCE: szIconFile = res_IconFile((int)IconResourceType.RES_ICON_CMD_EXP_DANCE); break;
case RoleExpression.ROLEEXP_FASHIONWEAPON: szIconFile = res_IconFile((int)IconResourceType.RES_ICON_CMD_EXP_FASHIONWEAPON); break;
default:
break;
}
break;
}
default:
return "";
}
return szIconFile;
}
// Get item cool time
int GetCoolTime(ref int piMax/* NULL */)
{
@@ -920,15 +1070,15 @@ namespace BrewMonster
break;
case CommandID.CMD_BINDBUDDY:
{
// CECCounter& cnt = pHost->GetBindCmdCoolCnt();
// iTime = cnt.GetPeriod() - cnt.GetCounter();
//
// if (piMax)
// *piMax = cnt.GetPeriod();
{
// CECCounter& cnt = pHost.GetBindCmdCoolCnt();
// iTime = cnt.GetPeriod() - cnt.GetCounter();
//
// if (piMax)
// *piMax = cnt.GetPeriod();
break;
}
break;
}
default:
// if (piMax) *piMax = 0;
@@ -939,37 +1089,37 @@ namespace BrewMonster
}
}
/* public class CECSCSkill : CECShortcut
{
private CECSkill m_pSkill;
public override ShortcutType GetType() => ShortcutType.SCT_SKILL;
public override CECShortcut Clone() => null;
public bool Init(CECSkill pSkill) { m_pSkill = pSkill; return true; }
public CECSkill GetSkill() => m_pSkill;
public void SetSkill(CECSkill pSkill) { m_pSkill = pSkill; }
}
/* public class CECSCSkill : CECShortcut
{
private CECSkill m_pSkill;
public override ShortcutType GetType() => ShortcutType.SCT_SKILL;
public override CECShortcut Clone() => null;
public bool Init(CECSkill pSkill) { m_pSkill = pSkill; return true; }
public CECSkill GetSkill() => m_pSkill;
public void SetSkill(CECSkill pSkill) { m_pSkill = pSkill; }
}
public class CECSCItem : CECShortcut
{
private int m_iInventory;
private int m_iIvtrSlot;
private int m_iItemTID;
private bool m_bAutoFind;
public class CECSCItem : CECShortcut
{
private int m_iInventory;
private int m_iIvtrSlot;
private int m_iItemTID;
private bool m_bAutoFind;
public override ShortcutType GetType() => ShortcutType.SCT_ITEM;
public override CECShortcut Clone() => null;
public bool Init(int iIvtr, int iSlot, CECIvtrItem pItem)
{
m_iInventory = iIvtr;
m_iIvtrSlot = iSlot;
return true;
}
public int GetInventory() => m_iInventory;
public int GetIvtrSlot() => m_iIvtrSlot;
public int GetItemTID() => m_iItemTID;
public bool GetAutoFindFlag() => m_bAutoFind;
public void MoveItem(int iIvtr, int iSlot) { m_iInventory = iIvtr; m_iIvtrSlot = iSlot; }
}*/
public override ShortcutType GetType() => ShortcutType.SCT_ITEM;
public override CECShortcut Clone() => null;
public bool Init(int iIvtr, int iSlot, CECIvtrItem pItem)
{
m_iInventory = iIvtr;
m_iIvtrSlot = iSlot;
return true;
}
public int GetInventory() => m_iInventory;
public int GetIvtrSlot() => m_iIvtrSlot;
public int GetItemTID() => m_iItemTID;
public bool GetAutoFindFlag() => m_bAutoFind;
public void MoveItem(int iIvtr, int iSlot) { m_iInventory = iIvtr; m_iIvtrSlot = iSlot; }
}*/
/* public class CECSCSkillGrp : CECShortcut
{
File diff suppressed because it is too large Load Diff
@@ -12,8 +12,12 @@
* Copyright (c) 2005 Archosaur Studio, All Rights Reserved.
*/
using BrewMonster.Assets.PerfectWorld.Scripts.Players;
using BrewMonster.Network;
using BrewMonster.Scripts.Skills;
using CSNetwork;
using UnityEngine;
using static CECPlayerWrapper;
namespace BrewMonster
{
@@ -79,12 +83,9 @@ namespace BrewMonster
m_bIgnoreAtkLoop = bIgnoreAtkLoop;
// TODO: Get combo skill configuration from CECConfigs
// CECConfigs pCfg = g_pGame.GetConfigs();
// m_cs = pCfg.GetVideoSettings().comboSkill[iGroup];
// For now, we'll just initialize with empty data
m_cs = new EC_COMBOSKILL(true);
CECConfigs pCfg = EC_Game.GetConfigs();
m_cs = pCfg.GetVideoSettings().comboSkill[iGroup];
// Find the last loop start flag - 查找最后一个循环开始标记 / Find the last loop start flag
m_iLoopStart = -1;
for (int i = 0; i < EC_ConfigConstants.EC_COMBOSKILL_LEN; i++)
{
@@ -130,7 +131,7 @@ namespace BrewMonster
return false;
// TODO: Get PlayerWrapper from CECAutoPolicy
// CECPlayerWrapper pWrapper = CECAutoPolicy.GetInstance().GetPlayerWrapper();
CECPlayerWrapper pWrapper = CECAutoPolicy.GetInstance().GetPlayerWrapper();
int idSkill = GetNextSkill();
if (idSkill > 0)
@@ -143,27 +144,27 @@ namespace BrewMonster
if (!IsStop())
{
// TODO: Post message to continue combo skill
// g_pGame.GetGameRun().PostMessage(MSG_HST_CONTINUECOMBOSKILL, MAN_PLAYER, 0, bMeleeing ? 1 : 0, m_iGroup);
EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_CONTINUECOMBOSKILL, MANAGER_INDEX.MAN_PLAYER, 0, bMeleeing ? 1 : 0, m_iGroup);
}
else
{
// 连击技能停止 / Combo skill finish
// TODO: AP_ActionEvent(AP_EVENT_COMBOFINISH);
AP_ActionEvent((int)AP_EVENT. AP_EVENT_COMBOFINISH);
}
return false;
}
else
{
// TODO: Handle auto policy events
// if (CECAutoPolicy.GetInstance().IsAutoPolicyEnabled())
// pWrapper.AddAttackError();
if (CECAutoPolicy.GetInstance().IsAutoPolicyEnabled())
pWrapper.AddAttackError();
// 检测自己施放是否追加 / Check if self-cast should be tracked
int iSelfCureFlag = 0;
if (m_pHost.GetPrepSkill() != null)
iSelfCureFlag = 1;
// TODO: AP_ActionEvent(AP_EVENT_COMBOCONTINUE, iSelfCureFlag);
AP_ActionEvent((int)AP_EVENT.AP_EVENT_COMBOCONTINUE, iSelfCureFlag);
}
}
else if (idSkill == (short)SpecialSkillID.SID_ATTACK)
@@ -175,10 +176,10 @@ namespace BrewMonster
if (bRet)
{
// TODO: Handle auto policy events
// if (CECAutoPolicy.GetInstance().IsAutoPolicyEnabled())
// pWrapper.AddAttackError();
if (CECAutoPolicy.GetInstance().IsAutoPolicyEnabled())
pWrapper.AddAttackError();
// TODO: AP_ActionEvent(AP_EVENT_COMBOCONTINUE);
AP_ActionEvent((int)AP_EVENT.AP_EVENT_COMBOCONTINUE);
}
else
{
@@ -186,12 +187,12 @@ namespace BrewMonster
if (!IsStop())
{
// TODO: Post message to continue combo skill
// g_pGame.GetGameRun().PostMessage(MSG_HST_CONTINUECOMBOSKILL, MAN_PLAYER, 0, bMeleeing ? 1 : 0, m_iGroup);
EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_CONTINUECOMBOSKILL, MANAGER_INDEX.MAN_PLAYER, 0, bMeleeing ? 1 : 0, m_iGroup);
}
else
{
// 连击技能停止 / Combo skill finish
// TODO: AP_ActionEvent(AP_EVENT_COMBOFINISH);
AP_ActionEvent((int)AP_EVENT.AP_EVENT_COMBOFINISH);
}
return false;
}
@@ -203,12 +204,12 @@ namespace BrewMonster
if (!IsStop())
{
// TODO: Post message to continue combo skill
// g_pGame.GetGameRun().PostMessage(MSG_HST_CONTINUECOMBOSKILL, MAN_PLAYER, 0, bMeleeing ? 1 : 0, m_iGroup);
EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_CONTINUECOMBOSKILL, MANAGER_INDEX.MAN_PLAYER, 0, bMeleeing ? 1 : 0, m_iGroup);
}
else
{
// 连击技能停止 / Combo skill finish
// TODO: AP_ActionEvent(AP_EVENT_COMBOFINISH);
AP_ActionEvent((int)AP_EVENT.AP_EVENT_COMBOFINISH);
}
return false;
}
@@ -326,6 +327,16 @@ namespace BrewMonster
{
return m_iGroup;
}
public void AP_ActionEvent(int iEvent, int iParam = 0)
{
if (!CECAutoPolicy.GetInstance().IsAutoPolicyEnabled())
return;
CECPlayerWrapper pWrapper = CECAutoPolicy.GetInstance().GetPlayerWrapper();
if (pWrapper != null) pWrapper.OnActionEvent(iEvent, iParam);
}
}
}
@@ -0,0 +1,69 @@
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;
private string m_strDesc;
public CECSCSkillGrp()
{
m_iSCType = (int)ShortcutType.SCT_SKILLGRP;
m_iGroupIdx = -1;
m_strDesc = string.Empty;
}
private CECSCSkillGrp(CECSCSkillGrp src)
{
m_iSCType = src.m_iSCType;
m_iGroupIdx = src.m_iGroupIdx;
m_strDesc = src.m_strDesc;
}
/// <summary>
/// Initialize the shortcut and build its description from the string tables.
/// </summary>
public bool Init(int iGroupIdx)
{
m_iGroupIdx = iGroupIdx;
var strTab = EC_Game.GetItemDesc();
string format = strTab?.GetWideString((int)DescriptipionMsg.CMDDESC_SKILLGROUP);
if (string.IsNullOrEmpty(format))
m_strDesc = $"Skill Group {m_iGroupIdx}";
else
m_strDesc = GPDataTypeHelper.ReplacePercentD(format, m_iGroupIdx);
return true;
}
public int GetGroupIndex() => m_iGroupIdx;
public string GetDesc() => m_strDesc;
public override CECShortcut Clone() => new CECSCSkillGrp(this);
public override bool Execute()
{
CECHostPlayer host = EC_Game.GetGameRun().GetHostPlayer();
if (host == null) {
BMLogger.LogError("HostPlayer = null in CECSCSkillGrp::Execute");
return false;
}
host.ApplyComboSkill(m_iGroupIdx, false, -1);
return true;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 512f4bac6274464499a0be2923ed4d63
@@ -0,0 +1,85 @@
// Filename : CECSkillConvert.cs
// Creator : zhangyitian
// Date : 2014/07/09
// Converted to C# from EC_SkillConvert.cpp
using CSNetwork.GPDataType;
using System.Collections.Generic;
namespace BrewMonster.Scripts.Skills
{
// 神魔转换查询表 // God-Evil skill conversion table
public class CECSkillConvert
{
private static CECSkillConvert instance;
private Dictionary<int, int> m_convertTable;
private bool m_bInitialized;
private CECSkillConvert()
{
m_convertTable = new Dictionary<int, int>();
m_bInitialized = false;
}
// 单例 // Singleton
public static CECSkillConvert Instance
{
get
{
if (instance == null)
{
instance = new CECSkillConvert();
}
return instance;
}
}
private void Initialize()
{
var pDB = ElementDataManProvider.GetElementDataMan();
var dt = DATA_TYPE.DT_GOD_EVIL_CONVERT_CONFIG;
var map = pDB.GetAllDataTypeWithType(ID_SPACE.ID_SPACE_CONFIG, dt);
if (map == null)
{
BMLogger.LogError("CECSkillConvert Initialize failed: no data found for DT_GOD_EVIL_CONVERT_CONFIG");
return;
}
foreach (var obj in map)
{
GOD_EVIL_CONVERT_CONFIG config = (GOD_EVIL_CONVERT_CONFIG)obj;
if (config.skill_map != null)
{
for (int i = 0; i + 1 < map.Length; i += 2)
{
if (config.skill_map[i] != 0 &&
config.skill_map[i+128] != 0)
{
m_convertTable[config.skill_map[i]] = config.skill_map[i + 128];
m_convertTable[config.skill_map[i+128]] = config.skill_map[i];
}
}
}
}
m_bInitialized = true;
}
// 获得转换后的技能,返回0表示没有转换的技能 // Get the converted skill, return 0 if no conversion exists
public int GetConvertSkill(int skillID)
{
if (!m_bInitialized)
{
Initialize();
}
if (m_convertTable.TryGetValue(skillID, out int convertedSkillID))
{
return convertedSkillID;
}
return 0;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7d3e4a2b8c9f4e1a9b6c5d8e7f3a2b1c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -44,13 +44,13 @@ namespace BrewMonster.Scripts.Skills
{
BMLogger.LogError("CECHostSkillModel BeforeSceneLoad Reset");
Instance = null;
}
}
#endif
public IReadOnlyDictionary<int, List<int>> GetAllRankProfSkills()
{
return m_allRankProfSkills;
}
public CECHostSkillModel()
public CECHostSkillModel()
{
m_skillLearnNPCNID = 0;
m_bReceivedNPCGreeting = false;
@@ -84,6 +84,24 @@ namespace BrewMonster.Scripts.Skills
//BMLogger.LogError($"[Skill] Sent SEVNPC_HELLO to skill-learn NPC nid={m_skillLearnNPCNID}");
}
}
public enumEvilGod GetSkillEvilGod(int skillID)
{
CECSkill skill = new CECSkill(skillID, 1);
int rank = skill.GetRank();
CECTaoistRank taoistRank = CECTaoistRank.GetTaoistRank(rank);
if (taoistRank.IsGodRank())
{
return enumEvilGod.SKILL_GOD;
}
else if (taoistRank.IsEvilRank())
{
return enumEvilGod.SKILL_EVIL;
}
else
{
return enumEvilGod.SKILL_BASE;
}
}
public enumSkillLearnedState GetSkillLearnedState(int skillID)
{
CECSkill pSkill = CECGameRun.Instance.GetHostPlayer().GetNormalSkill(skillID);
@@ -286,6 +304,16 @@ namespace BrewMonster.Scripts.Skills
{
return CECGameRun.Instance.GetHostPlayer().CheckSkillLearnCondition(skillID, true);
}
public string GetSkillDescription(int skillID, int level)
{
string tmp = "";
if (CECSkill.GetDesc(skillID, level, tmp, 1024))
{
return (tmp);
}
return tmp;
}
public int GetRequiredBook(int skillID, int level)
{
int itemId = ElementSkill.GetRequiredBook((uint)skillID, level);
@@ -372,9 +400,9 @@ namespace BrewMonster.Scripts.Skills
var juniors = GetJunior(rootSkillID);
int maxHeight = 0;
for (int i = 0; i < juniors.Count; i++)
foreach(var skill in juniors)
{
int subHeight = GetSkillTreeHeight((int)juniors[i].id);
int subHeight = GetSkillTreeHeight((int)skill.Key);
if (subHeight > maxHeight)
{
maxHeight = subHeight;
@@ -383,7 +411,7 @@ namespace BrewMonster.Scripts.Skills
return 1 + maxHeight;
}
private List<(uint id, int level)> GetJunior(int skillID)
public Dictionary<uint, int> GetJunior(int skillID)
{
if (!m_allProfSkills.TryGetValue(skillID, out var skill))
{
@@ -391,14 +419,13 @@ namespace BrewMonster.Scripts.Skills
}
var juniors = skill.GetJunior();
var ret = new List<(uint id, int level)>();
var ret = new Dictionary<uint, int>();
foreach (var (id, level) in juniors)
{
if (id != 0)
ret.Add((id, level));
ret[id] = level;
}
return ret;
}
private HashSet<int> GetRootSkillSet()
@@ -436,7 +463,7 @@ namespace BrewMonster.Scripts.Skills
{
ElementSkill pSkill = ElementSkill.Create(skillService.id_skills[i], 1);
if (pSkill == null)
if (pSkill == null)
{
//BMLogger.LogError($"Hoang Dev pSkill is null for skill {i} :" + skillService.id_skills[i]);
continue;
@@ -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
@@ -191,7 +191,7 @@ namespace BrewMonster.Scripts.Skills
return "";
}
// ˵
public virtual string GetIntroduction(StringBuilder buf, int len, SkillStr table) { return ""; }
public virtual void GetIntroduction(StringBuilder buf, SkillStr table) { }
// ְҵ
public virtual int GetCls() { return -1; }
// ȴʱλ
@@ -240,13 +240,30 @@ namespace BrewMonster.Scripts.Skills
int ret = skill.GetRequiredBook();
return ret;
}
// ѧϰ󾳽ȼ?
public static int GetRequiredLevel(uint id, int level)
{
Skill s = Skill.Create(id, level);
if (s == null)
return 0;
int ret = s.GetRequiredLevel();
return ret;
}
public virtual int GetRequiredRealmLevel() { return 0; }
public static int GetRequiredRealmLevel(uint id, int level)
{
Skill s = Skill.Create(id, level);
if (s == null)
return 0;
int ret = s.GetRequiredRealmLevel();
return ret;
}
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)
@@ -326,6 +343,41 @@ namespace BrewMonster.Scripts.Skills
return s.IsMovingSkill();
return false;
}
public static Dictionary<uint, int> GetComboSkActivated(ComboSkillState comboState)
{
var result = new Dictionary<uint, int>();
if (comboState.skillid == 0)
return result;
if (SkillStub.GetComboSkMap().TryGetValue(comboState.skillid, out List<uint> postSkills) && postSkills != null)
{
foreach (uint skillId in postSkills)
{
Skill skill = Skill.Create(skillId, 1);
if (skill == null)
continue;
ref ComboArg comboArg = ref skill.GetPlayer().GetComboarg();
for (int i = 0; i < ComboArg.MAX_COMBO_ARG; i++)
{
int argValue = 0;
if (comboState.arg != null && i < comboState.arg.Length)
{
argValue = comboState.arg[i];
}
comboArg.SetValue((uint)i, argValue);
}
if (skill.CheckComboSkExtraCondition())
{
result[skill.GetId()] = skill.GetComboSkInterval();
}
}
}
return result;
}
// ܷڵǰ״̬ʹ // Whether skill can be used in current form state
public bool IsValidForm(byte form)
{
@@ -363,13 +415,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;
@@ -389,17 +441,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)
{
@@ -419,65 +471,75 @@ 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);
if (s != null)
{
return s.combosk_preskill;
}
return 0;
}
}
}
@@ -0,0 +1,260 @@
# Comparison: OnMsgPlayerCastSkill - C++ vs C# Conversion
## Summary
The conversion from C++ to C# is **mostly complete** but has some **missing elements** and **commented-out code** that should be reviewed.
## All Switch Cases Present ✅
All 9 switch cases are present in both versions:
1. ✅ `OBJECT_CAST_SKILL`
2. ✅ `SKILL_PERFORM`
3. ✅ `HOST_STOP_SKILL`
4. ✅ `SELF_SKILL_INTERRUPTED`
5. ✅ `OBJECT_CAST_INSTANT_SKILL`
6. ✅ `OBJECT_CAST_POS_SKILL`
7. ✅ `PLAYER_CAST_RUNE_SKILL`
8. ✅ `PLAYER_CAST_RUNE_INSTANT_SKILL`
9. ✅ `ERROR_MESSAGE`
10. ✅ `default` case
## Missing/Incomplete Elements
### 1. Missing Assertion Check in OBJECT_CAST_SKILL ❌
**C++ (line 5876):**
```cpp
ASSERT(pCmd->caster == m_PlayerInfo.cid);
```
**C# (line 916):**
```csharp
// MISSING: No assertion check for pCmd.caster == m_PlayerInfo.cid
```
**Impact:** Medium - This is a safety check that validates the caster ID matches the player's ID.
### 2. Commented Out AP_ActionEvent in HOST_STOP_SKILL ⚠️
**C++ (line 5958):**
```cpp
AP_ActionEvent(AP_EVENT_STOPSKILL);
```
**C# (line 1025):**
```csharp
//AP_ActionEvent(AP_EVENT_STOPSKILL);
```
**Impact:** Medium - Action event notification is disabled. May affect auto-policy or other systems that listen to this event.
### 3. Commented Out AP_ActionEvent in SELF_SKILL_INTERRUPTED ⚠️
**C++ (line 6005):**
```cpp
AP_ActionEvent(AP_EVENT_STOPSKILL);
```
**C# (line 1084):**
```csharp
//AP_ActionEvent(AP_EVENT_STOPSKILL);
```
**Impact:** Medium - Same as above.
### 4. Missing Fixed Message in SELF_SKILL_INTERRUPTED ⚠️
**C++ (line 6003):**
```cpp
g_pGame->GetGameRun()->AddFixedMessage(FIXMSG_SKILLINTERRUPT);
```
**C# (line 1081):**
```csharp
// g_pGame.GetGameRun().AddFixedMessage(FIXMSG_SKILLINTERRUPT);
Debug.Log("Skill interrupted!");
```
**Impact:** Low - Replaced with Debug.Log, but the fixed message system might be needed for UI notifications.
### 5. Missing Auto-Policy Event in SELF_SKILL_INTERRUPTED ⚠️
**C++ (line 6008):**
```cpp
CECAutoPolicy::GetInstance().SendEvent_SkillInterrupt(skill_id);
```
**C# (line 1087):**
```csharp
// CECAutoPolicy::GetInstance().SendEvent_SkillInterrupt(skill_id);
```
**Impact:** Medium - Auto-policy system won't be notified of skill interruptions, which may affect automated gameplay features.
### 6. Commented Out Camera Update in OBJECT_CAST_POS_SKILL ⚠️
**C++ (line 6080):**
```cpp
UpdateFollowCamera(false, 10);
```
**C# (line 1165):**
```csharp
//UpdateFollowCamera(false, 10);
```
**Impact:** Low - Camera update is disabled. May affect camera behavior for certain skills.
## Differences That Are Acceptable
### 1. Logging System Differences ✅
**C++:**
```cpp
g_pGame->RuntimeDebugInfo(0xffffffff, _AL("Cast skill(%d): %s"), ...);
```
**C#:**
```csharp
Debug.Log($"Cast skill({m_pCurSkill.GetSkillID()})");
```
**Status:** Acceptable - Different logging systems, functionality preserved.
### 2. Additional Logging in C# ✅
C# has extra logging for return-to-town skill (ID 167):
```csharp
if (m_pCurSkill.GetSkillID() == ID_RETURNTOWN_SKILL)
{
Debug.Log($"Return-to-town skill (167) cast - State2 should trigger SetReturntown(1) on server");
}
```
**Status:** Acceptable - Additional debugging/logging is fine.
### 3. Memory Management Differences ✅
**C++:**
```cpp
if(m_pTargetItemSkill)
{
delete m_pTargetItemSkill;
m_pTargetItemSkill = NULL;
}
```
**C#:**
```csharp
if (m_pTargetItemSkill != null)
{
m_pTargetItemSkill = null; // GC will handle cleanup
}
```
**Status:** Acceptable - C# uses garbage collection, no explicit delete needed.
### 4. Type Casting Differences ✅
**C++:**
```cpp
cmd_object_cast_skill* pCmd = (cmd_object_cast_skill*)Msg.dwParam1;
```
**C#:**
```csharp
cmd_object_cast_skill pCmd = GPDataTypeHelper.FromBytes<cmd_object_cast_skill>((byte[])Msg.dwParam1);
```
**Status:** Acceptable - Different serialization approach, functionality preserved.
### 5. Switch Statement Syntax ✅
**C++:**
```cpp
switch (Msg.dwParam2)
{
case OBJECT_CAST_SKILL:
```
**C#:**
```csharp
switch (Convert.ToInt32(Msg.dwParam2))
{
case int value2 when value2 == CommandID.OBJECT_CAST_SKILL:
```
**Status:** Acceptable - C# pattern matching syntax, functionality preserved.
## Post-Switch Code Comparison
### Action Start Event ✅
**C++ (line 6241-6242):**
```cpp
if( bActionStartSkill )
AP_ActionEvent(AP_EVENT_STARTSKILL, iActionTime);
```
**C# (line 1338-1339):**
```csharp
if (bActionStartSkill)
AP.AP_ActionEvent((int)AP_EVENT.AP_EVENT_STARTSKILL, iActionTime);
```
**Status:** ✅ Complete - Properly converted and active.
### Do Other Thing Logic ✅
**C++ (line 6244-6258):**
```cpp
if (bDoOtherThing)
{
if (m_pComboSkill && !m_pComboSkill->IsStop())
{
if( CECAutoPolicy::GetInstance().IsAutoPolicyEnabled() )
g_pGame->GetGameRun()->PostMessage(MSG_HST_CONTINUECOMBOSKILL, MAN_PLAYER, 0, 0, m_pComboSkill->GetGroupIndex());
else
m_pComboSkill->Continue(false);
}
else
{
if( idTarget && idTarget != m_PlayerInfo.cid )
NormalAttackObject(idTarget, true);
}
}
```
**C# (line 1341-1357):**
```csharp
if (bDoOtherThing)
{
if (m_pComboSkill != null && !m_pComboSkill.IsStop())
{
if (CECAutoPolicy.GetInstance().IsAutoPolicyEnabled())
EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_CONTINUECOMBOSKILL, MANAGER_INDEX.MAN_PLAYER, 0, 0, m_pComboSkill.GetGroupIndex());
else
m_pComboSkill.Continue(false);
}
else
{
if (idTarget != 0 && idTarget != m_PlayerInfo.cid)
NormalAttackObject(idTarget, true);
}
}
```
**Status:** ✅ Complete - Properly converted with equivalent logic.
## Recommendations
### High Priority
1. **Add missing assertion check** in `OBJECT_CAST_SKILL` case:
```csharp
Debug.Assert(pCmd.caster == m_PlayerInfo.cid, "Caster ID mismatch");
```
### Medium Priority
2. **Uncomment and verify** `AP_ActionEvent(AP_EVENT_STOPSKILL)` calls if the AP system is implemented in C#.
3. **Uncomment** `CECAutoPolicy::GetInstance().SendEvent_SkillInterrupt(skill_id)` if auto-policy is needed.
### Low Priority
4. **Review** if `UpdateFollowCamera` is needed for `OBJECT_CAST_POS_SKILL` case.
5. **Consider** implementing `AddFixedMessage` system if UI notifications are needed for skill interruptions.
## Conclusion
**Conversion Completeness: ~90%**
The conversion is functionally complete with all major logic paths implemented. However, several safety checks and event notifications are commented out or missing, which may affect:
- Auto-policy system integration
- UI notifications for skill interruptions
- Camera behavior for position-based skills
- Safety validation (caster ID check)
These should be reviewed and either implemented or confirmed as intentionally disabled.
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 09b07dfdbdd8c094d9ded2ba81316847
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,625 @@
# Flow: Create Skill Combo by Drag-Drop - C++ Implementation
## Overview
This document describes the complete flow for creating a skill combo by drag-dropping individual skills into the combo skill list in the skill edit dialog. This is the process of building a combo skill sequence that can later be placed in the quick bar.
---
## Entry Points
### 1. **Open Skill Edit Dialog**
**Location:** `DlgSkillSubOther.cpp:49-57`
The user can open the skill edit dialog in two ways:
#### **A. Create New Combo Skill** (Button: "Btn_ConNew")
```cpp
// DlgSkillSubOther.cpp:54-57
void CDlgSkillSubOther::OnCommandNew(const char * szCommand) {
GetGameUIMan()->m_pDlgSkillEdit->SetData(0); // 0 = new combo
GetGameUIMan()->m_pDlgSkillEdit->Show(true);
}
```
#### **B. Edit Existing Combo Skill** (Button: "Btn_ConEdi")
```cpp
// DlgSkillSubOther.cpp:49-52
void CDlgSkillSubOther::OnCommandEdit(const char * szCommand) {
GetGameUIMan()->m_pDlgSkillEdit->SetData(m_nComboSelect); // 1-based combo index
GetGameUIMan()->m_pDlgSkillEdit->Show(true);
}
```
**Flow:**
1. User clicks "New" or "Edit" button
2. Set combo skill index (0 for new, 1-based for existing)
3. Show skill edit dialog (`Win_SkillEdit`)
---
## Detailed Flow: Drag-Drop Skill to Combo List
### **Step 1: Skill Edit Dialog Initialization**
**Location:** `DlgSkillEdit.cpp:143-231` - `OnShowDialog()`
When the skill edit dialog is shown, it initializes the combo skill list:
```cpp
// DlgSkillEdit.cpp:143-207
void CDlgSkillEdit::OnShowDialog() {
EC_VIDEO_SETTING setting = GetGame()->GetConfigs()->GetVideoSettings();
// If creating new combo (GetData() == 0), find first empty slot
if( GetData() == 0 ) {
for (i = 0; i < EC_COMBOSKILL_NUM; i++)
if( setting.comboSkill[i].nIcon == 0 ) {
SetData(i + 1); // Set to 1-based index
break;
}
}
// Load existing combo skill data if editing
if( setting.comboSkill[GetData() - 1].nIcon != 0 ) {
// Load existing skills from config
for (i = 0; i < EC_COMBOSKILL_LEN; i++) {
int idSkill = setting.comboSkill[GetData() - 1].idSkill[i];
if( idSkill > 0 ) {
CECSkill *pSkill = GetHostPlayer()->GetNormalSkill(idSkill);
if (!pSkill) pSkill = GetHostPlayer()->GetEquipSkillByID(idSkill);
GetGameUIMan()->m_pDlgSkillSubOther->SetImage(pImage, pSkill);
}
else {
// Special icons (Attack, Loop Start)
int iType = -idSkill;
SetSpecialIcon(pImage, iType);
}
}
}
}
```
**Key Points:**
- Dialog displays empty slots (`Item_1`, `Item_2`, ..., `Item_N`) for combo skill list
- Each slot can hold a skill or special icon (Attack, Loop Start)
- Existing combo skills are loaded from `EC_VIDEO_SETTING.comboSkill[]`
---
### **Step 2: User Drags Skill from Skill List**
**Location:** `DlgSkillSubOther.cpp:235-253` - `OnEventLButtonDownFixed()` / `OnEventLButtonDownItem()`
Skills can be dragged from multiple sources:
#### **A. Fixed Skills** (`Img_InSkill*`)
```cpp
// DlgSkillSubOther.cpp:235-243
void CDlgSkillSubOther::OnEventLButtonDownFixed(WPARAM wParam, LPARAM lParam, AUIObject * pObj) {
if (pObj->GetDataPtr("ptr_CECSkill") == 0) {
return; // No skill data
}
A3DVIEWPORTPARAM *p = m_pA3DEngine->GetActiveViewport()->GetParam();
GetGameUIMan()->m_ptLButtonDown.x = GET_X_LPARAM(lParam) - p->X;
GetGameUIMan()->m_ptLButtonDown.y = GET_Y_LPARAM(lParam) - p->Y;
GetGameUIMan()->InvokeDragDrop(this, pObj, GetGameUIMan()->m_ptLButtonDown);
}
```
#### **B. Item Skills** (`Img_ItemSkill*`)
```cpp
// DlgSkillSubOther.cpp:245-253
void CDlgSkillSubOther::OnEventLButtonDownItem(WPARAM wParam, LPARAM lParam, AUIObject * pObj) {
if (pObj->GetDataPtr("ptr_CECSkill") == 0) {
return; // No skill data
}
A3DVIEWPORTPARAM *p = m_pA3DEngine->GetActiveViewport()->GetParam();
GetGameUIMan()->m_ptLButtonDown.x = GET_X_LPARAM(lParam) - p->X;
GetGameUIMan()->m_ptLButtonDown.y = GET_Y_LPARAM(lParam) - p->Y;
GetGameUIMan()->InvokeDragDrop(this, pObj, GetGameUIMan()->m_ptLButtonDown);
}
```
**Key Points:**
- Skill data is stored via `SetDataPtr(pSkill, "ptr_CECSkill")` when skill icon is created
- Mouse position is captured for drag operation
- `InvokeDragDrop()` initiates the drag-drop system
---
### **Step 3: Drag-Drop System Routes to Handler**
**Location:** `DlgDragDrop.cpp:79-124` - `OnEventLButtonUp()`
When user releases mouse button, the drag-drop system processes the drop:
```cpp
// DlgDragDrop.cpp:79-124
void CDlgDragDrop::OnEventLButtonUp(WPARAM wParam, LPARAM lParam, AUIObject *pObj) {
PAUIOBJECT pObjSrc = (PAUIOBJECT)pItem->GetDataPtr("ptr_AUIObject");
if( !pObjSrc ) return;
// Get destination dialog and object via hit test
PAUIDIALOG pDlgOver;
PAUIOBJECT pObjOver;
GetGameUIMan()->HitTest(x, y, &pDlgOver, &pObjOver, this);
PAUIDIALOG pDlgSrc = pObjSrc->GetParent();
// Route to appropriate handler
OnGeneralScene(pDlgSrc, pObjSrc, pDlgOver, pObjOver, pIvtrSrc);
}
```
**Flow:**
1. Get source object from drag item
2. Hit test to find destination dialog and object
3. Route to `OnGeneralScene()` for processing
---
### **Step 4: Skill Drag-Drop Handler**
**Location:** `DlgDragDrop.cpp:589-627` - `OnSkillDragDrop()`
The handler checks if drop is on skill edit dialog:
```cpp
// DlgDragDrop.cpp:589-627
void CDlgDragDrop::OnSkillDragDrop(PAUIDIALOG pDlgSrc, PAUIOBJECT pObjSrc,
PAUIDIALOG pDlgOver, PAUIOBJECT pObjOver,
CECIvtrItem* pIvtrSrc) {
// Check if dropped on skill edit dialog
if( pDlgOver && stricmp(pDlgOver->GetName(), "Win_SkillEdit") == 0
&& pObjOver && strstr(pObjOver->GetName(), "Item_")
&& !strstr(pObjSrc->GetName(), "Img_ConSkill")) // Don't allow combo icons
{
GetGameUIMan()->m_pDlgSkillEdit->DragDropItem(pDlgSrc, pObjSrc, pObjOver);
}
// ... other handlers (quick bar, auto policy, etc.)
}
```
**Key Points:**
- Only processes drops on `Win_SkillEdit` dialog
- Destination must be an `Item_*` slot (combo skill list slot)
- Prevents dragging combo skill icons into combo list (prevents recursion)
---
### **Step 5: Add Skill to Combo List**
**Location:** `DlgSkillEdit.cpp:249-293` - `DragDropItem()`
This is the core function that adds the skill to the combo list:
```cpp
// DlgSkillEdit.cpp:249-293
void CDlgSkillEdit::DragDropItem(PAUIDIALOG pDlgSrc, PAUIOBJECT pObjSrc, PAUIOBJECT pObjOver) {
if( pObjSrc == pObjOver )
return; // Same object, do nothing
if( !pObjOver ) {
// Dropped outside - remove skill from source
if( !(pDlgSrc == this && strstr(pObjSrc->GetName(), "Special")) )
GetGameUIMan()->m_pDlgSkillSubOther->SetImage((PAUIIMAGEPICTURE)pObjSrc, NULL);
}
else {
// Extract target slot number (1-based)
int iSlot = atoi(pObjOver->GetName() + strlen("Item_"));
// Check if source is special icon (Attack, Loop Start)
if( strstr(pObjSrc->GetName(), "Special_") ) {
int iType = atoi(pObjSrc->GetName() + strlen("Special_"));
SetSpecialIcon((PAUIIMAGEPICTURE)pObjOver, iType);
}
else {
// Regular skill drag-drop
CECSkill *pSkill = (CECSkill *)pObjSrc->GetDataPtr("ptr_CECSkill");
int iType = pObjSrc->GetData();
if( pDlgSrc != this ) {
// Dragging from external dialog (skill list)
if( iType != 0)
SetSpecialIcon((PAUIIMAGEPICTURE)pObjOver, iType);
else
GetGameUIMan()->m_pDlgSkillSubOther->SetImage((PAUIIMAGEPICTURE)pObjOver, pSkill);
}
else {
// Dragging within skill edit dialog (reordering)
CECSkill *pSkill1 = (CECSkill *)pObjOver->GetDataPtr("ptr_CECSkill");
int iType1 = pObjOver->GetData();
// Swap skills
if( iType != 0)
SetSpecialIcon((PAUIIMAGEPICTURE)pObjOver, iType);
else
GetGameUIMan()->m_pDlgSkillSubOther->SetImage((PAUIIMAGEPICTURE)pObjOver, pSkill);
if( iType1 != 0)
SetSpecialIcon((PAUIIMAGEPICTURE)pObjSrc, iType1);
else
GetGameUIMan()->m_pDlgSkillSubOther->SetImage((PAUIIMAGEPICTURE)pObjSrc, pSkill1);
}
}
}
}
```
**Key Points:**
- **External Drag (from skill list):** Adds skill to target slot, clears source if needed
- **Internal Drag (within dialog):** Swaps skills between slots (reordering)
- **Special Icons:** Can add Attack (`Special_1`) or Loop Start (`Special_2`) icons
- Uses `SetImage()` to display skill icon in target slot
- Skill data is stored via `SetDataPtr(pSkill, "ptr_CECSkill")`
---
### **Step 6: Save Combo Skill Configuration (Local)**
**Location:** `DlgSkillEdit.cpp:56-85` - `OnCommandConfirm()`
When user clicks "Confirm" button, the combo skill is saved locally:
```cpp
// DlgSkillEdit.cpp:56-85
void CDlgSkillEdit::OnCommandConfirm(const char *szCommand) {
EC_VIDEO_SETTING setting = GetGame()->GetConfigs()->GetVideoSettings();
// Save icon index
setting.comboSkill[GetData() - 1].nIcon = m_nIcon;
// Save skill sequence
int i;
int j = 0;
for (i = 0; ; i++) {
AString strName;
strName.Format("Item_%d", i + 1);
PAUIIMAGEPICTURE pImage = static_cast<PAUIIMAGEPICTURE>(GetDlgItem(strName));
if (!pImage) break;
CECSkill *pSkill = (CECSkill *)pImage->GetDataPtr("ptr_CECSkill");
int iType = pImage->GetData();
if( iType == 0 && pSkill ) {
// Regular skill
setting.comboSkill[GetData() - 1].idSkill[j] = pSkill->GetSkillID();
j++;
}
else {
// Special icon (Attack = -1, Loop Start = -2)
setting.comboSkill[GetData() - 1].idSkill[j] = -iType;
j++;
}
}
// Save to local config (does NOT send to server immediately)
GetGame()->GetConfigs()->SetVideoSettings(setting);
Show(false);
// Update combo skill display
GetGameUIMan()->m_pDlgSkillSubOther->UpdateComboSkill();
}
```
**Key Points:**
- Saves icon index (`nIcon`) for combo skill
- Saves skill sequence to `idSkill[]` array:
- **Positive values:** Skill IDs
- **Negative values:** Special icons (-1 = Attack, -2 = Loop Start)
- Updates combo skill icons in main skill dialog
- **Note:** This only saves to local memory (`m_vs`), does NOT send to server yet
---
### **Step 7: Send Combo Skill to Server**
**Location:** `EC_GameRun.cpp:1942-2061` - `SaveConfigsToServer()`
Combo skill data is sent to server as part of the user configuration when:
- Player logs out
- Periodically during gameplay
- When explicitly triggered (e.g., via UI command)
```cpp
// EC_GameRun.cpp:1942-2061
DWORD CECGameRun::SaveConfigsToServer() {
// ... validation checks ...
// Calculate total size needed
int iTotalSize = 0;
iTotalSize += sizeof(DWORD); // Version
// Host player config (shortcuts, etc.)
int iHostSize = 0;
pHost->SaveConfigData(NULL, &iHostSize);
iTotalSize += sizeof(int) + iHostSize;
// UI layout config
DWORD dwUISize = 0;
pGameUI->GetUserLayout(NULL, dwUISize);
iTotalSize += sizeof(int) + (int)dwUISize;
// User settings config (INCLUDES COMBO SKILLS!)
int iSettingSize = 0;
g_pGame->GetConfigs()->SaveUserConfigData(NULL, &iSettingSize);
iTotalSize += sizeof(int) + iSettingSize;
// Allocate buffer and pack data
void* pDataBuf = a_malloctemp(iTotalSize);
// ... pack all data ...
// Compress and send to server
g_pGame->GetGameSession()->SaveConfigData(pCompBuf, dwCompLen+iVerLen);
}
```
**Key Function:** `EC_Configs::SaveUserConfigData()`
**Location:** `EC_Configs.cpp:569-615`
```cpp
// EC_Configs.cpp:569-615
bool CECConfigs::SaveUserConfigData(void* pDataBuf, int* piSize) {
int iTotalSize = 0;
BYTE* pData = (BYTE*)pDataBuf;
// Version
iTotalSize += sizeof(DWORD);
if (pDataBuf) {
*((DWORD*)pData) = EC_CONFIG_VERSION;
pData += sizeof(DWORD);
}
// ⭐ SAVE VIDEO SETTINGS (INCLUDES COMBO SKILLS!)
iTotalSize += sizeof(EC_VIDEO_SETTING);
if (pDataBuf) {
*((EC_VIDEO_SETTING*)pData) = m_vs; // Contains comboSkill[] array!
pData += sizeof(EC_VIDEO_SETTING);
}
// ... other settings (game, blacklist, computer aided) ...
}
```
**Key Points:**
- **Entire `EC_VIDEO_SETTING` structure is sent**, which includes:
- `comboSkill[EC_COMBOSKILL_NUM]` array
- Each combo skill contains `nIcon` and `idSkill[]` array
- **Data is compressed** before sending to reduce network traffic
- **Sent asynchronously** - not immediately when combo is saved
- **Server stores** this data and sends it back when player logs in
---
### **Step 8: Server Receives and Stores Combo Skill Data**
**Location:** Server-side (not in client codebase)
When server receives the config data:
1. Server decompresses the data
2. Server parses `EC_VIDEO_SETTING` structure
3. Server extracts `comboSkill[]` array
4. Server stores combo skill data in player's account/profile
5. Server sends combo skill data back to client on next login
**Note:** The server does NOT validate or execute combo skills - they are purely client-side UI configurations. The server only stores them for persistence across sessions.
---
## Complete Flow Diagram
```
┌─────────────────────────────────────────────────────────────┐
│ STEP 1: Open Skill Edit Dialog │
│ Location: DlgSkillSubOther.cpp:49-57 │
│ - User clicks "New" or "Edit" button │
│ - SetData(0) for new, SetData(n) for existing │
│ - Show Win_SkillEdit dialog │
└──────────────────────┬──────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ STEP 2: Dialog Initialization │
│ Location: DlgSkillEdit.cpp:143-231 │
│ - Load existing combo skill if editing │
│ - Display empty slots (Item_1, Item_2, ...) │
│ - Show special icons (Attack, Loop Start) │
└──────────────────────┬──────────────────────────────────────┘
│ (User drags skill from skill list)
┌─────────────────────────────────────────────────────────────┐
│ STEP 3: Drag Initiation │
│ Location: DlgSkillSubOther.cpp:235-253 │
│ - OnEventLButtonDownFixed() or OnEventLButtonDownItem() │
│ - Capture mouse position │
│ - InvokeDragDrop(this, pObj, pt) │
└──────────────────────┬──────────────────────────────────────┘
│ (User drags to combo list slot)
┌─────────────────────────────────────────────────────────────┐
│ STEP 4: Drop Processing │
│ Location: DlgDragDrop.cpp:79-124 │
│ - OnEventLButtonUp() detects drop │
│ - Hit test to find destination │
│ - Route to OnSkillDragDrop() │
└──────────────────────┬──────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ STEP 5: Skill Drag Handler │
│ Location: DlgDragDrop.cpp:589-627 │
│ - Check: pDlgOver == "Win_SkillEdit" │
│ - Check: pObjOver name contains "Item_" │
│ - Call DragDropItem() │
└──────────────────────┬──────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ STEP 6: Add Skill to Combo List │
│ Location: DlgSkillEdit.cpp:249-293 │
│ - Extract target slot: atoi("Item_N") │
│ - Get skill: pObjSrc->GetDataPtr("ptr_CECSkill") │
│ - SetImage(pObjOver, pSkill) │
│ - Store skill data in slot │
└──────────────────────┬──────────────────────────────────────┘
│ (User can drag more skills)
│ (User can reorder by dragging within dialog)
│ (User clicks "Confirm" button)
┌─────────────────────────────────────────────────────────────┐
│ STEP 6: Save Configuration (Local) │
│ Location: DlgSkillEdit.cpp:56-85 │
│ - Loop through all Item_* slots │
│ - Extract skill IDs or special icon types │
│ - Save to setting.comboSkill[n].idSkill[] │
│ - Save icon: setting.comboSkill[n].nIcon │
│ - SetVideoSettings(setting) → saves to m_vs (local only) │
│ - UpdateComboSkill() to refresh display │
└──────────────────────┬──────────────────────────────────────┘
│ (Later: on logout, periodically, or explicit save)
┌─────────────────────────────────────────────────────────────┐
│ STEP 7: Send to Server │
│ Location: EC_GameRun.cpp:1942-2061 │
│ - SaveConfigsToServer() called │
│ - SaveUserConfigData() includes entire EC_VIDEO_SETTING │
│ - Combo skills included in m_vs.comboSkill[] │
│ - Data compressed and sent via SaveConfigData() │
└──────────────────────┬──────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ STEP 8: Server Storage │
│ Location: Server-side (not in client code) │
│ - Server receives compressed config data │
│ - Server decompresses and parses EC_VIDEO_SETTING │
│ - Server stores comboSkill[] in player account │
│ - Server sends back on next login │
└─────────────────────────────────────────────────────────────┘
```
---
## Data Structures
### **EC_VIDEO_SETTING.comboSkill[]**
```cpp
struct COMBO_SKILL {
short nIcon; // Icon index (1-based, 0 = empty)
short idSkill[EC_COMBOSKILL_LEN]; // Skill sequence array
// idSkill values:
// > 0: Skill ID
// -1: Attack (SID_ATTACK)
// -2: Loop Start (SID_LOOPSTART)
// 0: Empty slot
};
```
### **Skill Storage in UI Objects**
- **Skill Pointer:** `pImage->SetDataPtr(pSkill, "ptr_CECSkill")`
- **Special Icon Type:** `pImage->SetData(iType)` where `iType = 1` (Attack) or `2` (Loop Start)
- **Regular Skill:** `pImage->SetData(0)`
---
## Key Constants
- **EC_COMBOSKILL_NUM:** Maximum number of combo skill groups (typically 8-12)
- **EC_COMBOSKILL_LEN:** Maximum number of skills per combo (typically 8-16)
- **SID_ATTACK:** Special skill ID for normal attack (typically -1)
- **SID_LOOPSTART:** Special skill ID for loop start marker (typically -2)
---
## Important Notes
1. **Index Conversion:**
- Combo group index is **1-based** in UI (`GetData()` returns 1, 2, 3, ...)
- Array index is **0-based** in config (`comboSkill[GetData() - 1]`)
2. **Skill Sources:**
- Skills can be dragged from:
- `Win_SkillSubOther` (Fixed skills, Item skills)
- `Win_SkillSubPool` (Skill pool)
- `Win_SkillSubListSkillItem` (Skill list items)
3. **Special Icons:**
- Attack icon (`Special_1`) = Normal attack in combo
- Loop Start icon (`Special_2`) = Marks where combo should loop back
4. **Reordering:**
- Users can drag skills within the skill edit dialog to reorder
- `DragDropItem()` handles swapping when `pDlgSrc == this`
5. **Validation:**
- Combo skill icons cannot be dragged into combo list (prevents recursion)
- Empty slots are allowed (stored as 0 in `idSkill[]`)
---
## Related Files
- **DlgSkillEdit.cpp/h:** Skill edit dialog implementation
- **DlgSkillSubOther.cpp/h:** Skill sub-other panel (source of skills)
- **DlgDragDrop.cpp/h:** Drag-drop system handler
- **EC_Configs.cpp/h:** Configuration management (stores combo skills locally)
- **EC_GameRun.cpp/h:** Game run logic (saves configs to server)
- **EC_GameSession.cpp/h:** Network session (sends config data to server)
- **EC_ComboSkill.cpp/h:** Combo skill execution logic
---
## Network Protocol Details
### **Protocol Command**
- **Command:** `SaveConfigData` (via `EC_GameSession::SaveConfigData()`)
- **Data Format:** Compressed binary blob containing:
1. Version (DWORD)
2. Host player config (shortcuts, etc.)
3. UI layout config
4. **User settings config (includes combo skills)**
### **Data Structure Sent**
```cpp
struct USER_CONFIG_DATA {
DWORD version; // USERCFG_VERSION
int hostConfigSize;
BYTE hostConfig[hostConfigSize]; // Host player shortcuts, etc.
int uiLayoutSize;
BYTE uiLayout[uiLayoutSize]; // UI window positions, etc.
int settingsSize;
BYTE settings[settingsSize]; // EC_VIDEO_SETTING + other settings
};
// Inside settings:
struct EC_VIDEO_SETTING {
// ... other video settings ...
EC_COMBOSKILL comboSkill[EC_COMBOSKILL_NUM]; // ⭐ COMBO SKILLS HERE!
};
struct EC_COMBOSKILL {
short nIcon; // Icon index (0 = empty)
short idSkill[EC_COMBOSKILL_LEN]; // Skill sequence
};
```
### **When Data is Sent**
1. **On Logout:** `DlgExit.cpp` triggers `SaveConfigsToServer()`
2. **Periodically:** Game may auto-save configs during gameplay
3. **Explicit Save:** UI commands can trigger save (e.g., `EC_GameUICommand.cpp:143`)
4. **Pending Actions:** Various pending actions append save requests
### **Important Notes**
- **Combo skills are NOT sent immediately** when saved - they're queued for next config save
- **Server does NOT validate combo skills** - they're client-side UI configurations
- **Combo skills are NOT executed on server** - execution is purely client-side
- **Server only stores for persistence** - so player's combo skills persist across sessions
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 4abca7ea223861b47b89d130f6902111
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -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:
@@ -3,7 +3,6 @@ using UnityEngine;
namespace BrewMonster
{
public class PlayerInfo { }
public struct ComboArg
@@ -141,18 +141,14 @@ namespace BrewMonster
public override int GetRequiredMoney(Skill skill) => RequiredMoneyArray[skill.GetLevel() - 1];
#if SKILL_CLIENT
public int GetIntroduction(Skill skill, StringBuilder buffer, int length, string format)
public override int GetIntroduction(Skill skill, StringBuilder buffer, string format)
{
string result = string.Format(format,
skill.GetLevel(),
-5 + 7 * skill.GetLevel(),
1.9 * skill.GetLevel() * skill.GetLevel() + 64 * skill.GetLevel() + 36.7);
if (result.Length < length)
{
buffer.Append(result);
return result.Length;
}
return 0;
buffer.Append(result);
return result.Length;
}
#endif
+27 -4
View File
@@ -1,6 +1,9 @@
using BrewMonster.Assets.PerfectWorld.Scripts.Skills;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
using UnityEngine.InputSystem;
using static UnityEngine.Rendering.DebugUI;
namespace BrewMonster.Scripts.Skills
{
@@ -74,7 +77,10 @@ namespace BrewMonster.Scripts.Skills
}
public override int GetRangeType() { return stub.GetRange().type; }
public override int GetRequiredLevel() { return stub.GetRequiredLevel(this); }
public override void GetIntroduction(StringBuilder buf, SkillStr table)
{
int n = stub.GetIntroduction(this, buf, table.Find((int)stub.id * 10 + 1));
}
public override int GetTargetType()
{
if (stub.restrict_corpse == 1)
@@ -89,6 +95,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; }
@@ -97,6 +104,7 @@ namespace BrewMonster.Scripts.Skills
return stub.GetIcon();
}
public int GetAbility() { return SkillWrapper.Instance.GetAbility(id); }
public bool CheckComboSkExtraCondition() { return stub.CheckComboSkExtraCondition(this); }
public override string GetName() { return stub.GetName(); }
public override float GetPrayRange(float range, float prayplus)
@@ -131,6 +139,7 @@ namespace BrewMonster.Scripts.Skills
public int GetRequiredRealmLevel() { return stub.GetRequiredRealmLevel(this); }
public int GetMaxability() { return stub.GetMaxAbility(this); }
public uint GetId() { return id; }
public int GetComboSkInterval() { return stub.combosk_interval; }
}
@@ -232,7 +241,12 @@ namespace BrewMonster.Scripts.Skills
}
public static List<uint> GetInherentSkillList(uint cls)
{
return inheritSkillMap[cls];
if (!inheritSkillMap.TryGetValue(cls, out List<uint> list))
{
list = new List<uint>();
inheritSkillMap[cls] = list;
}
return list;
}
public static SkillStub GetStub(uint i)
@@ -243,11 +257,20 @@ namespace BrewMonster.Scripts.Skills
public static void InitStaticData()
{
var map = GetMap();
var comboMap = GetComboSkMap();
foreach (var skill in map)
{
SkillStub sk = skill.Value;
if (sk.is_inherent) GetInherentSkillList((uint)sk.cls).Add(sk.id);
if (sk.combosk_preskill > 0) GetComboSkMap()[(uint)sk.combosk_preskill].Add(sk.id);
if (sk.combosk_preskill > 0)
{
if (!comboMap.TryGetValue((uint)sk.combosk_preskill, out List<uint> comboList))
{
comboList = new List<uint>();
comboMap[(uint)sk.combosk_preskill] = comboList;
}
comboList.Add(sk.id);
}
}
}
@@ -281,7 +304,7 @@ namespace BrewMonster.Scripts.Skills
public virtual int GetExecutetime(Skill skill) { return 1000; }
public virtual bool CheckHpCondition(int hp, int max_hp) { return true; }
public virtual bool CheckComboSkExtraCondition(Skill skill) { return true; }
public virtual int GetIntroduction(Skill skill, ushort[] descBuffer, int descBufferLen, ushort[] titleBuffer) { return 0; }
public virtual int GetIntroduction(Skill skill, StringBuilder descBuffer, string titleBuffer) { return 0; }
// ЧԼ // Validate weapon restriction
public bool ValidWeapon(int weapon)
@@ -1,5 +1,6 @@
fileFormatVersion: 2
guid: d0c06c588e2a6442488a3542551fb243
guid: 11093b8e0b61f8e46a8847e31fe74326
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
@@ -0,0 +1,121 @@
using BrewMonster.Assets.PerfectWorld.Scripts.UI.GamePlay;
using BrewMonster.UI;
using System;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using static UnityEditor.AddressableAssets.Build.Layout.BuildLayout;
namespace BrewMonster
{
public class CDlgSkillSubAction : MonoBehaviour
{
[SerializeField] List<ActionInfo> m_aActionInfo = new List<ActionInfo>();
[SerializeField] Transform orderContain;
[SerializeField] AUIImagePicture orderTemplate;
[SerializeField] AUIImagePicture actionTemplate;
[SerializeField] Transform actionContain;
private void Awake()
{
if (orderContain == null)
{
BMLogger.LogError("CDlgSkillSubAction: orderContain is not assigned!");
return;
}
;
if (actionContain == null)
{
BMLogger.LogError("CDlgSkillSubAction: actionContain is not assigned!");
return;
}
Init();
}
private void OnEnable()
{
OnShowDialog();
}
public void Init()
{
int[] objCount = { 8, 2, 2, 27 };
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < objCount[i]; j++)
{
var orderTP = Instantiate(orderTemplate, orderContain);
orderTP.gameObject.SetActive(true);
m_aActionInfo.Add(new ActionInfo
{
image = orderTP,
pLabel = orderTP.GetComponentInChildren<TextMeshProUGUI>()
});
}
}
for (int j = 0; j < 27; j++)
{
var actionTP = Instantiate(actionTemplate, actionContain);
actionTP.gameObject.SetActive(true);
m_aActionInfo.Add(new ActionInfo
{
image = actionTP,
pLabel = actionTP.GetComponentInChildren<TextMeshProUGUI>()
});
}
}
public void OnShowDialog()
{
AUIImagePicture pImage;
TextMeshProUGUI pLabel;
CECShortcut pSCThis;
string strFile = "";
var gameUIMan = CECUIManager.Instance.GetInGameUIMan();
CECGameRun pGameRun = CECGameRun.Instance;
int[] objCount = { 8, 2, 2, 27 };
CECShortcutSet[] a_pSC =
{
pGameRun.GetGenCmdShortcuts(),
pGameRun.GetTeamCmdShortcuts(),
pGameRun.GetTradeCmdShortcuts(),
pGameRun.GetPoseCmdShortcuts()
};
int count = 0;
for (int i = 0; i < a_pSC.Length; i++)
{
for (int j = 0; j < objCount[i]; j++)
{
pImage = m_aActionInfo[count].image;
pLabel = m_aActionInfo[count].pLabel;
if (!pImage) break;
if (j < a_pSC[i].GetShortcutNum())
{
pSCThis = a_pSC[i].GetShortcut(j);
pImage.SetDataPtr(pSCThis, "ptr_CECShortcut");
strFile = pSCThis.GetIconFile();
gameUIMan.SetCover(pImage, strFile, EC_GAMEUI_ICONS.ICONS_ACTION);
pLabel.SetText(pSCThis.GetDesc());
}
else
{
/* pImage->Show(false);
pLabel->Show(false);*/
}
count++;
}
}
}
[Serializable]
public struct ActionInfo
{
public AUIImagePicture image;
public TextMeshProUGUI pLabel;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 1eda03e6b4326e14b95d57bf324b96bb
@@ -0,0 +1,28 @@
using BrewMonster.Network;
using BrewMonster.UI;
using UnityEngine;
namespace BrewMonster
{
public class CDlgDragDrop : MonoBehaviour
{
#if UNITY_EDITOR
private void Update()
{
if (Input.GetKeyDown(KeyCode.S))
OnSkillDragDrop();
}
#endif
public void OnSkillDragDrop()
{
var iSlot = 2;
var nCombo = 2;
CECShortcutSet pSCS = CECUIManager.Instance.GetInGameUIMan().GetSCSByDlg(1);
if (pSCS.GetShortcut(iSlot - 1) == null || !EC_Game.GetConfigs().GetGameSettings().bLockQuickBar)
pSCS.CreateSkillGroupShortcut(iSlot - 1, nCombo - 1);
}
}
}
/*AString s = WC2AS(*pstr); // convert wide -> ansi/utf8 tùy macro bạn định nghĩa
a_LogOutput(0, "HoangDev: 1 n=%d, pstr=%s", n, static_cast <const char*>(s));*/
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: ad855801e0cddfe46a48b448016ee952
@@ -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
@@ -436,7 +436,7 @@ namespace BrewMonster
switch( pEquipA.GetClassID() )
{
case (int)EC_IvtrEquip.EQUIP_CLASS_ID.ICID_WEAPON:
nEquipLevel = ((EC_IvtrWeapon)pEquipA).GetDBEssence().level;
nEquipLevel = ((CECIvtrWeapon)pEquipA).GetDBEssence().level;
break;
case (int)EC_IvtrEquip.EQUIP_CLASS_ID.ICID_ARMOR:
nEquipLevel = ((EC_IvtrArmor)pEquipA).GetDBEssence().level;
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6d97873e24020e642b7c6f6b79ea8c2c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,15 @@
using BrewMonster.UI;
using UnityEngine;
namespace BrewMonster
{
public class CDlgTaskAction : AUIDialog
{
public void OnShowDialog()
{
uint param = GetData();
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: a5f25e21a0550df41af0c2e57639635a
@@ -13,8 +13,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;
@@ -32,14 +32,16 @@ namespace BrewMonster.Assets.PerfectWorld.Scripts.UI.GamePlay
if (skillbutton == null)
{
Debug.LogError("Skill Button is not assigned in AUIImagePicture");
return;
}
skillbutton.onClick.AddListener(Execute);
m_pParent = GetComponentInParent<AUIDialog>();
}
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,3 +1,5 @@
//#define Applyforalicense
using BrewMonster.Assets.PerfectWorld.Scripts.UI.GamePlay;
using BrewMonster.Network;
using BrewMonster.Scripts;
@@ -25,16 +27,17 @@ namespace BrewMonster
/// Apply for a license remove later
/// </summary>
/// <returns></returns>
public bool UpdateShortcuts()
{
CECShortcut pSC;
Image skillImage;
CECSCSkill pSCSkill;
int iIconFile, nMax;
AUIImagePicture pCell;
CECSkill pSkill = new CECSkill(-1, -1);
AUIClockIcon pClock;
int iIconFile, nMax = 0;
int nCurPanel9 = GetCurPanel1();
int nCurPanel8 = GetCurPanel2();
@@ -44,7 +47,6 @@ namespace BrewMonster
var a_pSCS = new List<CECShortcutSet>();
var a_pszPanel = new List<string>();
GetQuickBarNameAndSC(pHost, a_pszPanel, a_pSCS, nCurPanel9, nCurPanel8);
if (a_pSCS == null || a_pSCS.Count < 2)
return false;
@@ -53,12 +55,28 @@ namespace BrewMonster
if (a_pSCS[i] == null)
continue;
/*CDlgQuickBar* pQuickBar = dynamic_cast<CDlgQuickBar*>(GetGameUIMan()->GetDialog(a_pszPanel[i]));
if (!pQuickBar || !pQuickBar->IsShow()) continue;*/
//*//*CDlgQuickBar* pQuickBar = dynamic_cast<CDlgQuickBar*>(GetGameUIMan()->GetDialog(a_pszPanel[i]));
//if (!pQuickBar || !pQuickBar->IsShow()) continue;*//*
#if LICENSE_VERSION
int slotIndex = 0;
#endif
#if LICENSE_VERSION
for (int j = 0; j < a_pSCS.Count; j++)
{
#else
int nSlots = Mathf.Min(a_pSCS[i].GetShortcutNum(), AUIImagePictureList.Count);
for (int j = 0; j < nSlots; j++)
{
{
#endif
#if LICENSE_VERSION
pCell = AUIImagePictureList[slotIndex];
#else
pCell = AUIImagePictureList[j];
#endif
pSC = a_pSCS[i].GetShortcut(j);
pClock = pCell.GetClockIcon();
pClock.SetProgressRange(0, 1);
@@ -67,13 +85,12 @@ namespace BrewMonster
{
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;*/
/* a_pSCS[i]->SetShortcut(j, NULL);
pSC = NULL;*/
}
else
{
@@ -81,14 +98,14 @@ namespace BrewMonster
{
if (ElementSkill.IsGoblinSkill((uint)pSkill.GetSkillID()))
{
/* if (pHostGoblin && !pHostGoblin->CheckSkillCastCondition(pSkill))
{
pCell->SetColor(A3DCOLORRGB(255, 255, 255));
}
else
{
pCell->SetColor(A3DCOLORRGB(128, 128, 128));
}*/
/* if (pHostGoblin && !pHostGoblin->CheckSkillCastCondition(pSkill))
{
pCell->SetColor(A3DCOLORRGB(255, 255, 255));
}
else
{
pCell->SetColor(A3DCOLORRGB(128, 128, 128));
}*/
}
else
{
@@ -102,8 +119,8 @@ namespace BrewMonster
}
}
}
/* else
pClock.SetColor(A3DCOLORRGBA(0, 0, 0, 128));*/
/*else
pClock.SetColor(A3DCOLORRGBA(0, 0, 0, 128));*/
if (pSkill != null && (pSkill.GetCoolingTime() > 0 ||
pHost.GetPrepSkill() == pSkill))
{
@@ -199,7 +216,7 @@ namespace BrewMonster
pClock->SetColor(A3DCOLORRGBA(0, 0, 0, 128));
}
}*/
else if(pSC.GetType() == (int)CECShortcut.ShortcutType.SCT_ITEM)
else if (pSC.GetType() == (int)CECShortcut.ShortcutType.SCT_ITEM)
{
iIconFile = (int)EC_GAMEUI_ICONS.ICONS_INVENTORY;
pCell.SetColor(new Color(1f, 1f, 1f));
@@ -211,7 +228,7 @@ namespace BrewMonster
{
int maxNullable = -1;
int coolTime = pItem.GetCoolTime(out maxNullable);
nMax = maxNullable > 0 ? maxNullable: 0;
nMax = maxNullable > 0 ? maxNullable : 0;
if (coolTime > 0)
{
@@ -266,7 +283,7 @@ namespace BrewMonster
// GetGameUIMan().SetCover(pCell, petIcon, EC_GAMEUI_ICONS.ICONS_INVENTORY);
// }
//}
else if(pSC.GetType() == (int)CECShortcut.ShortcutType.SCT_AUTOFASHION)
else if (pSC.GetType() == (int)CECShortcut.ShortcutType.SCT_AUTOFASHION)
{
iIconFile = (int)EC_GAMEUI_ICONS.ICONS_SUITE;
@@ -320,218 +337,22 @@ namespace BrewMonster
if (pSC != null)
{
pCell.SetDataPtr(pSC, "ptr_CECShortcut");
#if LICENSE_VERSION
slotIndex++;
#endif
if (pCell.GetDataPtr() == pSC)
{
continue;
}
pCell.SetDataPtr(pSC);
if (pSC.GetType() == (int)CECShortcut.ShortcutType.SCT_SKILLGRP)
{
/* EC_VIDEO_SETTING setting = GetGame()->GetConfigs()->GetVideoSettings();
pCell->SetCover(GetGameUIMan()->m_pA2DSpriteIcons[CECGameUIMan::ICONS_SKILLGRP],
setting.comboSkill[((CECSCSkillGrp*)pSC)->GetGroupIndex()].nIcon + 1);*/
}
else
{
if (pSkill != null)
{
//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;
}
/* 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)
{
pCell.SetDataPtr(pSC, "ptr_CECShortcut");
if (pSC.GetType() == (int)CECShortcut.ShortcutType.SCT_SKILLGRP)
{
*//* EC_VIDEO_SETTING setting = GetGame()->GetConfigs()->GetVideoSettings();
pCell->SetCover(GetGameUIMan()->m_pA2DSpriteIcons[CECGameUIMan::ICONS_SKILLGRP],
setting.comboSkill[((CECSCSkillGrp*)pSC)->GetGroupIndex()].nIcon + 1);*//*
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
{
@@ -542,24 +363,24 @@ namespace BrewMonster
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]);*//*
/* 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->SetCover(NULL, -1);
pCell->SetText(_AL(""));
pCell->SetDataPtr(NULL);
pCell->SetColor(A3DCOLORRGB(255, 255, 255));*//*
pCell->SetColor(A3DCOLORRGB(255, 255, 255)); */
}
}
}
return true;
}*/
}
private void GetQuickBarNameAndSC(CECHostPlayer pHost, List<string> pszPanel, List<CECShortcutSet> pSCS, int panel9, int panel8)
{
string dlgName;
@@ -612,11 +433,11 @@ namespace BrewMonster
pszPanel.Add(dlgName);
}
}
private int GetCurPanel1()
public int GetCurPanel1()
{
return m_nCurPanel1;
}
private int GetCurPanel2()
public int GetCurPanel2()
{
return m_nCurPanel2;
}
@@ -10,7 +10,9 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Unity.VisualScripting;
using UnityEngine;
using static UnityEngine.Rendering.DebugUI;
namespace BrewMonster.UI
{
@@ -23,6 +25,7 @@ namespace BrewMonster.UI
private Dictionary<byte, (string, Sprite[])> m_IconMap;
private const string SKILL_ICONLIST_NAME = "iconlist_skill_multisprite";
private const string ACTION_ICONLIST_NAME = "ActionIcon/iconlist_action_multisprite";
public static bool TALKPROC_IS_TERMINAL(uint id)
{
@@ -60,7 +63,24 @@ namespace BrewMonster.UI
}
m_pDlgNPC.PopupNPCDialog(pTalk);
}
public CECShortcutSet GetSCSByDlg(int indexPanel)
{
CECHostPlayer pHost = CECGameRun.Instance.GetHostPlayer();
CDlgQuickBar cDlgQuickBar = CECUIManager.Instance.GetCDlgQuickBar();
CECShortcutSet pSCS = null;
int index = (0);
if (indexPanel == 1)
{
int panel = (index < 0 ? cDlgQuickBar.GetCurPanel1() : index) - 1;
pSCS = pHost.GetShortcutSet1(0);
}
else
{
int panel = (index < 0 ? cDlgQuickBar.GetCurPanel2() : index) - 1;
pSCS = pHost.GetShortcutSet2(panel);
}
return pSCS;
}
// 弹出任务完成对话框(到达/离开地点等触发) // Popup task-finish dialog (reach/leave site, etc.)
// C++: pTask->PopupTaskFinishDialog(taskId, &awardTalk); then OnUIDialogEnd() notifies server.
public bool PopupTaskFinishDialog(uint taskId, talk_proc pTalk)
@@ -146,9 +166,9 @@ namespace BrewMonster.UI
m_pDlgTask = GetDialog(CECUIHelper.DlgTaskName).GetComponent<DlgTask>();
m_pDlgTask.Show(false);
m_IconMap[(byte)EC_GAMEUI_ICONS.ICONS_SKILL] =("iconlist_skill_multisprite", Resources.LoadAll<Sprite>("iconlist_skill_multisprite"));
m_IconMap[(byte)EC_GAMEUI_ICONS.ICONS_SKILL] = (SKILL_ICONLIST_NAME, Resources.LoadAll<Sprite>(SKILL_ICONLIST_NAME));
m_IconMap[(byte)EC_GAMEUI_ICONS.ICONS_ACTION] = (ACTION_ICONLIST_NAME, Resources.LoadAll<Sprite>(ACTION_ICONLIST_NAME));
}
public void SetCover(AUIImagePicture pImgPic, string nameImage, EC_GAMEUI_ICONS iCONS_SKILL)
{
pImgPic.SetImage(m_IconMap[(byte)iCONS_SKILL].Item2.FirstOrDefault(s => s.name == nameImage));
@@ -176,7 +196,6 @@ namespace BrewMonster.UI
}
}
public enum EC_GAMEUI_ICONS : byte
{
ICONS_ACTION = 0,
ICONS_SKILL,
@@ -1,3 +1,4 @@
using BrewMonster.Assets.PerfectWorld.Scripts.UI.GamePlay;
using BrewMonster.Scripts.Skills;
using BrewMonster.UI;
using CSNetwork.GPDataType;
@@ -18,6 +19,7 @@ namespace BrewMonster
[SerializeField] private Image skillIcon;
[SerializeField] private GameObject m_highlight;
[SerializeField] private Button m_upgradeBtn;
[SerializeField] private AUIImagePicture m_skillIconImgPic;
private int m_skillID;
private int m_curLevel;
@@ -135,20 +137,36 @@ namespace BrewMonster
break;
}
}
string skillDsc;
int reqLevel;
int reqRealmLevel;
if (enumSkillLearnedState.SKILL_NOT_LEARNED == learnedState)
{
skillIcon.color = Color.gray;
/* skillDsc = model.GetSkillDescription(m_skillID, 1);
reqLevel = GNET::ElementSkill::GetRequiredLevel(m_skillID, 1);
reqRealmLevel = GNET::ElementSkill::GetRequiredRealmLevel(m_skillID, 1);*/
skillDsc = model.GetSkillDescription(m_skillID, 1);
reqLevel = ElementSkill.GetRequiredLevel((uint)m_skillID, 1);
reqRealmLevel = ElementSkill.GetRequiredRealmLevel((uint)m_skillID, 1);
}
else
{
skillIcon.color = Color.white;
/* skillDsc = model.GetSkillDescription(m_skillID, m_curLevel);
reqLevel = GNET::ElementSkill::GetRequiredLevel(m_skillID, m_curLevel);
reqRealmLevel = GNET::ElementSkill::GetRequiredRealmLevel(m_skillID, m_curLevel);*/
skillDsc = model.GetSkillDescription(m_skillID, m_curLevel);
reqLevel = ElementSkill.GetRequiredLevel((uint)m_skillID, m_curLevel);
reqRealmLevel = ElementSkill.GetRequiredRealmLevel((uint)m_skillID, m_curLevel);
}
if (reqLevel == 0)
{
reqLevel = 1;
}
skillDsc += GPDataTypeHelper.ReplacePercentD(GetStringFromTable(11328), reqLevel);
if (reqRealmLevel != 0)
{
skillDsc += GetStringFromTable(11401);
skillDsc += GetGameUIMan().GetRealmName(reqRealmLevel);
}
//m_skillIconImgPic.SetHint(skillDsc);
string skillName = model.GetSkillName(m_skillID);
/* if (model.IsPassiveSkill(m_skillID))
@@ -0,0 +1,80 @@
using Animancer;
using BrewMonster.Assets.PerfectWorld.Scripts.UI.GamePlay;
using BrewMonster.Network;
using BrewMonster.UI;
using System;
using UnityEngine;
namespace BrewMonster
{
public class CDlgSkillEdit : AUIDialog
{
int m_nIcon;
// Start is called once before the first execution of Update after the MonoBehaviour is created
public override void Start()
{
m_nIcon = 1;
}
#if UNITY_EDITOR
public override void Update()
{
if (Input.GetKeyDown(KeyCode.T))
{
OnShowDialog();
OnCommandConfirm();
}
}
#endif
public void OnShowDialog()
{
EC_VIDEO_SETTING setting = EC_Game.GetConfigs().GetVideoSettings();
if (GetData() == 0)
{
for (uint i = 0; i < EC_ConfigConstants.EC_COMBOSKILL_NUM; i++)
{
if (setting.comboSkill[i].nIcon == 0)
{
SetData(i + 1);
break;
}
}
if (GetData() == 0)
{
//Show(false);
return;
}
}
}
public void OnCommandConfirm()
{
EC_VIDEO_SETTING setting = EC_Game.GetConfigs().GetVideoSettings();
setting.comboSkill[GetData() - 1].nIcon = (byte)m_nIcon;
int i;
int j = 0;
for (i = 0; i < 2; i++)
{
/* AString strName;
strName.Format("Item_%d", i + 1);
PAUIIMAGEPICTURE pImage = static_cast<PAUIIMAGEPICTURE>(GetDlgItem(strName));
if (!pImage) break;*/
//CECSkill pSkill = (CECSkill)pImage.GetDataPtr("ptr_CECSkill");
//int iType = pImage->GetData();
if (true/*iType == 0 && pSkill != null*/)
{
setting.comboSkill[GetData() - 1].idSkill[j] = (short)(i +1) /*pSkill.GetSkillID()*/;
j++;
}
else
{
//setting.comboSkill[GetData() - 1].idSkill[j] = -iType;
j++;
}
}
EC_Game.GetConfigs().SetVideoSettings(setting);
//Show(false);
CECUIManager.Instance.m_pDlgSkillSubOther.UpdateComboSkill();
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: b46a84d2ff078524f974fb6d60019d8c
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:def3c6a4f58ef9b5c9558210328e5b6f3ed11bfee5b577271ee43fa110616b82
size 200521689
oid sha256:95d6d825ffc45e6ba389d5e2baffd576d409d5718b1888331c763dfe2b9460e5
size 200522636
+43 -30
View File
@@ -7,8 +7,10 @@ using BrewMonster.UI;
using CSNetwork;
using CSNetwork.GPDataType;
using CSNetwork.Protocols.RPCData;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using Unity.Cinemachine;
using UnityEngine;
@@ -32,6 +34,8 @@ public partial class CECGameRun
//[SerializeField] private Transform ground;
CECHostPlayer hostPlayer;
private CECWorld m_pWorld;
int m_iGameState; // Game state
protected CECUIManager m_pUIManager; // UI manager
@@ -108,7 +112,7 @@ public partial class CECGameRun
if (_npcServerPrefab == null)
{
BMLogger.LogError("CECGameRun::LoadPrefabs, Failed to load _npcServerPrefab prefab.");
}
}
#endif
}
@@ -277,7 +281,6 @@ public partial class CECGameRun
/// <returns>True if loaded successfully / 加载成功返回true</returns>
public bool LoadConfigsFromServer(byte[] pDataBuf, int iDataSize)
{
BMLogger.LogError("LoadConfigsFromServer ");
const uint USERCFG_VERSION = 3;
if (pDataBuf == null || iDataSize == 0)
@@ -305,7 +308,6 @@ public partial class CECGameRun
dwRealLen = 0;
}
byte[] pData = pDataBuf;
int dataOffset = offset;
if (dwVer >= 3)
{
@@ -348,32 +350,31 @@ public partial class CECGameRun
}
}
//TODO: flow in update fix later
Task.Run(() =>
{
GameSession.Context.Post(_ =>
{
if (m_pUIManager == null)
{
m_pUIManager = CECUIManager.Instance;
}
m_pUIManager.GetCDlgQuickBar().UpdateShortcuts();
}, null);
});
/* Task.Run(() =>
{
GameSession.Context.Post(_ =>
{
if (m_pUIManager == null)
{
m_pUIManager = CECUIManager.Instance;
}
m_pUIManager.GetCDlgQuickBar().UpdateShortcuts();
}, null);
});*/
// TODO: Uncomment when UI manager is available
// Load UI configs / 加载UI配置
CECGameUIMan pGameUI = m_pUIManager.GetInGameUIMan();
/* if (pGameUI != null)
{
int iSize = dr.ReadInt();
byte[] uiConfigData = dr.ReadData(iSize);
if (!pGameUI.SetUserLayout(uiConfigData, iSize))
{
BMLogger.LogError("CECGameRun::LoadConfigsFromServer, Failed to set user layout");
return false;
}
}*/
if (pGameUI != null)
{
int iSize = dr.ReadInt();
byte[] uiConfigData = dr.ReadData(iSize);
/*if (!pGameUI.SetUserLayout(uiConfigData, iSize))
{
BMLogger.LogError("CECGameRun::LoadConfigsFromServer, Failed to set user layout");
return false;
}*/
}
// Load user settings / 加载用户设置
if (dwVer >= 2)
@@ -381,11 +382,11 @@ public partial class CECGameRun
// TODO: Uncomment when game configs are available
int iSize = dr.ReadInt();
byte[] settingsData = dr.ReadData(iSize);
/* if (!EC_Game.GetConfigs().LoadUserConfigData(settingsData, iSize))
{
BMLogger.LogError("CECGameRun::LoadConfigsFromServer, Failed to load user config data");
return false;
}*/
if (!EC_Game.GetConfigs().LoadUserConfigData(settingsData, iSize))
{
BMLogger.LogError("CECGameRun::LoadConfigsFromServer, Failed to load user config data");
return false;
}
}
}
catch (System.Exception e)
@@ -705,4 +706,16 @@ public partial class CECGameRun
{
m_iDExpEndTime = endTime;
}
public int GetGameState() { return m_iGameState; }
public void SaveConfigsToServer()
{
}
}
public enum GameState
{
GS_NONE = 0, // None
GS_LOGIN, // Login in state
GS_GAME, // In game
};
File diff suppressed because it is too large Load Diff
+1
View File
@@ -25,6 +25,7 @@ public class CECUIManager : MonoSingleton<CECUIManager>
[SerializeField] private UnityEngine.UI.Button btnSecondClick;
[SerializeField] CDlgQuickBar m_pDlgQuickBar1;
public CDlgSkillSubOther m_pDlgSkillSubOther;
CDlgMessageBox m_pDlgMessageBox;
public CDlgSkillAction m_pDlgSkillAction;
+33 -10
View File
@@ -3,6 +3,7 @@ using BrewMonster.Network;
using CSNetwork.GPDataType;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Runtime.InteropServices;
@@ -68,6 +69,7 @@ public static class EC_Utility
Marshal.FreeHGlobal(ptr);
}
}
public static A3DVECTOR3 glb_DecompressDirH(byte byDir)
{
const float fInter = 360.0f / 256.0f;
@@ -84,6 +86,27 @@ public static class EC_Utility
{
return new System.Numerics.Vector3(v.x, v.y, v.z);
}
public static byte glb_BuildRefuseBLSMask()
{
byte byMask = 0;
// const EC_GAME_SETTING &gs = g_pGame->GetConfigs()->GetGameSettings();
var gs = EC_Game.GetConfigs().GetGameSettings();
if (gs.bBlsRefuse_Neutral)
byMask |= (byte)REFUSE_BLESS_MASK.REFUSE_NEUTRAL_BLESS;
if (gs.bBlsRefuse_NonTeammate)
byMask |= (byte)REFUSE_BLESS_MASK.REFUSE_NON_TEAMMATE_BLESS;
return byMask;
}
public static void Swap<T>(List<T> arr, int a, int b)
{
T tmp = arr[a];
arr[a] = arr[b];
arr[b] = tmp;
}
public static A3DVECTOR3 ToA3DVECTOR3(this UnityEngine.Vector3 v)
{
a3DVECTOR.Clear();
@@ -148,20 +171,20 @@ public static class EC_Utility
byte byMask = 0;
CECConfigs pConfigs = EC_Game.GetConfigs();
/* if (pConfigs->GetGameSettings().bBls_NoRed)
byMask |= GP_BLSMASK_NORED;
/* if (pConfigs->GetGameSettings().bBls_NoRed)
byMask |= GP_BLSMASK_NORED;
if (pConfigs->GetGameSettings().bBls_NoMafia)
byMask |= GP_BLSMASK_NOMAFIA;
if (pConfigs->GetGameSettings().bBls_NoMafia)
byMask |= GP_BLSMASK_NOMAFIA;
if (pConfigs->GetGameSettings().bBls_Self)
byMask |= GP_BLSMASK_SELF;
if (pConfigs->GetGameSettings().bBls_Self)
byMask |= GP_BLSMASK_SELF;
if (pConfigs->GetGameSettings().bBls_NoAlliance)
byMask |= GP_BLSMASK_NOALLIANCE;
if (pConfigs->GetGameSettings().bBls_NoAlliance)
byMask |= GP_BLSMASK_NOALLIANCE;
if (pConfigs->GetGameSettings().bBls_NoForce)
byMask |= GP_BLSMASK_NOFORCE;*/
if (pConfigs->GetGameSettings().bBls_NoForce)
byMask |= GP_BLSMASK_NOFORCE;*/
return byMask;
}
+76 -10
View File
@@ -28,6 +28,7 @@ namespace BrewMonster
"程序联入/金币效果.gfx", // RES_GFX_ITEMFLASH,
"程序联入/鼠标悬浮.gfx", // RES_GFX_CURSORHOVER,
"程序联入/目标被选中.gfx", // RES_GFX_SELECTED,
"程序联入/目标被选中.gfx", // RES_GFX_SELECTED,
"程序联入/海底飘尘.gfx", // RES_GFX_FLOATING_DUST,
"程序联入/人物游动水圈.gfx", // RES_GFX_WATER_WAVE_STILL,
"程序联入/人物游动水波.gfx", // RES_GFX_WATER_WAVE_MOVE,
@@ -51,7 +52,73 @@ namespace BrewMonster
"人物/通用/其它/境界提升.gfx",
"策划联入/状态效果/斗气%d级.gfx",
};
public static string res_IconFile(int n)
{
return l_aIconFiles[n];
}
public static readonly string[] l_aIconFiles =
{
"打坐",
"走跑转换",
"普通攻击",
"寻找目标",
"协助攻击",
"邀请加入",
"脱离队伍",
"踢出队伍",
"寻找队伍",
"交易命令",
"摆摊卖",
"摆摊买",
"邀请加入",
"飞行",
"招手", // RES_ICON_CMD_EXP_WAVE,
"点头", // RES_ICON_CMD_EXP_NOD,
"摇头", // RES_ICON_CMD_EXP_SHAKEHEAD,
"耸肩膀", // RES_ICON_CMD_EXP_SHRUG,
"大笑", // RES_ICON_CMD_EXP_LAUGH,
"生气", // RES_ICON_CMD_EXP_ANGRY,
"晕倒", // RES_ICON_CMD_EXP_STUN,
"沮丧", // RES_ICON_CMD_EXP_DEPRESSED,
"飞吻", // RES_ICON_CMD_EXP_KISSHAND,
"害羞", // RES_ICON_CMD_EXP_SHY,
"抱拳", // RES_ICON_CMD_EXP_SALUTE,
"坐下", // RES_ICON_CMD_EXP_SITDOWN,
"冲锋", // RES_ICON_CMD_EXP_ASSAULT,
"思考", // RES_ICON_CMD_EXP_THINK,
"挑衅", // RES_ICON_CMD_EXP_DEFIANCE,
"胜利", // RES_ICON_CMD_EXP_VICTORY,
"伸懒腰", // RES_ICON_CMD_EXP_GAPE
"亲吻", // RES_ICON_CMD_EXP_KISS
"战斗", // RES_ICON_CMD_EXP_FIGHT,
"攻击1", // RES_ICON_CMD_EXP_ATTACK1,
"攻击2", // RES_ICON_CMD_EXP_ATTACK2,
"攻击3", // RES_ICON_CMD_EXP_ATTACK3,
"攻击4", // RES_ICON_CMD_EXP_ATTACK4,
"防御", // RES_ICON_CMD_EXP_DEFENCE,
"摔倒", // RES_ICON_CMD_EXP_FALL,
"倒地", // RES_ICON_CMD_EXP_FALLONGROUND,
"张望", // RES_ICON_CMD_EXP_LOOKAROUND,
"舞蹈1", // RES_ICON_CMD_EXP_DANCE,
"舞蹈2", // RES_ICON_CMD_EXP_FASHIONWEAPON
"拾取", // RES_ICON_CMD_PICKUP
"挖掘", // RES_ICON_CMD_GATHER
"加速飞行", // RES_ICON_CMD_RUSHFLY
"相依相偎的动作", // RES_ICON_CMD_BINDBUDDY
"亲亲密密的动作", // RES_ICON_CMD_TWOKISS
"跳跃的动作1", // RES_ICON_CMD_JUMPTRICK,
"跳跃的动作2", // RES_ICON_CMD_RUNTRICK,
};
public static string res_GFXFile(int n)
{
if (n < 0 || n >= l_aGFXFiles.Length)
@@ -78,7 +145,7 @@ namespace BrewMonster
NUM_RES_CURSOR,
}
// GFX resource
// GFX resource
enum GfxResourceType
{
RES_GFX_LEVELUP = 0,
@@ -113,11 +180,11 @@ namespace BrewMonster
NUM_RES_GFX,
};
// Sound resource
// Sound resource
// Texture resource
// Texture resource
// Shader resource
// Shader resource
enum ShaderResourceType
{
RES_SHD_HAIR = 0,
@@ -134,8 +201,7 @@ namespace BrewMonster
NUM_RES_SHADER, // 10
};
// Icon resource
// Icon resource
enum IconResourceType
{
RES_ICON_CMD_SITDOWN = 0,
@@ -201,7 +267,7 @@ namespace BrewMonster
NUM_RES_ICON,
};
// Model resource
// Model resource
enum ModelResourceType
{
RES_MOD_GOLD = 0,
@@ -266,7 +332,7 @@ namespace BrewMonster
NUM_RES_MODEL,
};
// model file for change shape 2
// model file for change shape 2
enum ResourceModelType
{
RES_MOD_CHANGESAHPE_NULL, // 0
@@ -290,7 +356,7 @@ namespace BrewMonster
RES_MOD_FORCHANGESAHPE_NUM,
};
// Some unicode string resources
// Some unicode string resources
enum UnicodeStringResourceType
{
RES_FONT_TITLE = 0,
File diff suppressed because one or more lines are too long