NPCs draw weapon on dead monsters
NPCs sometimes draw their weapon even if the monster is already dead.
https://github.com/AmProsius/gothic-1-community-patch/blob/2775fbc70322594ed1d00c96717143f0c2af5af7/scriptbase/_work/Data/Scripts/Content/AI/ZS_Human/ZS_AssessMonster.d#L36-L40
changed to
PrintGlobals (PD_ZS_CHECK);
if C_NpcIsDown(other)
{
PrintDebugNpc (PD_ZS_CHECK, "...Monster kampfunfähig!");
return;
};
//######## Ist NSC eine WACHE oder BOSS ? ########
The provided fix might not fully work, because the NPC would remain in that AI state and continue to the state loop after the return. An AI_ContinueRoutine(self) before the return would work. UPDATE: I think it wouldn't the NPC would re-detect the monster every few seconds and restart their daily routine every time. Not an option.
Conceptually, I would like to stick to the mentioned assumptions in the comment block above the function, and not even let the state be started in the first place. That would require to find out which of the callers cause the bug: https://github.com/AmProsius/gothic-1-community-patch/blob/2775fbc70322594ed1d00c96717143f0c2af5af7/scriptbase/_work/Data/Scripts/Content/AI/ZS_Human/ZS_AssessMonster.d#L4-L15
@AmProsius, I think I am gonna need an example case where that happens, in order to work on it.
There are so many callers (notes to myself):
- [x] B_AssessEnemy
- [x] B_AssessFighter
- [ ] B_AssessFightSound <-- Intercept call to
AI_StartState, check forZS_AssessMonsterand return if monster is down - [ ] B_AssessWarn <-- Intercept call to
AI_StartState, check forZS_AssessMonsterand return if monster is down - [x] B_ObserveIntruder
- [ ] B_AssessEnterRoom <-- Intercept call to
AI_StartState, check forZS_AssessMonsterand return if monster is down - [ ] ZS_AssessDefeat <-- Intercept call to
AI_StartState, check forZS_AssessMonsterand return if monster is down - [ ] ZS_AssessMurder <-- Intercept call to
AI_StartState, check forZS_AssessMonsterand return if monster is down - [x] ZS_AssessMonster <-- should be safe
- [x] ZS_MCHunting <-- should be safe
In Gothic Mod Fix, this function is rewritten like this:
func void ZS_AssessMonster()
{
C_ZSInit();
// Идентификатор "other" не инициализирован (загрузка сохранения) -> выход.
if(!Hlp_IsValidNpc(other))
{
AI_ContinueRoutine(self);
return;
};
// К моменту старта состояния враг уже мёртв -> выход.
if(C_NpcIsDown(other))
{
AI_ContinueRoutine(self);
return;
};
Npc_SendPassivePerc(self,PERC_ASSESSWARN,other,self);
// Остановка, быстрый поворот к врагу.
B_FullStop(self);
B_WhirlAround(self,other);
// Непись является представителем стражи или боссом -> старт состояния атаки.
if(C_NpcIsGuard(self) || C_NpcIsGuardArcher(self) || C_NpcIsBoss(self))
{
B_Say_Monster(self,other);
B_SetAttackReason(self,AR_GuildEnemy);
Npc_SetTarget(self,other);
AI_StartState(self,ZS_Attack,0,"");
return;
};
// Непись слабее врага -> старт состояния бегства.
if(C_AmIWeaker(self,other))
{
Npc_SetTarget(self,other);
db_say(self,NULL,"ShitWhatAMonster");
AI_StartState(self,ZS_Flee,0,"");
return;
};
// Непись является партнёром по группе -> старт состояния атаки.
if(self.aivar[AIV_PartyMember])
{
B_Say_Monster(self,other);
B_SetAttackReason(self,AR_GuildEnemy);
Npc_SetTarget(self,other);
AI_StartState(self,ZS_Attack,0,"");
return;
};
// Активация восприятий.
Npc_PercEnable(self,PERC_ASSESSMAGIC,B_AssessMagic);
Npc_PercEnable(self,PERC_ASSESSDAMAGE,B_AssessDamage);
Npc_PercEnable(self,PERC_ASSESSTALK,B_RefuseTalk);
Npc_PercEnable(self,PERC_ASSESSSURPRISE,B_AssessSurprise);
// !!!!! Другие восприятия в состоянии?????
// Извлечение оружия.
B_DrawWeapon(self,other);
// Время ожидания.
self.aivar[AIV_StateTime] = Hlp_Random(100)%3 + 3;
};
func int ZS_AssessMonster_Loop()
{
//B_PrintAIInfo("ZS_AssessMonster_Loop",5,8,1);
// Выход, если враг уже мёртв.
if(C_NpcIsDown(other))
{
return LOOP_END;
};
// Выход, если расстояние до врага превышает 18м.
if(Npc_GetDistToNpc(self,other) > HAI_DIST_ABORT_ASSESS_MONSTER)
{
return LOOP_END;
};
// Непись находится в режиме дальнего боя или магического боя -> старт состояния атаки.
if(Npc_IsInFightMode(self,FMODE_FAR) || Npc_IsInFightMode(self,FMODE_MAGIC))
{
Npc_SetTarget(self,other);
B_SetAttackReason(self,AR_GuildEnemy);
AI_StartState(self,ZS_Attack,0,"");
};
// Расстояние до врага стало меньше 10м или время ожидание превысило отведённое значение -> старт состояния атаки.
if((Npc_GetDistToNpc(self,other) < HAI_DIST_ATTACK_MONSTER) || (Npc_GetStateTime(self) > self.aivar[AIV_StateTime]))
{
Npc_SetTarget(self,other);
B_Say_Monster(self,other);
B_SetAttackReason(self,AR_GuildEnemy);
AI_StartState(self,ZS_Attack,0,"");
};
B_SmartTurnToNpc(self,other);
B_SelectWeapon(self,other);
AI_Wait(self,0.5);
return LOOP_CONTINUE;
};
func void ZS_AssessMonster_End()
{
AI_StopLookAt(self);
B_RemoveWeapon(self);
self.aivar[AIV_StateTime] = 0;
};
I think then it should suffice to add a check for C_NpcIsDown(other) in both ZS_AssessMonster and ZS_AssessMonster_Loop to resolve the specific bug discussed here.
The test is not functional yet. I cannot reproduce the problem, and need an example of how to trigger the bug.
This bug still has to be validated. Whoever does this, when doing so, please provide instruction on how to reproduce the bug, such that the test can be adjusted.
Moved to v1.2.0, because of unclear circumstances of the bug.
What's left to do here is to clarify the circumstances that cause this bug. I could not reproduce it. As I never experienced it I don't know when it happens. Someone else will have to provide that information. I will have to remove myself from being assigned.