6884 lines
280 KiB
C#
6884 lines
280 KiB
C#
using BrewMonster;
|
|
using BrewMonster.Assets.PerfectWorld.Scripts.Players;
|
|
using BrewMonster.Managers;
|
|
using BrewMonster.Network;
|
|
using BrewMonster.PerfectWorld.Scripts.Vfx;
|
|
using BrewMonster.PerfectWorld.Scripts.Vfx;
|
|
using BrewMonster.Scripts;
|
|
using BrewMonster.Scripts.Managers;
|
|
using BrewMonster.Scripts.Skills;
|
|
using BrewMonster.Scripts.World;
|
|
using BrewMonster.UI;
|
|
using CSNetwork;
|
|
using CSNetwork.GPDataType;
|
|
using CSNetwork.Protocols.RPCData;
|
|
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.Scripts.Managers.EC_Inventory;
|
|
using Host_work_ID = BrewMonster.Scripts.CECHPWork.Host_work_ID;
|
|
using Trace_reason = BrewMonster.CECHPWorkTrace.Trace_reason;
|
|
|
|
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
|
|
|
|
// Shortcut sets (converted from C++: m_aSCSets1[NUM_HOSTSCSETS1], m_aSCSets2[NUM_HOSTSCSETS2])
|
|
public CECShortcutSet[] m_aSCSets1 = new CECShortcutSet[HostCfgConstants.NUM_HOSTSCSETS1]; // SC set 1
|
|
public CECShortcutSet[] m_aSCSets2 = new CECShortcutSet[HostCfgConstants.NUM_HOSTSCSETS2]; // SC set 2
|
|
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 List<CECObject> m_aTabSels = new List<CECObject>();
|
|
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>();
|
|
|
|
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 bool m_bSpellDSkill;
|
|
private CECSkill m_pCurSkill;
|
|
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; // תÉú
|
|
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 A3DVECTOR3 g_vOrigin = new A3DVECTOR3(0f);
|
|
private Transform playerTransform;
|
|
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
|
|
|
|
// 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;
|
|
|
|
// ====== 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;
|
|
|
|
// 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 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 // 获取导航玩家
|
|
private CECHostNavigatePlayer m_pNavigatePlayer = null;
|
|
public CECHostNavigatePlayer GetNavigatePlayer()
|
|
{
|
|
if (m_pNavigatePlayer == null)
|
|
{
|
|
// Create navigate player on-demand (C++: host owns a navigate/clone player + controller)
|
|
// 按需创建导航玩家(C++:宿主玩家持有导航/克隆玩家 + 控制器)
|
|
m_pNavigatePlayer = new CECHostNavigatePlayer(this);
|
|
}
|
|
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})");
|
|
CECHostNavigatePlayer pNavigatePlayer = GetNavigatePlayer();
|
|
if (pNavigatePlayer != null)
|
|
{
|
|
UnityEngine.Debug.Log($"[CECHostPlayer] OnNaviageEvent: Forwarding to NavigatePlayer");
|
|
pNavigatePlayer.OnNavigateEvent(task, e);
|
|
}
|
|
else
|
|
{
|
|
UnityEngine.Debug.LogWarning($"[CECHostPlayer] OnNaviageEvent: NavigatePlayer is 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 static class HostCfgConstants
|
|
{
|
|
public const int HOSTCFG_VERSION = 11;
|
|
public const int NUM_HOSTSCSETS1 = 5; // expanded from 3 to 5 (2009.05.27)
|
|
public const int NUM_HOSTSCSETS2 = 3;
|
|
public const int SIZE_HOSTSCSET1 = 9; // expanded from 6 to 9 (2009.05.27)
|
|
public const int SIZE_HOSTSCSET2 = 8;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
public 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;
|
|
|
|
if (!LoadPlayerSkeleton(true))
|
|
{
|
|
BMLogger.LogError("HoangDev CECHostPlayer::LoadResources, Failed to load skeleton");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private bool LoadPlayerSkeleton(bool bAtOnce)
|
|
{
|
|
EC_PLAYERLOADRESULT Ret = default;
|
|
if (bAtOnce /*|| !IsLoadThreadReady()*/)
|
|
{
|
|
// Under normal circumstances, only HostPlayer can reach here
|
|
/* if (!LoadPlayerModel(m_iProfession, m_iGender, m_CustomizeData.bodyID, aEquips, szPetPath, Ret, false, false))
|
|
{
|
|
a_LogOutput(1, "CECPlayer::Init, failed to call LoadPlayerModel() !");
|
|
return false;
|
|
}*/
|
|
|
|
SetPlayerLoadedResult(Ret);
|
|
|
|
/* if (IsShapeChanged() && !QueueLoadDummyModel(m_iShape, true))
|
|
{
|
|
// ignore the dummy model loading failure
|
|
a_LogOutput(1, "CECPlayer::Init, failed to call QueueLoadDummyModel() !");
|
|
}*/
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private bool SetPlayerLoadedResult(EC_PLAYERLOADRESULT ret)
|
|
{
|
|
OnAllResourceReady();
|
|
return true;
|
|
}
|
|
|
|
private void OnAllResourceReady()
|
|
{
|
|
CECHostSkillModel.Instance.Initialize();
|
|
}
|
|
|
|
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;
|
|
}
|
|
#if UNITY_EDITOR
|
|
if (Input.GetKeyDown(KeyCode.C))
|
|
{
|
|
ApplySkillShortcut(1);
|
|
}
|
|
#endif
|
|
|
|
// 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();
|
|
|
|
// track status of Task
|
|
TickTask();
|
|
// Update timers
|
|
UpdateTimers(Time.deltaTime);
|
|
|
|
m_pWorkMan?.Tick(Time.deltaTime);
|
|
|
|
|
|
// Update GFXs
|
|
UpdateGFXs(Time.deltaTime);
|
|
|
|
//m_dwMoveRelDir = 0;
|
|
m_fVertSpeed = 0.0f;
|
|
UpdateTimers(Time.deltaTime);
|
|
// Auto team / Automatic party grouping
|
|
// m_pAutoTeam.Tick(Time.deltaTime);
|
|
}
|
|
|
|
public void StopMovement()
|
|
{
|
|
m_MoveCtrl.SendStopMoveCmd(playerTransform.position, 5f, (int)GPMoveMode.GP_MOVE_WALK);
|
|
}
|
|
|
|
//public void HandleMovement()
|
|
//{
|
|
// // 1) Kiểm tra grounded bằng SphereCast ngắn dựa trên radius + skinWidth
|
|
// isGrounded = GroundCheck(out lastGroundHit);
|
|
// m_GndInfo.bOnGround = isGrounded;
|
|
// // 2) Input tạm thời: giữ nguyên như bạn
|
|
// //if (UnityEngine.Input.GetKeyDown(KeyCode.LeftShift)) SetStatusRun(true);
|
|
// //if (UnityEngine.Input.GetKeyUp(KeyCode.LeftShift)) SetStatusRun(false);
|
|
// //if (UnityEngine.Input.GetKeyDown(KeyCode.Space)) HandleJump();
|
|
|
|
// // 3) Trọng lực / sticky
|
|
// if (isGrounded && playerVelocity.y < 0f)
|
|
// {
|
|
// // Đè nhẹ để bám đất (tránh nhấp-nháy)
|
|
// playerVelocity.y = -2f;
|
|
// }
|
|
// else
|
|
// {
|
|
// playerVelocity.y += gravityValue * Time.deltaTime;
|
|
// }
|
|
|
|
// // 4) Chuyển động phẳng
|
|
// float x = joystick.Horizontal;
|
|
// float z = joystick.Vertical;
|
|
|
|
// Vector3 move = new Vector3(x, 0, z);
|
|
// move = Vector3.ClampMagnitude(move, 1f);
|
|
// if (move != Vector3.zero)
|
|
// {
|
|
// Vector3 finalMove = (move * playerSpeed) + (playerVelocity.y * Vector3.up);
|
|
// controller.Move(finalMove * Time.deltaTime);
|
|
// transform.forward = move;
|
|
// m_MoveCtrl.GroundMove(Time.deltaTime);
|
|
// m_MoveCtrl.SendMoveCmd(playerTransform.position, controller.velocity, (int)GPMoveMode.GP_MOVE_RUN);
|
|
// m_aabb.Center = EC_Utility.ToA3DVECTOR3(playerTransform.position) +
|
|
// new A3DVECTOR3(0.0f, m_aabb.Extents.y, 0.0f);
|
|
// m_aabb.CompleteMinsMaxs();
|
|
// m_aabbServer.Center = EC_Utility.ToA3DVECTOR3(playerTransform.position) +
|
|
// new A3DVECTOR3(0.0f, m_aabbServer.Extents.y, 0.0f);
|
|
// m_aabbServer.CompleteMinsMaxs();
|
|
// }
|
|
// else
|
|
// {
|
|
// }
|
|
//}
|
|
|
|
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 int value when value == EC_MsgDef.MSG_HST_CORRECTPOS: OnMsgHstCorrectPos(Msg); break;
|
|
case int value when value == EC_MsgDef.MSG_HST_GOTO: OnMsgHstGoto(Msg); break;
|
|
case int value when value == EC_MsgDef.MSG_HST_IVTRINFO:
|
|
{
|
|
OnMsgHstIvtrInfo(Msg);
|
|
break;
|
|
}
|
|
case int value when value == EC_MsgDef.MSG_HST_OWNITEMINFO:
|
|
{
|
|
OnMsgHstOwnItemInfo(Msg);
|
|
break;
|
|
}
|
|
case int value when value == EC_MsgDef.MSG_HST_TASKDATA:
|
|
{
|
|
OnMsgHstTaskData(Msg).Forget();
|
|
//Debug.LogError("[Dat]- OnMsgHstTaskData");
|
|
break;
|
|
}
|
|
case int value when value == EC_MsgDef.MSG_HST_ITEMOPERATION:
|
|
OnMsgHstItemOperation(Msg);
|
|
break;
|
|
case int value when value == EC_MsgDef.MSG_HST_PICKUPITEM:
|
|
OnMsgHstPickupItem(Msg);
|
|
break;
|
|
case int value when value == EC_MsgDef.MSG_HST_PRODUCEITEM:
|
|
OnMsgHstProduceItem(Msg);
|
|
break;
|
|
case int value when value == EC_MsgDef.MSG_HST_SELTARGET:
|
|
OnMsgHstSelTarget(Msg); break;
|
|
case int value when value == EC_MsgDef.MSG_HST_ATKRESULT: OnMsgHstAttackResult(Msg); break;
|
|
case int value when value == EC_MsgDef.MSG_HST_ATTACKED: OnMsgHstAttacked(Msg); break;
|
|
case int value when value == EC_MsgDef.MSG_HST_HURTRESULT: OnMsgHstHurtResult(Msg); break;
|
|
case int value when value == EC_MsgDef.MSG_HST_INFO00: OnMsgHstInfo00(Msg); break;
|
|
case int value when value == EC_MsgDef.MSG_HST_NPCGREETING: OnMsgHstNPCGreeting(Msg); break;
|
|
case int value when value == EC_MsgDef.MSG_HST_WAYPOINT: OnMsgHstWayPoint(Msg); break;
|
|
case int value when value == EC_MsgDef.MSG_HST_SKILLDATA: OnMsgHstSkillData(Msg); break;
|
|
case int value when value == EC_MsgDef.MSG_HST_DIED: OnMsgHstDied(Msg); break;
|
|
case int value when value == EC_MsgDef.MSG_HST_STARTATTACK: OnMsgHstStartAttack(Msg); break;
|
|
case int value when value == EC_MsgDef.MSG_HST_STOPATTACK: OnMsgHstStopAttack(Msg); break;
|
|
case int value when value == EC_MsgDef.MSG_HST_SKILLRESULT: OnMsgHstSkillResult(Msg); break;
|
|
case int value when value == EC_MsgDef.MSG_PM_CASTSKILL: OnMsgPlayerCastSkill(Msg); break;
|
|
case int value when value == EC_MsgDef.MSG_PM_ENCHANTRESULT: OnMsgEnchantResult(Msg); break;
|
|
|
|
|
|
case int value when value == EC_MsgDef.MSG_HST_OWNEXTPROP: OnMsgHstExtProp(Msg); break;
|
|
case int value when value == EC_MsgDef.MSG_PM_PLAYERDOEMOTE: OnMsgPlayerDoEmote(Msg); break;
|
|
case int value when value == EC_MsgDef.MSG_HST_TARGETISFAR: OnMsgHstTargetIsFar(Msg); break;
|
|
case int value when value == EC_MsgDef.MSG_PM_PLAYERGATHER: OnMsgPlayerGather(Msg); break;
|
|
case int value when value == EC_MsgDef.MSG_HST_COOLTIMEDATA: OnMsgHstCoolTimeData(Msg); break;
|
|
case int value when value == EC_MsgDef.MSG_HST_SETCOOLTIME: OnMsgHstSetCoolTime(Msg); break;
|
|
case int value when value == EC_MsgDef.MSG_HST_PRESSCANCEL: OnMsgHstPressCancel(Msg); break;
|
|
case int value when value == EC_MsgDef.MSG_HST_LEARNSKILL: OnMsgHstLearnSkill(Msg); break;
|
|
|
|
case int value when value == EC_MsgDef.MSG_PM_PLAYERFLY: OnMsgPlayerFly(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 OnMsgHstLearnSkill(ECMSG Msg)
|
|
{
|
|
BMLogger.LogError("OnMsgHstLearnSkill");
|
|
cmd_learn_skill pCmd = GPDataTypeHelper.FromBytes<cmd_learn_skill>((byte[])Msg.dwParam1);
|
|
|
|
CECSkill pSkill = GetNormalSkill(pCmd.skill_id);
|
|
if (pSkill != null)
|
|
{
|
|
if (pCmd.skill_level > 0)
|
|
{
|
|
pSkill.LevelUp();
|
|
ElementSkill.SetLevel((uint)pCmd.skill_id, pCmd.skill_level);
|
|
}
|
|
else
|
|
{
|
|
RemoveNormalSkill(pCmd.skill_id);
|
|
}
|
|
}
|
|
else if (pCmd.skill_level > 0)
|
|
{
|
|
pSkill = new CECSkill(pCmd.skill_id, pCmd.skill_level);
|
|
if (pSkill == null)
|
|
{
|
|
Debug.Assert(pSkill != null);
|
|
return;
|
|
}
|
|
|
|
if (!pSkill.GetJunior().Empty())
|
|
{
|
|
ReplaceJuniorSkill(pSkill);
|
|
}
|
|
else
|
|
{
|
|
if (pSkill.GetType() != (int)CECSkill.SkillType.TYPE_PASSIVE &&
|
|
pSkill.GetType() != (int)CECSkill.SkillType.TYPE_PRODUCE &&
|
|
pSkill.GetType() != (int)CECSkill.SkillType.TYPE_LIVE)
|
|
m_aPtSkills.Add(pSkill);
|
|
else
|
|
m_aPsSkills.Add(pSkill);
|
|
}
|
|
|
|
ElementSkill.SetLevel((uint)pCmd.skill_id, pCmd.skill_level);
|
|
}
|
|
|
|
CECHostSkillModel.Instance.OnLearnSkill(pCmd.skill_id, pCmd.skill_level);
|
|
}
|
|
|
|
private void OnMsgHstSetCoolTime(ECMSG Msg)
|
|
{
|
|
BMLogger.LogError("HoangDev : OnMsgHstSetCoolTime ");
|
|
|
|
cmd_set_cooldown pCmd = GPDataTypeHelper.FromBytes<cmd_set_cooldown>((byte[])Msg.dwParam1);
|
|
|
|
if (pCmd.cooldown_index < 0)
|
|
{
|
|
BMLogger.LogError("pCmd.cooldown_index >= 0 :" + (pCmd.cooldown_index >= 0));
|
|
return;
|
|
}
|
|
|
|
if (pCmd.cooldown_index < (int)CoolTimeIndex.GP_CT_MAX)
|
|
{
|
|
COOLTIME ct = m_aCoolTimes[pCmd.cooldown_index];
|
|
ct.iCurTime = pCmd.cooldown_time;
|
|
ct.iMaxTime = pCmd.cooldown_time;
|
|
Math.Min(ct.iCurTime, ct.iMaxTime);
|
|
|
|
if (pCmd.cooldown_index == (int)CoolTimeIndex.GP_CT_CAST_ELF_SKILL)
|
|
{
|
|
int i;
|
|
// other goblin skills should be set public cool down, 1 second
|
|
/* for (i = 0; i < m_aGoblinSkills.GetSize(); i++)
|
|
{
|
|
if (m_aGoblinSkills[i] && m_aGoblinSkills[i]->GetCoolingCnt() == 0)
|
|
m_aGoblinSkills[i]->StartCooling(GetCoolTime(GP_CT_CAST_ELF_SKILL), GetCoolTime(GP_CT_CAST_ELF_SKILL));
|
|
}*/
|
|
|
|
/* for (i = 0; i < m_aPsSkills.Count; i++)
|
|
{
|
|
CECSkill pSkill = GetPassiveSkillByIndex(i);
|
|
if (pSkill && (pSkill->GetCommonCoolDown() & (1 << (pCmd->cooldown_index - GP_CT_SKILLCOMMONCOOLDOWN0))))
|
|
pSkill->StartCooling(GetCoolTime(pCmd->cooldown_index), GetCoolTime(pCmd->cooldown_index));
|
|
}*/
|
|
}
|
|
|
|
if (pCmd.cooldown_index >= (int)CoolTimeIndex.GP_CT_SKILLCOMMONCOOLDOWN0 && pCmd.cooldown_index <= (int)CoolTimeIndex.GP_CT_SKILLCOMMONCOOLDOWN4)
|
|
{
|
|
// other player skills should be set public cool down too.
|
|
uint mask = (uint)(1 << (pCmd.cooldown_index - (int)CoolTimeIndex.GP_CT_SKILLCOMMONCOOLDOWN0));
|
|
for (int i = 0; i < GetPositiveSkillNum(); i++)
|
|
{
|
|
CECSkill pSkill = GetPositiveSkillByIndex(i);
|
|
int fakeRef = 0;
|
|
if (pSkill != null && (pSkill.GetCommonCoolDown() & mask) != 0)
|
|
pSkill.StartCooling(GetCoolTime(pCmd.cooldown_index, ref fakeRef), GetCoolTime(pCmd.cooldown_index, ref fakeRef));
|
|
}
|
|
/*const std::map<unsigned int, CECSkill*>&inherentSkillMap = CECComboSkillState::Instance().GetInherentSkillMap();
|
|
std::map < unsigned int, CECSkill*>::const_iterator it;
|
|
for (it = inherentSkillMap.begin(); it != inherentSkillMap.end(); ++it)
|
|
{
|
|
it.second.StartCooling(GetCoolTime(pCmd.cooldown_index), GetCoolTime(pCmd.cooldown_index));
|
|
}*/
|
|
}
|
|
}
|
|
else if (pCmd.cooldown_index > (int)CoolTimeIndex.GP_CT_SKILL_START)
|
|
{
|
|
int idSkill = pCmd.cooldown_index - (int)CoolTimeIndex.GP_CT_SKILL_START;
|
|
|
|
COOLTIME ct;
|
|
if (!m_skillCoolTime.TryGetValue(idSkill, out ct))
|
|
{
|
|
// Key doesn't exist, create new entry
|
|
ct = new COOLTIME();
|
|
m_skillCoolTime[idSkill] = ct;
|
|
}
|
|
ct.iCurTime = pCmd.cooldown_time;
|
|
ct.iMaxTime = pCmd.cooldown_time;
|
|
Math.Clamp(ct.iCurTime, 0, ct.iMaxTime);
|
|
|
|
CECSkill pSkill = GetNormalSkill(idSkill);
|
|
if (pSkill != null)
|
|
{
|
|
pSkill.StartCooling(pCmd.cooldown_time, pCmd.cooldown_time);
|
|
}
|
|
/* else if (pSkill = CECComboSkillState::Instance().GetInherentSkillByID(idSkill))
|
|
{
|
|
pSkill->StartCooling(pCmd->cooldown_time, pCmd->cooldown_time);
|
|
}*/
|
|
else if (GetEquipSkillByID(idSkill) == null)
|
|
{
|
|
BMLogger.LogError("HoangDev: pSkill " + pSkill);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// This is a annoying assert and mean nothing, so we ignore it.
|
|
// ASSERT(0);
|
|
}
|
|
|
|
UpdateEquipSkillCoolDown(pCmd.cooldown_index);
|
|
}
|
|
|
|
private void OnMsgHstCoolTimeData(ECMSG Msg)
|
|
{
|
|
cmd_cooltime_data pCmd = default;
|
|
var data = (byte[])Msg.dwParam1;
|
|
pCmd.count = GPDataTypeHelper.FromBytes<ushort>(data, 0);
|
|
long offset = Marshal.SizeOf(typeof(ushort));
|
|
pCmd.list = new item_t[pCmd.count];
|
|
for (int i = 0; i < pCmd.count; i++)
|
|
{
|
|
pCmd.list[i] = GPDataTypeHelper.FromBytes<item_t>(data, ref offset);
|
|
}
|
|
|
|
m_skillCoolTime.Clear();
|
|
|
|
for (int i = 0; i < pCmd.count; i++)
|
|
{
|
|
item_t item = pCmd.list[i];
|
|
if (item.idx > (int)CoolTimeIndex.GP_CT_SKILL_START)
|
|
{
|
|
// Is skill cool time
|
|
int idSkill = item.idx - (int)CoolTimeIndex.GP_CT_SKILL_START;
|
|
|
|
COOLTIME ct = default;
|
|
ct.iCurTime = item.cooldown;
|
|
ct.iMaxTime = item.max_cooltime;
|
|
Mathf.Clamp(ct.iCurTime, 0, ct.iMaxTime);
|
|
|
|
CECSkill pSkill = GetNormalSkill(idSkill);
|
|
if (pSkill != null)
|
|
{
|
|
pSkill.StartCooling(item.max_cooltime, item.cooldown);
|
|
}
|
|
/* else if (pSkill = CECComboSkillState::Instance().GetInherentSkillByID(idSkill))
|
|
{
|
|
pSkill.StartCooling(item.max_cooltime, item.cooldown);
|
|
}*/
|
|
else if (GetEquipSkillByID(idSkill) == null)
|
|
{
|
|
// Add to goblin skill list
|
|
pSkill = new CECSkill(idSkill, 1);
|
|
pSkill.StartCooling(item.max_cooltime, item.cooldown);
|
|
m_aGoblinSkills.Add(pSkill);
|
|
}
|
|
m_skillCoolTime[idSkill] = ct;
|
|
}
|
|
else if (item.idx >= 0 && item.idx < (int)CoolTimeIndex.GP_CT_MAX)
|
|
{
|
|
// Other cool time
|
|
COOLTIME ct = m_aCoolTimes[item.idx];
|
|
ct.iCurTime = item.cooldown;
|
|
ct.iMaxTime = item.max_cooltime;
|
|
Mathf.Clamp(ct.iCurTime, 0, ct.iMaxTime);
|
|
|
|
if (item.idx >= (int)CoolTimeIndex.GP_CT_SKILLCOMMONCOOLDOWN0 && item.idx <= (int)CoolTimeIndex.GP_CT_SKILLCOMMONCOOLDOWN4)
|
|
{
|
|
// other player skills should be set public cool down too.
|
|
uint mask = (uint)(1 << (item.idx - (int)CoolTimeIndex.GP_CT_SKILLCOMMONCOOLDOWN0));
|
|
for (int y = 0; y < GetPositiveSkillNum(); y++)
|
|
{
|
|
CECSkill pSkill = GetPositiveSkillByIndex(y);
|
|
if (pSkill != null && ((pSkill.GetCommonCoolDown() & mask) != 0))
|
|
pSkill.StartCooling(item.max_cooltime, item.cooldown);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
BMLogger.LogError("item.idx >= 0: " + (item.idx >= 0));
|
|
}
|
|
}
|
|
|
|
UpdateEquipSkillCoolDown();
|
|
}
|
|
|
|
private void OnMsgEnchantResult(ECMSG msg)
|
|
{
|
|
// 从消息中获取cmd_enchant_result结构 // Get cmd_enchant_result structure from message
|
|
cmd_enchant_result pCmd = GPDataTypeHelper.FromBytes<cmd_enchant_result>((byte[])msg.dwParam1);
|
|
|
|
// 初始化掩码为全1 // Initialize mask to all 1s
|
|
uint mask = 0xFFFFFFFF;
|
|
|
|
// 如果目标不是主机玩家且当前不是主机玩家,则过滤会引起气泡文本的修饰符
|
|
// We should filter out these things that will cause bubble texts
|
|
CECHostPlayer pHost = EC_ManMessageMono.Instance.GetECManPlayer.GetHostPlayer();
|
|
if (pCmd.target != pHost.GetCharacterID() && !IsHostPlayer())
|
|
{
|
|
mask &= (uint)(MOD.MOD_PHYSIC_ATTACK_RUNE | MOD.MOD_MAGIC_ATTACK_RUNE |
|
|
MOD.MOD_CRITICAL_STRIKE | MOD.MOD_ENCHANT_FAILED);
|
|
}
|
|
|
|
// 获取修饰符 // Get modifier
|
|
uint dwModifier = (uint)pCmd.attack_flag;
|
|
|
|
// 获取技能类型 // Get skill type
|
|
int nDamage = -2; // 默认为-2,不会引起受伤动作 // Default to -2, will not cause wounded action
|
|
|
|
if (ElementSkill.GetType((uint)pCmd.skill) == (byte)skill_type.TYPE_ATTACK)
|
|
{
|
|
// 只有攻击技能会引起受伤动作,伤害值为-1 // Only attack skill will cause wounded action, when damage is -1
|
|
nDamage = -1;
|
|
}
|
|
else
|
|
{
|
|
// 其他技能不会引起受伤动作,伤害值设为-2 // Other skills will not cause wounded action, so we set damage to -2
|
|
nDamage = -2;
|
|
}
|
|
|
|
// 播放攻击效果 // Play attack effect
|
|
int attackTime = 0;
|
|
PlayAttackEffect(pCmd.target, pCmd.skill, pCmd.level, nDamage,
|
|
dwModifier & mask, 0, ref attackTime, pCmd.section);
|
|
}
|
|
|
|
private void OnMsgPlayerCastSkill(ECMSG Msg)
|
|
{
|
|
bool bDoOtherThing = false;
|
|
int idTarget = 0;
|
|
|
|
bool bActionStartSkill = false;
|
|
int iActionTime = 1000;
|
|
// CECPlayerWrapper* pWrapper = CECAutoPolicy::GetInstance().GetPlayerWrapper();
|
|
|
|
switch (Convert.ToInt32(Msg.dwParam2))
|
|
{
|
|
case int value2 when value2 == CommandID.OBJECT_CAST_SKILL:
|
|
{
|
|
// Cast skill on target object
|
|
// 对目标对象施法
|
|
cmd_object_cast_skill pCmd =
|
|
GPDataTypeHelper.FromBytes<cmd_object_cast_skill>((byte[])Msg.dwParam1);
|
|
|
|
if (m_pCurSkill != null)
|
|
{
|
|
m_pCurSkill.EndCharging();
|
|
}
|
|
|
|
m_pCurSkill = GetPositiveSkillByID(pCmd.skill);
|
|
if (m_pCurSkill == null) m_pCurSkill = GetEquipSkillByID(pCmd.skill);
|
|
if (m_pCurSkill == null)
|
|
m_pCurSkill = CECComboSkillState.Instance.GetInherentSkillByID((uint)pCmd.skill);
|
|
if (m_pCurSkill == null)
|
|
{
|
|
Debug.Assert(m_pCurSkill != null, "Current skill should not be null");
|
|
return;
|
|
}
|
|
|
|
if (m_pCurSkill.IsChargeable())
|
|
m_pCurSkill.StartCharging(pCmd.time);
|
|
|
|
int iWaitTime = -1;
|
|
if (m_pCurSkill.GetExecuteTime() >= 0)
|
|
iWaitTime = pCmd.time + m_pCurSkill.GetExecuteTime();
|
|
|
|
CECHPWorkSpell pWork =
|
|
(CECHPWorkSpell)m_pWorkMan.CreateWork(CECHPWork.Host_work_ID.WORK_SPELLOBJECT);
|
|
pWork.PrepareCast(pCmd.target, m_pCurSkill, iWaitTime);
|
|
m_pWorkMan.StartWork_p1(pWork);
|
|
|
|
// Start time counter for some type skill
|
|
// 为某些类型的技能启动时间计数器
|
|
if (!m_pCurSkill.IsChargeable())
|
|
{
|
|
int iTime = pCmd.time;
|
|
if (iTime < 10) iTime = 10; // a_ClampFloor
|
|
m_IncantCnt.SetPeriod(iTime);
|
|
m_IncantCnt.Reset();
|
|
}
|
|
else
|
|
{
|
|
// make sure the counter is correct shown
|
|
// 确保计数器正确显示
|
|
m_IncantCnt.Reset(true);
|
|
}
|
|
|
|
m_bSpellDSkill = false;
|
|
|
|
TurnFaceTo(pCmd.target);
|
|
|
|
m_idCurSkillTarget = pCmd.target;
|
|
PlaySkillCastAction(m_pCurSkill.GetSkillID());
|
|
|
|
bActionStartSkill = true;
|
|
iActionTime = iWaitTime;
|
|
Debug.Log($"Cast skill({m_pCurSkill.GetSkillID()})");
|
|
break;
|
|
}
|
|
case int value2 when value2 == CommandID.SKILL_PERFORM:
|
|
{
|
|
// Skill perform
|
|
// 技能执行
|
|
m_pPrepSkill = null;
|
|
|
|
if (m_pCurSkill != null && m_pCurSkill.IsDurative())
|
|
m_bSpellDSkill = true;
|
|
|
|
break;
|
|
}
|
|
case int value2 when value2 == CommandID.HOST_STOP_SKILL:
|
|
{
|
|
// Host stop skill
|
|
// 主角停止技能
|
|
m_pPrepSkill = null;
|
|
|
|
CECSkill
|
|
pSkillToMatch = m_pCurSkill; // 保存指针值,用在后面函数调用中 | Save pointer value for later function call
|
|
if (m_pCurSkill != null)
|
|
{
|
|
ClearComActFlagAllRankNodes(true);
|
|
|
|
if (((m_pComboSkill != null && !m_pComboSkill.IsStop()) ||
|
|
m_pCurSkill.ChangeToMelee()) && !m_pWorkMan.HasDelayedWork())
|
|
{
|
|
bDoOtherThing = true;
|
|
idTarget = m_idCurSkillTarget;
|
|
}
|
|
|
|
m_pCurSkill.EndCharging();
|
|
m_pCurSkill = null;
|
|
}
|
|
|
|
Debug.Log("HOST_STOP_SKILL");
|
|
|
|
//AP_ActionEvent(AP_EVENT_STOPSKILL);
|
|
if (pSkillToMatch != null)
|
|
{
|
|
// m_pWorkMan中的当前任何Work为最高优先级或、或 CECHPWorkSpell 处于最高优先级且队列中有Delay时间
|
|
// 此时此处无法准确匹配,而导致的 CECHPWorkSpell 执行时将落后于其它 CECHPWorkSpell 执行期间将无法响应
|
|
// 在这种方法执行完成后,我们使用回城机制来
|
|
// If the current Work in m_pWorkMan is the highest priority or CECHPWorkSpell is at the highest priority and there is Delay time in the queue
|
|
// At this point, it cannot be accurately matched, causing the CECHPWorkSpell execution to lag behind other CECHPWorkSpell execution and cannot respond
|
|
// After this method is executed, we use the return to city mechanism
|
|
m_pWorkMan.FinishWork(new CECHPWorkSpellMatcher(pSkillToMatch));
|
|
}
|
|
|
|
StopSkillAttackAction();
|
|
|
|
m_idCurSkillTarget = 0;
|
|
break;
|
|
}
|
|
case int value2 when value2 == CommandID.SELF_SKILL_INTERRUPTED:
|
|
{
|
|
// Skill interrupted
|
|
// 技能被打断
|
|
int skill_id = 0;
|
|
m_pPrepSkill = null;
|
|
|
|
CECSkill
|
|
pSkillToMatch = m_pCurSkill; // 保存指针值,用在后面函数调用中 | Save pointer value for later function call
|
|
if (m_pCurSkill != null)
|
|
{
|
|
skill_id = m_pCurSkill.GetSkillID();
|
|
|
|
ClearComActFlagAllRankNodes(false);
|
|
|
|
if (((m_pComboSkill != null && !m_pComboSkill.IsStop()) ||
|
|
m_pCurSkill.ChangeToMelee()) && !m_pWorkMan.HasDelayedWork())
|
|
{
|
|
bDoOtherThing = true;
|
|
idTarget = m_idCurSkillTarget;
|
|
}
|
|
|
|
m_pCurSkill.EndCharging();
|
|
m_pCurSkill = null;
|
|
}
|
|
|
|
m_idCurSkillTarget = 0;
|
|
|
|
|
|
if (pSkillToMatch != null)
|
|
{
|
|
// 逻辑同 HOST_STOP_SKILL 分支处理 | Logic same as HOST_STOP_SKILL branch
|
|
m_pWorkMan.FinishWork(new CECHPWorkSpellMatcher(pSkillToMatch));
|
|
}
|
|
|
|
StopSkillCastAction();
|
|
|
|
// Print a notify message
|
|
// 打印提示消息
|
|
// g_pGame.GetGameRun().AddFixedMessage(FIXMSG_SKILLINTERRUPT);
|
|
Debug.Log("Skill interrupted!");
|
|
|
|
//AP_ActionEvent(AP_EVENT_STOPSKILL);
|
|
|
|
// 通知策略技能被打断 | Notify policy that skill is interrupted
|
|
// CECAutoPolicy::GetInstance().SendEvent_SkillInterrupt(skill_id);
|
|
break;
|
|
}
|
|
case int value2 when value2 == CommandID.OBJECT_CAST_INSTANT_SKILL:
|
|
{
|
|
// Cast instant skill
|
|
// 施放即时技能
|
|
cmd_object_cast_instant_skill pCmd =
|
|
GPDataTypeHelper.FromBytes<cmd_object_cast_instant_skill>((byte[])Msg.dwParam1);
|
|
Debug.Assert(pCmd.caster == m_PlayerInfo.cid);
|
|
|
|
CECSkill pSkill = GetPositiveSkillByID(pCmd.skill);
|
|
if (pSkill == null) pSkill = GetEquipSkillByID(pCmd.skill);
|
|
if (pSkill == null)
|
|
{
|
|
Debug.Assert(pSkill != null, "Skill should not be null");
|
|
return;
|
|
}
|
|
|
|
if (pCmd.target != 0 && pCmd.target != m_PlayerInfo.cid)
|
|
TurnFaceTo(pCmd.target);
|
|
|
|
PlaySkillCastAction(pSkill.GetSkillID());
|
|
bActionStartSkill = true;
|
|
break;
|
|
}
|
|
case int value2 when value2 == CommandID.OBJECT_CAST_POS_SKILL:
|
|
{
|
|
// Cast position skill (target position instead of target object)
|
|
// 施放位置技能(目标位置而不是目标对象)
|
|
cmd_object_cast_pos_skill pCmd =
|
|
GPDataTypeHelper.FromBytes<cmd_object_cast_pos_skill>((byte[])Msg.dwParam1);
|
|
Debug.Assert(pCmd.caster == m_PlayerInfo.cid);
|
|
|
|
CECSkill pSkill = GetNormalSkill(pCmd.skill);
|
|
if (pSkill == null) pSkill = GetEquipSkillByID(pCmd.skill);
|
|
if (pSkill == null)
|
|
{
|
|
Debug.Assert(pSkill != null, "Skill should not be null");
|
|
break;
|
|
}
|
|
|
|
TurnFaceTo(pCmd.target);
|
|
|
|
if (pSkill.GetRangeType() != (int)CECSkill.RangeType.RANGE_SLEF &&
|
|
pSkill.GetRangeType() != (int)CECSkill.RangeType.RANGE_SELFSPHERE &&
|
|
(pSkill.GetSkillID() == 1095 || // 瞬影瞬斩 | Shadow instant slash
|
|
pSkill.GetSkillID() == 1278 || // 魔·瞬影瞬斩 | Demon Shadow instant slash
|
|
pSkill.GetSkillID() == 1279 || // 妖瞬影瞬斩 | Monster Shadow instant slash
|
|
pSkill.GetSkillID() == 2313))
|
|
{
|
|
A3DVECTOR3 vPos = pCmd.pos;
|
|
if (!IsPosCollideFree(vPos))
|
|
{
|
|
// Add a little height to ensure player's AABB won't embed with building
|
|
// 添加一点高度以确保玩家的AABB不会嵌入建筑物
|
|
vPos += new A3DVECTOR3(0, 1, 0) * 0.1f;
|
|
}
|
|
|
|
// Ensure we are not under ground
|
|
// 确保我们不在地下
|
|
A3DVECTOR3 vNormal = new A3DVECTOR3();
|
|
float vTerrainHeight = CECWorld.Instance.GetTerrainHeight(vPos, ref vNormal);
|
|
if (vPos.y < vTerrainHeight)
|
|
vPos.y = vTerrainHeight;
|
|
|
|
SetPos(EC_Utility.ToVector3(vPos));
|
|
|
|
m_CDRInfo.vTPNormal = vPos.y <= vTerrainHeight + 0.1f ? vNormal : new A3DVECTOR3(0);
|
|
m_CDRInfo.fYVel = 0.0f;
|
|
m_CDRInfo.vAbsVelocity = new A3DVECTOR3(0);
|
|
ResetJump();
|
|
|
|
m_MoveCtrl.SetHostLastPos(vPos);
|
|
m_MoveCtrl.SetLastSevPos(vPos);
|
|
|
|
// Update camera
|
|
// 更新相机
|
|
//UpdateFollowCamera(false, 10);
|
|
}
|
|
else
|
|
{
|
|
CECHPWorkFMove pWork =
|
|
(CECHPWorkFMove)m_pWorkMan.CreateWork(CECHPWork.Host_work_ID.WORK_FLASHMOVE);
|
|
|
|
// 检查技能执行时间,防止出现浮点数0,出现错误 | Check skill execute time to prevent float 0 error
|
|
int nExecuteTime = pSkill.GetExecuteTime();
|
|
if (nExecuteTime < 50) nExecuteTime = 50; // a_ClampFloor
|
|
|
|
if (pSkill.GetSkillID() == 1145 || // 刺客的瞬移技能,需要增加地面效果1145:百步穿杨瞬斩 | Assassin teleport skill
|
|
pSkill.GetSkillID() == 1314 || // 魔·百步穿杨瞬斩 | Demon hundred steps instant slash
|
|
pSkill.GetSkillID() == 1315 || // 妖·百步穿杨瞬斩 | Monster hundred steps instant slash
|
|
pSkill.GetSkillID() == 1362 || // 影遁回杨 | Shadow escape return
|
|
pSkill.GetSkillID() == 1690 || // 影遁魔·回杨 | Shadow escape demon return
|
|
pSkill.GetSkillID() == 1691 || // 影遁妖回杨 | Shadow escape monster return
|
|
pSkill.GetSkillID() == 1845 || // 星湖之狐·瞬移技能 | Star Lake Fox teleport
|
|
pSkill.GetSkillID() == 1844 || // 星湖之狐·瞬移技能 | Star Lake Fox teleport
|
|
pSkill.GetSkillID() == 1815 || // 妖精 飞 | Fairy fly
|
|
pSkill.GetSkillID() == 2272 || // 一步登天 | One step to heaven
|
|
pSkill.GetSkillID() == 2315 || // 百步穿杨瞬斩 | Hundred steps instant slash
|
|
pSkill.GetSkillID() == 2285 || // 回杨 | Return
|
|
pSkill.GetSkillID() == 2340 || // 突袭 | Raid
|
|
pSkill.GetSkillID() == 2341 || // 突袭 | Raid
|
|
pSkill.GetSkillID() == 2342 || // 突袭 | Raid
|
|
pSkill.GetSkillID() == 2553 || // 飞箭雨 | Arrow rain
|
|
pSkill.GetSkillID() == 2740 || // 魔·飞箭雨 | Demon arrow rain
|
|
pSkill.GetSkillID() == 2741 || // 妖飞箭雨 | Monster arrow rain
|
|
pSkill.GetSkillID() == 2559 || // 闪电 | Lightning
|
|
pSkill.GetSkillID() == 2752 || // 魔·闪电 | Demon lightning
|
|
pSkill.GetSkillID() == 2753) // 妖闪电 | Monster lightning
|
|
{
|
|
A3DVECTOR3 vPos = pCmd.pos;
|
|
if (!IsPosCollideFree(vPos))
|
|
{
|
|
// Add a little height to ensure player's AABB won't embed with building
|
|
// 添加一点高度以确保玩家的AABB不会嵌入建筑物
|
|
vPos += new A3DVECTOR3(0, 1, 0) * 0.1f;
|
|
}
|
|
|
|
// Ensure we are not under ground
|
|
// 确保我们不在地下
|
|
A3DVECTOR3 vNormal = new A3DVECTOR3();
|
|
float vTerrainHeight = CECWorld.Instance.GetTerrainHeight(vPos, ref vNormal);
|
|
if (vPos.y < vTerrainHeight)
|
|
vPos.y = vTerrainHeight;
|
|
|
|
m_CDRInfo.vTPNormal = vPos.y <= vTerrainHeight + 0.1f ? vNormal : new A3DVECTOR3(0);
|
|
m_CDRInfo.fYVel = 0.0f;
|
|
m_CDRInfo.vAbsVelocity = new A3DVECTOR3(0);
|
|
ResetJump();
|
|
|
|
pWork.PrepareMove(pCmd.pos, nExecuteTime * 0.001f, pSkill.GetSkillID());
|
|
}
|
|
else
|
|
{
|
|
if (pSkill.GetRangeType() == (int)CECSkill.RangeType.RANGE_SLEF ||
|
|
pSkill.GetRangeType() == (int)CECSkill.RangeType.RANGE_SELFSPHERE)
|
|
{
|
|
m_CDRInfo.vTPNormal = m_MoveCtrl.m_vFlashTPNormal;
|
|
}
|
|
|
|
pWork.PrepareMove(pCmd.pos, nExecuteTime * 0.001f, 0);
|
|
}
|
|
|
|
m_pWorkMan.StartWork_p2(pWork);
|
|
iActionTime = nExecuteTime;
|
|
}
|
|
|
|
bActionStartSkill = true;
|
|
break;
|
|
}
|
|
case int value2 when value2 == CommandID.PLAYER_CAST_RUNE_SKILL:
|
|
{
|
|
// Cast rune skill (item skill)
|
|
// 施放符文技能(物品技能)
|
|
cmd_player_cast_rune_skill pCmd =
|
|
GPDataTypeHelper.FromBytes<cmd_player_cast_rune_skill>((byte[])Msg.dwParam1);
|
|
Debug.Assert(pCmd.caster == m_PlayerInfo.cid);
|
|
|
|
if (m_pTargetItemSkill != null)
|
|
{
|
|
// Delete old target item skill
|
|
m_pTargetItemSkill = null;
|
|
}
|
|
|
|
m_pTargetItemSkill = new CECSkill(pCmd.skill, pCmd.level);
|
|
if (m_pTargetItemSkill == null)
|
|
{
|
|
Debug.Assert(m_pTargetItemSkill != null, "Target item skill should not be null");
|
|
return;
|
|
}
|
|
|
|
m_pCurSkill = m_pTargetItemSkill;
|
|
|
|
if (m_pCurSkill.IsChargeable())
|
|
m_pCurSkill.StartCharging(pCmd.time);
|
|
|
|
int iWaitTime = -1;
|
|
if (m_pCurSkill.GetExecuteTime() >= 0)
|
|
iWaitTime = pCmd.time + m_pCurSkill.GetExecuteTime();
|
|
|
|
CECHPWorkSpell pWork =
|
|
(CECHPWorkSpell)m_pWorkMan.CreateWork(CECHPWork.Host_work_ID.WORK_SPELLOBJECT);
|
|
pWork.PrepareCast(pCmd.target, m_pCurSkill, iWaitTime);
|
|
m_pWorkMan.StartWork_p1(pWork);
|
|
|
|
// Start time counter for some type skill
|
|
// 为某些类型的技能启动时间计数器
|
|
if (!m_pCurSkill.IsChargeable())
|
|
{
|
|
int iTime = pCmd.time;
|
|
if (iTime < 10) iTime = 10; // a_ClampFloor
|
|
m_IncantCnt.SetPeriod(iTime);
|
|
m_IncantCnt.Reset();
|
|
}
|
|
else
|
|
{
|
|
// make sure the counter is correct shown
|
|
// 确保计数器正确显示
|
|
m_IncantCnt.Reset(true);
|
|
}
|
|
|
|
m_bSpellDSkill = false;
|
|
|
|
TurnFaceTo(pCmd.target);
|
|
|
|
m_idCurSkillTarget = pCmd.target;
|
|
PlaySkillCastAction(m_pCurSkill.GetSkillID());
|
|
|
|
bActionStartSkill = true;
|
|
iActionTime = iWaitTime;
|
|
Debug.Log($"Cast rune skill({m_pCurSkill.GetSkillID()})");
|
|
break;
|
|
}
|
|
case int value2 when value2 == CommandID.PLAYER_CAST_RUNE_INSTANT_SKILL:
|
|
{
|
|
// Cast instant rune skill
|
|
// 施放即时符文技能
|
|
cmd_player_cast_rune_instant_skill pCmd =
|
|
GPDataTypeHelper.FromBytes<cmd_player_cast_rune_instant_skill>((byte[])Msg.dwParam1);
|
|
Debug.Assert(pCmd.caster == m_PlayerInfo.cid);
|
|
|
|
if (m_pTargetItemSkill != null)
|
|
{
|
|
// Delete old target item skill
|
|
m_pTargetItemSkill = null;
|
|
}
|
|
|
|
m_pTargetItemSkill = new CECSkill(pCmd.skill, pCmd.level);
|
|
if (m_pTargetItemSkill == null)
|
|
{
|
|
Debug.Assert(m_pTargetItemSkill != null, "Target item skill should not be null");
|
|
return;
|
|
}
|
|
|
|
if (pCmd.target != 0 && pCmd.target != m_PlayerInfo.cid)
|
|
TurnFaceTo(pCmd.target);
|
|
|
|
PlaySkillCastAction(m_pTargetItemSkill.GetSkillID());
|
|
bActionStartSkill = true;
|
|
break;
|
|
}
|
|
case int value2 when value2 == CommandID.ERROR_MESSAGE:
|
|
bDoOtherThing = true;
|
|
break;
|
|
|
|
default:
|
|
Debug.Assert(false, "Unknown message type in OnMsgPlayerCastSkill");
|
|
break;
|
|
}
|
|
|
|
/* if (bActionStartSkill)
|
|
AP_ActionEvent(AP_EVENT_STARTSKILL, iActionTime);*/
|
|
|
|
if (bDoOtherThing)
|
|
{
|
|
if (m_pComboSkill != null && !m_pComboSkill.IsStop())
|
|
{
|
|
// Continue combo skill
|
|
// 继续连击技能
|
|
// if (CECAutoPolicy::GetInstance().IsAutoPolicyEnabled())
|
|
// g_pGame.GetGameRun().PostMessage(MSG_HST_CONTINUECOMBOSKILL, MAN_PLAYER, 0, 0, m_pComboSkill.GetGroupIndex());
|
|
// else
|
|
m_pComboSkill.Continue(false);
|
|
}
|
|
else
|
|
{
|
|
if (idTarget != 0 && idTarget != m_PlayerInfo.cid)
|
|
NormalAttackObject(idTarget, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnMsgHstSkillResult(ECMSG Msg)
|
|
{
|
|
cmd_host_skill_attack_result pCmd =
|
|
GPDataTypeHelper.FromBytes<cmd_host_skill_attack_result>((byte[])Msg.dwParam1);
|
|
int refFake = 0;
|
|
PlayAttackEffect(pCmd.idTarget, pCmd.idSkill, 0, pCmd.iDamage, (uint)pCmd.attack_flag,
|
|
pCmd.attack_speed * 50, ref refFake, pCmd.section);
|
|
}
|
|
|
|
private void OnMsgHstSkillData(ECMSG Msg)
|
|
{
|
|
cmd_skill_data pCmd = default;
|
|
pCmd.skill_count = GPDataTypeHelper.FromBytes<uint>((byte[])Msg.dwParam1);
|
|
int offset = sizeof(uint);
|
|
int skillSize = Marshal.SizeOf<cmd_skill_data.SKILL>();
|
|
pCmd.skill_list = new cmd_skill_data.SKILL[pCmd.skill_count];
|
|
for (int i = 0; i < pCmd.skill_count; i++)
|
|
{
|
|
pCmd.skill_list[i] = GPDataTypeHelper.FromBytes<cmd_skill_data.SKILL>((byte[])Msg.dwParam1, offset);
|
|
offset += skillSize;
|
|
}
|
|
|
|
if (pCmd.skill_list == null)
|
|
{
|
|
BMLogger.LogError("OnMsgHstSkillData: cmd is null");
|
|
return;
|
|
}
|
|
|
|
/* List<SkillShortCutConfig> skillSCConfigArray1 = new List<SkillShortCutConfig>();
|
|
List<SkillShortCutConfig> skillSCConfigArray2 = new List<SkillShortCutConfig>();
|
|
List<SkillGrpShortCutConfig> skillGrpSCConfigArray1 = new List<SkillGrpShortCutConfig>();
|
|
List<SkillGrpShortCutConfig> skillGrpSCConfigArray2 = new List<SkillGrpShortCutConfig>();*/
|
|
|
|
if (HostIsReady())
|
|
{
|
|
/*
|
|
for (int i = 0; i < HostConstants.NUM_HOSTSCSETS1; i++)
|
|
{
|
|
if (hostPlayer.m_aSCSets1[i] != null)
|
|
{
|
|
hostPlayer.m_aSCSets1[i].RemoveSkillShortcuts();
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < HostConstants.NUM_HOSTSCSETS2; i++)
|
|
{
|
|
if (hostPlayer.m_aSCSets2[i] != null)
|
|
{
|
|
hostPlayer.m_aSCSets2[i].RemoveSkillShortcuts();
|
|
}
|
|
}*/
|
|
|
|
// Release passive skills
|
|
m_aPsSkills.Clear();
|
|
}
|
|
|
|
// Load skill data from command
|
|
// C++: GNET::ElementSkill::LoadSkillData(pCmd);
|
|
ElementSkill.LoadSkillData(pCmd);
|
|
// Create skill objects from command data
|
|
for (int i = 0; i < pCmd.skill_count; i++)
|
|
{
|
|
cmd_skill_data.SKILL data = pCmd.skill_list[i];
|
|
CECSkill skill = new CECSkill(data.id_skill, data.level);
|
|
|
|
// Categorize skills into positive and passive
|
|
int skillType = skill.GetType();
|
|
if (skillType != (int)CECSkill.SkillType.TYPE_PASSIVE &&
|
|
skillType != (int)CECSkill.SkillType.TYPE_PRODUCE &&
|
|
skillType != (int)CECSkill.SkillType.TYPE_LIVE)
|
|
{
|
|
m_aPtSkills.Add(skill);
|
|
}
|
|
else
|
|
{
|
|
m_aPsSkills.Add(skill);
|
|
}
|
|
}
|
|
// Restore and convert shortcuts after loading new skills
|
|
if (HostIsReady())
|
|
{
|
|
/* ConvertSkillShortcut(skillSCConfigArray1);
|
|
AssignSkillShortcut(skillSCConfigArray1, hostPlayer.m_aSCSets1);*/
|
|
/* ConvertSkillShortcut(skillSCConfigArray2);
|
|
AssignSkillShortcut(skillSCConfigArray2, hostPlayer.m_aSCSets2);*/
|
|
/* ConvertComboSkill();
|
|
ValidateSkillGrpShortcut(skillGrpSCConfigArray1);
|
|
AssignSkillGrpShortcut(skillGrpSCConfigArray1, hostPlayer.m_aSCSets1);
|
|
ValidateSkillGrpShortcut(skillGrpSCConfigArray2);
|
|
AssignSkillGrpShortcut(skillGrpSCConfigArray2, hostPlayer.m_aSCSets2);*/
|
|
}
|
|
|
|
if (HostIsReady())
|
|
{
|
|
// Update UI when profession changes, save all shortcut configurations
|
|
// to remove effects from intermediate skills (invalid pointers)
|
|
// C++: CECGameUIMan *pGameUIMan = g_pGame.GetGameRun().GetUIManager().GetInGameUIMan();
|
|
// pGameUIMan.UpdateSkillRelatedUI();
|
|
CECUIManager.Instance.UpdateSkillRelatedUI();
|
|
}
|
|
}
|
|
|
|
public bool HostIsReady()
|
|
{
|
|
return m_bEnterGame;
|
|
}
|
|
|
|
private void OnMsgHstDied(in ECMSG msg)
|
|
{
|
|
// Mark host player as corpse so CECPlayer.IsDead() returns true
|
|
m_dwStates |= (uint)PlayerNPCState.GP_STATE_CORPSE;
|
|
|
|
EventBus.PublishChannel(GetCharacterID(), new ClearComActFlagAllRankNodesEvent(true));
|
|
PlayAction((int)PLAYER_ACTION_TYPE.ACT_GROUNDDIE);
|
|
if (PopupManager.Instance != null)
|
|
{
|
|
PopupManager.Instance.OnPlayerDied();
|
|
}
|
|
}
|
|
|
|
private void OnMsgHstInfo00(in ECMSG Msg)
|
|
{
|
|
cmd_self_info_00 pCmd = GPDataTypeHelper.FromBytes<cmd_self_info_00>((byte[])Msg.dwParam1);
|
|
|
|
bool bFirstTime = m_BasicProps.iLevel == 0 ? true : false;
|
|
if (!bFirstTime)
|
|
{
|
|
int iLimit = (int)(pCmd.iMaxHP * 0.3f);
|
|
if (pCmd.iHP < m_BasicProps.iCurHP && m_BasicProps.iCurHP >= iLimit && pCmd.iHP < iLimit)
|
|
{
|
|
/*if (CECUIHelper::GetGameUIMan().IsShowLowHP()) {
|
|
// ѪÁ¿µÍÓÚÁÙ½çÖµÔò²¥·ÅÌØÐ§
|
|
const int GfxLastTime = 10000; // ³ÖÐøÊ±¼ä10Ãë
|
|
CECUIHelper::GetGameUIMan().GetScreenEffectMan().StartEffect(CECScreenEffect::EFFECT_REDSPARK, GfxLastTime);
|
|
}*/
|
|
}
|
|
|
|
/*if (pCmd.iHP >= iLimit || pCmd.iHP <= 0) {
|
|
// ѪÁ¿¸ßÓÚÁÙ½çÖµ»òËÀÍö£¬ÔòÍ£Ö¹²¥·ÅÌØÐ§
|
|
CECUIHelper::GetGameUIMan().GetScreenEffectMan().FinishEffect(CECScreenEffect::EFFECT_REDSPARK);
|
|
}*/
|
|
|
|
/*iLimit = (int)(pCmd.iMaxMP * 0.2f);
|
|
if (pCmd.iMP < m_BasicProps.iCurMP && m_BasicProps.iCurMP >= iLimit && pCmd.iMP < iLimit)
|
|
BubbleText(BUBBLE_MPWARN, 0);*/
|
|
|
|
/*if (m_ExtProps.max_ap != pCmd.iMaxAP)
|
|
g_pGame.GetGameRun().AddFixedMessage(FIXMSG_ADDMAXAP, pCmd.iMaxAP - m_ExtProps.max_ap);*/
|
|
}
|
|
|
|
m_BasicProps.iLevel = pCmd.sLevel;
|
|
SetLevel2(pCmd.Level2, bFirstTime);
|
|
m_BasicProps.iExp = pCmd.iExp;
|
|
m_BasicProps.iSP = pCmd.iSP;
|
|
m_BasicProps.iCurHP = pCmd.iHP;
|
|
m_BasicProps.iCurMP = pCmd.iMP;
|
|
m_BasicProps.iCurAP = pCmd.iAP;
|
|
m_ExtProps.bs.max_hp = pCmd.iMaxHP;
|
|
m_ExtProps.bs.max_mp = pCmd.iMaxMP;
|
|
m_ExtProps.max_ap = pCmd.iMaxAP;
|
|
|
|
EventBus.Publish(new EXPToUpLevel(GetLevelUpExp(pCmd.sLevel)));
|
|
EventBus.Publish<cmd_self_info_00>(pCmd);
|
|
EventBus.PublishChannel<cmd_self_info_00>(GetCharacterID(), pCmd);
|
|
// if (pCmd.State != 0 && m_bFight == false) PlayEnterBattleGfx();
|
|
m_bFight = pCmd.State != 0 ? true : false;
|
|
|
|
// UpdateGodEvilSprite();
|
|
|
|
/*CECGameUIMan* pGameUI = g_pGame.GetGameRun().GetUIManager().GetInGameUIMan();
|
|
CDlgAutoHelp *pDlgHelp = dynamic_cast<CDlgAutoHelp *>(pGameUI.GetDialog("Win_WikiPop"));*/
|
|
/*if(pDlgHelp && m_bFight)
|
|
pDlgHelp.SetAutoHelpState(false);*/
|
|
}
|
|
|
|
void SetLevel2(int level2, bool bFirstTime)
|
|
{
|
|
int lastLevel2 = m_BasicProps.iLevel2;
|
|
m_BasicProps.iLevel2 = level2;
|
|
/*if (CanPlayTaoistEffect(lastLevel2, level2, bFirstTime)){
|
|
PlayTaoistEffect();
|
|
}*/
|
|
}
|
|
|
|
void OnMsgHstAttacked(ECMSG Msg)
|
|
{
|
|
var m_pPlayerMan = EC_ManMessageMono.Instance.EC_ManPlayer;
|
|
cmd_host_attacked pCmd = GPDataTypeHelper.FromBytes<cmd_host_attacked>(Msg.dwParam1 as byte[]);
|
|
|
|
if (pCmd.iDamage != 0 && (pCmd.cEquipment & 0x7f) != 0x7f)
|
|
{
|
|
/* char cEquip = (char)(pCmd.cEquipment & 0x7f);
|
|
CECIvtrEquip pEquip = (CECIvtrEquip)m_pEquipPack.GetItem(cEquip);
|
|
if (pEquip)
|
|
pEquip.AddCurEndurance(ARMOR_RUIN_SPEED);*/
|
|
}
|
|
|
|
// The host player is attacked, we should make an effect here
|
|
if (GPDataTypeHelper.ISPLAYERID(pCmd.idAttacker))
|
|
{
|
|
EC_ElsePlayer pAttacker = m_pPlayerMan.GetElsePlayer(pCmd.idAttacker);
|
|
if (pAttacker)
|
|
{
|
|
if (!pAttacker.IsDead())
|
|
{
|
|
// Face to target
|
|
pAttacker.TurnFaceTo(GetPlayerInfo().cid);
|
|
}
|
|
|
|
int useless_attacktime = 0;
|
|
pAttacker.PlayAttackEffect(GetCharacterID(), 0, 0, pCmd.iDamage, (uint)pCmd.attack_flag,
|
|
pCmd.speed * 50, ref useless_attacktime);
|
|
pAttacker.EnterFightState();
|
|
}
|
|
}
|
|
else if (GPDataTypeHelper.ISNPCID(pCmd.idAttacker))
|
|
{
|
|
CECNPC pAttacker = EC_ManMessageMono.Instance.CECNPCMan.GetNPC(pCmd.idAttacker);
|
|
if (pAttacker)
|
|
{
|
|
pAttacker.OnMsgAttackHostResult(GetCharacterID(), pCmd.iDamage, pCmd.attack_flag, pCmd.speed);
|
|
}
|
|
}
|
|
|
|
//CECAutoPolicy::GetInstance().SendEvent_BeHurt(pCmd.idAttacker);
|
|
}
|
|
|
|
public void OnMsgHstAttackResult(ECMSG Msg)
|
|
{
|
|
byte[] data = Msg.dwParam1 as byte[];
|
|
cmd_host_attack_result pCmd = EC_Utility.ByteArrayToStructure<cmd_host_attack_result>(data);
|
|
|
|
int iAttackTime = 0;
|
|
TurnFaceTo(pCmd.idTarget);
|
|
|
|
PlayAttackEffect(pCmd.idTarget, 0, 0, pCmd.iDamage, (uint)pCmd.attack_flag, pCmd.attack_speed * 50,
|
|
ref iAttackTime);
|
|
|
|
if (iAttackTime != 0)
|
|
{
|
|
if (m_pWorkMan.GetRunningWork(CECHPWork.Host_work_ID.WORK_HACKOBJECT) is CECHPWorkMelee pCurWork)
|
|
{
|
|
pCurWork.SetIdleTime(iAttackTime);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* public override bool IsWorkMoveRunning()
|
|
{
|
|
return m_pWorkMan.IsMovingToPosition();
|
|
}*/
|
|
private void OnMsgHstHurtResult(ECMSG Msg)
|
|
{
|
|
//BMLogger.LogError("HoangDev : OnMsgHstHurtResult");
|
|
/* int cmd = Convert.ToInt32(Msg.dwParam2);
|
|
if (cmd == CommandID.BE_HURT)
|
|
{
|
|
cmd_be_hurt pCmd = (cmd_be_hurt)Msg.dwParam1;
|
|
if (pCmd.damage != 0)
|
|
Damaged(pCmd.damage);
|
|
}
|
|
else if (cmd == CommandID.HURT_RESULT)
|
|
{
|
|
cmd_hurt_result pCmd = (cmd_hurt_result)Msg.dwParam1;
|
|
if (pCmd.target_id == m_PlayerInfo.cid)
|
|
return; // Host himself will receive BE_HURT, so ignore this.
|
|
|
|
if (UnityGameSession.Instance.GameSession.ISPLAYERID(pCmd.target_id))
|
|
{
|
|
CECElsePlayer pTarget = m_pPlayerMan.GetElsePlayer(pCmd.target_id);
|
|
if (pTarget)
|
|
pTarget.Damaged(pCmd.damage);
|
|
}
|
|
else if (UnityGameSession.Instance.GameSession.ISNPCID(pCmd.target_id))
|
|
{
|
|
CECNPC pTarget = EC_ManMessageMono.Instance._CECNPCMan.GetNPC(pCmd.target_id);
|
|
if (pTarget)
|
|
pTarget.Damaged(pCmd.damage);
|
|
}
|
|
}*/
|
|
}
|
|
|
|
public bool IsPosCollideFree(A3DVECTOR3 vTargetPos)
|
|
{
|
|
bool bAvailable = (false);
|
|
|
|
while (true)
|
|
{
|
|
A3DVECTOR3 refake = new A3DVECTOR3();
|
|
float terrianHeight = CECWorld.Instance.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 = CECWorld.Instance;
|
|
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 = CECWorld.Instance;
|
|
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 OnMsgHstPickupItem(in ECMSG Msg)
|
|
{
|
|
var data = Msg.dwParam1 as byte[];
|
|
int cmd = Convert.ToInt32(Msg.dwParam2);
|
|
|
|
bool bDoOther = false;
|
|
int idItem, iExpireDate = 0, iAmount, iCmdLastSlot, iCmdSlotAmount, iPack, iMsg = -1;
|
|
|
|
switch (cmd)
|
|
{
|
|
case CommandID.HOST_OBTAIN_ITEM:
|
|
{
|
|
// Parse cmd_host_obtain_item struct data
|
|
int type = BitConverter.ToInt32(data, 0);
|
|
int expire_date = BitConverter.ToInt32(data, 4);
|
|
uint amount = BitConverter.ToUInt32(data, 8);
|
|
uint slot_amount = BitConverter.ToUInt32(data, 12);
|
|
byte where = data[16]; // Package index
|
|
byte index = data[17]; // Slot index in that package
|
|
var newItem = EC_IvtrItem.CreateItem(type, expire_date, (int)amount);
|
|
|
|
// Add item to inventory
|
|
var ivt = GetInventory(where);
|
|
ivt.SetItem(index, newItem);
|
|
|
|
Debug.Log(
|
|
$"[HOST_OBTAIN_ITEM] Successfully added item {type} to package {where}, slot {index} with count {amount}");
|
|
|
|
// Trigger UI refresh if an EC_InventoryUI is present in scene
|
|
var ui = GameObject.FindFirstObjectByType<EC_InventoryUI>();
|
|
if (ui != null)
|
|
{
|
|
ui.RefreshAll();
|
|
}
|
|
|
|
UpdateEquipSkins();
|
|
}
|
|
break;
|
|
case CommandID.PICKUP_ITEM:
|
|
{
|
|
int tid = BitConverter.ToInt32(data, 0);
|
|
int expire_date = BitConverter.ToInt32(data, 4);
|
|
iAmount = (int)BitConverter.ToUInt32(data, 8);
|
|
uint iSlotAmount = BitConverter.ToUInt32(data, 12);
|
|
byte byPackage = data[16];
|
|
byte bySlot = data[17];
|
|
|
|
//Debug.Log($"[Inventory] PICKUP_ITEM: tid={tid}, expire_date={expire_date}, iAmount={iAmount}, iSlotAmount={iSlotAmount}, byPackage={byPackage}, bySlot={bySlot}");
|
|
|
|
// Notify pickupItem script about successful pickup
|
|
pickupItem pickupScript = pickupItem.Instance;
|
|
if (pickupScript != null)
|
|
{
|
|
//Debug.Log($"[Inventory] PICKUP_ITEM: tid={tid}, expire_date={expire_date}, iAmount={iAmount}, iSlotAmount={iSlotAmount}, byPackage={byPackage}, bySlot={bySlot}");
|
|
|
|
// Notify pickupItem script about successful pickup
|
|
pickupScript = UnityEngine.Object.FindFirstObjectByType<pickupItem>();
|
|
if (pickupScript != null)
|
|
{
|
|
pickupScript.OnPickupSuccess(tid);
|
|
}
|
|
|
|
// Create new inventory item data
|
|
var newItem = EC_IvtrItem.CreateItem(tid, expire_date, (int)iAmount);
|
|
|
|
// Add item to inventory
|
|
var ivt = GetInventory(byPackage);
|
|
ivt.SetItem(bySlot, newItem);
|
|
|
|
//Debug.Log($"[Inventory] Successfully added item {tid} to package {byPackage}, slot {bySlot} with count {iAmount}");
|
|
|
|
// Trigger UI refresh if an EC_InventoryUI is present in scene
|
|
var ui = GameObject.FindFirstObjectByType<EC_InventoryUI>();
|
|
if (ui != null)
|
|
{
|
|
ui.RefreshAll();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning("[Inventory] PICKUP_ITEM: Invalid data length");
|
|
}
|
|
|
|
break;
|
|
}
|
|
case CommandID.TASK_DELIVER_ITEM:
|
|
cmd_task_deliver_item pCmd = GPDataTypeHelper.FromBytes<cmd_task_deliver_item>(data);
|
|
// ASSERT(pCmd);
|
|
|
|
idItem = pCmd.type;
|
|
iExpireDate = pCmd.expire_date;
|
|
iAmount = (int)pCmd.amount;
|
|
iCmdLastSlot = pCmd.index;
|
|
iCmdSlotAmount = (int)pCmd.slot_amount;
|
|
iPack = pCmd.where;
|
|
iMsg = (int)FixedMsg.FIXMSG_GAINITEM;
|
|
bDoOther = true;
|
|
|
|
|
|
// Create new inventory item data
|
|
var taskNewItem = EC_IvtrItem.CreateItem(idItem, iExpireDate, (int)iAmount);
|
|
|
|
// Add item to inventory
|
|
var task_ivt = GetInventory((byte)iPack);
|
|
if (!task_ivt.MergeItem(idItem, iExpireDate, iAmount, out var iLastSlot, out var iSlotNum) ||
|
|
iLastSlot != iCmdLastSlot || iSlotNum != iCmdSlotAmount)
|
|
{
|
|
return;
|
|
}
|
|
task_ivt.SetItem(iCmdLastSlot, taskNewItem);
|
|
|
|
//Debug.Log($"[Inventory] Successfully added item {tid} to package {byPackage}, slot {bySlot} with count {iAmount}");
|
|
|
|
// Trigger UI refresh if an EC_InventoryUI is present in scene
|
|
var task_ui = GameObject.FindFirstObjectByType<EC_InventoryUI>();
|
|
if (task_ui != null)
|
|
{
|
|
task_ui.RefreshAll();
|
|
}
|
|
|
|
break;
|
|
case CommandID.PRODUCE_ONCE:
|
|
{
|
|
// Parse cmd_produce_once struct data
|
|
cmd_produce_once produceCmd = GPDataTypeHelper.FromBytes<cmd_produce_once>(data);
|
|
|
|
int produceItemId = produceCmd.type;
|
|
int produceExpireDate = 0;
|
|
uint produceAmount = produceCmd.amount;
|
|
byte producePack = produceCmd.where;
|
|
byte produceSlot = produceCmd.index;
|
|
|
|
Debug.Log($"[PRODUCE_ONCE] Received: itemId={produceItemId}, amount={produceAmount}, pack={producePack}, slot={produceSlot}");
|
|
|
|
// Get inventory
|
|
var produce_ivt = GetInventory(producePack);
|
|
if (produce_ivt == null)
|
|
{
|
|
Debug.LogWarning($"[PRODUCE_ONCE] Invalid inventory package {producePack}");
|
|
return;
|
|
}
|
|
|
|
// Check if the slot already has an item
|
|
var existingItem = produce_ivt.GetItem(produceSlot, false);
|
|
|
|
if (existingItem != null)
|
|
{
|
|
if (existingItem.m_tid == produceItemId)
|
|
{
|
|
existingItem.m_iCount = (int)produceAmount;
|
|
Debug.Log($"[PRODUCE_ONCE] Updated existing item count at slot {produceSlot} to {produceAmount}");
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning($"[PRODUCE_ONCE] Slot {produceSlot} already has different item (tid={existingItem.m_tid}), not overwriting with {produceItemId}");
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var produceNewItem = new EC_IvtrItem
|
|
{
|
|
Package = producePack,
|
|
Slot = produceSlot,
|
|
m_tid = produceItemId,
|
|
m_expire_date = produceExpireDate,
|
|
State = 0,
|
|
m_iCount = (int)produceAmount,
|
|
Crc = 0,
|
|
Content = null
|
|
};
|
|
|
|
produce_ivt.SetItem(produceSlot, produceNewItem);
|
|
Debug.Log($"[PRODUCE_ONCE] Created new item at slot {produceSlot} with count {produceAmount}");
|
|
}
|
|
|
|
// Trigger UI refresh
|
|
var produce_ui = GameObject.FindFirstObjectByType<EC_InventoryUI>();
|
|
if (produce_ui != null)
|
|
{
|
|
produce_ui.RefreshAll();
|
|
}
|
|
|
|
UpdateEquipSkins();
|
|
|
|
// Notify DlgProduce
|
|
var dlgProduce = GameObject.FindFirstObjectByType<DlgProduce>();
|
|
if (dlgProduce != null)
|
|
{
|
|
dlgProduce.OnProduceOnce(produceCmd);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
public void OnMsgHstProduceItem(in ECMSG Msg)
|
|
{
|
|
var data = Msg.dwParam1 as byte[];
|
|
int cmd = Convert.ToInt32(Msg.dwParam2);
|
|
|
|
// Get DlgProduce to notify
|
|
var dlgProduce = GameObject.FindFirstObjectByType<DlgProduce>();
|
|
if (dlgProduce == null)
|
|
{
|
|
Debug.LogWarning("[OnMsgHstProduceItem] DlgProduce not found");
|
|
return;
|
|
}
|
|
|
|
switch (cmd)
|
|
{
|
|
case CommandID.PRODUCE_START:
|
|
{
|
|
cmd_produce_start pCmd = GPDataTypeHelper.FromBytes<cmd_produce_start>(data);
|
|
Debug.Log($"[PRODUCE_START] type={pCmd.type}, use_time={pCmd.use_time}, count={pCmd.count}");
|
|
dlgProduce.OnProduceStart(pCmd);
|
|
}
|
|
break;
|
|
case CommandID.PRODUCE_END:
|
|
{
|
|
Debug.Log("[PRODUCE_END] Production ended");
|
|
dlgProduce.OnProduceEnd();
|
|
}
|
|
break;
|
|
case CommandID.PRODUCE_NULL:
|
|
{
|
|
cmd_produce_null pCmd = GPDataTypeHelper.FromBytes<cmd_produce_null>(data);
|
|
Debug.Log($"[PRODUCE_NULL] type={pCmd.type}");
|
|
dlgProduce.OnProduceNull(pCmd);
|
|
}
|
|
break;
|
|
default:
|
|
Debug.LogWarning($"[OnMsgHstProduceItem] Unknown command: {cmd}");
|
|
break;
|
|
}
|
|
}
|
|
|
|
public void OnMsgHstItemOperation(ECMSG Msg)
|
|
{
|
|
var data = Msg.dwParam1 as byte[];
|
|
int cmd = Convert.ToInt32(Msg.dwParam2);
|
|
switch (cmd)
|
|
{
|
|
case CommandID.PLAYER_DROP_ITEM:
|
|
{
|
|
// Parse the drop item data from the server response
|
|
if (data != null && data.Length >= 6)
|
|
{
|
|
byte byPackage = data[0];
|
|
byte bySlot = data[1];
|
|
int count = BitConverter.ToInt32(data, 2);
|
|
int tid = BitConverter.ToInt32(data, 6);
|
|
byte reason = data[10];
|
|
|
|
Debug.Log(
|
|
$"[Inventory] PLAYER_DROP_ITEM: package={byPackage}, slot={bySlot}, count={count}, tid={tid}, reason={reason}");
|
|
|
|
// Update the inventory by removing the item
|
|
var inv = GetInventory(byPackage);
|
|
bool success = inv != null && inv.RemoveItem(bySlot, count);
|
|
|
|
if (success)
|
|
{
|
|
Debug.Log(
|
|
$"[Inventory] Successfully removed {count} items from package {byPackage}, slot {bySlot}");
|
|
|
|
// Trigger UI refresh if an EC_InventoryUI is present in scene
|
|
var ui = GameObject.FindFirstObjectByType<EC_InventoryUI>();
|
|
if (ui != null)
|
|
{
|
|
ui.RefreshAll();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning(
|
|
$"[Inventory] Failed to remove items from package {byPackage}, slot {bySlot}");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning("[Inventory] PLAYER_DROP_ITEM: Invalid data length");
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case CommandID.EQUIP_ITEM:
|
|
{
|
|
byte index_inv = data[0];
|
|
byte index_equip = data[1];
|
|
// Update client-side data: move item between PACK_INVENTORY and PACK_EQUIPMENT
|
|
var packInv = GetInventory(InventoryConst.IVTRTYPE_PACK);
|
|
var equipInv = GetInventory(InventoryConst.IVTRTYPE_EQUIPPACK);
|
|
var invItem = packInv?.GetItem(index_inv, true);
|
|
var equipItem = equipInv?.GetItem(index_equip, true);
|
|
UpdateEquipSkins();
|
|
if (invItem != null)
|
|
{
|
|
invItem.Package = InventoryConst.IVTRTYPE_EQUIPPACK;
|
|
invItem.Slot = index_equip;
|
|
equipInv?.SetItem(index_equip, invItem);
|
|
}
|
|
|
|
if (equipItem != null)
|
|
{
|
|
equipItem.Package = InventoryConst.IVTRTYPE_PACK;
|
|
equipItem.Slot = index_inv;
|
|
packInv?.SetItem(index_inv, equipItem);
|
|
}
|
|
|
|
// Trigger UI refresh if an EC_InventoryUI is present in scene
|
|
var ui = GameObject.FindObjectOfType<EC_InventoryUI>();
|
|
if (ui != null)
|
|
{
|
|
ui.RefreshAll();
|
|
}
|
|
|
|
UpdateEquipSkins();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void OnMsgHstOwnItemInfo(ECMSG Msg)
|
|
{
|
|
int cmd = Convert.ToInt32(Msg.dwParam2);
|
|
switch (cmd)
|
|
{
|
|
case CommandID.OWN_ITEM_INFO:
|
|
{
|
|
Debug.Log("[Inventory] OWN_ITEM_INFO received");
|
|
var data = Msg.dwParam1 as byte[];
|
|
int hostId = Convert.ToInt32(Msg.dwParam3);
|
|
LogInventoryPacket("OWN_ITEM_INFO", data, hostId);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void OnMsgHstIvtrInfo(ECMSG Msg)
|
|
{
|
|
var data = Msg.dwParam1 as byte[];
|
|
int cmd = Convert.ToInt32(Msg.dwParam2);
|
|
int hostId = Convert.ToInt32(Msg.dwParam3);
|
|
|
|
switch (cmd)
|
|
{
|
|
case CommandID.OWN_IVTR_DATA:
|
|
{
|
|
LogInventoryPacket("OWN_IVTR_DATA", data, hostId);
|
|
break;
|
|
}
|
|
case CommandID.OWN_IVTR_DETAIL_DATA:
|
|
{
|
|
// EC_Inventory.LogInventoryPacket("OWN_IVTR_DETAIL_DATA", data, hostId);
|
|
// Parse and store
|
|
if (data != null && data.Length >= 6)
|
|
{
|
|
byte byPackage = data[0];
|
|
byte ivtrSize = data[1];
|
|
if (EC_IvtrItemUtils.Instance.TryParseInventoryDetail(data, out var pkg,
|
|
out var size, out var items))
|
|
{
|
|
var inv = GetInventory(pkg);
|
|
if (inv != null)
|
|
{
|
|
inv.Resize(size);
|
|
inv.RemoveAllItems();
|
|
|
|
if (items != null)
|
|
{
|
|
foreach (var it in items)
|
|
{
|
|
if (it != null && it.Slot >= 0 && it.Slot < size)
|
|
inv.SetItem(it.Slot, it);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// check if we got the item from the Equipment Pack. If so, we have to load the equipment items
|
|
if (byPackage == InventoryConst.IVTRTYPE_EQUIPPACK)
|
|
{
|
|
UpdateEquipSkins();
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
case CommandID.GET_OWN_MONEY:
|
|
{
|
|
if (data != null)
|
|
{
|
|
try
|
|
{
|
|
var money = GPDataTypeHelper.FromBytes<CSNetwork.GPDataType.cmd_get_own_money>(data);
|
|
|
|
SetMoneyAmount(money.amount);
|
|
|
|
var ui = GameObject.FindFirstObjectByType<BrewMonster.Scripts.Managers.EC_InventoryUI>();
|
|
if (ui == null)
|
|
{
|
|
var all = Resources.FindObjectsOfTypeAll<BrewMonster.Scripts.Managers.EC_InventoryUI>();
|
|
if (all != null)
|
|
{
|
|
for (int i = 0; i < all.Length; i++)
|
|
{
|
|
var candidate = all[i];
|
|
if (candidate != null && candidate.gameObject.scene.IsValid())
|
|
{
|
|
ui = candidate;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ui != null)
|
|
{
|
|
ui.UpdateMoney(money.amount, money.max_amount);
|
|
}
|
|
else
|
|
{
|
|
BrewMonster.Scripts.Managers.EC_InventoryUI.CacheMoney(money.amount, money.max_amount);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogWarning($"[Inventory] Failed to parse GET_OWN_MONEY: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
case CommandID.PLAYER_CASH:
|
|
{
|
|
if (data != null)
|
|
{
|
|
try
|
|
{
|
|
var cash = GPDataTypeHelper.FromBytes<CSNetwork.GPDataType.player_cash>(data);
|
|
var ui = GameObject.FindFirstObjectByType<BrewMonster.Scripts.Managers.EC_InventoryUI>();
|
|
if (ui == null)
|
|
{
|
|
var all = Resources.FindObjectsOfTypeAll<BrewMonster.Scripts.Managers.EC_InventoryUI>();
|
|
if (all != null)
|
|
{
|
|
for (int i = 0; i < all.Length; i++)
|
|
{
|
|
var candidate = all[i];
|
|
if (candidate != null && candidate.gameObject.scene.IsValid())
|
|
{
|
|
ui = candidate;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ui != null)
|
|
{
|
|
ui.UpdateCash(cash.cash_amount);
|
|
}
|
|
else
|
|
{
|
|
BrewMonster.Scripts.Managers.EC_InventoryUI.CacheCash(cash.cash_amount);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogWarning($"[Inventory] Failed to parse PLAYER_CASH: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void OnMsgHstCorrectPos(in ECMSG Msg)
|
|
{
|
|
//Debug.LogError("HoangDev : OnMsgHstCorrectPos");
|
|
byte[] buf = (byte[])Msg.dwParam1; // chỗ bạn lưu pDataBuf
|
|
GCHandle handle = GCHandle.Alloc(buf, GCHandleType.Pinned);
|
|
cmd_host_correct_pos pCmd = (cmd_host_correct_pos)Marshal.PtrToStructure(
|
|
handle.AddrOfPinnedObject(), typeof(cmd_host_correct_pos));
|
|
handle.Free();
|
|
//cmd_host_correct_pos pCmd = GPDataTypeHelper.FromBytes<cmd_host_correct_pos>((byte[])Msg.dwParam1);
|
|
//Debug.LogError("HoangDev :pCmd.pos " + pCmd.pos);
|
|
SetPos(pCmd.pos);
|
|
m_vVelocity.Clear();
|
|
m_CDRInfo.vAbsVelocity.Clear();
|
|
|
|
m_MoveCtrl.SetMoveStamp(pCmd.stamp);
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
public void OnMsgHstGoto(in ECMSG Msg)
|
|
{
|
|
PopupManager.Instance.OnPlayerRevived();
|
|
// p1 is a byte[] buffer; parse into cmd_notify_hostpos then set position
|
|
byte[] buf = (byte[])Msg.dwParam1;
|
|
cmd_notify_hostpos pCmd = GPDataTypeHelper.FromBytes<cmd_notify_hostpos>(buf);
|
|
SetPos(new Vector3(pCmd.vPos.x, pCmd.vPos.y, pCmd.vPos.z));
|
|
}
|
|
|
|
private void OnMsgHstStartAttack(in ECMSG Msg)
|
|
{
|
|
cmd_host_start_attack pCmd = GPDataTypeHelper.FromBytes<cmd_host_start_attack>((byte[])Msg.dwParam1);
|
|
|
|
// ASSERT(pCmd);
|
|
|
|
// test code...
|
|
// g_pGame.GetRTDebug().OutputNotifyMessage(RTDCOL_WARNING, _AL("start attack !"));
|
|
|
|
// Check whether target is the one that we have selected
|
|
if (m_idSelTarget != pCmd.idTarget)
|
|
// g_pGame.RuntimeDebugInfo(RTDCOL_WARNING, _AL("Target has changed !"));
|
|
|
|
// If target turn to be un-attackable, cancel action
|
|
if (AttackableJudge(pCmd.idTarget, true) == 0)
|
|
{
|
|
UnityGameSession.c2s_CmdCancelAction();
|
|
// g_pGame.RuntimeDebugInfo(RTDCOL_WARNING, _AL("Cannel attacking !"));
|
|
return;
|
|
}
|
|
|
|
// Synchronize ammo amount
|
|
// CECIvtrItem* pItem = m_pEquipPack.GetItem(EQUIPIVTR_PROJECTILE);
|
|
// if (pItem)
|
|
// {
|
|
// if (!pCmd.ammo_remain)
|
|
// m_pEquipPack.SetItem(EQUIPIVTR_PROJECTILE, NULL);
|
|
// else
|
|
// pItem.SetAmount(pCmd.ammo_remain);
|
|
// }
|
|
|
|
CECHPWorkMelee pWork = (CECHPWorkMelee)m_pWorkMan.CreateWork(Host_work_ID.WORK_HACKOBJECT);
|
|
m_pWorkMan.StartWork_p1(pWork);
|
|
|
|
m_bMelee = true;
|
|
// AP_ActionEvent(AP_EVENT_STARTMELEE);
|
|
}
|
|
|
|
private void OnMsgHstStopAttack(in ECMSG Msg)
|
|
{
|
|
// using namespace S2C;
|
|
//
|
|
m_bMelee = false;
|
|
//
|
|
// // if there is an attack event currently, we should let it fire now.
|
|
ClearComActFlagAllRankNodes(true);
|
|
|
|
cmd_host_stop_attack pCmd = GPDataTypeHelper.FromBytes<cmd_host_stop_attack>((byte[])Msg.dwParam1);
|
|
// ASSERT(pCmd);
|
|
|
|
/* If attack stopped for target is leave too far, trace it and continue
|
|
attacking. Stop reason defined as below:
|
|
|
|
0x00: attack is canceled or host want to do some other things.
|
|
0x01: unable to attack anymore (no ammo, weapon is broken etc.)
|
|
0x02: invalid target (target missed or died)
|
|
0x04: target is out of range
|
|
*/
|
|
if ((pCmd.iReason & 0x04) != 0)
|
|
{
|
|
if (!m_pWorkMan.IsMovingToPosition() &&
|
|
!m_pWorkMan.IsTracing())
|
|
{
|
|
if (CmdNormalAttack(false, false, 0, -1)) //m_pComboSkill != NULL
|
|
{
|
|
// AP_ActionEvent(AP_EVENT_MELEEOUTOFRANGE, 1);
|
|
}
|
|
else
|
|
{
|
|
m_pWorkMan.FinishRunningWork(Host_work_ID.WORK_HACKOBJECT);
|
|
// AP_ActionEvent(AP_EVENT_STOPMELEE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// AP_ActionEvent(AP_EVENT_STOPMELEE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_pWorkMan.FinishRunningWork(Host_work_ID.WORK_HACKOBJECT);
|
|
// AP_ActionEvent(AP_EVENT_STOPMELEE);
|
|
}
|
|
|
|
// #ifdef _SHOW_AUTOPOLICY_DEBUG
|
|
// a_LogOutput(1, "Stop Attack, Reason = %d", pCmd.iReason);
|
|
// #endif
|
|
}
|
|
|
|
// 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);
|
|
m_idSelTarget = pCmd.idTarget;
|
|
m_idUCSelTarget = 0;
|
|
}
|
|
else if (Convert.ToInt32(Msg.dwParam2) == CommandID.UNSELECT)
|
|
{
|
|
m_idSelTarget = 0;
|
|
}
|
|
}
|
|
|
|
public void SetPos(Vector3 pos)
|
|
{
|
|
playerTransform.position = pos;
|
|
|
|
m_aabb.Center = EC_Utility.ToA3DVECTOR3(pos) + new A3DVECTOR3(0.0f, m_aabb.Extents.y, 0.0f);
|
|
m_aabb.CompleteMinsMaxs();
|
|
m_aabbServer.Center = EC_Utility.ToA3DVECTOR3(pos) + new A3DVECTOR3(0.0f, m_aabbServer.Extents.y, 0.0f);
|
|
m_aabbServer.CompleteMinsMaxs();
|
|
}
|
|
|
|
public void SetStatusRun(bool value)
|
|
{
|
|
if (!isGrounded)
|
|
{
|
|
Debug.LogError("Player not in ground");
|
|
return;
|
|
}
|
|
|
|
isRun = value;
|
|
}
|
|
|
|
public override void SetUpPlayer()
|
|
{
|
|
base.SetUpPlayer();
|
|
|
|
m_IncantCnt = new CECCounter();
|
|
m_IncantCnt.SetPeriod(1000);
|
|
m_IncantCnt.Reset(true);
|
|
m_bEnterGame = false;
|
|
}
|
|
|
|
public CECSkill GetNormalSkill(int id, bool bSenior = false /* false */)
|
|
{
|
|
CECSkill pSkill = null;
|
|
if (ElementSkill.GetType((uint)id) == (byte)CECSkill.SkillType.TYPE_PASSIVE ||
|
|
ElementSkill.GetType((uint)id) == (byte)CECSkill.SkillType.TYPE_PRODUCE ||
|
|
ElementSkill.GetType((uint)id) == (byte)CECSkill.SkillType.TYPE_LIVE)
|
|
pSkill = GetPassiveSkillByID(id, bSenior);
|
|
else
|
|
pSkill = GetPositiveSkillByID(id, bSenior);
|
|
|
|
if (pSkill == null) // && m_pGoblin)
|
|
{
|
|
// This is a goblin skill
|
|
for (int i = 0; i < m_aGoblinSkills.Count; i++)
|
|
{
|
|
if (m_aGoblinSkills[i].GetSkillID() == id)
|
|
return m_aGoblinSkills[i];
|
|
}
|
|
}
|
|
|
|
if (pSkill == null) // may be target item skill
|
|
{
|
|
if (m_pTargetItemSkill != null && m_pTargetItemSkill.GetSkillID() == id)
|
|
pSkill = m_pTargetItemSkill;
|
|
}
|
|
|
|
return pSkill;
|
|
}
|
|
|
|
public async void 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));
|
|
LoadResources();
|
|
await SetPlayerModel(UnityGameSession.Instance.GetRoleInfo().occupation,
|
|
UnityGameSession.Instance.GetRoleInfo().gender);
|
|
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);
|
|
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);
|
|
}
|
|
else
|
|
{
|
|
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);
|
|
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);
|
|
}
|
|
|
|
private bool NormalAttackObject(int idTarget, bool bForceAttack, bool bMoreClose = false)
|
|
{
|
|
if (idTarget == 0 || idTarget == m_PlayerInfo.cid)
|
|
{
|
|
// We should have check target isn't dead
|
|
return false;
|
|
}
|
|
|
|
//if (!EC_Game.GetGameRun().GetWorld().GetObject(idTarget, 1))
|
|
// return false;
|
|
bool bStartNewWork = false;
|
|
|
|
bool bUseAutoPF = false;
|
|
//CECPlayerWrapper* pWrapper = CECAutoPolicy::GetInstance().GetPlayerWrapper();
|
|
//if (CECAutoPolicy::GetInstance().IsAutoPolicyEnabled() && pWrapper.GetAttackError() >= 2)
|
|
//bUseAutoPF = true;
|
|
|
|
CECHPWorkTrace pWorkTrace = null;
|
|
CECHPWork pWork = null;
|
|
if ((pWork = m_pWorkMan.GetWork(CECHPWork.Host_work_ID.WORK_TRACEOBJECT)) != null)
|
|
{
|
|
pWorkTrace = pWork as CECHPWorkTrace;
|
|
}
|
|
else if ((pWork = m_pWorkMan.GetWork(CECHPWork.Host_work_ID.WORK_HACKOBJECT)) != null)
|
|
{
|
|
if ((pWork as CECHPWorkMelee).GetTarget() == idTarget)
|
|
return false; // Host is attacking the target
|
|
|
|
pWorkTrace = (CECHPWorkTrace)m_pWorkMan.CreateWork(CECHPWork.Host_work_ID.WORK_TRACEOBJECT);
|
|
bStartNewWork = true;
|
|
}
|
|
else if (m_pWorkMan.CanStartWork(CECHPWork.Host_work_ID.WORK_TRACEOBJECT))
|
|
{
|
|
pWorkTrace = (CECHPWorkTrace)m_pWorkMan.CreateWork(CECHPWork.Host_work_ID.WORK_TRACEOBJECT);
|
|
bStartNewWork = true;
|
|
}
|
|
|
|
if (pWorkTrace != null)
|
|
{
|
|
pWorkTrace.SetTraceTarget(
|
|
pWorkTrace.CreatTraceTarget(idTarget, CECHPWorkTrace.Trace_reason.TRACE_ATTACK, bForceAttack),
|
|
bUseAutoPF);
|
|
pWorkTrace.SetMoveCloseFlag(bMoreClose);
|
|
|
|
if (bStartNewWork)
|
|
m_pWorkMan.StartWork_p1(pWorkTrace);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public int AttackableJudge(int idTarget, bool bForceAttack)
|
|
{
|
|
if (CannotAttack())
|
|
return 0;
|
|
|
|
//if (CDlgAutoHelp::IsAutoHelp())
|
|
// return 0;
|
|
|
|
if (idTarget == 0 || idTarget == m_PlayerInfo.cid)
|
|
return -1;
|
|
|
|
CECObject pObject = EC_ManMessageMono.Instance.GetObject(idTarget, 1);
|
|
if (!pObject)
|
|
return -1;
|
|
|
|
// If target is pet, it's attacked possibility depends on it's monster
|
|
if (GPDataTypeHelper.ISNPCID(idTarget))
|
|
{
|
|
CECNPC pNPC = (CECNPC)pObject;
|
|
int idMaster = pNPC.GetMasterID();
|
|
if (idMaster != 0)
|
|
{
|
|
// master¿ÉÄÜÊÇhostplayer
|
|
if (idMaster == m_PlayerInfo.cid)
|
|
return 0;
|
|
|
|
//// Follow pet cannot be attacked
|
|
//if (pNPC.IsPetNPC() && ((CECPet)pNPC).IsFollowPet())
|
|
// return 0;
|
|
|
|
idTarget = idMaster;
|
|
pObject = EC_ManMessageMono.Instance.GetObject(idTarget, 1);
|
|
if (!pObject)
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
int iRet = 0;
|
|
|
|
if (GPDataTypeHelper.ISNPCID(idTarget))
|
|
{
|
|
CECNPC pNPC = (CECNPC)pObject;
|
|
|
|
// If this npc is host's pet, cannot be attacked
|
|
if (pNPC.GetMasterID() == m_PlayerInfo.cid)
|
|
return 0;
|
|
|
|
// If it's a pet and can not be attacked, pet can be attacked only if it's a fighting pet
|
|
//if (pNPC.IsPetNPC() && !((CECPet)pNPC).CanBeAttacked())
|
|
// return 0;
|
|
|
|
if (IsInBattle()) // Host is in battle
|
|
{
|
|
if (InSameBattleCamp(pNPC))
|
|
iRet = 0;
|
|
else
|
|
{
|
|
if (pNPC.IsMonsterNPC())
|
|
iRet = 1;
|
|
else if (pNPC.IsServerNPC() &&
|
|
(IsInFortress() ||
|
|
pNPC.GetRoleInBattle() == 8)) // ¶Ô·þÎñÐÍNPCµÄ¹¥»÷£¬°ïÅÉ»ùµØ»ò³Çսʱ¿ÉÓÃ
|
|
iRet = 1;
|
|
else
|
|
iRet = 0;
|
|
}
|
|
}
|
|
else if (pNPC.IsServerNPC())
|
|
{
|
|
// In sanctuary we cannot attack NPCs
|
|
if (!IsPVPOpen() || m_bInSanctuary || !bForceAttack)
|
|
iRet = 0;
|
|
else
|
|
iRet = 1;
|
|
}
|
|
else // Is monster
|
|
{
|
|
iRet = 1;
|
|
}
|
|
|
|
if (iRet == 1 && pNPC.GetOwnerFaction() > 0)
|
|
{
|
|
// Õë¶Ô°ïÅÉ PVP Õ½ÕùÖнûÖ¹²¿·Ö¹¥»÷
|
|
if (GetFactionID() == pNPC.GetOwnerFaction() || // ²»¹¥»÷ͬ°ï¹Ö
|
|
pNPC.IsFactionPVPMineCar() && !CanAttackFactionPVPMineCar() || // ÎÞ·¨ÔÙ¹¥»÷Ëû°ï¿ó³µÇé¿ö
|
|
pNPC.IsFactionPVPMineBase() && !CanAttackFactionPVPMineBase())
|
|
{
|
|
// ÎÞ·¨ÔÙ¹¥»÷Ëû°ï´æ¿óµãÇé¿ö
|
|
iRet = 0;
|
|
}
|
|
}
|
|
}
|
|
// TO DO: fix later
|
|
//else if (GPDataTypeHelper.ISPLAYERID(idTarget))
|
|
//{
|
|
// // Check duel at first
|
|
// if (m_pvp.iDuelState == Duel_state.DUEL_ST_INDUEL && m_pvp.idDuelOpp == idTarget)
|
|
// return 1;
|
|
// else if (m_pvp.iDuelState == Duel_state.DUEL_ST_STOPPING && m_pvp.idDuelOpp == idTarget)
|
|
// return 0;
|
|
|
|
// // In sanctuary we cannot attack other players
|
|
// if (m_bInSanctuary)
|
|
// return 0;
|
|
|
|
// //ASSERT(pObject.GetClassID() == CECObject::OCID_ELSEPLAYER);
|
|
// EC_ElsePlayer pPlayer = (EC_ElsePlayer)pObject;
|
|
// ROLEBASICPROP bp = pPlayer.GetBasicProps();
|
|
// EC_GAME_SETTING gs = g_pGame.GetConfigs().GetGameSettings();
|
|
|
|
// if (m_pvp.bFreePVP)
|
|
// {
|
|
// if (IsTeamMember(idTarget))
|
|
// return 0;
|
|
|
|
// // In free pvp mode, for example, host is in arena.
|
|
// if (bForceAttack)
|
|
// iRet = 1;
|
|
// else if (gs.bAtk_NoMafia && IsFactionMember(pPlayer.GetFactionID()))
|
|
// iRet = 0;
|
|
// else if (gs.bAtk_NoWhite && !pPlayer.IsInvader() && !pPlayer.IsPariah())
|
|
// iRet = 0;
|
|
// else if (gs.bAtk_NoAlliance && g_pGame.GetFactionMan().IsFactionAlliance(pPlayer.GetFactionID()))
|
|
// iRet = 0;
|
|
// else if (gs.bAtk_NoForce && GetForce() > 0 && GetForce() == pPlayer.GetForce())
|
|
// iRet = 0;
|
|
// else
|
|
// iRet = 1;
|
|
// }
|
|
// else if (m_iBattleCamp != GP_BATTLE_CAMP_NONE)
|
|
// {
|
|
// // Host is in battle
|
|
// int iCamp = pPlayer.GetBattleCamp();
|
|
// if (iCamp != GP_BATTLE_CAMP_NONE && iCamp != m_iBattleCamp)
|
|
// iRet = 1;
|
|
// else
|
|
// iRet = 0;
|
|
// }
|
|
// else // Normal mode
|
|
// {
|
|
// if (IsTeamMember(idTarget))
|
|
// return 0;
|
|
|
|
// if (!IsPVPOpen() || !pPlayer.IsPVPOpen() || m_BasicProps.iLevel < EC_MAXNOPKLEVEL || bp.iLevel < EC_MAXNOPKLEVEL)
|
|
// iRet = 0;
|
|
// else if (bForceAttack)
|
|
// iRet = 1;
|
|
// else if (!gs.bAtk_Player)
|
|
// iRet = 0;
|
|
// else if (gs.bAtk_NoMafia && IsFactionMember(pPlayer.GetFactionID()))
|
|
// iRet = 0;
|
|
// else if (gs.bAtk_NoWhite && !pPlayer.IsInvader() && !pPlayer.IsPariah())
|
|
// iRet = 0;
|
|
// else if (gs.bAtk_NoAlliance && g_pGame.GetFactionMan().IsFactionAlliance(pPlayer.GetFactionID()))
|
|
// iRet = 0;
|
|
// else if (gs.bAtk_NoForce && GetForce() > 0 && GetForce() == pPlayer.GetForce())
|
|
// iRet = 0;
|
|
// else
|
|
// iRet = 1;
|
|
// }
|
|
//}
|
|
else
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
return iRet;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
// Load configs data (shortcut, etc.) from specified buffer
|
|
// Converted from: bool CECHostPlayer::LoadConfigData(const void* pDataBuf)
|
|
public bool LoadConfigData(byte[] dataBuf)
|
|
{
|
|
if (dataBuf == null || dataBuf.Length < sizeof(uint))
|
|
return false;
|
|
|
|
int offset = 0;
|
|
|
|
// Version number
|
|
uint dwVer = GPDataTypeHelper.FromBytes<uint>(dataBuf, offset);
|
|
offset += sizeof(uint);
|
|
if (dwVer > HostCfgConstants.HOSTCFG_VERSION)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Load shortcut configs...
|
|
int iHostSCSets1 = (dwVer <= 4) ? 3 : HostCfgConstants.NUM_HOSTSCSETS1;
|
|
|
|
for (int i = 0; i < iHostSCSets1; i++)
|
|
{
|
|
if (offset >= dataBuf.Length)
|
|
return false;
|
|
|
|
if (m_aSCSets1[i] == null)
|
|
{
|
|
m_aSCSets1[i] = new CECShortcutSet();
|
|
m_aSCSets1[i].Init(HostCfgConstants.SIZE_HOSTSCSET1);
|
|
}
|
|
|
|
if (!m_aSCSets1[i].LoadConfigData(dataBuf, dwVer, ref offset))
|
|
return false;
|
|
}
|
|
|
|
for (int i = 0; i < HostCfgConstants.NUM_HOSTSCSETS2; i++)
|
|
{
|
|
if (offset >= dataBuf.Length)
|
|
break; // No more data; tolerate truncated optional parts
|
|
|
|
if (m_aSCSets2[i] == null)
|
|
{
|
|
m_aSCSets2[i] = new CECShortcutSet();
|
|
m_aSCSets2[i].Init(HostCfgConstants.SIZE_HOSTSCSET2);
|
|
}
|
|
|
|
if (!m_aSCSets2[i].LoadConfigData(dataBuf, dwVer, ref offset))
|
|
return false;
|
|
}
|
|
|
|
// Notes:
|
|
// - Auto fashion sets, system module shortcut sets, booth packs, and AutoYinpiao
|
|
// sections from native are not loaded here in this Unity port yet.
|
|
// The native format appends these after the two shortcut-set groups.
|
|
// We intentionally ignore them safely for now.
|
|
|
|
return true;
|
|
}
|
|
|
|
public int GetCharacterID()
|
|
{
|
|
return m_PlayerInfo.cid;
|
|
}
|
|
|
|
public bool CannotAttack()
|
|
{
|
|
return (m_dwLIES & (uint)Logic_Influence_Extned_states.LIES_DISABLEFIGHT) != 0;
|
|
}
|
|
|
|
public bool CanTouchTarget(A3DVECTOR3 vHostPos, A3DVECTOR3 vTargetPos, float fTargetRad, int iReason,
|
|
float fMaxCut = 1.0f)
|
|
{
|
|
float fDist = A3d_Magnitude(vTargetPos - vHostPos);
|
|
switch (iReason)
|
|
{
|
|
case 1: // melee
|
|
{
|
|
float fRange;
|
|
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)
|
|
return true;
|
|
|
|
break;
|
|
}
|
|
case 2: // cast magic
|
|
{
|
|
if (m_pPrepSkill != null)
|
|
{
|
|
|
|
//TODO : Check this function GetCastRange
|
|
float fRange = m_pPrepSkill.GetCastRange(m_ExtProps.ak.AttackRange, GetPrayDistancePlus());
|
|
|
|
if (fRange > 0.0f)
|
|
{
|
|
if (fDist - fTargetRad <= fRange)
|
|
return true;
|
|
}
|
|
else
|
|
return true;
|
|
}
|
|
|
|
break;
|
|
}
|
|
case 3: // talk
|
|
{
|
|
if (fDist - fTargetRad <= 5.0f)
|
|
return true;
|
|
|
|
break;
|
|
}
|
|
default: // no special reason
|
|
{
|
|
if (fDist < (fTargetRad + m_fTouchRad) * 3.0f)
|
|
return true;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
public bool GetSkillCoolTime(int idSkill, out COOLTIME ct)
|
|
{
|
|
// 获取技能的非公共冷却时间
|
|
bool bFound = false;
|
|
ct = new COOLTIME();
|
|
|
|
if (m_skillCoolTime.TryGetValue(idSkill, out ct))
|
|
{
|
|
bFound = true;
|
|
}
|
|
|
|
return bFound;
|
|
}
|
|
|
|
// Update equipment skill cool down
|
|
// 更新装备技能冷却
|
|
public void UpdateEquipSkillCoolDown(int cooldown_index = -1)
|
|
{
|
|
if (cooldown_index < 0)
|
|
{
|
|
|
|
for (int i = 0; i < GetEquipSkillNum(); ++i)
|
|
{
|
|
CECSkill pSkill = GetEquipSkillByIndex(i);
|
|
|
|
// 检查技能冷却
|
|
// Check skill cooldown
|
|
COOLTIME temp;
|
|
if (GetSkillCoolTime(pSkill.GetSkillID(), out temp))
|
|
{
|
|
pSkill.StartCooling(temp.iMaxTime, temp.iCurTime);
|
|
continue;
|
|
}
|
|
|
|
// 检查公共冷却
|
|
// Check common cooldown
|
|
int ccd = pSkill.GetCommonCoolDown();
|
|
if (ccd == 0) continue;
|
|
for (int j = 0; j < 5; ++j)
|
|
{
|
|
if ((ccd & (1 << j)) != 0)
|
|
{
|
|
COOLTIME ct = m_aCoolTimes[(int)CoolTimeIndex.GP_CT_SKILLCOMMONCOOLDOWN0 + j];
|
|
pSkill.StartCooling(ct.iMaxTime, ct.iCurTime);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (cooldown_index > (int)CoolTimeIndex.GP_CT_SKILL_START)
|
|
{
|
|
int idSkill = cooldown_index - (int)CoolTimeIndex.GP_CT_SKILL_START;
|
|
CECSkill pSkill = GetEquipSkillByID(idSkill);
|
|
COOLTIME temp;
|
|
if (pSkill != null && GetSkillCoolTime(idSkill, out temp))
|
|
pSkill.StartCooling(temp.iMaxTime, temp.iCurTime);
|
|
}
|
|
else if (cooldown_index >= (int)CoolTimeIndex.GP_CT_SKILLCOMMONCOOLDOWN0 && cooldown_index <= (int)CoolTimeIndex.GP_CT_SKILLCOMMONCOOLDOWN4)
|
|
{
|
|
int index = cooldown_index - (int)CoolTimeIndex.GP_CT_SKILLCOMMONCOOLDOWN0;
|
|
COOLTIME ct = m_aCoolTimes[(int)CoolTimeIndex.GP_CT_SKILLCOMMONCOOLDOWN0 + index];
|
|
uint mask = (uint)(1 << index);
|
|
for (int i = 0; i < GetEquipSkillNum(); ++i)
|
|
{
|
|
CECSkill pSkill = GetEquipSkillByIndex(i);
|
|
int ccd = pSkill.GetCommonCoolDown();
|
|
if ((ccd & mask) != 0)
|
|
pSkill.StartCooling(ct.iMaxTime, ct.iCurTime);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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)
|
|
{
|
|
A3DVECTOR3 vector = new A3DVECTOR3(playerTransform.position.x, playerTransform.position.y,
|
|
playerTransform.position.z);
|
|
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 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;
|
|
|
|
// ·þÎñÆ÷¶Ë½«Ë®ÃæÒÔÏÂ2Ã×ÒÔÉÏ´¦ÀíΪ run_speed£¨ÓÐÎÊÌ⣩
|
|
// µ«Î´Ê¹ÓüÓËÙ¼¼ÄÜʱ swim_speed СÓÚ run_speed£¬
|
|
// ¿ÉÒÔÔÚË®ÃæÒÔÏÂ2Ã×ÒÔÉÏ»ñÈ¡³¬¹ý swim_speed µÄËÙ¶È£¬Òò´Ë£¬´Ë´¦È¡Á½Õß½ÏСֵΪºÏÀí×ö·¨
|
|
fSpeedSev = Math.Min(m_ExtProps.mv.run_speed, fSpeedSev);
|
|
break;
|
|
}
|
|
|
|
return fSpeedSev;
|
|
}
|
|
|
|
public bool ApplySkillShortcut(int idSkill, bool bCombo = false /* false */,
|
|
int idSelTarget = 0 /* 0 */, int iForceAtk = -1 /* -1 */)
|
|
{
|
|
//StackChecker::ACTrace(4);
|
|
|
|
if (m_pActionSwitcher != null)
|
|
m_pActionSwitcher.PostMessge((int)EMsgActionSwitcher.MSG_CASTSKILL);
|
|
|
|
// Return-town skill is very special, handle it separately
|
|
//if (idSkill == ID_RETURNTOWN_SKILL)
|
|
// return ReturnToTargetTown(0, bCombo);
|
|
|
|
//if (idSkill == ID_SUMMONPLAYER_SKILL)
|
|
// return SummonPlayer(idSelTarget, bCombo);
|
|
|
|
if (!CanDo(ActionCanDo.CANDO_SPELLMAGIC))
|
|
return false;
|
|
|
|
if (InSlidingState())
|
|
return false;
|
|
|
|
if (!bCombo)
|
|
//ClearComboSkill();
|
|
|
|
if (idSelTarget == 0)
|
|
idSelTarget = m_idSelTarget;
|
|
|
|
CECSkill pSkill = GetPositiveSkillByID(idSkill);
|
|
if (pSkill == null) pSkill = GetEquipSkillByID(idSkill);
|
|
if (pSkill == null) pSkill = CECComboSkillState.Instance.GetInherentSkillByID((uint)idSkill);
|
|
if (pSkill == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//// If we press a chargeable skill again when it's being charged,
|
|
//// we cast it out at once
|
|
if (IsSpellingMagic() && m_pCurSkill != null && m_pCurSkill.IsCharging() &&
|
|
m_pCurSkill.GetSkillID() == pSkill.GetSkillID())
|
|
{
|
|
m_pCurSkill.EndCharging();
|
|
UnityGameSession.c2s_SendCmdContinueAction();
|
|
return true;
|
|
}
|
|
|
|
//int iCon = CheckSkillCastCondition(pSkill);
|
|
//if (iCon)
|
|
//{
|
|
// ProcessSkillCondition(iCon);
|
|
// return false;
|
|
//}
|
|
|
|
//// Get force attack flag
|
|
bool bForceAttack = false;
|
|
if (iForceAtk < 0)
|
|
bForceAttack = glb_GetForceAttackFlag(0);
|
|
else
|
|
bForceAttack = iForceAtk > 0 ? true : false;
|
|
|
|
//// Check negative effect skill
|
|
//if (pSkill.GetType() == (int)skill_type.TYPE_ATTACK || pSkill.GetType() == (int)skill_type.TYPE_CURSE)
|
|
//{
|
|
// if (idSelTarget == m_PlayerInfo.cid)
|
|
// {
|
|
// // Host cannot spell negative effect magic to himself.
|
|
// EC_Game.GetGameRun().AddFixedChannelMsg(FIXMSG_TARGETWRONG, GP_CHAT_FIGHT);
|
|
// return false;
|
|
// }
|
|
// else if (idSelTarget != 0)
|
|
// {
|
|
// if (AttackableJudge(idSelTarget, bForceAttack) != 1)
|
|
// return false;
|
|
// }
|
|
//}
|
|
|
|
//// Check whether target type match
|
|
int idCastTarget = idSelTarget;
|
|
int iTargetType = pSkill.GetTargetType();
|
|
|
|
if (pSkill.GetType() == (int)skill_type.TYPE_BLESS ||
|
|
pSkill.GetType() == (int)skill_type.TYPE_NEUTRALBLESS)
|
|
{
|
|
if (iTargetType == 0 || !GPDataTypeHelper.ISPLAYERID(idSelTarget))
|
|
idCastTarget = m_PlayerInfo.cid;
|
|
|
|
// In some case, we shouldn't add bless effect to other players
|
|
if (GPDataTypeHelper.ISPLAYERID(idCastTarget) && idCastTarget != m_PlayerInfo.cid)
|
|
{
|
|
// If host has set bless skill filter only to himself, bless skill couldn't add to other players
|
|
byte byBLSMask = EC_Utility.glb_BuildBLSMask();
|
|
|
|
if (pSkill.GetRangeType() == (int)range_type.RANGE_POINT)
|
|
{
|
|
if (!IsTeamMember(idCastTarget))
|
|
{
|
|
if ((byBLSMask & (byte)PVPMask.GP_BLSMASK_SELF) != 0)
|
|
idCastTarget = m_PlayerInfo.cid;
|
|
else
|
|
{
|
|
EC_ElsePlayer pPlayer =
|
|
(EC_ElsePlayer)EC_ManMessageMono.Instance.GetECManPlayer.GetPlayer(idCastTarget);
|
|
if (pPlayer == null)
|
|
{
|
|
// Ä¿±êÏûʧ
|
|
return false;
|
|
}
|
|
|
|
if (pPlayer.IsInvader() || pPlayer.IsPariah())
|
|
{
|
|
if ((byBLSMask & (byte)PVPMask.GP_BLSMASK_NORED) != 0)
|
|
idCastTarget = m_PlayerInfo.cid;
|
|
}
|
|
|
|
if (!IsFactionMember(pPlayer.GetFactionID()))
|
|
{
|
|
if ((byBLSMask & (byte)PVPMask.GP_BLSMASK_NOMAFIA) != 0)
|
|
idCastTarget = m_PlayerInfo.cid;
|
|
}
|
|
|
|
if (!IsFactionAllianceMember(pPlayer.GetFactionID()))
|
|
{
|
|
if ((byBLSMask & (byte)PVPMask.GP_BLSMASK_NOALLIANCE) != 0)
|
|
idCastTarget = m_PlayerInfo.cid;
|
|
}
|
|
|
|
if (GetForce() != pPlayer.GetForce())
|
|
{
|
|
if ((byBLSMask & (byte)PVPMask.GP_BLSMASK_NOFORCE) != 0)
|
|
idCastTarget = m_PlayerInfo.cid;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If host is in duel, bless skill couldn't add to opponent
|
|
if (IsInDuel() && idSelTarget == m_pvp.idDuelOpp)
|
|
idCastTarget = m_PlayerInfo.cid;
|
|
|
|
// If host is in battle, bless skill couldn't add to enemies
|
|
if (IsInBattle())
|
|
{
|
|
EC_ElsePlayer pPlayer = EC_ManMessageMono.Instance.GetECManPlayer.GetElsePlayer(idCastTarget);
|
|
if (!InSameBattleCamp(pPlayer))
|
|
idCastTarget = m_PlayerInfo.cid;
|
|
}
|
|
}
|
|
}
|
|
/* else if (pSkill.GetType() == CECSkill::TYPE_BLESSPET)
|
|
{
|
|
CECPet* pPet = g_pGame.GetGameRun().GetWorld().GetNPCMan().GetPetByID(idSelTarget);
|
|
if (!pPet || pPet.GetMasterID() == GetCharacterID())
|
|
{
|
|
// Spell skill on host's pet
|
|
CECPetData* pPetData = m_pPetCorral.GetActivePet();
|
|
if (!pPetData ||
|
|
pPetData.GetClass() != GP_PET_CLASS_COMBAT &&
|
|
pPetData.GetClass() != GP_PET_CLASS_SUMMON &&
|
|
pPetData.GetClass() != GP_PET_CLASS_EVOLUTION)
|
|
return false;
|
|
|
|
idCastTarget = m_pPetCorral.GetActivePetNPCID();
|
|
}
|
|
// Only fighting pet can be blessed.
|
|
if (pPet && !pPet.CanBeAttacked())
|
|
return false;
|
|
}*/
|
|
else
|
|
{
|
|
if (iTargetType != 0 && idCastTarget == 0)
|
|
return false;
|
|
}
|
|
|
|
// iTargetType == 4 means target must be pet. The problem is that pet will
|
|
// disappear from world after it died, so GetWorld().GetObject() will return
|
|
// NULL when host spells revive-pet skill on his dead pet. So, the target
|
|
// type of revive-pet skill should be 0
|
|
if (iTargetType != 0)
|
|
{
|
|
// Target shoundn't be a corpse ?
|
|
int iAliveFlag = 0;
|
|
if (iTargetType == 1)
|
|
iAliveFlag = 1;
|
|
else if (iTargetType == 2)
|
|
iAliveFlag = 2;
|
|
|
|
/*CECObject pObject = EC_Game.GetGameRun().GetWorld().GetObject(idCastTarget, iAliveFlag);
|
|
if (!pObject)
|
|
return false;*/
|
|
}
|
|
|
|
if (!IsMeleeing() && !IsSpellingMagic() &&
|
|
(iTargetType == 0 || idCastTarget == m_PlayerInfo.cid))
|
|
{
|
|
// Cast this skill need't checking cast distance
|
|
if (!pSkill.ReadyToCast())
|
|
return false;
|
|
|
|
if (!pSkill.IsInstant() && pSkill.GetType() != (int)Skilltype.TYPE_FLASHMOVE)
|
|
{
|
|
/* if (!NaturallyStopMoving())
|
|
return false; */ // Couldn't stop naturally, so cancel casting skill
|
|
}
|
|
else if (pSkill.GetType() == (int)Skilltype.TYPE_FLASHMOVE)
|
|
{
|
|
if (!CanDo(ActionCanDo.CANDO_FLASHMOVE))
|
|
return false;
|
|
}
|
|
|
|
BMLogger.LogError("HoangDev: ApplySkillShortcut - PATH 1: Calling CastSkill (self-cast)");
|
|
|
|
m_pPrepSkill = pSkill;
|
|
CastSkill(m_PlayerInfo.cid, bForceAttack);
|
|
}
|
|
else if (IsSpellingMagic() && m_pCurSkill == pSkill)
|
|
{
|
|
// If we are casting the same skill and it's in cooling time
|
|
return false;
|
|
}
|
|
else // Have to trace selected object before cast skill
|
|
{
|
|
if (!pSkill.ReadyToCast())
|
|
return false;
|
|
|
|
if (CECCastSkillWhenMove.Instance.IsSkillSupported(pSkill.GetSkillID(), this) &&
|
|
m_pWorkMan.IsMovingToPosition() &&
|
|
m_pWorkMan.CanCastSkillImmediately(pSkill.GetSkillID()))
|
|
{
|
|
m_pPrepSkill = pSkill;
|
|
return CastSkill(idCastTarget, bForceAttack);
|
|
}
|
|
else
|
|
{
|
|
bool bTraceOK = false;
|
|
bool bUseAutoPF = false;
|
|
/* CECPlayerWrapper pWrapper = CECAutoPolicy::GetInstance().GetPlayerWrapper();
|
|
if (CECAutoPolicy::GetInstance().IsAutoPolicyEnabled() && pWrapper.GetAttackError() >= 2)
|
|
bUseAutoPF = true;*/
|
|
|
|
if (idCastTarget == 0)
|
|
{
|
|
idCastTarget =
|
|
GetCharacterID(); // ±ÜÃâË²ÒÆµÈ¼¼ÄÜʱ idCastTarget Ϊ0µ¼Ö CECWorkTrace::CreateTraceTarget ·µ»Ø¿Õ
|
|
}
|
|
|
|
CECHPWork pWork = m_pWorkMan.GetWork(Host_work_ID.WORK_TRACEOBJECT);
|
|
if (pWork != null)
|
|
{
|
|
CECHPWorkTrace pWorkTrace = (CECHPWorkTrace)(pWork);
|
|
if (pWorkTrace.GetTraceReason() == Trace_reason.TRACE_SPELL &&
|
|
pWorkTrace.GetTarget() == idCastTarget &&
|
|
pWorkTrace.GetPrepSkill() == pSkill)
|
|
return false; // We are just doing the same thing
|
|
|
|
pWorkTrace.SetTraceTarget(
|
|
pWorkTrace.CreatTraceTarget(idCastTarget, Trace_reason.TRACE_SPELL, bForceAttack),
|
|
bUseAutoPF);
|
|
pWorkTrace.SetPrepSkill(pSkill);
|
|
bTraceOK = true;
|
|
}
|
|
else if (m_pWorkMan.CanStartWork(Host_work_ID.WORK_TRACEOBJECT))
|
|
{
|
|
CECHPWorkTrace pWork2 = (CECHPWorkTrace)m_pWorkMan.CreateWork(Host_work_ID.WORK_TRACEOBJECT);
|
|
pWork2.SetTraceTarget(
|
|
pWork2.CreatTraceTarget(idCastTarget, Trace_reason.TRACE_SPELL, bForceAttack), bUseAutoPF);
|
|
pWork2.SetPrepSkill(pSkill);
|
|
m_pWorkMan.StartWork_p1(pWork2);
|
|
bTraceOK = true;
|
|
}
|
|
|
|
if (!bTraceOK) return false;
|
|
// }
|
|
//}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public CECSkill GetPrepSkill()
|
|
{
|
|
return m_pPrepSkill;
|
|
}
|
|
|
|
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 CastSkill(int idTarget, bool bForceAttack, CECObject pTarget = null)
|
|
{
|
|
// Check if prep skill is valid, ready to cast, and not currently spelling magic
|
|
if (m_pPrepSkill == null || !m_pPrepSkill.ReadyToCast() || IsSpellingMagic())
|
|
{
|
|
// Check if skill can change to melee attack
|
|
if (m_pPrepSkill != null && m_pPrepSkill.ChangeToMelee())
|
|
{
|
|
bool bFlag = m_pPrepSkill.ReadyToCast();
|
|
|
|
// Finish any tracing work
|
|
if (m_pWorkMan.IsTracing())
|
|
m_pWorkMan.FinishRunningWork(CECHPWork.Host_work_ID.WORK_TRACEOBJECT);
|
|
|
|
// Handle combo skill or normal attack
|
|
if (m_pComboSkill != null)
|
|
m_pComboSkill.Continue(false);
|
|
else
|
|
{
|
|
// Perform normal attack instead
|
|
NormalAttackObject(idTarget, true);
|
|
}
|
|
}
|
|
|
|
m_pPrepSkill = null;
|
|
return false;
|
|
}
|
|
|
|
if (m_pPrepSkill.GetType() == (int)CECSkill.SkillType.TYPE_ATTACK ||
|
|
m_pPrepSkill.GetType() == (int)CECSkill.SkillType.TYPE_CURSE)
|
|
{
|
|
if (idTarget != 0 && AttackableJudge(idTarget, bForceAttack) != 1)
|
|
{
|
|
m_pPrepSkill = null;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//TODO: Check cast condition - method not yet implemented
|
|
int iRet = CheckSkillCastCondition(m_pPrepSkill);
|
|
if (iRet != 0)
|
|
{
|
|
switch (iRet)
|
|
{
|
|
case 2: // Need MP
|
|
// g_pGame.GetGameRun().AddFixedMessage(FIXMSG_NEEDMP);
|
|
break;
|
|
case 8: // Need AP
|
|
// g_pGame.GetGameRun().AddFixedMessage(FIXMSG_NEEDAP);
|
|
break;
|
|
case 10: // Pack full
|
|
// g_pGame.GetGameRun().AddFixedMessage(FIXMSG_PACKFULL1);
|
|
break;
|
|
case 20: // Need item
|
|
// g_pGame.GetGameRun().AddFixedMessage(FIXMSG_NEEDITEM);
|
|
break;
|
|
case 12: // HP unsatisfied
|
|
// g_pGame.GetGameRun().AddFixedMessage(FIXMSG_HP_UNSATISFIED);
|
|
break;
|
|
}
|
|
|
|
m_pPrepSkill = null;
|
|
return false;
|
|
}
|
|
|
|
byte byPVPMask = glb_BuildPVPMask(bForceAttack);
|
|
|
|
Debug.Log($"HoangDev: Cast Skill ID={m_pPrepSkill.GetSkillID()}");
|
|
|
|
// Handle instant skills
|
|
if (m_pPrepSkill.IsInstant())
|
|
{
|
|
int countTarget = 1;
|
|
targetsCastSkill = new int[countTarget];
|
|
targetsCastSkill[0] = idTarget;
|
|
UnityGameSession.c2s_CmdCastInstantSkill(m_pPrepSkill.GetSkillID(), byPVPMask, countTarget, targetsCastSkill);
|
|
m_pPrepSkill = null;
|
|
}
|
|
// Handle flash move skills (瞬移技能)
|
|
else if (m_pPrepSkill.GetType() == (int)CECSkill.SkillType.TYPE_FLASHMOVE)
|
|
{
|
|
// Self or self-sphere range types
|
|
if (m_pPrepSkill.GetRangeType() == (int)CECSkill.RangeType.RANGE_SLEF ||
|
|
m_pPrepSkill.GetRangeType() == (int)CECSkill.RangeType.RANGE_SELFSPHERE)
|
|
{
|
|
A3DVECTOR3 vDir = GetDir();
|
|
float fDist = m_pPrepSkill.GetCastRange(m_ExtProps.ak.AttackRange, GetPrayDistancePlus());
|
|
|
|
// 左侧之翼,左跳 (Left wing skill - jump left)
|
|
if (m_pPrepSkill.GetSkillID() == 1844)
|
|
{
|
|
vDir = A3d_RotatePosAroundY(-vDir, Mathf.PI / 2);
|
|
}
|
|
// 右侧之翼,右跳 (Right wing skill - jump right)
|
|
else if (m_pPrepSkill.GetSkillID() == 1845)
|
|
{
|
|
vDir = A3d_RotatePosAroundY(vDir, Mathf.PI / 2);
|
|
}
|
|
// 范围小于0则后跳 (If range < 0, jump backward)
|
|
else if (fDist < 0.0f)
|
|
{
|
|
vDir = -vDir;
|
|
}
|
|
|
|
fDist = Mathf.Abs(fDist);
|
|
A3DVECTOR3 vDest = m_MoveCtrl.FlashMove(vDir, 100.0f, fDist);
|
|
BMLogger.LogError(
|
|
$"HoangDev: skill id={m_pPrepSkill.GetSkillID()} , vDest={vDest} ,position = {transform.position}, byPVPMask={byPVPMask} ");
|
|
UnityGameSession.c2s_CmdCastPosSkill(m_pPrepSkill.GetSkillID(), EC_Utility.ToVector3(vDest),
|
|
byPVPMask, 0, 0);
|
|
m_pPrepSkill = null;
|
|
}
|
|
else
|
|
{
|
|
// 刺客如影随行类技能 (Assassin shadow-following skills)
|
|
bool bSuccess = false;
|
|
|
|
while (true)
|
|
{
|
|
// Break if no target or self-target
|
|
if (idTarget == 0 || idTarget == GetCharacterID())
|
|
break;
|
|
|
|
// Get target object
|
|
CECObject pObject = CECWorld.Instance.GetObject(idTarget, 0);
|
|
if (pObject == null) break;
|
|
|
|
A3DVECTOR3 vHostPos = EC_Utility.ToA3DVECTOR3(transform.position); // GetPos()
|
|
A3DVECTOR3 vTargetPos = EC_Utility.ToA3DVECTOR3(pObject.transform.position); // pObject.GetPos()
|
|
|
|
// 判断技能释放距离限制是否满足 (Check if skill cast distance is satisfied)
|
|
float fTouchRadius = 0.0f;
|
|
|
|
if (GPDataTypeHelper.ISNPCID(idTarget))
|
|
{
|
|
CECNPC pNPC = pObject as CECNPC;
|
|
if (pNPC != null)
|
|
fTouchRadius = pNPC.GetTouchRadius();
|
|
else
|
|
break;
|
|
}
|
|
else if (GPDataTypeHelper.ISPLAYERID(idTarget))
|
|
{
|
|
EC_ElsePlayer pElsePlayer = pObject as EC_ElsePlayer;
|
|
if (pElsePlayer != null)
|
|
fTouchRadius = pElsePlayer.GetTouchRadius();
|
|
else
|
|
break;
|
|
}
|
|
else
|
|
break;
|
|
|
|
if (!CanTouchTarget(vTargetPos, fTouchRadius, 2))
|
|
{
|
|
// Target is far - show message
|
|
// g_pGame.GetGameRun().AddFixedMessage(FIXMSG_TARGETISFAR);
|
|
Debug.Log("Target is too far");
|
|
break;
|
|
}
|
|
|
|
A3DVECTOR3 vMoveDir = vTargetPos - vHostPos;
|
|
float fDist = EC_Utility.ToVector3(vMoveDir).magnitude;
|
|
|
|
// 距离目标太近,不处理 (Too close to target, don't process)
|
|
float fNearDist = 0.0f;
|
|
// TODO: Implement IsTooNear
|
|
if (IsTooNear(vTargetPos, ref fNearDist))
|
|
{
|
|
Debug.Log("Target is too near");
|
|
break;
|
|
}
|
|
|
|
// 计算要移往的目标位置(默认值) (Calculate target position to move to)
|
|
vMoveDir.Normalize();
|
|
A3DVECTOR3 vMovePos = vHostPos + vMoveDir * (fDist - fNearDist);
|
|
|
|
// TODO: Implement ClampAboveGround
|
|
float fClampedHeight = ClampAboveGround(vMovePos);
|
|
if (Mathf.Abs(fClampedHeight - vMovePos.y) >= 5.0f)
|
|
{
|
|
Debug.Log("Would stuck or so");
|
|
break;
|
|
}
|
|
|
|
vMovePos.y = fClampedHeight;
|
|
bool bPosVerified = false;
|
|
|
|
// 目标为带凸包的 NPC 时,单独处理 (Special handling for NPCs with collision)
|
|
if (GPDataTypeHelper.ISNPCID(idTarget))
|
|
{
|
|
// TODO: Implement CalcCollideFreePos for NPC AABB
|
|
CECNPC pNPC = pObject as CECNPC;
|
|
A3DAABB aabbNPC = new A3DAABB();
|
|
if (pNPC.GetCHAABB(ref aabbNPC))
|
|
{
|
|
A3DVECTOR3 vTestPos;
|
|
if (CalcCollideFreePos(aabbNPC, out vTestPos))
|
|
{
|
|
vMovePos = vTestPos;
|
|
bPosVerified = true;
|
|
}
|
|
else
|
|
{
|
|
Debug.Log("Would stuck or so");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO: Implement collision checking
|
|
if (!bPosVerified && !IsPosCollideFree(vMovePos))
|
|
{
|
|
A3DVECTOR3 vTestPos2;
|
|
if (!CalcVerticalCollideFreePos(vMovePos, out vTestPos2))
|
|
{
|
|
Debug.Log("Would stuck or so");
|
|
break;
|
|
}
|
|
|
|
vMovePos = vTestPos2;
|
|
bPosVerified = true;
|
|
}
|
|
|
|
//TODO: Implement IsTooNear check for final position
|
|
float reffake = 0;
|
|
if (IsTooNear(vMovePos, ref reffake))
|
|
{
|
|
Debug.Log("Target is too near");
|
|
break;
|
|
}
|
|
|
|
// 发送协议 (Send protocol)
|
|
UnityGameSession.c2s_CmdCastPosSkill(m_pPrepSkill.GetSkillID(), EC_Utility.ToVector3(vMovePos),
|
|
byPVPMask, 1, idTarget);
|
|
bSuccess = true;
|
|
}
|
|
|
|
m_pPrepSkill = null;
|
|
return bSuccess;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Regular skill casting
|
|
byte byPVPMask2 = glb_BuildPVPMask(bForceAttack);
|
|
int targets = 1;
|
|
targetsCastSkill = new int[targets];
|
|
targetsCastSkill[0] = idTarget;
|
|
UnityGameSession.c2s_CmdCastSkill(m_pPrepSkill.GetSkillID(), byPVPMask2, targets, targetsCastSkill);
|
|
}
|
|
|
|
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 int GetPositiveSkillNum() { return m_aPtSkills.Count; }
|
|
public CECSkill GetPositiveSkillByIndex(int n) { return m_aPtSkills[n]; }
|
|
|
|
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)
|
|
{
|
|
//BMLogger.LogError("HoangDev: HostPlayer SelectTarget");
|
|
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();
|
|
}
|
|
else
|
|
{
|
|
//BMLogger.LogError("HoangDev: HostPlayer setlect npc");
|
|
UnityGameSession.c2s_CmdSelectTarget(idTarget);
|
|
}
|
|
}
|
|
|
|
return bRet;
|
|
}
|
|
|
|
CECSkill GetPassiveSkillByID(int id, bool bSenior /* false */)
|
|
{
|
|
CECSkill pSenior = null;
|
|
|
|
for (int i = 0; i < m_aPsSkills.Count; i++)
|
|
{
|
|
if (m_aPsSkills[i].GetSkillID() == id)
|
|
return m_aPsSkills[i];
|
|
else if (m_aPsSkills[i].GetJunior().Find((uint)id))
|
|
pSenior = m_aPsSkills[i];
|
|
}
|
|
|
|
if (bSenior && pSenior != null)
|
|
return pSenior;
|
|
|
|
return null;
|
|
}
|
|
|
|
public CECSkill GetPositiveSkillByID(int id, bool bSenior = false)
|
|
{
|
|
CECSkill pSenior = null;
|
|
|
|
for (int i = 0; i < m_aPtSkills.Count; i++)
|
|
{
|
|
if (m_aPtSkills[i].GetSkillID() == id)
|
|
return m_aPtSkills[i];
|
|
else if (m_aPtSkills[i].GetJunior().Find((uint)id))
|
|
pSenior = m_aPtSkills[i];
|
|
}
|
|
|
|
if (bSenior && pSenior != null)
|
|
return pSenior;
|
|
|
|
return null;
|
|
}
|
|
public int GetMaxLevelSofar() { return Math.Max(m_ReincarnationTome.max_level, m_BasicProps.iLevel); }
|
|
|
|
// C# conversion of CECHostPlayer::GetEquipSkillByID
|
|
// Assumes: GetEquipSkillNum() returns the count of equipment skills
|
|
// GetEquipSkillByIndex(int) returns a CECSkill at the given index
|
|
public CECSkill GetEquipSkillByID(int id)
|
|
{
|
|
CECSkill pRet = null;
|
|
|
|
for (int i = 0; i < GetEquipSkillNum(); i++)
|
|
{
|
|
CECSkill pSkill = GetEquipSkillByIndex(i);
|
|
if (pSkill != null && pSkill.GetSkillID() == id)
|
|
{
|
|
pRet = pSkill;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return pRet;
|
|
}
|
|
|
|
public int GetEquipSkillNum()
|
|
{
|
|
return m_aEquipSkills.Count;
|
|
}
|
|
|
|
public CECSkill GetEquipSkillByIndex(int n)
|
|
{
|
|
return m_aEquipSkills[n];
|
|
}
|
|
|
|
// Check skill cast condition
|
|
// Returns: 0 if success, error code otherwise
|
|
// Error codes: 1=invalid weapon, 2=need mp, 3=invalid state, 6=target wrong, 7=invalid state,
|
|
// 8=need ap, 9=not enough ammo, 10=pack full, 11=invalid env, 12=hp unsatisfied,
|
|
// 13=combo skill not active, 20=need item
|
|
public int CheckSkillCastCondition(CECSkill pSkill)
|
|
{
|
|
// Check if skill requires an item
|
|
if (pSkill.SkillCore != null)
|
|
{
|
|
int idItem = pSkill.SkillCore.GetItemCost();
|
|
if (idItem > 0 && GetPack().GetItemTotalNum(idItem) <= 0)
|
|
{
|
|
return 20; // Need item
|
|
}
|
|
}
|
|
|
|
// Check combo skill prerequisite (for night shadow continuous skills)
|
|
// Note: GetComboSkPreSkill and IsActiveComboSkill methods need to be implemented
|
|
// TODO: Implement GetComboSkPreSkill in ElementSkill/CECSkill
|
|
// TODO: Implement IsActiveComboSkill in CECComboSkillState
|
|
/*
|
|
if (pSkill.GetComboSkPreSkill() != 0)
|
|
{
|
|
if (!CECComboSkillState.Instance.IsActiveComboSkill(pSkill.GetSkillID()))
|
|
{
|
|
return 13; // Combo skill not active
|
|
}
|
|
}
|
|
*/
|
|
|
|
// Build UseRequirement info
|
|
UseRequirement Info = new UseRequirement();
|
|
Info.mp = m_BasicProps.iCurMP;
|
|
Info.ap = m_BasicProps.iCurAP;
|
|
Info.form = m_iShape; // Different from PW, no need to mask
|
|
Info.freepackage = m_pPack.GetEmptySlotNum();
|
|
Info.move_env = GetMoveEnv();
|
|
Info.is_combat = IsFighting();
|
|
Info.hp = m_BasicProps.iCurHP;
|
|
Info.max_hp = m_ExtProps.bs.max_hp;
|
|
// Info.combo_state = CECComboSkillState.Instance.GetComboSkillState(); // TODO: Implement GetComboSkillState
|
|
|
|
// Get weapon's major class ID
|
|
// Equipment inventory slot constants from CECPlayer.IndexOfIteminEquipmentInventory
|
|
const int EQUIPIVTR_WEAPON = 0; // Weapon slot
|
|
const int EQUIPIVTR_PROJECTILE = 4; // Projectile slot (arrows)
|
|
|
|
Info.weapon = 0;
|
|
Info.arrow = 0;
|
|
|
|
EC_IvtrItem pWeaponItem = m_pEquipPack.GetItem(EQUIPIVTR_WEAPON);
|
|
if (pWeaponItem != null && pWeaponItem is EC_IvtrEquip pWeaponEquip)
|
|
{
|
|
// Check if weapon has endurance
|
|
if (pWeaponEquip.CurEndurance > 0)
|
|
{
|
|
// TODO: Implement CanUseEquipment method to check level/class requirements
|
|
// For now, use the template ID as weapon type
|
|
Info.weapon = pWeaponItem.GetTemplateID();
|
|
}
|
|
}
|
|
|
|
// Get remaining arrow number
|
|
EC_IvtrItem pArrowItem = m_pEquipPack.GetItem(EQUIPIVTR_PROJECTILE);
|
|
if (pArrowItem != null)
|
|
{
|
|
// TODO: Implement CanUseProjectile method to check requirements
|
|
// For now, use the item count
|
|
Info.arrow = pArrowItem.GetCount();
|
|
}
|
|
|
|
// Call ElementSkill Condition check
|
|
if (pSkill.SkillCore != null)
|
|
{
|
|
return pSkill.SkillCore.Condition(Info);
|
|
}
|
|
|
|
return 0; // Success
|
|
}
|
|
|
|
// Process skill condition error - display appropriate message
|
|
// Returns: true if message was displayed, false otherwise
|
|
public bool ProcessSkillCondition(int iCon)
|
|
{
|
|
int iMsg = -1;
|
|
switch (iCon)
|
|
{
|
|
case 1: iMsg = (int)FixedMsg.FIXMSG_INVALIDWEAPON; break;
|
|
case 2: iMsg = (int)FixedMsg.FIXMSG_NEEDMP; break;
|
|
case 6: iMsg = (int)FixedMsg.FIXMSG_TARGETWRONG; break;
|
|
case 3: iMsg = (int)FixedMsg.FIXMSG_SKILL_INVALIDSTATE; break;
|
|
case 7: iMsg = (int)FixedMsg.FIXMSG_SKILL_INVALIDSTATE; break;
|
|
case 8: iMsg = (int)FixedMsg.FIXMSG_NEEDAP; break;
|
|
case 9: iMsg = (int)FixedMsg.FIXMSG_NOTENOUGHAMMO; break;
|
|
case 10: iMsg = (int)FixedMsg.FIXMSG_PACKFULL1; break;
|
|
case 11: iMsg = (int)FixedMsg.FIXMSG_SKILL_INVALIDENV; break;
|
|
case 20: iMsg = (int)FixedMsg.FIXMSG_NEEDITEM; break;
|
|
case 12: iMsg = (int)FixedMsg.FIXMSG_HP_UNSATISFIED; break;
|
|
}
|
|
|
|
if (iMsg >= 0)
|
|
{
|
|
// TODO: Implement AddFixedChannelMsg or use existing message system
|
|
// EC_Game.GetGameRun().AddFixedChannelMsg(iMsg, GP_CHAT_FIGHT);
|
|
Debug.LogWarning($"Skill condition failed: {iMsg}");
|
|
}
|
|
|
|
return iMsg >= 0;
|
|
}
|
|
|
|
bool CanSelectTarget(int idTarget)
|
|
{
|
|
if (idTarget == 0 || idTarget == this.GetCharacterID())
|
|
{
|
|
// 0 means unselect
|
|
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
|
|
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 || IsOperatingPet() || IsRebuildingPet() ||
|
|
IsUsingItem() || IsRidingOnPet() || GetShapeType() == 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() || 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 || IsRidingOnPet() || IsOperatingPet() || IsRebuildingPet() ||
|
|
IsUsingItem() || IsPassiveMove()*/)
|
|
bRet = false;
|
|
|
|
break;
|
|
|
|
case ActionCanDo.CANDO_ASSISTSEL:
|
|
|
|
if (IsDead() || !GPDataTypeHelper.ISPLAYERID(m_idSelTarget) ||
|
|
m_idSelTarget == m_PlayerInfo.cid /*||
|
|
!m_pTeam || !m_pTeam.GetMemberByID(m_idSelTarget) || m_iBuddyId || IsPassiveMove() ||
|
|
m_playerLimits.test(PLAYER_LIMIT_NOCHANGESELECT)*/)
|
|
bRet = false;
|
|
|
|
break;
|
|
|
|
case ActionCanDo.CANDO_FLY:
|
|
|
|
if (IsDead() || IsRooting() || IsSitting() || IsTrading() || IsReviving() ||
|
|
IsUsingTrashBox() || IsTalkingWithNPC() || IsChangingFace() || GetBoothState() != 0 ||
|
|
//IsFlashMoving() ||
|
|
m_pWorkMan.HasWorkRunningOnPriority(CECHPWorkMan.Work_priority.PRIORITY_2) ||
|
|
m_bHangerOn || /*IsOperatingPet() || IsRebuildingPet() ||*/
|
|
IsUsingItem() || /*IsRidingOnPet() || GetShapeType() == PLAYERMODEL_DUMMYTYPE2 ||*/ IsPassiveMove() /*||
|
|
m_playerLimits.test(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() || IsRebuildingPet() || IsUsingItem() || IsPassiveMove()*/)
|
|
bRet = false;
|
|
|
|
break;
|
|
|
|
case ActionCanDo.CANDO_TRADE:
|
|
|
|
if (IsDead() || IsMeleeing() /*|| IsAboutToDie() || IsSitting() || IsJumping() ||
|
|
IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() || IsChangingFace() ||
|
|
IsSpellingMagic() || GetBoothState() != 0 || m_iBuddyId || IsOperatingPet() || IsRebuildingPet() ||
|
|
IsUsingItem() || IsInvisible() || IsPassiveMove()*/)
|
|
bRet = false;
|
|
|
|
break;
|
|
|
|
case ActionCanDo.CANDO_PLAYPOSE:
|
|
|
|
if (IsDead() || IsMeleeing() || /*|| IsAboutToDie() || IsSitting() || IsJumping() || /* ||
|
|
IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() || IsChangingFace() ||
|
|
IsSpellingMagic() || IsShapeChanged() || IsReviving() ||*/
|
|
m_iMoveEnv != (int)MoveEnvironment.MOVEENV_GROUND /*||
|
|
GetBoothState() != 0 || m_iBuddyId || IsOperatingPet() || IsRebuildingPet() || IsUsingItem() ||
|
|
IsRidingOnPet() || GetShapeType() == PLAYERMODEL_DUMMYTYPE2 || IsPassiveMove() || m_BattleInfo.IsChariotWar()*/
|
|
)
|
|
bRet = false;
|
|
|
|
break;
|
|
|
|
//case ActionCanDo.CANDO_SPELLMAGIC:
|
|
// if (IsDead() || ISMATTERID(m_idSelTarget) || IsAboutToDie() || IsSitting() ||
|
|
// IsJumping() || IsFlashMoving() || IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() ||
|
|
// IsChangingFace() || CannotAttack() || IsReviving() || GetBoothState() != 0 ||
|
|
// m_iBuddyId || IsRidingOnPet() || IsOperatingPet() || 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() || 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 || IsInvisible() || IsGMInvisible() || IsOperatingPet() || 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() == PLAYERMODEL_DUMMYTYPE2)) ||
|
|
IsJumpInWater() || m_iMoveEnv == Move_environment.MOVEENV_AIR || IsSitting() ||
|
|
IsMeleeing() || IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() ||
|
|
IsChangingFace() || IsReviving() || IsSpellingMagic() || IsPicking() ||
|
|
IsGathering() || IsRooting() || GetBoothState() != 0 ||
|
|
m_bHangerOn || /*(IsJumping() && IsRidingOnPet()) ||*/
|
|
/*IsOperatingPet() || 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() || 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 || IsOperatingPet() || 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.HasWorkRunningOnPriority(CECHPWorkMan::PRIORITY_2) ||
|
|
// m_iBuddyId || IsOperatingPet() || 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.IsStanding() || m_iBuddyId ||
|
|
// IsOperatingPet() || IsRebuildingPet() || IsUsingItem() || GetShapeType() == PLAYERMODEL_DUMMYTYPE2 || IsPassiveMove() ||
|
|
// m_playerLimits.test(PLAYER_LIMIT_NOBIND))
|
|
// bRet = false;
|
|
|
|
// break;
|
|
|
|
//case ActionCanDo.CANDO_DUEL:
|
|
|
|
// if (IsDead() || IsAboutToDie() || IsSitting() || IsFighting() || IsTrading() ||
|
|
// IsReviving() || IsUsingTrashBox() || IsTalkingWithNPC() || IsChangingFace() ||
|
|
// GetBoothState() != 0 || m_iBuddyId || m_pvp.iDuelState != DUEL_ST_NONE ||
|
|
// IsOperatingPet() || IsRebuildingPet() || IsUsingItem() || IsPassiveMove())
|
|
// bRet = false;
|
|
|
|
// break;
|
|
|
|
case ActionCanDo.CANDO_CHANGESELECT:
|
|
|
|
//if (m_playerLimits.test(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 || IsOperatingPet() || IsRebuildingPet() || IsUsingItem() ||
|
|
// GetShapeType() == 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();
|
|
}
|
|
|
|
//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.Instance.OnPlayerDied();
|
|
}
|
|
m_bEnterGame = true;
|
|
}
|
|
public void SetSelectedTarget(int id)
|
|
{
|
|
m_idSelTarget = id;
|
|
}
|
|
|
|
public new int GetSelectedTarget()
|
|
{
|
|
return m_idSelTarget;
|
|
}
|
|
|
|
// Auto select a attackable target / 自动选择一个可攻击的目标
|
|
// Auto select a attackable target / 自动选择一个可攻击的目标
|
|
public int AutoSelectTarget()
|
|
{
|
|
if (!CanDo(ActionCanDo.CANDO_CHANGESELECT))
|
|
return 0;
|
|
|
|
int idCurSel = (m_idSelTarget != 0 && m_idSelTarget != m_PlayerInfo.cid) ? m_idSelTarget : 0;
|
|
|
|
// Clean up invalid targets from tab selection array / 清理无效目标
|
|
if (idCurSel == 0)
|
|
{
|
|
// Rebuild selected table / 重建选中表
|
|
m_aTabSels.Clear();
|
|
}
|
|
else
|
|
{
|
|
// Remove targets that are too far / 移除距离过远的目标
|
|
for (int i = m_aTabSels.Count - 1; i >= 0; i--)
|
|
{
|
|
CECObject pObject = m_aTabSels[i];
|
|
if (pObject == null)
|
|
{
|
|
m_aTabSels.RemoveAt(i);
|
|
continue;
|
|
}
|
|
|
|
float fDistToHost = 0.0f;
|
|
if (pObject is EC_ElsePlayer pPlayer)
|
|
fDistToHost = pPlayer.GetDistToHost();
|
|
else if (pObject is CECNPC pNPC)
|
|
fDistToHost = pNPC.GetDistToHost();
|
|
|
|
if (fDistToHost > EC_RoleTypes.EC_TABSEL_DIST || !CanSafelySelectWith(fDistToHost))
|
|
{
|
|
m_aTabSels.RemoveAt(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
float fMinDist = 10000.0f;
|
|
CECObject pCand = null;
|
|
int idCandidate = 0;
|
|
|
|
// Get player candidates / 获取玩家候选列表
|
|
List<EC_ElsePlayer> aCandPlayers = new List<EC_ElsePlayer>();
|
|
EC_ManMessageMono.Instance.EC_ManPlayer.TabSelectCandidates(idCurSel, aCandPlayers);
|
|
|
|
foreach (var pPlayer in aCandPlayers)
|
|
{
|
|
if (pPlayer == null)
|
|
continue;
|
|
|
|
// Check whether this player is in selected array / 检查此玩家是否已在选中数组中
|
|
if (m_aTabSels.Contains(pPlayer))
|
|
continue; // This player has been in selected array / 此玩家已在选中数组中
|
|
|
|
// Record the nearest one as candidate / 记录最近的一个作为候选
|
|
float fDist = pPlayer.GetDistToHost();
|
|
if (fDist < fMinDist)
|
|
{
|
|
fMinDist = fDist;
|
|
pCand = pPlayer;
|
|
idCandidate = pPlayer.GetCharacterID();
|
|
}
|
|
}
|
|
|
|
// Get NPC candidates / 获取NPC候选列表
|
|
List<CECNPC> aCandNPCs = new List<CECNPC>();
|
|
EC_ManMessageMono.Instance.CECNPCMan.TabSelectCandidates(idCurSel, aCandNPCs);
|
|
|
|
foreach (var pNPC in aCandNPCs)
|
|
{
|
|
if (pNPC == null)
|
|
continue;
|
|
|
|
// Check whether this npc is in selected array / 检查此NPC是否已在选中数组中
|
|
if (m_aTabSels.Contains(pNPC))
|
|
continue; // This npc has been in selected array / 此NPC已在选中数组中
|
|
|
|
// Record the nearest one as candidate / 记录最近的一个作为候选
|
|
float fDist = pNPC.GetDistToHost();
|
|
if (fDist < fMinDist)
|
|
{
|
|
fMinDist = fDist;
|
|
pCand = pNPC;
|
|
idCandidate = pNPC.GetNPCID();
|
|
}
|
|
}
|
|
|
|
// Maximum number of candidates in tab selection array / 标签选择数组中的最大候选数
|
|
// Increased from 9 to 20 to allow more targets / 从9增加到20以允许更多目标
|
|
const int iMaxCand = 9;
|
|
int idNewSel = 0;
|
|
|
|
if (pCand != null && idCandidate != 0)
|
|
{
|
|
// Add candidate to tab selection array / 将候选添加到标签选择数组
|
|
if (m_aTabSels.Count >= iMaxCand)
|
|
{
|
|
m_aTabSels.RemoveAt(m_aTabSels.Count - 1);
|
|
m_aTabSels.Insert(0, pCand);
|
|
}
|
|
else
|
|
{
|
|
m_aTabSels.Add(pCand);
|
|
}
|
|
|
|
idNewSel = idCandidate;
|
|
}
|
|
else // No proper candidate was found / 未找到合适的候选
|
|
{
|
|
if (m_aTabSels.Count == 0)
|
|
{
|
|
idNewSel = idCurSel;
|
|
}
|
|
else // Try to select one which has been in selected array / 尝试选择已在选中数组中的一个
|
|
{
|
|
int iIndex = -1;
|
|
for (int i = 0; i < m_aTabSels.Count; i++)
|
|
{
|
|
CECObject pObject = m_aTabSels[i];
|
|
if (pObject == null)
|
|
continue;
|
|
|
|
int objId = 0;
|
|
if (pObject is EC_ElsePlayer pElsePlayer)
|
|
objId = pElsePlayer.GetCharacterID();
|
|
else if (pObject is CECNPC pNPC)
|
|
objId = pNPC.GetNPCID();
|
|
|
|
if (objId == idCurSel)
|
|
{
|
|
iIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
iIndex = (iIndex + 1) % m_aTabSels.Count;
|
|
CECObject pSelectedObj = m_aTabSels[iIndex];
|
|
if (pSelectedObj != null)
|
|
{
|
|
if (pSelectedObj is EC_ElsePlayer pSelPlayer)
|
|
idNewSel = pSelPlayer.GetCharacterID();
|
|
else if (pSelectedObj is CECNPC pSelNPC)
|
|
idNewSel = pSelNPC.GetNPCID();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Select the target if found / 如果找到目标则选择它
|
|
if (idNewSel != 0)
|
|
{
|
|
if (idNewSel != idCurSel)
|
|
{
|
|
SelectTarget(idNewSel);
|
|
}
|
|
}
|
|
|
|
return idNewSel;
|
|
}
|
|
|
|
public bool glb_GetForceAttackFlag(uint pdwParam)
|
|
{
|
|
/*bool bForceAttack = false;
|
|
CECInputCtrl* pInputCtrl = g_pGame.GetGameRun().GetInputCtrl();
|
|
|
|
if (pdwParam)
|
|
bForceAttack = pInputCtrl.IsCtrlPressed(*pdwParam);
|
|
else
|
|
bForceAttack = pInputCtrl.KeyIsBeingPressed(VK_CONTROL);
|
|
|
|
return bForceAttack;*/
|
|
return false;
|
|
}
|
|
//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;
|
|
//}
|
|
|
|
private void OnMsgHstNPCGreeting(ECMSG Msg)
|
|
{
|
|
cmd_npc_greeting pCmd = GPDataTypeHelper.FromBytes<cmd_npc_greeting>((byte[])Msg.dwParam1);
|
|
|
|
if (GPDataTypeHelper.ISNPCID(pCmd.idObject))
|
|
{
|
|
// רÃÅ´¦Àíѧϰ¼¼ÄܵÄÒþ²ØNPC
|
|
//if (CECHostSkillModel::Instance().IsSkillLearnNPC(pCmd.idObject))
|
|
//{
|
|
// CECGameUIMan* pGameUI = g_pGame.GetGameRun().GetUIManager().GetInGameUIMan();
|
|
// //m_idSevNPC = pCmd.idObject;
|
|
// //m_bTalkWithNPC = true;
|
|
// //pGameUI.GetDialog("Win_SkillAction").Show(true);
|
|
// //CDlgSkillAction* dlg = dynamic_cast<CDlgSkillAction*>(pGameUI.GetDialog("Win_SkillAction"));
|
|
// //dlg.ForceShowDialog();
|
|
// CDlgSkillAction* dlg = dynamic_cast<CDlgSkillAction*>(pGameUI.GetDialog("Win_SkillAction"));
|
|
// dlg.SetReceivedNPCGreeting(true);
|
|
// return;
|
|
//}
|
|
|
|
CECNPC pNPC = EC_ManMessageMono.Instance.CECNPCMan.GetNPC(pCmd.idObject);
|
|
if (!pNPC || !pNPC.IsServerNPC())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Check distance again
|
|
if (!CanTouchTarget(pNPC.GetPos(), pNPC.GetTouchRadius(), 3))
|
|
return;
|
|
|
|
m_idSevNPC = pCmd.idObject;
|
|
m_bTalkWithNPC = true;
|
|
|
|
// Check way point service on NPC
|
|
//TODO: Fix later
|
|
//var dwID = (pNPC as CECNPCServer).GetWayPointID();
|
|
//if (dwID != null && !HasWayPoint(dwID))
|
|
//UnityGameSession.c2s_CmdNPCSevWaypoint();
|
|
//g_pGame.GetGameSession().c2s_CmdNPCSevWaypoint();
|
|
|
|
var pGameUI = EC_Game.GetGameRun().GetUIManager().GetInGameUIMan();
|
|
NPC_ESSENCE? result = (pNPC as CECNPCServer).GetDBEssence();
|
|
if (result != null)
|
|
{
|
|
pGameUI.PopupNPCDialog(result.Value);
|
|
}
|
|
}
|
|
//else if (GPDataTypeHelper.ISPLAYERID(pCmd.idObject))
|
|
//{
|
|
// EC_ElsePlayer pPlayer = m_pPlayerMan.GetElsePlayer(pCmd.idObject);
|
|
|
|
// // Check distance again
|
|
// if (!pPlayer || !CanTouchTarget(pPlayer.GetPos(), 0.0f, 3))
|
|
// return;
|
|
|
|
// m_idSevNPC = pCmd.idObject;
|
|
// m_bTalkWithNPC = true;
|
|
// m_iBoothState = 3;
|
|
|
|
// g_pGame.GetGameSession().c2s_CmdNPCSevGetContent(GP_NPCSEV_BOOTHSELL);
|
|
|
|
// m_pBuyPack.RemoveAllItems();
|
|
// m_pSellPack.RemoveAllItems();
|
|
// m_pEPBoothBPack.RemoveAllItems();
|
|
// m_pEPBoothSPack.RemoveAllItems();
|
|
|
|
// CECGameUIMan* pGameUI = g_pGame.GetGameRun().GetUIManager().GetInGameUIMan();
|
|
// pGameUI.PopupBoothDialog(true, false, pCmd.idObject);
|
|
//}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
void OnMsgHstWayPoint(ECMSG Msg)
|
|
{
|
|
//CECGameUIMan pGameUI = EC_Game.GetGameRun().GetUIManager().GetInGameUIMan();
|
|
|
|
//if (Convert.ToInt32(Msg.dwParam2) == CommandID.ACTIVATE_WAYPOINT)
|
|
//{
|
|
// cmd_activate_waypoint pCmd = GPDataTypeHelper.FromBytes<cmd_activate_waypoint>((byte[])Msg.dwParam1);
|
|
// m_aWayPoints.Add(pCmd.waypoint);
|
|
|
|
// // add to waypoints array
|
|
// pGameUI.GetMapDlgsMgr().UpdateWayPoints(&pCmd.waypoint, 1, false);
|
|
|
|
// // Print a notify message
|
|
// const CECMapDlgsMgr::PointMap& aWayPoints = pGameUI.GetMapDlgsMgr().GetTransPoint();
|
|
// CECMapDlgsMgr::PointMap::const_iterator itr = aWayPoints.find(pCmd.waypoint);
|
|
// if(itr != aWayPoints.end())
|
|
// {
|
|
// g_pGame.GetGameRun().AddFixedMessage(FIXMSG_NEWWAYPOINT, (itr.second).strName);
|
|
|
|
// bool bCanPopUITips = true;
|
|
// int count = CECUIConfig::Instance().GetGameUI().GetTaskIDDisableWayPointsUITipsCount();
|
|
// // ¼ì²éÉíÉÏÊÇ·ñÓнûÖ¹µ¯³ötipsµÄÈÎÎñ
|
|
// for (int i=0;i<count;i++){
|
|
// int taskID = CECUIConfig::Instance().GetGameUI().GetTaskIDDisableWayPointsUITips(i);
|
|
// if(GetTaskInterface() && GetTaskInterface().HasTask(taskID)){
|
|
// bCanPopUITips = false;
|
|
// break;
|
|
// }
|
|
// }
|
|
// // µ¯³ötips
|
|
// CECScriptMan* pScriptMan = g_pGame.GetGameRun().GetUIManager().GetScriptMan();
|
|
// if (pScriptMan && bCanPopUITips)
|
|
// {
|
|
// pScriptMan.GetContext().GetUI().SetTipDialogTitleAndContent(AC2AS_CP(CP_UTF8, pGameUI.GetStringFromTable(11350)), AC2AS_CP(CP_UTF8, (itr.second).strName));
|
|
// pScriptMan.GetContext().GetUI().ShowTip(500, 500, 5000, 300);
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
// else if (Msg.dwParam2 == WAYPOINT_LIST)
|
|
// {
|
|
// cmd_waypoint_list* pCmd = (cmd_waypoint_list*)Msg.dwParam1;
|
|
|
|
// m_aWayPoints.SetSize(pCmd.count, 16);
|
|
// for (size_t i = 0; i < pCmd.count; i++)
|
|
// m_aWayPoints[i] = pCmd.list[i];
|
|
|
|
// // update the whole list
|
|
// pGameUI.GetMapDlgsMgr().UpdateWayPoints(pCmd.list, pCmd.count, true);
|
|
// }
|
|
}
|
|
|
|
// 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
|
|
|
|
// // Popup notify bubble text
|
|
// BubbleText(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);
|
|
}
|
|
|
|
// Start normal attacking to selected target
|
|
public bool CmdNormalAttack(bool bMoreClose /* false */, bool bCombo /* false */,
|
|
int idTarget /* 0 */, int iForceAtk /* -1 */)
|
|
{
|
|
// StackChecker::ACTrace(2);
|
|
|
|
// first of all see if we need to cancel sitdown work.
|
|
// if (m_pWorkMan.IsSitting())
|
|
// {
|
|
// g_pGame.GetGameSession().c2s_CmdStandUp();
|
|
// return false;
|
|
// }
|
|
|
|
if (!CanDo(ActionCanDo.CANDO_MELEE))
|
|
return false;
|
|
|
|
// if (InSlidingState())
|
|
// return false;
|
|
|
|
// if (!bCombo)
|
|
// ClearComboSkill();
|
|
|
|
if (idTarget <= 0)
|
|
idTarget = m_idSelTarget;
|
|
|
|
bool bForceAttack;
|
|
if (iForceAtk < 0)
|
|
bForceAttack = glb_GetForceAttackFlag(0);
|
|
else
|
|
bForceAttack = iForceAtk > 0 ? true : false;
|
|
|
|
if (AttackableJudge(idTarget, bForceAttack) != 1)
|
|
return false;
|
|
|
|
return NormalAttackObject(idTarget, bForceAttack, bMoreClose);
|
|
}
|
|
|
|
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
|
|
bool IsSpellingMagic()
|
|
{
|
|
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)
|
|
{
|
|
vTerrainPos = hits[0].point;
|
|
vTerrainNormal = hits[0].normal;
|
|
}
|
|
|
|
if (Physics.RaycastNonAlloc(vPos, (Vector3.down), hits, DeltaY, layerMaskBush) > 0)
|
|
{
|
|
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)
|
|
{
|
|
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)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
void OnMsgHstExtProp(ECMSG Msg)
|
|
{
|
|
cmd_own_ext_prop pCmd = GPDataTypeHelper.FromBytes<cmd_own_ext_prop>((byte[])Msg.dwParam1);
|
|
m_ExtProps = pCmd.prop;
|
|
m_BasicProps.iStatusPt = (int)pCmd.status_point;
|
|
m_BasicProps.iAtkDegree = pCmd.attack_degree;
|
|
m_BasicProps.iDefDegree = pCmd.defend_degree;
|
|
m_BasicProps.iCritRate = pCmd.crit_rate;
|
|
m_BasicProps.iCritDamageBonus = pCmd.crit_damage_bonus;
|
|
m_BasicProps.iInvisibleDegree = pCmd.invisible_degree;
|
|
m_BasicProps.iAntiInvisibleDegree = pCmd.anti_invisible_degree;
|
|
m_BasicProps.iPenetration = pCmd.penetration;
|
|
m_BasicProps.iResilience = pCmd.resilience;
|
|
m_BasicProps.iVigour = pCmd.vigour;
|
|
}
|
|
|
|
private void OnMsgPlayerDoEmote(ECMSG Msg)
|
|
{
|
|
if (!m_pWorkMan.IsStanding() &&
|
|
!m_pWorkMan.IsBeingBound())
|
|
return;
|
|
|
|
int cmd = Convert.ToInt32(Msg.dwParam2);
|
|
if (cmd == CommandID.OBJECT_DO_EMOTE)
|
|
{
|
|
cmd_object_do_emote pCmd = GPDataTypeHelper.FromBytes<cmd_object_do_emote>((byte[])Msg.dwParam1);
|
|
|
|
DoEmote(pCmd.emotion);
|
|
|
|
// if( m_iBuddyId )
|
|
// {
|
|
// CECPlayer pBuddy = m_pPlayerMan.GetPlayer(m_iBuddyId);
|
|
// if (pBuddy)
|
|
// pBuddy.DoEmote(pCmd.emotion);
|
|
// }
|
|
|
|
GetTaskInterface().SetEmotion(pCmd.emotion);
|
|
}
|
|
else if (cmd == CommandID.OBJECT_EMOTE_RESTORE)
|
|
{
|
|
CECHPWork pWork = m_pWorkMan.GetRunningWork((int)WorkID.WORK_STAND);
|
|
if (pWork != null)
|
|
{
|
|
((CECHPWorkStand)pWork).SetPoseAction((int)PLAYER_ACTION_TYPE.ACT_STAND, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Do emote action
|
|
private bool DoEmote(int idEmote)
|
|
{
|
|
if (!m_pWorkMan.IsStanding())
|
|
return false;
|
|
|
|
CECHPWorkStand pWork = m_pWorkMan.GetRunningWork(Host_work_ID.WORK_STAND) as CECHPWorkStand;
|
|
if (pWork == null)
|
|
{
|
|
Debug.LogError("Null CECHPWorkStand");
|
|
}
|
|
|
|
int iAction = (int)PLAYER_ACTION_TYPE.ACT_STAND;
|
|
bool bSession = false;
|
|
|
|
// Select action according to pose
|
|
switch (idEmote)
|
|
{
|
|
case (int)RoleExpression.ROLEEXP_WAVE: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_WAVE; break;
|
|
case (int)RoleExpression.ROLEEXP_NOD: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_NOD; break;
|
|
case (int)RoleExpression.ROLEEXP_SHAKEHEAD: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_SHAKEHEAD; break;
|
|
case (int)RoleExpression.ROLEEXP_SHRUG: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_SHRUG; break;
|
|
case (int)RoleExpression.ROLEEXP_LAUGH: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_LAUGH; break;
|
|
case (int)RoleExpression.ROLEEXP_ANGRY: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_ANGRY; break;
|
|
case (int)RoleExpression.ROLEEXP_STUN: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_STUN; break;
|
|
case (int)RoleExpression.ROLEEXP_DEPRESSED: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_DEPRESSED; break;
|
|
case (int)RoleExpression.ROLEEXP_KISSHAND: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_KISSHAND; break;
|
|
case (int)RoleExpression.ROLEEXP_SHY: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_SHY; break;
|
|
case (int)RoleExpression.ROLEEXP_SALUTE: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_SALUTE; break;
|
|
|
|
case (int)RoleExpression.ROLEEXP_SITDOWN:
|
|
|
|
iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_SITDOWN;
|
|
bSession = true;
|
|
break;
|
|
|
|
case (int)RoleExpression.ROLEEXP_ASSAULT: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_ASSAULT; break;
|
|
case (int)RoleExpression.ROLEEXP_THINK: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_THINK; break;
|
|
case (int)RoleExpression.ROLEEXP_DEFIANCE: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_DEFIANCE; break;
|
|
case (int)RoleExpression.ROLEEXP_VICTORY: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_VICTORY; break;
|
|
case (int)RoleExpression.ROLEEXP_GAPE: iAction = (int)PLAYER_ACTION_TYPE.ACT_GAPE; break;
|
|
case (int)RoleExpression.ROLEEXP_KISS: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_KISS; break;
|
|
case (int)RoleExpression.ROLEEXP_FIGHT: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_FIGHT; break;
|
|
case (int)RoleExpression.ROLEEXP_ATTACK1: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_ATTACK1; break;
|
|
case (int)RoleExpression.ROLEEXP_ATTACK2: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_ATTACK2; break;
|
|
case (int)RoleExpression.ROLEEXP_ATTACK3: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_ATTACK3; break;
|
|
case (int)RoleExpression.ROLEEXP_ATTACK4: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_ATTACK4; break;
|
|
case (int)RoleExpression.ROLEEXP_DEFENCE: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_DEFENCE; break;
|
|
case (int)RoleExpression.ROLEEXP_FALL: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_FALL; break;
|
|
case (int)RoleExpression.ROLEEXP_FALLONGROUND:
|
|
iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_FALLONGROUND; break;
|
|
case (int)RoleExpression.ROLEEXP_LOOKAROUND:
|
|
iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_LOOKAROUND; break;
|
|
case (int)RoleExpression.ROLEEXP_DANCE: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_DANCE; break;
|
|
case (int)RoleExpression.ROLEEXP_FASHIONWEAPON:
|
|
iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_FASHIONWEAPON; break;
|
|
case (int)RoleExpression.ROLEEXP_TWO_KISS: iAction = (int)PLAYER_ACTION_TYPE.ACT_TWO_KISS; break;
|
|
case (int)RoleExpression.ROLEEXP_FIREWORK: iAction = (int)PLAYER_ACTION_TYPE.ACT_ATTACK_TOSS; break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
pWork.SetPoseAction(iAction, bSession);
|
|
|
|
return true;
|
|
}
|
|
|
|
// Get cool time
|
|
public int GetCoolTime(int iIndex, ref int piMax /* NULL */)
|
|
{
|
|
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;
|
|
}
|
|
|
|
void OnMsgHstTargetIsFar(ECMSG Msg)
|
|
{
|
|
// TO DO: fix later
|
|
//if(CmdNormalAttack(true, m_pComboSkill != null, 0, -1) )
|
|
// AP_ActionEvent(AP_EVENT_MELEEOUTOFRANGE, 1);
|
|
|
|
if (CmdNormalAttack(true, false, 0, -1))
|
|
{
|
|
//AP_ActionEvent(AP_EVENT_MELEEOUTOFRANGE, 1);
|
|
}
|
|
}
|
|
|
|
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 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;
|
|
}
|
|
};
|
|
|
|
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 CanTakeItem(int idItem, int iAmount)
|
|
{
|
|
bool bCanPick = false;
|
|
|
|
if (GPDataTypeHelper.ISMONEYTID(idItem))
|
|
{
|
|
if (GetMoneyAmount() < GetMaxMoneyAmount())
|
|
bCanPick = true;
|
|
}
|
|
else
|
|
{
|
|
if (IvtrPack.CanAddItem(idItem, iAmount, false) >= 0)
|
|
bCanPick = true;
|
|
}
|
|
|
|
return bCanPick;
|
|
}
|
|
|
|
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;
|
|
}
|
|
void OnMsgPlayerGather(ECMSG Msg)
|
|
{
|
|
int cmd = Convert.ToInt32(Msg.dwParam2);
|
|
if (cmd == CommandID.PLAYER_GATHER_START)
|
|
{
|
|
cmd_player_gather_start pCmd = GPDataTypeHelper.FromBytes<cmd_player_gather_start>((byte[])Msg.dwParam1);
|
|
|
|
EC_ManMatter pMatterMan = EC_ManMessageMono.Instance.GetECManMatter;//g_pGame.GetGameRun().GetWorld().GetMatterMan();
|
|
CECMatter pMatter = pMatterMan.GetMatter(pCmd.mid);
|
|
// if (pMatter && pMatter.IsMonsterSpiritMine()) {
|
|
// CECHPWorkUse* pWork = (CECHPWorkUse*)m_pWorkMan.CreateWork(CECHPWork::WORK_USEITEM);
|
|
// if (pWork) {
|
|
// pWork.SetParams(pCmd.mid, pCmd.use_time * 1000, pCmd.mid, true);
|
|
// m_pWorkMan.StartWork_p1(pWork);
|
|
// }
|
|
// StartMonsterSpiritConnectGfx(pCmd.mid, pMatter.GetPos());
|
|
// } else {
|
|
EC_HPWorkPick pWork = (EC_HPWorkPick)m_pWorkMan.CreateWork(Host_work_ID.WORK_PICKUP);
|
|
if (pWork != null)
|
|
{
|
|
pWork.SetGather(true, pMatter ? pMatter.GetTemplateID() : 0);
|
|
m_pWorkMan.StartWork_p1(pWork);
|
|
}
|
|
// }
|
|
|
|
// Start time counter
|
|
m_GatherCnt.SetPeriod(pCmd.use_time * 1000);
|
|
m_GatherCnt.Reset();
|
|
}
|
|
else if (cmd == CommandID.PLAYER_GATHER_STOP)
|
|
{
|
|
cmd_player_gather_stop pCmd = GPDataTypeHelper.FromBytes<cmd_player_gather_stop>((byte[])Msg.dwParam1);
|
|
m_pWorkMan.FinishRunningWork(Host_work_ID.WORK_PICKUP);
|
|
m_pWorkMan.FinishRunningWork(Host_work_ID.WORK_USEITEM);
|
|
// StopMonsterSpiritConnectGfx();
|
|
var pDlg = EC_Game.GetGameRun().GetUIManager().GetInGameUIMan().GetDialog("Win_Prgs2");
|
|
if (pDlg == null)
|
|
{
|
|
BMLogger.LogError("Null Win_Prgs2");
|
|
return;
|
|
}
|
|
|
|
if (pDlg is not DlgWinPrgs2 dlgWinPrgs)
|
|
{
|
|
BMLogger.LogError("Can not cast Win_Prgs2");
|
|
return;
|
|
}
|
|
dlgWinPrgs.SetActiveProgress(false);
|
|
}
|
|
else if (cmd == CommandID.MINE_GATHERED)
|
|
{
|
|
cmd_mine_gathered pCmd = GPDataTypeHelper.FromBytes<cmd_mine_gathered>((byte[])Msg.dwParam1);
|
|
// ASSERT(pCmd && pCmd.player_id == m_PlayerInfo.cid);
|
|
elementdataman pDataMan = EC_Game.GetElementDataMan();
|
|
DATA_TYPE DataType = pDataMan.get_data_type((uint)pCmd.item_type, ID_SPACE.ID_SPACE_ESSENCE);
|
|
if (DataType == DATA_TYPE.DT_MONSTER_SPIRIT_ESSENCE)
|
|
{
|
|
// StartMonsterSpiritBallGfx();
|
|
// m_CardHolder.gain_times++;
|
|
}
|
|
}
|
|
}
|
|
|
|
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))
|
|
m_GatherCnt.Reset(true);
|
|
|
|
// Incant time counter
|
|
if (m_IncantCnt.IncCounter(dwDeltaTime))
|
|
m_IncantCnt.Reset(true);
|
|
|
|
// 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)
|
|
{
|
|
if ((m_BattleInfo.iResultCnt -= dwDeltaTime) < 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove skill reference / 删除技能引用
|
|
void RemoveSkillReference(int idSkill)
|
|
{
|
|
if (idSkill <= 0) return;
|
|
|
|
// Remove reference to self skill / 删除对技能的引用
|
|
if (m_pPrepSkill != null && m_pPrepSkill.GetSkillID() == idSkill)
|
|
m_pPrepSkill = null;
|
|
|
|
if (m_pCurSkill != null && m_pCurSkill.GetSkillID() == idSkill)
|
|
m_pCurSkill = null;
|
|
|
|
if (m_pComboSkill != null && m_pComboSkill.FindSkillID(idSkill))
|
|
ClearComboSkill();
|
|
|
|
if (m_pWorkMan != null)
|
|
{
|
|
CECHPWork pWork = m_pWorkMan.GetWork(CECHPWork.Host_work_ID.WORK_TRACEOBJECT);
|
|
if (pWork != null)
|
|
{
|
|
CECHPWorkTrace pWorkTrace = pWork as CECHPWorkTrace;
|
|
if (pWorkTrace != null &&
|
|
pWorkTrace.GetTraceReason() == CECHPWorkTrace.Trace_reason.TRACE_SPELL &&
|
|
pWorkTrace.GetPrepSkill() != null &&
|
|
pWorkTrace.GetPrepSkill().GetSkillID() == idSkill)
|
|
pWorkTrace.Reset();
|
|
}
|
|
}
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < HostCfgConstants.NUM_HOSTSCSETS1; i++)
|
|
{
|
|
if (m_aSCSets1[i] != null)
|
|
m_aSCSets1[i].RemoveSkillShortcut(idSkill);
|
|
}
|
|
for (i = 0; i < HostCfgConstants.NUM_HOSTSCSETS2; i++)
|
|
{
|
|
if (m_aSCSets2[i] != null)
|
|
m_aSCSets2[i].RemoveSkillShortcut(idSkill);
|
|
}
|
|
}
|
|
|
|
// Remove skill / 删除技能
|
|
void RemoveNormalSkill(int idSkill)
|
|
{
|
|
// Delete equipped skills / 删除装备技能
|
|
|
|
if (idSkill <= 0) return;
|
|
|
|
RemoveSkillReference(idSkill);
|
|
|
|
// Delete skill list pointer / 删除技能列表指针
|
|
int i;
|
|
|
|
for (i = 0; i < m_aPtSkills.Count; i++)
|
|
{
|
|
if (m_aPtSkills[i].GetSkillID() == idSkill)
|
|
{
|
|
m_aPtSkills.RemoveAt(i);
|
|
return;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < m_aPsSkills.Count; i++)
|
|
{
|
|
if (m_aPsSkills[i].GetSkillID() == idSkill)
|
|
{
|
|
m_aPsSkills.RemoveAt(i);
|
|
return;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < m_aGoblinSkills.Count; i++)
|
|
{
|
|
if (m_aGoblinSkills[i].GetSkillID() == idSkill)
|
|
{
|
|
m_aGoblinSkills.RemoveAt(i);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Clear combo skill / 清除连击技能
|
|
void ClearComboSkill()
|
|
{
|
|
if (m_pComboSkill != null)
|
|
{
|
|
m_pComboSkill = null;
|
|
}
|
|
}
|
|
|
|
// Replace specified skill with it's senior skill / 用高级技能替换指定的低级技能
|
|
void ReplaceJuniorSkill(CECSkill pSeniorSkill)
|
|
{
|
|
if (pSeniorSkill == null)
|
|
{
|
|
Debug.Assert(pSeniorSkill != null);
|
|
return;
|
|
}
|
|
SkillArrayWrapper juniorArray = pSeniorSkill.GetJunior();
|
|
if (juniorArray.Empty())
|
|
{
|
|
Debug.Assert(false);
|
|
return;
|
|
}
|
|
|
|
int i;
|
|
|
|
// Update shortcuts ... / 更新快捷方式...
|
|
for (i = 0; i < HostCfgConstants.NUM_HOSTSCSETS1; i++)
|
|
{
|
|
if (m_aSCSets1[i] != null)
|
|
m_aSCSets1[i].ReplaceSkillID(juniorArray, pSeniorSkill);
|
|
}
|
|
|
|
for (i = 0; i < HostCfgConstants.NUM_HOSTSCSETS2; i++)
|
|
{
|
|
if (m_aSCSets2[i] != null)
|
|
m_aSCSets2[i].ReplaceSkillID(juniorArray, pSeniorSkill);
|
|
}
|
|
|
|
// Update skill groups ... / 更新技能组...
|
|
// Note: Combo skill update logic may need to be added here
|
|
// Original C++ code had EC_VIDEO_SETTING and combo skill group logic
|
|
}
|
|
|
|
public int CheckSkillLearnCondition(int idSkill, bool bCheckBook)
|
|
{
|
|
int iLevel = 1;
|
|
CECSkill pSkill = GetNormalSkill(idSkill);
|
|
if (pSkill != null)
|
|
iLevel = pSkill.GetSkillLevel() + 1;
|
|
|
|
if (iLevel == 1 && bCheckBook)
|
|
{
|
|
// Do we have the skill book ?
|
|
int idBook = ElementSkill.GetRequiredBook((uint)idSkill, iLevel);
|
|
if (idBook != 0 /*&& m_pPack.FindItem(idBook) < 0*/)
|
|
return 8;
|
|
}
|
|
|
|
// Build player information
|
|
LearnRequirement Info;
|
|
|
|
Info.level = GetMaxLevelSofar();
|
|
Info.sp = m_BasicProps.iSP;
|
|
Info.money = (int)m_iMoneyCnt;
|
|
Info.profession = m_iProfession;
|
|
Info.rank = m_BasicProps.iLevel2;
|
|
Info.realm_level = GetRealmLevel();
|
|
|
|
return ElementSkill.LearnCondition((uint)idSkill, Info, iLevel);
|
|
}
|
|
private void OnMsgHstPressCancel(ECMSG Msg)
|
|
{
|
|
CECHPWork pCurWork = null;
|
|
pCurWork = m_pWorkMan.GetRunningWork(Host_work_ID.WORK_TRACEOBJECT);
|
|
if (pCurWork is CECHPWorkTrace workTrace)
|
|
{
|
|
workTrace.PressCancel();
|
|
return;
|
|
}
|
|
|
|
pCurWork = m_pWorkMan.GetRunningWork(Host_work_ID.WORK_HACKOBJECT);
|
|
if (pCurWork != null)
|
|
{
|
|
UnityGameSession.c2s_CmdCancelAction();
|
|
return;
|
|
}
|
|
|
|
pCurWork = m_pWorkMan.GetRunningWork(Host_work_ID.WORK_USEITEM);
|
|
if (pCurWork != null)
|
|
{
|
|
UnityGameSession.c2s_CmdCancelAction();
|
|
return;
|
|
}
|
|
|
|
pCurWork = m_pWorkMan.GetRunningWork(Host_work_ID.WORK_SPELLOBJECT);
|
|
if (pCurWork != null)
|
|
{
|
|
int iState = ((CECHPWorkSpell)pCurWork).GetState();
|
|
if (iState == CECHPWorkSpell.Spell_magic_state.ST_INCANT)
|
|
{
|
|
UnityGameSession.c2s_CmdCancelAction();
|
|
return;
|
|
}
|
|
}
|
|
|
|
pCurWork = m_pWorkMan.GetRunningWork(Host_work_ID.WORK_PICKUP);
|
|
if (pCurWork != null)
|
|
{
|
|
if (((EC_HPWorkPick)pCurWork).IsGather())
|
|
{
|
|
UnityGameSession.c2s_CmdCancelAction();
|
|
return;
|
|
}
|
|
}
|
|
//todo: handle this part
|
|
// pCurWork = m_pWorkMan.GetRunningWork(Host_work_ID.WORK_CONCENTRATE);
|
|
// if (pCurWork !=null){
|
|
// if (IsOperatingPet()){
|
|
// UnityGameSession.c2s_CmdCancelAction();
|
|
// return;
|
|
// }
|
|
// }
|
|
// pCurWork = m_pWorkMan.GetRunningWork(Host_work_ID.WORK_CONGREGATE);
|
|
// if (pCurWork !=null){
|
|
// if (IsCongregating()){
|
|
// UnityGameSession.c2s_CmdCancelAction();
|
|
// return;
|
|
// }
|
|
// }
|
|
|
|
if (m_bUsingTrashBox || DoingSessionPose())
|
|
{
|
|
UnityGameSession.c2s_CmdCancelAction();
|
|
return;
|
|
}
|
|
|
|
// Cancel current selection
|
|
if (m_idSelTarget > 0)
|
|
{
|
|
SelectTarget(0);
|
|
return;
|
|
}
|
|
|
|
// Some work have lower priority
|
|
pCurWork = m_pWorkMan.GetRunningWork(Host_work_ID.WORK_MOVETOPOS);
|
|
if (pCurWork != null)
|
|
{
|
|
((CECHPWorkMove)pCurWork).PressCancel();
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Is doing session pose ?
|
|
private bool DoingSessionPose()
|
|
{
|
|
var pCurWork = m_pWorkMan.GetRunningWork(Host_work_ID.WORK_STAND);
|
|
if (pCurWork != null)
|
|
{
|
|
if (((CECHPWorkStand)pCurWork).DoingSessionPose())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
void OnMsgPlayerFly(ECMSG Msg)
|
|
{
|
|
if (Convert.ToInt32(Msg.dwParam2) == CommandID.OBJECT_TAKEOFF)
|
|
{
|
|
if ((m_dwStates & PlayerNPCState.GP_STATE_FLY) == 0)
|
|
{
|
|
m_dwStates |= PlayerNPCState.GP_STATE_FLY;
|
|
m_bRushFly = false;
|
|
|
|
CECHPWorkFly pWork = (CECHPWorkFly)m_pWorkMan.CreateWork(CECHPWork.Host_work_ID.WORK_FLYOFF);
|
|
if (m_pWorkMan.IsFreeFalling())
|
|
{
|
|
pWork.m_bContinueFly = true;
|
|
m_pWorkMan.StartWork_p1(pWork);
|
|
}
|
|
else
|
|
{
|
|
pWork.m_bContinueFly = false;
|
|
m_pWorkMan.StartWork_p2(pWork);
|
|
}
|
|
}
|
|
}
|
|
else if (Convert.ToInt32(Msg.dwParam2) == CommandID.OBJECT_LANDING)
|
|
{
|
|
if ((m_dwStates & PlayerNPCState.GP_STATE_FLY) != 0)
|
|
{
|
|
m_dwStates &= ~(uint)PlayerNPCState.GP_STATE_FLY;
|
|
|
|
if (IsDead() || m_bCandHangerOn || IsHangerOn())
|
|
ShowWing(false);
|
|
else
|
|
{
|
|
CECHPWorkFall pWork = (CECHPWorkFall)m_pWorkMan.CreateWork(CECHPWork.Host_work_ID.WORK_FREEFALL);
|
|
pWork.SetFallType(CECHPWorkFall.Fall_type.TYPE_FLYFALL);
|
|
m_pWorkMan.StartWork_p1(pWork);
|
|
}
|
|
|
|
// Below two lines will fix the "host stand in air" bug.
|
|
m_iMoveEnv = Move_environment.MOVEENV_GROUND;
|
|
m_CDRInfo.vTPNormal.Clear();
|
|
}
|
|
}
|
|
else // HOST_RUSH_FLY
|
|
{
|
|
cmd_host_rush_fly pCmd = GPDataTypeHelper.FromBytes<cmd_host_rush_fly>((byte[])Msg.dwParam1);
|
|
m_bRushFly = pCmd.is_active != 0 ? true : false;
|
|
}
|
|
}
|
|
|
|
public EC_Inventory GetPack(int iPack)
|
|
{
|
|
EC_Inventory pInventory = null;
|
|
switch (iPack)
|
|
{
|
|
case Inventory_type.IVTRTYPE_PACK: pInventory = m_pPack; break;
|
|
case Inventory_type.IVTRTYPE_EQUIPPACK: pInventory = m_pEquipPack; break;
|
|
case Inventory_type.IVTRTYPE_TASKPACK: pInventory = m_pTaskPack; break;
|
|
//case Inventory_type.IVTRTYPE_TRASHBOX: pInventory = m_pTrashBoxPack; break;
|
|
//case Inventory_type.IVTRTYPE_TRASHBOX2: pInventory = m_pTrashBoxPack2; break;
|
|
//case Inventory_type.IVTRTYPE_TRASHBOX3: pInventory = m_pTrashBoxPack3; break;
|
|
//case Inventory_type.IVTRTYPE_ACCOUNT_BOX: pInventory = m_pAccountBoxPack; break;
|
|
//case Inventory_type.IVTRTYPE_GENERALCARD_BOX: pInventory = m_pGeneralCardPack; break;
|
|
//case IVTRTYPE_PACK_CLIENT_GENERALCAR.IVTRTYPE_CLIENT_GENERALCARD_PACK: pInventory = m_pClientGenCardPack; break;
|
|
default:
|
|
return null;
|
|
}
|
|
return pInventory;
|
|
}
|
|
|
|
public int GetEquippedSuiteItem(int idSuite, ref int[] aItems)
|
|
{
|
|
int i, iItemCnt = 0;
|
|
for(i = 0; i < m_pEquipPack.GetSize(); i++)
|
|
{
|
|
var pItem = m_pEquipPack.GetItem(i);
|
|
if(pItem == null || (pItem is not EC_IvtrWeapon && pItem is not EC_IvtrArmor))
|
|
{
|
|
continue;
|
|
}
|
|
EC_IvtrEquip pEquip = (EC_IvtrEquip)pItem;
|
|
if (pEquip == null)
|
|
{
|
|
continue;
|
|
}
|
|
if(pEquip.GetSuiteID() != idSuite)
|
|
{
|
|
continue;
|
|
}
|
|
int iReason = 0;
|
|
if(!CanUseEquipment(pEquip, ref iReason))
|
|
{
|
|
continue;
|
|
}
|
|
if(pEquip.CID == (int)EC_IvtrEquip.EQUIP_CLASS_ID.ICID_GENERALCARD)
|
|
{
|
|
//TODO: Add general card Suite
|
|
}
|
|
if(aItems.Length > 0)
|
|
{
|
|
aItems[iItemCnt] = pEquip.GetTemplateID();
|
|
}
|
|
iItemCnt++;
|
|
}
|
|
return iItemCnt;
|
|
}
|
|
bool CanUseEquipment(EC_IvtrEquip pEquip, ref int piReason)
|
|
{
|
|
int iReason = 0;
|
|
if(pEquip == null)
|
|
{
|
|
iReason = 1;
|
|
goto End;
|
|
}
|
|
if(GetMaxLevelSofar() < pEquip.LevelReq||
|
|
m_ExtProps.bs.strength < pEquip.StrengthReq||
|
|
m_ExtProps.bs.agility < pEquip.AgilityReq||
|
|
m_ExtProps.bs.vitality < pEquip.VitalityReq||
|
|
m_ExtProps.bs.energy < pEquip.EnergyReq/*||
|
|
Reputation < pEquip.ReputationReq*/)//todo Add reputation check
|
|
{
|
|
iReason = 2;
|
|
goto End;
|
|
}
|
|
switch(pEquip.CID)//class id
|
|
{
|
|
case (int)EC_IvtrEquip.EQUIP_CLASS_ID.ICID_ARROW:
|
|
break;
|
|
case (int)EC_IvtrEquip.EQUIP_CLASS_ID.ICID_WING:
|
|
if (m_iProfession != (int)PROFESSION.PROF_ARCHOR && m_iProfession != (int)PROFESSION.PROF_ANGEL)
|
|
iReason = 3;
|
|
break;
|
|
case (int)EC_IvtrEquip.EQUIP_CLASS_ID.ICID_FLYSWORD:
|
|
//TODO: Add flysword check
|
|
break;
|
|
case (int)EC_IvtrEquip.EQUIP_CLASS_ID.ICID_FASHION:
|
|
//TODO: Add fashion check
|
|
break;
|
|
case (int)EC_IvtrEquip.EQUIP_CLASS_ID.ICID_ARMOR:
|
|
case (int)EC_IvtrEquip.EQUIP_CLASS_ID.ICID_DECORATION:
|
|
if ((pEquip.ProfReq & (1 << m_iProfession)) == 0)
|
|
iReason = 3;
|
|
break;
|
|
case (int)EC_IvtrEquip.EQUIP_CLASS_ID.ICID_WEAPON:
|
|
if ((pEquip.ProfReq & (1 << m_iProfession)) == 0)
|
|
iReason = 3;
|
|
else
|
|
{
|
|
//TODO: check range weapon arrow
|
|
}
|
|
break;
|
|
case (int)EC_IvtrEquip.EQUIP_CLASS_ID.ICID_GENERALCARD:
|
|
// TODO: Add general card check
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
End:
|
|
if(piReason > 0)
|
|
{
|
|
piReason = iReason;
|
|
}
|
|
return iReason == 0? true : false;
|
|
}
|
|
}
|
|
}
|