66using Gothic . Core . Extensions ;
77using Gothic . Core . Logging ;
88using Gothic . Core . Models . Vm ;
9+ using FreePoint = Gothic . Core . Models . Vob . WayNet . FreePoint ;
10+ using WayPoint = Gothic . Core . Models . Vob . WayNet . WayPoint ;
911using Gothic . Core . Services ;
1012using Gothic . Core . Services . Config ;
1113using Gothic . Core . Services . Npc ;
@@ -52,12 +54,23 @@ private void Start()
5254 /// </summary>
5355 private void Update ( )
5456 {
55- // If NPC/Monster is dead, stop any further process logic.
57+ // If NPC/Monster is dead, only play out the already queued animations (e.g. the dying animation
58+ // enqueued by FightService), then stop any further process logic.
5659 if ( Properties . BodyState == VmGothicEnums . BodyState . BsDead )
5760 {
58- enabled = false ;
61+ Properties . CurrentAction . Tick ( ) ;
62+
63+ if ( Properties . CurrentAction . IsFinished ( ) )
64+ {
65+ if ( Properties . AnimationQueue . Count > 0 )
66+ PlayNextAnimation ( Properties . AnimationQueue . Dequeue ( ) ) ;
67+ else
68+ enabled = false ;
69+ }
70+
71+ return ;
5972 }
60-
73+
6174 ExecuteActivePerceptions ( ) ;
6275 ExecuteStates ( ) ;
6376
@@ -87,7 +100,6 @@ private void Update()
87100 Vm . GlobalOther = Vm . GlobalHero ;
88101 }
89102
90- DaedalusSymbol loopSymbol ;
91103 switch ( Properties . CurrentLoopState )
92104 {
93105 // None means, the NPC is newly created and didn't execute any Routine as of now OR a State was changed via Daedalus scripts.
@@ -135,7 +147,9 @@ private void Update()
135147 // Go on
136148 else
137149 {
138- Logger . Log ( $ "Start playing >{ Properties . AnimationQueue . Peek ( ) . GetType ( ) } < on >{ Go . transform . parent . name } <", LogCat . Ai ) ;
150+ // Editor-only: this fires for every dequeued action of every NPC - the string interpolation
151+ // plus file sink would be measurable noise on device.
152+ Logger . LogEditor ( $ "Start playing >{ Properties . AnimationQueue . Peek ( ) . GetType ( ) } < on >{ Go . transform . parent . name } <", LogCat . Ai ) ;
139153 PlayNextAnimation ( Properties . AnimationQueue . Dequeue ( ) ) ;
140154 }
141155 }
@@ -180,18 +194,25 @@ private void ExecuteActivePerceptions()
180194 return ;
181195 }
182196
183- _npcAiService . UpdateEnemyNpc ( NpcInstance ) ;
197+ var hero = ( NpcInstance ) _gameStateService . GothicVm . GlobalHero ;
198+ var assessPlayerRange = _npcHelperService . GetPerceptionRange ( VmGothicEnums . PerceptionType . AssessPlayer ) ;
184199
185- // FIXME - CanSense is not separating between smell, hear, and see as of now. Please add functionality.
186- if ( _npcHelperService . CanSenseNpc ( NpcInstance , ( NpcInstance ) _gameStateService . GothicVm . GlobalHero , false ) )
200+ if ( _npcHelperService . CanSenseNpc ( NpcInstance , hero , false , assessPlayerRange ) )
187201 {
188- _npcAiService . ExecutePerception ( VmGothicEnums . PerceptionType . AssessPlayer , Properties , NpcInstance , null , ( NpcInstance ) _gameStateService . GothicVm . GlobalHero ) ;
202+ _npcAiService . ExecutePerception ( VmGothicEnums . PerceptionType . AssessPlayer , Properties , NpcInstance , null , hero ) ;
189203 }
190204
191- // FIXME - Throws a lot of errors and warnings when NPCs are nearby monsters (e.g. Bridge guard next to OC)
192- if ( Properties . EnemyNpc != null )
205+ // Scanning all NPCs for the closest enemy is expensive - only do it for NPCs that react to enemies at all.
206+ if ( Properties . Perceptions . TryGetValue ( VmGothicEnums . PerceptionType . AssessEnemy , out var enemyPerception ) &&
207+ enemyPerception >= 0 )
193208 {
194- _npcAiService . ExecutePerception ( VmGothicEnums . PerceptionType . AssessEnemy , Properties , NpcInstance , null , Properties . EnemyNpc ) ;
209+ _npcAiService . UpdateEnemyNpc ( NpcInstance ) ;
210+
211+ // FIXME - Throws a lot of errors and warnings when NPCs are nearby monsters (e.g. Bridge guard next to OC)
212+ if ( Properties . EnemyNpc != null )
213+ {
214+ _npcAiService . ExecutePerception ( VmGothicEnums . PerceptionType . AssessEnemy , Properties , NpcInstance , null , Properties . EnemyNpc ) ;
215+ }
195216 }
196217
197218
@@ -267,6 +288,10 @@ public void StartRoutine(int action)
267288 var routineSymbol = Vm . GetSymbolByIndex ( action ) ! ;
268289 Vob . CurrentStateName = routineSymbol . Name ;
269290
291+ // Reset the previous routine's symbols: a new ZS without own _Loop/_End must not call the old ones.
292+ Properties . StateLoop = 0 ;
293+ Properties . StateEnd = 0 ;
294+
270295 var symbolLoop = Vm . GetSymbolByName ( $ "{ routineSymbol . Name } _Loop") ;
271296 if ( symbolLoop != null )
272297 {
@@ -316,6 +341,7 @@ public void ClearState(bool callEndFunction)
316341 Properties . AnimationQueue . Clear ( ) ;
317342 Properties . CurrentAction = new None ( new AnimationAction ( ) , NpcData ) ;
318343 Properties . CurrentLoopState = NpcProperties . LoopState . None ; // i.e. call StartNextState() next frame
344+ Properties . BodyState = VmGothicEnums . BodyState . BsStand ;
319345
320346 PrefabProps . AnimationSystem . StopAllAnimations ( ) ;
321347 }
@@ -339,7 +365,11 @@ public void ReEnableNpc()
339365 {
340366 var wp = _wayNetService . GetWayNetPoint ( currentRoutine . Waypoint ) ;
341367 if ( wp != null )
368+ {
342369 gameObject . transform . position = _npcService . GetFreeAreaAtSpawnPoint ( wp . Position ) ;
370+ Properties . CurrentFreePoint = wp as FreePoint ;
371+ Properties . CurrentWayPoint = wp as WayPoint ;
372+ }
343373 else
344374 Logger . LogWarning ( $ "ReEnableNpc: waypoint '{ currentRoutine . Waypoint } ' not found for { gameObject . name } — NPC will re-enable at current position.", LogCat . Npc ) ;
345375 }
0 commit comments