Files
perfect-dark/src/game/chraction.c
T
2023-05-04 19:37:20 +10:00

14524 lines
397 KiB
C

#include <ultra64.h>
#include "constants.h"
#include "game/chraction.h"
#include "game/chrai.h"
#include "game/dlights.h"
#include "game/footstep.h"
#include "game/game_006900.h"
#include "game/pdmode.h"
#include "game/chr.h"
#include "game/body.h"
#include "game/prop.h"
#include "game/propsnd.h"
#include "game/objectives.h"
#include "game/atan2f.h"
#include "game/acosfasinf.h"
#include "game/bondgun.h"
#include "game/gunfx.h"
#include "game/game_0b0fd0.h"
#include "game/modelmgr.h"
#include "game/tex.h"
#include "game/camera.h"
#include "game/player.h"
#include "game/inv.h"
#include "game/playermgr.h"
#include "game/explosions.h"
#include "game/sparks.h"
#include "game/bg.h"
#include "game/game_1531a0.h"
#include "game/stagetable.h"
#include "game/env.h"
#include "game/lv.h"
#include "game/bot.h"
#include "game/botact.h"
#include "game/botcmd.h"
#include "game/botinv.h"
#include "game/mplayer/mplayer.h"
#include "game/pad.h"
#include "game/padhalllv.h"
#include "game/pak.h"
#include "game/propobj.h"
#include "game/wallhit.h"
#include "game/mpstats.h"
#include "bss.h"
#include "lib/joy.h"
#include "lib/lib_17ce0.h"
#include "lib/main.h"
#include "lib/model.h"
#include "lib/snd.h"
#include "lib/rng.h"
#include "lib/mtx.h"
#include "lib/ailist.h"
#include "lib/anim.h"
#include "lib/collision.h"
#include "lib/vi.h"
#include "data.h"
#include "types.h"
s32 g_RecentQuipsPlayed[5];
u32 var8009cd84;
u32 var8009cd88;
u32 var8009cd8c;
u32 var8009cd90;
u32 var8009cd94;
u8 g_RecentQuipsIndex;
f32 g_EnemyAccuracyScale = 1;
f32 g_PlayerDamageRxScale = 1;
f32 g_PlayerDamageTxScale = 1;
f32 g_AttackWalkDurationScale = 1;
struct animtablerow g_DeathAnimsHumanLfoot[] = {
{ ANIM_DEATH_0020, 0, -1, 0.5, 0, 26, -1 },
{ 0, 0, 0, 0.5, 0, -1, -1 },
};
struct animtablerow g_DeathAnimsHumanLshin[] = {
{ ANIM_DEATH_0020, 0, -1, 0.5, 0, 26, -1 },
{ 0, 0, 0, 0.5, 0, -1, -1 },
};
struct animtablerow g_DeathAnimsHumanLthigh[] = {
{ ANIM_DEATH_0020, 0, -1, 0.5, 1, 26, -1 },
{ ANIM_DEATH_STOMACH_LONG, 1, -1, 0.5, 0, -1, -1 },
{ ANIM_0092, 1, -1, 0.4, 0, 42, 103 },
{ ANIM_0258, 1, -1, 0.5, 0, 43, 100 },
{ 0, 0, 0, 0.5, 0, -1, -1 },
};
struct animtablerow g_DeathAnimsHumanRfoot[] = {
{ ANIM_DEATH_0020, 1, -1, 0.5, 0, 26, -1 },
{ 0, 0, 0, 0.5, 0, -1, -1 },
};
struct animtablerow g_DeathAnimsHumanRshin[] = {
{ ANIM_DEATH_0020, 1, -1, 0.5, 0, 26, -1 },
{ 0, 0, 0, 0.5, 0, -1, -1 },
};
struct animtablerow g_DeathAnimsHumanRthigh[] = {
{ ANIM_DEATH_0020, 1, -1, 0.5, 1, 26, -1 },
{ ANIM_DEATH_STOMACH_LONG, 0, -1, 0.5, 0, -1, -1 },
{ ANIM_0092, 0, -1, 0.4, 0, 42, 103 },
{ ANIM_0258, 0, -1, 0.5, 0, 43, 100 },
{ 0, 0, 0, 0.5, 0, -1, -1 },
};
struct animtablerow g_DeathAnimsHumanPelvis[] = {
{ ANIM_DEATH_001A, 0, -1, 0.5, 0, 55, 39 },
{ ANIM_DEATH_001A, 1, -1, 0.5, 0, 55, 39 },
{ ANIM_DEATH_001C, 0, -1, 0.5, 1, 29, -1 },
{ ANIM_DEATH_001C, 1, -1, 0.5, 1, 29, -1 },
{ ANIM_DEATH_0021, 0, -1, 0.5, 0, 97, 64 },
{ ANIM_DEATH_0021, 1, -1, 0.5, 0, 97, 64 },
{ ANIM_DEATH_0023, 0, -1, 0.5, 0, 31, -1 },
{ ANIM_DEATH_0023, 1, -1, 0.5, 0, 31, -1 },
{ ANIM_DEATH_0024, 0, -1, 0.5, 0, 36, -1 },
{ ANIM_DEATH_0024, 1, -1, 0.5, 0, 36, -1 },
{ ANIM_DEATH_0025, 0, -1, 0.5, 0, 28, -1 },
{ ANIM_DEATH_0025, 1, -1, 0.5, 0, 28, -1 },
{ ANIM_0090, 0, -1, 0.6, 0, 157, 234 },
{ ANIM_0090, 1, -1, 0.6, 0, 157, 234 },
{ ANIM_0091, 0, -1, 0.6, 0, 75, 265 },
{ ANIM_0091, 1, -1, 0.6, 0, 75, 265 },
{ ANIM_0250, 0, -1, 0.5, 0, 65, 105 },
{ ANIM_0250, 1, -1, 0.5, 0, 65, 105 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_DeathAnimsHumanHead[] = {
{ ANIM_DEATH_001A, 0, -1, 0.5, 0, 55, 39 },
{ ANIM_DEATH_001A, 1, -1, 0.5, 0, 55, 39 },
{ ANIM_DEATH_001C, 0, -1, 0.5, 1, 29, -1 },
{ ANIM_DEATH_001C, 1, -1, 0.5, 1, 29, -1 },
{ ANIM_DEATH_0020, 0, -1, 0.5, 1, 26, -1 },
{ ANIM_DEATH_0020, 1, -1, 0.5, 1, 26, -1 },
{ ANIM_DEATH_0021, 0, -1, 0.5, 0, 97, 64 },
{ ANIM_DEATH_0021, 1, -1, 0.5, 0, 97, 64 },
{ ANIM_DEATH_0022, 0, -1, 0.5, 0, 94, 66 },
{ ANIM_DEATH_0022, 1, -1, 0.5, 0, 94, 66 },
{ ANIM_DEATH_0023, 0, -1, 0.5, 0, 31, -1 },
{ ANIM_DEATH_0023, 1, -1, 0.5, 0, 31, -1 },
{ ANIM_DEATH_0024, 0, -1, 0.5, 0, 36, -1 },
{ ANIM_DEATH_0024, 1, -1, 0.5, 0, 36, -1 },
{ ANIM_DEATH_0025, 0, -1, 0.5, 0, 28, -1 },
{ ANIM_DEATH_0025, 1, -1, 0.5, 0, 28, -1 },
{ ANIM_0038, 0, -1, 0.5, 0, -1, -1 },
{ ANIM_0038, 1, -1, 0.5, 0, -1, -1 },
{ ANIM_0251, 0, -1, 0.5, 0, 132, 201 },
{ ANIM_0251, 1, -1, 0.5, 0, 132, 201 },
{ ANIM_0252, 0, -1, 0.5, 0, 83, 150 },
{ ANIM_0252, 1, -1, 0.5, 0, 83, 150 },
{ ANIM_0256, 0, -1, 0.5, 0, 63, -1 },
{ ANIM_0256, 1, -1, 0.5, 0, 63, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_DeathAnimsHumanLhand[] = {
{ ANIM_DEATH_0020, 0, -1, 0.5, 0, 26, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_DeathAnimsHumanForearm[] = {
{ ANIM_DEATH_0020, 0, -1, 0.5, 0, 26, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_DeathAnimsHumanLbicep[] = {
{ ANIM_DEATH_0020, 0, -1, 0.5, 1, 26, -1 },
{ ANIM_008F, 1, -1, 0.45, 1, 52, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_DeathAnimsHumanRhand[] = {
{ ANIM_DEATH_0020, 1, -1, 0.5, 0, 26, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_DeathAnimsHumanRforearm[] = {
{ ANIM_DEATH_0020, 1, -1, 0.5, 0, 26, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_DeathAnimsHumanRbicep[] = {
{ ANIM_DEATH_0020, 1, -1, 0.5, 1, 26, -1 },
{ ANIM_008F, 0, -1, 0.45, 1, 52, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_DeathAnimsHumanTorso[] = {
{ ANIM_DEATH_001A, 0, -1, 0.5, 0, 55, 39 },
{ ANIM_DEATH_001A, 1, -1, 0.5, 0, 55, 39 },
{ ANIM_DEATH_001C, 0, -1, 0.5, 1, 29, -1 },
{ ANIM_DEATH_001C, 1, -1, 0.5, 1, 29, -1 },
{ ANIM_DEATH_0020, 0, -1, 0.5, 1, 26, -1 },
{ ANIM_DEATH_0020, 1, -1, 0.5, 1, 26, -1 },
{ ANIM_DEATH_0021, 0, -1, 0.5, 0, 97, 64 },
{ ANIM_DEATH_0021, 1, -1, 0.5, 0, 97, 64 },
{ ANIM_DEATH_0022, 0, -1, 0.5, 0, 94, 66 },
{ ANIM_DEATH_0022, 1, -1, 0.5, 0, 94, 66 },
{ ANIM_DEATH_0023, 0, -1, 0.5, 0, 31, -1 },
{ ANIM_DEATH_0023, 1, -1, 0.5, 0, 31, -1 },
{ ANIM_DEATH_0024, 0, -1, 0.5, 0, 36, -1 },
{ ANIM_DEATH_0024, 1, -1, 0.5, 0, 36, -1 },
{ ANIM_DEATH_0025, 0, -1, 0.5, 0, 28, -1 },
{ ANIM_DEATH_0025, 1, -1, 0.5, 0, 28, -1 },
{ ANIM_024E, 0, -1, 0.4, 0, 60, -1 },
{ ANIM_024E, 1, -1, 0.4, 0, 60, -1 },
{ ANIM_024F, 0, -1, 0.5, 0, 49, 80 },
{ ANIM_024F, 1, -1, 0.5, 0, 49, 80 },
{ ANIM_0253, 0, -1, 0.5, 1, 22, -1 },
{ ANIM_0253, 1, -1, 0.5, 1, 22, -1 },
{ ANIM_0254, 0, -1, 0.5, 0, 52, 75 },
{ ANIM_0254, 1, -1, 0.5, 0, 52, 75 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_DeathAnimsHumanGun[] = {
{ ANIM_DEATH_001A, 0, -1, 0.5, 0, 55, 39 },
{ ANIM_DEATH_001A, 1, -1, 0.5, 0, 55, 39 },
{ ANIM_DEATH_001C, 0, -1, 0.5, 1, 29, -1 },
{ ANIM_DEATH_001C, 1, -1, 0.5, 1, 29, -1 },
{ ANIM_DEATH_0021, 0, -1, 0.5, 0, 97, 64 },
{ ANIM_DEATH_0021, 1, -1, 0.5, 0, 97, 64 },
{ ANIM_DEATH_0022, 0, -1, 0.5, 0, 94, 66 },
{ ANIM_DEATH_0022, 1, -1, 0.5, 0, 94, 66 },
{ ANIM_DEATH_0023, 0, -1, 0.5, 0, 31, -1 },
{ ANIM_DEATH_0023, 1, -1, 0.5, 0, 31, -1 },
{ ANIM_DEATH_0024, 0, -1, 0.5, 0, 36, -1 },
{ ANIM_DEATH_0024, 1, -1, 0.5, 0, 36, -1 },
{ ANIM_DEATH_0025, 0, -1, 0.5, 0, 28, -1 },
{ ANIM_DEATH_0025, 1, -1, 0.5, 0, 28, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_AnimTableHumanSlumped[] = {
{ ANIM_0019, 0, -1, 0.5, 0, 67, 54 },
{ ANIM_0019, 1, -1, 0.5, 0, 67, 54 },
{ ANIM_0257, 0, -1, 0.5, 0, 15, 80 },
{ ANIM_0257, 1, -1, 0.5, 0, 15, 80 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_InjuryAnimsHumanLfoot[] = {
{ ANIM_0014, 0, -1, 0.5, 0, -1, -1 },
{ ANIM_0015, 1, -1, 0.5, 0, -1, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_InjuryAnimsHumanLshin[] = {
{ ANIM_0014, 0, -1, 0.5, 0, -1, -1 },
{ ANIM_0015, 1, -1, 0.5, 0, -1, -1 },
{ ANIM_00BC, 0, -1, 0.5, 0, -1, -1 },
{ ANIM_00BD, 0, -1, 0.5, 0, -1, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_InjuryAnimsHumanLthigh[] = {
{ ANIM_0014, 0, -1, 0.5, 0, -1, -1 },
{ ANIM_0015, 1, -1, 0.5, 0, -1, -1 },
{ ANIM_DEATH_STOMACH_LONG, 1, 20, 0.4, 0, -1, -1 },
{ ANIM_00BA, 0, -1, 0.5, 0, -1, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_InjuryAnimsHumanRfoot[] = {
{ ANIM_0015, 0, -1, 0.5, 0, -1, -1 },
{ ANIM_0014, 1, -1, 0.5, 0, -1, -1 },
{ ANIM_0236, 0, -1, 0.5, 0, -1, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_InjuryAnimsHumanRshin[] = {
{ ANIM_0015, 0, -1, 0.5, 0, -1, -1 },
{ ANIM_0014, 1, -1, 0.5, 0, -1, -1 },
{ ANIM_00BE, 0, -1, 0.5, 0, -1, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_InjuryAnimsHumanRthigh[] = {
{ ANIM_0015, 0, -1, 0.5, 0, -1, -1 },
{ ANIM_0014, 1, -1, 0.5, 0, -1, -1 },
{ ANIM_DEATH_STOMACH_LONG, 0, 20, 0.4, 0, -1, -1 },
{ ANIM_00BF, 0, -1, 0.5, 0, -1, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_InjuryAnimsHumanPelvis[] = {
{ ANIM_DEATH_0022, 0, 20, 0.5, 0, -1, -1 },
{ ANIM_DEATH_0022, 1, 20, 0.5, 0, -1, -1 },
{ ANIM_DEATH_001A, 0, 15, 0.5, 0, -1, -1 },
{ ANIM_DEATH_001A, 1, 15, 0.5, 0, -1, -1 },
{ ANIM_DEATH_0023, 0, 10, 0.25, 0, -1, -1 },
{ ANIM_DEATH_0023, 1, 10, 0.25, 0, -1, -1 },
{ ANIM_00DA, 0, -1, 0.5, 0, -1, -1 },
{ ANIM_00F4, 0, -1, 0.5, 0, -1, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_InjuryAnimsHumanHead[] = {
{ ANIM_DEATH_0022, 0, 20, 0.5, 0, -1, -1 },
{ ANIM_DEATH_0022, 1, 20, 0.5, 0, -1, -1 },
{ ANIM_DEATH_001A, 0, 15, 0.5, 0, -1, -1 },
{ ANIM_DEATH_001A, 1, 15, 0.5, 0, -1, -1 },
{ ANIM_00F8, 0, -1, 0.5, 0, -1, -1 },
{ ANIM_00FB, 0, -1, 0.5, 0, -1, -1 },
{ ANIM_0101, 0, -1, 0.5, 0, -1, -1 },
{ ANIM_0113, 0, -1, 0.5, 0, -1, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_InjuryAnimsHumanLhand[] = {
{ ANIM_0012, 0, -1, 0.5, 0, -1, -1 },
{ ANIM_0013, 1, -1, 0.5, 0, -1, -1 },
{ ANIM_00B8, 0, -1, 0.5, 0, -1, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_InjuryAnimsHumanForearm[] = {
{ ANIM_0010, 0, -1, 0.5, 0, -1, -1 },
{ ANIM_0011, 1, -1, 0.5, 0, -1, -1 },
{ ANIM_00B4, 0, -1, 0.5, 0, -1, -1 },
{ ANIM_021B, 0, -1, 0.5, 0, -1, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_InjuryAnimsHumanLbicep[] = {
{ ANIM_000E, 0, -1, 0.5, 0, -1, -1 },
{ ANIM_000F, 1, -1, 0.5, 0, -1, -1 },
{ ANIM_DEATH_0022, 0, 20, 0.5, 0, -1, -1 },
{ ANIM_00B0, 0, -1, 0.5, 0, -1, -1 },
{ ANIM_00B1, 0, -1, 0.5, 0, -1, -1 },
{ ANIM_021C, 0, -1, 0.5, 0, -1, -1 },
{ ANIM_00B5, 0, -1, 0.5, 0, -1, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_InjuryAnimsHumanRhand[] = {
{ ANIM_0013, 0, -1, 0.5, 0, -1, -1 },
{ ANIM_0012, 1, -1, 0.5, 0, -1, -1 },
{ ANIM_00B9, 0, -1, 0.5, 0, -1, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_InjuryAnimsHumanRforearm[] = {
{ ANIM_0011, 0, -1, 0.5, 0, -1, -1 },
{ ANIM_0010, 1, -1, 0.5, 0, -1, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_InjuryAnimsHumanRbicep[] = {
{ ANIM_000F, 0, -1, 0.5, 0, -1, -1 },
{ ANIM_000E, 1, -1, 0.5, 0, -1, -1 },
{ ANIM_DEATH_0022, 1, 20, 0.5, 0, -1, -1 },
{ ANIM_0190, 0, -1, 0.5, 0, -1, -1 },
{ ANIM_00B2, 0, -1, 0.5, 0, -1, -1 },
{ ANIM_00B3, 0, -1, 0.5, 0, -1, -1 },
{ ANIM_00B6, 0, -1, 0.5, 0, -1, -1 },
{ ANIM_00B7, 0, -1, 0.5, 0, -1, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_InjuryAnimsHumanTorso[] = {
{ ANIM_DEATH_0022, 0, 20, 0.5, 0, -1, -1 },
{ ANIM_DEATH_0022, 1, 20, 0.5, 0, -1, -1 },
{ ANIM_DEATH_001A, 0, 15, 0.5, 0, -1, -1 },
{ ANIM_DEATH_001A, 1, 15, 0.5, 0, -1, -1 },
{ ANIM_0114, 1, -1, 0.5, 0, -1, -1 },
{ ANIM_0130, 0, -1, 0.5, 0, -1, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_InjuryAnimsHumanGun[] = {
{ ANIM_DEATH_0022, 0, 20, 0.5, 0, -1, -1 },
{ ANIM_DEATH_0022, 1, 20, 0.5, 0, -1, -1 },
{ ANIM_DEATH_001A, 0, 15, 0.5, 0, -1, -1 },
{ ANIM_DEATH_001A, 1, 15, 0.5, 0, -1, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_DeathAnimsSkedarLfoot[] = {
{ ANIM_0337, 1, -1, 0.5, 0, -1, -1 },
{ ANIM_033C, 1, -1, 0.5, 0, -1, -1 },
{ ANIM_033B, 1, -1, 0.5, 0, -1, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_DeathAnimsSkedarLshin[] = {
{ ANIM_0337, 1, -1, 0.5, 0, -1, -1 },
{ ANIM_033C, 1, -1, 0.5, 0, -1, -1 },
{ ANIM_033B, 1, -1, 0.5, 0, -1, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_DeathAnimsSkedarLthigh[] = {
{ ANIM_0337, 1, -1, 0.5, 0, -1, -1 },
{ ANIM_033C, 1, -1, 0.5, 0, -1, -1 },
{ ANIM_033B, 1, -1, 0.5, 0, -1, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_DeathAnimsSkedarRfoot[] = {
{ ANIM_0337, 0, -1, 0.5, 0, -1, -1 },
{ ANIM_033C, 0, -1, 0.5, 0, -1, -1 },
{ ANIM_033B, 0, -1, 0.5, 0, -1, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_DeathAnimsSkedarRshin[] = {
{ ANIM_0337, 0, -1, 0.5, 0, -1, -1 },
{ ANIM_033C, 0, -1, 0.5, 0, -1, -1 },
{ ANIM_033B, 0, -1, 0.5, 0, -1, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_DeathAnimsSkedarRthigh[] = {
{ ANIM_0337, 0, -1, 0.5, 0, -1, -1 },
{ ANIM_033C, 0, -1, 0.5, 0, -1, -1 },
{ ANIM_033B, 0, -1, 0.5, 0, -1, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_DeathAnimsSkedarPelvis[] = {
{ ANIM_0336, 0, -1, 0.5, 0, -1, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_DeathAnimsSkedarHead[] = {
{ ANIM_0339, 0, -1, 0.5, 0, -1, -1 },
{ ANIM_0338, 0, -1, 0.5, 0, -1, -1 },
{ ANIM_033A, 0, -1, 0.5, 0, -1, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_DeathAnimsSkedarLhand[] = {
{ ANIM_0336, 0, -1, 0.5, 0, -1, -1 },
{ ANIM_033D, 0, -1, 0.5, 0, -1, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_DeathAnimsSkedarLforearm[] = {
{ ANIM_0336, 0, -1, 0.5, 0, -1, -1 },
{ ANIM_033D, 0, -1, 0.5, 0, -1, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_DeathAnimsSkedarLbicep[] = {
{ ANIM_0336, 0, -1, 0.5, 0, -1, -1 },
{ ANIM_033D, 0, -1, 0.5, 0, -1, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_DeathAnimsSkedarRhand[] = {
{ ANIM_0336, 0, -1, 0.5, 0, -1, -1 },
{ ANIM_033D, 0, -1, 0.5, 0, -1, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_DeathAnimsSkedarRforearm[] = {
{ ANIM_0336, 0, -1, 0.5, 0, -1, -1 },
{ ANIM_033D, 0, -1, 0.5, 0, -1, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_DeathAnimsSkedarRbicep[] = {
{ ANIM_0336, 0, -1, 0.5, 0, -1, -1 },
{ ANIM_033D, 0, -1, 0.5, 0, -1, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_DeathAnimsSkedarTorso[] = {
{ ANIM_0336, 0, -1, 0.5, 0, -1, -1 },
{ ANIM_033D, 0, -1, 0.5, 0, -1, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_DeathAnimsSkedarGun[] = {
{ ANIM_0336, 0, -1, 0.5, 0, -1, -1 },
{ ANIM_033D, 0, -1, 0.5, 0, -1, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_DeathAnimsSkedarTail[] = {
{ ANIM_0336, 0, -1, 0.5, 0, -1, -1 },
{ ANIM_033D, 0, -1, 0.5, 0, -1, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_InjuryAnimsSkedarLfoot[] = {
{ ANIM_038E, 0, -1, 1, 0, -1, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_InjuryAnimsSkedarLshin[] = {
{ ANIM_0390, 0, -1, 0.9, 0, -1, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_InjuryAnimsSkedarLthigh[] = {
{ ANIM_0390, 0, -1, 0.9, 0, -1, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_InjuryAnimsSkedarRfoot[] = {
{ ANIM_038E, 1, -1, 0.5, 0, -1, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_InjuryAnimsSkedarRshin[] = {
{ ANIM_0390, 1, -1, 0.9, 0, -1, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_InjuryAnimsSkedarRthigh[] = {
{ ANIM_0390, 1, -1, 0.9, 0, -1, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_InjuryAnimsSkedarPelvis[] = {
{ ANIM_038C, 0, -1, 0.9, 0, -1, -1 },
{ ANIM_038C, 1, -1, 0.9, 0, -1, -1 },
{ ANIM_0341, 0, -1, 0.5, 0, -1, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_InjuryAnimsSkedarHead[] = {
{ ANIM_0341, 0, -1, 0.5, 0, -1, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_InjuryAnimsSkedarLhand[] = {
{ ANIM_0343, 0, -1, 0.5, 0, -1, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_InjuryAnimsSkedarLforearm[] = {
{ ANIM_038B, 0, -1, 0.9, 0, -1, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_InjuryAnimsSkedarLbicep[] = {
{ ANIM_038B, 0, -1, 0.9, 0, -1, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_InjuryAnimsSkedarRhand[] = {
{ ANIM_0343, 1, -1, 0.5, 0, -1, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_InjuryAnimsSkedarRforearm[] = {
{ ANIM_038B, 1, -1, 0.9, 0, -1, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_InjuryAnimsSkedarRbicep[] = {
{ ANIM_038B, 1, -1, 0.9, 0, -1, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_InjuryAnimsSkedarTorso[] = {
{ ANIM_038C, 0, -1, 0.9, 0, -1, -1 },
{ ANIM_038C, 1, -1, 0.9, 0, -1, -1 },
{ ANIM_0341, 0, -1, 0.5, 0, -1, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_InjuryAnimsSkedarGun[] = {
{ ANIM_0343, 0, -1, 0.5, 0, -1, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_InjuryAnimsSkedarTail[] = {
{ ANIM_0341, 0, -1, 0.5, 0, -1, -1 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtablerow g_SpecialDieAnims[] = {
{ ANIM_SPECIALDIE_FALLBACK, 0, -1, 0.5, 0, 149, 175 },
{ ANIM_SPECIALDIE_ROLL1, 0, -1, 0.5, 0, 115, 152 },
{ ANIM_SPECIALDIE_ROLL2, 0, -1, 0.5, 0, 115, 152 },
{ ANIM_SPECIALDIE_ROLL3, 0, -1, 0.5, 0, 115, 152 },
{ ANIM_SPECIALDIE_OVERRAILING, 0, -1, 0.5, 0, 83, 99 },
{ ANIM_022B, 0, -1, 0.5, 0, 0, 0 },
{ ANIM_022C, 0, -1, 0.5, 0, 0, 0 },
{ ANIM_022D, 0, -1, 0.5, 0, 0, 0 },
{ ANIM_022E, 0, -1, 0.5, 0, 0, 0 },
{ 0, 0, -1, 0.5, 0, -1, -1 },
};
struct animtable g_AnimTablesHuman[] = {
{ 0, NULL, NULL, 0, 0 },
{ HITPART_LFOOT, g_DeathAnimsHumanLfoot, g_InjuryAnimsHumanLfoot, 0, 0 },
{ HITPART_LSHIN, g_DeathAnimsHumanLshin, g_InjuryAnimsHumanLshin, 0, 0 },
{ HITPART_LTHIGH, g_DeathAnimsHumanLthigh, g_InjuryAnimsHumanLthigh, 0, 0 },
{ HITPART_RFOOT, g_DeathAnimsHumanRfoot, g_InjuryAnimsHumanRfoot, 0, 0 },
{ HITPART_RSHIN, g_DeathAnimsHumanRshin, g_InjuryAnimsHumanRshin, 0, 0 },
{ HITPART_RTHIGH, g_DeathAnimsHumanRthigh, g_InjuryAnimsHumanRthigh, 0, 0 },
{ HITPART_PELVIS, g_DeathAnimsHumanPelvis, g_InjuryAnimsHumanPelvis, 0, 0 },
{ HITPART_HEAD, g_DeathAnimsHumanHead, g_InjuryAnimsHumanHead, 0, 0 },
{ HITPART_LHAND, g_DeathAnimsHumanLhand, g_InjuryAnimsHumanLhand, 0, 0 },
{ HITPART_LFOREARM, g_DeathAnimsHumanForearm, g_InjuryAnimsHumanForearm, 0, 0 },
{ HITPART_LBICEP, g_DeathAnimsHumanLbicep, g_InjuryAnimsHumanLbicep, 0, 0 },
{ HITPART_RHAND, g_DeathAnimsHumanRhand, g_InjuryAnimsHumanRhand, 0, 0 },
{ HITPART_RFOREARM, g_DeathAnimsHumanRforearm, g_InjuryAnimsHumanRforearm, 0, 0 },
{ HITPART_RBICEP, g_DeathAnimsHumanRbicep, g_InjuryAnimsHumanRbicep, 0, 0 },
{ HITPART_TORSO, g_DeathAnimsHumanTorso, g_InjuryAnimsHumanTorso, 0, 0 },
{ HITPART_GUN, g_DeathAnimsHumanGun, g_InjuryAnimsHumanGun, 0, 0 },
{ HITPART_HAT, NULL, NULL, 0, 0 },
{ -1, NULL, NULL, 0, 0 },
};
struct animtable g_AnimTablesSkedar[] = {
{ 0, NULL, NULL, 0, 0 },
{ HITPART_LFOOT, g_DeathAnimsSkedarLfoot, g_InjuryAnimsSkedarLfoot, 0, 0 },
{ HITPART_LSHIN, g_DeathAnimsSkedarLshin, g_InjuryAnimsSkedarLshin, 0, 0 },
{ HITPART_LTHIGH, g_DeathAnimsSkedarLthigh, g_InjuryAnimsSkedarLthigh, 0, 0 },
{ HITPART_RFOOT, g_DeathAnimsSkedarRfoot, g_InjuryAnimsSkedarRfoot, 0, 0 },
{ HITPART_RSHIN, g_DeathAnimsSkedarRshin, g_InjuryAnimsSkedarRshin, 0, 0 },
{ HITPART_RTHIGH, g_DeathAnimsSkedarRthigh, g_InjuryAnimsSkedarRthigh, 0, 0 },
{ HITPART_PELVIS, g_DeathAnimsSkedarPelvis, g_InjuryAnimsSkedarPelvis, 0, 0 },
{ HITPART_HEAD, g_DeathAnimsSkedarHead, g_InjuryAnimsSkedarHead, 0, 0 },
{ HITPART_LHAND, g_DeathAnimsSkedarLhand, g_InjuryAnimsSkedarLhand, 0, 0 },
{ HITPART_LFOREARM, g_DeathAnimsSkedarLforearm, g_InjuryAnimsSkedarLforearm, 0, 0 },
{ HITPART_LBICEP, g_DeathAnimsSkedarLbicep, g_InjuryAnimsSkedarLbicep, 0, 0 },
{ HITPART_RHAND, g_DeathAnimsSkedarRhand, g_InjuryAnimsSkedarRhand, 0, 0 },
{ HITPART_RFOREARM, g_DeathAnimsSkedarRforearm, g_InjuryAnimsSkedarRforearm, 0, 0 },
{ HITPART_RBICEP, g_DeathAnimsSkedarRbicep, g_InjuryAnimsSkedarRbicep, 0, 0 },
{ HITPART_TORSO, g_DeathAnimsSkedarTorso, g_InjuryAnimsSkedarTorso, 0, 0 },
{ HITPART_GUN, g_DeathAnimsSkedarGun, g_InjuryAnimsSkedarGun, 0, 0 },
{ HITPART_HAT, g_DeathAnimsSkedarHead, g_InjuryAnimsSkedarHead, 0, 0 },
{ HITPART_TAIL, g_DeathAnimsSkedarTail, g_InjuryAnimsSkedarTail, 0, 0 },
{ -1, NULL, NULL, 0, 0 },
};
struct animtable g_AnimTablesDrCaroll[] = {
{ 0, NULL, NULL, 0, 0 },
{ -1, NULL, NULL, 0, 0 },
};
struct animtable g_AnimTablesEyespy[] = {
{ 0, NULL, NULL, 0, 0 },
{ -1, NULL, NULL, 0, 0 },
};
struct animtable g_AnimTablesRobot[] = {
{ 0, NULL, NULL, 0, 0 },
{ -1, NULL, NULL, 0, 0 },
};
struct animtable *g_AnimTablesByRace[] = {
g_AnimTablesHuman,
g_AnimTablesSkedar,
g_AnimTablesDrCaroll,
g_AnimTablesEyespy,
g_AnimTablesRobot,
};
/**
* A yeet anim is an animation config for a chr being launched by an explosion.
*/
struct yeetanim {
s16 animnum;
bool flip;
f32 speed;
f32 startframe;
f32 thudframe;
f32 endframe;
};
struct yeetanim g_YeetAnimsHuman[] = {
/* 0*/ { ANIM_0082, 0, 0.5, 9, 29, -1 },
/* 1*/ { ANIM_0082, 1, 0.5, 9, 29, -1 },
/* 2*/ { ANIM_008A, 0, 0.5, 11, 31, -1 },
/* 3*/ { ANIM_008A, 1, 0.5, 11, 31, -1 },
/* 4*/ { ANIM_0089, 0, 0.5, 6, 27, -1 },
/* 5*/ { ANIM_0089, 1, 0.5, 6, 27, -1 },
/* 6*/ { ANIM_008C, 0, 0.5, 29, 48, -1 },
/* 7*/ { ANIM_008C, 1, 0.5, 29, 48, -1 },
/* 8*/ { ANIM_008D, 0, 0.5, 29, 49, -1 },
/* 9*/ { ANIM_008D, 1, 0.5, 29, 49, -1 },
/*10*/ { ANIM_008E, 0, 0.5, 19, 42, -1 },
/*11*/ { ANIM_008E, 1, 0.5, 19, 42, -1 },
/*12*/ { ANIM_0086, 0, 0.5, 0, 60, -1 },
/*13*/ { ANIM_0086, 1, 0.5, 0, 60, -1 },
/*14*/ { ANIM_0087, 0, 0.5, 6, 29, -1 },
/*15*/ { ANIM_0087, 1, 0.5, 6, 29, -1 },
/*16*/ { ANIM_0084, 0, 0.5, 8, 25, -1 },
/*17*/ { ANIM_0084, 1, 0.5, 8, 25, -1 },
/*18*/ { ANIM_0085, 0, 0.5, 8, 25, -1 },
/*19*/ { ANIM_0085, 1, 0.5, 8, 25, -1 },
/*20*/ { ANIM_0088, 0, 0.5, 12, 29, -1 },
/*21*/ { ANIM_0088, 1, 0.5, 12, 29, -1 },
/*22*/ { ANIM_008B, 0, 0.5, 22, 41, -1 },
/*23*/ { ANIM_008B, 1, 0.5, 22, 41, -1 },
/*24*/ { 0, 0, 0.5, 0, 0, -1 },
};
s8 g_YeetAnimIndexesHumanAngle0[] = { 0, 1, 2, 3, 4, 5 };
s8 g_YeetAnimIndexesHumanAngle1[] = { 7, 9, 11 };
s8 g_YeetAnimIndexesHumanAngle2[] = { 6, 8, 10 };
s8 g_YeetAnimIndexesHumanAngle3[] = { 12, 15, 0 };
s8 g_YeetAnimIndexesHumanAngle4[] = { 13, 14, 0 };
s8 g_YeetAnimIndexesHumanAngle5[] = { 18, 19, 20, 21 };
s8 g_YeetAnimIndexesHumanAngle6[] = { 16, 22 };
s8 g_YeetAnimIndexesHumanAngle7[] = { 17, 23 };
struct yeetanim g_YeetAnimsSkedar[] = {
/* 0*/ { ANIM_033F, 0, 0.5, 0, -1, -1 },
/* 1*/ { ANIM_033F, 0, 0.5, 0, -1, -1 },
/* 2*/ { ANIM_033F, 0, 0.5, 0, -1, -1 },
/* 3*/ { ANIM_033F, 0, 0.5, 0, -1, -1 },
/* 4*/ { ANIM_033F, 0, 0.5, 0, -1, -1 },
/* 5*/ { ANIM_033F, 0, 0.5, 0, -1, -1 },
/* 6*/ { ANIM_033F, 0, 0.5, 0, -1, -1 },
/* 7*/ { ANIM_033F, 0, 0.5, 0, -1, -1 },
/* 8*/ { 0, 0, 0.5, 0, 0, -1 },
};
s8 g_YeetAnimIndexesSkedarAngle0[] = { 0 };
s8 g_YeetAnimIndexesSkedarAngle1[] = { 1 };
s8 g_YeetAnimIndexesSkedarAngle2[] = { 2 };
s8 g_YeetAnimIndexesSkedarAngle3[] = { 3 };
s8 g_YeetAnimIndexesSkedarAngle4[] = { 4 };
s8 g_YeetAnimIndexesSkedarAngle5[] = { 7 };
s8 g_YeetAnimIndexesSkedarAngle6[] = { 5 };
s8 g_YeetAnimIndexesSkedarAngle7[] = { 6 };
struct yeetanimindexlist {
s8 *indexes;
s32 count;
};
struct yeetanimindexlist g_YeetAnimIndexesByRaceAngle[][8] = {
{
{ g_YeetAnimIndexesHumanAngle0, 6 },
{ g_YeetAnimIndexesHumanAngle1, 3 },
{ g_YeetAnimIndexesHumanAngle3, 3 },
{ g_YeetAnimIndexesHumanAngle6, 2 },
{ g_YeetAnimIndexesHumanAngle5, 4 },
{ g_YeetAnimIndexesHumanAngle7, 2 },
{ g_YeetAnimIndexesHumanAngle4, 3 },
{ g_YeetAnimIndexesHumanAngle2, 3 },
}, {
{ g_YeetAnimIndexesSkedarAngle0, 1 },
{ g_YeetAnimIndexesSkedarAngle1, 1 },
{ g_YeetAnimIndexesSkedarAngle3, 1 },
{ g_YeetAnimIndexesSkedarAngle6, 1 },
{ g_YeetAnimIndexesSkedarAngle5, 1 },
{ g_YeetAnimIndexesSkedarAngle7, 1 },
{ g_YeetAnimIndexesSkedarAngle4, 1 },
{ g_YeetAnimIndexesSkedarAngle2, 1 },
},
};
struct attackanimconfig var800656c0[] = {
{ ANIM_0002, 28, 0, 0, 0, -1, 23, 54, -1, -1, 18, 54, 0.87252569198608, -0.52351540327072, 1.0470308065414, -0.34901028871536, 1.6000000238419, 1.7999999523163 },
{ 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
};
struct attackanimgroup var80065750 = { var800656c0, 0xffffffff };
struct attackanimconfig var80065758[] = {
{ ANIM_0032, 37, 0, 0, 0, -1, 30, 81, -1, -1, 25, 81, 0.87252569198608, -0.69802057743073, 0.69802057743073, -0.69802057743073, 1.6000000238419, 1.75 },
{ ANIM_0003, 27, 0, 0, 0, -1, 22, 61, -1, -1, 17, 61, 0.87252569198608, -0.26175770163536, 0.69802057743073, -0.69802057743073, 2, 1 },
{ 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
};
struct attackanimgroup var80065830 = { var80065758, 0xffffffff };
struct attackanimconfig var80065838[] = {
{ ANIM_0032, 37, 0, 0, 0, -1, 30, 81, -1, -1, 25, 81, 0.87252569198608, -0.69802057743073, 0.69802057743073, -0.69802057743073, 1.6000000238419, 1.75 },
{ ANIM_0003, 27, 0, 0, 0, -1, 22, 61, -1, -1, 17, 61, 0.87252569198608, -0.26175770163536, 0.69802057743073, -0.69802057743073, 2, 1 },
{ 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
};
struct attackanimgroup var80065910 = { var80065838, 0xffffffff };
struct attackanimconfig var80065918[] = {
{ ANIM_0004, 19, 0, 1.5707963705063, 0, -1, 19, 61, -1, -1, 14, 61, 0.87252569198608, -0.34901028871536, 0.43626284599304, -1.0470308065414, 2.5, 2.5 },
{ 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
};
struct attackanimgroup var800659a8 = { var80065918, 0xffffffff };
struct attackanimconfig var800659b0[] = {
{ ANIM_0006, 27, 0, 0, 0, -1, 39, 74, -1, -1, 34, 74, 0.87252569198608, -0.69802057743073, 0.7852731347084, -0.69802057743073, 1.5, 1.5 },
{ 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
};
struct attackanimgroup var80065a40 = { var800659b0, 0xffffffff };
struct attackanimconfig var80065a48[] = {
{ ANIM_034A, 20, 0, 0, 0, -1, 25, 50, -1, -1, 10, 50, 0.34901028871536, -0.34901028871536, 0.52351540327072, -0.52351540327072, 1, 1 },
{ 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
};
struct attackanimgroup var80065ad8 = { var80065a48, 0xffffffff };
struct attackanimgroup *g_StandHeavyAttackAnims[][32] = {
{
// RACE_HUMAN
&var80065750, &var80065830, &var80065830, &var80065830,
&var80065830, &var80065830, &var80065830, &var80065830,
&var80065830, &var80065830, &var800659a8, &var800659a8,
&var800659a8, &var800659a8, &var800659a8, &var800659a8,
&var80065a40, &var80065a40, &var80065a40, &var80065a40,
&var80065a40, &var80065a40, &var80065910, &var80065910,
&var80065910, &var80065910, &var80065910, &var80065910,
&var80065910, &var80065910, &var80065910, &var80065750,
}, {
// RACE_SKEDAR
&var80065ad8, &var80065ad8, &var80065ad8, &var80065ad8,
&var80065ad8, &var80065ad8, &var80065ad8, &var80065ad8,
&var80065ad8, &var80065ad8, &var80065ad8, &var80065ad8,
&var80065ad8, &var80065ad8, &var80065ad8, &var80065ad8,
&var80065ad8, &var80065ad8, &var80065ad8, &var80065ad8,
&var80065ad8, &var80065ad8, &var80065ad8, &var80065ad8,
&var80065ad8, &var80065ad8, &var80065ad8, &var80065ad8,
&var80065ad8, &var80065ad8, &var80065ad8, &var80065ad8,
},
};
struct attackanimconfig var80065be0[] = {
{ ANIM_0041, 26, 0, 0, 12, 140, 58, 92, 60, 79, 20, 120, 0.87252569198608, -0.69802057743073, 0.69802057743073, -0.69802057743073, 0, 0 },
{ ANIM_0044, 0, 0, 0, 17, 100, 25, 87, 30, 55, 20, 93, 0.87252569198608, -0.69802057743073, 0.69802057743073, -1.0470308065414, 0, 0 },
{ ANIM_0045, 0, 0, 0, 12, 64, 19, 51, 24, 46, 14, 58, 0.87252569198608, -0.69802057743073, 0.52351540327072, -0.7852731347084, 0, 0 },
{ ANIM_0046, 22, 0, 0, 4, 69, 22, 49, 22, 33, 8, 58, 0.87252569198608, -0.69802057743073, 0.43626284599304, -0.7852731347084, 0, 0 },
{ 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
};
struct attackanimgroup var80065d48 = { var80065be0, 0xffffffff };
struct attackanimconfig var80065d50[] = {
{ ANIM_0041, 26, 0, 0, 12, 140, 58, 92, 60, 79, 20, 120, 0.87252569198608, -0.69802057743073, 0.69802057743073, -0.69802057743073, 0, 0 },
{ ANIM_0046, 22, 0, 0, 4, 69, 22, 49, 22, 33, 8, 58, 0.87252569198608, -0.69802057743073, 0.43626284599304, -0.7852731347084, 0, 0 },
{ 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
};
struct attackanimgroup var80065e28 = { var80065d50, 0xffffffff };
struct attackanimconfig var80065e30[] = {
{ ANIM_0041, 26, 0, 0, 12, 140, 58, 92, 60, 79, 20, 120, 0.87252569198608, -0.69802057743073, 0.69802057743073, -0.69802057743073, 0, 0 },
{ ANIM_0046, 22, 0, 0, 4, 69, 22, 49, 22, 33, 8, 58, 0.87252569198608, -0.69802057743073, 0.43626284599304, -0.7852731347084, 0, 0 },
{ ANIM_0049, 0, 0, 1.5707963705063, 7, 130, 45, 93, 56, 73, 26, 107, 0.87252569198608, -0.69802057743073, 0.34901028871536, -0.52351540327072, 0, 0 },
{ ANIM_004A, 15, 0, 1.5707963705063, 5, 76, 20, 31, 31, 38, 15, 49, 0.87252569198608, -0.69802057743073, 0.52351540327072, -1.0470308065414, 0, 0 },
{ 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
};
struct attackanimgroup var80065f98 = { var80065e30, 0xffffffff };
struct attackanimconfig var80065fa0[] = {
{ ANIM_0041, 26, 0, 0, 12, 140, 58, 92, 60, 79, 20, 120, 0.87252569198608, -0.69802057743073, 0.69802057743073, -0.69802057743073, 0, 0 },
{ ANIM_0046, 22, 0, 0, 4, 69, 22, 49, 22, 33, 8, 58, 0.87252569198608, -0.69802057743073, 0.43626284599304, -0.7852731347084, 0, 0 },
{ ANIM_0047, 0, 0, 4.7123889923096, 7, 139, 54, 105, 61, 88, 26, 120, 0.87252569198608, -0.69802057743073, 0.69802057743073, -0.61076802015305, 0, 0 },
{ ANIM_0048, 19, 0, 4.7123889923096, 4, 79, 21, 50, 26, 42, 10, 64, 0.87252569198608, -0.69802057743073, 0.69802057743073, -0.61076802015305, 0, 0 },
{ 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
};
struct attackanimgroup var80066108 = { var80065fa0, 0xffffffff };
struct attackanimconfig var80066110[] = {
{ ANIM_004A, 19, 0, 1.5707963705063, 5, 76, 20, 31, 31, 38, 15, 49, 0.87252569198608, -0.69802057743073, 0.52351540327072, -1.0470308065414, 0, 0 },
{ 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
};
struct attackanimgroup var800661a0 = { var80066110, 0xffffffff };
struct attackanimconfig var800661a8[] = {
{ ANIM_0048, 19, 0, 4.7123889923096, 4, 79, 21, 50, 26, 42, 10, 64, 0.87252569198608, -0.69802057743073, 0.69802057743073, -0.61076802015305, 0, 0 },
{ 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
};
struct attackanimgroup var80066238 = { var800661a8, 0xffffffff };
struct attackanimconfig var80066240[] = {
{ ANIM_034A, 20, 0, 0, 0, -1, 25, 50, -1, -1, 10, 50, 0.34901028871536, -0.34901028871536, 0.52351540327072, -0.52351540327072, 1, 1 },
{ 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
};
struct attackanimgroup var800662d0 = { var80066240, 0xffffffff };
struct attackanimgroup *g_StandLightAttackAnims[][32] = {
{
// RACE_HUMAN
&var80065d48, &var80065d48, &var80065e28, &var80065e28,
&var80065e28, &var80065f98, &var80065f98, &var80065f98,
&var80065f98, &var80065f98, &var800661a0, &var800661a0,
&var800661a0, &var800661a0, &var800661a0, &var800661a0,
&var80066238, &var80066238, &var80066238, &var80066238,
&var80066238, &var80066238, &var80066108, &var80066108,
&var80066108, &var80066108, &var80066108, &var80065e28,
&var80065e28, &var80065e28, &var80065d48, &var80065d48,
}, {
// RACE_SKEDAR
&var800662d0, &var800662d0, &var800662d0, &var800662d0,
&var800662d0, &var800662d0, &var800662d0, &var800662d0,
&var800662d0, &var800662d0, &var800662d0, &var800662d0,
&var800662d0, &var800662d0, &var800662d0, &var800662d0,
&var800662d0, &var800662d0, &var800662d0, &var800662d0,
&var800662d0, &var800662d0, &var800662d0, &var800662d0,
&var800662d0, &var800662d0, &var800662d0, &var800662d0,
&var800662d0, &var800662d0, &var800662d0, &var800662d0,
},
};
struct attackanimconfig var800663d8[] = {
{ ANIM_007A, 26, 0, 0, 7, 92, 28, 68, -1, -1, 11, 73, 0.87252569198608, -0.69802057743073, 0.69802057743073, -0.69802057743073, 0, 0 },
{ 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
};
struct attackanimgroup var80066468 = { var800663d8, 0xffffffff };
struct attackanimconfig var80066470[] = {
{ ANIM_007B, 26, 0, 1.5707963705063, 9, 112, 38, 87, -1, -1, 19, 98, 0.87252569198608, -0.69802057743073, 0.43626284599304, -0.43626284599304, 0, 0 },
{ ANIM_007D, 25, 0, 1.5707963705063, 10, 112, 32, 86, -1, -1, 19, 97, 0.87252569198608, -0.69802057743073, 0.43626284599304, -0.43626284599304, 0, 0 },
{ 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
};
struct attackanimgroup var80066548 = { var80066470, 0xffffffff };
struct attackanimconfig var80066550[] = {
{ ANIM_007C, 39, 0, 4.7123889923096, 22, 127, 44, 102, -1, -1, 28, 112, 0.87252569198608, -0.69802057743073, 0.43626284599304, -0.43626284599304, 0, 0 },
{ ANIM_007E, 39, 0, 4.7123889923096, 23, 130, 46, 100, -1, -1, 30, 110, 0.87252569198608, -0.69802057743073, 0.43626284599304, -0.43626284599304, 0, 0 },
{ 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
};
struct attackanimgroup var80066628 = { var80066550, 0xffffffff };
struct attackanimconfig var80066630[] = {
{ ANIM_034A, 20, 0, 0, 0, -1, 25, 50, -1, -1, 10, 50, 0.34901028871536, -0.34901028871536, 0.52351540327072, -0.52351540327072, 1, 1 },
{ 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
};
struct attackanimgroup var800666c0 = { var80066630, 0xffffffff };
struct attackanimgroup *g_StandDualAttackAnims[][32] = {
{
// RACE_HUMAN
&var80066468, &var80066468, &var80066468, &var80066468,
&var80066468, &var80066548, &var80066548, &var80066548,
&var80066548, &var80066548, &var80066548, &var80066548,
&var80066548, &var80066548, &var80066548, &var80066548,
&var80066628, &var80066628, &var80066628, &var80066628,
&var80066628, &var80066628, &var80066628, &var80066628,
&var80066628, &var80066628, &var80066628, &var80066468,
&var80066468, &var80066468, &var80066468, &var80066468,
}, {
// RACE_SKEDAR
&var800666c0, &var800666c0, &var800666c0, &var800666c0,
&var800666c0, &var800666c0, &var800666c0, &var800666c0,
&var800666c0, &var800666c0, &var800666c0, &var800666c0,
&var800666c0, &var800666c0, &var800666c0, &var800666c0,
&var800666c0, &var800666c0, &var800666c0, &var800666c0,
&var800666c0, &var800666c0, &var800666c0, &var800666c0,
&var800666c0, &var800666c0, &var800666c0, &var800666c0,
&var800666c0, &var800666c0, &var800666c0, &var800666c0,
},
};
struct attackanimconfig var800667c8[] = {
{ ANIM_0007, 27, 0, 0, 0, -1, 35, 75, -1, -1, 31, 75, 0.87252569198608, -0.69802057743073, 0.90742671489716, -0.69802057743073, 1.5, 1.5 },
{ ANIM_KNEEL_TWO_HANDED_GUN, 24, 0, 0, 0, -1, 46, 98, -1, -1, 41, 98, 0.87252569198608, -0.52351540327072, 1.1342834234238, -0.69802057743073, 1.6000000238419, 1.6000000238419 },
{ 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
};
struct attackanimgroup var800668a0 = { var800667c8, 0xffffffff };
struct attackanimconfig var800668a8[] = {
{ ANIM_0009, 26, 0, 0, 0, -1, 34, 87, -1, -1, 29, 87, 0.87252569198608, -0.52351540327072, 0.69802057743073, -0.95977824926376, 1.6000000238419, 2 },
{ 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
};
struct attackanimgroup var80066938 = { var800668a8, 0xffffffff };
struct attackanimconfig var80066940[] = {
{ ANIM_000A, 28, 0, 0, 0, -1, 36, 88, -1, -1, 31, 88, 0.87252569198608, -0.69802057743073, 0.87252569198608, -0.43626284599304, 1.6000000238419, 1.5 },
{ 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
};
struct attackanimgroup var800669d0 = { var80066940, 0xffffffff };
struct attackanimconfig var800669d8[] = {
{ ANIM_034A, 20, 0, 0, 0, -1, 25, 50, -1, -1, 10, 50, 0.34901028871536, -0.34901028871536, 0.52351540327072, -0.52351540327072, 1, 1 },
{ 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
};
struct attackanimgroup var80066a68 = { var800669d8, 0xffffffff };
struct attackanimgroup *g_KneelHeavyAttackAnims[][32] = {
{
&var800668a0, &var800668a0, &var800668a0, &var800668a0,
&var800668a0, &var800668a0, &var800668a0, &var800668a0,
&var800668a0, &var800668a0, &var80066938, &var80066938,
&var80066938, &var80066938, &var80066938, &var80066938,
&var800669d0, &var800669d0, &var800669d0, &var800669d0,
&var800669d0, &var800669d0, &var800668a0, &var800668a0,
&var800668a0, &var800668a0, &var800668a0, &var800668a0,
&var800668a0, &var800668a0, &var800668a0, &var800668a0,
}, {
&var80066a68, &var80066a68, &var80066a68, &var80066a68,
&var80066a68, &var80066a68, &var80066a68, &var80066a68,
&var80066a68, &var80066a68, &var80066a68, &var80066a68,
&var80066a68, &var80066a68, &var80066a68, &var80066a68,
&var80066a68, &var80066a68, &var80066a68, &var80066a68,
&var80066a68, &var80066a68, &var80066a68, &var80066a68,
&var80066a68, &var80066a68, &var80066a68, &var80066a68,
&var80066a68, &var80066a68, &var80066a68, &var80066a68,
},
};
struct attackanimconfig var80066b70[] = {
{ ANIM_KNEEL_SHOOT_RIGHT_HAND, 25, 0, 0, 12, 132, 55, 87, 67, 87, 26, 111, 0.87252569198608, -0.69802057743073, 0.61076802015305, -0.7852731347084, 0, 0 },
{ ANIM_004C, 26, 0, 0, 8, 89, 31, 63, 41, 51, 21, 80, 0.87252569198608, -0.69802057743073, 0.34901028871536, -1.1342834234238, 0, 0 },
{ 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
};
struct attackanimgroup var80066c48 = { var80066b70, 0xffffffff };
struct attackanimconfig var80066c50[] = {
{ ANIM_004F, 47, 0, 1.5707963705063, 7, 128, 33, 86, 47, 74, 23, 106, 0.87252569198608, -0.52351540327072, 0.52351540327072, -0.7852731347084, 0, 0 },
{ ANIM_0050, 18, 0, 1.5707963705063, 7, 78, 28, 52, 35, 45, 15, 66, 0.87252569198608, -0.087252572178841, 0.69802057743073, -0.7852731347084, 1.5, 1 },
{ ANIM_0051, 20, 0, 1.5707963705063, 13, 92, 37, 67, 42, 55, 25, 84, 0.87252569198608, -0.52351540327072, 0.34901028871536, -0.69802057743073, 0, 0 },
{ 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
};
struct attackanimgroup var80066d70 = { var80066c50, 0xffffffff };
struct attackanimconfig var80066d78[] = {
{ ANIM_004D, 28, 0, 4.7123889923096, 15, 124, 38, 97, 60, 84, 20, 106, 0.87252569198608, -0.69802057743073, 0.52351540327072, -0.87252569198608, 0, 0 },
{ ANIM_004E, 23, 0, 4.7123889923096, 0, 85, 32, 38, 38, 60, 14, 71, 0.87252569198608, -0.69802057743073, 0.61076802015305, -0.95977824926376, 0, 0 },
{ 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
};
struct attackanimgroup var80066e50 = { var80066d78, 0xffffffff };
struct attackanimconfig var80066e58[] = {
{ ANIM_034A, 20, 0, 0, 0, -1, 25, 50, -1, -1, 10, 50, 0.34901028871536, -0.34901028871536, 0.52351540327072, -0.52351540327072, 1, 1 },
{ 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
};
struct attackanimgroup var80066ee8 = { var80066e58, 0xffffffff };
struct attackanimgroup *g_KneelLightAttackAnims[][32] = {
{
&var80066c48, &var80066c48, &var80066c48, &var80066c48,
&var80066c48, &var80066c48, &var80066c48, &var80066c48,
&var80066c48, &var80066c48, &var80066d70, &var80066d70,
&var80066d70, &var80066d70, &var80066d70, &var80066d70,
&var80066e50, &var80066e50, &var80066e50, &var80066e50,
&var80066e50, &var80066e50, &var80066c48, &var80066c48,
&var80066c48, &var80066c48, &var80066c48, &var80066c48,
&var80066c48, &var80066c48, &var80066c48, &var80066c48,
}, {
&var80066ee8, &var80066ee8, &var80066ee8, &var80066ee8,
&var80066ee8, &var80066ee8, &var80066ee8, &var80066ee8,
&var80066ee8, &var80066ee8, &var80066ee8, &var80066ee8,
&var80066ee8, &var80066ee8, &var80066ee8, &var80066ee8,
&var80066ee8, &var80066ee8, &var80066ee8, &var80066ee8,
&var80066ee8, &var80066ee8, &var80066ee8, &var80066ee8,
&var80066ee8, &var80066ee8, &var80066ee8, &var80066ee8,
&var80066ee8, &var80066ee8, &var80066ee8, &var80066ee8,
},
};
struct attackanimconfig var80066ff0[] = {
{ ANIM_0074, 22, 0, 0, 10, 111, 34, 87, -1, -1, 17, 104, 0.87252569198608, -0.69802057743073, 0.61076802015305, -0.7852731347084, 0, 0 },
{ ANIM_0077, 25, 0, 0, 9, 92, 33, 62, -1, -1, 18, 69, 0.87252569198608, -0.69802057743073, 0.61076802015305, -0.7852731347084, 0, 0 },
{ 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
};
struct attackanimgroup var800670c8 = { var80066ff0, 0xffffffff };
struct attackanimconfig var800670d0[] = {
{ ANIM_0075, 28, 0, 1.5707963705063, 15, 108, 34, 73, -1, -1, 17, 93, 0.87252569198608, -0.69802057743073, 0.52351540327072, -0.7852731347084, 0, 0 },
{ ANIM_0078, 19, 0, 1.5707963705063, 3, 95, 30, 64, -1, -1, 14, 71, 0.87252569198608, -0.69802057743073, 0.52351540327072, -0.7852731347084, 1.5, 1 },
{ 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
};
struct attackanimgroup var800671a8 = { var800670d0, 0xffffffff };
struct attackanimconfig var800671b0[] = {
{ ANIM_0076, 31, 0, 4.7123889923096, 14, 111, 40, 83, -1, -1, 21, 94, 0.87252569198608, -0.69802057743073, 0.52351540327072, -0.7852731347084, 0, 0 },
{ ANIM_0079, 26, 0, 4.7123889923096, 7, 89, 34, 60, -1, -1, 20, 68, 0.87252569198608, -0.69802057743073, 0.52351540327072, -0.7852731347084, 0, 0 },
{ 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
};
struct attackanimgroup var80067288 = { var800671b0, 0xffffffff };
struct attackanimconfig var80067290[] = {
{ ANIM_034A, 20, 0, 0, 0, -1, 25, 50, -1, -1, 10, 50, 0.34901028871536, -0.34901028871536, 0.52351540327072, -0.52351540327072, 1, 1 },
{ 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
};
struct attackanimgroup var80067320 = { var80067290, 0xffffffff };
struct attackanimgroup *g_KneelDualAttackAnims[][32] = {
{
&var800670c8, &var800670c8, &var800670c8, &var800670c8,
&var800670c8, &var800670c8, &var800670c8, &var800670c8,
&var800670c8, &var800670c8, &var800671a8, &var800671a8,
&var800671a8, &var800671a8, &var800671a8, &var800671a8,
&var80067288, &var80067288, &var80067288, &var80067288,
&var80067288, &var80067288, &var800670c8, &var800670c8,
&var800670c8, &var800670c8, &var800670c8, &var800670c8,
&var800670c8, &var800670c8, &var800670c8, &var800670c8,
}, {
&var80067320, &var80067320, &var80067320, &var80067320,
&var80067320, &var80067320, &var80067320, &var80067320,
&var80067320, &var80067320, &var80067320, &var80067320,
&var80067320, &var80067320, &var80067320, &var80067320,
&var80067320, &var80067320, &var80067320, &var80067320,
&var80067320, &var80067320, &var80067320, &var80067320,
&var80067320, &var80067320, &var80067320, &var80067320,
&var80067320, &var80067320, &var80067320, &var80067320,
},
};
struct attackanimconfig g_RollAttackAnims[] = {
{ ANIM_000B, 76, 0, 0, 20, -1, 98, 161, -1, -1, 93, 161, 0.87252569198608, -0.52351540327072, 0.69802057743073, -0.69802057743073, 1.7000000476837, 2 },
{ ANIM_000C, 58, 0, 0, 10, -1, 77, 104, -1, -1, 72, 104, 0.87252569198608, -0.34901028871536, 0.61076802015305, -0.69802057743073, 1.5499999523163, 1.5 },
{ ANIM_000D, 61, 0, 0, 10, -1, 83, 128, -1, -1, 78, 128, 0.87252569198608, -0.52351540327072, 0.87252569198608, -0.52351540327072, 1.2000000476837, 1.2999999523163 },
{ ANIM_0027, 63, 0, 0, 10, -1, 73, 114, -1, -1, 68, 114, 0.87252569198608, -0.52351540327072, 0.61076802015305, -0.61076802015305, 1.6499999761581, 1.5 },
{ ANIM_000B, 76, 0, 0, 20, 76, 98, 161, -1, -1, 93, 161, 0.87252569198608, -0.52351540327072, 0.69802057743073, -0.69802057743073, 1.7000000476837, 2 },
{ ANIM_000C, 58, 0, 0, 10, 63, 77, 104, -1, -1, 72, 104, 0.87252569198608, -0.34901028871536, 0.61076802015305, -0.69802057743073, 1.5499999523163, 1.5 },
{ ANIM_000D, 61, 0, 0, 10, 56, 83, 128, -1, -1, 78, 128, 0.87252569198608, -0.52351540327072, 0.87252569198608, -0.52351540327072, 1.2000000476837, 1.2999999523163 },
{ ANIM_0027, 63, 0, 0, 10, 50, 73, 114, -1, -1, 68, 114, 0.87252569198608, -0.52351540327072, 0.61076802015305, -0.61076802015305, 1.6499999761581, 1.5 },
{ ANIM_0045, 0, 0, 0, 7, 64, 19, 51, 24, 46, 14, 58, 0.87252569198608, -0.69802057743073, 0.52351540327072, -0.7852731347084, 0, 0 },
{ ANIM_004A, 0, 0, 1.5707963705063, 14, 76, 26, 31, 31, 38, 15, 49, 0.87252569198608, -0.69802057743073, 0.52351540327072, -1.0470308065414, 0, 0 },
{ ANIM_004C, 26, 0, 0, 25, 89, 41, 63, 41, 51, 21, 80, 0.87252569198608, -0.69802057743073, 0.34901028871536, -1.1342834234238, 0, 0 },
{ ANIM_0050, 18, 0, 1.5707963705063, 11, 78, 33, 52, 35, 45, 15, 66, 0.87252569198608, -0.087252572178841, 0.69802057743073, -0.7852731347084, 1.5, 1 },
{ ANIM_007A, 26, 0, 0, 7, 92, 28, 68, -1, -1, 11, 73, 0.87252569198608, -0.69802057743073, 0.69802057743073, -0.69802057743073, 0, 0 },
{ ANIM_007B, 26, 0, 1.5707963705063, 9, 112, 38, 87, -1, -1, 19, 98, 0.87252569198608, -0.69802057743073, 0.43626284599304, -0.43626284599304, 0, 0 },
{ ANIM_0074, 22, 0, 0, 10, 11, 34, 87, -1, -1, 17, 104, 0.87252569198608, -0.69802057743073, 0.61076802015305, -0.7852731347084, 0, 0 },
{ ANIM_0075, 28, 0, 1.5707963705063, 15, 108, 34, 73, -1, -1, 17, 93, 0.87252569198608, -0.69802057743073, 0.52351540327072, -0.7852731347084, 0, 0 },
{ ANIM_007A, 26, 0, 0, 7, 92, 28, 68, -1, -1, 11, 73, 0.87252569198608, -0.69802057743073, 0.69802057743073, -0.69802057743073, 0, 0 },
{ ANIM_007D, 25, 0, 1.5707963705063, 10, 112, 32, 86, -1, -1, 19, 97, 0.87252569198608, -0.69802057743073, 0.43626284599304, -0.43626284599304, 0, 0 },
{ ANIM_0077, 25, 0, 0, 9, 92, 33, 62, -1, -1, 18, 69, 0.87252569198608, -0.69802057743073, 0.61076802015305, -0.7852731347084, 0, 0 },
{ ANIM_0078, 19, 0, 1.5707963705063, 3, 95, 30, 64, -1, -1, 14, 71, 0.87252569198608, -0.69802057743073, 0.52351540327072, -0.7852731347084, 1.5, 1 },
{ 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
};
struct attackanimconfig g_AttackAnimHeavyWalk = { ANIM_0030, 0, 0, 0, 0, -1, 0, 0, -1, -1, 0, 0, 0.87252569198608, -0.52351540327072, 0.52351540327072, -0.52351540327072, 1.3999999761581, 1.2999999523163 };
struct attackanimconfig g_AttackAnimHeavyRun = { ANIM_0031, 0, 0, 0, 0, -1, 0, 0, -1, -1, 0, 0, 0.87252569198608, -0.52351540327072, 0.52351540327072, -0.52351540327072, 1.1000000238419, 1.2000000476837 };
struct attackanimconfig g_AttackAnimLightWalk = { ANIM_0052, 0, 0, 0, 0, -1, 0, 0, -1, -1, 0, 0, 0.87252569198608, -0.52351540327072, 0.52351540327072, -0.52351540327072, 0, 0 };
struct attackanimconfig g_AttackAnimLightRun = { ANIM_0055, 0, 0, 0, 0, -1, 0, 0, -1, -1, 0, 0, 0.87252569198608, -0.52351540327072, 0.52351540327072, -0.52351540327072, 0, 0 };
struct attackanimconfig g_AttackAnimDualWalk = { ANIM_006C, 0, 0, 0, 0, -1, 0, 0, -1, -1, 0, 0, 0.87252569198608, -0.52351540327072, 0.52351540327072, -0.52351540327072, 0, 0 };
struct attackanimconfig g_AttackAnimDualRun = { ANIM_006E, 0, 0, 0, 0, -1, 0, 0, -1, -1, 0, 0, 0.87252569198608, -0.52351540327072, 0.52351540327072, -0.52351540327072, 0, 0 };
struct attackanimconfig g_AttackAnimDualCrossedWalk = { ANIM_006D, 0, 0, 0, 0, -1, 0, 0, -1, -1, 0, 0, 0.87252569198608, -0.52351540327072, 0.52351540327072, -0.52351540327072, 0, 0 };
struct attackanimconfig g_AttackAnimDualCrossedRun = { ANIM_006F, 0, 0, 0, 0, -1, 0, 0, -1, -1, 0, 0, 0.87252569198608, -0.52351540327072, 0.52351540327072, -0.52351540327072, 0, 0 };
struct attackanimconfig var80067c50[] = {
{ ANIM_0057, 0, 0, 1.5707963705063, 0, -1, 0, 0, -1, -1, 0, 0, 0.87252569198608, -0.52351540327072, 0.52351540327072, -0.52351540327072, 0, 0 },
{ ANIM_0056, 0, 0, 4.7123889923096, 0, -1, 0, 0, -1, -1, 0, 0, 0.87252569198608, -0.52351540327072, 0.52351540327072, -0.52351540327072, 0, 0 },
{ 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
};
struct attackanimconfig var80067d28[] = { ANIM_SNIPING_GETDOWN, 0, 0, 0, 0, 236, 0, 0, -1, -1, 0, 0, 0.87252569198608, -0.52351540327072, 0.52351540327072, -0.52351540327072, 0, 0 };
struct attackanimconfig var80067d70[] = {
{ ANIM_SNIPING_ONGROUND, 0, 0, 0, 0, -1, 0, 0, -1, -1, 0, 0, 0.87252569198608, -0.52351540327072, 0.52351540327072, -0.52351540327072, 0, 0 },
{ ANIM_SNIPING_GETUP, 0, 0, 0, 0, -1, 0, 0, -1, -1, 0, 0, 0.87252569198608, -0.52351540327072, 0.52351540327072, -0.52351540327072, 0, 0 },
{ 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
};
struct attackanimgroup var80067e48 = { var80067d28, 0xffffffff };
struct attackanimgroup *g_LieAttackAnims = &var80067e48;
u32 g_StageFlags = 0;
struct chrdata *g_BgChrs = NULL;
s16 *g_BgChrnums = NULL;
s32 g_NumBgChrs = 0;
s16 *g_TeamList = NULL;
s16 *g_SquadronList = NULL;
struct var80067e6c var80067e6c[] = {
{ ANIM_0028, 0 },
{ ANIM_RUNNING_TWOHANDGUN, 0 },
{ ANIM_0029, 0 },
{ ANIM_006B, 0 },
{ ANIM_RUNNING_ONEHANDGUN, 0 },
{ ANIM_005A, 0 },
{ ANIM_0072, 0 },
{ ANIM_0073, 0 },
{ ANIM_005A, 0 },
{ ANIM_006C, 0 },
{ ANIM_0030, 0 },
{ ANIM_0031, 0 },
{ ANIM_0052, 0 },
{ ANIM_0055, 0 },
{ ANIM_006E, 0 },
{ ANIM_006F, 0 },
{ ANIM_0057, 0 },
{ ANIM_0056, 0 },
{ ANIM_006D, 0 },
{ ANIM_RUNNING_ONEHANDGUN, 0 },
{ ANIM_020A, 0 },
{ ANIM_020D, 0 },
{ ANIM_01F9, 0 },
{ ANIM_01F8, 0 },
{ ANIM_021D, 0 },
{ ANIM_0016, 0 },
{ ANIM_0018, 0 },
{ ANIM_001B, 0 },
{ ANIM_001D, 0 },
{ ANIM_001E, 0 },
{ ANIM_005C, 0 },
{ ANIM_005D, 0 },
{ ANIM_005E, 0 },
{ ANIM_005F, 0 },
{ -1 },
};
struct var80067e6c var80067f84[] = {
{ ANIM_0392, 0 },
{ ANIM_0393, 0 },
{ ANIM_SKEDAR_RUNNING, 0 },
{ -1 },
};
struct var80067e6c var80067fa4[] = {
{ ANIM_015F, 0 },
{ ANIM_0160, 0 },
{ -1 },
};
struct var80067e6c var80067fbc[] = {
{ ANIM_015F, 0 },
{ -1 },
};
struct var80067e6c var80067fcc[] = {
{ ANIM_0238, 0 },
{ -1 },
};
struct var80067e6c *var80067fdc[] = {
var80067e6c,
var80067f84,
var80067fa4,
var80067fbc,
var80067fcc,
};
f32 func0f02dff0(s16 animnum)
{
s32 i;
for (i = 0; i < ARRAYCOUNT(var80067fdc); i++) {
s32 j = 0;
s16 thisanimnum = var80067fdc[i][j].animnum;
while (thisanimnum >= 0) {
if (thisanimnum == animnum) {
return var80067fdc[i][j].value;
}
j++;
thisanimnum = var80067fdc[i][j].animnum;
}
}
return 1.0f;
}
bool chrGoPosIsWaiting(struct chrdata *chr)
{
static s16 list1[] = { ANIM_TWO_GUN_HOLD, ANIM_006A, -1 };
static s16 list2[] = { ANIM_00C0, -1 };
static s16 list3[] = { ANIM_013E, -1 };
static s16 list4[] = { ANIM_013E, -1 };
static s16 list5[] = { ANIM_0237, -1 };
static s16 *waitableanims[] = { list1, list2, list3, list4, list5 };
if (chr->aibot) {
if (chr->actiontype == ACT_STAND
|| (chr->actiontype == ACT_GOPOS && (chr->act_gopos.flags & GOPOSFLAG_WAITING))) {
return true;
}
} else {
s16 animnum = modelGetAnimNum(chr->model);
s32 i;
for (i = 0; i < ARRAYCOUNT(waitableanims); i++) {
s16 thisanimnum;
s32 j;
for (j = 0; (thisanimnum = waitableanims[i][j]) >= 0; j++) {
if (thisanimnum == animnum) {
return true;
}
}
}
}
return false;
}
bool weaponIsOneHanded(struct prop *prop)
{
if (prop) {
struct weaponobj *weapon = prop->weapon;
return weaponHasFlag(weapon->weaponnum, WEAPONFLAG_ONEHANDED);
}
return false;
}
/**
* Returns a value between min and max based on the chr's speedrating property.
*
* chr->speedrating is between 0 and 100. The result is scaled between min and
* max accordingly.
*
* This function also applies the PD mode reaction speed, but the PD mode
* reaction speed is always zero because PD doesn't have it in the settings.
* It was used in GE but disabled in PD.
*/
f32 chrGetRangedSpeed(struct chrdata *chr, f32 min, f32 max)
{
f32 speedrating = chr->speedrating;
return (max - min) * speedrating * 0.01f + min;
}
/**
* Calculates a percentage of how slow the chr is.
*
* percentage is expected to be between 0 and 100.
* chr->speedrating is between 0 and 100.
*
* This function takes the difference between the speedrating and the max (100),
* then multiplies that amount by the given percentage and returns it.
*
* For example, if the chr's speedrating is 10 (out of 100) and the given
* percentage is 50, the result will be 45.
*/
s32 chrGetPercentageOfSlowness(struct chrdata *chr, s32 percentage)
{
s32 speedrating = chr->speedrating;
return (100 - speedrating) * percentage / 100;
}
f32 chrGetRangedArghSpeed(struct chrdata *chr, f32 min, f32 max)
{
f32 arghrating = chr->arghrating;
return (max - min) * arghrating * 0.01f + min;
}
f32 chrGetAttackEntityRelativeAngle(struct chrdata *chr, s32 attackflags, s32 entityid)
{
f32 angle;
struct coord pos;
s16 rooms[8];
if (attackflags & ATTACKFLAG_AIMFORWARD) {
return 0;
}
if (attackflags & ATTACKFLAG_AIMATDIRECTION) {
angle = entityid * (M_BADTAU / 65536);
angle -= chrGetInverseTheta(chr);
if (angle < 0) {
angle += M_BADTAU;
}
return angle;
}
chrGetAttackEntityPos(chr, attackflags, entityid, &pos, rooms);
return chrGetAngleToPos(chr, &pos);
}
f32 chrGetAttackEntityDistance(struct chrdata *chr, u32 attackflags, s32 entityid)
{
if (attackflags & ATTACKFLAG_AIMATTARGET) {
return chrGetDistanceToTarget(chr);
}
if (attackflags & ATTACKFLAG_AIMATCHR) {
return chrGetDistanceToChr(chr, entityid);
}
if (attackflags & ATTACKFLAG_AIMATPAD) {
return chrGetDistanceToPad(chr, entityid);
}
return 0;
}
void func0f02e3dc(struct coord *a, struct coord *b,struct coord *c, struct coord *d, struct coord *dst)
{
f32 value = d->z * (b->x - a->x) - (b->z - a->z) * d->x;
if (value != 0) {
f32 tmp = ((b->z - a->z) * (c->x - a->x) + (a->z - c->z) * (b->x - a->x)) / value;
dst->x = d->x * tmp + c->x;
dst->y = d->y * tmp + c->y;
dst->z = d->z * tmp + c->z;
} else if (d->x == 0 && d->z == 0) {
*dst = *c;
} else {
*dst = *a;
}
}
void func0f02e4f8(struct coord *arg0, struct coord *arg1, struct coord *dst)
{
struct coord sp2c;
struct coord sp20;
cdGetEdge(&sp2c, &sp20, 2298, "chraction.c");
func0f02e3dc(&sp2c, &sp20, arg0, arg1, dst);
}
f32 func0f02e550(struct prop *prop, f32 arg1, f32 arg2, u32 cdtypes, f32 ymax, f32 ymin)
{
struct coord sp5c;
struct coord sp50;
struct chrdata *chr = prop->chr;
f32 result;
struct coord sp3c;
f32 xdiff;
f32 zdiff;
sp5c.x = sinf(arg1);
sp5c.y = 0;
sp5c.z = cosf(arg1);
sp50.x = prop->pos.x + sp5c.f[0] * arg2;
sp50.y = prop->pos.y;
sp50.z = prop->pos.z + sp5c.f[2] * arg2;
chrSetPerimEnabled(chr, false);
if (cdExamCylMove03(&prop->pos, prop->rooms, &sp50, cdtypes, 1, ymax - prop->pos.y, ymin - prop->pos.y) != CDRESULT_COLLISION) {
result = arg2;
} else {
cdGetPos(&sp3c, 2377, "chraction.c");
xdiff = sp3c.x - prop->pos.x;
zdiff = sp3c.z - prop->pos.z;
result = sqrtf(xdiff * xdiff + zdiff * zdiff);
}
chrSetPerimEnabled(chr, true);
return result;
}
f32 func0f02e684(struct prop *prop, f32 arg1, f32 arg2)
{
f32 ymax;
f32 ymin;
f32 radius;
chrGetBbox(prop, &radius, &ymax, &ymin);
return func0f02e550(prop, arg1, arg2, CDTYPE_ALL, ymax, ymin);
}
void chrChooseStandAnimation(struct chrdata *chr, f32 mergetime)
{
struct prop *gun1 = chrGetHeldProp(chr, HAND_LEFT);
struct prop *gun2 = chrGetHeldProp(chr, HAND_RIGHT);
s32 race = CHRRACE(chr);
s32 prevanimnum = modelGetAnimNum(chr->model);
if (chr->actiontype == ACT_GOPOS) {
chr->act_gopos.flags |= GOPOSFLAG_WAITING;
}
if (chr->aibot) {
return;
}
if (race == RACE_EYESPY) {
modelSetAnimation(chr->model, ANIM_013E, 0, 0, 0, mergetime);
} else if (race == RACE_HUMAN) {
if (prevanimnum == ANIM_SNIPING_GETDOWN
|| prevanimnum == ANIM_SNIPING_GETUP
|| prevanimnum == ANIM_SNIPING_ONGROUND) {
modelSetAnimation(chr->model, ANIM_SNIPING_GETUP, chr->model->anim->flip, -1, chrGetRangedSpeed(chr, 0.5, 0.8), 16);
} else if ((gun1 && gun2) || (!gun1 && !gun2)
|| weaponIsOneHanded(gun1)
|| weaponIsOneHanded(gun2)) {
modelSetAnimation(chr->model, ANIM_006A, random() % 2, 0, 0.25, mergetime);
modelSetAnimLooping(chr->model, 0, 16);
} else if (gun2 || gun1) {
modelSetAnimation(chr->model, ANIM_TWO_GUN_HOLD, gun1 != NULL, 0, 0.25, mergetime);
modelSetAnimLooping(chr->model, 0, 16);
modelSetAnimEndFrame(chr->model, 120);
}
} else if (race == RACE_SKEDAR) {
modelSetAnimation(chr->model, ANIM_00C0, random() % 2, 0, 0.5, mergetime);
} else if (race == RACE_DRCAROLL) {
modelSetAnimation(chr->model, ANIM_013E, 0, 0, 0.5, mergetime);
} else if (race == RACE_ROBOT) {
modelSetAnimation(chr->model, ANIM_0237, 0, 0, 0.5, mergetime);
}
}
void func0f02e9a0(struct chrdata *chr, f32 mergetime)
{
f32 limit = 127;
f32 fsleep;
chrStopFiring(chr);
chr->actiontype = ACT_STAND;
chr->act_stand.prestand = false;
chr->act_stand.flags = 0;
chr->act_stand.entityid = 0;
chr->act_stand.reaim = 0;
chr->act_stand.turning = TURNSTATE_OFF;
chr->act_stand.checkfacingwall = false;
chr->act_stand.wallcount = random() % 120 + 180; // 180 to 299
chr->act_stand.mergetime = mergetime;
chr->act_stand.playwalkanim = false;
fsleep = mergetime;
if (chr->model->anim->playspeed != PALUPF(1.0f)) {
fsleep *= PALUPF(1.0f) / chr->model->anim->playspeed;
}
if (fsleep > limit) {
fsleep = limit;
}
chr->sleep = fsleep;
if (modelIsAnimMerging(chr->model) && !chr->aibot) {
chr->hidden |= CHRHFLAG_NEEDANIM;
} else {
chrChooseStandAnimation(chr, mergetime);
chr->hidden &= ~CHRHFLAG_NEEDANIM;
}
}
void chrStand(struct chrdata *chr)
{
s32 race = CHRRACE(chr);
f32 result;
if (race != RACE_EYESPY) {
chrStopFiring(chr);
if (race == RACE_HUMAN && chr->actiontype == ACT_KNEEL) {
chrStopFiring(chr);
chr->actiontype = ACT_STAND;
chr->act_stand.prestand = true;
chr->act_stand.flags = 0;
chr->act_stand.entityid = 0;
chr->act_stand.reaim = 0;
chr->act_stand.turning = TURNSTATE_OFF;
chr->act_stand.checkfacingwall = false;
chr->act_stand.wallcount = random() % 120 + 180;
chr->sleep = 0;
chr->act_stand.playwalkanim = false;
if (chr->aibot == NULL) {
if (modelGetAnimNum(chr->model) == ANIM_KNEEL_SHOOT_RIGHT_HAND) {
result = chrGetRangedSpeed(chr, 0.5, 0.8);
modelSetAnimation(chr->model, ANIM_KNEEL_SHOOT_RIGHT_HAND,
chr->model->anim->flip, 109, result, 16);
modelSetAnimEndFrame(chr->model, 140);
} else {
result = chrGetRangedSpeed(chr, 0.5, 0.8);
modelSetAnimation(chr->model, ANIM_KNEEL_TWO_HANDED_GUN,
chr->model->anim->flip, 120, result, 16);
modelSetAnimEndFrame(chr->model, 151);
}
}
} else if (race == RACE_DRCAROLL || race == RACE_ROBOT) {
chr->actiontype = ACT_STAND;
chr->act_stand.prestand = true;
chr->act_stand.flags = 0;
chr->act_stand.entityid = 0;
chr->act_stand.reaim = 0;
chr->act_stand.turning = TURNSTATE_OFF;
chr->act_stand.checkfacingwall = false;
chr->act_stand.wallcount = random() % 120 + 180;
chr->sleep = 0;
chr->act_stand.playwalkanim = false;
func0f02e9a0(chr, 16);
} else {
func0f02e9a0(chr, 16);
}
}
}
void func0f02ed28(struct chrdata *chr, f32 mergetime)
{
func0f02e9a0(chr, mergetime);
chr->act_stand.checkfacingwall = true;
}
void chrStop(struct chrdata *chr)
{
chrStand(chr);
chr->act_stand.checkfacingwall = true;
}
void chrKneelChooseAnimation(struct chrdata *chr)
{
struct prop *gun1 = chrGetHeldProp(chr, 1);
struct prop *gun2 = chrGetHeldProp(chr, 0);
if (chr->aibot == NULL) {
if ((gun1 && gun2)
|| (!gun1 && !gun2)
|| weaponIsOneHanded(gun1)
|| weaponIsOneHanded(gun2)) {
bool flip = random() % 2;
modelSetAnimation(chr->model, ANIM_KNEEL_SHOOT_RIGHT_HAND, flip, 0, chrGetRangedSpeed(chr, 0.5, 0.8), 16);
modelSetAnimEndFrame(chr->model, 28);
} else if (gun2 || gun1) {
modelSetAnimation(chr->model, ANIM_KNEEL_TWO_HANDED_GUN, gun1 != NULL, 0, chrGetRangedSpeed(chr, 0.5, 0.8), 16);
modelSetAnimEndFrame(chr->model, 27);
}
}
}
void chrKneel(struct chrdata *chr)
{
chrStopFiring(chr);
chr->actiontype = ACT_KNEEL;
chr->sleep = 0;
if (modelIsAnimMerging(chr->model)) {
chr->hidden |= CHRHFLAG_NEEDANIM;
} else {
chrKneelChooseAnimation(chr);
chr->hidden &= ~CHRHFLAG_NEEDANIM;
}
}
void chrStartAlarmChooseAnimation(struct chrdata *chr)
{
struct prop *gun1 = chrGetHeldProp(chr, 1);
struct prop *gun2 = chrGetHeldProp(chr, 0);
bool flip = false;
if (gun1 && !gun2) {
flip = true;
} else if ((gun1 && gun2) || (!gun1 && !gun2)) {
flip = random() % 2;
}
modelSetAnimation(chr->model, ANIM_TALKING_003D, flip, 40, 1, 16);
modelSetAnimEndFrame(chr->model, 82);
}
void chrThrowGrenadeChooseAnimation(struct chrdata *chr)
{
u32 rand = random();
if (chr->act_throwgrenade.needsequip) {
if (rand % 3 == 0) {
modelSetAnimation(chr->model, ANIM_THROWGRENADE_CROUCHING, chr->act_throwgrenade.hand != 0, 0, chrGetRangedSpeed(chr, 0.5, 1.2), 16);
} else if (rand % 3 == 1) {
modelSetAnimation(chr->model, ANIM_THROWGRENADE_NOPIN, chr->act_throwgrenade.hand != 0, 0, chrGetRangedSpeed(chr, 0.5, 1.2), 16);
} else {
modelSetAnimation(chr->model, ANIM_THROWGRENADE_STANDING, chr->act_throwgrenade.hand != 0, 0, chrGetRangedSpeed(chr, 0.5, 1.2), 16);
}
} else {
if (rand % 3 == 0) {
modelSetAnimation(chr->model, ANIM_THROWGRENADE_CROUCHING, chr->act_throwgrenade.hand != 0, 5, chrGetRangedSpeed(chr, 0.5, 1.2), 16);
} else if (rand % 3 == 1) {
modelSetAnimation(chr->model, ANIM_THROWGRENADE_NOPIN, chr->act_throwgrenade.hand != 0, 6, chrGetRangedSpeed(chr, 0.5, 1.2), 16);
} else {
modelSetAnimation(chr->model, ANIM_THROWGRENADE_STANDING, chr->act_throwgrenade.hand != 0, 84, chrGetRangedSpeed(chr, 0.5, 1.2), 16);
}
}
modelSetAnimEndFrame(chr->model, -1);
}
void chrThrowGrenade(struct chrdata *chr, s32 hand, s32 needsequip)
{
chrStopFiring(chr);
chr->actiontype = ACT_THROWGRENADE;
chr->act_throwgrenade.hand = hand;
chr->act_throwgrenade.needsequip = needsequip;
chr->sleep = 0;
if (modelIsAnimMerging(chr->model)) {
chr->hidden |= CHRHFLAG_NEEDANIM;
} else {
chrThrowGrenadeChooseAnimation(chr);
chr->hidden &= ~CHRHFLAG_NEEDANIM;
}
}
void chrSurprisedChooseAnimation(struct chrdata *chr)
{
if (chr->act_surprised.type == 1) {
struct prop *gun1 = chrGetHeldProp(chr, 1);
struct prop *gun0 = chrGetHeldProp(chr, 0);
s32 flip = 0;
if (gun1 != NULL && gun0 == NULL) {
flip = 1;
} else if ((gun1 != NULL && gun0 != NULL) || (gun1 == NULL && gun0 == NULL)) {
flip = random() & 1;
}
modelSetAnimation(chr->model, ANIM_003F, flip, 10, chrGetRangedSpeed(chr, 0.6f, 0.96000003f), 16);
modelSetAnimEndFrame(chr->model, 52);
} else if (chr->act_surprised.type == 2) {
modelSetAnimation(chr->model, ANIM_SURRENDER_002E, random() & 1, 0, chrGetRangedSpeed(chr, 0.35f, 0.56f), 16);
modelSetAnimEndFrame(chr->model, 7);
} else {
u32 part = random() % 3;
modelSetAnimation(chr->model, ANIM_0040, random() & 1, 17, 0.6f, 16);
if (part == 0) {
modelSetAnimEndFrame(chr->model, chrGetRangedSpeed(chr, 38, 8));
} else if (part == 1) {
modelSetAnimEndFrame(chr->model, chrGetRangedSpeed(chr, 66, 8));
} else {
modelSetAnimEndFrame(chr->model, chrGetRangedSpeed(chr, 96, 8));
}
}
}
void chrSurrenderChooseAnimation(struct chrdata *chr)
{
struct prop *gun1 = chrGetHeldProp(chr, 1);
struct prop *gun0 = chrGetHeldProp(chr, 0);
if (gun0 || gun1) {
modelSetAnimation(chr->model, ANIM_SURRENDER_002F, random() & 1, 0, 0.5, 16);
modelSetAnimLooping(chr->model, 40, 16);
if (gun1) {
objSetDropped(gun1, DROPTYPE_SURRENDER);
}
if (gun0) {
objSetDropped(gun0, DROPTYPE_SURRENDER);
}
chr->hidden |= CHRHFLAG_00000001;
} else {
modelSetAnimation(chr->model, ANIM_SURRENDER_002E, random() & 1, 0, 0.5, 16);
modelSetAnimLooping(chr->model, 30, 16);
}
chrDropConcealedItems(chr);
}
void chrSurrender(struct chrdata *chr)
{
u32 action = ACT_SURRENDER;
if (chr->actiontype != action) {
chrStopFiring(chr);
chr->actiontype = action;
chr->sleep = action;
if (modelIsAnimMerging(chr->model)) {
chr->hidden |= CHRHFLAG_NEEDANIM;
} else {
chrSurrenderChooseAnimation(chr);
chr->hidden &= ~CHRHFLAG_NEEDANIM;
}
}
}
void chrSidestepChooseAnimation(struct chrdata *chr)
{
struct prop *gun1 = chrGetHeldProp(chr, 1);
struct prop *gun2 = chrGetHeldProp(chr, 0);
bool flip = false;
bool allowflip = false;
u32 race = CHRRACE(chr);
if (gun1 && gun2) {
flip = random() % 2;
allowflip = random() % 2;
} else {
if (weaponIsOneHanded(gun1) == false
&& weaponIsOneHanded(gun2) == false
&& (gun1 || gun2)) {
flip = (gun1 != 0);
allowflip = random() % 2;
}
}
if (race == RACE_HUMAN) {
if (allowflip == false) {
if (chr->act_sidestep.side) {
modelSetAnimation(chr->model, ANIM_0068, true, 5, chrGetRangedSpeed(chr, 0.55, 0.88000005), 16);
modelSetAnimEndFrame(chr->model, 36);
} else {
modelSetAnimation(chr->model, ANIM_0068, false, 5, chrGetRangedSpeed(chr, 0.55, 0.88000005), 16);
modelSetAnimEndFrame(chr->model, 36);
}
} else {
if ((chr->act_sidestep.side && !flip) || (chr->act_sidestep.side == 0 && flip)) {
modelSetAnimation(chr->model, ANIM_003B, flip, 5, chrGetRangedSpeed(chr, 0.7, 1.12), 16);
modelSetAnimEndFrame(chr->model, 34);
} else {
modelSetAnimation(chr->model, ANIM_003A, flip, 5, chrGetRangedSpeed(chr, 0.7, 1.12), 16);
modelSetAnimEndFrame(chr->model, 32);
}
}
} else if (race == RACE_SKEDAR) {
if (chr->act_sidestep.side) {
modelSetAnimation(chr->model, ANIM_0328, false, 5, chrGetRangedSpeed(chr, 0.55, 0.88000005), 16);
modelSetAnimEndFrame(chr->model, 27);
} else {
modelSetAnimation(chr->model, ANIM_0328, true, 5, chrGetRangedSpeed(chr, 0.55, 0.88000005), 16);
modelSetAnimEndFrame(chr->model, 27);
}
}
}
void chrSidestep(struct chrdata *chr, bool side)
{
chrStopFiring(chr);
chr->actiontype = ACT_SIDESTEP;
chr->act_sidestep.side = side;
chr->sleep = 0;
if (modelIsAnimMerging(chr->model)) {
chr->hidden |= CHRHFLAG_NEEDANIM;
} else {
chrSidestepChooseAnimation(chr);
chr->hidden &= ~CHRHFLAG_NEEDANIM;
}
}
void chrJumpOutChooseAnimation(struct chrdata *chr)
{
struct prop *gun1 = chrGetHeldProp(chr, 1);
struct prop *gun2 = chrGetHeldProp(chr, 0);
bool flip = false;
if (gun1 && !gun2) {
flip = true;
} else if ((gun1 && gun2) || (!gun1 && !gun2)
|| weaponIsOneHanded(gun1) || weaponIsOneHanded(gun2)) {
flip = random() % 2;
}
if ((chr->act_jumpout.side && !flip) || (chr->act_jumpout.side == 0 && flip)) {
modelSetAnimation(chr->model, ANIM_0068, true, 5, chrGetRangedSpeed(chr, 0.55, 0.88000005), 16);
modelSetAnimEndFrame(chr->model, 36);
} else {
modelSetAnimation(chr->model, ANIM_0068, false, 5, chrGetRangedSpeed(chr, 0.55, 0.88000005), 16);
modelSetAnimEndFrame(chr->model, 36);
}
}
void chrJumpOut(struct chrdata *chr, bool side)
{
chrStopFiring(chr);
chr->actiontype = ACT_JUMPOUT;
chr->act_jumpout.side = side;
chr->sleep = 0;
if (modelIsAnimMerging(chr->model)) {
chr->hidden |= CHRHFLAG_NEEDANIM;
} else {
chrJumpOutChooseAnimation(chr);
chr->hidden &= ~CHRHFLAG_NEEDANIM;
}
}
void chrRunPosChooseAnimation(struct chrdata *chr)
{
f32 xdiff = chr->prop->pos.x - chr->act_runpos.pos.x;
f32 ydiff = chr->prop->pos.y - chr->act_runpos.pos.y;
f32 zdiff = chr->prop->pos.z - chr->act_runpos.pos.z;
f32 distance = sqrtf(xdiff * xdiff + zdiff * zdiff);
struct prop *gun1 = chrGetHeldProp(chr, 1);
struct prop *gun2 = chrGetHeldProp(chr, 0);
bool heavy = true;
bool flip;
s32 race = CHRRACE(chr);
if ((gun1 && gun2) || (!gun1 && !gun2)) {
heavy = false;
flip = random() % 2;
} else if (weaponIsOneHanded(gun1) || weaponIsOneHanded(gun2)) {
heavy = false;
flip = (bool)gun1 != false;
} else {
flip = (bool)gun1 != false;
}
if (race == RACE_HUMAN) {
if (heavy) {
f32 mult = 0.5;
#if PAL
chr->act_runpos.eta60 = 1.0f / (func0f02dff0(ANIM_RUNNING_TWOHANDGUN) * mult) * distance * 0.83333331346512f;
#else
chr->act_runpos.eta60 = 1.0f / (func0f02dff0(ANIM_RUNNING_TWOHANDGUN) * mult) * distance;
#endif
modelSetAnimation(chr->model, ANIM_RUNNING_TWOHANDGUN, flip, 0, mult, 16);
} else {
f32 mult = 0.5;
#if PAL
chr->act_runpos.eta60 = 1.0f / (func0f02dff0(ANIM_RUNNING_ONEHANDGUN) * mult) * distance * 0.83333331346512f;
#else
chr->act_runpos.eta60 = 1.0f / (func0f02dff0(ANIM_RUNNING_ONEHANDGUN) * mult) * distance;
#endif
modelSetAnimation(chr->model, ANIM_RUNNING_ONEHANDGUN, flip, 0, mult, 16);
}
} else if (race == RACE_SKEDAR) {
f32 mult = 0.5;
#if PAL
chr->act_runpos.eta60 = 1.0f / (func0f02dff0(ANIM_SKEDAR_RUNNING) * mult) * distance * 0.83333331346512f;
#else
chr->act_runpos.eta60 = 1.0f / (func0f02dff0(ANIM_SKEDAR_RUNNING) * mult) * distance;
#endif
modelSetAnimation(chr->model, ANIM_SKEDAR_RUNNING, flip, 0, mult, 16);
}
}
void chrRunToPos(struct chrdata *chr, struct coord *pos)
{
chrStopFiring(chr);
chr->actiontype = ACT_RUNPOS;
chr->act_runpos.pos = *pos;
chr->sleep = 0;
chr->act_runpos.neardist = 30;
chr->act_runpos.turnspeed = 0;
if (modelIsAnimMerging(chr->model)) {
chr->hidden |= CHRHFLAG_NEEDANIM;
} else {
chrRunPosChooseAnimation(chr);
chr->hidden &= ~CHRHFLAG_NEEDANIM;
}
}
void chrAttackStand(struct chrdata *chr, u32 attackflags, s32 entityid)
{
struct prop *leftgun = chrGetHeldProp(chr, HAND_LEFT);
struct prop *rightgun = chrGetHeldProp(chr, HAND_RIGHT);
bool flip;
struct attackanimgroup **animgroup;
bool firing[] = {false, false};
s32 race = CHRRACE(chr);
if (leftgun && rightgun) {
struct prop *leftgun2 = chrGetHeldUsableProp(chr, HAND_LEFT);
struct prop *rightgun2 = chrGetHeldUsableProp(chr, HAND_RIGHT);
if (leftgun2 && rightgun2) {
flip = random() % 2;
if (random() % 3 == 0) {
animgroup = g_StandLightAttackAnims[race];
firing[HAND_LEFT] = flip;
firing[HAND_RIGHT] = !flip;
} else {
animgroup = g_StandDualAttackAnims[race];
firing[HAND_LEFT] = true;
firing[HAND_RIGHT] = true;
}
} else {
flip = (bool)rightgun2 == false;
animgroup = g_StandLightAttackAnims[race];
firing[HAND_LEFT] = (bool)rightgun2 == false;
firing[HAND_RIGHT] = !flip;
}
} else {
if (weaponIsOneHanded(leftgun) || weaponIsOneHanded(rightgun)) {
flip = (bool)leftgun != false;
animgroup = g_StandLightAttackAnims[race];
firing[HAND_LEFT] = (bool)leftgun != false;
firing[HAND_RIGHT] = !flip;
} else {
flip = (bool)leftgun != false;
animgroup = g_StandHeavyAttackAnims[race];
firing[HAND_LEFT] = (bool)leftgun != false;
firing[HAND_RIGHT] = !flip;
}
}
chrAttack(chr, animgroup, flip, firing, attackflags, entityid, true);
}
void chrAttackLie(struct chrdata *chr, u32 attackflags, s32 entityid)
{
u32 stack[2];
struct prop *gun = chrGetHeldProp(chr, HAND_RIGHT);
s32 firing[2] = {false, false};
if (chr);
if (attackflags & ATTACKFLAG_AIMONLY) {
firing[1] = false;
firing[0] = false;
} else {
bool tmp = gun == NULL;
firing[1] = tmp;
firing[0] = (bool)!tmp;
}
chrAttack(chr, &g_LieAttackAnims, gun == NULL, firing, attackflags, entityid, false);
}
void chrAttackKneel(struct chrdata *chr, u32 attackflags, s32 entityid)
{
struct prop *leftgun = chrGetHeldProp(chr, HAND_LEFT);
struct prop *rightgun = chrGetHeldProp(chr, HAND_RIGHT);
s32 flip;
struct attackanimgroup **animgroup;
bool firing[2] = {false, false};
s32 race = CHRRACE(chr);
struct prop *leftgun2;
struct prop *rightgun2;
if (leftgun && rightgun) {
leftgun2 = chrGetHeldUsableProp(chr, HAND_LEFT);
rightgun2 = chrGetHeldUsableProp(chr, HAND_RIGHT);
if (leftgun2 && rightgun2) {
flip = random() % 2;
if (random() % 3 == 0) {
animgroup = g_KneelLightAttackAnims[race];
firing[HAND_LEFT] = flip;
firing[HAND_RIGHT] = !flip;
} else {
animgroup = g_KneelDualAttackAnims[race];
firing[HAND_LEFT] = true;
firing[HAND_RIGHT] = true;
}
} else {
flip = (bool)rightgun2 == false;
animgroup = g_KneelLightAttackAnims[race];
firing[HAND_LEFT] = (bool)rightgun2 == false;
firing[HAND_RIGHT] = !flip;
}
} else {
if (weaponIsOneHanded(leftgun) || weaponIsOneHanded(rightgun)) {
flip = (bool)leftgun != false;
animgroup = g_KneelLightAttackAnims[race];
firing[HAND_LEFT] = (bool)leftgun != false;
firing[HAND_RIGHT] = !flip;
} else {
flip = (bool)leftgun != false;
animgroup = g_KneelHeavyAttackAnims[race];
firing[HAND_LEFT] = (bool)leftgun != false;
firing[HAND_RIGHT] = !flip;
}
}
chrAttack(chr, animgroup, flip, firing, attackflags, entityid, false);
}
void chrAttackWalkChooseAnimation(struct chrdata *chr)
{
if (chr->aibot == NULL) {
modelSetAnimation(chr->model, chr->act_attackwalk.animcfg->animnum,
chr->act_attackwalk.flip, chr->act_attackwalk.animcfg->unk10, 0.5, 16);
}
}
void chrAttackWalk(struct chrdata *chr, bool run)
{
struct attackanimconfig *animcfg;
struct prop *leftgun = chrGetHeldProp(chr, HAND_LEFT);
struct prop *rightgun = chrGetHeldProp(chr, HAND_RIGHT);
bool flip;
bool firing[] = {false, false};
bool everytick[] = {false, false};
bool singleshot[] = {false, false};
s32 i;
struct prop *prop;
struct weaponobj *weapon;
if (leftgun && rightgun) {
struct prop *leftgun2 = chrGetHeldUsableProp(chr, HAND_LEFT);
struct prop *rightgun2 = chrGetHeldUsableProp(chr, HAND_RIGHT);
s32 style = 0;
if (leftgun2 && rightgun2) {
flip = random() % 2;
style = random() % 3;
} else {
flip = (bool)rightgun2 == false;
}
if (style == 0) {
if (run) {
animcfg = &g_AttackAnimLightRun;
} else {
animcfg = &g_AttackAnimLightWalk;
}
if (flip) {
firing[HAND_LEFT] = true;
} else {
firing[HAND_RIGHT] = true;
}
} else if (style == 1) {
if (run) {
animcfg = &g_AttackAnimDualRun;
} else {
animcfg = &g_AttackAnimDualWalk;
}
firing[HAND_LEFT] = firing[HAND_RIGHT] = true;
} else {
if (run) {
animcfg = &g_AttackAnimDualCrossedRun;
} else {
animcfg = &g_AttackAnimDualCrossedWalk;
}
firing[HAND_LEFT] = firing[HAND_RIGHT] = true;
}
} else {
if (weaponIsOneHanded(leftgun) || weaponIsOneHanded(rightgun)) {
flip = (bool)leftgun != false;
if (run) {
animcfg = &g_AttackAnimLightRun;
} else {
animcfg = &g_AttackAnimLightWalk;
}
if (flip) {
firing[HAND_LEFT] = true;
} else {
firing[HAND_RIGHT] = true;
}
} else {
flip = (bool)leftgun != false;
if (run) {
animcfg = &g_AttackAnimHeavyRun;
} else {
animcfg = &g_AttackAnimHeavyWalk;
}
if (flip) {
firing[HAND_LEFT] = true;
} else {
firing[HAND_RIGHT] = true;
}
}
}
for (i = 0; i < 2; i++) {
if (firing[i]) {
prop = chrGetHeldProp(chr, i);
weapon = prop->weapon;
if (weaponGetNumTicksPerShot(weapon->weaponnum, weapon->gunfunc) < 1) {
everytick[i] = true;
}
if (weapon->weaponnum == WEAPON_ROCKETLAUNCHER
|| weapon->weaponnum == WEAPON_ROCKETLAUNCHER_34
|| weapon->weaponnum == WEAPON_SLAYER
|| weapon->weaponnum == WEAPON_DEVASTATOR
|| (
!g_Vars.normmplayerisrunning
&& weapon->weaponnum == WEAPON_DY357MAGNUM
&& chr->headnum != HEAD_JONATHAN
&& chr->headnum != HEAD_CHRIST)
|| (
!g_Vars.normmplayerisrunning
&& weapon->weaponnum == WEAPON_DY357LX)
|| (
!g_Vars.normmplayerisrunning
&& weapon->weaponnum == WEAPON_SHOTGUN)) {
singleshot[i] = true;
}
}
}
chr->actiontype = ACT_ATTACKWALK;
chr->act_attackwalk.frame60count = 0;
#if PAL
// This is really TICKS(400.0f), but off by one bit :(
chr->act_attackwalk.frame60max = random() % (s32)(333.33331298828f * g_AttackWalkDurationScale) + TICKS(120);
#else
chr->act_attackwalk.frame60max = random() % (s32)(400 * g_AttackWalkDurationScale) + TICKS(120);
#endif
chr->act_attackwalk.facedtarget = false;
chr->act_attackwalk.animcfg = animcfg;
chr->act_attackwalk.nextshot60 = 0;
chr->act_attackwalk.nextgun = random() % 2;
chr->act_attackwalk.firegun[HAND_LEFT] = firing[HAND_LEFT];
chr->act_attackwalk.firegun[HAND_RIGHT] = firing[HAND_RIGHT];
chr->act_attackwalk.everytick[HAND_LEFT] = everytick[HAND_LEFT];
chr->act_attackwalk.everytick[HAND_RIGHT] = everytick[HAND_RIGHT];
chr->act_attackwalk.singleshot[HAND_LEFT] = singleshot[HAND_LEFT];
chr->act_attackwalk.singleshot[HAND_RIGHT] = singleshot[HAND_RIGHT];
chr->act_attackwalk.turnspeed = 0;
chr->act_attackwalk.flip = flip;
chr->sleep = 0;
chr->chrflags &= ~CHRCFLAG_INJUREDTARGET;
if (modelIsAnimMerging(chr->model)) {
chr->hidden |= CHRHFLAG_NEEDANIM;
} else {
chrAttackWalkChooseAnimation(chr);
chr->hidden &= ~CHRHFLAG_NEEDANIM;
}
}
void chrAttackRollChooseAnimation(struct chrdata *chr)
{
modelSetAnimation(chr->model, chr->act_attack.animcfg->animnum, chr->act_attack.flip,
chr->act_attack.animcfg->unk10, chrGetRangedSpeed(chr, 0.5, 0.8), 16);
if (chr->act_attack.onehanded == false) {
if (chr->act_attack.dorecoil) {
if (chr->act_attack.animcfg->unk24 >= 0) {
modelSetAnimEndFrame(chr->model, chr->act_attack.animcfg->unk24);
} else {
modelSetAnimEndFrame(chr->model, chr->act_attack.animcfg->unk1c);
}
} else {
if (chr->act_attack.animcfg->unk20 >= 0) {
modelSetAnimEndFrame(chr->model, chr->act_attack.animcfg->unk20);
} else if (chr->act_attack.animcfg->unk14 >= 0) {
modelSetAnimEndFrame(chr->model, chr->act_attack.animcfg->unk14);
}
}
}
}
void chrAttackRoll(struct chrdata *chr, bool toleft)
{
struct attackanimconfig *animcfg;
struct prop *leftgun = chrGetHeldProp(chr, HAND_LEFT);
struct prop *rightgun = chrGetHeldProp(chr, HAND_RIGHT);
bool flip;
bool onehanded = false;
struct prop *prop;
struct weaponobj *weapon;
bool dorecoil = true;
bool firing[] = {false, false};
s32 i;
bool dooneburst = false;
bool everytick[] = {false, false};
bool singleshot[] = {false, false};
if (leftgun && rightgun) {
struct prop *leftgun2 = chrGetHeldUsableProp(chr, HAND_LEFT);
struct prop *rightgun2 = chrGetHeldUsableProp(chr, HAND_RIGHT);
if (leftgun2 && rightgun2) {
flip = random() % 2;
onehanded = true;
if (random() % 3 == 0) {
firing[HAND_LEFT] = flip;
firing[HAND_RIGHT] = !flip;
} else {
firing[HAND_LEFT] = true;
firing[HAND_RIGHT] = true;
}
} else {
flip = (bool)rightgun2 == false;
onehanded = true;
firing[HAND_LEFT] = flip;
firing[HAND_RIGHT] = !flip;
}
} else {
if (weaponIsOneHanded(leftgun) || weaponIsOneHanded(rightgun)) {
flip = (bool)leftgun != false;
onehanded = true;
firing[HAND_LEFT] = flip;
firing[HAND_RIGHT] = !flip;
} else {
flip = (bool)leftgun != false;
firing[HAND_LEFT] = flip;
firing[HAND_RIGHT] = !flip;
}
}
if ((toleft && !flip) || (!toleft && flip)) {
// Roll to left
if (random() % 2) {
animcfg = &g_RollAttackAnims[0];
} else {
animcfg = &g_RollAttackAnims[2];
}
} else {
// Roll to right
if (random() % 2) {
animcfg = &g_RollAttackAnims[1];
} else {
animcfg = &g_RollAttackAnims[3];
}
}
if (onehanded) {
// Move the animation pointer to the one-handed version of the same
// animation. The one-handed versions are in the array immediately after
// the four heavy-weapon versions.
animcfg += 4;
}
for (i = 0; i < 2; i++) {
if (firing[i]) {
prop = chrGetHeldProp(chr, i);
weapon = prop->weapon;
if (weaponGetNumTicksPerShot(weapon->weaponnum, weapon->gunfunc) < 1) {
everytick[i] = true;
if (weapon->weaponnum == WEAPON_LASER) {
dorecoil = false;
}
} else {
dorecoil = false;
dooneburst = true;
}
if (weapon->weaponnum == WEAPON_ROCKETLAUNCHER
|| weapon->weaponnum == WEAPON_ROCKETLAUNCHER_34
|| weapon->weaponnum == WEAPON_SLAYER
|| weapon->weaponnum == WEAPON_DEVASTATOR
|| (
!g_Vars.normmplayerisrunning
&& weapon->weaponnum == WEAPON_DY357MAGNUM
&& chr->headnum != HEAD_JONATHAN
&& chr->headnum != HEAD_CHRIST)
|| (
!g_Vars.normmplayerisrunning
&& weapon->weaponnum == WEAPON_DY357LX)
|| (
!g_Vars.normmplayerisrunning
&& weapon->weaponnum == WEAPON_SHOTGUN)) {
singleshot[i] = true;
}
}
}
chr->actiontype = ACT_ATTACKROLL;
chr->act_attack.animcfg = animcfg;
chr->act_attack.fired = false;
chr->act_attack.nextgun = random() % 2;
chr->act_attack.firegun[HAND_LEFT] = firing[HAND_LEFT];
chr->act_attack.firegun[HAND_RIGHT] = firing[HAND_RIGHT];
chr->act_attack.everytick[HAND_LEFT] = everytick[HAND_LEFT];
chr->act_attack.everytick[HAND_RIGHT] = everytick[HAND_RIGHT];
chr->act_attack.singleshot[HAND_LEFT] = singleshot[HAND_LEFT];
chr->act_attack.singleshot[HAND_RIGHT] = singleshot[HAND_RIGHT];
chr->act_attack.dorecoil = dorecoil;
chr->act_attack.dooneburst = dooneburst;
chr->act_attack.onehanded = onehanded;
chr->act_attack.pausecount = 0;
chr->act_attack.numshots = 0;
chr->act_attack.turning = TURNSTATE_TURNING;
if (singleshot[HAND_LEFT] || singleshot[HAND_RIGHT]) {
if (singleshot[HAND_LEFT] && singleshot[HAND_RIGHT]) {
chr->act_attack.maxshots = 2;
} else {
chr->act_attack.maxshots = 1;
}
} else {
chr->act_attack.maxshots = (random() % 4) + 2;
if (firing[HAND_RIGHT] && firing[HAND_LEFT]) {
chr->act_attack.maxshots += (random() % 4) + 2;
}
}
chr->act_attack.flags = ATTACKFLAG_AIMATTARGET;
chr->act_attack.entityid = 0;
chr->act_attack.standing = true;
chr->act_attack.reaim = 0;
chr->act_attack.lastfire60 = 0;
chr->act_attack.lastontarget60 = 0;
chr->act_attack.flip = flip;
chr->sleep = 0;
// @bug: CHRCFLAG_INJUREDTARGET is not unset here. This means if the chr
// does an attack that hits the target, then does a roll attack which
// misses, their AI list will incorrectly read the roll attack as a hit
// provided it didn't read and clear the flag between the attacks.
// It usually (always?) does though, so this isn't really an issue.
if (modelIsAnimMerging(chr->model)) {
chr->hidden |= CHRHFLAG_NEEDANIM;
} else {
chrAttackRollChooseAnimation(chr);
chr->hidden &= ~CHRHFLAG_NEEDANIM;
}
}
void chrStartAnim(struct chrdata *chr, s32 animnum, f32 startframe, f32 endframe, u8 chranimflags, s32 merge, f32 speed)
{
u32 stack;
if (chr && chr->model) {
if (chranimflags & CHRANIMFLAG_REVERSE) {
speed = -speed;
}
if (CHRRACE(chr) != RACE_DRCAROLL) {
chrStopFiring(chr);
}
chr->actiontype = ACT_ANIM;
chr->act_anim.movewheninvis = (chranimflags & CHRANIMFLAG_MOVEWHENINVIS) != 0;
chr->act_anim.pauseatend = (chranimflags & CHRANIMFLAG_PAUSEATEND) != 0;
chr->act_anim.completed = (chranimflags & CHRANIMFLAG_COMPLETED) != 0;
chr->act_anim.slowupdate = (chranimflags & CHRANIMFLAG_SLOWUPDATE) != 0;
chr->act_anim.lockpos = (chranimflags & CHRANIMFLAG_LOCKPOS) != 0;
chr->act_anim.ishitanim = false;
chr->act_anim.animnum = animnum;
chr->act_anim.flip = (chranimflags & CHRANIMFLAG_FLIP) != 0;
chr->act_anim.startframe = startframe;
chr->act_anim.endframe = endframe;
chr->act_anim.speed = speed;
chr->act_anim.blend = merge;
chr->sleep = chr->act_anim.slowupdate ? merge : 0;
if (merge > 0 && modelIsAnimMerging(chr->model)) {
chr->hidden |= CHRHFLAG_NEEDANIM;
} else {
modelSetAnimation(chr->model, animnum, (chranimflags & CHRANIMFLAG_FLIP) != 0, startframe, speed, merge);
if (endframe >= 0) {
modelSetAnimEndFrame(chr->model, endframe);
}
chr->hidden &= ~CHRHFLAG_NEEDANIM;
}
}
}
void chrBeginDead(struct chrdata *chr)
{
if (chr->actiontype != ACT_DEAD) {
chrStopFiring(chr);
if (chr->cover != -1) {
coverSetInUse(chr->cover, false);
chr->cover = -1;
}
chr->actiontype = ACT_DEAD;
chr->act_dead.fadetimer60 = chr->aibot ? 0 : -1;
chr->act_dead.fadenow = false;
chr->act_dead.fadewheninvis = false;
chr->act_dead.invistimer60 = 0;
chr->act_dead.notifychrindex = 0;
chr->sleep = 0;
if (chr->race == RACE_DRCAROLL) {
chr->drcarollimage_left = DRCAROLLIMAGE_STATIC;
chr->drcarollimage_right = DRCAROLLIMAGE_STATIC;
}
}
}
void func0f031254(struct chrdata *chr)
{
struct model *model = chr->model;
struct attackanimconfig *animcfg = chr->act_attack.animcfg;
if (chr->act_attack.flags & ATTACKFLAG_AIMONLY) {
if (animcfg->unk20 >= 0 && animcfg->unk20 < animcfg->unk18) {
modelSetAnimEndFrame(model, animcfg->unk20);
} else {
modelSetAnimEndFrame(model, animcfg->unk18);
}
} else if (chr->act_attack.dorecoil) {
if (animcfg->unk20 >= 0) {
modelSetAnimEndFrame(model, animcfg->unk20);
} else {
modelSetAnimEndFrame(model, animcfg->unk18);
}
} else {
if (animcfg->unk20 >= 0) {
modelSetAnimEndFrame(model, animcfg->unk20);
} else if (animcfg->unk14 >= 0) {
modelSetAnimEndFrame(model, animcfg->unk14);
} else {
modelSetAnimEndFrame(model, -1);
}
}
}
/**
* This function implements attack behaviour common to all the attack types,
* such as stand, kneel and lie.
*/
void chrAttack(struct chrdata *chr, struct attackanimgroup **animgroups, bool flip, bool *firing, u32 attackflags, s32 entityid, bool standing)
{
struct model *model = chr->model;
s32 i;
f32 angle;
struct attackanimconfig *animcfg;
struct prop *prop;
struct weaponobj *weapon;
s32 groupindex;
bool dooneburst = false;
s32 index;
bool everytick[] = {false, false};
bool singleshot[] = {false, false};
bool dorecoil = true;
s32 race = CHRRACE(chr);
u8 sniping = false;
if (race != RACE_DRCAROLL && race != RACE_EYESPY && race != RACE_ROBOT) {
chr->actiontype = ACT_ATTACK;
if (&animgroups[0] == &g_LieAttackAnims) {
sniping = true;
if (modelGetAnimNum(chr->model) != ANIM_SNIPING_ONGROUND) {
// Getting up or getting down
animcfg = var80067d28;
modelSetAnimation(model, animcfg->animnum, flip, animcfg->unk10, chrGetRangedSpeed(chr, 0.5f, 0.8f), 16);
modelSetAnimEndFrame(model, 236);
} else {
animcfg = var80067d70;
}
} else {
// Non-sniping animations: Choose animation based on angle to target
angle = chrGetAttackEntityRelativeAngle(chr, attackflags, entityid);
if (flip) {
groupindex = (M_BADTAU - angle) * 5.0937690734863f + 0.5f;
} else {
groupindex = angle * 5.0937690734863f + 0.5f;
}
if (groupindex < 0 || groupindex > 31) {
groupindex = 0;
}
index = random() % animgroups[groupindex]->len;
animcfg = &animgroups[groupindex]->animcfg[index];
}
for (i = 0; i < 2; i++) {
if (firing[i]) {
prop = chrGetHeldProp(chr, i);
if (prop == NULL) {
chrChooseStandAnimation(chr, 16);
return;
}
weapon = prop->weapon;
if (weaponGetNumTicksPerShot(weapon->weaponnum, weapon->gunfunc) < 1) {
// Note: the only weapon that can enter this branch is the laser
everytick[i] = true;
if (weapon->weaponnum == WEAPON_LASER) {
dorecoil = false;
}
} else {
dorecoil = false;
dooneburst = true;
}
// There's an easter egg here: Any guard with Chris T's head
// (Foster from the firing range) can fire multiple shots with
// the magnum.
if (weapon->weaponnum == WEAPON_ROCKETLAUNCHER
|| weapon->weaponnum == WEAPON_ROCKETLAUNCHER_34
|| weapon->weaponnum == WEAPON_SLAYER
|| weapon->weaponnum == WEAPON_DEVASTATOR
|| (
!g_Vars.normmplayerisrunning
&& weapon->weaponnum == WEAPON_DY357MAGNUM
&& chr->headnum != HEAD_JONATHAN
&& chr->headnum != HEAD_CHRIST)
|| (
!g_Vars.normmplayerisrunning
&& weapon->weaponnum == WEAPON_DY357LX)
|| (
!g_Vars.normmplayerisrunning
&& weapon->weaponnum == WEAPON_SHOTGUN)) {
singleshot[i] = true;
}
}
}
chr->act_attack.turning = TURNSTATE_TURNING;
chr->act_attack.animcfg = animcfg;
chr->act_attack.fired = false;
chr->act_attack.nextgun = random() % 2;
chr->act_attack.firegun[HAND_LEFT] = firing[HAND_LEFT];
chr->act_attack.firegun[HAND_RIGHT] = firing[HAND_RIGHT];
chr->act_attack.everytick[HAND_LEFT] = everytick[HAND_LEFT];
chr->act_attack.everytick[HAND_RIGHT] = everytick[HAND_RIGHT];
chr->act_attack.singleshot[HAND_LEFT] = singleshot[HAND_LEFT];
chr->act_attack.singleshot[HAND_RIGHT] = singleshot[HAND_RIGHT];
chr->act_attack.dorecoil = dorecoil;
chr->act_attack.dooneburst = dooneburst;
chr->act_attack.pausecount = 0;
chr->act_attack.numshots = 0;
if (singleshot[HAND_LEFT] || singleshot[HAND_RIGHT]) {
if (singleshot[HAND_LEFT] && singleshot[HAND_RIGHT]) {
chr->act_attack.maxshots = 2;
} else {
chr->act_attack.maxshots = 1;
}
} else {
if (attackflags & ATTACKFLAG_SINGLESHOT) {
chr->act_attack.maxshots = 1;
} else {
chr->act_attack.maxshots = (random() % 4) + 2;
}
// @bug: ATTACKFLAG_SINGLESHOT is not respected here if both guns
// are firing.
if (firing[HAND_RIGHT] && firing[HAND_LEFT]) {
chr->act_attack.maxshots += (random() % 4) + 2;
}
}
chr->act_attack.flags = attackflags;
chr->act_attack.entityid = entityid;
chr->act_attack.standing = standing;
chr->act_attack.reaim = 0;
chr->act_attack.lastfire60 = 0;
chr->act_attack.lastontarget60 = 0;
chr->act_attack.flip = flip;
chr->sleep = 0;
chr->chrflags &= ~CHRCFLAG_INJUREDTARGET;
if (!sniping && !chr->aibot) {
if (modelIsAnimMerging(chr->model)) {
chr->hidden |= CHRHFLAG_NEEDANIM;
} else {
modelSetAnimation(model, animcfg->animnum, flip, animcfg->unk10, chrGetRangedSpeed(chr, 0.5f, 0.8f), 16);
func0f031254(chr);
chr->hidden &= ~CHRHFLAG_NEEDANIM;
}
}
}
}
void chrAttackAmount(struct chrdata *chr, u32 attackflags, u32 entityid, u32 maxshots)
{
u32 stack;
struct prop *prop = chrGetHeldProp(chr, 0);
struct attackanimgroup **things = NULL;
bool firing[] = {false, false};
u32 race = CHRRACE(chr);
if (prop) {
things = weaponIsOneHanded(prop) ? g_StandLightAttackAnims[race] : g_StandHeavyAttackAnims[race];
firing[1] = false;
firing[0] = true;
}
chrAttack(chr, things, false, firing, attackflags, entityid, false);
chr->actiontype = ACT_ATTACKAMOUNT;
chr->act_attack.numshots = 0;
chr->act_attack.maxshots = maxshots;
chr->act_attack.dooneburst = false;
}
#if PAL
s32 g_DrCarollDyingTimer = 8;
#else
s32 g_DrCarollDyingTimer = 10;
#endif
u8 var80068080 = 50;
/**
* Given a perfectly alive chr, make them begin the process of dying or being
* knocked unconscious.
*
* This function handles:
* - Eyespy destruction
* - Transitioning to ACT_DIE
* - Choosing and applying death animations
* - Updating kill statistics
* - Dropping items
*/
void chrBeginDeath(struct chrdata *chr, struct coord *dir, f32 relangle, s32 hitpart, struct gset *gset, bool knockout, s32 aplayernum)
{
bool overridden = false;
bool instant;
s32 index = -1;
s32 animnum;
u32 stack1;
struct prop *prop = chr->prop;
struct model *model = chr->model;
u32 stack2;
s32 race = CHRRACE(chr);
bool wasknockedout = false;
s32 prevplayernum;
s32 i;
s32 buddyplayernum;
struct eyespy *eyespy;
s32 objectivenum;
f32 impactforce1;
f32 impactforce2;
f32 impactforce3;
// If chr was previously knocked out, they are now dead so decrease KO counter
if (chr->actiontype == ACT_DRUGGEDCOMINGUP
|| chr->actiontype == ACT_DRUGGEDDROP
|| chr->actiontype == ACT_DRUGGEDKO) {
if (chr->actiontype == ACT_DRUGGEDKO) {
wasknockedout = true;
}
mpstatsDecrementTotalKnockoutCount();
}
// Handle eyespy then return early
if (race == RACE_EYESPY) {
prevplayernum = g_Vars.currentplayernum;
buddyplayernum = -1;
eyespy = chrToEyespy(chr);
objectivenum = -1;
// Figure out which playernum has the eyespy that's being destroyed,
// and the buddy's playernum if applicable. Note that the player count
// can only be 1 or 2 here.
for (i = 0; i < PLAYERCOUNT(); i++) {
if (eyespy == g_Vars.players[i]->eyespy) {
setCurrentPlayerNum(i);
} else {
buddyplayernum = i;
}
}
if (g_Vars.currentplayer->eyespy) {
// Stop using eyespy if active
if (g_Vars.currentplayer->eyespy->active) {
g_Vars.currentplayer->eyespy->active = false;
g_Vars.currentplayer->devicesactive &= ~DEVICE_EYESPY;
}
// Destroy the eyespy
chr->hidden |= CHRHFLAG_REAPED;
explosionCreateSimple(g_Vars.currentplayer->eyespy->prop,
&g_Vars.currentplayer->eyespy->prop->pos,
g_Vars.currentplayer->eyespy->prop->rooms, EXPLOSIONTYPE_EYESPY, 0);
invRemoveItemByNum(WEAPON_EYESPY);
func0f0926bc(g_Vars.currentplayer->eyespy->prop, 1, 0xffff);
g_Vars.currentplayer->eyespy = NULL;
setCurrentPlayerNum(prevplayernum);
// For Investigation and G5 Building, set a stage flag to show that
// the eyespy is destroyed. The scripting in those stages checks for
// this flag and fails the objective if set.
switch (g_Vars.stagenum) {
case STAGE_INVESTIGATION:
objectivenum = 0;
break;
case STAGE_G5BUILDING:
objectivenum = 2;
break;
}
// But don't set the flag if the coop buddy still has an eyespy
if (objectivenum >= 0 && buddyplayernum >= 0) {
setCurrentPlayerNum(buddyplayernum);
if (g_Vars.currentplayer->eyespy) {
objectivenum = -1;
}
setCurrentPlayerNum(prevplayernum);
}
if (objectivenum >= 0 && objectiveCheck(objectivenum) != OBJECTIVE_COMPLETE) {
g_StageFlags |= STAGEFLAG_EYESPY_DESTROYED;
}
}
setCurrentPlayerNum(prevplayernum);
return;
}
// instant = whether to merge into death animation or switch to it instantly
instant = chr->actiontype == ACT_ARGH && chr->act_argh.lvframe60 == g_Vars.lvframe60;
for (i = 0; g_AnimTablesByRace[race][i].hitpart != -1; i++) {
if (g_AnimTablesByRace[race][i].hitpart == hitpart) {
index = i;
break;
}
}
// Set up chr's new action
chr->blurdrugamount = 0;
chrStopFiring(chr);
chrUncloak(chr, true);
chr->chrflags &= ~CHRCFLAG_HIDDEN;
chr->actiontype = (knockout == true ? ACT_DRUGGEDDROP : ACT_DIE);
chr->act_die.notifychrindex = 0;
chr->act_die.timeextra = 0;
chr->act_die.drcarollimagedelay = TICKS(45);
chr->act_die.thudframe1 = -1;
chr->act_die.thudframe2 = -1;
if (chr->race == RACE_DRCAROLL) {
chr->drcarollimage_left = (s32)((random() % 400) * 0.01f) + 1;
chr->drcarollimage_right = (s32)((random() % 400) * 0.01f) + 1;
}
chr->sleep = 0;
// Handle robots and Dr Caroll then return early
if (race == RACE_ROBOT || race == RACE_DRCAROLL) {
impactforce1 = gsetGetImpactForce(gset) * 0.5f;
if (impactforce1 <= 0) {
impactforce1 = 3;
}
if (impactforce1 != 0.0f) {
chr->elapseextra = 0;
chr->timeextra = impactforce1 * 15;
chr->extraspeed.x = dir->x * impactforce1;
chr->extraspeed.y = dir->y * impactforce1;
chr->extraspeed.z = dir->z * impactforce1;
}
if (race == RACE_DRCAROLL) {
g_DrCarollDyingTimer = 0;
chr->soundtimer = 0;
chr->voicebox = VOICEBOX_MALE1;
modelSetAnimation(chr->model, ANIM_0164, false, 0, 0.5f, 16);
}
return;
}
// Handle humans and Skedar
if (race == RACE_HUMAN) {
animnum = modelGetAnimNum(chr->model);
// Chrs in lying-down sniping poses don't use standard death animations
if (animnum == ANIM_SNIPING_GETDOWN
|| animnum == ANIM_SNIPING_GETUP
|| animnum == ANIM_SNIPING_ONGROUND) {
modelSetAnimation(chr->model, ANIM_SNIPING_DIE, false, 0, 0.5f, 16);
} else {
// Consider making the chr do an animation where they slump against
// a wall or object which is behind them.
if ((relangle < 1.5705462694168f || relangle > 4.7116389274597f)
&& random() % 20 == 0
&& chr->specialdie == SPECIALDIE_NONE) {
f32 angle1;
f32 angle2 = chrGetInverseTheta(chr);
f32 fval1;
f32 fval2;
angle1 = angle2 + 3.3155977725983f;
angle2 += 2.966587305069f;
if (angle1 >= M_BADTAU) {
angle1 -= M_BADTAU;
}
if (angle2 >= M_BADTAU) {
angle2 -= M_BADTAU;
}
fval1 = func0f02e684(prop, angle1, 150);
fval2 = func0f02e684(prop, angle2, 150);
if (fval1 < 150 && fval2 < 150
&& fval1 - fval2 < 10 && fval1 - fval2 > -10
&& !wasknockedout) {
struct animtablerow *row = &g_AnimTableHumanSlumped[random() % 4];
u32 stack3;
chr->act_die.thudframe1 = row->thudframe1;
chr->act_die.thudframe2 = row->thudframe2;
modelSetAnimationWithMerge(model, row->animnum, row->flip, 0, row->speed, 16, !instant);
if (row->endframe >= 0) {
modelSetAnimEndFrame(model, row->endframe);
}
chr->radius = 10;
chr->chrflags &= ~CHRCFLAG_HAS_SPECIAL_DEATH_ANIMATION;
overridden = true;
}
}
// Consider making the chr do a fall forward animation.
// The player must be behind the chr for it to happen.
if (relangle > 2.3558194637299f && relangle < 3.9263656139374f
&& random() % 5 < 2
&& chr->specialdie == SPECIALDIE_NONE) {
struct animtablerow *row;
struct animtablerow rows[] = {
{ 0x005b, 0, -1, 0.6, 0, 27, -1 },
{ 0x0255, 0, -1, 0.5, 0, 25, -1 },
};
bool flip;
overridden = true;
if (hitpart == HITPART_LBICEP || hitpart == HITPART_RBICEP) {
row = &rows[0];
if (hitpart == HITPART_LBICEP) {
flip = true;
} else {
flip = false;
}
} else {
row = &rows[1];
flip = random() % 2;
}
chr->act_die.thudframe1 = row->thudframe1;
chr->act_die.thudframe2 = row->thudframe2;
modelSetAnimationWithMerge(model, row->animnum, flip, 0, row->speed, 16, !instant);
if (row->endframe >= 0) {
modelSetAnimEndFrame(model, chrGetRangedArghSpeed(chr, row->endframe, 8));
} else {
modelSetAnimEndFrame(model, chrGetRangedArghSpeed(chr, animGetNumFrames(row->animnum) - 1, 8));
}
chr->chrflags &= ~CHRCFLAG_HAS_SPECIAL_DEATH_ANIMATION;
}
if (!overridden && index >= 0) {
// Handle specialdie animations or choose a random one if not
// using specialdie
if (g_AnimTablesByRace[race][index].deathanims
&& g_AnimTablesByRace[race][index].deathanimcount > 0
&& !wasknockedout) {
struct animtablerow *row;
if (chr->specialdie == SPECIALDIE_NONE) {
s32 tmp = random() % g_AnimTablesByRace[race][index].deathanimcount;
row = &g_AnimTablesByRace[race][index].deathanims[tmp];
} else if (chr->specialdie == SPECIALDIE_ONCHAIR) {
row = &g_SpecialDieAnims[chr->specialdie + random() % 2];
// chr->myspecial is the tag number of the chr's chair
if (chr->myspecial >= 0) {
struct defaultobj *obj = objFindByTagId(chr->myspecial);
obj->flags3 &= ~OBJFLAG3_PUSHABLE;
obj->flags |= OBJFLAG_INVINCIBLE;
// The original source likely didn't have the brackets here,
// but I'm including them to show the logic that's actually
// being used. There is no bug here, as obj and obj->prop
// are always set at this point so these checks are
// unnecessary.
if ((obj && obj->prop && obj->modelnum == MODEL_DD_REDARM)
|| obj->modelnum == MODEL_DD_REDSOFA) {
row = &g_SpecialDieAnims[chr->specialdie - 1];
}
}
} else {
row = &g_SpecialDieAnims[chr->specialdie - 1];
}
chr->act_die.thudframe1 = row->thudframe1;
chr->act_die.thudframe2 = row->thudframe2;
if (chr->specialdie == SPECIALDIE_NONE) {
modelSetAnimationWithMerge(model, row->animnum, row->flip, 0, row->speed, 16, !instant);
} else {
modelSetAnimationWithMerge(model, row->animnum, row->flip, 0, row->speed, 30, !instant);
}
if (row->endframe >= 0) {
modelSetAnimEndFrame(model, row->endframe);
}
impactforce2 = gsetGetImpactForce(gset);
if (impactforce2 <= 0 && (chr->chrflags & CHRCFLAG_DIEWITHFORCE)) {
impactforce2 = 6;
}
if (row->unk10 && impactforce2 > 0) {
chr->act_die.timeextra = impactforce2 * 15;
chr->act_die.elapseextra = 0;
chr->act_die.extraspeed.x = dir->x * impactforce2;
chr->act_die.extraspeed.y = dir->y * impactforce2;
chr->act_die.extraspeed.z = dir->z * impactforce2;
}
chr->chrflags &= ~CHRCFLAG_HAS_SPECIAL_DEATH_ANIMATION;
}
}
}
} else if (race == RACE_SKEDAR) {
struct animtablerow *row;
if (relangle > 2.3558194637299f && relangle < 3.9263656139374f) {
// Player is behind the Skedar - use specific set of anims
row = &g_AnimTablesByRace[race][1 + (random() % 6)].deathanims[random() % 3];
chr->act_die.thudframe1 = row->thudframe1;
chr->act_die.thudframe2 = row->thudframe2;
modelSetAnimationWithMerge(model, row->animnum, row->flip, 0, row->speed, 16, !instant);
if (row->endframe >= 0) {
modelSetAnimEndFrame(model, row->endframe);
}
} else {
// Normal Skedar death
if (index >= 0
&& g_AnimTablesByRace[race][index].deathanims != NULL
&& g_AnimTablesByRace[race][index].deathanimcount > 0) {
s32 tmp = random() % g_AnimTablesByRace[race][index].deathanimcount;
row = &g_AnimTablesByRace[race][index].deathanims[tmp];
} else {
row = &g_AnimTablesByRace[race][0].deathanims[0];
}
chr->act_die.thudframe1 = row->thudframe1;
chr->act_die.thudframe2 = row->thudframe2;
modelSetAnimationWithMerge(model, row->animnum, row->flip, 0, row->speed, 16, !instant);
if (row->endframe >= 0) {
modelSetAnimEndFrame(model, row->endframe);
}
impactforce3 = gsetGetImpactForce(gset);
if (impactforce3 <= 0 && (chr->chrflags & CHRCFLAG_DIEWITHFORCE)) {
impactforce3 = 6;
}
if (row->unk10 != 0 && impactforce3 > 0) {
chr->act_die.timeextra = impactforce3 * 15;
chr->act_die.elapseextra = 0;
chr->act_die.extraspeed.x = dir->x * impactforce3;
chr->act_die.extraspeed.y = dir->y * impactforce3;
chr->act_die.extraspeed.z = dir->z * impactforce3;
}
}
} else if (race == RACE_DRCAROLL) {
// empty
}
// Handle multiplayer stats and kill count
if (g_Vars.mplayerisrunning) {
mpstatsRecordDeath(aplayernum, mpPlayerGetIndex(chr));
} else if (aplayernum >= 0) {
s32 prevplayernum = g_Vars.currentplayernum;
setCurrentPlayerNum(aplayernum);
mpstatsRecordPlayerKill();
setCurrentPlayerNum(prevplayernum);
}
if (chr->chrflags & CHRCFLAG_KILLCOUNTABLE) {
mpstatsIncrementTotalKillCount();
}
// Drop items
if (race == RACE_HUMAN || race == RACE_SKEDAR) {
if (chr->weapons_held[0] && (chr->weapons_held[0]->obj->flags & OBJFLAG_AIUNDROPPABLE) == 0) {
objSetDropped(chr->weapons_held[0], DROPTYPE_DEFAULT);
chr->hidden |= CHRHFLAG_00000001;
}
if (chr->weapons_held[1] && (chr->weapons_held[1]->obj->flags & OBJFLAG_AIUNDROPPABLE) == 0) {
objSetDropped(chr->weapons_held[1], DROPTYPE_DEFAULT);
chr->hidden |= CHRHFLAG_00000001;
}
chrDropConcealedItems(chr);
}
}
void chrBeginArgh(struct chrdata *chr, f32 angle, s32 hitpart)
{
bool doneanim = false;
s32 instant;
s32 index = -1;
struct model *model = chr->model;
s32 i;
s32 race = CHRRACE(chr);
s32 animnum = modelGetAnimNum(chr->model);
if (animnum == ANIM_SNIPING_GETDOWN
|| animnum == ANIM_SNIPING_GETUP
|| animnum == ANIM_SNIPING_ONGROUND) {
chrFlinchBody(chr);
return;
}
if (race == RACE_EYESPY || chr->aibot) {
return;
}
if (race == RACE_DRCAROLL) {
chr->actiontype = ACT_ARGH;
chr->act_argh.notifychrindex = 0;
chr->act_argh.lvframe60 = g_Vars.lvframe60;
chr->sleep = 0;
modelSetAnimation(chr->model, ANIM_0163, false, 0, 0.5f, 16);
chr->drcarollimage_left = DRCAROLLIMAGE_X;
chr->drcarollimage_right = DRCAROLLIMAGE_X;
return;
}
instant = chr->actiontype == ACT_ARGH && chr->act_argh.lvframe60 == g_Vars.lvframe60;
for (i = 0; g_AnimTablesByRace[race][i].hitpart != -1; i++) {
if (g_AnimTablesByRace[race][i].hitpart == hitpart) {
index = i;
if (chr->hitpart == 0) {
chr->hitpart = hitpart;
}
break;
}
}
// If shot in the butt from behind, 2 in 5 chance of doing a special anim
if (race == RACE_HUMAN
&& hitpart == HITPART_PELVIS
&& angle > 2.3558194637299f
&& angle < 3.9263656139374f
&& random() % 5 < 2) {
struct animtablerow *row;
struct animtablerow rows[] = {
{ 0x013b, 0, -1, 0.5, 0, -1, -1 },
{ 0x013c, 0, -1, 0.5, 0, -1, -1 },
{ 0x013f, 0, -1, 0.5, 0, -1, -1 },
{ 0x0142, 0, -1, 0.5, 0, -1, -1 },
{ 0x0145, 0, -1, 0.5, 0, -1, -1 },
{ 0x0148, 0, -1, 0.5, 0, -1, -1 },
{ 0x0036, 0, -1, 0.5, 0, -1, -1 },
{ 0x0037, 0, -1, 0.5, 0, -1, -1 },
};
chrStopFiring(chr);
chr->actiontype = ACT_ARGH;
chr->act_argh.notifychrindex = 0;
chr->act_argh.lvframe60 = g_Vars.lvframe60;
chr->sleep = 0;
row = &rows[random() % 8];
modelSetAnimationWithMerge(model, row->animnum, row->flip, 0, row->speed, 16, !instant);
if (row->endframe >= 0) {
modelSetAnimEndFrame(model, chrGetRangedArghSpeed(chr, row->endframe, 8));
doneanim = true;
} else {
modelSetAnimEndFrame(model, chrGetRangedArghSpeed(chr, animGetNumFrames(row->animnum) - 1, 8));
doneanim = true;
}
}
if (!doneanim
&& index >= 0
&& g_AnimTablesByRace[race][index].injuryanims
&& g_AnimTablesByRace[race][index].injuryanimcount > 0) {
// If shot in a hand that's holding a gun, remap the hit location to the
// forearm because the hand injury animations assume the hand is empty.
struct prop *lgun = chrGetHeldProp(chr, HAND_LEFT);
struct prop *rgun = chrGetHeldProp(chr, HAND_RIGHT);
s32 rowindex;
struct animtablerow *row;
if (race == RACE_HUMAN) {
if (index == 9 && lgun) { // left hand
index = 10; // left forearm
} else if (index == 12 && rgun) { // right hand
index = 13; // right forearm
}
}
// Select a random animation for this hit location and apply it
rowindex = random() % g_AnimTablesByRace[race][index].injuryanimcount;
row = &g_AnimTablesByRace[race][index].injuryanims[rowindex];
chrStopFiring(chr);
chr->actiontype = ACT_ARGH;
chr->act_argh.notifychrindex = 0;
chr->act_argh.lvframe60 = g_Vars.lvframe60;
chr->sleep = 0;
modelSetAnimationWithMerge(model, row->animnum, row->flip, 0, row->speed, 16, !instant);
if (row->endframe >= 0) {
modelSetAnimEndFrame(model, chrGetRangedArghSpeed(chr, row->endframe, 8));
} else {
modelSetAnimEndFrame(model, chrGetRangedArghSpeed(chr, animGetNumFrames(row->animnum) - 1, 8));
}
}
}
void chrReactToDamage(struct chrdata *chr, struct coord *vector, f32 angle, s32 hitpart, struct gset *gset, s32 aplayernum)
{
s32 race = CHRRACE(chr);
bool knockedout = false;
s32 animnum = modelGetAnimNum(chr->model);
if (chr->actiontype == ACT_DRUGGEDKO) {
knockedout = true;
}
if (race == RACE_EYESPY) {
f32 strength = gsetGetImpactForce(gset);
struct eyespy *eyespy = chrToEyespy(chr);
if (eyespy) {
if (strength <= 0) {
strength = 6;
}
strength *= 4;
eyespy->hit = EYESPYHIT_DAMAGE;
eyespy->vel.x += vector->x * strength;
eyespy->vel.z += vector->z * strength;
}
}
if (chr->damage >= chr->maxdamage) {
chrBeginDeath(chr, vector, angle, hitpart, gset, false, aplayernum);
} else if (animnum == ANIM_SNIPING_GETDOWN
|| animnum == ANIM_SNIPING_GETUP
|| animnum == ANIM_SNIPING_ONGROUND) {
chrFlinchBody(chr);
} else if (race == RACE_EYESPY) {
// empty
} else if (race == RACE_DRCAROLL || race == RACE_ROBOT) {
f32 strength = gsetGetImpactForce(gset);
if (race == RACE_DRCAROLL) {
strength *= 0.5f;
}
if (strength <= 0) {
strength = 6;
}
if (strength > 0) {
chr->elapseextra = 0;
chr->timeextra = strength * 15;
chr->extraspeed.x = vector->x * strength;
chr->extraspeed.y = vector->y * strength;
chr->extraspeed.z = vector->z * strength;
}
if (race == RACE_DRCAROLL) {
chrBeginArgh(chr, 0, 0);
}
} else if (!knockedout) {
chrBeginArgh(chr, angle, hitpart);
}
}
/**
* Launch a chr away from the given pos (for explosions).
*/
void chrYeetFromPos(struct chrdata *chr, struct coord *exppos, f32 force)
{
struct model *model = chr->model;
struct prop *prop = chr->prop;
f32 faceangle;
f32 latangle;
u32 stack1;
s32 angleindex;
u32 stack2;
struct yeetanim *row;
struct coord dist;
u32 stack3;
s32 race = CHRRACE(chr);
f32 speed;
s32 subindex;
f32 angletoexplosion;
if (race != RACE_DRCAROLL && race != RACE_EYESPY && race != RACE_ROBOT) {
faceangle = chrGetInverseTheta(chr);
latangle = atan2f(prop->pos.x - exppos->x, prop->pos.z - exppos->z);
dist.x = prop->pos.x - exppos->x;
dist.y = prop->pos.y - exppos->y;
dist.z = prop->pos.z - exppos->z;
if (dist.f[0] == 0 && dist.f[1] == 0 && dist.f[2] == 0) {
dist.z = 1;
}
speed = 0.625f * force / sqrtf(dist.f[0] * dist.f[0] + dist.f[1] * dist.f[1] + dist.f[2] * dist.f[2]);
angletoexplosion = latangle - faceangle;
dist.x *= speed;
dist.y *= speed;
dist.z *= speed;
chr->fallspeed = dist;
if (latangle < faceangle) {
angletoexplosion += M_BADTAU;
}
angleindex = angletoexplosion * 1.2734422683716f + 0.5f;
if (angleindex >= 8) {
angleindex = 0;
}
subindex = random() % g_YeetAnimIndexesByRaceAngle[race][angleindex].count;
if (race == RACE_HUMAN) {
row = &g_YeetAnimsHuman[g_YeetAnimIndexesByRaceAngle[race][angleindex].indexes[subindex]];
} else if (race == RACE_SKEDAR) {
row = &g_YeetAnimsSkedar[g_YeetAnimIndexesByRaceAngle[race][angleindex].indexes[subindex]];
}
chrStopFiring(chr);
chrUncloak(chr, true);
chr->chrflags &= ~CHRCFLAG_HIDDEN;
chr->actiontype = ACT_DIE;
chr->act_die.notifychrindex = 0;
chr->act_die.thudframe1 = row->thudframe;
chr->act_die.thudframe2 = -1;
chr->act_die.timeextra = 0;
chr->act_die.drcarollimagedelay = TICKS(45);
if (chr->race == RACE_DRCAROLL) {
chr->drcarollimage_left = 1 + (s32)((random() % 400) * 0.01f);
chr->drcarollimage_right = 1 + (s32)((random() % 400) * 0.01f);
}
chr->sleep = 0;
modelSetAnimation(model, row->animnum, row->flip, row->startframe, row->speed, 8);
if (row->endframe >= 0.0f) {
modelSetAnimEndFrame(model, row->endframe);
}
}
}
s32 gsetGetBlurAmount(struct gset *gset)
{
s32 amount = TICKS(1000);
if (g_Vars.normmplayerisrunning == false) {
amount = TICKS(250);
}
if (gset->weaponnum == WEAPON_TRANQUILIZER) {
amount = TICKS(2000);
}
if (gset->weaponnum == WEAPON_BOLT) {
amount = TICKS(5000);
}
if (gset->weaponnum == WEAPON_NBOMB) {
amount = TICKS(100);
}
return amount;
}
void chrKnockOut(struct chrdata *chr, f32 angle, s32 hitpart, struct gset *gset)
{
if (chr->actiontype != ACT_DRUGGEDCOMINGUP
&& chr->actiontype != ACT_DRUGGEDDROP
&& chr->actiontype != ACT_DRUGGEDKO) {
if (mpstatsGetTotalKnockoutCount() < 2) {
chr->chrflags |= CHRCFLAG_KEEPCORPSEKO;
}
mpstatsIncrementTotalKnockoutCount();
chr->actiontype = ACT_DRUGGEDCOMINGUP;
chr->act_druggedcomingup.timer60 = 0;
}
}
/**
* Return true if the chr's current animation would be too awkward to transition
* into an injury animation or if they're already in the PREARGH action state,
* and set dst to the anim frame number where the chr will become available for
* transition to an injury animation.
*
* The attack roll animation is the only one which is too awkward to transition.
*/
bool chrIsAnimPreventingArgh(struct chrdata *chr, f32 *dst)
{
bool result = false;
s32 race = CHRRACE(chr);
if (race == RACE_DRCAROLL || race == RACE_EYESPY || chr->aibot) {
return false;
}
if (race == RACE_HUMAN) {
s32 animnum = modelGetAnimNum(chr->model);
f32 endframe;
if (animnum == ANIM_SNIPING_GETDOWN
|| animnum == ANIM_SNIPING_GETUP
|| animnum == ANIM_SNIPING_ONGROUND) {
chrFlinchBody(chr);
} else if (chr->actiontype == ACT_ATTACKROLL
&& modelGetAnimNum(chr->model) == chr->act_attack.animcfg->animnum) {
if (chr->act_attack.onehanded) {
if (chr->act_attack.animcfg == &g_RollAttackAnims[4]
|| chr->act_attack.animcfg == &g_RollAttackAnims[5]
|| chr->act_attack.animcfg == &g_RollAttackAnims[6]
|| chr->act_attack.animcfg == &g_RollAttackAnims[7]) {
endframe = chr->act_attack.animcfg->unk04 - 8;
if (chr->act_attack.animcfg->unk14 < chr->act_attack.animcfg->unk04) {
endframe = chr->act_attack.animcfg->unk14;
}
if (endframe > modelGetCurAnimFrame(chr->model)) {
*dst = endframe;
result = true;
}
}
} else {
endframe = chr->act_attack.animcfg->unk04 - 8;
if (endframe > modelGetCurAnimFrame(chr->model)) {
*dst = endframe;
result = true;
}
}
}
}
if (chr->actiontype == ACT_PREARGH) {
result = true;
}
return result;
}
void chrChoke(struct chrdata *chr, s32 choketype)
{
bool male = false;
s16 soundnum = -1;
s32 race = CHRRACE(chr);
s32 playernum;
s32 allowoverride = false;
static s32 nextindexdrcaroll = 0;
static s32 nextindexmaian = 0;
static s32 nextindexskedar = 0;
static s32 nextindexshock = 0;
static s32 nextindexmale = 0;
static s32 nextindexfemale = 0;
if (race == RACE_EYESPY || race == RACE_ROBOT) {
return;
}
if (chr->prop->type == PROPTYPE_PLAYER) {
playernum = playermgrGetPlayerNumByProp(chr->prop);
if (g_Vars.players[playernum]->isdead) {
return;
}
}
if (g_HeadsAndBodies[chr->headnum].ismale) {
male = true;
}
if (race == RACE_DRCAROLL) {
s16 sounds[] = {
SFX_ARGH_DRCAROLL_0240,
SFX_ARGH_DRCAROLL_024C,
SFX_ARGH_DRCAROLL_0250,
SFX_ARGH_DRCAROLL_0251,
SFX_ARGH_DRCAROLL_0259,
SFX_ARGH_DRCAROLL_025A,
};
if (g_DrCarollDyingTimer > TICKS(10)) {
g_DrCarollDyingTimer = 0;
soundnum = sounds[nextindexdrcaroll];
nextindexdrcaroll++;
if (nextindexdrcaroll >= ARRAYCOUNT(sounds)) {
nextindexdrcaroll = 0;
}
}
} else if (chr->headnum == HEAD_THEKING
|| chr->headnum == HEAD_ELVIS
|| chr->headnum == HEAD_MAIAN_S
|| chr->headnum == HEAD_ELVIS_GOGS) {
s16 sounds[] = {
SFX_ARGH_MAIAN_05DF,
SFX_ARGH_MAIAN_05E0,
SFX_ARGH_MAIAN_05E1
};
soundnum = sounds[random() % 3];
nextindexmaian++;
if (nextindexmaian >= ARRAYCOUNT(sounds)) {
nextindexmaian = 0;
}
} else if (race == RACE_SKEDAR) {
if (chr->bodynum == BODY_MINISKEDAR) {
s16 sounds[] = {
SFX_SKEDAR_ROAR_0536,
SFX_SKEDAR_ROAR_0537,
SFX_SKEDAR_ROAR_0538,
SFX_SKEDAR_ROAR_0539,
SFX_SKEDAR_ROAR_053A,
};
soundnum = sounds[random() % 5];
nextindexskedar++;
if (nextindexskedar >= ARRAYCOUNT(sounds)) {
nextindexskedar = 0;
}
} else {
s16 sounds[] = {
SFX_SKEDAR_ROAR_052D,
SFX_SKEDAR_ROAR_052E,
SFX_SKEDAR_ROAR_052F,
};
soundnum = sounds[random() % 3];
nextindexskedar++;
if (nextindexskedar >= ARRAYCOUNT(sounds)) {
nextindexskedar = 0;
}
}
} else if (chr->headnum == HEAD_DDSHOCK) {
s16 sounds[] = {
SFX_ARGH_MALE_0086,
SFX_ARGH_MALE_0088,
SFX_ARGH_MALE_008A,
SFX_ARGH_MALE_008C,
SFX_ARGH_MALE_008E,
SFX_ARGH_MALE_0090,
SFX_ARGH_MALE_0092,
SFX_ARGH_MALE_0094,
SFX_ARGH_MALE_0096,
SFX_ARGH_MALE_0098,
SFX_ARGH_MALE_009A,
SFX_ARGH_MALE_009C,
SFX_ARGH_MALE_009E,
SFX_ARGH_MALE_0087,
};
soundnum = sounds[nextindexshock];
nextindexshock++;
if (nextindexshock >= ARRAYCOUNT(sounds)) {
nextindexshock = 0;
}
allowoverride = true;
} else if (male) {
s16 sounds[] = {
SFX_ARGH_MALE_0086,
SFX_ARGH_MALE_0087,
SFX_ARGH_MALE_0088,
SFX_ARGH_MALE_0089,
SFX_ARGH_MALE_008A,
SFX_ARGH_MALE_008B,
SFX_ARGH_MALE_008C,
SFX_ARGH_MALE_008D,
SFX_ARGH_MALE_008E,
SFX_ARGH_MALE_008F,
SFX_ARGH_MALE_0090,
SFX_ARGH_MALE_0091,
SFX_ARGH_MALE_0092,
SFX_ARGH_MALE_0093,
SFX_ARGH_MALE_0094,
SFX_ARGH_MALE_0095,
SFX_ARGH_MALE_0096,
SFX_ARGH_MALE_0097,
SFX_ARGH_MALE_0098,
SFX_ARGH_MALE_0099,
SFX_ARGH_MALE_009A,
SFX_ARGH_MALE_009B,
SFX_ARGH_MALE_009C,
SFX_ARGH_MALE_009D,
SFX_ARGH_MALE_009E,
};
soundnum = sounds[nextindexmale];
nextindexmale++;
allowoverride = true;
if (nextindexmale >= ARRAYCOUNT(sounds)) {
nextindexmale = 0;
}
} else if (chr->headnum == HEAD_DARK_COMBAT
|| chr->headnum == HEAD_DARK_FROCK
|| chr->headnum == HEAD_DARKAQUA
|| chr->headnum == HEAD_DARK_SNOW) {
s16 sounds[] = {
SFX_ARGH_JO_02AA,
SFX_ARGH_JO_02AB,
SFX_ARGH_JO_02AC,
SFX_ARGH_JO_02AD,
SFX_ARGH_JO_02AE,
SFX_ARGH_JO_02AF,
SFX_ARGH_JO_02B0,
SFX_ARGH_JO_02B1,
SFX_ARGH_JO_02B2,
SFX_ARGH_JO_02B3,
};
soundnum = sounds[random() % 10];
allowoverride = true;
} else {
s16 sounds[] = {
SFX_ARGH_FEMALE_000D,
SFX_ARGH_FEMALE_000E,
SFX_ARGH_FEMALE_000F,
};
soundnum = sounds[nextindexfemale];
nextindexfemale++;
if (nextindexfemale >= ARRAYCOUNT(sounds)) {
nextindexfemale = 0;
}
allowoverride = true;
}
if (allowoverride) {
if (choketype == CHOKETYPE_GURGLE) {
s32 sounds[] = {
SFX_M1_CHOKING,
SFX_GURGLE_05B1,
SFX_GURGLE_05B2,
};
if ((random() % 8) == 0) {
soundnum = sounds[random() % 3];
}
chr->soundgap = 10;
chr->soundtimer = 0;
} else if (choketype == CHOKETYPE_COUGH) {
if (male) {
if ((random() % 2) == 0) {
soundnum = SFX_COUGH_04AF;
} else {
soundnum = SFX_COUGH_04B0;
}
} else {
s32 index = random() % 4;
s32 sounds[] = {
SFX_COUGH_05AB,
SFX_COUGH_05AC,
SFX_COUGH_05AD,
SFX_COUGH_05AE,
};
soundnum = sounds[index];
}
chr->soundgap = 10;
chr->soundtimer = 0;
}
}
if (soundnum >= 0) {
if (chr->prop->type == PROPTYPE_PLAYER) {
if (g_Vars.players[playernum]->chokehandle == NULL) {
sndStart(var80095200, soundnum, &g_Vars.players[playernum]->chokehandle, -1, -1, -1, -1, -1);
}
} else {
func0f0926bc(chr->prop, 9, 0);
if (!func0f092610(chr->prop, 13)) {
propsnd0f0939f8(NULL, chr->prop, soundnum, -1,
-1, 0, 0, 13, NULL, -1, NULL, -1, -1, -1, -1);
}
}
}
}
f32 chrGetShield(struct chrdata *chr)
{
return chr->cshield;
}
void chrSetShield(struct chrdata *chr, f32 amount)
{
if (amount < 0) {
amount = 0;
}
chr->cshield = amount;
if ((chr->hidden & CHRHFLAG_INFINITESHIELD) && chr->cshield < 1) {
chr->cshield = 1;
}
if (chr->prop->type == PROPTYPE_PLAYER) {
s32 playernum = playermgrGetPlayerNumByProp(chr->prop);
if (playernum >= 0) {
s32 prevplayernum = g_Vars.currentplayernum;
setCurrentPlayerNum(playernum);
playerDisplayHealth();
g_Vars.currentplayerstats->armourcount += amount * 0.125f;
setCurrentPlayerNum(prevplayernum);
}
}
}
bool func0f034080(struct chrdata *chr, struct modelnode *node, struct prop *prop, struct model *model, s32 side, s16 *arg5)
{
if (chrGetShield(chr) > 0) {
if (node && (node->type & 0xff) == MODELNODETYPE_BBOX) {
shieldhitCreate(chr->prop, chrGetShield(chr), prop, node, model, side, arg5);
}
return true;
}
return false;
}
/**
* Damage the chr, bypassing any shield.
*
* Used for knife poison, nbomb damage, Investigation radioactivity and Escape gas.
*/
void chrDamageByMisc(struct chrdata *chr, f32 damage, struct coord *vector, struct gset *gset, struct prop *prop)
{
chrDamage(chr, damage, vector, gset, prop, HITPART_GENERAL,
false, // damageshield
NULL, // prop2
NULL, // node
NULL, // model
-1, // side
NULL, // arg11
false, // explosion
NULL); // explosionpos
}
void chrDamageByLaser(struct chrdata *chr, f32 damage, struct coord *vector, struct gset *gset, struct prop *prop)
{
chrDamage(chr, damage, vector, gset, prop, HITPART_GENERAL,
true, // damageshield
chr->prop, // prop2
NULL, // node
NULL, // model
-1, // side
NULL, // arg11
false, // explosion
NULL); // explosionpos
}
void func0f0341dc(struct chrdata *chr, f32 damage, struct coord *vector, struct gset *gset, struct prop *prop, s32 hitpart, struct prop *prop2, struct modelnode *node, struct model *model, s32 side, s16 *arg10)
{
chrDamage(chr, damage, vector, gset, prop, hitpart,
true, // damageshield
prop2, // prop2
node, // node
model, // model
side, // side
arg10, // arg11
false, // explosion
NULL); // explosionpos
}
/**
* Used for punching, but also used by AI commands to make chrs take damage.
*/
void chrDamageByImpact(struct chrdata *chr, f32 damage, struct coord *vector, struct gset *gset, struct prop *prop, s32 hitpart)
{
struct modelnode *node = NULL;
struct model *model = NULL;
s32 side = 0;
if (chrGetShield(chr) >= 0 && chr->model) {
chrCalculateShieldHit(chr, &chr->prop->pos, vector, &node, &hitpart, &model, &side);
}
chrDamage(chr, damage, vector, gset, prop, hitpart,
true, // damageshield
chr->prop, // prop2
node, // node
model, // model
side, // side
NULL, // arg11
false, // explosion
NULL); // explosionpos
}
void chrDamageByExplosion(struct chrdata *chr, f32 damage, struct coord *vector, struct prop *prop, struct coord *explosionpos)
{
chrDamage(chr, damage, vector, NULL, prop, HITPART_GENERAL,
true, // damageshield
chr->prop, // prop2
NULL, // node
NULL, // model
-1, // side
NULL, // arg11
true, // explosion
explosionpos);
}
void playerUpdateDamageStats(struct prop *attacker, struct prop *victim, f32 damage)
{
s32 playernum;
if (attacker && attacker->type == PROPTYPE_PLAYER) {
playernum = playermgrGetPlayerNumByProp(attacker);
if (playernum >= 0) {
g_Vars.playerstats[playernum].damtransmitted += damage;
}
}
if (victim && victim->type == PROPTYPE_PLAYER) {
playernum = playermgrGetPlayerNumByProp(victim);
if (playernum >= 0) {
g_Vars.playerstats[playernum].damreceived += damage;
}
}
}
/**
* Handle a chr being damaged.
*
* Calculates and damage based on many factors and applies it to the chr,
* killing them if needed.
*
* The chr can be a player, aibot or regular chr. The chr can also be of any
* race (human, Skedar, Dr Caroll, robot or eyespy).
*
* chr - the chr being damaged
* damage - the base amount of damage to deal, prior to scaling factors
* vector - position of the attacker?
* gset - gun settings struct
* aprop - the attacker's prop struct
* hitpart - "i've been hit" value, ie. the body part (see HITPART constants)
* damageshield - false if attack should bypass shield if any
* prop2 - ?
* node - if shielded, model node (of type bbox) which was hit
* model - if shielded, model of chr
* side - if shielded, side of the model node's bounding box which was hit (0-5)
* arg11 - ?
* explosion - true if damage is coming from an explosion
* explosionpos - position of said explosion
*/
void chrDamage(struct chrdata *chr, f32 damage, struct coord *vector, struct gset *gset,
struct prop *aprop, s32 hitpart, bool damageshield, struct prop *prop2,
struct modelnode *node, struct model *model, s32 side, s16 *arg11,
bool explosion, struct coord *explosionpos)
{
bool onehitko = false;
s32 race = CHRRACE(chr);
f32 shield;
bool makedizzy;
bool isclose;
struct prop *vprop = chr->prop;
f32 headshotdamagescale = 1;
bool usedshield = false;
bool showshield = false;
bool showdamage = false;
struct gset gset2 = {0};
f32 explosionforce = damage;
f32 healthscale = 1;
f32 armourscale = 1;
bool isfar = true;
bool forceapplydamage = false;
struct weaponfunc *func;
f32 amount;
bool canchoke = true;
s32 aplayernum = -1;
s32 choketype = CHOKETYPE_NONE;
if (hitpart == HITPART_HEAD) {
choketype = CHOKETYPE_GURGLE;
}
if (gset) {
if (gset->weaponnum == WEAPON_COMBATKNIFE) {
if (gset->weaponfunc == FUNC_2) {
canchoke = false;
}
if (gset->weaponfunc == FUNC_POISON) {
choketype = CHOKETYPE_COUGH;
}
} else if (gset->weaponnum == WEAPON_TRANQUILIZER) {
if (gset->weaponfunc == FUNC_SECONDARY) {
choketype = CHOKETYPE_GURGLE;
}
}
}
// Don't damage if in CI training outside of training session
if (chr->prop == g_Vars.currentplayer->prop
&& g_Vars.currentplayer->training == false
&& mainGetStageNum() == STAGE_CITRAINING) {
return;
}
// Don't damage if attacker was anti and chr is non-interactable by anti
if (g_Vars.antiplayernum >= 0
&& aprop
&& aprop == g_Vars.anti->prop
&& (chr->hidden & CHRHFLAG_ANTINONINTERACTABLE)) {
return;
}
// Don't damage if coop and friendly fire is off (human buddy)
if (g_Vars.coopplayernum >= 0
&& g_Vars.coopfriendlyfire == false
&& aprop
&& aprop != vprop
&& aprop->type == PROPTYPE_PLAYER
&& vprop->type == PROPTYPE_PLAYER) {
return;
}
// Don't damage if coop and friendly fire is off (AI buddy)
if (g_MissionConfig.iscoop
&& g_Vars.coopfriendlyfire == false
&& aprop
&& aprop != vprop
&& (aprop->type & (PROPTYPE_PLAYER | PROPTYPE_CHR))
&& chr->team == TEAM_ALLY
&& aprop->chr->team == TEAM_ALLY) {
return;
}
// Don't allow coop AI to kill or destroy anything
// which anti wouldn't be able to
if (g_MissionConfig.iscoop
&& aprop
&& aprop != vprop
&& aprop->type == PROPTYPE_CHR
&& aprop->chr->team == TEAM_ALLY
&& (chr->hidden & CHRHFLAG_ANTINONINTERACTABLE)) {
return;
}
if (gset == NULL) {
gset = &gset2;
}
func = gsetGetWeaponFunction(gset);
isclose = func && (func->type & 0xff) == INVENTORYFUNCTYPE_CLOSE;
makedizzy = race != RACE_DRCAROLL && gsetHasFunctionFlags(gset, FUNCFLAG_MAKEDIZZY);
if (chr->prop == g_Vars.currentplayer->prop && g_Vars.currentplayer->invincible) {
return;
}
if (isclose) {
isfar = false;
}
// Set a flag on the victim that makes them switch to their "shot" AI list
chr->chrflags |= CHRCFLAG_TRIGGERSHOTLIST;
// Set a flag on the attacker so their AI scripting can tell that they've
// hit their target
if (aprop
&& aprop->type == PROPTYPE_CHR
&& chrGetTargetProp(aprop->chr) == chr->prop) {
aprop->chr->chrflags |= CHRCFLAG_INJUREDTARGET;
}
// Disarm only hurts the victim in solo missions and if the victim is an NPC
if (gsetHasFunctionFlags(gset, FUNCFLAG_DISARM)
&& gset->weaponnum == WEAPON_UNARMED
&& (vprop->type == PROPTYPE_PLAYER || g_Vars.normmplayerisrunning)) {
damage = 0;
}
// Apply damage scaling based on difficulty settings
if (g_Vars.mplayerisrunning == false) {
// Solo
if (explosion) {
if (vprop->type == PROPTYPE_PLAYER) {
damage *= g_ExplosionDamageTxScale;
}
} else if (aprop && aprop->type == PROPTYPE_PLAYER) {
// Player is attacking
damage *= g_PlayerDamageTxScale;
headshotdamagescale = 25;
} else if (aprop && aprop->type == PROPTYPE_CHR && vprop->type == PROPTYPE_PLAYER) {
// Chr is attacking player
damage *= g_PlayerDamageRxScale * pdmodeGetEnemyDamage();
}
if (vprop->type != PROPTYPE_PLAYER) {
damage /= pdmodeGetEnemyHealth();
}
if (vprop->type == PROPTYPE_PLAYER) {
healthscale = g_Vars.players[playermgrGetPlayerNumByProp(vprop)]->healthscale;
armourscale = g_Vars.players[playermgrGetPlayerNumByProp(vprop)]->armourscale;
}
} else if (g_Vars.coopplayernum >= 0) {
// Co-op
if (explosion) {
if (vprop->type == PROPTYPE_PLAYER) {
damage *= g_ExplosionDamageTxScale;
}
} else if (aprop && aprop->type == PROPTYPE_PLAYER && vprop->type != PROPTYPE_PLAYER) {
damage *= g_PlayerDamageTxScale;
headshotdamagescale = 25;
} else if (aprop && aprop->type == PROPTYPE_CHR && vprop->type == PROPTYPE_PLAYER) {
damage *= g_PlayerDamageRxScale * pdmodeGetEnemyDamage();
}
if (vprop->type != PROPTYPE_PLAYER) {
damage /= pdmodeGetEnemyHealth();
}
if (vprop->type == PROPTYPE_PLAYER) {
healthscale = g_Vars.players[playermgrGetPlayerNumByProp(vprop)]->healthscale;
armourscale = g_Vars.players[playermgrGetPlayerNumByProp(vprop)]->armourscale;
}
} else if (g_Vars.antiplayernum >= 0) {
// Anti
if (explosion) {
if (vprop == g_Vars.bond->prop) {
damage *= g_ExplosionDamageTxScale;
}
} else if (aprop && aprop == g_Vars.bond->prop) {
damage *= g_PlayerDamageTxScale;
headshotdamagescale = 25;
} else if (aprop && aprop != g_Vars.bond->prop && vprop == g_Vars.bond->prop) {
damage *= g_PlayerDamageRxScale * pdmodeGetEnemyDamage();
}
if (vprop != g_Vars.bond->prop) {
damage /= pdmodeGetEnemyHealth();
}
if (vprop == g_Vars.bond->prop) {
healthscale = g_Vars.players[playermgrGetPlayerNumByProp(vprop)]->healthscale;
armourscale = g_Vars.players[playermgrGetPlayerNumByProp(vprop)]->armourscale;
}
// Anti shooting other enemies is lethal
if (aprop && aprop == g_Vars.anti->prop && vprop != g_Vars.bond->prop) {
damage *= 100;
}
} else {
// Normal multiplayer
if (vprop->type == PROPTYPE_PLAYER) {
s32 prevplayernum = g_Vars.currentplayernum;
setCurrentPlayerNum(playermgrGetPlayerNumByProp(vprop));
damage *= g_Vars.currentplayerstats->damagescale;
setCurrentPlayerNum(prevplayernum);
}
}
// Apply rumble
if (vprop->type == PROPTYPE_PLAYER) {
s32 prevplayernum = g_Vars.currentplayernum;
s32 contpad1;
s32 contpad2;
setCurrentPlayerNum(playermgrGetPlayerNumByProp(vprop));
joyGetContpadNumsForPlayer(g_Vars.currentplayernum, &contpad1, &contpad2);
if (contpad1 >= 0) {
pakRumble(contpad1, 0.25f, -1, -1);
}
if (contpad2 >= 0) {
pakRumble(contpad2, 0.25f, -1, -1);
}
setCurrentPlayerNum(prevplayernum);
}
if (aprop) {
// Find the attacker's player number if possible
// (includes MP aibots, not applicable for solo chrs)
if (g_Vars.mplayerisrunning) {
if (aprop->type & (PROPTYPE_PLAYER | PROPTYPE_CHR)) {
aplayernum = mpPlayerGetIndex(aprop->chr);
}
} else {
if (aprop->type == PROPTYPE_PLAYER) {
aplayernum = playermgrGetPlayerNumByProp(aprop);
}
}
// If using the shotgun, scale the damage based on distance
if (aprop->type == PROPTYPE_CHR && gset->weaponnum == WEAPON_SHOTGUN) {
f32 xdiff = aprop->pos.x - vprop->pos.x;
f32 ydiff = aprop->pos.y - vprop->pos.y;
f32 zdiff = aprop->pos.z - vprop->pos.z;
f32 sqdist = xdiff * xdiff + ydiff * ydiff + zdiff * zdiff;
if (sqdist < 200 * 200) {
damage *= 4.0f + (s32)(random() % 3); // 4, 5 or 6
} else if (sqdist < 400 * 400) {
damage *= 3.0f + (s32)(random() % 2); // 3 or 4
} else if (sqdist < 800 * 800) {
damage *= 2.0f + (s32)(random() % 2); // 2 or 3
} else if (sqdist < 1600 * 1600) {
damage *= 1.0f + (s32)(random() % 2); // 1 or 2
}
}
}
// damageshield is an argument to this function,
// but is forced on if using the Farsight.
if (gset && gset->weaponnum == WEAPON_FARSIGHT) {
damageshield = true;
damage *= 10;
}
// Handle shield damage
if (damageshield) {
shield = chrGetShield(chr);
if (chr->aibot && chr->aibot->config->type == BOTTYPE_TURTLE) {
armourscale = 4;
}
if (shield > 0) {
if (g_Vars.normmplayerisrunning) {
damage /= mpHandicapToDamageScale(g_PlayerConfigsArray[g_Vars.currentplayerstats->mpindex].handicap);
}
chr->chrflags |= CHRCFLAG_SHIELDDAMAGED;
if (prop2 && node && chr->model) {
func0f034080(chr, node, prop2, model, side, arg11);
} else {
shieldhitCreate(chr->prop, chrGetShield(chr), NULL, NULL, NULL, 0, 0);
}
if (g_Vars.normmplayerisrunning && (g_MpSetup.options & MPOPTION_ONEHITKILLS)) {
damage = 0;
chrSetShield(chr, 0);
} else if (shield >= damage / armourscale) {
// Has enough shield to sustain the damage
shield -= damage / armourscale;
damage = 0;
chrSetShield(chr, shield);
} else {
// Shield is now gone
damage = 0;
chrSetShield(chr, 0);
}
showshield = true;
usedshield = true;
}
}
// Handle incrementing player shot count
if (aprop && aprop->type == PROPTYPE_PLAYER && !explosion) {
bool alreadydead = false;
s32 prevplayernum = g_Vars.currentplayernum;
setCurrentPlayerNum(playermgrGetPlayerNumByProp(aprop));
// ACT_DIE is not checked here, so it would appear that shooting
// a chr as they're dying will increment the shots hit count
if (chr && chr->actiontype == ACT_DEAD) {
alreadydead = true;
}
if (vprop->type == PROPTYPE_PLAYER && g_Vars.players[playermgrGetPlayerNumByProp(vprop)]->isdead) {
alreadydead = true;
}
if (!alreadydead && hitpart) {
switch (hitpart) {
case HITPART_HEAD:
mpstatsIncrementPlayerShotCount2(gset, SHOTREGION_HEAD);
break;
case HITPART_GUN:
mpstatsIncrementPlayerShotCount2(gset, SHOTREGION_GUN);
break;
case HITPART_HAT:
mpstatsIncrementPlayerShotCount2(gset, SHOTREGION_HAT);
break;
case HITPART_PELVIS:
case HITPART_TORSO:
mpstatsIncrementPlayerShotCount2(gset, SHOTREGION_BODY);
break;
default:
mpstatsIncrementPlayerShotCount2(gset, SHOTREGION_LIMB);
break;
}
}
setCurrentPlayerNum(prevplayernum);
}
// If the chr is invincible, make them flinch then we're done
if (chr->chrflags & CHRCFLAG_INVINCIBLE) {
chrFlinchBody(chr);
return;
}
// If chr is dying or already dead, consider making their head flinch
// then we're done
if (chr->actiontype == ACT_DIE || chr->actiontype == ACT_DEAD) {
if (hitpart == HITPART_HEAD && chr->actiontype == ACT_DIE && race != RACE_SKEDAR && isfar) {
struct coord pos;
pos.x = vprop->pos.x - vector->x;
pos.y = vprop->pos.y - vector->y;
pos.z = vprop->pos.z - vector->z;
chrFlinchHead(chr, chrGetAngleToPos(chr, &pos));
}
return;
}
// At this point the chr is known to be alive before they were shot
{
struct prop *weapon;
struct coord sp9c;
f32 angle;
sp9c.x = vprop->pos.x - vector->x;
sp9c.y = vprop->pos.y - vector->y;
sp9c.z = vprop->pos.z - vector->z;
angle = chrGetAngleToPos(chr, &sp9c);
// Knife in the back to an unalerted chr is lethal
if (gset->weaponnum == WEAPON_COMBATKNIFE
&& gset->weaponfunc == FUNC_PRIMARY
&& angle > 2.0940616130829f
&& angle < 4.1881237030029f
&& (chr->alertness < 100 || chr->lastseetarget60 == 0)) {
damage *= 1000;
}
// Punching and pistol whipping is less effective from the front
if (gsetHasFunctionFlags(gset, FUNCFLAG_BLUNTIMPACT)) {
if (angle < 1.0470308065414f || angle > 5.2351541519165f) {
damage *= 0.4f;
} else if (angle < 2.0940616130829f || angle > 4.1881237030029f) {
damage *= 0.7f;
} else if (chr->alertness < 100) {
onehitko = true;
}
if (chrGetHeldProp(chr, HAND_RIGHT) == NULL
&& chrGetHeldProp(chr, HAND_LEFT) == NULL
&& (chr->gunprop == NULL || chr->actiontype == ACT_SURRENDER || chr->actiontype == ACT_SURPRISED)) {
// Chr is unarmed and has no hope of getting their gun
onehitko = true;
}
forceapplydamage = true;
}
if (hitpart == HITPART_GENERAL) {
// Halve the damage because it's doubled for torso below
hitpart = HITPART_TORSO;
damage *= 0.5f;
} else if (hitpart == HITPART_GENERALHALF) {
// Likewise, quarter it here so it becomes half below
hitpart = HITPART_TORSO;
damage *= 0.25f;
}
// Hits to a Skedar's tail are 10x more lethal
if (race == RACE_SKEDAR && hitpart == HITPART_TAIL) {
damage *= 10;
}
// Apply damage multipliers based on which body parts were hit,
// and flinch head if shot in the head
if (hitpart == HITPART_HEAD) {
if (race == RACE_SKEDAR) {
damage += damage;
chrFlinchHead(chr, angle);
} else {
damage *= 4;
if (isfar && !usedshield) {
chrFlinchHead(chr, angle);
damage *= headshotdamagescale;
if (gset->weaponnum == WEAPON_COMBATKNIFE && gset->weaponfunc != FUNC_POISON) {
damage += damage;
}
}
}
} else if (hitpart == HITPART_TORSO) {
// Double damage for torso hits
damage += damage;
} else if (hitpart == HITPART_GUN) {
// No damage for gun hits
damage = 0;
makedizzy = false;
} else if (hitpart == HITPART_HAT) {
// No damage for hat hits
damage = 0;
makedizzy = false;
}
// Handle situations where the player is the one being shot, then return
if (vprop->type == PROPTYPE_PLAYER) {
s32 prevplayernum = g_Vars.currentplayernum;
setCurrentPlayerNum(playermgrGetPlayerNumByProp(vprop));
if (g_Vars.normmplayerisrunning) {
damage /= mpHandicapToDamageScale(g_PlayerConfigsArray[g_Vars.currentplayerstats->mpindex].handicap);
}
if (g_Vars.currentplayer->isdead == false && !g_PlayerInvincible) {
f32 boostscale;
// Handle player losing gun
if (gsetHasFunctionFlags(gset, FUNCFLAG_DISARM)) {
bgunDisarm(aprop);
}
// Handle player dizziness
if (makedizzy && g_Vars.currentplayer->invincible == false) {
f32 blurscale = 1;
struct chrdata *achr = NULL;
if (aprop) {
achr = aprop->chr;
if (achr && achr->bodynum == BODY_MINISKEDAR) {
blurscale = 4;
}
}
if (!achr
|| !achr->aibot
|| !gsetHasFunctionFlags(gset, FUNCFLAG_00400000)
|| chr->blurdrugamount < TICKS(4500)) {
chr->blurdrugamount += gsetGetBlurAmount(gset) * blurscale;
}
chr->blurnumtimesdied = 0;
}
// Handle player damage
if (g_Vars.currentplayer->invincible == false && damage > 0) {
f32 statsamount = amount = damage * 0.125f;
if (statsamount > g_Vars.currentplayer->bondhealth) {
statsamount = g_Vars.currentplayer->bondhealth;
}
if (g_Vars.normmplayerisrunning && (g_MpSetup.options & MPOPTION_ONEHITKILLS)) {
statsamount = g_Vars.currentplayer->bondhealth;
}
playerUpdateDamageStats(aprop, vprop, statsamount);
playerDisplayHealth();
if (g_Vars.normmplayerisrunning && (g_MpSetup.options & MPOPTION_ONEHITKILLS)) {
g_Vars.currentplayer->bondhealth = 0;
}
g_Vars.currentplayer->bondhealth -= amount / healthscale;
chr->lastattacker = (aprop ? aprop->chr : NULL);
showdamage = true;
if (g_Vars.currentplayer->training == false
&& g_Vars.currentplayer->bondhealth <= 0) {
playerDieByShooter(aplayernum, false);
chr->blurnumtimesdied++;
}
if (!lvIsPaused() && canchoke) {
chrChoke(chr, choketype);
}
chrFlinchBody(chr);
}
// Handle player boost
if (isclose && gset->weaponnum == WEAPON_REAPER) {
boostscale = 0.1f;
} else if (g_Vars.normmplayerisrunning) {
boostscale = 0.75f;
} else {
boostscale = 1;
}
g_Vars.currentplayer->bondshotspeed.x += vector->x * boostscale;
g_Vars.currentplayer->bondshotspeed.z += vector->z * boostscale;
if (showdamage) {
playerDisplayDamage();
}
if (showshield) {
playerDisplayShield();
}
if (g_Vars.normmplayerisrunning && aprop && aprop->type == PROPTYPE_PLAYER) {
playerCheckIfShotInBack(prevplayernum, vector->x, vector->z);
}
}
setCurrentPlayerNum(prevplayernum);
return;
}
// This check is pointless - a similar check and return exists earlier
if (chr->actiontype == ACT_DIE || chr->actiontype == ACT_DEAD) {
return;
}
// At this point we know we're dealing with a NPC being shot, and the
// NPC was alive prior to being shot.
// Handle aibot/chr losing gun
if (gsetHasFunctionFlags(gset, FUNCFLAG_DISARM)
&& ((chr->flags & CHRFLAG0_CANLOSEGUN) || chr->aibot)) {
if (chr->aibot) {
botDisarm(chr, aprop);
} else {
weapon = chrGetHeldProp(chr, HAND_RIGHT);
if (weapon) {
chr->gunprop = weapon;
objSetDropped(weapon, DROPTYPE_DEFAULT);
chr->hidden |= CHRHFLAG_00000001;
}
weapon = chrGetHeldProp(chr, HAND_LEFT);
if (weapon) {
chr->gunprop = weapon;
objSetDropped(weapon, DROPTYPE_DEFAULT);
chr->hidden |= CHRHFLAG_00000001;
}
}
}
// Handle chr damage
if (chr->damage < chr->maxdamage
|| (!g_Vars.normmplayerisrunning && chr->actiontype != ACT_PREARGH)) {
f32 sp80 = 0;
chr->numarghs++;
// Handle chr dizziness and psychosis
if (makedizzy && race != RACE_DRCAROLL && race != RACE_ROBOT) {
if (gsetHasFunctionFlags(gset, FUNCFLAG_PSYCHOSIS)) {
chr->hidden |= CHRHFLAG_PSYCHOSISED;
} else {
chr->blurdrugamount += gsetGetBlurAmount(gset);
chr->blurnumtimesdied = 0;
if (!chr->aibot && chr->blurdrugamount >= TICKS(5000)) {
onehitko = true;
}
}
}
// Handle chr boost
if (chr->aibot) {
f32 boostscale;
if (isclose && gset->weaponnum == WEAPON_REAPER) {
boostscale = 0.1f;
} else {
boostscale = 0.75f;
}
chr->aibot->shotspeed.x += vector->x * boostscale;
chr->aibot->shotspeed.z += vector->z * boostscale;
}
if (gset->weaponnum == WEAPON_UNARMED) {
sp80 = 2;
}
if (gset->weaponnum == WEAPON_TRANQUILIZER || gset->weaponnum == WEAPON_PSYCHOSISGUN) {
forceapplydamage = true;
}
// Handle one-hit knockouts
if (onehitko && chr->aibot == NULL && race == RACE_HUMAN) {
chrKnockOut(chr, angle, hitpart, gset);
func0f0926bc(chr->prop, 9, 0);
if (canchoke) {
chrChoke(chr, choketype);
}
if (gset->weaponnum == WEAPON_UNARMED && chr->actiontype != ACT_DRUGGEDKO) {
return;
}
return;
}
// Handle applying damage to NPCs
// Don't enter this branch if there is no damage to give,
// or we are making a chr dizzy in solo mode (unless force is set)
if (damage > 0 && (g_Vars.normmplayerisrunning || !makedizzy || forceapplydamage)) {
f32 amount = damage;
if (chr->damage + damage > chr->maxdamage) {
amount = chr->maxdamage - chr->damage;
}
amount *= 0.125f;
playerUpdateDamageStats(aprop, vprop, amount);
chr->damage += damage;
chr->lastattacker = (aprop ? aprop->chr : NULL);
chr->chrflags |= CHRCFLAG_JUST_INJURED;
if (chr->aibot) {
if (g_Vars.normmplayerisrunning && (g_MpSetup.options & MPOPTION_ONEHITKILLS)) {
chr->damage = chr->maxdamage;
}
if (canchoke) {
chrChoke(chr, choketype);
}
chrFlinchBody(chr);
if (chr->damage >= chr->maxdamage) {
chrDie(chr, aplayernum);
}
} else if (explosion) {
// Chrs die instantly from explosion damage provided they
// don't have any armour (the chr has armour if their
// chr->damage is negative). Note that damage has already
// been applied to the chr above, so a perfectly healthy chr
// with no armour will already have a damage value > 0 at
// this point.
if (chr->damage > 0) {
chr->damage = chr->maxdamage;
if (race == RACE_DRCAROLL || race == RACE_EYESPY || race == RACE_ROBOT) {
chrBeginDeath(chr, vector, angle, hitpart, gset, false, aplayernum);
} else {
chrYeetFromPos(chr, explosionpos, explosionforce);
}
if (canchoke) {
chrChoke(chr, choketype);
}
if (g_Vars.mplayerisrunning) {
mpstatsRecordDeath(aplayernum, mpPlayerGetIndex(chr));
} else if (aprop && aprop->type == PROPTYPE_PLAYER) {
s32 prevplayernum = g_Vars.currentplayernum;
setCurrentPlayerNum(playermgrGetPlayerNumByProp(aprop));
mpstatsRecordPlayerKill();
setCurrentPlayerNum(prevplayernum);
}
if (chr->chrflags & CHRCFLAG_KILLCOUNTABLE) {
mpstatsIncrementTotalKillCount();
}
if (chr->aibot == NULL) {
chrDropConcealedItems(chr);
}
if (chr->aibot == NULL) {
weapon = chr->weapons_held[HAND_RIGHT];
if (weapon && (weapon->obj->flags & OBJFLAG_AIUNDROPPABLE) == 0) {
objSetDropped(weapon, DROPTYPE_DEFAULT);
chr->hidden |= CHRHFLAG_00000001;
}
weapon = chr->weapons_held[HAND_LEFT];
if (weapon && (weapon->obj->flags & OBJFLAG_AIUNDROPPABLE) == 0) {
objSetDropped(weapon, DROPTYPE_DEFAULT);
chr->hidden |= CHRHFLAG_00000001;
}
}
}
} else {
// Non-explosion damage to solo mode chr
if (chr->actiontype != ACT_DRUGGEDKO && canchoke) {
chrChoke(chr, choketype);
}
if (makedizzy && chr->damage >= chr->maxdamage) {
chr->damage = chr->maxdamage - 0.1f;
chrKnockOut(chr, angle, hitpart, gset);
}
// If chr has armour or the weapon doesn't stun
if (chr->damage < 0 ||
(gsetHasFunctionFlags(gset, FUNCFLAG_NOSTUN) && chr->damage < chr->maxdamage)) {
f32 endframe = -1;
if (!chrIsAnimPreventingArgh(chr, &endframe)) {
chrFlinchBody(chr);
}
} else if (hitpart != HITPART_HAT) {
// Cancel current animation and prepare for argh
f32 endframe = -1;
if (chrIsAnimPreventingArgh(chr, &endframe)) {
if (endframe >= 0) {
modelSetAnimEndFrame(chr->model, endframe);
}
chr->actiontype = ACT_PREARGH;
chr->act_preargh.dir = *vector;
chr->act_preargh.relshotdir = angle;
chr->act_preargh.hitpart = hitpart;
chr->act_preargh.aplayernum = aplayernum;
chr->act_preargh.gset.weaponnum = gset->weaponnum;
chr->act_preargh.gset.unk0639 = gset->unk0639;
chr->act_preargh.gset.unk063a = gset->unk063a;
chr->act_preargh.gset.weaponfunc = gset->weaponfunc;
chr->sleep = 0;
} else {
chrReactToDamage(chr, vector, angle, hitpart, gset, aplayernum);
}
}
}
if (sp80 > 0) {
chr->timeextra = sp80 * 15;
chr->elapseextra = 0;
chr->extraspeed.x = prop2->pos.x - aprop->pos.x;
chr->extraspeed.y = prop2->pos.y - aprop->pos.y;
chr->extraspeed.z = prop2->pos.z - aprop->pos.z;
guNormalize(&chr->extraspeed.x, &chr->extraspeed.y, &chr->extraspeed.z);
chr->extraspeed.x *= sp80;
chr->extraspeed.y *= sp80;
chr->extraspeed.z *= sp80;
}
}
}
}
}
void chrDie(struct chrdata *chr, s32 aplayernum)
{
if (chr->actiontype != ACT_DIE) {
chrStopFiring(chr);
chrUncloak(chr, true);
chr->actiontype = ACT_DIE;
chr->act_die.notifychrindex = 0;
chr->sleep = 0;
chr->blurnumtimesdied++;
chr->act_die.thudframe1 = -1;
chr->act_die.thudframe2 = -1;
chr->act_die.timeextra = 0;
chr->ailist = &ailist_0028;
chr->aioffset = chr->ailist;
mpstatsRecordDeath(aplayernum, mpPlayerGetIndex(chr));
botinvDropAll(chr, chr->aibot->weaponnum);
chr->aibot->hasbriefcase = false;
chr->aibot->hascase = false;
chr->aibot->unk04c_04 = false;
chr->aibot->unk04c_03 = false;
chr->aibot->hasuplink = false;
}
}
bool func0f03645c(struct chrdata *chr, struct coord *arg1, s16 *arg2, struct coord *arg3, struct coord *arg4, s32 arg5)
{
bool result = false;
f32 ymax;
f32 ymin;
f32 radius;
s16 rooms[8];
struct prop *prop = chr->prop;
chrGetBbox(prop, &radius, &ymax, &ymin);
chrSetPerimEnabled(chr, false);
if (cdTestCylMove04(arg1, arg2, arg3, rooms, arg5, 1, ymax - prop->pos.y, ymin - prop->pos.y) != CDRESULT_COLLISION) {
if (cdTestCylMove01(arg3, rooms, arg4, arg5, 1, ymax - prop->pos.y, ymin - prop->pos.y) != CDRESULT_COLLISION) {
result = true;
}
}
chrSetPerimEnabled(chr, true);
return result;
}
bool func0f03654c(struct chrdata *chr, struct coord *pos, s16 *rooms, struct coord *pos2, s16 *rooms2, struct coord *vector, f32 arg6, u32 types)
{
struct coord tmp;
f32 a;
f32 b;
struct prop *prop;
bool result = false;
f32 ymax;
f32 ymin;
f32 radius;
struct coord sp5c;
struct coord sp50;
s16 sp40[8];
f32 mult;
prop = chr->prop;
chrSetPerimEnabled(chr, false);
chrGetBbox(prop, &radius, &ymax, &ymin);
if ((rooms2 && cdTestCylMove02(pos, rooms, pos2, rooms2, types, true, ymax - prop->pos.y, ymin - prop->pos.y))
|| (rooms2 == NULL && cdTestCylMove01(pos, rooms, pos2, types, 1, ymax - prop->pos.y, ymin - prop->pos.y))) {
if (vector == NULL) {
vector = &tmp;
tmp.x = pos2->x - pos->x;
tmp.y = 0;
tmp.z = pos2->z - pos->z;
if (tmp.f[0] == 0 && tmp.f[2] == 0) {
// @bug: Needs to call chrSetPerimEnabled(chr, true)
// before returning
return true;
}
mult = 1.0f / sqrtf(tmp.f[0] * tmp.f[0] + tmp.f[2] * tmp.f[2]);
tmp.x *= mult;
tmp.z *= mult;
}
a = vector->x * arg6;
b = vector->z * arg6;
sp5c.x = pos->x + b;
sp5c.y = pos->y;
sp5c.z = pos->z - a;
sp50.x = pos2->x + b;
sp50.y = pos2->y;
sp50.z = pos2->z - a;
if (cdTestCylMove04(pos, rooms, &sp5c, sp40, types, 1, ymax - prop->pos.y, ymin - prop->pos.y)
&& cdTestCylMove01(&sp5c, sp40, &sp50, types, 1, ymax - prop->pos.y, ymin - prop->pos.y)) {
sp5c.x = pos->x - b;
sp5c.y = pos->y;
sp5c.z = pos->z + a;
sp50.x = pos2->x - b;
sp50.y = pos2->y;
sp50.z = pos2->z + a;
if (cdTestCylMove04(pos, rooms, &sp5c, sp40, types, 1, ymax - prop->pos.y, ymin - prop->pos.y)
&& cdTestCylMove01(&sp5c, sp40, &sp50, types, 1, ymax - prop->pos.y, ymin - prop->pos.y)) {
result = true;
}
}
}
chrSetPerimEnabled(chr, true);
return result;
}
bool propchrHasClearLineToPos(struct prop *prop, struct coord *dstpos, struct coord *vector)
{
return func0f03654c(prop->chr, &prop->pos, prop->rooms, dstpos, NULL, vector, prop->chr->radius * 1.2f, CDTYPE_ALL);
}
bool propchrHasClearLineInVector(struct prop *prop, struct coord *vector, f32 mult)
{
struct coord dstpos;
dstpos.x = vector->x * mult + prop->pos.x;
dstpos.y = prop->pos.y;
dstpos.z = vector->z * mult + prop->pos.z;
return propchrHasClearLineToPos(prop, &dstpos, vector);
}
void chrGetSideVectorToTarget(struct chrdata *chr, bool side, struct coord *vector)
{
struct prop *prop = chr->prop;
struct prop *target = chrGetTargetProp(chr);
vector->x = 0;
vector->y = 0;
vector->z = 1;
if (target) {
f32 x = target->pos.x - prop->pos.x;
f32 z = target->pos.z - prop->pos.z;
f32 distance = sqrtf(x * x + z * z);
if (distance > 0) {
x = x / distance;
z = z / distance;
if (side) {
vector->x = z;
vector->y = 0;
vector->z = -x;
} else {
vector->x = -z;
vector->y = 0;
vector->z = x;
}
}
}
}
bool chrCanRollInDirection(struct chrdata *chr, bool side, f32 distance)
{
struct prop *prop = chr->prop;
struct coord vector;
struct coord dstpos;
chrGetSideVectorToTarget(chr, side, &vector);
dstpos.x = vector.x * distance + prop->pos.x;
dstpos.y = prop->pos.y;
dstpos.z = vector.z * distance + prop->pos.z;
return propchrHasClearLineToPos(prop, &dstpos, &vector);
}
void chrGetSideVector(struct chrdata *chr, bool side, struct coord *vector)
{
f32 angle = chrGetInverseTheta(chr);
if (side) {
vector->x = cosf(angle);
vector->y = 0;
vector->z = -sinf(angle);
} else {
vector->x = -cosf(angle);
vector->y = 0;
vector->z = sinf(angle);
}
}
bool chrCanJumpInDirection(struct chrdata *chr, bool side, f32 distance)
{
struct prop *prop = chr->prop;
struct coord vector;
struct coord dstpos;
chrGetSideVector(chr, side, &vector);
dstpos.x = vector.x * distance + prop->pos.x;
dstpos.y = prop->pos.y;
dstpos.z = vector.z * distance + prop->pos.z;
return propchrHasClearLineToPos(prop, &dstpos, &vector);
}
bool chrIsRoomOffScreen(struct chrdata *chr, struct coord *waypos, s16 *wayrooms)
{
struct prop *prop = chr->prop;
s16 sp7c[20];
u32 stack;
s32 i;
s16 sp64[8];
bool offscreen = true;
s16 sp50[8];
if ((chr->hidden & CHRHFLAG_CLOAKED) == 0 || USINGDEVICE(DEVICE_IRSCANNER)) {
func0f065dfc(&prop->pos, prop->rooms, waypos, sp64, sp7c, 20);
if (g_Vars.mplayerisrunning) {
for (i = 0; sp7c[i] != -1; i++) {
if (g_MpRoomVisibility[sp7c[i]] & 0x0f) {
offscreen = false;
break;
}
}
} else {
for (i = 0; sp7c[i] != -1; i++) {
if (g_Rooms[sp7c[i]].flags & ROOMFLAG_ONSCREEN) {
offscreen = false;
break;
}
}
}
}
if (offscreen) {
for (i = 0; i < PLAYERCOUNT(); i++) {
portal00018148(waypos, &g_Vars.players[i]->prop->pos, wayrooms, sp50, 0, 0);
if (arrayIntersects(g_Vars.players[i]->prop->rooms, sp50)) {
offscreen = false;
break;
}
}
}
return offscreen;
}
void chrGoPosInitMagic(struct chrdata *chr, struct waydata *waydata, struct coord *padpos, struct coord *chrpos)
{
f32 xdiff1 = padpos->x - chr->prop->pos.x;
f32 zdiff1 = padpos->z - chr->prop->pos.z;
f32 angle = atan2f(xdiff1, zdiff1);
f32 xdiff2 = padpos->x - chrpos->x;
f32 zdiff2 = padpos->z - chrpos->z;
waydata->mode = WAYMODE_MAGIC;
waydata->magictotal = sqrtf(xdiff1 * xdiff1 + zdiff1 * zdiff1);
waydata->magicdone = waydata->magictotal - sqrtf(xdiff2 * xdiff2 + zdiff2 * zdiff2);
chrSetLookAngle(chr, angle);
}
void chrGoPosGetCurWaypointInfoWithFlags(struct chrdata *chr, struct coord *pos, s16 *rooms, u32 *flags)
{
struct waypoint *waypoint = chr->act_gopos.waypoints[chr->act_gopos.curindex];
struct pad *pad;
if (waypoint) {
pad = &g_Pads[waypoint->padnum];
*pos = pad->pos;
rooms[0] = pad->room;
rooms[1] = -1;
if (flags) {
*flags = pad->flags;
}
} else {
*pos = chr->act_gopos.endpos;
rooms[0] = chr->act_gopos.endrooms[0];
rooms[1] = -1;
if (flags) {
*flags = 0;
}
}
}
void chrGoPosGetCurWaypointInfo(struct chrdata *chr, struct coord *pos, s16 *rooms)
{
chrGoPosGetCurWaypointInfoWithFlags(chr, pos, rooms, NULL);
}
f32 func0f0370a8(struct chrdata *chr)
{
f32 result;
if (chr->aibot) {
result = botCalculateMaxSpeed(chr);
} else {
s16 animnum = modelGetAnimNum(chr->model);
result = func0f02dff0(animnum) * (chr->model->scale * 9.999999f);
}
return result;
}
s32 chrGoPosCalculateBaseTtl(struct chrdata *chr)
{
f32 xdiff;
f32 zdiff;
u32 stack;
struct coord pos;
s16 rooms[8];
f32 speed;
chrGoPosGetCurWaypointInfo(chr, &pos, rooms);
xdiff = pos.x - chr->prop->pos.x;
zdiff = pos.z - chr->prop->pos.z;
if (xdiff < 0) {
xdiff = -xdiff;
}
if (zdiff < 0) {
zdiff = -zdiff;
}
speed = func0f0370a8(chr);
if (chr->aibot == NULL) {
speed *= modelGetAbsAnimSpeed(chr->model);
}
if (speed < 0.001f) {
speed = 0.001f;
}
return (xdiff + zdiff) / speed;
}
void chrGoPosClearRestartTtl(struct chrdata *chr)
{
chr->act_gopos.restartttl = 0;
}
void chrGoPosConsiderRestart(struct chrdata *chr)
{
if (chr->act_gopos.waydata.mode != WAYMODE_MAGIC
&& chr->liftaction != LIFTACTION_WAITINGONLIFT
&& chr->liftaction != LIFTACTION_WAITINGFORLIFT) {
if (chr->act_gopos.restartttl == 0) {
#if PAL
s32 value = (chrGoPosCalculateBaseTtl(chr) * 100 + 15000) / 60;
#else
s32 value = chrGoPosCalculateBaseTtl(chr) * 2 + 300;
#endif
if (value > 0xffff) {
value = 0xffff;
}
chr->act_gopos.restartttl = value;
} else if (chr->act_gopos.restartttl <= (u16)g_Vars.lvupdate60) {
if (chr->aibot) {
botCheckFetch(chr);
} else {
chrGoToRoomPos(chr, &chr->act_gopos.endpos, chr->act_gopos.endrooms, chr->act_gopos.flags);
}
} else {
chr->act_gopos.restartttl -= (u16)g_Vars.lvupdate60;
}
}
}
void chrGoPosInitExpensive(struct chrdata *chr)
{
struct coord pos;
s16 rooms[8];
chrGoPosGetCurWaypointInfo(chr, &pos, rooms);
chr->act_gopos.waydata.mode = WAYMODE_INIT;
chr->act_gopos.waydata.iter = 0;
chr->act_gopos.waydata.gotaimpos = false;
chr->act_gopos.waydata.aimpos = pos;
chrGoPosClearRestartTtl(chr);
}
/**
* Advance the chr's current waypoint index to the next one in the route.
*
* The waypoints array allows 6 waypoints and it's important that they have a
* couple loaded in front of their current one. So if the index is moving too
* far into the array, new pathfinding will be done and the array and index will
* be reset.
*/
void chrGoPosAdvanceWaypoint(struct chrdata *chr)
{
if (chr->act_gopos.curindex < 3) {
chr->act_gopos.curindex++;
} else {
struct waypoint *from = chr->act_gopos.waypoints[chr->act_gopos.curindex];
u32 hash;
chr->act_gopos.curindex = 1;
hash = (g_Vars.lvframe60 >> 9) * 0x80 + chr->chrnum * 8;
waypointSetHashThing(hash, hash);
waypointFindRoute(from, chr->act_gopos.target, chr->act_gopos.waypoints, MAX_CHRWAYPOINTS);
waypointSetHashThing(0, 0);
}
chrGoPosInitExpensive(chr);
}
/**
* Determines which step index the chr will be at given their current index, the
* number of steps to take and in which direction (forward or back).
*
* Returns the step index and populates *forward with true or false depending on
* whether the chr will be traversing the path in the forward direction at that
* point.
*/
s32 chrPatrolCalculateStep(struct chrdata *chr, bool *forward, s32 numsteps)
{
s32 nextstep = chr->act_patrol.nextstep;
bool isforward = *forward;
if (numsteps < 0) {
isforward = !isforward;
numsteps = -numsteps;
}
while (numsteps > 0) {
numsteps--;
if (isforward) {
nextstep++;
if (chr->act_patrol.path->pads[nextstep] < 0) {
// Reached the end of the list
if (chr->act_patrol.path->flags & PATHFLAG_CIRCULAR) {
nextstep = 0;
} else {
isforward = false;
nextstep -= 2;
if (nextstep < 0) {
nextstep = 0;
}
}
}
} else {
nextstep--;
if (nextstep < 0) {
// Reached the start of the list
if (chr->act_patrol.path->flags & PATHFLAG_CIRCULAR) {
nextstep = chr->act_patrol.path->len - 1;
} else {
isforward = true;
nextstep = 1;
if (chr->act_patrol.path->len - 1 <= 0) {
nextstep = chr->act_patrol.path->len - 1;
}
}
}
}
}
*forward = isforward;
return nextstep;
}
/**
* Determines which pad number the chr will be at given their current index and
* the number of steps to take.
*/
s16 chrPatrolCalculatePadNum(struct chrdata *chr, s32 numsteps)
{
s32 *padnumptr;
bool forward = chr->act_patrol.forward;
s32 step = chrPatrolCalculateStep(chr, &forward, numsteps);
padnumptr = &chr->act_patrol.path->pads[step];
return *padnumptr;
}
void chrPatrolGetCurWaypointInfoWithFlags(struct chrdata *chr, struct coord *pos, s16 *rooms, u32 *flags)
{
s32 padnum = chrPatrolCalculatePadNum(chr, 0);
struct pad *pad;
pad = &g_Pads[padnum];
*pos = pad->pos;
rooms[0] = pad->room;
rooms[1] = -1;
if (flags) {
*flags = pad->flags;
}
}
void chrPatrolGetCurWaypointInfo(struct chrdata *chr, struct coord *pos, s16 *rooms)
{
chrPatrolGetCurWaypointInfoWithFlags(chr, pos, rooms, NULL);
}
void func0f037580(struct chrdata *chr)
{
s16 rooms[8];
chr->act_patrol.waydata.mode = WAYMODE_INIT;
chr->act_patrol.waydata.iter = 0;
chr->act_patrol.waydata.gotaimpos = false;
chrPatrolGetCurWaypointInfo(chr, &chr->act_patrol.waydata.aimpos, rooms);
}
void func0f0375b0(struct chrdata *chr)
{
s32 nextstep = chrPatrolCalculateStep(chr, &chr->act_patrol.forward, 1);
chr->act_patrol.nextstep = nextstep;
chr->patrolnextstep = nextstep;
func0f037580(chr);
}
void chrNavTickMagic(struct chrdata *chr, struct waydata *waydata, f32 speed, struct coord *arg3, s16 *rooms)
{
s16 sp118[8];
f32 ymax;
f32 ymin;
f32 radius;
f32 ground;
u16 floorcol;
u8 floortype;
s16 floorroom;
struct coord spf4;
struct prop *prop = chr->prop;
union modelrwdata *rwdata;
struct waypoint *waypoint;
struct coord spdc;
s16 spcc[8];
u32 stack[4];
struct coord sp5c;
s16 sp4c[8];
chr->invalidmove = 0;
chr->lastmoveok60 = g_Vars.lvframe60;
waydata->magicdone += speed * modelGetAbsAnimSpeed(chr->model) * g_Vars.lvupdate60freal;
if (waydata->magicdone >= waydata->magictotal) {
// Reached end of segment
chrSetPerimEnabled(chr, false);
roomsCopy(rooms, sp118);
chr0f021fa8(chr, arg3, sp118);
ground = cdFindGroundInfoAtCyl(arg3, chr->radius, sp118, &floorcol, &floortype, 0, &floorroom, NULL, NULL);
spf4.x = arg3->x;
spf4.y = prop->pos.y - chr->ground + ground;
spf4.z = arg3->z;
roomsCopy(rooms, sp118);
chr0f021fa8(chr, &spf4, sp118);
chrGetBbox(chr->prop, &radius, &ymax, &ymin);
if (cdTestVolume(&spf4, chr->radius, sp118, CDTYPE_ALL, CHECKVERTICAL_YES, ymax - prop->pos.y, ymin - prop->pos.y) != CDRESULT_COLLISION) {
// Reached end of segment with no collision
prop->pos = spf4;
chr->ground = ground;
chr->manground = ground;
chr->sumground = ground * (PAL ? 8.4175090789795f : 9.999998f);
chr->floorcol = floorcol;
chr->floortype = floortype;
chr->floorroom = floorroom;
propDeregisterRooms(prop);
roomsCopy(sp118, prop->rooms);
propRegisterRooms(prop);
modelSetRootPosition(chr->model, &prop->pos);
rwdata = modelGetNodeRwData(chr->model, chr->model->filedata->rootnode);
rwdata->chrinfo.ground = ground;
chr->chrflags |= CHRCFLAG_00000001;
if (chr->actiontype == ACT_PATROL) {
func0f0375b0(chr);
chrPatrolGetCurWaypointInfo(chr, &spdc, spcc);
chrGoPosInitMagic(chr, waydata, &spdc, &prop->pos);
} else if (chr->actiontype == ACT_GOPOS) {
if (chr->act_gopos.waypoints[chr->act_gopos.curindex] == NULL) {
// Reached the end of the route
if (chr->act_gopos.flags & GOPOSFLAG_FORPATHSTART) {
chrTryStartPatrol(chr);
} else {
if (chr->act_gopos.curindex >= 2) {
waypoint = chr->act_gopos.waypoints[chr->act_gopos.curindex - 2];
chrSetLookAngle(chr, atan2f(prop->pos.x - g_Pads[waypoint->padnum].pos.x, prop->pos.z - g_Pads[waypoint->padnum].pos.z));
}
if (CHRRACE(chr) == RACE_HUMAN || CHRRACE(chr) == RACE_SKEDAR) {
chrStop(chr);
}
}
} else {
// Advance to next segment, still using magic
chrGoPosAdvanceWaypoint(chr);
chrGoPosGetCurWaypointInfo(chr, &sp5c, sp4c);
chrGoPosInitMagic(chr, waydata, &sp5c, &prop->pos);
}
}
} else {
// Collision
waydata->magicdone = waydata->magictotal;
if (chr->actiontype == ACT_PATROL) {
chr->act_patrol.waydata.lastvisible60 = g_Vars.lvframe60;
func0f037580(chr);
} else {
chr->act_gopos.waydata.lastvisible60 = g_Vars.lvframe60;
chrGoPosInitExpensive(chr);
}
}
chrSetPerimEnabled(chr, true);
}
}
/**
* Calculate the chr's position when using the magic method of navigating.
*
* The magic method is used when the chr is off-screen. It measures the distance
* between two consecutive pads in the route and simply increments a distance
* value along that segment on each tick, ignoring collisions. Meanwhile, the
* chr's prop->pos is left as the original location where this segment started.
*
* The calculation is finding the fraction of the distance travelled in this
* segment, then finding the position between the starting pos and the pad's
* pos.
*
* The pos variable is used for both loading the next pad's position and for
* returning the new position, which means there's less stack usage.
*/
void chrCalculatePosition(struct chrdata *chr, struct coord *pos)
{
s16 rooms[8];
f32 frac;
if (chr->actiontype == ACT_PATROL && chr->act_patrol.waydata.mode == WAYMODE_MAGIC) {
chrPatrolGetCurWaypointInfo(chr, pos, rooms);
if (!(chr->act_patrol.waydata.magicdone >= chr->act_patrol.waydata.magictotal)
&& chr->act_patrol.waydata.magictotal > 0) {
frac = chr->act_patrol.waydata.magicdone / chr->act_patrol.waydata.magictotal;
pos->x = (pos->x - chr->prop->pos.x) * frac + chr->prop->pos.x;
pos->y = (pos->y - chr->prop->pos.y) * frac + chr->prop->pos.y;
pos->z = (pos->z - chr->prop->pos.z) * frac + chr->prop->pos.z;
}
} else if (chr->actiontype == ACT_GOPOS && chr->act_gopos.waydata.mode == WAYMODE_MAGIC) {
chrGoPosGetCurWaypointInfo(chr, pos, rooms);
if (!(chr->act_gopos.waydata.magicdone >= chr->act_gopos.waydata.magictotal)
&& chr->act_gopos.waydata.magictotal > 0) {
frac = chr->act_gopos.waydata.magicdone / chr->act_gopos.waydata.magictotal;
pos->x = (pos->x - chr->prop->pos.x) * frac + chr->prop->pos.x;
pos->y = (pos->y - chr->prop->pos.y) * frac + chr->prop->pos.y;
pos->z = (pos->z - chr->prop->pos.z) * frac + chr->prop->pos.z;
}
} else {
*pos = chr->prop->pos;
}
}
void chrGoPosChooseAnimation(struct chrdata *chr)
{
s32 gospeed = chr->act_gopos.flags & GOPOSMASK_SPEED;
s32 male = g_HeadsAndBodies[chr->bodynum].ismale;
struct prop *gun1 = chrGetHeldProp(chr, 1);
struct prop *gun2 = chrGetHeldProp(chr, 0);
s32 flip = false;
s32 heavy;
s32 race = CHRRACE(chr);
s32 anim = -1;
u32 stack;
f32 speed = 0.5;
f32 sp60 = 16;
f32 animspeed = -1;
f32 startframe = 16;
if (chr->actiontype == ACT_GOPOS) {
chr->act_gopos.flags &= ~GOPOSFLAG_WAITING;
}
if (race == RACE_EYESPY || chr->aibot) {
return;
}
if (race == RACE_HUMAN || race == RACE_SKEDAR) {
if ((gun1 && gun2) || (!gun1 && !gun2)) {
heavy = false;
flip = random() % 2;
} else {
if (weaponIsOneHanded(gun1) || weaponIsOneHanded(gun2)) {
heavy = false;
flip = (bool)gun1 != false;
} else {
heavy = true;
flip = (bool)gun1 != false;
}
}
if (race == RACE_SKEDAR) {
if (gospeed == GOPOSFLAG_RUN) {
anim = ANIM_SKEDAR_RUNNING;
} else if (gospeed == GOPOSFLAG_JOG) {
anim = ANIM_0393;
} else if (gospeed == GOPOSFLAG_WALK) {
anim = ANIM_0392;
}
} else {
if (heavy) {
if (gospeed == GOPOSFLAG_RUN) {
// Human, heavy weapon, running
if (chr->hitpart == HITPART_LFOOT
|| chr->hitpart == HITPART_LSHIN
|| chr->hitpart == HITPART_LTHIGH
|| chr->hitpart == HITPART_RFOOT
|| chr->hitpart == HITPART_RSHIN
|| chr->hitpart == HITPART_RTHIGH) {
anim = ANIM_020A;
speed = 0.4;
} else if (chr->hitpart == HITPART_LHAND
|| chr->hitpart == HITPART_LFOREARM
|| chr->hitpart == HITPART_LBICEP
|| chr->hitpart == HITPART_RHAND
|| chr->hitpart == HITPART_RFOREARM
|| chr->hitpart == HITPART_RBICEP) {
anim = ANIM_020D;
speed = 0.4;
} else {
anim = ANIM_0029;
speed = 0.25;
}
if (chr->chrflags & CHRCFLAG_RUNFASTER) {
animspeed = 0.65;
startframe = 48;
} else {
animspeed = 0.5;
startframe = 48;
}
} else if (gospeed == GOPOSFLAG_JOG) {
// Human, heavy weapon, jogging
if (chr->hitpart == HITPART_LFOOT
|| chr->hitpart == HITPART_LSHIN
|| chr->hitpart == HITPART_LTHIGH
|| chr->hitpart == HITPART_RFOOT
|| chr->hitpart == HITPART_RSHIN
|| chr->hitpart == HITPART_RTHIGH) {
anim = ANIM_01F9;
} else if (chr->hitpart == HITPART_LHAND
|| chr->hitpart == HITPART_LFOREARM
|| chr->hitpart == HITPART_LBICEP
|| chr->hitpart == HITPART_RHAND
|| chr->hitpart == HITPART_RFOREARM
|| chr->hitpart == HITPART_RBICEP) {
anim = ANIM_01F8;
} else {
anim = ANIM_RUNNING_TWOHANDGUN;
}
} else {
// Human, heavy weapon, walking
if (chr->hitpart == HITPART_LFOOT
|| chr->hitpart == HITPART_LSHIN
|| chr->hitpart == HITPART_LTHIGH
|| chr->hitpart == HITPART_RFOOT
|| chr->hitpart == HITPART_RSHIN
|| chr->hitpart == HITPART_RTHIGH) {
anim = ANIM_01F9;
} else if (chr->hitpart == HITPART_LHAND
|| chr->hitpart == HITPART_LFOREARM
|| chr->hitpart == HITPART_LBICEP
|| chr->hitpart == HITPART_RHAND
|| chr->hitpart == HITPART_RFOREARM
|| chr->hitpart == HITPART_RBICEP) {
anim = ANIM_01F8;
} else {
if (random() % 2) {
anim = ANIM_0018;
} else {
anim = ANIM_0028;
}
}
}
} else {
if (gospeed == GOPOSFLAG_RUN) {
// Human, light weapon, running
if (chr->hitpart == HITPART_LFOOT
|| chr->hitpart == HITPART_LSHIN
|| chr->hitpart == HITPART_LTHIGH) {
anim = ANIM_020A;
flip = true;
speed = 0.4;
} else if (chr->hitpart == HITPART_RFOOT
|| chr->hitpart == HITPART_RSHIN
|| chr->hitpart == HITPART_RTHIGH) {
anim = ANIM_020A;
speed = 0.4;
flip = false;
} else if (chr->hitpart == HITPART_LHAND
|| chr->hitpart == HITPART_LFOREARM
|| chr->hitpart == HITPART_LBICEP) {
anim = ANIM_020D;
speed = 0.4;
flip = true;
} else if (chr->hitpart == HITPART_RHAND
|| chr->hitpart == HITPART_RFOREARM
|| chr->hitpart == HITPART_RBICEP) {
anim = ANIM_020D;
speed = 0.4;
flip = false;
} else if (male) {
if (random() % 4 == 0) {
speed = 0.25;
anim = ANIM_001E;
} else {
speed = 0.25;
anim = ANIM_005A;
sp60 = 24;
}
} else {
if (random() % 2) {
anim = ANIM_005E;
} else {
anim = ANIM_005A;
}
speed = 0.25;
}
if (chr->chrflags & CHRCFLAG_RUNFASTER) {
animspeed = 0.65;
startframe = 48;
} else {
animspeed = 0.5;
startframe = 48;
}
} else if (gospeed == GOPOSFLAG_JOG) {
// Human, light weapon, jogging
if (chr->hitpart == HITPART_LFOOT
|| chr->hitpart == HITPART_LSHIN
|| chr->hitpart == HITPART_LTHIGH) {
anim = ANIM_01F9;
flip = false;
} else if (chr->hitpart == HITPART_RFOOT
|| chr->hitpart == HITPART_RSHIN
|| chr->hitpart == HITPART_RTHIGH) {
anim = ANIM_01F9;
flip = true;
} else if (chr->hitpart == HITPART_LHAND
|| chr->hitpart == HITPART_LFOREARM
|| chr->hitpart == HITPART_LBICEP) {
anim = ANIM_01F8;
flip = false;
} else if (chr->hitpart == HITPART_RHAND
|| chr->hitpart == HITPART_RFOREARM
|| chr->hitpart == HITPART_RBICEP) {
anim = ANIM_01F8;
flip = true;
} else if (g_Vars.stagenum == STAGE_CHICAGO) {
anim = ANIM_005F;
} else if (male) {
if (random() % 2) {
anim = ANIM_001D;
} else {
anim = ANIM_RUNNING_ONEHANDGUN;
}
} else {
if (chr->myaction != MA_PANIC) {
if (random() % 2) {
anim = ANIM_005D;
} else {
anim = ANIM_0073;
}
} else {
anim = ANIM_021D;
}
}
} else {
// Human, light weapon, walking
s32 anims[] = {ANIM_006B, ANIM_001B, ANIM_0016};
if (chr->hitpart == HITPART_LFOOT
|| chr->hitpart == HITPART_LSHIN
|| chr->hitpart == HITPART_LTHIGH) {
anim = ANIM_01F9;
flip = false;
} else if (chr->hitpart == HITPART_RFOOT
|| chr->hitpart == HITPART_RSHIN
|| chr->hitpart == HITPART_RTHIGH) {
anim = ANIM_01F9;
flip = true;
} else if (chr->hitpart == HITPART_LHAND
|| chr->hitpart == HITPART_LFOREARM
|| chr->hitpart == HITPART_LBICEP) {
anim = ANIM_01F8;
flip = false;
} else if (chr->hitpart == HITPART_RHAND
|| chr->hitpart == HITPART_RFOREARM
|| chr->hitpart == HITPART_RBICEP) {
anim = ANIM_01F8;
flip = true;
} else if (male) {
anim = anims[random() % 3];
} else {
if (random() % 2) {
anim = ANIM_005C;
} else {
anim = ANIM_0072;
}
}
}
}
}
} else if (race == RACE_DRCAROLL) {
if (gospeed == GOPOSFLAG_RUN) {
anim = ANIM_0160;
} else if (gospeed == GOPOSFLAG_WALK) {
anim = ANIM_015F;
} else {
anim = ANIM_015F;
}
} else if (race == RACE_ROBOT) {
anim = ANIM_0238;
}
if (anim >= 0) {
modelSetAnimation(chr->model, anim, flip, 0, speed, sp60);
if (animspeed > 0) {
modelSetAnimSpeed(chr->model, animspeed, startframe);
}
}
}
bool chrGoToRoomPos(struct chrdata *chr, struct coord *pos, s16 *room, u32 goposflags)
{
struct prop *prop = chr->prop;
struct waypoint *nextwaypoint;
struct waypoint *lastwaypoint;
struct waypoint *waypoints[MAX_CHRWAYPOINTS];
s32 i;
struct coord curwppos;
s16 curwprooms[8];
s32 isgopos = chr->actiontype == ACT_GOPOS
&& (chr->act_gopos.flags & GOPOSMASK_SPEED) == (goposflags & 0xff & GOPOSMASK_SPEED)
&& !chrGoPosIsWaiting(chr);
s32 ismagic = isgopos && chr->act_gopos.waydata.mode == WAYMODE_MAGIC;
struct coord prevpos;
s32 numwaypoints = 0;
for (i = 0; chr->prop->rooms[i] != -1; i++) {
chr->oldrooms[i] = chr->prop->rooms[i];
}
chr->oldrooms[i] = -1;
if (isgopos && ismagic && chr->act_gopos.waypoints[chr->act_gopos.curindex]) {
nextwaypoint = chr->act_gopos.waypoints[chr->act_gopos.curindex];
} else {
nextwaypoint = waypointFindClosestToPos(&prop->pos, prop->rooms);
}
lastwaypoint = waypointFindClosestToPos(pos, room);
if (nextwaypoint && lastwaypoint) {
waypointSetHashThing(
((g_Vars.lvframe60 >> 9) << 7) + chr->chrnum * 8,
((g_Vars.lvframe60 >> 9) << 7) + chr->chrnum * 8);
numwaypoints = waypointFindRoute(nextwaypoint, lastwaypoint, waypoints, MAX_CHRWAYPOINTS);
waypointSetHashThing(0, 0);
}
if (numwaypoints > 1) {
if (isgopos && ismagic) {
chrCalculatePosition(chr, &prevpos);
} else {
prevpos = prop->pos;
}
chrStopFiring(chr);
chr->actiontype = ACT_GOPOS;
chr->act_gopos.endpos = *pos;
roomsCopy(room, chr->act_gopos.endrooms);
chr->act_gopos.target = lastwaypoint;
chr->act_gopos.curindex = 0;
chr->act_gopos.flags = goposflags | GOPOSFLAG_INIT;
chr->act_gopos.turnspeed = 0;
chr->unk32c_21 = 0;
chr->act_gopos.waydata.age = random() % 100;
chr->act_gopos.waydata.gotaimposobj = 0;
if (!isgopos) {
chr->act_gopos.waydata.lastvisible60 = -1;
}
for (i = 0; i < MAX_CHRWAYPOINTS; i++) {
chr->act_gopos.waypoints[i] = waypoints[i];
}
chrGoPosInitExpensive(chr);
chr->goposforce = -1;
chr->sleep = 0;
chr->liftaction = 0;
chr->act_gopos.flags &= ~(GOPOSFLAG_WALKDIRECT | GOPOSFLAG_DUCK | GOPOSFLAG_WAITING);
chrGoPosGetCurWaypointInfo(chr, &curwppos, curwprooms);
if ((!isgopos || ismagic)
&& g_Vars.normmplayerisrunning == false
&& (prop->flags & (PROPFLAG_ONANYSCREENPREVTICK | PROPFLAG_ONANYSCREENTHISTICK | PROPFLAG_ONTHISSCREENTHISTICK)) == 0
&& chrIsRoomOffScreen(chr, &curwppos, curwprooms)
&& chr->inlift == false) {
chrGoPosInitMagic(chr, &chr->act_gopos.waydata, &curwppos, &prevpos);
}
if (chr->act_gopos.waydata.mode != WAYMODE_MAGIC
&& modelIsAnimMerging(chr->model) && !chr->aibot) {
chr->hidden |= CHRHFLAG_NEEDANIM;
return true;
} else {
if (!isgopos) {
chrGoPosChooseAnimation(chr);
}
chr->hidden &= ~CHRHFLAG_NEEDANIM;
return true;
}
}
return false;
}
struct path *pathFindById(u32 path_id)
{
s32 i = 0;
for (i = 0; g_StageSetup.paths[i].pads; i++) {
if (path_id == g_StageSetup.paths[i].id) {
return &g_StageSetup.paths[i];
}
}
return NULL;
}
void chrPatrolChooseAnimation(struct chrdata *chr)
{
struct prop *leftprop = chrGetHeldProp(chr, HAND_LEFT);
struct prop *rightprop = chrGetHeldProp(chr, HAND_RIGHT);
s32 flip;
bool heavy;
s32 race = CHRRACE(chr);
s32 ismale = g_HeadsAndBodies[chr->bodynum].ismale;
f32 speed;
if (race == RACE_EYESPY) {
// empty
} else if (race == RACE_HUMAN || race == RACE_SKEDAR) {
if ((leftprop && rightprop) || (!leftprop && !rightprop)) {
// No weapon, or double weapons
heavy = false;
flip = random() % 2;
} else {
// Single weapon
if (weaponIsOneHanded(leftprop) || weaponIsOneHanded(rightprop)) {
heavy = false;
flip = ((bool)leftprop != false);
} else {
heavy = true;
flip = ((bool)leftprop != false);
}
}
if (race == RACE_SKEDAR) {
modelSetAnimation(chr->model, ANIM_0392, flip, 0, 0.25f, 16);
} else {
speed = 0.5f * func0f02dff0(ANIM_0028) / func0f02dff0(ANIM_006B);
if (heavy) {
modelSetAnimation(chr->model, random() % 2 ? ANIM_0018 : ANIM_0028, flip, 0, speed, 16);
} else if (ismale) {
s32 anims[] = { ANIM_006B, ANIM_001B, ANIM_0016 };
modelSetAnimation(chr->model, anims[random() % 3], flip, 0, speed, 16);
} else {
modelSetAnimation(chr->model, random() % 2 ? ANIM_005C : ANIM_0072, flip, 0, speed, 16);
}
}
} else if (race == RACE_DRCAROLL) {
modelSetAnimation(chr->model, ANIM_015F, false, 0, 0.5f, 16);
} else if (race == RACE_ROBOT) {
modelSetAnimation(chr->model, ANIM_0238, false, 0, 0.5f, 16);
}
}
void chrStartPatrol(struct chrdata *chr, struct path *path)
{
s32 i;
f32 dist;
f32 xdiff;
f32 zdiff;
s32 *padnumptr;
struct pad *pad;
struct coord nextpos;
s16 nextrooms[8];
s16 rooms[8];
f32 ymax;
f32 ymin;
f32 radius;
f32 bestdistance = 0;
s32 nextstep = -1;
struct prop *prop = chr->prop;
s16 sp60[2];
if (CHRRACE(chr) != RACE_EYESPY) {
// Do some kind of collision test with the pad to resume from...
// maybe a line of sight check?
if (chr->patrolnextstep >= 0 && chr->patrolnextstep < path->len) {
padnumptr = &path->pads[chr->patrolnextstep];
pad = &g_Pads[*padnumptr];
rooms[0] = pad->room;
rooms[1] = -1;
chrGetBbox(prop, &radius, &ymax, &ymin);
chrSetPerimEnabled(chr, false);
if (cdTestCylMove04(&prop->pos, prop->rooms, &pad->pos, rooms, CDTYPE_BG, 1,
ymax - prop->pos.y, ymin - prop->pos.y) != CDRESULT_COLLISION) {
nextstep = chr->patrolnextstep;
}
chrSetPerimEnabled(chr, true);
}
// If the pad to resume from is not in sight, find the closest pad
// to the chr's current position and start from there.
if (nextstep < 0) {
for (i = 0; path->pads[i] >= 0; i++) {
padnumptr = &path->pads[i];
pad = &g_Pads[*padnumptr];
xdiff = pad->pos.x - prop->pos.x;
zdiff = pad->pos.z - prop->pos.z;
dist = xdiff * xdiff + zdiff * zdiff;
if (nextstep < 0 || dist < bestdistance) {
bestdistance = dist;
nextstep = i;
}
}
}
padnumptr = &path->pads[nextstep];
pad = &g_Pads[*padnumptr];
rooms[0] = pad->room;
rooms[1] = -1;
// If chr has line of sight to the pad then begin the patrol,
// otherwise use gopos to get to the starting pad
if (func0f03654c(chr, &prop->pos, prop->rooms, &pad->pos, rooms, NULL,
chr->radius * 1.2f, CDTYPE_PATHBLOCKER | CDTYPE_BG) != CDRESULT_COLLISION) {
chrStopFiring(chr);
chr->actiontype = ACT_PATROL;
chr->act_patrol.path = path;
chr->act_patrol.nextstep = nextstep;
chr->act_patrol.forward = true;
chr->act_patrol.waydata.age = random() % 100;
chr->act_patrol.waydata.gotaimposobj = 0;
chr->act_patrol.waydata.lastvisible60 = -1;
chr->act_patrol.turnspeed = 0;
func0f037580(chr);
chr->sleep = 0;
chr->liftaction = LIFTACTION_NOTUSINGLIFT;
chr->patrolnextstep = chr->act_patrol.nextstep;
chrPatrolGetCurWaypointInfo(chr, &nextpos, nextrooms);
if (!g_Vars.normmplayerisrunning
&& (chr->prop->flags & (PROPFLAG_ONTHISSCREENTHISTICK | PROPFLAG_ONANYSCREENTHISTICK | PROPFLAG_ONANYSCREENPREVTICK)) == 0
&& chrIsRoomOffScreen(chr, &nextpos, nextrooms)
&& !chr->inlift) {
chrGoPosInitMagic(chr, &chr->act_patrol.waydata, &nextpos, &prop->pos);
}
// @bug: This should be act_patrol rather than act_gopos.
// It's actually reading act_patrol.waydata.aimposobj.y which is a
// float, so it's taking its binary representation and comparing it
// with integer 6.
if (chr->act_gopos.waydata.mode != WAYMODE_MAGIC && modelIsAnimMerging(chr->model)) {
chr->hidden |= CHRHFLAG_NEEDANIM;
} else {
chrPatrolChooseAnimation(chr);
chr->hidden &= ~CHRHFLAG_NEEDANIM;
}
} else {
sp60[0] = pad->room;
sp60[1] = -1;
chrGoToRoomPos(chr, &pad->pos, sp60, GOPOSFLAG_FORPATHSTART);
}
}
}
void chrRecordLastVisibleTargetTime(struct chrdata *chr)
{
chr->lastvisibletarget60 = g_Vars.lvframe60;
}
bool chrCanSeeEntity(struct chrdata *chr, struct coord *chrpos, s16 *chrrooms, bool allowextraheight, u32 attackflags, u32 entityid)
{
bool result = false;
struct coord targetpos;
s16 targetrooms[8];
struct prop *targetprop;
struct chrdata *targetchr;
u32 types;
struct prop *weaponprop;
if (attackflags & ATTACKFLAG_AIMFORWARD) {
result = true;
} else {
types = CDTYPE_DOORSWITHOUTFLAG | CDTYPE_ALL;
weaponprop = chrGetHeldProp(chr, HAND_RIGHT);
if (weaponprop == NULL) {
weaponprop = chrGetHeldProp(chr, HAND_LEFT);
}
if (weaponprop) {
struct weaponobj *weapon = weaponprop->weapon;
if (weapon->weaponnum == WEAPON_ROCKETLAUNCHER
|| weapon->weaponnum == WEAPON_SLAYER
|| weapon->weaponnum == WEAPON_ROCKETLAUNCHER_34) {
types = CDTYPE_DOORSWITHOUTFLAG | CDTYPE_OBJSWITHFLAG2 | CDTYPE_ALL;
} else {
types = CDTYPE_DOORSWITHOUTFLAG | CDTYPE_OBJSWITHFLAG | CDTYPE_ALL;
}
}
chrGetAttackEntityPos(chr, attackflags, entityid, &targetpos, targetrooms);
chrSetPerimEnabled(chr, false);
if ((attackflags & ATTACKFLAG_AIMATTARGET)) {
targetprop = chrGetTargetProp(chr);
if (targetprop->type != PROPTYPE_PLAYER || g_Vars.bondvisible) {
propSetPerimEnabled(targetprop, false);
if (allowextraheight && (chr->chrflags & CHRCFLAG_LOSEXTRAHEIGHT)) {
struct coord frompos;
s16 fromrooms[8];
frompos.x = chrpos->x;
frompos.y = chrpos->y + 70;
frompos.z = chrpos->z;
func0f065dd8(chrpos, chrrooms, &frompos, fromrooms);
if (cdTestLos05(&frompos, fromrooms, &targetpos, targetrooms, types, GEOFLAG_BLOCK_SHOOT)) {
chrRecordLastVisibleTargetTime(chr);
result = true;
}
} else {
if (cdTestLos05(chrpos, chrrooms, &targetpos, targetrooms, types, GEOFLAG_BLOCK_SHOOT)) {
chrRecordLastVisibleTargetTime(chr);
result = true;
}
}
propSetPerimEnabled(targetprop, true);
}
} else if (attackflags & ATTACKFLAG_AIMATCHR) {
targetchr = chrFindById(chr, entityid);
if (!targetchr || !targetchr->prop) {
targetchr = chr;
}
chrSetPerimEnabled(targetchr, false);
if (cdTestLos05(chrpos, chrrooms, &targetpos, targetrooms, types, GEOFLAG_BLOCK_SHOOT)) {
result = true;
}
chrSetPerimEnabled(targetchr, true);
} else if (attackflags & ATTACKFLAG_AIMATPAD) {
if (cdTestLos05(chrpos, chrrooms, &targetpos, targetrooms, types, GEOFLAG_BLOCK_SHOOT)) {
result = true;
}
}
chrSetPerimEnabled(chr, true);
}
return result;
}
bool chrCanSeeAttackTarget(struct chrdata *chr, struct coord *pos, s16 *rooms, bool allowextraheight)
{
u32 attackflags = ATTACKFLAG_AIMATTARGET;
u32 entityid = 0;
if (chr->actiontype == ACT_ATTACK) {
attackflags = chr->act_attack.flags;
entityid = chr->act_attack.entityid;
}
return chrCanSeeEntity(chr, pos, rooms, allowextraheight, attackflags, entityid);
}
bool chrCanSeeChr(struct chrdata *chr, struct chrdata *target, s16 *room)
{
bool cansee = false;
u32 stack;
s16 sp88[] = {-1, 0, 0, 0, 0, 0, 0, 0};
if (botIsTargetInvisible(chr, target) == 0) {
struct prop *prop = chr->prop;
struct coord pos;
s16 rooms[8];
pos.x = prop->pos.x;
pos.y = chr->ground + chr->height - 20;
pos.z = prop->pos.z;
chrSetPerimEnabled(chr, false);
chrSetPerimEnabled(target, false);
func0f065e74(&prop->pos, prop->rooms, &pos, rooms);
if (cdTestLos07(&pos, rooms, &target->prop->pos, target->prop->rooms, sp88,
CDTYPE_OBJS | CDTYPE_DOORS | CDTYPE_PATHBLOCKER | CDTYPE_BG | CDTYPE_AIOPAQUE,
GEOFLAG_BLOCK_SIGHT)) {
cansee = true;
}
chrSetPerimEnabled(chr, true);
chrSetPerimEnabled(target, true);
}
if (room) {
*room = sp88[0];
}
return cansee;
}
bool chrCanSeeTarget(struct chrdata *chr)
{
bool cansee;
struct prop *prop;
prop = chrGetTargetProp(chr);
cansee = chrCanSeeChr(chr, prop->chr, NULL);
if (cansee) {
chrRecordLastVisibleTargetTime(chr);
}
return cansee;
}
bool chrHasLineOfSightToPos(struct chrdata *chr, struct coord *pos, s16 *rooms)
{
struct prop *prop = chr->prop;
bool result = false;
struct coord eyepos;
s16 chrrooms[8];
eyepos.x = prop->pos.x;
eyepos.y = chr->ground + chr->height - 20;
eyepos.z = prop->pos.z;
chrSetPerimEnabled(chr, false);
func0f065e74(&prop->pos, prop->rooms, &eyepos, chrrooms);
if (cdTestLos05(&eyepos, chrrooms, pos, rooms,
CDTYPE_OBJS | CDTYPE_DOORS | CDTYPE_PATHBLOCKER | CDTYPE_BG | CDTYPE_AIOPAQUE,
GEOFLAG_BLOCK_SIGHT)) {
result = true;
}
chrSetPerimEnabled(chr, true);
return result;
}
bool chrCanSeePos(struct chrdata *chr, struct coord *pos, s16 *rooms)
{
f32 facingangle = chrGetInverseTheta(chr);
f32 posangle = atan2f(pos->x - chr->prop->pos.x, pos->z - chr->prop->pos.z);
f32 diffangle = posangle - facingangle;
if (posangle < facingangle) {
diffangle += M_BADTAU;
}
// This check is pointless because chrHasLineOfSightToPos is called
// with the same arguments regardless.
if ((diffangle < 1.7450513839722f || diffangle > 4.5371336936951f)
&& chrHasFlag(chr, CHRFLAG1_NOOP_00200000, BANK_1) == false) {
return chrHasLineOfSightToPos(chr, pos, rooms);
}
return chrHasLineOfSightToPos(chr, pos, rooms);
}
bool chrCanSeeProp(struct chrdata *chr, struct prop *prop)
{
bool result;
propSetPerimEnabled(prop, false);
result = chrCanSeePos(chr, &prop->pos, prop->rooms);
propSetPerimEnabled(prop, true);
return result;
}
void chrRecordLastSeeTargetTime(struct chrdata *chr)
{
chr->lastseetarget60 = g_Vars.lvframe60;
}
void chrRecordLastHearTargetTime(struct chrdata *chr)
{
chr->hidden |= CHRHFLAG_IS_HEARING_TARGET;
chr->lastheartarget60 = g_Vars.lvframe60;
}
bool chrIsStopped(struct chrdata *chr)
{
s16 anim = modelGetAnimNum(chr->model);
if (anim == ANIM_SNIPING_GETDOWN || anim == ANIM_SNIPING_GETUP) {
return false;
}
if (anim == ANIM_SNIPING_ONGROUND && chr->act_attack.numshots >= chr->act_attack.maxshots) {
chrStopFiring(chr);
return true;
}
if (chr->actiontype == ACT_ROBOTATTACK && chr->act_robotattack.finished) {
return true;
}
if (chr->actiontype == ACT_ATTACKAMOUNT && chr->act_attack.numshots >= chr->act_attack.maxshots) {
return true;
}
if (chr->actiontype == ACT_STAND
&& !chr->act_stand.prestand
&& !chr->act_stand.reaim
&& chr->act_stand.turning != 1) {
return true;
}
if (chr->actiontype == ACT_ANIM) {
if (chr->act_anim.completed
|| (modelGetAnimSpeed(chr->model) >= 0 && modelGetCurAnimFrame(chr->model) >= modelGetAnimEndFrame(chr->model))
|| (modelGetAnimSpeed(chr->model) < 0 && modelGetCurAnimFrame(chr->model) <= 0)) {
return true;
}
} else if (chr->actiontype == ACT_PATROL) {
return true;
}
return false;
}
bool chrCheckTargetInSight(struct chrdata *chr)
{
struct prop *prop = chr->prop;
struct prop *target = chrGetTargetProp(chr);
f32 sqdistance;
f32 fVar5 = chrGetInverseTheta(chr);
f32 x = target->pos.x - prop->pos.x;
f32 y = target->pos.y - prop->pos.y;
f32 z = target->pos.z - prop->pos.z;
f32 fVar6 = atan2f(x, z);
f32 angle = fVar6 - fVar5;
bool result = false;
if (fVar6 < fVar5) {
angle += M_BADTAU;
}
sqdistance = x * x + y * y + z * z;
if ((sqdistance < chr->visionrange * chr->visionrange * 10000.0f && (angle < 1.9195564985275f || angle > 4.3626284599304f))
|| (sqdistance < 40000.0f && (angle < 1.9195564985275f || angle > 4.3626284599304f))) {
result = false;
if (sqdistance < env0f1657e4()) {
f32 tmp;
s32 iVar8 = (sqrtf(sqdistance) * 0.0018749999580905f);
s32 tmp2;
if (angle > 0.7852731347084f && angle < 5.4969120025635f) {
tmp = angle;
if (tmp > M_PI) {
tmp = M_BADTAU - angle;
}
tmp -= 0.7852731347084f;
tmp2 = tmp * 3.8203268051147f;
iVar8 *= 1 + tmp2;
}
iVar8 = chrGetPercentageOfSlowness(chr, iVar8) + 1;
result = random() % iVar8 == 0;
}
}
if (result) {
result = chrCanSeeTarget(chr);
}
if (result) {
chrRecordLastSeeTargetTime(chr);
}
return result;
}
bool chrIsReadyForOrders(struct chrdata *chr)
{
if (chr->onladder) {
return false;
}
switch (chr->actiontype) {
case ACT_DIE:
case ACT_DEAD:
case ACT_PREARGH:
case ACT_DRUGGEDDROP:
case ACT_DRUGGEDKO:
case ACT_DRUGGEDCOMINGUP:
return false;
case ACT_ARGH:
if ((chr->chrflags & CHRCFLAG_00000200) == 0) {
return false;
}
break;
case ACT_ROBOTATTACK:
if (!chr->act_robotattack.finished) {
return false;
}
break;
}
return true;
}
bool chrIsDead(struct chrdata *chr)
{
if (!chr || chr->actiontype == ACT_DIE || chr->actiontype == ACT_DEAD) {
return true;
}
if (chr->prop && chr->prop->type == PROPTYPE_PLAYER) {
u32 playernum = playermgrGetPlayerNumByProp(chr->prop);
if (g_Vars.players[playernum]->isdead) {
return true;
}
}
return false;
}
bool chrTrySidestep(struct chrdata *chr)
{
u8 race = CHRRACE(chr);
if ((race == RACE_HUMAN || race == RACE_SKEDAR)
&& chrIsReadyForOrders(chr)) {
struct prop *prop = chr->prop;
struct prop *target = chrGetTargetProp(chr);
f32 a = chrGetInverseTheta(chr);
f32 b = atan2f(target->pos.x - prop->pos.x, target->pos.z - prop->pos.z);
f32 angle = b - a;
u32 stack[2];
if (b < a) {
angle += M_BADTAU;
}
if (angle < 0.7852731347084f || angle > 5.4969120025635f
|| (angle > 2.3558194637299f && angle < 3.9263656139374f)) {
bool side = (random() % 2) == 0;
if (chrCanJumpInDirection(chr, side, 100)) {
chrSidestep(chr, side);
return true;
}
if (chrCanJumpInDirection(chr, !side, 100)) {
chrSidestep(chr, !side);
return true;
}
}
}
return false;
}
bool chrTryJumpOut(struct chrdata *chr)
{
if (CHRRACE(chr) == RACE_HUMAN && chrIsReadyForOrders(chr)) {
struct prop *prop = chr->prop;
struct prop *target = chrGetTargetProp(chr);
f32 a = chrGetInverseTheta(chr);
f32 b = atan2f(target->pos.x - prop->pos.x, target->pos.z - prop->pos.z);
f32 angle = b - a;
u32 stack[2];
if (b < a) {
angle += M_BADTAU;
}
// This commented code is what the floats represent, but mismatches due
// to float precision:
//if (angle < BADDEG2RAD(45) || angle > BADDEG2RAD(315)
// || (angle > BADDEG2RAD(135) && angle < BADDEG2RAD(225))) {
if (angle < 0.7852731347084f || angle > 5.4969120025635f
|| (angle > 2.3558194637299f && angle < BADDEG2RAD(225))) {
bool side = (random() % 2) == 0;
if (chrCanJumpInDirection(chr, side, 200)) {
chrJumpOut(chr, side);
return true;
}
if (chrCanJumpInDirection(chr, !side, 200)) {
chrJumpOut(chr, !side);
return true;
}
}
}
return false;
}
bool chrTryRunSideways(struct chrdata *chr)
{
u32 race = CHRRACE(chr);
if ((race == RACE_HUMAN || race == RACE_SKEDAR)
&& chrIsReadyForOrders(chr)
&& g_Vars.lvframe60 - chr->lastwalk60 > TICKS(180)) {
struct prop *prop = chr->prop;
f32 distance = 200.0f + RANDOMFRAC() * 200.0f;
struct coord vector;
struct coord dstpos;
chrGetSideVectorToTarget(chr, random() % 2 == 0, &vector);
dstpos.x = vector.x * distance + prop->pos.x;
dstpos.y = prop->pos.y;
dstpos.z = vector.z * distance + prop->pos.z;
if (propchrHasClearLineToPos(prop, &dstpos, &vector)) {
chrRunToPos(chr, &dstpos);
return true;
}
vector.x = -vector.x;
vector.z = -vector.z;
dstpos.x = vector.x * distance + prop->pos.x;
dstpos.y = prop->pos.y;
dstpos.z = vector.z * distance + prop->pos.z;
if (propchrHasClearLineToPos(prop, &dstpos, &vector)) {
chrRunToPos(chr, &dstpos);
return true;
}
}
return false;
}
bool chrTryAttackWalk(struct chrdata *chr)
{
u32 race = CHRRACE(chr);
if (race == RACE_DRCAROLL || race == RACE_EYESPY) {
return false;
}
if (chrIsReadyForOrders(chr)) {
struct prop *prop = chr->prop;
if (chrCanSeeAttackTarget(chr, &prop->pos, prop->rooms, false)
&& (chrGetHeldUsableProp(chr, 0) || chrGetHeldUsableProp(chr, 1))
&& g_Vars.lvframe60 - chr->lastwalk60 > TICKS(120)) {
struct prop *target = chrGetTargetProp(chr);
f32 x = target->pos.x - prop->pos.x;
f32 y = target->pos.y - prop->pos.y;
f32 z = target->pos.z - prop->pos.z;
if (race == RACE_HUMAN && x * x + y * y + z * z >= 1000 * 1000) {
chrAttackWalk(chr, false);
return true;
}
}
}
return false;
}
bool chrTryAttackRoll(struct chrdata *chr)
{
if (CHRRACE(chr) == RACE_HUMAN && chrIsReadyForOrders(chr)) {
struct prop *prop = chr->prop;
if (chrCanSeeAttackTarget(chr, &prop->pos, prop->rooms, false) &&
(chrGetHeldUsableProp(chr, 0) || chrGetHeldUsableProp(chr, 1))) {
struct prop *target = chrGetTargetProp(chr);
f32 x = target->pos.x - prop->pos.x;
f32 y = target->pos.y - prop->pos.y;
f32 z = target->pos.z - prop->pos.z;
f32 sqdistance = x * x + y * y + z * z;
if (sqdistance >= 200 * 200) {
bool toleft = (random() % 2) == 0;
if (chrCanRollInDirection(chr, toleft, 200)) {
chrAttackRoll(chr, toleft);
return true;
}
if (chrCanRollInDirection(chr, !toleft, 200)) {
chrAttackRoll(chr, !toleft);
return true;
}
}
}
}
return false;
}
bool chrTryAttackAmount(struct chrdata *chr, u32 arg1, u32 arg2, u8 lower, u8 upper)
{
u8 race = CHRRACE(chr);
if ((race == RACE_HUMAN || race == RACE_SKEDAR)
&& chrIsReadyForOrders(chr)
&& chr->weapons_held[0]) {
s32 quantity;
f32 percentage;
struct weaponobj *weapon = chr->weapons_held[0]->weapon;
struct inventory_ammo *ammo;
quantity = 1;
if (upper > 100) {
upper = 100;
}
if (lower < upper) {
percentage = ((random() % (upper - lower)) + (u32)lower) * 0.01f;
} else {
percentage = 0;
}
ammo = weaponGetAmmoByFunction(weapon->weaponnum, 0);
if (ammo) {
quantity = ammo->clipsize * percentage;
}
if (quantity < 1) {
quantity = 1;
}
chr->aimendrshoulder = 0;
chr->aimendlshoulder = 0;
chr->aimendback = 0;
chr->aimendsideback = 0;
chr->aimendcount = 10;
chrAttackAmount(chr, arg1, arg2, quantity);
return true;
}
return false;
}
bool chrTryAttackStand(struct chrdata *chr, u32 attackflags, s32 entityid)
{
s32 race = CHRRACE(chr);
if (race == RACE_DRCAROLL || race == RACE_EYESPY) {
return false;
}
if (chrIsReadyForOrders(chr)) {
if (race == RACE_ROBOT) {
robotAttack(chr);
return true;
}
if (race == RACE_HUMAN || race == RACE_SKEDAR) {
if (chrGetHeldUsableProp(chr, 0) ||
(chrGetHeldUsableProp(chr, 1))) {
chrAttackStand(chr, attackflags, entityid);
return true;
}
}
}
return false;
}
bool chrTryAttackKneel(struct chrdata *chr, u32 attackflags, s32 entityid)
{
s32 race = CHRRACE(chr);
if (race == RACE_HUMAN || race == RACE_SKEDAR) {
if (chrIsReadyForOrders(chr) && (chrGetHeldUsableProp(chr, 0) || chrGetHeldUsableProp(chr, 1))) {
chrAttackKneel(chr, attackflags, entityid);
return true;
}
}
return false;
}
bool chrTryAttackLie(struct chrdata *chr, u32 attackflags, s32 entityid)
{
s32 race = CHRRACE(chr);
if (race == RACE_HUMAN || race == RACE_SKEDAR) {
if (chrIsReadyForOrders(chr) && (chrGetHeldUsableProp(chr, 0) || chrGetHeldUsableProp(chr, 1))) {
chrAttackLie(chr, attackflags, entityid);
return true;
}
}
return false;
}
bool chrTryModifyAttack(struct chrdata *chr, u32 attackflags, s32 entityid)
{
s32 race = CHRRACE(chr);
if (race == RACE_DRCAROLL || race == RACE_EYESPY) {
return false;
}
if (chr->actiontype == ACT_ATTACK
&& (chr->act_attack.flags & (ATTACKFLAG_AIMONLY | ATTACKFLAG_DONTTURN))) {
chr->act_attack.flags = attackflags;
chr->act_attack.entityid = entityid;
func0f031254(chr);
return true;
}
return false;
}
bool chrFaceEntity(struct chrdata *chr, u32 attackflags, s32 entityid)
{
if (chrIsReadyForOrders(chr)) {
if (chr->actiontype != ACT_STAND) {
chrStand(chr);
}
if (attackflags != chr->act_stand.flags || entityid != chr->act_stand.entityid) {
chr->act_stand.flags = attackflags;
chr->act_stand.entityid = entityid;
chr->act_stand.reaim = 0;
chr->act_stand.checkfacingwall = false;
if (attackflags == ATTACKFLAG_AIMATTARGET && entityid == 1) {
chr->act_stand.playwalkanim = true;
chr->act_stand.entityid = 0;
}
}
return true;
}
return false;
}
bool chrGoToPad(struct chrdata *chr, s32 padnum, u32 goposflags)
{
if (padnum >= 0 && chrIsReadyForOrders(chr)) {
padnum = chrResolvePadId(chr, padnum);
if (padnum >= 0) {
s16 rooms[2];
rooms[0] = g_Pads[padnum].room;
rooms[1] = -1;
if (chrGoToRoomPos(chr, &g_Pads[padnum].pos, rooms, goposflags)) {
return true;
}
}
}
return false;
}
bool chrSetPath(struct chrdata *chr, u32 path_id)
{
chr->path = path_id;
return true;
}
bool chrTryStartPatrol(struct chrdata *chr)
{
struct path *path = pathFindById(chr->path);
if (path && chrIsReadyForOrders(chr)) {
chrStartPatrol(chr, path);
return true;
}
return false;
}
bool chrTrySurrender(struct chrdata *chr)
{
if (CHRRACE(chr) == RACE_HUMAN && chrIsReadyForOrders(chr)) {
chrSurrender(chr);
return true;
}
return false;
}
bool chrFadeOut(struct chrdata *chr)
{
chrBeginDead(chr);
chrFadeCorpse(chr);
return true;
}
bool chrGoToTarget(struct chrdata *chr, u32 goposflags)
{
if (chrIsReadyForOrders(chr)) {
struct prop *prop = chrGetTargetProp(chr);
if (chrGoToRoomPos(chr, &prop->pos, prop->rooms, goposflags)) {
return true;
}
}
return false;
}
bool chrGoToChr(struct chrdata *chr, u32 dst_chrnum, u32 goposflags)
{
if (chrIsReadyForOrders(chr)) {
struct chrdata *dstchr = chrFindById(chr, dst_chrnum);
if (dstchr && dstchr->prop && chrGoToRoomPos(chr, &dstchr->prop->pos, dstchr->prop->rooms, goposflags)) {
return true;
}
}
return false;
}
bool chrGoToProp(struct chrdata *chr, struct prop *prop, u32 goposflags)
{
if (chrIsReadyForOrders(chr) && prop) {
if (chrGoToRoomPos(chr, &prop->pos, prop->rooms, goposflags)) {
return true;
}
}
return false;
}
bool chrGoToPos(struct chrdata *chr, struct coord *pos, u32 goposflags)
{
s16 inrooms[21];
s16 aboverooms[21];
if (chrIsReadyForOrders(chr)) {
s16 *rooms = NULL;
bgFindRoomsByPos(pos, inrooms, aboverooms, 20, NULL);
if (inrooms[0] != -1) {
rooms = inrooms;
} else if (aboverooms[0] != -1) {
rooms = aboverooms;
}
if (rooms != NULL && chrGoToRoomPos(chr, pos, rooms, goposflags)) {
return true;
}
}
return false;
}
s32 func0f03aca0(struct chrdata *chr, f32 arg1, u8 arg2)
{
f32 somefloat;
if (!arg2) {
arg1 -= chrGetDistanceToCoord(chr, &chr->runfrompos);
}
if (arg1 < 0) {
chrAssignCoverByCriteria(g_Vars.chrdata,
COVERCRITERIA_FURTHEREST
| COVERCRITERIA_DISTTOTARGET
| COVERCRITERIA_ONLYNEIGHBOURINGROOMS
| COVERCRITERIA_ROOMSFROMME, 0);
return chrGoToCover(chr, GOPOSFLAG_RUN);
}
somefloat = arg1 - 2000;
if (somefloat < 2000) {
somefloat = 2000;
}
if (!chrAssignCoverAwayFromDanger(chr, somefloat, arg1 + 10000)) {
chrAssignCoverByCriteria(g_Vars.chrdata,
COVERCRITERIA_FURTHEREST
| COVERCRITERIA_DISTTOTARGET
| COVERCRITERIA_ONLYNEIGHBOURINGROOMS
| COVERCRITERIA_ROOMSFROMME, 0);
}
return chrGoToCover(chr, GOPOSFLAG_RUN);
}
bool chrTryStop(struct chrdata *chr)
{
if (CHRRACE(chr) == RACE_EYESPY) {
func0f02e9a0(chr, 0);
return true;
}
if (chrIsReadyForOrders(chr)) {
chrStop(chr);
return true;
}
return false;
}
bool chrTryKneel(struct chrdata *chr)
{
if (CHRRACE(chr) == RACE_HUMAN && chrIsReadyForOrders(chr)) {
chrKneel(chr);
return true;
}
return false;
}
bool chrTryStartAnim(struct chrdata *chr, s32 animfnum, f32 startframe, f32 endframe, u8 chranimflags, s32 merge, f32 speed)
{
if (chrIsReadyForOrders(chr)) {
chrStartAnim(chr, animfnum, startframe, endframe, chranimflags, merge, speed);
return true;
}
return false;
}
bool chrConsiderGrenadeThrow(struct chrdata *chr, u32 attackflags, u32 entityid)
{
bool done = false;
if (CHRRACE(chr) == RACE_HUMAN &&
chr->grenadeprob > (random() % 255) &&
chrGetDistanceToTarget(chr) > 200 &&
chrIsReadyForOrders(chr)) {
struct prop *target = chrGetTargetProp(chr);
struct coord pos;
if (target) {
pos = target->pos;
}
if (target && cdTestLos04(&chr->prop->pos, chr->prop->rooms, &pos,
CDTYPE_OBJS | CDTYPE_DOORS | CDTYPE_PATHBLOCKER | CDTYPE_BG)) {
struct prop *leftprop = chrGetHeldProp(chr, HAND_LEFT);
struct prop *rightprop = chrGetHeldProp(chr, HAND_RIGHT);
struct weaponobj *weapon;
// If grenade is equipped in either hand, use it
if (rightprop) {
weapon = rightprop->weapon;
if (weapon->weaponnum == WEAPON_GRENADE || weapon->weaponnum == WEAPON_NBOMB) {
chrThrowGrenade(chr, 0, false);
chr->act_throwgrenade.flags = attackflags;
chr->act_throwgrenade.entityid = entityid;
done = true;
}
}
if (!done && leftprop) {
weapon = leftprop->weapon;
if (weapon->weaponnum == WEAPON_GRENADE || weapon->weaponnum == WEAPON_NBOMB) {
chrThrowGrenade(chr, 1, false);
chr->act_throwgrenade.flags = attackflags;
chr->act_throwgrenade.entityid = entityid;
done = true;
}
}
// Grenade not equipped, and using a single weapon
if (!done && (leftprop == NULL || rightprop == NULL)) {
struct prop *prop;
u32 flags = 0;
u32 stackpadding2[2];
if (rightprop) {
flags = OBJFLAG_WEAPON_LEFTHANDED;
}
if (g_Vars.stagenum == STAGE_MBR) {
prop = chrGiveWeapon(chr, MODEL_CHRGRENADE, WEAPON_NBOMB, flags);
} else {
prop = chrGiveWeapon(chr, MODEL_CHRGRENADE, WEAPON_GRENADE, flags);
}
if (prop) {
weapon = prop->weapon;
weapon->base.hidden |= OBJHFLAG_00000800;
chrThrowGrenade(chr, rightprop == NULL ? 0 : 1, true);
chr->act_throwgrenade.flags = attackflags;
chr->act_throwgrenade.entityid = entityid;
done = true;
}
}
}
}
return done;
}
void chrPunchInflictDamage(struct chrdata *chr, s32 damage, s32 range, u8 reverse)
{
struct prop *targetprop = chrGetTargetProp(chr);
struct gset gset = {WEAPON_UNARMED, 0, 0, FUNC_PRIMARY};
struct coord vector;
if (chr->aibot) {
gset.weaponnum = chr->aibot->weaponnum;
gset.weaponfunc = chr->aibot->gunfunc;
}
if (chrIsTargetInFov(chr, 20, reverse)
&& chrGetDistanceToTarget(chr) < range
&& cdTestLos04(&chr->prop->pos, chr->prop->rooms, &targetprop->pos,
CDTYPE_OBJS | CDTYPE_DOORS | CDTYPE_PATHBLOCKER | CDTYPE_BG)) {
vector.x = targetprop->pos.x - chr->prop->pos.x;
vector.y = 0;
vector.z = targetprop->pos.z - chr->prop->pos.z;
guNormalize(&vector.x, &vector.y, &vector.z);
bgunPlayPropHitSound(&gset, targetprop, -1);
if (targetprop->type & (PROPTYPE_PLAYER | PROPTYPE_CHR)) {
chrDamageByImpact(targetprop->chr, gsetGetDamage(&gset) * damage, &vector, &gset, chr->prop, 200);
}
}
weaponPlayWhooshSound(gset.weaponnum, chr->prop);
}
struct punchanim {
s32 animnum;
u32 damage;
s16 hitframe;
f32 endframe;
};
struct punchanim g_HumanPunchAnims[] = {
{ 0x027c, 5, 20, 60 },
{ 0x027d, 5, 20, 31 },
{ 0x027e, 5, 20, 48 },
{ 0x027f, 5, 20, 69 },
{ 0x0212, 5, 20, 64 },
{ 0x0213, 5, 20, 52 },
{ 0x0214, 5, 20, 51 },
{ 0x020e, 5, 20, 53 },
{ 0x020f, 5, 20, 89 },
{ 0x0210, 5, 20, 71 },
{ 0x0215, 5, 20, 62 },
{ 0x0211, 5, 20, 72 },
};
struct punchanim g_SkedarPunchAnims[] = {
{ 0x034c, 15, 25, 100 },
{ 0x034d, 15, 25, -1 },
{ 0x0395, 15, 25, -1 },
{ 0x0346, 15, 25, -1 },
{ 0x0347, 15, 25, -1 },
{ 0x034f, 15, 25, -1 },
};
/**
* Make the chr try to punch or kick.
*
* The function is only ever called with reverse = 0. If non-zero, it would
* cause Skedar to kick behind them.
*
* Note that the final human anim can't be used because the modulus value is too
* short by one.
*/
bool chrTryPunch(struct chrdata *chr, u8 reverse)
{
struct punchanim *anims = NULL;
s32 race = CHRRACE(chr);
s32 animindex;
u32 chranimflags = (random() % 256 > 128) ? CHRANIMFLAG_FLIP : 0;
s32 chrhitradius;
s32 playerhitradius;
f32 startframe;
bool ok;
if (race == RACE_HUMAN) {
anims = g_HumanPunchAnims;
startframe = 10;
chrhitradius = 120;
playerhitradius = 120;
animindex = random() % 11;
} else if (race == RACE_SKEDAR) {
anims = g_SkedarPunchAnims;
chrhitradius = 200;
playerhitradius = 200;
startframe = 20;
if (reverse) {
// Skedar kick behind
animindex = 5;
} else if (!chr->weapons_held[HAND_RIGHT] && !chr->weapons_held[HAND_LEFT]) {
// Unarmed: Only use indexes 0 or 1
animindex = random() % 2;
} else {
// Allow indexes 0-4, but if 3 or 4 then flip the anim based on
// which hand is holding the gun
animindex = random() % 5;
if (animindex >= 3) {
if (!chr->weapons_held[HAND_RIGHT] || !chr->weapons_held[HAND_LEFT]) {
if (chr->weapons_held[HAND_RIGHT]) {
chranimflags = 0;
} else if (chr->weapons_held[HAND_LEFT]) {
chranimflags = CHRANIMFLAG_FLIP;
}
}
}
}
}
if (chrHasFlag(chr, CHRFLAG1_ADJUSTPUNCHSPEED, BANK_1)) {
if (chrHasFlag(chr, CHRFLAG0_CHUCKNORRIS, BANK_0)) {
// Fast punch
ok = chrTryStartAnim(chr, anims[animindex].animnum, startframe, anims[animindex].endframe, chranimflags, 16, 1.5f);
} else {
// Slow punch
ok = chrTryStartAnim(chr, anims[animindex].animnum, startframe, anims[animindex].endframe - 25, chranimflags, 16, 0.5f);
}
chr->dodgerating = 0;
} else {
// Normal punch
ok = chrTryStartAnim(chr, anims[animindex].animnum, startframe, anims[animindex].endframe, chranimflags, 16, 0.85f);
chr->dodgerating = 0;
}
if (ok) {
struct prop *targetprop = chrGetTargetProp(chr);
if (targetprop->type & (PROPTYPE_EYESPY | PROPTYPE_PLAYER)) {
chr->act_anim.hitradius = playerhitradius;
} else {
chr->act_anim.hitradius = chrhitradius;
}
chr->act_anim.ishitanim = true;
chr->act_anim.hitframe = anims[animindex].hitframe;
if (g_Vars.normmplayerisrunning) {
chr->act_anim.hitdamage = 1;
} else if (chrHasFlag(chr, CHRFLAG1_ADJUSTPUNCHSPEED, BANK_1) && chrHasFlag(chr, CHRFLAG0_CHUCKNORRIS, BANK_0)) {
chr->act_anim.hitdamage = (u16)anims[animindex].damage * (f32)chr->morale + (u16)anims[animindex].damage * (f32)chr->morale;
} else if (chrHasFlag(chr, CHRFLAG1_PUNCHHARDER, BANK_1)) {
chr->act_anim.hitdamage = (u16)anims[animindex].damage * 6;
} else {
chr->act_anim.hitdamage = anims[animindex].damage;
}
chr->act_anim.reverse = reverse;
chr->chrflags &= ~CHRCFLAG_INJUREDTARGET;
return true;
}
return false;
}
/**
* Verifies that the given chr struct is actually an eyespy and returns the
* eyespy struct.
*
* Eyespys have their own chr struct, even though they aren't a chr. Iterating
* the player list is required because the only pointer to an eyespy is via the
* player struct.
*/
struct eyespy *chrToEyespy(struct chrdata *chr)
{
if (chr && chr->prop) {
if (CHRRACE(chr) == RACE_EYESPY) {
s32 playercount = PLAYERCOUNT();
s32 i;
for (i = 0; i < playercount; i++) {
if (g_Vars.players[i]->eyespy && chr->prop == g_Vars.players[i]->eyespy->prop) {
return g_Vars.players[i]->eyespy;
}
}
return NULL;
}
}
return NULL;
}
void chrTickStand(struct chrdata *chr)
{
s32 race;
s32 i;
s32 j;
s32 stack2;
struct prop *leftgun;
struct prop *rightgun;
u32 stack[2];
f32 angle;
f32 sp74[8];
f32 sp70;
f32 sp6c;
s32 tmp;
s32 index;
s32 sp44[8];
if (chr->hidden & CHRHFLAG_NEEDANIM) {
if (modelIsAnimMerging(chr->model)) {
return;
}
chrChooseStandAnimation(chr, chr->act_stand.mergetime);
chr->hidden &= ~CHRHFLAG_NEEDANIM;
}
if (modelGetAnimNum(chr->model) == ANIM_SNIPING_GETUP) {
if (modelGetCurAnimFrame(chr->model) >= modelGetAnimEndFrame(chr->model)) {
chrChooseStandAnimation(chr, 8);
chr->act_stand.prestand = 0;
}
return;
}
if (chr->sleep > 0) {
return;
}
race = CHRRACE(chr);
if (race == RACE_EYESPY) {
return;
}
if (chr->act_stand.prestand) {
if (modelGetCurAnimFrame(chr->model) >= modelGetAnimEndFrame(chr->model)) {
chrChooseStandAnimation(chr, 8);
chr->act_stand.prestand = 0;
}
chr->sleep = 0;
return;
}
if (!chr->aibot && (race == RACE_HUMAN || race == RACE_SKEDAR) && chr->act_stand.flags > 0) {
if (chr->act_stand.reaim) {
chr->act_stand.turning = chrTurn(chr, chr->act_stand.turning, modelGetNumAnimFrames(chr->model) - 1, 1, 0);
if (chr->act_stand.turning != TURNSTATE_TURNING) {
chrChooseStandAnimation(chr, 8);
chr->act_stand.reaim = false;
if (chr->act_stand.flags & ATTACKFLAG_AIMATDIRECTION) {
chr->act_stand.flags = 0;
}
}
} else {
f32 relangle = chrGetAttackEntityRelativeAngle(chr, chr->act_stand.flags, chr->act_stand.entityid);
if ((relangle > 0.34901028871536f && relangle < 5.9331746101379f)
|| (relangle > 0.17450514435768f && relangle < 6.1076798439026f && !chr->act_stand.playwalkanim)) {
leftgun = chrGetHeldProp(chr, HAND_LEFT);
rightgun = chrGetHeldProp(chr, HAND_RIGHT);
chr->act_stand.reaim = true;
chr->act_stand.turning = TURNSTATE_TURNING;
if (race == RACE_HUMAN) {
if ((leftgun && rightgun)
|| (!leftgun && !rightgun)
|| weaponIsOneHanded(leftgun)
|| weaponIsOneHanded(rightgun)) {
modelSetAnimation(chr->model, ANIM_006B, random() % 2, 0, 0.5f, 16);
modelSetAnimEndFrame(chr->model, animGetNumFrames(ANIM_006B) - 1);
} else {
if (rightgun || leftgun) {
modelSetAnimation(chr->model, ANIM_0028, leftgun != NULL, 0, 0.5f, 16);
modelSetAnimEndFrame(chr->model, animGetNumFrames(ANIM_0028) - 1);
}
}
} else if (race == RACE_SKEDAR) {
modelSetAnimation(chr->model, ANIM_0392, random() % 2, 0, 0.5f, 16);
modelSetAnimEndFrame(chr->model, animGetNumFrames(ANIM_0392) - 1);
}
} else if (chr->act_stand.flags & ATTACKFLAG_AIMATDIRECTION) {
chr->act_stand.flags = 0;
}
}
chr->sleep = 0;
return;
}
if (chr->aibot) {
return;
}
if (chr->prop->flags & PROPFLAG_ONANYSCREENPREVTICK) {
chr->sleep = 0;
} else {
chr->sleep = 14 + (random() % 5);
}
if (chr->act_stand.checkfacingwall == false) {
return;
}
if (chr->chrflags & CHRCFLAG_CANFACEWALL) {
chr->act_stand.checkfacingwall = false;
return;
}
chr->act_stand.wallcount -= chr->sleep;
if (chr->act_stand.wallcount >= 0) {
return;
}
sp6c = sp70 = chrGetInverseTheta(chr);
for (i = 0; i < 8; i++) {
sp6c += 0.7852731347084f;
if (sp6c >= M_BADTAU) {
sp6c -= M_BADTAU;
}
sp74[i] = func0f02e550(chr->prop, sp6c, 1000, CDTYPE_BG, 0, 1);
}
for (i = 0; i < 8; i++) {
sp44[i] = i;
}
for (i = 0; i < 7; i++) {
index = i;
for (j = index + 1; j < 8; j++) {
if (sp74[sp44[j]] < sp74[sp44[index]]) {
index = j;
}
}
j = sp44[i];
sp44[i] = sp44[index];
sp44[index] = j;
}
index = -1;
if (sp74[0] < 490) {
if (sp74[sp44[4]] < 200) {
index = 7;
} else if (sp44[0] == 0 || sp44[1] == 0 || sp44[2] == 0) {
if ((sp44[3] == 4 || sp44[4] == 4) && (random() % 3) == 0) {
if (sp44[3] == 4) {
index = 3;
} else {
index = 4;
}
} else {
index = 5 + random() % 3;
}
} else if ((sp44[0] == 1 || sp44[0] == 7) && sp44[5] && sp44[6] && sp44[7]) {
index = 5 + random() % 3;
}
}
if (index >= 0) {
i = sp44[index];
angle = sp70 + i * 0.7852731347084f;
if (angle >= M_BADTAU) {
angle -= M_BADTAU;
}
chrFaceEntity(chr, ATTACKFLAG_AIMATDIRECTION, angle * 10432.0390625f);
} else {
chr->act_stand.checkfacingwall = false;
}
}
void chrTickKneel(struct chrdata *chr)
{
chr->sleep = 0;
if ((chr->hidden & CHRHFLAG_NEEDANIM) && !modelIsAnimMerging(chr->model)) {
chrKneelChooseAnimation(chr);
chr->hidden &= ~CHRHFLAG_NEEDANIM;
}
}
void chrTickAnim(struct chrdata *chr)
{
if (chr->hidden & CHRHFLAG_NEEDANIM) {
if (modelIsAnimMerging(chr->model)) {
return;
}
modelSetAnimation(chr->model, chr->act_anim.animnum, chr->act_anim.flip,
chr->act_anim.startframe, chr->act_anim.speed, chr->act_anim.blend);
if (chr->act_anim.endframe >= 0) {
modelSetAnimEndFrame(chr->model, chr->act_anim.endframe);
}
chr->hidden &= ~CHRHFLAG_NEEDANIM;
}
if (!chr->act_anim.pauseatend && modelGetCurAnimFrame(chr->model) >= modelGetAnimEndFrame(chr->model)) {
chrStand(chr);
}
if (chr->act_anim.ishitanim && modelGetCurAnimFrame(chr->model) >= (s32)chr->act_anim.hitframe) {
chr->act_anim.ishitanim = false;
chrPunchInflictDamage(chr, chr->act_anim.hitdamage, chr->act_anim.hitradius, chr->act_anim.reverse);
}
// Play sneezing sound
if (modelGetAnimNum(chr->model) == ANIM_SNEEZE
&& CHRRACE(chr) == RACE_HUMAN
&& modelGetCurAnimFrame(chr->model) >= 42
&& (g_Vars.lvframenum % 2) == 0
&& chrGetDistanceToCurrentPlayer(chr) < 800) {
propsnd0f0939f8(NULL, chr->prop, SFX_0037, -1,
-1, 0, 0, 0, 0, -1, 0, -1, -1, -1, -1);
}
if (chr->sleep <= 0 && chr->act_anim.slowupdate) {
chr->sleep = 14 + (random() % 5);
}
if (modelGetAnimNum(chr->model) == ANIM_RELOAD_0209) {
chrSetFiring(chr, HAND_RIGHT, false);
chrSetFiring(chr, HAND_LEFT, false);
}
}
void chrTickSurrender(struct chrdata *chr)
{
if (chr->hidden & CHRHFLAG_NEEDANIM) {
if (modelIsAnimMerging(chr->model)) {
return;
}
chrSurrenderChooseAnimation(chr);
chr->hidden &= ~CHRHFLAG_NEEDANIM;
}
if (chr->sleep <= 0) {
if (CHRRACE(chr) == RACE_HUMAN) {
struct model *model = chr->model;
chr->sleep = 16;
if (modelGetAnimNum(model) == ANIM_SURRENDER_002F && modelGetCurAnimFrame(model) >= 80.0f) {
struct coord coord = {0, 0, 0};
f32 value = chrGetInverseTheta(chr);
coord.x = -sinf(value);
coord.z = -cosf(value);
if (!propchrHasClearLineInVector(chr->prop, &coord, 20)) {
modelSetAnimation(chr->model, ANIM_SURRENDER_002E, random() & 1, 30, 0.5, 16);
modelSetAnimLooping(chr->model, 30, 16);
}
}
}
}
}
void chrFadeCorpse(struct chrdata *chr)
{
if (chr->actiontype == ACT_DEAD || chr->actiontype == ACT_DRUGGEDKO) {
chr->act_dead.fadenow = true;
}
}
void chrFadeCorpseWhenOffScreen(struct chrdata *chr)
{
if (chr->actiontype == ACT_DEAD) {
chr->act_dead.fadewheninvis = true;
}
}
void chrTickDead(struct chrdata *chr)
{
struct aibot *aibot = chr->aibot;
// If fade is active, handle it
if (chr->act_dead.fadetimer60 >= 0) {
chr->act_dead.fadetimer60 += g_Vars.lvupdate60;
if (chr->act_dead.fadetimer60 >= TICKS(90)) {
// Fade finished
chr->fadealpha = 0;
if (aibot) {
botSpawn(chr, true);
} else {
chr->hidden |= CHRHFLAG_REAPED;
}
} else {
// Still fading
chr->fadealpha = (TICKS(90) - chr->act_dead.fadetimer60) * 255 / TICKS(90);
}
} else {
// If fade has been triggered (this can happen when the corpse is on
// screen and there's lots of other chrs around)
if (chr->act_dead.fadenow) {
chr->act_dead.fadetimer60 = 0;
chrDropItemsForOwnerReap(chr);
}
if (chr->prop->flags & PROPFLAG_ONANYSCREENPREVTICK) {
// Keep corpse for now
chr->act_dead.invistimer60 = 0;
} else {
chr->act_dead.invistimer60 += g_Vars.lvupdate60;
}
if (chr->act_dead.fadewheninvis && chr->act_dead.invistimer60 >= TICKS(120)) {
// Remove corpse (off-screen)
if (aibot == NULL) {
chr->hidden |= CHRHFLAG_REAPED;
}
chr->fadealpha = 0;
chrDropItemsForOwnerReap(chr);
}
}
if (aibot == NULL) {
chr->ailist = NULL;
chr->aioffset = NULL;
}
}
/**
* This function is called when a chr is injured or killed. It iterates other
* chrs within 2000 units who can see the chr and updates their chrseeshot or
* chrseedie properties.
*
* The search stops once 7 chrs have been found in range, regardless of whether
* they can see the chr, presumably to avoid doing too many expensive line of
* sight checks in one tick. The last iterated chr index is stored and continued
* from there next time the function is called. The function is called on
* subsequent ticks while the chr is still in their injured or dying action.
*/
void chrAlertOthersOfInjury(struct chrdata *chr, bool dying)
{
s32 index = 0;
s32 numinrange = 0;
s32 numchrs = chrsGetNumSlots();
if (g_Vars.antiplayernum >= 0 && chr->prop == g_Vars.anti->prop) {
return;
}
if (chr->actiontype == ACT_ARGH) {
index = chr->act_argh.notifychrindex;
} else if (chr->actiontype == ACT_DIE || chr->actiontype == ACT_DRUGGEDDROP) {
index = chr->act_die.notifychrindex;
} else if (chr->actiontype == ACT_DEAD) {
index = chr->act_dead.notifychrindex;
}
for (; index < numchrs && numinrange < 7; index++) {
struct chrdata *loopchr = &g_ChrSlots[index];
if (loopchr->model && loopchr->prop && (loopchr->prop->flags & PROPFLAG_ENABLED)) {
f32 xdiff = loopchr->prop->pos.x - chr->prop->pos.x;
f32 ydiff = loopchr->prop->pos.y - chr->prop->pos.y;
f32 zdiff = loopchr->prop->pos.z - chr->prop->pos.z;
if (xdiff * xdiff + ydiff * ydiff + zdiff * zdiff < 4000000.0f) {
numinrange++;
if (chrCanSeePos(loopchr, &chr->prop->pos, chr->prop->rooms)) {
if (dying == false) {
loopchr->chrseeshot = chr->chrnum;
} else {
loopchr->chrseedie = chr->chrnum;
}
}
}
}
}
if (chr->actiontype == ACT_ARGH) {
chr->act_argh.notifychrindex = index;
} else if (chr->actiontype == ACT_DIE || chr->actiontype == ACT_DRUGGEDDROP) {
chr->act_die.notifychrindex = index;
} else if (chr->actiontype == ACT_DEAD) {
chr->act_dead.notifychrindex = index;
}
}
void chrTickDie(struct chrdata *chr)
{
struct model *model = chr->model;
u32 race = CHRRACE(chr);
u16 thuds[] = {
SFX_THUD_808D,
SFX_THUD_808E,
SFX_THUD_808F,
SFX_THUD_8090,
SFX_THUD_8091,
SFX_THUD_8092,
SFX_THUD_8093,
SFX_THUD_8094,
SFX_THUD_8095,
SFX_THUD_8096,
SFX_THUD_8097,
};
u16 specialdiesounds[] = {
SFX_M1_NOOO, // "Noooo!"
SFX_M1_SCREAM, // Death scream
SFX_M2_NOOO, // "Noooo!"
SFX_M2_NOOO, // "Noooo!"
SFX_M1_SCREAM, // Death scream
SFX_THUD_8092,
SFX_THUD_8093,
SFX_THUD_8094,
SFX_THUD_8095,
SFX_THUD_8096,
SFX_THUD_8097,
};
static s32 thudindex = 0;
if (race == RACE_EYESPY) {
return;
}
if (race == RACE_ROBOT) {
struct prop *prop = chr->prop;
func0f0926bc(prop, 1, 0xffff);
explosionCreateSimple(prop, &prop->pos, prop->rooms, EXPLOSIONTYPE_8, g_Vars.currentplayernum);
chr->hidden |= CHRHFLAG_REAPED;
return;
}
if (race == RACE_DRCAROLL) {
struct prop *prop = chr->prop;
if (g_DrCarollDyingTimer > TICKS(120) && chr->voicebox) {
// Play speech
u16 phrases[] = {
SFX_DRCAROLL_SYSTEMS_FAILURE,
SFX_DRCAROLL_YOU_GO_ON,
SFX_DRCAROLL_I_CANT_MAKE_IT,
SFX_DRCAROLL_IM_DYING,
SFX_DRCAROLL_GOODBYE,
SFX_DRCAROLL_YOU_WERE_SUPPOSED,
};
propsnd0f0939f8(NULL, chr->prop, phrases[random() % 5], -1,
-1, 0, 0, 0, 0, -1, 0, -1, -1, -1, -1);
chr->voicebox = 0;
}
// Change images randomly
if (chr->act_die.drcarollimagedelay > 0) {
chr->act_die.drcarollimagedelay -= g_Vars.lvupdate60;
} else {
chr->act_die.drcarollimagedelay = (random() % TICKS(1000)) * 0.01f + 5.0f;
chr->drcarollimage_left = 1 + (s32)((random() % 400) * 0.01f);
chr->drcarollimage_right = 1 + (s32)((random() % 400) * 0.01f);
}
if (g_DrCarollDyingTimer > TICKS(310)) {
// Explode
func0f0926bc(prop, 1, 0xffff);
explosionCreateSimple(prop, &prop->pos, prop->rooms, EXPLOSIONTYPE_8, g_Vars.currentplayernum);
chrBeginDead(chr);
} else if (chr->soundtimer > (s32)var80068080) {
// Play shield damage sound
chr->soundtimer = 0;
var80068080 -= 5;
propsnd0f0939f8(NULL, prop, SFX_SHIELD_DAMAGE, -1,
-1, 1024, 0, 0, 0, -1, 0, -1, -1, -1, -1);
sparksCreate(prop->rooms[0], prop, &prop->pos, NULL, 0, SPARKTYPE_ELECTRICAL);
}
return;
}
// Human or Skedar
// If due, play thud 1 sound
if (chr->act_die.thudframe1 >= 0 && modelGetCurAnimFrame(model) >= chr->act_die.thudframe1) {
if (chr->specialdie == 0) {
propsnd0f0939f8(NULL, chr->prop, thuds[thudindex], -1,
-1, 0, 0, 0, 0, -1, 0, -1, -1, -1, -1);
} else if (chr->specialdie != SPECIALDIE_OVERRAILING) {
propsnd0f0939f8(NULL, chr->prop, specialdiesounds[chr->specialdie - 1], -1,
-1, 0, 0, 0, 0, -1, 0, -1, -1, -1, -1);
}
thudindex++;
if (thudindex > 10) {
thudindex = 0;
}
chr->act_die.thudframe1 = -1;
}
// If due, play thud 2 sound
if (chr->act_die.thudframe2 >= 0 && modelGetCurAnimFrame(model) >= chr->act_die.thudframe2) {
if (chr->specialdie < 5) {
propsnd0f0939f8(NULL, chr->prop, SFX_THUD_808E, -1,
-1, 0, 0, 0, 0, -1, 0, -1, -1, -1, -1);
} else {
propsnd0f0939f8(NULL, chr->prop, thuds[thudindex], -1,
-1, 0, 0, 0, 0, -1, 0, -1, -1, -1, -1);
}
thudindex++;
if (thudindex > 10) {
thudindex = 0;
}
chr->act_die.thudframe2 = -1;
}
// Check for end of death animation and switch to ACT_DEAD
if (modelGetCurAnimFrame(model) >= modelGetAnimEndFrame(model)) {
if (CHRRACE(chr) == RACE_HUMAN && modelGetAnimNum(model) == ANIM_DEATH_STOMACH_LONG) {
modelSetAnimation(model, ANIM_003C, !modelIsFlipped(model), 50, 0.3, animGetNumFrames(ANIM_003C) - 51.0f);
modelSetAnimSpeed(model, 0.5, animGetNumFrames(ANIM_003C) - 51.0f);
return;
}
chrBeginDead(chr);
}
chrAlertOthersOfInjury(chr, true);
}
void chrTickDruggedComingUp(struct chrdata *chr)
{
u16 thuds[] = {
SFX_THUD_808D,
SFX_THUD_808E,
SFX_THUD_808F,
SFX_THUD_8090,
SFX_THUD_8091,
SFX_THUD_8092,
SFX_THUD_8093,
SFX_THUD_8094,
SFX_THUD_8095,
SFX_THUD_8096,
SFX_THUD_8097,
};
chr->act_druggedcomingup.timer60 += g_Vars.lvupdate60;
if (chr->act_druggedcomingup.timer60 > 0) {
struct animtablerow *row;
s32 race = CHRRACE(chr);
struct model *model = chr->model;
s32 i = 0;
bool done = false;
struct prop *weapon;
chrUncloak(chr, true);
chr->actiontype = ACT_DRUGGEDDROP;
while (!done) {
if (i >= 0
&& g_AnimTablesByRace[race][i].deathanims != NULL
&& g_AnimTablesByRace[race][i].deathanimcount > 0) {
s32 index = random() % g_AnimTablesByRace[race][i].deathanimcount;
row = &g_AnimTablesByRace[race][i].deathanims[index];
chr->act_die.thudframe1 = row->thudframe1;
chr->act_die.thudframe2 = row->thudframe2;
modelSetAnimationWithMerge(model, row->animnum, row->flip, 0, row->speed, 16, true);
if (row->endframe >= 0) {
modelSetAnimEndFrame(model, row->endframe);
}
done = true;
}
if (!done) {
i++;
if (g_AnimTablesByRace[race][i].hitpart == -1) {
done = true;
}
}
}
weapon = chr->weapons_held[HAND_RIGHT];
if (weapon && (weapon->obj->flags & OBJFLAG_AIUNDROPPABLE) == 0) {
objSetDropped(weapon, DROPTYPE_DEFAULT);
chr->hidden |= CHRHFLAG_00000001;
}
weapon = chr->weapons_held[HAND_LEFT];
if (weapon && (weapon->obj->flags & OBJFLAG_AIUNDROPPABLE) == 0) {
objSetDropped(weapon, DROPTYPE_DEFAULT);
chr->hidden |= CHRHFLAG_00000001;
}
chrDropConcealedItems(chr);
}
}
void chrTickDruggedDrop(struct chrdata *chr)
{
struct model *model = chr->model;
u16 thuds[] = {
SFX_THUD_808D,
SFX_THUD_808E,
SFX_THUD_808F,
SFX_THUD_8090,
SFX_THUD_8091,
SFX_THUD_8092,
SFX_THUD_8093,
SFX_THUD_8094,
SFX_THUD_8095,
SFX_THUD_8096,
SFX_THUD_8097,
};
static s32 thudindex = 0;
// If due, play thud 1 sound
if (chr->act_die.thudframe1 >= 0 && modelGetCurAnimFrame(model) >= chr->act_die.thudframe1) {
propsnd0f0939f8(NULL, chr->prop, thuds[thudindex], -1,
-1, 0, 0, 0, 0, -1, 0, -1, -1, -1, -1);
thudindex++;
if (thudindex > 10) {
thudindex = 0;
}
chr->act_die.thudframe1 = -1;
}
// If due, play thud 2 sound
if (chr->act_die.thudframe2 >= 0 && modelGetCurAnimFrame(model) >= chr->act_die.thudframe2) {
propsnd0f0939f8(NULL, chr->prop, thuds[thudindex], -1,
-1, 0, 0, 0, 0, -1, 0, -1, -1, -1, -1);
thudindex++;
if (thudindex > 10) {
thudindex = 0;
}
chr->act_die.thudframe2 = -1;
}
// If falling animation finished, assign ACT_DRUGGEDKO
if (modelGetCurAnimFrame(model) >= modelGetAnimEndFrame(model)) {
chr->actiontype = ACT_DRUGGEDKO;
chr->act_dead.fadetimer60 = chr->aibot ? 0 : -1;
chr->act_dead.fadenow = false;
chr->act_dead.fadewheninvis = false;
chr->act_dead.invistimer60 = 0;
chr->act_dead.notifychrindex = 0;
chr->sleep = 0;
}
chrAlertOthersOfInjury(chr, true);
}
void chrTickDruggedKo(struct chrdata *chr)
{
bool reap = false;
// If fade is active, handle it
if (chr->act_dead.fadetimer60 >= 0) {
chr->act_dead.fadetimer60 += g_Vars.lvupdate60;
if (chr->act_dead.fadetimer60 >= TICKS(90)) {
reap = true;
} else {
chr->fadealpha = (TICKS(90) - chr->act_dead.fadetimer60) * 255 / TICKS(90);
}
} else if ((chr->chrflags & CHRCFLAG_KEEPCORPSEKO) == 0) {
if (chr->act_dead.fadenow) {
chr->act_dead.fadetimer60 = 0;
}
if (chr->prop->flags & PROPFLAG_ONANYSCREENPREVTICK) {
chr->act_dead.invistimer60 = 0;
} else {
chr->act_dead.invistimer60 += g_Vars.lvupdate60;
}
if (chr->act_dead.fadewheninvis && chr->act_dead.invistimer60 >= TICKS(120)) {
reap = true;
}
}
if (reap) {
chr->fadealpha = 0;
chr->hidden |= CHRHFLAG_REAPED;
chrDropItemsForOwnerReap(chr);
}
}
void chrTickArgh(struct chrdata *chr)
{
struct model *model = chr->model;
if (modelGetCurAnimFrame(model) >= modelGetAnimEndFrame(model)) {
chrRecordLastSeeTargetTime(chr);
if (CHRRACE(chr) == RACE_HUMAN && modelGetAnimNum(model) == ANIM_DEATH_STOMACH_LONG) {
func0f02ed28(chr, 26);
} else {
if (chr->race == RACE_DRCAROLL) {
chr->drcarollimage_left = DRCAROLLIMAGE_EYESDEFAULT;
chr->drcarollimage_right = DRCAROLLIMAGE_EYESDEFAULT;
}
chrStop(chr);
}
}
chrAlertOthersOfInjury(chr, false);
}
void chrTickPreArgh(struct chrdata *chr)
{
struct model *model = chr->model;
if (modelGetCurAnimFrame(model) >= modelGetAnimEndFrame(model)) {
struct coord dir;
dir = chr->act_preargh.dir;
chrReactToDamage(chr, &dir,
chr->act_preargh.relshotdir,
chr->act_preargh.hitpart,
&chr->act_preargh.gset,
chr->act_preargh.aplayernum);
}
}
void chrTickSidestep(struct chrdata *chr)
{
struct model *model = chr->model;
if (chr->hidden & CHRHFLAG_NEEDANIM) {
if (modelIsAnimMerging(chr->model)) {
return;
}
chrSidestepChooseAnimation(chr);
chr->hidden &= ~CHRHFLAG_NEEDANIM;
}
if (modelGetCurAnimFrame(model) >= modelGetAnimEndFrame(model)) {
chrRecordLastSeeTargetTime(chr);
func0f02ed28(chr, 10);
}
}
void chrTickJumpOut(struct chrdata *chr)
{
struct model *model = chr->model;
if (chr->hidden & CHRHFLAG_NEEDANIM) {
if (modelIsAnimMerging(chr->model)) {
return;
}
chrJumpOutChooseAnimation(chr);
chr->hidden &= ~CHRHFLAG_NEEDANIM;
}
if (modelGetCurAnimFrame(model) >= modelGetAnimEndFrame(model)) {
chrRecordLastSeeTargetTime(chr);
chrStop(chr);
}
}
void chrTickTest(struct chrdata *chr)
{
struct model *model = chr->model;
if (modelGetCurAnimFrame(model) >= modelGetAnimEndFrame(model)) {
chrStand(chr);
}
}
void chrTickStartAlarm(struct chrdata *chr)
{
struct model *model = chr->model;
if (chr->hidden & CHRHFLAG_NEEDANIM) {
if (modelIsAnimMerging(chr->model)) {
return;
}
chrStartAlarmChooseAnimation(chr);
chr->hidden &= ~CHRHFLAG_NEEDANIM;
}
if (modelGetCurAnimFrame(model) >= 60) {
alarmActivate();
}
if (modelGetCurAnimFrame(model) >= modelGetAnimEndFrame(model)) {
chrStop(chr);
}
}
void chrTickSurprised(struct chrdata *chr)
{
if (chr->hidden & CHRHFLAG_NEEDANIM) {
if (modelIsAnimMerging(chr->model)) {
return;
}
chrSurprisedChooseAnimation(chr);
chr->hidden &= ~CHRHFLAG_NEEDANIM;
}
if (CHRRACE(chr) == RACE_HUMAN) {
struct model *model = chr->model;
if (modelGetCurAnimFrame(model) >= modelGetAnimEndFrame(model)) {
if (modelGetAnimNum(model) == ANIM_SURRENDER_002E) {
func0f02ed28(chr, 26);
} else if (modelGetAnimNum(model) == ANIM_003F) {
func0f02ed28(chr, 26);
} else {
chrStop(chr);
}
}
}
}
void chrCreateFireslot(struct chrdata *chr, s32 handnum, bool withsound, bool withbeam, struct coord *from, struct coord *to)
{
struct prop *weaponprop;
struct weaponobj *weapon;
s32 weaponnum;
bool playsound = false;
u8 duration;
u16 soundnum;
weaponprop = chrGetHeldProp(chr, handnum);
if (weaponprop) {
weapon = weaponprop->weapon;
weaponnum = weapon->weaponnum;
duration = gsetGetFireslotDuration(&weapon->gset);
soundnum = gsetGetSingleShootSound(&weapon->gset);
if (chr->fireslots[handnum] < 0) {
chr->fireslots[handnum] = bgunAllocateFireslot();
}
if (chr->fireslots[handnum] >= 0) {
struct fireslot *fireslot = &g_Fireslots[chr->fireslots[handnum]];
if (withsound) {
if (duration > 0) {
if (chr->hidden2 & CHRH2FLAG_0020) {
playsound = false;
} else {
if (g_Vars.lvframe60 > fireslot->endlvframe) {
playsound = true;
} else {
playsound = false;
}
}
} else {
playsound = true;
}
}
if (playsound) {
propsnd0f0939f8(NULL, chr->prop, soundnum, -1, -1, 0x400, 4, 0x11, NULL, -1, NULL, -1, -1, -1, -1);
fireslot->endlvframe = (u32)g_Vars.lvframe60 + duration;
chr->hidden2 |= CHRH2FLAG_0020;
}
if (withbeam) {
beamCreate(&fireslot->beam, weaponnum, from, to);
}
}
}
}
/**
* Returns the chr's turn angle difference to 360 degrees, in radians.
*/
f32 chrGetInverseTheta(struct chrdata *chr)
{
if (chr->aibot) {
return chr->aibot->unk0b0;
}
if (chr->model == NULL && chr->prop && chr->prop->type == PROPTYPE_PLAYER) {
struct player *player = g_Vars.players[playermgrGetPlayerNumByProp(chr->prop)];
f32 angle = (360.0f - player->vv_theta) * 0.017450513318181f;
if (angle >= M_BADTAU) {
angle -= M_BADTAU;
} else if (angle < 0) {
angle += M_BADTAU;
}
return angle;
}
return model0001ae44(chr->model);
}
void chrSetLookAngle(struct chrdata *chr, f32 angle)
{
if (chr->aibot) {
chr->aibot->unk0b0 = angle;
} else {
model0001ae90(chr->model, angle);
}
}
f32 func0f03e578(struct chrdata *chr)
{
if (chr->aibot) {
return chr->aibot->unk0a4;
}
return model0001ae44(chr->model);
}
void func0f03e5b0(struct chrdata *chr, f32 arg1)
{
if (chr->aibot) {
chr->aibot->unk0a4 = arg1;
} else {
model0001ae90(chr->model, arg1);
}
}
f32 chrGetAimAngle(struct chrdata *chr)
{
f32 angle = chrGetInverseTheta(chr) + chr->aimsideback;
f32 offset = 0;
if (angle >= M_BADTAU) {
angle -= M_BADTAU;
} else if (angle < 0) {
angle += M_BADTAU;
}
if (chr->aibot) {
if (chr->aibot->unk068) {
// empty
}
} else if (chr->actiontype == ACT_ATTACK
|| chr->actiontype == ACT_ATTACKROLL
|| chr->actiontype == ACT_BOT_ATTACKSTAND
|| chr->actiontype == ACT_BOT_ATTACKKNEEL
|| chr->actiontype == ACT_BOT_ATTACKSTRAFE) {
offset = chr->act_attack.animcfg->unk0c;
} else if (chr->prop->type == PROPTYPE_PLAYER) {
offset += g_Vars.players[playermgrGetPlayerNumByProp(chr->prop)]->angleoffset;
}
if (offset) {
if (chr->model->anim->flip) {
offset = M_BADTAU - offset;
}
angle += offset;
if (angle >= M_BADTAU) {
angle -= M_BADTAU;
} else if (angle < M_BADTAU) {
angle += M_BADTAU;
}
}
return angle;
}
f32 chrGetPitchAngle(struct chrdata *chr)
{
f32 sum = chr->aimuprshoulder + chr->aimupback;
if (sum < 0) {
sum += M_BADTAU;
}
return sum;
}
/**
* Turn the chr slightly towards their target.
*/
s32 chrTurn(struct chrdata *chr, s32 turning, f32 endanimframe, f32 speed, f32 toleranceangle)
{
if (turning != TURNSTATE_OFF) {
struct model *model = chr->model;
f32 curframe = modelGetCurAnimFrame(model);
u32 stack;
f32 finalangle = chrGetInverseTheta(chr);
f32 remainingangle;
f32 increment = M_BADTAU / 100.0f * speed * g_Vars.lvupdate60f * model->anim->playspeed;
if (chr->aibot) {
struct prop *target = chrGetTargetProp(chr);
remainingangle = chrGetAngleToPos(chr, &target->pos);
} else if (chr->actiontype == ACT_ATTACK
|| chr->actiontype == ACT_BOT_ATTACKSTAND
|| chr->actiontype == ACT_BOT_ATTACKKNEEL
|| chr->actiontype == ACT_BOT_ATTACKSTRAFE) {
remainingangle = chrGetAttackEntityRelativeAngle(chr, chr->act_attack.flags, chr->act_attack.entityid);
} else if (chr->actiontype == ACT_STAND) {
remainingangle = chrGetAttackEntityRelativeAngle(chr, chr->act_stand.flags, chr->act_stand.entityid);
} else if (chr->actiontype == ACT_THROWGRENADE) {
remainingangle = chrGetAttackEntityRelativeAngle(chr, chr->act_throwgrenade.flags, chr->act_throwgrenade.entityid);
} else {
struct prop *target = chrGetTargetProp(chr);
remainingangle = chrGetAngleToPos(chr, &target->pos);
}
remainingangle -= toleranceangle;
if (remainingangle < 0) {
remainingangle += M_BADTAU;
}
if (increment > remainingangle || M_BADTAU - increment < remainingangle) {
// Close enough to stop
finalangle += remainingangle;
if (finalangle >= M_BADTAU) {
finalangle -= M_BADTAU;
}
chrSetLookAngle(chr, finalangle);
turning = TURNSTATE_ONTARGET;
} else if (remainingangle < M_PI) {
// Turning in one direction
finalangle += increment;
if (finalangle >= M_BADTAU) {
finalangle -= M_BADTAU;
}
chrSetLookAngle(chr, finalangle);
} else {
// Turning in the other direction
finalangle -= increment;
if (finalangle < 0) {
finalangle += M_BADTAU;
}
chrSetLookAngle(chr, finalangle);
}
if (curframe >= endanimframe) {
turning = TURNSTATE_OFF;
}
}
return turning;
}
bool func0f03e9f4(struct chrdata *chr, struct attackanimconfig *animcfg, bool firingleft, bool firingright, f32 arg4)
{
f32 shootrotx = 0.0f;
f32 aimendsideback = 0.0f;
u32 flags = ATTACKFLAG_AIMATTARGET;
s32 entityid = 0;
bool result = true;
if (!chr->aibot) {
if (chr->actiontype == ACT_ATTACK
|| chr->actiontype == ACT_BOT_ATTACKSTAND
|| chr->actiontype == ACT_BOT_ATTACKKNEEL
|| chr->actiontype == ACT_BOT_ATTACKSTRAFE) {
flags = chr->act_attack.flags;
entityid = chr->act_attack.entityid;
} else if (chr->actiontype == ACT_STAND) {
flags = chr->act_stand.flags;
entityid = chr->act_stand.entityid;
}
}
if ((flags & ATTACKFLAG_AIMFORWARD) == 0) {
f32 sp178;
f32 sp174;
f32 sp170;
struct prop *chrprop = chr->prop;
struct prop *targetprop = chrGetTargetProp(chr);
f32 sqdist;
bool holdturn;
struct coord targetpos;
s16 targetrooms[8];
sp178 = targetprop->pos.x - chrprop->pos.x;
sp174 = targetprop->pos.y - chrprop->pos.y;
sp170 = targetprop->pos.z - chrprop->pos.z;
sqdist = sp178 * sp178 + sp174 * sp174 + sp170 * sp170;
if (chr->aibot) {
holdturn = false;
} else if (flags & ATTACKFLAG_AIMATTARGET) {
if (flags & ATTACKFLAG_DONTTURN) {
holdturn = true;
} else {
holdturn = chrCanSeeTarget(chr);
}
} else {
holdturn = true;
}
if ((flags & ATTACKFLAG_AIMATTARGET) && targetprop->type == PROPTYPE_PLAYER) {
f32 eyeheight = g_Vars.players[playermgrGetPlayerNumByProp(targetprop)]->vv_eyeheight;
targetpos = targetprop->pos;
if (chr->aibot) {
sp174 -= eyeheight * (0.4f + (0.05f * RANDOMFRAC() * arg4));
} else if (chr->chrflags & CHRCFLAG_LOSEXTRAHEIGHT) {
if (sqdist < 400.0f * 400.0f) {
if (chrprop->pos.y < targetpos.y - 2.0f * eyeheight) {
sp174 -= eyeheight * (0.55f + 0.1f * RANDOMFRAC() * arg4);
} else if (chrprop->pos.y > targetpos.y - eyeheight * 0.5f) {
sp174 -= eyeheight * (0.15f + 0.1f * RANDOMFRAC() * arg4);
} else {
sp174 = (RANDOMFRAC() * 0.1f * arg4 + 1.0f) * 40.0f;
}
} else {
sp174 += eyeheight * (0.025f - 0.05f * RANDOMFRAC() * arg4);
}
} else if (sqdist > 1000.0f * 1000.0f) {
if ((random() % 3) == 0) {
sp174 += eyeheight * (0.05f + 0.1f * RANDOMFRAC() * arg4);
} else {
sp174 -= eyeheight * (0.05f + 0.55f * RANDOMFRAC() * arg4);
}
} else {
if (chrprop->pos.y < targetpos.y - eyeheight) {
sp174 -= eyeheight * (0.55f + 0.1f * RANDOMFRAC() * arg4);
} else if (chrprop->pos.y > targetpos.y - eyeheight * 0.5f) {
sp174 -= eyeheight * (0.15f + 0.1f * RANDOMFRAC() * arg4);
} else {
sp174 = (RANDOMFRAC() * 0.1f * arg4 - 0.05f) * eyeheight;
}
}
} else {
struct coord chrpos;
modelGetRootPosition(chr->model, &chrpos);
chrGetAttackEntityPos(chr, flags, entityid, &targetpos, targetrooms);
sp178 = targetpos.x - chrpos.x;
sp174 = targetpos.y - chrpos.y;
sp170 = targetpos.z - chrpos.z;
}
if ((flags & ATTACKFLAG_NOVERTICAL) == 0) {
shootrotx = atan2f(sp174, sqrtf(sp178 * sp178 + sp170 * sp170));
if (shootrotx >= M_PI) {
shootrotx -= M_BADTAU;
}
}
if (holdturn) {
f32 aimangle = chrGetAimAngle(chr);
struct prop *gunprop;
struct modelnode *posnode;
struct model *gunmodel;
struct coord sp118;
s32 sp114;
struct modelnode *burstnode;
struct modelrwdata_chrinfo *chrrwdata;
Mtxf *sp108;
Mtxf spc8;
struct modelrodata_chrgunfire *burstrodata;
struct coord spb8;
Mtxf *spb4;
Mtxf *spb0;
Mtxf sp70;
Mtxf *sp6c;
struct coord sp60;
struct coord sp54;
struct coord sp48;
f32 anglev;
if (flags & ATTACKFLAG_AIMATTARGET) {
if (firingright) {
gunprop = chrGetHeldProp(chr, HAND_RIGHT);
} else {
gunprop = chrGetHeldProp(chr, HAND_LEFT);
}
if (PLAYERCOUNT() == 1
&& gunprop
&& (gunprop->flags & PROPFLAG_ONANYSCREENPREVTICK)
&& sqdist < 1000.0f * 1000.0f) {
struct defaultobj *gun = gunprop->obj;
gunmodel = gun->model;
sp114 = 0;
burstnode = modelGetPart(gunmodel->filedata, MODELPART_CHRGUN_GUNFIRE);
if (burstnode) {
sp108 = model0001a5cc(gunmodel, burstnode, 0);
burstrodata = &burstnode->rodata->chrgunfire;
spb4 = cam0f0b53a4((u8 *)sp108);
if (spb4) {
mtx00016798(sp108, &spc8);
mtx00015be0(spb4, &spc8);
spb8 = burstrodata->pos;
mtx4TransformVecInPlace(&spc8, &spb8);
sp114 = 1;
sp118 = spb8;
}
} else {
posnode = modelGetPart(gunmodel->filedata, MODELPART_CHRGUN_0001);
if (posnode) {
spb0 = model0001a5cc(gunmodel, posnode, 0);
sp6c = cam0f0b53a4((u8 *)spb0);
if (sp6c) {
mtx00016798(spb0, &sp70);
mtx00015be0(sp6c, &sp70);
sp114 = 1;
sp118.x = sp70.m[3][0];
sp118.y = sp70.m[3][1];
sp118.z = sp70.m[3][2];
}
}
}
if (sp114) {
sp54.x = sinf(aimangle);
sp54.y = 0.0f;
sp54.z = cosf(aimangle);
sp48.x = chrprop->pos.x - sp170;
sp48.y = chrprop->pos.y;
sp48.z = chrprop->pos.z + sp178;
func0f02e3dc(&chrprop->pos, &sp48, &sp118, &sp54, &sp60);
sp178 = targetpos.x - sp60.x;
sp170 = targetpos.z - sp60.z;
}
}
}
anglev = atan2f(sp178, sp170);
aimendsideback = anglev - aimangle;
if (anglev < aimangle) {
aimendsideback += M_BADTAU;
}
chrrwdata = modelGetNodeRwData(chr->model, chr->model->filedata->rootnode);
if (chrrwdata->unk5c > 0.0f) {
aimendsideback -= chrrwdata->unk5c * chrrwdata->unk58;
if (aimendsideback < 0.0f) {
aimendsideback += M_BADTAU;
}
if (aimendsideback >= M_BADTAU) {
aimendsideback -= M_BADTAU;
}
}
if (!chr->aibot
&& (flags & ATTACKFLAG_AIMATTARGET)
&& ((flags & (ATTACKFLAG_AIMONLY | ATTACKFLAG_DONTTURN)) == 0)
&& targetprop->type == PROPTYPE_PLAYER) {
if (1);
aimendsideback += chrGetAimLimitAngle(sqdist) * 0.5f * sinf((((s32) (g_Vars.lvframe60 * chr->model->anim->playspeed) + chr->chrnum) % 60) * 0.10470308f);
if (aimendsideback < 0.0f) {
aimendsideback += M_BADTAU;
}
if (aimendsideback >= M_BADTAU) {
aimendsideback -= M_BADTAU;
}
}
if (aimendsideback >= M_PI) {
aimendsideback -= M_BADTAU;
}
aimendsideback += chr->aimsideback;
if (animcfg) {
if (chr->model->anim->flip) {
if (aimendsideback < -animcfg->unk38) {
aimendsideback = -animcfg->unk38;
result = false;
} else if (aimendsideback > -animcfg->unk3c) {
aimendsideback = -animcfg->unk3c;
result = false;
}
} else {
if (aimendsideback > animcfg->unk38) {
aimendsideback = animcfg->unk38;
result = false;
} else if (aimendsideback < animcfg->unk3c) {
aimendsideback = animcfg->unk3c;
result = false;
}
}
}
}
}
chrCalculateAimEndProperties(chr, animcfg, firingleft, firingright, shootrotx);
chr->aimendsideback = aimendsideback;
chr->aimendcount = 10;
return result;
}
void chrCalculateAimEndProperties(struct chrdata *chr, struct attackanimconfig *animcfg, bool firingleft, bool firingright, f32 shootrotx)
{
f32 aimfreeshoulder = 0;
f32 aimendback = 0;
f32 aimgunshoulder = shootrotx;
if (animcfg != NULL) {
if (shootrotx > animcfg->unk30) {
aimendback = shootrotx - animcfg->unk30;
aimgunshoulder = animcfg->unk30;
} else if (shootrotx < animcfg->unk34) {
aimendback = shootrotx - animcfg->unk34;
aimgunshoulder = animcfg->unk34;
}
if (aimgunshoulder > 0) {
aimfreeshoulder = animcfg->unk40 * aimgunshoulder;
} else {
aimfreeshoulder = animcfg->unk44 * aimgunshoulder;
}
}
if (firingright) {
chr->aimendrshoulder = aimgunshoulder;
if (firingleft) {
chr->aimendlshoulder = aimgunshoulder;
} else {
chr->aimendlshoulder = aimfreeshoulder;
}
} else {
chr->aimendrshoulder = aimfreeshoulder;
chr->aimendlshoulder = aimgunshoulder;
}
chr->aimendback = aimendback;
}
void chrResetAimEndProperties(struct chrdata *chr)
{
chr->aimendcount = 10;
chr->aimendrshoulder = 0;
chr->aimendlshoulder = 0;
chr->aimendback = 0;
chr->aimendsideback = 0;
}
void chrSetFiring(struct chrdata *chr, s32 hand, bool firing)
{
struct prop *prop = chrGetHeldProp(chr, hand);
chr->prop->forcetick = firing ? true : false;
if (prop) {
weaponSetGunfireVisible(prop, firing, chr->prop->rooms[0]);
}
}
void chrStopFiring(struct chrdata *chr)
{
u8 race = CHRRACE(chr);
if (race != RACE_DRCAROLL && race != RACE_EYESPY && chr->aibot == NULL) {
chrSetFiring(chr, HAND_RIGHT, false);
chrSetFiring(chr, HAND_LEFT, false);
chrResetAimEndProperties(chr);
bgunFreeFireslot(chr->fireslots[0]);
bgunFreeFireslot(chr->fireslots[1]);
chr->fireslots[0] = -1;
chr->fireslots[1] = -1;
}
}
void chrSetHandFiring(struct chrdata *chr, s32 hand, bool firing)
{
if (firing) {
if (hand == HAND_LEFT) {
chr->hidden |= CHRHFLAG_FIRINGLEFT;
} else {
chr->hidden |= CHRHFLAG_FIRINGRIGHT;
}
} else {
if (hand == HAND_LEFT) {
chr->hidden &= ~CHRHFLAG_FIRINGLEFT;
} else {
chr->hidden &= ~CHRHFLAG_FIRINGRIGHT;
}
}
if (!firing) {
chrSetFiring(chr, hand, false);
}
}
f32 chrGetAimLimitAngle(f32 sqdist)
{
if (sqdist > 1600 * 1600) {
return 0.018752790987492f;
}
if (sqdist > 800 * 800) {
return 0.03761787340045f;
}
if (sqdist > 400 * 400) {
return 0.07478791475296f;
}
if (sqdist > 200 * 200) {
return 0.14957582950592f;
}
return 0.2512874007225f;
}
/**
* Calculate whether a chr's shot hits their target on this tick.
*
* Each chr maintains a shotbondsum property which is a float ranging from
* 0 to 1. Each time this function is called, the value is increased slightly
* based on the chr's accuracy and other factors. Once it reaches 1, the target
* is considered to be hit and the value is reset to 0.
*
* The function writes to the angleokptr argument if the angle to their target
* is within range, and writes to the hit argument to indicate if the target is
* being hit or not.
*/
void chrCalculateHit(struct chrdata *chr, bool *angleokptr, bool *hit, struct gset *gset)
{
struct prop *prop;
struct prop *target;
f32 xdist;
f32 ydist;
f32 zdist;
f32 angletotarget;
f32 angleaiming;
f32 anglediff;
f32 limitangle;
bool angleok;
u32 stack;
f32 taperdist;
f32 sqdist;
taperdist = 300;
// Check that the chr's aim angle is within an acceptable range to their
// target.
prop = chr->prop;
target = chrGetTargetProp(chr);
xdist = target->pos.x - prop->pos.x;
ydist = target->pos.y - prop->pos.y;
zdist = target->pos.z - prop->pos.z;
angletotarget = atan2f(xdist, zdist);
angleaiming = chrGetAimAngle(chr);
anglediff = angletotarget - angleaiming;
sqdist = xdist * xdist + ydist * ydist + zdist * zdist;
limitangle = chrGetAimLimitAngle(sqdist);
if (anglediff < 0) {
anglediff += M_BADTAU;
}
angleok = anglediff < limitangle || anglediff > M_BADTAU - limitangle;
*angleokptr = angleok;
*hit = false;
// Determine the distance at which accuracy starts to taper off
switch (gset->weaponnum) {
case WEAPON_FALCON2:
case WEAPON_FALCON2_SILENCER:
case WEAPON_MAULER:
case WEAPON_PHOENIX:
case WEAPON_DY357MAGNUM:
case WEAPON_DY357LX:
case WEAPON_CMP150:
case WEAPON_CYCLONE:
case WEAPON_CALLISTO:
case WEAPON_RCP120:
case WEAPON_LAPTOPGUN:
case WEAPON_DRAGON:
case WEAPON_K7AVENGER:
case WEAPON_AR34:
case WEAPON_SUPERDRAGON:
case WEAPON_SHOTGUN:
case WEAPON_REAPER:
case WEAPON_DEVASTATOR:
case WEAPON_ROCKETLAUNCHER:
case WEAPON_SLAYER:
case WEAPON_COMBATKNIFE:
case WEAPON_CROSSBOW:
case WEAPON_TRANQUILIZER:
case WEAPON_LASER:
case WEAPON_GRENADE:
case WEAPON_NBOMB:
case WEAPON_TIMEDMINE:
case WEAPON_PROXIMITYMINE:
case WEAPON_REMOTEMINE:
case WEAPON_COMBATBOOST:
case WEAPON_PP9I:
case WEAPON_CC13:
case WEAPON_KL01313:
case WEAPON_KF7SPECIAL:
case WEAPON_ZZT:
case WEAPON_DMC:
case WEAPON_AR53:
case WEAPON_RCP45:
case WEAPON_PSYCHOSISGUN:
default:
// Use default distance (300)
break;
case WEAPON_FALCON2_SCOPE:
case WEAPON_MAGSEC4:
taperdist = 600;
break;
case WEAPON_SNIPERRIFLE:
case WEAPON_FARSIGHT:
taperdist = 1200;
break;
}
if (angleok) {
f32 dist = sqrtf(xdist * xdist + ydist * ydist + zdist * zdist);
f32 accuracy = 0.16f;
// Decrease accuracy if further than taperdist
if (dist > taperdist) {
accuracy *= taperdist / dist;
}
// Scale accuracy up or down based on chr's accuracyrating
if (chr->accuracyrating > 0) {
accuracy *= 1 + chr->accuracyrating * 0.1f;
} else if (chr->accuracyrating < 0) {
if (chr->accuracyrating <= -100) {
accuracy = 0;
} else {
accuracy *= (chr->accuracyrating + 100) * 0.01f;
}
}
// Apply PD mode enemy accuracy setting (default 1 which is no op)
if (pdmodeGetEnemyAccuracy() <= 1) {
accuracy *= pdmodeGetEnemyAccuracy();
} else {
accuracy *= 9 / (10.001f - pdmodeGetEnemyAccuracy());
}
// Apply difficulty multiplier (solo A = 0.6, SA = 0.8, PA = 1.175)
accuracy *= g_EnemyAccuracyScale;
// If the weapon fires more than once per tick, double the value to
// account for it. No weapons meet this criteria, however.
if (weaponGetNumTicksPerShot(gset->weaponnum, gset->weaponfunc) <= 0) {
accuracy += accuracy;
}
// Shotgun doubles the value due to more bullets
if (gset->weaponnum == WEAPON_SHOTGUN) {
accuracy += accuracy;
}
chr->shotbondsum += accuracy;
if (chr->hidden & CHRHFLAG_PERFECTACCURACY) {
chr->shotbondsum += 1.1f;
}
if (chr->shotbondsum >= 1) {
*hit = true;
chr->shotbondsum = 0;
}
}
}
/**
* If the chr's gun is on screen, populate gunpos with the world coordinates of
* their gun and return true.
*
* If the chr's gun is off screen, return false without populating gunpos.
*/
bool chrGetGunPos(struct chrdata *chr, s32 handnum, struct coord *gunpos)
{
struct prop *weaponprop = chrGetHeldProp(chr, handnum);
struct defaultobj *obj;
struct model *model;
bool result = false;
struct modelnode *part0;
struct modelnode *part1;
Mtxf *spac;
Mtxf sp6c;
struct modelrodata_chrgunfire *rodata;
Mtxf *sp64;
Mtxf sp24;
if (weaponprop) {
obj = weaponprop->obj;
model = obj->model;
if ((chr->prop->flags & PROPFLAG_ONTHISSCREENTHISTICK) && (weaponprop->flags & PROPFLAG_ONTHISSCREENTHISTICK)) {
if ((part0 = modelGetPart(model->filedata, MODELPART_0000))) {
spac = model0001a5cc(model, part0, 0);
rodata = &part0->rodata->chrgunfire;
*gunpos = rodata->pos;
mtx00015be4(camGetProjectionMtxF(), spac, &sp6c);
mtx4TransformVecInPlace(&sp6c, gunpos);
result = true;
} else if ((part1 = modelGetPart(model->filedata, MODELPART_0001))) {
sp64 = model0001a5cc(model, part1, 0);
mtx00015be4(camGetProjectionMtxF(), sp64, &sp24);
gunpos->x = sp24.m[3][0];
gunpos->y = sp24.m[3][1];
gunpos->z = sp24.m[3][2];
result = true;
}
}
}
return result;
}
/**
* For a shielded chr, determine which model node has been shot and populate the
* last 4 pointer arguments with information about the hit.
*
* Shield hits are calculated using the bounding box of each body part.
* This gives a bit of padding around the chr which can register as a hit.
*
* nodeptr - will be populated with a pointer to the bbox model node.
* hitpartptr - will be populated with the HITPART constant value.
* modelptr - will be populated with a pointer to the chr's model.
* sideptr - will be populated with an index in the range 0-5 which
* represents which side of the node's bounding box was hit.
*/
void chrCalculateShieldHit(struct chrdata *chr, struct coord *pos, struct coord *vector,
struct modelnode **nodeptr, s32 *hitpartptr, struct model **modelptr, s32 *sideptr)
{
u32 stack1;
struct prop *prop = chr->prop;
bool done = false;
bool isdifferentmtx;
struct coord sp124;
struct coord sp118;
s32 i;
struct modelnode *bestnode;
u32 stack2[2];
Mtxf spc8;
f32 bestvolume;
Mtxf *worldtoscreenmtx;
struct modelnode *node;
f32 x;
f32 y;
f32 z;
f32 volume;
Mtxf *mtxptr1;
Mtxf *mtxptr2;
f32 sides[6];
u32 stack3;
if (prop->type != PROPTYPE_PLAYER || g_Vars.normmplayerisrunning || chrGetShield(chr) > 0) {
if (prop->flags & (PROPFLAG_ONTHISSCREENTHISTICK | PROPFLAG_ONANYSCREENTHISTICK | PROPFLAG_ONANYSCREENPREVTICK)) {
bestnode = NULL;
bestvolume = MAXFLOAT;
worldtoscreenmtx = cam0f0b5050((u8 *)chr->model->matrices);
if (worldtoscreenmtx) {
mtx4TransformVec(worldtoscreenmtx, pos, &sp124);
mtx4RotateVec(worldtoscreenmtx, vector, &sp118);
isdifferentmtx = (camGetWorldToScreenMtxf() != worldtoscreenmtx);
node = chr->model->filedata->rootnode;
while (node) {
if ((node->type & 0xff) == MODELNODETYPE_BBOX) {
mtxptr1 = model0001a5cc(chr->model, node, 0);
if (isdifferentmtx) {
mtx00016798(mtxptr1, &spc8);
mtxptr1 = &spc8;
}
x = mtxptr1->m[3][0] - sp124.f[0];
y = mtxptr1->m[3][1] - sp124.f[1];
z = mtxptr1->m[3][2] - sp124.f[2];
volume = x * x + y * y + z * z;
if (volume < bestvolume) {
bestvolume = volume;
bestnode = node;
}
}
// Iterate all nodes recursively except headspot's chidlren
if (node->child && (node->type & 0xff) != MODELNODETYPE_HEADSPOT) {
node = node->child;
} else {
while (node) {
if (node->next) {
node = node->next;
break;
}
node = node->parent;
}
}
}
if (bestvolume);
if (bestnode != NULL) {
Mtxf sp48;
struct modelrodata_bbox *rodata = &bestnode->rodata->bbox;
*hitpartptr = rodata->hitpart;
*nodeptr = bestnode;
*modelptr = chr->model;
*sideptr = 0;
mtxptr2 = model0001a5cc(chr->model, bestnode, 0);
if (isdifferentmtx) {
mtx00016798(mtxptr2, &sp48);
mtxptr2 = &sp48;
}
bestvolume = -2;
x = (sp118.f[0] * mtxptr2->m[0][0]) + (sp118.f[1] * mtxptr2->m[0][1]) + (sp118.f[2] * mtxptr2->m[0][2]);
y = (sp118.f[0] * mtxptr2->m[1][0]) + (sp118.f[1] * mtxptr2->m[1][1]) + (sp118.f[2] * mtxptr2->m[1][2]);
z = (sp118.f[0] * mtxptr2->m[2][0]) + (sp118.f[1] * mtxptr2->m[2][1]) + (sp118.f[2] * mtxptr2->m[2][2]);
sides[0] = x;
sides[1] = -x;
sides[2] = y;
sides[3] = -y;
sides[4] = z;
sides[5] = -z;
for (i = 0; i < 6; i++) {
if (sides[i] > bestvolume) {
bestvolume = sides[i];
*sideptr = i;
}
}
done = true;
}
}
}
// If no node was found above, search the model for the torso bbox
// and return that.
if (!done) {
node = chr->model->filedata->rootnode;
while (node) {
if ((node->type & 0xff) == MODELNODETYPE_BBOX) {
struct modelrodata_bbox *rodata = &node->rodata->bbox;
if (rodata->hitpart == HITPART_TORSO) {
*hitpartptr = rodata->hitpart;
*nodeptr = node;
*modelptr = chr->model;
*sideptr = 0;
return;
}
}
if (node->child) {
node = node->child;
} else {
while (node) {
if (node->next) {
node = node->next;
break;
}
node = node->parent;
}
}
}
}
}
}
/**
* Calculates the trajectory for thrown items.
*/
void chrCalculateTrajectory(struct coord *frompos, f32 arg1, struct coord *aimpos, struct coord *arg3)
{
f32 xvel;
f32 yvel;
f32 zvel;
f32 latvel;
f32 vel;
f32 sp40;
f32 sp3c;
f32 sp38;
f32 sp30;
f32 sp2c;
f32 sp24;
f32 sp28;
f32 sp20;
arg1 *= 0.59999999f;
xvel = (aimpos->x - frompos->x) * 0.01f;
yvel = (aimpos->y - frompos->y) * 0.01f;
zvel = (aimpos->z - frompos->z) * 0.01f;
vel = sqrtf(xvel * xvel + yvel * yvel + zvel * zvel);
latvel = sqrtf(xvel * xvel + zvel * zvel);
sp38 = latvel / vel;
sp40 = acosf(sp38);
if (yvel < 0) {
sp40 = -sp40;
}
sp2c = (vel * 9.81f * sp38 * sp38) / (arg1 * arg1) + yvel / vel;
if (sp2c < -1) {
sp2c = -1;
} else if (sp2c > 1) {
sp2c = 1;
}
sp3c = (asinf(sp2c) - sp40) * 0.5f + sp40;
sp28 = cosf(sp3c);
sp30 = sinf(sp3c);
arg3->x = xvel / latvel * sp28;
arg3->y = sp30;
arg3->z = zvel / latvel * sp28;
}
/**
* Fire the chr's gun, check what was hit and do all the appropriate things
* such as dealing damage, creating beams and sparks and playing sounds.
*
* This should be called on every frame while the chr is shooting.
* The function takes care of the gun's fire rate.
*/
void chrTickShoot(struct chrdata *chr, s32 handnum)
{
struct prop *chrprop = chr->prop;
struct prop *gunprop;
u8 isaibot = false;
u8 normalshoot = true;
if (chr->aibot) {
isaibot = true;
}
gunprop = chrGetHeldProp(chr, handnum);
if (gunprop) {
bool firingthisframe = false;
struct weaponobj *weapon = gunprop->weapon;
struct gset gset;
struct prop *targetprop = chrGetTargetProp(chr);
u32 attackflags;
bool shotdue;
bool makebeam;
struct coord gunpos;
s16 gunrooms[8];
struct coord hitpos;
bool hitsomething;
s16 hitrooms[8];
bool queriedhitrooms;
s32 tickspershot;
gset = weapon->gset;
attackflags = ATTACKFLAG_AIMATTARGET;
if (chr->actiontype == ACT_ATTACK
|| chr->actiontype == ACT_BOT_ATTACKSTAND
|| chr->actiontype == ACT_BOT_ATTACKKNEEL
|| chr->actiontype == ACT_BOT_ATTACKSTRAFE) {
attackflags = chr->act_attack.flags;
}
shotdue = false;
makebeam = false;
hitsomething = false;
queriedhitrooms = false;
// Most guns can fire at most once every few ticks - even automatics.
// The chr's firecount property tracks how many ticks have elapsed since
// the last bullet, which is used to determine if another bullet should
// be discharged on this tick.
tickspershot = weaponGetNumTicksPerShot(gset.weaponnum, gset.weaponfunc);
if (tickspershot <= 0) {
shotdue = true;
makebeam = true;
} else {
if (chr->aibot
&& chr->aibot->weaponnum == WEAPON_REAPER
&& chr->aibot->gunfunc == FUNC_PRIMARY) {
f32 sp208 = (TICKS(90) - chr->aibot->reaperspeed[handnum]) * (1.0f / TICKS(18.0f));
tickspershot *= 1 + sp208;
}
chr->firecount[handnum] += g_Vars.lvupdate60;
if (chr->firecount[handnum] >= tickspershot) {
chr->firecount[handnum] = 0;
chr->unk32c_12 ^= 1 << handnum;
shotdue = true;
if ((chr->unk32c_12 & (1 << handnum)) || gset.weaponnum == WEAPON_LASER) {
makebeam = true;
}
if (chr->actiontype == ACT_ATTACK) {
if (modelGetAnimNum(chr->model) == ANIM_SNIPING_ONGROUND) {
chr->act_attack.numshots++;
}
}
}
}
if (shotdue) {
f32 roty = chrGetAimAngle(chr);
f32 rotx = chrGetPitchAngle(chr);
bool extracdtypes = isaibot ? CDTYPE_PLAYERS : 0;
firingthisframe = true;
if (!chrGetGunPos(chr, handnum, &gunpos)) {
// Gun is off screen - use a quick but inexact calculation
gunpos.x = chrprop->pos.x;
gunpos.y = chrprop->pos.y + 30;
gunpos.z = chrprop->pos.z;
if (handnum == HAND_LEFT) {
gunpos.x += cosf(roty) * 10.0f;
gunpos.z += -sinf(roty) * 10.0f;
} else {
gunpos.x += -cosf(roty) * 10.0f;
gunpos.z += sinf(roty) * 10.0f;
}
}
// Check that the chr isn't clipping their gun through anything such
// as another chr or a closed door. If they are, the shot won't be
// taken because that wouldn't be fair.
// How nice of the developers to check for this!
chrSetPerimEnabled(chr, false);
if (cdTestLos10(&chrprop->pos, chrprop->rooms, &gunpos, gunrooms,
CDTYPE_DOORS | CDTYPE_CHRS | CDTYPE_BG | CDTYPE_DOORSWITHOUTFLAG | extracdtypes,
GEOFLAG_BLOCK_SHOOT) == CDRESULT_COLLISION) {
firingthisframe = false;
}
chrSetPerimEnabled(chr, true);
if (firingthisframe) {
bool angleok = false;
bool hitplayer = false;
bool effective = true;
s32 zero = 0;
struct coord vector;
f32 xdiff;
f32 ydiff;
f32 zdiff;
f32 sqshotdist;
struct prop *hitprop = NULL;
u32 cdtypes = isaibot
? CDTYPE_OBJS | CDTYPE_DOORS | CDTYPE_CHRS | CDTYPE_PATHBLOCKER | CDTYPE_BG | CDTYPE_DOORSWITHOUTFLAG | CDTYPE_PLAYERS
: CDTYPE_OBJS | CDTYPE_DOORS | CDTYPE_CHRS | CDTYPE_PATHBLOCKER | CDTYPE_BG | CDTYPE_DOORSWITHOUTFLAG;
u32 stack;
bool isshootingeyespy = CHRRACE(targetprop->chr) == RACE_EYESPY && chrGetDistanceToTarget(chr) > 150;
bool fudgeforeyespy = false;
if (isshootingeyespy) {
vector.x = targetprop->pos.x - gunpos.x;
vector.y = targetprop->pos.y - gunpos.y;
vector.z = targetprop->pos.z - gunpos.z;
guNormalize(&vector.x, &vector.y, &vector.z);
propSetPerimEnabled(targetprop, true);
} else {
vector.x = cosf(rotx) * sinf(roty);
vector.y = sinf(rotx);
vector.z = cosf(rotx) * cosf(roty);
if (isaibot) {
bgunCalculateBotShotSpread(&vector,
chr->aibot->weaponnum, chr->aibot->gunfunc,
chr->aibot->burstsdone[handnum], botGuessCrouchPos(chr),
chr->weapons_held[0] && chr->weapons_held[1]);
}
}
// Handle Farsight shots by aibots specially
// because they can shoot through walls.
if (chr->aibot && gset.weaponnum == WEAPON_FARSIGHT && !chr->aibot->targetinsight) {
makebeam = true;
// This function can never return 2 though...
if (botactShootFarsight(chr + zero, 0, &vector, &gunpos) == 2) {
normalshoot = random() % 255 > 200;
}
}
// Check if the shot would hit anything
hitpos.x = gunpos.x + vector.x * 65536.0f;
hitpos.y = gunpos.y + vector.y * 65536.0f;
hitpos.z = gunpos.z + vector.z * 65536.0f;
chrSetPerimEnabled(chr, false);
if (isaibot) {
g_Vars.useperimshoot = true;
}
if (cdExamLos08(&gunpos, gunrooms, &hitpos, cdtypes, GEOFLAG_BLOCK_SHOOT) == CDRESULT_COLLISION) {
hitsomething = true;
cdGetPos(&hitpos, 12072, "chraction.c");
hitprop = cdGetObstacleProp();
}
chrSetPerimEnabled(chr, true);
if (isaibot) {
g_Vars.useperimshoot = false;
}
// Eyespy is small and hard to hit, so make it a 50/50 chance
if (hitprop == NULL && isshootingeyespy) {
fudgeforeyespy = random() % 100 > 50;
if (fudgeforeyespy) {
hitprop = targetprop;
hitpos = hitprop->pos;
}
}
xdiff = hitpos.x - gunpos.x;
ydiff = hitpos.y - gunpos.y;
zdiff = hitpos.z - gunpos.z;
sqshotdist = xdiff * xdiff + ydiff * ydiff + zdiff * zdiff;
// Handle projectile launchers specially
if (gset.weaponnum == WEAPON_ROCKETLAUNCHER
|| gset.weaponnum == WEAPON_SLAYER
|| (gset.weaponnum == WEAPON_SUPERDRAGON && gset.weaponfunc == FUNC_SECONDARY)
|| gset.weaponnum == WEAPON_DEVASTATOR
|| gset.weaponnum == WEAPON_CROSSBOW
|| gset.weaponnum == WEAPON_ROCKETLAUNCHER_34) {
makebeam = false;
// Solo chrs won't fire their projectile weapon
// in less than 4 metres of space
if (isaibot || sqshotdist > 400.0f * 400.0f) {
struct weaponobj *projectileobj;
Mtxf identmtx;
struct coord sp16c;
f32 sp168;
struct coord sp15c;
Mtxf projectilemtx;
Mtxf yrotmtx;
struct weapon *weapondef = weaponFindById(gset.weaponnum);
struct weaponfunc_shootprojectile *func = weapondef->functions[gset.weaponfunc];
// Handle creating the projectile
if (gset.weaponnum == WEAPON_ROCKETLAUNCHER
|| gset.weaponnum == WEAPON_ROCKETLAUNCHER_34
|| gset.weaponnum == WEAPON_SLAYER) {
s32 rocketweaponnum = WEAPON_ROCKET;
if (func->base.base.flags & FUNCFLAG_HOMINGROCKET) {
rocketweaponnum = WEAPON_HOMINGROCKET;
}
projectileobj = weaponCreateProjectileFromWeaponNum(func->projectilemodelnum, rocketweaponnum, chr);
} else if (gset.weaponnum == WEAPON_CROSSBOW) {
projectileobj = weaponCreateProjectileFromWeaponNum(func->projectilemodelnum, WEAPON_BOLT, chr);
if (projectileobj) {
projectileobj->gunfunc = gset.weaponfunc;
}
} else if (gset.weaponnum == WEAPON_DEVASTATOR) {
projectileobj = weaponCreateProjectileFromWeaponNum(func->projectilemodelnum, WEAPON_GRENADEROUND, chr);
if (projectileobj) {
projectileobj->gunfunc = gset.weaponfunc;
}
} else if (gset.weaponnum == WEAPON_SUPERDRAGON) {
projectileobj = weaponCreateProjectileFromWeaponNum(func->projectilemodelnum, WEAPON_GRENADEROUND, chr);
if (projectileobj) {
projectileobj->gunfunc = FUNC_2;
}
} else {
// Unreachable
projectileobj = weaponCreateProjectileFromGset(func->projectilemodelnum, &gset, g_Vars.currentplayer->prop->chr);
}
if (projectileobj) {
f32 spcc;
sp168 = func->speed * (1.0f / 0.6f) / 60.0f;
spcc = func->traveldist * (1.0f / 0.6f);
// AI bots are a bit smarter than solo chrs
// with regard to how they aim their projectiles
if (isaibot && chrIsTargetInFov(chr, 30, 0)) {
bool hasaimpos = false;
u32 stack;
struct coord aimpos;
if (gset.weaponfunc == FUNC_PRIMARY &&
(gset.weaponnum == WEAPON_ROCKETLAUNCHER
|| gset.weaponnum == WEAPON_ROCKETLAUNCHER_34
|| gset.weaponnum == WEAPON_SLAYER)) {
if (targetprop->type & (PROPTYPE_CHR | PROPTYPE_PLAYER)) {
// Rockets - aim at target's feet
aimpos.x = targetprop->pos.x;
aimpos.y = targetprop->chr->manground;
aimpos.z = targetprop->pos.z;
vector.x = aimpos.x - gunpos.x;
vector.y = aimpos.y - gunpos.y;
vector.z = aimpos.z - gunpos.z;
guNormalize(&vector.x, &vector.y, &vector.z);
hasaimpos = true;
}
} else if ((gset.weaponnum == WEAPON_DEVASTATOR && gset.weaponfunc == FUNC_PRIMARY)
|| gset.weaponnum == WEAPON_SUPERDRAGON) {
if (targetprop->type & (PROPTYPE_CHR | PROPTYPE_PLAYER)) {
// Grenades - aim at target's feet
aimpos.x = targetprop->pos.x;
aimpos.y = targetprop->chr->manground;
aimpos.z = targetprop->pos.z;
chrCalculateTrajectory(&gunpos, spcc, &aimpos, &vector);
hasaimpos = true;
}
} else if ((gset.weaponnum == WEAPON_DEVASTATOR && gset.weaponfunc == FUNC_SECONDARY)
|| gset.weaponnum == WEAPON_CROSSBOW) {
// Wall hugger grenade or crossbow - aim at target directly
aimpos = targetprop->pos;
if (targetprop->type == PROPTYPE_PLAYER) {
aimpos.y -= 25;
}
chrCalculateTrajectory(&gunpos, spcc, &aimpos, &vector);
hasaimpos = true;
}
if (hasaimpos) {
f32 angle = chrGetAngleToPos(chr, &aimpos);
f32 cos = cosf(angle);
f32 sin = sinf(angle);
f32 x = vector.f[0];
f32 z = vector.f[2];
u32 stack;
vector.x = sin * z + cos * x;
vector.z = cos * z - sin * x;
}
}
// Calculate and projectile's matrix,
// spawn position and speed
mtx4LoadIdentity(&identmtx);
mtx4LoadXRotation(rotx, &projectilemtx);
mtx4LoadYRotation(roty, &yrotmtx);
mtx00015be0(&yrotmtx, &projectilemtx);
sp15c.x = vector.x * sp168;
sp15c.y = vector.y * sp168;
sp15c.z = vector.z * sp168;
sp16c.x = sp15c.f[0] * g_Vars.lvupdate60freal + vector.f[0] * spcc;
sp16c.y = sp15c.f[1] * g_Vars.lvupdate60freal + vector.f[1] * spcc;
sp16c.z = sp15c.f[2] * g_Vars.lvupdate60freal + vector.f[2] * spcc;
projectileobj->timer240 = func->timer60;
if (projectileobj->timer240 != -1) {
#if PAL
projectileobj->timer240 = projectileobj->timer240 * 200 / 60;
#else
projectileobj->timer240 *= 4;
#endif
}
bgun0f09ebcc(&projectileobj->base, &gunpos, gunrooms, &projectilemtx, &sp16c, &identmtx, chrprop, &gunpos);
if (projectileobj->base.hidden & OBJHFLAG_PROJECTILE) {
if (func->base.base.flags & FUNCFLAG_PROJECTILE_LIGHTWEIGHT) {
projectileobj->base.projectile->flags |= PROJECTILEFLAG_LIGHTWEIGHT;
} else if (func->base.base.flags & FUNCFLAG_PROJECTILE_POWERED) {
projectileobj->base.projectile->flags |= PROJECTILEFLAG_POWERED;
}
projectileobj->base.projectile->unk010 = sp15c.x;
projectileobj->base.projectile->unk014 = sp15c.y;
projectileobj->base.projectile->unk018 = sp15c.z;
projectileobj->base.projectile->pickuptimer240 = TICKS(240);
projectileobj->base.projectile->unk08c = func->reflectangle;
projectileobj->base.projectile->unk098 = func->unk50 * (1.0f / 0.6f);
projectileobj->base.projectile->targetprop = chrGetTargetProp(chr);
// Play sound
if (func->soundnum > 0) {
propsnd0f0939f8(NULL, projectileobj->base.prop, func->soundnum, -1,
-1, 0, 0, 0, NULL, -1, NULL, -1, -1, -1, -1);
}
}
}
} else {
firingthisframe = false;
}
normalshoot = false;
} else if (gset.weaponnum == WEAPON_MAULER && isaibot && gset.weaponfunc == FUNC_SECONDARY) {
gset.unk063a = (s32) (chr->aibot->maulercharge[handnum] * 10.0f);
chr->aibot->maulercharge[handnum] = 0;
}
if (normalshoot) {
if (!isaibot) {
if ((attackflags & ATTACKFLAG_AIMATTARGET)
&& targetprop->type == PROPTYPE_PLAYER
&& chrCanSeeAttackTarget(chr, &gunpos, gunrooms, false)
&& chrCompareTeams(targetprop->chr, chr, COMPARE_ENEMIES)) {
// Solo chr shooting at a player
xdiff = targetprop->pos.x - gunpos.x - vector.x * 15.0f;
ydiff = targetprop->pos.y - gunpos.y - vector.y * 15.0f;
zdiff = targetprop->pos.z - gunpos.z - vector.z * 15.0f;
if (xdiff * xdiff + ydiff * ydiff + zdiff * zdiff <= sqshotdist) {
// Player has a chance of being hit
chrCalculateHit(chr, &angleok, &hitplayer, &gset);
// If the player was hit then turn off effective
// (There's no need to check other props for
// hits later on in this function)
effective = !hitplayer;
if (angleok
&& (chr->actiontype == ACT_ATTACK
|| chr->actiontype == ACT_ATTACKROLL
|| chr->actiontype == ACT_BOT_ATTACKSTAND
|| chr->actiontype == ACT_BOT_ATTACKKNEEL
|| chr->actiontype == ACT_BOT_ATTACKSTRAFE)) {
chr->act_attack.lastontarget60 = g_Vars.lvframe60;
}
}
} else {
// Solo chr shooting at something else
if (chr->actiontype == ACT_ATTACK
|| chr->actiontype == ACT_ATTACKROLL
|| chr->actiontype == ACT_BOT_ATTACKSTAND
|| chr->actiontype == ACT_BOT_ATTACKKNEEL
|| chr->actiontype == ACT_BOT_ATTACKSTRAFE) {
chr->act_attack.lastontarget60 = g_Vars.lvframe60;
}
}
if (hitplayer) {
f32 damage = gsetGetDamage(&gset);
struct modelnode *node = NULL;
struct model *model = NULL;
s32 side = -1;
s32 hitpart = HITPART_GENERAL;
struct chrdata *targetchr = targetprop->chr;
hitpos = targetprop->pos;
if (random() % 2) {
hitpos.y += 2 + random() % 10;
} else {
hitpos.y -= 2 + random() % 10;
}
bgunPlayPropHitSound(&gset, targetprop, -1);
if (targetchr->model && chrGetShield(targetchr) > 0) {
chrCalculateShieldHit(targetchr, &hitpos, &vector, &node, &hitpart, &model, &side);
}
func0f0341dc(targetchr, damage, &vector, &gset, chr->prop, HITPART_GENERAL, targetprop, node, model, side, NULL);
} else if ((hitprop == NULL || (hitprop->type & (PROPTYPE_CHR | PROPTYPE_PLAYER)) == 0)
&& sqshotdist < 100.0f * 100.0f) {
// Hit the background or something other than a
// player or chr, and the shot distance was less
// than 1 metre. Don't bother applying damage etc.
effective = false;
}
}
if (effective) {
if (hitprop) {
if (hitprop->type & (PROPTYPE_PLAYER | PROPTYPE_CHR)) {
// Hit a player or chr other than the one they
// were aiming for
if (isaibot
|| fudgeforeyespy
|| ((chr->chrflags & CHRCFLAG_00000040) && chrCompareTeams(hitprop->chr, chr, COMPARE_ENEMIES))) {
struct modelnode *node = NULL;
struct model *model = NULL;
s32 side = -1;
s32 hitpart = HITPART_GENERAL;
f32 damage = gsetGetDamage(&gset);
struct chrdata *hitchr = hitprop->chr;
bgunPlayPropHitSound(&gset, hitprop, -1);
if (hitchr->model && chrGetShield(hitchr) > 0) {
chrCalculateShieldHit(hitchr, &hitpos, &vector, &node, &hitpart, &model, &side);
}
chrEmitSparks(hitchr, hitprop, hitpart, &hitpos, &vector, chr);
func0f0341dc(hitchr, damage, &vector, &gset, chr->prop, HITPART_GENERAL, hitprop, node, model, side, NULL);
} else {
makebeam = false;
firingthisframe = false;
}
} else if (hitprop->type & (PROPTYPE_OBJ | PROPTYPE_WEAPON | PROPTYPE_DOOR)) {
// Hit an object
struct defaultobj *hitobj = hitprop->obj;
s32 playernum = -1;
if (g_Vars.mplayerisrunning) {
playernum = mpPlayerGetIndex(chr);
}
bgunPlayPropHitSound(&gset, hitprop, -1);
func0f065e74(&gunpos, gunrooms, &hitpos, hitrooms);
queriedhitrooms = true;
if (chrIsUsingPaintball(chr)) {
sparksCreate(hitrooms[0], hitprop, &hitpos, NULL, NULL, SPARKTYPE_PAINT);
} else {
sparksCreate(hitrooms[0], hitprop, &hitpos, NULL, NULL, SPARKTYPE_DEFAULT);
}
if (g_MissionConfig.iscoop && chr->team == TEAM_ALLY
&& (hitobj->flags2 & OBJFLAG2_IMMUNETOANTI)) {
// Co-op can't damage mission critical objects
} else {
objTakeGunfire(hitobj, gsetGetDamage(&gset), &hitpos, gset.weaponnum, playernum);
}
}
} else if (hitsomething) {
// Hit the background
func0f065e74(&gunpos, gunrooms, &hitpos, hitrooms);
queriedhitrooms = true;
bgunPlayBgHitSound(&gset, &hitpos, -1, hitrooms);
if (chrIsUsingPaintball(chr)) {
sparksCreate(hitrooms[0], 0, &hitpos, NULL, NULL, SPARKTYPE_PAINT);
} else {
sparksCreate(hitrooms[0], 0, &hitpos, NULL, NULL, SPARKTYPE_DEFAULT);
}
}
// Create explosion if using Phoenix
if (gset.weaponnum == WEAPON_PHOENIX && gset.weaponfunc == FUNC_SECONDARY) {
s32 playernum = chr->aibot ? mpPlayerGetIndex(chr) : g_Vars.currentplayernum;
if (!queriedhitrooms) {
func0f065e74(&gunpos, gunrooms, &hitpos, hitrooms);
}
explosionCreateSimple(NULL, &hitpos, hitrooms, EXPLOSIONTYPE_PHOENIX, playernum);
}
}
}
if (isshootingeyespy) {
propSetPerimEnabled(targetprop, false);
}
}
}
if (makebeam) {
switch (gset.weaponnum) {
case WEAPON_FALCON2:
case WEAPON_FALCON2_SILENCER:
case WEAPON_FALCON2_SCOPE:
case WEAPON_MAGSEC4:
case WEAPON_MAULER:
case WEAPON_PHOENIX:
case WEAPON_DY357MAGNUM:
case WEAPON_DY357LX:
case WEAPON_CMP150:
case WEAPON_CYCLONE:
case WEAPON_CALLISTO:
case WEAPON_RCP120:
case WEAPON_LAPTOPGUN:
case WEAPON_DRAGON:
case WEAPON_K7AVENGER:
case WEAPON_AR34:
case WEAPON_SUPERDRAGON:
case WEAPON_REAPER:
case WEAPON_SNIPERRIFLE:
case WEAPON_FARSIGHT:
case WEAPON_TRANQUILIZER:
case WEAPON_LASER:
case WEAPON_PP9I:
case WEAPON_CC13:
case WEAPON_KL01313:
case WEAPON_KF7SPECIAL:
case WEAPON_ZZT:
case WEAPON_DMC:
case WEAPON_AR53:
case WEAPON_RCP45:
makebeam = true;
break;
default:
makebeam = false;
break;
}
}
// get optimised out but leave their strings in rodata.
// The on/off strings are surely used in a ternary statement in the
// previous string, but making this a ternary creates a mismatch.
chrCreateFireslot(chr, handnum, firingthisframe, firingthisframe && makebeam, &gunpos, &hitpos);
if (isaibot) {
if (firingthisframe) {
if (chr->aibot->loadedammo[handnum] > 0) {
chr->aibot->loadedammo[handnum]--;
}
}
chrSetFiring(chr, handnum, firingthisframe && normalshoot);
} else {
chrSetFiring(chr, handnum, firingthisframe);
}
}
}
void func0f041a74(struct chrdata *chr)
{
chr->hidden2 &= ~CHRH2FLAG_0020;
if (chr->actiontype == ACT_ROBOTATTACK) {
if (chr->act_robotattack.firing[0]) {
chr->prop->forcetick = true;
projectileCreate(chr->prop, chr->unk348[0], &chr->act_robotattack.pos[0],
&chr->act_robotattack.dir[0], chr->act_robotattack.guntype[0], chrGetTargetProp(chr));
chr->unk348[1]->unk08 = g_Vars.lvframe60 + 2;
chr->unk348[1]->unk14 = chr->unk348[0]->unk14;
} else {
chr->prop->forcetick = false;
}
if (chr->act_robotattack.firing[1]) {
chr->prop->forcetick = true;
projectileCreate(chr->prop, chr->unk348[1], &chr->act_robotattack.pos[1],
&chr->act_robotattack.dir[1], chr->act_robotattack.guntype[1], chrGetTargetProp(chr));
chr->unk348[0]->unk14 = chr->unk348[1]->unk14;
} else {
chr->prop->forcetick = false;
}
beamTick(chr->unk348[0]->beam);
beamTick(chr->unk348[1]->beam);
} else if (chr->actiontype == ACT_ATTACKAMOUNT) {
if (chr->act_attack.numshots < chr->act_attack.maxshots
&& (chr->hidden & CHRHFLAG_FIRINGRIGHT)) {
chrTickShoot(chr, HAND_RIGHT);
}
} else {
if (chr->hidden & CHRHFLAG_FIRINGRIGHT) {
chrTickShoot(chr, HAND_RIGHT);
chr->hidden &= ~CHRHFLAG_FIRINGRIGHT;
}
if (chr->hidden & CHRHFLAG_FIRINGLEFT) {
chrTickShoot(chr, HAND_LEFT);
chr->hidden &= ~CHRHFLAG_FIRINGLEFT;
}
}
}
bool func0f041c44(struct chrdata *chr)
{
if (CHRRACE(chr) == RACE_HUMAN) {
if (chr->act_attack.animcfg == &g_RollAttackAnims[2] || chr->act_attack.animcfg == &g_RollAttackAnims[3]) {
struct model *model = chr->model;
struct attackanimconfig *animcfg = &g_RollAttackAnims[1];
bool flip = model->anim->flip;
chr->act_attack.turning = TURNSTATE_OFF;
chr->act_attack.animcfg = animcfg;
chr->sleep = 0;
modelSetAnimation(model, animcfg->animnum, flip, animcfg->unk1c, chrGetRangedSpeed(chr, 0.7f, 1.12f), 22);
if (animcfg->unk14 >= 0) {
modelSetAnimEndFrame(model, animcfg->unk14);
}
return true;
}
}
return false;
}
void chrAttackAmountUpdateAnimation(struct chrdata *chr)
{
struct model *model = chr->model;
if (chr->act_attack.animcfg->unk24 > 0) {
modelSetAnimation(model,
modelGetAnimNum(model),
model->anim->flip,
chr->act_attack.animcfg->unk24,
chrGetRangedSpeed(chr, 0.5f, 0.8f),
8);
} else {
modelSetAnimation(model,
modelGetAnimNum(model),
model->anim->flip,
chr->act_attack.animcfg->unk1c,
chrGetRangedSpeed(chr, 0.5f, 0.8f),
8);
}
if (chr->act_attack.animcfg->unk14 >= 0) {
modelSetAnimEndFrame(model, chr->act_attack.animcfg->unk14);
}
}
void chrTickFire(struct chrdata *chr)
{
struct model *model = chr->model;
f32 curframe = modelGetCurAnimFrame(model);
s32 i;
if (modelGetAnimNum(model) == ANIM_SNIPING_GETDOWN) {
return;
}
if (chr->act_attack.lastontarget60 < chr->act_attack.lastfire60 - TICKS(30)
&& model->anim->animnum2 == 0
&& curframe > chr->act_attack.animcfg->unk18 + 10
&& curframe < chr->act_attack.animcfg->unk1c
&& (chr->act_attack.animcfg->unk24 < 0 || curframe < chr->act_attack.animcfg->unk24)) {
if (!chr->act_attack.dorecoil) {
if (!func0f041c44(chr)) {
modelSetAnimation(model, modelGetAnimNum(model), model->anim->flip,
chr->act_attack.animcfg->unk1c, chrGetRangedSpeed(chr, 0.5f, 0.8f), 8);
if (chr->act_attack.animcfg->unk14 >= 0) {
modelSetAnimEndFrame(model, chr->act_attack.animcfg->unk14);
}
}
} else {
chrAttackAmountUpdateAnimation(chr);
}
chr->act_attack.numshots = chr->act_attack.maxshots + 1;
curframe = modelGetCurAnimFrame(model);
}
if (curframe >= modelGetAnimEndFrame(model)) {
if (modelGetAnimNum(model) != ANIM_SNIPING_ONGROUND
&& (chr->act_attack.dooneburst || chr->act_attack.numshots > chr->act_attack.maxshots)) {
if (!func0f041c44(chr)) {
if (chr->act_attack.flags & ATTACKFLAG_AIMATTARGET) {
chrRecordLastSeeTargetTime(chr);
}
chrStop(chr);
return;
}
} else if (chr->act_attack.numshots == chr->act_attack.maxshots) {
chr->act_attack.numshots++;
chrAttackAmountUpdateAnimation(chr);
} else if (chr->act_attack.fired) {
f32 f2 = 0.5f;
f32 startframe;
f32 endframe;
f32 diff;
if (chr->act_attack.dorecoil) {
if (chr->act_attack.animcfg->unk20 > 0) {
startframe = chr->act_attack.animcfg->unk20;
} else {
startframe = chr->act_attack.animcfg->unk18;
}
if (chr->act_attack.animcfg->unk24 > 0) {
endframe = chr->act_attack.animcfg->unk24;
} else {
endframe = chr->act_attack.animcfg->unk1c;
}
} else {
startframe = chr->act_attack.animcfg->unk18;
if (chr->act_attack.animcfg->unk20 > 0) {
endframe = chr->act_attack.animcfg->unk20;
} else {
endframe = chr->act_attack.animcfg->unk1c;
}
}
diff = endframe - startframe;
if (diff < 12) {
f2 = diff * (1.0f / 24.0f);
} else if (diff > 16) {
f2 = diff * (1.0f / 32.0f);
}
if (chr->act_attack.everytick[HAND_RIGHT] && chr->act_attack.everytick[HAND_LEFT]) {
f2 = f2 + f2;
}
chr->act_attack.fired = false;
modelSetAnimation(model, modelGetAnimNum(model), model->anim->flip, startframe, f2, 8);
modelSetAnimEndFrame(model, endframe);
}
curframe = modelGetCurAnimFrame(model);
}
if (modelGetAnimNum(model) != ANIM_SNIPING_ONGROUND && (chr->act_attack.flags & ATTACKFLAG_DONTTURN) == 0) {
f32 f2 = chr->act_attack.animcfg->unk0c;
f32 f12 = chr->act_attack.animcfg->unk04;
if ((chr->act_attack.flags & ATTACKFLAG_AIMONLY) && f12 > modelGetAnimEndFrame(model)) {
f12 = modelGetAnimEndFrame(model);
}
if (model->anim->flip) {
f2 = M_BADTAU - f2;
}
chr->act_attack.turning = chrTurn(chr, chr->act_attack.turning, f12, chrGetRangedSpeed(chr, 1, 1.6f), f2);
}
if ((curframe > chr->act_attack.animcfg->unk28 && curframe < chr->act_attack.animcfg->unk2c)
|| modelGetAnimNum(model) == ANIM_SNIPING_ONGROUND) {
func0f03e9f4(chr, chr->act_attack.animcfg, chr->act_attack.firegun[HAND_LEFT], chr->act_attack.firegun[HAND_RIGHT], 1);
} else {
chrResetAimEndProperties(chr);
}
for (i = 0; i < 2; i++) {
if (chr->act_attack.firegun[i]) {
if (chr->act_attack.everytick[i] == 0) {
if (modelGetAnimNum(model) == ANIM_SNIPING_ONGROUND
|| (curframe >= chr->act_attack.animcfg->unk18 && curframe < chr->act_attack.animcfg->unk1c)) {
chrSetHandFiring(chr, i, true);
chr->act_attack.lastfire60 = g_Vars.lvframe60;
if (chr->actiontype == ACT_ATTACKROLL) {
f32 f12 = chr->act_attack.animcfg->unk1c - chr->act_attack.animcfg->unk18;
if (f12 < 30) {
if (chr->act_attack.pausecount >= TICKS(60) - (s32)(PAL ? f12 * (50.0f / 60.0f) : f12) * 2) {
modelSetAnimSpeed(model, 0.5f, 0);
} else {
modelSetAnimSpeed(model, 0.1f, 0);
chr->act_attack.pausecount += g_Vars.lvupdate60;
}
} else {
modelSetAnimSpeed(model, 0.5f, 0);
}
} else {
modelSetAnimSpeed(model, 0.5f, 0);
}
} else {
chrSetHandFiring(chr, i, false);
if (chr->actiontype == ACT_ATTACKROLL) {
modelSetAnimSpeed(model, chrGetRangedSpeed(chr, 0.5f, 0.8f), 0);
} else {
modelSetAnimSpeed(model, chrGetRangedSpeed(chr, 0.5f, 0.8f), 0);
}
}
} else if (modelGetAnimNum(model) == ANIM_SNIPING_ONGROUND
|| ((!chr->act_attack.fired && (i == chr->act_attack.nextgun || !chr->act_attack.everytick[chr->act_attack.nextgun]))
&& ((chr->act_attack.animcfg->unk20 >= 0 && curframe >= chr->act_attack.animcfg->unk20 && curframe <= chr->act_attack.animcfg->unk24)
|| (chr->act_attack.animcfg->unk20 < 0 && curframe >= chr->act_attack.animcfg->unk18)))) {
chr->act_attack.fired = true;
chr->act_attack.nextgun = 1 - chr->act_attack.nextgun;
chr->act_attack.numshots++;
chr->act_attack.lastfire60 = g_Vars.lvframe60;
chrSetHandFiring(chr, i, true);
} else {
chrSetHandFiring(chr, i, false);
}
} else {
chrSetHandFiring(chr, i, false);
}
}
}
void chrTickAttackAmount(struct chrdata *chr)
{
struct model *model = chr->model;
f32 frame = modelGetCurAnimFrame(model);
f32 unk0c = chr->act_attack.animcfg->unk0c;
f32 unk04 = chr->act_attack.animcfg->unk04;
chrTurn(chr, 1, unk04, chrGetRangedSpeed(chr, 1, 1.6f), unk0c);
if (frame > chr->act_attack.animcfg->unk28
&& frame < chr->act_attack.animcfg->unk2c) {
func0f03e9f4(chr, chr->act_attack.animcfg, false, true, 0.2f);
} else {
chrResetAimEndProperties(chr);
}
if (frame >= chr->act_attack.animcfg->unk18 && chr->act_attack.dooneburst == false) {
chr->act_attack.dooneburst = true;
}
if (chr->act_attack.dooneburst) {
if (chr->act_attack.numshots++ < chr->act_attack.maxshots) {
chrSetHandFiring(chr, HAND_RIGHT, true);
} else {
chrAttackAmountUpdateAnimation(chr);
chrSetHandFiring(chr, HAND_RIGHT, false);
}
} else {
chrSetHandFiring(chr, HAND_RIGHT, false);
}
}
/**
* Sets the robot's muzzle flash to on or off.
*
* There are two muzzles, left and right, which is specified using the `right`
* argument.
*/
void robotSetMuzzleFlash(struct chrdata *chr, bool right, bool visible)
{
struct modelnode *node;
union modelrwdata *rwdata;
s32 partnum;
if (right) {
partnum = MODELPART_ROBOT_RGUNFIRE;
} else {
partnum = MODELPART_ROBOT_LGUNFIRE;
}
node = modelGetPart(chr->model->filedata, partnum);
if (node) {
rwdata = modelGetNodeRwData(chr->model, node);
}
if (rwdata) {
rwdata->chrgunfire.visible = visible;
}
}
void robotAttack(struct chrdata *chr)
{
u32 numshots = random() % 20;
if (chr->unk348[0] && chr->unk348[1]) {
chr->actiontype = ACT_ROBOTATTACK;
chr->unk348[0]->beam->age = -1;
chr->unk348[0]->unk00 = random() % 3;
chr->unk348[0]->unk01 = 0;
chr->unk348[0]->unk08 = -1;
chr->unk348[0]->unk0c = 0.85f;
if ((lvGetDifficulty() == DIFF_PA) * 0.2f) {
chr->unk348[0]->unk10 = 2.0f;
} else {
chr->unk348[0]->unk10 = 1.0f;
}
chr->unk348[0]->unk14 = 0.0f;
chr->act_robotattack.pos[0].x = 0.0f;
chr->act_robotattack.pos[0].y = 0.0f;
chr->act_robotattack.pos[0].z = 0.0f;
chr->act_robotattack.dir[0].x = 0.0f;
chr->act_robotattack.dir[0].y = 0.0f;
chr->act_robotattack.dir[0].z = 0.0f;
chr->act_robotattack.guntype[0] = WEAPON_WATCHLASER;
chr->act_robotattack.firing[0] = false;
chr->unk348[1]->beam->age = -1;
chr->unk348[1]->unk00 = random() % 3;
chr->unk348[1]->unk01 = 0;
chr->unk348[1]->unk08 = -1;
chr->unk348[1]->unk0c = 0.85f;
chr->unk348[1]->unk10 = 0.2f;
chr->unk348[1]->unk14 = 0.0f;
chr->act_robotattack.guntype[1] = WEAPON_WATCHLASER;
chr->act_robotattack.firing[1] = false;
chr->act_robotattack.finished = false;
chr->act_robotattack.numshots[0] = numshots;
chr->act_robotattack.numshots[1] = numshots;
chr->act_robotattack.pos[1].x = 0.0f;
chr->act_robotattack.pos[1].y = 0.0f;
chr->act_robotattack.pos[1].z = 0.0f;
chr->act_robotattack.dir[1].x = 0.0f;
chr->act_robotattack.dir[1].y = 0.0f;
chr->act_robotattack.dir[1].z = 0.0f;
chrChooseStandAnimation(chr, 16);
}
}
void func0f0429d8(struct chrdata *chr, f32 arg1, f32 arg2)
{
struct prop *prop = chrGetTargetProp(chr);
f32 distance = atan2f(prop->pos.x - chr->prop->pos.x, prop->pos.z - chr->prop->pos.z);
f32 value = model0001afe8(arg2, distance, arg1);
chrSetLookAngle(chr, value);
}
void chrTickRobotAttack(struct chrdata *chr)
{
s32 i;
f32 roty = 0.0f;
f32 rotx = 0.0f;
struct prop *targetprop = chrGetTargetProp(chr);
bool firing;
bool empty;
f32 invtheta = chrGetInverseTheta(chr);
struct act_robotattack *act = &chr->act_robotattack;
func0f0429d8(chr, 0.085f, invtheta);
if (chr->model->filedata->skel != &g_SkelRobot) {
act->finished = true;
return;
}
for (i = 0; i < 2; i++) {
empty = false;
if (act->numshots[i] > 0) {
chr->unk348[i]->unk01 = !(chr->unk348[i]->unk00 % 3);
firing = !(chr->unk348[i]->unk00 % 2);
} else {
chr->unk348[i]->unk01 = 0;
firing = false;
}
act->firing[i] = firing;
if (act->numshots[0] <= 0 && act->numshots[1] <= 0) {
empty = true;
if (ABS(chr->gunroty[0]) < 0.03f
&& ABS(chr->gunrotx[0]) < 0.03f
&& ABS(chr->gunroty[1]) < 0.03f
&& ABS(chr->gunrotx[1]) < 0.03f) {
act->finished = true;
}
}
if (empty);
if ((f32)empty);
if (!empty) {
f32 aimy;
union modelrodata *rodata;
struct coord spe4;
Mtxf spa4;
aimy = targetprop->pos.y - 20.0f;
rodata = modelGetPartRodata(chr->model->filedata, (i ? MODELPART_ROBOT_0000 : MODELPART_ROBOT_0001));
act->pos[i].x = rodata->position.pos.x;
act->pos[i].y = rodata->position.pos.y - 300.0f;
act->pos[i].z = rodata->position.pos.z;
mtx4LoadYRotation(invtheta, &spa4);
mtx4RotateVec(&spa4, &act->pos[i], &spe4);
spe4.x *= chr->model->scale;
spe4.y *= chr->model->scale;
spe4.z *= chr->model->scale;
act->pos[i].x = spe4.x + chr->prop->pos.x;
act->pos[i].y = spe4.y + chr->prop->pos.y;
act->pos[i].z = spe4.z + chr->prop->pos.z;
roty = atan2f(targetprop->pos.x - act->pos[i].x, targetprop->pos.z - act->pos[i].z) - invtheta;
if (roty < 0.0f) {
roty += M_BADTAU;
}
if (roty > M_BADPI) {
roty -= M_BADTAU;
}
if (roty < -0.524f) {
roty = -0.524f;
}
if (roty > 0.524f) {
roty = 0.524f;
}
#define X() (targetprop->pos.x - act->pos[i].x)
#define Z() (targetprop->pos.z - act->pos[i].z)
rotx = M_BADTAU - atan2f(aimy - act->pos[i].y, sqrtf(Z() * Z() + X() * X()));
if (rotx > M_BADPI) {
rotx -= M_BADTAU;
}
if (rotx < -0.524f) {
rotx = -0.524f;
}
if (rotx > 0.524f) {
rotx = 0.524f;
}
}
chr->gunroty[i] += (roty - chr->gunroty[i]) * 0.15f;
chr->gunrotx[i] += (rotx - chr->gunrotx[i]) * 0.15f;
if (!empty) {
if (firing) {
f32 gunrotx = chr->gunrotx[i];
f32 gunroty = chr->gunroty[i];
if (gunrotx < 0.0f) {
gunrotx += M_BADTAU;
}
if (gunroty < 0.0f) {
gunroty += M_BADTAU;
}
gunroty += invtheta;
if (gunroty >= M_BADTAU) {
gunroty -= M_BADTAU;
}
act->dir[i].x = sinf(gunroty) * cosf(gunrotx);
act->dir[i].y = -sinf(gunrotx);
act->dir[i].z = cosf(gunroty) * cosf(gunrotx);
robotSetMuzzleFlash(chr, i, true);
act->numshots[i]--;
}
chr->unk348[i]->unk00++;
}
}
}
void chrTickAttack(struct chrdata *chr)
{
struct model *model = chr->model;
f32 curframe = modelGetCurAnimFrame(model);
if (chr->hidden & CHRHFLAG_NEEDANIM) {
if (modelIsAnimMerging(chr->model)) {
return;
}
modelSetAnimation(model, chr->act_attack.animcfg->animnum, chr->act_attack.flip,
chr->act_attack.animcfg->unk10, chrGetRangedSpeed(chr, 0.5f, 0.8f), 16);
func0f031254(chr);
chr->hidden &= ~CHRHFLAG_NEEDANIM;
}
if (chr->act_attack.animcfg->animnum == ANIM_SNIPING_GETDOWN) {
if (curframe >= modelGetAnimEndFrame(model)) {
chr->act_attack.animcfg = var80067d70;
modelSetAnimation(model, chr->act_attack.animcfg->animnum, chr->act_attack.flip,
chr->act_attack.animcfg->unk10, chrGetRangedSpeed(chr, 0.5f, 0.8f), 16);
}
}
if (!chr->aibot && chr->act_attack.reaim != 0) {
if (chr->act_attack.reaim == 1) {
f32 startframe;
if (chr->act_attack.animcfg->unk24 >= 0) {
startframe = chr->act_attack.animcfg->unk24;
} else {
startframe = chr->act_attack.animcfg->unk1c;
}
modelSetAnimation(model, modelGetAnimNum(model), model->anim->flip, startframe, chrGetRangedSpeed(chr, 0.5f, 0.8f), 16);
if (chr->act_attack.animcfg->unk14 >= 0) {
modelSetAnimEndFrame(model, chr->act_attack.animcfg->unk14);
}
chr->act_attack.reaim = 2;
chrResetAimEndProperties(chr);
return;
}
if (chr->act_attack.reaim == 2) {
if (curframe >= modelGetAnimEndFrame(model)) {
chr->act_attack.flags &= ~ATTACKFLAG_DONTTURN;
if (chr->act_attack.standing) {
chrAttackStand(chr, chr->act_attack.flags, chr->act_attack.entityid);
} else {
chrAttackKneel(chr, chr->act_attack.flags, chr->act_attack.entityid);
}
}
return;
}
}
if (!chr->aibot && (chr->act_attack.flags & ATTACKFLAG_AIMONLY)) {
if (chr->act_attack.flags & ATTACKFLAG_DONTTURN) {
if (!func0f03e9f4(chr, chr->act_attack.animcfg, chr->act_attack.firegun[HAND_LEFT], chr->act_attack.firegun[HAND_RIGHT], 0.2f)) {
chr->act_attack.reaim = 1;
}
return;
}
if (curframe >= modelGetAnimEndFrame(model)) {
chr->act_attack.flags |= ATTACKFLAG_DONTTURN;
chr->act_attack.turning = TURNSTATE_OFF;
return;
}
}
if (!chr->aibot
&& chr->act_attack.dorecoil == 0
&& chr->act_attack.animcfg->unk24 > 0
&& curframe <= chr->act_attack.animcfg->unk24
&& curframe >= modelGetAnimEndFrame(model)) {
modelSetAnimation(model, modelGetAnimNum(model), model->anim->flip,
chr->act_attack.animcfg->unk24, chrGetRangedSpeed(chr, 0.5f, 0.8f), 16);
if (chr->act_attack.dooneburst) {
if (chr->act_attack.animcfg->unk14 >= 0) {
modelSetAnimEndFrame(model, chr->act_attack.animcfg->unk14);
}
} else {
modelSetAnimEndFrame(model, chr->act_attack.animcfg->unk1c);
}
}
chrTickFire(chr);
}
void chrTickAttackRoll(struct chrdata *chr)
{
if (chr->hidden & CHRHFLAG_NEEDANIM) {
if (modelIsAnimMerging(chr->model)) {
return;
}
chrAttackRollChooseAnimation(chr);
chr->hidden &= ~CHRHFLAG_NEEDANIM;
}
if (chr->act_attack.onehanded) {
struct model *model = chr->model;
f32 curframe = modelGetCurAnimFrame(model);
if (chr->act_attack.animcfg == &g_RollAttackAnims[4]
|| chr->act_attack.animcfg == &g_RollAttackAnims[5]
|| chr->act_attack.animcfg == &g_RollAttackAnims[6]
|| chr->act_attack.animcfg == &g_RollAttackAnims[7]) {
if (curframe >= chr->act_attack.animcfg->unk14) {
struct attackanimconfig *newanimcfg = chr->act_attack.animcfg + 4;
bool flip = model->anim->flip;
f32 sp34 = 16;
if (chr->act_attack.firegun[HAND_LEFT] && chr->act_attack.firegun[HAND_RIGHT]) {
if (random() % 2 == 0) {
newanimcfg += 4;
} else {
newanimcfg += 8;
}
}
if (newanimcfg == &g_RollAttackAnims[8]) {
sp34 = 24;
} else if (newanimcfg == &g_RollAttackAnims[9]) {
sp34 = 24;
} else if (newanimcfg == &g_RollAttackAnims[10]) {
sp34 = 32;
} else if (newanimcfg == &g_RollAttackAnims[11]) {
sp34 = 44;
} else if (newanimcfg == &g_RollAttackAnims[12]) {
sp34 = 24;
} else if (newanimcfg == &g_RollAttackAnims[13]) {
sp34 = 34;
} else if (newanimcfg == &g_RollAttackAnims[14]) {
sp34 = 32;
} else if (newanimcfg == &g_RollAttackAnims[15]) {
sp34 = 44;
} else if (newanimcfg == &g_RollAttackAnims[16]) {
sp34 = 24;
} else if (newanimcfg == &g_RollAttackAnims[17]) {
sp34 = 34;
} else if (newanimcfg == &g_RollAttackAnims[18]) {
sp34 = 32;
} else if (newanimcfg == &g_RollAttackAnims[19]) {
sp34 = 44;
}
chr->act_attack.turning = TURNSTATE_OFF;
chr->act_attack.animcfg = newanimcfg;
chr->sleep = 0;
modelSetAnimation(model, newanimcfg->animnum, flip, newanimcfg->unk10,
chrGetRangedSpeed(chr, 0.5f, 0.8f), sp34);
if (chr->act_attack.dorecoil) {
if (newanimcfg->unk24 >= 0.0f) {
modelSetAnimEndFrame(model, newanimcfg->unk24);
} else {
modelSetAnimEndFrame(model, newanimcfg->unk1c);
}
} else {
if (newanimcfg->unk20 >= 0.0f) {
modelSetAnimEndFrame(model, newanimcfg->unk20);
} else {
if (newanimcfg->unk14 >= 0.0f) {
modelSetAnimEndFrame(model, newanimcfg->unk14);
}
}
}
if (chr->act_attack.animcfg->unk0c != 0.0f) {
union modelrwdata *rwdata = modelGetNodeRwData(model, model->filedata->rootnode);
rwdata->chrinfo.unk5c = sp34;
rwdata->chrinfo.unk58 = -chr->act_attack.animcfg->unk0c / sp34;
if (flip) {
rwdata->chrinfo.unk58 = -rwdata->chrinfo.unk58;
}
}
}
} else if (chr->act_attack.animcfg == &g_RollAttackAnims[8]
|| chr->act_attack.animcfg == &g_RollAttackAnims[9]
|| chr->act_attack.animcfg == &g_RollAttackAnims[10]
|| chr->act_attack.animcfg == &g_RollAttackAnims[11]
|| chr->act_attack.animcfg == &g_RollAttackAnims[12]
|| chr->act_attack.animcfg == &g_RollAttackAnims[13]
|| chr->act_attack.animcfg == &g_RollAttackAnims[14]
|| chr->act_attack.animcfg == &g_RollAttackAnims[15]
|| chr->act_attack.animcfg == &g_RollAttackAnims[16]
|| chr->act_attack.animcfg == &g_RollAttackAnims[17]
|| chr->act_attack.animcfg == &g_RollAttackAnims[18]
|| chr->act_attack.animcfg == &g_RollAttackAnims[19]) {
if (!chr->act_attack.dorecoil
&& chr->act_attack.animcfg->unk24 > 0
&& curframe <= chr->act_attack.animcfg->unk24
&& curframe >= modelGetAnimEndFrame(model)) {
modelSetAnimation(model, modelGetAnimNum(model), model->anim->flip,
chr->act_attack.animcfg->unk24, chrGetRangedSpeed(chr, 0.5f, 0.8f), 16);
if (chr->act_attack.dooneburst) {
if (chr->act_attack.animcfg->unk14 >= 0) {
modelSetAnimEndFrame(model, chr->act_attack.animcfg->unk14);
}
} else {
u32 stack;
modelSetAnimEndFrame(model, chr->act_attack.animcfg->unk1c);
}
}
}
}
chrTickFire(chr);
}
void propUnsetDangerous(struct prop *prop)
{
s32 i;
for (i = 0; i < g_NumDangerousProps; i++) {
if (g_DangerousProps[i] == prop) {
g_DangerousProps[i] = g_DangerousProps[g_NumDangerousProps - 1];
g_NumDangerousProps--;
return;
}
}
}
void propSetDangerous(struct prop *prop)
{
s32 i;
if (g_NumDangerousProps < ARRAYCOUNT(g_DangerousProps)) {
g_DangerousProps[g_NumDangerousProps] = prop;
g_NumDangerousProps++;
}
}
void chrTickThrowGrenade(struct chrdata *chr)
{
struct model *model;
f32 frame;
u32 hand;
struct prop *weaponprop;
struct defaultobj *obj;
struct weaponobj *weapon;
f32 frame2;
if (chr->hidden & CHRHFLAG_NEEDANIM) {
if (modelIsAnimMerging(chr->model)) {
return;
}
chrThrowGrenadeChooseAnimation(chr);
chr->hidden &= ~CHRHFLAG_NEEDANIM;
}
model = chr->model;
frame = modelGetCurAnimFrame(model);
hand = model->anim->flip ? 1 : 0;
weaponprop = chrGetHeldProp(chr, hand);
if ((frame >= 20 && weaponprop && modelGetAnimNum(model) == ANIM_THROWGRENADE_STANDING) ||
(frame >= 1 && weaponprop && modelGetAnimNum(model) == ANIM_THROWGRENADE_NOPIN) ||
(frame >= 1 && weaponprop && modelGetAnimNum(model) == ANIM_THROWGRENADE_CROUCHING)) {
obj = weaponprop->obj;
obj->hidden &= ~OBJHFLAG_00000800;
}
if ((frame >= 119 && weaponprop && modelGetAnimNum(model) == ANIM_THROWGRENADE_STANDING) ||
(frame >= 57 && weaponprop && modelGetAnimNum(model) == ANIM_THROWGRENADE_NOPIN) ||
(frame >= 58 && weaponprop && modelGetAnimNum(model) == ANIM_THROWGRENADE_CROUCHING)) {
weapon = weaponprop->weapon;
objSetDropped(weaponprop, DROPTYPE_THROWGRENADE);
chr->hidden |= CHRHFLAG_00000001;
weapon->timer240 = TICKS(240);
}
frame2 = modelGetCurAnimFrame(model);
if (frame2 >= modelGetAnimEndFrame(model)) {
chrStop(chr);
} else {
if ((frame >= 87 && frame <= 110 && modelGetAnimNum(model) == ANIM_THROWGRENADE_STANDING) ||
(frame >= 5 && frame <= 45 && modelGetAnimNum(model) == ANIM_THROWGRENADE_NOPIN) ||
((frame >= 20 && frame <= 45 && modelGetAnimNum(model) == ANIM_THROWGRENADE_CROUCHING))) {
f32 value = chrGetRangedSpeed(chr, 1, 3.2);
chrTurn(chr, 1, 110, value, 0);
}
}
}
bool chrDetectDangerousObject(struct chrdata *chr)
{
s32 i;
for (i = 0; i < g_NumDangerousProps; i++) {
struct prop *prop = g_DangerousProps[i];
if (prop->type == PROPTYPE_EXPLOSION
|| (prop->weapon && prop->weapon->weaponnum == WEAPON_GRENADE && prop->weapon->timer240 < TICKS(480))) {
if (chrGetSquaredDistanceToCoord(chr, &prop->pos) < 1600) {
chr->runfrompos = g_DangerousProps[i]->pos;
return true;
}
}
}
return false;
}
bool func0f043f2c(struct chrdata *chr, struct coord *runpos, u32 arg2, f32 *turnspeed)
{
struct prop *prop = chr->prop;
struct model *model = chr->model;
f32 accel;
f32 maxspeed;
f32 angle1;
f32 finalangle;
f32 angle2;
f32 angle3;
f32 xdiff = runpos->f[0] - prop->pos.f[0];
f32 zdiff = runpos->f[2] - prop->pos.f[2];
bool result;
result = false;
angle1 = atan2f(xdiff, zdiff);
finalangle = func0f03e578(chr);
angle2 = angle1 - finalangle;
if (finalangle > angle1) {
angle2 += M_BADTAU;
}
if (chr->aibot) {
if (chr->blurdrugamount > 0) {
angle1 += chr->blurdrugamount * PALUPF(0.00031410926021636f) * sinf((g_Vars.lvframe60 % TICKS(1200)) * PALUPF(0.0052351541817188f));
if (angle1 >= M_BADTAU) {
angle1 -= M_BADTAU;
}
angle1 += M_BADTAU;
}
finalangle = angle1;
*turnspeed = 0;
result = true;
} else {
angle3 = angle2;
if (angle2 > M_BADPI) {
angle3 = M_BADTAU - angle3;
}
if ((arg2 % 4) == 2) {
maxspeed = 0.29915165901184f;
accel = 0.014957583509386f;
} else if ((arg2 % 4) == 1) {
if (angle3 < 0.3926365673542f) {
maxspeed = 0.019631829112768f;
} else if (angle3 < 1.2564370632172f) {
maxspeed = 0.098159141838551f;
} else {
maxspeed = 0.1963182836771f;
}
accel = 0.014957583509386f;
} else {
if (angle3 < 0.3926365673542f) {
maxspeed = 0.0098159145563841f;
} else if (angle3 < 1.2564370632172f) {
maxspeed = 0.049079570919275f;
} else {
maxspeed = 0.12564370036125f;
}
accel = 0.0098159145563841f;
}
maxspeed *= model->anim->playspeed;
accel *= model->anim->playspeed;
applyRotation(&finalangle, angle1, turnspeed, accel, accel + accel, maxspeed);
if (ABS(finalangle - angle1) < 0.01f) {
*turnspeed = 0;
result = true;
}
}
func0f03e5b0(chr, finalangle);
return result;
}
void chrTickAttackWalk(struct chrdata *chr)
{
struct model *model = chr->model;
struct prop *prop = chr->prop;
struct prop *targetprop = chrGetTargetProp(chr);
s32 i;
f32 xdiff;
f32 zdiff;
if (chr->hidden & CHRHFLAG_NEEDANIM) {
if (modelIsAnimMerging(chr->model)) {
return;
}
chrAttackWalkChooseAnimation(chr);
chr->hidden &= ~CHRHFLAG_NEEDANIM;
}
chr->act_attackwalk.frame60count += g_Vars.lvupdate60;
chr->lastwalk60 = g_Vars.lvframe60;
// If stuck or time exceeded, stop
if (chr->invalidmove == 1
|| chr->lastmoveok60 < g_Vars.lvframe60 - TICKS(60)
|| chr->act_attackwalk.frame60count > chr->act_attackwalk.frame60max) {
if (modelGetCurAnimFrame(model) > modelGetNumAnimFrames(model) * 0.5f) {
modelSetAnimSpeedAuto(model, 0, 16);
} else {
modelSetAnimSpeedAuto(model, modelGetNumAnimFrames(model) * 0.5f, 16);
}
chrRecordLastSeeTargetTime(chr);
chrStop(chr);
return;
}
// If within 3 metres of the player, stop
xdiff = targetprop->pos.x - prop->pos.x;
zdiff = targetprop->pos.z - prop->pos.z;
if (xdiff < 300 && xdiff > -300 && zdiff < 300 && zdiff > -300) {
chrRecordLastSeeTargetTime(chr);
chrStop(chr);
return;
}
if (func0f043f2c(chr, &targetprop->pos, 0, &chr->act_attackwalk.turnspeed)) {
chr->act_attackwalk.facedtarget = true;
}
if (chr->act_attackwalk.frame60count > TICKS(20)) {
func0f03e9f4(chr, chr->act_attackwalk.animcfg,
chr->act_attackwalk.firegun[HAND_LEFT],
chr->act_attackwalk.firegun[HAND_RIGHT], 1);
} else {
chrResetAimEndProperties(chr);
}
if (chr->act_attackwalk.facedtarget && chr->act_attackwalk.frame60count > TICKS(30)) {
for (i = 0; i < 2; i++) {
if (chr->act_attackwalk.firegun[i]) {
if (!chr->act_attackwalk.everytick[i]) {
chrSetHandFiring(chr, i, true);
} else if (chr->act_attackwalk.frame60count > chr->act_attackwalk.nextshot60
&& (i == chr->act_attackwalk.nextgun || chr->act_attackwalk.everytick[chr->act_attackwalk.nextgun] == 0)) {
chr->act_attackwalk.nextshot60 = chr->act_attackwalk.frame60count;
if (chr->act_attackwalk.everytick[1 - i]) {
if (chr->act_attackwalk.singleshot[i]) {
chr->act_attackwalk.nextshot60 += TICKS(90);
} else {
chr->act_attackwalk.nextshot60 += TICKS(20);
}
} else {
if (chr->act_attackwalk.singleshot[i]) {
chr->act_attackwalk.nextshot60 += TICKS(180);
} else {
chr->act_attackwalk.nextshot60 += TICKS(40);
}
}
chr->act_attackwalk.nextgun = 1 - chr->act_attackwalk.nextgun;
chrSetHandFiring(chr, i, true);
} else {
chrSetHandFiring(chr, i, false);
}
} else {
chrSetHandFiring(chr, i, false);
}
}
} else {
chrSetHandFiring(chr, HAND_LEFT, false);
chrSetHandFiring(chr, HAND_RIGHT, false);
}
}
/**
* This function might be misnamed. It was named by isolating it and testing it
* with different inputs to see what it returns, but I couldn't determine how
* the range affects the latter part of the function.
*/
bool posIsMovingTowardsPosOrStoppedInRange(struct coord *prevpos, struct coord *moveddelta, struct coord *targetpos, f32 range)
{
struct coord prevdist;
f32 tmp;
prevdist.x = targetpos->x - prevpos->x;
prevdist.z = targetpos->z - prevpos->z;
if (moveddelta->f[0] == 0 && moveddelta->f[2] == 0) {
return prevdist.f[0] * prevdist.f[0] + prevdist.f[2] * prevdist.f[2] <= range * range;
}
tmp = moveddelta->f[0] * prevdist.f[0] + moveddelta->f[2] * prevdist.f[2];
if (tmp > 0) {
f32 sqmoveddist = moveddelta->f[0] * moveddelta->f[0] + moveddelta->f[2] * moveddelta->f[2];
f32 sqprevdist = prevdist.f[0] * prevdist.f[0] + prevdist.f[2] * prevdist.f[2];
if ((sqprevdist - range * range) * sqmoveddist <= tmp * tmp) {
return true;
}
return false;
}
return false;
}
/**
* Return true if:
* - either prevpos or curpos is within the given range of targetpos, and
* - the prev -> cur delta is moving towards the targetpos or is stopped inside
* the range.
*
* This is a lateral check, meaning the Y value is not considered.
*/
bool posIsArrivingLaterallyAtPos(struct coord *prevpos, struct coord *curpos, struct coord *targetpos, f32 range)
{
struct coord moveddelta;
if (prevpos->x <= targetpos->x - range && curpos->x <= targetpos->x - range) {
return false;
}
if (prevpos->x >= targetpos->x + range && curpos->x >= targetpos->x + range) {
return false;
}
if (prevpos->z <= targetpos->z - range && curpos->z <= targetpos->z - range) {
return false;
}
if (prevpos->z >= targetpos->z + range && curpos->z >= targetpos->z + range) {
return false;
}
moveddelta.x = curpos->x - prevpos->x;
moveddelta.y = 0;
moveddelta.z = curpos->z - prevpos->z;
return posIsMovingTowardsPosOrStoppedInRange(prevpos, &moveddelta, targetpos, range);
}
/**
* Return true if:
* - either prevpos or curpos is within the given range of targetpos,
* - the prev -> cur delta is moving towards the targetpos or is stopped inside
* the range, and
* - either prevpos or curpos is within 150cm vertically of targetpos.
*/
bool posIsArrivingAtPos(struct coord *prevpos, struct coord *curpos, struct coord *targetpos, f32 range)
{
if (prevpos->y <= targetpos->y - 150 && curpos->y <= targetpos->y - 150) {
return false;
}
if (prevpos->y >= targetpos->y + 150 && curpos->y >= targetpos->y + 150) {
return false;
}
return posIsArrivingLaterallyAtPos(prevpos, curpos, targetpos, range);
}
void chrTickRunPos(struct chrdata *chr)
{
struct prop *prop = chr->prop;
struct model *model = chr->model;
u32 race = CHRRACE(chr);
f32 fVar6;
f32 zero;
f32 fVar7;
if (chr->hidden & CHRHFLAG_NEEDANIM) {
if (modelIsAnimMerging(chr->model)) {
return;
}
chrRunPosChooseAnimation(chr);
chr->hidden &= ~CHRHFLAG_NEEDANIM;
}
chr->lastwalk60 = g_Vars.lvframe60;
if (chr->invalidmove == 1
|| g_Vars.lvframe60 - TICKS(60) > chr->lastmoveok60
|| posIsArrivingLaterallyAtPos(&chr->prevpos, &prop->pos, &chr->act_runpos.pos, chr->act_runpos.neardist)) {
if (race == RACE_HUMAN) {
modelGetAnimNum(model);
}
zero = 0;
fVar7 = modelGetCurAnimFrame(model);
fVar6 = fVar7 - zero;
if (fVar7 < 0) {
fVar6 += modelGetNumAnimFrames(model);
}
if (modelGetNumAnimFrames(model) * 0.5f < fVar6) {
zero = 0;
modelSetAnimSpeedAuto(model, modelGetNumAnimFrames(model) - zero, 16);
} else {
zero = 0;
fVar7 = modelGetNumAnimFrames(model) * 0.5f;
fVar6 = fVar7 - zero;
if (fVar7 < 0) {
fVar6 += modelGetNumAnimFrames(model);
}
modelSetAnimSpeedAuto(model, fVar6, 16);
}
chrStop(chr);
return;
}
func0f043f2c(chr, &chr->act_runpos.pos, 1, &chr->act_runpos.turnspeed);
if (chr->act_runpos.eta60 > 0) {
chr->act_runpos.eta60 -= g_Vars.lvupdate60;
} else {
fVar7 = 1;
if (race == RACE_HUMAN) {
if (modelGetAnimNum(model) == ANIM_RUNNING_ONEHANDGUN) {
fVar7 = func0f02dff0(ANIM_RUNNING_ONEHANDGUN);
} else {
fVar7 = func0f02dff0(ANIM_RUNNING_TWOHANDGUN);
}
} else if (race == RACE_SKEDAR) {
fVar7 = func0f02dff0(ANIM_SKEDAR_RUNNING);
}
chr->act_runpos.neardist += fVar7 * g_Vars.lvupdate60freal * modelGetAbsAnimSpeed(model);
}
}
void func0f044b68(struct coord *arg0, struct coord *arg1, struct coord *arg2)
{
struct coord sp0c;
struct coord sp00;
sp0c.x = arg1->x - arg0->x;
sp0c.y = arg1->y - arg0->y;
sp0c.z = arg1->z - arg0->z;
sp00.x = -arg2->z;
sp00.y = 0;
sp00.z = arg2->x;
if (sp00.f[0] * sp0c.f[0] + sp00.f[2] * sp0c.f[2] > 0) {
// empty
} else {
sp0c = *arg0;
*arg0 = *arg1;
*arg1 = sp0c;
}
}
/**
* Check to see if the chr can see the next pos in their route.
*
* If an obstacle is found, set the leftpos and rightpos coordinates to the
* left and right corners of the object from the chr's perspective and return
* false.
*
* Return true if path ahead is clear.
*
* This is similar to chrNavCheckForObstacle. The difference between the two are
* not yet understood.
*/
bool chrNavCanSeeNextPos(struct chrdata *chr, struct coord *chrpos, s16 *chrrooms, struct coord *aimpos, struct coord *leftpos, struct coord *rightpos, f32 negchrradius, f32 chrradius, s32 cdtypes, s32 arg9)
{
struct coord spd4;
f32 spd0;
f32 spcc;
f32 spc8;
f32 spc4;
f32 norm;
bool spbc = false;
bool spb8 = false;
struct coord spac;
struct coord spa0;
struct coord sp94;
struct coord sp88;
bool result = false;
f32 ymax;
f32 ymin;
f32 radius2;
struct coord sp6c;
struct coord sp60;
s16 sp50[8];
s16 sp40[8];
struct prop *prop = chr->prop;
chrGetBbox(prop, &radius2, &ymax, &ymin);
spd4.x = aimpos->x - chrpos->x;
spd4.y = 0.0f;
spd4.z = aimpos->z - chrpos->z;
if (spd4.f[0] == 0.0f && spd4.f[2] == 0.0f) {
return true;
}
norm = 1.0f / sqrtf(spd4.f[0] * spd4.f[0] + spd4.f[2] * spd4.f[2]);
spd4.x *= norm;
spd4.z *= norm;
spd0 = spd4.x * chrradius * 0.95f;
spcc = spd4.z * chrradius * 0.95f;
spc8 = spd4.x * chrradius * 1.2f;
spc4 = spd4.z * chrradius * 1.2f;
chrSetPerimEnabled(chr, false);
sp6c.x = chrpos->x + spcc;
sp6c.y = chrpos->y;
sp6c.z = chrpos->z - spd0;
sp60.x = (spd4.x * negchrradius) + (aimpos->x + spc4);
sp60.y = aimpos->y;
sp60.z = (spd4.z * negchrradius) + (aimpos->z - spc8);
if (cdExamCylMove07(chrpos, chrrooms, &sp6c, sp50, cdtypes, 1, ymax - prop->pos.y, ymin - prop->pos.y) == CDRESULT_COLLISION
|| cdExamCylMove03(&sp6c, sp50, &sp60, cdtypes, 1, ymax - prop->pos.y, ymin - prop->pos.y) == CDRESULT_COLLISION) {
spbc = true;
cdGetEdge(&spac, &spa0, 14145, "chraction.c");
func0f044b68(&spac, &spa0, &spd4);
}
sp6c.x = chrpos->x - spcc;
sp6c.y = chrpos->y;
sp6c.z = chrpos->z + spd0;
sp60.x = (spd4.x * negchrradius) + (aimpos->x - spc4);
sp60.y = aimpos->y;
sp60.z = (spd4.z * negchrradius) + (aimpos->z + spc8);
if (cdExamCylMove07(chrpos, chrrooms, &sp6c, sp50, cdtypes, 1, ymax - prop->pos.y, ymin - prop->pos.y) == CDRESULT_COLLISION
|| cdExamCylMove03(&sp6c, chrrooms, &sp60, cdtypes, 1, ymax - prop->pos.y, ymin - prop->pos.y) == CDRESULT_COLLISION) {
spb8 = true;
cdGetEdge(&sp94, &sp88, 14160, "chraction.c");
func0f044b68(&sp94, &sp88, &spd4);
}
if (spbc && spb8) {
func0f044b68(&spac, &sp94, &spd4);
func0f044b68(&spa0, &sp88, &spd4);
*leftpos = spac;
*rightpos = sp88;
} else if (spbc) {
*leftpos = spac;
*rightpos = spa0;
} else if (spb8) {
*leftpos = sp94;
*rightpos = sp88;
} else if (cdExamCylMove07(chrpos, chrrooms, aimpos, sp40, cdtypes, 1, ymax - prop->pos.y, ymin - prop->pos.y) != CDRESULT_COLLISION
&& (!arg9 || cdExamCylMove01(chrpos, aimpos, chrradius, sp40, cdtypes, CHECKVERTICAL_YES, ymax - prop->pos.y, ymin - prop->pos.y) != CDRESULT_COLLISION)) {
result = true;
} else {
cdGetEdge(leftpos, rightpos, 14230, "chraction.c");
func0f044b68(leftpos, rightpos, &spd4);
}
chrSetPerimEnabled(chr, true);
return result;
}
/**
* Check if the path ahead contains an obstacle that the chr should route
* around.
*
* If an obstacle is found, set the leftpos and rightpos coordinates to the
* left and right corners of the object from the chr's perspective and return
* false.
*
* Return true if path ahead is clear.
*
* This is similar to chrNavCanSeeNextPos. The only difference is this one uses
* the value1 and value2 variables.
*/
bool chrNavCheckForObstacle(struct chrdata *chr, struct coord *chrpos, s16 *chrrooms, struct coord *aimpos, struct coord *leftpos, struct coord *rightpos, f32 negchrradius, f32 chrradius, s32 cdtypes, bool hasobstacle)
{
struct coord spd4;
f32 spd0;
f32 spcc;
f32 spc8;
f32 spc4;
f32 norm;
bool spbc = false;
bool spb8 = false;
struct coord spac;
struct coord spa0;
struct coord sp94;
struct coord sp88;
bool result = false;
f32 value1;
f32 value2;
f32 ymax;
f32 ymin;
f32 radius2;
struct coord sp6c;
struct coord sp60;
s16 sp50[8];
s16 sp40[8];
struct prop *prop = chr->prop;
chrGetBbox(prop, &radius2, &ymax, &ymin);
spd4.x = aimpos->x - chrpos->x;
spd4.y = 0.0f;
spd4.z = aimpos->z - chrpos->z;
if (spd4.f[0] == 0.0f && spd4.f[2] == 0.0f) {
return true;
}
norm = 1.0f / sqrtf(spd4.f[0] * spd4.f[0] + spd4.f[2] * spd4.f[2]);
spd4.x *= norm;
spd4.z *= norm;
spd0 = spd4.x * chrradius * 0.95f;
spcc = spd4.z * chrradius * 0.95f;
spc8 = spd4.x * chrradius * 1.2f;
spc4 = spd4.z * chrradius * 1.2f;
chrSetPerimEnabled(chr, false);
sp6c.x = chrpos->x + spcc;
sp6c.y = chrpos->y;
sp6c.z = chrpos->z - spd0;
sp60.x = (spd4.x * negchrradius) + (aimpos->x + spc4);
sp60.y = aimpos->y;
sp60.z = (spd4.z * negchrradius) + (aimpos->z - spc8);
if (cdExamCylMove07(chrpos, chrrooms, &sp6c, sp50, cdtypes, 1, ymax - prop->pos.y, ymin - prop->pos.y) == CDRESULT_COLLISION
|| cdExamCylMove03(&sp6c, sp50, &sp60, cdtypes, 1, ymax - prop->pos.y, ymin - prop->pos.y) == CDRESULT_COLLISION) {
spbc = true;
cdGetEdge(&spac, &spa0, 14310, "chraction.c");
func0f044b68(&spac, &spa0, &spd4);
value1 = cd00024e40();
}
sp6c.x = chrpos->x - spcc;
sp6c.y = chrpos->y;
sp6c.z = chrpos->z + spd0;
sp60.x = (spd4.x * negchrradius) + (aimpos->x - spc4);
sp60.y = aimpos->y;
sp60.z = (spd4.z * negchrradius) + (aimpos->z + spc8);
if (cdExamCylMove07(chrpos, chrrooms, &sp6c, sp50, cdtypes, 1, ymax - prop->pos.y, ymin - prop->pos.y) == CDRESULT_COLLISION
|| cdExamCylMove03(&sp6c, chrrooms, &sp60, cdtypes, 1, ymax - prop->pos.y, ymin - prop->pos.y) == CDRESULT_COLLISION) {
spb8 = true;
cdGetEdge(&sp94, &sp88, 14325, "chraction.c");
func0f044b68(&sp94, &sp88, &spd4);
value2 = cd00024e40();
}
if (spbc && spb8) {
if (value1 < value2) {
*leftpos = spac;
*rightpos = spa0;
} else {
*leftpos = sp94;
*rightpos = sp88;
}
} else if (spbc) {
*leftpos = spac;
*rightpos = spa0;
} else if (spb8) {
*leftpos = sp94;
*rightpos = sp88;
} else if (cdExamCylMove07(chrpos, chrrooms, aimpos, sp40, cdtypes, 1, ymax - prop->pos.y, ymin - prop->pos.y) != CDRESULT_COLLISION
&& (!hasobstacle || cdExamCylMove01(chrpos, aimpos, chrradius, sp40, cdtypes, CHECKVERTICAL_YES, ymax - prop->pos.y, ymin - prop->pos.y) != CDRESULT_COLLISION)) {
result = true;
} else {
cdGetEdge(leftpos, rightpos, 14395, "chraction.c");
func0f044b68(leftpos, rightpos, &spd4);
}
chrSetPerimEnabled(chr, true);
return result;
}
bool chrNavTryObstacle(struct chrdata *chr, struct coord *arg1, bool arg2, struct coord *arg3, f32 radius, bool arg5, struct coord *nextpos, struct waydata *waydata, f32 arg8, s32 cdtypes, s32 arg10)
{
struct prop *prop = chr->prop;
struct coord sp68;
struct coord sp5c;
f32 norm;
f32 angle;
struct coord sp48;
struct coord *sp44;
struct coord *sp40;
if (arg2) {
sp44 = arg1;
sp40 = arg3;
} else {
sp44 = arg3;
sp40 = arg1;
}
sp68.x = arg1->x - prop->pos.x;
sp68.y = 0.0f;
sp68.z = arg1->z - prop->pos.z;
if (sp68.f[0] != 0.0f || sp68.f[2] != 0.0f) {
norm = sqrtf(sp68.f[0] * sp68.f[0] + sp68.f[2] * sp68.f[2]);
if (norm > 0.0f) {
norm = 1.0f / norm;
sp68.x *= radius * norm;
sp68.z *= radius * norm;
} else {
norm = 1.0f;
sp68.z = radius * norm;
}
} else {
norm = 1.0f;
sp68.z = radius * norm;
}
if (radius * norm > 1.0f) {
angle = 0.7852731347084f;
} else {
angle = acosf(radius * norm);
}
if (!arg2 && angle != 0.0f) {
angle = M_BADTAU - angle;
}
sp48.x = -cosf(angle) * sp68.f[0] + sinf(angle) * sp68.f[2];
sp48.y = 0.0f;
sp48.z = -sinf(angle) * sp68.f[0] - cosf(angle) * sp68.f[2];
sp5c.x = arg1->x + sp48.f[0];
sp5c.y = arg1->y;
sp5c.z = arg1->z + sp48.f[2];
if (chrNavCanSeeNextPos(chr, &prop->pos, prop->rooms, &sp5c, sp44, sp40, arg8, chr->radius, cdtypes, 1)) {
if (!arg5 || func0f03645c(chr, &prop->pos, prop->rooms, &sp5c, nextpos, cdtypes)) {
if (arg10) {
waydata->gotaimposobj = true;
waydata->aimposobj = sp5c;
} else {
waydata->gotaimpos = true;
waydata->aimpos = sp5c;
}
return true;
}
}
return false;
}
/**
* Check for doors in front of the chr and open them if possible.
*
* I'm guessing the coord argument is a position in front of the chr, and the
* collision check is looking for doors between the chr and that point.
*
* - Multiplayer simulants can open any doors
* - Solo chrs cannot open doors with OBJFLAG2_AICANNOTUSE
* - In ntsc-beta, solo chrs can bring down lasers even if they have that flag
*
* The chr must be within 200cm of the door unless it's a laser.
*/
struct prop *chrOpenDoor(struct chrdata *chr, struct coord *rangepos)
{
struct prop *doorprop = NULL;
if (cdExamCylMove03(&chr->prop->pos, chr->prop->rooms, rangepos,
CDTYPE_BG | CDTYPE_CLOSEDDOORS | CDTYPE_AJARDOORS,
1, 0, 0) == CDRESULT_COLLISION) {
doorprop = cdGetObstacleProp();
}
if (doorprop) {
struct doorobj *door = doorprop->door;
if (chr->aibot || (door->base.flags2 & OBJFLAG2_AICANNOTUSE) == 0) {
f32 xdiff = doorprop->pos.x - chr->prop->pos.x;
f32 zdiff = doorprop->pos.z - chr->prop->pos.z;
if (xdiff * xdiff + zdiff * zdiff < 200 * 200 || (door->doorflags & DOORFLAG_DAMAGEONCONTACT)) {
chrGoPosClearRestartTtl(chr);
doorsChooseSwingDirection(chr->prop, doorprop->door);
if (!doorCallLift(doorprop, false)) {
doorsRequestMode(doorprop->door, DOORMODE_OPENING);
}
} else {
doorprop = NULL;
}
} else {
doorprop = NULL;
}
}
return doorprop;
}
/**
* Tick a chr's navigation process when they are using the "main" method of
* navigating.
*
* Main refers to the chr being on screen or close to on screen, where all
* collision checks are enabled. This is opposed to the magic method, where
* they are off screen and the collision checks are skipped.
*
* The chr will generally move towards the nextpos, which is the position of the
* next pad/corner in the route, and open doors along the way. However they must
* also check for obstacles ahead of them such as other chrs or doors which have
* opened into their path. If one is found, the chr routes around the obstacle
* and then to the next pos again.
*/
void chrNavTickMain(struct chrdata *chr, struct coord *nextpos, struct waydata *waydata, bool arg3)
{
struct prop *prop = chr->prop;
struct coord sp100;
struct coord spf4;
s32 i;
s32 cdtypes;
// By default, do collision checks for everything except doors that AI can
// open, but if they're blocking a door then include all doors
cdtypes = (CDTYPE_ALL & ~CDTYPE_DOORS) | CDTYPE_DOORSLOCKEDTOAI;
if (chr->hidden & CHRHFLAG_BLOCKINGDOOR) {
cdtypes = CDTYPE_ALL;
}
for (i = 0; i < 1; i++) {
if (0.0f);
if (waydata->mode == WAYMODE_INIT || waydata->mode == WAYMODE_RETRY) {
sp100 = *nextpos;
// Check to see if the chr can see the next pad. This is almost
// always true, but if the chr has tried to avoid an object they
// may have gone behind a wall and can't see the pad any more.
if (chrNavCanSeeNextPos(chr, &prop->pos, prop->rooms, &sp100, &waydata->obstacleleft, &waydata->obstacleright, -chr->radius, chr->radius, CDTYPE_PATHBLOCKER | CDTYPE_BG, arg3)) {
// Can see the next pad
waydata->gotaimpos = true;
waydata->aimpos = sp100;
waydata->mode = WAYMODE_HAVEAIMPOS;
} else if (waydata->mode == WAYMODE_INIT) {
waydata->mode = WAYMODE_LOST1;
waydata->iter = 0;
} else if (waydata->mode == WAYMODE_RETRY) {
waydata->mode = WAYMODE_LOST2;
waydata->iter = 0;
}
} else if (waydata->mode == WAYMODE_LOST1) {
// Chr has tried to avoid an obstacle, and in the process can no
// longer see the next pad. The chr will try to get back on the
// route by navigating to either side of the obstacle they were
// trying to avoid.
f32 wantclearance = chr->radius * 1.26f;
if (chrNavTryObstacle(chr, &waydata->obstacleleft, true, &spf4, wantclearance, true, nextpos, waydata, 0, CDTYPE_PATHBLOCKER | CDTYPE_BG, 0)) {
// Will go to left side
waydata->mode = WAYMODE_HAVEAIMPOS;
} else if (chrNavTryObstacle(chr, &waydata->obstacleright, false, &spf4, wantclearance, true, nextpos, waydata, 0, CDTYPE_PATHBLOCKER | CDTYPE_BG, 0)) {
// Will go to right side
waydata->mode = WAYMODE_HAVEAIMPOS;
} else {
// Can't see the obstacle either!
// Remain in LOST1 for 5 iterations to see if line of sight
// comes back. If not, retry the next pad again.
waydata->iter++;
if (waydata->iter > 5) {
waydata->mode = WAYMODE_RETRY;
}
}
} else if (waydata->mode == WAYMODE_LOST2) {
// Chr has gone through LOST1 without seeing the obstacle, then
// tried to find the next pad but can't see that either. Try
// navigating to the obstacle again, but with different arguments.
// This is a more desparate attempt at returning to the path.
f32 wantclearance = chr->radius * 1.26f;
u32 stack;
if (chrNavTryObstacle(chr, &waydata->obstacleleft, true, &spf4, wantclearance, false, NULL, waydata, 0, CDTYPE_PATHBLOCKER | CDTYPE_BG, 0)) {
// Will go to left side
waydata->mode = WAYMODE_HAVEAIMPOS;
} else if (chrNavTryObstacle(chr, &waydata->obstacleright, false, &spf4, wantclearance, false, NULL, waydata, 0, CDTYPE_PATHBLOCKER | CDTYPE_BG, 0)) {
// Will go to right side
waydata->mode = WAYMODE_HAVEAIMPOS;
} else {
// Still can't see the obstacle using the desparate check.
// Remain in LOST2 for 5 iterations to see if line of sight
// comes back.
waydata->iter++;
if (waydata->iter > 5) {
// Clear any references to the obstacle we were trying to
// avoid and attempt to restart the process. I don't think
// this is likely to work, but there's nothing else that can
// be done.
waydata->gotaimposobj = waydata->gotaimpos = false;
waydata->aimposobj.x = waydata->aimpos.x = nextpos->x;
waydata->aimposobj.y = waydata->aimpos.y = nextpos->y;
waydata->aimposobj.z = waydata->aimpos.z = nextpos->z;
waydata->mode = WAYMODE_INIT;
}
}
} else if (waydata->mode == WAYMODE_HAVEAIMPOS) {
// Chr is happily walking towards a position (aim pos).
// The position could be the next pad, or it could be an arbitrary
// position next to an obstacle that has to be routed around.
// Either way, check that there's no new obstacles between the chr
// and their aim pos.
bool hasobstacle = true;
if (!arg3
&& nextpos->x == waydata->aimpos.x
&& nextpos->y == waydata->aimpos.y
&& nextpos->z == waydata->aimpos.z) {
hasobstacle = false;
}
if (chrNavCheckForObstacle(chr, &prop->pos, prop->rooms, &waydata->aimpos, &waydata->obstacleleft, &waydata->obstacleright, -chr->radius, chr->radius, cdtypes, hasobstacle)) {
// No obstacle ahead
waydata->gotaimposobj = true;
waydata->mode = WAYMODE_INIT;
waydata->aimposobj = waydata->aimpos;
} else {
// Obstacle ahead
waydata->mode = WAYMODE_NEWOBSTACLE;
waydata->iter = 0;
}
} else if (waydata->mode == WAYMODE_NEWOBSTACLE) {
// Chr has just noticed an obstacle ahead of them which they need to
// route around. The obstacleleft and obstacleright coordinates are
// already populated and they indicate the left and right corners of
// the object. This part of the code decides which side to use and
// calculates some turning angles.
f32 f20;
u32 stack;
f32 spd0;
f32 spcc;
u32 stack2;
f32 f24 = chr->radius * 1.26f;
f20 = atan2f(waydata->aimpos.x - prop->pos.x, waydata->aimpos.z - prop->pos.z);
spd0 = f20 - atan2f(waydata->obstacleleft.x - prop->pos.x, waydata->obstacleleft.z - prop->pos.z);
spcc = f20 - atan2f(waydata->obstacleright.x - prop->pos.x, waydata->obstacleright.z - prop->pos.z);
if (spd0 < 0) {
spd0 += M_BADTAU;
}
if (spd0 >= M_BADPI) {
spd0 -= M_BADTAU;
}
if (spd0 < 0) {
spd0 = -spd0;
}
if (spcc < 0) {
spcc += M_BADTAU;
}
if (spcc >= M_BADPI) {
spcc -= M_BADTAU;
}
if (spcc < 0) {
spcc = -spcc;
}
if (spd0 < spcc) {
f32 spc0;
f32 spbc;
u32 stack;
f32 f22 = f24 * 1.1f;
if (chrNavTryObstacle(chr, &waydata->obstacleleft, true, &spf4, f24, false, NULL, waydata, f22, cdtypes, 1)) {
waydata->mode = WAYMODE_INIT;
break;
}
spc0 = f20 - atan2f(waydata->obstacleleft.x - prop->pos.x, waydata->obstacleleft.z - prop->pos.z);
spbc = f20 - atan2f(spf4.x - prop->pos.x, spf4.z - prop->pos.z);
if (spc0 < 0) {
spc0 += M_BADTAU;
}
if (spc0 >= M_BADPI) {
spc0 -= M_BADTAU;
}
if (spc0 < 0) {
spc0 = -spc0;
}
if (spbc < 0) {
spbc += M_BADTAU;
}
if (spbc >= M_BADPI) {
spbc -= M_BADTAU;
}
if (spbc < 0) {
spbc = -spbc;
}
if (spbc < spc0) {
if (chrNavTryObstacle(chr, &spf4, false, &spf4, f24, false, NULL, waydata, f22, cdtypes, 1)) {
waydata->mode = WAYMODE_INIT;
break;
}
}
} else {
f32 spb0;
f32 spac;
f32 f22 = f24 * 1.1f;
if (chrNavTryObstacle(chr, &waydata->obstacleright, false, &spf4, f24, false, NULL, waydata, f22, cdtypes, 1)) {
waydata->mode = WAYMODE_INIT;
break;
}
spb0 = f20 - atan2f(waydata->obstacleright.x - prop->pos.x, waydata->obstacleright.z - prop->pos.z);
spac = f20 - atan2f(spf4.x - prop->pos.x, spf4.z - prop->pos.z);
if (spb0 < 0) {
spb0 += M_BADTAU;
}
if (spb0 >= M_BADPI) {
spb0 -= M_BADTAU;
}
if (spb0 < 0) {
spb0 = -spb0;
}
if (spac < 0) {
spac += M_BADTAU;
}
if (spac >= M_BADPI) {
spac -= M_BADTAU;
}
if (spac < 0) {
spac = -spac;
}
if (spac < spb0) {
if (chrNavTryObstacle(chr, &spf4, true, &spf4, f24, false, NULL, waydata, f22, cdtypes, 1)) {
waydata->mode = WAYMODE_INIT;
break;
}
}
}
waydata->iter++;
if (waydata->iter > 5) {
waydata->gotaimposobj = false;
waydata->mode = WAYMODE_INIT;
}
}
}
if (!waydata->gotaimposobj) {
waydata->aimposobj = waydata->aimpos;
}
// Every 10 ticks, attempt to open any door in front of the chr
if (waydata->age % 10 == 0) {
struct prop *doorprop = chrOpenDoor(chr, &waydata->aimposobj);
// Consider returning to stand animation while door is opening
if (doorprop
&& chr->aibot == NULL
&& (chr->hidden & CHRHFLAG_BLOCKINGDOOR) == 0
&& !chrGoPosIsWaiting(chr)) {
chrChooseStandAnimation(chr, 16);
chr->lastmoveok60 = g_Vars.lvframe60;
}
// Resume moving if there's no longer a door in the way
// or if the chr is blocking the door
if (!doorprop || (chr->hidden & CHRHFLAG_BLOCKINGDOOR)) {
if (chr->aibot == NULL
&& chrGoPosIsWaiting(chr)
&& chr->liftaction != LIFTACTION_WAITINGFORLIFT
&& chr->liftaction != LIFTACTION_WAITINGONLIFT) {
if (chr->actiontype == ACT_PATROL) {
chrPatrolChooseAnimation(chr);
} else {
chrGoPosChooseAnimation(chr);
}
}
if (!doorprop) {
chr->hidden &= ~CHRHFLAG_BLOCKINGDOOR;
}
}
}
// Handle turning and anim speed
if (chr->actiontype == ACT_PATROL) {
func0f043f2c(chr, &waydata->aimposobj, 0, &chr->act_patrol.turnspeed);
} else {
chr->unk32c_21 = chr->act_gopos.turnspeed != 0;
func0f043f2c(chr, &waydata->aimposobj, chr->act_gopos.flags, &chr->act_gopos.turnspeed);
if (chr->aibot == NULL && !chrGoPosIsWaiting(chr)) {
if ((chr->act_gopos.flags & GOPOSMASK_SPEED) == GOPOSFLAG_RUN) {
if (chr->act_gopos.turnspeed) {
if (!chr->unk32c_21) {
modelSetAnimSpeed(chr->model, 0.25f, 8);
}
} else {
if (chr->unk32c_21) {
if (chr->chrflags & CHRCFLAG_RUNFASTER) {
modelSetAnimSpeed(chr->model, 0.65f, 32);
} else {
modelSetAnimSpeed(chr->model, 0.5f, 32);
}
}
}
} else if ((chr->act_gopos.flags & GOPOSMASK_SPEED) == GOPOSFLAG_JOG) {
if (chr->act_gopos.turnspeed) {
modelSetAnimSpeed(chr->model, 0.4f, 0);
} else {
modelSetAnimSpeed(chr->model, 0.5f, 0);
}
}
}
}
}
bool chrGoPosUpdateLiftAction(struct chrdata *chr, u32 curpadflags, bool arg2, bool arrivingatlift, s16 curpadnum, s32 nextpadnum)
{
bool advance = false;
struct pad *nextpad = NULL;
u32 nextpadflags = 0;
f32 nextground;
f32 lifty;
struct liftobj *lift;
struct prop *liftprop = liftFindByPad(curpadnum);
u32 stack;
if (!liftprop) {
return false;
}
lift = (struct liftobj *) liftprop->obj;
lifty = liftGetY(lift);
if (nextpadnum >= 0) {
nextpad = &g_Pads[nextpadnum];
nextpadflags = nextpad->flags;
}
if (curpadflags & PADFLAG_AIWAITLIFT) {
if (nextpadflags & PADFLAG_AIONLIFT) {
if (arrivingatlift || chr->liftaction == LIFTACTION_WAITINGFORLIFT) {
if (nextpadflags);
// Begin entering lift if lift is under 40cm above this level
advance = (lifty <= chr->manground + 40);
// ...and (if solo mode) lift is over 1m under this level
// (this logic allows MP simulants to drop down onto lifts)
if (!g_Vars.normmplayerisrunning && advance) {
advance = (lifty > chr->manground - 100);
}
// ...and if the lift has a door, is at least halfway open
if (advance && lift->doors[lift->levelcur] && lift->doors[lift->levelcur]->frac < 0.5f) {
advance = false;
}
}
if (!advance) {
if (arrivingatlift && chr->liftaction != LIFTACTION_WAITINGFORLIFT) {
// Just arrived at lift
chr->liftaction = LIFTACTION_WAITINGFORLIFT;
chrChooseStandAnimation(chr, 16);
if (nextpad) {
// Call the lift
chrOpenDoor(chr, &nextpad->pos);
}
}
} else {
// Enter lift
chr->liftaction = LIFTACTION_NOTUSINGLIFT;
if (chrGoPosIsWaiting(chr)) {
if (chr->actiontype == ACT_PATROL) {
chrPatrolChooseAnimation(chr);
} else {
chrGoPosChooseAnimation(chr);
}
}
}
} else {
// On a wait lift pad, but the next pad in the route is not on the
// lift, so the chr is running past the lift without using it.
if (arrivingatlift) {
advance = true;
chr->liftaction = LIFTACTION_NOTUSINGLIFT;
}
}
} else if (curpadflags & PADFLAG_AIONLIFT) {
if (nextpadflags & PADFLAG_AIWAITLIFT) {
// Waiting for door to close or lift to arrive at destination
if (arg2 || chr->liftaction == LIFTACTION_WAITINGONLIFT) {
// Continue waiting
s16 rooms[] = {0, -1};
u32 stack2;
rooms[0] = nextpad->room;
nextground = cdFindFloorYColourTypeAtPos(&nextpad->pos, rooms, NULL, NULL);
// Begin exiting lift if lift is 30cm under destination or higher
advance = (lifty >= nextground - 30);
// ...and (if solo) lift is under 1m above destination
if (!g_Vars.normmplayerisrunning && advance) {
advance = (lifty < nextground + 100);
}
// ...and if the lift has a door, is at least halfway open
if (advance && lift->doors[lift->levelcur] && lift->doors[lift->levelcur]->frac < 0.5f) {
advance = false;
}
}
if (!advance) {
if (arg2 && chr->liftaction != LIFTACTION_WAITINGONLIFT) {
// Just arrived inside lift
chr->liftaction = LIFTACTION_WAITINGONLIFT;
chrChooseStandAnimation(chr, 16);
}
} else {
// Start disembarking
chr->liftaction = LIFTACTION_ONLIFT;
if (chrGoPosIsWaiting(chr)) {
if (chr->actiontype == ACT_PATROL) {
chrPatrolChooseAnimation(chr);
} else {
chrGoPosChooseAnimation(chr);
}
}
}
} else {
// Not our stop? Not sure why advance is true here. I guess the chr
// can't go anywhere anyway because the next pad is above or below
// them.
if (arg2) {
advance = true;
chr->liftaction = LIFTACTION_ONLIFT;
}
}
}
return advance;
}
s16 chrGoPosGetNextPadNum(struct chrdata *chr)
{
if (chr->act_gopos.waypoints[chr->act_gopos.curindex + 1]) {
return chr->act_gopos.waypoints[chr->act_gopos.curindex + 1]->padnum;
}
return -1;
}
void chrTickGoPos(struct chrdata *chr)
{
struct waypoint *waypoint;
struct coord nextpos;
s16 nextrooms[8];
struct prop *prop = chr->prop;
bool enteringmagic = false;
struct pad *pad;
bool sp240 = true;
struct coord curwppos;
s16 curwprooms[8];
u32 curwpflags;
chr->act_gopos.flags &= ~(GOPOSFLAG_DUCK | GOPOSFLAG_WALKDIRECT);
if (chr->hidden & CHRHFLAG_NEEDANIM) {
if (modelIsAnimMerging(chr->model)) {
return;
}
chrGoPosChooseAnimation(chr);
chr->hidden &= ~CHRHFLAG_NEEDANIM;
}
chr->act_gopos.waydata.age++;
// If stuck for 1 second
if (chr->lastmoveok60 < g_Vars.lvframe60 - TICKS(60)) {
if (chr->goposforce >= 0) {
// Try and warp the chr past whatever obstacle is blocking them?
struct coord sp196 = {0, 0, 0};
chrDamageByMisc(chr, 1, &sp196, NULL, NULL);
chr->lastmoveok60 = g_Vars.lvframe60;
return;
}
// Goposforce was not set - restart the action to try and find a new route
chrGoToRoomPos(chr, &chr->act_gopos.endpos, chr->act_gopos.endrooms, chr->act_gopos.flags);
}
chrGoPosConsiderRestart(chr);
chrGoPosGetCurWaypointInfoWithFlags(chr, &curwppos, curwprooms, &curwpflags);
// If magic mode ended over 3 seconds ago, not multiplayer, not in view of
// eyespy, pad is nothing special and not in lift, then enter the magic move
// mode.
if (chr->act_gopos.waydata.mode != WAYMODE_MAGIC
&& chr->act_gopos.waydata.lastvisible60 + TICKS(180) < g_Vars.lvframe60
&& g_Vars.normmplayerisrunning == false
&& chrIsRoomOffScreen(chr, &curwppos, curwprooms) // related to eyespy
&& (curwpflags & (PADFLAG_AIWAITLIFT | PADFLAG_AIONLIFT)) == 0
&& chr->inlift == false) {
enteringmagic = true;
chrGoPosInitMagic(chr, &chr->act_gopos.waydata, &curwppos, &prop->pos);
}
// If goposforce is set then decrease it on each tick. If it's reached -1
// then stop the chr. I guess goposforce is not only used to warp past
// obstacles, but is also used as a run countdown timer.
if (chr->goposforce >= 0) {
chr->goposforce -= g_Vars.lvupdate60;
if (chr->goposforce < 0) {
chrStop(chr);
return;
}
}
// Check if chr needs to exit magic mode
if (chr->act_gopos.waydata.mode == WAYMODE_MAGIC) {
if ((!enteringmagic && ((prop->flags & (PROPFLAG_ONANYSCREENPREVTICK | PROPFLAG_ONANYSCREENTHISTICK | PROPFLAG_ONTHISSCREENTHISTICK)) || !chrIsRoomOffScreen(chr, &curwppos, curwprooms)))
|| (curwpflags & (PADFLAG_AIWAITLIFT | PADFLAG_AIONLIFT))
|| chr->inlift) {
// Exiting magic mode
chrGoPosInitExpensive(chr);
chr->act_gopos.waydata.lastvisible60 = g_Vars.lvframe60;
return;
}
chrNavTickMagic(chr, &chr->act_gopos.waydata, func0f0370a8(chr), &curwppos, curwprooms);
} else {
bool advance = false;
bool sp188;
bool sp184;
f32 sp180;
f32 sp176;
f32 sp172;
f32 sp168;
bool candosomething;
f32 sp160;
f32 sp156;
struct waypoint *next;
struct pad *pad2;
waypoint = chr->act_gopos.waypoints[chr->act_gopos.curindex];
if (waypoint) {
pad = &g_Pads[waypoint->padnum];
// Both of these functions are calculating something with the coords
// and are returning a boolean. There are no write operations.
sp188 = posIsArrivingAtPos(&chr->prevpos, &prop->pos, &pad->pos, 30);
sp184 = posIsArrivingLaterallyAtPos(&chr->prevpos, &prop->pos, &pad->pos, 30);
if (pad->flags & PADFLAG_AIDUCK) {
chr->act_gopos.flags |= GOPOSFLAG_DUCK;
} else if (pad->flags & PADFLAG_10000) {
chr->act_gopos.flags |= GOPOSFLAG_WALKDIRECT;
}
if ((pad->flags & PADFLAG_AIWAITLIFT) || (pad->flags & PADFLAG_AIONLIFT)) {
advance = chrGoPosUpdateLiftAction(chr, pad->flags, sp184, sp188, waypoint->padnum, chrGoPosGetNextPadNum(chr));
} else {
if (sp188 || (sp184 && (chr->inlift || (pad->flags & PADFLAG_8000)))) {
advance = true;
}
}
} else {
// No more waypoints - chr is finished
if (posIsArrivingAtPos(&chr->prevpos, &prop->pos, &chr->act_gopos.endpos, 30) ||
(chr->inlift && posIsArrivingLaterallyAtPos(&chr->prevpos, &prop->pos, &chr->act_gopos.endpos, 30))) {
if (chr->act_gopos.flags & GOPOSFLAG_FORPATHSTART) {
chrTryStartPatrol(chr);
return;
}
chrStop(chr);
return;
}
}
if (advance) {
chrGoPosAdvanceWaypoint(chr);
}
// Every 10 ticks: Check something a couple of waypoints ahead
// This might be checking if the chr has line of sight to that pad and can
// walk straight to it.
if (chr->act_gopos.waydata.age % 10 == 5 || (chr->act_gopos.flags & GOPOSFLAG_INIT)) {
// Load waypoint that the chr is running to
waypoint = chr->act_gopos.waypoints[chr->act_gopos.curindex];
if (waypoint) {
pad = &g_Pads[waypoint->padnum];
if ((pad->flags & PADFLAG_AIWALKDIRECT) == 0) {
// The waypoint the chr is running to doesn't have
// PADFLAG_AIWALKDIRECT, so the chr is able to ignore it and run
// towards the next one if it's in sight.
// Load the next waypoint after the one the chr is running to
waypoint = chr->act_gopos.waypoints[chr->act_gopos.curindex + 1];
if (waypoint) {
pad = &g_Pads[waypoint->padnum];
if ((pad->flags & PADFLAG_AIWALKDIRECT) == 0) {
// And this one doesn't have PADFLAG_AIWALKDIRECT either,
// so the chr can consider skipping this one too.
waypoint = chr->act_gopos.waypoints[chr->act_gopos.curindex + 2];
if (waypoint) {
pad = &g_Pads[waypoint->padnum];
nextpos = pad->pos;
nextrooms[0] = pad->room;
nextrooms[1] = -1;
} else {
nextpos = chr->act_gopos.endpos;
roomsCopy(chr->act_gopos.endrooms, nextrooms);
}
// Some bbox related check
if (func0f03654c(chr, &prop->pos, prop->rooms, &nextpos, nextrooms, NULL, chr->radius * 1.2f, CDTYPE_PATHBLOCKER | CDTYPE_BG)) {
chrGoPosAdvanceWaypoint(chr);
chrGoPosAdvanceWaypoint(chr);
}
}
}
}
}
}
if (chr->act_gopos.waydata.age % 10 == 0 || (chr->act_gopos.flags & GOPOSFLAG_INIT)) {
waypoint = chr->act_gopos.waypoints[chr->act_gopos.curindex];
if (waypoint) {
candosomething = (chr->act_gopos.flags & GOPOSFLAG_INIT) != 0;
pad = &g_Pads[waypoint->padnum];
next = chr->act_gopos.waypoints[chr->act_gopos.curindex + 1];
if (next) {
pad2 = &g_Pads[next->padnum];
if ((pad->flags & (PADFLAG_AIWAITLIFT | PADFLAG_AIONLIFT))
&& (pad2->flags & (PADFLAG_AIWAITLIFT | PADFLAG_AIONLIFT))) {
candosomething = false;
}
}
if ((pad->flags & PADFLAG_AIWALKDIRECT) == 0 || candosomething) {
if (next) {
nextpos = pad2->pos;
nextrooms[0] = pad2->room;
nextrooms[1] = -1;
} else {
nextpos = chr->act_gopos.endpos;
roomsCopy(chr->act_gopos.endrooms, nextrooms);
}
// I suspect this is making the chr turn to face the next pad
if ((pad->flags & PADFLAG_AIWALKDIRECT) && candosomething) {
if (true) {
sp180 = prop->pos.x - pad->pos.x;
sp176 = prop->pos.z - pad->pos.z;
sp172 = nextpos.x - pad->pos.x;
sp168 = nextpos.z - pad->pos.z;
}
sp156 = sqrtf((sp180 * sp180 + sp176 * sp176) * (sp172 * sp172 + sp168 * sp168));
if (sp156 > 0) {
sp160 = acosf((sp180 * sp172 + sp176 * sp168) / sp156);
// sp160 < DEG2RAD(45) || sp160 > DEG2RAD(315)
if (sp160 < 0.7852731347084f || sp160 > 5.4969120025635f) {
if (func0f03654c(chr, &prop->pos, prop->rooms, &nextpos, nextrooms, NULL, chr->radius * 1.2f, CDTYPE_PATHBLOCKER | CDTYPE_BG)) {
chrGoPosAdvanceWaypoint(chr);
}
}
}
} else {
if (func0f03654c(chr, &prop->pos, prop->rooms, &nextpos, nextrooms, NULL, chr->radius * 1.2f, CDTYPE_PATHBLOCKER | CDTYPE_BG)) {
chrGoPosAdvanceWaypoint(chr);
}
}
}
}
chr->act_gopos.flags &= ~GOPOSFLAG_INIT;
}
waypoint = chr->act_gopos.waypoints[chr->act_gopos.curindex];
if (waypoint) {
nextpos = g_Pads[waypoint->padnum].pos;
} else {
nextpos = chr->act_gopos.endpos;
if (chr->aibot && chr->myaction == MA_AIBOTGETITEM) {
sp240 = false;
}
}
chrNavTickMain(chr, &nextpos, &chr->act_gopos.waydata, sp240);
}
}
void chrTickPatrol(struct chrdata *chr)
{
struct prop *prop = chr->prop;
bool enteringmagic;
u32 flags = 0;
struct coord sp58;
s16 sp48[8];
s32 arrivinglaterally;
s32 arriving;
bool advance;
enteringmagic = 0;
if (chr->hidden & CHRHFLAG_NEEDANIM) {
if (modelIsAnimMerging(chr->model)) {
return;
}
chrPatrolChooseAnimation(chr);
chr->hidden &= ~CHRHFLAG_NEEDANIM;
}
chrPatrolGetCurWaypointInfoWithFlags(chr, &sp58, sp48, &flags);
chr->act_patrol.waydata.age++;
// Consider starting magic
if (chr->act_patrol.waydata.mode != WAYMODE_MAGIC
&& g_Vars.lvframe60 > chr->act_patrol.waydata.lastvisible60 + TICKS(180)
&& !g_Vars.normmplayerisrunning
&& chrIsRoomOffScreen(chr, &sp58, sp48)
&& (flags & (PADFLAG_AIWAITLIFT | PADFLAG_AIONLIFT)) == 0
&& !chr->inlift) {
enteringmagic = true;
chrGoPosInitMagic(chr, &chr->act_patrol.waydata, &sp58, &prop->pos);
}
if (chr->act_patrol.waydata.mode == WAYMODE_MAGIC) {
if ((!enteringmagic && ((prop->flags & (PROPFLAG_ONTHISSCREENTHISTICK | PROPFLAG_ONANYSCREENTHISTICK | PROPFLAG_ONANYSCREENPREVTICK)) || !chrIsRoomOffScreen(chr, &sp58, sp48)))
|| (flags & (PADFLAG_AIWAITLIFT | PADFLAG_AIONLIFT))
|| chr->inlift) {
// Exit magic for lifts
chr->act_patrol.waydata.lastvisible60 = g_Vars.lvframe60;
func0f037580(chr);
} else {
// Continue magic
chrNavTickMagic(chr, &chr->act_patrol.waydata, func0f0370a8(chr), &sp58, sp48);
}
footstepCheckMagic(chr);
} else {
arrivinglaterally = posIsArrivingLaterallyAtPos(&chr->prevpos, &prop->pos, &sp58, 30);
arriving = posIsArrivingAtPos(&chr->prevpos, &prop->pos, &sp58, 30);
advance = false;
if ((flags & PADFLAG_AIWAITLIFT) || (flags & PADFLAG_AIONLIFT)) {
advance = chrGoPosUpdateLiftAction(chr, flags, arrivinglaterally, arriving,
chrPatrolCalculatePadNum(chr, 0),
chrPatrolCalculatePadNum(chr, 1));
} else if (arriving) {
advance = true;
}
if (advance) {
func0f0375b0(chr);
chrPatrolGetCurWaypointInfo(chr, &sp58, sp48);
}
chrNavTickMain(chr, &sp58, &chr->act_patrol.waydata, true);
footstepCheckDefault(chr);
}
}
bool chrTrySkJump(struct chrdata *chr, u8 arg1, u8 arg2, s32 arg3, u8 arg4)
{
if (chr && chr->actiontype != ACT_SKJUMP
&& chrIsReadyForOrders(chr)
&& CHRRACE(chr) == RACE_SKEDAR) {
return chrStartSkJump(chr, arg1, arg2, arg3, arg4);
}
return false;
}
bool chrStartSkJump(struct chrdata *chr, u8 arg1, u8 arg2, s32 arg3, u8 arg4)
{
f32 radius;
f32 ymax;
f32 ymin;
struct prop *prop = chr->prop;
struct prop *target = chrGetTargetProp(chr);
bool iVar2;
f32 distance = chrGetSquaredDistanceToCoord(chr, &target->pos);
f32 diffs[2];
f32 thing;
s32 time60;
if (distance < 40000 || distance > 302500 || !target) {
return false;
}
chrGetBbox(prop, &radius, &ymax, &ymin);
chrSetPerimEnabled(chr, false);
propSetPerimEnabled(target, false);
iVar2 = cdTestCylMove01(&prop->pos, prop->rooms, &target->pos,
CDTYPE_OBJS | CDTYPE_DOORS | CDTYPE_PATHBLOCKER | CDTYPE_BG,
1, ymax - prop->pos.y, ymin - prop->pos.y);
chrSetPerimEnabled(chr, true);
propSetPerimEnabled(target, true);
if (iVar2) {
diffs[0] = target->pos.x - chr->prop->pos.x;
diffs[1] = target->pos.z - chr->prop->pos.z;
thing = sqrtf(diffs[0] * diffs[0] + diffs[1] * diffs[1]) * 2.5f / PALUPF(21.0f);
time60 = thing;
if (time60 < TICKS(10)) {
time60 = TICKS(10);
}
chr->act_skjump.vel[0] = diffs[0] / time60;
chr->act_skjump.vel[1] = diffs[1] / time60;
chr->act_skjump.roty = chrGetInverseTheta(chr) + chrGetAngleToPos(chr, &target->pos);
chr->act_skjump.hit = false;
chr->act_skjump.timer60 = time60;
chr->act_skjump.total60 = time60;
chr->act_skjump.ground = cdFindGroundAtCyl(&chr->prop->pos, chr->radius, chr->prop->rooms, NULL, NULL);
} else {
return false;
}
chr->actiontype = ACT_SKJUMP;
chr->act_skjump.needsnewanim = true;
chr->act_skjump.state = SKJUMPSTATE_TAKEOFF;
return true;
}
void chrTickSkJump(struct chrdata *chr)
{
if (g_Vars.lvupdate60 == 0) {
return;
}
if (chr->act_skjump.needsnewanim) {
chr->act_skjump.needsnewanim = false;
switch (chr->act_skjump.state) {
case SKJUMPSTATE_TAKEOFF:
modelSetAnimation(chr->model, ANIM_SKEDAR_JUMPSTART, 0, 0, -1, 8);
modelSetAnimSpeed(chr->model, 2.5, 0);
break;
case SKJUMPSTATE_AIRBORNE: {
u16 sounds[] = {
SFX_SKEDAR_ROAR_0532,
SFX_SKEDAR_ROAR_0533,
SFX_SKEDAR_ROAR_0534,
};
propsnd0f0939f8(NULL, chr->prop, sounds[random() % 3], -1,
-1, 0, 0, 0, 0, -1, 0, -1, -1, -1, -1);
modelSetAnimation(chr->model, ANIM_SKEDAR_JUMPAIR, 0, 0, -1, 16);
modelSetAnimSpeed(chr->model, 1, 0);
}
break;
}
}
{
f32 fVar5;
f32 fVar6;
f32 fVar7;
u32 stack[1];
f32 frame;
switch (chr->act_skjump.state) {
case SKJUMPSTATE_TAKEOFF:
fVar6 = chrGetInverseTheta(chr);
fVar5 = model0001afe8(fVar6, chr->act_skjump.roty, 0.35);
chrSetLookAngle(chr, fVar5);
frame = modelGetCurAnimFrame(chr->model);
if (frame >= modelGetAnimEndFrame(chr->model)) {
chr->act_skjump.state++;
chr->act_skjump.needsnewanim = true;
}
break;
case SKJUMPSTATE_AIRBORNE:
chr->act_skjump.pos.x = chr->act_skjump.vel[0] * g_Vars.lvupdate60 + chr->prop->pos.x;
chr->act_skjump.pos.z = chr->act_skjump.vel[1] * g_Vars.lvupdate60 + chr->prop->pos.z;
if (chr->act_skjump.total60 > 0) {
fVar6 = 1.0f - chr->act_skjump.timer60 / (f32)chr->act_skjump.total60;
fVar7 = sinf(M_PI * fVar6);
fVar7 = fVar7 * 160.0f + chr->act_skjump.ground;
} else {
fVar6 = 1;
fVar7 = chr->act_skjump.ground;
}
chr->act_skjump.pos.y = fVar7 - chr->prop->pos.y;
if (fVar6 < 0.5f && chr->act_skjump.pos.y < 0.0f) {
chr->act_skjump.pos.y = 0;
}
if (!chr->act_skjump.hit && chrGetDistanceToTarget(chr) < 150.0f) {
chrPunchInflictDamage(chr, 3, 150, false);
chr->act_skjump.hit = true;
}
if (chr->act_skjump.timer60 > 0) {
chr->act_skjump.timer60 -= g_Vars.lvupdate60;
} else {
chrTryStop(chr);
}
break;
}
}
}
void chraTick(struct chrdata *chr)
{
u32 race = CHRRACE(chr);
if (g_Vars.lvupdate240 < 1) {
return;
}
if (chr->actiontype == ACT_INIT) {
chr->chrflags |= CHRCFLAG_00000001;
func0f02e9a0(chr, 0);
chr->sleep = 0;
}
if (race == RACE_DRCAROLL) {
g_DrCarollDyingTimer += g_Vars.lvupdate60;
}
chr->soundtimer += g_Vars.lvupdate60;
if (chr->hidden & CHRHFLAG_TIMER_RUNNING) {
chr->timer60 += g_Vars.lvupdate60;
}
chr->sleep -= g_Vars.lvupdate60;
if (chr->sleep < 0
|| (chr->chrflags & CHRCFLAG_00040000)
|| chr->alertness >= 65
|| (chr->aibot && (chr->actiontype == ACT_DIE || chr->actiontype == ACT_DEAD))) {
u8 pass = race == RACE_HUMAN || race == RACE_SKEDAR;
chr->sleep = 0;
chraiExecute(chr, PROPTYPE_CHR);
// Consider setting shootingatmelist
if (chr->aishootingatmelist != NULL && chr->ailist != chr->aishootingatmelist && chr->dodgerating > 0) {
if (chr->aimtesttimer60 < 1) {
chr->aimtesttimer60 = TICKS(30);
if (chrCanSeeTargetWithExtraCheck(chr)) {
chr->chrflags |= CHRCFLAG_CONSIDER_DODGE;
}
} else {
chr->aimtesttimer60 -= g_Vars.lvupdate60;
}
}
// Consider setting darkroomlist
if (chr->aidarkroomlist != NULL
&& roomGetBrightness(chr->prop->rooms[0]) < 25
&& chr->ailist != chr->aidarkroomlist) {
chr->darkroomthing = true;
}
if (race == RACE_ROBOT) {
robotSetMuzzleFlash(chr, 0, false);
robotSetMuzzleFlash(chr, 1, false);
}
if (g_Vars.in_cutscene) {
switch (chr->actiontype) {
case ACT_ANIM: chrTickAnim(chr); break;
case ACT_PATROL: chrTickPatrol(chr); pass = false; break;
}
} else {
switch (chr->actiontype) {
case ACT_STAND: chrTickStand(chr); break;
case ACT_KNEEL: chrTickKneel(chr); break;
case ACT_ANIM: chrTickAnim(chr); break;
case ACT_DIE: chrTickDie(chr); break;
case ACT_ARGH: chrTickArgh(chr); break;
case ACT_PREARGH: chrTickPreArgh(chr); break;
case ACT_SIDESTEP: chrTickSidestep(chr); break;
case ACT_JUMPOUT: chrTickJumpOut(chr); break;
case ACT_DEAD: chrTickDead(chr); break;
case ACT_ATTACK: chrTickAttack(chr); break;
case ACT_ATTACKWALK: chrTickAttackWalk(chr); break;
case ACT_ATTACKROLL: chrTickAttackRoll(chr); break;
case ACT_RUNPOS: chrTickRunPos(chr); break;
case ACT_PATROL: chrTickPatrol(chr); pass = false; break;
case ACT_GOPOS: chrTickGoPos(chr); break;
case ACT_SURRENDER: chrTickSurrender(chr); break;
case ACT_TEST: chrTickTest(chr); break;
case ACT_SURPRISED: chrTickSurprised(chr); break;
case ACT_STARTALARM: chrTickStartAlarm(chr); break;
case ACT_THROWGRENADE: chrTickThrowGrenade(chr); break;
case ACT_DRUGGEDCOMINGUP: chrTickDruggedComingUp(chr); break;
case ACT_DRUGGEDDROP: chrTickDruggedDrop(chr); break;
case ACT_DRUGGEDKO: chrTickDruggedKo(chr); break;
case ACT_ATTACKAMOUNT: chrTickAttackAmount(chr); break;
case ACT_ROBOTATTACK: chrTickRobotAttack(chr); break;
case ACT_SKJUMP: chrTickSkJump(chr); break;
}
}
chr->hidden &= ~CHRHFLAG_IS_HEARING_TARGET;
if (pass) {
footstepCheckDefault(chr);
}
} else {
footstepCheckMagic(chr);
}
}
void cutsceneStart(u32 ailistid)
{
struct prop *prop;
#if PAL
var8009e388pf = 0;
#else
g_CutsceneFrameOverrun240 = 0;
#endif
g_CutsceneSkipRequested = false;
g_CutsceneCurTotalFrame60f = 0;
prop = g_Vars.activeprops;
while (prop) {
prop->lastupdateframe = 0xffff;
prop->propupdate240 = 0;
prop->propupdate60err = 2;
prop = prop->next;
}
g_BgChrs[g_NumBgChrs - 1].ailist = ailistFindById(ailistid);
g_BgChrs[g_NumBgChrs - 1].aioffset = g_BgChrs[g_NumBgChrs - 1].ailist;
g_BgChrs[g_NumBgChrs - 1].aireturnlist = NULL;
}
/**
* Execute background tasks.
*
* - Calculate number of engaged chrs
* - Handle switching to new cutscenes when playing all
* - Run BG scripts
* - Choose which corpses to fade
*/
void chraTickBg(void)
{
s32 i;
s32 numchrs;
s32 numaliveonscreen;
s32 numdeadonscreen;
s32 onscreenlen;
s32 offscreenlen;
s32 spawnslen;
struct chrdata *onscreen[5];
struct chrdata *offscreen[5];
struct chrdata *spawns[10];
s32 writeindex;
s32 maxdeadonscreen;
static u32 var80068454 = 0;
numaliveonscreen = 0;
spawnslen = 0;
numdeadonscreen = 0;
var80068454++;
if (var80068454 > 10) {
var80068454 = 0;
}
// Handle switching to a new cutscene when using "Play All" from the menu
if (g_Vars.autocutnum >= 0) {
cutsceneStart(g_Vars.autocutnum + 0xc00);
g_Vars.autocutnum = -1;
g_Vars.autocutplaying = true;
}
// Run BG scripts
if (g_Vars.lvupdate240 > 0) {
for (i = 0; i < g_NumBgChrs; i++) {
struct chrdata *chr = &g_BgChrs[i];
if (!g_Vars.autocutplaying || (chr->hidden2 & CHRH2FLAG_TICKDURINGAUTOCUT)) {
if (chr->hidden & CHRHFLAG_TIMER_RUNNING) {
chr->timer60 += g_Vars.lvupdate60;
}
chraiExecute(chr, PROPTYPE_CHR);
if (!g_Vars.chrdata) {
// Ailist has terminated and the current bgchr has been removed
i--;
}
}
}
}
// This is 0 on every 10th frame, so do the below corpse checks every 10 frames
if (var80068454) {
return;
}
numchrs = chrsGetNumSlots();
// Calculate alive/dead counters. For *spawned* chrs that have died,
// allow 10 corpses and start fading if there's more.
for (i = 0; i < numchrs; i++) {
struct chrdata *chr = &g_ChrSlots[i];
if (chr->model && chr->prop) {
if (chr->prop->flags & PROPFLAG_ONANYSCREENPREVTICK) {
if (chr->actiontype != ACT_DEAD && chr->actiontype != ACT_DRUGGEDKO) {
numaliveonscreen++;
} else if (chr->actiontype == ACT_DRUGGEDKO) {
if ((chr->chrflags & CHRCFLAG_KEEPCORPSEKO) == 0) {
numdeadonscreen++;
}
} else {
numdeadonscreen++;
}
}
if (chr->actiontype == ACT_DEAD
|| (chr->actiontype == ACT_DRUGGEDKO && (chr->chrflags & CHRCFLAG_KEEPCORPSEKO) == 0)) {
if (chr->hidden2 & CHRH2FLAG_SPAWNED) {
spawns[spawnslen] = chr;
spawnslen++;
if (spawnslen >= 10) {
writeindex = random() % spawnslen;
chrFadeCorpse(spawns[writeindex]);
spawns[writeindex] = spawns[spawnslen - 1];
spawnslen--;
}
}
}
}
}
// Calculate how many corpses are allowed on screen. Ideally no more than
// 5 chrs (alive or dead) to keep the number of polygons low.
maxdeadonscreen = 5 - numaliveonscreen;
// If the game is lagging, take the number of 60ths over 6 since the
// previous frame and reduce the limit by that amount. So if it's been, say,
// 8 60ths since the last frame then the max corpses will be reduced by 2.
// This is why corpses are more likely to fade on screen when you change
// weapons - the lag spike from changing guns triggers a lower corpse limit.
if (g_Vars.lvupdate60 > 6) {
maxdeadonscreen = maxdeadonscreen - g_Vars.lvupdate60 + 6;
}
if (maxdeadonscreen < 0) {
maxdeadonscreen = 0;
}
// Decide which corpses to fade.
// NTSC Beta implements its engagement counter here.
if (numdeadonscreen) {
numdeadonscreen = 0;
onscreenlen = 0;
offscreenlen = 0;
for (i = 0; i < numchrs; i++) {
struct chrdata *chr = &g_ChrSlots[i];
if (chr->model) {
if (chr->actiontype == ACT_DEAD
|| (chr->actiontype == ACT_DRUGGEDKO && chr->prop && (chr->chrflags & CHRCFLAG_KEEPCORPSEKO) == 0)) {
if (chr->prop->flags & PROPFLAG_ONANYSCREENPREVTICK) {
// On-screen
if (chr->act_dead.fadetimer60 < 0 && !chr->act_dead.fadenow) {
numdeadonscreen++;
// If there's too many corpses on screen, start fading.
if (numdeadonscreen > maxdeadonscreen || chr->aibot) {
chrFadeCorpse(chr);
numdeadonscreen--;
} else if (!chr->act_dead.fadewheninvis) {
// If there are 2 or more corpses on screen,
// start marking them to be removed once off screen
onscreen[onscreenlen] = chr;
onscreenlen++;
if (onscreenlen >= 2) {
writeindex = random() % onscreenlen;
chrFadeCorpseWhenOffScreen(onscreen[writeindex]);
onscreen[writeindex] = onscreen[onscreenlen - 1];
onscreenlen--;
}
}
}
} else {
// Off-screen
if (!chr->act_dead.fadewheninvis) {
offscreen[offscreenlen] = chr;
offscreenlen++;
// Allow up to 5 corpses off-screen
if (offscreenlen >= 5) {
writeindex = random() % offscreenlen;
if (offscreen[writeindex]->actiontype != ACT_DEAD) {
chrBeginDead(offscreen[writeindex]);
}
chrFadeCorpseWhenOffScreen(offscreen[writeindex]);
offscreen[writeindex] = offscreen[offscreenlen - 1];
offscreenlen--;
}
}
}
}
}
}
}
}
bool chrHeardTargetRecently(struct chrdata *chr)
{
if (chr->lastheartarget60 > 0 && g_Vars.lvframe60 - chr->lastheartarget60 < TICKS(600)) {
return true;
}
return false;
}
f32 chrGetAngleToPos(struct chrdata *chr, struct coord *pos)
{
f32 fVar3;
f32 fVar2;
f32 fVar4;
struct prop *prop;
if (chr->prop->type == PROPTYPE_PLAYER) {
u32 playernum = playermgrGetPlayerNumByProp(chr->prop);
fVar3 = (360 - g_Vars.players[playernum]->vv_theta) * (M_BADTAU / 360);
} else {
fVar3 = chrGetInverseTheta(chr);
}
prop = chr->prop;
fVar2 = atan2f(pos->x - prop->pos.x, pos->z - prop->pos.z);
fVar4 = fVar2 - fVar3;
if (fVar2 < fVar3) {
fVar4 += M_BADTAU;
}
return fVar4;
}
f32 chrGetAngleToTarget(struct chrdata *chr)
{
struct prop *prop = chrGetTargetProp(chr);
return chrGetAngleToPos(chr, &prop->pos);
}
void chrGetAttackEntityPos(struct chrdata *chr, u32 attackflags, s32 entityid, struct coord *pos, s16 *rooms)
{
struct prop *targetprop;
struct chrdata *targetchr;
if (attackflags & ATTACKFLAG_AIMATCHR) {
// Aiming at a chr by chrnum
targetchr = chrFindById(chr, entityid);
if (!targetchr || !targetchr->prop) {
targetchr = chr;
}
*pos = targetchr->prop->pos;
if (targetchr) {
chr = targetprop->chr;
} else {
chr = NULL;
}
if (chr);
roomsCopy(targetchr->prop->rooms, rooms);
} else if (attackflags & ATTACKFLAG_AIMATPAD) {
// Aiming at a pad by padnum
s32 padnum = chrResolvePadId(chr, entityid);
*pos = g_Pads[padnum].pos;
rooms[0] = g_Pads[padnum].room;
rooms[1] = -1;
} else {
// Aiming at the chr's preconfigured target
targetprop = chrGetTargetProp(chr);
*pos = targetprop->pos;
if (targetprop->type == PROPTYPE_CHR && targetprop->chr) {
chr = targetprop->chr;
} else {
chr = NULL;
}
if (chr);
roomsCopy(targetprop->rooms, rooms);
}
}
f32 chrGetAngleFromTargetsFov(struct chrdata *chr)
{
f32 targetfacingangle = 0;
struct prop *prop = chr->prop;
struct prop *target = chrGetTargetProp(chr);
f32 xdiff;
f32 zdiff;
f32 angletotarget;
f32 result;
if (prop && target) {
xdiff = prop->pos.f[0] - target->pos.f[0];
zdiff = prop->pos.f[2] - target->pos.f[2];
angletotarget = atan2f(xdiff, zdiff);
if (target->type == PROPTYPE_PLAYER) {
s32 playernum = playermgrGetPlayerNumByProp(target);
targetfacingangle = (360.0f - g_Vars.players[playernum]->vv_theta) * M_BADTAU / 360.0f;
} else if (target->type == PROPTYPE_CHR) {
targetfacingangle = chrGetInverseTheta(target->chr);
}
result = angletotarget - targetfacingangle;
if (angletotarget < targetfacingangle) {
result += M_BADTAU;
}
}
return result;
}
f32 chrGetVerticalAngleToTarget(struct chrdata *chr)
{
struct prop *prop = chr->prop;
struct prop *target = chrGetTargetProp(chr);
f32 result = 0;
if (prop && target) {
f32 ydiff;
f32 xdiff;
f32 zdiff;
xdiff = prop->pos.x - target->pos.x;
ydiff = prop->pos.y - target->pos.y;
zdiff = prop->pos.z - target->pos.z;
result = atan2f(ydiff, sqrtf(xdiff * xdiff + zdiff * zdiff));
if (result < 0) {
result += M_BADTAU;
}
}
return result;
}
bool chrIsInTargetsFovX(struct chrdata *chr, u8 fov360)
{
f32 angle = chrGetAngleFromTargetsFov(chr);
if ((angle < fov360 * 0.024539785459638f && angle < M_PI)
|| (angle > M_BADTAU - fov360 * 0.024539785459638f && angle > M_PI)) {
return true;
}
return false;
}
bool chrIsVerticalAngleToTargetWithin(struct chrdata *chr, u8 fov360)
{
f32 val = chrGetVerticalAngleToTarget(chr);
if ((val < fov360 * 0.024539785459638f && val < M_PI)
|| (val > M_BADTAU - fov360 * 0.024539785459638f && val > M_PI)) {
return true;
}
return false;
}
f32 func0f048fcc(struct chrdata *chr, u8 reverse)
{
f32 result;
struct prop *chrprop = chr->prop;
struct prop *targetprop = chrGetTargetProp(chr);
f32 xdiff = chrprop->pos.x - targetprop->pos.x;
f32 zdiff = chrprop->pos.z - targetprop->pos.z;
f32 angle1 = atan2f(-xdiff, -zdiff);
f32 angle2 = chrGetInverseTheta(chr) + M_PI * (s32)reverse;
result = angle1 - angle2;
if (angle1 < angle2) {
result += M_BADTAU;
}
return result;
}
bool chrIsTargetInFov(struct chrdata *chr, u8 arg1, u8 reverse)
{
f32 angle = func0f048fcc(chr, reverse);
if ((angle < arg1 * 0.024539785459638f && angle < M_PI)
|| (angle > M_BADTAU - arg1 * 0.024539785459638f && angle > M_PI)) {
return true;
}
return false;
}
bool chrIsLookingAtPos(struct chrdata *chr, struct coord *pos, u8 arg2)
{
f32 angle = chrGetAngleToPos(chr, pos);
if ((angle < arg2 * 0.024539785459638f && angle < M_PI) ||
(M_BADTAU - arg2 * 0.024539785459638f < angle && M_PI < angle)) {
return true;
}
return false;
}
f32 chrGetDistanceToTarget(struct chrdata *chr)
{
struct prop *prop = chrGetTargetProp(chr);
return propGetDistanceToProp(chr->prop, prop);
}
f32 chrGetDistanceToCurrentPlayer(struct chrdata *chr)
{
return propGetDistanceToProp(chr->prop, g_Vars.currentplayer->prop);
}
f32 propGetDistanceToProp(struct prop *a, struct prop *b)
{
f32 xdiff = a->pos.x - b->pos.x;
f32 ydiff = a->pos.y - b->pos.y;
f32 zdiff = a->pos.z - b->pos.z;
return sqrtf(xdiff * xdiff + ydiff * ydiff + zdiff * zdiff);
}
f32 chrGetDistanceToPad(struct chrdata *chr, s32 pad_id)
{
struct prop *prop = chr->prop;
f32 xdiff, ydiff, zdiff;
f32 distance = 0;
pad_id = chrResolvePadId(chr, pad_id);
if (pad_id >= 0) {
xdiff = g_Pads[pad_id].pos.x - prop->pos.x;
ydiff = g_Pads[pad_id].pos.y - prop->pos.y;
zdiff = g_Pads[pad_id].pos.z - prop->pos.z;
distance = sqrtf(xdiff * xdiff + ydiff * ydiff + zdiff * zdiff);
}
return distance;
}
f32 chrGetSquaredDistanceToPad(struct chrdata *chr, s32 pad_id)
{
struct prop *prop = chr->prop;
f32 xdiff, ydiff, zdiff;
f32 distance = 0;
pad_id = chrResolvePadId(chr, pad_id);
if (pad_id >= 0) {
xdiff = g_Pads[pad_id].pos.x - prop->pos.x;
ydiff = g_Pads[pad_id].pos.y - prop->pos.y;
zdiff = g_Pads[pad_id].pos.z - prop->pos.z;
distance = xdiff * xdiff + ydiff * ydiff + zdiff * zdiff;
}
return distance;
}
f32 chrGetSameFloorDistanceToPad(struct chrdata *chr, s32 pad_id)
{
struct prop *prop = chr->prop;
f32 xdiff, ydiff, zdiff, ydiff_absolute;
f32 ret;
pad_id = chrResolvePadId(chr, pad_id);
xdiff = g_Pads[pad_id].pos.x - prop->pos.x;
ydiff = g_Pads[pad_id].pos.y - prop->pos.y;
zdiff = g_Pads[pad_id].pos.z - prop->pos.z;
if (ydiff > 0) {
ydiff_absolute = ydiff;
} else {
ydiff_absolute = -ydiff;
}
if (ydiff_absolute < 150) {
ret = sqrtf(xdiff * xdiff + zdiff * zdiff);
} else {
ret = 100000000;
}
return ret;
}
f32 chrGetDistanceToCoord(struct chrdata *chr, struct coord *pos)
{
f32 xdiff = pos->x - chr->prop->pos.x;
f32 ydiff = pos->y - chr->prop->pos.y;
f32 zdiff = pos->z - chr->prop->pos.z;
return sqrtf(xdiff * xdiff + ydiff * ydiff + zdiff * zdiff);
}
f32 chrGetSquaredLateralDistanceToCoord(struct chrdata *chr, struct coord *pos)
{
f32 xdiff = pos->x - chr->prop->pos.x;
f32 zdiff = pos->z - chr->prop->pos.z;
return xdiff * xdiff + zdiff * zdiff;
}
f32 chrGetSquaredDistanceToCoord(struct chrdata *chr, struct coord *pos)
{
f32 xdiff = pos->x - chr->prop->pos.x;
f32 ydiff = pos->y - chr->prop->pos.y;
f32 zdiff = pos->z - chr->prop->pos.z;
return xdiff * xdiff + ydiff * ydiff + zdiff * zdiff;
}
f32 coordGetSquaredDistanceToCoord(struct coord *a, struct coord *b)
{
f32 xdiff = a->x - b->x;
f32 ydiff = a->y - b->y;
f32 zdiff = a->z - b->z;
return xdiff * xdiff + ydiff * ydiff + zdiff * zdiff;
}
s32 chrGetPadRoom(struct chrdata *chr, s32 pad_id)
{
s32 ret = -1;
s32 pad_id_backup = pad_id;
if (pad_id >= 10000) {
s32 resolved_pad_id = chrResolvePadId(chr, pad_id - 10000);
if (resolved_pad_id >= 0) {
ret = g_Pads[resolved_pad_id].room;
}
} else {
ret = pad_id;
}
return ret;
}
s32 chrResolvePadId(struct chrdata *chr, s32 pad_id)
{
if (pad_id == 9000) {
pad_id = chr->padpreset1;
}
return pad_id;
}
/**
* For all chrs, clear their target and p1p2 values if set to the given player.
*
* This function is called when the given player has died. It causes all guards
* to switch their focus to the remaining coop player.
*/
void chrsClearRefsToPlayer(s32 playernum)
{
s32 otherplayernum;
s32 playerpropnum;
s32 i;
if (g_Vars.coopplayernum >= 0) {
if (playernum == g_Vars.bondplayernum) {
otherplayernum = g_Vars.coopplayernum;
playerpropnum = g_Vars.bond->prop - g_Vars.props;
} else {
otherplayernum = g_Vars.bondplayernum;
playerpropnum = g_Vars.coop->prop - g_Vars.props;
}
for (i = 0; i < chrsGetNumSlots(); i++) {
if (g_ChrSlots[i].p1p2 == playernum) {
g_ChrSlots[i].p1p2 = otherplayernum;
}
if (g_ChrSlots[i].target == playerpropnum) {
g_ChrSlots[i].target = -1;
}
}
for (i = 0; i < g_NumBgChrs; i++) {
if (g_BgChrs[i].p1p2 == playernum) {
g_BgChrs[i].p1p2 = otherplayernum;
}
if (g_BgChrs[i].target == playerpropnum) {
g_BgChrs[i].target = -1;
}
}
}
}
s32 chrResolveId(struct chrdata *ref, s32 id)
{
if (ref) {
switch (id) {
case CHR_SEESHOT:
id = ref->chrseeshot;
break;
case CHR_SEEDIE:
id = ref->chrseedie;
break;
case CHR_PRESET:
id = ref->chrpreset1;
break;
case CHR_SELF:
id = ref->chrnum;
break;
case CHR_CLONE:
id = ref->chrdup;
break;
case CHR_BOND:
if (g_Vars.bond && g_Vars.bond->prop && g_Vars.bond->prop->chr) {
id = g_Vars.bond->prop->chr->chrnum;
}
break;
case CHR_COOP:
if (g_Vars.coop && g_Vars.coop->prop && g_Vars.coop->prop->chr) {
id = g_Vars.coop->prop->chr->chrnum;
}
break;
case CHR_ANTI:
if (g_Vars.anti && g_Vars.anti->prop && g_Vars.anti->prop->chr) {
id = g_Vars.anti->prop->chr->chrnum;
}
break;
case CHR_P1P2:
{
u32 index = g_Vars.coopplayernum >= 0 ? ref->p1p2 : g_Vars.bondplayernum;
struct player *player = g_Vars.players[index];
if (player && player->prop && player->prop->chr) {
id = player->prop->chr->chrnum;
}
}
break;
case CHR_P1P2_OPPOSITE:
if (g_Vars.coopplayernum >= 0) {
struct player *player = g_Vars.players[1 - ref->p1p2];
if (player && player->prop && player->prop->chr) {
id = player->prop->chr->chrnum;
}
}
break;
case CHR_TARGET:
{
struct prop *target = chrGetTargetProp(ref);
if ((target->type & (PROPTYPE_CHR | PROPTYPE_PLAYER)) && target->chr) {
id = target->chr->chrnum;
}
}
break;
}
} else { // ref is NULL
switch (id) {
case CHR_BOND:
if (g_Vars.bond && g_Vars.bond->prop && g_Vars.bond->prop->chr) {
id = g_Vars.bond->prop->chr->chrnum;
}
break;
case CHR_COOP:
if (g_Vars.coop && g_Vars.coop->prop && g_Vars.coop->prop->chr) {
id = g_Vars.coop->prop->chr->chrnum;
}
break;
case CHR_ANTI:
if (g_Vars.anti && g_Vars.anti->prop && g_Vars.anti->prop->chr) {
id = g_Vars.anti->prop->chr->chrnum;
}
break;
case CHR_P1P2:
{
struct player *player = g_Vars.players[g_Vars.bondplayernum];
if (player && player->prop && player->prop->chr) {
id = player->prop->chr->chrnum;
}
}
break;
case CHR_P1P2_OPPOSITE:
if (g_Vars.coopplayernum >= 0) {
struct player *player = g_Vars.players[g_Vars.coopplayernum];
if (player && player->prop && player->prop->chr) {
id = player->prop->chr->chrnum;
}
}
break;
}
}
return id;
}
struct chrdata *chrFindById(struct chrdata *basechr, s32 chrnum)
{
struct chrdata *chr;
s32 lower;
s32 upper;
s32 i;
chrnum = chrResolveId(basechr, chrnum);
chr = chrFindByLiteralId(chrnum);
if (chr) {
return chr;
}
lower = 0;
upper = g_NumBgChrs;
while (upper >= lower) {
i = (lower + upper) / 2;
if (chrnum == g_BgChrnums[i]) {
return &g_BgChrs[i];
}
if (chrnum < g_BgChrnums[i]) {
upper = i - 1;
} else {
lower = i + 1;
}
}
return NULL;
}
s32 propGetIndexByChrId(struct chrdata *basechr, s32 chrnum)
{
s32 index;
if (chrnum == CHR_BOND || chrnum == CHR_BOND) {
index = g_Vars.bond->prop - g_Vars.props;
} else {
struct chrdata *chr = chrFindById(basechr, chrnum);
if (chr && chr->prop) {
index = chr->prop - g_Vars.props;
} else {
index = -1;
}
}
return index;
}
f32 chrGetDistanceToChr(struct chrdata *chr1, s32 chr2num)
{
struct prop *prop1 = chr1->prop;
struct chrdata *chr2 = chrFindById(chr1, chr2num);
f32 distance = 0;
if (chr2 && chr2->prop) {
f32 xdiff = chr2->prop->pos.x - prop1->pos.x;
f32 ydiff = chr2->prop->pos.y - prop1->pos.y;
f32 zdiff = chr2->prop->pos.z - prop1->pos.z;
distance = sqrtf(xdiff * xdiff + ydiff * ydiff + zdiff * zdiff);
}
return distance;
}
f32 chrGetDistanceFromTargetToPad(struct chrdata *chr, s32 pad_id)
{
struct prop *prop = chrGetTargetProp(chr);
f32 xdiff, ydiff, zdiff;
f32 distance = 0;
pad_id = chrResolvePadId(chr, pad_id);
if (pad_id >= 0) {
xdiff = g_Pads[pad_id].pos.x - prop->pos.x;
ydiff = g_Pads[pad_id].pos.y - prop->pos.y;
zdiff = g_Pads[pad_id].pos.z - prop->pos.z;
distance = sqrtf(xdiff * xdiff + ydiff * ydiff + zdiff * zdiff);
}
return distance;
}
void chrSetFlags(struct chrdata *chr, u32 flags, u8 bank)
{
if (bank == 0) {
chr->flags |= flags;
} else {
chr->flags2 |= flags;
}
}
void chrUnsetFlags(struct chrdata *chr, u32 flags, u8 bank)
{
if (bank == 0) {
chr->flags &= ~flags;
} else {
chr->flags2 &= ~flags;
}
}
bool chrHasFlag(struct chrdata *chr, u32 flag, u8 bank)
{
if (bank == 0) {
return (chr->flags & flag) != 0;
} else {
return (chr->flags2 & flag) != 0;
}
}
void chrSetFlagsById(struct chrdata *ref, u32 chrnum, u32 flags, u32 bank)
{
struct chrdata *chr = chrFindById(ref, chrnum);
if (chr) {
chrSetFlags(chr, flags, bank);
}
}
void chrUnsetFlagsById(struct chrdata *ref, u32 chrnum, u32 flags, u32 bank)
{
struct chrdata *chr = chrFindById(ref, chrnum);
if (chr) {
chrUnsetFlags(chr, flags, bank);
}
}
bool chrHasFlagById(struct chrdata *ref, u32 chrnum, u32 flag, u32 bank)
{
struct chrdata *chr = chrFindById(ref, chrnum);
if (chr) {
return chrHasFlag(chr, flag, bank);
}
return false;
}
void chrSetStageFlag(struct chrdata *chr, u32 flag)
{
g_StageFlags |= flag;
}
void chrUnsetStageFlag(struct chrdata *chr, u32 flag)
{
g_StageFlags = g_StageFlags & ~flag;
}
bool chrHasStageFlag(struct chrdata *chr, u32 flag)
{
return (g_StageFlags & flag) != 0;
}
bool chrIsHearingTarget(struct chrdata *chr)
{
return (chr->hidden & CHRHFLAG_IS_HEARING_TARGET) != 0;
}
void chrRestartTimer(struct chrdata *chr)
{
chr->timer60 = 0;
chr->hidden |= CHRHFLAG_TIMER_RUNNING;
}
s32 chrGetTimer(struct chrdata *chr)
{
return chr->timer60;
}
bool chrCanSeeTargetWithExtraCheck(struct chrdata *chr)
{
struct prop *target = chrGetTargetProp(chr);
if (target) {
if (target->type == PROPTYPE_CHR) {
if (!chrCanSeeTarget(chr)) {
return false;
}
return chrIsInTargetsFovX(chr, 20);
}
if (target->type == PROPTYPE_PLAYER) {
if (g_Vars.bondvisible &&
(cdTestLos05(&target->pos, target->rooms, &chr->prop->pos, chr->prop->rooms,
CDTYPE_OBJS | CDTYPE_DOORS | CDTYPE_PATHBLOCKER | CDTYPE_BG,
GEOFLAG_BLOCK_SIGHT))) {
struct model *model = chr->model;
struct coord sp68;
struct coord sp56;
struct coord sp44;
f32 somefloat = model0001af80(model) * 0.8f;
bgun0f0a0c08(&sp68, &sp56);
modelGetRootPosition(model, &sp44);
mtx4TransformVecInPlace(camGetWorldToScreenMtxf(), &sp44);
if (func0f06b39c(&sp68, &sp56, &sp44, somefloat)) {
return true;
}
}
}
}
return false;
}
bool chrResetNearMiss(struct chrdata *chr)
{
bool has_flag = (chr->chrflags & CHRCFLAG_NEAR_MISS) != 0;
chr->chrflags &= ~CHRCFLAG_NEAR_MISS;
return has_flag;
}
s32 chrGetNumArghs(struct chrdata *chr)
{
return chr->numarghs;
}
bool chrSawInjury(struct chrdata *chr, u8 arg1)
{
bool saw_injury = chr->chrseeshot >= 0;
if (saw_injury && arg1 == 0) {
chr->chrseeshot = -1;
} else if (saw_injury && arg1 == 1) {
struct chrdata *victim = chrFindById(chr, chr->chrseeshot);
if (victim && !chrCompareTeams(chr, victim, COMPARE_FRIENDS)) {
saw_injury = false;
}
} else {
chr->chrseeshot = -1;
}
return saw_injury;
}
bool chrSawDeath(struct chrdata *chr, u8 arg1)
{
bool saw_death = chr->chrseedie >= 0;
// The commented line below was likely originally there but removed before
// the final version. Compare with chrSawInjury above.
if (saw_death && arg1 == 0) {
//chr->chrseedie = -1;
} else if (saw_death && arg1 == 1) {
struct chrdata *victim = chrFindById(chr, chr->chrseedie);
if (victim && !chrCompareTeams(chr, victim, COMPARE_FRIENDS)) {
saw_death = false;
chr->chrseedie = -1;
}
} else {
chr->chrseedie = -1;
}
return saw_death;
}
void decrementByte(u8 *dst, u8 amount)
{
if (*dst < amount) {
*dst = 0;
return;
}
*dst -= amount;
}
void incrementByte(u8 *dst, u8 amount)
{
if (0xff - amount < *dst) {
*dst = 0xff;
return;
}
*dst += amount;
}
bool chrCanHearAlarm(struct chrdata *chr)
{
return alarmIsActive();
}
bool waypointIsWithin90DegreesOfPosAngle(struct waypoint *waypoint, struct coord *pos, f32 angle)
{
u32 stack[3];
f32 diffangle;
struct pad *pad;
pad = &g_Pads[waypoint->padnum];
diffangle = angle - atan2f(pad->pos.x - pos->x, pad->pos.z - pos->z);
if (diffangle < 0) {
diffangle += M_BADTAU;
}
if (diffangle < 1.5705462694168f || diffangle > 4.7116389274597f) {
return true;
}
return false;
}
/**
* Attempt to find a waypoint near pos which is in a particular quadrant to pos,
* then return its padnum.
*
* For example, pos is typically the player's position, angle is the direction
* the player is facing, and quadrant is which quadrant (front/back/left/right)
* that is desired relative to the player's position and angle.
*
* The function starts by finding the closest waypoint to the pos. If it's not
* in the quadrant then its neighouring waypoints are checked too. If none of
* those are in the quadrant then no further checks are made and the function
* returns -1.
*/
s32 chrFindWaypointWithinPosQuadrant(struct coord *pos, s16 *rooms, f32 angle, u8 quadrant)
{
struct waypoint *waypoint = waypointFindClosestToPos(pos, rooms);
s32 neighbournum;
s32 i;
if (waypoint) {
switch (quadrant) {
case QUADRANT_BACK:
angle += M_BADPI;
break;
case QUADRANT_SIDE1:
angle += 1.5705462694168f;
break;
case QUADRANT_SIDE2:
angle += 4.7116389274597f;
break;
case QUADRANT_FRONT:
break;
}
if (angle >= M_BADTAU) {
angle -= M_BADTAU;
}
if (waypointIsWithin90DegreesOfPosAngle(waypoint, pos, angle)) {
return waypoint->padnum;
}
for (i = 0; (neighbournum = waypoint->neighbours[i]) >= 0; i++) {
if ((neighbournum & 0x8000) == 0) {
neighbournum &= 0x3fff;
if (waypointIsWithin90DegreesOfPosAngle(&g_StageSetup.waypoints[neighbournum], pos, angle)) {
return g_StageSetup.waypoints[neighbournum].padnum;
}
}
}
}
return -1;
}
bool func0f04a4ec(struct chrdata *chr, u8 quadrant)
{
if (quadrant == QUADRANT_2NDWPTOTARGET || quadrant == QUADRANT_20) {
struct prop *prop = chr->prop;
struct prop *target = chrGetTargetProp(chr);
struct waypoint *fromwp = waypointFindClosestToPos(&prop->pos, prop->rooms);
struct waypoint *towp = waypointFindClosestToPos(&target->pos, target->rooms);
// @dangerous: I'm creating an array overflow here to get a match.
// waypoints should have len 3 but this causes a mismatch due to too
// much stack usage. If compiling using anything other than IDO and -O2
// then this will need to be changed to 3.
s32 numwaypoints;
struct waypoint *waypoints[2];
u32 hash;
if (fromwp && towp) {
if (quadrant == QUADRANT_2NDWPTOTARGET) {
hash = (g_Vars.lvframe60 >> 9) * 128 + chr->chrnum * 8;
waypointSetHashThing(hash, hash);
numwaypoints = waypointFindRoute(fromwp, towp, waypoints, 3);
waypointSetHashThing(0, 0);
if (numwaypoints >= 3) {
chr->padpreset1 = waypoints[1]->padnum;
return true;
}
} else {
hash = (g_Vars.lvframe60 >> 9) * 128 + chr->chrnum * 8;
waypointSetHashThing(hash, hash);
fromwp = func0f1155e0(fromwp, towp);
waypointSetHashThing(0, 0);
if (fromwp) {
chr->padpreset1 = fromwp->padnum;
return true;
}
}
}
} else {
s32 padnum = chrFindWaypointWithinPosQuadrant(&chr->prop->pos, chr->prop->rooms, chrGetInverseTheta(chr), quadrant);
if (padnum >= 0) {
chr->padpreset1 = padnum;
return true;
}
}
return false;
}
bool chrSetPadPresetToWaypointWithinTargetQuadrant(struct chrdata *chr, u8 quadrant)
{
f32 angle;
s32 padnum;
struct prop *prop;
if (quadrant == QUADRANT_2NDWPTOTARGET || quadrant == QUADRANT_20) {
return func0f04a4ec(chr, quadrant);
}
angle = 0;
prop = chrGetTargetProp(chr);
if (prop->type == PROPTYPE_PLAYER) {
angle = (360.0f - g_Vars.players[playermgrGetPlayerNumByProp(prop)]->vv_theta) * M_BADTAU / 360.0f;
} else if (prop->type == PROPTYPE_CHR) {
angle = chrGetInverseTheta(prop->chr);
}
padnum = chrFindWaypointWithinPosQuadrant(&prop->pos, prop->rooms, angle, quadrant);
if (padnum >= 0) {
chr->padpreset1 = padnum;
return true;
}
return false;
}
bool chrCompareTeams(struct chrdata *chr1, struct chrdata *chr2, u8 checktype)
{
if (chr1 && chr1->prop) {
if (checktype == COMPARE_ANY) {
return true;
}
if (checktype == COMPARE_FRIENDS) { // Return true if chrs are friends
if (g_Vars.normmplayerisrunning) {
if ((g_MpSetup.options & MPOPTION_TEAMSENABLED) && chr2->team == chr1->team) {
return true;
}
} else {
if (g_Vars.bond && g_Vars.bond->prop) {
struct chrdata *playerchr = g_Vars.bond->prop->chr;
// @bug: This makes Jon an ally in Duel
if ((chr2 == playerchr && chr1->headnum == HEAD_JONATHAN) ||
(chr1 == playerchr && chr2->headnum == HEAD_JONATHAN)) {
return true;
}
}
if ((chr2->team & chr1->team) != 0) {
return true;
}
}
} else if (checktype == COMPARE_ENEMIES) { // Return true if chrs are enemies
if (g_Vars.normmplayerisrunning) {
if ((g_MpSetup.options & MPOPTION_TEAMSENABLED) == 0 || chr2->team != chr1->team) {
return true;
}
} else {
if (g_Vars.bond && g_Vars.bond->prop) {
struct chrdata *playerchr = g_Vars.bond->prop->chr;
// @bug: This makes Jon an ally in Duel
if ((chr2 == playerchr && chr1->headnum == HEAD_JONATHAN) ||
(chr1 == playerchr && chr2->headnum == HEAD_JONATHAN)) {
return false;
}
}
if ((chr2->team & chr1->team) == 0) {
return true;
}
}
}
return false;
}
return false;
}
void chrSetChrPreset(struct chrdata *chr, s32 chrpreset)
{
chr->chrpreset1 = chrResolveId(chr, chrpreset);
}
void chrSetChrPresetByChrnum(struct chrdata *basechr, s32 chrnum, s32 chrpreset)
{
struct chrdata *chr = chrFindById(basechr, chrnum);
if (chr) {
chr->chrpreset1 = chrResolveId(basechr, chrpreset);
}
}
void chrSetPadPreset(struct chrdata *chr, s32 pad_id)
{
chr->padpreset1 = chrResolvePadId(chr, pad_id);
}
bool chrIsPosOffScreen(struct coord *pos, s16 *rooms)
{
bool offscreen = true;
s32 i;
if (env0f1666f8(pos, 0)) {
for (i = 0; rooms[i] != -1; i++) {
if (roomIsOnscreen(rooms[i])) {
break;
}
}
if (rooms[i] != -1) {
// Room is visible by player
offscreen = !camIsPosInScreenBox(pos, 200, func0f158140(rooms[i]));
}
}
return offscreen;
}
/**
* Test if a chr can be spawned into or nearby the given position, taking into
* consideration collision checks.
*
* If the spawn can happen, the position and rooms are updated with the actual
* position to be used and the function returns true.
*
* If the spawn cannot happen, the function return false.
*/
bool chrAdjustPosForSpawn(f32 chrradius, struct coord *pos, s16 *rooms, f32 angle, bool allowonscreen, bool ignorebg, bool arg6)
{
struct coord testpos;
s32 i;
u32 types;
s16 testrooms[8];
f32 ymin;
f32 ymax = 200;
f32 curangle = angle;
f32 ground;
if (ignorebg) {
types = CDTYPE_ALL & ~CDTYPE_BG;
allowonscreen = true;
} else {
types = CDTYPE_ALL;
}
if (arg6) {
// Skip testing the given pos, and just do the surrounding checks below.
// Add 45 degrees to the angle here, but this isn't necessary.
curangle += 0.7852731347084f;
if (curangle >= M_BADTAU) {
curangle -= M_BADTAU;
}
} else {
// Check that the chr isn't being spawned out of bounds, and do a volume
// test reaching 200cm above and below the chr's feet... unless there is
// ground under the chr (highly likely), in which case reduce the volume
// to be tested to the ground Y value. I'm not sure why this is useful,
// because if the chr was being spawned on top of another chr or object
// then the calculated ground value would be raised.
ymin = -200;
ground = cdFindGroundAtCyl(pos, chrradius, rooms, NULL, NULL);
if (ground > -100000 && ground - pos->y < -200) {
ymin = ground - pos->y;
}
if (cdTestVolume(pos, chrradius, rooms, types, CHECKVERTICAL_YES, ymax, ymin) != CDRESULT_COLLISION
&& (allowonscreen || chrIsPosOffScreen(pos, rooms))) {
return true;
}
}
// Try 60cm in 8 directions
for (i = 0; i < 8; i++) {
testpos.x = pos->x + sinf(curangle) * 60;
testpos.y = pos->y;
testpos.z = pos->z + cosf(curangle) * 60;
if ((arg6 && cdTestCylMove04(pos, rooms, &testpos, testrooms, CDTYPE_ALL & ~CDTYPE_PLAYERS, 1, ymax, -200) != CDRESULT_COLLISION)
|| (!arg6 && cdTestLos11(pos, rooms, &testpos, testrooms, CDTYPE_BG))) {
chr0f021fa8(NULL, &testpos, testrooms);
ground = cdFindGroundAtCyl(&testpos, chrradius, testrooms, 0, 0);
ymin = -200;
if (ground > -100000 && ground - pos->y < -200) {
ymin = ground - pos->y;
}
if (cdTestVolume(&testpos, chrradius, testrooms, CDTYPE_ALL, CHECKVERTICAL_YES, ymax, ymin) != CDRESULT_COLLISION
&& (allowonscreen || chrIsPosOffScreen(&testpos, testrooms))
&& (!arg6 || ground > -100000)) {
*pos = testpos;
roomsCopy(testrooms, rooms);
return true;
}
}
curangle += 0.7852731347084f;
if (curangle >= M_BADTAU) {
curangle -= M_BADTAU;
}
}
return false;
}
/**
* Attempts to spawn a chr at the given coordinates.
*
* In low memory conditions, the function will iterate all existing chrs in
* search of a corpse that can be reaped. If one is found then the reap will be
* triggered, but the function will not attempt to spawn the chr until the next
* time it's called.
*/
struct prop *chrSpawnAtCoord(s32 bodynum, s32 headnum, struct coord *pos, s16 *rooms, f32 angle, u8 *ailist, u32 spawnflags)
{
struct prop *prop;
struct coord pos2;
s16 rooms2[8];
s32 stack;
if (chrsGetNumFree() > 1) {
if (headnum < 0) {
headnum = bodyChooseHead(bodynum);
}
pos2 = *pos;
roomsCopy(rooms, rooms2);
if (chrAdjustPosForSpawn(20, &pos2, rooms2, angle, (spawnflags & SPAWNFLAG_00000010) != 0, 0, 0)) {
struct model *model = bodyAllocateModel(bodynum, headnum, spawnflags);
struct chrdata *chr;
if (model) {
prop = chrAllocate(model, &pos2, rooms2, angle, ailist);
if (prop) {
propActivateThisFrame(prop);
propEnable(prop);
chr = prop->chr;
chr->headnum = headnum;
chr->bodynum = bodynum;
chr->race = bodyGetRace(chr->bodynum);
chr->flags = 0;
chr->flags2 = 0;
chr->hidden2 |= CHRH2FLAG_SPAWNED;
if (spawnflags & SPAWNFLAG_NOBLOOD) {
chr->noblood = true;
}
return prop;
}
modelmgrFreeModel(model);
}
}
}
// Low memory - find a corpse to reap
if (chrsGetNumFree() < 4) {
s32 stack2;
struct chrdata *replacechr;
s32 startindex;
s32 index;
replacechr = NULL;
startindex = random() % g_NumChrSlots;
index = startindex;
do {
if (g_ChrSlots[index].chrnum >= 0 && g_ChrSlots[index].model && g_ChrSlots[index].prop) {
if (g_ChrSlots[index].actiontype == ACT_DEAD
|| (g_ChrSlots[index].actiontype == ACT_DRUGGEDKO && (g_ChrSlots[index].chrflags & CHRCFLAG_KEEPCORPSEKO) == 0)) {
// If we've found a chr that's ready to be reaped, great.
// Bail out of the loop.
if (g_ChrSlots[index].act_dead.invistimer60 >= TICKS(120)) {
replacechr = &g_ChrSlots[index];
break;
}
// Otherwise, this chr is dead/KO'ed and can be reaped as a
// last resort, so store them and keep looping in search of
// a better chr.
if (replacechr == NULL) {
replacechr = &g_ChrSlots[index];
}
}
}
index = (index + 1) % g_NumChrSlots;
} while (index != startindex);
if (replacechr) {
replacechr->act_dead.fadewheninvis = true;
replacechr->act_dead.fadenow = true;
}
}
return NULL;
}
struct prop *chrSpawnAtPad(struct chrdata *basechr, s32 body, s32 head, s32 pad_id, u8 *ailist, u32 spawnflags)
{
s32 resolved_pad_id = chrResolvePadId(basechr, pad_id);
struct pad *pad;
s16 room[2];
f32 fvalue;
pad = &g_Pads[resolved_pad_id];
fvalue = atan2f(pad->look.x, pad->look.z);
room[0] = pad->room;
room[1] = -1;
return chrSpawnAtCoord(body, head, &pad->pos, &room[0], fvalue, ailist, spawnflags);
}
struct prop *chrSpawnAtChr(struct chrdata *basechr, s32 body, s32 head, u32 chrnum, u8 *ailist, u32 spawnflags)
{
struct chrdata *chr = chrFindById(basechr, chrnum);
f32 fvalue;
if (1) {
fvalue = chrGetInverseTheta(chr);
}
return chrSpawnAtCoord(body, head, &chr->prop->pos, chr->prop->rooms, fvalue, ailist, spawnflags);
}
bool chrMoveToPos(struct chrdata *chr, struct coord *pos, s16 *rooms, f32 angle, bool allowonscreen)
{
struct coord pos2;
s16 rooms2[8];
bool result = false;
u32 nodetype;
union modelrwdata *rwdata;
struct player *player;
f32 ground;
pos2 = *pos;
roomsCopy(rooms, rooms2);
propSetPerimEnabled(chr->prop, false);
if (chrAdjustPosForSpawn(chr->radius, &pos2, rooms2, angle, (chr->hidden & CHRHFLAG_00100000) != 0, allowonscreen, (chr->hidden & CHRHFLAG_00000200) != 0)) {
ground = cdFindGroundInfoAtCyl(&pos2, chr->radius, rooms2, &chr->floorcol,
&chr->floortype, NULL, &chr->floorroom, NULL, NULL);
chr->ground = ground;
chr->manground = ground;
chr->sumground = ground * (PAL ? 8.4175090789795f : 9.999998f);
chr->prop->pos = pos2;
propDeregisterRooms(chr->prop);
roomsCopy(rooms2, chr->prop->rooms);
chr0f0220ac(chr);
modelSetRootPosition(chr->model, &pos2);
nodetype = chr->model->filedata->rootnode->type;
if ((nodetype & 0xff) == MODELNODETYPE_CHRINFO) {
rwdata = modelGetNodeRwData(chr->model, chr->model->filedata->rootnode);
rwdata->chrinfo.ground = ground;
}
chr->chrflags |= CHRCFLAG_00000001;
chrSetLookAngle(chr, angle);
if (chr->prop->type == PROPTYPE_PLAYER) {
player = g_Vars.players[playermgrGetPlayerNumByProp(chr->prop)];
player->vv_manground = ground;
player->vv_ground = ground;
player->vv_theta = ((M_BADTAU - angle) * 360.0f) / M_BADTAU;
player->vv_verta = 0;
player->unk1c64 = 1;
}
result = true;
}
propSetPerimEnabled(chr->prop, true);
return result;
}
bool chrCheckCoverOutOfSight(struct chrdata *chr, s32 covernum, bool soft)
{
struct cover cover;
struct prop *target;
bool targetcanseecover;
// @bug: Should be >= coverGetCount()
if (covernum < 0 || covernum > coverGetCount() || !coverUnpack(covernum, &cover)) {
return false;
}
target = chrGetTargetProp(chr);
if (!target) {
return false;
}
if (soft) {
targetcanseecover = cdTestLos03(&target->pos, target->rooms, cover.pos,
CDTYPE_OBJS | CDTYPE_DOORS | CDTYPE_BG,
GEOFLAG_BLOCK_SIGHT);
} else {
targetcanseecover = cdIsNearlyInSight(&target->pos, target->rooms, cover.pos, 50.0f,
CDTYPE_OBJS | CDTYPE_DOORS | CDTYPE_BG);
}
return !targetcanseecover;
}
s32 chrAssignCoverByCriteria(struct chrdata *chr, u16 criteria, s32 refdist)
{
s16 rooms[8];
struct cover cover;
struct covercandidate tmp;
s32 oldcover;
s32 i;
struct prop *roomprop;
s32 numcovers = coverGetCount();
s32 numcandidates = 0;
struct prop *target = chrGetTargetProp(chr);
bool userandomdist = false;
bool changed;
f32 sqdist;
f32 y = chr->prop->pos.y + 170;
s32 currefdist = refdist;
struct prop *gotoprop;
if (criteria & COVERCRITERIA_DISTTOFETCHPROP) {
if (!chr->aibot || !chr->aibot->gotoprop) {
return -1;
}
gotoprop = chr->aibot->gotoprop;
}
if (chr == NULL) {
return 0;
}
oldcover = chr->cover;
refdist *= refdist;
// Iterate all cover, filter them by criteria and store them in g_CoverCandidates
for (i = 0; i < numcovers; i++) {
if (coverUnpack(i, &cover)
&& ((criteria & COVERCRITERIA_2000) == 0 || (cover.flags & COVERFLAG_OMNIDIRECTIONAL))
&& ((criteria & COVERCRITERIA_1000) || (cover.flags & COVERFLAG_AIMDIFFROOM) == 0 || !arrayIntersects(cover.rooms, target->rooms))) {
userandomdist = false;
if ((criteria & COVERCRITERIA_0001) && (criteria & COVERCRITERIA_FURTHEREST)) {
userandomdist = true;
criteria &= ~(COVERCRITERIA_0001 | COVERCRITERIA_FURTHEREST);
}
if (((criteria & COVERCRITERIA_FORCENEWCOVER) == 0 || i != oldcover)
&& ((criteria & COVERCRITERIA_2000) || !(coverIsInUse(i) || cover.pos->y > y))) {
if (criteria & COVERCRITERIA_ROOMSFROMME) {
roomprop = chr->prop;
} else if (criteria & COVERCRITERIA_ROOMSFROMTARGET) {
roomprop = target;
} else if (criteria & COVERCRITERIA_DISTTOTARGET) {
roomprop = target;
} else {
roomprop = chr->prop;
}
rooms[0] = roomprop->rooms[0];
rooms[1] = -1;
if (criteria & COVERCRITERIA_ALLOWNEIGHBOURINGROOMS) {
roomGetNeighbours(roomprop->rooms[0], &rooms[1], 6);
} else if (criteria & COVERCRITERIA_ONLYNEIGHBOURINGROOMS) {
roomGetNeighbours(roomprop->rooms[0], &rooms[0], 7);
}
if (((criteria & COVERCRITERIA_0040) == 0 || !arrayIntersects(cover.rooms, rooms))
&& ((criteria & COVERCRITERIA_0020) == 0 || arrayIntersects(cover.rooms, rooms))
&& (rooms[1] == -1
|| chr->oldrooms[0] == -1
|| (criteria & COVERCRITERIA_0200) == 0
|| !arrayIntersects(cover.rooms, chr->oldrooms))) {
if (criteria & COVERCRITERIA_DISTTOME) {
sqdist = chrGetSquaredDistanceToCoord(chr, cover.pos);
} else if (criteria & COVERCRITERIA_DISTTOTARGET) {
sqdist = coordGetSquaredDistanceToCoord(&target->pos, cover.pos);
} else if (criteria & COVERCRITERIA_DISTTOFETCHPROP) {
sqdist = coordGetSquaredDistanceToCoord(&gotoprop->pos, cover.pos);
} else if (userandomdist) {
sqdist = random() % 0xf000;
} else {
sqdist = 0;
currefdist = 0;
}
if (!currefdist
|| (currefdist < 0 && sqdist > refdist)
|| (currefdist > 0 && sqdist < refdist)) {
g_CoverCandidates[numcandidates].sqdist = sqdist;
g_CoverCandidates[numcandidates].covernum = i;
numcandidates++;
}
}
}
}
}
// Sort candidates by distance ascending, or descending if using COVERCRITERIA_FURTHEREST
if (numcandidates >= 2) {
do {
changed = false;
for (i = 0; i < numcandidates - 1; i++) {
if ((((criteria & COVERCRITERIA_0001) || (criteria & COVERCRITERIA_DISTTOFETCHPROP) || userandomdist) && (g_CoverCandidates[i].sqdist > g_CoverCandidates[i + 1].sqdist))
|| ((criteria & COVERCRITERIA_FURTHEREST) && g_CoverCandidates[i].sqdist < g_CoverCandidates[i + 1].sqdist)) {
changed = true;
tmp = g_CoverCandidates[i];
g_CoverCandidates[i] = g_CoverCandidates[i + 1];
g_CoverCandidates[i + 1] = tmp;
}
}
} while (changed);
}
// Assign the first out of sight cover
for (i = 0; i < numcandidates; i++) {
if (1 && chrCheckCoverOutOfSight(chr, g_CoverCandidates[i].covernum, criteria & COVERCRITERIA_ALLOWSOFT)) {
chr->cover = g_CoverCandidates[i].covernum;
if (oldcover != -1) {
coverSetInUse(oldcover, false);
}
coverSetInUse(chr->cover, true);
return g_CoverCandidates[i].covernum;
}
}
return -1;
}
/**
* Find cover in the opposite direction (?) of the chr's runfrompos and assign
* it to the chr. The distance from the runfrompos to the cover must be between
* mindist and maxdist along the X/Z plane. The chr will not choose cover more
* than 170cm higher than their current elevation, but strangely there is no
* lower Y limit.
*
* Testing with this function logic results in the following, where:
* R = run from pos
* C = chr's pos
* A = available cover
* . = unavailable cover
*
* A A A A A A A A A A A A A A . . . . . .
* A A A A A A A A A A A A A A . . . . . .
* A A A A A A A A A A A A A A . . . . . .
* A A A A A A A A A A A A A A . . . . . .
* A A A A A A A A A A A A A A . . . . . .
* A A A A A A A R A A A A A A . . . . . .
* A A A A A A A A A A A A A A . . . . . .
* A A A A A A A A A A A A A A . . . . . .
* A A A A A A A A A A A A A A . . . . . .
* A A A A A A A A A A A A A A . . . . . .
* A A A A A A A A A A A A A A . . . . . .
* A A A A A A A A A A A A A A . . . . . .
* A A A A A A A A A A A A A A . . . . . .
* A A A A A A A A A A A A A A . . . . . .
* A A A A A A A A A A A A A A . . . . . A
* A A A A A A A A A A A A A A . . . A A A
* A A A A A A A A A A A A A A . A A A A A
* A A A A A A A A A A A A A C A A A A A A
* . . . . . . . . . . . A A A A A A A A A
* . . . . . . . . . A A A A A A A A A A A
*
* The block of available cover around the runfrompos might be a bug, but if
* this function is called with mindist = chr - runfrompos or higher then it
* will work as expected.
*
* Preference is given to cover which is the "most opposite", meaning ones
* which are directly behind the chr from the perspective of runfrompos.
*/
s32 chrAssignCoverAwayFromDanger(struct chrdata *chr, s32 mindist, s32 maxdist)
{
s32 i;
f32 vecfromdanger[2];
f32 vectocover[2];
f32 y;
f32 ymax;
f32 bestsqdist;
f32 sqdist;
s32 numcovers;
s32 prevcover;
s32 newcover;
f32 coversqdistfrompos;
struct cover cover;
ymax = chr->prop->pos.y + 170;
y = 0;
bestsqdist = 0;
newcover = -1;
numcovers = coverGetCount();
prevcover = chr->cover;
mindist *= mindist;
maxdist *= maxdist;
if (mindist);
if (maxdist);
vecfromdanger[0] = chr->prop->pos.x - chr->runfrompos.x;
vecfromdanger[1] = chr->prop->pos.z - chr->runfrompos.z;
guNormalize(&vecfromdanger[0], &y, &vecfromdanger[1]);
for (i = 0; i < numcovers; i++) {
if (coverUnpack(i, &cover) && !coverIsInUse(i) && !(cover.pos->y > ymax)) {
coversqdistfrompos = coordGetSquaredDistanceToCoord(&chr->runfrompos, cover.pos);
if (!(coversqdistfrompos < mindist) && !(coversqdistfrompos > maxdist)) {
vectocover[0] = cover.pos->x - chr->prop->pos.x;
vectocover[1] = cover.pos->z - chr->prop->pos.z;
guNormalize(&vectocover[0], &y, &vectocover[1]);
sqdist = vecfromdanger[0] * vectocover[0] + vecfromdanger[1] * vectocover[1];
if (!(sqdist < 0) && sqdist > bestsqdist) {
bestsqdist = sqdist;
newcover = i;
if (1);
}
}
}
if (numcovers);
}
chr->cover = newcover;
if (newcover != -1) {
if (prevcover != -1) {
coverSetInUse(prevcover, false);
}
coverSetInUse(chr->cover, true);
}
return newcover;
}
s16 chrGoToCover(struct chrdata *chr, u8 speed)
{
struct cover cover;
if (!chr) {
return 0;
}
if (chrIsReadyForOrders(chr) && chr->cover != -1 && coverUnpack(chr->cover, &cover)) {
chrGoToRoomPos(chr, cover.pos, &cover.rooms[0], speed);
return chr->cover;
}
return -1;
}
bool chrRunFromPos(struct chrdata *chr, u32 goposflags, f32 rundist, struct coord *frompos)
{
f32 curdistfrompos;
struct coord delta;
s16 rooms[8];
if (chrIsReadyForOrders(chr)) {
delta.x = chr->prop->pos.x - frompos->x;
delta.y = chr->prop->pos.y;
delta.z = chr->prop->pos.z - frompos->z;
// @bug: This check should be &&. The runfrompos will fail if the
// frompos is on the same X or Z axis as the chr, which is unlikely
// because it's a floating point number.
if (delta.f[0] == 0.0f || delta.f[2] == 0.0f) {
return false;
}
curdistfrompos = sqrtf(delta.f[0] * delta.f[0] + delta.f[2] * delta.f[2]);
delta.x *= rundist / curdistfrompos;
delta.z *= rundist / curdistfrompos;
chrSetPerimEnabled(chr, false);
if (cdExamLos08(&chr->prop->pos, chr->prop->rooms, &delta, CDTYPE_ALL, GEOFLAG_WALL) == CDRESULT_COLLISION) {
cdGetPos(&delta, 18547, "chraction.c");
}
chrSetPerimEnabled(chr, true);
func0f065e74(&chr->prop->pos, chr->prop->rooms, &delta, rooms);
return chrGoToRoomPos(chr, &delta, rooms, goposflags);
}
return false;
}
void chrAddTargetToBdlist(struct chrdata *chr)
{
if (chr->prop) {
struct prop *target = chrGetTargetProp(chr);
s32 i;
if (target) {
for (i = 0; i < g_Vars.lvupdate60; i++) {
chr->bdlist[chr->bdstart] = target->pos.x - chr->prop->pos.x;
chr->bdstart++;
chr->bdstart %= 60;
chr->bdlist[chr->bdstart] = target->pos.z - chr->prop->pos.z;
chr->bdstart++;
chr->bdstart %= 60;
}
}
}
}
s32 chrGetDistanceLostToTargetInLastSecond(struct chrdata *chr)
{
s32 *bdlist = &chr->bdlist[0];
s32 index = chr->bdstart;
u32 stack[2];
s32 x1 = bdlist[(index + 1) % 60];
s32 z1 = bdlist[index];
s32 olddist = sqrtf(x1 * x1 + z1 * z1);
s32 x2 = bdlist[(index + 59) % 60];
s32 z2 = bdlist[(index + 58) % 60];
s32 curdist = sqrtf(x2 * x2 + z2 * z2);
return curdist - olddist;
}
bool chrIsNearlyInTargetsSight(struct chrdata *chr, u32 distance)
{
struct prop *target = chrGetTargetProp(chr);
return cdIsNearlyInSight(&target->pos, target->rooms, &chr->prop->pos, distance, CDTYPE_BG);
}
f32 func0f04c784(struct chrdata *chr)
{
f32 targetfacingangle = 0;
u32 stack;
struct prop *target = chrGetTargetProp(chr);
f32 angletotarget;
f32 result;
if (target->type == PROPTYPE_CHR) {
targetfacingangle = chrGetInverseTheta(target->chr);
} else if (target->type == PROPTYPE_PLAYER) {
s32 playernum = playermgrGetPlayerNumByProp(target);
targetfacingangle = g_Vars.players[playernum]->vv_theta;
}
angletotarget = atan2f(target->pos.z - chr->prop->pos.z, target->pos.x - chr->prop->pos.x);
result = (angletotarget * 360 / M_BADTAU - targetfacingangle) + 90;
if (result > 180) {
result -= 360;
}
return result;
}
bool chr0f04c874(struct chrdata *chr, u32 angle360, struct coord *pos, u8 arg3, u8 arg4)
{
struct prop *target = chrGetTargetProp(chr);
f32 sqdist = 0;
f32 f24 = func0f04c784(chr);
f32 cosine;
f32 sine;
struct coord chrpos;
f32 xdiff;
f32 ydiff;
f32 zdiff;
f32 scale;
bool again;
struct coord saved;
s32 result;
f32 ymax;
f32 ymin;
f32 radius;
chrpos = chr->prop->pos;
do {
f32 angle360f = angle360;
if (angle360f > 180) {
angle360f -= 360;
}
angle360f -= f24;
angle360f *= 0.017450513318181f;
cosine = cosf(angle360f);
sine = sinf(angle360f);
xdiff = chrpos.x - target->pos.x;
zdiff = chrpos.z - target->pos.z;
pos->x = target->pos.x + (xdiff * cosine - zdiff * sine);
pos->y = chrpos.y;
pos->z = target->pos.z + (xdiff * sine + zdiff * cosine);
chrGetBbox(chr->prop, &radius, &ymax, &ymin);
result = cdExamCylMove03(&chrpos, chr->prop->rooms, pos,
CDTYPE_BG | CDTYPE_OBJS | CDTYPE_DOORS, 1,
ymax - chrpos.f[1],
ymin - chrpos.f[1]);
if (result == CDRESULT_COLLISION) {
f32 xdiff;
f32 zdiff;
f32 tmp;
cdGetPos(pos, 18686, "chraction.c");
xdiff = pos->x - chrpos.x;
zdiff = pos->z - chrpos.z;
tmp = sqrtf(xdiff * xdiff + zdiff * zdiff);
scale = (tmp - 50.0f) / tmp;
if (scale < 0) {
*pos = chrpos;
} else {
xdiff *= scale;
zdiff *= scale;
pos->x = chrpos.x + xdiff;
pos->y = chrpos.y;
pos->z = chrpos.z + zdiff;
}
}
if (arg3) {
xdiff = chrpos.x - pos->x;
ydiff = chrpos.y - pos->y;
zdiff = chrpos.z - pos->z;
sqdist = xdiff * xdiff + ydiff * ydiff + zdiff * zdiff;
saved = *pos;
angle360 = 360 - angle360;
again = true;
arg3 = 0;
} else {
again = false;
}
} while (again);
if (sqdist != 0) {
f32 sqdist2;
xdiff = chrpos.x - pos->x;
ydiff = chrpos.y - pos->y;
zdiff = chrpos.z - pos->z;
sqdist2 = xdiff * xdiff + ydiff * ydiff + zdiff * zdiff;
if (sqdist2 < sqdist) {
pos->x = saved.f[0];
pos->y = saved.f[1];
pos->z = saved.f[2];
}
}
chrGoToPos(chr, pos, arg4);
return true;
}
/**
* Chrs are assigned to teams, and they can be assigned to more than one.
* The team assignments determine if a chr considers another chr to be friendly
* or not.
*
* The chr->team value is a bitmask of which teams they belong to. There are 8
* teams total.
*
* The team list is an array of 264 shorts. The first 7 are indexes into
* the same list which mark the start of each team. Team 0 does not have
* an entry in this list because it always starts at offset 7.
*
* Elements 7 onwards are chrnums. Each team is terminated with -2.
*/
void rebuildTeams(void)
{
s32 numchrs = chrsGetNumSlots();
s16 index = 7;
s32 team;
s32 i;
struct chrdata *chr;
u8 teammasks[] = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 };
for (team = 0; team < 8; team++) {
if (team != 0) {
g_TeamList[team - 1] = index;
}
for (i = 0; i < numchrs; i++) {
chr = &g_ChrSlots[i];
if (chr->chrnum >= 0 && (chr->team & teammasks[team])) {
g_TeamList[index] = chr->chrnum;
index++;
}
}
g_TeamList[index] = -2;
index++;
if (index >= 264) {
break;
}
}
}
/**
* Chrs are partitioned into squadrons for AI scripting purposes, where their
* squadron number is in the range 0-15.
*
* The squadron list is an array of 272 shorts. The first 15 are indexes into
* the same list which mark the start of each squadron. Squadron 0 does not have
* an entry in this list because it always starts at offset 15.
*
* Elements 15 onwards are chrnums. Each squadron is terminated with -2.
*/
void rebuildSquadrons(void)
{
s32 numchrs = chrsGetNumSlots();
s16 index = 15;
s32 squadron;
s32 i;
for (squadron = 0; squadron < 16; squadron++) {
if (squadron != 0) {
g_SquadronList[squadron - 1] = index;
}
for (i = 0; i < numchrs; i++) {
struct chrdata *chr = &g_ChrSlots[i];
if (chr->chrnum >= 0 && chr->squadron == squadron) {
if (chr->prop == NULL || chr->prop->type != PROPTYPE_PLAYER) {
g_SquadronList[index] = chr->chrnum;
index++;
}
}
}
g_SquadronList[index] = -2;
index++;
if (index >= 272) {
break;
}
}
}
s16 *teamGetChrIds(s32 team_id)
{
s32 i;
u8 lookup[8] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};
for (i = 0; i != MAX_TEAMS; i++) {
if (lookup[i] == team_id) {
team_id = i;
break;
}
}
if (team_id < 0 || team_id >= MAX_TEAMS) {
return NULL;
}
if (team_id != 0) {
return &g_TeamList[g_TeamList[team_id - 1]];
}
return &g_TeamList[MAX_TEAMS - 1];
}
s16 *squadronGetChrIds(s32 squadron_id)
{
if (squadron_id < 0 || squadron_id >= MAX_SQUADRONS) {
return NULL;
}
if (squadron_id != 0) {
return &g_SquadronList[g_SquadronList[squadron_id - 1]];
}
return &g_SquadronList[MAX_SQUADRONS - 1];
}
void audioMarkAsRecentlyPlayed(s16 audioid)
{
g_RecentQuipsPlayed[g_RecentQuipsIndex++] = audioid;
if (g_RecentQuipsIndex > 4) {
g_RecentQuipsIndex = 0;
}
}
bool audioWasNotPlayedRecently(s16 audioid)
{
u8 i;
for (i = 0; i < 5; i++) {
if (g_RecentQuipsPlayed[i] == audioid) {
return false;
}
}
return true;
}
void chrToggleModelPart(struct chrdata *chr, s32 partnum)
{
if (chr && chr->model && chr->model->filedata) {
struct modelnode *node = modelGetPart(chr->model->filedata, partnum);
union modelrwdata *rwdata = NULL;
if (node) {
rwdata = modelGetNodeRwData(chr->model, node);
}
if (rwdata) {
bool visible = rwdata->toggle.visible;
rwdata->toggle.visible = !visible;
}
}
}
/**
* Checks if the chr is doing an avoiding animation.
*
* These animations are possibly unused. In each one, the chr jumps backwards or
* to the side as if avoiding something, then looks at whatever it was that just
* went past.
*/
bool chrIsAvoiding(struct chrdata *chr)
{
s32 anim = modelGetAnimNum(chr->model);
chr->chrflags &= ~CHRCFLAG_10000000;
// Possible @bug or just sloppy code: The flag check below can never pass
// because that flag was just turned off above.
if (anim == ANIM_0064
|| anim == ANIM_0065
|| anim == ANIM_0066
|| anim == ANIM_0067
|| (chr->chrflags & CHRCFLAG_10000000)) {
return true;
}
return false;
}
void chrDrCarollEmitSparks(struct chrdata *chr)
{
if (chr && chr->prop) {
propsnd0f0939f8(0, chr->prop, SFX_SHIELD_DAMAGE, -1, -1, 0, 0, 0, 0, -1, 0, -1, -1, -1, -1);
sparksCreate(chr->prop->rooms[0], chr->prop, &chr->prop->pos, NULL, 0, SPARKTYPE_ELECTRICAL);
}
}