push convert flow use skill combo
This commit is contained in:
@@ -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, // ¿¨Åưü¹ü
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
};
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
+356
-9
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user