push convert flow use skill combo

This commit is contained in:
VDH
2026-01-23 18:01:15 +07:00
parent ef366cfc32
commit 3de0850a2b
13 changed files with 2463 additions and 661 deletions
@@ -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
@@ -789,6 +789,17 @@ namespace BrewMonster.Scripts.Managers
return IndexOfIteminEquipmentInventory.EQUIPIVTR_FINGER1;
}
}
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, // ¿¨Åưü¹ü
};
}
+3 -2
View File
@@ -257,7 +257,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)
{
@@ -1088,7 +1088,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");
@@ -1573,6 +1573,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; }
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);
}
}
}
@@ -1,631 +1,260 @@
# Flow: Player Clicks Shortcut to Use Combo Skill
# Comparison: OnMsgPlayerCastSkill - C++ vs C# Conversion
## Overview
## Summary
The conversion from C++ to C# is **mostly complete** but has some **missing elements** and **commented-out code** that should be reviewed.
This document describes the complete flow from when a player clicks on a combo skill shortcut in the UI until the combo skill sequence is executed.
## 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
---
## Complete Flow Diagram
```
┌─────────────────────────────────────────────────────────────┐
│ 1. PLAYER CLICKS COMBO SKILL SHORTCUT │
│ (Mouse click on shortcut bar OR keyboard hotkey) │
└─────────────────┬───────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 2. UI EVENT HANDLER │
│ File: EC_GameUIEvent.cpp (line 173) │
│ OR EC_GameUIMan.cpp (line 1320, 1344) │
│ │
│ CECShortcut* pSC = GetShortcutFromUI(); │
│ pSC->Execute(); // ← Calls Execute() on shortcut │
└─────────────────┬───────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 3. CECSCSkillGrp::Execute() │
│ File: EC_Shortcut.cpp (line 712-716) │
│ │
│ bool CECSCSkillGrp::Execute() │
│ { │
│ CECHostPlayer* pHost = GetHostPlayer(); │
│ pHost->ApplyComboSkill(m_iGroupIdx); // ← Group ID │
│ return true; │
│ } │
└─────────────────┬───────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 4. CECHostPlayer::ApplyComboSkill() │
│ File: EC_HostPlayer.cpp (line 7696-7721) │
│ │
│ bool ApplyComboSkill(int iGroup, ...) │
│ { │
│ ClearComboSkill(); // Clear any existing combo │
│ │
│ m_pComboSkill = new CECComboSkill; │
│ │
│ // Initialize combo skill with group index │
│ m_pComboSkill->Init(this, iGroup, │
│ m_idSelTarget, │
│ bForceAttack, │
│ bIgnoreAtkLoop); │
│ │
│ // Start first skill in combo │
│ m_pComboSkill->Continue(m_bMelee); │
│ │
│ return true; │
│ } │
└─────────────────┬───────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 5. CECComboSkill::Init() │
│ File: EC_ComboSkill.cpp (line 74-123) │
│ │
│ bool Init(CECHostPlayer* pHost, int iGroup, ...) │
│ { │
│ // Load combo skill data from configs │
│ CECConfigs* pCfg = GetConfigs(); │
│ m_cs = pCfg->GetVideoSettings() │
│ .comboSkill[iGroup]; // ← Load combo │
│ │
│ // Find loop start flag (if any) │
│ // Move cursor to first valid skill │
│ StepCursor(true); │
│ │
│ return true; │
│ } │
└─────────────────┬───────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 6. CECComboSkill::Continue() - FIRST SKILL │
│ File: EC_ComboSkill.cpp (line 128-217) │
│ │
│ bool Continue(bool bMeleeing) │
│ { │
│ int idSkill = GetNextSkill(); // Get skill ID │
│ │
│ if (idSkill > 0) // Regular skill │
│ { │
│ // Execute skill │
│ bool bRet = m_pHost->ApplySkillShortcut( │
│ idSkill, true, m_idTarget, ...); │
│ │
│ if (bRet) │
│ { │
│ StepCursor(false); // Move to next skill │
│ return true; // Continue combo │
│ } │
│ } │
│ else if (idSkill == SID_ATTACK) // Normal attack │
│ { │
│ m_pHost->CmdNormalAttack(...); │
│ StepCursor(false); │
│ return true; │
│ } │
│ │
│ return false; // Combo stopped │
│ } │
└─────────────────┬───────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 7. SKILL EXECUTION │
│ File: EC_HostPlayer.cpp │
│ │
│ ApplySkillShortcut() executes the skill: │
│ - Checks if skill is ready │
│ - Sends command to server │
│ - Starts skill animation │
│ - Updates cooldown timers │
└─────────────────┬───────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 8. CONTINUE COMBO (After Skill Finishes) │
│ File: EC_HostPlayer.cpp (line 4129-4135, 6244-6252) │
│ │
│ When skill finishes or melee state changes: │
│ │
│ if (m_pComboSkill && !m_pComboSkill->IsStop()) │
│ { │
│ // Post message to continue combo │
│ PostMessage(MSG_HST_CONTINUECOMBOSKILL, │
│ MAN_PLAYER, 0, │
│ bMeleeing ? 1 : 0, │
│ m_pComboSkill->GetGroupIndex()); │
│ } │
└─────────────────┬───────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 9. OnMsgContinueComboSkill() │
│ File: EC_HostPlayer.cpp (line 6261-6268) │
│ │
│ void OnMsgContinueComboSkill(const ECMSG &Msg) │
│ { │
│ bool bMeleeing = (Msg.dwParam1 == 1); │
│ int iGroupID = (int)Msg.dwParam2; │
│ │
│ if (m_pComboSkill && │
│ m_pComboSkill->GetGroupIndex() == iGroupID && │
│ !m_pComboSkill->IsStop()) │
│ { │
│ m_pComboSkill->Continue(bMeleeing); // ← Loop │
│ } │
│ } │
└─────────────────┬───────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 10. REPEAT STEPS 6-9 │
│ Until combo skill sequence completes or stops │
│ │
│ - Continue() executes next skill in sequence │
│ - StepCursor() moves to next position │
│ - If loop flag found, cursor jumps back │
│ - If end reached, combo stops │
└─────────────────────────────────────────────────────────────┘
```
---
## Detailed Step-by-Step Flow
### **STEP 1: Player Clicks Shortcut**
**Trigger**:
- Mouse click on shortcut bar UI element
- OR Keyboard hotkey (1-9, Q, E, R, etc.)
**UI Elements**:
- `DlgQuickBar` - Main shortcut bar dialog
- `Item_01`, `Item_02`, ... - Shortcut slot UI controls
---
### **STEP 2: UI Event Handler**
**File**: `EC_GameUIEvent.cpp` (line 163-174)
## Missing/Incomplete Elements
### 1. Missing Assertion Check in OBJECT_CAST_SKILL ❌
**C++ (line 5876):**
```cpp
// Mouse click handler
if (abs(x - m_ptLButtonDown.x) < 3 && abs(y - m_ptLButtonDown.y) < 3)
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)
{
if (strstr(pDlgSrc->GetName(), "Win_Quickbar") ||
0 == stricmp(pDlgSrc->GetName(), "Win_Action"))
{
CECShortcut* pSC = (CECShortcut*)pDlg->GetDataPtr();
if (pSC)
pSC->Execute(); // ← Execute shortcut
}
Debug.Log($"Return-to-town skill (167) cast - State2 should trigger SetReturntown(1) on server");
}
```
**OR Keyboard Handler**:
**File**: `EC_GameUIMan.cpp` (line 1316-1321)
**Status:** Acceptable - Additional debugging/logging is fine.
### 3. Memory Management Differences ✅
**C++:**
```cpp
// Keyboard shortcut handler
int nCurPanel1 = CDlgQuickBar::GetCurPanel1();
CECShortcutSet* pSCS = pHost->GetShortcutSet1(nCurPanel1 - 1);
CECShortcut* pSC = pSCS->GetShortcut(iUsage - LKEY_UI_QUICK9_SC1);
if (pSC)
pSC->Execute(); // ← Execute shortcut
```
---
### **STEP 3: CECSCSkillGrp::Execute()**
**File**: `EC_Shortcut.cpp` (line 712-716)
```cpp
bool CECSCSkillGrp::Execute()
if(m_pTargetItemSkill)
{
CECHostPlayer* pHost = g_pGame->GetGameRun()->GetHostPlayer();
pHost->ApplyComboSkill(m_iGroupIdx); // ← Group index (0-EC_COMBOSKILL_NUM-1)
return true;
delete m_pTargetItemSkill;
m_pTargetItemSkill = NULL;
}
```
**Key Points**:
- `m_iGroupIdx` is the combo skill group index (0, 1, 2, ...)
- This was set when the shortcut was created from config data
---
### **STEP 4: CECHostPlayer::ApplyComboSkill()**
**File**: `EC_HostPlayer.cpp` (line 7696-7721)
```cpp
bool CECHostPlayer::ApplyComboSkill(int iGroup, bool bIgnoreAtkLoop, int iForceAtk)
**C#:**
```csharp
if (m_pTargetItemSkill != null)
{
// Clear any existing combo skill
ClearComboSkill();
// Create new combo skill object
if (!(m_pComboSkill = new CECComboSkill))
return false;
// Determine force attack flag
bool bForceAttack;
if (iForceAtk < 0)
bForceAttack = glb_GetForceAttackFlag(NULL);
else
bForceAttack = iForceAtk > 0 ? true : false;
// Initialize combo skill
if (!(m_pComboSkill->Init(this, iGroup, m_idSelTarget, bForceAttack, bIgnoreAtkLoop)))
{
delete m_pComboSkill;
m_pComboSkill = NULL;
return false;
}
// Start the combo skill sequence
m_pComboSkill->Continue(m_bMelee);
return true;
m_pTargetItemSkill = null; // GC will handle cleanup
}
```
**Key Points**:
- Clears any existing combo skill first
- Creates new `CECComboSkill` object
- Initializes with group index, target, force attack flag
- Immediately calls `Continue()` to start first skill
---
### **STEP 5: CECComboSkill::Init()**
**File**: `EC_ComboSkill.cpp` (line 74-123)
**Status:** Acceptable - C# uses garbage collection, no explicit delete needed.
### 4. Type Casting Differences ✅
**C++:**
```cpp
bool CECComboSkill::Init(CECHostPlayer* pHost, int iGroup, int idTarget,
bool bForceAttack, bool bIgnoreAtkLoop)
{
if (iGroup < 0 || iGroup >= EC_COMBOSKILL_NUM)
return false;
m_pHost = pHost;
m_iGroup = iGroup;
m_iCursor = 0;
m_bStop = false;
m_idTarget = idTarget;
m_bForceAtk = bForceAttack;
m_bIgnoreAtkLoop = bIgnoreAtkLoop;
// ★ KEY: Load combo skill data from configs
CECConfigs* pCfg = g_pGame->GetConfigs();
m_cs = pCfg->GetVideoSettings().comboSkill[iGroup];
// m_cs contains:
// - m_cs.nIcon: Icon index
// - m_cs.idSkill[]: Array of skill IDs in sequence
// Find the last loop start flag (SID_LOOPSTART = -2)
m_iLoopStart = -1;
for (int i = 0; i < EC_COMBOSKILL_LEN; i++)
{
if (m_cs.idSkill[i] == SID_LOOPSTART)
m_iLoopStart = i;
}
// Validate loop start flag
if (m_iLoopStart >= 0)
{
// Check if there's a valid skill after loop start
// ... validation code ...
}
// Move cursor to first valid skill position
StepCursor(true);
return true;
}
cmd_object_cast_skill* pCmd = (cmd_object_cast_skill*)Msg.dwParam1;
```
**Key Points**:
- Loads combo skill data from `EC_Configs::m_vs.comboSkill[iGroup]`
- Finds loop start flag if present
- Moves cursor to first valid skill
---
### **STEP 6: CECComboSkill::Continue() - Execute Skills**
**File**: `EC_ComboSkill.cpp` (line 128-217)
```cpp
bool CECComboSkill::Continue(bool bMeleeing)
{
// Check if combo should stop
if (m_bStop || !m_cs.nIcon || !m_pHost ||
m_iCursor < 0 || m_iCursor >= EC_COMBOSKILL_LEN ||
(m_idTarget != m_pHost->GetSelectedTarget() && !m_bIgnoreAtkLoop))
return false;
int idSkill = GetNextSkill(); // Get skill ID at current cursor position
if (idSkill > 0) // Regular skill
{
// Execute the skill
bool bRet = m_pHost->ApplySkillShortcut(idSkill, true, m_idTarget,
m_bForceAtk ? 1 : 0);
if (!bRet)
{
// Skill failed, try next skill
StepCursor(false);
if (!IsStop())
{
// Post message to continue combo
g_pGame->GetGameRun()->PostMessage(MSG_HST_CONTINUECOMBOSKILL,
MAN_PLAYER, 0,
bMeleeing ? 1 : 0,
m_iGroup);
}
else
{
// Combo finished
AP_ActionEvent(AP_EVENT_COMBOFINISH);
}
return false;
}
else
{
// Skill executed successfully
AP_ActionEvent(AP_EVENT_COMBOCONTINUE);
}
}
else if (idSkill == SID_ATTACK) // Normal attack (-1)
{
if (!bMeleeing && !m_bIgnoreAtkLoop)
{
bool bRet = m_pHost->CmdNormalAttack(false, true, m_idTarget,
m_bForceAtk ? 1 : 0);
if (!bRet)
{
StepCursor(false);
if (!IsStop())
{
g_pGame->GetGameRun()->PostMessage(MSG_HST_CONTINUECOMBOSKILL,
MAN_PLAYER, 0,
bMeleeing ? 1 : 0,
m_iGroup);
}
return false;
}
}
}
// Move cursor to next skill
StepCursor(false);
return true;
}
**C#:**
```csharp
cmd_object_cast_skill pCmd = GPDataTypeHelper.FromBytes<cmd_object_cast_skill>((byte[])Msg.dwParam1);
```
**Key Points**:
- Gets next skill ID from `m_cs.idSkill[m_iCursor]`
- Executes skill via `ApplySkillShortcut()`
- Moves cursor forward with `StepCursor(false)`
- If skill fails, posts message to try next skill
---
### **STEP 7: StepCursor() - Move Through Sequence**
**File**: `EC_ComboSkill.cpp` (line 220-247)
**Status:** Acceptable - Different serialization approach, functionality preserved.
### 5. Switch Statement Syntax ✅
**C++:**
```cpp
void CECComboSkill::StepCursor(bool bFirst)
switch (Msg.dwParam2)
{
if (bFirst)
m_iCursor = -1; // Start before first position
while (1)
{
m_iCursor++;
if (m_iCursor >= EC_COMBOSKILL_LEN) // Reached end
{
// If loop flag exists, jump back to loop start
if (m_iLoopStart >= 0 && !m_bIgnoreAtkLoop)
m_iCursor = m_iLoopStart;
else
{
m_bStop = true; // Combo finished
return;
}
}
else
{
int id = m_cs.idSkill[m_iCursor];
// Find next valid skill (id > 0) or attack (id == SID_ATTACK)
if (id > 0 || (id == SID_ATTACK && !m_bIgnoreAtkLoop))
break; // Found valid skill
}
}
}
case OBJECT_CAST_SKILL:
```
**Key Points**:
- Moves cursor forward through skill array
- Skips empty slots (id == 0)
- If loop flag exists, jumps back to loop start
- Sets `m_bStop = true` when combo ends
---
### **STEP 8: Continue Combo After Skill Finishes**
**File**: `EC_HostPlayer.cpp` (line 4129-4135, 6244-6252)
When a skill finishes or melee state changes:
```cpp
// After melee attack finishes
if (m_bMelee && m_pComboSkill && !m_pComboSkill->IsStop())
**C#:**
```csharp
switch (Convert.ToInt32(Msg.dwParam2))
{
if (CECAutoPolicy::GetInstance().IsAutoPolicyEnabled())
g_pGame->GetGameRun()->PostMessage(MSG_HST_CONTINUECOMBOSKILL,
MAN_PLAYER, 0, 1,
m_pComboSkill->GetGroupIndex());
else
m_pComboSkill->Continue(true);
}
case int value2 when value2 == CommandID.OBJECT_CAST_SKILL:
```
// After other actions finish
**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());
if( CECAutoPolicy::GetInstance().IsAutoPolicyEnabled() )
g_pGame->GetGameRun()->PostMessage(MSG_HST_CONTINUECOMBOSKILL, MAN_PLAYER, 0, 0, m_pComboSkill->GetGroupIndex());
else
m_pComboSkill->Continue(false);
}
}
```
**Key Points**:
- Posts `MSG_HST_CONTINUECOMBOSKILL` message to continue combo
- Message includes melee state and group index
- Allows combo to continue asynchronously
---
### **STEP 9: OnMsgContinueComboSkill()**
**File**: `EC_HostPlayer.cpp` (line 6261-6268)
```cpp
void CECHostPlayer::OnMsgContinueComboSkill(const ECMSG &Msg)
{
bool bMeleeing = (Msg.dwParam1 == 1);
if (bMeleeing != m_bMelee)
bMeleeing = m_bMelee; // Use current melee state
int iGroupID = (int)Msg.dwParam2;
// Verify combo is still active and matches group
if (m_pComboSkill &&
m_pComboSkill->GetGroupIndex() == iGroupID &&
!m_pComboSkill->IsStop())
else
{
m_pComboSkill->Continue(bMeleeing); // Continue to next skill
if( idTarget && idTarget != m_PlayerInfo.cid )
NormalAttackObject(idTarget, true);
}
}
```
**Key Points**:
- Handles message to continue combo
- Verifies combo is still active
- Calls `Continue()` again to execute next skill
---
### **STEP 10: Loop Until Complete**
Steps 6-9 repeat until:
- All skills in sequence are executed
- Combo reaches end (no loop flag)
- Combo is stopped (`m_bStop = true`)
- Target changes (unless `bIgnoreAtkLoop` is true)
---
## Key Data Structures
### **EC_COMBOSKILL**
```cpp
struct EC_COMBOSKILL
**C# (line 1341-1357):**
```csharp
if (bDoOtherThing)
{
BYTE nIcon; // Icon index (0-N)
short idSkill[EC_COMBOSKILL_LEN]; // Array of skill IDs
};
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);
}
}
```
**Special Skill IDs**:
- `SID_ATTACK = -1`: Normal attack
- `SID_LOOPSTART = -2`: Loop start flag
- `0`: Empty slot (skipped)
- `> 0`: Regular skill ID
**Status:** ✅ Complete - Properly converted with equivalent logic.
### **CECComboSkill Member Variables**
```cpp
CECHostPlayer* m_pHost; // Host player
EC_COMBOSKILL m_cs; // Combo skill data
int m_iGroup; // Group index (0-EC_COMBOSKILL_NUM-1)
int m_iCursor; // Current position in sequence
bool m_bStop; // Stop flag
int m_iLoopStart; // Loop start index (-1 if no loop)
int m_idTarget; // Attack target
bool m_bForceAtk; // Force attack flag
bool m_bIgnoreAtkLoop; // Ignore attack loop flag
```
## Recommendations
---
## Important Files
| File | Purpose |
|------|---------|
| `EC_GameUIEvent.cpp` | Handles mouse clicks on shortcuts |
| `EC_GameUIMan.cpp` | Handles keyboard shortcuts |
| `EC_Shortcut.cpp` | `CECSCSkillGrp::Execute()` - Entry point |
| `EC_HostPlayer.cpp` | `ApplyComboSkill()` - Creates and starts combo |
| `EC_ComboSkill.cpp` | `CECComboSkill` class - Manages combo sequence |
| `EC_Configs.cpp` | Stores combo skill data in `m_vs.comboSkill[]` |
---
## For Unity Port
To implement this in Unity:
1. **Shortcut Click Handler**:
### High Priority
1. **Add missing assertion check** in `OBJECT_CAST_SKILL` case:
```csharp
public void OnShortcutClicked(int slotIndex)
{
CECShortcut shortcut = GetShortcut(slotIndex);
if (shortcut != null && shortcut.GetType() == SCT_SKILLGRP)
{
CECSCSkillGrp skillGrp = (CECSCSkillGrp)shortcut;
GetHostPlayer().ApplyComboSkill(skillGrp.GetGroupIndex());
}
}
Debug.Assert(pCmd.caster == m_PlayerInfo.cid, "Caster ID mismatch");
```
2. **ApplyComboSkill**:
```csharp
public bool ApplyComboSkill(int groupIndex)
{
ClearComboSkill();
m_comboSkill = new CECComboSkill();
if (!m_comboSkill.Init(this, groupIndex, m_selectedTarget,
forceAttack, ignoreAtkLoop))
return false;
m_comboSkill.Continue(m_isMeleeing);
return true;
}
```
### 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.
3. **Continue Combo**:
```csharp
// After skill finishes
if (m_comboSkill != null && !m_comboSkill.IsStop())
{
StartCoroutine(ContinueComboAfterDelay());
}
```
### 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
## Summary
**Conversion Completeness: ~90%**
**Complete Flow**:
1. Player clicks shortcut → `pSC->Execute()`
2. `CECSCSkillGrp::Execute()` → `ApplyComboSkill(groupIndex)`
3. `ApplyComboSkill()` → Creates `CECComboSkill` and calls `Init()`
4. `Init()` → Loads combo data from configs
5. `Continue()` → Executes first skill
6. After skill finishes → Posts `MSG_HST_CONTINUECOMBOSKILL`
7. `OnMsgContinueComboSkill()` → Calls `Continue()` again
8. Repeat until combo completes
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)
**Key**: The combo skill data (`EC_COMBOSKILL`) is loaded from `EC_Configs::m_vs.comboSkill[groupIndex]`, which was loaded from server in `LoadUserConfigData()`.
These should be reviewed and either implemented or confirmed as intentionally disabled.
@@ -3,7 +3,6 @@ using UnityEngine;
namespace BrewMonster
{
public class PlayerInfo { }
public struct ComboArg
@@ -403,17 +403,14 @@ namespace BrewMonster
}*//**/
if (pSC != null)
{
BMLogger.LogError("HoangDev: QuickBar Set Skil");
pCell.SetDataPtr(pSC, "ptr_CECShortcut");
if (pSC.GetType() == (int)CECShortcut.ShortcutType.SCT_SKILLGRP)
{
BMLogger.LogError("HoangDev: QuickBar Set Skill Group Icon SCT_SKILLGRP");
EC_VIDEO_SETTING setting = EC_Game.GetConfigs().GetVideoSettings();
/* pCell.SetCover(GetGameUIMan()->m_pA2DSpriteIcons[CECGameUIMan::ICONS_SKILLGRP],
setting.comboSkill[((CECSCSkillGrp)pSC).GetGroupIndex()].nIcon + 1);
setting.comboSkill[((CECSCSkillGrp)pSC).GetGroupIndex()].nIcon + 1;*/
GetGameUIMan().SetCover(pCell, "unknown", EC_GAMEUI_ICONS.ICONS_SKILLGRP);
/* 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
{
+23 -12
View File
@@ -33,6 +33,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
@@ -105,7 +107,7 @@ public partial class CECGameRun
if (_npcServerPrefab == null)
{
BMLogger.LogError("CECGameRun::LoadPrefabs, Failed to load _npcServerPrefab prefab.");
}
}
#endif
}
@@ -344,17 +346,17 @@ 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配置
@@ -525,4 +527,13 @@ public partial class CECGameRun
}
return szRet;
}
public int GetGameState() { return m_iGameState; }
}
public enum GameState
{
GS_NONE = 0, // None
GS_LOGIN, // Login in state
GS_GAME, // In game
};
+47 -30
View File
@@ -26,6 +26,7 @@ using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.UI;
using static BrewMonster.Scripts.Managers.EC_Inventory;
using static CECPlayerWrapper;
using cmd_select_target = CSNetwork.GPDataType.cmd_select_target;
using Host_work_ID = BrewMonster.Scripts.CECHPWork.Host_work_ID;
using Trace_reason = BrewMonster.CECHPWorkTrace.Trace_reason;
@@ -588,27 +589,36 @@ namespace BrewMonster
case int value when value == EC_MsgDef.MSG_HST_COMBO_SKILL_PREPARE: OnMsgComboSkillPrepare(Msg); break;
case int value when value == EC_MsgDef.MSG_PM_PLAYERFLY: OnMsgPlayerFly(Msg); break;
case int value when value == EC_MsgDef.MSG_HST_EMBEDITEM: OnMsgHstEmbedItem(Msg); break;
case int value when value == EC_MsgDef.MSG_HST_CONTINUECOMBOSKILL: OnMsgContinueComboSkill(Msg); break;
}
/*if (bActionStartSkill)
AP_ActionEvent(AP_EVENT_STARTSKILL, iActionTime);
/* if (bActionStartSkill)
AP_ActionEvent(AP_EVENT_STARTSKILL, iActionTime);
if (bDoOtherThing)
{
if (m_pComboSkill != null && !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 != 0 && idTarget != m_PlayerInfo.cid)
NormalAttackObject(idTarget, true);
}
}*/
}
if (bDoOtherThing)
{
if (m_pComboSkill != null && !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 != 0 && idTarget != m_PlayerInfo.cid)
NormalAttackObject(idTarget, true);
}
}*/
private void OnMsgContinueComboSkill(ECMSG Msg)
{
bool bMeleeing = ((int)Msg.dwParam1 == 1);
if (bMeleeing != m_bMelee) bMeleeing = m_bMelee;
int iGroupID = (int)Msg.dwParam2;
if (m_pComboSkill != null && m_pComboSkill.GetGroupIndex() == iGroupID && !m_pComboSkill.IsStop())
m_pComboSkill.Continue(bMeleeing);
}
private void OnMsgComboSkillPrepare(ECMSG Msg)
@@ -894,7 +904,7 @@ namespace BrewMonster
bool bActionStartSkill = false;
int iActionTime = 1000;
// CECPlayerWrapper* pWrapper = CECAutoPolicy::GetInstance().GetPlayerWrapper();
CECPlayerWrapper pWrapper = CECAutoPolicy.GetInstance().GetPlayerWrapper();
switch (Convert.ToInt32(Msg.dwParam2))
{
@@ -1012,7 +1022,7 @@ namespace BrewMonster
Debug.Log("HOST_STOP_SKILL");
//AP_ActionEvent(AP_EVENT_STOPSKILL);
AP.AP_ActionEvent((int)AP_EVENT.AP_EVENT_STOPSKILL);
if (pSkillToMatch != null)
{
// m_pWorkMan中的当前任何Work为最高优先级或、或 CECHPWorkSpell 处于最高优先级且队列中有Delay时间
@@ -1068,13 +1078,13 @@ namespace BrewMonster
// Print a notify message
// 打印提示消息
// g_pGame.GetGameRun().AddFixedMessage(FIXMSG_SKILLINTERRUPT);
Debug.Log("Skill interrupted!");
//EC_Game.GetGameRun().AddFixedMessage(FIXMSG_SKILLINTERRUPT);
BMLogger.LogError("Skill interrupted!");
//AP_ActionEvent(AP_EVENT_STOPSKILL);
AP.AP_ActionEvent((int)AP_EVENT. AP_EVENT_STOPSKILL);
// 通知策略技能被打断 | Notify policy that skill is interrupted
// CECAutoPolicy::GetInstance().SendEvent_SkillInterrupt(skill_id);
CECAutoPolicy.GetInstance().SendEvent_SkillInterrupt(skill_id);
break;
}
case int value2 when value2 == CommandID.OBJECT_CAST_INSTANT_SKILL:
@@ -1325,8 +1335,8 @@ namespace BrewMonster
break;
}
/* if (bActionStartSkill)
AP_ActionEvent(AP_EVENT_STARTSKILL, iActionTime);*/
if (bActionStartSkill)
AP.AP_ActionEvent((int)AP_EVENT.AP_EVENT_STARTSKILL, iActionTime);
if (bDoOtherThing)
{
@@ -1334,10 +1344,10 @@ namespace BrewMonster
{
// Continue combo skill
// 继续连击技能
// if (CECAutoPolicy::GetInstance().IsAutoPolicyEnabled())
// g_pGame.GetGameRun().PostMessage(MSG_HST_CONTINUECOMBOSKILL, MAN_PLAYER, 0, 0, m_pComboSkill.GetGroupIndex());
// else
m_pComboSkill.Continue(false);
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
{
@@ -6875,7 +6885,7 @@ namespace BrewMonster
return true;
}
void ClearComboSkill()
public void ClearComboSkill()
{
if (m_pComboSkill != null)
{
@@ -7266,6 +7276,13 @@ namespace BrewMonster
{
return m_pPetCorral;
}
public bool IsPlayerMoving()
{
return m_pWorkMan.IsMoving();
}
public CECComboSkill GetComboSkill() { return m_pComboSkill; }
}
public struct SkillShortCutConfig
{
+1
View File
@@ -69,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;
File diff suppressed because one or more lines are too long