From e2c24283cccc3d20811ac85959fc3c512f7ad0c5 Mon Sep 17 00:00:00 2001 From: CuongNV <> Date: Tue, 19 May 2026 17:28:14 +0700 Subject: [PATCH 1/7] fix pos name text of matter --- .../PerfectWorld/Scripts/Objet/CECMatter.cs | 124 ++++++++++++++++-- 1 file changed, 116 insertions(+), 8 deletions(-) diff --git a/Assets/PerfectWorld/Scripts/Objet/CECMatter.cs b/Assets/PerfectWorld/Scripts/Objet/CECMatter.cs index 5776c5bd88..1a12a48c1a 100644 --- a/Assets/PerfectWorld/Scripts/Objet/CECMatter.cs +++ b/Assets/PerfectWorld/Scripts/Objet/CECMatter.cs @@ -37,6 +37,10 @@ namespace PerfectWorld.Scripts public const uint MATTER_MONEY = 3; public const uint MATTER_TYPEMASK = 0xff; + private const float MatterNameExtraWorldYOffset = 0.05f; + private const float MatterNameFallbackLocalY = 0.6f; + private const string ItemNameTextChildName = "ItemNameText"; + // Constructor / Constructor public CECMatter() { @@ -179,11 +183,24 @@ namespace PerfectWorld.Scripts var collider = matterObject.AddComponent(); //this is a workaround to fix the collider size issue when load prefab go wrong at some point //TODO: remove this workaround after the prefab load issue is fixed - Vector3 size = matterObject.GetComponentInChildren().bounds.size; - if (size.x < 0.5f) size.x = 0.5f; - if (size.y < 0.5f) size.y = 0.5f; - if (size.z < 0.5f) size.z = 0.5f; - collider.size = size; + if (TryGetCombinedRendererBounds(matterObject.transform, null, out var combinedBounds)) + { + Vector3 size = combinedBounds.size; + if (size.x < 0.5f) size.x = 0.5f; + if (size.y < 0.5f) size.y = 0.5f; + if (size.z < 0.5f) size.z = 0.5f; + collider.size = size; + collider.center = matterObject.transform.InverseTransformPoint(combinedBounds.center); + } + else + { + var firstRenderer = matterObject.GetComponentInChildren(); + Vector3 size = firstRenderer != null ? firstRenderer.bounds.size : Vector3.one; + if (size.x < 0.5f) size.x = 0.5f; + if (size.y < 0.5f) size.y = 0.5f; + if (size.z < 0.5f) size.z = 0.5f; + collider.size = size; + } } // Create text object to display item name above the cube CreateItemNameText(matterObject, Info.tid); @@ -244,18 +261,109 @@ namespace PerfectWorld.Scripts return null; } + /// + /// Merge world-space bounds of all child Renderers (MeshRenderer + SkinnedMeshRenderer). + /// Reads sharedMesh.bounds (mesh local space) and manually converts to world space — + /// same approach as PlayerVisual/NPCVisual — to avoid stale renderer.bounds after SetActive. + /// 合并所有子 Renderer 的世界包围盒(MeshRenderer + SkinnedMeshRenderer)。 + /// 直接读 sharedMesh.bounds(网格本地空间)再手动转为世界坐标,避免 SetActive 后同帧 renderer.bounds 未刷新的问题。 + /// + private static bool TryGetCombinedRendererBounds(Transform matterRoot, Transform excludeSubtree, out Bounds combinedBounds) + { + combinedBounds = default; + if (matterRoot == null) + return false; + + var renderers = matterRoot.GetComponentsInChildren(true); + bool hasAny = false; + for (int i = 0; i < renderers.Length; i++) + { + var renderer = renderers[i]; + if (renderer == null) + continue; + if (excludeSubtree != null && renderer.transform.IsChildOf(excludeSubtree)) + continue; + + Mesh mesh = null; + if (renderer is SkinnedMeshRenderer smr) + { + mesh = smr.sharedMesh; + } + else if (renderer is MeshRenderer) + { + var mf = renderer.GetComponent(); + if (mf != null) + mesh = mf.sharedMesh; + } + + if (mesh == null) + continue; + + // Manually build world-space bounds from mesh-local bounds + transform, + // identical to PlayerVisual/NPCVisual — reliable even right after SetActive(true). + // 与 PlayerVisual/NPCVisual 相同:从网格本地包围盒手动计算世界包围盒,SetActive 后同帧可靠。 + var meshBounds = mesh.bounds; + var scale = renderer.transform.lossyScale; + var worldCenter = renderer.transform.TransformPoint(meshBounds.center); + var worldSize = new Vector3( + Mathf.Abs(meshBounds.size.x * scale.x), + Mathf.Abs(meshBounds.size.y * scale.y), + Mathf.Abs(meshBounds.size.z * scale.z)); + var currentBounds = new Bounds(worldCenter, worldSize); + + Debug.Log($"[Cuong] [CECMatter] renderer={renderer.name} meshLocalCenter={meshBounds.center} meshLocalSize={meshBounds.size} worldCenter={worldCenter} worldSize={worldSize} worldMaxY={currentBounds.max.y}"); + + if (!hasAny) + { + combinedBounds = currentBounds; + hasAny = true; + } + else + { + combinedBounds.Encapsulate(currentBounds); + } + } + + return hasAny; + } + + /// + /// Name anchor at top of tallest mesh: combinedBounds.max.y in world, converted to local. + /// 名牌锚点位于最高 mesh 顶部:世界坐标 combinedBounds.max.y,再转为本地坐标。 + /// + private static bool TryGetItemNameAnchorLocal(Transform matterRoot, out Vector3 localAnchor) + { + if (!TryGetCombinedRendererBounds(matterRoot, null, out var combinedBounds)) + { + localAnchor = new Vector3(0f, MatterNameFallbackLocalY, 0f); + return false; + } + + var worldAnchor = new Vector3( + combinedBounds.center.x, + combinedBounds.max.y + MatterNameExtraWorldYOffset, + combinedBounds.center.z); + localAnchor = matterRoot.InverseTransformPoint(worldAnchor); + return true; + } + private static void CreateItemNameText(GameObject matterObject, int tid) { if (matterObject == null) return; // Avoid duplicating if prefab already contains it (or Init called twice). - if (matterObject.transform.Find("ItemNameText") != null) + if (matterObject.transform.Find(ItemNameTextChildName) != null) return; - var textObject = new GameObject("ItemNameText"); + var textObject = new GameObject(ItemNameTextChildName); textObject.transform.SetParent(matterObject.transform, false); - textObject.transform.localPosition = new Vector3(0f, 0.6f, 0f); + if (!TryGetItemNameAnchorLocal(matterObject.transform, out var localAnchor)) + { + Debug.LogWarning( + $"[Cuong] [CECMatter] No renderer bounds for '{matterObject.name}'; using fallback Y={MatterNameFallbackLocalY}"); + } + textObject.transform.localPosition = localAnchor; var textMesh = textObject.AddComponent(); From f8523004141d062baeda8ba895b54da0d5674cb0 Mon Sep 17 00:00:00 2001 From: HungDK <> Date: Tue, 19 May 2026 18:01:53 +0700 Subject: [PATCH 2/7] Fix route map smoothness --- .../Scripts/Managers/EC_HPWorkMove.cs | 6 +- .../Move/AutoPFImp/AutoMove/CMoveAgent.cs | 154 +++++++++++++++++- .../Scripts/Move/CECIntelligentRoute.cs | 41 ++++- Assets/PerfectWorld/Scripts/World/CECWorld.cs | 18 +- 4 files changed, 198 insertions(+), 21 deletions(-) diff --git a/Assets/PerfectWorld/Scripts/Managers/EC_HPWorkMove.cs b/Assets/PerfectWorld/Scripts/Managers/EC_HPWorkMove.cs index 7672d2d72a..000fb86a8b 100644 --- a/Assets/PerfectWorld/Scripts/Managers/EC_HPWorkMove.cs +++ b/Assets/PerfectWorld/Scripts/Managers/EC_HPWorkMove.cs @@ -875,11 +875,7 @@ namespace BrewMonster.Scripts vCurPos = m_pHost.m_MoveCtrl.GroundMove(m_vCurDir, fSpeed, fDeltaTime, m_pHost.m_fVertSpeed); if (!m_vCurDir.IsZero()) - { - //m_pHost.StartModelMove(m_vCurDir, GPDataTypeHelper.g_vAxisY, 100); - //m_pHost.ChangeModelTargetDirAndUp(m_vCurDir, GPDataTypeHelper.g_vAxisY); - UpdateFacingFromDelta(vCurPos); - } + OrientHostHorizontal(m_vCurDir); if (m_pHost.m_MoveCtrl.MoveBlocked() >= 3) { diff --git a/Assets/PerfectWorld/Scripts/Move/AutoPFImp/AutoMove/CMoveAgent.cs b/Assets/PerfectWorld/Scripts/Move/AutoPFImp/AutoMove/CMoveAgent.cs index 5199de3cb9..e4e37f05c1 100644 --- a/Assets/PerfectWorld/Scripts/Move/AutoPFImp/AutoMove/CMoveAgent.cs +++ b/Assets/PerfectWorld/Scripts/Move/AutoPFImp/AutoMove/CMoveAgent.cs @@ -35,6 +35,9 @@ namespace AutoMove private int m_iLayerGoal; private readonly List m_path3D = new List(1024); + private readonly List m_pathMap = new List(1024); + private int m_optimizeCurIndex; + private const int DefaultOptimizeCatchCount = 10; public bool Load(string basePathNoExt, Func resolver, Vector3? originOverride) { @@ -119,10 +122,16 @@ namespace AutoMove public int GetPathCount() => m_path3D.Count; /// - /// Matches C++ CMoveAgent::GetOptimizeCatchCount — lookahead window for path following. - /// 对应 C++:无 PathOptimizer 时为 0,FindNearest/Farthest 仅在单步索引上工作。 + /// Matches C++ CMoveAgent::GetOptimizeCatchCount (COptimizePath::m_CatchCount, default 10). /// - public int GetOptimizeCatchCount() => 0; + public int GetOptimizeCatchCount() => DefaultOptimizeCatchCount; + + public Vector3 Get2DPathNode(int index) + { + if (index < 0 || index >= m_path3D.Count) return Vector3.zero; + Vector3 v = m_path3D[index]; + return new Vector3(v.x, 0f, v.z); + } public Vector3 Get3DPathNode(int index) { @@ -130,6 +139,40 @@ namespace AutoMove return m_path3D[index]; } + /// Ported from C++ CMoveAgent::Optimize — incremental path smoothing while walking. + public bool Optimize(int moveIndex) + { + if (moveIndex < 0 || moveIndex >= m_pathMap.Count - 1) + return false; + + var layer0 = m_pMoveMap.GetLayer(0); + var rmap = layer0?.GetRMap(); + if (rmap == null) + return false; + + int from = Mathf.Max(moveIndex, m_optimizeCurIndex); + if (from >= m_pathMap.Count - 1) + return false; + + int best = from + 1; + for (int j = m_pathMap.Count - 1; j > from; j--) + { + if (IsLinePassable(rmap, m_pathMap[from], m_pathMap[j])) + { + best = j; + break; + } + } + + if (best <= from + 1) + return false; + + m_pathMap.RemoveRange(from + 1, best - from - 1); + m_path3D.RemoveRange(from + 1, best - from - 1); + m_optimizeCurIndex = from + 1; + return true; + } + public System.Collections.Generic.List GetFullPath() { return new System.Collections.Generic.List(m_path3D); @@ -293,14 +336,109 @@ namespace AutoMove } rev.Reverse(); - // Convert to world positions (y will be resolved by host terrain in movement). - // 转为世界坐标(y 由移动逻辑/地形解析)。 - for (int i = 0; i < rev.Count; i++) + var layer0 = m_pMoveMap.GetLayer(0); + var rmap = layer0?.GetRMap(); + List smoothed = rmap != null ? SmoothPathMap(rev, rmap) : rev; + + m_pathMap.Clear(); + m_path3D.Clear(); + m_optimizeCurIndex = 0; + for (int i = 0; i < smoothed.Count; i++) { - m_path3D.Add(Map2Wld(rev[i])); + m_pathMap.Add(smoothed[i]); + m_path3D.Add(Map2Wld(smoothed[i])); } } + /// String-pull on passable grid (same goal as C++ COptimizePath::SetupOptimize). + static List SmoothPathMap(List raw, CBitImage rmap) + { + if (raw == null || raw.Count <= 2) + return raw; + + var result = new List(raw.Count) { raw[0] }; + int i = 0; + while (i < raw.Count - 1) + { + int furthest = i + 1; + for (int j = raw.Count - 1; j > i; j--) + { + if (IsLinePassable(rmap, raw[i], raw[j])) + { + furthest = j; + break; + } + } + + if (furthest != i) + { + if (result[result.Count - 1] != raw[furthest]) + result.Add(raw[furthest]); + i = furthest; + } + else + { + i++; + if (result[result.Count - 1] != raw[i]) + result.Add(raw[i]); + } + } + + return result; + } + + /// Matches C++ COptimizePath::_LineTo strict diagonal checks on rmap. + static bool IsLinePassable(CBitImage rmap, Vector2Int from, Vector2Int to) + { + if (from == to) + return true; + + rmap.GetImageSize(out int w, out int h); + int x0 = from.x, y0 = from.y; + int x1 = to.x, y1 = to.y; + int dx = Mathf.Abs(x1 - x0); + int dy = Mathf.Abs(y1 - y0); + int sx = x0 < x1 ? 1 : -1; + int sy = y0 < y1 ? 1 : -1; + int err = dx - dy; + int lastX = x0, lastY = y0; + + while (true) + { + if (x0 < 0 || x0 >= w || y0 < 0 || y0 >= h || !rmap.GetPixel(x0, y0)) + return false; + + if (x0 == x1 && y0 == y1) + break; + + int e2 = err << 1; + int nextX = x0, nextY = y0; + if (e2 > -dy) + { + err -= dy; + nextX += sx; + } + if (e2 < dx) + { + err += dx; + nextY += sy; + } + + if (nextX != lastX && nextY != lastY) + { + if (!rmap.GetPixel(lastX, nextY) || !rmap.GetPixel(nextX, lastY)) + return false; + } + + x0 = nextX; + y0 = nextY; + lastX = x0; + lastY = y0; + } + + return true; + } + private static bool InBounds(Vector2Int p, int w, int h) => p.x >= 0 && p.x < w && p.y >= 0 && p.y < h; private static int Heuristic(Vector2Int a, Vector2Int b) @@ -358,6 +496,8 @@ namespace AutoMove //m_iStat = PF_STATE_UNKNOWN; m_path3D.Clear(); + m_pathMap.Clear(); + m_optimizeCurIndex = 0; } /// diff --git a/Assets/PerfectWorld/Scripts/Move/CECIntelligentRoute.cs b/Assets/PerfectWorld/Scripts/Move/CECIntelligentRoute.cs index e9d1e10c32..a9a0619740 100644 --- a/Assets/PerfectWorld/Scripts/Move/CECIntelligentRoute.cs +++ b/Assets/PerfectWorld/Scripts/Move/CECIntelligentRoute.cs @@ -376,9 +376,22 @@ namespace BrewMonster.Scripts // Determine layers (minimal: layer0 or invalid). // 确定层(最小实现:层0或无效)。 + float startDh = 0f; + float endDh = 0f; + var world = EC_Game.GetGameRun()?.GetWorld(); + if (world != null) + { + A3DVECTOR3 n = GPDataTypeHelper.g_vAxisY; + if (world.TryGetTerrainHeight(start, ref n, out float startTer)) + startDh = start.y - startTer; + n = GPDataTypeHelper.g_vAxisY; + if (world.TryGetTerrainHeight(end, ref n, out float endTer)) + endDh = end.y - endTer; + } + float dist; - int startLayer = agent.WhichLayer(new Vector3(start.x, start.y, start.z), 0.0f, out dist); - int endLayer = agent.WhichLayer(new Vector3(end.x, end.y, end.z), 0.0f, out dist); + int startLayer = agent.WhichLayer(new Vector3(start.x, start.y, start.z), startDh, out dist); + int endLayer = agent.WhichLayer(new Vector3(end.x, end.y, end.z), endDh, out dist); if (startLayer < 0) startLayer = 0; if (endLayer < 0) endLayer = 0; @@ -465,8 +478,11 @@ namespace BrewMonster.Scripts } m_iCurDest = FindNextNode(pos, m_iCurDest + 1); + agent.Optimize(m_iCurDest); + int newPathCount = agent.GetPathCount(); + if (m_iCurDest >= newPathCount) + m_iCurDest = Mathf.Max(0, newPathCount - 1); m_dist2CurDest = (GetCurDest() - pos).MagnitudeH(); - // C++ calls agent->Optimize(m_iCurDest) here; Unity CMoveAgent has no path optimizer yet. } A3DVECTOR3 GetNodePos(int iNode) @@ -489,17 +505,26 @@ namespace BrewMonster.Scripts if (agent == null) return new A3DVECTOR3(0f, 0f, 0f); Vector3 v = agent.Get3DPathNode(iNode); - // C++ adds GetTerrainHeight to Y; Unity path nodes use movemap/world height from loader. - return new A3DVECTOR3(v.x, v.y, v.z); + var pos = new A3DVECTOR3(v.x, v.y, v.z); + var world = EC_Game.GetGameRun()?.GetWorld(); + if (world != null) + { + A3DVECTOR3 n = GPDataTypeHelper.g_vAxisY; + if (world.TryGetTerrainHeight(pos, ref n, out float terY)) + pos.y = terY; + } + return pos; } A3DVECTOR3 GetNodePosXZ(int iNode) { if (IsIdle()) return new A3DVECTOR3(0f, 0f, 0f); - A3DVECTOR3 p = GetNodePosNoCheck(iNode); - p.y = 0f; - return p; + RangedMoveAgent? pCurAgent = GetCurAgent(); + if (pCurAgent == null || pCurAgent.Value.agent == null) + return new A3DVECTOR3(0f, 0f, 0f); + Vector3 v = pCurAgent.Value.agent.Get2DPathNode(iNode); + return new A3DVECTOR3(v.x, v.y, v.z); } int FindNearestNode(A3DVECTOR3 curPos, int iNodeFrom) diff --git a/Assets/PerfectWorld/Scripts/World/CECWorld.cs b/Assets/PerfectWorld/Scripts/World/CECWorld.cs index a929005c48..a7293d0eb6 100644 --- a/Assets/PerfectWorld/Scripts/World/CECWorld.cs +++ b/Assets/PerfectWorld/Scripts/World/CECWorld.cs @@ -32,9 +32,25 @@ namespace BrewMonster.Scripts.World return m_pOnmtMan; } public float GetTerrainHeight(A3DVECTOR3 vPos, ref A3DVECTOR3 pvNormal /* NULL */) + { + A3DVECTOR3 normal = pvNormal; + if (!TryGetTerrainHeight(vPos, ref normal, out float height)) + return vPos.y; + pvNormal = normal; + return height; + } + + /// False when terrain is not loaded yet (e.g. during early AutoPF Search). + public bool TryGetTerrainHeight(A3DVECTOR3 vPos, ref A3DVECTOR3 pvNormal, out float height) { A3DTerrain2 pTerrain = GetTerrain(); - return pTerrain.GetPosHeight(vPos, ref pvNormal); + if (pTerrain == null) + { + height = vPos.y; + return false; + } + height = pTerrain.GetPosHeight(vPos, ref pvNormal); + return true; } public float GetWaterHeight(A3DVECTOR3 vPos) From e1ca2dd8e3d4c97787da5605e865af107128430a Mon Sep 17 00:00:00 2001 From: HungDK <> Date: Wed, 20 May 2026 10:09:16 +0700 Subject: [PATCH 3/7] Add optimize path --- .../Move/AutoPFImp/AutoMove/CMoveAgent.cs | 294 ++++------------- .../Move/AutoPFImp/AutoMove/CMoveMap.cs | 9 +- .../Move/AutoPFImp/AutoMove/COptimizePath.cs | 298 ++++++++++++++++++ .../AutoPFImp/AutoMove/COptimizePath.cs.meta | 2 + .../Move/AutoPFImp/AutoMove/PathNode.cs | 22 ++ .../Move/AutoPFImp/AutoMove/PathNode.cs.meta | 2 + .../Scripts/Move/CECIntelligentRoute.cs | 6 +- 7 files changed, 402 insertions(+), 231 deletions(-) create mode 100644 Assets/PerfectWorld/Scripts/Move/AutoPFImp/AutoMove/COptimizePath.cs create mode 100644 Assets/PerfectWorld/Scripts/Move/AutoPFImp/AutoMove/COptimizePath.cs.meta create mode 100644 Assets/PerfectWorld/Scripts/Move/AutoPFImp/AutoMove/PathNode.cs create mode 100644 Assets/PerfectWorld/Scripts/Move/AutoPFImp/AutoMove/PathNode.cs.meta diff --git a/Assets/PerfectWorld/Scripts/Move/AutoPFImp/AutoMove/CMoveAgent.cs b/Assets/PerfectWorld/Scripts/Move/AutoPFImp/AutoMove/CMoveAgent.cs index e4e37f05c1..20fa085849 100644 --- a/Assets/PerfectWorld/Scripts/Move/AutoPFImp/AutoMove/CMoveAgent.cs +++ b/Assets/PerfectWorld/Scripts/Move/AutoPFImp/AutoMove/CMoveAgent.cs @@ -3,79 +3,59 @@ using System.Collections.Generic; using UnityEngine; // Filename : CMoveAgent.cs -// Creator : ported/simplified from C++ (AutoPFImp/AutoMoveImp/MoveAgent.*) +// Creator : ported from C++ (AutoPFImp/AutoMoveImp/MoveAgent.*) // Date : 2026/01/09 namespace AutoMove { /// - /// Minimal MoveAgent: loads movemap and provides A* path on layer0 rmap. - /// 最小 MoveAgent:加载 movemap,并在第0层 rmap 上执行 A* 路径搜索。 + /// MoveAgent: A* on layer0 rmap + COptimizePath (matches C++ CMoveAgent flow). /// public class CMoveAgent { - // Debug switch (enable temporarily while validating routes). - // 调试开关(验证寻路时可临时开启)。 - // NOTE: must not be const, otherwise Unity compiler warns about unreachable code. - // 注意:不要用 const,否则 Unity 编译会报“不可达代码”警告。 private static bool DEBUG_AUTOPF = false; + public abstract class BrushTest { - // from.y/to.y store DH in original engine. - // 原版中 from.y/to.y 存储 DH(相对地形高度差)。 public abstract bool Collide(Vector3 from, Vector3 to); } private readonly CMoveMap m_pMoveMap = new CMoveMap(); - private Vector3 m_vOriginOverride; + private COptimizePath m_pPathOptimizer; private Vector2Int m_ptStart; private Vector2Int m_ptGoal; private int m_iLayerStart; private int m_iLayerGoal; - private readonly List m_path3D = new List(1024); - private readonly List m_pathMap = new List(1024); - private int m_optimizeCurIndex; - private const int DefaultOptimizeCatchCount = 10; - public bool Load(string basePathNoExt, Func resolver, Vector3? originOverride) { - // basePathNoExt corresponds to "maps\\\\movemap\\r.._..-c.._..-l0" in C++. - // basePathNoExt 对应 C++ 的 "maps\\\\movemap\\r.._..-c.._..-l0"。 string cfgName = basePathNoExt; if (!cfgName.EndsWith(".cfg", StringComparison.OrdinalIgnoreCase)) - { cfgName += ".cfg"; - } byte[] cfgBytes = resolver(cfgName); if (cfgBytes == null) - { return false; - } - // Resolver for referenced files: in cfg, references are baseName + ".prmap" etc. - // cfg 内引用:baseName + ".prmap" 等。 bool ok = m_pMoveMap.Load(cfgBytes, resolver, basePathNoExt); if (!ok) - { return false; - } if (originOverride.HasValue) - { - m_vOriginOverride = originOverride.Value; - m_pMoveMap.SetOrigin(m_vOriginOverride); - } + m_pMoveMap.SetOrigin(originOverride.Value); + CreateOptimizer(); return true; } + void CreateOptimizer() + { + m_pPathOptimizer = new COptimizePath(); + } + public bool IsReady() { - // Original C++ requires MultiCluGraph; here we only require layer0 RMap. - // 原版 C++ 依赖 MultiCluGraph;这里最小实现只要求第0层 RMap。 var layer0 = m_pMoveMap.GetLayer(0); return layer0 != null && layer0.GetRMap() != null; } @@ -101,8 +81,6 @@ namespace AutoMove public int WhichLayer(Vector3 vWld, float dH, out float layerDist) { - // Minimal: single layer 0 if passable. - // 最小实现:如果可通行则使用单层0。 layerDist = 0.0f; var pt = TransWld2Map(vWld); var layer0 = m_pMoveMap.GetLayer(0); @@ -119,71 +97,72 @@ namespace AutoMove return true; } - public int GetPathCount() => m_path3D.Count; + public List Get2DPath() + { + return m_pPathOptimizer?.GetPath(); + } - /// - /// Matches C++ CMoveAgent::GetOptimizeCatchCount (COptimizePath::m_CatchCount, default 10). - /// - public int GetOptimizeCatchCount() => DefaultOptimizeCatchCount; + public int GetPathCount() + { + if (m_pPathOptimizer != null) + { + var path = m_pPathOptimizer.GetPath(); + if (path != null) + return path.Count; + } + return 0; + } + + public int GetOptimizeCatchCount() + { + return m_pPathOptimizer != null ? m_pPathOptimizer.GetCatchCount() : 0; + } public Vector3 Get2DPathNode(int index) { - if (index < 0 || index >= m_path3D.Count) return Vector3.zero; - Vector3 v = m_path3D[index]; - return new Vector3(v.x, 0f, v.z); + var path2d = Get2DPath(); + if (path2d == null || index < 0 || index >= path2d.Count) + return Vector3.zero; + PathNode pathNode = path2d[index]; + Vector2 ptWld = m_pMoveMap.TransMap2Wld(pathNode.ptMap.x, pathNode.ptMap.y); + return new Vector3(ptWld.x, 0f, ptWld.y); } public Vector3 Get3DPathNode(int index) { - if (index < 0 || index >= m_path3D.Count) return Vector3.zero; - return m_path3D[index]; + var path2d = Get2DPath(); + if (path2d == null || index < 0 || index >= path2d.Count) + return Vector3.zero; + PathNode pathNode = path2d[index]; + Vector2 ptWld = m_pMoveMap.TransMap2Wld(pathNode.ptMap.x, pathNode.ptMap.y); + float dh = m_pMoveMap.GetDH(pathNode.layer, (int)pathNode.ptMap.x, (int)pathNode.ptMap.y); + return new Vector3(ptWld.x, dh, ptWld.y); } - /// Ported from C++ CMoveAgent::Optimize — incremental path smoothing while walking. + /// Ported from C++ CMoveAgent::Optimize. public bool Optimize(int moveIndex) { - if (moveIndex < 0 || moveIndex >= m_pathMap.Count - 1) + if (m_pPathOptimizer == null) return false; - - var layer0 = m_pMoveMap.GetLayer(0); - var rmap = layer0?.GetRMap(); - if (rmap == null) + if (!m_pPathOptimizer.NeedOptimize(moveIndex)) return false; - - int from = Mathf.Max(moveIndex, m_optimizeCurIndex); - if (from >= m_pathMap.Count - 1) - return false; - - int best = from + 1; - for (int j = m_pathMap.Count - 1; j > from; j--) - { - if (IsLinePassable(rmap, m_pathMap[from], m_pathMap[j])) - { - best = j; - break; - } - } - - if (best <= from + 1) - return false; - - m_pathMap.RemoveRange(from + 1, best - from - 1); - m_path3D.RemoveRange(from + 1, best - from - 1); - m_optimizeCurIndex = from + 1; + m_pPathOptimizer.StepOptimize(); return true; } - public System.Collections.Generic.List GetFullPath() + public List GetFullPath() { - return new System.Collections.Generic.List(m_path3D); + var path = new List(); + var path2d = Get2DPath(); + if (path2d == null) + return path; + for (int i = 0; i < path2d.Count; i++) + path.Add(Get2DPathNode(i)); + return path; } public bool Search(int nMaxExpand = -1) { - // A* on layer0 rmap - // 在第0层 rmap 上执行 A* - m_path3D.Clear(); - var layer0 = m_pMoveMap.GetLayer(0); var rmap = layer0?.GetRMap(); if (rmap == null) return false; @@ -192,30 +171,18 @@ namespace AutoMove if (w <= 0 || h <= 0) return false; if (!InBounds(m_ptStart, w, h) || !InBounds(m_ptGoal, w, h)) - { return false; - } - // If goal or start is not passable, try to find nearest passable (original MoveAgent does this). - // 如果起点或终点不可通行,尝试寻找最近可通行点(原版 MoveAgent 会这样做)。 if (!rmap.GetPixel(m_ptStart.x, m_ptStart.y)) { if (!TryFindNearestPassable(rmap, m_ptStart, w, h, 64, out var newStart)) - { - if (DEBUG_AUTOPF) Debug.LogWarning($"[CMoveAgent] start not passable and no nearest passable found. start={m_ptStart}"); return false; - } - if (DEBUG_AUTOPF) Debug.Log($"[CMoveAgent] Adjust start {m_ptStart} -> {newStart}"); m_ptStart = newStart; } if (!rmap.GetPixel(m_ptGoal.x, m_ptGoal.y)) { if (!TryFindNearestPassable(rmap, m_ptGoal, w, h, 64, out var newGoal)) - { - if (DEBUG_AUTOPF) Debug.LogWarning($"[CMoveAgent] goal not passable and no nearest passable found. goal={m_ptGoal}"); return false; - } - if (DEBUG_AUTOPF) Debug.Log($"[CMoveAgent] Adjust goal {m_ptGoal} -> {newGoal}"); m_ptGoal = newGoal; } @@ -227,8 +194,6 @@ namespace AutoMove open.Push(m_ptStart, Heuristic(m_ptStart, m_ptGoal)); int expands = 0; - //ToDo: need use another method to caculate the value of maxExpand - //800000 is a magic number, need to be optimized int maxExpand = nMaxExpand > 0 ? nMaxExpand : 800000; while (open.Count > 0 && expands < maxExpand) @@ -271,16 +236,12 @@ namespace AutoMove if (rmap == null) return false; - // Check origin first - // 先检查原点 if (origin.x >= 0 && origin.x < w && origin.y >= 0 && origin.y < h && rmap.GetPixel(origin.x, origin.y)) { best = origin; return true; } - // Expand square rings - // 按方形“圈”扩展搜索 for (int r = 1; r <= maxRadius; r++) { int minX = Math.Max(0, origin.x - r); @@ -288,14 +249,11 @@ namespace AutoMove int minY = Math.Max(0, origin.y - r); int maxY = Math.Min(h - 1, origin.y + r); - // Top/bottom edges for (int x = minX; x <= maxX; x++) { TryConsider(x, minY); TryConsider(x, maxY); } - - // Left/right edges (excluding corners already checked) for (int y = minY + 1; y <= maxY - 1; y++) { TryConsider(minX, y); @@ -328,123 +286,35 @@ namespace AutoMove private void ReconstructPath(Dictionary cameFrom, Vector2Int cur) { - List rev = new List(1024) { cur }; + var rev = new List(1024) { cur }; while (cameFrom.TryGetValue(cur, out var prev)) { cur = prev; rev.Add(cur); } - rev.Reverse(); - var layer0 = m_pMoveMap.GetLayer(0); - var rmap = layer0?.GetRMap(); - List smoothed = rmap != null ? SmoothPathMap(rev, rmap) : rev; - m_pathMap.Clear(); - m_path3D.Clear(); - m_optimizeCurIndex = 0; - for (int i = 0; i < smoothed.Count; i++) + var initPath = new List(rev.Count); + for (int i = 0; i < rev.Count; i++) { - m_pathMap.Add(smoothed[i]); - m_path3D.Add(Map2Wld(smoothed[i])); - } - } - - /// String-pull on passable grid (same goal as C++ COptimizePath::SetupOptimize). - static List SmoothPathMap(List raw, CBitImage rmap) - { - if (raw == null || raw.Count <= 2) - return raw; - - var result = new List(raw.Count) { raw[0] }; - int i = 0; - while (i < raw.Count - 1) - { - int furthest = i + 1; - for (int j = raw.Count - 1; j > i; j--) + initPath.Add(new PathNode { - if (IsLinePassable(rmap, raw[i], raw[j])) - { - furthest = j; - break; - } - } - - if (furthest != i) - { - if (result[result.Count - 1] != raw[furthest]) - result.Add(raw[furthest]); - i = furthest; - } - else - { - i++; - if (result[result.Count - 1] != raw[i]) - result.Add(raw[i]); - } + ptMap = new Vector2(rev[i].x, rev[i].y), + layer = m_iLayerStart, + }); } - return result; - } + if (m_pPathOptimizer == null) + CreateOptimizer(); - /// Matches C++ COptimizePath::_LineTo strict diagonal checks on rmap. - static bool IsLinePassable(CBitImage rmap, Vector2Int from, Vector2Int to) - { - if (from == to) - return true; - - rmap.GetImageSize(out int w, out int h); - int x0 = from.x, y0 = from.y; - int x1 = to.x, y1 = to.y; - int dx = Mathf.Abs(x1 - x0); - int dy = Mathf.Abs(y1 - y0); - int sx = x0 < x1 ? 1 : -1; - int sy = y0 < y1 ? 1 : -1; - int err = dx - dy; - int lastX = x0, lastY = y0; - - while (true) - { - if (x0 < 0 || x0 >= w || y0 < 0 || y0 >= h || !rmap.GetPixel(x0, y0)) - return false; - - if (x0 == x1 && y0 == y1) - break; - - int e2 = err << 1; - int nextX = x0, nextY = y0; - if (e2 > -dy) - { - err -= dy; - nextX += sx; - } - if (e2 < dx) - { - err += dx; - nextY += sy; - } - - if (nextX != lastX && nextY != lastY) - { - if (!rmap.GetPixel(lastX, nextY) || !rmap.GetPixel(nextX, lastY)) - return false; - } - - x0 = nextX; - y0 = nextY; - lastX = x0; - lastY = y0; - } - - return true; + // C++: m_pPathOptimizer->SetupOptimize(GetMoveMap(), path); + m_pPathOptimizer.SetupOptimize(m_pMoveMap, initPath); } private static bool InBounds(Vector2Int p, int w, int h) => p.x >= 0 && p.x < w && p.y >= 0 && p.y < h; private static int Heuristic(Vector2Int a, Vector2Int b) { - // Octile distance * 10 - // 八方向启发式(octile)*10 int dx = Mathf.Abs(a.x - b.x); int dy = Mathf.Abs(a.y - b.y); int min = Math.Min(dx, dy); @@ -454,8 +324,6 @@ namespace AutoMove private static int Cost(Vector2Int a, Vector2Int b) { - // Diagonal=14, straight=10 - // 斜向=14,直向=10 int dx = Mathf.Abs(a.x - b.x); int dy = Mathf.Abs(a.y - b.y); return (dx + dy == 2) ? 14 : 10; @@ -475,35 +343,13 @@ namespace AutoMove public void ResetSearch() { - // Çå³ýµ±Ç°ËÑË÷״̬ - //if (m_iStat == PF_STATE_UNKNOWN) - //{ - // return; - //} - //if (m_pPfAlg) - //{ - // m_pPfAlg->Reset(); - //} - //if (m_pPathOptimizer) - //{ - // m_pPathOptimizer.Reset(); - //} - m_ptStart.x = m_ptStart.y = 0; + m_ptStart = Vector2Int.zero; m_iLayerStart = -1; - m_ptGoal.x = m_ptGoal.y = 0; + m_ptGoal = Vector2Int.zero; m_iLayerGoal = -1; - //m_pBrushTest = null; - - //m_iStat = PF_STATE_UNKNOWN; - m_path3D.Clear(); - m_pathMap.Clear(); - m_optimizeCurIndex = 0; + m_pPathOptimizer?.Reset(); } - /// - /// Very small min-heap for A*. - /// A* 用的小型最小堆。 - /// private sealed class MinHeap { private struct Node @@ -559,5 +405,3 @@ namespace AutoMove } } } - - diff --git a/Assets/PerfectWorld/Scripts/Move/AutoPFImp/AutoMove/CMoveMap.cs b/Assets/PerfectWorld/Scripts/Move/AutoPFImp/AutoMove/CMoveMap.cs index 70c4441a47..d4a8aa0139 100644 --- a/Assets/PerfectWorld/Scripts/Move/AutoPFImp/AutoMove/CMoveMap.cs +++ b/Assets/PerfectWorld/Scripts/Move/AutoPFImp/AutoMove/CMoveMap.cs @@ -56,8 +56,13 @@ namespace AutoMove public Vector2 TransMap2Wld(int x, int y) { - float wx = m_vOrigin.x + (x + 0.5f) * m_fPixelSize; - float wz = m_vOrigin.z + (y + 0.5f) * m_fPixelSize; + return TransMap2Wld((float)x, (float)y); + } + + public Vector2 TransMap2Wld(float mapX, float mapY) + { + float wx = m_vOrigin.x + (mapX + 0.5f) * m_fPixelSize; + float wz = m_vOrigin.z + (mapY + 0.5f) * m_fPixelSize; return new Vector2(wx, wz); } diff --git a/Assets/PerfectWorld/Scripts/Move/AutoPFImp/AutoMove/COptimizePath.cs b/Assets/PerfectWorld/Scripts/Move/AutoPFImp/AutoMove/COptimizePath.cs new file mode 100644 index 0000000000..e75975f261 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Move/AutoPFImp/AutoMove/COptimizePath.cs @@ -0,0 +1,298 @@ +using System.Collections.Generic; +using UnityEngine; + +// Ported from C++ OptimizePath.h / OptimizePath.cpp (Kui Wu, 2007). + +namespace AutoMove +{ + /// Line sampler for map-space path optimization (C++ CLine). + sealed class CLine + { + Vector2 m_from; + Vector2 m_dir; + int m_count; + + public void Init(Vector2 from, Vector2 dir) + { + m_from = from; + m_dir = dir; + float len = m_dir.magnitude; + if (len > 1e-8f) + { + m_dir.x /= len; + m_dir.y /= len; + } + m_count = 0; + } + + public void Init(Vector2 from, int dirX, int dirY) + { + Init(from, new Vector2(dirX, dirY)); + } + + public Vector2 Next() + { + m_count++; + return new Vector2(m_from.x + m_dir.x * m_count, m_from.y + m_dir.y * m_count); + } + + public int GetCount() => m_count; + public Vector2 GetFrom() => m_from; + public void Reset() => m_count = 0; + } + + /// Path optimizer — port of C++ COptimizePath. + public sealed class COptimizePath + { + const int LookAhead = 60; + const int LookStep = 3; + + CMoveMap m_pMoveMap; + int m_mapWidth; + int m_mapHeight; + readonly Dictionary m_lookUp = new Dictionary(4096); + readonly List m_path = new List(1024); + int m_curIndex = -1; + int m_catchCount = 10; + int m_curLayer = -1; + + public void Reset() + { + m_pMoveMap = null; + m_mapWidth = 0; + m_mapHeight = 0; + m_lookUp.Clear(); + m_path.Clear(); + m_curIndex = -1; + m_curLayer = -1; + } + + public int GetCatchCount() => m_catchCount; + + public List GetPath() => m_path; + + public bool NeedOptimize(int moveIndex) + { + if (m_curIndex < m_path.Count + && moveIndex < m_path.Count + && m_curIndex - moveIndex < m_catchCount) + { + if (moveIndex > m_curIndex) + { + SetFootprintRange(m_curIndex, moveIndex - 1, 0); + m_curIndex = moveIndex; + } + return true; + } + return false; + } + + public void SetupOptimize(CMoveMap pMoveMap, List initPath, int catchCount = 10) + { + m_path.Clear(); + if (initPath != null && initPath.Count > 0) + m_path.AddRange(initPath); + + m_pMoveMap = pMoveMap; + m_mapWidth = pMoveMap != null ? pMoveMap.GetMapWidth() : 0; + m_mapHeight = pMoveMap != null ? pMoveMap.GetMapLength() : 0; + m_curIndex = 0; + m_catchCount = catchCount; + m_curLayer = -1; + + if (m_path.Count == 0) + return; + + CheckLayer(); + } + + public void StepOptimize() + { + CheckLayer(); + int step = m_catchCount * 2; + if (step <= 0) + return; + + int i = 0; + while (i < step && m_curIndex < m_path.Count) + { + LocalOptimize(); + i++; + m_curIndex++; + } + } + + void CheckLayer() + { + if (m_path.Count == 0 || m_curIndex < 0 || m_curIndex >= m_path.Count) + return; + + if (m_path[m_curIndex].layer == m_curLayer) + return; + + m_curLayer = m_path[m_curIndex].layer; + m_lookUp.Clear(); + + int i = m_curIndex; + while (i < m_path.Count && m_path[i].layer == m_curLayer) + { + SetFootprint(m_path[i].ptMap, (short)(i + 1)); + i++; + } + } + + void LocalOptimize() + { + int toIndex = Mathf.Min(m_curIndex + LookAhead, m_path.Count - 1); + var line = new CLine(); + Vector2 dir; + int newCount = -1; + + while (toIndex - m_curIndex > LookStep) + { + if (GetFootprint(m_path[toIndex].ptMap) == 0) + { + toIndex -= LookStep; + continue; + } + if (m_path[toIndex].layer != m_curLayer) + { + toIndex -= LookStep; + continue; + } + + dir = m_path[toIndex].ptMap - m_path[m_curIndex].ptMap; + if ((int)dir.x == 0 && (int)dir.y == 0) + { + PathIntersect(toIndex); + return; + } + + line.Init(m_path[m_curIndex].ptMap, dir); + if (LineTo(line, m_path[toIndex].ptMap)) + { + newCount = line.GetCount(); + break; + } + toIndex -= LookStep; + } + + if (newCount > 0) + { + line.Reset(); + AddPathPortion(line, toIndex - m_curIndex, newCount); + } + } + + CBitImage GetRMap(int layer) + { + if (m_pMoveMap == null) + return null; + var layerMap = m_pMoveMap.GetLayer(layer); + return layerMap?.GetRMap(); + } + + bool LineTo(CLine line, Vector2 to) + { + int toX = (int)to.x; + int toY = (int)to.y; + int curX = (int)line.GetFrom().x; + int curY = (int)line.GetFrom().y; + + CBitImage pRMap = GetRMap(m_curLayer); + if (pRMap == null) + return false; + + int lastX = curX; + int lastY = curY; + + while (curX != toX || curY != toY) + { + Vector2 cur = line.Next(); + curX = (int)cur.x; + curY = (int)cur.y; + + if (!pRMap.GetPixel(curX, curY)) + return false; + + bool bNeighbor1 = pRMap.GetPixel(lastX, curY); + bool bNeighbor2 = pRMap.GetPixel(curX, lastY); + + if (toX == lastX && toY == curY) + break; + if (toX == curX && toY == lastY) + break; + + if (curX != lastX && curY != lastY && (!bNeighbor1 || !bNeighbor2)) + return false; + + lastX = curX; + lastY = curY; + } + + return true; + } + + void PathIntersect(int toIndex) + { + SetFootprintRange(m_curIndex + 1, toIndex, 0); + int removeCount = toIndex - m_curIndex; + if (removeCount > 0 && m_curIndex + 1 < m_path.Count) + m_path.RemoveRange(m_curIndex + 1, removeCount); + } + + void AddPathPortion(CLine line, int oldCount, int newCount) + { + SetFootprintRange(m_curIndex + 1, m_curIndex + oldCount, 0); + + if (oldCount > newCount) + { + m_path.RemoveRange(m_curIndex + 1, oldCount - newCount); + } + else if (oldCount < newCount) + { + int insertCount = newCount - oldCount; + for (int k = 0; k < insertCount; k++) + m_path.Insert(m_curIndex + 1, new PathNode()); + } + + int index = m_curIndex + 1; + while (line.GetCount() < newCount && index < m_path.Count) + { + var node = m_path[index]; + node.ptMap = line.Next(); + node.layer = m_curLayer; + m_path[index] = node; + index++; + } + + SetFootprintRange(m_curIndex + 1, m_curIndex + newCount, 1); + } + + int GetLookUpKey(int x, int y) => y * m_mapWidth + x; + + int GetFootprint(int x, int y) + { + return m_lookUp.TryGetValue(GetLookUpKey(x, y), out short v) ? v : 0; + } + + int GetFootprint(Vector2 pt) => GetFootprint((int)pt.x, (int)pt.y); + + void SetFootprint(int x, int y, int val) + { + int key = GetLookUpKey(x, y); + if (val == 0) + m_lookUp.Remove(key); + else + m_lookUp[key] = (short)val; + } + + void SetFootprint(Vector2 pt, int val) => SetFootprint((int)pt.x, (int)pt.y, val); + + void SetFootprintRange(int fromIndex, int toIndex, int val) + { + for (int i = fromIndex; i <= toIndex && i < m_path.Count; ++i) + SetFootprint(m_path[i].ptMap, val); + } + } +} diff --git a/Assets/PerfectWorld/Scripts/Move/AutoPFImp/AutoMove/COptimizePath.cs.meta b/Assets/PerfectWorld/Scripts/Move/AutoPFImp/AutoMove/COptimizePath.cs.meta new file mode 100644 index 0000000000..86d0edecc0 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Move/AutoPFImp/AutoMove/COptimizePath.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 96e45cded56a0bc4188f329f2cf3b676 \ No newline at end of file diff --git a/Assets/PerfectWorld/Scripts/Move/AutoPFImp/AutoMove/PathNode.cs b/Assets/PerfectWorld/Scripts/Move/AutoPFImp/AutoMove/PathNode.cs new file mode 100644 index 0000000000..885435e2d2 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Move/AutoPFImp/AutoMove/PathNode.cs @@ -0,0 +1,22 @@ +using UnityEngine; + +namespace AutoMove +{ + /// Matches C++ AutoMove::PathNode (PfConstant.h). + public struct PathNode + { + public Vector2 ptMap; + public int layer; + + public Vector2Int GetPtI() + { + return new Vector2Int(Mathf.FloorToInt(ptMap.x), Mathf.FloorToInt(ptMap.y)); + } + + public void SetI(Vector2Int pt, int iLayer) + { + ptMap = new Vector2(pt.x, pt.y); + layer = iLayer; + } + } +} diff --git a/Assets/PerfectWorld/Scripts/Move/AutoPFImp/AutoMove/PathNode.cs.meta b/Assets/PerfectWorld/Scripts/Move/AutoPFImp/AutoMove/PathNode.cs.meta new file mode 100644 index 0000000000..0cf163da7d --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Move/AutoPFImp/AutoMove/PathNode.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 98c1e0dc325b8e64bbda287a0128aeed \ No newline at end of file diff --git a/Assets/PerfectWorld/Scripts/Move/CECIntelligentRoute.cs b/Assets/PerfectWorld/Scripts/Move/CECIntelligentRoute.cs index a9a0619740..a772cd9a43 100644 --- a/Assets/PerfectWorld/Scripts/Move/CECIntelligentRoute.cs +++ b/Assets/PerfectWorld/Scripts/Move/CECIntelligentRoute.cs @@ -19,7 +19,7 @@ namespace BrewMonster.Scripts /// public sealed class CECIntelligentRoute { - private const bool DEBUG_AUTOPF = true; + private const bool DEBUG_AUTOPF = false; /// cos(5°) for FindFarthestNode — same as C++ EC_IntelligentRoute.cpp. private static readonly float s_cos5Deg = Mathf.Cos(5f * Mathf.PI / 180f); public enum SearchResult @@ -478,10 +478,8 @@ namespace BrewMonster.Scripts } m_iCurDest = FindNextNode(pos, m_iCurDest + 1); + m_dist2CurDest = (GetCurDest() - pos).MagnitudeH(); agent.Optimize(m_iCurDest); - int newPathCount = agent.GetPathCount(); - if (m_iCurDest >= newPathCount) - m_iCurDest = Mathf.Max(0, newPathCount - 1); m_dist2CurDest = (GetCurDest() - pos).MagnitudeH(); } From 3e545f026d2624066df203d5343677235fb1a77b Mon Sep 17 00:00:00 2001 From: CuongNV <> Date: Wed, 20 May 2026 14:01:38 +0700 Subject: [PATCH 4/7] fix pos textname for NPC --- Assets/PerfectWorld/Scripts/NPC/NPCVisual.cs | 53 +++++++++++++++++++ .../UI/GamePlay/NameplateWorldAnchor.cs | 37 +++++++++++++ .../PerfectWorld/Scripts/UI/GamePlay/UINPC.cs | 22 ++++++++ 3 files changed, 112 insertions(+) diff --git a/Assets/PerfectWorld/Scripts/NPC/NPCVisual.cs b/Assets/PerfectWorld/Scripts/NPC/NPCVisual.cs index 769a82f63a..6a544d802f 100644 --- a/Assets/PerfectWorld/Scripts/NPC/NPCVisual.cs +++ b/Assets/PerfectWorld/Scripts/NPC/NPCVisual.cs @@ -151,6 +151,59 @@ public class NPCVisual : MonoBehaviour } } + // Cached reference to the bone with the highest world-Y position at model-ready time. + // 模型就绪时世界Y最高的骨骼缓存引用。 + private Transform _cachedTopBone; + + /// + /// Scan all SkinnedMeshRenderer bones, find the one with the highest world-Y, and cache it. + /// Call once after the model and its first animation frame are ready (e.g. from UINPC coroutine). + /// 扫描所有SkinnedMeshRenderer骨骼,找到世界Y最高者并缓存。 + /// 在模型及首帧动画就绪后调用一次(例如从UINPC协程中调用)。 + /// + public void CacheTopBone() + { + var smrs = GetComponentsInChildren(true); + var seen = new HashSet(); + Transform highest = null; + float highestY = float.MinValue; + foreach (var smr in smrs) + { + if (smr == null || smr.bones == null) + continue; + foreach (var bone in smr.bones) + { + if (bone == null || !seen.Add(bone)) + continue; + float y = bone.position.y; + if (y > highestY) + { + highestY = y; + highest = bone; + } + } + } + _cachedTopBone = highest; + if (debugNamePlateBounds && _cachedTopBone != null) + Debug.Log($"[Cuong] [NPCVisual] CacheTopBone: topBone={_cachedTopBone.name} worldY={highestY:F3}"); + } + + /// + /// Return the world position of the cached top bone (set by ). + /// Returns false if no bone has been cached yet. + /// 返回缓存的最高骨骼世界坐标。若尚未缓存则返回false。 + /// + public bool TryGetTopBoneWorld(out Vector3 worldPos) + { + if (_cachedTopBone == null) + { + worldPos = default; + return false; + } + worldPos = _cachedTopBone.position; + return true; + } + /// /// Resolve world anchor from merged bounds of all SkinnedMeshRenderers. /// 从所有SkinnedMeshRenderer合并后的包围盒解析世界锚点。 diff --git a/Assets/PerfectWorld/Scripts/UI/GamePlay/NameplateWorldAnchor.cs b/Assets/PerfectWorld/Scripts/UI/GamePlay/NameplateWorldAnchor.cs index 8f244469a5..82e1f5b73a 100644 --- a/Assets/PerfectWorld/Scripts/UI/GamePlay/NameplateWorldAnchor.cs +++ b/Assets/PerfectWorld/Scripts/UI/GamePlay/NameplateWorldAnchor.cs @@ -26,6 +26,9 @@ namespace BrewMonster [SerializeField] private bool applyMaleHeadWorldOffsetForAabbOnly = true; [SerializeField] private float maleHeadWorldOffset = 0.1f; + [Header("NPC — skeleton hook")] + [SerializeField] private bool useNpcHeadHook = true; + [Header("NPC — SMR / root fallback")] [SerializeField] private bool preferVisualBoundsFallback = true; [SerializeField] private float fallbackHeightAboveRoot = 2f; @@ -55,6 +58,17 @@ namespace BrewMonster npcVisual = hostNpc.GetComponent(); } + /// + /// Trigger a scan of all NPC skeleton bones to cache the highest-Y bone. + /// Call from once the NPC model and its animation are ready. + /// 触发扫描NPC所有骨骼以缓存世界Y最高的骨骼。在NPC模型及动画就绪后由UINPC调用一次。 + /// + public void RefreshNpcTopBone() + { + CacheRefs(); + npcVisual?.CacheTopBone(); + } + /// /// Resolve world position for the nameplate canvas root. Call once at init or every frame if the plate should follow the host. /// 解析名牌Canvas根的世界坐标。仅在初始化时调用一次,或若名牌需跟随宿主则每帧调用。 @@ -119,6 +133,29 @@ namespace BrewMonster if (hostNpc == null) return false; + // Priority 1: skeleton hook (follows animation every frame — fixes floating/flying NPCs like 小星星). + // 优先级1:骨骼挂点(每帧跟随动画,修复小星星等浮空动画中名牌与模型重叠的问题)。 + if (useNpcHeadHook) + { + var headHook = hostNpc.GetHook(headHookName); + if (headHook != null) + { + worldPos = headHook.position + Vector3.up * (headHookWorldOffset + extraWorldYOffset); + usedSkinnedMeshMergedBounds = true; // treated as "precise anchor" so UINPC coroutine exits early + return true; + } + } + + // Priority 2: cached top bone — bone with highest world-Y found at model-ready time. + // Follows animation per-frame via UINPC.LateUpdate without needing a specific bone name. + // 优先级2:缓存的最高骨骼——模型就绪时世界Y最高的骨骼,每帧跟随动画,无需特定骨骼名。 + if (npcVisual != null && npcVisual.TryGetTopBoneWorld(out worldPos)) + { + usedSkinnedMeshMergedBounds = true; + worldPos.y += extraWorldYOffset; + return true; + } + if (preferVisualBoundsFallback && npcVisual != null && npcVisual.TryGetNamePlateAnchorWorld(out worldPos)) diff --git a/Assets/PerfectWorld/Scripts/UI/GamePlay/UINPC.cs b/Assets/PerfectWorld/Scripts/UI/GamePlay/UINPC.cs index eb6b7165b7..dbadd51e4e 100644 --- a/Assets/PerfectWorld/Scripts/UI/GamePlay/UINPC.cs +++ b/Assets/PerfectWorld/Scripts/UI/GamePlay/UINPC.cs @@ -63,6 +63,22 @@ namespace BrewMonster _initialNameplateRoutine = StartCoroutine(CoApplyInitialNameplateDeferred(maxFrames: 120)); } + /// + /// Update nameplate position every frame so it follows skeleton animations (e.g. floating/flying NPCs or monsters). + /// Only active after the initial anchor coroutine has resolved (i.e. _initialNameplateRoutine == null). + /// 每帧更新名牌位置,使其跟随骨骼动画(如浮空/飞行的NPC或怪物,例如小星星)。 + /// 仅在初始锚点协程完成后(_initialNameplateRoutine == null)生效。 + /// + private void LateUpdate() + { + if (_initialNameplateRoutine != null) + return; + if (_canvasRoot == null || _nameplateAnchor == null) + return; + if (_nameplateAnchor.TryGetWorldPosition(out var worldPos)) + ApplyCanvasRootLocalPosition(worldPos); + } + /// /// Call after NPC model async load (e.g. ) so SMR bounds exist before anchoring. /// 在NPC模型异步加载完成后调用(如 ),以便SMR包围盒已存在再定位名牌。 @@ -107,12 +123,17 @@ namespace BrewMonster && fromSmr) { ApplyCanvasRootLocalPosition(worldPos); + // Model + SMR ready: scan bones and cache the highest one for per-frame LateUpdate tracking. + // 模型和SMR就绪:扫描骨骼并缓存最高骨骼,供LateUpdate每帧跟踪。 + _nameplateAnchor.RefreshNpcTopBone(); _initialNameplateRoutine = null; yield break; } yield return null; } + // Timeout: set position from whatever is available (hook / SMR / root fallback), then still cache top bone. + // 超时:使用当前可用方式设置位置(挂点/SMR/根节点回退),仍然缓存最高骨骼。 if (_nameplateAnchor.TryGetWorldPosition(out var finalPos, out _, logNpcRootFallback: true)) { ApplyCanvasRootLocalPosition(finalPos); @@ -125,6 +146,7 @@ namespace BrewMonster "NPC with SMR bounds: worldPos = (combinedBounds.center.x, combinedBounds.max.y, combinedBounds.center.z) + Vector3.up * extraWorldYOffset. " + "NPC fallback: worldPos = hostNpc.transform.position + Vector3.up * (fallbackHeightAboveRoot + extraWorldYOffset)."); } + _nameplateAnchor.RefreshNpcTopBone(); _initialNameplateRoutine = null; } From 34c5094480609331616cfde62b6a6a6fcf8f4aab Mon Sep 17 00:00:00 2001 From: CuongNV <> Date: Wed, 20 May 2026 14:17:47 +0700 Subject: [PATCH 5/7] fix InventoryUI bug seen throught --- Assets/Prefabs/UI/InventoryUI.prefab | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Assets/Prefabs/UI/InventoryUI.prefab b/Assets/Prefabs/UI/InventoryUI.prefab index 4e18605df5..13f4fcf9a2 100644 --- a/Assets/Prefabs/UI/InventoryUI.prefab +++ b/Assets/Prefabs/UI/InventoryUI.prefab @@ -11419,9 +11419,9 @@ MonoBehaviour: m_EditorClassIdentifier: m_Material: {fileID: 0} m_Color: {r: 1, g: 1, b: 1, a: 1} - m_RaycastTarget: 1 + m_RaycastTarget: 0 m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} - m_Maskable: 1 + m_Maskable: 0 m_OnCullStateChanged: m_PersistentCalls: m_Calls: [] @@ -17603,7 +17603,7 @@ GameObject: - component: {fileID: 9178171791072353269} - component: {fileID: 7628333317866785984} m_Layer: 5 - m_Name: GameObject + m_Name: View m_TagString: Untagged m_Icon: {fileID: 0} m_NavMeshLayer: 0 @@ -17650,7 +17650,7 @@ MonoBehaviour: m_EditorClassIdentifier: m_Material: {fileID: 0} m_Color: {r: 1, g: 1, b: 1, a: 1} - m_RaycastTarget: 1 + m_RaycastTarget: 0 m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_Maskable: 1 m_OnCullStateChanged: From 1c53da5ce87c8edd9133012f484507570241f0f6 Mon Sep 17 00:00:00 2001 From: VuNgocHaiC7 Date: Wed, 20 May 2026 14:36:50 +0700 Subject: [PATCH 6/7] fix some ui --- .../Asssets/Panel/bg che tao.png.meta | 41 ++++++++++++++++++- .../Asssets/Panel/khung tui do mini.png.meta | 41 ++++++++++++++++++- .../UI/Inventory/khung nd tuido.png.meta | 28 ++++++++++++- .../UI/NPC/Uninstall/khung nd duclo.png.meta | 28 ++++++++++++- .../Resources/UI/NPCShop/khungnd1.png.meta | 28 ++++++++++++- .../UI/Notification/khung thongbao.png.meta | 28 ++++++++++++- .../Resources/UI/Skill/khungnd1.png.meta | 28 ++++++++++++- .../Resources/UI/Team/khung caidat.png.meta | 28 ++++++++++++- .../Scripts/UI/Dialogs/DlgPetDetail.cs | 10 ++--- Assets/PerfectWorld/UI/Pet/DlgPetList.prefab | 16 ++++---- 10 files changed, 256 insertions(+), 20 deletions(-) diff --git a/Assets/PerfectWorld/Asssets/Panel/bg che tao.png.meta b/Assets/PerfectWorld/Asssets/Panel/bg che tao.png.meta index ae27c44059..cfdc40f45d 100644 --- a/Assets/PerfectWorld/Asssets/Panel/bg che tao.png.meta +++ b/Assets/PerfectWorld/Asssets/Panel/bg che tao.png.meta @@ -69,7 +69,7 @@ TextureImporter: platformSettings: - serializedVersion: 4 buildTarget: DefaultTexturePlatform - maxTextureSize: 2048 + maxTextureSize: 512 resizeAlgorithm: 0 textureFormat: -1 textureCompression: 1 @@ -93,6 +93,45 @@ TextureImporter: ignorePlatformSupport: 0 androidETC2FallbackOverride: 0 forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: WebGL + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: WindowsStoreApps + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 spriteSheet: serializedVersion: 2 sprites: [] diff --git a/Assets/PerfectWorld/Asssets/Panel/khung tui do mini.png.meta b/Assets/PerfectWorld/Asssets/Panel/khung tui do mini.png.meta index badb9b80d1..b663a612c7 100644 --- a/Assets/PerfectWorld/Asssets/Panel/khung tui do mini.png.meta +++ b/Assets/PerfectWorld/Asssets/Panel/khung tui do mini.png.meta @@ -69,7 +69,7 @@ TextureImporter: platformSettings: - serializedVersion: 4 buildTarget: DefaultTexturePlatform - maxTextureSize: 2048 + maxTextureSize: 512 resizeAlgorithm: 0 textureFormat: -1 textureCompression: 1 @@ -93,6 +93,45 @@ TextureImporter: ignorePlatformSupport: 0 androidETC2FallbackOverride: 0 forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: WebGL + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: WindowsStoreApps + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 spriteSheet: serializedVersion: 2 sprites: [] diff --git a/Assets/PerfectWorld/Resources/UI/Inventory/khung nd tuido.png.meta b/Assets/PerfectWorld/Resources/UI/Inventory/khung nd tuido.png.meta index 25c9482fe2..e64e11dfb6 100644 --- a/Assets/PerfectWorld/Resources/UI/Inventory/khung nd tuido.png.meta +++ b/Assets/PerfectWorld/Resources/UI/Inventory/khung nd tuido.png.meta @@ -69,7 +69,7 @@ TextureImporter: platformSettings: - serializedVersion: 4 buildTarget: DefaultTexturePlatform - maxTextureSize: 2048 + maxTextureSize: 512 resizeAlgorithm: 0 textureFormat: -1 textureCompression: 1 @@ -106,6 +106,32 @@ TextureImporter: ignorePlatformSupport: 0 androidETC2FallbackOverride: 0 forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: WebGL + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: WindowsStoreApps + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 spriteSheet: serializedVersion: 2 sprites: [] diff --git a/Assets/PerfectWorld/Resources/UI/NPC/Uninstall/khung nd duclo.png.meta b/Assets/PerfectWorld/Resources/UI/NPC/Uninstall/khung nd duclo.png.meta index 0f43414fb9..a5164a3b20 100644 --- a/Assets/PerfectWorld/Resources/UI/NPC/Uninstall/khung nd duclo.png.meta +++ b/Assets/PerfectWorld/Resources/UI/NPC/Uninstall/khung nd duclo.png.meta @@ -69,7 +69,7 @@ TextureImporter: platformSettings: - serializedVersion: 4 buildTarget: DefaultTexturePlatform - maxTextureSize: 2048 + maxTextureSize: 512 resizeAlgorithm: 0 textureFormat: -1 textureCompression: 1 @@ -106,6 +106,32 @@ TextureImporter: ignorePlatformSupport: 0 androidETC2FallbackOverride: 0 forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: WebGL + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: WindowsStoreApps + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 spriteSheet: serializedVersion: 2 sprites: [] diff --git a/Assets/PerfectWorld/Resources/UI/NPCShop/khungnd1.png.meta b/Assets/PerfectWorld/Resources/UI/NPCShop/khungnd1.png.meta index 9a4967f671..79a6bbb4f5 100644 --- a/Assets/PerfectWorld/Resources/UI/NPCShop/khungnd1.png.meta +++ b/Assets/PerfectWorld/Resources/UI/NPCShop/khungnd1.png.meta @@ -69,7 +69,7 @@ TextureImporter: platformSettings: - serializedVersion: 4 buildTarget: DefaultTexturePlatform - maxTextureSize: 2048 + maxTextureSize: 512 resizeAlgorithm: 0 textureFormat: -1 textureCompression: 1 @@ -106,6 +106,32 @@ TextureImporter: ignorePlatformSupport: 0 androidETC2FallbackOverride: 0 forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: WebGL + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: WindowsStoreApps + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 spriteSheet: serializedVersion: 2 sprites: [] diff --git a/Assets/PerfectWorld/Resources/UI/Notification/khung thongbao.png.meta b/Assets/PerfectWorld/Resources/UI/Notification/khung thongbao.png.meta index d6405ebce2..0fd9969290 100644 --- a/Assets/PerfectWorld/Resources/UI/Notification/khung thongbao.png.meta +++ b/Assets/PerfectWorld/Resources/UI/Notification/khung thongbao.png.meta @@ -69,7 +69,7 @@ TextureImporter: platformSettings: - serializedVersion: 4 buildTarget: DefaultTexturePlatform - maxTextureSize: 2048 + maxTextureSize: 512 resizeAlgorithm: 0 textureFormat: -1 textureCompression: 1 @@ -106,6 +106,32 @@ TextureImporter: ignorePlatformSupport: 0 androidETC2FallbackOverride: 0 forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: WebGL + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: WindowsStoreApps + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 spriteSheet: serializedVersion: 2 sprites: [] diff --git a/Assets/PerfectWorld/Resources/UI/Skill/khungnd1.png.meta b/Assets/PerfectWorld/Resources/UI/Skill/khungnd1.png.meta index d9ba7f3351..ce17558531 100644 --- a/Assets/PerfectWorld/Resources/UI/Skill/khungnd1.png.meta +++ b/Assets/PerfectWorld/Resources/UI/Skill/khungnd1.png.meta @@ -69,7 +69,7 @@ TextureImporter: platformSettings: - serializedVersion: 4 buildTarget: DefaultTexturePlatform - maxTextureSize: 2048 + maxTextureSize: 512 resizeAlgorithm: 0 textureFormat: -1 textureCompression: 1 @@ -106,6 +106,32 @@ TextureImporter: ignorePlatformSupport: 0 androidETC2FallbackOverride: 0 forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: WebGL + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: WindowsStoreApps + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 spriteSheet: serializedVersion: 2 sprites: [] diff --git a/Assets/PerfectWorld/Resources/UI/Team/khung caidat.png.meta b/Assets/PerfectWorld/Resources/UI/Team/khung caidat.png.meta index 5ab5602f00..40eb42136e 100644 --- a/Assets/PerfectWorld/Resources/UI/Team/khung caidat.png.meta +++ b/Assets/PerfectWorld/Resources/UI/Team/khung caidat.png.meta @@ -69,7 +69,7 @@ TextureImporter: platformSettings: - serializedVersion: 4 buildTarget: DefaultTexturePlatform - maxTextureSize: 2048 + maxTextureSize: 512 resizeAlgorithm: 0 textureFormat: -1 textureCompression: 1 @@ -106,6 +106,32 @@ TextureImporter: ignorePlatformSupport: 0 androidETC2FallbackOverride: 0 forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: WebGL + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: WindowsStoreApps + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 spriteSheet: serializedVersion: 2 sprites: [] diff --git a/Assets/PerfectWorld/Scripts/UI/Dialogs/DlgPetDetail.cs b/Assets/PerfectWorld/Scripts/UI/Dialogs/DlgPetDetail.cs index 61849c9485..d8fc05d745 100644 --- a/Assets/PerfectWorld/Scripts/UI/Dialogs/DlgPetDetail.cs +++ b/Assets/PerfectWorld/Scripts/UI/Dialogs/DlgPetDetail.cs @@ -1,4 +1,4 @@ -using BrewMonster.Network; +using BrewMonster.Network; using BrewMonster.Scripts; using BrewMonster.Scripts.Pet; using CSNetwork.GPDataType; @@ -461,15 +461,15 @@ namespace BrewMonster.UI { if(m_textPetName != null) { - m_textPetName.text = "___"; + m_textPetName.text = ""; } if(m_textLevel != null) { - m_textLevel.text = "___"; + m_textLevel.text = ""; } if(m_textExp != null) { - m_textExp.text = "___"; + m_textExp.text = ""; } if (m_iconPet != null && m_spriteDefault_Icon != null) { @@ -477,7 +477,7 @@ namespace BrewMonster.UI } if(m_descPet != null) { - m_descPet.text = "___"; + m_descPet.text = ""; } } } diff --git a/Assets/PerfectWorld/UI/Pet/DlgPetList.prefab b/Assets/PerfectWorld/UI/Pet/DlgPetList.prefab index 2eaf5eaa72..6ac0acaa91 100644 --- a/Assets/PerfectWorld/UI/Pet/DlgPetList.prefab +++ b/Assets/PerfectWorld/UI/Pet/DlgPetList.prefab @@ -353,7 +353,7 @@ MonoBehaviour: m_OnCullStateChanged: m_PersistentCalls: m_Calls: [] - m_text: ___ + m_text: m_isRightToLeft: 0 m_fontAsset: {fileID: 11400000, guid: 369c2e14814cc9a4b8e3ad4e37769134, type: 2} m_sharedMaterial: {fileID: 9092487103257209053, guid: 369c2e14814cc9a4b8e3ad4e37769134, type: 2} @@ -380,7 +380,7 @@ MonoBehaviour: m_faceColor: serializedVersion: 2 rgba: 4294967295 - m_fontSize: 39.15 + m_fontSize: 36 m_fontSizeBase: 36 m_fontWeight: 400 m_enableAutoSizing: 1 @@ -565,7 +565,7 @@ MonoBehaviour: m_OnCullStateChanged: m_PersistentCalls: m_Calls: [] - m_text: ___ + m_text: m_isRightToLeft: 0 m_fontAsset: {fileID: 11400000, guid: 369c2e14814cc9a4b8e3ad4e37769134, type: 2} m_sharedMaterial: {fileID: 9092487103257209053, guid: 369c2e14814cc9a4b8e3ad4e37769134, type: 2} @@ -592,7 +592,7 @@ MonoBehaviour: m_faceColor: serializedVersion: 2 rgba: 4294967295 - m_fontSize: 39.15 + m_fontSize: 36 m_fontSizeBase: 36 m_fontWeight: 400 m_enableAutoSizing: 1 @@ -1464,6 +1464,7 @@ MonoBehaviour: m_EditorClassIdentifier: skillNameText: {fileID: 0} imageProgress: {fileID: 0} + btnCancel: {fileID: 0} m_prefabPetSlot: {fileID: 4659787501825667672, guid: 1f24da0ec1a703b4e9650d69ec4ceff2, type: 3} m_container: {fileID: 6146182060371184350} m_btnClose: {fileID: 8122086289517642079} @@ -1655,6 +1656,7 @@ MonoBehaviour: m_EditorClassIdentifier: skillNameText: {fileID: 0} imageProgress: {fileID: 0} + btnCancel: {fileID: 0} m_iconPet: {fileID: 3806612095625030478} m_textPetName: {fileID: 3493020700139178670} m_textLevel: {fileID: 7602645980480065390} @@ -1933,7 +1935,7 @@ MonoBehaviour: m_OnCullStateChanged: m_PersistentCalls: m_Calls: [] - m_text: New Text + m_text: m_isRightToLeft: 0 m_fontAsset: {fileID: 11400000, guid: 369c2e14814cc9a4b8e3ad4e37769134, type: 2} m_sharedMaterial: {fileID: 9092487103257209053, guid: 369c2e14814cc9a4b8e3ad4e37769134, type: 2} @@ -2373,7 +2375,7 @@ MonoBehaviour: m_OnCullStateChanged: m_PersistentCalls: m_Calls: [] - m_text: ___ + m_text: m_isRightToLeft: 0 m_fontAsset: {fileID: 11400000, guid: 369c2e14814cc9a4b8e3ad4e37769134, type: 2} m_sharedMaterial: {fileID: 9092487103257209053, guid: 369c2e14814cc9a4b8e3ad4e37769134, type: 2} @@ -2400,7 +2402,7 @@ MonoBehaviour: m_faceColor: serializedVersion: 2 rgba: 4294967295 - m_fontSize: 39.15 + m_fontSize: 36 m_fontSizeBase: 36 m_fontWeight: 400 m_enableAutoSizing: 1 From b487e24d8a040c3a9196301a8f9358c80e0057c1 Mon Sep 17 00:00:00 2001 From: vuong dinh hoang Date: Wed, 20 May 2026 15:08:09 +0700 Subject: [PATCH 7/7] fix action --- .../Samples/ExampleAnimator.controller | 70 ++++- .../Scripts/Managers/CECHPWorkSit.cs | 64 +++++ .../Scripts/Managers/CECHPWorkSit.cs.meta | 2 + .../Scripts/Managers/EC_HPWork.cs | 2 +- .../Scripts/Managers/EC_ManMatter.cs | 9 + .../Scripts/Managers/EC_ManPlayer.cs | 7 + Assets/PerfectWorld/Scripts/Move/CECPlayer.cs | 28 +- .../CSNetwork/C2SCommand/C2SCommand.cs | 15 + .../CSNetwork/C2SCommand/C2SCommandFactory.cs | 15 + .../Scripts/Network/CSNetwork/GPDataType.cs | 12 + .../Scripts/Network/CSNetwork/GameSession.cs | 53 ++++ .../Scripts/Network/UnityGameSession.cs | 41 +++ .../Scripts/Objet/Shortcut/CECShortcutSet.cs | 118 ++++---- .../Scripts/Players/CECActionSwitcherBase.cs | 3 + .../Scripts/Players/EC_ElsePlayer.cs | 17 ++ .../GamePlay/SkillUI/CDlgSkillSubListItem.cs | 2 - Assets/Resources/DebugCmdHistory.json | 16 +- Assets/Scripts/CECHostPlayer.HostCmd.cs | 259 ++++++++++++++++++ Assets/Scripts/CECHostPlayer.HostCmd.cs.meta | 2 + Assets/Scripts/CECHostPlayer.Interaction.cs | 1 + Assets/Scripts/CECHostPlayer.Sit.cs | 38 +++ Assets/Scripts/CECHostPlayer.Sit.cs.meta | 2 + Assets/Scripts/CECHostPlayer.cs | 10 +- Assets/Scripts/PlayerVisual.cs | 1 + 24 files changed, 693 insertions(+), 94 deletions(-) create mode 100644 Assets/PerfectWorld/Scripts/Managers/CECHPWorkSit.cs create mode 100644 Assets/PerfectWorld/Scripts/Managers/CECHPWorkSit.cs.meta create mode 100644 Assets/Scripts/CECHostPlayer.HostCmd.cs create mode 100644 Assets/Scripts/CECHostPlayer.HostCmd.cs.meta create mode 100644 Assets/Scripts/CECHostPlayer.Sit.cs create mode 100644 Assets/Scripts/CECHostPlayer.Sit.cs.meta diff --git a/Assets/EditorAttributes/Samples/ExampleAnimator.controller b/Assets/EditorAttributes/Samples/ExampleAnimator.controller index fcd51ccc2e..4d82241192 100644 --- a/Assets/EditorAttributes/Samples/ExampleAnimator.controller +++ b/Assets/EditorAttributes/Samples/ExampleAnimator.controller @@ -1,5 +1,57 @@ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: +--- !u!1102 &-6682150482281830067 +AnimatorState: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: "\u6253\u5750_\u901A\u7528" + m_Speed: 1 + m_CycleOffset: 0 + m_Transitions: [] + m_StateMachineBehaviours: [] + m_Position: {x: 50, y: 50, z: 0} + m_IKOnFeet: 0 + m_WriteDefaultValues: 1 + m_Mirror: 0 + m_SpeedParameterActive: 0 + m_MirrorParameterActive: 0 + m_CycleOffsetParameterActive: 0 + m_TimeParameterActive: 0 + m_Motion: {fileID: 7400000, guid: 93519909dda6cca42a0c28127afbbb52, type: 2} + m_Tag: + m_SpeedParameter: + m_MirrorParameter: + m_CycleOffsetParameter: + m_TimeParameter: +--- !u!1102 &-4679316952246346250 +AnimatorState: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: "\u6253\u5750\u5FAA\u73AF_\u901A\u7528" + m_Speed: 1 + m_CycleOffset: 0 + m_Transitions: [] + m_StateMachineBehaviours: [] + m_Position: {x: 50, y: 50, z: 0} + m_IKOnFeet: 0 + m_WriteDefaultValues: 1 + m_Mirror: 0 + m_SpeedParameterActive: 0 + m_MirrorParameterActive: 0 + m_CycleOffsetParameterActive: 0 + m_TimeParameterActive: 0 + m_Motion: {fileID: 7400000, guid: bf857f5576da777429ed5a6fd606294a, type: 2} + m_Tag: + m_SpeedParameter: + m_MirrorParameter: + m_CycleOffsetParameter: + m_TimeParameter: --- !u!1107 &-4297166198488271538 AnimatorStateMachine: serializedVersion: 6 @@ -8,7 +60,13 @@ AnimatorStateMachine: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_Name: Base Layer - m_ChildStates: [] + m_ChildStates: + - serializedVersion: 1 + m_State: {fileID: -6682150482281830067} + m_Position: {x: 330, y: 78, z: 0} + - serializedVersion: 1 + m_State: {fileID: -4679316952246346250} + m_Position: {x: 348, y: 166, z: 0} m_ChildStateMachines: [] m_AnyStateTransitions: [] m_EntryTransitions: [] @@ -18,7 +76,7 @@ AnimatorStateMachine: m_EntryPosition: {x: 50, y: 120, z: 0} m_ExitPosition: {x: 800, y: 120, z: 0} m_ParentStateMachinePosition: {x: 800, y: 20, z: 0} - m_DefaultState: {fileID: 0} + m_DefaultState: {fileID: -6682150482281830067} --- !u!91 &9100000 AnimatorController: m_ObjectHideFlags: 0 @@ -33,25 +91,25 @@ AnimatorController: m_DefaultFloat: 0 m_DefaultInt: 0 m_DefaultBool: 0 - m_Controller: {fileID: 0} + m_Controller: {fileID: 9100000} - m_Name: IntParam m_Type: 3 m_DefaultFloat: 0 m_DefaultInt: 0 m_DefaultBool: 0 - m_Controller: {fileID: 0} + m_Controller: {fileID: 9100000} - m_Name: BoolParam m_Type: 4 m_DefaultFloat: 0 m_DefaultInt: 0 m_DefaultBool: 0 - m_Controller: {fileID: 0} + m_Controller: {fileID: 9100000} - m_Name: TriggerParam m_Type: 9 m_DefaultFloat: 0 m_DefaultInt: 0 m_DefaultBool: 0 - m_Controller: {fileID: 0} + m_Controller: {fileID: 9100000} m_AnimatorLayers: - serializedVersion: 5 m_Name: Base Layer diff --git a/Assets/PerfectWorld/Scripts/Managers/CECHPWorkSit.cs b/Assets/PerfectWorld/Scripts/Managers/CECHPWorkSit.cs new file mode 100644 index 0000000000..84821fec71 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Managers/CECHPWorkSit.cs @@ -0,0 +1,64 @@ +/* + * FILE: CECHPWorkSit.cs + * + * DESCRIPTION: Host player sit work + * + * CONVERTED FROM: EC_HPWorkSit.cpp/EC_HPWorkSit.h + */ + +using BrewMonster; +using CSNetwork.GPDataType; + +namespace BrewMonster.Scripts +{ + public class CECHPWorkSit : CECHPWork + { + protected bool m_bBeSitting; + + public CECHPWorkSit(CECHPWorkMan pWorkMan) : base(Host_work_ID.WORK_SIT, pWorkMan) + { + m_dwMask = Work_mask.MASK_SIT; + m_dwTransMask = Work_mask.MASK_STAND; + Reset(); + } + + public void SetBeSittingFlag(bool bTrue) + { + m_bBeSitting = bTrue; + } + + public override void Reset() + { + base.Reset(); + m_bBeSitting = false; + } + + public override bool CopyData(CECHPWork pWork) + { + if (!base.CopyData(pWork)) + return false; + + CECHPWorkSit pSrc = (CECHPWorkSit)pWork; + m_bBeSitting = pSrc.m_bBeSitting; + return true; + } + + protected override void OnFirstTick() + { + m_pHost.m_iMoveMode = (int)Move_Mode.MOVE_STAND; + if (m_bBeSitting) + m_pHost.PlayAction((int)PLAYER_ACTION_TYPE.ACT_SITDOWN_LOOP); + else + { + m_pHost.PlayAction((int)PLAYER_ACTION_TYPE.ACT_SITDOWN); + m_pHost.PlayAction((int)PLAYER_ACTION_TYPE.ACT_SITDOWN_LOOP, true, 200, true); + } + } + + public override bool Tick(float dwDeltaTime) + { + base.Tick(dwDeltaTime); + return true; + } + } +} diff --git a/Assets/PerfectWorld/Scripts/Managers/CECHPWorkSit.cs.meta b/Assets/PerfectWorld/Scripts/Managers/CECHPWorkSit.cs.meta new file mode 100644 index 0000000000..fe6a91a001 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Managers/CECHPWorkSit.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d9a9c005284728d428a534fcf337a5b1 \ No newline at end of file diff --git a/Assets/PerfectWorld/Scripts/Managers/EC_HPWork.cs b/Assets/PerfectWorld/Scripts/Managers/EC_HPWork.cs index 4c734423b1..a92b763d96 100644 --- a/Assets/PerfectWorld/Scripts/Managers/EC_HPWork.cs +++ b/Assets/PerfectWorld/Scripts/Managers/EC_HPWork.cs @@ -649,7 +649,7 @@ namespace BrewMonster.Scripts //case CECHPWork.Host_work_ID.WORK_FOLLOW: pWork = new CECHPWorkFollow(this); break; case CECHPWork.Host_work_ID.WORK_FLYOFF: pWork = new CECHPWorkFly(this); break; case CECHPWork.Host_work_ID.WORK_FREEFALL: pWork = new CECHPWorkFall(this); break; - //case CECHPWork.Host_work_ID.WORK_SIT: pWork = new CECHPWorkSit(this); break; + case CECHPWork.Host_work_ID.WORK_SIT: pWork = new CECHPWorkSit(this); break; case CECHPWork.Host_work_ID.WORK_PICKUP: pWork = new EC_HPWorkPick(this); break; case CECHPWork.Host_work_ID.WORK_CONCENTRATE: pWork = new CECHPWorkConcentrate(this); break; //case CECHPWork.Host_work_ID.WORK_REVIVE: pWork = new CECHPWorkRevive(this); break; diff --git a/Assets/PerfectWorld/Scripts/Managers/EC_ManMatter.cs b/Assets/PerfectWorld/Scripts/Managers/EC_ManMatter.cs index 73d1204849..88fe69e3cc 100644 --- a/Assets/PerfectWorld/Scripts/Managers/EC_ManMatter.cs +++ b/Assets/PerfectWorld/Scripts/Managers/EC_ManMatter.cs @@ -343,6 +343,15 @@ namespace PerfectWorld.Scripts.Managers } return best; } + + /// Port of CECMatterMan::FindMatterNearHost — nearest pickupable ground matter. + public CECMatter FindMatterNearHost(float fRadius, bool bPickOnly = true) + { + CECHostPlayer host = CECGameRun.Instance?.GetHostPlayer(); + if (host == null) + return null; + return GetNearestPickupableMatter(host.GetPos(), fRadius); + } public CECMatter CreateMatter(info_matter info) { diff --git a/Assets/PerfectWorld/Scripts/Managers/EC_ManPlayer.cs b/Assets/PerfectWorld/Scripts/Managers/EC_ManPlayer.cs index d679e9ccc8..c01e9b4148 100644 --- a/Assets/PerfectWorld/Scripts/Managers/EC_ManPlayer.cs +++ b/Assets/PerfectWorld/Scripts/Managers/EC_ManPlayer.cs @@ -67,6 +67,7 @@ namespace PerfectWorld.Scripts.Managers case EC_MsgDef.MSG_PM_PLAYERDOEMOTE: case EC_MsgDef.MSG_PM_PLAYERGATHER: case EC_MsgDef.MSG_PM_PLAYERFLY: + case EC_MsgDef.MSG_PM_PLAYERSITDOWN: case EC_MsgDef.MSG_PM_PLAYERMOUNT: case EC_MsgDef.MSG_PM_PLAYERCHGSHAPE: case EC_MsgDef.MSG_PM_PLAYERSKILLRESULT: @@ -754,6 +755,12 @@ namespace PerfectWorld.Scripts.Managers cid = (GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1)).object_id; break; + case EC_MsgDef.MSG_PM_PLAYERSITDOWN: + if (Convert.ToInt32(Msg.dwParam2) == CommandID.OBJECT_SIT_DOWN) + cid = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1).id; + else + cid = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1).id; + break; case EC_MsgDef.MSG_PM_PLAYERMOUNT: cid = (GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1)).id; break; diff --git a/Assets/PerfectWorld/Scripts/Move/CECPlayer.cs b/Assets/PerfectWorld/Scripts/Move/CECPlayer.cs index 0a24e3ea40..aed6742a2f 100644 --- a/Assets/PerfectWorld/Scripts/Move/CECPlayer.cs +++ b/Assets/PerfectWorld/Scripts/Move/CECPlayer.cs @@ -1544,21 +1544,21 @@ namespace BrewMonster szAct= $"{action.data.ActionPrefix}_{action.data.action_weapon_suffix[weapon_type].Suffix}"; - // CECModel pRightHandWeapon = GetRightHandWeapon(); - // if( !bQueue ) - // { - // PlayNonSkillActionWithName(iAction, szAct, bRestart, iTransTime); + CECModel pRightHandWeapon = GetRightHandWeapon(); + if( !bQueue ) + { + m_pActionController. PlayNonSkillActionWithName(iAction, szAct, bRestart, iTransTime); - // if(pRightHandWeapon && IsUsingMagicWeapon()) - // pRightHandWeapon->PlayActionByName(_GenWeaponActionName(szAct, m_iGender), 1.0f, bRestart, iTransTime, true, iAction); - // } - // else - // { - // QueueNonSkillActionWithName(iAction, szAct, iTransTime, bRestart, false, false, true, NULL, COMACT_FLAG_MODE_ONCE_MULTIIGNOREGFX); + // if(pRightHandWeapon && IsUsingMagicWeapon()) + // pRightHandWeapon->PlayActionByName(_GenWeaponActionName(szAct, m_iGender), 1.0f, bRestart, iTransTime, true, iAction); + } + else + { + m_pActionController. QueueNonSkillActionWithName(iAction, szAct, iTransTime, bRestart, false, false, true, null, COMACT_FLAG_MODE_ONCE_MULTIIGNOREGFX); - // if(pRightHandWeapon && IsUsingMagicWeapon()) - // pRightHandWeapon->QueueAction(_GenWeaponActionName(szAct, m_iGender), iTransTime, iAction, bRestart ? true : false); - // } + // if(pRightHandWeapon && IsUsingMagicWeapon()) + // pRightHandWeapon->QueueAction(_GenWeaponActionName(szAct, m_iGender), iTransTime, iAction, bRestart ? true : false); + } if (m_iBuddyId != 0) { @@ -1619,7 +1619,7 @@ namespace BrewMonster // UpdateWeaponHangerPosByAction(iAction); // } // EventBus.PublishChannel(m_PlayerInfo.cid, new PlayActionEvent(szShapeName, szAct, iTransTime)); - m_pActionController.PlayNonSkillActionWithName(iAction, szAct, bRestart, iTransTime); + // m_pActionController.PlayNonSkillActionWithName(iAction, szAct, bRestart, iTransTime); return true; } else diff --git a/Assets/PerfectWorld/Scripts/Network/CSNetwork/C2SCommand/C2SCommand.cs b/Assets/PerfectWorld/Scripts/Network/CSNetwork/C2SCommand/C2SCommand.cs index d6d1719eec..8d22f9474a 100644 --- a/Assets/PerfectWorld/Scripts/Network/CSNetwork/C2SCommand/C2SCommand.cs +++ b/Assets/PerfectWorld/Scripts/Network/CSNetwork/C2SCommand/C2SCommand.cs @@ -314,6 +314,21 @@ namespace CSNetwork.C2SCommand public int id_task; // Task ID } + public struct cmd_assist_sel + { + public int idTeamMember; + } + + public struct cmd_bind_player_request + { + public int target; + } + + public struct cmd_bind_player_invite + { + public int target; + } + public struct cmd_error_msg { public int iMessage; diff --git a/Assets/PerfectWorld/Scripts/Network/CSNetwork/C2SCommand/C2SCommandFactory.cs b/Assets/PerfectWorld/Scripts/Network/CSNetwork/C2SCommand/C2SCommandFactory.cs index 859bcb5515..16d9809264 100644 --- a/Assets/PerfectWorld/Scripts/Network/CSNetwork/C2SCommand/C2SCommandFactory.cs +++ b/Assets/PerfectWorld/Scripts/Network/CSNetwork/C2SCommand/C2SCommandFactory.cs @@ -1045,6 +1045,21 @@ namespace CSNetwork.C2SCommand return SerializeCommand(CommandID.ACTIVE_RUSH_FLY, pCmd); } + public static Octets CreateTeamAssistSelCommand(int idTeamMember) + { + return SerializeCommand(CommandID.ASSIST_SELECT, new cmd_assist_sel { idTeamMember = idTeamMember }); + } + + public static Octets CreateBindPlayerRequestCommand(int idTarget) + { + return SerializeCommand(CommandID.BIND_PLAYER_REQUEST, new cmd_bind_player_request { target = idTarget }); + } + + public static Octets CreateBindPlayerInviteCommand(int idTarget) + { + return SerializeCommand(CommandID.BIND_PLAYER_INVITE, new cmd_bind_player_invite { target = idTarget }); + } + public static Octets CreatePetCtrlCmd(int idTarget, int cmd, byte[] pParamBuf, int iParamLen) { cmd_pet_ctrl pCmd = new cmd_pet_ctrl(); diff --git a/Assets/PerfectWorld/Scripts/Network/CSNetwork/GPDataType.cs b/Assets/PerfectWorld/Scripts/Network/CSNetwork/GPDataType.cs index 0012496751..1aa9283ae3 100644 --- a/Assets/PerfectWorld/Scripts/Network/CSNetwork/GPDataType.cs +++ b/Assets/PerfectWorld/Scripts/Network/CSNetwork/GPDataType.cs @@ -2830,6 +2830,18 @@ namespace CSNetwork.GPDataType public int object_id; }; + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct cmd_object_sit_down + { + public int id; + }; + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct cmd_object_stand_up + { + public int id; + }; + [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct cmd_host_rush_fly { diff --git a/Assets/PerfectWorld/Scripts/Network/CSNetwork/GameSession.cs b/Assets/PerfectWorld/Scripts/Network/CSNetwork/GameSession.cs index 6f48c4389b..9ba8cf2025 100644 --- a/Assets/PerfectWorld/Scripts/Network/CSNetwork/GameSession.cs +++ b/Assets/PerfectWorld/Scripts/Network/CSNetwork/GameSession.cs @@ -1572,6 +1572,11 @@ namespace CSNetwork break; } + case CommandID.OBJECT_SIT_DOWN: + case CommandID.OBJECT_STAND_UP: + + EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERSITDOWN, MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); + break; default: #if UNITY_EDITOR if (isDebug) @@ -2962,6 +2967,54 @@ namespace CSNetwork SendProtocol(gamedatasend); } + public void c2s_SendCmdSitDown() + { + var g = new gamedatasend(); + g.Data = C2SCommandFactory.CreateNakeCmd(C2SCommand.CommandID.SIT_DOWN); + SendProtocol(g); + } + + public void c2s_SendCmdTeamAssistSel(int idTeamMember) + { + var g = new gamedatasend(); + g.Data = C2SCommandFactory.CreateTeamAssistSelCommand(idTeamMember); + SendProtocol(g); + } + + public void c2s_SendCmdOpenBoothTest() + { + var g = new gamedatasend(); + g.Data = C2SCommandFactory.CreateNakeCmd(C2SCommand.CommandID.OPEN_BOOTH_TEST); + SendProtocol(g); + } + + public void c2s_SendCmdBindPlayerRequest(int idTarget) + { + var g = new gamedatasend(); + g.Data = C2SCommandFactory.CreateBindPlayerRequestCommand(idTarget); + SendProtocol(g); + } + + public void c2s_SendCmdBindPlayerInvite(int idTarget) + { + var g = new gamedatasend(); + g.Data = C2SCommandFactory.CreateBindPlayerInviteCommand(idTarget); + SendProtocol(g); + } + + /// Port of CECGameSession::trade_Start. + public bool trade_Start(int idTarget) + { + var p = new tradestart + { + Roleid = m_iCharID, + Partner_roleid = idTarget, + Localsid = (int)_localsid + }; + SendProtocol(p); + return true; + } + public void c2s_SendCmdPetCtrl(int idTarget, int cmd, byte[] pParamBuf, int iParamLen) { gamedatasend gamedatasend = new gamedatasend(); diff --git a/Assets/PerfectWorld/Scripts/Network/UnityGameSession.cs b/Assets/PerfectWorld/Scripts/Network/UnityGameSession.cs index 2551c38c7b..6c95b6d15d 100644 --- a/Assets/PerfectWorld/Scripts/Network/UnityGameSession.cs +++ b/Assets/PerfectWorld/Scripts/Network/UnityGameSession.cs @@ -655,6 +655,42 @@ namespace BrewMonster.Network { Instance._gameSession.c2s_SendCmdStandUp(); } + + public static void c2s_CmdSitDown() + { + BMLogger.LogError("HoangDev: c2s_CmdSitDown"); + Instance._gameSession.c2s_SendCmdSitDown(); + } + + public static void c2s_CmdTeamAssistSel(int idTeamMember) + { + Instance._gameSession.c2s_SendCmdTeamAssistSel(idTeamMember); + } + + public static void c2s_CmdActiveRushFly(bool bActive) + { + Instance._gameSession.c2s_SendCmdActiveRushFly(bActive); + } + + public static void c2s_CmdOpenBoothTest() + { + Instance._gameSession.c2s_SendCmdOpenBoothTest(); + } + + public static void c2s_CmdBindPlayerRequest(int idTarget) + { + Instance._gameSession.c2s_SendCmdBindPlayerRequest(idTarget); + } + + public static void c2s_CmdBindPlayerInvite(int idTarget) + { + Instance._gameSession.c2s_SendCmdBindPlayerInvite(idTarget); + } + + public static bool trade_Start(int idTarget) + { + return Instance._gameSession.trade_Start(idTarget); + } #region Task public static void c2s_CmdGetAllData(bool byPack, bool byEquip, bool byTask) { @@ -666,6 +702,11 @@ namespace BrewMonster.Network { Instance._gameSession.c2s_SendCmdEmoteAction(wPose); } + + public static void c2s_CmdSessionEmote(int iPose) + { + Instance._gameSession.c2s_SendCmdEmoteAction((uint)iPose); + } public static void c2s_CmdTaskNotify( byte[] pBuf, uint sz) { if (Instance != null && Instance._gameSession != null) diff --git a/Assets/PerfectWorld/Scripts/Objet/Shortcut/CECShortcutSet.cs b/Assets/PerfectWorld/Scripts/Objet/Shortcut/CECShortcutSet.cs index 63a08530c1..d5d1f683f6 100644 --- a/Assets/PerfectWorld/Scripts/Objet/Shortcut/CECShortcutSet.cs +++ b/Assets/PerfectWorld/Scripts/Objet/Shortcut/CECShortcutSet.cs @@ -17,6 +17,7 @@ using BrewMonster.Assets.PerfectWorld.Scripts.Skills; using BrewMonster.Network; using BrewMonster.Scripts; using BrewMonster.Scripts.Skills; +using static BrewMonster.Scripts.FixedMsg; using CSNetwork.C2SCommand; using CSNetwork.GPDataType; using CSNetwork.S2CCommand; @@ -513,7 +514,7 @@ namespace BrewMonster data.AddRange(BitConverter.GetBytes((int)cmdSC.GetParam())); break; } - + case CECShortcut.ShortcutType.SCT_SKILL: { CECSCSkill skillSC = (CECSCSkill)pSC; @@ -949,61 +950,61 @@ namespace BrewMonster public override bool Execute() { CECHostPlayer pHost = CECGameRun.Instance.GetHostPlayer(); - if (!pHost || !pHost.IsAllResReady()) + if (pHost == null || !pHost.IsAllResReady()) return false; - // if (GetCoolTime(0)>0) - // { - // bool bForbidCmd = true; - // if (m_iCommand == CMD_RUSHFLY && pHost.GetRushFlyFlag()) - // bForbidCmd = false; - // - // if (bForbidCmd) - // { - // g_pGame.GetGameRun().AddFixedMessage(FIXMSG_CMD_INCOOLTIME); - // return false; - // } - // } - switch (m_iCommand) + int iMax = 0; + if (GetCoolTime(ref iMax) > 0) { - // case CMD_SITDOWN: pHost.CmdSitDown(!pHost.IsSitting()); break; - // case CMD_WALKRUN: pHost.CmdWalkRun(!pHost.GetWalkRunFlag()); break; + bool bForbidCmd = true; + if ((CommandID)m_iCommand == CMD_RUSHFLY && pHost.GetRushFlyFlag()) + bForbidCmd = false; + + if (bForbidCmd) + { + CECGameRun.Instance?.AddFixedMessage((int)FIXMSG_CMD_INCOOLTIME); + return false; + } + } + BMLogger.LogError("HoangDev: (CommandID)m_iCommand:"+(CommandID)m_iCommand); + switch ((CommandID)m_iCommand) + { + case CMD_SITDOWN: pHost.CmdSitDown(!pHost.IsSitting()); break; + case CMD_WALKRUN: pHost.CmdWalkRun(!pHost.GetWalkRunFlag()); break; // case CMD_NORMALATTACK: pHost.CmdNormalAttack(); break; - // case CMD_FINDTARGET: pHost.CmdFindTarget(); break; - // case CMD_ASSISTATTACK: pHost.CmdAssistAttack(); break; - // case CMD_INVITETOTEAM: pHost.CmdInviteToTeam(); break; - // case CMD_LEAVETEAM: pHost.CmdLeaveTeam(); break; - // case CMD_KICKTEAMMEM: pHost.CmdKickTeamMember(); break; - // case CMD_FINDTEAM: pHost.CmdFindTeam(); break; - // case CMD_STARTTRADE: pHost.CmdStartTrade(); break; - // case CMD_SELLBOOTH: pHost.CmdSellBooth(); break; - // case CMD_BUYBOOTH: pHost.CmdBuyBooth(); break; - case (int)CommandID.CMD_PLAYPOSE: pHost.CmdStartPose((int)m_dwParam); break; - // case CMD_INVITETOFACTION: pHost.CmdInviteToFaction(); break; - case (int)CommandID.CMD_FLY: + case CMD_FINDTARGET: pHost.CmdFindTarget(); break; + case CMD_ASSISTATTACK: pHost.CmdAssistAttack(); break; + case CMD_INVITETOTEAM: pHost.CmdInviteToTeam(); break; + case CMD_LEAVETEAM: pHost.CmdLeaveTeam(); break; + case CMD_KICKTEAMMEM: pHost.CmdKickTeamMember(); break; + case CMD_FINDTEAM: pHost.CmdFindTeam(); break; + case CMD_STARTTRADE: pHost.CmdStartTrade(); break; + case CMD_SELLBOOTH: pHost.CmdSellBooth(); break; + case CMD_BUYBOOTH: pHost.CmdBuyBooth(); break; + case CMD_PLAYPOSE: pHost.CmdStartPose((int)m_dwParam); break; + case CMD_INVITETOFACTION: pHost.CmdInviteToFaction(); break; + case CMD_FLY: { // 如果骑乘要飞行,则这些 action switcher,否则 CmdFly - // If riding wants to fly, then these action switcher, otherwise CmdFly - // Call CmdFly(true) to match F4 key behavior - // Manual fly toggle should also cancel any active auto-route/AutoPF move. - // Use same behavior as local input (F4) so the player regains control immediately. - var wm = pHost.GetWorkMan(); - if (wm != null) + // If riding wants to fly, use action switcher; otherwise CmdFly + if (pHost.GetActionSwitcher()?.OnRideToFlyAction() != true) { - var mw = wm.GetRunningWork(CECHPWork.Host_work_ID.WORK_MOVETOPOS) as CECHPWorkMove; - if (mw != null && mw.GetAutoMove()) + var wm = pHost.GetWorkMan(); + if (wm != null) { - wm.FinishRunningWork(CECHPWork.Host_work_ID.WORK_MOVETOPOS); + var mw = wm.GetRunningWork(CECHPWork.Host_work_ID.WORK_MOVETOPOS) as CECHPWorkMove; + if (mw != null && mw.GetAutoMove()) + wm.FinishRunningWork(CECHPWork.Host_work_ID.WORK_MOVETOPOS); } + CECIntelligentRoute.Instance().ResetSearch(); + pHost.CmdFly(true); } - CECIntelligentRoute.Instance().ResetSearch(); - pHost.CmdFly(true); break; } // case CMD_PICKUP: pHost.CmdPickup(); break; - // case CMD_GATHER: pHost.CmdGather(); break; - // case CMD_RUSHFLY: pHost.CmdRushFly(); break; - // case CMD_BINDBUDDY: pHost.CmdBindBuddy(pHost.GetSelectedTarget()); break; + case CMD_GATHER: pHost.CmdGather(); break; + case CMD_RUSHFLY: pHost.CmdRushFly(); break; + case CMD_BINDBUDDY: pHost.CmdBindBuddy(pHost.GetSelectedTarget()); break; default: return false; } @@ -1079,37 +1080,34 @@ namespace BrewMonster return szIconFile; } - // Get item cool time - int GetCoolTime(ref int piMax/* NULL */) + public override int GetCoolTime(ref int piMax) { CECHostPlayer pHost = CECGameRun.Instance.GetHostPlayer(); int iTime = 0; + piMax = 0; + + if (pHost == null) + return 0; switch ((CommandID)m_iCommand) { - case CommandID.CMD_RUSHFLY: - - // iTime = pHost.GetCoolTime(GP_CT_FLY_RUSH, piMax); + case CMD_RUSHFLY: + iTime = pHost.GetCoolTime((int)CoolTimeIndex.GP_CT_FLY_RUSH, out piMax); break; - case CommandID.CMD_PLAYPOSE: - // todo need to get cooldown here - // iTime = pHost.GetCoolTime(GP_CT_EMOTE, piMax); + case CMD_PLAYPOSE: + iTime = pHost.GetCoolTime((int)CoolTimeIndex.GP_CT_EMOTE, out piMax); break; - case CommandID.CMD_BINDBUDDY: + case CMD_BINDBUDDY: { - // CECCounter& cnt = pHost.GetBindCmdCoolCnt(); - // iTime = cnt.GetPeriod() - cnt.GetCounter(); - // - // if (piMax) - // *piMax = cnt.GetPeriod(); - + CECCounter cnt = pHost.GetBindCmdCoolCnt(); + iTime = (int)(cnt.GetPeriod() - cnt.GetCounter()); + piMax = (int)cnt.GetPeriod(); break; } default: - - // if (piMax) *piMax = 0; + piMax = 0; break; } diff --git a/Assets/PerfectWorld/Scripts/Players/CECActionSwitcherBase.cs b/Assets/PerfectWorld/Scripts/Players/CECActionSwitcherBase.cs index 1b270a0c84..0a9a6102aa 100644 --- a/Assets/PerfectWorld/Scripts/Players/CECActionSwitcherBase.cs +++ b/Assets/PerfectWorld/Scripts/Players/CECActionSwitcherBase.cs @@ -206,6 +206,9 @@ namespace BrewMonster.Assets.PerfectWorld.Scripts.Players public void SetPostMessageFlag(bool bCan) { m_bCanAddMsg = bCan; } public virtual bool OnFlyToRideAction(int petIndex) { return false; } // fly -> ride + + /// Ride mount then fly (shortcut CMD_FLY). 骑乘后飞行 + public virtual bool OnRideToFlyAction() { return false; } } public enum EMsgActionSwitcher diff --git a/Assets/PerfectWorld/Scripts/Players/EC_ElsePlayer.cs b/Assets/PerfectWorld/Scripts/Players/EC_ElsePlayer.cs index b7c546ae47..894b3ee3d1 100644 --- a/Assets/PerfectWorld/Scripts/Players/EC_ElsePlayer.cs +++ b/Assets/PerfectWorld/Scripts/Players/EC_ElsePlayer.cs @@ -497,6 +497,7 @@ namespace BrewMonster switch (Msg.dwMsg) { case EC_MsgDef.MSG_PM_PLAYERFLY: OnMsgPlayerFly(Msg); break; + case EC_MsgDef.MSG_PM_PLAYERSITDOWN: OnMsgPlayerSitDown(Msg); break; case EC_MsgDef.MSG_PM_PLAYERBASEINFO: OnMsgPlayerBaseInfo(Msg); break; case EC_MsgDef.MSG_PM_PLAYEREXTSTATE: OnMsgPlayerExtState(Msg); break; case EC_MsgDef.MSG_PM_PLAYEREQUIPDATA: OnMsgPlayerEquipData(Msg); break; @@ -1202,6 +1203,22 @@ namespace BrewMonster m_dwStates |= (uint)PlayerNPCState.GP_STATE_FLY; } + void OnMsgPlayerSitDown(ECMSG Msg) + { + if (Convert.ToInt32(Msg.dwParam2) == CommandID.OBJECT_SIT_DOWN) + { + m_dwStates |= (uint)PlayerNPCState.GP_STATE_SITDOWN; + m_pEPWorkMan.StartNormalWork(new CECEPWorkIdle(m_pEPWorkMan, CECEPWork.Idle_work_type.IDLE_SITDOWN, 0, 0)); + } + else // OBJECT_STAND_UP + { + m_dwStates &= ~(uint)PlayerNPCState.GP_STATE_SITDOWN; + if (m_pEPWorkMan.IsWorkRunning(CECEPWork.EP_work_ID.WORK_IDLE)) + PlayAction((int)PLAYER_ACTION_TYPE.ACT_STANDUP); + m_pEPWorkMan.FinishIdleWork(CECEPWork.Idle_work_type.IDLE_SITDOWN); + } + } + // Load player equipments bool LoadPlayerEquipments() { diff --git a/Assets/PerfectWorld/Scripts/UI/GamePlay/SkillUI/CDlgSkillSubListItem.cs b/Assets/PerfectWorld/Scripts/UI/GamePlay/SkillUI/CDlgSkillSubListItem.cs index 1b1ffd1083..c06c7d3e5d 100644 --- a/Assets/PerfectWorld/Scripts/UI/GamePlay/SkillUI/CDlgSkillSubListItem.cs +++ b/Assets/PerfectWorld/Scripts/UI/GamePlay/SkillUI/CDlgSkillSubListItem.cs @@ -260,8 +260,6 @@ namespace BrewMonster enumSkillLearnedState learnedState = model.GetSkillLearnedState(m_skillID); int requiredItem = model.GetRequiredBook(m_skillID, m_curLevel + 1); - if(m_skillID == 1) - BMLogger.LogError($"UpdateUpgradeBtn learnedState:{learnedState}, fitLevel={fitLevel}, requiredItem:{requiredItem}, model.CheckPreItem(requiredItem):{model.CheckPreItem(requiredItem)}"); if (enumSkillLearnedState.SKILL_FULL != learnedState && enumSkillFitLevelState.SKILL_FIT_LEVEL == fitLevel && (requiredItem == 0 || model.CheckPreItem(requiredItem))) diff --git a/Assets/Resources/DebugCmdHistory.json b/Assets/Resources/DebugCmdHistory.json index 5da8d2d05a..43b4aedfe2 100644 --- a/Assets/Resources/DebugCmdHistory.json +++ b/Assets/Resources/DebugCmdHistory.json @@ -1,18 +1,18 @@ { "items": [ - { - "header": 1992, - "param": 0, - "hasParam": false, - "describe": "Buff rage", - "lastUsedUtcTicks": 639138321717437357 - }, { "header": 8903, "param": 73125, "hasParam": true, "describe": "NoCooldown", - "lastUsedUtcTicks": 639138321659072960 + "lastUsedUtcTicks": 639147565037094641 + }, + { + "header": 1992, + "param": 0, + "hasParam": false, + "describe": "Buff rage", + "lastUsedUtcTicks": 639147564992569650 }, { "header": 2000, diff --git a/Assets/Scripts/CECHostPlayer.HostCmd.cs b/Assets/Scripts/CECHostPlayer.HostCmd.cs new file mode 100644 index 0000000000..229e964fe1 --- /dev/null +++ b/Assets/Scripts/CECHostPlayer.HostCmd.cs @@ -0,0 +1,259 @@ +using BrewMonster.Managers; +using BrewMonster.Network; +using BrewMonster.Scripts; +using BrewMonster.UI; +using CSNetwork.GPDataType; +using PerfectWorld.Scripts.Managers; +using UnityEngine; +using static BrewMonster.Scripts.CECHPWork; +using static BrewMonster.CECHPWorkTrace; + +namespace BrewMonster +{ + public partial class CECHostPlayer + { + public bool GetWalkRunFlag() => m_bWalkRun; + public bool GetRushFlyFlag() => m_bRushFly; + + CECCounter m_BindCmdCoolCnt = new CECCounter(); + + public CECCounter GetBindCmdCoolCnt() => m_BindCmdCoolCnt; + + void InitBindCmdCoolCnt() + { + m_BindCmdCoolCnt.SetPeriod(35000); + m_BindCmdCoolCnt.Reset(true); + } + + // Sit down / Stand up + // 坐下 / 站起 + public bool CmdSitDown(bool bSitDown) + { + BMLogger.LogError("HoangDev: CmdSitDown:"+bSitDown); + if (!CanDo(ActionCanDo.CANDO_SITDOWN)) + return false; + + if (bSitDown) + UnityGameSession.c2s_CmdSitDown(); + else + UnityGameSession.c2s_CmdStandUp(); + + return true; + } + + // Switch walk and run state + // 切换走/跑状态 + public bool CmdWalkRun(bool bRun) + { + m_bWalkRun = bRun; + return true; + } + + // Find a near target (stub) + // 寻找附近目标(占位) + public bool CmdFindTarget() + { + return true; + } + + // Select other player's attacked target + // 选择队友正在攻击的目标 + public bool CmdAssistAttack() + { + if (m_pWorkMan.IsSitting()) + { + UnityGameSession.c2s_CmdStandUp(); + return false; + } + + if (CanDo(ActionCanDo.CANDO_ASSISTSEL)) + UnityGameSession.c2s_CmdTeamAssistSel(m_idSelTarget); + + return true; + } + + // Invite selected player to join team + // 邀请选中的玩家入队 + public bool CmdInviteToTeam() + { + if (IsDead() || !GPDataTypeHelper.ISPLAYERID(m_idSelTarget) || m_idSelTarget == GetCharacterID()) + return false; + + UnityGameSession.c2s_CmdTeamInvite(m_idSelTarget); + return true; + } + + // Leave current team (confirmation dialog) + // 离开当前队伍(确认框) + public bool CmdLeaveTeam() + { + if (IsDead() || m_pTeam == null) + return false; + + string szMsg = CECUIManager.Instance?.GetInGameUIMan()?.GetStringFromTable(235) + ?? "Are you sure you want to leave the team?"; + CECUIManager.Instance?.ShowMessageBoxYesAndNo("", szMsg, null, + () => UnityGameSession.c2s_CmdTeamLeaveParty(), null); + return true; + } + + // Kick one member of team (stub) + // 踢出队员(占位) + public bool CmdKickTeamMember() + { + return true; + } + + // Search for a team (stub) + // 寻找队伍(占位) + public bool CmdFindTeam() + { + return true; + } + + // Start trade with other selected player + // 与选中玩家开始交易 + public bool CmdStartTrade() + { + if (m_pWorkMan.IsSitting()) + { + UnityGameSession.c2s_CmdStandUp(); + return false; + } + + if (!CanDo(ActionCanDo.CANDO_TRADE)) + return false; + + if (!GPDataTypeHelper.ISPLAYERID(m_idSelTarget) || m_idSelTarget == m_PlayerInfo.cid) + return false; + + UnityGameSession.trade_Start(m_idSelTarget); + return true; + } + + // Open booth for selling items + // 开摊出售 + public bool CmdSellBooth() + { + if (IsInvisible()) + { + CECGameRun.Instance?.AddFixedMessage((int)FixedMsg.FIXMSG_CANNOT_USE_WHEN_INVISIBLE); + return false; + } + + if (m_pWorkMan.IsSitting()) + { + UnityGameSession.c2s_CmdStandUp(); + return false; + } + + if (m_pWorkMan.IsFollowing()) + m_pWorkMan.FinishAllWork(true); + + if (!CanDo(ActionCanDo.CANDO_BOOTH)) + return false; + + UnityGameSession.c2s_CmdOpenBoothTest(); + return true; + } + + // Open booth for buying items (stub) + // 开摊收购(占位) + public bool CmdBuyBooth() + { + return true; + } + + // Invite selected player to join faction (stub) + // 邀请入帮(占位) + public bool CmdInviteToFaction() + { + return true; + } + + /*public bool CmdPickup() + { + if (m_pWorkMan.IsSitting()) + { + UnityGameSession.c2s_CmdStandUp(); + return false; + } + + if (!CanDo(ActionCanDo.CANDO_PICKUP)) + return false; + + CECHPWork pWork = m_pWorkMan.GetWork(Host_work_ID.WORK_TRACEOBJECT); + if (pWork is CECHPWorkTrace pTrace && + pTrace.GetTraceReason() == Trace_reason.TRACE_PICKUP) + return true; + + EC_ManMatter matterMan = EC_ManMessageMono.Instance?.EC_ManMatter; + CECMatter pMatter = matterMan?.FindMatterNearHost(10.0f, true); + if (pMatter != null) + PickupObject(pMatter.GetMatterID(), false); + + return true; + }*/ + + public bool CmdGather() + { + if (m_pWorkMan.IsSitting()) + { + UnityGameSession.c2s_CmdStandUp(); + return false; + } + + if (!CanDo(ActionCanDo.CANDO_GATHER)) + return false; + + CECHPWork pWork = m_pWorkMan.GetWork(Host_work_ID.WORK_TRACEOBJECT); + if (pWork is CECHPWorkTrace pTrace && + pTrace.GetTraceReason() == Trace_reason.TRACE_GATHER) + return true; + + PickupObject(m_idSelTarget, true); + return true; + } + + public bool CmdRushFly() + { + if (m_bAboutToDie || IsDead() || !IsFlying()) + return false; + + UnityGameSession.c2s_CmdActiveRushFly(!m_bRushFly); + return true; + } + + public bool CmdBindBuddy(int idTarget) + { + if (!m_BindCmdCoolCnt.IsFull()) + { + CECGameRun.Instance?.AddFixedMessage((int)FixedMsg.FIXMSG_CMD_INCOOLTIME); + return false; + } + + if (!CanDo(ActionCanDo.CANDO_BINDBUDDY) || !GPDataTypeHelper.ISPLAYERID(idTarget) || + idTarget == GetCharacterID()) + return false; + + EC_ElsePlayer pPlayer = m_pPlayerMan?.GetElsePlayer(idTarget); + if (pPlayer == null || pPlayer.GetGender() == GetGender()) + return false; + + A3DVECTOR3 vDist = pPlayer.GetServerPos() - GetPos(); + if (vDist.Magnitude() >= 2.8f) + { + CECGameRun.Instance?.AddFixedMessage((int)FixedMsg.FIXMSG_TARGETISFAR); + return false; + } + + if (GetGender() == GENDER.GENDER_MALE) + UnityGameSession.c2s_CmdBindPlayerInvite(idTarget); + else + UnityGameSession.c2s_CmdBindPlayerRequest(idTarget); + + m_BindCmdCoolCnt.Reset(); + return true; + } + } +} diff --git a/Assets/Scripts/CECHostPlayer.HostCmd.cs.meta b/Assets/Scripts/CECHostPlayer.HostCmd.cs.meta new file mode 100644 index 0000000000..63626f90d5 --- /dev/null +++ b/Assets/Scripts/CECHostPlayer.HostCmd.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 61bcfdcbd8ffcd746b0e59990cc6970b \ No newline at end of file diff --git a/Assets/Scripts/CECHostPlayer.Interaction.cs b/Assets/Scripts/CECHostPlayer.Interaction.cs index c49bd9e17a..1fa25b3a17 100644 --- a/Assets/Scripts/CECHostPlayer.Interaction.cs +++ b/Assets/Scripts/CECHostPlayer.Interaction.cs @@ -135,6 +135,7 @@ namespace BrewMonster int iAction = (int)PLAYER_ACTION_TYPE.ACT_STAND; bool bSession = false; + BMLogger.LogError("HoangDev: idEmote "+idEmote); // Select action according to pose switch (idEmote) { diff --git a/Assets/Scripts/CECHostPlayer.Sit.cs b/Assets/Scripts/CECHostPlayer.Sit.cs new file mode 100644 index 0000000000..b98bfbc372 --- /dev/null +++ b/Assets/Scripts/CECHostPlayer.Sit.cs @@ -0,0 +1,38 @@ +using System; +using BrewMonster.Network; +using BrewMonster.Scripts; +using BrewMonster.Scripts.Task; +using CSNetwork; +using CSNetwork.GPDataType; + +namespace BrewMonster +{ + public partial class CECHostPlayer + { + private void OnMsgPlayerSitDown(ECMSG Msg) + { + if (Convert.ToInt32(Msg.dwParam2) == CommandID.OBJECT_SIT_DOWN) + { + if (m_pWorkMan.IsMovingToPosition() || + m_pWorkMan.IsTracing() || + m_pWorkMan.IsFollowing()) + { + m_MoveCtrl.SendStopMoveCmd(EC_Utility.ToVector3(GetPos()), GetGroundSpeed(), (int)GPMoveMode.GP_MOVE_RUN); + } + + m_dwStates |= (uint)PlayerNPCState.GP_STATE_SITDOWN; + CECHPWorkSit pWork = (CECHPWorkSit)m_pWorkMan.CreateWork(CECHPWork.Host_work_ID.WORK_SIT); + pWork.SetBeSittingFlag(false); + m_pWorkMan.StartWork_p1(pWork); + + GetTaskInterface().SetEmotion((int)TaskInterface.CommandTaskAction.CMD_EMOTION_SITDOWN); + } + else if (Convert.ToInt32(Msg.dwParam2) == CommandID.OBJECT_STAND_UP) + { + m_dwStates &= ~(uint)PlayerNPCState.GP_STATE_SITDOWN; + CECHPWorkStand pWork = (CECHPWorkStand)m_pWorkMan.CreateWork(CECHPWork.Host_work_ID.WORK_STAND); + m_pWorkMan.StartWork_p1(pWork); + } + } + } +} diff --git a/Assets/Scripts/CECHostPlayer.Sit.cs.meta b/Assets/Scripts/CECHostPlayer.Sit.cs.meta new file mode 100644 index 0000000000..7d483133ab --- /dev/null +++ b/Assets/Scripts/CECHostPlayer.Sit.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3b8afadf7e5c78f409d4e8746ef6ea77 \ No newline at end of file diff --git a/Assets/Scripts/CECHostPlayer.cs b/Assets/Scripts/CECHostPlayer.cs index ad0d5795c0..b0ddb19f6b 100644 --- a/Assets/Scripts/CECHostPlayer.cs +++ b/Assets/Scripts/CECHostPlayer.cs @@ -565,6 +565,7 @@ namespace BrewMonster case EC_MsgDef.MSG_HST_COOLTIMEDATA: OnMsgHstCoolTimeData(Msg); break; case EC_MsgDef.MSG_HST_PRESSCANCEL: OnMsgHstPressCancel(Msg); break; case EC_MsgDef.MSG_PM_PLAYERFLY: OnMsgPlayerFly(Msg); break; + case EC_MsgDef.MSG_PM_PLAYERSITDOWN: OnMsgPlayerSitDown(Msg); break; case EC_MsgDef.MSG_HST_PETOPT: OnMsgHstPetOpt(Msg); break; case EC_MsgDef.MSG_HST_SETPLAYERLIMIT: OnMsgHstSetPlayerLimit(Msg); break; case EC_MsgDef.MSG_PM_PLAYERMOUNT: OnMsgPlayerMount(Msg); break; @@ -1327,6 +1328,7 @@ namespace BrewMonster m_IncantCnt = new CECCounter(); m_IncantCnt.SetPeriod(1000); m_IncantCnt.Reset(true); + InitBindCmdCoolCnt(); m_bEnterGame = false; } @@ -3374,7 +3376,8 @@ namespace BrewMonster if (iPose == (int)RoleExpression.ROLEEXP_SITDOWN) { - // UnityGameSession.c2s_CmdSessionEmote(iPose); + BMLogger.LogError("HoangDev: c2s_CmdSessionEmote: "+iPose); + UnityGameSession.c2s_CmdSessionEmote(iPose); } else if (iPose == (int)RoleExpression.ROLEEXP_KISS) { @@ -3655,9 +3658,10 @@ namespace BrewMonster m_PetOptCnt.IncCounter(iRealTime); // Bind command cool counter - /* if (m_BindCmdCoolCnt.IncCounter(dwDeltaTime)) - m_BindCmdCoolCnt.Reset(true); + if (m_BindCmdCoolCnt.IncCounter(iRealTime)) + m_BindCmdCoolCnt.Reset(true); + /* // Auto fashion time counter if (m_bAutoFashion && GetBoothState() != 2 && !IsShapeChanged()) { diff --git a/Assets/Scripts/PlayerVisual.cs b/Assets/Scripts/PlayerVisual.cs index f0f7bed33b..711a9d3af7 100644 --- a/Assets/Scripts/PlayerVisual.cs +++ b/Assets/Scripts/PlayerVisual.cs @@ -221,6 +221,7 @@ namespace BrewMonster { return; } + BMLogger.LogError("HoangDev animationName:"+animationName); bool isState = namedAnimancer.States.TryGet(animationName, out var existingState) ? true : false; if (isState) {