Handle logic of Duel

This commit is contained in:
HungDK
2026-02-09 18:07:37 +07:00
parent 36b7591a98
commit 750e7b4ff8
5 changed files with 1020 additions and 834 deletions
File diff suppressed because it is too large Load Diff
@@ -51,6 +51,7 @@ namespace BrewMonster
public void Init(info_player_1 Info, int iAppearFlag)
{
SetUpPlayer();
m_iCID = (int)CECObject.Class_ID.OCID_ELSEPLAYER;
m_dwResFlags = (uint)PlayerResourcesReadyFlag.RESFG_ALL;
m_pEPWorkMan = new CECEPWorkMan(this);
+7
View File
@@ -205,6 +205,13 @@ public partial class CECGameRun
CECPlayer.InitStaticRes();
hostPlayer = ObjectSpawner.Instance.InstantiateObject(_playerPrefab, setThisAsParent: true).AddComponent<CECHostPlayer>();
hostPlayer.InitCharacter(info);
if (hostPlayer != null)
{
var t = Type.GetType("BrewMonster.UI.SelectedTargetHUDController, Assembly-CSharp");
if (t != null && hostPlayer.GetComponent(t) == null)
hostPlayer.gameObject.AddComponent(t);
}
}
public CECMonster GetMonster()
{
+154 -23
View File
@@ -622,6 +622,7 @@ namespace BrewMonster
case int value when value == EC_MsgDef.MSG_HST_NEWTEAMMEM: OnMsgHstNewTeamMem(Msg); break;
case int value when value == EC_MsgDef.MSG_HST_TEAMMEMBERDATA: OnMsgHstTeamMemberData(Msg); break;
case int value when value == EC_MsgDef.MSG_HST_CONTINUECOMBOSKILL: OnMsgContinueComboSkill(Msg); break;
case int value when value == EC_MsgDef.MSG_PM_DUELOPT: OnMsgHstDuelOpt(Msg); break;
}
/*if (bActionStartSkill)
@@ -714,6 +715,76 @@ namespace BrewMonster
catch { }
}
/// <summary>Update host duel state from S2C duel commands (MSG_PM_DUELOPT).</summary>
private void OnMsgHstDuelOpt(ECMSG Msg)
{
int cmdId = Convert.ToInt32(Msg.dwParam2);
byte[] data = Msg.dwParam1 as byte[];
int idOpp = 0;
if (data != null && data.Length >= 4)
idOpp = BitConverter.ToInt32(data, 0);
switch (cmdId)
{
case CommandID.DUEL_PREPARE:
m_pvp.iDuelState = Duel_state.DUEL_ST_PREPARE;
m_pvp.idDuelOpp = idOpp;
m_pvp.iDuelTimeCnt = data != null && data.Length >= 8 ? BitConverter.ToInt32(data, 4) : 3000;
break;
case CommandID.HOST_DUEL_START:
m_pvp.iDuelState = Duel_state.DUEL_ST_INDUEL;
m_pvp.idDuelOpp = idOpp;
m_pvp.iDuelTimeCnt = 0;
// Origin: server must be notified of force-attack (PVP) so it accepts attacks on the duel opponent
NotifyServerForceAttack(true);
break;
case CommandID.DUEL_STOP:
case CommandID.DUEL_RESULT:
m_pvp.iDuelState = Duel_state.DUEL_ST_STOPPING;
m_pvp.iDuelTimeCnt = data != null && data.Length >= 8 ? BitConverter.ToInt32(data, 4) : 3000;
if (data != null && data.Length >= 12)
m_pvp.iDuelRlt = BitConverter.ToInt32(data, 8);
NotifyServerForceAttack(false);
break;
case CommandID.DUEL_CANCEL:
case CommandID.DUEL_REJECT_REQUEST:
m_pvp.iDuelState = Duel_state.DUEL_ST_NONE;
m_pvp.idDuelOpp = 0;
m_pvp.iDuelTimeCnt = 0;
NotifyServerForceAttack(false);
break;
}
}
/// <summary>Called when MSG_PM_PLAYERDUELOPT (229) is received; server may send duel start to both participants. If host is one of the two ids, set duel state.</summary>
public void OnMsgPlayerDuelStart(byte[] data)
{
if (data == null || data.Length < 8) return;
int id1 = BitConverter.ToInt32(data, 0);
int id2 = BitConverter.ToInt32(data, 4);
int cid = m_PlayerInfo.cid;
if (cid == id1)
{
m_pvp.iDuelState = Duel_state.DUEL_ST_INDUEL;
m_pvp.idDuelOpp = id2;
m_pvp.iDuelTimeCnt = 0;
NotifyServerForceAttack(true);
}
else if (cid == id2)
{
m_pvp.iDuelState = Duel_state.DUEL_ST_INDUEL;
m_pvp.idDuelOpp = id1;
m_pvp.iDuelTimeCnt = 0;
NotifyServerForceAttack(true);
}
}
/// <summary>Origin: notify server of force-attack (PVP) state so it accepts/rejects attacks on players. Call when duel starts (true) or ends (false).</summary>
private void NotifyServerForceAttack(bool bForceAttack)
{
byte refuseBless = EC_Utility.glb_BuildRefuseBLSMask();
UnityGameSession.c2s_SendCmdNotifyForceAttack(glb_BuildPVPMask(bForceAttack), refuseBless);
}
private void OnMsgContinueComboSkill(ECMSG Msg)
{
bool bMeleeing = ((int)Msg.dwParam1 == 1);
@@ -2604,10 +2675,50 @@ namespace BrewMonster
{
case CommandID.OWN_ITEM_INFO:
{
Debug.Log("[Inventory] OWN_ITEM_INFO received");
var data = Msg.dwParam1 as byte[];
int hostId = Convert.ToInt32(Msg.dwParam3);
LogInventoryPacket("OWN_ITEM_INFO", data, hostId);
// Parse and apply single-item update (e.g. after embed) so UI reflects new item info without relog
if (data != null && data.Length >= 22)
{
byte byPackage = data[0];
byte bySlot = data[1];
int tid = BitConverter.ToInt32(data, 2);
int expireDate = BitConverter.ToInt32(data, 6);
int state = BitConverter.ToInt32(data, 10);
uint count = BitConverter.ToUInt32(data, 14);
ushort crc = BitConverter.ToUInt16(data, 18);
ushort contentLength = BitConverter.ToUInt16(data, 20);
byte[] content = null;
if (contentLength > 0 && data.Length >= 22 + contentLength)
{
content = new byte[contentLength];
Buffer.BlockCopy(data, 22, content, 0, contentLength);
}
try
{
var item = EC_IvtrItem.CreateItem(tid, expireDate, (int)count);
item.Package = byPackage;
item.Slot = bySlot;
item.State = state;
item.Crc = crc;
item.Content = content;
var inv = GetInventory(byPackage);
if (inv != null)
{
inv.SetItem(bySlot, item);
if (byPackage == InventoryConst.IVTRTYPE_EQUIPPACK)
UpdateEquipSkins();
}
}
catch (Exception ex)
{
Debug.LogWarning($"[Inventory] OWN_ITEM_INFO apply failed: {ex.Message}");
}
}
break;
}
}
@@ -2979,6 +3090,7 @@ namespace BrewMonster
public override void SetUpPlayer()
{
base.SetUpPlayer();
m_iCID = (int)CECObject.Class_ID.OCID_HOSTPLAYER;
m_IncantCnt = new CECCounter();
m_IncantCnt.SetPeriod(1000);
@@ -3234,8 +3346,6 @@ namespace BrewMonster
//if (!EC_Game.GetGameRun().GetWorld().GetObject(idTarget, 1))
// return false;
bool bStartNewWork = false;
bool bUseAutoPF = false;
//CECPlayerWrapper* pWrapper = CECAutoPolicy::GetInstance().GetPlayerWrapper();
//if (CECAutoPolicy::GetInstance().IsAutoPolicyEnabled() && pWrapper.GetAttackError() >= 2)
@@ -3253,12 +3363,10 @@ namespace BrewMonster
return false; // Host is attacking the target
pWorkTrace = (CECHPWorkTrace)m_pWorkMan.CreateWork(CECHPWork.Host_work_ID.WORK_TRACEOBJECT);
bStartNewWork = true;
}
else if (m_pWorkMan.CanStartWork(CECHPWork.Host_work_ID.WORK_TRACEOBJECT))
{
pWorkTrace = (CECHPWorkTrace)m_pWorkMan.CreateWork(CECHPWork.Host_work_ID.WORK_TRACEOBJECT);
bStartNewWork = true;
}
if (pWorkTrace != null)
@@ -3268,14 +3376,20 @@ namespace BrewMonster
bUseAutoPF);
pWorkTrace.SetMoveCloseFlag(bMoreClose);
if (bStartNewWork)
m_pWorkMan.StartWork_p1(pWorkTrace);
// Always start/re-start so trace runs (reuse path only updated target; work may be queued not current)
m_pWorkMan.StartWork_p1(pWorkTrace);
return true;
}
return false;
}
/// <summary>Use host's m_pvp (we update it from S2C duel packets). Base IsInDuel() reads CECPlayer.m_pvp which is never set.</summary>
public new bool IsInDuel() { return m_pvp.iDuelState == Duel_state.DUEL_ST_INDUEL; }
/// <summary>Duel opponent character id when in duel; 0 otherwise. Used by HPWork so trace/melee send correct PVP mask.</summary>
public int GetDuelOpponentId() { return m_pvp.idDuelOpp; }
public int AttackableJudge(int idTarget, bool bForceAttack)
{
if (CannotAttack())
@@ -3368,19 +3482,22 @@ namespace BrewMonster
}
}
}
// TO DO: fix later
//else if (GPDataTypeHelper.ISPLAYERID(idTarget))
//{
// // Check duel at first
// if (m_pvp.iDuelState == Duel_state.DUEL_ST_INDUEL && m_pvp.idDuelOpp == idTarget)
// return 1;
// else if (m_pvp.iDuelState == Duel_state.DUEL_ST_STOPPING && m_pvp.idDuelOpp == idTarget)
// return 0;
else if (GPDataTypeHelper.ISPLAYERID(idTarget))
{
// Duel: can attack only duel opponent while in duel (origin: attack but not kill)
if (m_pvp.iDuelState == Duel_state.DUEL_ST_INDUEL && m_pvp.idDuelOpp == idTarget)
return 1;
if (m_pvp.iDuelState == Duel_state.DUEL_ST_STOPPING && m_pvp.idDuelOpp == idTarget)
return 0;
// // In sanctuary we cannot attack other players
// if (m_bInSanctuary)
// return 0;
// In sanctuary we cannot attack other players
if (m_bInSanctuary)
return 0;
// Other player: no attack when not in duel (full PVP/free PVP/battle camp can be ported later)
iRet = 0;
}
// TO DO: full PVP branch ported from origin (commented below)
// //ASSERT(pObject.GetClassID() == CECObject::OCID_ELSEPLAYER);
// EC_ElsePlayer pPlayer = (EC_ElsePlayer)pObject;
// ROLEBASICPROP bp = pPlayer.GetBasicProps();
@@ -4025,6 +4142,10 @@ namespace BrewMonster
if (!pSkill.ReadyToCast())
return false;
// Duel: so trace and CastSkill send correct PVP mask when touching duel opp
if (IsInDuel() && idCastTarget == m_pvp.idDuelOpp)
bForceAttack = true;
if (CECCastSkillWhenMove.Instance.IsSkillSupported(pSkill.GetSkillID(), this) &&
m_pWorkMan.IsMovingToPosition() &&
m_pWorkMan.CanCastSkillImmediately(pSkill.GetSkillID()))
@@ -4301,6 +4422,10 @@ namespace BrewMonster
}
}
// Duel: server accepts attack/skill on player only with PVP/force mask
if (IsInDuel() && idTarget == m_pvp.idDuelOpp)
bForceAttack = true;
//TODO: Check cast condition - method not yet implemented
int iRet = CheckSkillCastCondition(m_pPrepSkill);
if (iRet != 0)
@@ -4687,7 +4812,6 @@ namespace BrewMonster
public bool SelectTarget(int idTarget)
{
//BMLogger.LogError("HoangDev: HostPlayer SelectTarget");
bool bRet = false;
bool canDo = CanDo(ActionCanDo.CANDO_CHANGESELECT);
bool canselect = CanSelectTarget(idTarget);
@@ -4696,16 +4820,17 @@ namespace BrewMonster
bRet = true;
if (idTarget == 0)
{
//BMLogger.LogError("HoangDev: HostPlayer Unsetlect npc");
UnityGameSession.c2s_CmdUnselect();
m_idSelTarget = 0;
m_idUCSelTarget = 0;
}
else
{
//BMLogger.LogError("HoangDev: HostPlayer setlect npc");
UnityGameSession.c2s_CmdSelectTarget(idTarget);
m_idSelTarget = idTarget;
m_idUCSelTarget = idTarget;
}
}
return bRet;
}
@@ -6546,7 +6671,10 @@ namespace BrewMonster
MaxHealth = maxHealth;
IDNPC = idnpc;
}
};
}
/// <summary>Fired when selected target HUD should be hidden (deselect or switch).</summary>
public struct TargetHUDClearEvent { }
public struct GNDINFO
{
@@ -7696,6 +7824,9 @@ namespace BrewMonster
return false;
}
}
// Duel: server accepts skill on opponent only with PVP/force mask
if (IsInDuel() && m_idSelTarget == m_pvp.idDuelOpp)
bForceAttack = true;
int idCastTarget = m_idSelTarget;
int iTargetType = pSkill.GetTargetType();
+22 -2
View File
@@ -21,6 +21,7 @@ public class CECUIManager : MonoSingleton<CECUIManager>
[SerializeField] private int currentTargetNPCID;
CECGameUIMan gameUI;
AUIManager aUIManager;
AUIDialog _dlgPlayerOptions;
[SerializeField] private DialogScriptTableObject dialogResouce;
[SerializeField] private Canvas canvasDlg;
@@ -37,6 +38,7 @@ public class CECUIManager : MonoSingleton<CECUIManager>
base.Awake();
EventBus.Subscribe<CECHostPlayer.NPCINFO>(ShowUINPC);
EventBus.Subscribe<CECHostPlayer.TargetHUDClearEvent>(OnTargetHUDClear);
EventBus.Subscribe<NPCDiedEvent>(TryHideUINPC);
EventBus.Subscribe<MessageBoxEvent>(OnMessageBox);
@@ -61,6 +63,7 @@ public class CECUIManager : MonoSingleton<CECUIManager>
private void OnDestroy()
{
EventBus.Unsubscribe<CECHostPlayer.NPCINFO>(ShowUINPC);
EventBus.Unsubscribe<CECHostPlayer.TargetHUDClearEvent>(OnTargetHUDClear);
EventBus.Unsubscribe<NPCDiedEvent>(TryHideUINPC);
EventBus.Unsubscribe<MessageBoxEvent>(OnMessageBox);
}
@@ -68,10 +71,17 @@ public class CECUIManager : MonoSingleton<CECUIManager>
private void ShowUINPC(CECHostPlayer.NPCINFO obj)
{
npsUI.gameObject.SetActive(true);
npsUI.SetText($"{obj.CurrentHealth}/{obj.MaxHealth}", obj.Name, "");
npsUI.SetHealthImage((float)obj.CurrentHealth / (float)obj.MaxHealth);
string hpText = obj.MaxHealth > 0 ? $"{obj.CurrentHealth}/{obj.MaxHealth}" : "-/-";
npsUI.SetText(hpText, obj.Name ?? "", "");
float fill = obj.MaxHealth > 0 ? (float)obj.CurrentHealth / (float)obj.MaxHealth : 1f;
npsUI.SetHealthImage(fill);
currentTargetNPCID = obj.IDNPC;
}
private void OnTargetHUDClear(CECHostPlayer.TargetHUDClearEvent _)
{
npsUI.gameObject.SetActive(false);
}
public CDlgQuickBar GetCDlgQuickBar()
{
return m_pDlgQuickBar1;
@@ -394,6 +404,16 @@ public class CECUIManager : MonoSingleton<CECUIManager>
return gameUI;
}
/// <summary>Shows the player options menu for the given character. Requires a prefab with id "DlgPlayerOptions" in DialogScriptTableObject.</summary>
public void ShowPlayerOptionsDialog(int characterId)
{
if (characterId == 0 || canvasDlg == null) return;
var gui = GetInGameUIMan();
if (_dlgPlayerOptions == null)
_dlgPlayerOptions = gui.GetDialog("DlgPlayerOptions");
_dlgPlayerOptions?.ShowForPlayer(characterId);
}
/// <summary>
/// Get the current target NPC ID (same as stored from NPCINFO event)
/// </summary>