diff --git a/Assets/Gothic-Core/Scripts/Adapters/Npc/AiHandler.cs b/Assets/Gothic-Core/Scripts/Adapters/Npc/AiHandler.cs index 9a683c22e..ae115eb5f 100644 --- a/Assets/Gothic-Core/Scripts/Adapters/Npc/AiHandler.cs +++ b/Assets/Gothic-Core/Scripts/Adapters/Npc/AiHandler.cs @@ -6,6 +6,8 @@ using Gothic.Core.Extensions; using Gothic.Core.Logging; using Gothic.Core.Models.Vm; +using FreePoint = Gothic.Core.Models.Vob.WayNet.FreePoint; +using WayPoint = Gothic.Core.Models.Vob.WayNet.WayPoint; using Gothic.Core.Services; using Gothic.Core.Services.Config; using Gothic.Core.Services.Npc; @@ -316,6 +318,7 @@ public void ClearState(bool callEndFunction) Properties.AnimationQueue.Clear(); Properties.CurrentAction = new None(new AnimationAction(), NpcData); Properties.CurrentLoopState = NpcProperties.LoopState.None; // i.e. call StartNextState() next frame + Properties.BodyState = VmGothicEnums.BodyState.BsStand; PrefabProps.AnimationSystem.StopAllAnimations(); } @@ -339,7 +342,11 @@ public void ReEnableNpc() { var wp = _wayNetService.GetWayNetPoint(currentRoutine.Waypoint); if (wp != null) + { gameObject.transform.position = _npcService.GetFreeAreaAtSpawnPoint(wp.Position); + Properties.CurrentFreePoint = wp as FreePoint; + Properties.CurrentWayPoint = wp as WayPoint; + } else Logger.LogWarning($"ReEnableNpc: waypoint '{currentRoutine.Waypoint}' not found for {gameObject.name} — NPC will re-enable at current position.", LogCat.Npc); } diff --git a/Assets/Gothic-Core/Scripts/Domain/Npc/Actions/AnimationActions/AbstractWalkAnimationAction2.cs b/Assets/Gothic-Core/Scripts/Domain/Npc/Actions/AnimationActions/AbstractWalkAnimationAction2.cs index 5b5e90322..43578569d 100644 --- a/Assets/Gothic-Core/Scripts/Domain/Npc/Actions/AnimationActions/AbstractWalkAnimationAction2.cs +++ b/Assets/Gothic-Core/Scripts/Domain/Npc/Actions/AnimationActions/AbstractWalkAnimationAction2.cs @@ -59,6 +59,11 @@ protected virtual void StartWalk() { PhysicsService.EnablePhysicsForNpc(PrefabProps); + var walkMode = (VmGothicEnums.WalkMode)Vob.AiHuman.WalkMode; + Props.BodyState = walkMode == VmGothicEnums.WalkMode.Walk + ? VmGothicEnums.BodyState.BsWalk + : VmGothicEnums.BodyState.BsRun; + var animName = AnimationService.GetAnimationName(VmGothicEnums.AnimationType.Move, NpcContainer); PrefabProps.AnimationSystem.PlayAnimation(animName); } @@ -66,6 +71,7 @@ protected virtual void StartWalk() protected virtual void StopWalk() { PhysicsService.EnablePhysicsForNpc(PrefabProps); + Props.BodyState = VmGothicEnums.BodyState.BsStand; var animName = AnimationService.GetAnimationName(VmGothicEnums.AnimationType.Move, NpcContainer); PrefabProps.AnimationSystem.StopAnimation(animName); diff --git a/Assets/Gothic-Core/Scripts/Domain/Npc/Actions/AnimationActions/ContinueRoutine.cs b/Assets/Gothic-Core/Scripts/Domain/Npc/Actions/AnimationActions/ContinueRoutine.cs index 81ee465f8..4021de638 100644 --- a/Assets/Gothic-Core/Scripts/Domain/Npc/Actions/AnimationActions/ContinueRoutine.cs +++ b/Assets/Gothic-Core/Scripts/Domain/Npc/Actions/AnimationActions/ContinueRoutine.cs @@ -1,3 +1,5 @@ +using Gothic.Core.Adapters.Npc; +using Gothic.Core.Logging; using Gothic.Core.Models.Container; namespace Gothic.Core.Domain.Npc.Actions.AnimationActions @@ -10,7 +12,14 @@ public ContinueRoutine(AnimationAction action, NpcContainer npcContainer) : base public override void Start() { - var ai = PrefabProps.AiHandler; + var ai = PrefabProps.AiHandler ?? NpcGo.GetComponent(); + + if (ai == null) + { + Logger.LogWarning($"[ContinueRoutine] AiHandler null on {NpcGo.name} — skipping routine restart", LogCat.Ai); + IsFinishedFlag = true; + return; + } ai.ClearState(false); diff --git a/Assets/Gothic-Core/Scripts/Domain/Npc/Actions/AnimationActions/UseMob.cs b/Assets/Gothic-Core/Scripts/Domain/Npc/Actions/AnimationActions/UseMob.cs index 57831ccc5..8e859c44f 100644 --- a/Assets/Gothic-Core/Scripts/Domain/Npc/Actions/AnimationActions/UseMob.cs +++ b/Assets/Gothic-Core/Scripts/Domain/Npc/Actions/AnimationActions/UseMob.cs @@ -49,7 +49,7 @@ public override void Start() _mobContainer = container; _mobsiScheme = _mobContainer?.Props.GetVisualScheme(); - if (container!.Go == null) + if (container == null || container.Go == null) { IsFinishedFlag = true; return; diff --git a/Assets/Gothic-Core/Scripts/Services/Npc/NpcAiService.cs b/Assets/Gothic-Core/Scripts/Services/Npc/NpcAiService.cs index 0adbe470f..cc15a4870 100644 --- a/Assets/Gothic-Core/Scripts/Services/Npc/NpcAiService.cs +++ b/Assets/Gothic-Core/Scripts/Services/Npc/NpcAiService.cs @@ -207,10 +207,21 @@ public void ExtAiStartState(NpcInstance npc, int action, bool stopCurrentState, { var other = (NpcInstance)_gameStateService.GothicVm.GlobalOther; var victim = (NpcInstance)_gameStateService.GothicVm.GlobalOther; - - npc.GetUserData().Props.AnimationQueue.Enqueue(new StartState( + + var container = npc.GetUserData(); + + if (stopCurrentState) + { + // Abandon current state immediately so the new one starts next frame, not after the whole queue drains. + container.PrefabProps?.AiHandler?.ClearState(false); + container.Props.StateEnd = 0; + container.Props.CurrentWayPoint = null; // forces GoToWp to use nearest WP, not stale pre-interrupt WP + + } + + container.Props.AnimationQueue.Enqueue(new StartState( new AnimationAction(int0: action, bool0: stopCurrentState, string0: wayPointName, instance0: other, instance1: victim), - npc.GetUserData())); + container)); } public void ExtAiLookAt(NpcInstance npc, string wayPointName) @@ -259,7 +270,10 @@ public void ExtAiStandUp(NpcInstance npc) // FIXME - Implement remaining tasks from G1 documentation: // * Ist der Nsc in einem Animatinsstate, wird die passende Rücktransition abgespielt. // * Benutzt der NSC gerade ein MOBSI, poppt er ins stehen. - npc.GetUserData().Props.AnimationQueue.Enqueue(new StandUp(new AnimationAction(), npc.GetUserData())); + var container = npc.GetUserData(); + // Reset immediately (not via queue) so Daedalus C_BodyStateContains checks in the same ZS_*_Loop tick see BsStand. + container.Props.BodyState = VmGothicEnums.BodyState.BsStand; + container.Props.AnimationQueue.Enqueue(new StandUp(new AnimationAction(), container)); } public void ExtAiTurnToNpc(NpcInstance npc, NpcInstance other) diff --git a/Assets/Gothic-Core/Scripts/Services/Npc/NpcHelperService.cs b/Assets/Gothic-Core/Scripts/Services/Npc/NpcHelperService.cs index b3a41e31f..dde9ff724 100644 --- a/Assets/Gothic-Core/Scripts/Services/Npc/NpcHelperService.cs +++ b/Assets/Gothic-Core/Scripts/Services/Npc/NpcHelperService.cs @@ -176,17 +176,13 @@ public bool ExtWldDetectNpcEx(NpcInstance npcInstance, int specificNpcIndex, int public int ExtNpcGetDistToWp(NpcInstance npc, string waypointName) { var npcGo = GetNpc(npc); - var npcPos = npcGo.transform.position; - var waypoint = _wayNetService.GetWayNetPoint(waypointName); - if (waypoint == null || !npcGo) - { + if (!npcGo || waypoint == null) return int.MaxValue; - } // *100 as Gothic metrics are in cm, not m. - return (int)(Vector3.Distance(npcPos, waypoint.Position) * 100); + return (int)(Vector3.Distance(npcGo.transform.position, waypoint.Position) * 100); } public int ExtNpcGetTalentSkill(NpcInstance npc, int skillId)