From f3e355b98f123b65c092cb64ab0fe2908ec94e5f Mon Sep 17 00:00:00 2001 From: Enemy-123 Date: Sun, 19 Apr 2026 23:09:53 -0400 Subject: [PATCH 01/24] Merge branch 'repro' of https://github.com/VortexQuake2/Vortex into repro-monsters-test From e85805dedc1e477aa33929205d57fca504c8537c Mon Sep 17 00:00:00 2001 From: Enemy-123 Date: Mon, 20 Apr 2026 11:51:38 -0400 Subject: [PATCH 02/24] adding some remaster's monsters for testing purposes --- src/characters/v_utils.c | 16 + src/combat/common/damage.c | 5 +- src/combat/common/v_misc.c | 50 +- src/entities/drone/drone_ai.c | 6 +- src/entities/drone/drone_daedalus.c | 242 +++ src/entities/drone/drone_gekk.c | 521 ++++++ src/entities/drone/drone_gladiator.c | 124 +- src/entities/drone/drone_guncmdr.c | 795 +++++++++ src/entities/drone/drone_misc.c | 81 +- src/entities/drone/drone_redmutant.c | 655 +++++++ src/entities/drone/drone_runnertank.c | 866 ++++++++++ src/entities/drone/drone_stalker.c | 331 ++++ src/g_local.h | 16 + src/gamemodes/invasion.c | 17 +- src/quake2/g_layout.c | 10 +- src/quake2/monsterframes/m_guncmdr.h | 809 +++++++++ src/quake2/monsterframes/m_redmutant.h | 156 ++ src/quake2/monsterframes/m_rogue_stalker.h | 104 ++ src/quake2/monsterframes/m_runnertank.h | 266 +++ src/quake2/monsterframes/m_xatrix_gekk.h | 361 ++++ to add/m_gladiator.cpp | 714 ++++++++ to add/m_guncmdr.cpp | 1475 ++++++++++++++++ to add/m_gunner.h | 809 +++++++++ to add/m_hover.cpp | 804 +++++++++ to add/m_redmutant.cpp | 786 +++++++++ to add/m_redmutant.h | 156 ++ to add/m_runnertank.cpp | 1806 ++++++++++++++++++++ to add/m_runnertank.h | 266 +++ 28 files changed, 12228 insertions(+), 19 deletions(-) create mode 100644 src/entities/drone/drone_daedalus.c create mode 100644 src/entities/drone/drone_gekk.c create mode 100644 src/entities/drone/drone_guncmdr.c create mode 100644 src/entities/drone/drone_redmutant.c create mode 100644 src/entities/drone/drone_runnertank.c create mode 100644 src/entities/drone/drone_stalker.c create mode 100644 src/quake2/monsterframes/m_guncmdr.h create mode 100644 src/quake2/monsterframes/m_redmutant.h create mode 100644 src/quake2/monsterframes/m_rogue_stalker.h create mode 100644 src/quake2/monsterframes/m_runnertank.h create mode 100644 src/quake2/monsterframes/m_xatrix_gekk.h create mode 100644 to add/m_gladiator.cpp create mode 100644 to add/m_guncmdr.cpp create mode 100644 to add/m_gunner.h create mode 100644 to add/m_hover.cpp create mode 100644 to add/m_redmutant.cpp create mode 100644 to add/m_redmutant.h create mode 100644 to add/m_runnertank.cpp create mode 100644 to add/m_runnertank.h diff --git a/src/characters/v_utils.c b/src/characters/v_utils.c index f736ce7a..3e92bb5b 100644 --- a/src/characters/v_utils.c +++ b/src/characters/v_utils.c @@ -2079,6 +2079,22 @@ char *V_GetMonsterKind(int mtype) { return "supertank"; case M_SHAMBLER: return "shambler"; + case M_REDMUTANT: + return "red mutant"; + case M_RUNNERTANK: + return "runner tank"; + case M_GUNCMDR: + return "gun commander"; + case M_DAEDALUS: + return "daedalus"; + case M_GLADB: + return "gladiator disruptor"; + case M_GLADC: + return "gladiator plasma"; + case M_STALKER: + return "stalker"; + case M_GEKK: + return "gekk"; case M_SKELETON: return "skeleton"; case M_GOLEM: diff --git a/src/combat/common/damage.c b/src/combat/common/damage.c index e2bf31cf..51c2ee1b 100644 --- a/src/combat/common/damage.c +++ b/src/combat/common/damage.c @@ -150,7 +150,10 @@ qboolean IsMorphedPlayer(const edict_t *ent) { } qboolean IsMonster(const edict_t* ent) { - return (ent->mtype && (ent->mtype <= M_TANK || ent->mtype == M_SHAMBLER)); + return (ent->mtype && (ent->mtype <= M_TANK || ent->mtype == M_SHAMBLER + || ent->mtype == M_REDMUTANT || ent->mtype == M_RUNNERTANK || ent->mtype == M_GUNCMDR + || ent->mtype == M_DAEDALUS || ent->mtype == M_GLADB || ent->mtype == M_GLADC + || ent->mtype == M_STALKER || ent->mtype == M_GEKK)); } float vrx_get_pack_modifier(const edict_t *ent) { diff --git a/src/combat/common/v_misc.c b/src/combat/common/v_misc.c index e81c9125..9fdef8ee 100644 --- a/src/combat/common/v_misc.c +++ b/src/combat/common/v_misc.c @@ -254,8 +254,8 @@ void vrx_pvm_spawn_monsters(edict_t* self, int max_monsters, int total_monsters) while (total_monsters < max_monsters && max_spawn_this_cycle > 0) { int rnd; do { - rnd = GetRandom(1, 15); // az: don't spawn soldiers - } while (rnd == 10); + rnd = GetRandom(1, DS_GEKK); // az: don't spawn soldiers or helper summons + } while (rnd == DS_SOLDIER || rnd == DS_DECOY || rnd == DS_SKELETON || rnd == DS_GOLEM); edict_t* scan; if ((scan = vrx_create_new_drone(self, rnd, true, true, self->monsterinfo.scale)) != NULL) { @@ -616,6 +616,28 @@ int vrx_GetMonsterCost(int mtype) { case M_SHAMBLER: cost = M_TANK_COST; //using tank atm break; + case M_REDMUTANT: + cost = M_MUTANT_COST; + break; + case M_RUNNERTANK: + cost = M_TANK_COST; + break; + case M_GUNCMDR: + cost = M_TANK_COST; + break; + case M_DAEDALUS: + cost = M_HOVER_COST; + break; + case M_GLADB: + case M_GLADC: + cost = M_DEFAULT_COST; + break; + case M_STALKER: + cost = M_DEFAULT_COST; + break; + case M_GEKK: + cost = M_MUTANT_COST; + break; case M_SUPERTANK: cost = M_SUPERTANK_COST; break; @@ -669,6 +691,28 @@ int vrx_GetMonsterControlCost(int mtype) { case M_SHAMBLER: cost = M_TANK_CONTROL_COST; //using tank atm break; + case M_REDMUTANT: + cost = M_MUTANT_CONTROL_COST; + break; + case M_RUNNERTANK: + cost = M_TANK_CONTROL_COST; + break; + case M_GUNCMDR: + cost = M_TANK_CONTROL_COST; + break; + case M_DAEDALUS: + cost = M_HOVER_CONTROL_COST; + break; + case M_GLADB: + case M_GLADC: + cost = M_GLADIATOR_CONTROL_COST; + break; + case M_STALKER: + cost = M_BERSERKER_CONTROL_COST; + break; + case M_GEKK: + cost = M_MUTANT_CONTROL_COST; + break; case M_HOVER: cost = M_HOVER_CONTROL_COST; break; @@ -1163,4 +1207,4 @@ qboolean vrx_spawn_nonessential_ent(vec3_t org) return true; //gi.dprintf("don't spawn non-essential entity!\n"); return false; -} \ No newline at end of file +} diff --git a/src/entities/drone/drone_ai.c b/src/entities/drone/drone_ai.c index 118b764d..d7e9f9d8 100644 --- a/src/entities/drone/drone_ai.c +++ b/src/entities/drone/drone_ai.c @@ -702,7 +702,8 @@ void drone_ai_idle (edict_t *self) { // change skin if we are being healed by someone else self->s.skinnum &= ~1; - if (self->mtype != M_COMMANDER) + if (self->mtype != M_COMMANDER && self->mtype != M_GUNCMDR + && self->mtype != M_DAEDALUS && self->mtype != M_GLADB && self->mtype != M_GLADC) self->s.skinnum &= ~2; } @@ -1903,7 +1904,8 @@ void drone_ai_run1 (edict_t *self, float dist) { // change skin if we are being healed by someone else self->s.skinnum &= ~1; - if (self->mtype != M_COMMANDER) + if (self->mtype != M_COMMANDER && self->mtype != M_GUNCMDR + && self->mtype != M_DAEDALUS && self->mtype != M_GLADB && self->mtype != M_GLADC) self->s.skinnum &= ~2; } diff --git a/src/entities/drone/drone_daedalus.c b/src/entities/drone/drone_daedalus.c new file mode 100644 index 00000000..7fd74cba --- /dev/null +++ b/src/entities/drone/drone_daedalus.c @@ -0,0 +1,242 @@ +/* +============================================================================== + +DAEDALUS + +============================================================================== +*/ + +#include "g_local.h" +#include "../../quake2/monsterframes/m_hover.h" + +static int sound_pain1; +static int sound_pain2; +static int sound_death1; +static int sound_death2; +static int sound_sight; +static int sound_search1; +static int sound_search2; + +extern mmove_t hover_move_stand; +extern mmove_t hover_move_walk; +extern mmove_t hover_move_run; +extern mmove_t hover_move_pain1; +extern mmove_t hover_move_pain2; +extern mmove_t hover_move_pain3; +extern mmove_t hover_move_death1; + +static void daedalus_run(edict_t *self); +static void daedalus_reattack(edict_t *self); +static void daedalus_fire_grenade(edict_t *self); + +static void daedalus_sight(edict_t *self, edict_t *other) +{ + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +static void daedalus_search(edict_t *self) +{ + if (!self->goalentity) + self->goalentity = world; + + if (random() < 0.5) + gi.sound(self, CHAN_VOICE, sound_search1, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_VOICE, sound_search2, 1, ATTN_NORM, 0); +} + +static void daedalus_stand(edict_t *self) +{ + self->monsterinfo.currentmove = &hover_move_stand; +} + +static void daedalus_walk(edict_t *self) +{ + self->monsterinfo.currentmove = &hover_move_walk; +} + +static void daedalus_run(edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &hover_move_stand; + else + self->monsterinfo.currentmove = &hover_move_run; +} + +mframe_t daedalus_frames_attack[] = +{ + ai_charge, -10, daedalus_fire_grenade, + ai_charge, -10, daedalus_fire_grenade, + ai_charge, 0, daedalus_reattack +}; +mmove_t daedalus_move_attack = { FRAME_attak104, FRAME_attak106, daedalus_frames_attack, NULL }; + +mframe_t daedalus_frames_end_attack[] = +{ + drone_ai_run, 15, NULL, + drone_ai_run, 15, NULL +}; +mmove_t daedalus_move_end_attack = { FRAME_attak107, FRAME_attak108, daedalus_frames_end_attack, daedalus_run }; + +static void daedalus_fire_grenade(edict_t *self) +{ + int damage, speed; + vec3_t forward, right, start, offset; + + if (!G_EntExists(self->enemy)) + return; + + damage = M_GRENADELAUNCHER_DMG_BASE + M_GRENADELAUNCHER_DMG_ADDON * drone_damagelevel(self); + if (M_GRENADELAUNCHER_DMG_MAX && damage > M_GRENADELAUNCHER_DMG_MAX) + damage = M_GRENADELAUNCHER_DMG_MAX; + + speed = M_GRENADELAUNCHER_SPEED_BASE + M_GRENADELAUNCHER_SPEED_ADDON * drone_damagelevel(self); + if (M_GRENADELAUNCHER_SPEED_MAX && speed > M_GRENADELAUNCHER_SPEED_MAX) + speed = M_GRENADELAUNCHER_SPEED_MAX; + + AngleVectors(self->s.angles, forward, right, NULL); + if (self->s.frame == FRAME_attak104) + VectorSet(offset, 1.7, 7.0, 11.3); + else + VectorSet(offset, 1.7, -7.0, 11.3); + G_ProjectSource(self->s.origin, offset, forward, right, start); + + MonsterAim(self, M_PROJECTILE_ACC, speed, false, -1, forward, start); + monster_fire_grenade(self, start, forward, damage, speed, MZ2_HOVER_BLASTER_1); +} + +static void daedalus_reattack(edict_t *self) +{ + if (G_ValidTarget(self, self->enemy, true, true) && random() <= 0.65) + { + self->s.frame = FRAME_attak104; + return; + } + + self->monsterinfo.attack_finished = level.time + 1.0; + if (!(self->monsterinfo.aiflags & AI_STAND_GROUND)) + self->monsterinfo.currentmove = &daedalus_move_end_attack; +} + +static void daedalus_attack(edict_t *self) +{ + self->monsterinfo.currentmove = &daedalus_move_attack; +} + +static void daedalus_pain(edict_t *self, edict_t *other, float kick, int damage) +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum |= 1; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3.0; + + if (random() < 0.5) + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + + if (skill->value == 3) + return; + + if (damage <= 25) + { + if (random() < 0.5) + self->monsterinfo.currentmove = &hover_move_pain3; + else + self->monsterinfo.currentmove = &hover_move_pain2; + } + else + self->monsterinfo.currentmove = &hover_move_pain1; +} + +static void daedalus_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + M_Notify(self); + + if (self->health <= self->gib_health) + { + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); + if (vrx_spawn_nonessential_ent(self->s.origin)) + { + for (n = 0; n < 2; n++) + ThrowGib(self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); + for (n = 0; n < 2; n++) + ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + } + M_Remove(self, false, false); + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + + DroneList_Remove(self); + + if (random() < 0.5) + gi.sound(self, CHAN_VOICE, sound_death1, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_VOICE, sound_death2, 1, ATTN_NORM, 0); + + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + self->monsterinfo.currentmove = &hover_move_death1; + + if (self->activator && !self->activator->client) + self->activator->num_monsters_real--; +} + +void init_drone_daedalus(edict_t *self) +{ + sound_pain1 = gi.soundindex("daedalus/daedpain1.wav"); + sound_pain2 = gi.soundindex("daedalus/daedpain2.wav"); + sound_death1 = gi.soundindex("daedalus/daeddeth1.wav"); + sound_death2 = gi.soundindex("daedalus/daeddeth2.wav"); + sound_sight = gi.soundindex("daedalus/daedsght1.wav"); + sound_search1 = gi.soundindex("daedalus/daedsrch1.wav"); + sound_search2 = gi.soundindex("daedalus/daedsrch2.wav"); + + gi.soundindex("hover/hovatck1.wav"); + self->s.sound = gi.soundindex("hover/hovidle1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/hover/tris.md2"); + VectorSet(self->mins, -24, -24, -24); + VectorSet(self->maxs, 24, 24, 32); + self->s.skinnum = 2; + + self->health = M_FLOATER_INITIAL_HEALTH + M_FLOATER_ADDON_HEALTH * self->monsterinfo.level; + self->max_health = self->health; + self->gib_health = -100; + self->mass = 225; + + self->mtype = M_DAEDALUS; + self->flags |= FL_FLY; + self->monsterinfo.power_armor_power = M_FLOATER_INITIAL_ARMOR + M_FLOATER_ADDON_ARMOR * self->monsterinfo.level; + self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD; + self->monsterinfo.max_armor = self->monsterinfo.power_armor_power; + self->monsterinfo.control_cost = M_HOVER_CONTROL_COST; + self->monsterinfo.cost = M_HOVER_COST; + self->item = FindItemByClassname("ammo_grenades"); + + self->pain = daedalus_pain; + self->die = daedalus_die; + self->monsterinfo.stand = daedalus_stand; + self->monsterinfo.walk = daedalus_walk; + self->monsterinfo.run = daedalus_run; + self->monsterinfo.attack = daedalus_attack; + self->monsterinfo.sight = daedalus_sight; + self->monsterinfo.idle = daedalus_search; + self->monsterinfo.pain_chance = 0.2f; + + gi.linkentity(self); + + self->monsterinfo.currentmove = &hover_move_stand; + self->monsterinfo.scale = MODEL_SCALE; +} diff --git a/src/entities/drone/drone_gekk.c b/src/entities/drone/drone_gekk.c new file mode 100644 index 00000000..165844f4 --- /dev/null +++ b/src/entities/drone/drone_gekk.c @@ -0,0 +1,521 @@ +/* +============================================================================== + +GEKK + +============================================================================== +*/ + +#include "g_local.h" +#include "../../quake2/monsterframes/m_xatrix_gekk.h" + +static int sound_swing; +static int sound_hit; +static int sound_hit2; +static int sound_speet; +static int sound_death; +static int sound_pain1; +static int sound_sight; +static int sound_search; +static int sound_step1; +static int sound_step2; +static int sound_step3; +static int sound_thud; + +void drone_ai_stand(edict_t *self, float dist); +void drone_ai_run(edict_t *self, float dist); +void drone_ai_walk(edict_t *self, float dist); + +static void gekk_stand(edict_t *self); +static void gekk_run(edict_t *self); + +extern void fire_acid(edict_t *self, vec3_t start, vec3_t aimdir, int projectile_damage, float radius, + int speed, int acid_damage, float acid_duration, int gas_damage, float gas_radius, float gas_duration); + +static void gekk_step(edict_t *self) +{ + const float r = random(); + + if (r < 0.33) + gi.sound(self, CHAN_BODY, sound_step1, 1, ATTN_NORM, 0); + else if (r < 0.66) + gi.sound(self, CHAN_BODY, sound_step2, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_BODY, sound_step3, 1, ATTN_NORM, 0); +} + +static void gekk_sight(edict_t *self, edict_t *other) +{ + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +static void gekk_search(edict_t *self) +{ + gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_IDLE, 0); +} + +mframe_t gekk_frames_stand[] = +{ + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL +}; +mmove_t gekk_move_stand = { FRAME_stand_01, FRAME_stand_39, gekk_frames_stand, NULL }; + +static void gekk_stand(edict_t *self) +{ + self->monsterinfo.currentmove = &gekk_move_stand; +} + +mframe_t gekk_frames_walk[] = +{ + drone_ai_walk, 5, gekk_step, + drone_ai_walk, 6, NULL, + drone_ai_walk, 8, NULL, + drone_ai_walk, 7, gekk_step, + drone_ai_walk, 6, NULL, + drone_ai_walk, 5, NULL +}; +mmove_t gekk_move_walk = { FRAME_run_01, FRAME_run_06, gekk_frames_walk, NULL }; + +static void gekk_walk(edict_t *self) +{ + self->monsterinfo.currentmove = &gekk_move_walk; +} + +mframe_t gekk_frames_run[] = +{ + drone_ai_run, 18, gekk_step, + drone_ai_run, 24, NULL, + drone_ai_run, 28, NULL, + drone_ai_run, 22, gekk_step, + drone_ai_run, 20, NULL, + drone_ai_run, 18, NULL +}; +mmove_t gekk_move_run = { FRAME_run_01, FRAME_run_06, gekk_frames_run, NULL }; + +static void gekk_run(edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &gekk_move_stand; + else + self->monsterinfo.currentmove = &gekk_move_run; +} + +static int gekk_melee_damage(edict_t *self) +{ + int damage = M_MELEE_DMG_BASE + M_MELEE_DMG_ADDON * drone_damagelevel(self); + + if (M_MELEE_DMG_MAX && damage > M_MELEE_DMG_MAX) + damage = M_MELEE_DMG_MAX; + + return damage; +} + +static void gekk_bite(edict_t *self) +{ + gi.sound(self, CHAN_WEAPON, sound_hit, 1, ATTN_NORM, 0); + M_MeleeAttack(self, self->enemy, 80, gekk_melee_damage(self), 120); +} + +static void gekk_claw(edict_t *self) +{ + gi.sound(self, CHAN_WEAPON, (random() < 0.5) ? sound_hit : sound_hit2, 1, ATTN_NORM, 0); + M_MeleeAttack(self, self->enemy, 96, gekk_melee_damage(self), 180); +} + +mframe_t gekk_frames_attack[] = +{ + ai_charge, 8, NULL, + ai_charge, 12, NULL, + ai_charge, 12, gekk_bite, + ai_charge, 14, NULL, + ai_charge, 14, NULL, + ai_charge, 12, gekk_bite, + ai_charge, 12, NULL, + ai_charge, 10, NULL, + ai_charge, 8, NULL, + ai_charge, 8, NULL, + ai_charge, 6, gekk_claw, + ai_charge, 6, NULL, + ai_charge, 6, NULL, + ai_charge, 6, NULL, + ai_charge, 6, gekk_claw, + ai_charge, 6, NULL, + ai_charge, 6, NULL, + ai_charge, 4, NULL, + ai_charge, 4, NULL, + ai_charge, 2, NULL, + ai_charge, 0, NULL +}; +mmove_t gekk_move_attack = { FRAME_attack_01, FRAME_attack_21, gekk_frames_attack, gekk_run }; + +mframe_t gekk_frames_attack1[] = +{ + ai_charge, 0, NULL, + ai_charge, 0, gekk_claw, + ai_charge, 0, NULL, + ai_charge, 0, gekk_claw, + ai_charge, 0, NULL, + ai_charge, 0, gekk_claw, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t gekk_move_attack1 = { FRAME_clawatk3_01, FRAME_clawatk3_09, gekk_frames_attack1, gekk_run }; + +mframe_t gekk_frames_attack2[] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, gekk_claw, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, gekk_claw, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t gekk_move_attack2 = { FRAME_clawatk5_01, FRAME_clawatk5_09, gekk_frames_attack2, gekk_run }; + +static void gekk_melee(edict_t *self) +{ + if (!G_ValidTarget(self, self->enemy, true, true) || entdist(self, self->enemy) > 120) + { + self->monsterinfo.melee_finished = level.time + 0.5; + gekk_run(self); + return; + } + + gi.sound(self, CHAN_WEAPON, sound_swing, 1, ATTN_NORM, 0); + if (random() < 0.5) + self->monsterinfo.currentmove = &gekk_move_attack1; + else + self->monsterinfo.currentmove = &gekk_move_attack2; +} + +static void gekk_jump_takeoff(edict_t *self) +{ + vec3_t dir; + + if (!G_EntExists(self->enemy)) + return; + + VectorSubtract(self->enemy->s.origin, self->s.origin, dir); + VectorNormalize(dir); + VectorScale(dir, 420, self->velocity); + self->velocity[2] = 260; + self->groundentity = NULL; +} + +static void gekk_spit(edict_t *self) +{ + int acid_level, damage, speed; + float radius; + vec3_t forward, right, start, target, dir, offset; + + if (!G_EntExists(self->enemy)) + return; + + acid_level = drone_damagelevel(self); + if (acid_level > 15) + acid_level = 15; + + damage = ACID_INITIAL_DAMAGE + ACID_ADDON_DAMAGE * acid_level; + speed = ACID_INITIAL_SPEED + ACID_ADDON_SPEED * acid_level; + radius = ACID_INITIAL_RADIUS + ACID_ADDON_RADIUS * acid_level; + + AngleVectors(self->s.angles, forward, right, NULL); + VectorSet(offset, -18, -1, 24); + G_ProjectSource(self->s.origin, offset, forward, right, start); + + VectorCopy(self->enemy->s.origin, target); + target[2] += self->enemy->viewheight; + VectorSubtract(target, start, dir); + VectorNormalize(dir); + + gi.sound(self, CHAN_WEAPON, sound_speet, 1, ATTN_NORM, 0); + fire_acid(self, start, dir, damage, radius, speed, (int)(0.1 * damage), ACID_DURATION, 0, 0, 0); +} + +mframe_t gekk_frames_leapatk[] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 6, gekk_jump_takeoff, + ai_charge, 12, NULL, + ai_charge, 20, gekk_spit, + ai_charge, 28, NULL, + ai_charge, 32, NULL, + ai_charge, 35, NULL, + ai_charge, 28, gekk_claw, + ai_charge, 18, NULL, + ai_charge, 12, NULL, + ai_charge, 6, NULL, + ai_charge, 4, NULL, + ai_charge, 2, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t gekk_move_leapatk = { FRAME_leapatk_01, FRAME_leapatk_19, gekk_frames_leapatk, gekk_run }; + +mframe_t gekk_frames_spit[] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, gekk_spit, + ai_charge, 0, NULL +}; +mmove_t gekk_move_spit = { FRAME_spit_01, FRAME_spit_07, gekk_frames_spit, gekk_run }; + +static void gekk_attack(edict_t *self) +{ + if (G_EntExists(self->enemy) && entdist(self, self->enemy) < 180 && random() < 0.35) + self->monsterinfo.currentmove = &gekk_move_leapatk; + else + self->monsterinfo.currentmove = &gekk_move_spit; + + self->monsterinfo.melee_finished = level.time + 1.0; + M_DelayNextAttack(self, 1.0 + random(), true); +} + +mframe_t gekk_frames_pain[] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t gekk_move_pain = { FRAME_pain_01, FRAME_pain_06, gekk_frames_pain, gekk_run }; + +mframe_t gekk_frames_pain1[] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t gekk_move_pain1 = { FRAME_pain3_01, FRAME_pain3_11, gekk_frames_pain1, gekk_run }; + +mframe_t gekk_frames_pain2[] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t gekk_move_pain2 = { FRAME_pain4_01, FRAME_pain4_13, gekk_frames_pain2, gekk_run }; + +static void gekk_pain(edict_t *self, edict_t *other, float kick, int damage) +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum |= 1; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3.0; + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + + if (skill->value == 3) + return; + + if (self->waterlevel >= 2) + self->monsterinfo.currentmove = &gekk_move_pain; + else if (random() < 0.5) + self->monsterinfo.currentmove = &gekk_move_pain1; + else + self->monsterinfo.currentmove = &gekk_move_pain2; +} + +static void gekk_dead(edict_t *self) +{ + VectorSet(self->mins, -18, -18, -24); + VectorSet(self->maxs, 18, 18, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + gi.linkentity(self); + M_PrepBodyRemoval(self); +} + +static void gekk_shrink(edict_t *self) +{ + self->maxs[2] = -8; + self->svflags |= SVF_DEADMONSTER; + gi.linkentity(self); +} + +mframe_t gekk_frames_death1[] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -7, gekk_shrink, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t gekk_move_death1 = { FRAME_death1_01, FRAME_death1_10, gekk_frames_death1, gekk_dead }; + +static void gekk_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + M_Notify(self); + + if (self->health <= self->gib_health) + { + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); + if (vrx_spawn_nonessential_ent(self->s.origin)) + { + ThrowGib(self, "models/objects/gekkgib/pelvis/tris.md2", damage, GIB_ORGANIC); + for (n = 0; n < 2; n++) + ThrowGib(self, "models/objects/gekkgib/arm/tris.md2", damage, GIB_ORGANIC); + ThrowGib(self, "models/objects/gekkgib/torso/tris.md2", damage, GIB_ORGANIC); + ThrowGib(self, "models/objects/gekkgib/claw/tris.md2", damage, GIB_ORGANIC); + ThrowHead(self, "models/objects/gekkgib/head/tris.md2", damage, GIB_ORGANIC); + } + M_Remove(self, false, false); + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + + DroneList_Remove(self); + gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + self->monsterinfo.currentmove = &gekk_move_death1; + + if (self->activator && !self->activator->client) + self->activator->num_monsters_real--; +} + +void init_drone_gekk(edict_t *self) +{ + sound_swing = gi.soundindex("gek/gk_atck1.wav"); + sound_hit = gi.soundindex("gek/gk_atck2.wav"); + sound_hit2 = gi.soundindex("gek/gk_atck3.wav"); + sound_speet = gi.soundindex("gek/gk_atck4.wav"); + sound_death = gi.soundindex("gek/gk_deth1.wav"); + sound_pain1 = gi.soundindex("gek/gk_pain1.wav"); + sound_sight = gi.soundindex("gek/gk_sght1.wav"); + sound_search = gi.soundindex("gek/gk_idle1.wav"); + sound_step1 = gi.soundindex("gek/gk_step1.wav"); + sound_step2 = gi.soundindex("gek/gk_step2.wav"); + sound_step3 = gi.soundindex("gek/gk_step3.wav"); + sound_thud = gi.soundindex("mutant/thud1.wav"); + gi.soundindex("gek/loogie_hit.wav"); + gi.soundindex("weapons/rocklx1a.wav"); + gi.modelindex("models/objects/loogy/tris.md2"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/gekk/tris.md2"); + VectorSet(self->mins, -18, -18, -24); + VectorSet(self->maxs, 18, 18, 24); + + gi.modelindex("models/objects/gekkgib/pelvis/tris.md2"); + gi.modelindex("models/objects/gekkgib/arm/tris.md2"); + gi.modelindex("models/objects/gekkgib/torso/tris.md2"); + gi.modelindex("models/objects/gekkgib/claw/tris.md2"); + gi.modelindex("models/objects/gekkgib/leg/tris.md2"); + gi.modelindex("models/objects/gekkgib/head/tris.md2"); + + self->health = M_MUTANT_INITIAL_HEALTH + M_MUTANT_ADDON_HEALTH * self->monsterinfo.level; + self->max_health = self->health; + self->gib_health = -30; + self->mass = 300; + self->mtype = M_GEKK; + + self->monsterinfo.power_armor_power = M_MUTANT_INITIAL_ARMOR + M_MUTANT_ADDON_ARMOR * self->monsterinfo.level; + self->monsterinfo.power_armor_type = POWER_ARMOR_SCREEN; + self->monsterinfo.max_armor = self->monsterinfo.power_armor_power; + self->monsterinfo.control_cost = M_MUTANT_CONTROL_COST; + self->monsterinfo.cost = M_MUTANT_COST; + self->monsterinfo.jumpdn = 256; + self->monsterinfo.jumpup = 88; + + self->item = FindItemByClassname("ammo_cells"); + + self->pain = gekk_pain; + self->die = gekk_die; + self->monsterinfo.stand = gekk_stand; + self->monsterinfo.walk = gekk_walk; + self->monsterinfo.run = gekk_run; + self->monsterinfo.attack = gekk_attack; + self->monsterinfo.melee = gekk_melee; + self->monsterinfo.sight = gekk_sight; + self->monsterinfo.idle = gekk_search; + self->monsterinfo.pain_chance = 0.2f; + + gi.linkentity(self); + + self->monsterinfo.currentmove = &gekk_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + self->nextthink = level.time + FRAMETIME; +} diff --git a/src/entities/drone/drone_gladiator.c b/src/entities/drone/drone_gladiator.c index 85916dcf..721c786e 100644 --- a/src/entities/drone/drone_gladiator.c +++ b/src/entities/drone/drone_gladiator.c @@ -13,6 +13,8 @@ static int sound_pain1; static int sound_pain2; static int sound_die; static int sound_gun; +static int sound_gunb; +static int sound_gunc; static int sound_cleaver_swing; static int sound_cleaver_hit; static int sound_cleaver_miss; @@ -105,7 +107,7 @@ mmove_t gladiator_move_pain = { FRAME_pain1, FRAME_pain6, gladiator_frames_pain, void gladiator_pain(edict_t* self, edict_t* other, float kick, int damage) { if (self->health < (self->max_health / 2)) - self->s.skinnum = 1; + self->s.skinnum = (self->mtype == M_GLADB || self->mtype == M_GLADC) ? 3 : 1; // we're already in a pain state if (self->monsterinfo.currentmove == &gladiator_move_pain) @@ -269,6 +271,42 @@ void GladiatorGun (edict_t *self) monster_fire_railgun (self, start, forward, damage, 100, MZ2_GLADIATOR_RAILGUN_1); } +static void GladiatorDisruptor(edict_t *self) +{ + int damage, speed; + vec3_t forward, start; + + if (!G_EntExists(self->enemy)) + return; + + damage = DISRUPTOR_INITIAL_DAMAGE + DISRUPTOR_ADDON_DAMAGE * drone_damagelevel(self); + speed = DISRUPTOR_INITIAL_SPEED + DISRUPTOR_ADDON_SPEED * drone_damagelevel(self); + + MonsterAim(self, M_PROJECTILE_ACC, speed, false, MZ2_GLADIATOR_RAILGUN_1, forward, start); + fire_disruptor(self, start, forward, damage, speed, visible(self, self->enemy) ? self->enemy : NULL); +} + +static void GladiatorPlasma(edict_t *self) +{ + int damage, speed, radius_damage; + vec3_t forward, start; + + if (!G_EntExists(self->enemy)) + return; + + damage = M_BLASTER_DMG_BASE + M_BLASTER_DMG_ADDON * drone_damagelevel(self); + if (M_BLASTER_DMG_MAX && damage > M_BLASTER_DMG_MAX) + damage = M_BLASTER_DMG_MAX; + + speed = M_BLASTER_SPEED_BASE + M_BLASTER_SPEED_ADDON * drone_damagelevel(self); + if (M_BLASTER_SPEED_MAX && speed > M_BLASTER_SPEED_MAX) + speed = M_BLASTER_SPEED_MAX; + + radius_damage = max(1, damage / 2); + MonsterAim(self, M_PROJECTILE_ACC, speed, false, MZ2_GLADIATOR_RAILGUN_1, forward, start); + fire_plasma(self, start, forward, damage, speed, 40, radius_damage); +} + void gladiator_refire (edict_t *self) { // if our enemy is still valid, then continue firing @@ -282,6 +320,30 @@ void gladiator_refire (edict_t *self) self->monsterinfo.attack_finished = level.time + 1.0; } +static void gladiator_refire_disruptor(edict_t *self) +{ + if (G_ValidTarget(self, self->enemy, true, true) && (random() <= 0.45)) + { + self->s.frame = FRAME_attack4; + GladiatorDisruptor(self); + return; + } + + self->monsterinfo.attack_finished = level.time + 1.0; +} + +static void gladiator_refire_plasma(edict_t *self) +{ + if (G_ValidTarget(self, self->enemy, true, true) && (random() <= 0.6)) + { + self->s.frame = FRAME_attack4; + GladiatorPlasma(self); + return; + } + + self->monsterinfo.attack_finished = level.time + 1.0; +} + mframe_t gladiator_frames_attack_gun [] = { ai_charge, 0, GladiatorGun, //49 @@ -292,6 +354,26 @@ mframe_t gladiator_frames_attack_gun [] = }; mmove_t gladiator_move_attack_gun = {FRAME_attack4, FRAME_attack8, gladiator_frames_attack_gun, gladiator_run}; +mframe_t gladb_frames_attack_gun[] = +{ + ai_charge, 0, GladiatorDisruptor, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, gladiator_refire_disruptor, +}; +mmove_t gladb_move_attack_gun = { FRAME_attack4, FRAME_attack8, gladb_frames_attack_gun, gladiator_run }; + +mframe_t gladc_frames_attack_gun[] = +{ + ai_charge, 0, NULL, + ai_charge, 0, GladiatorPlasma, + ai_charge, 0, NULL, + ai_charge, 0, GladiatorPlasma, + ai_charge, 0, gladiator_refire_plasma, +}; +mmove_t gladc_move_attack_gun = { FRAME_attack4, FRAME_attack8, gladc_frames_attack_gun, gladiator_run }; + void gladiator_lightning_attack (edict_t *self) { const float r = random(); @@ -332,9 +414,22 @@ void gladiator_attack(edict_t *self) return; } - // charge up the railgun - gi.sound (self, CHAN_WEAPON, sound_gun, 1, ATTN_NORM, 0); - self->monsterinfo.currentmove = &gladiator_move_attack_gun; + if (self->mtype == M_GLADB) + { + gi.sound(self, CHAN_WEAPON, sound_gunb, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &gladb_move_attack_gun; + } + else if (self->mtype == M_GLADC) + { + gi.sound(self, CHAN_WEAPON, sound_gunc, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &gladc_move_attack_gun; + } + else + { + // charge up the railgun + gi.sound (self, CHAN_WEAPON, sound_gun, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &gladiator_move_attack_gun; + } } void gladiator_sight (edict_t *self, edict_t *other) @@ -442,6 +537,8 @@ void init_drone_gladiator (edict_t *self) { sound_die = gi.soundindex ("gladiator/glddeth2.wav"); sound_gun = gi.soundindex ("gladiator/railgun.wav"); + sound_gunb = gi.soundindex("weapons/disrupt.wav"); + sound_gunc = gi.soundindex("weapons/plasshot.wav"); sound_cleaver_swing = gi.soundindex ("gladiator/melee1.wav"); sound_cleaver_hit = gi.soundindex ("gladiator/melee2.wav"); sound_cleaver_miss = gi.soundindex ("gladiator/melee3.wav"); @@ -482,3 +579,22 @@ void init_drone_gladiator (edict_t *self) self->monsterinfo.currentmove = &gladiator_move_stand; self->monsterinfo.scale = MODEL_SCALE; } + +void init_drone_gladb(edict_t *self) +{ + init_drone_gladiator(self); + self->mtype = M_GLADB; + self->s.skinnum = 2; + self->s.effects |= EF_TRACKER; + self->mass = 350; + self->item = FindItemByClassname("ammo_disruptor"); +} + +void init_drone_gladc(edict_t *self) +{ + init_drone_gladiator(self); + self->mtype = M_GLADC; + self->s.skinnum = 2; + self->mass = 350; + self->item = FindItemByClassname("ammo_cells"); +} diff --git a/src/entities/drone/drone_guncmdr.c b/src/entities/drone/drone_guncmdr.c new file mode 100644 index 00000000..c2dc5161 --- /dev/null +++ b/src/entities/drone/drone_guncmdr.c @@ -0,0 +1,795 @@ +/* +============================================================================== + +GUN COMMANDER + +============================================================================== +*/ + +#include "g_local.h" +#include "../../quake2/monsterframes/m_guncmdr.h" + +static int sound_pain; +static int sound_pain2; +static int sound_death; +static int sound_idle; +static int sound_open; +static int sound_search; +static int sound_sight; +static int sound_thud; + +#define GUNCMDR_GRENADE_RANGE 100.0f +#define GUNCMDR_MORTAR_RANGE 525.0f +#define GUNCMDR_CHAINGUN_RUN_RANGE 400.0f +#define GUNCMDR_MORTAR_SPEED 850 +#define GUNCMDR_GRENADE_SPEED 600 +#define GUNCMDR_WALK_SPEED_MULT 2.0f + +static void guncmdr_stand(edict_t *self); +static void guncmdr_run(edict_t *self); +static void guncmdr_attack(edict_t *self); +static void guncmdr_fire_chain(edict_t *self); +static void guncmdr_refire_chain(edict_t *self); +static void guncmdr_grenade_finished(edict_t *self); +extern mmove_t guncmdr_move_fidget; + +static void guncmdr_idle_sound(edict_t *self) +{ + gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + +static void guncmdr_sight(edict_t *self, edict_t *other) +{ + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +static void guncmdr_search(edict_t *self) +{ + gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); +} + +static void guncmdr_fidget(edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + return; + if (self->enemy) + return; + if (random() <= 0.05) + self->monsterinfo.currentmove = &guncmdr_move_fidget; +} + +mframe_t guncmdr_frames_fidget[] = +{ + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, guncmdr_idle_sound, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + + drone_ai_stand, 0, guncmdr_idle_sound, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL +}; +mmove_t guncmdr_move_fidget = { FRAME_c_stand201, FRAME_c_stand254, guncmdr_frames_fidget, guncmdr_stand }; + +mframe_t guncmdr_frames_stand[] = +{ + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, guncmdr_fidget, + + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, guncmdr_fidget, + + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, guncmdr_fidget, + + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, guncmdr_fidget +}; +mmove_t guncmdr_move_stand = { FRAME_c_stand101, FRAME_c_stand140, guncmdr_frames_stand, NULL }; + +static void guncmdr_stand(edict_t *self) +{ + self->monsterinfo.currentmove = &guncmdr_move_stand; +} + +static void guncmdr_ai_walk(edict_t *self, float dist) +{ + drone_ai_walk(self, dist * GUNCMDR_WALK_SPEED_MULT); +} + +mframe_t guncmdr_frames_walk[] = +{ + guncmdr_ai_walk, 1.5, NULL, + guncmdr_ai_walk, 2.5, NULL, + guncmdr_ai_walk, 3.0, NULL, + guncmdr_ai_walk, 2.5, NULL, + guncmdr_ai_walk, 2.3, NULL, + guncmdr_ai_walk, 3.0, NULL, + guncmdr_ai_walk, 2.8, NULL, + guncmdr_ai_walk, 3.6, NULL, + guncmdr_ai_walk, 2.8, NULL, + guncmdr_ai_walk, 2.5, NULL, + + guncmdr_ai_walk, 2.3, NULL, + guncmdr_ai_walk, 4.3, NULL, + guncmdr_ai_walk, 3.0, NULL, + guncmdr_ai_walk, 1.5, NULL, + guncmdr_ai_walk, 2.5, NULL, + guncmdr_ai_walk, 3.3, NULL, + guncmdr_ai_walk, 2.8, NULL, + guncmdr_ai_walk, 3.0, NULL, + guncmdr_ai_walk, 2.0, NULL, + guncmdr_ai_walk, 2.0, NULL, + + guncmdr_ai_walk, 3.3, NULL, + guncmdr_ai_walk, 3.6, NULL, + guncmdr_ai_walk, 3.4, NULL, + guncmdr_ai_walk, 2.8, NULL +}; +mmove_t guncmdr_move_walk = { FRAME_c_walk101, FRAME_c_walk124, guncmdr_frames_walk, NULL }; + +static void guncmdr_walk(edict_t *self) +{ + if (!self->goalentity) + self->goalentity = world; + self->monsterinfo.currentmove = &guncmdr_move_walk; +} + +mframe_t guncmdr_frames_run[] = +{ + drone_ai_run, 15, NULL, + drone_ai_run, 16, NULL, + drone_ai_run, 20, NULL, + drone_ai_run, 18, NULL, + drone_ai_run, 24, NULL, + drone_ai_run, 13.5, NULL +}; +mmove_t guncmdr_move_run = { FRAME_c_run101, FRAME_c_run106, guncmdr_frames_run, NULL }; + +static void guncmdr_run(edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &guncmdr_move_stand; + else + self->monsterinfo.currentmove = &guncmdr_move_run; +} + +static void GunnerCmdrFire(edict_t *self) +{ + int damage; + int flash_number; + vec3_t forward, start; + + if (!self->enemy || !self->enemy->inuse) + return; + + if (self->s.frame >= FRAME_c_run201 && self->s.frame <= FRAME_c_run206) + flash_number = MZ2_GUNNER_MACHINEGUN_1 + (self->s.frame - FRAME_c_run201); + else + flash_number = MZ2_GUNNER_MACHINEGUN_1 + ((self->s.frame - FRAME_c_attack107) % 6); + + damage = M_MACHINEGUN_DMG_BASE + M_MACHINEGUN_DMG_ADDON * drone_damagelevel(self); + if (M_MACHINEGUN_DMG_MAX && damage > M_MACHINEGUN_DMG_MAX) + damage = M_MACHINEGUN_DMG_MAX; + + MonsterAim(self, M_HITSCAN_CONT_ACC, 0, false, flash_number, forward, start); + monster_fire_bullet(self, start, forward, damage, damage, + DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flash_number); +} + +static void guncmdr_opengun(edict_t *self) +{ + gi.sound(self, CHAN_VOICE, sound_open, 1, ATTN_IDLE, 0); +} + +mframe_t guncmdr_frames_attack_chain[] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, guncmdr_opengun, + ai_charge, 0, NULL +}; +mmove_t guncmdr_move_attack_chain = { FRAME_c_attack101, FRAME_c_attack106, guncmdr_frames_attack_chain, guncmdr_fire_chain }; + +mframe_t guncmdr_frames_fire_chain[] = +{ + ai_charge, 0, GunnerCmdrFire, + ai_charge, 0, GunnerCmdrFire, + ai_charge, 0, GunnerCmdrFire, + ai_charge, 0, GunnerCmdrFire, + ai_charge, 0, GunnerCmdrFire, + ai_charge, 0, GunnerCmdrFire +}; +mmove_t guncmdr_move_fire_chain = { FRAME_c_attack107, FRAME_c_attack112, guncmdr_frames_fire_chain, guncmdr_refire_chain }; + +mframe_t guncmdr_frames_fire_chain_run[] = +{ + drone_ai_run, 15, GunnerCmdrFire, + drone_ai_run, 16, GunnerCmdrFire, + drone_ai_run, 20, GunnerCmdrFire, + drone_ai_run, 18, GunnerCmdrFire, + drone_ai_run, 24, GunnerCmdrFire, + drone_ai_run, 13.5, GunnerCmdrFire +}; +mmove_t guncmdr_move_fire_chain_run = { FRAME_c_run201, FRAME_c_run206, guncmdr_frames_fire_chain_run, guncmdr_refire_chain }; + +mframe_t guncmdr_frames_endfire_chain[] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, guncmdr_opengun, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t guncmdr_move_endfire_chain = { FRAME_c_attack118, FRAME_c_attack124, guncmdr_frames_endfire_chain, guncmdr_run }; + +static int guncmdr_grenade_flash(edict_t *self) +{ + if (self->s.frame == FRAME_c_attack205 || self->s.frame == FRAME_c_attack304 || self->s.frame == FRAME_c_attack911) + return MZ2_GUNNER_GRENADE_1; + if (self->s.frame == FRAME_c_attack208 || self->s.frame == FRAME_c_attack307 || self->s.frame == FRAME_c_attack912) + return MZ2_GUNNER_GRENADE_2; + return MZ2_GUNNER_GRENADE_4; +} + +static void GunnerCmdrGrenade(edict_t *self) +{ + int damage, speed, flash_number; + vec3_t forward, start; + + if (!self->enemy || !self->enemy->inuse) + return; + + damage = M_GRENADELAUNCHER_DMG_BASE + M_GRENADELAUNCHER_DMG_ADDON * drone_damagelevel(self); + if (M_GRENADELAUNCHER_DMG_MAX && damage > M_GRENADELAUNCHER_DMG_MAX) + damage = M_GRENADELAUNCHER_DMG_MAX; + + if (self->s.frame >= FRAME_c_attack201 && self->s.frame <= FRAME_c_attack221) + speed = GUNCMDR_MORTAR_SPEED; + else + speed = GUNCMDR_GRENADE_SPEED + M_GRENADELAUNCHER_SPEED_ADDON * drone_damagelevel(self); + + if (M_GRENADELAUNCHER_SPEED_MAX && speed > M_GRENADELAUNCHER_SPEED_MAX) + speed = M_GRENADELAUNCHER_SPEED_MAX; + + flash_number = guncmdr_grenade_flash(self); + MonsterAim(self, M_PROJECTILE_ACC, speed, false, flash_number, forward, start); + monster_fire_grenade(self, start, forward, damage, speed, flash_number); +} + +mframe_t guncmdr_frames_attack_mortar[] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, GunnerCmdrGrenade, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, GunnerCmdrGrenade, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + + ai_charge, 0, GunnerCmdrGrenade, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t guncmdr_move_attack_mortar = { FRAME_c_attack201, FRAME_c_attack221, guncmdr_frames_attack_mortar, guncmdr_grenade_finished }; + +mframe_t guncmdr_frames_attack_back[] = +{ + ai_charge, -2, NULL, + ai_charge, -1.5, NULL, + ai_charge, -0.5, GunnerCmdrGrenade, + ai_charge, -6.0, NULL, + ai_charge, -4, NULL, + ai_charge, -2.5, GunnerCmdrGrenade, + ai_charge, -7.0, NULL, + ai_charge, -3.5, NULL, + ai_charge, -1.1, GunnerCmdrGrenade, + + ai_charge, -4.6, NULL, + ai_charge, 1.9, NULL, + ai_charge, 1.0, NULL, + ai_charge, -4.5, NULL, + ai_charge, 3.2, NULL, + ai_charge, 4.4, NULL, + ai_charge, -6.5, NULL, + ai_charge, -6.1, NULL, + ai_charge, 3.0, NULL, + ai_charge, -0.7, NULL, + ai_charge, -1.0, NULL +}; +mmove_t guncmdr_move_attack_grenade_back = { FRAME_c_attack302, FRAME_c_attack321, guncmdr_frames_attack_back, guncmdr_grenade_finished }; + +static void guncmdr_kick(edict_t *self) +{ + int damage = M_MELEE_DMG_BASE + M_MELEE_DMG_ADDON * drone_damagelevel(self); + vec3_t aim; + + if (M_MELEE_DMG_MAX && damage > M_MELEE_DMG_MAX) + damage = M_MELEE_DMG_MAX; + + VectorSet(aim, MELEE_DISTANCE, 0, -32); + fire_hit(self, aim, damage, 400); +} + +static void guncmdr_kick_finished(edict_t *self) +{ + self->monsterinfo.melee_finished = level.time + 3.0; + if (G_ValidTarget(self, self->enemy, true, true)) + guncmdr_attack(self); + else + guncmdr_run(self); +} + +mframe_t guncmdr_frames_attack_kick[] = +{ + ai_charge, -7.7, NULL, + ai_charge, -4.9, NULL, + ai_charge, 12.6, guncmdr_kick, + ai_charge, 0, NULL, + ai_charge, -3.0, NULL, + ai_charge, 0, NULL, + ai_charge, -4.1, NULL, + ai_charge, 8.6, NULL +}; +mmove_t guncmdr_move_attack_kick = { FRAME_c_attack801, FRAME_c_attack808, guncmdr_frames_attack_kick, guncmdr_kick_finished }; + +static void guncmdr_duck_down(edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_DUCKED) + return; + if (!self->groundentity) + return; + + self->monsterinfo.aiflags |= AI_DUCKED; + self->maxs[2] = 4; + self->takedamage = DAMAGE_YES; + gi.linkentity(self); +} + +static void guncmdr_duck_up(edict_t *self) +{ + self->monsterinfo.aiflags &= ~AI_DUCKED; + self->maxs[2] = 36; + self->takedamage = DAMAGE_AIM; + VectorClear(self->velocity); + gi.linkentity(self); +} + +mframe_t guncmdr_frames_duck_attack[] = +{ + ai_move, 3.6, NULL, + ai_move, 5.6, guncmdr_duck_down, + ai_move, 8.4, NULL, + ai_move, 2.0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + + ai_charge, 0, GunnerCmdrGrenade, + ai_charge, 9.5, GunnerCmdrGrenade, + ai_charge, -1.5, GunnerCmdrGrenade, + ai_charge, 0, NULL, + ai_charge, 0, guncmdr_duck_up, + ai_charge, 0, NULL, + ai_charge, 11, NULL, + ai_charge, 2.0, NULL, + ai_charge, 5.6, NULL +}; +mmove_t guncmdr_move_duck_attack = { FRAME_c_attack901, FRAME_c_attack919, guncmdr_frames_duck_attack, guncmdr_run }; + +static void guncmdr_jump_now(edict_t *self) +{ + vec3_t forward; + + AngleVectors(self->s.angles, forward, NULL, NULL); + VectorMA(self->velocity, 150, forward, self->velocity); + self->velocity[2] += 350; +} + +static void guncmdr_jump_wait_land(edict_t *self) +{ + if (!self->groundentity && level.time < self->monsterinfo.pausetime) + self->monsterinfo.nextframe = self->s.frame; + else + self->monsterinfo.nextframe = self->s.frame + 1; +} + +mframe_t guncmdr_frames_jump[] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, guncmdr_jump_now, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, guncmdr_jump_wait_land, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t guncmdr_move_jump = { FRAME_c_jump01, FRAME_c_jump10, guncmdr_frames_jump, guncmdr_run }; + +static void guncmdr_dodge(edict_t *self, edict_t *attacker, vec3_t dir, int radius) +{ + if (random() > 0.8) + return; + if (level.time < self->monsterinfo.dodge_time) + return; + if (OnSameTeam(self, attacker)) + return; + + if (!self->enemy && G_EntIsAlive(attacker)) + self->enemy = attacker; + + if (radius && self->groundentity) + { + self->monsterinfo.pausetime = level.time + 2.0; + self->monsterinfo.currentmove = &guncmdr_move_jump; + self->monsterinfo.dodge_time = level.time + 3.0; + } + else + { + self->monsterinfo.currentmove = &guncmdr_move_duck_attack; + self->monsterinfo.dodge_time = level.time + 2.0; + } +} + +static void guncmdr_attack(edict_t *self) +{ + float dist; + + if (!G_ValidTarget(self, self->enemy, true, true)) + return; + + dist = entdist(self, self->enemy); + if (dist <= MELEE_DISTANCE && self->monsterinfo.melee_finished < level.time) + self->monsterinfo.currentmove = &guncmdr_move_attack_kick; + else if (dist <= GUNCMDR_GRENADE_RANGE || random() < 0.45) + self->monsterinfo.currentmove = &guncmdr_move_attack_chain; + else if (dist >= GUNCMDR_MORTAR_RANGE || fabs(self->s.origin[2] - self->enemy->s.origin[2]) > 64) + self->monsterinfo.currentmove = &guncmdr_move_attack_mortar; + else + self->monsterinfo.currentmove = &guncmdr_move_attack_grenade_back; + + M_DelayNextAttack(self, 0, true); +} + +static void guncmdr_fire_chain(edict_t *self) +{ + if (!(self->monsterinfo.aiflags & AI_STAND_GROUND) + && G_ValidTarget(self, self->enemy, true, true) + && entdist(self, self->enemy) > GUNCMDR_CHAINGUN_RUN_RANGE) + self->monsterinfo.currentmove = &guncmdr_move_fire_chain_run; + else + self->monsterinfo.currentmove = &guncmdr_move_fire_chain; +} + +static void guncmdr_refire_chain(edict_t *self) +{ + if (G_ValidTarget(self, self->enemy, true, true) && visible(self, self->enemy) && random() <= 0.5) + { + if (!(self->monsterinfo.aiflags & AI_STAND_GROUND) && entdist(self, self->enemy) > GUNCMDR_CHAINGUN_RUN_RANGE) + self->monsterinfo.currentmove = &guncmdr_move_fire_chain_run; + else + self->monsterinfo.currentmove = &guncmdr_move_fire_chain; + } + else + self->monsterinfo.currentmove = &guncmdr_move_endfire_chain; + + self->monsterinfo.attack_finished = level.time + 0.5; +} + +static void guncmdr_grenade_finished(edict_t *self) +{ + self->monsterinfo.attack_finished = level.time + 1.0; + guncmdr_run(self); +} + +mframe_t guncmdr_frames_pain1[] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t guncmdr_move_pain1 = { FRAME_c_pain101, FRAME_c_pain104, guncmdr_frames_pain1, guncmdr_run }; + +mframe_t guncmdr_frames_pain2[] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t guncmdr_move_pain2 = { FRAME_c_pain201, FRAME_c_pain204, guncmdr_frames_pain2, guncmdr_run }; + +mframe_t guncmdr_frames_pain3[] = +{ + ai_move, -3, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t guncmdr_move_pain3 = { FRAME_c_pain301, FRAME_c_pain304, guncmdr_frames_pain3, guncmdr_run }; + +static void guncmdr_pain(edict_t *self, edict_t *other, float kick, int damage) +{ + float r; + + if (self->health < (self->max_health / 2)) + self->s.skinnum = 3; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3.0; + gi.sound(self, CHAN_VOICE, (random() < 0.5) ? sound_pain : sound_pain2, 1, ATTN_NORM, 0); + + if (skill->value == 3) + return; + + r = random(); + if (r < 0.33) + self->monsterinfo.currentmove = &guncmdr_move_pain1; + else if (r < 0.66) + self->monsterinfo.currentmove = &guncmdr_move_pain2; + else + self->monsterinfo.currentmove = &guncmdr_move_pain3; +} + +static void guncmdr_dead(edict_t *self) +{ + VectorSet(self->mins, -16, -16, -24); + VectorSet(self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + gi.linkentity(self); + M_PrepBodyRemoval(self); +} + +static void guncmdr_shrink(edict_t *self) +{ + self->maxs[2] = -8; + self->svflags |= SVF_DEADMONSTER; + gi.linkentity(self); +} + +mframe_t guncmdr_frames_death1[] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -6, NULL, + ai_move, -3, NULL, + ai_move, -5, NULL, + ai_move, 8, NULL, + ai_move, 6, guncmdr_shrink, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t guncmdr_move_death1 = { FRAME_c_death101, FRAME_c_death118, guncmdr_frames_death1, guncmdr_dead }; + +static void guncmdr_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + M_Notify(self); + +#ifdef OLD_NOLAG_STYLE + if (nolag->value) + { + M_Remove(self, false, true); + return; + } +#endif + + if (self->health <= self->gib_health) + { + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); + if (vrx_spawn_nonessential_ent(self->s.origin)) + { + for (n = 0; n < 2; n++) + ThrowGib(self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); + for (n = 0; n < 4; n++) + ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead(self, "models/monsters/gunner/gibs/head.md2", damage, GIB_ORGANIC); + } +#ifdef OLD_NOLAG_STYLE + M_Remove(self, false, false); +#else + if (nolag->value) + M_Remove(self, false, true); + else + M_Remove(self, false, false); +#endif + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + + DroneList_Remove(self); + + gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + self->monsterinfo.currentmove = &guncmdr_move_death1; + + if (self->activator && !self->activator->client) + self->activator->num_monsters_real--; +} + +void init_drone_guncmdr(edict_t *self) +{ + sound_death = gi.soundindex("guncmdr/gcdrdeath1.wav"); + sound_pain = gi.soundindex("guncmdr/gcdrpain2.wav"); + sound_pain2 = gi.soundindex("guncmdr/gcdrpain1.wav"); + sound_idle = gi.soundindex("guncmdr/gcdridle1.wav"); + sound_open = gi.soundindex("guncmdr/gcdratck1.wav"); + sound_search = gi.soundindex("guncmdr/gcdrsrch1.wav"); + sound_sight = gi.soundindex("guncmdr/sight1.wav"); + sound_thud = gi.soundindex("player/land1.wav"); + + gi.soundindex("guncmdr/gcdratck2.wav"); + gi.soundindex("guncmdr/gcdratck3.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/gunner/tris.md2"); + gi.modelindex("models/monsters/gunner/gibs/chest.md2"); + gi.modelindex("models/monsters/gunner/gibs/foot.md2"); + gi.modelindex("models/monsters/gunner/gibs/garm.md2"); + gi.modelindex("models/monsters/gunner/gibs/gun.md2"); + gi.modelindex("models/monsters/gunner/gibs/head.md2"); + + VectorSet(self->mins, -16, -16, -24); + VectorSet(self->maxs, 16, 16, 36); + self->s.skinnum = 2; + + self->monsterinfo.control_cost = M_TANK_CONTROL_COST; + self->monsterinfo.cost = M_TANK_COST; + self->health = M_TANK_INITIAL_HEALTH + M_TANK_ADDON_HEALTH * self->monsterinfo.level; + self->max_health = self->health; + self->gib_health = -175; + self->mass = 255; + self->monsterinfo.jumpdn = 512; + self->monsterinfo.jumpup = 64; + + if (random() > 0.5) + self->item = FindItemByClassname("ammo_bullets"); + else + self->item = FindItemByClassname("ammo_grenades"); + + self->pain = guncmdr_pain; + self->die = guncmdr_die; + + self->monsterinfo.stand = guncmdr_stand; + self->monsterinfo.walk = guncmdr_walk; + self->monsterinfo.run = guncmdr_run; + self->monsterinfo.dodge = guncmdr_dodge; + self->monsterinfo.attack = guncmdr_attack; + self->monsterinfo.sight = guncmdr_sight; + self->monsterinfo.idle = guncmdr_idle_sound; + self->monsterinfo.pain_chance = 0.2f; + self->monsterinfo.aiflags |= AI_NO_CIRCLE_STRAFE; + + self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD; + self->monsterinfo.power_armor_power = M_TANK_INITIAL_ARMOR + M_TANK_ADDON_ARMOR * self->monsterinfo.level; + self->monsterinfo.max_armor = self->monsterinfo.power_armor_power; + self->mtype = M_GUNCMDR; + + gi.linkentity(self); + + self->monsterinfo.currentmove = &guncmdr_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + self->nextthink = level.time + FRAMETIME; +} diff --git a/src/entities/drone/drone_misc.c b/src/entities/drone/drone_misc.c index 4bc13bd0..171bf5e6 100644 --- a/src/entities/drone/drone_misc.c +++ b/src/entities/drone/drone_misc.c @@ -31,6 +31,14 @@ void init_drone_flyer (edict_t* self); void init_drone_floater(edict_t* self); void init_drone_hover(edict_t* self); void init_drone_shambler(edict_t* self); +void init_drone_redmutant(edict_t* self); +void init_drone_runnertank(edict_t* self); +void init_drone_guncmdr(edict_t* self); +void init_drone_daedalus(edict_t* self); +void init_drone_gladb(edict_t* self); +void init_drone_gladc(edict_t* self); +void init_drone_stalker(edict_t* self); +void init_drone_gekk(edict_t* self); void init_baron_fire(edict_t* self); void init_skeleton(edict_t* self); void init_golem(edict_t* self); @@ -849,9 +857,17 @@ edict_t *vrx_create_drone_from_ent(edict_t *drone, edict_t *ent, enum dronespawn case DS_FLOATER: init_drone_floater(drone); break; case DS_HOVER: init_drone_hover(drone); break; case DS_SHAMBLER: init_drone_shambler(drone); break; + case DS_REDMUTANT: init_drone_redmutant(drone); break; + case DS_RUNNERTANK: init_drone_runnertank(drone); break; + case DS_GUNCMDR: init_drone_guncmdr(drone); break; + case DS_DAEDALUS: init_drone_daedalus(drone); break; case DS_DECOY: init_drone_decoy(drone); break; case DS_SKELETON: init_skeleton(drone); break; case DS_GOLEM: init_golem(drone); break; + case DS_GLADB: init_drone_gladb(drone); break; + case DS_GLADC: init_drone_gladc(drone); break; + case DS_STALKER: init_drone_stalker(drone); break; + case DS_GEKK: init_drone_gekk(drone); break; // bosses case DS_COMMANDER: init_drone_commander(drone); break; @@ -1745,7 +1761,8 @@ qboolean M_Regenerate (edict_t *self, int regen_frames, int delay, float mult, q if (!self->client && (self->svflags & SVF_MONSTER) && vrx_has_pain_skin(self) && (self->health >= 0.5 * self->max_health)) { self->s.skinnum &= ~1; - if (self->mtype != M_COMMANDER) + if (self->mtype != M_COMMANDER && self->mtype != M_GUNCMDR + && self->mtype != M_DAEDALUS && self->mtype != M_GLADB && self->mtype != M_GLADC) self->s.skinnum &= ~2; } @@ -2039,6 +2056,14 @@ qboolean M_Initialize (edict_t *ent, edict_t *monster, float dur_bonus) case M_FLOATER: init_drone_floater(monster); break; case M_HOVER: init_drone_hover(monster); break; case M_SHAMBLER: init_drone_shambler(monster); break; + case M_REDMUTANT: init_drone_redmutant(monster); break; + case M_RUNNERTANK: init_drone_runnertank(monster); break; + case M_GUNCMDR: init_drone_guncmdr(monster); break; + case M_DAEDALUS: init_drone_daedalus(monster); break; + case M_GLADB: init_drone_gladb(monster); break; + case M_GLADC: init_drone_gladc(monster); break; + case M_STALKER: init_drone_stalker(monster); break; + case M_GEKK: init_drone_gekk(monster); break; case M_SKELETON: init_skeleton(monster); break; case M_GOLEM: init_golem(monster); break; default: return false; @@ -2128,6 +2153,30 @@ qboolean M_SetBoundingBox (int mtype, vec3_t boxmin, vec3_t boxmax) VectorSet(boxmin, -32, -32, -24); VectorSet(boxmax, 32, 32, 64); break; + case M_REDMUTANT: + VectorSet(boxmin, -18, -18, -24); + VectorSet(boxmax, 18, 18, 30); + break; + case M_RUNNERTANK: + VectorSet(boxmin, -28, -28, -14); + VectorSet(boxmax, 28, 28, 56); + break; + case M_GUNCMDR: + VectorSet(boxmin, -16, -16, -24); + VectorSet(boxmax, 16, 16, 36); + break; + case M_DAEDALUS: + VectorSet(boxmin, -24, -24, -24); + VectorSet(boxmax, 24, 24, 32); + break; + case M_STALKER: + VectorSet(boxmin, -28, -28, -18); + VectorSet(boxmax, 28, 28, 18); + break; + case M_GEKK: + VectorSet(boxmin, -18, -18, -24); + VectorSet(boxmax, 18, 18, 24); + break; case M_MEDIC: case M_MUTANT: VectorSet (boxmin, -24, -24, -24); @@ -2138,6 +2187,8 @@ qboolean M_SetBoundingBox (int mtype, vec3_t boxmin, vec3_t boxmax) VectorSet(boxmax, 16, 16, -8); break; case M_GLADIATOR: + case M_GLADB: + case M_GLADC: VectorSet(boxmin, -24, -24, -24); VectorSet(boxmax, 24, 24, 48); break; @@ -2194,6 +2245,14 @@ char *GetMonsterKindString (int mtype) case M_FLOATER: return "Floater"; case M_HOVER: return "Hover"; case M_SHAMBLER: return "Shambler"; + case M_REDMUTANT: return "Red Mutant"; + case M_RUNNERTANK: return "Runner Tank"; + case M_GUNCMDR: return "Gun Commander"; + case M_DAEDALUS: return "Daedalus"; + case M_GLADB: return "Gladiator Disruptor"; + case M_GLADC: return "Gladiator Plasma"; + case M_STALKER: return "Stalker"; + case M_GEKK: return "Gekk"; case M_SKELETON: return "Skeleton"; case M_GOLEM: return "Golem"; case M_BARON_FIRE: return "Fire Baron"; @@ -2778,7 +2837,7 @@ void Cmd_Drone_f (edict_t *ent) if (!Q_strcasecmp(s, "help")) { safe_cprintf(ent, PRINT_HIGH, "Monster summoning:\n"); - safe_cprintf(ent, PRINT_HIGH, "monster [gunner|parasite|brain|praetor|medic|tank|mutant|gladiator|berserker|soldier|enforcer|flyer|floater|hover|shambler\n"); + safe_cprintf(ent, PRINT_HIGH, "monster [gunner|parasite|brain|praetor|medic|tank|mutant|gladiator|gladb|gladc|berserker|soldier|enforcer|flyer|floater|hover|daedalus|stalker|gekk|shambler|redmutant|runnertank|guncmdr]\n"); safe_cprintf(ent, PRINT_HIGH, "Monster utility commands:\n"); safe_cprintf(ent, PRINT_HIGH, "monster [remove|command|follow me|count|attack]\n"); return; @@ -2826,6 +2885,22 @@ void Cmd_Drone_f (edict_t *ent) vrx_create_new_drone(ent, 14, false, true, 0); else if (!Q_strcasecmp(s, "shambler")) vrx_create_new_drone(ent, 15, false, true, 0); + else if (!Q_strcasecmp(s, "redmutant")) + vrx_create_new_drone(ent, 16, false, true, 0); + else if (!Q_strcasecmp(s, "runnertank")) + vrx_create_new_drone(ent, 17, false, true, 0); + else if (!Q_strcasecmp(s, "guncmdr")) + vrx_create_new_drone(ent, 18, false, true, 0); + else if (!Q_strcasecmp(s, "daedalus")) + vrx_create_new_drone(ent, 19, false, true, 0); + else if (!Q_strcasecmp(s, "gladb")) + vrx_create_new_drone(ent, 23, false, true, 0); + else if (!Q_strcasecmp(s, "gladc")) + vrx_create_new_drone(ent, 24, false, true, 0); + else if (!Q_strcasecmp(s, "stalker")) + vrx_create_new_drone(ent, 25, false, true, 0); + else if (!Q_strcasecmp(s, "gekk")) + vrx_create_new_drone(ent, 26, false, true, 0); else if (!Q_strcasecmp(s, "golem") && ent->myskills.administrator) vrx_create_new_drone(ent, 22, false, true, 0); //else if (!Q_strcasecmp(s, "baron fire") && ent->myskills.administrator) @@ -3034,4 +3109,4 @@ void M_Touchdown(edict_t* self) gi.sound(self, CHAN_VOICE, gi.soundindex("world/land.wav"), 1, ATTN_IDLE, 0); } self->monsterinfo.touchdown_delay = level.time + 1.0; -} \ No newline at end of file +} diff --git a/src/entities/drone/drone_redmutant.c b/src/entities/drone/drone_redmutant.c new file mode 100644 index 00000000..c79a050d --- /dev/null +++ b/src/entities/drone/drone_redmutant.c @@ -0,0 +1,655 @@ +/* +============================================================================== + +RED MUTANT + +============================================================================== +*/ + +#include "g_local.h" +#include "../../quake2/monsterframes/m_redmutant.h" + +static int sound_swing; +static int sound_hit; +static int sound_hit2; +static int sound_death; +static int sound_idle; +static int sound_pain1; +static int sound_pain2; +static int sound_sight; +static int sound_search; +static int sound_step1; +static int sound_step2; +static int sound_step3; +static int sound_thud; + +static void redmutant_stand(edict_t *self); +static void redmutant_walk(edict_t *self); +static void redmutant_run(edict_t *self); +static void redmutant_attack(edict_t *self); +static void redmutant_melee_unused(edict_t *self); +static void redmutant_post_jump(edict_t *self); +extern mmove_t redmutant_move_jump_finish; + +static void redmutant_step(edict_t *self) +{ + switch (GetRandom(0, 2)) + { + case 0: gi.sound(self, CHAN_BODY, sound_step1, 1, ATTN_NORM, 0); break; + case 1: gi.sound(self, CHAN_BODY, sound_step2, 1, ATTN_NORM, 0); break; + default: gi.sound(self, CHAN_BODY, sound_step3, 1, ATTN_NORM, 0); break; + } +} + +static void redmutant_sight(edict_t *self, edict_t *other) +{ + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +static void redmutant_search(edict_t *self) +{ + gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); +} + +static void redmutant_swing(edict_t *self) +{ + gi.sound(self, CHAN_VOICE, sound_swing, 1, ATTN_NORM, 0); +} + +mframe_t redmutant_frames_stand[] = +{ + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL +}; +mmove_t redmutant_move_stand = { FRAME_stand101, FRAME_stand112, redmutant_frames_stand, NULL }; + +static void redmutant_stand(edict_t *self) +{ + self->monsterinfo.currentmove = &redmutant_move_stand; +} + +static void redmutant_idle_loop(edict_t *self) +{ + if (random() < 0.75) + self->monsterinfo.nextframe = FRAME_stand202; +} + +mframe_t redmutant_frames_idle[] = +{ + drone_ai_stand, 0, redmutant_idle_loop, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL +}; +mmove_t redmutant_move_idle = { FRAME_stand202, FRAME_stand228, redmutant_frames_idle, redmutant_stand }; + +static void redmutant_idle(edict_t *self) +{ + self->monsterinfo.currentmove = &redmutant_move_idle; + gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + +mframe_t redmutant_frames_walk[] = +{ + drone_ai_walk, 3, NULL, + drone_ai_walk, 1, NULL, + drone_ai_walk, 5, NULL, + drone_ai_walk, 10, NULL, + drone_ai_walk, 13, NULL, + drone_ai_walk, 10, NULL, + drone_ai_walk, 0, NULL, + drone_ai_walk, 5, NULL, + drone_ai_walk, 6, NULL, + drone_ai_walk, 16, NULL, + drone_ai_walk, 15, NULL, + drone_ai_walk, 6, NULL +}; +mmove_t redmutant_move_walk = { FRAME_walk05, FRAME_walk16, redmutant_frames_walk, NULL }; + +static void redmutant_walk_loop(edict_t *self) +{ + self->monsterinfo.currentmove = &redmutant_move_walk; +} + +mframe_t redmutant_frames_start_walk[] = +{ + drone_ai_walk, 5, NULL, + drone_ai_walk, 5, NULL, + drone_ai_walk, -2, NULL, + drone_ai_walk, 1, NULL +}; +mmove_t redmutant_move_start_walk = { FRAME_walk01, FRAME_walk04, redmutant_frames_start_walk, redmutant_walk_loop }; + +static void redmutant_walk(edict_t *self) +{ + if (!self->goalentity) + self->goalentity = world; + self->monsterinfo.currentmove = &redmutant_move_start_walk; +} + +mframe_t redmutant_frames_run[] = +{ + drone_ai_run, 44, NULL, + drone_ai_run, 44, redmutant_step, + drone_ai_run, 28, NULL, + drone_ai_run, 8, redmutant_step, + drone_ai_run, 22, NULL, + drone_ai_run, 15, NULL +}; +mmove_t redmutant_move_run = { FRAME_run03, FRAME_run08, redmutant_frames_run, NULL }; + +static void redmutant_run(edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &redmutant_move_stand; + else + self->monsterinfo.currentmove = &redmutant_move_run; +} + +static void redmutant_hit_left(edict_t *self) +{ + int damage; + vec3_t aim; + + damage = M_MELEE_DMG_BASE + M_MELEE_DMG_ADDON * drone_damagelevel(self); + if (M_MELEE_DMG_MAX && damage > M_MELEE_DMG_MAX) + damage = M_MELEE_DMG_MAX; + + VectorSet(aim, 100, self->mins[0], 8); + if (fire_hit(self, aim, damage, 100)) + gi.sound(self, CHAN_WEAPON, sound_hit, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_WEAPON, sound_swing, 1, ATTN_NORM, 0); +} + +static void redmutant_hit_right(edict_t *self) +{ + int damage; + vec3_t aim; + + damage = M_MELEE_DMG_BASE + M_MELEE_DMG_ADDON * drone_damagelevel(self); + if (M_MELEE_DMG_MAX && damage > M_MELEE_DMG_MAX) + damage = M_MELEE_DMG_MAX; + + VectorSet(aim, 100, self->maxs[0], 8); + if (fire_hit(self, aim, damage, 100)) + gi.sound(self, CHAN_WEAPON, sound_hit2, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_WEAPON, sound_swing, 1, ATTN_NORM, 0); +} + +static void redmutant_check_refire(edict_t *self) +{ + if (G_ValidTarget(self, self->enemy, true, true) + && ((random() < 0.5) || (entdist(self, self->enemy) <= 96))) + self->monsterinfo.nextframe = FRAME_attack109; +} + +mframe_t redmutant_frames_attack[] = +{ + ai_charge, 0, NULL, + ai_charge, 0, redmutant_hit_left, + ai_charge, 0, redmutant_hit_right, + ai_charge, 0, NULL, + ai_charge, 0, redmutant_hit_right, + ai_charge, 0, NULL, + ai_charge, 0, redmutant_check_refire +}; +mmove_t redmutant_move_attack = { FRAME_attack109, FRAME_attack115, redmutant_frames_attack, redmutant_run }; + +static void redmutant_melee_unused(edict_t *self) +{ + (void)self; +} + +static void redmutant_jump_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + int damage, knockback; + vec3_t point, normal; + + if (self->health <= 0) + { + self->touch = NULL; + return; + } + + if (G_EntExists(other)) + { + VectorCopy(self->velocity, normal); + VectorNormalize(normal); + VectorMA(self->s.origin, self->maxs[0], normal, point); + + damage = 100 + 20 * drone_damagelevel(self); + damage = vrx_increase_monster_damage_by_talent(self->activator, damage); + knockback = min(damage, 500); + + T_Damage(other, self, self, self->velocity, point, normal, damage, knockback, 0, MOD_UNKNOWN); + self->style = 1; + } + + if (!M_CheckBottom(self)) + { + if (self->groundentity) + { + self->monsterinfo.nextframe = FRAME_attack102; + self->touch = NULL; + } + return; + } + + self->touch = NULL; +} + +extern mmove_t redmutant_move_jump_air; + +static void redmutant_jump_takeoff(edict_t *self) +{ + vec3_t forward; + qboolean high_jump = false; + + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); + self->lastsound = level.framenum; + AngleVectors(self->s.angles, forward, NULL, NULL); + self->s.origin[2] += 1; + + if (random() < 0.48) + high_jump = true; + + VectorScale(forward, 1125, self->velocity); + self->velocity[2] = high_jump ? 240 : 160; + self->groundentity = NULL; + self->monsterinfo.aiflags |= AI_DUCKED; + self->monsterinfo.attack_finished = level.time + (high_jump ? 1.55 : 1.3); + self->style = 0; + self->touch = redmutant_jump_touch; + + self->monsterinfo.currentmove = &redmutant_move_jump_air; +} + +static void redmutant_check_landing(edict_t *self) +{ + if (self->groundentity) + { + gi.sound(self, CHAN_WEAPON, sound_thud, 1, ATTN_NORM, 0); + self->monsterinfo.attack_finished = level.time + GetRandom(5, 15) * FRAMETIME; + self->monsterinfo.aiflags &= ~(AI_HOLD_FRAME | AI_DUCKED); + + self->monsterinfo.currentmove = &redmutant_move_jump_finish; + self->style = 0; + self->touch = NULL; + return; + } + + self->monsterinfo.aiflags |= AI_HOLD_FRAME; +} +mframe_t redmutant_frames_jump_air[] = { + ai_charge, 0, redmutant_check_landing +}; +mmove_t redmutant_move_jump_air = { FRAME_attack103, FRAME_attack103, redmutant_frames_jump_air, NULL }; + +mframe_t redmutant_frames_jump_start[] = { + ai_charge, 0, NULL, + ai_charge, 17, NULL, + ai_charge, 15, redmutant_jump_takeoff +}; +mmove_t redmutant_move_jump_start = { FRAME_attack101, FRAME_attack103, redmutant_frames_jump_start, NULL }; + +mframe_t redmutant_frames_jump_finish[] = { + ai_charge, 15, NULL, + ai_charge, 0, NULL, + ai_charge, 3, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t redmutant_move_jump_finish = { FRAME_attack104, FRAME_attack108, redmutant_frames_jump_finish, redmutant_post_jump }; + +static void redmutant_jump(edict_t *self) +{ + self->monsterinfo.currentmove = &redmutant_move_jump_start; +} + +static void redmutant_post_jump(edict_t *self) +{ + if (G_ValidTarget(self, self->enemy, true, true) && (entdist(self, self->enemy) <= MELEE_DISTANCE * 2)) + self->monsterinfo.currentmove = &redmutant_move_attack; + else + redmutant_run(self); +} + +static void redmutant_flip_takeoff(edict_t *self) +{ + vec3_t forward; + + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); + self->lastsound = level.framenum; + AngleVectors(self->s.angles, forward, NULL, NULL); + self->s.origin[2] += 1; + + VectorScale(forward, 800, self->velocity); + self->velocity[2] = 200; + self->groundentity = NULL; + self->monsterinfo.aiflags |= AI_DUCKED; +} + +static void redmutant_flip_check_landing(edict_t *self) +{ + if (self->groundentity) + { + gi.sound(self, CHAN_WEAPON, sound_thud, 1, ATTN_NORM, 0); + self->monsterinfo.aiflags &= ~(AI_HOLD_FRAME | AI_DUCKED); + return; + } + + self->monsterinfo.nextframe = FRAME_attack108; + self->monsterinfo.aiflags |= AI_HOLD_FRAME; +} + +mframe_t redmutant_frames_flip[] = { + ai_charge, 0, NULL, + ai_charge, 17, NULL, + ai_charge, 15, redmutant_flip_takeoff, + ai_charge, 15, NULL, + ai_charge, 15, NULL, + ai_charge, 0, NULL, + ai_charge, 3, NULL, + ai_charge, 0, redmutant_flip_check_landing +}; +mmove_t redmutant_move_flip = { FRAME_attack101, FRAME_attack108, redmutant_frames_flip, redmutant_post_jump }; + +static void redmutant_flip(edict_t *self) +{ + self->monsterinfo.currentmove = &redmutant_move_flip; +} + +static void redmutant_attack(edict_t *self) +{ + float dist; + float height_diff; + + if (!self->groundentity && !self->waterlevel) + return; + if (!G_ValidTarget(self, self->enemy, true, true)) + return; + + dist = entdist(self, self->enemy); + height_diff = self->enemy->absmin[2] - self->absmin[2]; + + if (dist <= MELEE_DISTANCE) + { + self->monsterinfo.currentmove = &redmutant_move_attack; + } + else if ((dist <= 384) && (height_diff > -64) && (height_diff < 96) && (random() < 0.8)) + { + if (random() < 0.5) + redmutant_jump(self); + else + redmutant_flip(self); + } +} + +static void redmutant_dead(edict_t *self) +{ + VectorSet(self->mins, -16, -16, -24); + VectorSet(self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + gi.linkentity(self); + M_PrepBodyRemoval(self); +} + +static void ai_move_slide_right(edict_t *self, float dist) +{ + M_walkmove(self, self->s.angles[YAW] + 90, dist); +} + +static void ai_move_slide_left(edict_t *self, float dist) +{ + M_walkmove(self, self->s.angles[YAW] - 90, dist); +} + +mframe_t redmutant_frames_death1[] = +{ + ai_move_slide_right, 0, NULL, + ai_move_slide_right, 0, NULL, + ai_move_slide_right, 0, NULL, + ai_move_slide_right, 2, NULL, + ai_move_slide_right, 5, NULL, + ai_move_slide_right, 7, NULL, + ai_move_slide_right, 6, NULL, + ai_move_slide_right, 2, NULL, + ai_move_slide_right, 0, NULL, + ai_move_slide_right, 0, NULL, + ai_move_slide_right, 0, NULL, + ai_move_slide_right, 0, NULL, + ai_move_slide_right, 0, NULL, + ai_move_slide_right, 0, NULL, + ai_move_slide_right, 0, NULL, + ai_move_slide_right, 0, NULL, + ai_move_slide_right, 0, NULL, + ai_move_slide_right, 0, NULL, + ai_move_slide_right, 0, NULL, + ai_move_slide_right, 0, NULL +}; +mmove_t redmutant_move_death1 = { FRAME_death101, FRAME_death120, redmutant_frames_death1, redmutant_dead }; + +mframe_t redmutant_frames_death2[] = +{ + ai_move_slide_left, 0, NULL, + ai_move_slide_left, 1, NULL, + ai_move_slide_left, 6, NULL, + ai_move_slide_left, 8, NULL, + ai_move_slide_left, 3, NULL, + ai_move_slide_left, 2, NULL, + ai_move_slide_left, 0, NULL +}; +mmove_t redmutant_move_death2 = { FRAME_death201, FRAME_death207, redmutant_frames_death2, redmutant_dead }; + +static void redmutant_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + M_Notify(self); + +#ifdef OLD_NOLAG_STYLE + if (nolag->value) + { + M_Remove(self, false, true); + return; + } +#endif + + if (self->health <= self->gib_health) + { + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); + if (vrx_spawn_nonessential_ent(self->s.origin)) + { + for (n = 0; n < 2; n++) + ThrowGib(self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); + for (n = 0; n < 4; n++) + ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowGib(self, "models/monsters/mutant/gibs/chest.md2", damage, GIB_ORGANIC); + ThrowHead(self, "models/monsters/mutant/gibs/head.md2", damage, GIB_ORGANIC); + } +#ifdef OLD_NOLAG_STYLE + M_Remove(self, false, false); +#else + if (nolag->value) + M_Remove(self, false, true); + else + M_Remove(self, false, false); +#endif + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + + gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + self->s.skinnum = 1; + self->monsterinfo.currentmove = (random() < 0.5) ? &redmutant_move_death1 : &redmutant_move_death2; + + DroneList_Remove(self); + + if (self->activator && !self->activator->client) + self->activator->num_monsters_real--; +} + +mframe_t redmutant_frames_pain1[] = +{ + ai_move, 4, NULL, + ai_move, -3, NULL, + ai_move, -8, NULL, + ai_move, 2, NULL, + ai_move, 5, NULL +}; +mmove_t redmutant_move_pain1 = { FRAME_pain101, FRAME_pain105, redmutant_frames_pain1, redmutant_run }; + +mframe_t redmutant_frames_pain2[] = +{ + ai_move, -24, NULL, + ai_move, 11, NULL, + ai_move, 5, NULL, + ai_move, -2, NULL, + ai_move, 6, NULL, + ai_move, 4, NULL +}; +mmove_t redmutant_move_pain2 = { FRAME_pain201, FRAME_pain206, redmutant_frames_pain2, redmutant_run }; + +mframe_t redmutant_frames_pain3[] = +{ + ai_move, -22, NULL, + ai_move, 3, NULL, + ai_move, 3, NULL, + ai_move, 2, NULL, + ai_move, 1, NULL, + ai_move, 1, NULL, + ai_move, 6, NULL, + ai_move, 3, NULL, + ai_move, 2, NULL, + ai_move, 0, NULL, + ai_move, 1, NULL +}; +mmove_t redmutant_move_pain3 = { FRAME_pain301, FRAME_pain311, redmutant_frames_pain3, redmutant_run }; + +static void redmutant_pain(edict_t *self, edict_t *other, float kick, int damage) +{ + float r; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3.0; + r = random(); + + if (r < 0.33) + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + else if (r < 0.66) + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + + if (skill->value == 3) + return; + + if (r < 0.33) + self->monsterinfo.currentmove = &redmutant_move_pain1; + else if (r < 0.66) + self->monsterinfo.currentmove = &redmutant_move_pain2; + else + self->monsterinfo.currentmove = &redmutant_move_pain3; +} + +void init_drone_redmutant(edict_t *self) +{ + sound_swing = gi.soundindex("mutant/mutatck1.wav"); + sound_hit = gi.soundindex("mutant/mutatck2.wav"); + sound_hit2 = gi.soundindex("mutant/mutatck3.wav"); + sound_death = gi.soundindex("mutant/mutdeth1.wav"); + sound_idle = gi.soundindex("mutant/mutidle1.wav"); + sound_pain1 = gi.soundindex("mutant/mutpain1.wav"); + sound_pain2 = gi.soundindex("mutant/mutpain2.wav"); + sound_sight = gi.soundindex("mutant/mutsght1.wav"); + sound_search = gi.soundindex("mutant/mutsrch1.wav"); + sound_step1 = gi.soundindex("mutant/step1.wav"); + sound_step2 = gi.soundindex("mutant/step2.wav"); + sound_step3 = gi.soundindex("mutant/step3.wav"); + sound_thud = gi.soundindex("mutant/thud1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/vault/monsters/mutant/tris.md2"); + gi.modelindex("models/monsters/mutant/gibs/head.md2"); + gi.modelindex("models/monsters/mutant/gibs/chest.md2"); + gi.modelindex("models/monsters/mutant/gibs/hand.md2"); + gi.modelindex("models/monsters/mutant/gibs/foot.md2"); + + VectorSet(self->mins, -18, -18, -24); + VectorSet(self->maxs, 18, 18, 30); + + self->health = M_MUTANT_INITIAL_HEALTH + M_MUTANT_ADDON_HEALTH * self->monsterinfo.level; + self->max_health = self->health; + self->gib_health = -BASE_GIB_HEALTH; + self->mass = 350; + + self->monsterinfo.pain_chance = 0.15f; + self->pain = redmutant_pain; + self->die = redmutant_die; + + self->monsterinfo.stand = redmutant_stand; + self->monsterinfo.walk = redmutant_walk; + self->monsterinfo.run = redmutant_run; + self->monsterinfo.attack = redmutant_attack; + self->monsterinfo.melee = redmutant_melee_unused; + self->monsterinfo.sight = redmutant_sight; + self->monsterinfo.idle = redmutant_idle; + self->monsterinfo.jumpup = 64; + self->monsterinfo.jumpdn = 512; + self->monsterinfo.aiflags |= AI_NO_CIRCLE_STRAFE; + + self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD; + self->monsterinfo.power_armor_power = M_MUTANT_INITIAL_ARMOR + M_MUTANT_ADDON_ARMOR * self->monsterinfo.level; + self->monsterinfo.control_cost = M_MUTANT_CONTROL_COST; + self->monsterinfo.cost = M_MUTANT_COST; + self->mtype = M_REDMUTANT; + + gi.linkentity(self); + + self->monsterinfo.currentmove = &redmutant_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + self->nextthink = level.time + FRAMETIME; +} diff --git a/src/entities/drone/drone_runnertank.c b/src/entities/drone/drone_runnertank.c new file mode 100644 index 00000000..df5c5dcf --- /dev/null +++ b/src/entities/drone/drone_runnertank.c @@ -0,0 +1,866 @@ +#include "g_local.h" +#include "../../quake2/monsterframes/m_runnertank.h" + +static int sound_thud; +static int sound_pain; +static int sound_idle; +static int sound_die; +static int sound_step; +static int sound_sight; +static int sound_windup; +static int sound_strike; + +#define RUNNERTANK_JUMP_ATTACK_DELAY 12.0f +#define RUNNERTANK_JUMP_ATTACK_FOV 35 +#define RUNNERTANK_JUMP_ATTACK_DROP_RADIUS 90.0f +#define RUNNERTANK_JUMP_ATTACK_DROP_SPEED 900.0f +#define RUNNERTANK_JUMP_ATTACK_DROP_GRAVITY 3.0f + +static void runnertank_stand(edict_t *self); +static void runnertank_walk(edict_t *self); +static void runnertank_walk_loop(edict_t *self); +static void runnertank_run(edict_t *self); +static void runnertank_attack(edict_t *self); +static void runnertank_melee(edict_t *self); +static void runnertank_restrike(edict_t *self); +static void runnertank_reattack_blast(edict_t *self); +static void runnertank_refire_rocket(edict_t *self); +static void runnertank_doattack_rocket(edict_t *self); +static void runnertank_jump_attack_takeoff(edict_t *self); +static void runnertank_jump_attack_hold(edict_t *self); + +static void runnertank_footstep(edict_t *self) +{ + gi.sound(self, CHAN_BODY, sound_step, 1, ATTN_NORM, 0); +} + +static void runnertank_thud(edict_t *self) +{ + gi.sound(self, CHAN_BODY, sound_thud, 1, ATTN_NORM, 0); +} + +static void runnertank_windup(edict_t *self) +{ + gi.sound(self, CHAN_WEAPON, sound_windup, 1, ATTN_NORM, 0); +} + +static void runnertank_idle(edict_t *self) +{ + gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); + self->superspeed = false; +} + +static void runnertank_sight(edict_t *self, edict_t *other) +{ + (void)other; + + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +mframe_t runnertank_frames_stand[] = +{ + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL +}; +mmove_t runnertank_move_stand = { FRAME_stand01, FRAME_stand30, runnertank_frames_stand, NULL }; + +static void runnertank_stand(edict_t *self) +{ + self->monsterinfo.currentmove = &runnertank_move_stand; +} + +mframe_t runnertank_frames_walk_start[] = +{ + drone_ai_walk, 0, NULL, + drone_ai_walk, 0, NULL, + drone_ai_walk, 0, NULL, + drone_ai_walk, 0, NULL, + drone_ai_walk, 0, NULL, + drone_ai_walk, 0, NULL, + drone_ai_walk, 0, NULL, + drone_ai_walk, 0, NULL, + drone_ai_walk, 0, NULL, + drone_ai_walk, 0, NULL, + drone_ai_walk, 0, NULL, + drone_ai_walk, 0, NULL, + drone_ai_walk, 0, NULL, + drone_ai_walk, 0, NULL, + drone_ai_walk, 0, NULL +}; +mmove_t runnertank_move_walk_start = { FRAME_walk01, FRAME_walk15, runnertank_frames_walk_start, runnertank_walk_loop }; + +mframe_t runnertank_frames_walk[] = +{ + drone_ai_walk, 4, runnertank_footstep, + drone_ai_walk, 4, NULL, + drone_ai_walk, 3, NULL, + drone_ai_walk, 5, NULL, + drone_ai_walk, 4, NULL, + drone_ai_walk, 5, NULL, + drone_ai_walk, 7, NULL, + drone_ai_walk, 7, NULL, + drone_ai_walk, 6, runnertank_footstep, + drone_ai_walk, 6, NULL, + drone_ai_walk, 4, NULL, + drone_ai_walk, 5, NULL, + drone_ai_walk, 3, NULL, + drone_ai_walk, 4, NULL, + drone_ai_walk, 6, NULL, + drone_ai_walk, 7, NULL, + drone_ai_walk, 5, NULL +}; +mmove_t runnertank_move_walk = { FRAME_walk22, FRAME_walk38, runnertank_frames_walk, NULL }; + +static void runnertank_walk_loop(edict_t *self) +{ + if (!self->goalentity) + self->goalentity = world; + self->monsterinfo.currentmove = &runnertank_move_walk; +} + +static void runnertank_walk(edict_t *self) +{ + if (!self->goalentity) + self->goalentity = world; + + if (self->s.frame >= FRAME_walk22 && self->s.frame <= FRAME_walk38) + self->monsterinfo.currentmove = &runnertank_move_walk; + else + self->monsterinfo.currentmove = &runnertank_move_walk_start; +} + +mframe_t runnertank_frames_run[] = +{ + drone_ai_run, 14, runnertank_footstep, + drone_ai_run, 18, NULL, + drone_ai_run, 15, NULL, + drone_ai_run, 15, NULL, + drone_ai_run, 15, NULL, + drone_ai_run, 19, runnertank_footstep, + drone_ai_run, 15, NULL, + drone_ai_run, 13, NULL, + drone_ai_run, 18, NULL, + drone_ai_run, 17, NULL +}; +mmove_t runnertank_move_run = { FRAME_run01, FRAME_run10, runnertank_frames_run, NULL }; + +static void runnertank_run(edict_t *self) +{ + if (self->deadflag == DEAD_DEAD) + return; + + if (self->enemy && self->enemy->client) + self->monsterinfo.aiflags |= AI_BRUTAL; + else + self->monsterinfo.aiflags &= ~AI_BRUTAL; + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + self->monsterinfo.currentmove = &runnertank_move_stand; + return; + } + + self->monsterinfo.currentmove = &runnertank_move_run; +} + +static void runnertank_rail(edict_t *self) +{ + int flash_number, damage; + vec3_t forward, start; + + if (self->s.frame == FRAME_attak110) + flash_number = MZ2_TANK_BLASTER_1; + else if (self->s.frame == FRAME_attak113) + flash_number = MZ2_TANK_BLASTER_2; + else + flash_number = MZ2_TANK_BLASTER_3; + + damage = M_RAILGUN_DMG_BASE + M_RAILGUN_DMG_ADDON * drone_damagelevel(self); + if (M_RAILGUN_DMG_MAX && damage > M_RAILGUN_DMG_MAX) + damage = M_RAILGUN_DMG_MAX; + + MonsterAim(self, M_HITSCAN_INSTANT_ACC, 0, false, flash_number, forward, start); + monster_fire_railgun(self, start, forward, damage, damage, MZ2_GLADIATOR_RAILGUN_1); +} + +static void runnertank_rocket(edict_t *self) +{ + int flash_number, damage, speed; + vec3_t forward, start; + + if (!self->enemy || !self->enemy->inuse) + return; + + if (self->s.frame == FRAME_attak324) + flash_number = MZ2_TANK_ROCKET_1; + else if (self->s.frame == FRAME_attak325) + flash_number = MZ2_TANK_ROCKET_2; + else + flash_number = MZ2_TANK_ROCKET_3; + + damage = M_ROCKETLAUNCHER_DMG_BASE + M_ROCKETLAUNCHER_DMG_ADDON * drone_damagelevel(self); + if (M_ROCKETLAUNCHER_DMG_MAX && damage > M_ROCKETLAUNCHER_DMG_MAX) + damage = M_ROCKETLAUNCHER_DMG_MAX; + speed = M_ROCKETLAUNCHER_SPEED_BASE + M_ROCKETLAUNCHER_SPEED_ADDON * drone_damagelevel(self); + if (M_ROCKETLAUNCHER_SPEED_MAX && speed > M_ROCKETLAUNCHER_SPEED_MAX) + speed = M_ROCKETLAUNCHER_SPEED_MAX; + + MonsterAim(self, M_PROJECTILE_ACC, speed, true, flash_number, forward, start); + monster_fire_rocket(self, start, forward, damage, speed, flash_number); +} + +static void runnertank_plasma(edict_t *self) +{ + vec3_t forward, start; + int flash_number, damage, speed, radius_damage; + + if (!self->enemy || !self->enemy->inuse) + return; + + flash_number = MZ2_TANK_MACHINEGUN_1 + (self->s.frame - FRAME_attak406); + damage = M_BLASTER_DMG_BASE + M_BLASTER_DMG_ADDON * drone_damagelevel(self); + if (M_BLASTER_DMG_MAX && damage > M_BLASTER_DMG_MAX) + damage = M_BLASTER_DMG_MAX; + speed = M_BLASTER_SPEED_BASE + M_BLASTER_SPEED_ADDON * drone_damagelevel(self); + if (M_BLASTER_SPEED_MAX && speed > M_BLASTER_SPEED_MAX) + speed = M_BLASTER_SPEED_MAX; + radius_damage = max(1, damage / 2); + + MonsterAim(self, M_PROJECTILE_ACC, speed, false, flash_number, forward, start); + fire_plasma(self, start, forward, damage, speed, 40, radius_damage); +} + +static void runnertank_strike_sound(edict_t *self) +{ + gi.sound(self, CHAN_WEAPON, sound_strike, 1, ATTN_NORM, 0); +} + +static void runnertank_meleeattack(edict_t *self) +{ + int damage; + trace_t tr; + edict_t *other = NULL; + vec3_t v; + + if (!self->groundentity) + return; + + damage = M_MELEE_DMG_BASE + M_MELEE_DMG_ADDON * drone_damagelevel(self); + if (M_MELEE_DMG_MAX && damage > M_MELEE_DMG_MAX) + damage = M_MELEE_DMG_MAX; + + gi.sound(self, CHAN_AUTO, sound_strike, 1, ATTN_NORM, 0); + + while ((other = findradius(other, self->s.origin, 128)) != NULL) + { + if (!G_ValidTarget(self, other, true, true)) + continue; + if (que_typeexists(self->curses, CURSE) && rand() > 0.2) + continue; + + VectorSubtract(other->s.origin, self->s.origin, v); + VectorNormalize(v); + tr = gi.trace(self->s.origin, NULL, NULL, other->s.origin, self, (MASK_PLAYERSOLID | MASK_MONSTERSOLID)); + T_Damage(other, self, self, v, tr.endpos, tr.plane.normal, damage, 200, 0, MOD_TANK_PUNCH); + } +} + +static void runnertank_attack_finished(edict_t *self) +{ + M_DelayNextAttack(self, 0.5, false); + runnertank_run(self); +} + +mframe_t runnertank_frames_attack_blast[] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, runnertank_rail, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, runnertank_rail, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, runnertank_rail +}; +mmove_t runnertank_move_attack_blast = { FRAME_attak101, FRAME_attak116, runnertank_frames_attack_blast, runnertank_reattack_blast }; + +mframe_t runnertank_frames_reattack_blast[] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, runnertank_rail, + ai_charge, 0, NULL, + ai_charge, 0, runnertank_rail +}; +mmove_t runnertank_move_reattack_blast = { FRAME_attak111, FRAME_attak116, runnertank_frames_reattack_blast, runnertank_reattack_blast }; + +mframe_t runnertank_frames_attack_post_blast[] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 2, NULL, + ai_move, 3, NULL, + ai_move, 2, NULL, + ai_move, -2, runnertank_footstep +}; +mmove_t runnertank_move_attack_post_blast = { FRAME_attak117, FRAME_attak122, runnertank_frames_attack_post_blast, runnertank_attack_finished }; + +static void runnertank_reattack_blast(edict_t *self) +{ + if (!G_ValidTarget(self, self->enemy, true, true) || !visible(self, self->enemy)) + { + self->monsterinfo.currentmove = &runnertank_move_attack_post_blast; + M_DelayNextAttack(self, 0, true); + return; + } + + M_ContinueAttack(self, &runnertank_move_reattack_blast, &runnertank_move_attack_post_blast, 0, 1024, 0.5); +} + +mframe_t runnertank_frames_strike[] = +{ + ai_move, 0, runnertank_windup, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, runnertank_meleeattack +}; +mmove_t runnertank_move_strike = { FRAME_attak222, FRAME_attak226, runnertank_frames_strike, runnertank_restrike }; + +mframe_t runnertank_frames_post_strike[] = +{ + ai_move, 0, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -3, NULL, + ai_move, -10, NULL, + ai_move, -10, NULL, + ai_move, -2, NULL, + ai_move, -3, NULL, + ai_move, -2, runnertank_footstep +}; +mmove_t runnertank_move_post_strike = { FRAME_attak227, FRAME_attak238, runnertank_frames_post_strike, runnertank_attack_finished }; + +static void runnertank_restrike(edict_t *self) +{ + M_ContinueAttack(self, &runnertank_move_strike, &runnertank_move_post_strike, 0, 128, 0.66); +} + +mframe_t runnertank_frames_attack_pre_rocket[] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t runnertank_move_attack_pre_rocket = { FRAME_attak303, FRAME_attak312, runnertank_frames_attack_pre_rocket, runnertank_doattack_rocket }; + +mframe_t runnertank_frames_attack_fire_rocket[] = +{ + ai_charge, 0, NULL, + ai_charge, 0, runnertank_rocket, + ai_charge, 1, NULL, + ai_charge, 2, NULL, + ai_charge, 7, runnertank_rocket, + ai_charge, 7, NULL, + ai_charge, 7, runnertank_footstep, + ai_charge, 0, runnertank_rocket, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t runnertank_move_attack_fire_rocket = { FRAME_attak312, FRAME_attak321, runnertank_frames_attack_fire_rocket, runnertank_refire_rocket }; + +mframe_t runnertank_frames_attack_post_rocket[] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, -9, NULL, + ai_charge, -8, NULL, + ai_charge, -7, NULL, + ai_charge, -1, NULL, + ai_charge, -1, runnertank_footstep, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t runnertank_move_attack_post_rocket = { FRAME_attak322, FRAME_attak335, runnertank_frames_attack_post_rocket, runnertank_attack_finished }; + +static void runnertank_refire_rocket(edict_t *self) +{ + if (!G_ValidTarget(self, self->enemy, true, true) || !visible(self, self->enemy)) + { + self->monsterinfo.currentmove = &runnertank_move_attack_post_rocket; + M_DelayNextAttack(self, 0, true); + return; + } + + M_ContinueAttack(self, &runnertank_move_attack_fire_rocket, &runnertank_move_attack_post_rocket, 0, 768, 0.35); +} + +static void runnertank_doattack_rocket(edict_t *self) +{ + self->monsterinfo.currentmove = &runnertank_move_attack_fire_rocket; +} + +mframe_t runnertank_frames_attack_chain[] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, runnertank_plasma, + ai_charge, 0, runnertank_plasma, + ai_charge, 0, runnertank_plasma, + ai_charge, 0, runnertank_plasma, + ai_charge, 0, runnertank_plasma, + ai_charge, 0, runnertank_plasma, + ai_charge, 0, runnertank_plasma, + ai_charge, 0, runnertank_plasma, + ai_charge, 0, runnertank_plasma, + ai_charge, 0, NULL +}; +mmove_t runnertank_move_attack_chain = { FRAME_attak404, FRAME_attak415, runnertank_frames_attack_chain, runnertank_attack_finished }; + +static void runnertank_jump_attack_takeoff(edict_t *self) +{ + const int speed = 800; + vec3_t forward; + float height_diff; + + if (!G_ValidTarget(self, self->enemy, true, true)) + return; + + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); + self->lastsound = level.framenum; + + AngleVectors(self->s.angles, forward, NULL, NULL); + self->s.origin[2] += 1; + self->groundentity = NULL; + + VectorScale(forward, speed, self->velocity); + height_diff = self->enemy->absmin[2] - self->absmin[2]; + self->velocity[2] = 260; + if (height_diff > 32) + self->velocity[2] += min(height_diff, 120); + + self->gravity = 1.35; + self->monsterinfo.pausetime = level.time + 1.6; + self->monsterinfo.melee_finished = level.time + RUNNERTANK_JUMP_ATTACK_DELAY; + self->monsterinfo.aiflags |= AI_DUCKED; +} + +static void runnertank_jump_attack_hold(edict_t *self) +{ + vec3_t v; + qboolean close_enough = false; + + if (G_ValidTarget(self, self->enemy, true, true)) + { + VectorSubtract(self->enemy->s.origin, self->s.origin, v); + self->ideal_yaw = vectoyaw(v); + M_ChangeYaw(self); + close_enough = entdist(self, self->enemy) <= RUNNERTANK_JUMP_ATTACK_DROP_RADIUS; + + if (!self->groundentity && close_enough) + { + self->velocity[0] *= 0.35f; + self->velocity[1] *= 0.35f; + if (self->velocity[2] > -RUNNERTANK_JUMP_ATTACK_DROP_SPEED) + self->velocity[2] = -RUNNERTANK_JUMP_ATTACK_DROP_SPEED; + self->gravity = RUNNERTANK_JUMP_ATTACK_DROP_GRAVITY; + if (self->monsterinfo.pausetime > level.time + 0.35f) + self->monsterinfo.pausetime = level.time + 0.35f; + } + } + + if (self->groundentity || (self->waterlevel > 1) || (level.time > self->monsterinfo.pausetime)) + { + self->monsterinfo.aiflags &= ~(AI_HOLD_FRAME | AI_DUCKED); + self->gravity = 1.0; + + if (self->groundentity && G_ValidTarget(self, self->enemy, true, true) && entdist(self, self->enemy) <= RUNNERTANK_JUMP_ATTACK_DROP_RADIUS) + self->monsterinfo.currentmove = &runnertank_move_strike; + } + else + { + self->monsterinfo.aiflags |= AI_HOLD_FRAME; + } +} + +mframe_t runnertank_frames_jump_attack[] = +{ + ai_charge, 15, NULL, + ai_move, 0, runnertank_jump_attack_takeoff, + ai_move, 0, runnertank_jump_attack_hold, + ai_move, 0, runnertank_jump_attack_hold, + ai_move, 0, runnertank_jump_attack_hold +}; +mmove_t runnertank_move_jump_attack = { FRAME_run01, FRAME_run05, runnertank_frames_jump_attack, runnertank_attack_finished }; + +static void runnertank_start_jump_attack(edict_t *self) +{ + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + self->monsterinfo.currentmove = &runnertank_move_jump_attack; + self->monsterinfo.nextframe = FRAME_run01; +} + +static qboolean runnertank_can_jump_attack(edict_t *self, float range) +{ + float height_diff; + + if (level.time < self->monsterinfo.melee_finished) + return false; + if (!self->groundentity || (self->waterlevel > 1)) + return false; + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + return false; + if ((range <= 128) || (range > 512)) + return false; + if (!nearfov(self, self->enemy, 0, RUNNERTANK_JUMP_ATTACK_FOV)) + return false; + + height_diff = self->enemy->absmin[2] - self->absmin[2]; + return (height_diff > -64) && (height_diff < 128); +} + +static void runnertank_melee(edict_t *self) +{ + float range; + + if (!G_ValidTarget(self, self->enemy, true, true)) + return; + + range = entdist(self, self->enemy); + if ((range <= 128) && self->groundentity) + self->monsterinfo.currentmove = &runnertank_move_strike; + else if (runnertank_can_jump_attack(self, range)) + runnertank_start_jump_attack(self); +} + +static void runnertank_attack(edict_t *self) +{ + float r, range; + qboolean attack_started = false; + + if (!G_ValidTarget(self, self->enemy, true, true)) + return; + + r = random(); + range = entdist(self, self->enemy); + + if ((range <= 128) && self->groundentity) + { + self->monsterinfo.currentmove = &runnertank_move_strike; + attack_started = true; + } + else if (runnertank_can_jump_attack(self, range)) + { + runnertank_start_jump_attack(self); + attack_started = true; + } + else if (range <= 256) + { + if (r <= 0.35) + { + self->monsterinfo.currentmove = &runnertank_move_attack_chain; + attack_started = true; + } + else if (r <= 0.5) + { + self->monsterinfo.currentmove = &runnertank_move_attack_pre_rocket; + attack_started = true; + } + } + else if (range <= 640) + { + if (r <= 0.25) + { + self->monsterinfo.currentmove = &runnertank_move_attack_chain; + attack_started = true; + } + else if (r <= 0.55) + { + self->monsterinfo.currentmove = &runnertank_move_attack_blast; + attack_started = true; + } + else if (r <= 0.7) + { + self->monsterinfo.currentmove = &runnertank_move_attack_pre_rocket; + attack_started = true; + } + } + else + { + if (r <= 0.35) + { + self->monsterinfo.currentmove = &runnertank_move_attack_blast; + attack_started = true; + } + else if (r <= 0.55) + { + self->monsterinfo.currentmove = &runnertank_move_attack_pre_rocket; + attack_started = true; + } + } + + if (attack_started) + M_DelayNextAttack(self, 0, true); +} + +mframe_t runnertank_frames_pain1[] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t runnertank_move_pain1 = { FRAME_pain201, FRAME_pain204, runnertank_frames_pain1, runnertank_run }; + +mframe_t runnertank_frames_pain3[] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, runnertank_footstep +}; +mmove_t runnertank_move_pain3 = { FRAME_pain301, FRAME_pain316, runnertank_frames_pain3, runnertank_run }; + +static void runnertank_pain(edict_t *self, edict_t *other, float kick, int damage) +{ + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3.0; + gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); + + if (skill->value == 3) + return; + + if (damage <= 30 || random() < 0.5) + self->monsterinfo.currentmove = &runnertank_move_pain1; + else + self->monsterinfo.currentmove = &runnertank_move_pain3; +} + +static void runnertank_dead(edict_t *self) +{ + VectorSet(self->mins, -16, -16, -16); + VectorSet(self->maxs, 16, 16, 0); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + gi.linkentity(self); + M_PrepBodyRemoval(self); +} + +static void runnertank_shrink(edict_t *self) +{ + self->maxs[2] = 0; + self->svflags |= SVF_DEADMONSTER; + gi.linkentity(self); +} + +mframe_t runnertank_frames_death[] = +{ + ai_move, -7, NULL, + ai_move, -2, NULL, + ai_move, -2, NULL, + ai_move, 1, NULL, + ai_move, 3, NULL, + ai_move, 6, NULL, + ai_move, 1, NULL, + ai_move, 1, NULL, + ai_move, 2, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -2, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -3, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -4, NULL, + ai_move, -6, NULL, + ai_move, -4, NULL, + ai_move, -5, NULL, + ai_move, -7, runnertank_shrink, + ai_move, -15, runnertank_thud, + ai_move, -5, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t runnertank_move_death = { FRAME_death01, FRAME_death32, runnertank_frames_death, runnertank_dead }; + +static void runnertank_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + M_Notify(self); + +#ifdef OLD_NOLAG_STYLE + if (nolag->value) + { + M_Remove(self, false, true); + return; + } +#endif + + if (self->health <= self->gib_health) + { + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); + if (vrx_spawn_nonessential_ent(self->s.origin)) + { + for (n = 0; n < 1; n++) + ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + for (n = 0; n < 4; n++) + ThrowGib(self, "models/objects/gibs/sm_metal/tris.md2", damage, GIB_METALLIC); + ThrowGib(self, "models/objects/gibs/chest/tris.md2", damage, GIB_ORGANIC); + } +#ifdef OLD_NOLAG_STYLE + M_Remove(self, false, false); +#else + if (nolag->value) + M_Remove(self, false, true); + else + M_Remove(self, false, false); +#endif + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + + DroneList_Remove(self); + + gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + self->monsterinfo.currentmove = &runnertank_move_death; + + if (self->activator && !self->activator->client) + self->activator->num_monsters_real--; +} + +void init_drone_runnertank(edict_t *self) +{ + self->s.modelindex = gi.modelindex("models/vault/monsters/tank/tris.md2"); + VectorSet(self->mins, -28, -28, -14); + VectorSet(self->maxs, 28, 28, 56); + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + sound_pain = gi.soundindex("tank/tnkpain2.wav"); + sound_idle = gi.soundindex("tank/tnkidle1.wav"); + sound_die = gi.soundindex("tank/death.wav"); + sound_step = gi.soundindex("tank/step.wav"); + sound_windup = gi.soundindex("tank/tnkatck4.wav"); + sound_strike = gi.soundindex("tank/tnkatck5.wav"); + sound_sight = gi.soundindex("tank/sight1.wav"); + sound_thud = gi.soundindex("tank/tnkdeth2.wav"); + + gi.soundindex("tank/tnkatck1.wav"); + gi.soundindex("tank/tnkatk2a.wav"); + gi.soundindex("tank/tnkatk2b.wav"); + gi.soundindex("tank/tnkatk2c.wav"); + gi.soundindex("tank/tnkatk2d.wav"); + gi.soundindex("tank/tnkatk2e.wav"); + gi.soundindex("tank/tnkatck3.wav"); + + self->health = M_TANK_INITIAL_HEALTH + M_TANK_ADDON_HEALTH * self->monsterinfo.level; + self->max_health = self->health; + self->gib_health = -200; + self->mass = 500; + + self->pain = runnertank_pain; + self->die = runnertank_die; + + self->monsterinfo.stand = runnertank_stand; + self->monsterinfo.walk = runnertank_walk; + self->monsterinfo.run = runnertank_run; + self->monsterinfo.attack = runnertank_attack; + self->monsterinfo.melee = runnertank_melee; + self->monsterinfo.sight = runnertank_sight; + self->monsterinfo.idle = runnertank_idle; + self->monsterinfo.jumpup = 64; + self->monsterinfo.jumpdn = 512; + self->monsterinfo.aiflags |= AI_NO_CIRCLE_STRAFE; + + self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD; + self->monsterinfo.power_armor_power = M_TANK_INITIAL_ARMOR + M_TANK_ADDON_ARMOR * self->monsterinfo.level; + self->monsterinfo.control_cost = M_TANK_CONTROL_COST; + self->monsterinfo.cost = M_TANK_COST; + self->mtype = M_RUNNERTANK; + self->s.renderfx |= RF_CUSTOMSKIN; + self->s.skinnum = gi.imageindex("models/vault/monsters/tank/skin.pcx"); + + gi.linkentity(self); + + self->monsterinfo.currentmove = &runnertank_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + self->nextthink = level.time + FRAMETIME; +} diff --git a/src/entities/drone/drone_stalker.c b/src/entities/drone/drone_stalker.c new file mode 100644 index 00000000..07d82863 --- /dev/null +++ b/src/entities/drone/drone_stalker.c @@ -0,0 +1,331 @@ +/* +============================================================================== + +STALKER + +============================================================================== +*/ + +#include "g_local.h" +#include "../../quake2/monsterframes/m_rogue_stalker.h" + +static int sound_pain; +static int sound_die; +static int sound_sight; +static int sound_punch_hit1; +static int sound_punch_hit2; +static int sound_idle; + +void drone_ai_stand(edict_t *self, float dist); +void drone_ai_run(edict_t *self, float dist); +void drone_ai_walk(edict_t *self, float dist); + +static void stalker_stand(edict_t *self); +static void stalker_run(edict_t *self); + +static void stalker_sight(edict_t *self, edict_t *other) +{ + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +static void stalker_idle(edict_t *self) +{ + gi.sound(self, CHAN_VOICE, sound_idle, 0.5, ATTN_IDLE, 0); +} + +mframe_t stalker_frames_stand[] = +{ + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, stalker_idle, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL +}; +mmove_t stalker_move_stand = { FRAME_idle01, FRAME_idle21, stalker_frames_stand, NULL }; + +static void stalker_stand(edict_t *self) +{ + self->monsterinfo.currentmove = &stalker_move_stand; +} + +mframe_t stalker_frames_walk[] = +{ + drone_ai_walk, 10, NULL, + drone_ai_walk, 10, NULL, + drone_ai_walk, 10, NULL, + drone_ai_walk, 10, NULL, + drone_ai_walk, 10, NULL, + drone_ai_walk, 10, NULL, + drone_ai_walk, 10, NULL, + drone_ai_walk, 10, NULL +}; +mmove_t stalker_move_walk = { FRAME_walk01, FRAME_walk08, stalker_frames_walk, stalker_run }; + +static void stalker_walk(edict_t *self) +{ + self->monsterinfo.currentmove = &stalker_move_walk; +} + +mframe_t stalker_frames_run[] = +{ + drone_ai_run, 24, NULL, + drone_ai_run, 28, NULL, + drone_ai_run, 32, NULL, + drone_ai_run, 28, NULL +}; +mmove_t stalker_move_run = { FRAME_run01, FRAME_run04, stalker_frames_run, NULL }; + +static void stalker_run(edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &stalker_move_stand; + else + self->monsterinfo.currentmove = &stalker_move_run; +} + +static void stalker_fire_ionripper(edict_t *self) +{ + int damage, speed; + vec3_t forward, right, start, target, dir, offset; + + if (!G_EntExists(self->enemy)) + return; + + damage = IONRIPPER_INITIAL_DAMAGE + IONRIPPER_ADDON_DAMAGE * drone_damagelevel(self); + speed = IONRIPPER_INITIAL_SPEED + IONRIPPER_ADDON_SPEED * drone_damagelevel(self); + + AngleVectors(self->s.angles, forward, right, NULL); + VectorSet(offset, 16, 0, 6); + G_ProjectSource(self->s.origin, offset, forward, right, start); + + VectorCopy(self->enemy->s.origin, target); + target[2] += self->enemy->viewheight; + VectorSubtract(target, start, dir); + VectorNormalize(dir); + + fire_ionripper(self, start, dir, damage, speed, EF_IONRIPPER); + + gi.WriteByte(svc_muzzleflash); + gi.WriteShort(self - g_edicts); + gi.WriteByte(MZ_IONRIPPER | MZ_SILENCED); + gi.multicast(start, MULTICAST_PVS); +} + +mframe_t stalker_frames_shoot[] = +{ + drone_ai_run, 10, NULL, + drone_ai_run, 10, stalker_fire_ionripper, + drone_ai_run, 12, stalker_fire_ionripper, + drone_ai_run, 12, stalker_fire_ionripper +}; +mmove_t stalker_move_shoot = { FRAME_run01, FRAME_run04, stalker_frames_shoot, stalker_run }; + +static void stalker_attack(edict_t *self) +{ + self->monsterinfo.currentmove = &stalker_move_shoot; + M_DelayNextAttack(self, 0.4, true); +} + +static void stalker_swing_attack(edict_t *self) +{ + int damage; + + damage = M_MELEE_DMG_BASE + M_MELEE_DMG_ADDON * drone_damagelevel(self); + if (M_MELEE_DMG_MAX && damage > M_MELEE_DMG_MAX) + damage = M_MELEE_DMG_MAX; + + if (M_MeleeAttack(self, self->enemy, 80, damage, 160)) + gi.sound(self, CHAN_WEAPON, (random() < 0.5) ? sound_punch_hit1 : sound_punch_hit2, 1, ATTN_NORM, 0); +} + +mframe_t stalker_frames_swing_l[] = +{ + ai_charge, 0, NULL, + ai_charge, 4, NULL, + ai_charge, 5, stalker_swing_attack, + ai_charge, 5, NULL, + ai_charge, 5, stalker_swing_attack, + ai_charge, 4, NULL, + ai_charge, 2, NULL, + ai_charge, 0, NULL +}; +mmove_t stalker_move_swing_l = { FRAME_attack01, FRAME_attack08, stalker_frames_swing_l, stalker_run }; + +mframe_t stalker_frames_swing_r[] = +{ + ai_charge, 0, NULL, + ai_charge, 6, stalker_swing_attack, + ai_charge, 5, NULL, + ai_charge, 5, stalker_swing_attack, + ai_charge, 0, NULL +}; +mmove_t stalker_move_swing_r = { FRAME_attack11, FRAME_attack15, stalker_frames_swing_r, stalker_run }; + +static void stalker_melee(edict_t *self) +{ + if (!G_ValidTarget(self, self->enemy, true, true) || entdist(self, self->enemy) > 96) + { + self->monsterinfo.melee_finished = level.time + 0.5; + stalker_run(self); + return; + } + + if (random() < 0.5) + self->monsterinfo.currentmove = &stalker_move_swing_l; + else + self->monsterinfo.currentmove = &stalker_move_swing_r; +} + +mframe_t stalker_frames_pain[] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t stalker_move_pain = { FRAME_pain01, FRAME_pain04, stalker_frames_pain, stalker_run }; + +static void stalker_pain(edict_t *self, edict_t *other, float kick, int damage) +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum |= 1; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3.0; + gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); + + if (skill->value == 3) + return; + + self->monsterinfo.currentmove = &stalker_move_pain; +} + +static void stalker_dead(edict_t *self) +{ + VectorSet(self->mins, -28, -28, -18); + VectorSet(self->maxs, 28, 28, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + gi.linkentity(self); + M_PrepBodyRemoval(self); +} + +mframe_t stalker_frames_death[] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t stalker_move_death = { FRAME_death01, FRAME_death09, stalker_frames_death, stalker_dead }; + +static void stalker_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + M_Notify(self); + + if (self->health <= self->gib_health) + { + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); + if (vrx_spawn_nonessential_ent(self->s.origin)) + { + ThrowGib(self, "models/monsters/stalker/gibs/bodya.md2", damage, GIB_ORGANIC); + ThrowGib(self, "models/monsters/stalker/gibs/bodyb.md2", damage, GIB_ORGANIC); + for (n = 0; n < 2; n++) + ThrowGib(self, "models/monsters/stalker/gibs/claw.md2", damage, GIB_ORGANIC); + ThrowHead(self, "models/monsters/stalker/gibs/head.md2", damage, GIB_ORGANIC); + } + M_Remove(self, false, false); + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + + DroneList_Remove(self); + gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + self->monsterinfo.currentmove = &stalker_move_death; + + if (self->activator && !self->activator->client) + self->activator->num_monsters_real--; +} + +void init_drone_stalker(edict_t *self) +{ + sound_pain = gi.soundindex("stalker/pain.wav"); + sound_die = gi.soundindex("stalker/death.wav"); + sound_sight = gi.soundindex("stalker/sight.wav"); + sound_punch_hit1 = gi.soundindex("stalker/melee1.wav"); + sound_punch_hit2 = gi.soundindex("stalker/melee2.wav"); + sound_idle = gi.soundindex("stalker/idle.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/stalker/tris.md2"); + gi.modelindex("models/monsters/stalker/gibs/bodya.md2"); + gi.modelindex("models/monsters/stalker/gibs/bodyb.md2"); + gi.modelindex("models/monsters/stalker/gibs/claw.md2"); + gi.modelindex("models/monsters/stalker/gibs/foot.md2"); + gi.modelindex("models/monsters/stalker/gibs/head.md2"); + gi.modelindex("models/monsters/stalker/gibs/leg.md2"); + + VectorSet(self->mins, -28, -28, -18); + VectorSet(self->maxs, 28, 28, 18); + + self->health = M_PARASITE_INITIAL_HEALTH + M_PARASITE_ADDON_HEALTH * self->monsterinfo.level; + self->max_health = self->health; + self->gib_health = -125; + self->mass = 250; + self->mtype = M_STALKER; + + //self->monsterinfo.power_armor_type = POWER_ARMOR_SCREEN; + //self->monsterinfo.power_armor_power = M_BERSERKER_INITIAL_ARMOR + M_BERSERKER_ADDON_ARMOR * self->monsterinfo.level; + //self->monsterinfo.max_armor = self->monsterinfo.power_armor_power; + self->monsterinfo.control_cost = M_BERSERKER_CONTROL_COST; + self->monsterinfo.cost = M_DEFAULT_COST; + + self->item = FindItemByClassname("ammo_cells"); + + self->pain = stalker_pain; + self->die = stalker_die; + self->monsterinfo.stand = stalker_stand; + self->monsterinfo.walk = stalker_walk; + self->monsterinfo.run = stalker_run; + self->monsterinfo.attack = stalker_attack; + self->monsterinfo.melee = stalker_melee; + self->monsterinfo.sight = stalker_sight; + self->monsterinfo.idle = stalker_idle; + self->monsterinfo.pain_chance = 0.3f; + + gi.linkentity(self); + + self->monsterinfo.currentmove = &stalker_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + self->nextthink = level.time + FRAMETIME; +} diff --git a/src/g_local.h b/src/g_local.h index 69b9e782..309c3f44 100644 --- a/src/g_local.h +++ b/src/g_local.h @@ -1500,6 +1500,14 @@ enum mtype_t { M_SHAMBLER = 25, M_SKELETON = 26, M_GOLEM = 27, + M_REDMUTANT = 28, + M_RUNNERTANK = 29, + M_GUNCMDR = 30, + M_DAEDALUS = 31, + M_GLADB = 32, + M_GLADC = 33, + M_STALKER = 34, + M_GEKK = 35, M_MINISENTRY = 100, M_SENTRY = 101, M_BFG_SENTRY = 102, @@ -1595,9 +1603,17 @@ enum dronespawn_t { DS_FLOATER = 13, DS_HOVER = 14, DS_SHAMBLER = 15, + DS_REDMUTANT = 16, + DS_RUNNERTANK = 17, + DS_GUNCMDR = 18, + DS_DAEDALUS = 19, DS_DECOY = 20, DS_SKELETON = 21, DS_GOLEM = 22, + DS_GLADB = 23, + DS_GLADC = 24, + DS_STALKER = 25, + DS_GEKK = 26, DS_COMMANDER = 30, DS_MAKRON = 31, DS_BARON_FIRE = 32, diff --git a/src/gamemodes/invasion.c b/src/gamemodes/invasion.c index f5dfd6c4..0567b31b 100644 --- a/src/gamemodes/invasion.c +++ b/src/gamemodes/invasion.c @@ -37,25 +37,32 @@ static constexpr int SET_EASY_MODE_MONSTERS[] = { DS_FLYER, DS_FLOATER, DS_HOVER, - // DS_SHAMBLER, + DS_SHAMBLER, + DS_REDMUTANT, + DS_RUNNERTANK, + DS_GUNCMDR, + DS_DAEDALUS, + DS_STALKER, + DS_GEKK, }; constexpr int SET_EASY_MODE_MONSTERS_COUNT = sizeof(SET_EASY_MODE_MONSTERS) / sizeof(int); // parasite, brain, medic, tank, mutant, gladiator, berserker, infantry, hover static constexpr int SET_HARD_MODE_MONSTERS[] = { - DS_PARASITE, DS_BRAIN, DS_MEDIC, DS_TANK, DS_MUTANT, DS_GLADIATOR, DS_BERSERK, DS_INFANTRY, DS_HOVER + DS_PARASITE, DS_BRAIN, DS_MEDIC, DS_TANK, DS_MUTANT, DS_GLADIATOR, DS_BERSERK, DS_INFANTRY, DS_HOVER, + DS_REDMUTANT, DS_RUNNERTANK, DS_GUNCMDR, DS_DAEDALUS, DS_GLADB, DS_GLADC, DS_STALKER, DS_GEKK }; constexpr int SET_HARD_MODE_MONSTERS_COUNT = sizeof(SET_HARD_MODE_MONSTERS) / sizeof(int); static constexpr int SET_FLYING_MONSTERS[] = { - DS_FLYER, DS_HOVER, DS_FLOATER + DS_FLYER, DS_HOVER, DS_FLOATER, DS_DAEDALUS }; constexpr int SET_FLYING_MONSTERS_COUNT = sizeof(SET_FLYING_MONSTERS) / sizeof(int); // parasite, brain, mutant, berserker static constexpr int SET_MELEE_MONSTERS[] = { - DS_PARASITE, DS_BRAIN, DS_MUTANT, DS_BERSERK, + DS_PARASITE, DS_BRAIN, DS_MUTANT, DS_BERSERK, DS_REDMUTANT, DS_STALKER, DS_GEKK, }; constexpr int SET_MELEE_MONSTERS_COUNT = sizeof(SET_MELEE_MONSTERS) / sizeof(int); @@ -67,7 +74,7 @@ constexpr int SET_RAGEQUIT_MONSTERS_COUNT = sizeof(SET_RAGEQUIT_MONSTERS) / size // tank, mutant, berserker, shambler! static constexpr int SET_TANKY_MONSTERS[] = { - DS_TANK, DS_MUTANT, DS_BERSERK, DS_SHAMBLER + DS_TANK, DS_MUTANT, DS_BERSERK, DS_SHAMBLER, DS_REDMUTANT, DS_RUNNERTANK, DS_GUNCMDR, DS_GLADB, DS_GLADC, DS_GEKK }; constexpr int SET_TANKY_MONSTERS_COUNT = sizeof(SET_TANKY_MONSTERS) / sizeof(int); diff --git a/src/quake2/g_layout.c b/src/quake2/g_layout.c index b5952568..e17e83d6 100644 --- a/src/quake2/g_layout.c +++ b/src/quake2/g_layout.c @@ -320,6 +320,14 @@ sidebar_entry_t layout_add_entity_info(sidebar_t* sidebar, edict_t* ent) case M_FORCEWALL: case M_BARON_FIRE: case M_SHAMBLER: + case M_REDMUTANT: + case M_RUNNERTANK: + case M_GUNCMDR: + case M_DAEDALUS: + case M_GLADB: + case M_GLADC: + case M_STALKER: + case M_GEKK: case M_SKELETON: case M_GOLEM: name = lva("%s", V_GetMonsterName(ent)); @@ -769,4 +777,4 @@ void layout_send(edict_t* ent) gi.WriteString(ent->client->layout.layout); gi.unicast(ent, false); #endif -} \ No newline at end of file +} diff --git a/src/quake2/monsterframes/m_guncmdr.h b/src/quake2/monsterframes/m_guncmdr.h new file mode 100644 index 00000000..e1a3e9fc --- /dev/null +++ b/src/quake2/monsterframes/m_guncmdr.h @@ -0,0 +1,809 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// E:\G Drive\md2f\quake2\baseq2\models/monsters/gunner + +// This file generated by qdata - Do NOT Modify + +enum { + FRAME_stand01, + FRAME_stand02, + FRAME_stand03, + FRAME_stand04, + FRAME_stand05, + FRAME_stand06, + FRAME_stand07, + FRAME_stand08, + FRAME_stand09, + FRAME_stand10, + FRAME_stand11, + FRAME_stand12, + FRAME_stand13, + FRAME_stand14, + FRAME_stand15, + FRAME_stand16, + FRAME_stand17, + FRAME_stand18, + FRAME_stand19, + FRAME_stand20, + FRAME_stand21, + FRAME_stand22, + FRAME_stand23, + FRAME_stand24, + FRAME_stand25, + FRAME_stand26, + FRAME_stand27, + FRAME_stand28, + FRAME_stand29, + FRAME_stand30, + FRAME_stand31, + FRAME_stand32, + FRAME_stand33, + FRAME_stand34, + FRAME_stand35, + FRAME_stand36, + FRAME_stand37, + FRAME_stand38, + FRAME_stand39, + FRAME_stand40, + FRAME_stand41, + FRAME_stand42, + FRAME_stand43, + FRAME_stand44, + FRAME_stand45, + FRAME_stand46, + FRAME_stand47, + FRAME_stand48, + FRAME_stand49, + FRAME_stand50, + FRAME_stand51, + FRAME_stand52, + FRAME_stand53, + FRAME_stand54, + FRAME_stand55, + FRAME_stand56, + FRAME_stand57, + FRAME_stand58, + FRAME_stand59, + FRAME_stand60, + FRAME_stand61, + FRAME_stand62, + FRAME_stand63, + FRAME_stand64, + FRAME_stand65, + FRAME_stand66, + FRAME_stand67, + FRAME_stand68, + FRAME_stand69, + FRAME_stand70, + FRAME_walk01, + FRAME_walk02, + FRAME_walk03, + FRAME_walk04, + FRAME_walk05, + FRAME_walk06, + FRAME_walk07, + FRAME_walk08, + FRAME_walk09, + FRAME_walk10, + FRAME_walk11, + FRAME_walk12, + FRAME_walk13, + FRAME_walk14, + FRAME_walk15, + FRAME_walk16, + FRAME_walk17, + FRAME_walk18, + FRAME_walk19, + FRAME_walk20, + FRAME_walk21, + FRAME_walk22, + FRAME_walk23, + FRAME_walk24, + FRAME_run01, + FRAME_run02, + FRAME_run03, + FRAME_run04, + FRAME_run05, + FRAME_run06, + FRAME_run07, + FRAME_run08, + FRAME_runs01, + FRAME_runs02, + FRAME_runs03, + FRAME_runs04, + FRAME_runs05, + FRAME_runs06, + FRAME_attak101, + FRAME_attak102, + FRAME_attak103, + FRAME_attak104, + FRAME_attak105, + FRAME_attak106, + FRAME_attak107, + FRAME_attak108, + FRAME_attak109, + FRAME_attak110, + FRAME_attak111, + FRAME_attak112, + FRAME_attak113, + FRAME_attak114, + FRAME_attak115, + FRAME_attak116, + FRAME_attak117, + FRAME_attak118, + FRAME_attak119, + FRAME_attak120, + FRAME_attak121, + FRAME_attak201, + FRAME_attak202, + FRAME_attak203, + FRAME_attak204, + FRAME_attak205, + FRAME_attak206, + FRAME_attak207, + FRAME_attak208, + FRAME_attak209, + FRAME_attak210, + FRAME_attak211, + FRAME_attak212, + FRAME_attak213, + FRAME_attak214, + FRAME_attak215, + FRAME_attak216, + FRAME_attak217, + FRAME_attak218, + FRAME_attak219, + FRAME_attak220, + FRAME_attak221, + FRAME_attak222, + FRAME_attak223, + FRAME_attak224, + FRAME_attak225, + FRAME_attak226, + FRAME_attak227, + FRAME_attak228, + FRAME_attak229, + FRAME_attak230, + FRAME_pain101, + FRAME_pain102, + FRAME_pain103, + FRAME_pain104, + FRAME_pain105, + FRAME_pain106, + FRAME_pain107, + FRAME_pain108, + FRAME_pain109, + FRAME_pain110, + FRAME_pain111, + FRAME_pain112, + FRAME_pain113, + FRAME_pain114, + FRAME_pain115, + FRAME_pain116, + FRAME_pain117, + FRAME_pain118, + FRAME_pain201, + FRAME_pain202, + FRAME_pain203, + FRAME_pain204, + FRAME_pain205, + FRAME_pain206, + FRAME_pain207, + FRAME_pain208, + FRAME_pain301, + FRAME_pain302, + FRAME_pain303, + FRAME_pain304, + FRAME_pain305, + FRAME_death01, + FRAME_death02, + FRAME_death03, + FRAME_death04, + FRAME_death05, + FRAME_death06, + FRAME_death07, + FRAME_death08, + FRAME_death09, + FRAME_death10, + FRAME_death11, + FRAME_duck01, + FRAME_duck02, + FRAME_duck03, + FRAME_duck04, + FRAME_duck05, + FRAME_duck06, + FRAME_duck07, + FRAME_duck08, + FRAME_jump01, + FRAME_jump02, + FRAME_jump03, + FRAME_jump04, + FRAME_jump05, + FRAME_jump06, + FRAME_jump07, + FRAME_jump08, + FRAME_jump09, + FRAME_jump10, + FRAME_shield01, + FRAME_shield02, + FRAME_shield03, + FRAME_shield04, + FRAME_shield05, + FRAME_shield06, + FRAME_attak301, + FRAME_attak302, + FRAME_attak303, + FRAME_attak304, + FRAME_attak305, + FRAME_attak306, + FRAME_attak307, + FRAME_attak308, + FRAME_attak309, + FRAME_attak310, + FRAME_attak311, + FRAME_attak312, + FRAME_attak313, + FRAME_attak314, + FRAME_attak315, + FRAME_attak316, + FRAME_attak317, + FRAME_attak318, + FRAME_attak319, + FRAME_attak320, + FRAME_attak321, + FRAME_attak322, + FRAME_attak323, + FRAME_attak324, + FRAME_c_stand101, + FRAME_c_stand102, + FRAME_c_stand103, + FRAME_c_stand104, + FRAME_c_stand105, + FRAME_c_stand106, + FRAME_c_stand107, + FRAME_c_stand108, + FRAME_c_stand109, + FRAME_c_stand110, + FRAME_c_stand111, + FRAME_c_stand112, + FRAME_c_stand113, + FRAME_c_stand114, + FRAME_c_stand115, + FRAME_c_stand116, + FRAME_c_stand117, + FRAME_c_stand118, + FRAME_c_stand119, + FRAME_c_stand120, + FRAME_c_stand121, + FRAME_c_stand122, + FRAME_c_stand123, + FRAME_c_stand124, + FRAME_c_stand125, + FRAME_c_stand126, + FRAME_c_stand127, + FRAME_c_stand128, + FRAME_c_stand129, + FRAME_c_stand130, + FRAME_c_stand131, + FRAME_c_stand132, + FRAME_c_stand133, + FRAME_c_stand134, + FRAME_c_stand135, + FRAME_c_stand136, + FRAME_c_stand137, + FRAME_c_stand138, + FRAME_c_stand139, + FRAME_c_stand140, + FRAME_c_stand201, + FRAME_c_stand202, + FRAME_c_stand203, + FRAME_c_stand204, + FRAME_c_stand205, + FRAME_c_stand206, + FRAME_c_stand207, + FRAME_c_stand208, + FRAME_c_stand209, + FRAME_c_stand210, + FRAME_c_stand211, + FRAME_c_stand212, + FRAME_c_stand213, + FRAME_c_stand214, + FRAME_c_stand215, + FRAME_c_stand216, + FRAME_c_stand217, + FRAME_c_stand218, + FRAME_c_stand219, + FRAME_c_stand220, + FRAME_c_stand221, + FRAME_c_stand222, + FRAME_c_stand223, + FRAME_c_stand224, + FRAME_c_stand225, + FRAME_c_stand226, + FRAME_c_stand227, + FRAME_c_stand228, + FRAME_c_stand229, + FRAME_c_stand230, + FRAME_c_stand231, + FRAME_c_stand232, + FRAME_c_stand233, + FRAME_c_stand234, + FRAME_c_stand235, + FRAME_c_stand236, + FRAME_c_stand237, + FRAME_c_stand238, + FRAME_c_stand239, + FRAME_c_stand240, + FRAME_c_stand241, + FRAME_c_stand242, + FRAME_c_stand243, + FRAME_c_stand244, + FRAME_c_stand245, + FRAME_c_stand246, + FRAME_c_stand247, + FRAME_c_stand248, + FRAME_c_stand249, + FRAME_c_stand250, + FRAME_c_stand251, + FRAME_c_stand252, + FRAME_c_stand253, + FRAME_c_stand254, + FRAME_c_attack101, + FRAME_c_attack102, + FRAME_c_attack103, + FRAME_c_attack104, + FRAME_c_attack105, + FRAME_c_attack106, + FRAME_c_attack107, + FRAME_c_attack108, + FRAME_c_attack109, + FRAME_c_attack110, + FRAME_c_attack111, + FRAME_c_attack112, + FRAME_c_attack113, + FRAME_c_attack114, + FRAME_c_attack115, + FRAME_c_attack116, + FRAME_c_attack117, + FRAME_c_attack118, + FRAME_c_attack119, + FRAME_c_attack120, + FRAME_c_attack121, + FRAME_c_attack122, + FRAME_c_attack123, + FRAME_c_attack124, + FRAME_c_jump01, + FRAME_c_jump02, + FRAME_c_jump03, + FRAME_c_jump04, + FRAME_c_jump05, + FRAME_c_jump06, + FRAME_c_jump07, + FRAME_c_jump08, + FRAME_c_jump09, + FRAME_c_jump10, + FRAME_c_attack201, + FRAME_c_attack202, + FRAME_c_attack203, + FRAME_c_attack204, + FRAME_c_attack205, + FRAME_c_attack206, + FRAME_c_attack207, + FRAME_c_attack208, + FRAME_c_attack209, + FRAME_c_attack210, + FRAME_c_attack211, + FRAME_c_attack212, + FRAME_c_attack213, + FRAME_c_attack214, + FRAME_c_attack215, + FRAME_c_attack216, + FRAME_c_attack217, + FRAME_c_attack218, + FRAME_c_attack219, + FRAME_c_attack220, + FRAME_c_attack221, + FRAME_c_attack301, + FRAME_c_attack302, + FRAME_c_attack303, + FRAME_c_attack304, + FRAME_c_attack305, + FRAME_c_attack306, + FRAME_c_attack307, + FRAME_c_attack308, + FRAME_c_attack309, + FRAME_c_attack310, + FRAME_c_attack311, + FRAME_c_attack312, + FRAME_c_attack313, + FRAME_c_attack314, + FRAME_c_attack315, + FRAME_c_attack316, + FRAME_c_attack317, + FRAME_c_attack318, + FRAME_c_attack319, + FRAME_c_attack320, + FRAME_c_attack321, + FRAME_c_attack401, + FRAME_c_attack402, + FRAME_c_attack403, + FRAME_c_attack404, + FRAME_c_attack405, + FRAME_c_attack501, + FRAME_c_attack502, + FRAME_c_attack503, + FRAME_c_attack504, + FRAME_c_attack505, + FRAME_c_attack601, + FRAME_c_attack602, + FRAME_c_attack603, + FRAME_c_attack604, + FRAME_c_attack605, + FRAME_c_attack701, + FRAME_c_attack702, + FRAME_c_attack703, + FRAME_c_attack704, + FRAME_c_attack705, + FRAME_c_pain101, + FRAME_c_pain102, + FRAME_c_pain103, + FRAME_c_pain104, + FRAME_c_pain201, + FRAME_c_pain202, + FRAME_c_pain203, + FRAME_c_pain204, + FRAME_c_pain301, + FRAME_c_pain302, + FRAME_c_pain303, + FRAME_c_pain304, + FRAME_c_pain401, + FRAME_c_pain402, + FRAME_c_pain403, + FRAME_c_pain404, + FRAME_c_pain405, + FRAME_c_pain406, + FRAME_c_pain407, + FRAME_c_pain408, + FRAME_c_pain409, + FRAME_c_pain410, + FRAME_c_pain411, + FRAME_c_pain412, + FRAME_c_pain413, + FRAME_c_pain414, + FRAME_c_pain415, + FRAME_c_pain501, + FRAME_c_pain502, + FRAME_c_pain503, + FRAME_c_pain504, + FRAME_c_pain505, + FRAME_c_pain506, + FRAME_c_pain507, + FRAME_c_pain508, + FRAME_c_pain509, + FRAME_c_pain510, + FRAME_c_pain511, + FRAME_c_pain512, + FRAME_c_pain513, + FRAME_c_pain514, + FRAME_c_pain515, + FRAME_c_pain516, + FRAME_c_pain517, + FRAME_c_pain518, + FRAME_c_pain519, + FRAME_c_pain520, + FRAME_c_pain521, + FRAME_c_pain522, + FRAME_c_pain523, + FRAME_c_pain524, + FRAME_c_death101, + FRAME_c_death102, + FRAME_c_death103, + FRAME_c_death104, + FRAME_c_death105, + FRAME_c_death106, + FRAME_c_death107, + FRAME_c_death108, + FRAME_c_death109, + FRAME_c_death110, + FRAME_c_death111, + FRAME_c_death112, + FRAME_c_death113, + FRAME_c_death114, + FRAME_c_death115, + FRAME_c_death116, + FRAME_c_death117, + FRAME_c_death118, + FRAME_c_death201, + FRAME_c_death202, + FRAME_c_death203, + FRAME_c_death204, + FRAME_c_death301, + FRAME_c_death302, + FRAME_c_death303, + FRAME_c_death304, + FRAME_c_death305, + FRAME_c_death306, + FRAME_c_death307, + FRAME_c_death308, + FRAME_c_death309, + FRAME_c_death310, + FRAME_c_death311, + FRAME_c_death312, + FRAME_c_death313, + FRAME_c_death314, + FRAME_c_death315, + FRAME_c_death316, + FRAME_c_death317, + FRAME_c_death318, + FRAME_c_death319, + FRAME_c_death320, + FRAME_c_death321, + FRAME_c_death401, + FRAME_c_death402, + FRAME_c_death403, + FRAME_c_death404, + FRAME_c_death405, + FRAME_c_death406, + FRAME_c_death407, + FRAME_c_death408, + FRAME_c_death409, + FRAME_c_death410, + FRAME_c_death411, + FRAME_c_death412, + FRAME_c_death413, + FRAME_c_death414, + FRAME_c_death415, + FRAME_c_death416, + FRAME_c_death417, + FRAME_c_death418, + FRAME_c_death419, + FRAME_c_death420, + FRAME_c_death421, + FRAME_c_death422, + FRAME_c_death423, + FRAME_c_death424, + FRAME_c_death425, + FRAME_c_death426, + FRAME_c_death427, + FRAME_c_death428, + FRAME_c_death429, + FRAME_c_death430, + FRAME_c_death431, + FRAME_c_death432, + FRAME_c_death433, + FRAME_c_death434, + FRAME_c_death435, + FRAME_c_death436, + FRAME_c_death501, + FRAME_c_death502, + FRAME_c_death503, + FRAME_c_death504, + FRAME_c_death505, + FRAME_c_death506, + FRAME_c_death507, + FRAME_c_death508, + FRAME_c_death509, + FRAME_c_death510, + FRAME_c_death511, + FRAME_c_death512, + FRAME_c_death513, + FRAME_c_death514, + FRAME_c_death515, + FRAME_c_death516, + FRAME_c_death517, + FRAME_c_death518, + FRAME_c_death519, + FRAME_c_death520, + FRAME_c_death521, + FRAME_c_death522, + FRAME_c_death523, + FRAME_c_death524, + FRAME_c_death525, + FRAME_c_death526, + FRAME_c_death527, + FRAME_c_death528, + FRAME_c_run101, + FRAME_c_run102, + FRAME_c_run103, + FRAME_c_run104, + FRAME_c_run105, + FRAME_c_run106, + FRAME_c_run201, + FRAME_c_run202, + FRAME_c_run203, + FRAME_c_run204, + FRAME_c_run205, + FRAME_c_run206, + FRAME_c_run301, + FRAME_c_run302, + FRAME_c_run303, + FRAME_c_run304, + FRAME_c_run305, + FRAME_c_run306, + FRAME_c_walk101, + FRAME_c_walk102, + FRAME_c_walk103, + FRAME_c_walk104, + FRAME_c_walk105, + FRAME_c_walk106, + FRAME_c_walk107, + FRAME_c_walk108, + FRAME_c_walk109, + FRAME_c_walk110, + FRAME_c_walk111, + FRAME_c_walk112, + FRAME_c_walk113, + FRAME_c_walk114, + FRAME_c_walk115, + FRAME_c_walk116, + FRAME_c_walk117, + FRAME_c_walk118, + FRAME_c_walk119, + FRAME_c_walk120, + FRAME_c_walk121, + FRAME_c_walk122, + FRAME_c_walk123, + FRAME_c_walk124, + FRAME_c_pain601, + FRAME_c_pain602, + FRAME_c_pain603, + FRAME_c_pain604, + FRAME_c_pain605, + FRAME_c_pain606, + FRAME_c_pain607, + FRAME_c_pain608, + FRAME_c_pain609, + FRAME_c_pain610, + FRAME_c_pain611, + FRAME_c_pain612, + FRAME_c_pain613, + FRAME_c_pain614, + FRAME_c_pain615, + FRAME_c_pain616, + FRAME_c_pain617, + FRAME_c_pain618, + FRAME_c_pain619, + FRAME_c_pain620, + FRAME_c_pain621, + FRAME_c_pain622, + FRAME_c_pain623, + FRAME_c_pain624, + FRAME_c_pain625, + FRAME_c_pain626, + FRAME_c_pain627, + FRAME_c_pain628, + FRAME_c_pain629, + FRAME_c_pain630, + FRAME_c_pain631, + FRAME_c_pain632, + FRAME_c_death601, + FRAME_c_death602, + FRAME_c_death603, + FRAME_c_death604, + FRAME_c_death605, + FRAME_c_death606, + FRAME_c_death607, + FRAME_c_death608, + FRAME_c_death609, + FRAME_c_death610, + FRAME_c_death611, + FRAME_c_death612, + FRAME_c_death613, + FRAME_c_death614, + FRAME_c_death701, + FRAME_c_death702, + FRAME_c_death703, + FRAME_c_death704, + FRAME_c_death705, + FRAME_c_death706, + FRAME_c_death707, + FRAME_c_death708, + FRAME_c_death709, + FRAME_c_death710, + FRAME_c_death711, + FRAME_c_death712, + FRAME_c_death713, + FRAME_c_death714, + FRAME_c_death715, + FRAME_c_death716, + FRAME_c_death717, + FRAME_c_death718, + FRAME_c_death719, + FRAME_c_death720, + FRAME_c_death721, + FRAME_c_death722, + FRAME_c_death723, + FRAME_c_death724, + FRAME_c_death725, + FRAME_c_death726, + FRAME_c_death727, + FRAME_c_death728, + FRAME_c_death729, + FRAME_c_death730, + FRAME_c_pain701, + FRAME_c_pain702, + FRAME_c_pain703, + FRAME_c_pain704, + FRAME_c_pain705, + FRAME_c_pain706, + FRAME_c_pain707, + FRAME_c_pain708, + FRAME_c_pain709, + FRAME_c_pain710, + FRAME_c_pain711, + FRAME_c_pain712, + FRAME_c_pain713, + FRAME_c_pain714, + FRAME_c_attack801, + FRAME_c_attack802, + FRAME_c_attack803, + FRAME_c_attack804, + FRAME_c_attack805, + FRAME_c_attack806, + FRAME_c_attack807, + FRAME_c_attack808, + FRAME_c_attack809, + FRAME_c_attack901, + FRAME_c_attack902, + FRAME_c_attack903, + FRAME_c_attack904, + FRAME_c_attack905, + FRAME_c_attack906, + FRAME_c_attack907, + FRAME_c_attack908, + FRAME_c_attack909, + FRAME_c_attack910, + FRAME_c_attack911, + FRAME_c_attack912, + FRAME_c_attack913, + FRAME_c_attack914, + FRAME_c_attack915, + FRAME_c_attack916, + FRAME_c_attack917, + FRAME_c_attack918, + FRAME_c_attack919, + FRAME_c_duck01, + FRAME_c_duck02, + FRAME_c_duckstep01, + FRAME_c_duckstep02, + FRAME_c_duckstep03, + FRAME_c_duckstep04, + FRAME_c_duckstep05, + FRAME_c_duckstep06, + FRAME_c_duckpain01, + FRAME_c_duckpain02, + FRAME_c_duckpain03, + FRAME_c_duckpain04, + FRAME_c_duckpain05, + FRAME_c_duckdeath01, + FRAME_c_duckdeath02, + FRAME_c_duckdeath03, + FRAME_c_duckdeath04, + FRAME_c_duckdeath05, + FRAME_c_duckdeath06, + FRAME_c_duckdeath07, + FRAME_c_duckdeath08, + FRAME_c_duckdeath09, + FRAME_c_duckdeath10, + FRAME_c_duckdeath11, + FRAME_c_duckdeath12, + FRAME_c_duckdeath13, + FRAME_c_duckdeath14, + FRAME_c_duckdeath15, + FRAME_c_duckdeath16, + FRAME_c_duckdeath17, + FRAME_c_duckdeath18, + FRAME_c_duckdeath19, + FRAME_c_duckdeath20, + FRAME_c_duckdeath21, + FRAME_c_duckdeath22, + FRAME_c_duckdeath23, + FRAME_c_duckdeath24, + FRAME_c_duckdeath25, + FRAME_c_duckdeath26, + FRAME_c_duckdeath27, + FRAME_c_duckdeath28, + FRAME_c_duckdeath29 +}; + +constexpr float MODEL_SCALE = 1.150000f; diff --git a/src/quake2/monsterframes/m_redmutant.h b/src/quake2/monsterframes/m_redmutant.h new file mode 100644 index 00000000..b8089e95 --- /dev/null +++ b/src/quake2/monsterframes/m_redmutant.h @@ -0,0 +1,156 @@ +#pragma once +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// RedMutant + +// This file generated by ModelGen - Do NOT Modify + +enum +{ + FRAME_attack101, + FRAME_attack102, + FRAME_attack103, + FRAME_attack104, + FRAME_attack105, + FRAME_attack106, + FRAME_attack107, + FRAME_attack108, + FRAME_attack109, + FRAME_attack110, + FRAME_attack111, + FRAME_attack112, + FRAME_attack113, + FRAME_attack114, + FRAME_attack115, + FRAME_attack116, + FRAME_attack117, + FRAME_attack118, + FRAME_attack119, + FRAME_attack120, + FRAME_attack121, + FRAME_attack122, + FRAME_attack123, + FRAME_attack124, + FRAME_attack125, + FRAME_attack126, + FRAME_death101, + FRAME_death102, + FRAME_death103, + FRAME_death104, + FRAME_death105, + FRAME_death106, + FRAME_death107, + FRAME_death108, + FRAME_death109, + FRAME_death110, + FRAME_death111, + FRAME_death112, + FRAME_death113, + FRAME_death114, + FRAME_death115, + FRAME_death116, + FRAME_death117, + FRAME_death118, + FRAME_death119, + FRAME_death120, + FRAME_death201, + FRAME_death202, + FRAME_death203, + FRAME_death204, + FRAME_death205, + FRAME_death206, + FRAME_death207, + FRAME_pain101, + FRAME_pain102, + FRAME_pain103, + FRAME_pain104, + FRAME_pain105, + FRAME_pain201, + FRAME_pain202, + FRAME_pain203, + FRAME_pain204, + FRAME_pain205, + FRAME_pain206, + FRAME_pain301, + FRAME_pain302, + FRAME_pain303, + FRAME_pain304, + FRAME_pain305, + FRAME_pain306, + FRAME_pain307, + FRAME_pain308, + FRAME_pain309, + FRAME_pain310, + FRAME_pain311, + FRAME_run03, + FRAME_run04, + FRAME_run05, + FRAME_run06, + FRAME_run07, + FRAME_run08, + FRAME_stand101, + FRAME_stand102, + FRAME_stand103, + FRAME_stand104, + FRAME_stand105, + FRAME_stand106, + FRAME_stand107, + FRAME_stand108, + FRAME_stand109, + FRAME_stand110, + FRAME_stand111, + FRAME_stand112, + FRAME_stand201, + FRAME_stand202, + FRAME_stand203, + FRAME_stand204, + FRAME_stand205, + FRAME_stand206, + FRAME_stand207, + FRAME_stand208, + FRAME_stand209, + FRAME_stand210, + FRAME_stand211, + FRAME_stand212, + FRAME_stand213, + FRAME_stand214, + FRAME_stand215, + FRAME_stand216, + FRAME_stand217, + FRAME_stand218, + FRAME_stand219, + FRAME_stand220, + FRAME_stand221, + FRAME_stand222, + FRAME_stand223, + FRAME_stand224, + FRAME_stand225, + FRAME_stand226, + FRAME_stand227, + FRAME_stand228, + FRAME_walk01, + FRAME_walk02, + FRAME_walk03, + FRAME_walk04, + FRAME_walk05, + FRAME_walk06, + FRAME_walk07, + FRAME_walk08, + FRAME_walk09, + FRAME_walk10, + FRAME_walk11, + FRAME_walk12, + FRAME_walk13, + FRAME_walk14, + FRAME_walk15, + FRAME_walk16, + FRAME_walk17, + FRAME_walk18, + FRAME_walk19, + FRAME_walk20, + FRAME_walk21, + FRAME_walk22, + FRAME_walk23, +}; + +constexpr float MODEL_SCALE = 1.000000f; diff --git a/src/quake2/monsterframes/m_rogue_stalker.h b/src/quake2/monsterframes/m_rogue_stalker.h new file mode 100644 index 00000000..160851a4 --- /dev/null +++ b/src/quake2/monsterframes/m_rogue_stalker.h @@ -0,0 +1,104 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// /expanse/quake2/xpack/models/monsters/stalker + +// This file generated by qdata - Do NOT Modify + +enum +{ + FRAME_idle01, + FRAME_idle02, + FRAME_idle03, + FRAME_idle04, + FRAME_idle05, + FRAME_idle06, + FRAME_idle07, + FRAME_idle08, + FRAME_idle09, + FRAME_idle10, + FRAME_idle11, + FRAME_idle12, + FRAME_idle13, + FRAME_idle14, + FRAME_idle15, + FRAME_idle16, + FRAME_idle17, + FRAME_idle18, + FRAME_idle19, + FRAME_idle20, + FRAME_idle21, + FRAME_idle201, + FRAME_idle202, + FRAME_idle203, + FRAME_idle204, + FRAME_idle205, + FRAME_idle206, + FRAME_idle207, + FRAME_idle208, + FRAME_idle209, + FRAME_idle210, + FRAME_idle211, + FRAME_idle212, + FRAME_idle213, + FRAME_walk01, + FRAME_walk02, + FRAME_walk03, + FRAME_walk04, + FRAME_walk05, + FRAME_walk06, + FRAME_walk07, + FRAME_walk08, + FRAME_jump01, + FRAME_jump02, + FRAME_jump03, + FRAME_jump04, + FRAME_jump05, + FRAME_jump06, + FRAME_jump07, + FRAME_run01, + FRAME_run02, + FRAME_run03, + FRAME_run04, + FRAME_attack01, + FRAME_attack02, + FRAME_attack03, + FRAME_attack04, + FRAME_attack05, + FRAME_attack06, + FRAME_attack07, + FRAME_attack08, + FRAME_attack11, + FRAME_attack12, + FRAME_attack13, + FRAME_attack14, + FRAME_attack15, + FRAME_pain01, + FRAME_pain02, + FRAME_pain03, + FRAME_pain04, + FRAME_death01, + FRAME_death02, + FRAME_death03, + FRAME_death04, + FRAME_death05, + FRAME_death06, + FRAME_death07, + FRAME_death08, + FRAME_death09, + FRAME_twitch01, + FRAME_twitch02, + FRAME_twitch03, + FRAME_twitch04, + FRAME_twitch05, + FRAME_twitch06, + FRAME_twitch07, + FRAME_twitch08, + FRAME_twitch09, + FRAME_twitch10, + FRAME_reactive01, + FRAME_reactive02, + FRAME_reactive03, + FRAME_reactive04 +}; + +constexpr float MODEL_SCALE = 1.000000f; diff --git a/src/quake2/monsterframes/m_runnertank.h b/src/quake2/monsterframes/m_runnertank.h new file mode 100644 index 00000000..65fe22bd --- /dev/null +++ b/src/quake2/monsterframes/m_runnertank.h @@ -0,0 +1,266 @@ +#pragma once +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/parasite + +// This file generated by ModelGen - Do NOT Modify + +enum +{ + FRAME_stand01, + FRAME_stand02, + FRAME_stand03, + FRAME_stand04, + FRAME_stand05, + FRAME_stand06, + FRAME_stand07, + FRAME_stand08, + FRAME_stand09, + FRAME_stand10, + FRAME_stand11, + FRAME_stand12, + FRAME_stand13, + FRAME_stand14, + FRAME_stand15, + FRAME_stand16, + FRAME_stand17, + FRAME_stand18, + FRAME_stand19, + FRAME_stand20, + FRAME_stand21, + FRAME_stand22, + FRAME_stand23, + FRAME_stand24, + FRAME_stand25, + FRAME_stand26, + FRAME_stand27, + FRAME_stand28, + FRAME_stand29, + FRAME_stand30, + FRAME_walk01, + FRAME_walk02, + FRAME_walk03, + FRAME_walk04, + FRAME_walk05, + FRAME_walk06, + FRAME_walk07, + FRAME_walk08, + FRAME_walk09, + FRAME_walk10, + FRAME_walk11, + FRAME_walk12, + FRAME_walk13, + FRAME_walk14, + FRAME_walk15, + FRAME_walk16, + FRAME_walk17, + FRAME_walk18, + FRAME_walk19, + FRAME_walk20, + FRAME_walk21, + FRAME_walk22, + FRAME_walk23, + FRAME_walk24, + FRAME_walk25, + FRAME_walk26, + FRAME_walk27, + FRAME_walk28, + FRAME_walk29, + FRAME_walk30, + FRAME_walk31, + FRAME_walk32, + FRAME_walk33, + FRAME_walk34, + FRAME_walk35, + FRAME_walk36, + FRAME_walk37, + FRAME_walk38, + FRAME_run01, + FRAME_run02, + FRAME_run03, + FRAME_run04, + FRAME_run05, + FRAME_run06, + FRAME_run07, + FRAME_run08, + FRAME_run09, + FRAME_run10, + FRAME_attak101, + FRAME_attak102, + FRAME_attak103, + FRAME_attak104, + FRAME_attak105, + FRAME_attak106, + FRAME_attak107, + FRAME_attak108, + FRAME_attak109, + FRAME_attak110, + FRAME_attak111, + FRAME_attak112, + FRAME_attak113, + FRAME_attak114, + FRAME_attak115, + FRAME_attak116, + FRAME_attak117, + FRAME_attak118, + FRAME_attak119, + FRAME_attak120, + FRAME_attak121, + FRAME_attak122, + FRAME_attak201, + FRAME_attak202, + FRAME_attak203, + FRAME_attak204, + FRAME_attak205, + FRAME_attak206, + FRAME_attak207, + FRAME_attak208, + FRAME_attak209, + FRAME_attak210, + FRAME_attak211, + FRAME_attak212, + FRAME_attak213, + FRAME_attak214, + FRAME_attak215, + FRAME_attak216, + FRAME_attak217, + FRAME_attak218, + FRAME_attak219, + FRAME_attak220, + FRAME_attak221, + FRAME_attak222, + FRAME_attak223, + FRAME_attak224, + FRAME_attak225, + FRAME_attak226, + FRAME_attak227, + FRAME_attak228, + FRAME_attak229, + FRAME_attak230, + FRAME_attak231, + FRAME_attak232, + FRAME_attak233, + FRAME_attak234, + FRAME_attak235, + FRAME_attak236, + FRAME_attak237, + FRAME_attak238, + FRAME_attak301, + FRAME_attak302, + FRAME_attak303, + FRAME_attak304, + FRAME_attak305, + FRAME_attak306, + FRAME_attak307, + FRAME_attak308, + FRAME_attak309, + FRAME_attak310, + FRAME_attak311, + FRAME_attak312, + FRAME_attak313, + FRAME_attak314, + FRAME_attak315, + FRAME_attak316, + FRAME_attak317, + FRAME_attak318, + FRAME_attak319, + FRAME_attak320, + FRAME_attak321, + FRAME_attak322, + FRAME_attak323, + FRAME_attak324, + FRAME_attak325, + FRAME_attak326, + FRAME_attak327, + FRAME_attak328, + FRAME_attak329, + FRAME_attak330, + FRAME_attak331, + FRAME_attak332, + FRAME_attak333, + FRAME_attak334, + FRAME_attak335, + FRAME_attak401, + FRAME_attak402, + FRAME_attak403, + FRAME_attak404, + FRAME_attak405, + FRAME_attak406, + FRAME_attak407, + FRAME_attak408, + FRAME_attak409, + FRAME_attak410, + FRAME_attak411, + FRAME_attak412, + FRAME_attak413, + FRAME_attak414, + FRAME_attak415, + FRAME_attak416, + FRAME_attak417, + FRAME_attak418, + FRAME_attak419, + FRAME_attak420, + FRAME_attak421, + FRAME_attak422, + FRAME_attak423, + FRAME_attak424, + FRAME_attak425, + FRAME_attak426, + FRAME_attak427, + FRAME_attak428, + FRAME_attak429, + FRAME_pain201, + FRAME_pain202, + FRAME_pain203, + FRAME_pain204, + FRAME_pain301, + FRAME_pain302, + FRAME_pain303, + FRAME_pain304, + FRAME_pain305, + FRAME_pain306, + FRAME_pain307, + FRAME_pain308, + FRAME_pain309, + FRAME_pain310, + FRAME_pain311, + FRAME_pain312, + FRAME_pain313, + FRAME_pain314, + FRAME_pain315, + FRAME_pain316, + FRAME_death01, + FRAME_death02, + FRAME_death03, + FRAME_death04, + FRAME_death05, + FRAME_death06, + FRAME_death07, + FRAME_death08, + FRAME_death09, + FRAME_death10, + FRAME_death11, + FRAME_death12, + FRAME_death13, + FRAME_death14, + FRAME_death15, + FRAME_death16, + FRAME_death17, + FRAME_death18, + FRAME_death19, + FRAME_death20, + FRAME_death21, + FRAME_death22, + FRAME_death23, + FRAME_death24, + FRAME_death25, + FRAME_death26, + FRAME_death27, + FRAME_death28, + FRAME_death29, + FRAME_death30, + FRAME_death31, + FRAME_death32, +}; + +constexpr float MODEL_SCALE = 1.000000f; diff --git a/src/quake2/monsterframes/m_xatrix_gekk.h b/src/quake2/monsterframes/m_xatrix_gekk.h new file mode 100644 index 00000000..db7ebb84 --- /dev/null +++ b/src/quake2/monsterframes/m_xatrix_gekk.h @@ -0,0 +1,361 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// ./gekk + +// This file generated by ModelGen - Do NOT Modify + +enum +{ + FRAME_stand_01, + FRAME_stand_02, + FRAME_stand_03, + FRAME_stand_04, + FRAME_stand_05, + FRAME_stand_06, + FRAME_stand_07, + FRAME_stand_08, + FRAME_stand_09, + FRAME_stand_10, + FRAME_stand_11, + FRAME_stand_12, + FRAME_stand_13, + FRAME_stand_14, + FRAME_stand_15, + FRAME_stand_16, + FRAME_stand_17, + FRAME_stand_18, + FRAME_stand_19, + FRAME_stand_20, + FRAME_stand_21, + FRAME_stand_22, + FRAME_stand_23, + FRAME_stand_24, + FRAME_stand_25, + FRAME_stand_26, + FRAME_stand_27, + FRAME_stand_28, + FRAME_stand_29, + FRAME_stand_30, + FRAME_stand_31, + FRAME_stand_32, + FRAME_stand_33, + FRAME_stand_34, + FRAME_stand_35, + FRAME_stand_36, + FRAME_stand_37, + FRAME_stand_38, + FRAME_stand_39, + FRAME_run_01, + FRAME_run_02, + FRAME_run_03, + FRAME_run_04, + FRAME_run_05, + FRAME_run_06, + FRAME_clawatk3_01, + FRAME_clawatk3_02, + FRAME_clawatk3_03, + FRAME_clawatk3_04, + FRAME_clawatk3_05, + FRAME_clawatk3_06, + FRAME_clawatk3_07, + FRAME_clawatk3_08, + FRAME_clawatk3_09, + FRAME_clawatk4_01, + FRAME_clawatk4_02, + FRAME_clawatk4_03, + FRAME_clawatk4_04, + FRAME_clawatk4_05, + FRAME_clawatk4_06, + FRAME_clawatk4_07, + FRAME_clawatk4_08, + FRAME_clawatk5_01, + FRAME_clawatk5_02, + FRAME_clawatk5_03, + FRAME_clawatk5_04, + FRAME_clawatk5_05, + FRAME_clawatk5_06, + FRAME_clawatk5_07, + FRAME_clawatk5_08, + FRAME_clawatk5_09, + FRAME_leapatk_01, + FRAME_leapatk_02, + FRAME_leapatk_03, + FRAME_leapatk_04, + FRAME_leapatk_05, + FRAME_leapatk_06, + FRAME_leapatk_07, + FRAME_leapatk_08, + FRAME_leapatk_09, + FRAME_leapatk_10, + FRAME_leapatk_11, + FRAME_leapatk_12, + FRAME_leapatk_13, + FRAME_leapatk_14, + FRAME_leapatk_15, + FRAME_leapatk_16, + FRAME_leapatk_17, + FRAME_leapatk_18, + FRAME_leapatk_19, + FRAME_pain3_01, + FRAME_pain3_02, + FRAME_pain3_03, + FRAME_pain3_04, + FRAME_pain3_05, + FRAME_pain3_06, + FRAME_pain3_07, + FRAME_pain3_08, + FRAME_pain3_09, + FRAME_pain3_10, + FRAME_pain3_11, + FRAME_pain4_01, + FRAME_pain4_02, + FRAME_pain4_03, + FRAME_pain4_04, + FRAME_pain4_05, + FRAME_pain4_06, + FRAME_pain4_07, + FRAME_pain4_08, + FRAME_pain4_09, + FRAME_pain4_10, + FRAME_pain4_11, + FRAME_pain4_12, + FRAME_pain4_13, + FRAME_death1_01, + FRAME_death1_02, + FRAME_death1_03, + FRAME_death1_04, + FRAME_death1_05, + FRAME_death1_06, + FRAME_death1_07, + FRAME_death1_08, + FRAME_death1_09, + FRAME_death1_10, + FRAME_death2_01, + FRAME_death2_02, + FRAME_death2_03, + FRAME_death2_04, + FRAME_death2_05, + FRAME_death2_06, + FRAME_death2_07, + FRAME_death2_08, + FRAME_death2_09, + FRAME_death2_10, + FRAME_death2_11, + FRAME_death3_01, + FRAME_death3_02, + FRAME_death3_03, + FRAME_death3_04, + FRAME_death3_05, + FRAME_death3_06, + FRAME_death3_07, + FRAME_death4_01, + FRAME_death4_02, + FRAME_death4_03, + FRAME_death4_04, + FRAME_death4_05, + FRAME_death4_06, + FRAME_death4_07, + FRAME_death4_08, + FRAME_death4_09, + FRAME_death4_10, + FRAME_death4_11, + FRAME_death4_12, + FRAME_death4_13, + FRAME_death4_14, + FRAME_death4_15, + FRAME_death4_16, + FRAME_death4_17, + FRAME_death4_18, + FRAME_death4_19, + FRAME_death4_20, + FRAME_death4_21, + FRAME_death4_22, + FRAME_death4_23, + FRAME_death4_24, + FRAME_death4_25, + FRAME_death4_26, + FRAME_death4_27, + FRAME_death4_28, + FRAME_death4_29, + FRAME_death4_30, + FRAME_death4_31, + FRAME_death4_32, + FRAME_death4_33, + FRAME_death4_34, + FRAME_death4_35, + FRAME_rduck_01, + FRAME_rduck_02, + FRAME_rduck_03, + FRAME_rduck_04, + FRAME_rduck_05, + FRAME_rduck_06, + FRAME_rduck_07, + FRAME_rduck_08, + FRAME_rduck_09, + FRAME_rduck_10, + FRAME_rduck_11, + FRAME_rduck_12, + FRAME_rduck_13, + FRAME_lduck_01, + FRAME_lduck_02, + FRAME_lduck_03, + FRAME_lduck_04, + FRAME_lduck_05, + FRAME_lduck_06, + FRAME_lduck_07, + FRAME_lduck_08, + FRAME_lduck_09, + FRAME_lduck_10, + FRAME_lduck_11, + FRAME_lduck_12, + FRAME_lduck_13, + FRAME_idle_01, + FRAME_idle_02, + FRAME_idle_03, + FRAME_idle_04, + FRAME_idle_05, + FRAME_idle_06, + FRAME_idle_07, + FRAME_idle_08, + FRAME_idle_09, + FRAME_idle_10, + FRAME_idle_11, + FRAME_idle_12, + FRAME_idle_13, + FRAME_idle_14, + FRAME_idle_15, + FRAME_idle_16, + FRAME_idle_17, + FRAME_idle_18, + FRAME_idle_19, + FRAME_idle_20, + FRAME_idle_21, + FRAME_idle_22, + FRAME_idle_23, + FRAME_idle_24, + FRAME_idle_25, + FRAME_idle_26, + FRAME_idle_27, + FRAME_idle_28, + FRAME_idle_29, + FRAME_idle_30, + FRAME_idle_31, + FRAME_idle_32, + FRAME_spit_01, + FRAME_spit_02, + FRAME_spit_03, + FRAME_spit_04, + FRAME_spit_05, + FRAME_spit_06, + FRAME_spit_07, + FRAME_amb_01, + FRAME_amb_02, + FRAME_amb_03, + FRAME_amb_04, + FRAME_wdeath_01, + FRAME_wdeath_02, + FRAME_wdeath_03, + FRAME_wdeath_04, + FRAME_wdeath_05, + FRAME_wdeath_06, + FRAME_wdeath_07, + FRAME_wdeath_08, + FRAME_wdeath_09, + FRAME_wdeath_10, + FRAME_wdeath_11, + FRAME_wdeath_12, + FRAME_wdeath_13, + FRAME_wdeath_14, + FRAME_wdeath_15, + FRAME_wdeath_16, + FRAME_wdeath_17, + FRAME_wdeath_18, + FRAME_wdeath_19, + FRAME_wdeath_20, + FRAME_wdeath_21, + FRAME_wdeath_22, + FRAME_wdeath_23, + FRAME_wdeath_24, + FRAME_wdeath_25, + FRAME_wdeath_26, + FRAME_wdeath_27, + FRAME_wdeath_28, + FRAME_wdeath_29, + FRAME_wdeath_30, + FRAME_wdeath_31, + FRAME_wdeath_32, + FRAME_wdeath_33, + FRAME_wdeath_34, + FRAME_wdeath_35, + FRAME_wdeath_36, + FRAME_wdeath_37, + FRAME_wdeath_38, + FRAME_wdeath_39, + FRAME_wdeath_40, + FRAME_wdeath_41, + FRAME_wdeath_42, + FRAME_wdeath_43, + FRAME_wdeath_44, + FRAME_wdeath_45, + FRAME_swim_01, + FRAME_swim_02, + FRAME_swim_03, + FRAME_swim_04, + FRAME_swim_05, + FRAME_swim_06, + FRAME_swim_07, + FRAME_swim_08, + FRAME_swim_09, + FRAME_swim_10, + FRAME_swim_11, + FRAME_swim_12, + FRAME_swim_13, + FRAME_swim_14, + FRAME_swim_15, + FRAME_swim_16, + FRAME_swim_17, + FRAME_swim_18, + FRAME_swim_19, + FRAME_swim_20, + FRAME_swim_21, + FRAME_swim_22, + FRAME_swim_23, + FRAME_swim_24, + FRAME_swim_25, + FRAME_swim_26, + FRAME_swim_27, + FRAME_swim_28, + FRAME_swim_29, + FRAME_swim_30, + FRAME_swim_31, + FRAME_swim_32, + FRAME_attack_01, + FRAME_attack_02, + FRAME_attack_03, + FRAME_attack_04, + FRAME_attack_05, + FRAME_attack_06, + FRAME_attack_07, + FRAME_attack_08, + FRAME_attack_09, + FRAME_attack_10, + FRAME_attack_11, + FRAME_attack_12, + FRAME_attack_13, + FRAME_attack_14, + FRAME_attack_15, + FRAME_attack_16, + FRAME_attack_17, + FRAME_attack_18, + FRAME_attack_19, + FRAME_attack_20, + FRAME_attack_21, + FRAME_pain_01, + FRAME_pain_02, + FRAME_pain_03, + FRAME_pain_04, + FRAME_pain_05, + FRAME_pain_06 +}; + +constexpr float MODEL_SCALE = 1.000000f; diff --git a/to add/m_gladiator.cpp b/to add/m_gladiator.cpp new file mode 100644 index 00000000..9dbaf7b6 --- /dev/null +++ b/to add/m_gladiator.cpp @@ -0,0 +1,714 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +GLADIATOR + +============================================================================== +*/ + +#include "g_local.h" +#include "m_gladiator.h" +#include "m_flash.h" +#include "shared.h" +#include "horde/g_horde_scaling.h" +#include "monster_constants.h" + +static cached_soundindex sound_pain1; +static cached_soundindex sound_pain2; +static cached_soundindex sound_die; +static cached_soundindex sound_die2; +static cached_soundindex sound_gun; +static cached_soundindex sound_gunb; +static cached_soundindex sound_gunc; +static cached_soundindex sound_cleaver_swing; +static cached_soundindex sound_cleaver_hit; +static cached_soundindex sound_cleaver_miss; +static cached_soundindex sound_idle; +static cached_soundindex sound_search; +static cached_soundindex sound_sight; + +MONSTERINFO_IDLE(gladiator_idle) (edict_t* self) -> void +{ + gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + +MONSTERINFO_SIGHT(gladiator_sight) (edict_t* self, edict_t* other) -> void +{ + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +MONSTERINFO_SEARCH(gladiator_search) (edict_t* self) -> void +{ + gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); +} + +void gladiator_cleaver_swing(edict_t* self) +{ + gi.sound(self, CHAN_WEAPON, sound_cleaver_swing, 1, ATTN_NORM, 0); +} + +mframe_t gladiator_frames_stand[] = { + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand } +}; +MMOVE_T(gladiator_move_stand) = { FRAME_stand1, FRAME_stand7, gladiator_frames_stand, nullptr }; + +MONSTERINFO_STAND(gladiator_stand) (edict_t* self) -> void +{ + M_SetAnimation(self, &gladiator_move_stand); +} + +mframe_t gladiator_frames_walk[] = { + { ai_walk, 15 }, + { ai_walk, 7 }, + { ai_walk, 6 }, + { ai_walk, 5 }, + { ai_walk, 2, monster_footstep }, + { ai_walk }, + { ai_walk, 2 }, + { ai_walk, 8 }, + { ai_walk, 12 }, + { ai_walk, 8 }, + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, 2, monster_footstep }, + { ai_walk, 2 }, + { ai_walk, 1 }, + { ai_walk, 8 } +}; +MMOVE_T(gladiator_move_walk) = { FRAME_walk1, FRAME_walk16, gladiator_frames_walk, nullptr }; + +MONSTERINFO_WALK(gladiator_walk) (edict_t* self) -> void +{ + M_SetAnimation(self, &gladiator_move_walk); +} + +mframe_t gladiator_frames_run[] = { + { ai_run, 23 }, + { ai_run, 14 }, + { ai_run, 14, monster_footstep }, + { ai_run, 21 }, + { ai_run, 12 }, + { ai_run, 13, monster_footstep } +}; +MMOVE_T(gladiator_move_run) = { FRAME_run1, FRAME_run6, gladiator_frames_run, nullptr }; + +MONSTERINFO_RUN(gladiator_run) (edict_t* self) -> void +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + M_SetAnimation(self, &gladiator_move_stand); + else + M_SetAnimation(self, &gladiator_move_run); +} + +void GladiatorMelee(edict_t* self) +{ + if (!M_HasValidTarget(self)) + { + self->monsterinfo.melee_debounce_time = level.time + 1.5_sec; + return; + } + + vec3_t const aim = { MELEE_DISTANCE, self->mins[0], -4 }; + + if (fire_hit(self, aim, irandom(30, 35), 300)) + { + gi.sound(self, CHAN_AUTO, sound_cleaver_hit, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self, CHAN_AUTO, sound_cleaver_miss, 1, ATTN_NORM, 0); + self->monsterinfo.melee_debounce_time = level.time + 1.5_sec; + } +} + +mframe_t gladiator_frames_attack_melee[] = { + { ai_charge, 0, gladiator_cleaver_swing }, + { ai_charge, 0, GladiatorMelee }, + { ai_charge, 0, gladiator_cleaver_swing }, + { ai_charge, 0, GladiatorMelee }, + { ai_charge, 0, GladiatorMelee }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, gladiator_cleaver_swing }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, GladiatorMelee }, + { ai_charge }, + { ai_charge, 0, GladiatorMelee }, +}; +MMOVE_T(gladiator_move_attack_melee) = { FRAME_melee3, FRAME_melee16, gladiator_frames_attack_melee, gladiator_run }; + +MONSTERINFO_MELEE(gladiator_melee) (edict_t* self) -> void +{ + M_SetAnimation(self, &gladiator_move_attack_melee); +} + +void GladiatorGun(edict_t* self) +{ + if (!M_HasValidTarget(self)) + { + return; // Stop immediately if the target is invalid. + } + + // Railgun is hitscan, requires visibility (no blindfire) + if (!visible(self, self->enemy)) + return; + + int damage = M_RAILGUN_DMG(self); + + vec3_t start; + vec3_t dir; + vec3_t forward, right; + + AngleVectors(self->s.angles, forward, right, nullptr); + start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_GLADIATOR_RAILGUN_1], forward, right); + + // calc direction to where we targted + dir = self->pos1 - start; + dir.normalize(); + + monster_fire_railgun(self, start, dir, damage, 100, MZ2_GLADIATOR_RAILGUN_1); +} + +void Gladiator_refire_chance(edict_t* self) +{ + if (M_HasValidTarget(self) && frandom() < 0.25f) + self->monsterinfo.nextframe = FRAME_attack1; // refire +} + +mframe_t gladiator_frames_attack_gun[] = { + { ai_charge }, + { ai_charge, 0, GladiatorGun }, + { ai_charge }, + { ai_charge }, + { ai_charge,0, Gladiator_refire_chance }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, monster_footstep }, + { ai_charge } +}; +MMOVE_T(gladiator_move_attack_gun) = { FRAME_attack1, FRAME_attack9, gladiator_frames_attack_gun, gladiator_run }; + +// RAFAEL +void gladbGun(edict_t* self) +{ + if (!M_HasValidTarget(self)) + { + return; // Stop immediately if the target is invalid. + } + + int damage = M_TRACKER_DMG(self); + + vec3_t start; + vec3_t dir; + vec3_t forward, right; + float len; + + AngleVectors(self->s.angles, forward, right, nullptr); + start = G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_GLADIATOR_RAILGUN_1], forward, right); + + // Blindfire support for tracker + bool blindfire = (self->monsterinfo.aiflags & AI_MANUAL_STEERING); + vec3_t target_pos; + + if (blindfire && self->monsterinfo.blind_fire_target) + { + target_pos = self->monsterinfo.blind_fire_target; + } + else + { + target_pos = self->pos1; // Use saved position from attack start + } + + dir = target_pos - self->enemy->s.origin; + len = dir.length(); + + if (len < 30) + { + // calc direction to where we targeted + dir = target_pos - start; + dir.normalize(); + + monster_fire_tracker(self, start, dir, damage, M_TRACKER_SPEED(self), self->enemy, MZ2_GLADIATOR_RAILGUN_1); + } + else + { + if (blindfire) + { + // Blindfire: aim at blind_fire_target + dir = (target_pos - start).normalized(); + monster_fire_tracker(self, start, dir, damage, M_TRACKER_SPEED(self), nullptr, MZ2_GLADIATOR_RAILGUN_1); + } + else + { + // Normal: use predictive aim + PredictAim(self, self->enemy, start, 980, true, 0, &dir, nullptr); + monster_fire_tracker(self, start, dir, damage, M_TRACKER_SPEED(self), nullptr, MZ2_GLADIATOR_RAILGUN_1); + } + } +} + +void gladbGun_check(edict_t* self) +{ + if (skill->integer == 3) + gladbGun(self); +} + +mframe_t gladb_frames_attack_gun[] = { + { ai_charge }, + { ai_charge, 0, gladbGun }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, gladbGun }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, Gladiator_refire_chance } + +}; +MMOVE_T(gladb_move_attack_gun) = { FRAME_attack1, FRAME_attack9, gladb_frames_attack_gun, gladiator_run }; +// HORDE +// RAFAEL +void gladcGun(edict_t* self) +{ + if (!M_HasValidTarget(self)) + { + return; // Stop immediately if the target is invalid. + } + + int damage = M_GET_DMG_OR(self, PLASMA, 35); + + int radius_damage = 45; + + vec3_t start; + vec3_t dir; + vec3_t forward, right; + + AngleVectors(self->s.angles, forward, right, nullptr); + start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_GLADIATOR_RAILGUN_1], forward, right); + + // Blindfire support for plasma + bool blindfire = (self->monsterinfo.aiflags & AI_MANUAL_STEERING); + vec3_t target_pos; + + if (blindfire && self->monsterinfo.blind_fire_target) + { + target_pos = self->monsterinfo.blind_fire_target; + } + else + { + target_pos = self->pos1; // Use saved position from attack start + } + + // calc direction to where we targeted + dir = target_pos - start; + dir.normalize(); + + if (self->s.frame > FRAME_attack3) + { + damage /= 2; + radius_damage /= 2; + } + float const r = frandom(); + fire_plasma(self, start, dir, damage, M_PLASMA_SPEED(self), M_PLASMA_RADIUS(self), M_PLASMA_RADIUS(self)); + if (r < 0.5f && current_wave_level >= 18) { + fire_plasma(self, start, dir, damage, 1225, M_PLASMA_RADIUS(self), M_PLASMA_RADIUS(self)); + } + + // FIX: Check if the enemy is still valid before updating the aim position. + // The plasma shot we just fired might have killed it. + if (self->enemy && self->enemy->inuse) + { + // save for aiming the shot + self->pos1 = self->enemy->s.origin; + self->pos1[2] += self->enemy->viewheight; + } +} + +void gladcGun_check(edict_t* self) +{ + if (skill->integer == 3) + gladcGun(self); +} + +mframe_t gladc_frames_attack_gun[] = { + { ai_charge }, + { ai_charge }, + { ai_charge, 0, gladcGun }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, gladcGun }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, gladcGun_check } +}; +MMOVE_T(gladc_move_attack_gun) = { FRAME_attack1, FRAME_attack9, gladc_frames_attack_gun, gladiator_run }; + +// RAFAEL + +MONSTERINFO_ATTACK(gladiator_attack) (edict_t* self) -> void +{ + monster_done_dodge(self); + + if (!M_HasValidTarget(self)) + { + return; // Stop immediately if the target is invalid. + } + + // Pre-calculate distance once for performance + vec3_t const v = self->s.origin - self->enemy->s.origin; + float const range = v.length(); + if (range <= (MELEE_DISTANCE + 32) && self->monsterinfo.melee_debounce_time <= level.time) + return; + else if (!M_CheckClearShot(self, monster_flash_offset[MZ2_GLADIATOR_RAILGUN_1])) + return; + + // charge up the railgun + self->pos1 = self->enemy->s.origin; // save for aiming the shot + self->pos1[2] += self->enemy->viewheight; + // RAFAEL + if (self->style == 1) + { + gi.sound(self, CHAN_WEAPON, sound_gunb, 1, ATTN_NORM, 0); + M_SetAnimation(self, &gladb_move_attack_gun); + } + else if (self->style == 3) + { + gi.sound(self, CHAN_WEAPON, sound_gun, 1, ATTN_NORM, 0); + M_SetAnimation(self, &gladc_move_attack_gun); + } + else + { + // RAFAEL + gi.sound(self, CHAN_WEAPON, sound_gun, 1, ATTN_NORM, 0); + M_SetAnimation(self, &gladiator_move_attack_gun); + // RAFAEL + } + // RAFAEL +} + +mframe_t gladiator_frames_pain[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(gladiator_move_pain) = { FRAME_pain2, FRAME_pain5, gladiator_frames_pain, gladiator_run }; + +mframe_t gladiator_frames_pain_air[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(gladiator_move_pain_air) = { FRAME_painup2, FRAME_painup6, gladiator_frames_pain_air, gladiator_run }; + +PAIN(gladiator_pain) (edict_t* self, edict_t* other, float kick, int damage, const mod_t& mod) -> void +{ + if (level.time < self->pain_debounce_time) + { + if ((self->velocity[2] > 100) && (self->monsterinfo.active_move == &gladiator_move_pain)) + M_SetAnimation(self, &gladiator_move_pain_air); + return; + } + + self->pain_debounce_time = level.time + 3_sec; + + if (frandom() < 0.5f) + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + + if (!M_ShouldReactToPain(self, mod)) + return; // no pain anims in nightmare + + if (self->velocity[2] > 100) + M_SetAnimation(self, &gladiator_move_pain_air); + else + M_SetAnimation(self, &gladiator_move_pain); +} + +MONSTERINFO_SETSKIN(gladiator_setskin) (edict_t* self) -> void +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum |= 1; + else + self->s.skinnum &= ~1; +} + +void gladiator_dead(edict_t* self) +{ + self->mins = { -16, -16, -24 }; + self->maxs = { 16, 16, -8 }; + monster_dead(self); +} + +static void gladiator_shrink(edict_t* self) +{ + self->maxs[2] = 0; + self->svflags |= SVF_DEADMONSTER; + gi.linkentity(self); +} + +mframe_t gladiator_frames_death[] = { + { ai_move }, + { ai_move }, + { ai_move, 0, gladiator_shrink }, + { ai_move, 0, monster_footstep }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(gladiator_move_death) = { FRAME_death2, FRAME_death22, gladiator_frames_death, gladiator_dead }; + +DIE(gladiator_die) (edict_t* self, edict_t* inflictor, edict_t* attacker, int damage, const vec3_t& point, const mod_t& mod) -> void +{ + //OnEntityDeath(self); + // check for gib + if (M_CheckGib(self, mod)) + { + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); + + self->s.skinnum /= 2; + + ThrowGibs(self, damage, { + { 2, "models/objects/gibs/bone/tris.md2" }, + { 2, "models/objects/gibs/sm_meat/tris.md2" }, + { 2, "models/monsters/gladiatr/gibs/thigh.md2", GIB_SKINNED }, + { "models/monsters/gladiatr/gibs/larm.md2", GIB_SKINNED | GIB_UPRIGHT }, + { "models/monsters/gladiatr/gibs/rarm.md2", GIB_SKINNED | GIB_UPRIGHT }, + { "models/monsters/gladiatr/gibs/chest.md2", GIB_SKINNED }, + { "models/monsters/gladiatr/gibs/head.md2", GIB_SKINNED | GIB_HEAD } + }); + self->deadflag = true; + return; + } + + if (self->deadflag) + return; + + // regular death + gi.sound(self, CHAN_BODY, sound_die, 1, ATTN_NORM, 0); + + if (brandom()) + gi.sound(self, CHAN_VOICE, sound_die2, 1, ATTN_NORM, 0); + + self->deadflag = true; + self->takedamage = true; + + M_SetAnimation(self, &gladiator_move_death); +} + +//=========== +// PGM +MONSTERINFO_BLOCKED(gladiator_blocked) (edict_t* self, float dist) -> bool +{ + if (blocked_checkplat(self, dist)) + return true; + + // Check if we can jump + if (auto const result = blocked_checkjump(self, dist); result != blocked_jump_result_t::NO_JUMP) + { + if (result != blocked_jump_result_t::JUMP_TURN) + { + // Reduced forward velocity to prevent excessive jump distance + M_MonsterJump(self, 180.0f, 250.0f); + } + return true; + } + + return false; +} +// PGM +//=========== + +/*QUAKED monster_gladiator (1 .5 0) (-32 -32 -24) (32 32 64) Ambush Trigger_Spawn Sight +*/ +void SP_monster_gladiator(edict_t* self) +{ + const spawn_temp_t& st = ED_GetSpawnTemp(); + + // Early exit check + if (!M_AllowSpawn(self)) { + G_FreeEdict(self); + return; + } + + if (self->monsterinfo.monster_type_id == MONSTER_TYPE_UNKNOWN) { // Check if it hasn't been set yet + self->monsterinfo.monster_type_id = static_cast(horde::MonsterTypeID::GLADIATOR); + } if (g_horde->integer && current_wave_level <= 18) + { + const float randomsearch = frandom(); // Generar un número aleatorio entre 0 y 1 + + if (randomsearch < 0.23f) + gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); + } + + + // Sound assignments + sound_pain1.assign("gladiator/pain.wav"); + sound_pain2.assign("gladiator/gldpain2.wav"); + sound_die.assign("gladiator/glddeth2.wav"); + sound_die2.assign("gladiator/death.wav"); + sound_cleaver_swing.assign("gladiator/melee1.wav"); + sound_cleaver_hit.assign("gladiator/melee2.wav"); + sound_cleaver_miss.assign("gladiator/melee3.wav"); + sound_idle.assign("gladiator/gldidle1.wav"); + sound_search.assign("gladiator/gldsrch1.wav"); + sound_sight.assign("gladiator/sight.wav"); + + // Model setup + self->s.modelindex = gi.modelindex("models/monsters/gladiatr/tris.md2"); + gi.modelindex("models/monsters/gladiatr/gibs/chest.md2"); + gi.modelindex("models/monsters/gladiatr/gibs/head.md2"); + gi.modelindex("models/monsters/gladiatr/gibs/larm.md2"); + gi.modelindex("models/monsters/gladiatr/gibs/rarm.md2"); + gi.modelindex("models/monsters/gladiatr/gibs/thigh.md2"); + + // Basic monster properties + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->gib_health = -175; + self->mins = { -32, -32, -24 }; + self->maxs = { 32, 32, 42 }; + + // Type-specific configuration based on style + switch (self->style) { + case 1: // gladb + sound_gunb.assign("weapons/disrupt.wav"); + { + if (g_horde && g_horde->integer && current_wave_level > 0) { + self->health = M_GLADIATOR_ADDON_HEALTH(self); + } else { + self->health = static_cast(M_GLADIATOR_INITIAL_HEALTH * st.health_multiplier); + } + } + self->mass = 350; + + self->s.skinnum = 2; + self->s.effects = EF_TRACKER; + self->monsterinfo.weapon_sound = gi.soundindex("weapons/phaloop.wav"); + break; + + case 3: // gladc + sound_gunc.assign("weapons/plasshot.wav"); + { + if (g_horde && g_horde->integer && current_wave_level > 0) { + self->health = M_GLADIATOR_ADDON_HEALTH(self); + } else { + self->health = static_cast(M_GLADIATOR_INITIAL_HEALTH * st.health_multiplier); + } + } + self->mass = 350; + + self->s.skinnum = 2; + self->monsterinfo.weapon_sound = gi.soundindex("weapons/phaloop.wav"); + break; + + default: // normal gladiator + sound_gun.assign("gladiator/railgun.wav"); + { + if (g_horde && g_horde->integer && current_wave_level > 0) { + self->health = M_GLADIATOR_ADDON_HEALTH(self); + } else { + self->health = static_cast(M_GLADIATOR_INITIAL_HEALTH * st.health_multiplier); + } + } + self->mass = 400; + + self->monsterinfo.weapon_sound = gi.soundindex("weapons/rg_hum.wav"); + break; + } + + // Armor setup based on actual monster_type_id (not hardcoded GLADIATOR) + // This ensures each variant gets the correct armor type from its JSON config + uint8_t type_id = self->monsterinfo.monster_type_id; + + // Get armor configuration dynamically based on monster_type_id + int base_armor = GetMonsterBaseArmor(type_id); + item_id_t power_armor_type = static_cast(GetMonsterPowerArmorType(type_id)); + + // Set power armor if configured + if (!st.was_key_specified("power_armor_type") && power_armor_type != IT_NULL) { + self->monsterinfo.power_armor_type = power_armor_type; + if (!st.was_key_specified("power_armor_power")) + self->monsterinfo.power_armor_power = GetMonsterScaledPowerArmor(type_id, current_wave_level, self->monsterinfo.IS_BOSS); + } + + // Set regular armor if configured + if (!st.was_key_specified("armor_type") && base_armor > 0) { + self->monsterinfo.armor_type = IT_ARMOR_COMBAT; + if (!st.was_key_specified("armor_power")) + self->monsterinfo.armor_power = GetMonsterScaledArmor(type_id, current_wave_level, self->monsterinfo.IS_BOSS); + } + + // Monster AI/behavior functions + self->pain = gladiator_pain; + self->die = gladiator_die; + self->monsterinfo.stand = gladiator_stand; + self->monsterinfo.walk = gladiator_walk; + self->monsterinfo.run = gladiator_run; + self->monsterinfo.dodge = nullptr; + self->monsterinfo.attack = gladiator_attack; + self->monsterinfo.melee = gladiator_melee; + self->monsterinfo.sight = gladiator_sight; + self->monsterinfo.idle = gladiator_idle; + self->monsterinfo.search = gladiator_search; + self->monsterinfo.blocked = gladiator_blocked; + self->monsterinfo.setskin = gladiator_setskin; + + // Horde mode specific + if (g_horde->integer && current_wave_level <= 18) { + if (frandom() < 0.23f) + gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); + } + + // Final setup + gi.linkentity(self); + M_SetAnimation(self, &gladiator_move_stand); + self->monsterinfo.scale = MODEL_SCALE; + walkmonster_start(self); + ApplyMonsterBonusFlags(self); +} + +/*QUAKED monster_gladb (1 .5 0) (-32 -32 -24) (32 32 64) Ambush Trigger_Spawn Sight +*/ +void SP_monster_gladb(edict_t* self) +{ + self->style = 1; + self->monsterinfo.monster_type_id = static_cast(horde::MonsterTypeID::GLADIATOR_B); + SP_monster_gladiator(self); + // All armor and health configuration is handled by SP_monster_gladiator's switch statement +} + +void SP_monster_gladc(edict_t* self) +{ + self->style = 3; + self->monsterinfo.monster_type_id = static_cast(horde::MonsterTypeID::GLADIATOR_C); + SP_monster_gladiator(self); + // All armor and health configuration is handled by SP_monster_gladiator's switch statement +} \ No newline at end of file diff --git a/to add/m_guncmdr.cpp b/to add/m_guncmdr.cpp new file mode 100644 index 00000000..e1559702 --- /dev/null +++ b/to add/m_guncmdr.cpp @@ -0,0 +1,1475 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +GUNNER + +============================================================================== +*/ + +#include "g_local.h" +#include "m_gunner.h" +#include "m_flash.h" + +constexpr spawnflags_t SPAWNFLAG_GUNCMDR_NOJUMPING = 8_spawnflag; + +static cached_soundindex sound_pain; +static cached_soundindex sound_pain2; +static cached_soundindex sound_death; +static cached_soundindex sound_idle; +static cached_soundindex sound_open; +static cached_soundindex sound_search; +static cached_soundindex sound_sight; + +void guncmdr_idlesound(edict_t *self) +{ + gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + +MONSTERINFO_SIGHT(guncmdr_sight) (edict_t *self, edict_t *other) -> void +{ + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +MONSTERINFO_SEARCH(guncmdr_search) (edict_t *self) -> void +{ + gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); +} + +void GunnerGrenade(edict_t *self); +void GunnerFire(edict_t *self); +void guncmdr_fire_chain(edict_t *self); +void guncmdr_refire_chain(edict_t *self); + +void guncmdr_stand(edict_t *self); + +mframe_t guncmdr_frames_fidget[] = { + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand, 0, guncmdr_idlesound }, + { ai_stand }, + { ai_stand }, + + { ai_stand }, + { ai_stand, 0, guncmdr_idlesound }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand } +}; +MMOVE_T(guncmdr_move_fidget) = { FRAME_c_stand201, FRAME_c_stand254, guncmdr_frames_fidget, guncmdr_stand }; + +void guncmdr_fidget(edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + return; + else if (self->enemy) + return; + if (frandom() <= 0.05f) + M_SetAnimation(self, &guncmdr_move_fidget); +} + +mframe_t guncmdr_frames_stand[] = { + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand, 0, guncmdr_fidget }, + + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand, 0, guncmdr_fidget }, + + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand, 0, guncmdr_fidget }, + + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand, 0, guncmdr_fidget } +}; +MMOVE_T(guncmdr_move_stand) = { FRAME_c_stand101, FRAME_c_stand140, guncmdr_frames_stand, nullptr }; + +MONSTERINFO_STAND(guncmdr_stand) (edict_t *self) -> void +{ + M_SetAnimation(self, &guncmdr_move_stand); +} + +mframe_t guncmdr_frames_walk[] = { + { ai_walk, 1.5f, monster_footstep }, + { ai_walk, 2.5f }, + { ai_walk, 3.0f }, + { ai_walk, 2.5f }, + { ai_walk, 2.3f }, + { ai_walk, 3.0f }, + { ai_walk, 2.8f, monster_footstep }, + { ai_walk, 3.6f }, + { ai_walk, 2.8f }, + { ai_walk, 2.5f }, + + { ai_walk, 2.3f }, + { ai_walk, 4.3f }, + { ai_walk, 3.0f, monster_footstep }, + { ai_walk, 1.5f }, + { ai_walk, 2.5f }, + { ai_walk, 3.3f }, + { ai_walk, 2.8f }, + { ai_walk, 3.0f }, + { ai_walk, 2.0f, monster_footstep }, + { ai_walk, 2.0f }, + + { ai_walk, 3.3f }, + { ai_walk, 3.6f }, + { ai_walk, 3.4f }, + { ai_walk, 2.8f }, +}; +MMOVE_T(guncmdr_move_walk) = { FRAME_c_walk101, FRAME_c_walk124, guncmdr_frames_walk, nullptr }; + +MONSTERINFO_WALK(guncmdr_walk) (edict_t *self) -> void +{ + M_SetAnimation(self, &guncmdr_move_walk); +} + +mframe_t guncmdr_frames_run[] = { + { ai_run, 15.f, monster_done_dodge }, + { ai_run, 16.f, monster_footstep }, + { ai_run, 20.f }, + { ai_run, 18.f }, + { ai_run, 24.f, monster_footstep }, + { ai_run, 13.5f } +}; + +MMOVE_T(guncmdr_move_run) = { FRAME_c_run101, FRAME_c_run106, guncmdr_frames_run, nullptr }; + +MONSTERINFO_RUN(guncmdr_run) (edict_t *self) -> void +{ + monster_done_dodge(self); + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + M_SetAnimation(self, &guncmdr_move_stand); + else + M_SetAnimation(self, &guncmdr_move_run); +} + +// standing pains + +mframe_t guncmdr_frames_pain1[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, +}; +MMOVE_T(guncmdr_move_pain1) = { FRAME_c_pain101, FRAME_c_pain104, guncmdr_frames_pain1, guncmdr_run }; + +mframe_t guncmdr_frames_pain2[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(guncmdr_move_pain2) = { FRAME_c_pain201, FRAME_c_pain204, guncmdr_frames_pain2, guncmdr_run }; + +mframe_t guncmdr_frames_pain3[] = { + { ai_move, -3.0f }, + { ai_move }, + { ai_move }, + { ai_move }, +}; +MMOVE_T(guncmdr_move_pain3) = { FRAME_c_pain301, FRAME_c_pain304, guncmdr_frames_pain3, guncmdr_run }; + +mframe_t guncmdr_frames_pain4[] = { + { ai_move, -17.1f }, + { ai_move, -3.2f }, + { ai_move, 0.9f }, + { ai_move, 3.6f }, + { ai_move, -2.6f }, + { ai_move, 1.0f }, + { ai_move, -5.1f }, + { ai_move, -6.7f }, + { ai_move, -8.8f }, + { ai_move }, + + { ai_move }, + { ai_move, -2.1f }, + { ai_move, -2.3f }, + { ai_move, -2.5f }, + { ai_move } +}; +MMOVE_T(guncmdr_move_pain4) = { FRAME_c_pain401, FRAME_c_pain415, guncmdr_frames_pain4, guncmdr_run }; + +void guncmdr_dead(edict_t *); + +mframe_t guncmdr_frames_death1[] = { + { ai_move }, + { ai_move }, + { ai_move, 4.0f }, // scoot + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(guncmdr_move_death1) = { FRAME_c_death101, FRAME_c_death118, guncmdr_frames_death1, guncmdr_dead }; + +void guncmdr_pain5_to_death1(edict_t *self) +{ + if (self->health <= 0) + M_SetAnimation(self, &guncmdr_move_death1, false); +} + +mframe_t guncmdr_frames_death2[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(guncmdr_move_death2) = { FRAME_c_death201, FRAME_c_death204, guncmdr_frames_death2, guncmdr_dead }; + +void guncmdr_pain5_to_death2(edict_t *self) +{ + if (self->health <= 0 && brandom()) + M_SetAnimation(self, &guncmdr_move_death2, false); +} + +mframe_t guncmdr_frames_pain5[] = { + { ai_move, -29.f }, + { ai_move, -5.f }, + { ai_move, -5.f }, + { ai_move, -3.f }, + { ai_move }, + { ai_move, 0, guncmdr_pain5_to_death2 }, + { ai_move, 9.f }, + { ai_move, 3.f }, + { ai_move, 0, guncmdr_pain5_to_death1 }, + { ai_move }, + + { ai_move }, + { ai_move, -4.6f }, + { ai_move, -4.8f }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 9.5f }, + { ai_move, 3.4f }, + { ai_move }, + { ai_move }, + + { ai_move, -2.4f }, + { ai_move, -9.0f }, + { ai_move, -5.0f }, + { ai_move, -3.6f }, +}; +MMOVE_T(guncmdr_move_pain5) = { FRAME_c_pain501, FRAME_c_pain524, guncmdr_frames_pain5, guncmdr_run }; + +void guncmdr_dead(edict_t *self) +{ + self->mins = vec3_t { -16, -16, -24 } * self->s.scale; + self->maxs = vec3_t { 16, 16, -8 } * self->s.scale; + monster_dead(self); +} + +static void guncmdr_shrink(edict_t *self) +{ + self->maxs[2] = -4 * self->s.scale; + self->svflags |= SVF_DEADMONSTER; + gi.linkentity(self); +} + +mframe_t guncmdr_frames_death6[] = { + { ai_move, 0, guncmdr_shrink }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(guncmdr_move_death6) = { FRAME_c_death601, FRAME_c_death614, guncmdr_frames_death6, guncmdr_dead }; + +static void guncmdr_pain6_to_death6(edict_t *self) +{ + if (self->health <= 0) + M_SetAnimation(self, &guncmdr_move_death6, false); +} + +mframe_t guncmdr_frames_pain6[] = { + { ai_move, 16.f }, + { ai_move, 16.f }, + { ai_move, 12.f }, + { ai_move, 5.5f, monster_duck_down }, + { ai_move, 3.0f }, + { ai_move, -4.7f }, + { ai_move, -6.0f, guncmdr_pain6_to_death6 }, + { ai_move }, + { ai_move, 1.8f }, + { ai_move, 0.7f }, + + { ai_move }, + { ai_move, -2.1f }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move, -6.1f }, + { ai_move, 10.5f }, + { ai_move, 4.3f }, + { ai_move, 4.7f, monster_duck_up }, + { ai_move, 1.4f }, + { ai_move }, + { ai_move, -3.2f }, + { ai_move, 2.3f }, + { ai_move, -4.4f }, + + { ai_move, -4.4f }, + { ai_move, -2.4f } +}; +MMOVE_T(guncmdr_move_pain6) = { FRAME_c_pain601, FRAME_c_pain632, guncmdr_frames_pain6, guncmdr_run }; + +mframe_t guncmdr_frames_pain7[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(guncmdr_move_pain7) = { FRAME_c_pain701, FRAME_c_pain714, guncmdr_frames_pain7, guncmdr_run }; + +extern const mmove_t guncmdr_move_jump; +extern const mmove_t guncmdr_move_jump2; +extern const mmove_t guncmdr_move_duck_attack; + +bool guncmdr_sidestep(edict_t *self); + +PAIN(guncmdr_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void +{ + monster_done_dodge(self); + + if (self->monsterinfo.active_move == &guncmdr_move_jump || + self->monsterinfo.active_move == &guncmdr_move_jump2 || + self->monsterinfo.active_move == &guncmdr_move_duck_attack) + return; + + if (level.time < self->pain_debounce_time) + { + if (frandom() < 0.3) + self->monsterinfo.dodge(self, other, FRAME_TIME_S, nullptr, false); + + return; + } + + self->pain_debounce_time = level.time + 3_sec; + + if (brandom()) + gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + + if (!M_ShouldReactToPain(self, mod)) + { + if (frandom() < 0.3) + self->monsterinfo.dodge(self, other, FRAME_TIME_S, nullptr, false); + + return; // no pain anims in nightmare + } + + vec3_t forward; + AngleVectors(self->s.angles, forward, nullptr, nullptr); + + vec3_t dif = (other->s.origin - self->s.origin); + dif.z = 0; + dif.normalize(); + + // small pain + if (damage < 35) + { + int r = irandom(0, 4); + + if (r == 0) + M_SetAnimation(self, &guncmdr_move_pain3); + else if (r == 1) + M_SetAnimation(self, &guncmdr_move_pain2); + else if (r == 2) + M_SetAnimation(self, &guncmdr_move_pain1); + else + M_SetAnimation(self, &guncmdr_move_pain7); + } + // large pain from behind (aka Paril) + else if (dif.dot(forward) < -0.40f) + { + M_SetAnimation(self, &guncmdr_move_pain6); + + self->pain_debounce_time += 1.5_sec; + } + else + { + if (brandom()) + M_SetAnimation(self, &guncmdr_move_pain4); + else + M_SetAnimation(self, &guncmdr_move_pain5); + + self->pain_debounce_time += 1.5_sec; + } + + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + + // PMM - clear duck flag + if (self->monsterinfo.aiflags & AI_DUCKED) + monster_duck_up(self); +} + +MONSTERINFO_SETSKIN(guncmdr_setskin) (edict_t *self) -> void +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum |= 1; + else + self->s.skinnum &= ~1; +} + +mframe_t guncmdr_frames_death3[] = { + { ai_move, 20.f }, + { ai_move, 10.f }, + { ai_move, 10.f, [](edict_t *self) { monster_footstep(self); guncmdr_shrink(self); } }, + { ai_move, 0.f, monster_footstep }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move } +}; +MMOVE_T(guncmdr_move_death3) = { FRAME_c_death301, FRAME_c_death321, guncmdr_frames_death3, guncmdr_dead }; + +mframe_t guncmdr_frames_death7[] = { + { ai_move, 30.f }, + { ai_move, 20.f }, + { ai_move, 16.f, [](edict_t *self) { monster_footstep(self); guncmdr_shrink(self); } }, + { ai_move, 5.f, monster_footstep }, + { ai_move, -6.f }, + { ai_move, -7.f, monster_footstep }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0.f, monster_footstep }, + { ai_move }, + { ai_move }, + { ai_move, 0.f, monster_footstep }, + { ai_move }, + { ai_move }, +}; +MMOVE_T(guncmdr_move_death7) = { FRAME_c_death701, FRAME_c_death730, guncmdr_frames_death7, guncmdr_dead }; + +mframe_t guncmdr_frames_death4[] = { + { ai_move, -20.f }, + { ai_move, -16.f }, + { ai_move, -26.f, [](edict_t *self) { monster_footstep(self); guncmdr_shrink(self); } }, + { ai_move, 0.f, monster_footstep }, + { ai_move, -12.f }, + { ai_move, 16.f }, + { ai_move, 9.2f }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(guncmdr_move_death4) = { FRAME_c_death401, FRAME_c_death436, guncmdr_frames_death4, guncmdr_dead }; + +mframe_t guncmdr_frames_death5[] = { + { ai_move, -14.f }, + { ai_move, -2.7f }, + { ai_move, -2.5f }, + { ai_move, -4.6f, monster_footstep }, + { ai_move, -4.0f, monster_footstep }, + { ai_move, -1.5f }, + { ai_move, 2.3f }, + { ai_move, 2.5f }, + { ai_move }, + { ai_move }, + + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 3.5f }, + { ai_move, 12.9f, monster_footstep }, + { ai_move, 3.8f }, + { ai_move }, + { ai_move }, + { ai_move }, + + { ai_move, -2.1f }, + { ai_move, -1.3f }, + { ai_move }, + { ai_move }, + { ai_move, 3.4f }, + { ai_move, 5.7f }, + { ai_move, 11.2f }, + { ai_move, 0, monster_footstep } +}; +MMOVE_T(guncmdr_move_death5) = { FRAME_c_death501, FRAME_c_death528, guncmdr_frames_death5, guncmdr_dead }; + +DIE(guncmdr_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void +{ + // check for gib + if (M_CheckGib(self, mod)) + { + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); + + const char *head_gib = (self->monsterinfo.active_move != &guncmdr_move_death5) ? "models/objects/gibs/sm_meat/tris.md2" : "models/monsters/gunner/gibs/head.md2"; + + self->s.skinnum /= 2; + + ThrowGibs(self, damage, { + { 2, "models/objects/gibs/bone/tris.md2" }, + { 2, "models/objects/gibs/sm_meat/tris.md2" }, + { 1, "models/objects/gibs/gear/tris.md2" }, + { "models/monsters/gunner/gibs/chest.md2", GIB_SKINNED }, + { "models/monsters/gunner/gibs/garm.md2", GIB_SKINNED | GIB_UPRIGHT }, + { "models/monsters/gunner/gibs/gun.md2", GIB_SKINNED | GIB_UPRIGHT }, + { "models/monsters/gunner/gibs/foot.md2", GIB_SKINNED }, + { head_gib, GIB_SKINNED | GIB_HEAD } + }); + self->deadflag = true; + return; + } + + if (self->deadflag) + return; + + // regular death + gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + self->deadflag = true; + self->takedamage = true; + + // these animations cleanly transitions to death, so just keep going + if (self->monsterinfo.active_move == &guncmdr_move_pain5 && + self->s.frame < FRAME_c_pain508) + return; + else if (self->monsterinfo.active_move == &guncmdr_move_pain6 && + self->s.frame < FRAME_c_pain607) + return; + + vec3_t forward; + AngleVectors(self->s.angles, forward, nullptr, nullptr); + + vec3_t dif = (inflictor->s.origin - self->s.origin); + dif.z = 0; + dif.normalize(); + + // off with da head + if (fabsf((self->s.origin[2] + self->viewheight) - point[2]) <= 4 && + self->velocity.z < 65.f) + { + M_SetAnimation(self, &guncmdr_move_death5); + + edict_t *head = ThrowGib(self, "models/monsters/gunner/gibs/head.md2", damage, GIB_NONE, self->s.scale); + + if (head) + { + head->s.angles = self->s.angles; + head->s.origin = self->s.origin + vec3_t{0, 0, 24.f}; + vec3_t headDir = (self->s.origin - inflictor->s.origin); + head->velocity = headDir / headDir.length() * 100.0f; + head->velocity[2] = 200.0f; + head->avelocity *= 0.15f; + gi.linkentity(head); + } + } + // damage came from behind; use backwards death + else if (dif.dot(forward) < -0.40f) + { + int r = irandom(0, self->monsterinfo.active_move == &guncmdr_move_pain6 ? 2 : 3); + + if (r == 0) + M_SetAnimation(self, &guncmdr_move_death3); + else if (r == 1) + M_SetAnimation(self, &guncmdr_move_death7); + else if (r == 2) + M_SetAnimation(self, &guncmdr_move_pain6); + } + else + { + int r = irandom(0, self->monsterinfo.active_move == &guncmdr_move_pain5 ? 1 : 2); + + if (r == 0) + M_SetAnimation(self, &guncmdr_move_death4); + else + M_SetAnimation(self, &guncmdr_move_pain5); + } +} + +void guncmdr_opengun(edict_t *self) +{ + gi.sound(self, CHAN_VOICE, sound_open, 1, ATTN_IDLE, 0); +} + +void GunnerCmdrFire(edict_t *self) +{ + vec3_t start; + vec3_t forward, right; + vec3_t aim; + monster_muzzleflash_id_t flash_number; + + if (!self->enemy || !self->enemy->inuse) // PGM + return; // PGM + + if (self->s.frame >= FRAME_c_attack401 && self->s.frame <= FRAME_c_attack505) + flash_number = MZ2_GUNCMDR_CHAINGUN_2; + else + flash_number = MZ2_GUNCMDR_CHAINGUN_1; + + AngleVectors(self->s.angles, forward, right, nullptr); + start = M_ProjectFlashSource(self, monster_flash_offset[flash_number], forward, right); + PredictAim(self, self->enemy, start, 800, false, frandom() * 0.3f, &aim, nullptr); + for (int i = 0; i < 3; i++) + aim[i] += crandom_open() * 0.025f; + monster_fire_flechette(self, start, aim, 4, 800, flash_number); +} + +mframe_t guncmdr_frames_attack_chain[] = { + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, guncmdr_opengun }, + { ai_charge } +}; +MMOVE_T(guncmdr_move_attack_chain) = { FRAME_c_attack101, FRAME_c_attack106, guncmdr_frames_attack_chain, guncmdr_fire_chain }; + +mframe_t guncmdr_frames_fire_chain[] = { + { ai_charge, 0, GunnerCmdrFire }, + { ai_charge, 0, GunnerCmdrFire }, + { ai_charge, 0, GunnerCmdrFire }, + { ai_charge, 0, GunnerCmdrFire }, + { ai_charge, 0, GunnerCmdrFire }, + { ai_charge, 0, GunnerCmdrFire } +}; +MMOVE_T(guncmdr_move_fire_chain) = { FRAME_c_attack107, FRAME_c_attack112, guncmdr_frames_fire_chain, guncmdr_refire_chain }; + +mframe_t guncmdr_frames_fire_chain_run[] = { + { ai_charge, 15.f, GunnerCmdrFire }, + { ai_charge, 16.f, GunnerCmdrFire }, + { ai_charge, 20.f, GunnerCmdrFire }, + { ai_charge, 18.f, GunnerCmdrFire }, + { ai_charge, 24.f, GunnerCmdrFire }, + { ai_charge, 13.5f, GunnerCmdrFire } +}; +MMOVE_T(guncmdr_move_fire_chain_run) = { FRAME_c_run201, FRAME_c_run206, guncmdr_frames_fire_chain_run, guncmdr_refire_chain }; + +mframe_t guncmdr_frames_fire_chain_dodge_right[] = { + { ai_charge, 5.1f * 2.0f, GunnerCmdrFire }, + { ai_charge, 9.0f * 2.0f, GunnerCmdrFire }, + { ai_charge, 3.5f * 2.0f, GunnerCmdrFire }, + { ai_charge, 3.6f * 2.0f, GunnerCmdrFire }, + { ai_charge, -1.0f * 2.0f, GunnerCmdrFire } +}; +MMOVE_T(guncmdr_move_fire_chain_dodge_right) = { FRAME_c_attack401, FRAME_c_attack405, guncmdr_frames_fire_chain_dodge_right, guncmdr_refire_chain }; + +mframe_t guncmdr_frames_fire_chain_dodge_left[] = { + { ai_charge, 5.1f * 2.0f, GunnerCmdrFire }, + { ai_charge, 9.0f * 2.0f, GunnerCmdrFire }, + { ai_charge, 3.5f * 2.0f, GunnerCmdrFire }, + { ai_charge, 3.6f * 2.0f, GunnerCmdrFire }, + { ai_charge, -1.0f * 2.0f, GunnerCmdrFire } +}; +MMOVE_T(guncmdr_move_fire_chain_dodge_left) = { FRAME_c_attack501, FRAME_c_attack505, guncmdr_frames_fire_chain_dodge_left, guncmdr_refire_chain }; + +mframe_t guncmdr_frames_endfire_chain[] = { + { ai_charge }, + { ai_charge }, + { ai_charge, 0, guncmdr_opengun }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge } +}; +MMOVE_T(guncmdr_move_endfire_chain) = { FRAME_c_attack118, FRAME_c_attack124, guncmdr_frames_endfire_chain, guncmdr_run }; + +constexpr float MORTAR_SPEED = 850.f; +constexpr float GRENADE_SPEED = 600.f; + +void GunnerCmdrGrenade(edict_t *self) +{ + vec3_t start; + vec3_t forward, right, up; + vec3_t aim; + monster_muzzleflash_id_t flash_number; + float spread; + float pitch = 0; + // PMM + vec3_t target; + bool blindfire = false; + + if (!self->enemy || !self->enemy->inuse) // PGM + return; // PGM + + // pmm + if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) + blindfire = true; + + if (self->s.frame == FRAME_c_attack205) + { + spread = -0.1f; + flash_number = MZ2_GUNCMDR_GRENADE_MORTAR_1; + } + else if (self->s.frame == FRAME_c_attack208) + { + spread = 0.f; + flash_number = MZ2_GUNCMDR_GRENADE_MORTAR_2; + } + else if (self->s.frame == FRAME_c_attack211) + { + spread = 0.1f; + flash_number = MZ2_GUNCMDR_GRENADE_MORTAR_3; + } + else if (self->s.frame == FRAME_c_attack304) + { + spread = -0.1f; + flash_number = MZ2_GUNCMDR_GRENADE_FRONT_1; + } + else if (self->s.frame == FRAME_c_attack307) + { + spread = 0.f; + flash_number = MZ2_GUNCMDR_GRENADE_FRONT_2; + } + else if (self->s.frame == FRAME_c_attack310) + { + spread = 0.1f; + flash_number = MZ2_GUNCMDR_GRENADE_FRONT_3; + } + else if (self->s.frame == FRAME_c_attack911) + { + spread = 0.25f; + flash_number = MZ2_GUNCMDR_GRENADE_CROUCH_1; + } + else if (self->s.frame == FRAME_c_attack912) + { + spread = 0.f; + flash_number = MZ2_GUNCMDR_GRENADE_CROUCH_2; + } + else if (self->s.frame == FRAME_c_attack913) + { + spread = -0.25f; + flash_number = MZ2_GUNCMDR_GRENADE_CROUCH_3; + } + + // pmm + // if we're shooting blind and we still can't see our enemy + if ((blindfire) && (!visible(self, self->enemy))) + { + // and we have a valid blind_fire_target + if (!self->monsterinfo.blind_fire_target) + return; + + target = self->monsterinfo.blind_fire_target; + } + else + target = self->enemy->s.origin; + // pmm + + AngleVectors(self->s.angles, forward, right, up); // PGM + start = M_ProjectFlashSource(self, monster_flash_offset[flash_number], forward, right); + + // PGM + if (self->enemy && !(flash_number >= MZ2_GUNCMDR_GRENADE_CROUCH_1 && flash_number <= MZ2_GUNCMDR_GRENADE_CROUCH_3)) + { + float dist; + + aim = target - self->s.origin; + dist = aim.length(); + + // aim up if they're on the same level as me and far away. + if ((dist > 512) && (aim[2] < 64) && (aim[2] > -64)) + { + aim[2] += (dist - 512); + } + + aim.normalize(); + pitch = aim[2]; + if (pitch > 0.4f) + pitch = 0.4f; + else if (pitch < -0.5f) + pitch = -0.5f; + + if ((self->enemy->absmin.z - self->absmax.z) > 16.f && flash_number >= MZ2_GUNCMDR_GRENADE_MORTAR_1 && flash_number <= MZ2_GUNCMDR_GRENADE_MORTAR_3) + pitch += 0.5f; + } + // PGM + + if (flash_number >= MZ2_GUNCMDR_GRENADE_FRONT_1 && flash_number <= MZ2_GUNCMDR_GRENADE_FRONT_3) + pitch -= 0.05f; + + if (!(flash_number >= MZ2_GUNCMDR_GRENADE_CROUCH_1 && flash_number <= MZ2_GUNCMDR_GRENADE_CROUCH_3)) + { + aim = forward + (right * spread); + aim += (up * pitch); + aim.normalize(); + } + else + { + PredictAim(self, self->enemy, start, 800, false, 0.f, &aim, nullptr); + aim += right * spread; + aim.normalize(); + } + + if (flash_number >= MZ2_GUNCMDR_GRENADE_CROUCH_1 && flash_number <= MZ2_GUNCMDR_GRENADE_CROUCH_3) + { + constexpr float inner_spread = 0.125f; + + for (int32_t i = 0; i < 3; i++) + fire_ionripper(self, start, aim + (right * (-(inner_spread * 2) + (inner_spread * (i + 1)))), 15, 800, EF_IONRIPPER); + + monster_muzzleflash(self, start, flash_number); + } + else + { + // mortar fires farther + float speed; + + if (flash_number >= MZ2_GUNCMDR_GRENADE_MORTAR_1 && flash_number <= MZ2_GUNCMDR_GRENADE_MORTAR_3) + speed = MORTAR_SPEED; + else + speed = GRENADE_SPEED; + + // try search for best pitch + if (M_CalculatePitchToFire(self, target, start, aim, speed, 2.5f, (flash_number >= MZ2_GUNCMDR_GRENADE_MORTAR_1 && flash_number <= MZ2_GUNCMDR_GRENADE_MORTAR_3))) + monster_fire_grenade(self, start, aim, 50, speed, flash_number, (crandom_open() * 10.0f), frandom() * 10.f); + else + // normal shot + monster_fire_grenade(self, start, aim, 50, speed, flash_number, (crandom_open() * 10.0f), 200.f + (crandom_open() * 10.0f)); + } +} + +mframe_t guncmdr_frames_attack_mortar[] = { + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, GunnerCmdrGrenade }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, GunnerCmdrGrenade }, + { ai_charge }, + { ai_charge }, + + { ai_charge, 0, GunnerCmdrGrenade }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, monster_duck_up }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge } +}; +MMOVE_T(guncmdr_move_attack_mortar) = { FRAME_c_attack201, FRAME_c_attack221, guncmdr_frames_attack_mortar, guncmdr_run }; + +void guncmdr_grenade_mortar_resume(edict_t *self) +{ + M_SetAnimation(self, &guncmdr_move_attack_mortar); + self->monsterinfo.attack_state = AS_STRAIGHT; + self->s.frame = self->count; +} + +mframe_t guncmdr_frames_attack_mortar_dodge[] = { + { ai_charge, 11.f }, + { ai_charge, 12.f }, + { ai_charge, 16.f }, + { ai_charge, 16.f }, + { ai_charge, 12.f }, + { ai_charge, 11.f } +}; +MMOVE_T(guncmdr_move_attack_mortar_dodge) = { FRAME_c_duckstep01, FRAME_c_duckstep06, guncmdr_frames_attack_mortar_dodge, guncmdr_grenade_mortar_resume }; + +mframe_t guncmdr_frames_attack_back[] = { + //{ ai_charge }, + { ai_charge, -2.f }, + { ai_charge, -1.5f }, + { ai_charge, -0.5f, GunnerCmdrGrenade }, + { ai_charge, -6.0f }, + { ai_charge, -4.f }, + { ai_charge, -2.5f, GunnerCmdrGrenade }, + { ai_charge, -7.0f }, + { ai_charge, -3.5f }, + { ai_charge, -1.1f, GunnerCmdrGrenade }, + + { ai_charge, -4.6f }, + { ai_charge, 1.9f }, + { ai_charge, 1.0f }, + { ai_charge, -4.5f }, + { ai_charge, 3.2f }, + { ai_charge, 4.4f }, + { ai_charge, -6.5f }, + { ai_charge, -6.1f }, + { ai_charge, 3.0f }, + { ai_charge, -0.7f }, + { ai_charge, -1.0f } +}; +MMOVE_T(guncmdr_move_attack_grenade_back) = { FRAME_c_attack302, FRAME_c_attack321, guncmdr_frames_attack_back, guncmdr_run }; + +void guncmdr_grenade_back_dodge_resume(edict_t *self) +{ + M_SetAnimation(self, &guncmdr_move_attack_grenade_back); + self->monsterinfo.attack_state = AS_STRAIGHT; + self->s.frame = self->count; +} + +mframe_t guncmdr_frames_attack_grenade_back_dodge_right[] = { + { ai_charge, 5.1f * 2.0f }, + { ai_charge, 9.0f * 2.0f }, + { ai_charge, 3.5f * 2.0f }, + { ai_charge, 3.6f * 2.0f }, + { ai_charge, -1.0f * 2.0f } +}; +MMOVE_T(guncmdr_move_attack_grenade_back_dodge_right) = { FRAME_c_attack601, FRAME_c_attack605, guncmdr_frames_attack_grenade_back_dodge_right, guncmdr_grenade_back_dodge_resume }; + +mframe_t guncmdr_frames_attack_grenade_back_dodge_left[] = { + { ai_charge, 5.1f * 2.0f }, + { ai_charge, 9.0f * 2.0f }, + { ai_charge, 3.5f * 2.0f }, + { ai_charge, 3.6f * 2.0f }, + { ai_charge, -1.0f * 2.0f } +}; +MMOVE_T(guncmdr_move_attack_grenade_back_dodge_left) = { FRAME_c_attack701, FRAME_c_attack705, guncmdr_frames_attack_grenade_back_dodge_left, guncmdr_grenade_back_dodge_resume }; + +static void guncmdr_kick_finished(edict_t *self) +{ + self->monsterinfo.melee_debounce_time = level.time + 3_sec; + self->monsterinfo.attack(self); +} + +static void guncmdr_kick(edict_t *self) +{ + if (fire_hit(self, vec3_t { MELEE_DISTANCE, 0.f, -32.f }, 15.f, 400.f)) + { + if (self->enemy && self->enemy->client && self->enemy->velocity.z < 270.f) + self->enemy->velocity.z = 270.f; + } +} + +mframe_t guncmdr_frames_attack_kick[] = { + { ai_charge, -7.7f }, + { ai_charge, -4.9f }, + { ai_charge, 12.6f, guncmdr_kick }, + { ai_charge }, + { ai_charge, -3.0f }, + { ai_charge }, + { ai_charge, -4.1f }, + { ai_charge, 8.6f }, + //{ ai_charge, -3.5f } +}; +MMOVE_T(guncmdr_move_attack_kick) = { FRAME_c_attack801, FRAME_c_attack808, guncmdr_frames_attack_kick, guncmdr_kick_finished }; + +// don't ever try grenades if we get this close +constexpr float RANGE_GRENADE = 100.f; + +// always use mortar at this range +constexpr float RANGE_GRENADE_MORTAR = 525.f; + +// at this range, run towards the enemy +constexpr float RANGE_CHAINGUN_RUN = 400.f; + +MONSTERINFO_ATTACK(guncmdr_attack) (edict_t *self) -> void +{ + monster_done_dodge(self); + + float d = range_to(self, self->enemy); + + vec3_t forward, right, aim; + AngleVectors(self->s.angles, forward, right, nullptr); // PGM + + // always use chaingun on tesla + // kick close enemies + if (!self->bad_area && d < RANGE_MELEE && self->monsterinfo.melee_debounce_time < level.time) + M_SetAnimation(self, &guncmdr_move_attack_kick); + else if (self->bad_area || ((d <= RANGE_GRENADE || brandom()) && M_CheckClearShot(self, monster_flash_offset[MZ2_GUNCMDR_CHAINGUN_1]))) + M_SetAnimation(self, &guncmdr_move_attack_chain); + else if ((d >= RANGE_GRENADE_MORTAR || + fabs(self->absmin.z - self->enemy->absmax.z) > 64.f // enemy is far below or above us, always try mortar + ) && M_CheckClearShot(self, monster_flash_offset[MZ2_GUNCMDR_GRENADE_MORTAR_1]) && + M_CalculatePitchToFire(self, self->enemy->s.origin, M_ProjectFlashSource(self, monster_flash_offset[MZ2_GUNCMDR_GRENADE_MORTAR_1], forward, right), + aim = (self->enemy->s.origin - self->s.origin).normalized(), MORTAR_SPEED, 2.5f, true) + ) + { + M_SetAnimation(self, &guncmdr_move_attack_mortar); + monster_duck_down(self); + } + else if (M_CheckClearShot(self, monster_flash_offset[MZ2_GUNCMDR_GRENADE_FRONT_1]) && !(self->monsterinfo.aiflags & AI_STAND_GROUND) && + M_CalculatePitchToFire(self, self->enemy->s.origin, M_ProjectFlashSource(self, monster_flash_offset[MZ2_GUNCMDR_GRENADE_FRONT_1], forward, right), + aim = (self->enemy->s.origin - self->s.origin).normalized(), GRENADE_SPEED, 2.5f, false)) + M_SetAnimation(self, &guncmdr_move_attack_grenade_back); + else if (self->monsterinfo.aiflags & AI_STAND_GROUND) + M_SetAnimation(self, &guncmdr_move_attack_chain); +} + +void guncmdr_fire_chain(edict_t *self) +{ + if (!(self->monsterinfo.aiflags & AI_STAND_GROUND) && self->enemy && range_to(self, self->enemy) > RANGE_CHAINGUN_RUN && ai_check_move(self, 8.0f)) + M_SetAnimation(self, &guncmdr_move_fire_chain_run); + else + M_SetAnimation(self, &guncmdr_move_fire_chain); +} + +void guncmdr_refire_chain(edict_t *self) +{ + monster_done_dodge(self); + self->monsterinfo.attack_state = AS_STRAIGHT; + + if (self->enemy->health > 0) + if (visible(self, self->enemy)) + if (frandom() <= 0.5f) + { + if (!(self->monsterinfo.aiflags & AI_STAND_GROUND) && self->enemy && range_to(self, self->enemy) > RANGE_CHAINGUN_RUN && ai_check_move(self, 8.0f)) + M_SetAnimation(self, &guncmdr_move_fire_chain_run, false); + else + M_SetAnimation(self, &guncmdr_move_fire_chain, false); + return; + } + M_SetAnimation(self, &guncmdr_move_endfire_chain, false); +} + +//=========== +// PGM +void guncmdr_jump_now(edict_t *self) +{ + vec3_t forward, up; + + AngleVectors(self->s.angles, forward, nullptr, up); + self->velocity += (forward * 100); + self->velocity += (up * 300); +} + +void guncmdr_jump2_now(edict_t *self) +{ + vec3_t forward, up; + + AngleVectors(self->s.angles, forward, nullptr, up); + self->velocity += (forward * 150); + self->velocity += (up * 400); +} + +void guncmdr_jump_wait_land(edict_t *self) +{ + if (self->groundentity == nullptr) + { + self->monsterinfo.nextframe = self->s.frame; + + if (monster_jump_finished(self)) + self->monsterinfo.nextframe = self->s.frame + 1; + } + else + self->monsterinfo.nextframe = self->s.frame + 1; +} + +mframe_t guncmdr_frames_jump[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0, guncmdr_jump_now }, + { ai_move }, + { ai_move }, + { ai_move, 0, guncmdr_jump_wait_land }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(guncmdr_move_jump) = { FRAME_c_jump01, FRAME_c_jump10, guncmdr_frames_jump, guncmdr_run }; + +mframe_t guncmdr_frames_jump2[] = { + { ai_move, -8 }, + { ai_move, -4 }, + { ai_move, -4 }, + { ai_move, 0, guncmdr_jump2_now }, + { ai_move }, + { ai_move }, + { ai_move, 0, guncmdr_jump_wait_land }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(guncmdr_move_jump2) = { FRAME_c_jump01, FRAME_c_jump10, guncmdr_frames_jump2, guncmdr_run }; + +void guncmdr_jump(edict_t *self, blocked_jump_result_t result) +{ + if (!self->enemy) + return; + + monster_done_dodge(self); + + if (result == blocked_jump_result_t::JUMP_JUMP_UP) + M_SetAnimation(self, &guncmdr_move_jump2); + else + M_SetAnimation(self, &guncmdr_move_jump); +} + +void T_SlamRadiusDamage(vec3_t point, edict_t *inflictor, edict_t *attacker, float damage, float kick, edict_t *ignore, float radius, mod_t mod); + +static void GunnerCmdrCounter(edict_t *self) +{ + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_BERSERK_SLAM); + vec3_t f, r, start; + AngleVectors(self->s.angles, f, r, nullptr); + start = M_ProjectFlashSource(self, { 20.f, 0.f, 14.f }, f, r); + trace_t tr = gi.traceline(self->s.origin, start, self, MASK_SOLID); + gi.WritePosition(tr.endpos); + gi.WriteDir(f); + gi.multicast(tr.endpos, MULTICAST_PHS, false); + + T_SlamRadiusDamage(tr.endpos, self, self, 15, 250.f, self, 200.f, MOD_UNKNOWN); +} + +//=========== +// PGM +mframe_t guncmdr_frames_duck_attack[] = { + { ai_move, 3.6f }, + { ai_move, 5.6f, monster_duck_down }, + { ai_move, 8.4f }, + { ai_move, 2.0f, monster_duck_hold }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + + //{ ai_charge, 0, GunnerCmdrGrenade }, + //{ ai_charge, 9.5f, GunnerCmdrGrenade }, + //{ ai_charge, -1.5f, GunnerCmdrGrenade }, + + { ai_charge, 0 }, + { ai_charge, 9.5f, GunnerCmdrCounter }, + { ai_charge, -1.5f }, + { ai_charge }, + { ai_charge, 0, monster_duck_up }, + { ai_charge }, + { ai_charge, 11.f }, + { ai_charge, 2.0f }, + { ai_charge, 5.6f } +}; +MMOVE_T(guncmdr_move_duck_attack) = { FRAME_c_attack901, FRAME_c_attack919, guncmdr_frames_duck_attack, guncmdr_run }; + +MONSTERINFO_DUCK(guncmdr_duck) (edict_t *self, gtime_t eta) -> bool +{ + if ((self->monsterinfo.active_move == &guncmdr_move_jump2) || + (self->monsterinfo.active_move == &guncmdr_move_jump)) + { + return false; + } + + if ((self->monsterinfo.active_move == &guncmdr_move_fire_chain_dodge_left) || + (self->monsterinfo.active_move == &guncmdr_move_fire_chain_dodge_right) || + (self->monsterinfo.active_move == &guncmdr_move_attack_grenade_back_dodge_left) || + (self->monsterinfo.active_move == &guncmdr_move_attack_grenade_back_dodge_right) || + (self->monsterinfo.active_move == &guncmdr_move_attack_mortar_dodge)) + { + // if we're dodging, don't duck + self->monsterinfo.unduck(self); + return false; + } + + M_SetAnimation(self, &guncmdr_move_duck_attack); + + return true; +} + +MONSTERINFO_SIDESTEP(guncmdr_sidestep) (edict_t *self) -> bool +{ + // use special dodge during the main firing anim + if (self->monsterinfo.active_move == &guncmdr_move_fire_chain || + self->monsterinfo.active_move == &guncmdr_move_fire_chain_run) + { + M_SetAnimation(self, !self->monsterinfo.lefty ? &guncmdr_move_fire_chain_dodge_right : &guncmdr_move_fire_chain_dodge_left, false); + return true; + } + + // for backwards mortar, back up where we are in the animation and do a quick dodge + if (self->monsterinfo.active_move == &guncmdr_move_attack_grenade_back) + { + self->count = self->s.frame; + M_SetAnimation(self, !self->monsterinfo.lefty ? &guncmdr_move_attack_grenade_back_dodge_right : &guncmdr_move_attack_grenade_back_dodge_left, false); + return true; + } + + // use crouch-move for mortar dodge + if (self->monsterinfo.active_move == &guncmdr_move_attack_mortar) + { + self->count = self->s.frame; + M_SetAnimation(self, &guncmdr_move_attack_mortar_dodge, false); + return true; + } + + // regular sidestep during run + if (self->monsterinfo.active_move == &guncmdr_move_run) + { + M_SetAnimation(self, &guncmdr_move_run, true); + return true; + } + + return false; +} + +MONSTERINFO_BLOCKED(guncmdr_blocked) (edict_t *self, float dist) -> bool +{ + if (blocked_checkplat(self, dist)) + return true; + + if (auto result = blocked_checkjump(self, dist); result != blocked_jump_result_t::NO_JUMP) + { + if (result != blocked_jump_result_t::JUMP_TURN) + guncmdr_jump(self, result); + + return true; + } + + return false; +} +// PGM +//=========== + +/*QUAKED monster_guncmdr (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight NoJumping +model="models/monsters/guncmdr/tris.md2" +*/ +void SP_monster_guncmdr(edict_t *self) +{ + const spawn_temp_t &st = ED_GetSpawnTemp(); + + if ( !M_AllowSpawn( self ) ) { + G_FreeEdict( self ); + return; + } + + sound_death.assign("guncmdr/gcdrdeath1.wav"); + sound_pain.assign("guncmdr/gcdrpain2.wav"); + sound_pain2.assign("guncmdr/gcdrpain1.wav"); + sound_idle.assign("guncmdr/gcdridle1.wav"); + sound_open.assign("guncmdr/gcdratck1.wav"); + sound_search.assign("guncmdr/gcdrsrch1.wav"); + sound_sight.assign("guncmdr/sight1.wav"); + + gi.soundindex("guncmdr/gcdratck2.wav"); + gi.soundindex("guncmdr/gcdratck3.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/gunner/tris.md2"); + + gi.modelindex("models/monsters/gunner/gibs/chest.md2"); + gi.modelindex("models/monsters/gunner/gibs/foot.md2"); + gi.modelindex("models/monsters/gunner/gibs/garm.md2"); + gi.modelindex("models/monsters/gunner/gibs/gun.md2"); + gi.modelindex("models/monsters/gunner/gibs/head.md2"); + + self->s.scale = 1.25f; + self->mins = vec3_t { -16, -16, -24 }; + self->maxs = vec3_t { 16, 16, 36 }; + self->s.skinnum = 2; + + self->health = 325 * st.health_multiplier; + self->gib_health = -175; + self->mass = 255; + + self->pain = guncmdr_pain; + self->die = guncmdr_die; + + self->monsterinfo.stand = guncmdr_stand; + self->monsterinfo.walk = guncmdr_walk; + self->monsterinfo.run = guncmdr_run; + // pmm + self->monsterinfo.dodge = M_MonsterDodge; + self->monsterinfo.duck = guncmdr_duck; + self->monsterinfo.unduck = monster_duck_up; + self->monsterinfo.sidestep = guncmdr_sidestep; + self->monsterinfo.blocked = guncmdr_blocked; // PGM + // pmm + self->monsterinfo.attack = guncmdr_attack; + self->monsterinfo.melee = nullptr; + self->monsterinfo.sight = guncmdr_sight; + self->monsterinfo.search = guncmdr_search; + self->monsterinfo.setskin = guncmdr_setskin; + + gi.linkentity(self); + + M_SetAnimation(self, &guncmdr_move_stand); + self->monsterinfo.scale = MODEL_SCALE; + + if (!st.was_key_specified("power_armor_power")) + self->monsterinfo.power_armor_power = 200; + if (!st.was_key_specified("power_armor_type")) + self->monsterinfo.power_armor_type = IT_ITEM_POWER_SHIELD; + + // PMM + //self->monsterinfo.blindfire = true; + self->monsterinfo.can_jump = !self->spawnflags.has(SPAWNFLAG_GUNCMDR_NOJUMPING); + self->monsterinfo.drop_height = 192; + self->monsterinfo.jump_height = 40; + + walkmonster_start(self); +} diff --git a/to add/m_gunner.h b/to add/m_gunner.h new file mode 100644 index 00000000..e1a3e9fc --- /dev/null +++ b/to add/m_gunner.h @@ -0,0 +1,809 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// E:\G Drive\md2f\quake2\baseq2\models/monsters/gunner + +// This file generated by qdata - Do NOT Modify + +enum { + FRAME_stand01, + FRAME_stand02, + FRAME_stand03, + FRAME_stand04, + FRAME_stand05, + FRAME_stand06, + FRAME_stand07, + FRAME_stand08, + FRAME_stand09, + FRAME_stand10, + FRAME_stand11, + FRAME_stand12, + FRAME_stand13, + FRAME_stand14, + FRAME_stand15, + FRAME_stand16, + FRAME_stand17, + FRAME_stand18, + FRAME_stand19, + FRAME_stand20, + FRAME_stand21, + FRAME_stand22, + FRAME_stand23, + FRAME_stand24, + FRAME_stand25, + FRAME_stand26, + FRAME_stand27, + FRAME_stand28, + FRAME_stand29, + FRAME_stand30, + FRAME_stand31, + FRAME_stand32, + FRAME_stand33, + FRAME_stand34, + FRAME_stand35, + FRAME_stand36, + FRAME_stand37, + FRAME_stand38, + FRAME_stand39, + FRAME_stand40, + FRAME_stand41, + FRAME_stand42, + FRAME_stand43, + FRAME_stand44, + FRAME_stand45, + FRAME_stand46, + FRAME_stand47, + FRAME_stand48, + FRAME_stand49, + FRAME_stand50, + FRAME_stand51, + FRAME_stand52, + FRAME_stand53, + FRAME_stand54, + FRAME_stand55, + FRAME_stand56, + FRAME_stand57, + FRAME_stand58, + FRAME_stand59, + FRAME_stand60, + FRAME_stand61, + FRAME_stand62, + FRAME_stand63, + FRAME_stand64, + FRAME_stand65, + FRAME_stand66, + FRAME_stand67, + FRAME_stand68, + FRAME_stand69, + FRAME_stand70, + FRAME_walk01, + FRAME_walk02, + FRAME_walk03, + FRAME_walk04, + FRAME_walk05, + FRAME_walk06, + FRAME_walk07, + FRAME_walk08, + FRAME_walk09, + FRAME_walk10, + FRAME_walk11, + FRAME_walk12, + FRAME_walk13, + FRAME_walk14, + FRAME_walk15, + FRAME_walk16, + FRAME_walk17, + FRAME_walk18, + FRAME_walk19, + FRAME_walk20, + FRAME_walk21, + FRAME_walk22, + FRAME_walk23, + FRAME_walk24, + FRAME_run01, + FRAME_run02, + FRAME_run03, + FRAME_run04, + FRAME_run05, + FRAME_run06, + FRAME_run07, + FRAME_run08, + FRAME_runs01, + FRAME_runs02, + FRAME_runs03, + FRAME_runs04, + FRAME_runs05, + FRAME_runs06, + FRAME_attak101, + FRAME_attak102, + FRAME_attak103, + FRAME_attak104, + FRAME_attak105, + FRAME_attak106, + FRAME_attak107, + FRAME_attak108, + FRAME_attak109, + FRAME_attak110, + FRAME_attak111, + FRAME_attak112, + FRAME_attak113, + FRAME_attak114, + FRAME_attak115, + FRAME_attak116, + FRAME_attak117, + FRAME_attak118, + FRAME_attak119, + FRAME_attak120, + FRAME_attak121, + FRAME_attak201, + FRAME_attak202, + FRAME_attak203, + FRAME_attak204, + FRAME_attak205, + FRAME_attak206, + FRAME_attak207, + FRAME_attak208, + FRAME_attak209, + FRAME_attak210, + FRAME_attak211, + FRAME_attak212, + FRAME_attak213, + FRAME_attak214, + FRAME_attak215, + FRAME_attak216, + FRAME_attak217, + FRAME_attak218, + FRAME_attak219, + FRAME_attak220, + FRAME_attak221, + FRAME_attak222, + FRAME_attak223, + FRAME_attak224, + FRAME_attak225, + FRAME_attak226, + FRAME_attak227, + FRAME_attak228, + FRAME_attak229, + FRAME_attak230, + FRAME_pain101, + FRAME_pain102, + FRAME_pain103, + FRAME_pain104, + FRAME_pain105, + FRAME_pain106, + FRAME_pain107, + FRAME_pain108, + FRAME_pain109, + FRAME_pain110, + FRAME_pain111, + FRAME_pain112, + FRAME_pain113, + FRAME_pain114, + FRAME_pain115, + FRAME_pain116, + FRAME_pain117, + FRAME_pain118, + FRAME_pain201, + FRAME_pain202, + FRAME_pain203, + FRAME_pain204, + FRAME_pain205, + FRAME_pain206, + FRAME_pain207, + FRAME_pain208, + FRAME_pain301, + FRAME_pain302, + FRAME_pain303, + FRAME_pain304, + FRAME_pain305, + FRAME_death01, + FRAME_death02, + FRAME_death03, + FRAME_death04, + FRAME_death05, + FRAME_death06, + FRAME_death07, + FRAME_death08, + FRAME_death09, + FRAME_death10, + FRAME_death11, + FRAME_duck01, + FRAME_duck02, + FRAME_duck03, + FRAME_duck04, + FRAME_duck05, + FRAME_duck06, + FRAME_duck07, + FRAME_duck08, + FRAME_jump01, + FRAME_jump02, + FRAME_jump03, + FRAME_jump04, + FRAME_jump05, + FRAME_jump06, + FRAME_jump07, + FRAME_jump08, + FRAME_jump09, + FRAME_jump10, + FRAME_shield01, + FRAME_shield02, + FRAME_shield03, + FRAME_shield04, + FRAME_shield05, + FRAME_shield06, + FRAME_attak301, + FRAME_attak302, + FRAME_attak303, + FRAME_attak304, + FRAME_attak305, + FRAME_attak306, + FRAME_attak307, + FRAME_attak308, + FRAME_attak309, + FRAME_attak310, + FRAME_attak311, + FRAME_attak312, + FRAME_attak313, + FRAME_attak314, + FRAME_attak315, + FRAME_attak316, + FRAME_attak317, + FRAME_attak318, + FRAME_attak319, + FRAME_attak320, + FRAME_attak321, + FRAME_attak322, + FRAME_attak323, + FRAME_attak324, + FRAME_c_stand101, + FRAME_c_stand102, + FRAME_c_stand103, + FRAME_c_stand104, + FRAME_c_stand105, + FRAME_c_stand106, + FRAME_c_stand107, + FRAME_c_stand108, + FRAME_c_stand109, + FRAME_c_stand110, + FRAME_c_stand111, + FRAME_c_stand112, + FRAME_c_stand113, + FRAME_c_stand114, + FRAME_c_stand115, + FRAME_c_stand116, + FRAME_c_stand117, + FRAME_c_stand118, + FRAME_c_stand119, + FRAME_c_stand120, + FRAME_c_stand121, + FRAME_c_stand122, + FRAME_c_stand123, + FRAME_c_stand124, + FRAME_c_stand125, + FRAME_c_stand126, + FRAME_c_stand127, + FRAME_c_stand128, + FRAME_c_stand129, + FRAME_c_stand130, + FRAME_c_stand131, + FRAME_c_stand132, + FRAME_c_stand133, + FRAME_c_stand134, + FRAME_c_stand135, + FRAME_c_stand136, + FRAME_c_stand137, + FRAME_c_stand138, + FRAME_c_stand139, + FRAME_c_stand140, + FRAME_c_stand201, + FRAME_c_stand202, + FRAME_c_stand203, + FRAME_c_stand204, + FRAME_c_stand205, + FRAME_c_stand206, + FRAME_c_stand207, + FRAME_c_stand208, + FRAME_c_stand209, + FRAME_c_stand210, + FRAME_c_stand211, + FRAME_c_stand212, + FRAME_c_stand213, + FRAME_c_stand214, + FRAME_c_stand215, + FRAME_c_stand216, + FRAME_c_stand217, + FRAME_c_stand218, + FRAME_c_stand219, + FRAME_c_stand220, + FRAME_c_stand221, + FRAME_c_stand222, + FRAME_c_stand223, + FRAME_c_stand224, + FRAME_c_stand225, + FRAME_c_stand226, + FRAME_c_stand227, + FRAME_c_stand228, + FRAME_c_stand229, + FRAME_c_stand230, + FRAME_c_stand231, + FRAME_c_stand232, + FRAME_c_stand233, + FRAME_c_stand234, + FRAME_c_stand235, + FRAME_c_stand236, + FRAME_c_stand237, + FRAME_c_stand238, + FRAME_c_stand239, + FRAME_c_stand240, + FRAME_c_stand241, + FRAME_c_stand242, + FRAME_c_stand243, + FRAME_c_stand244, + FRAME_c_stand245, + FRAME_c_stand246, + FRAME_c_stand247, + FRAME_c_stand248, + FRAME_c_stand249, + FRAME_c_stand250, + FRAME_c_stand251, + FRAME_c_stand252, + FRAME_c_stand253, + FRAME_c_stand254, + FRAME_c_attack101, + FRAME_c_attack102, + FRAME_c_attack103, + FRAME_c_attack104, + FRAME_c_attack105, + FRAME_c_attack106, + FRAME_c_attack107, + FRAME_c_attack108, + FRAME_c_attack109, + FRAME_c_attack110, + FRAME_c_attack111, + FRAME_c_attack112, + FRAME_c_attack113, + FRAME_c_attack114, + FRAME_c_attack115, + FRAME_c_attack116, + FRAME_c_attack117, + FRAME_c_attack118, + FRAME_c_attack119, + FRAME_c_attack120, + FRAME_c_attack121, + FRAME_c_attack122, + FRAME_c_attack123, + FRAME_c_attack124, + FRAME_c_jump01, + FRAME_c_jump02, + FRAME_c_jump03, + FRAME_c_jump04, + FRAME_c_jump05, + FRAME_c_jump06, + FRAME_c_jump07, + FRAME_c_jump08, + FRAME_c_jump09, + FRAME_c_jump10, + FRAME_c_attack201, + FRAME_c_attack202, + FRAME_c_attack203, + FRAME_c_attack204, + FRAME_c_attack205, + FRAME_c_attack206, + FRAME_c_attack207, + FRAME_c_attack208, + FRAME_c_attack209, + FRAME_c_attack210, + FRAME_c_attack211, + FRAME_c_attack212, + FRAME_c_attack213, + FRAME_c_attack214, + FRAME_c_attack215, + FRAME_c_attack216, + FRAME_c_attack217, + FRAME_c_attack218, + FRAME_c_attack219, + FRAME_c_attack220, + FRAME_c_attack221, + FRAME_c_attack301, + FRAME_c_attack302, + FRAME_c_attack303, + FRAME_c_attack304, + FRAME_c_attack305, + FRAME_c_attack306, + FRAME_c_attack307, + FRAME_c_attack308, + FRAME_c_attack309, + FRAME_c_attack310, + FRAME_c_attack311, + FRAME_c_attack312, + FRAME_c_attack313, + FRAME_c_attack314, + FRAME_c_attack315, + FRAME_c_attack316, + FRAME_c_attack317, + FRAME_c_attack318, + FRAME_c_attack319, + FRAME_c_attack320, + FRAME_c_attack321, + FRAME_c_attack401, + FRAME_c_attack402, + FRAME_c_attack403, + FRAME_c_attack404, + FRAME_c_attack405, + FRAME_c_attack501, + FRAME_c_attack502, + FRAME_c_attack503, + FRAME_c_attack504, + FRAME_c_attack505, + FRAME_c_attack601, + FRAME_c_attack602, + FRAME_c_attack603, + FRAME_c_attack604, + FRAME_c_attack605, + FRAME_c_attack701, + FRAME_c_attack702, + FRAME_c_attack703, + FRAME_c_attack704, + FRAME_c_attack705, + FRAME_c_pain101, + FRAME_c_pain102, + FRAME_c_pain103, + FRAME_c_pain104, + FRAME_c_pain201, + FRAME_c_pain202, + FRAME_c_pain203, + FRAME_c_pain204, + FRAME_c_pain301, + FRAME_c_pain302, + FRAME_c_pain303, + FRAME_c_pain304, + FRAME_c_pain401, + FRAME_c_pain402, + FRAME_c_pain403, + FRAME_c_pain404, + FRAME_c_pain405, + FRAME_c_pain406, + FRAME_c_pain407, + FRAME_c_pain408, + FRAME_c_pain409, + FRAME_c_pain410, + FRAME_c_pain411, + FRAME_c_pain412, + FRAME_c_pain413, + FRAME_c_pain414, + FRAME_c_pain415, + FRAME_c_pain501, + FRAME_c_pain502, + FRAME_c_pain503, + FRAME_c_pain504, + FRAME_c_pain505, + FRAME_c_pain506, + FRAME_c_pain507, + FRAME_c_pain508, + FRAME_c_pain509, + FRAME_c_pain510, + FRAME_c_pain511, + FRAME_c_pain512, + FRAME_c_pain513, + FRAME_c_pain514, + FRAME_c_pain515, + FRAME_c_pain516, + FRAME_c_pain517, + FRAME_c_pain518, + FRAME_c_pain519, + FRAME_c_pain520, + FRAME_c_pain521, + FRAME_c_pain522, + FRAME_c_pain523, + FRAME_c_pain524, + FRAME_c_death101, + FRAME_c_death102, + FRAME_c_death103, + FRAME_c_death104, + FRAME_c_death105, + FRAME_c_death106, + FRAME_c_death107, + FRAME_c_death108, + FRAME_c_death109, + FRAME_c_death110, + FRAME_c_death111, + FRAME_c_death112, + FRAME_c_death113, + FRAME_c_death114, + FRAME_c_death115, + FRAME_c_death116, + FRAME_c_death117, + FRAME_c_death118, + FRAME_c_death201, + FRAME_c_death202, + FRAME_c_death203, + FRAME_c_death204, + FRAME_c_death301, + FRAME_c_death302, + FRAME_c_death303, + FRAME_c_death304, + FRAME_c_death305, + FRAME_c_death306, + FRAME_c_death307, + FRAME_c_death308, + FRAME_c_death309, + FRAME_c_death310, + FRAME_c_death311, + FRAME_c_death312, + FRAME_c_death313, + FRAME_c_death314, + FRAME_c_death315, + FRAME_c_death316, + FRAME_c_death317, + FRAME_c_death318, + FRAME_c_death319, + FRAME_c_death320, + FRAME_c_death321, + FRAME_c_death401, + FRAME_c_death402, + FRAME_c_death403, + FRAME_c_death404, + FRAME_c_death405, + FRAME_c_death406, + FRAME_c_death407, + FRAME_c_death408, + FRAME_c_death409, + FRAME_c_death410, + FRAME_c_death411, + FRAME_c_death412, + FRAME_c_death413, + FRAME_c_death414, + FRAME_c_death415, + FRAME_c_death416, + FRAME_c_death417, + FRAME_c_death418, + FRAME_c_death419, + FRAME_c_death420, + FRAME_c_death421, + FRAME_c_death422, + FRAME_c_death423, + FRAME_c_death424, + FRAME_c_death425, + FRAME_c_death426, + FRAME_c_death427, + FRAME_c_death428, + FRAME_c_death429, + FRAME_c_death430, + FRAME_c_death431, + FRAME_c_death432, + FRAME_c_death433, + FRAME_c_death434, + FRAME_c_death435, + FRAME_c_death436, + FRAME_c_death501, + FRAME_c_death502, + FRAME_c_death503, + FRAME_c_death504, + FRAME_c_death505, + FRAME_c_death506, + FRAME_c_death507, + FRAME_c_death508, + FRAME_c_death509, + FRAME_c_death510, + FRAME_c_death511, + FRAME_c_death512, + FRAME_c_death513, + FRAME_c_death514, + FRAME_c_death515, + FRAME_c_death516, + FRAME_c_death517, + FRAME_c_death518, + FRAME_c_death519, + FRAME_c_death520, + FRAME_c_death521, + FRAME_c_death522, + FRAME_c_death523, + FRAME_c_death524, + FRAME_c_death525, + FRAME_c_death526, + FRAME_c_death527, + FRAME_c_death528, + FRAME_c_run101, + FRAME_c_run102, + FRAME_c_run103, + FRAME_c_run104, + FRAME_c_run105, + FRAME_c_run106, + FRAME_c_run201, + FRAME_c_run202, + FRAME_c_run203, + FRAME_c_run204, + FRAME_c_run205, + FRAME_c_run206, + FRAME_c_run301, + FRAME_c_run302, + FRAME_c_run303, + FRAME_c_run304, + FRAME_c_run305, + FRAME_c_run306, + FRAME_c_walk101, + FRAME_c_walk102, + FRAME_c_walk103, + FRAME_c_walk104, + FRAME_c_walk105, + FRAME_c_walk106, + FRAME_c_walk107, + FRAME_c_walk108, + FRAME_c_walk109, + FRAME_c_walk110, + FRAME_c_walk111, + FRAME_c_walk112, + FRAME_c_walk113, + FRAME_c_walk114, + FRAME_c_walk115, + FRAME_c_walk116, + FRAME_c_walk117, + FRAME_c_walk118, + FRAME_c_walk119, + FRAME_c_walk120, + FRAME_c_walk121, + FRAME_c_walk122, + FRAME_c_walk123, + FRAME_c_walk124, + FRAME_c_pain601, + FRAME_c_pain602, + FRAME_c_pain603, + FRAME_c_pain604, + FRAME_c_pain605, + FRAME_c_pain606, + FRAME_c_pain607, + FRAME_c_pain608, + FRAME_c_pain609, + FRAME_c_pain610, + FRAME_c_pain611, + FRAME_c_pain612, + FRAME_c_pain613, + FRAME_c_pain614, + FRAME_c_pain615, + FRAME_c_pain616, + FRAME_c_pain617, + FRAME_c_pain618, + FRAME_c_pain619, + FRAME_c_pain620, + FRAME_c_pain621, + FRAME_c_pain622, + FRAME_c_pain623, + FRAME_c_pain624, + FRAME_c_pain625, + FRAME_c_pain626, + FRAME_c_pain627, + FRAME_c_pain628, + FRAME_c_pain629, + FRAME_c_pain630, + FRAME_c_pain631, + FRAME_c_pain632, + FRAME_c_death601, + FRAME_c_death602, + FRAME_c_death603, + FRAME_c_death604, + FRAME_c_death605, + FRAME_c_death606, + FRAME_c_death607, + FRAME_c_death608, + FRAME_c_death609, + FRAME_c_death610, + FRAME_c_death611, + FRAME_c_death612, + FRAME_c_death613, + FRAME_c_death614, + FRAME_c_death701, + FRAME_c_death702, + FRAME_c_death703, + FRAME_c_death704, + FRAME_c_death705, + FRAME_c_death706, + FRAME_c_death707, + FRAME_c_death708, + FRAME_c_death709, + FRAME_c_death710, + FRAME_c_death711, + FRAME_c_death712, + FRAME_c_death713, + FRAME_c_death714, + FRAME_c_death715, + FRAME_c_death716, + FRAME_c_death717, + FRAME_c_death718, + FRAME_c_death719, + FRAME_c_death720, + FRAME_c_death721, + FRAME_c_death722, + FRAME_c_death723, + FRAME_c_death724, + FRAME_c_death725, + FRAME_c_death726, + FRAME_c_death727, + FRAME_c_death728, + FRAME_c_death729, + FRAME_c_death730, + FRAME_c_pain701, + FRAME_c_pain702, + FRAME_c_pain703, + FRAME_c_pain704, + FRAME_c_pain705, + FRAME_c_pain706, + FRAME_c_pain707, + FRAME_c_pain708, + FRAME_c_pain709, + FRAME_c_pain710, + FRAME_c_pain711, + FRAME_c_pain712, + FRAME_c_pain713, + FRAME_c_pain714, + FRAME_c_attack801, + FRAME_c_attack802, + FRAME_c_attack803, + FRAME_c_attack804, + FRAME_c_attack805, + FRAME_c_attack806, + FRAME_c_attack807, + FRAME_c_attack808, + FRAME_c_attack809, + FRAME_c_attack901, + FRAME_c_attack902, + FRAME_c_attack903, + FRAME_c_attack904, + FRAME_c_attack905, + FRAME_c_attack906, + FRAME_c_attack907, + FRAME_c_attack908, + FRAME_c_attack909, + FRAME_c_attack910, + FRAME_c_attack911, + FRAME_c_attack912, + FRAME_c_attack913, + FRAME_c_attack914, + FRAME_c_attack915, + FRAME_c_attack916, + FRAME_c_attack917, + FRAME_c_attack918, + FRAME_c_attack919, + FRAME_c_duck01, + FRAME_c_duck02, + FRAME_c_duckstep01, + FRAME_c_duckstep02, + FRAME_c_duckstep03, + FRAME_c_duckstep04, + FRAME_c_duckstep05, + FRAME_c_duckstep06, + FRAME_c_duckpain01, + FRAME_c_duckpain02, + FRAME_c_duckpain03, + FRAME_c_duckpain04, + FRAME_c_duckpain05, + FRAME_c_duckdeath01, + FRAME_c_duckdeath02, + FRAME_c_duckdeath03, + FRAME_c_duckdeath04, + FRAME_c_duckdeath05, + FRAME_c_duckdeath06, + FRAME_c_duckdeath07, + FRAME_c_duckdeath08, + FRAME_c_duckdeath09, + FRAME_c_duckdeath10, + FRAME_c_duckdeath11, + FRAME_c_duckdeath12, + FRAME_c_duckdeath13, + FRAME_c_duckdeath14, + FRAME_c_duckdeath15, + FRAME_c_duckdeath16, + FRAME_c_duckdeath17, + FRAME_c_duckdeath18, + FRAME_c_duckdeath19, + FRAME_c_duckdeath20, + FRAME_c_duckdeath21, + FRAME_c_duckdeath22, + FRAME_c_duckdeath23, + FRAME_c_duckdeath24, + FRAME_c_duckdeath25, + FRAME_c_duckdeath26, + FRAME_c_duckdeath27, + FRAME_c_duckdeath28, + FRAME_c_duckdeath29 +}; + +constexpr float MODEL_SCALE = 1.150000f; diff --git a/to add/m_hover.cpp b/to add/m_hover.cpp new file mode 100644 index 00000000..9262d5e1 --- /dev/null +++ b/to add/m_hover.cpp @@ -0,0 +1,804 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +hover + +This file combines the original hover monster (blaster) with the Xatrix expansion +hover (rocket), as well as daedalus variants (blaster2 and grenades). + +============================================================================== +*/ + +#include "g_local.h" +#include "m_hover.h" +#include "m_flash.h" +#include "shared.h" +#include "horde/g_horde_scaling.h" +#include "monster_constants.h" + +static cached_soundindex sound_pain1; +static cached_soundindex sound_pain2; +static cached_soundindex sound_death1; +static cached_soundindex sound_death2; +static cached_soundindex sound_sight; +static cached_soundindex sound_search1; +static cached_soundindex sound_search2; + +// ROGUE +// daedalus sounds +static cached_soundindex daed_sound_pain1; +static cached_soundindex daed_sound_pain2; +static cached_soundindex daed_sound_death1; +static cached_soundindex daed_sound_death2; +static cached_soundindex daed_sound_sight; +static cached_soundindex daed_sound_search1; +static cached_soundindex daed_sound_search2; +// ROGUE + +// FIX: Forward declare the master spawn function so other spawn functions can call it. +void SP_monster_hover(edict_t* self); + +// FIX: This helper function is now the single source of truth for identifying a Daedalus. +bool IsDaedalusType(const edict_t* ent) +{ + if (!ent) return false; + const auto id = static_cast(ent->monsterinfo.monster_type_id); + return (id == horde::MonsterTypeID::DAEDALUS || + id == horde::MonsterTypeID::DAEDALUS_BOMBER); +} + +struct hover_style_t +{ + enum weapon_t + { + Blaster = 0, + Rocket = 1, + Blaster2 = 2, + Grenade = 3 + }; + + weapon_t weapon; + + // This constructor is now reliable because the monster_type_id is set correctly at spawn. + hover_style_t(edict_t* self) + { + // gi.Com_PrintFmt("Hover attacking with ID: {}\n", self->monsterinfo.monster_type_id); + + // Use a switch on the definitive monster_type_id + switch (static_cast(self->monsterinfo.monster_type_id)) + { + case horde::MonsterTypeID::HOVER: + weapon = Rocket; + break; + case horde::MonsterTypeID::DAEDALUS: + weapon = Blaster2; + break; + case horde::MonsterTypeID::DAEDALUS_BOMBER: + weapon = Grenade; + break; + case horde::MonsterTypeID::HOVER_VANILLA: + default: // Fallback to the standard blaster + weapon = Blaster; + break; + } + } + + // Keep these as constexpr since they only depend on the weapon value + constexpr bool is_vanilla() const { return weapon < Blaster; } + constexpr bool is_xatrix() const { return weapon >= Blaster2; } + constexpr bool has_blaster() const { return weapon == Blaster; } + constexpr bool has_rocket() const { return weapon == Rocket; } + constexpr bool has_blaster2() const { return weapon == Blaster2; } + constexpr bool has_grenade() const { return weapon == Grenade; } +}; + +MONSTERINFO_SIGHT(hover_sight) (edict_t* self, edict_t* other) -> void +{ + // FIX: Use the reliable IsDaedalusType helper instead of checking mass. + if (!IsDaedalusType(self)) + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_VOICE, daed_sound_sight, 1, ATTN_NORM, 0); +} + +MONSTERINFO_SEARCH(hover_search) (edict_t* self) -> void +{ + // FIX: Use the reliable IsDaedalusType helper instead of checking mass. + if (!IsDaedalusType(self)) + { + if (frandom() < 0.5f) + gi.sound(self, CHAN_VOICE, sound_search1, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_VOICE, sound_search2, 1, ATTN_NORM, 0); + } + else + { + if (frandom() < 0.5f) + gi.sound(self, CHAN_VOICE, daed_sound_search1, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_VOICE, daed_sound_search2, 1, ATTN_NORM, 0); + } +} + +void hover_run(edict_t* self); +void hover_dead(edict_t* self); +void hover_attack(edict_t* self); +void hover_reattack(edict_t* self); +void hover_fire_weapon(edict_t* self); + +mframe_t hover_frames_stand[] = { + { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, + { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, + { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, + { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, + { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, + { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand } +}; +MMOVE_T(hover_move_stand) = { FRAME_stand01, FRAME_stand30, hover_frames_stand, nullptr }; + +mframe_t hover_frames_pain3[] = { + { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, + { ai_move }, { ai_move }, { ai_move }, { ai_move } +}; +MMOVE_T(hover_move_pain3) = { FRAME_pain301, FRAME_pain309, hover_frames_pain3, hover_run }; + +mframe_t hover_frames_pain2[] = { + { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, + { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, + { ai_move }, { ai_move } +}; +MMOVE_T(hover_move_pain2) = { FRAME_pain201, FRAME_pain212, hover_frames_pain2, hover_run }; + +mframe_t hover_frames_pain1[] = { + { ai_move }, { ai_move }, { ai_move, 2 }, { ai_move, -8 }, { ai_move, -4 }, + { ai_move, -6 }, { ai_move, -4 }, { ai_move, -3 }, { ai_move, 1 }, { ai_move }, + { ai_move }, { ai_move }, { ai_move, 3 }, { ai_move, 1 }, { ai_move }, + { ai_move, 2 }, { ai_move, 3 }, { ai_move, 2 }, { ai_move, 7 }, { ai_move, 1 }, + { ai_move }, { ai_move }, { ai_move, 2 }, { ai_move }, { ai_move }, + { ai_move, 5 }, { ai_move, 3 }, { ai_move, 4 } +}; +MMOVE_T(hover_move_pain1) = { FRAME_pain101, FRAME_pain128, hover_frames_pain1, hover_run }; + +mframe_t hover_frames_walk[] = { + { ai_walk, 4 }, { ai_walk, 4 }, { ai_walk, 4 }, { ai_walk, 4 }, { ai_walk, 4 }, + { ai_walk, 4 }, { ai_walk, 4 }, { ai_walk, 4 }, { ai_walk, 4 }, { ai_walk, 4 }, + { ai_walk, 4 }, { ai_walk, 4 }, { ai_walk, 4 }, { ai_walk, 4 }, { ai_walk, 4 }, + { ai_walk, 4 }, { ai_walk, 4 }, { ai_walk, 4 }, { ai_walk, 4 }, { ai_walk, 4 }, + { ai_walk, 4 }, { ai_walk, 4 }, { ai_walk, 4 }, { ai_walk, 4 }, { ai_walk, 4 }, + { ai_walk, 4 }, { ai_walk, 4 }, { ai_walk, 4 }, { ai_walk, 4 }, { ai_walk, 4 }, + { ai_walk, 4 }, { ai_walk, 4 }, { ai_walk, 4 }, { ai_walk, 4 }, { ai_walk, 4 } +}; +MMOVE_T(hover_move_walk) = { FRAME_forwrd01, FRAME_forwrd35, hover_frames_walk, nullptr }; + +mframe_t hover_frames_run[] = { + { ai_run, 10 }, { ai_run, 10 }, { ai_run, 10 }, { ai_run, 10 }, { ai_run, 10 }, + { ai_run, 10 }, { ai_run, 10 }, { ai_run, 10 }, { ai_run, 10 }, { ai_run, 10 }, + { ai_run, 10 }, { ai_run, 10 }, { ai_run, 10 }, { ai_run, 10 }, { ai_run, 10 }, + { ai_run, 10 }, { ai_run, 10 }, { ai_run, 10 }, { ai_run, 10 }, { ai_run, 10 }, + { ai_run, 10 }, { ai_run, 10 }, { ai_run, 10 }, { ai_run, 10 }, { ai_run, 10 }, + { ai_run, 10 }, { ai_run, 10 }, { ai_run, 10 }, { ai_run, 10 }, { ai_run, 10 }, + { ai_run, 10 }, { ai_run, 10 }, { ai_run, 10 }, { ai_run, 10 }, { ai_run, 10 } +}; +MMOVE_T(hover_move_run) = { FRAME_forwrd01, FRAME_forwrd35, hover_frames_run, nullptr }; + +static void hover_gib(edict_t* self) +{ + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1); + gi.WritePosition(self->s.origin); + gi.multicast(self->s.origin, MULTICAST_PHS, false); + + self->s.skinnum /= 2; + + ThrowGibs(self, 150, { + { 2, "models/objects/gibs/sm_meat/tris.md2" }, + { 2, "models/objects/gibs/sm_metal/tris.md2", GIB_METALLIC }, + { "models/monsters/hover/gibs/chest.md2", GIB_SKINNED }, + { 2, "models/monsters/hover/gibs/ring.md2", GIB_SKINNED | GIB_METALLIC }, + { 2, "models/monsters/hover/gibs/foot.md2", GIB_SKINNED }, + { "models/monsters/hover/gibs/head.md2", GIB_SKINNED | GIB_HEAD }, + }); +} + +THINK(hover_deadthink) (edict_t* self) -> void +{ + if (!self->groundentity && level.time < self->timestamp) + { + self->nextthink = level.time + FRAME_TIME_S; + return; + } + hover_gib(self); +} + +void hover_dying(edict_t* self) +{ + if (self->groundentity) + { + hover_deadthink(self); + return; + } + if (brandom()) + return; + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_PLAIN_EXPLOSION); + gi.WritePosition(self->s.origin); + gi.multicast(self->s.origin, MULTICAST_PHS, false); + if (brandom()) + ThrowGibs(self, 120, { { "models/objects/gibs/sm_meat/tris.md2" } }); + else + ThrowGibs(self, 120, { { "models/objects/gibs/sm_metal/tris.md2", GIB_METALLIC } }); +} + +mframe_t hover_frames_death1[] = { + { ai_move }, { ai_move, 0.f, hover_dying }, { ai_move }, { ai_move, 0.f, hover_dying }, + { ai_move }, { ai_move, 0.f, hover_dying }, { ai_move, -10, hover_dying }, { ai_move, 3 }, + { ai_move, 5, hover_dying }, { ai_move, 4, hover_dying }, { ai_move, 7 } +}; +MMOVE_T(hover_move_death1) = { FRAME_death101, FRAME_death111, hover_frames_death1, hover_dead }; + +mframe_t hover_frames_start_attack[] = { + { ai_charge, 1 }, { ai_charge, 1 }, { ai_charge, 1 } +}; +MMOVE_T(hover_move_start_attack) = { FRAME_attak101, FRAME_attak103, hover_frames_start_attack, hover_attack }; + +mframe_t hover_frames_attack1[] = { + { ai_charge, -10, hover_fire_weapon }, + { ai_charge, -10, hover_fire_weapon }, + { ai_charge, 0, hover_reattack }, +}; +MMOVE_T(hover_move_attack1) = { FRAME_attak104, FRAME_attak106, hover_frames_attack1, nullptr }; + +mframe_t hover_frames_end_attack[] = { + { ai_charge, 1 }, { ai_charge, 1 } +}; +MMOVE_T(hover_move_end_attack) = { FRAME_attak107, FRAME_attak108, hover_frames_end_attack, hover_run }; + +mframe_t hover_frames_attack2[] = { + { ai_charge, 10, hover_fire_weapon }, + { ai_charge, 10, hover_fire_weapon }, + { ai_charge, 10, hover_reattack }, +}; +MMOVE_T(hover_move_attack2) = { FRAME_attak104, FRAME_attak106, hover_frames_attack2, nullptr }; + +void hover_reattack(edict_t* self) +{ + hover_style_t style(self); + float reattack_chance = 0.5f; + + // Daedalus with Blaster2 is more aggressive + if (style.has_blaster2()) + reattack_chance = 0.6f; + // Daedalus with Grenades is less aggressive + else if (style.has_grenade()) + reattack_chance = 0.4f; + + // FIX: Add a check to ensure the enemy is valid before accessing its members. + if (self->enemy && self->enemy->inuse && self->enemy->health > 0) + { + if (visible(self, self->enemy)) + { + if (frandom() <= reattack_chance) + { + if (self->monsterinfo.attack_state == AS_STRAIGHT) + { + M_SetAnimation(self, &hover_move_attack1); + return; + } + else if (self->monsterinfo.attack_state == AS_SLIDING) + { + M_SetAnimation(self, &hover_move_attack2); + return; + } + } + } + } + M_SetAnimation(self, &hover_move_end_attack); +} + +void hover_fire_blaster(edict_t* self) +{ + if (!M_HasValidTarget(self)) + { + return; // Stop immediately if the target is invalid. + } + + vec3_t start, forward, right, end, dir; + int config_speed = M_BLASTER_SPEED(self); + int blasterSpeed = config_speed > 0 ? config_speed : 1230; + AngleVectors(self->s.angles, forward, right, nullptr); + vec3_t const o = monster_flash_offset[(self->s.frame & 1) ? MZ2_HOVER_BLASTER_2 : MZ2_HOVER_BLASTER_1]; + start = M_ProjectFlashSource(self, o, forward, right); + end = self->enemy->s.origin; + end[2] += self->enemy->viewheight; + + // Check if muzzle origin can see enemy before firing + trace_t trace = gi.traceline(start, end, self, MASK_PROJECTILE); + if (!(trace.fraction > 0.5f || trace.ent->solid != SOLID_BSP)) + return; + + dir = end - start; + dir.normalize(); + PredictAim(self, self->enemy, start, blasterSpeed / 1.5, true, 0.f, &dir, &end); + int damage = M_BLASTER_DMG(self); + monster_fire_blaster(self, start, dir, damage > 0 ? damage : 12, blasterSpeed, + (self->s.frame & 1) ? MZ2_HOVER_BLASTER_2 : MZ2_HOVER_BLASTER_1, + (self->s.frame % 4) ? EF_NONE : EF_BLASTER); +} + +void hover_fire_blaster2(edict_t* self) +{ + if (!M_HasValidTarget(self)) + { + return; // Stop immediately if the target is invalid. + } + + vec3_t start, forward, right, end, dir; + int config_speed = M_BLASTER2_SPEED(self); + int blasterSpeed = config_speed > 0 ? config_speed : 1230; + AngleVectors(self->s.angles, forward, right, nullptr); + vec3_t const o = monster_flash_offset[(self->s.frame & 1) ? MZ2_HOVER_BLASTER_2 : MZ2_HOVER_BLASTER_1]; + start = M_ProjectFlashSource(self, o, forward, right); + end = self->enemy->s.origin; + end[2] += self->enemy->viewheight; + + // Check if muzzle origin can see enemy before firing + trace_t trace = gi.traceline(start, end, self, MASK_PROJECTILE); + if (!(trace.fraction > 0.5f || trace.ent->solid != SOLID_BSP)) + return; + + dir = end - start; + dir.normalize(); + PredictAim(self, self->enemy, start, blasterSpeed / 1.5, true, 0.f, &dir, &end); + int damage = M_BLASTER2_DMG(self); + monster_fire_blaster2(self, start, dir, damage > 0 ? damage : 12, blasterSpeed, + (self->s.frame & 1) ? MZ2_DAEDALUS_BLASTER_2 : MZ2_DAEDALUS_BLASTER, + (self->s.frame % 4) ? EF_NONE : EF_BLASTER); +} + +void hover_fire_rocket(edict_t* self) +{ + // Basic enemy check - blindfire logic needs to execute + if (!M_HasEnemy(self)) + return; + + vec3_t forward, right, start, dir, vec, target; + trace_t trace; + int config_speed = M_ROCKET_SPEED(self); + int rocketSpeed = config_speed > 0 ? config_speed : 850; + bool blindfire = (self->monsterinfo.aiflags & AI_MANUAL_STEERING); + AngleVectors(self->s.angles, forward, right, nullptr); + start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_BOSS2_ROCKET_3], forward, right); + + if (blindfire && !visible(self, self->enemy)) + { + if (!self->monsterinfo.blind_fire_target) + return; + target = self->monsterinfo.blind_fire_target; + vec = target; + dir = vec - start; + } + else + { + // Not blindfiring - need fully valid target + if (!M_HasValidTarget(self)) + return; + + target = self->enemy->s.origin; + if (frandom() < 0.33f || (start[2] < self->enemy->absmin[2])) { + vec = target; + vec[2] += self->enemy->viewheight; + dir = vec - start; + } else { + vec = target; + vec[2] = self->enemy->absmin[2] + 1; + dir = vec - start; + } + if (frandom() < 0.35f) + PredictAim(self, self->enemy, start, rocketSpeed / 1.5, false, 0.f, &dir, &vec); + } + + dir.normalize(); + trace = gi.traceline(start, vec, self, MASK_PROJECTILE); + if (trace.fraction > 0.5f || trace.ent == self->enemy || trace.ent->solid != SOLID_BSP) + monster_fire_rocket(self, start, dir, M_ROCKET_DMG(self), M_ROCKET_SPEED(self), MZ2_BOSS2_ROCKET_3); +} + +void hover_fire_grenades(edict_t* self) +{ + // Basic enemy check - blindfire logic needs to execute + if (!M_HasEnemy(self)) + return; + + vec3_t forward, right, up, aim, target, offset{}; + monster_muzzleflash_id_t flash_number = MZ2_GUNCMDR_GRENADE_MORTAR_1; + bool blindfire = self->monsterinfo.aiflags & AI_MANUAL_STEERING; + + if (self->s.frame == FRAME_attak104) offset = { 1.7f, 7.0f, 11.3f }; + else if (self->s.frame == FRAME_attak105) offset = { 1.7f, -7.0f, 11.3f }; + + AngleVectors(self->s.angles, forward, right, up); + const vec3_t start = G_ProjectSource2(self->s.origin, offset, forward, right, up); + + // PMM - blindfire support + if (blindfire && !visible(self, self->enemy)) + { + if (!self->monsterinfo.blind_fire_target) + return; + target = self->monsterinfo.blind_fire_target; + aim = target - start; + } + else + { + // Not blindfiring - need fully valid target + if (!M_HasValidTarget(self)) + return; + + target = self->enemy->s.origin; + PredictAim(self, self->enemy, start, 800, false, 0.f, &aim, nullptr); + } + // pmm + + aim.normalize(); + monster_fire_grenade(self, start, aim, M_GRENADE_DMG(self), M_GRENADE_SPEED(self), flash_number, + (crandom_open() * 10.0f), 200.f + (crandom_open() * 10.0f)); +} + +void hover_fire_weapon(edict_t* self) +{ + if (!M_HasValidTarget(self)) + { + return; // Stop immediately if the target is invalid. + } + + hover_style_t style(self); + if (style.has_blaster()) hover_fire_blaster(self); + else if (style.has_rocket()) hover_fire_rocket(self); + else if (style.has_blaster2()) hover_fire_blaster2(self); + else if (style.has_grenade()) hover_fire_grenades(self); +} + +MONSTERINFO_STAND(hover_stand) (edict_t* self) -> void { M_SetAnimation(self, &hover_move_stand); } +MONSTERINFO_RUN(hover_run) (edict_t* self) -> void { + if (self->monsterinfo.aiflags & AI_STAND_GROUND) M_SetAnimation(self, &hover_move_stand); + else M_SetAnimation(self, &hover_move_run); +} +MONSTERINFO_WALK(hover_walk) (edict_t* self) -> void { M_SetAnimation(self, &hover_move_walk); } +MONSTERINFO_ATTACK(hover_start_attack) (edict_t* self) -> void { M_SetAnimation(self, &hover_move_start_attack); } + +void hover_attack(edict_t* self) +{ + monster_done_dodge(self); + + hover_style_t style(self); + float strafe_chance = 0.5f; + + // FIX: Use the reliable IsDaedalusType helper instead of checking mass. + if (IsDaedalusType(self)) + strafe_chance += 0.1f; + if (style.has_rocket()) + strafe_chance += 0.1f; + if (style.has_grenade()) + strafe_chance -= 0.15f; + + if (frandom() > strafe_chance) { + M_SetAnimation(self, &hover_move_attack1); + self->monsterinfo.attack_state = AS_STRAIGHT; + } else { + if (frandom() <= 0.5f) self->monsterinfo.lefty = !self->monsterinfo.lefty; + M_SetAnimation(self, &hover_move_attack2); + self->monsterinfo.attack_state = AS_SLIDING; + } +} + +PAIN(hover_pain) (edict_t* self, edict_t* other, float kick, int damage, const mod_t& mod) -> void +{ + if (level.time < self->pain_debounce_time) return; + self->pain_debounce_time = level.time + 3_sec; + + // FIX: Use the reliable IsDaedalusType helper instead of checking mass. + if (!IsDaedalusType(self)) { + if (frandom() < 0.5f) gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + else gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + } else { + if (frandom() < 0.5f) gi.sound(self, CHAN_VOICE, daed_sound_pain1, 1, ATTN_NORM, 0); + else gi.sound(self, CHAN_VOICE, daed_sound_pain2, 1, ATTN_NORM, 0); + } + + if (!M_ShouldReactToPain(self, mod)) return; + + // Apply knockback velocity away from damage source + if (other && other->inuse) + { + vec3_t knockback_dir = (self->s.origin - other->s.origin).normalized(); + float knockback_strength = min(200.f, 50.f + damage * 2.f); + self->velocity += knockback_dir * knockback_strength; + } + + if (damage <= 25) { + if (frandom() < 0.5f) M_SetAnimation(self, &hover_move_pain3); + else M_SetAnimation(self, &hover_move_pain2); + } else { + if (frandom() < 0.3f) M_SetAnimation(self, &hover_move_pain1); + else M_SetAnimation(self, &hover_move_pain2); + } +} + +MONSTERINFO_SETSKIN(hover_setskin) (edict_t* self) -> void +{ + if (self->health < (self->max_health / 2)) self->s.skinnum |= 1; + else self->s.skinnum &= ~1; +} + +void hover_dead(edict_t* self) +{ + self->mins = { -16, -16, -24 }; + self->maxs = { 16, 16, -8 }; + self->movetype = MOVETYPE_TOSS; + self->think = hover_deadthink; + self->nextthink = level.time + FRAME_TIME_S; + self->timestamp = level.time + 15_sec; + gi.linkentity(self); +} + +DIE(hover_die) (edict_t* self, edict_t* inflictor, edict_t* attacker, int damage, const vec3_t& point, const mod_t& mod) -> void +{ + self->s.effects = EF_NONE; + self->monsterinfo.power_armor_type = IT_NULL; + if (M_CheckGib(self, mod)) { + hover_gib(self); + return; + } + if (self->deadflag) return; + + // FIX: Use the reliable IsDaedalusType helper instead of checking mass. + if (!IsDaedalusType(self)) { + if (frandom() < 0.5f) gi.sound(self, CHAN_VOICE, sound_death1, 1, ATTN_NORM, 0); + else gi.sound(self, CHAN_VOICE, sound_death2, 1, ATTN_NORM, 0); + } else { + if (frandom() < 0.5f) gi.sound(self, CHAN_VOICE, daed_sound_death1, 1, ATTN_NORM, 0); + else gi.sound(self, CHAN_VOICE, daed_sound_death2, 1, ATTN_NORM, 0); + } + + self->deadflag = true; + self->takedamage = true; + M_SetAnimation(self, &hover_move_death1); +} + +static void hover_set_fly_parameters(edict_t* self) +{ + hover_style_t style(self); + + // FIX: Use the reliable IsDaedalusType helper instead of checking mass. + if (!IsDaedalusType(self)) + { + self->monsterinfo.fly_thrusters = false; + self->monsterinfo.fly_acceleration = 20.f; + self->monsterinfo.fly_speed = 270.f; + self->monsterinfo.fly_min_distance = 325.f; + self->monsterinfo.fly_max_distance = 670.f; + } + else // Is a Daedalus + { + self->monsterinfo.fly_thrusters = false; + self->monsterinfo.fly_acceleration = 20.f; + if (style.has_grenade()) { + self->monsterinfo.fly_speed = 320.f; + self->monsterinfo.fly_min_distance = 500.f; + self->monsterinfo.fly_max_distance = 850.f; + } else { // Blaster2 Daedalus + self->monsterinfo.fly_speed = 290.f; + self->monsterinfo.fly_min_distance = 400.f; + self->monsterinfo.fly_max_distance = 850.f; + } + } +} + +/*QUAKED monster_hover (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +This is the master spawn function for all hover variants. +*/ +void SP_monster_hover(edict_t* self) +{ + // FIX: This is the new central logic. It sets the monster_type_id from the classname + // if it hasn't been set already. This makes the system work for all spawn methods. + if (self->monsterinfo.monster_type_id == MONSTER_TYPE_UNKNOWN) // Check if it hasn't been set yet + self->monsterinfo.monster_type_id = static_cast(horde::MonsterTypeID::HOVER); + + const spawn_temp_t& st = ED_GetSpawnTemp(); + + if (!M_AllowSpawn(self)) { + G_FreeEdict(self); + return; + } + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/hover/tris.md2"); + gi.modelindex("models/monsters/hover/gibs/chest.md2"); + gi.modelindex("models/monsters/hover/gibs/foot.md2"); + gi.modelindex("models/monsters/hover/gibs/head.md2"); + gi.modelindex("models/monsters/hover/gibs/ring.md2"); + + self->mins = { -24, -24, -24 }; + self->maxs = { 24, 24, 32 }; + + int base_health = M_HOVER_INITIAL_HEALTH; + if (g_horde && g_horde->integer && current_wave_level > 0) { + bool is_boss = self->monsterinfo.IS_BOSS && !self->monsterinfo.BOSS_DEATH_HANDLED; + self->health = ScaleMonsterHealth(base_health, current_wave_level, is_boss); + } else { + self->health = base_health * st.health_multiplier; + } + self->gib_health = -100; + + // Only set mass if it hasn't been set already (for non-Daedalus types). + if (self->mass <= 150) { + self->mass = 150; + } + + // Power armor configuration + if (!st.was_key_specified("power_armor_type") && M_HOVER_POWER_ARMOR_TYPE != IT_NULL) { + self->monsterinfo.power_armor_type = static_cast(M_HOVER_POWER_ARMOR_TYPE); + if (!st.was_key_specified("power_armor_power")) + self->monsterinfo.power_armor_power = M_HOVER_ADDON_POWER_ARMOR(self); + } + + // Regular armor configuration + if (!st.was_key_specified("armor_type") && M_HOVER_INITIAL_ARMOR > 0) { + self->monsterinfo.armor_type = IT_ARMOR_COMBAT; + if (!st.was_key_specified("armor_power")) + self->monsterinfo.armor_power = M_HOVER_ADDON_ARMOR(self); + } + + self->pain = hover_pain; + self->die = hover_die; + self->s.scale = 1.15f; + self->monsterinfo.stand = hover_stand; + self->monsterinfo.walk = hover_walk; + self->monsterinfo.run = hover_run; + self->monsterinfo.attack = hover_start_attack; + self->monsterinfo.sight = hover_sight; + self->monsterinfo.search = hover_search; + self->monsterinfo.setskin = hover_setskin; + + // Standard hover sound setup + self->yaw_speed = 18; + sound_pain1.assign("hover/hovpain1.wav"); + sound_pain2.assign("hover/hovpain2.wav"); + sound_death1.assign("hover/hovdeth1.wav"); + sound_death2.assign("hover/hovdeth2.wav"); + sound_sight.assign("hover/hovsght1.wav"); + sound_search1.assign("hover/hovsrch1.wav"); + sound_search2.assign("hover/hovsrch2.wav"); + gi.soundindex("hover/hovatck1.wav"); + self->monsterinfo.engine_sound = gi.soundindex("hover/hovidle1.wav"); + + gi.linkentity(self); + M_SetAnimation(self, &hover_move_stand); + self->monsterinfo.scale = MODEL_SCALE; + flymonster_start(self); + self->monsterinfo.aiflags |= AI_ALTERNATE_FLY; + hover_set_fly_parameters(self); + ApplyMonsterBonusFlags(self); +} + +// FIX: This function is for the "monster_hover" classname (Rocket Hover). +// It just needs to call the master spawn function. +/*QUAKED monster_hover (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +This is now the rocket variant. +*/ +// The engine links "monster_hover" to SP_monster_hover, so we don't need a separate function. + +// FIX: This function is for the "monster_hover_vanilla" classname (Blaster Hover). +/*QUAKED monster_hover_vanilla (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +*/ +void SP_monster_hover_vanilla(edict_t* self) +{ + const spawn_temp_t& st = ED_GetSpawnTemp(); + + self->monsterinfo.monster_type_id = static_cast(horde::MonsterTypeID::HOVER_VANILLA); SP_monster_hover(self); + + // Power armor configuration + if (!st.was_key_specified("power_armor_type") && M_HOVER_VANILLA_POWER_ARMOR_TYPE != IT_NULL) { + self->monsterinfo.power_armor_type = static_cast(M_HOVER_VANILLA_POWER_ARMOR_TYPE); + if (!st.was_key_specified("power_armor_power")) + self->monsterinfo.power_armor_power = M_HOVER_VANILLA_ADDON_POWER_ARMOR(self); + } + + // Regular armor configuration + if (!st.was_key_specified("armor_type") && M_HOVER_VANILLA_INITIAL_ARMOR > 0) { + self->monsterinfo.armor_type = IT_ARMOR_COMBAT; + if (!st.was_key_specified("armor_power")) + self->monsterinfo.armor_power = M_HOVER_VANILLA_ADDON_ARMOR(self); + } + +} + +// FIX: This function is for the "monster_daedalus" classname (Blaster2 Daedalus). +/*QUAKED monster_daedalus (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +*/ +void SP_monster_daedalus(edict_t* self) +{ + const spawn_temp_t& st = ED_GetSpawnTemp(); + + if (self->monsterinfo.monster_type_id == MONSTER_TYPE_UNKNOWN) // Check if it hasn't been set yet + self->monsterinfo.monster_type_id = static_cast(horde::MonsterTypeID::DAEDALUS); + + if (!M_AllowSpawn(self)) { + G_FreeEdict(self); + return; + } + + int base_health = M_DAEDALUS_INITIAL_HEALTH; + if (g_horde && g_horde->integer && current_wave_level > 0) { + bool is_boss = self->monsterinfo.IS_BOSS && !self->monsterinfo.BOSS_DEATH_HANDLED; + self->health = ScaleMonsterHealth(base_health, current_wave_level, is_boss); + } else { + self->health = base_health * st.health_multiplier; + } + self->s.skinnum = 2; + // Set properties common to ALL Daedalus types + self->mass = 225; + self->yaw_speed = 23; + // Power armor configuration + if (!st.was_key_specified("power_armor_type") && M_DAEDALUS_POWER_ARMOR_TYPE != IT_NULL) { + self->monsterinfo.power_armor_type = static_cast(M_DAEDALUS_POWER_ARMOR_TYPE); + if (!st.was_key_specified("power_armor_power")) + self->monsterinfo.power_armor_power = M_DAEDALUS_ADDON_POWER_ARMOR(self); + } + + // Regular armor configuration + if (!st.was_key_specified("armor_type") && M_DAEDALUS_INITIAL_ARMOR > 0) { + self->monsterinfo.armor_type = IT_ARMOR_COMBAT; + if (!st.was_key_specified("armor_power")) + self->monsterinfo.armor_power = M_DAEDALUS_ADDON_ARMOR(self); + } + + daed_sound_pain1.assign("daedalus/daedpain1.wav"); + daed_sound_pain2.assign("daedalus/daedpain2.wav"); + daed_sound_death1.assign("daedalus/daeddeth1.wav"); + daed_sound_death2.assign("daedalus/daeddeth2.wav"); + daed_sound_sight.assign("daedalus/daedsght1.wav"); + daed_sound_search1.assign("daedalus/daedsrch1.wav"); + daed_sound_search2.assign("daedalus/daedsrch2.wav"); + + // Now call the master hover spawn function. + SP_monster_hover(self); +} + +// FIX: This function is for the "monster_daedalus_bomber" classname (Grenade Daedalus). +/*QUAKED monster_daedalus_bomber (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight +*/ +void SP_monster_daedalus_bomber(edict_t* self) +{ + const spawn_temp_t &st = ED_GetSpawnTemp(); + + self->monsterinfo.monster_type_id = static_cast(horde::MonsterTypeID::DAEDALUS_BOMBER); // A grenade Daedalus IS a Daedalus. Call its spawn function first + // to set up mass, sounds, power armor, etc. + SP_monster_daedalus(self); + + // Power armor configuration + if (!st.was_key_specified("power_armor_type") && M_DAEDALUS_BOMBER_POWER_ARMOR_TYPE != IT_NULL) { + self->monsterinfo.power_armor_type = static_cast(M_DAEDALUS_BOMBER_POWER_ARMOR_TYPE); + if (!st.was_key_specified("power_armor_power")) + self->monsterinfo.power_armor_power = M_DAEDALUS_BOMBER_ADDON_POWER_ARMOR(self); + } + + // Regular armor configuration + if (!st.was_key_specified("armor_type") && M_DAEDALUS_BOMBER_INITIAL_ARMOR > 0) { + self->monsterinfo.armor_type = IT_ARMOR_COMBAT; + if (!st.was_key_specified("armor_power")) + self->monsterinfo.armor_power = M_DAEDALUS_BOMBER_ADDON_ARMOR(self); + } + + int base_health = M_DAEDALUS_BOMBER_INITIAL_HEALTH; + if (g_horde && g_horde->integer && current_wave_level > 0) { + bool is_boss = self->monsterinfo.IS_BOSS && !self->monsterinfo.BOSS_DEATH_HANDLED; + self->health = ScaleMonsterHealth(base_health, current_wave_level, is_boss); + } else { + self->health = base_health * st.health_multiplier; + } + // The classname is still "monster_daedalus_bomber", so when SP_monster_hover + // is eventually called, it will get the correct ID from the registry. +} \ No newline at end of file diff --git a/to add/m_redmutant.cpp b/to add/m_redmutant.cpp new file mode 100644 index 00000000..790339e9 --- /dev/null +++ b/to add/m_redmutant.cpp @@ -0,0 +1,786 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +mutant + +============================================================================== +*/ + +#include "g_local.h" +#include "m_redmutant.h" +#include "shared.h" +#include "horde/g_horde_scaling.h" +#include "monster_constants.h" + +constexpr spawnflags_t SPAWNFLAG_REDMUTANT_NOJUMPING = 8_spawnflag; + +static cached_soundindex sound_swing; +static cached_soundindex sound_hit; +static cached_soundindex sound_hit2; +static cached_soundindex sound_death; +static cached_soundindex sound_idle; +static cached_soundindex sound_pain1; +static cached_soundindex sound_pain2; +static cached_soundindex sound_sight; +static cached_soundindex sound_search; +static cached_soundindex sound_step1; +static cached_soundindex sound_step2; +static cached_soundindex sound_step3; +static cached_soundindex sound_thud; + +// +// SOUNDS +// + +void redmutant_step(edict_t* self) +{ + int const n = irandom(3); + if (n == 0) + gi.sound(self, CHAN_BODY, sound_step1, 1, ATTN_NORM, 0); + else if (n == 1) + gi.sound(self, CHAN_BODY, sound_step2, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_BODY, sound_step3, 1, ATTN_NORM, 0); +} + +MONSTERINFO_SIGHT(redmutant_sight) (edict_t* self, edict_t* other) -> void +{ + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +MONSTERINFO_SEARCH(redmutant_search) (edict_t* self) -> void +{ + gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); +} + +void redmutant_swing(edict_t* self) +{ + gi.sound(self, CHAN_VOICE, sound_swing, 1, ATTN_NORM, 0); +} + +// +// STAND +// + +mframe_t redmutant_frames_stand[] = { + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand } +}; +MMOVE_T(redmutant_move_stand) = { FRAME_stand101, FRAME_stand112, redmutant_frames_stand, nullptr }; + +MONSTERINFO_STAND(redmutant_stand) (edict_t* self) -> void +{ + M_SetAnimation(self, &redmutant_move_stand); +} + +// +// IDLE +// + +void redmutant_idle_loop(edict_t* self) +{ + if (frandom() < 0.75f) + self->monsterinfo.nextframe = FRAME_stand201; +} + +mframe_t redmutant_frames_idle[] = { + { ai_stand, 0, redmutant_idle_loop }, // scratch loop end + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand } +}; +MMOVE_T(redmutant_move_idle) = { FRAME_stand202, FRAME_stand228, redmutant_frames_idle, redmutant_stand }; + +MONSTERINFO_IDLE(redmutant_idle) (edict_t* self) -> void +{ + M_SetAnimation(self, &redmutant_move_idle); + gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + +// +// WALK +// + +mframe_t redmutant_frames_walk[] = { + { ai_walk, 3 }, + { ai_walk, 1 }, + { ai_walk, 5 }, + { ai_walk, 10 }, + { ai_walk, 13 }, + { ai_walk, 10 }, + { ai_walk }, + { ai_walk, 5 }, + { ai_walk, 6 }, + { ai_walk, 16 }, + { ai_walk, 15 }, + { ai_walk, 6 } +}; +MMOVE_T(redmutant_move_walk) = { FRAME_walk05, FRAME_walk16, redmutant_frames_walk, nullptr }; + +void redmutant_walk_loop(edict_t* self) +{ + M_SetAnimation(self, &redmutant_move_walk); +} + +mframe_t redmutant_frames_start_walk[] = { + { ai_walk, 5 }, + { ai_walk, 5 }, + { ai_walk, -2 }, + { ai_walk, 1 } +}; +MMOVE_T(redmutant_move_start_walk) = { FRAME_walk01, FRAME_walk04, redmutant_frames_start_walk, redmutant_walk_loop }; + +MONSTERINFO_WALK(redmutant_walk) (edict_t* self) -> void +{ + M_SetAnimation(self, &redmutant_move_start_walk); +} + +// +// RUN +// + +mframe_t redmutant_frames_run[] = { + { ai_run, 44 }, + { ai_run, 44, redmutant_step }, + { ai_run, 28 }, + { ai_run, 8, redmutant_step }, + { ai_run, 22 }, + { ai_run, 15 } +}; +MMOVE_T(redmutant_move_run) = { FRAME_run03, FRAME_run08, redmutant_frames_run, nullptr }; + +MONSTERINFO_RUN(redmutant_run) (edict_t* self) -> void +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + M_SetAnimation(self, &redmutant_move_stand); + else + M_SetAnimation(self, &redmutant_move_run); +} + +// +// MELEE +// + +void redmutant_hit_left(edict_t* self) +{ + if (!M_HasValidTarget(self)) + { + self->monsterinfo.melee_debounce_time = level.time + 1.5_sec; + return; + } + + int damage = M_GET_DMG_OR(self, MELEE, 15); + + vec3_t const aim = { MELEE_DISTANCE, self->mins[0], 8 }; + if (fire_hit(self, aim, damage * M_DamageModifier(self), 100)) + gi.sound(self, CHAN_WEAPON, sound_hit, 1, ATTN_NORM, 0); + else + { + gi.sound(self, CHAN_WEAPON, sound_swing, 1, ATTN_NORM, 0); + self->monsterinfo.melee_debounce_time = level.time + 1.5_sec; + } +} + +void redmutant_hit_right(edict_t* self) +{ + if (!M_HasValidTarget(self)) + { + self->monsterinfo.melee_debounce_time = level.time + 1.5_sec; + return; + } + + int damage = M_GET_DMG_OR(self, MELEE, 15); + + vec3_t const aim = { MELEE_DISTANCE, self->maxs[0], 8 }; + if (fire_hit(self, aim, damage * M_DamageModifier(self), 100)) + gi.sound(self, CHAN_WEAPON, sound_hit2, 1, ATTN_NORM, 0); + else + { + gi.sound(self, CHAN_WEAPON, sound_swing, 1, ATTN_NORM, 0); + self->monsterinfo.melee_debounce_time = level.time + 1.5_sec; + } +} + +void redmutant_check_refire(edict_t* self) +{ + if (!self->enemy || !self->enemy->inuse || self->enemy->health <= 0) + return; + + if ((self->monsterinfo.melee_debounce_time <= level.time) && ((frandom() < 0.5f) || (range_to(self, self->enemy) <= RANGE_MELEE))) + self->monsterinfo.nextframe = FRAME_attack109; +} + +mframe_t redmutant_frames_attack[] = { + { ai_charge }, + { ai_charge, 0, redmutant_hit_left }, + { ai_charge, 0, redmutant_hit_right }, + { ai_charge }, + { ai_charge, 0, redmutant_hit_right }, + { ai_charge }, + { ai_charge, 0, redmutant_check_refire } +}; +MMOVE_T(redmutant_move_attack) = { FRAME_attack109, FRAME_attack115, redmutant_frames_attack, redmutant_run }; + +MONSTERINFO_MELEE(redmutant_melee) (edict_t* self) -> void +{ + M_SetAnimation(self, &redmutant_move_attack); +} + +// +// ATTACK +// + +TOUCH(redmutant_jump_touch) (edict_t* self, edict_t* other, const trace_t& tr, bool other_touching_self) -> void +{ + if (self->health <= 0) + { + self->touch = nullptr; + return; + } + + if (self->style == 1 && other->takedamage) + { + // [Paril-KEX] only if we're actually moving fast enough to hurt + if (self->velocity.length() > 30) + { + vec3_t point; + vec3_t normal; + int damage; + + normal = self->velocity; + normal.normalize(); + point = self->s.origin + (normal * self->maxs[0]); + damage = (int)frandom(70, 80); + T_Damage(other, self, self, self->velocity, point, normal, damage, damage, DAMAGE_NONE, MOD_UNKNOWN); + self->style = 0; + } + } + + if (!M_CheckBottom(self)) + { + if (self->groundentity) + { + self->monsterinfo.nextframe = FRAME_attack102; + self->touch = nullptr; + } + return; + } + + self->touch = nullptr; +} + +void redmutant_jump_takeoff(edict_t* self) +{ + vec3_t forward; + + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); + AngleVectors(self->s.angles, forward, nullptr, nullptr); + self->s.origin[2] += 1; + self->velocity = forward * 1125; + self->velocity[2] = 160; + self->groundentity = nullptr; + self->monsterinfo.aiflags |= AI_DUCKED; + self->monsterinfo.attack_finished = level.time + 1.3_sec; + self->style = 1; + self->touch = redmutant_jump_touch; +} + +void redmutant_check_landing(edict_t* self) +{ + monster_jump_finished(self); + + if (self->groundentity) + { + gi.sound(self, CHAN_WEAPON, sound_thud, 1, ATTN_NORM, 0); + self->monsterinfo.attack_finished = level.time + random_time(500_ms, 1.5_sec); + + if (self->monsterinfo.unduck) + self->monsterinfo.unduck(self); + + if (range_to(self, self->enemy) <= RANGE_MELEE * 2.f) + self->monsterinfo.melee(self); + + return; + } + + if (level.time > self->monsterinfo.attack_finished) + self->monsterinfo.nextframe = FRAME_attack101; + else + self->monsterinfo.nextframe = FRAME_attack108; +} + +mframe_t redmutant_frames_jump[] = { + { ai_charge }, + { ai_charge, 17 }, + { ai_charge, 15, redmutant_jump_takeoff }, + { ai_charge, 15 }, + { ai_charge, 15, redmutant_check_landing }, + { ai_charge }, + { ai_charge, 3 }, + { ai_charge } +}; +MMOVE_T(redmutant_move_jump) = { FRAME_attack101, FRAME_attack108, redmutant_frames_jump, redmutant_run }; + +MONSTERINFO_ATTACK(redmutant_jump) (edict_t* self) -> void +{ + M_SetAnimation(self, &redmutant_move_jump); +} + +// +// CHECKATTACK +// + +bool redmutant_check_melee(edict_t* self) +{ + return range_to(self, self->enemy) <= RANGE_MELEE && self->monsterinfo.melee_debounce_time <= level.time; +} + +bool redmutant_check_jump(edict_t* self) +{ + if (!M_HasValidTarget(self)) + { + return false; // Can't at a non-existent or dead target. + } + + vec3_t v; + + // Paril: no harm in letting them jump down if you're below them + // if (self->absmin[2] > (self->enemy->absmin[2] + 0.75 * self->enemy->size[2])) + // return false; + + // don't jump if there's no way we can reach standing height + if (self->absmin[2] + 125 < self->enemy->absmin[2]) + return false; + + v[0] = self->s.origin[0] - self->enemy->s.origin[0]; + v[1] = self->s.origin[1] - self->enemy->s.origin[1]; + v[2] = 0; + float const distance_sq = v.lengthSquared(); + + // if we're not trying to avoid a melee, then don't jump + if (distance_sq < 10000.f && self->monsterinfo.melee_debounce_time <= level.time) // 100^2 + return false; + // only use it to close distance gaps + if (distance_sq > 70225.f) // 265^2 + return false; + + return self->monsterinfo.attack_finished < level.time && brandom(); +} + +MONSTERINFO_CHECKATTACK(redmutant_checkattack) (edict_t* self) -> bool +{ + if (!self->enemy || self->enemy->health <= 0) + return false; + + if (redmutant_check_melee(self)) + { + self->monsterinfo.attack_state = AS_MELEE; + return true; + } + + if (!self->spawnflags.has(SPAWNFLAG_REDMUTANT_NOJUMPING) && redmutant_check_jump(self)) + { + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + + return false; +} + +// +// PAIN +// + +mframe_t redmutant_frames_pain1[] = { + { ai_move, 4 }, + { ai_move, -3 }, + { ai_move, -8 }, + { ai_move, 2 }, + { ai_move, 5 } +}; +MMOVE_T(redmutant_move_pain1) = { FRAME_pain101, FRAME_pain105, redmutant_frames_pain1, redmutant_run }; + +mframe_t redmutant_frames_pain2[] = { + { ai_move, -24 }, + { ai_move, 11 }, + { ai_move, 5 }, + { ai_move, -2 }, + { ai_move, 6 }, + { ai_move, 4 } +}; +MMOVE_T(redmutant_move_pain2) = { FRAME_pain201, FRAME_pain206, redmutant_frames_pain2, redmutant_run }; + +mframe_t redmutant_frames_pain3[] = { + { ai_move, -22 }, + { ai_move, 3 }, + { ai_move, 3 }, + { ai_move, 2 }, + { ai_move, 1 }, + { ai_move, 1 }, + { ai_move, 6 }, + { ai_move, 3 }, + { ai_move, 2 }, + { ai_move }, + { ai_move, 1 } +}; +MMOVE_T(redmutant_move_pain3) = { FRAME_pain301, FRAME_pain311, redmutant_frames_pain3, redmutant_run }; + +PAIN(redmutant_pain) (edict_t* self, edict_t* other, float kick, int damage, const mod_t& mod) -> void +{ + float r; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3_sec; + + r = frandom(); + if (r < 0.33f) + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + else if (r < 0.66f) + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + + if (!M_ShouldReactToPain(self, mod)) + return; // no pain anims in nightmare + + if (r < 0.33f) + M_SetAnimation(self, &redmutant_move_pain1); + else if (r < 0.66f) + M_SetAnimation(self, &redmutant_move_pain2); + else + M_SetAnimation(self, &redmutant_move_pain3); +} + +MONSTERINFO_SETSKIN(redmutant_setskin) (edict_t* self) -> void +{ +} + +// +// DEATH +// + +void redmutant_shrink(edict_t* self) +{ + self->maxs[2] = 0; + self->svflags |= SVF_DEADMONSTER; + gi.linkentity(self); +} + +// [Paril-KEX] +static void ai_move_slide_right(edict_t* self, float dist) +{ + M_walkmove(self, self->s.angles[YAW] + 90, dist); +} + +static void ai_move_slide_left(edict_t* self, float dist) +{ + M_walkmove(self, self->s.angles[YAW] - 90, dist); +} + +mframe_t redmutant_frames_death1[] = { + { ai_move_slide_right }, + { ai_move_slide_right }, + { ai_move_slide_right }, + { ai_move_slide_right, 2 }, + { ai_move_slide_right, 5 }, + { ai_move_slide_right, 7, redmutant_shrink }, + { ai_move_slide_right, 6 }, + { ai_move_slide_right, 2 }, + { ai_move_slide_right }, + { ai_move_slide_right }, + { ai_move_slide_right }, + { ai_move_slide_right }, + { ai_move_slide_right }, + { ai_move_slide_right }, + { ai_move_slide_right }, + { ai_move_slide_right }, + { ai_move_slide_right }, + { ai_move_slide_right }, + { ai_move_slide_right }, + { ai_move_slide_right } +}; +MMOVE_T(redmutant_move_death1) = { FRAME_death101, FRAME_death120, redmutant_frames_death1, monster_dead }; + +mframe_t redmutant_frames_death2[] = { + { ai_move_slide_left }, + { ai_move_slide_left, 1 }, + { ai_move_slide_left, 6 }, + { ai_move_slide_left, 8 }, + { ai_move_slide_left, 3, redmutant_shrink }, + { ai_move_slide_left, 2 }, + { ai_move_slide_left } +}; +MMOVE_T(redmutant_move_death2) = { FRAME_death201, FRAME_death207, redmutant_frames_death2, monster_dead }; + +DIE(redmutant_die) (edict_t* self, edict_t* inflictor, edict_t* attacker, int damage, const vec3_t& point, const mod_t& mod) -> void +{ + //OnEntityDeath(self); + if (M_CheckGib(self, mod)) + { + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); + + self->s.skinnum /= 2; + + ThrowGibs(self, damage, { + { 2, "models/objects/gibs/bone/tris.md2" }, + { 4, "models/objects/gibs/sm_meat/tris.md2" }, + { 2, "models/monsters/mutant/gibs/hand.md2", GIB_SKINNED | GIB_UPRIGHT }, + { 2, "models/monsters/mutant/gibs/foot.md2", GIB_SKINNED }, + { "models/monsters/mutant/gibs/chest.md2", GIB_SKINNED }, + { "models/monsters/mutant/gibs/head.md2", GIB_SKINNED | GIB_HEAD } + }); + + self->deadflag = true; + return; + } + + if (self->deadflag) + return; + + gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + self->deadflag = true; + self->takedamage = true; + + if (frandom() < 0.5f) + M_SetAnimation(self, &redmutant_move_death1); + else + M_SetAnimation(self, &redmutant_move_death2); +} + +//================ +// ROGUE +void redmutant_jump_down(edict_t* self) +{ + vec3_t forward, up; + + AngleVectors(self->s.angles, forward, nullptr, up); + self->velocity += (forward * 100); + self->velocity += (up * 300); +} + +void redmutant_jump_up(edict_t* self) +{ + vec3_t forward, up; + + AngleVectors(self->s.angles, forward, nullptr, up); + self->velocity += (forward * 200); + self->velocity += (up * 450); +} + +void redmutant_jump_wait_land(edict_t* self) +{ + if (!monster_jump_finished(self) && self->groundentity == nullptr) + self->monsterinfo.nextframe = self->s.frame; + else + self->monsterinfo.nextframe = self->s.frame + 1; +} + +mframe_t redmutant_frames_jump_up[] = { + { ai_move, -8 }, + { ai_move, -8, redmutant_jump_up }, + { ai_move, 0, redmutant_jump_wait_land }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(redmutant_move_jump_up) = { FRAME_attack101, FRAME_attack108, redmutant_frames_jump_up, redmutant_run }; + +mframe_t redmutant_frames_jump_down[] = { + { ai_move }, + { ai_move, 0, redmutant_jump_down }, + { ai_move, 0, redmutant_jump_wait_land }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(redmutant_move_jump_down) = { FRAME_attack101, FRAME_attack108, redmutant_frames_jump_down, redmutant_run }; + +void redmutant_jump_updown(edict_t* self, blocked_jump_result_t result) +{ + if (!self->enemy) + return; + + if (result == blocked_jump_result_t::JUMP_JUMP_UP) + M_SetAnimation(self, &redmutant_move_jump_up); + else + M_SetAnimation(self, &redmutant_move_jump_down); +} + +/* +=== +Blocked +=== +*/ +MONSTERINFO_BLOCKED(redmutant_blocked) (edict_t* self, float dist) -> bool +{ + if (auto const result = blocked_checkjump(self, dist); result != blocked_jump_result_t::NO_JUMP) + { + if (result != blocked_jump_result_t::JUMP_TURN) + redmutant_jump_updown(self, result); + return true; + } + + if (blocked_checkplat(self, dist)) + return true; + + return false; +} +// ROGUE +//================ + +// +// SPAWN +// + +/*QUAKED monster_redmutant (1 .5 0) (-32 -32 -24) (32 32 32) Ambush Trigger_Spawn Sight NoJumping +model="models/monsters/redmutant/tris.md2" +*/ +void SP_monster_redmutant(edict_t* self) +{ + const spawn_temp_t& st = ED_GetSpawnTemp(); + + + self->monsterinfo.monster_type_id = static_cast(horde::MonsterTypeID::REDMUTANT); if (g_horde->integer) + { + const float randomsearch = frandom(); // Generar un número aleatorio entre 0 y 1 + + if (randomsearch < 0.32f) + gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); + } + + if (!M_AllowSpawn(self)) { + G_FreeEdict(self); + return; + } + + sound_swing.assign("mutant/mutatck1.wav"); + sound_hit.assign("mutant/mutatck2.wav"); + sound_hit2.assign("mutant/mutatck3.wav"); + sound_death.assign("mutant/mutdeth1.wav"); + sound_idle.assign("mutant/mutidle1.wav"); + sound_pain1.assign("mutant/mutpain1.wav"); + sound_pain2.assign("mutant/mutpain2.wav"); + sound_sight.assign("mutant/mutsght1.wav"); + sound_search.assign("mutant/mutsrch1.wav"); + sound_step1.assign("mutant/step1.wav"); + sound_step2.assign("mutant/step2.wav"); + sound_step3.assign("mutant/step3.wav"); + sound_thud.assign("mutant/thud1.wav"); + + self->monsterinfo.aiflags |= AI_STINKY; + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/vault/monsters/mutant/tris.md2"); + + gi.modelindex("models/monsters/mutant/gibs/head.md2"); + gi.modelindex("models/monsters/mutant/gibs/chest.md2"); + gi.modelindex("models/monsters/mutant/gibs/hand.md2"); + gi.modelindex("models/monsters/mutant/gibs/foot.md2"); + + self->mins = { -18, -18, -24 }; + self->maxs = { 18, 18, 30 }; + + // Power armor configuration + if (!st.was_key_specified("power_armor_type") && M_REDMUTANT_POWER_ARMOR_TYPE != IT_NULL) { + self->monsterinfo.power_armor_type = static_cast(M_REDMUTANT_POWER_ARMOR_TYPE); + if (!st.was_key_specified("power_armor_power")) + self->monsterinfo.power_armor_power = M_REDMUTANT_ADDON_POWER_ARMOR(self); + } + + // Regular armor configuration + if (!st.was_key_specified("armor_type") && M_REDMUTANT_INITIAL_ARMOR > 0) { + self->monsterinfo.armor_type = IT_ARMOR_COMBAT; + if (!st.was_key_specified("armor_power")) + self->monsterinfo.armor_power = M_REDMUTANT_ADDON_ARMOR(self); + } + + + int base_health = M_REDMUTANT_INITIAL_HEALTH; + if (g_horde && g_horde->integer && current_wave_level > 0) { + self->health = ScaleMonsterHealth(base_health, current_wave_level, false); + } else { + self->health = base_health * st.health_multiplier; + } + self->gib_health = -120; + self->mass = 450; + + if (self->monsterinfo.IS_BOSS && !self->monsterinfo.BOSS_DEATH_HANDLED) { + self->health *= 3.8f; + self->gib_health = -999777; + self->mass *= 3.0f; + } + + self->pain = redmutant_pain; + self->die = redmutant_die; + + self->monsterinfo.stand = redmutant_stand; + self->monsterinfo.walk = redmutant_walk; + self->monsterinfo.run = redmutant_run; + self->monsterinfo.dodge = nullptr; + self->monsterinfo.attack = redmutant_jump; + self->monsterinfo.melee = redmutant_melee; + self->monsterinfo.sight = redmutant_sight; + self->monsterinfo.search = redmutant_search; + self->monsterinfo.idle = redmutant_idle; + self->monsterinfo.checkattack = redmutant_checkattack; + self->monsterinfo.blocked = redmutant_blocked; // PGM + self->monsterinfo.setskin = redmutant_setskin; + + gi.linkentity(self); + + M_SetAnimation(self, &redmutant_move_stand); + + self->monsterinfo.combat_style = COMBAT_MELEE; + + self->monsterinfo.scale = MODEL_SCALE; + self->monsterinfo.can_jump = !(self->spawnflags & SPAWNFLAG_REDMUTANT_NOJUMPING); + self->monsterinfo.drop_height = 256; + // HORDE MOD: Increased jump height from 68 to 88 (30% increase) for better obstacle navigation + self->monsterinfo.jump_height = 88; + + walkmonster_start(self); + + ApplyMonsterBonusFlags(self); +} \ No newline at end of file diff --git a/to add/m_redmutant.h b/to add/m_redmutant.h new file mode 100644 index 00000000..b8089e95 --- /dev/null +++ b/to add/m_redmutant.h @@ -0,0 +1,156 @@ +#pragma once +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// RedMutant + +// This file generated by ModelGen - Do NOT Modify + +enum +{ + FRAME_attack101, + FRAME_attack102, + FRAME_attack103, + FRAME_attack104, + FRAME_attack105, + FRAME_attack106, + FRAME_attack107, + FRAME_attack108, + FRAME_attack109, + FRAME_attack110, + FRAME_attack111, + FRAME_attack112, + FRAME_attack113, + FRAME_attack114, + FRAME_attack115, + FRAME_attack116, + FRAME_attack117, + FRAME_attack118, + FRAME_attack119, + FRAME_attack120, + FRAME_attack121, + FRAME_attack122, + FRAME_attack123, + FRAME_attack124, + FRAME_attack125, + FRAME_attack126, + FRAME_death101, + FRAME_death102, + FRAME_death103, + FRAME_death104, + FRAME_death105, + FRAME_death106, + FRAME_death107, + FRAME_death108, + FRAME_death109, + FRAME_death110, + FRAME_death111, + FRAME_death112, + FRAME_death113, + FRAME_death114, + FRAME_death115, + FRAME_death116, + FRAME_death117, + FRAME_death118, + FRAME_death119, + FRAME_death120, + FRAME_death201, + FRAME_death202, + FRAME_death203, + FRAME_death204, + FRAME_death205, + FRAME_death206, + FRAME_death207, + FRAME_pain101, + FRAME_pain102, + FRAME_pain103, + FRAME_pain104, + FRAME_pain105, + FRAME_pain201, + FRAME_pain202, + FRAME_pain203, + FRAME_pain204, + FRAME_pain205, + FRAME_pain206, + FRAME_pain301, + FRAME_pain302, + FRAME_pain303, + FRAME_pain304, + FRAME_pain305, + FRAME_pain306, + FRAME_pain307, + FRAME_pain308, + FRAME_pain309, + FRAME_pain310, + FRAME_pain311, + FRAME_run03, + FRAME_run04, + FRAME_run05, + FRAME_run06, + FRAME_run07, + FRAME_run08, + FRAME_stand101, + FRAME_stand102, + FRAME_stand103, + FRAME_stand104, + FRAME_stand105, + FRAME_stand106, + FRAME_stand107, + FRAME_stand108, + FRAME_stand109, + FRAME_stand110, + FRAME_stand111, + FRAME_stand112, + FRAME_stand201, + FRAME_stand202, + FRAME_stand203, + FRAME_stand204, + FRAME_stand205, + FRAME_stand206, + FRAME_stand207, + FRAME_stand208, + FRAME_stand209, + FRAME_stand210, + FRAME_stand211, + FRAME_stand212, + FRAME_stand213, + FRAME_stand214, + FRAME_stand215, + FRAME_stand216, + FRAME_stand217, + FRAME_stand218, + FRAME_stand219, + FRAME_stand220, + FRAME_stand221, + FRAME_stand222, + FRAME_stand223, + FRAME_stand224, + FRAME_stand225, + FRAME_stand226, + FRAME_stand227, + FRAME_stand228, + FRAME_walk01, + FRAME_walk02, + FRAME_walk03, + FRAME_walk04, + FRAME_walk05, + FRAME_walk06, + FRAME_walk07, + FRAME_walk08, + FRAME_walk09, + FRAME_walk10, + FRAME_walk11, + FRAME_walk12, + FRAME_walk13, + FRAME_walk14, + FRAME_walk15, + FRAME_walk16, + FRAME_walk17, + FRAME_walk18, + FRAME_walk19, + FRAME_walk20, + FRAME_walk21, + FRAME_walk22, + FRAME_walk23, +}; + +constexpr float MODEL_SCALE = 1.000000f; diff --git a/to add/m_runnertank.cpp b/to add/m_runnertank.cpp new file mode 100644 index 00000000..349f9b29 --- /dev/null +++ b/to add/m_runnertank.cpp @@ -0,0 +1,1806 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +/* +============================================================================== + +runnertank + +============================================================================== +*/ + +#include "g_local.h" + +#include "m_runnertank.h" + +#include "m_flash.h" +#include "shared.h" +#include "horde/g_horde_scaling.h" +#include "monster_constants.h" +void runnertankStrike(edict_t* self); +void runnertank_refire_rocket(edict_t* self); +//void runnetank_doattack_rocket(edict_t* self); +void runnertank_reattack_blaster(edict_t* self); +void runnertank_attack_finished(edict_t* self); +//bool runnertank_check_wall(edict_t* self, float dist); + +static cached_soundindex sound_thud; +static cached_soundindex sound_pain, sound_pain2; +static cached_soundindex sound_idle; +static cached_soundindex sound_die; +static cached_soundindex sound_step; +static cached_soundindex sound_sight; +static cached_soundindex sound_windup; +static cached_soundindex sound_strike; + +constexpr spawnflags_t SPAWNFLAG_runnertank_COMMANDER_GUARDIAN = 8_spawnflag; +constexpr spawnflags_t SPAWNFLAG_runnertank_COMMANDER_HEAT_SEEKING = 16_spawnflag; + +// +// misc +// + +MONSTERINFO_SIGHT(runnertank_sight) (edict_t* self, edict_t* other) -> void +{ + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +void runnertank_footstep(edict_t* self) +{ +brandom() ? gi.sound(self, CHAN_BODY, sound_step, 1.f, ATTN_NORM, 0) + : gi.sound(self, CHAN_BODY, sound_step, 0.75f, ATTN_NORM, 0); +} + +void runnertank_thud(edict_t* self) +{ + gi.sound(self, CHAN_BODY, sound_thud, 1, ATTN_NORM, 0); +} + +void runnertank_windup(edict_t* self) +{ + gi.sound(self, CHAN_WEAPON, sound_windup, 1, ATTN_NORM, 0); +} + +MONSTERINFO_IDLE(runnertank_idle) (edict_t* self) -> void +{ + gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + +// +// stand +// + +mframe_t runnertank_frames_stand[] = { + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand }, + { ai_stand } +}; +MMOVE_T(runnertank_move_stand) = { FRAME_stand01, FRAME_stand30, runnertank_frames_stand, nullptr }; + +MONSTERINFO_STAND(runnertank_stand) (edict_t* self) -> void +{ + M_SetAnimation(self, &runnertank_move_stand); +} + +// +// walk +// + +void runnertank_walk(edict_t* self); + +// Animación de caminata corregida +mframe_t runnertank_frames_walk[] = { + { ai_walk, 4 }, { ai_walk, 5 }, { ai_walk, 3 }, { ai_walk, 2 }, + { ai_walk, 5 }, { ai_walk, 5 }, { ai_walk, 4 }, { ai_walk, 4, runnertank_footstep }, + { ai_walk, 3 }, { ai_walk, 5 }, { ai_walk, 4 }, { ai_walk, 5 }, + { ai_walk, 7 }, { ai_walk, 7 }, { ai_walk, 6 }, { ai_walk, 6, runnertank_footstep }, + { ai_walk, 4 }, { ai_walk, 5 }, { ai_walk, 3 }, { ai_walk, 2 }, + { ai_walk, 5 }, { ai_walk, 5 }, { ai_walk, 4 }, { ai_walk, 4, runnertank_footstep }, + { ai_walk, 3 }, { ai_walk, 5 }, { ai_walk, 4 }, { ai_walk, 5 }, + { ai_walk, 7 }, { ai_walk, 7 }, { ai_walk, 6 }, { ai_walk, 6, runnertank_footstep }, + { ai_walk, 4 }, { ai_walk, 5 }, { ai_walk, 3 }, { ai_walk, 2 }, + { ai_walk, 4 }, { ai_walk, 5 } +}; +MMOVE_T(runnertank_move_walk) = { FRAME_walk01, FRAME_walk38, runnertank_frames_walk, runnertank_walk }; + + +mframe_t runnertank_frames_start_walk[] = { + { ai_walk }, + { ai_walk, 6 }, + { ai_walk, 6 }, + { ai_walk, 11, runnertank_footstep }, + { ai_walk }, + { ai_walk, 6 }, + { ai_walk, 6 }, + { ai_walk, 11, runnertank_footstep } + , { ai_walk }, + { ai_walk, 6 }, + { ai_walk, 6 }, + { ai_walk, 11, runnertank_footstep } + , { ai_walk }, + { ai_walk, 6 }, + { ai_walk, 6 }, + { ai_walk, 11, runnertank_footstep }, + { ai_walk }, + { ai_walk, 6 }, + { ai_walk, 6 }, + { ai_walk, 11, runnertank_footstep } + { ai_walk }, + { ai_walk, 6 } +}; +MMOVE_T(runnertank_move_start_walk) = { FRAME_walk15, FRAME_walk22, runnertank_frames_start_walk, runnertank_walk }; + +mframe_t runnertank_frames_stop_walk[] = { + { ai_walk, 3 }, + { ai_walk, 3 }, + { ai_walk, 2 }, + { ai_walk, 2 }, + { ai_walk, 4, runnertank_footstep } +}; +MMOVE_T(runnertank_move_stop_walk) = { FRAME_walk21, FRAME_walk25, runnertank_frames_stop_walk, runnertank_stand }; + +void runnertank_run(edict_t* self); + +// mframe_t runnertank_frames_start_walk[] = { +// { ai_walk, 0 }, { ai_walk, 3 }, { ai_walk, 3 }, { ai_walk, 3 } +// }; +// MMOVE_T(runnertank_move_start_walk) = { FRAME_walk01, FRAME_walk04, runnertank_frames_start_walk, runnertank_walk }; + +// mframe_t runnertank_frames_stop_walk[] = { +// { ai_walk, 3 }, { ai_walk, 3 }, { ai_walk, 2 }, { ai_walk, 2 }, +// { ai_walk, 0, runnertank_footstep } +// }; +// MMOVE_T(runnertank_move_stop_walk) = { FRAME_walk34, FRAME_walk38, runnertank_frames_stop_walk, runnertank_stand }; + +void runnertank_walk_to_run(edict_t* self); +void runnertank_stop_run_to_attack(edict_t* self); + +//mframe_t runnertank_frames_start_run[] = { +// { ai_run }, +// { ai_run, 6 }, +// { ai_run, 6 }, +// { ai_run, 11, runnertank_footstep } +//}; +//MMOVE_T(runnertank_move_start_run) = { FRAME_walk01, FRAME_walk04, runnertank_frames_start_run, runnertank_run }; + +mframe_t runnertank_frames_start_run[] = { + { ai_run, 6 }, { ai_run, 5 }, { ai_run, 6 }, { ai_run, 7, runnertank_footstep } +}; +MMOVE_T(runnertank_move_start_run) = { FRAME_walk35, FRAME_walk38, runnertank_frames_start_run, runnertank_walk_to_run }; + + +// Ajustar la función de caminata para una transición más suave +MONSTERINFO_WALK(runnertank_walk) (edict_t* self) -> void +{ + if (self->monsterinfo.active_move != &runnertank_move_walk) + { + M_SetAnimation(self, &runnertank_move_start_walk); + } + else + { + M_SetAnimation(self, &runnertank_move_walk); + } +} + +// +// run +// +// +// Actualizar la animación de carrera +mframe_t runnertank_frames_run[] = { + { ai_run, 14, runnertank_footstep }, + { ai_run, 18, nullptr }, + { ai_run, 15, nullptr }, + { ai_run, 15, nullptr }, + { ai_run, 15, nullptr }, + { ai_run, 19, runnertank_footstep }, + { ai_run, 15, nullptr }, + { ai_run, 13, nullptr }, + { ai_run, 18, nullptr }, + { ai_run, 17, nullptr } +}; +MMOVE_T(runnertank_move_run) = { FRAME_run01, FRAME_run10, runnertank_frames_run, nullptr }; + + +mframe_t runnertank_frames_stop_run[] = { + { ai_run, 3 }, + { ai_run, 3 }, + { ai_run, 2 }, + { ai_run, 2 }, + { ai_run, 4, runnertank_footstep } +}; +MMOVE_T(runnertank_move_stop_run) = { FRAME_walk21, FRAME_walk25, runnertank_frames_stop_run, runnertank_stop_run_to_attack }; + +// Función para manejar la transición de caminata a carrera +void runnertank_walk_to_run(edict_t* self) +{ + if (!M_HasValidTarget(self)) + { + return; + } + + if (range_to(self, self->enemy) > RANGE_NEAR) + { + M_SetAnimation(self, &runnertank_move_run); + self->monsterinfo.aiflags |= AI_CHARGING; + } + else + { + M_SetAnimation(self, &runnertank_move_walk); + } +} + + + +bool runnertank_enemy_visible(edict_t* self) +{ + return self->enemy && visible(self, self->enemy); +} + +void runnertank_attack(edict_t* self); +void runnertank_consider_strafe(edict_t* self); + + +// Forward declarations for jump attack +void runnertank_jump_attack_takeoff(edict_t* self); +void runnertank_high_gravity(edict_t* self); +void runnertank_check_jump_landing(edict_t* self); + +mframe_t tank_frames_punch_attack[] = +{ + {ai_charge, 0, nullptr}, + {ai_charge, 0, nullptr}, + {ai_charge, 0, nullptr}, + {ai_charge, 0, nullptr}, + {ai_charge, 0, runnertankStrike}, // FRAME_attak225 - Añadir footstep aquí + {ai_charge, 0, nullptr}, // FRAME_attak226 - Engendrar monstruo aquí + {ai_charge, -1, nullptr}, + {ai_charge, -1, nullptr}, + {ai_charge, -1, nullptr}, + {ai_charge, -1, nullptr}, + {ai_charge, -1, nullptr}, + {ai_charge, -1, nullptr}, + {ai_charge, -1, nullptr}, + {ai_charge, -2, nullptr} // FRAME_attak229 +}; +MMOVE_T(tank_move_punch_attack) = { FRAME_attak222, FRAME_attak235, tank_frames_punch_attack, runnertank_run }; + +// Jump attack animation - using frames that make sense for jumping +mframe_t runnertank_frames_jump_attack[] = +{ + {ai_charge, 15, runnertank_jump_attack_takeoff}, // Launch immediately! + {ai_move, 0, runnertank_high_gravity}, // In air 1 + {ai_move, 0, runnertank_check_jump_landing}, // Check landing (loops here until landed) + {ai_move, 0, nullptr}, // Landing recovery 1 + {ai_move, 0, nullptr} // Recovery 2 +}; +MMOVE_T(runnertank_move_jump_attack) = { FRAME_run01, FRAME_run05, runnertank_frames_jump_attack, runnertank_attack_finished }; + +MONSTERINFO_MELEE(runnertank_melee) (edict_t* self) -> void +{ + if (!M_HasValidTarget(self)) + { + return; + } + + float const range = range_to(self, self->enemy); + if (!visible(self, self->enemy)) + return; + + // Melee is only for close range punch + if (range <= MELEE_DISTANCE * 2.4f) + { + M_SetAnimation(self, &tank_move_punch_attack); + self->monsterinfo.attack_finished = level.time + 1.5_sec; + } +} + +MONSTERINFO_RUN(runnertank_run) (edict_t* self) -> void +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + M_SetAnimation(self, &runnertank_move_stand); + return; + } + else + { + M_SetAnimation(self, &runnertank_move_run); + return; + } + + // // ALWAYS set animation first to prevent getting stuck + // if (self->monsterinfo.active_move == &runnertank_move_walk || + // self->monsterinfo.active_move == &runnertank_move_start_walk) + // { + // M_SetAnimation(self, &runnertank_move_run); + // } + // else if (self->monsterinfo.active_move != &runnertank_move_run && + // self->monsterinfo.active_move != &runnertank_move_start_run) + // { + // M_SetAnimation(self, &runnertank_move_start_run); + // } + + // M_SetAnimation(self, &runnertank_move_run) + // // Try to attack if we have a valid target and attack cooldown has expired + // // Attack will override the run animation if successful + // if (M_HasValidTarget(self) && level.time >= self->monsterinfo.attack_finished) + // { + // runnertank_attack(self); + // } +} +// +// pain +// + +mframe_t runnertank_frames_pain1[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(runnertank_move_pain1) = { FRAME_pain201, FRAME_pain204, runnertank_frames_pain1, runnertank_run }; + +mframe_t runnertank_frames_pain3[] = { + { ai_move, -7 }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 2 }, + { ai_move }, + { ai_move }, + { ai_move, 3 }, + { ai_move }, + { ai_move, 2 }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0, runnertank_footstep } +}; +MMOVE_T(runnertank_move_pain3) = { FRAME_pain301, FRAME_pain316, runnertank_frames_pain3, runnertank_run }; + +PAIN(runnertank_pain) (edict_t* self, edict_t* other, float kick, int damage, const mod_t& mod) -> void +{ + if (mod.id != MOD_CHAINFIST && damage <= 10) + return; + + if (level.time < self->pain_debounce_time) + return; + + if (mod.id != MOD_CHAINFIST) + { + if (damage <= 30) + if (frandom() > 0.2f) + return; + + // don't go into pain while attacking + if ((self->s.frame >= FRAME_attak301) && (self->s.frame <= FRAME_attak330)) + return; + if ((self->s.frame >= FRAME_attak101) && (self->s.frame <= FRAME_attak116)) + return; + } + + self->pain_debounce_time = level.time + 3_sec; + + if (self->count) + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); + + if (!M_ShouldReactToPain(self, mod)) + return; // no pain anims in nightmare + + // PMM - blindfire cleanup + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + // pmm + + if (damage <= 50) + M_SetAnimation(self, &runnertank_move_pain1); + else + M_SetAnimation(self, &runnertank_move_pain3); +} + +MONSTERINFO_SETSKIN(runnertank_setskin) (edict_t* self) -> void +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum |= self->s.skinnum = gi.imageindex("models/monsters/tank/pain.pcx");; + //else + // self->s.skinnum &= ~1; +} + +// [Paril-KEX] +bool M_AdjustBlindfireTarget(edict_t* self, const vec3_t& start, const vec3_t& target, const vec3_t& right, vec3_t& out_dir); + +// +// attacks +// +// Definimos los offsets específicos para cada frame +struct RailOffset { + float x; + float y; + float z; +}; + +const RailOffset RAIL_OFFSETS[] = { + {28.7f, -18.5f, 28.7f}, // FRAME_attak110 + {24.6f, -21.5f, 30.1f}, // FRAME_attak113 + {19.8f, -23.9f, 32.1f} // FRAME_attak116 +}; + +void runnertankRail(edict_t* self) +{ + if (!M_HasEnemy(self)) + { + return; // Stop immediately if the enemy is invalid. + } + + int damage = M_GET_DMG_OR(self, RAILGUN, 45); + + vec3_t forward, right; + vec3_t start; + vec3_t dir; + monster_muzzleflash_id_t flash_number; + const RailOffset* current_offset; + + // Allow blindfire for rail gun + bool const blindfire = self->monsterinfo.aiflags & AI_MANUAL_STEERING; + if (!blindfire) + { + if (!M_HasValidTarget(self)) + return; + + if (!infront(self, self->enemy) || !visible(self, self->enemy)) + return; + } + + // Seleccionamos el offset basado en el frame actual + if (self->s.frame == FRAME_attak110) { + flash_number = MZ2_ARACHNID_RAIL2; + current_offset = &RAIL_OFFSETS[0]; + } + else if (self->s.frame == FRAME_attak113) { + flash_number = MZ2_ARACHNID_RAIL2; + current_offset = &RAIL_OFFSETS[1]; + } + else { // FRAME_attak116 + flash_number = MZ2_ARACHNID_RAIL2; + current_offset = &RAIL_OFFSETS[2]; + } + + AngleVectors(self->s.angles, forward, right, nullptr); + + // Creamos un vector temporal para el offset actual + vec3_t const custom_offset = { + current_offset->x, + current_offset->y, + current_offset->z + }; + + // Usamos el offset personalizado en lugar del monster_flash_offset + start = M_ProjectFlashSource(self, custom_offset, forward, right); + + vec3_t target; + if (blindfire) { + target = self->monsterinfo.blind_fire_target; + if (!M_AdjustBlindfireTarget(self, start, target, right, dir)) + return; + } + else { + // Check if muzzle origin can see enemy before firing + vec3_t target_pos = self->enemy->s.origin; + target_pos[2] += self->enemy->viewheight; + trace_t trace = gi.traceline(start, target_pos, self, MASK_PROJECTILE); + + if (!(trace.fraction > 0.5f || trace.ent->solid != SOLID_BSP)) + return; + + PredictAim(self, self->enemy, start, 0, false, 0.2f, &dir, nullptr); + } + + monster_fire_railgun(self, start, dir, damage, 100, flash_number); +} +void runnertankStrike(edict_t* self) +{ + gi.sound(self, CHAN_WEAPON, sound_strike, 1, ATTN_NORM, 0); + + { + gi.sound(self, CHAN_WEAPON, sound_strike, 1, ATTN_NORM, 0); + + + // Efecto visual similar al berserker + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_BERSERK_SLAM); + + vec3_t f, r, start; + AngleVectors(self->s.angles, f, r, nullptr); + start = M_ProjectFlashSource(self, { 20.f, -14.3f, -21.f }, f, r); + trace_t const tr = gi.traceline(self->s.origin, start, self, MASK_SOLID); + + gi.WritePosition(tr.endpos); + gi.WriteDir({ 0.f, 0.f, 1.f }); + gi.multicast(tr.endpos, MULTICAST_PHS, false); + int damage = M_GET_DMG_OR(self, SLAM, 45); + void T_SlamRadiusDamage(vec3_t point, edict_t * inflictor, edict_t * attacker, float damage, float kick, edict_t * ignore, float radius, mod_t mod); + // Daño radial + T_SlamRadiusDamage(tr.endpos, self, self, damage, 450.f, self, 165, MOD_TANK_PUNCH); + + } +} + + + +void runnertankRocket(edict_t* self) { + if (!M_HasEnemy(self)) + { + return; // Stop immediately if the enemy is invalid. + } + + int damage = M_GET_DMG_OR(self, ROCKET, 50); + + // Determinar flash number basado en el frame actual + monster_muzzleflash_id_t const flash_number = static_cast( + self->s.frame == FRAME_attak324 ? MZ2_TANK_ROCKET_1 : + self->s.frame == FRAME_attak327 ? MZ2_TANK_ROCKET_2 : + MZ2_TANK_ROCKET_3); + + // Obtener vectores de dirección usando destructuring + auto [forward, right, up] = AngleVectors(self->s.angles); + + // Calcular posición de inicio usando M_ProjectFlashSource + vec3_t const start = M_ProjectFlashSource(self, monster_flash_offset[flash_number], forward, right); + + // Determinar velocidad del cohete + int config_speed = self->spawnflags.has(SPAWNFLAG_runnertank_COMMANDER_HEAT_SEEKING) ? M_HEAT_SPEED(self) : M_ROCKET_SPEED(self); + int32_t const rocket_speed = config_speed > 0 ? config_speed : + (self->speed ? self->speed : + (self->spawnflags.has(SPAWNFLAG_runnertank_COMMANDER_HEAT_SEEKING) ? 500 : 650)); + + // Calcular punto objetivo + vec3_t target; + const bool blindfire = self->monsterinfo.aiflags & AI_MANUAL_STEERING; + + if (blindfire) { + target = self->monsterinfo.blind_fire_target; + vec3_t dir = target - start; + if (!M_AdjustBlindfireTarget(self, start, target, right, dir)) + return; + } + else { + if (!M_HasValidTarget(self)) + return; + // Decidir punto de objetivo basado en posición del enemigo + if (frandom() < 0.66f || start.z < self->enemy->absmin.z) { + // Apuntar al centro del cuerpo + target = self->enemy->s.origin; + target.z += self->enemy->viewheight; + } + else { + // Apuntar a los pies + target = self->enemy->s.origin; + target.z = self->enemy->absmin.z + 1; + } + } + + // Calcular dirección base + vec3_t dir = (target - start).normalized(); + + // Predicción de objetivo para disparos no ciegos + if (!blindfire && frandom() < (0.2f + ((3 - skill->integer) * 0.15f))) { + PredictAim(self, self->enemy, start, rocket_speed, false, 0, &dir, &target); + } + + // Verificar línea de visión y disparar + if (blindfire) { + if (M_AdjustBlindfireTarget(self, start, target, right, dir)) { + if (self->spawnflags.has(SPAWNFLAG_runnertank_COMMANDER_HEAT_SEEKING)) + monster_fire_heat(self, start, dir, damage, rocket_speed, flash_number, self->accel); + else + monster_fire_rocket(self, start, dir, damage, rocket_speed, flash_number); + } + } + else { + trace_t const trace = gi.traceline(start, target, self, MASK_PROJECTILE); + if (trace.fraction > 0.5f || trace.ent->solid != SOLID_BSP) { + if (self->spawnflags.has(SPAWNFLAG_runnertank_COMMANDER_HEAT_SEEKING)) + monster_fire_heat(self, start, dir, damage, rocket_speed, flash_number, self->accel); + else + monster_fire_rocket(self, start, dir, damage, rocket_speed, flash_number); + } + } +} + +void runnertankPlasmaGun(edict_t* self) { + + if (!M_HasValidTarget(self)) + { + return; // Stop immediately if the target is invalid. + } + + int damage = M_GET_DMG_OR(self, PLASMA, 35); + + // Blindfire support for plasma (like gunner ionripper) + bool blindfire = (self->monsterinfo.aiflags & AI_MANUAL_STEERING); + vec3_t target; + + if (blindfire) + { + // Blindfire mode: use blind_fire_target + if (!self->monsterinfo.blind_fire_target) + return; + target = self->monsterinfo.blind_fire_target; + } + else + { + // Normal mode: require visibility + if (!visible(self, self->enemy) || !infront(self, self->enemy)) + return; + target = self->enemy->s.origin; + } + + // Constantes del arma + constexpr float SPREAD = 0.08f; + constexpr float PREDICTION_TIME = 0.2f; + constexpr float PROJECTILE_SPEED = 700.0f; + constexpr float PLASMA_RADIUS = 40.0f; + + // Calcular flash number basado en el frame + monster_muzzleflash_id_t const flash_number = static_cast + (MZ2_TANK_MACHINEGUN_1 + (self->s.frame - FRAME_attak406)); + + // Use current angles for muzzle position - don't force rotation before firing + vec3_t forward, right, up; + AngleVectors(self->s.angles, &forward, &right, &up); + + // Calcular posición de inicio + vec3_t const start = M_ProjectFlashSource(self, monster_flash_offset[flash_number], forward, right); + + // Check if muzzle origin can see enemy before firing (skip for blindfire) + if (!blindfire) + { + vec3_t check_pos = self->enemy->s.origin; + check_pos[2] += self->enemy->viewheight; + trace_t trace = gi.traceline(start, check_pos, self, MASK_PROJECTILE); + + if (!(trace.fraction > 0.5f || trace.ent->solid != SOLID_BSP)) + return; + } + + // Calcular dirección base al objetivo + target = self->enemy->s.origin; + target.z += self->enemy->viewheight; + vec3_t dir = (target - start).normalized(); + + // Añadir dispersión a la dirección + dir += vec3_t{ + crandom() * SPREAD, + crandom() * SPREAD, + crandom() * SPREAD + }; + dir.normalize(); + + // Predicción de movimiento del objetivo + PredictAim(self, self->enemy, start, PROJECTILE_SPEED, false, + PREDICTION_TIME, &dir, nullptr); + + // Disparar el proyectil de plasma + fire_plasma(self, start, dir, damage, PROJECTILE_SPEED, + PLASMA_RADIUS, PLASMA_RADIUS); + + // Actualizar posición del último disparo + self->pos1 = target; +} + +//static void runnertank_blind_check(edict_t* self) +//{ +// if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) +// { +// const vec3_t aim = self->monsterinfo.blind_fire_target - self->s.origin; +// self->ideal_yaw = vectoyaw(aim); +// } +//} + +mframe_t runnertank_frames_attack_blast[] = { + { ai_charge }, { ai_charge }, { ai_charge }, { ai_charge }, + { ai_charge }, { ai_charge }, { ai_charge }, { ai_charge }, + { ai_charge }, { ai_charge, 0, runnertankRail }, + { ai_charge }, { ai_charge }, { ai_charge, 0, runnertankRail }, + { ai_charge }, { ai_charge }, { ai_charge, 0, runnertankRail }, + { ai_charge }, { ai_charge }, { ai_charge }, { ai_charge }, + { ai_charge }, { ai_charge } +}; +MMOVE_T(runnertank_move_attack_blast) = { FRAME_attak101, FRAME_attak122, runnertank_frames_attack_blast, runnertank_reattack_blaster }; + +mframe_t runnertank_frames_reattack_blast[] = { + { ai_charge }, + { ai_charge, 0, runnertankRail }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, runnertankRail }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge }, + { ai_charge, 0, runnertankRail } // 16 +}; +MMOVE_T(runnertank_move_reattack_blast) = { FRAME_attak111, FRAME_attak122, runnertank_frames_reattack_blast, runnertank_reattack_blaster }; + +mframe_t runnertank_frames_attack_post_blast[] = { + { ai_move }, // 17 + { ai_move }, + { ai_move, 2 }, + { ai_move, 3 }, + { ai_move, 2 }, + { ai_move, -2, runnertank_footstep } // 22 +}; +MMOVE_T(runnertank_move_attack_post_blast) = { FRAME_attak117, FRAME_attak122, runnertank_frames_attack_post_blast, runnertank_attack_finished }; + + +// Called when attack animations finish - adds cooldown before next attack +void runnertank_attack_finished(edict_t* self) +{ + // Add random cooldown after attack ends + self->monsterinfo.attack_finished = level.time + random_time(0.4_sec, 2.3_sec); + runnertank_run(self); +} + +void runnertank_reattack_blaster(edict_t* self) +{ + if (!M_HasValidTarget(self)) + { + M_SetAnimation(self, &runnertank_move_attack_post_blast); + return; + } + + if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) + { + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + M_SetAnimation(self, &runnertank_move_attack_post_blast); + return; + } + + // Check if we've been attacking too long + if (level.time >= self->monsterinfo.attack_finished) + { + M_SetAnimation(self, &runnertank_move_attack_post_blast); + return; + } + + // Reduce refire chance to prevent constant shooting + if (visible(self, self->enemy)) + if (self->enemy->health > 0) + if (frandom() <= 0.35f) // Reduced from 0.6f + { + M_SetAnimation(self, &runnertank_move_reattack_blast); + return; + } + M_SetAnimation(self, &runnertank_move_attack_post_blast); +} + +void runnertank_doattack_rocket(edict_t* self); + +void runnertank_poststrike(edict_t* self) +{ + self->enemy = nullptr; + // [Paril-KEX] + self->monsterinfo.pausetime = HOLD_FOREVER; + self->monsterinfo.stand(self); +} + +mframe_t runnertank_frames_attack_strike[] = { + { ai_charge }, { ai_charge }, { ai_charge }, { ai_charge }, + { ai_charge }, { ai_charge }, { ai_charge }, { ai_charge }, + { ai_charge }, { ai_charge }, { ai_charge }, { ai_charge }, + { ai_charge }, { ai_charge }, { ai_charge }, { ai_charge }, + { ai_charge }, { ai_charge }, { ai_charge }, { ai_charge }, + { ai_charge, 0, runnertankStrike }, { ai_charge }, { ai_charge }, + { ai_charge }, { ai_charge }, { ai_charge }, { ai_charge }, + { ai_charge }, { ai_charge }, { ai_charge }, { ai_charge }, + { ai_charge }, { ai_charge }, { ai_charge }, { ai_charge }, + { ai_charge }, { ai_charge }, { ai_charge } +}; +MMOVE_T(runnertank_move_attack_strike) = { FRAME_attak201, FRAME_attak238, runnertank_frames_attack_strike, runnertank_poststrike }; + +mframe_t runnertank_frames_attack_pre_rocket[] = { + { ai_charge }, { ai_charge }, + { ai_charge }, { ai_charge }, { ai_charge }, { ai_charge }, + { ai_charge, 0, ai_charge }, { ai_charge }, { ai_charge, 0, runnertankRocket }, { ai_charge }, + { ai_charge, 0, runnertankRocket }, { ai_charge }, { ai_charge }, { runnertankRocket, 0, ai_charge }, + { ai_charge }, { ai_charge }, { ai_charge, 0, ai_charge }, { ai_charge, 0, ai_charge }, + { ai_charge } +}; +MMOVE_T(runnertank_move_attack_pre_rocket) = { FRAME_attak303, FRAME_attak321, runnertank_frames_attack_pre_rocket, runnertank_doattack_rocket }; + +mframe_t runnertank_frames_attack_fire_rocket[] = { + { ai_charge }, { runnertankRocket }, + { ai_charge, 0, ai_charge }, + { runnertankRocket, 0, ai_charge }, + { ai_charge, 0, runnertankRocket }, + { ai_charge, 0, ai_charge } +}; +MMOVE_T(runnertank_move_attack_fire_rocket) = { FRAME_attak312, FRAME_attak322, runnertank_frames_attack_fire_rocket, runnertank_refire_rocket }; + +mframe_t runnertank_frames_attack_post_rocket[] = { + + { ai_charge, -9 }, + { ai_charge, -8 }, + { ai_charge, -7 }, + { ai_charge, -1 }, + { ai_charge, -1, runnertank_footstep }, + { ai_charge }, + { ai_charge }, + { ai_charge }, // 50 + { ai_charge }, + { ai_charge } +}; +MMOVE_T(runnertank_move_attack_post_rocket) = { FRAME_attak326, FRAME_attak335, runnertank_frames_attack_post_rocket, runnertank_attack_finished }; + + +void runnertank_refire_rocket(edict_t* self) +{ + // If the enemy is gone, the tank should finish its attack animation. + if (!M_HasValidTarget(self)) + { + M_SetAnimation(self, &runnertank_move_attack_post_rocket); + return; + } + + // PMM - blindfire cleanup + if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) + { + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + M_SetAnimation(self, &runnertank_move_attack_post_rocket); + return; + } + // pmm + + // Check if we've been attacking too long + if (level.time >= self->monsterinfo.attack_finished) + { + M_SetAnimation(self, &runnertank_move_attack_post_rocket); + return; + } + + if (self->enemy->health > 0) + if (visible(self, self->enemy)) + if (frandom() <= 0.3f) // Reduced from 0.4f + { + M_SetAnimation(self, &runnertank_move_attack_fire_rocket); + return; + } + M_SetAnimation(self, &runnertank_move_attack_post_rocket); +} + +void runnertank_doattack_rocket(edict_t* self) +{ + M_SetAnimation(self, &runnertank_move_attack_fire_rocket); +} + + +mframe_t runnertank_frames_attack_chain[] = { + { ai_charge }, + { ai_charge }, + { ai_charge, 0, runnertankPlasmaGun }, + { ai_charge, 0, runnertankPlasmaGun }, + { ai_charge, 0, runnertankPlasmaGun }, + { ai_charge, 0, runnertankPlasmaGun }, + { ai_charge, 0, runnertankPlasmaGun }, + { ai_charge, 0, runnertankPlasmaGun }, + { ai_charge, 0, runnertankPlasmaGun }, + { ai_charge, 0, runnertankPlasmaGun }, + { ai_charge, 0, runnertankPlasmaGun }, + { ai_charge } +}; +MMOVE_T(runnertank_move_attack_chain) = { FRAME_attak404, FRAME_attak415, runnertank_frames_attack_chain, runnertank_attack_finished }; + +void runnertank_stop_run_to_attack(edict_t* self) +{ + if (!M_HasValidTarget(self)) + { + M_SetAnimation(self, &runnertank_move_run); + return; + } + + // Don't attack if we're still in cooldown + if (level.time < self->monsterinfo.attack_finished) + { + M_SetAnimation(self, &runnertank_move_run); + return; + } + + if (range_to(self, self->enemy) <= RANGE_NEAR && visible(self, self->enemy)) + { + M_SetAnimation(self, &runnertank_move_attack_pre_rocket); + self->monsterinfo.attack_finished = level.time + 3_sec; + } + else + { + M_SetAnimation(self, &runnertank_move_run); + } +} + +void runnertank_consider_strafe(edict_t* self) +{ + // No strafear si estamos en medio de un ataque + if (self->monsterinfo.active_move == &runnertank_move_attack_blast || + self->monsterinfo.active_move == &runnertank_move_attack_pre_rocket || + self->monsterinfo.active_move == &runnertank_move_attack_fire_rocket || + self->monsterinfo.active_move == &tank_move_punch_attack) + return; + + // Use the comprehensive check + if (!M_HasValidTarget(self)) + return; + + // Don't strafe if we're still in strafe pause + if (level.time < self->monsterinfo.pausetime) + return; + + float strafe_chance = 0.4f; // Increased base chance for more responsive movement + + // Increase probability in critical situations + if (self->enemy && self->enemy->client && (self->enemy->client->buttons & BUTTON_ATTACK)) + strafe_chance += 0.3f; + if (self->health < self->max_health * 0.6f) + strafe_chance += 0.25f; + + // Distance-based strafing: strafe more when close to enemy + if (self->enemy) { + float dist = (self->s.origin - self->enemy->s.origin).length(); + if (dist < 512.0f) // Close range + strafe_chance += 0.2f; + else if (dist > 1024.0f) // Long range - less strafing + strafe_chance -= 0.1f; + } + + // Clamp the chance + strafe_chance = std::clamp(strafe_chance, 0.1f, 0.85f); + + if (frandom() < strafe_chance) + { + // Decide direction - consistent integer usage + self->monsterinfo.lefty = (frandom() < 0.5f) ? 1 : 0; + + // Calculate strafe direction + vec3_t right; + AngleVectors(self->s.angles, nullptr, right, nullptr); + + // Calculate strafe speed based on health and situation + float strafe_speed = 200.0f; + + // Boost speed when in danger + if (self->health < self->max_health * 0.5f) + strafe_speed *= 1.4f; + + // Add some randomness to make movement less predictable + strafe_speed += frandom() * 100.0f; + + // Apply strafe velocity - replace current lateral movement + vec3_t strafe_velocity = right * (self->monsterinfo.lefty ? -strafe_speed : strafe_speed); + + // Preserve forward/backward movement but replace lateral movement + vec3_t forward; + AngleVectors(self->s.angles, forward, nullptr, nullptr); + float forward_speed = self->velocity.dot(forward); + + // Set new velocity: keep forward momentum, add strafe + self->velocity = forward * forward_speed + strafe_velocity; + + // Moderate strafe duration + self->monsterinfo.pausetime = level.time + random_time(0.8_sec, 1.5_sec); + } +} + + +// Custom checkattack with higher attack chances than default +// Default is: stand=0.7, melee=0.4, near=0.25, mid=0.06, far=0.0 +// Runnertank is aggressive and should attack more often +MONSTERINFO_CHECKATTACK(runnertank_checkattack) (edict_t* self) -> bool +{ + return M_CheckAttack_Base(self, 0.8f, 0.5f, 0.4f, 0.25f, 0.15f, 1.0f); +} + +MONSTERINFO_ATTACK(runnertank_attack) (edict_t* self) -> void +{ + monster_done_dodge(self); + + if (!M_HasValidTarget(self)) + { + return; // Can't attack a non-existent or dead target. + } + + if (level.time < self->monsterinfo.attack_finished) + return; + + // Check for blindfire conditions + if (self->monsterinfo.attack_state == AS_BLIND) + { + // Blindfire logic - attack without visibility requirement + float chance; + if (self->monsterinfo.blind_fire_delay < 1_sec) + chance = 0.8f; + else if (self->monsterinfo.blind_fire_delay < 7.5_sec) + chance = 0.4f; + else + chance = 0.1f; + + if (frandom() > chance) + return; + + self->monsterinfo.blind_fire_delay += 5.2_sec + random_time(3_sec); + self->monsterinfo.aiflags |= AI_MANUAL_STEERING; + } + else if (!visible(self, self->enemy)) + { + // Regular attack needs visibility + return; + } + + //const float range = range_to(self, self->enemy); + const float r = frandom(); + + // Verificar líneas de visión para cada tipo de ataque + const bool can_blast = M_CheckClearShot(self, monster_flash_offset[MZ2_TANK_BLASTER_1]); + const bool can_rocket = M_CheckClearShot(self, monster_flash_offset[MZ2_TANK_ROCKET_1]); + const bool can_chain = M_CheckClearShot(self, monster_flash_offset[MZ2_TANK_MACHINEGUN_1]); + + float const range = range_to(self, self->enemy); + + // Jump attack - check first for medium range leap attack + // Range between 150-400 units, visible enemy, on ground + if (range > 150.0f && range <= 400.0f && + self->groundentity && visible(self, self->enemy) && + frandom() < 0.25f) // 25% chance + { + // Check if enemy is at similar height or slightly above + float height_diff = self->enemy->s.origin[2] - self->s.origin[2]; + if (height_diff > -50.0f && height_diff < 150.0f) + { + M_SetAnimation(self, &runnertank_move_jump_attack); + self->monsterinfo.attack_finished = level.time + 3_sec; + return; + } + } + + // Close range attack selection + if (range <= RANGE_MELEE * 1.5f) + { + M_SetAnimation(self, &tank_move_punch_attack); + self->monsterinfo.attack_finished = level.time + 1.5_sec; + } + else if (range <= RANGE_NEAR) + { + if (can_chain && r < 0.5f) + { + M_SetAnimation(self, &runnertank_move_attack_chain); + self->monsterinfo.attack_finished = level.time + 3_sec; + } + else if (can_rocket && r < 0.7f) + { + M_SetAnimation(self, &runnertank_move_attack_pre_rocket); + self->monsterinfo.attack_finished = level.time + 4_sec; + } + else if (can_blast) + { + M_SetAnimation(self, &runnertank_move_attack_blast); + self->monsterinfo.attack_finished = level.time + 3_sec; + } + else + { + // If no clear shot, let normal AI handle movement + return; + } + } + // Medium range attack selection + else if (range <= RANGE_MID) + { + if (can_blast && r < 0.4f) + { + M_SetAnimation(self, &runnertank_move_attack_blast); + self->monsterinfo.attack_finished = level.time + 3_sec; + } + else if (can_rocket && r < 0.7f) + { + M_SetAnimation(self, &runnertank_move_attack_pre_rocket); + self->monsterinfo.attack_finished = level.time + 4_sec; + } + else if (can_chain) + { + M_SetAnimation(self, &runnertank_move_attack_chain); + self->monsterinfo.attack_finished = level.time + 3_sec; + } + else + { + // If no clear shot, let normal AI handle movement + return; + } + } + // Long range attack selection + else + { + if (can_blast && r < 0.6f) + { + M_SetAnimation(self, &runnertank_move_attack_blast); + self->monsterinfo.attack_finished = level.time + 3.5_sec; + } + else if (can_rocket) + { + M_SetAnimation(self, &runnertank_move_attack_pre_rocket); + self->monsterinfo.attack_finished = level.time + 4_sec; + } + else + { + // If no attack is possible, let normal AI handle movement + return; + } + } +} +// +// death +// + +void runnertank_dead(edict_t* self) +{ + self->mins = { -16, -16, -16 }; + self->maxs = { 16, 16, -0 }; + monster_dead(self); +} + +static void runnertank_shrink(edict_t* self) +{ + self->maxs[2] = 0; + self->svflags |= SVF_DEADMONSTER; + gi.linkentity(self); +} + +mframe_t runnertank_frames_death1[] = { + { ai_move, -7 }, + { ai_move, -2 }, + { ai_move, -2 }, + { ai_move, 1 }, + { ai_move, 3 }, + { ai_move, 6 }, + { ai_move, 1 }, + { ai_move, 1 }, + { ai_move, 2 }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, -2 }, + { ai_move }, + { ai_move }, + { ai_move, -3 }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, -4 }, + { ai_move, -6 }, + { ai_move, -4 }, + { ai_move, -5 }, + { ai_move, -7, runnertank_shrink }, + { ai_move, -15, runnertank_thud }, + { ai_move, -5 }, + { ai_move }, + { ai_move }, + { ai_move } +}; +MMOVE_T(runnertank_move_death) = { FRAME_death01, FRAME_death32, runnertank_frames_death1, runnertank_dead }; + +DIE(runnertank_die) (edict_t* self, edict_t* inflictor, edict_t* attacker, int damage, const vec3_t& point, const mod_t& mod) -> void +{ + // check for gib + if (M_CheckGib(self, mod)) + { + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); + + self->s.skinnum /= 2; + + ThrowGibs(self, damage, { + { "models/objects/gibs/sm_meat/tris.md2" }, + { 3, "models/objects/gibs/sm_metal/tris.md2", GIB_METALLIC }, + { "models/objects/gibs/gear/tris.md2", GIB_METALLIC }, + { 2, "models/monsters/tank/gibs/foot.md2", GIB_SKINNED | GIB_METALLIC }, + { 2, "models/monsters/tank/gibs/thigh.md2", GIB_SKINNED | GIB_METALLIC }, + { "models/monsters/tank/gibs/chest.md2", GIB_SKINNED }, + { "models/monsters/tank/gibs/head.md2", GIB_HEAD | GIB_SKINNED } + }); + + if (!self->style) + ThrowGib(self, "models/monsters/tank/gibs/barm.md2", damage, GIB_SKINNED | GIB_UPRIGHT, self->s.scale); + + self->deadflag = true; + return; + } + + if (self->deadflag) + return; + + // [Paril-KEX] dropped arm + if (!self->style) + { + self->style = 1; + + auto [fwd, rgt, up] = AngleVectors(self->s.angles); + + edict_t* arm_gib = ThrowGib(self, "models/monsters/tank/gibs/barm.md2", damage, GIB_SKINNED | GIB_UPRIGHT, self->s.scale); + + if (!arm_gib) { + return; + } + + arm_gib->s.origin = self->s.origin + (rgt * -16.f) + (up * 23.f); + arm_gib->s.old_origin = arm_gib->s.origin; + arm_gib->avelocity = { crandom() * 15.f, crandom() * 15.f, 180.f }; + arm_gib->velocity = (up * 100.f) + (rgt * -120.f); + arm_gib->s.angles = self->s.angles; + arm_gib->s.angles[2] = -90.f; + arm_gib->s.skinnum /= 2; + gi.linkentity(arm_gib); + } + + // regular death + gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); + self->deadflag = true; + self->takedamage = true; + + M_SetAnimation(self, &runnertank_move_death); +} +void runnertank_jump_now(edict_t* self) +{ + auto [forward, right, up] = AngleVectors(self->s.angles); + + // Usar operadores vec3_t para impulso + self->velocity += forward * 130.0f + up * 300.0f; +} + +void runnertank_jump2_now(edict_t* self) +{ + auto [forward, right, up] = AngleVectors(self->s.angles); + + // Usar operadores vec3_t para impulso + self->velocity += forward * 250.0f + up * 400.0f; +} + +void runnertank_jump_wait_land(edict_t* self) +{ + if (self->groundentity == nullptr) + { + self->monsterinfo.nextframe = self->s.frame; + + if (monster_jump_finished(self)) + self->monsterinfo.nextframe = self->s.frame + 1; + } + else + self->monsterinfo.nextframe = self->s.frame + 1; +} + +mframe_t runnertank_frames_jump[] = { + { ai_move }, + { ai_move }, + { ai_move }, + { ai_move, 0, runnertank_jump_now }, + { ai_move }, + { ai_move, 0, runnertank_jump_wait_land }, + { ai_move } +}; +MMOVE_T(runnertank_move_jump) = { FRAME_run01, FRAME_run07, runnertank_frames_jump, runnertank_run }; + +mframe_t runnertank_frames_jump2[] = { + { ai_move, -6 }, + { ai_move, -4 }, + { ai_move, -5 }, + { ai_move, 0, runnertank_jump2_now }, + { ai_move }, + { ai_move, 0, runnertank_jump_wait_land }, + { ai_move } +}; +MMOVE_T(runnertank_move_jump2) = { FRAME_run01, FRAME_run07, runnertank_frames_jump2, runnertank_run }; + +// Jump attack functions implementation +void runnertank_jump_attack_takeoff(edict_t* self) +{ + if (!M_HasValidTarget(self)) + return; + + // Calculate jump trajectory to enemy + vec3_t enemy_pos = self->enemy->s.origin; + vec3_t dir_to_enemy = enemy_pos - self->s.origin; + float const length = dir_to_enemy.length(); + + // Adjust for enemy movement prediction + float const jump_time = length / 400.0f; // Estimate time to reach enemy + if (self->enemy->velocity.length() > 50.0f) + { + enemy_pos += self->enemy->velocity * jump_time * 0.5f; // Partial prediction + dir_to_enemy = enemy_pos - self->s.origin; + } + + // Calculate horizontal and vertical speeds + float const horizontal_speed = length * 1.5f; // Adjust multiplier as needed + float const vertical_speed = 200.0f + (length * 0.3f); // Higher for longer jumps + + // Face the target + self->s.angles[1] = vectoyaw(dir_to_enemy); + vec3_t forward; + AngleVectors(self->s.angles, forward, nullptr, nullptr); + + // Launch! + self->s.origin[2] += 1; + self->velocity = forward * horizontal_speed; + self->velocity[2] = vertical_speed; + self->groundentity = nullptr; + self->monsterinfo.aiflags |= AI_DUCKED; + + // Play a sound effect if we have one + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +void runnertank_high_gravity(edict_t* self) +{ + float const gravity_scale = (800.f / level.gravity); + if (self->velocity[2] < 0) + self->gravity = 2.0f; + else + self->gravity = 4.5f; + self->gravity *= gravity_scale; +} + +void runnertank_check_jump_landing(edict_t* self) +{ + runnertank_high_gravity(self); + + if (self->groundentity) + { + // Landed - do slam attack immediately + self->monsterinfo.aiflags &= ~AI_DUCKED; + self->gravity = 1.0f; + self->velocity = {}; + self->flags &= ~FL_KILL_VELOCITY; + + // Always do the slam on landing - this is a jump attack! + runnertankStrike(self); + + // Continue with the animation + return; + } + + // Still in air - keep checking + self->monsterinfo.nextframe = self->s.frame; +} + +void runnertank_jump(edict_t* self, blocked_jump_result_t result) +{ + if (!M_HasValidTarget(self)) + return; // Can't jump at a non-existent or dead target. + + monster_done_dodge(self); + + if (result == blocked_jump_result_t::JUMP_JUMP_UP) + M_SetAnimation(self, &runnertank_move_jump2); + else + M_SetAnimation(self, &runnertank_move_jump); +} +//=========== +// PGM + +//bool runnertank_check_wall(edict_t* self, float dist) +//{ +// auto [forward, right, up] = AngleVectors(self->s.angles); +// +// // Usar operador + de vec3_t para punto de verificación +// vec3_t const check_point = self->s.origin + (forward * (dist + 10.0f)); +// +// trace_t const tr = gi.trace(self->s.origin, self->mins, self->maxs, +// check_point, self, MASK_MONSTERSOLID); +// +// if (tr.fraction < 1.0f) { +// // Usar dot() de vec3_t +// float const dot = forward.dot(tr.plane.normal); +// float const turn_angle = 90.0f * (1.0f - std::abs(dot)); +// +// // Actualizar orientación usando el resultado +// float new_yaw = self->s.angles[YAW] + (dot < 0 ? turn_angle : -turn_angle); +// float const yaw_diff = AngleDifference(new_yaw, self->ideal_yaw); +// +// if (std::abs(yaw_diff) > 30.0f) +// new_yaw = self->s.angles[YAW] + (yaw_diff > 0 ? 30.0f : -30.0f); +// +// self->ideal_yaw = anglemod(new_yaw); +// M_ChangeYaw(self); +// +// // Ajustar velocidad usando operador * de vec3_t +// self->velocity = self->velocity * (tr.fraction * 0.8f); +// +// return true; +// } +// return false; +//} + +MONSTERINFO_BLOCKED(runnertank_blocked) (edict_t* self, float dist) -> bool +{ + if (self->monsterinfo.can_jump) + { + if (const auto result = blocked_checkjump(self, dist); result != blocked_jump_result_t::NO_JUMP) + { + runnertank_jump(self, result); + return true; + } + } + + if (blocked_checkplat(self, dist)) + return true; + + // Eliminar la llamada a runnertank_check_wall aquí + // if (runnertank_check_wall(self, dist)) + // { + // ai_turn(self, 0); + // return true; + // } + + return false; +} + +// PGM +// +//=========== + +// +// monster_runnertank +// + +MONSTERINFO_SIDESTEP(runnertank_sidestep) (edict_t* self) -> bool +{ + // No hacer sidestep si estamos en medio de un ataque + if ((self->monsterinfo.active_move == &runnertank_move_attack_blast) || + (self->monsterinfo.active_move == &runnertank_move_attack_pre_rocket) || + (self->monsterinfo.active_move == &runnertank_move_attack_fire_rocket) || + (self->monsterinfo.active_move == &tank_move_punch_attack) || + (self->monsterinfo.active_move == &runnertank_move_jump_attack)) + { + return false; + } + + // Don't sidestep if we just did one recently + if (level.time < self->monsterinfo.pausetime) + return false; + + // Si no estamos corriendo, cambiar a la animación de carrera + if (self->monsterinfo.active_move != &runnertank_move_run) + M_SetAnimation(self, &runnertank_move_run); + + // Consistent integer usage for lefty + self->monsterinfo.lefty = (frandom() < 0.5f) ? 1 : 0; + + // Calculate strafe direction + auto [forward, right, up] = AngleVectors(self->s.angles); + + // More dynamic speed calculation + float base_speed = 250.0f; + float speed_variation = frandom() * 150.0f; + float const strafe_speed = base_speed + speed_variation; + + // Enhanced evasion: check if enemy is aiming/attacking + float evasion_multiplier = 1.0f; + if (self->enemy && self->enemy->client) { + // Boost evasion if enemy is attacking + if (self->enemy->client->buttons & BUTTON_ATTACK) + evasion_multiplier = 1.6f; + + // Also consider enemy's facing direction for better dodging + vec3_t enemy_forward; + AngleVectors(self->enemy->s.angles, enemy_forward, nullptr, nullptr); + vec3_t to_self = (self->s.origin - self->enemy->s.origin).normalized(); + + // If enemy is facing us, dodge more aggressively + if (enemy_forward.dot(to_self) > 0.7f) + evasion_multiplier *= 1.3f; + } + + // Apply enhanced movement + vec3_t dodge_velocity = right * (self->monsterinfo.lefty ? -strafe_speed : strafe_speed) * evasion_multiplier; + + // Add slight forward/backward component for more unpredictable movement + float fb_component = (frandom() - 0.5f) * 100.0f; + dodge_velocity += forward * fb_component; + + // Replace lateral velocity but preserve some vertical momentum + vec3_t preserved_velocity = {0, 0, self->velocity.z}; + self->velocity = dodge_velocity + preserved_velocity; + + // Variable pause time based on evasion intensity + self->monsterinfo.pausetime = level.time + random_time(0.8_sec, 1.5_sec); + + return true; +} + +/*QUAKED monster_runnertank (1 .5 0) (-32 -32 -16) (32 32 72) Ambush Trigger_Spawn Sight +model="models/monsters/runnertank/tris.md2" +*/ +/*QUAKED monster_runnertank_commander (1 .5 0) (-32 -32 -16) (32 32 72) Ambush Trigger_Spawn Sight Guardian HeatSeeking + */ +MONSTERINFO_DODGE(runnertank_dodge) (edict_t* self, edict_t* attacker, gtime_t eta, trace_t* tr, bool gravity) -> void +{ + // Basic checks + if (!self->groundentity || self->health <= 0) + return; + + // Set enemy if we don't have one + if (!self->enemy && attacker) + { + self->enemy = attacker; + FoundTarget(self); + return; + } + + // Don't dodge if we're attacking melee or jump attacking + if (self->monsterinfo.active_move == &tank_move_punch_attack || + self->monsterinfo.active_move == &runnertank_move_jump_attack) + return; + + // Check dodge cooldown using timestamp + if (self->timestamp > level.time) + return; + + // Don't dodge if projectile impact is too soon or too far away + if (eta < FRAME_TIME_MS || eta > 3_sec) + return; + + // Don't dodge if attacker is invalid + if (!attacker) + return; + + // Calculate dodge direction based on attacker position + vec3_t dodge_dir; + + // Get our right vector for lateral dodge + vec3_t right; + AngleVectors(self->s.angles, nullptr, right, nullptr); + + // Decide dodge direction - prefer moving away from attacker + vec3_t to_attacker = (attacker->s.origin - self->s.origin).normalized(); + float side_dot = to_attacker.dot(right); + + // Dodge perpendicular to attack direction, away from attacker + if (side_dot > 0) + dodge_dir = right * -1.0f; // Dodge left + else + dodge_dir = right; // Dodge right + + // Add some forward/backward component based on distance + vec3_t forward; + AngleVectors(self->s.angles, forward, nullptr, nullptr); + float dist = (self->s.origin - attacker->s.origin).length(); + + if (dist < 400.0f) { + // Close range - dodge backward + dodge_dir += forward * -0.3f; + } else if (dist > 800.0f) { + // Long range - dodge forward to close distance + dodge_dir += forward * 0.2f; + } + + dodge_dir = dodge_dir.normalized(); + + // Calculate dodge speed based on urgency (eta) + float base_dodge_speed = 300.0f; + float eta_seconds = eta.seconds(); + float urgency_multiplier = std::clamp(2.0f - eta_seconds, 1.0f, 2.5f); + float dodge_speed = base_dodge_speed * urgency_multiplier; + + // Apply dodge velocity + vec3_t dodge_velocity = dodge_dir * dodge_speed; + + // Preserve some vertical momentum but replace horizontal + dodge_velocity.z = self->velocity.z * 0.5f; + self->velocity = dodge_velocity; + + // Set animation to running for dodge + if (self->monsterinfo.active_move != &runnertank_move_run) + M_SetAnimation(self, &runnertank_move_run); + + // Set cooldown using timestamp (like spider) + self->timestamp = level.time + random_time(0.5_sec, 1.5_sec); + + // Also set pausetime for movement consistency + self->monsterinfo.pausetime = level.time + random_time(0.3_sec, 0.7_sec); + + // Update lefty for consistency with sidestep + self->monsterinfo.lefty = (side_dot > 0) ? 1 : 0; + + // Mark that we're dodging + monster_done_dodge(self); +} + +void SP_monster_runnertank(edict_t* self) +{ + const spawn_temp_t& st = ED_GetSpawnTemp(); + + if (self->monsterinfo.monster_type_id == MONSTER_TYPE_UNKNOWN) { // Check if it hasn't been set yet + self->monsterinfo.monster_type_id = static_cast(horde::MonsterTypeID::RUNNERTANK); +} if (!M_AllowSpawn(self)) { + G_FreeEdict(self); + return; + } + + self->s.modelindex = gi.modelindex("models/vault/monsters/tank/tris.md2"); + self->mins = { -28, -28, -14 }; + self->maxs = { 28, 28, 56 }; + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + gi.modelindex("models/monsters/tank/gibs/barm.md2"); + gi.modelindex("models/monsters/tank/gibs/head.md2"); + gi.modelindex("models/monsters/tank/gibs/chest.md2"); + gi.modelindex("models/monsters/tank/gibs/foot.md2"); + gi.modelindex("models/monsters/tank/gibs/thigh.md2"); + + sound_thud.assign("tank/tnkdeth2.wav"); + sound_idle.assign("tank/tnkidle1.wav"); + sound_die.assign("tank/death.wav"); + sound_step.assign("tank/step.wav"); + sound_windup.assign("tank/tnkatck4.wav"); + sound_strike.assign("tank/tnkatck5.wav"); + sound_sight.assign("tank/sight1.wav"); + + gi.soundindex("tank/tnkatck1.wav"); + gi.soundindex("tank/tnkatk2a.wav"); + gi.soundindex("tank/tnkatk2b.wav"); + gi.soundindex("tank/tnkatk2c.wav"); + gi.soundindex("tank/tnkatk2d.wav"); + gi.soundindex("tank/tnkatk2e.wav"); + gi.soundindex("tank/tnkatck3.wav"); + + // if (strcmp(self->classname, "monster_runnertank_commander") == 0) + // { + // self->health = (config ? config->health : 1000) * st.health_multiplier; + // self->gib_health = -225; + // self->count = 1; + // sound_pain2.assign("tank/pain.wav"); + // } + // else + { + int base_health = M_RUNNERTANK_INITIAL_HEALTH; + if (g_horde && g_horde->integer && current_wave_level > 0) { + self->health = ScaleMonsterHealth(base_health, current_wave_level, false); + } else { + self->health = base_health * st.health_multiplier; + } + self->gib_health = -200; + sound_pain.assign("tank/tnkpain2.wav"); + } + + self->monsterinfo.scale = MODEL_SCALE; + + // [Paril-KEX] N64 runnertank commander is a chonky boy + if (self->spawnflags.has(SPAWNFLAG_runnertank_COMMANDER_GUARDIAN)) + { + if (!self->s.scale) + self->s.scale = 1.5f; + int base_health = M_RUNNERTANK_INITIAL_HEALTH; + if (g_horde && g_horde->integer && current_wave_level > 0) { + self->health = ScaleMonsterHealth(base_health, current_wave_level, false); + } else { + self->health = base_health * st.health_multiplier; + } + } + + // heat seekingness + if (!self->accel) + self->accel = 0.075f; + + self->mass = 500; + + self->pain = runnertank_pain; + self->die = runnertank_die; + self->monsterinfo.stand = runnertank_stand; + self->monsterinfo.walk = runnertank_walk; + self->monsterinfo.run = runnertank_run; + self->monsterinfo.sidestep = runnertank_sidestep; + self->monsterinfo.dodge = runnertank_dodge; + self->monsterinfo.attack = runnertank_attack; + self->monsterinfo.melee = runnertank_melee; + self->monsterinfo.sight = runnertank_sight; + self->monsterinfo.idle = runnertank_idle; + self->monsterinfo.blocked = runnertank_blocked; // PGM + self->monsterinfo.setskin = runnertank_setskin; + self->monsterinfo.checkattack = runnertank_checkattack; + self->yaw_speed = 20; // Better tracking but not too fast + + self->s.renderfx |= RF_CUSTOMSKIN; + self->s.skinnum = gi.imageindex("models/vault/monsters/tank/skin.pcx"); + + gi.linkentity(self); + + M_SetAnimation(self, &runnertank_move_stand); + + walkmonster_start(self); + + // PMM + self->monsterinfo.aiflags |= AI_IGNORE_SHOTS; + self->monsterinfo.blindfire = true; + // pmm +// if (strcmp(self->classname, "monster_runnertank_commander") == 0) +// self->s.skinnum = 2; + + self->monsterinfo.can_jump = true; + self->monsterinfo.drop_height = 256; + // HORDE MOD: Increased jump height from 68 to 88 (30% increase) for better obstacle navigation + self->monsterinfo.jump_height = 88; + ApplyMonsterBonusFlags(self); +} + +void Use_Boss3(edict_t* ent, edict_t* other, edict_t* activator); + +THINK(Think_runnertankStand) (edict_t* ent) -> void +{ + if (ent->s.frame == FRAME_stand30) + ent->s.frame = FRAME_stand01; + else + ent->s.frame++; + ent->nextthink = level.time + 10_hz; +} + +/*QUAKED monster_runnertank_stand (1 .5 0) (-32 -32 0) (32 32 90) + +Just stands and cycles in one place until targeted, then teleports away. +N64 edition! +*/ +void SP_monster_runnertank_stand(edict_t* self) +{ + if (!M_AllowSpawn(self)) { + G_FreeEdict(self); + return; + } + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->model = "models/vault/monsters/tank/tris.md2"; + self->s.modelindex = gi.modelindex(self->model); + self->s.frame = FRAME_stand01; + self->s.skinnum = 2; + + gi.soundindex("misc/bigtele.wav"); + + self->mins = { -32, -32, -16 }; + self->maxs = { 32, 32, 64 }; + + if (!self->s.scale) + self->s.scale = 1.5f; + // Removed manual (mins/maxs) scaling - monster_start() handles it automatically + + self->use = Use_Boss3; + self->think = Think_runnertankStand; + self->nextthink = level.time + 10_hz; + gi.linkentity(self); +} \ No newline at end of file diff --git a/to add/m_runnertank.h b/to add/m_runnertank.h new file mode 100644 index 00000000..8012ab12 --- /dev/null +++ b/to add/m_runnertank.h @@ -0,0 +1,266 @@ +#pragma once +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// G:\quake2\baseq2\models/monsters/parasite + +// This file generated by ModelGen - Do NOT Modify + +enum +{ + FRAME_stand01, + FRAME_stand02, + FRAME_stand03, + FRAME_stand04, + FRAME_stand05, + FRAME_stand06, + FRAME_stand07, + FRAME_stand08, + FRAME_stand09, + FRAME_stand10, + FRAME_stand11, + FRAME_stand12, + FRAME_stand13, + FRAME_stand14, + FRAME_stand15, + FRAME_stand16, + FRAME_stand17, + FRAME_stand18, + FRAME_stand19, + FRAME_stand20, + FRAME_stand21, + FRAME_stand22, + FRAME_stand23, + FRAME_stand24, + FRAME_stand25, + FRAME_stand26, + FRAME_stand27, + FRAME_stand28, + FRAME_stand29, + FRAME_stand30, + FRAME_walk01, + FRAME_walk02, + FRAME_walk03, + FRAME_walk04, + FRAME_walk05, + FRAME_walk06, + FRAME_walk07, + FRAME_walk08, + FRAME_walk09, + FRAME_walk10, + FRAME_walk11, + FRAME_walk12, + FRAME_walk13, + FRAME_walk14, + FRAME_walk15, + FRAME_walk16, + FRAME_walk17, + FRAME_walk18, + FRAME_walk19, + FRAME_walk20, + FRAME_walk21, + FRAME_walk22, + FRAME_walk23, + FRAME_walk24, + FRAME_walk25, + FRAME_walk26, + FRAME_walk27, + FRAME_walk28, + FRAME_walk29, + FRAME_walk30, + FRAME_walk31, + FRAME_walk32, + FRAME_walk33, + FRAME_walk34, + FRAME_walk35, + FRAME_walk36, + FRAME_walk37, + FRAME_walk38, + FRAME_run01, + FRAME_run02, + FRAME_run03, + FRAME_run04, + FRAME_run05, + FRAME_run06, + FRAME_run07, + FRAME_run08, + FRAME_run09, + FRAME_run10, + FRAME_attak101, + FRAME_attak102, + FRAME_attak103, + FRAME_attak104, + FRAME_attak105, + FRAME_attak106, + FRAME_attak107, + FRAME_attak108, + FRAME_attak109, + FRAME_attak110, + FRAME_attak111, + FRAME_attak112, + FRAME_attak113, + FRAME_attak114, + FRAME_attak115, + FRAME_attak116, + FRAME_attak117, + FRAME_attak118, + FRAME_attak119, + FRAME_attak120, + FRAME_attak121, + FRAME_attak122, + FRAME_attak201, + FRAME_attak202, + FRAME_attak203, + FRAME_attak204, + FRAME_attak205, + FRAME_attak206, + FRAME_attak207, + FRAME_attak208, + FRAME_attak209, + FRAME_attak210, + FRAME_attak211, + FRAME_attak212, + FRAME_attak213, + FRAME_attak214, + FRAME_attak215, + FRAME_attak216, + FRAME_attak217, + FRAME_attak218, + FRAME_attak219, + FRAME_attak220, + FRAME_attak221, + FRAME_attak222, + FRAME_attak223, + FRAME_attak224, + FRAME_attak225, + FRAME_attak226, + FRAME_attak227, + FRAME_attak228, + FRAME_attak229, + FRAME_attak230, + FRAME_attak231, + FRAME_attak232, + FRAME_attak233, + FRAME_attak234, + FRAME_attak235, + FRAME_attak236, + FRAME_attak237, + FRAME_attak238, + FRAME_attak301, + FRAME_attak302, + FRAME_attak303, + FRAME_attak304, + FRAME_attak305, + FRAME_attak306, + FRAME_attak307, + FRAME_attak308, + FRAME_attak309, + FRAME_attak310, + FRAME_attak311, + FRAME_attak312, + FRAME_attak313, + FRAME_attak314, + FRAME_attak315, + FRAME_attak316, + FRAME_attak317, + FRAME_attak318, + FRAME_attak319, + FRAME_attak320, + FRAME_attak321, + FRAME_attak322, + FRAME_attak323, + FRAME_attak324, + FRAME_attak325, + FRAME_attak326, + FRAME_attak327, + FRAME_attak328, + FRAME_attak329, + FRAME_attak330, + FRAME_attak331, + FRAME_attak332, + FRAME_attak333, + FRAME_attak334, + FRAME_attak335, + FRAME_attak401, + FRAME_attak402, + FRAME_attak403, + FRAME_attak404, + FRAME_attak405, + FRAME_attak406, + FRAME_attak407, + FRAME_attak408, + FRAME_attak409, + FRAME_attak410, + FRAME_attak411, + FRAME_attak412, + FRAME_attak413, + FRAME_attak414, + FRAME_attak415, + FRAME_attak416, + FRAME_attak417, + FRAME_attak418, + FRAME_attak419, + FRAME_attak420, + FRAME_attak421, + FRAME_attak422, + FRAME_attak423, + FRAME_attak424, + FRAME_attak425, + FRAME_attak426, + FRAME_attak427, + FRAME_attak428, + FRAME_attak429, + FRAME_pain201, + FRAME_pain202, + FRAME_pain203, + FRAME_pain204, + FRAME_pain301, + FRAME_pain302, + FRAME_pain303, + FRAME_pain304, + FRAME_pain305, + FRAME_pain306, + FRAME_pain307, + FRAME_pain308, + FRAME_pain309, + FRAME_pain310, + FRAME_pain311, + FRAME_pain312, + FRAME_pain313, + FRAME_pain314, + FRAME_pain315, + FRAME_pain316, + FRAME_death01, + FRAME_death02, + FRAME_death03, + FRAME_death04, + FRAME_death05, + FRAME_death06, + FRAME_death07, + FRAME_death08, + FRAME_death09, + FRAME_death10, + FRAME_death11, + FRAME_death12, + FRAME_death13, + FRAME_death14, + FRAME_death15, + FRAME_death16, + FRAME_death17, + FRAME_death18, + FRAME_death19, + FRAME_death20, + FRAME_death21, + FRAME_death22, + FRAME_death23, + FRAME_death24, + FRAME_death25, + FRAME_death26, + FRAME_death27, + FRAME_death28, + FRAME_death29, + FRAME_death30, + FRAME_death31, + FRAME_death32, +}; + +constexpr float MODEL_SCALE = 1.000000f; \ No newline at end of file From 5600abf03182e284e040d0c84705431f39cc17fc Mon Sep 17 00:00:00 2001 From: Enemy-123 Date: Tue, 21 Apr 2026 00:51:22 -0400 Subject: [PATCH 03/24] Added functions monster_fire_blaster2, monster_fire_blueblaster and monster_fire_heat.0 Added drone chick_heat, trying a fix on berserk not using animation on attacks and replaced berserk_swing's ATTN_STATIC to ATTN_NORM and now is making attack noises on repro, fixed gekk and stalker walk animation, stalker back to fire_blaster2 (high radius, OP ) cmd_drone using DS_ enum for easier understanding --- src/characters/v_utils.c | 2 + src/combat/common/damage.c | 2 +- src/combat/common/v_misc.c | 4 +- src/combat/weapons/g_weapon.c | 125 ++ src/entities/drone/drone_ai.c | 6 +- src/entities/drone/drone_berserk.c | 17 +- src/entities/drone/drone_bitch.c | 23 +- src/entities/drone/drone_gekk.c | 2 + src/entities/drone/drone_misc.c | 63 +- src/entities/drone/drone_stalker.c | 10 +- src/entities/drone/g_monster.c | 205 ++++ src/entities/g_spawn.c | 9 +- src/g_local.h | 11 + src/gamemodes/invasion.c | 3 +- src/menus/upgrades.c | 7 +- src/quake2/g_layout.c | 1 + to add/m_gladiator.cpp | 714 ----------- to add/m_guncmdr.cpp | 1475 ----------------------- to add/m_gunner.h | 809 ------------- to add/m_hover.cpp | 804 ------------- to add/m_redmutant.cpp | 786 ------------ to add/m_redmutant.h | 156 --- to add/m_runnertank.cpp | 1806 ---------------------------- to add/m_runnertank.h | 266 ---- 24 files changed, 444 insertions(+), 6862 deletions(-) delete mode 100644 to add/m_gladiator.cpp delete mode 100644 to add/m_guncmdr.cpp delete mode 100644 to add/m_gunner.h delete mode 100644 to add/m_hover.cpp delete mode 100644 to add/m_redmutant.cpp delete mode 100644 to add/m_redmutant.h delete mode 100644 to add/m_runnertank.cpp delete mode 100644 to add/m_runnertank.h diff --git a/src/characters/v_utils.c b/src/characters/v_utils.c index 3e92bb5b..5731d050 100644 --- a/src/characters/v_utils.c +++ b/src/characters/v_utils.c @@ -2056,6 +2056,8 @@ char *V_GetMonsterKind(int mtype) { return "gunner"; case M_CHICK: return "iron praetor"; + case M_CHICK_HEAT: + return "heat praetor"; case M_PARASITE: return "parasite"; case M_FLOATER: diff --git a/src/combat/common/damage.c b/src/combat/common/damage.c index 51c2ee1b..24bb813b 100644 --- a/src/combat/common/damage.c +++ b/src/combat/common/damage.c @@ -153,7 +153,7 @@ qboolean IsMonster(const edict_t* ent) { return (ent->mtype && (ent->mtype <= M_TANK || ent->mtype == M_SHAMBLER || ent->mtype == M_REDMUTANT || ent->mtype == M_RUNNERTANK || ent->mtype == M_GUNCMDR || ent->mtype == M_DAEDALUS || ent->mtype == M_GLADB || ent->mtype == M_GLADC - || ent->mtype == M_STALKER || ent->mtype == M_GEKK)); + || ent->mtype == M_STALKER || ent->mtype == M_GEKK || ent->mtype == M_CHICK_HEAT)); } float vrx_get_pack_modifier(const edict_t *ent) { diff --git a/src/combat/common/v_misc.c b/src/combat/common/v_misc.c index 9fdef8ee..27023bbb 100644 --- a/src/combat/common/v_misc.c +++ b/src/combat/common/v_misc.c @@ -254,7 +254,7 @@ void vrx_pvm_spawn_monsters(edict_t* self, int max_monsters, int total_monsters) while (total_monsters < max_monsters && max_spawn_this_cycle > 0) { int rnd; do { - rnd = GetRandom(1, DS_GEKK); // az: don't spawn soldiers or helper summons + rnd = GetRandom(1, DS_BITCH_HEAT); // az: don't spawn soldiers or helper summons } while (rnd == DS_SOLDIER || rnd == DS_DECOY || rnd == DS_SKELETON || rnd == DS_GOLEM); edict_t* scan; @@ -596,6 +596,7 @@ int vrx_GetMonsterCost(int mtype) { cost = M_GUNNER_COST; break; case M_CHICK: + case M_CHICK_HEAT: cost = M_CHICK_COST; break; case M_PARASITE: @@ -674,6 +675,7 @@ int vrx_GetMonsterControlCost(int mtype) { cost = M_GUNNER_CONTROL_COST; break; case M_CHICK: + case M_CHICK_HEAT: cost = M_CHICK_CONTROL_COST; break; case M_PARASITE: diff --git a/src/combat/weapons/g_weapon.c b/src/combat/weapons/g_weapon.c index 7cd95193..8412ef98 100644 --- a/src/combat/weapons/g_weapon.c +++ b/src/combat/weapons/g_weapon.c @@ -542,6 +542,131 @@ void blaster_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t * G_FreeEdict (self); } +static void blaster2_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other == self->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict(self); + return; + } + + if (self->owner && self->owner->client) + PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT); + + if (other->takedamage) + { + if (self->dmg >= 5) + T_RadiusDamage(self, self->owner, 2 * self->dmg, other, self->dmg_radius, MOD_BLASTER); + + T_Damage(other, self, self->owner, self->velocity, self->s.origin, + plane ? plane->normal : vec3_origin, self->dmg, 1, DAMAGE_ENERGY, MOD_BLASTER); + } + else + { + if (self->dmg >= 5) + T_RadiusDamage(self, self->owner, 2 * self->dmg, NULL, self->dmg_radius, MOD_BLASTER); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_BLASTER2); + gi.WritePosition(self->s.origin); + gi.WriteDir(plane ? plane->normal : vec3_origin); + gi.multicast(self->s.origin, MULTICAST_PHS); + } + + G_FreeEdict(self); +} + +void fire_blaster2(edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect, qboolean hyper) +{ + edict_t *bolt; + trace_t tr; + + self->lastsound = level.framenum; + + VectorNormalize(dir); + + bolt = G_Spawn(); + VectorCopy(start, bolt->s.origin); + VectorCopy(start, bolt->s.old_origin); + vectoangles(dir, bolt->s.angles); + VectorScale(dir, speed, bolt->velocity); + bolt->movetype = MOVETYPE_FLYMISSILE; + bolt->clipmask = MASK_SHOT; + bolt->solid = SOLID_BBOX; + bolt->s.effects |= effect; + if (effect) + bolt->s.effects |= EF_TRACKER; + bolt->s.modelindex = gi.modelindex("models/objects/laser/tris.md2"); + bolt->s.skinnum = 2; + bolt->s.sound = gi.soundindex("misc/lasfly.wav"); + bolt->owner = self; + bolt->touch = blaster2_touch; + bolt->nextthink = level.time + 2.0; + bolt->think = G_FreeEdict; + bolt->dmg = damage; + bolt->dmg_radius = hyper ? 64 : 128; + bolt->classname = "bolt"; + VectorClear(bolt->mins); + VectorClear(bolt->maxs); + gi.linkentity(bolt); + + if (self->client) + check_dodge(self, bolt->s.origin, dir, speed, 0); + + tr = gi.trace(self->s.origin, NULL, NULL, bolt->s.origin, bolt, MASK_SHOT); + if (tr.fraction < 1.0) + { + VectorMA(bolt->s.origin, -10, dir, bolt->s.origin); + bolt->touch(bolt, tr.ent, NULL, NULL); + } +} + +void fire_blueblaster(edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect) +{ + edict_t *bolt; + trace_t tr; + + self->lastsound = level.framenum; + + VectorNormalize(dir); + + bolt = G_Spawn(); + VectorCopy(start, bolt->s.origin); + VectorCopy(start, bolt->s.old_origin); + vectoangles(dir, bolt->s.angles); + VectorScale(dir, speed, bolt->velocity); + bolt->movetype = MOVETYPE_FLYMISSILE; + bolt->clipmask = MASK_SHOT; + bolt->solid = SOLID_BBOX; + bolt->s.effects |= effect; + bolt->s.modelindex = gi.modelindex("models/objects/laser/tris.md2"); + bolt->s.skinnum = 1; + bolt->s.sound = gi.soundindex("misc/lasfly.wav"); + bolt->owner = self; + bolt->style = MOD_HYPERBLASTER; + bolt->touch = blaster_touch; + bolt->nextthink = level.time + 2.0; + bolt->think = G_FreeEdict; + bolt->dmg = damage; + bolt->classname = "bolt"; + VectorClear(bolt->mins); + VectorClear(bolt->maxs); + gi.linkentity(bolt); + + if (self->client) + check_dodge(self, bolt->s.origin, dir, speed, 0); + + tr = gi.trace(self->s.origin, NULL, NULL, bolt->s.origin, bolt, MASK_SHOT); + if (tr.fraction < 1.0) + { + VectorMA(bolt->s.origin, -10, dir, bolt->s.origin); + bolt->touch(bolt, tr.ent, NULL, NULL); + } +} + void fire_blaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect, int proj_type, int mod, float duration, qboolean bounce) { edict_t *bolt; diff --git a/src/entities/drone/drone_ai.c b/src/entities/drone/drone_ai.c index d7e9f9d8..f4347e00 100644 --- a/src/entities/drone/drone_ai.c +++ b/src/entities/drone/drone_ai.c @@ -703,7 +703,8 @@ void drone_ai_idle (edict_t *self) // change skin if we are being healed by someone else self->s.skinnum &= ~1; if (self->mtype != M_COMMANDER && self->mtype != M_GUNCMDR - && self->mtype != M_DAEDALUS && self->mtype != M_GLADB && self->mtype != M_GLADC) + && self->mtype != M_DAEDALUS && self->mtype != M_GLADB && self->mtype != M_GLADC + && self->mtype != M_CHICK_HEAT && self->mtype != M_SOLDIER && self->mtype != M_STALKER) self->s.skinnum &= ~2; } @@ -1905,7 +1906,8 @@ void drone_ai_run1 (edict_t *self, float dist) // change skin if we are being healed by someone else self->s.skinnum &= ~1; if (self->mtype != M_COMMANDER && self->mtype != M_GUNCMDR - && self->mtype != M_DAEDALUS && self->mtype != M_GLADB && self->mtype != M_GLADC) + && self->mtype != M_DAEDALUS && self->mtype != M_GLADB && self->mtype != M_GLADC + && self->mtype != M_CHICK_HEAT && self->mtype != M_SOLDIER && self->mtype != M_STALKER) self->s.skinnum &= ~2; } diff --git a/src/entities/drone/drone_berserk.c b/src/entities/drone/drone_berserk.c index 8634a934..fa3f4718 100644 --- a/src/entities/drone/drone_berserk.c +++ b/src/entities/drone/drone_berserk.c @@ -143,7 +143,7 @@ void berserk_attack_spike (edict_t *self) void berserk_swing (edict_t *self) { //gi.dprintf("played sound at %d on frame %d\n", level.framenum, self->s.frame); - gi.sound (self, CHAN_WEAPON, sound_punch, 1, ATTN_STATIC, 0); + gi.sound (self, CHAN_WEAPON, sound_punch, 1, ATTN_NORM, 0); // doesn't make noises on static on repro - testing } mframe_t berserk_frames_attack_spike [] = @@ -441,7 +441,22 @@ void berserk_attack (edict_t *self) void berserk_melee (edict_t *self) { + float dist; + if (!G_EntExists(self->enemy)) + return; + + dist = entdist(self, self->enemy); + if (dist > 128) + return; + + if (random() <= 0.3 && dist <= 96) + self->monsterinfo.currentmove = &berserk_move_attack_club; + else + self->monsterinfo.currentmove = &berserk_move_attack_strike; + + self->monsterinfo.melee_finished = level.time + 0.4; + self->monsterinfo.attack_finished = level.time + 0.6; } /*QUAKED monster_berserk (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight diff --git a/src/entities/drone/drone_bitch.c b/src/entities/drone/drone_bitch.c index e564f0f3..9fa5486a 100644 --- a/src/entities/drone/drone_bitch.c +++ b/src/entities/drone/drone_bitch.c @@ -15,6 +15,7 @@ void mychick_reslash(edict_t *self); void mychick_rerocket(edict_t *self); void mychick_attack1(edict_t *self); void mychick_continue (edict_t *self); +extern mmove_t mychick_move_end_attack1; static int sound_missile_prelaunch; static int sound_missile_launch; @@ -502,8 +503,10 @@ void myChickRocket (edict_t *self) return; } - if (!G_EntExists(self->enemy)) + if (!G_EntExists(self->enemy) && self->mtype != M_CHICK_HEAT) + { return; + } damage = M_ROCKETLAUNCHER_DMG_BASE + M_ROCKETLAUNCHER_DMG_ADDON * drone_damagelevel(self); // dmg: myChickRocket if (M_ROCKETLAUNCHER_DMG_MAX && damage > M_ROCKETLAUNCHER_DMG_MAX) @@ -511,9 +514,14 @@ void myChickRocket (edict_t *self) speed = M_ROCKETLAUNCHER_SPEED_BASE + M_ROCKETLAUNCHER_SPEED_ADDON * drone_damagelevel(self); // spd: myChickRocket if (M_ROCKETLAUNCHER_SPEED_MAX && speed > M_ROCKETLAUNCHER_SPEED_MAX) speed = M_ROCKETLAUNCHER_SPEED_MAX; - + MonsterAim(self, M_PROJECTILE_ACC, speed, true, MZ2_CHICK_ROCKET_1, forward, start); - monster_fire_rocket (self, start, forward, damage, speed, MZ2_CHICK_ROCKET_1); + if (self->mtype == M_CHICK_HEAT) + { + monster_fire_heat(self, start, forward, damage, speed, MZ2_CHICK_ROCKET_1, 0.095f); + } + else + monster_fire_rocket (self, start, forward, damage, speed, MZ2_CHICK_ROCKET_1); } void myChickRail (edict_t *self) @@ -831,7 +839,7 @@ void mychick_pain(edict_t* self, edict_t* other, float kick, int damage) { const double rng = random(); if (self->health < (self->max_health / 2)) - self->s.skinnum = 1; + self->s.skinnum |= 1; // we're already in a pain state if (self->monsterinfo.currentmove == &mychick_move_pain_long || @@ -949,3 +957,10 @@ void init_drone_bitch (edict_t *self) // walkmonster_start (self); self->nextthink = level.time + 0.1; } + +void init_drone_bitch_heat (edict_t *self) +{ + init_drone_bitch(self); + self->mtype = M_CHICK_HEAT; + self->s.skinnum = 2; +} diff --git a/src/entities/drone/drone_gekk.c b/src/entities/drone/drone_gekk.c index 165844f4..0773e250 100644 --- a/src/entities/drone/drone_gekk.c +++ b/src/entities/drone/drone_gekk.c @@ -116,6 +116,8 @@ mmove_t gekk_move_walk = { FRAME_run_01, FRAME_run_06, gekk_frames_walk, NULL }; static void gekk_walk(edict_t *self) { + if (!self->goalentity) + self->goalentity = world; self->monsterinfo.currentmove = &gekk_move_walk; } diff --git a/src/entities/drone/drone_misc.c b/src/entities/drone/drone_misc.c index 171bf5e6..b5e1afab 100644 --- a/src/entities/drone/drone_misc.c +++ b/src/entities/drone/drone_misc.c @@ -14,6 +14,7 @@ qboolean drone_findtarget (edict_t *self, qboolean force); void init_drone_gunner (edict_t *self); void init_drone_parasite (edict_t *self); void init_drone_bitch (edict_t *self); +void init_drone_bitch_heat (edict_t *self); void init_drone_brain (edict_t *self); void init_drone_medic (edict_t *self); void init_drone_tank (edict_t *self); @@ -845,6 +846,7 @@ edict_t *vrx_create_drone_from_ent(edict_t *drone, edict_t *ent, enum dronespawn case DS_GUNNER: init_drone_gunner(drone); break; case DS_PARASITE: init_drone_parasite(drone); break; case DS_BITCH: init_drone_bitch(drone); break; + case DS_BITCH_HEAT: init_drone_bitch_heat(drone); break; case DS_BRAIN: init_drone_brain(drone); break; case DS_MEDIC: init_drone_medic(drone); break; case DS_TANK: init_drone_tank(drone); break; @@ -1762,7 +1764,8 @@ qboolean M_Regenerate (edict_t *self, int regen_frames, int delay, float mult, q { self->s.skinnum &= ~1; if (self->mtype != M_COMMANDER && self->mtype != M_GUNCMDR - && self->mtype != M_DAEDALUS && self->mtype != M_GLADB && self->mtype != M_GLADC) + && self->mtype != M_DAEDALUS && self->mtype != M_GLADB && self->mtype != M_GLADC + && self->mtype != M_CHICK_HEAT && self->mtype != M_SOLDIER && self->mtype != M_STALKER) self->s.skinnum &= ~2; } @@ -2043,6 +2046,7 @@ qboolean M_Initialize (edict_t *ent, edict_t *monster, float dur_bonus) { case M_GUNNER: init_drone_gunner(monster); break; case M_CHICK: init_drone_bitch(monster); break; + case M_CHICK_HEAT: init_drone_bitch_heat(monster); break; case M_BRAIN: init_drone_brain(monster); break; case M_MEDIC: init_drone_medic(monster); break; case M_MUTANT: init_drone_mutant(monster); break; @@ -2142,6 +2146,7 @@ qboolean M_SetBoundingBox (int mtype, vec3_t boxmin, vec3_t boxmax) VectorSet (boxmax, 16, 16, 0); break; case M_CHICK: + case M_CHICK_HEAT: VectorSet (boxmin, -16, -16, 0); VectorSet (boxmax, 16, 16, 56); break; @@ -2211,6 +2216,7 @@ char *GetMonsterKindString (int mtype) { case M_BRAIN: return "Brain"; case M_CHICK: return "Praetor"; + case M_CHICK_HEAT: return "Heat Praetor"; case M_MEDIC: return "Medic"; case M_MUTANT: return "Mutant"; case M_PARASITE: return "Parasite"; @@ -2247,7 +2253,7 @@ char *GetMonsterKindString (int mtype) case M_SHAMBLER: return "Shambler"; case M_REDMUTANT: return "Red Mutant"; case M_RUNNERTANK: return "Runner Tank"; - case M_GUNCMDR: return "Gun Commander"; + case M_GUNCMDR: return "Gunner Commander"; case M_DAEDALUS: return "Daedalus"; case M_GLADB: return "Gladiator Disruptor"; case M_GLADC: return "Gladiator Plasma"; @@ -2837,7 +2843,7 @@ void Cmd_Drone_f (edict_t *ent) if (!Q_strcasecmp(s, "help")) { safe_cprintf(ent, PRINT_HIGH, "Monster summoning:\n"); - safe_cprintf(ent, PRINT_HIGH, "monster [gunner|parasite|brain|praetor|medic|tank|mutant|gladiator|gladb|gladc|berserker|soldier|enforcer|flyer|floater|hover|daedalus|stalker|gekk|shambler|redmutant|runnertank|guncmdr]\n"); + safe_cprintf(ent, PRINT_HIGH, "monster [gunner|parasite|brain|praetor|praetor_heat|chick_heat|medic|tank|mutant|gladiator|gladb|gladc|berserker|soldier|enforcer|flyer|floater|hover|daedalus|stalker|gekk|shambler|redmutant|runnertank|guncmdr]\n"); safe_cprintf(ent, PRINT_HIGH, "Monster utility commands:\n"); safe_cprintf(ent, PRINT_HIGH, "monster [remove|command|follow me|count|attack]\n"); return; @@ -2856,53 +2862,56 @@ void Cmd_Drone_f (edict_t *ent) } if (!Q_strcasecmp(s, "gunner")) - vrx_create_new_drone(ent, 1, false, true, 0); + vrx_create_new_drone(ent, DS_GUNNER, false, true, 0); else if (!Q_strcasecmp(s, "parasite")) - vrx_create_new_drone(ent, 2, false, true, 0); + vrx_create_new_drone(ent, DS_PARASITE, false, true, 0); else if (!Q_strcasecmp(s, "brain")) - vrx_create_new_drone(ent, 4, false, true, 0); + vrx_create_new_drone(ent, DS_BRAIN, false, true, 0); else if (!Q_strcasecmp(s, "praetor")) - vrx_create_new_drone(ent, 3, false, true, 0); + vrx_create_new_drone(ent, DS_BITCH, false, true, 0); + else if (!Q_strcasecmp(s, "praetor_heat") || !Q_strcasecmp(s, "praetorheat") + || !Q_strcasecmp(s, "chick_heat") || !Q_strcasecmp(s, "chickheat")) + vrx_create_new_drone(ent, DS_BITCH_HEAT, false, true, 0); else if (!Q_strcasecmp(s, "medic")) - vrx_create_new_drone(ent, 5, false, true, 0); + vrx_create_new_drone(ent, DS_MEDIC, false, true, 0); else if (!Q_strcasecmp(s, "tank")) - vrx_create_new_drone(ent, 6, false, true, 0); + vrx_create_new_drone(ent, DS_TANK, false, true, 0); else if (!Q_strcasecmp(s, "mutant")) - vrx_create_new_drone(ent, 7, false, true, 0); + vrx_create_new_drone(ent, DS_MUTANT, false, true, 0); else if (!Q_strcasecmp(s, "gladiator")/* && ent->myskills.administrator*/) - vrx_create_new_drone(ent, 8, false, true, 0); + vrx_create_new_drone(ent, DS_GLADIATOR, false, true, 0); else if (!Q_strcasecmp(s, "berserker")) - vrx_create_new_drone(ent, 9, false, true, 0); + vrx_create_new_drone(ent, DS_BERSERK, false, true, 0); else if (!Q_strcasecmp(s, "soldier")) - vrx_create_new_drone(ent, 10, false, true, 0); + vrx_create_new_drone(ent, DS_SOLDIER, false, true, 0); else if (!Q_strcasecmp(s, "enforcer")) - vrx_create_new_drone(ent, 11, false, true, 0); + vrx_create_new_drone(ent, DS_INFANTRY, false, true, 0); else if (!Q_strcasecmp(s, "flyer")) - vrx_create_new_drone(ent, 12, false, true, 0); + vrx_create_new_drone(ent, DS_FLYER, false, true, 0); else if (!Q_strcasecmp(s, "floater")) - vrx_create_new_drone(ent, 13, false, true, 0); + vrx_create_new_drone(ent, DS_FLOATER, false, true, 0); else if (!Q_strcasecmp(s, "hover")) - vrx_create_new_drone(ent, 14, false, true, 0); + vrx_create_new_drone(ent, DS_HOVER, false, true, 0); else if (!Q_strcasecmp(s, "shambler")) - vrx_create_new_drone(ent, 15, false, true, 0); + vrx_create_new_drone(ent, DS_SHAMBLER, false, true, 0); else if (!Q_strcasecmp(s, "redmutant")) - vrx_create_new_drone(ent, 16, false, true, 0); + vrx_create_new_drone(ent, DS_REDMUTANT, false, true, 0); else if (!Q_strcasecmp(s, "runnertank")) - vrx_create_new_drone(ent, 17, false, true, 0); + vrx_create_new_drone(ent, DS_RUNNERTANK, false, true, 0); else if (!Q_strcasecmp(s, "guncmdr")) - vrx_create_new_drone(ent, 18, false, true, 0); + vrx_create_new_drone(ent, DS_GUNCMDR, false, true, 0); else if (!Q_strcasecmp(s, "daedalus")) - vrx_create_new_drone(ent, 19, false, true, 0); + vrx_create_new_drone(ent, DS_DAEDALUS, false, true, 0); else if (!Q_strcasecmp(s, "gladb")) - vrx_create_new_drone(ent, 23, false, true, 0); + vrx_create_new_drone(ent, DS_GLADB, false, true, 0); else if (!Q_strcasecmp(s, "gladc")) - vrx_create_new_drone(ent, 24, false, true, 0); + vrx_create_new_drone(ent, DS_GLADC, false, true, 0); else if (!Q_strcasecmp(s, "stalker")) - vrx_create_new_drone(ent, 25, false, true, 0); + vrx_create_new_drone(ent, DS_STALKER, false, true, 0); else if (!Q_strcasecmp(s, "gekk")) - vrx_create_new_drone(ent, 26, false, true, 0); + vrx_create_new_drone(ent, DS_GEKK, false, true, 0); else if (!Q_strcasecmp(s, "golem") && ent->myskills.administrator) - vrx_create_new_drone(ent, 22, false, true, 0); + vrx_create_new_drone(ent, DS_GOLEM, false, true, 0); //else if (!Q_strcasecmp(s, "baron fire") && ent->myskills.administrator) //vrx_create_new_drone(ent, 32, false, true); //else if (!Q_strcasecmp(s, "jorg")) diff --git a/src/entities/drone/drone_stalker.c b/src/entities/drone/drone_stalker.c index 07d82863..657e0ec5 100644 --- a/src/entities/drone/drone_stalker.c +++ b/src/entities/drone/drone_stalker.c @@ -79,6 +79,8 @@ mmove_t stalker_move_walk = { FRAME_walk01, FRAME_walk08, stalker_frames_walk, s static void stalker_walk(edict_t *self) { + if (!self->goalentity) + self->goalentity = world; self->monsterinfo.currentmove = &stalker_move_walk; } @@ -119,12 +121,8 @@ static void stalker_fire_ionripper(edict_t *self) VectorSubtract(target, start, dir); VectorNormalize(dir); - fire_ionripper(self, start, dir, damage, speed, EF_IONRIPPER); + monster_fire_blaster2(self, start, dir, damage, speed, EF_BLASTER, MZ2_STALKER_BLASTER); - gi.WriteByte(svc_muzzleflash); - gi.WriteShort(self - g_edicts); - gi.WriteByte(MZ_IONRIPPER | MZ_SILENCED); - gi.multicast(start, MULTICAST_PVS); } mframe_t stalker_frames_shoot[] = @@ -322,6 +320,8 @@ void init_drone_stalker(edict_t *self) self->monsterinfo.sight = stalker_sight; self->monsterinfo.idle = stalker_idle; self->monsterinfo.pain_chance = 0.3f; + self->monsterinfo.jumpup = 64; + self->monsterinfo.jumpdn = 512; gi.linkentity(self); diff --git a/src/entities/drone/g_monster.c b/src/entities/drone/g_monster.c index 840c41fd..d0bab532 100644 --- a/src/entities/drone/g_monster.c +++ b/src/entities/drone/g_monster.c @@ -187,6 +187,211 @@ void monster_fire_blaster (edict_t *self, vec3_t start, vec3_t dir, int damage, gi.multicast (start, MULTICAST_PVS); } +void monster_fire_blaster2(edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect, int flashtype) +{ + float chance; + + if (que_typeexists(self->curses, AURA_HOLYFREEZE) && random() <= 0.5) + return; + + if (self->chill_time > level.time) + { + chance = 1 / (1 + CHILL_DEFAULT_BASE + CHILL_DEFAULT_ADDON * self->chill_level); + if (random() > chance) + return; + } + + damage = vrx_increase_monster_damage_by_talent(self->activator, damage); + fire_blaster2(self, start, dir, damage, speed, effect, false); + + gi.WriteByte(svc_muzzleflash2); + gi.WriteShort(self - g_edicts); + gi.WriteByte(flashtype); + gi.multicast(start, MULTICAST_PVS); +} + +void monster_fire_blueblaster(edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect, int flashtype) +{ + float chance; + + if (que_typeexists(self->curses, AURA_HOLYFREEZE) && random() <= 0.5) + return; + + if (self->chill_time > level.time) + { + chance = 1 / (1 + CHILL_DEFAULT_BASE + CHILL_DEFAULT_ADDON * self->chill_level); + if (random() > chance) + return; + } + + damage = vrx_increase_monster_damage_by_talent(self->activator, damage); + fire_blueblaster(self, start, dir, damage, speed, effect); + + gi.WriteByte(svc_muzzleflash2); + gi.WriteShort(self - g_edicts); + gi.WriteByte(flashtype); + gi.multicast(start, MULTICAST_PVS); +} + +void rocket_touch(edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf); + +static qboolean heat_valid_target(edict_t *self, edict_t *target) +{ + if (target == self->owner) + return false; + if (!G_ValidTargetEnt(self->owner, target, true)) + return false; + if (self->owner && OnSameTeam(self->owner, target)) + return false; + if (!visible(self, target)) + return false; + return true; +} + +static void heat_think(edict_t *self) +{ + edict_t *target = NULL; + edict_t *acquire = NULL; + float best_dot = 1.0f; + float best_dist = 0.0f; + float turn_fraction; + float dot, dist; + vec3_t forward, dir; + + if (!G_EntExists(self->owner) || level.time >= self->delay) + { + BecomeExplosion1(self); + return; + } + + AngleVectors(self->s.angles, forward, NULL, NULL); + + if (heat_valid_target(self, self->enemy)) + acquire = self->enemy; + else + { + self->enemy = NULL; + } + + if (!acquire) + { + while ((target = findradius(target, self->s.origin, 1024)) != NULL) + { + if (!heat_valid_target(self, target)) + continue; + + VectorSubtract(self->s.origin, target->s.origin, dir); + dist = VectorNormalize(dir); + dot = DotProduct(dir, forward); + + if (dot >= best_dot) + continue; + if (!acquire || dot < best_dot || dist < best_dist) + { + acquire = target; + best_dot = dot; + best_dist = dist; + } + } + } + + if (acquire) + { + VectorSubtract(acquire->s.origin, self->s.origin, dir); + if (!VectorNormalize(dir)) + { + self->nextthink = level.time + FRAMETIME; + return; + } + + turn_fraction = self->accel; + if (turn_fraction < 0) + turn_fraction = 0; + else if (turn_fraction > 1) + turn_fraction = 1; + + dot = DotProduct(self->movedir, dir); + if (dot < 0.45f && dot > -0.45f) + VectorNegate(dir, dir); + + VectorScale(self->movedir, 1.0f - turn_fraction, self->movedir); + VectorMA(self->movedir, turn_fraction, dir, self->movedir); + VectorNormalize(self->movedir); + vectoangles(self->movedir, self->s.angles); + self->enemy = acquire; + } + else + { + self->enemy = NULL; + } + + VectorScale(self->movedir, self->speed, self->velocity); + self->nextthink = level.time + FRAMETIME; +} + +qboolean monster_fire_heat(edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype, float turn_fraction) +{ + float chance; + edict_t *heat; + + if (que_typeexists(self->curses, AURA_HOLYFREEZE) && random() <= 0.5) + return false; + + if (self->chill_time > level.time) + { + chance = 1 / (1 + CHILL_DEFAULT_BASE + CHILL_DEFAULT_ADDON * self->chill_level); + if (random() > chance) + return false; + } + + damage = vrx_increase_monster_damage_by_talent(self->activator, damage); + if (speed < 1) + speed = 1; + self->lastsound = level.framenum; + VectorNormalize(dir); + + heat = G_Spawn(); + VectorCopy(start, heat->s.origin); + VectorCopy(start, heat->s.old_origin); + VectorCopy(dir, heat->movedir); + vectoangles(dir, heat->s.angles); + VectorScale(dir, speed, heat->velocity); + heat->movetype = MOVETYPE_FLYMISSILE; + heat->clipmask = MASK_SHOT; + heat->solid = SOLID_BBOX; + heat->s.effects |= EF_ROCKET; + VectorClear(heat->mins); + VectorClear(heat->maxs); + heat->s.modelindex = gi.modelindex("models/objects/rocket/tris.md2"); + heat->owner = self; + heat->touch = rocket_touch; + heat->speed = speed; + heat->accel = turn_fraction; + heat->delay = level.time + 8000 * (sv_fps->value) / speed; + heat->nextthink = level.time + FRAMETIME; + heat->think = heat_think; + heat->dmg = damage; + heat->radius_dmg = damage; + heat->dmg_radius = damage; + heat->s.sound = gi.soundindex("weapons/rockfly.wav"); + heat->classname = "heat rocket"; + + if (G_ValidTarget(self, self->enemy, true, true)) + heat->enemy = self->enemy; + + gi.linkentity(heat); + + if (self->client) + check_dodge(self, heat->s.origin, dir, speed, damage); + + gi.WriteByte(svc_muzzleflash2); + gi.WriteShort(self - g_edicts); + gi.WriteByte(flashtype); + gi.multicast(start, MULTICAST_PVS); + + return true; +} + void monster_fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int flashtype) { float radius, chance; diff --git a/src/entities/g_spawn.c b/src/entities/g_spawn.c index 1756562c..94b9b9f2 100644 --- a/src/entities/g_spawn.c +++ b/src/entities/g_spawn.c @@ -276,6 +276,7 @@ spawn_t spawns[] = { {"monster_tank_commander", SP_monster_tank_commander}, {"monster_medic", SP_monster_medic}, {"monster_chick", SP_monster_chick}, + {"monster_chick_heat", SP_monster_chick_heat}, {"monster_parasite", SP_monster_parasite}, {"monster_brain", SP_monster_brain}, {"monster_mutant", SP_monster_mutant}, @@ -1112,7 +1113,13 @@ void SP_monster_mutant(edict_t *ent) void SP_monster_chick(edict_t *ent) { if (coop->value) - vrx_create_drone_from_ent(ent, g_edicts, 3, true, true, 0); + vrx_create_drone_from_ent(ent, g_edicts, DS_BITCH, true, true, 0); +} + +void SP_monster_chick_heat(edict_t *ent) +{ + if (coop->value) + vrx_create_drone_from_ent(ent, g_edicts, DS_BITCH_HEAT, true, true, 0); } void SP_monster_parasite(edict_t *ent) diff --git a/src/g_local.h b/src/g_local.h index 309c3f44..595232ec 100644 --- a/src/g_local.h +++ b/src/g_local.h @@ -1418,6 +1418,12 @@ void monster_fire_shotgun(edict_t *self, vec3_t start, vec3_t aimdir, float dama void monster_fire_blaster(edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect, int proj_type, float duration, qboolean bounce, int flashtype); +void monster_fire_blaster2(edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect, int flashtype); + +void monster_fire_blueblaster(edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect, int flashtype); + +qboolean monster_fire_heat(edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype, float turn_fraction); + void monster_fire_grenade(edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int flashtype); void monster_fire_rocket(edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype); @@ -1508,6 +1514,7 @@ enum mtype_t { M_GLADC = 33, M_STALKER = 34, M_GEKK = 35, + M_CHICK_HEAT = 36, M_MINISENTRY = 100, M_SENTRY = 101, M_BFG_SENTRY = 102, @@ -1614,6 +1621,7 @@ enum dronespawn_t { DS_GLADC = 24, DS_STALKER = 25, DS_GEKK = 26, + DS_BITCH_HEAT = 27, DS_COMMANDER = 30, DS_MAKRON = 31, DS_BARON_FIRE = 32, @@ -1716,6 +1724,8 @@ qboolean fire_player_melee(edict_t *self, vec3_t start, vec3_t dir, int range, i extern byte is_silenced; extern qboolean is_quadfire; +void check_dodge(edict_t *self, vec3_t start, vec3_t dir, int speed, int radius); + void fire_bullet(edict_t *self, vec3_t start, vec3_t aimdir, float damage, int kick, int hspread, int vspread, int mod); void fire_shotgun(edict_t *self, vec3_t start, vec3_t aimdir, float damage, int kick, int hspread, int vspread, @@ -1723,6 +1733,7 @@ void fire_shotgun(edict_t *self, vec3_t start, vec3_t aimdir, float damage, int void fire_blaster(edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect, int proj_type, int mod, float duration, qboolean bounce); +void fire_blaster2(edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect, qboolean hyper); void fire_grenade(edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius, int radius_damage); diff --git a/src/gamemodes/invasion.c b/src/gamemodes/invasion.c index 0567b31b..289cec85 100644 --- a/src/gamemodes/invasion.c +++ b/src/gamemodes/invasion.c @@ -44,6 +44,7 @@ static constexpr int SET_EASY_MODE_MONSTERS[] = { DS_DAEDALUS, DS_STALKER, DS_GEKK, + DS_BITCH_HEAT, }; constexpr int SET_EASY_MODE_MONSTERS_COUNT = sizeof(SET_EASY_MODE_MONSTERS) / sizeof(int); @@ -51,7 +52,7 @@ constexpr int SET_EASY_MODE_MONSTERS_COUNT = sizeof(SET_EASY_MODE_MONSTERS) / si // parasite, brain, medic, tank, mutant, gladiator, berserker, infantry, hover static constexpr int SET_HARD_MODE_MONSTERS[] = { DS_PARASITE, DS_BRAIN, DS_MEDIC, DS_TANK, DS_MUTANT, DS_GLADIATOR, DS_BERSERK, DS_INFANTRY, DS_HOVER, - DS_REDMUTANT, DS_RUNNERTANK, DS_GUNCMDR, DS_DAEDALUS, DS_GLADB, DS_GLADC, DS_STALKER, DS_GEKK + DS_REDMUTANT, DS_RUNNERTANK, DS_GUNCMDR, DS_DAEDALUS, DS_GLADB, DS_GLADC, DS_STALKER, DS_GEKK, DS_BITCH_HEAT }; constexpr int SET_HARD_MODE_MONSTERS_COUNT = sizeof(SET_HARD_MODE_MONSTERS) / sizeof(int); diff --git a/src/menus/upgrades.c b/src/menus/upgrades.c index 7884a9be..2c5893f3 100644 --- a/src/menus/upgrades.c +++ b/src/menus/upgrades.c @@ -426,14 +426,15 @@ int writeAbilityDescription(edict_t* ent, int abilityIndex) menu_add_line(ent, "Uses power cubes.", MENU_WHITE_CENTERED); menu_add_line(ent, "Summon commands:", MENU_WHITE_CENTERED); menu_add_line(ent, "monster [gunner|parasite", MENU_WHITE_CENTERED); - menu_add_line(ent, "brain|praetor|medic|tank", MENU_WHITE_CENTERED); - menu_add_line(ent, "mutant|gladiator|berserker", MENU_WHITE_CENTERED); + menu_add_line(ent, "brain|praetor|praetor_heat", MENU_WHITE_CENTERED); + menu_add_line(ent, "medic|tank|mutant", MENU_WHITE_CENTERED); + menu_add_line(ent, "gladiator|berserker", MENU_WHITE_CENTERED); menu_add_line(ent, "soldier|enforcer|flyer", MENU_WHITE_CENTERED); menu_add_line(ent, "floater|hover]", MENU_WHITE_CENTERED); menu_add_line(ent, "Utility commands:", MENU_WHITE_CENTERED); menu_add_line(ent, "monster [remove|command", MENU_WHITE_CENTERED); menu_add_line(ent, "follow me|count|attack]", MENU_WHITE_CENTERED); - return 12; + return 13; case SKELETON: menu_add_line(ent, "Raise skeletons to protect", MENU_WHITE_CENTERED); menu_add_line(ent, "you and fight your enemies!", MENU_WHITE_CENTERED); diff --git a/src/quake2/g_layout.c b/src/quake2/g_layout.c index e17e83d6..63f6f5e9 100644 --- a/src/quake2/g_layout.c +++ b/src/quake2/g_layout.c @@ -308,6 +308,7 @@ sidebar_entry_t layout_add_entity_info(sidebar_t* sidebar, edict_t* ent) case M_INSANE: case M_GUNNER: case M_CHICK: + case M_CHICK_HEAT: case M_PARASITE: case M_FLOATER: case M_HOVER: diff --git a/to add/m_gladiator.cpp b/to add/m_gladiator.cpp deleted file mode 100644 index 9dbaf7b6..00000000 --- a/to add/m_gladiator.cpp +++ /dev/null @@ -1,714 +0,0 @@ -// Copyright (c) ZeniMax Media Inc. -// Licensed under the GNU General Public License 2.0. -/* -============================================================================== - -GLADIATOR - -============================================================================== -*/ - -#include "g_local.h" -#include "m_gladiator.h" -#include "m_flash.h" -#include "shared.h" -#include "horde/g_horde_scaling.h" -#include "monster_constants.h" - -static cached_soundindex sound_pain1; -static cached_soundindex sound_pain2; -static cached_soundindex sound_die; -static cached_soundindex sound_die2; -static cached_soundindex sound_gun; -static cached_soundindex sound_gunb; -static cached_soundindex sound_gunc; -static cached_soundindex sound_cleaver_swing; -static cached_soundindex sound_cleaver_hit; -static cached_soundindex sound_cleaver_miss; -static cached_soundindex sound_idle; -static cached_soundindex sound_search; -static cached_soundindex sound_sight; - -MONSTERINFO_IDLE(gladiator_idle) (edict_t* self) -> void -{ - gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); -} - -MONSTERINFO_SIGHT(gladiator_sight) (edict_t* self, edict_t* other) -> void -{ - gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); -} - -MONSTERINFO_SEARCH(gladiator_search) (edict_t* self) -> void -{ - gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); -} - -void gladiator_cleaver_swing(edict_t* self) -{ - gi.sound(self, CHAN_WEAPON, sound_cleaver_swing, 1, ATTN_NORM, 0); -} - -mframe_t gladiator_frames_stand[] = { - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand } -}; -MMOVE_T(gladiator_move_stand) = { FRAME_stand1, FRAME_stand7, gladiator_frames_stand, nullptr }; - -MONSTERINFO_STAND(gladiator_stand) (edict_t* self) -> void -{ - M_SetAnimation(self, &gladiator_move_stand); -} - -mframe_t gladiator_frames_walk[] = { - { ai_walk, 15 }, - { ai_walk, 7 }, - { ai_walk, 6 }, - { ai_walk, 5 }, - { ai_walk, 2, monster_footstep }, - { ai_walk }, - { ai_walk, 2 }, - { ai_walk, 8 }, - { ai_walk, 12 }, - { ai_walk, 8 }, - { ai_walk, 5 }, - { ai_walk, 5 }, - { ai_walk, 2, monster_footstep }, - { ai_walk, 2 }, - { ai_walk, 1 }, - { ai_walk, 8 } -}; -MMOVE_T(gladiator_move_walk) = { FRAME_walk1, FRAME_walk16, gladiator_frames_walk, nullptr }; - -MONSTERINFO_WALK(gladiator_walk) (edict_t* self) -> void -{ - M_SetAnimation(self, &gladiator_move_walk); -} - -mframe_t gladiator_frames_run[] = { - { ai_run, 23 }, - { ai_run, 14 }, - { ai_run, 14, monster_footstep }, - { ai_run, 21 }, - { ai_run, 12 }, - { ai_run, 13, monster_footstep } -}; -MMOVE_T(gladiator_move_run) = { FRAME_run1, FRAME_run6, gladiator_frames_run, nullptr }; - -MONSTERINFO_RUN(gladiator_run) (edict_t* self) -> void -{ - if (self->monsterinfo.aiflags & AI_STAND_GROUND) - M_SetAnimation(self, &gladiator_move_stand); - else - M_SetAnimation(self, &gladiator_move_run); -} - -void GladiatorMelee(edict_t* self) -{ - if (!M_HasValidTarget(self)) - { - self->monsterinfo.melee_debounce_time = level.time + 1.5_sec; - return; - } - - vec3_t const aim = { MELEE_DISTANCE, self->mins[0], -4 }; - - if (fire_hit(self, aim, irandom(30, 35), 300)) - { - gi.sound(self, CHAN_AUTO, sound_cleaver_hit, 1, ATTN_NORM, 0); - } - else - { - gi.sound(self, CHAN_AUTO, sound_cleaver_miss, 1, ATTN_NORM, 0); - self->monsterinfo.melee_debounce_time = level.time + 1.5_sec; - } -} - -mframe_t gladiator_frames_attack_melee[] = { - { ai_charge, 0, gladiator_cleaver_swing }, - { ai_charge, 0, GladiatorMelee }, - { ai_charge, 0, gladiator_cleaver_swing }, - { ai_charge, 0, GladiatorMelee }, - { ai_charge, 0, GladiatorMelee }, - { ai_charge }, - { ai_charge }, - { ai_charge }, - { ai_charge, 0, gladiator_cleaver_swing }, - { ai_charge }, - { ai_charge }, - { ai_charge, 0, GladiatorMelee }, - { ai_charge }, - { ai_charge, 0, GladiatorMelee }, -}; -MMOVE_T(gladiator_move_attack_melee) = { FRAME_melee3, FRAME_melee16, gladiator_frames_attack_melee, gladiator_run }; - -MONSTERINFO_MELEE(gladiator_melee) (edict_t* self) -> void -{ - M_SetAnimation(self, &gladiator_move_attack_melee); -} - -void GladiatorGun(edict_t* self) -{ - if (!M_HasValidTarget(self)) - { - return; // Stop immediately if the target is invalid. - } - - // Railgun is hitscan, requires visibility (no blindfire) - if (!visible(self, self->enemy)) - return; - - int damage = M_RAILGUN_DMG(self); - - vec3_t start; - vec3_t dir; - vec3_t forward, right; - - AngleVectors(self->s.angles, forward, right, nullptr); - start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_GLADIATOR_RAILGUN_1], forward, right); - - // calc direction to where we targted - dir = self->pos1 - start; - dir.normalize(); - - monster_fire_railgun(self, start, dir, damage, 100, MZ2_GLADIATOR_RAILGUN_1); -} - -void Gladiator_refire_chance(edict_t* self) -{ - if (M_HasValidTarget(self) && frandom() < 0.25f) - self->monsterinfo.nextframe = FRAME_attack1; // refire -} - -mframe_t gladiator_frames_attack_gun[] = { - { ai_charge }, - { ai_charge, 0, GladiatorGun }, - { ai_charge }, - { ai_charge }, - { ai_charge,0, Gladiator_refire_chance }, - { ai_charge }, - { ai_charge }, - { ai_charge, 0, monster_footstep }, - { ai_charge } -}; -MMOVE_T(gladiator_move_attack_gun) = { FRAME_attack1, FRAME_attack9, gladiator_frames_attack_gun, gladiator_run }; - -// RAFAEL -void gladbGun(edict_t* self) -{ - if (!M_HasValidTarget(self)) - { - return; // Stop immediately if the target is invalid. - } - - int damage = M_TRACKER_DMG(self); - - vec3_t start; - vec3_t dir; - vec3_t forward, right; - float len; - - AngleVectors(self->s.angles, forward, right, nullptr); - start = G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_GLADIATOR_RAILGUN_1], forward, right); - - // Blindfire support for tracker - bool blindfire = (self->monsterinfo.aiflags & AI_MANUAL_STEERING); - vec3_t target_pos; - - if (blindfire && self->monsterinfo.blind_fire_target) - { - target_pos = self->monsterinfo.blind_fire_target; - } - else - { - target_pos = self->pos1; // Use saved position from attack start - } - - dir = target_pos - self->enemy->s.origin; - len = dir.length(); - - if (len < 30) - { - // calc direction to where we targeted - dir = target_pos - start; - dir.normalize(); - - monster_fire_tracker(self, start, dir, damage, M_TRACKER_SPEED(self), self->enemy, MZ2_GLADIATOR_RAILGUN_1); - } - else - { - if (blindfire) - { - // Blindfire: aim at blind_fire_target - dir = (target_pos - start).normalized(); - monster_fire_tracker(self, start, dir, damage, M_TRACKER_SPEED(self), nullptr, MZ2_GLADIATOR_RAILGUN_1); - } - else - { - // Normal: use predictive aim - PredictAim(self, self->enemy, start, 980, true, 0, &dir, nullptr); - monster_fire_tracker(self, start, dir, damage, M_TRACKER_SPEED(self), nullptr, MZ2_GLADIATOR_RAILGUN_1); - } - } -} - -void gladbGun_check(edict_t* self) -{ - if (skill->integer == 3) - gladbGun(self); -} - -mframe_t gladb_frames_attack_gun[] = { - { ai_charge }, - { ai_charge, 0, gladbGun }, - { ai_charge }, - { ai_charge }, - { ai_charge, 0, gladbGun }, - { ai_charge }, - { ai_charge }, - { ai_charge }, - { ai_charge, 0, Gladiator_refire_chance } - -}; -MMOVE_T(gladb_move_attack_gun) = { FRAME_attack1, FRAME_attack9, gladb_frames_attack_gun, gladiator_run }; -// HORDE -// RAFAEL -void gladcGun(edict_t* self) -{ - if (!M_HasValidTarget(self)) - { - return; // Stop immediately if the target is invalid. - } - - int damage = M_GET_DMG_OR(self, PLASMA, 35); - - int radius_damage = 45; - - vec3_t start; - vec3_t dir; - vec3_t forward, right; - - AngleVectors(self->s.angles, forward, right, nullptr); - start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_GLADIATOR_RAILGUN_1], forward, right); - - // Blindfire support for plasma - bool blindfire = (self->monsterinfo.aiflags & AI_MANUAL_STEERING); - vec3_t target_pos; - - if (blindfire && self->monsterinfo.blind_fire_target) - { - target_pos = self->monsterinfo.blind_fire_target; - } - else - { - target_pos = self->pos1; // Use saved position from attack start - } - - // calc direction to where we targeted - dir = target_pos - start; - dir.normalize(); - - if (self->s.frame > FRAME_attack3) - { - damage /= 2; - radius_damage /= 2; - } - float const r = frandom(); - fire_plasma(self, start, dir, damage, M_PLASMA_SPEED(self), M_PLASMA_RADIUS(self), M_PLASMA_RADIUS(self)); - if (r < 0.5f && current_wave_level >= 18) { - fire_plasma(self, start, dir, damage, 1225, M_PLASMA_RADIUS(self), M_PLASMA_RADIUS(self)); - } - - // FIX: Check if the enemy is still valid before updating the aim position. - // The plasma shot we just fired might have killed it. - if (self->enemy && self->enemy->inuse) - { - // save for aiming the shot - self->pos1 = self->enemy->s.origin; - self->pos1[2] += self->enemy->viewheight; - } -} - -void gladcGun_check(edict_t* self) -{ - if (skill->integer == 3) - gladcGun(self); -} - -mframe_t gladc_frames_attack_gun[] = { - { ai_charge }, - { ai_charge }, - { ai_charge, 0, gladcGun }, - { ai_charge }, - { ai_charge }, - { ai_charge, 0, gladcGun }, - { ai_charge }, - { ai_charge }, - { ai_charge, 0, gladcGun_check } -}; -MMOVE_T(gladc_move_attack_gun) = { FRAME_attack1, FRAME_attack9, gladc_frames_attack_gun, gladiator_run }; - -// RAFAEL - -MONSTERINFO_ATTACK(gladiator_attack) (edict_t* self) -> void -{ - monster_done_dodge(self); - - if (!M_HasValidTarget(self)) - { - return; // Stop immediately if the target is invalid. - } - - // Pre-calculate distance once for performance - vec3_t const v = self->s.origin - self->enemy->s.origin; - float const range = v.length(); - if (range <= (MELEE_DISTANCE + 32) && self->monsterinfo.melee_debounce_time <= level.time) - return; - else if (!M_CheckClearShot(self, monster_flash_offset[MZ2_GLADIATOR_RAILGUN_1])) - return; - - // charge up the railgun - self->pos1 = self->enemy->s.origin; // save for aiming the shot - self->pos1[2] += self->enemy->viewheight; - // RAFAEL - if (self->style == 1) - { - gi.sound(self, CHAN_WEAPON, sound_gunb, 1, ATTN_NORM, 0); - M_SetAnimation(self, &gladb_move_attack_gun); - } - else if (self->style == 3) - { - gi.sound(self, CHAN_WEAPON, sound_gun, 1, ATTN_NORM, 0); - M_SetAnimation(self, &gladc_move_attack_gun); - } - else - { - // RAFAEL - gi.sound(self, CHAN_WEAPON, sound_gun, 1, ATTN_NORM, 0); - M_SetAnimation(self, &gladiator_move_attack_gun); - // RAFAEL - } - // RAFAEL -} - -mframe_t gladiator_frames_pain[] = { - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move } -}; -MMOVE_T(gladiator_move_pain) = { FRAME_pain2, FRAME_pain5, gladiator_frames_pain, gladiator_run }; - -mframe_t gladiator_frames_pain_air[] = { - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move } -}; -MMOVE_T(gladiator_move_pain_air) = { FRAME_painup2, FRAME_painup6, gladiator_frames_pain_air, gladiator_run }; - -PAIN(gladiator_pain) (edict_t* self, edict_t* other, float kick, int damage, const mod_t& mod) -> void -{ - if (level.time < self->pain_debounce_time) - { - if ((self->velocity[2] > 100) && (self->monsterinfo.active_move == &gladiator_move_pain)) - M_SetAnimation(self, &gladiator_move_pain_air); - return; - } - - self->pain_debounce_time = level.time + 3_sec; - - if (frandom() < 0.5f) - gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); - else - gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); - - if (!M_ShouldReactToPain(self, mod)) - return; // no pain anims in nightmare - - if (self->velocity[2] > 100) - M_SetAnimation(self, &gladiator_move_pain_air); - else - M_SetAnimation(self, &gladiator_move_pain); -} - -MONSTERINFO_SETSKIN(gladiator_setskin) (edict_t* self) -> void -{ - if (self->health < (self->max_health / 2)) - self->s.skinnum |= 1; - else - self->s.skinnum &= ~1; -} - -void gladiator_dead(edict_t* self) -{ - self->mins = { -16, -16, -24 }; - self->maxs = { 16, 16, -8 }; - monster_dead(self); -} - -static void gladiator_shrink(edict_t* self) -{ - self->maxs[2] = 0; - self->svflags |= SVF_DEADMONSTER; - gi.linkentity(self); -} - -mframe_t gladiator_frames_death[] = { - { ai_move }, - { ai_move }, - { ai_move, 0, gladiator_shrink }, - { ai_move, 0, monster_footstep }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move } -}; -MMOVE_T(gladiator_move_death) = { FRAME_death2, FRAME_death22, gladiator_frames_death, gladiator_dead }; - -DIE(gladiator_die) (edict_t* self, edict_t* inflictor, edict_t* attacker, int damage, const vec3_t& point, const mod_t& mod) -> void -{ - //OnEntityDeath(self); - // check for gib - if (M_CheckGib(self, mod)) - { - gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); - - self->s.skinnum /= 2; - - ThrowGibs(self, damage, { - { 2, "models/objects/gibs/bone/tris.md2" }, - { 2, "models/objects/gibs/sm_meat/tris.md2" }, - { 2, "models/monsters/gladiatr/gibs/thigh.md2", GIB_SKINNED }, - { "models/monsters/gladiatr/gibs/larm.md2", GIB_SKINNED | GIB_UPRIGHT }, - { "models/monsters/gladiatr/gibs/rarm.md2", GIB_SKINNED | GIB_UPRIGHT }, - { "models/monsters/gladiatr/gibs/chest.md2", GIB_SKINNED }, - { "models/monsters/gladiatr/gibs/head.md2", GIB_SKINNED | GIB_HEAD } - }); - self->deadflag = true; - return; - } - - if (self->deadflag) - return; - - // regular death - gi.sound(self, CHAN_BODY, sound_die, 1, ATTN_NORM, 0); - - if (brandom()) - gi.sound(self, CHAN_VOICE, sound_die2, 1, ATTN_NORM, 0); - - self->deadflag = true; - self->takedamage = true; - - M_SetAnimation(self, &gladiator_move_death); -} - -//=========== -// PGM -MONSTERINFO_BLOCKED(gladiator_blocked) (edict_t* self, float dist) -> bool -{ - if (blocked_checkplat(self, dist)) - return true; - - // Check if we can jump - if (auto const result = blocked_checkjump(self, dist); result != blocked_jump_result_t::NO_JUMP) - { - if (result != blocked_jump_result_t::JUMP_TURN) - { - // Reduced forward velocity to prevent excessive jump distance - M_MonsterJump(self, 180.0f, 250.0f); - } - return true; - } - - return false; -} -// PGM -//=========== - -/*QUAKED monster_gladiator (1 .5 0) (-32 -32 -24) (32 32 64) Ambush Trigger_Spawn Sight -*/ -void SP_monster_gladiator(edict_t* self) -{ - const spawn_temp_t& st = ED_GetSpawnTemp(); - - // Early exit check - if (!M_AllowSpawn(self)) { - G_FreeEdict(self); - return; - } - - if (self->monsterinfo.monster_type_id == MONSTER_TYPE_UNKNOWN) { // Check if it hasn't been set yet - self->monsterinfo.monster_type_id = static_cast(horde::MonsterTypeID::GLADIATOR); - } if (g_horde->integer && current_wave_level <= 18) - { - const float randomsearch = frandom(); // Generar un número aleatorio entre 0 y 1 - - if (randomsearch < 0.23f) - gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); - } - - - // Sound assignments - sound_pain1.assign("gladiator/pain.wav"); - sound_pain2.assign("gladiator/gldpain2.wav"); - sound_die.assign("gladiator/glddeth2.wav"); - sound_die2.assign("gladiator/death.wav"); - sound_cleaver_swing.assign("gladiator/melee1.wav"); - sound_cleaver_hit.assign("gladiator/melee2.wav"); - sound_cleaver_miss.assign("gladiator/melee3.wav"); - sound_idle.assign("gladiator/gldidle1.wav"); - sound_search.assign("gladiator/gldsrch1.wav"); - sound_sight.assign("gladiator/sight.wav"); - - // Model setup - self->s.modelindex = gi.modelindex("models/monsters/gladiatr/tris.md2"); - gi.modelindex("models/monsters/gladiatr/gibs/chest.md2"); - gi.modelindex("models/monsters/gladiatr/gibs/head.md2"); - gi.modelindex("models/monsters/gladiatr/gibs/larm.md2"); - gi.modelindex("models/monsters/gladiatr/gibs/rarm.md2"); - gi.modelindex("models/monsters/gladiatr/gibs/thigh.md2"); - - // Basic monster properties - self->movetype = MOVETYPE_STEP; - self->solid = SOLID_BBOX; - self->gib_health = -175; - self->mins = { -32, -32, -24 }; - self->maxs = { 32, 32, 42 }; - - // Type-specific configuration based on style - switch (self->style) { - case 1: // gladb - sound_gunb.assign("weapons/disrupt.wav"); - { - if (g_horde && g_horde->integer && current_wave_level > 0) { - self->health = M_GLADIATOR_ADDON_HEALTH(self); - } else { - self->health = static_cast(M_GLADIATOR_INITIAL_HEALTH * st.health_multiplier); - } - } - self->mass = 350; - - self->s.skinnum = 2; - self->s.effects = EF_TRACKER; - self->monsterinfo.weapon_sound = gi.soundindex("weapons/phaloop.wav"); - break; - - case 3: // gladc - sound_gunc.assign("weapons/plasshot.wav"); - { - if (g_horde && g_horde->integer && current_wave_level > 0) { - self->health = M_GLADIATOR_ADDON_HEALTH(self); - } else { - self->health = static_cast(M_GLADIATOR_INITIAL_HEALTH * st.health_multiplier); - } - } - self->mass = 350; - - self->s.skinnum = 2; - self->monsterinfo.weapon_sound = gi.soundindex("weapons/phaloop.wav"); - break; - - default: // normal gladiator - sound_gun.assign("gladiator/railgun.wav"); - { - if (g_horde && g_horde->integer && current_wave_level > 0) { - self->health = M_GLADIATOR_ADDON_HEALTH(self); - } else { - self->health = static_cast(M_GLADIATOR_INITIAL_HEALTH * st.health_multiplier); - } - } - self->mass = 400; - - self->monsterinfo.weapon_sound = gi.soundindex("weapons/rg_hum.wav"); - break; - } - - // Armor setup based on actual monster_type_id (not hardcoded GLADIATOR) - // This ensures each variant gets the correct armor type from its JSON config - uint8_t type_id = self->monsterinfo.monster_type_id; - - // Get armor configuration dynamically based on monster_type_id - int base_armor = GetMonsterBaseArmor(type_id); - item_id_t power_armor_type = static_cast(GetMonsterPowerArmorType(type_id)); - - // Set power armor if configured - if (!st.was_key_specified("power_armor_type") && power_armor_type != IT_NULL) { - self->monsterinfo.power_armor_type = power_armor_type; - if (!st.was_key_specified("power_armor_power")) - self->monsterinfo.power_armor_power = GetMonsterScaledPowerArmor(type_id, current_wave_level, self->monsterinfo.IS_BOSS); - } - - // Set regular armor if configured - if (!st.was_key_specified("armor_type") && base_armor > 0) { - self->monsterinfo.armor_type = IT_ARMOR_COMBAT; - if (!st.was_key_specified("armor_power")) - self->monsterinfo.armor_power = GetMonsterScaledArmor(type_id, current_wave_level, self->monsterinfo.IS_BOSS); - } - - // Monster AI/behavior functions - self->pain = gladiator_pain; - self->die = gladiator_die; - self->monsterinfo.stand = gladiator_stand; - self->monsterinfo.walk = gladiator_walk; - self->monsterinfo.run = gladiator_run; - self->monsterinfo.dodge = nullptr; - self->monsterinfo.attack = gladiator_attack; - self->monsterinfo.melee = gladiator_melee; - self->monsterinfo.sight = gladiator_sight; - self->monsterinfo.idle = gladiator_idle; - self->monsterinfo.search = gladiator_search; - self->monsterinfo.blocked = gladiator_blocked; - self->monsterinfo.setskin = gladiator_setskin; - - // Horde mode specific - if (g_horde->integer && current_wave_level <= 18) { - if (frandom() < 0.23f) - gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); - } - - // Final setup - gi.linkentity(self); - M_SetAnimation(self, &gladiator_move_stand); - self->monsterinfo.scale = MODEL_SCALE; - walkmonster_start(self); - ApplyMonsterBonusFlags(self); -} - -/*QUAKED monster_gladb (1 .5 0) (-32 -32 -24) (32 32 64) Ambush Trigger_Spawn Sight -*/ -void SP_monster_gladb(edict_t* self) -{ - self->style = 1; - self->monsterinfo.monster_type_id = static_cast(horde::MonsterTypeID::GLADIATOR_B); - SP_monster_gladiator(self); - // All armor and health configuration is handled by SP_monster_gladiator's switch statement -} - -void SP_monster_gladc(edict_t* self) -{ - self->style = 3; - self->monsterinfo.monster_type_id = static_cast(horde::MonsterTypeID::GLADIATOR_C); - SP_monster_gladiator(self); - // All armor and health configuration is handled by SP_monster_gladiator's switch statement -} \ No newline at end of file diff --git a/to add/m_guncmdr.cpp b/to add/m_guncmdr.cpp deleted file mode 100644 index e1559702..00000000 --- a/to add/m_guncmdr.cpp +++ /dev/null @@ -1,1475 +0,0 @@ -// Copyright (c) ZeniMax Media Inc. -// Licensed under the GNU General Public License 2.0. -/* -============================================================================== - -GUNNER - -============================================================================== -*/ - -#include "g_local.h" -#include "m_gunner.h" -#include "m_flash.h" - -constexpr spawnflags_t SPAWNFLAG_GUNCMDR_NOJUMPING = 8_spawnflag; - -static cached_soundindex sound_pain; -static cached_soundindex sound_pain2; -static cached_soundindex sound_death; -static cached_soundindex sound_idle; -static cached_soundindex sound_open; -static cached_soundindex sound_search; -static cached_soundindex sound_sight; - -void guncmdr_idlesound(edict_t *self) -{ - gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); -} - -MONSTERINFO_SIGHT(guncmdr_sight) (edict_t *self, edict_t *other) -> void -{ - gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); -} - -MONSTERINFO_SEARCH(guncmdr_search) (edict_t *self) -> void -{ - gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); -} - -void GunnerGrenade(edict_t *self); -void GunnerFire(edict_t *self); -void guncmdr_fire_chain(edict_t *self); -void guncmdr_refire_chain(edict_t *self); - -void guncmdr_stand(edict_t *self); - -mframe_t guncmdr_frames_fidget[] = { - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand, 0, guncmdr_idlesound }, - { ai_stand }, - { ai_stand }, - - { ai_stand }, - { ai_stand, 0, guncmdr_idlesound }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand } -}; -MMOVE_T(guncmdr_move_fidget) = { FRAME_c_stand201, FRAME_c_stand254, guncmdr_frames_fidget, guncmdr_stand }; - -void guncmdr_fidget(edict_t *self) -{ - if (self->monsterinfo.aiflags & AI_STAND_GROUND) - return; - else if (self->enemy) - return; - if (frandom() <= 0.05f) - M_SetAnimation(self, &guncmdr_move_fidget); -} - -mframe_t guncmdr_frames_stand[] = { - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand, 0, guncmdr_fidget }, - - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand, 0, guncmdr_fidget }, - - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand, 0, guncmdr_fidget }, - - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand, 0, guncmdr_fidget } -}; -MMOVE_T(guncmdr_move_stand) = { FRAME_c_stand101, FRAME_c_stand140, guncmdr_frames_stand, nullptr }; - -MONSTERINFO_STAND(guncmdr_stand) (edict_t *self) -> void -{ - M_SetAnimation(self, &guncmdr_move_stand); -} - -mframe_t guncmdr_frames_walk[] = { - { ai_walk, 1.5f, monster_footstep }, - { ai_walk, 2.5f }, - { ai_walk, 3.0f }, - { ai_walk, 2.5f }, - { ai_walk, 2.3f }, - { ai_walk, 3.0f }, - { ai_walk, 2.8f, monster_footstep }, - { ai_walk, 3.6f }, - { ai_walk, 2.8f }, - { ai_walk, 2.5f }, - - { ai_walk, 2.3f }, - { ai_walk, 4.3f }, - { ai_walk, 3.0f, monster_footstep }, - { ai_walk, 1.5f }, - { ai_walk, 2.5f }, - { ai_walk, 3.3f }, - { ai_walk, 2.8f }, - { ai_walk, 3.0f }, - { ai_walk, 2.0f, monster_footstep }, - { ai_walk, 2.0f }, - - { ai_walk, 3.3f }, - { ai_walk, 3.6f }, - { ai_walk, 3.4f }, - { ai_walk, 2.8f }, -}; -MMOVE_T(guncmdr_move_walk) = { FRAME_c_walk101, FRAME_c_walk124, guncmdr_frames_walk, nullptr }; - -MONSTERINFO_WALK(guncmdr_walk) (edict_t *self) -> void -{ - M_SetAnimation(self, &guncmdr_move_walk); -} - -mframe_t guncmdr_frames_run[] = { - { ai_run, 15.f, monster_done_dodge }, - { ai_run, 16.f, monster_footstep }, - { ai_run, 20.f }, - { ai_run, 18.f }, - { ai_run, 24.f, monster_footstep }, - { ai_run, 13.5f } -}; - -MMOVE_T(guncmdr_move_run) = { FRAME_c_run101, FRAME_c_run106, guncmdr_frames_run, nullptr }; - -MONSTERINFO_RUN(guncmdr_run) (edict_t *self) -> void -{ - monster_done_dodge(self); - if (self->monsterinfo.aiflags & AI_STAND_GROUND) - M_SetAnimation(self, &guncmdr_move_stand); - else - M_SetAnimation(self, &guncmdr_move_run); -} - -// standing pains - -mframe_t guncmdr_frames_pain1[] = { - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, -}; -MMOVE_T(guncmdr_move_pain1) = { FRAME_c_pain101, FRAME_c_pain104, guncmdr_frames_pain1, guncmdr_run }; - -mframe_t guncmdr_frames_pain2[] = { - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move } -}; -MMOVE_T(guncmdr_move_pain2) = { FRAME_c_pain201, FRAME_c_pain204, guncmdr_frames_pain2, guncmdr_run }; - -mframe_t guncmdr_frames_pain3[] = { - { ai_move, -3.0f }, - { ai_move }, - { ai_move }, - { ai_move }, -}; -MMOVE_T(guncmdr_move_pain3) = { FRAME_c_pain301, FRAME_c_pain304, guncmdr_frames_pain3, guncmdr_run }; - -mframe_t guncmdr_frames_pain4[] = { - { ai_move, -17.1f }, - { ai_move, -3.2f }, - { ai_move, 0.9f }, - { ai_move, 3.6f }, - { ai_move, -2.6f }, - { ai_move, 1.0f }, - { ai_move, -5.1f }, - { ai_move, -6.7f }, - { ai_move, -8.8f }, - { ai_move }, - - { ai_move }, - { ai_move, -2.1f }, - { ai_move, -2.3f }, - { ai_move, -2.5f }, - { ai_move } -}; -MMOVE_T(guncmdr_move_pain4) = { FRAME_c_pain401, FRAME_c_pain415, guncmdr_frames_pain4, guncmdr_run }; - -void guncmdr_dead(edict_t *); - -mframe_t guncmdr_frames_death1[] = { - { ai_move }, - { ai_move }, - { ai_move, 4.0f }, // scoot - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move } -}; -MMOVE_T(guncmdr_move_death1) = { FRAME_c_death101, FRAME_c_death118, guncmdr_frames_death1, guncmdr_dead }; - -void guncmdr_pain5_to_death1(edict_t *self) -{ - if (self->health <= 0) - M_SetAnimation(self, &guncmdr_move_death1, false); -} - -mframe_t guncmdr_frames_death2[] = { - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move } -}; -MMOVE_T(guncmdr_move_death2) = { FRAME_c_death201, FRAME_c_death204, guncmdr_frames_death2, guncmdr_dead }; - -void guncmdr_pain5_to_death2(edict_t *self) -{ - if (self->health <= 0 && brandom()) - M_SetAnimation(self, &guncmdr_move_death2, false); -} - -mframe_t guncmdr_frames_pain5[] = { - { ai_move, -29.f }, - { ai_move, -5.f }, - { ai_move, -5.f }, - { ai_move, -3.f }, - { ai_move }, - { ai_move, 0, guncmdr_pain5_to_death2 }, - { ai_move, 9.f }, - { ai_move, 3.f }, - { ai_move, 0, guncmdr_pain5_to_death1 }, - { ai_move }, - - { ai_move }, - { ai_move, -4.6f }, - { ai_move, -4.8f }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move, 9.5f }, - { ai_move, 3.4f }, - { ai_move }, - { ai_move }, - - { ai_move, -2.4f }, - { ai_move, -9.0f }, - { ai_move, -5.0f }, - { ai_move, -3.6f }, -}; -MMOVE_T(guncmdr_move_pain5) = { FRAME_c_pain501, FRAME_c_pain524, guncmdr_frames_pain5, guncmdr_run }; - -void guncmdr_dead(edict_t *self) -{ - self->mins = vec3_t { -16, -16, -24 } * self->s.scale; - self->maxs = vec3_t { 16, 16, -8 } * self->s.scale; - monster_dead(self); -} - -static void guncmdr_shrink(edict_t *self) -{ - self->maxs[2] = -4 * self->s.scale; - self->svflags |= SVF_DEADMONSTER; - gi.linkentity(self); -} - -mframe_t guncmdr_frames_death6[] = { - { ai_move, 0, guncmdr_shrink }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move } -}; -MMOVE_T(guncmdr_move_death6) = { FRAME_c_death601, FRAME_c_death614, guncmdr_frames_death6, guncmdr_dead }; - -static void guncmdr_pain6_to_death6(edict_t *self) -{ - if (self->health <= 0) - M_SetAnimation(self, &guncmdr_move_death6, false); -} - -mframe_t guncmdr_frames_pain6[] = { - { ai_move, 16.f }, - { ai_move, 16.f }, - { ai_move, 12.f }, - { ai_move, 5.5f, monster_duck_down }, - { ai_move, 3.0f }, - { ai_move, -4.7f }, - { ai_move, -6.0f, guncmdr_pain6_to_death6 }, - { ai_move }, - { ai_move, 1.8f }, - { ai_move, 0.7f }, - - { ai_move }, - { ai_move, -2.1f }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - - { ai_move }, - { ai_move, -6.1f }, - { ai_move, 10.5f }, - { ai_move, 4.3f }, - { ai_move, 4.7f, monster_duck_up }, - { ai_move, 1.4f }, - { ai_move }, - { ai_move, -3.2f }, - { ai_move, 2.3f }, - { ai_move, -4.4f }, - - { ai_move, -4.4f }, - { ai_move, -2.4f } -}; -MMOVE_T(guncmdr_move_pain6) = { FRAME_c_pain601, FRAME_c_pain632, guncmdr_frames_pain6, guncmdr_run }; - -mframe_t guncmdr_frames_pain7[] = { - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move } -}; -MMOVE_T(guncmdr_move_pain7) = { FRAME_c_pain701, FRAME_c_pain714, guncmdr_frames_pain7, guncmdr_run }; - -extern const mmove_t guncmdr_move_jump; -extern const mmove_t guncmdr_move_jump2; -extern const mmove_t guncmdr_move_duck_attack; - -bool guncmdr_sidestep(edict_t *self); - -PAIN(guncmdr_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void -{ - monster_done_dodge(self); - - if (self->monsterinfo.active_move == &guncmdr_move_jump || - self->monsterinfo.active_move == &guncmdr_move_jump2 || - self->monsterinfo.active_move == &guncmdr_move_duck_attack) - return; - - if (level.time < self->pain_debounce_time) - { - if (frandom() < 0.3) - self->monsterinfo.dodge(self, other, FRAME_TIME_S, nullptr, false); - - return; - } - - self->pain_debounce_time = level.time + 3_sec; - - if (brandom()) - gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); - else - gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); - - if (!M_ShouldReactToPain(self, mod)) - { - if (frandom() < 0.3) - self->monsterinfo.dodge(self, other, FRAME_TIME_S, nullptr, false); - - return; // no pain anims in nightmare - } - - vec3_t forward; - AngleVectors(self->s.angles, forward, nullptr, nullptr); - - vec3_t dif = (other->s.origin - self->s.origin); - dif.z = 0; - dif.normalize(); - - // small pain - if (damage < 35) - { - int r = irandom(0, 4); - - if (r == 0) - M_SetAnimation(self, &guncmdr_move_pain3); - else if (r == 1) - M_SetAnimation(self, &guncmdr_move_pain2); - else if (r == 2) - M_SetAnimation(self, &guncmdr_move_pain1); - else - M_SetAnimation(self, &guncmdr_move_pain7); - } - // large pain from behind (aka Paril) - else if (dif.dot(forward) < -0.40f) - { - M_SetAnimation(self, &guncmdr_move_pain6); - - self->pain_debounce_time += 1.5_sec; - } - else - { - if (brandom()) - M_SetAnimation(self, &guncmdr_move_pain4); - else - M_SetAnimation(self, &guncmdr_move_pain5); - - self->pain_debounce_time += 1.5_sec; - } - - self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; - - // PMM - clear duck flag - if (self->monsterinfo.aiflags & AI_DUCKED) - monster_duck_up(self); -} - -MONSTERINFO_SETSKIN(guncmdr_setskin) (edict_t *self) -> void -{ - if (self->health < (self->max_health / 2)) - self->s.skinnum |= 1; - else - self->s.skinnum &= ~1; -} - -mframe_t guncmdr_frames_death3[] = { - { ai_move, 20.f }, - { ai_move, 10.f }, - { ai_move, 10.f, [](edict_t *self) { monster_footstep(self); guncmdr_shrink(self); } }, - { ai_move, 0.f, monster_footstep }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - - { ai_move } -}; -MMOVE_T(guncmdr_move_death3) = { FRAME_c_death301, FRAME_c_death321, guncmdr_frames_death3, guncmdr_dead }; - -mframe_t guncmdr_frames_death7[] = { - { ai_move, 30.f }, - { ai_move, 20.f }, - { ai_move, 16.f, [](edict_t *self) { monster_footstep(self); guncmdr_shrink(self); } }, - { ai_move, 5.f, monster_footstep }, - { ai_move, -6.f }, - { ai_move, -7.f, monster_footstep }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move, 0.f, monster_footstep }, - { ai_move }, - { ai_move }, - { ai_move, 0.f, monster_footstep }, - { ai_move }, - { ai_move }, -}; -MMOVE_T(guncmdr_move_death7) = { FRAME_c_death701, FRAME_c_death730, guncmdr_frames_death7, guncmdr_dead }; - -mframe_t guncmdr_frames_death4[] = { - { ai_move, -20.f }, - { ai_move, -16.f }, - { ai_move, -26.f, [](edict_t *self) { monster_footstep(self); guncmdr_shrink(self); } }, - { ai_move, 0.f, monster_footstep }, - { ai_move, -12.f }, - { ai_move, 16.f }, - { ai_move, 9.2f }, - { ai_move }, - { ai_move }, - { ai_move }, - - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move } -}; -MMOVE_T(guncmdr_move_death4) = { FRAME_c_death401, FRAME_c_death436, guncmdr_frames_death4, guncmdr_dead }; - -mframe_t guncmdr_frames_death5[] = { - { ai_move, -14.f }, - { ai_move, -2.7f }, - { ai_move, -2.5f }, - { ai_move, -4.6f, monster_footstep }, - { ai_move, -4.0f, monster_footstep }, - { ai_move, -1.5f }, - { ai_move, 2.3f }, - { ai_move, 2.5f }, - { ai_move }, - { ai_move }, - - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move, 3.5f }, - { ai_move, 12.9f, monster_footstep }, - { ai_move, 3.8f }, - { ai_move }, - { ai_move }, - { ai_move }, - - { ai_move, -2.1f }, - { ai_move, -1.3f }, - { ai_move }, - { ai_move }, - { ai_move, 3.4f }, - { ai_move, 5.7f }, - { ai_move, 11.2f }, - { ai_move, 0, monster_footstep } -}; -MMOVE_T(guncmdr_move_death5) = { FRAME_c_death501, FRAME_c_death528, guncmdr_frames_death5, guncmdr_dead }; - -DIE(guncmdr_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void -{ - // check for gib - if (M_CheckGib(self, mod)) - { - gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); - - const char *head_gib = (self->monsterinfo.active_move != &guncmdr_move_death5) ? "models/objects/gibs/sm_meat/tris.md2" : "models/monsters/gunner/gibs/head.md2"; - - self->s.skinnum /= 2; - - ThrowGibs(self, damage, { - { 2, "models/objects/gibs/bone/tris.md2" }, - { 2, "models/objects/gibs/sm_meat/tris.md2" }, - { 1, "models/objects/gibs/gear/tris.md2" }, - { "models/monsters/gunner/gibs/chest.md2", GIB_SKINNED }, - { "models/monsters/gunner/gibs/garm.md2", GIB_SKINNED | GIB_UPRIGHT }, - { "models/monsters/gunner/gibs/gun.md2", GIB_SKINNED | GIB_UPRIGHT }, - { "models/monsters/gunner/gibs/foot.md2", GIB_SKINNED }, - { head_gib, GIB_SKINNED | GIB_HEAD } - }); - self->deadflag = true; - return; - } - - if (self->deadflag) - return; - - // regular death - gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); - self->deadflag = true; - self->takedamage = true; - - // these animations cleanly transitions to death, so just keep going - if (self->monsterinfo.active_move == &guncmdr_move_pain5 && - self->s.frame < FRAME_c_pain508) - return; - else if (self->monsterinfo.active_move == &guncmdr_move_pain6 && - self->s.frame < FRAME_c_pain607) - return; - - vec3_t forward; - AngleVectors(self->s.angles, forward, nullptr, nullptr); - - vec3_t dif = (inflictor->s.origin - self->s.origin); - dif.z = 0; - dif.normalize(); - - // off with da head - if (fabsf((self->s.origin[2] + self->viewheight) - point[2]) <= 4 && - self->velocity.z < 65.f) - { - M_SetAnimation(self, &guncmdr_move_death5); - - edict_t *head = ThrowGib(self, "models/monsters/gunner/gibs/head.md2", damage, GIB_NONE, self->s.scale); - - if (head) - { - head->s.angles = self->s.angles; - head->s.origin = self->s.origin + vec3_t{0, 0, 24.f}; - vec3_t headDir = (self->s.origin - inflictor->s.origin); - head->velocity = headDir / headDir.length() * 100.0f; - head->velocity[2] = 200.0f; - head->avelocity *= 0.15f; - gi.linkentity(head); - } - } - // damage came from behind; use backwards death - else if (dif.dot(forward) < -0.40f) - { - int r = irandom(0, self->monsterinfo.active_move == &guncmdr_move_pain6 ? 2 : 3); - - if (r == 0) - M_SetAnimation(self, &guncmdr_move_death3); - else if (r == 1) - M_SetAnimation(self, &guncmdr_move_death7); - else if (r == 2) - M_SetAnimation(self, &guncmdr_move_pain6); - } - else - { - int r = irandom(0, self->monsterinfo.active_move == &guncmdr_move_pain5 ? 1 : 2); - - if (r == 0) - M_SetAnimation(self, &guncmdr_move_death4); - else - M_SetAnimation(self, &guncmdr_move_pain5); - } -} - -void guncmdr_opengun(edict_t *self) -{ - gi.sound(self, CHAN_VOICE, sound_open, 1, ATTN_IDLE, 0); -} - -void GunnerCmdrFire(edict_t *self) -{ - vec3_t start; - vec3_t forward, right; - vec3_t aim; - monster_muzzleflash_id_t flash_number; - - if (!self->enemy || !self->enemy->inuse) // PGM - return; // PGM - - if (self->s.frame >= FRAME_c_attack401 && self->s.frame <= FRAME_c_attack505) - flash_number = MZ2_GUNCMDR_CHAINGUN_2; - else - flash_number = MZ2_GUNCMDR_CHAINGUN_1; - - AngleVectors(self->s.angles, forward, right, nullptr); - start = M_ProjectFlashSource(self, monster_flash_offset[flash_number], forward, right); - PredictAim(self, self->enemy, start, 800, false, frandom() * 0.3f, &aim, nullptr); - for (int i = 0; i < 3; i++) - aim[i] += crandom_open() * 0.025f; - monster_fire_flechette(self, start, aim, 4, 800, flash_number); -} - -mframe_t guncmdr_frames_attack_chain[] = { - { ai_charge }, - { ai_charge }, - { ai_charge }, - { ai_charge }, - { ai_charge, 0, guncmdr_opengun }, - { ai_charge } -}; -MMOVE_T(guncmdr_move_attack_chain) = { FRAME_c_attack101, FRAME_c_attack106, guncmdr_frames_attack_chain, guncmdr_fire_chain }; - -mframe_t guncmdr_frames_fire_chain[] = { - { ai_charge, 0, GunnerCmdrFire }, - { ai_charge, 0, GunnerCmdrFire }, - { ai_charge, 0, GunnerCmdrFire }, - { ai_charge, 0, GunnerCmdrFire }, - { ai_charge, 0, GunnerCmdrFire }, - { ai_charge, 0, GunnerCmdrFire } -}; -MMOVE_T(guncmdr_move_fire_chain) = { FRAME_c_attack107, FRAME_c_attack112, guncmdr_frames_fire_chain, guncmdr_refire_chain }; - -mframe_t guncmdr_frames_fire_chain_run[] = { - { ai_charge, 15.f, GunnerCmdrFire }, - { ai_charge, 16.f, GunnerCmdrFire }, - { ai_charge, 20.f, GunnerCmdrFire }, - { ai_charge, 18.f, GunnerCmdrFire }, - { ai_charge, 24.f, GunnerCmdrFire }, - { ai_charge, 13.5f, GunnerCmdrFire } -}; -MMOVE_T(guncmdr_move_fire_chain_run) = { FRAME_c_run201, FRAME_c_run206, guncmdr_frames_fire_chain_run, guncmdr_refire_chain }; - -mframe_t guncmdr_frames_fire_chain_dodge_right[] = { - { ai_charge, 5.1f * 2.0f, GunnerCmdrFire }, - { ai_charge, 9.0f * 2.0f, GunnerCmdrFire }, - { ai_charge, 3.5f * 2.0f, GunnerCmdrFire }, - { ai_charge, 3.6f * 2.0f, GunnerCmdrFire }, - { ai_charge, -1.0f * 2.0f, GunnerCmdrFire } -}; -MMOVE_T(guncmdr_move_fire_chain_dodge_right) = { FRAME_c_attack401, FRAME_c_attack405, guncmdr_frames_fire_chain_dodge_right, guncmdr_refire_chain }; - -mframe_t guncmdr_frames_fire_chain_dodge_left[] = { - { ai_charge, 5.1f * 2.0f, GunnerCmdrFire }, - { ai_charge, 9.0f * 2.0f, GunnerCmdrFire }, - { ai_charge, 3.5f * 2.0f, GunnerCmdrFire }, - { ai_charge, 3.6f * 2.0f, GunnerCmdrFire }, - { ai_charge, -1.0f * 2.0f, GunnerCmdrFire } -}; -MMOVE_T(guncmdr_move_fire_chain_dodge_left) = { FRAME_c_attack501, FRAME_c_attack505, guncmdr_frames_fire_chain_dodge_left, guncmdr_refire_chain }; - -mframe_t guncmdr_frames_endfire_chain[] = { - { ai_charge }, - { ai_charge }, - { ai_charge, 0, guncmdr_opengun }, - { ai_charge }, - { ai_charge }, - { ai_charge }, - { ai_charge } -}; -MMOVE_T(guncmdr_move_endfire_chain) = { FRAME_c_attack118, FRAME_c_attack124, guncmdr_frames_endfire_chain, guncmdr_run }; - -constexpr float MORTAR_SPEED = 850.f; -constexpr float GRENADE_SPEED = 600.f; - -void GunnerCmdrGrenade(edict_t *self) -{ - vec3_t start; - vec3_t forward, right, up; - vec3_t aim; - monster_muzzleflash_id_t flash_number; - float spread; - float pitch = 0; - // PMM - vec3_t target; - bool blindfire = false; - - if (!self->enemy || !self->enemy->inuse) // PGM - return; // PGM - - // pmm - if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) - blindfire = true; - - if (self->s.frame == FRAME_c_attack205) - { - spread = -0.1f; - flash_number = MZ2_GUNCMDR_GRENADE_MORTAR_1; - } - else if (self->s.frame == FRAME_c_attack208) - { - spread = 0.f; - flash_number = MZ2_GUNCMDR_GRENADE_MORTAR_2; - } - else if (self->s.frame == FRAME_c_attack211) - { - spread = 0.1f; - flash_number = MZ2_GUNCMDR_GRENADE_MORTAR_3; - } - else if (self->s.frame == FRAME_c_attack304) - { - spread = -0.1f; - flash_number = MZ2_GUNCMDR_GRENADE_FRONT_1; - } - else if (self->s.frame == FRAME_c_attack307) - { - spread = 0.f; - flash_number = MZ2_GUNCMDR_GRENADE_FRONT_2; - } - else if (self->s.frame == FRAME_c_attack310) - { - spread = 0.1f; - flash_number = MZ2_GUNCMDR_GRENADE_FRONT_3; - } - else if (self->s.frame == FRAME_c_attack911) - { - spread = 0.25f; - flash_number = MZ2_GUNCMDR_GRENADE_CROUCH_1; - } - else if (self->s.frame == FRAME_c_attack912) - { - spread = 0.f; - flash_number = MZ2_GUNCMDR_GRENADE_CROUCH_2; - } - else if (self->s.frame == FRAME_c_attack913) - { - spread = -0.25f; - flash_number = MZ2_GUNCMDR_GRENADE_CROUCH_3; - } - - // pmm - // if we're shooting blind and we still can't see our enemy - if ((blindfire) && (!visible(self, self->enemy))) - { - // and we have a valid blind_fire_target - if (!self->monsterinfo.blind_fire_target) - return; - - target = self->monsterinfo.blind_fire_target; - } - else - target = self->enemy->s.origin; - // pmm - - AngleVectors(self->s.angles, forward, right, up); // PGM - start = M_ProjectFlashSource(self, monster_flash_offset[flash_number], forward, right); - - // PGM - if (self->enemy && !(flash_number >= MZ2_GUNCMDR_GRENADE_CROUCH_1 && flash_number <= MZ2_GUNCMDR_GRENADE_CROUCH_3)) - { - float dist; - - aim = target - self->s.origin; - dist = aim.length(); - - // aim up if they're on the same level as me and far away. - if ((dist > 512) && (aim[2] < 64) && (aim[2] > -64)) - { - aim[2] += (dist - 512); - } - - aim.normalize(); - pitch = aim[2]; - if (pitch > 0.4f) - pitch = 0.4f; - else if (pitch < -0.5f) - pitch = -0.5f; - - if ((self->enemy->absmin.z - self->absmax.z) > 16.f && flash_number >= MZ2_GUNCMDR_GRENADE_MORTAR_1 && flash_number <= MZ2_GUNCMDR_GRENADE_MORTAR_3) - pitch += 0.5f; - } - // PGM - - if (flash_number >= MZ2_GUNCMDR_GRENADE_FRONT_1 && flash_number <= MZ2_GUNCMDR_GRENADE_FRONT_3) - pitch -= 0.05f; - - if (!(flash_number >= MZ2_GUNCMDR_GRENADE_CROUCH_1 && flash_number <= MZ2_GUNCMDR_GRENADE_CROUCH_3)) - { - aim = forward + (right * spread); - aim += (up * pitch); - aim.normalize(); - } - else - { - PredictAim(self, self->enemy, start, 800, false, 0.f, &aim, nullptr); - aim += right * spread; - aim.normalize(); - } - - if (flash_number >= MZ2_GUNCMDR_GRENADE_CROUCH_1 && flash_number <= MZ2_GUNCMDR_GRENADE_CROUCH_3) - { - constexpr float inner_spread = 0.125f; - - for (int32_t i = 0; i < 3; i++) - fire_ionripper(self, start, aim + (right * (-(inner_spread * 2) + (inner_spread * (i + 1)))), 15, 800, EF_IONRIPPER); - - monster_muzzleflash(self, start, flash_number); - } - else - { - // mortar fires farther - float speed; - - if (flash_number >= MZ2_GUNCMDR_GRENADE_MORTAR_1 && flash_number <= MZ2_GUNCMDR_GRENADE_MORTAR_3) - speed = MORTAR_SPEED; - else - speed = GRENADE_SPEED; - - // try search for best pitch - if (M_CalculatePitchToFire(self, target, start, aim, speed, 2.5f, (flash_number >= MZ2_GUNCMDR_GRENADE_MORTAR_1 && flash_number <= MZ2_GUNCMDR_GRENADE_MORTAR_3))) - monster_fire_grenade(self, start, aim, 50, speed, flash_number, (crandom_open() * 10.0f), frandom() * 10.f); - else - // normal shot - monster_fire_grenade(self, start, aim, 50, speed, flash_number, (crandom_open() * 10.0f), 200.f + (crandom_open() * 10.0f)); - } -} - -mframe_t guncmdr_frames_attack_mortar[] = { - { ai_charge }, - { ai_charge }, - { ai_charge }, - { ai_charge }, - { ai_charge, 0, GunnerCmdrGrenade }, - { ai_charge }, - { ai_charge }, - { ai_charge, 0, GunnerCmdrGrenade }, - { ai_charge }, - { ai_charge }, - - { ai_charge, 0, GunnerCmdrGrenade }, - { ai_charge }, - { ai_charge }, - { ai_charge, 0, monster_duck_up }, - { ai_charge }, - { ai_charge }, - { ai_charge }, - { ai_charge }, - { ai_charge }, - { ai_charge }, - { ai_charge } -}; -MMOVE_T(guncmdr_move_attack_mortar) = { FRAME_c_attack201, FRAME_c_attack221, guncmdr_frames_attack_mortar, guncmdr_run }; - -void guncmdr_grenade_mortar_resume(edict_t *self) -{ - M_SetAnimation(self, &guncmdr_move_attack_mortar); - self->monsterinfo.attack_state = AS_STRAIGHT; - self->s.frame = self->count; -} - -mframe_t guncmdr_frames_attack_mortar_dodge[] = { - { ai_charge, 11.f }, - { ai_charge, 12.f }, - { ai_charge, 16.f }, - { ai_charge, 16.f }, - { ai_charge, 12.f }, - { ai_charge, 11.f } -}; -MMOVE_T(guncmdr_move_attack_mortar_dodge) = { FRAME_c_duckstep01, FRAME_c_duckstep06, guncmdr_frames_attack_mortar_dodge, guncmdr_grenade_mortar_resume }; - -mframe_t guncmdr_frames_attack_back[] = { - //{ ai_charge }, - { ai_charge, -2.f }, - { ai_charge, -1.5f }, - { ai_charge, -0.5f, GunnerCmdrGrenade }, - { ai_charge, -6.0f }, - { ai_charge, -4.f }, - { ai_charge, -2.5f, GunnerCmdrGrenade }, - { ai_charge, -7.0f }, - { ai_charge, -3.5f }, - { ai_charge, -1.1f, GunnerCmdrGrenade }, - - { ai_charge, -4.6f }, - { ai_charge, 1.9f }, - { ai_charge, 1.0f }, - { ai_charge, -4.5f }, - { ai_charge, 3.2f }, - { ai_charge, 4.4f }, - { ai_charge, -6.5f }, - { ai_charge, -6.1f }, - { ai_charge, 3.0f }, - { ai_charge, -0.7f }, - { ai_charge, -1.0f } -}; -MMOVE_T(guncmdr_move_attack_grenade_back) = { FRAME_c_attack302, FRAME_c_attack321, guncmdr_frames_attack_back, guncmdr_run }; - -void guncmdr_grenade_back_dodge_resume(edict_t *self) -{ - M_SetAnimation(self, &guncmdr_move_attack_grenade_back); - self->monsterinfo.attack_state = AS_STRAIGHT; - self->s.frame = self->count; -} - -mframe_t guncmdr_frames_attack_grenade_back_dodge_right[] = { - { ai_charge, 5.1f * 2.0f }, - { ai_charge, 9.0f * 2.0f }, - { ai_charge, 3.5f * 2.0f }, - { ai_charge, 3.6f * 2.0f }, - { ai_charge, -1.0f * 2.0f } -}; -MMOVE_T(guncmdr_move_attack_grenade_back_dodge_right) = { FRAME_c_attack601, FRAME_c_attack605, guncmdr_frames_attack_grenade_back_dodge_right, guncmdr_grenade_back_dodge_resume }; - -mframe_t guncmdr_frames_attack_grenade_back_dodge_left[] = { - { ai_charge, 5.1f * 2.0f }, - { ai_charge, 9.0f * 2.0f }, - { ai_charge, 3.5f * 2.0f }, - { ai_charge, 3.6f * 2.0f }, - { ai_charge, -1.0f * 2.0f } -}; -MMOVE_T(guncmdr_move_attack_grenade_back_dodge_left) = { FRAME_c_attack701, FRAME_c_attack705, guncmdr_frames_attack_grenade_back_dodge_left, guncmdr_grenade_back_dodge_resume }; - -static void guncmdr_kick_finished(edict_t *self) -{ - self->monsterinfo.melee_debounce_time = level.time + 3_sec; - self->monsterinfo.attack(self); -} - -static void guncmdr_kick(edict_t *self) -{ - if (fire_hit(self, vec3_t { MELEE_DISTANCE, 0.f, -32.f }, 15.f, 400.f)) - { - if (self->enemy && self->enemy->client && self->enemy->velocity.z < 270.f) - self->enemy->velocity.z = 270.f; - } -} - -mframe_t guncmdr_frames_attack_kick[] = { - { ai_charge, -7.7f }, - { ai_charge, -4.9f }, - { ai_charge, 12.6f, guncmdr_kick }, - { ai_charge }, - { ai_charge, -3.0f }, - { ai_charge }, - { ai_charge, -4.1f }, - { ai_charge, 8.6f }, - //{ ai_charge, -3.5f } -}; -MMOVE_T(guncmdr_move_attack_kick) = { FRAME_c_attack801, FRAME_c_attack808, guncmdr_frames_attack_kick, guncmdr_kick_finished }; - -// don't ever try grenades if we get this close -constexpr float RANGE_GRENADE = 100.f; - -// always use mortar at this range -constexpr float RANGE_GRENADE_MORTAR = 525.f; - -// at this range, run towards the enemy -constexpr float RANGE_CHAINGUN_RUN = 400.f; - -MONSTERINFO_ATTACK(guncmdr_attack) (edict_t *self) -> void -{ - monster_done_dodge(self); - - float d = range_to(self, self->enemy); - - vec3_t forward, right, aim; - AngleVectors(self->s.angles, forward, right, nullptr); // PGM - - // always use chaingun on tesla - // kick close enemies - if (!self->bad_area && d < RANGE_MELEE && self->monsterinfo.melee_debounce_time < level.time) - M_SetAnimation(self, &guncmdr_move_attack_kick); - else if (self->bad_area || ((d <= RANGE_GRENADE || brandom()) && M_CheckClearShot(self, monster_flash_offset[MZ2_GUNCMDR_CHAINGUN_1]))) - M_SetAnimation(self, &guncmdr_move_attack_chain); - else if ((d >= RANGE_GRENADE_MORTAR || - fabs(self->absmin.z - self->enemy->absmax.z) > 64.f // enemy is far below or above us, always try mortar - ) && M_CheckClearShot(self, monster_flash_offset[MZ2_GUNCMDR_GRENADE_MORTAR_1]) && - M_CalculatePitchToFire(self, self->enemy->s.origin, M_ProjectFlashSource(self, monster_flash_offset[MZ2_GUNCMDR_GRENADE_MORTAR_1], forward, right), - aim = (self->enemy->s.origin - self->s.origin).normalized(), MORTAR_SPEED, 2.5f, true) - ) - { - M_SetAnimation(self, &guncmdr_move_attack_mortar); - monster_duck_down(self); - } - else if (M_CheckClearShot(self, monster_flash_offset[MZ2_GUNCMDR_GRENADE_FRONT_1]) && !(self->monsterinfo.aiflags & AI_STAND_GROUND) && - M_CalculatePitchToFire(self, self->enemy->s.origin, M_ProjectFlashSource(self, monster_flash_offset[MZ2_GUNCMDR_GRENADE_FRONT_1], forward, right), - aim = (self->enemy->s.origin - self->s.origin).normalized(), GRENADE_SPEED, 2.5f, false)) - M_SetAnimation(self, &guncmdr_move_attack_grenade_back); - else if (self->monsterinfo.aiflags & AI_STAND_GROUND) - M_SetAnimation(self, &guncmdr_move_attack_chain); -} - -void guncmdr_fire_chain(edict_t *self) -{ - if (!(self->monsterinfo.aiflags & AI_STAND_GROUND) && self->enemy && range_to(self, self->enemy) > RANGE_CHAINGUN_RUN && ai_check_move(self, 8.0f)) - M_SetAnimation(self, &guncmdr_move_fire_chain_run); - else - M_SetAnimation(self, &guncmdr_move_fire_chain); -} - -void guncmdr_refire_chain(edict_t *self) -{ - monster_done_dodge(self); - self->monsterinfo.attack_state = AS_STRAIGHT; - - if (self->enemy->health > 0) - if (visible(self, self->enemy)) - if (frandom() <= 0.5f) - { - if (!(self->monsterinfo.aiflags & AI_STAND_GROUND) && self->enemy && range_to(self, self->enemy) > RANGE_CHAINGUN_RUN && ai_check_move(self, 8.0f)) - M_SetAnimation(self, &guncmdr_move_fire_chain_run, false); - else - M_SetAnimation(self, &guncmdr_move_fire_chain, false); - return; - } - M_SetAnimation(self, &guncmdr_move_endfire_chain, false); -} - -//=========== -// PGM -void guncmdr_jump_now(edict_t *self) -{ - vec3_t forward, up; - - AngleVectors(self->s.angles, forward, nullptr, up); - self->velocity += (forward * 100); - self->velocity += (up * 300); -} - -void guncmdr_jump2_now(edict_t *self) -{ - vec3_t forward, up; - - AngleVectors(self->s.angles, forward, nullptr, up); - self->velocity += (forward * 150); - self->velocity += (up * 400); -} - -void guncmdr_jump_wait_land(edict_t *self) -{ - if (self->groundentity == nullptr) - { - self->monsterinfo.nextframe = self->s.frame; - - if (monster_jump_finished(self)) - self->monsterinfo.nextframe = self->s.frame + 1; - } - else - self->monsterinfo.nextframe = self->s.frame + 1; -} - -mframe_t guncmdr_frames_jump[] = { - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move, 0, guncmdr_jump_now }, - { ai_move }, - { ai_move }, - { ai_move, 0, guncmdr_jump_wait_land }, - { ai_move }, - { ai_move }, - { ai_move } -}; -MMOVE_T(guncmdr_move_jump) = { FRAME_c_jump01, FRAME_c_jump10, guncmdr_frames_jump, guncmdr_run }; - -mframe_t guncmdr_frames_jump2[] = { - { ai_move, -8 }, - { ai_move, -4 }, - { ai_move, -4 }, - { ai_move, 0, guncmdr_jump2_now }, - { ai_move }, - { ai_move }, - { ai_move, 0, guncmdr_jump_wait_land }, - { ai_move }, - { ai_move }, - { ai_move } -}; -MMOVE_T(guncmdr_move_jump2) = { FRAME_c_jump01, FRAME_c_jump10, guncmdr_frames_jump2, guncmdr_run }; - -void guncmdr_jump(edict_t *self, blocked_jump_result_t result) -{ - if (!self->enemy) - return; - - monster_done_dodge(self); - - if (result == blocked_jump_result_t::JUMP_JUMP_UP) - M_SetAnimation(self, &guncmdr_move_jump2); - else - M_SetAnimation(self, &guncmdr_move_jump); -} - -void T_SlamRadiusDamage(vec3_t point, edict_t *inflictor, edict_t *attacker, float damage, float kick, edict_t *ignore, float radius, mod_t mod); - -static void GunnerCmdrCounter(edict_t *self) -{ - gi.WriteByte(svc_temp_entity); - gi.WriteByte(TE_BERSERK_SLAM); - vec3_t f, r, start; - AngleVectors(self->s.angles, f, r, nullptr); - start = M_ProjectFlashSource(self, { 20.f, 0.f, 14.f }, f, r); - trace_t tr = gi.traceline(self->s.origin, start, self, MASK_SOLID); - gi.WritePosition(tr.endpos); - gi.WriteDir(f); - gi.multicast(tr.endpos, MULTICAST_PHS, false); - - T_SlamRadiusDamage(tr.endpos, self, self, 15, 250.f, self, 200.f, MOD_UNKNOWN); -} - -//=========== -// PGM -mframe_t guncmdr_frames_duck_attack[] = { - { ai_move, 3.6f }, - { ai_move, 5.6f, monster_duck_down }, - { ai_move, 8.4f }, - { ai_move, 2.0f, monster_duck_hold }, - { ai_charge }, - { ai_charge }, - { ai_charge }, - { ai_charge }, - { ai_charge }, - { ai_charge }, - - //{ ai_charge, 0, GunnerCmdrGrenade }, - //{ ai_charge, 9.5f, GunnerCmdrGrenade }, - //{ ai_charge, -1.5f, GunnerCmdrGrenade }, - - { ai_charge, 0 }, - { ai_charge, 9.5f, GunnerCmdrCounter }, - { ai_charge, -1.5f }, - { ai_charge }, - { ai_charge, 0, monster_duck_up }, - { ai_charge }, - { ai_charge, 11.f }, - { ai_charge, 2.0f }, - { ai_charge, 5.6f } -}; -MMOVE_T(guncmdr_move_duck_attack) = { FRAME_c_attack901, FRAME_c_attack919, guncmdr_frames_duck_attack, guncmdr_run }; - -MONSTERINFO_DUCK(guncmdr_duck) (edict_t *self, gtime_t eta) -> bool -{ - if ((self->monsterinfo.active_move == &guncmdr_move_jump2) || - (self->monsterinfo.active_move == &guncmdr_move_jump)) - { - return false; - } - - if ((self->monsterinfo.active_move == &guncmdr_move_fire_chain_dodge_left) || - (self->monsterinfo.active_move == &guncmdr_move_fire_chain_dodge_right) || - (self->monsterinfo.active_move == &guncmdr_move_attack_grenade_back_dodge_left) || - (self->monsterinfo.active_move == &guncmdr_move_attack_grenade_back_dodge_right) || - (self->monsterinfo.active_move == &guncmdr_move_attack_mortar_dodge)) - { - // if we're dodging, don't duck - self->monsterinfo.unduck(self); - return false; - } - - M_SetAnimation(self, &guncmdr_move_duck_attack); - - return true; -} - -MONSTERINFO_SIDESTEP(guncmdr_sidestep) (edict_t *self) -> bool -{ - // use special dodge during the main firing anim - if (self->monsterinfo.active_move == &guncmdr_move_fire_chain || - self->monsterinfo.active_move == &guncmdr_move_fire_chain_run) - { - M_SetAnimation(self, !self->monsterinfo.lefty ? &guncmdr_move_fire_chain_dodge_right : &guncmdr_move_fire_chain_dodge_left, false); - return true; - } - - // for backwards mortar, back up where we are in the animation and do a quick dodge - if (self->monsterinfo.active_move == &guncmdr_move_attack_grenade_back) - { - self->count = self->s.frame; - M_SetAnimation(self, !self->monsterinfo.lefty ? &guncmdr_move_attack_grenade_back_dodge_right : &guncmdr_move_attack_grenade_back_dodge_left, false); - return true; - } - - // use crouch-move for mortar dodge - if (self->monsterinfo.active_move == &guncmdr_move_attack_mortar) - { - self->count = self->s.frame; - M_SetAnimation(self, &guncmdr_move_attack_mortar_dodge, false); - return true; - } - - // regular sidestep during run - if (self->monsterinfo.active_move == &guncmdr_move_run) - { - M_SetAnimation(self, &guncmdr_move_run, true); - return true; - } - - return false; -} - -MONSTERINFO_BLOCKED(guncmdr_blocked) (edict_t *self, float dist) -> bool -{ - if (blocked_checkplat(self, dist)) - return true; - - if (auto result = blocked_checkjump(self, dist); result != blocked_jump_result_t::NO_JUMP) - { - if (result != blocked_jump_result_t::JUMP_TURN) - guncmdr_jump(self, result); - - return true; - } - - return false; -} -// PGM -//=========== - -/*QUAKED monster_guncmdr (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight NoJumping -model="models/monsters/guncmdr/tris.md2" -*/ -void SP_monster_guncmdr(edict_t *self) -{ - const spawn_temp_t &st = ED_GetSpawnTemp(); - - if ( !M_AllowSpawn( self ) ) { - G_FreeEdict( self ); - return; - } - - sound_death.assign("guncmdr/gcdrdeath1.wav"); - sound_pain.assign("guncmdr/gcdrpain2.wav"); - sound_pain2.assign("guncmdr/gcdrpain1.wav"); - sound_idle.assign("guncmdr/gcdridle1.wav"); - sound_open.assign("guncmdr/gcdratck1.wav"); - sound_search.assign("guncmdr/gcdrsrch1.wav"); - sound_sight.assign("guncmdr/sight1.wav"); - - gi.soundindex("guncmdr/gcdratck2.wav"); - gi.soundindex("guncmdr/gcdratck3.wav"); - - self->movetype = MOVETYPE_STEP; - self->solid = SOLID_BBOX; - self->s.modelindex = gi.modelindex("models/monsters/gunner/tris.md2"); - - gi.modelindex("models/monsters/gunner/gibs/chest.md2"); - gi.modelindex("models/monsters/gunner/gibs/foot.md2"); - gi.modelindex("models/monsters/gunner/gibs/garm.md2"); - gi.modelindex("models/monsters/gunner/gibs/gun.md2"); - gi.modelindex("models/monsters/gunner/gibs/head.md2"); - - self->s.scale = 1.25f; - self->mins = vec3_t { -16, -16, -24 }; - self->maxs = vec3_t { 16, 16, 36 }; - self->s.skinnum = 2; - - self->health = 325 * st.health_multiplier; - self->gib_health = -175; - self->mass = 255; - - self->pain = guncmdr_pain; - self->die = guncmdr_die; - - self->monsterinfo.stand = guncmdr_stand; - self->monsterinfo.walk = guncmdr_walk; - self->monsterinfo.run = guncmdr_run; - // pmm - self->monsterinfo.dodge = M_MonsterDodge; - self->monsterinfo.duck = guncmdr_duck; - self->monsterinfo.unduck = monster_duck_up; - self->monsterinfo.sidestep = guncmdr_sidestep; - self->monsterinfo.blocked = guncmdr_blocked; // PGM - // pmm - self->monsterinfo.attack = guncmdr_attack; - self->monsterinfo.melee = nullptr; - self->monsterinfo.sight = guncmdr_sight; - self->monsterinfo.search = guncmdr_search; - self->monsterinfo.setskin = guncmdr_setskin; - - gi.linkentity(self); - - M_SetAnimation(self, &guncmdr_move_stand); - self->monsterinfo.scale = MODEL_SCALE; - - if (!st.was_key_specified("power_armor_power")) - self->monsterinfo.power_armor_power = 200; - if (!st.was_key_specified("power_armor_type")) - self->monsterinfo.power_armor_type = IT_ITEM_POWER_SHIELD; - - // PMM - //self->monsterinfo.blindfire = true; - self->monsterinfo.can_jump = !self->spawnflags.has(SPAWNFLAG_GUNCMDR_NOJUMPING); - self->monsterinfo.drop_height = 192; - self->monsterinfo.jump_height = 40; - - walkmonster_start(self); -} diff --git a/to add/m_gunner.h b/to add/m_gunner.h deleted file mode 100644 index e1a3e9fc..00000000 --- a/to add/m_gunner.h +++ /dev/null @@ -1,809 +0,0 @@ -// Copyright (c) ZeniMax Media Inc. -// Licensed under the GNU General Public License 2.0. -// E:\G Drive\md2f\quake2\baseq2\models/monsters/gunner - -// This file generated by qdata - Do NOT Modify - -enum { - FRAME_stand01, - FRAME_stand02, - FRAME_stand03, - FRAME_stand04, - FRAME_stand05, - FRAME_stand06, - FRAME_stand07, - FRAME_stand08, - FRAME_stand09, - FRAME_stand10, - FRAME_stand11, - FRAME_stand12, - FRAME_stand13, - FRAME_stand14, - FRAME_stand15, - FRAME_stand16, - FRAME_stand17, - FRAME_stand18, - FRAME_stand19, - FRAME_stand20, - FRAME_stand21, - FRAME_stand22, - FRAME_stand23, - FRAME_stand24, - FRAME_stand25, - FRAME_stand26, - FRAME_stand27, - FRAME_stand28, - FRAME_stand29, - FRAME_stand30, - FRAME_stand31, - FRAME_stand32, - FRAME_stand33, - FRAME_stand34, - FRAME_stand35, - FRAME_stand36, - FRAME_stand37, - FRAME_stand38, - FRAME_stand39, - FRAME_stand40, - FRAME_stand41, - FRAME_stand42, - FRAME_stand43, - FRAME_stand44, - FRAME_stand45, - FRAME_stand46, - FRAME_stand47, - FRAME_stand48, - FRAME_stand49, - FRAME_stand50, - FRAME_stand51, - FRAME_stand52, - FRAME_stand53, - FRAME_stand54, - FRAME_stand55, - FRAME_stand56, - FRAME_stand57, - FRAME_stand58, - FRAME_stand59, - FRAME_stand60, - FRAME_stand61, - FRAME_stand62, - FRAME_stand63, - FRAME_stand64, - FRAME_stand65, - FRAME_stand66, - FRAME_stand67, - FRAME_stand68, - FRAME_stand69, - FRAME_stand70, - FRAME_walk01, - FRAME_walk02, - FRAME_walk03, - FRAME_walk04, - FRAME_walk05, - FRAME_walk06, - FRAME_walk07, - FRAME_walk08, - FRAME_walk09, - FRAME_walk10, - FRAME_walk11, - FRAME_walk12, - FRAME_walk13, - FRAME_walk14, - FRAME_walk15, - FRAME_walk16, - FRAME_walk17, - FRAME_walk18, - FRAME_walk19, - FRAME_walk20, - FRAME_walk21, - FRAME_walk22, - FRAME_walk23, - FRAME_walk24, - FRAME_run01, - FRAME_run02, - FRAME_run03, - FRAME_run04, - FRAME_run05, - FRAME_run06, - FRAME_run07, - FRAME_run08, - FRAME_runs01, - FRAME_runs02, - FRAME_runs03, - FRAME_runs04, - FRAME_runs05, - FRAME_runs06, - FRAME_attak101, - FRAME_attak102, - FRAME_attak103, - FRAME_attak104, - FRAME_attak105, - FRAME_attak106, - FRAME_attak107, - FRAME_attak108, - FRAME_attak109, - FRAME_attak110, - FRAME_attak111, - FRAME_attak112, - FRAME_attak113, - FRAME_attak114, - FRAME_attak115, - FRAME_attak116, - FRAME_attak117, - FRAME_attak118, - FRAME_attak119, - FRAME_attak120, - FRAME_attak121, - FRAME_attak201, - FRAME_attak202, - FRAME_attak203, - FRAME_attak204, - FRAME_attak205, - FRAME_attak206, - FRAME_attak207, - FRAME_attak208, - FRAME_attak209, - FRAME_attak210, - FRAME_attak211, - FRAME_attak212, - FRAME_attak213, - FRAME_attak214, - FRAME_attak215, - FRAME_attak216, - FRAME_attak217, - FRAME_attak218, - FRAME_attak219, - FRAME_attak220, - FRAME_attak221, - FRAME_attak222, - FRAME_attak223, - FRAME_attak224, - FRAME_attak225, - FRAME_attak226, - FRAME_attak227, - FRAME_attak228, - FRAME_attak229, - FRAME_attak230, - FRAME_pain101, - FRAME_pain102, - FRAME_pain103, - FRAME_pain104, - FRAME_pain105, - FRAME_pain106, - FRAME_pain107, - FRAME_pain108, - FRAME_pain109, - FRAME_pain110, - FRAME_pain111, - FRAME_pain112, - FRAME_pain113, - FRAME_pain114, - FRAME_pain115, - FRAME_pain116, - FRAME_pain117, - FRAME_pain118, - FRAME_pain201, - FRAME_pain202, - FRAME_pain203, - FRAME_pain204, - FRAME_pain205, - FRAME_pain206, - FRAME_pain207, - FRAME_pain208, - FRAME_pain301, - FRAME_pain302, - FRAME_pain303, - FRAME_pain304, - FRAME_pain305, - FRAME_death01, - FRAME_death02, - FRAME_death03, - FRAME_death04, - FRAME_death05, - FRAME_death06, - FRAME_death07, - FRAME_death08, - FRAME_death09, - FRAME_death10, - FRAME_death11, - FRAME_duck01, - FRAME_duck02, - FRAME_duck03, - FRAME_duck04, - FRAME_duck05, - FRAME_duck06, - FRAME_duck07, - FRAME_duck08, - FRAME_jump01, - FRAME_jump02, - FRAME_jump03, - FRAME_jump04, - FRAME_jump05, - FRAME_jump06, - FRAME_jump07, - FRAME_jump08, - FRAME_jump09, - FRAME_jump10, - FRAME_shield01, - FRAME_shield02, - FRAME_shield03, - FRAME_shield04, - FRAME_shield05, - FRAME_shield06, - FRAME_attak301, - FRAME_attak302, - FRAME_attak303, - FRAME_attak304, - FRAME_attak305, - FRAME_attak306, - FRAME_attak307, - FRAME_attak308, - FRAME_attak309, - FRAME_attak310, - FRAME_attak311, - FRAME_attak312, - FRAME_attak313, - FRAME_attak314, - FRAME_attak315, - FRAME_attak316, - FRAME_attak317, - FRAME_attak318, - FRAME_attak319, - FRAME_attak320, - FRAME_attak321, - FRAME_attak322, - FRAME_attak323, - FRAME_attak324, - FRAME_c_stand101, - FRAME_c_stand102, - FRAME_c_stand103, - FRAME_c_stand104, - FRAME_c_stand105, - FRAME_c_stand106, - FRAME_c_stand107, - FRAME_c_stand108, - FRAME_c_stand109, - FRAME_c_stand110, - FRAME_c_stand111, - FRAME_c_stand112, - FRAME_c_stand113, - FRAME_c_stand114, - FRAME_c_stand115, - FRAME_c_stand116, - FRAME_c_stand117, - FRAME_c_stand118, - FRAME_c_stand119, - FRAME_c_stand120, - FRAME_c_stand121, - FRAME_c_stand122, - FRAME_c_stand123, - FRAME_c_stand124, - FRAME_c_stand125, - FRAME_c_stand126, - FRAME_c_stand127, - FRAME_c_stand128, - FRAME_c_stand129, - FRAME_c_stand130, - FRAME_c_stand131, - FRAME_c_stand132, - FRAME_c_stand133, - FRAME_c_stand134, - FRAME_c_stand135, - FRAME_c_stand136, - FRAME_c_stand137, - FRAME_c_stand138, - FRAME_c_stand139, - FRAME_c_stand140, - FRAME_c_stand201, - FRAME_c_stand202, - FRAME_c_stand203, - FRAME_c_stand204, - FRAME_c_stand205, - FRAME_c_stand206, - FRAME_c_stand207, - FRAME_c_stand208, - FRAME_c_stand209, - FRAME_c_stand210, - FRAME_c_stand211, - FRAME_c_stand212, - FRAME_c_stand213, - FRAME_c_stand214, - FRAME_c_stand215, - FRAME_c_stand216, - FRAME_c_stand217, - FRAME_c_stand218, - FRAME_c_stand219, - FRAME_c_stand220, - FRAME_c_stand221, - FRAME_c_stand222, - FRAME_c_stand223, - FRAME_c_stand224, - FRAME_c_stand225, - FRAME_c_stand226, - FRAME_c_stand227, - FRAME_c_stand228, - FRAME_c_stand229, - FRAME_c_stand230, - FRAME_c_stand231, - FRAME_c_stand232, - FRAME_c_stand233, - FRAME_c_stand234, - FRAME_c_stand235, - FRAME_c_stand236, - FRAME_c_stand237, - FRAME_c_stand238, - FRAME_c_stand239, - FRAME_c_stand240, - FRAME_c_stand241, - FRAME_c_stand242, - FRAME_c_stand243, - FRAME_c_stand244, - FRAME_c_stand245, - FRAME_c_stand246, - FRAME_c_stand247, - FRAME_c_stand248, - FRAME_c_stand249, - FRAME_c_stand250, - FRAME_c_stand251, - FRAME_c_stand252, - FRAME_c_stand253, - FRAME_c_stand254, - FRAME_c_attack101, - FRAME_c_attack102, - FRAME_c_attack103, - FRAME_c_attack104, - FRAME_c_attack105, - FRAME_c_attack106, - FRAME_c_attack107, - FRAME_c_attack108, - FRAME_c_attack109, - FRAME_c_attack110, - FRAME_c_attack111, - FRAME_c_attack112, - FRAME_c_attack113, - FRAME_c_attack114, - FRAME_c_attack115, - FRAME_c_attack116, - FRAME_c_attack117, - FRAME_c_attack118, - FRAME_c_attack119, - FRAME_c_attack120, - FRAME_c_attack121, - FRAME_c_attack122, - FRAME_c_attack123, - FRAME_c_attack124, - FRAME_c_jump01, - FRAME_c_jump02, - FRAME_c_jump03, - FRAME_c_jump04, - FRAME_c_jump05, - FRAME_c_jump06, - FRAME_c_jump07, - FRAME_c_jump08, - FRAME_c_jump09, - FRAME_c_jump10, - FRAME_c_attack201, - FRAME_c_attack202, - FRAME_c_attack203, - FRAME_c_attack204, - FRAME_c_attack205, - FRAME_c_attack206, - FRAME_c_attack207, - FRAME_c_attack208, - FRAME_c_attack209, - FRAME_c_attack210, - FRAME_c_attack211, - FRAME_c_attack212, - FRAME_c_attack213, - FRAME_c_attack214, - FRAME_c_attack215, - FRAME_c_attack216, - FRAME_c_attack217, - FRAME_c_attack218, - FRAME_c_attack219, - FRAME_c_attack220, - FRAME_c_attack221, - FRAME_c_attack301, - FRAME_c_attack302, - FRAME_c_attack303, - FRAME_c_attack304, - FRAME_c_attack305, - FRAME_c_attack306, - FRAME_c_attack307, - FRAME_c_attack308, - FRAME_c_attack309, - FRAME_c_attack310, - FRAME_c_attack311, - FRAME_c_attack312, - FRAME_c_attack313, - FRAME_c_attack314, - FRAME_c_attack315, - FRAME_c_attack316, - FRAME_c_attack317, - FRAME_c_attack318, - FRAME_c_attack319, - FRAME_c_attack320, - FRAME_c_attack321, - FRAME_c_attack401, - FRAME_c_attack402, - FRAME_c_attack403, - FRAME_c_attack404, - FRAME_c_attack405, - FRAME_c_attack501, - FRAME_c_attack502, - FRAME_c_attack503, - FRAME_c_attack504, - FRAME_c_attack505, - FRAME_c_attack601, - FRAME_c_attack602, - FRAME_c_attack603, - FRAME_c_attack604, - FRAME_c_attack605, - FRAME_c_attack701, - FRAME_c_attack702, - FRAME_c_attack703, - FRAME_c_attack704, - FRAME_c_attack705, - FRAME_c_pain101, - FRAME_c_pain102, - FRAME_c_pain103, - FRAME_c_pain104, - FRAME_c_pain201, - FRAME_c_pain202, - FRAME_c_pain203, - FRAME_c_pain204, - FRAME_c_pain301, - FRAME_c_pain302, - FRAME_c_pain303, - FRAME_c_pain304, - FRAME_c_pain401, - FRAME_c_pain402, - FRAME_c_pain403, - FRAME_c_pain404, - FRAME_c_pain405, - FRAME_c_pain406, - FRAME_c_pain407, - FRAME_c_pain408, - FRAME_c_pain409, - FRAME_c_pain410, - FRAME_c_pain411, - FRAME_c_pain412, - FRAME_c_pain413, - FRAME_c_pain414, - FRAME_c_pain415, - FRAME_c_pain501, - FRAME_c_pain502, - FRAME_c_pain503, - FRAME_c_pain504, - FRAME_c_pain505, - FRAME_c_pain506, - FRAME_c_pain507, - FRAME_c_pain508, - FRAME_c_pain509, - FRAME_c_pain510, - FRAME_c_pain511, - FRAME_c_pain512, - FRAME_c_pain513, - FRAME_c_pain514, - FRAME_c_pain515, - FRAME_c_pain516, - FRAME_c_pain517, - FRAME_c_pain518, - FRAME_c_pain519, - FRAME_c_pain520, - FRAME_c_pain521, - FRAME_c_pain522, - FRAME_c_pain523, - FRAME_c_pain524, - FRAME_c_death101, - FRAME_c_death102, - FRAME_c_death103, - FRAME_c_death104, - FRAME_c_death105, - FRAME_c_death106, - FRAME_c_death107, - FRAME_c_death108, - FRAME_c_death109, - FRAME_c_death110, - FRAME_c_death111, - FRAME_c_death112, - FRAME_c_death113, - FRAME_c_death114, - FRAME_c_death115, - FRAME_c_death116, - FRAME_c_death117, - FRAME_c_death118, - FRAME_c_death201, - FRAME_c_death202, - FRAME_c_death203, - FRAME_c_death204, - FRAME_c_death301, - FRAME_c_death302, - FRAME_c_death303, - FRAME_c_death304, - FRAME_c_death305, - FRAME_c_death306, - FRAME_c_death307, - FRAME_c_death308, - FRAME_c_death309, - FRAME_c_death310, - FRAME_c_death311, - FRAME_c_death312, - FRAME_c_death313, - FRAME_c_death314, - FRAME_c_death315, - FRAME_c_death316, - FRAME_c_death317, - FRAME_c_death318, - FRAME_c_death319, - FRAME_c_death320, - FRAME_c_death321, - FRAME_c_death401, - FRAME_c_death402, - FRAME_c_death403, - FRAME_c_death404, - FRAME_c_death405, - FRAME_c_death406, - FRAME_c_death407, - FRAME_c_death408, - FRAME_c_death409, - FRAME_c_death410, - FRAME_c_death411, - FRAME_c_death412, - FRAME_c_death413, - FRAME_c_death414, - FRAME_c_death415, - FRAME_c_death416, - FRAME_c_death417, - FRAME_c_death418, - FRAME_c_death419, - FRAME_c_death420, - FRAME_c_death421, - FRAME_c_death422, - FRAME_c_death423, - FRAME_c_death424, - FRAME_c_death425, - FRAME_c_death426, - FRAME_c_death427, - FRAME_c_death428, - FRAME_c_death429, - FRAME_c_death430, - FRAME_c_death431, - FRAME_c_death432, - FRAME_c_death433, - FRAME_c_death434, - FRAME_c_death435, - FRAME_c_death436, - FRAME_c_death501, - FRAME_c_death502, - FRAME_c_death503, - FRAME_c_death504, - FRAME_c_death505, - FRAME_c_death506, - FRAME_c_death507, - FRAME_c_death508, - FRAME_c_death509, - FRAME_c_death510, - FRAME_c_death511, - FRAME_c_death512, - FRAME_c_death513, - FRAME_c_death514, - FRAME_c_death515, - FRAME_c_death516, - FRAME_c_death517, - FRAME_c_death518, - FRAME_c_death519, - FRAME_c_death520, - FRAME_c_death521, - FRAME_c_death522, - FRAME_c_death523, - FRAME_c_death524, - FRAME_c_death525, - FRAME_c_death526, - FRAME_c_death527, - FRAME_c_death528, - FRAME_c_run101, - FRAME_c_run102, - FRAME_c_run103, - FRAME_c_run104, - FRAME_c_run105, - FRAME_c_run106, - FRAME_c_run201, - FRAME_c_run202, - FRAME_c_run203, - FRAME_c_run204, - FRAME_c_run205, - FRAME_c_run206, - FRAME_c_run301, - FRAME_c_run302, - FRAME_c_run303, - FRAME_c_run304, - FRAME_c_run305, - FRAME_c_run306, - FRAME_c_walk101, - FRAME_c_walk102, - FRAME_c_walk103, - FRAME_c_walk104, - FRAME_c_walk105, - FRAME_c_walk106, - FRAME_c_walk107, - FRAME_c_walk108, - FRAME_c_walk109, - FRAME_c_walk110, - FRAME_c_walk111, - FRAME_c_walk112, - FRAME_c_walk113, - FRAME_c_walk114, - FRAME_c_walk115, - FRAME_c_walk116, - FRAME_c_walk117, - FRAME_c_walk118, - FRAME_c_walk119, - FRAME_c_walk120, - FRAME_c_walk121, - FRAME_c_walk122, - FRAME_c_walk123, - FRAME_c_walk124, - FRAME_c_pain601, - FRAME_c_pain602, - FRAME_c_pain603, - FRAME_c_pain604, - FRAME_c_pain605, - FRAME_c_pain606, - FRAME_c_pain607, - FRAME_c_pain608, - FRAME_c_pain609, - FRAME_c_pain610, - FRAME_c_pain611, - FRAME_c_pain612, - FRAME_c_pain613, - FRAME_c_pain614, - FRAME_c_pain615, - FRAME_c_pain616, - FRAME_c_pain617, - FRAME_c_pain618, - FRAME_c_pain619, - FRAME_c_pain620, - FRAME_c_pain621, - FRAME_c_pain622, - FRAME_c_pain623, - FRAME_c_pain624, - FRAME_c_pain625, - FRAME_c_pain626, - FRAME_c_pain627, - FRAME_c_pain628, - FRAME_c_pain629, - FRAME_c_pain630, - FRAME_c_pain631, - FRAME_c_pain632, - FRAME_c_death601, - FRAME_c_death602, - FRAME_c_death603, - FRAME_c_death604, - FRAME_c_death605, - FRAME_c_death606, - FRAME_c_death607, - FRAME_c_death608, - FRAME_c_death609, - FRAME_c_death610, - FRAME_c_death611, - FRAME_c_death612, - FRAME_c_death613, - FRAME_c_death614, - FRAME_c_death701, - FRAME_c_death702, - FRAME_c_death703, - FRAME_c_death704, - FRAME_c_death705, - FRAME_c_death706, - FRAME_c_death707, - FRAME_c_death708, - FRAME_c_death709, - FRAME_c_death710, - FRAME_c_death711, - FRAME_c_death712, - FRAME_c_death713, - FRAME_c_death714, - FRAME_c_death715, - FRAME_c_death716, - FRAME_c_death717, - FRAME_c_death718, - FRAME_c_death719, - FRAME_c_death720, - FRAME_c_death721, - FRAME_c_death722, - FRAME_c_death723, - FRAME_c_death724, - FRAME_c_death725, - FRAME_c_death726, - FRAME_c_death727, - FRAME_c_death728, - FRAME_c_death729, - FRAME_c_death730, - FRAME_c_pain701, - FRAME_c_pain702, - FRAME_c_pain703, - FRAME_c_pain704, - FRAME_c_pain705, - FRAME_c_pain706, - FRAME_c_pain707, - FRAME_c_pain708, - FRAME_c_pain709, - FRAME_c_pain710, - FRAME_c_pain711, - FRAME_c_pain712, - FRAME_c_pain713, - FRAME_c_pain714, - FRAME_c_attack801, - FRAME_c_attack802, - FRAME_c_attack803, - FRAME_c_attack804, - FRAME_c_attack805, - FRAME_c_attack806, - FRAME_c_attack807, - FRAME_c_attack808, - FRAME_c_attack809, - FRAME_c_attack901, - FRAME_c_attack902, - FRAME_c_attack903, - FRAME_c_attack904, - FRAME_c_attack905, - FRAME_c_attack906, - FRAME_c_attack907, - FRAME_c_attack908, - FRAME_c_attack909, - FRAME_c_attack910, - FRAME_c_attack911, - FRAME_c_attack912, - FRAME_c_attack913, - FRAME_c_attack914, - FRAME_c_attack915, - FRAME_c_attack916, - FRAME_c_attack917, - FRAME_c_attack918, - FRAME_c_attack919, - FRAME_c_duck01, - FRAME_c_duck02, - FRAME_c_duckstep01, - FRAME_c_duckstep02, - FRAME_c_duckstep03, - FRAME_c_duckstep04, - FRAME_c_duckstep05, - FRAME_c_duckstep06, - FRAME_c_duckpain01, - FRAME_c_duckpain02, - FRAME_c_duckpain03, - FRAME_c_duckpain04, - FRAME_c_duckpain05, - FRAME_c_duckdeath01, - FRAME_c_duckdeath02, - FRAME_c_duckdeath03, - FRAME_c_duckdeath04, - FRAME_c_duckdeath05, - FRAME_c_duckdeath06, - FRAME_c_duckdeath07, - FRAME_c_duckdeath08, - FRAME_c_duckdeath09, - FRAME_c_duckdeath10, - FRAME_c_duckdeath11, - FRAME_c_duckdeath12, - FRAME_c_duckdeath13, - FRAME_c_duckdeath14, - FRAME_c_duckdeath15, - FRAME_c_duckdeath16, - FRAME_c_duckdeath17, - FRAME_c_duckdeath18, - FRAME_c_duckdeath19, - FRAME_c_duckdeath20, - FRAME_c_duckdeath21, - FRAME_c_duckdeath22, - FRAME_c_duckdeath23, - FRAME_c_duckdeath24, - FRAME_c_duckdeath25, - FRAME_c_duckdeath26, - FRAME_c_duckdeath27, - FRAME_c_duckdeath28, - FRAME_c_duckdeath29 -}; - -constexpr float MODEL_SCALE = 1.150000f; diff --git a/to add/m_hover.cpp b/to add/m_hover.cpp deleted file mode 100644 index 9262d5e1..00000000 --- a/to add/m_hover.cpp +++ /dev/null @@ -1,804 +0,0 @@ -// Copyright (c) ZeniMax Media Inc. -// Licensed under the GNU General Public License 2.0. -/* -============================================================================== - -hover - -This file combines the original hover monster (blaster) with the Xatrix expansion -hover (rocket), as well as daedalus variants (blaster2 and grenades). - -============================================================================== -*/ - -#include "g_local.h" -#include "m_hover.h" -#include "m_flash.h" -#include "shared.h" -#include "horde/g_horde_scaling.h" -#include "monster_constants.h" - -static cached_soundindex sound_pain1; -static cached_soundindex sound_pain2; -static cached_soundindex sound_death1; -static cached_soundindex sound_death2; -static cached_soundindex sound_sight; -static cached_soundindex sound_search1; -static cached_soundindex sound_search2; - -// ROGUE -// daedalus sounds -static cached_soundindex daed_sound_pain1; -static cached_soundindex daed_sound_pain2; -static cached_soundindex daed_sound_death1; -static cached_soundindex daed_sound_death2; -static cached_soundindex daed_sound_sight; -static cached_soundindex daed_sound_search1; -static cached_soundindex daed_sound_search2; -// ROGUE - -// FIX: Forward declare the master spawn function so other spawn functions can call it. -void SP_monster_hover(edict_t* self); - -// FIX: This helper function is now the single source of truth for identifying a Daedalus. -bool IsDaedalusType(const edict_t* ent) -{ - if (!ent) return false; - const auto id = static_cast(ent->monsterinfo.monster_type_id); - return (id == horde::MonsterTypeID::DAEDALUS || - id == horde::MonsterTypeID::DAEDALUS_BOMBER); -} - -struct hover_style_t -{ - enum weapon_t - { - Blaster = 0, - Rocket = 1, - Blaster2 = 2, - Grenade = 3 - }; - - weapon_t weapon; - - // This constructor is now reliable because the monster_type_id is set correctly at spawn. - hover_style_t(edict_t* self) - { - // gi.Com_PrintFmt("Hover attacking with ID: {}\n", self->monsterinfo.monster_type_id); - - // Use a switch on the definitive monster_type_id - switch (static_cast(self->monsterinfo.monster_type_id)) - { - case horde::MonsterTypeID::HOVER: - weapon = Rocket; - break; - case horde::MonsterTypeID::DAEDALUS: - weapon = Blaster2; - break; - case horde::MonsterTypeID::DAEDALUS_BOMBER: - weapon = Grenade; - break; - case horde::MonsterTypeID::HOVER_VANILLA: - default: // Fallback to the standard blaster - weapon = Blaster; - break; - } - } - - // Keep these as constexpr since they only depend on the weapon value - constexpr bool is_vanilla() const { return weapon < Blaster; } - constexpr bool is_xatrix() const { return weapon >= Blaster2; } - constexpr bool has_blaster() const { return weapon == Blaster; } - constexpr bool has_rocket() const { return weapon == Rocket; } - constexpr bool has_blaster2() const { return weapon == Blaster2; } - constexpr bool has_grenade() const { return weapon == Grenade; } -}; - -MONSTERINFO_SIGHT(hover_sight) (edict_t* self, edict_t* other) -> void -{ - // FIX: Use the reliable IsDaedalusType helper instead of checking mass. - if (!IsDaedalusType(self)) - gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); - else - gi.sound(self, CHAN_VOICE, daed_sound_sight, 1, ATTN_NORM, 0); -} - -MONSTERINFO_SEARCH(hover_search) (edict_t* self) -> void -{ - // FIX: Use the reliable IsDaedalusType helper instead of checking mass. - if (!IsDaedalusType(self)) - { - if (frandom() < 0.5f) - gi.sound(self, CHAN_VOICE, sound_search1, 1, ATTN_NORM, 0); - else - gi.sound(self, CHAN_VOICE, sound_search2, 1, ATTN_NORM, 0); - } - else - { - if (frandom() < 0.5f) - gi.sound(self, CHAN_VOICE, daed_sound_search1, 1, ATTN_NORM, 0); - else - gi.sound(self, CHAN_VOICE, daed_sound_search2, 1, ATTN_NORM, 0); - } -} - -void hover_run(edict_t* self); -void hover_dead(edict_t* self); -void hover_attack(edict_t* self); -void hover_reattack(edict_t* self); -void hover_fire_weapon(edict_t* self); - -mframe_t hover_frames_stand[] = { - { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, - { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, - { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, - { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, - { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, - { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand } -}; -MMOVE_T(hover_move_stand) = { FRAME_stand01, FRAME_stand30, hover_frames_stand, nullptr }; - -mframe_t hover_frames_pain3[] = { - { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, - { ai_move }, { ai_move }, { ai_move }, { ai_move } -}; -MMOVE_T(hover_move_pain3) = { FRAME_pain301, FRAME_pain309, hover_frames_pain3, hover_run }; - -mframe_t hover_frames_pain2[] = { - { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, - { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, - { ai_move }, { ai_move } -}; -MMOVE_T(hover_move_pain2) = { FRAME_pain201, FRAME_pain212, hover_frames_pain2, hover_run }; - -mframe_t hover_frames_pain1[] = { - { ai_move }, { ai_move }, { ai_move, 2 }, { ai_move, -8 }, { ai_move, -4 }, - { ai_move, -6 }, { ai_move, -4 }, { ai_move, -3 }, { ai_move, 1 }, { ai_move }, - { ai_move }, { ai_move }, { ai_move, 3 }, { ai_move, 1 }, { ai_move }, - { ai_move, 2 }, { ai_move, 3 }, { ai_move, 2 }, { ai_move, 7 }, { ai_move, 1 }, - { ai_move }, { ai_move }, { ai_move, 2 }, { ai_move }, { ai_move }, - { ai_move, 5 }, { ai_move, 3 }, { ai_move, 4 } -}; -MMOVE_T(hover_move_pain1) = { FRAME_pain101, FRAME_pain128, hover_frames_pain1, hover_run }; - -mframe_t hover_frames_walk[] = { - { ai_walk, 4 }, { ai_walk, 4 }, { ai_walk, 4 }, { ai_walk, 4 }, { ai_walk, 4 }, - { ai_walk, 4 }, { ai_walk, 4 }, { ai_walk, 4 }, { ai_walk, 4 }, { ai_walk, 4 }, - { ai_walk, 4 }, { ai_walk, 4 }, { ai_walk, 4 }, { ai_walk, 4 }, { ai_walk, 4 }, - { ai_walk, 4 }, { ai_walk, 4 }, { ai_walk, 4 }, { ai_walk, 4 }, { ai_walk, 4 }, - { ai_walk, 4 }, { ai_walk, 4 }, { ai_walk, 4 }, { ai_walk, 4 }, { ai_walk, 4 }, - { ai_walk, 4 }, { ai_walk, 4 }, { ai_walk, 4 }, { ai_walk, 4 }, { ai_walk, 4 }, - { ai_walk, 4 }, { ai_walk, 4 }, { ai_walk, 4 }, { ai_walk, 4 }, { ai_walk, 4 } -}; -MMOVE_T(hover_move_walk) = { FRAME_forwrd01, FRAME_forwrd35, hover_frames_walk, nullptr }; - -mframe_t hover_frames_run[] = { - { ai_run, 10 }, { ai_run, 10 }, { ai_run, 10 }, { ai_run, 10 }, { ai_run, 10 }, - { ai_run, 10 }, { ai_run, 10 }, { ai_run, 10 }, { ai_run, 10 }, { ai_run, 10 }, - { ai_run, 10 }, { ai_run, 10 }, { ai_run, 10 }, { ai_run, 10 }, { ai_run, 10 }, - { ai_run, 10 }, { ai_run, 10 }, { ai_run, 10 }, { ai_run, 10 }, { ai_run, 10 }, - { ai_run, 10 }, { ai_run, 10 }, { ai_run, 10 }, { ai_run, 10 }, { ai_run, 10 }, - { ai_run, 10 }, { ai_run, 10 }, { ai_run, 10 }, { ai_run, 10 }, { ai_run, 10 }, - { ai_run, 10 }, { ai_run, 10 }, { ai_run, 10 }, { ai_run, 10 }, { ai_run, 10 } -}; -MMOVE_T(hover_move_run) = { FRAME_forwrd01, FRAME_forwrd35, hover_frames_run, nullptr }; - -static void hover_gib(edict_t* self) -{ - gi.WriteByte(svc_temp_entity); - gi.WriteByte(TE_EXPLOSION1); - gi.WritePosition(self->s.origin); - gi.multicast(self->s.origin, MULTICAST_PHS, false); - - self->s.skinnum /= 2; - - ThrowGibs(self, 150, { - { 2, "models/objects/gibs/sm_meat/tris.md2" }, - { 2, "models/objects/gibs/sm_metal/tris.md2", GIB_METALLIC }, - { "models/monsters/hover/gibs/chest.md2", GIB_SKINNED }, - { 2, "models/monsters/hover/gibs/ring.md2", GIB_SKINNED | GIB_METALLIC }, - { 2, "models/monsters/hover/gibs/foot.md2", GIB_SKINNED }, - { "models/monsters/hover/gibs/head.md2", GIB_SKINNED | GIB_HEAD }, - }); -} - -THINK(hover_deadthink) (edict_t* self) -> void -{ - if (!self->groundentity && level.time < self->timestamp) - { - self->nextthink = level.time + FRAME_TIME_S; - return; - } - hover_gib(self); -} - -void hover_dying(edict_t* self) -{ - if (self->groundentity) - { - hover_deadthink(self); - return; - } - if (brandom()) - return; - gi.WriteByte(svc_temp_entity); - gi.WriteByte(TE_PLAIN_EXPLOSION); - gi.WritePosition(self->s.origin); - gi.multicast(self->s.origin, MULTICAST_PHS, false); - if (brandom()) - ThrowGibs(self, 120, { { "models/objects/gibs/sm_meat/tris.md2" } }); - else - ThrowGibs(self, 120, { { "models/objects/gibs/sm_metal/tris.md2", GIB_METALLIC } }); -} - -mframe_t hover_frames_death1[] = { - { ai_move }, { ai_move, 0.f, hover_dying }, { ai_move }, { ai_move, 0.f, hover_dying }, - { ai_move }, { ai_move, 0.f, hover_dying }, { ai_move, -10, hover_dying }, { ai_move, 3 }, - { ai_move, 5, hover_dying }, { ai_move, 4, hover_dying }, { ai_move, 7 } -}; -MMOVE_T(hover_move_death1) = { FRAME_death101, FRAME_death111, hover_frames_death1, hover_dead }; - -mframe_t hover_frames_start_attack[] = { - { ai_charge, 1 }, { ai_charge, 1 }, { ai_charge, 1 } -}; -MMOVE_T(hover_move_start_attack) = { FRAME_attak101, FRAME_attak103, hover_frames_start_attack, hover_attack }; - -mframe_t hover_frames_attack1[] = { - { ai_charge, -10, hover_fire_weapon }, - { ai_charge, -10, hover_fire_weapon }, - { ai_charge, 0, hover_reattack }, -}; -MMOVE_T(hover_move_attack1) = { FRAME_attak104, FRAME_attak106, hover_frames_attack1, nullptr }; - -mframe_t hover_frames_end_attack[] = { - { ai_charge, 1 }, { ai_charge, 1 } -}; -MMOVE_T(hover_move_end_attack) = { FRAME_attak107, FRAME_attak108, hover_frames_end_attack, hover_run }; - -mframe_t hover_frames_attack2[] = { - { ai_charge, 10, hover_fire_weapon }, - { ai_charge, 10, hover_fire_weapon }, - { ai_charge, 10, hover_reattack }, -}; -MMOVE_T(hover_move_attack2) = { FRAME_attak104, FRAME_attak106, hover_frames_attack2, nullptr }; - -void hover_reattack(edict_t* self) -{ - hover_style_t style(self); - float reattack_chance = 0.5f; - - // Daedalus with Blaster2 is more aggressive - if (style.has_blaster2()) - reattack_chance = 0.6f; - // Daedalus with Grenades is less aggressive - else if (style.has_grenade()) - reattack_chance = 0.4f; - - // FIX: Add a check to ensure the enemy is valid before accessing its members. - if (self->enemy && self->enemy->inuse && self->enemy->health > 0) - { - if (visible(self, self->enemy)) - { - if (frandom() <= reattack_chance) - { - if (self->monsterinfo.attack_state == AS_STRAIGHT) - { - M_SetAnimation(self, &hover_move_attack1); - return; - } - else if (self->monsterinfo.attack_state == AS_SLIDING) - { - M_SetAnimation(self, &hover_move_attack2); - return; - } - } - } - } - M_SetAnimation(self, &hover_move_end_attack); -} - -void hover_fire_blaster(edict_t* self) -{ - if (!M_HasValidTarget(self)) - { - return; // Stop immediately if the target is invalid. - } - - vec3_t start, forward, right, end, dir; - int config_speed = M_BLASTER_SPEED(self); - int blasterSpeed = config_speed > 0 ? config_speed : 1230; - AngleVectors(self->s.angles, forward, right, nullptr); - vec3_t const o = monster_flash_offset[(self->s.frame & 1) ? MZ2_HOVER_BLASTER_2 : MZ2_HOVER_BLASTER_1]; - start = M_ProjectFlashSource(self, o, forward, right); - end = self->enemy->s.origin; - end[2] += self->enemy->viewheight; - - // Check if muzzle origin can see enemy before firing - trace_t trace = gi.traceline(start, end, self, MASK_PROJECTILE); - if (!(trace.fraction > 0.5f || trace.ent->solid != SOLID_BSP)) - return; - - dir = end - start; - dir.normalize(); - PredictAim(self, self->enemy, start, blasterSpeed / 1.5, true, 0.f, &dir, &end); - int damage = M_BLASTER_DMG(self); - monster_fire_blaster(self, start, dir, damage > 0 ? damage : 12, blasterSpeed, - (self->s.frame & 1) ? MZ2_HOVER_BLASTER_2 : MZ2_HOVER_BLASTER_1, - (self->s.frame % 4) ? EF_NONE : EF_BLASTER); -} - -void hover_fire_blaster2(edict_t* self) -{ - if (!M_HasValidTarget(self)) - { - return; // Stop immediately if the target is invalid. - } - - vec3_t start, forward, right, end, dir; - int config_speed = M_BLASTER2_SPEED(self); - int blasterSpeed = config_speed > 0 ? config_speed : 1230; - AngleVectors(self->s.angles, forward, right, nullptr); - vec3_t const o = monster_flash_offset[(self->s.frame & 1) ? MZ2_HOVER_BLASTER_2 : MZ2_HOVER_BLASTER_1]; - start = M_ProjectFlashSource(self, o, forward, right); - end = self->enemy->s.origin; - end[2] += self->enemy->viewheight; - - // Check if muzzle origin can see enemy before firing - trace_t trace = gi.traceline(start, end, self, MASK_PROJECTILE); - if (!(trace.fraction > 0.5f || trace.ent->solid != SOLID_BSP)) - return; - - dir = end - start; - dir.normalize(); - PredictAim(self, self->enemy, start, blasterSpeed / 1.5, true, 0.f, &dir, &end); - int damage = M_BLASTER2_DMG(self); - monster_fire_blaster2(self, start, dir, damage > 0 ? damage : 12, blasterSpeed, - (self->s.frame & 1) ? MZ2_DAEDALUS_BLASTER_2 : MZ2_DAEDALUS_BLASTER, - (self->s.frame % 4) ? EF_NONE : EF_BLASTER); -} - -void hover_fire_rocket(edict_t* self) -{ - // Basic enemy check - blindfire logic needs to execute - if (!M_HasEnemy(self)) - return; - - vec3_t forward, right, start, dir, vec, target; - trace_t trace; - int config_speed = M_ROCKET_SPEED(self); - int rocketSpeed = config_speed > 0 ? config_speed : 850; - bool blindfire = (self->monsterinfo.aiflags & AI_MANUAL_STEERING); - AngleVectors(self->s.angles, forward, right, nullptr); - start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_BOSS2_ROCKET_3], forward, right); - - if (blindfire && !visible(self, self->enemy)) - { - if (!self->monsterinfo.blind_fire_target) - return; - target = self->monsterinfo.blind_fire_target; - vec = target; - dir = vec - start; - } - else - { - // Not blindfiring - need fully valid target - if (!M_HasValidTarget(self)) - return; - - target = self->enemy->s.origin; - if (frandom() < 0.33f || (start[2] < self->enemy->absmin[2])) { - vec = target; - vec[2] += self->enemy->viewheight; - dir = vec - start; - } else { - vec = target; - vec[2] = self->enemy->absmin[2] + 1; - dir = vec - start; - } - if (frandom() < 0.35f) - PredictAim(self, self->enemy, start, rocketSpeed / 1.5, false, 0.f, &dir, &vec); - } - - dir.normalize(); - trace = gi.traceline(start, vec, self, MASK_PROJECTILE); - if (trace.fraction > 0.5f || trace.ent == self->enemy || trace.ent->solid != SOLID_BSP) - monster_fire_rocket(self, start, dir, M_ROCKET_DMG(self), M_ROCKET_SPEED(self), MZ2_BOSS2_ROCKET_3); -} - -void hover_fire_grenades(edict_t* self) -{ - // Basic enemy check - blindfire logic needs to execute - if (!M_HasEnemy(self)) - return; - - vec3_t forward, right, up, aim, target, offset{}; - monster_muzzleflash_id_t flash_number = MZ2_GUNCMDR_GRENADE_MORTAR_1; - bool blindfire = self->monsterinfo.aiflags & AI_MANUAL_STEERING; - - if (self->s.frame == FRAME_attak104) offset = { 1.7f, 7.0f, 11.3f }; - else if (self->s.frame == FRAME_attak105) offset = { 1.7f, -7.0f, 11.3f }; - - AngleVectors(self->s.angles, forward, right, up); - const vec3_t start = G_ProjectSource2(self->s.origin, offset, forward, right, up); - - // PMM - blindfire support - if (blindfire && !visible(self, self->enemy)) - { - if (!self->monsterinfo.blind_fire_target) - return; - target = self->monsterinfo.blind_fire_target; - aim = target - start; - } - else - { - // Not blindfiring - need fully valid target - if (!M_HasValidTarget(self)) - return; - - target = self->enemy->s.origin; - PredictAim(self, self->enemy, start, 800, false, 0.f, &aim, nullptr); - } - // pmm - - aim.normalize(); - monster_fire_grenade(self, start, aim, M_GRENADE_DMG(self), M_GRENADE_SPEED(self), flash_number, - (crandom_open() * 10.0f), 200.f + (crandom_open() * 10.0f)); -} - -void hover_fire_weapon(edict_t* self) -{ - if (!M_HasValidTarget(self)) - { - return; // Stop immediately if the target is invalid. - } - - hover_style_t style(self); - if (style.has_blaster()) hover_fire_blaster(self); - else if (style.has_rocket()) hover_fire_rocket(self); - else if (style.has_blaster2()) hover_fire_blaster2(self); - else if (style.has_grenade()) hover_fire_grenades(self); -} - -MONSTERINFO_STAND(hover_stand) (edict_t* self) -> void { M_SetAnimation(self, &hover_move_stand); } -MONSTERINFO_RUN(hover_run) (edict_t* self) -> void { - if (self->monsterinfo.aiflags & AI_STAND_GROUND) M_SetAnimation(self, &hover_move_stand); - else M_SetAnimation(self, &hover_move_run); -} -MONSTERINFO_WALK(hover_walk) (edict_t* self) -> void { M_SetAnimation(self, &hover_move_walk); } -MONSTERINFO_ATTACK(hover_start_attack) (edict_t* self) -> void { M_SetAnimation(self, &hover_move_start_attack); } - -void hover_attack(edict_t* self) -{ - monster_done_dodge(self); - - hover_style_t style(self); - float strafe_chance = 0.5f; - - // FIX: Use the reliable IsDaedalusType helper instead of checking mass. - if (IsDaedalusType(self)) - strafe_chance += 0.1f; - if (style.has_rocket()) - strafe_chance += 0.1f; - if (style.has_grenade()) - strafe_chance -= 0.15f; - - if (frandom() > strafe_chance) { - M_SetAnimation(self, &hover_move_attack1); - self->monsterinfo.attack_state = AS_STRAIGHT; - } else { - if (frandom() <= 0.5f) self->monsterinfo.lefty = !self->monsterinfo.lefty; - M_SetAnimation(self, &hover_move_attack2); - self->monsterinfo.attack_state = AS_SLIDING; - } -} - -PAIN(hover_pain) (edict_t* self, edict_t* other, float kick, int damage, const mod_t& mod) -> void -{ - if (level.time < self->pain_debounce_time) return; - self->pain_debounce_time = level.time + 3_sec; - - // FIX: Use the reliable IsDaedalusType helper instead of checking mass. - if (!IsDaedalusType(self)) { - if (frandom() < 0.5f) gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); - else gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); - } else { - if (frandom() < 0.5f) gi.sound(self, CHAN_VOICE, daed_sound_pain1, 1, ATTN_NORM, 0); - else gi.sound(self, CHAN_VOICE, daed_sound_pain2, 1, ATTN_NORM, 0); - } - - if (!M_ShouldReactToPain(self, mod)) return; - - // Apply knockback velocity away from damage source - if (other && other->inuse) - { - vec3_t knockback_dir = (self->s.origin - other->s.origin).normalized(); - float knockback_strength = min(200.f, 50.f + damage * 2.f); - self->velocity += knockback_dir * knockback_strength; - } - - if (damage <= 25) { - if (frandom() < 0.5f) M_SetAnimation(self, &hover_move_pain3); - else M_SetAnimation(self, &hover_move_pain2); - } else { - if (frandom() < 0.3f) M_SetAnimation(self, &hover_move_pain1); - else M_SetAnimation(self, &hover_move_pain2); - } -} - -MONSTERINFO_SETSKIN(hover_setskin) (edict_t* self) -> void -{ - if (self->health < (self->max_health / 2)) self->s.skinnum |= 1; - else self->s.skinnum &= ~1; -} - -void hover_dead(edict_t* self) -{ - self->mins = { -16, -16, -24 }; - self->maxs = { 16, 16, -8 }; - self->movetype = MOVETYPE_TOSS; - self->think = hover_deadthink; - self->nextthink = level.time + FRAME_TIME_S; - self->timestamp = level.time + 15_sec; - gi.linkentity(self); -} - -DIE(hover_die) (edict_t* self, edict_t* inflictor, edict_t* attacker, int damage, const vec3_t& point, const mod_t& mod) -> void -{ - self->s.effects = EF_NONE; - self->monsterinfo.power_armor_type = IT_NULL; - if (M_CheckGib(self, mod)) { - hover_gib(self); - return; - } - if (self->deadflag) return; - - // FIX: Use the reliable IsDaedalusType helper instead of checking mass. - if (!IsDaedalusType(self)) { - if (frandom() < 0.5f) gi.sound(self, CHAN_VOICE, sound_death1, 1, ATTN_NORM, 0); - else gi.sound(self, CHAN_VOICE, sound_death2, 1, ATTN_NORM, 0); - } else { - if (frandom() < 0.5f) gi.sound(self, CHAN_VOICE, daed_sound_death1, 1, ATTN_NORM, 0); - else gi.sound(self, CHAN_VOICE, daed_sound_death2, 1, ATTN_NORM, 0); - } - - self->deadflag = true; - self->takedamage = true; - M_SetAnimation(self, &hover_move_death1); -} - -static void hover_set_fly_parameters(edict_t* self) -{ - hover_style_t style(self); - - // FIX: Use the reliable IsDaedalusType helper instead of checking mass. - if (!IsDaedalusType(self)) - { - self->monsterinfo.fly_thrusters = false; - self->monsterinfo.fly_acceleration = 20.f; - self->monsterinfo.fly_speed = 270.f; - self->monsterinfo.fly_min_distance = 325.f; - self->monsterinfo.fly_max_distance = 670.f; - } - else // Is a Daedalus - { - self->monsterinfo.fly_thrusters = false; - self->monsterinfo.fly_acceleration = 20.f; - if (style.has_grenade()) { - self->monsterinfo.fly_speed = 320.f; - self->monsterinfo.fly_min_distance = 500.f; - self->monsterinfo.fly_max_distance = 850.f; - } else { // Blaster2 Daedalus - self->monsterinfo.fly_speed = 290.f; - self->monsterinfo.fly_min_distance = 400.f; - self->monsterinfo.fly_max_distance = 850.f; - } - } -} - -/*QUAKED monster_hover (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight -This is the master spawn function for all hover variants. -*/ -void SP_monster_hover(edict_t* self) -{ - // FIX: This is the new central logic. It sets the monster_type_id from the classname - // if it hasn't been set already. This makes the system work for all spawn methods. - if (self->monsterinfo.monster_type_id == MONSTER_TYPE_UNKNOWN) // Check if it hasn't been set yet - self->monsterinfo.monster_type_id = static_cast(horde::MonsterTypeID::HOVER); - - const spawn_temp_t& st = ED_GetSpawnTemp(); - - if (!M_AllowSpawn(self)) { - G_FreeEdict(self); - return; - } - - self->movetype = MOVETYPE_STEP; - self->solid = SOLID_BBOX; - self->s.modelindex = gi.modelindex("models/monsters/hover/tris.md2"); - gi.modelindex("models/monsters/hover/gibs/chest.md2"); - gi.modelindex("models/monsters/hover/gibs/foot.md2"); - gi.modelindex("models/monsters/hover/gibs/head.md2"); - gi.modelindex("models/monsters/hover/gibs/ring.md2"); - - self->mins = { -24, -24, -24 }; - self->maxs = { 24, 24, 32 }; - - int base_health = M_HOVER_INITIAL_HEALTH; - if (g_horde && g_horde->integer && current_wave_level > 0) { - bool is_boss = self->monsterinfo.IS_BOSS && !self->monsterinfo.BOSS_DEATH_HANDLED; - self->health = ScaleMonsterHealth(base_health, current_wave_level, is_boss); - } else { - self->health = base_health * st.health_multiplier; - } - self->gib_health = -100; - - // Only set mass if it hasn't been set already (for non-Daedalus types). - if (self->mass <= 150) { - self->mass = 150; - } - - // Power armor configuration - if (!st.was_key_specified("power_armor_type") && M_HOVER_POWER_ARMOR_TYPE != IT_NULL) { - self->monsterinfo.power_armor_type = static_cast(M_HOVER_POWER_ARMOR_TYPE); - if (!st.was_key_specified("power_armor_power")) - self->monsterinfo.power_armor_power = M_HOVER_ADDON_POWER_ARMOR(self); - } - - // Regular armor configuration - if (!st.was_key_specified("armor_type") && M_HOVER_INITIAL_ARMOR > 0) { - self->monsterinfo.armor_type = IT_ARMOR_COMBAT; - if (!st.was_key_specified("armor_power")) - self->monsterinfo.armor_power = M_HOVER_ADDON_ARMOR(self); - } - - self->pain = hover_pain; - self->die = hover_die; - self->s.scale = 1.15f; - self->monsterinfo.stand = hover_stand; - self->monsterinfo.walk = hover_walk; - self->monsterinfo.run = hover_run; - self->monsterinfo.attack = hover_start_attack; - self->monsterinfo.sight = hover_sight; - self->monsterinfo.search = hover_search; - self->monsterinfo.setskin = hover_setskin; - - // Standard hover sound setup - self->yaw_speed = 18; - sound_pain1.assign("hover/hovpain1.wav"); - sound_pain2.assign("hover/hovpain2.wav"); - sound_death1.assign("hover/hovdeth1.wav"); - sound_death2.assign("hover/hovdeth2.wav"); - sound_sight.assign("hover/hovsght1.wav"); - sound_search1.assign("hover/hovsrch1.wav"); - sound_search2.assign("hover/hovsrch2.wav"); - gi.soundindex("hover/hovatck1.wav"); - self->monsterinfo.engine_sound = gi.soundindex("hover/hovidle1.wav"); - - gi.linkentity(self); - M_SetAnimation(self, &hover_move_stand); - self->monsterinfo.scale = MODEL_SCALE; - flymonster_start(self); - self->monsterinfo.aiflags |= AI_ALTERNATE_FLY; - hover_set_fly_parameters(self); - ApplyMonsterBonusFlags(self); -} - -// FIX: This function is for the "monster_hover" classname (Rocket Hover). -// It just needs to call the master spawn function. -/*QUAKED monster_hover (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight -This is now the rocket variant. -*/ -// The engine links "monster_hover" to SP_monster_hover, so we don't need a separate function. - -// FIX: This function is for the "monster_hover_vanilla" classname (Blaster Hover). -/*QUAKED monster_hover_vanilla (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight -*/ -void SP_monster_hover_vanilla(edict_t* self) -{ - const spawn_temp_t& st = ED_GetSpawnTemp(); - - self->monsterinfo.monster_type_id = static_cast(horde::MonsterTypeID::HOVER_VANILLA); SP_monster_hover(self); - - // Power armor configuration - if (!st.was_key_specified("power_armor_type") && M_HOVER_VANILLA_POWER_ARMOR_TYPE != IT_NULL) { - self->monsterinfo.power_armor_type = static_cast(M_HOVER_VANILLA_POWER_ARMOR_TYPE); - if (!st.was_key_specified("power_armor_power")) - self->monsterinfo.power_armor_power = M_HOVER_VANILLA_ADDON_POWER_ARMOR(self); - } - - // Regular armor configuration - if (!st.was_key_specified("armor_type") && M_HOVER_VANILLA_INITIAL_ARMOR > 0) { - self->monsterinfo.armor_type = IT_ARMOR_COMBAT; - if (!st.was_key_specified("armor_power")) - self->monsterinfo.armor_power = M_HOVER_VANILLA_ADDON_ARMOR(self); - } - -} - -// FIX: This function is for the "monster_daedalus" classname (Blaster2 Daedalus). -/*QUAKED monster_daedalus (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight -*/ -void SP_monster_daedalus(edict_t* self) -{ - const spawn_temp_t& st = ED_GetSpawnTemp(); - - if (self->monsterinfo.monster_type_id == MONSTER_TYPE_UNKNOWN) // Check if it hasn't been set yet - self->monsterinfo.monster_type_id = static_cast(horde::MonsterTypeID::DAEDALUS); - - if (!M_AllowSpawn(self)) { - G_FreeEdict(self); - return; - } - - int base_health = M_DAEDALUS_INITIAL_HEALTH; - if (g_horde && g_horde->integer && current_wave_level > 0) { - bool is_boss = self->monsterinfo.IS_BOSS && !self->monsterinfo.BOSS_DEATH_HANDLED; - self->health = ScaleMonsterHealth(base_health, current_wave_level, is_boss); - } else { - self->health = base_health * st.health_multiplier; - } - self->s.skinnum = 2; - // Set properties common to ALL Daedalus types - self->mass = 225; - self->yaw_speed = 23; - // Power armor configuration - if (!st.was_key_specified("power_armor_type") && M_DAEDALUS_POWER_ARMOR_TYPE != IT_NULL) { - self->monsterinfo.power_armor_type = static_cast(M_DAEDALUS_POWER_ARMOR_TYPE); - if (!st.was_key_specified("power_armor_power")) - self->monsterinfo.power_armor_power = M_DAEDALUS_ADDON_POWER_ARMOR(self); - } - - // Regular armor configuration - if (!st.was_key_specified("armor_type") && M_DAEDALUS_INITIAL_ARMOR > 0) { - self->monsterinfo.armor_type = IT_ARMOR_COMBAT; - if (!st.was_key_specified("armor_power")) - self->monsterinfo.armor_power = M_DAEDALUS_ADDON_ARMOR(self); - } - - daed_sound_pain1.assign("daedalus/daedpain1.wav"); - daed_sound_pain2.assign("daedalus/daedpain2.wav"); - daed_sound_death1.assign("daedalus/daeddeth1.wav"); - daed_sound_death2.assign("daedalus/daeddeth2.wav"); - daed_sound_sight.assign("daedalus/daedsght1.wav"); - daed_sound_search1.assign("daedalus/daedsrch1.wav"); - daed_sound_search2.assign("daedalus/daedsrch2.wav"); - - // Now call the master hover spawn function. - SP_monster_hover(self); -} - -// FIX: This function is for the "monster_daedalus_bomber" classname (Grenade Daedalus). -/*QUAKED monster_daedalus_bomber (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight -*/ -void SP_monster_daedalus_bomber(edict_t* self) -{ - const spawn_temp_t &st = ED_GetSpawnTemp(); - - self->monsterinfo.monster_type_id = static_cast(horde::MonsterTypeID::DAEDALUS_BOMBER); // A grenade Daedalus IS a Daedalus. Call its spawn function first - // to set up mass, sounds, power armor, etc. - SP_monster_daedalus(self); - - // Power armor configuration - if (!st.was_key_specified("power_armor_type") && M_DAEDALUS_BOMBER_POWER_ARMOR_TYPE != IT_NULL) { - self->monsterinfo.power_armor_type = static_cast(M_DAEDALUS_BOMBER_POWER_ARMOR_TYPE); - if (!st.was_key_specified("power_armor_power")) - self->monsterinfo.power_armor_power = M_DAEDALUS_BOMBER_ADDON_POWER_ARMOR(self); - } - - // Regular armor configuration - if (!st.was_key_specified("armor_type") && M_DAEDALUS_BOMBER_INITIAL_ARMOR > 0) { - self->monsterinfo.armor_type = IT_ARMOR_COMBAT; - if (!st.was_key_specified("armor_power")) - self->monsterinfo.armor_power = M_DAEDALUS_BOMBER_ADDON_ARMOR(self); - } - - int base_health = M_DAEDALUS_BOMBER_INITIAL_HEALTH; - if (g_horde && g_horde->integer && current_wave_level > 0) { - bool is_boss = self->monsterinfo.IS_BOSS && !self->monsterinfo.BOSS_DEATH_HANDLED; - self->health = ScaleMonsterHealth(base_health, current_wave_level, is_boss); - } else { - self->health = base_health * st.health_multiplier; - } - // The classname is still "monster_daedalus_bomber", so when SP_monster_hover - // is eventually called, it will get the correct ID from the registry. -} \ No newline at end of file diff --git a/to add/m_redmutant.cpp b/to add/m_redmutant.cpp deleted file mode 100644 index 790339e9..00000000 --- a/to add/m_redmutant.cpp +++ /dev/null @@ -1,786 +0,0 @@ -// Copyright (c) ZeniMax Media Inc. -// Licensed under the GNU General Public License 2.0. -/* -============================================================================== - -mutant - -============================================================================== -*/ - -#include "g_local.h" -#include "m_redmutant.h" -#include "shared.h" -#include "horde/g_horde_scaling.h" -#include "monster_constants.h" - -constexpr spawnflags_t SPAWNFLAG_REDMUTANT_NOJUMPING = 8_spawnflag; - -static cached_soundindex sound_swing; -static cached_soundindex sound_hit; -static cached_soundindex sound_hit2; -static cached_soundindex sound_death; -static cached_soundindex sound_idle; -static cached_soundindex sound_pain1; -static cached_soundindex sound_pain2; -static cached_soundindex sound_sight; -static cached_soundindex sound_search; -static cached_soundindex sound_step1; -static cached_soundindex sound_step2; -static cached_soundindex sound_step3; -static cached_soundindex sound_thud; - -// -// SOUNDS -// - -void redmutant_step(edict_t* self) -{ - int const n = irandom(3); - if (n == 0) - gi.sound(self, CHAN_BODY, sound_step1, 1, ATTN_NORM, 0); - else if (n == 1) - gi.sound(self, CHAN_BODY, sound_step2, 1, ATTN_NORM, 0); - else - gi.sound(self, CHAN_BODY, sound_step3, 1, ATTN_NORM, 0); -} - -MONSTERINFO_SIGHT(redmutant_sight) (edict_t* self, edict_t* other) -> void -{ - gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); -} - -MONSTERINFO_SEARCH(redmutant_search) (edict_t* self) -> void -{ - gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); -} - -void redmutant_swing(edict_t* self) -{ - gi.sound(self, CHAN_VOICE, sound_swing, 1, ATTN_NORM, 0); -} - -// -// STAND -// - -mframe_t redmutant_frames_stand[] = { - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand } -}; -MMOVE_T(redmutant_move_stand) = { FRAME_stand101, FRAME_stand112, redmutant_frames_stand, nullptr }; - -MONSTERINFO_STAND(redmutant_stand) (edict_t* self) -> void -{ - M_SetAnimation(self, &redmutant_move_stand); -} - -// -// IDLE -// - -void redmutant_idle_loop(edict_t* self) -{ - if (frandom() < 0.75f) - self->monsterinfo.nextframe = FRAME_stand201; -} - -mframe_t redmutant_frames_idle[] = { - { ai_stand, 0, redmutant_idle_loop }, // scratch loop end - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand } -}; -MMOVE_T(redmutant_move_idle) = { FRAME_stand202, FRAME_stand228, redmutant_frames_idle, redmutant_stand }; - -MONSTERINFO_IDLE(redmutant_idle) (edict_t* self) -> void -{ - M_SetAnimation(self, &redmutant_move_idle); - gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); -} - -// -// WALK -// - -mframe_t redmutant_frames_walk[] = { - { ai_walk, 3 }, - { ai_walk, 1 }, - { ai_walk, 5 }, - { ai_walk, 10 }, - { ai_walk, 13 }, - { ai_walk, 10 }, - { ai_walk }, - { ai_walk, 5 }, - { ai_walk, 6 }, - { ai_walk, 16 }, - { ai_walk, 15 }, - { ai_walk, 6 } -}; -MMOVE_T(redmutant_move_walk) = { FRAME_walk05, FRAME_walk16, redmutant_frames_walk, nullptr }; - -void redmutant_walk_loop(edict_t* self) -{ - M_SetAnimation(self, &redmutant_move_walk); -} - -mframe_t redmutant_frames_start_walk[] = { - { ai_walk, 5 }, - { ai_walk, 5 }, - { ai_walk, -2 }, - { ai_walk, 1 } -}; -MMOVE_T(redmutant_move_start_walk) = { FRAME_walk01, FRAME_walk04, redmutant_frames_start_walk, redmutant_walk_loop }; - -MONSTERINFO_WALK(redmutant_walk) (edict_t* self) -> void -{ - M_SetAnimation(self, &redmutant_move_start_walk); -} - -// -// RUN -// - -mframe_t redmutant_frames_run[] = { - { ai_run, 44 }, - { ai_run, 44, redmutant_step }, - { ai_run, 28 }, - { ai_run, 8, redmutant_step }, - { ai_run, 22 }, - { ai_run, 15 } -}; -MMOVE_T(redmutant_move_run) = { FRAME_run03, FRAME_run08, redmutant_frames_run, nullptr }; - -MONSTERINFO_RUN(redmutant_run) (edict_t* self) -> void -{ - if (self->monsterinfo.aiflags & AI_STAND_GROUND) - M_SetAnimation(self, &redmutant_move_stand); - else - M_SetAnimation(self, &redmutant_move_run); -} - -// -// MELEE -// - -void redmutant_hit_left(edict_t* self) -{ - if (!M_HasValidTarget(self)) - { - self->monsterinfo.melee_debounce_time = level.time + 1.5_sec; - return; - } - - int damage = M_GET_DMG_OR(self, MELEE, 15); - - vec3_t const aim = { MELEE_DISTANCE, self->mins[0], 8 }; - if (fire_hit(self, aim, damage * M_DamageModifier(self), 100)) - gi.sound(self, CHAN_WEAPON, sound_hit, 1, ATTN_NORM, 0); - else - { - gi.sound(self, CHAN_WEAPON, sound_swing, 1, ATTN_NORM, 0); - self->monsterinfo.melee_debounce_time = level.time + 1.5_sec; - } -} - -void redmutant_hit_right(edict_t* self) -{ - if (!M_HasValidTarget(self)) - { - self->monsterinfo.melee_debounce_time = level.time + 1.5_sec; - return; - } - - int damage = M_GET_DMG_OR(self, MELEE, 15); - - vec3_t const aim = { MELEE_DISTANCE, self->maxs[0], 8 }; - if (fire_hit(self, aim, damage * M_DamageModifier(self), 100)) - gi.sound(self, CHAN_WEAPON, sound_hit2, 1, ATTN_NORM, 0); - else - { - gi.sound(self, CHAN_WEAPON, sound_swing, 1, ATTN_NORM, 0); - self->monsterinfo.melee_debounce_time = level.time + 1.5_sec; - } -} - -void redmutant_check_refire(edict_t* self) -{ - if (!self->enemy || !self->enemy->inuse || self->enemy->health <= 0) - return; - - if ((self->monsterinfo.melee_debounce_time <= level.time) && ((frandom() < 0.5f) || (range_to(self, self->enemy) <= RANGE_MELEE))) - self->monsterinfo.nextframe = FRAME_attack109; -} - -mframe_t redmutant_frames_attack[] = { - { ai_charge }, - { ai_charge, 0, redmutant_hit_left }, - { ai_charge, 0, redmutant_hit_right }, - { ai_charge }, - { ai_charge, 0, redmutant_hit_right }, - { ai_charge }, - { ai_charge, 0, redmutant_check_refire } -}; -MMOVE_T(redmutant_move_attack) = { FRAME_attack109, FRAME_attack115, redmutant_frames_attack, redmutant_run }; - -MONSTERINFO_MELEE(redmutant_melee) (edict_t* self) -> void -{ - M_SetAnimation(self, &redmutant_move_attack); -} - -// -// ATTACK -// - -TOUCH(redmutant_jump_touch) (edict_t* self, edict_t* other, const trace_t& tr, bool other_touching_self) -> void -{ - if (self->health <= 0) - { - self->touch = nullptr; - return; - } - - if (self->style == 1 && other->takedamage) - { - // [Paril-KEX] only if we're actually moving fast enough to hurt - if (self->velocity.length() > 30) - { - vec3_t point; - vec3_t normal; - int damage; - - normal = self->velocity; - normal.normalize(); - point = self->s.origin + (normal * self->maxs[0]); - damage = (int)frandom(70, 80); - T_Damage(other, self, self, self->velocity, point, normal, damage, damage, DAMAGE_NONE, MOD_UNKNOWN); - self->style = 0; - } - } - - if (!M_CheckBottom(self)) - { - if (self->groundentity) - { - self->monsterinfo.nextframe = FRAME_attack102; - self->touch = nullptr; - } - return; - } - - self->touch = nullptr; -} - -void redmutant_jump_takeoff(edict_t* self) -{ - vec3_t forward; - - gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); - AngleVectors(self->s.angles, forward, nullptr, nullptr); - self->s.origin[2] += 1; - self->velocity = forward * 1125; - self->velocity[2] = 160; - self->groundentity = nullptr; - self->monsterinfo.aiflags |= AI_DUCKED; - self->monsterinfo.attack_finished = level.time + 1.3_sec; - self->style = 1; - self->touch = redmutant_jump_touch; -} - -void redmutant_check_landing(edict_t* self) -{ - monster_jump_finished(self); - - if (self->groundentity) - { - gi.sound(self, CHAN_WEAPON, sound_thud, 1, ATTN_NORM, 0); - self->monsterinfo.attack_finished = level.time + random_time(500_ms, 1.5_sec); - - if (self->monsterinfo.unduck) - self->monsterinfo.unduck(self); - - if (range_to(self, self->enemy) <= RANGE_MELEE * 2.f) - self->monsterinfo.melee(self); - - return; - } - - if (level.time > self->monsterinfo.attack_finished) - self->monsterinfo.nextframe = FRAME_attack101; - else - self->monsterinfo.nextframe = FRAME_attack108; -} - -mframe_t redmutant_frames_jump[] = { - { ai_charge }, - { ai_charge, 17 }, - { ai_charge, 15, redmutant_jump_takeoff }, - { ai_charge, 15 }, - { ai_charge, 15, redmutant_check_landing }, - { ai_charge }, - { ai_charge, 3 }, - { ai_charge } -}; -MMOVE_T(redmutant_move_jump) = { FRAME_attack101, FRAME_attack108, redmutant_frames_jump, redmutant_run }; - -MONSTERINFO_ATTACK(redmutant_jump) (edict_t* self) -> void -{ - M_SetAnimation(self, &redmutant_move_jump); -} - -// -// CHECKATTACK -// - -bool redmutant_check_melee(edict_t* self) -{ - return range_to(self, self->enemy) <= RANGE_MELEE && self->monsterinfo.melee_debounce_time <= level.time; -} - -bool redmutant_check_jump(edict_t* self) -{ - if (!M_HasValidTarget(self)) - { - return false; // Can't at a non-existent or dead target. - } - - vec3_t v; - - // Paril: no harm in letting them jump down if you're below them - // if (self->absmin[2] > (self->enemy->absmin[2] + 0.75 * self->enemy->size[2])) - // return false; - - // don't jump if there's no way we can reach standing height - if (self->absmin[2] + 125 < self->enemy->absmin[2]) - return false; - - v[0] = self->s.origin[0] - self->enemy->s.origin[0]; - v[1] = self->s.origin[1] - self->enemy->s.origin[1]; - v[2] = 0; - float const distance_sq = v.lengthSquared(); - - // if we're not trying to avoid a melee, then don't jump - if (distance_sq < 10000.f && self->monsterinfo.melee_debounce_time <= level.time) // 100^2 - return false; - // only use it to close distance gaps - if (distance_sq > 70225.f) // 265^2 - return false; - - return self->monsterinfo.attack_finished < level.time && brandom(); -} - -MONSTERINFO_CHECKATTACK(redmutant_checkattack) (edict_t* self) -> bool -{ - if (!self->enemy || self->enemy->health <= 0) - return false; - - if (redmutant_check_melee(self)) - { - self->monsterinfo.attack_state = AS_MELEE; - return true; - } - - if (!self->spawnflags.has(SPAWNFLAG_REDMUTANT_NOJUMPING) && redmutant_check_jump(self)) - { - self->monsterinfo.attack_state = AS_MISSILE; - return true; - } - - return false; -} - -// -// PAIN -// - -mframe_t redmutant_frames_pain1[] = { - { ai_move, 4 }, - { ai_move, -3 }, - { ai_move, -8 }, - { ai_move, 2 }, - { ai_move, 5 } -}; -MMOVE_T(redmutant_move_pain1) = { FRAME_pain101, FRAME_pain105, redmutant_frames_pain1, redmutant_run }; - -mframe_t redmutant_frames_pain2[] = { - { ai_move, -24 }, - { ai_move, 11 }, - { ai_move, 5 }, - { ai_move, -2 }, - { ai_move, 6 }, - { ai_move, 4 } -}; -MMOVE_T(redmutant_move_pain2) = { FRAME_pain201, FRAME_pain206, redmutant_frames_pain2, redmutant_run }; - -mframe_t redmutant_frames_pain3[] = { - { ai_move, -22 }, - { ai_move, 3 }, - { ai_move, 3 }, - { ai_move, 2 }, - { ai_move, 1 }, - { ai_move, 1 }, - { ai_move, 6 }, - { ai_move, 3 }, - { ai_move, 2 }, - { ai_move }, - { ai_move, 1 } -}; -MMOVE_T(redmutant_move_pain3) = { FRAME_pain301, FRAME_pain311, redmutant_frames_pain3, redmutant_run }; - -PAIN(redmutant_pain) (edict_t* self, edict_t* other, float kick, int damage, const mod_t& mod) -> void -{ - float r; - - if (level.time < self->pain_debounce_time) - return; - - self->pain_debounce_time = level.time + 3_sec; - - r = frandom(); - if (r < 0.33f) - gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); - else if (r < 0.66f) - gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); - else - gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); - - if (!M_ShouldReactToPain(self, mod)) - return; // no pain anims in nightmare - - if (r < 0.33f) - M_SetAnimation(self, &redmutant_move_pain1); - else if (r < 0.66f) - M_SetAnimation(self, &redmutant_move_pain2); - else - M_SetAnimation(self, &redmutant_move_pain3); -} - -MONSTERINFO_SETSKIN(redmutant_setskin) (edict_t* self) -> void -{ -} - -// -// DEATH -// - -void redmutant_shrink(edict_t* self) -{ - self->maxs[2] = 0; - self->svflags |= SVF_DEADMONSTER; - gi.linkentity(self); -} - -// [Paril-KEX] -static void ai_move_slide_right(edict_t* self, float dist) -{ - M_walkmove(self, self->s.angles[YAW] + 90, dist); -} - -static void ai_move_slide_left(edict_t* self, float dist) -{ - M_walkmove(self, self->s.angles[YAW] - 90, dist); -} - -mframe_t redmutant_frames_death1[] = { - { ai_move_slide_right }, - { ai_move_slide_right }, - { ai_move_slide_right }, - { ai_move_slide_right, 2 }, - { ai_move_slide_right, 5 }, - { ai_move_slide_right, 7, redmutant_shrink }, - { ai_move_slide_right, 6 }, - { ai_move_slide_right, 2 }, - { ai_move_slide_right }, - { ai_move_slide_right }, - { ai_move_slide_right }, - { ai_move_slide_right }, - { ai_move_slide_right }, - { ai_move_slide_right }, - { ai_move_slide_right }, - { ai_move_slide_right }, - { ai_move_slide_right }, - { ai_move_slide_right }, - { ai_move_slide_right }, - { ai_move_slide_right } -}; -MMOVE_T(redmutant_move_death1) = { FRAME_death101, FRAME_death120, redmutant_frames_death1, monster_dead }; - -mframe_t redmutant_frames_death2[] = { - { ai_move_slide_left }, - { ai_move_slide_left, 1 }, - { ai_move_slide_left, 6 }, - { ai_move_slide_left, 8 }, - { ai_move_slide_left, 3, redmutant_shrink }, - { ai_move_slide_left, 2 }, - { ai_move_slide_left } -}; -MMOVE_T(redmutant_move_death2) = { FRAME_death201, FRAME_death207, redmutant_frames_death2, monster_dead }; - -DIE(redmutant_die) (edict_t* self, edict_t* inflictor, edict_t* attacker, int damage, const vec3_t& point, const mod_t& mod) -> void -{ - //OnEntityDeath(self); - if (M_CheckGib(self, mod)) - { - gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); - - self->s.skinnum /= 2; - - ThrowGibs(self, damage, { - { 2, "models/objects/gibs/bone/tris.md2" }, - { 4, "models/objects/gibs/sm_meat/tris.md2" }, - { 2, "models/monsters/mutant/gibs/hand.md2", GIB_SKINNED | GIB_UPRIGHT }, - { 2, "models/monsters/mutant/gibs/foot.md2", GIB_SKINNED }, - { "models/monsters/mutant/gibs/chest.md2", GIB_SKINNED }, - { "models/monsters/mutant/gibs/head.md2", GIB_SKINNED | GIB_HEAD } - }); - - self->deadflag = true; - return; - } - - if (self->deadflag) - return; - - gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); - self->deadflag = true; - self->takedamage = true; - - if (frandom() < 0.5f) - M_SetAnimation(self, &redmutant_move_death1); - else - M_SetAnimation(self, &redmutant_move_death2); -} - -//================ -// ROGUE -void redmutant_jump_down(edict_t* self) -{ - vec3_t forward, up; - - AngleVectors(self->s.angles, forward, nullptr, up); - self->velocity += (forward * 100); - self->velocity += (up * 300); -} - -void redmutant_jump_up(edict_t* self) -{ - vec3_t forward, up; - - AngleVectors(self->s.angles, forward, nullptr, up); - self->velocity += (forward * 200); - self->velocity += (up * 450); -} - -void redmutant_jump_wait_land(edict_t* self) -{ - if (!monster_jump_finished(self) && self->groundentity == nullptr) - self->monsterinfo.nextframe = self->s.frame; - else - self->monsterinfo.nextframe = self->s.frame + 1; -} - -mframe_t redmutant_frames_jump_up[] = { - { ai_move, -8 }, - { ai_move, -8, redmutant_jump_up }, - { ai_move, 0, redmutant_jump_wait_land }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move } -}; -MMOVE_T(redmutant_move_jump_up) = { FRAME_attack101, FRAME_attack108, redmutant_frames_jump_up, redmutant_run }; - -mframe_t redmutant_frames_jump_down[] = { - { ai_move }, - { ai_move, 0, redmutant_jump_down }, - { ai_move, 0, redmutant_jump_wait_land }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move } -}; -MMOVE_T(redmutant_move_jump_down) = { FRAME_attack101, FRAME_attack108, redmutant_frames_jump_down, redmutant_run }; - -void redmutant_jump_updown(edict_t* self, blocked_jump_result_t result) -{ - if (!self->enemy) - return; - - if (result == blocked_jump_result_t::JUMP_JUMP_UP) - M_SetAnimation(self, &redmutant_move_jump_up); - else - M_SetAnimation(self, &redmutant_move_jump_down); -} - -/* -=== -Blocked -=== -*/ -MONSTERINFO_BLOCKED(redmutant_blocked) (edict_t* self, float dist) -> bool -{ - if (auto const result = blocked_checkjump(self, dist); result != blocked_jump_result_t::NO_JUMP) - { - if (result != blocked_jump_result_t::JUMP_TURN) - redmutant_jump_updown(self, result); - return true; - } - - if (blocked_checkplat(self, dist)) - return true; - - return false; -} -// ROGUE -//================ - -// -// SPAWN -// - -/*QUAKED monster_redmutant (1 .5 0) (-32 -32 -24) (32 32 32) Ambush Trigger_Spawn Sight NoJumping -model="models/monsters/redmutant/tris.md2" -*/ -void SP_monster_redmutant(edict_t* self) -{ - const spawn_temp_t& st = ED_GetSpawnTemp(); - - - self->monsterinfo.monster_type_id = static_cast(horde::MonsterTypeID::REDMUTANT); if (g_horde->integer) - { - const float randomsearch = frandom(); // Generar un número aleatorio entre 0 y 1 - - if (randomsearch < 0.32f) - gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); - } - - if (!M_AllowSpawn(self)) { - G_FreeEdict(self); - return; - } - - sound_swing.assign("mutant/mutatck1.wav"); - sound_hit.assign("mutant/mutatck2.wav"); - sound_hit2.assign("mutant/mutatck3.wav"); - sound_death.assign("mutant/mutdeth1.wav"); - sound_idle.assign("mutant/mutidle1.wav"); - sound_pain1.assign("mutant/mutpain1.wav"); - sound_pain2.assign("mutant/mutpain2.wav"); - sound_sight.assign("mutant/mutsght1.wav"); - sound_search.assign("mutant/mutsrch1.wav"); - sound_step1.assign("mutant/step1.wav"); - sound_step2.assign("mutant/step2.wav"); - sound_step3.assign("mutant/step3.wav"); - sound_thud.assign("mutant/thud1.wav"); - - self->monsterinfo.aiflags |= AI_STINKY; - - self->movetype = MOVETYPE_STEP; - self->solid = SOLID_BBOX; - self->s.modelindex = gi.modelindex("models/vault/monsters/mutant/tris.md2"); - - gi.modelindex("models/monsters/mutant/gibs/head.md2"); - gi.modelindex("models/monsters/mutant/gibs/chest.md2"); - gi.modelindex("models/monsters/mutant/gibs/hand.md2"); - gi.modelindex("models/monsters/mutant/gibs/foot.md2"); - - self->mins = { -18, -18, -24 }; - self->maxs = { 18, 18, 30 }; - - // Power armor configuration - if (!st.was_key_specified("power_armor_type") && M_REDMUTANT_POWER_ARMOR_TYPE != IT_NULL) { - self->monsterinfo.power_armor_type = static_cast(M_REDMUTANT_POWER_ARMOR_TYPE); - if (!st.was_key_specified("power_armor_power")) - self->monsterinfo.power_armor_power = M_REDMUTANT_ADDON_POWER_ARMOR(self); - } - - // Regular armor configuration - if (!st.was_key_specified("armor_type") && M_REDMUTANT_INITIAL_ARMOR > 0) { - self->monsterinfo.armor_type = IT_ARMOR_COMBAT; - if (!st.was_key_specified("armor_power")) - self->monsterinfo.armor_power = M_REDMUTANT_ADDON_ARMOR(self); - } - - - int base_health = M_REDMUTANT_INITIAL_HEALTH; - if (g_horde && g_horde->integer && current_wave_level > 0) { - self->health = ScaleMonsterHealth(base_health, current_wave_level, false); - } else { - self->health = base_health * st.health_multiplier; - } - self->gib_health = -120; - self->mass = 450; - - if (self->monsterinfo.IS_BOSS && !self->monsterinfo.BOSS_DEATH_HANDLED) { - self->health *= 3.8f; - self->gib_health = -999777; - self->mass *= 3.0f; - } - - self->pain = redmutant_pain; - self->die = redmutant_die; - - self->monsterinfo.stand = redmutant_stand; - self->monsterinfo.walk = redmutant_walk; - self->monsterinfo.run = redmutant_run; - self->monsterinfo.dodge = nullptr; - self->monsterinfo.attack = redmutant_jump; - self->monsterinfo.melee = redmutant_melee; - self->monsterinfo.sight = redmutant_sight; - self->monsterinfo.search = redmutant_search; - self->monsterinfo.idle = redmutant_idle; - self->monsterinfo.checkattack = redmutant_checkattack; - self->monsterinfo.blocked = redmutant_blocked; // PGM - self->monsterinfo.setskin = redmutant_setskin; - - gi.linkentity(self); - - M_SetAnimation(self, &redmutant_move_stand); - - self->monsterinfo.combat_style = COMBAT_MELEE; - - self->monsterinfo.scale = MODEL_SCALE; - self->monsterinfo.can_jump = !(self->spawnflags & SPAWNFLAG_REDMUTANT_NOJUMPING); - self->monsterinfo.drop_height = 256; - // HORDE MOD: Increased jump height from 68 to 88 (30% increase) for better obstacle navigation - self->monsterinfo.jump_height = 88; - - walkmonster_start(self); - - ApplyMonsterBonusFlags(self); -} \ No newline at end of file diff --git a/to add/m_redmutant.h b/to add/m_redmutant.h deleted file mode 100644 index b8089e95..00000000 --- a/to add/m_redmutant.h +++ /dev/null @@ -1,156 +0,0 @@ -#pragma once -// Copyright (c) ZeniMax Media Inc. -// Licensed under the GNU General Public License 2.0. -// RedMutant - -// This file generated by ModelGen - Do NOT Modify - -enum -{ - FRAME_attack101, - FRAME_attack102, - FRAME_attack103, - FRAME_attack104, - FRAME_attack105, - FRAME_attack106, - FRAME_attack107, - FRAME_attack108, - FRAME_attack109, - FRAME_attack110, - FRAME_attack111, - FRAME_attack112, - FRAME_attack113, - FRAME_attack114, - FRAME_attack115, - FRAME_attack116, - FRAME_attack117, - FRAME_attack118, - FRAME_attack119, - FRAME_attack120, - FRAME_attack121, - FRAME_attack122, - FRAME_attack123, - FRAME_attack124, - FRAME_attack125, - FRAME_attack126, - FRAME_death101, - FRAME_death102, - FRAME_death103, - FRAME_death104, - FRAME_death105, - FRAME_death106, - FRAME_death107, - FRAME_death108, - FRAME_death109, - FRAME_death110, - FRAME_death111, - FRAME_death112, - FRAME_death113, - FRAME_death114, - FRAME_death115, - FRAME_death116, - FRAME_death117, - FRAME_death118, - FRAME_death119, - FRAME_death120, - FRAME_death201, - FRAME_death202, - FRAME_death203, - FRAME_death204, - FRAME_death205, - FRAME_death206, - FRAME_death207, - FRAME_pain101, - FRAME_pain102, - FRAME_pain103, - FRAME_pain104, - FRAME_pain105, - FRAME_pain201, - FRAME_pain202, - FRAME_pain203, - FRAME_pain204, - FRAME_pain205, - FRAME_pain206, - FRAME_pain301, - FRAME_pain302, - FRAME_pain303, - FRAME_pain304, - FRAME_pain305, - FRAME_pain306, - FRAME_pain307, - FRAME_pain308, - FRAME_pain309, - FRAME_pain310, - FRAME_pain311, - FRAME_run03, - FRAME_run04, - FRAME_run05, - FRAME_run06, - FRAME_run07, - FRAME_run08, - FRAME_stand101, - FRAME_stand102, - FRAME_stand103, - FRAME_stand104, - FRAME_stand105, - FRAME_stand106, - FRAME_stand107, - FRAME_stand108, - FRAME_stand109, - FRAME_stand110, - FRAME_stand111, - FRAME_stand112, - FRAME_stand201, - FRAME_stand202, - FRAME_stand203, - FRAME_stand204, - FRAME_stand205, - FRAME_stand206, - FRAME_stand207, - FRAME_stand208, - FRAME_stand209, - FRAME_stand210, - FRAME_stand211, - FRAME_stand212, - FRAME_stand213, - FRAME_stand214, - FRAME_stand215, - FRAME_stand216, - FRAME_stand217, - FRAME_stand218, - FRAME_stand219, - FRAME_stand220, - FRAME_stand221, - FRAME_stand222, - FRAME_stand223, - FRAME_stand224, - FRAME_stand225, - FRAME_stand226, - FRAME_stand227, - FRAME_stand228, - FRAME_walk01, - FRAME_walk02, - FRAME_walk03, - FRAME_walk04, - FRAME_walk05, - FRAME_walk06, - FRAME_walk07, - FRAME_walk08, - FRAME_walk09, - FRAME_walk10, - FRAME_walk11, - FRAME_walk12, - FRAME_walk13, - FRAME_walk14, - FRAME_walk15, - FRAME_walk16, - FRAME_walk17, - FRAME_walk18, - FRAME_walk19, - FRAME_walk20, - FRAME_walk21, - FRAME_walk22, - FRAME_walk23, -}; - -constexpr float MODEL_SCALE = 1.000000f; diff --git a/to add/m_runnertank.cpp b/to add/m_runnertank.cpp deleted file mode 100644 index 349f9b29..00000000 --- a/to add/m_runnertank.cpp +++ /dev/null @@ -1,1806 +0,0 @@ -// Copyright (c) ZeniMax Media Inc. -// Licensed under the GNU General Public License 2.0. -/* -============================================================================== - -runnertank - -============================================================================== -*/ - -#include "g_local.h" - -#include "m_runnertank.h" - -#include "m_flash.h" -#include "shared.h" -#include "horde/g_horde_scaling.h" -#include "monster_constants.h" -void runnertankStrike(edict_t* self); -void runnertank_refire_rocket(edict_t* self); -//void runnetank_doattack_rocket(edict_t* self); -void runnertank_reattack_blaster(edict_t* self); -void runnertank_attack_finished(edict_t* self); -//bool runnertank_check_wall(edict_t* self, float dist); - -static cached_soundindex sound_thud; -static cached_soundindex sound_pain, sound_pain2; -static cached_soundindex sound_idle; -static cached_soundindex sound_die; -static cached_soundindex sound_step; -static cached_soundindex sound_sight; -static cached_soundindex sound_windup; -static cached_soundindex sound_strike; - -constexpr spawnflags_t SPAWNFLAG_runnertank_COMMANDER_GUARDIAN = 8_spawnflag; -constexpr spawnflags_t SPAWNFLAG_runnertank_COMMANDER_HEAT_SEEKING = 16_spawnflag; - -// -// misc -// - -MONSTERINFO_SIGHT(runnertank_sight) (edict_t* self, edict_t* other) -> void -{ - gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); -} - -void runnertank_footstep(edict_t* self) -{ -brandom() ? gi.sound(self, CHAN_BODY, sound_step, 1.f, ATTN_NORM, 0) - : gi.sound(self, CHAN_BODY, sound_step, 0.75f, ATTN_NORM, 0); -} - -void runnertank_thud(edict_t* self) -{ - gi.sound(self, CHAN_BODY, sound_thud, 1, ATTN_NORM, 0); -} - -void runnertank_windup(edict_t* self) -{ - gi.sound(self, CHAN_WEAPON, sound_windup, 1, ATTN_NORM, 0); -} - -MONSTERINFO_IDLE(runnertank_idle) (edict_t* self) -> void -{ - gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); -} - -// -// stand -// - -mframe_t runnertank_frames_stand[] = { - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand }, - { ai_stand } -}; -MMOVE_T(runnertank_move_stand) = { FRAME_stand01, FRAME_stand30, runnertank_frames_stand, nullptr }; - -MONSTERINFO_STAND(runnertank_stand) (edict_t* self) -> void -{ - M_SetAnimation(self, &runnertank_move_stand); -} - -// -// walk -// - -void runnertank_walk(edict_t* self); - -// Animación de caminata corregida -mframe_t runnertank_frames_walk[] = { - { ai_walk, 4 }, { ai_walk, 5 }, { ai_walk, 3 }, { ai_walk, 2 }, - { ai_walk, 5 }, { ai_walk, 5 }, { ai_walk, 4 }, { ai_walk, 4, runnertank_footstep }, - { ai_walk, 3 }, { ai_walk, 5 }, { ai_walk, 4 }, { ai_walk, 5 }, - { ai_walk, 7 }, { ai_walk, 7 }, { ai_walk, 6 }, { ai_walk, 6, runnertank_footstep }, - { ai_walk, 4 }, { ai_walk, 5 }, { ai_walk, 3 }, { ai_walk, 2 }, - { ai_walk, 5 }, { ai_walk, 5 }, { ai_walk, 4 }, { ai_walk, 4, runnertank_footstep }, - { ai_walk, 3 }, { ai_walk, 5 }, { ai_walk, 4 }, { ai_walk, 5 }, - { ai_walk, 7 }, { ai_walk, 7 }, { ai_walk, 6 }, { ai_walk, 6, runnertank_footstep }, - { ai_walk, 4 }, { ai_walk, 5 }, { ai_walk, 3 }, { ai_walk, 2 }, - { ai_walk, 4 }, { ai_walk, 5 } -}; -MMOVE_T(runnertank_move_walk) = { FRAME_walk01, FRAME_walk38, runnertank_frames_walk, runnertank_walk }; - - -mframe_t runnertank_frames_start_walk[] = { - { ai_walk }, - { ai_walk, 6 }, - { ai_walk, 6 }, - { ai_walk, 11, runnertank_footstep }, - { ai_walk }, - { ai_walk, 6 }, - { ai_walk, 6 }, - { ai_walk, 11, runnertank_footstep } - , { ai_walk }, - { ai_walk, 6 }, - { ai_walk, 6 }, - { ai_walk, 11, runnertank_footstep } - , { ai_walk }, - { ai_walk, 6 }, - { ai_walk, 6 }, - { ai_walk, 11, runnertank_footstep }, - { ai_walk }, - { ai_walk, 6 }, - { ai_walk, 6 }, - { ai_walk, 11, runnertank_footstep } - { ai_walk }, - { ai_walk, 6 } -}; -MMOVE_T(runnertank_move_start_walk) = { FRAME_walk15, FRAME_walk22, runnertank_frames_start_walk, runnertank_walk }; - -mframe_t runnertank_frames_stop_walk[] = { - { ai_walk, 3 }, - { ai_walk, 3 }, - { ai_walk, 2 }, - { ai_walk, 2 }, - { ai_walk, 4, runnertank_footstep } -}; -MMOVE_T(runnertank_move_stop_walk) = { FRAME_walk21, FRAME_walk25, runnertank_frames_stop_walk, runnertank_stand }; - -void runnertank_run(edict_t* self); - -// mframe_t runnertank_frames_start_walk[] = { -// { ai_walk, 0 }, { ai_walk, 3 }, { ai_walk, 3 }, { ai_walk, 3 } -// }; -// MMOVE_T(runnertank_move_start_walk) = { FRAME_walk01, FRAME_walk04, runnertank_frames_start_walk, runnertank_walk }; - -// mframe_t runnertank_frames_stop_walk[] = { -// { ai_walk, 3 }, { ai_walk, 3 }, { ai_walk, 2 }, { ai_walk, 2 }, -// { ai_walk, 0, runnertank_footstep } -// }; -// MMOVE_T(runnertank_move_stop_walk) = { FRAME_walk34, FRAME_walk38, runnertank_frames_stop_walk, runnertank_stand }; - -void runnertank_walk_to_run(edict_t* self); -void runnertank_stop_run_to_attack(edict_t* self); - -//mframe_t runnertank_frames_start_run[] = { -// { ai_run }, -// { ai_run, 6 }, -// { ai_run, 6 }, -// { ai_run, 11, runnertank_footstep } -//}; -//MMOVE_T(runnertank_move_start_run) = { FRAME_walk01, FRAME_walk04, runnertank_frames_start_run, runnertank_run }; - -mframe_t runnertank_frames_start_run[] = { - { ai_run, 6 }, { ai_run, 5 }, { ai_run, 6 }, { ai_run, 7, runnertank_footstep } -}; -MMOVE_T(runnertank_move_start_run) = { FRAME_walk35, FRAME_walk38, runnertank_frames_start_run, runnertank_walk_to_run }; - - -// Ajustar la función de caminata para una transición más suave -MONSTERINFO_WALK(runnertank_walk) (edict_t* self) -> void -{ - if (self->monsterinfo.active_move != &runnertank_move_walk) - { - M_SetAnimation(self, &runnertank_move_start_walk); - } - else - { - M_SetAnimation(self, &runnertank_move_walk); - } -} - -// -// run -// -// -// Actualizar la animación de carrera -mframe_t runnertank_frames_run[] = { - { ai_run, 14, runnertank_footstep }, - { ai_run, 18, nullptr }, - { ai_run, 15, nullptr }, - { ai_run, 15, nullptr }, - { ai_run, 15, nullptr }, - { ai_run, 19, runnertank_footstep }, - { ai_run, 15, nullptr }, - { ai_run, 13, nullptr }, - { ai_run, 18, nullptr }, - { ai_run, 17, nullptr } -}; -MMOVE_T(runnertank_move_run) = { FRAME_run01, FRAME_run10, runnertank_frames_run, nullptr }; - - -mframe_t runnertank_frames_stop_run[] = { - { ai_run, 3 }, - { ai_run, 3 }, - { ai_run, 2 }, - { ai_run, 2 }, - { ai_run, 4, runnertank_footstep } -}; -MMOVE_T(runnertank_move_stop_run) = { FRAME_walk21, FRAME_walk25, runnertank_frames_stop_run, runnertank_stop_run_to_attack }; - -// Función para manejar la transición de caminata a carrera -void runnertank_walk_to_run(edict_t* self) -{ - if (!M_HasValidTarget(self)) - { - return; - } - - if (range_to(self, self->enemy) > RANGE_NEAR) - { - M_SetAnimation(self, &runnertank_move_run); - self->monsterinfo.aiflags |= AI_CHARGING; - } - else - { - M_SetAnimation(self, &runnertank_move_walk); - } -} - - - -bool runnertank_enemy_visible(edict_t* self) -{ - return self->enemy && visible(self, self->enemy); -} - -void runnertank_attack(edict_t* self); -void runnertank_consider_strafe(edict_t* self); - - -// Forward declarations for jump attack -void runnertank_jump_attack_takeoff(edict_t* self); -void runnertank_high_gravity(edict_t* self); -void runnertank_check_jump_landing(edict_t* self); - -mframe_t tank_frames_punch_attack[] = -{ - {ai_charge, 0, nullptr}, - {ai_charge, 0, nullptr}, - {ai_charge, 0, nullptr}, - {ai_charge, 0, nullptr}, - {ai_charge, 0, runnertankStrike}, // FRAME_attak225 - Añadir footstep aquí - {ai_charge, 0, nullptr}, // FRAME_attak226 - Engendrar monstruo aquí - {ai_charge, -1, nullptr}, - {ai_charge, -1, nullptr}, - {ai_charge, -1, nullptr}, - {ai_charge, -1, nullptr}, - {ai_charge, -1, nullptr}, - {ai_charge, -1, nullptr}, - {ai_charge, -1, nullptr}, - {ai_charge, -2, nullptr} // FRAME_attak229 -}; -MMOVE_T(tank_move_punch_attack) = { FRAME_attak222, FRAME_attak235, tank_frames_punch_attack, runnertank_run }; - -// Jump attack animation - using frames that make sense for jumping -mframe_t runnertank_frames_jump_attack[] = -{ - {ai_charge, 15, runnertank_jump_attack_takeoff}, // Launch immediately! - {ai_move, 0, runnertank_high_gravity}, // In air 1 - {ai_move, 0, runnertank_check_jump_landing}, // Check landing (loops here until landed) - {ai_move, 0, nullptr}, // Landing recovery 1 - {ai_move, 0, nullptr} // Recovery 2 -}; -MMOVE_T(runnertank_move_jump_attack) = { FRAME_run01, FRAME_run05, runnertank_frames_jump_attack, runnertank_attack_finished }; - -MONSTERINFO_MELEE(runnertank_melee) (edict_t* self) -> void -{ - if (!M_HasValidTarget(self)) - { - return; - } - - float const range = range_to(self, self->enemy); - if (!visible(self, self->enemy)) - return; - - // Melee is only for close range punch - if (range <= MELEE_DISTANCE * 2.4f) - { - M_SetAnimation(self, &tank_move_punch_attack); - self->monsterinfo.attack_finished = level.time + 1.5_sec; - } -} - -MONSTERINFO_RUN(runnertank_run) (edict_t* self) -> void -{ - if (self->monsterinfo.aiflags & AI_STAND_GROUND) - { - M_SetAnimation(self, &runnertank_move_stand); - return; - } - else - { - M_SetAnimation(self, &runnertank_move_run); - return; - } - - // // ALWAYS set animation first to prevent getting stuck - // if (self->monsterinfo.active_move == &runnertank_move_walk || - // self->monsterinfo.active_move == &runnertank_move_start_walk) - // { - // M_SetAnimation(self, &runnertank_move_run); - // } - // else if (self->monsterinfo.active_move != &runnertank_move_run && - // self->monsterinfo.active_move != &runnertank_move_start_run) - // { - // M_SetAnimation(self, &runnertank_move_start_run); - // } - - // M_SetAnimation(self, &runnertank_move_run) - // // Try to attack if we have a valid target and attack cooldown has expired - // // Attack will override the run animation if successful - // if (M_HasValidTarget(self) && level.time >= self->monsterinfo.attack_finished) - // { - // runnertank_attack(self); - // } -} -// -// pain -// - -mframe_t runnertank_frames_pain1[] = { - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move } -}; -MMOVE_T(runnertank_move_pain1) = { FRAME_pain201, FRAME_pain204, runnertank_frames_pain1, runnertank_run }; - -mframe_t runnertank_frames_pain3[] = { - { ai_move, -7 }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move, 2 }, - { ai_move }, - { ai_move }, - { ai_move, 3 }, - { ai_move }, - { ai_move, 2 }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move, 0, runnertank_footstep } -}; -MMOVE_T(runnertank_move_pain3) = { FRAME_pain301, FRAME_pain316, runnertank_frames_pain3, runnertank_run }; - -PAIN(runnertank_pain) (edict_t* self, edict_t* other, float kick, int damage, const mod_t& mod) -> void -{ - if (mod.id != MOD_CHAINFIST && damage <= 10) - return; - - if (level.time < self->pain_debounce_time) - return; - - if (mod.id != MOD_CHAINFIST) - { - if (damage <= 30) - if (frandom() > 0.2f) - return; - - // don't go into pain while attacking - if ((self->s.frame >= FRAME_attak301) && (self->s.frame <= FRAME_attak330)) - return; - if ((self->s.frame >= FRAME_attak101) && (self->s.frame <= FRAME_attak116)) - return; - } - - self->pain_debounce_time = level.time + 3_sec; - - if (self->count) - gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); - else - gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); - - if (!M_ShouldReactToPain(self, mod)) - return; // no pain anims in nightmare - - // PMM - blindfire cleanup - self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; - // pmm - - if (damage <= 50) - M_SetAnimation(self, &runnertank_move_pain1); - else - M_SetAnimation(self, &runnertank_move_pain3); -} - -MONSTERINFO_SETSKIN(runnertank_setskin) (edict_t* self) -> void -{ - if (self->health < (self->max_health / 2)) - self->s.skinnum |= self->s.skinnum = gi.imageindex("models/monsters/tank/pain.pcx");; - //else - // self->s.skinnum &= ~1; -} - -// [Paril-KEX] -bool M_AdjustBlindfireTarget(edict_t* self, const vec3_t& start, const vec3_t& target, const vec3_t& right, vec3_t& out_dir); - -// -// attacks -// -// Definimos los offsets específicos para cada frame -struct RailOffset { - float x; - float y; - float z; -}; - -const RailOffset RAIL_OFFSETS[] = { - {28.7f, -18.5f, 28.7f}, // FRAME_attak110 - {24.6f, -21.5f, 30.1f}, // FRAME_attak113 - {19.8f, -23.9f, 32.1f} // FRAME_attak116 -}; - -void runnertankRail(edict_t* self) -{ - if (!M_HasEnemy(self)) - { - return; // Stop immediately if the enemy is invalid. - } - - int damage = M_GET_DMG_OR(self, RAILGUN, 45); - - vec3_t forward, right; - vec3_t start; - vec3_t dir; - monster_muzzleflash_id_t flash_number; - const RailOffset* current_offset; - - // Allow blindfire for rail gun - bool const blindfire = self->monsterinfo.aiflags & AI_MANUAL_STEERING; - if (!blindfire) - { - if (!M_HasValidTarget(self)) - return; - - if (!infront(self, self->enemy) || !visible(self, self->enemy)) - return; - } - - // Seleccionamos el offset basado en el frame actual - if (self->s.frame == FRAME_attak110) { - flash_number = MZ2_ARACHNID_RAIL2; - current_offset = &RAIL_OFFSETS[0]; - } - else if (self->s.frame == FRAME_attak113) { - flash_number = MZ2_ARACHNID_RAIL2; - current_offset = &RAIL_OFFSETS[1]; - } - else { // FRAME_attak116 - flash_number = MZ2_ARACHNID_RAIL2; - current_offset = &RAIL_OFFSETS[2]; - } - - AngleVectors(self->s.angles, forward, right, nullptr); - - // Creamos un vector temporal para el offset actual - vec3_t const custom_offset = { - current_offset->x, - current_offset->y, - current_offset->z - }; - - // Usamos el offset personalizado en lugar del monster_flash_offset - start = M_ProjectFlashSource(self, custom_offset, forward, right); - - vec3_t target; - if (blindfire) { - target = self->monsterinfo.blind_fire_target; - if (!M_AdjustBlindfireTarget(self, start, target, right, dir)) - return; - } - else { - // Check if muzzle origin can see enemy before firing - vec3_t target_pos = self->enemy->s.origin; - target_pos[2] += self->enemy->viewheight; - trace_t trace = gi.traceline(start, target_pos, self, MASK_PROJECTILE); - - if (!(trace.fraction > 0.5f || trace.ent->solid != SOLID_BSP)) - return; - - PredictAim(self, self->enemy, start, 0, false, 0.2f, &dir, nullptr); - } - - monster_fire_railgun(self, start, dir, damage, 100, flash_number); -} -void runnertankStrike(edict_t* self) -{ - gi.sound(self, CHAN_WEAPON, sound_strike, 1, ATTN_NORM, 0); - - { - gi.sound(self, CHAN_WEAPON, sound_strike, 1, ATTN_NORM, 0); - - - // Efecto visual similar al berserker - gi.WriteByte(svc_temp_entity); - gi.WriteByte(TE_BERSERK_SLAM); - - vec3_t f, r, start; - AngleVectors(self->s.angles, f, r, nullptr); - start = M_ProjectFlashSource(self, { 20.f, -14.3f, -21.f }, f, r); - trace_t const tr = gi.traceline(self->s.origin, start, self, MASK_SOLID); - - gi.WritePosition(tr.endpos); - gi.WriteDir({ 0.f, 0.f, 1.f }); - gi.multicast(tr.endpos, MULTICAST_PHS, false); - int damage = M_GET_DMG_OR(self, SLAM, 45); - void T_SlamRadiusDamage(vec3_t point, edict_t * inflictor, edict_t * attacker, float damage, float kick, edict_t * ignore, float radius, mod_t mod); - // Daño radial - T_SlamRadiusDamage(tr.endpos, self, self, damage, 450.f, self, 165, MOD_TANK_PUNCH); - - } -} - - - -void runnertankRocket(edict_t* self) { - if (!M_HasEnemy(self)) - { - return; // Stop immediately if the enemy is invalid. - } - - int damage = M_GET_DMG_OR(self, ROCKET, 50); - - // Determinar flash number basado en el frame actual - monster_muzzleflash_id_t const flash_number = static_cast( - self->s.frame == FRAME_attak324 ? MZ2_TANK_ROCKET_1 : - self->s.frame == FRAME_attak327 ? MZ2_TANK_ROCKET_2 : - MZ2_TANK_ROCKET_3); - - // Obtener vectores de dirección usando destructuring - auto [forward, right, up] = AngleVectors(self->s.angles); - - // Calcular posición de inicio usando M_ProjectFlashSource - vec3_t const start = M_ProjectFlashSource(self, monster_flash_offset[flash_number], forward, right); - - // Determinar velocidad del cohete - int config_speed = self->spawnflags.has(SPAWNFLAG_runnertank_COMMANDER_HEAT_SEEKING) ? M_HEAT_SPEED(self) : M_ROCKET_SPEED(self); - int32_t const rocket_speed = config_speed > 0 ? config_speed : - (self->speed ? self->speed : - (self->spawnflags.has(SPAWNFLAG_runnertank_COMMANDER_HEAT_SEEKING) ? 500 : 650)); - - // Calcular punto objetivo - vec3_t target; - const bool blindfire = self->monsterinfo.aiflags & AI_MANUAL_STEERING; - - if (blindfire) { - target = self->monsterinfo.blind_fire_target; - vec3_t dir = target - start; - if (!M_AdjustBlindfireTarget(self, start, target, right, dir)) - return; - } - else { - if (!M_HasValidTarget(self)) - return; - // Decidir punto de objetivo basado en posición del enemigo - if (frandom() < 0.66f || start.z < self->enemy->absmin.z) { - // Apuntar al centro del cuerpo - target = self->enemy->s.origin; - target.z += self->enemy->viewheight; - } - else { - // Apuntar a los pies - target = self->enemy->s.origin; - target.z = self->enemy->absmin.z + 1; - } - } - - // Calcular dirección base - vec3_t dir = (target - start).normalized(); - - // Predicción de objetivo para disparos no ciegos - if (!blindfire && frandom() < (0.2f + ((3 - skill->integer) * 0.15f))) { - PredictAim(self, self->enemy, start, rocket_speed, false, 0, &dir, &target); - } - - // Verificar línea de visión y disparar - if (blindfire) { - if (M_AdjustBlindfireTarget(self, start, target, right, dir)) { - if (self->spawnflags.has(SPAWNFLAG_runnertank_COMMANDER_HEAT_SEEKING)) - monster_fire_heat(self, start, dir, damage, rocket_speed, flash_number, self->accel); - else - monster_fire_rocket(self, start, dir, damage, rocket_speed, flash_number); - } - } - else { - trace_t const trace = gi.traceline(start, target, self, MASK_PROJECTILE); - if (trace.fraction > 0.5f || trace.ent->solid != SOLID_BSP) { - if (self->spawnflags.has(SPAWNFLAG_runnertank_COMMANDER_HEAT_SEEKING)) - monster_fire_heat(self, start, dir, damage, rocket_speed, flash_number, self->accel); - else - monster_fire_rocket(self, start, dir, damage, rocket_speed, flash_number); - } - } -} - -void runnertankPlasmaGun(edict_t* self) { - - if (!M_HasValidTarget(self)) - { - return; // Stop immediately if the target is invalid. - } - - int damage = M_GET_DMG_OR(self, PLASMA, 35); - - // Blindfire support for plasma (like gunner ionripper) - bool blindfire = (self->monsterinfo.aiflags & AI_MANUAL_STEERING); - vec3_t target; - - if (blindfire) - { - // Blindfire mode: use blind_fire_target - if (!self->monsterinfo.blind_fire_target) - return; - target = self->monsterinfo.blind_fire_target; - } - else - { - // Normal mode: require visibility - if (!visible(self, self->enemy) || !infront(self, self->enemy)) - return; - target = self->enemy->s.origin; - } - - // Constantes del arma - constexpr float SPREAD = 0.08f; - constexpr float PREDICTION_TIME = 0.2f; - constexpr float PROJECTILE_SPEED = 700.0f; - constexpr float PLASMA_RADIUS = 40.0f; - - // Calcular flash number basado en el frame - monster_muzzleflash_id_t const flash_number = static_cast - (MZ2_TANK_MACHINEGUN_1 + (self->s.frame - FRAME_attak406)); - - // Use current angles for muzzle position - don't force rotation before firing - vec3_t forward, right, up; - AngleVectors(self->s.angles, &forward, &right, &up); - - // Calcular posición de inicio - vec3_t const start = M_ProjectFlashSource(self, monster_flash_offset[flash_number], forward, right); - - // Check if muzzle origin can see enemy before firing (skip for blindfire) - if (!blindfire) - { - vec3_t check_pos = self->enemy->s.origin; - check_pos[2] += self->enemy->viewheight; - trace_t trace = gi.traceline(start, check_pos, self, MASK_PROJECTILE); - - if (!(trace.fraction > 0.5f || trace.ent->solid != SOLID_BSP)) - return; - } - - // Calcular dirección base al objetivo - target = self->enemy->s.origin; - target.z += self->enemy->viewheight; - vec3_t dir = (target - start).normalized(); - - // Añadir dispersión a la dirección - dir += vec3_t{ - crandom() * SPREAD, - crandom() * SPREAD, - crandom() * SPREAD - }; - dir.normalize(); - - // Predicción de movimiento del objetivo - PredictAim(self, self->enemy, start, PROJECTILE_SPEED, false, - PREDICTION_TIME, &dir, nullptr); - - // Disparar el proyectil de plasma - fire_plasma(self, start, dir, damage, PROJECTILE_SPEED, - PLASMA_RADIUS, PLASMA_RADIUS); - - // Actualizar posición del último disparo - self->pos1 = target; -} - -//static void runnertank_blind_check(edict_t* self) -//{ -// if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) -// { -// const vec3_t aim = self->monsterinfo.blind_fire_target - self->s.origin; -// self->ideal_yaw = vectoyaw(aim); -// } -//} - -mframe_t runnertank_frames_attack_blast[] = { - { ai_charge }, { ai_charge }, { ai_charge }, { ai_charge }, - { ai_charge }, { ai_charge }, { ai_charge }, { ai_charge }, - { ai_charge }, { ai_charge, 0, runnertankRail }, - { ai_charge }, { ai_charge }, { ai_charge, 0, runnertankRail }, - { ai_charge }, { ai_charge }, { ai_charge, 0, runnertankRail }, - { ai_charge }, { ai_charge }, { ai_charge }, { ai_charge }, - { ai_charge }, { ai_charge } -}; -MMOVE_T(runnertank_move_attack_blast) = { FRAME_attak101, FRAME_attak122, runnertank_frames_attack_blast, runnertank_reattack_blaster }; - -mframe_t runnertank_frames_reattack_blast[] = { - { ai_charge }, - { ai_charge, 0, runnertankRail }, - { ai_charge }, - { ai_charge }, - { ai_charge }, - { ai_charge, 0, runnertankRail }, - { ai_charge }, - { ai_charge }, - { ai_charge }, - { ai_charge }, - { ai_charge }, - { ai_charge, 0, runnertankRail } // 16 -}; -MMOVE_T(runnertank_move_reattack_blast) = { FRAME_attak111, FRAME_attak122, runnertank_frames_reattack_blast, runnertank_reattack_blaster }; - -mframe_t runnertank_frames_attack_post_blast[] = { - { ai_move }, // 17 - { ai_move }, - { ai_move, 2 }, - { ai_move, 3 }, - { ai_move, 2 }, - { ai_move, -2, runnertank_footstep } // 22 -}; -MMOVE_T(runnertank_move_attack_post_blast) = { FRAME_attak117, FRAME_attak122, runnertank_frames_attack_post_blast, runnertank_attack_finished }; - - -// Called when attack animations finish - adds cooldown before next attack -void runnertank_attack_finished(edict_t* self) -{ - // Add random cooldown after attack ends - self->monsterinfo.attack_finished = level.time + random_time(0.4_sec, 2.3_sec); - runnertank_run(self); -} - -void runnertank_reattack_blaster(edict_t* self) -{ - if (!M_HasValidTarget(self)) - { - M_SetAnimation(self, &runnertank_move_attack_post_blast); - return; - } - - if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) - { - self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; - M_SetAnimation(self, &runnertank_move_attack_post_blast); - return; - } - - // Check if we've been attacking too long - if (level.time >= self->monsterinfo.attack_finished) - { - M_SetAnimation(self, &runnertank_move_attack_post_blast); - return; - } - - // Reduce refire chance to prevent constant shooting - if (visible(self, self->enemy)) - if (self->enemy->health > 0) - if (frandom() <= 0.35f) // Reduced from 0.6f - { - M_SetAnimation(self, &runnertank_move_reattack_blast); - return; - } - M_SetAnimation(self, &runnertank_move_attack_post_blast); -} - -void runnertank_doattack_rocket(edict_t* self); - -void runnertank_poststrike(edict_t* self) -{ - self->enemy = nullptr; - // [Paril-KEX] - self->monsterinfo.pausetime = HOLD_FOREVER; - self->monsterinfo.stand(self); -} - -mframe_t runnertank_frames_attack_strike[] = { - { ai_charge }, { ai_charge }, { ai_charge }, { ai_charge }, - { ai_charge }, { ai_charge }, { ai_charge }, { ai_charge }, - { ai_charge }, { ai_charge }, { ai_charge }, { ai_charge }, - { ai_charge }, { ai_charge }, { ai_charge }, { ai_charge }, - { ai_charge }, { ai_charge }, { ai_charge }, { ai_charge }, - { ai_charge, 0, runnertankStrike }, { ai_charge }, { ai_charge }, - { ai_charge }, { ai_charge }, { ai_charge }, { ai_charge }, - { ai_charge }, { ai_charge }, { ai_charge }, { ai_charge }, - { ai_charge }, { ai_charge }, { ai_charge }, { ai_charge }, - { ai_charge }, { ai_charge }, { ai_charge } -}; -MMOVE_T(runnertank_move_attack_strike) = { FRAME_attak201, FRAME_attak238, runnertank_frames_attack_strike, runnertank_poststrike }; - -mframe_t runnertank_frames_attack_pre_rocket[] = { - { ai_charge }, { ai_charge }, - { ai_charge }, { ai_charge }, { ai_charge }, { ai_charge }, - { ai_charge, 0, ai_charge }, { ai_charge }, { ai_charge, 0, runnertankRocket }, { ai_charge }, - { ai_charge, 0, runnertankRocket }, { ai_charge }, { ai_charge }, { runnertankRocket, 0, ai_charge }, - { ai_charge }, { ai_charge }, { ai_charge, 0, ai_charge }, { ai_charge, 0, ai_charge }, - { ai_charge } -}; -MMOVE_T(runnertank_move_attack_pre_rocket) = { FRAME_attak303, FRAME_attak321, runnertank_frames_attack_pre_rocket, runnertank_doattack_rocket }; - -mframe_t runnertank_frames_attack_fire_rocket[] = { - { ai_charge }, { runnertankRocket }, - { ai_charge, 0, ai_charge }, - { runnertankRocket, 0, ai_charge }, - { ai_charge, 0, runnertankRocket }, - { ai_charge, 0, ai_charge } -}; -MMOVE_T(runnertank_move_attack_fire_rocket) = { FRAME_attak312, FRAME_attak322, runnertank_frames_attack_fire_rocket, runnertank_refire_rocket }; - -mframe_t runnertank_frames_attack_post_rocket[] = { - - { ai_charge, -9 }, - { ai_charge, -8 }, - { ai_charge, -7 }, - { ai_charge, -1 }, - { ai_charge, -1, runnertank_footstep }, - { ai_charge }, - { ai_charge }, - { ai_charge }, // 50 - { ai_charge }, - { ai_charge } -}; -MMOVE_T(runnertank_move_attack_post_rocket) = { FRAME_attak326, FRAME_attak335, runnertank_frames_attack_post_rocket, runnertank_attack_finished }; - - -void runnertank_refire_rocket(edict_t* self) -{ - // If the enemy is gone, the tank should finish its attack animation. - if (!M_HasValidTarget(self)) - { - M_SetAnimation(self, &runnertank_move_attack_post_rocket); - return; - } - - // PMM - blindfire cleanup - if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) - { - self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; - M_SetAnimation(self, &runnertank_move_attack_post_rocket); - return; - } - // pmm - - // Check if we've been attacking too long - if (level.time >= self->monsterinfo.attack_finished) - { - M_SetAnimation(self, &runnertank_move_attack_post_rocket); - return; - } - - if (self->enemy->health > 0) - if (visible(self, self->enemy)) - if (frandom() <= 0.3f) // Reduced from 0.4f - { - M_SetAnimation(self, &runnertank_move_attack_fire_rocket); - return; - } - M_SetAnimation(self, &runnertank_move_attack_post_rocket); -} - -void runnertank_doattack_rocket(edict_t* self) -{ - M_SetAnimation(self, &runnertank_move_attack_fire_rocket); -} - - -mframe_t runnertank_frames_attack_chain[] = { - { ai_charge }, - { ai_charge }, - { ai_charge, 0, runnertankPlasmaGun }, - { ai_charge, 0, runnertankPlasmaGun }, - { ai_charge, 0, runnertankPlasmaGun }, - { ai_charge, 0, runnertankPlasmaGun }, - { ai_charge, 0, runnertankPlasmaGun }, - { ai_charge, 0, runnertankPlasmaGun }, - { ai_charge, 0, runnertankPlasmaGun }, - { ai_charge, 0, runnertankPlasmaGun }, - { ai_charge, 0, runnertankPlasmaGun }, - { ai_charge } -}; -MMOVE_T(runnertank_move_attack_chain) = { FRAME_attak404, FRAME_attak415, runnertank_frames_attack_chain, runnertank_attack_finished }; - -void runnertank_stop_run_to_attack(edict_t* self) -{ - if (!M_HasValidTarget(self)) - { - M_SetAnimation(self, &runnertank_move_run); - return; - } - - // Don't attack if we're still in cooldown - if (level.time < self->monsterinfo.attack_finished) - { - M_SetAnimation(self, &runnertank_move_run); - return; - } - - if (range_to(self, self->enemy) <= RANGE_NEAR && visible(self, self->enemy)) - { - M_SetAnimation(self, &runnertank_move_attack_pre_rocket); - self->monsterinfo.attack_finished = level.time + 3_sec; - } - else - { - M_SetAnimation(self, &runnertank_move_run); - } -} - -void runnertank_consider_strafe(edict_t* self) -{ - // No strafear si estamos en medio de un ataque - if (self->monsterinfo.active_move == &runnertank_move_attack_blast || - self->monsterinfo.active_move == &runnertank_move_attack_pre_rocket || - self->monsterinfo.active_move == &runnertank_move_attack_fire_rocket || - self->monsterinfo.active_move == &tank_move_punch_attack) - return; - - // Use the comprehensive check - if (!M_HasValidTarget(self)) - return; - - // Don't strafe if we're still in strafe pause - if (level.time < self->monsterinfo.pausetime) - return; - - float strafe_chance = 0.4f; // Increased base chance for more responsive movement - - // Increase probability in critical situations - if (self->enemy && self->enemy->client && (self->enemy->client->buttons & BUTTON_ATTACK)) - strafe_chance += 0.3f; - if (self->health < self->max_health * 0.6f) - strafe_chance += 0.25f; - - // Distance-based strafing: strafe more when close to enemy - if (self->enemy) { - float dist = (self->s.origin - self->enemy->s.origin).length(); - if (dist < 512.0f) // Close range - strafe_chance += 0.2f; - else if (dist > 1024.0f) // Long range - less strafing - strafe_chance -= 0.1f; - } - - // Clamp the chance - strafe_chance = std::clamp(strafe_chance, 0.1f, 0.85f); - - if (frandom() < strafe_chance) - { - // Decide direction - consistent integer usage - self->monsterinfo.lefty = (frandom() < 0.5f) ? 1 : 0; - - // Calculate strafe direction - vec3_t right; - AngleVectors(self->s.angles, nullptr, right, nullptr); - - // Calculate strafe speed based on health and situation - float strafe_speed = 200.0f; - - // Boost speed when in danger - if (self->health < self->max_health * 0.5f) - strafe_speed *= 1.4f; - - // Add some randomness to make movement less predictable - strafe_speed += frandom() * 100.0f; - - // Apply strafe velocity - replace current lateral movement - vec3_t strafe_velocity = right * (self->monsterinfo.lefty ? -strafe_speed : strafe_speed); - - // Preserve forward/backward movement but replace lateral movement - vec3_t forward; - AngleVectors(self->s.angles, forward, nullptr, nullptr); - float forward_speed = self->velocity.dot(forward); - - // Set new velocity: keep forward momentum, add strafe - self->velocity = forward * forward_speed + strafe_velocity; - - // Moderate strafe duration - self->monsterinfo.pausetime = level.time + random_time(0.8_sec, 1.5_sec); - } -} - - -// Custom checkattack with higher attack chances than default -// Default is: stand=0.7, melee=0.4, near=0.25, mid=0.06, far=0.0 -// Runnertank is aggressive and should attack more often -MONSTERINFO_CHECKATTACK(runnertank_checkattack) (edict_t* self) -> bool -{ - return M_CheckAttack_Base(self, 0.8f, 0.5f, 0.4f, 0.25f, 0.15f, 1.0f); -} - -MONSTERINFO_ATTACK(runnertank_attack) (edict_t* self) -> void -{ - monster_done_dodge(self); - - if (!M_HasValidTarget(self)) - { - return; // Can't attack a non-existent or dead target. - } - - if (level.time < self->monsterinfo.attack_finished) - return; - - // Check for blindfire conditions - if (self->monsterinfo.attack_state == AS_BLIND) - { - // Blindfire logic - attack without visibility requirement - float chance; - if (self->monsterinfo.blind_fire_delay < 1_sec) - chance = 0.8f; - else if (self->monsterinfo.blind_fire_delay < 7.5_sec) - chance = 0.4f; - else - chance = 0.1f; - - if (frandom() > chance) - return; - - self->monsterinfo.blind_fire_delay += 5.2_sec + random_time(3_sec); - self->monsterinfo.aiflags |= AI_MANUAL_STEERING; - } - else if (!visible(self, self->enemy)) - { - // Regular attack needs visibility - return; - } - - //const float range = range_to(self, self->enemy); - const float r = frandom(); - - // Verificar líneas de visión para cada tipo de ataque - const bool can_blast = M_CheckClearShot(self, monster_flash_offset[MZ2_TANK_BLASTER_1]); - const bool can_rocket = M_CheckClearShot(self, monster_flash_offset[MZ2_TANK_ROCKET_1]); - const bool can_chain = M_CheckClearShot(self, monster_flash_offset[MZ2_TANK_MACHINEGUN_1]); - - float const range = range_to(self, self->enemy); - - // Jump attack - check first for medium range leap attack - // Range between 150-400 units, visible enemy, on ground - if (range > 150.0f && range <= 400.0f && - self->groundentity && visible(self, self->enemy) && - frandom() < 0.25f) // 25% chance - { - // Check if enemy is at similar height or slightly above - float height_diff = self->enemy->s.origin[2] - self->s.origin[2]; - if (height_diff > -50.0f && height_diff < 150.0f) - { - M_SetAnimation(self, &runnertank_move_jump_attack); - self->monsterinfo.attack_finished = level.time + 3_sec; - return; - } - } - - // Close range attack selection - if (range <= RANGE_MELEE * 1.5f) - { - M_SetAnimation(self, &tank_move_punch_attack); - self->monsterinfo.attack_finished = level.time + 1.5_sec; - } - else if (range <= RANGE_NEAR) - { - if (can_chain && r < 0.5f) - { - M_SetAnimation(self, &runnertank_move_attack_chain); - self->monsterinfo.attack_finished = level.time + 3_sec; - } - else if (can_rocket && r < 0.7f) - { - M_SetAnimation(self, &runnertank_move_attack_pre_rocket); - self->monsterinfo.attack_finished = level.time + 4_sec; - } - else if (can_blast) - { - M_SetAnimation(self, &runnertank_move_attack_blast); - self->monsterinfo.attack_finished = level.time + 3_sec; - } - else - { - // If no clear shot, let normal AI handle movement - return; - } - } - // Medium range attack selection - else if (range <= RANGE_MID) - { - if (can_blast && r < 0.4f) - { - M_SetAnimation(self, &runnertank_move_attack_blast); - self->monsterinfo.attack_finished = level.time + 3_sec; - } - else if (can_rocket && r < 0.7f) - { - M_SetAnimation(self, &runnertank_move_attack_pre_rocket); - self->monsterinfo.attack_finished = level.time + 4_sec; - } - else if (can_chain) - { - M_SetAnimation(self, &runnertank_move_attack_chain); - self->monsterinfo.attack_finished = level.time + 3_sec; - } - else - { - // If no clear shot, let normal AI handle movement - return; - } - } - // Long range attack selection - else - { - if (can_blast && r < 0.6f) - { - M_SetAnimation(self, &runnertank_move_attack_blast); - self->monsterinfo.attack_finished = level.time + 3.5_sec; - } - else if (can_rocket) - { - M_SetAnimation(self, &runnertank_move_attack_pre_rocket); - self->monsterinfo.attack_finished = level.time + 4_sec; - } - else - { - // If no attack is possible, let normal AI handle movement - return; - } - } -} -// -// death -// - -void runnertank_dead(edict_t* self) -{ - self->mins = { -16, -16, -16 }; - self->maxs = { 16, 16, -0 }; - monster_dead(self); -} - -static void runnertank_shrink(edict_t* self) -{ - self->maxs[2] = 0; - self->svflags |= SVF_DEADMONSTER; - gi.linkentity(self); -} - -mframe_t runnertank_frames_death1[] = { - { ai_move, -7 }, - { ai_move, -2 }, - { ai_move, -2 }, - { ai_move, 1 }, - { ai_move, 3 }, - { ai_move, 6 }, - { ai_move, 1 }, - { ai_move, 1 }, - { ai_move, 2 }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move, -2 }, - { ai_move }, - { ai_move }, - { ai_move, -3 }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move, -4 }, - { ai_move, -6 }, - { ai_move, -4 }, - { ai_move, -5 }, - { ai_move, -7, runnertank_shrink }, - { ai_move, -15, runnertank_thud }, - { ai_move, -5 }, - { ai_move }, - { ai_move }, - { ai_move } -}; -MMOVE_T(runnertank_move_death) = { FRAME_death01, FRAME_death32, runnertank_frames_death1, runnertank_dead }; - -DIE(runnertank_die) (edict_t* self, edict_t* inflictor, edict_t* attacker, int damage, const vec3_t& point, const mod_t& mod) -> void -{ - // check for gib - if (M_CheckGib(self, mod)) - { - gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); - - self->s.skinnum /= 2; - - ThrowGibs(self, damage, { - { "models/objects/gibs/sm_meat/tris.md2" }, - { 3, "models/objects/gibs/sm_metal/tris.md2", GIB_METALLIC }, - { "models/objects/gibs/gear/tris.md2", GIB_METALLIC }, - { 2, "models/monsters/tank/gibs/foot.md2", GIB_SKINNED | GIB_METALLIC }, - { 2, "models/monsters/tank/gibs/thigh.md2", GIB_SKINNED | GIB_METALLIC }, - { "models/monsters/tank/gibs/chest.md2", GIB_SKINNED }, - { "models/monsters/tank/gibs/head.md2", GIB_HEAD | GIB_SKINNED } - }); - - if (!self->style) - ThrowGib(self, "models/monsters/tank/gibs/barm.md2", damage, GIB_SKINNED | GIB_UPRIGHT, self->s.scale); - - self->deadflag = true; - return; - } - - if (self->deadflag) - return; - - // [Paril-KEX] dropped arm - if (!self->style) - { - self->style = 1; - - auto [fwd, rgt, up] = AngleVectors(self->s.angles); - - edict_t* arm_gib = ThrowGib(self, "models/monsters/tank/gibs/barm.md2", damage, GIB_SKINNED | GIB_UPRIGHT, self->s.scale); - - if (!arm_gib) { - return; - } - - arm_gib->s.origin = self->s.origin + (rgt * -16.f) + (up * 23.f); - arm_gib->s.old_origin = arm_gib->s.origin; - arm_gib->avelocity = { crandom() * 15.f, crandom() * 15.f, 180.f }; - arm_gib->velocity = (up * 100.f) + (rgt * -120.f); - arm_gib->s.angles = self->s.angles; - arm_gib->s.angles[2] = -90.f; - arm_gib->s.skinnum /= 2; - gi.linkentity(arm_gib); - } - - // regular death - gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); - self->deadflag = true; - self->takedamage = true; - - M_SetAnimation(self, &runnertank_move_death); -} -void runnertank_jump_now(edict_t* self) -{ - auto [forward, right, up] = AngleVectors(self->s.angles); - - // Usar operadores vec3_t para impulso - self->velocity += forward * 130.0f + up * 300.0f; -} - -void runnertank_jump2_now(edict_t* self) -{ - auto [forward, right, up] = AngleVectors(self->s.angles); - - // Usar operadores vec3_t para impulso - self->velocity += forward * 250.0f + up * 400.0f; -} - -void runnertank_jump_wait_land(edict_t* self) -{ - if (self->groundentity == nullptr) - { - self->monsterinfo.nextframe = self->s.frame; - - if (monster_jump_finished(self)) - self->monsterinfo.nextframe = self->s.frame + 1; - } - else - self->monsterinfo.nextframe = self->s.frame + 1; -} - -mframe_t runnertank_frames_jump[] = { - { ai_move }, - { ai_move }, - { ai_move }, - { ai_move, 0, runnertank_jump_now }, - { ai_move }, - { ai_move, 0, runnertank_jump_wait_land }, - { ai_move } -}; -MMOVE_T(runnertank_move_jump) = { FRAME_run01, FRAME_run07, runnertank_frames_jump, runnertank_run }; - -mframe_t runnertank_frames_jump2[] = { - { ai_move, -6 }, - { ai_move, -4 }, - { ai_move, -5 }, - { ai_move, 0, runnertank_jump2_now }, - { ai_move }, - { ai_move, 0, runnertank_jump_wait_land }, - { ai_move } -}; -MMOVE_T(runnertank_move_jump2) = { FRAME_run01, FRAME_run07, runnertank_frames_jump2, runnertank_run }; - -// Jump attack functions implementation -void runnertank_jump_attack_takeoff(edict_t* self) -{ - if (!M_HasValidTarget(self)) - return; - - // Calculate jump trajectory to enemy - vec3_t enemy_pos = self->enemy->s.origin; - vec3_t dir_to_enemy = enemy_pos - self->s.origin; - float const length = dir_to_enemy.length(); - - // Adjust for enemy movement prediction - float const jump_time = length / 400.0f; // Estimate time to reach enemy - if (self->enemy->velocity.length() > 50.0f) - { - enemy_pos += self->enemy->velocity * jump_time * 0.5f; // Partial prediction - dir_to_enemy = enemy_pos - self->s.origin; - } - - // Calculate horizontal and vertical speeds - float const horizontal_speed = length * 1.5f; // Adjust multiplier as needed - float const vertical_speed = 200.0f + (length * 0.3f); // Higher for longer jumps - - // Face the target - self->s.angles[1] = vectoyaw(dir_to_enemy); - vec3_t forward; - AngleVectors(self->s.angles, forward, nullptr, nullptr); - - // Launch! - self->s.origin[2] += 1; - self->velocity = forward * horizontal_speed; - self->velocity[2] = vertical_speed; - self->groundentity = nullptr; - self->monsterinfo.aiflags |= AI_DUCKED; - - // Play a sound effect if we have one - gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); -} - -void runnertank_high_gravity(edict_t* self) -{ - float const gravity_scale = (800.f / level.gravity); - if (self->velocity[2] < 0) - self->gravity = 2.0f; - else - self->gravity = 4.5f; - self->gravity *= gravity_scale; -} - -void runnertank_check_jump_landing(edict_t* self) -{ - runnertank_high_gravity(self); - - if (self->groundentity) - { - // Landed - do slam attack immediately - self->monsterinfo.aiflags &= ~AI_DUCKED; - self->gravity = 1.0f; - self->velocity = {}; - self->flags &= ~FL_KILL_VELOCITY; - - // Always do the slam on landing - this is a jump attack! - runnertankStrike(self); - - // Continue with the animation - return; - } - - // Still in air - keep checking - self->monsterinfo.nextframe = self->s.frame; -} - -void runnertank_jump(edict_t* self, blocked_jump_result_t result) -{ - if (!M_HasValidTarget(self)) - return; // Can't jump at a non-existent or dead target. - - monster_done_dodge(self); - - if (result == blocked_jump_result_t::JUMP_JUMP_UP) - M_SetAnimation(self, &runnertank_move_jump2); - else - M_SetAnimation(self, &runnertank_move_jump); -} -//=========== -// PGM - -//bool runnertank_check_wall(edict_t* self, float dist) -//{ -// auto [forward, right, up] = AngleVectors(self->s.angles); -// -// // Usar operador + de vec3_t para punto de verificación -// vec3_t const check_point = self->s.origin + (forward * (dist + 10.0f)); -// -// trace_t const tr = gi.trace(self->s.origin, self->mins, self->maxs, -// check_point, self, MASK_MONSTERSOLID); -// -// if (tr.fraction < 1.0f) { -// // Usar dot() de vec3_t -// float const dot = forward.dot(tr.plane.normal); -// float const turn_angle = 90.0f * (1.0f - std::abs(dot)); -// -// // Actualizar orientación usando el resultado -// float new_yaw = self->s.angles[YAW] + (dot < 0 ? turn_angle : -turn_angle); -// float const yaw_diff = AngleDifference(new_yaw, self->ideal_yaw); -// -// if (std::abs(yaw_diff) > 30.0f) -// new_yaw = self->s.angles[YAW] + (yaw_diff > 0 ? 30.0f : -30.0f); -// -// self->ideal_yaw = anglemod(new_yaw); -// M_ChangeYaw(self); -// -// // Ajustar velocidad usando operador * de vec3_t -// self->velocity = self->velocity * (tr.fraction * 0.8f); -// -// return true; -// } -// return false; -//} - -MONSTERINFO_BLOCKED(runnertank_blocked) (edict_t* self, float dist) -> bool -{ - if (self->monsterinfo.can_jump) - { - if (const auto result = blocked_checkjump(self, dist); result != blocked_jump_result_t::NO_JUMP) - { - runnertank_jump(self, result); - return true; - } - } - - if (blocked_checkplat(self, dist)) - return true; - - // Eliminar la llamada a runnertank_check_wall aquí - // if (runnertank_check_wall(self, dist)) - // { - // ai_turn(self, 0); - // return true; - // } - - return false; -} - -// PGM -// -//=========== - -// -// monster_runnertank -// - -MONSTERINFO_SIDESTEP(runnertank_sidestep) (edict_t* self) -> bool -{ - // No hacer sidestep si estamos en medio de un ataque - if ((self->monsterinfo.active_move == &runnertank_move_attack_blast) || - (self->monsterinfo.active_move == &runnertank_move_attack_pre_rocket) || - (self->monsterinfo.active_move == &runnertank_move_attack_fire_rocket) || - (self->monsterinfo.active_move == &tank_move_punch_attack) || - (self->monsterinfo.active_move == &runnertank_move_jump_attack)) - { - return false; - } - - // Don't sidestep if we just did one recently - if (level.time < self->monsterinfo.pausetime) - return false; - - // Si no estamos corriendo, cambiar a la animación de carrera - if (self->monsterinfo.active_move != &runnertank_move_run) - M_SetAnimation(self, &runnertank_move_run); - - // Consistent integer usage for lefty - self->monsterinfo.lefty = (frandom() < 0.5f) ? 1 : 0; - - // Calculate strafe direction - auto [forward, right, up] = AngleVectors(self->s.angles); - - // More dynamic speed calculation - float base_speed = 250.0f; - float speed_variation = frandom() * 150.0f; - float const strafe_speed = base_speed + speed_variation; - - // Enhanced evasion: check if enemy is aiming/attacking - float evasion_multiplier = 1.0f; - if (self->enemy && self->enemy->client) { - // Boost evasion if enemy is attacking - if (self->enemy->client->buttons & BUTTON_ATTACK) - evasion_multiplier = 1.6f; - - // Also consider enemy's facing direction for better dodging - vec3_t enemy_forward; - AngleVectors(self->enemy->s.angles, enemy_forward, nullptr, nullptr); - vec3_t to_self = (self->s.origin - self->enemy->s.origin).normalized(); - - // If enemy is facing us, dodge more aggressively - if (enemy_forward.dot(to_self) > 0.7f) - evasion_multiplier *= 1.3f; - } - - // Apply enhanced movement - vec3_t dodge_velocity = right * (self->monsterinfo.lefty ? -strafe_speed : strafe_speed) * evasion_multiplier; - - // Add slight forward/backward component for more unpredictable movement - float fb_component = (frandom() - 0.5f) * 100.0f; - dodge_velocity += forward * fb_component; - - // Replace lateral velocity but preserve some vertical momentum - vec3_t preserved_velocity = {0, 0, self->velocity.z}; - self->velocity = dodge_velocity + preserved_velocity; - - // Variable pause time based on evasion intensity - self->monsterinfo.pausetime = level.time + random_time(0.8_sec, 1.5_sec); - - return true; -} - -/*QUAKED monster_runnertank (1 .5 0) (-32 -32 -16) (32 32 72) Ambush Trigger_Spawn Sight -model="models/monsters/runnertank/tris.md2" -*/ -/*QUAKED monster_runnertank_commander (1 .5 0) (-32 -32 -16) (32 32 72) Ambush Trigger_Spawn Sight Guardian HeatSeeking - */ -MONSTERINFO_DODGE(runnertank_dodge) (edict_t* self, edict_t* attacker, gtime_t eta, trace_t* tr, bool gravity) -> void -{ - // Basic checks - if (!self->groundentity || self->health <= 0) - return; - - // Set enemy if we don't have one - if (!self->enemy && attacker) - { - self->enemy = attacker; - FoundTarget(self); - return; - } - - // Don't dodge if we're attacking melee or jump attacking - if (self->monsterinfo.active_move == &tank_move_punch_attack || - self->monsterinfo.active_move == &runnertank_move_jump_attack) - return; - - // Check dodge cooldown using timestamp - if (self->timestamp > level.time) - return; - - // Don't dodge if projectile impact is too soon or too far away - if (eta < FRAME_TIME_MS || eta > 3_sec) - return; - - // Don't dodge if attacker is invalid - if (!attacker) - return; - - // Calculate dodge direction based on attacker position - vec3_t dodge_dir; - - // Get our right vector for lateral dodge - vec3_t right; - AngleVectors(self->s.angles, nullptr, right, nullptr); - - // Decide dodge direction - prefer moving away from attacker - vec3_t to_attacker = (attacker->s.origin - self->s.origin).normalized(); - float side_dot = to_attacker.dot(right); - - // Dodge perpendicular to attack direction, away from attacker - if (side_dot > 0) - dodge_dir = right * -1.0f; // Dodge left - else - dodge_dir = right; // Dodge right - - // Add some forward/backward component based on distance - vec3_t forward; - AngleVectors(self->s.angles, forward, nullptr, nullptr); - float dist = (self->s.origin - attacker->s.origin).length(); - - if (dist < 400.0f) { - // Close range - dodge backward - dodge_dir += forward * -0.3f; - } else if (dist > 800.0f) { - // Long range - dodge forward to close distance - dodge_dir += forward * 0.2f; - } - - dodge_dir = dodge_dir.normalized(); - - // Calculate dodge speed based on urgency (eta) - float base_dodge_speed = 300.0f; - float eta_seconds = eta.seconds(); - float urgency_multiplier = std::clamp(2.0f - eta_seconds, 1.0f, 2.5f); - float dodge_speed = base_dodge_speed * urgency_multiplier; - - // Apply dodge velocity - vec3_t dodge_velocity = dodge_dir * dodge_speed; - - // Preserve some vertical momentum but replace horizontal - dodge_velocity.z = self->velocity.z * 0.5f; - self->velocity = dodge_velocity; - - // Set animation to running for dodge - if (self->monsterinfo.active_move != &runnertank_move_run) - M_SetAnimation(self, &runnertank_move_run); - - // Set cooldown using timestamp (like spider) - self->timestamp = level.time + random_time(0.5_sec, 1.5_sec); - - // Also set pausetime for movement consistency - self->monsterinfo.pausetime = level.time + random_time(0.3_sec, 0.7_sec); - - // Update lefty for consistency with sidestep - self->monsterinfo.lefty = (side_dot > 0) ? 1 : 0; - - // Mark that we're dodging - monster_done_dodge(self); -} - -void SP_monster_runnertank(edict_t* self) -{ - const spawn_temp_t& st = ED_GetSpawnTemp(); - - if (self->monsterinfo.monster_type_id == MONSTER_TYPE_UNKNOWN) { // Check if it hasn't been set yet - self->monsterinfo.monster_type_id = static_cast(horde::MonsterTypeID::RUNNERTANK); -} if (!M_AllowSpawn(self)) { - G_FreeEdict(self); - return; - } - - self->s.modelindex = gi.modelindex("models/vault/monsters/tank/tris.md2"); - self->mins = { -28, -28, -14 }; - self->maxs = { 28, 28, 56 }; - self->movetype = MOVETYPE_STEP; - self->solid = SOLID_BBOX; - - gi.modelindex("models/monsters/tank/gibs/barm.md2"); - gi.modelindex("models/monsters/tank/gibs/head.md2"); - gi.modelindex("models/monsters/tank/gibs/chest.md2"); - gi.modelindex("models/monsters/tank/gibs/foot.md2"); - gi.modelindex("models/monsters/tank/gibs/thigh.md2"); - - sound_thud.assign("tank/tnkdeth2.wav"); - sound_idle.assign("tank/tnkidle1.wav"); - sound_die.assign("tank/death.wav"); - sound_step.assign("tank/step.wav"); - sound_windup.assign("tank/tnkatck4.wav"); - sound_strike.assign("tank/tnkatck5.wav"); - sound_sight.assign("tank/sight1.wav"); - - gi.soundindex("tank/tnkatck1.wav"); - gi.soundindex("tank/tnkatk2a.wav"); - gi.soundindex("tank/tnkatk2b.wav"); - gi.soundindex("tank/tnkatk2c.wav"); - gi.soundindex("tank/tnkatk2d.wav"); - gi.soundindex("tank/tnkatk2e.wav"); - gi.soundindex("tank/tnkatck3.wav"); - - // if (strcmp(self->classname, "monster_runnertank_commander") == 0) - // { - // self->health = (config ? config->health : 1000) * st.health_multiplier; - // self->gib_health = -225; - // self->count = 1; - // sound_pain2.assign("tank/pain.wav"); - // } - // else - { - int base_health = M_RUNNERTANK_INITIAL_HEALTH; - if (g_horde && g_horde->integer && current_wave_level > 0) { - self->health = ScaleMonsterHealth(base_health, current_wave_level, false); - } else { - self->health = base_health * st.health_multiplier; - } - self->gib_health = -200; - sound_pain.assign("tank/tnkpain2.wav"); - } - - self->monsterinfo.scale = MODEL_SCALE; - - // [Paril-KEX] N64 runnertank commander is a chonky boy - if (self->spawnflags.has(SPAWNFLAG_runnertank_COMMANDER_GUARDIAN)) - { - if (!self->s.scale) - self->s.scale = 1.5f; - int base_health = M_RUNNERTANK_INITIAL_HEALTH; - if (g_horde && g_horde->integer && current_wave_level > 0) { - self->health = ScaleMonsterHealth(base_health, current_wave_level, false); - } else { - self->health = base_health * st.health_multiplier; - } - } - - // heat seekingness - if (!self->accel) - self->accel = 0.075f; - - self->mass = 500; - - self->pain = runnertank_pain; - self->die = runnertank_die; - self->monsterinfo.stand = runnertank_stand; - self->monsterinfo.walk = runnertank_walk; - self->monsterinfo.run = runnertank_run; - self->monsterinfo.sidestep = runnertank_sidestep; - self->monsterinfo.dodge = runnertank_dodge; - self->monsterinfo.attack = runnertank_attack; - self->monsterinfo.melee = runnertank_melee; - self->monsterinfo.sight = runnertank_sight; - self->monsterinfo.idle = runnertank_idle; - self->monsterinfo.blocked = runnertank_blocked; // PGM - self->monsterinfo.setskin = runnertank_setskin; - self->monsterinfo.checkattack = runnertank_checkattack; - self->yaw_speed = 20; // Better tracking but not too fast - - self->s.renderfx |= RF_CUSTOMSKIN; - self->s.skinnum = gi.imageindex("models/vault/monsters/tank/skin.pcx"); - - gi.linkentity(self); - - M_SetAnimation(self, &runnertank_move_stand); - - walkmonster_start(self); - - // PMM - self->monsterinfo.aiflags |= AI_IGNORE_SHOTS; - self->monsterinfo.blindfire = true; - // pmm -// if (strcmp(self->classname, "monster_runnertank_commander") == 0) -// self->s.skinnum = 2; - - self->monsterinfo.can_jump = true; - self->monsterinfo.drop_height = 256; - // HORDE MOD: Increased jump height from 68 to 88 (30% increase) for better obstacle navigation - self->monsterinfo.jump_height = 88; - ApplyMonsterBonusFlags(self); -} - -void Use_Boss3(edict_t* ent, edict_t* other, edict_t* activator); - -THINK(Think_runnertankStand) (edict_t* ent) -> void -{ - if (ent->s.frame == FRAME_stand30) - ent->s.frame = FRAME_stand01; - else - ent->s.frame++; - ent->nextthink = level.time + 10_hz; -} - -/*QUAKED monster_runnertank_stand (1 .5 0) (-32 -32 0) (32 32 90) - -Just stands and cycles in one place until targeted, then teleports away. -N64 edition! -*/ -void SP_monster_runnertank_stand(edict_t* self) -{ - if (!M_AllowSpawn(self)) { - G_FreeEdict(self); - return; - } - - self->movetype = MOVETYPE_STEP; - self->solid = SOLID_BBOX; - self->model = "models/vault/monsters/tank/tris.md2"; - self->s.modelindex = gi.modelindex(self->model); - self->s.frame = FRAME_stand01; - self->s.skinnum = 2; - - gi.soundindex("misc/bigtele.wav"); - - self->mins = { -32, -32, -16 }; - self->maxs = { 32, 32, 64 }; - - if (!self->s.scale) - self->s.scale = 1.5f; - // Removed manual (mins/maxs) scaling - monster_start() handles it automatically - - self->use = Use_Boss3; - self->think = Think_runnertankStand; - self->nextthink = level.time + 10_hz; - gi.linkentity(self); -} \ No newline at end of file diff --git a/to add/m_runnertank.h b/to add/m_runnertank.h deleted file mode 100644 index 8012ab12..00000000 --- a/to add/m_runnertank.h +++ /dev/null @@ -1,266 +0,0 @@ -#pragma once -// Copyright (c) ZeniMax Media Inc. -// Licensed under the GNU General Public License 2.0. -// G:\quake2\baseq2\models/monsters/parasite - -// This file generated by ModelGen - Do NOT Modify - -enum -{ - FRAME_stand01, - FRAME_stand02, - FRAME_stand03, - FRAME_stand04, - FRAME_stand05, - FRAME_stand06, - FRAME_stand07, - FRAME_stand08, - FRAME_stand09, - FRAME_stand10, - FRAME_stand11, - FRAME_stand12, - FRAME_stand13, - FRAME_stand14, - FRAME_stand15, - FRAME_stand16, - FRAME_stand17, - FRAME_stand18, - FRAME_stand19, - FRAME_stand20, - FRAME_stand21, - FRAME_stand22, - FRAME_stand23, - FRAME_stand24, - FRAME_stand25, - FRAME_stand26, - FRAME_stand27, - FRAME_stand28, - FRAME_stand29, - FRAME_stand30, - FRAME_walk01, - FRAME_walk02, - FRAME_walk03, - FRAME_walk04, - FRAME_walk05, - FRAME_walk06, - FRAME_walk07, - FRAME_walk08, - FRAME_walk09, - FRAME_walk10, - FRAME_walk11, - FRAME_walk12, - FRAME_walk13, - FRAME_walk14, - FRAME_walk15, - FRAME_walk16, - FRAME_walk17, - FRAME_walk18, - FRAME_walk19, - FRAME_walk20, - FRAME_walk21, - FRAME_walk22, - FRAME_walk23, - FRAME_walk24, - FRAME_walk25, - FRAME_walk26, - FRAME_walk27, - FRAME_walk28, - FRAME_walk29, - FRAME_walk30, - FRAME_walk31, - FRAME_walk32, - FRAME_walk33, - FRAME_walk34, - FRAME_walk35, - FRAME_walk36, - FRAME_walk37, - FRAME_walk38, - FRAME_run01, - FRAME_run02, - FRAME_run03, - FRAME_run04, - FRAME_run05, - FRAME_run06, - FRAME_run07, - FRAME_run08, - FRAME_run09, - FRAME_run10, - FRAME_attak101, - FRAME_attak102, - FRAME_attak103, - FRAME_attak104, - FRAME_attak105, - FRAME_attak106, - FRAME_attak107, - FRAME_attak108, - FRAME_attak109, - FRAME_attak110, - FRAME_attak111, - FRAME_attak112, - FRAME_attak113, - FRAME_attak114, - FRAME_attak115, - FRAME_attak116, - FRAME_attak117, - FRAME_attak118, - FRAME_attak119, - FRAME_attak120, - FRAME_attak121, - FRAME_attak122, - FRAME_attak201, - FRAME_attak202, - FRAME_attak203, - FRAME_attak204, - FRAME_attak205, - FRAME_attak206, - FRAME_attak207, - FRAME_attak208, - FRAME_attak209, - FRAME_attak210, - FRAME_attak211, - FRAME_attak212, - FRAME_attak213, - FRAME_attak214, - FRAME_attak215, - FRAME_attak216, - FRAME_attak217, - FRAME_attak218, - FRAME_attak219, - FRAME_attak220, - FRAME_attak221, - FRAME_attak222, - FRAME_attak223, - FRAME_attak224, - FRAME_attak225, - FRAME_attak226, - FRAME_attak227, - FRAME_attak228, - FRAME_attak229, - FRAME_attak230, - FRAME_attak231, - FRAME_attak232, - FRAME_attak233, - FRAME_attak234, - FRAME_attak235, - FRAME_attak236, - FRAME_attak237, - FRAME_attak238, - FRAME_attak301, - FRAME_attak302, - FRAME_attak303, - FRAME_attak304, - FRAME_attak305, - FRAME_attak306, - FRAME_attak307, - FRAME_attak308, - FRAME_attak309, - FRAME_attak310, - FRAME_attak311, - FRAME_attak312, - FRAME_attak313, - FRAME_attak314, - FRAME_attak315, - FRAME_attak316, - FRAME_attak317, - FRAME_attak318, - FRAME_attak319, - FRAME_attak320, - FRAME_attak321, - FRAME_attak322, - FRAME_attak323, - FRAME_attak324, - FRAME_attak325, - FRAME_attak326, - FRAME_attak327, - FRAME_attak328, - FRAME_attak329, - FRAME_attak330, - FRAME_attak331, - FRAME_attak332, - FRAME_attak333, - FRAME_attak334, - FRAME_attak335, - FRAME_attak401, - FRAME_attak402, - FRAME_attak403, - FRAME_attak404, - FRAME_attak405, - FRAME_attak406, - FRAME_attak407, - FRAME_attak408, - FRAME_attak409, - FRAME_attak410, - FRAME_attak411, - FRAME_attak412, - FRAME_attak413, - FRAME_attak414, - FRAME_attak415, - FRAME_attak416, - FRAME_attak417, - FRAME_attak418, - FRAME_attak419, - FRAME_attak420, - FRAME_attak421, - FRAME_attak422, - FRAME_attak423, - FRAME_attak424, - FRAME_attak425, - FRAME_attak426, - FRAME_attak427, - FRAME_attak428, - FRAME_attak429, - FRAME_pain201, - FRAME_pain202, - FRAME_pain203, - FRAME_pain204, - FRAME_pain301, - FRAME_pain302, - FRAME_pain303, - FRAME_pain304, - FRAME_pain305, - FRAME_pain306, - FRAME_pain307, - FRAME_pain308, - FRAME_pain309, - FRAME_pain310, - FRAME_pain311, - FRAME_pain312, - FRAME_pain313, - FRAME_pain314, - FRAME_pain315, - FRAME_pain316, - FRAME_death01, - FRAME_death02, - FRAME_death03, - FRAME_death04, - FRAME_death05, - FRAME_death06, - FRAME_death07, - FRAME_death08, - FRAME_death09, - FRAME_death10, - FRAME_death11, - FRAME_death12, - FRAME_death13, - FRAME_death14, - FRAME_death15, - FRAME_death16, - FRAME_death17, - FRAME_death18, - FRAME_death19, - FRAME_death20, - FRAME_death21, - FRAME_death22, - FRAME_death23, - FRAME_death24, - FRAME_death25, - FRAME_death26, - FRAME_death27, - FRAME_death28, - FRAME_death29, - FRAME_death30, - FRAME_death31, - FRAME_death32, -}; - -constexpr float MODEL_SCALE = 1.000000f; \ No newline at end of file From 9bff76c3d26f8f7ee78b274a2500c64c38263fec Mon Sep 17 00:00:00 2001 From: Enemy-123 Date: Tue, 21 Apr 2026 12:59:44 -0400 Subject: [PATCH 04/24] added LUA support on new monsters, added arachnid, added infantry's animation run+attack, gekk's swimming animations, redmutant better jumping choice, better guncmdr animations, added medic commander with working summonables and spawngrow model. --- lua/variables.lua | 57 ++++ src/characters/settings.h | 57 ++++ src/characters/v_utils.c | 6 +- src/combat/common/damage.c | 4 +- src/combat/common/v_misc.c | 14 +- src/entities/drone/drone_ai.c | 6 +- src/entities/drone/drone_arachnid.c | 385 +++++++++++++++++++++++ src/entities/drone/drone_bitch.c | 4 + src/entities/drone/drone_daedalus.c | 4 +- src/entities/drone/drone_gekk.c | 248 ++++++++++++++- src/entities/drone/drone_gladiator.c | 24 +- src/entities/drone/drone_guncmdr.c | 391 +++++++++++++++++++++-- src/entities/drone/drone_infantry.c | 83 ++++- src/entities/drone/drone_medic.c | 435 +++++++++++++++++++++++++- src/entities/drone/drone_misc.c | 22 +- src/entities/drone/drone_redmutant.c | 7 +- src/entities/drone/drone_runnertank.c | 19 +- src/entities/drone/drone_stalker.c | 16 +- src/entities/drone/g_monster.c | 11 +- src/g_local.h | 4 + src/gamemodes/invasion.c | 7 +- src/quake2/g_layout.c | 2 + src/quake2/monsterframes/m_arachnid.h | 138 ++++++++ src/quake2/monsterframes/m_infantry.h | 57 ++++ src/server/v_luasettings.c | 59 ++++ 25 files changed, 1971 insertions(+), 89 deletions(-) create mode 100644 src/entities/drone/drone_arachnid.c create mode 100644 src/quake2/monsterframes/m_arachnid.h diff --git a/lua/variables.lua b/lua/variables.lua index ab650329..53f1a7fa 100644 --- a/lua/variables.lua +++ b/lua/variables.lua @@ -689,6 +689,10 @@ M_CHICK_INITIAL_HEALTH = 50 -- 200 M_CHICK_ADDON_HEALTH = 15 M_CHICK_INITIAL_ARMOR = 25 -- 175 M_CHICK_ADDON_ARMOR = 15 +M_CHICK_HEAT_INITIAL_HEALTH = 75 -- heat praetor +M_CHICK_HEAT_ADDON_HEALTH = 25 +M_CHICK_HEAT_INITIAL_ARMOR = 35 +M_CHICK_HEAT_ADDON_ARMOR = 20 M_BRAIN_INITIAL_HEALTH = 100 -- 500 M_BRAIN_ADDON_HEALTH = 40 M_BRAIN_INITIAL_ARMOR = 0 -- 1500 @@ -705,22 +709,46 @@ M_GLADIATOR_INITIAL_HEALTH = 100 -- 200 M_GLADIATOR_ADDON_HEALTH = 10 M_GLADIATOR_INITIAL_ARMOR = 100 -- 300 M_GLADIATOR_ADDON_ARMOR = 20 +M_GLADB_INITIAL_HEALTH = 120 -- gladiator disruptor +M_GLADB_ADDON_HEALTH = 35 +M_GLADB_INITIAL_ARMOR = 100 +M_GLADB_ADDON_ARMOR = 55 +M_GLADC_INITIAL_HEALTH = 110 -- gladiator plasma +M_GLADC_ADDON_HEALTH = 25 +M_GLADC_INITIAL_ARMOR = 100 +M_GLADC_ADDON_ARMOR = 40 M_GUNNER_INITIAL_HEALTH = 50 -- 300 M_GUNNER_ADDON_HEALTH = 25 M_GUNNER_INITIAL_ARMOR = 50 -- 200 M_GUNNER_ADDON_ARMOR = 15 +M_GUNCMDR_INITIAL_HEALTH = 100 -- Gunner Commander +M_GUNCMDR_ADDON_HEALTH = 65 +M_GUNCMDR_INITIAL_ARMOR = 200 +M_GUNCMDR_ADDON_ARMOR = 105 M_HOVER_INITIAL_HEALTH = 50 -- 150 M_HOVER_ADDON_HEALTH = 10 M_HOVER_INITIAL_ARMOR = 50 -- 350 M_HOVER_ADDON_ARMOR = 30 +M_DAEDALUS_INITIAL_HEALTH = 50 -- hover/daedalus +M_DAEDALUS_ADDON_HEALTH = 20 +M_DAEDALUS_INITIAL_ARMOR = 150 +M_DAEDALUS_ADDON_ARMOR = 60 M_MEDIC_INITIAL_HEALTH = 100 -- 200 M_MEDIC_ADDON_HEALTH = 10 M_MEDIC_INITIAL_ARMOR = 100 -- 300 M_MEDIC_ADDON_ARMOR = 20 +M_MEDIC_COMMANDER_INITIAL_HEALTH = 240 +M_MEDIC_COMMANDER_ADDON_HEALTH = 25 +M_MEDIC_COMMANDER_INITIAL_ARMOR = 200 +M_MEDIC_COMMANDER_ADDON_ARMOR = 40 M_MUTANT_INITIAL_HEALTH = 150 -- 1000 M_MUTANT_ADDON_HEALTH = 85 M_MUTANT_INITIAL_ARMOR = 0 M_MUTANT_ADDON_ARMOR = 0 +M_REDMUTANT_INITIAL_HEALTH = 180 +M_REDMUTANT_ADDON_HEALTH = 100 +M_REDMUTANT_INITIAL_ARMOR = 0 +M_REDMUTANT_ADDON_ARMOR = 0 M_PARASITE_INITIAL_HEALTH = 150 -- 1000 M_PARASITE_ADDON_HEALTH = 85 M_PARASITE_INITIAL_ARMOR = 0 @@ -729,12 +757,28 @@ M_TANK_INITIAL_HEALTH = 100 -- 750 M_TANK_ADDON_HEALTH = 65 M_TANK_INITIAL_ARMOR = 200 -- 1250 M_TANK_ADDON_ARMOR = 105 +M_RUNNERTANK_INITIAL_HEALTH = 100 +M_RUNNERTANK_ADDON_HEALTH = 65 +M_RUNNERTANK_INITIAL_ARMOR = 200 +M_RUNNERTANK_ADDON_ARMOR = 90 M_BARON_FIRE_INITIAL_HEALTH = 0 M_BARON_FIRE_ADDON_HEALTH = 2500 M_BARON_FIRE_INITIAL_ARMOR = 0 M_BARON_FIRE_ADDON_ARMOR = 0 M_SHAMBLER_INITIAL_HEALTH = 250 -- 1500 M_SHAMBLER_ADDON_HEALTH = 125 +M_GEKK_INITIAL_HEALTH = 150 +M_GEKK_ADDON_HEALTH = 85 +M_GEKK_INITIAL_ARMOR = 0 +M_GEKK_ADDON_ARMOR = 0 +M_STALKER_INITIAL_HEALTH = 150 +M_STALKER_ADDON_HEALTH = 85 +M_STALKER_INITIAL_ARMOR = 0 +M_STALKER_ADDON_ARMOR = 0 +M_ARACHNID_INITIAL_HEALTH = 100 +M_ARACHNID_ADDON_HEALTH = 55 +M_ARACHNID_INITIAL_ARMOR = 200 +M_ARACHNID_ADDON_ARMOR = 85 -- Monster Weapons -- M_ENABLE_WORLDSPAWN_SOFTCAP = 1 @@ -778,6 +822,19 @@ M_BLASTER_DMG_MAX = 0 M_BLASTER_SPEED_BASE = 1000 M_BLASTER_SPEED_ADDON = 50 M_BLASTER_SPEED_MAX = 1500 +M_BLASTER2_DMG_BASE = 50 +M_BLASTER2_DMG_ADDON = 2 +M_BLASTER2_DMG_MAX = 0 +M_BLASTER2_SPEED_BASE = 500 +M_BLASTER2_SPEED_ADDON = 0 +M_BLASTER2_SPEED_MAX = 0 +M_PLASMA_DMG_BASE = 50 +M_PLASMA_DMG_ADDON = 10 +M_PLASMA_DMG_MAX = 0 +M_PLASMA_SPEED_BASE = 1000 +M_PLASMA_SPEED_ADDON = 50 +M_PLASMA_SPEED_MAX = 1500 +M_PLASMA_DAMAGE_RADIUS = 40 M_20MM_RANGE_BASE = 750 M_20MM_RANGE_ADDON = 0 M_20MM_RANGE_MAX = 750 diff --git a/src/characters/settings.h b/src/characters/settings.h index b64b07a8..b4886b8b 100644 --- a/src/characters/settings.h +++ b/src/characters/settings.h @@ -756,6 +756,10 @@ VRX_V_LUASETTINGS_IMPL double M_MUTANT_INITIAL_HEALTH; VRX_V_LUASETTINGS_IMPL double M_MUTANT_ADDON_HEALTH; VRX_V_LUASETTINGS_IMPL double M_MUTANT_INITIAL_ARMOR; VRX_V_LUASETTINGS_IMPL double M_MUTANT_ADDON_ARMOR; +VRX_V_LUASETTINGS_IMPL double M_REDMUTANT_INITIAL_HEALTH; +VRX_V_LUASETTINGS_IMPL double M_REDMUTANT_ADDON_HEALTH; +VRX_V_LUASETTINGS_IMPL double M_REDMUTANT_INITIAL_ARMOR; +VRX_V_LUASETTINGS_IMPL double M_REDMUTANT_ADDON_ARMOR; VRX_V_LUASETTINGS_IMPL double M_PARASITE_INITIAL_HEALTH; VRX_V_LUASETTINGS_IMPL double M_PARASITE_ADDON_HEALTH; VRX_V_LUASETTINGS_IMPL double M_PARASITE_INITIAL_ARMOR; @@ -770,6 +774,46 @@ VRX_V_LUASETTINGS_IMPL double M_BARON_FIRE_INITIAL_ARMOR; VRX_V_LUASETTINGS_IMPL double M_BARON_FIRE_ADDON_ARMOR; VRX_V_LUASETTINGS_IMPL double M_SHAMBLER_INITIAL_HEALTH; VRX_V_LUASETTINGS_IMPL double M_SHAMBLER_ADDON_HEALTH; +VRX_V_LUASETTINGS_IMPL double M_CHICK_HEAT_INITIAL_HEALTH; +VRX_V_LUASETTINGS_IMPL double M_CHICK_HEAT_ADDON_HEALTH; +VRX_V_LUASETTINGS_IMPL double M_CHICK_HEAT_INITIAL_ARMOR; +VRX_V_LUASETTINGS_IMPL double M_CHICK_HEAT_ADDON_ARMOR; +VRX_V_LUASETTINGS_IMPL double M_DAEDALUS_INITIAL_HEALTH; +VRX_V_LUASETTINGS_IMPL double M_DAEDALUS_ADDON_HEALTH; +VRX_V_LUASETTINGS_IMPL double M_DAEDALUS_INITIAL_ARMOR; +VRX_V_LUASETTINGS_IMPL double M_DAEDALUS_ADDON_ARMOR; +VRX_V_LUASETTINGS_IMPL double M_RUNNERTANK_INITIAL_HEALTH; +VRX_V_LUASETTINGS_IMPL double M_RUNNERTANK_ADDON_HEALTH; +VRX_V_LUASETTINGS_IMPL double M_RUNNERTANK_INITIAL_ARMOR; +VRX_V_LUASETTINGS_IMPL double M_RUNNERTANK_ADDON_ARMOR; +VRX_V_LUASETTINGS_IMPL double M_GEKK_INITIAL_HEALTH; +VRX_V_LUASETTINGS_IMPL double M_GEKK_ADDON_HEALTH; +VRX_V_LUASETTINGS_IMPL double M_GEKK_INITIAL_ARMOR; +VRX_V_LUASETTINGS_IMPL double M_GEKK_ADDON_ARMOR; +VRX_V_LUASETTINGS_IMPL double M_STALKER_INITIAL_HEALTH; +VRX_V_LUASETTINGS_IMPL double M_STALKER_ADDON_HEALTH; +VRX_V_LUASETTINGS_IMPL double M_STALKER_INITIAL_ARMOR; +VRX_V_LUASETTINGS_IMPL double M_STALKER_ADDON_ARMOR; +VRX_V_LUASETTINGS_IMPL double M_MEDIC_COMMANDER_INITIAL_HEALTH; +VRX_V_LUASETTINGS_IMPL double M_MEDIC_COMMANDER_ADDON_HEALTH; +VRX_V_LUASETTINGS_IMPL double M_MEDIC_COMMANDER_INITIAL_ARMOR; +VRX_V_LUASETTINGS_IMPL double M_MEDIC_COMMANDER_ADDON_ARMOR; +VRX_V_LUASETTINGS_IMPL double M_GUNCMDR_INITIAL_HEALTH; +VRX_V_LUASETTINGS_IMPL double M_GUNCMDR_ADDON_HEALTH; +VRX_V_LUASETTINGS_IMPL double M_GUNCMDR_INITIAL_ARMOR; +VRX_V_LUASETTINGS_IMPL double M_GUNCMDR_ADDON_ARMOR; +VRX_V_LUASETTINGS_IMPL double M_GLADB_INITIAL_HEALTH; +VRX_V_LUASETTINGS_IMPL double M_GLADB_ADDON_HEALTH; +VRX_V_LUASETTINGS_IMPL double M_GLADB_INITIAL_ARMOR; +VRX_V_LUASETTINGS_IMPL double M_GLADB_ADDON_ARMOR; +VRX_V_LUASETTINGS_IMPL double M_GLADC_INITIAL_HEALTH; +VRX_V_LUASETTINGS_IMPL double M_GLADC_ADDON_HEALTH; +VRX_V_LUASETTINGS_IMPL double M_GLADC_INITIAL_ARMOR; +VRX_V_LUASETTINGS_IMPL double M_GLADC_ADDON_ARMOR; +VRX_V_LUASETTINGS_IMPL double M_ARACHNID_INITIAL_HEALTH; +VRX_V_LUASETTINGS_IMPL double M_ARACHNID_ADDON_HEALTH; +VRX_V_LUASETTINGS_IMPL double M_ARACHNID_INITIAL_ARMOR; +VRX_V_LUASETTINGS_IMPL double M_ARACHNID_ADDON_ARMOR; VRX_V_LUASETTINGS_IMPL double M_BRAIN_INITIAL_PULL; VRX_V_LUASETTINGS_IMPL double M_BRAIN_ADDON_PULL; VRX_V_LUASETTINGS_IMPL double M_BRAIN_MAX_PULL; @@ -811,6 +855,19 @@ VRX_V_LUASETTINGS_IMPL double M_BLASTER_DMG_MAX; VRX_V_LUASETTINGS_IMPL double M_BLASTER_SPEED_BASE; VRX_V_LUASETTINGS_IMPL double M_BLASTER_SPEED_ADDON; VRX_V_LUASETTINGS_IMPL double M_BLASTER_SPEED_MAX; +VRX_V_LUASETTINGS_IMPL double M_BLASTER2_DMG_BASE; +VRX_V_LUASETTINGS_IMPL double M_BLASTER2_DMG_ADDON; +VRX_V_LUASETTINGS_IMPL double M_BLASTER2_DMG_MAX; +VRX_V_LUASETTINGS_IMPL double M_BLASTER2_SPEED_BASE; +VRX_V_LUASETTINGS_IMPL double M_BLASTER2_SPEED_ADDON; +VRX_V_LUASETTINGS_IMPL double M_BLASTER2_SPEED_MAX; +VRX_V_LUASETTINGS_IMPL double M_PLASMA_DMG_BASE; +VRX_V_LUASETTINGS_IMPL double M_PLASMA_DMG_ADDON; +VRX_V_LUASETTINGS_IMPL double M_PLASMA_DMG_MAX; +VRX_V_LUASETTINGS_IMPL double M_PLASMA_SPEED_BASE; +VRX_V_LUASETTINGS_IMPL double M_PLASMA_SPEED_ADDON; +VRX_V_LUASETTINGS_IMPL double M_PLASMA_SPEED_MAX; +VRX_V_LUASETTINGS_IMPL double M_PLASMA_DAMAGE_RADIUS; VRX_V_LUASETTINGS_IMPL double M_20MM_RANGE_BASE; VRX_V_LUASETTINGS_IMPL double M_20MM_RANGE_ADDON; VRX_V_LUASETTINGS_IMPL double M_20MM_RANGE_MAX; diff --git a/src/characters/v_utils.c b/src/characters/v_utils.c index 5731d050..fade7bd7 100644 --- a/src/characters/v_utils.c +++ b/src/characters/v_utils.c @@ -2068,6 +2068,8 @@ char *V_GetMonsterKind(int mtype) { return "berserker"; case M_MEDIC: return "medic"; + case M_MEDIC_COMMANDER: + return "medic commander"; case M_MUTANT: return "mutant"; case M_BRAIN: @@ -2086,7 +2088,7 @@ char *V_GetMonsterKind(int mtype) { case M_RUNNERTANK: return "runner tank"; case M_GUNCMDR: - return "gun commander"; + return "gunner commander"; case M_DAEDALUS: return "daedalus"; case M_GLADB: @@ -2097,6 +2099,8 @@ char *V_GetMonsterKind(int mtype) { return "stalker"; case M_GEKK: return "gekk"; + case M_ARACHNID: + return "arachnid"; case M_SKELETON: return "skeleton"; case M_GOLEM: diff --git a/src/combat/common/damage.c b/src/combat/common/damage.c index 24bb813b..8abe5982 100644 --- a/src/combat/common/damage.c +++ b/src/combat/common/damage.c @@ -153,7 +153,9 @@ qboolean IsMonster(const edict_t* ent) { return (ent->mtype && (ent->mtype <= M_TANK || ent->mtype == M_SHAMBLER || ent->mtype == M_REDMUTANT || ent->mtype == M_RUNNERTANK || ent->mtype == M_GUNCMDR || ent->mtype == M_DAEDALUS || ent->mtype == M_GLADB || ent->mtype == M_GLADC - || ent->mtype == M_STALKER || ent->mtype == M_GEKK || ent->mtype == M_CHICK_HEAT)); + || ent->mtype == M_STALKER || ent->mtype == M_GEKK || ent->mtype == M_ARACHNID + || ent->mtype == M_MEDIC_COMMANDER + || ent->mtype == M_CHICK_HEAT)); } float vrx_get_pack_modifier(const edict_t *ent) { diff --git a/src/combat/common/v_misc.c b/src/combat/common/v_misc.c index 27023bbb..85dcc2a3 100644 --- a/src/combat/common/v_misc.c +++ b/src/combat/common/v_misc.c @@ -254,7 +254,7 @@ void vrx_pvm_spawn_monsters(edict_t* self, int max_monsters, int total_monsters) while (total_monsters < max_monsters && max_spawn_this_cycle > 0) { int rnd; do { - rnd = GetRandom(1, DS_BITCH_HEAT); // az: don't spawn soldiers or helper summons + rnd = GetRandom(1, DS_MEDIC_COMMANDER); // az: don't spawn soldiers or helper summons } while (rnd == DS_SOLDIER || rnd == DS_DECOY || rnd == DS_SKELETON || rnd == DS_GOLEM); edict_t* scan; @@ -605,6 +605,9 @@ int vrx_GetMonsterCost(int mtype) { case M_MEDIC: cost = M_MEDIC_COST; break; + case M_MEDIC_COMMANDER: + cost = M_TANK_COST; + break; case M_BRAIN: cost = M_BRAIN_COST; break; @@ -639,6 +642,9 @@ int vrx_GetMonsterCost(int mtype) { case M_GEKK: cost = M_MUTANT_COST; break; + case M_ARACHNID: + cost = M_DEFAULT_COST; + break; case M_SUPERTANK: cost = M_SUPERTANK_COST; break; @@ -684,6 +690,9 @@ int vrx_GetMonsterControlCost(int mtype) { case M_MEDIC: cost = M_MEDIC_CONTROL_COST; break; + case M_MEDIC_COMMANDER: + cost = M_TANK_CONTROL_COST; + break; case M_BRAIN: cost = M_BRAIN_CONTROL_COST; break; @@ -715,6 +724,9 @@ int vrx_GetMonsterControlCost(int mtype) { case M_GEKK: cost = M_MUTANT_CONTROL_COST; break; + case M_ARACHNID: + cost = M_GLADIATOR_CONTROL_COST; + break; case M_HOVER: cost = M_HOVER_CONTROL_COST; break; diff --git a/src/entities/drone/drone_ai.c b/src/entities/drone/drone_ai.c index f4347e00..eec2c4d0 100644 --- a/src/entities/drone/drone_ai.c +++ b/src/entities/drone/drone_ai.c @@ -704,7 +704,8 @@ void drone_ai_idle (edict_t *self) self->s.skinnum &= ~1; if (self->mtype != M_COMMANDER && self->mtype != M_GUNCMDR && self->mtype != M_DAEDALUS && self->mtype != M_GLADB && self->mtype != M_GLADC - && self->mtype != M_CHICK_HEAT && self->mtype != M_SOLDIER && self->mtype != M_STALKER) + && self->mtype != M_CHICK_HEAT && self->mtype != M_MEDIC_COMMANDER + && self->mtype != M_SOLDIER && self->mtype != M_STALKER) self->s.skinnum &= ~2; } @@ -1907,7 +1908,8 @@ void drone_ai_run1 (edict_t *self, float dist) self->s.skinnum &= ~1; if (self->mtype != M_COMMANDER && self->mtype != M_GUNCMDR && self->mtype != M_DAEDALUS && self->mtype != M_GLADB && self->mtype != M_GLADC - && self->mtype != M_CHICK_HEAT && self->mtype != M_SOLDIER && self->mtype != M_STALKER) + && self->mtype != M_CHICK_HEAT && self->mtype != M_MEDIC_COMMANDER + && self->mtype != M_SOLDIER && self->mtype != M_STALKER) self->s.skinnum &= ~2; } diff --git a/src/entities/drone/drone_arachnid.c b/src/entities/drone/drone_arachnid.c new file mode 100644 index 00000000..95e043c3 --- /dev/null +++ b/src/entities/drone/drone_arachnid.c @@ -0,0 +1,385 @@ +/* +============================================================================== + +ARACHNID + +============================================================================== +*/ + +#include "g_local.h" +#include "../../quake2/monsterframes/m_arachnid.h" + +static int sound_pain; +static int sound_death; +static int sound_sight; +static int sound_step; +static int sound_charge; +static int sound_melee; +static int sound_melee_hit; + +static void arachnid_stand(edict_t *self); +static void arachnid_run(edict_t *self); + +static void arachnid_footstep(edict_t *self) +{ + gi.sound(self, CHAN_BODY, sound_step, 0.5, ATTN_IDLE, 0); +} + +static void arachnid_sight(edict_t *self, edict_t *other) +{ + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +mframe_t arachnid_frames_stand[] = +{ + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL +}; +mmove_t arachnid_move_stand = { FRAME_idle1, FRAME_idle13, arachnid_frames_stand, NULL }; + +static void arachnid_stand(edict_t *self) +{ + self->monsterinfo.currentmove = &arachnid_move_stand; +} + +mframe_t arachnid_frames_walk[] = +{ + drone_ai_walk, 8, arachnid_footstep, + drone_ai_walk, 8, NULL, + drone_ai_walk, 8, NULL, + drone_ai_walk, 8, NULL, + drone_ai_walk, 8, NULL, + drone_ai_walk, 8, arachnid_footstep, + drone_ai_walk, 8, NULL, + drone_ai_walk, 8, NULL, + drone_ai_walk, 8, NULL, + drone_ai_walk, 8, NULL +}; +mmove_t arachnid_move_walk = { FRAME_walk1, FRAME_walk10, arachnid_frames_walk, NULL }; + +static void arachnid_walk(edict_t *self) +{ + if (!self->goalentity) + self->goalentity = world; + self->monsterinfo.currentmove = &arachnid_move_walk; +} + +mframe_t arachnid_frames_run[] = +{ + drone_ai_run, 13, arachnid_footstep, + drone_ai_run, 13, NULL, + drone_ai_run, 13, NULL, + drone_ai_run, 13, NULL, + drone_ai_run, 13, NULL, + drone_ai_run, 13, arachnid_footstep, + drone_ai_run, 13, NULL, + drone_ai_run, 13, NULL, + drone_ai_run, 13, NULL, + drone_ai_run, 13, NULL +}; +mmove_t arachnid_move_run = { FRAME_walk1, FRAME_walk10, arachnid_frames_run, NULL }; + +static void arachnid_run(edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &arachnid_move_stand; + else + self->monsterinfo.currentmove = &arachnid_move_run; +} + +static void arachnid_charge_rail(edict_t *self) +{ + if (!G_ValidTarget(self, self->enemy, true, true)) + return; + + gi.sound(self, CHAN_WEAPON, sound_charge, 1, ATTN_NORM, 0); + VectorCopy(self->enemy->s.origin, self->pos1); + self->pos1[2] += self->enemy->viewheight; +} + +static void arachnid_rail(edict_t *self) +{ + int damage; + int flash_number; + vec3_t start, forward, right, offset, dir; + + if (!G_ValidTarget(self, self->enemy, true, true)) + return; + + damage = M_RAILGUN_DMG_BASE + M_RAILGUN_DMG_ADDON * drone_damagelevel(self); + if (M_RAILGUN_DMG_MAX && damage > M_RAILGUN_DMG_MAX) + damage = M_RAILGUN_DMG_MAX; + + flash_number = MZ2_GLADIATOR_RAILGUN_1; + AngleVectors(self->s.angles, forward, right, NULL); + VectorSet(offset, 48, (self->s.frame == FRAME_rails7 || self->s.frame == FRAME_rails_up5 || self->s.frame == FRAME_rails_up11) ? -16 : 16, 28); + G_ProjectSource(self->s.origin, offset, forward, right, start); + VectorSubtract(self->pos1, start, dir); + VectorNormalize(dir); + + monster_fire_railgun(self, start, dir, damage, 100, flash_number); + M_DelayNextAttack(self, 0, true); +} + +mframe_t arachnid_frames_attack1[] = +{ + ai_charge, 0, arachnid_charge_rail, + ai_charge, 0, arachnid_rail, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, arachnid_charge_rail, + ai_charge, 0, arachnid_rail, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, arachnid_charge_rail, + ai_charge, 0, NULL +}; +mmove_t arachnid_move_attack1 = { FRAME_rails2, FRAME_rails11, arachnid_frames_attack1, arachnid_run }; + +mframe_t arachnid_frames_attack_up1[] = +{ + ai_charge, 0, arachnid_charge_rail, + ai_charge, 0, arachnid_rail, + ai_charge, 0, NULL, + ai_charge, 0, arachnid_charge_rail, + ai_charge, 0, arachnid_rail, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, arachnid_charge_rail, + ai_charge, 0, arachnid_rail, + ai_charge, 0, arachnid_charge_rail, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t arachnid_move_attack_up1 = { FRAME_rails_up1, FRAME_rails_up13, arachnid_frames_attack_up1, arachnid_run }; + +static void arachnid_melee_charge(edict_t *self) +{ + gi.sound(self, CHAN_WEAPON, sound_melee, 1, ATTN_NORM, 0); +} + +static void arachnid_melee_hit(edict_t *self) +{ + int damage; + + if (!G_ValidTarget(self, self->enemy, true, true)) + return; + + damage = M_MELEE_DMG_BASE + M_MELEE_DMG_ADDON * drone_damagelevel(self); + if (M_MELEE_DMG_MAX && damage > M_MELEE_DMG_MAX) + damage = M_MELEE_DMG_MAX; + + if (M_MeleeAttack(self, self->enemy, 96, damage, 100)) + gi.sound(self, CHAN_WEAPON, sound_melee_hit, 1, ATTN_NORM, 0); + else + self->monsterinfo.melee_finished = level.time + 1.0; +} + +mframe_t arachnid_frames_melee[] = +{ + ai_charge, 0, NULL, + ai_charge, 0, arachnid_melee_charge, + ai_charge, 0, arachnid_melee_hit, + ai_charge, 0, arachnid_melee_hit, + ai_charge, 0, arachnid_melee_charge, + ai_charge, 0, arachnid_melee_hit, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, arachnid_melee_charge, + ai_charge, 0, NULL, + ai_charge, 0, arachnid_melee_hit, + ai_charge, 0, NULL +}; +mmove_t arachnid_move_melee = { FRAME_melee_atk1, FRAME_melee_atk12, arachnid_frames_melee, arachnid_run }; + +static void arachnid_attack(edict_t *self) +{ + if (!G_ValidTarget(self, self->enemy, true, true)) + return; + + if (self->monsterinfo.melee_finished < level.time && entdist(self, self->enemy) < MELEE_DISTANCE) + self->monsterinfo.currentmove = &arachnid_move_melee; + else if ((self->enemy->s.origin[2] - self->s.origin[2]) > 150) + self->monsterinfo.currentmove = &arachnid_move_attack_up1; + else + self->monsterinfo.currentmove = &arachnid_move_attack1; + + M_DelayNextAttack(self, 0, true); +} + +static void arachnid_melee(edict_t *self) +{ + self->monsterinfo.currentmove = &arachnid_move_melee; + M_DelayNextAttack(self, 0, true); +} + +mframe_t arachnid_frames_pain1[] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t arachnid_move_pain1 = { FRAME_pain11, FRAME_pain15, arachnid_frames_pain1, arachnid_run }; + +mframe_t arachnid_frames_pain2[] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t arachnid_move_pain2 = { FRAME_pain21, FRAME_pain26, arachnid_frames_pain2, arachnid_run }; + +static void arachnid_pain(edict_t *self, edict_t *other, float kick, int damage) +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3.0; + gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); + + if (skill->value == 3) + return; + + if (random() < 0.5) + self->monsterinfo.currentmove = &arachnid_move_pain1; + else + self->monsterinfo.currentmove = &arachnid_move_pain2; +} + +static void arachnid_dead(edict_t *self) +{ + VectorSet(self->mins, -16, -16, -24); + VectorSet(self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + gi.linkentity(self); + M_PrepBodyRemoval(self); +} + +mframe_t arachnid_frames_death[] = +{ + ai_move, 0, NULL, + ai_move, -1.23, NULL, + ai_move, -1.23, NULL, + ai_move, -1.23, NULL, + ai_move, -1.23, NULL, + ai_move, -1.64, NULL, + ai_move, -1.64, NULL, + ai_move, -2.45, NULL, + ai_move, -8.63, NULL, + ai_move, -4.0, NULL, + ai_move, -4.5, NULL, + ai_move, -6.8, NULL, + ai_move, -8.0, NULL, + ai_move, -5.4, NULL, + ai_move, -3.4, NULL, + ai_move, -1.9, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t arachnid_move_death = { FRAME_death1, FRAME_death20, arachnid_frames_death, arachnid_dead }; + +static void arachnid_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + M_Notify(self); + + if (self->health <= self->gib_health) + { + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); + if (vrx_spawn_nonessential_ent(self->s.origin)) + { + for (n = 0; n < 2; n++) + ThrowGib(self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); + for (n = 0; n < 4; n++) + ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead(self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); + } + M_Remove(self, false, false); + return; + } + + if (self->deadflag == DEAD_DEAD) + return; + + DroneList_Remove(self); + gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + self->monsterinfo.currentmove = &arachnid_move_death; + + if (self->activator && !self->activator->client) + self->activator->num_monsters_real--; +} + +void init_drone_arachnid(edict_t *self) +{ + sound_step = gi.soundindex("insane/insane11.wav"); + sound_charge = gi.soundindex("gladiator/railgun.wav"); + sound_melee = gi.soundindex("gladiator/melee3.wav"); + sound_melee_hit = gi.soundindex("gladiator/melee2.wav"); + sound_pain = gi.soundindex("arachnid/pain.wav"); + sound_death = gi.soundindex("arachnid/death.wav"); + sound_sight = gi.soundindex("arachnid/sight.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/arachnid/tris.md2"); + VectorSet(self->mins, -48, -48, -20); + VectorSet(self->maxs, 48, 48, 48); + + self->health = M_ARACHNID_INITIAL_HEALTH + M_ARACHNID_ADDON_HEALTH * self->monsterinfo.level; + self->max_health = self->health; + self->gib_health = -200; + self->mass = 450; + self->mtype = M_ARACHNID; + + self->monsterinfo.control_cost = M_GLADIATOR_CONTROL_COST; + self->monsterinfo.cost = M_DEFAULT_COST; + self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD; + self->monsterinfo.power_armor_power = M_ARACHNID_INITIAL_ARMOR + M_ARACHNID_ADDON_ARMOR * self->monsterinfo.level; + self->monsterinfo.max_armor = self->monsterinfo.power_armor_power; + self->monsterinfo.jumpup = 64; + self->monsterinfo.jumpdn = 512; + self->monsterinfo.aiflags |= AI_NO_CIRCLE_STRAFE; + self->monsterinfo.pain_chance = 0.2f; + self->item = FindItemByClassname("ammo_slugs"); + + self->pain = arachnid_pain; + self->die = arachnid_die; + self->monsterinfo.stand = arachnid_stand; + self->monsterinfo.walk = arachnid_walk; + self->monsterinfo.run = arachnid_run; + self->monsterinfo.attack = arachnid_attack; + self->monsterinfo.melee = arachnid_melee; + self->monsterinfo.sight = arachnid_sight; + + gi.linkentity(self); + + self->monsterinfo.currentmove = &arachnid_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + self->nextthink = level.time + FRAMETIME; +} diff --git a/src/entities/drone/drone_bitch.c b/src/entities/drone/drone_bitch.c index 9fa5486a..80f2f72d 100644 --- a/src/entities/drone/drone_bitch.c +++ b/src/entities/drone/drone_bitch.c @@ -963,4 +963,8 @@ void init_drone_bitch_heat (edict_t *self) init_drone_bitch(self); self->mtype = M_CHICK_HEAT; self->s.skinnum = 2; + self->health = M_CHICK_HEAT_INITIAL_HEALTH + M_CHICK_HEAT_ADDON_HEALTH * self->monsterinfo.level; + self->max_health = self->health; + self->monsterinfo.power_armor_power = M_CHICK_HEAT_INITIAL_ARMOR + M_CHICK_HEAT_ADDON_ARMOR * self->monsterinfo.level; + self->monsterinfo.max_armor = self->monsterinfo.power_armor_power; } diff --git a/src/entities/drone/drone_daedalus.c b/src/entities/drone/drone_daedalus.c index 7fd74cba..ae913228 100644 --- a/src/entities/drone/drone_daedalus.c +++ b/src/entities/drone/drone_daedalus.c @@ -211,14 +211,14 @@ void init_drone_daedalus(edict_t *self) VectorSet(self->maxs, 24, 24, 32); self->s.skinnum = 2; - self->health = M_FLOATER_INITIAL_HEALTH + M_FLOATER_ADDON_HEALTH * self->monsterinfo.level; + self->health = M_DAEDALUS_INITIAL_HEALTH + M_DAEDALUS_ADDON_HEALTH * self->monsterinfo.level; self->max_health = self->health; self->gib_health = -100; self->mass = 225; self->mtype = M_DAEDALUS; self->flags |= FL_FLY; - self->monsterinfo.power_armor_power = M_FLOATER_INITIAL_ARMOR + M_FLOATER_ADDON_ARMOR * self->monsterinfo.level; + self->monsterinfo.power_armor_power = M_DAEDALUS_INITIAL_ARMOR + M_DAEDALUS_ADDON_ARMOR * self->monsterinfo.level; self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD; self->monsterinfo.max_armor = self->monsterinfo.power_armor_power; self->monsterinfo.control_cost = M_HOVER_CONTROL_COST; diff --git a/src/entities/drone/drone_gekk.c b/src/entities/drone/drone_gekk.c index 0773e250..5440acf5 100644 --- a/src/entities/drone/drone_gekk.c +++ b/src/entities/drone/drone_gekk.c @@ -27,7 +27,15 @@ void drone_ai_run(edict_t *self, float dist); void drone_ai_walk(edict_t *self, float dist); static void gekk_stand(edict_t *self); +static void gekk_walk(edict_t *self); static void gekk_run(edict_t *self); +static void gekk_land_to_water(edict_t *self); +static void gekk_water_to_land(edict_t *self); +static void gekk_swim_loop(edict_t *self); + +extern mmove_t gekk_move_standunderwater; +extern mmove_t gekk_move_swim_loop; +extern mmove_t gekk_move_swim_start; extern void fire_acid(edict_t *self, vec3_t start, vec3_t aimdir, int projectile_damage, float radius, int speed, int acid_damage, float acid_duration, int gas_damage, float gas_radius, float gas_duration); @@ -54,6 +62,31 @@ static void gekk_search(edict_t *self) gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_IDLE, 0); } +static qboolean gekk_should_swim(edict_t *self) +{ + return self->waterlevel >= WATER_WAIST; +} + +static void gekk_set_water_bounds(edict_t *self) +{ + self->flags |= FL_SWIM; + self->yaw_speed = 10; + self->viewheight = 10; + VectorSet(self->mins, -18, -18, -24); + VectorSet(self->maxs, 18, 18, 16); + gi.linkentity(self); +} + +static void gekk_set_land_bounds(edict_t *self) +{ + self->flags &= ~FL_SWIM; + self->yaw_speed = 20; + self->viewheight = 25; + VectorSet(self->mins, -18, -18, -24); + VectorSet(self->maxs, 18, 18, 24); + gi.linkentity(self); +} + mframe_t gekk_frames_stand[] = { drone_ai_stand, 0, NULL, @@ -96,26 +129,44 @@ mframe_t gekk_frames_stand[] = drone_ai_stand, 0, NULL, drone_ai_stand, 0, NULL }; -mmove_t gekk_move_stand = { FRAME_stand_01, FRAME_stand_39, gekk_frames_stand, NULL }; +mmove_t gekk_move_stand = { FRAME_stand_01, FRAME_stand_39, gekk_frames_stand, gekk_stand }; static void gekk_stand(edict_t *self) { - self->monsterinfo.currentmove = &gekk_move_stand; + if (gekk_should_swim(self)) + { + gekk_set_water_bounds(self); + self->monsterinfo.currentmove = &gekk_move_standunderwater; + } + else + { + if (self->flags & FL_SWIM) + gekk_set_land_bounds(self); + self->monsterinfo.currentmove = &gekk_move_stand; + } } mframe_t gekk_frames_walk[] = { - drone_ai_walk, 5, gekk_step, - drone_ai_walk, 6, NULL, - drone_ai_walk, 8, NULL, - drone_ai_walk, 7, gekk_step, - drone_ai_walk, 6, NULL, - drone_ai_walk, 5, NULL + drone_ai_walk, 8, gekk_step, + drone_ai_walk, 9, NULL, + drone_ai_walk, 12, NULL, + drone_ai_walk, 11, gekk_step, + drone_ai_walk, 9, NULL, + drone_ai_walk, 8, NULL }; -mmove_t gekk_move_walk = { FRAME_run_01, FRAME_run_06, gekk_frames_walk, NULL }; +mmove_t gekk_move_walk = { FRAME_run_01, FRAME_run_06, gekk_frames_walk, gekk_walk }; static void gekk_walk(edict_t *self) { + if (gekk_should_swim(self)) + { + gekk_land_to_water(self); + return; + } + if (self->flags & FL_SWIM) + gekk_set_land_bounds(self); + if (!self->goalentity) self->goalentity = world; self->monsterinfo.currentmove = &gekk_move_walk; @@ -130,10 +181,18 @@ mframe_t gekk_frames_run[] = drone_ai_run, 20, NULL, drone_ai_run, 18, NULL }; -mmove_t gekk_move_run = { FRAME_run_01, FRAME_run_06, gekk_frames_run, NULL }; +mmove_t gekk_move_run = { FRAME_run_01, FRAME_run_06, gekk_frames_run, gekk_run }; static void gekk_run(edict_t *self) { + if (gekk_should_swim(self)) + { + gekk_land_to_water(self); + return; + } + if (self->flags & FL_SWIM) + gekk_set_land_bounds(self); + if (self->monsterinfo.aiflags & AI_STAND_GROUND) self->monsterinfo.currentmove = &gekk_move_stand; else @@ -162,6 +221,150 @@ static void gekk_claw(edict_t *self) M_MeleeAttack(self, self->enemy, 96, gekk_melee_damage(self), 180); } +static void gekk_swim_check(edict_t *self) +{ + if (!gekk_should_swim(self) && (self->flags & FL_SWIM)) + gekk_water_to_land(self); +} + +static void gekk_swim_loop(edict_t *self) +{ + if (!gekk_should_swim(self)) + { + gekk_water_to_land(self); + return; + } + + gekk_set_water_bounds(self); + self->monsterinfo.currentmove = &gekk_move_swim_loop; +} + +mframe_t gekk_frames_standunderwater[] = +{ + drone_ai_stand, 14, NULL, + drone_ai_stand, 14, NULL, + drone_ai_stand, 14, NULL, + drone_ai_stand, 14, NULL, + drone_ai_stand, 16, NULL, + drone_ai_stand, 16, NULL, + drone_ai_stand, 16, NULL, + drone_ai_stand, 18, NULL, + drone_ai_stand, 18, NULL, + drone_ai_stand, 18, NULL, + drone_ai_stand, 20, NULL, + drone_ai_stand, 20, NULL, + drone_ai_stand, 22, NULL, + drone_ai_stand, 22, NULL, + drone_ai_stand, 24, NULL, + drone_ai_stand, 24, NULL, + drone_ai_stand, 26, NULL, + drone_ai_stand, 26, NULL, + drone_ai_stand, 24, NULL, + drone_ai_stand, 24, NULL, + drone_ai_stand, 22, NULL, + drone_ai_stand, 22, NULL, + drone_ai_stand, 22, NULL, + drone_ai_stand, 22, NULL, + drone_ai_stand, 22, NULL, + drone_ai_stand, 22, NULL, + drone_ai_stand, 22, NULL, + drone_ai_stand, 22, NULL, + drone_ai_stand, 18, NULL, + drone_ai_stand, 18, NULL, + drone_ai_stand, 18, NULL, + drone_ai_stand, 18, gekk_swim_check +}; +mmove_t gekk_move_standunderwater = { FRAME_swim_01, FRAME_swim_32, gekk_frames_standunderwater, gekk_stand }; + +mframe_t gekk_frames_swim[] = +{ + drone_ai_run, 14, NULL, + drone_ai_run, 14, NULL, + drone_ai_run, 14, NULL, + drone_ai_run, 14, NULL, + drone_ai_run, 16, NULL, + drone_ai_run, 16, NULL, + drone_ai_run, 16, NULL, + drone_ai_run, 18, NULL, + drone_ai_run, 18, NULL, + drone_ai_run, 18, NULL, + drone_ai_run, 20, NULL, + drone_ai_run, 20, NULL, + drone_ai_run, 22, NULL, + drone_ai_run, 22, NULL, + drone_ai_run, 24, NULL, + drone_ai_run, 24, NULL, + drone_ai_run, 26, NULL, + drone_ai_run, 26, NULL, + drone_ai_run, 24, NULL, + drone_ai_run, 24, NULL, + drone_ai_run, 22, NULL, + drone_ai_run, 22, NULL, + drone_ai_run, 22, NULL, + drone_ai_run, 22, NULL, + drone_ai_run, 22, NULL, + drone_ai_run, 22, NULL, + drone_ai_run, 22, NULL, + drone_ai_run, 22, NULL, + drone_ai_run, 18, NULL, + drone_ai_run, 18, NULL, + drone_ai_run, 18, NULL, + drone_ai_run, 18, gekk_swim_check +}; +mmove_t gekk_move_swim_loop = { FRAME_swim_01, FRAME_swim_32, gekk_frames_swim, gekk_swim_loop }; + +mframe_t gekk_frames_swim_start[] = +{ + drone_ai_run, 14, NULL, + drone_ai_run, 14, NULL, + drone_ai_run, 14, NULL, + drone_ai_run, 14, NULL, + drone_ai_run, 16, NULL, + drone_ai_run, 16, NULL, + drone_ai_run, 16, NULL, + drone_ai_run, 18, NULL, + drone_ai_run, 18, gekk_claw, + drone_ai_run, 18, NULL, + drone_ai_run, 20, NULL, + drone_ai_run, 20, NULL, + drone_ai_run, 22, NULL, + drone_ai_run, 22, NULL, + drone_ai_run, 24, gekk_claw, + drone_ai_run, 24, NULL, + drone_ai_run, 26, NULL, + drone_ai_run, 26, NULL, + drone_ai_run, 24, NULL, + drone_ai_run, 24, NULL, + drone_ai_run, 22, gekk_bite, + drone_ai_run, 22, NULL, + drone_ai_run, 22, NULL, + drone_ai_run, 22, NULL, + drone_ai_run, 22, NULL, + drone_ai_run, 22, NULL, + drone_ai_run, 22, NULL, + drone_ai_run, 22, NULL, + drone_ai_run, 18, NULL, + drone_ai_run, 18, NULL, + drone_ai_run, 18, NULL, + drone_ai_run, 18, gekk_swim_check +}; +mmove_t gekk_move_swim_start = { FRAME_swim_01, FRAME_swim_32, gekk_frames_swim_start, gekk_swim_loop }; + +static void gekk_land_to_water(edict_t *self) +{ + gekk_set_water_bounds(self); + self->monsterinfo.currentmove = &gekk_move_swim_start; +} + +static void gekk_water_to_land(edict_t *self) +{ + gekk_set_land_bounds(self); + if (self->enemy || self->goalentity) + self->monsterinfo.currentmove = &gekk_move_run; + else + self->monsterinfo.currentmove = &gekk_move_stand; +} + mframe_t gekk_frames_attack[] = { ai_charge, 8, NULL, @@ -218,6 +421,15 @@ mmove_t gekk_move_attack2 = { FRAME_clawatk5_01, FRAME_clawatk5_09, gekk_frames_ static void gekk_melee(edict_t *self) { + if (gekk_should_swim(self)) + { + gekk_land_to_water(self); + self->monsterinfo.melee_finished = level.time + 1.0; + return; + } + if (self->flags & FL_SWIM) + gekk_set_land_bounds(self); + if (!G_ValidTarget(self, self->enemy, true, true) || entdist(self, self->enemy) > 120) { self->monsterinfo.melee_finished = level.time + 0.5; @@ -314,6 +526,16 @@ mmove_t gekk_move_spit = { FRAME_spit_01, FRAME_spit_07, gekk_frames_spit, gekk_ static void gekk_attack(edict_t *self) { + if (gekk_should_swim(self)) + { + gekk_land_to_water(self); + self->monsterinfo.melee_finished = level.time + 1.0; + M_DelayNextAttack(self, 1.0 + random(), true); + return; + } + if (self->flags & FL_SWIM) + gekk_set_land_bounds(self); + if (G_EntExists(self->enemy) && entdist(self, self->enemy) < 180 && random() < 0.35) self->monsterinfo.currentmove = &gekk_move_leapatk; else @@ -392,6 +614,7 @@ static void gekk_pain(edict_t *self, edict_t *other, float kick, int damage) static void gekk_dead(edict_t *self) { + self->flags &= ~FL_SWIM; VectorSet(self->mins, -18, -18, -24); VectorSet(self->maxs, 18, 18, -8); self->movetype = MOVETYPE_TOSS; @@ -480,6 +703,7 @@ void init_drone_gekk(edict_t *self) self->s.modelindex = gi.modelindex("models/monsters/gekk/tris.md2"); VectorSet(self->mins, -18, -18, -24); VectorSet(self->maxs, 18, 18, 24); + self->viewheight = 25; gi.modelindex("models/objects/gekkgib/pelvis/tris.md2"); gi.modelindex("models/objects/gekkgib/arm/tris.md2"); @@ -488,13 +712,13 @@ void init_drone_gekk(edict_t *self) gi.modelindex("models/objects/gekkgib/leg/tris.md2"); gi.modelindex("models/objects/gekkgib/head/tris.md2"); - self->health = M_MUTANT_INITIAL_HEALTH + M_MUTANT_ADDON_HEALTH * self->monsterinfo.level; + self->health = M_GEKK_INITIAL_HEALTH + M_GEKK_ADDON_HEALTH * self->monsterinfo.level; self->max_health = self->health; self->gib_health = -30; self->mass = 300; self->mtype = M_GEKK; - self->monsterinfo.power_armor_power = M_MUTANT_INITIAL_ARMOR + M_MUTANT_ADDON_ARMOR * self->monsterinfo.level; + self->monsterinfo.power_armor_power = M_GEKK_INITIAL_ARMOR + M_GEKK_ADDON_ARMOR * self->monsterinfo.level; self->monsterinfo.power_armor_type = POWER_ARMOR_SCREEN; self->monsterinfo.max_armor = self->monsterinfo.power_armor_power; self->monsterinfo.control_cost = M_MUTANT_CONTROL_COST; diff --git a/src/entities/drone/drone_gladiator.c b/src/entities/drone/drone_gladiator.c index 721c786e..b4b93dcf 100644 --- a/src/entities/drone/drone_gladiator.c +++ b/src/entities/drone/drone_gladiator.c @@ -294,17 +294,17 @@ static void GladiatorPlasma(edict_t *self) if (!G_EntExists(self->enemy)) return; - damage = M_BLASTER_DMG_BASE + M_BLASTER_DMG_ADDON * drone_damagelevel(self); - if (M_BLASTER_DMG_MAX && damage > M_BLASTER_DMG_MAX) - damage = M_BLASTER_DMG_MAX; + damage = M_PLASMA_DMG_BASE + M_PLASMA_DMG_ADDON * drone_damagelevel(self); + if (M_PLASMA_DMG_MAX && damage > M_PLASMA_DMG_MAX) + damage = M_PLASMA_DMG_MAX; - speed = M_BLASTER_SPEED_BASE + M_BLASTER_SPEED_ADDON * drone_damagelevel(self); - if (M_BLASTER_SPEED_MAX && speed > M_BLASTER_SPEED_MAX) - speed = M_BLASTER_SPEED_MAX; + speed = M_PLASMA_SPEED_BASE + M_PLASMA_SPEED_ADDON * drone_damagelevel(self); + if (M_PLASMA_SPEED_MAX && speed > M_PLASMA_SPEED_MAX) + speed = M_PLASMA_SPEED_MAX; radius_damage = max(1, damage / 2); MonsterAim(self, M_PROJECTILE_ACC, speed, false, MZ2_GLADIATOR_RAILGUN_1, forward, start); - fire_plasma(self, start, forward, damage, speed, 40, radius_damage); + fire_plasma(self, start, forward, damage, speed, M_PLASMA_DAMAGE_RADIUS, radius_damage); } void gladiator_refire (edict_t *self) @@ -583,9 +583,14 @@ void init_drone_gladiator (edict_t *self) void init_drone_gladb(edict_t *self) { init_drone_gladiator(self); + // GLADB is the Gladiator Disruptor variant. self->mtype = M_GLADB; self->s.skinnum = 2; self->s.effects |= EF_TRACKER; + self->health = M_GLADB_INITIAL_HEALTH + M_GLADB_ADDON_HEALTH * self->monsterinfo.level; + self->max_health = self->health; + self->monsterinfo.power_armor_power = M_GLADB_INITIAL_ARMOR + M_GLADB_ADDON_ARMOR * self->monsterinfo.level; + self->monsterinfo.max_armor = self->monsterinfo.power_armor_power; self->mass = 350; self->item = FindItemByClassname("ammo_disruptor"); } @@ -593,8 +598,13 @@ void init_drone_gladb(edict_t *self) void init_drone_gladc(edict_t *self) { init_drone_gladiator(self); + // GLADC is the Gladiator Plasma variant. self->mtype = M_GLADC; self->s.skinnum = 2; + self->health = M_GLADC_INITIAL_HEALTH + M_GLADC_ADDON_HEALTH * self->monsterinfo.level; + self->max_health = self->health; + self->monsterinfo.power_armor_power = M_GLADC_INITIAL_ARMOR + M_GLADC_ADDON_ARMOR * self->monsterinfo.level; + self->monsterinfo.max_armor = self->monsterinfo.power_armor_power; self->mass = 350; self->item = FindItemByClassname("ammo_cells"); } diff --git a/src/entities/drone/drone_guncmdr.c b/src/entities/drone/drone_guncmdr.c index c2dc5161..427f0871 100644 --- a/src/entities/drone/drone_guncmdr.c +++ b/src/entities/drone/drone_guncmdr.c @@ -31,6 +31,8 @@ static void guncmdr_attack(edict_t *self); static void guncmdr_fire_chain(edict_t *self); static void guncmdr_refire_chain(edict_t *self); static void guncmdr_grenade_finished(edict_t *self); +static void guncmdr_grenade_mortar_resume(edict_t *self); +static void guncmdr_resume_back_attack(edict_t *self); extern mmove_t guncmdr_move_fidget; static void guncmdr_idle_sound(edict_t *self) @@ -246,18 +248,25 @@ static void GunnerCmdrFire(edict_t *self) if (!self->enemy || !self->enemy->inuse) return; - if (self->s.frame >= FRAME_c_run201 && self->s.frame <= FRAME_c_run206) + if (self->s.frame >= FRAME_c_attack401 && self->s.frame <= FRAME_c_attack505) + flash_number = MZ2_GUNNER_MACHINEGUN_2; + else if (self->s.frame >= FRAME_c_run201 && self->s.frame <= FRAME_c_run206) flash_number = MZ2_GUNNER_MACHINEGUN_1 + (self->s.frame - FRAME_c_run201); else flash_number = MZ2_GUNNER_MACHINEGUN_1 + ((self->s.frame - FRAME_c_attack107) % 6); - damage = M_MACHINEGUN_DMG_BASE + M_MACHINEGUN_DMG_ADDON * drone_damagelevel(self); - if (M_MACHINEGUN_DMG_MAX && damage > M_MACHINEGUN_DMG_MAX) - damage = M_MACHINEGUN_DMG_MAX; + damage = M_SHOTGUN_DMG_BASE + M_SHOTGUN_DMG_ADDON * drone_damagelevel(self); + if (M_SHOTGUN_DMG_MAX && damage > M_SHOTGUN_DMG_MAX) + damage = M_SHOTGUN_DMG_MAX; - MonsterAim(self, M_HITSCAN_CONT_ACC, 0, false, flash_number, forward, start); - monster_fire_bullet(self, start, forward, damage, damage, - DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flash_number); + MonsterAim(self, M_PROJECTILE_ACC, 800, false, flash_number, forward, start); + damage = vrx_increase_monster_damage_by_talent(self->activator, damage); + fire_flechette(self, start, forward, damage, 800, damage); + + gi.WriteByte(svc_muzzleflash2); + gi.WriteShort(self - g_edicts); + gi.WriteByte(flash_number); + gi.multicast(start, MULTICAST_PVS); } static void guncmdr_opengun(edict_t *self) @@ -298,6 +307,26 @@ mframe_t guncmdr_frames_fire_chain_run[] = }; mmove_t guncmdr_move_fire_chain_run = { FRAME_c_run201, FRAME_c_run206, guncmdr_frames_fire_chain_run, guncmdr_refire_chain }; +mframe_t guncmdr_frames_fire_chain_dodge_right[] = +{ + ai_charge, 10.2, GunnerCmdrFire, + ai_charge, 18.0, GunnerCmdrFire, + ai_charge, 7.0, GunnerCmdrFire, + ai_charge, 7.2, GunnerCmdrFire, + ai_charge, -2.0, GunnerCmdrFire +}; +mmove_t guncmdr_move_fire_chain_dodge_right = { FRAME_c_attack401, FRAME_c_attack405, guncmdr_frames_fire_chain_dodge_right, guncmdr_refire_chain }; + +mframe_t guncmdr_frames_fire_chain_dodge_left[] = +{ + ai_charge, 10.2, GunnerCmdrFire, + ai_charge, 18.0, GunnerCmdrFire, + ai_charge, 7.0, GunnerCmdrFire, + ai_charge, 7.2, GunnerCmdrFire, + ai_charge, -2.0, GunnerCmdrFire +}; +mmove_t guncmdr_move_fire_chain_dodge_left = { FRAME_c_attack501, FRAME_c_attack505, guncmdr_frames_fire_chain_dodge_left, guncmdr_refire_chain }; + mframe_t guncmdr_frames_endfire_chain[] = { ai_charge, 0, NULL, @@ -371,6 +400,23 @@ mframe_t guncmdr_frames_attack_mortar[] = }; mmove_t guncmdr_move_attack_mortar = { FRAME_c_attack201, FRAME_c_attack221, guncmdr_frames_attack_mortar, guncmdr_grenade_finished }; +static void guncmdr_grenade_mortar_resume(edict_t *self) +{ + self->monsterinfo.currentmove = &guncmdr_move_attack_mortar; + self->s.frame = self->count; +} + +mframe_t guncmdr_frames_attack_mortar_dodge[] = +{ + ai_charge, 11, NULL, + ai_charge, 12, NULL, + ai_charge, 16, NULL, + ai_charge, 16, NULL, + ai_charge, 12, NULL, + ai_charge, 11, NULL +}; +mmove_t guncmdr_move_attack_mortar_dodge = { FRAME_c_duckstep01, FRAME_c_duckstep06, guncmdr_frames_attack_mortar_dodge, guncmdr_grenade_mortar_resume }; + mframe_t guncmdr_frames_attack_back[] = { ai_charge, -2, NULL, @@ -397,6 +443,32 @@ mframe_t guncmdr_frames_attack_back[] = }; mmove_t guncmdr_move_attack_grenade_back = { FRAME_c_attack302, FRAME_c_attack321, guncmdr_frames_attack_back, guncmdr_grenade_finished }; +static void guncmdr_resume_back_attack(edict_t *self) +{ + self->monsterinfo.currentmove = &guncmdr_move_attack_grenade_back; + self->s.frame = self->count; +} + +mframe_t guncmdr_frames_attack_grenade_back_dodge_right[] = +{ + ai_charge, 10.2, NULL, + ai_charge, 18.0, NULL, + ai_charge, 7.0, NULL, + ai_charge, 7.2, NULL, + ai_charge, -2.0, NULL +}; +mmove_t guncmdr_move_attack_grenade_back_dodge_right = { FRAME_c_attack601, FRAME_c_attack605, guncmdr_frames_attack_grenade_back_dodge_right, guncmdr_resume_back_attack }; + +mframe_t guncmdr_frames_attack_grenade_back_dodge_left[] = +{ + ai_charge, 10.2, NULL, + ai_charge, 18.0, NULL, + ai_charge, 7.0, NULL, + ai_charge, 7.2, NULL, + ai_charge, -2.0, NULL +}; +mmove_t guncmdr_move_attack_grenade_back_dodge_left = { FRAME_c_attack701, FRAME_c_attack705, guncmdr_frames_attack_grenade_back_dodge_left, guncmdr_resume_back_attack }; + static void guncmdr_kick(edict_t *self) { int damage = M_MELEE_DMG_BASE + M_MELEE_DMG_ADDON * drone_damagelevel(self); @@ -522,12 +594,30 @@ static void guncmdr_dodge(edict_t *self, edict_t *attacker, vec3_t dir, int radi if (!self->enemy && G_EntIsAlive(attacker)) self->enemy = attacker; - if (radius && self->groundentity) + if (self->monsterinfo.currentmove == &guncmdr_move_attack_mortar) + { + self->count = self->s.frame; + self->monsterinfo.currentmove = &guncmdr_move_attack_mortar_dodge; + self->monsterinfo.dodge_time = level.time + 1.5; + } + else if (radius && self->groundentity) { self->monsterinfo.pausetime = level.time + 2.0; self->monsterinfo.currentmove = &guncmdr_move_jump; self->monsterinfo.dodge_time = level.time + 3.0; } + else if (self->monsterinfo.currentmove == &guncmdr_move_fire_chain || + self->monsterinfo.currentmove == &guncmdr_move_fire_chain_run) + { + self->monsterinfo.currentmove = (random() < 0.5) ? &guncmdr_move_fire_chain_dodge_left : &guncmdr_move_fire_chain_dodge_right; + self->monsterinfo.dodge_time = level.time + 1.5; + } + else if (self->monsterinfo.currentmove == &guncmdr_move_attack_grenade_back) + { + self->count = self->s.frame; + self->monsterinfo.currentmove = (random() < 0.5) ? &guncmdr_move_attack_grenade_back_dodge_left : &guncmdr_move_attack_grenade_back_dodge_right; + self->monsterinfo.dodge_time = level.time + 1.5; + } else { self->monsterinfo.currentmove = &guncmdr_move_duck_attack; @@ -535,41 +625,78 @@ static void guncmdr_dodge(edict_t *self, edict_t *attacker, vec3_t dir, int radi } } +static qboolean guncmdr_can_proactive_dodge(edict_t *self, float dist) +{ + return !(self->monsterinfo.aiflags & AI_STAND_GROUND) + && self->groundentity + && dist > 160 + && level.time >= self->monsterinfo.dodge_time; +} + +static void guncmdr_start_fire_chain_dodge(edict_t *self) +{ + self->monsterinfo.lefty = !self->monsterinfo.lefty; + self->monsterinfo.currentmove = self->monsterinfo.lefty ? &guncmdr_move_fire_chain_dodge_left : &guncmdr_move_fire_chain_dodge_right; + self->monsterinfo.dodge_time = level.time + 1.0; +} + static void guncmdr_attack(edict_t *self) { float dist; + float zdiff; + float r; if (!G_ValidTarget(self, self->enemy, true, true)) return; dist = entdist(self, self->enemy); + zdiff = fabs(self->s.origin[2] - self->enemy->s.origin[2]); + r = random(); if (dist <= MELEE_DISTANCE && self->monsterinfo.melee_finished < level.time) self->monsterinfo.currentmove = &guncmdr_move_attack_kick; - else if (dist <= GUNCMDR_GRENADE_RANGE || random() < 0.45) - self->monsterinfo.currentmove = &guncmdr_move_attack_chain; - else if (dist >= GUNCMDR_MORTAR_RANGE || fabs(self->s.origin[2] - self->enemy->s.origin[2]) > 64) + else if (self->groundentity && dist > 96 && r < 0.25) + { + self->monsterinfo.currentmove = &guncmdr_move_duck_attack; + self->monsterinfo.dodge_time = level.time + 2.0; + } + else if (guncmdr_can_proactive_dodge(self, dist) && r < 0.55) + guncmdr_start_fire_chain_dodge(self); + else if ((dist >= GUNCMDR_MORTAR_RANGE || zdiff > 96) && r < 0.75) self->monsterinfo.currentmove = &guncmdr_move_attack_mortar; - else + else if (!(self->monsterinfo.aiflags & AI_STAND_GROUND) && dist > GUNCMDR_GRENADE_RANGE && r < 0.90) self->monsterinfo.currentmove = &guncmdr_move_attack_grenade_back; + else + self->monsterinfo.currentmove = &guncmdr_move_attack_chain; M_DelayNextAttack(self, 0, true); } static void guncmdr_fire_chain(edict_t *self) { - if (!(self->monsterinfo.aiflags & AI_STAND_GROUND) - && G_ValidTarget(self, self->enemy, true, true) - && entdist(self, self->enemy) > GUNCMDR_CHAINGUN_RUN_RANGE) - self->monsterinfo.currentmove = &guncmdr_move_fire_chain_run; + if (!(self->monsterinfo.aiflags & AI_STAND_GROUND) && G_ValidTarget(self, self->enemy, true, true)) + { + float dist = entdist(self, self->enemy); + + if (guncmdr_can_proactive_dodge(self, dist)) + guncmdr_start_fire_chain_dodge(self); + else if (dist > GUNCMDR_CHAINGUN_RUN_RANGE) + self->monsterinfo.currentmove = &guncmdr_move_fire_chain_run; + else + self->monsterinfo.currentmove = &guncmdr_move_fire_chain; + } else self->monsterinfo.currentmove = &guncmdr_move_fire_chain; } static void guncmdr_refire_chain(edict_t *self) { - if (G_ValidTarget(self, self->enemy, true, true) && visible(self, self->enemy) && random() <= 0.5) + if (G_ValidTarget(self, self->enemy, true, true) && visible(self, self->enemy) && random() <= 0.75) { - if (!(self->monsterinfo.aiflags & AI_STAND_GROUND) && entdist(self, self->enemy) > GUNCMDR_CHAINGUN_RUN_RANGE) + float dist = entdist(self, self->enemy); + + if (guncmdr_can_proactive_dodge(self, dist) && random() < 0.65) + guncmdr_start_fire_chain_dodge(self); + else if (!(self->monsterinfo.aiflags & AI_STAND_GROUND) && dist > GUNCMDR_CHAINGUN_RUN_RANGE) self->monsterinfo.currentmove = &guncmdr_move_fire_chain_run; else self->monsterinfo.currentmove = &guncmdr_move_fire_chain; @@ -655,16 +782,27 @@ static void guncmdr_shrink(edict_t *self) gi.linkentity(self); } +static void guncmdr_footstep(edict_t *self) +{ + gi.sound(self, CHAN_BODY, sound_thud, 1, ATTN_NORM, 0); +} + +static void guncmdr_shrink_footstep(edict_t *self) +{ + guncmdr_footstep(self); + guncmdr_shrink(self); +} + mframe_t guncmdr_frames_death1[] = { + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 4, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, - ai_move, -6, NULL, - ai_move, -3, NULL, - ai_move, -5, NULL, - ai_move, 8, NULL, - ai_move, 6, guncmdr_shrink, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, @@ -678,9 +816,174 @@ mframe_t guncmdr_frames_death1[] = }; mmove_t guncmdr_move_death1 = { FRAME_c_death101, FRAME_c_death118, guncmdr_frames_death1, guncmdr_dead }; +mframe_t guncmdr_frames_death2[] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t guncmdr_move_death2 = { FRAME_c_death201, FRAME_c_death204, guncmdr_frames_death2, guncmdr_dead }; + +mframe_t guncmdr_frames_death3[] = +{ + ai_move, 20, NULL, + ai_move, 10, NULL, + ai_move, 10, guncmdr_shrink_footstep, + ai_move, 0, guncmdr_footstep, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t guncmdr_move_death3 = { FRAME_c_death301, FRAME_c_death321, guncmdr_frames_death3, guncmdr_dead }; + +mframe_t guncmdr_frames_death4[] = +{ + ai_move, -20, NULL, + ai_move, -16, NULL, + ai_move, -26, guncmdr_shrink_footstep, + ai_move, 0, guncmdr_footstep, + ai_move, -12, NULL, + ai_move, 16, NULL, + ai_move, 9.2, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t guncmdr_move_death4 = { FRAME_c_death401, FRAME_c_death436, guncmdr_frames_death4, guncmdr_dead }; + +mframe_t guncmdr_frames_death5[] = +{ + ai_move, -14, NULL, + ai_move, -2.7, NULL, + ai_move, -2.5, NULL, + ai_move, -4.6, guncmdr_footstep, + ai_move, -4.0, guncmdr_footstep, + ai_move, -1.5, NULL, + ai_move, 2.3, NULL, + ai_move, 2.5, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 3.5, NULL, + ai_move, 12.9, guncmdr_footstep, + ai_move, 3.8, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -2.1, NULL, + ai_move, -1.3, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 3.4, NULL, + ai_move, 5.7, NULL, + ai_move, 11.2, NULL, + ai_move, 0, guncmdr_footstep +}; +mmove_t guncmdr_move_death5 = { FRAME_c_death501, FRAME_c_death528, guncmdr_frames_death5, guncmdr_dead }; + +mframe_t guncmdr_frames_death6[] = +{ + ai_move, 0, guncmdr_shrink, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t guncmdr_move_death6 = { FRAME_c_death601, FRAME_c_death614, guncmdr_frames_death6, guncmdr_dead }; + +mframe_t guncmdr_frames_death7[] = +{ + ai_move, 30, NULL, + ai_move, 20, NULL, + ai_move, 16, guncmdr_shrink_footstep, + ai_move, 5, guncmdr_footstep, + ai_move, -6, NULL, + ai_move, -7, guncmdr_footstep, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, guncmdr_footstep, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, guncmdr_footstep, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t guncmdr_move_death7 = { FRAME_c_death701, FRAME_c_death730, guncmdr_frames_death7, guncmdr_dead }; + static void guncmdr_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) { int n; + vec3_t forward, dir; + float dot = 0; M_Notify(self); @@ -722,7 +1025,42 @@ static void guncmdr_die(edict_t *self, edict_t *inflictor, edict_t *attacker, in gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); self->deadflag = DEAD_DEAD; self->takedamage = DAMAGE_YES; - self->monsterinfo.currentmove = &guncmdr_move_death1; + + if (inflictor) + { + AngleVectors(self->s.angles, forward, NULL, NULL); + VectorSubtract(inflictor->s.origin, self->s.origin, dir); + dir[2] = 0; + VectorNormalize(dir); + dot = DotProduct(dir, forward); + } + + if (point[2] >= self->s.origin[2] + self->maxs[2] - 8 && self->velocity[2] < 65) + { + if (vrx_spawn_nonessential_ent(self->s.origin)) + ThrowGib(self, "models/monsters/gunner/gibs/head.md2", damage, GIB_ORGANIC); + self->monsterinfo.currentmove = &guncmdr_move_death5; + } + else if (dot < -0.40) + { + n = GetRandom(0, 2); + if (n == 0) + self->monsterinfo.currentmove = &guncmdr_move_death3; + else if (n == 1) + self->monsterinfo.currentmove = &guncmdr_move_death7; + else + self->monsterinfo.currentmove = &guncmdr_move_death6; + } + else + { + n = GetRandom(0, 2); + if (n == 0) + self->monsterinfo.currentmove = &guncmdr_move_death4; + else if (n == 1) + self->monsterinfo.currentmove = &guncmdr_move_death2; + else + self->monsterinfo.currentmove = &guncmdr_move_death1; + } if (self->activator && !self->activator->client) self->activator->num_monsters_real--; @@ -741,6 +1079,7 @@ void init_drone_guncmdr(edict_t *self) gi.soundindex("guncmdr/gcdratck2.wav"); gi.soundindex("guncmdr/gcdratck3.wav"); + gi.modelindex("models/proj/flechette/tris.md2"); self->movetype = MOVETYPE_STEP; self->solid = SOLID_BBOX; @@ -757,7 +1096,7 @@ void init_drone_guncmdr(edict_t *self) self->monsterinfo.control_cost = M_TANK_CONTROL_COST; self->monsterinfo.cost = M_TANK_COST; - self->health = M_TANK_INITIAL_HEALTH + M_TANK_ADDON_HEALTH * self->monsterinfo.level; + self->health = M_GUNCMDR_INITIAL_HEALTH + M_GUNCMDR_ADDON_HEALTH * self->monsterinfo.level; self->max_health = self->health; self->gib_health = -175; self->mass = 255; @@ -783,7 +1122,7 @@ void init_drone_guncmdr(edict_t *self) self->monsterinfo.aiflags |= AI_NO_CIRCLE_STRAFE; self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD; - self->monsterinfo.power_armor_power = M_TANK_INITIAL_ARMOR + M_TANK_ADDON_ARMOR * self->monsterinfo.level; + self->monsterinfo.power_armor_power = M_GUNCMDR_INITIAL_ARMOR + M_GUNCMDR_ADDON_ARMOR * self->monsterinfo.level; self->monsterinfo.max_armor = self->monsterinfo.power_armor_power; self->mtype = M_GUNCMDR; diff --git a/src/entities/drone/drone_infantry.c b/src/entities/drone/drone_infantry.c index 6c05c58c..e855d10a 100644 --- a/src/entities/drone/drone_infantry.c +++ b/src/entities/drone/drone_infantry.c @@ -26,6 +26,8 @@ static int sound_sight; static int sound_search; static int sound_idle; +#define INFANTRY_RUN_ATTACK_MIN_DIST 256 + mframe_t infantry_frames_stand [] = { @@ -214,12 +216,13 @@ void Infantry20mm(edict_t* self) vec3_t start, forward, right, vec; int damage, flash_number; const float range = M_20MM_RANGE_BASE + M_20MM_RANGE_ADDON * drone_damagelevel(self); + qboolean run_attack = (self->s.frame >= FRAME_run201 && self->s.frame <= FRAME_run208); damage = M_20MM_DMG_BASE + M_20MM_DMG_ADDON * drone_damagelevel(self); if (M_20MM_DMG_MAX && damage > M_20MM_DMG_MAX) damage = M_20MM_DMG_MAX; - if (self->s.frame == FRAME_attak111) + if (self->s.frame == FRAME_attak111 || run_attack) { flash_number = MZ2_INFANTRY_MACHINEGUN_1; MonsterAim(self, M_HITSCAN_CONT_ACC, 0, false, flash_number, forward, start); @@ -540,6 +543,70 @@ void infantry_fire(edict_t* self) M_DelayNextAttack(self, 0, true); } +static void infantry_run_attack_ai(edict_t* self, float dist) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + return; + if (!G_ValidTarget(self, self->enemy, true, true)) + return; + + ai_charge(self, 0); + M_MoveToGoal(self, dist); +} + +static void infantry_run_fire(edict_t* self) +{ + int range = M_20MM_RANGE_BASE + M_20MM_RANGE_ADDON * drone_damagelevel(self); + + if (M_20MM_RANGE_MAX && range > M_20MM_RANGE_MAX) + range = M_20MM_RANGE_MAX; + + if (!G_ValidTarget(self, self->enemy, true, true)) + return; + + if (entdist(self, self->enemy) > range) + return; + + Infantry20mm(self); + + M_DelayNextAttack(self, 0, true); +} + +extern mmove_t infantry_move_attack1; + +void infantry_attack4_refire(edict_t* self) +{ + if (level.time >= self->monsterinfo.pausetime) + { + self->monsterinfo.currentmove = &infantry_move_attack1; + self->monsterinfo.nextframe = FRAME_attak114; + } + else if ((self->monsterinfo.aiflags & AI_STAND_GROUND) + || !G_ValidTarget(self, self->enemy, true, true) + || entdist(self, self->enemy) < INFANTRY_RUN_ATTACK_MIN_DIST) + { + self->monsterinfo.currentmove = &infantry_move_attack1; + self->monsterinfo.nextframe = FRAME_attak110; + } + else + self->monsterinfo.nextframe = FRAME_run201; + + infantry_run_fire(self); +} + +mframe_t infantry_frames_attack4[] = +{ + infantry_run_attack_ai, 16, infantry_run_fire, + infantry_run_attack_ai, 16, infantry_run_fire, + infantry_run_attack_ai, 13, infantry_run_fire, + infantry_run_attack_ai, 10, infantry_run_fire, + infantry_run_attack_ai, 16, infantry_run_fire, + infantry_run_attack_ai, 16, infantry_run_fire, + infantry_run_attack_ai, 16, infantry_run_fire, + infantry_run_attack_ai, 16, infantry_attack4_refire +}; +mmove_t infantry_move_attack4 = { FRAME_run201, FRAME_run208, infantry_frames_attack4, infantry_run }; + mframe_t infantry_frames_attack1 [] = { //ai_charge, 4, NULL, @@ -650,7 +717,11 @@ void infantry_attack(edict_t* self) maxrange = M_20MM_RANGE_MAX; } else - maxrange = 512; + { + maxrange = M_20MM_RANGE_BASE + M_20MM_RANGE_ADDON * drone_damagelevel(self); + if (M_20MM_RANGE_MAX && maxrange > M_20MM_RANGE_MAX) + maxrange = M_20MM_RANGE_MAX; + } if (range > maxrange) return; @@ -671,6 +742,14 @@ void infantry_attack(edict_t* self) return; } + if (!(self->monsterinfo.aiflags & AI_STAND_GROUND) + && range >= INFANTRY_RUN_ATTACK_MIN_DIST && range <= maxrange) + { + self->monsterinfo.pausetime = level.time + 1.8 + random(); + self->monsterinfo.currentmove = &infantry_move_attack4; + return; + } + // Machinegun attack (default) self->monsterinfo.currentmove = &infantry_move_attack1; } diff --git a/src/entities/drone/drone_medic.c b/src/entities/drone/drone_medic.c index 0c14c992..8e42d27f 100644 --- a/src/entities/drone/drone_medic.c +++ b/src/entities/drone/drone_medic.c @@ -19,13 +19,82 @@ static int sound_hook_launch; static int sound_hook_hit; static int sound_hook_heal; static int sound_hook_retract; +static int commander_sound_idle1; +static int commander_sound_pain1; +static int commander_sound_pain2; +static int commander_sound_die; +static int commander_sound_sight; +static int commander_sound_hook_launch; +static int commander_sound_hook_hit; +static int commander_sound_hook_heal; +static int commander_sound_hook_retract; +static int commander_sound_spawn; + +#define MEDIC_COMMANDER_SUMMON_COUNT 2 +#define MEDIC_COMMANDER_SUMMON_COOLDOWN 8.0f +#define SPAWNGROW_LIFESPAN 1.0f void mymedic_refire (edict_t *self); void mymedic_heal (edict_t *self); +void medic_commander_attack(edict_t *self); + +static qboolean medic_is_commander(edict_t *self) +{ + return self && self->mtype == M_MEDIC_COMMANDER; +} + +static int medic_idle_sound(edict_t *self) +{ + return (medic_is_commander(self) && commander_sound_idle1) ? commander_sound_idle1 : sound_idle1; +} + +static int medic_pain_sound1(edict_t *self) +{ + return (medic_is_commander(self) && commander_sound_pain1) ? commander_sound_pain1 : sound_pain1; +} + +static int medic_pain_sound2(edict_t *self) +{ + return (medic_is_commander(self) && commander_sound_pain2) ? commander_sound_pain2 : sound_pain2; +} + +static int medic_die_sound(edict_t *self) +{ + return (medic_is_commander(self) && commander_sound_die) ? commander_sound_die : sound_die; +} + +static int medic_sight_sound(edict_t *self) +{ + return (medic_is_commander(self) && commander_sound_sight) ? commander_sound_sight : sound_sight; +} + +static int medic_hook_launch_sound(edict_t *self) +{ + return (medic_is_commander(self) && commander_sound_hook_launch) ? commander_sound_hook_launch : sound_hook_launch; +} + +static int medic_hook_retract_sound(edict_t *self) +{ + return (medic_is_commander(self) && commander_sound_hook_retract) ? commander_sound_hook_retract : sound_hook_retract; +} + +static float medic_clampf(float value, float min_value, float max_value) +{ + if (value < min_value) + return min_value; + if (value > max_value) + return max_value; + return value; +} + +static float medic_lerpf(float a, float b, float t) +{ + return a + (b - a) * t; +} void mymedic_idle (edict_t *self) { - gi.sound (self, CHAN_VOICE, sound_idle1, 1, ATTN_IDLE, 0); + gi.sound (self, CHAN_VOICE, medic_idle_sound(self), 1, ATTN_IDLE, 0); } @@ -262,7 +331,7 @@ mmove_t medic_move_pain_long = { FRAME_painb1, FRAME_painb15, medic_frames_pain_ void medic_pain(edict_t* self, edict_t* other, float kick, int damage) { if (self->health < (self->max_health / 2)) - self->s.skinnum = 1; + self->s.skinnum |= 1; // we're already in a pain state if (self->monsterinfo.currentmove == &medic_move_pain_short || @@ -284,9 +353,9 @@ void medic_pain(edict_t* self, edict_t* other, float kick, int damage) return; if (random() < 0.5) - gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + gi.sound(self, CHAN_VOICE, medic_pain_sound1(self), 1, ATTN_NORM, 0); else { - gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + gi.sound(self, CHAN_VOICE, medic_pain_sound2(self), 1, ATTN_NORM, 0); } if (self->monsterinfo.currentmove == &medic_move_walk || @@ -376,7 +445,7 @@ void mymedic_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int dama DroneList_Remove(self); // regular death - gi.sound (self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); + gi.sound (self, CHAN_VOICE, medic_die_sound(self), 1, ATTN_NORM, 0); self->deadflag = DEAD_DEAD; self->takedamage = DAMAGE_YES; @@ -416,7 +485,7 @@ void mymedic_jump_takeoff (edict_t *self) { vec3_t v; - gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); + gi.sound (self, CHAN_VOICE, medic_sight_sound(self), 1, ATTN_NORM, 0); VectorSubtract(self->monsterinfo.dir, self->s.origin, v); v[2] = 0; VectorNormalize(v); @@ -615,12 +684,12 @@ mmove_t mymedic_move_attackBlaster = {FRAME_attack1, FRAME_attack14, mymedic_fra void mymedic_hook_launch (edict_t *self) { - gi.sound (self, CHAN_WEAPON, sound_hook_launch, 1, ATTN_NORM, 0); + gi.sound (self, CHAN_WEAPON, medic_hook_launch_sound(self), 1, ATTN_NORM, 0); } void mymedic_hook_retract (edict_t *self) { - gi.sound (self, CHAN_WEAPON, sound_hook_retract, 1, ATTN_NORM, 0); + gi.sound (self, CHAN_WEAPON, medic_hook_retract_sound(self), 1, ATTN_NORM, 0); if (!self->enemy) return; @@ -998,6 +1067,299 @@ mframe_t mymedic_frames_attackCable [] = }; mmove_t mymedic_move_attackCable = {FRAME_attack33, FRAME_attack60, mymedic_frames_attackCable, mymedic_heal}; +static void spawngrow_beam_think(edict_t *self); + +static void spawngrow_beam_pos(edict_t *self, vec3_t pos) +{ + float theta; + float phi; + float radius; + vec3_t dir; + + if (!self->owner || !self->owner->inuse) + { + VectorCopy(self->s.origin, pos); + return; + } + + theta = random() * 2.0f * (float)M_PI; + phi = acosf(crandom()); + dir[0] = sinf(phi) * cosf(theta); + dir[1] = sinf(phi) * sinf(theta); + dir[2] = cosf(phi); + + radius = self->owner->s.scale; + if (radius <= 0.0f) + radius = 1.0f; + radius *= 9.0f; + + VectorMA(self->owner->s.origin, radius, dir, pos); +} + +static void spawngrow_think(edict_t *self) +{ + float t; + int i; + + if (level.time >= self->timestamp) + { + if (self->target_ent && self->target_ent->inuse) + G_FreeEdict(self->target_ent); + G_FreeEdict(self); + return; + } + + for (i = 0; i < 3; i++) + self->s.angles[i] += self->avelocity[i] * FRAMETIME; + + t = 1.0f - ((level.time - self->teleport_time) / self->wait); + t = medic_clampf(t, 0.0f, 1.0f); + self->s.scale = medic_clampf(medic_lerpf(self->decel, self->accel, t) / 16.0f, 0.001f, 16.0f); + self->s.alpha = t * t; + + self->nextthink = level.time + FRAMETIME; +} + +static void spawngrow_beam_think(edict_t *self) +{ + if (!self->owner || !self->owner->inuse) + { + G_FreeEdict(self); + return; + } + + spawngrow_beam_pos(self, self->s.old_origin); + gi.linkentity(self); + self->nextthink = level.time + FRAMETIME; +} + +static void SpawnGrow_Spawn(vec3_t startpos, float start_size, float end_size) +{ + edict_t *ent; + edict_t *beam; + + ent = G_Spawn(); + VectorCopy(startpos, ent->s.origin); + VectorSet(ent->s.angles, GetRandom(0, 359), GetRandom(0, 359), GetRandom(0, 359)); + VectorSet(ent->avelocity, + (280.0f + random() * 80.0f) * 2.0f, + (280.0f + random() * 80.0f) * 2.0f, + (280.0f + random() * 80.0f) * 2.0f); + + ent->solid = SOLID_NOT; + ent->movetype = MOVETYPE_NONE; + ent->classname = "spawngro"; + ent->s.modelindex = gi.modelindex("models/items/spawngro3/tris.md2"); + ent->s.skinnum = 1; + ent->s.renderfx |= RF_IR_VISIBLE | RF_TRANSLUCENT; + ent->accel = start_size; + ent->decel = end_size; + ent->think = spawngrow_think; + ent->s.scale = medic_clampf(start_size / 16.0f, 0.001f, 8.0f); + ent->s.alpha = 1.0f; + ent->teleport_time = level.time; + ent->wait = SPAWNGROW_LIFESPAN; + ent->timestamp = level.time + SPAWNGROW_LIFESPAN; + ent->nextthink = level.time + FRAMETIME; + gi.linkentity(ent); + + beam = ent->target_ent = G_Spawn(); + beam->solid = SOLID_NOT; + beam->movetype = MOVETYPE_NONE; + beam->s.modelindex = 1; + beam->s.renderfx = RF_BEAM_LIGHTNING | RF_TRANSLUCENT; + beam->s.frame = 1; + beam->s.skinnum = 0x30303030; + beam->classname = "spawngro_beam"; + beam->owner = ent; + VectorCopy(ent->s.origin, beam->s.origin); + spawngrow_beam_pos(beam, beam->s.old_origin); + beam->think = spawngrow_beam_think; + beam->nextthink = level.time + FRAMETIME; + gi.linkentity(beam); +} + +static void medic_commander_start_spawn(edict_t *self) +{ + if (commander_sound_spawn) + gi.sound(self, CHAN_WEAPON, commander_sound_spawn, 1, ATTN_NORM, 0); + + self->monsterinfo.nextframe = FRAME_attack48; +} + +static void medic_commander_cleanup_failed_spawn(edict_t *owner, edict_t *spawned) +{ + if (owner && owner->client) + layout_remove_tracked_entity(&owner->client->layout, spawned); + + DroneList_Remove(spawned); + AI_EnemyRemoved(spawned); + G_FreeEdict(spawned); +} + +static qboolean medic_commander_valid_spawn_spot(edict_t *self, vec3_t mins, vec3_t maxs, vec3_t spot) +{ + if (G_IsValidLocation(self, spot, mins, maxs)) + return true; + + spot[2] += 24; + if (G_IsValidLocation(self, spot, mins, maxs)) + return true; + + spot[2] -= 48; + return G_IsValidLocation(self, spot, mins, maxs); +} + +static qboolean medic_commander_find_spawn_spot(edict_t *self, vec3_t mins, vec3_t maxs, float side, vec3_t spot) +{ + vec3_t forward, right; + + AngleVectors(self->s.angles, forward, right, NULL); + + VectorCopy(self->s.origin, spot); + VectorMA(spot, 96, forward, spot); + VectorMA(spot, side, right, spot); + spot[2] += 8; + if (medic_commander_valid_spawn_spot(self, mins, maxs, spot)) + return true; + + VectorCopy(self->s.origin, spot); + VectorMA(spot, -72, forward, spot); + VectorMA(spot, side, right, spot); + spot[2] += 8; + if (medic_commander_valid_spawn_spot(self, mins, maxs, spot)) + return true; + + VectorCopy(self->s.origin, spot); + VectorMA(spot, 64, forward, spot); + VectorMA(spot, -side, right, spot); + spot[2] += 8; + return medic_commander_valid_spawn_spot(self, mins, maxs, spot); +} + +static qboolean medic_commander_spawn_gunner(edict_t *self, float side) +{ + edict_t *owner = self->activator; + edict_t *gunner; + vec3_t spot; + + if (!owner || !owner->inuse) + return false; + + if (owner->client && owner->num_monsters + M_GUNNER_CONTROL_COST > MAX_MONSTERS) + return false; + + gunner = G_Spawn(); + gunner->mtype = M_GUNNER; + gunner->activator = owner; + gunner->monsterinfo.level = self->monsterinfo.level; + + if (!M_Initialize(owner, gunner, 0.0f)) + { + G_FreeEdict(gunner); + return false; + } + + if (!medic_commander_find_spawn_spot(self, gunner->mins, gunner->maxs, side, spot)) + { + medic_commander_cleanup_failed_spawn(owner, gunner); + return false; + } + + gunner->monsterinfo.cost = 0; + gunner->s.effects |= EF_PLASMA; + VectorCopy(spot, gunner->s.origin); + VectorCopy(spot, gunner->s.old_origin); + VectorCopy(self->s.angles, gunner->s.angles); + gunner->nextthink = level.time + FRAMETIME; + gunner->monsterinfo.attack_finished = level.time + 1.0; + + gi.linkentity(gunner); + + owner->num_monsters += gunner->monsterinfo.control_cost; + owner->num_monsters_real++; + + if (G_ValidTarget(gunner, self->enemy, true, true)) + { + gunner->enemy = self->enemy; + if (gunner->monsterinfo.run) + gunner->monsterinfo.run(gunner); + } + else if (gunner->monsterinfo.stand) + gunner->monsterinfo.stand(gunner); + + return true; +} + +static void medic_commander_spawngrows(edict_t *self) +{ + vec3_t mins, maxs, size, spot, effect_origin; + float radius; + int i; + + VectorSet(mins, -16, -16, -24); + VectorSet(maxs, 16, 16, 32); + VectorSubtract(maxs, mins, size); + radius = VectorLength(size) * 0.5f; + + for (i = 0; i < MEDIC_COMMANDER_SUMMON_COUNT; i++) + { + float side = (i & 1) ? 56 : -56; + + if (!medic_commander_find_spawn_spot(self, mins, maxs, side, spot)) + continue; + + VectorAdd(mins, maxs, effect_origin); + VectorAdd(spot, effect_origin, effect_origin); + SpawnGrow_Spawn(effect_origin, radius, radius * 2.0f); + } +} + +static void medic_commander_finish_spawn(edict_t *self) +{ + int spawned = 0; + int i; + + for (i = 0; i < MEDIC_COMMANDER_SUMMON_COUNT; i++) + { + float side = (i & 1) ? 56 : -56; + + if (medic_commander_spawn_gunner(self, side)) + spawned++; + } + + if (spawned) + self->monsterinfo.melee_finished = level.time + MEDIC_COMMANDER_SUMMON_COOLDOWN; +} + +mframe_t medic_commander_frames_callReinforcements[] = +{ + ai_charge, 2, NULL, //209 + ai_charge, 3, NULL, + ai_charge, 5, NULL, + ai_charge, 4, NULL, + ai_charge, 5, NULL, + ai_charge, 5, NULL, + ai_charge, 6, NULL, + ai_charge, 4, NULL, + ai_charge, 0, NULL, //217 + ai_move, 0, medic_commander_start_spawn, //218 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, //224 + ai_charge, 0, medic_commander_spawngrows, //225 + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -15, medic_commander_finish_spawn, //228 + ai_move, -1.5, NULL, + ai_move, -1.2, NULL, + ai_move, -3, NULL +}; +mmove_t medic_commander_move_callReinforcements = {FRAME_attack33, FRAME_attack55, medic_commander_frames_callReinforcements, mymedic_run}; + void drone_wakeallies (edict_t *self); // search for nearby enemies, return true if one is found // this is to make the medic stop healing if there is a higher priority target @@ -1077,6 +1439,30 @@ void mymedic_attack(edict_t *self) M_DelayNextAttack(self, 0, true); } +void medic_commander_attack(edict_t *self) +{ + edict_t *owner = self->activator; + float dist; + + if (!self->enemy || !self->enemy->inuse) + return; + + dist = entdist(self, self->enemy); + + if (dist > 150 + && owner && owner->inuse + && (!owner->client || owner->num_monsters + M_GUNNER_CONTROL_COST <= MAX_MONSTERS) + && level.time >= self->monsterinfo.melee_finished + && random() < 0.6) + { + self->monsterinfo.currentmove = &medic_commander_move_callReinforcements; + M_DelayNextAttack(self, 0, true); + return; + } + + mymedic_attack(self); +} + void mymedic_melee (edict_t *self) { // just here to keep monster from circle strafing @@ -1084,7 +1470,7 @@ void mymedic_melee (edict_t *self) void mymedic_sight (edict_t *self, edict_t *other) { - gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); + gi.sound (self, CHAN_VOICE, medic_sight_sound(self), 1, ATTN_NORM, 0); } /*QUAKED monster_medic (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight @@ -1164,3 +1550,34 @@ void init_drone_medic (edict_t *self) self->nextthink = level.time + 0.1; //self->activator->num_monsters += self->monsterinfo.control_cost; } + +void init_drone_medic_commander(edict_t *self) +{ + init_drone_medic(self); + + commander_sound_idle1 = gi.soundindex("medic_commander/medidle.wav"); + commander_sound_pain1 = gi.soundindex("medic_commander/medpain1.wav"); + commander_sound_pain2 = gi.soundindex("medic_commander/medpain2.wav"); + commander_sound_die = gi.soundindex("medic_commander/meddeth.wav"); + commander_sound_sight = gi.soundindex("medic_commander/medsght.wav"); + commander_sound_hook_launch = gi.soundindex("medic_commander/medatck2c.wav"); + commander_sound_hook_hit = gi.soundindex("medic_commander/medatck3a.wav"); + commander_sound_hook_heal = gi.soundindex("medic_commander/medatck4a.wav"); + commander_sound_hook_retract = gi.soundindex("medic_commander/medatck5a.wav"); + commander_sound_spawn = gi.soundindex("medic_commander/monsterspawn1.wav"); + gi.soundindex("tank/tnkatck3.wav"); + gi.modelindex("models/items/spawngro3/tris.md2"); + + self->mtype = M_MEDIC_COMMANDER; + self->s.skinnum = 2; + self->mass = 600; + self->yaw_speed = 40; + self->health = M_MEDIC_COMMANDER_INITIAL_HEALTH + M_MEDIC_COMMANDER_ADDON_HEALTH * self->monsterinfo.level; + self->max_health = self->health; + self->monsterinfo.power_armor_power = M_MEDIC_COMMANDER_INITIAL_ARMOR + M_MEDIC_COMMANDER_ADDON_ARMOR * self->monsterinfo.level; + self->monsterinfo.max_armor = self->monsterinfo.power_armor_power; + self->monsterinfo.control_cost = M_TANK_CONTROL_COST; + self->monsterinfo.cost = M_TANK_COST; + self->monsterinfo.attack = medic_commander_attack; + self->monsterinfo.melee_finished = level.time + 1.0; +} diff --git a/src/entities/drone/drone_misc.c b/src/entities/drone/drone_misc.c index b5e1afab..136c664b 100644 --- a/src/entities/drone/drone_misc.c +++ b/src/entities/drone/drone_misc.c @@ -17,6 +17,7 @@ void init_drone_bitch (edict_t *self); void init_drone_bitch_heat (edict_t *self); void init_drone_brain (edict_t *self); void init_drone_medic (edict_t *self); +void init_drone_medic_commander(edict_t *self); void init_drone_tank (edict_t *self); void init_drone_mutant (edict_t *self); void init_drone_decoy (edict_t *self); @@ -40,6 +41,7 @@ void init_drone_gladb(edict_t* self); void init_drone_gladc(edict_t* self); void init_drone_stalker(edict_t* self); void init_drone_gekk(edict_t* self); +void init_drone_arachnid(edict_t* self); void init_baron_fire(edict_t* self); void init_skeleton(edict_t* self); void init_golem(edict_t* self); @@ -870,6 +872,8 @@ edict_t *vrx_create_drone_from_ent(edict_t *drone, edict_t *ent, enum dronespawn case DS_GLADC: init_drone_gladc(drone); break; case DS_STALKER: init_drone_stalker(drone); break; case DS_GEKK: init_drone_gekk(drone); break; + case DS_ARACHNID: init_drone_arachnid(drone); break; + case DS_MEDIC_COMMANDER: init_drone_medic_commander(drone); break; // bosses case DS_COMMANDER: init_drone_commander(drone); break; @@ -1765,7 +1769,8 @@ qboolean M_Regenerate (edict_t *self, int regen_frames, int delay, float mult, q self->s.skinnum &= ~1; if (self->mtype != M_COMMANDER && self->mtype != M_GUNCMDR && self->mtype != M_DAEDALUS && self->mtype != M_GLADB && self->mtype != M_GLADC - && self->mtype != M_CHICK_HEAT && self->mtype != M_SOLDIER && self->mtype != M_STALKER) + && self->mtype != M_CHICK_HEAT && self->mtype != M_MEDIC_COMMANDER + && self->mtype != M_SOLDIER && self->mtype != M_STALKER) self->s.skinnum &= ~2; } @@ -2049,6 +2054,7 @@ qboolean M_Initialize (edict_t *ent, edict_t *monster, float dur_bonus) case M_CHICK_HEAT: init_drone_bitch_heat(monster); break; case M_BRAIN: init_drone_brain(monster); break; case M_MEDIC: init_drone_medic(monster); break; + case M_MEDIC_COMMANDER: init_drone_medic_commander(monster); break; case M_MUTANT: init_drone_mutant(monster); break; case M_PARASITE: init_drone_parasite(monster); break; case M_TANK: init_drone_tank(monster); break; @@ -2068,6 +2074,7 @@ qboolean M_Initialize (edict_t *ent, edict_t *monster, float dur_bonus) case M_GLADC: init_drone_gladc(monster); break; case M_STALKER: init_drone_stalker(monster); break; case M_GEKK: init_drone_gekk(monster); break; + case M_ARACHNID: init_drone_arachnid(monster); break; case M_SKELETON: init_skeleton(monster); break; case M_GOLEM: init_golem(monster); break; default: return false; @@ -2182,7 +2189,12 @@ qboolean M_SetBoundingBox (int mtype, vec3_t boxmin, vec3_t boxmax) VectorSet(boxmin, -18, -18, -24); VectorSet(boxmax, 18, 18, 24); break; + case M_ARACHNID: + VectorSet(boxmin, -48, -48, -20); + VectorSet(boxmax, 48, 48, 48); + break; case M_MEDIC: + case M_MEDIC_COMMANDER: case M_MUTANT: VectorSet (boxmin, -24, -24, -24); VectorSet (boxmax, 24, 24, 32); @@ -2218,6 +2230,7 @@ char *GetMonsterKindString (int mtype) case M_CHICK: return "Praetor"; case M_CHICK_HEAT: return "Heat Praetor"; case M_MEDIC: return "Medic"; + case M_MEDIC_COMMANDER: return "Medic Commander"; case M_MUTANT: return "Mutant"; case M_PARASITE: return "Parasite"; case M_BERSERK: return "Berserker"; @@ -2259,6 +2272,7 @@ char *GetMonsterKindString (int mtype) case M_GLADC: return "Gladiator Plasma"; case M_STALKER: return "Stalker"; case M_GEKK: return "Gekk"; + case M_ARACHNID: return "Arachnid"; case M_SKELETON: return "Skeleton"; case M_GOLEM: return "Golem"; case M_BARON_FIRE: return "Fire Baron"; @@ -2796,7 +2810,7 @@ void Cmd_Drone_f (edict_t *ent) ent->selected[i]->monsterinfo.sight_range = 256; if (ent->selected[i]->mtype == M_PARASITE) ent->selected[i]->monsterinfo.sight_range = 128; - if (ent->selected[i]->mtype == M_MEDIC) + if (ent->selected[i]->mtype == M_MEDIC || ent->selected[i]->mtype == M_MEDIC_COMMANDER) ent->selected[i]->monsterinfo.sight_range = 256; if (ent->selected[i]->mtype == M_BERSERK) ent->selected[i]->monsterinfo.sight_range = 128; @@ -2843,7 +2857,7 @@ void Cmd_Drone_f (edict_t *ent) if (!Q_strcasecmp(s, "help")) { safe_cprintf(ent, PRINT_HIGH, "Monster summoning:\n"); - safe_cprintf(ent, PRINT_HIGH, "monster [gunner|parasite|brain|praetor|praetor_heat|chick_heat|medic|tank|mutant|gladiator|gladb|gladc|berserker|soldier|enforcer|flyer|floater|hover|daedalus|stalker|gekk|shambler|redmutant|runnertank|guncmdr]\n"); + safe_cprintf(ent, PRINT_HIGH, "monster [gunner|parasite|brain|praetor|praetor_heat|chick_heat|medic|tank|mutant|gladiator|gladb|gladc|berserker|soldier|enforcer|flyer|floater|hover|daedalus|stalker|gekk|arachnid|shambler|redmutant|runnertank|guncmdr]\n"); safe_cprintf(ent, PRINT_HIGH, "Monster utility commands:\n"); safe_cprintf(ent, PRINT_HIGH, "monster [remove|command|follow me|count|attack]\n"); return; @@ -2910,6 +2924,8 @@ void Cmd_Drone_f (edict_t *ent) vrx_create_new_drone(ent, DS_STALKER, false, true, 0); else if (!Q_strcasecmp(s, "gekk")) vrx_create_new_drone(ent, DS_GEKK, false, true, 0); + else if (!Q_strcasecmp(s, "arachnid")) + vrx_create_new_drone(ent, DS_ARACHNID, false, true, 0); else if (!Q_strcasecmp(s, "golem") && ent->myskills.administrator) vrx_create_new_drone(ent, DS_GOLEM, false, true, 0); //else if (!Q_strcasecmp(s, "baron fire") && ent->myskills.administrator) diff --git a/src/entities/drone/drone_redmutant.c b/src/entities/drone/drone_redmutant.c index c79a050d..7987057b 100644 --- a/src/entities/drone/drone_redmutant.c +++ b/src/entities/drone/drone_redmutant.c @@ -285,7 +285,7 @@ static void redmutant_jump_takeoff(edict_t *self) AngleVectors(self->s.angles, forward, NULL, NULL); self->s.origin[2] += 1; - if (random() < 0.48) + if (random() < 0.28) high_jump = true; VectorScale(forward, 1125, self->velocity); @@ -621,7 +621,7 @@ void init_drone_redmutant(edict_t *self) VectorSet(self->mins, -18, -18, -24); VectorSet(self->maxs, 18, 18, 30); - self->health = M_MUTANT_INITIAL_HEALTH + M_MUTANT_ADDON_HEALTH * self->monsterinfo.level; + self->health = M_REDMUTANT_INITIAL_HEALTH + M_REDMUTANT_ADDON_HEALTH * self->monsterinfo.level; self->max_health = self->health; self->gib_health = -BASE_GIB_HEALTH; self->mass = 350; @@ -642,7 +642,8 @@ void init_drone_redmutant(edict_t *self) self->monsterinfo.aiflags |= AI_NO_CIRCLE_STRAFE; self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD; - self->monsterinfo.power_armor_power = M_MUTANT_INITIAL_ARMOR + M_MUTANT_ADDON_ARMOR * self->monsterinfo.level; + self->monsterinfo.power_armor_power = M_REDMUTANT_INITIAL_ARMOR + M_REDMUTANT_ADDON_ARMOR * self->monsterinfo.level; + self->monsterinfo.max_armor = self->monsterinfo.power_armor_power; self->monsterinfo.control_cost = M_MUTANT_CONTROL_COST; self->monsterinfo.cost = M_MUTANT_COST; self->mtype = M_REDMUTANT; diff --git a/src/entities/drone/drone_runnertank.c b/src/entities/drone/drone_runnertank.c index df5c5dcf..1b55f94d 100644 --- a/src/entities/drone/drone_runnertank.c +++ b/src/entities/drone/drone_runnertank.c @@ -246,16 +246,16 @@ static void runnertank_plasma(edict_t *self) return; flash_number = MZ2_TANK_MACHINEGUN_1 + (self->s.frame - FRAME_attak406); - damage = M_BLASTER_DMG_BASE + M_BLASTER_DMG_ADDON * drone_damagelevel(self); - if (M_BLASTER_DMG_MAX && damage > M_BLASTER_DMG_MAX) - damage = M_BLASTER_DMG_MAX; - speed = M_BLASTER_SPEED_BASE + M_BLASTER_SPEED_ADDON * drone_damagelevel(self); - if (M_BLASTER_SPEED_MAX && speed > M_BLASTER_SPEED_MAX) - speed = M_BLASTER_SPEED_MAX; + damage = M_PLASMA_DMG_BASE + M_PLASMA_DMG_ADDON * drone_damagelevel(self); + if (M_PLASMA_DMG_MAX && damage > M_PLASMA_DMG_MAX) + damage = M_PLASMA_DMG_MAX; + speed = M_PLASMA_SPEED_BASE + M_PLASMA_SPEED_ADDON * drone_damagelevel(self); + if (M_PLASMA_SPEED_MAX && speed > M_PLASMA_SPEED_MAX) + speed = M_PLASMA_SPEED_MAX; radius_damage = max(1, damage / 2); MonsterAim(self, M_PROJECTILE_ACC, speed, false, flash_number, forward, start); - fire_plasma(self, start, forward, damage, speed, 40, radius_damage); + fire_plasma(self, start, forward, damage, speed, M_PLASMA_DAMAGE_RADIUS, radius_damage); } static void runnertank_strike_sound(edict_t *self) @@ -831,7 +831,7 @@ void init_drone_runnertank(edict_t *self) gi.soundindex("tank/tnkatk2e.wav"); gi.soundindex("tank/tnkatck3.wav"); - self->health = M_TANK_INITIAL_HEALTH + M_TANK_ADDON_HEALTH * self->monsterinfo.level; + self->health = M_RUNNERTANK_INITIAL_HEALTH + M_RUNNERTANK_ADDON_HEALTH * self->monsterinfo.level; self->max_health = self->health; self->gib_health = -200; self->mass = 500; @@ -851,7 +851,8 @@ void init_drone_runnertank(edict_t *self) self->monsterinfo.aiflags |= AI_NO_CIRCLE_STRAFE; self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD; - self->monsterinfo.power_armor_power = M_TANK_INITIAL_ARMOR + M_TANK_ADDON_ARMOR * self->monsterinfo.level; + self->monsterinfo.power_armor_power = M_RUNNERTANK_INITIAL_ARMOR + M_RUNNERTANK_ADDON_ARMOR * self->monsterinfo.level; + self->monsterinfo.max_armor = self->monsterinfo.power_armor_power; self->monsterinfo.control_cost = M_TANK_CONTROL_COST; self->monsterinfo.cost = M_TANK_COST; self->mtype = M_RUNNERTANK; diff --git a/src/entities/drone/drone_stalker.c b/src/entities/drone/drone_stalker.c index 657e0ec5..32b95f20 100644 --- a/src/entities/drone/drone_stalker.c +++ b/src/entities/drone/drone_stalker.c @@ -109,8 +109,12 @@ static void stalker_fire_ionripper(edict_t *self) if (!G_EntExists(self->enemy)) return; - damage = IONRIPPER_INITIAL_DAMAGE + IONRIPPER_ADDON_DAMAGE * drone_damagelevel(self); - speed = IONRIPPER_INITIAL_SPEED + IONRIPPER_ADDON_SPEED * drone_damagelevel(self); + damage = M_BLASTER2_DMG_BASE + M_BLASTER2_DMG_ADDON * drone_damagelevel(self); + if (M_BLASTER2_DMG_MAX && damage > M_BLASTER2_DMG_MAX) + damage = M_BLASTER2_DMG_MAX; + speed = M_BLASTER2_SPEED_BASE + M_BLASTER2_SPEED_ADDON * drone_damagelevel(self); + if (M_BLASTER2_SPEED_MAX && speed > M_BLASTER2_SPEED_MAX) + speed = M_BLASTER2_SPEED_MAX; AngleVectors(self->s.angles, forward, right, NULL); VectorSet(offset, 16, 0, 6); @@ -296,15 +300,15 @@ void init_drone_stalker(edict_t *self) VectorSet(self->mins, -28, -28, -18); VectorSet(self->maxs, 28, 28, 18); - self->health = M_PARASITE_INITIAL_HEALTH + M_PARASITE_ADDON_HEALTH * self->monsterinfo.level; + self->health = M_STALKER_INITIAL_HEALTH + M_STALKER_ADDON_HEALTH * self->monsterinfo.level; self->max_health = self->health; self->gib_health = -125; self->mass = 250; self->mtype = M_STALKER; - //self->monsterinfo.power_armor_type = POWER_ARMOR_SCREEN; - //self->monsterinfo.power_armor_power = M_BERSERKER_INITIAL_ARMOR + M_BERSERKER_ADDON_ARMOR * self->monsterinfo.level; - //self->monsterinfo.max_armor = self->monsterinfo.power_armor_power; + self->monsterinfo.power_armor_type = POWER_ARMOR_SCREEN; + self->monsterinfo.power_armor_power = M_STALKER_INITIAL_ARMOR + M_STALKER_ADDON_ARMOR * self->monsterinfo.level; + self->monsterinfo.max_armor = self->monsterinfo.power_armor_power; self->monsterinfo.control_cost = M_BERSERKER_CONTROL_COST; self->monsterinfo.cost = M_DEFAULT_COST; diff --git a/src/entities/drone/g_monster.c b/src/entities/drone/g_monster.c index d0bab532..f1d20ca6 100644 --- a/src/entities/drone/g_monster.c +++ b/src/entities/drone/g_monster.c @@ -332,6 +332,7 @@ static void heat_think(edict_t *self) qboolean monster_fire_heat(edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype, float turn_fraction) { float chance; + float radius; edict_t *heat; if (que_typeexists(self->curses, AURA_HOLYFREEZE) && random() <= 0.5) @@ -344,6 +345,12 @@ qboolean monster_fire_heat(edict_t *self, vec3_t start, vec3_t dir, int damage, return false; } + radius = damage; + if (damage > 125) + radius = 125; + if (speed > 1000) + speed = 1000; + damage = vrx_increase_monster_damage_by_talent(self->activator, damage); if (speed < 1) speed = 1; @@ -372,7 +379,7 @@ qboolean monster_fire_heat(edict_t *self, vec3_t start, vec3_t dir, int damage, heat->think = heat_think; heat->dmg = damage; heat->radius_dmg = damage; - heat->dmg_radius = damage; + heat->dmg_radius = radius; heat->s.sound = gi.soundindex("weapons/rockfly.wav"); heat->classname = "heat rocket"; @@ -1046,4 +1053,4 @@ void M_FlyCheck (edict_t *self) void AttackFinished (edict_t *self, float time) { self->monsterinfo.attack_finished = level.time + time; -} \ No newline at end of file +} diff --git a/src/g_local.h b/src/g_local.h index 595232ec..2c972564 100644 --- a/src/g_local.h +++ b/src/g_local.h @@ -1515,6 +1515,8 @@ enum mtype_t { M_STALKER = 34, M_GEKK = 35, M_CHICK_HEAT = 36, + M_ARACHNID = 37, + M_MEDIC_COMMANDER = 38, M_MINISENTRY = 100, M_SENTRY = 101, M_BFG_SENTRY = 102, @@ -1622,6 +1624,8 @@ enum dronespawn_t { DS_STALKER = 25, DS_GEKK = 26, DS_BITCH_HEAT = 27, + DS_ARACHNID = 28, + DS_MEDIC_COMMANDER = 29, DS_COMMANDER = 30, DS_MAKRON = 31, DS_BARON_FIRE = 32, diff --git a/src/gamemodes/invasion.c b/src/gamemodes/invasion.c index 289cec85..06cc430b 100644 --- a/src/gamemodes/invasion.c +++ b/src/gamemodes/invasion.c @@ -44,6 +44,7 @@ static constexpr int SET_EASY_MODE_MONSTERS[] = { DS_DAEDALUS, DS_STALKER, DS_GEKK, + DS_ARACHNID, DS_BITCH_HEAT, }; @@ -52,7 +53,7 @@ constexpr int SET_EASY_MODE_MONSTERS_COUNT = sizeof(SET_EASY_MODE_MONSTERS) / si // parasite, brain, medic, tank, mutant, gladiator, berserker, infantry, hover static constexpr int SET_HARD_MODE_MONSTERS[] = { DS_PARASITE, DS_BRAIN, DS_MEDIC, DS_TANK, DS_MUTANT, DS_GLADIATOR, DS_BERSERK, DS_INFANTRY, DS_HOVER, - DS_REDMUTANT, DS_RUNNERTANK, DS_GUNCMDR, DS_DAEDALUS, DS_GLADB, DS_GLADC, DS_STALKER, DS_GEKK, DS_BITCH_HEAT + DS_REDMUTANT, DS_RUNNERTANK, DS_GUNCMDR, DS_DAEDALUS, DS_GLADB, DS_GLADC, DS_STALKER, DS_GEKK, DS_ARACHNID, DS_BITCH_HEAT }; constexpr int SET_HARD_MODE_MONSTERS_COUNT = sizeof(SET_HARD_MODE_MONSTERS) / sizeof(int); @@ -63,7 +64,7 @@ constexpr int SET_FLYING_MONSTERS_COUNT = sizeof(SET_FLYING_MONSTERS) / sizeof(i // parasite, brain, mutant, berserker static constexpr int SET_MELEE_MONSTERS[] = { - DS_PARASITE, DS_BRAIN, DS_MUTANT, DS_BERSERK, DS_REDMUTANT, DS_STALKER, DS_GEKK, + DS_PARASITE, DS_BRAIN, DS_MUTANT, DS_BERSERK, DS_REDMUTANT, DS_STALKER, DS_GEKK, DS_ARACHNID, }; constexpr int SET_MELEE_MONSTERS_COUNT = sizeof(SET_MELEE_MONSTERS) / sizeof(int); @@ -75,7 +76,7 @@ constexpr int SET_RAGEQUIT_MONSTERS_COUNT = sizeof(SET_RAGEQUIT_MONSTERS) / size // tank, mutant, berserker, shambler! static constexpr int SET_TANKY_MONSTERS[] = { - DS_TANK, DS_MUTANT, DS_BERSERK, DS_SHAMBLER, DS_REDMUTANT, DS_RUNNERTANK, DS_GUNCMDR, DS_GLADB, DS_GLADC, DS_GEKK + DS_TANK, DS_MUTANT, DS_BERSERK, DS_SHAMBLER, DS_REDMUTANT, DS_RUNNERTANK, DS_GUNCMDR, DS_GLADB, DS_GLADC, DS_GEKK, DS_ARACHNID }; constexpr int SET_TANKY_MONSTERS_COUNT = sizeof(SET_TANKY_MONSTERS) / sizeof(int); diff --git a/src/quake2/g_layout.c b/src/quake2/g_layout.c index 63f6f5e9..960f0f8d 100644 --- a/src/quake2/g_layout.c +++ b/src/quake2/g_layout.c @@ -314,6 +314,7 @@ sidebar_entry_t layout_add_entity_info(sidebar_t* sidebar, edict_t* ent) case M_HOVER: case M_BERSERK: case M_MEDIC: + case M_MEDIC_COMMANDER: case M_MUTANT: case M_BRAIN: case M_GLADIATOR: @@ -329,6 +330,7 @@ sidebar_entry_t layout_add_entity_info(sidebar_t* sidebar, edict_t* ent) case M_GLADC: case M_STALKER: case M_GEKK: + case M_ARACHNID: case M_SKELETON: case M_GOLEM: name = lva("%s", V_GetMonsterName(ent)); diff --git a/src/quake2/monsterframes/m_arachnid.h b/src/quake2/monsterframes/m_arachnid.h new file mode 100644 index 00000000..c81f9f89 --- /dev/null +++ b/src/quake2/monsterframes/m_arachnid.h @@ -0,0 +1,138 @@ +// G:\quake2\baseq2\models/monsters/arachnid + +enum +{ + FRAME_rails1, + FRAME_rails2, + FRAME_rails3, + FRAME_rails4, + FRAME_rails5, + FRAME_rails6, + FRAME_rails7, + FRAME_rails8, + FRAME_rails9, + FRAME_rails10, + FRAME_rails11, + FRAME_death1, + FRAME_death2, + FRAME_death3, + FRAME_death4, + FRAME_death5, + FRAME_death6, + FRAME_death7, + FRAME_death8, + FRAME_death9, + FRAME_death10, + FRAME_death11, + FRAME_death12, + FRAME_death13, + FRAME_death14, + FRAME_death15, + FRAME_death16, + FRAME_death17, + FRAME_death18, + FRAME_death19, + FRAME_death20, + FRAME_melee_atk1, + FRAME_melee_atk2, + FRAME_melee_atk3, + FRAME_melee_atk4, + FRAME_melee_atk5, + FRAME_melee_atk6, + FRAME_melee_atk7, + FRAME_melee_atk8, + FRAME_melee_atk9, + FRAME_melee_atk10, + FRAME_melee_atk11, + FRAME_melee_atk12, + FRAME_pain11, + FRAME_pain12, + FRAME_pain13, + FRAME_pain14, + FRAME_pain15, + FRAME_idle1, + FRAME_idle2, + FRAME_idle3, + FRAME_idle4, + FRAME_idle5, + FRAME_idle6, + FRAME_idle7, + FRAME_idle8, + FRAME_idle9, + FRAME_idle10, + FRAME_idle11, + FRAME_idle12, + FRAME_idle13, + FRAME_walk1, + FRAME_walk2, + FRAME_walk3, + FRAME_walk4, + FRAME_walk5, + FRAME_walk6, + FRAME_walk7, + FRAME_walk8, + FRAME_walk9, + FRAME_walk10, + FRAME_turn1, + FRAME_turn2, + FRAME_turn3, + FRAME_melee_out1, + FRAME_melee_out2, + FRAME_melee_out3, + FRAME_pain21, + FRAME_pain22, + FRAME_pain23, + FRAME_pain24, + FRAME_pain25, + FRAME_pain26, + FRAME_melee_pain1, + FRAME_melee_pain2, + FRAME_melee_pain3, + FRAME_melee_pain4, + FRAME_melee_pain5, + FRAME_melee_pain6, + FRAME_melee_pain7, + FRAME_melee_pain8, + FRAME_melee_pain9, + FRAME_melee_pain10, + FRAME_melee_pain11, + FRAME_melee_pain12, + FRAME_melee_pain13, + FRAME_melee_pain14, + FRAME_melee_pain15, + FRAME_melee_pain16, + FRAME_melee_in1, + FRAME_melee_in2, + FRAME_melee_in3, + FRAME_melee_in4, + FRAME_melee_in5, + FRAME_melee_in6, + FRAME_melee_in7, + FRAME_melee_in8, + FRAME_melee_in9, + FRAME_melee_in10, + FRAME_melee_in11, + FRAME_melee_in12, + FRAME_melee_in13, + FRAME_melee_in14, + FRAME_melee_in15, + FRAME_melee_in16, + FRAME_rails_up1, + FRAME_rails_up2, + FRAME_rails_up3, + FRAME_rails_up4, + FRAME_rails_up5, + FRAME_rails_up6, + FRAME_rails_up7, + FRAME_rails_up8, + FRAME_rails_up9, + FRAME_rails_up10, + FRAME_rails_up11, + FRAME_rails_up12, + FRAME_rails_up13, + FRAME_rails_up14, + FRAME_rails_up15, + FRAME_rails_up16 +}; + +#define MODEL_SCALE 1.000000f diff --git a/src/quake2/monsterframes/m_infantry.h b/src/quake2/monsterframes/m_infantry.h index d0314541..8c29a668 100644 --- a/src/quake2/monsterframes/m_infantry.h +++ b/src/quake2/monsterframes/m_infantry.h @@ -209,5 +209,62 @@ #define FRAME_attak206 204 #define FRAME_attak207 205 #define FRAME_attak208 206 +#define FRAME_jump01 207 +#define FRAME_jump02 208 +#define FRAME_jump03 209 +#define FRAME_jump04 210 +#define FRAME_jump05 211 +#define FRAME_jump06 212 +#define FRAME_jump07 213 +#define FRAME_jump08 214 +#define FRAME_jump09 215 +#define FRAME_jump10 216 +#define FRAME_attak301 217 +#define FRAME_attak302 218 +#define FRAME_attak303 219 +#define FRAME_attak304 220 +#define FRAME_attak305 221 +#define FRAME_attak306 222 +#define FRAME_attak307 223 +#define FRAME_attak308 224 +#define FRAME_attak309 225 +#define FRAME_attak310 226 +#define FRAME_attak311 227 +#define FRAME_attak312 228 +#define FRAME_attak313 229 +#define FRAME_attak314 230 +#define FRAME_attak315 231 +#define FRAME_run201 232 +#define FRAME_run202 233 +#define FRAME_run203 234 +#define FRAME_run204 235 +#define FRAME_run205 236 +#define FRAME_run206 237 +#define FRAME_run207 238 +#define FRAME_run208 239 +#define FRAME_attak401 240 +#define FRAME_attak402 241 +#define FRAME_attak403 242 +#define FRAME_attak404 243 +#define FRAME_attak405 244 +#define FRAME_attak406 245 +#define FRAME_attak407 246 +#define FRAME_attak408 247 +#define FRAME_attak409 248 +#define FRAME_attak410 249 +#define FRAME_attak411 250 +#define FRAME_attak412 251 +#define FRAME_attak413 252 +#define FRAME_attak414 253 +#define FRAME_attak415 254 +#define FRAME_attak416 255 +#define FRAME_attak417 256 +#define FRAME_attak418 257 +#define FRAME_attak419 258 +#define FRAME_attak420 259 +#define FRAME_attak421 260 +#define FRAME_attak422 261 +#define FRAME_attak423 262 +#define FRAME_attak424 263 #define MODEL_SCALE 1.000000 diff --git a/src/server/v_luasettings.c b/src/server/v_luasettings.c index 826c2654..1d2cb003 100644 --- a/src/server/v_luasettings.c +++ b/src/server/v_luasettings.c @@ -1075,6 +1075,10 @@ void Lua_LoadVariables() M_MUTANT_ADDON_HEALTH = vrx_lua_get_variable("M_MUTANT_ADDON_HEALTH", 75); M_MUTANT_INITIAL_ARMOR = vrx_lua_get_variable("M_MUTANT_INITIAL_ARMOR", 0); M_MUTANT_ADDON_ARMOR = vrx_lua_get_variable("M_MUTANT_ADDON_ARMOR", 0); + M_REDMUTANT_INITIAL_HEALTH = vrx_lua_get_variable("M_REDMUTANT_INITIAL_HEALTH", 150); + M_REDMUTANT_ADDON_HEALTH = vrx_lua_get_variable("M_REDMUTANT_ADDON_HEALTH", 85); + M_REDMUTANT_INITIAL_ARMOR = vrx_lua_get_variable("M_REDMUTANT_INITIAL_ARMOR", 0); + M_REDMUTANT_ADDON_ARMOR = vrx_lua_get_variable("M_REDMUTANT_ADDON_ARMOR", 0); M_PARASITE_INITIAL_HEALTH = vrx_lua_get_variable("M_PARASITE_INITIAL_HEALTH", 75); M_PARASITE_ADDON_HEALTH = vrx_lua_get_variable("M_PARASITE_ADDON_HEALTH", 30); @@ -1094,6 +1098,47 @@ void Lua_LoadVariables() M_SHAMBLER_INITIAL_HEALTH = vrx_lua_get_variable("M_SHAMBLER_INITIAL_HEALTH", 250); M_SHAMBLER_ADDON_HEALTH = vrx_lua_get_variable("M_SHAMBLER_ADDON_HEALTH", 125); + M_CHICK_HEAT_INITIAL_HEALTH = vrx_lua_get_variable("M_CHICK_HEAT_INITIAL_HEALTH", 50); + M_CHICK_HEAT_ADDON_HEALTH = vrx_lua_get_variable("M_CHICK_HEAT_ADDON_HEALTH", 15); + M_CHICK_HEAT_INITIAL_ARMOR = vrx_lua_get_variable("M_CHICK_HEAT_INITIAL_ARMOR", 25); + M_CHICK_HEAT_ADDON_ARMOR = vrx_lua_get_variable("M_CHICK_HEAT_ADDON_ARMOR", 15); + M_DAEDALUS_INITIAL_HEALTH = vrx_lua_get_variable("M_DAEDALUS_INITIAL_HEALTH", 50); + M_DAEDALUS_ADDON_HEALTH = vrx_lua_get_variable("M_DAEDALUS_ADDON_HEALTH", 20); + M_DAEDALUS_INITIAL_ARMOR = vrx_lua_get_variable("M_DAEDALUS_INITIAL_ARMOR", 150); + M_DAEDALUS_ADDON_ARMOR = vrx_lua_get_variable("M_DAEDALUS_ADDON_ARMOR", 60); + M_RUNNERTANK_INITIAL_HEALTH = vrx_lua_get_variable("M_RUNNERTANK_INITIAL_HEALTH", 100); + M_RUNNERTANK_ADDON_HEALTH = vrx_lua_get_variable("M_RUNNERTANK_ADDON_HEALTH", 65); + M_RUNNERTANK_INITIAL_ARMOR = vrx_lua_get_variable("M_RUNNERTANK_INITIAL_ARMOR", 200); + M_RUNNERTANK_ADDON_ARMOR = vrx_lua_get_variable("M_RUNNERTANK_ADDON_ARMOR", 105); + M_GEKK_INITIAL_HEALTH = vrx_lua_get_variable("M_GEKK_INITIAL_HEALTH", 150); + M_GEKK_ADDON_HEALTH = vrx_lua_get_variable("M_GEKK_ADDON_HEALTH", 85); + M_GEKK_INITIAL_ARMOR = vrx_lua_get_variable("M_GEKK_INITIAL_ARMOR", 0); + M_GEKK_ADDON_ARMOR = vrx_lua_get_variable("M_GEKK_ADDON_ARMOR", 0); + M_STALKER_INITIAL_HEALTH = vrx_lua_get_variable("M_STALKER_INITIAL_HEALTH", 150); + M_STALKER_ADDON_HEALTH = vrx_lua_get_variable("M_STALKER_ADDON_HEALTH", 85); + M_STALKER_INITIAL_ARMOR = vrx_lua_get_variable("M_STALKER_INITIAL_ARMOR", 0); + M_STALKER_ADDON_ARMOR = vrx_lua_get_variable("M_STALKER_ADDON_ARMOR", 0); + M_MEDIC_COMMANDER_INITIAL_HEALTH = vrx_lua_get_variable("M_MEDIC_COMMANDER_INITIAL_HEALTH", 200); + M_MEDIC_COMMANDER_ADDON_HEALTH = vrx_lua_get_variable("M_MEDIC_COMMANDER_ADDON_HEALTH", 20); + M_MEDIC_COMMANDER_INITIAL_ARMOR = vrx_lua_get_variable("M_MEDIC_COMMANDER_INITIAL_ARMOR", 200); + M_MEDIC_COMMANDER_ADDON_ARMOR = vrx_lua_get_variable("M_MEDIC_COMMANDER_ADDON_ARMOR", 40); + M_GUNCMDR_INITIAL_HEALTH = vrx_lua_get_variable("M_GUNCMDR_INITIAL_HEALTH", 100); + M_GUNCMDR_ADDON_HEALTH = vrx_lua_get_variable("M_GUNCMDR_ADDON_HEALTH", 65); + M_GUNCMDR_INITIAL_ARMOR = vrx_lua_get_variable("M_GUNCMDR_INITIAL_ARMOR", 200); + M_GUNCMDR_ADDON_ARMOR = vrx_lua_get_variable("M_GUNCMDR_ADDON_ARMOR", 105); + M_GLADB_INITIAL_HEALTH = vrx_lua_get_variable("M_GLADB_INITIAL_HEALTH", 100); + M_GLADB_ADDON_HEALTH = vrx_lua_get_variable("M_GLADB_ADDON_HEALTH", 10); + M_GLADB_INITIAL_ARMOR = vrx_lua_get_variable("M_GLADB_INITIAL_ARMOR", 100); + M_GLADB_ADDON_ARMOR = vrx_lua_get_variable("M_GLADB_ADDON_ARMOR", 20); + M_GLADC_INITIAL_HEALTH = vrx_lua_get_variable("M_GLADC_INITIAL_HEALTH", 100); + M_GLADC_ADDON_HEALTH = vrx_lua_get_variable("M_GLADC_ADDON_HEALTH", 10); + M_GLADC_INITIAL_ARMOR = vrx_lua_get_variable("M_GLADC_INITIAL_ARMOR", 100); + M_GLADC_ADDON_ARMOR = vrx_lua_get_variable("M_GLADC_ADDON_ARMOR", 20); + M_ARACHNID_INITIAL_HEALTH = vrx_lua_get_variable("M_ARACHNID_INITIAL_HEALTH", 100); + M_ARACHNID_ADDON_HEALTH = vrx_lua_get_variable("M_ARACHNID_ADDON_HEALTH", 10); + M_ARACHNID_INITIAL_ARMOR = vrx_lua_get_variable("M_ARACHNID_INITIAL_ARMOR", 100); + M_ARACHNID_ADDON_ARMOR = vrx_lua_get_variable("M_ARACHNID_ADDON_ARMOR", 20); + M_BRAIN_INITIAL_PULL = vrx_lua_get_variable("M_BRAIN_INITIAL_PULL", -60); M_BRAIN_ADDON_PULL = vrx_lua_get_variable("M_BRAIN_ADDON_PULL", -2); M_BRAIN_MAX_PULL = vrx_lua_get_variable("M_BRAIN_MAX_PULL", -100); @@ -1145,6 +1190,20 @@ void Lua_LoadVariables() M_BLASTER_SPEED_ADDON = vrx_lua_get_variable("M_BLASTER_SPEED_ADDON", 50); M_BLASTER_SPEED_MAX = vrx_lua_get_variable("M_BLASTER_SPEED_MAX", 1500); + M_BLASTER2_DMG_BASE = vrx_lua_get_variable("M_BLASTER2_DMG_BASE", 50); + M_BLASTER2_DMG_ADDON = vrx_lua_get_variable("M_BLASTER2_DMG_ADDON", 2); + M_BLASTER2_DMG_MAX = vrx_lua_get_variable("M_BLASTER2_DMG_MAX", 0); + M_BLASTER2_SPEED_BASE = vrx_lua_get_variable("M_BLASTER2_SPEED_BASE", 500); + M_BLASTER2_SPEED_ADDON = vrx_lua_get_variable("M_BLASTER2_SPEED_ADDON", 0); + M_BLASTER2_SPEED_MAX = vrx_lua_get_variable("M_BLASTER2_SPEED_MAX", 0); + M_PLASMA_DMG_BASE = vrx_lua_get_variable("M_PLASMA_DMG_BASE", 50); + M_PLASMA_DMG_ADDON = vrx_lua_get_variable("M_PLASMA_DMG_ADDON", 10); + M_PLASMA_DMG_MAX = vrx_lua_get_variable("M_PLASMA_DMG_MAX", 0); + M_PLASMA_SPEED_BASE = vrx_lua_get_variable("M_PLASMA_SPEED_BASE", 1000); + M_PLASMA_SPEED_ADDON = vrx_lua_get_variable("M_PLASMA_SPEED_ADDON", 50); + M_PLASMA_SPEED_MAX = vrx_lua_get_variable("M_PLASMA_SPEED_MAX", 1500); + M_PLASMA_DAMAGE_RADIUS = vrx_lua_get_variable("M_PLASMA_DAMAGE_RADIUS", 40); + M_20MM_RANGE_BASE = vrx_lua_get_variable("M_20MM_RANGE_BASE", 750); M_20MM_RANGE_ADDON = vrx_lua_get_variable("M_20MM_RANGE_ADDON", 0); M_20MM_RANGE_MAX = vrx_lua_get_variable("M_20MM_RANGE_MAX", 750); From adb3f53a05ef1ac7b5b56e7e3eee47f9e4a6ce9f Mon Sep 17 00:00:00 2001 From: Enemy-123 Date: Tue, 21 Apr 2026 15:19:02 -0400 Subject: [PATCH 05/24] s.scale works if REPRO, added carrier boss, gekk smarter on water, arachnid scaled 0.65x, infantry 1.15x and carrier 0.75x added medic_commander_setup_invasion_spawn added carrier_setup_invasion_spawn --- lua/variables.lua | 4 + src/characters/settings.h | 4 + src/characters/v_utils.c | 2 + src/combat/common/damage.c | 2 +- src/combat/common/v_misc.c | 6 + src/entities/drone/drone_arachnid.c | 2 +- src/entities/drone/drone_carrier.c | 524 +++++++++++++++++++++ src/entities/drone/drone_gekk.c | 101 +++- src/entities/drone/drone_guncmdr.c | 1 + src/entities/drone/drone_infantry.c | 1 + src/entities/drone/drone_medic.c | 14 +- src/entities/drone/drone_misc.c | 20 +- src/g_local.h | 3 + src/gamemodes/invasion.c | 7 +- src/quake2/g_layout.c | 1 + src/quake2/g_save.c | 3 + src/quake2/g_svcmds.c | 7 + src/quake2/monsterframes/m_rogue_carrier.h | 86 ++++ src/server/v_luasettings.c | 4 + 19 files changed, 765 insertions(+), 27 deletions(-) create mode 100644 src/entities/drone/drone_carrier.c create mode 100644 src/quake2/monsterframes/m_rogue_carrier.h diff --git a/lua/variables.lua b/lua/variables.lua index 53f1a7fa..bd8418ce 100644 --- a/lua/variables.lua +++ b/lua/variables.lua @@ -779,6 +779,10 @@ M_ARACHNID_INITIAL_HEALTH = 100 M_ARACHNID_ADDON_HEALTH = 55 M_ARACHNID_INITIAL_ARMOR = 200 M_ARACHNID_ADDON_ARMOR = 85 +M_CARRIER_INITIAL_HEALTH = 2200 +M_CARRIER_ADDON_HEALTH = 650 +M_CARRIER_INITIAL_ARMOR = 500 +M_CARRIER_ADDON_ARMOR = 350 -- Monster Weapons -- M_ENABLE_WORLDSPAWN_SOFTCAP = 1 diff --git a/src/characters/settings.h b/src/characters/settings.h index b4886b8b..a2204dec 100644 --- a/src/characters/settings.h +++ b/src/characters/settings.h @@ -814,6 +814,10 @@ VRX_V_LUASETTINGS_IMPL double M_ARACHNID_INITIAL_HEALTH; VRX_V_LUASETTINGS_IMPL double M_ARACHNID_ADDON_HEALTH; VRX_V_LUASETTINGS_IMPL double M_ARACHNID_INITIAL_ARMOR; VRX_V_LUASETTINGS_IMPL double M_ARACHNID_ADDON_ARMOR; +VRX_V_LUASETTINGS_IMPL double M_CARRIER_INITIAL_HEALTH; +VRX_V_LUASETTINGS_IMPL double M_CARRIER_ADDON_HEALTH; +VRX_V_LUASETTINGS_IMPL double M_CARRIER_INITIAL_ARMOR; +VRX_V_LUASETTINGS_IMPL double M_CARRIER_ADDON_ARMOR; VRX_V_LUASETTINGS_IMPL double M_BRAIN_INITIAL_PULL; VRX_V_LUASETTINGS_IMPL double M_BRAIN_ADDON_PULL; VRX_V_LUASETTINGS_IMPL double M_BRAIN_MAX_PULL; diff --git a/src/characters/v_utils.c b/src/characters/v_utils.c index fade7bd7..4523621b 100644 --- a/src/characters/v_utils.c +++ b/src/characters/v_utils.c @@ -2101,6 +2101,8 @@ char *V_GetMonsterKind(int mtype) { return "gekk"; case M_ARACHNID: return "arachnid"; + case M_CARRIER: + return "carrier"; case M_SKELETON: return "skeleton"; case M_GOLEM: diff --git a/src/combat/common/damage.c b/src/combat/common/damage.c index 8abe5982..59f6752e 100644 --- a/src/combat/common/damage.c +++ b/src/combat/common/damage.c @@ -154,7 +154,7 @@ qboolean IsMonster(const edict_t* ent) { || ent->mtype == M_REDMUTANT || ent->mtype == M_RUNNERTANK || ent->mtype == M_GUNCMDR || ent->mtype == M_DAEDALUS || ent->mtype == M_GLADB || ent->mtype == M_GLADC || ent->mtype == M_STALKER || ent->mtype == M_GEKK || ent->mtype == M_ARACHNID - || ent->mtype == M_MEDIC_COMMANDER + || ent->mtype == M_MEDIC_COMMANDER || ent->mtype == M_CARRIER || ent->mtype == M_CHICK_HEAT)); } diff --git a/src/combat/common/v_misc.c b/src/combat/common/v_misc.c index 85dcc2a3..5105d212 100644 --- a/src/combat/common/v_misc.c +++ b/src/combat/common/v_misc.c @@ -645,6 +645,9 @@ int vrx_GetMonsterCost(int mtype) { case M_ARACHNID: cost = M_DEFAULT_COST; break; + case M_CARRIER: + cost = M_COMMANDER_COST; + break; case M_SUPERTANK: cost = M_SUPERTANK_COST; break; @@ -727,6 +730,9 @@ int vrx_GetMonsterControlCost(int mtype) { case M_ARACHNID: cost = M_GLADIATOR_CONTROL_COST; break; + case M_CARRIER: + cost = M_JORG_CONTROL_COST; + break; case M_HOVER: cost = M_HOVER_CONTROL_COST; break; diff --git a/src/entities/drone/drone_arachnid.c b/src/entities/drone/drone_arachnid.c index 95e043c3..fb9c97d9 100644 --- a/src/entities/drone/drone_arachnid.c +++ b/src/entities/drone/drone_arachnid.c @@ -356,7 +356,7 @@ void init_drone_arachnid(edict_t *self) self->gib_health = -200; self->mass = 450; self->mtype = M_ARACHNID; - + self->s.scale = 0.65f; self->monsterinfo.control_cost = M_GLADIATOR_CONTROL_COST; self->monsterinfo.cost = M_DEFAULT_COST; self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD; diff --git a/src/entities/drone/drone_carrier.c b/src/entities/drone/drone_carrier.c new file mode 100644 index 00000000..7d3ef17d --- /dev/null +++ b/src/entities/drone/drone_carrier.c @@ -0,0 +1,524 @@ +/* +============================================================================== + +carrier + +============================================================================== +*/ + +#include "g_local.h" +#include "../../quake2/monsterframes/m_rogue_carrier.h" + +#define CARRIER_SUMMON_COUNT 4 +#define CARRIER_SUMMON_COOLDOWN 8.0f + +static int sound_pain1; +static int sound_pain2; +static int sound_pain3; +static int sound_death; +static int sound_sight; +static int sound_rail; +static int sound_spawn; + +void drone_ai_stand(edict_t *self, float dist); +void drone_ai_run(edict_t *self, float dist); +void drone_ai_walk(edict_t *self, float dist); + +static void carrier_stand(edict_t *self); +static void carrier_walk(edict_t *self); +static void carrier_run(edict_t *self); +static void carrier_attack(edict_t *self); + +static const int carrier_summons[CARRIER_SUMMON_COUNT] = +{ + M_DAEDALUS, + M_FLYER, + M_HOVER, + M_FLOATER +}; + +static void carrier_sight(edict_t *self, edict_t *other) +{ + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +static void carrier_dead(edict_t *self) +{ + int n; + + for (n = 0; n < 4; n++) + ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", 500, GIB_ORGANIC); + for (n = 0; n < 6; n++) + ThrowGib(self, "models/objects/gibs/sm_metal/tris.md2", 500, GIB_METALLIC); + + BecomeBigExplosion(self); +} + +static void carrier_fire_rocket(edict_t *self) +{ + int damage; + int speed; + vec3_t forward, start; + const int flashes[4] = + { + MZ2_CARRIER_ROCKET_1, + MZ2_CARRIER_ROCKET_2, + MZ2_CARRIER_ROCKET_3, + MZ2_CARRIER_ROCKET_4 + }; + + if (!G_EntExists(self->enemy)) + return; + + damage = M_ROCKETLAUNCHER_DMG_BASE + M_ROCKETLAUNCHER_DMG_ADDON * drone_damagelevel(self); + if (M_ROCKETLAUNCHER_DMG_MAX && damage > M_ROCKETLAUNCHER_DMG_MAX) + damage = M_ROCKETLAUNCHER_DMG_MAX; + speed = M_ROCKETLAUNCHER_SPEED_BASE + M_ROCKETLAUNCHER_SPEED_ADDON * drone_damagelevel(self); + if (M_ROCKETLAUNCHER_SPEED_MAX && speed > M_ROCKETLAUNCHER_SPEED_MAX) + speed = M_ROCKETLAUNCHER_SPEED_MAX; + + for (int i = 0; i < 4; i++) + { + MonsterAim(self, M_PROJECTILE_ACC, speed, true, flashes[i], forward, start); + monster_fire_heat(self, start, forward, damage, speed, flashes[i], 0.06f); + } +} + +static void carrier_fire_bullets(edict_t *self) +{ + int damage; + vec3_t forward, start; + const int flashes[2] = + { + MZ2_CARRIER_MACHINEGUN_L1, + MZ2_CARRIER_MACHINEGUN_R1 + }; + + if (!G_EntExists(self->enemy)) + return; + + damage = M_MACHINEGUN_DMG_BASE + M_MACHINEGUN_DMG_ADDON * drone_damagelevel(self); + if (M_MACHINEGUN_DMG_MAX && damage > M_MACHINEGUN_DMG_MAX) + damage = M_MACHINEGUN_DMG_MAX; + + for (int i = 0; i < 2; i++) + { + MonsterAim(self, M_PROJECTILE_ACC, 0, false, flashes[i], forward, start); + monster_fire_bullet(self, start, forward, damage, damage, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flashes[i]); + } +} + +static void carrier_fire_rail(edict_t *self) +{ + int damage; + vec3_t forward, start; + + if (!G_EntExists(self->enemy)) + return; + + damage = M_RAILGUN_DMG_BASE + M_RAILGUN_DMG_ADDON * drone_damagelevel(self); + if (M_RAILGUN_DMG_MAX && damage > M_RAILGUN_DMG_MAX) + damage = M_RAILGUN_DMG_MAX; + + MonsterAim(self, 0.25f, 0, false, MZ2_CARRIER_RAILGUN, forward, start); + gi.sound(self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0); + monster_fire_railgun(self, start, forward, damage, damage, MZ2_CARRIER_RAILGUN); +} + +static qboolean carrier_valid_spawn_spot(edict_t *self, vec3_t mins, vec3_t maxs, vec3_t spot) +{ + if (G_IsValidLocation(self, spot, mins, maxs)) + return true; + + spot[2] += 32; + if (G_IsValidLocation(self, spot, mins, maxs)) + return true; + + spot[2] -= 64; + return G_IsValidLocation(self, spot, mins, maxs); +} + +static qboolean carrier_find_spawn_spot(edict_t *self, vec3_t mins, vec3_t maxs, int index, vec3_t spot) +{ + vec3_t forward, right; + float side; + float dist; + + AngleVectors(self->s.angles, forward, right, NULL); + side = (index & 1) ? 104.0f : -104.0f; + dist = 128.0f + 48.0f * (float)(index / 2); + + VectorCopy(self->s.origin, spot); + VectorMA(spot, dist, forward, spot); + VectorMA(spot, side, right, spot); + spot[2] -= 32; + if (carrier_valid_spawn_spot(self, mins, maxs, spot)) + return true; + + VectorCopy(self->s.origin, spot); + VectorMA(spot, -96, forward, spot); + VectorMA(spot, side, right, spot); + spot[2] -= 16; + return carrier_valid_spawn_spot(self, mins, maxs, spot); +} + +static void carrier_cleanup_failed_spawn(edict_t *owner, edict_t *spawned) +{ + if (owner && owner->client) + layout_remove_tracked_entity(&owner->client->layout, spawned); + + DroneList_Remove(spawned); + AI_EnemyRemoved(spawned); + G_FreeEdict(spawned); +} + +static void carrier_setup_invasion_spawn(edict_t *spawned) +{ + if (!invasion->value) + return; + + spawned->monsterinfo.aiflags &= ~AI_STAND_GROUND; + spawned->monsterinfo.aiflags |= AI_FIND_NAVI; + spawned->prev_navi = NULL; + spawned->goalentity = NULL; +} + +static qboolean carrier_spawn_monster(edict_t *self, int index) +{ + edict_t *owner; + edict_t *spawned; + vec3_t spot; + + owner = (self->activator && self->activator->inuse) ? self->activator : self; + spawned = G_Spawn(); + spawned->mtype = carrier_summons[index % CARRIER_SUMMON_COUNT]; + spawned->activator = owner; + spawned->monsterinfo.level = self->monsterinfo.level; + + if (!M_Initialize(owner, spawned, 0.0f)) + { + G_FreeEdict(spawned); + return false; + } + + if (!carrier_find_spawn_spot(self, spawned->mins, spawned->maxs, index, spot)) + { + carrier_cleanup_failed_spawn(owner, spawned); + return false; + } + + spawned->monsterinfo.cost = 0; + spawned->s.effects |= EF_PLASMA; + VectorCopy(spot, spawned->s.origin); + VectorCopy(spot, spawned->s.old_origin); + VectorCopy(self->s.angles, spawned->s.angles); + spawned->nextthink = level.time + FRAMETIME; + spawned->monsterinfo.attack_finished = level.time + 1.0; + carrier_setup_invasion_spawn(spawned); + + if (G_ValidTarget(spawned, self->enemy, true, true)) + spawned->enemy = self->enemy; + + gi.linkentity(spawned); + owner->num_monsters += spawned->monsterinfo.control_cost; + owner->num_monsters_real++; + + if (spawned->enemy && spawned->monsterinfo.run) + spawned->monsterinfo.run(spawned); + else if (spawned->monsterinfo.stand) + spawned->monsterinfo.stand(spawned); + + return true; +} + +static void carrier_spawngrows(edict_t *self) +{ + vec3_t mins, maxs, size, spot, effect_origin; + float radius; + + VectorSet(mins, -24, -24, -24); + VectorSet(maxs, 24, 24, 32); + VectorSubtract(maxs, mins, size); + radius = VectorLength(size) * 0.5f; + + for (int i = 0; i < CARRIER_SUMMON_COUNT; i++) + { + if (!carrier_find_spawn_spot(self, mins, maxs, i, spot)) + continue; + + VectorAdd(mins, maxs, effect_origin); + VectorAdd(spot, effect_origin, effect_origin); + SpawnGrow_Spawn(effect_origin, radius, radius * 2.0f); + } +} + +static void carrier_finish_spawn(edict_t *self) +{ + int spawned = 0; + + for (int i = 0; i < CARRIER_SUMMON_COUNT; i++) + { + if (carrier_spawn_monster(self, i)) + spawned++; + } + + if (spawned) + self->monsterinfo.melee_finished = level.time + CARRIER_SUMMON_COOLDOWN; +} + +mframe_t carrier_frames_stand[] = +{ + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL +}; +mmove_t carrier_move_stand = { FRAME_search01, FRAME_search13, carrier_frames_stand, NULL }; + +static void carrier_stand(edict_t *self) +{ + self->monsterinfo.currentmove = &carrier_move_stand; +} + +mframe_t carrier_frames_run[] = +{ + drone_ai_run, 12, NULL, + drone_ai_run, 14, NULL, + drone_ai_run, 16, NULL, + drone_ai_run, 18, NULL, + drone_ai_run, 20, NULL, + drone_ai_run, 20, NULL, + drone_ai_run, 18, NULL, + drone_ai_run, 16, NULL, + drone_ai_run, 14, NULL, + drone_ai_run, 12, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL +}; +mmove_t carrier_move_run = { FRAME_search01, FRAME_search13, carrier_frames_run, NULL }; + +static void carrier_run(edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &carrier_move_stand; + else + self->monsterinfo.currentmove = &carrier_move_run; +} + +static void carrier_walk(edict_t *self) +{ + if (!self->goalentity) + self->goalentity = world; + self->monsterinfo.currentmove = &carrier_move_run; +} + +mframe_t carrier_frames_attack_mg[] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, carrier_fire_bullets, + ai_charge, 0, carrier_fire_bullets, + ai_charge, 0, carrier_fire_bullets, + ai_charge, 0, NULL +}; +mmove_t carrier_move_attack_mg = { FRAME_firea06, FRAME_firea11, carrier_frames_attack_mg, carrier_run }; + +mframe_t carrier_frames_attack_rocket[] = +{ + ai_charge, 0, NULL, + ai_charge, 0, carrier_fire_rocket, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t carrier_move_attack_rocket = { FRAME_fireb01, FRAME_fireb04, carrier_frames_attack_rocket, carrier_run }; + +mframe_t carrier_frames_attack_rail[] = +{ + ai_charge, 0, NULL, + ai_charge, -10, NULL, + ai_charge, -20, carrier_fire_rail, + ai_charge, -10, NULL, + ai_charge, 0, NULL +}; +mmove_t carrier_move_attack_rail = { FRAME_search01, FRAME_search05, carrier_frames_attack_rail, carrier_run }; + +mframe_t carrier_frames_spawn[] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, carrier_spawngrows, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, -2, carrier_finish_spawn, + ai_charge, -6, NULL, + ai_charge, -10, NULL, + ai_charge, -6, NULL, + ai_charge, -2, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t carrier_move_spawn = { FRAME_spawn01, FRAME_spawn18, carrier_frames_spawn, carrier_run }; + +static void carrier_start_spawn(edict_t *self) +{ + if (sound_spawn) + gi.sound(self, CHAN_WEAPON, sound_spawn, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &carrier_move_spawn; +} + +static void carrier_attack(edict_t *self) +{ + float r; + + if (!G_EntExists(self->enemy)) + return; + + r = random(); + if (level.time >= self->monsterinfo.melee_finished && r < 0.25f) + carrier_start_spawn(self); + else if (r < 0.50f) + self->monsterinfo.currentmove = &carrier_move_attack_rocket; + else if (r < 0.75f) + self->monsterinfo.currentmove = &carrier_move_attack_rail; + else + self->monsterinfo.currentmove = &carrier_move_attack_mg; + + M_DelayNextAttack(self, 1.0f + random(), true); +} + +mframe_t carrier_frames_pain[] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t carrier_move_pain = { FRAME_spawn01, FRAME_spawn04, carrier_frames_pain, carrier_run }; + +static void carrier_pain(edict_t *self, edict_t *other, float kick, int damage) +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3.0; + if (random() < 0.33f) + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + else if (random() < 0.5f) + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_VOICE, sound_pain3, 1, ATTN_NORM, 0); + + if (skill->value != 3) + self->monsterinfo.currentmove = &carrier_move_pain; +} + +mframe_t carrier_frames_death[] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t carrier_move_death = { FRAME_death01, FRAME_death16, carrier_frames_death, carrier_dead }; + +static void carrier_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + M_Notify(self); + + if (self->deadflag == DEAD_DEAD) + return; + + DroneList_Remove(self); + gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + self->monsterinfo.currentmove = &carrier_move_death; + + if (self->activator && !self->activator->client) + self->activator->num_monsters_real--; +} + +void init_drone_carrier(edict_t *self) +{ + sound_pain1 = gi.soundindex("carrier/pain_md.wav"); + sound_pain2 = gi.soundindex("carrier/pain_lg.wav"); + sound_pain3 = gi.soundindex("carrier/pain_sm.wav"); + sound_death = gi.soundindex("carrier/death.wav"); + sound_sight = gi.soundindex("carrier/sight.wav"); + sound_rail = gi.soundindex("gladiator/railgun.wav"); + sound_spawn = gi.soundindex("medic_commander/monsterspawn1.wav"); + self->s.sound = gi.soundindex("bosshovr/bhvengn1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/carrier/tris.md2"); + VectorSet(self->mins, -56, -56, -44); + VectorSet(self->maxs, 56, 56, 44); + + gi.modelindex("models/items/spawngro3/tris.md2"); + gi.modelindex("models/monsters/flyer/tris.md2"); + gi.modelindex("models/monsters/float/tris.md2"); + gi.modelindex("models/monsters/hover/tris.md2"); + + self->health = M_CARRIER_INITIAL_HEALTH + M_CARRIER_ADDON_HEALTH * self->monsterinfo.level; + self->max_health = self->health; + self->gib_health = -2000; + self->mass = 1000; + self->mtype = M_CARRIER; + self->flags |= FL_FLY; + self->s.scale = 0.75f; + + self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD; + self->monsterinfo.power_armor_power = M_CARRIER_INITIAL_ARMOR + M_CARRIER_ADDON_ARMOR * self->monsterinfo.level; + self->monsterinfo.max_armor = self->monsterinfo.power_armor_power; + self->monsterinfo.control_cost = M_JORG_CONTROL_COST; + self->monsterinfo.cost = M_COMMANDER_COST; + self->monsterinfo.jumpup = 64; + self->monsterinfo.jumpdn = 512; + self->monsterinfo.aiflags |= AI_NO_CIRCLE_STRAFE; + + self->pain = carrier_pain; + self->die = carrier_die; + self->monsterinfo.stand = carrier_stand; + self->monsterinfo.walk = carrier_walk; + self->monsterinfo.run = carrier_run; + self->monsterinfo.attack = carrier_attack; + self->monsterinfo.sight = carrier_sight; + + gi.linkentity(self); + + self->monsterinfo.currentmove = &carrier_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + self->nextthink = level.time + FRAMETIME; +} diff --git a/src/entities/drone/drone_gekk.c b/src/entities/drone/drone_gekk.c index 5440acf5..a00404bf 100644 --- a/src/entities/drone/drone_gekk.c +++ b/src/entities/drone/drone_gekk.c @@ -32,10 +32,13 @@ static void gekk_run(edict_t *self); static void gekk_land_to_water(edict_t *self); static void gekk_water_to_land(edict_t *self); static void gekk_swim_loop(edict_t *self); +static void gekk_check_landing(edict_t *self); +static void gekk_stop_skid(edict_t *self); extern mmove_t gekk_move_standunderwater; extern mmove_t gekk_move_swim_loop; extern mmove_t gekk_move_swim_start; +extern mmove_t gekk_move_leapatk; extern void fire_acid(edict_t *self, vec3_t start, vec3_t aimdir, int projectile_damage, float radius, int speed, int acid_damage, float acid_duration, int gas_damage, float gas_radius, float gas_duration); @@ -235,6 +238,12 @@ static void gekk_swim_loop(edict_t *self) return; } + if (G_EntExists(self->enemy) && self->enemy->waterlevel < WATER_WAIST && random() < 0.3f) + { + gekk_water_to_land(self); + return; + } + gekk_set_water_bounds(self); self->monsterinfo.currentmove = &gekk_move_swim_loop; } @@ -358,11 +367,23 @@ static void gekk_land_to_water(edict_t *self) static void gekk_water_to_land(edict_t *self) { + vec3_t dir; + gekk_set_land_bounds(self); - if (self->enemy || self->goalentity) - self->monsterinfo.currentmove = &gekk_move_run; - else - self->monsterinfo.currentmove = &gekk_move_stand; + if (G_EntExists(self->enemy)) + { + VectorSubtract(self->enemy->s.origin, self->s.origin, dir); + dir[2] = 0; + VectorNormalize(dir); + VectorScale(dir, 450, self->velocity); + self->velocity[2] = 320; + self->groundentity = NULL; + self->monsterinfo.attack_finished = level.time + 1.5; + self->monsterinfo.currentmove = &gekk_move_leapatk; + return; + } + + self->monsterinfo.currentmove = self->goalentity ? &gekk_move_run : &gekk_move_stand; } mframe_t gekk_frames_attack[] = @@ -446,16 +467,43 @@ static void gekk_melee(edict_t *self) static void gekk_jump_takeoff(edict_t *self) { - vec3_t dir; + vec3_t forward, start; + int speed = 700; if (!G_EntExists(self->enemy)) return; - VectorSubtract(self->enemy->s.origin, self->s.origin, dir); - VectorNormalize(dir); - VectorScale(dir, 420, self->velocity); - self->velocity[2] = 260; + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); + self->lastsound = level.framenum; + self->s.origin[2] += 1; self->groundentity = NULL; + MonsterAim(self, -1, speed, false, 0, forward, start); + VectorScale(forward, speed, self->velocity); + self->velocity[2] += 300; + self->monsterinfo.pausetime = level.time + 2.0; + self->monsterinfo.aiflags |= AI_HOLD_FRAME; +} + +static void gekk_stop_skid(edict_t *self) +{ + if (self->groundentity) + { + VectorClear(self->velocity); + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + } +} + +static void gekk_check_landing(edict_t *self) +{ + if (self->groundentity || (self->waterlevel > 1) || (level.time > self->monsterinfo.pausetime)) + { + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + if (self->waterlevel > 1) + gekk_land_to_water(self); + return; + } + + self->monsterinfo.aiflags |= AI_HOLD_FRAME; } static void gekk_spit(edict_t *self) @@ -494,19 +542,19 @@ mframe_t gekk_frames_leapatk[] = ai_charge, 0, NULL, ai_charge, 0, NULL, ai_charge, 6, gekk_jump_takeoff, - ai_charge, 12, NULL, - ai_charge, 20, gekk_spit, - ai_charge, 28, NULL, - ai_charge, 32, NULL, - ai_charge, 35, NULL, - ai_charge, 28, gekk_claw, - ai_charge, 18, NULL, - ai_charge, 12, NULL, ai_charge, 6, NULL, - ai_charge, 4, NULL, - ai_charge, 2, NULL, - ai_charge, 0, NULL, ai_charge, 0, NULL, + ai_charge, 28, NULL, + ai_charge, 24, NULL, + ai_charge, 32, NULL, + ai_charge, 36, gekk_check_landing, + ai_charge, 12, gekk_stop_skid, + ai_charge, 20, gekk_stop_skid, + ai_charge, -1, gekk_stop_skid, + ai_charge, 3, gekk_stop_skid, + ai_charge, 1, gekk_stop_skid, + ai_charge, 2, gekk_stop_skid, + ai_charge, 1, gekk_stop_skid, ai_charge, 0, NULL, ai_charge, 0, NULL }; @@ -526,9 +574,14 @@ mmove_t gekk_move_spit = { FRAME_spit_01, FRAME_spit_07, gekk_frames_spit, gekk_ static void gekk_attack(edict_t *self) { + float dist; + if (gekk_should_swim(self)) { - gekk_land_to_water(self); + if (G_EntExists(self->enemy) && self->enemy->waterlevel < WATER_WAIST) + gekk_water_to_land(self); + else + gekk_land_to_water(self); self->monsterinfo.melee_finished = level.time + 1.0; M_DelayNextAttack(self, 1.0 + random(), true); return; @@ -536,7 +589,11 @@ static void gekk_attack(edict_t *self) if (self->flags & FL_SWIM) gekk_set_land_bounds(self); - if (G_EntExists(self->enemy) && entdist(self, self->enemy) < 180 && random() < 0.35) + if (!G_EntExists(self->enemy)) + return; + + dist = entdist(self, self->enemy); + if (visible(self, self->enemy) && dist <= 512) self->monsterinfo.currentmove = &gekk_move_leapatk; else self->monsterinfo.currentmove = &gekk_move_spit; diff --git a/src/entities/drone/drone_guncmdr.c b/src/entities/drone/drone_guncmdr.c index 427f0871..366954ef 100644 --- a/src/entities/drone/drone_guncmdr.c +++ b/src/entities/drone/drone_guncmdr.c @@ -1102,6 +1102,7 @@ void init_drone_guncmdr(edict_t *self) self->mass = 255; self->monsterinfo.jumpdn = 512; self->monsterinfo.jumpup = 64; + self->s.scale = 1.2f; if (random() > 0.5) self->item = FindItemByClassname("ammo_bullets"); diff --git a/src/entities/drone/drone_infantry.c b/src/entities/drone/drone_infantry.c index e855d10a..b2231247 100644 --- a/src/entities/drone/drone_infantry.c +++ b/src/entities/drone/drone_infantry.c @@ -791,6 +791,7 @@ void init_drone_infantry(edict_t* self) self->mass = 400; + self->s.scale = 1.15f; //don't override previous mtype if (!self->mtype) self->mtype = M_ENFORCER; diff --git a/src/entities/drone/drone_medic.c b/src/entities/drone/drone_medic.c index 8e42d27f..24123887 100644 --- a/src/entities/drone/drone_medic.c +++ b/src/entities/drone/drone_medic.c @@ -1133,7 +1133,7 @@ static void spawngrow_beam_think(edict_t *self) self->nextthink = level.time + FRAMETIME; } -static void SpawnGrow_Spawn(vec3_t startpos, float start_size, float end_size) +void SpawnGrow_Spawn(vec3_t startpos, float start_size, float end_size) { edict_t *ent; edict_t *beam; @@ -1197,6 +1197,17 @@ static void medic_commander_cleanup_failed_spawn(edict_t *owner, edict_t *spawne G_FreeEdict(spawned); } +static void medic_commander_setup_invasion_spawn(edict_t *spawned) +{ + if (!invasion->value) + return; + + spawned->monsterinfo.aiflags &= ~AI_STAND_GROUND; + spawned->monsterinfo.aiflags |= AI_FIND_NAVI; + spawned->prev_navi = NULL; + spawned->goalentity = NULL; +} + static qboolean medic_commander_valid_spawn_spot(edict_t *self, vec3_t mins, vec3_t maxs, vec3_t spot) { if (G_IsValidLocation(self, spot, mins, maxs)) @@ -1273,6 +1284,7 @@ static qboolean medic_commander_spawn_gunner(edict_t *self, float side) VectorCopy(self->s.angles, gunner->s.angles); gunner->nextthink = level.time + FRAMETIME; gunner->monsterinfo.attack_finished = level.time + 1.0; + medic_commander_setup_invasion_spawn(gunner); gi.linkentity(gunner); diff --git a/src/entities/drone/drone_misc.c b/src/entities/drone/drone_misc.c index 136c664b..d33239c1 100644 --- a/src/entities/drone/drone_misc.c +++ b/src/entities/drone/drone_misc.c @@ -42,6 +42,7 @@ void init_drone_gladc(edict_t* self); void init_drone_stalker(edict_t* self); void init_drone_gekk(edict_t* self); void init_drone_arachnid(edict_t* self); +void init_drone_carrier(edict_t* self); void init_baron_fire(edict_t* self); void init_skeleton(edict_t* self); void init_golem(edict_t* self); @@ -529,7 +530,7 @@ void drone_death (edict_t *self, edict_t *attacker) //4.2 bosses can drop up to 4 runes - if (self->mtype == M_COMMANDER || self->mtype == M_SUPERTANK || self->mtype == M_MAKRON) + if (self->mtype == M_COMMANDER || self->mtype == M_SUPERTANK || self->mtype == M_MAKRON || self->mtype == M_CARRIER) { edict_t *e; float drop_chance = 0.25; @@ -881,6 +882,7 @@ edict_t *vrx_create_drone_from_ent(edict_t *drone, edict_t *ent, enum dronespawn case DS_BARON_FIRE: init_baron_fire(drone); break; case DS_SUPERTANK: init_drone_supertank(drone); break; case DS_JORG: init_drone_jorg(drone); break; + case DS_CARRIER: init_drone_carrier(drone); break; // default default: init_drone_gunner(drone); break; @@ -2075,11 +2077,22 @@ qboolean M_Initialize (edict_t *ent, edict_t *monster, float dur_bonus) case M_STALKER: init_drone_stalker(monster); break; case M_GEKK: init_drone_gekk(monster); break; case M_ARACHNID: init_drone_arachnid(monster); break; + case M_CARRIER: init_drone_carrier(monster); break; case M_SKELETON: init_skeleton(monster); break; case M_GOLEM: init_golem(monster); break; default: return false; } +#ifdef VRX_REPRO + if (monster->s.scale) + { + monster->monsterinfo.scale *= monster->s.scale; + VectorScale(monster->mins, monster->s.scale, monster->mins); + VectorScale(monster->maxs, monster->s.scale, monster->maxs); + monster->mass *= monster->s.scale; + } +#endif + if ( (ent && ent->inuse && ent->client) || (monster->activator && monster->activator->inuse && monster->activator->client) ) // player summons exception. { @@ -2193,6 +2206,10 @@ qboolean M_SetBoundingBox (int mtype, vec3_t boxmin, vec3_t boxmax) VectorSet(boxmin, -48, -48, -20); VectorSet(boxmax, 48, 48, 48); break; + case M_CARRIER: + VectorSet(boxmin, -80, -80, -24); + VectorSet(boxmax, 80, 80, 104); + break; case M_MEDIC: case M_MEDIC_COMMANDER: case M_MUTANT: @@ -2273,6 +2290,7 @@ char *GetMonsterKindString (int mtype) case M_STALKER: return "Stalker"; case M_GEKK: return "Gekk"; case M_ARACHNID: return "Arachnid"; + case M_CARRIER: return "Carrier"; case M_SKELETON: return "Skeleton"; case M_GOLEM: return "Golem"; case M_BARON_FIRE: return "Fire Baron"; diff --git a/src/g_local.h b/src/g_local.h index 2c972564..f22645c8 100644 --- a/src/g_local.h +++ b/src/g_local.h @@ -1517,6 +1517,7 @@ enum mtype_t { M_CHICK_HEAT = 36, M_ARACHNID = 37, M_MEDIC_COMMANDER = 38, + M_CARRIER = 39, M_MINISENTRY = 100, M_SENTRY = 101, M_BFG_SENTRY = 102, @@ -1631,6 +1632,7 @@ enum dronespawn_t { DS_BARON_FIRE = 32, DS_SUPERTANK = 33, DS_JORG = 34, + DS_CARRIER = 35, }; @@ -1757,6 +1759,7 @@ void fire_disruptor(edict_t *self, vec3_t start, vec3_t dir, int damage, int spe void fire_ionripper (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int effect); void fire_flechette (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int kick); void fire_heat (edict_t *self, vec3_t start, vec3_t aimdir, vec3_t offset, int damage, int kick, qboolean monster); +void SpawnGrow_Spawn(vec3_t startpos, float start_size, float end_size); void fire_blueblaster (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int effect); void fire_plasma (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage); void fire_prox (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float damage_radius); diff --git a/src/gamemodes/invasion.c b/src/gamemodes/invasion.c index 06cc430b..4e1cbad2 100644 --- a/src/gamemodes/invasion.c +++ b/src/gamemodes/invasion.c @@ -87,6 +87,11 @@ static constexpr int SET_PARASITE_MONSTERS[] = { constexpr int SET_PARASITE_MONSTERS_COUNT = sizeof(SET_PARASITE_MONSTERS) / sizeof(int); +static constexpr int SET_BOSS_MONSTERS[] = { + DS_COMMANDER, DS_MAKRON, DS_BARON_FIRE, DS_CARRIER +}; +constexpr int SET_BOSS_MONSTERS_COUNT = sizeof(SET_BOSS_MONSTERS) / sizeof(int); + qboolean vrx_inv_is_boss_wave(int wave) { return wave % 5 == 0 && wave > 0; } @@ -621,7 +626,7 @@ void vrx_inv_boss_check(edict_t *self) { while ((e = vrx_inv_get_monster_spawn(e)) != NULL) { if (invasion_data.boss) continue; - invasion_data.boss = vrx_inv_spawn_drone(self, e, GetRandom(30, 32)); + invasion_data.boss = vrx_inv_spawn_drone(self, e, SET_BOSS_MONSTERS[GetRandom(0, SET_BOSS_MONSTERS_COUNT - 1)]); if (!invasion_data.boss) { iter++; diff --git a/src/quake2/g_layout.c b/src/quake2/g_layout.c index 960f0f8d..207796b3 100644 --- a/src/quake2/g_layout.c +++ b/src/quake2/g_layout.c @@ -331,6 +331,7 @@ sidebar_entry_t layout_add_entity_info(sidebar_t* sidebar, edict_t* ent) case M_STALKER: case M_GEKK: case M_ARACHNID: + case M_CARRIER: case M_SKELETON: case M_GOLEM: name = lva("%s", V_GetMonsterName(ent)); diff --git a/src/quake2/g_save.c b/src/quake2/g_save.c index 3bca9700..5adc0768 100644 --- a/src/quake2/g_save.c +++ b/src/quake2/g_save.c @@ -20,6 +20,9 @@ cvar_t *bot_debugmonster; field_t fields[] = { { "classname", FOFS(classname), F_LSTRING }, { "origin", FOFS(s.origin), F_VECTOR }, +#ifdef VRX_REPRO + { "scale", FOFS(s.scale), F_FLOAT }, +#endif { "model", FOFS(model), F_LSTRING }, { "spawnflags", FOFS(spawnflags), F_INT }, { "speed", FOFS(speed), F_FLOAT }, diff --git a/src/quake2/g_svcmds.c b/src/quake2/g_svcmds.c index 420fdcc3..35245aa7 100644 --- a/src/quake2/g_svcmds.c +++ b/src/quake2/g_svcmds.c @@ -644,6 +644,13 @@ void SVCmd_SpawnBoss_f (void) else vrx_create_new_drone(m_worldspawn, 34, true, true, 0); } + else if (!strcmp(gi.argv(2), "carrier")) + { + if (invasion->value) + vrx_inv_spawn_boss(m_worldspawn, DS_CARRIER); + else + vrx_create_new_drone(m_worldspawn, DS_CARRIER, true, true, 0); + } else safe_cprintf(NULL, PRINT_HIGH, "Invalid boss type. Usage: sv spawnboss \n"); } diff --git a/src/quake2/monsterframes/m_rogue_carrier.h b/src/quake2/monsterframes/m_rogue_carrier.h new file mode 100644 index 00000000..ec94e855 --- /dev/null +++ b/src/quake2/monsterframes/m_rogue_carrier.h @@ -0,0 +1,86 @@ +// Licensed under the GNU General Public License 2.0. +// xpack/models/monsters/carrier + +enum +{ + FRAME_search01, + FRAME_search02, + FRAME_search03, + FRAME_search04, + FRAME_search05, + FRAME_search06, + FRAME_search07, + FRAME_search08, + FRAME_search09, + FRAME_search10, + FRAME_search11, + FRAME_search12, + FRAME_search13, + FRAME_firea01, + FRAME_firea02, + FRAME_firea03, + FRAME_firea04, + FRAME_firea05, + FRAME_firea06, + FRAME_firea07, + FRAME_firea08, + FRAME_firea09, + FRAME_firea10, + FRAME_firea11, + FRAME_firea12, + FRAME_firea13, + FRAME_firea14, + FRAME_firea15, + FRAME_fireb01, + FRAME_fireb02, + FRAME_fireb03, + FRAME_fireb04, + FRAME_fireb05, + FRAME_fireb06, + FRAME_fireb07, + FRAME_fireb08, + FRAME_fireb09, + FRAME_fireb10, + FRAME_fireb11, + FRAME_fireb12, + FRAME_fireb13, + FRAME_fireb14, + FRAME_fireb15, + FRAME_fireb16, + FRAME_spawn01, + FRAME_spawn02, + FRAME_spawn03, + FRAME_spawn04, + FRAME_spawn05, + FRAME_spawn06, + FRAME_spawn07, + FRAME_spawn08, + FRAME_spawn09, + FRAME_spawn10, + FRAME_spawn11, + FRAME_spawn12, + FRAME_spawn13, + FRAME_spawn14, + FRAME_spawn15, + FRAME_spawn16, + FRAME_spawn17, + FRAME_spawn18, + FRAME_death01, + FRAME_death02, + FRAME_death03, + FRAME_death04, + FRAME_death05, + FRAME_death06, + FRAME_death07, + FRAME_death08, + FRAME_death09, + FRAME_death10, + FRAME_death11, + FRAME_death12, + FRAME_death13, + FRAME_death14, + FRAME_death15, + FRAME_death16 +}; + +constexpr float MODEL_SCALE = 2.000000f; diff --git a/src/server/v_luasettings.c b/src/server/v_luasettings.c index 1d2cb003..f197326d 100644 --- a/src/server/v_luasettings.c +++ b/src/server/v_luasettings.c @@ -1138,6 +1138,10 @@ void Lua_LoadVariables() M_ARACHNID_ADDON_HEALTH = vrx_lua_get_variable("M_ARACHNID_ADDON_HEALTH", 10); M_ARACHNID_INITIAL_ARMOR = vrx_lua_get_variable("M_ARACHNID_INITIAL_ARMOR", 100); M_ARACHNID_ADDON_ARMOR = vrx_lua_get_variable("M_ARACHNID_ADDON_ARMOR", 20); + M_CARRIER_INITIAL_HEALTH = vrx_lua_get_variable("M_CARRIER_INITIAL_HEALTH", 2200); + M_CARRIER_ADDON_HEALTH = vrx_lua_get_variable("M_CARRIER_ADDON_HEALTH", 650); + M_CARRIER_INITIAL_ARMOR = vrx_lua_get_variable("M_CARRIER_INITIAL_ARMOR", 500); + M_CARRIER_ADDON_ARMOR = vrx_lua_get_variable("M_CARRIER_ADDON_ARMOR", 350); M_BRAIN_INITIAL_PULL = vrx_lua_get_variable("M_BRAIN_INITIAL_PULL", -60); M_BRAIN_ADDON_PULL = vrx_lua_get_variable("M_BRAIN_ADDON_PULL", -2); From fce23ecb4d2641c24caba1744d521d40bf545cb2 Mon Sep 17 00:00:00 2001 From: Enemy-123 Date: Tue, 21 Apr 2026 21:44:11 -0400 Subject: [PATCH 06/24] added soldier new variants, hover improved dying animation, added fire_dabeam for guardian, brain and laser soldier, soldiers improved animation for new weapons, added janitor, a small supertank variant, miniguardian, fixed carrier crash and death animation, added missing remaster's m_flash.c , added punch effect for tanks and runnertanks, TODO: find a way to not change bosses ID enum dronespawn_t so some monster entities on maps won't bug, and pvm variants would not get excluded for pvm (janitor, miniguardian, soldiers) --- lua/variables.lua | 24 + src/characters/settings.h | 26 + src/characters/v_utils.c | 12 + src/combat/weapons/g_weapon.c | 3 +- src/entities/drone/drone_brain.c | 120 ++++- src/entities/drone/drone_carrier.c | 35 +- src/entities/drone/drone_guardian.c | 669 ++++++++++++++++++++++++++ src/entities/drone/drone_hover.c | 38 +- src/entities/drone/drone_medic.c | 6 +- src/entities/drone/drone_misc.c | 85 +++- src/entities/drone/drone_runnertank.c | 19 + src/entities/drone/drone_soldier.c | 287 ++++++++++- src/entities/drone/drone_supertank.c | 42 +- src/entities/drone/drone_tank.c | 19 + src/entities/drone/g_monster.c | 241 +++++++++- src/entities/g_spawn.c | 59 ++- src/g_local.h | 22 + src/q_shared.h | 2 +- src/quake2/g_layout.c | 6 + src/quake2/g_main.c | 3 +- src/quake2/g_svcmds.c | 9 +- src/quake2/monsterframes/m_flash.c | 137 +++++- src/quake2/monsterframes/m_guardian.h | 221 +++++++++ src/quake2/monsterframes/m_soldier.h | 100 ++++ src/server/v_luasettings.c | 27 ++ 25 files changed, 2138 insertions(+), 74 deletions(-) create mode 100644 src/entities/drone/drone_guardian.c create mode 100644 src/quake2/monsterframes/m_guardian.h diff --git a/lua/variables.lua b/lua/variables.lua index bd8418ce..e1ea33fc 100644 --- a/lua/variables.lua +++ b/lua/variables.lua @@ -783,6 +783,30 @@ M_CARRIER_INITIAL_HEALTH = 2200 M_CARRIER_ADDON_HEALTH = 650 M_CARRIER_INITIAL_ARMOR = 500 M_CARRIER_ADDON_ARMOR = 350 +M_GUARDIAN_INITIAL_HEALTH = 6500 +M_GUARDIAN_ADDON_HEALTH = 1200 +M_GUARDIAN_INITIAL_ARMOR = 550 +M_GUARDIAN_ADDON_ARMOR = 250 +M_JANITOR_INITIAL_HEALTH = 125 +M_JANITOR_ADDON_HEALTH = 50 +M_JANITOR_INITIAL_ARMOR = 300 +M_JANITOR_ADDON_ARMOR = 100 +M_MINIGUARDIAN_INITIAL_HEALTH = 275 +M_MINIGUARDIAN_ADDON_HEALTH = 80 +M_MINIGUARDIAN_INITIAL_ARMOR = 200 +M_MINIGUARDIAN_ADDON_ARMOR = 125 +M_SOLDIER_RIPPER_INITIAL_HEALTH = 100 +M_SOLDIER_RIPPER_ADDON_HEALTH = 40 +M_SOLDIER_RIPPER_INITIAL_ARMOR = 50 +M_SOLDIER_RIPPER_ADDON_ARMOR = 20 +M_SOLDIER_BLUEBLASTER_INITIAL_HEALTH = 100 +M_SOLDIER_BLUEBLASTER_ADDON_HEALTH = 40 +M_SOLDIER_BLUEBLASTER_INITIAL_ARMOR = 50 +M_SOLDIER_BLUEBLASTER_ADDON_ARMOR = 20 +M_SOLDIER_LASER_INITIAL_HEALTH = 120 +M_SOLDIER_LASER_ADDON_HEALTH = 45 +M_SOLDIER_LASER_INITIAL_ARMOR = 70 +M_SOLDIER_LASER_ADDON_ARMOR = 25 -- Monster Weapons -- M_ENABLE_WORLDSPAWN_SOFTCAP = 1 diff --git a/src/characters/settings.h b/src/characters/settings.h index a2204dec..68ca96b6 100644 --- a/src/characters/settings.h +++ b/src/characters/settings.h @@ -818,6 +818,30 @@ VRX_V_LUASETTINGS_IMPL double M_CARRIER_INITIAL_HEALTH; VRX_V_LUASETTINGS_IMPL double M_CARRIER_ADDON_HEALTH; VRX_V_LUASETTINGS_IMPL double M_CARRIER_INITIAL_ARMOR; VRX_V_LUASETTINGS_IMPL double M_CARRIER_ADDON_ARMOR; +VRX_V_LUASETTINGS_IMPL double M_GUARDIAN_INITIAL_HEALTH; +VRX_V_LUASETTINGS_IMPL double M_GUARDIAN_ADDON_HEALTH; +VRX_V_LUASETTINGS_IMPL double M_GUARDIAN_INITIAL_ARMOR; +VRX_V_LUASETTINGS_IMPL double M_GUARDIAN_ADDON_ARMOR; +VRX_V_LUASETTINGS_IMPL double M_JANITOR_INITIAL_HEALTH; +VRX_V_LUASETTINGS_IMPL double M_JANITOR_ADDON_HEALTH; +VRX_V_LUASETTINGS_IMPL double M_JANITOR_INITIAL_ARMOR; +VRX_V_LUASETTINGS_IMPL double M_JANITOR_ADDON_ARMOR; +VRX_V_LUASETTINGS_IMPL double M_MINIGUARDIAN_INITIAL_HEALTH; +VRX_V_LUASETTINGS_IMPL double M_MINIGUARDIAN_ADDON_HEALTH; +VRX_V_LUASETTINGS_IMPL double M_MINIGUARDIAN_INITIAL_ARMOR; +VRX_V_LUASETTINGS_IMPL double M_MINIGUARDIAN_ADDON_ARMOR; +VRX_V_LUASETTINGS_IMPL double M_SOLDIER_RIPPER_INITIAL_HEALTH; +VRX_V_LUASETTINGS_IMPL double M_SOLDIER_RIPPER_ADDON_HEALTH; +VRX_V_LUASETTINGS_IMPL double M_SOLDIER_RIPPER_INITIAL_ARMOR; +VRX_V_LUASETTINGS_IMPL double M_SOLDIER_RIPPER_ADDON_ARMOR; +VRX_V_LUASETTINGS_IMPL double M_SOLDIER_BLUEBLASTER_INITIAL_HEALTH; +VRX_V_LUASETTINGS_IMPL double M_SOLDIER_BLUEBLASTER_ADDON_HEALTH; +VRX_V_LUASETTINGS_IMPL double M_SOLDIER_BLUEBLASTER_INITIAL_ARMOR; +VRX_V_LUASETTINGS_IMPL double M_SOLDIER_BLUEBLASTER_ADDON_ARMOR; +VRX_V_LUASETTINGS_IMPL double M_SOLDIER_LASER_INITIAL_HEALTH; +VRX_V_LUASETTINGS_IMPL double M_SOLDIER_LASER_ADDON_HEALTH; +VRX_V_LUASETTINGS_IMPL double M_SOLDIER_LASER_INITIAL_ARMOR; +VRX_V_LUASETTINGS_IMPL double M_SOLDIER_LASER_ADDON_ARMOR; VRX_V_LUASETTINGS_IMPL double M_BRAIN_INITIAL_PULL; VRX_V_LUASETTINGS_IMPL double M_BRAIN_ADDON_PULL; VRX_V_LUASETTINGS_IMPL double M_BRAIN_MAX_PULL; @@ -843,6 +867,8 @@ VRX_V_LUASETTINGS_IMPL double M_RAILGUN_DMG_BASE; VRX_V_LUASETTINGS_IMPL double M_SHOTGUN_DMG_MAX; VRX_V_LUASETTINGS_IMPL double M_RAILGUN_DMG_ADDON; VRX_V_LUASETTINGS_IMPL double M_RAILGUN_DMG_MAX; +VRX_V_LUASETTINGS_IMPL double M_DABEAM_DMG_BASE; +VRX_V_LUASETTINGS_IMPL double M_DABEAM_DMG_ADDON; VRX_V_LUASETTINGS_IMPL double M_MELEE_DMG_BASE; VRX_V_LUASETTINGS_IMPL double M_MELEE_DMG_ADDON; VRX_V_LUASETTINGS_IMPL double M_MELEE_DMG_MAX; diff --git a/src/characters/v_utils.c b/src/characters/v_utils.c index 4523621b..573f913c 100644 --- a/src/characters/v_utils.c +++ b/src/characters/v_utils.c @@ -2043,6 +2043,12 @@ char *V_GetMonsterKind(int mtype) { case M_SOLDIERLT: case M_SOLDIERSS: return "soldier"; + case M_SOLDIER_RIPPER: + return "Ripper Guard"; + case M_SOLDIER_BLUEBLASTER: + return "Hyper Guard"; + case M_SOLDIER_LASER: + return "Laser Guard"; case M_FLIPPER: return "flipper"; case M_FLYER: @@ -2103,6 +2109,12 @@ char *V_GetMonsterKind(int mtype) { return "arachnid"; case M_CARRIER: return "carrier"; + case M_GUARDIAN: + return "Guardian"; + case M_JANITOR: + return "Janitor"; + case M_MINIGUARDIAN: + return "Mini Guardian"; case M_SKELETON: return "skeleton"; case M_GOLEM: diff --git a/src/combat/weapons/g_weapon.c b/src/combat/weapons/g_weapon.c index 8412ef98..afc5c409 100644 --- a/src/combat/weapons/g_weapon.c +++ b/src/combat/weapons/g_weapon.c @@ -512,7 +512,8 @@ void blaster_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t * if (other->takedamage) { WeaponStun(self->owner, other, self->style); - T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 0, DAMAGE_ENERGY, self->style); + T_Damage (other, self, self->owner, self->velocity, self->s.origin, + plane ? plane->normal : vec3_origin, self->dmg, 0, DAMAGE_ENERGY, self->style); } else { diff --git a/src/entities/drone/drone_brain.c b/src/entities/drone/drone_brain.c index f8b4fe80..3989eeec 100644 --- a/src/entities/drone/drone_brain.c +++ b/src/entities/drone/drone_brain.c @@ -727,6 +727,118 @@ void mybrain_attack3 (edict_t *self) self->monsterinfo.currentmove = &mybrain_move_attack3; } +static const vec3_t brain_reye[] = +{ + {0.746700f, 0.238370f, 34.167690f}, + {-1.076390f, 0.238370f, 33.386372f}, + {-1.335500f, 5.334300f, 32.177170f}, + {-0.175360f, 8.846370f, 30.635479f}, + {-2.757590f, 7.804610f, 30.150860f}, + {-5.575090f, 5.152840f, 30.056160f}, + {-7.017550f, 3.262470f, 30.552521f}, + {-7.915740f, 0.638800f, 33.176189f}, + {-3.915390f, 8.285730f, 33.976349f}, + {-0.913540f, 10.933030f, 34.141811f}, + {-0.369900f, 8.923900f, 34.189079f} +}; + +static const vec3_t brain_leye[] = +{ + {-3.364710f, 0.327750f, 33.938381f}, + {-5.140450f, 0.493480f, 32.659851f}, + {-5.341980f, 5.646980f, 31.277901f}, + {-4.134480f, 9.277440f, 29.925621f}, + {-6.598340f, 6.815090f, 29.322620f}, + {-8.610840f, 2.529650f, 29.251591f}, + {-9.231360f, 0.093280f, 29.747959f}, + {-11.004110f, 1.936930f, 32.395260f}, + {-7.878310f, 7.648190f, 33.148151f}, + {-4.947370f, 11.430050f, 33.313610f}, + {-4.332820f, 9.444570f, 33.526340f} +}; + +static void mybrain_eye_laser_update(edict_t *laser, qboolean left_eye) +{ + edict_t *self; + vec3_t forward, right, up, start, aim, target; + int frame_index; + const vec3_t *eye_offsets; + qboolean do_damage; + + self = laser->owner; + if (!self || !self->inuse || !G_EntExists(self->enemy)) + { + laser->spawnflags |= DABEAM_SPAWNED; + return; + } + + AngleVectors(self->s.angles, forward, right, up); + frame_index = self->s.frame - FRAME_walk101; + if (frame_index < 0 || frame_index >= 11) + frame_index = 0; + + eye_offsets = left_eye ? brain_leye : brain_reye; + VectorCopy(self->s.origin, start); + VectorMA(start, eye_offsets[frame_index][0], right, start); + VectorMA(start, eye_offsets[frame_index][1], forward, start); + VectorMA(start, eye_offsets[frame_index][2], up, start); + + G_EntMidPoint(self->enemy, target); + VectorSubtract(target, start, aim); + VectorNormalize(aim); + + VectorCopy(start, laser->s.origin); + VectorCopy(aim, laser->movedir); + + do_damage = !(laser->spawnflags & DABEAM_SPAWNED); + dabeam_update(laser, do_damage); + laser->spawnflags |= DABEAM_SPAWNED; +} + +static void mybrain_right_eye_laser_update(edict_t *laser) +{ + mybrain_eye_laser_update(laser, false); +} + +static void mybrain_left_eye_laser_update(edict_t *laser) +{ + mybrain_eye_laser_update(laser, true); +} + +static void mybrain_laserbeam(edict_t *self) +{ + int damage; + + if (!G_EntExists(self->enemy)) + return; + + damage = M_DABEAM_DMG_BASE + M_DABEAM_DMG_ADDON * drone_damagelevel(self); + monster_fire_dabeam(self, damage, false, mybrain_right_eye_laser_update); + monster_fire_dabeam(self, damage, true, mybrain_left_eye_laser_update); +} + +static void mybrain_laserbeam_reattack(edict_t *self) +{ + if (G_EntExists(self->enemy) && visible(self, self->enemy) && self->enemy->health > 0 && random() < 0.5) + self->monsterinfo.nextframe = FRAME_walk101; +} + +mframe_t mybrain_frames_attack4[] = +{ + ai_charge, 0, mybrain_laserbeam, + ai_charge, 0, mybrain_laserbeam, + ai_charge, 0, mybrain_laserbeam, + ai_charge, 0, mybrain_laserbeam, + ai_charge, 0, mybrain_laserbeam, + ai_charge, 0, mybrain_laserbeam, + ai_charge, 0, mybrain_laserbeam, + ai_charge, 0, mybrain_laserbeam, + ai_charge, 0, mybrain_laserbeam, + ai_charge, 0, mybrain_laserbeam, + ai_charge, 0, mybrain_laserbeam_reattack +}; +mmove_t mybrain_move_attack4 = {FRAME_walk101, FRAME_walk111, mybrain_frames_attack4, mybrain_run}; + void mybrain_melee (edict_t *self) { if (entdist(self, self->enemy) < MELEE_DISTANCE) @@ -741,11 +853,17 @@ void mybrain_melee (edict_t *self) void mybrain_attack (edict_t *self) { float dist; + qboolean has_los; dist = entdist(self, self->enemy); + has_los = visible(self, self->enemy); // jump to our enemy if he's close and on even ground - if ((dist > 256) && (self->enemy->absmin[2]+18 >= self->absmin[2]) + if (has_los && dist > 512) + self->monsterinfo.currentmove = &mybrain_move_attack4; + else if (has_los && dist > MELEE_DISTANCE && random() < 0.4) + self->monsterinfo.currentmove = &mybrain_move_attack4; + else if ((dist > 256) && (self->enemy->absmin[2]+18 >= self->absmin[2]) && (self->enemy->absmin[2]-18 <= self->absmin[2]) && !(self->monsterinfo.aiflags & AI_STAND_GROUND)) self->monsterinfo.currentmove = &mybrain_move_jumpattack; diff --git a/src/entities/drone/drone_carrier.c b/src/entities/drone/drone_carrier.c index 7d3ef17d..e848e1ac 100644 --- a/src/entities/drone/drone_carrier.c +++ b/src/entities/drone/drone_carrier.c @@ -42,6 +42,21 @@ static void carrier_sight(edict_t *self, edict_t *other) gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); } +static void carrier_explode(edict_t *self) +{ + vec3_t org; + + VectorCopy(self->s.origin, org); + org[0] += crandom() * self->maxs[0]; + org[1] += crandom() * self->maxs[1]; + org[2] += crandom() * self->maxs[2]; + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1_BIG); + gi.WritePosition(org); + gi.multicast(self->s.origin, MULTICAST_PVS); +} + static void carrier_dead(edict_t *self) { int n; @@ -51,7 +66,12 @@ static void carrier_dead(edict_t *self) for (n = 0; n < 6; n++) ThrowGib(self, "models/objects/gibs/sm_metal/tris.md2", 500, GIB_METALLIC); - BecomeBigExplosion(self); + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1_BIG); + gi.WritePosition(self->s.origin); + gi.multicast(self->s.origin, MULTICAST_PVS); + + M_Remove(self, false, false); } static void carrier_fire_rocket(edict_t *self) @@ -433,22 +453,22 @@ static void carrier_pain(edict_t *self, edict_t *other, float kick, int damage) mframe_t carrier_frames_death[] = { + ai_move, 0, carrier_explode, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, + ai_move, 0, carrier_explode, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, + ai_move, 0, carrier_explode, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, + ai_move, 0, carrier_explode, ai_move, 0, NULL, ai_move, 0, NULL, - ai_move, 0, NULL, - ai_move, 0, NULL, - ai_move, 0, NULL, - ai_move, 0, NULL, - ai_move, 0, NULL + ai_move, 0, carrier_explode }; mmove_t carrier_move_death = { FRAME_death01, FRAME_death16, carrier_frames_death, carrier_dead }; @@ -464,9 +484,6 @@ static void carrier_die(edict_t *self, edict_t *inflictor, edict_t *attacker, in self->deadflag = DEAD_DEAD; self->takedamage = DAMAGE_YES; self->monsterinfo.currentmove = &carrier_move_death; - - if (self->activator && !self->activator->client) - self->activator->num_monsters_real--; } void init_drone_carrier(edict_t *self) diff --git a/src/entities/drone/drone_guardian.c b/src/entities/drone/drone_guardian.c new file mode 100644 index 00000000..26cd7e11 --- /dev/null +++ b/src/entities/drone/drone_guardian.c @@ -0,0 +1,669 @@ +/* +============================================================================== + +GUARDIAN / MiniGuardian + +============================================================================== +*/ + +#include "g_local.h" +#include "../../quake2/monsterframes/m_guardian.h" + +static int sound_step; +static int sound_charge; +static int sound_spin_loop; +static int sound_laser; +static int sound_pew; + +void guardian_run(edict_t *self); + +static void guardian_footstep(edict_t *self) +{ + gi.sound(self, CHAN_BODY, sound_step, 1, ATTN_NORM, 0); +} + +mframe_t guardian_frames_stand[] = +{ + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL +}; +mmove_t guardian_move_stand = {FRAME_idle1, FRAME_idle52, guardian_frames_stand, NULL}; + +void guardian_stand(edict_t *self) +{ + self->monsterinfo.currentmove = &guardian_move_stand; +} + +mframe_t guardian_frames_walk[] = +{ + drone_ai_walk, 8, NULL, + drone_ai_walk, 8, NULL, + drone_ai_walk, 8, NULL, + drone_ai_walk, 8, NULL, + drone_ai_walk, 8, NULL, + drone_ai_walk, 8, NULL, + drone_ai_walk, 8, NULL, + drone_ai_walk, 8, guardian_footstep, + drone_ai_walk, 8, NULL, + drone_ai_walk, 8, NULL, + drone_ai_walk, 8, NULL, + drone_ai_walk, 8, NULL, + drone_ai_walk, 8, NULL, + drone_ai_walk, 8, NULL, + drone_ai_walk, 8, NULL, + drone_ai_walk, 8, NULL, + drone_ai_walk, 8, NULL, + drone_ai_walk, 8, guardian_footstep, + drone_ai_walk, 8, NULL +}; +mmove_t guardian_move_walk = {FRAME_walk1, FRAME_walk19, guardian_frames_walk, NULL}; + +void guardian_walk(edict_t *self) +{ + self->monsterinfo.currentmove = &guardian_move_walk; +} + +mframe_t guardian_frames_run[] = +{ + drone_ai_run, 8, NULL, + drone_ai_run, 8, NULL, + drone_ai_run, 8, NULL, + drone_ai_run, 8, NULL, + drone_ai_run, 8, NULL, + drone_ai_run, 8, NULL, + drone_ai_run, 8, NULL, + drone_ai_run, 8, guardian_footstep, + drone_ai_run, 8, NULL, + drone_ai_run, 8, NULL, + drone_ai_run, 8, NULL, + drone_ai_run, 8, NULL, + drone_ai_run, 8, NULL, + drone_ai_run, 8, NULL, + drone_ai_run, 8, NULL, + drone_ai_run, 8, NULL, + drone_ai_run, 8, NULL, + drone_ai_run, 8, guardian_footstep, + drone_ai_run, 8, NULL +}; +mmove_t guardian_move_run = {FRAME_walk1, FRAME_walk19, guardian_frames_run, NULL}; + +void guardian_run(edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &guardian_move_stand; + else + self->monsterinfo.currentmove = &guardian_move_run; +} + +mframe_t guardian_frames_pain1[] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t guardian_move_pain1 = {FRAME_pain1_1, FRAME_pain1_8, guardian_frames_pain1, guardian_run}; + +void guardian_pain(edict_t *self, edict_t *other, float kick, int damage) +{ + (void)other; + (void)kick; + + if (level.time < self->pain_debounce_time) + return; + if (damage <= 75 && random() > 0.2) + return; + if ((self->s.frame >= FRAME_atk1_spin1 && self->s.frame <= FRAME_atk1_spin15) || + (self->s.frame >= FRAME_atk2_fire1 && self->s.frame <= FRAME_atk2_fire4) || + (self->s.frame >= FRAME_kick_in1 && self->s.frame <= FRAME_kick_in13)) + return; + if (G_GetClient(self) || invasion->value == 2) + return; + + self->pain_debounce_time = level.time + 3.0; + self->monsterinfo.currentmove = &guardian_move_pain1; + self->s.sound = 0; +} + +mframe_t guardian_frames_atk1_out[] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t guardian_move_atk1_out = {FRAME_atk1_out1, FRAME_atk1_out3, guardian_frames_atk1_out, guardian_run}; + +void guardian_atk1_finish(edict_t *self) +{ + self->monsterinfo.currentmove = &guardian_move_atk1_out; + self->s.sound = 0; +} + +void guardian_atk1_charge(edict_t *self) +{ + self->s.sound = sound_spin_loop; + + if (self->mtype == M_GUARDIAN) + gi.sound(self, CHAN_WEAPON, sound_charge, 1, ATTN_NORM, 0); +} + +void guardian_fire_blaster(edict_t *self) +{ + vec3_t forward, right, start; + int damage, speed, effect; + + if (!G_EntExists(self->enemy)) + return; + + if (self->mtype == M_MINIGUARDIAN) + { + damage = IONRIPPER_INITIAL_DAMAGE + IONRIPPER_ADDON_DAMAGE * drone_damagelevel(self); + speed = IONRIPPER_INITIAL_SPEED + IONRIPPER_ADDON_SPEED * drone_damagelevel(self); + MonsterAim(self, M_PROJECTILE_ACC, speed, false, MZ2_SOLDIER_RIPPER_8, forward, start); + monster_fire_ionripper(self, start, forward, damage, speed, EF_IONRIPPER, MZ2_SOLDIER_RIPPER_8); + } + else + { + damage = M_HYPERBLASTER_DMG_BASE + M_HYPERBLASTER_DMG_ADDON * drone_damagelevel(self); + if (M_HYPERBLASTER_DMG_MAX && damage > M_HYPERBLASTER_DMG_MAX) + damage = M_HYPERBLASTER_DMG_MAX; + + speed = 1100; + effect = (self->s.frame % 4) ? 0 : EF_HYPERBLASTER; + AngleVectors(self->s.angles, forward, right, NULL); + G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_GUARDIAN_BLASTER], forward, right, start); + MonsterAim(self, M_PROJECTILE_ACC, speed, false, -1, forward, start); + monster_fire_blaster(self, start, forward, damage, speed, effect, BLASTER_PROJ_BOLT, 2.0, true, -1); + } + + if (G_EntExists(self->enemy) && self->s.frame == FRAME_atk1_spin12 && self->timestamp > level.time && visible(self, self->enemy)) + self->s.frame = FRAME_atk1_spin5 - 1; +} + +mframe_t guardian_frames_atk1_spin[] = +{ + ai_charge, 0, guardian_atk1_charge, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, guardian_fire_blaster, + ai_charge, 0, guardian_fire_blaster, + ai_charge, 0, guardian_fire_blaster, + ai_charge, 0, guardian_fire_blaster, + ai_charge, 0, guardian_fire_blaster, + ai_charge, 0, guardian_fire_blaster, + ai_charge, 0, guardian_fire_blaster, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t guardian_move_atk1_spin = {FRAME_atk1_spin1, FRAME_atk1_spin15, guardian_frames_atk1_spin, guardian_atk1_finish}; + +void guardian_atk1(edict_t *self) +{ + self->monsterinfo.currentmove = &guardian_move_atk1_spin; + self->timestamp = level.time + 0.65 + random() * 1.5; +} + +mframe_t guardian_frames_atk1_in[] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t guardian_move_atk1_in = {FRAME_atk1_in1, FRAME_atk1_in3, guardian_frames_atk1_in, guardian_atk1}; + +mframe_t guardian_frames_atk2_out[] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, guardian_footstep, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t guardian_move_atk2_out = {FRAME_atk2_out1, FRAME_atk2_out7, guardian_frames_atk2_out, guardian_run}; + +void guardian_atk2_out(edict_t *self) +{ + self->monsterinfo.currentmove = &guardian_move_atk2_out; +} + +static int guardian_grenade_flash(edict_t *self) +{ + return (self->s.frame & 1) ? MZ2_SUPERTANK_GRENADE_1 : MZ2_SUPERTANK_GRENADE_2; +} + +void guardian_grenade(edict_t *self) +{ + vec3_t forward, start; + int damage, speed, flash_number; + + if (!G_EntExists(self->enemy)) + return; + + damage = M_GRENADELAUNCHER_DMG_BASE + M_GRENADELAUNCHER_DMG_ADDON * drone_damagelevel(self); + if (M_GRENADELAUNCHER_DMG_MAX && damage > M_GRENADELAUNCHER_DMG_MAX) + damage = M_GRENADELAUNCHER_DMG_MAX; + speed = M_GRENADELAUNCHER_SPEED_BASE + M_GRENADELAUNCHER_SPEED_ADDON * drone_damagelevel(self); + if (M_GRENADELAUNCHER_SPEED_MAX && speed > M_GRENADELAUNCHER_SPEED_MAX) + speed = M_GRENADELAUNCHER_SPEED_MAX; + + flash_number = guardian_grenade_flash(self); + MonsterAim(self, M_PROJECTILE_ACC, speed, true, flash_number, forward, start); + monster_fire_grenade(self, start, forward, damage, speed, flash_number); +} + +void guardian_laser_fire(edict_t *self) +{ + int damage; + + if (!G_EntExists(self->enemy)) + return; + + gi.sound(self, CHAN_WEAPON, sound_laser, 1, ATTN_NORM, 0); + damage = M_DABEAM_DMG_BASE + M_DABEAM_DMG_ADDON * drone_damagelevel(self); + if (self->mtype == M_GUARDIAN) + damage += 10 + 2 * drone_damagelevel(self); + monster_fire_dabeam(self, damage, self->s.frame & 1, NULL); +} + +void guardian_fire_attack(edict_t *self) +{ + if (self->mtype == M_MINIGUARDIAN) + guardian_grenade(self); + else + guardian_laser_fire(self); +} + +mframe_t guardian_frames_atk2_fire[] = +{ + ai_charge, 0, guardian_fire_attack, + ai_charge, 0, guardian_fire_attack, + ai_charge, 0, guardian_fire_attack, + ai_charge, 0, guardian_fire_attack +}; +mmove_t guardian_move_atk2_fire = {FRAME_atk2_fire1, FRAME_atk2_fire4, guardian_frames_atk2_fire, guardian_atk2_out}; + +void guardian_atk2(edict_t *self) +{ + self->monsterinfo.currentmove = &guardian_move_atk2_fire; +} + +mframe_t guardian_frames_atk2_in[] = +{ + ai_charge, 0, guardian_footstep, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, guardian_footstep, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, guardian_footstep, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t guardian_move_atk2_in = {FRAME_atk2_in1, FRAME_atk2_in12, guardian_frames_atk2_in, guardian_atk2}; + +void guardian_fire_rocket(edict_t *self, float offset) +{ + vec3_t forward, right, up, start, dir; + int damage, speed; + + if (!G_EntExists(self->enemy)) + return; + + AngleVectors(self->s.angles, forward, right, up); + VectorCopy(self->s.origin, start); + VectorMA(start, -8, forward, start); + VectorMA(start, offset, right, start); + VectorMA(start, 50, up, start); + + speed = M_ROCKETLAUNCHER_SPEED_BASE + M_ROCKETLAUNCHER_SPEED_ADDON * drone_damagelevel(self); + if (M_ROCKETLAUNCHER_SPEED_MAX && speed > M_ROCKETLAUNCHER_SPEED_MAX) + speed = M_ROCKETLAUNCHER_SPEED_MAX; + speed *= 0.9f; + if (speed < 200) + speed = 200; + + damage = M_ROCKETLAUNCHER_DMG_BASE + M_ROCKETLAUNCHER_DMG_ADDON * drone_damagelevel(self); + if (M_ROCKETLAUNCHER_DMG_MAX && damage > M_ROCKETLAUNCHER_DMG_MAX) + damage = M_ROCKETLAUNCHER_DMG_MAX; + + VectorScale(forward, 0.35f, dir); + VectorMA(dir, 0.95f, up, dir); + VectorNormalize(dir); + monster_fire_heat(self, start, dir, damage, speed, MZ2_GUARDIAN_BLASTER, 0.18f); + gi.sound(self, CHAN_WEAPON, sound_pew, 1, 0.5f, 0); +} + +void guardian_fire_rocket_l(edict_t *self) +{ + guardian_fire_rocket(self, -14.0f); +} + +void guardian_fire_rocket_r(edict_t *self) +{ + guardian_fire_rocket(self, 14.0f); +} + +mframe_t guardian_frames_rocket[] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, guardian_fire_rocket_l, + ai_charge, 0, NULL, + ai_charge, 0, guardian_fire_rocket_l, + ai_charge, 0, NULL, + ai_charge, 0, guardian_fire_rocket_r, + ai_charge, 0, NULL, + ai_charge, 0, guardian_fire_rocket_r, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, guardian_fire_rocket_l, + ai_charge, 0, NULL, + ai_charge, 0, guardian_fire_rocket_l, + ai_charge, 0, NULL, + ai_charge, 0, guardian_fire_rocket_r, + ai_charge, 0, NULL, + ai_charge, 0, guardian_fire_rocket_r, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t guardian_move_rocket = {FRAME_turnl_1, FRAME_turnr_11, guardian_frames_rocket, guardian_run}; + +void guardian_kick(edict_t *self) +{ + vec3_t aim; + + if (!G_EntExists(self->enemy)) + { + self->monsterinfo.melee_finished = level.time + 1.0; + return; + } + + VectorSet(aim, MELEE_DISTANCE, 0, -80); + if (self->mtype == M_GUARDIAN) + aim[0] = 160; + if (!fire_hit(self, aim, (self->mtype == M_GUARDIAN) ? 85 : 30, 700)) + self->monsterinfo.melee_finished = level.time + ((self->mtype == M_GUARDIAN) ? 3.5 : 1.0); +} + +mframe_t guardian_frames_kick[] = +{ + ai_charge, 0, NULL, + ai_charge, 0, guardian_footstep, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, guardian_kick, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, guardian_footstep, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t guardian_move_kick = {FRAME_kick_in1, FRAME_kick_in13, guardian_frames_kick, guardian_run}; + +void guardian_attack(edict_t *self) +{ + float dist; + + if (!G_EntExists(self->enemy)) + return; + + dist = entdist(self, self->enemy); + + if (self->mtype == M_GUARDIAN && self->monsterinfo.melee_finished < level.time && dist < 160) + self->monsterinfo.currentmove = &guardian_move_kick; + else if (self->mtype != M_GUARDIAN && self->monsterinfo.melee_finished < level.time && dist < 120) + self->monsterinfo.currentmove = &guardian_move_kick; + else if (self->mtype == M_GUARDIAN && dist > 300 && self->count <= 0 && random() < 0.25) + { + self->monsterinfo.currentmove = &guardian_move_rocket; + self->count = 6; + } + else if (dist > 512 || (self->mtype == M_GUARDIAN && dist > 300 && random() < 0.5)) + self->monsterinfo.currentmove = &guardian_move_atk2_in; + else + self->monsterinfo.currentmove = &guardian_move_atk1_in; + + if (self->mtype == M_GUARDIAN && self->count > 0) + self->count--; + + M_DelayNextAttack(self, 0, true); +} + +void guardian_explode(edict_t *self) +{ + vec3_t org; + + VectorCopy(self->s.origin, org); + org[0] += crandom() * self->maxs[0]; + org[1] += crandom() * self->maxs[1]; + org[2] += 24 + random() * self->maxs[2]; + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1_BIG); + gi.WritePosition(org); + gi.multicast(self->s.origin, MULTICAST_PVS); +} + +void guardian_dead(edict_t *self) +{ + int n; + + if (vrx_spawn_nonessential_ent(self->s.origin)) + { + for (n = 0; n < 3; n++) + guardian_explode(self); + for (n = 0; n < 4; n++) + ThrowGib(self, "models/objects/gibs/sm_metal/tris.md2", 500, GIB_METALLIC); + for (n = 0; n < 2; n++) + ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", 500, GIB_ORGANIC); + ThrowHead(self, "models/objects/gibs/gear/tris.md2", 500, GIB_METALLIC); + } + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1); + gi.WritePosition(self->s.origin); + gi.multicast(self->s.origin, MULTICAST_PVS); + + M_Remove(self, false, false); +} + +mframe_t guardian_frames_deathboss[] = +{ + ai_move, 0, guardian_explode, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, guardian_explode, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, guardian_explode, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, guardian_explode, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, guardian_explode, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, guardian_explode +}; +mmove_t guardian_move_deathboss = {FRAME_death1, FRAME_death26, guardian_frames_deathboss, guardian_dead}; + +mframe_t guardian_frames_death[] = +{ + ai_move, 0, guardian_explode, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, guardian_explode, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, guardian_explode +}; +mmove_t guardian_move_death = {FRAME_death1, FRAME_death11, guardian_frames_death, guardian_dead}; + +void guardian_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + (void)inflictor; + (void)attacker; + (void)damage; + (void)point; + + M_Notify(self); + + if (self->deadflag == DEAD_DEAD) + return; + + self->s.sound = 0; + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_NO; + + if (self->mtype == M_GUARDIAN) + self->monsterinfo.currentmove = &guardian_move_deathboss; + else + self->monsterinfo.currentmove = &guardian_move_death; + + DroneList_Remove(self); + + if (self->activator && !self->activator->client) + self->activator->num_monsters_real--; +} + +void init_drone_guardian(edict_t *self) +{ + qboolean MiniGuardian = (self->mtype == M_MINIGUARDIAN); + + sound_step = gi.soundindex("zortemp/step.wav"); + sound_charge = gi.soundindex("weapons/hyprbu1a.wav"); + sound_spin_loop = gi.soundindex("weapons/hyprbl1a.wav"); + sound_laser = gi.soundindex("weapons/laser2.wav"); + sound_pew = gi.soundindex("weapons/rocklf1a.wav"); + + if (!self->mtype) + self->mtype = M_GUARDIAN; + + self->s.modelindex = gi.modelindex("models/monsters/guardian/tris.md2"); + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->monsterinfo.control_cost = MiniGuardian ? M_TANK_CONTROL_COST : M_JORG_CONTROL_COST; + self->monsterinfo.cost = MiniGuardian ? 175 : 500; + + if (MiniGuardian) + { + self->s.skinnum = 2; + self->s.scale = 0.4f; + self->monsterinfo.scale = MODEL_SCALE * 0.4f; + VectorSet(self->mins, -38, -38, -26); + VectorSet(self->maxs, 38, 38, 25); + self->health = M_MINIGUARDIAN_INITIAL_HEALTH + M_MINIGUARDIAN_ADDON_HEALTH * self->monsterinfo.level; + self->monsterinfo.power_armor_power = M_MINIGUARDIAN_INITIAL_ARMOR + M_MINIGUARDIAN_ADDON_ARMOR * self->monsterinfo.level; + self->mass = 340; + } + else + { + self->monsterinfo.scale = MODEL_SCALE; + VectorSet(self->mins, -96, -96, -66); + VectorSet(self->maxs, 96, 96, 62); + self->health = M_GUARDIAN_INITIAL_HEALTH + M_GUARDIAN_ADDON_HEALTH * self->monsterinfo.level; + self->monsterinfo.power_armor_power = M_GUARDIAN_INITIAL_ARMOR + M_GUARDIAN_ADDON_ARMOR * self->monsterinfo.level; + self->mass = 850; + } + + self->max_health = self->health; + self->gib_health = MiniGuardian ? -3 * BASE_GIB_HEALTH : -6 * BASE_GIB_HEALTH; + self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD; + self->monsterinfo.max_armor = self->monsterinfo.power_armor_power; + self->monsterinfo.jumpup = 64; + self->monsterinfo.jumpdn = 512; + self->monsterinfo.aiflags |= AI_NO_CIRCLE_STRAFE; + + self->pain = guardian_pain; + self->die = guardian_die; + self->monsterinfo.stand = guardian_stand; + self->monsterinfo.walk = guardian_walk; + self->monsterinfo.run = guardian_run; + self->monsterinfo.attack = guardian_attack; + self->monsterinfo.currentmove = &guardian_move_stand; + + self->nextthink = level.time + FRAMETIME; + gi.linkentity(self); +} diff --git a/src/entities/drone/drone_hover.c b/src/entities/drone/drone_hover.c index ecd5d39e..9e02099b 100644 --- a/src/entities/drone/drone_hover.c +++ b/src/entities/drone/drone_hover.c @@ -41,6 +41,7 @@ void hover_search (edict_t *self) void hover_run (edict_t *self); void hover_stand (edict_t *self); void hover_dead (edict_t *self); +void hover_deadthink (edict_t *self); void hover_attack (edict_t *self); void hover_reattack (edict_t *self); void hover_fire_blaster (edict_t *self); @@ -333,18 +334,43 @@ mframe_t hover_frames_run [] = }; mmove_t hover_move_run = {FRAME_forwrd01, FRAME_forwrd35, hover_frames_run, NULL}; +void hover_dying (edict_t *self) +{ + if (self->groundentity) + { + hover_deadthink(self); + return; + } + + if (random() < 0.5) + return; + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_PLAIN_EXPLOSION); + gi.WritePosition(self->s.origin); + gi.multicast(self->s.origin, MULTICAST_PHS); + + if (!vrx_spawn_nonessential_ent(self->s.origin)) + return; + + if (random() < 0.5) + ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", 120, GIB_ORGANIC); + else + ThrowGib(self, "models/objects/gibs/sm_metal/tris.md2", 120, GIB_METALLIC); +} + mframe_t hover_frames_death1 [] = { ai_move, 0, NULL, + ai_move, 0, hover_dying, ai_move, 0, NULL, + ai_move, 0, hover_dying, ai_move, 0, NULL, - ai_move, 0, NULL, - ai_move, 0, NULL, - ai_move, 0, NULL, - ai_move, -10,NULL, + ai_move, 0, hover_dying, + ai_move, -10,hover_dying, ai_move, 3, NULL, - ai_move, 5, NULL, - ai_move, 4, NULL, + ai_move, 5, hover_dying, + ai_move, 4, hover_dying, ai_move, 7, NULL }; mmove_t hover_move_death1 = {FRAME_death101, FRAME_death111, hover_frames_death1, hover_dead}; diff --git a/src/entities/drone/drone_medic.c b/src/entities/drone/drone_medic.c index 24123887..c811e04e 100644 --- a/src/entities/drone/drone_medic.c +++ b/src/entities/drone/drone_medic.c @@ -1248,7 +1248,7 @@ static qboolean medic_commander_find_spawn_spot(edict_t *self, vec3_t mins, vec3 return medic_commander_valid_spawn_spot(self, mins, maxs, spot); } -static qboolean medic_commander_spawn_gunner(edict_t *self, float side) +static qboolean medic_commander_spawn_laserguard(edict_t *self, float side) { edict_t *owner = self->activator; edict_t *gunner; @@ -1261,7 +1261,7 @@ static qboolean medic_commander_spawn_gunner(edict_t *self, float side) return false; gunner = G_Spawn(); - gunner->mtype = M_GUNNER; + gunner->mtype = M_SOLDIER_LASER; gunner->activator = owner; gunner->monsterinfo.level = self->monsterinfo.level; @@ -1336,7 +1336,7 @@ static void medic_commander_finish_spawn(edict_t *self) { float side = (i & 1) ? 56 : -56; - if (medic_commander_spawn_gunner(self, side)) + if (medic_commander_spawn_laserguard(self, side)) spawned++; } diff --git a/src/entities/drone/drone_misc.c b/src/entities/drone/drone_misc.c index d33239c1..c7adf8a4 100644 --- a/src/entities/drone/drone_misc.c +++ b/src/entities/drone/drone_misc.c @@ -26,6 +26,7 @@ void init_drone_supertank (edict_t *self); void init_drone_jorg (edict_t *self); void init_drone_makron (edict_t *self); void init_drone_soldier (edict_t *self); +void init_drone_guardian (edict_t *self); void init_drone_gladiator (edict_t *self); void init_drone_berserk (edict_t *self); void init_drone_infantry (edict_t *self); @@ -766,19 +767,38 @@ void vrx_roll_to_make_champion(edict_t *drone, enum dronespawn_t *drone_type) } } +static qboolean vrx_drone_spawn_is_boss(enum dronespawn_t drone_type) +{ + switch (drone_type) + { + case DS_COMMANDER: + case DS_MAKRON: + case DS_BARON_FIRE: + case DS_SUPERTANK: + case DS_JORG: + case DS_CARRIER: + case DS_GUARDIAN: + return true; + default: + return false; + } +} + edict_t *vrx_create_drone_from_ent(edict_t *drone, edict_t *ent, enum dronespawn_t drone_type, qboolean worldspawn, qboolean link_now, int bonus_level) { vec3_t forward, right, start, end, offset; trace_t tr; float mult; int talentLevel; + qboolean is_boss_spawn; drone->classname = "drone"; + is_boss_spawn = vrx_drone_spawn_is_boss(drone_type); // set monster level if (worldspawn) { - if (drone_type >= 30)// tank commander, supertank + if (is_boss_spawn) drone->monsterinfo.level = HighestLevelPlayer(); else if (INVASION_OTHERSPAWNS_REMOVED) { @@ -857,6 +877,9 @@ edict_t *vrx_create_drone_from_ent(edict_t *drone, edict_t *ent, enum dronespawn case DS_GLADIATOR: init_drone_gladiator(drone); break; case DS_BERSERK: init_drone_berserk(drone); break; case DS_SOLDIER: init_drone_soldier(drone); break; + case DS_SOLDIER_RIPPER: drone->mtype = M_SOLDIER_RIPPER; init_drone_soldier(drone); break; + case DS_SOLDIER_BLUEBLASTER: drone->mtype = M_SOLDIER_BLUEBLASTER; init_drone_soldier(drone); break; + case DS_SOLDIER_LASER: drone->mtype = M_SOLDIER_LASER; init_drone_soldier(drone); break; case DS_INFANTRY: init_drone_infantry(drone); break; case DS_FLYER: init_drone_flyer(drone); break; case DS_FLOATER: init_drone_floater(drone); break; @@ -883,6 +906,9 @@ edict_t *vrx_create_drone_from_ent(edict_t *drone, edict_t *ent, enum dronespawn case DS_SUPERTANK: init_drone_supertank(drone); break; case DS_JORG: init_drone_jorg(drone); break; case DS_CARRIER: init_drone_carrier(drone); break; + case DS_GUARDIAN: init_drone_guardian(drone); break; + case DS_JANITOR: drone->mtype = M_JANITOR; init_drone_supertank(drone); break; + case DS_MiniGuardian: drone->mtype = M_MINIGUARDIAN; init_drone_guardian(drone); break; // default default: init_drone_gunner(drone); break; @@ -897,7 +923,12 @@ edict_t *vrx_create_drone_from_ent(edict_t *drone, edict_t *ent, enum dronespawn drone->pain = drone_pain; //4.0 gib health based on monster control cost - if (drone_type < 30) + if (drone_type < 30 || + drone_type == DS_JANITOR || + drone_type == DS_MiniGuardian || + drone_type == DS_SOLDIER_RIPPER || + drone_type == DS_SOLDIER_BLUEBLASTER || + drone_type == DS_SOLDIER_LASER) drone->gib_health = -drone->monsterinfo.control_cost * BASE_GIB_HEALTH * M_CONTROL_COST_SCALE; else drone->gib_health = 0;//gib boss immediately @@ -940,7 +971,7 @@ edict_t *vrx_create_drone_from_ent(edict_t *drone, edict_t *ent, enum dronespawn if (worldspawn) { // non-invasion mode monsters or bosses are spawned randomly throughout the map - if (!INVASION_OTHERSPAWNS_REMOVED || drone_type >= 30) // only use designated spawns in invasion mode + if (!INVASION_OTHERSPAWNS_REMOVED || is_boss_spawn) // only use designated spawns in invasion mode { if (link_now && drone->mtype != M_JORG && !vrx_find_random_spawn_point(drone, false)) { @@ -957,7 +988,7 @@ edict_t *vrx_create_drone_from_ent(edict_t *drone, edict_t *ent, enum dronespawn // trigger spree war if a boss successfully spawns in PvP mode if (deathmatch->value && !domination->value && !ctf->value && !invasion->value && !pvm->value - && !ptr->value && !ffa->value && (drone_type >= 30)) + && !ptr->value && !ffa->value && is_boss_spawn) { SPREE_WAR = true; SPREE_TIME = level.time; @@ -2061,7 +2092,9 @@ qboolean M_Initialize (edict_t *ent, edict_t *monster, float dur_bonus) case M_PARASITE: init_drone_parasite(monster); break; case M_TANK: init_drone_tank(monster); break; case M_BERSERK: init_drone_berserk(monster); break; - case M_SOLDIER: case M_SOLDIERLT: case M_SOLDIERSS: init_drone_soldier(monster); break; + case M_SOLDIER: case M_SOLDIERLT: case M_SOLDIERSS: + case M_SOLDIER_RIPPER: case M_SOLDIER_BLUEBLASTER: case M_SOLDIER_LASER: + init_drone_soldier(monster); break; case M_GLADIATOR: init_drone_gladiator(monster); break; case M_INFANTRY: init_drone_infantry(monster); break; case M_FLYER: init_drone_flyer(monster); break; @@ -2078,6 +2111,8 @@ qboolean M_Initialize (edict_t *ent, edict_t *monster, float dur_bonus) case M_GEKK: init_drone_gekk(monster); break; case M_ARACHNID: init_drone_arachnid(monster); break; case M_CARRIER: init_drone_carrier(monster); break; + case M_GUARDIAN: case M_MINIGUARDIAN: init_drone_guardian(monster); break; + case M_JANITOR: init_drone_supertank(monster); break; case M_SKELETON: init_skeleton(monster); break; case M_GOLEM: init_golem(monster); break; default: return false; @@ -2156,6 +2191,9 @@ qboolean M_SetBoundingBox (int mtype, vec3_t boxmin, vec3_t boxmax) case M_SOLDIER: case M_SOLDIERLT: case M_SOLDIERSS: + case M_SOLDIER_RIPPER: + case M_SOLDIER_BLUEBLASTER: + case M_SOLDIER_LASER: case M_GUNNER: case M_BRAIN: VectorSet (boxmin, -16, -16, -24); @@ -2210,6 +2248,18 @@ qboolean M_SetBoundingBox (int mtype, vec3_t boxmin, vec3_t boxmax) VectorSet(boxmin, -80, -80, -24); VectorSet(boxmax, 80, 80, 104); break; + case M_GUARDIAN: + VectorSet(boxmin, -96, -96, -66); + VectorSet(boxmax, 96, 96, 62); + break; + case M_JANITOR: + VectorSet(boxmin, -38, -38, 0); + VectorSet(boxmax, 38, 38, 67); + break; + case M_MINIGUARDIAN: + VectorSet(boxmin, -38, -38, -26); + VectorSet(boxmax, 38, 38, 25); + break; case M_MEDIC: case M_MEDIC_COMMANDER: case M_MUTANT: @@ -2252,6 +2302,9 @@ char *GetMonsterKindString (int mtype) case M_PARASITE: return "Parasite"; case M_BERSERK: return "Berserker"; case M_SOLDIER: case M_SOLDIERLT: case M_SOLDIERSS: return "Soldier"; + case M_SOLDIER_RIPPER: return "Ripper Guard"; + case M_SOLDIER_BLUEBLASTER: return "Hyper Guard"; + case M_SOLDIER_LASER: return "Laser Guard"; case M_TANK: return "Tank"; case M_GUNNER: return "Gunner"; case M_YANGSPIRIT: return "Yang Spirit"; @@ -2291,6 +2344,9 @@ char *GetMonsterKindString (int mtype) case M_GEKK: return "Gekk"; case M_ARACHNID: return "Arachnid"; case M_CARRIER: return "Carrier"; + case M_GUARDIAN: return "Guardian"; + case M_JANITOR: return "Janitor"; + case M_MINIGUARDIAN: return "Mini Guardian"; case M_SKELETON: return "Skeleton"; case M_GOLEM: return "Golem"; case M_BARON_FIRE: return "Fire Baron"; @@ -2875,7 +2931,7 @@ void Cmd_Drone_f (edict_t *ent) if (!Q_strcasecmp(s, "help")) { safe_cprintf(ent, PRINT_HIGH, "Monster summoning:\n"); - safe_cprintf(ent, PRINT_HIGH, "monster [gunner|parasite|brain|praetor|praetor_heat|chick_heat|medic|tank|mutant|gladiator|gladb|gladc|berserker|soldier|enforcer|flyer|floater|hover|daedalus|stalker|gekk|arachnid|shambler|redmutant|runnertank|guncmdr]\n"); + safe_cprintf(ent, PRINT_HIGH, "monster [gunner|parasite|brain|praetor|praetor_heat|medic|tank|mutant|gladiator|gladb|gladc|berserker|soldier|ripper|hyper|laser|janitor|janitor2|enforcer|flyer|floater|hover|daedalus|stalker|gekk|arachnid|shambler|redmutant|runnertank|guncmdr]\n"); safe_cprintf(ent, PRINT_HIGH, "Monster utility commands:\n"); safe_cprintf(ent, PRINT_HIGH, "monster [remove|command|follow me|count|attack]\n"); return; @@ -2916,6 +2972,19 @@ void Cmd_Drone_f (edict_t *ent) vrx_create_new_drone(ent, DS_BERSERK, false, true, 0); else if (!Q_strcasecmp(s, "soldier")) vrx_create_new_drone(ent, DS_SOLDIER, false, true, 0); + else if (!Q_strcasecmp(s, "soldier_ionripper") || !Q_strcasecmp(s, "soldierripper") || !Q_strcasecmp(s, "rippersoldier") + || !Q_strcasecmp(s, "ripper_guard") || !Q_strcasecmp(s, "ripperguard")) + vrx_create_new_drone(ent, DS_SOLDIER_RIPPER, false, true, 0); + else if (!Q_strcasecmp(s, "soldierhyper") || !Q_strcasecmp(s, "hypersoldier") || !Q_strcasecmp(s, "hyperguard") + || !Q_strcasecmp(s, "hyper") || !Q_strcasecmp(s, "hyper_guard") || !Q_strcasecmp(s, "hyper guard")) + vrx_create_new_drone(ent, DS_SOLDIER_BLUEBLASTER, false, true, 0); + else if (!Q_strcasecmp(s, "soldier_laser") || !Q_strcasecmp(s, "soldierlaser") || !Q_strcasecmp(s, "lasersoldier") + || !Q_strcasecmp(s, "laser") || !Q_strcasecmp(s, "laserguard") || !Q_strcasecmp(s, "laserguard")) + vrx_create_new_drone(ent, DS_SOLDIER_LASER, false, true, 0); + else if (!Q_strcasecmp(s, "janitor")) + vrx_create_new_drone(ent, DS_JANITOR, false, true, 0); + else if (!Q_strcasecmp(s, "miniguardian") || !Q_strcasecmp(s, "mini guardian") || !Q_strcasecmp(s, "mini_guardian")) + vrx_create_new_drone(ent, DS_MiniGuardian, false, true, 0); else if (!Q_strcasecmp(s, "enforcer")) vrx_create_new_drone(ent, DS_INFANTRY, false, true, 0); else if (!Q_strcasecmp(s, "flyer")) @@ -2934,9 +3003,9 @@ void Cmd_Drone_f (edict_t *ent) vrx_create_new_drone(ent, DS_GUNCMDR, false, true, 0); else if (!Q_strcasecmp(s, "daedalus")) vrx_create_new_drone(ent, DS_DAEDALUS, false, true, 0); - else if (!Q_strcasecmp(s, "gladb")) + else if (!Q_strcasecmp(s, "gladb") || !Q_strcasecmp(s, "gladiatordisruptor")) vrx_create_new_drone(ent, DS_GLADB, false, true, 0); - else if (!Q_strcasecmp(s, "gladc")) + else if (!Q_strcasecmp(s, "gladc") || !Q_strcasecmp(s, "gladiatorplasma")) vrx_create_new_drone(ent, DS_GLADC, false, true, 0); else if (!Q_strcasecmp(s, "stalker")) vrx_create_new_drone(ent, DS_STALKER, false, true, 0); diff --git a/src/entities/drone/drone_runnertank.c b/src/entities/drone/drone_runnertank.c index 1b55f94d..f2fb1ded 100644 --- a/src/entities/drone/drone_runnertank.c +++ b/src/entities/drone/drone_runnertank.c @@ -44,6 +44,24 @@ static void runnertank_windup(edict_t *self) gi.sound(self, CHAN_WEAPON, sound_windup, 1, ATTN_NORM, 0); } +static void runnertank_slam_effect(edict_t *self) +{ + vec3_t forward, right, start, offset, up; + trace_t tr; + + AngleVectors(self->s.angles, forward, right, NULL); + VectorSet(offset, 20, -14.3f, -21); + G_ProjectSource(self->s.origin, offset, forward, right, start); + tr = gi.trace(self->s.origin, NULL, NULL, start, self, MASK_SOLID); + VectorSet(up, 0, 0, 1); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_BERSERK_SLAM); + gi.WritePosition(tr.endpos); + gi.WriteDir(up); + gi.multicast(tr.endpos, MULTICAST_PHS); +} + static void runnertank_idle(edict_t *self) { gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); @@ -278,6 +296,7 @@ static void runnertank_meleeattack(edict_t *self) damage = M_MELEE_DMG_MAX; gi.sound(self, CHAN_AUTO, sound_strike, 1, ATTN_NORM, 0); + runnertank_slam_effect(self); while ((other = findradius(other, self->s.origin, 128)) != NULL) { diff --git a/src/entities/drone/drone_soldier.c b/src/entities/drone/drone_soldier.c index 8bb9f1ac..683299a0 100644 --- a/src/entities/drone/drone_soldier.c +++ b/src/entities/drone/drone_soldier.c @@ -20,6 +20,32 @@ static int sound_death; static int sound_death_ss; static int sound_cock; +static const int soldier_ripper_flash[] = +{ + MZ2_SOLDIER_RIPPER_1, + MZ2_SOLDIER_RIPPER_2, + MZ2_SOLDIER_RIPPER_3, + MZ2_SOLDIER_RIPPER_4, + MZ2_SOLDIER_RIPPER_5, + MZ2_SOLDIER_RIPPER_6, + MZ2_SOLDIER_RIPPER_7, + MZ2_SOLDIER_RIPPER_8, + MZ2_SOLDIER_RIPPER_9 +}; + +static const int soldier_hyper_flash[] = +{ + MZ2_SOLDIER_HYPERGUN_1, + MZ2_SOLDIER_HYPERGUN_2, + MZ2_SOLDIER_HYPERGUN_3, + MZ2_SOLDIER_HYPERGUN_4, + MZ2_SOLDIER_HYPERGUN_5, + MZ2_SOLDIER_HYPERGUN_6, + MZ2_SOLDIER_HYPERGUN_7, + MZ2_SOLDIER_HYPERGUN_8, + MZ2_SOLDIER_HYPERGUN_9 +}; + void m_soldier_stand (edict_t *self); void m_soldier_runandshoot_continue (edict_t *self); @@ -202,6 +228,96 @@ void soldier_fireshotgun(edict_t* self) monster_fire_shotgun(self, start, forward, damage, 15, 375, 375, 10, MZ2_SOLDIER_SHOTGUN_8); } +void soldier_fireionripper(edict_t* self, int flash_number) +{ + int damage, speed; + int flash; + vec3_t forward, start; + + if (!G_EntExists(self->enemy)) + return; + + if (flash_number < 0 || flash_number >= (int)(sizeof(soldier_ripper_flash) / sizeof(soldier_ripper_flash[0]))) + flash_number = 7; + flash = soldier_ripper_flash[flash_number]; + damage = IONRIPPER_INITIAL_DAMAGE + IONRIPPER_ADDON_DAMAGE * drone_damagelevel(self); + speed = IONRIPPER_INITIAL_SPEED + IONRIPPER_ADDON_SPEED * drone_damagelevel(self); + + MonsterAim(self, M_PROJECTILE_ACC, speed, false, flash, forward, start); + monster_fire_ionripper(self, start, forward, damage, speed, EF_IONRIPPER, flash); +} + +void soldier_fireblueblaster(edict_t* self, int flash_number) +{ + int damage, speed; + int flash; + vec3_t forward, start; + + if (!G_EntExists(self->enemy)) + return; + + if (flash_number < 0 || flash_number >= (int)(sizeof(soldier_hyper_flash) / sizeof(soldier_hyper_flash[0]))) + flash_number = 7; + flash = soldier_hyper_flash[flash_number]; + damage = M_HYPERBLASTER_DMG_BASE + M_HYPERBLASTER_DMG_ADDON * drone_damagelevel(self); + if (M_HYPERBLASTER_DMG_MAX && damage > M_HYPERBLASTER_DMG_MAX) + damage = M_HYPERBLASTER_DMG_MAX; + + speed = HYPERBLASTER_INITIAL_SPEED + HYPERBLASTER_ADDON_SPEED * drone_damagelevel(self); + if (speed < 400) + speed = 400; + + MonsterAim(self, M_PROJECTILE_ACC, speed, false, flash, forward, start); + monster_fire_blueblaster(self, start, forward, damage, speed, EF_BLUEHYPERBLASTER, flash); +} + +static void soldier_laser_update(edict_t *laser) +{ + edict_t *self; + vec3_t forward, right, up, start, offset, target; + qboolean do_damage; + + self = laser->owner; + if (!self || !self->inuse || !G_EntExists(self->enemy)) + { + laser->spawnflags |= DABEAM_SPAWNED; + return; + } + + if (self->radius_dmg <= 0 || self->radius_dmg > MZ2_LAST) + self->radius_dmg = MZ2_SOLDIER_MACHINEGUN_4; + + AngleVectors(self->s.angles, forward, right, up); + VectorCopy(self->s.origin, start); + VectorCopy(monster_flash_offset[self->radius_dmg], offset); + VectorMA(start, offset[0], forward, start); + VectorMA(start, offset[1], right, start); + VectorMA(start, offset[2] + 6, up, start); + + G_EntMidPoint(self->enemy, target); + VectorSubtract(target, start, forward); + VectorNormalize(forward); + + VectorCopy(start, laser->s.origin); + VectorCopy(forward, laser->movedir); + + do_damage = !(laser->spawnflags & DABEAM_SPAWNED); + dabeam_update(laser, do_damage); + laser->spawnflags |= DABEAM_SPAWNED; +} + +void soldier_firelaser(edict_t* self, int flash_number) +{ + int damage; + + if (!G_EntExists(self->enemy)) + return; + + self->radius_dmg = flash_number; + damage = M_DABEAM_DMG_BASE + M_DABEAM_DMG_ADDON * drone_damagelevel(self); + monster_fire_dabeam(self, damage, false, soldier_laser_update); +} + void m_soldier_fire (edict_t *self) { if (!G_EntExists(self->enemy)) @@ -213,6 +329,20 @@ void m_soldier_fire (edict_t *self) soldier_firerocket(self); else if (self->mtype == M_SOLDIERSS) soldier_fireshotgun(self); + else if (self->mtype == M_SOLDIER_RIPPER) + soldier_fireionripper(self, 7); + else if (self->mtype == M_SOLDIER_BLUEBLASTER) + soldier_fireblueblaster(self, 7); + else if (self->mtype == M_SOLDIER_LASER) + soldier_firelaser(self, MZ2_SOLDIER_MACHINEGUN_4); +} + +void m_soldier_hyperripper_run_fire(edict_t *self) +{ + if (self->mtype == M_SOLDIER_RIPPER) + soldier_fireionripper(self, 7); + else if (self->mtype == M_SOLDIER_BLUEBLASTER) + soldier_fireblueblaster(self, 7); } mframe_t m_soldier_frames_runandshoot [] = @@ -221,8 +351,8 @@ mframe_t m_soldier_frames_runandshoot [] = drone_ai_run, 25, NULL, drone_ai_run, 25, NULL, drone_ai_run, 25, m_soldier_fire, //112 - drone_ai_run, 25, NULL, - drone_ai_run, 25, NULL, + drone_ai_run, 25, m_soldier_hyperripper_run_fire, + drone_ai_run, 25, m_soldier_hyperripper_run_fire, drone_ai_run, 25, NULL, drone_ai_run, 25, NULL, drone_ai_run, 25, m_soldier_fire, //117 @@ -282,8 +412,73 @@ mframe_t m_soldier_frames_attack1 [] = }; mmove_t m_soldier_move_attack1 = {FRAME_attak101, FRAME_attak112, m_soldier_frames_attack1, m_soldier_endattack1}; +void m_soldier_endattack_laser(edict_t* self) +{ + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + self->monsterinfo.pausetime = 0; + if (self->beam && self->beam->inuse) + { + self->beam->prethink = NULL; + self->beam->nextthink = level.time + FRAMETIME; + } + m_soldier_run(self); +} + +void m_soldier_firelaser_frame(edict_t* self) +{ + if (!G_EntExists(self->enemy) || !visible(self, self->enemy)) + { + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + return; + } + + if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME)) + self->monsterinfo.pausetime = level.time + 0.3 + random() * 0.8; + + soldier_firelaser(self, MZ2_SOLDIER_MACHINEGUN_4); + + if (level.time < self->monsterinfo.pausetime) + self->monsterinfo.aiflags |= AI_HOLD_FRAME; + else + { + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + self->monsterinfo.pausetime = 0; + if (self->beam && self->beam->inuse) + { + self->beam->prethink = NULL; + self->beam->nextthink = level.time + FRAMETIME; + } + } +} + +mframe_t m_soldier_frames_attack_laser[] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, m_soldier_firelaser_frame, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t m_soldier_move_attack_laser = {FRAME_attak401, FRAME_attak406, m_soldier_frames_attack_laser, m_soldier_endattack_laser}; + void m_soldier_attack(edict_t* self) { + if (self->mtype == M_SOLDIER_LASER) + { + self->monsterinfo.currentmove = &m_soldier_move_attack_laser; + M_DelayNextAttack(self, 0, true); + return; + } + + if ((self->mtype == M_SOLDIER_RIPPER || self->mtype == M_SOLDIER_BLUEBLASTER) + && !(self->monsterinfo.aiflags & AI_STAND_GROUND)) + { + self->monsterinfo.currentmove = &m_soldier_move_runandshoot; + M_DelayNextAttack(self, 0, true); + return; + } + if (self->monsterinfo.aiflags & AI_STAND_GROUND) { self->monsterinfo.currentmove = &m_soldier_move_attack1; @@ -342,6 +537,28 @@ void m_soldier_jump_takeoff (edict_t *self) self->monsterinfo.pausetime = level.time + 2.0; // maximum duration of jump } +void m_soldier_jump_forward_takeoff(edict_t *self, float forward_velocity, float up_velocity) +{ + vec3_t forward; + + gi.sound(self, CHAN_VOICE, sound_sight1, 1, ATTN_NORM, 0); + AngleVectors(self->s.angles, forward, NULL, NULL); + VectorScale(forward, forward_velocity, self->velocity); + self->velocity[2] = up_velocity; + self->groundentity = NULL; + self->monsterinfo.pausetime = level.time + 2.0; +} + +void m_soldier_jump_attack_takeoff(edict_t *self) +{ + m_soldier_jump_forward_takeoff(self, 250, 250); +} + +void m_soldier_jump2_takeoff(edict_t *self) +{ + m_soldier_jump_forward_takeoff(self, 200, 350); +} + void m_soldier_jump_hold (edict_t *self) { vec3_t v; @@ -368,7 +585,7 @@ void m_soldier_jump_hold (edict_t *self) mframe_t m_soldier_frames_jump [] = { - ai_move, 0, m_soldier_jump_takeoff, + ai_move, 0, m_soldier_jump2_takeoff, ai_move, 0, m_soldier_jump_hold, ai_move, 0, NULL, ai_move, 0, NULL, @@ -376,10 +593,28 @@ mframe_t m_soldier_frames_jump [] = }; mmove_t m_soldier_move_jump = {FRAME_duck01, FRAME_duck05, m_soldier_frames_jump, m_soldier_run}; +mframe_t m_soldier_frames_jump_attack [] = +{ + ai_move, 0, m_soldier_jump_attack_takeoff, + ai_move, 0, m_soldier_jump_hold, + ai_move, 0, m_soldier_jump_hold, + ai_move, 0, m_soldier_jump_hold, + ai_move, 0, m_soldier_jump_hold, + ai_move, 0, m_soldier_jump_hold, + ai_move, 0, m_soldier_jump_hold, + ai_move, 0, m_soldier_jump_hold +}; +mmove_t m_soldier_move_jump_attack = {FRAME_attak501, FRAME_attak508, m_soldier_frames_jump_attack, m_soldier_run}; + void m_soldier_jump (edict_t *self) { if (self->groundentity) - self->monsterinfo.currentmove = &m_soldier_move_jump; + { + if (random() < 0.5) + self->monsterinfo.currentmove = &m_soldier_move_jump_attack; + else + self->monsterinfo.currentmove = &m_soldier_move_jump; + } } void m_soldier_dodge (edict_t *self, edict_t *attacker, vec3_t dir, int radius) @@ -495,13 +730,17 @@ mmove_t soldier_move_pain_long2 = { FRAME_pain401, FRAME_pain417, soldier_frames void soldier_pain(edict_t* self, edict_t* other, float kick, int damage) { if (self->health < (self->max_health / 2)) - self->s.skinnum = 1; + self->s.skinnum |= 1; + else + self->s.skinnum &= ~1; // we're already in a pain state if (self->monsterinfo.currentmove == &soldier_move_pain_long1 || self->monsterinfo.currentmove == &soldier_move_pain_long2 || self->monsterinfo.currentmove == &soldier_move_pain_short1 || - self->monsterinfo.currentmove == &soldier_move_pain_short2) + self->monsterinfo.currentmove == &soldier_move_pain_short2 || + self->monsterinfo.currentmove == &m_soldier_move_jump || + self->monsterinfo.currentmove == &m_soldier_move_jump_attack) return; // monster players don't get pain state induced @@ -879,15 +1118,45 @@ void init_drone_soldier (edict_t *self) self->monsterinfo.cost = 50; // set health - self->health = M_SOLDIER_INITIAL_HEALTH+M_SOLDIER_ADDON_HEALTH*self->monsterinfo.level; // hlt: soldier + if (self->mtype == M_SOLDIER_RIPPER) + self->health = M_SOLDIER_RIPPER_INITIAL_HEALTH + M_SOLDIER_RIPPER_ADDON_HEALTH * self->monsterinfo.level; + else if (self->mtype == M_SOLDIER_BLUEBLASTER) + self->health = M_SOLDIER_BLUEBLASTER_INITIAL_HEALTH + M_SOLDIER_BLUEBLASTER_ADDON_HEALTH * self->monsterinfo.level; + else if (self->mtype == M_SOLDIER_LASER) + self->health = M_SOLDIER_LASER_INITIAL_HEALTH + M_SOLDIER_LASER_ADDON_HEALTH * self->monsterinfo.level; + else + self->health = M_SOLDIER_INITIAL_HEALTH+M_SOLDIER_ADDON_HEALTH*self->monsterinfo.level; // hlt: soldier self->max_health = self->health; self->gib_health = -1.5 * BASE_GIB_HEALTH; // set armor self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD; - self->monsterinfo.power_armor_power = M_SOLDIER_INITIAL_ARMOR+M_SOLDIER_ADDON_ARMOR*self->monsterinfo.level; // pow: soldier + if (self->mtype == M_SOLDIER_RIPPER) + self->monsterinfo.power_armor_power = M_SOLDIER_RIPPER_INITIAL_ARMOR + M_SOLDIER_RIPPER_ADDON_ARMOR * self->monsterinfo.level; + else if (self->mtype == M_SOLDIER_BLUEBLASTER) + self->monsterinfo.power_armor_power = M_SOLDIER_BLUEBLASTER_INITIAL_ARMOR + M_SOLDIER_BLUEBLASTER_ADDON_ARMOR * self->monsterinfo.level; + else if (self->mtype == M_SOLDIER_LASER) + self->monsterinfo.power_armor_power = M_SOLDIER_LASER_INITIAL_ARMOR + M_SOLDIER_LASER_ADDON_ARMOR * self->monsterinfo.level; + else + self->monsterinfo.power_armor_power = M_SOLDIER_INITIAL_ARMOR+M_SOLDIER_ADDON_ARMOR*self->monsterinfo.level; // pow: soldier self->monsterinfo.max_armor = self->monsterinfo.power_armor_power; + if (self->mtype == M_SOLDIER_RIPPER) + { + self->s.skinnum = 6; + self->count = self->s.skinnum - 6; + } + else if (self->mtype == M_SOLDIER_BLUEBLASTER) + { + self->s.skinnum = 8; + self->count = self->s.skinnum - 6; + } + else if (self->mtype == M_SOLDIER_LASER) + { + self->s.skinnum = 10; + self->count = self->s.skinnum - 6; + } + // set AI jump parameters self->monsterinfo.jumpdn = 512; self->monsterinfo.jumpup = 64; @@ -905,4 +1174,4 @@ void init_drone_soldier (edict_t *self) gi.linkentity (self); self->nextthink = level.time + FRAMETIME; self->monsterinfo.currentmove = &m_soldier_move_stand1; -} \ No newline at end of file +} diff --git a/src/entities/drone/drone_supertank.c b/src/entities/drone/drone_supertank.c index 9d990e38..57b53e98 100644 --- a/src/entities/drone/drone_supertank.c +++ b/src/entities/drone/drone_supertank.c @@ -580,6 +580,8 @@ void supertank_sight (edict_t *self, edict_t *other) void init_drone_supertank (edict_t *self) { + qboolean janitor = (self->mtype == M_JANITOR); + sound_death = gi.soundindex ("bosstank/btkdeth1.wav"); sound_search1 = gi.soundindex ("bosstank/btkunqv1.wav"); sound_search2 = gi.soundindex ("bosstank/btkunqv2.wav"); @@ -587,16 +589,36 @@ void init_drone_supertank (edict_t *self) self->movetype = MOVETYPE_STEP; self->solid = SOLID_BBOX; - self->mtype = M_SUPERTANK; - self->monsterinfo.control_cost = M_SUPERTANK_CONTROL_COST; - self->monsterinfo.cost = 300; + if (!janitor) + self->mtype = M_SUPERTANK; + self->monsterinfo.control_cost = janitor ? M_TANK_CONTROL_COST : M_SUPERTANK_CONTROL_COST; + self->monsterinfo.cost = janitor ? 150 : 300; self->s.modelindex = gi.modelindex ("models/monsters/boss1/tris.md2"); - VectorSet (self->mins, -64, -64, 0); - VectorSet (self->maxs, 64, 64, 112); - - self->health = self->max_health = 20000*self->monsterinfo.level; + if (janitor) + { + self->s.skinnum = 2; + self->s.scale = 0.6f; + self->monsterinfo.scale = MODEL_SCALE * 0.6f; + VectorSet (self->mins, -38, -38, 0); + VectorSet (self->maxs, 38, 38, 67); + self->health = self->max_health = M_JANITOR_INITIAL_HEALTH + M_JANITOR_ADDON_HEALTH * self->monsterinfo.level; + } + else + { + VectorSet (self->mins, -64, -64, 0); + VectorSet (self->maxs, 64, 64, 112); + self->health = self->max_health = 20000*self->monsterinfo.level; + self->monsterinfo.scale = MODEL_SCALE; + } self->gib_health = -5 * BASE_GIB_HEALTH; - self->mass = 800; + self->mass = janitor ? 480 : 800; + + self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD; + if (janitor) + self->monsterinfo.power_armor_power = M_JANITOR_INITIAL_ARMOR + M_JANITOR_ADDON_ARMOR * self->monsterinfo.level; + else + self->monsterinfo.power_armor_power = 0; + self->monsterinfo.max_armor = self->monsterinfo.power_armor_power; self->die = supertank_die; self->monsterinfo.stand = supertank_stand; @@ -607,11 +629,11 @@ void init_drone_supertank (edict_t *self) self->monsterinfo.jumpdn = 512; self->monsterinfo.aiflags |= AI_NO_CIRCLE_STRAFE; self->monsterinfo.currentmove = &supertank_move_stand; - self->monsterinfo.scale = MODEL_SCALE; self->monsterinfo.sight = supertank_sight; self->nextthink = level.time + FRAMETIME; gi.linkentity (self); - G_PrintGreenText(va("A level %d super tank has spawned!", self->monsterinfo.level)); + if (!janitor && (!self->activator || !self->activator->client)) + G_PrintGreenText(va("A level %d super tank has spawned!", self->monsterinfo.level)); } diff --git a/src/entities/drone/drone_tank.c b/src/entities/drone/drone_tank.c index a01c9018..24f907fc 100644 --- a/src/entities/drone/drone_tank.c +++ b/src/entities/drone/drone_tank.c @@ -45,6 +45,24 @@ void mytank_windup (edict_t *self) gi.sound (self, CHAN_WEAPON, sound_windup, 1, ATTN_NORM, 0); } +static void mytank_slam_effect(edict_t *self) +{ + vec3_t forward, right, start, offset, up; + trace_t tr; + + AngleVectors(self->s.angles, forward, right, NULL); + VectorSet(offset, 20, -14.3f, -21); + G_ProjectSource(self->s.origin, offset, forward, right, start); + tr = gi.trace(self->s.origin, NULL, NULL, start, self, MASK_SOLID); + VectorSet(up, 0, 0, 1); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_BERSERK_SLAM); + gi.WritePosition(tr.endpos); + gi.WriteDir(up); + gi.multicast(tr.endpos, MULTICAST_PHS); +} + void mytank_idle (edict_t *self) { int range; @@ -654,6 +672,7 @@ void mytank_meleeattack (edict_t *self) damage = M_MELEE_DMG_MAX; gi.sound (self, CHAN_AUTO, gi.soundindex ("tank/tnkatck5.wav"), 1, ATTN_NORM, 0); + mytank_slam_effect(self); while ((other = findradius(other, self->s.origin, 128)) != NULL) { diff --git a/src/entities/drone/g_monster.c b/src/entities/drone/g_monster.c index f1d20ca6..489e7acb 100644 --- a/src/entities/drone/g_monster.c +++ b/src/entities/drone/g_monster.c @@ -45,11 +45,14 @@ void monster_fire_bullet (edict_t *self, vec3_t start, vec3_t dir, int damage, i damage = vrx_increase_monster_damage_by_talent(self->activator, damage); fire_bullet (self, start, dir, damage, kick, hspread, vspread, MOD_UNKNOWN); - gi.WriteByte (svc_muzzleflash2); - gi.WriteShort (self - g_edicts); - gi.WriteByte (flashtype); - gi.multicast (start, MULTICAST_PVS); -} + if (flashtype >= 0) + { + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); + } +} static void debris_die(edict_t* self, edict_t* inflictor, edict_t* attacker, int damage, vec3_t point) { @@ -146,11 +149,14 @@ void monster_fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, float dam damage = vrx_increase_monster_damage_by_talent(self->activator, damage); fire_shotgun (self, start, aimdir, damage, kick, hspread, vspread, count, MOD_UNKNOWN); - gi.WriteByte (svc_muzzleflash2); - gi.WriteShort (self - g_edicts); - gi.WriteByte (flashtype); - gi.multicast (start, MULTICAST_PVS); -} + if (flashtype >= 0) + { + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); + } +} void monster_fire_blaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect, int proj_type, float duration, qboolean bounce, int flashtype) { @@ -181,10 +187,13 @@ void monster_fire_blaster (edict_t *self, vec3_t start, vec3_t dir, int damage, damage = vrx_increase_monster_damage_by_talent(self->activator, damage); fire_blaster(self, start, dir, damage, speed, effect, proj_type, mod, duration, bounce); - gi.WriteByte (svc_muzzleflash2); - gi.WriteShort (self - g_edicts); - gi.WriteByte (flashtype); - gi.multicast (start, MULTICAST_PVS); + if (flashtype >= 0) + { + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); + } } void monster_fire_blaster2(edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect, int flashtype) @@ -233,6 +242,198 @@ void monster_fire_blueblaster(edict_t *self, vec3_t start, vec3_t dir, int damag gi.multicast(start, MULTICAST_PVS); } +void monster_fire_ionripper(edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect, int flashtype) +{ + float chance; + + if (que_typeexists(self->curses, AURA_HOLYFREEZE) && random() <= 0.5) + return; + + if (self->chill_time > level.time) + { + chance = 1 / (1 + CHILL_DEFAULT_BASE + CHILL_DEFAULT_ADDON * self->chill_level); + if (random() > chance) + return; + } + + damage = vrx_increase_monster_damage_by_talent(self->activator, damage); + fire_ionripper(self, start, dir, damage, speed, effect); + + gi.WriteByte(svc_muzzleflash2); + gi.WriteShort(self - g_edicts); + gi.WriteByte(flashtype); + gi.multicast(start, MULTICAST_PVS); +} + +static void dabeam_think(edict_t *self) +{ + if (self->owner && self->owner->inuse) + { + if ((self->spawnflags & DABEAM_SECONDARY) && self->owner->beam2 == self) + self->owner->beam2 = NULL; + else if (!(self->spawnflags & DABEAM_SECONDARY) && self->owner->beam == self) + self->owner->beam = NULL; + } + + G_FreeEdict(self); +} + +void dabeam_update(edict_t *self, qboolean damage) +{ + vec3_t start, end; + trace_t tr; + + if (!self->owner || !self->owner->inuse || !G_EntExists(self->owner->enemy)) + return; + + VectorCopy(self->s.origin, start); + VectorMA(start, 8192, self->movedir, end); + tr = gi.trace(start, NULL, NULL, end, self->owner, MASK_SHOT); + + if (damage && tr.ent && tr.ent->takedamage && !OnSameTeam(self->owner, tr.ent)) + T_Damage(tr.ent, self->owner, self->owner, self->movedir, tr.endpos, tr.plane.normal, self->dmg, self->dmg, + DAMAGE_ENERGY, MOD_BFG_LASER); + + VectorCopy(tr.endpos, self->s.old_origin); + VectorCopy(start, self->pos1); + VectorCopy(tr.endpos, self->pos2); + gi.linkentity(self); +} + +void monster_fire_dabeam(edict_t *self, int damage, qboolean secondary, void (*update_func)(edict_t *self)) +{ + vec3_t forward, right, start, end, offset; + trace_t tr; + edict_t **beam_slot; + edict_t *beam; + int flashtype; + float chance; + + if (!G_EntExists(self->enemy)) + return; + + if (que_typeexists(self->curses, AURA_HOLYFREEZE) && random() <= 0.5) + return; + + if (self->chill_time > level.time) + { + chance = 1 / (1 + CHILL_DEFAULT_BASE + CHILL_DEFAULT_ADDON * self->chill_level); + if (random() > chance) + return; + } + + if (self->mtype == M_GUARDIAN) + flashtype = -1; + else if (self->mtype == M_MINIGUARDIAN) + flashtype = MZ2_SOLDIER_RIPPER_8; + else + flashtype = MZ2_SOLDIER_HYPERGUN_8; + + damage = vrx_increase_monster_damage_by_talent(self->activator, damage); + + if (update_func) + { + beam_slot = secondary ? &self->beam2 : &self->beam; + beam = *beam_slot; + if (!beam || !beam->inuse) + { + beam = G_Spawn(); + if (!beam) + return; + *beam_slot = beam; + beam->movetype = MOVETYPE_NONE; + beam->solid = SOLID_NOT; + beam->s.renderfx = RF_BEAM | RF_TRANSLUCENT; + beam->s.modelindex = 1; + beam->s.frame = 2; + beam->s.skinnum = 0xf2f2f0f0; // red + beam->owner = self; + beam->classname = "monster_dabeam"; + beam->think = dabeam_think; + beam->s.sound = gi.soundindex("misc/lasfly.wav"); + beam->spawnflags = secondary ? DABEAM_SECONDARY : 0; + } + + beam->dmg = damage; + beam->prethink = update_func; + beam->nextthink = level.time + 0.2; + beam->spawnflags &= ~DABEAM_SPAWNED; + update_func(beam); + + beam = *beam_slot; + if (!beam || !beam->inuse) + return; + + if (!(beam->spawnflags & DABEAM_SPAWNED)) + { + dabeam_update(beam, true); + beam->spawnflags |= DABEAM_SPAWNED; + } + + return; + } + + if (self->mtype == M_GUARDIAN) + { + AngleVectors(self->s.angles, forward, right, NULL); + if (secondary) + VectorSet(offset, 112, -62, 60); + else + VectorSet(offset, 125, -70, 60); + G_ProjectSource(self->s.origin, offset, forward, right, start); + MonsterAim(self, M_HITSCAN_CONT_ACC, 0, false, -1, forward, start); + } + else + { + MonsterAim(self, M_HITSCAN_CONT_ACC, 0, false, flashtype, forward, start); + } + + VectorMA(start, 8192, forward, end); + tr = gi.trace(start, NULL, NULL, end, self, MASK_SHOT); + + if (tr.ent && tr.ent->takedamage && !OnSameTeam(self, tr.ent)) + T_Damage(tr.ent, self, self, forward, tr.endpos, tr.plane.normal, damage, damage, DAMAGE_ENERGY, MOD_BFG_LASER); + + beam_slot = secondary ? &self->beam2 : &self->beam; + beam = *beam_slot; + if (!beam || !beam->inuse) + { + beam = G_Spawn(); + if (!beam) + return; + *beam_slot = beam; + beam->movetype = MOVETYPE_NONE; + beam->solid = SOLID_NOT; + beam->s.renderfx = RF_BEAM | RF_TRANSLUCENT; + beam->s.modelindex = 1; + beam->s.frame = 2; + beam->s.skinnum = 0xf2f2f0f0; // red + beam->owner = self; + beam->classname = "monster_dabeam"; + beam->think = dabeam_think; + beam->prethink = NULL; + beam->s.sound = gi.soundindex("misc/lasfly.wav"); + beam->spawnflags = secondary ? DABEAM_SECONDARY : 0; + } + + beam->dmg = damage; + beam->prethink = NULL; + beam->nextthink = level.time + 0.2; + VectorCopy(start, beam->s.origin); + VectorCopy(tr.endpos, beam->s.old_origin); + VectorCopy(start, beam->pos1); + VectorCopy(tr.endpos, beam->pos2); + gi.linkentity(beam); + + if (flashtype >= 0) + { + gi.WriteByte(svc_muzzleflash2); + gi.WriteShort(self - g_edicts); + gi.WriteByte(flashtype); + gi.multicast(start, MULTICAST_PVS); + } +} + void rocket_touch(edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf); static qboolean heat_valid_target(edict_t *self, edict_t *target) @@ -252,7 +453,7 @@ static void heat_think(edict_t *self) { edict_t *target = NULL; edict_t *acquire = NULL; - float best_dot = 1.0f; + float best_dot = -1.0f; float best_dist = 0.0f; float turn_fraction; float dot, dist; @@ -280,13 +481,13 @@ static void heat_think(edict_t *self) if (!heat_valid_target(self, target)) continue; - VectorSubtract(self->s.origin, target->s.origin, dir); + VectorSubtract(target->s.origin, self->s.origin, dir); dist = VectorNormalize(dir); dot = DotProduct(dir, forward); - if (dot >= best_dot) + if (dot <= best_dot) continue; - if (!acquire || dot < best_dot || dist < best_dist) + if (!acquire || dot > best_dot || dist < best_dist) { acquire = target; best_dot = dot; @@ -310,10 +511,6 @@ static void heat_think(edict_t *self) else if (turn_fraction > 1) turn_fraction = 1; - dot = DotProduct(self->movedir, dir); - if (dot < 0.45f && dot > -0.45f) - VectorNegate(dir, dir); - VectorScale(self->movedir, 1.0f - turn_fraction, self->movedir); VectorMA(self->movedir, turn_fraction, dir, self->movedir); VectorNormalize(self->movedir); diff --git a/src/entities/g_spawn.c b/src/entities/g_spawn.c index 94b9b9f2..71b674a7 100644 --- a/src/entities/g_spawn.c +++ b/src/entities/g_spawn.c @@ -104,6 +104,8 @@ void SP_monster_infantry (edict_t *self); void SP_monster_soldier_light (edict_t *self); void SP_monster_soldier (edict_t *self); void SP_monster_soldier_ss (edict_t *self); +void SP_monster_soldier_blueblaster (edict_t *self); +void SP_monster_soldier_laser (edict_t *self); void SP_monster_tank (edict_t *self); void SP_monster_medic (edict_t *self); void SP_monster_flipper (edict_t *self); @@ -131,6 +133,9 @@ void SP_turret_driver (edict_t *self); void SP_monster_soldier_hypergun (edict_t *self); void SP_monster_soldier_lasergun (edict_t *self); void SP_monster_soldier_ripper (edict_t *self); +void SP_monster_guardian (edict_t *self); +void SP_monster_janitor (edict_t *self); +void SP_monster_MiniGuardian (edict_t *self); void SP_monster_fixbot (edict_t *self); void SP_monster_gekk (edict_t *self); void SP_monster_chick_heat (edict_t *self); @@ -272,6 +277,13 @@ spawn_t spawns[] = { {"monster_gladiator", SP_monster_gladiator}, {"monster_gunner", SP_monster_gunner}, {"monster_soldier", SP_monster_soldier}, + {"monster_soldier_ripper", SP_monster_soldier_ripper}, + {"monster_soldier_blueblaster", SP_monster_soldier_blueblaster}, + {"monster_soldier_hypergun", SP_monster_soldier_blueblaster}, + {"monster_soldier_laser", SP_monster_soldier_laser}, + {"monster_soldier_lasergun", SP_monster_soldier_laser}, + {"monster_janitor", SP_monster_janitor}, + {"monster_MiniGuardian", SP_monster_MiniGuardian}, {"monster_tank", SP_monster_tank}, {"monster_tank_commander", SP_monster_tank_commander}, {"monster_medic", SP_monster_medic}, @@ -1077,7 +1089,52 @@ void SP_monster_gunner(edict_t *ent) void SP_monster_soldier(edict_t *ent) { if (coop->value) - vrx_create_drone_from_ent(ent, g_edicts, 10, true, true, 0); + vrx_create_drone_from_ent(ent, g_edicts, DS_SOLDIER, true, true, 0); +} + +void SP_monster_soldier_ripper(edict_t *ent) +{ + if (coop->value) + vrx_create_drone_from_ent(ent, g_edicts, DS_SOLDIER_RIPPER, true, true, 0); +} + +void SP_monster_soldier_blueblaster(edict_t *ent) +{ + if (coop->value) + vrx_create_drone_from_ent(ent, g_edicts, DS_SOLDIER_BLUEBLASTER, true, true, 0); +} + +void SP_monster_soldier_hypergun(edict_t *ent) +{ + SP_monster_soldier_blueblaster(ent); +} + +void SP_monster_soldier_laser(edict_t *ent) +{ + if (coop->value) + vrx_create_drone_from_ent(ent, g_edicts, DS_SOLDIER_LASER, true, true, 0); +} + +void SP_monster_soldier_lasergun(edict_t *ent) +{ + SP_monster_soldier_laser(ent); +} + +void SP_monster_guardian(edict_t *ent) +{ + G_FreeEdict(ent); +} + +void SP_monster_janitor(edict_t *ent) +{ + if (coop->value) + vrx_create_drone_from_ent(ent, g_edicts, DS_JANITOR, true, true, 0); +} + +void SP_monster_MiniGuardian(edict_t *ent) +{ + if (coop->value) + vrx_create_drone_from_ent(ent, g_edicts, DS_MiniGuardian, true, true, 0); } void SP_monster_tank(edict_t *ent) diff --git a/src/g_local.h b/src/g_local.h index f22645c8..e26fc7d5 100644 --- a/src/g_local.h +++ b/src/g_local.h @@ -1422,6 +1422,12 @@ void monster_fire_blaster2(edict_t *self, vec3_t start, vec3_t dir, int damage, void monster_fire_blueblaster(edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect, int flashtype); +void monster_fire_ionripper(edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect, int flashtype); +#define DABEAM_SECONDARY 1 +#define DABEAM_SPAWNED 2 +void monster_fire_dabeam(edict_t *self, int damage, qboolean secondary, void (*update_func)(edict_t *self)); +void dabeam_update(edict_t *self, qboolean damage); + qboolean monster_fire_heat(edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype, float turn_fraction); void monster_fire_grenade(edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int flashtype); @@ -1518,6 +1524,12 @@ enum mtype_t { M_ARACHNID = 37, M_MEDIC_COMMANDER = 38, M_CARRIER = 39, + M_GUARDIAN = 40, + M_JANITOR = 41, + M_MINIGUARDIAN = 42, + M_SOLDIER_RIPPER = 43, + M_SOLDIER_BLUEBLASTER = 44, + M_SOLDIER_LASER = 45, M_MINISENTRY = 100, M_SENTRY = 101, M_BFG_SENTRY = 102, @@ -1627,12 +1639,20 @@ enum dronespawn_t { DS_BITCH_HEAT = 27, DS_ARACHNID = 28, DS_MEDIC_COMMANDER = 29, + + DS_COMMANDER = 30, DS_MAKRON = 31, DS_BARON_FIRE = 32, DS_SUPERTANK = 33, DS_JORG = 34, DS_CARRIER = 35, + DS_GUARDIAN = 36, + DS_JANITOR = 37, + DS_MiniGuardian = 38, + DS_SOLDIER_RIPPER = 39, + DS_SOLDIER_BLUEBLASTER = 40, + DS_SOLDIER_LASER = 41, }; @@ -2564,6 +2584,8 @@ struct edict_s { edict_t *selected[4]; // drone selection edict_t *other; // laser effect for hw/ctf + edict_t *beam; // dabeam primary laser + edict_t *beam2; // dabeam secondary laser edict_t *supplystation; //edict_t *magmine; //GHz START diff --git a/src/q_shared.h b/src/q_shared.h index 7713f0e5..71855a38 100644 --- a/src/q_shared.h +++ b/src/q_shared.h @@ -1338,7 +1338,7 @@ enum monster_muzzleflash_id_t : uint16_t { MZ2_LAST }; -extern vec3_t monster_flash_offset[212]; +extern vec3_t monster_flash_offset[MZ2_LAST + 1]; // temp entity events diff --git a/src/quake2/g_layout.c b/src/quake2/g_layout.c index 207796b3..f0bd0f83 100644 --- a/src/quake2/g_layout.c +++ b/src/quake2/g_layout.c @@ -302,6 +302,9 @@ sidebar_entry_t layout_add_entity_info(sidebar_t* sidebar, edict_t* ent) case M_SOLDIERLT: case M_SOLDIER: case M_SOLDIERSS: + case M_SOLDIER_RIPPER: + case M_SOLDIER_BLUEBLASTER: + case M_SOLDIER_LASER: case M_FLIPPER: case M_FLYER: case M_INFANTRY: @@ -332,6 +335,9 @@ sidebar_entry_t layout_add_entity_info(sidebar_t* sidebar, edict_t* ent) case M_GEKK: case M_ARACHNID: case M_CARRIER: + case M_GUARDIAN: + case M_JANITOR: + case M_MINIGUARDIAN: case M_SKELETON: case M_GOLEM: name = lva("%s", V_GetMonsterName(ent)); diff --git a/src/quake2/g_main.c b/src/quake2/g_main.c index 3a90202b..545e4cee 100644 --- a/src/quake2/g_main.c +++ b/src/quake2/g_main.c @@ -836,7 +836,8 @@ void G_RunFrame(bool main_loop) level.current_entity = ent; - VectorCopy(ent->s.origin, ent->s.old_origin); + if (!(ent->s.renderfx & RF_BEAM)) // this line allows guardian, soldier_lasergun to have visual on their laser attacks :) + VectorCopy(ent->s.origin, ent->s.old_origin); // if the ground entity moved, make sure we are still on it if ((ent->groundentity) && (ent->groundentity->linkcount != ent->groundentity_linkcount)) diff --git a/src/quake2/g_svcmds.c b/src/quake2/g_svcmds.c index 35245aa7..2a487279 100644 --- a/src/quake2/g_svcmds.c +++ b/src/quake2/g_svcmds.c @@ -651,8 +651,15 @@ void SVCmd_SpawnBoss_f (void) else vrx_create_new_drone(m_worldspawn, DS_CARRIER, true, true, 0); } + else if (!strcmp(gi.argv(2), "guardian") || !strcmp(gi.argv(2), "psxguardian")) + { + if (invasion->value) + vrx_inv_spawn_boss(m_worldspawn, DS_GUARDIAN); + else + vrx_create_new_drone(m_worldspawn, DS_GUARDIAN, true, true, 0); + } else - safe_cprintf(NULL, PRINT_HIGH, "Invalid boss type. Usage: sv spawnboss \n"); + safe_cprintf(NULL, PRINT_HIGH, "Invalid boss type. Usage: sv spawnboss \n"); } void SVCmd_MakeBoss_f (void) diff --git a/src/quake2/monsterframes/m_flash.c b/src/quake2/monsterframes/m_flash.c index 65f6d31f..c383169a 100644 --- a/src/quake2/monsterframes/m_flash.c +++ b/src/quake2/monsterframes/m_flash.c @@ -5,7 +5,7 @@ // this file is included in both the game dll and quake2, // the game needs it to source shot locations, the client // needs it to position muzzle flashes -vec3_t monster_flash_offset [212] = +vec3_t monster_flash_offset [MZ2_LAST + 1] = { // flash 0 is not used 0.0, 0.0, 0.0, @@ -464,6 +464,141 @@ vec3_t monster_flash_offset [212] = // MZ2_WIDOW2_BEAM_SWEEP_11 210 58.29, 27.11, 92.00, +// MZ2_SOLDIER_RIPPER_1 211 + 10.6 * 1.2, 7.7 * 1.2, 7.8 * 1.2, +// MZ2_SOLDIER_RIPPER_2 212 + 25.1 * 1.2, 3.6 * 1.2, 19.0 * 1.2, +// MZ2_SOLDIER_RIPPER_3 213 + 20.8 * 1.2, 10.1 * 1.2, -2.7 * 1.2, +// MZ2_SOLDIER_RIPPER_4 214 + 7.6 * 1.2, 9.3 * 1.2, 0.8 * 1.2, +// MZ2_SOLDIER_RIPPER_5 215 + 30.5 * 1.2, 9.9 * 1.2, -18.7 * 1.2, +// MZ2_SOLDIER_RIPPER_6 216 + 27.6 * 1.2, 3.4 * 1.2, -10.4 * 1.2, +// MZ2_SOLDIER_RIPPER_7 217 + 28.9 * 1.2, 4.6 * 1.2, -8.1 * 1.2, +// MZ2_SOLDIER_RIPPER_8 218 + 31.5 * 1.2, 9.6 * 1.2, 10.1 * 1.2, +// MZ2_SOLDIER_HYPERGUN_1 219 + 10.6 * 1.2, 7.7 * 1.2, 7.8 * 1.2, +// MZ2_SOLDIER_HYPERGUN_2 220 + 25.1 * 1.2, 3.6 * 1.2, 19.0 * 1.2, +// MZ2_SOLDIER_HYPERGUN_3 221 + 20.8 * 1.2, 10.1 * 1.2, -2.7 * 1.2, +// MZ2_SOLDIER_HYPERGUN_4 222 + 7.6 * 1.2, 9.3 * 1.2, 0.8 * 1.2, +// MZ2_SOLDIER_HYPERGUN_5 223 + 30.5 * 1.2, 9.9 * 1.2, -18.7 * 1.2, +// MZ2_SOLDIER_HYPERGUN_6 224 + 27.6 * 1.2, 3.4 * 1.2, -10.4 * 1.2, +// MZ2_SOLDIER_HYPERGUN_7 225 + 28.9 * 1.2, 4.6 * 1.2, -8.1 * 1.2, +// MZ2_SOLDIER_HYPERGUN_8 226 + 31.5 * 1.2, 9.6 * 1.2, 10.1 * 1.2, +// MZ2_GUARDIAN_BLASTER 227 + 88, 50, 60, +// MZ2_ARACHNID_RAIL1 228 + 58, 20, 17.2, +// MZ2_ARACHNID_RAIL2 229 + 64, -22, 24, +// MZ2_ARACHNID_RAIL_UP1 230 + 37, 13, 72, +// MZ2_ARACHNID_RAIL_UP2 231 + 58, -25, 72, +// MZ2_INFANTRY_MACHINEGUN_14 232 + 34, 11, 13, +// MZ2_INFANTRY_MACHINEGUN_15 233 + 28, 13, 10.5, +// MZ2_INFANTRY_MACHINEGUN_16 234 + 29, 13, 8.5, +// MZ2_INFANTRY_MACHINEGUN_17 235 + 30, 12.5, 12, +// MZ2_INFANTRY_MACHINEGUN_18 236 + 29, 12.5, 14.7, +// MZ2_INFANTRY_MACHINEGUN_19 237 + 30, 6.5, 12, +// MZ2_INFANTRY_MACHINEGUN_20 238 + 29, 1.5, 8.5, +// MZ2_INFANTRY_MACHINEGUN_21 239 + 29, 6, 10, +// MZ2_GUNCMDR_CHAINGUN_1 240 + 25, 11, 21, +// MZ2_GUNCMDR_CHAINGUN_2 241 + 26.5, 5, 21, +// MZ2_GUNCMDR_GRENADE_MORTAR_1 242 + 27, 6.5, 4, +// MZ2_GUNCMDR_GRENADE_MORTAR_2 243 + 28, 4, 4, +// MZ2_GUNCMDR_GRENADE_MORTAR_3 244 + 27, 1.7, 4, +// MZ2_GUNCMDR_GRENADE_FRONT_1 245 + 21.7, -1.5, 22.5, +// MZ2_GUNCMDR_GRENADE_FRONT_2 246 + 22, 0, 20.5, +// MZ2_GUNCMDR_GRENADE_FRONT_3 247 + 22.5, 3.7, 20.5, +// MZ2_GUNCMDR_GRENADE_CROUCH_1 248 + 8, 40, 18, +// MZ2_GUNCMDR_GRENADE_CROUCH_2 249 + 29, 16, 19, +// MZ2_GUNCMDR_GRENADE_CROUCH_3 250 + 4.7, -30, 20, +// MZ2_SOLDIER_BLASTER_9 251 + 36.33, 12.24, -17.39, +// MZ2_SOLDIER_SHOTGUN_9 252 + 36.33, 12.24, -17.39, +// MZ2_SOLDIER_MACHINEGUN_9 253 + 36.33, 12.24, -17.39, +// MZ2_SOLDIER_RIPPER_9 254 + 36.33, 12.24, -17.39, +// MZ2_SOLDIER_HYPERGUN_9 255 + 36.33, 12.24, -17.39, +// MZ2_GUNNER_GRENADE2_1 256 + 36, -6.2, 19.59, +// MZ2_GUNNER_GRENADE2_2 257 + 36, -6.2, 19.59, +// MZ2_GUNNER_GRENADE2_3 258 + 36, -6.2, 19.59, +// MZ2_GUNNER_GRENADE2_4 259 + 36, -6.2, 19.59, +// MZ2_INFANTRY_MACHINEGUN_22 260 + 14.8, 10.5, 8.82, +// MZ2_SUPERTANK_GRENADE_1 261 + 31.31, -37, 54.32, +// MZ2_SUPERTANK_GRENADE_2 262 + 31.31, 37, 54.32, +// MZ2_HOVER_BLASTER_2 263 + 1.7, -7.0, 11.3, +// MZ2_DAEDALUS_BLASTER_2 264 + 1.7, -7.0, 11.3, +// MZ2_MEDIC_HYPERBLASTER1_1-12 265 + 34, 12.5, 15, + 33.4, 11.2, 15, + 36.6, 7.4, 15, + 35, 4.1, 15, + 37.6, 1, 15, + 35.7, -1.9, 15, + 37.6, -0.5, 15, + 35.2, 2.8, 15, + 37.5, 3.8, 15, + 34.5, 6.9, 15, + 33.7, 9.9, 15, + 35.5, 11, 15, +// MZ2_MEDIC_HYPERBLASTER2_1-12 277 + 34, 12.5, 15, + 33.4, 11.2, 15, + 36.6, 7.4, 15, + 35, 4.1, 15, + 37.6, 1, 15, + 35.7, -1.9, 15, + 37.6, -0.5, 15, + 35.2, 2.8, 15, + 37.5, 3.8, 15, + 34.5, 6.9, 15, + 33.7, 9.9, 15, + 35.5, 11, 15, + // end of table 0.0, 0.0, 0.0 }; diff --git a/src/quake2/monsterframes/m_guardian.h b/src/quake2/monsterframes/m_guardian.h new file mode 100644 index 00000000..e82466de --- /dev/null +++ b/src/quake2/monsterframes/m_guardian.h @@ -0,0 +1,221 @@ +// Frame definitions for models/monsters/guardian/tris.md2. + +enum { + FRAME_sleep1, + FRAME_sleep2, + FRAME_sleep3, + FRAME_sleep4, + FRAME_sleep5, + FRAME_sleep6, + FRAME_sleep7, + FRAME_sleep8, + FRAME_sleep9, + FRAME_sleep10, + FRAME_sleep11, + FRAME_sleep12, + FRAME_sleep13, + FRAME_sleep14, + FRAME_death1, + FRAME_death2, + FRAME_death3, + FRAME_death4, + FRAME_death5, + FRAME_death6, + FRAME_death7, + FRAME_death8, + FRAME_death9, + FRAME_death10, + FRAME_death11, + FRAME_death12, + FRAME_death13, + FRAME_death14, + FRAME_death15, + FRAME_death16, + FRAME_death17, + FRAME_death18, + FRAME_death19, + FRAME_death20, + FRAME_death21, + FRAME_death22, + FRAME_death23, + FRAME_death24, + FRAME_death25, + FRAME_death26, + FRAME_atk1_out1, + FRAME_atk1_out2, + FRAME_atk1_out3, + FRAME_atk2_out1, + FRAME_atk2_out2, + FRAME_atk2_out3, + FRAME_atk2_out4, + FRAME_atk2_out5, + FRAME_atk2_out6, + FRAME_atk2_out7, + FRAME_kick_out1, + FRAME_kick_out2, + FRAME_kick_out3, + FRAME_kick_out4, + FRAME_kick_out5, + FRAME_kick_out6, + FRAME_kick_out7, + FRAME_kick_out8, + FRAME_kick_out9, + FRAME_kick_out10, + FRAME_kick_out11, + FRAME_kick_out12, + FRAME_pain1_1, + FRAME_pain1_2, + FRAME_pain1_3, + FRAME_pain1_4, + FRAME_pain1_5, + FRAME_pain1_6, + FRAME_pain1_7, + FRAME_pain1_8, + FRAME_idle1, + FRAME_idle2, + FRAME_idle3, + FRAME_idle4, + FRAME_idle5, + FRAME_idle6, + FRAME_idle7, + FRAME_idle8, + FRAME_idle9, + FRAME_idle10, + FRAME_idle11, + FRAME_idle12, + FRAME_idle13, + FRAME_idle14, + FRAME_idle15, + FRAME_idle16, + FRAME_idle17, + FRAME_idle18, + FRAME_idle19, + FRAME_idle20, + FRAME_idle21, + FRAME_idle22, + FRAME_idle23, + FRAME_idle24, + FRAME_idle25, + FRAME_idle26, + FRAME_idle27, + FRAME_idle28, + FRAME_idle29, + FRAME_idle30, + FRAME_idle31, + FRAME_idle32, + FRAME_idle33, + FRAME_idle34, + FRAME_idle35, + FRAME_idle36, + FRAME_idle37, + FRAME_idle38, + FRAME_idle39, + FRAME_idle40, + FRAME_idle41, + FRAME_idle42, + FRAME_idle43, + FRAME_idle44, + FRAME_idle45, + FRAME_idle46, + FRAME_idle47, + FRAME_idle48, + FRAME_idle49, + FRAME_idle50, + FRAME_idle51, + FRAME_idle52, + FRAME_atk1_in1, + FRAME_atk1_in2, + FRAME_atk1_in3, + FRAME_kick_in1, + FRAME_kick_in2, + FRAME_kick_in3, + FRAME_kick_in4, + FRAME_kick_in5, + FRAME_kick_in6, + FRAME_kick_in7, + FRAME_kick_in8, + FRAME_kick_in9, + FRAME_kick_in10, + FRAME_kick_in11, + FRAME_kick_in12, + FRAME_kick_in13, + FRAME_walk1, + FRAME_walk2, + FRAME_walk3, + FRAME_walk4, + FRAME_walk5, + FRAME_walk6, + FRAME_walk7, + FRAME_walk8, + FRAME_walk9, + FRAME_walk10, + FRAME_walk11, + FRAME_walk12, + FRAME_walk13, + FRAME_walk14, + FRAME_walk15, + FRAME_walk16, + FRAME_walk17, + FRAME_walk18, + FRAME_walk19, + FRAME_wake1, + FRAME_wake2, + FRAME_wake3, + FRAME_wake4, + FRAME_wake5, + FRAME_atk1_spin1, + FRAME_atk1_spin2, + FRAME_atk1_spin3, + FRAME_atk1_spin4, + FRAME_atk1_spin5, + FRAME_atk1_spin6, + FRAME_atk1_spin7, + FRAME_atk1_spin8, + FRAME_atk1_spin9, + FRAME_atk1_spin10, + FRAME_atk1_spin11, + FRAME_atk1_spin12, + FRAME_atk1_spin13, + FRAME_atk1_spin14, + FRAME_atk1_spin15, + FRAME_atk2_fire1, + FRAME_atk2_fire2, + FRAME_atk2_fire3, + FRAME_atk2_fire4, + FRAME_turnl_1, + FRAME_turnl_2, + FRAME_turnl_3, + FRAME_turnl_4, + FRAME_turnl_5, + FRAME_turnl_6, + FRAME_turnl_7, + FRAME_turnl_8, + FRAME_turnl_9, + FRAME_turnl_10, + FRAME_turnl_11, + FRAME_turnr_1, + FRAME_turnr_2, + FRAME_turnr_3, + FRAME_turnr_4, + FRAME_turnr_5, + FRAME_turnr_6, + FRAME_turnr_7, + FRAME_turnr_8, + FRAME_turnr_9, + FRAME_turnr_10, + FRAME_turnr_11, + FRAME_atk2_in1, + FRAME_atk2_in2, + FRAME_atk2_in3, + FRAME_atk2_in4, + FRAME_atk2_in5, + FRAME_atk2_in6, + FRAME_atk2_in7, + FRAME_atk2_in8, + FRAME_atk2_in9, + FRAME_atk2_in10, + FRAME_atk2_in11, + FRAME_atk2_in12 +}; + +#define MODEL_SCALE 1.000000 diff --git a/src/quake2/monsterframes/m_soldier.h b/src/quake2/monsterframes/m_soldier.h index a14b8cc4..07bf5d14 100644 --- a/src/quake2/monsterframes/m_soldier.h +++ b/src/quake2/monsterframes/m_soldier.h @@ -477,5 +477,105 @@ #define FRAME_death608 472 #define FRAME_death609 473 #define FRAME_death610 474 +#define FRAME_stand401 475 +#define FRAME_stand402 476 +#define FRAME_stand403 477 +#define FRAME_stand404 478 +#define FRAME_stand405 479 +#define FRAME_stand406 480 +#define FRAME_stand407 481 +#define FRAME_stand408 482 +#define FRAME_stand409 483 +#define FRAME_stand410 484 +#define FRAME_stand411 485 +#define FRAME_stand412 486 +#define FRAME_stand413 487 +#define FRAME_stand414 488 +#define FRAME_stand415 489 +#define FRAME_stand416 490 +#define FRAME_stand417 491 +#define FRAME_stand418 492 +#define FRAME_stand419 493 +#define FRAME_stand420 494 +#define FRAME_stand421 495 +#define FRAME_stand422 496 +#define FRAME_stand423 497 +#define FRAME_stand424 498 +#define FRAME_stand425 499 +#define FRAME_stand426 500 +#define FRAME_stand427 501 +#define FRAME_stand428 502 +#define FRAME_stand429 503 +#define FRAME_stand430 504 +#define FRAME_stand431 505 +#define FRAME_stand432 506 +#define FRAME_stand433 507 +#define FRAME_stand434 508 +#define FRAME_stand435 509 +#define FRAME_stand436 510 +#define FRAME_stand437 511 +#define FRAME_stand438 512 +#define FRAME_stand439 513 +#define FRAME_stand440 514 +#define FRAME_stand441 515 +#define FRAME_stand442 516 +#define FRAME_stand443 517 +#define FRAME_stand444 518 +#define FRAME_stand445 519 +#define FRAME_stand446 520 +#define FRAME_stand447 521 +#define FRAME_stand448 522 +#define FRAME_stand449 523 +#define FRAME_stand450 524 +#define FRAME_stand451 525 +#define FRAME_stand452 526 +#define FRAME_stand201 527 +#define FRAME_stand202 528 +#define FRAME_stand203 529 +#define FRAME_stand204 530 +#define FRAME_stand205 531 +#define FRAME_stand206 532 +#define FRAME_stand207 533 +#define FRAME_stand208 534 +#define FRAME_stand209 535 +#define FRAME_stand210 536 +#define FRAME_stand211 537 +#define FRAME_stand212 538 +#define FRAME_stand213 539 +#define FRAME_stand214 540 +#define FRAME_stand215 541 +#define FRAME_stand216 542 +#define FRAME_stand217 543 +#define FRAME_stand218 544 +#define FRAME_stand219 545 +#define FRAME_stand220 546 +#define FRAME_stand221 547 +#define FRAME_stand222 548 +#define FRAME_stand223 549 +#define FRAME_stand224 550 +#define FRAME_stand225 551 +#define FRAME_stand226 552 +#define FRAME_stand227 553 +#define FRAME_stand228 554 +#define FRAME_stand229 555 +#define FRAME_stand230 556 +#define FRAME_stand231 557 +#define FRAME_stand232 558 +#define FRAME_stand233 559 +#define FRAME_stand234 560 +#define FRAME_stand235 561 +#define FRAME_stand236 562 +#define FRAME_stand237 563 +#define FRAME_stand238 564 +#define FRAME_stand239 565 +#define FRAME_stand240 566 +#define FRAME_attak501 567 +#define FRAME_attak502 568 +#define FRAME_attak503 569 +#define FRAME_attak504 570 +#define FRAME_attak505 571 +#define FRAME_attak506 572 +#define FRAME_attak507 573 +#define FRAME_attak508 574 #define MODEL_SCALE 1.200000 diff --git a/src/server/v_luasettings.c b/src/server/v_luasettings.c index f197326d..4b1746e1 100644 --- a/src/server/v_luasettings.c +++ b/src/server/v_luasettings.c @@ -1142,6 +1142,30 @@ void Lua_LoadVariables() M_CARRIER_ADDON_HEALTH = vrx_lua_get_variable("M_CARRIER_ADDON_HEALTH", 650); M_CARRIER_INITIAL_ARMOR = vrx_lua_get_variable("M_CARRIER_INITIAL_ARMOR", 500); M_CARRIER_ADDON_ARMOR = vrx_lua_get_variable("M_CARRIER_ADDON_ARMOR", 350); + M_GUARDIAN_INITIAL_HEALTH = vrx_lua_get_variable("M_GUARDIAN_INITIAL_HEALTH", 6500); + M_GUARDIAN_ADDON_HEALTH = vrx_lua_get_variable("M_GUARDIAN_ADDON_HEALTH", 1200); + M_GUARDIAN_INITIAL_ARMOR = vrx_lua_get_variable("M_GUARDIAN_INITIAL_ARMOR", 550); + M_GUARDIAN_ADDON_ARMOR = vrx_lua_get_variable("M_GUARDIAN_ADDON_ARMOR", 250); + M_JANITOR_INITIAL_HEALTH = vrx_lua_get_variable("M_JANITOR_INITIAL_HEALTH", 1200); + M_JANITOR_ADDON_HEALTH = vrx_lua_get_variable("M_JANITOR_ADDON_HEALTH", 450); + M_JANITOR_INITIAL_ARMOR = vrx_lua_get_variable("M_JANITOR_INITIAL_ARMOR", 500); + M_JANITOR_ADDON_ARMOR = vrx_lua_get_variable("M_JANITOR_ADDON_ARMOR", 150); + M_MINIGUARDIAN_INITIAL_HEALTH = vrx_lua_get_variable("M_MINIGUARDIAN_INITIAL_HEALTH", 400); + M_MINIGUARDIAN_ADDON_HEALTH = vrx_lua_get_variable("M_MINIGUARDIAN_ADDON_HEALTH", 100); + M_MINIGUARDIAN_INITIAL_ARMOR = vrx_lua_get_variable("M_MINIGUARDIAN_INITIAL_ARMOR", 450); + M_MINIGUARDIAN_ADDON_ARMOR = vrx_lua_get_variable("M_MINIGUARDIAN_ADDON_ARMOR", 150); + M_SOLDIER_RIPPER_INITIAL_HEALTH = vrx_lua_get_variable("M_SOLDIER_RIPPER_INITIAL_HEALTH", 100); + M_SOLDIER_RIPPER_ADDON_HEALTH = vrx_lua_get_variable("M_SOLDIER_RIPPER_ADDON_HEALTH", 40); + M_SOLDIER_RIPPER_INITIAL_ARMOR = vrx_lua_get_variable("M_SOLDIER_RIPPER_INITIAL_ARMOR", 50); + M_SOLDIER_RIPPER_ADDON_ARMOR = vrx_lua_get_variable("M_SOLDIER_RIPPER_ADDON_ARMOR", 20); + M_SOLDIER_BLUEBLASTER_INITIAL_HEALTH = vrx_lua_get_variable("M_SOLDIER_BLUEBLASTER_INITIAL_HEALTH", 100); + M_SOLDIER_BLUEBLASTER_ADDON_HEALTH = vrx_lua_get_variable("M_SOLDIER_BLUEBLASTER_ADDON_HEALTH", 40); + M_SOLDIER_BLUEBLASTER_INITIAL_ARMOR = vrx_lua_get_variable("M_SOLDIER_BLUEBLASTER_INITIAL_ARMOR", 50); + M_SOLDIER_BLUEBLASTER_ADDON_ARMOR = vrx_lua_get_variable("M_SOLDIER_BLUEBLASTER_ADDON_ARMOR", 20); + M_SOLDIER_LASER_INITIAL_HEALTH = vrx_lua_get_variable("M_SOLDIER_LASER_INITIAL_HEALTH", 120); + M_SOLDIER_LASER_ADDON_HEALTH = vrx_lua_get_variable("M_SOLDIER_LASER_ADDON_HEALTH", 45); + M_SOLDIER_LASER_INITIAL_ARMOR = vrx_lua_get_variable("M_SOLDIER_LASER_INITIAL_ARMOR", 70); + M_SOLDIER_LASER_ADDON_ARMOR = vrx_lua_get_variable("M_SOLDIER_LASER_ADDON_ARMOR", 25); M_BRAIN_INITIAL_PULL = vrx_lua_get_variable("M_BRAIN_INITIAL_PULL", -60); M_BRAIN_ADDON_PULL = vrx_lua_get_variable("M_BRAIN_ADDON_PULL", -2); @@ -1174,6 +1198,9 @@ void Lua_LoadVariables() M_RAILGUN_DMG_ADDON = vrx_lua_get_variable("M_RAILGUN_DMG_ADDON", 15); M_RAILGUN_DMG_MAX = vrx_lua_get_variable("M_RAILGUN_DMG_MAX", 0); + M_DABEAM_DMG_BASE = vrx_lua_get_variable("M_DABEAM_DMG_BASE", 5); + M_DABEAM_DMG_ADDON = vrx_lua_get_variable("M_DABEAM_DMG_ADDON", 1); + M_MELEE_DMG_BASE = vrx_lua_get_variable("M_MELEE_DMG_BASE", 50); M_MELEE_DMG_ADDON = vrx_lua_get_variable("M_MELEE_DMG_ADDON", 25); M_MELEE_DMG_MAX = vrx_lua_get_variable("M_MELEE_DMG_MAX", 0); From d1f8cbb01bbecc718cdb1125f8e260d6ae9ca27b Mon Sep 17 00:00:00 2001 From: Enemy-123 Date: Wed, 22 Apr 2026 12:25:05 -0400 Subject: [PATCH 07/24] using FL_PROJECTILE on weapon and some abilities, modified AI so monsters will dodge/sidestep similarly to remaster, added more animations for some monsters and fixed some related bugs to new monsters --- src/combat/abilities/emp.c | 4 +- src/combat/abilities/fire.c | 4 +- src/combat/abilities/gloom.c | 2 + src/combat/abilities/hammer.c | 2 + src/combat/abilities/ice.c | 6 + src/combat/abilities/magicbolt.c | 2 + src/combat/abilities/mirv.c | 4 +- src/combat/abilities/napalm.c | 4 +- src/combat/abilities/plasmabolt.c | 4 +- .../playermonster/playertocacodemon.c | 2 + src/combat/abilities/spike.c | 4 +- src/combat/abilities/spikegrenade.c | 5 +- src/combat/common/v_misc.c | 13 +- src/combat/weapons/g_weapon.c | 23 +- src/combat/weapons/mp/g_weapon_mp.c | 12 + src/combat/weapons/w_sword.c | 4 +- src/entities/drone/drone_ai.c | 145 ++++- src/entities/drone/drone_arachnid.c | 21 +- src/entities/drone/drone_berserk.c | 184 +++++- src/entities/drone/drone_bitch.c | 149 ++++- src/entities/drone/drone_brain.c | 41 +- src/entities/drone/drone_daedalus.c | 5 + src/entities/drone/drone_gekk.c | 127 ++++- src/entities/drone/drone_guncmdr.c | 522 ++++++++++++++++-- src/entities/drone/drone_gunner.c | 87 ++- src/entities/drone/drone_hover.c | 5 + src/entities/drone/drone_infantry.c | 138 +++++ src/entities/drone/drone_medic.c | 179 ++++-- src/entities/drone/drone_misc.c | 4 +- src/entities/drone/drone_soldier.c | 372 +++++++++++-- src/entities/drone/drone_stalker.c | 312 +++++++++++ src/g_local.h | 2 - 32 files changed, 2162 insertions(+), 226 deletions(-) diff --git a/src/combat/abilities/emp.c b/src/combat/abilities/emp.c index cdb04a4f..10fcc7cc 100644 --- a/src/combat/abilities/emp.c +++ b/src/combat/abilities/emp.c @@ -299,6 +299,8 @@ void fire_emp_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int slevel, f grenade->monsterinfo.level = slevel; grenade->dmg_radius = radius; grenade->classname = "emp grenade"; + if (self->client) + grenade->svflags |= SVF_PROJECTILE; gi.linkentity (grenade); grenade->nextthink = level.time + timer; @@ -333,4 +335,4 @@ void Cmd_TossEMP (edict_t *ent) ent->client->ability_delay = level.time + EMP_DELAY; ent->client->pers.inventory[power_cube_index] -= cost; -} \ No newline at end of file +} diff --git a/src/combat/abilities/fire.c b/src/combat/abilities/fire.c index d2cbb8a1..838117b9 100644 --- a/src/combat/abilities/fire.c +++ b/src/combat/abilities/fire.c @@ -241,6 +241,8 @@ void fire_fireball(edict_t* self, vec3_t start, vec3_t aimdir, int damage, float fireball->radius_dmg = flame_damage; fireball->count = flames; fireball->classname = "fireball"; + if (self->client) + fireball->svflags |= SVF_PROJECTILE; fireball->delay = level.time + 10.0; gi.linkentity(fireball); fireball->nextthink = level.time + FRAMETIME; @@ -590,4 +592,4 @@ void Cmd_Firewall_f(edict_t* ent, float skill_mult, float cost_mult) ent->client->ability_delay = level.time + FIREWALL_DELAY; ent->client->pers.inventory[power_cube_index] -= cost; ent->num_firewalls++;// increase firewall counter -} \ No newline at end of file +} diff --git a/src/combat/abilities/gloom.c b/src/combat/abilities/gloom.c index ac55d664..35512f04 100644 --- a/src/combat/abilities/gloom.c +++ b/src/combat/abilities/gloom.c @@ -3691,6 +3691,8 @@ void fire_acid (edict_t *self, vec3_t start, vec3_t aimdir, int projectile_damag grenade->dmg_radius = radius; grenade->delay = acid_duration; grenade->classname = "acid"; + if (self->client) + grenade->svflags |= SVF_PROJECTILE; // Talent: Spitting Gasser if (gas_damage > 0 && gas_radius > 0 && gas_duration > 0) { diff --git a/src/combat/abilities/hammer.c b/src/combat/abilities/hammer.c index 7fe362a7..8824f4b3 100644 --- a/src/combat/abilities/hammer.c +++ b/src/combat/abilities/hammer.c @@ -248,6 +248,8 @@ void SpawnBlessedHammer (edict_t *ent, int boomerang_level) hammer->nextthink = level.time + FRAMETIME; hammer->dmg = HAMMER_INITIAL_DAMAGE+HAMMER_ADDON_DAMAGE*ent->myskills.abilities[HAMMER].current_level; hammer->classname = "hammer"; + if (ent->client) + hammer->svflags |= SVF_PROJECTILE; gi.linkentity (hammer); // write a nice effect so everyone knows we've cast a spell diff --git a/src/combat/abilities/ice.c b/src/combat/abilities/ice.c index 25781746..b8947827 100644 --- a/src/combat/abilities/ice.c +++ b/src/combat/abilities/ice.c @@ -251,6 +251,8 @@ void fire_icebolt(edict_t* self, vec3_t start, vec3_t aimdir, int damage, float icebolt->chill_level = chillLevel; icebolt->chill_time = chillDuration; icebolt->classname = "icebolt"; + if (self->client) + icebolt->svflags |= SVF_PROJECTILE; icebolt->delay = level.time + 10.0; // timeout gi.linkentity(icebolt); icebolt->nextthink = level.time + FRAMETIME; @@ -359,6 +361,8 @@ void fire_iceshard(edict_t* self, vec3_t start, vec3_t dir, float speed, int dam bolt->chill_level = chillLevel; bolt->chill_time = chillDuration; bolt->classname = "ice shard"; + if (G_GetClient(self)) + bolt->svflags |= SVF_PROJECTILE; gi.linkentity(bolt); // cloak a player-owned ice shard in PvM if there are too many entities nearby @@ -489,6 +493,8 @@ void fire_frozenorb(edict_t* self, vec3_t start, vec3_t aimdir, int damage, int orb->chill_level = chillLevel; orb->chill_time = chillDuration; orb->classname = "frozen orb"; + if (self->client) + orb->svflags |= SVF_PROJECTILE; orb->delay = level.time + FROZEN_ORB_DURATION; // timeout gi.linkentity(orb); orb->nextthink = level.time + FRAMETIME; diff --git a/src/combat/abilities/magicbolt.c b/src/combat/abilities/magicbolt.c index cabefc83..b6f1b3a9 100644 --- a/src/combat/abilities/magicbolt.c +++ b/src/combat/abilities/magicbolt.c @@ -72,6 +72,8 @@ void fire_magicbolt (edict_t *ent, int damage, int radius_damage, float damage_r bolt->dmg_radius = damage_radius; bolt->radius_dmg = radius_damage; bolt->classname = "magicbolt"; + if (ent->client) + bolt->svflags |= SVF_PROJECTILE; gi.linkentity (bolt); // write a nice effect so everyone knows we've cast a spell diff --git a/src/combat/abilities/mirv.c b/src/combat/abilities/mirv.c index 01240487..28805f34 100644 --- a/src/combat/abilities/mirv.c +++ b/src/combat/abilities/mirv.c @@ -131,6 +131,8 @@ void fire_mirv_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, grenade->radius_dmg = damage; grenade->dmg_radius = radius; grenade->classname = "mirv grenade"; + if (self->client) + grenade->svflags |= SVF_PROJECTILE; gi.linkentity (grenade); grenade->nextthink = level.time + timer; @@ -171,4 +173,4 @@ void Cmd_TossMirv (edict_t *ent) ent->client->ability_delay = level.time + MIRV_DELAY; ent->client->pers.inventory[power_cube_index] -= cost; ent->client->pers.inventory[grenade_index]--; -} \ No newline at end of file +} diff --git a/src/combat/abilities/napalm.c b/src/combat/abilities/napalm.c index d99baef8..75a06d90 100644 --- a/src/combat/abilities/napalm.c +++ b/src/combat/abilities/napalm.c @@ -102,6 +102,8 @@ void fire_napalm (edict_t *self, vec3_t start, vec3_t aimdir, grenade->radius_dmg = burn_damage; grenade->classname = "napalm"; + if (self->client) + grenade->svflags |= SVF_PROJECTILE; gi.linkentity (grenade); VectorScale (aimdir, speed, grenade->velocity); @@ -180,4 +182,4 @@ void Cmd_Napalm_f (edict_t *ent) ent->client->pers.inventory[power_cube_index] -= cost; ent->client->pers.inventory[grenade_index]--; ent->client->ability_delay = level.time + NAPALM_DELAY; -} \ No newline at end of file +} diff --git a/src/combat/abilities/plasmabolt.c b/src/combat/abilities/plasmabolt.c index bb18bf20..e5cc4100 100644 --- a/src/combat/abilities/plasmabolt.c +++ b/src/combat/abilities/plasmabolt.c @@ -75,6 +75,8 @@ void fire_plasmabolt (edict_t *self, vec3_t start, vec3_t aimdir, int damage, fl bolt->dmg_radius = damage_radius; bolt->dmg = damage; bolt->classname = "plasma bolt"; + if (self->client) + bolt->svflags |= SVF_PROJECTILE; bolt->random = duration; bolt->delay = level.time + 10.0; gi.linkentity(bolt); @@ -121,4 +123,4 @@ void Cmd_Plasmabolt_f (edict_t *ent) gi.multicast (ent->s.origin, MULTICAST_PVS); gi.sound(ent, CHAN_ITEM, gi.soundindex("abilities/holybolt2.wav"), 1, ATTN_NORM, 0); -} \ No newline at end of file +} diff --git a/src/combat/abilities/playermonster/playertocacodemon.c b/src/combat/abilities/playermonster/playertocacodemon.c index 346d31fa..58405fc5 100644 --- a/src/combat/abilities/playermonster/playertocacodemon.c +++ b/src/combat/abilities/playermonster/playertocacodemon.c @@ -69,6 +69,8 @@ void fire_skull(edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, skull->radius_dmg = damage; skull->dmg_radius = damage_radius; skull->classname = "skull"; + if (self->client) + skull->svflags |= SVF_PROJECTILE; skull->s.sound = gi.soundindex("weapons/bfg__l1a.wav"); skull->delay = level.time + 10; skull->think = bskull_think; diff --git a/src/combat/abilities/spike.c b/src/combat/abilities/spike.c index a899a444..4a3736bd 100644 --- a/src/combat/abilities/spike.c +++ b/src/combat/abilities/spike.c @@ -46,6 +46,8 @@ void fire_spike (edict_t *self, vec3_t start, vec3_t dir, int damage, float stun bolt->dmg = damage; bolt->dmg_radius = stun_length; bolt->classname = "spike"; + if (self->client) + bolt->svflags |= SVF_PROJECTILE; gi.linkentity (bolt); // cloak a player-owned spike in PvM if there are too many entities nearby @@ -152,4 +154,4 @@ void Cmd_Spike_f (edict_t *ent) if (ent->myskills.abilities[SPIKE].disable) return; player_fire_spike(ent); -} \ No newline at end of file +} diff --git a/src/combat/abilities/spikegrenade.c b/src/combat/abilities/spikegrenade.c index e0a191b9..2927c5d7 100644 --- a/src/combat/abilities/spikegrenade.c +++ b/src/combat/abilities/spikegrenade.c @@ -68,6 +68,9 @@ void fire_spikey (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed bolt->think = G_FreeEdict; bolt->dmg = damage; bolt->classname = "spikey"; + bolt->owner = self; + if (self->client) + bolt->svflags |= SVF_PROJECTILE; gi.linkentity (bolt); } @@ -242,4 +245,4 @@ void Cmd_SpikeGrenade_f (edict_t *ent) ent->client->pers.inventory[power_cube_index] -= cost; ent->client->pers.inventory[grenade_index]--; ent->client->ability_delay = level.time + SPIKEGRENADE_DELAY; -} \ No newline at end of file +} diff --git a/src/combat/common/v_misc.c b/src/combat/common/v_misc.c index 5105d212..a0d4d25c 100644 --- a/src/combat/common/v_misc.c +++ b/src/combat/common/v_misc.c @@ -611,6 +611,7 @@ int vrx_GetMonsterCost(int mtype) { case M_BRAIN: cost = M_BRAIN_COST; break; + case M_RUNNERTANK: case M_TANK: cost = M_TANK_COST; break; @@ -623,11 +624,8 @@ int vrx_GetMonsterCost(int mtype) { case M_REDMUTANT: cost = M_MUTANT_COST; break; - case M_RUNNERTANK: - cost = M_TANK_COST; - break; case M_GUNCMDR: - cost = M_TANK_COST; + cost = M_GUNNER_COST; break; case M_DAEDALUS: cost = M_HOVER_COST; @@ -648,6 +646,9 @@ int vrx_GetMonsterCost(int mtype) { case M_CARRIER: cost = M_COMMANDER_COST; break; + case M_JANITOR: + cost = M_TANK_COST; + break; case M_SUPERTANK: cost = M_SUPERTANK_COST; break; @@ -714,9 +715,6 @@ int vrx_GetMonsterControlCost(int mtype) { case M_GUNCMDR: cost = M_TANK_CONTROL_COST; break; - case M_DAEDALUS: - cost = M_HOVER_CONTROL_COST; - break; case M_GLADB: case M_GLADC: cost = M_GLADIATOR_CONTROL_COST; @@ -734,6 +732,7 @@ int vrx_GetMonsterControlCost(int mtype) { cost = M_JORG_CONTROL_COST; break; case M_HOVER: + case M_DAEDALUS: cost = M_HOVER_CONTROL_COST; break; case M_SUPERTANK: diff --git a/src/combat/weapons/g_weapon.c b/src/combat/weapons/g_weapon.c index afc5c409..370029eb 100644 --- a/src/combat/weapons/g_weapon.c +++ b/src/combat/weapons/g_weapon.c @@ -45,7 +45,6 @@ void check_dodge (edict_t *self, vec3_t start, vec3_t dir, int speed, int radius { vec3_t end; vec3_t v; - const vec3_t zvec = {0,0,0}; trace_t tr; float eta; edict_t *blip = NULL; @@ -62,7 +61,7 @@ void check_dodge (edict_t *self, vec3_t start, vec3_t dir, int speed, int radius tr.ent->monsterinfo.eta = level.time + eta; //gi.dprintf("ETA for impact is %.1f\n", tr.ent->monsterinfo.eta); tr.ent->monsterinfo.attacker = self; - VectorCopy(zvec, self->monsterinfo.dir); + VectorCopy(start, tr.ent->monsterinfo.dir); tr.ent->monsterinfo.radius = 0; } @@ -610,6 +609,8 @@ void fire_blaster2(edict_t *self, vec3_t start, vec3_t dir, int damage, int spee bolt->dmg = damage; bolt->dmg_radius = hyper ? 64 : 128; bolt->classname = "bolt"; + if (self->client) + bolt->svflags |= SVF_PROJECTILE; VectorClear(bolt->mins); VectorClear(bolt->maxs); gi.linkentity(bolt); @@ -653,6 +654,8 @@ void fire_blueblaster(edict_t *self, vec3_t start, vec3_t dir, int damage, int s bolt->think = G_FreeEdict; bolt->dmg = damage; bolt->classname = "bolt"; + if (self->client) + bolt->svflags |= SVF_PROJECTILE; VectorClear(bolt->mins); VectorClear(bolt->maxs); gi.linkentity(bolt); @@ -730,6 +733,8 @@ void fire_blaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int spee bolt->think = G_FreeEdict; bolt->dmg = damage; bolt->classname = "bolt"; + if (self->client) + bolt->svflags |= SVF_PROJECTILE; gi.linkentity (bolt); // call monster's dodge function @@ -1030,6 +1035,8 @@ void fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int s grenade->radius_dmg = radius_damage; grenade->dmg_radius = damage_radius; grenade->classname = "grenade"; + if (self->client) + grenade->svflags |= SVF_PROJECTILE; gi.linkentity (grenade); } @@ -1072,6 +1079,8 @@ edict_t *fire_grenade2 (edict_t *self, vec3_t start, vec3_t aimdir, int damage, grenade->radius_dmg = radius_damage; grenade->dmg_radius = damage_radius; grenade->classname = "hgrenade"; + if (self->client) + grenade->svflags |= SVF_PROJECTILE; if (held) grenade->spawnflags = 3; else @@ -1242,6 +1251,8 @@ void fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed rocket->dmg_radius = damage_radius; rocket->s.sound = gi.soundindex ("weapons/rockfly.wav"); rocket->classname = "rocket"; + if (self->client) + rocket->svflags |= SVF_PROJECTILE; if (self->client) check_dodge (self, rocket->s.origin, dir, speed, damage_radius); @@ -1352,6 +1363,8 @@ void fire_lockon_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, in rocket->dmg_radius = damage_radius; rocket->s.sound = gi.soundindex ("weapons/rockfly.wav"); rocket->classname = "lockon rocket"; + if (self->client) + rocket->svflags |= SVF_PROJECTILE; if (self->client) check_dodge (self, rocket->s.origin, dir, speed, damage_radius); @@ -1469,6 +1482,8 @@ void fire_smartrocket (edict_t *self, edict_t *target, vec3_t start, vec3_t dir, rocket->dmg_radius = damage_radius; rocket->s.sound = gi.soundindex ("weapons/rockfly.wav"); rocket->classname = "smartrocket"; + if (self->client) + rocket->svflags |= SVF_PROJECTILE; gi.linkentity (rocket); } @@ -1949,6 +1964,8 @@ void fire_bfg (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, f bfg->dmg = damage; bfg->dmg_radius = damage_radius; bfg->classname = "bfg blast"; + if (self->client) + bfg->svflags |= SVF_PROJECTILE; bfg->s.sound = gi.soundindex ("weapons/bfg__l1a.wav"); bfg->delay = level.time + 10.0; // initial time to expire @@ -2000,6 +2017,8 @@ void spawn_grenades(edict_t *ent, vec3_t origin, float time, int damage, int num grenade->dmg = damage; grenade->dmg_radius = 100; grenade->radius_dmg = damage; + if (ent->client) + grenade->svflags |= SVF_PROJECTILE; VectorSet(grenade->mins,-8,-8,-8); VectorSet(grenade->maxs,8,8,8); grenade->touch = Grenade_Touch; diff --git a/src/combat/weapons/mp/g_weapon_mp.c b/src/combat/weapons/mp/g_weapon_mp.c index 8d292957..8e914cbf 100644 --- a/src/combat/weapons/mp/g_weapon_mp.c +++ b/src/combat/weapons/mp/g_weapon_mp.c @@ -520,6 +520,8 @@ void fire_ionripper (edict_t *self, vec3_t start, vec3_t dir, int damage, int sp ion->think = ionripper_sparks; ion->dmg = damage; ion->dmg_radius = 100; + if (self->client) + ion->svflags |= SVF_PROJECTILE; gi.linkentity (ion); if (self->client) @@ -602,6 +604,8 @@ void fire_flechette (edict_t *self, vec3_t start, vec3_t dir, int damage, int sp flechette->think = G_FreeEdict; flechette->dmg = damage; flechette->dmg_radius = kick; + if (self->client) + flechette->svflags |= SVF_PROJECTILE; gi.linkentity(flechette); if (self->client) @@ -795,6 +799,8 @@ void fire_plasma (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed plasma->s.modelindex = gi.modelindex ("sprites/s_photon.sp2"); plasma->s.effects |= EF_PLASMA | EF_ANIM_ALLFAST; + if (self->client) + plasma->svflags |= SVF_PROJECTILE; if (self->client) check_dodge (self, plasma->s.origin, dir, speed, damage_radius); @@ -1040,6 +1046,8 @@ void fire_trap (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int spee trap->dmg = damage; trap->dmg_radius = damage_radius; trap->classname = "htrap"; + if (self->client) + trap->svflags |= SVF_PROJECTILE; trap->s.sound = gi.soundindex("weapons/traploop.wav"); trap->spawnflags = held ? 3 : 1; @@ -1294,6 +1302,8 @@ void fire_prox(edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed prox->dmg = damage; prox->dmg_radius = damage_radius; prox->classname = "prox"; + if (self->client) + prox->svflags |= SVF_PROJECTILE; gi.linkentity(prox); } @@ -1623,6 +1633,8 @@ void fire_tesla(edict_t *self, vec3_t start, vec3_t aimdir, int damage, int spee tesla->classname = "tesla"; tesla->clipmask = MASK_SHOT | CONTENTS_SLIME | CONTENTS_LAVA; tesla->mtype = M_TESLA; + if (self->client) + tesla->svflags |= SVF_PROJECTILE; gi.linkentity(tesla); diff --git a/src/combat/weapons/w_sword.c b/src/combat/weapons/w_sword.c index e2b97c88..c75d18a1 100644 --- a/src/combat/weapons/w_sword.c +++ b/src/combat/weapons/w_sword.c @@ -368,6 +368,8 @@ void fire_lance (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int bur lance->dmg = damage; lance->radius_dmg = burn_damage; lance->classname = "lance"; + if (self->client) + lance->svflags |= SVF_PROJECTILE; lance->delay = level.time + 10.0; //gi.setmodel(lance, "models/objects/javelin/tris.md2"); // lance->s.skinnum = 1; @@ -518,4 +520,4 @@ void Weapon_Sword (edict_t *ent) Weapon_Generic (ent, 4, 20, 52, 55, pause_frames, lance_frames, Weapon_Lance_Fire); else Weapon_Generic (ent, 4, 20, 52, 55, pause_frames, sword_frames, Weapon_Sword_Fire); -} \ No newline at end of file +} diff --git a/src/entities/drone/drone_ai.c b/src/entities/drone/drone_ai.c index eec2c4d0..467d6d77 100644 --- a/src/entities/drone/drone_ai.c +++ b/src/entities/drone/drone_ai.c @@ -705,7 +705,9 @@ void drone_ai_idle (edict_t *self) if (self->mtype != M_COMMANDER && self->mtype != M_GUNCMDR && self->mtype != M_DAEDALUS && self->mtype != M_GLADB && self->mtype != M_GLADC && self->mtype != M_CHICK_HEAT && self->mtype != M_MEDIC_COMMANDER - && self->mtype != M_SOLDIER && self->mtype != M_STALKER) + && self->mtype != M_SOLDIER && self->mtype != M_SOLDIER_RIPPER + && self->mtype != M_SOLDIER_BLUEBLASTER && self->mtype != M_SOLDIER_LASER + && self->mtype != M_STALKER) self->s.skinnum &= ~2; } @@ -1909,7 +1911,9 @@ void drone_ai_run1 (edict_t *self, float dist) if (self->mtype != M_COMMANDER && self->mtype != M_GUNCMDR && self->mtype != M_DAEDALUS && self->mtype != M_GLADB && self->mtype != M_GLADC && self->mtype != M_CHICK_HEAT && self->mtype != M_MEDIC_COMMANDER - && self->mtype != M_SOLDIER && self->mtype != M_STALKER) + && self->mtype != M_SOLDIER && self->mtype != M_SOLDIER_RIPPER + && self->mtype != M_SOLDIER_BLUEBLASTER && self->mtype != M_SOLDIER_LASER + && self->mtype != M_STALKER) self->s.skinnum &= ~2; } @@ -2164,18 +2168,143 @@ void drone_togglelight (edict_t *self) } } +#define DRONE_PROJECTILE_DODGE_RANGE 512 +#define DRONE_PROJECTILE_DODGE_MAX 256 +#define DRONE_PROJECTILE_DODGE_MIN_ETA FRAMETIME +#define DRONE_PROJECTILE_DODGE_MAX_ETA 2.5f +#define DRONE_PROJECTILE_DODGE_DIRECT_LEAD 0.5f +#define DRONE_PROJECTILE_DODGE_RADIUS_LEAD 0.45f + +static qboolean drone_projectile_infront(edict_t *self, edict_t *projectile) +{ + vec3_t forward, vec; + + AngleVectors(self->s.angles, forward, NULL, NULL); + VectorSubtract(projectile->s.origin, self->s.origin, vec); + VectorNormalize(vec); + + return DotProduct(vec, forward) > 0.35f; +} + +static qboolean drone_projectile_is_explosive(edict_t *projectile) +{ + if (!projectile->classname) + return projectile->dmg_radius > 0; + + if (projectile->dmg_radius > 0) + return true; + + return !Q_strcasecmp(projectile->classname, "rocket") || + !Q_strcasecmp(projectile->classname, "smartrocket") || + !Q_strcasecmp(projectile->classname, "lockon rocket") || + !Q_strcasecmp(projectile->classname, "grenade") || + !Q_strcasecmp(projectile->classname, "hgrenade") || + !Q_strcasecmp(projectile->classname, "prox") || + !Q_strcasecmp(projectile->classname, "htrap") || + !Q_strcasecmp(projectile->classname, "tesla") || + !Q_strcasecmp(projectile->classname, "bfg blast"); +} + +static edict_t *drone_projectile_attacker(edict_t *projectile) +{ + if (G_EntExists(projectile->owner)) + return projectile->owner; + if (G_EntExists(projectile->creator)) + return projectile->creator; + if (G_EntExists(projectile->activator)) + return projectile->activator; + if (G_EntExists(projectile->teammaster)) + return projectile->teammaster; + + return NULL; +} + +static void drone_check_active_projectile_dodge(edict_t *self) +{ + edict_t *touch[DRONE_PROJECTILE_DODGE_MAX]; + vec3_t mins, maxs, end; + int area_pass, i, num; + + if (level.time < self->monsterinfo.dodge_time) + return; + + VectorSet(mins, self->absmin[0] - DRONE_PROJECTILE_DODGE_RANGE, + self->absmin[1] - DRONE_PROJECTILE_DODGE_RANGE, + self->absmin[2] - DRONE_PROJECTILE_DODGE_RANGE); + VectorSet(maxs, self->absmax[0] + DRONE_PROJECTILE_DODGE_RANGE, + self->absmax[1] + DRONE_PROJECTILE_DODGE_RANGE, + self->absmax[2] + DRONE_PROJECTILE_DODGE_RANGE); + + for (area_pass = 0; area_pass < 2; ++area_pass) + { + num = gi.BoxEdicts(mins, maxs, touch, DRONE_PROJECTILE_DODGE_MAX, area_pass ? AREA_TRIGGERS : AREA_SOLID); + for (i = 0; i < num; ++i) + { + edict_t *projectile = touch[i]; + edict_t *attacker; + trace_t tr; + float speed, eta; + int radius; + + if (!projectile || !projectile->inuse) + continue; + if (!(projectile->svflags & SVF_PROJECTILE)) + continue; + attacker = drone_projectile_attacker(projectile); + if (!G_EntExists(attacker) || !attacker->client) + continue; + if (OnSameTeam(self, attacker)) + continue; + if (VectorLength(projectile->velocity) < 16) + continue; + if (!drone_projectile_infront(self, projectile)) + continue; + + VectorAdd(projectile->s.origin, projectile->velocity, end); + tr = gi.trace(projectile->s.origin, projectile->mins, projectile->maxs, end, projectile, projectile->clipmask ? projectile->clipmask : MASK_SHOT); + if (tr.ent != self) + continue; + + speed = VectorLength(projectile->velocity); + if (speed < 1) + continue; + + VectorSubtract(tr.endpos, projectile->s.origin, end); + eta = VectorLength(end) / speed; + if (eta < DRONE_PROJECTILE_DODGE_MIN_ETA || eta > DRONE_PROJECTILE_DODGE_MAX_ETA) + continue; + + radius = drone_projectile_is_explosive(projectile) ? (int)projectile->dmg_radius : 0; + if ((self->monsterinfo.aiflags & AI_DODGE) && self->monsterinfo.eta <= level.time + eta) + continue; + + self->monsterinfo.aiflags |= AI_DODGE; + self->monsterinfo.eta = level.time + eta; + self->monsterinfo.attacker = attacker; + VectorCopy(tr.endpos, self->monsterinfo.dir); + self->monsterinfo.radius = radius; + return; + } + } +} + void drone_dodgeprojectiles (edict_t *self) { const qboolean alive = self->health > 0; - const qboolean can_dodge = self->monsterinfo.dodge && (self->monsterinfo.aiflags & AI_DODGE); const qboolean stand_ground = (self->monsterinfo.aiflags & AI_STAND_GROUND); // dodge incoming projectiles - if (alive && can_dodge && !stand_ground) // don't dodge if we are holding position + if (alive && self->monsterinfo.dodge && !stand_ground) { - if (((self->monsterinfo.radius > 0) && ((level.time + 0.3) > self->monsterinfo.eta)) - || (level.time + FRAMETIME) > self->monsterinfo.eta) { - self->monsterinfo.dodge(self, self->monsterinfo.attacker, self->monsterinfo.dir, self->monsterinfo.radius); - self->monsterinfo.aiflags &= ~AI_DODGE; + drone_check_active_projectile_dodge(self); + + if (self->monsterinfo.aiflags & AI_DODGE) { + const float eta = self->monsterinfo.eta - level.time; + const float lead = (self->monsterinfo.radius > 0) ? DRONE_PROJECTILE_DODGE_RADIUS_LEAD : DRONE_PROJECTILE_DODGE_DIRECT_LEAD; + + if (eta < 0 || eta <= lead) { + self->monsterinfo.dodge(self, self->monsterinfo.attacker, self->monsterinfo.dir, self->monsterinfo.radius); + self->monsterinfo.aiflags &= ~AI_DODGE; + } } } } diff --git a/src/entities/drone/drone_arachnid.c b/src/entities/drone/drone_arachnid.c index fb9c97d9..c5eaa215 100644 --- a/src/entities/drone/drone_arachnid.c +++ b/src/entities/drone/drone_arachnid.c @@ -167,6 +167,9 @@ mmove_t arachnid_move_attack_up1 = { FRAME_rails_up1, FRAME_rails_up13, arachnid static void arachnid_melee_charge(edict_t *self) { + if (!G_ValidTarget(self, self->enemy, true, true) || entdist(self, self->enemy) > 96) + return; + gi.sound(self, CHAN_WEAPON, sound_melee, 1, ATTN_NORM, 0); } @@ -176,6 +179,11 @@ static void arachnid_melee_hit(edict_t *self) if (!G_ValidTarget(self, self->enemy, true, true)) return; + if (entdist(self, self->enemy) > 96) + { + self->monsterinfo.melee_finished = level.time + 1.0; + return; + } damage = M_MELEE_DMG_BASE + M_MELEE_DMG_ADDON * drone_damagelevel(self); if (M_MELEE_DMG_MAX && damage > M_MELEE_DMG_MAX) @@ -221,7 +229,10 @@ static void arachnid_attack(edict_t *self) static void arachnid_melee(edict_t *self) { - self->monsterinfo.currentmove = &arachnid_move_melee; + if (!G_ValidTarget(self, self->enemy, true, true) || entdist(self, self->enemy) > 96) + self->monsterinfo.currentmove = &arachnid_move_attack1; + else + self->monsterinfo.currentmove = &arachnid_move_melee; M_DelayNextAttack(self, 0, true); } @@ -348,15 +359,15 @@ void init_drone_arachnid(edict_t *self) self->movetype = MOVETYPE_STEP; self->solid = SOLID_BBOX; self->s.modelindex = gi.modelindex("models/monsters/arachnid/tris.md2"); - VectorSet(self->mins, -48, -48, -20); - VectorSet(self->maxs, 48, 48, 48); + VectorSet(self->mins, -36, -36, -18); + VectorSet(self->maxs, 36, 36, 42); self->health = M_ARACHNID_INITIAL_HEALTH + M_ARACHNID_ADDON_HEALTH * self->monsterinfo.level; self->max_health = self->health; self->gib_health = -200; self->mass = 450; self->mtype = M_ARACHNID; - self->s.scale = 0.65f; + self->s.scale = 0.75f; self->monsterinfo.control_cost = M_GLADIATOR_CONTROL_COST; self->monsterinfo.cost = M_DEFAULT_COST; self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD; @@ -380,6 +391,6 @@ void init_drone_arachnid(edict_t *self) gi.linkentity(self); self->monsterinfo.currentmove = &arachnid_move_stand; - self->monsterinfo.scale = MODEL_SCALE; + self->monsterinfo.scale = MODEL_SCALE * self->s.scale; self->nextthink = level.time + FRAMETIME; } diff --git a/src/entities/drone/drone_berserk.c b/src/entities/drone/drone_berserk.c index fa3f4718..35d1a8a1 100644 --- a/src/entities/drone/drone_berserk.c +++ b/src/entities/drone/drone_berserk.c @@ -17,6 +17,10 @@ static int sound_punch; static int sound_sight; static int sound_search; +void drone_ai_run_slide(edict_t *self, float dist); +static void berserk_ai_dodge_slide(edict_t *self, float dist); +static void berserk_duck_up(edict_t *self); + void berserk_sight (edict_t *self, edict_t *other) { @@ -119,6 +123,8 @@ mmove_t berserk_move_run1 = {FRAME_run1, FRAME_run6, berserk_frames_run1, NULL}; void berserk_run (edict_t *self) { + berserk_duck_up(self); + if (self->monsterinfo.aiflags & AI_STAND_GROUND) self->monsterinfo.currentmove = &berserk_move_stand; else @@ -317,6 +323,179 @@ mframe_t berserk_frames_pain_long[] = }; mmove_t berserk_move_pain_long = { FRAME_painb1, FRAME_painb20, berserk_frames_pain_long, berserk_run }; +#define BERSERK_SCALE(self) ((self)->s.scale > 0 ? (self)->s.scale : 1.0f) +#define BERSERK_STAND_MAX_Z_SCALED(self) (32.0f * BERSERK_SCALE(self)) +#define BERSERK_DUCK_MAX_Z_SCALED(self) (0.0f * BERSERK_SCALE(self)) + +static void berserk_duck_down(edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_DUCKED) + return; + if (!self->groundentity) + return; + + self->monsterinfo.aiflags |= AI_DUCKED; + self->maxs[2] = BERSERK_DUCK_MAX_Z_SCALED(self); + self->takedamage = DAMAGE_YES; + gi.linkentity(self); +} + +static void berserk_duck_hold(edict_t *self) +{ + if (self->monsterinfo.pausetime > level.time) + self->monsterinfo.nextframe = self->s.frame; +} + +static void berserk_duck_up(edict_t *self) +{ + vec3_t oldmaxs; + trace_t tr; + + if (!(self->monsterinfo.aiflags & AI_DUCKED) && self->maxs[2] == BERSERK_STAND_MAX_Z_SCALED(self)) + return; + + VectorCopy(self->maxs, oldmaxs); + self->maxs[2] = BERSERK_STAND_MAX_Z_SCALED(self); + + tr = gi.trace(self->s.origin, self->mins, self->maxs, self->s.origin, self, MASK_MONSTERSOLID); + + if (tr.startsolid || tr.allsolid) + { + VectorCopy(oldmaxs, self->maxs); + self->monsterinfo.aiflags |= AI_DUCKED; + return; + } + + self->monsterinfo.aiflags &= ~AI_DUCKED; + self->takedamage = DAMAGE_AIM; + gi.linkentity(self); +} + +mframe_t berserk_frames_dodge_slide[] = +{ + berserk_ai_dodge_slide, 21, NULL, + berserk_ai_dodge_slide, 11, NULL, + berserk_ai_dodge_slide, 21, NULL, + berserk_ai_dodge_slide, 25, NULL, + berserk_ai_dodge_slide, 18, NULL, + berserk_ai_dodge_slide, 19, NULL +}; +mmove_t berserk_move_dodge_slide = { FRAME_run1, FRAME_run6, berserk_frames_dodge_slide, berserk_run }; + +mframe_t berserk_frames_dodge_duck[] = +{ + ai_move, 21, berserk_duck_down, + ai_move, 28, NULL, + ai_move, 20, NULL, + ai_move, 12, NULL, + ai_move, 7, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, berserk_duck_hold, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, berserk_duck_up, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t berserk_move_dodge_duck = { FRAME_fall2, FRAME_fall18, berserk_frames_dodge_duck, berserk_run }; + +mframe_t berserk_frames_dodge_duck_short[] = +{ + ai_move, 0, berserk_duck_down, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, berserk_duck_hold, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, berserk_duck_up, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t berserk_move_dodge_duck_short = { FRAME_duck1, FRAME_duck10, berserk_frames_dodge_duck_short, berserk_run }; + +static qboolean berserk_is_dodge_move(edict_t *self) +{ + return self->monsterinfo.currentmove == &berserk_move_dodge_slide || + self->monsterinfo.currentmove == &berserk_move_dodge_duck || + self->monsterinfo.currentmove == &berserk_move_dodge_duck_short; +} + +static void berserk_ai_dodge_slide(edict_t *self, float dist) +{ + if (!G_EntIsAlive(self->enemy) && G_EntIsAlive(self->monsterinfo.attacker)) + self->enemy = self->monsterinfo.attacker; + if (!G_EntIsAlive(self->enemy)) + return; + + drone_ai_run_slide(self, dist); +} + +void berserk_dodge(edict_t *self, edict_t *attacker, vec3_t dir, int radius) +{ + if (level.time < self->monsterinfo.dodge_time) + return; + if (!attacker) + return; + if (OnSameTeam(self, attacker)) + return; + if (berserk_is_dodge_move(self) || + self->monsterinfo.currentmove == &berserk_move_attack_strike || + self->monsterinfo.currentmove == &berserk_move_pain_long) + return; + if (!self->groundentity) + return; + + if (!G_EntIsAlive(self->enemy)) + { + if (!G_EntIsAlive(attacker)) + return; + self->enemy = attacker; + } + self->monsterinfo.attacker = attacker; + + if (random() > 0.5f) + return; + + if (radius || random() < 0.05f) + { + self->monsterinfo.pausetime = level.time + 0.5f; + + if (radius && random() < 0.5f) + { + self->monsterinfo.currentmove = &berserk_move_dodge_duck_short; + berserk_duck_down(self); + self->monsterinfo.dodge_time = level.time + 1.0f; + } + else + { + self->monsterinfo.currentmove = &berserk_move_dodge_duck; + berserk_duck_down(self); + self->monsterinfo.dodge_time = level.time + 1.7f; + } + + return; + } + + if (random() < 0.25f) + { + self->monsterinfo.pausetime = level.time + 0.5f; + self->monsterinfo.currentmove = &berserk_move_dodge_duck_short; + berserk_duck_down(self); + self->monsterinfo.dodge_time = level.time + 1.0f; + return; + } + + self->monsterinfo.lefty = 1 - self->monsterinfo.lefty; + self->monsterinfo.currentmove = &berserk_move_dodge_slide; + self->monsterinfo.dodge_time = level.time + 0.4f + random() * 1.6f; +} + void berserk_pain(edict_t* self, edict_t* other, float kick, int damage) { const double rng = random(); @@ -325,7 +504,8 @@ void berserk_pain(edict_t* self, edict_t* other, float kick, int damage) // we're already in a pain state if (self->monsterinfo.currentmove == &berserk_move_pain_long || - self->monsterinfo.currentmove == &berserk_move_pain_short) + self->monsterinfo.currentmove == &berserk_move_pain_short || + berserk_is_dodge_move(self)) return; // monster players don't get pain state induced @@ -497,7 +677,7 @@ void init_drone_berserk (edict_t *self) self->monsterinfo.stand = berserk_stand; self->monsterinfo.walk = berserk_walk; self->monsterinfo.run = berserk_run; - //self->monsterinfo.dodge = NULL; + self->monsterinfo.dodge = berserk_dodge; self->monsterinfo.attack = berserk_attack; self->monsterinfo.melee = berserk_melee; self->monsterinfo.sight = berserk_sight; diff --git a/src/entities/drone/drone_bitch.c b/src/entities/drone/drone_bitch.c index 80f2f72d..43e6c329 100644 --- a/src/entities/drone/drone_bitch.c +++ b/src/entities/drone/drone_bitch.c @@ -15,7 +15,10 @@ void mychick_reslash(edict_t *self); void mychick_rerocket(edict_t *self); void mychick_attack1(edict_t *self); void mychick_continue (edict_t *self); +extern mmove_t mychick_move_start_attack1; +extern mmove_t mychick_move_attack1; extern mmove_t mychick_move_end_attack1; +void drone_ai_run_slide(edict_t *self, float dist); static int sound_missile_prelaunch; static int sound_missile_launch; @@ -179,6 +182,31 @@ mframe_t mychick_frames_run [] = }; mmove_t mychick_move_run = {FRAME_walk11, FRAME_walk20, mychick_frames_run, NULL}; +static void mychick_ai_dodge_slide(edict_t *self, float dist) +{ + if (!G_EntIsAlive(self->enemy) && G_EntIsAlive(self->monsterinfo.attacker)) + self->enemy = self->monsterinfo.attacker; + if (!G_EntIsAlive(self->enemy)) + return; + + drone_ai_run_slide(self, dist); +} + +mframe_t mychick_frames_dodge_slide[] = +{ + mychick_ai_dodge_slide, 16, NULL, + mychick_ai_dodge_slide, 16, NULL, + mychick_ai_dodge_slide, 14, NULL, + mychick_ai_dodge_slide, 13, NULL, + mychick_ai_dodge_slide, 15, NULL, + mychick_ai_dodge_slide, 12, NULL, + mychick_ai_dodge_slide, 11, NULL, + mychick_ai_dodge_slide, 10, NULL, + mychick_ai_dodge_slide, 8, NULL, + mychick_ai_dodge_slide, 6, NULL +}; +mmove_t mychick_move_dodge_slide = {FRAME_walk11, FRAME_walk20, mychick_frames_dodge_slide, mychick_run}; + void mychick_run (edict_t *self) { if (self->monsterinfo.aiflags & AI_STAND_GROUND) @@ -314,6 +342,10 @@ void mychick_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int dama } } +#define MYCHICK_STAND_SCALE(self) ((self)->s.scale > 0 ? (self)->s.scale : 1.0f) +#define MYCHICK_STAND_MAX_Z(self) (56 * MYCHICK_STAND_SCALE(self)) +#define MYCHICK_DUCK_MAX_Z 0 + void mychick_duck_down (edict_t *self) { if (self->monsterinfo.aiflags & AI_DUCKED) @@ -322,29 +354,54 @@ void mychick_duck_down (edict_t *self) return; self->monsterinfo.aiflags |= AI_DUCKED; - self->maxs[2] = 0; + self->maxs[2] = MYCHICK_DUCK_MAX_Z; self->takedamage = DAMAGE_YES; - gi.linkentity (self); + gi.linkentity(self); } void mychick_duck_up (edict_t *self) { + vec3_t oldmaxs; + trace_t tr; + + if (!(self->monsterinfo.aiflags & AI_DUCKED) && + self->maxs[2] == MYCHICK_STAND_MAX_Z(self)) + return; + + VectorCopy(self->maxs, oldmaxs); + self->maxs[2] = MYCHICK_STAND_MAX_Z(self); + + tr = gi.trace(self->s.origin, self->mins, self->maxs, self->s.origin, self, MASK_MONSTERSOLID); + + if (tr.startsolid || tr.allsolid) + { + VectorCopy(oldmaxs, self->maxs); + self->monsterinfo.aiflags |= AI_DUCKED; + return; + } + self->monsterinfo.aiflags &= ~AI_DUCKED; - self->maxs[2] = 32; self->takedamage = DAMAGE_AIM; - VectorClear(self->velocity); - gi.linkentity (self); + gi.linkentity(self); +} + +void mychick_duck_hold (edict_t *self) +{ + if (self->monsterinfo.pausetime > level.time) + self->monsterinfo.nextframe = self->s.frame; } mframe_t mychick_frames_duck [] = { ai_move, 0, mychick_duck_down, - ai_move, 0, NULL, - ai_move, 0, mychick_duck_up, - ai_move, 0, NULL, - ai_move, 0, NULL + ai_move, 1, NULL, + ai_move, 4, mychick_duck_hold, + ai_move, -4, NULL, + ai_move, -5, mychick_duck_up, + ai_move, 3, NULL, + ai_move, 1, NULL }; -mmove_t mychick_move_duck = {FRAME_duck03, FRAME_duck07, mychick_frames_duck, mychick_run}; +mmove_t mychick_move_duck = {FRAME_duck01, FRAME_duck07, mychick_frames_duck, mychick_run}; void mychick_jump_takeoff (edict_t *self) { @@ -401,29 +458,74 @@ void mychick_leap (edict_t *self) self->monsterinfo.currentmove = &mychick_move_leap; } -void mychick_dodge (edict_t *self, edict_t *attacker, vec3_t dir, int radius) +static qboolean mychick_is_dodge_move(edict_t *self) +{ + return self->monsterinfo.currentmove == &mychick_move_duck || + self->monsterinfo.currentmove == &mychick_move_leap || + self->monsterinfo.currentmove == &mychick_move_dodge_slide; +} + +static qboolean mychick_is_uninterruptible_attack(edict_t *self) +{ + return self->monsterinfo.currentmove == &mychick_move_start_attack1 || + self->monsterinfo.currentmove == &mychick_move_attack1; +} + +static qboolean mychick_dodge_hit_low(edict_t *self, vec3_t dir) +{ + const float duck_height = self->absmax[2] - 33; + + return dir[2] > self->absmin[2] && dir[2] <= duck_height; +} + +static void mychick_start_duck(edict_t *self) +{ + self->monsterinfo.pausetime = level.time + 0.5f; + self->monsterinfo.currentmove = &mychick_move_duck; + mychick_duck_down(self); + self->monsterinfo.dodge_time = level.time + 2.0f; +} + +static void mychick_dodge (edict_t *self, edict_t *attacker, vec3_t dir, int radius) { - if (random() > 0.9) - return; - if (!G_GetClient(self)) - return; if (level.time < self->monsterinfo.dodge_time) return; + if (!attacker) + return; if (OnSameTeam(self, attacker)) return; + if (mychick_is_dodge_move(self) || mychick_is_uninterruptible_attack(self)) + return; - if (!self->enemy && G_EntIsAlive(attacker)) + if (!G_EntIsAlive(self->enemy) && G_EntIsAlive(attacker)) self->enemy = attacker; - if (!radius) + + if (random() > 0.9f) + return; + + self->monsterinfo.attacker = attacker; + if (radius) { - self->monsterinfo.currentmove = &mychick_move_duck; - self->monsterinfo.dodge_time = level.time + 2.0; + mychick_leap(self); + self->monsterinfo.dodge_time = level.time + 3.0f; + return; } - else + + if (!mychick_dodge_hit_low(self, dir)) { - mychick_leap(self); - self->monsterinfo.dodge_time = level.time + 3.0; + mychick_start_duck(self); + return; } + + if (!(self->monsterinfo.aiflags & AI_STAND_GROUND)) + { + self->monsterinfo.lefty = 1 - self->monsterinfo.lefty; + self->monsterinfo.currentmove = &mychick_move_dodge_slide; + self->monsterinfo.dodge_time = level.time + 1.0f; + return; + } + + mychick_start_duck(self); } void fire_meteor (edict_t *self, vec3_t end, int damage, int radius, int speed); @@ -844,7 +946,8 @@ void mychick_pain(edict_t* self, edict_t* other, float kick, int damage) // we're already in a pain state if (self->monsterinfo.currentmove == &mychick_move_pain_long || self->monsterinfo.currentmove == &mychick_move_pain_short1 || - self->monsterinfo.currentmove == &mychick_move_pain_short2) + self->monsterinfo.currentmove == &mychick_move_pain_short2 || + mychick_is_dodge_move(self)) return; // monster players don't get pain state induced diff --git a/src/entities/drone/drone_brain.c b/src/entities/drone/drone_brain.c index 3989eeec..ab18756b 100644 --- a/src/entities/drone/drone_brain.c +++ b/src/entities/drone/drone_brain.c @@ -27,6 +27,7 @@ static int sound_thud; void mybrain_attack (edict_t *self); void mybrain_attack3 (edict_t *self); +static qboolean mybrain_is_dodge_move(edict_t *self); void mybrain_sight (edict_t *self, edict_t *other) { @@ -241,7 +242,8 @@ void mybrain_pain(edict_t* self, edict_t* other, float kick, int damage) if (self->monsterinfo.currentmove == &mybrain_move_pain_long || self->monsterinfo.currentmove == &mybrain_move_pain_short1 || self->monsterinfo.currentmove == &mybrain_move_pain_short2 || - self->monsterinfo.currentmove == &mybrain_move_defense) + self->monsterinfo.currentmove == &mybrain_move_defense || + mybrain_is_dodge_move(self)) return; // monster players don't get pain state induced @@ -299,6 +301,12 @@ void mybrain_duck_up (edict_t *self) gi.linkentity (self); } +void mybrain_duck_hold (edict_t *self) +{ + if (self->monsterinfo.pausetime > level.time) + self->monsterinfo.nextframe = self->s.frame; +} + void mybrain_jump_takeoff (edict_t *self) { vec3_t v; @@ -335,14 +343,14 @@ void mybrain_jump_hold (edict_t *self) mframe_t mybrain_frames_duck [] = { - ai_move, 0, mybrain_duck_down, - ai_move, 0, NULL,//mybrain_duck_down, - ai_move, 0, NULL, + ai_move, 1, mybrain_duck_down, + ai_move, 1, NULL, + ai_move, 1, mybrain_duck_hold, ai_move, 0, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, ai_move, 0, mybrain_duck_up, - ai_move, 0, NULL, - ai_move, 0, NULL, - ai_move, 0, NULL + ai_move, -1, NULL }; mmove_t mybrain_move_duck = {FRAME_duck01, FRAME_duck08, mybrain_frames_duck, mybrain_run}; @@ -457,22 +465,34 @@ void mybrain_jump (edict_t *self) self->monsterinfo.currentmove = &mybrain_move_jump; } +static qboolean mybrain_is_dodge_move(edict_t *self) +{ + return self->monsterinfo.currentmove == &mybrain_move_duck || + self->monsterinfo.currentmove == &mybrain_move_jump || + self->monsterinfo.currentmove == &mybrain_move_jumpattack; +} + void mybrain_dodge (edict_t *self, edict_t *attacker, vec3_t dir, int radius) { if (random() > 0.9) return; - if (!G_GetClient(self)) - return; if (level.time < self->monsterinfo.dodge_time) return; + if (!attacker) + return; if (OnSameTeam(self, attacker)) return; + if (mybrain_is_dodge_move(self)) + return; - if (!self->enemy && G_EntIsAlive(attacker)) + self->monsterinfo.attacker = attacker; + if (!G_EntIsAlive(self->enemy) && G_EntIsAlive(attacker)) self->enemy = attacker; if (!radius) { + self->monsterinfo.pausetime = level.time + 0.5; self->monsterinfo.currentmove = &mybrain_move_duck; + mybrain_duck_down(self); self->monsterinfo.dodge_time = level.time + 2.0; } else @@ -724,6 +744,7 @@ mmove_t mybrain_move_attack3 = {FRAME_attak205, FRAME_attak217, mybrain_frames_a void mybrain_attack3 (edict_t *self) { + gi.sound(self, CHAN_WEAPON, sound_tentacles_extend, 1, ATTN_NORM, 0); self->monsterinfo.currentmove = &mybrain_move_attack3; } diff --git a/src/entities/drone/drone_daedalus.c b/src/entities/drone/drone_daedalus.c index ae913228..920852dd 100644 --- a/src/entities/drone/drone_daedalus.c +++ b/src/entities/drone/drone_daedalus.c @@ -185,6 +185,11 @@ static void daedalus_die(edict_t *self, edict_t *inflictor, edict_t *attacker, i self->deadflag = DEAD_DEAD; self->takedamage = DAMAGE_YES; + self->flags &= ~FL_FLY; + self->movetype = MOVETYPE_TOSS; + self->gravity = 1.0; + if (self->velocity[2] > -120) + self->velocity[2] = -120; self->monsterinfo.currentmove = &hover_move_death1; if (self->activator && !self->activator->client) diff --git a/src/entities/drone/drone_gekk.c b/src/entities/drone/drone_gekk.c index a00404bf..18789c5f 100644 --- a/src/entities/drone/drone_gekk.c +++ b/src/entities/drone/drone_gekk.c @@ -25,6 +25,7 @@ static int sound_thud; void drone_ai_stand(edict_t *self, float dist); void drone_ai_run(edict_t *self, float dist); void drone_ai_walk(edict_t *self, float dist); +void drone_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf); static void gekk_stand(edict_t *self); static void gekk_walk(edict_t *self); @@ -43,6 +44,14 @@ extern mmove_t gekk_move_leapatk; extern void fire_acid(edict_t *self, vec3_t start, vec3_t aimdir, int projectile_damage, float radius, int speed, int acid_damage, float acid_duration, int gas_damage, float gas_radius, float gas_duration); +static void gekk_set_leap_cooldown(edict_t *self, float base, float extra) +{ + float cooldown = level.time + base + random() * extra; + + if (self->monsterinfo.dodge_time < cooldown) + self->monsterinfo.dodge_time = cooldown; +} + static void gekk_step(edict_t *self) { const float r = random(); @@ -465,10 +474,87 @@ static void gekk_melee(edict_t *self) self->monsterinfo.currentmove = &gekk_move_attack2; } +static qboolean gekk_can_leap(edict_t *self) +{ + vec3_t v; + float distance; + + if (!G_ValidTarget(self, self->enemy, true, true)) + return false; + if (!self->groundentity) + return false; + if (self->monsterinfo.melee_finished > level.time) + return false; + if (self->monsterinfo.dodge_time > level.time) + return false; + if (!visible(self, self->enemy)) + return false; + + VectorSubtract(self->s.origin, self->enemy->s.origin, v); + v[2] = 0; + distance = VectorLength(v); + + if (distance < 100) + { + if (self->absmax[2] <= self->enemy->absmin[2]) + return false; + } + else if (self->absmin[2] + 125 < self->enemy->absmin[2]) + return false; + + if (self->waterlevel > 1 && random() < 0.2f) + return false; + + return true; +} + +static qboolean gekk_use_high_leap(edict_t *self) +{ + vec3_t v; + float distance; + + if (!G_EntExists(self->enemy)) + return false; + if (self->absmin[2] + 125 < self->enemy->absmin[2]) + return false; + + VectorSubtract(self->s.origin, self->enemy->s.origin, v); + v[2] = 0; + distance = VectorLength(v); + if (distance < 100) + return false; + + return random() >= (self->waterlevel > 1 ? 0.2f : 0.9f); +} + +static void gekk_jump_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (self->health <= 0) + { + self->touch = drone_touch; + return; + } + + if (other && other->takedamage) + return; + + if (!self->groundentity) + { + self->velocity[0] = 0; + self->velocity[1] = 0; + if (self->velocity[2] > -200) + self->velocity[2] = -200; + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + self->monsterinfo.nextframe = FRAME_leapatk_11; + gekk_set_leap_cooldown(self, 4.0, 2.0); + } + + self->touch = drone_touch; +} + static void gekk_jump_takeoff(edict_t *self) { - vec3_t forward, start; - int speed = 700; + vec3_t forward; if (!G_EntExists(self->enemy)) return; @@ -477,11 +563,21 @@ static void gekk_jump_takeoff(edict_t *self) self->lastsound = level.framenum; self->s.origin[2] += 1; self->groundentity = NULL; - MonsterAim(self, -1, speed, false, 0, forward, start); - VectorScale(forward, speed, self->velocity); - self->velocity[2] += 300; + AngleVectors(self->s.angles, forward, NULL, NULL); + if (gekk_use_high_leap(self)) + { + VectorScale(forward, 700, self->velocity); + self->velocity[2] = 250; + } + else + { + VectorScale(forward, 250, self->velocity); + self->velocity[2] = 400; + } self->monsterinfo.pausetime = level.time + 2.0; self->monsterinfo.aiflags |= AI_HOLD_FRAME; + self->touch = gekk_jump_touch; + gekk_set_leap_cooldown(self, 1.0, 0.5); } static void gekk_stop_skid(edict_t *self) @@ -490,6 +586,7 @@ static void gekk_stop_skid(edict_t *self) { VectorClear(self->velocity); self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + self->touch = drone_touch; } } @@ -498,6 +595,13 @@ static void gekk_check_landing(edict_t *self) if (self->groundentity || (self->waterlevel > 1) || (level.time > self->monsterinfo.pausetime)) { self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + self->monsterinfo.melee_finished = level.time + 1.5; + self->monsterinfo.attack_finished = level.time + 1.0; + self->monsterinfo.nextframe = FRAME_leapatk_11; + self->touch = drone_touch; + gekk_set_leap_cooldown(self, 1.5, 1.0); + if (!self->groundentity && self->velocity[2] > -200) + self->velocity[2] = -200; if (self->waterlevel > 1) gekk_land_to_water(self); return; @@ -574,8 +678,6 @@ mmove_t gekk_move_spit = { FRAME_spit_01, FRAME_spit_07, gekk_frames_spit, gekk_ static void gekk_attack(edict_t *self) { - float dist; - if (gekk_should_swim(self)) { if (G_EntExists(self->enemy) && self->enemy->waterlevel < WATER_WAIST) @@ -592,13 +694,16 @@ static void gekk_attack(edict_t *self) if (!G_EntExists(self->enemy)) return; - dist = entdist(self, self->enemy); - if (visible(self, self->enemy) && dist <= 512) + if (gekk_can_leap(self) && random() >= 0.35f) + { self->monsterinfo.currentmove = &gekk_move_leapatk; + self->monsterinfo.melee_finished = level.time + 3.0; + } else + { self->monsterinfo.currentmove = &gekk_move_spit; - - self->monsterinfo.melee_finished = level.time + 1.0; + self->monsterinfo.melee_finished = level.time + 0.5; + } M_DelayNextAttack(self, 1.0 + random(), true); } diff --git a/src/entities/drone/drone_guncmdr.c b/src/entities/drone/drone_guncmdr.c index 366954ef..97354867 100644 --- a/src/entities/drone/drone_guncmdr.c +++ b/src/entities/drone/drone_guncmdr.c @@ -25,6 +25,11 @@ static int sound_thud; #define GUNCMDR_GRENADE_SPEED 600 #define GUNCMDR_WALK_SPEED_MULT 2.0f +#define GUNCMDR_STAND_MAXZ_SCALED(self) (36.0f * ((self)->s.scale > 0 ? (self)->s.scale : 1.0f)) +#define GUNCMDR_DUCK_MAXZ_SCALED(self) (4.0f * ((self)->s.scale > 0 ? (self)->s.scale : 1.0f)) +#define GUNCMDR_DEAD_MAXZ_SCALED(self) (-8.0f * ((self)->s.scale > 0 ? (self)->s.scale : 1.0f)) +#define GUNCMDR_SHRINK_MAXZ_SCALED(self) (-4.0f * ((self)->s.scale > 0 ? (self)->s.scale : 1.0f)) + static void guncmdr_stand(edict_t *self); static void guncmdr_run(edict_t *self); static void guncmdr_attack(edict_t *self); @@ -33,7 +38,17 @@ static void guncmdr_refire_chain(edict_t *self); static void guncmdr_grenade_finished(edict_t *self); static void guncmdr_grenade_mortar_resume(edict_t *self); static void guncmdr_resume_back_attack(edict_t *self); +static void guncmdr_duck_down(edict_t *self); +static void guncmdr_duck_up(edict_t *self); +static mmove_t guncmdr_move_duckstep_dodge; extern mmove_t guncmdr_move_fidget; +extern mmove_t guncmdr_move_death1; +extern mmove_t guncmdr_move_death2; +extern mmove_t guncmdr_move_death6; + +void drone_ai_run_slide(edict_t *self, float dist); +static void guncmdr_ai_dodge_slide(edict_t *self, float dist); +extern mmove_t guncmdr_move_dodge_slide; static void guncmdr_idle_sound(edict_t *self) { @@ -233,6 +248,8 @@ mmove_t guncmdr_move_run = { FRAME_c_run101, FRAME_c_run106, guncmdr_frames_run, static void guncmdr_run(edict_t *self) { + guncmdr_duck_up(self); + if (self->monsterinfo.aiflags & AI_STAND_GROUND) self->monsterinfo.currentmove = &guncmdr_move_stand; else @@ -252,6 +269,8 @@ static void GunnerCmdrFire(edict_t *self) flash_number = MZ2_GUNNER_MACHINEGUN_2; else if (self->s.frame >= FRAME_c_run201 && self->s.frame <= FRAME_c_run206) flash_number = MZ2_GUNNER_MACHINEGUN_1 + (self->s.frame - FRAME_c_run201); + else if (self->s.frame >= FRAME_c_duckstep01 && self->s.frame <= FRAME_c_duckstep06) + flash_number = MZ2_GUNCMDR_CHAINGUN_2; else flash_number = MZ2_GUNNER_MACHINEGUN_1 + ((self->s.frame - FRAME_c_attack107) % 6); @@ -373,9 +392,20 @@ static void GunnerCmdrGrenade(edict_t *self) monster_fire_grenade(self, start, forward, damage, speed, flash_number); } +static void guncmdr_ai_duckstep_slide(edict_t *self, float dist) +{ + if (!G_EntIsAlive(self->enemy) && G_EntIsAlive(self->monsterinfo.attacker)) + self->enemy = self->monsterinfo.attacker; + if (!G_EntIsAlive(self->enemy)) + return; + + guncmdr_duck_down(self); + drone_ai_run_slide(self, dist); +} + mframe_t guncmdr_frames_attack_mortar[] = { - ai_charge, 0, NULL, + ai_charge, 0, guncmdr_duck_down, ai_charge, 0, NULL, ai_charge, 0, NULL, ai_charge, 0, NULL, @@ -393,7 +423,7 @@ mframe_t guncmdr_frames_attack_mortar[] = ai_charge, 0, NULL, ai_charge, 0, NULL, ai_charge, 0, NULL, - ai_charge, 0, NULL, + ai_charge, 0, guncmdr_duck_up, ai_charge, 0, NULL, ai_charge, 0, NULL, ai_charge, 0, NULL @@ -408,12 +438,12 @@ static void guncmdr_grenade_mortar_resume(edict_t *self) mframe_t guncmdr_frames_attack_mortar_dodge[] = { - ai_charge, 11, NULL, - ai_charge, 12, NULL, - ai_charge, 16, NULL, - ai_charge, 16, NULL, - ai_charge, 12, NULL, - ai_charge, 11, NULL + guncmdr_ai_duckstep_slide, 11, NULL, + guncmdr_ai_duckstep_slide, 12, GunnerCmdrFire, + guncmdr_ai_duckstep_slide, 16, NULL, + guncmdr_ai_duckstep_slide, 16, GunnerCmdrFire, + guncmdr_ai_duckstep_slide, 12, NULL, + guncmdr_ai_duckstep_slide, 11, GunnerCmdrFire }; mmove_t guncmdr_move_attack_mortar_dodge = { FRAME_c_duckstep01, FRAME_c_duckstep06, guncmdr_frames_attack_mortar_dodge, guncmdr_grenade_mortar_resume }; @@ -511,26 +541,35 @@ static void guncmdr_duck_down(edict_t *self) return; self->monsterinfo.aiflags |= AI_DUCKED; - self->maxs[2] = 4; + self->maxs[2] = GUNCMDR_DUCK_MAXZ_SCALED(self); self->takedamage = DAMAGE_YES; gi.linkentity(self); } static void guncmdr_duck_up(edict_t *self) { + if (!(self->monsterinfo.aiflags & AI_DUCKED) && + self->maxs[2] == GUNCMDR_STAND_MAXZ_SCALED(self)) + return; + self->monsterinfo.aiflags &= ~AI_DUCKED; - self->maxs[2] = 36; + self->maxs[2] = GUNCMDR_STAND_MAXZ_SCALED(self); self->takedamage = DAMAGE_AIM; - VectorClear(self->velocity); gi.linkentity(self); } +static void guncmdr_duck_hold(edict_t *self) +{ + if (self->monsterinfo.pausetime > level.time) + self->monsterinfo.nextframe = self->s.frame; +} + mframe_t guncmdr_frames_duck_attack[] = { ai_move, 3.6, NULL, ai_move, 5.6, guncmdr_duck_down, ai_move, 8.4, NULL, - ai_move, 2.0, NULL, + ai_move, 2.0, guncmdr_duck_hold, ai_charge, 0, NULL, ai_charge, 0, NULL, ai_charge, 0, NULL, @@ -550,6 +589,31 @@ mframe_t guncmdr_frames_duck_attack[] = }; mmove_t guncmdr_move_duck_attack = { FRAME_c_attack901, FRAME_c_attack919, guncmdr_frames_duck_attack, guncmdr_run }; +static void guncmdr_finish_duckstep_dodge(edict_t *self) +{ + if (self->monsterinfo.nextattack > 0 && G_ValidTarget(self, self->enemy, true, true)) + { + self->monsterinfo.nextattack--; + self->monsterinfo.currentmove = &guncmdr_move_duckstep_dodge; + return; + } + + self->monsterinfo.nextattack = 0; + guncmdr_duck_up(self); + guncmdr_run(self); +} + +mframe_t guncmdr_frames_duckstep_dodge[] = +{ + guncmdr_ai_duckstep_slide, 11, NULL, + guncmdr_ai_duckstep_slide, 12, GunnerCmdrFire, + guncmdr_ai_duckstep_slide, 16, NULL, + guncmdr_ai_duckstep_slide, 16, GunnerCmdrFire, + guncmdr_ai_duckstep_slide, 12, NULL, + guncmdr_ai_duckstep_slide, 11, GunnerCmdrFire +}; +static mmove_t guncmdr_move_duckstep_dodge = { FRAME_c_duckstep01, FRAME_c_duckstep06, guncmdr_frames_duckstep_dodge, guncmdr_finish_duckstep_dodge }; + static void guncmdr_jump_now(edict_t *self) { vec3_t forward; @@ -582,46 +646,128 @@ mframe_t guncmdr_frames_jump[] = }; mmove_t guncmdr_move_jump = { FRAME_c_jump01, FRAME_c_jump10, guncmdr_frames_jump, guncmdr_run }; -static void guncmdr_dodge(edict_t *self, edict_t *attacker, vec3_t dir, int radius) +static qboolean guncmdr_try_sidestep(edict_t *self); + +static qboolean guncmdr_is_dodge_move(edict_t *self) { - if (random() > 0.8) + return self->monsterinfo.currentmove == &guncmdr_move_fire_chain_dodge_left || + self->monsterinfo.currentmove == &guncmdr_move_fire_chain_dodge_right || + self->monsterinfo.currentmove == &guncmdr_move_attack_grenade_back_dodge_left || + self->monsterinfo.currentmove == &guncmdr_move_attack_grenade_back_dodge_right || + self->monsterinfo.currentmove == &guncmdr_move_attack_mortar_dodge || + self->monsterinfo.currentmove == &guncmdr_move_duckstep_dodge || + self->monsterinfo.currentmove == &guncmdr_move_dodge_slide || + self->monsterinfo.currentmove == &guncmdr_move_jump || + self->monsterinfo.currentmove == &guncmdr_move_duck_attack; +} + +static qboolean guncmdr_dodge_hit_low(edict_t *self, vec3_t dir) +{ + const float duck_height = self->absmax[2] - 33; + + return dir[2] > self->absmin[2] && dir[2] <= duck_height; +} + +static void guncmdr_start_duck_dodge(edict_t *self) +{ + if (!self->groundentity) return; + + self->monsterinfo.nextattack = 0; + self->monsterinfo.pausetime = level.time + 0.75f; + self->monsterinfo.currentmove = &guncmdr_move_duck_attack; + guncmdr_duck_down(self); + self->monsterinfo.dodge_time = level.time + 1.8f; +} + +static qboolean guncmdr_start_duckstep_dodge(edict_t *self) +{ + if (!self->groundentity) + return false; + + self->monsterinfo.nextattack = random() < 0.55f ? 1 : 0; + self->monsterinfo.currentmove = &guncmdr_move_duckstep_dodge; + guncmdr_duck_down(self); + self->monsterinfo.dodge_time = level.time + 1.6f; + return true; +} + +static qboolean guncmdr_start_mortar_dodge(edict_t *self) +{ + if (!self->groundentity || !G_ValidTarget(self, self->enemy, true, true)) + return false; + + if (entdist(self, self->enemy) < GUNCMDR_CHAINGUN_RUN_RANGE) + return false; + + self->monsterinfo.nextattack = 0; + self->monsterinfo.currentmove = &guncmdr_move_attack_mortar; + guncmdr_duck_down(self); + self->monsterinfo.dodge_time = level.time + 2.0f; + return true; +} + +static void guncmdr_dodge(edict_t *self, edict_t *attacker, vec3_t dir, int radius) +{ if (level.time < self->monsterinfo.dodge_time) return; + if (!attacker) + return; if (OnSameTeam(self, attacker)) return; - if (!self->enemy && G_EntIsAlive(attacker)) + self->monsterinfo.attacker = attacker; + if (!G_EntIsAlive(self->enemy) && G_EntIsAlive(attacker)) self->enemy = attacker; - if (self->monsterinfo.currentmove == &guncmdr_move_attack_mortar) + if (guncmdr_is_dodge_move(self)) { - self->count = self->s.frame; - self->monsterinfo.currentmove = &guncmdr_move_attack_mortar_dodge; - self->monsterinfo.dodge_time = level.time + 1.5; + if (self->monsterinfo.currentmove == &guncmdr_move_duck_attack && + (radius || guncmdr_dodge_hit_low(self, dir))) + guncmdr_start_duckstep_dodge(self); + return; } - else if (radius && self->groundentity) + + if (random() > 0.8) + return; + + if (guncmdr_try_sidestep(self)) { + self->monsterinfo.dodge_time = level.time + 1.2f; + return; + } + + if (radius && self->groundentity) + { + if (random() < 0.35f && guncmdr_start_duckstep_dodge(self)) + return; + if (random() < 0.35f && guncmdr_start_mortar_dodge(self)) + return; self->monsterinfo.pausetime = level.time + 2.0; self->monsterinfo.currentmove = &guncmdr_move_jump; self->monsterinfo.dodge_time = level.time + 3.0; + return; } - else if (self->monsterinfo.currentmove == &guncmdr_move_fire_chain || - self->monsterinfo.currentmove == &guncmdr_move_fire_chain_run) - { - self->monsterinfo.currentmove = (random() < 0.5) ? &guncmdr_move_fire_chain_dodge_left : &guncmdr_move_fire_chain_dodge_right; - self->monsterinfo.dodge_time = level.time + 1.5; - } - else if (self->monsterinfo.currentmove == &guncmdr_move_attack_grenade_back) + + if (!radius && !guncmdr_dodge_hit_low(self, dir) && self->groundentity) { - self->count = self->s.frame; - self->monsterinfo.currentmove = (random() < 0.5) ? &guncmdr_move_attack_grenade_back_dodge_left : &guncmdr_move_attack_grenade_back_dodge_right; - self->monsterinfo.dodge_time = level.time + 1.5; + if (random() < 0.45f && guncmdr_start_duckstep_dodge(self)) + return; + if (random() < 0.35f && guncmdr_start_mortar_dodge(self)) + return; + guncmdr_start_duck_dodge(self); + return; } - else + + if (guncmdr_start_duckstep_dodge(self)) + return; + + if (self->groundentity) { - self->monsterinfo.currentmove = &guncmdr_move_duck_attack; - self->monsterinfo.dodge_time = level.time + 2.0; + self->monsterinfo.lefty = !self->monsterinfo.lefty; + self->monsterinfo.currentmove = &guncmdr_move_dodge_slide; + self->monsterinfo.dodge_time = level.time + 1.0; + return; } } @@ -635,8 +781,14 @@ static qboolean guncmdr_can_proactive_dodge(edict_t *self, float dist) static void guncmdr_start_fire_chain_dodge(edict_t *self) { + guncmdr_duck_up(self); + + if (self->monsterinfo.lefty) + self->monsterinfo.currentmove = &guncmdr_move_fire_chain_dodge_left; + else + self->monsterinfo.currentmove = &guncmdr_move_fire_chain_dodge_right; + self->monsterinfo.lefty = !self->monsterinfo.lefty; - self->monsterinfo.currentmove = self->monsterinfo.lefty ? &guncmdr_move_fire_chain_dodge_left : &guncmdr_move_fire_chain_dodge_right; self->monsterinfo.dodge_time = level.time + 1.0; } @@ -649,20 +801,27 @@ static void guncmdr_attack(edict_t *self) if (!G_ValidTarget(self, self->enemy, true, true)) return; + guncmdr_duck_up(self); + dist = entdist(self, self->enemy); zdiff = fabs(self->s.origin[2] - self->enemy->s.origin[2]); r = random(); if (dist <= MELEE_DISTANCE && self->monsterinfo.melee_finished < level.time) self->monsterinfo.currentmove = &guncmdr_move_attack_kick; - else if (self->groundentity && dist > 96 && r < 0.25) - { - self->monsterinfo.currentmove = &guncmdr_move_duck_attack; - self->monsterinfo.dodge_time = level.time + 2.0; - } else if (guncmdr_can_proactive_dodge(self, dist) && r < 0.55) guncmdr_start_fire_chain_dodge(self); else if ((dist >= GUNCMDR_MORTAR_RANGE || zdiff > 96) && r < 0.75) + { self->monsterinfo.currentmove = &guncmdr_move_attack_mortar; + guncmdr_duck_down(self); + } + else if (self->groundentity && dist > 96 && dist < GUNCMDR_CHAINGUN_RUN_RANGE && r < 0.05) + { + self->monsterinfo.pausetime = level.time + 0.75f; + self->monsterinfo.currentmove = &guncmdr_move_duck_attack; + guncmdr_duck_down(self); + self->monsterinfo.dodge_time = level.time + 1.8f; + } else if (!(self->monsterinfo.aiflags & AI_STAND_GROUND) && dist > GUNCMDR_GRENADE_RANGE && r < 0.90) self->monsterinfo.currentmove = &guncmdr_move_attack_grenade_back; else @@ -690,6 +849,8 @@ static void guncmdr_fire_chain(edict_t *self) static void guncmdr_refire_chain(edict_t *self) { + guncmdr_duck_up(self); + if (G_ValidTarget(self, self->enemy, true, true) && visible(self, self->enemy) && random() <= 0.75) { float dist = entdist(self, self->enemy); @@ -709,6 +870,7 @@ static void guncmdr_refire_chain(edict_t *self) static void guncmdr_grenade_finished(edict_t *self) { + guncmdr_duck_up(self); self->monsterinfo.attack_finished = level.time + 1.0; guncmdr_run(self); } @@ -740,13 +902,147 @@ mframe_t guncmdr_frames_pain3[] = }; mmove_t guncmdr_move_pain3 = { FRAME_c_pain301, FRAME_c_pain304, guncmdr_frames_pain3, guncmdr_run }; +static void guncmdr_pain5_to_death1(edict_t *self) +{ + if (self->health <= 0) + self->monsterinfo.currentmove = &guncmdr_move_death1; +} + +static void guncmdr_pain5_to_death2(edict_t *self) +{ + if (self->health <= 0 && random() < 0.5f) + self->monsterinfo.currentmove = &guncmdr_move_death2; +} + +mframe_t guncmdr_frames_pain4[] = +{ + ai_move, -17.1, NULL, + ai_move, -3.2, NULL, + ai_move, 0.9, NULL, + ai_move, 3.6, NULL, + ai_move, -2.6, NULL, + ai_move, 1.0, NULL, + ai_move, -5.1, NULL, + ai_move, -6.7, NULL, + ai_move, -8.8, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, -2.1, NULL, + ai_move, -2.3, NULL, + ai_move, -2.5, NULL, + ai_move, 0, NULL +}; +mmove_t guncmdr_move_pain4 = { FRAME_c_pain401, FRAME_c_pain415, guncmdr_frames_pain4, guncmdr_run }; + +mframe_t guncmdr_frames_pain5[] = +{ + ai_move, -29, NULL, + ai_move, -5, NULL, + ai_move, -5, NULL, + ai_move, -3, NULL, + ai_move, 0, NULL, + ai_move, 0, guncmdr_pain5_to_death2, + ai_move, 9, NULL, + ai_move, 3, NULL, + ai_move, 0, guncmdr_pain5_to_death1, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, -4.6, NULL, + ai_move, -4.8, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 9.5, NULL, + ai_move, 3.4, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, -2.4, NULL, + ai_move, -9.0, NULL, + ai_move, -5.0, NULL, + ai_move, -3.6, NULL +}; +mmove_t guncmdr_move_pain5 = { FRAME_c_pain501, FRAME_c_pain524, guncmdr_frames_pain5, guncmdr_run }; + +static void guncmdr_pain6_to_death6(edict_t *self) +{ + if (self->health <= 0) + self->monsterinfo.currentmove = &guncmdr_move_death6; +} + +mframe_t guncmdr_frames_pain6[] = +{ + ai_move, 16, NULL, + ai_move, 16, NULL, + ai_move, 12, NULL, + ai_move, 5.5, guncmdr_duck_down, + ai_move, 3.0, NULL, + ai_move, -4.7, NULL, + ai_move, -6.0, guncmdr_pain6_to_death6, + ai_move, 0, NULL, + ai_move, 1.8, NULL, + ai_move, 0.7, NULL, + + ai_move, 0, NULL, + ai_move, -2.1, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, -6.1, NULL, + ai_move, 10.5, NULL, + ai_move, 4.3, NULL, + ai_move, 4.7, guncmdr_duck_up, + ai_move, 1.4, NULL, + ai_move, 0, NULL, + ai_move, -3.2, NULL, + ai_move, 2.3, NULL, + ai_move, -4.4, NULL, + + ai_move, -4.4, NULL, + ai_move, -2.4, NULL +}; +mmove_t guncmdr_move_pain6 = { FRAME_c_pain601, FRAME_c_pain632, guncmdr_frames_pain6, guncmdr_run }; + +mframe_t guncmdr_frames_pain7[] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t guncmdr_move_pain7 = { FRAME_c_pain701, FRAME_c_pain714, guncmdr_frames_pain7, guncmdr_run }; + static void guncmdr_pain(edict_t *self, edict_t *other, float kick, int damage) { - float r; + float r, dot = 1.0f; + vec3_t forward, dir; if (self->health < (self->max_health / 2)) self->s.skinnum = 3; + if (guncmdr_is_dodge_move(self)) + return; + if (level.time < self->pain_debounce_time) return; @@ -756,19 +1052,49 @@ static void guncmdr_pain(edict_t *self, edict_t *other, float kick, int damage) if (skill->value == 3) return; - r = random(); - if (r < 0.33) - self->monsterinfo.currentmove = &guncmdr_move_pain1; - else if (r < 0.66) - self->monsterinfo.currentmove = &guncmdr_move_pain2; + guncmdr_duck_up(self); + + if (other) + { + AngleVectors(self->s.angles, forward, NULL, NULL); + VectorSubtract(other->s.origin, self->s.origin, dir); + dir[2] = 0; + if (VectorNormalize(dir) != 0) + dot = DotProduct(dir, forward); + } + + if (damage < 35) + { + r = random(); + if (r < 0.25) + self->monsterinfo.currentmove = &guncmdr_move_pain1; + else if (r < 0.50) + self->monsterinfo.currentmove = &guncmdr_move_pain2; + else if (r < 0.75) + self->monsterinfo.currentmove = &guncmdr_move_pain3; + else + self->monsterinfo.currentmove = &guncmdr_move_pain7; + } + else if (dot < -0.40f) + { + self->monsterinfo.currentmove = &guncmdr_move_pain6; + guncmdr_duck_down(self); + self->pain_debounce_time += 1.5; + } else - self->monsterinfo.currentmove = &guncmdr_move_pain3; + { + if (random() < 0.5) + self->monsterinfo.currentmove = &guncmdr_move_pain4; + else + self->monsterinfo.currentmove = &guncmdr_move_pain5; + self->pain_debounce_time += 1.5; + } } static void guncmdr_dead(edict_t *self) { - VectorSet(self->mins, -16, -16, -24); - VectorSet(self->maxs, 16, 16, -8); + VectorSet(self->mins, -16 * self->s.scale, -16 * self->s.scale, -24 * self->s.scale); + VectorSet(self->maxs, 16 * self->s.scale, 16 * self->s.scale, GUNCMDR_DEAD_MAXZ_SCALED(self)); self->movetype = MOVETYPE_TOSS; self->svflags |= SVF_DEADMONSTER; gi.linkentity(self); @@ -777,11 +1103,77 @@ static void guncmdr_dead(edict_t *self) static void guncmdr_shrink(edict_t *self) { - self->maxs[2] = -8; + self->maxs[2] = GUNCMDR_SHRINK_MAXZ_SCALED(self); self->svflags |= SVF_DEADMONSTER; gi.linkentity(self); } +static void guncmdr_ai_dodge_slide(edict_t *self, float dist) +{ + if (!G_EntIsAlive(self->enemy)) + return; + + drone_ai_run_slide(self, dist); +} + +mframe_t guncmdr_frames_dodge_slide[] = +{ + guncmdr_ai_dodge_slide, 12, NULL, + guncmdr_ai_dodge_slide, 10, NULL, + guncmdr_ai_dodge_slide, 14, NULL, + guncmdr_ai_dodge_slide, 13, NULL, + guncmdr_ai_dodge_slide, 14, NULL, + guncmdr_ai_dodge_slide, 10, NULL +}; +mmove_t guncmdr_move_dodge_slide = { FRAME_c_run101, FRAME_c_run106, guncmdr_frames_dodge_slide, guncmdr_run }; + +static qboolean guncmdr_try_sidestep(edict_t *self) +{ + if (self->monsterinfo.currentmove == &guncmdr_move_fire_chain || + self->monsterinfo.currentmove == &guncmdr_move_fire_chain_run) + { + if (self->monsterinfo.lefty) + self->monsterinfo.currentmove = &guncmdr_move_fire_chain_dodge_left; + else + self->monsterinfo.currentmove = &guncmdr_move_fire_chain_dodge_right; + + self->monsterinfo.lefty = !self->monsterinfo.lefty; + return true; + } + + if (self->monsterinfo.currentmove == &guncmdr_move_attack_grenade_back) + { + self->count = self->s.frame; + + if (self->monsterinfo.lefty) + self->monsterinfo.currentmove = &guncmdr_move_attack_grenade_back_dodge_left; + else + self->monsterinfo.currentmove = &guncmdr_move_attack_grenade_back_dodge_right; + + self->monsterinfo.lefty = !self->monsterinfo.lefty; + return true; + } + + if (self->monsterinfo.currentmove == &guncmdr_move_attack_mortar) + { + self->count = self->s.frame; + self->monsterinfo.currentmove = &guncmdr_move_attack_mortar_dodge; + return true; + } + + if (self->monsterinfo.currentmove == &guncmdr_move_run) + { + if (random() < 0.45f) + return guncmdr_start_duckstep_dodge(self); + + self->monsterinfo.lefty = !self->monsterinfo.lefty; + self->monsterinfo.currentmove = &guncmdr_move_dodge_slide; + return true; + } + + return false; +} + static void guncmdr_footstep(edict_t *self) { gi.sound(self, CHAN_BODY, sound_thud, 1, ATTN_NORM, 0); @@ -1026,6 +1418,21 @@ static void guncmdr_die(edict_t *self, edict_t *inflictor, edict_t *attacker, in self->deadflag = DEAD_DEAD; self->takedamage = DAMAGE_YES; + if (self->monsterinfo.currentmove == &guncmdr_move_pain5 && + self->s.frame < FRAME_c_pain508) + { + if (self->activator && !self->activator->client) + self->activator->num_monsters_real--; + return; + } + if (self->monsterinfo.currentmove == &guncmdr_move_pain6 && + self->s.frame < FRAME_c_pain607) + { + if (self->activator && !self->activator->client) + self->activator->num_monsters_real--; + return; + } + if (inflictor) { AngleVectors(self->s.angles, forward, NULL, NULL); @@ -1043,23 +1450,23 @@ static void guncmdr_die(edict_t *self, edict_t *inflictor, edict_t *attacker, in } else if (dot < -0.40) { - n = GetRandom(0, 2); + n = GetRandom(0, self->monsterinfo.currentmove == &guncmdr_move_pain6 ? 2 : 3); if (n == 0) self->monsterinfo.currentmove = &guncmdr_move_death3; else if (n == 1) self->monsterinfo.currentmove = &guncmdr_move_death7; - else + else if (n == 2) self->monsterinfo.currentmove = &guncmdr_move_death6; + else + self->monsterinfo.currentmove = &guncmdr_move_pain6; } else { - n = GetRandom(0, 2); + n = GetRandom(0, self->monsterinfo.currentmove == &guncmdr_move_pain5 ? 1 : 2); if (n == 0) self->monsterinfo.currentmove = &guncmdr_move_death4; - else if (n == 1) - self->monsterinfo.currentmove = &guncmdr_move_death2; else - self->monsterinfo.currentmove = &guncmdr_move_death1; + self->monsterinfo.currentmove = &guncmdr_move_pain5; } if (self->activator && !self->activator->client) @@ -1090,8 +1497,9 @@ void init_drone_guncmdr(edict_t *self) gi.modelindex("models/monsters/gunner/gibs/gun.md2"); gi.modelindex("models/monsters/gunner/gibs/head.md2"); + self->s.scale = 1.25f; VectorSet(self->mins, -16, -16, -24); - VectorSet(self->maxs, 16, 16, 36); + VectorSet(self->maxs, 16, 16, GUNCMDR_STAND_MAXZ_SCALED(self)); self->s.skinnum = 2; self->monsterinfo.control_cost = M_TANK_CONTROL_COST; @@ -1102,7 +1510,7 @@ void init_drone_guncmdr(edict_t *self) self->mass = 255; self->monsterinfo.jumpdn = 512; self->monsterinfo.jumpup = 64; - self->s.scale = 1.2f; + self->s.origin[2] += fabsf(self->mins[2]) * (self->s.scale - 1.0f); if (random() > 0.5) self->item = FindItemByClassname("ammo_bullets"); diff --git a/src/entities/drone/drone_gunner.c b/src/entities/drone/drone_gunner.c index b9efb675..65d04190 100644 --- a/src/entities/drone/drone_gunner.c +++ b/src/entities/drone/drone_gunner.c @@ -19,11 +19,13 @@ static int sound_sight; static int sound_thud; void mygunner_continue (edict_t *self); +void mygunnerrun (edict_t *self); void mygunner_refire_chain(edict_t *self); void mygunner_fire_chain(edict_t *self); void mygunner_delay (edict_t *self); void gunner_attack_grenade (edict_t *self); void gunner_refire_grenade (edict_t *self); +void drone_ai_run_slide(edict_t *self, float dist); void mygunneridlesound (edict_t *self) { @@ -184,6 +186,29 @@ mframe_t mygunnerframes_run [] = mmove_t mygunnermove_run = {FRAME_run01, FRAME_run08, mygunnerframes_run, NULL}; +static void mygunner_ai_dodge_slide(edict_t *self, float dist) +{ + if (!G_EntIsAlive(self->enemy) && G_EntIsAlive(self->monsterinfo.attacker)) + self->enemy = self->monsterinfo.attacker; + if (!G_EntIsAlive(self->enemy)) + return; + + drone_ai_run_slide(self, dist); +} + +mframe_t mygunner_frames_dodge_slide[] = +{ + mygunner_ai_dodge_slide, 25, NULL, + mygunner_ai_dodge_slide, 25, NULL, + mygunner_ai_dodge_slide, 25, NULL, + mygunner_ai_dodge_slide, 25, NULL, + mygunner_ai_dodge_slide, 25, NULL, + mygunner_ai_dodge_slide, 25, NULL, + mygunner_ai_dodge_slide, 25, NULL, + mygunner_ai_dodge_slide, 25, NULL +}; +mmove_t mygunner_move_dodge_slide = {FRAME_run01, FRAME_run08, mygunner_frames_dodge_slide, mygunnerrun}; + void mygunnerrun (edict_t *self) { if (self->monsterinfo.aiflags & AI_STAND_GROUND) @@ -485,15 +510,24 @@ void mygunner_duck_up (edict_t *self) gi.linkentity (self); } +void mygunner_duck_hold (edict_t *self) +{ + if (self->monsterinfo.pausetime > level.time) + self->monsterinfo.nextframe = self->s.frame; +} + mframe_t mygunner_frames_duck [] = { - ai_move, 0, mygunner_duck_down, - ai_move, 0, NULL, + ai_move, 1, mygunner_duck_down, + ai_move, 1, NULL, + ai_move, 1, mygunner_duck_hold, ai_move, 0, NULL, - ai_move, 0, NULL, - ai_move, 0, mygunner_duck_up, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, 0, mygunner_duck_up, + ai_move, -1, NULL }; -mmove_t mygunner_move_duck = {FRAME_duck03, FRAME_duck07, mygunner_frames_duck, mygunnerrun}; +mmove_t mygunner_move_duck = {FRAME_duck01, FRAME_duck08, mygunner_frames_duck, mygunnerrun}; void mygunner_jump_takeoff (edict_t *self) { @@ -551,22 +585,54 @@ void mygunner_leap (edict_t *self) self->monsterinfo.currentmove = &mygunner_move_leap; } +static qboolean mygunner_is_dodge_move(edict_t *self) +{ + return self->monsterinfo.currentmove == &mygunner_move_duck || + self->monsterinfo.currentmove == &mygunner_move_leap || + self->monsterinfo.currentmove == &mygunner_move_dodge_slide; +} + +static qboolean mygunner_is_uninterruptible_attack(edict_t *self) +{ + return self->monsterinfo.currentmove == &mygunner_move_attack_chain || + self->monsterinfo.currentmove == &mygunner_move_fire_chain || + self->monsterinfo.currentmove == &mygunner_move_attack_grenade; +} + +static qboolean mygunner_dodge_hit_low(edict_t *self, vec3_t dir) +{ + const float duck_height = self->absmax[2] - 33; + + return dir[2] > self->absmin[2] && dir[2] <= duck_height; +} + void mygunner_dodge (edict_t *self, edict_t *attacker, vec3_t dir, int radius) { if (random() > 0.9) return; - if (!G_GetClient(self)) - return; if (level.time < self->monsterinfo.dodge_time) return; if (OnSameTeam(self, attacker)) return; + if (mygunner_is_dodge_move(self) || mygunner_is_uninterruptible_attack(self)) + return; - if (!self->enemy && G_EntIsAlive(attacker)) + self->monsterinfo.attacker = attacker; + if (!G_EntIsAlive(self->enemy) && G_EntIsAlive(attacker)) self->enemy = attacker; if (!radius) { - self->monsterinfo.currentmove = &mygunner_move_duck; + if (mygunner_dodge_hit_low(self, dir) && !(self->monsterinfo.aiflags & AI_STAND_GROUND)) + { + self->monsterinfo.lefty = 1 - self->monsterinfo.lefty; + self->monsterinfo.currentmove = &mygunner_move_dodge_slide; + } + else + { + self->monsterinfo.pausetime = level.time + 0.5; + self->monsterinfo.currentmove = &mygunner_move_duck; + mygunner_duck_down(self); + } self->monsterinfo.dodge_time = level.time + 2.0; } else @@ -635,7 +701,8 @@ void mygunner_pain(edict_t* self, edict_t* other, float kick, int damage) // we're already in a pain state if (self->monsterinfo.currentmove == &mygunnermove_pain_long1 || self->monsterinfo.currentmove == &mygunnermove_pain_long2 || - self->monsterinfo.currentmove == &mygunnermove_pain_short) + self->monsterinfo.currentmove == &mygunnermove_pain_short || + mygunner_is_dodge_move(self)) return; // monster players don't get pain state induced diff --git a/src/entities/drone/drone_hover.c b/src/entities/drone/drone_hover.c index 9e02099b..8f5b82e4 100644 --- a/src/entities/drone/drone_hover.c +++ b/src/entities/drone/drone_hover.c @@ -600,6 +600,11 @@ void hover_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage gi.sound (self, CHAN_VOICE, sound_death2, 1, ATTN_NORM, 0); self->deadflag = DEAD_DEAD; self->takedamage = DAMAGE_YES; + self->flags &= ~FL_FLY; + self->movetype = MOVETYPE_TOSS; + self->gravity = 1.0; + if (self->velocity[2] > -120) + self->velocity[2] = -120; self->monsterinfo.currentmove = &hover_move_death1; } diff --git a/src/entities/drone/drone_infantry.c b/src/entities/drone/drone_infantry.c index b2231247..fa558f7e 100644 --- a/src/entities/drone/drone_infantry.c +++ b/src/entities/drone/drone_infantry.c @@ -28,6 +28,11 @@ static int sound_idle; #define INFANTRY_RUN_ATTACK_MIN_DIST 256 +void drone_ai_run_slide(edict_t *self, float dist); +static void infantry_run_fire(edict_t *self); +extern mmove_t infantry_move_attack4; + + mframe_t infantry_frames_stand [] = { @@ -543,6 +548,138 @@ void infantry_fire(edict_t* self) M_DelayNextAttack(self, 0, true); } +static void infantry_ai_dodge_slide(edict_t *self, float dist); + +static void infantry_ai_dodge_slide(edict_t *self, float dist) +{ + if (!G_EntIsAlive(self->enemy) && G_EntIsAlive(self->monsterinfo.attacker)) + self->enemy = self->monsterinfo.attacker; + if (!G_EntIsAlive(self->enemy)) + return; + + drone_ai_run_slide(self, dist); +} + +mframe_t infantry_frames_dodge_slide[] = +{ + infantry_ai_dodge_slide, 12, NULL, + infantry_ai_dodge_slide, 10, NULL, + infantry_ai_dodge_slide, 12, NULL, + infantry_ai_dodge_slide, 10, NULL, + infantry_ai_dodge_slide, 12, NULL, + infantry_ai_dodge_slide, 10, NULL, + infantry_ai_dodge_slide, 8, NULL, + infantry_ai_dodge_slide, 6, NULL +}; +mmove_t infantry_move_dodge_slide = { FRAME_run01, FRAME_run08, infantry_frames_dodge_slide, infantry_run }; + +static void infantry_attack4_dodge_ai(edict_t *self, float dist) +{ + if (!G_EntIsAlive(self->enemy)) + return; + + drone_ai_run_slide(self, dist); +} + +static void infantry_resume_attack4(edict_t *self) +{ + self->monsterinfo.currentmove = &infantry_move_attack4; + self->s.frame = self->count; +} + +mframe_t infantry_frames_attack4_dodge[] = +{ + infantry_attack4_dodge_ai, 10, infantry_run_fire, + infantry_attack4_dodge_ai, 12, NULL, + infantry_attack4_dodge_ai, 10, infantry_run_fire, + infantry_attack4_dodge_ai, 9, NULL, + infantry_attack4_dodge_ai, 8, NULL +}; +mmove_t infantry_move_attack4_dodge = { FRAME_run201, FRAME_run205, infantry_frames_attack4_dodge, infantry_resume_attack4 }; + +static qboolean infantry_try_sidestep(edict_t *self) +{ + if (self->monsterinfo.currentmove == &infantry_move_attack4) + { + self->count = self->s.frame; + self->monsterinfo.lefty = 1 - self->monsterinfo.lefty; + self->monsterinfo.currentmove = &infantry_move_attack4_dodge; + return true; + } + + if (self->monsterinfo.currentmove == &infantry_move_run) + { + self->monsterinfo.lefty = 1 - self->monsterinfo.lefty; + self->monsterinfo.currentmove = &infantry_move_dodge_slide; + return true; + } + + return false; +} + +static qboolean infantry_is_dodge_move(edict_t *self) +{ + return self->monsterinfo.currentmove == &infantry_move_dodge_slide || + self->monsterinfo.currentmove == &infantry_move_attack4_dodge || + self->monsterinfo.currentmove == &infantry_move_duck; +} + +static qboolean infantry_dodge_hit_low(edict_t *self, vec3_t dir) +{ + const float duck_height = self->absmax[2] - 33; + + return dir[2] > self->absmin[2] && dir[2] <= duck_height; +} + +static void infantry_start_duck(edict_t *self) +{ + self->monsterinfo.currentmove = &infantry_move_duck; + infantry_duck_down(self); + self->monsterinfo.dodge_time = level.time + 1.2f; +} + +static void infantry_dodge(edict_t *self, edict_t *attacker, vec3_t dir, int radius) +{ + if (level.time < self->monsterinfo.dodge_time) + return; + if (!attacker) + return; + if (OnSameTeam(self, attacker)) + return; + if (infantry_is_dodge_move(self)) + return; + + self->monsterinfo.attacker = attacker; + if (!G_EntIsAlive(self->enemy) && G_EntIsAlive(attacker)) + self->enemy = attacker; + + if (random() > 0.75f) + return; + + // Match remaster: sidestep low shots, duck high direct shots. + if (!radius && !infantry_dodge_hit_low(self, dir)) + { + infantry_start_duck(self); + return; + } + + if (infantry_try_sidestep(self)) + { + self->monsterinfo.dodge_time = level.time + 1.0f; + return; + } + + if (!(self->monsterinfo.aiflags & AI_STAND_GROUND)) + { + self->monsterinfo.lefty = 1 - self->monsterinfo.lefty; + self->monsterinfo.currentmove = &infantry_move_dodge_slide; + self->monsterinfo.dodge_time = level.time + 0.9f; + return; + } + + infantry_start_duck(self); +} + static void infantry_run_attack_ai(edict_t* self, float dist) { if (self->monsterinfo.aiflags & AI_STAND_GROUND) @@ -828,6 +965,7 @@ void init_drone_infantry(edict_t* self) self->monsterinfo.sight = infantry_sight; //self->monsterinfo.idle = infantry_fidget; self->monsterinfo.melee = infantry_melee; + self->monsterinfo.dodge = infantry_dodge; gi.linkentity(self); diff --git a/src/entities/drone/drone_medic.c b/src/entities/drone/drone_medic.c index c811e04e..fe329d1d 100644 --- a/src/entities/drone/drone_medic.c +++ b/src/entities/drone/drone_medic.c @@ -30,6 +30,14 @@ static int commander_sound_hook_heal; static int commander_sound_hook_retract; static int commander_sound_spawn; +void drone_ai_run_slide(edict_t *self, float dist); +void mymedic_run(edict_t *self); +extern mmove_t mymedic_move_attackHyperBlaster; +extern mmove_t mymedic_move_attackBlaster; +extern mmove_t mymedic_move_attackCable; +extern mmove_t medic_commander_move_callReinforcements; +static qboolean mymedic_is_dodge_move(edict_t *self); + #define MEDIC_COMMANDER_SUMMON_COUNT 2 #define MEDIC_COMMANDER_SUMMON_COOLDOWN 8.0f #define SPAWNGROW_LIFESPAN 1.0f @@ -235,6 +243,27 @@ mframe_t mymedic_frames_run [] = }; mmove_t mymedic_move_run = {FRAME_run1, FRAME_run6, mymedic_frames_run, NULL}; +static void mymedic_ai_dodge_slide(edict_t *self, float dist) +{ + if (!G_EntIsAlive(self->enemy) && G_EntIsAlive(self->monsterinfo.attacker)) + self->enemy = self->monsterinfo.attacker; + if (!G_EntIsAlive(self->enemy)) + return; + + drone_ai_run_slide(self, dist); +} + +mframe_t mymedic_frames_dodge_slide[] = +{ + mymedic_ai_dodge_slide, 18, NULL, + mymedic_ai_dodge_slide, 22.5, NULL, + mymedic_ai_dodge_slide, 25.4, NULL, + mymedic_ai_dodge_slide, 23.4, NULL, + mymedic_ai_dodge_slide, 24, NULL, + mymedic_ai_dodge_slide, 35.6, NULL +}; +mmove_t mymedic_move_dodge_slide = {FRAME_run1, FRAME_run6, mymedic_frames_dodge_slide, mymedic_run}; + void mymedic_run (edict_t *self) { if (self->monsterinfo.aiflags & AI_STAND_GROUND) @@ -335,7 +364,8 @@ void medic_pain(edict_t* self, edict_t* other, float kick, int damage) // we're already in a pain state if (self->monsterinfo.currentmove == &medic_move_pain_short || - self->monsterinfo.currentmove == &medic_move_pain_long) + self->monsterinfo.currentmove == &medic_move_pain_long || + mymedic_is_dodge_move(self)) return; // monster players don't get pain state induced @@ -480,6 +510,12 @@ void mymedic_duck_up (edict_t *self) gi.linkentity (self); } +void mymedic_duck_hold (edict_t *self) +{ + if (self->monsterinfo.pausetime > level.time) + self->monsterinfo.nextframe = self->s.frame; +} + void mymedic_jump_takeoff (edict_t *self) { @@ -520,26 +556,21 @@ void mymedic_jump_hold (edict_t *self) mframe_t mymedic_frames_duck [] = { - ai_move, 0, mymedic_duck_down, - ai_move, 0, NULL, - ai_move, 0, NULL,//mymedic_duck_down, - ai_move, 0, NULL, - ai_move, 0, NULL, - ai_move, 0, NULL, - ai_move, 0, mymedic_duck_up - /* - ai_move, 0, NULL, - ai_move, 0, NULL, - ai_move, 0, NULL, - ai_move, 0, NULL, - ai_move, 0, NULL, - ai_move, 0, NULL, - ai_move, 0, NULL, - ai_move, 0, NULL, - ai_move, 0, NULL - */ + ai_move, -1, NULL, + ai_move, -1, mymedic_duck_down, + ai_move, -1, mymedic_duck_hold, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, -1, mymedic_duck_up }; -mmove_t mymedic_move_duck = {FRAME_duck1, FRAME_duck7, mymedic_frames_duck, mymedic_run}; +mmove_t mymedic_move_duck = {FRAME_duck2, FRAME_duck14, mymedic_frames_duck, mymedic_run}; mframe_t mymedic_frames_leap [] = { @@ -570,22 +601,63 @@ void mymedic_leap (edict_t *self) self->monsterinfo.currentmove = &mymedic_move_leap; } +static qboolean mymedic_is_dodge_move(edict_t *self) +{ + return self->monsterinfo.currentmove == &mymedic_move_duck || + self->monsterinfo.currentmove == &mymedic_move_leap || + self->monsterinfo.currentmove == &mymedic_move_dodge_slide; +} + +static qboolean mymedic_is_uninterruptible_attack(edict_t *self) +{ + return self->monsterinfo.currentmove == &mymedic_move_attackHyperBlaster || + self->monsterinfo.currentmove == &mymedic_move_attackCable || + self->monsterinfo.currentmove == &mymedic_move_attackBlaster || + self->monsterinfo.currentmove == &medic_commander_move_callReinforcements; +} + +static qboolean mymedic_dodge_hit_low(edict_t *self, vec3_t dir) +{ + const float duck_height = self->absmax[2] - 33; + + return dir[2] > self->absmin[2] && dir[2] <= duck_height; +} + void mymedic_dodge (edict_t *self, edict_t *attacker, vec3_t dir, int radius) { if (random() > 0.9) return; - if (!G_GetClient(self)) - return; if (level.time < self->monsterinfo.dodge_time) return; + if (!attacker) + return; if (OnSameTeam(self, attacker)) return; + if (mymedic_is_dodge_move(self) || mymedic_is_uninterruptible_attack(self)) + return; - if (!self->enemy && G_EntIsAlive(attacker)) + self->monsterinfo.attacker = attacker; + if (!G_EntIsAlive(self->enemy) && G_EntIsAlive(attacker)) self->enemy = attacker; if (!radius) { - self->monsterinfo.currentmove = &mymedic_move_duck; + if (!mymedic_dodge_hit_low(self, dir)) + { + self->monsterinfo.pausetime = level.time + 0.5; + self->monsterinfo.currentmove = &mymedic_move_duck; + mymedic_duck_down(self); + } + else if (!(self->monsterinfo.aiflags & AI_STAND_GROUND)) + { + self->monsterinfo.lefty = 1 - self->monsterinfo.lefty; + self->monsterinfo.currentmove = &mymedic_move_dodge_slide; + } + else + { + self->monsterinfo.pausetime = level.time + 0.5; + self->monsterinfo.currentmove = &mymedic_move_duck; + mymedic_duck_down(self); + } self->monsterinfo.dodge_time = level.time + 2.0; } else @@ -769,7 +841,7 @@ void M_Reanimate (edict_t *ent, edict_t *target, int r_level, float r_modifier, } else if ((!strcmp(target->classname, "bodyque") || !strcmp(target->classname, "player"))) { - const int random=GetRandom(1, 3); + const int random=GetRandom(1, 6); vec3_t start; // if the summoner is a player, check for sufficient monster slots @@ -781,27 +853,43 @@ void M_Reanimate (edict_t *ent, edict_t *target, int r_level, float r_modifier, VectorCopy(target->s.origin, start); // kill the corpse - T_Damage(target, target, target, vec3_origin, target->s.origin, + T_Damage(target, target, target, vec3_origin, target->s.origin, vec3_origin, 10000, 0, DAMAGE_NO_PROTECTION, 0); - //4.2 random soldier type with different weapons - if (random == 1) + // random soldier type with different weapons + switch (random) { + case 1: // blaster e->mtype = M_SOLDIER; e->s.skinnum = 0; - } - else if (random == 2) - { + break; + case 2: // rocket e->mtype = M_SOLDIERLT; e->s.skinnum = 4; - } - else - { + break; + case 3: // shotgun e->mtype = M_SOLDIERSS; e->s.skinnum = 2; + break; + case 4: + // ripper + e->mtype = M_SOLDIER_RIPPER; + e->s.skinnum = 6; + break; + case 5: + // blue blaster + e->mtype = M_SOLDIER_BLUEBLASTER; + e->s.skinnum = 8; + break; + case 6: + default: + // laser + e->mtype = M_SOLDIER_LASER; + e->s.skinnum = 10; + break; } e->activator = ent; @@ -813,7 +901,7 @@ void M_Reanimate (edict_t *ent, edict_t *target, int r_level, float r_modifier, e->s.skinnum |= 1; // injured skin e->monsterinfo.stand(e); - + if (!G_IsValidLocation(target, start, e->mins, e->maxs)) { start[2] += 24; @@ -1248,7 +1336,22 @@ static qboolean medic_commander_find_spawn_spot(edict_t *self, vec3_t mins, vec3 return medic_commander_valid_spawn_spot(self, mins, maxs, spot); } -static qboolean medic_commander_spawn_laserguard(edict_t *self, float side) +static int medic_commander_random_soldier_type(void) +{ + static const int soldier_types[] = + { + M_SOLDIER, + M_SOLDIERLT, + M_SOLDIERSS, + M_SOLDIER_RIPPER, + M_SOLDIER_BLUEBLASTER, + M_SOLDIER_LASER + }; + + return soldier_types[GetRandom(0, (int)(sizeof(soldier_types) / sizeof(soldier_types[0])) - 1)]; +} + +static qboolean medic_commander_spawn_soldier(edict_t *self, float side) { edict_t *owner = self->activator; edict_t *gunner; @@ -1261,7 +1364,7 @@ static qboolean medic_commander_spawn_laserguard(edict_t *self, float side) return false; gunner = G_Spawn(); - gunner->mtype = M_SOLDIER_LASER; + gunner->mtype = medic_commander_random_soldier_type(); gunner->activator = owner; gunner->monsterinfo.level = self->monsterinfo.level; @@ -1336,7 +1439,7 @@ static void medic_commander_finish_spawn(edict_t *self) { float side = (i & 1) ? 56 : -56; - if (medic_commander_spawn_laserguard(self, side)) + if (medic_commander_spawn_soldier(self, side)) spawned++; } diff --git a/src/entities/drone/drone_misc.c b/src/entities/drone/drone_misc.c index c7adf8a4..d60d7052 100644 --- a/src/entities/drone/drone_misc.c +++ b/src/entities/drone/drone_misc.c @@ -1803,7 +1803,9 @@ qboolean M_Regenerate (edict_t *self, int regen_frames, int delay, float mult, q if (self->mtype != M_COMMANDER && self->mtype != M_GUNCMDR && self->mtype != M_DAEDALUS && self->mtype != M_GLADB && self->mtype != M_GLADC && self->mtype != M_CHICK_HEAT && self->mtype != M_MEDIC_COMMANDER - && self->mtype != M_SOLDIER && self->mtype != M_STALKER) + && self->mtype != M_SOLDIER && self->mtype != M_SOLDIER_RIPPER + && self->mtype != M_SOLDIER_BLUEBLASTER && self->mtype != M_SOLDIER_LASER + && self->mtype != M_STALKER) self->s.skinnum &= ~2; } diff --git a/src/entities/drone/drone_soldier.c b/src/entities/drone/drone_soldier.c index 683299a0..348951bb 100644 --- a/src/entities/drone/drone_soldier.c +++ b/src/entities/drone/drone_soldier.c @@ -20,6 +20,49 @@ static int sound_death; static int sound_death_ss; static int sound_cock; +static mmove_t m_soldier_move_attack5; +static mmove_t m_soldier_move_trip; +static mmove_t m_soldier_move_duck; + +static const int soldier_blaster_flash[] = +{ + MZ2_SOLDIER_BLASTER_1, + MZ2_SOLDIER_BLASTER_2, + MZ2_SOLDIER_BLASTER_3, + MZ2_SOLDIER_BLASTER_4, + MZ2_SOLDIER_BLASTER_5, + MZ2_SOLDIER_BLASTER_6, + MZ2_SOLDIER_BLASTER_7, + MZ2_SOLDIER_BLASTER_8, + MZ2_SOLDIER_BLASTER_9 +}; + +static const int soldier_shotgun_flash[] = +{ + MZ2_SOLDIER_SHOTGUN_1, + MZ2_SOLDIER_SHOTGUN_2, + MZ2_SOLDIER_SHOTGUN_3, + MZ2_SOLDIER_SHOTGUN_4, + MZ2_SOLDIER_SHOTGUN_5, + MZ2_SOLDIER_SHOTGUN_6, + MZ2_SOLDIER_SHOTGUN_7, + MZ2_SOLDIER_SHOTGUN_8, + MZ2_SOLDIER_SHOTGUN_9 +}; + +static const int soldier_machinegun_flash[] = +{ + MZ2_SOLDIER_MACHINEGUN_1, + MZ2_SOLDIER_MACHINEGUN_2, + MZ2_SOLDIER_MACHINEGUN_3, + MZ2_SOLDIER_MACHINEGUN_4, + MZ2_SOLDIER_MACHINEGUN_5, + MZ2_SOLDIER_MACHINEGUN_6, + MZ2_SOLDIER_MACHINEGUN_7, + MZ2_SOLDIER_MACHINEGUN_8, + MZ2_SOLDIER_MACHINEGUN_9 +}; + static const int soldier_ripper_flash[] = { MZ2_SOLDIER_RIPPER_1, @@ -47,7 +90,33 @@ static const int soldier_hyper_flash[] = }; void m_soldier_stand (edict_t *self); +void m_soldier_run (edict_t *self); void m_soldier_runandshoot_continue (edict_t *self); +void drone_ai_run_slide(edict_t *self, float dist); + +static qboolean soldier_uses_light_voice(edict_t *self) +{ + return self->mtype == M_SOLDIER || + self->mtype == M_SOLDIER_RIPPER; +} + +static int soldier_pain_sound(edict_t *self) +{ + if (soldier_uses_light_voice(self)) + return sound_pain_light; + if (self->mtype == M_SOLDIERSS) + return sound_pain; + return sound_pain_ss; +} + +static int soldier_death_sound(edict_t *self) +{ + if (soldier_uses_light_voice(self)) + return sound_death_light; + if (self->mtype == M_SOLDIERSS) + return sound_death; + return sound_death_ss; +} void m_soldier_idle (edict_t *self) { @@ -166,6 +235,27 @@ mframe_t m_soldier_frames_run [] = }; mmove_t m_soldier_move_run = {FRAME_run03, FRAME_run08, m_soldier_frames_run, NULL}; +static void m_soldier_ai_dodge_slide(edict_t *self, float dist) +{ + if (!G_EntIsAlive(self->enemy) && G_EntIsAlive(self->monsterinfo.attacker)) + self->enemy = self->monsterinfo.attacker; + if (!G_EntIsAlive(self->enemy)) + return; + + drone_ai_run_slide(self, dist); +} + +mframe_t m_soldier_frames_dodge_slide[] = +{ + m_soldier_ai_dodge_slide, 10, NULL, + m_soldier_ai_dodge_slide, 11, NULL, + m_soldier_ai_dodge_slide, 11, NULL, + m_soldier_ai_dodge_slide, 16, NULL, + m_soldier_ai_dodge_slide, 10, NULL, + m_soldier_ai_dodge_slide, 15, NULL +}; +mmove_t m_soldier_move_dodge_slide = {FRAME_run03, FRAME_run08, m_soldier_frames_dodge_slide, m_soldier_run}; + void m_soldier_run (edict_t *self) { if (self->monsterinfo.aiflags & AI_STAND_GROUND) @@ -174,7 +264,14 @@ void m_soldier_run (edict_t *self) self->monsterinfo.currentmove = &m_soldier_move_run; } -void soldier_fireblaster(edict_t* self) +static int soldier_flash_from_table(const int *flashes, size_t count, int flash_number) +{ + if (flash_number < 0 || flash_number >= (int)count) + flash_number = 7; + return flashes[flash_number]; +} + +static void soldier_fireblaster_flash(edict_t* self, int flash) { int damage, speed; vec3_t forward, start; @@ -190,11 +287,16 @@ void soldier_fireblaster(edict_t* self) if (M_BLASTER_SPEED_MAX && speed > M_BLASTER_SPEED_MAX) speed = M_BLASTER_SPEED_MAX; - MonsterAim(self, M_PROJECTILE_ACC, speed, false, MZ2_SOLDIER_BLASTER_8, forward, start); - monster_fire_blaster(self, start, forward, damage, speed, EF_BLASTER, BLASTER_PROJ_BOLT, 2.0, true, MZ2_SOLDIER_BLASTER_8); + MonsterAim(self, M_PROJECTILE_ACC, speed, false, flash, forward, start); + monster_fire_blaster(self, start, forward, damage, speed, EF_BLASTER, BLASTER_PROJ_BOLT, 2.0, true, flash); } -void soldier_firerocket(edict_t* self) +void soldier_fireblaster(edict_t* self) +{ + soldier_fireblaster_flash(self, MZ2_SOLDIER_BLASTER_8); +} + +static void soldier_firerocket_flash(edict_t* self, int flash) { int damage, speed; vec3_t forward, start; @@ -209,11 +311,16 @@ void soldier_firerocket(edict_t* self) if (M_ROCKETLAUNCHER_SPEED_MAX && speed > M_ROCKETLAUNCHER_SPEED_MAX) speed = M_ROCKETLAUNCHER_SPEED_MAX; - MonsterAim(self, M_PROJECTILE_ACC, speed, true, MZ2_SOLDIER_BLASTER_8, forward, start); - monster_fire_rocket(self, start, forward, damage, speed, MZ2_SOLDIER_BLASTER_8); + MonsterAim(self, M_PROJECTILE_ACC, speed, true, flash, forward, start); + monster_fire_rocket(self, start, forward, damage, speed, flash); } -void soldier_fireshotgun(edict_t* self) +void soldier_firerocket(edict_t* self) +{ + soldier_firerocket_flash(self, MZ2_SOLDIER_BLASTER_8); +} + +static void soldier_fireshotgun_flash(edict_t* self, int flash) { int damage; vec3_t forward, start; @@ -224,8 +331,13 @@ void soldier_fireshotgun(edict_t* self) damage = M_SHOTGUN_DMG_BASE + M_SHOTGUN_DMG_ADDON * drone_damagelevel(self); if (M_SHOTGUN_DMG_MAX && damage > M_SHOTGUN_DMG_MAX) damage = M_SHOTGUN_DMG_MAX; - MonsterAim(self, M_HITSCAN_INSTANT_ACC, 0, false, MZ2_SOLDIER_SHOTGUN_8, forward, start); - monster_fire_shotgun(self, start, forward, damage, 15, 375, 375, 10, MZ2_SOLDIER_SHOTGUN_8); + MonsterAim(self, M_HITSCAN_INSTANT_ACC, 0, false, flash, forward, start); + monster_fire_shotgun(self, start, forward, damage, 15, 375, 375, 10, flash); +} + +void soldier_fireshotgun(edict_t* self) +{ + soldier_fireshotgun_flash(self, MZ2_SOLDIER_SHOTGUN_8); } void soldier_fireionripper(edict_t* self, int flash_number) @@ -237,9 +349,8 @@ void soldier_fireionripper(edict_t* self, int flash_number) if (!G_EntExists(self->enemy)) return; - if (flash_number < 0 || flash_number >= (int)(sizeof(soldier_ripper_flash) / sizeof(soldier_ripper_flash[0]))) - flash_number = 7; - flash = soldier_ripper_flash[flash_number]; + flash = soldier_flash_from_table(soldier_ripper_flash, + sizeof(soldier_ripper_flash) / sizeof(soldier_ripper_flash[0]), flash_number); damage = IONRIPPER_INITIAL_DAMAGE + IONRIPPER_ADDON_DAMAGE * drone_damagelevel(self); speed = IONRIPPER_INITIAL_SPEED + IONRIPPER_ADDON_SPEED * drone_damagelevel(self); @@ -256,9 +367,8 @@ void soldier_fireblueblaster(edict_t* self, int flash_number) if (!G_EntExists(self->enemy)) return; - if (flash_number < 0 || flash_number >= (int)(sizeof(soldier_hyper_flash) / sizeof(soldier_hyper_flash[0]))) - flash_number = 7; - flash = soldier_hyper_flash[flash_number]; + flash = soldier_flash_from_table(soldier_hyper_flash, + sizeof(soldier_hyper_flash) / sizeof(soldier_hyper_flash[0]), flash_number); damage = M_HYPERBLASTER_DMG_BASE + M_HYPERBLASTER_DMG_ADDON * drone_damagelevel(self); if (M_HYPERBLASTER_DMG_MAX && damage > M_HYPERBLASTER_DMG_MAX) damage = M_HYPERBLASTER_DMG_MAX; @@ -515,14 +625,30 @@ void m_soldier_duck_up (edict_t *self) gi.linkentity (self); } +void m_soldier_duck_hold (edict_t *self) +{ + if (self->monsterinfo.pausetime > level.time) + self->monsterinfo.nextframe = self->s.frame; +} + +static void m_soldier_start_duck_dodge(edict_t *self, float hold_time) +{ + self->monsterinfo.nextattack = 0; + self->monsterinfo.pausetime = level.time + hold_time; + self->monsterinfo.currentmove = &m_soldier_move_duck; + m_soldier_duck_down(self); + self->monsterinfo.dodge_time = level.time + 1.25f; +} + mframe_t m_soldier_frames_duck [] = { - ai_move, 0, m_soldier_duck_down, - ai_move, 0, NULL, - ai_move, 0, NULL, - ai_move, 0, m_soldier_duck_up + ai_move, 5, m_soldier_duck_down, + ai_move, -1, m_soldier_duck_hold, + ai_move, 1, NULL, + ai_move, 0, m_soldier_duck_up, + ai_move, 5, NULL }; -mmove_t m_soldier_move_duck = {FRAME_duck01, FRAME_duck04, m_soldier_frames_duck, m_soldier_run}; +static mmove_t m_soldier_move_duck = {FRAME_duck01, FRAME_duck05, m_soldier_frames_duck, m_soldier_run}; void m_soldier_jump_takeoff (edict_t *self) { @@ -593,50 +719,203 @@ mframe_t m_soldier_frames_jump [] = }; mmove_t m_soldier_move_jump = {FRAME_duck01, FRAME_duck05, m_soldier_frames_jump, m_soldier_run}; -mframe_t m_soldier_frames_jump_attack [] = +static qboolean m_soldier_prone_shoot_ok(edict_t *self) +{ + vec3_t forward, diff; + + if (!G_EntIsAlive(self->enemy)) + return false; + + AngleVectors(self->s.angles, forward, NULL, NULL); + VectorSubtract(self->enemy->s.origin, self->s.origin, diff); + diff[2] = 0; + if (VectorNormalize(diff) == 0) + return false; + + return DotProduct(forward, diff) >= 0.80f; +} + +static void m_soldier_stand_up(edict_t *self) +{ + if (self->monsterinfo.nextattack > 0 && m_soldier_prone_shoot_ok(self)) + { + self->monsterinfo.nextattack--; + self->monsterinfo.currentmove = &m_soldier_move_attack5; + self->monsterinfo.nextframe = FRAME_attak504; + return; + } + + self->monsterinfo.nextattack = 0; + self->monsterinfo.currentmove = &m_soldier_move_trip; + self->monsterinfo.nextframe = FRAME_runt08; +} + +static void m_soldier_ai_prone_move(edict_t *self, float dist) +{ + ai_move(self, dist); + + if (!m_soldier_prone_shoot_ok(self)) + m_soldier_stand_up(self); +} + +static void m_soldier_fire_prone(edict_t *self) +{ + const int prone_flash = 8; + + if (self->mtype == M_SOLDIER) + soldier_fireblaster_flash(self, soldier_flash_from_table(soldier_blaster_flash, + sizeof(soldier_blaster_flash) / sizeof(soldier_blaster_flash[0]), prone_flash)); + else if (self->mtype == M_SOLDIERLT) + soldier_firerocket_flash(self, soldier_flash_from_table(soldier_blaster_flash, + sizeof(soldier_blaster_flash) / sizeof(soldier_blaster_flash[0]), prone_flash)); + else if (self->mtype == M_SOLDIERSS) + soldier_fireshotgun_flash(self, soldier_flash_from_table(soldier_shotgun_flash, + sizeof(soldier_shotgun_flash) / sizeof(soldier_shotgun_flash[0]), prone_flash)); + else if (self->mtype == M_SOLDIER_RIPPER) + soldier_fireionripper(self, prone_flash); + else if (self->mtype == M_SOLDIER_BLUEBLASTER) + soldier_fireblueblaster(self, prone_flash); + else if (self->mtype == M_SOLDIER_LASER) + soldier_firelaser(self, soldier_flash_from_table(soldier_machinegun_flash, + sizeof(soldier_machinegun_flash) / sizeof(soldier_machinegun_flash[0]), prone_flash)); +} + +static void m_soldier_start_prone_dodge(edict_t *self) +{ + self->monsterinfo.nextattack = GetRandom(1, 2); + self->monsterinfo.currentmove = &m_soldier_move_attack5; + m_soldier_duck_down(self); + self->monsterinfo.dodge_time = level.time + 2.25f; +} + +mframe_t m_soldier_frames_attack5 [] = +{ + ai_move, 18, m_soldier_duck_down, + ai_move, 11, NULL, + ai_move, 0, NULL, + m_soldier_ai_prone_move, 0, NULL, + m_soldier_ai_prone_move, 0, NULL, + m_soldier_ai_prone_move, 0, m_soldier_fire_prone, + m_soldier_ai_prone_move, 0, m_soldier_fire_prone, + m_soldier_ai_prone_move, 0, m_soldier_fire_prone +}; +static mmove_t m_soldier_move_attack5 = {FRAME_attak501, FRAME_attak508, m_soldier_frames_attack5, m_soldier_stand_up}; + +static void m_soldier_check_prone(edict_t *self) +{ + if (m_soldier_prone_shoot_ok(self)) + self->monsterinfo.currentmove = &m_soldier_move_attack5; +} + +mframe_t m_soldier_frames_trip [] = { - ai_move, 0, m_soldier_jump_attack_takeoff, - ai_move, 0, m_soldier_jump_hold, - ai_move, 0, m_soldier_jump_hold, - ai_move, 0, m_soldier_jump_hold, - ai_move, 0, m_soldier_jump_hold, - ai_move, 0, m_soldier_jump_hold, - ai_move, 0, m_soldier_jump_hold, - ai_move, 0, m_soldier_jump_hold + ai_move, 10, NULL, + ai_move, 2, m_soldier_check_prone, + ai_move, 18, m_soldier_duck_down, + ai_move, 11, NULL, + ai_move, 9, NULL, + ai_move, -11, NULL, + ai_move, -2, NULL, + ai_move, 0, NULL, + ai_move, 6, NULL, + ai_move, -5, NULL, + ai_move, 0, NULL, + ai_move, 1, NULL, + ai_move, 0, NULL, + ai_move, 0, m_soldier_duck_up, + ai_move, 3, NULL, + ai_move, 2, NULL, + ai_move, -1, NULL, + ai_move, 2, NULL, + ai_move, 0, NULL }; -mmove_t m_soldier_move_jump_attack = {FRAME_attak501, FRAME_attak508, m_soldier_frames_jump_attack, m_soldier_run}; + +static void m_soldier_end_trip(edict_t *self) +{ + self->monsterinfo.nextattack = 0; + m_soldier_run(self); +} + +static mmove_t m_soldier_move_trip = {FRAME_runt01, FRAME_runt19, m_soldier_frames_trip, m_soldier_end_trip}; void m_soldier_jump (edict_t *self) { if (self->groundentity) { - if (random() < 0.5) - self->monsterinfo.currentmove = &m_soldier_move_jump_attack; + if (random() < 0.5 && m_soldier_prone_shoot_ok(self)) + m_soldier_start_prone_dodge(self); else - self->monsterinfo.currentmove = &m_soldier_move_jump; + { + self->monsterinfo.nextattack = random() < 0.5f ? 1 : 0; + self->monsterinfo.currentmove = &m_soldier_move_trip; + } } } +static qboolean m_soldier_is_dodge_move(edict_t *self) +{ + return self->monsterinfo.currentmove == &m_soldier_move_duck || + self->monsterinfo.currentmove == &m_soldier_move_jump || + self->monsterinfo.currentmove == &m_soldier_move_attack5 || + self->monsterinfo.currentmove == &m_soldier_move_trip || + self->monsterinfo.currentmove == &m_soldier_move_dodge_slide; +} + +static qboolean m_soldier_dodge_hit_low(edict_t *self, vec3_t dir) +{ + const float duck_height = self->absmax[2] - 33; + + return dir[2] > self->absmin[2] && dir[2] <= duck_height; +} + void m_soldier_dodge (edict_t *self, edict_t *attacker, vec3_t dir, int radius) { if (random() > 0.9) return; if (level.time < self->monsterinfo.dodge_time) return; + if (!attacker) + return; if (OnSameTeam(self, attacker)) return; + if (m_soldier_is_dodge_move(self)) + return; - if (!self->enemy && G_EntIsAlive(attacker)) + self->monsterinfo.attacker = attacker; + if (!G_EntIsAlive(self->enemy) && G_EntIsAlive(attacker)) self->enemy = attacker; if (!radius) { - self->monsterinfo.currentmove = &m_soldier_move_duck; - self->monsterinfo.dodge_time = level.time + 2.0; + if (!m_soldier_dodge_hit_low(self, dir)) + { + if (self->groundentity && m_soldier_prone_shoot_ok(self) && + (self->monsterinfo.currentmove == &m_soldier_move_runandshoot || random() < 0.45f)) + { + m_soldier_start_prone_dodge(self); + } + else + { + m_soldier_start_duck_dodge(self, 1.10f); + } + return; + } + + if (!(self->monsterinfo.aiflags & AI_STAND_GROUND)) + { + self->monsterinfo.nextattack = 0; + self->monsterinfo.lefty = 1 - self->monsterinfo.lefty; + self->monsterinfo.currentmove = &m_soldier_move_dodge_slide; + self->monsterinfo.dodge_time = level.time + 1.35f; + } + else + { + m_soldier_start_duck_dodge(self, 1.10f); + } } else { m_soldier_jump(self); - self->monsterinfo.dodge_time = level.time + 3.0; + self->monsterinfo.dodge_time = level.time + 2.5; } } @@ -739,8 +1018,7 @@ void soldier_pain(edict_t* self, edict_t* other, float kick, int damage) self->monsterinfo.currentmove == &soldier_move_pain_long2 || self->monsterinfo.currentmove == &soldier_move_pain_short1 || self->monsterinfo.currentmove == &soldier_move_pain_short2 || - self->monsterinfo.currentmove == &m_soldier_move_jump || - self->monsterinfo.currentmove == &m_soldier_move_jump_attack) + m_soldier_is_dodge_move(self)) return; // monster players don't get pain state induced @@ -751,14 +1029,18 @@ void soldier_pain(edict_t* self, edict_t* other, float kick, int damage) if (invasion->value == 2) return; + if (level.time >= self->pain_debounce_time) + { + gi.sound(self, CHAN_VOICE, soldier_pain_sound(self), 1, ATTN_NORM, 0); + self->pain_debounce_time = level.time + 0.5; + } + // if we're fidgeting, always go into pain state. if (random() <= (1.0f - self->monsterinfo.pain_chance) && self->monsterinfo.currentmove != &m_soldier_move_stand1 && self->monsterinfo.currentmove != &m_soldier_move_stand3) return; - gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); - if (self->monsterinfo.currentmove == &m_soldier_move_stand1 || self->monsterinfo.currentmove == &m_soldier_move_stand3) { if (random() < 0.5) @@ -1068,7 +1350,7 @@ void m_soldier_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int da return; // regular death - gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + gi.sound (self, CHAN_VOICE, soldier_death_sound(self), 1, ATTN_NORM, 0); self->takedamage = DAMAGE_YES; self->deadflag = DEAD_DEAD; @@ -1107,6 +1389,12 @@ void init_drone_soldier (edict_t *self) sound_sight1 = gi.soundindex ("soldier/solsght1.wav"); sound_sight2 = gi.soundindex ("soldier/solsrch1.wav"); sound_cock = gi.soundindex ("infantry/infatck3.wav"); + sound_pain_light = gi.soundindex ("soldier/solpain2.wav"); + sound_pain = gi.soundindex ("soldier/solpain1.wav"); + sound_pain_ss = gi.soundindex ("soldier/solpain3.wav"); + sound_death_light = gi.soundindex ("soldier/soldeth2.wav"); + sound_death = gi.soundindex ("soldier/soldeth1.wav"); + sound_death_ss = gi.soundindex ("soldier/soldeth3.wav"); self->mass = 100; diff --git a/src/entities/drone/drone_stalker.c b/src/entities/drone/drone_stalker.c index 32b95f20..41bba338 100644 --- a/src/entities/drone/drone_stalker.c +++ b/src/entities/drone/drone_stalker.c @@ -16,12 +16,21 @@ static int sound_punch_hit1; static int sound_punch_hit2; static int sound_idle; +#define STALKER_CEILING_NONE 0 +#define STALKER_CEILING_ON 1 +#define STALKER_CEILING_JUMPING 2 +#define STALKER_CEILING_TRACE_DIST 256 + void drone_ai_stand(edict_t *self, float dist); void drone_ai_run(edict_t *self, float dist); +void drone_ai_run_slide(edict_t *self, float dist); void drone_ai_walk(edict_t *self, float dist); static void stalker_stand(edict_t *self); static void stalker_run(edict_t *self); +static void stalker_set_floor(edict_t *self); +extern mmove_t stalker_move_jump_straightup; +static mmove_t stalker_move_dodge_run; static void stalker_sight(edict_t *self, edict_t *other) { @@ -101,6 +110,245 @@ static void stalker_run(edict_t *self) self->monsterinfo.currentmove = &stalker_move_run; } +static qboolean stalker_dodge_allowed(edict_t *self) +{ + return self && self->health > 0 && self->deadflag == DEAD_NO; +} + +static qboolean stalker_ceiling_allowed(edict_t *self) +{ + return stalker_dodge_allowed(self) && !invasion->value; +} + +static qboolean stalker_on_ceiling(edict_t *self) +{ + return self->style == STALKER_CEILING_ON; +} + +static qboolean stalker_find_ceiling(edict_t *self, float max_dist, float *ceiling_z) +{ + trace_t tr; + vec3_t end; + + VectorCopy(self->s.origin, end); + end[2] += max_dist; + tr = gi.trace(self->s.origin, self->mins, self->maxs, end, self, MASK_MONSTERSOLID); + + if (tr.fraction == 1.0 || !(tr.contents & CONTENTS_SOLID) || tr.ent != world) + return false; + if (tr.plane.normal[2] > -0.7) + return false; + + if (ceiling_z) + *ceiling_z = tr.endpos[2] + self->maxs[2]; + return true; +} + +static void stalker_attach_ceiling(edict_t *self, float ceiling_z) +{ + self->style = STALKER_CEILING_ON; + self->flags |= FL_FLY; + self->gravity = 0; + self->groundentity = NULL; + self->s.angles[ROLL] = 180; + self->s.origin[2] = ceiling_z - self->maxs[2] - 1; + VectorClear(self->velocity); + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + gi.linkentity(self); +} + +static void stalker_set_floor(edict_t *self) +{ + self->style = STALKER_CEILING_NONE; + self->flags &= ~FL_FLY; + self->gravity = 1.0; + self->s.angles[ROLL] = 0; + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; +} + +static qboolean stalker_is_jump_move(edict_t *self) +{ + return self->monsterinfo.currentmove == &stalker_move_jump_straightup || + self->style == STALKER_CEILING_JUMPING; +} + +static qboolean stalker_is_dodge_move(edict_t *self) +{ + return stalker_is_jump_move(self) || + self->monsterinfo.currentmove == &stalker_move_dodge_run; +} + +static void stalker_ceiling_prethink(edict_t *self) +{ + float ceiling_z; + + if (!stalker_on_ceiling(self)) + return; + + if (!stalker_ceiling_allowed(self)) + { + stalker_set_floor(self); + return; + } + + if (stalker_find_ceiling(self, 80, &ceiling_z)) + stalker_attach_ceiling(self, ceiling_z); + else + { + stalker_set_floor(self); + self->velocity[2] = -120; + } +} + +static void stalker_jump_straightup(edict_t *self) +{ + float ceiling_z; + + if (!stalker_ceiling_allowed(self)) + return; + + if (stalker_on_ceiling(self)) + { + stalker_set_floor(self); + self->velocity[2] = -300; + return; + } + + if (!self->groundentity) + return; + + if (stalker_find_ceiling(self, STALKER_CEILING_TRACE_DIST, &ceiling_z)) + { + self->pos1[2] = ceiling_z; + self->flags |= FL_FLY; + self->gravity = 0; + self->velocity[2] = 500; + } + else + { + self->pos1[2] = 0; + self->gravity = 1.0; + self->velocity[2] = 400; + } + + self->style = STALKER_CEILING_JUMPING; + self->s.origin[2] += 1; + self->groundentity = NULL; + self->velocity[0] += crandom() * 5; + self->velocity[1] += crandom() * 5; + self->monsterinfo.pausetime = level.time + 1.3; + self->monsterinfo.aiflags |= AI_HOLD_FRAME; +} + +static void stalker_jump_wait_land(edict_t *self) +{ + float ceiling_z; + + if (self->style != STALKER_CEILING_JUMPING) + return; + + if (!stalker_ceiling_allowed(self)) + { + stalker_set_floor(self); + return; + } + + if (stalker_find_ceiling(self, STALKER_CEILING_TRACE_DIST, &ceiling_z)) + self->pos1[2] = ceiling_z; + + if (self->pos1[2] && self->s.origin[2] + self->maxs[2] >= self->pos1[2] - 12) + { + stalker_attach_ceiling(self, self->pos1[2]); + return; + } + + if (self->pos1[2]) + { + self->flags |= FL_FLY; + self->gravity = 0; + if (self->velocity[2] < 300) + self->velocity[2] = 300; + } + + if (self->groundentity || level.time > self->monsterinfo.pausetime) + { + if (self->pos1[2] && !self->groundentity) + { + stalker_attach_ceiling(self, self->pos1[2]); + return; + } + stalker_set_floor(self); + VectorClear(self->velocity); + return; + } + + self->monsterinfo.aiflags |= AI_HOLD_FRAME; +} + +mframe_t stalker_frames_jump_straightup[] = +{ + ai_move, 1, stalker_jump_straightup, + ai_move, 1, stalker_jump_wait_land, + ai_move, -1, NULL, + ai_move, -1, NULL +}; +mmove_t stalker_move_jump_straightup = { FRAME_jump04, FRAME_jump07, stalker_frames_jump_straightup, stalker_run }; + +static qboolean stalker_start_ceiling_jump(edict_t *self, float cooldown) +{ + if (!stalker_ceiling_allowed(self) || stalker_on_ceiling(self) || !self->groundentity) + return false; + if (stalker_is_dodge_move(self) || level.time < self->monsterinfo.dodge_time) + return false; + + self->monsterinfo.dodge_time = level.time + cooldown; + self->monsterinfo.currentmove = &stalker_move_jump_straightup; + stalker_jump_straightup(self); + return true; +} + +static void stalker_ai_dodge_slide(edict_t *self, float dist) +{ + if (!G_EntIsAlive(self->enemy) && G_EntIsAlive(self->monsterinfo.attacker)) + self->enemy = self->monsterinfo.attacker; + if (!G_EntIsAlive(self->enemy)) + return; + + drone_ai_run_slide(self, dist); +} + +mframe_t stalker_frames_dodge_run[] = +{ + stalker_ai_dodge_slide, 13, NULL, + stalker_ai_dodge_slide, 17, NULL, + stalker_ai_dodge_slide, 21, NULL, + stalker_ai_dodge_slide, 18, NULL +}; +static mmove_t stalker_move_dodge_run = { FRAME_run01, FRAME_run04, stalker_frames_dodge_run, stalker_run }; + +static qboolean stalker_start_dodge_slide(edict_t *self, edict_t *attacker, vec3_t dir) +{ + vec3_t right, diff; + + if (!self->groundentity || stalker_is_dodge_move(self)) + return false; + if (!G_EntIsAlive(self->enemy) && G_EntIsAlive(attacker)) + self->enemy = attacker; + if (!G_EntIsAlive(self->enemy)) + return false; + + AngleVectors(self->s.angles, NULL, right, NULL); + VectorSubtract(dir, self->s.origin, diff); + if (VectorLength(diff) > 1) + self->monsterinfo.lefty = DotProduct(right, diff) >= 0; + else + self->monsterinfo.lefty = 1 - self->monsterinfo.lefty; + + self->monsterinfo.currentmove = &stalker_move_dodge_run; + self->monsterinfo.dodge_time = level.time + 0.4f + random() * 1.2f; + return true; +} + static void stalker_fire_ionripper(edict_t *self) { int damage, speed; @@ -140,10 +388,55 @@ mmove_t stalker_move_shoot = { FRAME_run01, FRAME_run04, stalker_frames_shoot, s static void stalker_attack(edict_t *self) { + if (stalker_ceiling_allowed(self) && !stalker_on_ceiling(self) && self->groundentity && + level.time > self->monsterinfo.melee_finished && random() < 0.33f) + { + if (stalker_start_ceiling_jump(self, 3.0)) + { + self->monsterinfo.melee_finished = level.time + 3.0; + M_DelayNextAttack(self, 1.0, true); + return; + } + } + self->monsterinfo.currentmove = &stalker_move_shoot; M_DelayNextAttack(self, 0.4, true); } +static void stalker_dodge(edict_t *self, edict_t *attacker, vec3_t dir, int radius) +{ + float ceiling_z; + float eta; + + if (!stalker_dodge_allowed(self) || !self->groundentity) + return; + if (!attacker || OnSameTeam(self, attacker)) + return; + if (level.time < self->monsterinfo.dodge_time || stalker_is_dodge_move(self)) + return; + + if (!G_EntIsAlive(self->enemy) && G_EntIsAlive(attacker)) + self->enemy = attacker; + self->monsterinfo.attacker = attacker; + + eta = self->monsterinfo.eta - level.time; + if ((eta < FRAMETIME) || (eta > 5.0f)) + return; + + if (stalker_ceiling_allowed(self) && + stalker_find_ceiling(self, STALKER_CEILING_TRACE_DIST, &ceiling_z) && + stalker_start_ceiling_jump(self, 1.0f + random() * 1.5f)) + return; + + if (radius || random() < 0.35f) + { + if (stalker_start_ceiling_jump(self, 1.0f + random() * 1.5f)) + return; + } + + stalker_start_dodge_slide(self, attacker, dir); +} + static void stalker_swing_attack(edict_t *self) { int damage; @@ -181,6 +474,14 @@ mmove_t stalker_move_swing_r = { FRAME_attack11, FRAME_attack15, stalker_frames_ static void stalker_melee(edict_t *self) { + if (stalker_on_ceiling(self)) + { + stalker_set_floor(self); + self->velocity[2] = -300; + stalker_run(self); + return; + } + if (!G_ValidTarget(self, self->enemy, true, true) || entdist(self, self->enemy) > 96) { self->monsterinfo.melee_finished = level.time + 0.5; @@ -217,6 +518,12 @@ static void stalker_pain(edict_t *self, edict_t *other, float kick, int damage) if (skill->value == 3) return; + if (stalker_is_dodge_move(self)) + return; + + if (damage > 10 && random() < 0.5 && stalker_start_ceiling_jump(self, 3.0)) + return; + self->monsterinfo.currentmove = &stalker_move_pain; } @@ -249,6 +556,9 @@ static void stalker_die(edict_t *self, edict_t *inflictor, edict_t *attacker, in int n; M_Notify(self); + stalker_set_floor(self); + self->prethink = NULL; + self->movetype = MOVETYPE_TOSS; if (self->health <= self->gib_health) { @@ -319,10 +629,12 @@ void init_drone_stalker(edict_t *self) self->monsterinfo.stand = stalker_stand; self->monsterinfo.walk = stalker_walk; self->monsterinfo.run = stalker_run; + self->monsterinfo.dodge = stalker_dodge; self->monsterinfo.attack = stalker_attack; self->monsterinfo.melee = stalker_melee; self->monsterinfo.sight = stalker_sight; self->monsterinfo.idle = stalker_idle; + self->prethink = stalker_ceiling_prethink; self->monsterinfo.pain_chance = 0.3f; self->monsterinfo.jumpup = 64; self->monsterinfo.jumpdn = 512; diff --git a/src/g_local.h b/src/g_local.h index 045e3644..721e9e53 100644 --- a/src/g_local.h +++ b/src/g_local.h @@ -1639,8 +1639,6 @@ enum dronespawn_t { DS_BITCH_HEAT = 27, DS_ARACHNID = 28, DS_MEDIC_COMMANDER = 29, - - DS_COMMANDER = 30, DS_MAKRON = 31, DS_BARON_FIRE = 32, From 23768034d9850d6da8a5c637a7968850393679ca Mon Sep 17 00:00:00 2001 From: Enemy-123 Date: Wed, 22 Apr 2026 20:02:37 -0400 Subject: [PATCH 08/24] fixed projectile origin on some monsters --- lua/variables.lua | 8 +-- src/combat/abilities/emp.c | 2 +- src/combat/abilities/fire.c | 2 +- src/combat/abilities/mirv.c | 2 +- src/combat/abilities/napalm.c | 2 +- src/combat/abilities/spikegrenade.c | 2 +- src/combat/common/v_misc.c | 42 ++++++++++++-- src/combat/weapons/w_sword.c | 2 +- src/entities/drone/drone_arachnid.c | 24 +++++++- src/entities/drone/drone_daedalus.c | 5 +- src/entities/drone/drone_flyer.c | 9 ++- src/entities/drone/drone_guardian.c | 23 +++++++- src/entities/drone/drone_hover.c | 4 +- src/entities/drone/drone_infantry.c | 39 +++++++++---- src/entities/drone/drone_runnertank.c | 6 +- src/entities/drone/drone_soldier.c | 80 ++++++++++++++++++++++----- src/entities/drone/drone_supertank.c | 44 ++++++++++++++- src/entities/drone/drone_tank.c | 24 +++++++- src/entities/drone/g_monster.c | 20 +++---- src/server/v_luasettings.c | 8 +-- 20 files changed, 278 insertions(+), 70 deletions(-) diff --git a/lua/variables.lua b/lua/variables.lua index e1ea33fc..49cad44a 100644 --- a/lua/variables.lua +++ b/lua/variables.lua @@ -791,10 +791,10 @@ M_JANITOR_INITIAL_HEALTH = 125 M_JANITOR_ADDON_HEALTH = 50 M_JANITOR_INITIAL_ARMOR = 300 M_JANITOR_ADDON_ARMOR = 100 -M_MINIGUARDIAN_INITIAL_HEALTH = 275 -M_MINIGUARDIAN_ADDON_HEALTH = 80 -M_MINIGUARDIAN_INITIAL_ARMOR = 200 -M_MINIGUARDIAN_ADDON_ARMOR = 125 +M_MINIGUARDIAN_INITIAL_HEALTH = 105 +M_MINIGUARDIAN_ADDON_HEALTH = 65 +M_MINIGUARDIAN_INITIAL_ARMOR = 120 +M_MINIGUARDIAN_ADDON_ARMOR = 100 M_SOLDIER_RIPPER_INITIAL_HEALTH = 100 M_SOLDIER_RIPPER_ADDON_HEALTH = 40 M_SOLDIER_RIPPER_INITIAL_ARMOR = 50 diff --git a/src/combat/abilities/emp.c b/src/combat/abilities/emp.c index 10fcc7cc..7676fd9f 100644 --- a/src/combat/abilities/emp.c +++ b/src/combat/abilities/emp.c @@ -335,4 +335,4 @@ void Cmd_TossEMP (edict_t *ent) ent->client->ability_delay = level.time + EMP_DELAY; ent->client->pers.inventory[power_cube_index] -= cost; -} +} \ No newline at end of file diff --git a/src/combat/abilities/fire.c b/src/combat/abilities/fire.c index 838117b9..0ca8d6b1 100644 --- a/src/combat/abilities/fire.c +++ b/src/combat/abilities/fire.c @@ -592,4 +592,4 @@ void Cmd_Firewall_f(edict_t* ent, float skill_mult, float cost_mult) ent->client->ability_delay = level.time + FIREWALL_DELAY; ent->client->pers.inventory[power_cube_index] -= cost; ent->num_firewalls++;// increase firewall counter -} +} \ No newline at end of file diff --git a/src/combat/abilities/mirv.c b/src/combat/abilities/mirv.c index 28805f34..67a17a25 100644 --- a/src/combat/abilities/mirv.c +++ b/src/combat/abilities/mirv.c @@ -173,4 +173,4 @@ void Cmd_TossMirv (edict_t *ent) ent->client->ability_delay = level.time + MIRV_DELAY; ent->client->pers.inventory[power_cube_index] -= cost; ent->client->pers.inventory[grenade_index]--; -} +} \ No newline at end of file diff --git a/src/combat/abilities/napalm.c b/src/combat/abilities/napalm.c index 75a06d90..f0244d1f 100644 --- a/src/combat/abilities/napalm.c +++ b/src/combat/abilities/napalm.c @@ -182,4 +182,4 @@ void Cmd_Napalm_f (edict_t *ent) ent->client->pers.inventory[power_cube_index] -= cost; ent->client->pers.inventory[grenade_index]--; ent->client->ability_delay = level.time + NAPALM_DELAY; -} +} \ No newline at end of file diff --git a/src/combat/abilities/spikegrenade.c b/src/combat/abilities/spikegrenade.c index 2927c5d7..5f2417b3 100644 --- a/src/combat/abilities/spikegrenade.c +++ b/src/combat/abilities/spikegrenade.c @@ -245,4 +245,4 @@ void Cmd_SpikeGrenade_f (edict_t *ent) ent->client->pers.inventory[power_cube_index] -= cost; ent->client->pers.inventory[grenade_index]--; ent->client->ability_delay = level.time + SPIKEGRENADE_DELAY; -} +} \ No newline at end of file diff --git a/src/combat/common/v_misc.c b/src/combat/common/v_misc.c index a0d4d25c..0c16458a 100644 --- a/src/combat/common/v_misc.c +++ b/src/combat/common/v_misc.c @@ -242,6 +242,43 @@ int vrx_remove_all_monsters(edict_t *monster_owner) { } +static enum dronespawn_t vrx_pvm_random_drone_type(void) +{ + static const enum dronespawn_t pvm_drone_types[] = + { + DS_GUNNER, + DS_PARASITE, + DS_BITCH, + DS_BRAIN, + DS_MEDIC, + DS_TANK, + DS_MUTANT, + DS_GLADIATOR, + DS_BERSERK, + DS_INFANTRY, + DS_FLYER, + DS_FLOATER, + DS_HOVER, + DS_SHAMBLER, + DS_REDMUTANT, + DS_RUNNERTANK, + DS_GUNCMDR, + DS_DAEDALUS, + DS_GLADB, + DS_GLADC, + DS_STALKER, + DS_GEKK, + DS_BITCH_HEAT, + DS_ARACHNID, + DS_MEDIC_COMMANDER, + DS_JANITOR, + DS_MiniGuardian + }; + + const int count = (int)(sizeof(pvm_drone_types) / sizeof(pvm_drone_types[0])); + return pvm_drone_types[GetRandom(0, count - 1)]; +} + void vrx_pvm_spawn_monsters(edict_t* self, int max_monsters, int total_monsters) { int max_spawn_this_cycle = GetRandom(0, max_monsters - total_monsters); @@ -252,10 +289,7 @@ void vrx_pvm_spawn_monsters(edict_t* self, int max_monsters, int total_monsters) max_spawn_this_cycle = max(max_spawn_this_cycle, 3); while (total_monsters < max_monsters && max_spawn_this_cycle > 0) { - int rnd; - do { - rnd = GetRandom(1, DS_MEDIC_COMMANDER); // az: don't spawn soldiers or helper summons - } while (rnd == DS_SOLDIER || rnd == DS_DECOY || rnd == DS_SKELETON || rnd == DS_GOLEM); + enum dronespawn_t rnd = vrx_pvm_random_drone_type(); edict_t* scan; if ((scan = vrx_create_new_drone(self, rnd, true, true, self->monsterinfo.scale)) != NULL) { diff --git a/src/combat/weapons/w_sword.c b/src/combat/weapons/w_sword.c index c75d18a1..4e7fd9b7 100644 --- a/src/combat/weapons/w_sword.c +++ b/src/combat/weapons/w_sword.c @@ -520,4 +520,4 @@ void Weapon_Sword (edict_t *ent) Weapon_Generic (ent, 4, 20, 52, 55, pause_frames, lance_frames, Weapon_Lance_Fire); else Weapon_Generic (ent, 4, 20, 52, 55, pause_frames, sword_frames, Weapon_Sword_Fire); -} +} \ No newline at end of file diff --git a/src/entities/drone/drone_arachnid.c b/src/entities/drone/drone_arachnid.c index c5eaa215..45598acf 100644 --- a/src/entities/drone/drone_arachnid.c +++ b/src/entities/drone/drone_arachnid.c @@ -121,9 +121,29 @@ static void arachnid_rail(edict_t *self) if (M_RAILGUN_DMG_MAX && damage > M_RAILGUN_DMG_MAX) damage = M_RAILGUN_DMG_MAX; - flash_number = MZ2_GLADIATOR_RAILGUN_1; + switch (self->s.frame) + { + case FRAME_rails7: + flash_number = MZ2_ARACHNID_RAIL2; + break; + case FRAME_rails_up2: + case FRAME_rails_up9: + flash_number = MZ2_ARACHNID_RAIL_UP1; + break; + case FRAME_rails_up5: + case FRAME_rails_up11: + flash_number = MZ2_ARACHNID_RAIL_UP2; + break; + case FRAME_rails3: + default: + flash_number = MZ2_ARACHNID_RAIL1; + break; + } + AngleVectors(self->s.angles, forward, right, NULL); - VectorSet(offset, 48, (self->s.frame == FRAME_rails7 || self->s.frame == FRAME_rails_up5 || self->s.frame == FRAME_rails_up11) ? -16 : 16, 28); + VectorCopy(monster_flash_offset[flash_number], offset); + if (self->s.scale) + VectorScale(offset, self->s.scale, offset); G_ProjectSource(self->s.origin, offset, forward, right, start); VectorSubtract(self->pos1, start, dir); VectorNormalize(dir); diff --git a/src/entities/drone/drone_daedalus.c b/src/entities/drone/drone_daedalus.c index 920852dd..b3d5b3cf 100644 --- a/src/entities/drone/drone_daedalus.c +++ b/src/entities/drone/drone_daedalus.c @@ -80,7 +80,7 @@ mmove_t daedalus_move_end_attack = { FRAME_attak107, FRAME_attak108, daedalus_fr static void daedalus_fire_grenade(edict_t *self) { - int damage, speed; + int damage, speed, flash_number; vec3_t forward, right, start, offset; if (!G_EntExists(self->enemy)) @@ -101,8 +101,9 @@ static void daedalus_fire_grenade(edict_t *self) VectorSet(offset, 1.7, -7.0, 11.3); G_ProjectSource(self->s.origin, offset, forward, right, start); + flash_number = MZ2_GUNCMDR_GRENADE_MORTAR_1; MonsterAim(self, M_PROJECTILE_ACC, speed, false, -1, forward, start); - monster_fire_grenade(self, start, forward, damage, speed, MZ2_HOVER_BLASTER_1); + monster_fire_grenade(self, start, forward, damage, speed, flash_number); } static void daedalus_reattack(edict_t *self) diff --git a/src/entities/drone/drone_flyer.c b/src/entities/drone/drone_flyer.c index d47f1a48..58a5c2bf 100644 --- a/src/entities/drone/drone_flyer.c +++ b/src/entities/drone/drone_flyer.c @@ -349,7 +349,7 @@ mmove_t flyer_move_bankleft = {FRAME_bankl01, FRAME_bankl07, flyer_frames_bankle void flyer_fire (edict_t *self, int flash_number) { - vec3_t start, forward; + vec3_t start, forward, right, offset; int effect, damage; if ((self->s.frame == FRAME_attak204) || (self->s.frame == FRAME_attak207) || (self->s.frame == FRAME_attak210)) @@ -361,7 +361,12 @@ void flyer_fire (edict_t *self, int flash_number) if (M_HYPERBLASTER_DMG_MAX && damage > M_HYPERBLASTER_DMG_MAX) damage = M_HYPERBLASTER_DMG_MAX; - MonsterAim(self, M_PROJECTILE_ACC, 2000, false, MZ2_FLOAT_BLASTER_1, forward, start); + AngleVectors(self->s.angles, forward, right, NULL); + VectorCopy(monster_flash_offset[flash_number], offset); + if (self->s.scale) + VectorScale(offset, self->s.scale, offset); + G_ProjectSource(self->s.origin, offset, forward, right, start); + MonsterAim(self, M_PROJECTILE_ACC, 2000, false, -1, forward, start); monster_fire_blaster(self, start, forward, damage, 2000, effect, BLASTER_PROJ_BOLT, 2.0, false, flash_number); } diff --git a/src/entities/drone/drone_guardian.c b/src/entities/drone/drone_guardian.c index 26cd7e11..d8b51234 100644 --- a/src/entities/drone/drone_guardian.c +++ b/src/entities/drone/drone_guardian.c @@ -17,6 +17,21 @@ static int sound_pew; void guardian_run(edict_t *self); +static void guardian_project_laser_frame_origin(edict_t *self, vec3_t forward, vec3_t right, vec3_t start) +{ + vec3_t offset; + + if (self->s.frame & 1) + VectorSet(offset, 125, -70, 60); + else + VectorSet(offset, 112, -62, 60); + + if (self->s.scale) + VectorScale(offset, self->s.scale, offset); + + G_ProjectSource(self->s.origin, offset, forward, right, start); +} + static void guardian_footstep(edict_t *self) { gi.sound(self, CHAN_BODY, sound_step, 1, ATTN_NORM, 0); @@ -292,7 +307,7 @@ static int guardian_grenade_flash(edict_t *self) void guardian_grenade(edict_t *self) { - vec3_t forward, start; + vec3_t forward, right, start; int damage, speed, flash_number; if (!G_EntExists(self->enemy)) @@ -306,6 +321,12 @@ void guardian_grenade(edict_t *self) speed = M_GRENADELAUNCHER_SPEED_MAX; flash_number = guardian_grenade_flash(self); + if (self->mtype == M_MINIGUARDIAN) + { + AngleVectors(self->s.angles, forward, right, NULL); + guardian_project_laser_frame_origin(self, forward, right, start); + flash_number = -1; + } MonsterAim(self, M_PROJECTILE_ACC, speed, true, flash_number, forward, start); monster_fire_grenade(self, start, forward, damage, speed, flash_number); } diff --git a/src/entities/drone/drone_hover.c b/src/entities/drone/drone_hover.c index 8f5b82e4..6580560d 100644 --- a/src/entities/drone/drone_hover.c +++ b/src/entities/drone/drone_hover.c @@ -471,8 +471,8 @@ void hover_fire_blaster (edict_t *self) if (M_HYPERBLASTER_DMG_MAX && damage > M_HYPERBLASTER_DMG_MAX) damage = M_HYPERBLASTER_DMG_MAX; - MonsterAim(self, M_PROJECTILE_ACC, speed, true, MZ2_HOVER_BLASTER_1, forward, start); - monster_fire_rocket (self, start, forward, damage, speed, MZ2_HOVER_BLASTER_1); + MonsterAim(self, M_PROJECTILE_ACC, speed, true, MZ2_BOSS2_ROCKET_3, forward, start); + monster_fire_rocket (self, start, forward, damage, speed, MZ2_BOSS2_ROCKET_3); } void hover_stand (edict_t *self) diff --git a/src/entities/drone/drone_infantry.c b/src/entities/drone/drone_infantry.c index fa558f7e..fa6190e3 100644 --- a/src/entities/drone/drone_infantry.c +++ b/src/entities/drone/drone_infantry.c @@ -32,7 +32,25 @@ void drone_ai_run_slide(edict_t *self, float dist); static void infantry_run_fire(edict_t *self); extern mmove_t infantry_move_attack4; +static void infantry_project_flash(edict_t *self, int flash_number, vec3_t forward, vec3_t start) +{ + vec3_t right, offset; + + AngleVectors(self->s.angles, forward, right, NULL); + VectorCopy(monster_flash_offset[flash_number], offset); + if (self->s.scale) + VectorScale(offset, self->s.scale, offset); + G_ProjectSource(self->s.origin, offset, forward, right, start); +} +static int infantry_20mm_flash_for_frame(edict_t *self) +{ + if (self->s.frame >= FRAME_run201 && self->s.frame <= FRAME_run208) + return MZ2_INFANTRY_MACHINEGUN_14 + (self->s.frame - FRAME_run201); + if (self->s.frame == FRAME_attak111) + return MZ2_INFANTRY_MACHINEGUN_1; + return MZ2_INFANTRY_MACHINEGUN_2 + (self->s.frame - FRAME_death211); +} mframe_t infantry_frames_stand [] = { @@ -199,14 +217,14 @@ void InfantryMachineGun (edict_t *self) if (self->s.frame == FRAME_attak111) { flash_number = MZ2_INFANTRY_MACHINEGUN_1; - MonsterAim(self, 0.8, 0, false, flash_number, forward, start); + infantry_project_flash(self, flash_number, forward, start); + MonsterAim(self, 0.8, 0, false, -1, forward, start); } else { flash_number = MZ2_INFANTRY_MACHINEGUN_2 + (self->s.frame - FRAME_death211); - AngleVectors (self->s.angles, forward, right, NULL); - G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start); + infantry_project_flash(self, flash_number, forward, start); VectorSubtract (self->s.angles, aimangles[flash_number-MZ2_INFANTRY_MACHINEGUN_2], vec); AngleVectors (vec, forward, NULL, NULL); @@ -218,7 +236,7 @@ void InfantryMachineGun (edict_t *self) void Infantry20mm(edict_t* self) { - vec3_t start, forward, right, vec; + vec3_t start, forward, vec; int damage, flash_number; const float range = M_20MM_RANGE_BASE + M_20MM_RANGE_ADDON * drone_damagelevel(self); qboolean run_attack = (self->s.frame >= FRAME_run201 && self->s.frame <= FRAME_run208); @@ -227,23 +245,22 @@ void Infantry20mm(edict_t* self) if (M_20MM_DMG_MAX && damage > M_20MM_DMG_MAX) damage = M_20MM_DMG_MAX; + flash_number = infantry_20mm_flash_for_frame(self); + if (self->s.frame == FRAME_attak111 || run_attack) { - flash_number = MZ2_INFANTRY_MACHINEGUN_1; - MonsterAim(self, M_HITSCAN_CONT_ACC, 0, false, flash_number, forward, start); + infantry_project_flash(self, flash_number, forward, start); + MonsterAim(self, M_HITSCAN_CONT_ACC, 0, false, -1, forward, start); } else { - flash_number = MZ2_INFANTRY_MACHINEGUN_2 + (self->s.frame - FRAME_death211); - - AngleVectors(self->s.angles, forward, right, NULL); - G_ProjectSource(self->s.origin, monster_flash_offset[flash_number], forward, right, start); + infantry_project_flash(self, flash_number, forward, start); VectorSubtract(self->s.angles, aimangles[flash_number - MZ2_INFANTRY_MACHINEGUN_2], vec); AngleVectors(vec, forward, NULL, NULL); } - monster_fire_20mm(self, start, forward, damage, damage, range, MZ_IONRIPPER | MZ_SILENCED); + monster_fire_20mm(self, start, forward, damage, damage, range, flash_number); } void infantry_sight (edict_t *self, edict_t *other) diff --git a/src/entities/drone/drone_runnertank.c b/src/entities/drone/drone_runnertank.c index f2fb1ded..265ed7e1 100644 --- a/src/entities/drone/drone_runnertank.c +++ b/src/entities/drone/drone_runnertank.c @@ -226,7 +226,7 @@ static void runnertank_rail(edict_t *self) damage = M_RAILGUN_DMG_MAX; MonsterAim(self, M_HITSCAN_INSTANT_ACC, 0, false, flash_number, forward, start); - monster_fire_railgun(self, start, forward, damage, damage, MZ2_GLADIATOR_RAILGUN_1); + monster_fire_railgun(self, start, forward, damage, damage, flash_number); } static void runnertank_rocket(edict_t *self) @@ -237,9 +237,9 @@ static void runnertank_rocket(edict_t *self) if (!self->enemy || !self->enemy->inuse) return; - if (self->s.frame == FRAME_attak324) + if (self->s.frame == FRAME_attak313) flash_number = MZ2_TANK_ROCKET_1; - else if (self->s.frame == FRAME_attak325) + else if (self->s.frame == FRAME_attak316) flash_number = MZ2_TANK_ROCKET_2; else flash_number = MZ2_TANK_ROCKET_3; diff --git a/src/entities/drone/drone_soldier.c b/src/entities/drone/drone_soldier.c index 348951bb..917db797 100644 --- a/src/entities/drone/drone_soldier.c +++ b/src/entities/drone/drone_soldier.c @@ -271,7 +271,15 @@ static int soldier_flash_from_table(const int *flashes, size_t count, int flash_ return flashes[flash_number]; } -static void soldier_fireblaster_flash(edict_t* self, int flash) +static void soldier_project_raw_flash_origin(edict_t *self, int flash, vec3_t forward, vec3_t start) +{ + vec3_t right; + + AngleVectors(self->s.angles, forward, right, NULL); + G_ProjectSource(self->s.origin, monster_flash_offset[flash], forward, right, start); +} + +static void soldier_fireblaster_flash_ex(edict_t* self, int flash, qboolean raw_origin) { int damage, speed; vec3_t forward, start; @@ -287,16 +295,27 @@ static void soldier_fireblaster_flash(edict_t* self, int flash) if (M_BLASTER_SPEED_MAX && speed > M_BLASTER_SPEED_MAX) speed = M_BLASTER_SPEED_MAX; - MonsterAim(self, M_PROJECTILE_ACC, speed, false, flash, forward, start); + if (raw_origin) + { + soldier_project_raw_flash_origin(self, flash, forward, start); + MonsterAim(self, M_PROJECTILE_ACC, speed, false, -1, forward, start); + } + else + MonsterAim(self, M_PROJECTILE_ACC, speed, false, flash, forward, start); monster_fire_blaster(self, start, forward, damage, speed, EF_BLASTER, BLASTER_PROJ_BOLT, 2.0, true, flash); } +static void soldier_fireblaster_flash(edict_t* self, int flash) +{ + soldier_fireblaster_flash_ex(self, flash, false); +} + void soldier_fireblaster(edict_t* self) { soldier_fireblaster_flash(self, MZ2_SOLDIER_BLASTER_8); } -static void soldier_firerocket_flash(edict_t* self, int flash) +static void soldier_firerocket_flash_ex(edict_t* self, int flash, qboolean raw_origin) { int damage, speed; vec3_t forward, start; @@ -311,10 +330,21 @@ static void soldier_firerocket_flash(edict_t* self, int flash) if (M_ROCKETLAUNCHER_SPEED_MAX && speed > M_ROCKETLAUNCHER_SPEED_MAX) speed = M_ROCKETLAUNCHER_SPEED_MAX; - MonsterAim(self, M_PROJECTILE_ACC, speed, true, flash, forward, start); + if (raw_origin) + { + soldier_project_raw_flash_origin(self, flash, forward, start); + MonsterAim(self, M_PROJECTILE_ACC, speed, true, -1, forward, start); + } + else + MonsterAim(self, M_PROJECTILE_ACC, speed, true, flash, forward, start); monster_fire_rocket(self, start, forward, damage, speed, flash); } +static void soldier_firerocket_flash(edict_t* self, int flash) +{ + soldier_firerocket_flash_ex(self, flash, false); +} + void soldier_firerocket(edict_t* self) { soldier_firerocket_flash(self, MZ2_SOLDIER_BLASTER_8); @@ -340,7 +370,7 @@ void soldier_fireshotgun(edict_t* self) soldier_fireshotgun_flash(self, MZ2_SOLDIER_SHOTGUN_8); } -void soldier_fireionripper(edict_t* self, int flash_number) +static void soldier_fireionripper_ex(edict_t* self, int flash_number, qboolean raw_origin) { int damage, speed; int flash; @@ -354,11 +384,22 @@ void soldier_fireionripper(edict_t* self, int flash_number) damage = IONRIPPER_INITIAL_DAMAGE + IONRIPPER_ADDON_DAMAGE * drone_damagelevel(self); speed = IONRIPPER_INITIAL_SPEED + IONRIPPER_ADDON_SPEED * drone_damagelevel(self); - MonsterAim(self, M_PROJECTILE_ACC, speed, false, flash, forward, start); + if (raw_origin) + { + soldier_project_raw_flash_origin(self, flash, forward, start); + MonsterAim(self, M_PROJECTILE_ACC, speed, false, -1, forward, start); + } + else + MonsterAim(self, M_PROJECTILE_ACC, speed, false, flash, forward, start); monster_fire_ionripper(self, start, forward, damage, speed, EF_IONRIPPER, flash); } -void soldier_fireblueblaster(edict_t* self, int flash_number) +void soldier_fireionripper(edict_t* self, int flash_number) +{ + soldier_fireionripper_ex(self, flash_number, false); +} + +static void soldier_fireblueblaster_ex(edict_t* self, int flash_number, qboolean raw_origin) { int damage, speed; int flash; @@ -377,10 +418,21 @@ void soldier_fireblueblaster(edict_t* self, int flash_number) if (speed < 400) speed = 400; - MonsterAim(self, M_PROJECTILE_ACC, speed, false, flash, forward, start); + if (raw_origin) + { + soldier_project_raw_flash_origin(self, flash, forward, start); + MonsterAim(self, M_PROJECTILE_ACC, speed, false, -1, forward, start); + } + else + MonsterAim(self, M_PROJECTILE_ACC, speed, false, flash, forward, start); monster_fire_blueblaster(self, start, forward, damage, speed, EF_BLUEHYPERBLASTER, flash); } +void soldier_fireblueblaster(edict_t* self, int flash_number) +{ + soldier_fireblueblaster_ex(self, flash_number, false); +} + static void soldier_laser_update(edict_t *laser) { edict_t *self; @@ -763,18 +815,18 @@ static void m_soldier_fire_prone(edict_t *self) const int prone_flash = 8; if (self->mtype == M_SOLDIER) - soldier_fireblaster_flash(self, soldier_flash_from_table(soldier_blaster_flash, - sizeof(soldier_blaster_flash) / sizeof(soldier_blaster_flash[0]), prone_flash)); + soldier_fireblaster_flash_ex(self, soldier_flash_from_table(soldier_blaster_flash, + sizeof(soldier_blaster_flash) / sizeof(soldier_blaster_flash[0]), prone_flash), true); else if (self->mtype == M_SOLDIERLT) - soldier_firerocket_flash(self, soldier_flash_from_table(soldier_blaster_flash, - sizeof(soldier_blaster_flash) / sizeof(soldier_blaster_flash[0]), prone_flash)); + soldier_firerocket_flash_ex(self, soldier_flash_from_table(soldier_blaster_flash, + sizeof(soldier_blaster_flash) / sizeof(soldier_blaster_flash[0]), prone_flash), true); else if (self->mtype == M_SOLDIERSS) soldier_fireshotgun_flash(self, soldier_flash_from_table(soldier_shotgun_flash, sizeof(soldier_shotgun_flash) / sizeof(soldier_shotgun_flash[0]), prone_flash)); else if (self->mtype == M_SOLDIER_RIPPER) - soldier_fireionripper(self, prone_flash); + soldier_fireionripper_ex(self, prone_flash, true); else if (self->mtype == M_SOLDIER_BLUEBLASTER) - soldier_fireblueblaster(self, prone_flash); + soldier_fireblueblaster_ex(self, prone_flash, true); else if (self->mtype == M_SOLDIER_LASER) soldier_firelaser(self, soldier_flash_from_table(soldier_machinegun_flash, sizeof(soldier_machinegun_flash) / sizeof(soldier_machinegun_flash[0]), prone_flash)); diff --git a/src/entities/drone/drone_supertank.c b/src/entities/drone/drone_supertank.c index 57b53e98..2535dbac 100644 --- a/src/entities/drone/drone_supertank.c +++ b/src/entities/drone/drone_supertank.c @@ -125,6 +125,29 @@ mframe_t supertank_frames_run [] = }; mmove_t supertank_move_run = {FRAME_forwrd_1, FRAME_forwrd_18, supertank_frames_run, NULL}; +mframe_t supertank_frames_run_janitor [] = +{ + drone_ai_run, 18, TreadSound, + drone_ai_run, 18, NULL, + drone_ai_run, 18, NULL, + drone_ai_run, 18, NULL, + drone_ai_run, 18, NULL, + drone_ai_run, 18, NULL, + drone_ai_run, 18, NULL, + drone_ai_run, 18, NULL, + drone_ai_run, 18, NULL, + drone_ai_run, 18, NULL, + drone_ai_run, 18, NULL, + drone_ai_run, 18, NULL, + drone_ai_run, 18, NULL, + drone_ai_run, 18, NULL, + drone_ai_run, 18, NULL, + drone_ai_run, 18, NULL, + drone_ai_run, 18, NULL, + drone_ai_run, 18, NULL +}; +mmove_t supertank_move_run_janitor = {FRAME_forwrd_1, FRAME_forwrd_18, supertank_frames_run_janitor, NULL}; + // // walk // @@ -170,6 +193,8 @@ void supertank_run (edict_t *self) { if (self->monsterinfo.aiflags & AI_STAND_GROUND) self->monsterinfo.currentmove = &supertank_move_stand; + else if (self->mtype == M_JANITOR) + self->monsterinfo.currentmove = &supertank_move_run_janitor; else self->monsterinfo.currentmove = &supertank_move_run; } @@ -421,7 +446,7 @@ void supertank_reattack1(edict_t *self) void supertankRocket (edict_t *self) { - vec3_t forward, start; + vec3_t forward, right, start, offset; int damage, speed, flash_number; if (self->s.frame == FRAME_attak2_8) @@ -434,7 +459,22 @@ void supertankRocket (edict_t *self) damage = 50 + 10 * drone_damagelevel(self); speed = 650 + 30 * drone_damagelevel(self); - MonsterAim(self, 0.5, speed, true, flash_number, forward, start); + if (self->mtype == M_JANITOR) + { + AngleVectors(self->s.angles, forward, right, NULL); + if (flash_number == MZ2_SUPERTANK_ROCKET_1) + VectorSet(offset, 16.0, -22.5, 108.7); + else if (flash_number == MZ2_SUPERTANK_ROCKET_2) + VectorSet(offset, 16.0, -33.4, 106.7); + else + VectorSet(offset, 16.0, -42.8, 104.7); + if (self->s.scale) + VectorScale(offset, self->s.scale, offset); + G_ProjectSource(self->s.origin, offset, forward, right, start); + MonsterAim(self, 0.5, speed, true, -1, forward, start); + } + else + MonsterAim(self, 0.5, speed, true, flash_number, forward, start); monster_fire_rocket (self, start, forward, damage, speed, flash_number); } diff --git a/src/entities/drone/drone_tank.c b/src/entities/drone/drone_tank.c index 24f907fc..2d76ef3f 100644 --- a/src/entities/drone/drone_tank.c +++ b/src/entities/drone/drone_tank.c @@ -271,7 +271,7 @@ void myTankRail (edict_t *self) damage = M_RAILGUN_DMG_MAX; MonsterAim(self, M_HITSCAN_INSTANT_ACC, 0, false, flash_number, forward, start); - monster_fire_railgun(self, start, forward, damage, damage, MZ2_GLADIATOR_RAILGUN_1); + monster_fire_railgun(self, start, forward, damage, damage, flash_number); } void myTankBlaster(edict_t* self) @@ -340,7 +340,7 @@ void myTankRocket(edict_t* self) void myTankMachineGun(edict_t* self) { - vec3_t forward, start; + vec3_t forward, right, start, dir, vec; int flash_number, damage; // sanity check @@ -354,7 +354,25 @@ void myTankMachineGun(edict_t* self) if (M_MACHINEGUN_DMG_MAX && damage > 2 * M_MACHINEGUN_DMG_MAX) damage = 2 * M_MACHINEGUN_DMG_MAX; - MonsterAim(self, M_HITSCAN_CONT_ACC, 0, false, flash_number, forward, start); + AngleVectors(self->s.angles, forward, right, NULL); + G_ProjectSource(self->s.origin, monster_flash_offset[flash_number], forward, right, start); + + if (self->enemy) + { + VectorCopy(self->enemy->s.origin, vec); + vec[2] += self->enemy->viewheight; + VectorSubtract(vec, start, vec); + vectoangles(vec, dir); + } + else + dir[0] = 0; + if (self->s.frame <= FRAME_attak415) + dir[1] = self->s.angles[1] - 8 * (self->s.frame - FRAME_attak411); + else + dir[1] = self->s.angles[1] + 8 * (self->s.frame - FRAME_attak419); + dir[2] = 0; + + AngleVectors(dir, forward, NULL, NULL); monster_fire_bullet(self, start, forward, damage, 40, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flash_number); diff --git a/src/entities/drone/g_monster.c b/src/entities/drone/g_monster.c index 489e7acb..0490eb23 100644 --- a/src/entities/drone/g_monster.c +++ b/src/entities/drone/g_monster.c @@ -98,6 +98,8 @@ void monster_fire_20mm(edict_t* self, vec3_t start, vec3_t dir, int damage, int { float chance; + (void)flashtype; + // holy freeze reduces firing rate by 50% if (que_typeexists(self->curses, AURA_HOLYFREEZE)) { @@ -116,12 +118,7 @@ void monster_fire_20mm(edict_t* self, vec3_t start, vec3_t dir, int damage, int damage = vrx_increase_monster_damage_by_talent(self->activator, damage); fire_20mm(self, start, dir, damage, kick, range); - gi.WriteByte(svc_muzzleflash); - gi.WriteShort(self - g_edicts); - gi.WriteByte(flashtype); - gi.multicast(start, MULTICAST_PVS); - - gi.sound(self, CHAN_WEAPON, gi.soundindex("weapons/sgun1.wav"), 1, ATTN_NORM, 0); + gi.positioned_sound(start, self, CHAN_WEAPON, gi.soundindex("weapons/sgun1.wav"), 1, ATTN_NORM, 0); if (vrx_spawn_nonessential_ent(self->s.origin)) ThrowShell(self, "models/objects/shell1/tris.md2", start); @@ -628,10 +625,13 @@ void monster_fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damag damage = vrx_increase_monster_damage_by_talent(self->activator, damage); fire_grenade (self, start, aimdir, damage, speed, 2.5, radius, damage); - gi.WriteByte (svc_muzzleflash2); - gi.WriteShort (self - g_edicts); - gi.WriteByte (flashtype); - gi.multicast (start, MULTICAST_PVS); + if (flashtype >= 0) + { + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); + } } void monster_fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype) diff --git a/src/server/v_luasettings.c b/src/server/v_luasettings.c index 4b1746e1..5471972b 100644 --- a/src/server/v_luasettings.c +++ b/src/server/v_luasettings.c @@ -1146,10 +1146,10 @@ void Lua_LoadVariables() M_GUARDIAN_ADDON_HEALTH = vrx_lua_get_variable("M_GUARDIAN_ADDON_HEALTH", 1200); M_GUARDIAN_INITIAL_ARMOR = vrx_lua_get_variable("M_GUARDIAN_INITIAL_ARMOR", 550); M_GUARDIAN_ADDON_ARMOR = vrx_lua_get_variable("M_GUARDIAN_ADDON_ARMOR", 250); - M_JANITOR_INITIAL_HEALTH = vrx_lua_get_variable("M_JANITOR_INITIAL_HEALTH", 1200); - M_JANITOR_ADDON_HEALTH = vrx_lua_get_variable("M_JANITOR_ADDON_HEALTH", 450); - M_JANITOR_INITIAL_ARMOR = vrx_lua_get_variable("M_JANITOR_INITIAL_ARMOR", 500); - M_JANITOR_ADDON_ARMOR = vrx_lua_get_variable("M_JANITOR_ADDON_ARMOR", 150); + M_JANITOR_INITIAL_HEALTH = vrx_lua_get_variable("M_JANITOR_INITIAL_HEALTH", 125); + M_JANITOR_ADDON_HEALTH = vrx_lua_get_variable("M_JANITOR_ADDON_HEALTH", 50); + M_JANITOR_INITIAL_ARMOR = vrx_lua_get_variable("M_JANITOR_INITIAL_ARMOR", 300); + M_JANITOR_ADDON_ARMOR = vrx_lua_get_variable("M_JANITOR_ADDON_ARMOR", 100); M_MINIGUARDIAN_INITIAL_HEALTH = vrx_lua_get_variable("M_MINIGUARDIAN_INITIAL_HEALTH", 400); M_MINIGUARDIAN_ADDON_HEALTH = vrx_lua_get_variable("M_MINIGUARDIAN_ADDON_HEALTH", 100); M_MINIGUARDIAN_INITIAL_ARMOR = vrx_lua_get_variable("M_MINIGUARDIAN_INITIAL_ARMOR", 450); From ea802a56bf9151bcd39da16209aa0d449b878d7d Mon Sep 17 00:00:00 2001 From: Enemy-123 Date: Thu, 23 Apr 2026 15:15:45 -0400 Subject: [PATCH 09/24] adding more LUAs for dabeam, etfrifle, ionripper, disruptor monster attacks, brain rarely uses laser now, stalker's speed on climbing up/down ceilings improved --- lua/variables.lua | 29 +++++++++++++-- src/characters/settings.h | 23 ++++++++++++ src/characters/v_utils.c | 11 +++--- src/combat/common/v_misc.c | 2 +- src/entities/drone/drone_brain.c | 10 +++-- src/entities/drone/drone_gladiator.c | 6 ++- src/entities/drone/drone_guardian.c | 24 ++++++++---- src/entities/drone/drone_misc.c | 6 +-- src/entities/drone/drone_soldier.c | 2 + src/entities/drone/drone_stalker.c | 55 +++++++++++++++++++++++----- src/entities/g_spawn.c | 8 ++-- src/g_local.h | 2 +- src/server/v_luasettings.c | 31 ++++++++++++++-- 13 files changed, 164 insertions(+), 45 deletions(-) diff --git a/lua/variables.lua b/lua/variables.lua index 49cad44a..664540e5 100644 --- a/lua/variables.lua +++ b/lua/variables.lua @@ -834,6 +834,9 @@ M_SHOTGUN_DMG_MAX = 0 M_RAILGUN_DMG_BASE = 50 M_RAILGUN_DMG_ADDON = 15 M_RAILGUN_DMG_MAX = 0 +M_DABEAM_DMG_BASE = 2 +M_DABEAM_DMG_ADDON = 1 +M_DABEAM_DMG_MAX = 0 M_MELEE_DMG_BASE = 50 M_MELEE_DMG_ADDON = 25 M_MELEE_DMG_MAX = 0 @@ -851,16 +854,16 @@ M_BLASTER_SPEED_BASE = 1000 M_BLASTER_SPEED_ADDON = 50 M_BLASTER_SPEED_MAX = 1500 M_BLASTER2_DMG_BASE = 50 -M_BLASTER2_DMG_ADDON = 2 +M_BLASTER2_DMG_ADDON = 2 M_BLASTER2_DMG_MAX = 0 M_BLASTER2_SPEED_BASE = 500 M_BLASTER2_SPEED_ADDON = 0 -M_BLASTER2_SPEED_MAX = 0 +M_BLASTER2_SPEED_MAX = 0 M_PLASMA_DMG_BASE = 50 M_PLASMA_DMG_ADDON = 10 M_PLASMA_DMG_MAX = 0 -M_PLASMA_SPEED_BASE = 1000 -M_PLASMA_SPEED_ADDON = 50 +M_PLASMA_SPEED_BASE = 750 +M_PLASMA_SPEED_ADDON = 30 M_PLASMA_SPEED_MAX = 1500 M_PLASMA_DAMAGE_RADIUS = 40 M_20MM_RANGE_BASE = 750 @@ -869,6 +872,24 @@ M_20MM_RANGE_MAX = 750 M_20MM_DMG_BASE = 40 M_20MM_DMG_ADDON = 2 M_20MM_DMG_MAX = 0 +M_ETFRIFLE_DMG_BASE = 15 +M_ETFRIFLE_DMG_ADDON = 2 +M_ETFRIFLE_DMG_MAX = 0 +M_ETFRIFLE_SPEED_BASE = 750 +M_ETFRIFLE_SPEED_ADDON = 40 +M_ETFRIFLE_SPEED_MAX = 1200 +M_IONRIPPER_DMG_BASE = 12 +M_IONRIPPER_DMG_ADDON = 8 +M_IONRIPPER_DMG_MAX = 0 +M_IONRIPPER_SPEED_BASE = 680 +M_IONRIPPER_SPEED_ADDON = 30 +M_IONRIPPER_SPEED_MAX = 1000 +M_DISRUPTOR_DMG_BASE = 35 +M_DISRUPTOR_DMG_ADDON = 15 +M_DISRUPTOR_DMG_MAX = 0 +M_DISRUPTOR_SPEED_BASE = 700 +M_DISRUPTOR_SPEED_ADDON = 40 +M_DISRUPTOR_SPEED_MAX = 1000 -- 20mm cannon diff --git a/src/characters/settings.h b/src/characters/settings.h index 68ca96b6..b20bd67b 100644 --- a/src/characters/settings.h +++ b/src/characters/settings.h @@ -869,6 +869,7 @@ VRX_V_LUASETTINGS_IMPL double M_RAILGUN_DMG_ADDON; VRX_V_LUASETTINGS_IMPL double M_RAILGUN_DMG_MAX; VRX_V_LUASETTINGS_IMPL double M_DABEAM_DMG_BASE; VRX_V_LUASETTINGS_IMPL double M_DABEAM_DMG_ADDON; +VRX_V_LUASETTINGS_IMPL double M_DABEAM_DMG_MAX; VRX_V_LUASETTINGS_IMPL double M_MELEE_DMG_BASE; VRX_V_LUASETTINGS_IMPL double M_MELEE_DMG_ADDON; VRX_V_LUASETTINGS_IMPL double M_MELEE_DMG_MAX; @@ -904,6 +905,28 @@ VRX_V_LUASETTINGS_IMPL double M_20MM_RANGE_MAX; VRX_V_LUASETTINGS_IMPL double M_20MM_DMG_BASE; VRX_V_LUASETTINGS_IMPL double M_20MM_DMG_ADDON; VRX_V_LUASETTINGS_IMPL double M_20MM_DMG_MAX; + +VRX_V_LUASETTINGS_IMPL double M_IONRIPPER_DMG_BASE; +VRX_V_LUASETTINGS_IMPL double M_IONRIPPER_DMG_ADDON; +VRX_V_LUASETTINGS_IMPL double M_IONRIPPER_DMG_MAX; +VRX_V_LUASETTINGS_IMPL double M_IONRIPPER_SPEED_BASE; +VRX_V_LUASETTINGS_IMPL double M_IONRIPPER_SPEED_ADDON; +VRX_V_LUASETTINGS_IMPL double M_IONRIPPER_SPEED_MAX; + +VRX_V_LUASETTINGS_IMPL double M_ETFRIFLE_DMG_BASE; +VRX_V_LUASETTINGS_IMPL double M_ETFRIFLE_DMG_ADDON; +VRX_V_LUASETTINGS_IMPL double M_ETFRIFLE_DMG_MAX; +VRX_V_LUASETTINGS_IMPL double M_ETFRIFLE_SPEED_BASE; +VRX_V_LUASETTINGS_IMPL double M_ETFRIFLE_SPEED_ADDON; +VRX_V_LUASETTINGS_IMPL double M_ETFRIFLE_SPEED_MAX; + +VRX_V_LUASETTINGS_IMPL double M_DISRUPTOR_DMG_BASE; +VRX_V_LUASETTINGS_IMPL double M_DISRUPTOR_DMG_ADDON; +VRX_V_LUASETTINGS_IMPL double M_DISRUPTOR_DMG_MAX; +VRX_V_LUASETTINGS_IMPL double M_DISRUPTOR_SPEED_BASE; +VRX_V_LUASETTINGS_IMPL double M_DISRUPTOR_SPEED_ADDON; +VRX_V_LUASETTINGS_IMPL double M_DISRUPTOR_SPEED_MAX; + VRX_V_LUASETTINGS_IMPL double BERSERK_SLASH_INITIAL_DAMAGE; VRX_V_LUASETTINGS_IMPL double BERSERK_SLASH_ADDON_DAMAGE; VRX_V_LUASETTINGS_IMPL double BERSERK_SLASH_RANGE; diff --git a/src/characters/v_utils.c b/src/characters/v_utils.c index 573f913c..e46ee67a 100644 --- a/src/characters/v_utils.c +++ b/src/characters/v_utils.c @@ -2098,9 +2098,9 @@ char *V_GetMonsterKind(int mtype) { case M_DAEDALUS: return "daedalus"; case M_GLADB: - return "gladiator disruptor"; + return "disruptor gladiator"; case M_GLADC: - return "gladiator plasma"; + return "plasma gladiator"; case M_STALKER: return "stalker"; case M_GEKK: @@ -2112,9 +2112,9 @@ char *V_GetMonsterKind(int mtype) { case M_GUARDIAN: return "Guardian"; case M_JANITOR: - return "Janitor"; + return "janitor"; case M_MINIGUARDIAN: - return "Mini Guardian"; + return "mini guardian"; case M_SKELETON: return "skeleton"; case M_GOLEM: @@ -2860,7 +2860,8 @@ qboolean vrx_is_morphing_polt(edict_t *ent) { // returns true if ent has a pain/damaged skin qboolean vrx_has_pain_skin(edict_t* ent) { - return (ent->mtype != M_DECOY && ent->mtype != M_SKELETON && ent->mtype != M_GOLEM); + return (ent->mtype != M_DECOY && ent->mtype != M_SKELETON && ent->mtype != M_GOLEM + && ent->mtype != M_RUNNERTANK && ent->mtype != M_REDMUTANT); } // returns a value >= 1 based on any synergy bonuses that apply for ability_index diff --git a/src/combat/common/v_misc.c b/src/combat/common/v_misc.c index 0c16458a..aed220dd 100644 --- a/src/combat/common/v_misc.c +++ b/src/combat/common/v_misc.c @@ -272,7 +272,7 @@ static enum dronespawn_t vrx_pvm_random_drone_type(void) DS_ARACHNID, DS_MEDIC_COMMANDER, DS_JANITOR, - DS_MiniGuardian + DS_MINIGUARDIAN }; const int count = (int)(sizeof(pvm_drone_types) / sizeof(pvm_drone_types[0])); diff --git a/src/entities/drone/drone_brain.c b/src/entities/drone/drone_brain.c index ab18756b..292b7a9f 100644 --- a/src/entities/drone/drone_brain.c +++ b/src/entities/drone/drone_brain.c @@ -834,6 +834,9 @@ static void mybrain_laserbeam(edict_t *self) return; damage = M_DABEAM_DMG_BASE + M_DABEAM_DMG_ADDON * drone_damagelevel(self); + if (M_DABEAM_DMG_MAX && damage > M_DABEAM_DMG_MAX) + damage = M_DABEAM_DMG_MAX; + monster_fire_dabeam(self, damage, false, mybrain_right_eye_laser_update); monster_fire_dabeam(self, damage, true, mybrain_left_eye_laser_update); } @@ -879,11 +882,10 @@ void mybrain_attack (edict_t *self) dist = entdist(self, self->enemy); has_los = visible(self, self->enemy); - // jump to our enemy if he's close and on even ground - if (has_los && dist > 512) - self->monsterinfo.currentmove = &mybrain_move_attack4; - else if (has_los && dist > MELEE_DISTANCE && random() < 0.4) + // laser attack + if (has_los && dist >= 192 && dist <= 640 && random() < 0.15) self->monsterinfo.currentmove = &mybrain_move_attack4; + // jump to our enemy if he's close and on even ground else if ((dist > 256) && (self->enemy->absmin[2]+18 >= self->absmin[2]) && (self->enemy->absmin[2]-18 <= self->absmin[2]) && !(self->monsterinfo.aiflags & AI_STAND_GROUND)) diff --git a/src/entities/drone/drone_gladiator.c b/src/entities/drone/drone_gladiator.c index b4b93dcf..d19ccbcc 100644 --- a/src/entities/drone/drone_gladiator.c +++ b/src/entities/drone/drone_gladiator.c @@ -280,7 +280,11 @@ static void GladiatorDisruptor(edict_t *self) return; damage = DISRUPTOR_INITIAL_DAMAGE + DISRUPTOR_ADDON_DAMAGE * drone_damagelevel(self); + if (M_DISRUPTOR_DMG_MAX && damage > M_DISRUPTOR_DMG_MAX) + damage = M_DISRUPTOR_DMG_MAX; speed = DISRUPTOR_INITIAL_SPEED + DISRUPTOR_ADDON_SPEED * drone_damagelevel(self); + if (M_DISRUPTOR_SPEED_MAX && damage > M_DISRUPTOR_SPEED_MAX) + speed = M_DISRUPTOR_SPEED_MAX; MonsterAim(self, M_PROJECTILE_ACC, speed, false, MZ2_GLADIATOR_RAILGUN_1, forward, start); fire_disruptor(self, start, forward, damage, speed, visible(self, self->enemy) ? self->enemy : NULL); @@ -606,5 +610,5 @@ void init_drone_gladc(edict_t *self) self->monsterinfo.power_armor_power = M_GLADC_INITIAL_ARMOR + M_GLADC_ADDON_ARMOR * self->monsterinfo.level; self->monsterinfo.max_armor = self->monsterinfo.power_armor_power; self->mass = 350; - self->item = FindItemByClassname("ammo_cells"); + self->item = FindItemByClassname("ammo_magslug"); } diff --git a/src/entities/drone/drone_guardian.c b/src/entities/drone/drone_guardian.c index d8b51234..ceb09c74 100644 --- a/src/entities/drone/drone_guardian.c +++ b/src/entities/drone/drone_guardian.c @@ -1,7 +1,7 @@ /* ============================================================================== -GUARDIAN / MiniGuardian +GUARDIAN / miniguardian ============================================================================== */ @@ -125,6 +125,8 @@ mmove_t guardian_move_walk = {FRAME_walk1, FRAME_walk19, guardian_frames_walk, N void guardian_walk(edict_t *self) { + if (!self->goalentity) + self->goalentity = world; self->monsterinfo.currentmove = &guardian_move_walk; } @@ -226,8 +228,12 @@ void guardian_fire_blaster(edict_t *self) if (self->mtype == M_MINIGUARDIAN) { - damage = IONRIPPER_INITIAL_DAMAGE + IONRIPPER_ADDON_DAMAGE * drone_damagelevel(self); - speed = IONRIPPER_INITIAL_SPEED + IONRIPPER_ADDON_SPEED * drone_damagelevel(self); + damage = M_IONRIPPER_DMG_BASE + M_IONRIPPER_DMG_ADDON * drone_damagelevel(self); + if (M_IONRIPPER_DMG_MAX && damage > M_IONRIPPER_DMG_MAX) + damage = M_IONRIPPER_DMG_MAX; + speed = M_IONRIPPER_SPEED_BASE + M_IONRIPPER_SPEED_ADDON * drone_damagelevel(self); + if (M_IONRIPPER_SPEED_MAX && speed > M_IONRIPPER_SPEED_MAX) + speed = M_IONRIPPER_SPEED_MAX; MonsterAim(self, M_PROJECTILE_ACC, speed, false, MZ2_SOLDIER_RIPPER_8, forward, start); monster_fire_ionripper(self, start, forward, damage, speed, EF_IONRIPPER, MZ2_SOLDIER_RIPPER_8); } @@ -340,6 +346,8 @@ void guardian_laser_fire(edict_t *self) gi.sound(self, CHAN_WEAPON, sound_laser, 1, ATTN_NORM, 0); damage = M_DABEAM_DMG_BASE + M_DABEAM_DMG_ADDON * drone_damagelevel(self); + if (M_DABEAM_DMG_MAX && damage > M_DABEAM_DMG_MAX) + damage = M_DABEAM_DMG_MAX; if (self->mtype == M_GUARDIAN) damage += 10 + 2 * drone_damagelevel(self); monster_fire_dabeam(self, damage, self->s.frame & 1, NULL); @@ -631,7 +639,7 @@ void guardian_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int dama void init_drone_guardian(edict_t *self) { - qboolean MiniGuardian = (self->mtype == M_MINIGUARDIAN); + qboolean miniguardian = (self->mtype == M_MINIGUARDIAN); sound_step = gi.soundindex("zortemp/step.wav"); sound_charge = gi.soundindex("weapons/hyprbu1a.wav"); @@ -645,10 +653,10 @@ void init_drone_guardian(edict_t *self) self->s.modelindex = gi.modelindex("models/monsters/guardian/tris.md2"); self->movetype = MOVETYPE_STEP; self->solid = SOLID_BBOX; - self->monsterinfo.control_cost = MiniGuardian ? M_TANK_CONTROL_COST : M_JORG_CONTROL_COST; - self->monsterinfo.cost = MiniGuardian ? 175 : 500; + self->monsterinfo.control_cost = miniguardian ? M_TANK_CONTROL_COST : M_JORG_CONTROL_COST; + self->monsterinfo.cost = miniguardian ? 175 : 500; - if (MiniGuardian) + if (miniguardian) { self->s.skinnum = 2; self->s.scale = 0.4f; @@ -670,7 +678,7 @@ void init_drone_guardian(edict_t *self) } self->max_health = self->health; - self->gib_health = MiniGuardian ? -3 * BASE_GIB_HEALTH : -6 * BASE_GIB_HEALTH; + self->gib_health = miniguardian ? -3 * BASE_GIB_HEALTH : -6 * BASE_GIB_HEALTH; self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD; self->monsterinfo.max_armor = self->monsterinfo.power_armor_power; self->monsterinfo.jumpup = 64; diff --git a/src/entities/drone/drone_misc.c b/src/entities/drone/drone_misc.c index d60d7052..36573665 100644 --- a/src/entities/drone/drone_misc.c +++ b/src/entities/drone/drone_misc.c @@ -908,7 +908,7 @@ edict_t *vrx_create_drone_from_ent(edict_t *drone, edict_t *ent, enum dronespawn case DS_CARRIER: init_drone_carrier(drone); break; case DS_GUARDIAN: init_drone_guardian(drone); break; case DS_JANITOR: drone->mtype = M_JANITOR; init_drone_supertank(drone); break; - case DS_MiniGuardian: drone->mtype = M_MINIGUARDIAN; init_drone_guardian(drone); break; + case DS_MINIGUARDIAN: drone->mtype = M_MINIGUARDIAN; init_drone_guardian(drone); break; // default default: init_drone_gunner(drone); break; @@ -925,7 +925,7 @@ edict_t *vrx_create_drone_from_ent(edict_t *drone, edict_t *ent, enum dronespawn //4.0 gib health based on monster control cost if (drone_type < 30 || drone_type == DS_JANITOR || - drone_type == DS_MiniGuardian || + drone_type == DS_MINIGUARDIAN || drone_type == DS_SOLDIER_RIPPER || drone_type == DS_SOLDIER_BLUEBLASTER || drone_type == DS_SOLDIER_LASER) @@ -2986,7 +2986,7 @@ void Cmd_Drone_f (edict_t *ent) else if (!Q_strcasecmp(s, "janitor")) vrx_create_new_drone(ent, DS_JANITOR, false, true, 0); else if (!Q_strcasecmp(s, "miniguardian") || !Q_strcasecmp(s, "mini guardian") || !Q_strcasecmp(s, "mini_guardian")) - vrx_create_new_drone(ent, DS_MiniGuardian, false, true, 0); + vrx_create_new_drone(ent, DS_MINIGUARDIAN, false, true, 0); else if (!Q_strcasecmp(s, "enforcer")) vrx_create_new_drone(ent, DS_INFANTRY, false, true, 0); else if (!Q_strcasecmp(s, "flyer")) diff --git a/src/entities/drone/drone_soldier.c b/src/entities/drone/drone_soldier.c index 917db797..93c40b56 100644 --- a/src/entities/drone/drone_soldier.c +++ b/src/entities/drone/drone_soldier.c @@ -477,6 +477,8 @@ void soldier_firelaser(edict_t* self, int flash_number) self->radius_dmg = flash_number; damage = M_DABEAM_DMG_BASE + M_DABEAM_DMG_ADDON * drone_damagelevel(self); + if (M_DABEAM_DMG_MAX && damage > M_DABEAM_DMG_MAX) + damage = M_DABEAM_DMG_MAX; monster_fire_dabeam(self, damage, false, soldier_laser_update); } diff --git a/src/entities/drone/drone_stalker.c b/src/entities/drone/drone_stalker.c index 41bba338..878f6b95 100644 --- a/src/entities/drone/drone_stalker.c +++ b/src/entities/drone/drone_stalker.c @@ -20,6 +20,8 @@ static int sound_idle; #define STALKER_CEILING_ON 1 #define STALKER_CEILING_JUMPING 2 #define STALKER_CEILING_TRACE_DIST 256 +#define STALKER_CEILING_JUMP_SPEED 550 +#define STALKER_CEILING_MIN_SPEED 360 void drone_ai_stand(edict_t *self, float dist); void drone_ai_run(edict_t *self, float dist); @@ -29,6 +31,7 @@ void drone_ai_walk(edict_t *self, float dist); static void stalker_stand(edict_t *self); static void stalker_run(edict_t *self); static void stalker_set_floor(edict_t *self); +static void stalker_jump_wait_land(edict_t *self); extern mmove_t stalker_move_jump_straightup; static mmove_t stalker_move_dodge_run; @@ -182,6 +185,12 @@ static void stalker_ceiling_prethink(edict_t *self) { float ceiling_z; + if (self->style == STALKER_CEILING_JUMPING) + { + stalker_jump_wait_land(self); + return; + } + if (!stalker_on_ceiling(self)) return; @@ -191,12 +200,12 @@ static void stalker_ceiling_prethink(edict_t *self) return; } - if (stalker_find_ceiling(self, 80, &ceiling_z)) + if (stalker_find_ceiling(self, 96, &ceiling_z)) stalker_attach_ceiling(self, ceiling_z); else { - stalker_set_floor(self); - self->velocity[2] = -120; + // don't instantly detach on one failed trace + self->velocity[2] = 0; } } @@ -222,7 +231,7 @@ static void stalker_jump_straightup(edict_t *self) self->pos1[2] = ceiling_z; self->flags |= FL_FLY; self->gravity = 0; - self->velocity[2] = 500; + self->velocity[2] = STALKER_CEILING_JUMP_SPEED; } else { @@ -266,8 +275,8 @@ static void stalker_jump_wait_land(edict_t *self) { self->flags |= FL_FLY; self->gravity = 0; - if (self->velocity[2] < 300) - self->velocity[2] = 300; + if (self->velocity[2] < STALKER_CEILING_MIN_SPEED) + self->velocity[2] = STALKER_CEILING_MIN_SPEED; } if (self->groundentity || level.time > self->monsterinfo.pausetime) @@ -282,7 +291,7 @@ static void stalker_jump_wait_land(edict_t *self) return; } - self->monsterinfo.aiflags |= AI_HOLD_FRAME; + self->monsterinfo.aiflags |= AI_HOLD_FRAME; } mframe_t stalker_frames_jump_straightup[] = @@ -388,6 +397,24 @@ mmove_t stalker_move_shoot = { FRAME_run01, FRAME_run04, stalker_frames_shoot, s static void stalker_attack(edict_t *self) { + if (stalker_on_ceiling(self)) + { + // sometimes come back down during combat + if (entdist(self, self->enemy) < 96 || random() < 0.25f) + { + stalker_set_floor(self); + self->velocity[2] = -300; + stalker_run(self); + M_DelayNextAttack(self, 0.6, true); + return; + } + + // otherwise keep fighting from the ceiling + self->monsterinfo.currentmove = &stalker_move_shoot; + M_DelayNextAttack(self, 0.4, true); + return; + } + if (stalker_ceiling_allowed(self) && !stalker_on_ceiling(self) && self->groundentity && level.time > self->monsterinfo.melee_finished && random() < 0.33f) { @@ -476,9 +503,17 @@ static void stalker_melee(edict_t *self) { if (stalker_on_ceiling(self)) { - stalker_set_floor(self); - self->velocity[2] = -300; - stalker_run(self); + if (!G_ValidTarget(self, self->enemy, true, true) || entdist(self, self->enemy) > 96) + { + self->monsterinfo.melee_finished = level.time + 0.5; + stalker_run(self); + return; + } + + if (random() < 0.5) + self->monsterinfo.currentmove = &stalker_move_swing_l; + else + self->monsterinfo.currentmove = &stalker_move_swing_r; return; } diff --git a/src/entities/g_spawn.c b/src/entities/g_spawn.c index 71b674a7..ed02cde6 100644 --- a/src/entities/g_spawn.c +++ b/src/entities/g_spawn.c @@ -135,7 +135,7 @@ void SP_monster_soldier_lasergun (edict_t *self); void SP_monster_soldier_ripper (edict_t *self); void SP_monster_guardian (edict_t *self); void SP_monster_janitor (edict_t *self); -void SP_monster_MiniGuardian (edict_t *self); +void SP_monster_miniguardian (edict_t *self); void SP_monster_fixbot (edict_t *self); void SP_monster_gekk (edict_t *self); void SP_monster_chick_heat (edict_t *self); @@ -283,7 +283,7 @@ spawn_t spawns[] = { {"monster_soldier_laser", SP_monster_soldier_laser}, {"monster_soldier_lasergun", SP_monster_soldier_laser}, {"monster_janitor", SP_monster_janitor}, - {"monster_MiniGuardian", SP_monster_MiniGuardian}, + {"monster_miniguardian", SP_monster_miniguardian}, {"monster_tank", SP_monster_tank}, {"monster_tank_commander", SP_monster_tank_commander}, {"monster_medic", SP_monster_medic}, @@ -1131,10 +1131,10 @@ void SP_monster_janitor(edict_t *ent) vrx_create_drone_from_ent(ent, g_edicts, DS_JANITOR, true, true, 0); } -void SP_monster_MiniGuardian(edict_t *ent) +void SP_monster_miniguardian(edict_t *ent) { if (coop->value) - vrx_create_drone_from_ent(ent, g_edicts, DS_MiniGuardian, true, true, 0); + vrx_create_drone_from_ent(ent, g_edicts, DS_MINIGUARDIAN, true, true, 0); } void SP_monster_tank(edict_t *ent) diff --git a/src/g_local.h b/src/g_local.h index 721e9e53..14a5da47 100644 --- a/src/g_local.h +++ b/src/g_local.h @@ -1647,7 +1647,7 @@ enum dronespawn_t { DS_CARRIER = 35, DS_GUARDIAN = 36, DS_JANITOR = 37, - DS_MiniGuardian = 38, + DS_MINIGUARDIAN = 38, DS_SOLDIER_RIPPER = 39, DS_SOLDIER_BLUEBLASTER = 40, DS_SOLDIER_LASER = 41, diff --git a/src/server/v_luasettings.c b/src/server/v_luasettings.c index 5471972b..ccedbfac 100644 --- a/src/server/v_luasettings.c +++ b/src/server/v_luasettings.c @@ -1198,8 +1198,9 @@ void Lua_LoadVariables() M_RAILGUN_DMG_ADDON = vrx_lua_get_variable("M_RAILGUN_DMG_ADDON", 15); M_RAILGUN_DMG_MAX = vrx_lua_get_variable("M_RAILGUN_DMG_MAX", 0); - M_DABEAM_DMG_BASE = vrx_lua_get_variable("M_DABEAM_DMG_BASE", 5); + M_DABEAM_DMG_BASE = vrx_lua_get_variable("M_DABEAM_DMG_BASE", 2); M_DABEAM_DMG_ADDON = vrx_lua_get_variable("M_DABEAM_DMG_ADDON", 1); + M_DABEAM_DMG_MAX = vrx_lua_get_variable("M_DABEAM_DMG_MAX", 0); M_MELEE_DMG_BASE = vrx_lua_get_variable("M_MELEE_DMG_BASE", 50); M_MELEE_DMG_ADDON = vrx_lua_get_variable("M_MELEE_DMG_ADDON", 25); @@ -1227,10 +1228,11 @@ void Lua_LoadVariables() M_BLASTER2_SPEED_BASE = vrx_lua_get_variable("M_BLASTER2_SPEED_BASE", 500); M_BLASTER2_SPEED_ADDON = vrx_lua_get_variable("M_BLASTER2_SPEED_ADDON", 0); M_BLASTER2_SPEED_MAX = vrx_lua_get_variable("M_BLASTER2_SPEED_MAX", 0); - M_PLASMA_DMG_BASE = vrx_lua_get_variable("M_PLASMA_DMG_BASE", 50); - M_PLASMA_DMG_ADDON = vrx_lua_get_variable("M_PLASMA_DMG_ADDON", 10); + + M_PLASMA_DMG_BASE = vrx_lua_get_variable("M_PLASMA_DMG_BASE", 24); + M_PLASMA_DMG_ADDON = vrx_lua_get_variable("M_PLASMA_DMG_ADDON", 8); M_PLASMA_DMG_MAX = vrx_lua_get_variable("M_PLASMA_DMG_MAX", 0); - M_PLASMA_SPEED_BASE = vrx_lua_get_variable("M_PLASMA_SPEED_BASE", 1000); + M_PLASMA_SPEED_BASE = vrx_lua_get_variable("M_PLASMA_SPEED_BASE", 850); M_PLASMA_SPEED_ADDON = vrx_lua_get_variable("M_PLASMA_SPEED_ADDON", 50); M_PLASMA_SPEED_MAX = vrx_lua_get_variable("M_PLASMA_SPEED_MAX", 1500); M_PLASMA_DAMAGE_RADIUS = vrx_lua_get_variable("M_PLASMA_DAMAGE_RADIUS", 40); @@ -1242,6 +1244,27 @@ void Lua_LoadVariables() M_20MM_DMG_ADDON = vrx_lua_get_variable("M_20MM_DMG_ADDON", 3); M_20MM_DMG_MAX = vrx_lua_get_variable("M_20MM_DMG_MAX", 0); + M_ETFRIFLE_DMG_BASE = vrx_lua_get_variable("M_ETFRIFLE_DMG_BASE", 10); + M_ETFRIFLE_DMG_ADDON = vrx_lua_get_variable("M_ETFRIFLE_DMG_ADDON", 3); + M_ETFRIFLE_DMG_MAX = vrx_lua_get_variable("M_ETFRIFLE_DMG_MAX", 0); + M_ETFRIFLE_SPEED_BASE = vrx_lua_get_variable("M_ETFRIFLE_SPEED_BASE", 1000); + M_ETFRIFLE_SPEED_ADDON = vrx_lua_get_variable("M_ETFRIFLE_SPEED_ADDON", 50); + M_ETFRIFLE_SPEED_MAX = vrx_lua_get_variable("M_ETFRIFLE_SPEED_MAX", 1500); + + M_IONRIPPER_DMG_BASE = vrx_lua_get_variable("M_IONRIPPER_DMG_BASE", 20); + M_IONRIPPER_DMG_ADDON = vrx_lua_get_variable("M_IONRIPPER_DMG_ADDON", 3); + M_IONRIPPER_DMG_MAX = vrx_lua_get_variable("M_IONRIPPER_DMG_MAX", 0); + M_IONRIPPER_SPEED_BASE = vrx_lua_get_variable("M_IONRIPPER_SPEED_BASE", 1000); + M_IONRIPPER_SPEED_ADDON = vrx_lua_get_variable("M_IONRIPPER_SPEED_ADDON", 50); + M_IONRIPPER_SPEED_MAX = vrx_lua_get_variable("M_IONRIPPER_SPEED_MAX", 1500); + + M_DISRUPTOR_DMG_BASE = vrx_lua_get_variable("M_DISRUPTOR_DMG_BASE", 40); + M_DISRUPTOR_DMG_ADDON = vrx_lua_get_variable("M_DISRUPTOR_DMG_ADDON", 3); + M_DISRUPTOR_DMG_MAX = vrx_lua_get_variable("M_DISRUPTOR_DMG_MAX", 0); + M_DISRUPTOR_SPEED_BASE = vrx_lua_get_variable("M_DISRUPTOR_SPEED_BASE", 1000); + M_DISRUPTOR_SPEED_ADDON = vrx_lua_get_variable("M_DISRUPTOR_SPEED_ADDON", 50); + M_DISRUPTOR_SPEED_MAX = vrx_lua_get_variable("M_DISRUPTOR_SPEED_MAX", 1500); + BERSERK_SLASH_INITIAL_DAMAGE = vrx_lua_get_variable("BERSERK_SLASH_INITIAL_DAMAGE", 100); BERSERK_SLASH_ADDON_DAMAGE = vrx_lua_get_variable("BERSERK_SLASH_ADDON_DAMAGE", 20); BERSERK_SLASH_RANGE = vrx_lua_get_variable("BERSERK_SLASH_RANGE", 96); From 4d595d58057b7e750e54d0423f05d9fddf684b39 Mon Sep 17 00:00:00 2001 From: Enemy-123 Date: Fri, 24 Apr 2026 21:55:33 -0400 Subject: [PATCH 10/24] guncmdr bbox fix + misc --- src/characters/v_utils.c | 6 +++--- src/entities/drone/drone_carrier.c | 2 ++ src/entities/drone/drone_guardian.c | 2 ++ src/entities/drone/drone_guncmdr.c | 27 ++++++++++++++++++--------- src/entities/drone/drone_misc.c | 4 ++-- 5 files changed, 27 insertions(+), 14 deletions(-) diff --git a/src/characters/v_utils.c b/src/characters/v_utils.c index e46ee67a..33aa204b 100644 --- a/src/characters/v_utils.c +++ b/src/characters/v_utils.c @@ -2044,11 +2044,11 @@ char *V_GetMonsterKind(int mtype) { case M_SOLDIERSS: return "soldier"; case M_SOLDIER_RIPPER: - return "Ripper Guard"; + return "ripper Guard"; case M_SOLDIER_BLUEBLASTER: - return "Hyper Guard"; + return "hyper Guard"; case M_SOLDIER_LASER: - return "Laser Guard"; + return "laser Guard"; case M_FLIPPER: return "flipper"; case M_FLYER: diff --git a/src/entities/drone/drone_carrier.c b/src/entities/drone/drone_carrier.c index e848e1ac..0e1e570b 100644 --- a/src/entities/drone/drone_carrier.c +++ b/src/entities/drone/drone_carrier.c @@ -538,4 +538,6 @@ void init_drone_carrier(edict_t *self) self->monsterinfo.currentmove = &carrier_move_stand; self->monsterinfo.scale = MODEL_SCALE; self->nextthink = level.time + FRAMETIME; + + G_PrintGreenText(va("A level %d carrier has spawned!", self->monsterinfo.level)); } diff --git a/src/entities/drone/drone_guardian.c b/src/entities/drone/drone_guardian.c index ceb09c74..4353b30c 100644 --- a/src/entities/drone/drone_guardian.c +++ b/src/entities/drone/drone_guardian.c @@ -695,4 +695,6 @@ void init_drone_guardian(edict_t *self) self->nextthink = level.time + FRAMETIME; gi.linkentity(self); + + G_PrintGreenText(va("A level %d guardian has spawned!", self->monsterinfo.level)); } diff --git a/src/entities/drone/drone_guncmdr.c b/src/entities/drone/drone_guncmdr.c index 97354867..dd7b7d8d 100644 --- a/src/entities/drone/drone_guncmdr.c +++ b/src/entities/drone/drone_guncmdr.c @@ -25,10 +25,12 @@ static int sound_thud; #define GUNCMDR_GRENADE_SPEED 600 #define GUNCMDR_WALK_SPEED_MULT 2.0f -#define GUNCMDR_STAND_MAXZ_SCALED(self) (36.0f * ((self)->s.scale > 0 ? (self)->s.scale : 1.0f)) -#define GUNCMDR_DUCK_MAXZ_SCALED(self) (4.0f * ((self)->s.scale > 0 ? (self)->s.scale : 1.0f)) -#define GUNCMDR_DEAD_MAXZ_SCALED(self) (-8.0f * ((self)->s.scale > 0 ? (self)->s.scale : 1.0f)) -#define GUNCMDR_SHRINK_MAXZ_SCALED(self) (-4.0f * ((self)->s.scale > 0 ? (self)->s.scale : 1.0f)) +#define GUNCMDR_SCALE(self) (((self)->s.scale > 0) ? (self)->s.scale : 1.0f) + +#define GUNCMDR_STAND_MAXZ_SCALED(self) (36.0f * GUNCMDR_SCALE(self)) +#define GUNCMDR_DUCK_MAXZ_SCALED(self) (4.0f * GUNCMDR_SCALE(self)) +#define GUNCMDR_DEAD_MAXZ_SCALED(self) (-8.0f * GUNCMDR_SCALE(self)) +#define GUNCMDR_SHRINK_MAXZ_SCALED(self) (-4.0f * GUNCMDR_SCALE(self)) static void guncmdr_stand(edict_t *self); static void guncmdr_run(edict_t *self); @@ -1093,8 +1095,16 @@ static void guncmdr_pain(edict_t *self, edict_t *other, float kick, int damage) static void guncmdr_dead(edict_t *self) { - VectorSet(self->mins, -16 * self->s.scale, -16 * self->s.scale, -24 * self->s.scale); - VectorSet(self->maxs, 16 * self->s.scale, 16 * self->s.scale, GUNCMDR_DEAD_MAXZ_SCALED(self)); + VectorSet(self->mins, + -16 * GUNCMDR_SCALE(self), + -16 * GUNCMDR_SCALE(self), + -24 * GUNCMDR_SCALE(self)); + + VectorSet(self->maxs, + 16 * GUNCMDR_SCALE(self), + 16 * GUNCMDR_SCALE(self), + GUNCMDR_DEAD_MAXZ_SCALED(self)); + self->movetype = MOVETYPE_TOSS; self->svflags |= SVF_DEADMONSTER; gi.linkentity(self); @@ -1498,8 +1508,8 @@ void init_drone_guncmdr(edict_t *self) gi.modelindex("models/monsters/gunner/gibs/head.md2"); self->s.scale = 1.25f; - VectorSet(self->mins, -16, -16, -24); - VectorSet(self->maxs, 16, 16, GUNCMDR_STAND_MAXZ_SCALED(self)); + VectorSet(self->mins, -20, -20, -30); // default scale = -16, -16, -24 + VectorSet(self->maxs, 20, 20, GUNCMDR_STAND_MAXZ_SCALED(self)); // default scale = 16, 16, 36 self->s.skinnum = 2; self->monsterinfo.control_cost = M_TANK_CONTROL_COST; @@ -1510,7 +1520,6 @@ void init_drone_guncmdr(edict_t *self) self->mass = 255; self->monsterinfo.jumpdn = 512; self->monsterinfo.jumpup = 64; - self->s.origin[2] += fabsf(self->mins[2]) * (self->s.scale - 1.0f); if (random() > 0.5) self->item = FindItemByClassname("ammo_bullets"); diff --git a/src/entities/drone/drone_misc.c b/src/entities/drone/drone_misc.c index 36573665..6a7d6fe7 100644 --- a/src/entities/drone/drone_misc.c +++ b/src/entities/drone/drone_misc.c @@ -531,7 +531,7 @@ void drone_death (edict_t *self, edict_t *attacker) //4.2 bosses can drop up to 4 runes - if (self->mtype == M_COMMANDER || self->mtype == M_SUPERTANK || self->mtype == M_MAKRON || self->mtype == M_CARRIER) + if (self->mtype == M_COMMANDER || self->mtype == M_SUPERTANK || self->mtype == M_MAKRON || self->mtype == M_CARRIER || self->mtype == M_GUARDIAN) { edict_t *e; float drop_chance = 0.25; @@ -2228,7 +2228,7 @@ qboolean M_SetBoundingBox (int mtype, vec3_t boxmin, vec3_t boxmax) break; case M_GUNCMDR: VectorSet(boxmin, -16, -16, -24); - VectorSet(boxmax, 16, 16, 36); + VectorSet(boxmax, 16, 16, 42); break; case M_DAEDALUS: VectorSet(boxmin, -24, -24, -24); From 7b4a1131a61055d2e3a571d7c46a31f2a105adbe Mon Sep 17 00:00:00 2001 From: Enemy-123 Date: Sat, 25 Apr 2026 02:29:43 -0400 Subject: [PATCH 11/24] miniguardian printing boss spawning message fix --- src/entities/drone/drone_guardian.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/entities/drone/drone_guardian.c b/src/entities/drone/drone_guardian.c index 4353b30c..4d2b5b44 100644 --- a/src/entities/drone/drone_guardian.c +++ b/src/entities/drone/drone_guardian.c @@ -696,5 +696,6 @@ void init_drone_guardian(edict_t *self) self->nextthink = level.time + FRAMETIME; gi.linkentity(self); + if (!miniguardian) G_PrintGreenText(va("A level %d guardian has spawned!", self->monsterinfo.level)); } From 5f7a7797d5445593c2fe33b645b35fdafb9cf446 Mon Sep 17 00:00:00 2001 From: Enemy-123 Date: Sat, 25 Apr 2026 13:59:15 -0400 Subject: [PATCH 12/24] guncmdr improved bbox fix --- src/entities/drone/drone_guncmdr.c | 47 ++++++++++++++++++++++++------ src/entities/drone/drone_misc.c | 6 ++-- 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/src/entities/drone/drone_guncmdr.c b/src/entities/drone/drone_guncmdr.c index dd7b7d8d..3e6aad82 100644 --- a/src/entities/drone/drone_guncmdr.c +++ b/src/entities/drone/drone_guncmdr.c @@ -27,6 +27,11 @@ static int sound_thud; #define GUNCMDR_SCALE(self) (((self)->s.scale > 0) ? (self)->s.scale : 1.0f) +#define GUNCMDR_MINS_X_SCALED(self) (-16.0f * GUNCMDR_SCALE(self)) +#define GUNCMDR_MINS_Y_SCALED(self) (-16.0f * GUNCMDR_SCALE(self)) +#define GUNCMDR_MINS_Z_SCALED(self) (-24.0f * GUNCMDR_SCALE(self)) +#define GUNCMDR_MAXS_X_SCALED(self) (16.0f * GUNCMDR_SCALE(self)) +#define GUNCMDR_MAXS_Y_SCALED(self) (16.0f * GUNCMDR_SCALE(self)) #define GUNCMDR_STAND_MAXZ_SCALED(self) (36.0f * GUNCMDR_SCALE(self)) #define GUNCMDR_DUCK_MAXZ_SCALED(self) (4.0f * GUNCMDR_SCALE(self)) #define GUNCMDR_DEAD_MAXZ_SCALED(self) (-8.0f * GUNCMDR_SCALE(self)) @@ -52,6 +57,18 @@ void drone_ai_run_slide(edict_t *self, float dist); static void guncmdr_ai_dodge_slide(edict_t *self, float dist); extern mmove_t guncmdr_move_dodge_slide; +static void guncmdr_set_stand_bbox(edict_t *self) +{ + VectorSet(self->mins, + GUNCMDR_MINS_X_SCALED(self), + GUNCMDR_MINS_Y_SCALED(self), + GUNCMDR_MINS_Z_SCALED(self)); + VectorSet(self->maxs, + GUNCMDR_MAXS_X_SCALED(self), + GUNCMDR_MAXS_Y_SCALED(self), + GUNCMDR_STAND_MAXZ_SCALED(self)); +} + static void guncmdr_idle_sound(edict_t *self) { gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); @@ -550,12 +567,25 @@ static void guncmdr_duck_down(edict_t *self) static void guncmdr_duck_up(edict_t *self) { + vec3_t oldmaxs; + trace_t tr; + if (!(self->monsterinfo.aiflags & AI_DUCKED) && self->maxs[2] == GUNCMDR_STAND_MAXZ_SCALED(self)) return; - self->monsterinfo.aiflags &= ~AI_DUCKED; + VectorCopy(self->maxs, oldmaxs); self->maxs[2] = GUNCMDR_STAND_MAXZ_SCALED(self); + + tr = gi.trace(self->s.origin, self->mins, self->maxs, self->s.origin, self, MASK_MONSTERSOLID); + if (tr.startsolid || tr.allsolid) + { + VectorCopy(oldmaxs, self->maxs); + self->monsterinfo.aiflags |= AI_DUCKED; + return; + } + + self->monsterinfo.aiflags &= ~AI_DUCKED; self->takedamage = DAMAGE_AIM; gi.linkentity(self); } @@ -1096,14 +1126,14 @@ static void guncmdr_pain(edict_t *self, edict_t *other, float kick, int damage) static void guncmdr_dead(edict_t *self) { VectorSet(self->mins, - -16 * GUNCMDR_SCALE(self), - -16 * GUNCMDR_SCALE(self), - -24 * GUNCMDR_SCALE(self)); + GUNCMDR_MINS_X_SCALED(self), + GUNCMDR_MINS_Y_SCALED(self), + GUNCMDR_MINS_Z_SCALED(self)); VectorSet(self->maxs, - 16 * GUNCMDR_SCALE(self), - 16 * GUNCMDR_SCALE(self), - GUNCMDR_DEAD_MAXZ_SCALED(self)); + GUNCMDR_MAXS_X_SCALED(self), + GUNCMDR_MAXS_Y_SCALED(self), + GUNCMDR_DEAD_MAXZ_SCALED(self)); self->movetype = MOVETYPE_TOSS; self->svflags |= SVF_DEADMONSTER; @@ -1508,8 +1538,7 @@ void init_drone_guncmdr(edict_t *self) gi.modelindex("models/monsters/gunner/gibs/head.md2"); self->s.scale = 1.25f; - VectorSet(self->mins, -20, -20, -30); // default scale = -16, -16, -24 - VectorSet(self->maxs, 20, 20, GUNCMDR_STAND_MAXZ_SCALED(self)); // default scale = 16, 16, 36 + guncmdr_set_stand_bbox(self); // base bbox is -16 -16 -24, 16 16 36 at scale 1.0 self->s.skinnum = 2; self->monsterinfo.control_cost = M_TANK_CONTROL_COST; diff --git a/src/entities/drone/drone_misc.c b/src/entities/drone/drone_misc.c index 6a7d6fe7..6e56a085 100644 --- a/src/entities/drone/drone_misc.c +++ b/src/entities/drone/drone_misc.c @@ -2121,7 +2121,7 @@ qboolean M_Initialize (edict_t *ent, edict_t *monster, float dur_bonus) } #ifdef VRX_REPRO - if (monster->s.scale) + if (monster->s.scale && monster->mtype != M_GUNCMDR) { monster->monsterinfo.scale *= monster->s.scale; VectorScale(monster->mins, monster->s.scale, monster->mins); @@ -2227,8 +2227,8 @@ qboolean M_SetBoundingBox (int mtype, vec3_t boxmin, vec3_t boxmax) VectorSet(boxmax, 28, 28, 56); break; case M_GUNCMDR: - VectorSet(boxmin, -16, -16, -24); - VectorSet(boxmax, 16, 16, 42); + VectorSet(boxmin, -20, -20, -30); + VectorSet(boxmax, 20, 20, 45); break; case M_DAEDALUS: VectorSet(boxmin, -24, -24, -24); From 1ec7f8168420f658c184fe03ad740e9913834f7b Mon Sep 17 00:00:00 2001 From: Enemy-123 Date: Sat, 25 Apr 2026 15:19:00 -0400 Subject: [PATCH 13/24] adding widow + widow2 bosses, fix on stalker failing on jump to ceiling animation --- lua/variables.lua | 8 + src/characters/settings.h | 8 + src/characters/v_utils.c | 4 + src/combat/common/damage.c | 1 + src/combat/common/v_misc.c | 4 + src/entities/drone/drone_misc.c | 20 +- src/entities/drone/drone_stalker.c | 54 ++- src/entities/drone/drone_widow.c | 654 +++++++++++++++++++++++++ src/entities/drone/drone_widow2.c | 745 +++++++++++++++++++++++++++++ src/g_local.h | 4 + src/quake2/g_layout.c | 2 + src/quake2/g_svcmds.c | 16 +- src/server/v_luasettings.c | 8 + 13 files changed, 1512 insertions(+), 16 deletions(-) create mode 100644 src/entities/drone/drone_widow.c create mode 100644 src/entities/drone/drone_widow2.c diff --git a/lua/variables.lua b/lua/variables.lua index 664540e5..8fe36123 100644 --- a/lua/variables.lua +++ b/lua/variables.lua @@ -783,6 +783,14 @@ M_CARRIER_INITIAL_HEALTH = 2200 M_CARRIER_ADDON_HEALTH = 650 M_CARRIER_INITIAL_ARMOR = 500 M_CARRIER_ADDON_ARMOR = 350 +M_WIDOW_INITIAL_HEALTH = 2400 +M_WIDOW_ADDON_HEALTH = 700 +M_WIDOW_INITIAL_ARMOR = 600 +M_WIDOW_ADDON_ARMOR = 350 +M_WIDOW2_INITIAL_HEALTH = 3000 +M_WIDOW2_ADDON_HEALTH = 900 +M_WIDOW2_INITIAL_ARMOR = 750 +M_WIDOW2_ADDON_ARMOR = 450 M_GUARDIAN_INITIAL_HEALTH = 6500 M_GUARDIAN_ADDON_HEALTH = 1200 M_GUARDIAN_INITIAL_ARMOR = 550 diff --git a/src/characters/settings.h b/src/characters/settings.h index b20bd67b..f041a194 100644 --- a/src/characters/settings.h +++ b/src/characters/settings.h @@ -818,6 +818,14 @@ VRX_V_LUASETTINGS_IMPL double M_CARRIER_INITIAL_HEALTH; VRX_V_LUASETTINGS_IMPL double M_CARRIER_ADDON_HEALTH; VRX_V_LUASETTINGS_IMPL double M_CARRIER_INITIAL_ARMOR; VRX_V_LUASETTINGS_IMPL double M_CARRIER_ADDON_ARMOR; +VRX_V_LUASETTINGS_IMPL double M_WIDOW_INITIAL_HEALTH; +VRX_V_LUASETTINGS_IMPL double M_WIDOW_ADDON_HEALTH; +VRX_V_LUASETTINGS_IMPL double M_WIDOW_INITIAL_ARMOR; +VRX_V_LUASETTINGS_IMPL double M_WIDOW_ADDON_ARMOR; +VRX_V_LUASETTINGS_IMPL double M_WIDOW2_INITIAL_HEALTH; +VRX_V_LUASETTINGS_IMPL double M_WIDOW2_ADDON_HEALTH; +VRX_V_LUASETTINGS_IMPL double M_WIDOW2_INITIAL_ARMOR; +VRX_V_LUASETTINGS_IMPL double M_WIDOW2_ADDON_ARMOR; VRX_V_LUASETTINGS_IMPL double M_GUARDIAN_INITIAL_HEALTH; VRX_V_LUASETTINGS_IMPL double M_GUARDIAN_ADDON_HEALTH; VRX_V_LUASETTINGS_IMPL double M_GUARDIAN_INITIAL_ARMOR; diff --git a/src/characters/v_utils.c b/src/characters/v_utils.c index 33aa204b..b815754c 100644 --- a/src/characters/v_utils.c +++ b/src/characters/v_utils.c @@ -2109,6 +2109,10 @@ char *V_GetMonsterKind(int mtype) { return "arachnid"; case M_CARRIER: return "carrier"; + case M_WIDOW: + return "widow"; + case M_WIDOW2: + return "black widow"; case M_GUARDIAN: return "Guardian"; case M_JANITOR: diff --git a/src/combat/common/damage.c b/src/combat/common/damage.c index 59f6752e..a79995b5 100644 --- a/src/combat/common/damage.c +++ b/src/combat/common/damage.c @@ -155,6 +155,7 @@ qboolean IsMonster(const edict_t* ent) { || ent->mtype == M_DAEDALUS || ent->mtype == M_GLADB || ent->mtype == M_GLADC || ent->mtype == M_STALKER || ent->mtype == M_GEKK || ent->mtype == M_ARACHNID || ent->mtype == M_MEDIC_COMMANDER || ent->mtype == M_CARRIER + || ent->mtype == M_WIDOW || ent->mtype == M_WIDOW2 || ent->mtype == M_CHICK_HEAT)); } diff --git a/src/combat/common/v_misc.c b/src/combat/common/v_misc.c index aed220dd..7cf3bf0a 100644 --- a/src/combat/common/v_misc.c +++ b/src/combat/common/v_misc.c @@ -678,6 +678,8 @@ int vrx_GetMonsterCost(int mtype) { cost = M_DEFAULT_COST; break; case M_CARRIER: + case M_WIDOW: + case M_WIDOW2: cost = M_COMMANDER_COST; break; case M_JANITOR: @@ -763,6 +765,8 @@ int vrx_GetMonsterControlCost(int mtype) { cost = M_GLADIATOR_CONTROL_COST; break; case M_CARRIER: + case M_WIDOW: + case M_WIDOW2: cost = M_JORG_CONTROL_COST; break; case M_HOVER: diff --git a/src/entities/drone/drone_misc.c b/src/entities/drone/drone_misc.c index 6e56a085..6efe3077 100644 --- a/src/entities/drone/drone_misc.c +++ b/src/entities/drone/drone_misc.c @@ -44,6 +44,8 @@ void init_drone_stalker(edict_t* self); void init_drone_gekk(edict_t* self); void init_drone_arachnid(edict_t* self); void init_drone_carrier(edict_t* self); +void init_drone_widow(edict_t* self); +void init_drone_widow2(edict_t* self); void init_baron_fire(edict_t* self); void init_skeleton(edict_t* self); void init_golem(edict_t* self); @@ -531,7 +533,7 @@ void drone_death (edict_t *self, edict_t *attacker) //4.2 bosses can drop up to 4 runes - if (self->mtype == M_COMMANDER || self->mtype == M_SUPERTANK || self->mtype == M_MAKRON || self->mtype == M_CARRIER || self->mtype == M_GUARDIAN) + if (self->mtype == M_COMMANDER || self->mtype == M_SUPERTANK || self->mtype == M_MAKRON || self->mtype == M_CARRIER || self->mtype == M_WIDOW || self->mtype == M_WIDOW2 || self->mtype == M_GUARDIAN) { edict_t *e; float drop_chance = 0.25; @@ -777,6 +779,8 @@ static qboolean vrx_drone_spawn_is_boss(enum dronespawn_t drone_type) case DS_SUPERTANK: case DS_JORG: case DS_CARRIER: + case DS_WIDOW: + case DS_WIDOW2: case DS_GUARDIAN: return true; default: @@ -906,6 +910,8 @@ edict_t *vrx_create_drone_from_ent(edict_t *drone, edict_t *ent, enum dronespawn case DS_SUPERTANK: init_drone_supertank(drone); break; case DS_JORG: init_drone_jorg(drone); break; case DS_CARRIER: init_drone_carrier(drone); break; + case DS_WIDOW: init_drone_widow(drone); break; + case DS_WIDOW2: init_drone_widow2(drone); break; case DS_GUARDIAN: init_drone_guardian(drone); break; case DS_JANITOR: drone->mtype = M_JANITOR; init_drone_supertank(drone); break; case DS_MINIGUARDIAN: drone->mtype = M_MINIGUARDIAN; init_drone_guardian(drone); break; @@ -2113,6 +2119,8 @@ qboolean M_Initialize (edict_t *ent, edict_t *monster, float dur_bonus) case M_GEKK: init_drone_gekk(monster); break; case M_ARACHNID: init_drone_arachnid(monster); break; case M_CARRIER: init_drone_carrier(monster); break; + case M_WIDOW: init_drone_widow(monster); break; + case M_WIDOW2: init_drone_widow2(monster); break; case M_GUARDIAN: case M_MINIGUARDIAN: init_drone_guardian(monster); break; case M_JANITOR: init_drone_supertank(monster); break; case M_SKELETON: init_skeleton(monster); break; @@ -2250,6 +2258,14 @@ qboolean M_SetBoundingBox (int mtype, vec3_t boxmin, vec3_t boxmax) VectorSet(boxmin, -80, -80, -24); VectorSet(boxmax, 80, 80, 104); break; + case M_WIDOW: + VectorSet(boxmin, -40, -40, 0); + VectorSet(boxmax, 40, 40, 144); + break; + case M_WIDOW2: + VectorSet(boxmin, -70, -70, 0); + VectorSet(boxmax, 70, 70, 144); + break; case M_GUARDIAN: VectorSet(boxmin, -96, -96, -66); VectorSet(boxmax, 96, 96, 62); @@ -2346,6 +2362,8 @@ char *GetMonsterKindString (int mtype) case M_GEKK: return "Gekk"; case M_ARACHNID: return "Arachnid"; case M_CARRIER: return "Carrier"; + case M_WIDOW: return "Widow"; + case M_WIDOW2: return "Black Widow"; case M_GUARDIAN: return "Guardian"; case M_JANITOR: return "Janitor"; case M_MINIGUARDIAN: return "Mini Guardian"; diff --git a/src/entities/drone/drone_stalker.c b/src/entities/drone/drone_stalker.c index 878f6b95..39d97f6d 100644 --- a/src/entities/drone/drone_stalker.c +++ b/src/entities/drone/drone_stalker.c @@ -169,6 +169,27 @@ static void stalker_set_floor(edict_t *self) self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; } +static void stalker_drop_from_ceiling(edict_t *self) +{ + stalker_set_floor(self); + self->groundentity = NULL; + self->velocity[2] = -300; + gi.linkentity(self); +} + +static void stalker_abort_ceiling_jump(edict_t *self) +{ + stalker_set_floor(self); + self->gravity = 1.0; + self->flags &= ~FL_FLY; + self->monsterinfo.dodge_time = level.time + 2.0f; + if (self->groundentity) + VectorClear(self->velocity); + else if (self->velocity[2] > 0) + self->velocity[2] = 0; + stalker_run(self); +} + static qboolean stalker_is_jump_move(edict_t *self) { return self->monsterinfo.currentmove == &stalker_move_jump_straightup || @@ -203,10 +224,7 @@ static void stalker_ceiling_prethink(edict_t *self) if (stalker_find_ceiling(self, 96, &ceiling_z)) stalker_attach_ceiling(self, ceiling_z); else - { - // don't instantly detach on one failed trace - self->velocity[2] = 0; - } + stalker_drop_from_ceiling(self); } static void stalker_jump_straightup(edict_t *self) @@ -235,9 +253,8 @@ static void stalker_jump_straightup(edict_t *self) } else { - self->pos1[2] = 0; - self->gravity = 1.0; - self->velocity[2] = 400; + stalker_abort_ceiling_jump(self); + return; } self->style = STALKER_CEILING_JUMPING; @@ -281,13 +298,7 @@ static void stalker_jump_wait_land(edict_t *self) if (self->groundentity || level.time > self->monsterinfo.pausetime) { - if (self->pos1[2] && !self->groundentity) - { - stalker_attach_ceiling(self, self->pos1[2]); - return; - } - stalker_set_floor(self); - VectorClear(self->velocity); + stalker_abort_ceiling_jump(self); return; } @@ -305,12 +316,20 @@ mmove_t stalker_move_jump_straightup = { FRAME_jump04, FRAME_jump07, stalker_fra static qboolean stalker_start_ceiling_jump(edict_t *self, float cooldown) { + float ceiling_z; + if (!stalker_ceiling_allowed(self) || stalker_on_ceiling(self) || !self->groundentity) return false; if (stalker_is_dodge_move(self) || level.time < self->monsterinfo.dodge_time) return false; + if (!stalker_find_ceiling(self, STALKER_CEILING_TRACE_DIST, &ceiling_z)) + { + self->monsterinfo.dodge_time = level.time + cooldown; + return false; + } self->monsterinfo.dodge_time = level.time + cooldown; + self->pos1[2] = ceiling_z; self->monsterinfo.currentmove = &stalker_move_jump_straightup; stalker_jump_straightup(self); return true; @@ -553,6 +572,13 @@ static void stalker_pain(edict_t *self, edict_t *other, float kick, int damage) if (skill->value == 3) return; + if (self->style == STALKER_CEILING_JUMPING && damage > 10) + { + stalker_abort_ceiling_jump(self); + self->monsterinfo.currentmove = &stalker_move_pain; + return; + } + if (stalker_is_dodge_move(self)) return; diff --git a/src/entities/drone/drone_widow.c b/src/entities/drone/drone_widow.c new file mode 100644 index 00000000..ad43935b --- /dev/null +++ b/src/entities/drone/drone_widow.c @@ -0,0 +1,654 @@ +/* +============================================================================== + +black widow + +============================================================================== +*/ + +#include "g_local.h" + +#define WIDOW_FRAME_idle01 0 +#define WIDOW_FRAME_idle11 10 +#define WIDOW_FRAME_walk01 11 +#define WIDOW_FRAME_walk13 23 +#define WIDOW_FRAME_run01 24 +#define WIDOW_FRAME_run08 31 +#define WIDOW_FRAME_firea01 32 +#define WIDOW_FRAME_firea09 40 +#define WIDOW_FRAME_fireb01 41 +#define WIDOW_FRAME_fireb09 49 +#define WIDOW_FRAME_firec01 50 +#define WIDOW_FRAME_firec09 58 +#define WIDOW_FRAME_fired02a 61 +#define WIDOW_FRAME_fired03 62 +#define WIDOW_FRAME_fired20 79 +#define WIDOW_FRAME_spawn01 82 +#define WIDOW_FRAME_spawn18 99 +#define WIDOW_FRAME_pain01 100 +#define WIDOW_FRAME_pain05 104 +#define WIDOW_FRAME_death01 130 +#define WIDOW_FRAME_death31 160 +#define WIDOW_FRAME_kick01 161 +#define WIDOW_FRAME_kick08 168 + +#define WIDOW_SUMMON_COUNT 2 +#define WIDOW_SUMMON_COOLDOWN 10.0f +#define WIDOW_MELEE_RANGE 176.0f + +static int sound_pain1; +static int sound_pain2; +static int sound_pain3; +static int sound_death; +static int sound_laugh; +static int sound_rail; +static int sound_step1; +static int sound_step2; +static int sound_hit; +static int sound_spawn; +static int shotsfired; + +void drone_ai_stand(edict_t *self, float dist); +void drone_ai_run(edict_t *self, float dist); +void drone_ai_walk(edict_t *self, float dist); + +static void widow_stand(edict_t *self); +static void widow_walk(edict_t *self); +static void widow_run(edict_t *self); +static void widow_attack(edict_t *self); +static void widow_fire_blaster(edict_t *self); +static void widow_fire_rail(edict_t *self); +static void widow_melee_hit(edict_t *self); +static void widow_spawn_effects(edict_t *self); +static void widow_finish_spawn(edict_t *self); +static void widow_explode(edict_t *self); +static void widow_dead(edict_t *self); +static void widow_step1(edict_t *self); +static void widow_step2(edict_t *self); + +static mframe_t widow_frames_stand[] = +{ + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL +}; +static mmove_t widow_move_stand = { WIDOW_FRAME_idle01, WIDOW_FRAME_idle11, widow_frames_stand, widow_stand }; + +static mframe_t widow_frames_walk[] = +{ + drone_ai_walk, 7, widow_step1, + drone_ai_walk, 8, NULL, + drone_ai_walk, 9, NULL, + drone_ai_walk, 10, NULL, + drone_ai_walk, 9, widow_step2, + drone_ai_walk, 8, NULL, + drone_ai_walk, 7, NULL, + drone_ai_walk, 6, NULL, + drone_ai_walk, 7, widow_step1, + drone_ai_walk, 8, NULL, + drone_ai_walk, 9, NULL, + drone_ai_walk, 8, NULL, + drone_ai_walk, 7, widow_step2 +}; +static mmove_t widow_move_walk = { WIDOW_FRAME_walk01, WIDOW_FRAME_walk13, widow_frames_walk, widow_walk }; + +static mframe_t widow_frames_run[] = +{ + drone_ai_run, 12, widow_step1, + drone_ai_run, 16, NULL, + drone_ai_run, 18, NULL, + drone_ai_run, 20, widow_step2, + drone_ai_run, 18, NULL, + drone_ai_run, 16, NULL, + drone_ai_run, 14, NULL, + drone_ai_run, 12, widow_step1 +}; +static mmove_t widow_move_run = { WIDOW_FRAME_run01, WIDOW_FRAME_run08, widow_frames_run, NULL }; + +static mframe_t widow_frames_blaster[] = +{ + ai_charge, 0, widow_fire_blaster, + ai_charge, 0, widow_fire_blaster, + ai_charge, 0, widow_fire_blaster, + ai_charge, 0, widow_fire_blaster, + ai_charge, 0, widow_fire_blaster, + ai_charge, 0, widow_fire_blaster, + ai_charge, 0, widow_fire_blaster, + ai_charge, 0, widow_fire_blaster, + ai_charge, 0, widow_fire_blaster, + ai_charge, 0, widow_fire_blaster, + ai_charge, 0, widow_fire_blaster, + ai_charge, 0, widow_fire_blaster, + ai_charge, 0, widow_fire_blaster, + ai_charge, 0, widow_fire_blaster, + ai_charge, 0, widow_fire_blaster, + ai_charge, 0, widow_fire_blaster, + ai_charge, 0, widow_fire_blaster, + ai_charge, 0, widow_fire_blaster, + ai_charge, 0, widow_fire_blaster +}; +static mmove_t widow_move_blaster = { WIDOW_FRAME_fired02a, WIDOW_FRAME_fired20, widow_frames_blaster, widow_run }; + +static mframe_t widow_frames_rail[] = +{ + ai_charge, 0, NULL, + ai_charge, -6, NULL, + ai_charge, -10, widow_fire_rail, + ai_charge, -4, NULL, + ai_charge, 0, NULL, + ai_charge, -6, widow_fire_rail, + ai_charge, -4, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +static mmove_t widow_move_rail = { WIDOW_FRAME_firea01, WIDOW_FRAME_firea09, widow_frames_rail, widow_run }; +static mmove_t widow_move_rail_right = { WIDOW_FRAME_fireb01, WIDOW_FRAME_fireb09, widow_frames_rail, widow_run }; +static mmove_t widow_move_rail_left = { WIDOW_FRAME_firec01, WIDOW_FRAME_firec09, widow_frames_rail, widow_run }; + +static mframe_t widow_frames_spawn[] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, widow_spawn_effects, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, -4, widow_finish_spawn, + ai_charge, -8, NULL, + ai_charge, -10, NULL, + ai_charge, -8, NULL, + ai_charge, -4, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +static mmove_t widow_move_spawn = { WIDOW_FRAME_spawn01, WIDOW_FRAME_spawn18, widow_frames_spawn, widow_run }; + +static mframe_t widow_frames_pain[] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +static mmove_t widow_move_pain = { WIDOW_FRAME_pain01, WIDOW_FRAME_pain05, widow_frames_pain, widow_run }; + +static mframe_t widow_frames_death[] = +{ + ai_move, 0, widow_explode, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, widow_explode, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, widow_explode, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, widow_explode, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, widow_explode, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, widow_explode, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, widow_explode, + ai_move, 0, widow_dead +}; +static mmove_t widow_move_death = { WIDOW_FRAME_death01, WIDOW_FRAME_death31, widow_frames_death, NULL }; + +static mframe_t widow_frames_kick[] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, widow_melee_hit, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +static mmove_t widow_move_kick = { WIDOW_FRAME_kick01, WIDOW_FRAME_kick08, widow_frames_kick, widow_run }; + +static void widow_step1(edict_t *self) +{ + gi.sound(self, CHAN_BODY, sound_step1, 1, ATTN_NORM, 0); +} + +static void widow_step2(edict_t *self) +{ + gi.sound(self, CHAN_BODY, sound_step2, 1, ATTN_NORM, 0); +} + +static void widow_sight(edict_t *self, edict_t *other) +{ + gi.sound(self, CHAN_VOICE, sound_laugh, 1, ATTN_NORM, 0); +} + +static void widow_stand(edict_t *self) +{ + self->monsterinfo.currentmove = &widow_move_stand; +} + +static void widow_walk(edict_t *self) +{ + if (!self->goalentity) + self->goalentity = world; + self->monsterinfo.currentmove = &widow_move_walk; +} + +static void widow_run(edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &widow_move_stand; + else + self->monsterinfo.currentmove = &widow_move_run; +} + +static qboolean widow_can_melee(edict_t *self) +{ + return G_EntExists(self->enemy) && entdist(self, self->enemy) <= WIDOW_MELEE_RANGE; +} + +static int widow_blaster_flash(edict_t *self) +{ + if (self->s.frame >= WIDOW_FRAME_spawn01 + 4 && self->s.frame <= WIDOW_FRAME_spawn01 + 12) + return MZ2_WIDOW_BLASTER_SWEEP1 + self->s.frame - (WIDOW_FRAME_spawn01 + 4); + if (self->s.frame == WIDOW_FRAME_fired02a) + return MZ2_WIDOW_BLASTER_0; + if (self->s.frame >= WIDOW_FRAME_fired03 && self->s.frame <= WIDOW_FRAME_fired20) + return MZ2_WIDOW_BLASTER_100 + self->s.frame - WIDOW_FRAME_fired03; + if (self->s.frame >= WIDOW_FRAME_run01 && self->s.frame <= WIDOW_FRAME_run08) + return MZ2_WIDOW_RUN_1 + self->s.frame - WIDOW_FRAME_run01; + return MZ2_WIDOW_BLASTER; +} + +static void widow_fire_blaster(edict_t *self) +{ + int damage; + int speed; + int flash; + int effect; + vec3_t forward, start; + + if (!G_EntExists(self->enemy)) + return; + + damage = M_BLASTER2_DMG_BASE + M_BLASTER2_DMG_ADDON * drone_damagelevel(self); + if (M_BLASTER2_DMG_MAX && damage > M_BLASTER2_DMG_MAX) + damage = M_BLASTER2_DMG_MAX; + speed = M_BLASTER2_SPEED_BASE + M_BLASTER2_SPEED_ADDON * drone_damagelevel(self); + if (M_BLASTER2_SPEED_MAX && speed > M_BLASTER2_SPEED_MAX) + speed = M_BLASTER2_SPEED_MAX; + + flash = widow_blaster_flash(self); + shotsfired++; + effect = (shotsfired % 4) ? 0 : EF_BLASTER; + MonsterAim(self, M_PROJECTILE_ACC, speed, false, flash, forward, start); + monster_fire_blaster2(self, start, forward, damage, speed, effect, flash); +} + +static void widow_fire_rail(edict_t *self) +{ + int damage; + int flash; + vec3_t forward, start; + + if (!G_EntExists(self->enemy)) + return; + + damage = M_RAILGUN_DMG_BASE + M_RAILGUN_DMG_ADDON * drone_damagelevel(self); + if (M_RAILGUN_DMG_MAX && damage > M_RAILGUN_DMG_MAX) + damage = M_RAILGUN_DMG_MAX; + + if (self->monsterinfo.currentmove == &widow_move_rail_left) + flash = MZ2_WIDOW_RAIL_LEFT; + else if (self->monsterinfo.currentmove == &widow_move_rail_right) + flash = MZ2_WIDOW_RAIL_RIGHT; + else + flash = MZ2_WIDOW_RAIL; + MonsterAim(self, M_HITSCAN_INSTANT_ACC, 0, false, flash, forward, start); + gi.sound(self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0); + monster_fire_railgun(self, start, forward, damage, damage, flash); +} + +static void widow_melee_hit(edict_t *self) +{ + int damage; + + damage = M_MELEE_DMG_BASE + M_MELEE_DMG_ADDON * drone_damagelevel(self); + if (M_MeleeAttack(self, self->enemy, 160, damage, 350)) + gi.sound(self, CHAN_WEAPON, sound_hit, 1, ATTN_NORM, 0); +} + +static qboolean widow_valid_spawn_spot(edict_t *self, vec3_t mins, vec3_t maxs, vec3_t spot) +{ + vec3_t start, end; + trace_t tr; + + VectorCopy(spot, start); + start[2] += 72; + VectorCopy(spot, end); + end[2] -= 128; + + tr = gi.trace(start, mins, maxs, end, self, MASK_MONSTERSOLID); + if (tr.fraction == 1.0f || tr.startsolid || tr.allsolid) + return false; + + VectorCopy(tr.endpos, spot); + return G_IsValidLocation(self, spot, mins, maxs); +} + +static qboolean widow_find_spawn_spot(edict_t *self, int index, vec3_t spot) +{ + vec3_t mins, maxs; + vec3_t forward, right; + float side; + + VectorSet(mins, -28, -28, -18); + VectorSet(maxs, 28, 28, 18); + AngleVectors(self->s.angles, forward, right, NULL); + + side = (index & 1) ? 88.0f : -88.0f; + VectorCopy(self->s.origin, spot); + VectorMA(spot, 112.0f, forward, spot); + VectorMA(spot, side, right, spot); + if (widow_valid_spawn_spot(self, mins, maxs, spot)) + return true; + + VectorCopy(self->s.origin, spot); + VectorMA(spot, -96.0f, forward, spot); + VectorMA(spot, side, right, spot); + return widow_valid_spawn_spot(self, mins, maxs, spot); +} + +static void widow_cleanup_failed_spawn(edict_t *owner, edict_t *spawned) +{ + if (owner && owner->client) + layout_remove_tracked_entity(&owner->client->layout, spawned); + + DroneList_Remove(spawned); + AI_EnemyRemoved(spawned); + G_FreeEdict(spawned); +} + +static qboolean widow_spawn_stalker(edict_t *self, int index) +{ + edict_t *owner; + edict_t *spawned; + vec3_t spot; + + owner = (self->activator && self->activator->inuse) ? self->activator : self; + spawned = G_Spawn(); + spawned->mtype = M_STALKER; + spawned->activator = owner; + spawned->monsterinfo.level = self->monsterinfo.level; + + if (!M_Initialize(owner, spawned, 0.0f)) + { + G_FreeEdict(spawned); + return false; + } + + if (!widow_find_spawn_spot(self, index, spot)) + { + widow_cleanup_failed_spawn(owner, spawned); + return false; + } + + spawned->monsterinfo.cost = 0; + spawned->s.effects |= EF_PLASMA; + VectorCopy(spot, spawned->s.origin); + VectorCopy(spot, spawned->s.old_origin); + VectorCopy(self->s.angles, spawned->s.angles); + spawned->nextthink = level.time + FRAMETIME; + spawned->monsterinfo.attack_finished = level.time + 1.0f; + + if (invasion->value) + { + spawned->monsterinfo.aiflags &= ~AI_STAND_GROUND; + spawned->monsterinfo.aiflags |= AI_FIND_NAVI; + spawned->prev_navi = NULL; + spawned->goalentity = NULL; + } + + if (G_ValidTarget(spawned, self->enemy, true, true)) + spawned->enemy = self->enemy; + + gi.linkentity(spawned); + owner->num_monsters += spawned->monsterinfo.control_cost; + owner->num_monsters_real++; + + if (spawned->enemy && spawned->monsterinfo.run) + spawned->monsterinfo.run(spawned); + else if (spawned->monsterinfo.stand) + spawned->monsterinfo.stand(spawned); + + return true; +} + +static void widow_spawn_effects(edict_t *self) +{ + vec3_t mins, maxs, size, spot, effect_origin; + float radius; + + VectorSet(mins, -28, -28, -18); + VectorSet(maxs, 28, 28, 18); + VectorSubtract(maxs, mins, size); + radius = VectorLength(size) * 0.5f; + + for (int i = 0; i < WIDOW_SUMMON_COUNT; i++) + { + if (!widow_find_spawn_spot(self, i, spot)) + continue; + + VectorAdd(mins, maxs, effect_origin); + VectorAdd(spot, effect_origin, effect_origin); + SpawnGrow_Spawn(effect_origin, radius, radius * 2.0f); + } +} + +static void widow_finish_spawn(edict_t *self) +{ + int spawned = 0; + + for (int i = 0; i < WIDOW_SUMMON_COUNT; i++) + { + if (widow_spawn_stalker(self, i)) + spawned++; + } + + if (spawned) + self->monsterinfo.melee_finished = level.time + WIDOW_SUMMON_COOLDOWN; +} + +static void widow_start_spawn(edict_t *self) +{ + gi.sound(self, CHAN_WEAPON, sound_spawn, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &widow_move_spawn; +} + +static void widow_melee(edict_t *self) +{ + if (!widow_can_melee(self)) + { + self->monsterinfo.melee_finished = level.time + 0.5f; + return; + } + + self->monsterinfo.currentmove = &widow_move_kick; + self->monsterinfo.melee_finished = level.time + 1.2f; + M_DelayNextAttack(self, 1.0f, true); +} + +static void widow_attack(edict_t *self) +{ + float r; + + if (!G_EntExists(self->enemy)) + return; + + r = random(); + if (widow_can_melee(self) && r < 0.40f) + widow_melee(self); + else if (level.time >= self->monsterinfo.melee_finished && r < 0.65f) + widow_start_spawn(self); + else if (r < 0.82f) + { + if (random() < 0.33f) + self->monsterinfo.currentmove = &widow_move_rail_left; + else if (random() < 0.5f) + self->monsterinfo.currentmove = &widow_move_rail_right; + else + self->monsterinfo.currentmove = &widow_move_rail; + } + else + self->monsterinfo.currentmove = &widow_move_blaster; + + M_DelayNextAttack(self, 1.0f + random(), true); +} + +static void widow_pain(edict_t *self, edict_t *other, float kick, int damage) +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3.0f; + if (random() < 0.33f) + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + else if (random() < 0.5f) + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_VOICE, sound_pain3, 1, ATTN_NORM, 0); + + if (skill->value != 3) + self->monsterinfo.currentmove = &widow_move_pain; +} + +static void widow_explode(edict_t *self) +{ + vec3_t org; + + VectorCopy(self->s.origin, org); + org[0] += crandom() * self->maxs[0]; + org[1] += crandom() * self->maxs[1]; + org[2] += random() * self->maxs[2]; + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1_BIG); + gi.WritePosition(org); + gi.multicast(self->s.origin, MULTICAST_PVS); +} + +static void widow_dead(edict_t *self) +{ + for (int n = 0; n < 4; n++) + ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", 500, GIB_ORGANIC); + for (int n = 0; n < 6; n++) + ThrowGib(self, "models/objects/gibs/sm_metal/tris.md2", 500, GIB_METALLIC); + + M_Remove(self, false, false); +} + +static void widow_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + M_Notify(self); + + if (self->deadflag == DEAD_DEAD) + return; + + DroneList_Remove(self); + gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + self->monsterinfo.currentmove = &widow_move_death; +} + +void init_drone_widow(edict_t *self) +{ + sound_pain1 = gi.soundindex("widow/bw1pain1.wav"); + sound_pain2 = gi.soundindex("widow/bw1pain2.wav"); + sound_pain3 = gi.soundindex("widow/bw1pain3.wav"); + sound_death = gi.soundindex("widow/death.wav"); + sound_laugh = gi.soundindex("widow/laugh.wav"); + sound_rail = gi.soundindex("gladiator/railgun.wav"); + sound_step1 = gi.soundindex("widow/bwstep1.wav"); + sound_step2 = gi.soundindex("widow/bwstep2.wav"); + sound_hit = gi.soundindex("tank/tnkatck3.wav"); + sound_spawn = gi.soundindex("medic_commander/monsterspawn1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/blackwidow/tris.md2"); + VectorSet(self->mins, -40, -40, 0); + VectorSet(self->maxs, 40, 40, 144); + + gi.modelindex("models/items/spawngro3/tris.md2"); + gi.modelindex("models/monsters/stalker/tris.md2"); + gi.modelindex("models/monsters/blackwidow/gib1/tris.md2"); + gi.modelindex("models/monsters/blackwidow/gib2/tris.md2"); + gi.modelindex("models/monsters/blackwidow/gib3/tris.md2"); + gi.modelindex("models/monsters/blackwidow/gib4/tris.md2"); + + self->health = M_WIDOW_INITIAL_HEALTH + M_WIDOW_ADDON_HEALTH * self->monsterinfo.level; + self->max_health = self->health; + self->gib_health = -5000; + self->mass = 1500; + self->mtype = M_WIDOW; + self->yaw_speed = 30; + self->flags |= FL_IMMUNE_LASER; + + self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD; + self->monsterinfo.power_armor_power = M_WIDOW_INITIAL_ARMOR + M_WIDOW_ADDON_ARMOR * self->monsterinfo.level; + self->monsterinfo.max_armor = self->monsterinfo.power_armor_power; + self->monsterinfo.control_cost = M_JORG_CONTROL_COST; + self->monsterinfo.cost = M_COMMANDER_COST; + self->monsterinfo.jumpup = 64; + self->monsterinfo.jumpdn = 512; + self->monsterinfo.aiflags |= AI_NO_CIRCLE_STRAFE; + + self->pain = widow_pain; + self->die = widow_die; + self->monsterinfo.stand = widow_stand; + self->monsterinfo.walk = widow_walk; + self->monsterinfo.run = widow_run; + self->monsterinfo.attack = widow_attack; + self->monsterinfo.melee = widow_melee; + self->monsterinfo.sight = widow_sight; + + gi.linkentity(self); + + self->monsterinfo.currentmove = &widow_move_stand; + self->monsterinfo.scale = 2.0f; + self->nextthink = level.time + FRAMETIME; + + G_PrintGreenText(va("A level %d widow has spawned!", self->monsterinfo.level)); +} diff --git a/src/entities/drone/drone_widow2.c b/src/entities/drone/drone_widow2.c new file mode 100644 index 00000000..387e989f --- /dev/null +++ b/src/entities/drone/drone_widow2.c @@ -0,0 +1,745 @@ +/* +============================================================================== + +black widow 2 + +============================================================================== +*/ + +#include "g_local.h" + +#define WIDOW2_FRAME_blackwidow3 0 +#define WIDOW2_FRAME_walk01 1 +#define WIDOW2_FRAME_walk09 9 +#define WIDOW2_FRAME_spawn01 10 +#define WIDOW2_FRAME_spawn04 13 +#define WIDOW2_FRAME_spawn14 23 +#define WIDOW2_FRAME_spawn18 27 +#define WIDOW2_FRAME_firea01 28 +#define WIDOW2_FRAME_firea07 34 +#define WIDOW2_FRAME_fireb01 35 +#define WIDOW2_FRAME_fireb04 38 +#define WIDOW2_FRAME_fireb05 39 +#define WIDOW2_FRAME_fireb09 43 +#define WIDOW2_FRAME_tongs01 47 +#define WIDOW2_FRAME_tongs08 54 +#define WIDOW2_FRAME_pain01 55 +#define WIDOW2_FRAME_pain05 59 +#define WIDOW2_FRAME_death01 60 +#define WIDOW2_FRAME_death44 103 + +#define WIDOW2_SUMMON_COUNT 2 +#define WIDOW2_SUMMON_COOLDOWN 10.0f +#define WIDOW2_MELEE_RANGE 256.0f + +static int sound_pain1; +static int sound_pain2; +static int sound_pain3; +static int sound_death; +static int sound_search; +static int sound_tongue; +static int sound_step; +static int sound_beam; +static int sound_hit; +static int sound_spawn; + +static vec3_t widow2_tongue_offsets[] = +{ + { 17.48f, 0.10f, 68.92f }, + { 17.47f, 0.29f, 68.91f }, + { 17.45f, 0.53f, 68.87f }, + { 17.42f, 0.78f, 68.81f }, + { 17.39f, 1.02f, 68.75f }, + { 17.37f, 1.20f, 68.70f }, + { 17.36f, 1.24f, 68.71f }, + { 17.37f, 1.21f, 68.72f } +}; + +void drone_ai_stand(edict_t *self, float dist); +void drone_ai_run(edict_t *self, float dist); +void drone_ai_walk(edict_t *self, float dist); + +static void widow2_stand(edict_t *self); +static void widow2_walk(edict_t *self); +static void widow2_run(edict_t *self); +static void widow2_attack(edict_t *self); +static void widow2_fire_beam(edict_t *self); +static void widow2_fire_disruptor(edict_t *self); +static void widow2_attack_beam(edict_t *self); +static void widow2_show_proboscis(edict_t *self); +static void widow2_pull_proboscis(edict_t *self); +static void widow2_melee_hit(edict_t *self); +static void widow2_spawn_effects(edict_t *self); +static void widow2_finish_spawn(edict_t *self); +static void widow2_explode(edict_t *self); +static void widow2_dead(edict_t *self); +static void widow2_step(edict_t *self); + +static mframe_t widow2_frames_stand[] = +{ + drone_ai_stand, 0, NULL +}; +static mmove_t widow2_move_stand = { WIDOW2_FRAME_blackwidow3, WIDOW2_FRAME_blackwidow3, widow2_frames_stand, widow2_stand }; + +static mframe_t widow2_frames_walk[] = +{ + drone_ai_walk, 9, widow2_step, + drone_ai_walk, 8, NULL, + drone_ai_walk, 7, NULL, + drone_ai_walk, 7, NULL, + drone_ai_walk, 6, NULL, + drone_ai_walk, 6, widow2_step, + drone_ai_walk, 7, NULL, + drone_ai_walk, 8, NULL, + drone_ai_walk, 10, NULL +}; +static mmove_t widow2_move_walk = { WIDOW2_FRAME_walk01, WIDOW2_FRAME_walk09, widow2_frames_walk, widow2_walk }; + +static mframe_t widow2_frames_run[] = +{ + drone_ai_run, 9, widow2_step, + drone_ai_run, 8, NULL, + drone_ai_run, 7, NULL, + drone_ai_run, 7, NULL, + drone_ai_run, 6, NULL, + drone_ai_run, 6, widow2_step, + drone_ai_run, 7, NULL, + drone_ai_run, 8, NULL, + drone_ai_run, 10, NULL +}; +static mmove_t widow2_move_run = { WIDOW2_FRAME_walk01, WIDOW2_FRAME_walk09, widow2_frames_run, NULL }; + +static mframe_t widow2_frames_pre_beam[] = +{ + ai_charge, 4, NULL, + ai_charge, 4, widow2_step, + ai_charge, 4, NULL, + ai_charge, 4, widow2_attack_beam +}; +static mmove_t widow2_move_pre_beam = { WIDOW2_FRAME_fireb01, WIDOW2_FRAME_fireb04, widow2_frames_pre_beam, NULL }; + +static mframe_t widow2_frames_beam[] = +{ + ai_charge, 0, widow2_fire_beam, + ai_charge, 0, widow2_fire_beam, + ai_charge, 0, widow2_fire_beam, + ai_charge, 0, widow2_fire_beam, + ai_charge, 0, widow2_fire_beam +}; +static mmove_t widow2_move_beam = { WIDOW2_FRAME_fireb05, WIDOW2_FRAME_fireb09, widow2_frames_beam, widow2_run }; + +static mframe_t widow2_frames_disruptor[] = +{ + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, -20, widow2_fire_disruptor, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL +}; +static mmove_t widow2_move_disruptor = { WIDOW2_FRAME_firea01, WIDOW2_FRAME_firea07, widow2_frames_disruptor, widow2_run }; + +static mframe_t widow2_frames_spawn[] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, widow2_step, + ai_charge, 0, widow2_fire_beam, + ai_charge, 0, NULL, + ai_charge, 0, widow2_fire_beam, + ai_charge, 0, NULL, + ai_charge, 0, widow2_fire_beam, + ai_charge, 0, NULL, + ai_charge, 0, widow2_spawn_effects, + ai_charge, 0, NULL, + ai_charge, 0, widow2_fire_beam, + ai_charge, 0, NULL, + ai_charge, 0, widow2_finish_spawn, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +static mmove_t widow2_move_spawn = { WIDOW2_FRAME_spawn01, WIDOW2_FRAME_spawn18, widow2_frames_spawn, widow2_run }; + +static mframe_t widow2_frames_tongs[] = +{ + ai_charge, 0, widow2_show_proboscis, + ai_charge, 0, widow2_show_proboscis, + ai_charge, 0, widow2_show_proboscis, + ai_charge, 0, widow2_pull_proboscis, + ai_charge, 0, widow2_pull_proboscis, + ai_charge, 0, widow2_pull_proboscis, + ai_charge, 0, widow2_melee_hit, + ai_charge, 0, NULL +}; +static mmove_t widow2_move_tongs = { WIDOW2_FRAME_tongs01, WIDOW2_FRAME_tongs08, widow2_frames_tongs, widow2_run }; + +static mframe_t widow2_frames_pain[] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +static mmove_t widow2_move_pain = { WIDOW2_FRAME_pain01, WIDOW2_FRAME_pain05, widow2_frames_pain, widow2_run }; + +static mframe_t widow2_frames_death[] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, widow2_explode, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, widow2_explode, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, widow2_explode, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, widow2_explode, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, widow2_explode, + ai_move, 0, widow2_explode, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, widow2_explode, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, widow2_explode, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, widow2_dead +}; +static mmove_t widow2_move_death = { WIDOW2_FRAME_death01, WIDOW2_FRAME_death44, widow2_frames_death, NULL }; + +static void widow2_step(edict_t *self) +{ + gi.sound(self, CHAN_BODY, sound_step, 1, ATTN_NORM, 0); +} + +static void widow2_sight(edict_t *self, edict_t *other) +{ + gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); +} + +static void widow2_stand(edict_t *self) +{ + self->monsterinfo.currentmove = &widow2_move_stand; +} + +static void widow2_walk(edict_t *self) +{ + if (!self->goalentity) + self->goalentity = world; + self->monsterinfo.currentmove = &widow2_move_walk; +} + +static void widow2_run(edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &widow2_move_stand; + else + self->monsterinfo.currentmove = &widow2_move_run; +} + +static qboolean widow2_can_melee(edict_t *self) +{ + return G_EntExists(self->enemy) && entdist(self, self->enemy) <= WIDOW2_MELEE_RANGE; +} + +static void widow2_fire_beam(edict_t *self) +{ + int damage; + int flash; + vec3_t forward, start; + + if (!G_EntExists(self->enemy)) + return; + + damage = M_RAILGUN_DMG_BASE + M_RAILGUN_DMG_ADDON * drone_damagelevel(self); + if (M_RAILGUN_DMG_MAX && damage > M_RAILGUN_DMG_MAX) + damage = M_RAILGUN_DMG_MAX; + + if (self->s.frame >= WIDOW2_FRAME_fireb05 && self->s.frame <= WIDOW2_FRAME_fireb09) + flash = MZ2_WIDOW2_BEAMER_1 + self->s.frame - WIDOW2_FRAME_fireb05; + else if (self->s.frame >= WIDOW2_FRAME_spawn04 && self->s.frame <= WIDOW2_FRAME_spawn14) + flash = MZ2_WIDOW2_BEAM_SWEEP_1 + self->s.frame - WIDOW2_FRAME_spawn04; + else + flash = MZ2_WIDOW2_BEAM_SWEEP_1; + + MonsterAim(self, M_HITSCAN_INSTANT_ACC, 0, false, flash, forward, start); + gi.sound(self, CHAN_WEAPON, sound_beam, 1, ATTN_NORM, 0); + monster_fire_railgun(self, start, forward, damage, damage, flash); +} + +static void widow2_fire_disruptor(edict_t *self) +{ + int damage; + int speed; + vec3_t forward, start; + + if (!G_EntExists(self->enemy)) + return; + + damage = DISRUPTOR_INITIAL_DAMAGE + DISRUPTOR_ADDON_DAMAGE * drone_damagelevel(self); + if (M_DISRUPTOR_DMG_MAX && damage > M_DISRUPTOR_DMG_MAX) + damage = M_DISRUPTOR_DMG_MAX; + speed = DISRUPTOR_INITIAL_SPEED + DISRUPTOR_ADDON_SPEED * drone_damagelevel(self); + if (M_DISRUPTOR_SPEED_MAX && speed > M_DISRUPTOR_SPEED_MAX) + speed = M_DISRUPTOR_SPEED_MAX; + + MonsterAim(self, M_PROJECTILE_ACC, speed, true, MZ2_WIDOW_DISRUPTOR, forward, start); + fire_disruptor(self, start, forward, damage, speed, visible(self, self->enemy) ? self->enemy : NULL); + gi.WriteByte(svc_muzzleflash2); + gi.WriteShort(self - g_edicts); + gi.WriteByte(MZ2_WIDOW_DISRUPTOR); + gi.multicast(start, MULTICAST_PVS); + widow2_step(self); +} + +static void widow2_attack_beam(edict_t *self) +{ + self->monsterinfo.currentmove = &widow2_move_beam; + widow2_step(self); +} + +static void widow2_proboscis_start(edict_t *self, vec3_t start) +{ + int index = self->s.frame - WIDOW2_FRAME_tongs01; + vec3_t forward, right, offset; + + if (index < 0) + index = 0; + else if (index > 7) + index = 7; + + VectorCopy(widow2_tongue_offsets[index], offset); + AngleVectors(self->s.angles, forward, right, NULL); + G_ProjectSource(self->s.origin, offset, forward, right, start); +} + +static qboolean widow2_proboscis_ok(edict_t *self, vec3_t start, vec3_t end) +{ + vec3_t dir, angles; + trace_t tr; + + VectorSubtract(start, end, dir); + if (VectorLength(dir) > WIDOW2_MELEE_RANGE) + return false; + + vectoangles(dir, angles); + if (angles[PITCH] < -180) + angles[PITCH] += 360; + if (fabsf(angles[PITCH]) > 30) + return false; + + tr = gi.trace(start, NULL, NULL, end, self, MASK_SHOT); + return tr.ent == self->enemy; +} + +static qboolean widow2_proboscis_target(edict_t *self, vec3_t start, vec3_t end) +{ + if (!G_EntExists(self->enemy)) + return false; + + G_EntMidPoint(self->enemy, end); + if (widow2_proboscis_ok(self, start, end)) + return true; + + VectorCopy(self->enemy->s.origin, end); + end[2] = self->enemy->absmax[2] - 8; + if (widow2_proboscis_ok(self, start, end)) + return true; + + VectorCopy(self->enemy->s.origin, end); + end[2] = self->enemy->absmin[2] + 8; + return widow2_proboscis_ok(self, start, end); +} + +static qboolean widow2_draw_proboscis(edict_t *self, vec3_t start, vec3_t end) +{ + widow2_proboscis_start(self, start); + if (!widow2_proboscis_target(self, start, end)) + return false; + + gi.sound(self, CHAN_WEAPON, sound_tongue, 1, ATTN_NORM, 0); + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_PARASITE_ATTACK); + gi.WriteShort(self - g_edicts); + gi.WritePosition(start); + gi.WritePosition(end); + gi.multicast(self->s.origin, MULTICAST_PVS); + return true; +} + +static void widow2_pull_enemy(edict_t *self) +{ + vec3_t pull; + + if (!G_EntExists(self->enemy)) + return; + + if (self->enemy->groundentity) + { + self->enemy->s.origin[2] += 1; + self->enemy->groundentity = NULL; + } + + VectorSubtract(self->s.origin, self->enemy->s.origin, pull); + VectorNormalize(pull); + VectorMA(self->enemy->velocity, 700, pull, self->enemy->velocity); +} + +static void widow2_show_proboscis(edict_t *self) +{ + vec3_t start, end; + + widow2_draw_proboscis(self, start, end); +} + +static void widow2_pull_proboscis(edict_t *self) +{ + vec3_t start, end; + + if (widow2_draw_proboscis(self, start, end)) + widow2_pull_enemy(self); +} + +static void widow2_melee_hit(edict_t *self) +{ + int damage; + vec3_t start, end; + + if (!widow2_draw_proboscis(self, start, end)) + return; + + widow2_pull_enemy(self); + + damage = M_MELEE_DMG_BASE + M_MELEE_DMG_ADDON * drone_damagelevel(self); + if (M_MeleeAttack(self, self->enemy, WIDOW2_MELEE_RANGE, damage, 500)) + gi.sound(self, CHAN_WEAPON, sound_hit, 1, ATTN_NORM, 0); +} + +static qboolean widow2_valid_spawn_spot(edict_t *self, vec3_t mins, vec3_t maxs, vec3_t spot) +{ + vec3_t start, end; + trace_t tr; + + VectorCopy(spot, start); + start[2] += 96; + VectorCopy(spot, end); + end[2] -= 160; + + tr = gi.trace(start, mins, maxs, end, self, MASK_MONSTERSOLID); + if (tr.fraction == 1.0f || tr.startsolid || tr.allsolid) + return false; + + VectorCopy(tr.endpos, spot); + return G_IsValidLocation(self, spot, mins, maxs); +} + +static qboolean widow2_find_spawn_spot(edict_t *self, int index, vec3_t spot) +{ + vec3_t mins, maxs; + vec3_t forward, right; + float side; + + VectorSet(mins, -28, -28, -18); + VectorSet(maxs, 28, 28, 18); + AngleVectors(self->s.angles, forward, right, NULL); + + side = (index & 1) ? 112.0f : -112.0f; + VectorCopy(self->s.origin, spot); + VectorMA(spot, 144.0f, forward, spot); + VectorMA(spot, side, right, spot); + if (widow2_valid_spawn_spot(self, mins, maxs, spot)) + return true; + + VectorCopy(self->s.origin, spot); + VectorMA(spot, -120.0f, forward, spot); + VectorMA(spot, side, right, spot); + return widow2_valid_spawn_spot(self, mins, maxs, spot); +} + +static void widow2_cleanup_failed_spawn(edict_t *owner, edict_t *spawned) +{ + if (owner && owner->client) + layout_remove_tracked_entity(&owner->client->layout, spawned); + + DroneList_Remove(spawned); + AI_EnemyRemoved(spawned); + G_FreeEdict(spawned); +} + +static qboolean widow2_spawn_stalker(edict_t *self, int index) +{ + edict_t *owner; + edict_t *spawned; + vec3_t spot; + + owner = (self->activator && self->activator->inuse) ? self->activator : self; + spawned = G_Spawn(); + spawned->mtype = M_STALKER; + spawned->activator = owner; + spawned->monsterinfo.level = self->monsterinfo.level; + + if (!M_Initialize(owner, spawned, 0.0f)) + { + G_FreeEdict(spawned); + return false; + } + + if (!widow2_find_spawn_spot(self, index, spot)) + { + widow2_cleanup_failed_spawn(owner, spawned); + return false; + } + + spawned->monsterinfo.cost = 0; + spawned->s.effects |= EF_PLASMA; + VectorCopy(spot, spawned->s.origin); + VectorCopy(spot, spawned->s.old_origin); + VectorCopy(self->s.angles, spawned->s.angles); + spawned->nextthink = level.time + FRAMETIME; + spawned->monsterinfo.attack_finished = level.time + 1.0f; + + if (invasion->value) + { + spawned->monsterinfo.aiflags &= ~AI_STAND_GROUND; + spawned->monsterinfo.aiflags |= AI_FIND_NAVI; + spawned->prev_navi = NULL; + spawned->goalentity = NULL; + } + + if (G_ValidTarget(spawned, self->enemy, true, true)) + spawned->enemy = self->enemy; + + gi.linkentity(spawned); + owner->num_monsters += spawned->monsterinfo.control_cost; + owner->num_monsters_real++; + + if (spawned->enemy && spawned->monsterinfo.run) + spawned->monsterinfo.run(spawned); + else if (spawned->monsterinfo.stand) + spawned->monsterinfo.stand(spawned); + + return true; +} + +static void widow2_spawn_effects(edict_t *self) +{ + vec3_t mins, maxs, size, spot, effect_origin; + float radius; + + VectorSet(mins, -28, -28, -18); + VectorSet(maxs, 28, 28, 18); + VectorSubtract(maxs, mins, size); + radius = VectorLength(size) * 0.5f; + + for (int i = 0; i < WIDOW2_SUMMON_COUNT; i++) + { + if (!widow2_find_spawn_spot(self, i, spot)) + continue; + + VectorAdd(mins, maxs, effect_origin); + VectorAdd(spot, effect_origin, effect_origin); + SpawnGrow_Spawn(effect_origin, radius, radius * 2.0f); + } +} + +static void widow2_finish_spawn(edict_t *self) +{ + int spawned = 0; + + for (int i = 0; i < WIDOW2_SUMMON_COUNT; i++) + { + if (widow2_spawn_stalker(self, i)) + spawned++; + } + + if (spawned) + self->monsterinfo.melee_finished = level.time + WIDOW2_SUMMON_COOLDOWN; +} + +static void widow2_start_spawn(edict_t *self) +{ + gi.sound(self, CHAN_WEAPON, sound_spawn, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &widow2_move_spawn; +} + +static void widow2_melee(edict_t *self) +{ + if (!widow2_can_melee(self)) + { + self->monsterinfo.melee_finished = level.time + 0.5f; + return; + } + + self->monsterinfo.currentmove = &widow2_move_tongs; + self->monsterinfo.melee_finished = level.time + 1.5f; + M_DelayNextAttack(self, 1.0f, true); +} + +static void widow2_attack(edict_t *self) +{ + float r; + + if (!G_EntExists(self->enemy)) + return; + + r = random(); + if (widow2_can_melee(self) && r < 0.35f) + widow2_melee(self); + else if (level.time >= self->monsterinfo.melee_finished && r < 0.60f) + widow2_start_spawn(self); + else if (r < 0.80f) + self->monsterinfo.currentmove = &widow2_move_disruptor; + else + self->monsterinfo.currentmove = &widow2_move_pre_beam; + + M_DelayNextAttack(self, 1.0f + random(), true); +} + +static void widow2_pain(edict_t *self, edict_t *other, float kick, int damage) +{ + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3.0f; + if (random() < 0.33f) + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + else if (random() < 0.5f) + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_VOICE, sound_pain3, 1, ATTN_NORM, 0); + + if (skill->value != 3) + self->monsterinfo.currentmove = &widow2_move_pain; +} + +static void widow2_explode(edict_t *self) +{ + vec3_t org; + + VectorCopy(self->s.origin, org); + org[0] += crandom() * self->maxs[0]; + org[1] += crandom() * self->maxs[1]; + org[2] += random() * self->maxs[2]; + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1_BIG); + gi.WritePosition(org); + gi.multicast(self->s.origin, MULTICAST_PVS); +} + +static void widow2_dead(edict_t *self) +{ + for (int n = 0; n < 5; n++) + ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", 500, GIB_ORGANIC); + for (int n = 0; n < 8; n++) + ThrowGib(self, "models/objects/gibs/sm_metal/tris.md2", 500, GIB_METALLIC); + + M_Remove(self, false, false); +} + +static void widow2_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + M_Notify(self); + + if (self->deadflag == DEAD_DEAD) + return; + + DroneList_Remove(self); + gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + self->monsterinfo.currentmove = &widow2_move_death; +} + +void init_drone_widow2(edict_t *self) +{ + sound_pain1 = gi.soundindex("widow/bw2pain1.wav"); + sound_pain2 = gi.soundindex("widow/bw2pain2.wav"); + sound_pain3 = gi.soundindex("widow/bw2pain3.wav"); + sound_death = gi.soundindex("widow/death.wav"); + sound_search = gi.soundindex("bosshovr/bhvunqv1.wav"); + sound_tongue = gi.soundindex("brain/brnatck3.wav"); + sound_step = gi.soundindex("widow/bwstep1.wav"); + gi.soundindex("weapons/disrupt.wav"); + sound_beam = gi.soundindex("weapons/disint2.wav"); + sound_hit = gi.soundindex("infantry/melee2.wav"); + sound_spawn = gi.soundindex("medic_commander/monsterspawn1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/blackwidow2/tris.md2"); + VectorSet(self->mins, -70, -70, 0); + VectorSet(self->maxs, 70, 70, 144); + + gi.modelindex("models/items/spawngro3/tris.md2"); + gi.modelindex("models/monsters/stalker/tris.md2"); + gi.modelindex("models/monsters/blackwidow2/gib1/tris.md2"); + gi.modelindex("models/monsters/blackwidow2/gib2/tris.md2"); + gi.modelindex("models/monsters/blackwidow2/gib3/tris.md2"); + gi.modelindex("models/monsters/blackwidow2/gib4/tris.md2"); + + self->health = M_WIDOW2_INITIAL_HEALTH + M_WIDOW2_ADDON_HEALTH * self->monsterinfo.level; + self->max_health = self->health; + self->gib_health = -5000; + self->mass = 2500; + self->mtype = M_WIDOW2; + self->yaw_speed = 30; + self->flags |= FL_IMMUNE_LASER; + + self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD; + self->monsterinfo.power_armor_power = M_WIDOW2_INITIAL_ARMOR + M_WIDOW2_ADDON_ARMOR * self->monsterinfo.level; + self->monsterinfo.max_armor = self->monsterinfo.power_armor_power; + self->monsterinfo.control_cost = M_JORG_CONTROL_COST; + self->monsterinfo.cost = M_COMMANDER_COST; + self->monsterinfo.jumpup = 64; + self->monsterinfo.jumpdn = 512; + self->monsterinfo.aiflags |= AI_NO_CIRCLE_STRAFE; + + self->pain = widow2_pain; + self->die = widow2_die; + self->monsterinfo.stand = widow2_stand; + self->monsterinfo.walk = widow2_walk; + self->monsterinfo.run = widow2_run; + self->monsterinfo.attack = widow2_attack; + self->monsterinfo.melee = widow2_melee; + self->monsterinfo.sight = widow2_sight; + + gi.linkentity(self); + + self->monsterinfo.currentmove = &widow2_move_stand; + self->monsterinfo.scale = 2.0f; + self->nextthink = level.time + FRAMETIME; + + G_PrintGreenText(va("A level %d widow2 has spawned!", self->monsterinfo.level)); +} diff --git a/src/g_local.h b/src/g_local.h index 14a5da47..0de7f56f 100644 --- a/src/g_local.h +++ b/src/g_local.h @@ -1530,6 +1530,8 @@ enum mtype_t { M_SOLDIER_RIPPER = 43, M_SOLDIER_BLUEBLASTER = 44, M_SOLDIER_LASER = 45, + M_WIDOW = 46, + M_WIDOW2 = 47, M_MINISENTRY = 100, M_SENTRY = 101, M_BFG_SENTRY = 102, @@ -1651,6 +1653,8 @@ enum dronespawn_t { DS_SOLDIER_RIPPER = 39, DS_SOLDIER_BLUEBLASTER = 40, DS_SOLDIER_LASER = 41, + DS_WIDOW = 42, + DS_WIDOW2 = 43, }; diff --git a/src/quake2/g_layout.c b/src/quake2/g_layout.c index f0bd0f83..61a41ae6 100644 --- a/src/quake2/g_layout.c +++ b/src/quake2/g_layout.c @@ -335,6 +335,8 @@ sidebar_entry_t layout_add_entity_info(sidebar_t* sidebar, edict_t* ent) case M_GEKK: case M_ARACHNID: case M_CARRIER: + case M_WIDOW: + case M_WIDOW2: case M_GUARDIAN: case M_JANITOR: case M_MINIGUARDIAN: diff --git a/src/quake2/g_svcmds.c b/src/quake2/g_svcmds.c index 2a487279..ecea8934 100644 --- a/src/quake2/g_svcmds.c +++ b/src/quake2/g_svcmds.c @@ -651,6 +651,20 @@ void SVCmd_SpawnBoss_f (void) else vrx_create_new_drone(m_worldspawn, DS_CARRIER, true, true, 0); } + else if (!strcmp(gi.argv(2), "widow") || !strcmp(gi.argv(2), "blackwidow")) + { + if (invasion->value) + vrx_inv_spawn_boss(m_worldspawn, DS_WIDOW); + else + vrx_create_new_drone(m_worldspawn, DS_WIDOW, true, true, 0); + } + else if (!strcmp(gi.argv(2), "widow2") || !strcmp(gi.argv(2), "blackwidow2")) + { + if (invasion->value) + vrx_inv_spawn_boss(m_worldspawn, DS_WIDOW2); + else + vrx_create_new_drone(m_worldspawn, DS_WIDOW2, true, true, 0); + } else if (!strcmp(gi.argv(2), "guardian") || !strcmp(gi.argv(2), "psxguardian")) { if (invasion->value) @@ -659,7 +673,7 @@ void SVCmd_SpawnBoss_f (void) vrx_create_new_drone(m_worldspawn, DS_GUARDIAN, true, true, 0); } else - safe_cprintf(NULL, PRINT_HIGH, "Invalid boss type. Usage: sv spawnboss \n"); + safe_cprintf(NULL, PRINT_HIGH, "Invalid boss type. Usage: sv spawnboss \n"); } void SVCmd_MakeBoss_f (void) diff --git a/src/server/v_luasettings.c b/src/server/v_luasettings.c index ccedbfac..29268309 100644 --- a/src/server/v_luasettings.c +++ b/src/server/v_luasettings.c @@ -1142,6 +1142,14 @@ void Lua_LoadVariables() M_CARRIER_ADDON_HEALTH = vrx_lua_get_variable("M_CARRIER_ADDON_HEALTH", 650); M_CARRIER_INITIAL_ARMOR = vrx_lua_get_variable("M_CARRIER_INITIAL_ARMOR", 500); M_CARRIER_ADDON_ARMOR = vrx_lua_get_variable("M_CARRIER_ADDON_ARMOR", 350); + M_WIDOW_INITIAL_HEALTH = vrx_lua_get_variable("M_WIDOW_INITIAL_HEALTH", 2400); + M_WIDOW_ADDON_HEALTH = vrx_lua_get_variable("M_WIDOW_ADDON_HEALTH", 700); + M_WIDOW_INITIAL_ARMOR = vrx_lua_get_variable("M_WIDOW_INITIAL_ARMOR", 600); + M_WIDOW_ADDON_ARMOR = vrx_lua_get_variable("M_WIDOW_ADDON_ARMOR", 350); + M_WIDOW2_INITIAL_HEALTH = vrx_lua_get_variable("M_WIDOW2_INITIAL_HEALTH", 3000); + M_WIDOW2_ADDON_HEALTH = vrx_lua_get_variable("M_WIDOW2_ADDON_HEALTH", 900); + M_WIDOW2_INITIAL_ARMOR = vrx_lua_get_variable("M_WIDOW2_INITIAL_ARMOR", 750); + M_WIDOW2_ADDON_ARMOR = vrx_lua_get_variable("M_WIDOW2_ADDON_ARMOR", 450); M_GUARDIAN_INITIAL_HEALTH = vrx_lua_get_variable("M_GUARDIAN_INITIAL_HEALTH", 6500); M_GUARDIAN_ADDON_HEALTH = vrx_lua_get_variable("M_GUARDIAN_ADDON_HEALTH", 1200); M_GUARDIAN_INITIAL_ARMOR = vrx_lua_get_variable("M_GUARDIAN_INITIAL_ARMOR", 550); From 165177ee0f6be9f1b042348636e69efeb78bc863 Mon Sep 17 00:00:00 2001 From: Enemy-123 Date: Sat, 25 Apr 2026 16:51:45 -0400 Subject: [PATCH 14/24] added fixbot + fixer (fixbot) boss + rogue_turrets monsters --- lua/variables.lua | 12 + src/characters/settings.h | 12 + src/characters/v_utils.c | 9 +- src/combat/common/damage.c | 1 + src/combat/common/v_misc.c | 15 + src/entities/drone/drone_fixbot.c | 1212 +++++++++++++++++++++++ src/entities/drone/drone_misc.c | 33 +- src/entities/drone/drone_rogue_turret.c | 342 +++++++ src/g_local.h | 6 + src/quake2/g_layout.c | 3 + src/quake2/g_svcmds.c | 9 +- src/server/v_luasettings.c | 12 + 12 files changed, 1662 insertions(+), 4 deletions(-) create mode 100644 src/entities/drone/drone_fixbot.c create mode 100644 src/entities/drone/drone_rogue_turret.c diff --git a/lua/variables.lua b/lua/variables.lua index 8fe36123..bf9dddb5 100644 --- a/lua/variables.lua +++ b/lua/variables.lua @@ -791,6 +791,18 @@ M_WIDOW2_INITIAL_HEALTH = 3000 M_WIDOW2_ADDON_HEALTH = 900 M_WIDOW2_INITIAL_ARMOR = 750 M_WIDOW2_ADDON_ARMOR = 450 +M_FIXBOT_INITIAL_HEALTH = 250 +M_FIXBOT_ADDON_HEALTH = 90 +M_FIXBOT_INITIAL_ARMOR = 120 +M_FIXBOT_ADDON_ARMOR = 45 +M_FIXBOT_BOSS_INITIAL_HEALTH = 3200 +M_FIXBOT_BOSS_ADDON_HEALTH = 850 +M_FIXBOT_BOSS_INITIAL_ARMOR = 700 +M_FIXBOT_BOSS_ADDON_ARMOR = 350 +M_ROGUE_TURRET_INITIAL_HEALTH = 50 +M_ROGUE_TURRET_ADDON_HEALTH = 25 +M_ROGUE_TURRET_INITIAL_ARMOR = 50 +M_ROGUE_TURRET_ADDON_ARMOR = 15 M_GUARDIAN_INITIAL_HEALTH = 6500 M_GUARDIAN_ADDON_HEALTH = 1200 M_GUARDIAN_INITIAL_ARMOR = 550 diff --git a/src/characters/settings.h b/src/characters/settings.h index f041a194..9dbc0c10 100644 --- a/src/characters/settings.h +++ b/src/characters/settings.h @@ -826,6 +826,18 @@ VRX_V_LUASETTINGS_IMPL double M_WIDOW2_INITIAL_HEALTH; VRX_V_LUASETTINGS_IMPL double M_WIDOW2_ADDON_HEALTH; VRX_V_LUASETTINGS_IMPL double M_WIDOW2_INITIAL_ARMOR; VRX_V_LUASETTINGS_IMPL double M_WIDOW2_ADDON_ARMOR; +VRX_V_LUASETTINGS_IMPL double M_FIXBOT_INITIAL_HEALTH; +VRX_V_LUASETTINGS_IMPL double M_FIXBOT_ADDON_HEALTH; +VRX_V_LUASETTINGS_IMPL double M_FIXBOT_INITIAL_ARMOR; +VRX_V_LUASETTINGS_IMPL double M_FIXBOT_ADDON_ARMOR; +VRX_V_LUASETTINGS_IMPL double M_FIXBOT_BOSS_INITIAL_HEALTH; +VRX_V_LUASETTINGS_IMPL double M_FIXBOT_BOSS_ADDON_HEALTH; +VRX_V_LUASETTINGS_IMPL double M_FIXBOT_BOSS_INITIAL_ARMOR; +VRX_V_LUASETTINGS_IMPL double M_FIXBOT_BOSS_ADDON_ARMOR; +VRX_V_LUASETTINGS_IMPL double M_ROGUE_TURRET_INITIAL_HEALTH; +VRX_V_LUASETTINGS_IMPL double M_ROGUE_TURRET_ADDON_HEALTH; +VRX_V_LUASETTINGS_IMPL double M_ROGUE_TURRET_INITIAL_ARMOR; +VRX_V_LUASETTINGS_IMPL double M_ROGUE_TURRET_ADDON_ARMOR; VRX_V_LUASETTINGS_IMPL double M_GUARDIAN_INITIAL_HEALTH; VRX_V_LUASETTINGS_IMPL double M_GUARDIAN_ADDON_HEALTH; VRX_V_LUASETTINGS_IMPL double M_GUARDIAN_INITIAL_ARMOR; diff --git a/src/characters/v_utils.c b/src/characters/v_utils.c index b815754c..a61d58c6 100644 --- a/src/characters/v_utils.c +++ b/src/characters/v_utils.c @@ -2113,6 +2113,12 @@ char *V_GetMonsterKind(int mtype) { return "widow"; case M_WIDOW2: return "black widow"; + case M_FIXBOT: + return "fixbot"; + case M_FIXBOT_BOSS: + return "fixer"; + case M_ROGUE_TURRET: + return "rocket turret"; case M_GUARDIAN: return "Guardian"; case M_JANITOR: @@ -2865,7 +2871,8 @@ qboolean vrx_is_morphing_polt(edict_t *ent) { qboolean vrx_has_pain_skin(edict_t* ent) { return (ent->mtype != M_DECOY && ent->mtype != M_SKELETON && ent->mtype != M_GOLEM - && ent->mtype != M_RUNNERTANK && ent->mtype != M_REDMUTANT); + && ent->mtype != M_RUNNERTANK && ent->mtype != M_REDMUTANT + && ent->mtype != M_ROGUE_TURRET); } // returns a value >= 1 based on any synergy bonuses that apply for ability_index diff --git a/src/combat/common/damage.c b/src/combat/common/damage.c index a79995b5..257267f8 100644 --- a/src/combat/common/damage.c +++ b/src/combat/common/damage.c @@ -156,6 +156,7 @@ qboolean IsMonster(const edict_t* ent) { || ent->mtype == M_STALKER || ent->mtype == M_GEKK || ent->mtype == M_ARACHNID || ent->mtype == M_MEDIC_COMMANDER || ent->mtype == M_CARRIER || ent->mtype == M_WIDOW || ent->mtype == M_WIDOW2 + || ent->mtype == M_FIXBOT || ent->mtype == M_FIXBOT_BOSS || ent->mtype == M_ROGUE_TURRET || ent->mtype == M_CHICK_HEAT)); } diff --git a/src/combat/common/v_misc.c b/src/combat/common/v_misc.c index 7cf3bf0a..13ea6749 100644 --- a/src/combat/common/v_misc.c +++ b/src/combat/common/v_misc.c @@ -271,6 +271,7 @@ static enum dronespawn_t vrx_pvm_random_drone_type(void) DS_BITCH_HEAT, DS_ARACHNID, DS_MEDIC_COMMANDER, + DS_FIXBOT, DS_JANITOR, DS_MINIGUARDIAN }; @@ -680,8 +681,15 @@ int vrx_GetMonsterCost(int mtype) { case M_CARRIER: case M_WIDOW: case M_WIDOW2: + case M_FIXBOT_BOSS: cost = M_COMMANDER_COST; break; + case M_FIXBOT: + cost = M_HOVER_COST; + break; + case M_ROGUE_TURRET: + cost = M_DEFAULT_COST; + break; case M_JANITOR: cost = M_TANK_COST; break; @@ -767,8 +775,15 @@ int vrx_GetMonsterControlCost(int mtype) { case M_CARRIER: case M_WIDOW: case M_WIDOW2: + case M_FIXBOT_BOSS: cost = M_JORG_CONTROL_COST; break; + case M_FIXBOT: + cost = M_HOVER_CONTROL_COST; + break; + case M_ROGUE_TURRET: + cost = M_DEFAULT_CONTROL_COST; + break; case M_HOVER: case M_DAEDALUS: cost = M_HOVER_CONTROL_COST; diff --git a/src/entities/drone/drone_fixbot.c b/src/entities/drone/drone_fixbot.c new file mode 100644 index 00000000..748af076 --- /dev/null +++ b/src/entities/drone/drone_fixbot.c @@ -0,0 +1,1212 @@ +/* +============================================================================== + +fixbot + +============================================================================== +*/ + +#include "g_local.h" + +#define FIXBOT_FRAME_charging_01 0 +#define FIXBOT_FRAME_charging_27 26 +#define FIXBOT_FRAME_charging_31 30 +#define FIXBOT_FRAME_ambient_01 121 +#define FIXBOT_FRAME_ambient_19 139 +#define FIXBOT_FRAME_paina_01 140 +#define FIXBOT_FRAME_paina_06 145 +#define FIXBOT_FRAME_painb_01 146 +#define FIXBOT_FRAME_painb_08 153 +#define FIXBOT_FRAME_freeze_01 181 +#define FIXBOT_FRAME_weldstart_01 188 +#define FIXBOT_FRAME_weldstart_07 194 + +#define FIXBOT_BOSS_TURRET_MAX 6 +#define FIXBOT_BOSS_SPAWN_COOLDOWN 8.0f +#define FIXBOT_BOSS_FAIL_COOLDOWN 2.0f +#define FIXBOT_BLASTER_FLASH MZ2_HOVER_BLASTER_1 + +static int sound_pain; +static int sound_die; +static int sound_pew; +static int sound_weld; +static int sound_spawn; + +void drone_ai_stand(edict_t *self, float dist); +void drone_ai_walk(edict_t *self, float dist); +void drone_ai_run(edict_t *self, float dist); +void rogue_turret_force_ready(edict_t *self); + +static void fixbot_stand(edict_t *self); +static void fixbot_walk(edict_t *self); +static void fixbot_run(edict_t *self); +static void fixbot_attack(edict_t *self); +static void fixbot_try_start_spawn(edict_t *self); + +static qboolean fixbot_is_boss(edict_t *self) +{ + return self->mtype == M_FIXBOT_BOSS; +} + +static int fixbot_count_live_turrets(edict_t *self) +{ + int count = 0; + edict_t *ent = DroneList_Iterate(); + + while (ent) + { + if (G_EntExists(ent) && ent->owner == self && ent->mtype == M_ROGUE_TURRET) + count++; + ent = DroneList_Next(ent); + } + + return count; +} + +static void fixbot_remove_turrets(edict_t *self) +{ + edict_t *ent = DroneList_Iterate(); + + while (ent) + { + edict_t *next = DroneList_Next(ent); + + if (G_EntExists(ent) && ent->owner == self && ent->mtype == M_ROGUE_TURRET) + M_Remove(ent, false, true); + + ent = next; + } +} + +static qboolean fixbot_turret_position_clear(edict_t *self, vec3_t position) +{ + edict_t *ent = NULL; + + if (gi.pointcontents(position) & MASK_WATER) + return false; + + while ((ent = findradius(ent, position, 144)) != NULL) + { + if (!ent->inuse || ent == self) + continue; + if (ent->mtype == M_ROGUE_TURRET) + return false; + if ((ent->client || (ent->svflags & SVF_MONSTER)) && ent->health > 0 && ent->solid != SOLID_NOT) + return false; + if (ent->solid == SOLID_BSP || (ent->solid == SOLID_BBOX && ent->takedamage)) + return false; + } + + return true; +} + +static qboolean fixbot_find_turret_spawn_position(edict_t *self, vec3_t position, vec3_t direction) +{ + vec3_t start; + vec3_t initial_forward; + vec3_t best_pos; + vec3_t best_dir; + float best_dist = 0; + qboolean found = false; + qboolean best_front = false; + const float trace_distance = 1000.0f; + vec3_t turret_mins = { -12, -12, -12 }; + vec3_t turret_maxs = { 12, 12, 12 }; + + VectorCopy(self->s.origin, start); + start[2] += 16; + AngleVectors(self->s.angles, initial_forward, NULL, NULL); + VectorClear(best_pos); + VectorClear(best_dir); + + for (int attempt = 0; attempt < 12; attempt++) + { + vec3_t angles; + vec3_t forward; + vec3_t end; + vec3_t candidate; + vec3_t normal; + vec3_t to_pos; + trace_t tr; + float dist; + qboolean in_front; + qboolean better = false; + + VectorCopy(self->s.angles, angles); + if (attempt == 0) + { + VectorCopy(initial_forward, forward); + } + else + { + if (attempt <= 6) + { + angles[YAW] += (float)(attempt - 1) * 30.0f - 75.0f; + angles[PITCH] = -15.0f; + } + else + { + angles[YAW] += (float)(attempt - 7) * 60.0f - 150.0f; + angles[PITCH] += crandom() * 15.0f - 15.0f; + } + + while (angles[YAW] < 0) + angles[YAW] += 360; + while (angles[YAW] >= 360) + angles[YAW] -= 360; + AngleVectors(angles, forward, NULL, NULL); + } + + VectorMA(start, trace_distance, forward, end); + tr = gi.trace(start, NULL, NULL, end, self, MASK_SOLID); + if (tr.fraction >= 1.0) + continue; + if (tr.ent && tr.ent != world && tr.ent->solid == SOLID_BBOX) + continue; + + VectorCopy(tr.plane.normal, normal); + if (fabs(normal[2]) > 0.9f) + { + normal[2] = (normal[2] > 0) ? 0.7f : -0.7f; + VectorNormalize(normal); + } + + VectorMA(tr.endpos, 16.0f, normal, candidate); + if (!G_IsValidLocation(self, candidate, turret_mins, turret_maxs)) + continue; + if (!fixbot_turret_position_clear(self, candidate)) + continue; + if (!G_IsClearPath(self, MASK_SOLID, start, candidate)) + continue; + + VectorSubtract(candidate, self->s.origin, to_pos); + dist = VectorLength(to_pos); + if (dist <= 56.0f) + continue; + VectorNormalize(to_pos); + in_front = DotProduct(to_pos, initial_forward) > 0.1f; + + if (!found) + better = true; + else if (in_front && !best_front) + better = true; + else if (in_front == best_front && dist < best_dist) + better = true; + + if (better) + { + VectorCopy(candidate, best_pos); + VectorCopy(normal, best_dir); + best_dist = dist; + best_front = in_front; + found = true; + } + } + + if (!found) + { + const float distances[] = { 120.0f, 180.0f, 240.0f, 320.0f }; + const float yaws[] = { 0.0f, -45.0f, 45.0f, -90.0f, 90.0f, -135.0f, 135.0f, 180.0f }; + + for (int d = 0; d < (int)(sizeof(distances) / sizeof(distances[0])); d++) + { + for (int y = 0; y < (int)(sizeof(yaws) / sizeof(yaws[0])); y++) + { + vec3_t angles; + vec3_t forward; + vec3_t probe; + vec3_t down; + vec3_t candidate; + vec3_t to_pos; + trace_t tr; + + VectorCopy(self->s.angles, angles); + angles[YAW] += yaws[y]; + while (angles[YAW] < 0) + angles[YAW] += 360; + while (angles[YAW] >= 360) + angles[YAW] -= 360; + angles[PITCH] = 0; + AngleVectors(angles, forward, NULL, NULL); + + VectorMA(self->s.origin, distances[d], forward, probe); + probe[2] += 96.0f; + VectorCopy(probe, down); + down[2] -= 384.0f; + tr = gi.trace(probe, NULL, NULL, down, self, MASK_SOLID); + if (tr.startsolid || tr.allsolid || tr.fraction >= 1.0f) + continue; + if (tr.surface && (tr.surface->flags & SURF_SKY)) + continue; + if (tr.plane.normal[2] < 0.7f) + continue; + + VectorCopy(tr.endpos, candidate); + candidate[2] -= turret_mins[2]; + if (!G_IsValidLocation(self, candidate, turret_mins, turret_maxs)) + continue; + if (!fixbot_turret_position_clear(self, candidate)) + continue; + if (!G_IsClearPath(self, MASK_SOLID, start, candidate)) + continue; + + VectorSubtract(candidate, self->s.origin, to_pos); + if (VectorLength(to_pos) <= 56.0f) + continue; + + VectorCopy(candidate, position); + VectorSet(direction, 0, 0, 1); + return true; + } + } + + VectorClear(position); + VectorClear(direction); + return false; + } + + VectorCopy(best_pos, position); + VectorCopy(best_dir, direction); + return true; +} + +static qboolean fixbot_plasma_valid_target(edict_t *self, edict_t *target) +{ + if (!G_EntExists(self->owner)) + return false; + if (target == self->owner) + return false; + if (!G_ValidTargetEnt(self->owner, target, true)) + return false; + if (OnSameTeam(self->owner, target)) + return false; + if (!visible(self, target)) + return false; + return true; +} + +static void fixbot_plasma_turn_toward(edict_t *self, edict_t *target) +{ + vec3_t dir; + float turn_fraction; + + if (!fixbot_plasma_valid_target(self, target)) + return; + + VectorSubtract(target->s.origin, self->s.origin, dir); + if (!VectorNormalize(dir)) + return; + + turn_fraction = self->accel; + if (turn_fraction < 0) + turn_fraction = 0; + else if (turn_fraction > 1) + turn_fraction = 1; + + VectorScale(self->movedir, 1.0f - turn_fraction, self->movedir); + VectorMA(self->movedir, turn_fraction, dir, self->movedir); + VectorNormalize(self->movedir); + vectoangles(self->movedir, self->s.angles); + self->enemy = target; +} + +static edict_t *fixbot_plasma_acquire_target(edict_t *self) +{ + edict_t *target = NULL; + edict_t *acquire = NULL; + vec3_t forward; + vec3_t dir; + float best_dot = -1.0f; + float best_dist = 0.0f; + + if (fixbot_plasma_valid_target(self, self->enemy)) + return self->enemy; + self->enemy = NULL; + + AngleVectors(self->s.angles, forward, NULL, NULL); + + while ((target = findradius(target, self->s.origin, 1024)) != NULL) + { + float dot; + float dist; + + if (!fixbot_plasma_valid_target(self, target)) + continue; + + VectorSubtract(target->s.origin, self->s.origin, dir); + dist = VectorNormalize(dir); + dot = DotProduct(dir, forward); + + if (!acquire || dot > best_dot || (dot == best_dot && dist < best_dist)) + { + acquire = target; + best_dot = dot; + best_dist = dist; + } + } + + return acquire; +} + +static void fixbot_plasma_think(edict_t *self) +{ + edict_t *target; + vec3_t upward; + + if (!G_EntExists(self->owner) || level.time >= self->delay) + { + BecomeExplosion1(self); + return; + } + + if (self->timestamp > level.time) + { + VectorSet(upward, 0, 0, self->movedir[2] > 0.3f ? 0.10f : 0.02f); + VectorAdd(self->movedir, upward, self->movedir); + VectorNormalize(self->movedir); + vectoangles(self->movedir, self->s.angles); + VectorScale(self->movedir, self->speed, self->velocity); + self->nextthink = level.time + FRAMETIME; + return; + } + + target = fixbot_plasma_acquire_target(self); + if (target) + fixbot_plasma_turn_toward(self, target); + + VectorScale(self->movedir, self->speed, self->velocity); + self->nextthink = level.time + FRAMETIME; +} + +static void fixbot_plasma_touch(edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + vec3_t origin; + vec3_t normal; + + if (other == ent->owner) + return; + + if (!G_EntExists(ent->owner)) + { + G_FreeEdict(ent); + return; + } + + if (surf && (surf->flags & SURF_SKY)) + { + ent->timestamp = 0; + if (plane) + VectorMA(ent->s.origin, 16.0f, plane->normal, ent->s.origin); + if (ent->movedir[2] > 0) + { + ent->movedir[2] *= -0.35f; + VectorNormalize(ent->movedir); + } + if (fixbot_plasma_valid_target(ent, ent->enemy)) + fixbot_plasma_turn_toward(ent, ent->enemy); + ent->nextthink = level.time + FRAMETIME; + return; + } + + VectorMA(ent->s.origin, -0.02f, ent->velocity, origin); + if (plane) + VectorCopy(plane->normal, normal); + else + VectorClear(normal); + + if (other->takedamage) + T_Damage(other, ent, ent->owner, ent->velocity, ent->s.origin, normal, ent->dmg, 0, DAMAGE_ENERGY, MOD_PHALANX); + + T_RadiusDamage(ent, ent->owner, ent->radius_dmg, other, ent->dmg_radius, MOD_PHALANX); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_PLASMA_EXPLOSION); + gi.WritePosition(origin); + gi.multicast(ent->s.origin, MULTICAST_PVS); + + G_FreeEdict(ent); +} + +static void fixbot_fire_plasma_shot(edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float radius, int radius_damage, float turn_fraction) +{ + edict_t *plasma; + + damage = vrx_increase_monster_damage_by_talent(self->activator, damage); + if (speed < 1) + speed = 1; + + VectorNormalize(dir); + self->lastsound = level.framenum; + + plasma = G_Spawn(); + VectorCopy(start, plasma->s.origin); + VectorCopy(start, plasma->s.old_origin); + VectorCopy(dir, plasma->movedir); + vectoangles(dir, plasma->s.angles); + VectorScale(dir, speed, plasma->velocity); + plasma->movetype = MOVETYPE_FLYMISSILE; + plasma->clipmask = MASK_SHOT; + plasma->solid = SOLID_BBOX; + VectorSet(plasma->mins, -5, -5, -5); + VectorSet(plasma->maxs, 5, 5, 5); + plasma->owner = self; + plasma->touch = fixbot_plasma_touch; + plasma->think = fixbot_plasma_think; + plasma->nextthink = level.time + FRAMETIME; + plasma->speed = speed; + plasma->accel = turn_fraction; + plasma->dmg = damage; + plasma->radius_dmg = radius_damage; + plasma->dmg_radius = radius; + plasma->s.sound = gi.soundindex("weapons/rockfly.wav"); + plasma->s.modelindex = gi.modelindex("sprites/s_photon.sp2"); + plasma->s.effects |= EF_PLASMA | EF_ANIM_ALLFAST; + plasma->s.scale = 0.75f; + plasma->svflags |= SVF_PROJECTILE; + plasma->timestamp = level.time + (fixbot_is_boss(self) ? 0.7f : 1.0f); + plasma->delay = level.time + 8.0f; + + if (G_ValidTarget(self, self->enemy, true, true)) + plasma->enemy = self->enemy; + + gi.linkentity(plasma); +} + +static qboolean fixbot_has_open_sky(edict_t *self) +{ + vec3_t start; + vec3_t end; + trace_t tr; + + VectorCopy(self->s.origin, start); + start[2] += 20.0f; + VectorCopy(start, end); + end[2] += 2000.0f; + + tr = gi.trace(start, NULL, NULL, end, self, MASK_SOLID); + return tr.surface && (tr.surface->flags & SURF_SKY); +} + +static void fixbot_fire_ionripper_spread(edict_t *self, vec3_t start, vec3_t forward, vec3_t right) +{ + int damage; + int speed; + int shots = fixbot_is_boss(self) ? 5 : 3; + vec3_t target; + vec3_t dir; + + if (!G_ValidTarget(self, self->enemy, true, true)) + return; + + damage = M_IONRIPPER_DMG_BASE + M_IONRIPPER_DMG_ADDON * drone_damagelevel(self); + if (M_IONRIPPER_DMG_MAX && damage > M_IONRIPPER_DMG_MAX) + damage = M_IONRIPPER_DMG_MAX; + if (damage <= 0) + damage = fixbot_is_boss(self) ? 20 : 12; + + speed = M_IONRIPPER_SPEED_BASE + M_IONRIPPER_SPEED_ADDON * drone_damagelevel(self); + if (M_IONRIPPER_SPEED_MAX && speed > M_IONRIPPER_SPEED_MAX) + speed = M_IONRIPPER_SPEED_MAX; + if (speed <= 0) + speed = fixbot_is_boss(self) ? 750 : 650; + + G_EntMidPoint(self->enemy, target); + VectorSubtract(target, start, dir); + if (!VectorNormalize(dir)) + VectorCopy(forward, dir); + + if (shots == 5) + { + vec3_t dir1, dir2, dir3, dir4, dir5; + + VectorCopy(dir, dir1); + VectorMA(dir1, 0.16f, right, dir1); + VectorNormalize(dir1); + VectorCopy(dir, dir2); + VectorMA(dir2, 0.12f, right, dir2); + VectorNormalize(dir2); + VectorCopy(dir, dir3); + VectorCopy(dir, dir4); + VectorMA(dir4, -0.12f, right, dir4); + VectorNormalize(dir4); + VectorCopy(dir, dir5); + VectorMA(dir5, -0.16f, right, dir5); + VectorNormalize(dir5); + + monster_fire_ionripper(self, start, dir1, damage, speed, EF_IONRIPPER, MZ2_HOVER_BLASTER_1); + monster_fire_ionripper(self, start, dir2, damage, speed, EF_IONRIPPER, MZ2_HOVER_BLASTER_1); + monster_fire_ionripper(self, start, dir3, damage, speed, EF_IONRIPPER, MZ2_HOVER_BLASTER_1); + monster_fire_ionripper(self, start, dir4, damage, speed, EF_IONRIPPER, MZ2_HOVER_BLASTER_1); + monster_fire_ionripper(self, start, dir5, damage, speed, EF_IONRIPPER, MZ2_HOVER_BLASTER_1); + } + else + { + vec3_t dir1, dir2, dir3; + + VectorCopy(dir, dir1); + VectorMA(dir1, 0.12f, right, dir1); + VectorNormalize(dir1); + VectorCopy(dir, dir2); + VectorCopy(dir, dir3); + VectorMA(dir3, -0.12f, right, dir3); + VectorNormalize(dir3); + + monster_fire_ionripper(self, start, dir1, damage, speed, EF_IONRIPPER, MZ2_HOVER_BLASTER_1); + monster_fire_ionripper(self, start, dir2, damage, speed, EF_IONRIPPER, MZ2_HOVER_BLASTER_1); + monster_fire_ionripper(self, start, dir3, damage, speed, EF_IONRIPPER, MZ2_HOVER_BLASTER_1); + } + + gi.sound(self, CHAN_WEAPON, sound_pew, 1, ATTN_NORM, 0); +} + +static void fixbot_fire_plasma(edict_t *self, float offset) +{ + int damage; + int speed; + int radius_damage; + float turn_fraction; + vec3_t forward; + vec3_t right; + vec3_t up; + vec3_t start; + vec3_t dir; + + if (!G_ValidTarget(self, self->enemy, true, true)) + return; + + AngleVectors(self->s.angles, forward, right, up); + VectorCopy(self->s.origin, start); + VectorMA(start, 25.0f, forward, start); + VectorMA(start, offset, right, start); + VectorMA(start, 50.0f, up, start); + + if (!fixbot_has_open_sky(self)) + { + fixbot_fire_ionripper_spread(self, start, forward, right); + return; + } + + VectorCopy(forward, dir); + VectorMA(dir, 0.55f, up, dir); + VectorNormalize(dir); + + damage = (fixbot_is_boss(self) ? 24 : 20) + (fixbot_is_boss(self) ? 4 : 3) * drone_damagelevel(self); + if (damage > (fixbot_is_boss(self) ? 120 : 90)) + damage = fixbot_is_boss(self) ? 120 : 90; + + speed = fixbot_is_boss(self) ? (520 + (int)(random() * 120.0f)) : (380 + (int)(random() * 100.0f)); + radius_damage = (int)(damage * 1.75f); + turn_fraction = fixbot_is_boss(self) ? 0.085f : 0.065f; + + if (fixbot_is_boss(self)) + { + vec3_t start1, start2, start3; + vec3_t dir1, dir2, dir3; + + VectorCopy(start, start1); + VectorMA(start1, 25.0f, right, start1); + VectorMA(start1, 15.0f, up, start1); + VectorCopy(start, start2); + VectorCopy(start, start3); + VectorMA(start3, -25.0f, right, start3); + VectorMA(start3, 15.0f, up, start3); + + VectorCopy(dir, dir1); + VectorMA(dir1, 0.10f, right, dir1); + VectorMA(dir1, 0.05f, up, dir1); + VectorNormalize(dir1); + VectorCopy(dir, dir2); + VectorCopy(dir, dir3); + VectorMA(dir3, -0.10f, right, dir3); + VectorMA(dir3, 0.05f, up, dir3); + VectorNormalize(dir3); + + fixbot_fire_plasma_shot(self, start1, dir1, damage, speed, 150.0f, radius_damage, turn_fraction); + fixbot_fire_plasma_shot(self, start2, dir2, damage, speed, 150.0f, radius_damage, turn_fraction); + fixbot_fire_plasma_shot(self, start3, dir3, damage, speed, 150.0f, radius_damage, turn_fraction); + } + else + { + fixbot_fire_plasma_shot(self, start, dir, damage, speed, 150.0f, radius_damage, turn_fraction); + } + + gi.sound(self, CHAN_WEAPON, sound_pew, 1, ATTN_NORM, 0); +} + +static void fixbot_fire_blaster(edict_t *self) +{ + int damage; + int speed = 1000; + int effect; + vec3_t forward; + vec3_t base_forward; + vec3_t right; + vec3_t start; + + if (!G_ValidTarget(self, self->enemy, true, true)) + return; + + damage = (fixbot_is_boss(self) ? 10 : 7) + (fixbot_is_boss(self) ? 3 : 2) * self->monsterinfo.level; + if (damage > (fixbot_is_boss(self) ? 80 : 50)) + damage = fixbot_is_boss(self) ? 80 : 50; + + effect = (self->s.frame & 3) ? 0 : EF_BLASTER; + AngleVectors(self->s.angles, forward, right, NULL); + G_ProjectSource(self->s.origin, monster_flash_offset[FIXBOT_BLASTER_FLASH], forward, right, start); + MonsterAim(self, M_PROJECTILE_ACC, speed, false, -1, forward, start); + VectorCopy(forward, base_forward); + monster_fire_blaster(self, start, forward, damage, speed, effect, BLASTER_PROJ_BOLT, 2.0f, false, FIXBOT_BLASTER_FLASH); + + if (fixbot_is_boss(self) && (self->s.frame & 1)) + { + VectorCopy(base_forward, forward); + VectorMA(forward, 0.10f, right, forward); + VectorNormalize(forward); + monster_fire_blaster(self, start, forward, damage, speed, 0, BLASTER_PROJ_BOLT, 2.0f, false, FIXBOT_BLASTER_FLASH); + + VectorCopy(base_forward, forward); + VectorMA(forward, -0.10f, right, forward); + VectorNormalize(forward); + monster_fire_blaster(self, start, forward, damage, speed, 0, BLASTER_PROJ_BOLT, 2.0f, false, FIXBOT_BLASTER_FLASH); + } + + gi.sound(self, CHAN_WEAPON, sound_pew, 1, ATTN_NORM, 0); +} + +static void fixbot_reattack(edict_t *self) +{ + if (G_ValidTarget(self, self->enemy, true, true)) + { + if (random() < (fixbot_is_boss(self) ? 0.16f : 0.12f)) + { + fixbot_fire_plasma(self, 0.0f); + self->monsterinfo.nextframe = FIXBOT_FRAME_charging_27; + return; + } + + if (random() < (fixbot_is_boss(self) ? 0.80f : 0.55f)) + { + self->monsterinfo.nextframe = FIXBOT_FRAME_charging_27; + return; + } + } + + M_DelayNextAttack(self, fixbot_is_boss(self) ? 0.4f : 0.8f, true); +} + +static void fixbot_update_spawn_probe(edict_t *self) +{ + static const float scan_yaws[] = { 0.0f, -35.0f, 35.0f, -70.0f, 70.0f, -110.0f, 110.0f, -150.0f, 150.0f, 180.0f }; + vec3_t start; + vec3_t angles; + vec3_t forward; + vec3_t end; + trace_t tr; + int index; + + VectorCopy(self->s.origin, start); + start[2] += 16.0f; + + index = (self->s.frame - FIXBOT_FRAME_weldstart_01) % (int)(sizeof(scan_yaws) / sizeof(scan_yaws[0])); + if (index < 0) + index = 0; + + VectorCopy(self->s.angles, angles); + angles[YAW] += scan_yaws[index]; + angles[PITCH] = -10.0f; + while (angles[YAW] < 0) + angles[YAW] += 360; + while (angles[YAW] >= 360) + angles[YAW] -= 360; + + AngleVectors(angles, forward, NULL, NULL); + VectorMA(start, 1000.0f, forward, end); + tr = gi.trace(start, NULL, NULL, end, self, MASK_SOLID); + + if (tr.fraction < 1.0f) + VectorCopy(tr.endpos, self->pos2); + else + VectorCopy(end, self->pos2); +} + +static qboolean fixbot_try_select_turret_position(edict_t *self) +{ + if (fixbot_find_turret_spawn_position(self, self->pos1, self->pos2)) + return true; + + VectorClear(self->pos1); + fixbot_update_spawn_probe(self); + return false; +} + +static void fixbot_prep_spawn(edict_t *self) +{ + VectorClear(self->pos1); + VectorClear(self->pos2); + + if (!fixbot_is_boss(self) || level.time < self->monsterinfo.melee_finished) + { + self->monsterinfo.currentmove = NULL; + fixbot_run(self); + return; + } + + if (fixbot_count_live_turrets(self) >= FIXBOT_BOSS_TURRET_MAX) + { + self->monsterinfo.melee_finished = level.time + FIXBOT_BOSS_FAIL_COOLDOWN; + self->monsterinfo.currentmove = NULL; + fixbot_run(self); + return; + } + + fixbot_try_select_turret_position(self); + self->s.effects |= EF_HYPERBLASTER | EF_PLASMA; + gi.sound(self, CHAN_WEAPON, sound_weld, 1, ATTN_NORM, 0); +} + +static void fixbot_spawn_laser_off(edict_t *self) +{ + if (self->beam && self->beam->inuse) + G_FreeEdict(self->beam); + self->beam = NULL; +} + +static void fixbot_fire_spawn_laser(edict_t *self) +{ + edict_t *laser; + vec3_t forward; + vec3_t start; + vec3_t target; + + if (VectorLength(self->pos1) >= 1) + VectorCopy(self->pos1, target); + else + VectorCopy(self->pos2, target); + + if (VectorLength(target) < 1) + return; + + laser = self->beam; + if (!laser || !laser->inuse) + { + laser = G_Spawn(); + if (!laser) + return; + self->beam = laser; + laser->movetype = MOVETYPE_NONE; + laser->solid = SOLID_NOT; + laser->s.renderfx = RF_BEAM | RF_TRANSLUCENT; + laser->s.modelindex = 1; + laser->s.frame = 2; + laser->owner = self; + laser->classname = "fixbot_spawn_laser"; + laser->s.sound = gi.soundindex("misc/lasfly.wav"); + } + + AngleVectors(self->s.angles, forward, NULL, NULL); + VectorMA(self->s.origin, 16.0f, forward, start); + VectorCopy(start, laser->s.origin); + VectorCopy(target, laser->s.old_origin); + VectorCopy(start, laser->pos1); + VectorCopy(target, laser->pos2); + laser->s.skinnum = fixbot_is_boss(self) ? 0xf0f0f0f0 : 0xf2f2f0f0; + laser->think = G_FreeEdict; + laser->nextthink = level.time + 0.2f; + gi.linkentity(laser); +} + +static void fixbot_spawn_effect(edict_t *self) +{ + vec3_t dir; + vec3_t target; + qboolean has_position; + + has_position = VectorLength(self->pos1) >= 1; + if (!has_position) + has_position = fixbot_try_select_turret_position(self); + + if (has_position) + VectorCopy(self->pos1, target); + else + VectorCopy(self->pos2, target); + + if (VectorLength(target) < 1) + return; + + VectorSubtract(target, self->s.origin, dir); + if (VectorLength(dir) > 1) + { + self->ideal_yaw = vectoyaw(dir); + M_ChangeYaw(self); + } + + fixbot_fire_spawn_laser(self); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_WELDING_SPARKS); + gi.WriteByte(has_position ? (fixbot_is_boss(self) ? 18 : 10) : 5); + gi.WritePosition(target); + gi.WriteDir(vec3_origin); + gi.WriteByte(has_position ? (fixbot_is_boss(self) ? 0xf0 : 0xe0) : 0xd0); + gi.multicast(target, MULTICAST_PVS); +} + +static qboolean fixbot_spawn_turret(edict_t *self) +{ + edict_t *spawned; + edict_t *summoner; + vec3_t dir; + vec3_t angles; + + if (VectorLength(self->pos1) < 1) + return false; + if (fixbot_count_live_turrets(self) >= FIXBOT_BOSS_TURRET_MAX) + return false; + + spawned = G_Spawn(); + spawned->mtype = M_ROGUE_TURRET; + spawned->activator = self; + spawned->owner = self; + spawned->monsterinfo.level = self->monsterinfo.level; + + summoner = G_GetSummoner(self); + if (summoner) + spawned->creator = summoner; + + if (!M_Initialize(self, spawned, 0.0f)) + { + G_FreeEdict(spawned); + return false; + } + + spawned->activator = self; + spawned->owner = self; + if (summoner) + spawned->creator = summoner; + spawned->monsterinfo.control_cost = 0; + spawned->monsterinfo.cost = 0; + spawned->movetype = MOVETYPE_NONE; + spawned->gravity = 0; + spawned->health = spawned->max_health; + spawned->monsterinfo.power_armor_power = spawned->monsterinfo.max_armor; + spawned->monsterinfo.pausetime = 0; + spawned->monsterinfo.attack_finished = level.time; + spawned->monsterinfo.melee_finished = level.time; + VectorCopy(self->pos1, spawned->s.origin); + VectorCopy(self->pos1, spawned->s.old_origin); + + if (G_ValidTarget(self, self->enemy, false, true)) + VectorSubtract(self->enemy->s.origin, self->pos1, dir); + else + VectorSubtract(self->s.origin, self->pos1, dir); + if (VectorLength(dir) < 1) + VectorSet(dir, 1, 0, 0); + vectoangles(dir, angles); + VectorCopy(angles, spawned->s.angles); + + if (G_ValidTarget(spawned, self->enemy, false, true)) + spawned->enemy = self->enemy; + + gi.linkentity(spawned); + rogue_turret_force_ready(spawned); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_TELEPORT_EFFECT); + gi.WritePosition(self->pos1); + gi.multicast(self->pos1, MULTICAST_PVS); + if (sound_spawn) + gi.sound(self, CHAN_AUTO, sound_spawn, 1, ATTN_NORM, 0); + + return true; +} + +static void fixbot_finish_spawn(edict_t *self) +{ + if (VectorLength(self->pos1) < 1) + fixbot_try_select_turret_position(self); + + if (fixbot_spawn_turret(self)) + self->monsterinfo.melee_finished = level.time + FIXBOT_BOSS_SPAWN_COOLDOWN; + else + self->monsterinfo.melee_finished = level.time + FIXBOT_BOSS_FAIL_COOLDOWN; + + VectorClear(self->pos1); + VectorClear(self->pos2); + fixbot_spawn_laser_off(self); + self->s.effects &= ~(EF_HYPERBLASTER | EF_PLASMA); +} + +static mframe_t fixbot_frames_stand[] = +{ + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL +}; +static mmove_t fixbot_move_stand = { FIXBOT_FRAME_ambient_01, FIXBOT_FRAME_ambient_19, fixbot_frames_stand, fixbot_run }; + +static void fixbot_stand(edict_t *self) +{ + self->monsterinfo.currentmove = &fixbot_move_stand; +} + +static mframe_t fixbot_frames_run[] = +{ + drone_ai_run, 10, fixbot_try_start_spawn +}; +static mmove_t fixbot_move_run = { FIXBOT_FRAME_freeze_01, FIXBOT_FRAME_freeze_01, fixbot_frames_run, NULL }; + +static void fixbot_run(edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &fixbot_move_stand; + else + self->monsterinfo.currentmove = &fixbot_move_run; +} + +static mframe_t fixbot_frames_walk[] = +{ + drone_ai_walk, 5, NULL +}; +static mmove_t fixbot_move_walk = { FIXBOT_FRAME_freeze_01, FIXBOT_FRAME_freeze_01, fixbot_frames_walk, NULL }; + +static void fixbot_walk(edict_t *self) +{ + self->monsterinfo.currentmove = &fixbot_move_walk; +} + +static mframe_t fixbot_frames_attack[] = +{ + ai_charge, 0, fixbot_fire_blaster, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, fixbot_fire_blaster, + ai_charge, 0, NULL, + ai_charge, 0, fixbot_fire_blaster, + ai_charge, 0, NULL, + ai_charge, 0, fixbot_fire_blaster, + ai_charge, 0, fixbot_fire_blaster, + ai_charge, -10, NULL, + ai_charge, 0, NULL, + ai_charge, 0, fixbot_fire_blaster, + ai_charge, -10, NULL, + ai_charge, 0, fixbot_fire_blaster, + ai_charge, 0, NULL, + ai_charge, -10, NULL, + ai_charge, 0, fixbot_fire_blaster, + ai_charge, 0, NULL, + ai_charge, 0, fixbot_fire_blaster, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, fixbot_fire_blaster, + ai_charge, 0, NULL, + ai_charge, 0, fixbot_fire_blaster, + ai_charge, 0, fixbot_fire_blaster, + ai_charge, 0, fixbot_fire_blaster, + ai_charge, 0, fixbot_fire_blaster, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, fixbot_reattack +}; +static mmove_t fixbot_move_attack = { FIXBOT_FRAME_charging_01, FIXBOT_FRAME_charging_31, fixbot_frames_attack, fixbot_run }; + +static mframe_t fixbot_frames_spawn[] = +{ + ai_charge, 0, fixbot_prep_spawn, + ai_charge, 0, fixbot_spawn_effect, + ai_charge, 0, fixbot_spawn_effect, + ai_charge, 0, fixbot_spawn_effect, + ai_charge, 0, fixbot_spawn_effect, + ai_charge, 0, fixbot_finish_spawn, + ai_charge, 0, NULL +}; +static mmove_t fixbot_move_spawn = { FIXBOT_FRAME_weldstart_01, FIXBOT_FRAME_weldstart_07, fixbot_frames_spawn, fixbot_run }; + +static void fixbot_try_start_spawn(edict_t *self) +{ + if (!fixbot_is_boss(self)) + return; + if (level.time < self->monsterinfo.melee_finished) + return; + if (fixbot_count_live_turrets(self) >= FIXBOT_BOSS_TURRET_MAX) + return; + if (!G_ValidTarget(self, self->enemy, false, true)) + return; + + self->monsterinfo.currentmove = &fixbot_move_spawn; +} + +static void fixbot_attack(edict_t *self) +{ + if (!G_ValidTarget(self, self->enemy, false, true)) + return; + + if (fixbot_is_boss(self) && level.time >= self->monsterinfo.melee_finished + && fixbot_count_live_turrets(self) < FIXBOT_BOSS_TURRET_MAX) + { + self->monsterinfo.currentmove = &fixbot_move_spawn; + return; + } + + self->monsterinfo.lefty = random() <= 0.5f; + self->monsterinfo.currentmove = &fixbot_move_attack; +} + +static mframe_t fixbot_frames_paina[] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +static mmove_t fixbot_move_paina = { FIXBOT_FRAME_paina_01, FIXBOT_FRAME_paina_06, fixbot_frames_paina, fixbot_run }; + +static mframe_t fixbot_frames_painb[] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +static mmove_t fixbot_move_painb = { FIXBOT_FRAME_painb_01, FIXBOT_FRAME_painb_08, fixbot_frames_painb, fixbot_run }; + +static mframe_t fixbot_frames_pain3[] = +{ + ai_move, -1, NULL +}; +static mmove_t fixbot_move_pain3 = { FIXBOT_FRAME_freeze_01, FIXBOT_FRAME_freeze_01, fixbot_frames_pain3, fixbot_run }; + +static void fixbot_pain(edict_t *self, edict_t *other, float kick, int damage) +{ + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3.0f; + gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); + + if (fixbot_is_boss(self) && self->monsterinfo.currentmove == &fixbot_move_spawn) + return; + + if (damage <= 10) + self->monsterinfo.currentmove = &fixbot_move_pain3; + else if (damage <= 25) + self->monsterinfo.currentmove = &fixbot_move_painb; + else + self->monsterinfo.currentmove = &fixbot_move_paina; +} + +static void fixbot_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + if (fixbot_is_boss(self)) + fixbot_remove_turrets(self); + fixbot_spawn_laser_off(self); + + gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); + for (n = 0; n < 3; n++) + ThrowGib(self, "models/objects/gibs/sm_metal/tris.md2", damage, GIB_METALLIC); + for (n = 0; n < 2; n++) + ThrowGib(self, "models/objects/gibs/gear/tris.md2", damage, GIB_METALLIC); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1); + gi.WritePosition(self->s.origin); + gi.multicast(self->s.origin, MULTICAST_PVS); + + M_Remove(self, false, false); +} + +static void init_drone_fixbot_common(edict_t *self, qboolean boss) +{ + sound_pain = gi.soundindex("daedalus/daedpain1.wav"); + sound_die = gi.soundindex("daedalus/daeddeth1.wav"); + sound_pew = gi.soundindex("makron/blaster.wav"); + sound_weld = gi.soundindex("misc/welder1.wav"); + sound_spawn = gi.soundindex("infantry/inflies1.wav"); + gi.soundindex("misc/welder2.wav"); + gi.soundindex("misc/welder3.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/fixbot/tris.md2"); + + if (boss) + { + VectorSet(self->mins, -36, -36, -28); + VectorSet(self->maxs, 36, 36, 28); + self->health = M_FIXBOT_BOSS_INITIAL_HEALTH + M_FIXBOT_BOSS_ADDON_HEALTH * self->monsterinfo.level; + self->monsterinfo.power_armor_power = M_FIXBOT_BOSS_INITIAL_ARMOR + M_FIXBOT_BOSS_ADDON_ARMOR * self->monsterinfo.level; + self->mass = 400; + self->s.scale = 2.6f; + self->mtype = M_FIXBOT_BOSS; + self->monsterinfo.control_cost = M_JORG_CONTROL_COST; + self->monsterinfo.cost = M_COMMANDER_COST; + } + else + { + VectorSet(self->mins, -24, -24, -18); + VectorSet(self->maxs, 24, 24, 24); + self->health = M_FIXBOT_INITIAL_HEALTH + M_FIXBOT_ADDON_HEALTH * self->monsterinfo.level; + self->monsterinfo.power_armor_power = M_FIXBOT_INITIAL_ARMOR + M_FIXBOT_ADDON_ARMOR * self->monsterinfo.level; + self->mass = 150; + self->s.scale = 1.55f; + self->mtype = M_FIXBOT; + self->monsterinfo.control_cost = M_HOVER_CONTROL_COST; + self->monsterinfo.cost = M_HOVER_COST; + } + + self->gib_health = -100; + self->max_health = self->health; + self->flags |= FL_FLY | FL_NO_KNOCKBACK; + self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD; + self->monsterinfo.max_armor = self->monsterinfo.power_armor_power; + self->monsterinfo.sight_range = boss ? 1400 : 1024; + self->monsterinfo.pain_chance = boss ? 0.08f : 0.18f; + self->yaw_speed = boss ? 25 : 30; + + self->pain = fixbot_pain; + self->die = fixbot_die; + + self->monsterinfo.stand = fixbot_stand; + self->monsterinfo.walk = fixbot_walk; + self->monsterinfo.run = fixbot_run; + self->monsterinfo.attack = fixbot_attack; + + gi.linkentity(self); + + self->monsterinfo.currentmove = &fixbot_move_stand; + self->monsterinfo.scale = 1.0f; +} + +void init_drone_fixbot(edict_t *self) +{ + init_drone_fixbot_common(self, false); +} + +void init_drone_fixbot_boss(edict_t *self) +{ + init_drone_fixbot_common(self, true); +} diff --git a/src/entities/drone/drone_misc.c b/src/entities/drone/drone_misc.c index 6efe3077..122414b1 100644 --- a/src/entities/drone/drone_misc.c +++ b/src/entities/drone/drone_misc.c @@ -46,6 +46,9 @@ void init_drone_arachnid(edict_t* self); void init_drone_carrier(edict_t* self); void init_drone_widow(edict_t* self); void init_drone_widow2(edict_t* self); +void init_drone_fixbot(edict_t* self); +void init_drone_fixbot_boss(edict_t* self); +void init_drone_rogue_turret(edict_t* self); void init_baron_fire(edict_t* self); void init_skeleton(edict_t* self); void init_golem(edict_t* self); @@ -533,7 +536,7 @@ void drone_death (edict_t *self, edict_t *attacker) //4.2 bosses can drop up to 4 runes - if (self->mtype == M_COMMANDER || self->mtype == M_SUPERTANK || self->mtype == M_MAKRON || self->mtype == M_CARRIER || self->mtype == M_WIDOW || self->mtype == M_WIDOW2 || self->mtype == M_GUARDIAN) + if (self->mtype == M_COMMANDER || self->mtype == M_SUPERTANK || self->mtype == M_MAKRON || self->mtype == M_CARRIER || self->mtype == M_WIDOW || self->mtype == M_WIDOW2 || self->mtype == M_FIXBOT_BOSS || self->mtype == M_GUARDIAN) { edict_t *e; float drop_chance = 0.25; @@ -781,6 +784,7 @@ static qboolean vrx_drone_spawn_is_boss(enum dronespawn_t drone_type) case DS_CARRIER: case DS_WIDOW: case DS_WIDOW2: + case DS_FIXBOT_BOSS: case DS_GUARDIAN: return true; default: @@ -902,6 +906,8 @@ edict_t *vrx_create_drone_from_ent(edict_t *drone, edict_t *ent, enum dronespawn case DS_GEKK: init_drone_gekk(drone); break; case DS_ARACHNID: init_drone_arachnid(drone); break; case DS_MEDIC_COMMANDER: init_drone_medic_commander(drone); break; + case DS_FIXBOT: init_drone_fixbot(drone); break; + case DS_ROGUE_TURRET: init_drone_rogue_turret(drone); break; // bosses case DS_COMMANDER: init_drone_commander(drone); break; @@ -912,6 +918,7 @@ edict_t *vrx_create_drone_from_ent(edict_t *drone, edict_t *ent, enum dronespawn case DS_CARRIER: init_drone_carrier(drone); break; case DS_WIDOW: init_drone_widow(drone); break; case DS_WIDOW2: init_drone_widow2(drone); break; + case DS_FIXBOT_BOSS: init_drone_fixbot_boss(drone); break; case DS_GUARDIAN: init_drone_guardian(drone); break; case DS_JANITOR: drone->mtype = M_JANITOR; init_drone_supertank(drone); break; case DS_MINIGUARDIAN: drone->mtype = M_MINIGUARDIAN; init_drone_guardian(drone); break; @@ -932,6 +939,8 @@ edict_t *vrx_create_drone_from_ent(edict_t *drone, edict_t *ent, enum dronespawn if (drone_type < 30 || drone_type == DS_JANITOR || drone_type == DS_MINIGUARDIAN || + drone_type == DS_FIXBOT || + drone_type == DS_ROGUE_TURRET || drone_type == DS_SOLDIER_RIPPER || drone_type == DS_SOLDIER_BLUEBLASTER || drone_type == DS_SOLDIER_LASER) @@ -2121,6 +2130,9 @@ qboolean M_Initialize (edict_t *ent, edict_t *monster, float dur_bonus) case M_CARRIER: init_drone_carrier(monster); break; case M_WIDOW: init_drone_widow(monster); break; case M_WIDOW2: init_drone_widow2(monster); break; + case M_FIXBOT: init_drone_fixbot(monster); break; + case M_FIXBOT_BOSS: init_drone_fixbot_boss(monster); break; + case M_ROGUE_TURRET: init_drone_rogue_turret(monster); break; case M_GUARDIAN: case M_MINIGUARDIAN: init_drone_guardian(monster); break; case M_JANITOR: init_drone_supertank(monster); break; case M_SKELETON: init_skeleton(monster); break; @@ -2266,6 +2278,18 @@ qboolean M_SetBoundingBox (int mtype, vec3_t boxmin, vec3_t boxmax) VectorSet(boxmin, -70, -70, 0); VectorSet(boxmax, 70, 70, 144); break; + case M_FIXBOT: + VectorSet(boxmin, -24, -24, -18); + VectorSet(boxmax, 24, 24, 24); + break; + case M_FIXBOT_BOSS: + VectorSet(boxmin, -36, -36, -28); + VectorSet(boxmax, 36, 36, 28); + break; + case M_ROGUE_TURRET: + VectorSet(boxmin, -12, -12, -12); + VectorSet(boxmax, 12, 12, 12); + break; case M_GUARDIAN: VectorSet(boxmin, -96, -96, -66); VectorSet(boxmax, 96, 96, 62); @@ -2364,6 +2388,9 @@ char *GetMonsterKindString (int mtype) case M_CARRIER: return "Carrier"; case M_WIDOW: return "Widow"; case M_WIDOW2: return "Black Widow"; + case M_FIXBOT: return "Fixbot"; + case M_FIXBOT_BOSS: return "Fixer"; + case M_ROGUE_TURRET: return "Rocket Turret"; case M_GUARDIAN: return "Guardian"; case M_JANITOR: return "Janitor"; case M_MINIGUARDIAN: return "Mini Guardian"; @@ -2951,7 +2978,7 @@ void Cmd_Drone_f (edict_t *ent) if (!Q_strcasecmp(s, "help")) { safe_cprintf(ent, PRINT_HIGH, "Monster summoning:\n"); - safe_cprintf(ent, PRINT_HIGH, "monster [gunner|parasite|brain|praetor|praetor_heat|medic|tank|mutant|gladiator|gladb|gladc|berserker|soldier|ripper|hyper|laser|janitor|janitor2|enforcer|flyer|floater|hover|daedalus|stalker|gekk|arachnid|shambler|redmutant|runnertank|guncmdr]\n"); + safe_cprintf(ent, PRINT_HIGH, "monster [gunner|parasite|brain|praetor|praetor_heat|medic|tank|mutant|gladiator|gladb|gladc|berserker|soldier|ripper|hyper|laser|janitor|janitor2|enforcer|flyer|floater|hover|fixbot|daedalus|stalker|gekk|arachnid|shambler|redmutant|runnertank|guncmdr]\n"); safe_cprintf(ent, PRINT_HIGH, "Monster utility commands:\n"); safe_cprintf(ent, PRINT_HIGH, "monster [remove|command|follow me|count|attack]\n"); return; @@ -3013,6 +3040,8 @@ void Cmd_Drone_f (edict_t *ent) vrx_create_new_drone(ent, DS_FLOATER, false, true, 0); else if (!Q_strcasecmp(s, "hover")) vrx_create_new_drone(ent, DS_HOVER, false, true, 0); + else if (!Q_strcasecmp(s, "fixbot")) + vrx_create_new_drone(ent, DS_FIXBOT, false, true, 0); else if (!Q_strcasecmp(s, "shambler")) vrx_create_new_drone(ent, DS_SHAMBLER, false, true, 0); else if (!Q_strcasecmp(s, "redmutant")) diff --git a/src/entities/drone/drone_rogue_turret.c b/src/entities/drone/drone_rogue_turret.c new file mode 100644 index 00000000..95bf9d65 --- /dev/null +++ b/src/entities/drone/drone_rogue_turret.c @@ -0,0 +1,342 @@ +/* +============================================================================== + +rogue rocket turret + +============================================================================== +*/ + +#include "g_local.h" + +#define TURRET_FRAME_stand01 0 +#define TURRET_FRAME_stand02 1 +#define TURRET_FRAME_active01 2 +#define TURRET_FRAME_run01 8 +#define TURRET_FRAME_run02 9 +#define TURRET_FRAME_pow01 10 +#define TURRET_FRAME_pow04 13 + +#define TURRET_ROCKET_DAMAGE 40 +#define TURRET_ROCKET_SPEED 650 + +static int sound_moved; +static int sound_moving; + +void drone_ai_stand(edict_t *self, float dist); + +static void rogue_turret_stand(edict_t *self); +static void rogue_turret_run(edict_t *self); +static void rogue_turret_active(edict_t *self); + +static void rogue_turret_laser_off(edict_t *self) +{ + if (self->target_ent && self->target_ent->inuse) + G_FreeEdict(self->target_ent); + self->target_ent = NULL; +} + +static void rogue_turret_update_laser(edict_t *self) +{ + vec3_t forward; + vec3_t end; + trace_t tr; + edict_t *laser; + float scan_range; + float phase; + + if (!G_ValidTarget(self, self->enemy, false, true)) + { + rogue_turret_laser_off(self); + return; + } + + laser = self->target_ent; + if (!laser || !laser->inuse) + { + laser = G_Spawn(); + if (!laser) + return; + self->target_ent = laser; + laser->movetype = MOVETYPE_NONE; + laser->solid = SOLID_NOT; + laser->s.renderfx = RF_BEAM | RF_TRANSLUCENT; + laser->s.modelindex = 1; + laser->s.frame = 1; + laser->s.skinnum = 0xf2f2f0f0; + laser->classname = "turret_lasersight"; + laser->owner = self; + } + + AngleVectors(self->s.angles, forward, NULL, NULL); + VectorMA(self->s.origin, 8192, forward, end); + tr = gi.trace(self->s.origin, NULL, NULL, end, self, MASK_SOLID); + + scan_range = visible(self, self->enemy) ? 12.0f : 64.0f; + phase = level.time + (float)(self - g_edicts); + tr.endpos[0] += sinf(phase) * scan_range; + tr.endpos[1] += cosf(phase * 3.0f) * scan_range; + tr.endpos[2] += sinf(phase * 2.5f) * scan_range; + + VectorSubtract(tr.endpos, self->s.origin, forward); + if (VectorNormalize(forward)) + { + VectorMA(self->s.origin, 8192, forward, end); + tr = gi.trace(self->s.origin, NULL, NULL, end, self, MASK_SOLID); + } + + VectorCopy(self->s.origin, laser->s.origin); + VectorCopy(tr.endpos, laser->s.old_origin); + VectorCopy(self->s.origin, laser->pos1); + VectorCopy(tr.endpos, laser->pos2); + laser->think = G_FreeEdict; + laser->nextthink = level.time + 0.3f; + gi.linkentity(laser); +} + +static void rogue_turret_aim(edict_t *self) +{ + vec3_t dir; + vec3_t angles; + + if (!G_ValidTarget(self, self->enemy, false, true)) + { + rogue_turret_laser_off(self); + return; + } + + VectorSubtract(self->enemy->s.origin, self->s.origin, dir); + if (VectorLength(dir) < 1) + { + rogue_turret_laser_off(self); + return; + } + + vectoangles(dir, angles); + self->ideal_yaw = angles[YAW]; + M_ChangeYaw(self); + self->s.angles[PITCH] = angles[PITCH]; + rogue_turret_update_laser(self); +} + +static void rogue_turret_fire(edict_t *self) +{ + vec3_t forward; + vec3_t aim; + vec3_t right; + vec3_t start; + vec3_t end; + trace_t tr; + + if (!G_ValidTarget(self, self->enemy, false, true)) + { + rogue_turret_laser_off(self); + return; + } + + rogue_turret_aim(self); + + if (entdist(self, self->enemy) <= 72) + return; + + G_EntMidPoint(self->enemy, end); + AngleVectors(self->s.angles, forward, right, NULL); + G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_TURRET_ROCKET], forward, right, start); + VectorSubtract(end, start, aim); + if (!VectorNormalize(aim)) + return; + + if (DotProduct(aim, forward) < 0.90f) + return; + + tr = gi.trace(start, NULL, NULL, end, self, MASK_SHOT); + if (tr.fraction < 1.0 && tr.ent != self->enemy && !G_ValidTarget(self, tr.ent, false, true)) + return; + + monster_fire_rocket(self, start, aim, TURRET_ROCKET_DAMAGE, TURRET_ROCKET_SPEED, MZ2_TURRET_ROCKET); +} + +static mframe_t rogue_turret_frames_stand[] = +{ + drone_ai_stand, 0, rogue_turret_aim, + drone_ai_stand, 0, rogue_turret_aim +}; +static mmove_t rogue_turret_move_stand = { TURRET_FRAME_stand01, TURRET_FRAME_stand02, rogue_turret_frames_stand, NULL }; + +static void rogue_turret_stand(edict_t *self) +{ + if (!G_ValidTarget(self, self->enemy, false, true)) + rogue_turret_laser_off(self); + self->monsterinfo.currentmove = &rogue_turret_move_stand; +} + +static mframe_t rogue_turret_frames_ready[] = +{ + ai_move, 0, rogue_turret_aim, + ai_move, 0, rogue_turret_aim, + ai_move, 0, rogue_turret_aim, + ai_move, 0, rogue_turret_aim, + ai_move, 0, rogue_turret_aim, + ai_move, 0, rogue_turret_aim, + ai_move, 0, rogue_turret_aim +}; +static mmove_t rogue_turret_move_ready = { TURRET_FRAME_active01, TURRET_FRAME_run01, rogue_turret_frames_ready, rogue_turret_run }; + +static void rogue_turret_ready(edict_t *self) +{ + if (self->monsterinfo.currentmove != &rogue_turret_move_ready) + { + self->monsterinfo.currentmove = &rogue_turret_move_ready; + self->random = 1; + gi.sound(self, CHAN_WEAPON, sound_moving, 1, ATTN_NORM, 0); + } +} + +static mframe_t rogue_turret_frames_run[] = +{ + drone_ai_stand, 0, rogue_turret_active, + drone_ai_stand, 0, rogue_turret_active +}; +static mmove_t rogue_turret_move_run = { TURRET_FRAME_run01, TURRET_FRAME_run02, rogue_turret_frames_run, rogue_turret_run }; + +static void rogue_turret_run(edict_t *self) +{ + if (self->s.frame < TURRET_FRAME_run01) + { + rogue_turret_ready(self); + return; + } + + if (self->random) + { + self->random = 0; + gi.sound(self, CHAN_WEAPON, sound_moved, 1, ATTN_NORM, 0); + } + + self->monsterinfo.currentmove = &rogue_turret_move_run; +} + +static mframe_t rogue_turret_frames_fire[] = +{ + ai_charge, 0, rogue_turret_aim, + ai_charge, 0, rogue_turret_fire, + ai_charge, 0, rogue_turret_aim, + ai_charge, 0, rogue_turret_aim +}; +static mmove_t rogue_turret_move_fire = { TURRET_FRAME_pow01, TURRET_FRAME_pow04, rogue_turret_frames_fire, rogue_turret_run }; + +static void rogue_turret_active(edict_t *self) +{ + rogue_turret_aim(self); + + if (!G_ValidTarget(self, self->enemy, false, true)) + return; + if (level.time < self->monsterinfo.attack_finished) + return; + if (entdist(self, self->enemy) <= 72) + { + M_DelayNextAttack(self, 0.3f, false); + return; + } + + self->monsterinfo.currentmove = &rogue_turret_move_fire; + M_DelayNextAttack(self, 0.8f + random() * 0.4f, true); +} + +static void rogue_turret_attack(edict_t *self) +{ + if (!G_ValidTarget(self, self->enemy, false, true)) + { + rogue_turret_laser_off(self); + return; + } + + rogue_turret_aim(self); + if (self->s.frame < TURRET_FRAME_run01) + rogue_turret_ready(self); + else + self->monsterinfo.currentmove = &rogue_turret_move_fire; + + M_DelayNextAttack(self, 1.0f + random() * 0.5f, true); +} + +static void rogue_turret_pain(edict_t *self, edict_t *other, float kick, int damage) +{ +} + +void rogue_turret_force_ready(edict_t *self) +{ + self->s.frame = TURRET_FRAME_run01; + self->monsterinfo.currentmove = &rogue_turret_move_run; + self->monsterinfo.attack_finished = level.time; + self->monsterinfo.pausetime = 0; + self->nextthink = level.time + FRAMETIME; + + if (G_ValidTarget(self, self->enemy, false, true)) + rogue_turret_aim(self); + else + rogue_turret_laser_off(self); +} + +static void rogue_turret_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + rogue_turret_laser_off(self); + + for (n = 0; n < 3; n++) + ThrowGib(self, "models/objects/debris1/tris.md2", 150, GIB_METALLIC); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1); + gi.WritePosition(self->s.origin); + gi.multicast(self->s.origin, MULTICAST_PVS); + + M_Remove(self, false, false); +} + +void init_drone_rogue_turret(edict_t *self) +{ + sound_moved = gi.soundindex("turret/moved.wav"); + sound_moving = gi.soundindex("turret/moving.wav"); + gi.soundindex("weapons/rockfly.wav"); + gi.soundindex("chick/chkatck2.wav"); + gi.modelindex("models/objects/rocket/tris.md2"); + gi.modelindex("models/objects/debris1/tris.md2"); + + self->movetype = MOVETYPE_NONE; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/turret/tris.md2"); + self->s.skinnum = 2; + VectorSet(self->mins, -12, -12, -12); + VectorSet(self->maxs, 12, 12, 12); + + self->health = M_ROGUE_TURRET_INITIAL_HEALTH + M_ROGUE_TURRET_ADDON_HEALTH * self->monsterinfo.level; + self->gib_health = -100; + self->mass = 250; + self->mtype = M_ROGUE_TURRET; + self->flags |= FL_NO_KNOCKBACK; + self->max_health = self->health; + self->monsterinfo.power_armor_power = M_ROGUE_TURRET_INITIAL_ARMOR + M_ROGUE_TURRET_ADDON_ARMOR * self->monsterinfo.level; + self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD; + self->monsterinfo.max_armor = self->monsterinfo.power_armor_power; + self->monsterinfo.control_cost = M_DEFAULT_CONTROL_COST; + self->monsterinfo.cost = M_DEFAULT_COST; + self->monsterinfo.sight_range = 1024; + self->monsterinfo.aiflags |= AI_STAND_GROUND; + self->monsterinfo.pain_chance = 0.0f; + self->gravity = 0; + self->yaw_speed = 40; + + self->pain = rogue_turret_pain; + self->die = rogue_turret_die; + + self->monsterinfo.stand = rogue_turret_stand; + self->monsterinfo.walk = rogue_turret_run; + self->monsterinfo.run = rogue_turret_run; + self->monsterinfo.attack = rogue_turret_attack; + + gi.linkentity(self); + + self->monsterinfo.currentmove = &rogue_turret_move_stand; + self->monsterinfo.scale = 3.5f; +} diff --git a/src/g_local.h b/src/g_local.h index 0de7f56f..3e07bd37 100644 --- a/src/g_local.h +++ b/src/g_local.h @@ -1532,6 +1532,9 @@ enum mtype_t { M_SOLDIER_LASER = 45, M_WIDOW = 46, M_WIDOW2 = 47, + M_FIXBOT = 48, + M_FIXBOT_BOSS = 49, + M_ROGUE_TURRET = 50, M_MINISENTRY = 100, M_SENTRY = 101, M_BFG_SENTRY = 102, @@ -1655,6 +1658,9 @@ enum dronespawn_t { DS_SOLDIER_LASER = 41, DS_WIDOW = 42, DS_WIDOW2 = 43, + DS_FIXBOT = 44, + DS_FIXBOT_BOSS = 45, + DS_ROGUE_TURRET = 46, }; diff --git a/src/quake2/g_layout.c b/src/quake2/g_layout.c index 61a41ae6..1fbc7988 100644 --- a/src/quake2/g_layout.c +++ b/src/quake2/g_layout.c @@ -337,6 +337,9 @@ sidebar_entry_t layout_add_entity_info(sidebar_t* sidebar, edict_t* ent) case M_CARRIER: case M_WIDOW: case M_WIDOW2: + case M_FIXBOT: + case M_FIXBOT_BOSS: + case M_ROGUE_TURRET: case M_GUARDIAN: case M_JANITOR: case M_MINIGUARDIAN: diff --git a/src/quake2/g_svcmds.c b/src/quake2/g_svcmds.c index ecea8934..67763778 100644 --- a/src/quake2/g_svcmds.c +++ b/src/quake2/g_svcmds.c @@ -665,6 +665,13 @@ void SVCmd_SpawnBoss_f (void) else vrx_create_new_drone(m_worldspawn, DS_WIDOW2, true, true, 0); } + else if (!strcmp(gi.argv(2), "fixbot_boss") || !strcmp(gi.argv(2), "fixbotboss") || !strcmp(gi.argv(2), "fixbotkl")) + { + if (invasion->value) + vrx_inv_spawn_boss(m_worldspawn, DS_FIXBOT_BOSS); + else + vrx_create_new_drone(m_worldspawn, DS_FIXBOT_BOSS, true, true, 0); + } else if (!strcmp(gi.argv(2), "guardian") || !strcmp(gi.argv(2), "psxguardian")) { if (invasion->value) @@ -673,7 +680,7 @@ void SVCmd_SpawnBoss_f (void) vrx_create_new_drone(m_worldspawn, DS_GUARDIAN, true, true, 0); } else - safe_cprintf(NULL, PRINT_HIGH, "Invalid boss type. Usage: sv spawnboss \n"); + safe_cprintf(NULL, PRINT_HIGH, "Invalid boss type. Usage: sv spawnboss \n"); } void SVCmd_MakeBoss_f (void) diff --git a/src/server/v_luasettings.c b/src/server/v_luasettings.c index 29268309..0de80800 100644 --- a/src/server/v_luasettings.c +++ b/src/server/v_luasettings.c @@ -1150,6 +1150,18 @@ void Lua_LoadVariables() M_WIDOW2_ADDON_HEALTH = vrx_lua_get_variable("M_WIDOW2_ADDON_HEALTH", 900); M_WIDOW2_INITIAL_ARMOR = vrx_lua_get_variable("M_WIDOW2_INITIAL_ARMOR", 750); M_WIDOW2_ADDON_ARMOR = vrx_lua_get_variable("M_WIDOW2_ADDON_ARMOR", 450); + M_FIXBOT_INITIAL_HEALTH = vrx_lua_get_variable("M_FIXBOT_INITIAL_HEALTH", 250); + M_FIXBOT_ADDON_HEALTH = vrx_lua_get_variable("M_FIXBOT_ADDON_HEALTH", 90); + M_FIXBOT_INITIAL_ARMOR = vrx_lua_get_variable("M_FIXBOT_INITIAL_ARMOR", 120); + M_FIXBOT_ADDON_ARMOR = vrx_lua_get_variable("M_FIXBOT_ADDON_ARMOR", 45); + M_FIXBOT_BOSS_INITIAL_HEALTH = vrx_lua_get_variable("M_FIXBOT_BOSS_INITIAL_HEALTH", 3200); + M_FIXBOT_BOSS_ADDON_HEALTH = vrx_lua_get_variable("M_FIXBOT_BOSS_ADDON_HEALTH", 850); + M_FIXBOT_BOSS_INITIAL_ARMOR = vrx_lua_get_variable("M_FIXBOT_BOSS_INITIAL_ARMOR", 700); + M_FIXBOT_BOSS_ADDON_ARMOR = vrx_lua_get_variable("M_FIXBOT_BOSS_ADDON_ARMOR", 350); + M_ROGUE_TURRET_INITIAL_HEALTH = vrx_lua_get_variable("M_ROGUE_TURRET_INITIAL_HEALTH", 50); + M_ROGUE_TURRET_ADDON_HEALTH = vrx_lua_get_variable("M_ROGUE_TURRET_ADDON_HEALTH", 25); + M_ROGUE_TURRET_INITIAL_ARMOR = vrx_lua_get_variable("M_ROGUE_TURRET_INITIAL_ARMOR", 50); + M_ROGUE_TURRET_ADDON_ARMOR = vrx_lua_get_variable("M_ROGUE_TURRET_ADDON_ARMOR", 15); M_GUARDIAN_INITIAL_HEALTH = vrx_lua_get_variable("M_GUARDIAN_INITIAL_HEALTH", 6500); M_GUARDIAN_ADDON_HEALTH = vrx_lua_get_variable("M_GUARDIAN_ADDON_HEALTH", 1200); M_GUARDIAN_INITIAL_ARMOR = vrx_lua_get_variable("M_GUARDIAN_INITIAL_ARMOR", 550); From 8e6e9dd247e37b0867889c64e1d034d81537a481 Mon Sep 17 00:00:00 2001 From: Enemy-123 Date: Sat, 25 Apr 2026 17:00:19 -0400 Subject: [PATCH 15/24] G_PrintGreenText only if fixer boss --- src/entities/drone/drone_fixbot.c | 4 ++++ src/quake2/g_svcmds.c | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/entities/drone/drone_fixbot.c b/src/entities/drone/drone_fixbot.c index 748af076..09610ed2 100644 --- a/src/entities/drone/drone_fixbot.c +++ b/src/entities/drone/drone_fixbot.c @@ -1199,6 +1199,10 @@ static void init_drone_fixbot_common(edict_t *self, qboolean boss) self->monsterinfo.currentmove = &fixbot_move_stand; self->monsterinfo.scale = 1.0f; + + qboolean isBoss = (self->mtype == M_FIXBOT_BOSS); + if (isBoss) + G_PrintGreenText(va("A level %d fixer has spawned!", self->monsterinfo.level)); } void init_drone_fixbot(edict_t *self) diff --git a/src/quake2/g_svcmds.c b/src/quake2/g_svcmds.c index 67763778..504f4fae 100644 --- a/src/quake2/g_svcmds.c +++ b/src/quake2/g_svcmds.c @@ -665,7 +665,7 @@ void SVCmd_SpawnBoss_f (void) else vrx_create_new_drone(m_worldspawn, DS_WIDOW2, true, true, 0); } - else if (!strcmp(gi.argv(2), "fixbot_boss") || !strcmp(gi.argv(2), "fixbotboss") || !strcmp(gi.argv(2), "fixbotkl")) + else if (!strcmp(gi.argv(2), "fixbot_boss") || !strcmp(gi.argv(2), "fixbotboss") || !strcmp(gi.argv(2), "fixer")) { if (invasion->value) vrx_inv_spawn_boss(m_worldspawn, DS_FIXBOT_BOSS); From 1ef87a333bec0a45b7665795944e738eec669193 Mon Sep 17 00:00:00 2001 From: Enemy-123 Date: Sat, 25 Apr 2026 18:22:15 -0400 Subject: [PATCH 16/24] added boss2 and boss2 n64 + mini boss2 hyperblaster variant --- lua/variables.lua | 8 + src/characters/settings.h | 8 + src/characters/v_utils.c | 6 +- src/combat/common/damage.c | 2 +- src/combat/common/v_misc.c | 9 + src/entities/drone/drone_boss2.c | 726 +++++++++++++++++++++++++++++++ src/entities/drone/drone_misc.c | 30 +- src/g_local.h | 4 + src/gamemodes/invasion.c | 5 +- src/quake2/g_layout.c | 2 + src/quake2/g_svcmds.c | 17 +- src/server/v_luasettings.c | 8 + 12 files changed, 817 insertions(+), 8 deletions(-) create mode 100644 src/entities/drone/drone_boss2.c diff --git a/lua/variables.lua b/lua/variables.lua index bf9dddb5..39ffdde3 100644 --- a/lua/variables.lua +++ b/lua/variables.lua @@ -803,6 +803,14 @@ M_ROGUE_TURRET_INITIAL_HEALTH = 50 M_ROGUE_TURRET_ADDON_HEALTH = 25 M_ROGUE_TURRET_INITIAL_ARMOR = 50 M_ROGUE_TURRET_ADDON_ARMOR = 15 +M_BOSS2_INITIAL_HEALTH = 2400 +M_BOSS2_ADDON_HEALTH = 700 +M_BOSS2_INITIAL_ARMOR = 600 +M_BOSS2_ADDON_ARMOR = 350 +M_BOSS2_SMALL_INITIAL_HEALTH = 250 +M_BOSS2_SMALL_ADDON_HEALTH = 100 +M_BOSS2_SMALL_INITIAL_ARMOR = 160 +M_BOSS2_SMALL_ADDON_ARMOR = 70 M_GUARDIAN_INITIAL_HEALTH = 6500 M_GUARDIAN_ADDON_HEALTH = 1200 M_GUARDIAN_INITIAL_ARMOR = 550 diff --git a/src/characters/settings.h b/src/characters/settings.h index 9dbc0c10..b6516dd3 100644 --- a/src/characters/settings.h +++ b/src/characters/settings.h @@ -838,6 +838,14 @@ VRX_V_LUASETTINGS_IMPL double M_ROGUE_TURRET_INITIAL_HEALTH; VRX_V_LUASETTINGS_IMPL double M_ROGUE_TURRET_ADDON_HEALTH; VRX_V_LUASETTINGS_IMPL double M_ROGUE_TURRET_INITIAL_ARMOR; VRX_V_LUASETTINGS_IMPL double M_ROGUE_TURRET_ADDON_ARMOR; +VRX_V_LUASETTINGS_IMPL double M_BOSS2_INITIAL_HEALTH; +VRX_V_LUASETTINGS_IMPL double M_BOSS2_ADDON_HEALTH; +VRX_V_LUASETTINGS_IMPL double M_BOSS2_INITIAL_ARMOR; +VRX_V_LUASETTINGS_IMPL double M_BOSS2_ADDON_ARMOR; +VRX_V_LUASETTINGS_IMPL double M_BOSS2_SMALL_INITIAL_HEALTH; +VRX_V_LUASETTINGS_IMPL double M_BOSS2_SMALL_ADDON_HEALTH; +VRX_V_LUASETTINGS_IMPL double M_BOSS2_SMALL_INITIAL_ARMOR; +VRX_V_LUASETTINGS_IMPL double M_BOSS2_SMALL_ADDON_ARMOR; VRX_V_LUASETTINGS_IMPL double M_GUARDIAN_INITIAL_HEALTH; VRX_V_LUASETTINGS_IMPL double M_GUARDIAN_ADDON_HEALTH; VRX_V_LUASETTINGS_IMPL double M_GUARDIAN_INITIAL_ARMOR; diff --git a/src/characters/v_utils.c b/src/characters/v_utils.c index a61d58c6..b0879e91 100644 --- a/src/characters/v_utils.c +++ b/src/characters/v_utils.c @@ -2107,6 +2107,10 @@ char *V_GetMonsterKind(int mtype) { return "gekk"; case M_ARACHNID: return "arachnid"; + case M_BOSS2: + return "hornet"; + case M_BOSS2_SMALL: + return "mini hornet"; case M_CARRIER: return "carrier"; case M_WIDOW: @@ -2120,7 +2124,7 @@ char *V_GetMonsterKind(int mtype) { case M_ROGUE_TURRET: return "rocket turret"; case M_GUARDIAN: - return "Guardian"; + return "guardian"; case M_JANITOR: return "janitor"; case M_MINIGUARDIAN: diff --git a/src/combat/common/damage.c b/src/combat/common/damage.c index 257267f8..c8f45878 100644 --- a/src/combat/common/damage.c +++ b/src/combat/common/damage.c @@ -154,7 +154,7 @@ qboolean IsMonster(const edict_t* ent) { || ent->mtype == M_REDMUTANT || ent->mtype == M_RUNNERTANK || ent->mtype == M_GUNCMDR || ent->mtype == M_DAEDALUS || ent->mtype == M_GLADB || ent->mtype == M_GLADC || ent->mtype == M_STALKER || ent->mtype == M_GEKK || ent->mtype == M_ARACHNID - || ent->mtype == M_MEDIC_COMMANDER || ent->mtype == M_CARRIER + || ent->mtype == M_MEDIC_COMMANDER || ent->mtype == M_BOSS2 || ent->mtype == M_BOSS2_SMALL || ent->mtype == M_CARRIER || ent->mtype == M_WIDOW || ent->mtype == M_WIDOW2 || ent->mtype == M_FIXBOT || ent->mtype == M_FIXBOT_BOSS || ent->mtype == M_ROGUE_TURRET || ent->mtype == M_CHICK_HEAT)); diff --git a/src/combat/common/v_misc.c b/src/combat/common/v_misc.c index 13ea6749..1d5f680e 100644 --- a/src/combat/common/v_misc.c +++ b/src/combat/common/v_misc.c @@ -272,6 +272,7 @@ static enum dronespawn_t vrx_pvm_random_drone_type(void) DS_ARACHNID, DS_MEDIC_COMMANDER, DS_FIXBOT, + DS_BOSS2_SMALL, DS_JANITOR, DS_MINIGUARDIAN }; @@ -682,8 +683,12 @@ int vrx_GetMonsterCost(int mtype) { case M_WIDOW: case M_WIDOW2: case M_FIXBOT_BOSS: + case M_BOSS2: cost = M_COMMANDER_COST; break; + case M_BOSS2_SMALL: + cost = M_HOVER_COST; + break; case M_FIXBOT: cost = M_HOVER_COST; break; @@ -776,8 +781,12 @@ int vrx_GetMonsterControlCost(int mtype) { case M_WIDOW: case M_WIDOW2: case M_FIXBOT_BOSS: + case M_BOSS2: cost = M_JORG_CONTROL_COST; break; + case M_BOSS2_SMALL: + cost = M_HOVER_CONTROL_COST; + break; case M_FIXBOT: cost = M_HOVER_CONTROL_COST; break; diff --git a/src/entities/drone/drone_boss2.c b/src/entities/drone/drone_boss2.c new file mode 100644 index 00000000..157e068f --- /dev/null +++ b/src/entities/drone/drone_boss2.c @@ -0,0 +1,726 @@ +/* +============================================================================== + +boss2 + +============================================================================== +*/ + +#include "g_local.h" +#include "../../quake2/monsterframes/m_boss2.h" + +#define BOSS2_VARIANT_MG 0 +#define BOSS2_VARIANT_HYPER 1 +#define BOSS2_VARIANT_SMALL 2 +#define BOSS2_ROCKET_SPEED 750 + +static int sound_pain1; +static int sound_pain2; +static int sound_pain3; +static int sound_death; +static int sound_search1; + +void drone_ai_stand(edict_t *self, float dist); +void drone_ai_walk(edict_t *self, float dist); +void drone_ai_run(edict_t *self, float dist); + +static void boss2_run(edict_t *self); +static void boss2_attack_mg(edict_t *self); +static void boss2_reattack_mg(edict_t *self); + +static qboolean boss2_is_small(const edict_t *self) +{ + return self->style == BOSS2_VARIANT_SMALL; +} + +static qboolean boss2_is_hyper(const edict_t *self) +{ + return self->style == BOSS2_VARIANT_HYPER || boss2_is_small(self); +} + +static void boss2_project_flash(edict_t *self, int flash, vec3_t start) +{ + vec3_t forward, right; + vec3_t offset; + + AngleVectors(self->s.angles, forward, right, NULL); + VectorCopy(monster_flash_offset[flash], offset); + if (self->s.scale && self->s.scale != 1.0f) + VectorScale(offset, self->s.scale, offset); + G_ProjectSource(self->s.origin, offset, forward, right, start); +} + +static void boss2_predict_aim(edict_t *self, vec3_t start, int speed, float lead, vec3_t dir) +{ + vec3_t target; + float dist; + float time; + + if (!G_ValidTarget(self, self->enemy, false, true)) + { + AngleVectors(self->s.angles, dir, NULL, NULL); + return; + } + + G_EntMidPoint(self->enemy, target); + if (speed > 0) + { + VectorSubtract(target, start, dir); + dist = VectorLength(dir); + time = dist / speed + lead; + if (time < 0) + time = 0; + VectorMA(target, time, self->enemy->velocity, target); + } + + VectorSubtract(target, start, dir); + if (!VectorNormalize(dir)) + AngleVectors(self->s.angles, dir, NULL, NULL); +} + +static void boss2_search(edict_t *self) +{ + if (random() < 0.5f) + gi.sound(self, CHAN_VOICE, sound_search1, 1, ATTN_NONE, 0); +} + +static void boss2_explode(edict_t *self) +{ + vec3_t org; + + VectorCopy(self->s.origin, org); + org[0] += crandom() * self->maxs[0]; + org[1] += crandom() * self->maxs[1]; + org[2] += crandom() * self->maxs[2]; + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1_BIG); + gi.WritePosition(org); + gi.multicast(self->s.origin, MULTICAST_PVS); +} + +static void boss2_dead(edict_t *self) +{ + int n; + + for (n = 0; n < 3; n++) + ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", 500, GIB_ORGANIC); + for (n = 0; n < 5; n++) + ThrowGib(self, "models/objects/gibs/sm_metal/tris.md2", 500, GIB_METALLIC); + + ThrowGib(self, "models/monsters/boss2/gibs/chest.md2", 500, GIB_METALLIC); + ThrowGib(self, "models/monsters/boss2/gibs/chaingun.md2", 500, GIB_METALLIC); + ThrowGib(self, "models/monsters/boss2/gibs/cpu.md2", 500, GIB_METALLIC); + ThrowGib(self, "models/monsters/boss2/gibs/engine.md2", 500, GIB_METALLIC); + ThrowGib(self, "models/monsters/boss2/gibs/rocket.md2", 500, GIB_METALLIC); + ThrowGib(self, "models/monsters/boss2/gibs/spine.md2", 500, GIB_METALLIC); + ThrowGib(self, "models/monsters/boss2/gibs/wing.md2", 500, GIB_METALLIC); + ThrowGib(self, "models/monsters/boss2/gibs/larm.md2", 500, GIB_METALLIC); + ThrowGib(self, "models/monsters/boss2/gibs/rarm.md2", 500, GIB_METALLIC); + ThrowHead(self, "models/monsters/boss2/gibs/head.md2", 500, GIB_METALLIC); + + boss2_explode(self); + M_Remove(self, false, false); +} + +static void boss2_fire_predictive_rocket(edict_t *self, int flash, float lead) +{ + vec3_t start, dir; + + if (!G_ValidTarget(self, self->enemy, false, true)) + return; + + boss2_project_flash(self, flash, start); + boss2_predict_aim(self, start, BOSS2_ROCKET_SPEED, lead, dir); + monster_fire_rocket(self, start, dir, 50, BOSS2_ROCKET_SPEED, flash); +} + +static void Boss2PredictiveRocket(edict_t *self) +{ + boss2_fire_predictive_rocket(self, MZ2_BOSS2_ROCKET_1, -0.10f); + boss2_fire_predictive_rocket(self, MZ2_BOSS2_ROCKET_2, -0.05f); + boss2_fire_predictive_rocket(self, MZ2_BOSS2_ROCKET_3, 0.05f); + boss2_fire_predictive_rocket(self, MZ2_BOSS2_ROCKET_4, 0.10f); +} + +static void boss2_fire_spread_rocket(edict_t *self, int flash, float zofs, float rightofs) +{ + vec3_t forward, right; + vec3_t start, dir, target; + + if (!G_ValidTarget(self, self->enemy, false, true)) + return; + + AngleVectors(self->s.angles, forward, right, NULL); + G_ProjectSource(self->s.origin, monster_flash_offset[flash], forward, right, start); + VectorCopy(self->enemy->s.origin, target); + target[2] += zofs; + VectorSubtract(target, start, dir); + if (!VectorNormalize(dir)) + VectorCopy(forward, dir); + VectorMA(dir, rightofs, right, dir); + VectorNormalize(dir); + monster_fire_rocket(self, start, dir, 50, 500, flash); +} + +static void Boss2Rocket(edict_t *self) +{ + if (!G_ValidTarget(self, self->enemy, false, true)) + return; + + if (self->enemy->client && random() < 0.9f) + { + Boss2PredictiveRocket(self); + return; + } + + boss2_fire_spread_rocket(self, MZ2_BOSS2_ROCKET_1, -15, 0.4f); + boss2_fire_spread_rocket(self, MZ2_BOSS2_ROCKET_2, 0, 0.025f); + boss2_fire_spread_rocket(self, MZ2_BOSS2_ROCKET_3, 0, -0.025f); + boss2_fire_spread_rocket(self, MZ2_BOSS2_ROCKET_4, -15, -0.4f); +} + +static void Boss2Rocket64(edict_t *self) +{ + vec3_t forward, right; + vec3_t start, dir, target; + float scale; + + if (!G_ValidTarget(self, self->enemy, false, true)) + return; + + AngleVectors(self->s.angles, forward, right, NULL); + boss2_project_flash(self, MZ2_BOSS2_ROCKET_1, start); + + scale = self->s.scale ? self->s.scale : 1.0f; + start[2] += 10.0f * scale; + VectorMA(start, -2.0f * scale, right, start); + VectorMA(start, -((self->count++ % 4) * 8.0f * scale), right, start); + + if (self->enemy->client && random() < 0.9f) + boss2_predict_aim(self, start, BOSS2_ROCKET_SPEED, -0.3f, dir); + else + { + VectorCopy(self->enemy->s.origin, target); + target[2] -= 15; + VectorSubtract(target, start, dir); + if (!VectorNormalize(dir)) + VectorCopy(forward, dir); + } + + monster_fire_rocket(self, start, dir, 35, BOSS2_ROCKET_SPEED, MZ2_BOSS2_ROCKET_1); +} + +static void boss2_firebullet(edict_t *self, int flash) +{ + vec3_t start, dir; + + if (!G_ValidTarget(self, self->enemy, false, true)) + return; + + boss2_project_flash(self, flash, start); + boss2_predict_aim(self, start, 0, 0, dir); + monster_fire_bullet(self, start, dir, 6, 4, DEFAULT_BULLET_HSPREAD * 3, DEFAULT_BULLET_VSPREAD, flash); +} + +static void Boss2MachineGun(edict_t *self) +{ + boss2_firebullet(self, MZ2_BOSS2_MACHINEGUN_L1); + boss2_firebullet(self, MZ2_BOSS2_MACHINEGUN_R1); +} + +static void Boss2HyperBlaster(edict_t *self) +{ + int flash; + int effect; + vec3_t start, dir; + + if (!G_ValidTarget(self, self->enemy, false, true)) + return; + + flash = (self->s.frame & 1) ? MZ2_BOSS2_MACHINEGUN_L2 : MZ2_BOSS2_MACHINEGUN_R2; + effect = (self->s.frame % 4) ? 0 : EF_HYPERBLASTER; + boss2_project_flash(self, flash, start); + boss2_predict_aim(self, start, 1000, 0, dir); + monster_fire_blaster(self, start, dir, 2, 1000, effect, BLASTER_PROJ_BOLT, 2.0f, false, flash); +} + +static void Boss2HyperBlasterReattack(edict_t *self) +{ + Boss2HyperBlaster(self); + boss2_reattack_mg(self); +} + +static mframe_t boss2_frames_stand[] = +{ + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL +}; +static mmove_t boss2_move_stand = { FRAME_stand30, FRAME_stand50, boss2_frames_stand, NULL }; + +static mframe_t boss2_frames_walk[] = +{ + drone_ai_walk, 10, NULL, + drone_ai_walk, 10, NULL, + drone_ai_walk, 10, NULL, + drone_ai_walk, 10, NULL, + drone_ai_walk, 10, NULL, + drone_ai_walk, 10, NULL, + drone_ai_walk, 10, NULL, + drone_ai_walk, 10, NULL, + drone_ai_walk, 10, NULL, + drone_ai_walk, 10, NULL, + drone_ai_walk, 10, NULL, + drone_ai_walk, 10, NULL, + drone_ai_walk, 10, NULL, + drone_ai_walk, 10, NULL, + drone_ai_walk, 10, NULL, + drone_ai_walk, 10, NULL, + drone_ai_walk, 10, NULL, + drone_ai_walk, 10, NULL, + drone_ai_walk, 10, NULL, + drone_ai_walk, 10, NULL +}; +static mmove_t boss2_move_walk = { FRAME_walk1, FRAME_walk20, boss2_frames_walk, NULL }; + +static mframe_t boss2_frames_run[] = +{ + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL +}; +static mmove_t boss2_move_run = { FRAME_walk1, FRAME_walk20, boss2_frames_run, NULL }; + +static mframe_t boss2_frames_attack_pre_mg[] = +{ + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, boss2_attack_mg +}; +static mmove_t boss2_move_attack_pre_mg = { FRAME_attack1, FRAME_attack9, boss2_frames_attack_pre_mg, NULL }; + +static mframe_t boss2_frames_attack_mg[] = +{ + ai_charge, 2, Boss2MachineGun, + ai_charge, 2, Boss2MachineGun, + ai_charge, 2, Boss2MachineGun, + ai_charge, 2, Boss2MachineGun, + ai_charge, 2, Boss2MachineGun, + ai_charge, 2, boss2_reattack_mg +}; +static mmove_t boss2_move_attack_mg = { FRAME_attack10, FRAME_attack15, boss2_frames_attack_mg, NULL }; + +static mframe_t boss2_frames_attack_hb[] = +{ + ai_charge, 2, Boss2HyperBlaster, + ai_charge, 2, Boss2HyperBlaster, + ai_charge, 2, Boss2HyperBlaster, + ai_charge, 2, Boss2HyperBlaster, + ai_charge, 2, Boss2HyperBlaster, + ai_charge, 2, Boss2HyperBlasterReattack +}; +static mmove_t boss2_move_attack_hb = { FRAME_attack10, FRAME_attack15, boss2_frames_attack_hb, NULL }; + +static mframe_t boss2_frames_attack_post_mg[] = +{ + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL +}; +static mmove_t boss2_move_attack_post_mg = { FRAME_attack16, FRAME_attack19, boss2_frames_attack_post_mg, boss2_run }; + +static mframe_t boss2_frames_attack_rocket[] = +{ + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_move, -5, Boss2Rocket, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL +}; +static mmove_t boss2_move_attack_rocket = { FRAME_attack20, FRAME_attack40, boss2_frames_attack_rocket, boss2_run }; + +static mframe_t boss2_frames_attack_rocket2[] = +{ + ai_charge, 2, Boss2Rocket64, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, Boss2Rocket64, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, Boss2Rocket64, + ai_charge, 2, NULL, + ai_charge, 2, Boss2Rocket64, + ai_charge, 2, NULL, + ai_charge, 2, Boss2Rocket64, + ai_charge, 2, Boss2Rocket64, + ai_charge, 2, Boss2Rocket64, + ai_charge, 2, Boss2Rocket64, + ai_charge, 2, Boss2Rocket64, + ai_charge, 2, Boss2Rocket64, + ai_charge, 2, Boss2Rocket64, + ai_charge, 2, Boss2Rocket64 +}; +static mmove_t boss2_move_attack_rocket2 = { FRAME_attack20, FRAME_attack39, boss2_frames_attack_rocket2, boss2_run }; + +static mframe_t boss2_frames_pain_heavy[] = +{ + ai_move, -2, NULL, + ai_move, -2, NULL, + ai_move, -2, NULL, + ai_move, -2, NULL, + ai_move, -2, NULL, + ai_move, -2, NULL, + ai_move, -2, NULL, + ai_move, -2, NULL, + ai_move, -2, NULL, + ai_move, -2, NULL, + ai_move, -2, NULL, + ai_move, -2, NULL, + ai_move, -2, NULL, + ai_move, -2, NULL, + ai_move, -2, NULL, + ai_move, -2, NULL, + ai_move, -2, NULL, + ai_move, -2, NULL +}; +static mmove_t boss2_move_pain_heavy = { FRAME_pain2, FRAME_pain19, boss2_frames_pain_heavy, boss2_run }; + +static mframe_t boss2_frames_pain_light[] = +{ + ai_move, -2, NULL, + ai_move, -2, NULL, + ai_move, -2, NULL, + ai_move, -2, NULL +}; +static mmove_t boss2_move_pain_light = { FRAME_pain20, FRAME_pain23, boss2_frames_pain_light, boss2_run }; + +static void boss2_shrink(edict_t *self) +{ + self->maxs[2] = boss2_is_small(self) ? 30 : 50; + gi.linkentity(self); +} + +static mframe_t boss2_frames_death[] = +{ + ai_move, 0, boss2_explode, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, boss2_shrink, + ai_move, 0, NULL, +}; +static mmove_t boss2_move_death = { FRAME_death2, FRAME_death10, boss2_frames_death, boss2_dead }; + +static mframe_t boss2_frames_deathboss[] = +{ + ai_move, 0, boss2_explode, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, boss2_shrink, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +static mmove_t boss2_move_deathboss = { FRAME_death2, FRAME_death50, boss2_frames_deathboss, boss2_dead }; + +static void boss2_stand(edict_t *self) +{ + self->monsterinfo.currentmove = &boss2_move_stand; +} + +static void boss2_run(edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.currentmove = &boss2_move_stand; + else + self->monsterinfo.currentmove = &boss2_move_run; +} + +static void boss2_walk(edict_t *self) +{ + self->monsterinfo.currentmove = &boss2_move_walk; +} + +static void boss2_attack(edict_t *self) +{ + vec3_t delta; + float range; + + if (!G_ValidTarget(self, self->enemy, false, true)) + return; + + VectorSubtract(self->enemy->s.origin, self->s.origin, delta); + range = VectorLength(delta); + + if (range <= 125 || random() <= 0.6f) + self->monsterinfo.currentmove = boss2_is_hyper(self) ? &boss2_move_attack_hb : &boss2_move_attack_pre_mg; + else + self->monsterinfo.currentmove = boss2_is_hyper(self) ? &boss2_move_attack_rocket2 : &boss2_move_attack_rocket; + + M_DelayNextAttack(self, 1.0f + random() * 0.5f, true); +} + +static void boss2_attack_mg(edict_t *self) +{ + self->monsterinfo.currentmove = boss2_is_hyper(self) ? &boss2_move_attack_hb : &boss2_move_attack_mg; +} + +static void boss2_reattack_mg(edict_t *self) +{ + if (G_ValidTarget(self, self->enemy, false, true) && infront(self, self->enemy) && random() <= 0.7f) + boss2_attack_mg(self); + else + self->monsterinfo.currentmove = &boss2_move_attack_post_mg; +} + +static void boss2_pain(edict_t *self, edict_t *other, float kick, int damage) +{ + (void)other; + (void)kick; + + if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3.0f; + if (damage < 10) + gi.sound(self, CHAN_VOICE, sound_pain3, 1, ATTN_NORM, 0); + else if (damage < 30) + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + + if (skill->value == 3) + return; + if (damage < 30) + self->monsterinfo.currentmove = &boss2_move_pain_light; + else + self->monsterinfo.currentmove = &boss2_move_pain_heavy; +} + +static void boss2_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + (void)inflictor; + (void)attacker; + (void)damage; + (void)point; + + M_Notify(self); + + if (self->deadflag == DEAD_DEAD) + return; + + DroneList_Remove(self); + gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + self->s.sound = 0; + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + self->count = 0; + self->monsterinfo.currentmove = boss2_is_small(self) ? &boss2_move_death : &boss2_move_deathboss; +} + +static void init_drone_boss2_common(edict_t *self, int variant) +{ + qboolean hyper = (variant == BOSS2_VARIANT_HYPER || variant == BOSS2_VARIANT_SMALL); + qboolean small = (variant == BOSS2_VARIANT_SMALL); + + sound_pain1 = gi.soundindex("bosshovr/bhvpain1.wav"); + sound_pain2 = gi.soundindex("bosshovr/bhvpain2.wav"); + sound_pain3 = gi.soundindex("bosshovr/bhvpain3.wav"); + sound_death = gi.soundindex("bosshovr/bhvdeth1.wav"); + sound_search1 = gi.soundindex("bosshovr/bhvunqv1.wav"); + gi.soundindex("tank/rocket.wav"); + gi.soundindex(hyper ? "flyer/flyatck3.wav" : "infantry/infatck1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/boss2/tris.md2"); + if (small) + { + VectorSet(self->mins, -34, -34, 0); + VectorSet(self->maxs, 34, 34, 48); + } + else + { + VectorSet(self->mins, -56, -56, 0); + VectorSet(self->maxs, 56, 56, 80); + } + + gi.modelindex("models/monsters/boss2/gibs/chaingun.md2"); + gi.modelindex("models/monsters/boss2/gibs/chest.md2"); + gi.modelindex("models/monsters/boss2/gibs/cpu.md2"); + gi.modelindex("models/monsters/boss2/gibs/engine.md2"); + gi.modelindex("models/monsters/boss2/gibs/head.md2"); + gi.modelindex("models/monsters/boss2/gibs/larm.md2"); + gi.modelindex("models/monsters/boss2/gibs/rarm.md2"); + gi.modelindex("models/monsters/boss2/gibs/rocket.md2"); + gi.modelindex("models/monsters/boss2/gibs/spine.md2"); + gi.modelindex("models/monsters/boss2/gibs/wing.md2"); + + if (small) + self->health = M_BOSS2_SMALL_INITIAL_HEALTH + M_BOSS2_SMALL_ADDON_HEALTH * self->monsterinfo.level; + else + self->health = M_BOSS2_INITIAL_HEALTH + M_BOSS2_ADDON_HEALTH * self->monsterinfo.level; + self->max_health = self->health; + self->gib_health = -200; + self->mass = small ? 1000 : 2000; + self->mtype = small ? M_BOSS2_SMALL : M_BOSS2; + self->style = variant; + self->yaw_speed = small ? 80 : 50; + self->flags |= FL_FLY | FL_IMMUNE_LASER; + self->s.sound = gi.soundindex("bosshovr/bhvengn1.wav"); + self->s.scale = small ? 0.6f : 1.0f; + + self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD; + if (small) + self->monsterinfo.power_armor_power = M_BOSS2_SMALL_INITIAL_ARMOR + M_BOSS2_SMALL_ADDON_ARMOR * self->monsterinfo.level; + else + self->monsterinfo.power_armor_power = M_BOSS2_INITIAL_ARMOR + M_BOSS2_ADDON_ARMOR * self->monsterinfo.level; + self->monsterinfo.max_armor = self->monsterinfo.power_armor_power; + self->monsterinfo.control_cost = small ? M_HOVER_CONTROL_COST : M_JORG_CONTROL_COST; + self->monsterinfo.cost = small ? M_HOVER_COST : M_COMMANDER_COST; + self->monsterinfo.jumpup = 64; + self->monsterinfo.jumpdn = 512; + self->monsterinfo.sight_range = 1024; + self->monsterinfo.aiflags |= AI_NO_CIRCLE_STRAFE; + + self->pain = boss2_pain; + self->die = boss2_die; + self->monsterinfo.stand = boss2_stand; + self->monsterinfo.walk = boss2_walk; + self->monsterinfo.run = boss2_run; + self->monsterinfo.attack = boss2_attack; + self->monsterinfo.idle = boss2_search; + + gi.linkentity(self); + + self->monsterinfo.currentmove = &boss2_move_stand; + self->monsterinfo.scale = small ? MODEL_SCALE * 0.6f : MODEL_SCALE; + self->nextthink = level.time + FRAMETIME; + + if (!small) + G_PrintGreenText(va("A level %d hornet%s has spawned!", self->monsterinfo.level, hyper ? " hyper" : "")); +} + +void init_drone_boss2(edict_t *self) +{ + init_drone_boss2_common(self, BOSS2_VARIANT_MG); +} + +void init_drone_boss2_hyper(edict_t *self) +{ + init_drone_boss2_common(self, BOSS2_VARIANT_HYPER); +} + +void init_drone_boss2_small(edict_t *self) +{ + init_drone_boss2_common(self, BOSS2_VARIANT_SMALL); +} diff --git a/src/entities/drone/drone_misc.c b/src/entities/drone/drone_misc.c index 122414b1..3f79262d 100644 --- a/src/entities/drone/drone_misc.c +++ b/src/entities/drone/drone_misc.c @@ -49,6 +49,9 @@ void init_drone_widow2(edict_t* self); void init_drone_fixbot(edict_t* self); void init_drone_fixbot_boss(edict_t* self); void init_drone_rogue_turret(edict_t* self); +void init_drone_boss2(edict_t* self); +void init_drone_boss2_hyper(edict_t* self); +void init_drone_boss2_small(edict_t* self); void init_baron_fire(edict_t* self); void init_skeleton(edict_t* self); void init_golem(edict_t* self); @@ -536,7 +539,7 @@ void drone_death (edict_t *self, edict_t *attacker) //4.2 bosses can drop up to 4 runes - if (self->mtype == M_COMMANDER || self->mtype == M_SUPERTANK || self->mtype == M_MAKRON || self->mtype == M_CARRIER || self->mtype == M_WIDOW || self->mtype == M_WIDOW2 || self->mtype == M_FIXBOT_BOSS || self->mtype == M_GUARDIAN) + if (self->mtype == M_COMMANDER || self->mtype == M_SUPERTANK || self->mtype == M_MAKRON || self->mtype == M_BOSS2 || self->mtype == M_CARRIER || self->mtype == M_WIDOW || self->mtype == M_WIDOW2 || self->mtype == M_FIXBOT_BOSS || self->mtype == M_GUARDIAN) { edict_t *e; float drop_chance = 0.25; @@ -786,6 +789,8 @@ static qboolean vrx_drone_spawn_is_boss(enum dronespawn_t drone_type) case DS_WIDOW2: case DS_FIXBOT_BOSS: case DS_GUARDIAN: + case DS_BOSS2: + case DS_BOSS2_HYPER: return true; default: return false; @@ -908,6 +913,7 @@ edict_t *vrx_create_drone_from_ent(edict_t *drone, edict_t *ent, enum dronespawn case DS_MEDIC_COMMANDER: init_drone_medic_commander(drone); break; case DS_FIXBOT: init_drone_fixbot(drone); break; case DS_ROGUE_TURRET: init_drone_rogue_turret(drone); break; + case DS_BOSS2_SMALL: init_drone_boss2_small(drone); break; // bosses case DS_COMMANDER: init_drone_commander(drone); break; @@ -920,6 +926,8 @@ edict_t *vrx_create_drone_from_ent(edict_t *drone, edict_t *ent, enum dronespawn case DS_WIDOW2: init_drone_widow2(drone); break; case DS_FIXBOT_BOSS: init_drone_fixbot_boss(drone); break; case DS_GUARDIAN: init_drone_guardian(drone); break; + case DS_BOSS2: init_drone_boss2(drone); break; + case DS_BOSS2_HYPER: init_drone_boss2_hyper(drone); break; case DS_JANITOR: drone->mtype = M_JANITOR; init_drone_supertank(drone); break; case DS_MINIGUARDIAN: drone->mtype = M_MINIGUARDIAN; init_drone_guardian(drone); break; @@ -941,6 +949,7 @@ edict_t *vrx_create_drone_from_ent(edict_t *drone, edict_t *ent, enum dronespawn drone_type == DS_MINIGUARDIAN || drone_type == DS_FIXBOT || drone_type == DS_ROGUE_TURRET || + drone_type == DS_BOSS2_SMALL || drone_type == DS_SOLDIER_RIPPER || drone_type == DS_SOLDIER_BLUEBLASTER || drone_type == DS_SOLDIER_LASER) @@ -2127,6 +2136,8 @@ qboolean M_Initialize (edict_t *ent, edict_t *monster, float dur_bonus) case M_STALKER: init_drone_stalker(monster); break; case M_GEKK: init_drone_gekk(monster); break; case M_ARACHNID: init_drone_arachnid(monster); break; + case M_BOSS2: init_drone_boss2(monster); break; + case M_BOSS2_SMALL: init_drone_boss2_small(monster); break; case M_CARRIER: init_drone_carrier(monster); break; case M_WIDOW: init_drone_widow(monster); break; case M_WIDOW2: init_drone_widow2(monster); break; @@ -2141,7 +2152,7 @@ qboolean M_Initialize (edict_t *ent, edict_t *monster, float dur_bonus) } #ifdef VRX_REPRO - if (monster->s.scale && monster->mtype != M_GUNCMDR) + if (monster->s.scale && monster->mtype != M_GUNCMDR && monster->mtype != M_BOSS2_SMALL) { monster->monsterinfo.scale *= monster->s.scale; VectorScale(monster->mins, monster->s.scale, monster->mins); @@ -2266,6 +2277,14 @@ qboolean M_SetBoundingBox (int mtype, vec3_t boxmin, vec3_t boxmax) VectorSet(boxmin, -48, -48, -20); VectorSet(boxmax, 48, 48, 48); break; + case M_BOSS2: + VectorSet(boxmin, -56, -56, 0); + VectorSet(boxmax, 56, 56, 80); + break; + case M_BOSS2_SMALL: + VectorSet(boxmin, -34, -34, 0); + VectorSet(boxmax, 34, 34, 48); + break; case M_CARRIER: VectorSet(boxmin, -80, -80, -24); VectorSet(boxmax, 80, 80, 104); @@ -2385,6 +2404,8 @@ char *GetMonsterKindString (int mtype) case M_STALKER: return "Stalker"; case M_GEKK: return "Gekk"; case M_ARACHNID: return "Arachnid"; + case M_BOSS2: return "Hornet"; + case M_BOSS2_SMALL: return "Mini Hornet"; case M_CARRIER: return "Carrier"; case M_WIDOW: return "Widow"; case M_WIDOW2: return "Black Widow"; @@ -2978,7 +2999,7 @@ void Cmd_Drone_f (edict_t *ent) if (!Q_strcasecmp(s, "help")) { safe_cprintf(ent, PRINT_HIGH, "Monster summoning:\n"); - safe_cprintf(ent, PRINT_HIGH, "monster [gunner|parasite|brain|praetor|praetor_heat|medic|tank|mutant|gladiator|gladb|gladc|berserker|soldier|ripper|hyper|laser|janitor|janitor2|enforcer|flyer|floater|hover|fixbot|daedalus|stalker|gekk|arachnid|shambler|redmutant|runnertank|guncmdr]\n"); + safe_cprintf(ent, PRINT_HIGH, "monster [gunner|parasite|brain|praetor|praetor_heat|medic|tank|mutant|gladiator|gladb|gladc|berserker|soldier|ripper|hyper|laser|janitor|janitor2|enforcer|flyer|floater|hover|fixbot|hornet|daedalus|stalker|gekk|arachnid|shambler|redmutant|runnertank|guncmdr]\n"); safe_cprintf(ent, PRINT_HIGH, "Monster utility commands:\n"); safe_cprintf(ent, PRINT_HIGH, "monster [remove|command|follow me|count|attack]\n"); return; @@ -3042,6 +3063,9 @@ void Cmd_Drone_f (edict_t *ent) vrx_create_new_drone(ent, DS_HOVER, false, true, 0); else if (!Q_strcasecmp(s, "fixbot")) vrx_create_new_drone(ent, DS_FIXBOT, false, true, 0); + else if (!Q_strcasecmp(s, "hornet") || !Q_strcasecmp(s, "mini_hornet") + || !Q_strcasecmp(s, "minihornet") || !Q_strcasecmp(s, "boss2_small")) + vrx_create_new_drone(ent, DS_BOSS2_SMALL, false, true, 0); else if (!Q_strcasecmp(s, "shambler")) vrx_create_new_drone(ent, DS_SHAMBLER, false, true, 0); else if (!Q_strcasecmp(s, "redmutant")) diff --git a/src/g_local.h b/src/g_local.h index 3e07bd37..9960cf9b 100644 --- a/src/g_local.h +++ b/src/g_local.h @@ -1535,6 +1535,7 @@ enum mtype_t { M_FIXBOT = 48, M_FIXBOT_BOSS = 49, M_ROGUE_TURRET = 50, + M_BOSS2_SMALL = 51, M_MINISENTRY = 100, M_SENTRY = 101, M_BFG_SENTRY = 102, @@ -1661,6 +1662,9 @@ enum dronespawn_t { DS_FIXBOT = 44, DS_FIXBOT_BOSS = 45, DS_ROGUE_TURRET = 46, + DS_BOSS2 = 47, + DS_BOSS2_HYPER = 48, + DS_BOSS2_SMALL = 49, }; diff --git a/src/gamemodes/invasion.c b/src/gamemodes/invasion.c index 4e1cbad2..8f3e40be 100644 --- a/src/gamemodes/invasion.c +++ b/src/gamemodes/invasion.c @@ -42,6 +42,7 @@ static constexpr int SET_EASY_MODE_MONSTERS[] = { DS_RUNNERTANK, DS_GUNCMDR, DS_DAEDALUS, + DS_BOSS2_SMALL, DS_STALKER, DS_GEKK, DS_ARACHNID, @@ -53,12 +54,12 @@ constexpr int SET_EASY_MODE_MONSTERS_COUNT = sizeof(SET_EASY_MODE_MONSTERS) / si // parasite, brain, medic, tank, mutant, gladiator, berserker, infantry, hover static constexpr int SET_HARD_MODE_MONSTERS[] = { DS_PARASITE, DS_BRAIN, DS_MEDIC, DS_TANK, DS_MUTANT, DS_GLADIATOR, DS_BERSERK, DS_INFANTRY, DS_HOVER, - DS_REDMUTANT, DS_RUNNERTANK, DS_GUNCMDR, DS_DAEDALUS, DS_GLADB, DS_GLADC, DS_STALKER, DS_GEKK, DS_ARACHNID, DS_BITCH_HEAT + DS_REDMUTANT, DS_RUNNERTANK, DS_GUNCMDR, DS_DAEDALUS, DS_BOSS2_SMALL, DS_GLADB, DS_GLADC, DS_STALKER, DS_GEKK, DS_ARACHNID, DS_BITCH_HEAT }; constexpr int SET_HARD_MODE_MONSTERS_COUNT = sizeof(SET_HARD_MODE_MONSTERS) / sizeof(int); static constexpr int SET_FLYING_MONSTERS[] = { - DS_FLYER, DS_HOVER, DS_FLOATER, DS_DAEDALUS + DS_FLYER, DS_HOVER, DS_FLOATER, DS_DAEDALUS, DS_BOSS2_SMALL }; constexpr int SET_FLYING_MONSTERS_COUNT = sizeof(SET_FLYING_MONSTERS) / sizeof(int); diff --git a/src/quake2/g_layout.c b/src/quake2/g_layout.c index 1fbc7988..4b4fd711 100644 --- a/src/quake2/g_layout.c +++ b/src/quake2/g_layout.c @@ -334,6 +334,8 @@ sidebar_entry_t layout_add_entity_info(sidebar_t* sidebar, edict_t* ent) case M_STALKER: case M_GEKK: case M_ARACHNID: + case M_BOSS2: + case M_BOSS2_SMALL: case M_CARRIER: case M_WIDOW: case M_WIDOW2: diff --git a/src/quake2/g_svcmds.c b/src/quake2/g_svcmds.c index 504f4fae..3e185dca 100644 --- a/src/quake2/g_svcmds.c +++ b/src/quake2/g_svcmds.c @@ -644,6 +644,21 @@ void SVCmd_SpawnBoss_f (void) else vrx_create_new_drone(m_worldspawn, 34, true, true, 0); } + else if (!strcmp(gi.argv(2), "boss2") || !strcmp(gi.argv(2), "hornet") || !strcmp(gi.argv(2), "boss2mg")) + { + if (invasion->value) + vrx_inv_spawn_boss(m_worldspawn, DS_BOSS2); + else + vrx_create_new_drone(m_worldspawn, DS_BOSS2, true, true, 0); + } + else if (!strcmp(gi.argv(2), "boss2_hyper") || !strcmp(gi.argv(2), "boss2hyper") || !strcmp(gi.argv(2), "boss2_n64") + || !strcmp(gi.argv(2), "boss2n64") || !strcmp(gi.argv(2), "hornethb") || !strcmp(gi.argv(2), "hornet_hyper")) + { + if (invasion->value) + vrx_inv_spawn_boss(m_worldspawn, DS_BOSS2_HYPER); + else + vrx_create_new_drone(m_worldspawn, DS_BOSS2_HYPER, true, true, 0); + } else if (!strcmp(gi.argv(2), "carrier")) { if (invasion->value) @@ -680,7 +695,7 @@ void SVCmd_SpawnBoss_f (void) vrx_create_new_drone(m_worldspawn, DS_GUARDIAN, true, true, 0); } else - safe_cprintf(NULL, PRINT_HIGH, "Invalid boss type. Usage: sv spawnboss \n"); + safe_cprintf(NULL, PRINT_HIGH, "Invalid boss type. Usage: sv spawnboss \n"); } void SVCmd_MakeBoss_f (void) diff --git a/src/server/v_luasettings.c b/src/server/v_luasettings.c index 0de80800..3e46a17e 100644 --- a/src/server/v_luasettings.c +++ b/src/server/v_luasettings.c @@ -1162,6 +1162,14 @@ void Lua_LoadVariables() M_ROGUE_TURRET_ADDON_HEALTH = vrx_lua_get_variable("M_ROGUE_TURRET_ADDON_HEALTH", 25); M_ROGUE_TURRET_INITIAL_ARMOR = vrx_lua_get_variable("M_ROGUE_TURRET_INITIAL_ARMOR", 50); M_ROGUE_TURRET_ADDON_ARMOR = vrx_lua_get_variable("M_ROGUE_TURRET_ADDON_ARMOR", 15); + M_BOSS2_INITIAL_HEALTH = vrx_lua_get_variable("M_BOSS2_INITIAL_HEALTH", 2400); + M_BOSS2_ADDON_HEALTH = vrx_lua_get_variable("M_BOSS2_ADDON_HEALTH", 700); + M_BOSS2_INITIAL_ARMOR = vrx_lua_get_variable("M_BOSS2_INITIAL_ARMOR", 600); + M_BOSS2_ADDON_ARMOR = vrx_lua_get_variable("M_BOSS2_ADDON_ARMOR", 350); + M_BOSS2_SMALL_INITIAL_HEALTH = vrx_lua_get_variable("M_BOSS2_SMALL_INITIAL_HEALTH", 250); + M_BOSS2_SMALL_ADDON_HEALTH = vrx_lua_get_variable("M_BOSS2_SMALL_ADDON_HEALTH", 100); + M_BOSS2_SMALL_INITIAL_ARMOR = vrx_lua_get_variable("M_BOSS2_SMALL_INITIAL_ARMOR", 160); + M_BOSS2_SMALL_ADDON_ARMOR = vrx_lua_get_variable("M_BOSS2_SMALL_ADDON_ARMOR", 70); M_GUARDIAN_INITIAL_HEALTH = vrx_lua_get_variable("M_GUARDIAN_INITIAL_HEALTH", 6500); M_GUARDIAN_ADDON_HEALTH = vrx_lua_get_variable("M_GUARDIAN_ADDON_HEALTH", 1200); M_GUARDIAN_INITIAL_ARMOR = vrx_lua_get_variable("M_GUARDIAN_INITIAL_ARMOR", 550); From 3d47f73a1337665b843f2666e446b626428a7e67 Mon Sep 17 00:00:00 2001 From: Enemy-123 Date: Sat, 25 Apr 2026 18:43:20 -0400 Subject: [PATCH 17/24] added supertank variant "boss5" --- src/characters/v_utils.c | 2 ++ src/combat/common/damage.c | 2 +- src/combat/common/v_misc.c | 2 ++ src/entities/drone/drone_ai.c | 4 ++-- src/entities/drone/drone_misc.c | 16 +++++++++++++-- src/entities/drone/drone_supertank.c | 29 ++++++++++++++++++++++++++-- src/entities/g_spawn.c | 7 +++++++ src/g_local.h | 2 ++ src/quake2/g_layout.c | 2 ++ src/quake2/g_svcmds.c | 9 ++++++++- 10 files changed, 67 insertions(+), 8 deletions(-) diff --git a/src/characters/v_utils.c b/src/characters/v_utils.c index b0879e91..bee94cb3 100644 --- a/src/characters/v_utils.c +++ b/src/characters/v_utils.c @@ -2087,6 +2087,8 @@ char *V_GetMonsterKind(int mtype) { return "tank"; case M_SUPERTANK: return "supertank"; + case M_BOSS5: + return "supertank heat"; case M_SHAMBLER: return "shambler"; case M_REDMUTANT: diff --git a/src/combat/common/damage.c b/src/combat/common/damage.c index c8f45878..f8043ab8 100644 --- a/src/combat/common/damage.c +++ b/src/combat/common/damage.c @@ -150,7 +150,7 @@ qboolean IsMorphedPlayer(const edict_t *ent) { } qboolean IsMonster(const edict_t* ent) { - return (ent->mtype && (ent->mtype <= M_TANK || ent->mtype == M_SHAMBLER + return (ent->mtype && (ent->mtype <= M_TANK || ent->mtype == M_SUPERTANK || ent->mtype == M_BOSS5 || ent->mtype == M_SHAMBLER || ent->mtype == M_REDMUTANT || ent->mtype == M_RUNNERTANK || ent->mtype == M_GUNCMDR || ent->mtype == M_DAEDALUS || ent->mtype == M_GLADB || ent->mtype == M_GLADC || ent->mtype == M_STALKER || ent->mtype == M_GEKK || ent->mtype == M_ARACHNID diff --git a/src/combat/common/v_misc.c b/src/combat/common/v_misc.c index 1d5f680e..d444d61b 100644 --- a/src/combat/common/v_misc.c +++ b/src/combat/common/v_misc.c @@ -699,6 +699,7 @@ int vrx_GetMonsterCost(int mtype) { cost = M_TANK_COST; break; case M_SUPERTANK: + case M_BOSS5: cost = M_SUPERTANK_COST; break; case M_COMMANDER: @@ -798,6 +799,7 @@ int vrx_GetMonsterControlCost(int mtype) { cost = M_HOVER_CONTROL_COST; break; case M_SUPERTANK: + case M_BOSS5: cost = M_SUPERTANK_CONTROL_COST; break; case M_COMMANDER: diff --git a/src/entities/drone/drone_ai.c b/src/entities/drone/drone_ai.c index 467d6d77..9c64e6b0 100644 --- a/src/entities/drone/drone_ai.c +++ b/src/entities/drone/drone_ai.c @@ -707,7 +707,7 @@ void drone_ai_idle (edict_t *self) && self->mtype != M_CHICK_HEAT && self->mtype != M_MEDIC_COMMANDER && self->mtype != M_SOLDIER && self->mtype != M_SOLDIER_RIPPER && self->mtype != M_SOLDIER_BLUEBLASTER && self->mtype != M_SOLDIER_LASER - && self->mtype != M_STALKER) + && self->mtype != M_STALKER && self->mtype != M_BOSS5) self->s.skinnum &= ~2; } @@ -1913,7 +1913,7 @@ void drone_ai_run1 (edict_t *self, float dist) && self->mtype != M_CHICK_HEAT && self->mtype != M_MEDIC_COMMANDER && self->mtype != M_SOLDIER && self->mtype != M_SOLDIER_RIPPER && self->mtype != M_SOLDIER_BLUEBLASTER && self->mtype != M_SOLDIER_LASER - && self->mtype != M_STALKER) + && self->mtype != M_STALKER && self->mtype != M_BOSS5) self->s.skinnum &= ~2; } diff --git a/src/entities/drone/drone_misc.c b/src/entities/drone/drone_misc.c index 3f79262d..4c8ed730 100644 --- a/src/entities/drone/drone_misc.c +++ b/src/entities/drone/drone_misc.c @@ -23,6 +23,7 @@ void init_drone_mutant (edict_t *self); void init_drone_decoy (edict_t *self); void init_drone_commander (edict_t *self); void init_drone_supertank (edict_t *self); +void init_drone_boss5 (edict_t *self); void init_drone_jorg (edict_t *self); void init_drone_makron (edict_t *self); void init_drone_soldier (edict_t *self); @@ -539,7 +540,7 @@ void drone_death (edict_t *self, edict_t *attacker) //4.2 bosses can drop up to 4 runes - if (self->mtype == M_COMMANDER || self->mtype == M_SUPERTANK || self->mtype == M_MAKRON || self->mtype == M_BOSS2 || self->mtype == M_CARRIER || self->mtype == M_WIDOW || self->mtype == M_WIDOW2 || self->mtype == M_FIXBOT_BOSS || self->mtype == M_GUARDIAN) + if (self->mtype == M_COMMANDER || self->mtype == M_SUPERTANK || self->mtype == M_BOSS5 || self->mtype == M_MAKRON || self->mtype == M_BOSS2 || self->mtype == M_CARRIER || self->mtype == M_WIDOW || self->mtype == M_WIDOW2 || self->mtype == M_FIXBOT_BOSS || self->mtype == M_GUARDIAN) { edict_t *e; float drop_chance = 0.25; @@ -783,6 +784,7 @@ static qboolean vrx_drone_spawn_is_boss(enum dronespawn_t drone_type) case DS_MAKRON: case DS_BARON_FIRE: case DS_SUPERTANK: + case DS_BOSS5: case DS_JORG: case DS_CARRIER: case DS_WIDOW: @@ -920,6 +922,7 @@ edict_t *vrx_create_drone_from_ent(edict_t *drone, edict_t *ent, enum dronespawn case DS_MAKRON: init_drone_makron(drone); break; case DS_BARON_FIRE: init_baron_fire(drone); break; case DS_SUPERTANK: init_drone_supertank(drone); break; + case DS_BOSS5: init_drone_boss5(drone); break; case DS_JORG: init_drone_jorg(drone); break; case DS_CARRIER: init_drone_carrier(drone); break; case DS_WIDOW: init_drone_widow(drone); break; @@ -1829,7 +1832,7 @@ qboolean M_Regenerate (edict_t *self, int regen_frames, int delay, float mult, q && self->mtype != M_CHICK_HEAT && self->mtype != M_MEDIC_COMMANDER && self->mtype != M_SOLDIER && self->mtype != M_SOLDIER_RIPPER && self->mtype != M_SOLDIER_BLUEBLASTER && self->mtype != M_SOLDIER_LASER - && self->mtype != M_STALKER) + && self->mtype != M_STALKER && self->mtype != M_BOSS5) self->s.skinnum &= ~2; } @@ -2117,6 +2120,8 @@ qboolean M_Initialize (edict_t *ent, edict_t *monster, float dur_bonus) case M_MUTANT: init_drone_mutant(monster); break; case M_PARASITE: init_drone_parasite(monster); break; case M_TANK: init_drone_tank(monster); break; + case M_SUPERTANK: init_drone_supertank(monster); break; + case M_BOSS5: init_drone_boss5(monster); break; case M_BERSERK: init_drone_berserk(monster); break; case M_SOLDIER: case M_SOLDIERLT: case M_SOLDIERSS: case M_SOLDIER_RIPPER: case M_SOLDIER_BLUEBLASTER: case M_SOLDIER_LASER: @@ -2245,6 +2250,11 @@ qboolean M_SetBoundingBox (int mtype, vec3_t boxmin, vec3_t boxmax) VectorSet (boxmin, -24, -24, -16); VectorSet (boxmax, 24, 24, 64); break; + case M_SUPERTANK: + case M_BOSS5: + VectorSet(boxmin, -64, -64, 0); + VectorSet(boxmax, 64, 64, 112); + break; case M_SHAMBLER: VectorSet(boxmin, -32, -32, -24); VectorSet(boxmax, 32, 32, 64); @@ -2367,6 +2377,8 @@ char *GetMonsterKindString (int mtype) case M_SOLDIER_BLUEBLASTER: return "Hyper Guard"; case M_SOLDIER_LASER: return "Laser Guard"; case M_TANK: return "Tank"; + case M_SUPERTANK: return "Super Tank"; + case M_BOSS5: return "Super Tank Heat"; case M_GUNNER: return "Gunner"; case M_YANGSPIRIT: return "Yang Spirit"; case M_BALANCESPIRIT: return "Balance Spirit"; diff --git a/src/entities/drone/drone_supertank.c b/src/entities/drone/drone_supertank.c index 2535dbac..e763d3a9 100644 --- a/src/entities/drone/drone_supertank.c +++ b/src/entities/drone/drone_supertank.c @@ -18,6 +18,11 @@ static int tread_sound; void BossExplode (edict_t *self); +static qboolean supertank_is_boss5(const edict_t *self) +{ + return self->mtype == M_BOSS5; +} + void TreadSound (edict_t *self) { gi.sound (self, CHAN_VOICE, tread_sound, 1, ATTN_NORM, 0); @@ -459,6 +464,13 @@ void supertankRocket (edict_t *self) damage = 50 + 10 * drone_damagelevel(self); speed = 650 + 30 * drone_damagelevel(self); + if (supertank_is_boss5(self)) + { + MonsterAim(self, 0.5, speed, true, flash_number, forward, start); + monster_fire_heat(self, start, forward, damage, speed, flash_number, 0.075f); + return; + } + if (self->mtype == M_JANITOR) { AngleVectors(self->s.angles, forward, right, NULL); @@ -621,15 +633,18 @@ void supertank_sight (edict_t *self, edict_t *other) void init_drone_supertank (edict_t *self) { qboolean janitor = (self->mtype == M_JANITOR); + qboolean boss5 = supertank_is_boss5(self); sound_death = gi.soundindex ("bosstank/btkdeth1.wav"); sound_search1 = gi.soundindex ("bosstank/btkunqv1.wav"); sound_search2 = gi.soundindex ("bosstank/btkunqv2.wav"); tread_sound = gi.soundindex ("bosstank/btkengn1.wav"); + if (boss5) + gi.soundindex("weapons/railgr1a.wav"); self->movetype = MOVETYPE_STEP; self->solid = SOLID_BBOX; - if (!janitor) + if (!janitor && !boss5) self->mtype = M_SUPERTANK; self->monsterinfo.control_cost = janitor ? M_TANK_CONTROL_COST : M_SUPERTANK_CONTROL_COST; self->monsterinfo.cost = janitor ? 150 : 300; @@ -645,6 +660,8 @@ void init_drone_supertank (edict_t *self) } else { + if (boss5) + self->s.skinnum = 2; VectorSet (self->mins, -64, -64, 0); VectorSet (self->maxs, 64, 64, 112); self->health = self->max_health = 20000*self->monsterinfo.level; @@ -656,6 +673,8 @@ void init_drone_supertank (edict_t *self) self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD; if (janitor) self->monsterinfo.power_armor_power = M_JANITOR_INITIAL_ARMOR + M_JANITOR_ADDON_ARMOR * self->monsterinfo.level; + else if (boss5) + self->monsterinfo.power_armor_power = 400 * self->monsterinfo.level; else self->monsterinfo.power_armor_power = 0; self->monsterinfo.max_armor = self->monsterinfo.power_armor_power; @@ -675,5 +694,11 @@ void init_drone_supertank (edict_t *self) gi.linkentity (self); if (!janitor && (!self->activator || !self->activator->client)) - G_PrintGreenText(va("A level %d super tank has spawned!", self->monsterinfo.level)); + G_PrintGreenText(va("A level %d %s has spawned!", self->monsterinfo.level, boss5 ? "super tank heat" : "super tank")); +} + +void init_drone_boss5(edict_t *self) +{ + self->mtype = M_BOSS5; + init_drone_supertank(self); } diff --git a/src/entities/g_spawn.c b/src/entities/g_spawn.c index ed02cde6..cada7ac3 100644 --- a/src/entities/g_spawn.c +++ b/src/entities/g_spawn.c @@ -284,6 +284,7 @@ spawn_t spawns[] = { {"monster_soldier_lasergun", SP_monster_soldier_laser}, {"monster_janitor", SP_monster_janitor}, {"monster_miniguardian", SP_monster_miniguardian}, + {"monster_boss5", SP_monster_boss5}, {"monster_tank", SP_monster_tank}, {"monster_tank_commander", SP_monster_tank_commander}, {"monster_medic", SP_monster_medic}, @@ -1137,6 +1138,12 @@ void SP_monster_miniguardian(edict_t *ent) vrx_create_drone_from_ent(ent, g_edicts, DS_MINIGUARDIAN, true, true, 0); } +void SP_monster_boss5(edict_t *ent) +{ + if (coop->value) + vrx_create_drone_from_ent(ent, g_edicts, DS_BOSS5, true, true, 0); +} + void SP_monster_tank(edict_t *ent) { if (coop->value) diff --git a/src/g_local.h b/src/g_local.h index 9960cf9b..8d095755 100644 --- a/src/g_local.h +++ b/src/g_local.h @@ -1536,6 +1536,7 @@ enum mtype_t { M_FIXBOT_BOSS = 49, M_ROGUE_TURRET = 50, M_BOSS2_SMALL = 51, + M_BOSS5 = 52, M_MINISENTRY = 100, M_SENTRY = 101, M_BFG_SENTRY = 102, @@ -1665,6 +1666,7 @@ enum dronespawn_t { DS_BOSS2 = 47, DS_BOSS2_HYPER = 48, DS_BOSS2_SMALL = 49, + DS_BOSS5 = 50, }; diff --git a/src/quake2/g_layout.c b/src/quake2/g_layout.c index 4b4fd711..6bfc1014 100644 --- a/src/quake2/g_layout.c +++ b/src/quake2/g_layout.c @@ -322,6 +322,8 @@ sidebar_entry_t layout_add_entity_info(sidebar_t* sidebar, edict_t* ent) case M_BRAIN: case M_GLADIATOR: case M_TANK: + case M_SUPERTANK: + case M_BOSS5: case M_FORCEWALL: case M_BARON_FIRE: case M_SHAMBLER: diff --git a/src/quake2/g_svcmds.c b/src/quake2/g_svcmds.c index 3e185dca..762af2e3 100644 --- a/src/quake2/g_svcmds.c +++ b/src/quake2/g_svcmds.c @@ -637,6 +637,13 @@ void SVCmd_SpawnBoss_f (void) else vrx_create_new_drone(m_worldspawn, 33, true, true, 0); } + else if (!strcmp(gi.argv(2), "boss5") || !strcmp(gi.argv(2), "supertank_heat")) + { + if (invasion->value) + vrx_inv_spawn_boss(m_worldspawn, DS_BOSS5); + else + vrx_create_new_drone(m_worldspawn, DS_BOSS5, true, true, 0); + } else if (!strcmp(gi.argv(2), "jorg")) { if (invasion->value) @@ -695,7 +702,7 @@ void SVCmd_SpawnBoss_f (void) vrx_create_new_drone(m_worldspawn, DS_GUARDIAN, true, true, 0); } else - safe_cprintf(NULL, PRINT_HIGH, "Invalid boss type. Usage: sv spawnboss \n"); + safe_cprintf(NULL, PRINT_HIGH, "Invalid boss type. Usage: sv spawnboss \n"); } void SVCmd_MakeBoss_f (void) From 2c3ab2900d321ea213775ed4097e64b335e4c008 Mon Sep 17 00:00:00 2001 From: Enemy-123 Date: Sun, 26 Apr 2026 04:36:17 -0400 Subject: [PATCH 18/24] Scaling bosses for invasion, improving muzzleflashes, deleting extra header guncmdr --- src/combat/weapons/g_weapon.c | 4 +- src/entities/drone/drone_arachnid.c | 18 +- src/entities/drone/drone_boss2.c | 115 +-- src/entities/drone/drone_carrier.c | 101 ++- src/entities/drone/drone_fixbot.c | 41 +- src/entities/drone/drone_guardian.c | 138 ++-- src/entities/drone/drone_guncmdr.c | 57 +- src/entities/drone/drone_gunner.c | 115 ++- src/entities/drone/drone_jorg.c | 3 +- src/entities/drone/drone_makron.c | 52 +- src/entities/drone/drone_medic.c | 57 +- src/entities/drone/drone_misc.c | 104 ++- src/entities/drone/drone_runnertank.c | 26 +- src/entities/drone/drone_supertank.c | 118 ++- src/entities/drone/drone_widow.c | 29 +- src/entities/drone/drone_widow2.c | 76 +- src/entities/drone/g_monster.c | 103 +-- src/gamemodes/invasion.c | 11 +- src/quake2/monsterframes/m_guncmdr.h | 809 -------------------- src/quake2/monsterframes/m_gunner.h | 1020 ++++++++++++++++++++----- src/quake2/monsterframes/m_mutant.h | 7 + src/quake2/monsterframes/m_parasite.h | 10 + 22 files changed, 1649 insertions(+), 1365 deletions(-) delete mode 100644 src/quake2/monsterframes/m_guncmdr.h diff --git a/src/combat/weapons/g_weapon.c b/src/combat/weapons/g_weapon.c index 370029eb..99469c36 100644 --- a/src/combat/weapons/g_weapon.c +++ b/src/combat/weapons/g_weapon.c @@ -61,8 +61,8 @@ void check_dodge (edict_t *self, vec3_t start, vec3_t dir, int speed, int radius tr.ent->monsterinfo.eta = level.time + eta; //gi.dprintf("ETA for impact is %.1f\n", tr.ent->monsterinfo.eta); tr.ent->monsterinfo.attacker = self; - VectorCopy(start, tr.ent->monsterinfo.dir); - tr.ent->monsterinfo.radius = 0; + VectorCopy(tr.endpos, tr.ent->monsterinfo.dir); + tr.ent->monsterinfo.radius = radius; } else diff --git a/src/entities/drone/drone_arachnid.c b/src/entities/drone/drone_arachnid.c index 45598acf..5eea7f54 100644 --- a/src/entities/drone/drone_arachnid.c +++ b/src/entities/drone/drone_arachnid.c @@ -17,6 +17,9 @@ static int sound_charge; static int sound_melee; static int sound_melee_hit; +#define ARACHNID_DEFAULT_SCALE 0.75f +#define ARACHNID_INVASION_SCALE 0.60f + static void arachnid_stand(edict_t *self); static void arachnid_run(edict_t *self); @@ -379,15 +382,24 @@ void init_drone_arachnid(edict_t *self) self->movetype = MOVETYPE_STEP; self->solid = SOLID_BBOX; self->s.modelindex = gi.modelindex("models/monsters/arachnid/tris.md2"); - VectorSet(self->mins, -36, -36, -18); - VectorSet(self->maxs, 36, 36, 42); + if (invasion->value) + { + VectorSet(self->mins, -28, -28, -18); + VectorSet(self->maxs, 28, 28, 34); + self->s.scale = ARACHNID_INVASION_SCALE; + } + else + { + VectorSet(self->mins, -36, -36, -18); + VectorSet(self->maxs, 36, 36, 42); + self->s.scale = ARACHNID_DEFAULT_SCALE; + } self->health = M_ARACHNID_INITIAL_HEALTH + M_ARACHNID_ADDON_HEALTH * self->monsterinfo.level; self->max_health = self->health; self->gib_health = -200; self->mass = 450; self->mtype = M_ARACHNID; - self->s.scale = 0.75f; self->monsterinfo.control_cost = M_GLADIATOR_CONTROL_COST; self->monsterinfo.cost = M_DEFAULT_COST; self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD; diff --git a/src/entities/drone/drone_boss2.c b/src/entities/drone/drone_boss2.c index 157e068f..c1126f7d 100644 --- a/src/entities/drone/drone_boss2.c +++ b/src/entities/drone/drone_boss2.c @@ -13,6 +13,8 @@ boss2 #define BOSS2_VARIANT_HYPER 1 #define BOSS2_VARIANT_SMALL 2 #define BOSS2_ROCKET_SPEED 750 +#define BOSS2_INVASION_SCALE 0.75f +#define BOSS2_INVASION_MOVE_SCALE 1.5f static int sound_pain1; static int sound_pain2; @@ -38,6 +40,26 @@ static qboolean boss2_is_hyper(const edict_t *self) return self->style == BOSS2_VARIANT_HYPER || boss2_is_small(self); } +static float boss2_move_scale(const edict_t *self) +{ + return (invasion->value && !boss2_is_small(self)) ? BOSS2_INVASION_MOVE_SCALE : 1.0f; +} + +static void boss2_ai_walk(edict_t *self, float dist) +{ + drone_ai_walk(self, dist * boss2_move_scale(self)); +} + +static void boss2_ai_run(edict_t *self, float dist) +{ + drone_ai_run(self, dist * boss2_move_scale(self)); +} + +static float boss2_voice_attenuation(const edict_t *self) +{ + return boss2_is_small(self) ? ATTN_NORM : ATTN_NONE; +} + static void boss2_project_flash(edict_t *self, int flash, vec3_t start) { vec3_t forward, right; @@ -81,7 +103,7 @@ static void boss2_predict_aim(edict_t *self, vec3_t start, int speed, float lead static void boss2_search(edict_t *self) { if (random() < 0.5f) - gi.sound(self, CHAN_VOICE, sound_search1, 1, ATTN_NONE, 0); + gi.sound(self, CHAN_VOICE, sound_search1, 1, boss2_voice_attenuation(self), 0); } static void boss2_explode(edict_t *self) @@ -279,51 +301,51 @@ static mmove_t boss2_move_stand = { FRAME_stand30, FRAME_stand50, boss2_frames_s static mframe_t boss2_frames_walk[] = { - drone_ai_walk, 10, NULL, - drone_ai_walk, 10, NULL, - drone_ai_walk, 10, NULL, - drone_ai_walk, 10, NULL, - drone_ai_walk, 10, NULL, - drone_ai_walk, 10, NULL, - drone_ai_walk, 10, NULL, - drone_ai_walk, 10, NULL, - drone_ai_walk, 10, NULL, - drone_ai_walk, 10, NULL, - drone_ai_walk, 10, NULL, - drone_ai_walk, 10, NULL, - drone_ai_walk, 10, NULL, - drone_ai_walk, 10, NULL, - drone_ai_walk, 10, NULL, - drone_ai_walk, 10, NULL, - drone_ai_walk, 10, NULL, - drone_ai_walk, 10, NULL, - drone_ai_walk, 10, NULL, - drone_ai_walk, 10, NULL + boss2_ai_walk, 10, NULL, + boss2_ai_walk, 10, NULL, + boss2_ai_walk, 10, NULL, + boss2_ai_walk, 10, NULL, + boss2_ai_walk, 10, NULL, + boss2_ai_walk, 10, NULL, + boss2_ai_walk, 10, NULL, + boss2_ai_walk, 10, NULL, + boss2_ai_walk, 10, NULL, + boss2_ai_walk, 10, NULL, + boss2_ai_walk, 10, NULL, + boss2_ai_walk, 10, NULL, + boss2_ai_walk, 10, NULL, + boss2_ai_walk, 10, NULL, + boss2_ai_walk, 10, NULL, + boss2_ai_walk, 10, NULL, + boss2_ai_walk, 10, NULL, + boss2_ai_walk, 10, NULL, + boss2_ai_walk, 10, NULL, + boss2_ai_walk, 10, NULL }; static mmove_t boss2_move_walk = { FRAME_walk1, FRAME_walk20, boss2_frames_walk, NULL }; static mframe_t boss2_frames_run[] = { - drone_ai_run, 10, NULL, - drone_ai_run, 10, NULL, - drone_ai_run, 10, NULL, - drone_ai_run, 10, NULL, - drone_ai_run, 10, NULL, - drone_ai_run, 10, NULL, - drone_ai_run, 10, NULL, - drone_ai_run, 10, NULL, - drone_ai_run, 10, NULL, - drone_ai_run, 10, NULL, - drone_ai_run, 10, NULL, - drone_ai_run, 10, NULL, - drone_ai_run, 10, NULL, - drone_ai_run, 10, NULL, - drone_ai_run, 10, NULL, - drone_ai_run, 10, NULL, - drone_ai_run, 10, NULL, - drone_ai_run, 10, NULL, - drone_ai_run, 10, NULL, - drone_ai_run, 10, NULL + boss2_ai_run, 10, NULL, + boss2_ai_run, 10, NULL, + boss2_ai_run, 10, NULL, + boss2_ai_run, 10, NULL, + boss2_ai_run, 10, NULL, + boss2_ai_run, 10, NULL, + boss2_ai_run, 10, NULL, + boss2_ai_run, 10, NULL, + boss2_ai_run, 10, NULL, + boss2_ai_run, 10, NULL, + boss2_ai_run, 10, NULL, + boss2_ai_run, 10, NULL, + boss2_ai_run, 10, NULL, + boss2_ai_run, 10, NULL, + boss2_ai_run, 10, NULL, + boss2_ai_run, 10, NULL, + boss2_ai_run, 10, NULL, + boss2_ai_run, 10, NULL, + boss2_ai_run, 10, NULL, + boss2_ai_run, 10, NULL }; static mmove_t boss2_move_run = { FRAME_walk1, FRAME_walk20, boss2_frames_run, NULL }; @@ -648,6 +670,11 @@ static void init_drone_boss2_common(edict_t *self, int variant) VectorSet(self->mins, -34, -34, 0); VectorSet(self->maxs, 34, 34, 48); } + else if (invasion->value) + { + VectorSet(self->mins, -42, -42, 0); + VectorSet(self->maxs, 42, 42, 60); + } else { VectorSet(self->mins, -56, -56, 0); @@ -677,7 +704,7 @@ static void init_drone_boss2_common(edict_t *self, int variant) self->yaw_speed = small ? 80 : 50; self->flags |= FL_FLY | FL_IMMUNE_LASER; self->s.sound = gi.soundindex("bosshovr/bhvengn1.wav"); - self->s.scale = small ? 0.6f : 1.0f; + self->s.scale = small ? 0.6f : (invasion->value ? BOSS2_INVASION_SCALE : 1.0f); self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD; if (small) @@ -703,10 +730,10 @@ static void init_drone_boss2_common(edict_t *self, int variant) gi.linkentity(self); self->monsterinfo.currentmove = &boss2_move_stand; - self->monsterinfo.scale = small ? MODEL_SCALE * 0.6f : MODEL_SCALE; + self->monsterinfo.scale = MODEL_SCALE * self->s.scale; self->nextthink = level.time + FRAMETIME; - if (!small) + if (!small && !invasion->value) G_PrintGreenText(va("A level %d hornet%s has spawned!", self->monsterinfo.level, hyper ? " hyper" : "")); } diff --git a/src/entities/drone/drone_carrier.c b/src/entities/drone/drone_carrier.c index 0e1e570b..f06f329a 100644 --- a/src/entities/drone/drone_carrier.c +++ b/src/entities/drone/drone_carrier.c @@ -11,6 +11,8 @@ carrier #define CARRIER_SUMMON_COUNT 4 #define CARRIER_SUMMON_COOLDOWN 8.0f +#define CARRIER_DEFAULT_SCALE 0.75f +#define CARRIER_INVASION_SCALE 0.60f static int sound_pain1; static int sound_pain2; @@ -29,6 +31,17 @@ static void carrier_walk(edict_t *self); static void carrier_run(edict_t *self); static void carrier_attack(edict_t *self); +static void carrier_project_flash(edict_t *self, int flash, vec3_t forward, vec3_t start) +{ + vec3_t right, offset; + + AngleVectors(self->s.angles, forward, right, NULL); + VectorCopy(monster_flash_offset[flash], offset); + if (self->s.scale && self->s.scale != 1.0f) + VectorScale(offset, self->s.scale, offset); + G_ProjectSource(self->s.origin, offset, forward, right, start); +} + static const int carrier_summons[CARRIER_SUMMON_COUNT] = { M_DAEDALUS, @@ -99,7 +112,8 @@ static void carrier_fire_rocket(edict_t *self) for (int i = 0; i < 4; i++) { - MonsterAim(self, M_PROJECTILE_ACC, speed, true, flashes[i], forward, start); + carrier_project_flash(self, flashes[i], forward, start); + MonsterAim(self, M_PROJECTILE_ACC, speed, true, -1, forward, start); monster_fire_heat(self, start, forward, damage, speed, flashes[i], 0.06f); } } @@ -108,11 +122,12 @@ static void carrier_fire_bullets(edict_t *self) { int damage; vec3_t forward, start; - const int flashes[2] = + const int flashes[2][2] = { - MZ2_CARRIER_MACHINEGUN_L1, - MZ2_CARRIER_MACHINEGUN_R1 + { MZ2_CARRIER_MACHINEGUN_L1, MZ2_CARRIER_MACHINEGUN_R1 }, + { MZ2_CARRIER_MACHINEGUN_L2, MZ2_CARRIER_MACHINEGUN_R2 } }; + const int flash_row = self->monsterinfo.lefty ? 1 : 0; if (!G_EntExists(self->enemy)) return; @@ -123,9 +138,45 @@ static void carrier_fire_bullets(edict_t *self) for (int i = 0; i < 2; i++) { - MonsterAim(self, M_PROJECTILE_ACC, 0, false, flashes[i], forward, start); - monster_fire_bullet(self, start, forward, damage, damage, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flashes[i]); + carrier_project_flash(self, flashes[flash_row][i], forward, start); + MonsterAim(self, M_PROJECTILE_ACC, 0, false, -1, forward, start); + monster_fire_bullet(self, start, forward, damage, damage, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flashes[flash_row][i]); } + + self->monsterinfo.lefty = 1 - self->monsterinfo.lefty; +} + +static void carrier_fire_grenade(edict_t *self) +{ + int damage; + int speed; + vec3_t forward, right, up, start, target, aim, offset; + + if (!G_EntExists(self->enemy)) + return; + + damage = M_GRENADELAUNCHER_DMG_BASE + M_GRENADELAUNCHER_DMG_ADDON * drone_damagelevel(self); + if (M_GRENADELAUNCHER_DMG_MAX && damage > M_GRENADELAUNCHER_DMG_MAX) + damage = M_GRENADELAUNCHER_DMG_MAX; + speed = M_GRENADELAUNCHER_SPEED_BASE + M_GRENADELAUNCHER_SPEED_ADDON * drone_damagelevel(self); + if (M_GRENADELAUNCHER_SPEED_MAX && speed > M_GRENADELAUNCHER_SPEED_MAX) + speed = M_GRENADELAUNCHER_SPEED_MAX; + + AngleVectors(self->s.angles, forward, right, up); + VectorCopy(monster_flash_offset[MZ2_CARRIER_GRENADE], offset); + if (self->s.scale && self->s.scale != 1.0f) + VectorScale(offset, self->s.scale, offset); + G_ProjectSource(self->s.origin, offset, forward, right, start); + + VectorCopy(self->enemy->s.origin, target); + target[2] += self->enemy->viewheight; + VectorSubtract(target, start, aim); + VectorNormalize(aim); + VectorMA(aim, crandom() * 0.15f, right, aim); + VectorMA(aim, 0.1f, up, aim); + VectorNormalize(aim); + + monster_fire_grenade(self, start, aim, damage, speed, MZ2_CARRIER_GRENADE); } static void carrier_fire_rail(edict_t *self) @@ -140,7 +191,8 @@ static void carrier_fire_rail(edict_t *self) if (M_RAILGUN_DMG_MAX && damage > M_RAILGUN_DMG_MAX) damage = M_RAILGUN_DMG_MAX; - MonsterAim(self, 0.25f, 0, false, MZ2_CARRIER_RAILGUN, forward, start); + carrier_project_flash(self, MZ2_CARRIER_RAILGUN, forward, start); + MonsterAim(self, 0.25f, 0, false, -1, forward, start); gi.sound(self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0); monster_fire_railgun(self, start, forward, damage, damage, MZ2_CARRIER_RAILGUN); } @@ -362,6 +414,15 @@ mframe_t carrier_frames_attack_rocket[] = }; mmove_t carrier_move_attack_rocket = { FRAME_fireb01, FRAME_fireb04, carrier_frames_attack_rocket, carrier_run }; +mframe_t carrier_frames_attack_grenade[] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, -15, carrier_fire_grenade, + ai_charge, 0, NULL +}; +mmove_t carrier_move_attack_grenade = { FRAME_fireb07, FRAME_fireb10, carrier_frames_attack_grenade, carrier_run }; + mframe_t carrier_frames_attack_rail[] = { ai_charge, 0, NULL, @@ -410,11 +471,13 @@ static void carrier_attack(edict_t *self) return; r = random(); - if (level.time >= self->monsterinfo.melee_finished && r < 0.25f) + if (level.time >= self->monsterinfo.melee_finished && r < 0.20f) carrier_start_spawn(self); - else if (r < 0.50f) + else if (r < 0.40f) self->monsterinfo.currentmove = &carrier_move_attack_rocket; - else if (r < 0.75f) + else if (r < 0.60f) + self->monsterinfo.currentmove = &carrier_move_attack_grenade; + else if (r < 0.80f) self->monsterinfo.currentmove = &carrier_move_attack_rail; else self->monsterinfo.currentmove = &carrier_move_attack_mg; @@ -500,8 +563,18 @@ void init_drone_carrier(edict_t *self) self->movetype = MOVETYPE_STEP; self->solid = SOLID_BBOX; self->s.modelindex = gi.modelindex("models/monsters/carrier/tris.md2"); - VectorSet(self->mins, -56, -56, -44); - VectorSet(self->maxs, 56, 56, 44); + if (invasion->value) + { + self->s.scale = CARRIER_INVASION_SCALE; + VectorSet(self->mins, -40, -40, -24); + VectorSet(self->maxs, 40, 40, 82); + } + else + { + self->s.scale = CARRIER_DEFAULT_SCALE; + VectorSet(self->mins, -56, -56, -44); + VectorSet(self->maxs, 56, 56, 44); + } gi.modelindex("models/items/spawngro3/tris.md2"); gi.modelindex("models/monsters/flyer/tris.md2"); @@ -514,7 +587,6 @@ void init_drone_carrier(edict_t *self) self->mass = 1000; self->mtype = M_CARRIER; self->flags |= FL_FLY; - self->s.scale = 0.75f; self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD; self->monsterinfo.power_armor_power = M_CARRIER_INITIAL_ARMOR + M_CARRIER_ADDON_ARMOR * self->monsterinfo.level; @@ -539,5 +611,6 @@ void init_drone_carrier(edict_t *self) self->monsterinfo.scale = MODEL_SCALE; self->nextthink = level.time + FRAMETIME; - G_PrintGreenText(va("A level %d carrier has spawned!", self->monsterinfo.level)); + if (!invasion->value) + G_PrintGreenText(va("A level %d carrier has spawned!", self->monsterinfo.level)); } diff --git a/src/entities/drone/drone_fixbot.c b/src/entities/drone/drone_fixbot.c index 09610ed2..721bc663 100644 --- a/src/entities/drone/drone_fixbot.c +++ b/src/entities/drone/drone_fixbot.c @@ -25,6 +25,9 @@ fixbot #define FIXBOT_BOSS_SPAWN_COOLDOWN 8.0f #define FIXBOT_BOSS_FAIL_COOLDOWN 2.0f #define FIXBOT_BLASTER_FLASH MZ2_HOVER_BLASTER_1 +#define FIXBOT_BOSS_DEFAULT_SCALE 2.6f +#define FIXBOT_BOSS_INVASION_SCALE 2.0f +#define FIXBOT_BOSS_INVASION_MOVE_SCALE 1.5f static int sound_pain; static int sound_die; @@ -48,6 +51,21 @@ static qboolean fixbot_is_boss(edict_t *self) return self->mtype == M_FIXBOT_BOSS; } +static float fixbot_move_scale(edict_t *self) +{ + return (fixbot_is_boss(self) && invasion->value) ? FIXBOT_BOSS_INVASION_MOVE_SCALE : 1.0f; +} + +static void fixbot_ai_run(edict_t *self, float dist) +{ + drone_ai_run(self, dist * fixbot_move_scale(self)); +} + +static void fixbot_ai_walk(edict_t *self, float dist) +{ + drone_ai_walk(self, dist * fixbot_move_scale(self)); +} + static int fixbot_count_live_turrets(edict_t *self) { int count = 0; @@ -967,7 +985,7 @@ static void fixbot_stand(edict_t *self) static mframe_t fixbot_frames_run[] = { - drone_ai_run, 10, fixbot_try_start_spawn + fixbot_ai_run, 10, fixbot_try_start_spawn }; static mmove_t fixbot_move_run = { FIXBOT_FRAME_freeze_01, FIXBOT_FRAME_freeze_01, fixbot_frames_run, NULL }; @@ -981,7 +999,7 @@ static void fixbot_run(edict_t *self) static mframe_t fixbot_frames_walk[] = { - drone_ai_walk, 5, NULL + fixbot_ai_walk, 5, NULL }; static mmove_t fixbot_move_walk = { FIXBOT_FRAME_freeze_01, FIXBOT_FRAME_freeze_01, fixbot_frames_walk, NULL }; @@ -1155,12 +1173,21 @@ static void init_drone_fixbot_common(edict_t *self, qboolean boss) if (boss) { - VectorSet(self->mins, -36, -36, -28); - VectorSet(self->maxs, 36, 36, 28); + if (invasion->value) + { + VectorSet(self->mins, -30, -30, -24); + VectorSet(self->maxs, 30, 30, 24); + self->s.scale = FIXBOT_BOSS_INVASION_SCALE; + } + else + { + VectorSet(self->mins, -36, -36, -28); + VectorSet(self->maxs, 36, 36, 28); + self->s.scale = FIXBOT_BOSS_DEFAULT_SCALE; + } self->health = M_FIXBOT_BOSS_INITIAL_HEALTH + M_FIXBOT_BOSS_ADDON_HEALTH * self->monsterinfo.level; self->monsterinfo.power_armor_power = M_FIXBOT_BOSS_INITIAL_ARMOR + M_FIXBOT_BOSS_ADDON_ARMOR * self->monsterinfo.level; self->mass = 400; - self->s.scale = 2.6f; self->mtype = M_FIXBOT_BOSS; self->monsterinfo.control_cost = M_JORG_CONTROL_COST; self->monsterinfo.cost = M_COMMANDER_COST; @@ -1201,8 +1228,8 @@ static void init_drone_fixbot_common(edict_t *self, qboolean boss) self->monsterinfo.scale = 1.0f; qboolean isBoss = (self->mtype == M_FIXBOT_BOSS); - if (isBoss) - G_PrintGreenText(va("A level %d fixer has spawned!", self->monsterinfo.level)); + if (isBoss && !invasion->value) + G_PrintGreenText(va("A level %d fixer has spawned!", self->monsterinfo.level)); } void init_drone_fixbot(edict_t *self) diff --git a/src/entities/drone/drone_guardian.c b/src/entities/drone/drone_guardian.c index 4d2b5b44..6c33a8bc 100644 --- a/src/entities/drone/drone_guardian.c +++ b/src/entities/drone/drone_guardian.c @@ -15,8 +15,36 @@ static int sound_spin_loop; static int sound_laser; static int sound_pew; +#define GUARDIAN_INVASION_SCALE 0.45f +#define GUARDIAN_INVASION_MOVE_SCALE 1.75f + void guardian_run(edict_t *self); +static float guardian_scale(edict_t *self) +{ + return (self->s.scale > 0.0f) ? self->s.scale : 1.0f; +} + +static void guardian_ai_walk(edict_t *self, float dist) +{ + drone_ai_walk(self, invasion->value ? dist * GUARDIAN_INVASION_MOVE_SCALE : dist); +} + +static void guardian_ai_run(edict_t *self, float dist) +{ + drone_ai_run(self, invasion->value ? dist * GUARDIAN_INVASION_MOVE_SCALE : dist); +} + +static void guardian_project_flash(edict_t *self, int flash, vec3_t forward, vec3_t right, vec3_t start) +{ + vec3_t offset; + + VectorCopy(monster_flash_offset[flash], offset); + if (guardian_scale(self) != 1.0f) + VectorScale(offset, guardian_scale(self), offset); + G_ProjectSource(self->s.origin, offset, forward, right, start); +} + static void guardian_project_laser_frame_origin(edict_t *self, vec3_t forward, vec3_t right, vec3_t start) { vec3_t offset; @@ -26,8 +54,8 @@ static void guardian_project_laser_frame_origin(edict_t *self, vec3_t forward, v else VectorSet(offset, 112, -62, 60); - if (self->s.scale) - VectorScale(offset, self->s.scale, offset); + if (guardian_scale(self) != 1.0f) + VectorScale(offset, guardian_scale(self), offset); G_ProjectSource(self->s.origin, offset, forward, right, start); } @@ -101,25 +129,25 @@ void guardian_stand(edict_t *self) mframe_t guardian_frames_walk[] = { - drone_ai_walk, 8, NULL, - drone_ai_walk, 8, NULL, - drone_ai_walk, 8, NULL, - drone_ai_walk, 8, NULL, - drone_ai_walk, 8, NULL, - drone_ai_walk, 8, NULL, - drone_ai_walk, 8, NULL, - drone_ai_walk, 8, guardian_footstep, - drone_ai_walk, 8, NULL, - drone_ai_walk, 8, NULL, - drone_ai_walk, 8, NULL, - drone_ai_walk, 8, NULL, - drone_ai_walk, 8, NULL, - drone_ai_walk, 8, NULL, - drone_ai_walk, 8, NULL, - drone_ai_walk, 8, NULL, - drone_ai_walk, 8, NULL, - drone_ai_walk, 8, guardian_footstep, - drone_ai_walk, 8, NULL + guardian_ai_walk, 8, NULL, + guardian_ai_walk, 8, NULL, + guardian_ai_walk, 8, NULL, + guardian_ai_walk, 8, NULL, + guardian_ai_walk, 8, NULL, + guardian_ai_walk, 8, NULL, + guardian_ai_walk, 8, NULL, + guardian_ai_walk, 8, guardian_footstep, + guardian_ai_walk, 8, NULL, + guardian_ai_walk, 8, NULL, + guardian_ai_walk, 8, NULL, + guardian_ai_walk, 8, NULL, + guardian_ai_walk, 8, NULL, + guardian_ai_walk, 8, NULL, + guardian_ai_walk, 8, NULL, + guardian_ai_walk, 8, NULL, + guardian_ai_walk, 8, NULL, + guardian_ai_walk, 8, guardian_footstep, + guardian_ai_walk, 8, NULL }; mmove_t guardian_move_walk = {FRAME_walk1, FRAME_walk19, guardian_frames_walk, NULL}; @@ -132,25 +160,25 @@ void guardian_walk(edict_t *self) mframe_t guardian_frames_run[] = { - drone_ai_run, 8, NULL, - drone_ai_run, 8, NULL, - drone_ai_run, 8, NULL, - drone_ai_run, 8, NULL, - drone_ai_run, 8, NULL, - drone_ai_run, 8, NULL, - drone_ai_run, 8, NULL, - drone_ai_run, 8, guardian_footstep, - drone_ai_run, 8, NULL, - drone_ai_run, 8, NULL, - drone_ai_run, 8, NULL, - drone_ai_run, 8, NULL, - drone_ai_run, 8, NULL, - drone_ai_run, 8, NULL, - drone_ai_run, 8, NULL, - drone_ai_run, 8, NULL, - drone_ai_run, 8, NULL, - drone_ai_run, 8, guardian_footstep, - drone_ai_run, 8, NULL + guardian_ai_run, 8, NULL, + guardian_ai_run, 8, NULL, + guardian_ai_run, 8, NULL, + guardian_ai_run, 8, NULL, + guardian_ai_run, 8, NULL, + guardian_ai_run, 8, NULL, + guardian_ai_run, 8, NULL, + guardian_ai_run, 8, guardian_footstep, + guardian_ai_run, 8, NULL, + guardian_ai_run, 8, NULL, + guardian_ai_run, 8, NULL, + guardian_ai_run, 8, NULL, + guardian_ai_run, 8, NULL, + guardian_ai_run, 8, NULL, + guardian_ai_run, 8, NULL, + guardian_ai_run, 8, NULL, + guardian_ai_run, 8, NULL, + guardian_ai_run, 8, guardian_footstep, + guardian_ai_run, 8, NULL }; mmove_t guardian_move_run = {FRAME_walk1, FRAME_walk19, guardian_frames_run, NULL}; @@ -246,9 +274,9 @@ void guardian_fire_blaster(edict_t *self) speed = 1100; effect = (self->s.frame % 4) ? 0 : EF_HYPERBLASTER; AngleVectors(self->s.angles, forward, right, NULL); - G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_GUARDIAN_BLASTER], forward, right, start); + guardian_project_flash(self, MZ2_GUARDIAN_BLASTER, forward, right, start); MonsterAim(self, M_PROJECTILE_ACC, speed, false, -1, forward, start); - monster_fire_blaster(self, start, forward, damage, speed, effect, BLASTER_PROJ_BOLT, 2.0, true, -1); + monster_fire_blaster(self, start, forward, damage, speed, effect, BLASTER_PROJ_BOLT, 2.0, true, MZ2_GUARDIAN_BLASTER); } if (G_EntExists(self->enemy) && self->s.frame == FRAME_atk1_spin12 && self->timestamp > level.time && visible(self, self->enemy)) @@ -402,9 +430,9 @@ void guardian_fire_rocket(edict_t *self, float offset) AngleVectors(self->s.angles, forward, right, up); VectorCopy(self->s.origin, start); - VectorMA(start, -8, forward, start); - VectorMA(start, offset, right, start); - VectorMA(start, 50, up, start); + VectorMA(start, -8 * guardian_scale(self), forward, start); + VectorMA(start, offset * guardian_scale(self), right, start); + VectorMA(start, 50 * guardian_scale(self), up, start); speed = M_ROCKETLAUNCHER_SPEED_BASE + M_ROCKETLAUNCHER_SPEED_ADDON * drone_damagelevel(self); if (M_ROCKETLAUNCHER_SPEED_MAX && speed > M_ROCKETLAUNCHER_SPEED_MAX) @@ -669,9 +697,19 @@ void init_drone_guardian(edict_t *self) } else { - self->monsterinfo.scale = MODEL_SCALE; - VectorSet(self->mins, -96, -96, -66); - VectorSet(self->maxs, 96, 96, 62); + if (invasion->value) + { + self->s.scale = GUARDIAN_INVASION_SCALE; + self->monsterinfo.scale = MODEL_SCALE * GUARDIAN_INVASION_SCALE; + VectorSet(self->mins, -44, -44, -30); + VectorSet(self->maxs, 44, 44, 45); + } + else + { + self->monsterinfo.scale = MODEL_SCALE; + VectorSet(self->mins, -96, -96, -66); + VectorSet(self->maxs, 96, 96, 62); + } self->health = M_GUARDIAN_INITIAL_HEALTH + M_GUARDIAN_ADDON_HEALTH * self->monsterinfo.level; self->monsterinfo.power_armor_power = M_GUARDIAN_INITIAL_ARMOR + M_GUARDIAN_ADDON_ARMOR * self->monsterinfo.level; self->mass = 850; @@ -696,6 +734,6 @@ void init_drone_guardian(edict_t *self) self->nextthink = level.time + FRAMETIME; gi.linkentity(self); - if (!miniguardian) - G_PrintGreenText(va("A level %d guardian has spawned!", self->monsterinfo.level)); + if (!miniguardian && !invasion->value) + G_PrintGreenText(va("A level %d guardian has spawned!", self->monsterinfo.level)); } diff --git a/src/entities/drone/drone_guncmdr.c b/src/entities/drone/drone_guncmdr.c index 3e6aad82..da8c845f 100644 --- a/src/entities/drone/drone_guncmdr.c +++ b/src/entities/drone/drone_guncmdr.c @@ -7,7 +7,7 @@ GUN COMMANDER */ #include "g_local.h" -#include "../../quake2/monsterframes/m_guncmdr.h" +#include "../../quake2/monsterframes/m_gunner.h" static int sound_pain; static int sound_pain2; @@ -24,6 +24,7 @@ static int sound_thud; #define GUNCMDR_MORTAR_SPEED 850 #define GUNCMDR_GRENADE_SPEED 600 #define GUNCMDR_WALK_SPEED_MULT 2.0f +#define GUNCMDR_INVASION_RUN_SCALE 1.15f #define GUNCMDR_SCALE(self) (((self)->s.scale > 0) ? (self)->s.scale : 1.0f) @@ -57,6 +58,11 @@ void drone_ai_run_slide(edict_t *self, float dist); static void guncmdr_ai_dodge_slide(edict_t *self, float dist); extern mmove_t guncmdr_move_dodge_slide; +static void guncmdr_ai_run(edict_t *self, float dist) +{ + drone_ai_run(self, invasion->value ? dist * GUNCMDR_INVASION_RUN_SCALE : dist); +} + static void guncmdr_set_stand_bbox(edict_t *self) { VectorSet(self->mins, @@ -256,12 +262,12 @@ static void guncmdr_walk(edict_t *self) mframe_t guncmdr_frames_run[] = { - drone_ai_run, 15, NULL, - drone_ai_run, 16, NULL, - drone_ai_run, 20, NULL, - drone_ai_run, 18, NULL, - drone_ai_run, 24, NULL, - drone_ai_run, 13.5, NULL + guncmdr_ai_run, 15, NULL, + guncmdr_ai_run, 16, NULL, + guncmdr_ai_run, 20, NULL, + guncmdr_ai_run, 18, NULL, + guncmdr_ai_run, 24, NULL, + guncmdr_ai_run, 13.5, NULL }; mmove_t guncmdr_move_run = { FRAME_c_run101, FRAME_c_run106, guncmdr_frames_run, NULL }; @@ -336,12 +342,12 @@ mmove_t guncmdr_move_fire_chain = { FRAME_c_attack107, FRAME_c_attack112, guncmd mframe_t guncmdr_frames_fire_chain_run[] = { - drone_ai_run, 15, GunnerCmdrFire, - drone_ai_run, 16, GunnerCmdrFire, - drone_ai_run, 20, GunnerCmdrFire, - drone_ai_run, 18, GunnerCmdrFire, - drone_ai_run, 24, GunnerCmdrFire, - drone_ai_run, 13.5, GunnerCmdrFire + guncmdr_ai_run, 15, GunnerCmdrFire, + guncmdr_ai_run, 16, GunnerCmdrFire, + guncmdr_ai_run, 20, GunnerCmdrFire, + guncmdr_ai_run, 18, GunnerCmdrFire, + guncmdr_ai_run, 24, GunnerCmdrFire, + guncmdr_ai_run, 13.5, GunnerCmdrFire }; mmove_t guncmdr_move_fire_chain_run = { FRAME_c_run201, FRAME_c_run206, guncmdr_frames_fire_chain_run, guncmdr_refire_chain }; @@ -655,6 +661,15 @@ static void guncmdr_jump_now(edict_t *self) self->velocity[2] += 350; } +static void guncmdr_jump2_now(edict_t *self) +{ + vec3_t forward; + + AngleVectors(self->s.angles, forward, NULL, NULL); + VectorMA(self->velocity, 150, forward, self->velocity); + self->velocity[2] += 400; +} + static void guncmdr_jump_wait_land(edict_t *self) { if (!self->groundentity && level.time < self->monsterinfo.pausetime) @@ -678,6 +693,21 @@ mframe_t guncmdr_frames_jump[] = }; mmove_t guncmdr_move_jump = { FRAME_c_jump01, FRAME_c_jump10, guncmdr_frames_jump, guncmdr_run }; +mframe_t guncmdr_frames_jump2[] = +{ + ai_move, -8, NULL, + ai_move, -4, NULL, + ai_move, -4, NULL, + ai_move, 0, guncmdr_jump2_now, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, guncmdr_jump_wait_land, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t guncmdr_move_jump2 = { FRAME_c_jump01, FRAME_c_jump10, guncmdr_frames_jump2, guncmdr_run }; + static qboolean guncmdr_try_sidestep(edict_t *self); static qboolean guncmdr_is_dodge_move(edict_t *self) @@ -690,6 +720,7 @@ static qboolean guncmdr_is_dodge_move(edict_t *self) self->monsterinfo.currentmove == &guncmdr_move_duckstep_dodge || self->monsterinfo.currentmove == &guncmdr_move_dodge_slide || self->monsterinfo.currentmove == &guncmdr_move_jump || + self->monsterinfo.currentmove == &guncmdr_move_jump2 || self->monsterinfo.currentmove == &guncmdr_move_duck_attack; } diff --git a/src/entities/drone/drone_gunner.c b/src/entities/drone/drone_gunner.c index 65d04190..cfd999f2 100644 --- a/src/entities/drone/drone_gunner.c +++ b/src/entities/drone/drone_gunner.c @@ -236,11 +236,18 @@ void myGunnerGrenade (edict_t *self) if (M_GRENADELAUNCHER_SPEED_MAX && speed > M_GRENADELAUNCHER_SPEED_MAX) speed = M_GRENADELAUNCHER_SPEED_MAX; - if (self->s.frame == FRAME_attak105) + if (self->s.frame == FRAME_attak105 || self->s.frame == FRAME_attak309) flash_number = MZ2_GUNNER_GRENADE_1; + else if (self->s.frame == FRAME_attak108 || self->s.frame == FRAME_attak312) + flash_number = MZ2_GUNNER_GRENADE_2; + else if (self->s.frame == FRAME_attak111 || self->s.frame == FRAME_attak315) + flash_number = MZ2_GUNNER_GRENADE_3; else flash_number = MZ2_GUNNER_GRENADE_4; + if (self->s.frame >= FRAME_attak301 && self->s.frame <= FRAME_attak324) + flash_number = MZ2_GUNNER_GRENADE2_1 + (MZ2_GUNNER_GRENADE_4 - flash_number); + MonsterAim(self, M_PROJECTILE_ACC, speed, false, flash_number, forward, start); monster_fire_grenade(self, start, forward, damage, speed, flash_number); } @@ -281,12 +288,45 @@ mframe_t mygunner_frames_attack_grenade [] = }; mmove_t mygunner_move_attack_grenade = {FRAME_attak105, FRAME_attak113, mygunner_frames_attack_grenade, gunner_refire_grenade}; +mframe_t mygunner_frames_attack_grenade2[] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, myGunnerGrenade, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, myGunnerGrenade, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, myGunnerGrenade, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, myGunnerGrenade, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t mygunner_move_attack_grenade2 = {FRAME_attak305, FRAME_attak324, mygunner_frames_attack_grenade2, mygunnerrun}; + +static void mygunner_start_grenade(edict_t *self) +{ + if (random() <= 0.5) + self->monsterinfo.currentmove = &mygunner_move_attack_grenade2; + else + self->monsterinfo.currentmove = &mygunner_move_attack_grenade; +} + void gunner_refire_grenade (edict_t *self) { // continue firing unless enemy is no longer valid or out of range if (G_ValidTarget(self, self->enemy, true, true) && (random() <= 0.8) && (entdist(self, self->enemy) <= 384)) - self->monsterinfo.currentmove = &mygunner_move_attack_grenade; + mygunner_start_grenade(self); else self->monsterinfo.currentmove = &mygunner_move_attack_grenade_end; @@ -298,7 +338,7 @@ void gunner_attack_grenade (edict_t *self) { // continue attack sequence unless enemy is no longer valid if (G_ValidTarget(self, self->enemy, true, true)) - self->monsterinfo.currentmove = &mygunner_move_attack_grenade; + mygunner_start_grenade(self); else mygunnerrun(self); } @@ -444,7 +484,7 @@ void mygunner_refire_chain(edict_t *self) void gunner_stand_attack (edict_t *self) { if (entdist(self, self->enemy) <= 384 && random() <= 0.8) - self->monsterinfo.currentmove = &mygunner_move_attack_grenade; + mygunner_start_grenade(self); else self->monsterinfo.currentmove = &mygunner_move_attack_chain; } @@ -458,7 +498,7 @@ void gunner_attack (edict_t *self) if (dist <= 128) { if (r <= 0.2) - self->monsterinfo.currentmove = &mygunner_move_attack_grenade; + mygunner_start_grenade(self); else self->monsterinfo.currentmove = &mygunner_move_runandshoot; } @@ -529,7 +569,17 @@ mframe_t mygunner_frames_duck [] = }; mmove_t mygunner_move_duck = {FRAME_duck01, FRAME_duck08, mygunner_frames_duck, mygunnerrun}; -void mygunner_jump_takeoff (edict_t *self) +void mygunner_jump_now (edict_t *self) +{ + vec3_t forward; + + AngleVectors(self->s.angles, forward, NULL, NULL); + VectorMA(self->velocity, 100, forward, self->velocity); + self->velocity[2] += 300; + self->monsterinfo.pausetime = level.time + 2.0; +} + +void mygunner_jump2_now (edict_t *self) { vec3_t v; @@ -537,12 +587,12 @@ void mygunner_jump_takeoff (edict_t *self) VectorSubtract(self->monsterinfo.dir, self->s.origin, v); v[2] = 0; VectorNormalize(v); - VectorScale(v, -200, self->velocity); + VectorScale(v, -150, self->velocity); self->velocity[2] = 400; self->monsterinfo.pausetime = level.time + 2.0; // maximum duration of jump } -void mygunner_jump_hold (edict_t *self) +void mygunner_jump_wait_land (edict_t *self) { vec3_t v; @@ -556,39 +606,57 @@ void mygunner_jump_hold (edict_t *self) // check for landing or jump timeout if (self->groundentity || (level.time > self->monsterinfo.pausetime)) { - self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; VectorClear(self->velocity); + self->monsterinfo.nextframe = self->s.frame + 1; } else { // we're still in the air - self->monsterinfo.aiflags |= AI_HOLD_FRAME; + self->monsterinfo.nextframe = self->s.frame; } } -mframe_t mygunner_frames_leap [] = +mframe_t mygunner_frames_jump [] = { - ai_move, 0, mygunner_jump_takeoff, - ai_move, 0, NULL, - ai_move, 0, mygunner_jump_hold, - ai_move, 0, NULL, - ai_move, 0, NULL, - ai_move, 0, NULL, - ai_move, 0, NULL, - ai_move, 0, NULL + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, mygunner_jump_now, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, mygunner_jump_wait_land, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t mygunner_move_jump = {FRAME_jump01, FRAME_jump10, mygunner_frames_jump, mygunnerrun}; + +mframe_t mygunner_frames_jump2 [] = +{ + ai_move, -8, NULL, + ai_move, -4, NULL, + ai_move, -4, NULL, + ai_move, 0, mygunner_jump2_now, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, mygunner_jump_wait_land, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL }; -mmove_t mygunner_move_leap = {FRAME_duck01, FRAME_duck08, mygunner_frames_leap, mygunnerrun}; +mmove_t mygunner_move_jump2 = {FRAME_jump01, FRAME_jump10, mygunner_frames_jump2, mygunnerrun}; void mygunner_leap (edict_t *self) { if (self->groundentity) - self->monsterinfo.currentmove = &mygunner_move_leap; + self->monsterinfo.currentmove = &mygunner_move_jump2; } static qboolean mygunner_is_dodge_move(edict_t *self) { return self->monsterinfo.currentmove == &mygunner_move_duck || - self->monsterinfo.currentmove == &mygunner_move_leap || + self->monsterinfo.currentmove == &mygunner_move_jump || + self->monsterinfo.currentmove == &mygunner_move_jump2 || self->monsterinfo.currentmove == &mygunner_move_dodge_slide; } @@ -596,7 +664,8 @@ static qboolean mygunner_is_uninterruptible_attack(edict_t *self) { return self->monsterinfo.currentmove == &mygunner_move_attack_chain || self->monsterinfo.currentmove == &mygunner_move_fire_chain || - self->monsterinfo.currentmove == &mygunner_move_attack_grenade; + self->monsterinfo.currentmove == &mygunner_move_attack_grenade || + self->monsterinfo.currentmove == &mygunner_move_attack_grenade2; } static qboolean mygunner_dodge_hit_low(edict_t *self, vec3_t dir) diff --git a/src/entities/drone/drone_jorg.c b/src/entities/drone/drone_jorg.c index f6ad093e..04029067 100644 --- a/src/entities/drone/drone_jorg.c +++ b/src/entities/drone/drone_jorg.c @@ -613,5 +613,6 @@ void init_drone_jorg (edict_t *self) self->nextthink = level.time + 0.1; //walkmonster_start(self); - G_PrintGreenText(va("A level %d jorg has spawned!", self->monsterinfo.level)); + if (!invasion->value) + G_PrintGreenText(va("A level %d jorg has spawned!", self->monsterinfo.level)); } diff --git a/src/entities/drone/drone_makron.c b/src/entities/drone/drone_makron.c index b6cd2503..1e93aa7c 100644 --- a/src/entities/drone/drone_makron.c +++ b/src/entities/drone/drone_makron.c @@ -9,6 +9,8 @@ Makron -- Final Boss #include "g_local.h" #include "../../quake2/monsterframes/m_boss32.h" +#define MAKRON_INVASION_MOVE_SCALE 0.75f + qboolean visible (const edict_t *self, const edict_t *other); void MakronRailgun (edict_t *self); @@ -34,6 +36,16 @@ static int sound_taunt2; static int sound_taunt3; static int sound_hit; +static void makron_ai_run(edict_t *self, float dist) +{ + drone_ai_run(self, invasion->value ? dist * MAKRON_INVASION_MOVE_SCALE : dist); +} + +static void makron_ai_walk(edict_t *self, float dist) +{ + drone_ai_walk(self, invasion->value ? dist * MAKRON_INVASION_MOVE_SCALE : dist); +} + void makron_taunt (edict_t *self) { float r; @@ -123,16 +135,16 @@ void makron_stand (edict_t *self) mframe_t makron_frames_run [] = { - drone_ai_run, 20, makron_step_left, - drone_ai_run, 20, NULL, - drone_ai_run, 20, NULL, - drone_ai_run, 20, NULL, - drone_ai_run, 20, makron_step_right, - drone_ai_run, 20, NULL, - drone_ai_run, 20, NULL, - drone_ai_run, 20, NULL, - drone_ai_run, 20, NULL, - drone_ai_run, 20, NULL + makron_ai_run, 20, makron_step_left, + makron_ai_run, 20, NULL, + makron_ai_run, 20, NULL, + makron_ai_run, 20, NULL, + makron_ai_run, 20, makron_step_right, + makron_ai_run, 20, NULL, + makron_ai_run, 20, NULL, + makron_ai_run, 20, NULL, + makron_ai_run, 20, NULL, + makron_ai_run, 20, NULL }; mmove_t makron_move_run = {FRAME_walk204, FRAME_walk213, makron_frames_run, NULL}; @@ -169,16 +181,16 @@ void makron_prerailgun (edict_t *self) mframe_t makron_frames_walk [] = { - drone_ai_walk, 3, makron_step_left, - drone_ai_walk, 12, NULL, - drone_ai_walk, 8, NULL, - drone_ai_walk, 8, NULL, - drone_ai_walk, 8, makron_step_right, - drone_ai_walk, 6, NULL, - drone_ai_walk, 12, NULL, - drone_ai_walk, 9, NULL, - drone_ai_walk, 6, NULL, - drone_ai_walk, 12, NULL + makron_ai_walk, 3, makron_step_left, + makron_ai_walk, 12, NULL, + makron_ai_walk, 8, NULL, + makron_ai_walk, 8, NULL, + makron_ai_walk, 8, makron_step_right, + makron_ai_walk, 6, NULL, + makron_ai_walk, 12, NULL, + makron_ai_walk, 9, NULL, + makron_ai_walk, 6, NULL, + makron_ai_walk, 12, NULL }; mmove_t makron_move_walk = {FRAME_walk204, FRAME_walk213, makron_frames_walk, NULL}; diff --git a/src/entities/drone/drone_medic.c b/src/entities/drone/drone_medic.c index fe329d1d..042e59d5 100644 --- a/src/entities/drone/drone_medic.c +++ b/src/entities/drone/drone_medic.c @@ -274,26 +274,43 @@ void mymedic_run (edict_t *self) void mymedic_fire_blaster (edict_t *self) { - int effect, damage; - const float speed = 2000; // speed: medic_blaster + int effect, damage, flash_number; + const int speed = 2000; // speed: medic_blaster vec3_t forward, start; qboolean bounce = false; + + if (!G_EntExists(self->enemy)) + return; if ((self->s.frame == FRAME_attack9) || (self->s.frame == FRAME_attack12)) { effect = EF_BLASTER; bounce = true; + flash_number = medic_is_commander(self) ? MZ2_MEDIC_BLASTER_2 : MZ2_MEDIC_BLASTER_1; } else - effect = EF_HYPERBLASTER; + { + int frame_offset = self->s.frame - FRAME_attack19; + + if (frame_offset < 0) + frame_offset = 0; + else if (frame_offset > 11) + frame_offset = 11; + + effect = (self->s.frame % 4) ? 0 : EF_HYPERBLASTER; + flash_number = (medic_is_commander(self) ? MZ2_MEDIC_HYPERBLASTER2_1 : MZ2_MEDIC_HYPERBLASTER1_1) + frame_offset; + } damage = M_HYPERBLASTER_DMG_BASE + M_HYPERBLASTER_DMG_ADDON * drone_damagelevel(self); if (M_HYPERBLASTER_DMG_MAX && damage > M_HYPERBLASTER_DMG_MAX) damage = M_HYPERBLASTER_DMG_MAX; - MonsterAim(self, M_PROJECTILE_ACC, speed, false, MZ2_MEDIC_BLASTER_1, forward, start); - monster_fire_blaster(self, start, forward, damage, speed, effect, BLASTER_PROJ_BOLT, 2.0, bounce, MZ2_MEDIC_BLASTER_1); + MonsterAim(self, M_PROJECTILE_ACC, speed, false, flash_number, forward, start); + if (medic_is_commander(self)) + monster_fire_blaster2(self, start, forward, damage, speed, effect, flash_number); + else + monster_fire_blaster(self, start, forward, damage, speed, effect, BLASTER_PROJ_BOLT, 2.0, bounce, flash_number); } void mymedic_fire_bolt (edict_t *self) @@ -307,7 +324,9 @@ void mymedic_fire_bolt (edict_t *self) damage = GetRandom(min, max); MonsterAim(self, M_PROJECTILE_ACC, 1500, false, MZ2_MEDIC_BLASTER_1, forward, start); - monster_fire_blaster(self, start, forward, damage, 1500, EF_BLASTER, BLASTER_PROJ_BLAST, 2.0, true, MZ2_MEDIC_BLASTER_1); + // Keep the medic muzzle origin, but don't emit the muzzleflash packet here; + // it can override the secondary blaster sound that should play per bolt. + monster_fire_blaster(self, start, forward, damage, 1500, EF_BLASTER, BLASTER_PROJ_BLAST, 2.0, true, -1); gi.sound (self, CHAN_WEAPON, gi.soundindex("weapons/photon.wav"), 1, ATTN_NORM, 0); } @@ -679,24 +698,28 @@ void medic_checktarget (edict_t *self) mframe_t mymedic_frames_attackHyperBlaster [] = { - ai_charge, 0, mymedic_fire_blaster, //191 - ai_charge, 0, medic_checktarget, + ai_charge, 0, NULL, // attack15 + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, mymedic_fire_blaster, // attack19 + ai_charge, 0, mymedic_fire_blaster, ai_charge, 0, mymedic_fire_blaster, - ai_charge, 0, medic_checktarget, - ai_charge, 0, mymedic_fire_blaster, //195 - ai_charge, 0, medic_checktarget, ai_charge, 0, mymedic_fire_blaster, - ai_charge, 0, medic_checktarget, ai_charge, 0, mymedic_fire_blaster, - ai_charge, 0, medic_checktarget, ai_charge, 0, mymedic_fire_blaster, - ai_charge, 0, medic_checktarget, ai_charge, 0, mymedic_fire_blaster, - ai_charge, 0, medic_checktarget, ai_charge, 0, mymedic_fire_blaster, - ai_charge, 0, medic_checktarget //206 + ai_charge, 0, mymedic_fire_blaster, + ai_charge, 0, mymedic_fire_blaster, + ai_charge, 0, mymedic_fire_blaster, + ai_charge, 0, mymedic_fire_blaster, // attack30 + ai_charge, 0, medic_checktarget, + ai_charge, 0, NULL, + ai_charge, 2, NULL, + ai_charge, 3, NULL }; -mmove_t mymedic_move_attackHyperBlaster = {FRAME_attack15, FRAME_attack30, mymedic_frames_attackHyperBlaster, mymedic_refire}; +mmove_t mymedic_move_attackHyperBlaster = {FRAME_attack15, FRAME_attack34, mymedic_frames_attackHyperBlaster, mymedic_refire}; void mymedic_refire(edict_t* self) { diff --git a/src/entities/drone/drone_misc.c b/src/entities/drone/drone_misc.c index 4c8ed730..a8600f21 100644 --- a/src/entities/drone/drone_misc.c +++ b/src/entities/drone/drone_misc.c @@ -2157,7 +2157,13 @@ qboolean M_Initialize (edict_t *ent, edict_t *monster, float dur_bonus) } #ifdef VRX_REPRO - if (monster->s.scale && monster->mtype != M_GUNCMDR && monster->mtype != M_BOSS2_SMALL) + if (monster->s.scale && monster->mtype != M_GUNCMDR && monster->mtype != M_BOSS2_SMALL + && monster->mtype != M_WIDOW && monster->mtype != M_WIDOW2 + && monster->mtype != M_GUARDIAN && monster->mtype != M_MINIGUARDIAN + && !(invasion->value && (monster->mtype == M_CARRIER + || monster->mtype == M_ARACHNID || monster->mtype == M_FIXBOT_BOSS + || monster->mtype == M_SUPERTANK || monster->mtype == M_BOSS5 + || monster->mtype == M_BOSS2))) { monster->monsterinfo.scale *= monster->s.scale; VectorScale(monster->mins, monster->s.scale, monster->mins); @@ -2252,8 +2258,16 @@ qboolean M_SetBoundingBox (int mtype, vec3_t boxmin, vec3_t boxmax) break; case M_SUPERTANK: case M_BOSS5: - VectorSet(boxmin, -64, -64, 0); - VectorSet(boxmax, 64, 64, 112); + if (invasion->value) + { + VectorSet(boxmin, -40, -40, 0); + VectorSet(boxmax, 40, 40, 72); + } + else + { + VectorSet(boxmin, -64, -64, 0); + VectorSet(boxmax, 64, 64, 112); + } break; case M_SHAMBLER: VectorSet(boxmin, -32, -32, -24); @@ -2284,44 +2298,100 @@ qboolean M_SetBoundingBox (int mtype, vec3_t boxmin, vec3_t boxmax) VectorSet(boxmax, 18, 18, 24); break; case M_ARACHNID: - VectorSet(boxmin, -48, -48, -20); - VectorSet(boxmax, 48, 48, 48); + if (invasion->value) + { + VectorSet(boxmin, -28, -28, -18); + VectorSet(boxmax, 28, 28, 34); + } + else + { + VectorSet(boxmin, -48, -48, -20); + VectorSet(boxmax, 48, 48, 48); + } break; case M_BOSS2: - VectorSet(boxmin, -56, -56, 0); - VectorSet(boxmax, 56, 56, 80); + if (invasion->value) + { + VectorSet(boxmin, -42, -42, 0); + VectorSet(boxmax, 42, 42, 60); + } + else + { + VectorSet(boxmin, -56, -56, 0); + VectorSet(boxmax, 56, 56, 80); + } break; case M_BOSS2_SMALL: VectorSet(boxmin, -34, -34, 0); VectorSet(boxmax, 34, 34, 48); break; case M_CARRIER: - VectorSet(boxmin, -80, -80, -24); - VectorSet(boxmax, 80, 80, 104); + if (invasion->value) + { + VectorSet(boxmin, -40, -40, -24); + VectorSet(boxmax, 40, 40, 82); + } + else + { + VectorSet(boxmin, -80, -80, -24); + VectorSet(boxmax, 80, 80, 104); + } break; case M_WIDOW: - VectorSet(boxmin, -40, -40, 0); - VectorSet(boxmax, 40, 40, 144); + if (invasion->value) + { + VectorSet(boxmin, -30, -30, 0); + VectorSet(boxmax, 30, 30, 108); + } + else + { + VectorSet(boxmin, -40, -40, 0); + VectorSet(boxmax, 40, 40, 144); + } break; case M_WIDOW2: - VectorSet(boxmin, -70, -70, 0); - VectorSet(boxmax, 70, 70, 144); + if (invasion->value) + { + VectorSet(boxmin, -40, -40, 0); + VectorSet(boxmax, 40, 40, 82); + } + else + { + VectorSet(boxmin, -70, -70, 0); + VectorSet(boxmax, 70, 70, 144); + } break; case M_FIXBOT: VectorSet(boxmin, -24, -24, -18); VectorSet(boxmax, 24, 24, 24); break; case M_FIXBOT_BOSS: - VectorSet(boxmin, -36, -36, -28); - VectorSet(boxmax, 36, 36, 28); + if (invasion->value) + { + VectorSet(boxmin, -30, -30, -24); + VectorSet(boxmax, 30, 30, 24); + } + else + { + VectorSet(boxmin, -36, -36, -28); + VectorSet(boxmax, 36, 36, 28); + } break; case M_ROGUE_TURRET: VectorSet(boxmin, -12, -12, -12); VectorSet(boxmax, 12, 12, 12); break; case M_GUARDIAN: - VectorSet(boxmin, -96, -96, -66); - VectorSet(boxmax, 96, 96, 62); + if (invasion->value) + { + VectorSet(boxmin, -44, -44, -30); + VectorSet(boxmax, 44, 44, 45); + } + else + { + VectorSet(boxmin, -96, -96, -66); + VectorSet(boxmax, 96, 96, 62); + } break; case M_JANITOR: VectorSet(boxmin, -38, -38, 0); diff --git a/src/entities/drone/drone_runnertank.c b/src/entities/drone/drone_runnertank.c index 265ed7e1..c0402a73 100644 --- a/src/entities/drone/drone_runnertank.c +++ b/src/entities/drone/drone_runnertank.c @@ -15,6 +15,7 @@ static int sound_strike; #define RUNNERTANK_JUMP_ATTACK_DROP_RADIUS 90.0f #define RUNNERTANK_JUMP_ATTACK_DROP_SPEED 900.0f #define RUNNERTANK_JUMP_ATTACK_DROP_GRAVITY 3.0f +#define RUNNERTANK_INVASION_RUN_SCALE 1.15f static void runnertank_stand(edict_t *self); static void runnertank_walk(edict_t *self); @@ -29,6 +30,11 @@ static void runnertank_doattack_rocket(edict_t *self); static void runnertank_jump_attack_takeoff(edict_t *self); static void runnertank_jump_attack_hold(edict_t *self); +static void runnertank_ai_run(edict_t *self, float dist) +{ + drone_ai_run(self, invasion->value ? dist * RUNNERTANK_INVASION_RUN_SCALE : dist); +} + static void runnertank_footstep(edict_t *self) { gi.sound(self, CHAN_BODY, sound_step, 1, ATTN_NORM, 0); @@ -177,16 +183,16 @@ static void runnertank_walk(edict_t *self) mframe_t runnertank_frames_run[] = { - drone_ai_run, 14, runnertank_footstep, - drone_ai_run, 18, NULL, - drone_ai_run, 15, NULL, - drone_ai_run, 15, NULL, - drone_ai_run, 15, NULL, - drone_ai_run, 19, runnertank_footstep, - drone_ai_run, 15, NULL, - drone_ai_run, 13, NULL, - drone_ai_run, 18, NULL, - drone_ai_run, 17, NULL + runnertank_ai_run, 14, runnertank_footstep, + runnertank_ai_run, 18, NULL, + runnertank_ai_run, 15, NULL, + runnertank_ai_run, 15, NULL, + runnertank_ai_run, 15, NULL, + runnertank_ai_run, 19, runnertank_footstep, + runnertank_ai_run, 15, NULL, + runnertank_ai_run, 13, NULL, + runnertank_ai_run, 18, NULL, + runnertank_ai_run, 17, NULL }; mmove_t runnertank_move_run = { FRAME_run01, FRAME_run10, runnertank_frames_run, NULL }; diff --git a/src/entities/drone/drone_supertank.c b/src/entities/drone/drone_supertank.c index e763d3a9..57024df8 100644 --- a/src/entities/drone/drone_supertank.c +++ b/src/entities/drone/drone_supertank.c @@ -10,6 +10,10 @@ SUPERTANK #include "../../quake2/monsterframes/m_supertank.h" qboolean visible (const edict_t *self, const edict_t *other); +#define SUPERTANK_INVASION_SCALE 0.65f +#define SUPERTANK_INVASION_BASE_HEALTH 5000 +#define SUPERTANK_INVASION_ADDON_HEALTH 1000 + static int sound_death; static int sound_search1; static int sound_search2; @@ -23,6 +27,17 @@ static qboolean supertank_is_boss5(const edict_t *self) return self->mtype == M_BOSS5; } +static void supertank_project_flash(edict_t *self, int flash_number, vec3_t forward, vec3_t start) +{ + vec3_t right, offset; + + AngleVectors(self->s.angles, forward, right, NULL); + VectorCopy(monster_flash_offset[flash_number], offset); + if (self->s.scale && self->s.scale != 1.0f) + VectorScale(offset, self->s.scale, offset); + G_ProjectSource(self->s.origin, offset, forward, right, start); +} + void TreadSound (edict_t *self) { gi.sound (self, CHAN_VOICE, tread_sound, 1, ATTN_NORM, 0); @@ -30,6 +45,7 @@ void TreadSound (edict_t *self) void supertankRocket (edict_t *self); void supertankMachineGun (edict_t *self); +void supertankGrenade (edict_t *self); void supertank_reattack1(edict_t *self); // @@ -404,42 +420,49 @@ mframe_t supertank_frames_end_attack1[]= ai_move, 0, NULL }; mmove_t supertank_move_end_attack1 = {FRAME_attak1_7, FRAME_attak1_20, supertank_frames_end_attack1, supertank_run}; -/* -void fire_mirv_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, float radius, int speed, float timer); - -void supertankMirv (edict_t *self) +void supertankGrenade (edict_t *self) { vec3_t forward, start; - int damage; + int damage, speed, flash_number; - damage = 50 + 10*self->monsterinfo.level; + if (!G_EntExists(self->enemy)) + return; + + if (self->s.frame == FRAME_attak4_1) + flash_number = MZ2_SUPERTANK_GRENADE_1; + else + flash_number = MZ2_SUPERTANK_GRENADE_2; - MonsterAim(self, 0.5, 400, true, 0, forward, start); + damage = M_GRENADELAUNCHER_DMG_BASE + M_GRENADELAUNCHER_DMG_ADDON * drone_damagelevel(self); + if (M_GRENADELAUNCHER_DMG_MAX && damage > M_GRENADELAUNCHER_DMG_MAX) + damage = M_GRENADELAUNCHER_DMG_MAX; - fire_mirv_grenade(self, start, forward, damage, 150, 400, 3.0); -} + speed = M_GRENADELAUNCHER_SPEED_BASE + M_GRENADELAUNCHER_SPEED_ADDON * drone_damagelevel(self); + if (M_GRENADELAUNCHER_SPEED_MAX && speed > M_GRENADELAUNCHER_SPEED_MAX) + speed = M_GRENADELAUNCHER_SPEED_MAX; + + if (self->s.scale && self->s.scale != 1.0f) + { + supertank_project_flash(self, flash_number, forward, start); + MonsterAim(self, M_PROJECTILE_ACC, speed, true, -1, forward, start); + } + else + MonsterAim(self, M_PROJECTILE_ACC, speed, true, flash_number, forward, start); -void supertank_reattack4(edict_t *self); + monster_fire_grenade(self, start, forward, damage, speed, flash_number); +} mframe_t supertank_frames_attack4[]= { - ai_charge, 0, supertankMirv,//74 + ai_charge, 0, supertankGrenade,//74 ai_charge, 0, NULL, ai_charge, 0, NULL, - ai_charge, 0, supertankMirv,//79 + ai_charge, 0, supertankGrenade,//77 ai_charge, 0, NULL, ai_charge, 0, NULL, }; -mmove_t supertank_move_attack4 = {FRAME_attak4_1, FRAME_attak4_6, supertank_frames_attack4, supertank_reattack4}; - -void supertank_reattack4(edict_t *self) -{ - if (G_ValidTarget(self, self->enemy, true) && random() <= 0.5) - self->monsterinfo.currentmove = &supertank_move_attack4; - self->monsterinfo.attack_finished = level.time + 1.0; -} -*/ +mmove_t supertank_move_attack4 = {FRAME_attak4_1, FRAME_attak4_6, supertank_frames_attack4, supertank_run}; void supertank_reattack1(edict_t *self) { if (G_ValidTarget(self, self->enemy, true, true) && random() <= 0.9) @@ -466,7 +489,13 @@ void supertankRocket (edict_t *self) if (supertank_is_boss5(self)) { - MonsterAim(self, 0.5, speed, true, flash_number, forward, start); + if (self->s.scale && self->s.scale != 1.0f) + { + supertank_project_flash(self, flash_number, forward, start); + MonsterAim(self, 0.5, speed, true, -1, forward, start); + } + else + MonsterAim(self, 0.5, speed, true, flash_number, forward, start); monster_fire_heat(self, start, forward, damage, speed, flash_number, 0.075f); return; } @@ -486,7 +515,15 @@ void supertankRocket (edict_t *self) MonsterAim(self, 0.5, speed, true, -1, forward, start); } else - MonsterAim(self, 0.5, speed, true, flash_number, forward, start); + { + if (self->s.scale && self->s.scale != 1.0f) + { + supertank_project_flash(self, flash_number, forward, start); + MonsterAim(self, 0.5, speed, true, -1, forward, start); + } + else + MonsterAim(self, 0.5, speed, true, flash_number, forward, start); + } monster_fire_rocket (self, start, forward, damage, speed, flash_number); } @@ -500,7 +537,13 @@ void supertankMachineGun (edict_t *self) damage = 20 + 2* drone_damagelevel(self); - MonsterAim(self, 0.8, 0, false, flash_number, forward, start); + if (self->s.scale && self->s.scale != 1.0f) + { + supertank_project_flash(self, flash_number, forward, start); + MonsterAim(self, 0.8, 0, false, -1, forward, start); + } + else + MonsterAim(self, 0.8, 0, false, flash_number, forward, start); monster_fire_bullet (self, start, forward, damage, damage, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flash_number); @@ -517,8 +560,8 @@ void supertank_attack(edict_t *self) { if (r <= 0.2) self->monsterinfo.currentmove = &supertank_move_attack1; - //else if (r <= 0.4) - // self->monsterinfo.currentmove = &supertank_move_attack4; + else if (r <= 0.4) + self->monsterinfo.currentmove = &supertank_move_attack4; else self->monsterinfo.currentmove = &supertank_move_attack2; } @@ -527,6 +570,8 @@ void supertank_attack(edict_t *self) { if (r <= 0.2) self->monsterinfo.currentmove = &supertank_move_attack2; + else if (r <= 0.4) + self->monsterinfo.currentmove = &supertank_move_attack4; else self->monsterinfo.currentmove = &supertank_move_attack1; } @@ -662,10 +707,21 @@ void init_drone_supertank (edict_t *self) { if (boss5) self->s.skinnum = 2; - VectorSet (self->mins, -64, -64, 0); - VectorSet (self->maxs, 64, 64, 112); - self->health = self->max_health = 20000*self->monsterinfo.level; - self->monsterinfo.scale = MODEL_SCALE; + if (invasion->value) + { + self->s.scale = SUPERTANK_INVASION_SCALE; + self->monsterinfo.scale = MODEL_SCALE * SUPERTANK_INVASION_SCALE; + VectorSet (self->mins, -40, -40, 0); + VectorSet (self->maxs, 40, 40, 72); + self->health = self->max_health = SUPERTANK_INVASION_BASE_HEALTH + SUPERTANK_INVASION_ADDON_HEALTH * self->monsterinfo.level; + } + else + { + VectorSet (self->mins, -64, -64, 0); + VectorSet (self->maxs, 64, 64, 112); + self->health = self->max_health = 20000*self->monsterinfo.level; + self->monsterinfo.scale = MODEL_SCALE; + } } self->gib_health = -5 * BASE_GIB_HEALTH; self->mass = janitor ? 480 : 800; @@ -693,7 +749,7 @@ void init_drone_supertank (edict_t *self) self->nextthink = level.time + FRAMETIME; gi.linkentity (self); - if (!janitor && (!self->activator || !self->activator->client)) + if (!janitor && !invasion->value && (!self->activator || !self->activator->client)) G_PrintGreenText(va("A level %d %s has spawned!", self->monsterinfo.level, boss5 ? "super tank heat" : "super tank")); } diff --git a/src/entities/drone/drone_widow.c b/src/entities/drone/drone_widow.c index ad43935b..84ff475b 100644 --- a/src/entities/drone/drone_widow.c +++ b/src/entities/drone/drone_widow.c @@ -35,6 +35,9 @@ black widow #define WIDOW_SUMMON_COUNT 2 #define WIDOW_SUMMON_COOLDOWN 10.0f #define WIDOW_MELEE_RANGE 176.0f +#define WIDOW_INVASION_SCALE 0.75f +#define WIDOW_INVASION_HALF_WIDTH 30.0f +#define WIDOW_INVASION_HEIGHT 108.0f static int sound_pain1; static int sound_pain2; @@ -275,6 +278,17 @@ static qboolean widow_can_melee(edict_t *self) return G_EntExists(self->enemy) && entdist(self, self->enemy) <= WIDOW_MELEE_RANGE; } +static void widow_project_flash(edict_t *self, int flash, vec3_t forward, vec3_t start) +{ + vec3_t right, offset; + + AngleVectors(self->s.angles, forward, right, NULL); + VectorCopy(monster_flash_offset[flash], offset); + if (self->s.scale && self->s.scale != 1.0f) + VectorScale(offset, self->s.scale, offset); + G_ProjectSource(self->s.origin, offset, forward, right, start); +} + static int widow_blaster_flash(edict_t *self) { if (self->s.frame >= WIDOW_FRAME_spawn01 + 4 && self->s.frame <= WIDOW_FRAME_spawn01 + 12) @@ -309,7 +323,8 @@ static void widow_fire_blaster(edict_t *self) flash = widow_blaster_flash(self); shotsfired++; effect = (shotsfired % 4) ? 0 : EF_BLASTER; - MonsterAim(self, M_PROJECTILE_ACC, speed, false, flash, forward, start); + widow_project_flash(self, flash, forward, start); + MonsterAim(self, M_PROJECTILE_ACC, speed, false, -1, forward, start); monster_fire_blaster2(self, start, forward, damage, speed, effect, flash); } @@ -332,7 +347,8 @@ static void widow_fire_rail(edict_t *self) flash = MZ2_WIDOW_RAIL_RIGHT; else flash = MZ2_WIDOW_RAIL; - MonsterAim(self, M_HITSCAN_INSTANT_ACC, 0, false, flash, forward, start); + widow_project_flash(self, flash, forward, start); + MonsterAim(self, M_HITSCAN_INSTANT_ACC, 0, false, -1, forward, start); gi.sound(self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0); monster_fire_railgun(self, start, forward, damage, damage, flash); } @@ -610,6 +626,12 @@ void init_drone_widow(edict_t *self) self->s.modelindex = gi.modelindex("models/monsters/blackwidow/tris.md2"); VectorSet(self->mins, -40, -40, 0); VectorSet(self->maxs, 40, 40, 144); + if (invasion->value) + { + self->s.scale = WIDOW_INVASION_SCALE; + VectorSet(self->mins, -WIDOW_INVASION_HALF_WIDTH, -WIDOW_INVASION_HALF_WIDTH, 0); + VectorSet(self->maxs, WIDOW_INVASION_HALF_WIDTH, WIDOW_INVASION_HALF_WIDTH, WIDOW_INVASION_HEIGHT); + } gi.modelindex("models/items/spawngro3/tris.md2"); gi.modelindex("models/monsters/stalker/tris.md2"); @@ -650,5 +672,6 @@ void init_drone_widow(edict_t *self) self->monsterinfo.scale = 2.0f; self->nextthink = level.time + FRAMETIME; - G_PrintGreenText(va("A level %d widow has spawned!", self->monsterinfo.level)); + if (!invasion->value) + G_PrintGreenText(va("A level %d widow has spawned!", self->monsterinfo.level)); } diff --git a/src/entities/drone/drone_widow2.c b/src/entities/drone/drone_widow2.c index 387e989f..76c7ae53 100644 --- a/src/entities/drone/drone_widow2.c +++ b/src/entities/drone/drone_widow2.c @@ -31,6 +31,8 @@ black widow 2 #define WIDOW2_SUMMON_COUNT 2 #define WIDOW2_SUMMON_COOLDOWN 10.0f #define WIDOW2_MELEE_RANGE 256.0f +#define WIDOW2_INVASION_SCALE 0.60f +#define WIDOW2_INVASION_MOVE_SCALE 1.75f static int sound_pain1; static int sound_pain2; @@ -59,6 +61,16 @@ void drone_ai_stand(edict_t *self, float dist); void drone_ai_run(edict_t *self, float dist); void drone_ai_walk(edict_t *self, float dist); +static void widow2_ai_walk(edict_t *self, float dist) +{ + drone_ai_walk(self, invasion->value ? dist * WIDOW2_INVASION_MOVE_SCALE : dist); +} + +static void widow2_ai_run(edict_t *self, float dist) +{ + drone_ai_run(self, invasion->value ? dist * WIDOW2_INVASION_MOVE_SCALE : dist); +} + static void widow2_stand(edict_t *self); static void widow2_walk(edict_t *self); static void widow2_run(edict_t *self); @@ -83,29 +95,29 @@ static mmove_t widow2_move_stand = { WIDOW2_FRAME_blackwidow3, WIDOW2_FRAME_blac static mframe_t widow2_frames_walk[] = { - drone_ai_walk, 9, widow2_step, - drone_ai_walk, 8, NULL, - drone_ai_walk, 7, NULL, - drone_ai_walk, 7, NULL, - drone_ai_walk, 6, NULL, - drone_ai_walk, 6, widow2_step, - drone_ai_walk, 7, NULL, - drone_ai_walk, 8, NULL, - drone_ai_walk, 10, NULL + widow2_ai_walk, 9, widow2_step, + widow2_ai_walk, 8, NULL, + widow2_ai_walk, 7, NULL, + widow2_ai_walk, 7, NULL, + widow2_ai_walk, 6, NULL, + widow2_ai_walk, 6, widow2_step, + widow2_ai_walk, 7, NULL, + widow2_ai_walk, 8, NULL, + widow2_ai_walk, 10, NULL }; static mmove_t widow2_move_walk = { WIDOW2_FRAME_walk01, WIDOW2_FRAME_walk09, widow2_frames_walk, widow2_walk }; static mframe_t widow2_frames_run[] = { - drone_ai_run, 9, widow2_step, - drone_ai_run, 8, NULL, - drone_ai_run, 7, NULL, - drone_ai_run, 7, NULL, - drone_ai_run, 6, NULL, - drone_ai_run, 6, widow2_step, - drone_ai_run, 7, NULL, - drone_ai_run, 8, NULL, - drone_ai_run, 10, NULL + widow2_ai_run, 9, widow2_step, + widow2_ai_run, 8, NULL, + widow2_ai_run, 7, NULL, + widow2_ai_run, 7, NULL, + widow2_ai_run, 6, NULL, + widow2_ai_run, 6, widow2_step, + widow2_ai_run, 7, NULL, + widow2_ai_run, 8, NULL, + widow2_ai_run, 10, NULL }; static mmove_t widow2_move_run = { WIDOW2_FRAME_walk01, WIDOW2_FRAME_walk09, widow2_frames_run, NULL }; @@ -270,6 +282,17 @@ static qboolean widow2_can_melee(edict_t *self) return G_EntExists(self->enemy) && entdist(self, self->enemy) <= WIDOW2_MELEE_RANGE; } +static void widow2_project_flash(edict_t *self, int flash, vec3_t forward, vec3_t start) +{ + vec3_t right, offset; + + AngleVectors(self->s.angles, forward, right, NULL); + VectorCopy(monster_flash_offset[flash], offset); + if (self->s.scale && self->s.scale != 1.0f) + VectorScale(offset, self->s.scale, offset); + G_ProjectSource(self->s.origin, offset, forward, right, start); +} + static void widow2_fire_beam(edict_t *self) { int damage; @@ -290,7 +313,8 @@ static void widow2_fire_beam(edict_t *self) else flash = MZ2_WIDOW2_BEAM_SWEEP_1; - MonsterAim(self, M_HITSCAN_INSTANT_ACC, 0, false, flash, forward, start); + widow2_project_flash(self, flash, forward, start); + MonsterAim(self, M_HITSCAN_INSTANT_ACC, 0, false, -1, forward, start); gi.sound(self, CHAN_WEAPON, sound_beam, 1, ATTN_NORM, 0); monster_fire_railgun(self, start, forward, damage, damage, flash); } @@ -311,7 +335,8 @@ static void widow2_fire_disruptor(edict_t *self) if (M_DISRUPTOR_SPEED_MAX && speed > M_DISRUPTOR_SPEED_MAX) speed = M_DISRUPTOR_SPEED_MAX; - MonsterAim(self, M_PROJECTILE_ACC, speed, true, MZ2_WIDOW_DISRUPTOR, forward, start); + widow2_project_flash(self, MZ2_WIDOW_DISRUPTOR, forward, start); + MonsterAim(self, M_PROJECTILE_ACC, speed, true, -1, forward, start); fire_disruptor(self, start, forward, damage, speed, visible(self, self->enemy) ? self->enemy : NULL); gi.WriteByte(svc_muzzleflash2); gi.WriteShort(self - g_edicts); @@ -337,6 +362,8 @@ static void widow2_proboscis_start(edict_t *self, vec3_t start) index = 7; VectorCopy(widow2_tongue_offsets[index], offset); + if (self->s.scale && self->s.scale != 1.0f) + VectorScale(offset, self->s.scale, offset); AngleVectors(self->s.angles, forward, right, NULL); G_ProjectSource(self->s.origin, offset, forward, right, start); } @@ -701,6 +728,12 @@ void init_drone_widow2(edict_t *self) self->s.modelindex = gi.modelindex("models/monsters/blackwidow2/tris.md2"); VectorSet(self->mins, -70, -70, 0); VectorSet(self->maxs, 70, 70, 144); + if (invasion->value) + { + self->s.scale = WIDOW2_INVASION_SCALE; + VectorSet(self->mins, -40, -40, 0); + VectorSet(self->maxs, 40, 40, 82); + } gi.modelindex("models/items/spawngro3/tris.md2"); gi.modelindex("models/monsters/stalker/tris.md2"); @@ -741,5 +774,6 @@ void init_drone_widow2(edict_t *self) self->monsterinfo.scale = 2.0f; self->nextthink = level.time + FRAMETIME; - G_PrintGreenText(va("A level %d widow2 has spawned!", self->monsterinfo.level)); + if (!invasion->value) + G_PrintGreenText(va("A level %d widow2 has spawned!", self->monsterinfo.level)); } diff --git a/src/entities/drone/g_monster.c b/src/entities/drone/g_monster.c index 0490eb23..10d5077e 100644 --- a/src/entities/drone/g_monster.c +++ b/src/entities/drone/g_monster.c @@ -19,6 +19,27 @@ float vrx_increase_monster_damage_by_talent(edict_t *owner, float damage) // monster weapons // +static void monster_muzzleflash(edict_t *self, vec3_t start, int flashtype) +{ + if (flashtype < 0) + return; + + if (flashtype <= 255) + { + gi.WriteByte(svc_muzzleflash2); + gi.WriteShort(self - g_edicts); + gi.WriteByte(flashtype); + } + else + { + gi.WriteByte(svc_muzzleflash3); + gi.WriteShort(self - g_edicts); + gi.WriteShort(flashtype); + } + + gi.multicast(start, MULTICAST_PVS); +} + //FIXME mosnters should call these with a totally accurate direction // and we can mess it up based on skill. Spread should be for normal // and we can tighten or loosen based on skill. We could muck with @@ -45,13 +66,7 @@ void monster_fire_bullet (edict_t *self, vec3_t start, vec3_t dir, int damage, i damage = vrx_increase_monster_damage_by_talent(self->activator, damage); fire_bullet (self, start, dir, damage, kick, hspread, vspread, MOD_UNKNOWN); - if (flashtype >= 0) - { - gi.WriteByte (svc_muzzleflash2); - gi.WriteShort (self - g_edicts); - gi.WriteByte (flashtype); - gi.multicast (start, MULTICAST_PVS); - } + monster_muzzleflash(self, start, flashtype); } static void debris_die(edict_t* self, edict_t* inflictor, edict_t* attacker, int damage, vec3_t point) @@ -146,13 +161,7 @@ void monster_fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, float dam damage = vrx_increase_monster_damage_by_talent(self->activator, damage); fire_shotgun (self, start, aimdir, damage, kick, hspread, vspread, count, MOD_UNKNOWN); - if (flashtype >= 0) - { - gi.WriteByte (svc_muzzleflash2); - gi.WriteShort (self - g_edicts); - gi.WriteByte (flashtype); - gi.multicast (start, MULTICAST_PVS); - } + monster_muzzleflash(self, start, flashtype); } void monster_fire_blaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect, int proj_type, float duration, qboolean bounce, int flashtype) @@ -184,13 +193,7 @@ void monster_fire_blaster (edict_t *self, vec3_t start, vec3_t dir, int damage, damage = vrx_increase_monster_damage_by_talent(self->activator, damage); fire_blaster(self, start, dir, damage, speed, effect, proj_type, mod, duration, bounce); - if (flashtype >= 0) - { - gi.WriteByte (svc_muzzleflash2); - gi.WriteShort (self - g_edicts); - gi.WriteByte (flashtype); - gi.multicast (start, MULTICAST_PVS); - } + monster_muzzleflash(self, start, flashtype); } void monster_fire_blaster2(edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect, int flashtype) @@ -210,10 +213,7 @@ void monster_fire_blaster2(edict_t *self, vec3_t start, vec3_t dir, int damage, damage = vrx_increase_monster_damage_by_talent(self->activator, damage); fire_blaster2(self, start, dir, damage, speed, effect, false); - gi.WriteByte(svc_muzzleflash2); - gi.WriteShort(self - g_edicts); - gi.WriteByte(flashtype); - gi.multicast(start, MULTICAST_PVS); + monster_muzzleflash(self, start, flashtype); } void monster_fire_blueblaster(edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect, int flashtype) @@ -233,10 +233,7 @@ void monster_fire_blueblaster(edict_t *self, vec3_t start, vec3_t dir, int damag damage = vrx_increase_monster_damage_by_talent(self->activator, damage); fire_blueblaster(self, start, dir, damage, speed, effect); - gi.WriteByte(svc_muzzleflash2); - gi.WriteShort(self - g_edicts); - gi.WriteByte(flashtype); - gi.multicast(start, MULTICAST_PVS); + monster_muzzleflash(self, start, flashtype); } void monster_fire_ionripper(edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect, int flashtype) @@ -256,10 +253,7 @@ void monster_fire_ionripper(edict_t *self, vec3_t start, vec3_t dir, int damage, damage = vrx_increase_monster_damage_by_talent(self->activator, damage); fire_ionripper(self, start, dir, damage, speed, effect); - gi.WriteByte(svc_muzzleflash2); - gi.WriteShort(self - g_edicts); - gi.WriteByte(flashtype); - gi.multicast(start, MULTICAST_PVS); + monster_muzzleflash(self, start, flashtype); } static void dabeam_think(edict_t *self) @@ -377,6 +371,8 @@ void monster_fire_dabeam(edict_t *self, int damage, qboolean secondary, void (*u VectorSet(offset, 112, -62, 60); else VectorSet(offset, 125, -70, 60); + if (self->s.scale && self->s.scale != 1.0f) + VectorScale(offset, self->s.scale, offset); G_ProjectSource(self->s.origin, offset, forward, right, start); MonsterAim(self, M_HITSCAN_CONT_ACC, 0, false, -1, forward, start); } @@ -422,13 +418,7 @@ void monster_fire_dabeam(edict_t *self, int damage, qboolean secondary, void (*u VectorCopy(tr.endpos, beam->pos2); gi.linkentity(beam); - if (flashtype >= 0) - { - gi.WriteByte(svc_muzzleflash2); - gi.WriteShort(self - g_edicts); - gi.WriteByte(flashtype); - gi.multicast(start, MULTICAST_PVS); - } + monster_muzzleflash(self, start, flashtype); } void rocket_touch(edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf); @@ -585,10 +575,7 @@ qboolean monster_fire_heat(edict_t *self, vec3_t start, vec3_t dir, int damage, if (self->client) check_dodge(self, heat->s.origin, dir, speed, damage); - gi.WriteByte(svc_muzzleflash2); - gi.WriteShort(self - g_edicts); - gi.WriteByte(flashtype); - gi.multicast(start, MULTICAST_PVS); + monster_muzzleflash(self, start, flashtype); return true; } @@ -625,13 +612,7 @@ void monster_fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damag damage = vrx_increase_monster_damage_by_talent(self->activator, damage); fire_grenade (self, start, aimdir, damage, speed, 2.5, radius, damage); - if (flashtype >= 0) - { - gi.WriteByte (svc_muzzleflash2); - gi.WriteShort (self - g_edicts); - gi.WriteByte (flashtype); - gi.multicast (start, MULTICAST_PVS); - } + monster_muzzleflash(self, start, flashtype); } void monster_fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype) @@ -666,10 +647,7 @@ void monster_fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, i damage = vrx_increase_monster_damage_by_talent(self->activator, damage); fire_rocket (self, start, dir, damage, speed, radius, damage); - gi.WriteByte (svc_muzzleflash2); - gi.WriteShort (self - g_edicts); - gi.WriteByte (flashtype); - gi.multicast (start, MULTICAST_PVS); + monster_muzzleflash(self, start, flashtype); } void monster_fire_railgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int flashtype) @@ -694,10 +672,7 @@ void monster_fire_railgun (edict_t *self, vec3_t start, vec3_t aimdir, int damag damage = vrx_increase_monster_damage_by_talent(self->activator, damage); fire_rail (self, start, aimdir, damage, kick); - gi.WriteByte (svc_muzzleflash2); - gi.WriteShort (self - g_edicts); - gi.WriteByte (flashtype); - gi.multicast (start, MULTICAST_PVS); + monster_muzzleflash(self, start, flashtype); } void monster_fire_bfg (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int kick, float damage_radius, int flashtype) @@ -722,10 +697,7 @@ void monster_fire_bfg (edict_t *self, vec3_t start, vec3_t aimdir, int damage, i damage = vrx_increase_monster_damage_by_talent(self->activator, damage); fire_bfg (self, start, aimdir, damage, speed, damage_radius); - gi.WriteByte (svc_muzzleflash2); - gi.WriteShort (self - g_edicts); - gi.WriteByte (flashtype); - gi.multicast (start, MULTICAST_PVS); + monster_muzzleflash(self, start, flashtype); } void fire_sword ( edict_t *self, vec3_t start, vec3_t aimdir, int damage, int length, int color); @@ -734,10 +706,7 @@ void monster_fire_sword (edict_t *self, vec3_t start, vec3_t aimdir, int damage, damage = vrx_increase_monster_damage_by_talent(self->activator, damage); fire_sword (self, start, aimdir, damage, kick, 0xd2d3d2d3); - gi.WriteByte (svc_muzzleflash2); - gi.WriteShort (self - g_edicts); - gi.WriteByte (flashtype); - gi.multicast (start, MULTICAST_PVS); + monster_muzzleflash(self, start, flashtype); } void monster_fire_fireball(edict_t* self) diff --git a/src/gamemodes/invasion.c b/src/gamemodes/invasion.c index 8f3e40be..bc2cf784 100644 --- a/src/gamemodes/invasion.c +++ b/src/gamemodes/invasion.c @@ -89,7 +89,16 @@ static constexpr int SET_PARASITE_MONSTERS[] = { constexpr int SET_PARASITE_MONSTERS_COUNT = sizeof(SET_PARASITE_MONSTERS) / sizeof(int); static constexpr int SET_BOSS_MONSTERS[] = { - DS_COMMANDER, DS_MAKRON, DS_BARON_FIRE, DS_CARRIER + DS_GUARDIAN, + DS_FIXBOT_BOSS, + DS_WIDOW2, + DS_WIDOW, + DS_CARRIER, + DS_BOSS2, + DS_BOSS5, + DS_BARON_FIRE, + DS_MAKRON, + DS_COMMANDER, }; constexpr int SET_BOSS_MONSTERS_COUNT = sizeof(SET_BOSS_MONSTERS) / sizeof(int); diff --git a/src/quake2/monsterframes/m_guncmdr.h b/src/quake2/monsterframes/m_guncmdr.h deleted file mode 100644 index e1a3e9fc..00000000 --- a/src/quake2/monsterframes/m_guncmdr.h +++ /dev/null @@ -1,809 +0,0 @@ -// Copyright (c) ZeniMax Media Inc. -// Licensed under the GNU General Public License 2.0. -// E:\G Drive\md2f\quake2\baseq2\models/monsters/gunner - -// This file generated by qdata - Do NOT Modify - -enum { - FRAME_stand01, - FRAME_stand02, - FRAME_stand03, - FRAME_stand04, - FRAME_stand05, - FRAME_stand06, - FRAME_stand07, - FRAME_stand08, - FRAME_stand09, - FRAME_stand10, - FRAME_stand11, - FRAME_stand12, - FRAME_stand13, - FRAME_stand14, - FRAME_stand15, - FRAME_stand16, - FRAME_stand17, - FRAME_stand18, - FRAME_stand19, - FRAME_stand20, - FRAME_stand21, - FRAME_stand22, - FRAME_stand23, - FRAME_stand24, - FRAME_stand25, - FRAME_stand26, - FRAME_stand27, - FRAME_stand28, - FRAME_stand29, - FRAME_stand30, - FRAME_stand31, - FRAME_stand32, - FRAME_stand33, - FRAME_stand34, - FRAME_stand35, - FRAME_stand36, - FRAME_stand37, - FRAME_stand38, - FRAME_stand39, - FRAME_stand40, - FRAME_stand41, - FRAME_stand42, - FRAME_stand43, - FRAME_stand44, - FRAME_stand45, - FRAME_stand46, - FRAME_stand47, - FRAME_stand48, - FRAME_stand49, - FRAME_stand50, - FRAME_stand51, - FRAME_stand52, - FRAME_stand53, - FRAME_stand54, - FRAME_stand55, - FRAME_stand56, - FRAME_stand57, - FRAME_stand58, - FRAME_stand59, - FRAME_stand60, - FRAME_stand61, - FRAME_stand62, - FRAME_stand63, - FRAME_stand64, - FRAME_stand65, - FRAME_stand66, - FRAME_stand67, - FRAME_stand68, - FRAME_stand69, - FRAME_stand70, - FRAME_walk01, - FRAME_walk02, - FRAME_walk03, - FRAME_walk04, - FRAME_walk05, - FRAME_walk06, - FRAME_walk07, - FRAME_walk08, - FRAME_walk09, - FRAME_walk10, - FRAME_walk11, - FRAME_walk12, - FRAME_walk13, - FRAME_walk14, - FRAME_walk15, - FRAME_walk16, - FRAME_walk17, - FRAME_walk18, - FRAME_walk19, - FRAME_walk20, - FRAME_walk21, - FRAME_walk22, - FRAME_walk23, - FRAME_walk24, - FRAME_run01, - FRAME_run02, - FRAME_run03, - FRAME_run04, - FRAME_run05, - FRAME_run06, - FRAME_run07, - FRAME_run08, - FRAME_runs01, - FRAME_runs02, - FRAME_runs03, - FRAME_runs04, - FRAME_runs05, - FRAME_runs06, - FRAME_attak101, - FRAME_attak102, - FRAME_attak103, - FRAME_attak104, - FRAME_attak105, - FRAME_attak106, - FRAME_attak107, - FRAME_attak108, - FRAME_attak109, - FRAME_attak110, - FRAME_attak111, - FRAME_attak112, - FRAME_attak113, - FRAME_attak114, - FRAME_attak115, - FRAME_attak116, - FRAME_attak117, - FRAME_attak118, - FRAME_attak119, - FRAME_attak120, - FRAME_attak121, - FRAME_attak201, - FRAME_attak202, - FRAME_attak203, - FRAME_attak204, - FRAME_attak205, - FRAME_attak206, - FRAME_attak207, - FRAME_attak208, - FRAME_attak209, - FRAME_attak210, - FRAME_attak211, - FRAME_attak212, - FRAME_attak213, - FRAME_attak214, - FRAME_attak215, - FRAME_attak216, - FRAME_attak217, - FRAME_attak218, - FRAME_attak219, - FRAME_attak220, - FRAME_attak221, - FRAME_attak222, - FRAME_attak223, - FRAME_attak224, - FRAME_attak225, - FRAME_attak226, - FRAME_attak227, - FRAME_attak228, - FRAME_attak229, - FRAME_attak230, - FRAME_pain101, - FRAME_pain102, - FRAME_pain103, - FRAME_pain104, - FRAME_pain105, - FRAME_pain106, - FRAME_pain107, - FRAME_pain108, - FRAME_pain109, - FRAME_pain110, - FRAME_pain111, - FRAME_pain112, - FRAME_pain113, - FRAME_pain114, - FRAME_pain115, - FRAME_pain116, - FRAME_pain117, - FRAME_pain118, - FRAME_pain201, - FRAME_pain202, - FRAME_pain203, - FRAME_pain204, - FRAME_pain205, - FRAME_pain206, - FRAME_pain207, - FRAME_pain208, - FRAME_pain301, - FRAME_pain302, - FRAME_pain303, - FRAME_pain304, - FRAME_pain305, - FRAME_death01, - FRAME_death02, - FRAME_death03, - FRAME_death04, - FRAME_death05, - FRAME_death06, - FRAME_death07, - FRAME_death08, - FRAME_death09, - FRAME_death10, - FRAME_death11, - FRAME_duck01, - FRAME_duck02, - FRAME_duck03, - FRAME_duck04, - FRAME_duck05, - FRAME_duck06, - FRAME_duck07, - FRAME_duck08, - FRAME_jump01, - FRAME_jump02, - FRAME_jump03, - FRAME_jump04, - FRAME_jump05, - FRAME_jump06, - FRAME_jump07, - FRAME_jump08, - FRAME_jump09, - FRAME_jump10, - FRAME_shield01, - FRAME_shield02, - FRAME_shield03, - FRAME_shield04, - FRAME_shield05, - FRAME_shield06, - FRAME_attak301, - FRAME_attak302, - FRAME_attak303, - FRAME_attak304, - FRAME_attak305, - FRAME_attak306, - FRAME_attak307, - FRAME_attak308, - FRAME_attak309, - FRAME_attak310, - FRAME_attak311, - FRAME_attak312, - FRAME_attak313, - FRAME_attak314, - FRAME_attak315, - FRAME_attak316, - FRAME_attak317, - FRAME_attak318, - FRAME_attak319, - FRAME_attak320, - FRAME_attak321, - FRAME_attak322, - FRAME_attak323, - FRAME_attak324, - FRAME_c_stand101, - FRAME_c_stand102, - FRAME_c_stand103, - FRAME_c_stand104, - FRAME_c_stand105, - FRAME_c_stand106, - FRAME_c_stand107, - FRAME_c_stand108, - FRAME_c_stand109, - FRAME_c_stand110, - FRAME_c_stand111, - FRAME_c_stand112, - FRAME_c_stand113, - FRAME_c_stand114, - FRAME_c_stand115, - FRAME_c_stand116, - FRAME_c_stand117, - FRAME_c_stand118, - FRAME_c_stand119, - FRAME_c_stand120, - FRAME_c_stand121, - FRAME_c_stand122, - FRAME_c_stand123, - FRAME_c_stand124, - FRAME_c_stand125, - FRAME_c_stand126, - FRAME_c_stand127, - FRAME_c_stand128, - FRAME_c_stand129, - FRAME_c_stand130, - FRAME_c_stand131, - FRAME_c_stand132, - FRAME_c_stand133, - FRAME_c_stand134, - FRAME_c_stand135, - FRAME_c_stand136, - FRAME_c_stand137, - FRAME_c_stand138, - FRAME_c_stand139, - FRAME_c_stand140, - FRAME_c_stand201, - FRAME_c_stand202, - FRAME_c_stand203, - FRAME_c_stand204, - FRAME_c_stand205, - FRAME_c_stand206, - FRAME_c_stand207, - FRAME_c_stand208, - FRAME_c_stand209, - FRAME_c_stand210, - FRAME_c_stand211, - FRAME_c_stand212, - FRAME_c_stand213, - FRAME_c_stand214, - FRAME_c_stand215, - FRAME_c_stand216, - FRAME_c_stand217, - FRAME_c_stand218, - FRAME_c_stand219, - FRAME_c_stand220, - FRAME_c_stand221, - FRAME_c_stand222, - FRAME_c_stand223, - FRAME_c_stand224, - FRAME_c_stand225, - FRAME_c_stand226, - FRAME_c_stand227, - FRAME_c_stand228, - FRAME_c_stand229, - FRAME_c_stand230, - FRAME_c_stand231, - FRAME_c_stand232, - FRAME_c_stand233, - FRAME_c_stand234, - FRAME_c_stand235, - FRAME_c_stand236, - FRAME_c_stand237, - FRAME_c_stand238, - FRAME_c_stand239, - FRAME_c_stand240, - FRAME_c_stand241, - FRAME_c_stand242, - FRAME_c_stand243, - FRAME_c_stand244, - FRAME_c_stand245, - FRAME_c_stand246, - FRAME_c_stand247, - FRAME_c_stand248, - FRAME_c_stand249, - FRAME_c_stand250, - FRAME_c_stand251, - FRAME_c_stand252, - FRAME_c_stand253, - FRAME_c_stand254, - FRAME_c_attack101, - FRAME_c_attack102, - FRAME_c_attack103, - FRAME_c_attack104, - FRAME_c_attack105, - FRAME_c_attack106, - FRAME_c_attack107, - FRAME_c_attack108, - FRAME_c_attack109, - FRAME_c_attack110, - FRAME_c_attack111, - FRAME_c_attack112, - FRAME_c_attack113, - FRAME_c_attack114, - FRAME_c_attack115, - FRAME_c_attack116, - FRAME_c_attack117, - FRAME_c_attack118, - FRAME_c_attack119, - FRAME_c_attack120, - FRAME_c_attack121, - FRAME_c_attack122, - FRAME_c_attack123, - FRAME_c_attack124, - FRAME_c_jump01, - FRAME_c_jump02, - FRAME_c_jump03, - FRAME_c_jump04, - FRAME_c_jump05, - FRAME_c_jump06, - FRAME_c_jump07, - FRAME_c_jump08, - FRAME_c_jump09, - FRAME_c_jump10, - FRAME_c_attack201, - FRAME_c_attack202, - FRAME_c_attack203, - FRAME_c_attack204, - FRAME_c_attack205, - FRAME_c_attack206, - FRAME_c_attack207, - FRAME_c_attack208, - FRAME_c_attack209, - FRAME_c_attack210, - FRAME_c_attack211, - FRAME_c_attack212, - FRAME_c_attack213, - FRAME_c_attack214, - FRAME_c_attack215, - FRAME_c_attack216, - FRAME_c_attack217, - FRAME_c_attack218, - FRAME_c_attack219, - FRAME_c_attack220, - FRAME_c_attack221, - FRAME_c_attack301, - FRAME_c_attack302, - FRAME_c_attack303, - FRAME_c_attack304, - FRAME_c_attack305, - FRAME_c_attack306, - FRAME_c_attack307, - FRAME_c_attack308, - FRAME_c_attack309, - FRAME_c_attack310, - FRAME_c_attack311, - FRAME_c_attack312, - FRAME_c_attack313, - FRAME_c_attack314, - FRAME_c_attack315, - FRAME_c_attack316, - FRAME_c_attack317, - FRAME_c_attack318, - FRAME_c_attack319, - FRAME_c_attack320, - FRAME_c_attack321, - FRAME_c_attack401, - FRAME_c_attack402, - FRAME_c_attack403, - FRAME_c_attack404, - FRAME_c_attack405, - FRAME_c_attack501, - FRAME_c_attack502, - FRAME_c_attack503, - FRAME_c_attack504, - FRAME_c_attack505, - FRAME_c_attack601, - FRAME_c_attack602, - FRAME_c_attack603, - FRAME_c_attack604, - FRAME_c_attack605, - FRAME_c_attack701, - FRAME_c_attack702, - FRAME_c_attack703, - FRAME_c_attack704, - FRAME_c_attack705, - FRAME_c_pain101, - FRAME_c_pain102, - FRAME_c_pain103, - FRAME_c_pain104, - FRAME_c_pain201, - FRAME_c_pain202, - FRAME_c_pain203, - FRAME_c_pain204, - FRAME_c_pain301, - FRAME_c_pain302, - FRAME_c_pain303, - FRAME_c_pain304, - FRAME_c_pain401, - FRAME_c_pain402, - FRAME_c_pain403, - FRAME_c_pain404, - FRAME_c_pain405, - FRAME_c_pain406, - FRAME_c_pain407, - FRAME_c_pain408, - FRAME_c_pain409, - FRAME_c_pain410, - FRAME_c_pain411, - FRAME_c_pain412, - FRAME_c_pain413, - FRAME_c_pain414, - FRAME_c_pain415, - FRAME_c_pain501, - FRAME_c_pain502, - FRAME_c_pain503, - FRAME_c_pain504, - FRAME_c_pain505, - FRAME_c_pain506, - FRAME_c_pain507, - FRAME_c_pain508, - FRAME_c_pain509, - FRAME_c_pain510, - FRAME_c_pain511, - FRAME_c_pain512, - FRAME_c_pain513, - FRAME_c_pain514, - FRAME_c_pain515, - FRAME_c_pain516, - FRAME_c_pain517, - FRAME_c_pain518, - FRAME_c_pain519, - FRAME_c_pain520, - FRAME_c_pain521, - FRAME_c_pain522, - FRAME_c_pain523, - FRAME_c_pain524, - FRAME_c_death101, - FRAME_c_death102, - FRAME_c_death103, - FRAME_c_death104, - FRAME_c_death105, - FRAME_c_death106, - FRAME_c_death107, - FRAME_c_death108, - FRAME_c_death109, - FRAME_c_death110, - FRAME_c_death111, - FRAME_c_death112, - FRAME_c_death113, - FRAME_c_death114, - FRAME_c_death115, - FRAME_c_death116, - FRAME_c_death117, - FRAME_c_death118, - FRAME_c_death201, - FRAME_c_death202, - FRAME_c_death203, - FRAME_c_death204, - FRAME_c_death301, - FRAME_c_death302, - FRAME_c_death303, - FRAME_c_death304, - FRAME_c_death305, - FRAME_c_death306, - FRAME_c_death307, - FRAME_c_death308, - FRAME_c_death309, - FRAME_c_death310, - FRAME_c_death311, - FRAME_c_death312, - FRAME_c_death313, - FRAME_c_death314, - FRAME_c_death315, - FRAME_c_death316, - FRAME_c_death317, - FRAME_c_death318, - FRAME_c_death319, - FRAME_c_death320, - FRAME_c_death321, - FRAME_c_death401, - FRAME_c_death402, - FRAME_c_death403, - FRAME_c_death404, - FRAME_c_death405, - FRAME_c_death406, - FRAME_c_death407, - FRAME_c_death408, - FRAME_c_death409, - FRAME_c_death410, - FRAME_c_death411, - FRAME_c_death412, - FRAME_c_death413, - FRAME_c_death414, - FRAME_c_death415, - FRAME_c_death416, - FRAME_c_death417, - FRAME_c_death418, - FRAME_c_death419, - FRAME_c_death420, - FRAME_c_death421, - FRAME_c_death422, - FRAME_c_death423, - FRAME_c_death424, - FRAME_c_death425, - FRAME_c_death426, - FRAME_c_death427, - FRAME_c_death428, - FRAME_c_death429, - FRAME_c_death430, - FRAME_c_death431, - FRAME_c_death432, - FRAME_c_death433, - FRAME_c_death434, - FRAME_c_death435, - FRAME_c_death436, - FRAME_c_death501, - FRAME_c_death502, - FRAME_c_death503, - FRAME_c_death504, - FRAME_c_death505, - FRAME_c_death506, - FRAME_c_death507, - FRAME_c_death508, - FRAME_c_death509, - FRAME_c_death510, - FRAME_c_death511, - FRAME_c_death512, - FRAME_c_death513, - FRAME_c_death514, - FRAME_c_death515, - FRAME_c_death516, - FRAME_c_death517, - FRAME_c_death518, - FRAME_c_death519, - FRAME_c_death520, - FRAME_c_death521, - FRAME_c_death522, - FRAME_c_death523, - FRAME_c_death524, - FRAME_c_death525, - FRAME_c_death526, - FRAME_c_death527, - FRAME_c_death528, - FRAME_c_run101, - FRAME_c_run102, - FRAME_c_run103, - FRAME_c_run104, - FRAME_c_run105, - FRAME_c_run106, - FRAME_c_run201, - FRAME_c_run202, - FRAME_c_run203, - FRAME_c_run204, - FRAME_c_run205, - FRAME_c_run206, - FRAME_c_run301, - FRAME_c_run302, - FRAME_c_run303, - FRAME_c_run304, - FRAME_c_run305, - FRAME_c_run306, - FRAME_c_walk101, - FRAME_c_walk102, - FRAME_c_walk103, - FRAME_c_walk104, - FRAME_c_walk105, - FRAME_c_walk106, - FRAME_c_walk107, - FRAME_c_walk108, - FRAME_c_walk109, - FRAME_c_walk110, - FRAME_c_walk111, - FRAME_c_walk112, - FRAME_c_walk113, - FRAME_c_walk114, - FRAME_c_walk115, - FRAME_c_walk116, - FRAME_c_walk117, - FRAME_c_walk118, - FRAME_c_walk119, - FRAME_c_walk120, - FRAME_c_walk121, - FRAME_c_walk122, - FRAME_c_walk123, - FRAME_c_walk124, - FRAME_c_pain601, - FRAME_c_pain602, - FRAME_c_pain603, - FRAME_c_pain604, - FRAME_c_pain605, - FRAME_c_pain606, - FRAME_c_pain607, - FRAME_c_pain608, - FRAME_c_pain609, - FRAME_c_pain610, - FRAME_c_pain611, - FRAME_c_pain612, - FRAME_c_pain613, - FRAME_c_pain614, - FRAME_c_pain615, - FRAME_c_pain616, - FRAME_c_pain617, - FRAME_c_pain618, - FRAME_c_pain619, - FRAME_c_pain620, - FRAME_c_pain621, - FRAME_c_pain622, - FRAME_c_pain623, - FRAME_c_pain624, - FRAME_c_pain625, - FRAME_c_pain626, - FRAME_c_pain627, - FRAME_c_pain628, - FRAME_c_pain629, - FRAME_c_pain630, - FRAME_c_pain631, - FRAME_c_pain632, - FRAME_c_death601, - FRAME_c_death602, - FRAME_c_death603, - FRAME_c_death604, - FRAME_c_death605, - FRAME_c_death606, - FRAME_c_death607, - FRAME_c_death608, - FRAME_c_death609, - FRAME_c_death610, - FRAME_c_death611, - FRAME_c_death612, - FRAME_c_death613, - FRAME_c_death614, - FRAME_c_death701, - FRAME_c_death702, - FRAME_c_death703, - FRAME_c_death704, - FRAME_c_death705, - FRAME_c_death706, - FRAME_c_death707, - FRAME_c_death708, - FRAME_c_death709, - FRAME_c_death710, - FRAME_c_death711, - FRAME_c_death712, - FRAME_c_death713, - FRAME_c_death714, - FRAME_c_death715, - FRAME_c_death716, - FRAME_c_death717, - FRAME_c_death718, - FRAME_c_death719, - FRAME_c_death720, - FRAME_c_death721, - FRAME_c_death722, - FRAME_c_death723, - FRAME_c_death724, - FRAME_c_death725, - FRAME_c_death726, - FRAME_c_death727, - FRAME_c_death728, - FRAME_c_death729, - FRAME_c_death730, - FRAME_c_pain701, - FRAME_c_pain702, - FRAME_c_pain703, - FRAME_c_pain704, - FRAME_c_pain705, - FRAME_c_pain706, - FRAME_c_pain707, - FRAME_c_pain708, - FRAME_c_pain709, - FRAME_c_pain710, - FRAME_c_pain711, - FRAME_c_pain712, - FRAME_c_pain713, - FRAME_c_pain714, - FRAME_c_attack801, - FRAME_c_attack802, - FRAME_c_attack803, - FRAME_c_attack804, - FRAME_c_attack805, - FRAME_c_attack806, - FRAME_c_attack807, - FRAME_c_attack808, - FRAME_c_attack809, - FRAME_c_attack901, - FRAME_c_attack902, - FRAME_c_attack903, - FRAME_c_attack904, - FRAME_c_attack905, - FRAME_c_attack906, - FRAME_c_attack907, - FRAME_c_attack908, - FRAME_c_attack909, - FRAME_c_attack910, - FRAME_c_attack911, - FRAME_c_attack912, - FRAME_c_attack913, - FRAME_c_attack914, - FRAME_c_attack915, - FRAME_c_attack916, - FRAME_c_attack917, - FRAME_c_attack918, - FRAME_c_attack919, - FRAME_c_duck01, - FRAME_c_duck02, - FRAME_c_duckstep01, - FRAME_c_duckstep02, - FRAME_c_duckstep03, - FRAME_c_duckstep04, - FRAME_c_duckstep05, - FRAME_c_duckstep06, - FRAME_c_duckpain01, - FRAME_c_duckpain02, - FRAME_c_duckpain03, - FRAME_c_duckpain04, - FRAME_c_duckpain05, - FRAME_c_duckdeath01, - FRAME_c_duckdeath02, - FRAME_c_duckdeath03, - FRAME_c_duckdeath04, - FRAME_c_duckdeath05, - FRAME_c_duckdeath06, - FRAME_c_duckdeath07, - FRAME_c_duckdeath08, - FRAME_c_duckdeath09, - FRAME_c_duckdeath10, - FRAME_c_duckdeath11, - FRAME_c_duckdeath12, - FRAME_c_duckdeath13, - FRAME_c_duckdeath14, - FRAME_c_duckdeath15, - FRAME_c_duckdeath16, - FRAME_c_duckdeath17, - FRAME_c_duckdeath18, - FRAME_c_duckdeath19, - FRAME_c_duckdeath20, - FRAME_c_duckdeath21, - FRAME_c_duckdeath22, - FRAME_c_duckdeath23, - FRAME_c_duckdeath24, - FRAME_c_duckdeath25, - FRAME_c_duckdeath26, - FRAME_c_duckdeath27, - FRAME_c_duckdeath28, - FRAME_c_duckdeath29 -}; - -constexpr float MODEL_SCALE = 1.150000f; diff --git a/src/quake2/monsterframes/m_gunner.h b/src/quake2/monsterframes/m_gunner.h index fc3f5324..cd951dcb 100644 --- a/src/quake2/monsterframes/m_gunner.h +++ b/src/quake2/monsterframes/m_gunner.h @@ -1,215 +1,811 @@ -// G:\quake2\baseq2\models/gunner +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. +// E:\G Drive\md2f\quake2\baseq2\models/monsters/gunner -// This file generated by ModelGen - Do NOT Modify +// This file generated by qdata - Do NOT Modify -#define FRAME_stand01 0 -#define FRAME_stand02 1 -#define FRAME_stand03 2 -#define FRAME_stand04 3 -#define FRAME_stand05 4 -#define FRAME_stand06 5 -#define FRAME_stand07 6 -#define FRAME_stand08 7 -#define FRAME_stand09 8 -#define FRAME_stand10 9 -#define FRAME_stand11 10 -#define FRAME_stand12 11 -#define FRAME_stand13 12 -#define FRAME_stand14 13 -#define FRAME_stand15 14 -#define FRAME_stand16 15 -#define FRAME_stand17 16 -#define FRAME_stand18 17 -#define FRAME_stand19 18 -#define FRAME_stand20 19 -#define FRAME_stand21 20 -#define FRAME_stand22 21 -#define FRAME_stand23 22 -#define FRAME_stand24 23 -#define FRAME_stand25 24 -#define FRAME_stand26 25 -#define FRAME_stand27 26 -#define FRAME_stand28 27 -#define FRAME_stand29 28 -#define FRAME_stand30 29 -#define FRAME_stand31 30 -#define FRAME_stand32 31 -#define FRAME_stand33 32 -#define FRAME_stand34 33 -#define FRAME_stand35 34 -#define FRAME_stand36 35 -#define FRAME_stand37 36 -#define FRAME_stand38 37 -#define FRAME_stand39 38 -#define FRAME_stand40 39 -#define FRAME_stand41 40 -#define FRAME_stand42 41 -#define FRAME_stand43 42 -#define FRAME_stand44 43 -#define FRAME_stand45 44 -#define FRAME_stand46 45 -#define FRAME_stand47 46 -#define FRAME_stand48 47 -#define FRAME_stand49 48 -#define FRAME_stand50 49 -#define FRAME_stand51 50 -#define FRAME_stand52 51 -#define FRAME_stand53 52 -#define FRAME_stand54 53 -#define FRAME_stand55 54 -#define FRAME_stand56 55 -#define FRAME_stand57 56 -#define FRAME_stand58 57 -#define FRAME_stand59 58 -#define FRAME_stand60 59 -#define FRAME_stand61 60 -#define FRAME_stand62 61 -#define FRAME_stand63 62 -#define FRAME_stand64 63 -#define FRAME_stand65 64 -#define FRAME_stand66 65 -#define FRAME_stand67 66 -#define FRAME_stand68 67 -#define FRAME_stand69 68 -#define FRAME_stand70 69 -#define FRAME_walk01 70 -#define FRAME_walk02 71 -#define FRAME_walk03 72 -#define FRAME_walk04 73 -#define FRAME_walk05 74 -#define FRAME_walk06 75 -#define FRAME_walk07 76 -#define FRAME_walk08 77 -#define FRAME_walk09 78 -#define FRAME_walk10 79 -#define FRAME_walk11 80 -#define FRAME_walk12 81 -#define FRAME_walk13 82 -#define FRAME_walk14 83 -#define FRAME_walk15 84 -#define FRAME_walk16 85 -#define FRAME_walk17 86 -#define FRAME_walk18 87 -#define FRAME_walk19 88 -#define FRAME_walk20 89 -#define FRAME_walk21 90 -#define FRAME_walk22 91 -#define FRAME_walk23 92 -#define FRAME_walk24 93 -#define FRAME_run01 94 -#define FRAME_run02 95 -#define FRAME_run03 96 -#define FRAME_run04 97 -#define FRAME_run05 98 -#define FRAME_run06 99 -#define FRAME_run07 100 -#define FRAME_run08 101 -#define FRAME_runs01 102 -#define FRAME_runs02 103 -#define FRAME_runs03 104 -#define FRAME_runs04 105 -#define FRAME_runs05 106 -#define FRAME_runs06 107 -#define FRAME_attak101 108 -#define FRAME_attak102 109 -#define FRAME_attak103 110 -#define FRAME_attak104 111 -#define FRAME_attak105 112 -#define FRAME_attak106 113 -#define FRAME_attak107 114 -#define FRAME_attak108 115 -#define FRAME_attak109 116 -#define FRAME_attak110 117 -#define FRAME_attak111 118 -#define FRAME_attak112 119 -#define FRAME_attak113 120 -#define FRAME_attak114 121 -#define FRAME_attak115 122 -#define FRAME_attak116 123 -#define FRAME_attak117 124 -#define FRAME_attak118 125 -#define FRAME_attak119 126 -#define FRAME_attak120 127 -#define FRAME_attak121 128 -#define FRAME_attak201 129 -#define FRAME_attak202 130 -#define FRAME_attak203 131 -#define FRAME_attak204 132 -#define FRAME_attak205 133 -#define FRAME_attak206 134 -#define FRAME_attak207 135 -#define FRAME_attak208 136 -#define FRAME_attak209 137 -#define FRAME_attak210 138 -#define FRAME_attak211 139 -#define FRAME_attak212 140 -#define FRAME_attak213 141 -#define FRAME_attak214 142 -#define FRAME_attak215 143 -#define FRAME_attak216 144 -#define FRAME_attak217 145 -#define FRAME_attak218 146 -#define FRAME_attak219 147 -#define FRAME_attak220 148 -#define FRAME_attak221 149 -#define FRAME_attak222 150 -#define FRAME_attak223 151 -#define FRAME_attak224 152 -#define FRAME_attak225 153 -#define FRAME_attak226 154 -#define FRAME_attak227 155 -#define FRAME_attak228 156 -#define FRAME_attak229 157 -#define FRAME_attak230 158 -#define FRAME_pain101 159 -#define FRAME_pain102 160 -#define FRAME_pain103 161 -#define FRAME_pain104 162 -#define FRAME_pain105 163 -#define FRAME_pain106 164 -#define FRAME_pain107 165 -#define FRAME_pain108 166 -#define FRAME_pain109 167 -#define FRAME_pain110 168 -#define FRAME_pain111 169 -#define FRAME_pain112 170 -#define FRAME_pain113 171 -#define FRAME_pain114 172 -#define FRAME_pain115 173 -#define FRAME_pain116 174 -#define FRAME_pain117 175 -#define FRAME_pain118 176 -#define FRAME_pain201 177 -#define FRAME_pain202 178 -#define FRAME_pain203 179 -#define FRAME_pain204 180 -#define FRAME_pain205 181 -#define FRAME_pain206 182 -#define FRAME_pain207 183 -#define FRAME_pain208 184 -#define FRAME_pain301 185 -#define FRAME_pain302 186 -#define FRAME_pain303 187 -#define FRAME_pain304 188 -#define FRAME_pain305 189 -#define FRAME_death01 190 -#define FRAME_death02 191 -#define FRAME_death03 192 -#define FRAME_death04 193 -#define FRAME_death05 194 -#define FRAME_death06 195 -#define FRAME_death07 196 -#define FRAME_death08 197 -#define FRAME_death09 198 -#define FRAME_death10 199 -#define FRAME_death11 200 -#define FRAME_duck01 201 -#define FRAME_duck02 202 -#define FRAME_duck03 203 -#define FRAME_duck04 204 -#define FRAME_duck05 205 -#define FRAME_duck06 206 -#define FRAME_duck07 207 -#define FRAME_duck08 208 +#pragma once -#define MODEL_SCALE 1.150000 +enum { + FRAME_stand01, + FRAME_stand02, + FRAME_stand03, + FRAME_stand04, + FRAME_stand05, + FRAME_stand06, + FRAME_stand07, + FRAME_stand08, + FRAME_stand09, + FRAME_stand10, + FRAME_stand11, + FRAME_stand12, + FRAME_stand13, + FRAME_stand14, + FRAME_stand15, + FRAME_stand16, + FRAME_stand17, + FRAME_stand18, + FRAME_stand19, + FRAME_stand20, + FRAME_stand21, + FRAME_stand22, + FRAME_stand23, + FRAME_stand24, + FRAME_stand25, + FRAME_stand26, + FRAME_stand27, + FRAME_stand28, + FRAME_stand29, + FRAME_stand30, + FRAME_stand31, + FRAME_stand32, + FRAME_stand33, + FRAME_stand34, + FRAME_stand35, + FRAME_stand36, + FRAME_stand37, + FRAME_stand38, + FRAME_stand39, + FRAME_stand40, + FRAME_stand41, + FRAME_stand42, + FRAME_stand43, + FRAME_stand44, + FRAME_stand45, + FRAME_stand46, + FRAME_stand47, + FRAME_stand48, + FRAME_stand49, + FRAME_stand50, + FRAME_stand51, + FRAME_stand52, + FRAME_stand53, + FRAME_stand54, + FRAME_stand55, + FRAME_stand56, + FRAME_stand57, + FRAME_stand58, + FRAME_stand59, + FRAME_stand60, + FRAME_stand61, + FRAME_stand62, + FRAME_stand63, + FRAME_stand64, + FRAME_stand65, + FRAME_stand66, + FRAME_stand67, + FRAME_stand68, + FRAME_stand69, + FRAME_stand70, + FRAME_walk01, + FRAME_walk02, + FRAME_walk03, + FRAME_walk04, + FRAME_walk05, + FRAME_walk06, + FRAME_walk07, + FRAME_walk08, + FRAME_walk09, + FRAME_walk10, + FRAME_walk11, + FRAME_walk12, + FRAME_walk13, + FRAME_walk14, + FRAME_walk15, + FRAME_walk16, + FRAME_walk17, + FRAME_walk18, + FRAME_walk19, + FRAME_walk20, + FRAME_walk21, + FRAME_walk22, + FRAME_walk23, + FRAME_walk24, + FRAME_run01, + FRAME_run02, + FRAME_run03, + FRAME_run04, + FRAME_run05, + FRAME_run06, + FRAME_run07, + FRAME_run08, + FRAME_runs01, + FRAME_runs02, + FRAME_runs03, + FRAME_runs04, + FRAME_runs05, + FRAME_runs06, + FRAME_attak101, + FRAME_attak102, + FRAME_attak103, + FRAME_attak104, + FRAME_attak105, + FRAME_attak106, + FRAME_attak107, + FRAME_attak108, + FRAME_attak109, + FRAME_attak110, + FRAME_attak111, + FRAME_attak112, + FRAME_attak113, + FRAME_attak114, + FRAME_attak115, + FRAME_attak116, + FRAME_attak117, + FRAME_attak118, + FRAME_attak119, + FRAME_attak120, + FRAME_attak121, + FRAME_attak201, + FRAME_attak202, + FRAME_attak203, + FRAME_attak204, + FRAME_attak205, + FRAME_attak206, + FRAME_attak207, + FRAME_attak208, + FRAME_attak209, + FRAME_attak210, + FRAME_attak211, + FRAME_attak212, + FRAME_attak213, + FRAME_attak214, + FRAME_attak215, + FRAME_attak216, + FRAME_attak217, + FRAME_attak218, + FRAME_attak219, + FRAME_attak220, + FRAME_attak221, + FRAME_attak222, + FRAME_attak223, + FRAME_attak224, + FRAME_attak225, + FRAME_attak226, + FRAME_attak227, + FRAME_attak228, + FRAME_attak229, + FRAME_attak230, + FRAME_pain101, + FRAME_pain102, + FRAME_pain103, + FRAME_pain104, + FRAME_pain105, + FRAME_pain106, + FRAME_pain107, + FRAME_pain108, + FRAME_pain109, + FRAME_pain110, + FRAME_pain111, + FRAME_pain112, + FRAME_pain113, + FRAME_pain114, + FRAME_pain115, + FRAME_pain116, + FRAME_pain117, + FRAME_pain118, + FRAME_pain201, + FRAME_pain202, + FRAME_pain203, + FRAME_pain204, + FRAME_pain205, + FRAME_pain206, + FRAME_pain207, + FRAME_pain208, + FRAME_pain301, + FRAME_pain302, + FRAME_pain303, + FRAME_pain304, + FRAME_pain305, + FRAME_death01, + FRAME_death02, + FRAME_death03, + FRAME_death04, + FRAME_death05, + FRAME_death06, + FRAME_death07, + FRAME_death08, + FRAME_death09, + FRAME_death10, + FRAME_death11, + FRAME_duck01, + FRAME_duck02, + FRAME_duck03, + FRAME_duck04, + FRAME_duck05, + FRAME_duck06, + FRAME_duck07, + FRAME_duck08, + FRAME_jump01, + FRAME_jump02, + FRAME_jump03, + FRAME_jump04, + FRAME_jump05, + FRAME_jump06, + FRAME_jump07, + FRAME_jump08, + FRAME_jump09, + FRAME_jump10, + FRAME_shield01, + FRAME_shield02, + FRAME_shield03, + FRAME_shield04, + FRAME_shield05, + FRAME_shield06, + FRAME_attak301, + FRAME_attak302, + FRAME_attak303, + FRAME_attak304, + FRAME_attak305, + FRAME_attak306, + FRAME_attak307, + FRAME_attak308, + FRAME_attak309, + FRAME_attak310, + FRAME_attak311, + FRAME_attak312, + FRAME_attak313, + FRAME_attak314, + FRAME_attak315, + FRAME_attak316, + FRAME_attak317, + FRAME_attak318, + FRAME_attak319, + FRAME_attak320, + FRAME_attak321, + FRAME_attak322, + FRAME_attak323, + FRAME_attak324, + FRAME_c_stand101, + FRAME_c_stand102, + FRAME_c_stand103, + FRAME_c_stand104, + FRAME_c_stand105, + FRAME_c_stand106, + FRAME_c_stand107, + FRAME_c_stand108, + FRAME_c_stand109, + FRAME_c_stand110, + FRAME_c_stand111, + FRAME_c_stand112, + FRAME_c_stand113, + FRAME_c_stand114, + FRAME_c_stand115, + FRAME_c_stand116, + FRAME_c_stand117, + FRAME_c_stand118, + FRAME_c_stand119, + FRAME_c_stand120, + FRAME_c_stand121, + FRAME_c_stand122, + FRAME_c_stand123, + FRAME_c_stand124, + FRAME_c_stand125, + FRAME_c_stand126, + FRAME_c_stand127, + FRAME_c_stand128, + FRAME_c_stand129, + FRAME_c_stand130, + FRAME_c_stand131, + FRAME_c_stand132, + FRAME_c_stand133, + FRAME_c_stand134, + FRAME_c_stand135, + FRAME_c_stand136, + FRAME_c_stand137, + FRAME_c_stand138, + FRAME_c_stand139, + FRAME_c_stand140, + FRAME_c_stand201, + FRAME_c_stand202, + FRAME_c_stand203, + FRAME_c_stand204, + FRAME_c_stand205, + FRAME_c_stand206, + FRAME_c_stand207, + FRAME_c_stand208, + FRAME_c_stand209, + FRAME_c_stand210, + FRAME_c_stand211, + FRAME_c_stand212, + FRAME_c_stand213, + FRAME_c_stand214, + FRAME_c_stand215, + FRAME_c_stand216, + FRAME_c_stand217, + FRAME_c_stand218, + FRAME_c_stand219, + FRAME_c_stand220, + FRAME_c_stand221, + FRAME_c_stand222, + FRAME_c_stand223, + FRAME_c_stand224, + FRAME_c_stand225, + FRAME_c_stand226, + FRAME_c_stand227, + FRAME_c_stand228, + FRAME_c_stand229, + FRAME_c_stand230, + FRAME_c_stand231, + FRAME_c_stand232, + FRAME_c_stand233, + FRAME_c_stand234, + FRAME_c_stand235, + FRAME_c_stand236, + FRAME_c_stand237, + FRAME_c_stand238, + FRAME_c_stand239, + FRAME_c_stand240, + FRAME_c_stand241, + FRAME_c_stand242, + FRAME_c_stand243, + FRAME_c_stand244, + FRAME_c_stand245, + FRAME_c_stand246, + FRAME_c_stand247, + FRAME_c_stand248, + FRAME_c_stand249, + FRAME_c_stand250, + FRAME_c_stand251, + FRAME_c_stand252, + FRAME_c_stand253, + FRAME_c_stand254, + FRAME_c_attack101, + FRAME_c_attack102, + FRAME_c_attack103, + FRAME_c_attack104, + FRAME_c_attack105, + FRAME_c_attack106, + FRAME_c_attack107, + FRAME_c_attack108, + FRAME_c_attack109, + FRAME_c_attack110, + FRAME_c_attack111, + FRAME_c_attack112, + FRAME_c_attack113, + FRAME_c_attack114, + FRAME_c_attack115, + FRAME_c_attack116, + FRAME_c_attack117, + FRAME_c_attack118, + FRAME_c_attack119, + FRAME_c_attack120, + FRAME_c_attack121, + FRAME_c_attack122, + FRAME_c_attack123, + FRAME_c_attack124, + FRAME_c_jump01, + FRAME_c_jump02, + FRAME_c_jump03, + FRAME_c_jump04, + FRAME_c_jump05, + FRAME_c_jump06, + FRAME_c_jump07, + FRAME_c_jump08, + FRAME_c_jump09, + FRAME_c_jump10, + FRAME_c_attack201, + FRAME_c_attack202, + FRAME_c_attack203, + FRAME_c_attack204, + FRAME_c_attack205, + FRAME_c_attack206, + FRAME_c_attack207, + FRAME_c_attack208, + FRAME_c_attack209, + FRAME_c_attack210, + FRAME_c_attack211, + FRAME_c_attack212, + FRAME_c_attack213, + FRAME_c_attack214, + FRAME_c_attack215, + FRAME_c_attack216, + FRAME_c_attack217, + FRAME_c_attack218, + FRAME_c_attack219, + FRAME_c_attack220, + FRAME_c_attack221, + FRAME_c_attack301, + FRAME_c_attack302, + FRAME_c_attack303, + FRAME_c_attack304, + FRAME_c_attack305, + FRAME_c_attack306, + FRAME_c_attack307, + FRAME_c_attack308, + FRAME_c_attack309, + FRAME_c_attack310, + FRAME_c_attack311, + FRAME_c_attack312, + FRAME_c_attack313, + FRAME_c_attack314, + FRAME_c_attack315, + FRAME_c_attack316, + FRAME_c_attack317, + FRAME_c_attack318, + FRAME_c_attack319, + FRAME_c_attack320, + FRAME_c_attack321, + FRAME_c_attack401, + FRAME_c_attack402, + FRAME_c_attack403, + FRAME_c_attack404, + FRAME_c_attack405, + FRAME_c_attack501, + FRAME_c_attack502, + FRAME_c_attack503, + FRAME_c_attack504, + FRAME_c_attack505, + FRAME_c_attack601, + FRAME_c_attack602, + FRAME_c_attack603, + FRAME_c_attack604, + FRAME_c_attack605, + FRAME_c_attack701, + FRAME_c_attack702, + FRAME_c_attack703, + FRAME_c_attack704, + FRAME_c_attack705, + FRAME_c_pain101, + FRAME_c_pain102, + FRAME_c_pain103, + FRAME_c_pain104, + FRAME_c_pain201, + FRAME_c_pain202, + FRAME_c_pain203, + FRAME_c_pain204, + FRAME_c_pain301, + FRAME_c_pain302, + FRAME_c_pain303, + FRAME_c_pain304, + FRAME_c_pain401, + FRAME_c_pain402, + FRAME_c_pain403, + FRAME_c_pain404, + FRAME_c_pain405, + FRAME_c_pain406, + FRAME_c_pain407, + FRAME_c_pain408, + FRAME_c_pain409, + FRAME_c_pain410, + FRAME_c_pain411, + FRAME_c_pain412, + FRAME_c_pain413, + FRAME_c_pain414, + FRAME_c_pain415, + FRAME_c_pain501, + FRAME_c_pain502, + FRAME_c_pain503, + FRAME_c_pain504, + FRAME_c_pain505, + FRAME_c_pain506, + FRAME_c_pain507, + FRAME_c_pain508, + FRAME_c_pain509, + FRAME_c_pain510, + FRAME_c_pain511, + FRAME_c_pain512, + FRAME_c_pain513, + FRAME_c_pain514, + FRAME_c_pain515, + FRAME_c_pain516, + FRAME_c_pain517, + FRAME_c_pain518, + FRAME_c_pain519, + FRAME_c_pain520, + FRAME_c_pain521, + FRAME_c_pain522, + FRAME_c_pain523, + FRAME_c_pain524, + FRAME_c_death101, + FRAME_c_death102, + FRAME_c_death103, + FRAME_c_death104, + FRAME_c_death105, + FRAME_c_death106, + FRAME_c_death107, + FRAME_c_death108, + FRAME_c_death109, + FRAME_c_death110, + FRAME_c_death111, + FRAME_c_death112, + FRAME_c_death113, + FRAME_c_death114, + FRAME_c_death115, + FRAME_c_death116, + FRAME_c_death117, + FRAME_c_death118, + FRAME_c_death201, + FRAME_c_death202, + FRAME_c_death203, + FRAME_c_death204, + FRAME_c_death301, + FRAME_c_death302, + FRAME_c_death303, + FRAME_c_death304, + FRAME_c_death305, + FRAME_c_death306, + FRAME_c_death307, + FRAME_c_death308, + FRAME_c_death309, + FRAME_c_death310, + FRAME_c_death311, + FRAME_c_death312, + FRAME_c_death313, + FRAME_c_death314, + FRAME_c_death315, + FRAME_c_death316, + FRAME_c_death317, + FRAME_c_death318, + FRAME_c_death319, + FRAME_c_death320, + FRAME_c_death321, + FRAME_c_death401, + FRAME_c_death402, + FRAME_c_death403, + FRAME_c_death404, + FRAME_c_death405, + FRAME_c_death406, + FRAME_c_death407, + FRAME_c_death408, + FRAME_c_death409, + FRAME_c_death410, + FRAME_c_death411, + FRAME_c_death412, + FRAME_c_death413, + FRAME_c_death414, + FRAME_c_death415, + FRAME_c_death416, + FRAME_c_death417, + FRAME_c_death418, + FRAME_c_death419, + FRAME_c_death420, + FRAME_c_death421, + FRAME_c_death422, + FRAME_c_death423, + FRAME_c_death424, + FRAME_c_death425, + FRAME_c_death426, + FRAME_c_death427, + FRAME_c_death428, + FRAME_c_death429, + FRAME_c_death430, + FRAME_c_death431, + FRAME_c_death432, + FRAME_c_death433, + FRAME_c_death434, + FRAME_c_death435, + FRAME_c_death436, + FRAME_c_death501, + FRAME_c_death502, + FRAME_c_death503, + FRAME_c_death504, + FRAME_c_death505, + FRAME_c_death506, + FRAME_c_death507, + FRAME_c_death508, + FRAME_c_death509, + FRAME_c_death510, + FRAME_c_death511, + FRAME_c_death512, + FRAME_c_death513, + FRAME_c_death514, + FRAME_c_death515, + FRAME_c_death516, + FRAME_c_death517, + FRAME_c_death518, + FRAME_c_death519, + FRAME_c_death520, + FRAME_c_death521, + FRAME_c_death522, + FRAME_c_death523, + FRAME_c_death524, + FRAME_c_death525, + FRAME_c_death526, + FRAME_c_death527, + FRAME_c_death528, + FRAME_c_run101, + FRAME_c_run102, + FRAME_c_run103, + FRAME_c_run104, + FRAME_c_run105, + FRAME_c_run106, + FRAME_c_run201, + FRAME_c_run202, + FRAME_c_run203, + FRAME_c_run204, + FRAME_c_run205, + FRAME_c_run206, + FRAME_c_run301, + FRAME_c_run302, + FRAME_c_run303, + FRAME_c_run304, + FRAME_c_run305, + FRAME_c_run306, + FRAME_c_walk101, + FRAME_c_walk102, + FRAME_c_walk103, + FRAME_c_walk104, + FRAME_c_walk105, + FRAME_c_walk106, + FRAME_c_walk107, + FRAME_c_walk108, + FRAME_c_walk109, + FRAME_c_walk110, + FRAME_c_walk111, + FRAME_c_walk112, + FRAME_c_walk113, + FRAME_c_walk114, + FRAME_c_walk115, + FRAME_c_walk116, + FRAME_c_walk117, + FRAME_c_walk118, + FRAME_c_walk119, + FRAME_c_walk120, + FRAME_c_walk121, + FRAME_c_walk122, + FRAME_c_walk123, + FRAME_c_walk124, + FRAME_c_pain601, + FRAME_c_pain602, + FRAME_c_pain603, + FRAME_c_pain604, + FRAME_c_pain605, + FRAME_c_pain606, + FRAME_c_pain607, + FRAME_c_pain608, + FRAME_c_pain609, + FRAME_c_pain610, + FRAME_c_pain611, + FRAME_c_pain612, + FRAME_c_pain613, + FRAME_c_pain614, + FRAME_c_pain615, + FRAME_c_pain616, + FRAME_c_pain617, + FRAME_c_pain618, + FRAME_c_pain619, + FRAME_c_pain620, + FRAME_c_pain621, + FRAME_c_pain622, + FRAME_c_pain623, + FRAME_c_pain624, + FRAME_c_pain625, + FRAME_c_pain626, + FRAME_c_pain627, + FRAME_c_pain628, + FRAME_c_pain629, + FRAME_c_pain630, + FRAME_c_pain631, + FRAME_c_pain632, + FRAME_c_death601, + FRAME_c_death602, + FRAME_c_death603, + FRAME_c_death604, + FRAME_c_death605, + FRAME_c_death606, + FRAME_c_death607, + FRAME_c_death608, + FRAME_c_death609, + FRAME_c_death610, + FRAME_c_death611, + FRAME_c_death612, + FRAME_c_death613, + FRAME_c_death614, + FRAME_c_death701, + FRAME_c_death702, + FRAME_c_death703, + FRAME_c_death704, + FRAME_c_death705, + FRAME_c_death706, + FRAME_c_death707, + FRAME_c_death708, + FRAME_c_death709, + FRAME_c_death710, + FRAME_c_death711, + FRAME_c_death712, + FRAME_c_death713, + FRAME_c_death714, + FRAME_c_death715, + FRAME_c_death716, + FRAME_c_death717, + FRAME_c_death718, + FRAME_c_death719, + FRAME_c_death720, + FRAME_c_death721, + FRAME_c_death722, + FRAME_c_death723, + FRAME_c_death724, + FRAME_c_death725, + FRAME_c_death726, + FRAME_c_death727, + FRAME_c_death728, + FRAME_c_death729, + FRAME_c_death730, + FRAME_c_pain701, + FRAME_c_pain702, + FRAME_c_pain703, + FRAME_c_pain704, + FRAME_c_pain705, + FRAME_c_pain706, + FRAME_c_pain707, + FRAME_c_pain708, + FRAME_c_pain709, + FRAME_c_pain710, + FRAME_c_pain711, + FRAME_c_pain712, + FRAME_c_pain713, + FRAME_c_pain714, + FRAME_c_attack801, + FRAME_c_attack802, + FRAME_c_attack803, + FRAME_c_attack804, + FRAME_c_attack805, + FRAME_c_attack806, + FRAME_c_attack807, + FRAME_c_attack808, + FRAME_c_attack809, + FRAME_c_attack901, + FRAME_c_attack902, + FRAME_c_attack903, + FRAME_c_attack904, + FRAME_c_attack905, + FRAME_c_attack906, + FRAME_c_attack907, + FRAME_c_attack908, + FRAME_c_attack909, + FRAME_c_attack910, + FRAME_c_attack911, + FRAME_c_attack912, + FRAME_c_attack913, + FRAME_c_attack914, + FRAME_c_attack915, + FRAME_c_attack916, + FRAME_c_attack917, + FRAME_c_attack918, + FRAME_c_attack919, + FRAME_c_duck01, + FRAME_c_duck02, + FRAME_c_duckstep01, + FRAME_c_duckstep02, + FRAME_c_duckstep03, + FRAME_c_duckstep04, + FRAME_c_duckstep05, + FRAME_c_duckstep06, + FRAME_c_duckpain01, + FRAME_c_duckpain02, + FRAME_c_duckpain03, + FRAME_c_duckpain04, + FRAME_c_duckpain05, + FRAME_c_duckdeath01, + FRAME_c_duckdeath02, + FRAME_c_duckdeath03, + FRAME_c_duckdeath04, + FRAME_c_duckdeath05, + FRAME_c_duckdeath06, + FRAME_c_duckdeath07, + FRAME_c_duckdeath08, + FRAME_c_duckdeath09, + FRAME_c_duckdeath10, + FRAME_c_duckdeath11, + FRAME_c_duckdeath12, + FRAME_c_duckdeath13, + FRAME_c_duckdeath14, + FRAME_c_duckdeath15, + FRAME_c_duckdeath16, + FRAME_c_duckdeath17, + FRAME_c_duckdeath18, + FRAME_c_duckdeath19, + FRAME_c_duckdeath20, + FRAME_c_duckdeath21, + FRAME_c_duckdeath22, + FRAME_c_duckdeath23, + FRAME_c_duckdeath24, + FRAME_c_duckdeath25, + FRAME_c_duckdeath26, + FRAME_c_duckdeath27, + FRAME_c_duckdeath28, + FRAME_c_duckdeath29 +}; + +constexpr float MODEL_SCALE = 1.150000f; diff --git a/src/quake2/monsterframes/m_mutant.h b/src/quake2/monsterframes/m_mutant.h index 7afd6930..f56d00bd 100644 --- a/src/quake2/monsterframes/m_mutant.h +++ b/src/quake2/monsterframes/m_mutant.h @@ -152,4 +152,11 @@ #define FRAME_walk22 147 #define FRAME_walk23 148 +// ROGUE +#define FRAME_jump01 149 +#define FRAME_jump02 150 +#define FRAME_jump03 151 +#define FRAME_jump04 152 +#define FRAME_jump05 153 + #define MODEL_SCALE 1.000000 diff --git a/src/quake2/monsterframes/m_parasite.h b/src/quake2/monsterframes/m_parasite.h index d895ce1d..2c768cfa 100644 --- a/src/quake2/monsterframes/m_parasite.h +++ b/src/quake2/monsterframes/m_parasite.h @@ -121,4 +121,14 @@ #define FRAME_stand34 116 #define FRAME_stand35 117 +// ROGUE +#define FRAME_jump01 118 +#define FRAME_jump02 119 +#define FRAME_jump03 120 +#define FRAME_jump04 121 +#define FRAME_jump05 122 +#define FRAME_jump06 123 +#define FRAME_jump07 124 +#define FRAME_jump08 125 + #define MODEL_SCALE 1.000000 From 1be2a6277c7449f1ba4cceba54a1638852b6a548 Mon Sep 17 00:00:00 2001 From: Enemy-123 Date: Sun, 26 Apr 2026 15:11:24 -0400 Subject: [PATCH 19/24] added shrink of bbox on dying like remaster, smarter sidestep, fix on monsters not getting pain skin if oneshotted --- src/characters/v_utils.c | 17 ++++++++ src/characters/v_utils.h | 1 + src/entities/drone/baron_fire.c | 1 + src/entities/drone/drone_ai.c | 36 +++++++++++++++++ src/entities/drone/drone_arachnid.c | 1 + src/entities/drone/drone_berserk.c | 17 +++++--- src/entities/drone/drone_bitch.c | 20 +++++++--- src/entities/drone/drone_boss2.c | 1 + src/entities/drone/drone_brain.c | 12 +++++- src/entities/drone/drone_carrier.c | 11 +++--- src/entities/drone/drone_daedalus.c | 1 + src/entities/drone/drone_gekk.c | 3 +- src/entities/drone/drone_gladiator.c | 10 ++++- src/entities/drone/drone_guncmdr.c | 57 ++++++++++++++------------- src/entities/drone/drone_gunner.c | 32 +++++++++------ src/entities/drone/drone_hover.c | 1 + src/entities/drone/drone_infantry.c | 24 ++++++----- src/entities/drone/drone_jorg.c | 1 + src/entities/drone/drone_makron.c | 7 +++- src/entities/drone/drone_medic.c | 10 ++++- src/entities/drone/drone_misc.c | 37 ++++++----------- src/entities/drone/drone_mutant.c | 15 +++++-- src/entities/drone/drone_parasite.c | 10 ++++- src/entities/drone/drone_redmutant.c | 42 ++++++++++++++++++-- src/entities/drone/drone_runnertank.c | 33 ++++++++++------ src/entities/drone/drone_shambler.c | 12 +++++- src/entities/drone/drone_soldier.c | 46 ++++++++++++++------- src/entities/drone/drone_stalker.c | 3 +- src/entities/drone/drone_tank.c | 40 +++++++++++++------ src/entities/drone/drone_widow.c | 1 + src/entities/drone/drone_widow2.c | 1 + src/g_local.h | 6 +++ 32 files changed, 365 insertions(+), 144 deletions(-) diff --git a/src/characters/v_utils.c b/src/characters/v_utils.c index bee94cb3..b92523d0 100644 --- a/src/characters/v_utils.c +++ b/src/characters/v_utils.c @@ -2881,6 +2881,23 @@ qboolean vrx_has_pain_skin(edict_t* ent) && ent->mtype != M_ROGUE_TURRET); } +void vrx_update_drone_death_skin(edict_t* ent) +{ + if (!ent || !ent->inuse || !(ent->svflags & SVF_MONSTER)) + return; + if (ent->s.modelindex == 255 || ent->max_health <= 0 || ent->health <= ent->gib_health) + return; + if (ent->health >= 0.5f * ent->max_health) + return; + if (!vrx_has_pain_skin(ent)) + return; + + if (ent->mtype == M_BARON_FIRE && ent->health < 0.2f * ent->max_health) + ent->s.skinnum = 2; + else + ent->s.skinnum |= 1; +} + // returns a value >= 1 based on any synergy bonuses that apply for ability_index // note: if no bonus applies, the value returned is 1.0 float vrx_get_synergy_mult(const edict_t* ent, int ability_index) diff --git a/src/characters/v_utils.h b/src/characters/v_utils.h index 5785ce34..b0e43b2a 100644 --- a/src/characters/v_utils.h +++ b/src/characters/v_utils.h @@ -27,4 +27,5 @@ qboolean V_MatchPlayerPrefs (edict_t *player, int monsters, int players);//4.5 qboolean isMonster (edict_t *ent); qboolean vrx_is_morphing_polt(edict_t *ent); qboolean vrx_has_pain_skin(edict_t* ent); +void vrx_update_drone_death_skin(edict_t* ent); float vrx_get_synergy_mult(const edict_t* ent, int ability_index); \ No newline at end of file diff --git a/src/entities/drone/baron_fire.c b/src/entities/drone/baron_fire.c index 9bcca0c6..378969bd 100644 --- a/src/entities/drone/baron_fire.c +++ b/src/entities/drone/baron_fire.c @@ -623,6 +623,7 @@ void baron_fire_die(edict_t* self, edict_t* inflictor, edict_t* attacker, int da gi.sound(self, CHAN_VOICE, sound_death2, 1, ATTN_IDLE, 0); self->deadflag = DEAD_DEAD; self->takedamage = DAMAGE_YES; + vrx_update_drone_death_skin(self); self->monsterinfo.currentmove = &baron_fire_move_death; } diff --git a/src/entities/drone/drone_ai.c b/src/entities/drone/drone_ai.c index 9c64e6b0..c22eb251 100644 --- a/src/entities/drone/drone_ai.c +++ b/src/entities/drone/drone_ai.c @@ -1352,6 +1352,42 @@ void drone_ai_run_slide (edict_t *self, float dist) M_walkmove (self, self->ideal_yaw - ofs, dist); } +void drone_set_dodge_side(edict_t *self, vec3_t impact) +{ + vec3_t right, diff; + + if (!self) + return; + + AngleVectors(self->s.angles, NULL, right, NULL); + VectorSubtract(impact, self->s.origin, diff); + + if (DotProduct(right, diff) < 0) + self->monsterinfo.lefty = 0; + else + self->monsterinfo.lefty = 1; +} + +void drone_ai_dodge_slide(edict_t *self, float dist) +{ + float ofs; + vec3_t v; + + if (!G_EntIsAlive(self->enemy)) + return; + + VectorSubtract(self->enemy->s.origin, self->s.origin, v); + self->ideal_yaw = vectoyaw(v); + M_ChangeYaw(self); + + ofs = self->monsterinfo.lefty ? 90 : -90; + if (M_walkmove(self, self->ideal_yaw + ofs, dist)) + return; + + self->monsterinfo.lefty = 1 - self->monsterinfo.lefty; + M_walkmove(self, self->ideal_yaw - ofs, dist); +} + void TeleportForward (edict_t *ent, vec3_t vec, float dist); void drone_cleargoal (edict_t *self) diff --git a/src/entities/drone/drone_arachnid.c b/src/entities/drone/drone_arachnid.c index 5eea7f54..19b2e2eb 100644 --- a/src/entities/drone/drone_arachnid.c +++ b/src/entities/drone/drone_arachnid.c @@ -363,6 +363,7 @@ static void arachnid_die(edict_t *self, edict_t *inflictor, edict_t *attacker, i gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); self->deadflag = DEAD_DEAD; self->takedamage = DAMAGE_YES; + vrx_update_drone_death_skin(self); self->monsterinfo.currentmove = &arachnid_move_death; if (self->activator && !self->activator->client) diff --git a/src/entities/drone/drone_berserk.c b/src/entities/drone/drone_berserk.c index 35d1a8a1..1f4efb75 100644 --- a/src/entities/drone/drone_berserk.c +++ b/src/entities/drone/drone_berserk.c @@ -17,7 +17,6 @@ static int sound_punch; static int sound_sight; static int sound_search; -void drone_ai_run_slide(edict_t *self, float dist); static void berserk_ai_dodge_slide(edict_t *self, float dist); static void berserk_duck_up(edict_t *self); @@ -256,6 +255,13 @@ void berserk_dead (edict_t *self) M_PrepBodyRemoval(self); } +static void berserk_shrink(edict_t *self) +{ + self->maxs[2] = 0; + self->svflags |= SVF_DEADMONSTER; + gi.linkentity(self); +} + mframe_t berserk_frames_death1 [] = { ai_move, 0, NULL, @@ -264,7 +270,7 @@ mframe_t berserk_frames_death1 [] = ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, - ai_move, 0, NULL, + ai_move, 0, berserk_shrink, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, @@ -281,7 +287,7 @@ mframe_t berserk_frames_death2 [] = ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, - ai_move, 0, NULL, + ai_move, 0, berserk_shrink, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, @@ -433,7 +439,7 @@ static void berserk_ai_dodge_slide(edict_t *self, float dist) if (!G_EntIsAlive(self->enemy)) return; - drone_ai_run_slide(self, dist); + drone_ai_dodge_slide(self, dist); } void berserk_dodge(edict_t *self, edict_t *attacker, vec3_t dir, int radius) @@ -491,7 +497,7 @@ void berserk_dodge(edict_t *self, edict_t *attacker, vec3_t dir, int radius) return; } - self->monsterinfo.lefty = 1 - self->monsterinfo.lefty; + drone_set_dodge_side(self, dir); self->monsterinfo.currentmove = &berserk_move_dodge_slide; self->monsterinfo.dodge_time = level.time + 0.4f + random() * 1.6f; } @@ -578,6 +584,7 @@ void berserk_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int dama gi.sound (self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); self->deadflag = DEAD_DEAD; self->takedamage = DAMAGE_YES; + vrx_update_drone_death_skin(self); if (damage >= 50) self->monsterinfo.currentmove = &berserk_move_death1; diff --git a/src/entities/drone/drone_bitch.c b/src/entities/drone/drone_bitch.c index 43e6c329..75cdf428 100644 --- a/src/entities/drone/drone_bitch.c +++ b/src/entities/drone/drone_bitch.c @@ -18,7 +18,6 @@ void mychick_continue (edict_t *self); extern mmove_t mychick_move_start_attack1; extern mmove_t mychick_move_attack1; extern mmove_t mychick_move_end_attack1; -void drone_ai_run_slide(edict_t *self, float dist); static int sound_missile_prelaunch; static int sound_missile_launch; @@ -189,7 +188,7 @@ static void mychick_ai_dodge_slide(edict_t *self, float dist) if (!G_EntIsAlive(self->enemy)) return; - drone_ai_run_slide(self, dist); + drone_ai_dodge_slide(self, dist); } mframe_t mychick_frames_dodge_slide[] = @@ -221,7 +220,7 @@ void mychick_dead (edict_t *self) { // gi.dprintf("mychick_dead()\n"); VectorSet (self->mins, -16, -16, 0); - VectorSet (self->maxs, 16, 16, 16); + VectorSet (self->maxs, 16, 16, 8); self->movetype = MOVETYPE_TOSS; self->svflags |= SVF_DEADMONSTER; //self->nextthink = 0; @@ -229,6 +228,13 @@ void mychick_dead (edict_t *self) M_PrepBodyRemoval(self); } +static void mychick_shrink(edict_t *self) +{ + self->maxs[2] = 12; + self->svflags |= SVF_DEADMONSTER; + gi.linkentity(self); +} + mframe_t mychick_frames_death2 [] = { ai_move, -6, NULL, @@ -251,7 +257,7 @@ mframe_t mychick_frames_death2 [] = ai_move, -3, NULL, ai_move, -5, NULL, ai_move, 4, NULL, - ai_move, 15, NULL, + ai_move, 15, mychick_shrink, ai_move, 14, NULL, ai_move, 1, NULL }; @@ -263,7 +269,7 @@ mframe_t mychick_frames_death1 [] = ai_move, 0, NULL, ai_move, -7, NULL, ai_move, 4, NULL, - ai_move, 11, NULL, + ai_move, 11, mychick_shrink, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, @@ -320,6 +326,7 @@ void mychick_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int dama // regular death self->deadflag = DEAD_DEAD; self->takedamage = DAMAGE_YES; + vrx_update_drone_death_skin(self); //level.total_monsters--; n = randomMT() % 2; @@ -517,9 +524,10 @@ static void mychick_dodge (edict_t *self, edict_t *attacker, vec3_t dir, int rad return; } + drone_set_dodge_side(self, dir); + if (!(self->monsterinfo.aiflags & AI_STAND_GROUND)) { - self->monsterinfo.lefty = 1 - self->monsterinfo.lefty; self->monsterinfo.currentmove = &mychick_move_dodge_slide; self->monsterinfo.dodge_time = level.time + 1.0f; return; diff --git a/src/entities/drone/drone_boss2.c b/src/entities/drone/drone_boss2.c index c1126f7d..ee3a91fb 100644 --- a/src/entities/drone/drone_boss2.c +++ b/src/entities/drone/drone_boss2.c @@ -645,6 +645,7 @@ static void boss2_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int self->s.sound = 0; self->deadflag = DEAD_DEAD; self->takedamage = DAMAGE_YES; + vrx_update_drone_death_skin(self); self->count = 0; self->monsterinfo.currentmove = boss2_is_small(self) ? &boss2_move_death : &boss2_move_deathboss; } diff --git a/src/entities/drone/drone_brain.c b/src/entities/drone/drone_brain.c index 292b7a9f..15ff1162 100644 --- a/src/entities/drone/drone_brain.c +++ b/src/entities/drone/drone_brain.c @@ -502,11 +502,18 @@ void mybrain_dodge (edict_t *self, edict_t *attacker, vec3_t dir, int radius) } } +static void mybrain_shrink(edict_t *self) +{ + self->maxs[2] = 0; + self->svflags |= SVF_DEADMONSTER; + gi.linkentity(self); +} + mframe_t mybrain_frames_death2 [] = { ai_move, 0, NULL, ai_move, 0, NULL, - ai_move, 0, NULL, + ai_move, 0, mybrain_shrink, ai_move, 9, NULL, ai_move, 0, NULL }; @@ -517,7 +524,7 @@ mframe_t mybrain_frames_death1 [] = ai_move, 0, NULL, ai_move, 0, NULL, ai_move, -2, NULL, - ai_move, 9, NULL, + ai_move, 9, mybrain_shrink, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, @@ -1007,6 +1014,7 @@ void mybrain_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int dama gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); self->deadflag = DEAD_DEAD; self->takedamage = DAMAGE_YES; + vrx_update_drone_death_skin(self); if (random() <= 0.5) self->monsterinfo.currentmove = &mybrain_move_death1; else diff --git a/src/entities/drone/drone_carrier.c b/src/entities/drone/drone_carrier.c index f06f329a..baad7e28 100644 --- a/src/entities/drone/drone_carrier.c +++ b/src/entities/drone/drone_carrier.c @@ -12,7 +12,7 @@ carrier #define CARRIER_SUMMON_COUNT 4 #define CARRIER_SUMMON_COOLDOWN 8.0f #define CARRIER_DEFAULT_SCALE 0.75f -#define CARRIER_INVASION_SCALE 0.60f +#define CARRIER_INVASION_SCALE 0.50f // 0.60 was too big for spambox static int sound_pain1; static int sound_pain2; @@ -113,7 +113,7 @@ static void carrier_fire_rocket(edict_t *self) for (int i = 0; i < 4; i++) { carrier_project_flash(self, flashes[i], forward, start); - MonsterAim(self, M_PROJECTILE_ACC, speed, true, -1, forward, start); + MonsterAim(self, M_PROJECTILE_ACC, speed, true, flashes[i], forward, start); monster_fire_heat(self, start, forward, damage, speed, flashes[i], 0.06f); } } @@ -192,7 +192,7 @@ static void carrier_fire_rail(edict_t *self) damage = M_RAILGUN_DMG_MAX; carrier_project_flash(self, MZ2_CARRIER_RAILGUN, forward, start); - MonsterAim(self, 0.25f, 0, false, -1, forward, start); + MonsterAim(self, 0.25f, 0, false, MZ2_CARRIER_RAILGUN, forward, start); gi.sound(self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0); monster_fire_railgun(self, start, forward, damage, damage, MZ2_CARRIER_RAILGUN); } @@ -546,6 +546,7 @@ static void carrier_die(edict_t *self, edict_t *inflictor, edict_t *attacker, in gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); self->deadflag = DEAD_DEAD; self->takedamage = DAMAGE_YES; + vrx_update_drone_death_skin(self); self->monsterinfo.currentmove = &carrier_move_death; } @@ -566,8 +567,8 @@ void init_drone_carrier(edict_t *self) if (invasion->value) { self->s.scale = CARRIER_INVASION_SCALE; - VectorSet(self->mins, -40, -40, -24); - VectorSet(self->maxs, 40, 40, 82); + VectorSet(self->mins, -34, -34, -20); + VectorSet(self->maxs, 34, 34, 68); } else { diff --git a/src/entities/drone/drone_daedalus.c b/src/entities/drone/drone_daedalus.c index b3d5b3cf..90a0cfb3 100644 --- a/src/entities/drone/drone_daedalus.c +++ b/src/entities/drone/drone_daedalus.c @@ -186,6 +186,7 @@ static void daedalus_die(edict_t *self, edict_t *inflictor, edict_t *attacker, i self->deadflag = DEAD_DEAD; self->takedamage = DAMAGE_YES; + vrx_update_drone_death_skin(self); self->flags &= ~FL_FLY; self->movetype = MOVETYPE_TOSS; self->gravity = 1.0; diff --git a/src/entities/drone/drone_gekk.c b/src/entities/drone/drone_gekk.c index 18789c5f..ce4b1942 100644 --- a/src/entities/drone/drone_gekk.c +++ b/src/entities/drone/drone_gekk.c @@ -787,7 +787,7 @@ static void gekk_dead(edict_t *self) static void gekk_shrink(edict_t *self) { - self->maxs[2] = -8; + self->maxs[2] = 0; self->svflags |= SVF_DEADMONSTER; gi.linkentity(self); } @@ -836,6 +836,7 @@ static void gekk_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int d gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); self->deadflag = DEAD_DEAD; self->takedamage = DAMAGE_YES; + vrx_update_drone_death_skin(self); self->monsterinfo.currentmove = &gekk_move_death1; if (self->activator && !self->activator->client) diff --git a/src/entities/drone/drone_gladiator.c b/src/entities/drone/drone_gladiator.c index d19ccbcc..d9f9ca58 100644 --- a/src/entities/drone/drone_gladiator.c +++ b/src/entities/drone/drone_gladiator.c @@ -453,11 +453,18 @@ void gladiator_dead (edict_t *self) M_PrepBodyRemoval(self); } +static void gladiator_shrink(edict_t *self) +{ + self->maxs[2] = 0; + self->svflags |= SVF_DEADMONSTER; + gi.linkentity(self); +} + mframe_t gladiator_frames_death [] = { ai_move, 0, NULL, ai_move, 0, NULL, - ai_move, 0, NULL, + ai_move, 0, gladiator_shrink, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, @@ -525,6 +532,7 @@ void gladiator_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int da gi.sound (self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); self->deadflag = DEAD_DEAD; self->takedamage = DAMAGE_YES; + vrx_update_drone_death_skin(self); self->monsterinfo.currentmove = &gladiator_move_death; diff --git a/src/entities/drone/drone_guncmdr.c b/src/entities/drone/drone_guncmdr.c index da8c845f..ff69382b 100644 --- a/src/entities/drone/drone_guncmdr.c +++ b/src/entities/drone/drone_guncmdr.c @@ -54,7 +54,6 @@ extern mmove_t guncmdr_move_death1; extern mmove_t guncmdr_move_death2; extern mmove_t guncmdr_move_death6; -void drone_ai_run_slide(edict_t *self, float dist); static void guncmdr_ai_dodge_slide(edict_t *self, float dist); extern mmove_t guncmdr_move_dodge_slide; @@ -353,21 +352,21 @@ mmove_t guncmdr_move_fire_chain_run = { FRAME_c_run201, FRAME_c_run206, guncmdr_ mframe_t guncmdr_frames_fire_chain_dodge_right[] = { - ai_charge, 10.2, GunnerCmdrFire, - ai_charge, 18.0, GunnerCmdrFire, - ai_charge, 7.0, GunnerCmdrFire, - ai_charge, 7.2, GunnerCmdrFire, - ai_charge, -2.0, GunnerCmdrFire + guncmdr_ai_dodge_slide, 10.2, GunnerCmdrFire, + guncmdr_ai_dodge_slide, 18.0, GunnerCmdrFire, + guncmdr_ai_dodge_slide, 7.0, GunnerCmdrFire, + guncmdr_ai_dodge_slide, 7.2, GunnerCmdrFire, + guncmdr_ai_dodge_slide, -2.0, GunnerCmdrFire }; mmove_t guncmdr_move_fire_chain_dodge_right = { FRAME_c_attack401, FRAME_c_attack405, guncmdr_frames_fire_chain_dodge_right, guncmdr_refire_chain }; mframe_t guncmdr_frames_fire_chain_dodge_left[] = { - ai_charge, 10.2, GunnerCmdrFire, - ai_charge, 18.0, GunnerCmdrFire, - ai_charge, 7.0, GunnerCmdrFire, - ai_charge, 7.2, GunnerCmdrFire, - ai_charge, -2.0, GunnerCmdrFire + guncmdr_ai_dodge_slide, 10.2, GunnerCmdrFire, + guncmdr_ai_dodge_slide, 18.0, GunnerCmdrFire, + guncmdr_ai_dodge_slide, 7.0, GunnerCmdrFire, + guncmdr_ai_dodge_slide, 7.2, GunnerCmdrFire, + guncmdr_ai_dodge_slide, -2.0, GunnerCmdrFire }; mmove_t guncmdr_move_fire_chain_dodge_left = { FRAME_c_attack501, FRAME_c_attack505, guncmdr_frames_fire_chain_dodge_left, guncmdr_refire_chain }; @@ -425,7 +424,7 @@ static void guncmdr_ai_duckstep_slide(edict_t *self, float dist) return; guncmdr_duck_down(self); - drone_ai_run_slide(self, dist); + drone_ai_dodge_slide(self, dist); } mframe_t guncmdr_frames_attack_mortar[] = @@ -506,21 +505,21 @@ static void guncmdr_resume_back_attack(edict_t *self) mframe_t guncmdr_frames_attack_grenade_back_dodge_right[] = { - ai_charge, 10.2, NULL, - ai_charge, 18.0, NULL, - ai_charge, 7.0, NULL, - ai_charge, 7.2, NULL, - ai_charge, -2.0, NULL + guncmdr_ai_dodge_slide, 10.2, NULL, + guncmdr_ai_dodge_slide, 18.0, NULL, + guncmdr_ai_dodge_slide, 7.0, NULL, + guncmdr_ai_dodge_slide, 7.2, NULL, + guncmdr_ai_dodge_slide, -2.0, NULL }; mmove_t guncmdr_move_attack_grenade_back_dodge_right = { FRAME_c_attack601, FRAME_c_attack605, guncmdr_frames_attack_grenade_back_dodge_right, guncmdr_resume_back_attack }; mframe_t guncmdr_frames_attack_grenade_back_dodge_left[] = { - ai_charge, 10.2, NULL, - ai_charge, 18.0, NULL, - ai_charge, 7.0, NULL, - ai_charge, 7.2, NULL, - ai_charge, -2.0, NULL + guncmdr_ai_dodge_slide, 10.2, NULL, + guncmdr_ai_dodge_slide, 18.0, NULL, + guncmdr_ai_dodge_slide, 7.0, NULL, + guncmdr_ai_dodge_slide, 7.2, NULL, + guncmdr_ai_dodge_slide, -2.0, NULL }; mmove_t guncmdr_move_attack_grenade_back_dodge_left = { FRAME_c_attack701, FRAME_c_attack705, guncmdr_frames_attack_grenade_back_dodge_left, guncmdr_resume_back_attack }; @@ -787,13 +786,18 @@ static void guncmdr_dodge(edict_t *self, edict_t *attacker, vec3_t dir, int radi { if (self->monsterinfo.currentmove == &guncmdr_move_duck_attack && (radius || guncmdr_dodge_hit_low(self, dir))) + { + drone_set_dodge_side(self, dir); guncmdr_start_duckstep_dodge(self); + } return; } if (random() > 0.8) return; + drone_set_dodge_side(self, dir); + if (guncmdr_try_sidestep(self)) { self->monsterinfo.dodge_time = level.time + 1.2f; @@ -827,7 +831,6 @@ static void guncmdr_dodge(edict_t *self, edict_t *attacker, vec3_t dir, int radi if (self->groundentity) { - self->monsterinfo.lefty = !self->monsterinfo.lefty; self->monsterinfo.currentmove = &guncmdr_move_dodge_slide; self->monsterinfo.dodge_time = level.time + 1.0; return; @@ -845,13 +848,13 @@ static qboolean guncmdr_can_proactive_dodge(edict_t *self, float dist) static void guncmdr_start_fire_chain_dodge(edict_t *self) { guncmdr_duck_up(self); + self->monsterinfo.lefty = !self->monsterinfo.lefty; if (self->monsterinfo.lefty) self->monsterinfo.currentmove = &guncmdr_move_fire_chain_dodge_left; else self->monsterinfo.currentmove = &guncmdr_move_fire_chain_dodge_right; - self->monsterinfo.lefty = !self->monsterinfo.lefty; self->monsterinfo.dodge_time = level.time + 1.0; } @@ -1184,7 +1187,7 @@ static void guncmdr_ai_dodge_slide(edict_t *self, float dist) if (!G_EntIsAlive(self->enemy)) return; - drone_ai_run_slide(self, dist); + drone_ai_dodge_slide(self, dist); } mframe_t guncmdr_frames_dodge_slide[] = @@ -1208,7 +1211,6 @@ static qboolean guncmdr_try_sidestep(edict_t *self) else self->monsterinfo.currentmove = &guncmdr_move_fire_chain_dodge_right; - self->monsterinfo.lefty = !self->monsterinfo.lefty; return true; } @@ -1221,7 +1223,6 @@ static qboolean guncmdr_try_sidestep(edict_t *self) else self->monsterinfo.currentmove = &guncmdr_move_attack_grenade_back_dodge_right; - self->monsterinfo.lefty = !self->monsterinfo.lefty; return true; } @@ -1237,7 +1238,6 @@ static qboolean guncmdr_try_sidestep(edict_t *self) if (random() < 0.45f) return guncmdr_start_duckstep_dodge(self); - self->monsterinfo.lefty = !self->monsterinfo.lefty; self->monsterinfo.currentmove = &guncmdr_move_dodge_slide; return true; } @@ -1488,6 +1488,7 @@ static void guncmdr_die(edict_t *self, edict_t *inflictor, edict_t *attacker, in gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); self->deadflag = DEAD_DEAD; self->takedamage = DAMAGE_YES; + vrx_update_drone_death_skin(self); if (self->monsterinfo.currentmove == &guncmdr_move_pain5 && self->s.frame < FRAME_c_pain508) diff --git a/src/entities/drone/drone_gunner.c b/src/entities/drone/drone_gunner.c index cfd999f2..c221953c 100644 --- a/src/entities/drone/drone_gunner.c +++ b/src/entities/drone/drone_gunner.c @@ -25,7 +25,6 @@ void mygunner_fire_chain(edict_t *self); void mygunner_delay (edict_t *self); void gunner_attack_grenade (edict_t *self); void gunner_refire_grenade (edict_t *self); -void drone_ai_run_slide(edict_t *self, float dist); void mygunneridlesound (edict_t *self) { @@ -193,19 +192,19 @@ static void mygunner_ai_dodge_slide(edict_t *self, float dist) if (!G_EntIsAlive(self->enemy)) return; - drone_ai_run_slide(self, dist); + drone_ai_dodge_slide(self, dist); } mframe_t mygunner_frames_dodge_slide[] = { - mygunner_ai_dodge_slide, 25, NULL, - mygunner_ai_dodge_slide, 25, NULL, - mygunner_ai_dodge_slide, 25, NULL, - mygunner_ai_dodge_slide, 25, NULL, - mygunner_ai_dodge_slide, 25, NULL, - mygunner_ai_dodge_slide, 25, NULL, - mygunner_ai_dodge_slide, 25, NULL, - mygunner_ai_dodge_slide, 25, NULL + mygunner_ai_dodge_slide, 26, NULL, + mygunner_ai_dodge_slide, 9, NULL, + mygunner_ai_dodge_slide, 9, NULL, + mygunner_ai_dodge_slide, 9, NULL, + mygunner_ai_dodge_slide, 15, NULL, + mygunner_ai_dodge_slide, 10, NULL, + mygunner_ai_dodge_slide, 13, NULL, + mygunner_ai_dodge_slide, 6, NULL }; mmove_t mygunner_move_dodge_slide = {FRAME_run01, FRAME_run08, mygunner_frames_dodge_slide, mygunnerrun}; @@ -691,9 +690,10 @@ void mygunner_dodge (edict_t *self, edict_t *attacker, vec3_t dir, int radius) self->enemy = attacker; if (!radius) { + drone_set_dodge_side(self, dir); + if (mygunner_dodge_hit_low(self, dir) && !(self->monsterinfo.aiflags & AI_STAND_GROUND)) { - self->monsterinfo.lefty = 1 - self->monsterinfo.lefty; self->monsterinfo.currentmove = &mygunner_move_dodge_slide; } else @@ -816,12 +816,19 @@ void mygunnerdead (edict_t *self) M_PrepBodyRemoval(self); } +static void mygunner_shrink(edict_t *self) +{ + self->maxs[2] = -4; + self->svflags |= SVF_DEADMONSTER; + gi.linkentity(self); +} + mframe_t mygunnerframes_death [] = { ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, - ai_move, -7, NULL, + ai_move, -7, mygunner_shrink, ai_move, -3, NULL, ai_move, -5, NULL, ai_move, 8, NULL, @@ -880,6 +887,7 @@ void mygunnerdie (edict_t *self, edict_t *inflictor, edict_t *attacker, int dama gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); self->deadflag = DEAD_DEAD; self->takedamage = DAMAGE_YES; + vrx_update_drone_death_skin(self); self->monsterinfo.currentmove = &mygunnermove_death; if (self->activator && !self->activator->client) diff --git a/src/entities/drone/drone_hover.c b/src/entities/drone/drone_hover.c index 6580560d..49487f82 100644 --- a/src/entities/drone/drone_hover.c +++ b/src/entities/drone/drone_hover.c @@ -600,6 +600,7 @@ void hover_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage gi.sound (self, CHAN_VOICE, sound_death2, 1, ATTN_NORM, 0); self->deadflag = DEAD_DEAD; self->takedamage = DAMAGE_YES; + vrx_update_drone_death_skin(self); self->flags &= ~FL_FLY; self->movetype = MOVETYPE_TOSS; self->gravity = 1.0; diff --git a/src/entities/drone/drone_infantry.c b/src/entities/drone/drone_infantry.c index fa6190e3..d0e0e848 100644 --- a/src/entities/drone/drone_infantry.c +++ b/src/entities/drone/drone_infantry.c @@ -28,7 +28,6 @@ static int sound_idle; #define INFANTRY_RUN_ATTACK_MIN_DIST 256 -void drone_ai_run_slide(edict_t *self, float dist); static void infantry_run_fire(edict_t *self); extern mmove_t infantry_move_attack4; @@ -278,6 +277,13 @@ void infantry_dead (edict_t *self) M_PrepBodyRemoval(self); } +static void infantry_shrink(edict_t *self) +{ + self->maxs[2] = 0; + self->svflags |= SVF_DEADMONSTER; + gi.linkentity(self); +} + mframe_t infantry_frames_pain1[] = { ai_move, 0, NULL, @@ -362,7 +368,7 @@ mframe_t infantry_frames_death1 [] = ai_move, -2, NULL, ai_move, 2, NULL, ai_move, 2, NULL, - ai_move, 9, NULL, + ai_move, 9, infantry_shrink, ai_move, 9, NULL, ai_move, 5, NULL, ai_move, -3, NULL, @@ -395,7 +401,7 @@ mframe_t infantry_frames_death2 [] = ai_move, -10, Infantry20mm, ai_move, -7, Infantry20mm, ai_move, -8, Infantry20mm, - ai_move, -6, NULL, + ai_move, -6, infantry_shrink, ai_move, 4, NULL, ai_move, 0, NULL }; @@ -405,7 +411,7 @@ mframe_t infantry_frames_death3 [] = { ai_move, 0, NULL, ai_move, 0, NULL, - ai_move, 0, NULL, + ai_move, 0, infantry_shrink, ai_move, -6, NULL, ai_move, -11, NULL, ai_move, -3, NULL, @@ -462,6 +468,7 @@ void infantry_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int dam // regular death self->deadflag = DEAD_DEAD; self->takedamage = DAMAGE_YES; + vrx_update_drone_death_skin(self); n = randomMT() % 3; if (n == 0) @@ -574,7 +581,7 @@ static void infantry_ai_dodge_slide(edict_t *self, float dist) if (!G_EntIsAlive(self->enemy)) return; - drone_ai_run_slide(self, dist); + drone_ai_dodge_slide(self, dist); } mframe_t infantry_frames_dodge_slide[] = @@ -595,7 +602,7 @@ static void infantry_attack4_dodge_ai(edict_t *self, float dist) if (!G_EntIsAlive(self->enemy)) return; - drone_ai_run_slide(self, dist); + drone_ai_dodge_slide(self, dist); } static void infantry_resume_attack4(edict_t *self) @@ -619,14 +626,12 @@ static qboolean infantry_try_sidestep(edict_t *self) if (self->monsterinfo.currentmove == &infantry_move_attack4) { self->count = self->s.frame; - self->monsterinfo.lefty = 1 - self->monsterinfo.lefty; self->monsterinfo.currentmove = &infantry_move_attack4_dodge; return true; } if (self->monsterinfo.currentmove == &infantry_move_run) { - self->monsterinfo.lefty = 1 - self->monsterinfo.lefty; self->monsterinfo.currentmove = &infantry_move_dodge_slide; return true; } @@ -680,6 +685,8 @@ static void infantry_dodge(edict_t *self, edict_t *attacker, vec3_t dir, int rad return; } + drone_set_dodge_side(self, dir); + if (infantry_try_sidestep(self)) { self->monsterinfo.dodge_time = level.time + 1.0f; @@ -688,7 +695,6 @@ static void infantry_dodge(edict_t *self, edict_t *attacker, vec3_t dir, int rad if (!(self->monsterinfo.aiflags & AI_STAND_GROUND)) { - self->monsterinfo.lefty = 1 - self->monsterinfo.lefty; self->monsterinfo.currentmove = &infantry_move_dodge_slide; self->monsterinfo.dodge_time = level.time + 0.9f; return; diff --git a/src/entities/drone/drone_jorg.c b/src/entities/drone/drone_jorg.c index 04029067..3ae03c93 100644 --- a/src/entities/drone/drone_jorg.c +++ b/src/entities/drone/drone_jorg.c @@ -452,6 +452,7 @@ void jorg_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); self->deadflag = DEAD_DEAD; self->takedamage = DAMAGE_NO; + vrx_update_drone_death_skin(self); self->s.sound = 0; self->count = 0; self->monsterinfo.currentmove = &jorg_move_death; diff --git a/src/entities/drone/drone_makron.c b/src/entities/drone/drone_makron.c index 1e93aa7c..9d85be0a 100644 --- a/src/entities/drone/drone_makron.c +++ b/src/entities/drone/drone_makron.c @@ -547,7 +547,7 @@ void makron_torso (edict_t *ent) void makron_dead (edict_t *self) { VectorSet (self->mins, -60, -60, 0); - VectorSet (self->maxs, 60, 60, 72); + VectorSet (self->maxs, 60, 60, 24); self->movetype = MOVETYPE_TOSS; self->svflags |= SVF_DEADMONSTER; self->nextthink = 0; @@ -586,12 +586,17 @@ void makron_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damag gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NONE, 0); self->deadflag = DEAD_DEAD; self->takedamage = DAMAGE_YES; + self->svflags |= SVF_DEADMONSTER; + vrx_update_drone_death_skin(self); tempent = G_Spawn(); VectorCopy (self->s.origin, tempent->s.origin); VectorCopy (self->s.angles, tempent->s.angles); tempent->s.origin[1] -= 84; makron_torso (tempent); + VectorSet (self->mins, -60, -60, 0); + VectorSet (self->maxs, 60, 60, 48); + gi.linkentity(self); self->monsterinfo.currentmove = &makron_move_death2; DroneList_Remove(self); diff --git a/src/entities/drone/drone_medic.c b/src/entities/drone/drone_medic.c index 042e59d5..33a3adad 100644 --- a/src/entities/drone/drone_medic.c +++ b/src/entities/drone/drone_medic.c @@ -343,6 +343,13 @@ void mymedic_dead (edict_t *self) M_PrepBodyRemoval(self); } +static void mymedic_shrink(edict_t *self) +{ + self->maxs[2] = -2; + self->svflags |= SVF_DEADMONSTER; + gi.linkentity(self); +} + mframe_t medic_frames_pain_short[] = { ai_move, 0, NULL, @@ -420,7 +427,7 @@ mframe_t mymedic_frames_death [] = ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, - ai_move, 0, NULL, + ai_move, 0, mymedic_shrink, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, @@ -497,6 +504,7 @@ void mymedic_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int dama gi.sound (self, CHAN_VOICE, medic_die_sound(self), 1, ATTN_NORM, 0); self->deadflag = DEAD_DEAD; self->takedamage = DAMAGE_YES; + vrx_update_drone_death_skin(self); self->monsterinfo.currentmove = &mymedic_move_death; diff --git a/src/entities/drone/drone_misc.c b/src/entities/drone/drone_misc.c index a8600f21..7e7c3a20 100644 --- a/src/entities/drone/drone_misc.c +++ b/src/entities/drone/drone_misc.c @@ -413,10 +413,10 @@ void drone_ai_checkattack (edict_t *self) // if we see an easier target, go for it if (!visible(self, self->enemy)) { - self->oldenemy = self->enemy; - if (!drone_findtarget(self, false)) - return; - //gi.dprintf("%d going for an easier target\n", self->mtype); + self->oldenemy = self->enemy; + if (!drone_findtarget(self, false)) + return; + //gi.dprintf("%d going for an easier target\n", self->mtype); } //if (!infront(self, self->enemy)) @@ -434,10 +434,10 @@ void drone_ai_checkattack (edict_t *self) if (!tr.ent || tr.ent != self->enemy) { //gi.dprintf("blocked shot\n"); - if (G_ValidTarget(self, tr.ent, false, true)) - self->enemy = tr.ent; - else - return; + if (G_ValidTarget(self, tr.ent, false, true)) + self->enemy = tr.ent; + else + return; } //AngleVectors(self->s.angles, forward, NULL, NULL); //VectorMA(self->s.origin, self->maxs[1]+8, forward , start); @@ -540,7 +540,8 @@ void drone_death (edict_t *self, edict_t *attacker) //4.2 bosses can drop up to 4 runes - if (self->mtype == M_COMMANDER || self->mtype == M_SUPERTANK || self->mtype == M_BOSS5 || self->mtype == M_MAKRON || self->mtype == M_BOSS2 || self->mtype == M_CARRIER || self->mtype == M_WIDOW || self->mtype == M_WIDOW2 || self->mtype == M_FIXBOT_BOSS || self->mtype == M_GUARDIAN) + if (self->mtype == M_COMMANDER || self->mtype == M_SUPERTANK || self->mtype == M_BOSS5 || self->mtype == M_MAKRON || self->mtype == M_BOSS2 || self->mtype == M_CARRIER + || self->mtype == M_WIDOW || self->mtype == M_WIDOW2 || self->mtype == M_FIXBOT_BOSS || self->mtype == M_GUARDIAN) { edict_t *e; float drop_chance = 0.25; @@ -913,24 +914,15 @@ edict_t *vrx_create_drone_from_ent(edict_t *drone, edict_t *ent, enum dronespawn case DS_GEKK: init_drone_gekk(drone); break; case DS_ARACHNID: init_drone_arachnid(drone); break; case DS_MEDIC_COMMANDER: init_drone_medic_commander(drone); break; - case DS_FIXBOT: init_drone_fixbot(drone); break; - case DS_ROGUE_TURRET: init_drone_rogue_turret(drone); break; - case DS_BOSS2_SMALL: init_drone_boss2_small(drone); break; // bosses case DS_COMMANDER: init_drone_commander(drone); break; case DS_MAKRON: init_drone_makron(drone); break; case DS_BARON_FIRE: init_baron_fire(drone); break; case DS_SUPERTANK: init_drone_supertank(drone); break; - case DS_BOSS5: init_drone_boss5(drone); break; case DS_JORG: init_drone_jorg(drone); break; case DS_CARRIER: init_drone_carrier(drone); break; - case DS_WIDOW: init_drone_widow(drone); break; - case DS_WIDOW2: init_drone_widow2(drone); break; - case DS_FIXBOT_BOSS: init_drone_fixbot_boss(drone); break; case DS_GUARDIAN: init_drone_guardian(drone); break; - case DS_BOSS2: init_drone_boss2(drone); break; - case DS_BOSS2_HYPER: init_drone_boss2_hyper(drone); break; case DS_JANITOR: drone->mtype = M_JANITOR; init_drone_supertank(drone); break; case DS_MINIGUARDIAN: drone->mtype = M_MINIGUARDIAN; init_drone_guardian(drone); break; @@ -950,9 +942,6 @@ edict_t *vrx_create_drone_from_ent(edict_t *drone, edict_t *ent, enum dronespawn if (drone_type < 30 || drone_type == DS_JANITOR || drone_type == DS_MINIGUARDIAN || - drone_type == DS_FIXBOT || - drone_type == DS_ROGUE_TURRET || - drone_type == DS_BOSS2_SMALL || drone_type == DS_SOLDIER_RIPPER || drone_type == DS_SOLDIER_BLUEBLASTER || drone_type == DS_SOLDIER_LASER) @@ -2409,7 +2398,7 @@ qboolean M_SetBoundingBox (int mtype, vec3_t boxmin, vec3_t boxmax) break; case M_BERSERK: // az: these were missing... VectorSet(boxmin, -16, -16, -24); - VectorSet(boxmax, 16, 16, -8); + VectorSet(boxmax, 16, 16, 32); break; case M_GLADIATOR: case M_GLADB: @@ -2447,7 +2436,7 @@ char *GetMonsterKindString (int mtype) case M_SOLDIER_BLUEBLASTER: return "Hyper Guard"; case M_SOLDIER_LASER: return "Laser Guard"; case M_TANK: return "Tank"; - case M_SUPERTANK: return "Super Tank"; + case M_SUPERTANK: return "Super Tank"; case M_BOSS5: return "Super Tank Heat"; case M_GUNNER: return "Gunner"; case M_YANGSPIRIT: return "Yang Spirit"; @@ -3081,7 +3070,7 @@ void Cmd_Drone_f (edict_t *ent) if (!Q_strcasecmp(s, "help")) { safe_cprintf(ent, PRINT_HIGH, "Monster summoning:\n"); - safe_cprintf(ent, PRINT_HIGH, "monster [gunner|parasite|brain|praetor|praetor_heat|medic|tank|mutant|gladiator|gladb|gladc|berserker|soldier|ripper|hyper|laser|janitor|janitor2|enforcer|flyer|floater|hover|fixbot|hornet|daedalus|stalker|gekk|arachnid|shambler|redmutant|runnertank|guncmdr]\n"); + safe_cprintf(ent, PRINT_HIGH, "monster [gunner|parasite|brain|praetor|praetor_heat|medic|tank|mutant|gladiator|gladb|gladc|berserker|soldier|ripper|hyper|laser|janitor|janitor2|enforcer|flyer|floater|hover|daedalus|stalker|gekk|arachnid|shambler|redmutant|runnertank|guncmdr]\n"); safe_cprintf(ent, PRINT_HIGH, "Monster utility commands:\n"); safe_cprintf(ent, PRINT_HIGH, "monster [remove|command|follow me|count|attack]\n"); return; diff --git a/src/entities/drone/drone_mutant.c b/src/entities/drone/drone_mutant.c index d7fb023f..a730c07b 100644 --- a/src/entities/drone/drone_mutant.c +++ b/src/entities/drone/drone_mutant.c @@ -421,7 +421,7 @@ void mutant_attack (edict_t *self) void mutant_dead (edict_t *self) { VectorSet (self->mins, -16, -16, -24); - VectorSet (self->maxs, 16, 16, -8); + VectorSet (self->maxs, 16, 16, 0); self->movetype = MOVETYPE_TOSS; self->svflags |= SVF_DEADMONSTER; //self->nextthink = 0; @@ -431,6 +431,13 @@ void mutant_dead (edict_t *self) // M_FlyCheck (self); } +static void mutant_shrink(edict_t *self) +{ + self->maxs[2] = 0; + self->svflags |= SVF_DEADMONSTER; + gi.linkentity(self); +} + mframe_t mutant_frames_death1 [] = { ai_move, 0, NULL, @@ -438,7 +445,7 @@ mframe_t mutant_frames_death1 [] = ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, - ai_move, 0, NULL, + ai_move, 0, mutant_shrink, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL @@ -451,7 +458,7 @@ mframe_t mutant_frames_death2 [] = ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, - ai_move, 0, NULL, + ai_move, 0, mutant_shrink, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, @@ -506,7 +513,7 @@ void mutant_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damag gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); self->deadflag = DEAD_DEAD; self->takedamage = DAMAGE_YES; - self->s.skinnum = 1; + vrx_update_drone_death_skin(self); if (random() < 0.5) self->monsterinfo.currentmove = &mutant_move_death1; diff --git a/src/entities/drone/drone_parasite.c b/src/entities/drone/drone_parasite.c index 36c879a6..f5078367 100644 --- a/src/entities/drone/drone_parasite.c +++ b/src/entities/drone/drone_parasite.c @@ -466,12 +466,19 @@ void myparasite_dead (edict_t *self) M_PrepBodyRemoval(self); } +static void myparasite_shrink(edict_t *self) +{ + self->maxs[2] = 0; + self->svflags |= SVF_DEADMONSTER; + gi.linkentity(self); +} + mframe_t myparasite_frames_death [] = { ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, - ai_move, 0, NULL, + ai_move, 0, myparasite_shrink, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL @@ -527,6 +534,7 @@ void myparasite_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int d gi.sound (self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); self->deadflag = DEAD_DEAD; self->takedamage = DAMAGE_YES; + vrx_update_drone_death_skin(self); self->monsterinfo.currentmove = &myparasite_move_death; DroneList_Remove(self); diff --git a/src/entities/drone/drone_redmutant.c b/src/entities/drone/drone_redmutant.c index 7987057b..f90e4063 100644 --- a/src/entities/drone/drone_redmutant.c +++ b/src/entities/drone/drone_redmutant.c @@ -23,6 +23,23 @@ static int sound_step2; static int sound_step3; static int sound_thud; +#define REDMUTANT_STAND_MAX_Z 36 +#define REDMUTANT_IDLE_MAX_Z 56 + +static void redmutant_set_bbox_height(edict_t *self, float max_z) +{ + if (self->maxs[2] == max_z) + return; + + self->maxs[2] = max_z; + gi.linkentity(self); +} + +static void redmutant_restore_bbox(edict_t *self) +{ + redmutant_set_bbox_height(self, REDMUTANT_STAND_MAX_Z); +} + static void redmutant_stand(edict_t *self); static void redmutant_walk(edict_t *self); static void redmutant_run(edict_t *self); @@ -75,6 +92,7 @@ mmove_t redmutant_move_stand = { FRAME_stand101, FRAME_stand112, redmutant_frame static void redmutant_stand(edict_t *self) { + redmutant_set_bbox_height(self, REDMUTANT_IDLE_MAX_Z); self->monsterinfo.currentmove = &redmutant_move_stand; } @@ -118,6 +136,7 @@ mmove_t redmutant_move_idle = { FRAME_stand202, FRAME_stand228, redmutant_frames static void redmutant_idle(edict_t *self) { + redmutant_set_bbox_height(self, REDMUTANT_IDLE_MAX_Z); self->monsterinfo.currentmove = &redmutant_move_idle; gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); } @@ -141,6 +160,7 @@ mmove_t redmutant_move_walk = { FRAME_walk05, FRAME_walk16, redmutant_frames_wal static void redmutant_walk_loop(edict_t *self) { + redmutant_restore_bbox(self); self->monsterinfo.currentmove = &redmutant_move_walk; } @@ -155,6 +175,7 @@ mmove_t redmutant_move_start_walk = { FRAME_walk01, FRAME_walk04, redmutant_fram static void redmutant_walk(edict_t *self) { + redmutant_restore_bbox(self); if (!self->goalentity) self->goalentity = world; self->monsterinfo.currentmove = &redmutant_move_start_walk; @@ -173,6 +194,7 @@ mmove_t redmutant_move_run = { FRAME_run03, FRAME_run08, redmutant_frames_run, N static void redmutant_run(edict_t *self) { + redmutant_restore_bbox(self); if (self->monsterinfo.aiflags & AI_STAND_GROUND) self->monsterinfo.currentmove = &redmutant_move_stand; else @@ -338,6 +360,7 @@ mmove_t redmutant_move_jump_finish = { FRAME_attack104, FRAME_attack108, redmuta static void redmutant_jump(edict_t *self) { + redmutant_restore_bbox(self); self->monsterinfo.currentmove = &redmutant_move_jump_start; } @@ -391,6 +414,7 @@ mmove_t redmutant_move_flip = { FRAME_attack101, FRAME_attack108, redmutant_fram static void redmutant_flip(edict_t *self) { + redmutant_restore_bbox(self); self->monsterinfo.currentmove = &redmutant_move_flip; } @@ -409,6 +433,7 @@ static void redmutant_attack(edict_t *self) if (dist <= MELEE_DISTANCE) { + redmutant_restore_bbox(self); self->monsterinfo.currentmove = &redmutant_move_attack; } else if ((dist <= 384) && (height_diff > -64) && (height_diff < 96) && (random() < 0.8)) @@ -423,13 +448,20 @@ static void redmutant_attack(edict_t *self) static void redmutant_dead(edict_t *self) { VectorSet(self->mins, -16, -16, -24); - VectorSet(self->maxs, 16, 16, -8); + VectorSet(self->maxs, 16, 16, 0); self->movetype = MOVETYPE_TOSS; self->svflags |= SVF_DEADMONSTER; gi.linkentity(self); M_PrepBodyRemoval(self); } +static void redmutant_shrink(edict_t *self) +{ + self->maxs[2] = 0; + self->svflags |= SVF_DEADMONSTER; + gi.linkentity(self); +} + static void ai_move_slide_right(edict_t *self, float dist) { M_walkmove(self, self->s.angles[YAW] + 90, dist); @@ -447,7 +479,7 @@ mframe_t redmutant_frames_death1[] = ai_move_slide_right, 0, NULL, ai_move_slide_right, 2, NULL, ai_move_slide_right, 5, NULL, - ai_move_slide_right, 7, NULL, + ai_move_slide_right, 7, redmutant_shrink, ai_move_slide_right, 6, NULL, ai_move_slide_right, 2, NULL, ai_move_slide_right, 0, NULL, @@ -471,7 +503,7 @@ mframe_t redmutant_frames_death2[] = ai_move_slide_left, 1, NULL, ai_move_slide_left, 6, NULL, ai_move_slide_left, 8, NULL, - ai_move_slide_left, 3, NULL, + ai_move_slide_left, 3, redmutant_shrink, ai_move_slide_left, 2, NULL, ai_move_slide_left, 0, NULL }; @@ -586,6 +618,8 @@ static void redmutant_pain(edict_t *self, edict_t *other, float kick, int damage if (skill->value == 3) return; + redmutant_restore_bbox(self); + if (r < 0.33) self->monsterinfo.currentmove = &redmutant_move_pain1; else if (r < 0.66) @@ -619,7 +653,7 @@ void init_drone_redmutant(edict_t *self) gi.modelindex("models/monsters/mutant/gibs/foot.md2"); VectorSet(self->mins, -18, -18, -24); - VectorSet(self->maxs, 18, 18, 30); + VectorSet(self->maxs, 18, 18, REDMUTANT_STAND_MAX_Z); self->health = M_REDMUTANT_INITIAL_HEALTH + M_REDMUTANT_ADDON_HEALTH * self->monsterinfo.level; self->max_health = self->health; diff --git a/src/entities/drone/drone_runnertank.c b/src/entities/drone/drone_runnertank.c index c0402a73..bbbbe8c0 100644 --- a/src/entities/drone/drone_runnertank.c +++ b/src/entities/drone/drone_runnertank.c @@ -9,6 +9,7 @@ static int sound_step; static int sound_sight; static int sound_windup; static int sound_strike; +static int sound_plasma; #define RUNNERTANK_JUMP_ATTACK_DELAY 12.0f #define RUNNERTANK_JUMP_ATTACK_FOV 35 @@ -50,22 +51,29 @@ static void runnertank_windup(edict_t *self) gi.sound(self, CHAN_WEAPON, sound_windup, 1, ATTN_NORM, 0); } -static void runnertank_slam_effect(edict_t *self) +static void runnertank_slam_origin(edict_t *self, vec3_t origin) { - vec3_t forward, right, start, offset, up; + vec3_t forward, right, offset; trace_t tr; AngleVectors(self->s.angles, forward, right, NULL); VectorSet(offset, 20, -14.3f, -21); - G_ProjectSource(self->s.origin, offset, forward, right, start); - tr = gi.trace(self->s.origin, NULL, NULL, start, self, MASK_SOLID); + G_ProjectSource(self->s.origin, offset, forward, right, origin); + tr = gi.trace(self->s.origin, NULL, NULL, origin, self, MASK_SOLID); + VectorCopy(tr.endpos, origin); +} + +static void runnertank_slam_effect(vec3_t origin) +{ + vec3_t up; + VectorSet(up, 0, 0, 1); gi.WriteByte(svc_temp_entity); gi.WriteByte(TE_BERSERK_SLAM); - gi.WritePosition(tr.endpos); + gi.WritePosition(origin); gi.WriteDir(up); - gi.multicast(tr.endpos, MULTICAST_PHS); + gi.multicast(origin, MULTICAST_PHS); } static void runnertank_idle(edict_t *self) @@ -280,6 +288,7 @@ static void runnertank_plasma(edict_t *self) MonsterAim(self, M_PROJECTILE_ACC, speed, false, flash_number, forward, start); fire_plasma(self, start, forward, damage, speed, M_PLASMA_DAMAGE_RADIUS, radius_damage); + gi.positioned_sound(start, self, CHAN_WEAPON, sound_plasma, 1, ATTN_NORM, 0); } static void runnertank_strike_sound(edict_t *self) @@ -292,7 +301,7 @@ static void runnertank_meleeattack(edict_t *self) int damage; trace_t tr; edict_t *other = NULL; - vec3_t v; + vec3_t v, damage_origin; if (!self->groundentity) return; @@ -302,18 +311,19 @@ static void runnertank_meleeattack(edict_t *self) damage = M_MELEE_DMG_MAX; gi.sound(self, CHAN_AUTO, sound_strike, 1, ATTN_NORM, 0); - runnertank_slam_effect(self); + runnertank_slam_origin(self, damage_origin); + runnertank_slam_effect(damage_origin); - while ((other = findradius(other, self->s.origin, 128)) != NULL) + while ((other = findradius(other, damage_origin, 128)) != NULL) { if (!G_ValidTarget(self, other, true, true)) continue; if (que_typeexists(self->curses, CURSE) && rand() > 0.2) continue; - VectorSubtract(other->s.origin, self->s.origin, v); + VectorSubtract(other->s.origin, damage_origin, v); VectorNormalize(v); - tr = gi.trace(self->s.origin, NULL, NULL, other->s.origin, self, (MASK_PLAYERSOLID | MASK_MONSTERSOLID)); + tr = gi.trace(damage_origin, NULL, NULL, other->s.origin, self, (MASK_PLAYERSOLID | MASK_MONSTERSOLID)); T_Damage(other, self, self, v, tr.endpos, tr.plane.normal, damage, 200, 0, MOD_TANK_PUNCH); } } @@ -847,6 +857,7 @@ void init_drone_runnertank(edict_t *self) sound_strike = gi.soundindex("tank/tnkatck5.wav"); sound_sight = gi.soundindex("tank/sight1.wav"); sound_thud = gi.soundindex("tank/tnkdeth2.wav"); + sound_plasma = gi.soundindex("weapons/plasshot.wav"); gi.soundindex("tank/tnkatck1.wav"); gi.soundindex("tank/tnkatk2a.wav"); diff --git a/src/entities/drone/drone_shambler.c b/src/entities/drone/drone_shambler.c index c8399220..70c405d4 100644 --- a/src/entities/drone/drone_shambler.c +++ b/src/entities/drone/drone_shambler.c @@ -823,12 +823,19 @@ mmove_t shambler_move_pain = { FRAME_pain1, FRAME_pain6, shambler_frames_pain, s void shambler_dead(edict_t* self); +static void shambler_shrink(edict_t *self) +{ + self->maxs[2] = 0; + self->svflags |= SVF_DEADMONSTER; + gi.linkentity(self); +} + mframe_t shambler_frames_death[] = { {ai_move, 0, NULL}, {ai_move, 0, NULL}, - {ai_move, 0, NULL}, + {ai_move, 0, shambler_shrink}, {ai_move, 0, NULL}, {ai_move, 0, NULL}, {ai_move, 0, NULL}, @@ -928,7 +935,7 @@ void shambler_pain(edict_t* self, edict_t* other, float kick, int damage) void shambler_dead(edict_t* self) { VectorSet(self->mins, -16, -16, -24); - VectorSet(self->maxs, 16, 16, -8); + VectorSet(self->maxs, 16, 16, 0); self->movetype = MOVETYPE_TOSS; self->svflags |= SVF_DEADMONSTER; self->nextthink = 0; @@ -986,6 +993,7 @@ void shambler_die(edict_t* self, edict_t* inflictor, edict_t* attacker, int dama gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); self->deadflag = DEAD_DEAD; self->takedamage = DAMAGE_YES; + vrx_update_drone_death_skin(self); self->monsterinfo.currentmove = &shambler_move_death; if (self->activator && !self->activator->client) diff --git a/src/entities/drone/drone_soldier.c b/src/entities/drone/drone_soldier.c index 93c40b56..561c1267 100644 --- a/src/entities/drone/drone_soldier.c +++ b/src/entities/drone/drone_soldier.c @@ -983,6 +983,13 @@ void m_soldier_dead (edict_t *self) M_PrepBodyRemoval(self); } +static void m_soldier_death_shrink(edict_t *self) +{ + self->svflags |= SVF_DEADMONSTER; + self->maxs[2] = 0; + gi.linkentity(self); +} + mframe_t soldier_frames_pain_short1[] = { @@ -1115,7 +1122,7 @@ mframe_t m_soldier_frames_death1 [] = ai_move, 0, NULL, ai_move, -10, NULL, ai_move, -10, NULL, - ai_move, -10, NULL, + ai_move, -10, m_soldier_death_shrink, ai_move, -5, NULL, ai_move, 0, NULL, ai_move, 0, NULL, @@ -1159,7 +1166,7 @@ mframe_t m_soldier_frames_death2 [] = ai_move, -5, NULL, ai_move, -5, NULL, ai_move, -5, NULL, - ai_move, 0, NULL, + ai_move, 0, m_soldier_death_shrink, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, @@ -1202,7 +1209,7 @@ mframe_t m_soldier_frames_death3 [] = ai_move, -5, NULL, ai_move, -5, NULL, ai_move, -5, NULL, - ai_move, 0, NULL, + ai_move, 0, m_soldier_death_shrink, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, @@ -1267,7 +1274,7 @@ mframe_t m_soldier_frames_death4 [] = ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, - ai_move, 0, NULL, + ai_move, 0, m_soldier_death_shrink, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, @@ -1321,7 +1328,7 @@ mframe_t m_soldier_frames_death5 [] = ai_move, -5, NULL, ai_move, 0, NULL, ai_move, 0, NULL, - ai_move, 0, NULL, + ai_move, 0, m_soldier_death_shrink, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, @@ -1352,7 +1359,7 @@ mframe_t m_soldier_frames_death6 [] = ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, - ai_move, 0, NULL, + ai_move, 0, m_soldier_death_shrink, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, @@ -1407,16 +1414,27 @@ void m_soldier_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int da gi.sound (self, CHAN_VOICE, soldier_death_sound(self), 1, ATTN_NORM, 0); self->takedamage = DAMAGE_YES; self->deadflag = DEAD_DEAD; + vrx_update_drone_death_skin(self); - n = GetRandom(1, 6); - switch (n) + if (self->monsterinfo.currentmove == &m_soldier_move_trip || + self->monsterinfo.currentmove == &m_soldier_move_attack5) + { + self->monsterinfo.currentmove = &m_soldier_move_death4; + self->monsterinfo.nextframe = FRAME_death413; + m_soldier_death_shrink(self); + } + else { - case 1: self->monsterinfo.currentmove = &m_soldier_move_death1; break; - case 2: self->monsterinfo.currentmove = &m_soldier_move_death2; break; - case 3: self->monsterinfo.currentmove = &m_soldier_move_death3; break; - case 4: self->monsterinfo.currentmove = &m_soldier_move_death4; break; - case 5: self->monsterinfo.currentmove = &m_soldier_move_death5; break; - case 6: self->monsterinfo.currentmove = &m_soldier_move_death6; break; + n = GetRandom(1, 6); + switch (n) + { + case 1: self->monsterinfo.currentmove = &m_soldier_move_death1; break; + case 2: self->monsterinfo.currentmove = &m_soldier_move_death2; break; + case 3: self->monsterinfo.currentmove = &m_soldier_move_death3; break; + case 4: self->monsterinfo.currentmove = &m_soldier_move_death4; break; + case 5: self->monsterinfo.currentmove = &m_soldier_move_death5; break; + case 6: self->monsterinfo.currentmove = &m_soldier_move_death6; break; + } } DroneList_Remove(self); diff --git a/src/entities/drone/drone_stalker.c b/src/entities/drone/drone_stalker.c index 39d97f6d..8e8e3f10 100644 --- a/src/entities/drone/drone_stalker.c +++ b/src/entities/drone/drone_stalker.c @@ -591,7 +591,7 @@ static void stalker_pain(edict_t *self, edict_t *other, float kick, int damage) static void stalker_dead(edict_t *self) { VectorSet(self->mins, -28, -28, -18); - VectorSet(self->maxs, 28, 28, -8); + VectorSet(self->maxs, 28, 28, -4); self->movetype = MOVETYPE_TOSS; self->svflags |= SVF_DEADMONSTER; gi.linkentity(self); @@ -643,6 +643,7 @@ static void stalker_die(edict_t *self, edict_t *inflictor, edict_t *attacker, in gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); self->deadflag = DEAD_DEAD; self->takedamage = DAMAGE_YES; + vrx_update_drone_death_skin(self); self->monsterinfo.currentmove = &stalker_move_death; if (self->activator && !self->activator->client) diff --git a/src/entities/drone/drone_tank.c b/src/entities/drone/drone_tank.c index 2d76ef3f..e43fdf53 100644 --- a/src/entities/drone/drone_tank.c +++ b/src/entities/drone/drone_tank.c @@ -45,22 +45,29 @@ void mytank_windup (edict_t *self) gi.sound (self, CHAN_WEAPON, sound_windup, 1, ATTN_NORM, 0); } -static void mytank_slam_effect(edict_t *self) +static void mytank_slam_origin(edict_t *self, vec3_t origin) { - vec3_t forward, right, start, offset, up; + vec3_t forward, right, offset; trace_t tr; AngleVectors(self->s.angles, forward, right, NULL); VectorSet(offset, 20, -14.3f, -21); - G_ProjectSource(self->s.origin, offset, forward, right, start); - tr = gi.trace(self->s.origin, NULL, NULL, start, self, MASK_SOLID); + G_ProjectSource(self->s.origin, offset, forward, right, origin); + tr = gi.trace(self->s.origin, NULL, NULL, origin, self, MASK_SOLID); + VectorCopy(tr.endpos, origin); +} + +static void mytank_slam_effect(vec3_t origin) +{ + vec3_t up; + VectorSet(up, 0, 0, 1); gi.WriteByte(svc_temp_entity); gi.WriteByte(TE_BERSERK_SLAM); - gi.WritePosition(tr.endpos); + gi.WritePosition(origin); gi.WriteDir(up); - gi.multicast(tr.endpos, MULTICAST_PHS); + gi.multicast(origin, MULTICAST_PHS); } void mytank_idle (edict_t *self) @@ -677,7 +684,7 @@ void mytank_meleeattack (edict_t *self) int damage; trace_t tr; edict_t *other=NULL; - vec3_t v; + vec3_t v, damage_origin; // tank must be on the ground to punch if (!self->groundentity) @@ -690,9 +697,10 @@ void mytank_meleeattack (edict_t *self) damage = M_MELEE_DMG_MAX; gi.sound (self, CHAN_AUTO, gi.soundindex ("tank/tnkatck5.wav"), 1, ATTN_NORM, 0); - mytank_slam_effect(self); + mytank_slam_origin(self, damage_origin); + mytank_slam_effect(damage_origin); - while ((other = findradius(other, self->s.origin, 128)) != NULL) + while ((other = findradius(other, damage_origin, 128)) != NULL) { if (!G_ValidTarget(self, other, true, true)) continue; @@ -703,9 +711,9 @@ void mytank_meleeattack (edict_t *self) //if ((self->monsterinfo.control_cost < 3) && !nearfov(self, other, 0, 60))//!infront(self, other)) // continue; - VectorSubtract(other->s.origin, self->s.origin, v); + VectorSubtract(other->s.origin, damage_origin, v); VectorNormalize(v); - tr = gi.trace(self->s.origin, NULL, NULL, other->s.origin, self, (MASK_PLAYERSOLID | MASK_MONSTERSOLID)); + tr = gi.trace(damage_origin, NULL, NULL, other->s.origin, self, (MASK_PLAYERSOLID | MASK_MONSTERSOLID)); T_Damage (other, self, self, v, tr.endpos, tr.plane.normal, damage, 200, 0, MOD_TANK_PUNCH); //other->velocity[2] += 200;//damage / 2; } @@ -1025,6 +1033,13 @@ void mytank_dead(edict_t* self) M_PrepBodyRemoval(self); } +static void mytank_shrink(edict_t *self) +{ + self->maxs[2] = 0; + self->svflags |= SVF_DEADMONSTER; + gi.linkentity(self); +} + mframe_t mytank_frames_death1[] = { ai_move, -7, NULL, @@ -1053,7 +1068,7 @@ mframe_t mytank_frames_death1[] = ai_move, -6, NULL, ai_move, -4, NULL, ai_move, -5, NULL, - ai_move, -7, NULL, + ai_move, -7, mytank_shrink, ai_move, -15, mytank_thud, ai_move, -5, NULL, ai_move, 0, NULL, @@ -1112,6 +1127,7 @@ void mytank_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damag gi.sound (self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); self->deadflag = DEAD_DEAD; self->takedamage = DAMAGE_YES; + vrx_update_drone_death_skin(self); self->monsterinfo.currentmove = &mytank_move_death; if (self->activator && !self->activator->client) diff --git a/src/entities/drone/drone_widow.c b/src/entities/drone/drone_widow.c index 84ff475b..8163c7b4 100644 --- a/src/entities/drone/drone_widow.c +++ b/src/entities/drone/drone_widow.c @@ -605,6 +605,7 @@ static void widow_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); self->deadflag = DEAD_DEAD; self->takedamage = DAMAGE_YES; + vrx_update_drone_death_skin(self); self->monsterinfo.currentmove = &widow_move_death; } diff --git a/src/entities/drone/drone_widow2.c b/src/entities/drone/drone_widow2.c index 76c7ae53..82bd93c5 100644 --- a/src/entities/drone/drone_widow2.c +++ b/src/entities/drone/drone_widow2.c @@ -706,6 +706,7 @@ static void widow2_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); self->deadflag = DEAD_DEAD; self->takedamage = DAMAGE_YES; + vrx_update_drone_death_skin(self); self->monsterinfo.currentmove = &widow2_move_death; } diff --git a/src/g_local.h b/src/g_local.h index 8d095755..4f6bb17c 100644 --- a/src/g_local.h +++ b/src/g_local.h @@ -1740,6 +1740,12 @@ void drone_ai_run(edict_t *self, float dist); void drone_ai_run1(edict_t *self, float dist); +void drone_ai_run_slide(edict_t *self, float dist); + +void drone_ai_dodge_slide(edict_t *self, float dist); + +void drone_set_dodge_side(edict_t *self, vec3_t impact); + void drone_ai_walk(edict_t *self, float dist); // az begin From 8c96d76bbf2bcea0aaa733867a6455043d23244b Mon Sep 17 00:00:00 2001 From: Enemy-123 Date: Wed, 29 Apr 2026 22:22:58 -0400 Subject: [PATCH 20/24] Slightly modified MonsterAim becausse of remaster's muzzleflash's AI decision-making on attacks and accuracy / improved bosses, and normal drones attacks/animations bosses being scaled for invasion, smarter sidestep --- src/characters/v_utils.c | 10 +- src/combat/weapons/g_weapon.c | 6 +- src/entities/drone/drone_ai.c | 9 +- src/entities/drone/drone_bitch.c | 50 ++- src/entities/drone/drone_brain.c | 139 ++++-- src/entities/drone/drone_carrier.c | 536 ++++++++++++++++++++---- src/entities/drone/drone_daedalus.c | 54 ++- src/entities/drone/drone_fixbot.c | 257 ++++++++++-- src/entities/drone/drone_float.c | 75 +++- src/entities/drone/drone_flyer.c | 106 +++-- src/entities/drone/drone_gekk.c | 97 ++++- src/entities/drone/drone_guardian.c | 87 +++- src/entities/drone/drone_guncmdr.c | 72 +++- src/entities/drone/drone_gunner.c | 79 +++- src/entities/drone/drone_hover.c | 129 +++--- src/entities/drone/drone_medic.c | 91 +++- src/entities/drone/drone_misc.c | 224 +++++++--- src/entities/drone/drone_mutant.c | 8 +- src/entities/drone/drone_parasite.c | 27 +- src/entities/drone/drone_redmutant.c | 24 +- src/entities/drone/drone_rogue_turret.c | 48 ++- src/entities/drone/drone_runnertank.c | 203 ++++++++- src/entities/drone/drone_shambler.c | 248 +---------- src/entities/drone/drone_soldier.c | 211 +++++++++- src/entities/drone/drone_stalker.c | 23 +- src/entities/drone/drone_tank.c | 79 +++- src/entities/drone/drone_widow.c | 68 ++- src/entities/drone/drone_widow2.c | 141 +++++-- src/g_local.h | 12 +- src/gamemodes/invasion.c | 2 +- 30 files changed, 2301 insertions(+), 814 deletions(-) diff --git a/src/characters/v_utils.c b/src/characters/v_utils.c index b92523d0..399ccee9 100644 --- a/src/characters/v_utils.c +++ b/src/characters/v_utils.c @@ -2804,7 +2804,9 @@ void V_NonShellEffects(edict_t *ent) { } void V_SetEffects(edict_t *ent) { - int effects, r_effects; + int effects, r_effects, preserved_renderfx; + + preserved_renderfx = ent->s.renderfx & RF_CUSTOMSKIN; // clear all effects ent->s.effects = ent->s.renderfx = 0; @@ -2813,8 +2815,10 @@ void V_SetEffects(edict_t *ent) { if (que_typeexists(ent->curses, CURSE_PLAGUE)) ent->s.effects |= EF_FLIES; - if (ent->mtype != M_MAGMINE && ent->health < 1) + if (ent->mtype != M_MAGMINE && ent->health < 1) { + ent->s.renderfx |= preserved_renderfx; return; + } // apply non-ability shell effects V_ShellNonAbilityEffects(ent); @@ -2843,6 +2847,8 @@ void V_SetEffects(edict_t *ent) { // apply non-shell effects V_NonShellEffects(ent); + + ent->s.renderfx |= preserved_renderfx; } /* diff --git a/src/combat/weapons/g_weapon.c b/src/combat/weapons/g_weapon.c index 99469c36..6e49b82c 100644 --- a/src/combat/weapons/g_weapon.c +++ b/src/combat/weapons/g_weapon.c @@ -1035,8 +1035,7 @@ void fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int s grenade->radius_dmg = radius_damage; grenade->dmg_radius = damage_radius; grenade->classname = "grenade"; - if (self->client) - grenade->svflags |= SVF_PROJECTILE; + grenade->svflags |= SVF_PROJECTILE; gi.linkentity (grenade); } @@ -1079,8 +1078,7 @@ edict_t *fire_grenade2 (edict_t *self, vec3_t start, vec3_t aimdir, int damage, grenade->radius_dmg = radius_damage; grenade->dmg_radius = damage_radius; grenade->classname = "hgrenade"; - if (self->client) - grenade->svflags |= SVF_PROJECTILE; + grenade->svflags |= SVF_PROJECTILE; if (held) grenade->spawnflags = 3; else diff --git a/src/entities/drone/drone_ai.c b/src/entities/drone/drone_ai.c index c22eb251..0c104a2c 100644 --- a/src/entities/drone/drone_ai.c +++ b/src/entities/drone/drone_ai.c @@ -245,6 +245,9 @@ void ai_charge (edict_t *self, float dist) VectorSubtract (self->enemy->s.origin, self->s.origin, v); self->ideal_yaw = vectoyaw(v); M_ChangeYaw (self); + + if (entdist(self, self->enemy) <= MELEE_DISTANCE * 2.5f) + M_ChangeYaw(self); } /* @@ -257,11 +260,13 @@ Strafe sideways, but stay at aproximately the same range void ai_run_slide(edict_t *self, float distance) { float ofs; + vec3_t v; if (!self->enemy) return; - self->ideal_yaw = self->enemy->s.angles[YAW]; + VectorSubtract (self->enemy->s.origin, self->s.origin, v); + self->ideal_yaw = vectoyaw(v); M_ChangeYaw (self); if (self->monsterinfo.lefty) @@ -2287,7 +2292,7 @@ static void drone_check_active_projectile_dodge(edict_t *self) if (!(projectile->svflags & SVF_PROJECTILE)) continue; attacker = drone_projectile_attacker(projectile); - if (!G_EntExists(attacker) || !attacker->client) + if (!G_EntExists(attacker) || attacker == self) continue; if (OnSameTeam(self, attacker)) continue; diff --git a/src/entities/drone/drone_bitch.c b/src/entities/drone/drone_bitch.c index 75cdf428..705a1b8a 100644 --- a/src/entities/drone/drone_bitch.c +++ b/src/entities/drone/drone_bitch.c @@ -447,11 +447,17 @@ void mychick_jump_hold (edict_t *self) } } +static void mychick_jump_hold_ai(edict_t *self, float dist) +{ + ai_move(self, dist); + mychick_jump_hold(self); +} + mframe_t mychick_frames_leap [] = { ai_move, 0, mychick_jump_takeoff, ai_move, 0, NULL, - ai_move, 0, mychick_jump_hold, + mychick_jump_hold_ai, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, @@ -493,6 +499,17 @@ static void mychick_start_duck(edict_t *self) self->monsterinfo.dodge_time = level.time + 2.0f; } +static qboolean mychick_start_sidestep(edict_t *self, vec3_t dir) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + return false; + + drone_set_dodge_side(self, dir); + self->monsterinfo.currentmove = &mychick_move_dodge_slide; + self->monsterinfo.dodge_time = level.time + 1.0f; + return true; +} + static void mychick_dodge (edict_t *self, edict_t *attacker, vec3_t dir, int radius) { if (level.time < self->monsterinfo.dodge_time) @@ -513,8 +530,7 @@ static void mychick_dodge (edict_t *self, edict_t *attacker, vec3_t dir, int rad self->monsterinfo.attacker = attacker; if (radius) { - mychick_leap(self); - self->monsterinfo.dodge_time = level.time + 3.0f; + mychick_start_sidestep(self, dir); return; } @@ -524,14 +540,8 @@ static void mychick_dodge (edict_t *self, edict_t *attacker, vec3_t dir, int rad return; } - drone_set_dodge_side(self, dir); - - if (!(self->monsterinfo.aiflags & AI_STAND_GROUND)) - { - self->monsterinfo.currentmove = &mychick_move_dodge_slide; - self->monsterinfo.dodge_time = level.time + 1.0f; + if (mychick_start_sidestep(self, dir)) return; - } mychick_start_duck(self); } @@ -597,6 +607,9 @@ void myChickFireball (edict_t *self) speed = 650 + 35 * slvl; // spd: myChickFireball MonsterAim(self, M_PROJECTILE_ACC, speed, true, MZ2_CHICK_ROCKET_1, forward, start); + if (!M_MonsterHasClearShotFrom(self, start)) + return; + fire_fireball(self, start, forward, damage, 125.0, speed, 5, flame_damage); gi.sound(self, CHAN_ITEM, gi.soundindex("abilities/firecast.wav"), 1, ATTN_NORM, 0); @@ -626,6 +639,9 @@ void myChickRocket (edict_t *self) speed = M_ROCKETLAUNCHER_SPEED_MAX; MonsterAim(self, M_PROJECTILE_ACC, speed, true, MZ2_CHICK_ROCKET_1, forward, start); + if (!M_MonsterHasClearShotFrom(self, start)) + return; + if (self->mtype == M_CHICK_HEAT) { monster_fire_heat(self, start, forward, damage, speed, MZ2_CHICK_ROCKET_1, 0.095f); @@ -645,6 +661,9 @@ void myChickRail (edict_t *self) damage = 50 + 10 * drone_damagelevel(self); // dmg: myChickRailWorld MonsterAim(self, 0.33, 0, false, MZ2_CHICK_ROCKET_1, forward, start); + if (!M_MonsterHasClearShotFrom(self, start)) + return; + monster_fire_railgun (self, start, forward, damage, damage, MZ2_GLADIATOR_RAILGUN_1); } @@ -709,7 +728,7 @@ mmove_t mychick_move_end_attack1 = {FRAME_attak128, FRAME_attak132, mychick_fram void mychick_rerocket(edict_t *self) { - if (G_ValidTarget(self, self->enemy, true, true)) + if (G_ValidTarget(self, self->enemy, true, true) && M_MonsterHasClearShotFromFlash(self, MZ2_CHICK_ROCKET_1)) { if (random() <= 0.8 && (entdist(self, self->enemy) <= 512 || (self->monsterinfo.aiflags & AI_STAND_GROUND))) self->monsterinfo.currentmove = &mychick_move_attack1; @@ -744,7 +763,7 @@ void mychick_runandshoot (edict_t *self) void mychick_continue (edict_t *self) { if (G_ValidTarget(self, self->enemy, true, true) && (random() <= 0.9) - && (entdist(self, self->enemy) <= 512)) + && (entdist(self, self->enemy) <= 512) && M_MonsterHasClearShotFromFlash(self, MZ2_CHICK_ROCKET_1)) { self->monsterinfo.currentmove = &mychick_move_runandshoot; @@ -842,7 +861,9 @@ void chick_fire_attack (edict_t *self) } else { - if (self->monsterinfo.aiflags & AI_STAND_GROUND) + if (!M_MonsterHasClearShotFromFlash(self, MZ2_CHICK_ROCKET_1)) + self->monsterinfo.currentmove = &mychick_move_slash; + else if (self->monsterinfo.aiflags & AI_STAND_GROUND) self->monsterinfo.currentmove = &mychick_move_attack1; else mychick_runandshoot(self); @@ -864,6 +885,9 @@ void mychick_attack(edict_t *self) return; } + if (!M_MonsterHasClearShotFromFlash(self, MZ2_CHICK_ROCKET_1)) + return; + if (self->monsterinfo.aiflags & AI_STAND_GROUND) self->monsterinfo.currentmove = &mychick_move_attack1; else diff --git a/src/entities/drone/drone_brain.c b/src/entities/drone/drone_brain.c index 15ff1162..642f1b9d 100644 --- a/src/entities/drone/drone_brain.c +++ b/src/entities/drone/drone_brain.c @@ -341,6 +341,12 @@ void mybrain_jump_hold (edict_t *self) self->monsterinfo.aiflags |= AI_HOLD_FRAME; } +static void mybrain_jump_hold_ai(edict_t *self, float dist) +{ + ai_move(self, dist); + mybrain_jump_hold(self); +} + mframe_t mybrain_frames_duck [] = { ai_move, 1, mybrain_duck_down, @@ -358,7 +364,7 @@ mframe_t mybrain_frames_jump [] = { ai_move, 0, NULL, ai_move, 0, mybrain_jump_takeoff, - ai_move, 0, mybrain_jump_hold, + mybrain_jump_hold_ai, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, @@ -445,12 +451,18 @@ void mybrain_jumpattack_hold (edict_t *self) } } +static void mybrain_jumpattack_hold_ai(edict_t *self, float dist) +{ + ai_move(self, dist); + mybrain_jumpattack_hold(self); +} + mframe_t mybrain_frames_jumpattack [] = { ai_move, 0, NULL, ai_move, 0, mybrain_jumpattack_takeoff, - ai_move, 0, mybrain_jumpattack_hold, + mybrain_jumpattack_hold_ai, 0, NULL, ai_move, 0, NULL, ai_move, 0, mybrain_jumpattack_landing, ai_move, 0, NULL, @@ -785,12 +797,78 @@ static const vec3_t brain_leye[] = {-4.332820f, 9.444570f, 33.526340f} }; +static int mybrain_eye_frame_index(edict_t *self) +{ + int frame_index = self->s.frame - FRAME_walk101; + + if (frame_index < 0 || frame_index >= 11) + frame_index = 0; + + return frame_index; +} + +static void mybrain_project_eye_origin(edict_t *self, qboolean left_eye, vec3_t start) +{ + vec3_t forward, right, up; + const vec3_t *eye_offsets = left_eye ? brain_leye : brain_reye; + int frame_index = mybrain_eye_frame_index(self); + + AngleVectors(self->s.angles, forward, right, up); + VectorCopy(self->s.origin, start); + VectorMA(start, eye_offsets[frame_index][0], right, start); + VectorMA(start, eye_offsets[frame_index][1], forward, start); + VectorMA(start, eye_offsets[frame_index][2], up, start); +} + +static qboolean mybrain_trace_eye_target(edict_t *self, vec3_t start, vec3_t target) +{ + trace_t tr; + + tr = gi.trace(start, NULL, NULL, target, self, MASK_SHOT); + return tr.ent && tr.ent == self->enemy; +} + +static void mybrain_aim_eye_laser(edict_t *self, vec3_t start, vec3_t aim) +{ + vec3_t target, predicted; + float offset; + trace_t tr; + + VectorCopy(self->enemy->s.origin, target); + if (!mybrain_trace_eye_target(self, start, target)) + { + G_EntViewPoint(self->enemy, target); + if (!mybrain_trace_eye_target(self, start, target)) + G_EntMidPoint(self->enemy, target); + } + + offset = 0.10f + random() * 0.10f; + VectorMA(target, -offset, self->enemy->velocity, predicted); + tr = gi.trace(start, NULL, NULL, predicted, self, MASK_SOLID); + if (tr.fraction < 0.9f) + VectorCopy(target, predicted); + + VectorSubtract(predicted, start, aim); + VectorNormalize(aim); +} + +static qboolean mybrain_has_eye_laser_shot(edict_t *self, qboolean left_eye) +{ + vec3_t start; + + mybrain_project_eye_origin(self, left_eye, start); + return M_MonsterHasClearShotFrom(self, start); +} + +static qboolean mybrain_has_laser_shot(edict_t *self) +{ + return mybrain_has_eye_laser_shot(self, false) || mybrain_has_eye_laser_shot(self, true); +} + static void mybrain_eye_laser_update(edict_t *laser, qboolean left_eye) { edict_t *self; - vec3_t forward, right, up, start, aim, target; - int frame_index; - const vec3_t *eye_offsets; + vec3_t start, aim; qboolean do_damage; self = laser->owner; @@ -800,20 +878,14 @@ static void mybrain_eye_laser_update(edict_t *laser, qboolean left_eye) return; } - AngleVectors(self->s.angles, forward, right, up); - frame_index = self->s.frame - FRAME_walk101; - if (frame_index < 0 || frame_index >= 11) - frame_index = 0; - - eye_offsets = left_eye ? brain_leye : brain_reye; - VectorCopy(self->s.origin, start); - VectorMA(start, eye_offsets[frame_index][0], right, start); - VectorMA(start, eye_offsets[frame_index][1], forward, start); - VectorMA(start, eye_offsets[frame_index][2], up, start); + mybrain_project_eye_origin(self, left_eye, start); + if (!M_MonsterHasClearShotFrom(self, start)) + { + laser->spawnflags |= DABEAM_SPAWNED; + return; + } - G_EntMidPoint(self->enemy, target); - VectorSubtract(target, start, aim); - VectorNormalize(aim); + mybrain_aim_eye_laser(self, start, aim); VectorCopy(start, laser->s.origin); VectorCopy(aim, laser->movedir); @@ -841,32 +913,35 @@ static void mybrain_laserbeam(edict_t *self) return; damage = M_DABEAM_DMG_BASE + M_DABEAM_DMG_ADDON * drone_damagelevel(self); - if (M_DABEAM_DMG_MAX && damage > M_DABEAM_DMG_MAX) + if (M_DABEAM_DMG_MAX && damage > M_DABEAM_DMG_MAX) damage = M_DABEAM_DMG_MAX; - monster_fire_dabeam(self, damage, false, mybrain_right_eye_laser_update); - monster_fire_dabeam(self, damage, true, mybrain_left_eye_laser_update); + if (mybrain_has_eye_laser_shot(self, false)) + monster_fire_dabeam(self, damage, false, mybrain_right_eye_laser_update); + if (mybrain_has_eye_laser_shot(self, true)) + monster_fire_dabeam(self, damage, true, mybrain_left_eye_laser_update); } static void mybrain_laserbeam_reattack(edict_t *self) { - if (G_EntExists(self->enemy) && visible(self, self->enemy) && self->enemy->health > 0 && random() < 0.5) + if (G_EntExists(self->enemy) && visible(self, self->enemy) && self->enemy->health > 0 + && mybrain_has_laser_shot(self) && random() < 0.5) self->monsterinfo.nextframe = FRAME_walk101; } mframe_t mybrain_frames_attack4[] = { + ai_charge, 9, mybrain_laserbeam, + ai_charge, 2, mybrain_laserbeam, + ai_charge, 3, mybrain_laserbeam, + ai_charge, 3, mybrain_laserbeam, + ai_charge, 1, mybrain_laserbeam, ai_charge, 0, mybrain_laserbeam, ai_charge, 0, mybrain_laserbeam, - ai_charge, 0, mybrain_laserbeam, - ai_charge, 0, mybrain_laserbeam, - ai_charge, 0, mybrain_laserbeam, - ai_charge, 0, mybrain_laserbeam, - ai_charge, 0, mybrain_laserbeam, - ai_charge, 0, mybrain_laserbeam, - ai_charge, 0, mybrain_laserbeam, - ai_charge, 0, mybrain_laserbeam, - ai_charge, 0, mybrain_laserbeam_reattack + ai_charge, 10, mybrain_laserbeam, + ai_charge, -4, mybrain_laserbeam, + ai_charge, -1, mybrain_laserbeam, + ai_charge, 2, mybrain_laserbeam_reattack }; mmove_t mybrain_move_attack4 = {FRAME_walk101, FRAME_walk111, mybrain_frames_attack4, mybrain_run}; @@ -890,7 +965,7 @@ void mybrain_attack (edict_t *self) has_los = visible(self, self->enemy); // laser attack - if (has_los && dist >= 192 && dist <= 640 && random() < 0.15) + if (has_los && dist >= 192 && dist <= 640 && mybrain_has_laser_shot(self) && random() < 0.15) self->monsterinfo.currentmove = &mybrain_move_attack4; // jump to our enemy if he's close and on even ground else if ((dist > 256) && (self->enemy->absmin[2]+18 >= self->absmin[2]) diff --git a/src/entities/drone/drone_carrier.c b/src/entities/drone/drone_carrier.c index baad7e28..8bd50de5 100644 --- a/src/entities/drone/drone_carrier.c +++ b/src/entities/drone/drone_carrier.c @@ -10,7 +10,15 @@ carrier #include "../../quake2/monsterframes/m_rogue_carrier.h" #define CARRIER_SUMMON_COUNT 4 -#define CARRIER_SUMMON_COOLDOWN 8.0f +#define CARRIER_SUMMON_COOLDOWN 6.0f +#define CARRIER_HEAT_TURN_FRACTION 0.085f +#define CARRIER_NO_SPAWN_Z -99999.0f +#define CARRIER_NO_SPAWN_YAW -99999.0f +#define CARRIER_YAW_SPEED 20.0f +#define CARRIER_SPAWN_YAW_SPEED 30.0f +#define CARRIER_AI_SPAWNING 0x00400000 +#define CARRIER_RAIL_REFIRE_CHANCE 0.65f +#define CARRIER_RAIL_MAX_REFIRES 1 #define CARRIER_DEFAULT_SCALE 0.75f #define CARRIER_INVASION_SCALE 0.50f // 0.60 was too big for spambox @@ -25,11 +33,17 @@ static int sound_spawn; void drone_ai_stand(edict_t *self, float dist); void drone_ai_run(edict_t *self, float dist); void drone_ai_walk(edict_t *self, float dist); +qboolean drone_findtarget(edict_t *self, qboolean force); static void carrier_stand(edict_t *self); static void carrier_walk(edict_t *self); static void carrier_run(edict_t *self); static void carrier_attack(edict_t *self); +static void carrier_attack_grenade(edict_t *self); +static void carrier_reattack_grenade(edict_t *self); +static void carrier_attack_rail(edict_t *self); +static void carrier_reattack_rail(edict_t *self); +static void carrier_turn_to_spawn_yaw(edict_t *self); static void carrier_project_flash(edict_t *self, int flash, vec3_t forward, vec3_t start) { @@ -42,6 +56,38 @@ static void carrier_project_flash(edict_t *self, int flash, vec3_t forward, vec3 G_ProjectSource(self->s.origin, offset, forward, right, start); } +static void carrier_spawn_ai(edict_t *self, float dist) +{ + if (!self || !self->inuse) + return; + + if (que_typeexists(self->curses, CURSE_FROZEN)) + return; + + VectorClear(self->velocity); + VectorClear(self->avelocity); + if (self->monsterinfo.aiflags & CARRIER_AI_SPAWNING) + carrier_turn_to_spawn_yaw(self); + (void)dist; +} + +static void carrier_restore_spawn_steering(edict_t *self) +{ + if (self->monsterinfo.aiflags & CARRIER_AI_SPAWNING) + { + if (self->delay > 0.0f) + { + self->yaw_speed = self->delay; + self->delay = 0.0f; + } + else + self->yaw_speed = CARRIER_YAW_SPEED; + } + + self->monsterinfo.aiflags &= ~(AI_HOLD_FRAME | CARRIER_AI_SPAWNING); + VectorClear(self->avelocity); +} + static const int carrier_summons[CARRIER_SUMMON_COUNT] = { M_DAEDALUS, @@ -87,7 +133,7 @@ static void carrier_dead(edict_t *self) M_Remove(self, false, false); } -static void carrier_fire_rocket(edict_t *self) +static void carrier_fire_heat(edict_t *self) { int damage; int speed; @@ -114,7 +160,7 @@ static void carrier_fire_rocket(edict_t *self) { carrier_project_flash(self, flashes[i], forward, start); MonsterAim(self, M_PROJECTILE_ACC, speed, true, flashes[i], forward, start); - monster_fire_heat(self, start, forward, damage, speed, flashes[i], 0.06f); + monster_fire_heat(self, start, forward, damage, speed, flashes[i], CARRIER_HEAT_TURN_FRACTION); } } @@ -151,6 +197,9 @@ static void carrier_fire_grenade(edict_t *self) int damage; int speed; vec3_t forward, right, up, start, target, aim, offset; + float direction; + float spread_r, spread_u; + int mytime; if (!G_EntExists(self->enemy)) return; @@ -172,9 +221,40 @@ static void carrier_fire_grenade(edict_t *self) target[2] += self->enemy->viewheight; VectorSubtract(target, start, aim); VectorNormalize(aim); - VectorMA(aim, crandom() * 0.15f, right, aim); - VectorMA(aim, 0.1f, up, aim); + + direction = (random() < 0.5f) ? -1.0f : 1.0f; + mytime = (int)((level.time - self->timestamp) / 0.4f); + switch (mytime) + { + case 0: + spread_r = 0.15f * direction; + spread_u = 0.1f - 0.1f * direction; + break; + case 1: + spread_r = 0.0f; + spread_u = 0.1f; + break; + case 2: + spread_r = -0.15f * direction; + spread_u = 0.1f + 0.1f * direction; + break; + case 3: + spread_r = 0.0f; + spread_u = 0.1f; + break; + default: + spread_r = 0.0f; + spread_u = 0.0f; + break; + } + + VectorMA(aim, spread_r, right, aim); + VectorMA(aim, spread_u, up, aim); VectorNormalize(aim); + if (aim[2] > 0.15f) + aim[2] = 0.15f; + else if (aim[2] < -0.5f) + aim[2] = -0.5f; monster_fire_grenade(self, start, aim, damage, speed, MZ2_CARRIER_GRENADE); } @@ -192,11 +272,26 @@ static void carrier_fire_rail(edict_t *self) damage = M_RAILGUN_DMG_MAX; carrier_project_flash(self, MZ2_CARRIER_RAILGUN, forward, start); - MonsterAim(self, 0.25f, 0, false, MZ2_CARRIER_RAILGUN, forward, start); + if (self->pos2[2] > CARRIER_NO_SPAWN_Z + 1.0f) + { + VectorSubtract(self->pos2, start, forward); + VectorNormalize(forward); + } + else + MonsterAim(self, 0.25f, 0, false, MZ2_CARRIER_RAILGUN, forward, start); gi.sound(self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0); monster_fire_railgun(self, start, forward, damage, damage, MZ2_CARRIER_RAILGUN); } +static void carrier_save_rail_target(edict_t *self) +{ + if (!G_EntExists(self->enemy)) + return; + + VectorCopy(self->enemy->s.origin, self->pos2); + self->pos2[2] += self->enemy->viewheight; +} + static qboolean carrier_valid_spawn_spot(edict_t *self, vec3_t mins, vec3_t maxs, vec3_t spot) { if (G_IsValidLocation(self, spot, mins, maxs)) @@ -210,28 +305,105 @@ static qboolean carrier_valid_spawn_spot(edict_t *self, vec3_t mins, vec3_t maxs return G_IsValidLocation(self, spot, mins, maxs); } +static void carrier_spawn_basis(edict_t *self, vec3_t forward, vec3_t right) +{ + vec3_t angles; + + VectorCopy(self->s.angles, angles); + if ((self->monsterinfo.aiflags & CARRIER_AI_SPAWNING) + && self->angle > CARRIER_NO_SPAWN_YAW + 1.0f) + angles[YAW] = self->angle; + + AngleVectors(angles, forward, right, NULL); +} + +static int carrier_spawn_type(int index) +{ + return carrier_summons[index % CARRIER_SUMMON_COUNT]; +} + +static void carrier_spawn_bounds(int mtype, vec3_t mins, vec3_t maxs) +{ + switch (mtype) + { + case M_FLYER: + VectorSet(mins, -16, -16, -24); + VectorSet(maxs, 16, 16, 8); + break; + case M_FLOATER: + VectorSet(mins, -24, -24, -24); + VectorSet(maxs, 24, 24, 40); + break; + case M_DAEDALUS: + case M_HOVER: + default: + VectorSet(mins, -24, -24, -24); + VectorSet(maxs, 24, 24, 32); + break; + } +} + static qboolean carrier_find_spawn_spot(edict_t *self, vec3_t mins, vec3_t maxs, int index, vec3_t spot) { vec3_t forward, right; - float side; - float dist; + static const vec3_t local_offsets[8] = + { + { 105.0f, 0.0f, -58.0f }, + { 150.0f, 0.0f, -58.0f }, + { 200.0f, 0.0f, -58.0f }, + { 140.0f, 72.0f, -58.0f }, + { 140.0f, -72.0f, -58.0f }, + { 190.0f, 96.0f, -48.0f }, + { 190.0f, -96.0f, -48.0f }, + { 220.0f, 0.0f, -32.0f } + }; + static const float ring_dirs[8][2] = + { + { 1.0f, 0.0f }, + { 0.707f, 0.707f }, + { 0.0f, 1.0f }, + { -0.707f, 0.707f }, + { -1.0f, 0.0f }, + { -0.707f,-0.707f }, + { 0.0f, -1.0f }, + { 0.707f,-0.707f } + }; + static const float radii[3] = { 160.0f, 240.0f, 320.0f }; + static const float z_offsets[3] = { -58.0f, -32.0f, 8.0f }; - AngleVectors(self->s.angles, forward, right, NULL); - side = (index & 1) ? 104.0f : -104.0f; - dist = 128.0f + 48.0f * (float)(index / 2); - - VectorCopy(self->s.origin, spot); - VectorMA(spot, dist, forward, spot); - VectorMA(spot, side, right, spot); - spot[2] -= 32; - if (carrier_valid_spawn_spot(self, mins, maxs, spot)) - return true; + carrier_spawn_basis(self, forward, right); + + for (int i = 0; i < 8; i++) + { + const int offset_index = i; + + VectorCopy(self->s.origin, spot); + VectorMA(spot, local_offsets[offset_index][0], forward, spot); + VectorMA(spot, local_offsets[offset_index][1], right, spot); + spot[2] += local_offsets[offset_index][2]; + if (carrier_valid_spawn_spot(self, mins, maxs, spot)) + return true; + } - VectorCopy(self->s.origin, spot); - VectorMA(spot, -96, forward, spot); - VectorMA(spot, side, right, spot); - spot[2] -= 16; - return carrier_valid_spawn_spot(self, mins, maxs, spot); + for (int radius_index = 0; radius_index < 3; radius_index++) + { + for (int z_index = 0; z_index < 3; z_index++) + { + for (int dir_index = 0; dir_index < 8; dir_index++) + { + const int rotated_index = dir_index; + + VectorCopy(self->s.origin, spot); + VectorMA(spot, radii[radius_index] * ring_dirs[rotated_index][0], forward, spot); + VectorMA(spot, radii[radius_index] * ring_dirs[rotated_index][1], right, spot); + spot[2] += z_offsets[z_index]; + if (carrier_valid_spawn_spot(self, mins, maxs, spot)) + return true; + } + } + } + + return false; } static void carrier_cleanup_failed_spawn(edict_t *owner, edict_t *spawned) @@ -255,15 +427,34 @@ static void carrier_setup_invasion_spawn(edict_t *spawned) spawned->goalentity = NULL; } +static void carrier_start_spawned_monster(edict_t *self, edict_t *spawned) +{ + const qboolean force_start = invasion->value || pvm->value; + + if (G_ValidTarget(spawned, self->enemy, !force_start, true)) + { + spawned->enemy = self->enemy; + VectorCopy(self->enemy->s.origin, spawned->monsterinfo.last_sighting); + } + else if (force_start) + drone_findtarget(spawned, true); + + if ((spawned->enemy || spawned->goalentity) && spawned->monsterinfo.run) + spawned->monsterinfo.run(spawned); + else if (spawned->monsterinfo.stand) + spawned->monsterinfo.stand(spawned); +} + static qboolean carrier_spawn_monster(edict_t *self, int index) { edict_t *owner; edict_t *spawned; + vec3_t saved_spot; vec3_t spot; owner = (self->activator && self->activator->inuse) ? self->activator : self; spawned = G_Spawn(); - spawned->mtype = carrier_summons[index % CARRIER_SUMMON_COUNT]; + spawned->mtype = carrier_spawn_type(index); spawned->activator = owner; spawned->monsterinfo.level = self->monsterinfo.level; @@ -273,7 +464,10 @@ static qboolean carrier_spawn_monster(edict_t *self, int index) return false; } - if (!carrier_find_spawn_spot(self, spawned->mins, spawned->maxs, index, spot)) + VectorCopy(self->pos1, saved_spot); + if (saved_spot[2] > CARRIER_NO_SPAWN_Z + 1.0f && G_IsValidLocation(self, saved_spot, spawned->mins, spawned->maxs)) + VectorCopy(saved_spot, spot); + else if (!carrier_find_spawn_spot(self, spawned->mins, spawned->maxs, index, spot)) { carrier_cleanup_failed_spawn(owner, spawned); return false; @@ -288,56 +482,151 @@ static qboolean carrier_spawn_monster(edict_t *self, int index) spawned->monsterinfo.attack_finished = level.time + 1.0; carrier_setup_invasion_spawn(spawned); - if (G_ValidTarget(spawned, self->enemy, true, true)) - spawned->enemy = self->enemy; - gi.linkentity(spawned); owner->num_monsters += spawned->monsterinfo.control_cost; owner->num_monsters_real++; - if (spawned->enemy && spawned->monsterinfo.run) - spawned->monsterinfo.run(spawned); - else if (spawned->monsterinfo.stand) - spawned->monsterinfo.stand(spawned); + if (sound_spawn) + gi.sound(self, CHAN_BODY, sound_spawn, 1, ATTN_NORM, 0); + carrier_start_spawned_monster(self, spawned); return true; } -static void carrier_spawngrows(edict_t *self) +static qboolean carrier_spawngrow(edict_t *self, int index) { - vec3_t mins, maxs, size, spot, effect_origin; + vec3_t mins, maxs, size, spot; float radius; - VectorSet(mins, -24, -24, -24); - VectorSet(maxs, 24, 24, 32); + carrier_spawn_bounds(carrier_spawn_type(index), mins, maxs); VectorSubtract(maxs, mins, size); radius = VectorLength(size) * 0.5f; - for (int i = 0; i < CARRIER_SUMMON_COUNT; i++) + VectorSet(self->pos1, 0, 0, CARRIER_NO_SPAWN_Z); + if (!carrier_find_spawn_spot(self, mins, maxs, index, spot)) + return false; + + VectorCopy(spot, self->pos1); + SpawnGrow_Spawn(spot, radius, radius * 2.0f); + return true; +} + +static float carrier_yaw_delta(float a, float b) +{ + float delta = anglemod(a - b); + + if (delta > 180.0f) + delta = 360.0f - delta; + return delta; +} + +static qboolean carrier_set_spawn_base_yaw(edict_t *self) +{ + vec3_t to_enemy; + + if (!G_EntExists(self->enemy)) + return false; + + VectorSubtract(self->enemy->s.origin, self->s.origin, to_enemy); + self->move_angles[YAW] = vectoyaw(to_enemy); + return true; +} + +static void carrier_set_spawn_yaw(edict_t *self) +{ + static const float yaw_offsets[CARRIER_SUMMON_COUNT] = { -30.0f, 0.0f, 30.0f, 0.0f }; + + self->angle = anglemod(self->move_angles[YAW] + yaw_offsets[self->count % CARRIER_SUMMON_COUNT]); + self->ideal_yaw = self->angle; + self->teleport_time = level.time + 1.0f; +} + +static void carrier_turn_to_spawn_yaw(edict_t *self) +{ + if (self->angle <= CARRIER_NO_SPAWN_YAW + 1.0f) + return; + + VectorClear(self->velocity); + VectorClear(self->avelocity); + self->ideal_yaw = self->angle; + M_ChangeYaw(self); +} + +static void carrier_prep_spawn(edict_t *self) +{ + self->count = 0; + self->timestamp = level.time; + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + if (!(self->monsterinfo.aiflags & CARRIER_AI_SPAWNING)) + self->delay = self->yaw_speed > 0.0f ? self->yaw_speed : CARRIER_YAW_SPEED; + self->monsterinfo.aiflags |= CARRIER_AI_SPAWNING; + self->yaw_speed = CARRIER_SPAWN_YAW_SPEED; + self->angle = CARRIER_NO_SPAWN_YAW; + VectorClear(self->velocity); + VectorClear(self->avelocity); + VectorSet(self->pos1, 0, 0, CARRIER_NO_SPAWN_Z); + if (carrier_set_spawn_base_yaw(self)) + carrier_set_spawn_yaw(self); +} + +static void carrier_start_spawn(edict_t *self) +{ + if (self->angle <= CARRIER_NO_SPAWN_YAW + 1.0f + && carrier_set_spawn_base_yaw(self)) + carrier_set_spawn_yaw(self); +} + +static void carrier_ready_spawn(edict_t *self) +{ + if (self->angle <= CARRIER_NO_SPAWN_YAW + 1.0f) { - if (!carrier_find_spawn_spot(self, mins, maxs, i, spot)) - continue; + if (!carrier_set_spawn_base_yaw(self)) + { + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + return; + } + carrier_set_spawn_yaw(self); + } - VectorAdd(mins, maxs, effect_origin); - VectorAdd(spot, effect_origin, effect_origin); - SpawnGrow_Spawn(effect_origin, radius, radius * 2.0f); + if (carrier_yaw_delta(self->s.angles[YAW], self->angle) > 0.5f + && level.time < self->teleport_time) + { + self->monsterinfo.aiflags |= AI_HOLD_FRAME; + return; } + + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + carrier_spawngrow(self, self->count); } -static void carrier_finish_spawn(edict_t *self) +static void carrier_spawn_check(edict_t *self) { - int spawned = 0; - - for (int i = 0; i < CARRIER_SUMMON_COUNT; i++) + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + if (self->count < CARRIER_SUMMON_COUNT) { - if (carrier_spawn_monster(self, i)) - spawned++; + carrier_spawn_monster(self, self->count); + self->count++; } - if (spawned) + if (self->count < CARRIER_SUMMON_COUNT) + { + self->angle = CARRIER_NO_SPAWN_YAW; + VectorSet(self->pos1, 0, 0, CARRIER_NO_SPAWN_Z); + carrier_set_spawn_yaw(self); + self->monsterinfo.nextframe = FRAME_spawn08; + } + else self->monsterinfo.melee_finished = level.time + CARRIER_SUMMON_COOLDOWN; } +static void carrier_done_spawn(edict_t *self) +{ + self->count = 0; + carrier_restore_spawn_steering(self); + self->angle = CARRIER_NO_SPAWN_YAW; + VectorSet(self->pos1, 0, 0, CARRIER_NO_SPAWN_Z); +} + mframe_t carrier_frames_stand[] = { drone_ai_stand, 0, NULL, @@ -381,6 +670,8 @@ mmove_t carrier_move_run = { FRAME_search01, FRAME_search13, carrier_frames_run, static void carrier_run(edict_t *self) { + carrier_restore_spawn_steering(self); + if (self->monsterinfo.aiflags & AI_STAND_GROUND) self->monsterinfo.currentmove = &carrier_move_stand; else @@ -405,64 +696,133 @@ mframe_t carrier_frames_attack_mg[] = }; mmove_t carrier_move_attack_mg = { FRAME_firea06, FRAME_firea11, carrier_frames_attack_mg, carrier_run }; -mframe_t carrier_frames_attack_rocket[] = +mframe_t carrier_frames_attack_heat[] = { ai_charge, 0, NULL, - ai_charge, 0, carrier_fire_rocket, + ai_charge, 0, carrier_fire_heat, ai_charge, 0, NULL, ai_charge, 0, NULL }; -mmove_t carrier_move_attack_rocket = { FRAME_fireb01, FRAME_fireb04, carrier_frames_attack_rocket, carrier_run }; +mmove_t carrier_move_attack_heat = { FRAME_fireb01, FRAME_fireb04, carrier_frames_attack_heat, carrier_run }; -mframe_t carrier_frames_attack_grenade[] = +mframe_t carrier_frames_attack_pre_grenade[] = { ai_charge, 0, NULL, ai_charge, 0, NULL, - ai_charge, -15, carrier_fire_grenade, - ai_charge, 0, NULL + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, carrier_attack_grenade }; -mmove_t carrier_move_attack_grenade = { FRAME_fireb07, FRAME_fireb10, carrier_frames_attack_grenade, carrier_run }; +mmove_t carrier_move_attack_pre_grenade = { FRAME_fireb01, FRAME_fireb06, carrier_frames_attack_pre_grenade, NULL }; -mframe_t carrier_frames_attack_rail[] = +mframe_t carrier_frames_attack_grenade[] = { - ai_charge, 0, NULL, - ai_charge, -10, NULL, - ai_charge, -20, carrier_fire_rail, - ai_charge, -10, NULL, - ai_charge, 0, NULL + ai_charge, -15, carrier_fire_grenade, + ai_charge, 4, NULL, + ai_charge, 4, NULL, + ai_charge, 4, carrier_reattack_grenade }; -mmove_t carrier_move_attack_rail = { FRAME_search01, FRAME_search05, carrier_frames_attack_rail, carrier_run }; +mmove_t carrier_move_attack_grenade = { FRAME_fireb07, FRAME_fireb10, carrier_frames_attack_grenade, NULL }; -mframe_t carrier_frames_spawn[] = +mframe_t carrier_frames_attack_post_grenade[] = { ai_charge, 0, NULL, ai_charge, 0, NULL, ai_charge, 0, NULL, ai_charge, 0, NULL, - ai_charge, 0, carrier_spawngrows, - ai_charge, 0, NULL, - ai_charge, 0, NULL, - ai_charge, 0, NULL, - ai_charge, 0, NULL, - ai_charge, 0, NULL, - ai_charge, 0, NULL, - ai_charge, -2, carrier_finish_spawn, - ai_charge, -6, NULL, - ai_charge, -10, NULL, - ai_charge, -6, NULL, - ai_charge, -2, NULL, ai_charge, 0, NULL, ai_charge, 0, NULL }; +mmove_t carrier_move_attack_post_grenade = { FRAME_fireb11, FRAME_fireb16, carrier_frames_attack_post_grenade, carrier_run }; + +mframe_t carrier_frames_attack_rail[] = +{ + ai_charge, 2, NULL, + ai_charge, 2, carrier_save_rail_target, + ai_charge, 2, NULL, + ai_charge, -20, carrier_fire_rail, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, NULL, + ai_charge, 2, carrier_reattack_rail +}; +mmove_t carrier_move_attack_rail = { FRAME_search01, FRAME_search09, carrier_frames_attack_rail, carrier_run }; + +mframe_t carrier_frames_spawn[] = +{ + carrier_spawn_ai, -2, NULL, + carrier_spawn_ai, -2, NULL, + carrier_spawn_ai, -2, NULL, + carrier_spawn_ai, -2, NULL, + carrier_spawn_ai, -2, NULL, + carrier_spawn_ai, -2, NULL, + carrier_spawn_ai, -2, carrier_prep_spawn, + carrier_spawn_ai, -2, carrier_start_spawn, + carrier_spawn_ai, -2, carrier_ready_spawn, + carrier_spawn_ai, -2, NULL, + carrier_spawn_ai, -2, NULL, + carrier_spawn_ai, -10, carrier_spawn_check, + carrier_spawn_ai, -2, NULL, + carrier_spawn_ai, -2, NULL, + carrier_spawn_ai, -2, NULL, + carrier_spawn_ai, -2, NULL, + carrier_spawn_ai, -2, NULL, + carrier_spawn_ai, -2, carrier_done_spawn +}; mmove_t carrier_move_spawn = { FRAME_spawn01, FRAME_spawn18, carrier_frames_spawn, carrier_run }; -static void carrier_start_spawn(edict_t *self) +static void carrier_attack_grenade(edict_t *self) +{ + self->timestamp = level.time; + self->monsterinfo.currentmove = &carrier_move_attack_grenade; +} + +static void carrier_reattack_grenade(edict_t *self) +{ + if (G_EntExists(self->enemy) && infront(self, self->enemy) + && self->timestamp + 1.3f > level.time) + { + self->monsterinfo.currentmove = &carrier_move_attack_grenade; + return; + } + + self->monsterinfo.currentmove = &carrier_move_attack_post_grenade; +} + +static void carrier_begin_spawn(edict_t *self) { - if (sound_spawn) - gi.sound(self, CHAN_WEAPON, sound_spawn, 1, ATTN_NORM, 0); self->monsterinfo.currentmove = &carrier_move_spawn; } +static void carrier_attack_rail(edict_t *self) +{ + self->count = 0; + self->timestamp = level.time; + VectorSet(self->pos2, 0, 0, CARRIER_NO_SPAWN_Z); + self->monsterinfo.currentmove = &carrier_move_attack_rail; +} + +static void carrier_reattack_rail(edict_t *self) +{ + if (self->count < CARRIER_RAIL_MAX_REFIRES + && G_ValidTarget(self, self->enemy, true, true) + && infront(self, self->enemy) + && random() < CARRIER_RAIL_REFIRE_CHANCE) + { + self->count++; + VectorSet(self->pos2, 0, 0, CARRIER_NO_SPAWN_Z); + self->monsterinfo.currentmove = &carrier_move_attack_rail; + self->monsterinfo.nextframe = FRAME_search01; + M_DelayNextAttack(self, 0.0f, true); + return; + } + + self->count = 0; + self->monsterinfo.attack_finished = level.time + 1.0f; +} + static void carrier_attack(edict_t *self) { float r; @@ -471,14 +831,14 @@ static void carrier_attack(edict_t *self) return; r = random(); - if (level.time >= self->monsterinfo.melee_finished && r < 0.20f) - carrier_start_spawn(self); - else if (r < 0.40f) - self->monsterinfo.currentmove = &carrier_move_attack_rocket; - else if (r < 0.60f) - self->monsterinfo.currentmove = &carrier_move_attack_grenade; + if (level.time >= self->monsterinfo.melee_finished && r < 0.25f) + carrier_begin_spawn(self); + else if (r < 0.50f) + self->monsterinfo.currentmove = &carrier_move_attack_heat; else if (r < 0.80f) - self->monsterinfo.currentmove = &carrier_move_attack_rail; + carrier_attack_rail(self); + else if (r < 0.92f) + self->monsterinfo.currentmove = &carrier_move_attack_pre_grenade; else self->monsterinfo.currentmove = &carrier_move_attack_mg; @@ -496,6 +856,8 @@ mmove_t carrier_move_pain = { FRAME_spawn01, FRAME_spawn04, carrier_frames_pain, static void carrier_pain(edict_t *self, edict_t *other, float kick, int damage) { + carrier_restore_spawn_steering(self); + if (self->health < (self->max_health / 2)) self->s.skinnum = 1; @@ -538,6 +900,7 @@ mmove_t carrier_move_death = { FRAME_death01, FRAME_death16, carrier_frames_deat static void carrier_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) { M_Notify(self); + carrier_restore_spawn_steering(self); if (self->deadflag == DEAD_DEAD) return; @@ -588,6 +951,7 @@ void init_drone_carrier(edict_t *self) self->mass = 1000; self->mtype = M_CARRIER; self->flags |= FL_FLY; + self->yaw_speed = CARRIER_YAW_SPEED; self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD; self->monsterinfo.power_armor_power = M_CARRIER_INITIAL_ARMOR + M_CARRIER_ADDON_ARMOR * self->monsterinfo.level; diff --git a/src/entities/drone/drone_daedalus.c b/src/entities/drone/drone_daedalus.c index 90a0cfb3..027ca318 100644 --- a/src/entities/drone/drone_daedalus.c +++ b/src/entities/drone/drone_daedalus.c @@ -71,10 +71,18 @@ mframe_t daedalus_frames_attack[] = }; mmove_t daedalus_move_attack = { FRAME_attak104, FRAME_attak106, daedalus_frames_attack, NULL }; +mframe_t daedalus_frames_attack_slide[] = +{ + ai_charge, 10, daedalus_fire_grenade, + ai_charge, 10, daedalus_fire_grenade, + ai_charge, 10, daedalus_reattack +}; +mmove_t daedalus_move_attack_slide = { FRAME_attak104, FRAME_attak106, daedalus_frames_attack_slide, NULL }; + mframe_t daedalus_frames_end_attack[] = { - drone_ai_run, 15, NULL, - drone_ai_run, 15, NULL + ai_charge, 1, NULL, + ai_charge, 1, NULL }; mmove_t daedalus_move_end_attack = { FRAME_attak107, FRAME_attak108, daedalus_frames_end_attack, daedalus_run }; @@ -103,12 +111,17 @@ static void daedalus_fire_grenade(edict_t *self) flash_number = MZ2_GUNCMDR_GRENADE_MORTAR_1; MonsterAim(self, M_PROJECTILE_ACC, speed, false, -1, forward, start); + if (!M_MonsterHasClearShotFrom(self, start)) + { + M_MonsterBlockedShot(self, 0.4f); + return; + } monster_fire_grenade(self, start, forward, damage, speed, flash_number); } static void daedalus_reattack(edict_t *self) { - if (G_ValidTarget(self, self->enemy, true, true) && random() <= 0.65) + if (G_ValidTarget(self, self->enemy, true, true) && random() <= 0.4) { self->s.frame = FRAME_attak104; return; @@ -121,7 +134,18 @@ static void daedalus_reattack(edict_t *self) static void daedalus_attack(edict_t *self) { - self->monsterinfo.currentmove = &daedalus_move_attack; + if (random() < 0.65f) + { + self->monsterinfo.attack_state = AS_STRAIGHT; + self->monsterinfo.currentmove = &daedalus_move_attack; + } + else + { + if (random() <= 0.5f) + self->monsterinfo.lefty = 1 - self->monsterinfo.lefty; + self->monsterinfo.attack_state = AS_SLIDING; + self->monsterinfo.currentmove = &daedalus_move_attack_slide; + } } static void daedalus_pain(edict_t *self, edict_t *other, float kick, int damage) @@ -155,31 +179,19 @@ static void daedalus_pain(edict_t *self, edict_t *other, float kick, int damage) static void daedalus_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) { - int n; + qboolean overkill; M_Notify(self); - - if (self->health <= self->gib_health) - { - gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); - if (vrx_spawn_nonessential_ent(self->s.origin)) - { - for (n = 0; n < 2; n++) - ThrowGib(self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); - for (n = 0; n < 2; n++) - ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); - ThrowHead(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); - } - M_Remove(self, false, false); - return; - } + overkill = self->health <= self->gib_health; if (self->deadflag == DEAD_DEAD) return; DroneList_Remove(self); - if (random() < 0.5) + if (overkill) + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); + else if (random() < 0.5) gi.sound(self, CHAN_VOICE, sound_death1, 1, ATTN_NORM, 0); else gi.sound(self, CHAN_VOICE, sound_death2, 1, ATTN_NORM, 0); diff --git a/src/entities/drone/drone_fixbot.c b/src/entities/drone/drone_fixbot.c index 721bc663..6b8ca1d7 100644 --- a/src/entities/drone/drone_fixbot.c +++ b/src/entities/drone/drone_fixbot.c @@ -28,10 +28,16 @@ fixbot #define FIXBOT_BOSS_DEFAULT_SCALE 2.6f #define FIXBOT_BOSS_INVASION_SCALE 2.0f #define FIXBOT_BOSS_INVASION_MOVE_SCALE 1.5f +#define FIXBOT_SPAWN_YAW_SPEED 12.0f +#define FIXBOT_SPAWN_PITCH_SPEED 12.0f +#define FIXBOT_SPAWN_AIM_TIMEOUT 2.0f +#define FIXBOT_SPAWN_AIM_EPSILON 5.0f +#define FIXBOT_NO_SPAWN_YAW -99999.0f static int sound_pain; static int sound_die; static int sound_pew; +static int sound_ionripper; static int sound_weld; static int sound_spawn; @@ -45,6 +51,7 @@ static void fixbot_walk(edict_t *self); static void fixbot_run(edict_t *self); static void fixbot_attack(edict_t *self); static void fixbot_try_start_spawn(edict_t *self); +static mmove_t fixbot_move_spawn; static qboolean fixbot_is_boss(edict_t *self) { @@ -90,7 +97,12 @@ static void fixbot_remove_turrets(edict_t *self) edict_t *next = DroneList_Next(ent); if (G_EntExists(ent) && ent->owner == self && ent->mtype == M_ROGUE_TURRET) - M_Remove(ent, false, true); + { + if (ent->die) + ent->die(ent, self, self, ent->health + 100, ent->s.origin); + else + M_Remove(ent, false, true); + } ent = next; } @@ -118,9 +130,58 @@ static qboolean fixbot_turret_position_clear(edict_t *self, vec3_t position) return true; } +static float fixbot_angle_delta(float a, float b) +{ + float delta = anglemod(a - b); + + if (delta > 180.0f) + delta = 360.0f - delta; + return delta; +} + +static qboolean fixbot_set_spawn_base_yaw(edict_t *self) +{ + vec3_t to_enemy; + + if (!G_EntExists(self->enemy)) + return false; + + VectorSubtract(self->enemy->s.origin, self->s.origin, to_enemy); + self->move_angles[YAW] = vectoyaw(to_enemy); + return true; +} + +static void fixbot_set_spawn_yaw(edict_t *self) +{ + static const float yaw_offsets[] = { -35.0f, 35.0f, 0.0f, -70.0f, 70.0f, -110.0f, 110.0f, 180.0f }; + int index; + + index = fixbot_count_live_turrets(self) % (int)(sizeof(yaw_offsets) / sizeof(yaw_offsets[0])); + self->angle = anglemod(self->move_angles[YAW] + yaw_offsets[index]); + self->ideal_yaw = self->angle; + self->teleport_time = level.time + FIXBOT_SPAWN_AIM_TIMEOUT; +} + +static void fixbot_turn_to_spawn_yaw(edict_t *self) +{ + float old_yaw_speed; + + if (self->angle <= FIXBOT_NO_SPAWN_YAW + 1.0f) + return; + + VectorClear(self->velocity); + VectorClear(self->avelocity); + self->ideal_yaw = self->angle; + old_yaw_speed = self->yaw_speed; + self->yaw_speed = FIXBOT_SPAWN_YAW_SPEED; + M_ChangeYaw(self); + self->yaw_speed = old_yaw_speed; +} + static qboolean fixbot_find_turret_spawn_position(edict_t *self, vec3_t position, vec3_t direction) { vec3_t start; + vec3_t base_angles; vec3_t initial_forward; vec3_t best_pos; vec3_t best_dir; @@ -133,7 +194,11 @@ static qboolean fixbot_find_turret_spawn_position(edict_t *self, vec3_t position VectorCopy(self->s.origin, start); start[2] += 16; - AngleVectors(self->s.angles, initial_forward, NULL, NULL); + VectorCopy(self->s.angles, base_angles); + if (self->angle > FIXBOT_NO_SPAWN_YAW + 1.0f) + base_angles[YAW] = self->angle; + base_angles[PITCH] = 0.0f; + AngleVectors(base_angles, initial_forward, NULL, NULL); VectorClear(best_pos); VectorClear(best_dir); @@ -150,7 +215,7 @@ static qboolean fixbot_find_turret_spawn_position(edict_t *self, vec3_t position qboolean in_front; qboolean better = false; - VectorCopy(self->s.angles, angles); + VectorCopy(base_angles, angles); if (attempt == 0) { VectorCopy(initial_forward, forward); @@ -238,7 +303,7 @@ static qboolean fixbot_find_turret_spawn_position(edict_t *self, vec3_t position vec3_t to_pos; trace_t tr; - VectorCopy(self->s.angles, angles); + VectorCopy(base_angles, angles); angles[YAW] += yaws[y]; while (angles[YAW] < 0) angles[YAW] += 360; @@ -384,6 +449,14 @@ static void fixbot_plasma_think(edict_t *self) VectorNormalize(self->movedir); vectoangles(self->movedir, self->s.angles); VectorScale(self->movedir, self->speed, self->velocity); + if (random() > 0.5f) + { + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_BLASTER); + gi.WritePosition(self->s.origin); + gi.WriteDir(vec3_origin); + gi.multicast(self->s.origin, MULTICAST_PVS); + } self->nextthink = level.time + FRAMETIME; return; } @@ -551,11 +624,11 @@ static void fixbot_fire_ionripper_spread(edict_t *self, vec3_t start, vec3_t for VectorMA(dir5, -0.16f, right, dir5); VectorNormalize(dir5); - monster_fire_ionripper(self, start, dir1, damage, speed, EF_IONRIPPER, MZ2_HOVER_BLASTER_1); - monster_fire_ionripper(self, start, dir2, damage, speed, EF_IONRIPPER, MZ2_HOVER_BLASTER_1); - monster_fire_ionripper(self, start, dir3, damage, speed, EF_IONRIPPER, MZ2_HOVER_BLASTER_1); - monster_fire_ionripper(self, start, dir4, damage, speed, EF_IONRIPPER, MZ2_HOVER_BLASTER_1); - monster_fire_ionripper(self, start, dir5, damage, speed, EF_IONRIPPER, MZ2_HOVER_BLASTER_1); + monster_fire_ionripper(self, start, dir1, damage, speed, EF_IONRIPPER, -1); + monster_fire_ionripper(self, start, dir2, damage, speed, EF_IONRIPPER, -1); + monster_fire_ionripper(self, start, dir3, damage, speed, EF_IONRIPPER, -1); + monster_fire_ionripper(self, start, dir4, damage, speed, EF_IONRIPPER, -1); + monster_fire_ionripper(self, start, dir5, damage, speed, EF_IONRIPPER, -1); } else { @@ -569,12 +642,12 @@ static void fixbot_fire_ionripper_spread(edict_t *self, vec3_t start, vec3_t for VectorMA(dir3, -0.12f, right, dir3); VectorNormalize(dir3); - monster_fire_ionripper(self, start, dir1, damage, speed, EF_IONRIPPER, MZ2_HOVER_BLASTER_1); - monster_fire_ionripper(self, start, dir2, damage, speed, EF_IONRIPPER, MZ2_HOVER_BLASTER_1); - monster_fire_ionripper(self, start, dir3, damage, speed, EF_IONRIPPER, MZ2_HOVER_BLASTER_1); + monster_fire_ionripper(self, start, dir1, damage, speed, EF_IONRIPPER, -1); + monster_fire_ionripper(self, start, dir2, damage, speed, EF_IONRIPPER, -1); + monster_fire_ionripper(self, start, dir3, damage, speed, EF_IONRIPPER, -1); } - gi.sound(self, CHAN_WEAPON, sound_pew, 1, ATTN_NORM, 0); + gi.sound(self, CHAN_WEAPON, sound_ionripper ? sound_ionripper : sound_pew, 1, ATTN_NORM, 0); } static void fixbot_fire_plasma(edict_t *self, float offset) @@ -730,6 +803,8 @@ static void fixbot_update_spawn_probe(edict_t *self) index = 0; VectorCopy(self->s.angles, angles); + if (self->angle > FIXBOT_NO_SPAWN_YAW + 1.0f) + angles[YAW] = self->angle; angles[YAW] += scan_yaws[index]; angles[PITCH] = -10.0f; while (angles[YAW] < 0) @@ -757,10 +832,101 @@ static qboolean fixbot_try_select_turret_position(edict_t *self) return false; } +static qboolean fixbot_get_spawn_target(edict_t *self, vec3_t target) +{ + if (VectorLength(self->pos1) >= 1) + VectorCopy(self->pos1, target); + else + VectorCopy(self->pos2, target); + + return VectorLength(target) >= 1; +} + +static float fixbot_turn_angle(float current, float ideal, float speed) +{ + float move; + float step; + + current = anglemod(current); + ideal = anglemod(ideal); + if (current == ideal) + return current; + + move = ideal - current; + if (ideal > current) + { + if (move >= 180.0f) + move -= 360.0f; + } + else + { + if (move <= -180.0f) + move += 360.0f; + } + + step = speed * FRAMETIME * 10.0f; + if (move > step) + move = step; + else if (move < -step) + move = -step; + + return anglemod(current + move); +} + +static qboolean fixbot_facing_spawn_target(edict_t *self) +{ + vec3_t dir; + vec3_t angles; + vec3_t target; + + if (!fixbot_get_spawn_target(self, target)) + return false; + + VectorSubtract(target, self->s.origin, dir); + if (VectorLength(dir) <= 1) + return false; + + vectoangles(dir, angles); + return fixbot_angle_delta(self->s.angles[YAW], angles[YAW]) <= FIXBOT_SPAWN_AIM_EPSILON; +} + +static qboolean fixbot_aim_at_spawn_target(edict_t *self) +{ + vec3_t dir; + vec3_t angles; + vec3_t target; + float old_yaw_speed; + qboolean building; + + if (!fixbot_get_spawn_target(self, target)) + return false; + + VectorSubtract(target, self->s.origin, dir); + if (VectorLength(dir) <= 1) + return false; + + vectoangles(dir, angles); + building = self->monsterinfo.currentmove == &fixbot_move_spawn; + self->ideal_yaw = angles[YAW]; + if (building) + self->s.angles[PITCH] = fixbot_turn_angle(self->s.angles[PITCH], angles[PITCH], FIXBOT_SPAWN_PITCH_SPEED); + else + self->s.angles[PITCH] = angles[PITCH]; + old_yaw_speed = self->yaw_speed; + if (building) + self->yaw_speed = FIXBOT_SPAWN_YAW_SPEED; + M_ChangeYaw(self); + self->yaw_speed = old_yaw_speed; + return true; +} + static void fixbot_prep_spawn(edict_t *self) { VectorClear(self->pos1); VectorClear(self->pos2); + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + self->angle = FIXBOT_NO_SPAWN_YAW; + self->teleport_time = level.time; if (!fixbot_is_boss(self) || level.time < self->monsterinfo.melee_finished) { @@ -777,7 +943,10 @@ static void fixbot_prep_spawn(edict_t *self) return; } + if (fixbot_set_spawn_base_yaw(self)) + fixbot_set_spawn_yaw(self); fixbot_try_select_turret_position(self); + fixbot_aim_at_spawn_target(self); self->s.effects |= EF_HYPERBLASTER | EF_PLASMA; gi.sound(self, CHAN_WEAPON, sound_weld, 1, ATTN_NORM, 0); } @@ -796,12 +965,7 @@ static void fixbot_fire_spawn_laser(edict_t *self) vec3_t start; vec3_t target; - if (VectorLength(self->pos1) >= 1) - VectorCopy(self->pos1, target); - else - VectorCopy(self->pos2, target); - - if (VectorLength(target) < 1) + if (!fixbot_get_spawn_target(self, target)) return; laser = self->beam; @@ -835,7 +999,6 @@ static void fixbot_fire_spawn_laser(edict_t *self) static void fixbot_spawn_effect(edict_t *self) { - vec3_t dir; vec3_t target; qboolean has_position; @@ -843,21 +1006,9 @@ static void fixbot_spawn_effect(edict_t *self) if (!has_position) has_position = fixbot_try_select_turret_position(self); - if (has_position) - VectorCopy(self->pos1, target); - else - VectorCopy(self->pos2, target); - - if (VectorLength(target) < 1) + if (!fixbot_get_spawn_target(self, target)) return; - VectorSubtract(target, self->s.origin, dir); - if (VectorLength(dir) > 1) - { - self->ideal_yaw = vectoyaw(dir); - M_ChangeYaw(self); - } - fixbot_fire_spawn_laser(self); gi.WriteByte(svc_temp_entity); @@ -869,6 +1020,16 @@ static void fixbot_spawn_effect(edict_t *self) gi.multicast(target, MULTICAST_PVS); } +static void fixbot_spawn_aim_ai(edict_t *self, float dist) +{ + ai_move(self, dist); + if (VectorLength(self->pos1) < 1) + fixbot_try_select_turret_position(self); + if (!fixbot_aim_at_spawn_target(self)) + fixbot_turn_to_spawn_yaw(self); + fixbot_fire_spawn_laser(self); +} + static qboolean fixbot_spawn_turret(edict_t *self) { edict_t *spawned; @@ -943,6 +1104,15 @@ static void fixbot_finish_spawn(edict_t *self) if (VectorLength(self->pos1) < 1) fixbot_try_select_turret_position(self); + fixbot_aim_at_spawn_target(self); + fixbot_fire_spawn_laser(self); + if (!fixbot_facing_spawn_target(self) && level.time < self->teleport_time) + { + self->monsterinfo.aiflags |= AI_HOLD_FRAME; + return; + } + + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; if (fixbot_spawn_turret(self)) self->monsterinfo.melee_finished = level.time + FIXBOT_BOSS_SPAWN_COOLDOWN; else @@ -950,6 +1120,7 @@ static void fixbot_finish_spawn(edict_t *self) VectorClear(self->pos1); VectorClear(self->pos2); + self->angle = FIXBOT_NO_SPAWN_YAW; fixbot_spawn_laser_off(self); self->s.effects &= ~(EF_HYPERBLASTER | EF_PLASMA); } @@ -1046,13 +1217,13 @@ static mmove_t fixbot_move_attack = { FIXBOT_FRAME_charging_01, FIXBOT_FRAME_cha static mframe_t fixbot_frames_spawn[] = { - ai_charge, 0, fixbot_prep_spawn, - ai_charge, 0, fixbot_spawn_effect, - ai_charge, 0, fixbot_spawn_effect, - ai_charge, 0, fixbot_spawn_effect, - ai_charge, 0, fixbot_spawn_effect, - ai_charge, 0, fixbot_finish_spawn, - ai_charge, 0, NULL + ai_move, 0, fixbot_prep_spawn, + fixbot_spawn_aim_ai, 0, fixbot_spawn_effect, + fixbot_spawn_aim_ai, 0, fixbot_spawn_effect, + fixbot_spawn_aim_ai, 0, fixbot_spawn_effect, + fixbot_spawn_aim_ai, 0, fixbot_spawn_effect, + ai_move, 0, fixbot_finish_spawn, + ai_move, 0, NULL }; static mmove_t fixbot_move_spawn = { FIXBOT_FRAME_weldstart_01, FIXBOT_FRAME_weldstart_07, fixbot_frames_spawn, fixbot_run }; @@ -1139,8 +1310,7 @@ static void fixbot_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int { int n; - if (fixbot_is_boss(self)) - fixbot_remove_turrets(self); + fixbot_remove_turrets(self); fixbot_spawn_laser_off(self); gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); @@ -1162,8 +1332,9 @@ static void init_drone_fixbot_common(edict_t *self, qboolean boss) sound_pain = gi.soundindex("daedalus/daedpain1.wav"); sound_die = gi.soundindex("daedalus/daeddeth1.wav"); sound_pew = gi.soundindex("makron/blaster.wav"); + sound_ionripper = gi.soundindex("weapons/rippfire.wav"); sound_weld = gi.soundindex("misc/welder1.wav"); - sound_spawn = gi.soundindex("infantry/inflies1.wav"); + sound_spawn = gi.soundindex("makron/popup.wav"); gi.soundindex("misc/welder2.wav"); gi.soundindex("misc/welder3.wav"); diff --git a/src/entities/drone/drone_float.c b/src/entities/drone/drone_float.c index d1444931..22812737 100644 --- a/src/entities/drone/drone_float.c +++ b/src/entities/drone/drone_float.c @@ -57,6 +57,11 @@ void floater_fire_blaster (edict_t *self) damage = M_HYPERBLASTER_DMG_MAX; MonsterAim(self, M_PROJECTILE_ACC, speed, false, MZ2_FLOAT_BLASTER_1, forward, start); + if (!M_MonsterHasClearShotFrom(self, start)) + { + M_MonsterBlockedShot(self, 0.35f); + return; + } monster_fire_blaster(self, start, forward, damage, speed, EF_BLASTER, BLASTER_PROJ_BOLT, 2.0, true, MZ2_FLOAT_BLASTER_1); } @@ -219,23 +224,42 @@ mmove_t floater_move_activate = {FRAME_actvat01, FRAME_actvat31, floater_frames_ mframe_t floater_frames_attack1 [] = { - drone_ai_run, 15, NULL, // Blaster attack - drone_ai_run, 15, NULL, - drone_ai_run, 15, NULL, - drone_ai_run, 15, floater_fire_blaster, // BOOM (0, -25.8, 32.5) -- LOOP Starts - drone_ai_run, 15, floater_fire_blaster, - drone_ai_run, 15, floater_fire_blaster, - drone_ai_run, 15, floater_fire_blaster, - drone_ai_run, 15, floater_fire_blaster, - drone_ai_run, 15, floater_fire_blaster, - drone_ai_run, 15, floater_fire_blaster, - drone_ai_run, 15, floater_continue_attack, - drone_ai_run, 15, NULL, - drone_ai_run, 15, NULL, - drone_ai_run, 15, NULL // -- LOOP Ends + ai_charge, 0, NULL, // Blaster attack + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, floater_fire_blaster, // BOOM (0, -25.8, 32.5) -- LOOP Starts + ai_charge, 0, floater_fire_blaster, + ai_charge, 0, floater_fire_blaster, + ai_charge, 0, floater_fire_blaster, + ai_charge, 0, floater_fire_blaster, + ai_charge, 0, floater_fire_blaster, + ai_charge, 0, floater_fire_blaster, + ai_charge, 0, floater_continue_attack, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL // -- LOOP Ends }; mmove_t floater_move_attack1 = {FRAME_attak101, FRAME_attak114, floater_frames_attack1, floater_run}; +mframe_t floater_frames_attack1a [] = +{ + ai_charge, 10, NULL, // Blaster attack + ai_charge, 10, NULL, + ai_charge, 10, NULL, + ai_charge, 10, floater_fire_blaster, // BOOM (0, -25.8, 32.5) -- LOOP Starts + ai_charge, 10, floater_fire_blaster, + ai_charge, 10, floater_fire_blaster, + ai_charge, 10, floater_fire_blaster, + ai_charge, 10, floater_fire_blaster, + ai_charge, 10, floater_fire_blaster, + ai_charge, 10, floater_fire_blaster, + ai_charge, 10, floater_continue_attack, + ai_charge, 10, NULL, + ai_charge, 10, NULL, + ai_charge, 10, NULL // -- LOOP Ends +}; +mmove_t floater_move_attack1a = {FRAME_attak101, FRAME_attak114, floater_frames_attack1a, floater_run}; + mframe_t floater_frames_attack4[] = @@ -263,6 +287,8 @@ void floater_continue_attack(edict_t* self) if (self->monsterinfo.aiflags & AI_STAND_GROUND) move = &floater_move_attack4; + else if (self->monsterinfo.attack_state == AS_SLIDING) + move = &floater_move_attack1a; else move = &floater_move_attack1; @@ -273,7 +299,7 @@ void floater_continue_attack(edict_t* self) return; } - M_DelayNextAttack(self, 0, true); + M_DelayNextAttack(self, 0, true); } mframe_t floater_frames_attack2 [] = @@ -571,9 +597,22 @@ void floater_zap (edict_t *self) void floater_attack(edict_t *self) { if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + self->monsterinfo.attack_state = AS_STRAIGHT; self->monsterinfo.currentmove = &floater_move_attack4; - else + } + else if (random() < 0.5f) + { + self->monsterinfo.attack_state = AS_STRAIGHT; self->monsterinfo.currentmove = &floater_move_attack1; + } + else + { + if (random() <= 0.5f) + self->monsterinfo.lefty = 1 - self->monsterinfo.lefty; + self->monsterinfo.attack_state = AS_SLIDING; + self->monsterinfo.currentmove = &floater_move_attack1a; + } } @@ -684,7 +723,9 @@ void init_drone_floater (edict_t *self) self->monsterinfo.run = floater_run; // self->monsterinfo.dodge = floater_dodge; self->monsterinfo.attack = floater_attack; - self->monsterinfo.melee = floater_melee; + // Future melee addon: + // self->monsterinfo.melee = floater_melee; + self->monsterinfo.melee = NULL; self->monsterinfo.sight = floater_sight; self->monsterinfo.idle = floater_idle; diff --git a/src/entities/drone/drone_flyer.c b/src/entities/drone/drone_flyer.c index 58a5c2bf..fbd3a3e9 100644 --- a/src/entities/drone/drone_flyer.c +++ b/src/entities/drone/drone_flyer.c @@ -28,7 +28,7 @@ void flyer_melee (edict_t *self); void flyer_setstart (edict_t *self); void flyer_stand (edict_t *self); void flyer_nextmove (edict_t *self); - +static void flyer_attack_finished(edict_t *self); void flyer_sight (edict_t *self, edict_t *other) { @@ -367,6 +367,11 @@ void flyer_fire (edict_t *self, int flash_number) VectorScale(offset, self->s.scale, offset); G_ProjectSource(self->s.origin, offset, forward, right, start); MonsterAim(self, M_PROJECTILE_ACC, 2000, false, -1, forward, start); + if (!M_MonsterHasClearShotFrom(self, start)) + { + M_MonsterBlockedShot(self, 0.35f); + return; + } monster_fire_blaster(self, start, forward, damage, 2000, effect, BLASTER_PROJ_BOLT, 2.0, false, flash_number); } @@ -380,49 +385,66 @@ void flyer_fireright (edict_t *self) flyer_fire (self, MZ2_FLYER_BLASTER_2); } +static void flyer_reattack_blaster(edict_t *self) +{ + if (G_EntExists(self->enemy) && visible(self, self->enemy) && random() < 0.55f) + { + self->monsterinfo.nextframe = FRAME_attak204; + return; + } + + flyer_attack_finished(self); +} + mframe_t flyer_frames_attack3[] = +{ + ai_charge, 10, NULL, + ai_charge, 10, NULL, + ai_charge, 10, NULL, + ai_charge, 10, flyer_fireleft, // left gun + ai_charge, 10, flyer_fireright, // right gun + ai_charge, 10, flyer_fireleft, // left gun + ai_charge, 10, flyer_fireright, // right gun + ai_charge, 10, flyer_fireleft, // left gun + ai_charge, 10, flyer_fireright, // right gun + ai_charge, 10, flyer_fireleft, // left gun + ai_charge, 10, flyer_fireright, // right gun + ai_charge, 10, NULL, + ai_charge, 10, NULL, + ai_charge, 10, NULL, + ai_charge, -15, flyer_reattack_blaster, + ai_charge, 10, NULL, + ai_charge, 10, NULL +}; +static void flyer_attack_finished(edict_t *self) +{ + self->monsterinfo.attack_finished = level.time + 0.8f + random() * 0.6f; + flyer_run(self); +} + +mmove_t flyer_move_attack3 = { FRAME_attak201, FRAME_attak217, flyer_frames_attack3, flyer_attack_finished }; + +mframe_t flyer_frames_attack2 [] = { ai_charge, 0, NULL, ai_charge, 0, NULL, ai_charge, 0, NULL, - ai_charge, 0, flyer_fireleft, // left gun - ai_charge, 0, flyer_fireright, // right gun - ai_charge, 0, flyer_fireleft, // left gun - ai_charge, 0, flyer_fireright, // right gun - ai_charge, 0, flyer_fireleft, // left gun - ai_charge, 0, flyer_fireright, // right gun - ai_charge, 0, flyer_fireleft, // left gun - ai_charge, 0, flyer_fireright, // right gun - ai_charge, 0, NULL, + ai_charge, -10, flyer_fireleft, // left gun + ai_charge, -10, flyer_fireright, // right gun + ai_charge, -10, flyer_fireleft, // left gun + ai_charge, -10, flyer_fireright, // right gun + ai_charge, -10, flyer_fireleft, // left gun + ai_charge, -10, flyer_fireright, // right gun + ai_charge, -10, flyer_fireleft, // left gun + ai_charge, -10, flyer_fireright, // right gun ai_charge, 0, NULL, ai_charge, 0, NULL, ai_charge, 0, NULL, + ai_charge, -15, flyer_reattack_blaster, ai_charge, 0, NULL, ai_charge, 0, NULL }; -mmove_t flyer_move_attack3 = { FRAME_attak201, FRAME_attak217, flyer_frames_attack3, flyer_run }; - -mframe_t flyer_frames_attack2 [] = -{ - drone_ai_run, 20, NULL, - drone_ai_run, 20, NULL, - drone_ai_run, 20, NULL, - drone_ai_run, 20, flyer_fireleft, // left gun - drone_ai_run, 20, flyer_fireright, // right gun - drone_ai_run, 20, flyer_fireleft, // left gun - drone_ai_run, 20, flyer_fireright, // right gun - drone_ai_run, 20, flyer_fireleft, // left gun - drone_ai_run, 20, flyer_fireright, // right gun - drone_ai_run, 20, flyer_fireleft, // left gun - drone_ai_run, 20, flyer_fireright, // right gun - drone_ai_run, 20, NULL, - drone_ai_run, 20, NULL, - drone_ai_run, 20, NULL, - drone_ai_run, 20, NULL, - drone_ai_run, 20, NULL, - drone_ai_run, 20, NULL -}; -mmove_t flyer_move_attack2 = {FRAME_attak201, FRAME_attak217, flyer_frames_attack2, flyer_run}; +mmove_t flyer_move_attack2 = {FRAME_attak201, FRAME_attak217, flyer_frames_attack2, flyer_attack_finished}; void flyer_slash_left (edict_t *self) @@ -483,10 +505,12 @@ mmove_t flyer_move_loop_melee = {FRAME_attak107, FRAME_attak118, flyer_frames_lo void flyer_check_melee(edict_t *self) { if (entdist (self, self->enemy) == RANGE_MELEE) + { if (random() <= 0.8) self->monsterinfo.currentmove = &flyer_move_loop_melee; else self->monsterinfo.currentmove = &flyer_move_end_melee; + } else self->monsterinfo.currentmove = &flyer_move_end_melee; } @@ -507,9 +531,22 @@ void flyer_attack (edict_t *self) self->monsterinfo.currentmove = &flyer_move_attack1; else */ if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + self->monsterinfo.attack_state = AS_STRAIGHT; self->monsterinfo.currentmove = &flyer_move_attack3; - else + } + else if (random() < 0.5f) + { + self->monsterinfo.attack_state = AS_STRAIGHT; self->monsterinfo.currentmove = &flyer_move_attack2; + } + else + { + if (random() <= 0.5f) + self->monsterinfo.lefty = 1 - self->monsterinfo.lefty; + self->monsterinfo.attack_state = AS_SLIDING; + self->monsterinfo.currentmove = &flyer_move_attack3; + } } void flyer_setstart (edict_t *self) @@ -596,6 +633,7 @@ void init_drone_flyer (edict_t *self) gi.soundindex ("flyer/flyatck3.wav"); self->s.modelindex = gi.modelindex ("models/monsters/flyer/tris.md2"); + gi.imageindex ("models/monsters/flyer/pain.pcx"); VectorSet (self->mins, -16, -16, -24); VectorSet (self->maxs, 16, 16, 8); self->movetype = MOVETYPE_STEP; diff --git a/src/entities/drone/drone_gekk.c b/src/entities/drone/drone_gekk.c index ce4b1942..e6570251 100644 --- a/src/entities/drone/drone_gekk.c +++ b/src/entities/drone/drone_gekk.c @@ -40,6 +40,7 @@ extern mmove_t gekk_move_standunderwater; extern mmove_t gekk_move_swim_loop; extern mmove_t gekk_move_swim_start; extern mmove_t gekk_move_leapatk; +extern mmove_t gekk_move_leapatk2; extern void fire_acid(edict_t *self, vec3_t start, vec3_t aimdir, int projectile_damage, float radius, int speed, int acid_damage, float acid_duration, int gas_damage, float gas_radius, float gas_duration); @@ -81,6 +82,8 @@ static qboolean gekk_should_swim(edict_t *self) static void gekk_set_water_bounds(edict_t *self) { + if (!self->goalentity) + self->goalentity = world; self->flags |= FL_SWIM; self->yaw_speed = 10; self->viewheight = 10; @@ -376,19 +379,11 @@ static void gekk_land_to_water(edict_t *self) static void gekk_water_to_land(edict_t *self) { - vec3_t dir; - gekk_set_land_bounds(self); if (G_EntExists(self->enemy)) { - VectorSubtract(self->enemy->s.origin, self->s.origin, dir); - dir[2] = 0; - VectorNormalize(dir); - VectorScale(dir, 450, self->velocity); - self->velocity[2] = 320; - self->groundentity = NULL; self->monsterinfo.attack_finished = level.time + 1.5; - self->monsterinfo.currentmove = &gekk_move_leapatk; + self->monsterinfo.currentmove = &gekk_move_leapatk2; return; } @@ -468,7 +463,10 @@ static void gekk_melee(edict_t *self) } gi.sound(self, CHAN_WEAPON, sound_swing, 1, ATTN_NORM, 0); - if (random() < 0.5) + float attack_roll = random(); + if (attack_roll < 0.34f) + self->monsterinfo.currentmove = &gekk_move_attack; + else if (attack_roll < 0.67f) self->monsterinfo.currentmove = &gekk_move_attack1; else self->monsterinfo.currentmove = &gekk_move_attack2; @@ -580,6 +578,34 @@ static void gekk_jump_takeoff(edict_t *self) gekk_set_leap_cooldown(self, 1.0, 0.5); } +static void gekk_jump_takeoff2(edict_t *self) +{ + vec3_t forward; + + if (!G_EntExists(self->enemy)) + return; + + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); + self->lastsound = level.framenum; + self->s.origin[2] = self->enemy->s.origin[2]; + self->groundentity = NULL; + AngleVectors(self->s.angles, forward, NULL, NULL); + if (gekk_use_high_leap(self)) + { + VectorScale(forward, 300, self->velocity); + self->velocity[2] = 250; + } + else + { + VectorScale(forward, 150, self->velocity); + self->velocity[2] = 300; + } + self->monsterinfo.pausetime = level.time + 2.0; + self->monsterinfo.aiflags |= AI_HOLD_FRAME; + self->touch = gekk_jump_touch; + gekk_set_leap_cooldown(self, 1.0, 0.5); +} + static void gekk_stop_skid(edict_t *self) { if (self->groundentity) @@ -610,6 +636,12 @@ static void gekk_check_landing(edict_t *self) self->monsterinfo.aiflags |= AI_HOLD_FRAME; } +static void gekk_check_landing_ai(edict_t *self, float dist) +{ + ai_charge(self, dist); + gekk_check_landing(self); +} + static void gekk_spit(edict_t *self) { int acid_level, damage, speed; @@ -640,6 +672,17 @@ static void gekk_spit(edict_t *self) fire_acid(self, start, dir, damage, radius, speed, (int)(0.1 * damage), ACID_DURATION, 0, 0, 0); } +static qboolean gekk_should_use_acid(float distance) +{ + if (distance >= 768.0f) + return true; + if (distance >= 384.0f) + return random() < 0.65f; + if (distance >= 160.0f) + return random() < 0.30f; + return false; +} + mframe_t gekk_frames_leapatk[] = { ai_charge, 0, NULL, @@ -651,7 +694,7 @@ mframe_t gekk_frames_leapatk[] = ai_charge, 28, NULL, ai_charge, 24, NULL, ai_charge, 32, NULL, - ai_charge, 36, gekk_check_landing, + gekk_check_landing_ai, 36, NULL, ai_charge, 12, gekk_stop_skid, ai_charge, 20, gekk_stop_skid, ai_charge, -1, gekk_stop_skid, @@ -664,6 +707,30 @@ mframe_t gekk_frames_leapatk[] = }; mmove_t gekk_move_leapatk = { FRAME_leapatk_01, FRAME_leapatk_19, gekk_frames_leapatk, gekk_run }; +mframe_t gekk_frames_leapatk2[] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 6, gekk_jump_takeoff2, + ai_charge, 6, NULL, + ai_charge, 0, NULL, + ai_charge, 28, NULL, + ai_charge, 24, NULL, + ai_charge, 32, NULL, + gekk_check_landing_ai, 36, NULL, + ai_charge, 12, gekk_stop_skid, + ai_charge, 20, gekk_stop_skid, + ai_charge, -1, gekk_stop_skid, + ai_charge, 3, gekk_stop_skid, + ai_charge, 1, gekk_stop_skid, + ai_charge, 2, gekk_stop_skid, + ai_charge, 1, gekk_stop_skid, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +mmove_t gekk_move_leapatk2 = { FRAME_leapatk_01, FRAME_leapatk_19, gekk_frames_leapatk2, gekk_run }; + mframe_t gekk_frames_spit[] = { ai_charge, 0, NULL, @@ -694,7 +761,13 @@ static void gekk_attack(edict_t *self) if (!G_EntExists(self->enemy)) return; - if (gekk_can_leap(self) && random() >= 0.35f) + float distance = entdist(self, self->enemy); + if (gekk_should_use_acid(distance)) + { + self->monsterinfo.currentmove = &gekk_move_spit; + self->monsterinfo.melee_finished = level.time + 0.5; + } + else if (gekk_can_leap(self) && random() >= 0.35f) { self->monsterinfo.currentmove = &gekk_move_leapatk; self->monsterinfo.melee_finished = level.time + 3.0; diff --git a/src/entities/drone/drone_guardian.c b/src/entities/drone/drone_guardian.c index 6c33a8bc..9f7f50e0 100644 --- a/src/entities/drone/drone_guardian.c +++ b/src/entities/drone/drone_guardian.c @@ -60,6 +60,63 @@ static void guardian_project_laser_frame_origin(edict_t *self, vec3_t forward, v G_ProjectSource(self->s.origin, offset, forward, right, start); } +static void guardian_project_laser_origin(edict_t *self, qboolean secondary, vec3_t forward, vec3_t right, vec3_t start) +{ + vec3_t offset; + + if (secondary) + VectorSet(offset, 112, -62, 60); + else + VectorSet(offset, 125, -70, 60); + + if (guardian_scale(self) != 1.0f) + VectorScale(offset, guardian_scale(self), offset); + + G_ProjectSource(self->s.origin, offset, forward, right, start); +} + +static qboolean guardian_has_origin_shot(edict_t *self, vec3_t start) +{ + return M_MonsterHasClearShotFrom(self, start); +} + +static qboolean guardian_has_laser_shot(edict_t *self, qboolean secondary) +{ + vec3_t forward, right, start; + + AngleVectors(self->s.angles, forward, right, NULL); + guardian_project_laser_origin(self, secondary, forward, right, start); + return guardian_has_origin_shot(self, start); +} + +static qboolean guardian_has_blaster_shot(edict_t *self) +{ + if (self->mtype == M_MINIGUARDIAN) + return M_MonsterHasClearShotFromFlash(self, MZ2_SOLDIER_RIPPER_8); + + return M_MonsterHasClearShotFromFlash(self, MZ2_GUARDIAN_BLASTER); +} + +static qboolean guardian_has_grenade_or_laser_shot(edict_t *self) +{ + if (self->mtype == M_MINIGUARDIAN) + return guardian_has_laser_shot(self, false) || guardian_has_laser_shot(self, true); + + return guardian_has_laser_shot(self, false) || guardian_has_laser_shot(self, true); +} + +static qboolean guardian_has_rocket_shot(edict_t *self, float offset) +{ + vec3_t forward, right, up, start; + + AngleVectors(self->s.angles, forward, right, up); + VectorCopy(self->s.origin, start); + VectorMA(start, -8 * guardian_scale(self), forward, start); + VectorMA(start, offset * guardian_scale(self), right, start); + VectorMA(start, 50 * guardian_scale(self), up, start); + return guardian_has_origin_shot(self, start); +} + static void guardian_footstep(edict_t *self) { gi.sound(self, CHAN_BODY, sound_step, 1, ATTN_NORM, 0); @@ -263,6 +320,9 @@ void guardian_fire_blaster(edict_t *self) if (M_IONRIPPER_SPEED_MAX && speed > M_IONRIPPER_SPEED_MAX) speed = M_IONRIPPER_SPEED_MAX; MonsterAim(self, M_PROJECTILE_ACC, speed, false, MZ2_SOLDIER_RIPPER_8, forward, start); + if (!M_MonsterHasClearShotFrom(self, start)) + return; + monster_fire_ionripper(self, start, forward, damage, speed, EF_IONRIPPER, MZ2_SOLDIER_RIPPER_8); } else @@ -276,6 +336,9 @@ void guardian_fire_blaster(edict_t *self) AngleVectors(self->s.angles, forward, right, NULL); guardian_project_flash(self, MZ2_GUARDIAN_BLASTER, forward, right, start); MonsterAim(self, M_PROJECTILE_ACC, speed, false, -1, forward, start); + if (!M_MonsterHasClearShotFrom(self, start)) + return; + monster_fire_blaster(self, start, forward, damage, speed, effect, BLASTER_PROJ_BOLT, 2.0, true, MZ2_GUARDIAN_BLASTER); } @@ -362,6 +425,9 @@ void guardian_grenade(edict_t *self) flash_number = -1; } MonsterAim(self, M_PROJECTILE_ACC, speed, true, flash_number, forward, start); + if (!M_MonsterHasClearShotFrom(self, start)) + return; + monster_fire_grenade(self, start, forward, damage, speed, flash_number); } @@ -378,6 +444,9 @@ void guardian_laser_fire(edict_t *self) damage = M_DABEAM_DMG_MAX; if (self->mtype == M_GUARDIAN) damage += 10 + 2 * drone_damagelevel(self); + if (!guardian_has_laser_shot(self, self->s.frame & 1)) + return; + monster_fire_dabeam(self, damage, self->s.frame & 1, NULL); } @@ -433,6 +502,8 @@ void guardian_fire_rocket(edict_t *self, float offset) VectorMA(start, -8 * guardian_scale(self), forward, start); VectorMA(start, offset * guardian_scale(self), right, start); VectorMA(start, 50 * guardian_scale(self), up, start); + if (!M_MonsterHasClearShotFrom(self, start)) + return; speed = M_ROCKETLAUNCHER_SPEED_BASE + M_ROCKETLAUNCHER_SPEED_ADDON * drone_damagelevel(self); if (M_ROCKETLAUNCHER_SPEED_MAX && speed > M_ROCKETLAUNCHER_SPEED_MAX) @@ -527,25 +598,35 @@ mmove_t guardian_move_kick = {FRAME_kick_in1, FRAME_kick_in13, guardian_frames_k void guardian_attack(edict_t *self) { float dist; + qboolean can_blaster; + qboolean can_atk2; + qboolean can_rocket; if (!G_EntExists(self->enemy)) return; dist = entdist(self, self->enemy); + can_blaster = guardian_has_blaster_shot(self); + can_atk2 = guardian_has_grenade_or_laser_shot(self); + can_rocket = guardian_has_rocket_shot(self, -14.0f) || guardian_has_rocket_shot(self, 14.0f); if (self->mtype == M_GUARDIAN && self->monsterinfo.melee_finished < level.time && dist < 160) self->monsterinfo.currentmove = &guardian_move_kick; else if (self->mtype != M_GUARDIAN && self->monsterinfo.melee_finished < level.time && dist < 120) self->monsterinfo.currentmove = &guardian_move_kick; - else if (self->mtype == M_GUARDIAN && dist > 300 && self->count <= 0 && random() < 0.25) + else if (can_rocket && self->mtype == M_GUARDIAN && dist > 300 && self->count <= 0 && random() < 0.25) { self->monsterinfo.currentmove = &guardian_move_rocket; self->count = 6; } - else if (dist > 512 || (self->mtype == M_GUARDIAN && dist > 300 && random() < 0.5)) + else if (can_atk2 && (dist > 512 || (self->mtype == M_GUARDIAN && dist > 300 && random() < 0.5))) self->monsterinfo.currentmove = &guardian_move_atk2_in; - else + else if (can_blaster) self->monsterinfo.currentmove = &guardian_move_atk1_in; + else if (can_atk2) + self->monsterinfo.currentmove = &guardian_move_atk2_in; + else + return; if (self->mtype == M_GUARDIAN && self->count > 0) self->count--; diff --git a/src/entities/drone/drone_guncmdr.c b/src/entities/drone/drone_guncmdr.c index ff69382b..bbe64efb 100644 --- a/src/entities/drone/drone_guncmdr.c +++ b/src/entities/drone/drone_guncmdr.c @@ -303,6 +303,9 @@ static void GunnerCmdrFire(edict_t *self) damage = M_SHOTGUN_DMG_MAX; MonsterAim(self, M_PROJECTILE_ACC, 800, false, flash_number, forward, start); + if (!M_MonsterHasClearShotFrom(self, start)) + return; + damage = vrx_increase_monster_damage_by_talent(self->activator, damage); fire_flechette(self, start, forward, damage, 800, damage); @@ -413,6 +416,9 @@ static void GunnerCmdrGrenade(edict_t *self) flash_number = guncmdr_grenade_flash(self); MonsterAim(self, M_PROJECTILE_ACC, speed, false, flash_number, forward, start); + if (!M_MonsterHasClearShotFrom(self, start)) + return; + monster_fire_grenade(self, start, forward, damage, speed, flash_number); } @@ -677,6 +683,12 @@ static void guncmdr_jump_wait_land(edict_t *self) self->monsterinfo.nextframe = self->s.frame + 1; } +static void guncmdr_jump_wait_land_ai(edict_t *self, float dist) +{ + ai_move(self, dist); + guncmdr_jump_wait_land(self); +} + mframe_t guncmdr_frames_jump[] = { ai_move, 0, NULL, @@ -685,7 +697,7 @@ mframe_t guncmdr_frames_jump[] = ai_move, 0, guncmdr_jump_now, ai_move, 0, NULL, ai_move, 0, NULL, - ai_move, 0, guncmdr_jump_wait_land, + guncmdr_jump_wait_land_ai, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL @@ -700,7 +712,7 @@ mframe_t guncmdr_frames_jump2[] = ai_move, 0, guncmdr_jump2_now, ai_move, 0, NULL, ai_move, 0, NULL, - ai_move, 0, guncmdr_jump_wait_land, + guncmdr_jump_wait_land_ai, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL @@ -845,6 +857,21 @@ static qboolean guncmdr_can_proactive_dodge(edict_t *self, float dist) && level.time >= self->monsterinfo.dodge_time; } +static qboolean guncmdr_can_chain(edict_t *self) +{ + return M_MonsterHasClearShotFromFlash(self, MZ2_GUNNER_MACHINEGUN_1); +} + +static qboolean guncmdr_can_dodge_chain(edict_t *self) +{ + return M_MonsterHasClearShotFromFlash(self, MZ2_GUNNER_MACHINEGUN_2); +} + +static qboolean guncmdr_can_grenade(edict_t *self) +{ + return M_MonsterHasClearShotFromFlash(self, MZ2_GUNNER_GRENADE_1); +} + static void guncmdr_start_fire_chain_dodge(edict_t *self) { guncmdr_duck_up(self); @@ -863,6 +890,8 @@ static void guncmdr_attack(edict_t *self) float dist; float zdiff; float r; + qboolean can_chain; + qboolean can_grenade; if (!G_ValidTarget(self, self->enemy, true, true)) return; @@ -872,26 +901,35 @@ static void guncmdr_attack(edict_t *self) dist = entdist(self, self->enemy); zdiff = fabs(self->s.origin[2] - self->enemy->s.origin[2]); r = random(); + can_chain = guncmdr_can_chain(self); + can_grenade = guncmdr_can_grenade(self); + if (dist <= MELEE_DISTANCE && self->monsterinfo.melee_finished < level.time) self->monsterinfo.currentmove = &guncmdr_move_attack_kick; - else if (guncmdr_can_proactive_dodge(self, dist) && r < 0.55) + else if (can_chain && guncmdr_can_proactive_dodge(self, dist) && r < 0.55) guncmdr_start_fire_chain_dodge(self); - else if ((dist >= GUNCMDR_MORTAR_RANGE || zdiff > 96) && r < 0.75) + else if (can_grenade && (dist >= GUNCMDR_MORTAR_RANGE || zdiff > 96) && r < 0.75) { self->monsterinfo.currentmove = &guncmdr_move_attack_mortar; guncmdr_duck_down(self); } - else if (self->groundentity && dist > 96 && dist < GUNCMDR_CHAINGUN_RUN_RANGE && r < 0.05) + else if (can_grenade && self->groundentity && dist > 96 && dist < GUNCMDR_CHAINGUN_RUN_RANGE && r < 0.05) { self->monsterinfo.pausetime = level.time + 0.75f; self->monsterinfo.currentmove = &guncmdr_move_duck_attack; guncmdr_duck_down(self); self->monsterinfo.dodge_time = level.time + 1.8f; } - else if (!(self->monsterinfo.aiflags & AI_STAND_GROUND) && dist > GUNCMDR_GRENADE_RANGE && r < 0.90) + else if (can_grenade && !(self->monsterinfo.aiflags & AI_STAND_GROUND) && dist > GUNCMDR_GRENADE_RANGE && r < 0.90) self->monsterinfo.currentmove = &guncmdr_move_attack_grenade_back; - else + else if (can_chain) self->monsterinfo.currentmove = &guncmdr_move_attack_chain; + else if (can_grenade) + { + self->monsterinfo.currentmove = &guncmdr_move_attack_grenade_back; + } + else + return; M_DelayNextAttack(self, 0, true); } @@ -902,15 +940,19 @@ static void guncmdr_fire_chain(edict_t *self) { float dist = entdist(self, self->enemy); - if (guncmdr_can_proactive_dodge(self, dist)) + if (guncmdr_can_dodge_chain(self) && guncmdr_can_proactive_dodge(self, dist)) guncmdr_start_fire_chain_dodge(self); - else if (dist > GUNCMDR_CHAINGUN_RUN_RANGE) + else if (guncmdr_can_chain(self) && dist > GUNCMDR_CHAINGUN_RUN_RANGE) self->monsterinfo.currentmove = &guncmdr_move_fire_chain_run; - else + else if (guncmdr_can_chain(self)) self->monsterinfo.currentmove = &guncmdr_move_fire_chain; + else + self->monsterinfo.currentmove = &guncmdr_move_endfire_chain; } - else + else if (guncmdr_can_chain(self)) self->monsterinfo.currentmove = &guncmdr_move_fire_chain; + else + self->monsterinfo.currentmove = &guncmdr_move_endfire_chain; } static void guncmdr_refire_chain(edict_t *self) @@ -921,12 +963,14 @@ static void guncmdr_refire_chain(edict_t *self) { float dist = entdist(self, self->enemy); - if (guncmdr_can_proactive_dodge(self, dist) && random() < 0.65) + if (guncmdr_can_dodge_chain(self) && guncmdr_can_proactive_dodge(self, dist) && random() < 0.65) guncmdr_start_fire_chain_dodge(self); - else if (!(self->monsterinfo.aiflags & AI_STAND_GROUND) && dist > GUNCMDR_CHAINGUN_RUN_RANGE) + else if (guncmdr_can_chain(self) && !(self->monsterinfo.aiflags & AI_STAND_GROUND) && dist > GUNCMDR_CHAINGUN_RUN_RANGE) self->monsterinfo.currentmove = &guncmdr_move_fire_chain_run; - else + else if (guncmdr_can_chain(self)) self->monsterinfo.currentmove = &guncmdr_move_fire_chain; + else + self->monsterinfo.currentmove = &guncmdr_move_endfire_chain; } else self->monsterinfo.currentmove = &guncmdr_move_endfire_chain; diff --git a/src/entities/drone/drone_gunner.c b/src/entities/drone/drone_gunner.c index c221953c..9914cf7a 100644 --- a/src/entities/drone/drone_gunner.c +++ b/src/entities/drone/drone_gunner.c @@ -248,6 +248,9 @@ void myGunnerGrenade (edict_t *self) flash_number = MZ2_GUNNER_GRENADE2_1 + (MZ2_GUNNER_GRENADE_4 - flash_number); MonsterAim(self, M_PROJECTILE_ACC, speed, false, flash_number, forward, start); + if (!M_MonsterHasClearShotFrom(self, start)) + return; + monster_fire_grenade(self, start, forward, damage, speed, flash_number); } @@ -312,21 +315,42 @@ mframe_t mygunner_frames_attack_grenade2[] = }; mmove_t mygunner_move_attack_grenade2 = {FRAME_attak305, FRAME_attak324, mygunner_frames_attack_grenade2, mygunnerrun}; -static void mygunner_start_grenade(edict_t *self) +static qboolean mygunner_can_grenade(edict_t *self, qboolean second_set) +{ + return M_MonsterHasClearShotFromFlash(self, second_set ? MZ2_GUNNER_GRENADE2_4 : MZ2_GUNNER_GRENADE_1); +} + +static qboolean mygunner_can_run_grenade(edict_t *self) +{ + return M_MonsterHasClearShotFromFlash(self, MZ2_GUNNER_GRENADE_4); +} + +static qboolean mygunner_can_chain(edict_t *self) { - if (random() <= 0.5) + return M_MonsterHasClearShotFromFlash(self, MZ2_GUNNER_MACHINEGUN_1); +} + +static qboolean mygunner_start_grenade(edict_t *self) +{ + qboolean can_first = mygunner_can_grenade(self, false); + qboolean can_second = mygunner_can_grenade(self, true); + + if (!can_first && !can_second) + return false; + + if (can_second && (!can_first || random() <= 0.5)) self->monsterinfo.currentmove = &mygunner_move_attack_grenade2; else self->monsterinfo.currentmove = &mygunner_move_attack_grenade; + + return true; } void gunner_refire_grenade (edict_t *self) { // continue firing unless enemy is no longer valid or out of range - if (G_ValidTarget(self, self->enemy, true, true) && (random() <= 0.8) - && (entdist(self, self->enemy) <= 384)) - mygunner_start_grenade(self); - else + if (!(G_ValidTarget(self, self->enemy, true, true) && (random() <= 0.8) + && (entdist(self, self->enemy) <= 384) && mygunner_start_grenade(self))) self->monsterinfo.currentmove = &mygunner_move_attack_grenade_end; // don't call the attack function again for awhile! @@ -336,8 +360,8 @@ void gunner_refire_grenade (edict_t *self) void gunner_attack_grenade (edict_t *self) { // continue attack sequence unless enemy is no longer valid - if (G_ValidTarget(self, self->enemy, true, true)) - mygunner_start_grenade(self); + if (G_ValidTarget(self, self->enemy, true, true) && mygunner_start_grenade(self)) + return; else mygunnerrun(self); } @@ -372,7 +396,7 @@ mmove_t mygunner_move_runandshoot = {FRAME_runs01, FRAME_runs06, mygunner_frames void mygunner_continue (edict_t *self) { if (G_ValidTarget(self, self->enemy, true, true) && (random() <= 0.9) - && (entdist(self, self->enemy) <= 512)) + && (entdist(self, self->enemy) <= 512) && mygunner_can_run_grenade(self)) self->monsterinfo.currentmove = &mygunner_move_runandshoot; else self->monsterinfo.currentmove = &mygunnermove_run; @@ -402,6 +426,8 @@ void myGunnerFire (edict_t *self) damage = M_MACHINEGUN_DMG_MAX; MonsterAim(self, M_HITSCAN_CONT_ACC, 0, false, flash_number, forward, start); + if (!M_MonsterHasClearShotFrom(self, start)) + return; monster_fire_bullet (self, start, forward, damage, damage, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flash_number); @@ -469,7 +495,7 @@ void mygunner_refire_chain(edict_t *self) { // keep firing if (G_ValidTarget(self, self->enemy, true, true) && (random() <= 0.8) - && entdist(self, self->enemy) > 128) + && entdist(self, self->enemy) > 128 && mygunner_can_chain(self)) self->monsterinfo.currentmove = &mygunner_move_fire_chain; else self->monsterinfo.currentmove = &mygunner_move_endfire_chain; @@ -482,9 +508,10 @@ void mygunner_refire_chain(edict_t *self) void gunner_stand_attack (edict_t *self) { - if (entdist(self, self->enemy) <= 384 && random() <= 0.8) - mygunner_start_grenade(self); - else + if (entdist(self, self->enemy) <= 384 && random() <= 0.8 && mygunner_start_grenade(self)) + return; + + if (mygunner_can_chain(self)) self->monsterinfo.currentmove = &mygunner_move_attack_chain; } @@ -496,20 +523,26 @@ void gunner_attack (edict_t *self) // short range (20% chance grenade, 80% chance run and shoot) if (dist <= 128) { - if (r <= 0.2) - mygunner_start_grenade(self); - else + if (r <= 0.2 && mygunner_start_grenade(self)) + return; + + if (mygunner_can_run_grenade(self)) self->monsterinfo.currentmove = &mygunner_move_runandshoot; + else if (mygunner_can_chain(self)) + self->monsterinfo.currentmove = &mygunner_move_attack_chain; } // medium range (100% run and shoot) else if (dist <= 512) { - self->monsterinfo.currentmove = &mygunner_move_runandshoot; + if (mygunner_can_run_grenade(self)) + self->monsterinfo.currentmove = &mygunner_move_runandshoot; + else if (mygunner_can_chain(self)) + self->monsterinfo.currentmove = &mygunner_move_attack_chain; } // long range (50% chance chaingun) else { - if (r <= 0.5) + if (r <= 0.5 && mygunner_can_chain(self)) self->monsterinfo.currentmove = &mygunner_move_attack_chain; else self->monsterinfo.attack_finished = level.time + 2.0;// don't attack, try to get closer @@ -615,6 +648,12 @@ void mygunner_jump_wait_land (edict_t *self) } } +static void mygunner_jump_wait_land_ai(edict_t *self, float dist) +{ + ai_move(self, dist); + mygunner_jump_wait_land(self); +} + mframe_t mygunner_frames_jump [] = { ai_move, 0, NULL, @@ -623,7 +662,7 @@ mframe_t mygunner_frames_jump [] = ai_move, 0, mygunner_jump_now, ai_move, 0, NULL, ai_move, 0, NULL, - ai_move, 0, mygunner_jump_wait_land, + mygunner_jump_wait_land_ai, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL @@ -638,7 +677,7 @@ mframe_t mygunner_frames_jump2 [] = ai_move, 0, mygunner_jump2_now, ai_move, 0, NULL, ai_move, 0, NULL, - ai_move, 0, mygunner_jump_wait_land, + mygunner_jump_wait_land_ai, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL diff --git a/src/entities/drone/drone_hover.c b/src/entities/drone/drone_hover.c index 49487f82..c9237768 100644 --- a/src/entities/drone/drone_hover.c +++ b/src/entities/drone/drone_hover.c @@ -296,41 +296,41 @@ mmove_t hover_move_walk = {FRAME_forwrd01, FRAME_forwrd35, hover_frames_walk, NU mframe_t hover_frames_run [] = { - drone_ai_run, 15, NULL, - drone_ai_run, 15, NULL, - drone_ai_run, 15, NULL, - drone_ai_run, 15, NULL, - drone_ai_run, 15, NULL, - drone_ai_run, 15, NULL, - drone_ai_run, 15, NULL, - drone_ai_run, 15, NULL, - drone_ai_run, 15, NULL, - drone_ai_run, 15, NULL, - drone_ai_run, 15, NULL, - drone_ai_run, 15, NULL, - drone_ai_run, 15, NULL, - drone_ai_run, 15, NULL, - drone_ai_run, 15, NULL, - drone_ai_run, 15, NULL, - drone_ai_run, 15, NULL, - drone_ai_run, 15, NULL, - drone_ai_run, 15, NULL, - drone_ai_run, 15, NULL, - drone_ai_run, 15, NULL, - drone_ai_run, 15, NULL, - drone_ai_run, 15, NULL, - drone_ai_run, 15, NULL, - drone_ai_run, 15, NULL, - drone_ai_run, 15, NULL, - drone_ai_run, 15, NULL, - drone_ai_run, 15, NULL, - drone_ai_run, 15, NULL, - drone_ai_run, 15, NULL, - drone_ai_run, 15, NULL, - drone_ai_run, 15, NULL, - drone_ai_run, 15, NULL, - drone_ai_run, 15, NULL, - drone_ai_run, 15, NULL + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL, + drone_ai_run, 10, NULL }; mmove_t hover_move_run = {FRAME_forwrd01, FRAME_forwrd35, hover_frames_run, NULL}; @@ -414,32 +414,32 @@ mmove_t hover_move_start_attack = {FRAME_attak101, FRAME_attak103, hover_frames_ mframe_t hover_frames_attack2[] = { - ai_charge, 15, hover_fire_blaster, - ai_charge, 15, hover_fire_blaster, - ai_charge, 15, hover_reattack + ai_charge, 10, hover_fire_blaster, + ai_charge, 10, hover_fire_blaster, + ai_charge, 10, hover_reattack }; mmove_t hover_move_attack2 = { FRAME_attak104, FRAME_attak106, hover_frames_attack2, hover_run }; mframe_t hover_frames_attack1 [] = { - drone_ai_run, 15, hover_fire_blaster, - drone_ai_run, 15, hover_fire_blaster, - drone_ai_run, 15, hover_reattack + ai_charge, -10, hover_fire_blaster, + ai_charge, -10, hover_fire_blaster, + ai_charge, 0, hover_reattack }; mmove_t hover_move_attack1 = {FRAME_attak104, FRAME_attak106, hover_frames_attack1, hover_run }; mframe_t hover_frames_end_attack [] = { - drone_ai_run, 15, NULL, - drone_ai_run, 15, NULL + ai_charge, 1, NULL, + ai_charge, 1, NULL }; mmove_t hover_move_end_attack = {FRAME_attak107, FRAME_attak108, hover_frames_end_attack, hover_run}; void hover_reattack (edict_t *self) { // if our enemy is still valid, then continue firing - if (G_ValidTarget(self, self->enemy, true, true) && (random() <= 0.9)) + if (G_ValidTarget(self, self->enemy, true, true) && (random() <= 0.6)) { self->s.frame = FRAME_attak104; //hover_fire_blaster(self); @@ -472,6 +472,11 @@ void hover_fire_blaster (edict_t *self) damage = M_HYPERBLASTER_DMG_MAX; MonsterAim(self, M_PROJECTILE_ACC, speed, true, MZ2_BOSS2_ROCKET_3, forward, start); + if (!M_MonsterHasClearShotFrom(self, start)) + { + M_MonsterBlockedShot(self, 0.4f); + return; + } monster_fire_rocket (self, start, forward, damage, speed, MZ2_BOSS2_ROCKET_3); } @@ -501,9 +506,22 @@ void hover_start_attack (edict_t *self) void hover_attack(edict_t *self) { if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + self->monsterinfo.attack_state = AS_STRAIGHT; self->monsterinfo.currentmove = &hover_move_attack2; - else + } + else if (random() < 0.5f) + { + self->monsterinfo.attack_state = AS_STRAIGHT; self->monsterinfo.currentmove = &hover_move_attack1; + } + else + { + if (random() <= 0.5f) + self->monsterinfo.lefty = 1 - self->monsterinfo.lefty; + self->monsterinfo.attack_state = AS_SLIDING; + self->monsterinfo.currentmove = &hover_move_attack2; + } } @@ -570,23 +588,10 @@ void hover_dead (edict_t *self) void hover_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) { - int n; + qboolean overkill; M_Notify(self); - -// check for gib - if (self->health <= self->gib_health) - { - gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); - for (n= 0; n < 2; n++) - ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); - for (n= 0; n < 2; n++) - ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); - ThrowHead (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); - self->deadflag = DEAD_DEAD; - M_Remove(self, false, false); - return; - } + overkill = self->health <= self->gib_health; if (self->deadflag == DEAD_DEAD) return; @@ -594,7 +599,9 @@ void hover_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage DroneList_Remove(self); // regular death - if (random() < 0.5) + if (overkill) + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + else if (random() < 0.5) gi.sound (self, CHAN_VOICE, sound_death1, 1, ATTN_NORM, 0); else gi.sound (self, CHAN_VOICE, sound_death2, 1, ATTN_NORM, 0); diff --git a/src/entities/drone/drone_medic.c b/src/entities/drone/drone_medic.c index 33a3adad..b29e70d8 100644 --- a/src/entities/drone/drone_medic.c +++ b/src/entities/drone/drone_medic.c @@ -31,6 +31,7 @@ static int commander_sound_hook_retract; static int commander_sound_spawn; void drone_ai_run_slide(edict_t *self, float dist); +qboolean drone_findtarget(edict_t *self, qboolean force); void mymedic_run(edict_t *self); extern mmove_t mymedic_move_attackHyperBlaster; extern mmove_t mymedic_move_attackBlaster; @@ -51,6 +52,26 @@ static qboolean medic_is_commander(edict_t *self) return self && self->mtype == M_MEDIC_COMMANDER; } +static int medic_blaster_flash(edict_t *self) +{ + return medic_is_commander(self) ? MZ2_MEDIC_BLASTER_2 : MZ2_MEDIC_BLASTER_1; +} + +static int medic_hyperblaster_flash(edict_t *self) +{ + return medic_is_commander(self) ? MZ2_MEDIC_HYPERBLASTER2_1 : MZ2_MEDIC_HYPERBLASTER1_1; +} + +static qboolean medic_can_blaster(edict_t *self) +{ + return M_MonsterHasClearShotFromFlash(self, medic_blaster_flash(self)); +} + +static qboolean medic_can_hyperblaster(edict_t *self) +{ + return M_MonsterHasClearShotFromFlash(self, medic_hyperblaster_flash(self)); +} + static int medic_idle_sound(edict_t *self) { return (medic_is_commander(self) && commander_sound_idle1) ? commander_sound_idle1 : sound_idle1; @@ -286,7 +307,7 @@ void mymedic_fire_blaster (edict_t *self) { effect = EF_BLASTER; bounce = true; - flash_number = medic_is_commander(self) ? MZ2_MEDIC_BLASTER_2 : MZ2_MEDIC_BLASTER_1; + flash_number = medic_blaster_flash(self); } else { @@ -298,7 +319,7 @@ void mymedic_fire_blaster (edict_t *self) frame_offset = 11; effect = (self->s.frame % 4) ? 0 : EF_HYPERBLASTER; - flash_number = (medic_is_commander(self) ? MZ2_MEDIC_HYPERBLASTER2_1 : MZ2_MEDIC_HYPERBLASTER1_1) + frame_offset; + flash_number = medic_hyperblaster_flash(self) + frame_offset; } damage = M_HYPERBLASTER_DMG_BASE + M_HYPERBLASTER_DMG_ADDON * drone_damagelevel(self); @@ -307,6 +328,9 @@ void mymedic_fire_blaster (edict_t *self) MonsterAim(self, M_PROJECTILE_ACC, speed, false, flash_number, forward, start); + if (!M_MonsterHasClearShotFrom(self, start)) + return; + if (medic_is_commander(self)) monster_fire_blaster2(self, start, forward, damage, speed, effect, flash_number); else @@ -315,15 +339,26 @@ void mymedic_fire_blaster (edict_t *self) void mymedic_fire_bolt (edict_t *self) { - int min, max, damage; + int min, max, damage, flash_number; vec3_t forward, start; min = 4 * drone_damagelevel(self); // dmg.min: medic_fire_bolt max = 50 + 25 * drone_damagelevel(self); // dmg.max: medic_fire_bolt damage = GetRandom(min, max); + flash_number = medic_blaster_flash(self); + + MonsterAim(self, M_PROJECTILE_ACC, 1500, false, flash_number, forward, start); + if (!M_MonsterHasClearShotFrom(self, start)) + return; + + if (medic_is_commander(self)) + { + monster_fire_blaster2(self, start, forward, damage, 1500, EF_BLASTER, -1); + gi.sound (self, CHAN_WEAPON, gi.soundindex("weapons/photon.wav"), 1, ATTN_NORM, 0); + return; + } - MonsterAim(self, M_PROJECTILE_ACC, 1500, false, MZ2_MEDIC_BLASTER_1, forward, start); // Keep the medic muzzle origin, but don't emit the muzzleflash packet here; // it can override the secondary blaster sound that should play per bolt. monster_fire_blaster(self, start, forward, damage, 1500, EF_BLASTER, BLASTER_PROJ_BLAST, 2.0, true, -1); @@ -581,6 +616,12 @@ void mymedic_jump_hold (edict_t *self) } } +static void mymedic_jump_hold_ai(edict_t *self, float dist) +{ + ai_move(self, dist); + mymedic_jump_hold(self); +} + mframe_t mymedic_frames_duck [] = { ai_move, -1, NULL, @@ -607,7 +648,7 @@ mframe_t mymedic_frames_leap [] = ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, - ai_move, 0, mymedic_jump_hold, //137 + mymedic_jump_hold_ai, 0, NULL, //137 /* ai_move, 0, NULL, ai_move, 0, NULL, @@ -737,7 +778,7 @@ void mymedic_refire(edict_t* self) { dist = entdist(self, self->enemy); - if (random() <= 0.8 && dist <= 512) + if (random() <= 0.8 && dist <= 512 && medic_can_hyperblaster(self)) { // continue attack self->s.frame = FRAME_attack19; @@ -759,7 +800,7 @@ void mymedic_refire(edict_t* self) void mymedic_continue(edict_t* self) { - if (M_ContinueAttack(self, &mymedic_move_attackHyperBlaster, NULL, 0, 512, 0.8)) + if (medic_can_hyperblaster(self) && M_ContinueAttack(self, &mymedic_move_attackHyperBlaster, NULL, 0, 512, 0.8)) return; // end attack @@ -1384,12 +1425,10 @@ static int medic_commander_random_soldier_type(void) static qboolean medic_commander_spawn_soldier(edict_t *self, float side) { - edict_t *owner = self->activator; + edict_t *owner = (self->activator && self->activator->inuse) ? self->activator : self; edict_t *gunner; vec3_t spot; - - if (!owner || !owner->inuse) - return false; + const qboolean force_start = invasion->value || pvm->value; if (owner->client && owner->num_monsters + M_GUNNER_CONTROL_COST > MAX_MONSTERS) return false; @@ -1425,12 +1464,15 @@ static qboolean medic_commander_spawn_soldier(edict_t *self, float side) owner->num_monsters += gunner->monsterinfo.control_cost; owner->num_monsters_real++; - if (G_ValidTarget(gunner, self->enemy, true, true)) + if (G_ValidTarget(gunner, self->enemy, !force_start, true)) { gunner->enemy = self->enemy; + VectorCopy(self->enemy->s.origin, gunner->monsterinfo.last_sighting); if (gunner->monsterinfo.run) gunner->monsterinfo.run(gunner); } + else if (force_start && drone_findtarget(gunner, true) && gunner->monsterinfo.run) + gunner->monsterinfo.run(gunner); else if (gunner->monsterinfo.stand) gunner->monsterinfo.stand(gunner); @@ -1550,6 +1592,8 @@ void mymedic_heal (edict_t *self) void mymedic_attack(edict_t *self) { float dist, r; + qboolean can_blaster; + qboolean can_hyperblaster; if (!self->enemy) return; @@ -1558,6 +1602,8 @@ void mymedic_attack(edict_t *self) dist = entdist(self, self->enemy); r = random(); + can_blaster = medic_can_blaster(self); + can_hyperblaster = medic_can_hyperblaster(self); if ((self->monsterinfo.aiflags & AI_MEDIC) && ((self->enemy->health < 1 || OnSameTeam(self, self->enemy)))) @@ -1569,17 +1615,21 @@ void mymedic_attack(edict_t *self) if (dist <= 256) { - if (r <= 0.2) + if (can_hyperblaster && (r <= 0.2 || !can_blaster)) self->monsterinfo.currentmove = &mymedic_move_attackHyperBlaster; - else + else if (can_blaster) self->monsterinfo.currentmove = &mymedic_move_attackBlaster; + else + return; } else { - if (r <= 0.3) + if (can_blaster && (r <= 0.3 || !can_hyperblaster)) self->monsterinfo.currentmove = &mymedic_move_attackBlaster; - else + else if (can_hyperblaster) self->monsterinfo.currentmove = &mymedic_move_attackHyperBlaster; + else + return; } M_DelayNextAttack(self, 0, true); @@ -1587,16 +1637,21 @@ void mymedic_attack(edict_t *self) void medic_commander_attack(edict_t *self) { - edict_t *owner = self->activator; + edict_t *owner = (self->activator && self->activator->inuse) ? self->activator : self; float dist; if (!self->enemy || !self->enemy->inuse) return; + if (!G_ValidTarget(self, self->enemy, false, true)) + { + mymedic_attack(self); + return; + } + dist = entdist(self, self->enemy); if (dist > 150 - && owner && owner->inuse && (!owner->client || owner->num_monsters + M_GUNNER_CONTROL_COST <= MAX_MONSTERS) && level.time >= self->monsterinfo.melee_finished && random() < 0.6) diff --git a/src/entities/drone/drone_misc.c b/src/entities/drone/drone_misc.c index 7e7c3a20..604f1553 100644 --- a/src/entities/drone/drone_misc.c +++ b/src/entities/drone/drone_misc.c @@ -413,10 +413,10 @@ void drone_ai_checkattack (edict_t *self) // if we see an easier target, go for it if (!visible(self, self->enemy)) { - self->oldenemy = self->enemy; - if (!drone_findtarget(self, false)) - return; - //gi.dprintf("%d going for an easier target\n", self->mtype); + self->oldenemy = self->enemy; + if (!drone_findtarget(self, false)) + return; + //gi.dprintf("%d going for an easier target\n", self->mtype); } //if (!infront(self, self->enemy)) @@ -434,10 +434,10 @@ void drone_ai_checkattack (edict_t *self) if (!tr.ent || tr.ent != self->enemy) { //gi.dprintf("blocked shot\n"); - if (G_ValidTarget(self, tr.ent, false, true)) - self->enemy = tr.ent; - else - return; + if (G_ValidTarget(self, tr.ent, false, true)) + self->enemy = tr.ent; + else + return; } //AngleVectors(self->s.angles, forward, NULL, NULL); //VectorMA(self->s.origin, self->maxs[1]+8, forward , start); @@ -540,8 +540,7 @@ void drone_death (edict_t *self, edict_t *attacker) //4.2 bosses can drop up to 4 runes - if (self->mtype == M_COMMANDER || self->mtype == M_SUPERTANK || self->mtype == M_BOSS5 || self->mtype == M_MAKRON || self->mtype == M_BOSS2 || self->mtype == M_CARRIER - || self->mtype == M_WIDOW || self->mtype == M_WIDOW2 || self->mtype == M_FIXBOT_BOSS || self->mtype == M_GUARDIAN) + if (self->mtype == M_COMMANDER || self->mtype == M_SUPERTANK || self->mtype == M_MAKRON || self->mtype == M_CARRIER) { edict_t *e; float drop_chance = 0.25; @@ -777,7 +776,7 @@ void vrx_roll_to_make_champion(edict_t *drone, enum dronespawn_t *drone_type) } } -static qboolean vrx_drone_spawn_is_boss(enum dronespawn_t drone_type) +qboolean vrx_drone_spawn_is_boss(enum dronespawn_t drone_type) { switch (drone_type) { @@ -785,15 +784,15 @@ static qboolean vrx_drone_spawn_is_boss(enum dronespawn_t drone_type) case DS_MAKRON: case DS_BARON_FIRE: case DS_SUPERTANK: - case DS_BOSS5: case DS_JORG: case DS_CARRIER: + case DS_GUARDIAN: case DS_WIDOW: case DS_WIDOW2: case DS_FIXBOT_BOSS: - case DS_GUARDIAN: case DS_BOSS2: - case DS_BOSS2_HYPER: + //case DS_BOSS2_HYPER: + case DS_BOSS5: return true; default: return false; @@ -923,11 +922,24 @@ edict_t *vrx_create_drone_from_ent(edict_t *drone, edict_t *ent, enum dronespawn case DS_JORG: init_drone_jorg(drone); break; case DS_CARRIER: init_drone_carrier(drone); break; case DS_GUARDIAN: init_drone_guardian(drone); break; + case DS_WIDOW: init_drone_widow(drone); break; + case DS_WIDOW2: init_drone_widow2(drone); break; + case DS_FIXBOT_BOSS: init_drone_fixbot_boss(drone); break; + case DS_BOSS2: init_drone_boss2(drone); break; + case DS_BOSS2_HYPER: init_drone_boss2_hyper(drone); break; + case DS_BOSS5: init_drone_boss5(drone); break; + + // special/miniboss-sized normal monsters case DS_JANITOR: drone->mtype = M_JANITOR; init_drone_supertank(drone); break; case DS_MINIGUARDIAN: drone->mtype = M_MINIGUARDIAN; init_drone_guardian(drone); break; + case DS_FIXBOT: init_drone_fixbot(drone); break; + case DS_ROGUE_TURRET: init_drone_rogue_turret(drone); break; + case DS_BOSS2_SMALL: init_drone_boss2_small(drone); break; - // default - default: init_drone_gunner(drone); break; + default: + gi.dprintf("WARNING: unknown drone spawn type %d\n", drone_type); + G_FreeEdict(drone); + return NULL; } /* az: init functions might have set up a pain function -- address that here */ @@ -939,12 +951,7 @@ edict_t *vrx_create_drone_from_ent(edict_t *drone, edict_t *ent, enum dronespawn drone->pain = drone_pain; //4.0 gib health based on monster control cost - if (drone_type < 30 || - drone_type == DS_JANITOR || - drone_type == DS_MINIGUARDIAN || - drone_type == DS_SOLDIER_RIPPER || - drone_type == DS_SOLDIER_BLUEBLASTER || - drone_type == DS_SOLDIER_LASER) + if (!is_boss_spawn) drone->gib_health = -drone->monsterinfo.control_cost * BASE_GIB_HEALTH * M_CONTROL_COST_SCALE; else drone->gib_health = 0;//gib boss immediately @@ -1086,7 +1093,7 @@ edict_t *vrx_create_drone_from_ent(edict_t *drone, edict_t *ent, enum dronespawn } //4.4 FIXME: should we be doing this if ent is world? - if (!ent->client && (drone_type == 30 || drone_type == 31)) // boss + if (!ent->client && is_boss_spawn) ent->num_sentries++; else { @@ -1525,48 +1532,152 @@ double randfrac(void) { // note: flash_number is used by monsters to determine muzzle location; use -1 if muzzle location is already known, or 0 for non-monsters to estimate muzzle location // aiming vector will be copied to 'forward' and can be used for firing functions -void MonsterAim (edict_t *self, float accuracy, int projectile_speed, qboolean rocket, - int flash_number, vec3_t forward, vec3_t start) +static qboolean M_MonsterTraceCombatSight(edict_t *self, vec3_t start, vec3_t end) { - float velocity, dist, rnd, crnd;//, base_acc = accuracy; - vec3_t target, end; - vec3_t right, offset; - trace_t tr; + if (!gi.inPVS(start, end)) + return false; + + return gi.trace(start, NULL, NULL, end, self, MASK_SOLID).fraction == 1.0f; +} + +static void M_MonsterProjectMuzzleSource(edict_t *self, int flash_number, vec3_t forward, vec3_t right, vec3_t start) +{ + vec3_t offset; - // determine muzzle origin - AngleVectors (self->s.angles, forward, right, NULL); if (self->client && !flash_number) { - VectorSet(offset, 0, 8, self->viewheight-8); - P_ProjectSource (self->client, self->s.origin, offset, forward, right, start); + VectorSet(offset, 0, 8, self->viewheight - 8); + P_ProjectSource(self->client, self->s.origin, offset, forward, right, start); } - else if (flash_number) + else if (flash_number > 0) { - // -1 flash number indicates we've already calculated our muzzle location as 'start' - // otherwise proceed using flash offset - if (flash_number != -1) - { - // monsters have special offsets that determine their exact firing origin - G_ProjectSource(self->s.origin, monster_flash_offset[flash_number], forward, right, start); - // fix for retarded chick muzzle location - if ((self->svflags & SVF_MONSTER) && (start[2] < self->absmin[2] + 32)) - start[2] += 32; - } + G_ProjectSource(self->s.origin, monster_flash_offset[flash_number], forward, right, start); + if ((self->svflags & SVF_MONSTER) && (start[2] < self->absmin[2] + 32)) + start[2] += 32; } - else // can't determine the muzzle origin + else if (self->viewheight) { - // is viewheight set? if so, use that as a starting point - if (self->viewheight) - { - VectorCopy(self->s.origin, start); - start[2] += self->viewheight; - } - else // otherwise, use the mid-point of the bounding box - G_EntMidPoint(self, start); - // move starting point forward - VectorMA(start, self->maxs[1]+8, forward, start); + VectorCopy(self->s.origin, start); + start[2] += self->viewheight; + VectorMA(start, self->maxs[1] + 8, forward, start); + } + else + { + G_EntMidPoint(self, start); + VectorMA(start, self->maxs[1] + 8, forward, start); + } +} + +static qboolean M_MonsterTraceShotToTarget(edict_t *self, vec3_t start, edict_t *target, vec3_t end) +{ + trace_t tr; + + tr = gi.trace(start, NULL, NULL, end, self, MASK_SHOT); + return tr.ent && tr.ent == target; +} + +static qboolean M_MonsterFindClearTargetPoint(edict_t *self, vec3_t start, edict_t *target, vec3_t point) +{ + vec3_t test; + + if (!G_EntExists(target)) + return false; + + G_EntMidPoint(target, test); + if (M_MonsterTraceShotToTarget(self, start, target, test)) + { + VectorCopy(test, point); + return true; + } + + G_EntViewPoint(target, test); + if (M_MonsterTraceShotToTarget(self, start, target, test)) + { + VectorCopy(test, point); + return true; + } + + VectorCopy(target->s.origin, test); + if (M_MonsterTraceShotToTarget(self, start, target, test)) + { + VectorCopy(test, point); + return true; } + return false; +} + +qboolean M_MonsterHasCombatSight(edict_t *self, edict_t *other) +{ + vec3_t start, end; + + if (!G_EntExists(other)) + return false; + + if (visible(self, other)) + return true; + + G_EntViewPoint(self, start); + G_EntViewPoint(other, end); + if (M_MonsterTraceCombatSight(self, start, end)) + return true; + + G_EntMidPoint(other, end); + if (M_MonsterTraceCombatSight(self, start, end)) + return true; + + G_EntMidPoint(self, start); + if (M_MonsterTraceCombatSight(self, start, end)) + return true; + + return false; +} + +qboolean M_MonsterHasClearShotFrom(edict_t *self, vec3_t start) +{ + vec3_t end; + + return M_MonsterFindClearShot(self, start, end); +} + +qboolean M_MonsterFindClearShot(edict_t *self, vec3_t start, vec3_t point) +{ + if (!G_EntExists(self->enemy)) + return false; + + return M_MonsterFindClearTargetPoint(self, start, self->enemy, point); +} + +qboolean M_MonsterHasClearShotFromFlash(edict_t *self, int flash_number) +{ + vec3_t forward, right, start; + + AngleVectors(self->s.angles, forward, right, NULL); + M_MonsterProjectMuzzleSource(self, flash_number, forward, right, start); + return M_MonsterHasClearShotFrom(self, start); +} + +void M_MonsterBlockedShot(edict_t *self, float delay) +{ + if (!self || !self->inuse) + return; + + self->monsterinfo.attack_finished = max(self->monsterinfo.attack_finished, level.time + delay); +} + +void MonsterAim (edict_t *self, float accuracy, int projectile_speed, qboolean rocket, + int flash_number, vec3_t forward, vec3_t start) +{ + float velocity, dist, rnd, crnd;//, base_acc = accuracy; + vec3_t target, end; + vec3_t right; + trace_t tr; + + // determine muzzle origin + AngleVectors (self->s.angles, forward, right, NULL); + if (flash_number != -1) + M_MonsterProjectMuzzleSource(self, flash_number, forward, right, start); + // fire ahead if our enemy is invalid or out of our FOV if (!G_EntExists(self->enemy) || !nearfov(self, self->enemy, 0, 60)) { @@ -1645,6 +1756,7 @@ void MonsterAim (edict_t *self, float accuracy, int projectile_speed, qboolean r accuracy *= 0.2; G_EntMidPoint(self->enemy, target); // 3.58 aim at the ent's actual mid point + M_MonsterFindClearTargetPoint(self, start, self->enemy, target); //VectorCopy(self->enemy->s.origin, target); // miss the shot @@ -2398,7 +2510,7 @@ qboolean M_SetBoundingBox (int mtype, vec3_t boxmin, vec3_t boxmax) break; case M_BERSERK: // az: these were missing... VectorSet(boxmin, -16, -16, -24); - VectorSet(boxmax, 16, 16, 32); + VectorSet(boxmax, 16, 16, -8); break; case M_GLADIATOR: case M_GLADB: diff --git a/src/entities/drone/drone_mutant.c b/src/entities/drone/drone_mutant.c index a730c07b..c485a395 100644 --- a/src/entities/drone/drone_mutant.c +++ b/src/entities/drone/drone_mutant.c @@ -376,13 +376,19 @@ void mutant_check_landing (edict_t *self) } +static void mutant_check_landing_ai(edict_t *self, float dist) +{ + ai_charge(self, dist); + mutant_check_landing(self); +} + mframe_t mutant_frames_jump [] = { ai_charge, 0, NULL, ai_charge, 0, NULL, ai_charge, 0, mutant_jump_takeoff, ai_charge, 0, NULL, - ai_charge, 0, mutant_check_landing, + mutant_check_landing_ai, 0, NULL, ai_charge, 0, NULL, // ai_charge, 0, NULL, // ai_charge, 0, NULL diff --git a/src/entities/drone/drone_parasite.c b/src/entities/drone/drone_parasite.c index f5078367..96d6d1e6 100644 --- a/src/entities/drone/drone_parasite.c +++ b/src/entities/drone/drone_parasite.c @@ -33,6 +33,14 @@ void myparasite_checkattack (edict_t *self); void myparasite_attack1 (edict_t *self); void myparasite_continue (edict_t *self); +static void ParasiteProjectDrainSource(edict_t *self, vec3_t start) +{ + vec3_t forward, right, offset; + + AngleVectors(self->s.angles, forward, right, NULL); + VectorSet(offset, 24, 0, 6); + G_ProjectSource(self->s.origin, offset, forward, right, start); +} void myparasite_launch (edict_t *self) { @@ -245,8 +253,6 @@ void myparasite_run (edict_t *self) static qboolean ParasiteCanAttack (edict_t *self, vec3_t start, vec3_t end) { - vec3_t f, r, offset; - if (!self->enemy) return false; if (entdist(self, self->enemy) > 128) @@ -265,16 +271,11 @@ static qboolean ParasiteCanAttack (edict_t *self, vec3_t start, vec3_t end) return false; // get starting point - AngleVectors (self->s.angles, f, r, NULL); - VectorSet (offset, 24, 0, 6); - G_ProjectSource (self->s.origin, offset, f, r, start); - - // target the midpoint of enemy - G_EntMidPoint(self->enemy, end); + ParasiteProjectDrainSource(self, start); // make sure there is a clear shot //if (!G_IsClearPath(self->enemy, MASK_SHOT, start, end)) - if (!G_ClearShot(self, start, self->enemy)) + if (!M_MonsterFindClearShot(self, start, end)) { //gi.dprintf("no clear path\n"); return false; @@ -377,7 +378,9 @@ mmove_t myparasite_move_runandattack = {FRAME_run03, FRAME_run09, myparasite_fra void myparasite_continue (edict_t *self) { - if (G_ValidTarget(self, self->enemy, true, true) && (entdist(self, self->enemy) <= 128)) + vec3_t start, end; + + if (G_ValidTarget(self, self->enemy, true, true) && ParasiteCanAttack(self, start, end)) self->monsterinfo.currentmove = &myparasite_move_runandattack; } @@ -472,13 +475,12 @@ static void myparasite_shrink(edict_t *self) self->svflags |= SVF_DEADMONSTER; gi.linkentity(self); } - mframe_t myparasite_frames_death [] = { ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, - ai_move, 0, myparasite_shrink, + ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL @@ -534,7 +536,6 @@ void myparasite_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int d gi.sound (self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); self->deadflag = DEAD_DEAD; self->takedamage = DAMAGE_YES; - vrx_update_drone_death_skin(self); self->monsterinfo.currentmove = &myparasite_move_death; DroneList_Remove(self); diff --git a/src/entities/drone/drone_redmutant.c b/src/entities/drone/drone_redmutant.c index f90e4063..3b41cf31 100644 --- a/src/entities/drone/drone_redmutant.c +++ b/src/entities/drone/drone_redmutant.c @@ -92,7 +92,6 @@ mmove_t redmutant_move_stand = { FRAME_stand101, FRAME_stand112, redmutant_frame static void redmutant_stand(edict_t *self) { - redmutant_set_bbox_height(self, REDMUTANT_IDLE_MAX_Z); self->monsterinfo.currentmove = &redmutant_move_stand; } @@ -136,7 +135,6 @@ mmove_t redmutant_move_idle = { FRAME_stand202, FRAME_stand228, redmutant_frames static void redmutant_idle(edict_t *self) { - redmutant_set_bbox_height(self, REDMUTANT_IDLE_MAX_Z); self->monsterinfo.currentmove = &redmutant_move_idle; gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); } @@ -160,7 +158,6 @@ mmove_t redmutant_move_walk = { FRAME_walk05, FRAME_walk16, redmutant_frames_wal static void redmutant_walk_loop(edict_t *self) { - redmutant_restore_bbox(self); self->monsterinfo.currentmove = &redmutant_move_walk; } @@ -175,7 +172,6 @@ mmove_t redmutant_move_start_walk = { FRAME_walk01, FRAME_walk04, redmutant_fram static void redmutant_walk(edict_t *self) { - redmutant_restore_bbox(self); if (!self->goalentity) self->goalentity = world; self->monsterinfo.currentmove = &redmutant_move_start_walk; @@ -194,7 +190,6 @@ mmove_t redmutant_move_run = { FRAME_run03, FRAME_run08, redmutant_frames_run, N static void redmutant_run(edict_t *self) { - redmutant_restore_bbox(self); if (self->monsterinfo.aiflags & AI_STAND_GROUND) self->monsterinfo.currentmove = &redmutant_move_stand; else @@ -337,8 +332,15 @@ static void redmutant_check_landing(edict_t *self) self->monsterinfo.aiflags |= AI_HOLD_FRAME; } + +static void redmutant_check_landing_ai(edict_t *self, float dist) +{ + ai_charge(self, dist); + redmutant_check_landing(self); +} + mframe_t redmutant_frames_jump_air[] = { - ai_charge, 0, redmutant_check_landing + redmutant_check_landing_ai, 0, NULL }; mmove_t redmutant_move_jump_air = { FRAME_attack103, FRAME_attack103, redmutant_frames_jump_air, NULL }; @@ -360,7 +362,6 @@ mmove_t redmutant_move_jump_finish = { FRAME_attack104, FRAME_attack108, redmuta static void redmutant_jump(edict_t *self) { - redmutant_restore_bbox(self); self->monsterinfo.currentmove = &redmutant_move_jump_start; } @@ -400,6 +401,12 @@ static void redmutant_flip_check_landing(edict_t *self) self->monsterinfo.aiflags |= AI_HOLD_FRAME; } +static void redmutant_flip_check_landing_ai(edict_t *self, float dist) +{ + ai_charge(self, dist); + redmutant_flip_check_landing(self); +} + mframe_t redmutant_frames_flip[] = { ai_charge, 0, NULL, ai_charge, 17, NULL, @@ -408,13 +415,12 @@ mframe_t redmutant_frames_flip[] = { ai_charge, 15, NULL, ai_charge, 0, NULL, ai_charge, 3, NULL, - ai_charge, 0, redmutant_flip_check_landing + redmutant_flip_check_landing_ai, 0, NULL }; mmove_t redmutant_move_flip = { FRAME_attack101, FRAME_attack108, redmutant_frames_flip, redmutant_post_jump }; static void redmutant_flip(edict_t *self) { - redmutant_restore_bbox(self); self->monsterinfo.currentmove = &redmutant_move_flip; } diff --git a/src/entities/drone/drone_rogue_turret.c b/src/entities/drone/drone_rogue_turret.c index 95bf9d65..33a29283 100644 --- a/src/entities/drone/drone_rogue_turret.c +++ b/src/entities/drone/drone_rogue_turret.c @@ -118,6 +118,24 @@ static void rogue_turret_aim(edict_t *self) rogue_turret_update_laser(self); } +static void rogue_turret_aim_stand_ai(edict_t *self, float dist) +{ + drone_ai_stand(self, dist); + rogue_turret_aim(self); +} + +static void rogue_turret_aim_move_ai(edict_t *self, float dist) +{ + ai_move(self, dist); + rogue_turret_aim(self); +} + +static void rogue_turret_aim_charge_ai(edict_t *self, float dist) +{ + ai_charge(self, dist); + rogue_turret_aim(self); +} + static void rogue_turret_fire(edict_t *self) { vec3_t forward; @@ -157,8 +175,8 @@ static void rogue_turret_fire(edict_t *self) static mframe_t rogue_turret_frames_stand[] = { - drone_ai_stand, 0, rogue_turret_aim, - drone_ai_stand, 0, rogue_turret_aim + rogue_turret_aim_stand_ai, 0, NULL, + rogue_turret_aim_stand_ai, 0, NULL }; static mmove_t rogue_turret_move_stand = { TURRET_FRAME_stand01, TURRET_FRAME_stand02, rogue_turret_frames_stand, NULL }; @@ -171,13 +189,13 @@ static void rogue_turret_stand(edict_t *self) static mframe_t rogue_turret_frames_ready[] = { - ai_move, 0, rogue_turret_aim, - ai_move, 0, rogue_turret_aim, - ai_move, 0, rogue_turret_aim, - ai_move, 0, rogue_turret_aim, - ai_move, 0, rogue_turret_aim, - ai_move, 0, rogue_turret_aim, - ai_move, 0, rogue_turret_aim + rogue_turret_aim_move_ai, 0, NULL, + rogue_turret_aim_move_ai, 0, NULL, + rogue_turret_aim_move_ai, 0, NULL, + rogue_turret_aim_move_ai, 0, NULL, + rogue_turret_aim_move_ai, 0, NULL, + rogue_turret_aim_move_ai, 0, NULL, + rogue_turret_aim_move_ai, 0, NULL }; static mmove_t rogue_turret_move_ready = { TURRET_FRAME_active01, TURRET_FRAME_run01, rogue_turret_frames_ready, rogue_turret_run }; @@ -193,8 +211,8 @@ static void rogue_turret_ready(edict_t *self) static mframe_t rogue_turret_frames_run[] = { - drone_ai_stand, 0, rogue_turret_active, - drone_ai_stand, 0, rogue_turret_active + rogue_turret_aim_stand_ai, 0, rogue_turret_active, + rogue_turret_aim_stand_ai, 0, rogue_turret_active }; static mmove_t rogue_turret_move_run = { TURRET_FRAME_run01, TURRET_FRAME_run02, rogue_turret_frames_run, rogue_turret_run }; @@ -217,10 +235,10 @@ static void rogue_turret_run(edict_t *self) static mframe_t rogue_turret_frames_fire[] = { - ai_charge, 0, rogue_turret_aim, - ai_charge, 0, rogue_turret_fire, - ai_charge, 0, rogue_turret_aim, - ai_charge, 0, rogue_turret_aim + rogue_turret_aim_charge_ai, 0, NULL, + rogue_turret_aim_charge_ai, 0, rogue_turret_fire, + rogue_turret_aim_charge_ai, 0, NULL, + rogue_turret_aim_charge_ai, 0, NULL }; static mmove_t rogue_turret_move_fire = { TURRET_FRAME_pow01, TURRET_FRAME_pow04, rogue_turret_frames_fire, rogue_turret_run }; diff --git a/src/entities/drone/drone_runnertank.c b/src/entities/drone/drone_runnertank.c index bbbbe8c0..d42aa408 100644 --- a/src/entities/drone/drone_runnertank.c +++ b/src/entities/drone/drone_runnertank.c @@ -10,6 +10,8 @@ static int sound_sight; static int sound_windup; static int sound_strike; static int sound_plasma; +static int skin_normal; +static int skin_pain; #define RUNNERTANK_JUMP_ATTACK_DELAY 12.0f #define RUNNERTANK_JUMP_ATTACK_FOV 35 @@ -17,6 +19,8 @@ static int sound_plasma; #define RUNNERTANK_JUMP_ATTACK_DROP_SPEED 900.0f #define RUNNERTANK_JUMP_ATTACK_DROP_GRAVITY 3.0f #define RUNNERTANK_INVASION_RUN_SCALE 1.15f +#define RUNNERTANK_NORMAL_SKIN "models/vault/monsters/tank/skin.pcx" +#define RUNNERTANK_PAIN_SKIN "models/monsters/tank/pain.pcx" static void runnertank_stand(edict_t *self); static void runnertank_walk(edict_t *self); @@ -31,8 +35,28 @@ static void runnertank_doattack_rocket(edict_t *self); static void runnertank_jump_attack_takeoff(edict_t *self); static void runnertank_jump_attack_hold(edict_t *self); +static void runnertank_update_skin(edict_t *self) +{ + int desired_skin; + + if (self->max_health <= 0) + return; + + if (!skin_normal) + skin_normal = gi.imageindex(RUNNERTANK_NORMAL_SKIN); + if (!skin_pain) + skin_pain = gi.imageindex(RUNNERTANK_PAIN_SKIN); + + self->s.renderfx |= RF_CUSTOMSKIN; + desired_skin = self->health < (self->max_health / 2) ? skin_pain : skin_normal; + + if (desired_skin) + self->s.skinnum = desired_skin; +} + static void runnertank_ai_run(edict_t *self, float dist) { + runnertank_update_skin(self); drone_ai_run(self, invasion->value ? dist * RUNNERTANK_INVASION_RUN_SCALE : dist); } @@ -76,6 +100,21 @@ static void runnertank_slam_effect(vec3_t origin) gi.multicast(origin, MULTICAST_PHS); } +static qboolean runnertank_can_rail(edict_t *self) +{ + return M_MonsterHasClearShotFromFlash(self, MZ2_TANK_BLASTER_1); +} + +static qboolean runnertank_can_rocket(edict_t *self) +{ + return M_MonsterHasClearShotFromFlash(self, MZ2_TANK_ROCKET_1); +} + +static qboolean runnertank_can_chain(edict_t *self) +{ + return M_MonsterHasClearShotFromFlash(self, MZ2_TANK_MACHINEGUN_1); +} + static void runnertank_idle(edict_t *self) { gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); @@ -126,6 +165,7 @@ mmove_t runnertank_move_stand = { FRAME_stand01, FRAME_stand30, runnertank_frame static void runnertank_stand(edict_t *self) { + runnertank_update_skin(self); self->monsterinfo.currentmove = &runnertank_move_stand; } @@ -173,6 +213,7 @@ mmove_t runnertank_move_walk = { FRAME_walk22, FRAME_walk38, runnertank_frames_w static void runnertank_walk_loop(edict_t *self) { + runnertank_update_skin(self); if (!self->goalentity) self->goalentity = world; self->monsterinfo.currentmove = &runnertank_move_walk; @@ -180,6 +221,7 @@ static void runnertank_walk_loop(edict_t *self) static void runnertank_walk(edict_t *self) { + runnertank_update_skin(self); if (!self->goalentity) self->goalentity = world; @@ -209,6 +251,8 @@ static void runnertank_run(edict_t *self) if (self->deadflag == DEAD_DEAD) return; + runnertank_update_skin(self); + if (self->enemy && self->enemy->client) self->monsterinfo.aiflags |= AI_BRUTAL; else @@ -240,6 +284,9 @@ static void runnertank_rail(edict_t *self) damage = M_RAILGUN_DMG_MAX; MonsterAim(self, M_HITSCAN_INSTANT_ACC, 0, false, flash_number, forward, start); + if (!M_MonsterHasClearShotFrom(self, start)) + return; + monster_fire_railgun(self, start, forward, damage, damage, flash_number); } @@ -266,6 +313,9 @@ static void runnertank_rocket(edict_t *self) speed = M_ROCKETLAUNCHER_SPEED_MAX; MonsterAim(self, M_PROJECTILE_ACC, speed, true, flash_number, forward, start); + if (!M_MonsterHasClearShotFrom(self, start)) + return; + monster_fire_rocket(self, start, forward, damage, speed, flash_number); } @@ -287,6 +337,9 @@ static void runnertank_plasma(edict_t *self) radius_damage = max(1, damage / 2); MonsterAim(self, M_PROJECTILE_ACC, speed, false, flash_number, forward, start); + if (!M_MonsterHasClearShotFrom(self, start)) + return; + fire_plasma(self, start, forward, damage, speed, M_PLASMA_DAMAGE_RADIUS, radius_damage); gi.positioned_sound(start, self, CHAN_WEAPON, sound_plasma, 1, ATTN_NORM, 0); } @@ -330,6 +383,7 @@ static void runnertank_meleeattack(edict_t *self) static void runnertank_attack_finished(edict_t *self) { + runnertank_update_skin(self); M_DelayNextAttack(self, 0.5, false); runnertank_run(self); } @@ -379,7 +433,7 @@ mmove_t runnertank_move_attack_post_blast = { FRAME_attak117, FRAME_attak122, ru static void runnertank_reattack_blast(edict_t *self) { - if (!G_ValidTarget(self, self->enemy, true, true) || !visible(self, self->enemy)) + if (!G_ValidTarget(self, self->enemy, true, true) || !visible(self, self->enemy) || !runnertank_can_rail(self)) { self->monsterinfo.currentmove = &runnertank_move_attack_post_blast; M_DelayNextAttack(self, 0, true); @@ -472,7 +526,7 @@ mmove_t runnertank_move_attack_post_rocket = { FRAME_attak322, FRAME_attak335, r static void runnertank_refire_rocket(edict_t *self) { - if (!G_ValidTarget(self, self->enemy, true, true) || !visible(self, self->enemy)) + if (!G_ValidTarget(self, self->enemy, true, true) || !visible(self, self->enemy) || !runnertank_can_rocket(self)) { self->monsterinfo.currentmove = &runnertank_move_attack_post_rocket; M_DelayNextAttack(self, 0, true); @@ -570,13 +624,19 @@ static void runnertank_jump_attack_hold(edict_t *self) } } +static void runnertank_jump_attack_hold_ai(edict_t *self, float dist) +{ + ai_move(self, dist); + runnertank_jump_attack_hold(self); +} + mframe_t runnertank_frames_jump_attack[] = { ai_charge, 15, NULL, ai_move, 0, runnertank_jump_attack_takeoff, - ai_move, 0, runnertank_jump_attack_hold, - ai_move, 0, runnertank_jump_attack_hold, - ai_move, 0, runnertank_jump_attack_hold + runnertank_jump_attack_hold_ai, 0, NULL, + runnertank_jump_attack_hold_ai, 0, NULL, + runnertank_jump_attack_hold_ai, 0, NULL }; mmove_t runnertank_move_jump_attack = { FRAME_run01, FRAME_run05, runnertank_frames_jump_attack, runnertank_attack_finished }; @@ -624,12 +684,18 @@ static void runnertank_attack(edict_t *self) { float r, range; qboolean attack_started = false; + qboolean can_rail; + qboolean can_rocket; + qboolean can_chain; if (!G_ValidTarget(self, self->enemy, true, true)) return; r = random(); range = entdist(self, self->enemy); + can_rail = runnertank_can_rail(self); + can_rocket = runnertank_can_rocket(self); + can_chain = runnertank_can_chain(self); if ((range <= 128) && self->groundentity) { @@ -645,44 +711,135 @@ static void runnertank_attack(edict_t *self) { if (r <= 0.35) { - self->monsterinfo.currentmove = &runnertank_move_attack_chain; - attack_started = true; + if (can_chain) + { + self->monsterinfo.currentmove = &runnertank_move_attack_chain; + attack_started = true; + } + else if (can_rocket) + { + self->monsterinfo.currentmove = &runnertank_move_attack_pre_rocket; + attack_started = true; + } + else if (can_rail) + { + self->monsterinfo.currentmove = &runnertank_move_attack_blast; + attack_started = true; + } } else if (r <= 0.5) { - self->monsterinfo.currentmove = &runnertank_move_attack_pre_rocket; - attack_started = true; + if (can_rocket) + { + self->monsterinfo.currentmove = &runnertank_move_attack_pre_rocket; + attack_started = true; + } + else if (can_chain) + { + self->monsterinfo.currentmove = &runnertank_move_attack_chain; + attack_started = true; + } + else if (can_rail) + { + self->monsterinfo.currentmove = &runnertank_move_attack_blast; + attack_started = true; + } } } else if (range <= 640) { if (r <= 0.25) { - self->monsterinfo.currentmove = &runnertank_move_attack_chain; - attack_started = true; + if (can_chain) + { + self->monsterinfo.currentmove = &runnertank_move_attack_chain; + attack_started = true; + } + else if (can_rail) + { + self->monsterinfo.currentmove = &runnertank_move_attack_blast; + attack_started = true; + } + else if (can_rocket) + { + self->monsterinfo.currentmove = &runnertank_move_attack_pre_rocket; + attack_started = true; + } } else if (r <= 0.55) { - self->monsterinfo.currentmove = &runnertank_move_attack_blast; - attack_started = true; + if (can_rail) + { + self->monsterinfo.currentmove = &runnertank_move_attack_blast; + attack_started = true; + } + else if (can_rocket) + { + self->monsterinfo.currentmove = &runnertank_move_attack_pre_rocket; + attack_started = true; + } + else if (can_chain) + { + self->monsterinfo.currentmove = &runnertank_move_attack_chain; + attack_started = true; + } } else if (r <= 0.7) { - self->monsterinfo.currentmove = &runnertank_move_attack_pre_rocket; - attack_started = true; + if (can_rocket) + { + self->monsterinfo.currentmove = &runnertank_move_attack_pre_rocket; + attack_started = true; + } + else if (can_rail) + { + self->monsterinfo.currentmove = &runnertank_move_attack_blast; + attack_started = true; + } + else if (can_chain) + { + self->monsterinfo.currentmove = &runnertank_move_attack_chain; + attack_started = true; + } } } else { if (r <= 0.35) { - self->monsterinfo.currentmove = &runnertank_move_attack_blast; - attack_started = true; + if (can_rail) + { + self->monsterinfo.currentmove = &runnertank_move_attack_blast; + attack_started = true; + } + else if (can_rocket) + { + self->monsterinfo.currentmove = &runnertank_move_attack_pre_rocket; + attack_started = true; + } + else if (can_chain) + { + self->monsterinfo.currentmove = &runnertank_move_attack_chain; + attack_started = true; + } } else if (r <= 0.55) { - self->monsterinfo.currentmove = &runnertank_move_attack_pre_rocket; - attack_started = true; + if (can_rocket) + { + self->monsterinfo.currentmove = &runnertank_move_attack_pre_rocket; + attack_started = true; + } + else if (can_rail) + { + self->monsterinfo.currentmove = &runnertank_move_attack_blast; + attack_started = true; + } + else if (can_chain) + { + self->monsterinfo.currentmove = &runnertank_move_attack_chain; + attack_started = true; + } } } @@ -722,6 +879,8 @@ mmove_t runnertank_move_pain3 = { FRAME_pain301, FRAME_pain316, runnertank_frame static void runnertank_pain(edict_t *self, edict_t *other, float kick, int damage) { + runnertank_update_skin(self); + if (level.time < self->pain_debounce_time) return; @@ -835,6 +994,7 @@ static void runnertank_die(edict_t *self, edict_t *inflictor, edict_t *attacker, gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); self->deadflag = DEAD_DEAD; self->takedamage = DAMAGE_YES; + runnertank_update_skin(self); self->monsterinfo.currentmove = &runnertank_move_death; if (self->activator && !self->activator->client) @@ -892,8 +1052,9 @@ void init_drone_runnertank(edict_t *self) self->monsterinfo.control_cost = M_TANK_CONTROL_COST; self->monsterinfo.cost = M_TANK_COST; self->mtype = M_RUNNERTANK; - self->s.renderfx |= RF_CUSTOMSKIN; - self->s.skinnum = gi.imageindex("models/vault/monsters/tank/skin.pcx"); + skin_normal = gi.imageindex(RUNNERTANK_NORMAL_SKIN); + skin_pain = gi.imageindex(RUNNERTANK_PAIN_SKIN); + runnertank_update_skin(self); gi.linkentity(self); diff --git a/src/entities/drone/drone_shambler.c b/src/entities/drone/drone_shambler.c index 70c405d4..b0cdb246 100644 --- a/src/entities/drone/drone_shambler.c +++ b/src/entities/drone/drone_shambler.c @@ -425,19 +425,9 @@ void ShamblerCastLightning(edict_t* self) gi.WritePosition(tr.endpos); gi.multicast(start, MULTICAST_PVS); - - //apply dmg! - - - - if (tr.fraction < 1.0 && tr.ent) + if (tr.fraction < 1.0f && tr.ent) { - //int damage = M_SHAMBLER_LIGHTNING_BASE_DMG + M_SHAMBLER_ADDON_LIGHTNING_DMG; //* self->monsterinfo.level; ? const int damage = 4 + 3 * drone_damagelevel(self); - - //if (M_SHAMBLER_LIGHTNING_MAX_DMG && damage > M_SHAMBLER_LIGHTNING_MAX_DMG) - // damage = M_SHAMBLER_LIGHTNING_MAX_DMG; - T_Damage(tr.ent, self, self, dir, tr.endpos, tr.plane.normal, damage, 0, DAMAGE_ENERGY, MOD_LIGHTNING); } } @@ -458,44 +448,6 @@ mframe_t shambler_frames_magic[] = { }; mmove_t shambler_move_attack = { FRAME_magic1, FRAME_magic12, shambler_frames_magic, shambler_run }; - -//fiery skull attack - -static void shambler_fieryskull_update(edict_t* self) -{ - const int frame_offset = self->s.frame - FRAME_magic1; - if (frame_offset >= MAX_LIGHTNING_FRAMES) - { - return; - } - - vec3_t f, r; - AngleVectors(self->s.angles, f, r, NULL); - - vec3_t left_pos, right_pos; - VectorMA(self->s.origin, lightning_left_hand[frame_offset][0], f, left_pos); - VectorMA(left_pos, lightning_left_hand[frame_offset][1], r, left_pos); - left_pos[2] += lightning_left_hand[frame_offset][2]; - - VectorMA(self->s.origin, lightning_right_hand[frame_offset][0], f, right_pos); - VectorMA(right_pos, lightning_right_hand[frame_offset][1], r, right_pos); - right_pos[2] += lightning_right_hand[frame_offset][2]; - - gi.WriteByte(svc_temp_entity); -#ifndef VRX_REPRO - gi.WriteByte(TE_MONSTER_HEATBEAM); - gi.WriteShort(self - g_edicts); -#else - gi.WriteByte(TE_LIGHTNING); - gi.WriteShort(self - g_edicts); - gi.WriteShort(0); -#endif - - gi.WritePosition(left_pos); - gi.WritePosition(right_pos); - gi.multicast(left_pos, MULTICAST_PVS); -} - // New function to calculate aim direction void CalculateAimDirection(edict_t* self, vec3_t start, vec3_t aim) { @@ -583,32 +535,29 @@ static void shambler_ice_update(edict_t* self) VectorMA(right_pos, lightning_right_hand[frame_offset][1], r, right_pos); right_pos[2] += lightning_right_hand[frame_offset][2]; - // create fire models on both hands - edict_t* left_fire = G_Spawn(); - edict_t* right_fire = G_Spawn(); + // create cyan ice glow on both hands + edict_t* left_glow = G_Spawn(); + edict_t* right_glow = G_Spawn(); - VectorCopy(left_pos, left_fire->s.origin); - VectorCopy(right_pos, right_fire->s.origin); + VectorCopy(left_pos, left_glow->s.origin); + VectorCopy(right_pos, right_glow->s.origin); - left_fire->s.modelindex = gi.modelindex("models/fire/tris.md2"); - right_fire->s.modelindex = gi.modelindex("models/fire/tris.md2"); - - //left_fire->s.modelindex = gi.modelindex("models/objects/flball/tris.md2"); // ugly - //right_fire->s.modelindex = gi.modelindex("models/objects/flball/tris.md2"); // ugly - left_fire->s.effects |= EF_QUAD | RF_SHELL_CYAN; - right_fire->s.effects |= EF_QUAD | RF_SHELL_CYAN; + left_glow->s.modelindex = gi.modelindex("models/fire/tris.md2"); + right_glow->s.modelindex = gi.modelindex("models/fire/tris.md2"); + left_glow->s.effects |= EF_QUAD | RF_SHELL_CYAN; + right_glow->s.effects |= EF_QUAD | RF_SHELL_CYAN; - left_fire->s.renderfx |= RF_FULLBRIGHT; - right_fire->s.renderfx |= RF_FULLBRIGHT; + left_glow->s.renderfx |= RF_FULLBRIGHT; + right_glow->s.renderfx |= RF_FULLBRIGHT; - left_fire->think = G_FreeEdict; - right_fire->think = G_FreeEdict; + left_glow->think = G_FreeEdict; + right_glow->think = G_FreeEdict; - left_fire->nextthink = level.time + 0.1; - right_fire->nextthink = level.time + 0.1; + left_glow->nextthink = level.time + 0.1; + right_glow->nextthink = level.time + 0.1; - gi.linkentity(left_fire); - gi.linkentity(right_fire); + gi.linkentity(left_glow); + gi.linkentity(right_glow); } void shambler_windupIce(edict_t* self) // lightning preparing @@ -637,159 +586,6 @@ mframe_t shambler_frames_icebolt[] = { }; mmove_t shambler_move_icebolt = { FRAME_magic1, FRAME_magic12, shambler_frames_icebolt, shambler_run }; - -// FIERY ROCKET SKULLS - -void shambler_windupFire(edict_t* self) // lightning preparing -{ - shambler_fieryskull_update(self); - - gi.sound(self, CHAN_WEAPON, gi.soundindex("sound_attack"), 1, ATTN_NORM, 0); - - self->nextthink = level.time + FRAMETIME; -} - - -//pre fire attack stuff -static void shambler_fire_update(edict_t* self) -{ - const int frame_offset = self->s.frame - FRAME_magic1; - if (frame_offset >= MAX_LIGHTNING_FRAMES) - { - return; - } - vec3_t f, r; - AngleVectors(self->s.angles, f, r, NULL); - - // Proyectar las posiciones de las manos - vec3_t left_pos, right_pos; - VectorMA(self->s.origin, lightning_left_hand[frame_offset][0], f, left_pos); - VectorMA(left_pos, lightning_left_hand[frame_offset][1], r, left_pos); - left_pos[2] += lightning_left_hand[frame_offset][2]; - - VectorMA(self->s.origin, lightning_right_hand[frame_offset][0], f, right_pos); - VectorMA(right_pos, lightning_right_hand[frame_offset][1], r, right_pos); - right_pos[2] += lightning_right_hand[frame_offset][2]; - - // Crear efectos de cr�neo en ambas manos - edict_t* left_fire = G_Spawn(); - edict_t* right_fire = G_Spawn(); - - VectorCopy(left_pos, left_fire->s.origin); - VectorCopy(right_pos, right_fire->s.origin); - - left_fire->s.modelindex = gi.modelindex("models/fire/tris.md2"); - right_fire->s.modelindex = gi.modelindex("models/fire/tris.md2"); - - left_fire->s.effects |= EF_GIB | EF_ROCKET; - right_fire->s.effects |= EF_GIB | EF_ROCKET; - - left_fire->s.renderfx |= RF_FULLBRIGHT; - right_fire->s.renderfx |= RF_FULLBRIGHT; - - left_fire->think = G_FreeEdict; - right_fire->think = G_FreeEdict; - - left_fire->nextthink = level.time + 0.1; - right_fire->nextthink = level.time + 0.1; - - gi.linkentity(left_fire); - gi.linkentity(right_fire); -} - -void bskull_touch(edict_t* self, edict_t* other, cplane_t* plane, csurface_t* surf); -//void magicbolt_touch(edict_t* self, edict_t* other, cplane_t* plane, csurface_t* surf); -void fire_shambler_skull(edict_t* self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius) -{ - edict_t* skull; - skull = G_Spawn(); - VectorCopy(start, skull->s.origin); - VectorCopy(dir, skull->movedir); - vectoangles(dir, skull->s.angles); - VectorScale(dir, speed, skull->velocity); - skull->movetype = MOVETYPE_FLYMISSILE; - skull->clipmask = MASK_SHOT; - skull->solid = SOLID_BBOX; - VectorClear(skull->mins); - VectorClear(skull->maxs); - skull->s.modelindex = gi.modelindex("models/objects/gibs/skull/tris.md2"); - skull->owner = self; - skull->touch = bskull_touch; - skull->dmg = damage; - skull->radius_dmg = 120; - skull->dmg_radius = damage_radius; - skull->s.effects = EF_GIB | EF_ROCKET; - skull->s.sound = gi.soundindex("weapons/rockfly.wav"); - skull->classname = "shambler_skull"; - skull->nextthink = level.time + 8000 / speed; - skull->think = G_FreeEdict; - gi.linkentity(skull); -} - -void fire_skull(edict_t* self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius); - -void ShamblerCastSkull(edict_t* self) -{ - vec3_t forward, right; - vec3_t start_left, start_right; - const float accuracy = M_PROJECTILE_ACC; - - if (!G_EntIsAlive(self->enemy)) - return; - - // Get the current frame offset - int frame_offset = self->s.frame - FRAME_magic1; - if (frame_offset >= MAX_LIGHTNING_FRAMES) - { - frame_offset = MAX_LIGHTNING_FRAMES - 1; - } - - // Calculate hand positions using the same method as for lightning - AngleVectors(self->s.angles, forward, right, NULL); - - // Left hand - VectorMA(self->s.origin, lightning_left_hand[frame_offset][0], forward, start_left); - VectorMA(start_left, lightning_left_hand[frame_offset][1], right, start_left); - start_left[2] = self->s.origin[2] + lightning_left_hand[frame_offset][2]; - - // Right hand - VectorMA(self->s.origin, lightning_right_hand[frame_offset][0], forward, start_right); - VectorMA(start_right, lightning_right_hand[frame_offset][1], right, start_right); - start_right[2] = self->s.origin[2] + lightning_right_hand[frame_offset][2]; - - // Calculate damage - const int damage = 30 + 15 * self->monsterinfo.level; - - // Fire skull from left hand - MonsterAim(self, accuracy, 1200, false, -1, forward, start_left); - fire_shambler_skull(self, start_left, forward, damage, 1600, 50); - - // Fire skull from right hand - MonsterAim(self, accuracy, 1600, false, -1, forward, start_right); - fire_shambler_skull(self, start_right, forward, damage, 1300, 50); - - // Play sound effect - gi.sound(self, CHAN_WEAPON, gi.soundindex("spells/circle1.wav"), 1, ATTN_NORM, 0); -} - - -mframe_t shambler_frames_skull[] = { - {ai_charge, 0, shambler_windupFire}, - {ai_charge, 0, shambler_fire_update}, - {ai_charge, 0, shambler_fire_update}, - {ai_move, 0, shambler_fire_update}, - {ai_move, 0, shambler_fire_update}, - {ai_move, 0, ShamblerSaveLoc}, - {ai_move, 0, NULL}, - {ai_move, 0, NULL}, - {ai_move, 0, ShamblerCastSkull}, - {ai_move, 0, ShamblerSaveLoc}, - {ai_move, 0, ShamblerCastSkull}, - {ai_charge, 0, NULL}, -}; -mmove_t shambler_move_skull = { FRAME_magic1, FRAME_magic12, shambler_frames_skull, shambler_run }; - - void shambler_meleehit(edict_t* self); mframe_t shambler_frames_melee[] = @@ -901,7 +697,6 @@ void shambler_attack(edict_t* self) } else if (r < 0.3 && infront(self, self->enemy)) // 30% to use icebolt attack { - // self->monsterinfo.currentmove = &shambler_move_skull; self->monsterinfo.currentmove = &shambler_move_icebolt; } else @@ -1020,13 +815,6 @@ void init_drone_shambler(edict_t* self) //icebolt shambler gi.soundindex("spells/coldcast.wav"); - //fire shambler ( unused ) - // - //gi.modelindex("models/objects/gibs/skull/tris.md2"); - //gi.modelindex("models/fire/tris.md2"); - //gi.soundindex("weapons/rockfly.wav"); - //gi.soundindex("spells/circle1.wav"); - //shambler sounds sound_pain = gi.soundindex("shambler/shurt2.wav"); sound_idle = gi.soundindex("shambler/sidle.wav"); diff --git a/src/entities/drone/drone_soldier.c b/src/entities/drone/drone_soldier.c index 561c1267..1db9dc29 100644 --- a/src/entities/drone/drone_soldier.c +++ b/src/entities/drone/drone_soldier.c @@ -20,10 +20,14 @@ static int sound_death; static int sound_death_ss; static int sound_cock; +static mmove_t m_soldier_move_attack3; static mmove_t m_soldier_move_attack5; static mmove_t m_soldier_move_trip; static mmove_t m_soldier_move_duck; +void m_soldier_duck_down(edict_t *self); +void m_soldier_duck_up(edict_t *self); + static const int soldier_blaster_flash[] = { MZ2_SOLDIER_BLASTER_1, @@ -218,7 +222,7 @@ mmove_t m_soldier_move_stand3 = {FRAME_stand301, FRAME_stand339, m_soldier_frame void m_soldier_stand (edict_t *self) { - if (random() > 0.5) + if (self->monsterinfo.currentmove != &m_soldier_move_stand1 || random() < 0.8) self->monsterinfo.currentmove = &m_soldier_move_stand1; else self->monsterinfo.currentmove = &m_soldier_move_stand3; @@ -279,6 +283,60 @@ static void soldier_project_raw_flash_origin(edict_t *self, int flash, vec3_t fo G_ProjectSource(self->s.origin, monster_flash_offset[flash], forward, right, start); } +static qboolean soldier_has_raw_flash_shot(edict_t *self, int flash) +{ + vec3_t forward, start; + + soldier_project_raw_flash_origin(self, flash, forward, start); + return M_MonsterHasClearShotFrom(self, start); +} + +static qboolean soldier_has_flash_shot(edict_t *self, int flash, qboolean raw_origin) +{ + if (raw_origin) + return soldier_has_raw_flash_shot(self, flash); + + return M_MonsterHasClearShotFromFlash(self, flash); +} + +static void soldier_project_laser_origin(edict_t *self, int flash, vec3_t start) +{ + vec3_t forward, right, up, offset; + + AngleVectors(self->s.angles, forward, right, up); + VectorCopy(self->s.origin, start); + VectorCopy(monster_flash_offset[flash], offset); + VectorMA(start, offset[0], forward, start); + VectorMA(start, offset[1], right, start); + VectorMA(start, offset[2] + 6, up, start); +} + +static qboolean soldier_has_laser_shot(edict_t *self, int flash) +{ + vec3_t start; + + soldier_project_laser_origin(self, flash, start); + return M_MonsterHasClearShotFrom(self, start); +} + +static qboolean m_soldier_can_primary_shot(edict_t *self) +{ + if (self->mtype == M_SOLDIER) + return M_MonsterHasClearShotFromFlash(self, MZ2_SOLDIER_BLASTER_8); + if (self->mtype == M_SOLDIERLT) + return M_MonsterHasClearShotFromFlash(self, MZ2_SOLDIER_BLASTER_8); + if (self->mtype == M_SOLDIERSS) + return M_MonsterHasClearShotFromFlash(self, MZ2_SOLDIER_SHOTGUN_8); + if (self->mtype == M_SOLDIER_RIPPER) + return M_MonsterHasClearShotFromFlash(self, MZ2_SOLDIER_RIPPER_8); + if (self->mtype == M_SOLDIER_BLUEBLASTER) + return M_MonsterHasClearShotFromFlash(self, MZ2_SOLDIER_HYPERGUN_8); + if (self->mtype == M_SOLDIER_LASER) + return soldier_has_laser_shot(self, MZ2_SOLDIER_MACHINEGUN_4); + + return false; +} + static void soldier_fireblaster_flash_ex(edict_t* self, int flash, qboolean raw_origin) { int damage, speed; @@ -302,6 +360,9 @@ static void soldier_fireblaster_flash_ex(edict_t* self, int flash, qboolean raw_ } else MonsterAim(self, M_PROJECTILE_ACC, speed, false, flash, forward, start); + if (!M_MonsterHasClearShotFrom(self, start)) + return; + monster_fire_blaster(self, start, forward, damage, speed, EF_BLASTER, BLASTER_PROJ_BOLT, 2.0, true, flash); } @@ -337,6 +398,9 @@ static void soldier_firerocket_flash_ex(edict_t* self, int flash, qboolean raw_o } else MonsterAim(self, M_PROJECTILE_ACC, speed, true, flash, forward, start); + if (!M_MonsterHasClearShotFrom(self, start)) + return; + monster_fire_rocket(self, start, forward, damage, speed, flash); } @@ -362,6 +426,9 @@ static void soldier_fireshotgun_flash(edict_t* self, int flash) if (M_SHOTGUN_DMG_MAX && damage > M_SHOTGUN_DMG_MAX) damage = M_SHOTGUN_DMG_MAX; MonsterAim(self, M_HITSCAN_INSTANT_ACC, 0, false, flash, forward, start); + if (!M_MonsterHasClearShotFrom(self, start)) + return; + monster_fire_shotgun(self, start, forward, damage, 15, 375, 375, 10, flash); } @@ -391,6 +458,9 @@ static void soldier_fireionripper_ex(edict_t* self, int flash_number, qboolean r } else MonsterAim(self, M_PROJECTILE_ACC, speed, false, flash, forward, start); + if (!M_MonsterHasClearShotFrom(self, start)) + return; + monster_fire_ionripper(self, start, forward, damage, speed, EF_IONRIPPER, flash); } @@ -425,6 +495,9 @@ static void soldier_fireblueblaster_ex(edict_t* self, int flash_number, qboolean } else MonsterAim(self, M_PROJECTILE_ACC, speed, false, flash, forward, start); + if (!M_MonsterHasClearShotFrom(self, start)) + return; + monster_fire_blueblaster(self, start, forward, damage, speed, EF_BLUEHYPERBLASTER, flash); } @@ -474,6 +547,8 @@ void soldier_firelaser(edict_t* self, int flash_number) if (!G_EntExists(self->enemy)) return; + if (!soldier_has_laser_shot(self, flash_number)) + return; self->radius_dmg = flash_number; damage = M_DABEAM_DMG_BASE + M_DABEAM_DMG_ADDON * drone_damagelevel(self); @@ -509,6 +584,77 @@ void m_soldier_hyperripper_run_fire(edict_t *self) soldier_fireblueblaster(self, 7); } +static qboolean m_soldier_can_duck_shot(edict_t *self) +{ + const int duck_flash = 2; + + if (self->mtype == M_SOLDIER) + return soldier_has_flash_shot(self, soldier_flash_from_table(soldier_blaster_flash, + sizeof(soldier_blaster_flash) / sizeof(soldier_blaster_flash[0]), duck_flash), false); + if (self->mtype == M_SOLDIERLT) + return soldier_has_flash_shot(self, soldier_flash_from_table(soldier_blaster_flash, + sizeof(soldier_blaster_flash) / sizeof(soldier_blaster_flash[0]), duck_flash), false); + if (self->mtype == M_SOLDIERSS) + return soldier_has_flash_shot(self, soldier_flash_from_table(soldier_shotgun_flash, + sizeof(soldier_shotgun_flash) / sizeof(soldier_shotgun_flash[0]), duck_flash), false); + if (self->mtype == M_SOLDIER_RIPPER) + return soldier_has_flash_shot(self, soldier_flash_from_table(soldier_ripper_flash, + sizeof(soldier_ripper_flash) / sizeof(soldier_ripper_flash[0]), duck_flash), false); + if (self->mtype == M_SOLDIER_BLUEBLASTER) + return soldier_has_flash_shot(self, soldier_flash_from_table(soldier_hyper_flash, + sizeof(soldier_hyper_flash) / sizeof(soldier_hyper_flash[0]), duck_flash), false); + if (self->mtype == M_SOLDIER_LASER) + return soldier_has_laser_shot(self, soldier_flash_from_table(soldier_machinegun_flash, + sizeof(soldier_machinegun_flash) / sizeof(soldier_machinegun_flash[0]), duck_flash)); + + return false; +} + +static void m_soldier_fire_duck(edict_t *self) +{ + const int duck_flash = 2; + + if (!G_EntExists(self->enemy)) + return; + + if (self->mtype == M_SOLDIER) + soldier_fireblaster_flash(self, soldier_flash_from_table(soldier_blaster_flash, + sizeof(soldier_blaster_flash) / sizeof(soldier_blaster_flash[0]), duck_flash)); + else if (self->mtype == M_SOLDIERLT) + soldier_firerocket_flash(self, soldier_flash_from_table(soldier_blaster_flash, + sizeof(soldier_blaster_flash) / sizeof(soldier_blaster_flash[0]), duck_flash)); + else if (self->mtype == M_SOLDIERSS) + soldier_fireshotgun_flash(self, soldier_flash_from_table(soldier_shotgun_flash, + sizeof(soldier_shotgun_flash) / sizeof(soldier_shotgun_flash[0]), duck_flash)); + else if (self->mtype == M_SOLDIER_RIPPER) + soldier_fireionripper(self, duck_flash); + else if (self->mtype == M_SOLDIER_BLUEBLASTER) + soldier_fireblueblaster(self, duck_flash); + else if (self->mtype == M_SOLDIER_LASER) + soldier_firelaser(self, soldier_flash_from_table(soldier_machinegun_flash, + sizeof(soldier_machinegun_flash) / sizeof(soldier_machinegun_flash[0]), duck_flash)); +} + +static void m_soldier_attack3_refire(edict_t *self) +{ + if (level.time + 0.4f < self->monsterinfo.pausetime && m_soldier_can_duck_shot(self)) + self->monsterinfo.nextframe = FRAME_attak303; +} + +mframe_t m_soldier_frames_attack3 [] = +{ + ai_charge, 0, m_soldier_duck_down, + ai_charge, 0, NULL, + ai_charge, 0, m_soldier_fire_duck, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, m_soldier_attack3_refire, + ai_charge, 0, m_soldier_duck_up, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +static mmove_t m_soldier_move_attack3 = {FRAME_attak301, FRAME_attak309, m_soldier_frames_attack3, m_soldier_run}; + mframe_t m_soldier_frames_runandshoot [] = { drone_ai_run, 25, NULL, //109 @@ -530,7 +676,7 @@ mmove_t m_soldier_move_runandshoot = {FRAME_runs01, FRAME_runs14, m_soldier_fram void m_soldier_runandshoot_continue (edict_t* self) { - if (M_ContinueAttack(self, &m_soldier_move_runandshoot, NULL, 0, 512, 0.9)) + if (m_soldier_can_primary_shot(self) && M_ContinueAttack(self, &m_soldier_move_runandshoot, NULL, 0, 512, 0.9)) return; // end attack @@ -546,7 +692,8 @@ void m_soldier_attack1_refire1 (edict_t* self) { // continue firing if the enemy is still close, or we are standing ground if (G_ValidTarget(self, self->enemy, true, true) && (random() <= 0.9) - && ((entdist(self, self->enemy) <= 512) || (self->monsterinfo.aiflags & AI_STAND_GROUND))) + && ((entdist(self, self->enemy) <= 512) || (self->monsterinfo.aiflags & AI_STAND_GROUND)) + && m_soldier_can_primary_shot(self)) self->s.frame = FRAME_attak102; M_DelayNextAttack(self, 0, true); @@ -590,9 +737,16 @@ void m_soldier_endattack_laser(edict_t* self) void m_soldier_firelaser_frame(edict_t* self) { - if (!G_EntExists(self->enemy) || !visible(self, self->enemy)) + if (!G_EntExists(self->enemy) || !visible(self, self->enemy) + || !soldier_has_laser_shot(self, MZ2_SOLDIER_MACHINEGUN_4)) { self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + self->monsterinfo.pausetime = 0; + if (self->beam && self->beam->inuse) + { + self->beam->prethink = NULL; + self->beam->nextthink = level.time + FRAMETIME; + } return; } @@ -628,6 +782,9 @@ mmove_t m_soldier_move_attack_laser = {FRAME_attak401, FRAME_attak406, m_soldier void m_soldier_attack(edict_t* self) { + if (!m_soldier_can_primary_shot(self)) + return; + if (self->mtype == M_SOLDIER_LASER) { self->monsterinfo.currentmove = &m_soldier_move_attack_laser; @@ -763,10 +920,16 @@ void m_soldier_jump_hold (edict_t *self) } } +static void m_soldier_jump_hold_ai(edict_t *self, float dist) +{ + ai_move(self, dist); + m_soldier_jump_hold(self); +} + mframe_t m_soldier_frames_jump [] = { ai_move, 0, m_soldier_jump2_takeoff, - ai_move, 0, m_soldier_jump_hold, + m_soldier_jump_hold_ai, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL @@ -776,6 +939,7 @@ mmove_t m_soldier_move_jump = {FRAME_duck01, FRAME_duck05, m_soldier_frames_jump static qboolean m_soldier_prone_shoot_ok(edict_t *self) { vec3_t forward, diff; + const int prone_flash = 8; if (!G_EntIsAlive(self->enemy)) return false; @@ -785,8 +949,29 @@ static qboolean m_soldier_prone_shoot_ok(edict_t *self) diff[2] = 0; if (VectorNormalize(diff) == 0) return false; + if (DotProduct(forward, diff) < 0.80f) + return false; + + if (self->mtype == M_SOLDIER) + return soldier_has_flash_shot(self, soldier_flash_from_table(soldier_blaster_flash, + sizeof(soldier_blaster_flash) / sizeof(soldier_blaster_flash[0]), prone_flash), true); + if (self->mtype == M_SOLDIERLT) + return soldier_has_flash_shot(self, soldier_flash_from_table(soldier_blaster_flash, + sizeof(soldier_blaster_flash) / sizeof(soldier_blaster_flash[0]), prone_flash), true); + if (self->mtype == M_SOLDIERSS) + return soldier_has_flash_shot(self, soldier_flash_from_table(soldier_shotgun_flash, + sizeof(soldier_shotgun_flash) / sizeof(soldier_shotgun_flash[0]), prone_flash), false); + if (self->mtype == M_SOLDIER_RIPPER) + return soldier_has_flash_shot(self, soldier_flash_from_table(soldier_ripper_flash, + sizeof(soldier_ripper_flash) / sizeof(soldier_ripper_flash[0]), prone_flash), true); + if (self->mtype == M_SOLDIER_BLUEBLASTER) + return soldier_has_flash_shot(self, soldier_flash_from_table(soldier_hyper_flash, + sizeof(soldier_hyper_flash) / sizeof(soldier_hyper_flash[0]), prone_flash), true); + if (self->mtype == M_SOLDIER_LASER) + return soldier_has_laser_shot(self, soldier_flash_from_table(soldier_machinegun_flash, + sizeof(soldier_machinegun_flash) / sizeof(soldier_machinegun_flash[0]), prone_flash)); - return DotProduct(forward, diff) >= 0.80f; + return false; } static void m_soldier_stand_up(edict_t *self) @@ -949,7 +1134,16 @@ void m_soldier_dodge (edict_t *self, edict_t *attacker, vec3_t dir, int radius) } else { - m_soldier_start_duck_dodge(self, 1.10f); + if (m_soldier_can_duck_shot(self) && random() < 0.5f) + { + self->monsterinfo.nextattack = 0; + self->monsterinfo.pausetime = level.time + 1.10f; + self->monsterinfo.currentmove = &m_soldier_move_attack3; + m_soldier_duck_down(self); + self->monsterinfo.dodge_time = level.time + 1.25f; + } + else + m_soldier_start_duck_dodge(self, 1.10f); } return; } @@ -990,7 +1184,6 @@ static void m_soldier_death_shrink(edict_t *self) gi.linkentity(self); } - mframe_t soldier_frames_pain_short1[] = { ai_move, 0, NULL, @@ -1274,7 +1467,7 @@ mframe_t m_soldier_frames_death4 [] = ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, - ai_move, 0, m_soldier_death_shrink, + ai_move, 0, m_soldier_death_shrink, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, diff --git a/src/entities/drone/drone_stalker.c b/src/entities/drone/drone_stalker.c index 8e8e3f10..073f80d1 100644 --- a/src/entities/drone/drone_stalker.c +++ b/src/entities/drone/drone_stalker.c @@ -305,10 +305,16 @@ static void stalker_jump_wait_land(edict_t *self) self->monsterinfo.aiflags |= AI_HOLD_FRAME; } +static void stalker_jump_wait_land_ai(edict_t *self, float dist) +{ + ai_move(self, dist); + stalker_jump_wait_land(self); +} + mframe_t stalker_frames_jump_straightup[] = { ai_move, 1, stalker_jump_straightup, - ai_move, 1, stalker_jump_wait_land, + stalker_jump_wait_land_ai, 1, NULL, ai_move, -1, NULL, ai_move, -1, NULL }; @@ -377,7 +383,7 @@ static qboolean stalker_start_dodge_slide(edict_t *self, edict_t *attacker, vec3 return true; } -static void stalker_fire_ionripper(edict_t *self) +static void stalker_fire(edict_t *self) { int damage, speed; vec3_t forward, right, start, target, dir, offset; @@ -393,24 +399,25 @@ static void stalker_fire_ionripper(edict_t *self) speed = M_BLASTER2_SPEED_MAX; AngleVectors(self->s.angles, forward, right, NULL); - VectorSet(offset, 16, 0, 6); + VectorCopy(monster_flash_offset[MZ2_STALKER_BLASTER], offset); G_ProjectSource(self->s.origin, offset, forward, right, start); + if (!M_MonsterHasClearShotFrom(self, start)) + return; VectorCopy(self->enemy->s.origin, target); target[2] += self->enemy->viewheight; VectorSubtract(target, start, dir); VectorNormalize(dir); - monster_fire_blaster2(self, start, dir, damage, speed, EF_BLASTER, MZ2_STALKER_BLASTER); - + monster_fire_blaster2(self, start, dir, damage, speed, EF_BLASTER, MZ2_STALKER_BLASTER); } mframe_t stalker_frames_shoot[] = { drone_ai_run, 10, NULL, - drone_ai_run, 10, stalker_fire_ionripper, - drone_ai_run, 12, stalker_fire_ionripper, - drone_ai_run, 12, stalker_fire_ionripper + drone_ai_run, 10, stalker_fire, + drone_ai_run, 12, stalker_fire, + drone_ai_run, 12, stalker_fire }; mmove_t stalker_move_shoot = { FRAME_run01, FRAME_run04, stalker_frames_shoot, stalker_run }; diff --git a/src/entities/drone/drone_tank.c b/src/entities/drone/drone_tank.c index e43fdf53..14c19de9 100644 --- a/src/entities/drone/drone_tank.c +++ b/src/entities/drone/drone_tank.c @@ -70,6 +70,21 @@ static void mytank_slam_effect(vec3_t origin) gi.multicast(origin, MULTICAST_PHS); } +static qboolean mytank_can_blaster(edict_t *self) +{ + return M_MonsterHasClearShotFromFlash(self, MZ2_TANK_BLASTER_1); +} + +static qboolean mytank_can_rocket(edict_t *self) +{ + return M_MonsterHasClearShotFromFlash(self, MZ2_TANK_ROCKET_1); +} + +static qboolean mytank_can_chain(edict_t *self) +{ + return M_MonsterHasClearShotFromFlash(self, MZ2_TANK_MACHINEGUN_5); +} + void mytank_idle (edict_t *self) { int range; @@ -278,6 +293,9 @@ void myTankRail (edict_t *self) damage = M_RAILGUN_DMG_MAX; MonsterAim(self, M_HITSCAN_INSTANT_ACC, 0, false, flash_number, forward, start); + if (!M_MonsterHasClearShotFrom(self, start)) + return; + monster_fire_railgun(self, start, forward, damage, damage, flash_number); } @@ -309,6 +327,9 @@ void myTankBlaster(edict_t* self) speed = M_BLASTER_SPEED_MAX; MonsterAim(self, M_PROJECTILE_ACC, speed, false, flash_number, forward, start); + if (!M_MonsterHasClearShotFrom(self, start)) + return; + monster_fire_blaster(self, start, forward, damage, speed, EF_BLASTER, BLASTER_PROJ_BOLT, 2.0, true, flash_number); } @@ -341,6 +362,8 @@ void myTankRocket(edict_t* self) speed = M_ROCKETLAUNCHER_SPEED_MAX; MonsterAim(self, M_PROJECTILE_ACC, speed, true, flash_number, forward, start); + if (!M_MonsterHasClearShotFrom(self, start)) + return; monster_fire_rocket(self, start, forward, damage, speed, flash_number); } @@ -381,6 +404,9 @@ void myTankMachineGun(edict_t* self) AngleVectors(dir, forward, NULL, NULL); + if (!M_MonsterHasClearShotFrom(self, start)) + return; + monster_fire_bullet(self, start, forward, damage, 40, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flash_number); } @@ -821,6 +847,8 @@ void commander_attack (edict_t *self) { const float r = random(); float range = entdist(self, self->enemy); + qboolean can_blast; + qboolean can_rocket; // short range attack if (range <= 128 && r <= 0.6) @@ -846,18 +874,30 @@ void commander_attack (edict_t *self) } } + can_blast = mytank_can_blaster(self); + can_rocket = mytank_can_rocket(self); + // medium range attack if (range <= 512) { - if (r <= 0.2) + if (r <= 0.2 && can_blast) self->monsterinfo.currentmove = &mytank_move_attack_blast; - else + else if (can_rocket) self->monsterinfo.currentmove = &mytank_move_attack_fire_rocket; + else if (can_blast) + self->monsterinfo.currentmove = &mytank_move_attack_blast; + else + return; } // long range attack else { - self->monsterinfo.currentmove = &mytank_move_attack_blast; + if (can_blast) + self->monsterinfo.currentmove = &mytank_move_attack_blast; + else if (can_rocket) + self->monsterinfo.currentmove = &mytank_move_attack_fire_rocket; + else + return; } } @@ -869,6 +909,9 @@ void tank_attack(edict_t* self) { const float r = random(); const float range = entdist(self, self->enemy); + const qboolean can_blast = mytank_can_blaster(self); + const qboolean can_rocket = mytank_can_rocket(self); + const qboolean can_chain = mytank_can_chain(self); //gi.dprintf("%d tank_attack()\n", level.framenum); @@ -881,29 +924,45 @@ void tank_attack(edict_t* self) } else { - if (r <= 0.2) + if (r <= 0.2 && can_blast) self->monsterinfo.currentmove = &mytank_move_attack_blast; - else + else if (can_rocket) self->monsterinfo.currentmove = &mytank_move_attack_fire_rocket; + else if (can_blast) + self->monsterinfo.currentmove = &mytank_move_attack_blast; + else if (can_chain) + self->monsterinfo.currentmove = &mytank_move_attack_chain; + else + return; } } // medium range attack (20% chain, 40% blaster, 40% rocket) else if (range <= 512) { - if (r <= 0.2) + if (r <= 0.2 && can_chain) self->monsterinfo.currentmove = &mytank_move_attack_chain; - else if (r <= 0.6) + else if (r <= 0.6 && can_blast) self->monsterinfo.currentmove = &mytank_move_attack_blast; - else + else if (can_rocket) self->monsterinfo.currentmove = &mytank_move_attack_fire_rocket; + else if (can_blast) + self->monsterinfo.currentmove = &mytank_move_attack_blast; + else if (can_chain) + self->monsterinfo.currentmove = &mytank_move_attack_chain; + else + return; } // long range attack (20% blaster, 80% chain) else { - if (r <= 0.2) + if (r <= 0.2 && can_blast) self->monsterinfo.currentmove = &mytank_move_attack_blast; - else + else if (can_chain) self->monsterinfo.currentmove = &mytank_move_attack_chain; + else if (can_blast) + self->monsterinfo.currentmove = &mytank_move_attack_blast; + else + return; } M_DelayNextAttack(self, 0, true); diff --git a/src/entities/drone/drone_widow.c b/src/entities/drone/drone_widow.c index 8163c7b4..f6c8dfcf 100644 --- a/src/entities/drone/drone_widow.c +++ b/src/entities/drone/drone_widow.c @@ -54,6 +54,7 @@ static int shotsfired; void drone_ai_stand(edict_t *self, float dist); void drone_ai_run(edict_t *self, float dist); void drone_ai_walk(edict_t *self, float dist); +qboolean drone_findtarget(edict_t *self, qboolean force); static void widow_stand(edict_t *self); static void widow_walk(edict_t *self); @@ -413,6 +414,35 @@ static void widow_cleanup_failed_spawn(edict_t *owner, edict_t *spawned) G_FreeEdict(spawned); } +static void widow_setup_invasion_spawn(edict_t *spawned) +{ + if (!invasion->value) + return; + + spawned->monsterinfo.aiflags &= ~AI_STAND_GROUND; + spawned->monsterinfo.aiflags |= AI_FIND_NAVI; + spawned->prev_navi = NULL; + spawned->goalentity = NULL; +} + +static void widow_start_spawned_monster(edict_t *self, edict_t *spawned) +{ + const qboolean force_start = invasion->value || pvm->value; + + if (G_ValidTarget(spawned, self->enemy, !force_start, true)) + { + spawned->enemy = self->enemy; + VectorCopy(self->enemy->s.origin, spawned->monsterinfo.last_sighting); + } + else if (force_start) + drone_findtarget(spawned, true); + + if ((spawned->enemy || spawned->goalentity) && spawned->monsterinfo.run) + spawned->monsterinfo.run(spawned); + else if (spawned->monsterinfo.stand) + spawned->monsterinfo.stand(spawned); +} + static qboolean widow_spawn_stalker(edict_t *self, int index) { edict_t *owner; @@ -444,26 +474,13 @@ static qboolean widow_spawn_stalker(edict_t *self, int index) VectorCopy(self->s.angles, spawned->s.angles); spawned->nextthink = level.time + FRAMETIME; spawned->monsterinfo.attack_finished = level.time + 1.0f; - - if (invasion->value) - { - spawned->monsterinfo.aiflags &= ~AI_STAND_GROUND; - spawned->monsterinfo.aiflags |= AI_FIND_NAVI; - spawned->prev_navi = NULL; - spawned->goalentity = NULL; - } - - if (G_ValidTarget(spawned, self->enemy, true, true)) - spawned->enemy = self->enemy; + widow_setup_invasion_spawn(spawned); gi.linkentity(spawned); owner->num_monsters += spawned->monsterinfo.control_cost; owner->num_monsters_real++; - if (spawned->enemy && spawned->monsterinfo.run) - spawned->monsterinfo.run(spawned); - else if (spawned->monsterinfo.stand) - spawned->monsterinfo.stand(spawned); + widow_start_spawned_monster(self, spawned); return true; } @@ -503,6 +520,19 @@ static void widow_finish_spawn(edict_t *self) self->monsterinfo.melee_finished = level.time + WIDOW_SUMMON_COOLDOWN; } +static qboolean widow_can_spawn_stalker(edict_t *self) +{ + vec3_t spot; + + for (int i = 0; i < WIDOW_SUMMON_COUNT; i++) + { + if (widow_find_spawn_spot(self, i, spot)) + return true; + } + + return false; +} + static void widow_start_spawn(edict_t *self) { gi.sound(self, CHAN_WEAPON, sound_spawn, 1, ATTN_NORM, 0); @@ -530,10 +560,10 @@ static void widow_attack(edict_t *self) return; r = random(); - if (widow_can_melee(self) && r < 0.40f) - widow_melee(self); - else if (level.time >= self->monsterinfo.melee_finished && r < 0.65f) + if (level.time >= self->monsterinfo.melee_finished && widow_can_spawn_stalker(self)) widow_start_spawn(self); + else if (widow_can_melee(self) && r < 0.40f) + widow_melee(self); else if (r < 0.82f) { if (random() < 0.33f) @@ -605,7 +635,7 @@ static void widow_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); self->deadflag = DEAD_DEAD; self->takedamage = DAMAGE_YES; - vrx_update_drone_death_skin(self); + vrx_update_drone_death_skin(self); self->monsterinfo.currentmove = &widow_move_death; } diff --git a/src/entities/drone/drone_widow2.c b/src/entities/drone/drone_widow2.c index 82bd93c5..e40d32ba 100644 --- a/src/entities/drone/drone_widow2.c +++ b/src/entities/drone/drone_widow2.c @@ -60,6 +60,7 @@ static vec3_t widow2_tongue_offsets[] = void drone_ai_stand(edict_t *self, float dist); void drone_ai_run(edict_t *self, float dist); void drone_ai_walk(edict_t *self, float dist); +qboolean drone_findtarget(edict_t *self, qboolean force); static void widow2_ai_walk(edict_t *self, float dist) { @@ -422,10 +423,8 @@ static qboolean widow2_draw_proboscis(edict_t *self, vec3_t start, vec3_t end) return true; } -static void widow2_pull_enemy(edict_t *self) +static void widow2_prepare_pulled_enemy(edict_t *self) { - vec3_t pull; - if (!G_EntExists(self->enemy)) return; @@ -434,40 +433,79 @@ static void widow2_pull_enemy(edict_t *self) self->enemy->s.origin[2] += 1; self->enemy->groundentity = NULL; } +} + +static int widow2_proboscis_damage(edict_t *self) +{ + int damage; - VectorSubtract(self->s.origin, self->enemy->s.origin, pull); - VectorNormalize(pull); - VectorMA(self->enemy->velocity, 700, pull, self->enemy->velocity); + damage = M_MELEE_DMG_BASE + M_MELEE_DMG_ADDON * drone_damagelevel(self); + if (M_MELEE_DMG_MAX && damage > M_MELEE_DMG_MAX) + damage = M_MELEE_DMG_MAX; + + damage = max(1, damage / 4); + return vrx_increase_monster_damage_by_talent(self->activator, damage); } -static void widow2_show_proboscis(edict_t *self) +static int widow2_proboscis_pull(edict_t *self) { - vec3_t start, end; + int pull; - widow2_draw_proboscis(self, start, end); + pull = PARASITE_INITIAL_KNOCKBACK + PARASITE_ADDON_KNOCKBACK * drone_damagelevel(self); + if (PARASITE_MAX_KNOCKBACK && pull < PARASITE_MAX_KNOCKBACK) + pull = PARASITE_MAX_KNOCKBACK; + if (G_EntExists(self->enemy) && self->enemy->groundentity) + pull *= 2; + + return pull; } -static void widow2_pull_proboscis(edict_t *self) +static void widow2_heal_from_proboscis(edict_t *self, int damage) { - vec3_t start, end; + if (self->health >= self->max_health) + return; - if (widow2_draw_proboscis(self, start, end)) - widow2_pull_enemy(self); + self->health += damage; + if (self->health > self->max_health) + self->health = self->max_health; } -static void widow2_melee_hit(edict_t *self) +static void widow2_drain_proboscis(edict_t *self) { int damage; - vec3_t start, end; + int pull; + vec3_t start, end, dir; if (!widow2_draw_proboscis(self, start, end)) return; - widow2_pull_enemy(self); + damage = widow2_proboscis_damage(self); + pull = widow2_proboscis_pull(self); + widow2_prepare_pulled_enemy(self); + widow2_heal_from_proboscis(self, damage); - damage = M_MELEE_DMG_BASE + M_MELEE_DMG_ADDON * drone_damagelevel(self); - if (M_MeleeAttack(self, self->enemy, WIDOW2_MELEE_RANGE, damage, 500)) - gi.sound(self, CHAN_WEAPON, sound_hit, 1, ATTN_NORM, 0); + if (self->s.frame == WIDOW2_FRAME_tongs01 + 3) + gi.sound(self->enemy, CHAN_AUTO, sound_hit, 1, ATTN_NORM, 0); + + VectorSubtract(end, start, dir); + T_Damage(self->enemy, self, self, dir, self->enemy->s.origin, end, damage, pull, DAMAGE_NO_ABILITIES, MOD_UNKNOWN); +} + +static void widow2_show_proboscis(edict_t *self) +{ + vec3_t start, end; + + widow2_draw_proboscis(self, start, end); +} + +static void widow2_pull_proboscis(edict_t *self) +{ + widow2_drain_proboscis(self); +} + +static void widow2_melee_hit(edict_t *self) +{ + widow2_drain_proboscis(self); } static qboolean widow2_valid_spawn_spot(edict_t *self, vec3_t mins, vec3_t maxs, vec3_t spot) @@ -521,6 +559,35 @@ static void widow2_cleanup_failed_spawn(edict_t *owner, edict_t *spawned) G_FreeEdict(spawned); } +static void widow2_setup_invasion_spawn(edict_t *spawned) +{ + if (!invasion->value) + return; + + spawned->monsterinfo.aiflags &= ~AI_STAND_GROUND; + spawned->monsterinfo.aiflags |= AI_FIND_NAVI; + spawned->prev_navi = NULL; + spawned->goalentity = NULL; +} + +static void widow2_start_spawned_monster(edict_t *self, edict_t *spawned) +{ + const qboolean force_start = invasion->value || pvm->value; + + if (G_ValidTarget(spawned, self->enemy, !force_start, true)) + { + spawned->enemy = self->enemy; + VectorCopy(self->enemy->s.origin, spawned->monsterinfo.last_sighting); + } + else if (force_start) + drone_findtarget(spawned, true); + + if ((spawned->enemy || spawned->goalentity) && spawned->monsterinfo.run) + spawned->monsterinfo.run(spawned); + else if (spawned->monsterinfo.stand) + spawned->monsterinfo.stand(spawned); +} + static qboolean widow2_spawn_stalker(edict_t *self, int index) { edict_t *owner; @@ -552,26 +619,13 @@ static qboolean widow2_spawn_stalker(edict_t *self, int index) VectorCopy(self->s.angles, spawned->s.angles); spawned->nextthink = level.time + FRAMETIME; spawned->monsterinfo.attack_finished = level.time + 1.0f; - - if (invasion->value) - { - spawned->monsterinfo.aiflags &= ~AI_STAND_GROUND; - spawned->monsterinfo.aiflags |= AI_FIND_NAVI; - spawned->prev_navi = NULL; - spawned->goalentity = NULL; - } - - if (G_ValidTarget(spawned, self->enemy, true, true)) - spawned->enemy = self->enemy; + widow2_setup_invasion_spawn(spawned); gi.linkentity(spawned); owner->num_monsters += spawned->monsterinfo.control_cost; owner->num_monsters_real++; - if (spawned->enemy && spawned->monsterinfo.run) - spawned->monsterinfo.run(spawned); - else if (spawned->monsterinfo.stand) - spawned->monsterinfo.stand(spawned); + widow2_start_spawned_monster(self, spawned); return true; } @@ -611,6 +665,19 @@ static void widow2_finish_spawn(edict_t *self) self->monsterinfo.melee_finished = level.time + WIDOW2_SUMMON_COOLDOWN; } +static qboolean widow2_can_spawn_stalker(edict_t *self) +{ + vec3_t spot; + + for (int i = 0; i < WIDOW2_SUMMON_COUNT; i++) + { + if (widow2_find_spawn_spot(self, i, spot)) + return true; + } + + return false; +} + static void widow2_start_spawn(edict_t *self) { gi.sound(self, CHAN_WEAPON, sound_spawn, 1, ATTN_NORM, 0); @@ -638,10 +705,10 @@ static void widow2_attack(edict_t *self) return; r = random(); - if (widow2_can_melee(self) && r < 0.35f) - widow2_melee(self); - else if (level.time >= self->monsterinfo.melee_finished && r < 0.60f) + if (level.time >= self->monsterinfo.melee_finished && widow2_can_spawn_stalker(self)) widow2_start_spawn(self); + else if (widow2_can_melee(self) && r < 0.35f) + widow2_melee(self); else if (r < 0.80f) self->monsterinfo.currentmove = &widow2_move_disruptor; else diff --git a/src/g_local.h b/src/g_local.h index 4f6bb17c..93dc82fb 100644 --- a/src/g_local.h +++ b/src/g_local.h @@ -629,7 +629,7 @@ typedef struct { vec3_t last_sighting; // last known position of enemy bool last_sighting_is_navi; // is the last known position of enemy a navigation point - // int attack_state; + int attack_state; int lefty; float idle_delay; // how often idle func is called int idle_frames; // number of frames monster has been idle @@ -1664,13 +1664,14 @@ enum dronespawn_t { DS_FIXBOT_BOSS = 45, DS_ROGUE_TURRET = 46, DS_BOSS2 = 47, - DS_BOSS2_HYPER = 48, - DS_BOSS2_SMALL = 49, + DS_BOSS2_HYPER = 48, // unused boss, same attacks than small hornet + DS_BOSS2_SMALL = 49, // not a boss, small hornet DS_BOSS5 = 50, }; edict_t *vrx_create_new_drone(edict_t *ent, enum dronespawn_t drone_type, qboolean worldspawn, qboolean link_now, int bonus_level); +qboolean vrx_drone_spawn_is_boss(enum dronespawn_t drone_type); edict_t * vrx_create_drone_from_ent(edict_t *drone, edict_t *ent, enum dronespawn_t drone_type, qboolean worldspawn, qboolean link_now, @@ -2823,6 +2824,11 @@ void Check_full(edict_t *ent); void MonsterAim(edict_t *self, float accuracy, int projectile_speed, qboolean rocket, int flash_number, vec3_t forward, vec3_t start); +qboolean M_MonsterHasCombatSight(edict_t *self, edict_t *other); +qboolean M_MonsterFindClearShot(edict_t *self, vec3_t start, vec3_t point); +qboolean M_MonsterHasClearShotFrom(edict_t *self, vec3_t start); +qboolean M_MonsterHasClearShotFromFlash(edict_t *self, int flash_number); +void M_MonsterBlockedShot(edict_t *self, float delay); float entdist(const edict_t *ent1, const edict_t *ent2); diff --git a/src/gamemodes/invasion.c b/src/gamemodes/invasion.c index bc2cf784..41bad7c6 100644 --- a/src/gamemodes/invasion.c +++ b/src/gamemodes/invasion.c @@ -607,7 +607,7 @@ void vrx_inv_spawn_boss(edict_t *self, int index) { if (invasion_data.boss) return; - if (index < 30) + if (!vrx_drone_spawn_is_boss((enum dronespawn_t)index)) return; while ((spawn = vrx_inv_get_monster_spawn(spawn))) { From d791b77fac96c72ea72356da3da314d3d47d190e Mon Sep 17 00:00:00 2001 From: Enemy-123 Date: Wed, 29 Apr 2026 22:49:22 -0400 Subject: [PATCH 21/24] Adding remaster's AI_ALTERNATE_FLY. and improved chasing mechanic for flying monsters and gekk, mainly a "drone_move.c" update --- src/entities/drone/drone_ai.c | 28 +- src/entities/drone/drone_boss2.c | 26 + src/entities/drone/drone_carrier.c | 12 + src/entities/drone/drone_daedalus.c | 11 + src/entities/drone/drone_fixbot.c | 23 +- src/entities/drone/drone_float.c | 12 + src/entities/drone/drone_flyer.c | 26 + src/entities/drone/drone_gekk.c | 14 + src/entities/drone/drone_hover.c | 11 + src/entities/drone/drone_misc.c | 113 +- src/entities/drone/drone_move.c | 1529 ++++++++++++++++++++++++++- src/g_local.h | 17 + src/quake2/g_phys.c | 6 +- 13 files changed, 1811 insertions(+), 17 deletions(-) diff --git a/src/entities/drone/drone_ai.c b/src/entities/drone/drone_ai.c index 0c104a2c..9cc2618f 100644 --- a/src/entities/drone/drone_ai.c +++ b/src/entities/drone/drone_ai.c @@ -201,6 +201,7 @@ void ai_charge (edict_t *self, float dist) { edict_t *target; vec3_t v; + float ofs; if (!self || !self->inuse) return; @@ -246,6 +247,23 @@ void ai_charge (edict_t *self, float dist) self->ideal_yaw = vectoyaw(v); M_ChangeYaw (self); + if (self->monsterinfo.aiflags & AI_ALTERNATE_FLY) + { + if (self->monsterinfo.attack_state == AS_SLIDING) + { + ofs = self->monsterinfo.lefty ? 90 : -90; + if (M_walkmove(self, self->ideal_yaw + ofs, dist)) + return; + + self->monsterinfo.lefty = 1 - self->monsterinfo.lefty; + M_walkmove(self, self->ideal_yaw - ofs, dist); + } + else + { + M_walkmove(self, self->s.angles[YAW], dist); + } + } + if (entdist(self, self->enemy) <= MELEE_DISTANCE * 2.5f) M_ChangeYaw(self); } @@ -749,6 +767,9 @@ void drone_ai_idle (edict_t *self) // called when a the drone finds a new target void drone_newtarget(edict_t* self) { + if (self->monsterinfo.aiflags & AI_ALTERNATE_FLY) + self->monsterinfo.fly_position_time = 0.0f; + // if monster is not standing ground or enemy is a player if (!(self->monsterinfo.aiflags & AI_STAND_GROUND) || (self->enemy && self->enemy->inuse && self->enemy->client)) @@ -895,7 +916,7 @@ void drone_ai_stand (edict_t *self, float dist) if (DRONE_DEBUG) gi.dprintf("drone_ai_stand()\n"); // used for slight position adjustments for animations - if (dist) + if (dist || (self->monsterinfo.aiflags & AI_ALTERNATE_FLY)) M_walkmove(self, self->s.angles[YAW], dist); if (!self->enemy) @@ -2447,7 +2468,8 @@ void M_UpdateLastSight (edict_t *self) return; // is the goal entity visible? - if (!visible(self, goal)) + if (!visible(self, goal) && + (!(self->monsterinfo.aiflags & AI_ALTERNATE_FLY) || goal != self->enemy || !M_MonsterHasCombatSight(self, goal))) return; // last sight position has not been reached yet @@ -2582,7 +2604,7 @@ void drone_think (edict_t *self) // don't slide if (self->groundentity) VectorClear(self->velocity); - else if (self->flags & FL_FLY) + else if ((self->flags & FL_FLY) && !(self->monsterinfo.aiflags & AI_ALTERNATE_FLY)) { self->velocity[0] *= 0.8; if (self->velocity[0] < 1) diff --git a/src/entities/drone/drone_boss2.c b/src/entities/drone/drone_boss2.c index ee3a91fb..e8b04112 100644 --- a/src/entities/drone/drone_boss2.c +++ b/src/entities/drone/drone_boss2.c @@ -35,6 +35,11 @@ static qboolean boss2_is_small(const edict_t *self) return self->style == BOSS2_VARIANT_SMALL; } +static qboolean boss2_is_boss(const edict_t *self) +{ + return self->mtype == M_BOSS2 && !boss2_is_small(self); +} + static qboolean boss2_is_hyper(const edict_t *self) { return self->style == BOSS2_VARIANT_HYPER || boss2_is_small(self); @@ -55,6 +60,25 @@ static void boss2_ai_run(edict_t *self, float dist) drone_ai_run(self, dist * boss2_move_scale(self)); } +static void boss2_set_fly_parameters(edict_t *self) +{ + float speed_scale = boss2_move_scale(self); + + self->monsterinfo.fly_thrusters = false; + self->monsterinfo.fly_acceleration = 20.0f * speed_scale; + self->monsterinfo.fly_speed = 120.0f * speed_scale; + if (boss2_is_boss(self)) + { + self->monsterinfo.fly_min_distance = 220.0f; + self->monsterinfo.fly_max_distance = 750.0f; + } + else + { + self->monsterinfo.fly_min_distance = 250.0f; + self->monsterinfo.fly_max_distance = 450.0f; + } +} + static float boss2_voice_attenuation(const edict_t *self) { return boss2_is_small(self) ? ATTN_NORM : ATTN_NONE; @@ -704,6 +728,8 @@ static void init_drone_boss2_common(edict_t *self, int variant) self->style = variant; self->yaw_speed = small ? 80 : 50; self->flags |= FL_FLY | FL_IMMUNE_LASER; + self->monsterinfo.aiflags |= AI_ALTERNATE_FLY; + boss2_set_fly_parameters(self); self->s.sound = gi.soundindex("bosshovr/bhvengn1.wav"); self->s.scale = small ? 0.6f : (invasion->value ? BOSS2_INVASION_SCALE : 1.0f); diff --git a/src/entities/drone/drone_carrier.c b/src/entities/drone/drone_carrier.c index 8bd50de5..951bf52d 100644 --- a/src/entities/drone/drone_carrier.c +++ b/src/entities/drone/drone_carrier.c @@ -56,6 +56,16 @@ static void carrier_project_flash(edict_t *self, int flash, vec3_t forward, vec3 G_ProjectSource(self->s.origin, offset, forward, right, start); } +static void carrier_set_fly_parameters(edict_t *self) +{ + self->monsterinfo.fly_thrusters = false; + self->monsterinfo.fly_acceleration = 20.0f; + self->monsterinfo.fly_speed = 120.0f; + self->monsterinfo.fly_above = false; + self->monsterinfo.fly_min_distance = 375.0f; + self->monsterinfo.fly_max_distance = 650.0f; +} + static void carrier_spawn_ai(edict_t *self, float dist) { if (!self || !self->inuse) @@ -952,6 +962,8 @@ void init_drone_carrier(edict_t *self) self->mtype = M_CARRIER; self->flags |= FL_FLY; self->yaw_speed = CARRIER_YAW_SPEED; + self->monsterinfo.aiflags |= AI_ALTERNATE_FLY; + carrier_set_fly_parameters(self); self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD; self->monsterinfo.power_armor_power = M_CARRIER_INITIAL_ARMOR + M_CARRIER_ADDON_ARMOR * self->monsterinfo.level; diff --git a/src/entities/drone/drone_daedalus.c b/src/entities/drone/drone_daedalus.c index 027ca318..2cdeb211 100644 --- a/src/entities/drone/drone_daedalus.c +++ b/src/entities/drone/drone_daedalus.c @@ -29,6 +29,15 @@ static void daedalus_run(edict_t *self); static void daedalus_reattack(edict_t *self); static void daedalus_fire_grenade(edict_t *self); +static void daedalus_set_fly_parameters(edict_t *self) +{ + self->monsterinfo.fly_thrusters = false; + self->monsterinfo.fly_acceleration = 20.0f; + self->monsterinfo.fly_speed = 120.0f; + self->monsterinfo.fly_min_distance = 270.0f; + self->monsterinfo.fly_max_distance = 390.0f; +} + static void daedalus_sight(edict_t *self, edict_t *other) { gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); @@ -237,6 +246,8 @@ void init_drone_daedalus(edict_t *self) self->mtype = M_DAEDALUS; self->flags |= FL_FLY; + self->monsterinfo.aiflags |= AI_ALTERNATE_FLY; + daedalus_set_fly_parameters(self); self->monsterinfo.power_armor_power = M_DAEDALUS_INITIAL_ARMOR + M_DAEDALUS_ADDON_ARMOR * self->monsterinfo.level; self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD; self->monsterinfo.max_armor = self->monsterinfo.power_armor_power; diff --git a/src/entities/drone/drone_fixbot.c b/src/entities/drone/drone_fixbot.c index 6b8ca1d7..a309597b 100644 --- a/src/entities/drone/drone_fixbot.c +++ b/src/entities/drone/drone_fixbot.c @@ -73,6 +73,25 @@ static void fixbot_ai_walk(edict_t *self, float dist) drone_ai_walk(self, dist * fixbot_move_scale(self)); } +static void fixbot_set_fly_parameters(edict_t *self) +{ + float speed_scale = fixbot_move_scale(self); + + self->monsterinfo.fly_thrusters = false; + self->monsterinfo.fly_acceleration = 20.0f * speed_scale; + self->monsterinfo.fly_speed = 120.0f * speed_scale; + if (fixbot_is_boss(self)) + { + self->monsterinfo.fly_min_distance = 220.0f; + self->monsterinfo.fly_max_distance = 650.0f; + } + else + { + self->monsterinfo.fly_min_distance = 250.0f; + self->monsterinfo.fly_max_distance = 450.0f; + } +} + static int fixbot_count_live_turrets(edict_t *self) { int count = 0; @@ -1379,6 +1398,8 @@ static void init_drone_fixbot_common(edict_t *self, qboolean boss) self->gib_health = -100; self->max_health = self->health; self->flags |= FL_FLY | FL_NO_KNOCKBACK; + self->monsterinfo.aiflags |= AI_ALTERNATE_FLY; + fixbot_set_fly_parameters(self); self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD; self->monsterinfo.max_armor = self->monsterinfo.power_armor_power; self->monsterinfo.sight_range = boss ? 1400 : 1024; @@ -1411,4 +1432,4 @@ void init_drone_fixbot(edict_t *self) void init_drone_fixbot_boss(edict_t *self) { init_drone_fixbot_common(self, true); -} +} \ No newline at end of file diff --git a/src/entities/drone/drone_float.c b/src/entities/drone/drone_float.c index 22812737..3d1c928b 100644 --- a/src/entities/drone/drone_float.c +++ b/src/entities/drone/drone_float.c @@ -42,6 +42,16 @@ void floater_wham (edict_t *self); void floater_zap (edict_t *self); void floater_continue_attack(edict_t* self); +static void floater_set_fly_parameters(edict_t *self) +{ + // Using ranged-only attacks like hover does, so for now flyer and float do not have melee attacks + self->monsterinfo.fly_thrusters = false; + self->monsterinfo.fly_acceleration = 20.0f; + self->monsterinfo.fly_speed = 120.0f; + self->monsterinfo.fly_min_distance = 250.0f; + self->monsterinfo.fly_max_distance = 450.0f; +} + void floater_fire_blaster (edict_t *self) { int damage, speed=2000, effect; @@ -704,6 +714,8 @@ void init_drone_floater (edict_t *self) self->gib_health = -0.6 * BASE_GIB_HEALTH; self->mass = 150; self->mtype = M_FLOATER; + self->monsterinfo.aiflags |= AI_ALTERNATE_FLY; + floater_set_fly_parameters(self); self->monsterinfo.power_armor_power = M_FLOATER_INITIAL_ARMOR + M_FLOATER_ADDON_ARMOR*self->monsterinfo.level; diff --git a/src/entities/drone/drone_flyer.c b/src/entities/drone/drone_flyer.c index fbd3a3e9..c59ce1bc 100644 --- a/src/entities/drone/drone_flyer.c +++ b/src/entities/drone/drone_flyer.c @@ -30,6 +30,17 @@ void flyer_stand (edict_t *self); void flyer_nextmove (edict_t *self); static void flyer_attack_finished(edict_t *self); +static void flyer_set_fly_parameters(edict_t *self) +{ + // Using ranged-only attacks like hover does, so for now flyer and float do not have melee attacks + self->monsterinfo.fly_thrusters = false; + self->monsterinfo.fly_acceleration = 15.0f; + self->monsterinfo.fly_speed = 165.0f; + self->monsterinfo.fly_min_distance = 250.0f; + self->monsterinfo.fly_max_distance = 450.0f; +} + + void flyer_sight (edict_t *self, edict_t *other) { gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); @@ -507,12 +518,21 @@ void flyer_check_melee(edict_t *self) if (entdist (self, self->enemy) == RANGE_MELEE) { if (random() <= 0.8) + { + flyer_set_fly_parameters(self); self->monsterinfo.currentmove = &flyer_move_loop_melee; + } else + { + flyer_set_fly_parameters(self); self->monsterinfo.currentmove = &flyer_move_end_melee; + } } else + { + flyer_set_fly_parameters(self); self->monsterinfo.currentmove = &flyer_move_end_melee; + } } void flyer_loop_melee (edict_t *self) @@ -527,6 +547,8 @@ void flyer_loop_melee (edict_t *self) void flyer_attack (edict_t *self) { + flyer_set_fly_parameters(self); + /* if (random() <= 0.5) self->monsterinfo.currentmove = &flyer_move_attack1; else */ @@ -570,6 +592,7 @@ void flyer_melee (edict_t *self) // flyer.nextmove = ACTION_attack1; // self->monsterinfo.currentmove = &flyer_move_stop; //self->monsterinfo.currentmove = &flyer_move_start_melee; + flyer_set_fly_parameters(self); } void flyer_pain (edict_t *self, edict_t *other, float kick, int damage) @@ -646,6 +669,9 @@ void init_drone_flyer (edict_t *self) self->mtype = M_FLYER; self->flags |= FL_FLY; + self->monsterinfo.aiflags |= AI_ALTERNATE_FLY; + self->monsterinfo.fly_buzzard = true; + flyer_set_fly_parameters(self); self->max_health = self->health; self->monsterinfo.power_armor_power = M_FLYER_INITIAL_ARMOR + M_FLYER_ADDON_ARMOR*self->monsterinfo.level; self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD; diff --git a/src/entities/drone/drone_gekk.c b/src/entities/drone/drone_gekk.c index e6570251..886a0d05 100644 --- a/src/entities/drone/drone_gekk.c +++ b/src/entities/drone/drone_gekk.c @@ -75,6 +75,15 @@ static void gekk_search(edict_t *self) gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_IDLE, 0); } +static void gekk_set_fly_parameters(edict_t *self) +{ + self->monsterinfo.fly_thrusters = false; + self->monsterinfo.fly_acceleration = 25.0f; + self->monsterinfo.fly_speed = 150.0f; + self->monsterinfo.fly_min_distance = 10.0f; + self->monsterinfo.fly_max_distance = 10.0f; +} + static qboolean gekk_should_swim(edict_t *self) { return self->waterlevel >= WATER_WAIST; @@ -82,9 +91,11 @@ static qboolean gekk_should_swim(edict_t *self) static void gekk_set_water_bounds(edict_t *self) { + gekk_set_fly_parameters(self); if (!self->goalentity) self->goalentity = world; self->flags |= FL_SWIM; + self->monsterinfo.aiflags |= AI_ALTERNATE_FLY; self->yaw_speed = 10; self->viewheight = 10; VectorSet(self->mins, -18, -18, -24); @@ -95,6 +106,8 @@ static void gekk_set_water_bounds(edict_t *self) static void gekk_set_land_bounds(edict_t *self) { self->flags &= ~FL_SWIM; + self->monsterinfo.aiflags &= ~AI_ALTERNATE_FLY; + self->monsterinfo.fly_pinned = false; self->yaw_speed = 20; self->viewheight = 25; VectorSet(self->mins, -18, -18, -24); @@ -961,6 +974,7 @@ void init_drone_gekk(edict_t *self) self->monsterinfo.cost = M_MUTANT_COST; self->monsterinfo.jumpdn = 256; self->monsterinfo.jumpup = 88; + gekk_set_fly_parameters(self); self->item = FindItemByClassname("ammo_cells"); diff --git a/src/entities/drone/drone_hover.c b/src/entities/drone/drone_hover.c index c9237768..b83012f8 100644 --- a/src/entities/drone/drone_hover.c +++ b/src/entities/drone/drone_hover.c @@ -47,6 +47,15 @@ void hover_reattack (edict_t *self); void hover_fire_blaster (edict_t *self); void hover_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point); +static void hover_set_fly_parameters(edict_t *self) +{ + self->monsterinfo.fly_thrusters = false; + self->monsterinfo.fly_acceleration = 20.0f; + self->monsterinfo.fly_speed = 120.0f; + self->monsterinfo.fly_min_distance = 275.0f; //250.0f default value + self->monsterinfo.fly_max_distance = 550.0f; //450.0f default value +} + mframe_t hover_frames_stand [] = { drone_ai_stand, 0, NULL, @@ -644,6 +653,8 @@ void init_drone_hover (edict_t *self) self->mtype = M_HOVER; self->flags |= FL_FLY; + self->monsterinfo.aiflags |= AI_ALTERNATE_FLY; + hover_set_fly_parameters(self); self->max_health = self->health; self->monsterinfo.power_armor_power = M_FLOATER_INITIAL_ARMOR + M_FLOATER_ADDON_ARMOR*self->monsterinfo.level; self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD; diff --git a/src/entities/drone/drone_misc.c b/src/entities/drone/drone_misc.c index 604f1553..6d3fb1a8 100644 --- a/src/entities/drone/drone_misc.c +++ b/src/entities/drone/drone_misc.c @@ -413,15 +413,26 @@ void drone_ai_checkattack (edict_t *self) // if we see an easier target, go for it if (!visible(self, self->enemy)) { - self->oldenemy = self->enemy; - if (!drone_findtarget(self, false)) - return; - //gi.dprintf("%d going for an easier target\n", self->mtype); + if (!(self->monsterinfo.aiflags & AI_ALTERNATE_FLY) || !M_MonsterHasCombatSight(self, self->enemy)) + { + self->oldenemy = self->enemy; + if (!drone_findtarget(self, false)) + return; + //gi.dprintf("%d going for an easier target\n", self->mtype); + } } //if (!infront(self, self->enemy)) if (!nearfov(self, self->enemy, 0, 60)) { + if ((self->monsterinfo.aiflags & AI_ALTERNATE_FLY) && M_MonsterHasCombatSight(self, self->enemy)) + { + vec3_t dir; + VectorSubtract(self->enemy->s.origin, self->s.origin, dir); + self->ideal_yaw = vectoyaw(dir); + M_ChangeYaw(self); + M_ChangeYaw(self); + } //gi.dprintf("target is not in front\n"); return; } @@ -434,10 +445,13 @@ void drone_ai_checkattack (edict_t *self) if (!tr.ent || tr.ent != self->enemy) { //gi.dprintf("blocked shot\n"); - if (G_ValidTarget(self, tr.ent, false, true)) - self->enemy = tr.ent; - else - return; + if (!(self->monsterinfo.aiflags & AI_ALTERNATE_FLY) || !M_MonsterHasCombatSight(self, self->enemy)) + { + if (G_ValidTarget(self, tr.ent, false, true)) + self->enemy = tr.ent; + else + return; + } } //AngleVectors(self->s.angles, forward, NULL, NULL); //VectorMA(self->s.origin, self->maxs[1]+8, forward , start); @@ -644,6 +658,82 @@ void PassThruEntity (edict_t *self, edict_t *other) gi.linkentity(other); } +#define DRONE_FLYER_SEPARATION_SPEED 650.0f +#define DRONE_ALT_FLY_SEPARATION_SPEED 420.0f +#define DRONE_FLYER_SEPARATION_DELAY 0.75f + +static qboolean drone_alt_fly_can_separate(edict_t *ent) +{ + if (!ent || !G_EntIsAlive(ent)) + return false; + if (ent->mtype != M_FLYER) + return false; + if (!(ent->flags & FL_FLY)) + return false; + if (!(ent->monsterinfo.aiflags & AI_ALTERNATE_FLY)) + return false; + return true; +} + +static qboolean drone_alt_fly_touch_obstacle(edict_t *ent) +{ + if (!ent || !G_EntIsAlive(ent)) + return false; + if (!(ent->svflags & SVF_MONSTER) || ent->solid == SOLID_NOT) + return false; + return true; +} + +static qboolean drone_alt_fly_apply_separation(edict_t *ent, vec3_t dir) +{ + float speed; + + if (ent->monsterinfo.fly_separation_time > level.time) + return false; + + ent->monsterinfo.fly_separation_time = level.time + DRONE_FLYER_SEPARATION_DELAY; + ent->monsterinfo.fly_thrusters = false; + ent->monsterinfo.fly_position_time = 0.0f; + ent->monsterinfo.fly_pinned = false; + speed = (ent->mtype == M_FLYER) ? DRONE_FLYER_SEPARATION_SPEED : DRONE_ALT_FLY_SEPARATION_SPEED; + VectorScale(dir, speed, ent->velocity); + return true; +} + +static qboolean drone_alt_fly_separation_touch(edict_t *self, edict_t *other) +{ + vec3_t dir; + qboolean pushed_self; + + if (!self || !other || self == other) + return false; + if (!drone_alt_fly_can_separate(self) || !drone_alt_fly_touch_obstacle(other)) + return false; + + VectorSubtract(self->s.origin, other->s.origin, dir); + if (VectorNormalize(dir) <= 0.1f) + { + VectorSet(dir, crandom(), crandom(), 0.2f); + if (VectorNormalize(dir) <= 0.1f) + VectorSet(dir, 1.0f, 0.0f, 0.0f); + } + + pushed_self = drone_alt_fly_apply_separation(self, dir); + + if (pushed_self) + { + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_SPLASH); + gi.WriteByte(32); + gi.WritePosition(self->s.origin); + gi.WriteDir(dir); + gi.WriteByte(SPLASH_SPARKS); + gi.multicast(self->s.origin, MULTICAST_PVS); + } + + return pushed_self; +} + void drone_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) { vec3_t forward, right, start, offset; @@ -651,6 +741,9 @@ void drone_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *su //gi.dprintf("drone_touch\n"); V_Touch(self, other, plane, surf); + if (drone_alt_fly_separation_touch(self, other)) + return; + // the monster's owner or allies can push him around //if (G_EntIsAlive(other) && self->activator // && ((other == self->activator) || IsAlly(self->activator, other))) @@ -1659,9 +1752,11 @@ qboolean M_MonsterHasClearShotFromFlash(edict_t *self, int flash_number) void M_MonsterBlockedShot(edict_t *self, float delay) { - if (!self || !self->inuse) + if (!(self->monsterinfo.aiflags & AI_ALTERNATE_FLY)) return; + self->monsterinfo.fly_position_time = 0.0f; + self->monsterinfo.fly_pinned = false; self->monsterinfo.attack_finished = max(self->monsterinfo.attack_finished, level.time + delay); } diff --git a/src/entities/drone/drone_move.c b/src/entities/drone/drone_move.c index 26584809..40289476 100644 --- a/src/entities/drone/drone_move.c +++ b/src/entities/drone/drone_move.c @@ -406,6 +406,1502 @@ qboolean M_MoveVertical(edict_t* ent, vec3_t dest, vec3_t neworg) } return false; } + +#define FLY_POSITION_UPDATE_MIN 3.0f +#define FLY_POSITION_UPDATE_MAX 10.0f +#define FLY_TURN_FACTOR_FAST 0.45f +#define FLY_TURN_FACTOR_BASE 0.84f +#define FLY_TURN_FACTOR_SPEED_SCALE 0.08f +#define FLY_PITCH_LERP_SPEED 4.0f +#define FLY_WALL_STUCK_THRESHOLD 1.5f +#define FLY_DESCENT_SPEED_MULTIPLIER 0.6f +#define FLY_TARGET_LEAD_TIME 0.45f +#define FLY_CATCHUP_MARGIN 32.0f +#define FLY_CATCHUP_DISTANCE_SCALE 0.85f +#define FLY_ATTACK_DISTANCE_FRACTION 0.2f +#define FLY_ATTACK_SPEED_SCALE 1.35f +#define FLY_ATTACK_ACCEL_SCALE 1.6f +#define FLY_LOS_PRESERVE_SPEED_SCALE 1.12f +#define FLY_LOS_PRESERVE_ACCEL_SCALE 1.30f +#define FLY_LOS_PRESERVE_TURN_FACTOR 0.32f +#define FLY_LOS_PRESSURE_SNAP_DOT 0.0f +#define FLY_LOS_FULL_SIGHT_FRACTION 0.98f +#define FLY_LOS_SIDE_SWITCH_SPEED 64.0f +#define FLY_LOS_PRESERVE_PROBE 72.0f +#define FLY_LOS_PRESERVE_MIN_FRACTION 0.25f +#define FLY_LOS_PRESERVE_SIDE_WEIGHT 1.05f +#define FLY_LOS_PRESERVE_FWD_WEIGHT 0.75f +#define FLY_LOS_PRESERVE_WALL_WEIGHT 0.35f +#define FLY_LOS_PRESERVE_DOWN_WEIGHT 0.55f +#define FLY_LOS_PREVENT_LEAD_TIME 0.45f +#define FLY_LOS_PREVENT_THRESHOLD 0.86f +#define FLY_LOS_PREVENT_MIN_GAIN 0.10f +#define FLY_LOS_PREVENT_ALLOWED_DROP 0.06f +#define FLY_LOS_PREVENT_PROBE 96.0f +#define FLY_LOS_PREVENT_SIDE_WEIGHT 1.15f +#define FLY_LOS_PREVENT_FWD_WEIGHT 0.85f +#define FLY_LOS_PREVENT_DOWN_WEIGHT 0.35f +#define FLY_LOS_RECOVER_MEMORY_TIME 1.6f +#define FLY_LOS_RECOVER_LAST_SEEN_RANGE 512.0f +#define FLY_LOS_RECOVER_MAX_RANGE 1200.0f +#define FLY_LOS_RECOVER_PROBE 96.0f +#define FLY_LOS_RECOVER_MIN_GAIN 0.08f +#define FLY_LOS_RECOVER_SIDE_WEIGHT 1.20f +#define FLY_LOS_RECOVER_FWD_WEIGHT 0.80f +#define FLY_LOS_RECOVER_DOWN_WEIGHT 0.45f +#define FLY_ATTACK_STRAFE_SPEED_SCALE 0.6f +#define FLY_ATTACK_DRIFT_SPEED_SCALE 0.3f +#define FLY_ATTACK_STRAFE_MIN 32.0f +#define FLY_ATTACK_STRAFE_MAX 128.0f +#define FLY_ATTACK_ORBIT_DISTANCE 48.0f +#define FLY_ATTACK_HEIGHT_VARIANCE 56.0f +#define FLY_TARGET_STRAFE_MIN_SPEED 20.0f +#define FLY_TARGET_STRAFE_DISTANCE_SCALE 0.45f +#define FLY_COMBAT_MIN_HEIGHT 48.0f +#define FLY_COMBAT_HEIGHT_ABOVE_VIEW 24.0f +#define FLY_LOWER_TARGET_DESCENT_HEIGHT 96.0f +#define FLY_LOWER_TARGET_MIN_HEIGHT 16.0f +#define FLY_BLOCKED_DESCENT_HEIGHT 32.0f +#define FLY_BLOCKED_DESCENT_XY_SCALE 0.45f +#define FLY_COMBAT_WALL_STUCK_THRESHOLD 0.35f +#define FLY_STAIR_CLIMB_MIN_HEIGHT 24.0f +#define FLY_STAIR_CLIMB_MAX_HEIGHT 320.0f +#define FLY_STAIR_CLIMB_MAX_RANGE 420.0f +#define FLY_STAIR_CLIMB_PROBE 64.0f +#define FLY_STAIR_CLIMB_MIN_FRACTION 0.60f +#define FLY_STAIR_CLIMB_MIN_GAIN 4.0f +#define FLY_STAIR_CLIMB_FWD_WEIGHT 0.90f +#define FLY_STAIR_CLIMB_UP_WEIGHT 0.75f +#define FLY_CEILING_LOOKAHEAD 256.0f +#define FLY_CEILING_CLEARANCE 8.0f +#define FLY_SEPARATION_RADIUS 112.0f +#define FLY_SEPARATION_MAX_PUSH 96.0f +#define FLY_SEPARATION_Z_SCALE 0.45f +#define FLY_SEPARATION_MAX_NEIGHBORS 24 + +static float fly_frand_range(float min_value, float max_value) +{ + return min_value + random() * (max_value - min_value); +} + +static float fly_clampf(float value, float min_value, float max_value) +{ + if (value < min_value) + return min_value; + if (value > max_value) + return max_value; + return value; +} + +static void fly_scale_add(vec3_t out, vec3_t a, float ascale, vec3_t b, float bscale) +{ + out[0] = a[0] * ascale + b[0] * bscale; + out[1] = a[1] * ascale + b[1] * bscale; + out[2] = a[2] * ascale + b[2] * bscale; +} + +static qboolean fly_vector_valid(vec3_t v) +{ + return isfinite(v[0]) && isfinite(v[1]) && isfinite(v[2]); +} + +static float fly_entity_unit(edict_t *ent, unsigned int salt) +{ + unsigned int n = (unsigned int)(ent - g_edicts + 1); + + n ^= salt * 0x9e3779b9u; + n ^= n >> 16; + n *= 0x7feb352du; + n ^= n >> 15; + n *= 0x846ca68bu; + n ^= n >> 16; + return (float)(n & 0xffffu) / 65535.0f; +} + +static void fly_entity_fallback_dir(edict_t *ent, vec3_t out) +{ + float angle = fly_entity_unit(ent, 0x51u) * M_PI * 2.0f; + + VectorSet(out, cosf(angle), sinf(angle), 0.2f + fly_entity_unit(ent, 0x52u) * 0.35f); + VectorNormalize(out); +} + +static qboolean fly_following_path(edict_t *ent) +{ + if (ent->monsterinfo.aiflags & (AI_FIND_NAVI | AI_COMBAT_POINT | AI_LOST_SIGHT)) + return true; + + if (ent->goalentity && ent->goalentity->inuse) + { + if (ent->goalentity->mtype == INVASION_NAVI || ent->goalentity->mtype == PLAYER_NAVI || + ent->goalentity->mtype == INVASION_PLAYERSPAWN) + return true; + } + + if (ent->enemy && ent->enemy->inuse && invasion->value && ent->enemy->mtype == INVASION_PLAYERSPAWN) + return true; + + return false; +} + +static qboolean fly_has_visible_combat_enemy(edict_t *ent) +{ + if (!ent->enemy || !ent->enemy->inuse) + return false; + + if (invasion->value && ent->enemy->mtype == INVASION_PLAYERSPAWN) + return false; + + if (!M_MonsterHasCombatSight(ent, ent->enemy)) + return false; + + ent->monsterinfo.aiflags &= ~AI_LOST_SIGHT; + ent->monsterinfo.search_frames = 0; + VectorCopy(ent->enemy->s.origin, ent->monsterinfo.last_sighting); + ent->monsterinfo.trail_time = level.time; + return true; +} + +static qboolean fly_has_recent_combat_memory(edict_t *ent); + +static qboolean fly_has_recent_combat_enemy(edict_t *ent) +{ + if (fly_has_visible_combat_enemy(ent)) + return true; + + return fly_has_recent_combat_memory(ent); +} + +static qboolean fly_has_recent_combat_memory(edict_t *ent) +{ + if (!ent->enemy || !ent->enemy->inuse) + return false; + + if (invasion->value && ent->enemy->mtype == INVASION_PLAYERSPAWN) + return false; + + if (ent->monsterinfo.aiflags & AI_LOST_SIGHT) + return false; + + return ent->monsterinfo.search_frames <= 3; +} + +static qboolean fly_has_lost_combat_goal(edict_t *ent, vec3_t dest) +{ + if (!ent->enemy || !ent->enemy->inuse) + return false; + + if (invasion->value && ent->enemy->mtype == INVASION_PLAYERSPAWN) + return false; + + if (!(ent->monsterinfo.aiflags & AI_LOST_SIGHT)) + return false; + + if (dest) + return true; + + if (!VectorCompare(ent->monsterinfo.last_sighting, vec3_origin)) + return true; + + return ent->goalentity && ent->goalentity->inuse && ent->goalentity == ent->enemy; +} + +static qboolean fly_can_separate_from(edict_t *ent, edict_t *other) +{ + if (!other || other == ent || !G_EntIsAlive(other)) + return false; + if (!(other->svflags & SVF_MONSTER) || other->solid == SOLID_NOT) + return false; + if (!(other->flags & (FL_FLY | FL_SWIM))) + return false; + if (!(other->monsterinfo.aiflags & AI_ALTERNATE_FLY)) + return false; + return true; +} + +static qboolean fly_apply_neighbor_separation(edict_t *ent, vec3_t wanted_pos) +{ + edict_t *touch[FLY_SEPARATION_MAX_NEIGHBORS]; + vec3_t mins, maxs, away, separation; + float dist, weight, push; + int i, num; + + VectorSet(mins, + ent->s.origin[0] - FLY_SEPARATION_RADIUS, + ent->s.origin[1] - FLY_SEPARATION_RADIUS, + ent->s.origin[2] - FLY_SEPARATION_RADIUS); + VectorSet(maxs, + ent->s.origin[0] + FLY_SEPARATION_RADIUS, + ent->s.origin[1] + FLY_SEPARATION_RADIUS, + ent->s.origin[2] + FLY_SEPARATION_RADIUS); + + num = gi.BoxEdicts(mins, maxs, touch, FLY_SEPARATION_MAX_NEIGHBORS, AREA_SOLID); + VectorClear(separation); + + for (i = 0; i < num; ++i) + { + edict_t *other = touch[i]; + + if (!fly_can_separate_from(ent, other)) + continue; + + VectorSubtract(ent->s.origin, other->s.origin, away); + away[2] *= FLY_SEPARATION_Z_SCALE; + dist = VectorNormalize(away); + if (dist > FLY_SEPARATION_RADIUS) + continue; + if (dist <= 0.1f) + { + fly_entity_fallback_dir(ent, away); + dist = 1.0f; + } + + weight = (FLY_SEPARATION_RADIUS - dist) / FLY_SEPARATION_RADIUS; + if (ent->enemy && other->enemy == ent->enemy) + weight *= 1.35f; + VectorMA(separation, weight, away, separation); + } + + push = VectorNormalize(separation); + if (push <= 0.1f) + return false; + + push = fly_clampf(push * FLY_SEPARATION_MAX_PUSH, 0.0f, FLY_SEPARATION_MAX_PUSH); + VectorMA(wanted_pos, push, separation, wanted_pos); + return true; +} + +static qboolean fly_use_alternate_step(edict_t *ent, vec3_t dest) +{ + if (!(ent->monsterinfo.aiflags & AI_ALTERNATE_FLY)) + return false; + + if (!fly_has_recent_combat_enemy(ent) && !fly_has_lost_combat_goal(ent, dest)) + return false; + + // Invasion destination movement is used by navis/base pathing and expects an immediate origin step. + if (dest && invasion->value && !fly_has_lost_combat_goal(ent, dest)) + return false; + + return true; +} + +static void fly_reset_alternate_step(edict_t *ent) +{ + VectorClear(ent->velocity); + ent->monsterinfo.fly_pinned = false; + ent->monsterinfo.fly_wall_stuck_time = 0.0f; +} + +static qboolean fly_has_last_sighting(edict_t *ent) +{ + return !VectorCompare(ent->monsterinfo.last_sighting, vec3_origin); +} + +static void fly_face_target(edict_t *ent, vec3_t target_origin) +{ + vec3_t target_dir; + + VectorSubtract(target_origin, ent->s.origin, target_dir); + if (VectorNormalize(target_dir) <= 0.1f) + return; + + ent->ideal_yaw = vectoyaw(target_dir); + M_ChangeYaw(ent); +} + +static qboolean fly_clamp_to_ceiling(edict_t *ent, vec3_t wanted_pos) +{ + vec3_t ceiling_check; + trace_t tr; + float desired_up; + float trace_height; + float highest_origin; + + desired_up = wanted_pos[2] - ent->s.origin[2]; + if (desired_up <= 1.0f) + return false; + + VectorCopy(ent->s.origin, ceiling_check); + trace_height = max(FLY_CEILING_LOOKAHEAD, desired_up + FLY_CEILING_CLEARANCE); + ceiling_check[2] += trace_height; + + tr = gi.trace(ent->s.origin, ent->mins, ent->maxs, ceiling_check, ent, MASK_SOLID | CONTENTS_MONSTERCLIP); + if (tr.fraction == 1.0f || tr.startsolid || tr.allsolid) + return false; + + highest_origin = tr.endpos[2] - FLY_CEILING_CLEARANCE; + if (wanted_pos[2] <= highest_origin) + return false; + + wanted_pos[2] = highest_origin; + if (ent->monsterinfo.fly_pinned) + ent->monsterinfo.fly_position_time = 0.0f; + ent->monsterinfo.fly_pinned = false; + return true; +} + +static qboolean fly_can_descend(edict_t *ent) +{ + vec3_t descent_check; + trace_t tr; + + VectorCopy(ent->s.origin, descent_check); + descent_check[2] -= ent->monsterinfo.fly_acceleration; + + tr = gi.trace(ent->s.origin, ent->mins, ent->maxs, descent_check, ent, MASK_SOLID | CONTENTS_MONSTERCLIP); + return tr.fraction == 1.0f && !tr.startsolid && !tr.allsolid; +} + +static qboolean fly_force_descent(edict_t *ent, vec3_t wanted_dir, float xy_scale) +{ + if (!fly_can_descend(ent)) + return false; + + ent->monsterinfo.fly_position_time = 0.0f; + ent->monsterinfo.fly_pinned = false; + wanted_dir[0] *= xy_scale; + wanted_dir[1] *= xy_scale; + wanted_dir[2] = -FLY_DESCENT_SPEED_MULTIPLIER; + VectorNormalize(wanted_dir); + return true; +} + +static qboolean fly_should_descend_for_lower_path(edict_t *ent, qboolean following_paths, vec3_t towards_origin, vec3_t wanted_pos) +{ + trace_t tr; + + if (!following_paths) + return false; + if (ent->s.origin[2] <= towards_origin[2] + FLY_BLOCKED_DESCENT_HEIGHT) + return false; + + tr = gi.trace(ent->s.origin, ent->mins, ent->maxs, wanted_pos, ent, MASK_SOLID | CONTENTS_MONSTERCLIP); + return tr.fraction < 1.0f && !tr.startsolid && !tr.allsolid; +} + +static qboolean fly_consider_stair_climb_dir(edict_t *ent, vec3_t candidate, vec3_t target_dir, + float *best_score, vec3_t best_dir) +{ + vec3_t dir, end; + trace_t tr; + float probe_dist, z_gain, score; + + VectorCopy(candidate, dir); + if (VectorNormalize(dir) <= 0.1f) + return false; + + probe_dist = max(FLY_STAIR_CLIMB_PROBE, ent->monsterinfo.fly_acceleration * 2.5f); + VectorMA(ent->s.origin, probe_dist, dir, end); + tr = gi.trace(ent->s.origin, ent->mins, ent->maxs, end, ent, MASK_SOLID | CONTENTS_MONSTERCLIP); + if (tr.startsolid || tr.allsolid || tr.fraction < FLY_STAIR_CLIMB_MIN_FRACTION) + return false; + + z_gain = tr.endpos[2] - ent->s.origin[2]; + if (z_gain < FLY_STAIR_CLIMB_MIN_GAIN) + return false; + + score = tr.fraction * 36.0f + z_gain * 0.25f + DotProduct(dir, target_dir) * 8.0f; + if (score <= *best_score) + return true; + + *best_score = score; + VectorCopy(dir, best_dir); + return true; +} + +static qboolean fly_try_climb_for_higher_target(edict_t *ent, vec3_t target_origin, vec3_t wanted_dir) +{ + vec3_t to_target, target_dir, candidate, best_dir; + float height_delta, horizontal_dist, best_score; + qboolean found; + + VectorSubtract(target_origin, ent->s.origin, to_target); + height_delta = to_target[2]; + if (height_delta < FLY_STAIR_CLIMB_MIN_HEIGHT || height_delta > FLY_STAIR_CLIMB_MAX_HEIGHT) + return false; + + VectorCopy(to_target, target_dir); + target_dir[2] = 0.0f; + horizontal_dist = VectorNormalize(target_dir); + if (horizontal_dist > FLY_STAIR_CLIMB_MAX_RANGE) + return false; + if (horizontal_dist <= 0.1f) + { + VectorCopy(wanted_dir, target_dir); + target_dir[2] = 0.0f; + if (VectorNormalize(target_dir) <= 0.1f) + return false; + } + + best_score = -9999.0f; + VectorClear(best_dir); + found = false; + + VectorScale(target_dir, FLY_STAIR_CLIMB_FWD_WEIGHT, candidate); + candidate[2] = FLY_STAIR_CLIMB_UP_WEIGHT; + found |= fly_consider_stair_climb_dir(ent, candidate, target_dir, &best_score, best_dir); + + VectorScale(target_dir, FLY_STAIR_CLIMB_FWD_WEIGHT * 0.45f, candidate); + candidate[2] = FLY_STAIR_CLIMB_UP_WEIGHT * 1.25f; + found |= fly_consider_stair_climb_dir(ent, candidate, target_dir, &best_score, best_dir); + + VectorCopy(wanted_dir, candidate); + candidate[2] += FLY_STAIR_CLIMB_UP_WEIGHT; + found |= fly_consider_stair_climb_dir(ent, candidate, target_dir, &best_score, best_dir); + + if (!found) + return false; + + VectorCopy(best_dir, wanted_dir); + ent->monsterinfo.fly_position_time = 0.0f; + ent->monsterinfo.fly_pinned = false; + return true; +} + +static void G_IdealHoverPosition(edict_t *ent, vec3_t out) +{ + float theta, phi, distance_scale; + vec3_t direction; + + VectorClear(out); + + if ((!ent->enemy && !(ent->monsterinfo.aiflags & AI_MEDIC)) || + (fly_following_path(ent) && !fly_has_visible_combat_enemy(ent))) + return; + + theta = random() * M_PI * 2.0f; + if (ent->monsterinfo.fly_above) + phi = acosf(0.7f + random() * 0.3f); + else if (ent->monsterinfo.fly_buzzard || (ent->monsterinfo.aiflags & AI_MEDIC)) + phi = acosf(random()); + else + phi = acosf(random() * 0.7f); + + VectorSet(direction, sinf(phi) * cosf(theta), sinf(phi) * sinf(theta), cosf(phi)); + + distance_scale = fly_frand_range(ent->monsterinfo.fly_min_distance, ent->monsterinfo.fly_max_distance); + VectorScale(direction, distance_scale, out); +} + +static qboolean fly_adjust_visible_combat_goal(edict_t *ent, vec3_t target_origin, vec3_t target_velocity, vec3_t wanted_pos, qboolean visible_combat_enemy) +{ + float current_dist, min_dist, max_dist, desired_dist, attack_dist, radial_speed, catchup_step, strafe_dist, min_height; + float attack_fraction, strafe_variance, orbit_phase, orbit; + float target_lateral_speed, target_lateral_speed_abs; + qboolean attack_drive, sliding_attack, force_drive, strafe_left, lower_target; + vec3_t away, ideal_offset, predicted_target, lateral; + + if (!visible_combat_enemy || ent->monsterinfo.fly_pinned) + return false; + + VectorSubtract(ent->s.origin, target_origin, away); + current_dist = VectorNormalize(away); + if (current_dist <= 0.1f) + return false; + + min_dist = max(0.0f, ent->monsterinfo.fly_min_distance); + max_dist = max(min_dist, ent->monsterinfo.fly_max_distance); + desired_dist = current_dist; + orbit_phase = level.time * (0.55f + fly_entity_unit(ent, 0x70u) * 0.65f) + + fly_entity_unit(ent, 0x71u) * M_PI * 2.0f; + orbit = sinf(orbit_phase); + attack_fraction = FLY_ATTACK_DISTANCE_FRACTION + ((fly_entity_unit(ent, 0x61u) - 0.5f) * 0.22f) + orbit * 0.08f; + attack_dist = fly_clampf(min_dist + ((max_dist - min_dist) * attack_fraction), min_dist, max_dist); + attack_drive = (ent->monsterinfo.attack_state == AS_STRAIGHT || ent->monsterinfo.attack_state == AS_SLIDING); + sliding_attack = (ent->monsterinfo.attack_state == AS_SLIDING); + lower_target = ent->s.origin[2] > target_origin[2] + FLY_LOWER_TARGET_DESCENT_HEIGHT; + force_drive = false; + + if (current_dist > max_dist + FLY_CATCHUP_MARGIN) + { + if (attack_drive) + desired_dist = attack_dist; + else + desired_dist = max(min_dist, max_dist * FLY_CATCHUP_DISTANCE_SCALE); + force_drive = true; + } + else + { + radial_speed = DotProduct(ent->velocity, away) - DotProduct(target_velocity, away); + if (radial_speed > 8.0f && current_dist > min_dist + FLY_CATCHUP_MARGIN) + { + catchup_step = max(FLY_CATCHUP_MARGIN, ent->monsterinfo.fly_speed * 0.35f); + if (attack_drive) + desired_dist = attack_dist; + else + desired_dist = fly_clampf(current_dist - catchup_step, min_dist, max_dist); + force_drive = true; + } + else if (attack_drive) + { + desired_dist = attack_dist; + force_drive = true; + } + else + { + return false; + } + } + + VectorScale(away, desired_dist, ideal_offset); + + if (attack_drive && !(ent->flags & FL_SWIM)) + { + min_height = FLY_COMBAT_MIN_HEIGHT; + if (ent->enemy && ent->enemy->viewheight > 0) + min_height = max(min_height, ent->enemy->viewheight + FLY_COMBAT_HEIGHT_ABOVE_VIEW); + min_height += fly_entity_unit(ent, 0x62u) * FLY_ATTACK_HEIGHT_VARIANCE; + if (ideal_offset[2] < min_height) + ideal_offset[2] = min_height; + } + if (attack_drive && lower_target && !(ent->flags & FL_SWIM)) + { + if (ideal_offset[2] > FLY_LOWER_TARGET_MIN_HEIGHT) + ideal_offset[2] = FLY_LOWER_TARGET_MIN_HEIGHT; + force_drive = true; + } + + if (attack_drive) + { + VectorSet(lateral, -away[1], away[0], 0.0f); + if (VectorNormalize(lateral) > 0.1f) + { + target_lateral_speed = DotProduct(target_velocity, lateral); + target_lateral_speed_abs = fabs(target_lateral_speed); + + if (target_lateral_speed_abs > FLY_TARGET_STRAFE_MIN_SPEED) + { + if (target_lateral_speed < 0.0f) + VectorNegate(lateral, lateral); + strafe_dist = fly_clampf(target_lateral_speed_abs * FLY_TARGET_STRAFE_DISTANCE_SCALE, + FLY_ATTACK_STRAFE_MIN, FLY_ATTACK_STRAFE_MAX); + } + else + { + strafe_left = orbit >= 0.0f; + if (fly_entity_unit(ent, 0x63u) > 0.5f) + strafe_left = !strafe_left; + if (!strafe_left) + VectorNegate(lateral, lateral); + strafe_dist = fly_clampf(ent->monsterinfo.fly_speed * + (sliding_attack ? FLY_ATTACK_STRAFE_SPEED_SCALE : FLY_ATTACK_DRIFT_SPEED_SCALE), + FLY_ATTACK_STRAFE_MIN, FLY_ATTACK_STRAFE_MAX); + } + + strafe_variance = 0.60f + fly_entity_unit(ent, 0x64u) * 0.65f + fabsf(orbit) * 0.35f; + strafe_dist = fly_clampf(strafe_dist * strafe_variance, FLY_ATTACK_STRAFE_MIN, FLY_ATTACK_STRAFE_MAX); + strafe_dist = fly_clampf(strafe_dist + orbit * FLY_ATTACK_ORBIT_DISTANCE, + FLY_ATTACK_STRAFE_MIN, FLY_ATTACK_STRAFE_MAX); + VectorMA(ideal_offset, strafe_dist, lateral, ideal_offset); + } + } + + VectorMA(target_origin, FLY_TARGET_LEAD_TIME, target_velocity, predicted_target); + VectorAdd(predicted_target, ideal_offset, wanted_pos); + VectorCopy(ideal_offset, ent->monsterinfo.fly_ideal_position); + + if (ent->monsterinfo.fly_position_time > level.time + 0.5f) + ent->monsterinfo.fly_position_time = level.time + 0.5f; + + return force_drive; +} + +qboolean SV_flystep_testvisposition(vec3_t start, vec3_t end, vec3_t starta, vec3_t startb, edict_t* ent) +{ + trace_t tr; + + tr = gi.trace(start, NULL, NULL, end, ent, MASK_SOLID | CONTENTS_MONSTERCLIP); + if (tr.fraction == 1.0f) + { + tr = gi.trace(starta, ent->mins, ent->maxs, startb, ent, MASK_SOLID | CONTENTS_MONSTERCLIP); + if (tr.fraction == 1.0f) + return true; + } + + return false; +} + +// Alternate flyers steer directly, so they need local side probes instead of +// Horde's temporary ai_run pursuit goals when LOS is about to be blocked. +static float fly_sight_fraction_to_point_from(edict_t *ent, vec3_t origin, vec3_t point) +{ + vec3_t start; + trace_t tr; + + VectorCopy(origin, start); + if (ent->viewheight) + start[2] += ent->viewheight; + else + start[2] += (ent->mins[2] + ent->maxs[2]) * 0.5f; + + if (!gi.inPVS(start, point)) + return 0.0f; + + tr = gi.trace(start, NULL, NULL, point, ent, MASK_SOLID); + if (tr.startsolid || tr.allsolid) + return 0.0f; + + return fly_clampf(tr.fraction, 0.0f, 1.0f); +} + +static float fly_combat_sight_fraction_from(edict_t *ent, vec3_t origin, edict_t *target) +{ + vec3_t point; + float best, fraction; + + if (!G_EntExists(target)) + return 0.0f; + + best = 0.0f; + + G_EntViewPoint(target, point); + fraction = fly_sight_fraction_to_point_from(ent, origin, point); + if (fraction > best) + best = fraction; + + G_EntMidPoint(target, point); + fraction = fly_sight_fraction_to_point_from(ent, origin, point); + if (fraction > best) + best = fraction; + + VectorCopy(target->s.origin, point); + fraction = fly_sight_fraction_to_point_from(ent, origin, point); + if (fraction > best) + best = fraction; + + return best; +} + +static float fly_predicted_combat_sight_fraction_from(edict_t *ent, vec3_t origin, edict_t *target, vec3_t target_velocity) +{ + vec3_t point; + float best, fraction; + + if (!G_EntExists(target)) + return 0.0f; + + best = 0.0f; + + G_EntViewPoint(target, point); + VectorMA(point, FLY_LOS_PREVENT_LEAD_TIME, target_velocity, point); + fraction = fly_sight_fraction_to_point_from(ent, origin, point); + if (fraction > best) + best = fraction; + + G_EntMidPoint(target, point); + VectorMA(point, FLY_LOS_PREVENT_LEAD_TIME, target_velocity, point); + fraction = fly_sight_fraction_to_point_from(ent, origin, point); + if (fraction > best) + best = fraction; + + VectorMA(target->s.origin, FLY_LOS_PREVENT_LEAD_TIME, target_velocity, point); + fraction = fly_sight_fraction_to_point_from(ent, origin, point); + if (fraction > best) + best = fraction; + + return best; +} + +static qboolean fly_recent_los_recovery_allowed(edict_t *ent, edict_t *target) +{ + if (!G_EntExists(target) || !fly_has_last_sighting(ent)) + return false; + + if (ent->monsterinfo.aiflags & AI_LOST_SIGHT) + return false; + + if (ent->monsterinfo.trail_time <= 0.0f || + level.time > ent->monsterinfo.trail_time + FLY_LOS_RECOVER_MEMORY_TIME) + return false; + + if (entdist(ent, target) > FLY_LOS_RECOVER_MAX_RANGE) + return false; + + if (distance(target->s.origin, ent->monsterinfo.last_sighting) > FLY_LOS_RECOVER_LAST_SEEN_RANGE) + return false; + + return true; +} + +static qboolean fly_los_side_vectors(edict_t *ent, vec3_t target_dir, vec3_t target_velocity, + vec3_t side, vec3_t other_side) +{ + vec3_t lateral; + float lateral_speed; + + VectorSet(lateral, -target_dir[1], target_dir[0], 0.0f); + if (VectorNormalize(lateral) <= 0.1f) + return false; + + lateral_speed = DotProduct(target_velocity, lateral); + if (lateral_speed < -FLY_LOS_SIDE_SWITCH_SPEED || + (fabsf(lateral_speed) <= FLY_LOS_SIDE_SWITCH_SPEED && fly_entity_unit(ent, 0x91u) < 0.5f)) + { + VectorNegate(lateral, lateral); + } + + VectorCopy(lateral, side); + VectorNegate(side, other_side); + return true; +} + +static qboolean fly_consider_los_preserve_dir(edict_t *ent, edict_t *target, vec3_t candidate, + vec3_t wanted_dir, float *best_score, vec3_t best_dir) +{ + vec3_t dir, end; + trace_t tr; + float probe_dist, sight, score; + + VectorCopy(candidate, dir); + if (VectorNormalize(dir) <= 0.1f) + return false; + + probe_dist = max(FLY_LOS_PRESERVE_PROBE, ent->monsterinfo.fly_acceleration * 2.5f); + VectorMA(ent->s.origin, probe_dist, dir, end); + tr = gi.trace(ent->s.origin, ent->mins, ent->maxs, end, ent, MASK_SOLID | CONTENTS_MONSTERCLIP); + if (tr.startsolid || tr.allsolid || tr.fraction < FLY_LOS_PRESERVE_MIN_FRACTION) + return false; + sight = fly_combat_sight_fraction_from(ent, tr.endpos, target); + if (sight < FLY_LOS_FULL_SIGHT_FRACTION) + return false; + + score = sight * 100.0f + tr.fraction * 28.0f + DotProduct(dir, wanted_dir) * 4.0f; + if (dir[2] < -0.1f) + score += 2.0f; + + if (score <= *best_score) + return true; + + *best_score = score; + VectorCopy(dir, best_dir); + return true; +} + +static qboolean fly_try_preserve_combat_sight(edict_t *ent, vec3_t towards_origin, vec3_t target_velocity, + vec3_t wanted_dir, vec3_t obstacle_normal, vec3_t out_dir) +{ + vec3_t to_target, side, other_side, down, wall_push, candidate, best_dir; + float best_score; + qboolean found; + + if (!ent->enemy || !ent->enemy->inuse) + return false; + + VectorSubtract(towards_origin, ent->s.origin, to_target); + to_target[2] = 0.0f; + if (VectorNormalize(to_target) <= 0.1f) + return false; + + if (!fly_los_side_vectors(ent, to_target, target_velocity, side, other_side)) + return false; + + VectorSet(down, 0.0f, 0.0f, -1.0f); + VectorCopy(obstacle_normal, wall_push); + wall_push[2] = min(wall_push[2], 0.0f); + if (VectorNormalize(wall_push) <= 0.1f) + VectorClear(wall_push); + + best_score = -9999.0f; + VectorClear(best_dir); + found = false; + + VectorScale(side, FLY_LOS_PRESERVE_SIDE_WEIGHT, candidate); + VectorMA(candidate, FLY_LOS_PRESERVE_FWD_WEIGHT, to_target, candidate); + VectorMA(candidate, FLY_LOS_PRESERVE_WALL_WEIGHT, wall_push, candidate); + found |= fly_consider_los_preserve_dir(ent, ent->enemy, candidate, wanted_dir, &best_score, best_dir); + + VectorScale(side, FLY_LOS_PRESERVE_SIDE_WEIGHT, candidate); + VectorMA(candidate, FLY_LOS_PRESERVE_DOWN_WEIGHT, down, candidate); + VectorMA(candidate, FLY_LOS_PRESERVE_WALL_WEIGHT, wall_push, candidate); + found |= fly_consider_los_preserve_dir(ent, ent->enemy, candidate, wanted_dir, &best_score, best_dir); + + VectorScale(side, FLY_LOS_PRESERVE_SIDE_WEIGHT, candidate); + VectorMA(candidate, FLY_LOS_PRESERVE_WALL_WEIGHT, wall_push, candidate); + found |= fly_consider_los_preserve_dir(ent, ent->enemy, candidate, wanted_dir, &best_score, best_dir); + + VectorScale(other_side, FLY_LOS_PRESERVE_SIDE_WEIGHT, candidate); + VectorMA(candidate, FLY_LOS_PRESERVE_FWD_WEIGHT, to_target, candidate); + VectorMA(candidate, FLY_LOS_PRESERVE_WALL_WEIGHT, wall_push, candidate); + found |= fly_consider_los_preserve_dir(ent, ent->enemy, candidate, wanted_dir, &best_score, best_dir); + + VectorScale(other_side, FLY_LOS_PRESERVE_SIDE_WEIGHT, candidate); + VectorMA(candidate, FLY_LOS_PRESERVE_DOWN_WEIGHT, down, candidate); + VectorMA(candidate, FLY_LOS_PRESERVE_WALL_WEIGHT, wall_push, candidate); + found |= fly_consider_los_preserve_dir(ent, ent->enemy, candidate, wanted_dir, &best_score, best_dir); + + VectorScale(to_target, FLY_LOS_PRESERVE_FWD_WEIGHT, candidate); + VectorMA(candidate, FLY_LOS_PRESERVE_DOWN_WEIGHT, down, candidate); + VectorMA(candidate, FLY_LOS_PRESERVE_WALL_WEIGHT, wall_push, candidate); + found |= fly_consider_los_preserve_dir(ent, ent->enemy, candidate, wanted_dir, &best_score, best_dir); + + if (!found) + return false; + + VectorCopy(best_dir, out_dir); + return true; +} + +static qboolean fly_consider_los_prevent_dir(edict_t *ent, edict_t *target, vec3_t target_velocity, + vec3_t candidate, vec3_t wanted_dir, float current_future_sight, float preference, + float *best_score, vec3_t best_dir) +{ + vec3_t dir, end; + trace_t tr; + float probe_dist, future_sight, actual_sight, score; + + VectorCopy(candidate, dir); + if (VectorNormalize(dir) <= 0.1f) + return false; + + probe_dist = max(FLY_LOS_PRESERVE_PROBE, ent->monsterinfo.fly_acceleration * 3.0f); + VectorMA(ent->s.origin, probe_dist, dir, end); + tr = gi.trace(ent->s.origin, ent->mins, ent->maxs, end, ent, MASK_SOLID | CONTENTS_MONSTERCLIP); + if (tr.startsolid || tr.allsolid || tr.fraction < FLY_LOS_PRESERVE_MIN_FRACTION) + return false; + + future_sight = fly_predicted_combat_sight_fraction_from(ent, tr.endpos, target, target_velocity); + actual_sight = fly_combat_sight_fraction_from(ent, tr.endpos, target); + if (actual_sight < FLY_LOS_FULL_SIGHT_FRACTION && + future_sight < current_future_sight + FLY_LOS_PREVENT_MIN_GAIN) + return false; + if (future_sight < current_future_sight + FLY_LOS_PREVENT_MIN_GAIN && + future_sight < FLY_LOS_FULL_SIGHT_FRACTION) + return false; + + score = future_sight * 100.0f + actual_sight * 30.0f + tr.fraction * 24.0f + + DotProduct(dir, wanted_dir) * 4.0f + preference; + if (dir[2] < -0.1f) + score += 1.5f; + + if (score <= *best_score) + return true; + + *best_score = score; + VectorCopy(dir, best_dir); + return true; +} + +static qboolean fly_try_prevent_combat_sight_loss(edict_t *ent, vec3_t target_origin, vec3_t target_velocity, + vec3_t wanted_dir, vec3_t out_dir) +{ + vec3_t to_target, side, other_side, down, candidate, best_dir, intended_end; + trace_t tr; + float current_future_sight, intended_future_sight, best_score, side_preference, other_preference, probe_dist; + qboolean found; + + if (!G_EntExists(ent->enemy)) + return false; + + current_future_sight = fly_predicted_combat_sight_fraction_from(ent, ent->s.origin, ent->enemy, target_velocity); + intended_future_sight = current_future_sight; + probe_dist = max(FLY_LOS_PREVENT_PROBE, ent->monsterinfo.fly_acceleration * 3.0f); + VectorMA(ent->s.origin, probe_dist, wanted_dir, intended_end); + tr = gi.trace(ent->s.origin, ent->mins, ent->maxs, intended_end, ent, MASK_SOLID | CONTENTS_MONSTERCLIP); + if (!tr.startsolid && !tr.allsolid && tr.fraction >= FLY_LOS_PRESERVE_MIN_FRACTION) + intended_future_sight = fly_predicted_combat_sight_fraction_from(ent, tr.endpos, ent->enemy, target_velocity); + + if (current_future_sight >= FLY_LOS_PREVENT_THRESHOLD && + intended_future_sight >= current_future_sight - FLY_LOS_PREVENT_ALLOWED_DROP) + return false; + current_future_sight = min(current_future_sight, intended_future_sight); + + VectorSubtract(target_origin, ent->s.origin, to_target); + to_target[2] = 0.0f; + if (VectorNormalize(to_target) <= 0.1f) + return false; + + if (!fly_los_side_vectors(ent, to_target, target_velocity, side, other_side)) + return false; + + VectorSet(down, 0.0f, 0.0f, -1.0f); + VectorClear(best_dir); + best_score = -9999.0f; + side_preference = 5.0f + fly_entity_unit(ent, 0x94u); + other_preference = fly_entity_unit(ent, 0x95u); + found = false; + + VectorScale(side, FLY_LOS_PREVENT_SIDE_WEIGHT, candidate); + VectorMA(candidate, FLY_LOS_PREVENT_FWD_WEIGHT, to_target, candidate); + found |= fly_consider_los_prevent_dir(ent, ent->enemy, target_velocity, candidate, to_target, + current_future_sight, side_preference, &best_score, best_dir); + + VectorScale(side, FLY_LOS_PREVENT_SIDE_WEIGHT, candidate); + VectorMA(candidate, FLY_LOS_PREVENT_FWD_WEIGHT * 0.45f, to_target, candidate); + VectorMA(candidate, FLY_LOS_PREVENT_DOWN_WEIGHT, down, candidate); + found |= fly_consider_los_prevent_dir(ent, ent->enemy, target_velocity, candidate, to_target, + current_future_sight, side_preference * 0.75f, &best_score, best_dir); + + VectorScale(other_side, FLY_LOS_PREVENT_SIDE_WEIGHT, candidate); + VectorMA(candidate, FLY_LOS_PREVENT_FWD_WEIGHT, to_target, candidate); + found |= fly_consider_los_prevent_dir(ent, ent->enemy, target_velocity, candidate, to_target, + current_future_sight, other_preference, &best_score, best_dir); + + VectorScale(other_side, FLY_LOS_PREVENT_SIDE_WEIGHT, candidate); + VectorMA(candidate, FLY_LOS_PREVENT_FWD_WEIGHT * 0.45f, to_target, candidate); + VectorMA(candidate, FLY_LOS_PREVENT_DOWN_WEIGHT, down, candidate); + found |= fly_consider_los_prevent_dir(ent, ent->enemy, target_velocity, candidate, to_target, + current_future_sight, other_preference * 0.75f, &best_score, best_dir); + + if (!found) + return false; + + VectorCopy(best_dir, out_dir); + return true; +} + +static qboolean fly_consider_los_recover_dir(edict_t *ent, edict_t *target, vec3_t candidate, + vec3_t wanted_dir, float current_sight, float preference, float *best_score, vec3_t best_dir) +{ + vec3_t dir, end; + trace_t tr; + float probe_dist, sight, score; + + VectorCopy(candidate, dir); + if (VectorNormalize(dir) <= 0.1f) + return false; + + probe_dist = max(FLY_LOS_RECOVER_PROBE, ent->monsterinfo.fly_acceleration * 3.0f); + VectorMA(ent->s.origin, probe_dist, dir, end); + tr = gi.trace(ent->s.origin, ent->mins, ent->maxs, end, ent, MASK_SOLID | CONTENTS_MONSTERCLIP); + if (tr.startsolid || tr.allsolid || tr.fraction < FLY_LOS_PRESERVE_MIN_FRACTION) + return false; + + sight = fly_combat_sight_fraction_from(ent, tr.endpos, target); + if (sight < FLY_LOS_FULL_SIGHT_FRACTION && sight < current_sight + FLY_LOS_RECOVER_MIN_GAIN) + return false; + + score = sight * 100.0f + tr.fraction * 24.0f + DotProduct(dir, wanted_dir) * 5.0f + preference; + if (dir[2] < -0.1f) + score += 2.0f; + + if (score <= *best_score) + return true; + + *best_score = score; + VectorCopy(dir, best_dir); + return true; +} + +static qboolean fly_try_recover_combat_sight(edict_t *ent, vec3_t target_origin, vec3_t target_velocity, + vec3_t wanted_dir, vec3_t out_dir) +{ + vec3_t to_target, target_dir, side, other_side, down, candidate, point, best_dir; + trace_t tr; + float target_dist, current_sight, side_preference, other_preference; + float block_fraction, temp_dist, side_offset; + qboolean found; + + if (!fly_recent_los_recovery_allowed(ent, ent->enemy)) + return false; + + current_sight = fly_combat_sight_fraction_from(ent, ent->s.origin, ent->enemy); + if (current_sight >= FLY_LOS_FULL_SIGHT_FRACTION) + return false; + + VectorSubtract(target_origin, ent->s.origin, to_target); + target_dist = VectorNormalize(to_target); + if (target_dist <= 0.1f) + return false; + + VectorCopy(to_target, target_dir); + target_dir[2] = 0.0f; + if (VectorNormalize(target_dir) <= 0.1f) + { + VectorCopy(wanted_dir, target_dir); + target_dir[2] = 0.0f; + if (VectorNormalize(target_dir) <= 0.1f) + return false; + } + + if (!fly_los_side_vectors(ent, target_dir, target_velocity, side, other_side)) + return false; + + VectorSet(down, 0.0f, 0.0f, -1.0f); + + found = false; + VectorClear(best_dir); + side_preference = 4.0f + fly_entity_unit(ent, 0x92u); + other_preference = fly_entity_unit(ent, 0x93u); + current_sight = max(current_sight, 0.0f); + { + float best_score = -9999.0f; + + tr = gi.trace(ent->s.origin, ent->mins, ent->maxs, target_origin, ent, MASK_SOLID | CONTENTS_MONSTERCLIP); + if (!tr.startsolid && !tr.allsolid && tr.fraction < 1.0f) + { + block_fraction = fly_clampf(tr.fraction, 0.0f, 1.0f); + temp_dist = target_dist * ((block_fraction + 1.0f) * 0.5f); + temp_dist = fly_clampf(temp_dist, FLY_LOS_RECOVER_PROBE, target_dist); + side_offset = max(max(fabsf(ent->maxs[0]), fabsf(ent->maxs[1])) + 24.0f, 32.0f); + + VectorMA(ent->s.origin, temp_dist, target_dir, point); + VectorMA(point, side_offset, side, point); + VectorSubtract(point, ent->s.origin, candidate); + found |= fly_consider_los_recover_dir(ent, ent->enemy, candidate, target_dir, current_sight, + side_preference + 2.0f, &best_score, best_dir); + + VectorMA(ent->s.origin, temp_dist, target_dir, point); + VectorMA(point, side_offset, other_side, point); + VectorSubtract(point, ent->s.origin, candidate); + found |= fly_consider_los_recover_dir(ent, ent->enemy, candidate, target_dir, current_sight, + other_preference + 2.0f, &best_score, best_dir); + } + + VectorScale(side, FLY_LOS_RECOVER_SIDE_WEIGHT, candidate); + VectorMA(candidate, FLY_LOS_RECOVER_FWD_WEIGHT, target_dir, candidate); + found |= fly_consider_los_recover_dir(ent, ent->enemy, candidate, target_dir, current_sight, + side_preference, &best_score, best_dir); + + VectorScale(side, FLY_LOS_RECOVER_SIDE_WEIGHT, candidate); + VectorMA(candidate, FLY_LOS_RECOVER_FWD_WEIGHT * 0.45f, target_dir, candidate); + VectorMA(candidate, FLY_LOS_RECOVER_DOWN_WEIGHT, down, candidate); + found |= fly_consider_los_recover_dir(ent, ent->enemy, candidate, target_dir, current_sight, + side_preference * 0.75f, &best_score, best_dir); + + VectorScale(other_side, FLY_LOS_RECOVER_SIDE_WEIGHT, candidate); + VectorMA(candidate, FLY_LOS_RECOVER_FWD_WEIGHT, target_dir, candidate); + found |= fly_consider_los_recover_dir(ent, ent->enemy, candidate, target_dir, current_sight, + other_preference, &best_score, best_dir); + + VectorScale(other_side, FLY_LOS_RECOVER_SIDE_WEIGHT, candidate); + VectorMA(candidate, FLY_LOS_RECOVER_FWD_WEIGHT * 0.45f, target_dir, candidate); + VectorMA(candidate, FLY_LOS_RECOVER_DOWN_WEIGHT, down, candidate); + found |= fly_consider_los_recover_dir(ent, ent->enemy, candidate, target_dir, current_sight, + other_preference * 0.75f, &best_score, best_dir); + + VectorScale(target_dir, FLY_LOS_RECOVER_FWD_WEIGHT, candidate); + VectorMA(candidate, FLY_LOS_RECOVER_DOWN_WEIGHT, down, candidate); + found |= fly_consider_los_recover_dir(ent, ent->enemy, candidate, target_dir, current_sight, + 0.0f, &best_score, best_dir); + } + + if (!found) + return false; + + VectorCopy(best_dir, out_dir); + return true; +} + +qboolean SV_alternate_flystep(edict_t* ent, vec3_t dest, vec3_t move, qboolean relink) +{ + vec3_t dir, towards_origin, towards_velocity, wanted_pos, dest_diff, wanted_dir, final_dir; + vec3_t trace_end, aim_fwd, aim_rgt, aim_up, yaw_angles; + vec3_t box_mins, box_maxs; + trace_t tr; + float current_speed, dist_to_wanted, turn_factor, base_fly_speed, accel, speed_factor, wanted_speed; + qboolean following_paths, have_target, bad_movement_direction, visible_combat_enemy, recent_combat_memory, catchup_goal, combat_attack_drive, los_preserve_drive, los_recover_drive, los_pressure_drive; + + (void)move; + (void)relink; + + if ((ent->flags & FL_SWIM) && ent->waterlevel < WATER_WAIST) + return true; + + if (ent->monsterinfo.fly_speed <= 0.0f || ent->monsterinfo.fly_acceleration <= 0.0f) + return false; + + if (ent->monsterinfo.fly_max_distance < ent->monsterinfo.fly_min_distance) + ent->monsterinfo.fly_max_distance = ent->monsterinfo.fly_min_distance; + + if (ent->monsterinfo.fly_position_time <= level.time || + (ent->enemy && ent->monsterinfo.fly_pinned && !visible(ent, ent->enemy)) || + (ent->enemy && VectorCompare(ent->monsterinfo.fly_ideal_position, vec3_origin))) + { + ent->monsterinfo.fly_pinned = false; + ent->monsterinfo.fly_position_time = level.time + fly_frand_range(FLY_POSITION_UPDATE_MIN, FLY_POSITION_UPDATE_MAX); + G_IdealHoverPosition(ent, ent->monsterinfo.fly_ideal_position); + } + + VectorCopy(ent->velocity, dir); + current_speed = VectorNormalize(dir); + if (current_speed < 0.1f) + VectorClear(dir); + + VectorClear(towards_origin); + VectorClear(towards_velocity); + visible_combat_enemy = fly_has_visible_combat_enemy(ent); + recent_combat_memory = visible_combat_enemy || fly_has_recent_combat_memory(ent); + following_paths = fly_following_path(ent) && !visible_combat_enemy; + have_target = false; + los_preserve_drive = false; + los_recover_drive = false; + + if (dest) + { + VectorCopy(dest, towards_origin); + following_paths = true; + have_target = true; + } + else if (following_paths) + { + if (ent->goalentity && ent->goalentity->inuse) + { + VectorCopy(ent->goalentity->s.origin, towards_origin); + have_target = true; + } + else if (fly_has_last_sighting(ent)) + { + VectorCopy(ent->monsterinfo.last_sighting, towards_origin); + have_target = true; + } + } + else if (!visible_combat_enemy && recent_combat_memory && fly_has_last_sighting(ent)) + { + VectorCopy(ent->monsterinfo.last_sighting, towards_origin); + following_paths = true; + have_target = true; + } + else if (ent->enemy && ent->enemy->inuse) + { + VectorCopy(ent->enemy->s.origin, towards_origin); + VectorCopy(ent->enemy->velocity, towards_velocity); + have_target = true; + } + else if (ent->goalentity && ent->goalentity->inuse && ent->goalentity != world) + { + VectorCopy(ent->goalentity->s.origin, towards_origin); + have_target = true; + } + if (!have_target) + { + accel = ent->monsterinfo.fly_acceleration; + if (current_speed > 0.0f) + current_speed = max(0.0f, current_speed - accel); + + if (current_speed > 0.0f) + VectorScale(dir, current_speed, ent->velocity); + else + VectorClear(ent->velocity); + return true; + } + + if (ent->monsterinfo.fly_pinned) + VectorCopy(ent->monsterinfo.fly_ideal_position, wanted_pos); + else if (following_paths) + VectorCopy(towards_origin, wanted_pos); + else + { + VectorMA(towards_origin, FLY_TARGET_LEAD_TIME, towards_velocity, wanted_pos); + VectorAdd(wanted_pos, ent->monsterinfo.fly_ideal_position, wanted_pos); + } + if (following_paths) + catchup_goal = false; + else + catchup_goal = fly_adjust_visible_combat_goal(ent, towards_origin, towards_velocity, wanted_pos, visible_combat_enemy); + if (visible_combat_enemy && !following_paths) + fly_apply_neighbor_separation(ent, wanted_pos); + + VectorSet(box_mins, -8.0f, -8.0f, -8.0f); + VectorSet(box_maxs, 8.0f, 8.0f, 8.0f); + tr = gi.trace(towards_origin, box_mins, box_maxs, wanted_pos, ent, MASK_SOLID | CONTENTS_MONSTERCLIP); + if (!tr.allsolid) + VectorCopy(tr.endpos, wanted_pos); + fly_clamp_to_ceiling(ent, wanted_pos); + VectorSubtract(wanted_pos, ent->s.origin, dest_diff); + if (dest_diff[2] > ent->mins[2] && dest_diff[2] < ent->maxs[2]) + dest_diff[2] = 0.0f; + + VectorCopy(dest_diff, wanted_dir); + dist_to_wanted = VectorNormalize(wanted_dir); + if (dist_to_wanted > 0.1f && + fly_should_descend_for_lower_path(ent, following_paths, towards_origin, wanted_pos)) + fly_force_descent(ent, wanted_dir, FLY_BLOCKED_DESCENT_XY_SCALE); + + if (dist_to_wanted > 0.1f && (visible_combat_enemy || following_paths || recent_combat_memory)) + { + vec3_t climb_target; + + if (visible_combat_enemy && ent->enemy && ent->enemy->inuse) + VectorCopy(ent->enemy->s.origin, climb_target); + else + VectorCopy(towards_origin, climb_target); + + if (fly_try_climb_for_higher_target(ent, climb_target, wanted_dir)) + catchup_goal = true; + } + + if (visible_combat_enemy && dist_to_wanted > 0.1f && + ent->enemy && ent->enemy->inuse && + fly_try_prevent_combat_sight_loss(ent, ent->enemy->s.origin, ent->enemy->velocity, wanted_dir, wanted_dir)) + { + los_preserve_drive = true; + ent->monsterinfo.fly_position_time = 0.0f; + ent->monsterinfo.fly_pinned = false; + } + else if (!visible_combat_enemy && recent_combat_memory && dist_to_wanted > 0.1f && + ent->enemy && ent->enemy->inuse && + fly_try_recover_combat_sight(ent, ent->enemy->s.origin, ent->enemy->velocity, wanted_dir, wanted_dir)) + { + los_recover_drive = true; + ent->monsterinfo.fly_position_time = 0.0f; + ent->monsterinfo.fly_pinned = false; + } + + if (visible_combat_enemy) + { + fly_face_target(ent, ent->enemy->s.origin); + M_ChangeYaw(ent); + } + else if (los_recover_drive && ent->enemy && ent->enemy->inuse) + { + fly_face_target(ent, ent->enemy->s.origin); + M_ChangeYaw(ent); + } + else + { + fly_face_target(ent, towards_origin); + M_ChangeYaw(ent); + } + + if (dist_to_wanted > 0.1f) + { + VectorMA(ent->s.origin, ent->monsterinfo.fly_acceleration, wanted_dir, trace_end); + tr = gi.trace(ent->s.origin, ent->mins, ent->maxs, trace_end, ent, MASK_SOLID | CONTENTS_MONSTERCLIP); + } + else + { + tr.fraction = 1.0f; + } + + VectorSet(yaw_angles, 0.0f, ent->s.angles[YAW], 0.0f); + AngleVectors(yaw_angles, aim_fwd, aim_rgt, aim_up); + + if (tr.fraction < 0.25f && dist_to_wanted > 0.1f) + { + vec3_t bottom_pos, top_pos, startb, floor_check, side_offset, lateral_offset, left_start, right_start, obstacle_normal; + qboolean bottom_visible, top_visible, left_visible, right_visible, force_descent; + + if (ent->monsterinfo.fly_wall_stuck_time == 0.0f) + ent->monsterinfo.fly_wall_stuck_time = level.time; + + VectorCopy(tr.plane.normal, obstacle_normal); + + VectorCopy(ent->s.origin, bottom_pos); + bottom_pos[2] += ent->mins[2]; + VectorCopy(ent->s.origin, top_pos); + top_pos[2] += ent->maxs[2]; + + VectorCopy(ent->s.origin, startb); + startb[2] += ent->mins[2] - ent->monsterinfo.fly_acceleration; + bottom_visible = SV_flystep_testvisposition(bottom_pos, wanted_pos, ent->s.origin, startb, ent); + + VectorCopy(top_pos, startb); + startb[2] += ent->monsterinfo.fly_acceleration; + top_visible = SV_flystep_testvisposition(top_pos, wanted_pos, ent->s.origin, startb, ent); + + force_descent = false; + if (visible_combat_enemy && + fly_try_preserve_combat_sight(ent, towards_origin, towards_velocity, wanted_dir, obstacle_normal, wanted_dir)) + { + los_preserve_drive = true; + ent->monsterinfo.fly_position_time = 0.0f; + ent->monsterinfo.fly_pinned = false; + } + else if ((following_paths || visible_combat_enemy) && + ent->s.origin[2] > towards_origin[2] + FLY_BLOCKED_DESCENT_HEIGHT && + fly_force_descent(ent, wanted_dir, FLY_BLOCKED_DESCENT_XY_SCALE)) + { + force_descent = true; + } + else if (level.time > ent->monsterinfo.fly_wall_stuck_time + + (visible_combat_enemy ? FLY_COMBAT_WALL_STUCK_THRESHOLD : FLY_WALL_STUCK_THRESHOLD)) + { + VectorCopy(ent->s.origin, floor_check); + floor_check[2] -= 512.0f; + tr = gi.trace(ent->s.origin, ent->mins, ent->maxs, floor_check, ent, MASK_SOLID | CONTENTS_MONSTERCLIP); + if (tr.fraction < 1.0f && fly_force_descent(ent, wanted_dir, 0.3f)) + { + force_descent = true; + } + } + + if (!los_preserve_drive && !force_descent) + { + if (bottom_visible == top_visible) + { + side_offset[0] = aim_fwd[0] * ent->maxs[0]; + side_offset[1] = aim_fwd[1] * ent->maxs[1]; + side_offset[2] = aim_fwd[2] * ent->maxs[2]; + lateral_offset[0] = aim_rgt[0] * ent->maxs[0]; + lateral_offset[1] = aim_rgt[1] * ent->maxs[1]; + lateral_offset[2] = aim_rgt[2] * ent->maxs[2]; + + VectorAdd(ent->s.origin, side_offset, left_start); + VectorSubtract(left_start, lateral_offset, left_start); + VectorAdd(ent->s.origin, side_offset, right_start); + VectorAdd(right_start, lateral_offset, right_start); + + left_visible = gi.trace(left_start, NULL, NULL, wanted_pos, ent, MASK_SOLID | CONTENTS_MONSTERCLIP).fraction == 1.0f; + right_visible = gi.trace(right_start, NULL, NULL, wanted_pos, ent, MASK_SOLID | CONTENTS_MONSTERCLIP).fraction == 1.0f; + + if (left_visible != right_visible) + { + if (right_visible) + VectorAdd(wanted_dir, aim_rgt, wanted_dir); + else + VectorSubtract(wanted_dir, aim_rgt, wanted_dir); + } + else + { + VectorCopy(obstacle_normal, wanted_dir); + wanted_dir[2] -= 0.2f; + } + } + else + { + if (top_visible) + VectorAdd(wanted_dir, aim_up, wanted_dir); + else + VectorSubtract(wanted_dir, aim_up, wanted_dir); + } + VectorNormalize(wanted_dir); + } + } + else + { + ent->monsterinfo.fly_wall_stuck_time = 0.0f; + } + + bad_movement_direction = false; + if (dist_to_wanted > 0.1f) + { + vec3_t contents_test; + VectorMA(ent->s.origin, max(current_speed, ent->monsterinfo.fly_speed) * FRAMETIME, wanted_dir, contents_test); + if ((ent->flags & FL_FLY) && ent->waterlevel < 3) + bad_movement_direction = (gi.pointcontents(contents_test) & MASK_WATER) != 0; + else if (ent->flags & FL_SWIM) + bad_movement_direction = (gi.pointcontents(contents_test) & MASK_WATER) == 0; + } + + if (bad_movement_direction) + { + if (ent->monsterinfo.fly_recovery_time < level.time) + { + VectorSet(ent->monsterinfo.fly_recovery_dir, crandom(), crandom(), crandom()); + if (VectorNormalize(ent->monsterinfo.fly_recovery_dir) < 0.1f) + VectorSet(ent->monsterinfo.fly_recovery_dir, 0.0f, 0.0f, 1.0f); + ent->monsterinfo.fly_recovery_time = level.time + 1.0f; + } + VectorCopy(ent->monsterinfo.fly_recovery_dir, wanted_dir); + } + + los_pressure_drive = los_preserve_drive || los_recover_drive; + if (dir[0] || dir[1] || dir[2]) + { + float dir_dot = DotProduct(dir, wanted_dir); + + if (los_pressure_drive && dir_dot < FLY_LOS_PRESSURE_SNAP_DOT) + { + VectorCopy(wanted_dir, final_dir); + } + else if (los_pressure_drive) + { + turn_factor = FLY_LOS_PRESERVE_TURN_FACTOR; + fly_scale_add(final_dir, dir, turn_factor, wanted_dir, 1.0f - turn_factor); + if (VectorNormalize(final_dir) < 0.1f) + VectorCopy(wanted_dir, final_dir); + } + else if (catchup_goal && dir_dot < 0.0f) + { + VectorCopy(wanted_dir, final_dir); + } + else if (catchup_goal && dir_dot < 0.7f) + { + turn_factor = 0.2f; + fly_scale_add(final_dir, dir, turn_factor, wanted_dir, 1.0f - turn_factor); + if (VectorNormalize(final_dir) < 0.1f) + VectorCopy(wanted_dir, final_dir); + } + else if (((ent->monsterinfo.fly_thrusters && !ent->monsterinfo.fly_pinned) || following_paths) && + DotProduct(dir, wanted_dir) > 0.0f) + { + turn_factor = FLY_TURN_FACTOR_FAST; + fly_scale_add(final_dir, dir, turn_factor, wanted_dir, 1.0f - turn_factor); + if (VectorNormalize(final_dir) < 0.1f) + VectorCopy(wanted_dir, final_dir); + } + else + { + turn_factor = min(1.0f, FLY_TURN_FACTOR_BASE + + (FLY_TURN_FACTOR_SPEED_SCALE * (current_speed / ent->monsterinfo.fly_speed))); + fly_scale_add(final_dir, dir, turn_factor, wanted_dir, 1.0f - turn_factor); + if (VectorNormalize(final_dir) < 0.1f) + VectorCopy(wanted_dir, final_dir); + } + } + else + { + VectorCopy(wanted_dir, final_dir); + } + + base_fly_speed = ent->monsterinfo.fly_speed; + accel = ent->monsterinfo.fly_acceleration; + combat_attack_drive = visible_combat_enemy && + (ent->monsterinfo.attack_state == AS_STRAIGHT || ent->monsterinfo.attack_state == AS_SLIDING); + if (combat_attack_drive) + { + base_fly_speed *= FLY_ATTACK_SPEED_SCALE; + accel *= FLY_ATTACK_ACCEL_SCALE; + } + if (los_pressure_drive) + { + base_fly_speed *= FLY_LOS_PRESERVE_SPEED_SCALE; + accel *= FLY_LOS_PRESERVE_ACCEL_SCALE; + } + + if (!ent->enemy || (ent->monsterinfo.fly_thrusters && !ent->monsterinfo.fly_pinned) || following_paths || catchup_goal) + { + if (following_paths && (dir[0] || dir[1] || dir[2]) && DotProduct(wanted_dir, dir) < -0.25f) + speed_factor = 0.0f; + else + speed_factor = 1.0f; + } + else + { + speed_factor = min(1.0f, dist_to_wanted / base_fly_speed); + } + + if (bad_movement_direction) + speed_factor = -speed_factor; + + if (DotProduct(final_dir, wanted_dir) < 0.25f) + accel *= 2.0f; + + wanted_speed = base_fly_speed * speed_factor; + if (current_speed > wanted_speed) + current_speed = max(wanted_speed, current_speed - accel); + else if (current_speed < wanted_speed) + current_speed = min(wanted_speed, current_speed + accel); + + if (!fly_vector_valid(final_dir) || !isfinite(current_speed)) + return false; + + VectorScale(final_dir, current_speed, ent->velocity); + + if (ent->enemy && (ent->monsterinfo.fly_buzzard || (ent->monsterinfo.aiflags & AI_MEDIC))) + { + vec3_t pitch_dir, pitch_angles, pitch_target; + if (visible_combat_enemy) + VectorCopy(ent->enemy->s.origin, pitch_target); + else + VectorCopy(towards_origin, pitch_target); + + VectorSubtract(ent->s.origin, pitch_target, pitch_dir); + if (VectorNormalize(pitch_dir) > 0.1f) + { + vectoangles(pitch_dir, pitch_angles); + ent->s.angles[PITCH] = LerpAngle(ent->s.angles[PITCH], -pitch_angles[PITCH], FRAMETIME * FLY_PITCH_LERP_SPEED); + } + } + else + { + ent->s.angles[PITCH] = 0.0f; + } + + return true; +} + qboolean M_FlyMove(edict_t* ent, vec3_t dest, vec3_t move, qboolean relink) { //float dz; // delta Z @@ -413,6 +1909,22 @@ qboolean M_FlyMove(edict_t* ent, vec3_t dest, vec3_t move, qboolean relink) float zSpeed = 8; // Z movement speed trace_t trace; vec3_t neworg1, neworg2; + + if (ent->monsterinfo.aiflags & AI_ALTERNATE_FLY) + { + if (fly_use_alternate_step(ent, dest)) + { + qboolean use_path_dest = dest && !fly_has_visible_combat_enemy(ent); + + // Once a flyer has visible combat contact, let the alternate + // fly logic steer by enemy/hover offset instead of parking on path nodes. + if (SV_alternate_flystep(ent, use_path_dest ? dest : NULL, move, relink)) + return true; + } + + fly_reset_alternate_step(ent); + } + if (!M_MoveVertical(ent, dest, neworg1)) { //gi.dprintf("movevertical failed\n"); @@ -485,6 +1997,19 @@ qboolean SV_movestep(edict_t* ent, vec3_t dest, vec3_t move, qboolean relink) { if (ent->flags & FL_FLY) return M_FlyMove(ent, dest, move, relink); + + if (fly_use_alternate_step(ent, dest)) + { + if (SV_alternate_flystep(ent, NULL, move, relink)) + return true; + + fly_reset_alternate_step(ent); + } + else if (ent->monsterinfo.aiflags & AI_ALTERNATE_FLY) + { + fly_reset_alternate_step(ent); + } + // gi.dprintf("trying to swim\n"); // try one move with vertical motion, then one without for (i = 0; i < 2; i++) @@ -1433,7 +2958,8 @@ void M_MoveToGoal (edict_t *ent, float dist) // if the next step hits the enemy, return immediately if (ent->enemy && (ent->enemy->solid == SOLID_BBOX) - && SV_CloseEnough (ent, ent->enemy, dist) ) + && SV_CloseEnough (ent, ent->enemy, dist) + && !((ent->monsterinfo.aiflags & AI_ALTERNATE_FLY) && fly_use_alternate_step(ent, NULL)) ) //GHz START { vec3_t v; @@ -1587,4 +3113,3 @@ qboolean M_walkmove (edict_t *ent, float yaw, float dist) else return false; } - diff --git a/src/g_local.h b/src/g_local.h index 93dc82fb..c7e07039 100644 --- a/src/g_local.h +++ b/src/g_local.h @@ -241,6 +241,7 @@ typedef enum { #define AI_PURSUE_PLAT_GOAL 0x00040000 #define AI_DODGE 0x00080000 #define AI_SNAP_TO_NAVI 0x00100000 +#define AI_ALTERNATE_FLY 0x00200000 //monster attack state #define AS_STRAIGHT 1 @@ -678,6 +679,22 @@ typedef struct { dmglist_t dmglist[MAX_CLIENTS]; // keep track of damage by players qboolean slots_freed; // true if player slots have been refunded prior to removal + // Remaster-style alternate flying mechanics. + float fly_max_distance; + float fly_min_distance; + float fly_acceleration; + float fly_speed; + vec3_t fly_ideal_position; + float fly_position_time; + qboolean fly_buzzard; + qboolean fly_above; + qboolean fly_pinned; + qboolean fly_thrusters; + float fly_recovery_time; + vec3_t fly_recovery_dir; + float fly_wall_stuck_time; + float fly_separation_time; + // az begin // targeting diff --git a/src/quake2/g_phys.c b/src/quake2/g_phys.c index a5e03534..25b96280 100644 --- a/src/quake2/g_phys.c +++ b/src/quake2/g_phys.c @@ -839,7 +839,8 @@ void SV_Physics_Step (edict_t *ent) } // friction for flying monsters that have been given vertical velocity - if ((ent->flags & FL_FLY) && (ent->velocity[2] != 0)) + if ((ent->flags & FL_FLY) && (ent->velocity[2] != 0) + && !(ent->monsterinfo.aiflags & AI_ALTERNATE_FLY)) { //gi.bprintf(PRINT_HIGH,"FLY!\n"); speed = fabs(ent->velocity[2]); @@ -880,7 +881,8 @@ void SV_Physics_Step (edict_t *ent) //gi.dprintf("velocity is nonzero\n"); // apply friction // let dead monsters who aren't completely onground slide - if ((wasonground) || (ent->flags & (FL_SWIM|FL_FLY))) + if (((wasonground) || (ent->flags & (FL_SWIM|FL_FLY))) + && !(ent->monsterinfo.aiflags & AI_ALTERNATE_FLY)) if (!(ent->health <= 0.0 && !M_CheckBottom(ent))) { //K03 Begin From 872b5aefe58a60086d16bde6307d29d7e2c55ace Mon Sep 17 00:00:00 2001 From: Enemy-123 Date: Sat, 2 May 2026 18:38:17 -0400 Subject: [PATCH 22/24] Modified monster_fire_grenade, grenade2 and acid projectile arcs --- src/combat/abilities/gloom.c | 62 ++++++++++++++++++++++++++++++- src/combat/weapons/g_weapon.c | 70 +++++++++++++++++++++++++++++++++-- 2 files changed, 126 insertions(+), 6 deletions(-) diff --git a/src/combat/abilities/gloom.c b/src/combat/abilities/gloom.c index 35512f04..7c6a9b7d 100644 --- a/src/combat/abilities/gloom.c +++ b/src/combat/abilities/gloom.c @@ -3660,18 +3660,76 @@ void acid_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf acid_explode(ent); } +#define ACID_VERTICAL_BOOST 200.0f + +static float acid_throwing_pitch(vec3_t start, vec3_t end, float speed, float vertical_boost) +{ + const float gravity = sv_gravity->value; + const float dist = Get2dDistance(start, end); + const float height = end[2] - start[2]; + const float effective_speed = sqrtf(speed * speed + vertical_boost * vertical_boost); + const double speed_sq = (double)effective_speed * effective_speed; + const double discriminant = speed_sq * speed_sq - (double)gravity * + ((double)gravity * dist * dist + 2.0 * height * speed_sq); + float pitch, boost_angle; + + if (speed <= 0 || gravity <= 0 || dist < 1.0f || discriminant < 0) + return -999; + + pitch = (float)(-atan((speed_sq - sqrt(discriminant)) / ((double)gravity * dist)) * (180.0 / M_PI)); + boost_angle = (float)(atan2(vertical_boost, speed) * (180.0 / M_PI)); + return pitch + boost_angle; +} + +static qboolean monster_adjust_acid_aim(edict_t *self, vec3_t start, int speed, vec3_t aimdir) +{ + float dist, flat_len, pitch; + vec3_t angles, flat, target; + + if (!(self->svflags & SVF_MONSTER) || !G_EntExists(self->enemy) || speed <= 100) + return false; + + if (!M_MonsterFindClearShot(self, start, target)) + G_EntMidPoint(self->enemy, target); + + dist = Get2dDistance(start, target); + VectorCopy(aimdir, flat); + flat[2] = 0; + flat_len = VectorNormalize(flat); + if (dist < 1.0f || flat_len < 0.01f) + return false; + + target[0] = start[0] + flat[0] * dist; + target[1] = start[1] + flat[1] * dist; + target[2] = start[2] + aimdir[2] / flat_len * dist; + + pitch = acid_throwing_pitch(start, target, speed, ACID_VERTICAL_BOOST); + if (pitch < -90) + return false; + + vectoangles(aimdir, angles); + angles[PITCH] = pitch; + AngleVectors(angles, aimdir, NULL, NULL); + VectorNormalize(aimdir); + return true; +} + void fire_acid (edict_t *self, vec3_t start, vec3_t aimdir, int projectile_damage, float radius, int speed, int acid_damage, float acid_duration, int gas_damage, float gas_radius, float gas_duration) { edict_t *grenade; vec3_t dir; vec3_t forward, right, up; + vec3_t adjusted_aim; // calling entity made a sound, used to alert monsters self->lastsound = level.framenum; + VectorCopy(aimdir, adjusted_aim); + monster_adjust_acid_aim(self, start, speed, adjusted_aim); + // get aiming angles - vectoangles(aimdir, dir); + vectoangles(adjusted_aim, dir); // get directional vectors AngleVectors(dir, forward, right, up); @@ -3704,7 +3762,7 @@ void fire_acid (edict_t *self, vec3_t start, vec3_t aimdir, int projectile_damag grenade->nextthink = level.time + 10.0; // adjust velocity - VectorScale (aimdir, speed, grenade->velocity); + VectorScale (adjusted_aim, speed, grenade->velocity); VectorMA (grenade->velocity, 200 + crandom() * 10.0, up, grenade->velocity); VectorMA (grenade->velocity, crandom() * 10.0, right, grenade->velocity); VectorSet (grenade->avelocity, 300, 300, 300); diff --git a/src/combat/weapons/g_weapon.c b/src/combat/weapons/g_weapon.c index 6e49b82c..f1572449 100644 --- a/src/combat/weapons/g_weapon.c +++ b/src/combat/weapons/g_weapon.c @@ -984,21 +984,79 @@ static void Grenade_Touch (edict_t *ent, edict_t *other, cplane_t *plane, csurfa //Cluster_Explode(ent); } +#define GRENADE_VERTICAL_BOOST 200.0f + +static float grenade_throwing_pitch(vec3_t start, vec3_t end, float speed, float vertical_boost) +{ + const float gravity = sv_gravity->value; + const float dist = Get2dDistance(start, end); + const float height = end[2] - start[2]; + const float effective_speed = sqrtf(speed * speed + vertical_boost * vertical_boost); + const double speed_sq = (double)effective_speed * effective_speed; + const double discriminant = speed_sq * speed_sq - (double)gravity * + ((double)gravity * dist * dist + 2.0 * height * speed_sq); + float pitch, boost_angle; + + if (speed <= 0 || gravity <= 0 || dist < 1.0f || discriminant < 0) + return -999; + + pitch = (float)(-atan((speed_sq - sqrt(discriminant)) / ((double)gravity * dist)) * (180.0 / M_PI)); + boost_angle = (float)(atan2(vertical_boost, speed) * (180.0 / M_PI)); + return pitch + boost_angle; +} + +static qboolean monster_adjust_grenade_aim(edict_t *self, vec3_t start, int speed, vec3_t aimdir) +{ + float dist, flat_len, pitch; + vec3_t angles, flat, target; + + if (!(self->svflags & SVF_MONSTER) || !G_EntExists(self->enemy) || speed <= 100) + return false; + + if (!M_MonsterFindClearShot(self, start, target)) + G_EntMidPoint(self->enemy, target); + + dist = Get2dDistance(start, target); + VectorCopy(aimdir, flat); + flat[2] = 0; + flat_len = VectorNormalize(flat); + if (dist < 1.0f || flat_len < 0.01f) + return false; + + target[0] = start[0] + flat[0] * dist; + target[1] = start[1] + flat[1] * dist; + target[2] = start[2] + aimdir[2] / flat_len * dist; + + pitch = grenade_throwing_pitch(start, target, speed, GRENADE_VERTICAL_BOOST); + if (pitch < -90) + return false; + + vectoangles(aimdir, angles); + angles[PITCH] = pitch; + AngleVectors(angles, aimdir, NULL, NULL); + VectorNormalize(aimdir); + return true; +} + void fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius, int radius_damage) { edict_t *grenade; vec3_t dir; vec3_t forward, right, up; + vec3_t adjusted_aim; // calling entity made a sound, used to alert monsters self->lastsound = level.framenum; - vectoangles (aimdir, dir); + VectorCopy(aimdir, adjusted_aim); + monster_adjust_grenade_aim(self, start, speed, adjusted_aim); + + vectoangles (adjusted_aim, dir); AngleVectors (dir, forward, right, up); grenade = G_Spawn(); VectorCopy (start, grenade->s.origin); - VectorScale (aimdir, speed, grenade->velocity); + VectorScale (adjusted_aim, speed, grenade->velocity); VectorMA (grenade->velocity, 200 + crandom() * 10.0, up, grenade->velocity); VectorMA (grenade->velocity, crandom() * 10.0, right, grenade->velocity); VectorSet (grenade->avelocity, 300, 300, 300); @@ -1044,16 +1102,20 @@ edict_t *fire_grenade2 (edict_t *self, vec3_t start, vec3_t aimdir, int damage, edict_t *grenade; vec3_t dir; vec3_t forward, right, up; + vec3_t adjusted_aim; // calling entity made a sound, used to alert monsters self->lastsound = level.framenum; - vectoangles (aimdir, dir); + VectorCopy(aimdir, adjusted_aim); + monster_adjust_grenade_aim(self, start, speed, adjusted_aim); + + vectoangles (adjusted_aim, dir); AngleVectors (dir, forward, right, up); grenade = G_Spawn(); VectorCopy (start, grenade->s.origin); - VectorScale (aimdir, speed, grenade->velocity); + VectorScale (adjusted_aim, speed, grenade->velocity); VectorMA (grenade->velocity, 200 + crandom() * 10.0, up, grenade->velocity); VectorMA (grenade->velocity, crandom() * 10.0, right, grenade->velocity); VectorSet (grenade->avelocity, 300, 300, 300); From f5dae344564af6c1bdff906afa2ab3d83296194e Mon Sep 17 00:00:00 2001 From: Enemy-123 Date: Tue, 5 May 2026 11:09:06 -0400 Subject: [PATCH 23/24] new file drone_gibs.c, remaster's gibs models added, new flag FL_ALWAYS_TOUCH for new GIB_UPRIGHT's pieces correcting their pitch/placement, improved/added animations on some monsters, fix gib rotating on some angled floors, removed a if nightmare preventing pain animations on new monsters, search sounds random between idle --- src/characters/v_utils.c | 6 + src/combat/common/v_misc.c | 2 +- src/entities/drone/drone_arachnid.c | 13 +- src/entities/drone/drone_berserk.c | 109 +++-- src/entities/drone/drone_bitch.c | 14 +- src/entities/drone/drone_boss2.c | 106 +++-- src/entities/drone/drone_brain.c | 14 +- src/entities/drone/drone_carrier.c | 45 +- src/entities/drone/drone_daedalus.c | 9 +- src/entities/drone/drone_fixbot.c | 35 +- src/entities/drone/drone_float.c | 5 +- src/entities/drone/drone_flyer.c | 5 +- src/entities/drone/drone_gekk.c | 513 ++++++++++++++------ src/entities/drone/drone_gibs.c | 597 ++++++++++++++++++++++++ src/entities/drone/drone_gladiator.c | 37 +- src/entities/drone/drone_guardian.c | 26 +- src/entities/drone/drone_guncmdr.c | 24 +- src/entities/drone/drone_gunner.c | 12 +- src/entities/drone/drone_hover.c | 26 +- src/entities/drone/drone_infantry.c | 39 +- src/entities/drone/drone_jorg.c | 99 +++- src/entities/drone/drone_makron.c | 109 ++++- src/entities/drone/drone_medic.c | 26 +- src/entities/drone/drone_mutant.c | 77 ++- src/entities/drone/drone_parasite.c | 11 +- src/entities/drone/drone_redmutant.c | 14 +- src/entities/drone/drone_rogue_turret.c | 5 +- src/entities/drone/drone_runnertank.c | 92 ++-- src/entities/drone/drone_shambler.c | 11 +- src/entities/drone/drone_soldier.c | 334 +++++++++++-- src/entities/drone/drone_stalker.c | 107 ++++- src/entities/drone/drone_supertank.c | 93 +++- src/entities/drone/drone_tank.c | 53 ++- src/entities/drone/drone_widow.c | 239 +++++++++- src/entities/drone/drone_widow2.c | 225 ++++++++- src/entities/g_misc.c | 83 +++- src/entities/g_spawn.c | 1 + src/g_local.h | 13 +- src/quake2/g_phys.c | 38 +- 39 files changed, 2647 insertions(+), 620 deletions(-) create mode 100644 src/entities/drone/drone_gibs.c diff --git a/src/characters/v_utils.c b/src/characters/v_utils.c index 399ccee9..3855dd57 100644 --- a/src/characters/v_utils.c +++ b/src/characters/v_utils.c @@ -2898,6 +2898,12 @@ void vrx_update_drone_death_skin(edict_t* ent) if (!vrx_has_pain_skin(ent)) return; + if (ent->mtype == M_GEKK) + { + ent->s.skinnum = (ent->health < 0.25f * ent->max_health) ? 2 : 1; + return; + } + if (ent->mtype == M_BARON_FIRE && ent->health < 0.2f * ent->max_health) ent->s.skinnum = 2; else diff --git a/src/combat/common/v_misc.c b/src/combat/common/v_misc.c index d444d61b..7594bde4 100644 --- a/src/combat/common/v_misc.c +++ b/src/combat/common/v_misc.c @@ -913,7 +913,7 @@ void ThrowDeadlyGib(edict_t* self, char* modelname, vec3_t origin, vec3_t dir, i gib->creator = self; gib->die = deadly_gib_die; gib->touch = shrapnel_touch; - if (type == GIB_ORGANIC) + if (!(type & GIB_METALLIC)) gib->movetype = MOVETYPE_TOSS; else gib->movetype = MOVETYPE_BOUNCE; diff --git a/src/entities/drone/drone_arachnid.c b/src/entities/drone/drone_arachnid.c index 19b2e2eb..0415f5c7 100644 --- a/src/entities/drone/drone_arachnid.c +++ b/src/entities/drone/drone_arachnid.c @@ -291,7 +291,7 @@ static void arachnid_pain(edict_t *self, edict_t *other, float kick, int damage) self->pain_debounce_time = level.time + 3.0; gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); - if (skill->value == 3) + if (invasion->value == 2) return; if (random() < 0.5) @@ -337,21 +337,12 @@ mmove_t arachnid_move_death = { FRAME_death1, FRAME_death20, arachnid_frames_dea static void arachnid_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) { - int n; - M_Notify(self); if (self->health <= self->gib_health) { gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); - if (vrx_spawn_nonessential_ent(self->s.origin)) - { - for (n = 0; n < 2; n++) - ThrowGib(self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); - for (n = 0; n < 4; n++) - ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); - ThrowHead(self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); - } + vrx_throw_drone_gibs(self, damage); M_Remove(self, false, false); return; } diff --git a/src/entities/drone/drone_berserk.c b/src/entities/drone/drone_berserk.c index 1f4efb75..323ce09f 100644 --- a/src/entities/drone/drone_berserk.c +++ b/src/entities/drone/drone_berserk.c @@ -20,6 +20,10 @@ static int sound_search; static void berserk_ai_dodge_slide(edict_t *self, float dist); static void berserk_duck_up(edict_t *self); +#define BERSERK_CLOSE_MELEE_RANGE 96 +#define BERSERK_SLAM_MELEE_RANGE 128 +#define BERSERK_SLAM_COOLDOWN 3.0f + void berserk_sight (edict_t *self, edict_t *other) { @@ -153,6 +157,8 @@ void berserk_swing (edict_t *self) mframe_t berserk_frames_attack_spike [] = { + ai_charge, 0, NULL, + ai_charge, 0, NULL, ai_charge, 0, berserk_swing, ai_charge, 0, berserk_attack_spike, ai_charge, 0, NULL, @@ -160,7 +166,7 @@ mframe_t berserk_frames_attack_spike [] = ai_charge, 0, NULL, ai_charge, 0, NULL }; -mmove_t berserk_move_attack_spike = {FRAME_att_c3, FRAME_att_c8, berserk_frames_attack_spike, berserk_run}; +mmove_t berserk_move_attack_spike = {FRAME_att_c1, FRAME_att_c8, berserk_frames_attack_spike, berserk_run}; void berserk_attack_club (edict_t *self) { @@ -178,25 +184,55 @@ void berserk_attack_club (edict_t *self) mframe_t berserk_frames_attack_club [] = { + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, ai_charge, 0, berserk_swing, ai_charge, 0, NULL, ai_charge, 0, berserk_attack_club, ai_charge, 0, berserk_attack_club, ai_charge, 0, berserk_attack_club, + ai_charge, 0, NULL, + ai_charge, 0, NULL, ai_charge, 0, NULL }; -mmove_t berserk_move_attack_club = {FRAME_att_b10, FRAME_att_b15, berserk_frames_attack_club, berserk_run}; +mmove_t berserk_move_attack_club = {FRAME_att_c9, FRAME_att_c20, berserk_frames_attack_club, berserk_run}; + +static void berserk_run_attack_speed(edict_t *self) +{ + if (G_EntExists(self->enemy) && entdist(self, self->enemy) <= BERSERK_CLOSE_MELEE_RANGE) + self->monsterinfo.nextframe = self->s.frame + 6; +} + +static void berserk_run_swing(edict_t *self) +{ + berserk_swing(self); + self->monsterinfo.melee_finished = level.time + 0.6f; +} mframe_t berserk_frames_runattack1 [] = { - drone_ai_run, 35, berserk_swing, - drone_ai_run, 35, NULL, - drone_ai_run, 35, berserk_attack_club, - drone_ai_run, 35, berserk_attack_club, - drone_ai_run, 35, berserk_attack_club, - drone_ai_run, 35, NULL + drone_ai_run, 21, berserk_run_attack_speed, + drone_ai_run, 11, berserk_run_attack_speed, + drone_ai_run, 21, berserk_run_attack_speed, + drone_ai_run, 25, berserk_run_attack_speed, + drone_ai_run, 18, berserk_run_attack_speed, + drone_ai_run, 19, berserk_run_attack_speed, + drone_ai_run, 21, NULL, + drone_ai_run, 11, NULL, + drone_ai_run, 21, NULL, + drone_ai_run, 25, NULL, + drone_ai_run, 18, NULL, + drone_ai_run, 19, NULL, + drone_ai_run, 21, berserk_run_swing, + drone_ai_run, 11, NULL, + drone_ai_run, 21, berserk_attack_club, + drone_ai_run, 25, berserk_attack_club, + drone_ai_run, 18, berserk_attack_club, + drone_ai_run, 19, NULL }; -mmove_t berserk_move_runattack1 = {FRAME_r_att13, FRAME_r_att18, berserk_frames_runattack1, berserk_run}; +mmove_t berserk_move_runattack1 = {FRAME_r_att1, FRAME_r_att18, berserk_frames_runattack1, berserk_run}; void berserk_attack_strike (edict_t *self) @@ -245,6 +281,18 @@ mframe_t berserk_frames_attack_strike [] = mmove_t berserk_move_attack_strike = {FRAME_slam5, FRAME_slam10, berserk_frames_attack_strike, berserk_run}; +static qboolean berserk_can_slam(edict_t *self, float dist) +{ + return self->groundentity && level.time >= self->timestamp && + dist > BERSERK_CLOSE_MELEE_RANGE && dist <= BERSERK_SLAM_MELEE_RANGE; +} + +static void berserk_start_slam(edict_t *self) +{ + self->timestamp = level.time + BERSERK_SLAM_COOLDOWN; + self->monsterinfo.currentmove = &berserk_move_attack_strike; +} + void berserk_dead (edict_t *self) { VectorSet (self->mins, -16, -16, -24); @@ -541,8 +589,6 @@ void berserk_pain(edict_t* self, edict_t* other, float kick, int damage) void berserk_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) { - int n; - M_Notify(self); #ifdef OLD_NOLAG_STYLE @@ -558,14 +604,7 @@ void berserk_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int dama if (self->health <= self->gib_health) { gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); - if (vrx_spawn_nonessential_ent(self->s.origin)) - { - for (n = 0; n < 2; n++) - ThrowGib(self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); - for (n = 0; n < 4; n++) - ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); - //ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); - } + vrx_throw_drone_gibs(self, damage); #ifdef OLD_NOLAG_STYLE M_Remove(self, false, false); #else @@ -603,23 +642,31 @@ void berserk_attack (edict_t *self) float r = random(); const float dist = entdist(self, self->enemy); - if (dist > 128) + if (dist > 160) return; if (self->monsterinfo.aiflags & AI_STAND_GROUND) { - if (random() <= 0.3 && dist <= 96) + if (dist <= BERSERK_CLOSE_MELEE_RANGE && r <= 0.45f) + self->monsterinfo.currentmove = &berserk_move_attack_spike; + else if (dist <= BERSERK_CLOSE_MELEE_RANGE) self->monsterinfo.currentmove = &berserk_move_attack_club; + else if (berserk_can_slam(self, dist)) + berserk_start_slam(self); else - self->monsterinfo.currentmove = &berserk_move_attack_strike; + return; } - else if (dist <= 96) + else if (dist <= BERSERK_CLOSE_MELEE_RANGE) { - if (random() <= 0.2) - self->monsterinfo.currentmove = &berserk_move_attack_strike; + if (r <= 0.45f) + self->monsterinfo.currentmove = &berserk_move_attack_spike; else self->monsterinfo.currentmove = &berserk_move_runattack1; } + else if (berserk_can_slam(self, dist)) + berserk_start_slam(self); + else if (self->monsterinfo.currentmove == &berserk_move_run1) + self->monsterinfo.currentmove = &berserk_move_runattack1; else return; @@ -634,13 +681,17 @@ void berserk_melee (edict_t *self) return; dist = entdist(self, self->enemy); - if (dist > 128) + if (dist > BERSERK_SLAM_MELEE_RANGE) return; - if (random() <= 0.3 && dist <= 96) + if (dist <= BERSERK_CLOSE_MELEE_RANGE && random() <= 0.45f) + self->monsterinfo.currentmove = &berserk_move_attack_spike; + else if (dist <= BERSERK_CLOSE_MELEE_RANGE) self->monsterinfo.currentmove = &berserk_move_attack_club; + else if (berserk_can_slam(self, dist)) + berserk_start_slam(self); else - self->monsterinfo.currentmove = &berserk_move_attack_strike; + return; self->monsterinfo.melee_finished = level.time + 0.4; self->monsterinfo.attack_finished = level.time + 0.6; @@ -688,7 +739,7 @@ void init_drone_berserk (edict_t *self) self->monsterinfo.attack = berserk_attack; self->monsterinfo.melee = berserk_melee; self->monsterinfo.sight = berserk_sight; - //self->monsterinfo.search = berserk_search; + self->monsterinfo.idle = berserk_search; self->monsterinfo.currentmove = &berserk_move_stand; self->monsterinfo.scale = MODEL_SCALE; diff --git a/src/entities/drone/drone_bitch.c b/src/entities/drone/drone_bitch.c index 705a1b8a..f6aae1b1 100644 --- a/src/entities/drone/drone_bitch.c +++ b/src/entities/drone/drone_bitch.c @@ -300,14 +300,7 @@ void mychick_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int dama if (self->health <= self->gib_health) { gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); - if (vrx_spawn_nonessential_ent(self->s.origin)) - { - for (n = 0; n < 2; n++) - ThrowGib(self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); - for (n = 0; n < 4; n++) - ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); - //ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); - } + vrx_throw_drone_gibs(self, damage); //self->deadflag = DEAD_DEAD; #ifdef OLD_NOLAG_STYLE M_Remove(self, false, false); @@ -899,7 +892,10 @@ void mychick_attack(edict_t *self) void mychick_sight(edict_t *self, edict_t *other) { - gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); + if (random() < 0.5) + gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); } mframe_t mychick_frames_jump [] = diff --git a/src/entities/drone/drone_boss2.c b/src/entities/drone/drone_boss2.c index e8b04112..f72bcf42 100644 --- a/src/entities/drone/drone_boss2.c +++ b/src/entities/drone/drone_boss2.c @@ -15,6 +15,8 @@ boss2 #define BOSS2_ROCKET_SPEED 750 #define BOSS2_INVASION_SCALE 0.75f #define BOSS2_INVASION_MOVE_SCALE 1.5f +#define BOSS2_SMALL_DEATH_GRAVITY 0.30f +#define BOSS2_SMALL_DEATH_FALL_SPEED -40.0f static int sound_pain1; static int sound_pain2; @@ -130,42 +132,71 @@ static void boss2_search(edict_t *self) gi.sound(self, CHAN_VOICE, sound_search1, 1, boss2_voice_attenuation(self), 0); } -static void boss2_explode(edict_t *self) +static void boss2_emit_random_explosion(edict_t *self, int effect) { vec3_t org; - VectorCopy(self->s.origin, org); - org[0] += crandom() * self->maxs[0]; - org[1] += crandom() * self->maxs[1]; - org[2] += crandom() * self->maxs[2]; + VectorAdd(self->s.origin, self->mins, org); + org[0] += random() * self->size[0]; + org[1] += random() * self->size[1]; + org[2] += random() * self->size[2]; gi.WriteByte(svc_temp_entity); - gi.WriteByte(TE_EXPLOSION1_BIG); + gi.WriteByte(effect); gi.WritePosition(org); - gi.multicast(self->s.origin, MULTICAST_PVS); + gi.multicast(org, MULTICAST_PVS); +} + +static void boss2_explosion_sequence_think(edict_t *self) +{ + edict_t *owner = self->owner; + int effect; + + if (level.time > self->timestamp || !owner || !owner->inuse + || owner->deadflag != DEAD_DEAD || owner->s.modelindex != self->style) + { + G_FreeEdict(self); + return; + } + if (self->count >= self->dmg) + { + G_FreeEdict(self); + return; + } + + effect = (self->count++ % 4) ? TE_EXPLOSION1_NL : TE_EXPLOSION1; + boss2_emit_random_explosion(owner, effect); + self->nextthink = level.time + 0.2f + random() * 0.25f; +} + +static void boss2_start_explosion_sequence(edict_t *self) +{ + edict_t *exploder; + + if (self->count) + return; + self->count = 1; + + boss2_emit_random_explosion(self, TE_EXPLOSION1_BIG); + + exploder = G_Spawn(); + exploder->classname = "boss2_exploder"; + exploder->svflags |= SVF_NOCLIENT; + exploder->solid = SOLID_NOT; + exploder->owner = self; + exploder->style = self->s.modelindex; + exploder->count = 1; + exploder->dmg = boss2_is_small(self) ? 4 : 13; + exploder->think = boss2_explosion_sequence_think; + exploder->nextthink = level.time + 0.2f + random() * 0.25f; + exploder->timestamp = level.time + (boss2_is_small(self) ? 1.5f : 5.0f); } static void boss2_dead(edict_t *self) { - int n; - - for (n = 0; n < 3; n++) - ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", 500, GIB_ORGANIC); - for (n = 0; n < 5; n++) - ThrowGib(self, "models/objects/gibs/sm_metal/tris.md2", 500, GIB_METALLIC); - - ThrowGib(self, "models/monsters/boss2/gibs/chest.md2", 500, GIB_METALLIC); - ThrowGib(self, "models/monsters/boss2/gibs/chaingun.md2", 500, GIB_METALLIC); - ThrowGib(self, "models/monsters/boss2/gibs/cpu.md2", 500, GIB_METALLIC); - ThrowGib(self, "models/monsters/boss2/gibs/engine.md2", 500, GIB_METALLIC); - ThrowGib(self, "models/monsters/boss2/gibs/rocket.md2", 500, GIB_METALLIC); - ThrowGib(self, "models/monsters/boss2/gibs/spine.md2", 500, GIB_METALLIC); - ThrowGib(self, "models/monsters/boss2/gibs/wing.md2", 500, GIB_METALLIC); - ThrowGib(self, "models/monsters/boss2/gibs/larm.md2", 500, GIB_METALLIC); - ThrowGib(self, "models/monsters/boss2/gibs/rarm.md2", 500, GIB_METALLIC); - ThrowHead(self, "models/monsters/boss2/gibs/head.md2", 500, GIB_METALLIC); - - boss2_explode(self); + vrx_throw_drone_gibs(self, 500); + + boss2_emit_random_explosion(self, TE_EXPLOSION1_BIG); M_Remove(self, false, false); } @@ -509,7 +540,7 @@ static void boss2_shrink(edict_t *self) static mframe_t boss2_frames_death[] = { - ai_move, 0, boss2_explode, + ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, @@ -523,7 +554,7 @@ static mmove_t boss2_move_death = { FRAME_death2, FRAME_death10, boss2_frames_de static mframe_t boss2_frames_deathboss[] = { - ai_move, 0, boss2_explode, + ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, @@ -644,7 +675,7 @@ static void boss2_pain(edict_t *self, edict_t *other, float kick, int damage) else gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); - if (skill->value == 3) + if (invasion->value == 2) return; if (damage < 30) self->monsterinfo.currentmove = &boss2_move_pain_light; @@ -668,9 +699,24 @@ static void boss2_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); self->s.sound = 0; self->deadflag = DEAD_DEAD; - self->takedamage = DAMAGE_YES; + self->takedamage = DAMAGE_NO; vrx_update_drone_death_skin(self); self->count = 0; + self->monsterinfo.aiflags &= ~AI_ALTERNATE_FLY; + self->monsterinfo.attack_state = AS_STRAIGHT; + VectorClear(self->velocity); + VectorClear(self->avelocity); + self->ideal_yaw = self->s.angles[YAW]; + + if (boss2_is_small(self)) + { + self->flags &= ~FL_FLY; + self->movetype = MOVETYPE_TOSS; + self->gravity = BOSS2_SMALL_DEATH_GRAVITY; + self->velocity[2] = BOSS2_SMALL_DEATH_FALL_SPEED; + } + + boss2_start_explosion_sequence(self); self->monsterinfo.currentmove = boss2_is_small(self) ? &boss2_move_death : &boss2_move_deathboss; } diff --git a/src/entities/drone/drone_brain.c b/src/entities/drone/drone_brain.c index 642f1b9d..1eadb31f 100644 --- a/src/entities/drone/drone_brain.c +++ b/src/entities/drone/drone_brain.c @@ -1042,8 +1042,6 @@ void mybrain_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *s void mybrain_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) { - int n; - M_Notify(self); // reduce lag by removing the entity right away @@ -1063,14 +1061,7 @@ void mybrain_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int dama if (self->health <= self->gib_health) { gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); - if (vrx_spawn_nonessential_ent(self->s.origin)) - { - for (n = 0; n < 2; n++) - ThrowGib(self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); - for (n = 0; n < 4; n++) - ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); - //ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); - } + vrx_throw_drone_gibs(self, damage); #ifdef OLD_NOLAG_STYLE M_Remove(self, false, false); #else @@ -1160,8 +1151,7 @@ void init_drone_brain (edict_t *self) self->monsterinfo.attack = mybrain_attack; self->monsterinfo.melee = mybrain_melee; self->monsterinfo.sight = mybrain_sight; -// self->monsterinfo.search = mybrain_search; - //self->monsterinfo.idle = mybrain_idle; + self->monsterinfo.idle = mybrain_search; self->monsterinfo.jumpup = 64; self->monsterinfo.jumpdn = 512; diff --git a/src/entities/drone/drone_carrier.c b/src/entities/drone/drone_carrier.c index 951bf52d..f0171491 100644 --- a/src/entities/drone/drone_carrier.c +++ b/src/entities/drone/drone_carrier.c @@ -128,12 +128,7 @@ static void carrier_explode(edict_t *self) static void carrier_dead(edict_t *self) { - int n; - - for (n = 0; n < 4; n++) - ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", 500, GIB_ORGANIC); - for (n = 0; n < 6; n++) - ThrowGib(self, "models/objects/gibs/sm_metal/tris.md2", 500, GIB_METALLIC); + vrx_throw_drone_gibs(self, 500); gi.WriteByte(svc_temp_entity); gi.WriteByte(TE_EXPLOSION1_BIG); @@ -855,14 +850,29 @@ static void carrier_attack(edict_t *self) M_DelayNextAttack(self, 1.0f + random(), true); } -mframe_t carrier_frames_pain[] = +mframe_t carrier_frames_pain_heavy[] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t carrier_move_pain_heavy = { FRAME_death01, FRAME_death10, carrier_frames_pain_heavy, carrier_run }; + +mframe_t carrier_frames_pain_light[] = { ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL }; -mmove_t carrier_move_pain = { FRAME_spawn01, FRAME_spawn04, carrier_frames_pain, carrier_run }; +mmove_t carrier_move_pain_light = { FRAME_spawn01, FRAME_spawn04, carrier_frames_pain_light, carrier_run }; static void carrier_pain(edict_t *self, edict_t *other, float kick, int damage) { @@ -874,16 +884,21 @@ static void carrier_pain(edict_t *self, edict_t *other, float kick, int damage) if (level.time < self->pain_debounce_time) return; - self->pain_debounce_time = level.time + 3.0; - if (random() < 0.33f) + self->pain_debounce_time = level.time + 5.0; + if (damage < 10) + gi.sound(self, CHAN_VOICE, sound_pain3, 1, ATTN_NORM, 0); + else if (damage < 30) gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); - else if (random() < 0.5f) - gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); else - gi.sound(self, CHAN_VOICE, sound_pain3, 1, ATTN_NORM, 0); + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + + if (invasion->value == 2) + return; - if (skill->value != 3) - self->monsterinfo.currentmove = &carrier_move_pain; + if (damage >= 30) + self->monsterinfo.currentmove = &carrier_move_pain_heavy; + else if (damage >= 10 && random() < 0.5f) + self->monsterinfo.currentmove = &carrier_move_pain_light; } mframe_t carrier_frames_death[] = diff --git a/src/entities/drone/drone_daedalus.c b/src/entities/drone/drone_daedalus.c index 2cdeb211..5a14592b 100644 --- a/src/entities/drone/drone_daedalus.c +++ b/src/entities/drone/drone_daedalus.c @@ -172,7 +172,7 @@ static void daedalus_pain(edict_t *self, edict_t *other, float kick, int damage) else gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); - if (skill->value == 3) + if (invasion->value == 2) return; if (damage <= 25) @@ -183,7 +183,12 @@ static void daedalus_pain(edict_t *self, edict_t *other, float kick, int damage) self->monsterinfo.currentmove = &hover_move_pain2; } else - self->monsterinfo.currentmove = &hover_move_pain1; + { + if (random() < 0.3f) + self->monsterinfo.currentmove = &hover_move_pain1; + else + self->monsterinfo.currentmove = &hover_move_pain2; + } } static void daedalus_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) diff --git a/src/entities/drone/drone_fixbot.c b/src/entities/drone/drone_fixbot.c index a309597b..fe505c6b 100644 --- a/src/entities/drone/drone_fixbot.c +++ b/src/entities/drone/drone_fixbot.c @@ -1306,6 +1306,14 @@ static mframe_t fixbot_frames_pain3[] = }; static mmove_t fixbot_move_pain3 = { FIXBOT_FRAME_freeze_01, FIXBOT_FRAME_freeze_01, fixbot_frames_pain3, fixbot_run }; +static void fixbot_dead(edict_t *self); + +static mframe_t fixbot_frames_death1[] = +{ + ai_move, 0, NULL +}; +static mmove_t fixbot_move_death1 = { FIXBOT_FRAME_freeze_01, FIXBOT_FRAME_freeze_01, fixbot_frames_death1, fixbot_dead }; + static void fixbot_pain(edict_t *self, edict_t *other, float kick, int damage) { if (level.time < self->pain_debounce_time) @@ -1325,18 +1333,12 @@ static void fixbot_pain(edict_t *self, edict_t *other, float kick, int damage) self->monsterinfo.currentmove = &fixbot_move_paina; } -static void fixbot_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +static void fixbot_dead(edict_t *self) { - int n; - fixbot_remove_turrets(self); fixbot_spawn_laser_off(self); - gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); - for (n = 0; n < 3; n++) - ThrowGib(self, "models/objects/gibs/sm_metal/tris.md2", damage, GIB_METALLIC); - for (n = 0; n < 2; n++) - ThrowGib(self, "models/objects/gibs/gear/tris.md2", damage, GIB_METALLIC); + vrx_throw_drone_gibs(self, self->dmg ? self->dmg : 120); gi.WriteByte(svc_temp_entity); gi.WriteByte(TE_EXPLOSION1); @@ -1346,6 +1348,21 @@ static void fixbot_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int M_Remove(self, false, false); } +static void fixbot_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + M_Notify(self); + + if (self->deadflag == DEAD_DEAD) + return; + + gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_NO; + self->s.sound = 0; + self->dmg = damage; + self->monsterinfo.currentmove = &fixbot_move_death1; +} + static void init_drone_fixbot_common(edict_t *self, qboolean boss) { sound_pain = gi.soundindex("daedalus/daedpain1.wav"); @@ -1432,4 +1449,4 @@ void init_drone_fixbot(edict_t *self) void init_drone_fixbot_boss(edict_t *self) { init_drone_fixbot_common(self, true); -} \ No newline at end of file +} diff --git a/src/entities/drone/drone_float.c b/src/entities/drone/drone_float.c index 3d1c928b..3208d425 100644 --- a/src/entities/drone/drone_float.c +++ b/src/entities/drone/drone_float.c @@ -648,8 +648,8 @@ void floater_pain (edict_t *self, edict_t *other, float kick, int damage) return; self->pain_debounce_time = level.time + 3; - if (skill->value == 3) - return; // no pain anims in nightmare + if (invasion->value == 2) + return; n = (rand() + 1) % 3; if (n == 0) @@ -683,6 +683,7 @@ void floater_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int dama gi.multicast (self->s.origin, MULTICAST_PVS); M_Notify(self); + vrx_throw_drone_gibs(self, 55); M_Remove(self, false, false); } diff --git a/src/entities/drone/drone_flyer.c b/src/entities/drone/drone_flyer.c index c59ce1bc..6aa5d230 100644 --- a/src/entities/drone/drone_flyer.c +++ b/src/entities/drone/drone_flyer.c @@ -606,8 +606,8 @@ void flyer_pain (edict_t *self, edict_t *other, float kick, int damage) return; self->pain_debounce_time = level.time + 3; - if (skill->value == 3) - return; // no pain anims in nightmare + if (invasion->value == 2) + return; n = rand() % 3; if (n == 0) @@ -637,6 +637,7 @@ void flyer_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, gi.multicast (self->s.origin, MULTICAST_PVS); M_Notify(self); + vrx_throw_drone_gibs(self, 55); M_Remove(self, false, false); } diff --git a/src/entities/drone/drone_gekk.c b/src/entities/drone/drone_gekk.c index 886a0d05..89472393 100644 --- a/src/entities/drone/drone_gekk.c +++ b/src/entities/drone/drone_gekk.c @@ -29,10 +29,12 @@ void drone_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *sur static void gekk_stand(edict_t *self); static void gekk_walk(edict_t *self); +static void gekk_run_start(edict_t *self); static void gekk_run(edict_t *self); static void gekk_land_to_water(edict_t *self); static void gekk_water_to_land(edict_t *self); static void gekk_swim_loop(edict_t *self); +static void gekk_swim_check(edict_t *self); static void gekk_check_landing(edict_t *self); static void gekk_stop_skid(edict_t *self); @@ -41,6 +43,9 @@ extern mmove_t gekk_move_swim_loop; extern mmove_t gekk_move_swim_start; extern mmove_t gekk_move_leapatk; extern mmove_t gekk_move_leapatk2; +extern mmove_t gekk_move_run_start; +extern mmove_t gekk_move_attack1; +extern mmove_t gekk_move_attack2; extern void fire_acid(edict_t *self, vec3_t start, vec3_t aimdir, int projectile_damage, float radius, int speed, int acid_damage, float acid_duration, int gas_damage, float gas_radius, float gas_duration); @@ -75,6 +80,16 @@ static void gekk_search(edict_t *self) gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_IDLE, 0); } +static void gekk_setskin(edict_t *self) +{ + if (self->health < (self->max_health / 4)) + self->s.skinnum = 2; + else if (self->health < (self->max_health / 2)) + self->s.skinnum = 1; + else + self->s.skinnum = 0; +} + static void gekk_set_fly_parameters(edict_t *self) { self->monsterinfo.fly_thrusters = false; @@ -161,6 +176,8 @@ mmove_t gekk_move_stand = { FRAME_stand_01, FRAME_stand_39, gekk_frames_stand, g static void gekk_stand(edict_t *self) { + gekk_setskin(self); + if (gekk_should_swim(self)) { gekk_set_water_bounds(self); @@ -176,17 +193,19 @@ static void gekk_stand(edict_t *self) mframe_t gekk_frames_walk[] = { - drone_ai_walk, 8, gekk_step, - drone_ai_walk, 9, NULL, - drone_ai_walk, 12, NULL, - drone_ai_walk, 11, gekk_step, - drone_ai_walk, 9, NULL, - drone_ai_walk, 8, NULL + drone_ai_walk, 3.849f, gekk_swim_check, + drone_ai_walk, 19.606f, NULL, + drone_ai_walk, 25.583f, NULL, + drone_ai_walk, 34.625f, gekk_step, + drone_ai_walk, 27.365f, NULL, + drone_ai_walk, 28.480f, NULL }; mmove_t gekk_move_walk = { FRAME_run_01, FRAME_run_06, gekk_frames_walk, gekk_walk }; static void gekk_walk(edict_t *self) { + gekk_setskin(self); + if (gekk_should_swim(self)) { gekk_land_to_water(self); @@ -202,17 +221,40 @@ static void gekk_walk(edict_t *self) mframe_t gekk_frames_run[] = { - drone_ai_run, 18, gekk_step, - drone_ai_run, 24, NULL, - drone_ai_run, 28, NULL, - drone_ai_run, 22, gekk_step, - drone_ai_run, 20, NULL, - drone_ai_run, 18, NULL + drone_ai_run, 3.849f, gekk_swim_check, + drone_ai_run, 19.606f, NULL, + drone_ai_run, 25.583f, NULL, + drone_ai_run, 34.625f, gekk_step, + drone_ai_run, 27.365f, NULL, + drone_ai_run, 28.480f, NULL }; mmove_t gekk_move_run = { FRAME_run_01, FRAME_run_06, gekk_frames_run, gekk_run }; +static void gekk_run_start(edict_t *self) +{ + if (gekk_should_swim(self)) + { + gekk_land_to_water(self); + return; + } + + if (self->flags & FL_SWIM) + gekk_set_land_bounds(self); + + self->monsterinfo.currentmove = &gekk_move_run_start; +} + +mframe_t gekk_frames_run_start[] = +{ + drone_ai_run, 0.212f, NULL, + drone_ai_run, 19.753f, NULL +}; +mmove_t gekk_move_run_start = { FRAME_stand_01, FRAME_stand_02, gekk_frames_run_start, gekk_run }; + static void gekk_run(edict_t *self) { + gekk_setskin(self); + if (gekk_should_swim(self)) { gekk_land_to_water(self); @@ -243,12 +285,32 @@ static void gekk_bite(edict_t *self) M_MeleeAttack(self, self->enemy, 80, gekk_melee_damage(self), 120); } -static void gekk_claw(edict_t *self) +static void gekk_hit_left(edict_t *self) +{ + gi.sound(self, CHAN_WEAPON, sound_hit, 1, ATTN_NORM, 0); + M_MeleeAttack(self, self->enemy, 96, gekk_melee_damage(self), 180); +} + +static void gekk_hit_right(edict_t *self) { - gi.sound(self, CHAN_WEAPON, (random() < 0.5) ? sound_hit : sound_hit2, 1, ATTN_NORM, 0); + gi.sound(self, CHAN_WEAPON, sound_hit2, 1, ATTN_NORM, 0); M_MeleeAttack(self, self->enemy, 96, gekk_melee_damage(self), 180); } +static void gekk_check_refire(edict_t *self) +{ + if (!G_EntExists(self->enemy) || self->monsterinfo.melee_finished > level.time) + return; + + if (entdist(self, self->enemy) <= 96) + { + if (self->s.frame == FRAME_clawatk3_09) + self->monsterinfo.currentmove = &gekk_move_attack2; + else if (self->s.frame == FRAME_clawatk5_09) + self->monsterinfo.currentmove = &gekk_move_attack1; + } +} + static void gekk_swim_check(edict_t *self) { if (!gekk_should_swim(self) && (self->flags & FL_SWIM)) @@ -357,13 +419,13 @@ mframe_t gekk_frames_swim_start[] = drone_ai_run, 16, NULL, drone_ai_run, 16, NULL, drone_ai_run, 18, NULL, - drone_ai_run, 18, gekk_claw, + drone_ai_run, 18, gekk_hit_left, drone_ai_run, 18, NULL, drone_ai_run, 20, NULL, drone_ai_run, 20, NULL, drone_ai_run, 22, NULL, drone_ai_run, 22, NULL, - drone_ai_run, 24, gekk_claw, + drone_ai_run, 24, gekk_hit_right, drone_ai_run, 24, NULL, drone_ai_run, 26, NULL, drone_ai_run, 26, NULL, @@ -405,57 +467,57 @@ static void gekk_water_to_land(edict_t *self) mframe_t gekk_frames_attack[] = { - ai_charge, 8, NULL, - ai_charge, 12, NULL, - ai_charge, 12, gekk_bite, - ai_charge, 14, NULL, - ai_charge, 14, NULL, - ai_charge, 12, gekk_bite, - ai_charge, 12, NULL, - ai_charge, 10, NULL, - ai_charge, 8, NULL, - ai_charge, 8, NULL, - ai_charge, 6, gekk_claw, - ai_charge, 6, NULL, - ai_charge, 6, NULL, - ai_charge, 6, NULL, - ai_charge, 6, gekk_claw, - ai_charge, 6, NULL, - ai_charge, 6, NULL, - ai_charge, 4, NULL, - ai_charge, 4, NULL, - ai_charge, 2, NULL, + ai_charge, 16, NULL, + ai_charge, 16, NULL, + ai_charge, 16, NULL, + ai_charge, 16, NULL, + ai_charge, 16, gekk_bite, + ai_charge, 16, NULL, + ai_charge, 16, NULL, + ai_charge, 16, NULL, + ai_charge, 16, NULL, + ai_charge, 16, gekk_bite, + ai_charge, 16, NULL, + ai_charge, 16, NULL, + ai_charge, 16, NULL, + ai_charge, 16, gekk_hit_left, + ai_charge, 16, NULL, + ai_charge, 16, NULL, + ai_charge, 16, NULL, + ai_charge, 16, NULL, + ai_charge, 16, gekk_hit_right, + ai_charge, 16, NULL, ai_charge, 0, NULL }; -mmove_t gekk_move_attack = { FRAME_attack_01, FRAME_attack_21, gekk_frames_attack, gekk_run }; +mmove_t gekk_move_attack = { FRAME_attack_01, FRAME_attack_21, gekk_frames_attack, gekk_run_start }; mframe_t gekk_frames_attack1[] = { ai_charge, 0, NULL, - ai_charge, 0, gekk_claw, ai_charge, 0, NULL, - ai_charge, 0, gekk_claw, ai_charge, 0, NULL, - ai_charge, 0, gekk_claw, + ai_charge, 0, gekk_hit_left, ai_charge, 0, NULL, ai_charge, 0, NULL, - ai_charge, 0, NULL + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, gekk_check_refire }; -mmove_t gekk_move_attack1 = { FRAME_clawatk3_01, FRAME_clawatk3_09, gekk_frames_attack1, gekk_run }; +mmove_t gekk_move_attack1 = { FRAME_clawatk3_01, FRAME_clawatk3_09, gekk_frames_attack1, gekk_run_start }; mframe_t gekk_frames_attack2[] = { ai_charge, 0, NULL, ai_charge, 0, NULL, - ai_charge, 0, gekk_claw, + ai_charge, 0, gekk_hit_left, ai_charge, 0, NULL, ai_charge, 0, NULL, - ai_charge, 0, gekk_claw, + ai_charge, 0, gekk_hit_right, ai_charge, 0, NULL, ai_charge, 0, NULL, - ai_charge, 0, NULL + ai_charge, 0, gekk_check_refire }; -mmove_t gekk_move_attack2 = { FRAME_clawatk5_01, FRAME_clawatk5_09, gekk_frames_attack2, gekk_run }; +mmove_t gekk_move_attack2 = { FRAME_clawatk5_01, FRAME_clawatk5_09, gekk_frames_attack2, gekk_run_start }; static void gekk_melee(edict_t *self) { @@ -475,11 +537,8 @@ static void gekk_melee(edict_t *self) return; } - gi.sound(self, CHAN_WEAPON, sound_swing, 1, ATTN_NORM, 0); float attack_roll = random(); - if (attack_roll < 0.34f) - self->monsterinfo.currentmove = &gekk_move_attack; - else if (attack_roll < 0.67f) + if (attack_roll > 0.66f) self->monsterinfo.currentmove = &gekk_move_attack1; else self->monsterinfo.currentmove = &gekk_move_attack2; @@ -549,15 +608,14 @@ static void gekk_jump_touch(edict_t *self, edict_t *other, cplane_t *plane, csur if (other && other->takedamage) return; - if (!self->groundentity) + if (!M_CheckBottom(self)) { - self->velocity[0] = 0; - self->velocity[1] = 0; - if (self->velocity[2] > -200) - self->velocity[2] = -200; - self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; - self->monsterinfo.nextframe = FRAME_leapatk_11; - gekk_set_leap_cooldown(self, 4.0, 2.0); + if (self->groundentity) + { + self->monsterinfo.nextframe = FRAME_leapatk_11; + self->touch = drone_touch; + } + return; } self->touch = drone_touch; @@ -585,8 +643,9 @@ static void gekk_jump_takeoff(edict_t *self) VectorScale(forward, 250, self->velocity); self->velocity[2] = 400; } - self->monsterinfo.pausetime = level.time + 2.0; - self->monsterinfo.aiflags |= AI_HOLD_FRAME; + self->monsterinfo.aiflags |= AI_DUCKED; + self->monsterinfo.attack_finished = level.time + 3.0; + self->style = 1; self->touch = gekk_jump_touch; gekk_set_leap_cooldown(self, 1.0, 0.5); } @@ -613,8 +672,9 @@ static void gekk_jump_takeoff2(edict_t *self) VectorScale(forward, 150, self->velocity); self->velocity[2] = 300; } - self->monsterinfo.pausetime = level.time + 2.0; - self->monsterinfo.aiflags |= AI_HOLD_FRAME; + self->monsterinfo.aiflags |= AI_DUCKED; + self->monsterinfo.attack_finished = level.time + 3.0; + self->style = 1; self->touch = gekk_jump_touch; gekk_set_leap_cooldown(self, 1.0, 0.5); } @@ -624,35 +684,42 @@ static void gekk_stop_skid(edict_t *self) if (self->groundentity) { VectorClear(self->velocity); - self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + self->monsterinfo.aiflags &= ~AI_DUCKED; self->touch = drone_touch; } } static void gekk_check_landing(edict_t *self) { - if (self->groundentity || (self->waterlevel > 1) || (level.time > self->monsterinfo.pausetime)) + if (self->groundentity) { - self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; - self->monsterinfo.melee_finished = level.time + 1.5; - self->monsterinfo.attack_finished = level.time + 1.0; - self->monsterinfo.nextframe = FRAME_leapatk_11; + gi.sound(self, CHAN_WEAPON, sound_thud, 1, ATTN_NORM, 0); + self->monsterinfo.aiflags &= ~AI_DUCKED; + self->monsterinfo.attack_finished = 0; self->touch = drone_touch; + VectorClear(self->velocity); gekk_set_leap_cooldown(self, 1.5, 1.0); - if (!self->groundentity && self->velocity[2] > -200) - self->velocity[2] = -200; - if (self->waterlevel > 1) - gekk_land_to_water(self); return; } - self->monsterinfo.aiflags |= AI_HOLD_FRAME; -} + if (self->waterlevel > 1) + { + self->monsterinfo.aiflags &= ~AI_DUCKED; + self->monsterinfo.nextframe = FRAME_leapatk_11; + self->touch = drone_touch; + gekk_land_to_water(self); + return; + } -static void gekk_check_landing_ai(edict_t *self, float dist) -{ - ai_charge(self, dist); - gekk_check_landing(self); + vec3_t forward; + AngleVectors(self->s.angles, forward, NULL, NULL); + if (DotProduct(forward, self->velocity) < 200) + VectorMA(self->velocity, 200, forward, self->velocity); + + if (level.time > self->monsterinfo.attack_finished) + self->monsterinfo.nextframe = FRAME_leapatk_11; + else + self->monsterinfo.nextframe = FRAME_leapatk_12; } static void gekk_spit(edict_t *self) @@ -685,64 +752,53 @@ static void gekk_spit(edict_t *self) fire_acid(self, start, dir, damage, radius, speed, (int)(0.1 * damage), ACID_DURATION, 0, 0, 0); } -static qboolean gekk_should_use_acid(float distance) -{ - if (distance >= 768.0f) - return true; - if (distance >= 384.0f) - return random() < 0.65f; - if (distance >= 160.0f) - return random() < 0.30f; - return false; -} - mframe_t gekk_frames_leapatk[] = { ai_charge, 0, NULL, - ai_charge, 0, NULL, - ai_charge, 0, NULL, - ai_charge, 6, gekk_jump_takeoff, - ai_charge, 6, NULL, - ai_charge, 0, NULL, - ai_charge, 28, NULL, - ai_charge, 24, NULL, - ai_charge, 32, NULL, - gekk_check_landing_ai, 36, NULL, - ai_charge, 12, gekk_stop_skid, - ai_charge, 20, gekk_stop_skid, - ai_charge, -1, gekk_stop_skid, - ai_charge, 3, gekk_stop_skid, - ai_charge, 1, gekk_stop_skid, - ai_charge, 2, gekk_stop_skid, - ai_charge, 1, gekk_stop_skid, - ai_charge, 0, NULL, - ai_charge, 0, NULL + ai_charge, -0.387f, NULL, + ai_charge, -1.113f, NULL, + ai_charge, -0.237f, NULL, + ai_charge, 6.720f, gekk_jump_takeoff, + ai_charge, 6.414f, NULL, + ai_charge, 0.163f, NULL, + ai_charge, 28.316f, NULL, + ai_charge, 24.198f, NULL, + ai_charge, 31.742f, NULL, + ai_charge, 35.977f, gekk_check_landing, + ai_charge, 12.303f, gekk_stop_skid, + ai_charge, 20.122f, gekk_stop_skid, + ai_charge, -1.042f, gekk_stop_skid, + ai_charge, 2.556f, gekk_stop_skid, + ai_charge, 0.544f, gekk_stop_skid, + ai_charge, 1.862f, gekk_stop_skid, + ai_charge, 1.224f, gekk_stop_skid, + ai_charge, -0.457f, gekk_swim_check }; -mmove_t gekk_move_leapatk = { FRAME_leapatk_01, FRAME_leapatk_19, gekk_frames_leapatk, gekk_run }; +mmove_t gekk_move_leapatk = { FRAME_leapatk_01, FRAME_leapatk_19, gekk_frames_leapatk, gekk_run_start }; mframe_t gekk_frames_leapatk2[] = { ai_charge, 0, NULL, - ai_charge, 0, NULL, - ai_charge, 0, NULL, - ai_charge, 6, gekk_jump_takeoff2, - ai_charge, 6, NULL, - ai_charge, 0, NULL, - ai_charge, 28, NULL, - ai_charge, 24, NULL, - ai_charge, 32, NULL, - gekk_check_landing_ai, 36, NULL, - ai_charge, 12, gekk_stop_skid, - ai_charge, 20, gekk_stop_skid, - ai_charge, -1, gekk_stop_skid, - ai_charge, 3, gekk_stop_skid, - ai_charge, 1, gekk_stop_skid, - ai_charge, 2, gekk_stop_skid, - ai_charge, 1, gekk_stop_skid, - ai_charge, 0, NULL, - ai_charge, 0, NULL + ai_charge, -0.387f, NULL, + ai_charge, -1.113f, NULL, + ai_charge, -0.237f, NULL, + ai_charge, 6.720f, gekk_jump_takeoff2, + ai_charge, 6.414f, NULL, + ai_charge, 0.163f, NULL, + ai_charge, 28.316f, NULL, + ai_charge, 24.198f, NULL, + ai_charge, 31.742f, NULL, + ai_charge, 35.977f, gekk_check_landing, + ai_charge, 12.303f, gekk_stop_skid, + ai_charge, 20.122f, gekk_stop_skid, + ai_charge, -1.042f, gekk_stop_skid, + ai_charge, 2.556f, gekk_stop_skid, + ai_charge, 0.544f, gekk_stop_skid, + ai_charge, 1.862f, gekk_stop_skid, + ai_charge, 1.224f, gekk_stop_skid, + ai_charge, -0.457f, gekk_swim_check }; -mmove_t gekk_move_leapatk2 = { FRAME_leapatk_01, FRAME_leapatk_19, gekk_frames_leapatk2, gekk_run }; +mmove_t gekk_move_leapatk2 = { FRAME_leapatk_01, FRAME_leapatk_19, gekk_frames_leapatk2, gekk_run_start }; mframe_t gekk_frames_spit[] = { @@ -754,7 +810,7 @@ mframe_t gekk_frames_spit[] = ai_charge, 0, gekk_spit, ai_charge, 0, NULL }; -mmove_t gekk_move_spit = { FRAME_spit_01, FRAME_spit_07, gekk_frames_spit, gekk_run }; +mmove_t gekk_move_spit = { FRAME_spit_01, FRAME_spit_07, gekk_frames_spit, gekk_run_start }; static void gekk_attack(edict_t *self) { @@ -774,21 +830,37 @@ static void gekk_attack(edict_t *self) if (!G_EntExists(self->enemy)) return; - float distance = entdist(self, self->enemy); - if (gekk_should_use_acid(distance)) + float enemy_distance = entdist(self, self->enemy); + if (enemy_distance >= 500.0f) { - self->monsterinfo.currentmove = &gekk_move_spit; - self->monsterinfo.melee_finished = level.time + 0.5; + if (random() > 0.5f) + { + self->monsterinfo.currentmove = &gekk_move_spit; + self->monsterinfo.melee_finished = level.time + 0.5; + } + else + { + self->monsterinfo.currentmove = &gekk_move_run_start; + self->monsterinfo.attack_finished = level.time + 2.0; + } } - else if (gekk_can_leap(self) && random() >= 0.35f) + else if (random() > 0.7f) { - self->monsterinfo.currentmove = &gekk_move_leapatk; - self->monsterinfo.melee_finished = level.time + 3.0; + self->monsterinfo.currentmove = &gekk_move_spit; + self->monsterinfo.melee_finished = level.time + 0.5; } else { - self->monsterinfo.currentmove = &gekk_move_spit; - self->monsterinfo.melee_finished = level.time + 0.5; + if (!gekk_can_leap(self) || random() > 0.7f) + { + self->monsterinfo.currentmove = &gekk_move_run_start; + self->monsterinfo.attack_finished = level.time + 1.4; + } + else + { + self->monsterinfo.currentmove = &gekk_move_leapatk; + self->monsterinfo.melee_finished = level.time + 3.0; + } } M_DelayNextAttack(self, 1.0 + random(), true); } @@ -802,7 +874,7 @@ mframe_t gekk_frames_pain[] = ai_move, 0, NULL, ai_move, 0, NULL }; -mmove_t gekk_move_pain = { FRAME_pain_01, FRAME_pain_06, gekk_frames_pain, gekk_run }; +mmove_t gekk_move_pain = { FRAME_pain_01, FRAME_pain_06, gekk_frames_pain, gekk_run_start }; mframe_t gekk_frames_pain1[] = { @@ -818,7 +890,7 @@ mframe_t gekk_frames_pain1[] = ai_move, 0, NULL, ai_move, 0, NULL }; -mmove_t gekk_move_pain1 = { FRAME_pain3_01, FRAME_pain3_11, gekk_frames_pain1, gekk_run }; +mmove_t gekk_move_pain1 = { FRAME_pain3_01, FRAME_pain3_11, gekk_frames_pain1, gekk_run_start }; mframe_t gekk_frames_pain2[] = { @@ -836,12 +908,11 @@ mframe_t gekk_frames_pain2[] = ai_move, 0, NULL, ai_move, 0, NULL }; -mmove_t gekk_move_pain2 = { FRAME_pain4_01, FRAME_pain4_13, gekk_frames_pain2, gekk_run }; +mmove_t gekk_move_pain2 = { FRAME_pain4_01, FRAME_pain4_13, gekk_frames_pain2, gekk_run_start }; static void gekk_pain(edict_t *self, edict_t *other, float kick, int damage) { - if (self->health < (self->max_health / 2)) - self->s.skinnum |= 1; + gekk_setskin(self); if (level.time < self->pain_debounce_time) return; @@ -849,7 +920,7 @@ static void gekk_pain(edict_t *self, edict_t *other, float kick, int damage) self->pain_debounce_time = level.time + 3.0; gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); - if (skill->value == 3) + if (invasion->value == 2) return; if (self->waterlevel >= 2) @@ -878,6 +949,26 @@ static void gekk_shrink(edict_t *self) gi.linkentity(self); } +static void gekk_gibfest(edict_t *self) +{ + if (vrx_throw_drone_gibs(self, 20)) + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); + self->takedamage = DAMAGE_NO; + self->solid = SOLID_NOT; + self->svflags |= SVF_NOCLIENT; + self->deadflag = DEAD_DEAD; + self->activator = NULL; + self->think = G_FreeEdict; + self->nextthink = level.time + FRAMETIME; + gi.unlinkentity(self); +} + +static void gekk_isgibfest(edict_t *self) +{ + if (random() > 0.9f) + gekk_gibfest(self); +} + mframe_t gekk_frames_death1[] = { ai_move, 0, NULL, @@ -893,24 +984,118 @@ mframe_t gekk_frames_death1[] = }; mmove_t gekk_move_death1 = { FRAME_death1_01, FRAME_death1_10, gekk_frames_death1, gekk_dead }; +mframe_t gekk_frames_death3[] = +{ + ai_move, 0, NULL, + ai_move, 0.022f, NULL, + ai_move, 0.169f, NULL, + ai_move, -0.710f, NULL, + ai_move, -13.446f, NULL, + ai_move, -7.654f, gekk_isgibfest, + ai_move, -31.951f, NULL +}; +mmove_t gekk_move_death3 = { FRAME_death3_01, FRAME_death3_07, gekk_frames_death3, gekk_dead }; + +mframe_t gekk_frames_death4[] = +{ + ai_move, 5.103f, NULL, + ai_move, -4.808f, NULL, + ai_move, -10.509f, NULL, + ai_move, -9.899f, NULL, + ai_move, 4.033f, gekk_isgibfest, + ai_move, -5.197f, NULL, + ai_move, -0.919f, NULL, + ai_move, -8.821f, NULL, + ai_move, -5.626f, NULL, + ai_move, -8.865f, gekk_isgibfest, + ai_move, -0.845f, NULL, + ai_move, 1.986f, NULL, + ai_move, 0.170f, NULL, + ai_move, 1.339f, gekk_isgibfest, + ai_move, -0.922f, NULL, + ai_move, 0.818f, NULL, + ai_move, -1.288f, NULL, + ai_move, -1.408f, gekk_isgibfest, + ai_move, -7.787f, NULL, + ai_move, -3.995f, NULL, + ai_move, -4.604f, NULL, + ai_move, -1.715f, gekk_isgibfest, + ai_move, -0.564f, NULL, + ai_move, -0.597f, NULL, + ai_move, 0.074f, NULL, + ai_move, -0.309f, gekk_isgibfest, + ai_move, -0.395f, NULL, + ai_move, -0.501f, NULL, + ai_move, -0.325f, NULL, + ai_move, -0.931f, gekk_isgibfest, + ai_move, -1.433f, NULL, + ai_move, -1.626f, NULL, + ai_move, 4.680f, NULL, + ai_move, 0.560f, NULL, + ai_move, -0.549f, gekk_gibfest +}; +mmove_t gekk_move_death4 = { FRAME_death4_01, FRAME_death4_35, gekk_frames_death4, gekk_dead }; + +mframe_t gekk_frames_wdeath[] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t gekk_move_wdeath = { FRAME_wdeath_01, FRAME_wdeath_45, gekk_frames_wdeath, gekk_dead }; + static void gekk_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) { - int n; + float r; M_Notify(self); if (self->health <= self->gib_health) { gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); - if (vrx_spawn_nonessential_ent(self->s.origin)) - { - ThrowGib(self, "models/objects/gekkgib/pelvis/tris.md2", damage, GIB_ORGANIC); - for (n = 0; n < 2; n++) - ThrowGib(self, "models/objects/gekkgib/arm/tris.md2", damage, GIB_ORGANIC); - ThrowGib(self, "models/objects/gekkgib/torso/tris.md2", damage, GIB_ORGANIC); - ThrowGib(self, "models/objects/gekkgib/claw/tris.md2", damage, GIB_ORGANIC); - ThrowHead(self, "models/objects/gekkgib/head/tris.md2", damage, GIB_ORGANIC); - } + vrx_throw_drone_gibs(self, damage); M_Remove(self, false, false); return; } @@ -922,8 +1107,26 @@ static void gekk_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int d gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); self->deadflag = DEAD_DEAD; self->takedamage = DAMAGE_YES; - vrx_update_drone_death_skin(self); - self->monsterinfo.currentmove = &gekk_move_death1; + gekk_setskin(self); + if (self->waterlevel >= WATER_WAIST) + { + VectorClear(self->velocity); + self->monsterinfo.aiflags &= ~AI_ALTERNATE_FLY; + self->monsterinfo.fly_thrusters = false; + self->monsterinfo.fly_pinned = false; + gekk_shrink(self); + self->monsterinfo.currentmove = &gekk_move_wdeath; + } + else + { + r = random(); + if (r > 0.66f) + self->monsterinfo.currentmove = &gekk_move_death1; + else if (r > 0.33f) + self->monsterinfo.currentmove = &gekk_move_death3; + else + self->monsterinfo.currentmove = &gekk_move_death4; + } if (self->activator && !self->activator->client) self->activator->num_monsters_real--; diff --git a/src/entities/drone/drone_gibs.c b/src/entities/drone/drone_gibs.c new file mode 100644 index 00000000..363c8b3c --- /dev/null +++ b/src/entities/drone/drone_gibs.c @@ -0,0 +1,597 @@ +#include "g_local.h" + +extern mmove_t infantry_move_death3; + +typedef struct drone_gib_s { + int count; + const char *model; + int type; + float scale; + int frame; +} drone_gib_t; + +typedef struct drone_gib_list_s { + const drone_gib_t *gibs; + int count; +} drone_gib_list_t; + +#define HGIB(name, type) { 1, name, type, 1.0f, 0 } +#define HGIB_N(count, name, type) { count, name, type, 1.0f, 0 } +#define HGIB_SCALE(name, scale, type) { 1, name, type, scale, 0 } +#define HGIB_FRAME(name, frame, type) { 1, name, type, 1.0f, frame } +#define HGIB_COUNT(list) ((int)(sizeof(list) / sizeof((list)[0]))) +#define HGIB_LIST(list) { list, HGIB_COUNT(list) } + +static const drone_gib_t gibs_soldier[] = { + HGIB_N(3, "models/objects/gibs/sm_meat/tris.md2", GIB_ORGANIC), + HGIB("models/objects/gibs/bone2/tris.md2", GIB_ORGANIC), + HGIB("models/objects/gibs/bone/tris.md2", GIB_ORGANIC), + HGIB("models/monsters/soldier/gibs/arm.md2", GIB_SKINNED), + HGIB("models/monsters/soldier/gibs/gun.md2", GIB_SKINNED | GIB_UPRIGHT), + HGIB("models/monsters/soldier/gibs/chest.md2", GIB_SKINNED), + HGIB("models/monsters/soldier/gibs/head.md2", GIB_HEAD | GIB_SKINNED), +}; + +static const drone_gib_t gibs_gunner[] = { + HGIB_N(2, "models/objects/gibs/bone/tris.md2", GIB_ORGANIC), + HGIB_N(2, "models/objects/gibs/sm_meat/tris.md2", GIB_ORGANIC), + HGIB("models/monsters/gunner/gibs/chest.md2", GIB_SKINNED), + HGIB("models/monsters/gunner/gibs/garm.md2", GIB_SKINNED | GIB_UPRIGHT), + HGIB("models/monsters/gunner/gibs/gun.md2", GIB_SKINNED | GIB_UPRIGHT), + HGIB("models/monsters/gunner/gibs/foot.md2", GIB_SKINNED), + HGIB("models/monsters/gunner/gibs/head.md2", GIB_SKINNED | GIB_HEAD), +}; + +static const drone_gib_t gibs_guncmdr[] = { + HGIB_N(2, "models/objects/gibs/bone/tris.md2", GIB_ORGANIC), + HGIB_N(2, "models/objects/gibs/sm_meat/tris.md2", GIB_ORGANIC), + HGIB("models/objects/gibs/gear/tris.md2", GIB_ORGANIC), + HGIB("models/monsters/gunner/gibs/chest.md2", GIB_SKINNED), + HGIB("models/monsters/gunner/gibs/garm.md2", GIB_SKINNED | GIB_UPRIGHT), + HGIB("models/monsters/gunner/gibs/gun.md2", GIB_SKINNED | GIB_UPRIGHT), + HGIB("models/monsters/gunner/gibs/foot.md2", GIB_SKINNED), + HGIB("models/monsters/gunner/gibs/head.md2", GIB_SKINNED | GIB_HEAD), +}; + +static const drone_gib_t gibs_chick[] = { + HGIB_N(2, "models/objects/gibs/bone/tris.md2", GIB_ORGANIC), + HGIB_N(3, "models/objects/gibs/sm_meat/tris.md2", GIB_ORGANIC), + HGIB("models/monsters/bitch/gibs/arm.md2", GIB_SKINNED | GIB_UPRIGHT), + HGIB("models/monsters/bitch/gibs/foot.md2", GIB_SKINNED | GIB_UPRIGHT), + HGIB("models/monsters/bitch/gibs/tube.md2", GIB_SKINNED | GIB_UPRIGHT), + HGIB("models/monsters/bitch/gibs/chest.md2", GIB_SKINNED), + HGIB("models/monsters/bitch/gibs/head.md2", GIB_HEAD | GIB_SKINNED), +}; + +static const drone_gib_t gibs_brain[] = { + HGIB("models/objects/gibs/bone/tris.md2", GIB_ORGANIC), + HGIB_N(2, "models/objects/gibs/sm_meat/tris.md2", GIB_ORGANIC), + HGIB_N(2, "models/monsters/brain/gibs/arm.md2", GIB_SKINNED | GIB_UPRIGHT), + HGIB("models/monsters/brain/gibs/boot.md2", GIB_SKINNED | GIB_UPRIGHT), + HGIB("models/monsters/brain/gibs/pelvis.md2", GIB_SKINNED), + HGIB("models/monsters/brain/gibs/chest.md2", GIB_SKINNED), + HGIB_N(2, "models/monsters/brain/gibs/door.md2", GIB_SKINNED | GIB_UPRIGHT), + HGIB("models/monsters/brain/gibs/head.md2", GIB_SKINNED | GIB_HEAD), +}; + +static const drone_gib_t gibs_medic[] = { + HGIB_N(2, "models/objects/gibs/bone/tris.md2", GIB_ORGANIC), + HGIB("models/objects/gibs/sm_meat/tris.md2", GIB_ORGANIC), + HGIB("models/objects/gibs/sm_metal/tris.md2", GIB_METALLIC), + HGIB("models/monsters/medic/gibs/chest.md2", GIB_SKINNED), + HGIB_N(2, "models/monsters/medic/gibs/leg.md2", GIB_SKINNED | GIB_UPRIGHT), + HGIB("models/monsters/medic/gibs/hook.md2", GIB_SKINNED | GIB_UPRIGHT), + HGIB("models/monsters/medic/gibs/gun.md2", GIB_SKINNED | GIB_UPRIGHT), + HGIB("models/monsters/medic/gibs/head.md2", GIB_SKINNED | GIB_HEAD), +}; + +static const drone_gib_t gibs_mutant[] = { + HGIB_N(2, "models/objects/gibs/bone/tris.md2", GIB_ORGANIC), + HGIB_N(4, "models/objects/gibs/sm_meat/tris.md2", GIB_ORGANIC), + HGIB_N(2, "models/monsters/mutant/gibs/hand.md2", GIB_SKINNED | GIB_UPRIGHT), + HGIB_N(2, "models/monsters/mutant/gibs/foot.md2", GIB_SKINNED), + HGIB("models/monsters/mutant/gibs/chest.md2", GIB_SKINNED), + HGIB("models/monsters/mutant/gibs/head.md2", GIB_SKINNED | GIB_HEAD), +}; + +static const drone_gib_t gibs_parasite[] = { + HGIB("models/objects/gibs/bone/tris.md2", GIB_ORGANIC), + HGIB_N(3, "models/objects/gibs/sm_meat/tris.md2", GIB_ORGANIC), + HGIB("models/monsters/parasite/gibs/chest.md2", GIB_SKINNED), + HGIB_N(2, "models/monsters/parasite/gibs/bleg.md2", GIB_SKINNED | GIB_UPRIGHT), + HGIB_N(2, "models/monsters/parasite/gibs/fleg.md2", GIB_SKINNED | GIB_UPRIGHT), + HGIB("models/monsters/parasite/gibs/head.md2", GIB_SKINNED | GIB_HEAD), +}; + +static const drone_gib_t gibs_berserk[] = { + HGIB_N(2, "models/objects/gibs/bone/tris.md2", GIB_ORGANIC), + HGIB_N(3, "models/objects/gibs/sm_meat/tris.md2", GIB_ORGANIC), + HGIB("models/objects/gibs/gear/tris.md2", GIB_ORGANIC), + HGIB("models/monsters/berserk/gibs/chest.md2", GIB_SKINNED), + HGIB("models/monsters/berserk/gibs/hammer.md2", GIB_SKINNED | GIB_UPRIGHT), + HGIB("models/monsters/berserk/gibs/thigh.md2", GIB_SKINNED), + HGIB("models/monsters/berserk/gibs/head.md2", GIB_HEAD | GIB_SKINNED), +}; + +static const drone_gib_t gibs_gladiator[] = { + HGIB_N(2, "models/objects/gibs/bone/tris.md2", GIB_ORGANIC), + HGIB_N(2, "models/objects/gibs/sm_meat/tris.md2", GIB_ORGANIC), + HGIB_N(2, "models/monsters/gladiatr/gibs/thigh.md2", GIB_SKINNED), + HGIB("models/monsters/gladiatr/gibs/larm.md2", GIB_SKINNED | GIB_UPRIGHT), + HGIB("models/monsters/gladiatr/gibs/rarm.md2", GIB_SKINNED | GIB_UPRIGHT), + HGIB("models/monsters/gladiatr/gibs/chest.md2", GIB_SKINNED), + HGIB("models/monsters/gladiatr/gibs/head.md2", GIB_SKINNED | GIB_HEAD), +}; + +static const drone_gib_t gibs_infantry[] = { + HGIB("models/objects/gibs/bone/tris.md2", GIB_ORGANIC), + HGIB_N(3, "models/objects/gibs/sm_meat/tris.md2", GIB_ORGANIC), + HGIB("models/monsters/infantry/gibs/chest.md2", GIB_SKINNED), + HGIB("models/monsters/infantry/gibs/gun.md2", GIB_SKINNED | GIB_UPRIGHT), + HGIB_N(2, "models/monsters/infantry/gibs/foot.md2", GIB_SKINNED), + HGIB_N(2, "models/monsters/infantry/gibs/arm.md2", GIB_SKINNED), + HGIB("models/monsters/infantry/gibs/head.md2", GIB_HEAD | GIB_SKINNED), +}; + +static const drone_gib_t gibs_tank[] = { + HGIB("models/objects/gibs/sm_meat/tris.md2", GIB_ORGANIC), + HGIB_N(3, "models/objects/gibs/sm_metal/tris.md2", GIB_METALLIC), + HGIB("models/objects/gibs/gear/tris.md2", GIB_METALLIC), + HGIB_N(2, "models/monsters/tank/gibs/foot.md2", GIB_SKINNED | GIB_METALLIC), + HGIB_N(2, "models/monsters/tank/gibs/thigh.md2", GIB_SKINNED | GIB_METALLIC), + HGIB("models/monsters/tank/gibs/chest.md2", GIB_SKINNED), + HGIB("models/monsters/tank/gibs/head.md2", GIB_HEAD | GIB_SKINNED), +}; + +static const drone_gib_t gibs_flyer[] = { + HGIB_N(2, "models/objects/gibs/sm_metal/tris.md2", GIB_METALLIC), + HGIB_N(2, "models/objects/gibs/sm_meat/tris.md2", GIB_ORGANIC), + HGIB("models/monsters/flyer/gibs/base.md2", GIB_SKINNED), + HGIB_N(2, "models/monsters/flyer/gibs/gun.md2", GIB_SKINNED), + HGIB_N(2, "models/monsters/flyer/gibs/wing.md2", GIB_SKINNED), + HGIB("models/monsters/flyer/gibs/head.md2", GIB_SKINNED | GIB_HEAD), +}; + +static const drone_gib_t gibs_floater[] = { + HGIB_N(2, "models/objects/gibs/sm_metal/tris.md2", GIB_METALLIC), + HGIB_N(3, "models/objects/gibs/sm_meat/tris.md2", GIB_ORGANIC), + HGIB("models/monsters/float/gibs/piece.md2", GIB_SKINNED), + HGIB("models/monsters/float/gibs/gun.md2", GIB_SKINNED), + HGIB("models/monsters/float/gibs/base.md2", GIB_SKINNED), + HGIB("models/monsters/float/gibs/jar.md2", GIB_SKINNED | GIB_HEAD), +}; + +static const drone_gib_t gibs_hover[] = { + HGIB_N(2, "models/objects/gibs/sm_meat/tris.md2", GIB_ORGANIC), + HGIB_N(2, "models/objects/gibs/sm_metal/tris.md2", GIB_METALLIC), + HGIB("models/monsters/hover/gibs/chest.md2", GIB_SKINNED), + HGIB_N(2, "models/monsters/hover/gibs/ring.md2", GIB_SKINNED | GIB_METALLIC), + HGIB_N(2, "models/monsters/hover/gibs/foot.md2", GIB_SKINNED), + HGIB("models/monsters/hover/gibs/head.md2", GIB_SKINNED | GIB_HEAD), +}; + +static const drone_gib_t gibs_shambler[] = { + HGIB_N(3, "models/objects/gibs/sm_meat/tris.md2", GIB_ORGANIC), + HGIB_N(3, "models/objects/gibs/chest/tris.md2", GIB_ORGANIC), + HGIB("models/objects/gibs/head2/tris.md2", GIB_HEAD), +}; + +static const drone_gib_t gibs_arachnid[] = { + HGIB_N(2, "models/objects/gibs/bone/tris.md2", GIB_ORGANIC), + HGIB_N(2, "models/objects/gibs/sm_meat/tris.md2", GIB_ORGANIC), + HGIB("models/monsters/gunner/gibs/chest.md2", GIB_METALLIC), + HGIB("models/monsters/gunner/gibs/garm.md2", GIB_METALLIC | GIB_UPRIGHT), + HGIB("models/monsters/gladiatr/gibs/rarm.md2", GIB_METALLIC | GIB_UPRIGHT), + HGIB("models/monsters/gunner/gibs/foot.md2", GIB_METALLIC), + HGIB("models/monsters/gunner/gibs/head.md2", GIB_METALLIC | GIB_HEAD), +}; + +static const drone_gib_t gibs_gekk[] = { + HGIB("models/objects/gekkgib/pelvis/tris.md2", GIB_ACID), + HGIB_N(2, "models/objects/gekkgib/arm/tris.md2", GIB_ACID), + HGIB("models/objects/gekkgib/torso/tris.md2", GIB_ACID), + HGIB("models/objects/gekkgib/claw/tris.md2", GIB_ACID), + HGIB_N(2, "models/objects/gekkgib/leg/tris.md2", GIB_ACID), + HGIB("models/objects/gekkgib/head/tris.md2", GIB_ACID | GIB_HEAD), +}; + +static const drone_gib_t gibs_stalker[] = { + HGIB_N(2, "models/objects/gibs/sm_meat/tris.md2", GIB_ORGANIC), + HGIB_N(2, "models/objects/gibs/sm_metal/tris.md2", GIB_METALLIC), + HGIB("models/monsters/stalker/gibs/bodya.md2", GIB_SKINNED), + HGIB("models/monsters/stalker/gibs/bodyb.md2", GIB_SKINNED), + HGIB_N(2, "models/monsters/stalker/gibs/claw.md2", GIB_SKINNED | GIB_UPRIGHT), + HGIB_N(2, "models/monsters/stalker/gibs/leg.md2", GIB_SKINNED | GIB_UPRIGHT), + HGIB_N(2, "models/monsters/stalker/gibs/foot.md2", GIB_SKINNED), + HGIB("models/monsters/stalker/gibs/head.md2", GIB_SKINNED | GIB_HEAD), +}; + +static const drone_gib_t gibs_fixbot[] = { + HGIB("models/objects/gibs/sm_metal/tris.md2", GIB_ACID), + HGIB("models/objects/gibs/gear/tris.md2", GIB_METALLIC), +}; + +static const drone_gib_t gibs_supertank[] = { + HGIB_N(2, "models/objects/gibs/sm_meat/tris.md2", GIB_ORGANIC), + HGIB_N(2, "models/objects/gibs/sm_metal/tris.md2", GIB_METALLIC), + HGIB("models/monsters/boss1/gibs/cgun.md2", GIB_SKINNED | GIB_METALLIC), + HGIB("models/monsters/boss1/gibs/chest.md2", GIB_SKINNED), + HGIB("models/monsters/boss1/gibs/core.md2", GIB_SKINNED), + HGIB("models/monsters/boss1/gibs/ltread.md2", GIB_SKINNED | GIB_UPRIGHT), + HGIB("models/monsters/boss1/gibs/rgun.md2", GIB_SKINNED | GIB_UPRIGHT), + HGIB("models/monsters/boss1/gibs/rtread.md2", GIB_SKINNED | GIB_UPRIGHT), + HGIB("models/monsters/boss1/gibs/tube.md2", GIB_SKINNED | GIB_UPRIGHT), + HGIB("models/monsters/boss1/gibs/head.md2", GIB_SKINNED | GIB_METALLIC | GIB_HEAD), +}; + +static const drone_gib_t gibs_boss2[] = { + HGIB_N(2, "models/objects/gibs/sm_meat/tris.md2", GIB_ORGANIC), + HGIB_N(2, "models/objects/gibs/sm_metal/tris.md2", GIB_METALLIC), + HGIB("models/monsters/boss2/gibs/chest.md2", GIB_SKINNED), + HGIB_N(2, "models/monsters/boss2/gibs/chaingun.md2", GIB_SKINNED | GIB_UPRIGHT), + HGIB("models/monsters/boss2/gibs/cpu.md2", GIB_SKINNED | GIB_UPRIGHT), + HGIB("models/monsters/boss2/gibs/engine.md2", GIB_SKINNED), + HGIB("models/monsters/boss2/gibs/rocket.md2", GIB_SKINNED | GIB_UPRIGHT), + HGIB("models/monsters/boss2/gibs/spine.md2", GIB_SKINNED), + HGIB_N(2, "models/monsters/boss2/gibs/wing.md2", GIB_SKINNED | GIB_UPRIGHT), + HGIB("models/monsters/boss2/gibs/larm.md2", GIB_SKINNED | GIB_UPRIGHT), + HGIB("models/monsters/boss2/gibs/rarm.md2", GIB_SKINNED | GIB_UPRIGHT), + HGIB_SCALE("models/monsters/boss2/gibs/larm.md2", 2.0f, GIB_SKINNED | GIB_UPRIGHT), + HGIB_SCALE("models/monsters/boss2/gibs/rarm.md2", 2.0f, GIB_SKINNED | GIB_UPRIGHT), + HGIB_SCALE("models/monsters/boss2/gibs/larm.md2", 1.35f, GIB_SKINNED | GIB_UPRIGHT), + HGIB_SCALE("models/monsters/boss2/gibs/rarm.md2", 1.35f, GIB_SKINNED | GIB_UPRIGHT), + HGIB("models/monsters/boss2/gibs/head.md2", GIB_SKINNED | GIB_METALLIC | GIB_HEAD), +}; + +static const drone_gib_t gibs_jorg[] = { + HGIB_N(2, "models/objects/gibs/sm_meat/tris.md2", GIB_ORGANIC), + HGIB_N(2, "models/objects/gibs/sm_metal/tris.md2", GIB_METALLIC), + HGIB("models/monsters/boss3/jorg/gibs/chest.md2", GIB_SKINNED), + HGIB_N(2, "models/monsters/boss3/jorg/gibs/foot.md2", GIB_SKINNED), + HGIB_N(2, "models/monsters/boss3/jorg/gibs/gun.md2", GIB_SKINNED | GIB_UPRIGHT), + HGIB_N(2, "models/monsters/boss3/jorg/gibs/thigh.md2", GIB_SKINNED | GIB_UPRIGHT), + HGIB("models/monsters/boss3/jorg/gibs/spine.md2", GIB_SKINNED | GIB_UPRIGHT), + HGIB_N(4, "models/monsters/boss3/jorg/gibs/tube.md2", GIB_SKINNED), + HGIB_N(6, "models/monsters/boss3/jorg/gibs/spike.md2", GIB_SKINNED), + HGIB("models/monsters/boss3/jorg/gibs/head.md2", GIB_SKINNED | GIB_METALLIC | GIB_HEAD), +}; + +static const drone_gib_t gibs_makron[] = { + HGIB("models/objects/gibs/sm_meat/tris.md2", GIB_ORGANIC), + HGIB_N(4, "models/objects/gibs/sm_metal/tris.md2", GIB_METALLIC), + HGIB("models/objects/gibs/gear/tris.md2", GIB_METALLIC | GIB_HEAD), +}; + +static const drone_gib_t gibs_carrier[] = { + HGIB_N(2, "models/objects/gibs/sm_meat/tris.md2", GIB_ORGANIC), + HGIB_N(3, "models/objects/gibs/sm_metal/tris.md2", GIB_METALLIC), + HGIB("models/monsters/carrier/gibs/base.md2", GIB_SKINNED), + HGIB("models/monsters/carrier/gibs/chest.md2", GIB_SKINNED | GIB_UPRIGHT), + HGIB("models/monsters/carrier/gibs/gl.md2", GIB_SKINNED), + HGIB("models/monsters/carrier/gibs/lcg.md2", GIB_SKINNED | GIB_UPRIGHT), + HGIB("models/monsters/carrier/gibs/lwing.md2", GIB_SKINNED | GIB_UPRIGHT), + HGIB("models/monsters/carrier/gibs/rcg.md2", GIB_SKINNED | GIB_UPRIGHT), + HGIB("models/monsters/carrier/gibs/rwing.md2", GIB_SKINNED | GIB_UPRIGHT), + HGIB_N(2, "models/monsters/carrier/gibs/spawner.md2", GIB_SKINNED), + HGIB_N(2, "models/monsters/carrier/gibs/thigh.md2", GIB_SKINNED), + HGIB("models/monsters/carrier/gibs/head.md2", GIB_SKINNED | GIB_METALLIC | GIB_HEAD), +}; + +static const drone_gib_t gibs_guardian[] = { + HGIB_N(2, "models/objects/gibs/sm_meat/tris.md2", GIB_ORGANIC), + HGIB_N(4, "models/objects/gibs/sm_metal/tris.md2", GIB_METALLIC), + HGIB_N(2, "models/monsters/guardian/gib1.md2", GIB_METALLIC), + HGIB_N(2, "models/monsters/guardian/gib2.md2", GIB_METALLIC), + HGIB_N(2, "models/monsters/guardian/gib3.md2", GIB_METALLIC), + HGIB_N(2, "models/monsters/guardian/gib4.md2", GIB_METALLIC), + HGIB_N(2, "models/monsters/guardian/gib5.md2", GIB_METALLIC), + HGIB_N(2, "models/monsters/guardian/gib6.md2", GIB_METALLIC), + HGIB("models/monsters/guardian/gib7.md2", GIB_METALLIC | GIB_HEAD), +}; + +static const drone_gib_t gibs_widow[] = { + HGIB_N(2, "models/objects/gibs/sm_meat/tris.md2", GIB_ORGANIC), + HGIB_N(3, "models/objects/gibs/sm_metal/tris.md2", GIB_METALLIC), + HGIB("models/monsters/blackwidow/gib1/tris.md2", GIB_METALLIC | GIB_UPRIGHT), + HGIB("models/monsters/blackwidow/gib2/tris.md2", GIB_METALLIC | GIB_UPRIGHT), + HGIB("models/monsters/blackwidow/gib3/tris.md2", GIB_METALLIC | GIB_UPRIGHT), + HGIB("models/monsters/blackwidow/gib4/tris.md2", GIB_METALLIC | GIB_UPRIGHT), +}; + +static const drone_gib_t gibs_widow2[] = { + HGIB_N(2, "models/objects/gibs/bone/tris.md2", GIB_ORGANIC), + HGIB_N(3, "models/objects/gibs/sm_meat/tris.md2", GIB_ORGANIC), + HGIB_N(3, "models/objects/gibs/sm_metal/tris.md2", GIB_METALLIC), + HGIB_N(3, "models/monsters/blackwidow2/gib1/tris.md2", GIB_METALLIC | GIB_UPRIGHT), + HGIB_N(3, "models/monsters/blackwidow2/gib2/tris.md2", GIB_METALLIC | GIB_UPRIGHT), + HGIB_N(2, "models/monsters/blackwidow2/gib3/tris.md2", GIB_METALLIC | GIB_UPRIGHT), + HGIB("models/monsters/blackwidow2/gib4/tris.md2", GIB_METALLIC | GIB_UPRIGHT), + HGIB_N(2, "models/monsters/blackwidow/gib3/tris.md2", GIB_METALLIC | GIB_UPRIGHT), + HGIB("models/objects/gibs/chest/tris.md2", GIB_ORGANIC), + HGIB("models/objects/gibs/head2/tris.md2", GIB_HEAD), +}; + +static const drone_gib_t gibs_rogue_turret[] = { + HGIB_N(4, "models/objects/debris1/tris.md2", GIB_METALLIC | GIB_DEBRIS), + HGIB_FRAME("models/monsters/turret/tris.md2", 14, GIB_SKINNED | GIB_METALLIC | GIB_HEAD | GIB_DEBRIS), +}; + +static const drone_gib_list_t list_soldier = HGIB_LIST(gibs_soldier); +static const drone_gib_list_t list_gunner = HGIB_LIST(gibs_gunner); +static const drone_gib_list_t list_guncmdr = HGIB_LIST(gibs_guncmdr); +static const drone_gib_list_t list_chick = HGIB_LIST(gibs_chick); +static const drone_gib_list_t list_brain = HGIB_LIST(gibs_brain); +static const drone_gib_list_t list_medic = HGIB_LIST(gibs_medic); +static const drone_gib_list_t list_mutant = HGIB_LIST(gibs_mutant); +static const drone_gib_list_t list_parasite = HGIB_LIST(gibs_parasite); +static const drone_gib_list_t list_berserk = HGIB_LIST(gibs_berserk); +static const drone_gib_list_t list_gladiator = HGIB_LIST(gibs_gladiator); +static const drone_gib_list_t list_infantry = HGIB_LIST(gibs_infantry); +static const drone_gib_list_t list_tank = HGIB_LIST(gibs_tank); +static const drone_gib_list_t list_flyer = HGIB_LIST(gibs_flyer); +static const drone_gib_list_t list_floater = HGIB_LIST(gibs_floater); +static const drone_gib_list_t list_hover = HGIB_LIST(gibs_hover); +static const drone_gib_list_t list_shambler = HGIB_LIST(gibs_shambler); +static const drone_gib_list_t list_arachnid = HGIB_LIST(gibs_arachnid); +static const drone_gib_list_t list_gekk = HGIB_LIST(gibs_gekk); +static const drone_gib_list_t list_stalker = HGIB_LIST(gibs_stalker); +static const drone_gib_list_t list_fixbot = HGIB_LIST(gibs_fixbot); +static const drone_gib_list_t list_supertank = HGIB_LIST(gibs_supertank); +static const drone_gib_list_t list_boss2 = HGIB_LIST(gibs_boss2); +static const drone_gib_list_t list_jorg = HGIB_LIST(gibs_jorg); +static const drone_gib_list_t list_makron = HGIB_LIST(gibs_makron); +static const drone_gib_list_t list_carrier = HGIB_LIST(gibs_carrier); +static const drone_gib_list_t list_guardian = HGIB_LIST(gibs_guardian); +static const drone_gib_list_t list_widow = HGIB_LIST(gibs_widow); +static const drone_gib_list_t list_widow2 = HGIB_LIST(gibs_widow2); +static const drone_gib_list_t list_rogue_turret = HGIB_LIST(gibs_rogue_turret); + +static const drone_gib_list_t all_drone_gib_lists[] = { + HGIB_LIST(gibs_soldier), + HGIB_LIST(gibs_gunner), + HGIB_LIST(gibs_guncmdr), + HGIB_LIST(gibs_chick), + HGIB_LIST(gibs_brain), + HGIB_LIST(gibs_medic), + HGIB_LIST(gibs_mutant), + HGIB_LIST(gibs_parasite), + HGIB_LIST(gibs_berserk), + HGIB_LIST(gibs_gladiator), + HGIB_LIST(gibs_infantry), + HGIB_LIST(gibs_tank), + HGIB_LIST(gibs_flyer), + HGIB_LIST(gibs_floater), + HGIB_LIST(gibs_hover), + HGIB_LIST(gibs_shambler), + HGIB_LIST(gibs_arachnid), + HGIB_LIST(gibs_gekk), + HGIB_LIST(gibs_stalker), + HGIB_LIST(gibs_fixbot), + HGIB_LIST(gibs_supertank), + HGIB_LIST(gibs_boss2), + HGIB_LIST(gibs_jorg), + HGIB_LIST(gibs_makron), + HGIB_LIST(gibs_carrier), + HGIB_LIST(gibs_guardian), + HGIB_LIST(gibs_widow), + HGIB_LIST(gibs_widow2), + HGIB_LIST(gibs_rogue_turret), +}; + +static const drone_gib_list_t *vrx_get_drone_gibs(edict_t *self) +{ + switch (self->mtype) + { + case M_SOLDIERLT: + case M_SOLDIER: + case M_SOLDIERSS: + case M_SOLDIER_RIPPER: + case M_SOLDIER_BLUEBLASTER: + case M_SOLDIER_LASER: + return &list_soldier; + case M_GUNNER: + return &list_gunner; + case M_GUNCMDR: + return &list_guncmdr; + case M_CHICK: + case M_CHICK_HEAT: + return &list_chick; + case M_BRAIN: + return &list_brain; + case M_MEDIC: + case M_MEDIC_COMMANDER: + return &list_medic; + case M_MUTANT: + case M_REDMUTANT: + return &list_mutant; + case M_PARASITE: + return &list_parasite; + case M_BERSERK: + return &list_berserk; + case M_GLADIATOR: + case M_GLADB: + case M_GLADC: + return &list_gladiator; + case M_INFANTRY: + return &list_infantry; + case M_TANK: + case M_COMMANDER: + case M_RUNNERTANK: + return &list_tank; + case M_FLYER: + return &list_flyer; + case M_FLOATER: + return &list_floater; + case M_HOVER: + case M_DAEDALUS: + return &list_hover; + case M_SHAMBLER: + return &list_shambler; + case M_ARACHNID: + return &list_arachnid; + case M_GEKK: + return &list_gekk; + case M_STALKER: + return &list_stalker; + case M_FIXBOT: + case M_FIXBOT_BOSS: + return &list_fixbot; + case M_SUPERTANK: + case M_JANITOR: + case M_BOSS5: + return &list_supertank; + case M_BOSS2: + case M_BOSS2_SMALL: + return &list_boss2; + case M_JORG: + return &list_jorg; + case M_MAKRON: + return &list_makron; + case M_CARRIER: + return &list_carrier; + case M_GUARDIAN: + case M_MINIGUARDIAN: + return &list_guardian; + case M_WIDOW: + return &list_widow; + case M_WIDOW2: + return &list_widow2; + case M_ROGUE_TURRET: + return &list_rogue_turret; + default: + return NULL; + } +} + +static int vrx_get_drone_gib_skinnum(edict_t *self) +{ + if (!self) + return 0; + + if (self->s.renderfx & RF_CUSTOMSKIN) + return 0; + + if (self->mtype == M_ROGUE_TURRET) + return self->s.skinnum; + + return self->s.skinnum / 2; +} + +static const char *vrx_get_drone_gib_model(edict_t *self, const drone_gib_t *gib) +{ + if (self->mtype == M_INFANTRY && (gib->type & GIB_HEAD) && + !strcmp(gib->model, "models/monsters/infantry/gibs/head.md2") && + self->monsterinfo.currentmove != &infantry_move_death3) + return "models/objects/gibs/sm_meat/tris.md2"; + + return gib->model; +} + +static void vrx_throw_drone_gib_list(edict_t *self, int damage, const drone_gib_list_t *list) +{ + edict_t *gib; + int gib_skinnum = vrx_get_drone_gib_skinnum(self); + float self_scale = self->s.scale ? self->s.scale : 1.0f; + const char *model; + int i; + int n; + + for (i = 0; i < list->count; i++) + { + model = vrx_get_drone_gib_model(self, &list->gibs[i]); + + for (n = 0; n < list->gibs[i].count; n++) + { + gib = ThrowGibEx(self, (char *)model, damage, list->gibs[i].type, + list->gibs[i].scale * self_scale); + if (gib) + { + if (list->gibs[i].type & GIB_SKINNED) + gib->s.skinnum = gib_skinnum; + if (list->gibs[i].frame) + gib->s.frame = list->gibs[i].frame; + } + } + } +} + +void vrx_precache_drone_gibs(void) +{ + int i; + int j; + + for (i = 0; i < HGIB_COUNT(all_drone_gib_lists); i++) + { + for (j = 0; j < all_drone_gib_lists[i].count; j++) + gi.modelindex((char *)all_drone_gib_lists[i].gibs[j].model); + } + gi.modelindex("models/monsters/tank/gibs/barm.md2"); +} + +qboolean vrx_throw_drone_gibs(edict_t *self, int damage) +{ + const drone_gib_list_t *list; + + if (!self || !self->inuse || nolag->value) + return false; + if (!vrx_spawn_nonessential_ent(self->s.origin)) + return false; + + list = vrx_get_drone_gibs(self); + if (!list) + return false; + + vrx_throw_drone_gib_list(self, damage, list); + + if ((self->mtype == M_TANK || self->mtype == M_COMMANDER || self->mtype == M_RUNNERTANK) && !self->style) + { + edict_t *arm = ThrowGibEx(self, "models/monsters/tank/gibs/barm.md2", damage, GIB_SKINNED | GIB_UPRIGHT, + self->s.scale ? self->s.scale : 1.0f); + if (arm) + arm->s.skinnum = vrx_get_drone_gib_skinnum(self); + } + + return true; +} + +void vrx_drop_tank_death_arm(edict_t *self, int damage) +{ + edict_t *arm; + vec3_t forward; + vec3_t right; + vec3_t up; + + if (!self || self->style) + return; + + self->style = 1; + + if (nolag->value || !vrx_spawn_nonessential_ent(self->s.origin)) + return; + + AngleVectors(self->s.angles, forward, right, up); + arm = ThrowGibEx(self, "models/monsters/tank/gibs/barm.md2", damage, GIB_SKINNED | GIB_UPRIGHT, + self->s.scale ? self->s.scale : 1.0f); + if (!arm) + return; + + arm->s.skinnum = vrx_get_drone_gib_skinnum(self); + VectorMA(self->s.origin, -16, right, arm->s.origin); + VectorMA(arm->s.origin, 23, up, arm->s.origin); + VectorCopy(arm->s.origin, arm->s.old_origin); + VectorScale(up, 100, arm->velocity); + VectorMA(arm->velocity, -120, right, arm->velocity); + VectorCopy(self->s.angles, arm->s.angles); + arm->s.angles[ROLL] = -90; + arm->avelocity[0] = crandom() * 15; + arm->avelocity[1] = crandom() * 15; + arm->avelocity[2] = 180; + gi.linkentity(arm); +} + +#undef HGIB +#undef HGIB_N +#undef HGIB_SCALE +#undef HGIB_FRAME +#undef HGIB_COUNT +#undef HGIB_LIST diff --git a/src/entities/drone/drone_gladiator.c b/src/entities/drone/drone_gladiator.c index d9f9ca58..780a22b0 100644 --- a/src/entities/drone/drone_gladiator.c +++ b/src/entities/drone/drone_gladiator.c @@ -24,7 +24,10 @@ static int sound_sight; void gladiator_idle (edict_t *self) { - gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); + if (random() < 0.5) + gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); + else + gi.sound (self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); } void gladiator_search (edict_t *self) @@ -99,10 +102,18 @@ mframe_t gladiator_frames_pain[] = ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, +}; +mmove_t gladiator_move_pain = { FRAME_pain2, FRAME_pain5, gladiator_frames_pain, gladiator_walk }; + +mframe_t gladiator_frames_pain_air[] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, }; -mmove_t gladiator_move_pain = { FRAME_pain1, FRAME_pain6, gladiator_frames_pain, gladiator_walk }; +mmove_t gladiator_move_pain_air = { FRAME_painup2, FRAME_painup6, gladiator_frames_pain_air, gladiator_walk }; void gladiator_pain(edict_t* self, edict_t* other, float kick, int damage) { @@ -110,8 +121,13 @@ void gladiator_pain(edict_t* self, edict_t* other, float kick, int damage) self->s.skinnum = (self->mtype == M_GLADB || self->mtype == M_GLADC) ? 3 : 1; // we're already in a pain state - if (self->monsterinfo.currentmove == &gladiator_move_pain) + if (self->monsterinfo.currentmove == &gladiator_move_pain || + self->monsterinfo.currentmove == &gladiator_move_pain_air) + { + if (self->velocity[2] > 100 && self->monsterinfo.currentmove == &gladiator_move_pain) + self->monsterinfo.currentmove = &gladiator_move_pain_air; return; + } // monster players don't get pain state induced if (G_GetClient(self)) @@ -133,7 +149,10 @@ void gladiator_pain(edict_t* self, edict_t* other, float kick, int damage) gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); } - self->monsterinfo.currentmove = &gladiator_move_pain; + if (self->velocity[2] > 100) + self->monsterinfo.currentmove = &gladiator_move_pain_air; + else + self->monsterinfo.currentmove = &gladiator_move_pain; } void gladiator_run (edict_t *self) @@ -489,8 +508,6 @@ mmove_t gladiator_move_death = {FRAME_death1, FRAME_death22, gladiator_frames_de void gladiator_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) { - int n; - M_Notify(self); // reduce lag by removing the entity right away @@ -506,13 +523,7 @@ void gladiator_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int da if (self->health <= self->gib_health) { gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); - if (vrx_spawn_nonessential_ent(self->s.origin)) - { - for (n = 0; n < 2; n++) - ThrowGib(self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); - for (n = 0; n < 4; n++) - ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); - } + vrx_throw_drone_gibs(self, damage); #ifdef OLD_NOLAG_STYLE M_Remove(self, false, false); diff --git a/src/entities/drone/drone_guardian.c b/src/entities/drone/drone_guardian.c index 9f7f50e0..346ca7de 100644 --- a/src/entities/drone/drone_guardian.c +++ b/src/entities/drone/drone_guardian.c @@ -14,9 +14,11 @@ static int sound_charge; static int sound_spin_loop; static int sound_laser; static int sound_pew; +static int sound_pain; #define GUARDIAN_INVASION_SCALE 0.45f #define GUARDIAN_INVASION_MOVE_SCALE 1.75f +#define GUARDIAN_SIGHT_ACK_CHANCE 0.30f void guardian_run(edict_t *self); @@ -122,6 +124,14 @@ static void guardian_footstep(edict_t *self) gi.sound(self, CHAN_BODY, sound_step, 1, ATTN_NORM, 0); } +static void guardian_sight(edict_t *self, edict_t *other) +{ + (void)other; + + if (random() < GUARDIAN_SIGHT_ACK_CHANCE) + gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); +} + mframe_t guardian_frames_stand[] = { drone_ai_stand, 0, NULL, @@ -277,6 +287,7 @@ void guardian_pain(edict_t *self, edict_t *other, float kick, int damage) return; self->pain_debounce_time = level.time + 3.0; + gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); self->monsterinfo.currentmove = &guardian_move_pain1; self->s.sound = 0; } @@ -653,16 +664,9 @@ void guardian_dead(edict_t *self) { int n; - if (vrx_spawn_nonessential_ent(self->s.origin)) - { - for (n = 0; n < 3; n++) - guardian_explode(self); - for (n = 0; n < 4; n++) - ThrowGib(self, "models/objects/gibs/sm_metal/tris.md2", 500, GIB_METALLIC); - for (n = 0; n < 2; n++) - ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", 500, GIB_ORGANIC); - ThrowHead(self, "models/objects/gibs/gear/tris.md2", 500, GIB_METALLIC); - } + for (n = 0; n < 3; n++) + guardian_explode(self); + vrx_throw_drone_gibs(self, 125); gi.WriteByte(svc_temp_entity); gi.WriteByte(TE_EXPLOSION1); @@ -755,6 +759,7 @@ void init_drone_guardian(edict_t *self) sound_spin_loop = gi.soundindex("weapons/hyprbl1a.wav"); sound_laser = gi.soundindex("weapons/laser2.wav"); sound_pew = gi.soundindex("weapons/rocklf1a.wav"); + sound_pain = gi.soundindex("zortemp/ack.wav"); if (!self->mtype) self->mtype = M_GUARDIAN; @@ -810,6 +815,7 @@ void init_drone_guardian(edict_t *self) self->monsterinfo.walk = guardian_walk; self->monsterinfo.run = guardian_run; self->monsterinfo.attack = guardian_attack; + self->monsterinfo.sight = guardian_sight; self->monsterinfo.currentmove = &guardian_move_stand; self->nextthink = level.time + FRAMETIME; diff --git a/src/entities/drone/drone_guncmdr.c b/src/entities/drone/drone_guncmdr.c index bbe64efb..1bea4d78 100644 --- a/src/entities/drone/drone_guncmdr.c +++ b/src/entities/drone/drone_guncmdr.c @@ -76,7 +76,10 @@ static void guncmdr_set_stand_bbox(edict_t *self) static void guncmdr_idle_sound(edict_t *self) { - gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); + if (random() < 0.5) + gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); + else + gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); } static void guncmdr_sight(edict_t *self, edict_t *other) @@ -1159,7 +1162,7 @@ static void guncmdr_pain(edict_t *self, edict_t *other, float kick, int damage) self->pain_debounce_time = level.time + 3.0; gi.sound(self, CHAN_VOICE, (random() < 0.5) ? sound_pain : sound_pain2, 1, ATTN_NORM, 0); - if (skill->value == 3) + if (invasion->value == 2) return; guncmdr_duck_up(self); @@ -1491,6 +1494,7 @@ static void guncmdr_die(edict_t *self, edict_t *inflictor, edict_t *attacker, in int n; vec3_t forward, dir; float dot = 0; + edict_t *head; M_Notify(self); @@ -1505,14 +1509,7 @@ static void guncmdr_die(edict_t *self, edict_t *inflictor, edict_t *attacker, in if (self->health <= self->gib_health) { gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); - if (vrx_spawn_nonessential_ent(self->s.origin)) - { - for (n = 0; n < 2; n++) - ThrowGib(self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); - for (n = 0; n < 4; n++) - ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); - ThrowHead(self, "models/monsters/gunner/gibs/head.md2", damage, GIB_ORGANIC); - } + vrx_throw_drone_gibs(self, damage); #ifdef OLD_NOLAG_STYLE M_Remove(self, false, false); #else @@ -1561,7 +1558,12 @@ static void guncmdr_die(edict_t *self, edict_t *inflictor, edict_t *attacker, in if (point[2] >= self->s.origin[2] + self->maxs[2] - 8 && self->velocity[2] < 65) { if (vrx_spawn_nonessential_ent(self->s.origin)) - ThrowGib(self, "models/monsters/gunner/gibs/head.md2", damage, GIB_ORGANIC); + { + head = ThrowGibEx(self, "models/monsters/gunner/gibs/head.md2", damage, GIB_SKINNED | GIB_HEAD, + self->s.scale ? self->s.scale : 1.0f); + if (head) + head->s.skinnum /= 2; + } self->monsterinfo.currentmove = &guncmdr_move_death5; } else if (dot < -0.40) diff --git a/src/entities/drone/drone_gunner.c b/src/entities/drone/drone_gunner.c index 9914cf7a..acb96ca9 100644 --- a/src/entities/drone/drone_gunner.c +++ b/src/entities/drone/drone_gunner.c @@ -880,8 +880,6 @@ mmove_t mygunnermove_death = {FRAME_death01, FRAME_death11, mygunnerframes_death void mygunnerdie (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) { - int n; - M_Notify(self); #ifdef OLD_NOLAG_STYLE @@ -897,14 +895,7 @@ void mygunnerdie (edict_t *self, edict_t *inflictor, edict_t *attacker, int dama if (self->health <= self->gib_health) { gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); - if (vrx_spawn_nonessential_ent(self->s.origin)) - { - for (n = 0; n < 2; n++) - ThrowGib(self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); - for (n = 0; n < 4; n++) - ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); - //ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); - } + vrx_throw_drone_gibs(self, damage); //self->deadflag = DEAD_DEAD; #ifdef OLD_NOLAG_STYLE M_Remove(self, false, false); @@ -978,6 +969,7 @@ void init_drone_gunner (edict_t *self) self->monsterinfo.dodge = mygunner_dodge; self->monsterinfo.attack = mygunner_attack; self->monsterinfo.walk = gunner_walk; + self->monsterinfo.idle = mygunnersearch; self->monsterinfo.pain_chance = 0.3f; self->pain = mygunner_pain; diff --git a/src/entities/drone/drone_hover.c b/src/entities/drone/drone_hover.c index b83012f8..d699e96f 100644 --- a/src/entities/drone/drone_hover.c +++ b/src/entities/drone/drone_hover.c @@ -549,6 +549,11 @@ void hover_pain (edict_t *self, edict_t *other, float kick, int damage) if (G_GetClient(self)) return; + if (level.time < self->pain_debounce_time) + return; + + self->pain_debounce_time = level.time + 3.0f; + // stand animation always gets pain state if (random() <= (1.0f - self->monsterinfo.pain_chance) && self->monsterinfo.currentmove == &hover_move_stand) @@ -570,17 +575,34 @@ void hover_pain (edict_t *self, edict_t *other, float kick, int damage) else { gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); - self->monsterinfo.currentmove = &hover_move_pain1; + if (random() < 0.3f) + self->monsterinfo.currentmove = &hover_move_pain1; + else + self->monsterinfo.currentmove = &hover_move_pain2; } } void hover_deadthink (edict_t *self) { - if (!self->groundentity && level.time < self->timestamp) + vec3_t end; + trace_t tr; + qboolean on_floor; + + on_floor = self->groundentity != NULL; + if (!on_floor) + { + VectorCopy(self->s.origin, end); + end[2] -= 24; + tr = gi.trace(self->s.origin, self->mins, self->maxs, end, self, MASK_SOLID); + on_floor = tr.fraction < 1.0f && tr.plane.normal[2] > 0.7f; + } + + if (!on_floor && level.time < self->timestamp) { self->nextthink = level.time + FRAMETIME; return; } + vrx_throw_drone_gibs(self, 150); BecomeExplosion1(self); } diff --git a/src/entities/drone/drone_infantry.c b/src/entities/drone/drone_infantry.c index d0e0e848..a2121c32 100644 --- a/src/entities/drone/drone_infantry.c +++ b/src/entities/drone/drone_infantry.c @@ -264,7 +264,10 @@ void Infantry20mm(edict_t* self) void infantry_sight (edict_t *self, edict_t *other) { - gi.sound (self, CHAN_BODY, sound_sight, 1, ATTN_NORM, 0); + if (random() < 0.5) + gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); } void infantry_dead (edict_t *self) @@ -425,6 +428,8 @@ mmove_t infantry_move_death3 = {FRAME_death301, FRAME_death309, infantry_frames_ void infantry_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) { int n; + edict_t *head; + vec3_t head_dir; M_Notify(self); @@ -440,14 +445,7 @@ void infantry_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int dam if (self->health <= self->gib_health) { gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); - if (vrx_spawn_nonessential_ent(self->s.origin)) - { - for (n = 0; n < 2; n++) - ThrowGib(self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); - for (n = 0; n < 4; n++) - ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); - ThrowHead(self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); - } + vrx_throw_drone_gibs(self, damage); #ifdef OLD_NOLAG_STYLE M_Remove(self, false, false); @@ -487,6 +485,29 @@ void infantry_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int dam gi.sound (self, CHAN_VOICE, sound_die2, 1, ATTN_NORM, 0); } + if (n != 2 && random() <= 0.25f && vrx_spawn_nonessential_ent(self->s.origin)) + { + head = ThrowGibEx(self, "models/monsters/infantry/gibs/head.md2", damage, GIB_ORGANIC, + self->s.scale ? self->s.scale : 1.0f); + if (head) + { + VectorCopy(self->s.angles, head->s.angles); + VectorCopy(self->s.origin, head->s.origin); + head->s.origin[2] += 32; + if (inflictor) + VectorSubtract(self->s.origin, inflictor->s.origin, head_dir); + else + VectorSet(head_dir, crandom(), crandom(), 0.5f); + if (VectorNormalize(head_dir) == 0) + VectorSet(head_dir, 0, 0, 1); + VectorScale(head_dir, 100, head->velocity); + head->velocity[2] = 200; + VectorScale(head->avelocity, 0.15f, head->avelocity); + head->s.skinnum = 0; + gi.linkentity(head); + } + } + if (self->activator && !self->activator->client) { self->activator->num_monsters_real--; diff --git a/src/entities/drone/drone_jorg.c b/src/entities/drone/drone_jorg.c index 3ae03c93..da3415ea 100644 --- a/src/entities/drone/drone_jorg.c +++ b/src/entities/drone/drone_jorg.c @@ -32,7 +32,6 @@ static int sound_death_hit; void BossExplode (edict_t *self); void MakronToss (edict_t *self); -/* void jorg_search (edict_t *self) { float r; @@ -46,7 +45,6 @@ void jorg_search (edict_t *self) else gi.sound (self, CHAN_VOICE, sound_search3, 1, ATTN_NORM, 0); } -*/ void jorgBFG (edict_t *self); @@ -224,6 +222,100 @@ void jorg_run (edict_t *self) self->monsterinfo.currentmove = &jorg_move_run; } +mframe_t jorg_frames_pain3[] = +{ + ai_move, -28, NULL, + ai_move, -6, NULL, + ai_move, -3, jorg_step_left, + ai_move, -9, NULL, + ai_move, 0, jorg_step_right, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, -7, NULL, + ai_move, 1, NULL, + ai_move, -11, NULL, + ai_move, -4, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 10, NULL, + ai_move, 11, NULL, + ai_move, 0, NULL, + ai_move, 10, NULL, + ai_move, 3, NULL, + ai_move, 10, NULL, + ai_move, 7, jorg_step_left, + ai_move, 17, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, jorg_step_right +}; +mmove_t jorg_move_pain3 = { FRAME_pain301, FRAME_pain325, jorg_frames_pain3, jorg_run }; + +mframe_t jorg_frames_pain2[] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t jorg_move_pain2 = { FRAME_pain201, FRAME_pain203, jorg_frames_pain2, jorg_run }; + +mframe_t jorg_frames_pain1[] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t jorg_move_pain1 = { FRAME_pain101, FRAME_pain103, jorg_frames_pain1, jorg_run }; + +void jorg_pain(edict_t *self, edict_t *other, float kick, int damage) +{ + qboolean do_pain3 = false; + + (void)other; + (void)kick; + + if (level.time < self->pain_debounce_time) + return; + + if (damage <= 40 && random() <= 0.6f) + return; + + if (self->s.frame >= FRAME_attak101 && self->s.frame <= FRAME_attak108 && random() <= 0.005f) + return; + + if (self->s.frame >= FRAME_attak109 && self->s.frame <= FRAME_attak114 && random() <= 0.00005f) + return; + + if (self->s.frame >= FRAME_attak201 && self->s.frame <= FRAME_attak208 && random() <= 0.005f) + return; + + self->pain_debounce_time = level.time + 3.0f; + + if (damage > 50) + { + if (damage <= 100) + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + else if (random() <= 0.3f) + { + do_pain3 = true; + gi.sound(self, CHAN_VOICE, sound_pain3, 1, ATTN_NORM, 0); + } + } + + if (invasion->value == 2) + return; + + self->s.sound = 0; + + if (damage <= 50) + self->monsterinfo.currentmove = &jorg_move_pain1; + else if (damage <= 100) + self->monsterinfo.currentmove = &jorg_move_pain2; + else if (do_pain3) + self->monsterinfo.currentmove = &jorg_move_pain3; +} + void jorgBFG_refire (edict_t *self) { if (G_ValidTarget(self, self->enemy, true, true) && entdist(self, self->enemy) <= 768 && random() <= 0.8) @@ -594,6 +686,7 @@ void init_drone_jorg (edict_t *self) self->monsterinfo.jumpup = 64; self->monsterinfo.jumpdn = 512; + // vortex bosses have pain animations disabled. //self->pain = jorg_pain; self->die = jorg_die; self->monsterinfo.stand = jorg_stand; @@ -602,7 +695,7 @@ void init_drone_jorg (edict_t *self) self->monsterinfo.aiflags |= AI_NO_CIRCLE_STRAFE; //self->monsterinfo.dodge = NULL; self->monsterinfo.attack = jorg_attack; - //self->monsterinfo.search = jorg_search; + self->monsterinfo.idle = jorg_search; //self->monsterinfo.melee = NULL; //self->monsterinfo.sight = NULL; //self->monsterinfo.checkattack = Jorg_CheckAttack; diff --git a/src/entities/drone/drone_makron.c b/src/entities/drone/drone_makron.c index 9d85be0a..aee687d9 100644 --- a/src/entities/drone/drone_makron.c +++ b/src/entities/drone/drone_makron.c @@ -353,6 +353,103 @@ mframe_t makron_frames_sight [] = }; mmove_t makron_move_sight= {FRAME_active01, FRAME_active13, makron_frames_sight, makron_run}; +mframe_t makron_frames_pain6[] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, makron_popup, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, makron_taunt, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t makron_move_pain6 = { FRAME_pain601, FRAME_pain627, makron_frames_pain6, makron_run }; + +mframe_t makron_frames_pain5[] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t makron_move_pain5 = { FRAME_pain501, FRAME_pain504, makron_frames_pain5, makron_run }; + +mframe_t makron_frames_pain4[] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t makron_move_pain4 = { FRAME_pain401, FRAME_pain404, makron_frames_pain4, makron_run }; + +void makron_pain(edict_t *self, edict_t *other, float kick, int damage) +{ + qboolean do_pain6 = false; + + (void)other; + (void)kick; + + if (self->monsterinfo.currentmove == &makron_move_sight) + return; + + if (level.time < self->pain_debounce_time) + return; + + if (damage <= 25 && random() < 0.2f) + return; + + self->pain_debounce_time = level.time + 3.0f; + + if (damage <= 40) + gi.sound(self, CHAN_VOICE, sound_pain4, 1, ATTN_NORM, 0); + else if (damage <= 110) + gi.sound(self, CHAN_VOICE, sound_pain5, 1, ATTN_NORM, 0); + else if (damage <= 150) + { + if (random() <= 0.45f) + { + do_pain6 = true; + gi.sound(self, CHAN_VOICE, sound_pain6, 1, ATTN_NORM, 0); + } + } + else if (random() <= 0.35f) + { + do_pain6 = true; + gi.sound(self, CHAN_VOICE, sound_pain6, 1, ATTN_NORM, 0); + } + + if (invasion->value == 2) + return; + + if (damage <= 40) + self->monsterinfo.currentmove = &makron_move_pain4; + else if (damage <= 110) + self->monsterinfo.currentmove = &makron_move_pain5; + else if (do_pain6) + self->monsterinfo.currentmove = &makron_move_pain6; +} + void makronBFG (edict_t *self) { vec3_t forward, start; @@ -559,21 +656,12 @@ void makron_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damag { edict_t *tempent; - int n; - self->s.sound = 0; // check for gib if (self->health <= self->gib_health) { gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); - if (vrx_spawn_nonessential_ent(self->s.origin)) - { - for (n = 0; n < 1 /*4*/; n++) - ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); - for (n = 0; n < 4; n++) - ThrowGib(self, "models/objects/gibs/sm_metal/tris.md2", damage, GIB_METALLIC); - ThrowHead(self, "models/objects/gibs/gear/tris.md2", damage, GIB_METALLIC); - } + vrx_throw_drone_gibs(self, damage); //self->deadflag = DEAD_DEAD; M_Remove(self, false, false); return; @@ -655,6 +743,7 @@ void init_drone_makron (edict_t *self) self->monsterinfo.jumpup = 64; self->monsterinfo.jumpdn = 512; + // vortex bosses have pain animations disabled. //self->pain = makron_pain; self->die = makron_die; self->monsterinfo.stand = makron_stand; diff --git a/src/entities/drone/drone_medic.c b/src/entities/drone/drone_medic.c index b29e70d8..1f93bc7d 100644 --- a/src/entities/drone/drone_medic.c +++ b/src/entities/drone/drone_medic.c @@ -24,6 +24,7 @@ static int commander_sound_pain1; static int commander_sound_pain2; static int commander_sound_die; static int commander_sound_sight; +static int commander_sound_search; static int commander_sound_hook_launch; static int commander_sound_hook_hit; static int commander_sound_hook_heal; @@ -97,6 +98,11 @@ static int medic_sight_sound(edict_t *self) return (medic_is_commander(self) && commander_sound_sight) ? commander_sound_sight : sound_sight; } +static int medic_search_sound(edict_t *self) +{ + return (medic_is_commander(self) && commander_sound_search) ? commander_sound_search : sound_search; +} + static int medic_hook_launch_sound(edict_t *self) { return (medic_is_commander(self) && commander_sound_hook_launch) ? commander_sound_hook_launch : sound_hook_launch; @@ -127,6 +133,11 @@ void mymedic_idle (edict_t *self) } +void mymedic_search (edict_t *self) +{ + gi.sound (self, CHAN_VOICE, medic_search_sound(self), 1, ATTN_IDLE, 0); +} + mframe_t mymedic_frames_stand [] = { drone_ai_stand, 0, mymedic_idle, //12 @@ -493,8 +504,6 @@ mmove_t mymedic_move_death = {FRAME_death1, FRAME_death30, mymedic_frames_death, void mymedic_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) { - int n; - M_Notify(self); #ifdef OLD_NOLAG_STYLE @@ -510,14 +519,7 @@ void mymedic_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int dama if (self->health <= self->gib_health) { gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); - if (vrx_spawn_nonessential_ent(self->s.origin)) - { - for (n = 0; n < 2; n++) - ThrowGib(self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); - for (n = 0; n < 4; n++) - ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); - //ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); - } + vrx_throw_drone_gibs(self, damage); //self->deadflag = DEAD_DEAD; #ifdef OLD_NOLAG_STYLE M_Remove(self, false, false); @@ -1726,8 +1728,7 @@ void init_drone_medic (edict_t *self) self->monsterinfo.sight = mymedic_sight; self->monsterinfo.jumpup = 64; self->monsterinfo.jumpdn = 512; -// self->monsterinfo.idle = mymedic_idle; -// self->monsterinfo.search = mymedic_search; + self->monsterinfo.idle = mymedic_search; // self->monsterinfo.checkattack = mymedic_checkattack; // self->monsterinfo.control_cost = 1; // self->monsterinfo.cost = 150; @@ -1761,6 +1762,7 @@ void init_drone_medic_commander(edict_t *self) commander_sound_pain2 = gi.soundindex("medic_commander/medpain2.wav"); commander_sound_die = gi.soundindex("medic_commander/meddeth.wav"); commander_sound_sight = gi.soundindex("medic_commander/medsght.wav"); + commander_sound_search = gi.soundindex("medic_commander/medsrch.wav"); commander_sound_hook_launch = gi.soundindex("medic_commander/medatck2c.wav"); commander_sound_hook_hit = gi.soundindex("medic_commander/medatck3a.wav"); commander_sound_hook_heal = gi.soundindex("medic_commander/medatck4a.wav"); diff --git a/src/entities/drone/drone_mutant.c b/src/entities/drone/drone_mutant.c index c485a395..dbece6d2 100644 --- a/src/entities/drone/drone_mutant.c +++ b/src/entities/drone/drone_mutant.c @@ -23,7 +23,12 @@ static int sound_step2; static int sound_step3; static int sound_thud; +#define MUTANT_SEARCH_SOUND_MIN_DELAY 15.0f +#define MUTANT_SEARCH_SOUND_RANDOM_DELAY 15.0f + void mutant_jump (edict_t *self); +extern mmove_t mutant_move_pain_short1; +extern mmove_t mutant_move_pain_short2; // // SOUNDS // @@ -45,6 +50,15 @@ void mutant_sight (edict_t *self, edict_t *other) gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); } +void mutant_search (edict_t *self) +{ + if (level.time < self->wait) + return; + + gi.sound (self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); + self->wait = level.time + MUTANT_SEARCH_SOUND_MIN_DELAY + (float)random() * MUTANT_SEARCH_SOUND_RANDOM_DELAY; +} + void mutant_swing (edict_t *self) { gi.sound (self, CHAN_VOICE, sound_swing, 1, ATTN_NORM, 0); @@ -366,7 +380,7 @@ void mutant_check_landing (edict_t *self) || (level.time > self->monsterinfo.pausetime)) { self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; - //VectorClear(self->velocity); + self->monsterinfo.nextattack = 0; } else { @@ -475,8 +489,6 @@ mmove_t mutant_move_death2 = {FRAME_death201, FRAME_death210, mutant_frames_deat void mutant_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) { - int n; - M_Notify(self); #ifdef OLD_NOLAG_STYLE @@ -492,14 +504,7 @@ void mutant_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damag if (self->health <= self->gib_health) { gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); - if (vrx_spawn_nonessential_ent(self->s.origin)) - { - for (n = 0; n < 2; n++) - ThrowGib(self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); - for (n = 0; n < 4; n++) - ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); - //ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); - } + vrx_throw_drone_gibs(self, damage); //self->deadflag = DEAD_DEAD; #ifdef OLD_NOLAG_STYLE M_Remove(self, false, false); @@ -578,6 +583,18 @@ mframe_t mutant_frames_pain_short2[] = }; mmove_t mutant_move_pain_short2 = { FRAME_pain101, FRAME_pain105, mutant_frames_pain_short2, mutant_run }; +static float mutant_air_pain_velocity_scale(edict_t *self, int damage) +{ + float heavy_damage = self->max_health * 0.12f; + float medium_damage = self->max_health * 0.06f; + + if (damage >= heavy_damage) + return 0.70f; + if (damage >= medium_damage) + return 0.85f; + return 0.90f; +} + void mutant_pain(edict_t* self, edict_t* other, float kick, int damage) { const double rng = random(); @@ -598,6 +615,25 @@ void mutant_pain(edict_t* self, edict_t* other, float kick, int damage) if (invasion->value == 2) return; + if (self->monsterinfo.currentmove == &mutant_move_jump && !self->groundentity) + { + float air_scale = mutant_air_pain_velocity_scale(self, damage); + + if (level.time >= self->pain_debounce_time) + { + if (random() < 0.5) + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + self->pain_debounce_time = level.time + 0.5; + } + + self->velocity[0] *= air_scale; + self->velocity[1] *= air_scale; + self->velocity[2] *= air_scale; + return; + } + // if we're fidgeting, always go into pain state. if (rng <= (1.0f - self->monsterinfo.pain_chance) && self->monsterinfo.currentmove != &mutant_move_idle && @@ -605,18 +641,13 @@ void mutant_pain(edict_t* self, edict_t* other, float kick, int damage) self->monsterinfo.currentmove != &mutant_move_walk) return; - if (self->monsterinfo.currentmove == &mutant_move_jump) - gi.sound(self, CHAN_VOICE, sound_thud, 1, ATTN_NORM, 0); + if (random() < 0.5) + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); else - { - if (random() < 0.5) - gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); - else - gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); - } + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + self->monsterinfo.nextattack = 0; if (self->monsterinfo.currentmove == &mutant_move_idle || - self->monsterinfo.currentmove == &mutant_move_jump || self->monsterinfo.currentmove == &mutant_move_walk) { self->monsterinfo.currentmove = &mutant_move_pain_long1; } @@ -644,7 +675,7 @@ void init_drone_mutant (edict_t *self) sound_pain1 = gi.soundindex ("mutant/mutpain1.wav"); sound_pain2 = gi.soundindex ("mutant/mutpain2.wav"); sound_sight = gi.soundindex ("mutant/mutsght1.wav"); - //sound_search = gi.soundindex ("mutant/mutsrch1.wav"); + sound_search = gi.soundindex ("mutant/mutsrch1.wav"); sound_step1 = gi.soundindex ("mutant/step1.wav"); sound_step2 = gi.soundindex ("mutant/step2.wav"); sound_step3 = gi.soundindex ("mutant/step3.wav"); @@ -678,8 +709,8 @@ void init_drone_mutant (edict_t *self) self->monsterinfo.attack = mutant_attack; self->monsterinfo.melee = mutant_melee; self->monsterinfo.sight = mutant_sight; -// self->monsterinfo.search = mutant_search; - //self->monsterinfo.idle = mutant_idle; + self->monsterinfo.idle = mutant_search; + self->wait = level.time + MUTANT_SEARCH_SOUND_MIN_DELAY + (float)random() * MUTANT_SEARCH_SOUND_RANDOM_DELAY; // self->monsterinfo.checkattack = mutant_checkattack; self->monsterinfo.jumpup = 64; self->monsterinfo.jumpdn = 512; diff --git a/src/entities/drone/drone_parasite.c b/src/entities/drone/drone_parasite.c index 96d6d1e6..d1608319 100644 --- a/src/entities/drone/drone_parasite.c +++ b/src/entities/drone/drone_parasite.c @@ -489,8 +489,6 @@ mmove_t myparasite_move_death = {FRAME_death101, FRAME_death107, myparasite_fram void myparasite_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) { - int n; - M_Notify(self); #ifdef OLD_NOLAG_STYLE @@ -509,14 +507,7 @@ void myparasite_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int d if (self->health <= self->gib_health) { gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); - if (vrx_spawn_nonessential_ent(self->s.origin)) - { - for (n = 0; n < 2; n++) - ThrowGib(self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); - for (n = 0; n < 4; n++) - ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); - //ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); - } + vrx_throw_drone_gibs(self, damage); //self->deadflag = DEAD_DEAD; #ifdef OLD_NOLAG_STYLE M_Remove(self, false, false); diff --git a/src/entities/drone/drone_redmutant.c b/src/entities/drone/drone_redmutant.c index 3b41cf31..02944dc3 100644 --- a/src/entities/drone/drone_redmutant.c +++ b/src/entities/drone/drone_redmutant.c @@ -517,8 +517,6 @@ mmove_t redmutant_move_death2 = { FRAME_death201, FRAME_death207, redmutant_fram static void redmutant_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) { - int n; - M_Notify(self); #ifdef OLD_NOLAG_STYLE @@ -532,15 +530,7 @@ static void redmutant_die(edict_t *self, edict_t *inflictor, edict_t *attacker, if (self->health <= self->gib_health) { gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); - if (vrx_spawn_nonessential_ent(self->s.origin)) - { - for (n = 0; n < 2; n++) - ThrowGib(self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); - for (n = 0; n < 4; n++) - ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); - ThrowGib(self, "models/monsters/mutant/gibs/chest.md2", damage, GIB_ORGANIC); - ThrowHead(self, "models/monsters/mutant/gibs/head.md2", damage, GIB_ORGANIC); - } + vrx_throw_drone_gibs(self, damage); #ifdef OLD_NOLAG_STYLE M_Remove(self, false, false); #else @@ -621,7 +611,7 @@ static void redmutant_pain(edict_t *self, edict_t *other, float kick, int damage else gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); - if (skill->value == 3) + if (invasion->value == 2) return; redmutant_restore_bbox(self); diff --git a/src/entities/drone/drone_rogue_turret.c b/src/entities/drone/drone_rogue_turret.c index 33a29283..28000ba2 100644 --- a/src/entities/drone/drone_rogue_turret.c +++ b/src/entities/drone/drone_rogue_turret.c @@ -297,12 +297,9 @@ void rogue_turret_force_ready(edict_t *self) static void rogue_turret_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) { - int n; - rogue_turret_laser_off(self); - for (n = 0; n < 3; n++) - ThrowGib(self, "models/objects/debris1/tris.md2", 150, GIB_METALLIC); + vrx_throw_drone_gibs(self, damage ? damage : 150); gi.WriteByte(svc_temp_entity); gi.WriteByte(TE_EXPLOSION1); diff --git a/src/entities/drone/drone_runnertank.c b/src/entities/drone/drone_runnertank.c index d42aa408..73b0a6c5 100644 --- a/src/entities/drone/drone_runnertank.c +++ b/src/entities/drone/drone_runnertank.c @@ -18,6 +18,7 @@ static int skin_pain; #define RUNNERTANK_JUMP_ATTACK_DROP_RADIUS 90.0f #define RUNNERTANK_JUMP_ATTACK_DROP_SPEED 900.0f #define RUNNERTANK_JUMP_ATTACK_DROP_GRAVITY 3.0f +#define RUNNERTANK_MELEE_RANGE 144.0f #define RUNNERTANK_INVASION_RUN_SCALE 1.15f #define RUNNERTANK_NORMAL_SKIN "models/vault/monsters/tank/skin.pcx" #define RUNNERTANK_PAIN_SKIN "models/monsters/tank/pain.pcx" @@ -649,7 +650,7 @@ static void runnertank_start_jump_attack(edict_t *self) static qboolean runnertank_can_jump_attack(edict_t *self, float range) { - float height_diff; + float height_diff; if (level.time < self->monsterinfo.melee_finished) return false; @@ -657,33 +658,38 @@ static qboolean runnertank_can_jump_attack(edict_t *self, float range) return false; if (self->monsterinfo.aiflags & AI_STAND_GROUND) return false; - if ((range <= 128) || (range > 512)) - return false; - if (!nearfov(self, self->enemy, 0, RUNNERTANK_JUMP_ATTACK_FOV)) - return false; + if ((range <= RUNNERTANK_MELEE_RANGE) || (range > 512)) + return false; + if (!nearfov(self, self->enemy, 0, RUNNERTANK_JUMP_ATTACK_FOV)) + return false; height_diff = self->enemy->absmin[2] - self->absmin[2]; - return (height_diff > -64) && (height_diff < 128); + return (height_diff > -64) && (height_diff < 128); +} + +static qboolean runnertank_should_melee(edict_t *self, float range) +{ + return self->groundentity && range <= RUNNERTANK_MELEE_RANGE; } static void runnertank_melee(edict_t *self) { - float range; + float range; if (!G_ValidTarget(self, self->enemy, true, true)) return; - range = entdist(self, self->enemy); - if ((range <= 128) && self->groundentity) - self->monsterinfo.currentmove = &runnertank_move_strike; - else if (runnertank_can_jump_attack(self, range)) - runnertank_start_jump_attack(self); + range = entdist(self, self->enemy); + if (runnertank_should_melee(self, range)) + self->monsterinfo.currentmove = &runnertank_move_strike; + else if (runnertank_can_jump_attack(self, range)) + runnertank_start_jump_attack(self); } static void runnertank_attack(edict_t *self) { - float r, range; - qboolean attack_started = false; + float r, range; + qboolean attack_started = false; qboolean can_rail; qboolean can_rocket; qboolean can_chain; @@ -691,25 +697,30 @@ static void runnertank_attack(edict_t *self) if (!G_ValidTarget(self, self->enemy, true, true)) return; - r = random(); - range = entdist(self, self->enemy); - can_rail = runnertank_can_rail(self); - can_rocket = runnertank_can_rocket(self); - can_chain = runnertank_can_chain(self); - - if ((range <= 128) && self->groundentity) - { - self->monsterinfo.currentmove = &runnertank_move_strike; - attack_started = true; - } - else if (runnertank_can_jump_attack(self, range)) - { - runnertank_start_jump_attack(self); - attack_started = true; - } - else if (range <= 256) - { - if (r <= 0.35) + r = random(); + range = entdist(self, self->enemy); + + if (runnertank_should_melee(self, range)) + { + self->monsterinfo.currentmove = &runnertank_move_strike; + M_DelayNextAttack(self, 0, true); + return; + } + + if (runnertank_can_jump_attack(self, range)) + { + runnertank_start_jump_attack(self); + M_DelayNextAttack(self, 0, true); + return; + } + + can_rail = runnertank_can_rail(self); + can_rocket = runnertank_can_rocket(self); + can_chain = runnertank_can_chain(self); + + if (range <= 256) + { + if (r <= 0.35) { if (can_chain) { @@ -887,7 +898,7 @@ static void runnertank_pain(edict_t *self, edict_t *other, float kick, int damag self->pain_debounce_time = level.time + 3.0; gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); - if (skill->value == 3) + if (invasion->value == 2) return; if (damage <= 30 || random() < 0.5) @@ -952,8 +963,6 @@ mmove_t runnertank_move_death = { FRAME_death01, FRAME_death32, runnertank_frame static void runnertank_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) { - int n; - M_Notify(self); #ifdef OLD_NOLAG_STYLE @@ -967,14 +976,7 @@ static void runnertank_die(edict_t *self, edict_t *inflictor, edict_t *attacker, if (self->health <= self->gib_health) { gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); - if (vrx_spawn_nonessential_ent(self->s.origin)) - { - for (n = 0; n < 1; n++) - ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); - for (n = 0; n < 4; n++) - ThrowGib(self, "models/objects/gibs/sm_metal/tris.md2", damage, GIB_METALLIC); - ThrowGib(self, "models/objects/gibs/chest/tris.md2", damage, GIB_ORGANIC); - } + vrx_throw_drone_gibs(self, damage); #ifdef OLD_NOLAG_STYLE M_Remove(self, false, false); #else @@ -989,6 +991,8 @@ static void runnertank_die(edict_t *self, edict_t *inflictor, edict_t *attacker, if (self->deadflag == DEAD_DEAD) return; + vrx_drop_tank_death_arm(self, damage); + DroneList_Remove(self); gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); diff --git a/src/entities/drone/drone_shambler.c b/src/entities/drone/drone_shambler.c index b0cdb246..0cf5d4f9 100644 --- a/src/entities/drone/drone_shambler.c +++ b/src/entities/drone/drone_shambler.c @@ -740,8 +740,6 @@ void shambler_dead(edict_t* self) void shambler_die(edict_t* self, edict_t* inflictor, edict_t* attacker, int damage, vec3_t point) { - int n; - // notify the owner that the monster is dead M_Notify(self); @@ -757,14 +755,7 @@ void shambler_die(edict_t* self, edict_t* inflictor, edict_t* attacker, int dama if (self->health <= self->gib_health) { gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); - if (vrx_spawn_nonessential_ent(self->s.origin)) - { - for (n = 0; n < 2; n++) - ThrowGib(self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); - for (n = 0; n < 4; n++) - ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); - ThrowHead(self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); - } + vrx_throw_drone_gibs(self, damage); //self->deadflag = DEAD_DEAD; //return;//FIXME: this will cause DroneList_Next to enter an infinite loop diff --git a/src/entities/drone/drone_soldier.c b/src/entities/drone/drone_soldier.c index 1db9dc29..6cfbc8a8 100644 --- a/src/entities/drone/drone_soldier.c +++ b/src/entities/drone/drone_soldier.c @@ -21,6 +21,7 @@ static int sound_death_ss; static int sound_cock; static mmove_t m_soldier_move_attack3; +static mmove_t m_soldier_move_attack2; static mmove_t m_soldier_move_attack5; static mmove_t m_soldier_move_trip; static mmove_t m_soldier_move_duck; @@ -94,6 +95,7 @@ static const int soldier_hyper_flash[] = }; void m_soldier_stand (edict_t *self); +void m_soldier_walk (edict_t *self); void m_soldier_run (edict_t *self); void m_soldier_runandshoot_continue (edict_t *self); void drone_ai_run_slide(edict_t *self, float dist); @@ -136,6 +138,14 @@ void m_soldier_cock (edict_t *self) gi.sound (self, CHAN_WEAPON, sound_cock, 1, ATTN_NORM, 0); } +void m_soldier_sight (edict_t *self, edict_t *other) +{ + if (random() < 0.5) + gi.sound (self, CHAN_VOICE, sound_sight1, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, sound_sight2, 1, ATTN_NORM, 0); +} + mframe_t m_soldier_frames_stand1 [] = { drone_ai_stand, 0, m_soldier_idle, @@ -173,6 +183,54 @@ mframe_t m_soldier_frames_stand1 [] = }; mmove_t m_soldier_move_stand1 = {FRAME_stand101, FRAME_stand130, m_soldier_frames_stand1, m_soldier_stand}; +mframe_t m_soldier_frames_stand2 [] = +{ + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL +}; +mmove_t m_soldier_move_stand2 = {FRAME_stand201, FRAME_stand240, m_soldier_frames_stand2, m_soldier_stand}; + mframe_t m_soldier_frames_stand3 [] = { drone_ai_stand, 0, NULL, @@ -222,12 +280,90 @@ mmove_t m_soldier_move_stand3 = {FRAME_stand301, FRAME_stand339, m_soldier_frame void m_soldier_stand (edict_t *self) { - if (self->monsterinfo.currentmove != &m_soldier_move_stand1 || random() < 0.8) + float r = random(); + + if (self->monsterinfo.currentmove != &m_soldier_move_stand1 || r < 0.6f) self->monsterinfo.currentmove = &m_soldier_move_stand1; + else if (r < 0.8f) + self->monsterinfo.currentmove = &m_soldier_move_stand2; else self->monsterinfo.currentmove = &m_soldier_move_stand3; } +mframe_t m_soldier_frames_walk1 [] = +{ + drone_ai_walk, 3, NULL, + drone_ai_walk, 6, NULL, + drone_ai_walk, 2, NULL, + drone_ai_walk, 2, NULL, + drone_ai_walk, 2, NULL, + drone_ai_walk, 1, NULL, + drone_ai_walk, 6, NULL, + drone_ai_walk, 5, NULL, + drone_ai_walk, 3, NULL, + drone_ai_walk, -1, NULL, + + drone_ai_walk, 0, NULL, + drone_ai_walk, 0, NULL, + drone_ai_walk, 0, NULL, + drone_ai_walk, 0, NULL, + drone_ai_walk, 0, NULL, + drone_ai_walk, 0, NULL, + drone_ai_walk, 0, NULL, + drone_ai_walk, 0, NULL, + drone_ai_walk, 0, NULL, + drone_ai_walk, 0, NULL, + + drone_ai_walk, 0, NULL, + drone_ai_walk, 0, NULL, + drone_ai_walk, 0, NULL, + drone_ai_walk, 0, NULL, + drone_ai_walk, 0, NULL, + drone_ai_walk, 0, NULL, + drone_ai_walk, 0, NULL, + drone_ai_walk, 0, NULL, + drone_ai_walk, 0, NULL, + drone_ai_walk, 0, NULL, + + drone_ai_walk, 0, NULL, + drone_ai_walk, 0, NULL, + drone_ai_walk, 0, NULL +}; +mmove_t m_soldier_move_walk1 = {FRAME_walk101, FRAME_walk133, m_soldier_frames_walk1, NULL}; + +mframe_t m_soldier_frames_walk2 [] = +{ + drone_ai_walk, 4, NULL, + drone_ai_walk, 4, NULL, + drone_ai_walk, 9, NULL, + drone_ai_walk, 8, NULL, + drone_ai_walk, 5, NULL, + drone_ai_walk, 1, NULL, + drone_ai_walk, 3, NULL, + drone_ai_walk, 7, NULL, + drone_ai_walk, 6, NULL, + drone_ai_walk, 7, NULL +}; +mmove_t m_soldier_move_walk2 = {FRAME_walk209, FRAME_walk218, m_soldier_frames_walk2, NULL}; + +void m_soldier_walk (edict_t *self) +{ + if (!self->goalentity) + self->goalentity = world; + + if (random() < 0.5f) + self->monsterinfo.currentmove = &m_soldier_move_walk1; + else + self->monsterinfo.currentmove = &m_soldier_move_walk2; +} + +mframe_t m_soldier_frames_start_run [] = +{ + drone_ai_run, 7, NULL, + drone_ai_run, 5, NULL +}; +mmove_t m_soldier_move_start_run = {FRAME_run01, FRAME_run02, m_soldier_frames_start_run, m_soldier_run}; + mframe_t m_soldier_frames_run [] = { drone_ai_run, 25, NULL, @@ -264,8 +400,13 @@ void m_soldier_run (edict_t *self) { if (self->monsterinfo.aiflags & AI_STAND_GROUND) m_soldier_stand(self); - else + else if (self->monsterinfo.currentmove == &m_soldier_move_walk1 || + self->monsterinfo.currentmove == &m_soldier_move_walk2 || + self->monsterinfo.currentmove == &m_soldier_move_start_run || + self->monsterinfo.currentmove == &m_soldier_move_run) self->monsterinfo.currentmove = &m_soldier_move_run; + else + self->monsterinfo.currentmove = &m_soldier_move_start_run; } static int soldier_flash_from_table(const int *flashes, size_t count, int flash_number) @@ -576,6 +717,53 @@ void m_soldier_fire (edict_t *self) soldier_firelaser(self, MZ2_SOLDIER_MACHINEGUN_4); } +static qboolean m_soldier_can_flash_slot_shot(edict_t *self, int flash_slot) +{ + if (self->mtype == M_SOLDIER) + return soldier_has_flash_shot(self, soldier_flash_from_table(soldier_blaster_flash, + sizeof(soldier_blaster_flash) / sizeof(soldier_blaster_flash[0]), flash_slot), false); + if (self->mtype == M_SOLDIERLT) + return soldier_has_flash_shot(self, soldier_flash_from_table(soldier_blaster_flash, + sizeof(soldier_blaster_flash) / sizeof(soldier_blaster_flash[0]), flash_slot), false); + if (self->mtype == M_SOLDIERSS) + return soldier_has_flash_shot(self, soldier_flash_from_table(soldier_shotgun_flash, + sizeof(soldier_shotgun_flash) / sizeof(soldier_shotgun_flash[0]), flash_slot), false); + if (self->mtype == M_SOLDIER_RIPPER) + return soldier_has_flash_shot(self, soldier_flash_from_table(soldier_ripper_flash, + sizeof(soldier_ripper_flash) / sizeof(soldier_ripper_flash[0]), flash_slot), false); + if (self->mtype == M_SOLDIER_BLUEBLASTER) + return soldier_has_flash_shot(self, soldier_flash_from_table(soldier_hyper_flash, + sizeof(soldier_hyper_flash) / sizeof(soldier_hyper_flash[0]), flash_slot), false); + if (self->mtype == M_SOLDIER_LASER) + return soldier_has_laser_shot(self, soldier_flash_from_table(soldier_machinegun_flash, + sizeof(soldier_machinegun_flash) / sizeof(soldier_machinegun_flash[0]), flash_slot)); + + return false; +} + +static void m_soldier_fire_flash_slot(edict_t *self, int flash_slot) +{ + if (!G_EntExists(self->enemy)) + return; + + if (self->mtype == M_SOLDIER) + soldier_fireblaster_flash(self, soldier_flash_from_table(soldier_blaster_flash, + sizeof(soldier_blaster_flash) / sizeof(soldier_blaster_flash[0]), flash_slot)); + else if (self->mtype == M_SOLDIERLT) + soldier_firerocket_flash(self, soldier_flash_from_table(soldier_blaster_flash, + sizeof(soldier_blaster_flash) / sizeof(soldier_blaster_flash[0]), flash_slot)); + else if (self->mtype == M_SOLDIERSS) + soldier_fireshotgun_flash(self, soldier_flash_from_table(soldier_shotgun_flash, + sizeof(soldier_shotgun_flash) / sizeof(soldier_shotgun_flash[0]), flash_slot)); + else if (self->mtype == M_SOLDIER_RIPPER) + soldier_fireionripper(self, flash_slot); + else if (self->mtype == M_SOLDIER_BLUEBLASTER) + soldier_fireblueblaster(self, flash_slot); + else if (self->mtype == M_SOLDIER_LASER) + soldier_firelaser(self, soldier_flash_from_table(soldier_machinegun_flash, + sizeof(soldier_machinegun_flash) / sizeof(soldier_machinegun_flash[0]), flash_slot)); +} + void m_soldier_hyperripper_run_fire(edict_t *self) { if (self->mtype == M_SOLDIER_RIPPER) @@ -723,6 +911,59 @@ mframe_t m_soldier_frames_attack1 [] = }; mmove_t m_soldier_move_attack1 = {FRAME_attak101, FRAME_attak112, m_soldier_frames_attack1, m_soldier_endattack1}; +static qboolean m_soldier_can_attack2_shot(edict_t *self) +{ + return m_soldier_can_flash_slot_shot(self, 1); +} + +static void m_soldier_fire_attack2(edict_t *self) +{ + m_soldier_fire_flash_slot(self, 1); +} + +static void m_soldier_attack2_refire(edict_t *self) +{ + if (G_ValidTarget(self, self->enemy, true, true) && + entdist(self, self->enemy) <= 512 && + random() < 0.65f && + m_soldier_can_attack2_shot(self)) + self->monsterinfo.nextframe = FRAME_attak204; + + M_DelayNextAttack(self, 0, true); +} + +mframe_t m_soldier_frames_attack2 [] = +{ + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, m_soldier_fire_attack2, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, m_soldier_cock, + ai_charge, 0, NULL, + ai_charge, 0, m_soldier_attack2_refire, + ai_charge, 0, NULL, + ai_charge, 0, NULL, + ai_charge, 0, NULL +}; +static mmove_t m_soldier_move_attack2 = {FRAME_attak201, FRAME_attak218, m_soldier_frames_attack2, m_soldier_run}; + +static void m_soldier_select_stationary_attack(edict_t *self) +{ + if (m_soldier_can_attack2_shot(self) && random() < 0.5f) + self->monsterinfo.currentmove = &m_soldier_move_attack2; + else + self->monsterinfo.currentmove = &m_soldier_move_attack1; +} + void m_soldier_endattack_laser(edict_t* self) { self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; @@ -782,7 +1023,7 @@ mmove_t m_soldier_move_attack_laser = {FRAME_attak401, FRAME_attak406, m_soldier void m_soldier_attack(edict_t* self) { - if (!m_soldier_can_primary_shot(self)) + if (!m_soldier_can_primary_shot(self) && !m_soldier_can_attack2_shot(self)) return; if (self->mtype == M_SOLDIER_LASER) @@ -802,12 +1043,12 @@ void m_soldier_attack(edict_t* self) if (self->monsterinfo.aiflags & AI_STAND_GROUND) { - self->monsterinfo.currentmove = &m_soldier_move_attack1; + m_soldier_select_stationary_attack(self); return; } if ((entdist(self, self->enemy) < 128) && (random() <= 0.8)) - self->monsterinfo.currentmove = &m_soldier_move_attack1; + m_soldier_select_stationary_attack(self); else self->monsterinfo.currentmove = &m_soldier_move_runandshoot; @@ -1186,51 +1427,51 @@ static void m_soldier_death_shrink(edict_t *self) mframe_t soldier_frames_pain_short1[] = { - ai_move, 0, NULL, - ai_move, 0, NULL, - ai_move, 0, NULL, - ai_move, 0, NULL, + ai_move, -3, NULL, + ai_move, 4, NULL, + ai_move, 1, NULL, + ai_move, 1, NULL, ai_move, 0, NULL, }; mmove_t soldier_move_pain_short1 = { FRAME_pain101, FRAME_pain105, soldier_frames_pain_short1, m_soldier_run }; mframe_t soldier_frames_pain_short2[] = { - ai_move, 0, NULL, - ai_move, 0, NULL, - ai_move, 0, NULL, - ai_move, 0, NULL, + ai_move, -13, NULL, + ai_move, -1, NULL, + ai_move, 2, NULL, + ai_move, 4, NULL, - ai_move, 0, NULL, - ai_move, 0, NULL, - ai_move, 0, NULL, + ai_move, 2, NULL, + ai_move, 3, NULL, + ai_move, 2, NULL, }; mmove_t soldier_move_pain_short2 = { FRAME_pain201, FRAME_pain207, soldier_frames_pain_short2, m_soldier_run }; mframe_t soldier_frames_pain_long1[] = { - ai_move, 0, NULL, - ai_move, 0, NULL, - ai_move, 0, NULL, - ai_move, 0, NULL, + ai_move, -8, NULL, + ai_move, 10, NULL, + ai_move, -4, NULL, + ai_move, -1, NULL, - ai_move, 0, NULL, - ai_move, 0, NULL, + ai_move, -3, NULL, ai_move, 0, NULL, + ai_move, 3, NULL, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, - ai_move, 0, NULL, + ai_move, 1, NULL, ai_move, 0, NULL, - ai_move, 0, NULL, - ai_move, 0, NULL, - ai_move, 0, NULL, + ai_move, 1, NULL, + ai_move, 2, NULL, + ai_move, 4, NULL, - ai_move, 0, NULL, - ai_move, 0, NULL, + ai_move, 3, NULL, + ai_move, 2, NULL, }; mmove_t soldier_move_pain_long1 = { FRAME_pain301, FRAME_pain318, soldier_frames_pain_long1, m_soldier_run }; @@ -1239,22 +1480,22 @@ mframe_t soldier_frames_pain_long2[] = ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, - ai_move, 0, NULL, + ai_move, -10, NULL, - ai_move, 0, NULL, - ai_move, 0, NULL, - ai_move, 0, NULL, - ai_move, 0, NULL, + ai_move, -6, NULL, + ai_move, 8, NULL, + ai_move, 4, NULL, + ai_move, 1, NULL, ai_move, 0, NULL, - ai_move, 0, NULL, - ai_move, 0, NULL, - ai_move, 0, NULL, + ai_move, 2, NULL, + ai_move, 5, NULL, + ai_move, 2, NULL, - ai_move, 0, NULL, - ai_move, 0, NULL, - ai_move, 0, NULL, - ai_move, 0, NULL, + ai_move, -1, NULL, + ai_move, -1, NULL, + ai_move, 3, NULL, + ai_move, 2, NULL, ai_move, 0, NULL, }; @@ -1292,10 +1533,12 @@ void soldier_pain(edict_t* self, edict_t* other, float kick, int damage) // if we're fidgeting, always go into pain state. if (random() <= (1.0f - self->monsterinfo.pain_chance) && self->monsterinfo.currentmove != &m_soldier_move_stand1 && + self->monsterinfo.currentmove != &m_soldier_move_stand2 && self->monsterinfo.currentmove != &m_soldier_move_stand3) return; if (self->monsterinfo.currentmove == &m_soldier_move_stand1 || + self->monsterinfo.currentmove == &m_soldier_move_stand2 || self->monsterinfo.currentmove == &m_soldier_move_stand3) { if (random() < 0.5) self->monsterinfo.currentmove = &soldier_move_pain_long1; @@ -1580,14 +1823,7 @@ void m_soldier_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int da if (self->health <= self->gib_health) { gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); - if (vrx_spawn_nonessential_ent(self->s.origin)) - { - for (n = 0; n < 2; n++) - ThrowGib(self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); - for (n = 0; n < 4; n++) - ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); - //ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); - } + vrx_throw_drone_gibs(self, damage); //self->deadflag = DEAD_DEAD; #ifdef OLD_NOLAG_STYLE M_Remove(self, false, false); @@ -1720,9 +1956,11 @@ void init_drone_soldier (edict_t *self) self->die = m_soldier_die; self->monsterinfo.stand = m_soldier_stand; + self->monsterinfo.walk = m_soldier_walk; self->monsterinfo.run = m_soldier_run; self->monsterinfo.dodge = m_soldier_dodge; self->monsterinfo.attack = m_soldier_attack; + self->monsterinfo.sight = m_soldier_sight; gi.linkentity (self); self->nextthink = level.time + FRAMETIME; diff --git a/src/entities/drone/drone_stalker.c b/src/entities/drone/drone_stalker.c index 073f80d1..154f2b8c 100644 --- a/src/entities/drone/drone_stalker.c +++ b/src/entities/drone/drone_stalker.c @@ -96,6 +96,83 @@ static void stalker_walk(edict_t *self) self->monsterinfo.currentmove = &stalker_move_walk; } +static void stalker_reactivate(edict_t *self); +static void stalker_false_death(edict_t *self); + +static mframe_t stalker_frames_reactivate[] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +static mmove_t stalker_move_false_death_end = { FRAME_reactive01, FRAME_reactive04, stalker_frames_reactivate, stalker_run }; + +static void stalker_reactivate(edict_t *self) +{ + self->monsterinfo.aiflags &= ~AI_STAND_GROUND; + self->monsterinfo.currentmove = &stalker_move_false_death_end; +} + +static void stalker_heal(edict_t *self) +{ + if (skill->value >= 3) + self->health += 3; + else if (skill->value >= 2) + self->health += 2; + else + self->health++; + + self->s.skinnum = self->health < (self->max_health / 2); + + if (self->health >= self->max_health) + { + self->health = self->max_health; + stalker_reactivate(self); + } +} + +static mframe_t stalker_frames_false_death[] = +{ + ai_move, 0, stalker_heal, + ai_move, 0, stalker_heal, + ai_move, 0, stalker_heal, + ai_move, 0, stalker_heal, + ai_move, 0, stalker_heal, + ai_move, 0, stalker_heal, + ai_move, 0, stalker_heal, + ai_move, 0, stalker_heal, + ai_move, 0, stalker_heal, + ai_move, 0, stalker_heal +}; +static mmove_t stalker_move_false_death = { FRAME_twitch01, FRAME_twitch10, stalker_frames_false_death, stalker_false_death }; + +static void stalker_false_death(edict_t *self) +{ + self->monsterinfo.currentmove = &stalker_move_false_death; +} + +static mframe_t stalker_frames_false_death_start[] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +static mmove_t stalker_move_false_death_start = { FRAME_death01, FRAME_death09, stalker_frames_false_death_start, stalker_false_death }; + +static void stalker_false_death_start(edict_t *self) +{ + stalker_set_floor(self); + self->monsterinfo.aiflags |= AI_STAND_GROUND; + self->monsterinfo.currentmove = &stalker_move_false_death_start; +} + mframe_t stalker_frames_run[] = { drone_ai_run, 24, NULL, @@ -570,13 +647,30 @@ static void stalker_pain(edict_t *self, edict_t *other, float kick, int damage) if (self->health < (self->max_health / 2)) self->s.skinnum |= 1; + if (self->monsterinfo.currentmove == &stalker_move_false_death_end || + self->monsterinfo.currentmove == &stalker_move_false_death_start) + return; + + if (self->monsterinfo.currentmove == &stalker_move_false_death) + { + stalker_reactivate(self); + return; + } + + if (self->health > 0 && self->health < (self->max_health / 4) && + self->groundentity && !stalker_on_ceiling(self) && random() < 0.30f) + { + stalker_false_death_start(self); + return; + } + if (level.time < self->pain_debounce_time) return; self->pain_debounce_time = level.time + 3.0; gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); - if (skill->value == 3) + if (invasion->value == 2) return; if (self->style == STALKER_CEILING_JUMPING && damage > 10) @@ -621,8 +715,6 @@ mmove_t stalker_move_death = { FRAME_death01, FRAME_death09, stalker_frames_deat static void stalker_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) { - int n; - M_Notify(self); stalker_set_floor(self); self->prethink = NULL; @@ -631,14 +723,7 @@ static void stalker_die(edict_t *self, edict_t *inflictor, edict_t *attacker, in if (self->health <= self->gib_health) { gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); - if (vrx_spawn_nonessential_ent(self->s.origin)) - { - ThrowGib(self, "models/monsters/stalker/gibs/bodya.md2", damage, GIB_ORGANIC); - ThrowGib(self, "models/monsters/stalker/gibs/bodyb.md2", damage, GIB_ORGANIC); - for (n = 0; n < 2; n++) - ThrowGib(self, "models/monsters/stalker/gibs/claw.md2", damage, GIB_ORGANIC); - ThrowHead(self, "models/monsters/stalker/gibs/head.md2", damage, GIB_ORGANIC); - } + vrx_throw_drone_gibs(self, damage); M_Remove(self, false, false); return; } diff --git a/src/entities/drone/drone_supertank.c b/src/entities/drone/drone_supertank.c index 57024df8..8c182ec4 100644 --- a/src/entities/drone/drone_supertank.c +++ b/src/entities/drone/drone_supertank.c @@ -14,6 +14,9 @@ qboolean visible (const edict_t *self, const edict_t *other); #define SUPERTANK_INVASION_BASE_HEALTH 5000 #define SUPERTANK_INVASION_ADDON_HEALTH 1000 +static int sound_pain1; +static int sound_pain2; +static int sound_pain3; static int sound_death; static int sound_search1; static int sound_search2; @@ -43,6 +46,14 @@ void TreadSound (edict_t *self) gi.sound (self, CHAN_VOICE, tread_sound, 1, ATTN_NORM, 0); } +void supertank_search (edict_t *self) +{ + if (random() > 0.5) + gi.sound (self, CHAN_VOICE, sound_search1, 1, ATTN_NORM, 0); + else + gi.sound (self, CHAN_VOICE, sound_search2, 1, ATTN_NORM, 0); +} + void supertankRocket (edict_t *self); void supertankMachineGun (edict_t *self); void supertankGrenade (edict_t *self); @@ -220,6 +231,67 @@ void supertank_run (edict_t *self) self->monsterinfo.currentmove = &supertank_move_run; } +mframe_t supertank_frames_pain3[] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t supertank_move_pain3 = { FRAME_pain3_9, FRAME_pain3_12, supertank_frames_pain3, supertank_run }; + +mframe_t supertank_frames_pain2[] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t supertank_move_pain2 = { FRAME_pain2_5, FRAME_pain2_8, supertank_frames_pain2, supertank_run }; + +mframe_t supertank_frames_pain1[] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +mmove_t supertank_move_pain1 = { FRAME_pain1_1, FRAME_pain1_4, supertank_frames_pain1, supertank_run }; + +void supertank_pain(edict_t *self, edict_t *other, float kick, int damage) +{ + (void)other; + (void)kick; + + if (level.time < self->pain_debounce_time) + return; + + if (damage <= 25 && random() < 0.2f) + return; + + if (self->s.frame >= FRAME_attak2_1 && self->s.frame <= FRAME_attak2_14) + return; + + if (damage <= 10) + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + else if (damage <= 25) + gi.sound(self, CHAN_VOICE, sound_pain3, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + + self->pain_debounce_time = level.time + 3.0f; + + if (invasion->value == 2) + return; + + if (damage <= 10) + self->monsterinfo.currentmove = &supertank_move_pain1; + else if (damage <= 25) + self->monsterinfo.currentmove = &supertank_move_pain2; + else + self->monsterinfo.currentmove = &supertank_move_pain3; +} + mframe_t supertank_frames_turn_right [] = { ai_move, 0, TreadSound, @@ -587,7 +659,6 @@ void supertank_attack(edict_t *self) void BossExplode (edict_t *self) { vec3_t org; - int n; self->think = BossExplode; VectorCopy (self->s.origin, org); @@ -628,12 +699,7 @@ void BossExplode (edict_t *self) break; case 8: self->s.sound = 0; - for (n= 0; n < 4; n++) - ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", 500, GIB_ORGANIC); - for (n= 0; n < 8; n++) - ThrowGib (self, "models/objects/gibs/sm_metal/tris.md2", 500, GIB_METALLIC); - ThrowGib (self, "models/objects/gibs/chest/tris.md2", 500, GIB_ORGANIC); - ThrowHead (self, "models/objects/gibs/gear/tris.md2", 500, GIB_METALLIC); + vrx_throw_drone_gibs(self, 500); self->deadflag = DEAD_DEAD; M_Remove(self, false, false); return; @@ -669,10 +735,7 @@ void supertank_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int da void supertank_sight (edict_t *self, edict_t *other) { - if (random() > 0.5) - gi.sound (self, CHAN_VOICE, sound_search1, 1, ATTN_NORM, 0); - else - gi.sound (self, CHAN_VOICE, sound_search2, 1, ATTN_NORM, 0); + supertank_search(self); } void init_drone_supertank (edict_t *self) @@ -681,6 +744,9 @@ void init_drone_supertank (edict_t *self) qboolean boss5 = supertank_is_boss5(self); sound_death = gi.soundindex ("bosstank/btkdeth1.wav"); + sound_pain1 = gi.soundindex ("bosstank/btkpain1.wav"); + sound_pain2 = gi.soundindex ("bosstank/btkpain2.wav"); + sound_pain3 = gi.soundindex ("bosstank/btkpain3.wav"); sound_search1 = gi.soundindex ("bosstank/btkunqv1.wav"); sound_search2 = gi.soundindex ("bosstank/btkunqv2.wav"); tread_sound = gi.soundindex ("bosstank/btkengn1.wav"); @@ -736,6 +802,10 @@ void init_drone_supertank (edict_t *self) self->monsterinfo.max_armor = self->monsterinfo.power_armor_power; self->die = supertank_die; + // vortex bosses have pain animations disabled. + if (janitor) + self->pain = supertank_pain; + self->monsterinfo.stand = supertank_stand; self->monsterinfo.walk = supertank_walk; self->monsterinfo.run = supertank_run; @@ -745,6 +815,7 @@ void init_drone_supertank (edict_t *self) self->monsterinfo.aiflags |= AI_NO_CIRCLE_STRAFE; self->monsterinfo.currentmove = &supertank_move_stand; self->monsterinfo.sight = supertank_sight; + self->monsterinfo.idle = supertank_search; self->nextthink = level.time + FRAMETIME; gi.linkentity (self); diff --git a/src/entities/drone/drone_tank.c b/src/entities/drone/drone_tank.c index 14c19de9..380908a6 100644 --- a/src/entities/drone/drone_tank.c +++ b/src/entities/drone/drone_tank.c @@ -19,6 +19,7 @@ void mytank_attack_chain (edict_t *self); static int sound_thud; static int sound_pain; +static int sound_pain2; static int sound_idle; static int sound_die; static int sound_step; @@ -700,6 +701,11 @@ void mytank_doattack_rocket (edict_t *self) self->monsterinfo.currentmove = &mytank_move_attack_fire_rocket; } +static qboolean mytank_should_melee(edict_t *self, float range) +{ + return self->groundentity && range <= 144.0f; +} + void mytank_melee (edict_t *self) { @@ -850,6 +856,13 @@ void commander_attack (edict_t *self) qboolean can_blast; qboolean can_rocket; + if (mytank_should_melee(self, range)) + { + self->monsterinfo.currentmove = &mytank_move_strike; + self->monsterinfo.attack_finished = level.time + 2.0; + return; + } + // short range attack if (range <= 128 && r <= 0.6) { @@ -862,15 +875,13 @@ void commander_attack (edict_t *self) { if (TeleportNearTarget(self, self->enemy, 16.0, true)) { - if (r <= 0.5) + range = entdist(self, self->enemy); + if (mytank_should_melee(self, range)) { self->monsterinfo.currentmove = &mytank_move_strike; self->monsterinfo.attack_finished = level.time + 0.5; return; } - - // recalculate enemy distance - range = entdist(self, self->enemy); } } @@ -909,12 +920,23 @@ void tank_attack(edict_t* self) { const float r = random(); const float range = entdist(self, self->enemy); - const qboolean can_blast = mytank_can_blaster(self); - const qboolean can_rocket = mytank_can_rocket(self); - const qboolean can_chain = mytank_can_chain(self); + qboolean can_blast; + qboolean can_rocket; + qboolean can_chain; //gi.dprintf("%d tank_attack()\n", level.framenum); + if (mytank_should_melee(self, range)) + { + self->monsterinfo.currentmove = &mytank_move_strike; + M_DelayNextAttack(self, 0, true); + return; + } + + can_blast = mytank_can_blaster(self); + can_rocket = mytank_can_rocket(self); + can_chain = mytank_can_chain(self); + // short range attack (60% strike, then 20% blaster, 80% rocket) if (range <= 128) { @@ -1065,7 +1087,7 @@ void tank_pain(edict_t* self, edict_t* other, float kick, int damage) !(is_idling || moving_without_enemy)) return; - gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); + gi.sound(self, CHAN_VOICE, self->mtype == M_COMMANDER ? sound_pain2 : sound_pain, 1, ATTN_NORM, 0); if (is_idling || moving_without_enemy) self->monsterinfo.currentmove = &tank_move_pain_long; @@ -1138,8 +1160,6 @@ mmove_t mytank_move_death = { FRAME_death101, FRAME_death132, mytank_frames_deat void mytank_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) { - int n; - //gi.dprintf("mytank_die called at %.1f\n", level.time);//DEBUG M_Notify(self); @@ -1156,15 +1176,7 @@ void mytank_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damag if (self->health <= self->gib_health) { gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); - if (vrx_spawn_nonessential_ent(self->s.origin)) - { - for (n = 0; n < 1; n++) - ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); - for (n = 0; n < 4; n++) - ThrowGib(self, "models/objects/gibs/sm_metal/tris.md2", damage, GIB_METALLIC); - ThrowGib(self, "models/objects/gibs/chest/tris.md2", damage, GIB_ORGANIC); - //ThrowHead (self, "models/objects/gibs/gear/tris.md2", damage, GIB_METALLIC); - } + vrx_throw_drone_gibs(self, damage); //self->deadflag = DEAD_DEAD; #ifdef OLD_NOLAG_STYLE M_Remove(self, false, false); @@ -1180,6 +1192,8 @@ void mytank_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damag if (self->deadflag == DEAD_DEAD) return; + vrx_drop_tank_death_arm(self, damage); + DroneList_Remove(self); // begin death sequence @@ -1219,6 +1233,7 @@ void init_drone_tank (edict_t *self) self->solid = SOLID_BBOX; sound_pain = gi.soundindex ("tank/tnkpain2.wav"); + sound_pain2 = gi.soundindex ("tank/pain.wav"); sound_thud = gi.soundindex ("tank/tnkdeth2.wav"); sound_idle = gi.soundindex ("tank/tnkidle1.wav"); sound_die = gi.soundindex ("tank/death.wav"); diff --git a/src/entities/drone/drone_widow.c b/src/entities/drone/drone_widow.c index f6c8dfcf..f094873b 100644 --- a/src/entities/drone/drone_widow.c +++ b/src/entities/drone/drone_widow.c @@ -27,6 +27,9 @@ black widow #define WIDOW_FRAME_spawn18 99 #define WIDOW_FRAME_pain01 100 #define WIDOW_FRAME_pain05 104 +#define WIDOW_FRAME_pain13 112 +#define WIDOW_FRAME_pain201 113 +#define WIDOW_FRAME_pain203 115 #define WIDOW_FRAME_death01 130 #define WIDOW_FRAME_death31 160 #define WIDOW_FRAME_kick01 161 @@ -38,6 +41,9 @@ black widow #define WIDOW_INVASION_SCALE 0.75f #define WIDOW_INVASION_HALF_WIDTH 30.0f #define WIDOW_INVASION_HEIGHT 108.0f +#define WIDOW_LEGS_MAX_FRAME 23 +#define WIDOW_LEGS_FRAME_TIME 0.1f +#define WIDOW_LEGS_WAIT_TIME 1.0f static int sound_pain1; static int sound_pain2; @@ -66,10 +72,16 @@ static void widow_melee_hit(edict_t *self); static void widow_spawn_effects(edict_t *self); static void widow_finish_spawn(edict_t *self); static void widow_explode(edict_t *self); -static void widow_dead(edict_t *self); +static void widow_spawn_out_start(edict_t *self); +static void widow_spawn_out_do(edict_t *self); static void widow_step1(edict_t *self); static void widow_step2(edict_t *self); +static vec3_t widow_beam_effects[] = { + { 12.58f, -43.71f, 68.88f }, + { 3.43f, 58.72f, 68.41f } +}; + static mframe_t widow_frames_stand[] = { drone_ai_stand, 0, NULL, @@ -180,15 +192,31 @@ static mframe_t widow_frames_spawn[] = }; static mmove_t widow_move_spawn = { WIDOW_FRAME_spawn01, WIDOW_FRAME_spawn18, widow_frames_spawn, widow_run }; -static mframe_t widow_frames_pain[] = +static mframe_t widow_frames_pain_heavy[] = { ai_move, 0, NULL, ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL +}; +static mmove_t widow_move_pain_heavy = { WIDOW_FRAME_pain01, WIDOW_FRAME_pain13, widow_frames_pain_heavy, widow_run }; + +static mframe_t widow_frames_pain_light[] = +{ ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL }; -static mmove_t widow_move_pain = { WIDOW_FRAME_pain01, WIDOW_FRAME_pain05, widow_frames_pain, widow_run }; +static mmove_t widow_move_pain_light = { WIDOW_FRAME_pain201, WIDOW_FRAME_pain203, widow_frames_pain_light, widow_run }; static mframe_t widow_frames_death[] = { @@ -201,7 +229,7 @@ static mframe_t widow_frames_death[] = ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, widow_explode, - ai_move, 0, NULL, + ai_move, 0, widow_spawn_out_start, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, @@ -222,7 +250,7 @@ static mframe_t widow_frames_death[] = ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, widow_explode, - ai_move, 0, widow_dead + ai_move, 0, widow_spawn_out_do }; static mmove_t widow_move_death = { WIDOW_FRAME_death01, WIDOW_FRAME_death31, widow_frames_death, NULL }; @@ -587,16 +615,27 @@ static void widow_pain(edict_t *self, edict_t *other, float kick, int damage) if (level.time < self->pain_debounce_time) return; - self->pain_debounce_time = level.time + 3.0f; - if (random() < 0.33f) + self->pain_debounce_time = level.time + 5.0f; + if (damage < 15) gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); - else if (random() < 0.5f) + else if (damage < 75) gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); else gi.sound(self, CHAN_VOICE, sound_pain3, 1, ATTN_NORM, 0); - if (skill->value != 3) - self->monsterinfo.currentmove = &widow_move_pain; + if (skill->value == 3) + return; + + if (damage >= 40 && damage < 200) + { + if (random() < (0.6f - (0.2f * skill->value))) + self->monsterinfo.currentmove = &widow_move_pain_light; + } + else if (damage >= 200) + { + if (random() < (0.75f - (0.1f * skill->value))) + self->monsterinfo.currentmove = &widow_move_pain_heavy; + } } static void widow_explode(edict_t *self) @@ -614,12 +653,178 @@ static void widow_explode(edict_t *self) gi.multicast(self->s.origin, MULTICAST_PVS); } -static void widow_dead(edict_t *self) +static void widow_project_source2(vec3_t origin, vec3_t offset, vec3_t forward, vec3_t right, vec3_t up, vec3_t result) +{ + result[0] = origin[0] + forward[0] * offset[0] + right[0] * offset[1] + up[0] * offset[2]; + result[1] = origin[1] + forward[1] * offset[0] + right[1] * offset[1] + up[1] * offset[2]; + result[2] = origin[2] + forward[2] * offset[0] + right[2] * offset[1] + up[2] * offset[2]; +} + +static void widow_temp_explosion(vec3_t point) +{ + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1); + gi.WritePosition(point); + gi.multicast(point, MULTICAST_ALL); +} + +static void widowlegs_throw_gib_at(edict_t *self, char *gibname, int damage, int type, vec3_t point) +{ + edict_t *gib; + float scale; + + if (nolag->value) + return; + + scale = self->s.scale ? self->s.scale : 1.0f; + gib = ThrowGibEx(self, gibname, damage, type, scale); + if (!gib) + return; + + VectorCopy(point, gib->s.origin); + VectorCopy(point, gib->s.old_origin); + gi.linkentity(gib); +} + +static void widowlegs_project(edict_t *self, vec3_t offset, vec3_t point) +{ + vec3_t forward, right, up; + + AngleVectors(self->s.angles, forward, right, up); + widow_project_source2(self->s.origin, offset, forward, right, up, point); +} + +static void widowlegs_think(edict_t *self) +{ + vec3_t offset; + vec3_t point; + + if (self->s.frame == 17) + { + VectorSet(offset, 11.77f, -7.24f, 23.31f); + widowlegs_project(self, offset, point); + widow_temp_explosion(point); + } + + if (self->s.frame < WIDOW_LEGS_MAX_FRAME) + { + self->s.frame++; + self->nextthink = level.time + WIDOW_LEGS_FRAME_TIME; + return; + } + + if (!self->wait) + self->wait = level.time + WIDOW_LEGS_WAIT_TIME; + + if ((level.time > self->wait - 0.5f) && !self->count) + { + self->count = 1; + + VectorSet(offset, 31.0f, -88.7f, 10.96f); + widowlegs_project(self, offset, point); + widow_temp_explosion(point); + + VectorSet(offset, -12.67f, -4.39f, 15.68f); + widowlegs_project(self, offset, point); + widow_temp_explosion(point); + } + + if (level.time > self->wait) + { + VectorSet(offset, -65.6f, -8.44f, 28.59f); + widowlegs_project(self, offset, point); + widow_temp_explosion(point); + widowlegs_throw_gib_at(self, "models/monsters/blackwidow/gib1/tris.md2", 90, GIB_METALLIC | GIB_UPRIGHT, point); + widowlegs_throw_gib_at(self, "models/monsters/blackwidow/gib2/tris.md2", 90, GIB_METALLIC | GIB_UPRIGHT, point); + + VectorSet(offset, -1.04f, -51.18f, 7.04f); + widowlegs_project(self, offset, point); + widow_temp_explosion(point); + widowlegs_throw_gib_at(self, "models/monsters/blackwidow/gib1/tris.md2", 90, GIB_METALLIC | GIB_UPRIGHT, point); + widowlegs_throw_gib_at(self, "models/monsters/blackwidow/gib2/tris.md2", 90, GIB_METALLIC | GIB_UPRIGHT, point); + widowlegs_throw_gib_at(self, "models/monsters/blackwidow/gib3/tris.md2", 90, GIB_METALLIC | GIB_UPRIGHT, point); + + G_FreeEdict(self); + return; + } + + self->nextthink = level.time + WIDOW_LEGS_FRAME_TIME; +} + +static void widowlegs_spawn(vec3_t startpos, vec3_t angles, float scale) { - for (int n = 0; n < 4; n++) - ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", 500, GIB_ORGANIC); - for (int n = 0; n < 6; n++) - ThrowGib(self, "models/objects/gibs/sm_metal/tris.md2", 500, GIB_METALLIC); + edict_t *ent; + + if (nolag->value) + return; + + ent = G_Spawn(); + VectorCopy(startpos, ent->s.origin); + VectorCopy(angles, ent->s.angles); + ent->solid = SOLID_NOT; + ent->s.renderfx = RF_IR_VISIBLE; + ent->movetype = MOVETYPE_NONE; + ent->classname = "widowlegs"; + ent->s.modelindex = gi.modelindex("models/monsters/legs/tris.md2"); + if (scale) + ent->s.scale = scale; + ent->think = widowlegs_think; + ent->nextthink = level.time + WIDOW_LEGS_FRAME_TIME; + gi.linkentity(ent); +} + +static void widow_spawn_out_start(edict_t *self) +{ + vec3_t startpoint; + vec3_t forward, right, up; + + AngleVectors(self->s.angles, forward, right, up); + + widow_project_source2(self->s.origin, widow_beam_effects[0], forward, right, up, startpoint); + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_WIDOWBEAMOUT); + gi.WriteShort(20001); + gi.WritePosition(startpoint); + gi.multicast(startpoint, MULTICAST_ALL); + + widow_project_source2(self->s.origin, widow_beam_effects[1], forward, right, up, startpoint); + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_WIDOWBEAMOUT); + gi.WriteShort(20002); + gi.WritePosition(startpoint); + gi.multicast(startpoint, MULTICAST_ALL); + + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/bwidowbeamout.wav"), 1, ATTN_NORM, 0); +} + +static void widow_spawn_out_do(edict_t *self) +{ + vec3_t startpoint; + vec3_t forward, right, up; + + AngleVectors(self->s.angles, forward, right, up); + + widow_project_source2(self->s.origin, widow_beam_effects[0], forward, right, up, startpoint); + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_WIDOWSPLASH); + gi.WritePosition(startpoint); + gi.multicast(startpoint, MULTICAST_ALL); + + widow_project_source2(self->s.origin, widow_beam_effects[1], forward, right, up, startpoint); + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_WIDOWSPLASH); + gi.WritePosition(startpoint); + gi.multicast(startpoint, MULTICAST_ALL); + + VectorCopy(self->s.origin, startpoint); + startpoint[2] += 36; + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_BOSSTPORT); + gi.WritePosition(startpoint); + gi.multicast(startpoint, MULTICAST_PHS); + + widowlegs_spawn(self->s.origin, self->s.angles, self->s.scale); + vrx_throw_drone_gibs(self, 500); M_Remove(self, false, false); } @@ -635,7 +840,7 @@ static void widow_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); self->deadflag = DEAD_DEAD; self->takedamage = DAMAGE_YES; - vrx_update_drone_death_skin(self); + vrx_update_drone_death_skin(self); self->monsterinfo.currentmove = &widow_move_death; } @@ -651,6 +856,7 @@ void init_drone_widow(edict_t *self) sound_step2 = gi.soundindex("widow/bwstep2.wav"); sound_hit = gi.soundindex("tank/tnkatck3.wav"); sound_spawn = gi.soundindex("medic_commander/monsterspawn1.wav"); + gi.soundindex("misc/bwidowbeamout.wav"); self->movetype = MOVETYPE_STEP; self->solid = SOLID_BBOX; @@ -666,6 +872,7 @@ void init_drone_widow(edict_t *self) gi.modelindex("models/items/spawngro3/tris.md2"); gi.modelindex("models/monsters/stalker/tris.md2"); + gi.modelindex("models/monsters/legs/tris.md2"); gi.modelindex("models/monsters/blackwidow/gib1/tris.md2"); gi.modelindex("models/monsters/blackwidow/gib2/tris.md2"); gi.modelindex("models/monsters/blackwidow/gib3/tris.md2"); diff --git a/src/entities/drone/drone_widow2.c b/src/entities/drone/drone_widow2.c index e40d32ba..c21ee31d 100644 --- a/src/entities/drone/drone_widow2.c +++ b/src/entities/drone/drone_widow2.c @@ -27,6 +27,10 @@ black widow 2 #define WIDOW2_FRAME_pain05 59 #define WIDOW2_FRAME_death01 60 #define WIDOW2_FRAME_death44 103 +#define WIDOW2_FRAME_dthsrh01 104 +#define WIDOW2_FRAME_dthsrh15 118 +#define WIDOW2_FRAME_dthsrh16 119 +#define WIDOW2_FRAME_dthsrh22 125 #define WIDOW2_SUMMON_COUNT 2 #define WIDOW2_SUMMON_COOLDOWN 10.0f @@ -84,8 +88,18 @@ static void widow2_pull_proboscis(edict_t *self); static void widow2_melee_hit(edict_t *self); static void widow2_spawn_effects(edict_t *self); static void widow2_finish_spawn(edict_t *self); -static void widow2_explode(edict_t *self); +static void widow2_explosion1(edict_t *self); +static void widow2_explosion2(edict_t *self); +static void widow2_explosion3(edict_t *self); +static void widow2_explosion4(edict_t *self); +static void widow2_explosion5(edict_t *self); +static void widow2_explosion6(edict_t *self); +static void widow2_explosion7(edict_t *self); +static void widow2_explosion_leg(edict_t *self); static void widow2_dead(edict_t *self); +static void widow2_start_searching(edict_t *self); +static void widow2_keep_searching(edict_t *self); +static void widow2_finaldeath(edict_t *self); static void widow2_step(edict_t *self); static mframe_t widow2_frames_stand[] = @@ -203,10 +217,10 @@ static mframe_t widow2_frames_death[] = { ai_move, 0, NULL, ai_move, 0, NULL, - ai_move, 0, widow2_explode, + ai_move, 0, widow2_explosion1, ai_move, 0, NULL, ai_move, 0, NULL, - ai_move, 0, widow2_explode, + ai_move, 0, widow2_explosion2, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, @@ -218,27 +232,27 @@ static mframe_t widow2_frames_death[] = ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, - ai_move, 0, widow2_explode, + ai_move, 0, widow2_explosion3, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, - ai_move, 0, widow2_explode, + ai_move, 0, widow2_explosion4, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, - ai_move, 0, widow2_explode, - ai_move, 0, widow2_explode, + ai_move, 0, widow2_explosion5, + ai_move, 0, widow2_explosion_leg, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, - ai_move, 0, widow2_explode, + ai_move, 0, widow2_explosion6, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, - ai_move, 0, widow2_explode, + ai_move, 0, widow2_explosion7, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, @@ -248,11 +262,75 @@ static mframe_t widow2_frames_death[] = }; static mmove_t widow2_move_death = { WIDOW2_FRAME_death01, WIDOW2_FRAME_death44, widow2_frames_death, NULL }; +static mframe_t widow2_frames_dead[] = +{ + ai_move, 0, widow2_start_searching, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, widow2_keep_searching +}; +static mmove_t widow2_move_dead = { WIDOW2_FRAME_dthsrh01, WIDOW2_FRAME_dthsrh15, widow2_frames_dead, NULL }; + +static mframe_t widow2_frames_really_dead[] = +{ + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, NULL, + ai_move, 0, widow2_finaldeath +}; +static mmove_t widow2_move_really_dead = { WIDOW2_FRAME_dthsrh16, WIDOW2_FRAME_dthsrh22, widow2_frames_really_dead, NULL }; + +static void widow2_start_searching(edict_t *self) +{ + self->count = 0; +} + +static void widow2_keep_searching(edict_t *self) +{ + if (self->count <= 2) + { + self->monsterinfo.currentmove = &widow2_move_dead; + self->s.frame = WIDOW2_FRAME_dthsrh01; + self->count++; + return; + } + + self->monsterinfo.currentmove = &widow2_move_really_dead; +} + +static void widow2_finaldeath(edict_t *self) +{ + // M_Remove ignores SOLID_NOT entities; the corpse is kept nonsolid during + // the final death animation so it does not block map paths. + self->solid = SOLID_BBOX; + M_Remove(self, false, false); +} + static void widow2_step(edict_t *self) { gi.sound(self, CHAN_BODY, sound_step, 1, ATTN_NORM, 0); } +static void widow2_search(edict_t *self) +{ + if (random() < 0.5f) + gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); +} + static void widow2_sight(edict_t *self, edict_t *other) { gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); @@ -733,33 +811,128 @@ static void widow2_pain(edict_t *self, edict_t *other, float kick, int damage) else gi.sound(self, CHAN_VOICE, sound_pain3, 1, ATTN_NORM, 0); - if (skill->value != 3) + if (invasion->value != 2) self->monsterinfo.currentmove = &widow2_move_pain; } -static void widow2_explode(edict_t *self) +static void widow2_project_source2(vec3_t origin, vec3_t offset, vec3_t forward, vec3_t right, vec3_t up, vec3_t result) { - vec3_t org; + result[0] = origin[0] + forward[0] * offset[0] + right[0] * offset[1] + up[0] * offset[2]; + result[1] = origin[1] + forward[1] * offset[0] + right[1] * offset[1] + up[1] * offset[2]; + result[2] = origin[2] + forward[2] * offset[0] + right[2] * offset[1] + up[2] * offset[2]; +} - VectorCopy(self->s.origin, org); - org[0] += crandom() * self->maxs[0]; - org[1] += crandom() * self->maxs[1]; - org[2] += random() * self->maxs[2]; +static void widow2_throw_gib_at(edict_t *self, char *gibname, int damage, int type, vec3_t point) +{ + edict_t *gib; + float scale; + + if (nolag->value || !vrx_spawn_nonessential_ent(point)) + return; + + scale = self->s.scale ? self->s.scale : 1.0f; + gib = ThrowGibEx(self, gibname, damage, type, scale); + if (!gib) + return; + + VectorCopy(point, gib->s.origin); + VectorCopy(point, gib->s.old_origin); + gi.linkentity(gib); +} + +static void widow2_explode_at(edict_t *self, vec3_t offset, int effect, vec3_t point) +{ + vec3_t forward, right, up; + + AngleVectors(self->s.angles, forward, right, up); + widow2_project_source2(self->s.origin, offset, forward, right, up, point); gi.WriteByte(svc_temp_entity); - gi.WriteByte(TE_EXPLOSION1_BIG); - gi.WritePosition(org); - gi.multicast(self->s.origin, MULTICAST_PVS); + gi.WriteByte(effect); + gi.WritePosition(point); + gi.multicast(self->s.origin, MULTICAST_ALL); } -static void widow2_dead(edict_t *self) +static void widow2_throw_loose_gibs(edict_t *self, vec3_t point) { - for (int n = 0; n < 5; n++) - ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", 500, GIB_ORGANIC); - for (int n = 0; n < 8; n++) - ThrowGib(self, "models/objects/gibs/sm_metal/tris.md2", 500, GIB_METALLIC); + widow2_throw_gib_at(self, "models/objects/gibs/sm_meat/tris.md2", 300, GIB_ORGANIC, point); + widow2_throw_gib_at(self, "models/objects/gibs/sm_metal/tris.md2", 100, GIB_METALLIC, point); + widow2_throw_gib_at(self, "models/objects/gibs/sm_metal/tris.md2", 300, GIB_METALLIC, point); + widow2_throw_gib_at(self, "models/objects/gibs/sm_metal/tris.md2", 300, GIB_METALLIC, point); +} - M_Remove(self, false, false); +static void widow2_explosion(edict_t *self, float x, float y, float z) +{ + vec3_t offset; + vec3_t point; + + VectorSet(offset, x, y, z); + widow2_explode_at(self, offset, TE_EXPLOSION1, point); + widow2_throw_loose_gibs(self, point); +} + +static void widow2_explosion1(edict_t *self) +{ + widow2_explosion(self, 23.74f, -37.67f, 76.96f); +} + +static void widow2_explosion2(edict_t *self) +{ + widow2_explosion(self, -20.49f, 36.92f, 73.52f); +} + +static void widow2_explosion3(edict_t *self) +{ + widow2_explosion(self, 2.11f, 0.05f, 92.20f); +} + +static void widow2_explosion4(edict_t *self) +{ + widow2_explosion(self, -28.04f, -35.57f, -77.56f); +} + +static void widow2_explosion5(edict_t *self) +{ + widow2_explosion(self, -20.11f, -1.11f, 40.76f); +} + +static void widow2_explosion6(edict_t *self) +{ + widow2_explosion(self, -20.11f, -1.11f, 40.76f); +} + +static void widow2_explosion7(edict_t *self) +{ + widow2_explosion(self, -20.11f, -1.11f, 40.76f); +} + +static void widow2_explosion_leg(edict_t *self) +{ + vec3_t offset; + vec3_t point; + + VectorSet(offset, -31.89f, -47.86f, 67.02f); + widow2_explode_at(self, offset, TE_EXPLOSION1_BIG, point); + widow2_throw_gib_at(self, "models/monsters/blackwidow2/gib2/tris.md2", 200, GIB_METALLIC | GIB_UPRIGHT, point); + widow2_throw_gib_at(self, "models/objects/gibs/sm_meat/tris.md2", 300, GIB_ORGANIC, point); + widow2_throw_gib_at(self, "models/objects/gibs/sm_metal/tris.md2", 100, GIB_METALLIC, point); + + VectorSet(offset, -44.9f, -82.14f, 54.72f); + widow2_explode_at(self, offset, TE_EXPLOSION1, point); + widow2_throw_gib_at(self, "models/monsters/blackwidow2/gib1/tris.md2", 300, GIB_METALLIC | GIB_UPRIGHT, point); + widow2_throw_gib_at(self, "models/objects/gibs/sm_meat/tris.md2", 300, GIB_ORGANIC, point); + widow2_throw_gib_at(self, "models/objects/gibs/sm_metal/tris.md2", 100, GIB_METALLIC, point); +} + +static void widow2_dead(edict_t *self) +{ + vrx_throw_drone_gibs(self, 500); + self->s.sound = 0; + self->count = 0; + self->solid = SOLID_NOT; + self->takedamage = DAMAGE_NO; + gi.linkentity(self); + self->monsterinfo.currentmove = &widow2_move_dead; } static void widow2_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) @@ -805,6 +978,7 @@ void init_drone_widow2(edict_t *self) gi.modelindex("models/items/spawngro3/tris.md2"); gi.modelindex("models/monsters/stalker/tris.md2"); + gi.modelindex("models/objects/gibs/sm_metal/tris.md2"); gi.modelindex("models/monsters/blackwidow2/gib1/tris.md2"); gi.modelindex("models/monsters/blackwidow2/gib2/tris.md2"); gi.modelindex("models/monsters/blackwidow2/gib3/tris.md2"); @@ -835,6 +1009,7 @@ void init_drone_widow2(edict_t *self) self->monsterinfo.attack = widow2_attack; self->monsterinfo.melee = widow2_melee; self->monsterinfo.sight = widow2_sight; + self->monsterinfo.idle = widow2_search; gi.linkentity(self); diff --git a/src/entities/g_misc.c b/src/entities/g_misc.c index 924152fe..30982373 100644 --- a/src/entities/g_misc.c +++ b/src/entities/g_misc.c @@ -70,6 +70,8 @@ void ClipGibVelocity(edict_t *ent) gibs ================= */ +#define GIB_SPAWNFLAG_MONSTER 1 + void gib_think(edict_t *self) { if ( ( level.framenum % qf2sf(1) ) == 0 ) @@ -85,20 +87,12 @@ void gib_think(edict_t *self) void gib_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) { - vec3_t normal_angles, right; - - if (!self->groundentity) - return; - - self->touch = NULL; - - if (plane) + if (plane && plane->normal[2] > 0.7f) { - gi.sound(self, CHAN_VOICE, gi.soundindex("misc/fhit3.wav"), 1, ATTN_NORM, 0); - - vectoangles(plane->normal, normal_angles); - AngleVectors(normal_angles, NULL, right, NULL); - vectoangles(right, self->s.angles); + if (!(self->spawnflags & GIB_SPAWNFLAG_MONSTER) && !(self->svflags & SVF_MONSTER)) + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/fhit3.wav"), 1, ATTN_NORM, 0); + self->s.angles[0] = max(-5.0f, min(self->s.angles[0], 5.0f)); + self->s.angles[2] = max(-5.0f, min(self->s.angles[2], 5.0f)); if (self->s.modelindex == sm_meat_index) { @@ -114,7 +108,7 @@ void gib_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, v G_FreeEdict(self); } -void ThrowGib(edict_t *self, char *gibname, int damage, int type) +edict_t *ThrowGibEx(edict_t *self, char *gibname, int damage, int type, float scale) { edict_t *gib; vec3_t vd = { 0, 0, 0 }; @@ -122,10 +116,8 @@ void ThrowGib(edict_t *self, char *gibname, int damage, int type) vec3_t size; float vscale; -#ifndef OLD_NOLAG_STYLE if (nolag->value) - return; -#endif + return NULL; gib = G_Spawn(); @@ -136,35 +128,74 @@ void ThrowGib(edict_t *self, char *gibname, int damage, int type) gib->s.origin[2] = origin[2] + crandom() * size[2]; gi.setmodel(gib, gibname); + gib->s.modelindex2 = 0; + if (scale != 1.0f) + gib->s.scale = scale; + VectorClear(gib->mins); + VectorClear(gib->maxs); gib->solid = SOLID_NOT; - gib->s.effects |= EF_GIB; + if (type & GIB_ACID) + gib->s.effects |= EF_GREENGIB; + else if (!(type & GIB_DEBRIS)) + gib->s.effects |= EF_GIB; gib->flags |= FL_NO_KNOCKBACK; + gib->classname = "gib"; + if (self->svflags & SVF_MONSTER) + gib->spawnflags |= GIB_SPAWNFLAG_MONSTER; gib->takedamage = DAMAGE_YES; gib->die = gib_die; + gib->s.frame = 0; + gib->s.sound = 0; + if (type & GIB_SKINNED) + gib->s.skinnum = self->s.skinnum; - if (type == GIB_ORGANIC) + if (!(type & GIB_METALLIC)) { gib->movetype = MOVETYPE_TOSS; - gib->touch = gib_touch; - vscale = 0.5; + vscale = (type & GIB_ACID) ? 3.0 : 0.5; } else { gib->movetype = MOVETYPE_BOUNCE; vscale = 1.0; } + if (type & GIB_UPRIGHT) + { + gib->touch = gib_touch; + gib->flags |= FL_ALWAYS_TOUCH; + } - VelocityForDamage(damage, vd); - VectorMA(self->velocity, vscale, vd, gib->velocity); - ClipGibVelocity(gib); + if (type & GIB_DEBRIS) + { + vd[0] = 100 * crandom(); + vd[1] = 100 * crandom(); + vd[2] = 100 + 100 * crandom(); + VectorMA(self->velocity, damage, vd, gib->velocity); + } + else + { + VelocityForDamage(damage, vd); + VectorMA(self->velocity, vscale, vd, gib->velocity); + ClipGibVelocity(gib); + } gib->avelocity[0] = random() * 600; gib->avelocity[1] = random() * 600; gib->avelocity[2] = random() * 600; + gib->s.angles[0] = random() * 359; + gib->s.angles[1] = random() * 359; + gib->s.angles[2] = random() * 359; gib->think = G_FreeEdict; gib->nextthink = level.time + GetRandom(2, 10); gi.linkentity(gib); + + return gib; +} + +void ThrowGib(edict_t *self, char *gibname, int damage, int type) +{ + ThrowGibEx(self, gibname, damage, type, 1.0f); } void ThrowHead(edict_t *self, char *gibname, int damage, int type) @@ -194,11 +225,13 @@ void ThrowHead2(edict_t *self, char *gibname, int damage, int type) self->s.effects &= ~EF_FLIES; self->s.sound = 0; self->flags |= FL_NO_KNOCKBACK; + if (self->svflags & SVF_MONSTER) + self->spawnflags |= GIB_SPAWNFLAG_MONSTER; // self->svflags &= ~SVF_MONSTER; self->takedamage = DAMAGE_YES; self->die = gib_die; - if (type == GIB_ORGANIC) + if (!(type & GIB_METALLIC)) { self->movetype = MOVETYPE_TOSS; self->touch = gib_touch; diff --git a/src/entities/g_spawn.c b/src/entities/g_spawn.c index cada7ac3..9fe3df64 100644 --- a/src/entities/g_spawn.c +++ b/src/entities/g_spawn.c @@ -1407,6 +1407,7 @@ void SP_worldspawn (edict_t *ent) gi.modelindex ("models/objects/gibs/chest/tris.md2"); skullindex = gi.modelindex ("models/objects/gibs/skull/tris.md2"); headindex = gi.modelindex ("models/objects/gibs/head2/tris.md2"); + vrx_precache_drone_gibs(); gi.modelindex("models/proj/beam/tris.md2"); // 3.7 heatbeam model //gi.modelindex("models/proj/lightning/tris.md2"); //3.9 lightning model used by hellspawn/holyshock diff --git a/src/g_local.h b/src/g_local.h index c7e07039..adb166cf 100644 --- a/src/g_local.h +++ b/src/g_local.h @@ -165,7 +165,8 @@ enum flags_t { FL_UNDEAD = 1 << 23, // entity cannot die (temporary death until resurrection) FL_PACKANIMAL = 1 << 24, // pack animal: use num_packanimals to track active quantity FL_RESPAWN = 1 << 25, // used for item respawning - FL_FLASHLIGHT = 1 << 26 + FL_FLASHLIGHT = 1 << 26, + FL_ALWAYS_TOUCH = 1 << 27 }; @@ -218,6 +219,11 @@ typedef enum { //gib types #define GIB_ORGANIC 0 #define GIB_METALLIC 1 +#define GIB_SKINNED 2 +#define GIB_UPRIGHT 4 +#define GIB_HEAD 8 +#define GIB_ACID 16 +#define GIB_DEBRIS 32 //monster ai flags #define AI_STAND_GROUND 0x00000001 @@ -1701,6 +1707,8 @@ void ThrowHead(edict_t *self, char *gibname, int damage, int type); void ThrowClientHead(edict_t *self, int damage); +edict_t *ThrowGibEx(edict_t *self, char *gibname, int damage, int type, float scale); + void ThrowGib(edict_t *self, char *gibname, int damage, int type); void BecomeExplosion1(edict_t *self); @@ -1719,6 +1727,9 @@ int vrx_get_alive_players(void); //Apple int vrx_GetMonsterCost(int mtype); //GHz int vrx_GetMonsterControlCost(int mtype); //GHz void vrx_remove_player_summonables(edict_t *self); //GHz +void vrx_precache_drone_gibs(void); +qboolean vrx_throw_drone_gibs(edict_t *self, int damage); +void vrx_drop_tank_death_arm(edict_t *self, int damage); // // g_ai.c diff --git a/src/quake2/g_phys.c b/src/quake2/g_phys.c index 25b96280..3e0b18d5 100644 --- a/src/quake2/g_phys.c +++ b/src/quake2/g_phys.c @@ -126,10 +126,10 @@ void SV_Impact (edict_t *e1, trace_t *trace) { edict_t *e2 = trace->ent; - if (e1->touch && e1->solid != SOLID_NOT) + if (e1->touch && (e1->solid != SOLID_NOT || (e1->flags & FL_ALWAYS_TOUCH))) e1->touch (e1, e2, &trace->plane, trace->surface); - if (e2->touch && e2->solid != SOLID_NOT) + if (e2->touch && (e2->solid != SOLID_NOT || (e2->flags & FL_ALWAYS_TOUCH))) e2->touch (e2, e1, NULL, NULL); } @@ -613,11 +613,15 @@ void SV_Physics_Toss (edict_t *ent) qboolean wasinwater; qboolean isinwater; vec3_t old_origin; + qboolean is_gib; + qboolean stop_on_ground; + float impact_speed; const qboolean forcethrough = false; // regular thinking SV_RunThink (ent); + is_gib = ent->classname && !strcmp(ent->classname, "gib"); // if not a team captain, so movement will be handled elsewhere if ( ent->flags & FL_TEAMSLAVE) @@ -675,6 +679,8 @@ void SV_Physics_Toss (edict_t *ent) if (trace.fraction < 1 && !forcethrough) { + impact_speed = fabs(DotProduct(ent->velocity, trace.plane.normal)); + // RAFAEL if (ent->movetype == MOVETYPE_WALLBOUNCE) backoff = 2.0; @@ -703,12 +709,32 @@ void SV_Physics_Toss (edict_t *ent) if (ent->movetype == MOVETYPE_WALLBOUNCE) vectoangles (ent->velocity, ent->s.angles); + if (is_gib && trace.plane.normal[2] <= 0.7f) + { + VectorScale(ent->avelocity, 0.75f, ent->avelocity); + if (impact_speed < 45.0f && VectorLength(ent->velocity) < 120.0f) + VectorCopy(vec3_origin, ent->avelocity); + } + // stop if on ground // RAFAEL //K03 Begin - if (trace.plane.normal[2] > 0.95 && ent->movetype != MOVETYPE_WALLBOUNCE && ent->movetype != MOVETYPE_SLIDE)//was 0.7 + if (trace.plane.normal[2] > (is_gib ? 0.7f : 0.95f) && ent->movetype != MOVETYPE_WALLBOUNCE && ent->movetype != MOVETYPE_SLIDE)//was 0.7 { - if (ent->velocity[2] < 30/*60*/ || ent->movetype != MOVETYPE_BOUNCE ) + if (is_gib) + VectorCopy(vec3_origin, ent->avelocity); + + if (is_gib) + { + if (ent->movetype == MOVETYPE_TOSS) + stop_on_ground = VectorLength(ent->velocity) < 60.0f; + else + stop_on_ground = fabs(DotProduct(ent->velocity, trace.plane.normal)) < 60.0f; + } + else + stop_on_ground = ent->velocity[2] < 30/*60*/ || ent->movetype != MOVETYPE_BOUNCE; + + if (stop_on_ground) //K03 End { // gi.dprintf("SV_Physics_Toss() stopped the entity\n"); @@ -717,6 +743,10 @@ void SV_Physics_Toss (edict_t *ent) VectorCopy (vec3_origin, ent->velocity); VectorCopy (vec3_origin, ent->avelocity); } + else if (is_gib && ent->movetype == MOVETYPE_TOSS) + { + VectorScale(ent->velocity, 0.75f, ent->velocity); + } } // if (ent->touch) From fabfaa3a81d1b0f99cc951161e2df38286337742 Mon Sep 17 00:00:00 2001 From: Enemy-123 Date: Sat, 16 May 2026 21:53:39 -0400 Subject: [PATCH 24/24] carrier/fixbot will keep a small distance from the ground liek remaster does, stalker fixed idle/sound animation, stuck prevention for flying monsters will turn around easier when ai_walking and touching a wall --- src/entities/drone/drone_boss2.c | 2 + src/entities/drone/drone_fixbot.c | 2 + src/entities/drone/drone_move.c | 86 +++++++++++++++++++++++++++--- src/entities/drone/drone_stalker.c | 37 +++++++++++-- 4 files changed, 116 insertions(+), 11 deletions(-) diff --git a/src/entities/drone/drone_boss2.c b/src/entities/drone/drone_boss2.c index f72bcf42..5bc52473 100644 --- a/src/entities/drone/drone_boss2.c +++ b/src/entities/drone/drone_boss2.c @@ -621,6 +621,8 @@ static void boss2_run(edict_t *self) static void boss2_walk(edict_t *self) { + if (!self->goalentity) + self->goalentity = world; self->monsterinfo.currentmove = &boss2_move_walk; } diff --git a/src/entities/drone/drone_fixbot.c b/src/entities/drone/drone_fixbot.c index fe505c6b..c448d753 100644 --- a/src/entities/drone/drone_fixbot.c +++ b/src/entities/drone/drone_fixbot.c @@ -1195,6 +1195,8 @@ static mmove_t fixbot_move_walk = { FIXBOT_FRAME_freeze_01, FIXBOT_FRAME_freeze_ static void fixbot_walk(edict_t *self) { + if (!self->goalentity) + self->goalentity = world; self->monsterinfo.currentmove = &fixbot_move_walk; } diff --git a/src/entities/drone/drone_move.c b/src/entities/drone/drone_move.c index 40289476..eb280ad8 100644 --- a/src/entities/drone/drone_move.c +++ b/src/entities/drone/drone_move.c @@ -336,11 +336,27 @@ qboolean M_Move (edict_t *ent, vec3_t move, qboolean relink) return true; } +static float M_FlyFloorClearance(const edict_t *ent) +{ + switch (ent->mtype) + { + case M_CARRIER: + return 104.0f; + case M_FIXBOT: + case M_FIXBOT_BOSS: + case M_BOSS2_SMALL: + return 40.0f; + default: + return 16.0f; + } +} + qboolean M_MoveVertical(edict_t* ent, vec3_t dest, vec3_t neworg) { float dz; // delta Z float idealZ; // ideal Z position, either a destination waypoint/node or a goal entity const float zSpeed = 8; // Z movement speed + const float floorClearance = M_FlyFloorClearance(ent); vec3_t end, goalpos; trace_t trace; //qboolean stopOnCollision=false; @@ -354,13 +370,26 @@ qboolean M_MoveVertical(edict_t* ent, vec3_t dest, vec3_t neworg) } else if (ent->goalentity) { - if (ent->goalentity == world) + if (ent->goalentity == world && floorClearance <= 16.0f) return true; - idealZ = ent->goalentity->s.origin[2] + 16; - VectorCopy(ent->goalentity->s.origin, goalpos); + if (ent->goalentity == world) + { + idealZ = ent->s.origin[2]; + VectorCopy(ent->s.origin, goalpos); + } + else + { + idealZ = ent->goalentity->s.origin[2] + 16; + VectorCopy(ent->goalentity->s.origin, goalpos); + } } - else + else if (floorClearance <= 16.0f) return true; // no goal, no destination - probably idle + else + { + idealZ = ent->s.origin[2]; + VectorCopy(ent->s.origin, goalpos); + } // if we can see our enemy, stay above him if (ent->enemy && visible(ent, ent->enemy) && entdist(ent, ent->enemy) <= 256) idealZ = ent->enemy->absmax[2] + 48; @@ -373,8 +402,8 @@ qboolean M_MoveVertical(edict_t* ent, vec3_t dest, vec3_t neworg) end[2] -= 8192; trace = gi.trace(ent->s.origin, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID); // always stay above the floor, regardless of goal/destination - if (idealZ < trace.endpos[2] + 16) - idealZ = trace.endpos[2] + 16; + if (idealZ < trace.endpos[2] + floorClearance) + idealZ = trace.endpos[2] + floorClearance; //gi.dprintf("floor @ %.0f position @ %.0f\n", trace.endpos[2], ent->s.origin[2]); dz = ent->s.origin[2] - idealZ; VectorCopy(ent->s.origin, end); @@ -2628,6 +2657,41 @@ void SV_MoveRandom(edict_t* actor, vec3_t dest, float dist) } } +static qboolean SV_FlyIdleWallDodge(edict_t *actor, float dist) +{ + vec3_t start, end, forward, wall_angles; + trace_t tr; + float yaw1, yaw2; + + if (!(actor->flags & FL_FLY)) + return false; + + AngleVectors(actor->s.angles, forward, NULL, NULL); + G_EntMidPoint(actor, start); + VectorMA(start, max(64.0f, fabsf(dist) * 4.0f), forward, end); + tr = gi.trace(start, NULL, NULL, end, actor, MASK_MONSTERSOLID); + if (tr.fraction == 1.0f || tr.startsolid || tr.allsolid) + return false; + + vectoangles(tr.plane.normal, wall_angles); + yaw1 = wall_angles[YAW] + 90.0f; + yaw2 = wall_angles[YAW] - 90.0f; + AngleCheck(&yaw1); + AngleCheck(&yaw2); + + actor->monsterinfo.lefty = 1 - actor->monsterinfo.lefty; + if (actor->monsterinfo.lefty) + { + if (SV_StepDirection(actor, NULL, yaw1, dist, true)) + return true; + return SV_StepDirection(actor, NULL, yaw2, dist, true); + } + + if (SV_StepDirection(actor, NULL, yaw2, dist, true)) + return true; + return SV_StepDirection(actor, NULL, yaw1, dist, true); +} + void SV_NewChaseDir2 (edict_t *self, vec3_t dest, float dist) { float minyaw, maxyaw, bestyaw, temp; @@ -2999,7 +3063,15 @@ void M_MoveToGoal (edict_t *ent, float dist) if (ent->inuse && (level.time > ent->monsterinfo.bump_delay)) { //gi.dprintf("tried course correction %s\n", ent->goalentity?"true":"false"); - SV_NewChaseDir (ent, goal, dist); + if ((ent->flags & FL_FLY) && (!goal || goal == world)) + { + if (!SV_FlyIdleWallDodge(ent, dist)) + SV_MoveRandom(ent, NULL, dist); + } + else + { + SV_NewChaseDir(ent, goal, dist); + } ent->monsterinfo.bump_delay = level.time + FRAMETIME*GetRandom(2, 5); return; } diff --git a/src/entities/drone/drone_stalker.c b/src/entities/drone/drone_stalker.c index 154f2b8c..738c2e6b 100644 --- a/src/entities/drone/drone_stalker.c +++ b/src/entities/drone/drone_stalker.c @@ -40,7 +40,7 @@ static void stalker_sight(edict_t *self, edict_t *other) gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); } -static void stalker_idle(edict_t *self) +static void stalker_idle_noise(edict_t *self) { gi.sound(self, CHAN_VOICE, sound_idle, 0.5, ATTN_IDLE, 0); } @@ -53,8 +53,26 @@ mframe_t stalker_frames_stand[] = drone_ai_stand, 0, NULL, drone_ai_stand, 0, NULL, drone_ai_stand, 0, NULL, - drone_ai_stand, 0, stalker_idle, + drone_ai_stand, 0, stalker_idle_noise, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL, drone_ai_stand, 0, NULL, + drone_ai_stand, 0, NULL +}; +mmove_t stalker_move_stand = { FRAME_idle01, FRAME_idle21, stalker_frames_stand, stalker_stand }; + +mframe_t stalker_frames_idle2[] = +{ drone_ai_stand, 0, NULL, drone_ai_stand, 0, NULL, drone_ai_stand, 0, NULL, @@ -69,11 +87,22 @@ mframe_t stalker_frames_stand[] = drone_ai_stand, 0, NULL, drone_ai_stand, 0, NULL }; -mmove_t stalker_move_stand = { FRAME_idle01, FRAME_idle21, stalker_frames_stand, NULL }; +mmove_t stalker_move_idle2 = { FRAME_idle201, FRAME_idle213, stalker_frames_idle2, stalker_stand }; + +static void stalker_idle(edict_t *self) +{ + if (random() < 0.35f) + self->monsterinfo.currentmove = &stalker_move_stand; + else + self->monsterinfo.currentmove = &stalker_move_idle2; +} static void stalker_stand(edict_t *self) { - self->monsterinfo.currentmove = &stalker_move_stand; + if (random() < 0.25f) + self->monsterinfo.currentmove = &stalker_move_stand; + else + self->monsterinfo.currentmove = &stalker_move_idle2; } mframe_t stalker_frames_walk[] =