From 34dbaa4c0ac3c70fcd0431536c46b4cea6cf9d27 Mon Sep 17 00:00:00 2001
From: HungDK <>
Date: Wed, 25 Feb 2026 13:38:33 +0700
Subject: [PATCH] Fixing select target and id duel
---
.../Prefab/UI/PlayerOptionPopup.prefab | 2 +-
.../Scripts/Managers/EC_HPWorkTrace.cs | 12 +++++++++--
.../Scripts/Managers/EC_HostInputFilter.cs | 8 ++++++--
.../Scripts/Managers/EC_ManPlayer.cs | 13 ++++++------
.../CSNetwork/C2SCommand/C2SCommandFactory.cs | 8 ++++++--
.../Scripts/Network/CSNetwork/GPDataType.cs | 6 +++---
Assets/Scripts/CECHostPlayer.cs | 20 ++++++++++++++++++-
Assets/Scripts/CECUIManager.cs | 3 +++
8 files changed, 54 insertions(+), 18 deletions(-)
diff --git a/Assets/PerfectWorld/Prefab/UI/PlayerOptionPopup.prefab b/Assets/PerfectWorld/Prefab/UI/PlayerOptionPopup.prefab
index ba4ca439d2..61f908b24d 100644
--- a/Assets/PerfectWorld/Prefab/UI/PlayerOptionPopup.prefab
+++ b/Assets/PerfectWorld/Prefab/UI/PlayerOptionPopup.prefab
@@ -1208,7 +1208,7 @@ MonoBehaviour:
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
- m_text: Invite
+ m_text: "Th\xE1ch \u0111\u1EA5u\n"
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: 369c2e14814cc9a4b8e3ad4e37769134, type: 2}
m_sharedMaterial: {fileID: 9092487103257209053, guid: 369c2e14814cc9a4b8e3ad4e37769134, type: 2}
diff --git a/Assets/PerfectWorld/Scripts/Managers/EC_HPWorkTrace.cs b/Assets/PerfectWorld/Scripts/Managers/EC_HPWorkTrace.cs
index 652d2799c3..fdd5b8c8f9 100644
--- a/Assets/PerfectWorld/Scripts/Managers/EC_HPWorkTrace.cs
+++ b/Assets/PerfectWorld/Scripts/Managers/EC_HPWorkTrace.cs
@@ -262,10 +262,14 @@ namespace BrewMonster
// 为了保证追踪到目标后能正常攻击,这里在必要时主动选中目标。
// Server select-target ack can arrive later than the trace touch moment, which would
// make this block forever. Ensure we select the traced target before attacking.
+ // In duel: don't overwrite selection if player explicitly chose another target.
+ bool duelPlayerChoseOther = m_pHost.IsInDuel() && m_pHost.GetDuelOpponentId() == m_iObjectId
+ && m_pHost.m_idSelTarget != 0 && m_pHost.m_idSelTarget != m_iObjectId;
if (m_iObjectId != m_pHost.m_idSelTarget)
{
UnityGameSession.c2s_CmdSelectTarget(m_iObjectId);
- m_pHost.m_idSelTarget = m_iObjectId;
+ if (!duelPlayerChoseOther)
+ m_pHost.m_idSelTarget = m_iObjectId;
}
if (m_pHost.AttackableJudge(m_iObjectId, m_bForceAttack) == 1)
{
@@ -381,10 +385,14 @@ namespace BrewMonster
{
// 同 NPC:追踪触碰可能早于服务器选中目标同步,需主动选中以避免攻击不触发。
// Same as NPC: trace touch can happen before server selection ack; proactively select.
+ // In duel: don't overwrite selection if player explicitly chose another target.
+ bool duelPlayerChoseOther = m_pHost.IsInDuel() && m_pHost.GetDuelOpponentId() == m_iObjectId
+ && m_pHost.m_idSelTarget != 0 && m_pHost.m_idSelTarget != m_iObjectId;
if (m_iObjectId != m_pHost.m_idSelTarget)
{
UnityGameSession.c2s_CmdSelectTarget(m_iObjectId);
- m_pHost.m_idSelTarget = m_iObjectId;
+ if (!duelPlayerChoseOther)
+ m_pHost.m_idSelTarget = m_iObjectId;
}
// Duel: always send PVP/force mask when target is duel opponent so server accepts the attack
bool bUseForceAttack = m_bForceAttack || (m_pHost.IsInDuel() && m_iObjectId == m_pHost.GetDuelOpponentId());
diff --git a/Assets/PerfectWorld/Scripts/Managers/EC_HostInputFilter.cs b/Assets/PerfectWorld/Scripts/Managers/EC_HostInputFilter.cs
index 3177d02b80..89884d340e 100644
--- a/Assets/PerfectWorld/Scripts/Managers/EC_HostInputFilter.cs
+++ b/Assets/PerfectWorld/Scripts/Managers/EC_HostInputFilter.cs
@@ -407,8 +407,10 @@ namespace BrewMonster
// Trace a object / Trace a object
if (iTraceReason == CECHPWorkTrace.Trace_reason.TRACE_ATTACK)
{
- // So CanDo(CANDO_MELEE) passes: it requires m_idSelTarget set to the attack target
- m_idSelTarget = idTraceTarget;
+ // So CanDo(CANDO_MELEE) passes and target HUD updates (e.g. duel opponent)
+ SetSelectedTarget(idTraceTarget);
+ // Sync selected target to server so attack is not rejected (wrong target / too far)
+ UnityGameSession.c2s_CmdSelectTarget(idTraceTarget);
if (!CanDo(ActionCanDo.CANDO_MELEE))
return;
// When attacking duel opponent, send force/PVP so server accepts (origin: duel = attack but not kill)
@@ -448,6 +450,8 @@ namespace BrewMonster
if (idSelTarget != 0 && m_idSelTarget != idSelTarget)
{
m_idUCSelTarget = idSelTarget;
+ // Update local selection and HUD first so first click changes target (e.g. in duel); then sync to server
+ SetSelectedTarget(m_idUCSelTarget);
SelectTarget(m_idUCSelTarget);
}
}
diff --git a/Assets/PerfectWorld/Scripts/Managers/EC_ManPlayer.cs b/Assets/PerfectWorld/Scripts/Managers/EC_ManPlayer.cs
index 9211f1736a..8c93446808 100644
--- a/Assets/PerfectWorld/Scripts/Managers/EC_ManPlayer.cs
+++ b/Assets/PerfectWorld/Scripts/Managers/EC_ManPlayer.cs
@@ -38,13 +38,12 @@ namespace PerfectWorld.Scripts.Managers
if ((int)Msg.dwMsg == EC_MsgDef.MSG_PM_DUELOPT && Convert.ToInt32(Msg.dwParam2) == CommandID.DUEL_RECV_REQUEST && Msg.dwParam1 is byte[] pDataBuf && pDataBuf.Length >= 4)
{
int inviterId = BitConverter.ToInt32(pDataBuf, 0);
- var gameUI = EC_Game.GetGameRun()?.GetUIManager()?.GetInGameUIMan();
- if (gameUI != null)
- {
- var dlg = gameUI.GetDialog("DlgDuelInvite");
- var showMethod = dlg?.GetType().GetMethod("ShowForDuelInvite", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance, null, new[] { typeof(int) }, null);
- showMethod?.Invoke(dlg, new object[] { inviterId });
- }
+ CECUIManager.Instance?.ShowMessageBox(
+ title: "",
+ message: "You have received a duel request. Do you accept?",
+ messageBoxType: MessageBoxType.BothYesNoButton,
+ onClickedYes: () => UnityGameSession.c2s_CmdDuelReply(true, inviterId),
+ onClickedNo: () => UnityGameSession.c2s_CmdDuelReply(false, inviterId));
}
CECGameRun.Instance.GetHostPlayer().ProcessMessage(Msg);
}
diff --git a/Assets/PerfectWorld/Scripts/Network/CSNetwork/C2SCommand/C2SCommandFactory.cs b/Assets/PerfectWorld/Scripts/Network/CSNetwork/C2SCommand/C2SCommandFactory.cs
index f0440f4937..4ed0b279d7 100644
--- a/Assets/PerfectWorld/Scripts/Network/CSNetwork/C2SCommand/C2SCommandFactory.cs
+++ b/Assets/PerfectWorld/Scripts/Network/CSNetwork/C2SCommand/C2SCommandFactory.cs
@@ -824,10 +824,14 @@ namespace CSNetwork.C2SCommand
return SerializeCommand(CommandID.DUEL_REQUEST, new cmd_team_new_member { idMember = idTarget });
}
- /// C2S: accept (true) or reject (false) duel from inviter.
+ /// C2S: accept (true) or reject (false) duel from inviter. Origin: [cmd][who:4][param:4] — who=inviter id, param=0 accept / non-zero reject.
public static Octets CreateDuelReplyCommand(bool accept, int idInviter)
{
- return SerializeCommand(CommandID.DUEL_REPLY, new cmd_duel_reply { accept = accept ? 1 : 0, idInviter = idInviter });
+ var octets = new Octets();
+ WriteBasicValue(octets, (ushort)CommandID.DUEL_REPLY);
+ WriteBasicValue(octets, idInviter); // who
+ WriteBasicValue(octets, accept ? 0 : 1); // param: 0 = accept, non-zero = reject reason
+ return octets;
}
public static Octets CreateTeamKickMemberCommand(int idMember)
diff --git a/Assets/PerfectWorld/Scripts/Network/CSNetwork/GPDataType.cs b/Assets/PerfectWorld/Scripts/Network/CSNetwork/GPDataType.cs
index af25b9ef28..c9c56592ed 100644
--- a/Assets/PerfectWorld/Scripts/Network/CSNetwork/GPDataType.cs
+++ b/Assets/PerfectWorld/Scripts/Network/CSNetwork/GPDataType.cs
@@ -2188,12 +2188,12 @@ namespace CSNetwork.GPDataType
public int idMember;
}
- /// C2S duel reply: accept (1) / reject (0) and inviter character id.
+ /// C2S duel reply (origin: who = inviter id, param = 0 accept / non-zero reject reason).
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct cmd_duel_reply
{
- public int accept; // 1 = accept, 0 = reject
- public int idInviter;
+ public int who; // inviter character id (who sent the duel request)
+ public int param; // 0 = accept (同意), non-zero = reject reason (拒绝原因)
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
diff --git a/Assets/Scripts/CECHostPlayer.cs b/Assets/Scripts/CECHostPlayer.cs
index 2db325eeeb..2a1a02921f 100644
--- a/Assets/Scripts/CECHostPlayer.cs
+++ b/Assets/Scripts/CECHostPlayer.cs
@@ -1105,6 +1105,9 @@ namespace BrewMonster
{
var data = (byte[])Msg.dwParam1;
cmd_select_target pCmd = GPDataTypeHelper.FromBytes(data);
+ // In duel: don't let server force selection back to duel opponent when player chose another target
+ if (IsInDuel() && pCmd.idTarget == m_pvp.idDuelOpp && m_idSelTarget != 0 && m_idSelTarget != m_pvp.idDuelOpp)
+ return;
m_idSelTarget = pCmd.idTarget;
m_idUCSelTarget = 0;
}
@@ -1894,7 +1897,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);
@@ -2373,7 +2375,23 @@ namespace BrewMonster
}
public void SetSelectedTarget(int id)
{
+ if (m_idSelTarget != id)
+ EventBus.Publish(new CECHostPlayer.TargetHUDClearEvent());
m_idSelTarget = id;
+ // In duel, when player selects a different target, cancel trace work so it doesn't overwrite selection on touch
+ if (IsInDuel() && id != 0 && id != m_pvp.idDuelOpp)
+ m_pWorkMan?.FinishRunningWork(CECHPWork.Host_work_ID.WORK_TRACEOBJECT);
+ // When selecting another player, publish NPCINFO so target HUD shows (server doesn't resend base info on select)
+ if (id != 0 && id != GetCharacterID() && GPDataTypeHelper.ISPLAYERID(id))
+ {
+ var elsePlayer = EC_ManMessageMono.Instance?.GetECManPlayer?.GetPlayer(id) as EC_ElsePlayer;
+ if (elsePlayer != null && elsePlayer.m_bBaseInfoReady)
+ {
+ string name = elsePlayer.GetName();
+ if (!string.IsNullOrEmpty(name))
+ EventBus.Publish(new CECHostPlayer.NPCINFO(name, 100, 100, id));
+ }
+ }
}
public new int GetSelectedTarget()
diff --git a/Assets/Scripts/CECUIManager.cs b/Assets/Scripts/CECUIManager.cs
index 9e9715d792..a9c6906992 100644
--- a/Assets/Scripts/CECUIManager.cs
+++ b/Assets/Scripts/CECUIManager.cs
@@ -112,6 +112,9 @@ public class CECUIManager : MonoSingleton
private void ShowUINPC(CECHostPlayer.NPCINFO obj)
{
+ var host = EC_Game.GetGameRun()?.GetHostPlayer();
+ if (host != null && host.GetSelectedTarget() != obj.IDNPC)
+ return;
npsUI.gameObject.SetActive(true);
string hpText = obj.MaxHealth > 0 ? $"{obj.CurrentHealth}/{obj.MaxHealth}" : "-/-";
npsUI.SetText(hpText, obj.Name ?? "", "");