Files
test/Assets/Scripts/CECHostPlayer.cs
T
2026-04-15 15:33:35 +07:00

4250 lines
168 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using BrewMonster.Assets.PerfectWorld.Scripts.Players;
using BrewMonster.Managers;
using BrewMonster.Network;
using BrewMonster.PerfectWorld.Scripts.Vfx;
using BrewMonster.Scripts;
using BrewMonster.Scripts.Managers;
using BrewMonster.Scripts.Pet;
using BrewMonster.Scripts.Skills;
using BrewMonster.Scripts.World;
using BrewMonster.UI;
using CSNetwork;
using CSNetwork.GPDataType;
using CSNetwork.Protocols.RPCData;
using Cysharp.Threading.Tasks;
using ModelRenderer.Scripts.GameData;
using PerfectWorld.Scripts;
using PerfectWorld.Scripts.Managers;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using static BrewMonster.CECPlayer;
using cmd_select_target = CSNetwork.GPDataType.cmd_select_target;
using cmd_player_chgshape = CSNetwork.GPDataType.cmd_player_chgshape;
using Host_work_ID = BrewMonster.Scripts.CECHPWork.Host_work_ID;
using ObjectCoords = System.Collections.Generic.List<CSNetwork.GPDataType.OBJECT_COORD>;
using Trace_reason = BrewMonster.CECHPWorkTrace.Trace_reason;
using System.Threading.Tasks;
namespace BrewMonster
{
public partial class CECHostPlayer : CECPlayer
{
[SerializeField] private CharacterController controller;
[SerializeField] private Joystick joystick;
[SerializeField] private Button btnJump;
[SerializeField] private Button btnRun;
public CECHostMove m_MoveCtrl;
private CECHPWorkMan m_pWorkMan; // Host work manager
private uint m_dwLIES; // Logic-influence extend states
private FACTION_FORTRESS_ENTER m_fortressEnter; // ½øÈë»ùµØÐÅÏ¢
//private PVPINFO m_pvp; // pvp information
//private bool m_bInSanctuary = false; // true, player is in sanctuary
//private int m_idFaction = 0; // ID of player's faction
public bool m_bPrepareFight = false; // true, prepare to fight
private int m_iJumpCount = 0;
private bool m_bJumpInWater = false;
public A3DVECTOR3 m_vVelocity; // Velocity
bool m_bChangingFace; // true, host is changing face
private int m_iRoleCreateTime;
private int m_iRoleLastLoginTime; // Role last login time
private int m_iAccountTotalCash;
private CECPetCorral m_pPetCorral;
private List<CECObject> m_aTabSels = new List<CECObject>();
private int m_lastAutoSelectNearestId = 0;
private List<CECSkill> m_aPtSkills = new List<CECSkill>();
private List<CECSkill> m_aPsSkills = new List<CECSkill>();
private List<CECSkill> m_aEquipSkills = new List<CECSkill>();
private List<CECSkill> m_aGoblinSkills = new List<CECSkill>();
#if UNITY_EDITOR
[SerializeField] private int m_startingSkillID = 0; // Starting skill ID for H key shortcut (0 = use cycling, >0 = use specific ID)
private int m_currentSkillCycleIndex = 0;
#endif
private bool m_bEnterGame;
public CECSkill m_pPrepSkill;
private float playerSpeed = 5.0f;
private float jumpHeight = 1.5f;
private float gravityValue = -9.81f;
private StateAnim stateAnim = StateAnim.Idle;
private Vector3 playerVelocity;
private bool isGrounded = false;
private bool isRun = false;
private Vector3 m_vLastSevPos;
public CDR_INFO m_CDRInfo;
public GNDINFO m_GndInfo;
private int m_idUCSelTarget; // Uncertificately selected object's ID
public float m_fVertSpeed = 0f;
int m_idSevNPC = 0; // Current service NPC
bool m_bTalkWithNPC = false; // true, is talking with NPC
List<ushort> m_aWayPoints = new List<ushort>(); // Active way points
bool m_bIsInKingService = false; // ÊÇ·ñÕýÔÚ½øÐйúÍõ·þÎñ²Ù×÷
CECActionSwitcherBase m_pActionSwitcher;
private int m_iWorldContribution;
private int m_iWorldContributionSpend;
private bool m_bSpellDSkill;
private CECSkill m_pTargetItemSkill; // Target item skill
public CECComboSkill m_pComboSkill; // Combo skill
public A3DVECTOR3 m_vAccel; // Accelerate\
public byte m_RealmLevel;
REINCARNATION_TOME m_ReincarnationTome; // תÉú
/// <summary>Used by <see cref="CECPlayer.BubbleText"/> for fight-channel EXP strings (book vs normal).</summary>
internal bool IsReincarnationTomeActive => m_ReincarnationTome.tome_active != 0;
public bool m_bRushFly = false; // true, in rush fly mode
private CECCounter m_IncantCnt;
private bool m_bMelee;
private int MAX_JUMP_COUNT = 2;
bool m_bUsingTrashBox = false; // Whether being using trash box
private float m_fPrayDistancePlus;
private bool m_bInRebuildPet = false; // Whether rebuilding pet / 是否正在重建宠物
private uint m_dwGMFlags = 0; // GM flags / GM标志
private A3DVECTOR3 g_vOrigin = new A3DVECTOR3(0f);
private float EC_SLOPE_Y = 0.5f;
int m_iOldWalkMode = Move_Mode.MOVE_STAND; // Copy of work mode
public uint m_dwMoveRelDir = 0; // Move relative direction flags
public ON_AIR_CDR_INFO m_AirCDRInfo;
public ushort m_wMoveStamp = 0;
private CECCounter m_GatherCnt; // Gather counter
Dictionary<int, COOLTIME> m_skillCoolTime = new Dictionary<int, COOLTIME>();
COOLTIME[] m_aCoolTimes = new COOLTIME[(int)CoolTimeIndex.GP_CT_MAX]; // Cool times
CECCounter m_PetOptCnt = new CECCounter(); // Pet operation time counter
protected bool[] m_playerLimits = new bool[(int)PLAYER_LIMIT.PLAYER_LIMIT_MAX];
// תÉú´ÎÊý
byte m_ReincarnationCount = 0;
// Host config data version
const int HOSTCFG_VERSION = 11;
// Favorite auction version
const int FAVOR_AUCTION_VERSION = 1;
// ID of return-town skill
const int ID_RETURNTOWN_SKILL = 167;
// ID of summon player skill
const int ID_SUMMONPLAYER_SKILL = 1824;
private CECAutoTeam m_pAutoTeam;
private CECTeam m_pTeam; // Current team (null when not in team)
// ====== Ground cast config ======
[Header("Ground Cast")]
[Tooltip("Khoảng thêm ngoài skinWidth để SphereCast xuống (m ngắn)")]
[SerializeField]
private float extraGroundDistance = 1f;
[Tooltip("Bớt bán kính một chút để tránh tự va vào capsule (epsilon)")]
[SerializeField]
private float radiusEpsilon = 0.005f;
[Tooltip("Layer mặt đất")]
[SerializeField]
private LayerMask groundMask = 1 << 6;
[Tooltip("Layer mặt đất")]
[SerializeField]
private float slopeToleranceDeg = 2f;
// cache tùy chọn (không bắt buộc)
float ccRadius, ccSkin;
//RaycastHit lastGroundHit;
Camera mainCam;
Ray ray;
RaycastHit[] hits = new RaycastHit[5];
bool isDataAwaitToReady = false;
private BaseVfxObject m_pSelectedGFX;
private BaseVfxObject m_pHoverGFX;
// ── Movement sound (C++ equivalent: m_pCurMoveSnd) ──────────────────
private int _curMoveSndId = -1; // currently playing move-sound ID (-1 = none)
private int _moveSndUpdateCounter; // 3-tick throttle counter
// Cursor estimation optimization
private Vector2 m_lastMousePosition;
private float m_cursorUpdateTimer;
private const float CURSOR_UPDATE_INTERVAL = 0.05f; // 20 times per second instead of 60+
private UnityEngine.InputSystem.Mouse m_cachedMouse;
private UnityEngine.InputSystem.Keyboard m_cachedKeyboard;
int[] targetsCastSkill;
public bool IsChangingFace()
{
return m_bChangingFace;
}
// ===== Inventory packs (instance-based) =====
// 0 = normal pack, 1 = equip pack, 2 = task pack (see InventoryConst.IVTRTYPE_*)
private readonly EC_Inventory m_pPack = new EC_Inventory();
/// <summary>
/// m_pEquipPack is changed to m_equipInventory
/// </summary>
private readonly EC_Inventory m_pEquipPack = new EC_Inventory();
private readonly EC_Inventory m_pTaskPack = new EC_Inventory();
public EC_Inventory IvtrPack => m_pPack;
public EC_Inventory IvtrEquipPack => m_pEquipPack;
public EC_Inventory IvtrTaskPack => m_pTaskPack;
public Transform PointCam { get => pointCam; }
private void OnApplicationQuit()
{
if (m_pTaskInterface != null)
m_pTaskInterface.Despose();
}
public bool IsMeleeing()
{
return m_bMelee;
}
public CECAutoTeam GetAutoTeam()
{
return m_pAutoTeam;
}
public override CECTeam GetTeam()
{
return m_pTeam;
}
internal void SetTeam(CECTeam team)
{
m_pTeam = team;
}
public EC_Inventory GetPack()
{
return m_pPack;
}
public EC_Inventory GetTaskPack()
{
return m_pTaskPack;
}
public EC_Inventory GetEquipment()
{
return m_pEquipPack;
}
// Get work manager // 获取工作管理器
public CECHPWorkMan GetWorkMan()
{
return m_pWorkMan;
}
// Get navigate player // 获取导航玩家
public int GetWorldContribution() { return m_iWorldContribution; }
public int GetWorldContributionSpend() { return m_iWorldContributionSpend; }
private CECHostNavigatePlayer m_pNavigatePlayer = null;
public CECHostNavigatePlayer GetNavigatePlayer()
{
return m_pNavigatePlayer;
}
// Check if in force navigate state // 检查是否在强制导航状态
public bool IsInForceNavigateState()
{
CECHostNavigatePlayer pNavigatePlayer = GetNavigatePlayer();
if (pNavigatePlayer != null && pNavigatePlayer.GetNavigateCtrl() != null)
{
return pNavigatePlayer.GetNavigateCtrl().IsInForceNavigateState();
}
return false;
}
// Handle navigation event // 处理导航事件
public void OnNaviageEvent(int task, int e)
{
UnityEngine.Debug.Log($"[CECHostPlayer] OnNaviageEvent: Task={task}, Event={e} ({(BrewMonster.Scripts.CECNavigateCtrl.NavigateEvent)e})");
if (e == (int)CECNavigateCtrl.NavigateEvent.EM_PREPARE)
{
CreateNavigatePlayer();
}
if (m_pNavigatePlayer != null)
{
m_pNavigatePlayer.OnNavigateEvent(task, e);
}
if(e == (int)CECNavigateCtrl.NavigateEvent.EM_END)
{
ReleaseNavigatePlayer();
}
}
private void CreateNavigatePlayer()
{
ReleaseNavigatePlayer();
GameObject navigateGo = new GameObject("HostNavigatePlayer");
navigateGo.transform.SetParent(transform, false);
navigateGo.transform.localScale = Vector3.one;
navigateGo.AddComponent<PlayerVisual>();
CECHostNavigatePlayer navigatePlayer = navigateGo.AddComponent<CECHostNavigatePlayer>();
navigatePlayer.InitializeHost(this);
//navigatePlayer.LoadConfig();
m_pNavigatePlayer = navigatePlayer;
}
private void ReleaseNavigatePlayer()
{
if (m_pNavigatePlayer != null)
{
m_pNavigatePlayer.Release();
m_pNavigatePlayer = null;
}
}
public EC_Inventory GetInventory(byte byPackage)
{
switch (byPackage)
{
case InventoryConst.IVTRTYPE_PACK:
return m_pPack;
case InventoryConst.IVTRTYPE_EQUIPPACK:
return m_pEquipPack;
case InventoryConst.IVTRTYPE_TASKPACK:
return m_pTaskPack;
default:
return null;
}
}
private void Awake()
{
base.Awake();
m_MoveCtrl = new CECHostMove(this);
// Cache: không bắt buộc, nhưng gọn tay và ít gọi property lặp.
if (controller != null)
{
ccRadius = controller.radius;
ccSkin = controller.skinWidth;
}
// 任务状态检查节流(毫秒) // Throttle task status check (milliseconds)
// NOTE: CECCounter uses "Period" as threshold; SetCounter only sets current value.
// We want task checking roughly every 3 seconds, not every frame.
m_TaskCounter.SetPeriod(3000f);
m_TaskCounter.Reset(true); // trigger first check immediately
m_bTitleDataReady = false;
//playerTransform = transform;
m_GatherCnt ??= new CECCounter();
m_GatherCnt.SetPeriod(1000);
m_GatherCnt.Reset(true);
m_PetOptCnt.SetPeriod(1000);
m_PetOptCnt.Reset(true);
CreateInventories();
// run a process on background to keep track of task status.
TickTask().Forget();
}
public async Task<bool> LoadResources()
{
//BMLogger.LogError("HoangDev: CECHostPlayer::LoadResources");
RoleInfo RoleInfo = UnityGameSession.Instance.GetRoleInfo();
m_iProfession = RoleInfo.occupation;
m_iGender = RoleInfo.gender;
m_iRoleCreateTime = RoleInfo.create_time;
m_iRoleLastLoginTime = RoleInfo.lastlogin_time;
m_iAccountTotalCash = RoleInfo.cash_add;
EC_Game.GetGameRun().AddPlayerName(m_PlayerInfo.cid, m_strName);
if (!await LoadPlayerSkeleton(true))
{
BMLogger.LogError("HoangDev CECHostPlayer::LoadResources, Failed to load skeleton");
return false;
}
return true;
}
private void Start()
{
mainCam = Camera.main;
if (mainCam == null)
{
mainCam = FindFirstObjectByType<Camera>();
}
// btnJump.onClick.AddListener(HandleJump);
// Cache input devices for better performance
m_cachedMouse = UnityEngine.InputSystem.Mouse.current;
m_cachedKeyboard = UnityEngine.InputSystem.Keyboard.current;
}
protected override void Update()
{
base.Update();
if (!isDataAwaitToReady)
{
return;
}
// Always set the pointCam's world rotation value to Vector3.zero
// because rotating the character with the camera will cause a lagging effect.
if (pointCam != null)
{
pointCam.rotation = Quaternion.identity;
}
// Update cursor based on what's under mouse
EstimateCursor();
//Debug.Log($"(ulong)Time.deltaTime * 1000 {(ulong)(Time.deltaTime * 1000)}");
m_MoveCtrl.Tick((ulong)(Time.deltaTime * 1000));
// Nếu có thay đổi runtime, có thể lấy lại mỗi vài giây/Start nếu bạn thích:
// ccRadius = controller.radius; ccSkin = controller.skinWidth;
EstimateMoveEnv(GetPos());
OnKeyDown();
// Update timers
UpdateTimers(Time.deltaTime);
m_pWorkMan?.Tick(Time.deltaTime);
// Update GFXs
UpdateGFXs(Time.deltaTime);
// Update movement sound
UpdateMoveSound();
//m_dwMoveRelDir = 0;
m_fVertSpeed = 0.0f;
// Auto team / Automatic party grouping
// m_pAutoTeam.Tick(Time.deltaTime);
// TODO: Remove later
// UpdatePosWing();
}
private void JoystickRelease(JoystickRealeaseEvent joystickRealeaseEvent)
{
// _playerStateMachine.ChangeState(_idleState);
m_dwMoveRelDir = 0;
}
public bool GroundCheck(out RaycastHit hit)
{
float radius = controller.radius;
float skin = controller.skinWidth;
float height = controller.height;
// Tâm capsule theo world
Vector3 cWorld = transform.TransformPoint(controller.center);
float hemi = Mathf.Max(0f, (height * 0.5f) - radius);
// Hai điểm top/bottom của capsule nhân vật (đang đứng)
Vector3 pTop = cWorld + Vector3.up * hemi;
Vector3 pBottom = cWorld - Vector3.up * hemi;
// Ta tạo một "đoạn capsule ngắn" gần đáy để sweep xuống
// Nhấc đoạn bắt đầu lên 1 chút để không bắt đầu trong trạng thái giao nhau
Vector3 startBottom = pBottom + Vector3.up * (skin + 0.01f);
Vector3 startTop = startBottom + Vector3.up * (radius * 2f - 0.02f); // chiều cao đoạn ngắn ~2*radius
float castRadius = Mathf.Max(0f, radius - radiusEpsilon);
float castDistance = skin + extraGroundDistance; // quãng sweep ngắn
bool hitSomething = Physics.CapsuleCast(
startTop, startBottom, castRadius,
Vector3.down, out hit, castDistance,
groundMask, QueryTriggerInteraction.Ignore
);
if (!hitSomething) return false;
// Lọc theo slope limit
float maxSlope = controller.slopeLimit + slopeToleranceDeg;
float slope = Vector3.Angle(hit.normal, Vector3.up);
if (slope > maxSlope) return false;
return true;
}
private void HandleJump()
{
if (isGrounded)
{
playerVelocity.y = Mathf.Sqrt(jumpHeight * -2f * gravityValue);
}
}
public void ProcessMessage(in ECMSG Msg)
{
var msg = (int)Msg.dwMsg;
//Debug.LogError("HoangDev : ProcessMessageProcessMessageProcessMessage " + msg);
switch (msg)
{
case EC_MsgDef.MSG_HST_CORRECTPOS: OnMsgHstCorrectPos(Msg); break;
case EC_MsgDef.MSG_HST_GOTO: OnMsgHstGoto(Msg); break;
case EC_MsgDef.MSG_HST_IVTRINFO:
{
OnMsgHstIvtrInfo(Msg);
break;
}
case EC_MsgDef.MSG_HST_OWNITEMINFO:
{
OnMsgHstOwnItemInfo(Msg);
break;
}
case EC_MsgDef.MSG_HST_TASKDATA:
{
OnMsgHstTaskData(Msg).Forget();
//Debug.LogError("[Dat]- OnMsgHstTaskData");
break;
}
case EC_MsgDef.MSG_HST_ITEMOPERATION:
OnMsgHstItemOperation(Msg);
break;
case EC_MsgDef.MSG_HST_PICKUPITEM:
OnMsgHstPickupItem(Msg);
break;
case EC_MsgDef.MSG_HST_PURCHASEITEMS:
OnMsgHstPurchaseItems(Msg);
break;
case EC_MsgDef.MSG_HST_PRODUCEITEM:
OnMsgHstProduceItem(Msg);
break;
case EC_MsgDef.MSG_HST_SELTARGET:
OnMsgHstSelTarget(Msg); break;
case EC_MsgDef.MSG_HST_USEITEM:
OnMsgHstUseItem(Msg);
break;
case EC_MsgDef.MSG_HST_PICKUPMONEY:
OnMsgHstPickupMoney(Msg);
break;
case EC_MsgDef.MSG_HST_SPENDMONEY:
OnMsgHstSpendMoney(Msg);
break;
case EC_MsgDef.MSG_HST_ITEMTOMONEY:
OnMsgHstItemToMoney(Msg);
break;
case EC_MsgDef.MSG_HST_ATKRESULT: OnMsgHstAttackResult(Msg); break;
case EC_MsgDef.MSG_HST_ATTACKONCE: OnMsgHstAttackOnce(Msg); break;
case EC_MsgDef.MSG_HST_ATTACKED: OnMsgHstAttacked(Msg); break;
case EC_MsgDef.MSG_HST_HURTRESULT: OnMsgHstHurtResult(Msg); break;
case EC_MsgDef.MSG_HST_INFO00: OnMsgHstInfo00(Msg); break;
case EC_MsgDef.MSG_HST_NPCGREETING: OnMsgHstNPCGreeting(Msg); break;
case EC_MsgDef.MSG_HST_WAYPOINT: OnMsgHstWayPoint(Msg); break;
case EC_MsgDef.MSG_HST_SKILLDATA: OnMsgHstSkillData(Msg); break;
case EC_MsgDef.MSG_HST_DIED: OnMsgHstDied(Msg); break;
case EC_MsgDef.MSG_HST_STARTATTACK: OnMsgHstStartAttack(Msg); break;
case EC_MsgDef.MSG_HST_STOPATTACK: OnMsgHstStopAttack(Msg); break;
case EC_MsgDef.MSG_HST_SKILLRESULT: OnMsgHstSkillResult(Msg); break;
case EC_MsgDef.MSG_HST_SKILLATTACKED: OnMsgHstSkillAttacked(Msg); break;
case EC_MsgDef.MSG_PM_CASTSKILL: OnMsgPlayerCastSkill(Msg); break;
case EC_MsgDef.MSG_HST_SETCOOLTIME: OnMsgHstSetCoolTime(Msg); break;
case EC_MsgDef.MSG_PM_PLAYEREXTSTATE: OnMsgPlayerExtState(Msg); break;
case EC_MsgDef.MSG_PM_ENCHANTRESULT: OnMsgEnchantResult(Msg); break;
case EC_MsgDef.MSG_HST_LEARNSKILL: OnMsgHstLearnSkill(Msg); break;
case EC_MsgDef.MSG_HST_COMBO_SKILL_PREPARE: OnMsgComboSkillPrepare(Msg); break;
case EC_MsgDef.MSG_HST_CONTINUECOMBOSKILL: OnMsgContinueComboSkill(Msg); break;
case EC_MsgDef.MSG_HST_OWNEXTPROP: OnMsgHstExtProp(Msg); break;
case EC_MsgDef.MSG_PM_PLAYERDOEMOTE: OnMsgPlayerDoEmote(Msg); break;
case EC_MsgDef.MSG_HST_TARGETISFAR: OnMsgHstTargetIsFar(Msg); break;
case EC_MsgDef.MSG_PM_PLAYERGATHER: OnMsgPlayerGather(Msg); break;
case EC_MsgDef.MSG_HST_COOLTIMEDATA: OnMsgHstCoolTimeData(Msg); break;
case EC_MsgDef.MSG_HST_PRESSCANCEL: OnMsgHstPressCancel(Msg); break;
case EC_MsgDef.MSG_PM_PLAYERFLY: OnMsgPlayerFly(Msg); break;
case EC_MsgDef.MSG_HST_PETOPT: OnMsgHstPetOpt(Msg); break;
case EC_MsgDef.MSG_HST_SETPLAYERLIMIT: OnMsgHstSetPlayerLimit(Msg); break;
case EC_MsgDef.MSG_PM_PLAYERMOUNT: OnMsgPlayerMount(Msg); break;
case EC_MsgDef.MSG_HST_EMBEDITEM: OnMsgHstEmbedItem(Msg); break;
case EC_MsgDef.MSG_HST_CLEARTESSERA: OnMsgHstClearTessera(Msg); break;
case EC_MsgDef.MSG_HST_JOINTEAM: OnMsgHstJoinTeam(Msg); break;
case EC_MsgDef.MSG_HST_LEAVETEAM: OnMsgHstLeaveTeam(Msg); break;
case EC_MsgDef.MSG_HST_NEWTEAMMEM: OnMsgHstNewTeamMem(Msg); break;
case EC_MsgDef.MSG_HST_TEAMINVITE: OnMsgHstTeamInvite(Msg); break;
case EC_MsgDef.MSG_HST_TEAMMEMBERDATA: OnMsgHstTeamMemberData(Msg); break;
case EC_MsgDef.MSG_PM_DUELOPT: OnMsgHstDuelOpt(Msg); break;
case EC_MsgDef.MSG_PM_PLAYERCHGSHAPE : OnMsgPlayerChgShape(Msg); break;
case EC_MsgDef.MSG_HST_SETMOVESTAMP: OnMsgHstSetMoveStamp(Msg); break;
case EC_MsgDef.MSG_HST_SANCTUARY: OnMsgHstSanctuary(Msg); break;
case EC_MsgDef.MSG_HST_RECEIVEEXP: OnMsgHstReceiveExp(Msg); break;
default:
// Uncomment to debug unhandled messages
// Debug.LogWarning($"[CECHostPlayer] ProcessMessage: Unhandled message {msg}");
break;
}
/*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);
}
}*/
}
private void OnMsgHstPickupMoney(ECMSG msg)
{
var data = msg.dwParam1 as byte[];
if (data == null || data.Length == 0)
return;
cmd_pickup_money pCmd = GPDataTypeHelper.FromBytes<cmd_pickup_money>(data);
AddMoneyAmount(pCmd.amount);
CECGameRun pGameRun = EC_Game.GetGameRun();
pGameRun.AddFixedMessage((int)FixedMsg.FIXMSG_PICKUPMONEY, pCmd.amount);
BubbleText((int)BubbleTextType.BUBBLE_MONEY, (uint)pCmd.amount);
}
private void OnMsgHstSpendMoney(ECMSG msg)
{
var data = msg.dwParam1 as byte[];
if (data == null || data.Length == 0)
return;
cmd_spend_money pCmd = GPDataTypeHelper.FromBytes<cmd_spend_money>(data);
AddMoneyAmount(-(int)pCmd.cost);
}
/// <summary>
/// Sell to NPC: server sends ITEM_TO_MONEY. Removes sold items from pack and adds gained money.
/// Payload layout differs across server builds, so we parse defensively.
/// </summary>
private void OnMsgHstItemToMoney(ECMSG Msg)
{
var data = Msg.dwParam1 as byte[];
if (data == null || data.Length < 4)
return;
// Two observed/expected formats:
// A) [u32 itemCount] + itemCount * npc_sell_item(16 bytes)
// B) [u32 moneyGain] [u32 itemCount] + itemCount * npc_sell_item(16 bytes)
uint a = BitConverter.ToUInt32(data, 0);
int offset = 0;
uint moneyGain = 0;
uint itemCount = 0;
const int itemSize = 16; // npc_sell_item: tid(4) index(4) count(4) price(4)
bool formatA = ((data.Length - 4) >= 0) && (((data.Length - 4) % itemSize) == 0) && (a == (uint)((data.Length - 4) / itemSize));
if (formatA)
{
itemCount = a;
offset = 4;
}
else if (data.Length >= 8)
{
uint b = BitConverter.ToUInt32(data, 4);
bool formatB = ((data.Length - 8) >= 0) && (((data.Length - 8) % itemSize) == 0) && (b == (uint)((data.Length - 8) / itemSize));
if (formatB)
{
moneyGain = a;
itemCount = b;
offset = 8;
}
}
// If we can't parse, fall back to server refresh.
if (itemCount == 0 || offset == 0)
{
UnityGameSession.RequestInventoryAsync(0, () =>
{
var ui = UnityEngine.Object.FindFirstObjectByType<EC_InventoryUI>();
ui?.RefreshAll();
});
return;
}
long computedGain = moneyGain;
var pPack = GetPack(EC_Inventory.Inventory_type.IVTRTYPE_PACK);
if (pPack == null)
return;
for (int i = 0; i < itemCount; i++)
{
int baseIndex = offset + i * itemSize;
if (baseIndex + itemSize > data.Length)
break;
int tid = BitConverter.ToInt32(data, baseIndex);
uint invIndex = BitConverter.ToUInt32(data, baseIndex + 4);
uint count = BitConverter.ToUInt32(data, baseIndex + 8);
int price = BitConverter.ToInt32(data, baseIndex + 12);
if (invIndex >= (uint)pPack.GetSize())
continue;
if (count > 0)
pPack.RemoveItem((int)invIndex, (int)count);
// If server didn't include a total moneyGain, sum per-item prices.
if (moneyGain == 0 && price > 0)
computedGain += price;
var remain = pPack.GetItem((int)invIndex, false);
if (remain != null && remain.m_tid != tid)
UnityGameSession.c2s_CmdGetItemInfo(0, (byte)invIndex);
}
if (computedGain > 0)
AddMoneyAmount((int)Mathf.Min(int.MaxValue, computedGain));
var invUi = UnityEngine.Object.FindFirstObjectByType<EC_InventoryUI>();
invUi?.RefreshAll();
UpdateEquipSkins();
}
private bool IsInChariot()
{
CECGameRun pRun = EC_Game.GetGameRun();
if(pRun == null)
return false;
CECHostPlayer pHost = pRun.GetHostPlayer();
if (pHost && pHost.GetBattleInfo().IsChariotWar() && GetShapeType() == (int)PLAYERMODEL_TYPE.PLAYERMODEL_DUMMYTYPE2
&& GetDummyModel((int)PLAYERMODEL_TYPE.PLAYERMODEL_DUMMYTYPE2))
{
return true;
}
return false;
}
private BATTLEINFO m_BattleInfo;
private BATTLEINFO GetBattleInfo()
{
return m_BattleInfo;
}
private void NotifyUIUpdateTeam(bool showDialog = false)
{
//try
//{
// var ui = EC_Game.GetGameRun()?.GetUIManager()?.GetInGameUIMan();
// var ui = EC_Game.GetGameRun()?.GetUIManager()?.GetInGameUIMan();
// if (ui is CECGameUIMan gui)
// gui.UpdateTeam(false);
//}
//catch { }
var uiMan = CECUIManager.Instance?.GetInGameUIMan();
if (uiMan == null)
return;
// CECGameUIMan will forward to DlgTeamMain if it's open, otherwise do nothing.
var dlgTeamMain = uiMan.GetDialog("Win_TeamMain") as DlgTeamMain;
if (dlgTeamMain != null)
{
if (showDialog)
{
dlgTeamMain.ShowTeamDialog();
}
else
{
dlgTeamMain.UpdateTeamInfo();
}
}
else
{
BMLogger.LogWarning("[CECHostPlayer] NotifyUIUpdateTeam: Failed to show DlgTeamMain");
}
}
/// <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_RECV_REQUEST:
// Duel invite: show accept/reject popup (origin MSG_PM_DUELOPT with command DUEL_RECV_REQUEST = 214)
if (idOpp != 0)
{
// CECUIManager.Instance?.ShowMessageBox(
// title: "",
// message: "Bạn vừa nhận được lời thách đấu. Bạn có chấp nhận không?",
// messageBoxType: MessageBoxType.BothYesNoButton,
// onClickedYes: () => UnityGameSession.c2s_CmdDuelReply(true, idOpp),
// onClickedNo: () => UnityGameSession.c2s_CmdDuelReply(false, idOpp));
CECUIManager.Instance?.ShowMessageBoxYesAndNo("",
"Bạn vừa nhận được lời thách đấu. Bạn có chấp nhận không?", null,
()=>UnityGameSession.c2s_CmdDuelReply(true, idOpp),
() => UnityGameSession.c2s_CmdDuelReply(false, idOpp));
}
break;
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>Host received a team invite (MSG_HST_TEAMINVITE). Payload is cmd_team_leader_invite: idLeader, seq, pickFlag. Shows accept/reject message box and sends reply.</summary>
private void OnMsgHstTeamInvite(ECMSG Msg)
{
byte[] data = Msg.dwParam1 as byte[];
int idLeader = 0;
int team_seq = 0;
if (data != null && data.Length >= 8)
{
idLeader = BitConverter.ToInt32(data, 0);
team_seq = BitConverter.ToInt32(data, 4);
}
else if (data != null && data.Length >= 4)
{
idLeader = BitConverter.ToInt32(data, 0);
}
if (idLeader == 0)
return;
int seqCapture = team_seq;
// CECUIManager.Instance?.ShowMessageBox(
// title: "",
// message: "Bạn đã nhận được lời mời tham gia tổ đội. Bạn có chấp nhận không?",
// messageBoxType: MessageBoxType.BothYesNoButton,
// onClickedYes: () => UnityGameSession.c2s_CmdTeamAgreeInvite(idLeader, seqCapture),
// onClickedNo: () => UnityGameSession.c2s_CmdTeamRejectInvite(idLeader));
CECUIManager.Instance?.ShowMessageBoxYesAndNo("", "Bạn đã nhận được lời mời tham gia tổ đội. Bạn có chấp nhận không?",null,
()=>UnityGameSession.c2s_CmdTeamAgreeInvite(idLeader, seqCapture),
() => UnityGameSession.c2s_CmdTeamRejectInvite(idLeader));
}
/// <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);
}
}
#if UNITY_EDITOR
/// <summary>
/// Cycles through learned skills by removing all shortcuts and adding 2 new skills to slots 0 and 1.
/// If m_startingSkillID is set (>0), uses that specific skill ID and the next one (ID+1).
/// Otherwise, cycles through all learned skills in pairs.
/// </summary>
#endif
public bool HostIsReady()
{
return m_bEnterGame;
}
void SetLevel2(int level2, bool bFirstTime)
{
int lastLevel2 = m_BasicProps.iLevel2;
m_BasicProps.iLevel2 = level2;
/*if (CanPlayTaoistEffect(lastLevel2, level2, bFirstTime)){
PlayTaoistEffect();
}*/
}
/* public override bool IsWorkMoveRunning()
{
return m_pWorkMan.IsMovingToPosition();
}*/
public bool IsPosCollideFree(A3DVECTOR3 vTargetPos)
{
bool bAvailable = (false);
while (true)
{
A3DVECTOR3 refake = new A3DVECTOR3();
float terrianHeight = CECGameRun.Instance.GetWorld().GetTerrainHeight(vTargetPos, ref refake);
if (terrianHeight > vTargetPos.y + 1E-4f)
break;
A3DVECTOR3 vExt = m_CDRInfo.vExtent;
env_trace_t trcInfo = default;
trcInfo.dwCheckFlag = EC_CDR.CDR_EVN.CDR_BRUSH;
trcInfo.vExt = vExt;
trcInfo.vStart = vTargetPos + EC_Utility.ToA3DVECTOR3(g_vAxisY) * vExt.y;
trcInfo.vDelta = new A3DVECTOR3(0);
if (EC_CDR.CollideWithEnv(ref trcInfo))
break;
bAvailable = true;
break;
}
return bAvailable;
}
private float CalcAABBOnCollidePos(A3DAABB aabbTarget)
{
// ¸ù¾Ý HostPlayer Óëij¶ÔÏó£¨ÈçNPC£© AABB£¬¼ÆËã AABB ³åÍ»ÁÙ½ç״̬ÏÂÁ½Õß¾àÀë (Compute the separation distance between host/player AABBs when they nearly collide)
// ·µ»ØÖµÓ¦Êʵ±Ôö¼ÓÒ»¸öÔöÁ¿£¬¼´Àë³åͻλÖÃÉÔԶЩ (Return value should keep a slightly safer distance)
float fAABBCollideDist = 0.0f;
A3DAABB aabbHost = m_aabb;
A3DVECTOR3 vHostHeight = new A3DVECTOR3(0.0f, aabbHost.Extents.y, 0.0f);
A3DVECTOR3 vHostRoot = aabbHost.Center - vHostHeight;
A3DVECTOR3 vTargetHeight = new A3DVECTOR3(0.0f, aabbTarget.Extents.y, 0.0f);
A3DVECTOR3 vTargetRoot = aabbTarget.Center - vTargetHeight;
A3DVECTOR3 vRootDir = vTargetRoot - vHostRoot;
float fRootDist = vRootDir.Normalize();
float fMinDist = Mathf.Min(Mathf.Min(aabbHost.Extents.x, aabbHost.Extents.y), aabbHost.Extents.z);
fMinDist = Mathf.Max(fMinDist, 0.01f);
if (fRootDist >= fMinDist)
{
A3DVECTOR3 vCenterDelta0 = aabbHost.Center - aabbTarget.Center;
A3DVECTOR3 vTargetExt = aabbTarget.Extents;
A3DVECTOR3 vHostExt = aabbHost.Extents;
A3DVECTOR3 vSumExt = vTargetExt + vHostExt;
A3DVECTOR3 t = new A3DVECTOR3(0.0f);
const float fZero = 0.001f;
if (Mathf.Abs(vRootDir.x) >= fZero)
{
float t1 = (vSumExt.x - vCenterDelta0.x) / vRootDir.x;
float t2 = (vSumExt.x + vCenterDelta0.x) / -vRootDir.x;
if (t1 >= fZero && t1 < fRootDist) t.x = t1;
if (t2 >= fZero && t2 < fRootDist && t2 > t.x) t.x = t2;
}
if (Mathf.Abs(vRootDir.y) >= fZero)
{
float t1 = (vSumExt.y - vCenterDelta0.y) / vRootDir.y;
float t2 = (vSumExt.y + vCenterDelta0.y) / -vRootDir.y;
if (t1 >= fZero && t1 < fRootDist) t.y = t1;
if (t2 >= fZero && t2 < fRootDist && t2 > t.y) t.y = t2;
}
if (Mathf.Abs(vRootDir.z) >= fZero)
{
float t1 = (vSumExt.z - vCenterDelta0.z) / vRootDir.z;
float t2 = (vSumExt.z + vCenterDelta0.z) / -vRootDir.z;
if (t1 >= fZero && t1 < fRootDist) t.z = t1;
if (t2 >= fZero && t2 < fRootDist && t2 > t.z) t.z = t2;
}
float fHostMove = 0.0f;
if (t.x > 0.0f) fHostMove = t.x;
if (t.y > 0.0f && t.y > fHostMove) fHostMove = t.y;
if (t.z > 0.0f && t.z > fHostMove) fHostMove = t.z;
if (fHostMove > 0.0f)
{
fAABBCollideDist = fRootDist - fHostMove;
}
}
return fAABBCollideDist;
}
private bool CalcCollideFreePos(A3DAABB aabbTarget, out A3DVECTOR3 vPos)
{
// ¸ù¾Ýµ±Ç° HostPlayer λÖÃÓë¸ø¶¨Ä¿±êµÄÅöײ°üΧºÐ aabbTarget£¬¼ÆËãÄ¿±ê¸½½ü(ÓëµØÐκÍ͹°ü)ÎÞ³åÍ»µÄλÖà (Find a terrain/brush free position near the target AABB)
// vPos ´æ·ÅÎÞ³åÍ»µÄλÖ㬿ÉÓÃÓÚ SetPos (vPos stores the safe position for SetPos)
bool bFound = false;
vPos = default;
A3DAABB aabbHost = m_aabb;
A3DVECTOR3 vHostHeight = new A3DVECTOR3(0.0f, aabbHost.Extents.y, 0.0f);
A3DVECTOR3 vHostRoot = aabbHost.Center - vHostHeight;
A3DVECTOR3 vTargetHeight = new A3DVECTOR3(0.0f, aabbTarget.Extents.y, 0.0f);
A3DVECTOR3 vTargetRoot = aabbTarget.Center - vTargetHeight;
A3DVECTOR3 vRootDir = vTargetRoot - vHostRoot;
float fRootDist = vRootDir.Normalize();
float fAABBCollideDist = CalcAABBOnCollidePos(aabbTarget);
if (fAABBCollideDist > 0.0f)
{
float[] fDeltaTests = { 0.001f, 0.1f };
foreach (float fDelta in fDeltaTests)
{
// ¿¼Âǵ½µØÐÎÆð·üµÈÒòËØ£¬Óëʵ¼Ê³åͻλÖÃÀ­¿ªÒ»¶¨¾àÀë²¢Öð¸ö²âÊÔ£¬ÒÔÔö´ó³É¹¦·µ»Ø¼¸ÂÊ (Offset slightly to improve success probability)
if (fRootDist > fAABBCollideDist + fDelta)
{
float fTestMoveDist = fRootDist - (fAABBCollideDist + fDelta);
A3DVECTOR3 vTestPos = vHostRoot + vRootDir * fTestMoveDist;
// ¸ù¾ÝµØÐκÍ͹°ü½øÐÐÐÞÕý¡¢²¢²âÊÔ¿ÉÓÃÐÔ (Adjust with terrain/brush constraints and verify)
vTestPos.y = ClampAboveGround(vTestPos);
if (IsPosCollideFree(vTestPos))
{
// ºÍ͹°üÎÞÅöײ£¬Ö±½Ó¿ÉÓà (No brush collision, use directly)
bFound = true;
vPos = vTestPos;
}
else
{
// ºÍ͹°üÓÐÅöײ£¬³¢ÊÔÔÚÊúÖ±·½ÏòÉϲéÕÒ (If still colliding, search vertically)
if (CalcVerticalCollideFreePos(vTestPos, out A3DVECTOR3 vTestPos2))
{
bFound = true;
vPos = vTestPos2;
}
}
if (bFound)
{
break;
}
}
}
}
return bFound;
}
private bool CalcBrushOnCollidePos(A3DVECTOR3 vTestPos, A3DVECTOR3 vDelta, A3DVECTOR3 vExtents,
out A3DVECTOR3 vPos, out bool bNoCollide)
{
// ·µ»Ø true£ºvPos ΪÎÞÅöײλÖã»Èô bNoCollide Ϊ true£¬Ôò vPos Ϊ vTestPos£»·ñÔòΪ¼ÆËã³öµÄλÖà (Return true with vPos pointing at a usable location; if bNoCollide==true the input position was already free)
// ·µ»Ø false£ºÒâζ×Å bStartSolid = true (Return false implies we started within solid geometry)
bool bFound = false;
A3DVECTOR3 vCenterHeight = new A3DVECTOR3(0.0f, vExtents.y, 0.0f);
env_trace_t trcInfo = new env_trace_t
{
dwCheckFlag = EC_CDR.CDR_EVN.CDR_BRUSH,
vExt = vExtents,
vStart = vTestPos + vCenterHeight,
vDelta = vDelta,
vTerStart = vTestPos + vCenterHeight,
vWatStart = vTestPos + vCenterHeight,
bWaterSolid = false
};
if (EC_CDR.CollideWithEnv(ref trcInfo))
{
bNoCollide = false;
if (!trcInfo.bStartSolid)
{
vPos = trcInfo.vStart + trcInfo.fFraction * trcInfo.vDelta - vCenterHeight;
bFound = true;
}
else
{
vPos = default;
}
}
else
{
vPos = vTestPos;
bNoCollide = true;
bFound = true;
}
if (!bFound)
{
vPos = default;
bNoCollide = false;
}
return bFound;
}
private bool CalcVerticalCollideFreePos(A3DVECTOR3 vRefPos, out A3DVECTOR3 vPos)
{
// ¼ÆËã vRefPos ¸½½üÊúÖ±·½ÏòÉÏÓë͹°ü¼°µØÐÎÎÞ³åÍ»µÄλÖà (Search along the vertical direction around vRefPos for a collision-free spot)
// vRefPos Ϊ HostPlayer foot λÖà (vRefPos corresponds to the host's foot)
bool bFound = false;
vPos = default;
CECWorld world = CECGameRun.Instance.GetWorld();
if (world == null)
{
return false;
}
while (true)
{
A3DAABB hostAabb = m_aabb;
A3DVECTOR3 vHostExts = hostAabb.Extents;
// ¹¹½¨ËõС°æ HostPlayer £¬ÏòÏÂѰÕÒ¿ÉÓÃλÖà (Use a shrinked collider to probe downward)
A3DVECTOR3 vShrinkExts = new A3DVECTOR3(vHostExts.x, vHostExts.y * 0.5f, vHostExts.z);
A3DVECTOR3 vShrinkPos = vRefPos + new A3DVECTOR3(0.0f, vHostExts.y * 0.5f, 0.0f);
A3DVECTOR3 vStartPos = vShrinkPos;
A3DVECTOR3 vVerticalDelta = new A3DVECTOR3(0.0f, -1.0f, 0.0f);
A3DVECTOR3 dummyNormal = default;
float terrainHeight = world.GetTerrainHeight(vStartPos, ref dummyNormal);
if (vStartPos.y < terrainHeight + 0.01f)
{
// ²âÊÔλÖÃÐè´¦ÓÚµØÐÎÒÔÉÏ (Candidate must be above the terrain)
break;
}
if (!CalcBrushOnCollidePos(vStartPos, vVerticalDelta, vShrinkExts, out A3DVECTOR3 vCandidate,
out bool bNoCollide))
{
// vStartPos ´¦ÓÚ͹°üÀï (Start position lies inside a brush)
break;
}
if (bNoCollide)
{
// ´Ó vStartPos µ½ vRefPos ¶¼ÎÞÅöײ£¬¿ÉÄÜÊÇ (No collision between vStartPos and vRefPos; possible cases)
// ÇéÐÎ1£ºvRefPos ´¦Í·¶¥ÓÐ͹°üµ¼ÖÂÅöײ£¨´Ó¶øµ÷Óô˺¯ÊýÐÞÕý£© (Case1: upper brush causes the issue)
// ÇéÐÎ2£º»òÕß vRefPos ±¾Éí¼´ÎÞÅöײ£¬Îóµ÷Óô˺¯Êý×öÖØ¸´¼ÆËã (Case2: vRefPos was already free)
// ÇéÐÎ3£º»òÆäËüδ¿¼ÂÇÇé¿ö (Case3: other edge cases)
// ³¢ÊԴӵײ¿Ïò vRefPos ʹÓÃԭʼ´óС HostPlayer ²éÕÒÎÞÅöײλÖã¬ÒÔ´¦ÀíÉÏÊöÇéÐÎ1 (Try again with the original extents to address case1)
vStartPos += vVerticalDelta;
vVerticalDelta = vRefPos - vStartPos;
if (!CalcBrushOnCollidePos(vStartPos, vVerticalDelta, vHostExts, out vCandidate, out bNoCollide))
{
break;
}
if (bNoCollide)
{
vCandidate = vRefPos;
}
// else vCandidate ÊÇËõС°æ HostPlayer ÎÞÅöײµÄλÖã¬Ò²ÊÇʵ¼Ê´óС HostPlayer ÐÞÕý vRefPos µÄλÖà (Otherwise the computed candidate already fixes the offset)
}
vCandidate.y = ClampAboveGround(vCandidate);
if (!IsPosCollideFree(vCandidate))
{
// λÖóåÍ» (Still interpenetrating)
break;
}
vPos = vCandidate;
bFound = true;
break;
}
return bFound;
}
private float ClampAboveGround(A3DVECTOR3 vPos)
{
// ½« vPos ÏÞÖÆµ½µØÃæÒÔÉÏ (Clamp vPos above the ground)
// ·ÉÐÐ״̬»òË®µ×ʱ£¬ÏÞÖÆÈËÎïÔÚË®ÃæÒÔÉÏ£¬ÀëË®Ãæ/µØÃæÒ»¶¨¾àÀ룻Èôµ÷Õûºó vPos ÊúÖ±ÍùÏÂÓÐ͹°ü£¬Ôò»¹»áÏÞÖÆµ½Àë͹°üÒ»¶¨¾àÀë (When flying/underwater we also keep distance from surfaces and nearby brushes)
// ×¢Ò⣺vPos ±»µ÷Õûºó£¬ÓпÉÄÜ´¦ÓÚ͹°üÖУ»Ðè¼ì²é·µ»Ø¸ß¶Èµ÷ÕûÖµ£¬ÒÔ±ÜÃâµ÷Õû¹ý´ó (Beware of large adjustments placing us back into brushes)
A3DVECTOR3 vTemp = new A3DVECTOR3(vPos);
CECWorld world = CECGameRun.Instance.GetWorld();
if (world == null)
{
return vTemp.y;
}
while (true)
{
A3DVECTOR3 dummyNormal = default;
float fTerrainHeight = world.GetTerrainHeight(vTemp, ref dummyNormal);
vTemp.y = EC_Utility.a_ClampFloor(vTemp.y, fTerrainHeight);
A3DAABB hostAabb = m_aabb;
A3DVECTOR3 vExts = hostAabb.Extents;
if (IsFlying())
{
float fAbove = m_MoveConst.fMinAirHei;
// Ïȱ£Ö¤ÀëµØÃæ/Ë®ÃæÒ»¶¨¸ß¶È (Keep some height over terrain/water)
float fWaterHeight = world.GetWaterHeight(vTemp);
float fSurface = Mathf.Max(fTerrainHeight, fWaterHeight);
vTemp.y = EC_Utility.a_ClampFloor(vTemp.y, fSurface + fAbove);
// ÔÙ³¢ÊÔÀë͹°üÒ»¶¨¸ß¶È (Then test against brushes)
if (!CalcBrushOnCollidePos(vTemp, GPDataTypeHelper.g_vAxisY * (fSurface - vTemp.y), vExts,
out A3DVECTOR3 vHitPos, out bool bNoCollide) || bNoCollide)
{
break;
}
// ÉèÖÃÀë͹°üÒ»¶¨¸ß¶È (Clamp to stay above brushes)
vTemp.y = EC_Utility.a_ClampFloor(vTemp.y, vHitPos.y + fAbove);
break;
}
float fWaterHeightLow = world.GetWaterHeight(vTemp);
if (fWaterHeightLow > fTerrainHeight && vTemp.y + vExts.y < fWaterHeightLow - m_MoveConst.fWaterSurf)
{
// Ë®µ× (Underwater)
float fAbove = m_MoveConst.fMinWaterHei;
// Ïȱ£Ö¤ÀëË®µ×Ò»¶¨¾àÀë (Keep distance from the riverbed)
vTemp.y = EC_Utility.a_ClampFloor(vTemp.y, fTerrainHeight + fAbove);
// ÔÙ³¢ÊÔÀë͹°üÒ»¶¨¸ß¶È (Then ensure clearance from brushes)
if (!CalcBrushOnCollidePos(vTemp, GPDataTypeHelper.g_vAxisY * (fTerrainHeight - vTemp.y), vExts,
out A3DVECTOR3 vHitPos, out bool bNoCollide) || bNoCollide)
{
break;
}
// ÉèÖÃÀë͹°üÒ»¶¨¸ß¶È (Clamp relative to brush hit position)
vTemp.y = EC_Utility.a_ClampFloor(vTemp.y, vHitPos.y + fAbove);
break;
}
// µØÃæÉÏ£¬ÎÞÐèÔÙ´¦Àí (Already safe on ground)
break;
}
return vTemp.y;
}
public void HandleRevive(short sReviveType, A3DVECTOR3 pos)
{
// Move to revive position and play revive animation
PlayAction((int)PLAYER_ACTION_TYPE.ACT_REVIVE);
// Clear any running dead work if exists
m_pWorkMan?.FinishRunningWork(CECHPWork.Host_work_ID.WORK_DEAD);
// Clear corpse state so player is alive again
m_dwStates &= ~(uint)PlayerNPCState.GP_STATE_CORPSE;
UnityGameSession.c2s_CmdGetAllData(true, true, false);
UnityGameSession.c2s_CmdSendEnterPKPrecinct();
UnityGameSession.RequesrQueryPlayerCash();
}
// Message MSG_HST_SELTARGET handler
void OnMsgHstSelTarget(ECMSG Msg)
{
//BMLogger.LogError("HoangDev: OnMsgHstSelTarget");
if (Convert.ToInt32(Msg.dwParam2) == CommandID.SELECT_TARGET)
{
var data = (byte[])Msg.dwParam1;
cmd_select_target pCmd = GPDataTypeHelper.FromBytes<cmd_select_target>(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;
}
else if (Convert.ToInt32(Msg.dwParam2) == CommandID.UNSELECT)
{
m_idSelTarget = 0;
}
}
public override void SetUpPlayer()
{
base.SetUpPlayer();
m_iCID = (int)CECObject.Class_ID.OCID_HOSTPLAYER;
m_IncantCnt = new CECCounter();
m_IncantCnt.SetPeriod(1000);
m_IncantCnt.Reset(true);
m_bEnterGame = false;
}
public async Task InitCharacter(cmd_self_info_1 role)
{
SetUpPlayer();
m_dwStates = (uint)role.state;
controller = GetComponent<CharacterController>();
if (!controller)
{
BMLogger.LogError("HostPlayer InitCharacter no CharacterController");
}
//if (role.name != null && role.name.ByteArray != null)
//{
// roleName = Encoding.UTF8.GetString(role.name.ByteArray, 0, role.name.Length);
//}
SetPlayerInfor(new INFO(role.cid, role.crc_e, role.crc_c));
await LoadResources();
isDataAwaitToReady = true;
Vector3 pos = new Vector3(role.pos.x, role.pos.y, role.pos.z);
string roleName = Encoding.Unicode.GetString(UnityGameSession.Instance.GetRoleInfo().name.ByteArray);
SetPlayerName(roleName);
if (txtName != null) txtName.text = roleName;
EventBus.Publish(new InfoHostPlayer(roleName));
playerTransform.position = pos;
m_dwResFlags = (uint)PlayerResourcesReadyFlag.RESFG_ALL;
joystick = FindAnyObjectByType<Joystick>();
EventBus.Subscribe<JoystickRealeaseEvent>(JoystickRelease);
EventBus.Subscribe<JoystickPressEvent>(OnClickJoystick);
if (TryGetComponent<PlayerVisual>(out var visual))
{
visual.InitPlayerEventDoneHandler();
}
m_aabb.Center = GPDataTypeHelper.g_vOrigin;
m_aabb.Extents.Set(0.3f, 0.9f, 0.3f);
m_aabbServer = m_aabb;
m_MoveConst.fStepHei = 0.8f;
m_MoveConst.fMinAirHei = 1.6f;
m_MoveConst.fMinWaterHei = 0.3f;
m_MoveConst.fShoreDepth = 1.6f;
m_MoveConst.fWaterSurf = 0.6f;
CalcPlayerAABB();
SetPos(pos);
m_MoveCtrl.SetHostLastPos(EC_Utility.ToA3DVECTOR3(pos));
m_MoveCtrl.SetLastSevPos(EC_Utility.ToA3DVECTOR3(pos));
//m_CDRInfo.vTPNormal = GroundCheck(out RaycastHit hit) ? hit.normal : Vector3.zero;
m_CDRInfo.vExtent = m_aabbServer.Extents;
Vector3 pStart = pos;
pos.y += m_CDRInfo.vExtent.y;
if (Physics.RaycastNonAlloc(pos, Vector3.down, hits, m_CDRInfo.vExtent.y, 1 << 6) > 0)
{
m_CDRInfo.vTPNormal = EC_Utility.ToA3DVECTOR3(hits[0].normal);
}
else
{
m_CDRInfo.vTPNormal = g_vOrigin;
}
m_CDRInfo.fYVel = 0.0f;
m_CDRInfo.fSlopeThresh = EC_SLOPE_Y;
m_CDRInfo.fStepHeight = m_MoveConst.fStepHei;
m_AirCDRInfo.vExtent = m_aabbServer.Extents;
m_AirCDRInfo.fUnderWaterDistThresh = m_MoveConst.fWaterSurf;
// Create work manager
m_pWorkMan = new CECHPWorkMan(this);
m_pWorkMan.StartWork_p0(m_pWorkMan.CreateWork(Host_work_ID.WORK_STAND));
if (IsDead())
{
//CECHPWorkDead pWork = (CECHPWorkDead*)m_pWorkMan.CreateWork(CECHPWork.Host_work_ID.WORK_DEAD);
//pWork.SetBeDeadFlag(true);
//m_pWorkMan.StartWork_p0(pWork);
EventBus.PublishChannel(GetCharacterID(), new ClearComActFlagAllRankNodesEvent(true));
PlayAction((int)PLAYER_ACTION_TYPE.ACT_GROUNDDIE);
PopupManager.NotifyPlayerDied();
}
else
{
PopupManager.NotifyPlayerRevived();
UnityGameSession.c2s_CmdGetAllData(true, true, false);
UnityGameSession.c2s_CmdSendEnterPKPrecinct();
UnityGameSession.RequesrQueryPlayerCash();
}
/*
else if (IsSitting())
{
CECHPWorkSit* pWork = (CECHPWorkSit*)m_pWorkMan.CreateWork(CECHPWork.Host_work_ID.WORK_SIT);
pWork.SetBeSittingFlag(true);
m_pWorkMan.StartWork_p1(pWork);
}*/
//m_GndInfo.bOnGround = GroundCheck(out lastGroundHit);
m_pPetCorral = new CECPetCorral();
if (m_pWorkMan == null)
{
return;
}
LoadGfx();
}
public async void LoadGfx()
{
// Load GFX
var gfxCaster = EC_Game.GetGFXCaster();
// m_pMoveTargetGFX = g_pGame.GetGFXCaster().LoadGFXEx(res_GFXFile(RES_GFX_MOVETARGET));
m_pSelectedGFX =
await gfxCaster.LoadGFXEx(EC_Resource.res_GFXFile((int)GfxResourceType.RES_GFX_CURSORHOVER));
m_pHoverGFX = await gfxCaster.LoadGFXEx(EC_Resource.res_GFXFile((int)GfxResourceType.RES_GFX_CURSORHOVER));
// m_pFloatDust = g_pGame.GetGFXCaster().LoadGFXEx(res_GFXFile(RES_GFX_FLOATING_DUST));
if (true /*CECUIConfig::Instance().GetGameUI().bEnableActionSwitch*/)
{
m_pActionSwitcher = new CECActionSwitcher(this);
}
else
m_pActionSwitcher = new CECActionSwitcherBase(this);
// TODO: Move this to right flow later , it's just for test now
}
private void OnMsgHstPushMove(JoystickPressEvent joystickPressEvent)
{
//_playerStateMachine.ChangeState(_moveState);
/* if (m_pWorkMan.IsSitting())
{
g_pGame.GetGameSession().c2s_CmdStandUp();
return;
}*/
m_dwMoveRelDir = 0;
if (!CanDo(ActionCanDo.CANDO_MOVETO)) return;
if (joystick.Vertical > 0)
{
if (joystick.Horizontal > 0)
{
m_dwMoveRelDir |= (uint)(MOVE_DIR.MD_FORWARD | MOVE_DIR.MD_RIGHT);
}
else if (joystick.Horizontal < 0)
{
m_dwMoveRelDir |= (uint)(MOVE_DIR.MD_FORWARD | MOVE_DIR.MD_LEFT);
}
else
{
m_dwMoveRelDir |= (uint)MOVE_DIR.MD_FORWARD;
}
}
else if (joystick.Vertical < 0)
{
if (joystick.Horizontal > 0)
{
m_dwMoveRelDir |= (uint)(MOVE_DIR.MD_BACK | MOVE_DIR.MD_RIGHT);
}
else if (joystick.Horizontal < 0)
{
m_dwMoveRelDir |= (uint)(MOVE_DIR.MD_BACK | MOVE_DIR.MD_LEFT);
}
else
{
m_dwMoveRelDir |= (uint)MOVE_DIR.MD_BACK;
}
}
else
{
if (joystick.Horizontal > 0)
{
m_dwMoveRelDir |= (uint)(MOVE_DIR.MD_RIGHT);
}
else if (joystick.Horizontal < 0)
{
m_dwMoveRelDir |= (uint)(MOVE_DIR.MD_LEFT);
}
}
bool bPushMove = true;
/* if (Msg.dwParam1 == 8 || Msg.dwParam1 == 9)
{
if (m_iMoveEnv != (int)MoveEnvironment.MOVEENV_AIR && m_iMoveEnv != (int)MoveEnvironment.MOVEENV_WATER)
bPushMove = false;
}*/
if (bPushMove /*&& !IsAboutToDie()*/ && CanDo(ActionCanDo.CANDO_MOVETO))
{
if (m_pWorkMan.CanStartWork(Host_work_ID.WORK_MOVETOPOS))
{
CECHPWorkMove pNewWork = (CECHPWorkMove)m_pWorkMan.CreateWork(Host_work_ID.WORK_MOVETOPOS);
pNewWork.SetDestination(CECHPWorkMove.DestTypes.DEST_PUSH, g_vOrigin);
m_pWorkMan.StartWork_p1(pNewWork);
}
}
}
private void OnDestroy()
{
EventBus.Unsubscribe<JoystickRealeaseEvent>(JoystickRelease);
EventBus.Unsubscribe<JoystickPressEvent>(OnClickJoystick);
}
//TODO: Remove this function. Since it has been deprecated.
public void InitCharacter(info_player_1 role)
{
string roleName = "(Error decoding name)";
//if (role.name != null && role.name.ByteArray != null)
//{
// roleName = Encoding.UTF8.GetString(role.name.ByteArray, 0, role.name.Length);
//}
Vector3 pos = new Vector3(role.pos.x, role.pos.y, role.pos.z);
if (txtName != null) txtName.text = roleName;
playerTransform.position = pos;
// SetPlayerModel();
//Debug.LogError("Pos Character = " + pos);
}
/// <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 void ClearAnimation()
{
EventBus.PublishChannel(GetCharacterID(), new ClearComActFlagAllRankNodesEvent(true));
}
public CECActionSwitcherBase GetActionSwitcher()
{
return m_pActionSwitcher;
}
private float A3d_Magnitude(A3DVECTOR3 v)
{
return Mathf.Sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
}
public int GetCharacterID()
{
return m_PlayerInfo.cid;
}
public bool CanTouchTarget(A3DVECTOR3 vHostPos, A3DVECTOR3 vTargetPos, float fTargetRad, int iReason,
float fMaxCut = 1.0f)
{
float fDist = A3d_Magnitude(vTargetPos - vHostPos);
//bool bResult = false;
float fRange = 0.0f;
//string reasonStr = iReason == 1 ? "melee" : (iReason == 2 ? "cast magic" : (iReason == 3 ? "talk" : $"unknown({iReason})"));
switch (iReason)
{
case 1: // melee
{
if (fMaxCut >= 0.0f)
{
float fCutDist = m_ExtProps.ak.AttackRange * 0.3f;
if (fCutDist > fMaxCut)
fCutDist = fMaxCut;
fRange = m_ExtProps.ak.AttackRange - fCutDist;
}
else
{
fRange = m_ExtProps.ak.AttackRange * 0.7f;
}
if (fDist - fTargetRad <= fRange)
{
//bResult = true;
/* BMLogger.Log($"[DISTANCE_DEBUG] CanTouchTarget: reason={reasonStr}, " +
$"hostPos=({vHostPos.x:F2}, {vHostPos.y:F2}, {vHostPos.z:F2}), " +
$"targetPos=({vTargetPos.x:F2}, {vTargetPos.y:F2}, {vTargetPos.z:F2}), " +
$"distance={fDist:F2}, targetRadius={fTargetRad:F2}, " +
$"requiredRange={fRange:F2}, effectiveDistance={fDist - fTargetRad:F2}, " +
$"result={bResult}, attackRange={m_ExtProps.ak.AttackRange:F2}");*/
return true;
}
break;
}
case 2: // cast magic
{
if (m_pPrepSkill != null)
{
//TODO : Check this function GetCastRange
fRange = m_pPrepSkill.GetCastRange(m_ExtProps.ak.AttackRange, GetPrayDistancePlus());
if (fRange > 0.0f)
{
if (fDist - fTargetRad <= fRange)
{
//bResult = true;
/* BMLogger.Log($"[DISTANCE_DEBUG] CanTouchTarget: reason={reasonStr}, " +
$"hostPos=({vHostPos.x:F2}, {vHostPos.y:F2}, {vHostPos.z:F2}), " +
$"targetPos=({vTargetPos.x:F2}, {vTargetPos.y:F2}, {vTargetPos.z:F2}), " +
$"distance={fDist:F2}, targetRadius={fTargetRad:F2}, " +
$"requiredRange={fRange:F2}, effectiveDistance={fDist - fTargetRad:F2}, " +
$"result={bResult}, skillID={m_pPrepSkill.GetSkillID()}, attackRange={m_ExtProps.ak.AttackRange:F2}");*/
return true;
}
}
else
{
//bResult = true;
/*BMLogger.Log($"[DISTANCE_DEBUG] CanTouchTarget: reason={reasonStr}, " +
$"hostPos=({vHostPos.x:F2}, {vHostPos.y:F2}, {vHostPos.z:F2}), " +
$"targetPos=({vTargetPos.x:F2}, {vTargetPos.y:F2}, {vTargetPos.z:F2}), " +
$"distance={fDist:F2}, targetRadius={fTargetRad:F2}, " +
$"requiredRange=unlimited, result={bResult}, skillID={m_pPrepSkill.GetSkillID()}");*/
return true;
}
}
break;
}
case 3: // talk
{
fRange = 5.0f;
if (fDist - fTargetRad <= fRange)
{
//bResult = true;
return true;
}
break;
}
default: // no special reason
{
fRange = (fTargetRad + m_fTouchRad) * 3.0f;
if (fDist < fRange)
{
//bResult = true;
return true;
}
break;
}
}
// Log distance check result for debugging
// 记录距离检查结果用于调试
/* BMLogger.Log($"[DISTANCE_DEBUG] CanTouchTarget: reason={reasonStr}, " +
$"hostPos=({vHostPos.x:F2}, {vHostPos.y:F2}, {vHostPos.z:F2}), " +
$"targetPos=({vTargetPos.x:F2}, {vTargetPos.y:F2}, {vTargetPos.z:F2}), " +
$"distance={fDist:F2}, targetRadius={fTargetRad:F2}, " +
$"requiredRange={fRange:F2}, effectiveDistance={fDist - fTargetRad:F2}, " +
$"result={bResult}, attackRange={m_ExtProps.ak.AttackRange:F2}");*/
return false;
}
public void RemoveObjectFromTabSels(CECObject pObject)
{
for (int i = 0; i < m_aTabSels.Count; i++)
{
if (m_aTabSels[i] == pObject)
{
m_aTabSels.RemoveAt(i);
break;
}
}
}
public bool CanTouchTarget(A3DVECTOR3 vTargetPos, float fTargetRad, int iReason, float fMaxCut = 1.0f)
{
// Use server-tracked position instead of visual position for distance checks
// This ensures distance checks use the correct position immediately after flashmove
// 使用服务器跟踪的位置而不是视觉位置进行距离检查
// 这确保在闪移后立即使用正确的位置进行距离检查
A3DVECTOR3 vector = EC_Utility.ToA3DVECTOR3(m_MoveCtrl.GetLastSevPos());
return CanTouchTarget(vector, vTargetPos, fTargetRad, iReason, fMaxCut);
}
public bool IsRooting()
{
var mask = (uint)(Logic_Influence_Extned_states.LIES_ROOT
| Logic_Influence_Extned_states.LIES_SLEEP
| Logic_Influence_Extned_states.LIES_STUN);
return (m_dwLIES & mask) != 0;
}
public bool IsInFortress()
{
return m_fortressEnter.role_in_war != 0;
}
bool IsPVPOpen()
{
return m_pvp.bEnable;
}
public float GetPrayDistancePlus()
{
return m_fPrayDistancePlus;
}
// Get faction ID
//public int GetFactionID()
//{
// return m_idFaction;
//}
public void SetPrayDistancePlus(float prayDistancePlus)
{
m_fPrayDistancePlus = prayDistancePlus;
}
public bool IsJumping()
{
return m_iJumpCount > 0;
}
public bool IsFalling()
{
return m_iMoveEnv == (int)MoveEnvironment.MOVEENV_GROUND
&& m_GndInfo.bOnGround == false;
}
public bool IsPlayingAction(int iAction)
{
if (iAction == (int)PLAYER_ACTION_TYPE.ACT_WALK) //&& _playerStateMachine.State is PlayerMoveState
{
return true;
}
if (iAction == (int)PLAYER_ACTION_TYPE.ACT_STAND) // && _playerStateMachine.State is PlayerIdleState
{
return true;
}
return false;
}
public void ResetJump()
{
m_iJumpCount = 0;
m_bJumpInWater = false;
}
// Get move speed
public float GetFlySpeed()
{
return m_ExtProps.mv.flight_speed;
}
public float GetSwimSpeed()
{
return m_ExtProps.mv.swim_speed;
}
public float GetSwimSpeedSev()
{
float fSpeedSev = GetSwimSpeed();
LayerMask layerGround = 1 << 6;
LayerMask layerWater = 1 << 8;
while (true)
{
if (!IsUnderWater()) break;
CECWorld pWorld = EC_Game.GetGameRun().GetWorld();
if (pWorld == null) break;
A3DVECTOR3 vPos = GetPos();
Vector3 startPoint = EC_Utility.ToVector3(vPos);
Vector3 dir = EC_Utility.ToVector3(vPos) + Vector3.down;
float fTerrainHeight = 0f;
if (Physics.RaycastNonAlloc(startPoint, dir, hits, layerGround) > 0)
{
fTerrainHeight = Vector3.Distance(hits[0].point, startPoint);
}
float fWaterHeight = 0f;
if (Physics.RaycastNonAlloc(startPoint, dir, hits, layerWater) > 0)
{
fWaterHeight = Vector3.Distance(hits[0].point, startPoint);
}
if (fWaterHeight <= fTerrainHeight) break;
float fBorderLine = fWaterHeight - 2.0f;
if (vPos.y <= fBorderLine) break;
fSpeedSev = Math.Min(m_ExtProps.mv.run_speed, fSpeedSev);
break;
}
return fSpeedSev;
}
public void PrepareNPCService(int idSev)
{
if (!GPDataTypeHelper.ISNPCID(m_idSevNPC))
{
return;
}
DATA_TYPE DataType = new DATA_TYPE();
object pBuf = ElementDataManProvider.GetElementDataMan()
.get_data_ptr((uint)idSev, ID_SPACE.ID_SPACE_ESSENCE, ref DataType);
switch (DataType)
{
case DATA_TYPE.DT_NPC_TALK_SERVICE:
break;
case DATA_TYPE.DT_NPC_SELL_SERVICE:
case DATA_TYPE.DT_NPC_BUY_SERVICE:
{
//// Get NPC's tex rate
//float fScale = 1.0f;
//CECNPC pNPC = EC_ManMessageMono.Instance._CECNPCMan.GetNPC(m_idSevNPC);
//if (pNPC && pNPC.IsServerNPC())
//{
// CECNPCServer pServer = (CECNPCServer)pNPC;
// fScale = (1.0f + pServer.GetTaxRate()) * pServer.GetPriceScale();
//}
//// Fill NPC package
//NPC_SELL_SERVICE pData = (NPC_SELL_SERVICE)pBuf;
//int[] id_goods = new int[InventoryConst.IVTRSIZE_NPCPACK];
//for (int j = 0; j < InventoryConst.NUM_NPCIVTR; j++)
//{
// for (int i = 0; i < InventoryConst.IVTRSIZE_NPCPACK; ++i)
// id_goods[i] = (int)pData.pages[j].goods[i].id;
// FillNPCPack(j, pData.pages[j].page_title, id_goods, fScale, false);
//}
//// Clear packs
//m_pBuyPack.RemoveAllItems();
//m_pSellPack.RemoveAllItems();
break;
}
case DATA_TYPE.DT_NPC_SKILL_SERVICE:
case DATA_TYPE.DT_NPC_PETLEARNSKILL_SERVICE:
{
//CECNPC pNPC = EC_ManMessageMono.Instance._CECNPCMan.GetNPC(m_idSevNPC);
//if (!pNPC || !pNPC.IsServerNPC())
//{
// return;
//}
//if (DataType == DATA_TYPE.DT_NPC_SKILL_SERVICE)
// ((CECNPCServer)pNPC).BuildSkillList(idSev);
//else
// ((CECNPCServer)pNPC).BuildPetSkillList();
break;
}
case DATA_TYPE.DT_NPC_REPAIR_SERVICE:
case DATA_TYPE.DT_NPC_INSTALL_SERVICE:
case DATA_TYPE.DT_NPC_UNINSTALL_SERVICE:
case DATA_TYPE.DT_NPC_TASK_IN_SERVICE:
case DATA_TYPE.DT_NPC_TASK_OUT_SERVICE:
case DATA_TYPE.DT_NPC_TASK_MATTER_SERVICE:
case DATA_TYPE.DT_NPC_HEAL_SERVICE:
case DATA_TYPE.DT_NPC_TRANSMIT_SERVICE:
case DATA_TYPE.DT_NPC_TRANSPORT_SERVICE:
case DATA_TYPE.DT_NPC_PROXY_SERVICE:
case DATA_TYPE.DT_NPC_STORAGE_SERVICE:
case DATA_TYPE.DT_NPC_DECOMPOSE_SERVICE:
case DATA_TYPE.DT_NPC_PETNAME_SERVICE:
case DATA_TYPE.DT_NPC_PETFORGETSKILL_SERVICE:
break;
case DATA_TYPE.DT_NPC_MAKE_SERVICE:
{
//// Fill NPC package
//NPC_MAKE_SERVICE pData = (NPC_MAKE_SERVICE)pBuf;
//for (int j = 0; j < NUM_NPCIVTR; j++)
// FillNPCPack(j, pData.pages[j].page_title, pData.pages[j].id_goods, 1.0f, true);
//// Clear deal pack
//m_pDealPack.RemoveAllItems();
break;
}
case DATA_TYPE.DT_NPC_RANDPROP_SERVICE:
{
//NPC_RANDPROP_SERVICE* pData = (NPC_RANDPROP_SERVICE*)pBuf;
//elementdataman* pDataMan = g_pGame.GetElementDataMan();
//// Fill equip data into NPC pack
//ASSERT(sizeof(pData.pages) / sizeof(pData.pages[0]) == NUM_NPCIVTR );
//for (int j = 0; j < NUM_NPCIVTR; j++)
//{
// unsigned int id_recipe = pData.pages[j].id_recipe;
// DATA_TYPE dt = DT_INVALID;
// RANDPROP_ESSENCE* pRecipe = (RANDPROP_ESSENCE*)pDataMan.get_data_ptr(id_recipe, ID_SPACE_RECIPE, dt);
// if (pRecipe && dt == DT_RANDPROP_ESSENCE)
// {
// FillNPCPack(j, pData.pages[j].page_title, (int*)pRecipe.equip_id, 1.0f, false);
// }
// else
// {
// // skip the invalid recipe id
// GetNPCSevPack(j).RemoveAllItems();
// GetNPCSevPack(j).SetName(_AL(""));
// }
//}
//// Clear deal pack
//m_pDealPack.RemoveAllItems();
break;
}
}
}
private void LogInventoryPacket(string tag, byte[] buffer, int hostId)
{
if (buffer == null)
return;
int index = 0;
if (buffer.Length < 6)
{
//LogInventoryRaw(tag, buffer);
return;
}
byte byPackage = buffer[index++];
byte ivtrSize = buffer[index++];
uint contentLength = BitConverter.ToUInt32(buffer, index);
index += 4;
int remaining = buffer.Length - index;
int contentBytes = remaining;
if (contentLength < (uint)remaining)
{
contentBytes = (int)contentLength;
}
if (contentBytes > 0)
{
byte[] content = new byte[contentBytes];
Buffer.BlockCopy(buffer, index, content, 0, contentBytes);
}
int trailing = buffer.Length - (index + contentBytes);
if (trailing > 0)
{
byte[] tail = new byte[trailing];
Buffer.BlockCopy(buffer, index + contentBytes, tail, 0, trailing);
}
}
public bool NaturallyStopMoving()
{
// if (!m_MoveCtrl.IsStop())
if (!IsPlayerMoving())
return true; // Host has been stopped
if (m_iMoveMode == (int)MoveMode.MOVE_FREEFALL || InSlidingState() || IsJumping())
return false; // Host couldn't stop naturally
if (!m_pWorkMan.IsStanding())
{
m_pWorkMan.FinishAllWork(true);
}
m_MoveCtrl.SendStopMoveCmd();
return true;
}
public A3DVECTOR3 GetDir()
{
// Return forward direction from transform
return EC_Utility.ToA3DVECTOR3(transform.forward);
}
public A3DVECTOR3 A3d_RotatePosAroundY(A3DVECTOR3 vDir, float fAngle)
{
float cos = Mathf.Cos(fAngle);
float sin = Mathf.Sin(fAngle);
A3DVECTOR3 result = new A3DVECTOR3();
result.x = vDir.x * cos + vDir.z * sin;
result.y = vDir.y;
result.z = -vDir.x * sin + vDir.z * cos;
return result;
}
bool IsTooNear(A3DVECTOR3 vTarget, ref float fNearDist)
{
// ¸ù¾Ý¿Õ¼äÇé¿ö£¬¼ÆËãÏà½ü 3D ¿Õ¼äÉÏÓ¦±£³ÖµÄ½ÏС¾àÀ룬±ÜÃâÒÆ¶¯ºÜ½üÉõÖÁÖØºÏµÄÇé¿ö
// ·µ»Ø true ±íÃ÷µ±Ç°¾àÀëСÓÚ¼ÆËã³öµÄ×îС¾àÀë
A3DVECTOR3 vPos = GetPos();
A3DVECTOR3 vMoveDir = vTarget - vPos;
float fDist = vMoveDir.Magnitude();
float fTestDist = (0.0f);
const float fTestDistH = 0.1f;
const float fMoveDistH = 0.01f; // ±£Ö¤ fTestDistH > fMoveDistH£¬Ê¹¼ì²â¸üÓÐЧ
float fDeltaXZ = vMoveDir.MagnitudeH();
if (fDeltaXZ > 0.001f && (MathF.Abs(vMoveDir.y) / fDeltaXZ) <= 50)
{
// tangent Öµ²»Ì«´ó
// ÒÔ fTestDistH Ϊˮƽ¾àÀëÒªÇ󣬼ÆËã¿Õ¼äÉÏÓ¦±£³ÖµÄ¾àÀë
fTestDist = fTestDistH * fDist / fDeltaXZ;
// ÒÔ fMoveDistH Ϊˮƽ¾àÀëÒªÇ󣬼ÆËã¿Õ¼äÉÏÓ¦ÒÆ½üµÄ¾àÀë
fNearDist = fMoveDistH * fDist / fDeltaXZ;
}
else
{
// tangent ÖµºÜ´ó£¬»ò vPos Óë vTarget ÖØºÏµÄÇé¿ö
fTestDist = 0.5f;
fNearDist = fDist > 0.01f ? 0.01f : 0.0f;
}
return (fDist <= fTestDist);
}
public bool UpdateEquipSkins()
{
int[] aNewEquips = new int[InventoryConst.IVTRSIZE_EQUIPPACK];
EC_IvtrItem pItem = null;
for (int i = 0; i < InventoryConst.IVTRSIZE_EQUIPPACK; i++)
{
// Use host player's equipment inventory (per-instance CECInventory)
var host = CECGameRun.Instance?.GetHostPlayer();
var equipInv = host?.IvtrEquipPack;
pItem = equipInv?.GetItem(i, false);
if (pItem != null)
aNewEquips[i] = pItem.m_tid;
}
ShowEquipments(aNewEquips, true, true);
return true;
}
public byte glb_BuildPVPMask(bool bForceAttack)
{
byte byMask = 0;
if (bForceAttack)
byMask |= (byte)PVPMask.GP_PVPMASK_FORCE;
else
{
CECConfigs pConfigs = EC_Game.GetConfigs();
if (pConfigs.GetGameSettings().bAtk_Player)
{
byMask |= (byte)PVPMask.GP_PVPMASK_FORCE;
if (pConfigs.GetGameSettings().bAtk_NoMafia)
byMask |= (byte)PVPMask.GP_PVPMASK_NOMAFIA;
if (pConfigs.GetGameSettings().bAtk_NoWhite)
byMask |= (byte)PVPMask.GP_PVPMASK_NOWHITE;
if (pConfigs.GetGameSettings().bAtk_NoAlliance)
byMask |= (byte)PVPMask.GP_PVPMASK_NOALLIANCE;
if (pConfigs.GetGameSettings().bAtk_NoForce)
byMask |= (byte)PVPMask.GP_PVPMASK_NOFORCE;
}
}
return byMask;
}
public bool SelectTarget(int idTarget)
{
bool bRet = false;
bool canDo = CanDo(ActionCanDo.CANDO_CHANGESELECT);
bool canselect = CanSelectTarget(idTarget);
if (canDo && canselect)
{
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;
}
public int GetMaxLevelSofar() { return Math.Max(m_ReincarnationTome.max_level, m_BasicProps.iLevel); }
public bool CanUseProjectile(CECIvtrArrow pArrow)
{
if (pArrow == null)
return false;
CECIvtrWeapon pWeapon = (CECIvtrWeapon)m_pEquipPack.GetItem((int)IndexOfIteminEquipmentInventory.EQUIPIVTR_WEAPON);
if (pWeapon == null)
return false;
IVTR_ESSENCE_WEAPON we = pWeapon.GetEssence();
if (we.weapon_type != (int)WeaponType.WEAPONTYPE_RANGE)
return false;
IVTR_ESSENCE_ARROW ae = pArrow.GetEssence();
if (we.require_projectile != (int)pArrow.GetDBSubType().id ||
we.weapon_level < ae.iWeaponReqLow || we.weapon_level > ae.iWeaponReqHigh)
return false;
return true;
}
bool CanSelectTarget(int idTarget)
{
if (idTarget == 0 || idTarget == this.GetCharacterID())
{
// 0 means unselect
return true;
}
// Duel: always allow selecting the duel opponent so we can attack/cast (distance checked when trace runs)
if (IsInDuel() && idTarget == m_pvp.idDuelOpp)
return true;
CECObject pTarget = null;
if (GPDataTypeHelper.ISPLAYERID(idTarget))
{
EC_ElsePlayer pElsePlayer =
(EC_ManMessageMono.Instance.GetECManPlayer.GetPlayer(idTarget)) as EC_ElsePlayer;
if (pElsePlayer != null)
{
if (CanSafelySelect(pElsePlayer))
{
pTarget = pElsePlayer;
}
}
}
else if (GPDataTypeHelper.ISNPCID(idTarget))
{
CECNPC pNPC = EC_ManMessageMono.Instance.CECNPCMan.GetNPC(idTarget);
if (pNPC != null)
{
if (CanSafelySelect(pNPC) && !pNPC.IsDead())
{
pTarget = pNPC;
}
}
}
return pTarget ? pTarget.IsSelectable() : false;
}
float SafelySelectDistance()
{
// ·þÎñÆ÷¶Ô SelectTarget ÓжîÍâ¾àÀëÏÞÖÆ£¬Èýά¾àÀë 150.0¡¢Ë®Æ½¾àÀë 125.0 ÒÔÉϵ쬶¼»áÎÞ·¨Ñ¡ÖÐ
// »ùÓÚÒÔÉÏÔ­Òò£¬¿Í»§¶ËÑ¡Ôñ¶ÔÏó¡¢»òÕß¶ÔÒѾ­Ñ¡ÔñµÄ¶ÔÏ󣬶¼È·±£ÆäÔÚ´ËÏÞÖÆ·¶Î§ÄÚ£¬¼´Ñ¡ÔñʱʹÓýÏС¾àÀë¼ì²â
return 100.0f;
}
public bool CanSafelySelectWith(float fDistanceToHostPlayer)
{
return fDistanceToHostPlayer <= SafelySelectDistance();
}
bool CanSafelySelect(EC_ElsePlayer pElsePlayer)
{
// IsSkeletonReady() Ϊ true ʱ, GetDistToHost() ²ÅΪÓÐЧÊý¾Ý
// !IsSkeletonReady() ʱ£¬Ò²ÔÊÐíʹÓã¬Ä¿µÄÊDZÜÃâδ¿¼Âǵ½µÄÒâÍâÇé¿ö
// ÏÂͬ
return pElsePlayer && ( /*!IsSkeletonReady() || */CanSafelySelectWith(pElsePlayer.GetDistToHost()));
}
bool CanSafelySelect(CECNPC pNPC)
{
return pNPC && ( /*!IsSkeletonReady() ||*/ CanSafelySelectWith(pNPC.GetDistToHost()));
}
// Check whether host can do a behavior / 检查宿主是否可以执行某个行为
public bool CanDo(int iThing)
{
bool bRet = true;
switch (iThing)
{
case ActionCanDo.CANDO_SITDOWN:
if (IsDead() || IsAboutToDie() || IsJumping() || IsTrading() || IsUsingTrashBox() ||
IsRooting() || IsReviving() || IsTalkingWithNPC() || IsChangingFace() ||
!m_GndInfo.bOnGround || GetBoothState() != 0 || m_iBuddyId != 0 || IsOperatingPet() != 0 || IsRebuildingPet() ||
IsUsingItem() || IsRidingOnPet() || GetShapeType() == (int)PlayerModelType.PLAYERMODEL_DUMMYTYPE2 || IsPassiveMove())
bRet = false;
break;
case ActionCanDo.CANDO_MOVETO:
if (IsDead() || IsSitting() || IsTrading() || IsUsingTrashBox() || IsRooting() ||
IsReviving() || IsTalkingWithNPC() || IsChangingFace() || IsUsingItem() ||
GetBoothState() != 0 || m_bHangerOn || IsOperatingPet() != 0 || IsRebuildingPet() || IsPassiveMove())
bRet = false;
break;
case ActionCanDo.CANDO_MELEE:
if (IsDead() || IsSitting() || m_idSelTarget == 0 || m_idSelTarget == m_PlayerInfo.cid ||
IsJumping() || GPDataTypeHelper.ISMATTERID(m_idSelTarget) || IsTrading() || IsReviving() ||
IsUsingTrashBox() || IsTalkingWithNPC() || IsChangingFace() || CannotAttack() ||
GetBoothState() != 0 || m_iBuddyId != 0 || IsRidingOnPet() || IsOperatingPet() != 0 || IsRebuildingPet() ||
IsUsingItem() || IsPassiveMove())
bRet = false;
break;
case ActionCanDo.CANDO_ASSISTSEL:
if (IsDead() || !GPDataTypeHelper.ISPLAYERID(m_idSelTarget) || m_idSelTarget == m_PlayerInfo.cid ||
m_pTeam == null || m_pTeam.GetMemberByID(m_idSelTarget) == null || m_iBuddyId != 0 || IsPassiveMove() ||
m_playerLimits[(int)PLAYER_LIMIT.PLAYER_LIMIT_NOCHANGESELECT])
bRet = false;
break;
case ActionCanDo.CANDO_FLY:
if (IsDead() || IsRooting() || IsSitting() || IsTrading() || IsReviving() ||
IsUsingTrashBox() || IsTalkingWithNPC() || IsChangingFace() || GetBoothState() != 0 ||
IsFlashMoving() || (m_pWorkMan != null && m_pWorkMan.HasWorkRunningOnPriority(CECHPWorkMan.Work_priority.PRIORITY_2)) ||
m_bHangerOn || IsOperatingPet() != 0 || IsRebuildingPet() ||
IsUsingItem() || IsRidingOnPet() || GetShapeType() == (int)PlayerModelType.PLAYERMODEL_DUMMYTYPE2 || IsPassiveMove() ||
m_playerLimits[(int)PLAYER_LIMIT.PLAYER_LIMIT_NOFLY] /*|| m_BattleInfo.IsChariotWar()*/)
bRet = false;
break;
case ActionCanDo.CANDO_PICKUP:
case ActionCanDo.CANDO_GATHER:
if (IsDead() || IsAboutToDie() || IsSitting() || IsTrading() || IsUsingTrashBox() ||
IsReviving() || IsTalkingWithNPC() || IsChangingFace() || GetBoothState() != 0 ||
GetBuddyState() == 1 || IsOperatingPet() != 0 || IsRebuildingPet() || IsUsingItem() || IsPassiveMove())
bRet = false;
break;
case ActionCanDo.CANDO_TRADE:
if (IsDead() || IsAboutToDie() || IsSitting() || IsJumping() || IsMeleeing() ||
IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() || IsChangingFace() ||
IsSpellingMagic() || GetBoothState() != 0 || m_iBuddyId != 0 || IsOperatingPet() != 0 || IsRebuildingPet() ||
IsUsingItem() || IsInvisible() || IsPassiveMove())
bRet = false;
break;
case ActionCanDo.CANDO_PLAYPOSE:
if (IsDead() || IsAboutToDie() || IsSitting() || IsJumping() || IsMeleeing() ||
IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() || IsChangingFace() ||
IsSpellingMagic() || IsShapeChanged() || IsReviving() || m_iMoveEnv != (int)MoveEnvironment.MOVEENV_GROUND ||
GetBoothState() != 0 || m_iBuddyId != 0 || IsOperatingPet() != 0 || IsRebuildingPet() || IsUsingItem() ||
IsRidingOnPet() || GetShapeType() == (int)PlayerModelType.PLAYERMODEL_DUMMYTYPE2 || IsPassiveMove() /*|| m_BattleInfo.IsChariotWar()*/)
bRet = false;
break;
case ActionCanDo.CANDO_SPELLMAGIC:
if (IsDead() || GPDataTypeHelper.ISMATTERID(m_idSelTarget) || IsAboutToDie() || IsSitting() ||
IsJumping() || IsFlashMoving() || IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() ||
IsChangingFace() || CannotAttack() || IsReviving() || GetBoothState() != 0 ||
m_iBuddyId != 0 || IsRidingOnPet() || IsOperatingPet() != 0 || IsRebuildingPet() || IsUsingItem() || IsPassiveMove())
bRet = false;
break;
case ActionCanDo.CANDO_SUMMONPET:
if (IsDead() || GPDataTypeHelper.ISMATTERID(m_idSelTarget) || IsAboutToDie() || IsSitting() ||
IsJumping() || IsFlashMoving() || IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() ||
IsChangingFace() || CannotAttack() || IsReviving() || GetBoothState() != 0 ||
IsInvisible() || IsGMInvisible() || IsOperatingPet() != 0 || IsRebuildingPet() || IsUsingItem() || IsPassiveMove()
/*|| m_BattleInfo.IsChariotWar()*/)
bRet = false;
break;
case ActionCanDo.CANDO_REBUILDPET:
if (IsDead() || GPDataTypeHelper.ISMATTERID(m_idSelTarget) || IsAboutToDie() || IsSitting() ||
IsJumping() || IsFlashMoving() || IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() ||
IsChangingFace() || CannotAttack() || IsReviving() || GetBoothState() != 0 ||
m_iBuddyId != 0 || IsInvisible() || IsGMInvisible() || IsOperatingPet() != 0 || IsRebuildingPet() || IsUsingItem() || IsPassiveMove() ||
IsPlayerMoving() /*|| m_BattleInfo.IsChariotWar()*/)
bRet = false;
break;
case ActionCanDo.CANDO_USEITEM:
if (IsAboutToDie() || IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() ||
IsChangingFace() || GetBoothState() != 0 || IsPassiveMove() /*|| m_BattleInfo.IsChariotWar()*/)
bRet = false;
break;
case ActionCanDo.CANDO_JUMP:
if (IsDead() ||
m_iJumpCount >= MAX_JUMP_COUNT ||
// cannot jump more than one time if shape mode is type2
(IsJumping() && (GetShapeType() == (int)PlayerModelType.PLAYERMODEL_DUMMYTYPE2)) ||
IsJumpInWater() || m_iMoveEnv == (int)MoveEnvironment.MOVEENV_AIR || IsSitting() ||
IsMeleeing() || IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() ||
IsChangingFace() || IsReviving() || IsSpellingMagic() || IsPicking() ||
IsGathering() || IsRooting() || GetBoothState() != 0 || m_bHangerOn || (IsJumping() && IsRidingOnPet()) ||
IsOperatingPet() != 0 || IsRebuildingPet() || IsUsingItem() || IsPassiveMove() /*|| m_BattleInfo.IsChariotWar()*/)
bRet = false;
break;
case ActionCanDo.CANDO_FOLLOW:
if (IsDead() || IsAboutToDie() || IsSitting() || IsMeleeing() || IsReviving() ||
IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() || IsChangingFace() ||
IsSpellingMagic() || GetBoothState() != 0 || m_bHangerOn || IsOperatingPet() != 0 || IsRebuildingPet() ||
IsUsingItem() || IsPassiveMove())
bRet = false;
break;
case ActionCanDo.CANDO_BOOTH:
if (IsDead() || IsAboutToDie() || IsPlayerMoving() || IsSitting() || IsReviving() ||
IsMeleeing() || IsJumping() || IsTrading() || IsUsingTrashBox() ||
IsTalkingWithNPC() || IsChangingFace() || IsSpellingMagic() || IsFlying() ||
IsUnderWater() || m_iBuddyId != 0 || IsOperatingPet() != 0 || IsRebuildingPet() || IsUsingItem() || IsRidingOnPet() || IsInvisible() ||
IsPassiveMove())
bRet = false;
break;
case ActionCanDo.CANDO_FLASHMOVE:
if (IsDead() || IsAboutToDie() || IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() ||
IsJumping() || IsFlashMoving() || IsFalling() || IsChangingFace() || GetBoothState() != 0 || IsTakingOff() ||
(m_pWorkMan != null && m_pWorkMan.HasWorkRunningOnPriority(CECHPWorkMan.Work_priority.PRIORITY_2)) ||
m_iBuddyId != 0 || IsOperatingPet() != 0 || IsRebuildingPet() || IsUsingItem() || IsPassiveMove())
bRet = false;
break;
case ActionCanDo.CANDO_BINDBUDDY:
if (IsDead() || IsAboutToDie() || IsJumping() || IsSitting() ||
IsMeleeing() || IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() ||
IsChangingFace() || IsReviving() || IsSpellingMagic() || IsPicking() ||
IsGathering() || IsRooting() || GetBoothState() != 0 ||
(m_pWorkMan != null && !m_pWorkMan.IsStanding()) || m_iBuddyId != 0 ||
IsOperatingPet() != 0 || IsRebuildingPet() || IsUsingItem() || GetShapeType() == (int)PlayerModelType.PLAYERMODEL_DUMMYTYPE2 || IsPassiveMove() ||
m_playerLimits[(int)PLAYER_LIMIT.PLAYER_LIMIT_NOBIND])
bRet = false;
break;
case ActionCanDo.CANDO_DUEL:
if (IsDead() || IsAboutToDie() || IsSitting() || IsFighting() || IsTrading() ||
IsReviving() || IsUsingTrashBox() || IsTalkingWithNPC() || IsChangingFace() ||
GetBoothState() != 0 || m_iBuddyId != 0 || m_pvp.iDuelState != (int)DuelState.DUEL_ST_NONE ||
IsOperatingPet() != 0 || IsRebuildingPet() || IsUsingItem() || IsPassiveMove())
bRet = false;
break;
case ActionCanDo.CANDO_CHANGESELECT:
if (m_playerLimits[(int)PLAYER_LIMIT.PLAYER_LIMIT_NOCHANGESELECT])
bRet = false;
break;
case ActionCanDo.CANDO_SWITCH_PARALLEL_WORLD:
if (IsDead() || IsAboutToDie() || IsJumping() || IsFighting() ||
IsMeleeing() || IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() ||
IsChangingFace() || IsReviving() || IsSpellingMagic() || IsPicking() ||
IsGathering() || IsRooting() || GetBoothState() != 0 ||
m_iBuddyId != 0 || IsOperatingPet() != 0 || IsRebuildingPet() || IsUsingItem() ||
GetShapeType() == (int)PlayerModelType.PLAYERMODEL_DUMMYTYPE2 || IsPassiveMove())
bRet = false;
break;
}
return bRet;
}
public bool GetPushDir(ref Vector3 vPushDir, uint dwMask, float deltaTime)
{
vPushDir = Vector3.zero;
if (joystick.Horizontal == 0 && joystick.Vertical == 0)
{
if (isPressMoveUp)
{
vPushDir = Vector3.up;
}
else if (isPressMoveDown)
{
vPushDir = Vector3.down;
}
return false;
}
if (m_iMoveEnv == Move_environment.MOVEENV_WATER || m_iMoveEnv == Move_environment.MOVEENV_AIR)
{
float angle = Vector2.Angle(new Vector2(joystick.Horizontal, joystick.Vertical), Vector2.up);
angle *= joystick.Horizontal < 0 ? 1 : -1;
Vector2 v2Cam = new Vector2(mainCam.transform.forward.x, mainCam.transform.forward.z);
v2Cam = Quaternion.Euler(0, 0, angle) * v2Cam;
v2Cam.Normalize();
vPushDir.x = v2Cam.x;
vPushDir.y = ((transform.position + Vector3.up * m_CDRInfo.vExtent.y) - mainCam.transform.position).normalized.y;
if (isPressMoveUp)
{
vPushDir.y = Math.Abs(vPushDir.y) * Time.deltaTime;
}
else if (isPressMoveDown)
{
vPushDir.y = -Math.Abs(vPushDir.y) * Time.deltaTime;
}
vPushDir.z = v2Cam.y;
}
else
{
float angle = Vector2.Angle(new Vector2(joystick.Horizontal, joystick.Vertical), Vector2.up);
angle *= joystick.Horizontal < 0 ? 1 : -1;
Vector2 v2Cam = new Vector2(mainCam.transform.forward.x, mainCam.transform.forward.z);
v2Cam = Quaternion.Euler(0, 0, angle) * v2Cam;
v2Cam.Normalize();
vPushDir.x = v2Cam.x;
vPushDir.y = 0;
vPushDir.z = v2Cam.y;
}
return true;
}
// Is under water
bool CanTakeOffWater()
{
// TO DO: fix later
//A3DVECTOR3 vPos = GetPos();
//if (vPos.y < EC_Game.GetGameRun().GetWorld().GetWaterHeight(vPos) - m_MoveConst.fShoreDepth)
// return false;
//else
// return true;
A3DVECTOR3 vPos = GetPos();
float h0 = 0f;
int countHits0 = Physics.RaycastNonAlloc(EC_Utility.ToVector3(vPos) + Vector3.up * 500f, Vector3.down, hits, 1000f, 1 << 8);
if (countHits0 > 0)
{
h0 = hits[0].point.y;
}
if (vPos.y < h0 - m_MoveConst.fShoreDepth)
{
return false;
}
else
{
return true;
}
}
bool IsUsingItem()
{
return m_pWorkMan.IsUsingItem();
}
bool IsPassiveMove()
{
return m_pWorkMan.IsPassiveMoving();
}
// Is about to die / 是否即将死亡
bool IsAboutToDie()
{
return m_bAboutToDie;
}
// Is rebuilding pet / 是否正在重建宠物
bool IsRebuildingPet()
{
return m_bInRebuildPet;
}
// Is riding on pet / 是否骑乘宠物
bool IsRidingOnPet()
{
return m_RidingPet.id != 0;
}
// Is flash moving / 是否在闪移
bool IsFlashMoving()
{
if (m_pWorkMan == null) return false;
return m_pWorkMan.IsFlashMoving();
}
// Get buddy state / 获取伙伴状态
// return value: 0 = no buddy, 1 = has buddy, 2 = hanger on
int GetBuddyState()
{
if (m_bHangerOn) return 2;
if (m_iBuddyId != 0) return 1;
return 0;
}
// Is invisible / 是否隐身
bool IsInvisible()
{
return (m_dwStates & (uint)PlayerNPCState.GP_STATE_INVISIBLE) != 0;
}
// Is GM invisible / 是否GM隐身
bool IsGMInvisible()
{
// GMF_INVISIBLE would be a constant, using bit check
// GMF_INVISIBLE 将是一个常量,使用位检查
return (m_dwGMFlags & 0x01) != 0; // Assuming GMF_INVISIBLE = 0x01
}
// Is shape changed / 是否形状已改变
bool IsShapeChanged()
{
return m_iShape != 0;
}
// Is taking off / 是否正在起飞
bool IsTakingOff()
{
if (m_pWorkMan == null) return false;
return m_pWorkMan.IsFlyingOff();
}
// Is flying / 是否在飞行
bool IsFlying()
{
return (m_dwStates & (uint)PlayerNPCState.GP_STATE_FLY) != 0;
}
//public void SetGroundInfoClient()
//{
// isGrounded = GroundCheck(out lastGroundHit);
// m_GndInfo.bOnGround = isGrounded;
//}
public void SetRotationHP(Vector3 dir)
{
transform.rotation = Quaternion.LookRotation(dir);
}
public void SetRotationHPWithTime(Vector3 dir, float time)
{
StartCoroutine(RotateToDir(transform, dir, time));
}
IEnumerator RotateToDir(Transform target, Vector3 dir, float duration)
{
Quaternion startRot = target.rotation;
Quaternion endRot = Quaternion.LookRotation(dir);
float t = 0f;
while (t < duration)
{
t += Time.deltaTime;
target.rotation = Quaternion.Slerp(startRot, endRot, t / duration);
yield return null;
}
target.rotation = endRot;
}
void SetJumpInWater(bool b)
{
m_bJumpInWater = b;
}
// Is host in sliding state (in the state, host is sliding on slope) ?
public int GetProfession()
{
return m_iProfession;
}
public void OnAllInitDataReady()
{
if (IsDead())
{
/* CECGameUIMan pGameUI = g_pGame.GetGameRun().GetUIManager().GetInGameUIMan();
pGameUI.PopupReviveDialog(true);*/
PopupManager.NotifyPlayerDied();
}
m_bEnterGame = true;
}
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()
{
return m_idSelTarget;
}
// Auto select a attackable target — cycle by distance (nearest → farthest, then repeat)
// 自动选择可攻击目标 — 按距离循环(从近到远,再回到最近)
public int AutoSelectTarget()
{
if (!CanDo(ActionCanDo.CANDO_CHANGESELECT))
return 0;
int idCurSel = (m_idSelTarget != 0 && m_idSelTarget != m_PlayerInfo.cid) ? m_idSelTarget : 0;
// idCurSel == 0 so TabSelectCandidates includes current target for index-based cycling
List<EC_ElsePlayer> aCandPlayers = new List<EC_ElsePlayer>();
EC_ManMessageMono.Instance.EC_ManPlayer.TabSelectCandidates(0, aCandPlayers);
List<CECNPC> aCandNPCs = new List<CECNPC>();
EC_ManMessageMono.Instance.CECNPCMan.TabSelectCandidates(0, aCandNPCs);
var sorted = new List<(float dist, int id, CECObject obj)>();
foreach (var pPlayer in aCandPlayers)
{
if (pPlayer == null)
continue;
sorted.Add((pPlayer.GetDistToHost(), pPlayer.GetCharacterID(), pPlayer));
}
foreach (var pNPC in aCandNPCs)
{
if (pNPC == null)
continue;
sorted.Add((pNPC.GetDistToHost(), pNPC.GetNPCID(), pNPC));
}
sorted.Sort((a, b) =>
{
int c = a.dist.CompareTo(b.dist);
return c != 0 ? c : a.id.CompareTo(b.id);
});
m_aTabSels.Clear();
for (int i = 0; i < sorted.Count; i++)
m_aTabSels.Add(sorted[i].obj);
int idNewSel = 0;
if (sorted.Count == 0)
{
m_lastAutoSelectNearestId = 0;
idNewSel = idCurSel;
}
else
{
int nearestId = sorted[0].id;
if (nearestId != 0 && nearestId != m_lastAutoSelectNearestId)
{
// If the nearest target changed (movement/spawn/despawn), reset cycle back to nearest
m_lastAutoSelectNearestId = nearestId;
idNewSel = nearestId;
}
else
{
m_lastAutoSelectNearestId = nearestId;
int idx = -1;
for (int i = 0; i < sorted.Count; i++)
{
if (sorted[i].id == idCurSel)
{
idx = i;
break;
}
}
int nextIdx = idx < 0 ? 0 : (idx + 1) % sorted.Count;
idNewSel = sorted[nextIdx].id;
}
}
// Select the target if found / 如果找到目标则选择它
if (idNewSel != 0)
{
if (idNewSel != idCurSel)
{
SelectTarget(idNewSel);
}
}
return idNewSel;
}
//public float GetSwimSpeedSev()
//{
// float fSpeedSev = GetSwimSpeed();
// while (true)
// {
// if (!IsUnderWater()) break;
// //CECWorld* pWorld = g_pGame.GetGameRun().GetWorld();
// //if (!pWorld) break;
// const A3DVECTOR3 vPos = GetPos();
// float fTerrainHeight = pWorld.GetTerrainHeight(vPos);
// float fWaterHeight = pWorld.GetWaterHeight(vPos);
// if (fWaterHeight <= fTerrainHeight) break;
// float fBorderLine = fWaterHeight - 2.0f;
// if (vPos.y <= fBorderLine) break;
// // ·þÎñÆ÷¶Ë½«Ë®ÃæÒÔÏÂ2Ã×ÒÔÉÏ´¦ÀíΪ run_speed£¨ÓÐÎÊÌ⣩
// // µ«Î´Ê¹ÓüÓËÙ¼¼ÄÜʱ swim_speed СÓÚ run_speed£¬
// // ¿ÉÒÔÔÚË®ÃæÒÔÏÂ2Ã×ÒÔÉÏ»ñÈ¡³¬¹ý swim_speed µÄËÙ¶È£¬Òò´Ë£¬´Ë´¦È¡Á½Õß½ÏСֵΪºÏÀí×ö·¨
// fSpeedSev = min(m_ExtProps.mv.run_speed, fSpeedSev);
// break;
// }
// return fSpeedSev;
//}
// Does host player have specified way point ?
bool HasWayPoint(uint? wID)
{
if (wID == null)
{
return false;
}
for (int i = 0; i < m_aWayPoints.Count; i++)
{
if (m_aWayPoints[i] == wID)
return true;
}
return false;
}
// Get faction role
public int GetFRoleID()
{
return m_idFRole;
}
public bool InSlidingState()
{
if (m_iMoveMode != (int)MoveMode.MOVE_SLIDE)
return false;
var work = m_pWorkMan.GetRunningWork(Host_work_ID.WORK_STAND);
var standWork = work as CECHPWorkStand;
if (standWork != null)
{
if (standWork.GetStopSlideFlag())
return false;
}
return true;
}
public FACTION_FORTRESS_CONFIG GetFactionFortressConfig()
{
elementdataman pDataMan = EC_Game.GetElementDataMan();
DATA_TYPE dt = DATA_TYPE.DT_INVALID;
var ob = pDataMan.get_data_ptr(854, ID_SPACE.ID_SPACE_CONFIG, ref dt);
FACTION_FORTRESS_CONFIG pConfig = new FACTION_FORTRESS_CONFIG();
if (ob == null || dt != DATA_TYPE.DT_FACTION_FORTRESS_CONFIG)
{
// ûÓлùµØÅäÖñí
pConfig.require_level = int.MinValue;
}
else
{
pConfig = (FACTION_FORTRESS_CONFIG)ob;
}
return pConfig;
}
public bool IsInFortressWar()
{
bool bInWar = false;
if (IsInFortress())
{
int serverTime = EC_Game.GetServerGMTTime();
if (m_fortressEnter.end_time > serverTime)
bInWar = true;
}
return bInWar;
}
public bool IsFactionMember(int idTargetFaction)
{
// µ±Ç°ÊôÓÚij°ïÅÉ£¬ÇÒ¶Ô·½ÊôÓÚͬһ°ïÅÉʱ·µ»Ø true
return GetFactionID() != 0 && GetFactionID() == idTargetFaction;
}
public bool IsFactionAllianceMember(int idTargetFaction)
{
// µ±Ç°ÊôÓÚij°ïÅÉ£¬¶Ô·½ÊôÓÚͬһ°ïÅÉ¡¢»òÕßÊôÓÚͬÃ˰ïÅÉ
return GetFactionID() != 0
&& idTargetFaction != 0
&& (GetFactionID() == idTargetFaction || EC_Game.GetFactionMan().IsFactionAlliance(idTargetFaction));
}
public int GetCountry()
{
return m_idCountry;
}
// Get battle info.
//public BATTLEINFO GetBattleInfo() { return m_BattleInfo; }
//public bool IsInCountryWar() { return IsInBattle() && GetBattleInfo().IsCountryWar(); }
// End NPC service
public void EndNPCService()
{
m_idSevNPC = 0;
m_bTalkWithNPC = false;
m_iBoothState = 0;
m_bIsInKingService = false;
//m_pOffShopCtrl.SetNPCSevFlag(COfflineShopCtrl::NPCSEV_NULL);
}
void UpdateGFXs(float dwDeltaTime)
{
// if (m_pLevelUpGFX)
// m_pLevelUpGFX.SetParentTM(GetAbsoluteTM());
// Update hover GFX / 更新悬停特效
if (m_pHoverGFX) // && m_idCurHover != m_idSelTarget)
{
if (!IsChangingFace() && (GPDataTypeHelper.ISPLAYERID(m_idCurHover) || GPDataTypeHelper.ISNPCID(m_idCurHover)))
{
CECObject pObject = EC_ManMessageMono.Instance?.GetObject(m_idCurHover, 1);
if (pObject)
{
// Start GFX if stopped / 如果停止则启动特效
if (m_pHoverGFX.GetState() == GFX_STATE.ST_STOP)
m_pHoverGFX.Play();
// Update parent transform every frame to follow target / 每帧更新父变换以跟随目标
if (m_pHoverGFX.transform.parent != pObject.transform)
{
m_pHoverGFX.transform.parent = pObject.transform;
m_pHoverGFX.transform.localPosition = Vector3.zero;
}
}
else
m_pHoverGFX.Stop(true);
}
else
m_pHoverGFX.Stop(true);
}
// Update selected GFX / 更新选中特效
if (m_pSelectedGFX)
{
if (!IsChangingFace() && (GPDataTypeHelper.ISPLAYERID(m_idSelTarget) || GPDataTypeHelper.ISNPCID(m_idSelTarget)))
{
CECObject pObject = EC_ManMessageMono.Instance?.GetObject(m_idSelTarget, 1);
if (pObject)
{
// Start GFX if stopped / 如果停止则启动特效
if (m_pSelectedGFX.GetState() == GFX_STATE.ST_STOP)
m_pSelectedGFX.Play();
// Update parent transform every frame to follow target / 每帧更新父变换以跟随目标
if (m_pSelectedGFX.transform.parent != pObject.transform)
{
m_pSelectedGFX.transform.parent = pObject.transform;
m_pSelectedGFX.transform.localPosition = Vector3.zero;
}
}
else
m_pSelectedGFX.Stop(true);
}
else
m_pSelectedGFX.Stop(true);
}
// if (m_pFloatDust)
// {
// A3DTerrainWater* pWater = g_pGame.GetGameRun().GetWorld().GetTerrainWater();
//
// if (pWater.IsUnderWater(m_CameraCoord.GetPos()))
// {
// if (m_pFloatDust.GetState() == ST_STOP)
// {
// m_pFloatDust.Start(true);
// m_pFloatDust.TickAnimation(2000);
// }
//
// m_pFloatDust.SetParentTM(GetAbsoluteTM());
// }
// else if (m_pFloatDust.GetState() != ST_STOP)
// m_pFloatDust.Stop();
// }
// UpdateMonsterSpiritGfx(dwDeltaTime);
}
public byte GetRealmLevel() { return m_RealmLevel; }
// Level up
public void LevelUp()
{
// CECGameSession *pSession = g_pGame.GetGameSession();
//
// m_BasicProps.iLevel++;
// g_pGame.GetGameRun().AddFixedMessage(FIXMSG_LEVELUP, m_BasicProps.iLevel);
//
// // Get all extend properties
// pSession.c2s_CmdGetExtProps();
// if (m_pLevelUpGFX)
// m_pLevelUpGFX.Start(true);
PlayGfx(EC_Resource.res_GFXFile((int)GfxResourceType.RES_GFX_LEVELUP), null, 1f, 1); //PLAYERMODEL_TYPEALL
EventBus.Publish(new HostPlayerLevelUpUIEvent());
// // Popup notify bubble text
BubbleText((int)BubbleTextType.BUBBLE_LEVELUP, 0);
//
// // Notify my friends that my level changed
// ACHAR szInfo[40];
// a_sprintf(szInfo, _AL("L%d"), m_BasicProps.iLevel);
//
// for (int i=0; i < m_pFriendMan.GetGroupNum(); i++)
// {
// CECFriendMan::GROUP* pGroup = m_pFriendMan.GetGroupByIndex(i);
// for (int j=0; j < pGroup.aFriends.GetSize(); j++)
// {
// CECFriendMan::FRIEND* pFriend = pGroup.aFriends[j];
// if (pFriend.IsGameOnline())
// {
// pSession.SendPrivateChatData(pFriend.GetName(),
// szInfo, GNET::CHANNEL_USERINFO, pFriend.id);
// }
// }
// }
//
// if (GetBasicProps().iLevel==30)
// {
// CECGameUIMan* pGameUI = g_pGame.GetGameRun().GetUIManager().GetInGameUIMan();
// pGameUI.AddChatMessage(pGameUI.GetStringFromTable(9638), GP_CHAT_SYSTEM);
// }
// if (GetBasicProps().iLevel>31)
// {
// CECGameUIMan* pGameUI = g_pGame.GetGameRun().GetUIManager().GetInGameUIMan();
// ((CDlgOnlineAward*)pGameUI.GetDialog("Win_AddExp2")).RestartWhenLevelup();
// }
}
// Estimate mouse cursor
private void EstimateCursor()
{
m_cursorUpdateTimer += Time.deltaTime;
if (m_cursorUpdateTimer < CURSOR_UPDATE_INTERVAL)
return;
m_cursorUpdateTimer = 0f;
// Early exit checks
if (IsChangingFace() || mainCam == null || m_cachedMouse == null)
{
m_idCurHover = 0;
return;
}
// Get mouse position using cached device
Vector2 mousePosition = m_cachedMouse.position.ReadValue();
// Early exit if mouse hasn't moved significantly (2 pixel threshold)
if (Vector2.Distance(mousePosition, m_lastMousePosition) < 2f)
return;
m_lastMousePosition = mousePosition;
m_idCurHover = 0;
CursorType cursorType = CursorType.RES_CUR_NORMAL;
// Check modifier keys using cached keyboard
bool isShiftPressed = m_cachedKeyboard != null &&
(m_cachedKeyboard.leftShiftKey.isPressed || m_cachedKeyboard.rightShiftKey.isPressed);
Ray ray = mainCam.ScreenPointToRay(mousePosition);
RaycastHit hit;
// You can add a layer mask here to only raycast against specific layers
// LayerMask interactableMask = LayerMask.GetMask("NPC", "Player", "Item");
// if (Physics.Raycast(ray, out hit, 100f, interactableMask))
if (Physics.Raycast(ray, out hit, 1000f)) // Reduced from 1000f to 100f for better performance
{
// Try to get CECObject component (cached lookup)
CECObject hitObject = hit.collider.GetComponent<CECObject>();
if (hitObject != null)
{
int idHitObject = CECObject.GetObjectID(hitObject);
if (idHitObject != 0)
{
bool bForceAttack = isShiftPressed;
// Check object type and set appropriate cursor
if (GPDataTypeHelper.ISNPCID(idHitObject))
{
// NPC handling
CECNPC pNPC = EC_ManMessageMono.Instance?.CECNPCMan?.GetNPC(idHitObject);
if (pNPC != null && !pNPC.IsDead())
{
m_idCurHover = idHitObject;
if (m_idSelTarget == idHitObject && AttackableJudge(idHitObject, bForceAttack) == 1)
{
cursorType = CursorType.RES_CUR_ATTACK;
}
else if (pNPC.IsServerNPC())
{
if (!IsInBattle() || InSameBattleCamp(pNPC))
{
cursorType = CursorType.RES_CUR_TALK;
}
}
}
}
else if (GPDataTypeHelper.ISPLAYERID(idHitObject))
{
// Player handling
EC_ElsePlayer pPlayer =
EC_ManMessageMono.Instance?.GetECManPlayer?.GetPlayer(idHitObject) as EC_ElsePlayer;
if (pPlayer != null)
{
m_idCurHover = idHitObject;
if (m_idSelTarget == idHitObject && AttackableJudge(idHitObject, bForceAttack) == 1)
{
cursorType = CursorType.RES_CUR_ATTACK;
}
}
}
else if (GPDataTypeHelper.ISMATTERID(idHitObject))
{
//todo
// BMLogger.LogError($"[EstimateCursor]- GPDataTypeHelper.ISMATTERID: {idHitObject}");
// Matter/item handling (uncomment when CECMatter is implemented)
// CECMatter pMatter = GetMatterManager()?.GetMatter(idHitObject);
// if (pMatter != null)
// {
// if (!pMatter.IsMine())
// cursorType = CursorType.Pickup;
// else if (CanGatherMatter(pMatter))
// cursorType = pMatter.IsMonsterSpiritMine() ? CursorType.Swallow : CursorType.Dig;
//
// if (cursorType != CursorType.Normal)
// m_idCurHover = idHitObject;
// }
}
}
}
}
// Apply cursor change
EC_Game.ChangeCursor((int)cursorType);
}
bool IsJumpInWater()
{
return m_bJumpInWater;
}
// Is under water
bool IsUnderWater()
{
return m_iMoveEnv == Move_environment.MOVEENV_WATER ? true : false;
}
// Can jump or take off in water ?
public bool IsSitting()
{
return (m_dwStates & PlayerNPCState.GP_STATE_SITDOWN) != 0 ? true : false;
}
// Is host player open trash box ?
bool IsUsingTrashBox()
{
return m_bUsingTrashBox;
}
// Is host player talking with NPC ?
public bool IsTalkingWithNPC()
{
return m_bTalkWithNPC;
}
// Is reviving
bool IsReviving()
{
return m_pWorkMan.IsReviving();
}
// Is spelling magic
public bool IsSpellingMagic()
{
if (m_pWorkMan == null) return false;
return m_pWorkMan.IsSpellingMagic();
}
// Is picking up something
bool IsPicking()
{
return false;
// TODO: fix later
//CECHPWork pWork = m_pWorkMan.GetRunningWork(Host_work_ID.WORK_PICKUP);
//if (pWork != null)
//{
// return !(pWork as CECHPWorkPick).IsGather();
//}
//else
// return false;
}
// Is gathering resources
public bool IsGathering()
{
if (m_pWorkMan == null) return false;
CECHPWork pWork = m_pWorkMan.GetRunningWork(Host_work_ID.WORK_PICKUP);
if (pWork != null)
return ((EC_HPWorkPick)pWork).IsGather();
return false;
}
// Is using item ?
// Estimate move environment
void EstimateMoveEnv(A3DVECTOR3 vPos)
{
if (IsFlying())
{
m_iMoveEnv = Move_environment.MOVEENV_AIR;
return;
}
CECWorld pWorld = EC_Game.GetGameRun().GetWorld();
Vector3 vStart = EC_Utility.ToVector3(vPos);
Vector3 vGndPos0 = Vector3.zero;
Vector3 vTestPos0 = m_MoveCtrl.GetLastSevPos() + g_vAxisY * m_aabbServer.Extents.y;
VertRayTrace(vTestPos0, ref vGndPos0, ref m_GndInfo.vGndNormal, 1000f);
m_GndInfo.fGndHei = vGndPos0.y;
Vector3 vAABBGnd = Vector3.zero;
VertAABBTrace(vTestPos0, EC_Utility.ToVector3(m_aabbServer.Extents), ref vAABBGnd, ref m_GndInfo.vGndNormal,
1000f);
vAABBGnd.y -= m_aabbServer.Extents.y;
bool bIsInAir = false;
if (m_MoveCtrl.GetLastSevPos().y - vAABBGnd.y > 0.2f)
bIsInAir = true;
Vector3 vGndPos = Vector3.zero;
A3DVECTOR3 vTestPos = vPos + EC_Utility.ToA3DVECTOR3(g_vAxisY) * m_aabbServer.Extents.y;
VertRayTrace(EC_Utility.ToVector3(vTestPos), ref vGndPos, ref m_GndInfo.vGndNormal, 1000f);
m_GndInfo.fGndHei = vGndPos.y;
if (Physics.RaycastNonAlloc(EC_Utility.ToVector3(vTestPos) + Vector3.up * 500f, Vector3.down, hits, 1000f, 1 << 8) > 0)
{
m_GndInfo.fWaterHei = hits[0].point.y;
}
VertAABBTrace(EC_Utility.ToVector3(vTestPos), EC_Utility.ToVector3(m_aabbServer.Extents), ref vAABBGnd,
ref m_GndInfo.vGndNormal, 1000f);
vAABBGnd.y -= m_aabbServer.Extents.y;
int iNewEnv = Move_environment.MOVEENV_GROUND;
if (CheckWaterMoveEnv(vPos, m_GndInfo.fWaterHei, vAABBGnd.y))
{
iNewEnv = Move_environment.MOVEENV_WATER;
}
if (iNewEnv == Move_environment.MOVEENV_GROUND && GetPos().y - vAABBGnd.y < 0.2f && bIsInAir &&
EC_Utility.ToVector3(GetPos()) != m_MoveCtrl.GetLastSevPos())
{
m_MoveCtrl.SendMoveCmd(GetPos(), 2, EC_Utility.ToA3DVECTOR3(g_vAxisY), m_CDRInfo.vAbsVelocity,
m_iMoveMode, true);
// BubbleText(BUBBLE_LEVELUP, 0);
}
if (iNewEnv == Move_environment.MOVEENV_GROUND)
{
m_GndInfo.bOnGround = true;
// if (vPos.y > m_GndInfo.fGndHei + 0.2f)
if (m_CDRInfo.vTPNormal == new A3DVECTOR3(0))
{
if (m_iMoveMode != (int)MoveMode.MOVE_FREEFALL)
m_iMoveMode = (int)MoveMode.MOVE_FREEFALL;
m_GndInfo.bOnGround = false;
if (IsJumping() && m_CDRInfo.vAbsVelocity.y < 0.0f && vPos.y - vAABBGnd.y < 0.6f)
{
PlayAction((int)PLAYER_ACTION_TYPE.ACT_JUMP_LAND, false);
}
}
else
{
if (IsJumping() && m_CDRInfo.vAbsVelocity.y < 0.0f && vPos.y - vAABBGnd.y < 0.6f)
{
PlayAction((int)PLAYER_ACTION_TYPE.ACT_JUMP_LAND, false);
ResetJump();
}
// if (m_GndInfo.vGndNormal.y < EC_SLOPE_Y)
if (m_CDRInfo.vTPNormal.y < EC_SLOPE_Y)
{
if (!m_MoveCtrl.GetSlideLock())
{
if (m_iMoveMode != (int)MoveMode.MOVE_SLIDE)
{
m_iOldWalkMode = m_iMoveMode;
m_iMoveMode = (int)MoveMode.MOVE_SLIDE;
}
}
else
{
m_MoveCtrl.SetSlideLock(false);
m_iMoveMode = (int)MoveMode.MOVE_STAND;
}
}
else
{
m_MoveCtrl.SetSlideLock(false);
if (m_iMoveMode == (int)MoveMode.MOVE_FREEFALL)
{
m_iMoveMode = (int)MoveMode.MOVE_STAND;
}
else if (m_iMoveMode == (int)MoveMode.MOVE_SLIDE)
m_iMoveMode = m_iOldWalkMode;
}
}
}
else if (iNewEnv == Move_environment.MOVEENV_WATER)
{
m_CDRInfo.fYVel = 0.0f;
if (m_iMoveMode == (int)MoveMode.MOVE_SLIDE)
{
if (m_pWorkMan.IsMoving())
m_iMoveMode = (int)MoveMode.MOVE_MOVE;
else
m_iMoveMode = (int)MoveMode.MOVE_STAND;
}
}
m_iMoveEnv = iNewEnv;
}
// ÏòÏ Trace µØÐκͽ¨Öþ£¬²¢·µ»ØµÚÒ»¸öÅöײµãµÄÇé¿ö
bool VertRayTrace(Vector3 vPos, ref Vector3 vHitPos, ref A3DVECTOR3 vHitNormal, float DeltaY)
{
Vector3 vTerrainPos = Vector3.zero;
Vector3 vTerrainNormal = Vector3.zero;
Vector3 vBuildingPos = Vector3.zero;
Vector3 vBuildingNormal = Vector3.zero;
LayerMask layerMaskTerrain = 1 << 6;
LayerMask layerMaskBush = 1 << 7;
if (Physics.RaycastNonAlloc(vPos, (Vector3.down), hits, 1000f, layerMaskTerrain) > 0 /*&& hits[0].distance > 0.0009f*/)
{
vTerrainPos = hits[0].point;
vTerrainNormal = hits[0].normal;
}
if (Physics.RaycastNonAlloc(vPos, (Vector3.down), hits, DeltaY, layerMaskBush) > 0 /*&& hits[0].distance > 0.0009f*/)
{
if (vBuildingPos.y > vTerrainPos.y)
{
// Ó뽨Öþ·¢ÉúÁËÅöײ
vHitPos = vBuildingPos;
vHitNormal = EC_Utility.ToA3DVECTOR3(vBuildingNormal);
return true;
}
}
if (vTerrainPos.y > vPos.y || (vTerrainPos.y <= vPos.y && vTerrainPos.y >= vPos.y - DeltaY))
{
vHitPos = vTerrainPos;
vHitNormal = EC_Utility.ToA3DVECTOR3(vTerrainNormal);
return true;
}
return false;
}
void VertAABBTrace(Vector3 vCenter, Vector3 vExt, ref Vector3 vHitPos, ref A3DVECTOR3 vHitNormal,
float DeltaY /* =100.0f */)
{
vHitPos = vCenter;
vHitPos.y -= DeltaY;
LayerMask layerMask = 1 << 6 | 1 << 7;
hits = new RaycastHit[5];
int count = Physics.BoxCastNonAlloc(vCenter, vExt, (Vector3.down).normalized,
hits, transform.rotation, DeltaY, layerMask);
if (count == 0 || (count > 0 && hits[0].distance < 0.0009f))
{
vHitPos = vCenter;
vHitNormal = EC_Utility.ToA3DVECTOR3(Vector3.up);
}
else
{
if (Math.Abs(hits[0].distance - 0f) <= float.Epsilon)
{
// halfBox with y = 0.05f? I need Y box check too small.
count = Physics.BoxCastNonAlloc(vCenter, new Vector3(vExt.x, 0.05f, vExt.z), (Vector3.down).normalized,
hits, transform.rotation, DeltaY, layerMask);
if (count == 0 || ( count > 0 && hits[0].distance < 0.0009f))
{
vHitPos = vCenter;
vHitNormal = EC_Utility.ToA3DVECTOR3(Vector3.up);
}
else
{
vHitPos = hits[0].point;
vHitPos.y += vExt.y;
vHitNormal = EC_Utility.ToA3DVECTOR3(hits[0].normal);
}
}
else
{
vHitPos = hits[0].point;
vHitPos.y += vExt.y;
vHitNormal = EC_Utility.ToA3DVECTOR3(hits[0].normal);
}
}
}
// Get cool time
public virtual int GetCoolTime(int iIndex, out int piMax /* NULL */)
{
piMax = 1;
if (iIndex >= 0 && iIndex < (int)CoolTimeIndex.GP_CT_MAX)
{
if (piMax > 0)
piMax = m_aCoolTimes[iIndex].iMaxTime;
return m_aCoolTimes[iIndex].iCurTime;
}
return 0;
}
// Play a pose
public bool CmdStartPose(int iPose)
{
// first of all see if we need to cancel sitdown work.
if (m_pWorkMan.IsSitting())
{
UnityGameSession.c2s_CmdStandUp();
return false;
}
if (!CanDo(ActionCanDo.CANDO_PLAYPOSE))
return false;
if (!m_pWorkMan.IsStanding())
return false;
if (iPose == (int)RoleExpression.ROLEEXP_SITDOWN)
{
// UnityGameSession.c2s_CmdSessionEmote(iPose);
}
else if (iPose == (int)RoleExpression.ROLEEXP_KISS)
{
// if (GPDataTypeHelper.ISPLAYERID(m_idSelTarget))
// UnityGameSession.c2s_CmdConEmoteRequest(RoleExpression.ROLEEXP_KISS, m_idSelTarget);
}
else
UnityGameSession.c2s_CmdEmoteAction((uint)iPose);
return true;
}
public enum StateAnim
{
Idle = 1,
Walk = 2,
Run = 3,
Jump = 4
}
// Mask of some special extend states which will influence host game logic.
// Logic Influence Extned states
[Flags]
public enum Logic_Influence_Extned_states
{
LIES_SLEEP = 0x0001,
LIES_STUN = 0x0002,
LIES_ROOT = 0x0004,
LIES_NOFGIHT = 0x0008,
LIES_DISABLEFIGHT = 0x000B,
}
public enum BubbleTextType
{
BUBBLE_DAMAGE = 0,
BUBBLE_EXP,
BUBBLE_SP,
BUBBLE_MONEY,
BUBBLE_LEVELUP,
BUBBLE_HITMISSED,
BUBBLE_INVALIDHIT,
BUBBLE_IMMUNE,
BUBBLE_HPWARN,
BUBBLE_MPWARN,
BUBBLE_REBOUND, // ·´µ¯
BUBBLE_BEAT_BACK, // ·´»÷
BUBBLE_ADD, // ÎüѪµÄ¼ÓºÅ
BUBBLE_DODGE_DEBUFF,
BUBBLE_REALMEXP,
}
// ½øÈë»ùµØÐÅÏ¢
public struct FACTION_FORTRESS_ENTER
{
public int faction_id;
public int role_in_war; // 0 : ÖÐÁ¢»ò²»ÔÚ»ùµØ 1:¹¥·½ 2: ÊØ·½
public int end_time;
public FACTION_FORTRESS_ENTER(int faction_id, int role_in_war, int end_time)
{
this.faction_id = faction_id;
this.role_in_war = role_in_war;
this.end_time = end_time;
}
}
public struct EXPToUpLevel
{
public int NeededExp;
public EXPToUpLevel(int neededExp)
{
NeededExp = neededExp;
}
}
public enum MOVE_DIR
{
MD_FORWARD = 0x01,
MD_RIGHT = 0x02,
MD_BACK = 0x04,
MD_LEFT = 0x08,
MD_ABSUP = 0x10,
MD_ABSDOWN = 0x20,
MD_ALL = 0x3f,
};
public struct NPCINFO
{
public string Name; // Movement properties
public int CurrentHealth;
public int MaxHealth; // Attacking properties
public int IDNPC; // Attacking properties
public NPCINFO(string name, int currentHealth, int maxHealth, int idnpc)
{
Name = name;
CurrentHealth = currentHealth;
MaxHealth = maxHealth;
IDNPC = idnpc;
}
}
/// <summary>Fired when selected target HUD should be hidden (deselect or switch).</summary>
public struct TargetHUDClearEvent { }
/// <summary>Host leveled up — UI layer may refresh quest-offer lists, popups, etc. 玩家升级,UI 可刷新可接任务等。</summary>
public struct HostPlayerLevelUpUIEvent { }
public struct GNDINFO
{
public float fGndHei; // Ground height
public float fWaterHei; // Water height
public A3DVECTOR3 vGndNormal; // Terrain normal
public bool bOnGround; // On ground flag
};
public struct InfoHostPlayer
{
public string NameHostPlayer;
public InfoHostPlayer(string name)
{
NameHostPlayer = name;
}
}
public struct REINCARNATION_TOME
{
public int tome_exp;
public char tome_active; // 1¼¤»î0δ¼¤»î
public int max_level; // ÀúÊ·×î¸ßµÈ¼¶
cmd_reincarnation_tome_info._entry[] reincarnations;
static int max_exp;
};
public struct cmd_reincarnation_tome_info
{
public int tome_exp;
public char tome_active; // 1¼¤»î0δ¼¤»î
public int count;
public struct _entry
{
int level;
int timestamp;
int exp;
}
public _entry[] records;
};
// Behavior id used by CanDo()
public static class ActionCanDo
{
public const int CANDO_SITDOWN = 0,
CANDO_MOVETO = 1,
CANDO_MELEE = 2,
CANDO_ASSISTSEL = 3,
CANDO_FLY = 4,
CANDO_PICKUP = 5,
CANDO_TRADE = 6,
CANDO_PLAYPOSE = 7,
CANDO_SPELLMAGIC = 8,
CANDO_USEITEM = 9,
CANDO_JUMP = 10,
CANDO_FOLLOW = 11,
CANDO_GATHER = 12,
CANDO_BOOTH = 13,
CANDO_FLASHMOVE = 14,
CANDO_BINDBUDDY = 15,
CANDO_DUEL = 16,
CANDO_SUMMONPET = 17,
CANDO_CHANGESELECT = 18,
CANDO_REBUILDPET = 19,
CANDO_SWITCH_PARALLEL_WORLD = 20;
}
public bool FindMineTool(int tidMine, ref int piPack, ref int piIndex, ref int pidTool)
{
if (tidMine == 0)
return false;
DATA_TYPE DataType = DATA_TYPE.DT_INVALID;
object pDataPtr = ElementDataManProvider.GetElementDataMan()
.get_data_ptr((uint)tidMine, ID_SPACE.ID_SPACE_ESSENCE, ref DataType);
if (DataType != DATA_TYPE.DT_MINE_ESSENCE)
{
//ASSERT(DataType != DATA_TYPE.DT_MINE_ESSENCE);
return false;
}
MINE_ESSENCE pData = (MINE_ESSENCE)pDataPtr;
int idTool = (int)pData.id_equipment_required;
bool bRet = true;
if (idTool != 0)
{
int iIndex = IvtrPack.FindItem(idTool);
if (iIndex >= 0)
{
piPack = EC_Inventory.Inventory_type.IVTRTYPE_PACK;
piIndex = iIndex;
pidTool = idTool;
}
else if ((iIndex = IvtrTaskPack.FindItem(idTool)) >= 0)
{
piPack = EC_Inventory.Inventory_type.IVTRTYPE_TASKPACK;
piIndex = iIndex;
pidTool = idTool;
}
else
bRet = false;
}
else
{
piPack = 0;
piIndex = 0;
pidTool = 0;
}
return bRet;
}
public CECCounter GetGatherCnt() { return m_GatherCnt; }
public void UpdateTimers(float dwDeltaTime)
{
// Get real time tick of this frame
var iRealTime = EC_Game.GetRealTickTime();
// Update flysword time
/*if (IsFlying() && GetRushFlyFlag())
{
CECIvtrItem pItem = m_pEquipPack.GetItem(EQUIPIVTR_FLYSWORD);
Debug.Assert(pItem != null);
if (pItem.GetClassID() == CECIvtrItem.ICID_FLYSWORD)
{
CECIvtrFlySword pFlySword = (CECIvtrFlySword)pItem;
pFlySword.TimePass(dwDeltaTime);
}
}*/
int i;
// Update skills
for (i = 0; i < m_aPtSkills.Count; i++)
m_aPtSkills[i].Tick(dwDeltaTime);
for (i = 0; i < m_aPsSkills.Count; i++)
m_aPsSkills[i].Tick(dwDeltaTime);
for (i = 0; i < m_aGoblinSkills.Count; i++)
m_aGoblinSkills[i].Tick(dwDeltaTime);
for (i = 0; i < m_aEquipSkills.Count; i++)
m_aEquipSkills[i].Tick(dwDeltaTime);
if (m_pTargetItemSkill != null)
m_pTargetItemSkill.Tick(dwDeltaTime);
//CECComboSkillState.Instance().Tick();
// Update cool times
for (i = 0; i < (int)CoolTimeIndex.GP_CT_MAX; i++)
m_aCoolTimes[i].Update(dwDeltaTime);
foreach (var kvp in m_skillCoolTime)
{
kvp.Value.Update(dwDeltaTime);
}
// Gather time counter
if (m_GatherCnt.IncCounter(iRealTime * 1000))
m_GatherCnt.Reset(true);
// Incant time counter
if (m_IncantCnt.IncCounter(dwDeltaTime * 1000))
m_IncantCnt.Reset(true);
m_PetOptCnt.IncCounter(iRealTime);
// Bind command cool counter
/* if (m_BindCmdCoolCnt.IncCounter(dwDeltaTime))
m_BindCmdCoolCnt.Reset(true);
// Auto fashion time counter
if (m_bAutoFashion && GetBoothState() != 2 && !IsShapeChanged())
{
if (m_AutoFashionCnt.IncCounter(dwDeltaTime))
{
if (!CheckAutoFashionCondition())
{
while (!EquipFashionBySuitID((m_iCurFashionSuitID + 1) % GetMaxFashionSuitNum()))
m_iCurFashionSuitID++;
m_AutoFashionCnt.Reset();
}
}
}
// Auto convert Yinpiao
if (m_AutoYinpiao.open)
{
if (m_AutoYinpiao.cnt.IncCounter(dwDeltaTime))
{
ExchangeYinpiao();
m_AutoYinpiao.cnt.Reset();
}
}
// Control the dialog of the target item
m_TargetItemDlgCtrl.Update(dwDeltaTime);
// For some reasons on server, sometimes friend list couldn't be got
// successfully. Try to get it again every 20s if this case really happen
if (m_pFriendMan != null && !m_pFriendMan.CheckInit())
{
m_iGetFriendCnt -= dwDeltaTime;
if (m_iGetFriendCnt < 0)
{
EC_Game.GetGameSession().friend_GetList();
m_iGetFriendCnt = 60000;
}
}
// Duel stopping time counter
if (m_pvp.iDuelState == DUEL_ST_PREPARE)
{
m_pvp.iDuelTimeCnt -= dwDeltaTime;
if (m_pvp.iDuelTimeCnt < 0)
m_pvp.iDuelTimeCnt = 0;
}
else if (m_pvp.iDuelState == DUEL_ST_INDUEL)
{
m_pvp.iDuelTimeCnt += dwDeltaTime;
}
else if (m_pvp.iDuelState == DUEL_ST_STOPPING)
{
m_pvp.iDuelTimeCnt -= dwDeltaTime;
if (m_pvp.iDuelTimeCnt < 0)
{
m_pvp.iDuelTimeCnt = 0;
m_pvp.iDuelState = DUEL_ST_NONE;
m_pvp.idDuelOpp = 0;
}
}
// Update pariah time counter
if (m_dwPariahTime > 0)
{
if (m_dwPariahTime > (uint)dwDeltaTime)
m_dwPariahTime -= (uint)dwDeltaTime;
else
m_dwPariahTime = 0;
}
// Update pet operation time counter
m_PetOptCnt.IncCounter(dwDeltaTime);
// Update battle result time counter
if (IsInBattle() && !IsInFortress() && m_BattleInfo.iResult != 0 && m_BattleInfo.iResultCnt != 0)
{
// iResultCnt is time counter (likely in milliseconds as int), dwDeltaTime is in seconds (float)
// iResultCnt是时间计数器(可能是毫秒为int),dwDeltaTime是秒数(float
// Convert seconds to milliseconds and subtract / 将秒转换为毫秒并减去
int deltaTimeMs = (int)(dwDeltaTime * 1000f);
if ((m_BattleInfo.iResultCnt -= deltaTimeMs) < 0)
m_BattleInfo.iResultCnt = 0;
}
// Update pet corral
if (m_pPetCorral != null)
m_pPetCorral.Tick((uint)dwDeltaTime);
// Update the related people
var keysToRemove = new List<int>();
foreach (var kvp in m_RelatedPlayer)
{
m_RelatedPlayer[kvp.Key] -= dwDeltaTime;
if (m_RelatedPlayer[kvp.Key] <= 0)
keysToRemove.Add(kvp.Key);
}
foreach (var key in keysToRemove)
{
m_RelatedPlayer.Remove(key);
}*/
}
public struct COOLTIME
{
public int iCurTime;
public int iMaxTime;
public void Update(float dwDeltaTime)
{
if (iCurTime > 0)
{
iCurTime -= (int)(dwDeltaTime * 1000);
Mathf.Clamp(iCurTime, 0, float.MaxValue);
}
}
}
// Start / Stop flying
public bool CmdFly(bool bForceFly)
{
//if (m_pActionSwitcher)
// m_pActionSwitcher.PostMessge(CECActionSwitcherBase::MSG_FLY);
// first of all see if we need to cancel sitdown work.
if (!CanDo(ActionCanDo.CANDO_FLY))
return false;
EC_IvtrItem pItem = IvtrEquipPack.GetItem(InventoryConst.EQUIPIVTR_FLYSWORD);
if (pItem == null)
return false;
if (pItem is EC_IvtrEquip)
{
if ((pItem as EC_IvtrEquip).IsDestroying())
return false;
}
if (!IsFlying())
{
// TODO: Maybe we should let server tell us whether we can fly or not
bool bCanFly = true;
if (m_iMoveEnv == (int)MoveEnvironment.MOVEENV_AIR)
bCanFly = false;
else if (m_iMoveEnv == (int)MoveEnvironment.MOVEENV_WATER && !CanTakeOffWater())
bCanFly = false;
if (!bCanFly)
return false;
}
UnityGameSession.c2s_SendCmdUseItem(InventoryConst.IVTRTYPE_EQUIPPACK, InventoryConst.EQUIPIVTR_FLYSWORD, pItem.GetTemplateID(), 1);
return true;
}
public CECCounter GetIncantCnt() { return m_IncantCnt; }
// Get key object(NPC..) coordinates
public A3DVECTOR3 GetObjectCoordinates(int idTarget, out List<OBJECT_COORD> TargetCoord, ref bool bInTable)
{
TargetCoord = new List<OBJECT_COORD>();
A3DVECTOR3 vDestPos = new A3DVECTOR3(0, 0, 0);
bInTable = false;
// Get object coordinates from CECGame::m_CoordTab
string szText = idTarget.ToString();
List<OBJECT_COORD> originalCoords = new List<OBJECT_COORD>();
int iCount = EC_Game.GetObjectCoord(szText, out originalCoords);
if (iCount == 0)
{
return vDestPos;
}
float fMinDist = 99999999.0f;
// Get Current map name, such as 'a32' etc.
int idInstance = CECGameRun.Instance.GetWorld().GetInstanceID();
CECInstance pInstance = CECGameRun.Instance.GetInstance(idInstance);
string strCurMap = pInstance.GetPath();
// ȼͬһͼǷҪҵƷ
bool bHasObjectInCurrentInstance = originalCoords.Any(coord => coord.strMap == strCurMap);
// Iterate over original list and build filtered TargetCoord list
for (int i = 0; i < iCount; i++)
{
OBJECT_COORD objCoord = originalCoords[i];
if (strCurMap == objCoord.strMap)
{
TargetCoord.Add(objCoord);
// Check if this is the nearest target
float tempDist = (objCoord.vPos - GetPos()).Magnitude();
if (tempDist < fMinDist)
{
fMinDist = tempDist;
bInTable = true;
vDestPos = objCoord.vPos;
}
}
// ǰͼûĿĻĿڵͼڵǰͼ
else if (!bHasObjectInCurrentInstance)
{
// find the entrance of instance
List<OBJECT_COORD> instCoord = new List<OBJECT_COORD>();
int iCount2 = EC_Game.GetObjectCoord(objCoord.strMap, out instCoord);
for (int j = 0; j < iCount2; ++j)
{
if (instCoord[j].strMap == strCurMap)
{
TargetCoord.Add(instCoord[j]);
// Check if this is the nearest target
float tempDist = (instCoord[j].vPos - GetPos()).Magnitude();
if (tempDist < fMinDist)
{
fMinDist = tempDist;
bInTable = true;
vDestPos = instCoord[j].vPos;
}
}
}
}
}
return vDestPos;
}
public int GetRealmLayer() { return m_RealmLevel / 100; }
public int GetRealmSubLevel() { return m_RealmLevel % 100; }
public static int GetRealmLayer(int realmLevel) { return realmLevel > 0 ? (realmLevel + 9) / 10 : 0; }
public static int GetRealmSubLevel(int realmLevel) { return realmLevel > 0 ? (realmLevel % 10 > 0 ? realmLevel % 10 : 10) : 0; }
// <summary>
// Calculate distance to an object and optionally retrieve the object reference
// 计算到对象的距离,并可选地获取对象引用
// </summary>
// <param name="idObject">Target object ID / 目标对象ID</param>
// <param name="pfDist">Output distance / 输出距离</param>
// <param name="ppObject">Output object reference (optional) / 输出对象引用(可选)</param>
// <returns>True if calculation succeeded / 计算成功返回true</returns>
public bool CalcDist(int idObject, out float pfDist, out CECObject ppObject)
{
pfDist = 0.0f;
ppObject = null;
if (idObject == 0 || idObject == m_PlayerInfo.cid)
return false;
CECWorld pWorld = CECGameRun.Instance.GetWorld();
if (pWorld == null)
return false;
CECObject pObject = pWorld.GetObject(idObject, 1);
if (ppObject == null)
return false;
ppObject = pObject;
float fDist = 0.0f;
if (GPDataTypeHelper.ISNPCID(idObject))
{
CECNPC pNPC = pObject as CECNPC;
if (pNPC == null)
return false;
fDist = pNPC.CalcDist(GetPos(), true);
}
else if (GPDataTypeHelper.ISMATTERID(idObject))
{
Debug.Assert(pObject.GetClassID() == Class_ID.OCID_MATTER);
CECMatter pMatter = pObject as CECMatter;
if (pMatter == null)
return false;
A3DVECTOR3 vDelta = pMatter.GetPos() - GetPos();
fDist = A3d_Magnitude(vDelta);
}
else
{
return false;
}
pfDist = fDist;
return true;
}
// Helper method overload without object output
public bool CalcDist(int idObject, out float pfDist)
{
CECObject pObject;
return CalcDist(idObject, out pfDist, out pObject);
}
private void UpdatePosWing()
{
if(m_HH_chibang == null)
{
m_HH_chibang = FindChildRecursive(m_pPlayerModel.transform, "HH_chibang");
}
if(m_HH_feijian == null)
{
m_HH_feijian = FindChildRecursive(m_pPlayerModel.transform, "HH_feijian");
}
if(m_Wing != null)
{
if (m_wingType == enumWingType.WINGTYPE_WING)
{
if(m_CC_chibang == null)
{
m_CC_chibang = FindChildRecursive(m_Wing, "CC_chibang");
}
m_delta_CC_to_HH = (m_Wing.position - m_CC_chibang.position);
m_Wing.position = m_HH_chibang.position + m_delta_CC_to_HH;
}
else if (m_wingType == enumWingType.WINGTYPE_FLYSWORD)
{
if (m_CC_feijian == null)
{
m_CC_feijian = FindChildRecursive(m_Wing, "CC_feijian");
}
//m_delta_CC_to_HH = (m_Wing.position - m_CC_feijian.position);
//m_Wing.position = m_HH_feijian.position + m_delta_CC_to_HH;
m_Wing.position = m_HH_feijian.position;
}
//else
//{
//}
}
//if (m_Wing2 != null)
//{
// if (m_wingType == enumWingType.WINGTYPE_WING)
// {
// m_Wing.transform.position = m_HH_chibang.transform.position;
// }
// else if (m_wingType == enumWingType.WINGTYPE_FLYSWORD)
// {
// m_Wing.transform.position = m_HH_feijian.transform.position;
// }
// else
// {
// }
//}
}
//// ID checking helper methods
//private bool ISNPCID(int id) => ((id & 0x80000000) != 0) && ((id & 0x40000000) == 0);
//private bool ISPLAYERID(int id) => id != 0 && (id & 0x80000000) == 0;
//private bool ISMATTERID(int id) => ((id) & 0xC0000000) == 0xC0000000;
// Release object
public void Release()
{
// TODO: Release all objects created by player, such as inventory, skills, etc.
// CECInstanceReenter::Instance().Clear();
// CECShoppingItemsMover::Instance().Clear();
// CECFashionShopManager::Instance().Clear();
// CECShoppingManager::Instance().Clear();
// CECUseUniversalTokenCommandManager::Instance().Clear();
// CECUniversalTokenHTTPOSNavigatorTicketHandler::Instance().Clear();
// RandMallShoppingManager::Instance().Release();
// CECFactionPVPModel::Instance().Clear();
// CECHostSkillModel::Instance().Release();
// CECComboSkillState::Instance().Release();
// CECPlayerLevelRankRealmChangeCheck::Instance().Release();
// CECHostFashionEquipFromStorageSystem::Instance().Clear();
//
// m_pSaveLifeTrigger = NULL;
// CECQuickBuyPopManager::Instance().ClearPolicies();
//
// // Ïú»ÙPlayerWrapper
// CECAutoPolicy::GetInstance().OnLeaveWorld();
//
// // Save favorite auction list first
// SaveFavorAucItems();
//
// // Release duel images
// ReleaseDuelImages();
//
// // Release sounds
// g_pGame->GetGameRun()->ReleaseSoundTable();
// m_pCurMoveSnd = NULL;
//
// // Release friend manger
// if (m_pFriendMan)
// {
// delete m_pFriendMan;
// m_pFriendMan = NULL;
// }
//
// // Release pet corral
// if (m_pPetCorral)
// {
// delete m_pPetCorral;
// m_pPetCorral = NULL;
// }
//
// if (m_pPetWords)
// {
// delete m_pPetWords;
// m_pPetWords = NULL;
// }
//
// if (m_pForceMgr)
// {
// delete m_pForceMgr;
// m_pForceMgr = NULL;
// }
//
// if (m_pOnlineAwardCtrl)
// {
// delete m_pOnlineAwardCtrl;
// m_pOnlineAwardCtrl = NULL;
// }
//
// if (m_pOffShopCtrl)
// {
// delete m_pOffShopCtrl;
// m_pOffShopCtrl = NULL;
// }
//
// if (m_pAutoTeam)
// {
// delete m_pAutoTeam;
// m_pAutoTeam = NULL;
// }
//
// if (m_pChariot)
// {
// delete m_pChariot;
// m_pChariot = NULL;
// }
//
// int i;
//
// // Release all shortcuts
// for (i=0; i < NUM_HOSTSCSETS1; i++)
// A3DRELEASE(m_aSCSets1[i]);
//
// for (i=0; i < NUM_HOSTSCSETS2; i++)
// A3DRELEASE(m_aSCSets2[i]);
//
// for (i=0; i < NUM_SYSMODSETS; i++)
// A3DRELEASE(m_aSCSetSysMod[i]);
//
// // Release all inventories
// A3DRELEASE(m_pPack);
// A3DRELEASE(m_pEquipPack);
// A3DRELEASE(m_pTrashBoxPack);
// A3DRELEASE(m_pTrashBoxPack2);
// A3DRELEASE(m_pTrashBoxPack3);
// A3DRELEASE(m_pAccountBoxPack);
// A3DRELEASE(m_pGeneralCardPack);
// A3DRELEASE(m_pTaskPack);
// A3DRELEASE(m_pDealPack);
// A3DRELEASE(m_pEPDealPack);
// A3DRELEASE(m_pTaskInterface);
// A3DRELEASE(m_pSpritePortrait);
// A3DRELEASE(m_pBuyPack);
// A3DRELEASE(m_pSellPack);
// A3DRELEASE(m_pBoothSPack);
// A3DRELEASE(m_pBoothBPack);
// A3DRELEASE(m_pEPBoothSPack);
// A3DRELEASE(m_pEPBoothBPack);
// A3DRELEASE(m_pEPEquipPack);
// A3DRELEASE(m_pClientGenCardPack);
//
// for (i=0; i < NUM_NPCIVTR; i++)
// {
// A3DRELEASE(m_aNPCPacks[i]);
// }
//
// // Release all skills
// ReleaseSkills();
//
// // Clear current combo skill
// ClearComboSkill();
//
// if (m_pWorkMan)
// {
// delete m_pWorkMan;
// m_pWorkMan = NULL;
// }
//
// m_CameraCtrl.Release();
//
// m_aTeamInvs.RemoveAll();
//
// g_pGame->GetGFXCaster()->ReleaseGFXEx(m_pMoveTargetGFX);
// g_pGame->GetGFXCaster()->ReleaseGFXEx(m_pSelectedGFX);
// g_pGame->GetGFXCaster()->ReleaseGFXEx(m_pHoverGFX);
// g_pGame->GetGFXCaster()->ReleaseGFXEx(m_pFloatDust);
//
// m_pMoveTargetGFX = NULL;
// m_pSelectedGFX = NULL;
// m_pHoverGFX = NULL;
// m_pFloatDust = NULL;
//
// // Clear tab select table
// m_aTabSels.RemoveAll(false);
//
// m_aForceInfo.RemoveAll();
//
// if (m_pActionSwitcher)
// {
// delete m_pActionSwitcher;
// m_pActionSwitcher = NULL;
// }
//
// CECQShopConfig::Instance().ClearBuyedItem();
//
// A3DRELEASE(m_pNavigatePlayer);
//
// CECPlayer::Release();
}
public int GetCurServiceNPC() { return m_idSevNPC; }
// ────────────────────────────────────────────────────────────────────
// Move sound — C++ equivalent: CECHostPlayer::UpdateMoveSound / PlayMoveSound
// ────────────────────────────────────────────────────────────────────
/// <summary>
/// Selects and plays the correct looping movement SFX based on environment,
/// shape, and movement state. Throttled to run once every 3 frames, matching
/// the C++ dwUpdateCnt % 3 guard.
/// </summary>
private void UpdateMoveSound()
{
_moveSndUpdateCounter++;
if (_moveSndUpdateCounter % 3 != 0) return;
if (m_pWorkMan == null) return;
int newId;
if (m_pWorkMan.IsMoving())
{
if (m_iMoveEnv == (int)MoveEnvironment.MOVEENV_GROUND)
{
if (IsJumping())
{
newId = 162;
}
else if (!m_GndInfo.bOnGround)
{
newId = 163;
}
else
{
int iIndex = m_iProfession * GENDER.NUM_GENDER + m_iGender;
int iWalkRunOffset = m_bWalkRun ? 1 : 0;
if (IsShapeChanged() && (GetShapeID() == (int)ModelResourceType.RES_MOD_ORC_FOX
|| GetShapeID() == (int)ModelResourceType.RES_MOD_ORC_FOX2))
newId = 170 + iWalkRunOffset;
else if (IsShapeChanged() && GetShapeID() == (int)ModelResourceType.RES_MOD_ORC_TIGER)
newId = 172 + iWalkRunOffset;
else if (m_RidingPet.id != 0)
newId = 200 + GetRidingPetSndType(m_RidingPet.id) * 2 + iWalkRunOffset;
else
newId = (m_bWalkRun ? 130 : 100) + iIndex;
}
}
else if (m_iMoveEnv == (int)MoveEnvironment.MOVEENV_WATER)
{
float eyeY = GetPosVector3().y + 1.7f; // approximate eye height
newId = eyeY > m_GndInfo.fWaterHei ? 160 : 161;
}
else // MOVEENV_AIR
{
newId = 164;
}
}
else
{
newId = 164;
}
PlayMoveSound(newId);
}
/// <summary>
/// Switches the looping move sound to <paramref name="id"/>, no-op if already playing.
/// C++ equivalent: CECHostPlayer::PlayMoveSound
/// </summary>
private void PlayMoveSound(int id)
{
BMLogger.LogError($"HoangDev PlayMoveSound called with id: {id}"); // Debug log to trace sound ID changes
if (id == _curMoveSndId) return;
_curMoveSndId = id;
SFXManager.Instance?.PlayMoveSoundAsync(id).Forget();
}
/// <summary>
/// Returns the pet_snd_type for a riding pet, used to select the mount SFX series
/// (200 + type*2 + walkRunOffset).
/// TODO: look up PET_ESSENCE.pet_snd_type via ElementDataManager when available.
/// C++ equivalent: pData->pet_snd_type from PET_ESSENCE
/// </summary>
private static int GetRidingPetSndType(int petId)
{
return 0; // default type 0 = horse (walk 200, run 201)
}
}
public sealed class CECHPTraceSpellMatcher : CECHPWorkMatcher
{
public override bool Match(CECHPWork pWork, int priority, bool isDelayWork)
{
if (pWork == null) return false;
if (pWork.GetWorkID() != Host_work_ID.WORK_TRACEOBJECT) return false;
// dynamic_cast<CECHPWorkTrace*>(pWork) trong C#
if (pWork is not CECHPWorkTrace trace) return false;
return trace.GetTraceReason() == Trace_reason.TRACE_SPELL;
}
}
}