From 4f35c7b6bf4e5eb5a7fbeb1f6fe536d4204fce58 Mon Sep 17 00:00:00 2001 From: Marco Cawthorne Date: Mon, 18 Sep 2023 15:34:23 -0700 Subject: [PATCH] NSMonster: Explicitly reset animation time before attack acts happen, add class documentation --- src/shared/NSMonster.h | 112 +++++++++++++++++++++++++++++++--------- src/shared/NSMonster.qc | 39 +++++++++++--- 2 files changed, 120 insertions(+), 31 deletions(-) diff --git a/src/shared/NSMonster.h b/src/shared/NSMonster.h index 44fdfadc..799f30f9 100644 --- a/src/shared/NSMonster.h +++ b/src/shared/NSMonster.h @@ -179,10 +179,10 @@ typedef enum /** Alliance states. */ typedef enum { - MAL_FRIEND, /* friendly towards the player */ - MAL_ENEMY, /* unfriendly towards the player */ - MAL_ALIEN, /* unfriendly towards anyone but themselves */ - MAL_ROGUE /* no allies, not even amongst themselves */ + MAL_FRIEND, /**< 1, friendly towards the player */ + MAL_ENEMY, /**< 2, unfriendly towards the player */ + MAL_ALIEN, /**< 3, unfriendly towards anyone but themselves */ + MAL_ROGUE /**< 4, no allies, not even amongst themselves */ } allianceState_t; /** Movement states */ @@ -199,37 +199,100 @@ it's set to. If any of those checks are successful, we trigger our target under the m_strTriggerTarget attribute. */ typedef enum { - MTRIG_NONE, /**< nothing */ - MTRIG_SEEPLAYER_ANGRY, /**< we see an enemy player, that we want to harm */ - MTRIG_PAIN, /**< taken damage */ - MTRIG_HALFHEALTH, /**< lost half of our base_health */ - MTRIG_DEATH, /**< we have died. */ - MTRIG_SQUADMEMBERDEAD, /**< a squad member died */ - MTRIG_SQUADLEADERDEAD, /**< the squad leader died */ - MTRIG_HEARNOISE, /**< we hear some noise around the world. */ - MTRIG_HEARENEMYPLAYER, /**< we hear a player we are enemies with */ - MTRIG_HEARWEAPONS, /**< we hear weapons being fired */ - MTRIG_SEEPLAYER, /**< we see a player, don't have to be angry at him. */ - MTRIG_SEEPLAYER_RELAXED, /**< we see a player and we're currently attacking anything */ + MTRIG_NONE, /**< 1, nothing */ + MTRIG_SEEPLAYER_ANGRY, /**< 2, we see an enemy player, that we want to harm */ + MTRIG_PAIN, /**< 3, taken damage */ + MTRIG_HALFHEALTH, /**< 4, lost half of our base_health */ + MTRIG_DEATH, /**< 5, we have died. */ + MTRIG_SQUADMEMBERDEAD, /**< 6, a squad member died */ + MTRIG_SQUADLEADERDEAD, /**< 7, the squad leader died */ + MTRIG_HEARNOISE, /**< 8, we hear some noise around the world. */ + MTRIG_HEARENEMYPLAYER, /**< 9, we hear a player we are enemies with */ + MTRIG_HEARWEAPONS, /**< 10, we hear weapons being fired */ + MTRIG_SEEPLAYER, /**< 11, we see a player, don't have to be angry at him. */ + MTRIG_SEEPLAYER_RELAXED, /**< 12, we see a player and we're currently attacking anything */ } triggerCondition_t; /* FIXME: I'd like to move this into NSMonster, but our current IsFriend() * check is currently only checking on a .takedamage basis. */ .int m_iAlliance; -/** This entity class represents non-player characters. +/*! \brief This entity class represents non-player characters. */ +/*!QUAKED NSMonster (0 0.8 0.8) (-16 -16 0) (16 16 72) WAITTILLSEEN GAG MONSTERCLIP x PRISONER x IGNOREPLAYER WAITFORSCRIPT PREDISASTER FADECORPSE MULTIPLAYER FALLING HORDE +# OVERVIEW +This entity class represents non-player characters. They have the ability to move around (or stand still) but are all capable of fighting if prompted to. -There are a few methods that you need to reimplement in order for them -to do some basic combat: +# KEYS +- "targetname" : Name +- "netname" : Name used for obituaries and debug info. +- "maxs" : Bounding box mins. +- "mins" : Bounding box maxs. - virtual void(void) AttackDraw; - virtual void(void) AttackHolster; - virtual int(void) AttackMelee; - virtual int(void) AttackRanged; +## KEYS - TRIGGERS +- "TriggerCondition" : See triggerCondition_t for which numerical values to pick. +- "TriggerTarget" : Will trigger this entity when TriggerCondition (triggerCondition_t) is met. -Check their individual descriptions as to how you're supposed to approach them. +## KEYS - BEHAVIOUR +- "health" : Starting health. +- "team" : Alliance. See allianceState_t for which numerical values to pick. +- "speed_walk" : Walk speed in units per second. +- "speed_run" : Run speed in units per second. +- "eye_height" : Height in units at which to place the eyes from the origin. Use the cvar r_showViewCone to debug it. +- "snd_sight" : SoundDef to play upon 'alert'. +- "snd_idle" : SoundDef to play when the monster is idle. +- "idle_min" : Min idle delay in seconds. +- "idle_max" : Max idle delay in seconds. +- "snd_footstep" : Which soundDef to play when a footstep should occur. +- "snd_chatter" : An idle type soundDef to play. +- "snd_chatter_combat" : An idle type soundDef, only during combat. +- "snd_pain" : SoundDef to play when in pain. +- "snd_death" : SoundDef to play when death settles in. +- "snd_thud" : SoundDef to play when the monster falls to the ground. + +## KEYS - ATTACK (MELEE) +- "def_attack_melee" : Which entityDef to look into for a melee attack. [CONTINUED] +- "attack_melee_range" : Range under which melee attacks occur. +- "snd_melee_attack" : SoundDef to play when melee attacking. +- "snd_melee_attack_hit" : SoundDef to play when a successful melee attack occurs. +- "snd_melee_attack_miss" : SoundDef to play when a melee attack misses. + +## KEYS - ATTACK (RANGED) +- "def_attack_ranged_1" : EntityDef that contains primary ranged attack info. +- "attack_ranged1_range" : Range for the primary ranged attack. +- "def_attack_ranged_2" : EntityDef that contains secondary ranged attack info. +- "attack_ranged2_range" : Range for the secondary ranged attack. +- "snd_ranged_attack" : SoundDef to play upon ranged attack. +- "reload_count" : how many ranged attacks until reload. Only affects primary ranged attacks. +- "reload_delay" : Time between reloads in seconds. Requires `reload_count` to be set > 0. +- "snd_reload" : SoundDef to play when reloading. +- "attack_cone" : Cone in which to attack. +- "attack_accuracy" : Accuracy (or rather, lack of) multiplier. + +## KEYS - ATTACK (SPECIAL) +- "def_attack_special_1" : EntityDef that contains primary special attack info. Intended for projectiles. +- "attack_special1_range" : Range for the primary special attack. +- "num_projectiles" : The number of primary special projectiles to shoot. +- "projectile_spread" : Spread of the projectiles. 0 is none. 1 is the max. +- "projectile_delay" : Delay in seconds until a special attack projectile is thrown. + +- "weapon_drawn" : Whether or not the weapon is drawn by default. Either 0 or 1. +- "body_on_draw" : Which bodygroup to switch to when the monster has drawn its weapon. +- "leap_damage" : Amount of damage appled when the enemy leaps towards you and hits. + +# SPAWNFLAGS +- WAITTILLSEEN (1) - Play scripted sequence only once the monster gets seen by a player. +- GAG (2) - Won't speak. +- MONSTERCLIP (4) - Interacts with monsterclips? +- PRISONER (16) - Never used. +- IGNOREPLAYER (64) - Ignores the player. Like 'notarget'. +- WAITFORSCRIPT (128) - Does nothing, until a scripted sequence runs on the monster. Then becomes alive. +- PREDISASTER (256) - Special flag used in Half-Life. +- FADECORPSE (512) - Corpse will disappear on its own. +- MULTIPLAYER (1024) - Available in multiplayer. +- FALLING (2048) - Will not drop to the floor upon level spawn - but fall when in-game. +- HORDE (4096) - Never used. */ class NSMonster:NSNavAI { @@ -348,6 +411,7 @@ public: virtual void AnimationUpdate(void); /** Returns if we're currently in a forced animation sequence. */ nonvirtual bool InAnimation(void); + nonvirtual void AnimReset(void); /* states */ /** Called whenever the state of this NSMonster changes. */ diff --git a/src/shared/NSMonster.qc b/src/shared/NSMonster.qc index d22eb6f4..7b255af5 100644 --- a/src/shared/NSMonster.qc +++ b/src/shared/NSMonster.qc @@ -492,7 +492,13 @@ NSMonster::AnimPlay(float seq) SetSendFlags(MONFL_CHANGED_FRAME); SetFrame(seq); - m_flAnimTime = time + frameduration(modelindex, frame); + m_flAnimTime = time + frameduration(modelindex, seq); +} + +void +NSMonster::AnimReset(void) +{ + frame1time = 0.0f; } bool @@ -921,6 +927,8 @@ NSMonster::AttackMelee(void) ScheduleThink(AttackMelee_AttackFlail, _m_flMeleeDelay + meleeWait); } + AnimReset(); + if (random() < 0.5 || actMelee2 == -1) AnimPlay(actMelee1); else @@ -944,6 +952,7 @@ NSMonster::AttackRanged(void) } static void AttackRanged_RangedSpecial(void) { + NSMonster_Log("AttackRanged_RangedSpecial: %S", m_defRanged2); NSProjectile_SpawnDef(m_defRanged2, this); } @@ -961,6 +970,7 @@ NSMonster::AttackRanged(void) if (_m_flReloadTracker > m_flReloadCount) { throwAnyway = true; _m_bShouldThrow = true; + NSMonster_Log("throwAnyway: true!"); } /* special always first if possible */ @@ -987,21 +997,29 @@ NSMonster::AttackRanged(void) StartSoundDef(m_sndReload, CHAN_WEAPON, true); _m_bShouldThrow = false; - if (m_flReloadDelay) + if (m_flReloadDelay) { m_flAttackThink = time + m_flReloadDelay; - else - m_flAttackThink = time + frameduration(modelindex, actReload); + NSMonster_Log("Reloading, delay %f seconds", m_flReloadDelay); + } else { + float actDuration = frameduration(modelindex, actReload); + m_flAttackThink = time + actDuration; + NSMonster_Log("Reloading, act delays it by %f seconds", actDuration); + } return 1; } + AnimReset(); AnimPlay(actRanged); /* if we have no spawnclass, it must be a hitscan weapon */ + if (m_defRanged1) if (EntityDef_HasSpawnClass(m_defRanged1)) { NSProjectile_SpawnDef(m_defRanged1, this); + NSMonster_Log("Firing ranged def %S", m_defRanged1); } else { TraceAttack_FireBullets(1, GetEyePos(), rangedDmg, [0.01,0.01] * m_flAttackAccuracy, 0); + NSMonster_Log("Firing traceline def with %d", rangedDmg); } StartSoundDef(m_sndRangedAttack, CHAN_WEAPON, true); @@ -1014,19 +1032,25 @@ NSMonster::AttackRanged(void) burstTime = burstDelay; } - if (rangedDly) + if (rangedDly) { m_flAttackThink = time + rangedDly + burstTime; - else - m_flAttackThink = time + frameduration(modelindex, actRanged) + burstTime; + NSMonster_Log("Primary ranged attack, delay %f seconds (burst %d)", rangedDly, burstTime); + } else { + float actAttackTime = frameduration(modelindex, actRanged); + m_flAttackThink = time + actAttackTime + burstTime; + NSMonster_Log("Primary ranged attack, act delays it by %f seconds (burst %f)", actAttackTime, burstTime); + } return 1; } else if (throwAnyway == false && inRanged2Range && trace_ent == m_eEnemy) { float actRangedSpecial = FramegroupForAct(ACT_RANGE_ATTACK2); + AnimReset(); AnimPlay(actRangedSpecial); ScheduleThink(AttackRanged_RangedSpecial, 0.0f); m_flAttackThink = time + frameduration(modelindex, actRangedSpecial); return 1; } else if (inSpecial1Range) { + AnimReset(); AnimPlay(FramegroupForAct(ACT_SPECIAL_ATTACK1)); ScheduleThink(AttackRanged_Throw, m_flProjectileDelay); @@ -1037,6 +1061,7 @@ NSMonster::AttackRanged(void) return 1; } else if (inSpecial2Range) { + AnimReset(); AnimPlay(FramegroupForAct(ACT_SPECIAL_ATTACK2)); ScheduleThink(AttackRanged_Throw, m_flProjectileDelay);