game: Add ReRelease Shambler Monster

This commit is contained in:
Denis Pauk 2023-11-17 00:42:23 +02:00
parent 1151c91069
commit 9958db18d6
6 changed files with 860 additions and 5 deletions

View file

@ -921,6 +921,7 @@ GAME_OBJS_ = \
src/game/monster/soldier/soldier.o \
src/game/monster/stalker/stalker.o \
src/game/monster/supertank/supertank.o \
src/game/monster/shambler/shambler.o \
src/game/monster/tank/tank.o \
src/game/monster/turret/turret.o \
src/game/monster/widow/widow2.o \

View file

@ -30,6 +30,10 @@ State:
* q64/outpost: no known issies
* mguhub: loaded, transparent walls and broken logic for surface fall in next maps
Monsters:
* incorrect weapon effect for Shambler
* incorrect dead animation for Arachnoid
Goals (none of it finished):
* Single player support,
* BSPX DECOUPLEDLM light map support (base1),

View file

@ -162,6 +162,7 @@ void SP_turret_breach(edict_t *self);
void SP_turret_base(edict_t *self);
void SP_turret_driver(edict_t *self);
void SP_monster_shambler(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);
@ -352,6 +353,7 @@ static spawn_t spawns[] = {
{"monster_makron", SP_monster_makron},
{"monster_jorg", SP_monster_jorg},
{"monster_commander_body", SP_monster_commander_body},
{"monster_shambler", SP_monster_shambler},
{"monster_soldier_hypergun", SP_monster_soldier_hypergun},
{"monster_soldier_lasergun", SP_monster_soldier_lasergun},
{"monster_soldier_ripper", SP_monster_soldier_ripper},

View file

@ -305,7 +305,8 @@ mmove_t guardian_atk1_out =
guardian_run
};
void guardian_atk1_finish(edict_t *self)
void
guardian_atk1_finish(edict_t *self)
{
if (!self)
{
@ -315,12 +316,14 @@ void guardian_atk1_finish(edict_t *self)
self->monsterinfo.currentmove = &guardian_atk1_out;
}
void guardian_atk1_charge(edict_t *self)
void
guardian_atk1_charge(edict_t *self)
{
gi.sound(self, CHAN_WEAPON, sound_charge, 1.f, ATTN_NORM, 0.f);
}
void guardian_fire_blaster(edict_t *self)
void
guardian_fire_blaster(edict_t *self)
{
vec3_t forward, right, target;
vec3_t start;
@ -426,7 +429,8 @@ mmove_t guardian_move_atk2_out =
guardian_run
};
void guardian_atk2_out(edict_t *self)
void
guardian_atk2_out(edict_t *self)
{
if (!self)
{
@ -443,7 +447,8 @@ static vec3_t laser_positions[] = {
{ 112.0f, -62.f, 60.f }
};
void guardian_laser_fire(edict_t *self)
void
guardian_laser_fire(edict_t *self)
{
vec3_t forward, right, up;
vec3_t tempang, start;

View file

@ -0,0 +1,719 @@
/*
* Copyright (C) 1997-2001 Id Software, Inc.
* Copyright (c) ZeniMax Media Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
* =======================================================================
*
* SHAMBLER
*
* =======================================================================
*/
#include "../../header/local.h"
#include "shambler.h"
static int sound_pain;
static int sound_idle;
static int sound_die;
static int sound_sight;
static int sound_windup;
static int sound_melee1;
static int sound_melee2;
static int sound_smack;
static int sound_boom;
//
// misc
//
void
shambler_sight(edict_t* self, edict_t* other)
{
gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
}
static vec3_t lightning_left_hand[] = {
{ 44, 36, 25},
{ 10, 44, 57},
{ -1, 40, 70},
{ -10, 34, 75},
{ 7.4f, 24, 89 }
};
static vec3_t lightning_right_hand[] = {
{ 28, -38, 25},
{ 31, -7, 70},
{ 20, 0, 80},
{ 16, 1.2f, 81},
{ 27, -11, 83 }
};
void
shambler_lightning_update(edict_t *self)
{
edict_t *lightning;
vec3_t f, r;
if (self->s.frame >= FRAME_magic01 + sizeof(lightning_left_hand) / sizeof(*lightning_left_hand))
{
return;
}
lightning = G_Spawn();
lightning->s.modelindex = gi.modelindex("models/proj/lightning/tris.md2");
lightning->s.renderfx |= RF_BEAM;
lightning->owner = self;
AngleVectors(self->s.angles, f, r, NULL);
G_ProjectSource(self->s.origin, lightning_left_hand[self->s.frame - FRAME_magic01], f, r, lightning->s.origin);
G_ProjectSource(self->s.origin, lightning_right_hand[self->s.frame - FRAME_magic01], f, r, lightning->s.old_origin);
gi.linkentity(lightning);
}
void shambler_windup(edict_t* self)
{
gi.sound(self, CHAN_WEAPON, sound_windup, 1, ATTN_NORM, 0);
shambler_lightning_update(self);
}
void
shambler_idle(edict_t* self)
{
gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
}
void
shambler_maybe_idle(edict_t* self)
{
if (random() > 0.8)
{
gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
}
}
//
// stand
//
static mframe_t shambler_frames_stand[] =
{
{ai_stand, 0, NULL},
{ai_stand, 0, NULL},
{ai_stand, 0, NULL},
{ai_stand, 0, NULL},
{ai_stand, 0, NULL},
{ai_stand, 0, NULL},
{ai_stand, 0, NULL},
{ai_stand, 0, NULL},
{ai_stand, 0, NULL},
{ai_stand, 0, NULL},
{ai_stand, 0, NULL},
{ai_stand, 0, NULL},
{ai_stand, 0, NULL},
{ai_stand, 0, NULL},
{ai_stand, 0, NULL},
{ai_stand, 0, NULL},
{ai_stand, 0, NULL}
};
mmove_t shambler_move_stand =
{
FRAME_stand01,
FRAME_stand17,
shambler_frames_stand,
NULL
};
void
shambler_stand(edict_t* self)
{
if (!self)
{
return;
}
self->monsterinfo.currentmove = &shambler_move_stand;
}
//
// walk
//
void shambler_walk(edict_t* self);
static mframe_t shambler_frames_walk[] =
{
{ai_walk, 10, NULL}, /* FIXME: add footsteps? */
{ai_walk, 9, NULL},
{ai_walk, 9, NULL},
{ai_walk, 5, NULL},
{ai_walk, 6, NULL},
{ai_walk, 12, NULL},
{ai_walk, 8, NULL},
{ai_walk, 3, NULL},
{ai_walk, 13, NULL},
{ai_walk, 9, NULL},
{ai_walk, 7, shambler_maybe_idle},
{ai_walk, 5, NULL},
};
mmove_t shambler_move_walk =
{
FRAME_walk01,
FRAME_walk12,
shambler_frames_walk,
NULL
};
void
shambler_walk(edict_t* self)
{
if (!self)
{
return;
}
self->monsterinfo.currentmove = &shambler_move_walk;
}
//
// run
//
void shambler_run(edict_t* self);
static mframe_t shambler_frames_run[] =
{
{ai_run, 20, NULL}, /* FIXME: add footsteps? */
{ai_run, 24, NULL},
{ai_run, 20, NULL},
{ai_run, 20, NULL},
{ai_run, 24, NULL},
{ai_run, 20, shambler_maybe_idle},
};
mmove_t shambler_move_run =
{
FRAME_run01,
FRAME_run06,
shambler_frames_run,
NULL
};
void
shambler_run(edict_t* self)
{
if (!self)
{
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 = &shambler_move_stand;
return;
}
self->monsterinfo.currentmove = &shambler_move_run;
}
//
// pain
//
// FIXME: needs halved explosion damage
static mframe_t shambler_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 shambler_move_pain =
{
FRAME_pain01,
FRAME_pain06,
shambler_frames_pain,
shambler_run
};
void
shambler_pain(edict_t *self, edict_t *other /* unused */,
float kick /* unused */, int damage /* unused */)
{
if (!self)
{
return;
}
if (level.time < self->timestamp)
{
return;
}
self->timestamp = level.time + (1.0f / 1000.0f);
gi.sound(self, CHAN_AUTO, sound_pain, 1, ATTN_NORM, 0);
if (damage <= 30 && random() > 0.2f)
{
return;
}
/* If hard or nightmare, don't go into pain while attacking */
if (skill->value >= SKILL_HARDPLUS)
{
if ((self->s.frame >= FRAME_smash01) && (self->s.frame <= FRAME_smash12))
return;
if ((self->s.frame >= FRAME_swingl01) && (self->s.frame <= FRAME_swingl09))
return;
if ((self->s.frame >= FRAME_swingr01) && (self->s.frame <= FRAME_swingr09))
return;
}
if (skill->value == SKILL_HARDPLUS)
{
return; /* no pain anims in nightmare */
}
if (level.time < self->pain_debounce_time)
{
return;
}
self->pain_debounce_time = level.time + 2;
self->monsterinfo.currentmove = &shambler_move_pain;
}
/*
* attacks
*/
void
ShamblerSaveLoc(edict_t* self)
{
/* save for aiming the shot */
VectorCopy(self->enemy->s.origin, self->pos1);
self->pos1[2] += self->enemy->viewheight;
self->monsterinfo.nextframe = FRAME_magic09;
gi.sound(self, CHAN_WEAPON, sound_boom, 1, ATTN_NORM, 0);
shambler_lightning_update(self);
}
void
ShamblerCastLightning(edict_t* self)
{
vec3_t start;
vec3_t forward, right, target;
vec3_t offset = {0, 0, 60.f};
if (!self->enemy)
{
return;
}
AngleVectors(self->s.angles, forward, right, NULL);
G_ProjectSource(self->s.origin, offset, forward, right, start);
VectorCopy(self->enemy->s.origin, target);
target[2] += self->enemy->viewheight;
for (int i = 0; i < 3; i++)
{
target[i] += (randk() % 10) - 5.f;
}
/* calc direction to where we targeted */
VectorSubtract(target, start, forward);
VectorNormalize(forward);
/* TODO should be tesla effect */
monster_fire_railgun(self, start, forward, 2, 1000, MZ2_WIDOW_RAIL);
}
static mframe_t shambler_frames_magic[] = {
{ai_charge, 0, shambler_windup},
{ai_charge, 0, shambler_lightning_update},
{ai_charge, 0, shambler_lightning_update},
{ai_move, 0, shambler_lightning_update},
{ai_move, 0, shambler_lightning_update},
{ai_move, 0, ShamblerSaveLoc},
{ai_move, 0, NULL},
{ai_charge, 0, NULL},
{ai_move, 0, ShamblerCastLightning},
{ai_move, 0, ShamblerCastLightning},
{ai_move, 0, ShamblerCastLightning},
{ai_move, 0, NULL},
};
mmove_t shambler_attack_magic =
{
FRAME_magic01,
FRAME_magic12,
shambler_frames_magic,
shambler_run
};
void
shambler_attack(edict_t* self)
{
if (!self)
{
return;
}
self->monsterinfo.currentmove = &shambler_attack_magic;
}
//
// melee
//
void shambler_melee1(edict_t* self)
{
gi.sound(self, CHAN_WEAPON, sound_melee1, 1, ATTN_NORM, 0);
}
void shambler_melee2(edict_t* self)
{
gi.sound(self, CHAN_WEAPON, sound_melee2, 1, ATTN_NORM, 0);
}
void sham_swingl9(edict_t* self);
void sham_swingr9(edict_t* self);
void sham_smash10(edict_t* self)
{
if (!self->enemy)
return;
ai_charge(self, 0);
if (!CanDamage(self->enemy, self))
return;
vec3_t aim = { MELEE_DISTANCE, self->mins[0], -4 };
if (fire_hit(self, aim, (110 + (randk() % 10)), 120))
{
/* Slower attack */
gi.sound(self, CHAN_WEAPON, sound_smack, 1, ATTN_NORM, 0);
}
};
void ShamClaw(edict_t* self)
{
if (!self->enemy)
return;
ai_charge(self, 10);
if (!CanDamage(self->enemy, self))
return;
vec3_t aim = { MELEE_DISTANCE, self->mins[0], -4 };
if (fire_hit(self, aim, (70 + (randk() % 10)), 80))
{
/* Slower attack */
gi.sound(self, CHAN_WEAPON, sound_smack, 1, ATTN_NORM, 0);
}
};
static mframe_t shambler_frames_smash[] = {
{ai_charge, 2, shambler_melee1},
{ai_charge, 6},
{ai_charge, 6},
{ai_charge, 5},
{ai_charge, 4},
{ai_charge, 1},
{ai_charge, 0},
{ai_charge, 0},
{ai_charge, 0},
{ai_charge, 0, sham_smash10},
{ai_charge, 5},
{ai_charge, 4},
};
mmove_t shambler_attack_smash =
{
FRAME_smash01,
FRAME_smash12,
shambler_frames_smash,
shambler_run
};
static mframe_t shambler_frames_swingl[] = {
{ai_charge, 5, shambler_melee1},
{ai_charge, 3},
{ai_charge, 7},
{ai_charge, 3},
{ai_charge, 7},
{ai_charge, 9},
{ai_charge, 5, ShamClaw},
{ai_charge, 4},
{ai_charge, 8, sham_swingl9},
};
mmove_t shambler_attack_swingl =
{
FRAME_swingl01,
FRAME_swingl09,
shambler_frames_swingl,
shambler_run
};
static mframe_t shambler_frames_swingr[] = {
{ai_charge, 1, shambler_melee2},
{ai_charge, 8},
{ai_charge, 14},
{ai_charge, 7},
{ai_charge, 3},
{ai_charge, 6},
{ai_charge, 6, ShamClaw},
{ai_charge, 3},
{ai_charge, 8, sham_swingr9},
};
mmove_t shambler_attack_swingr =
{
FRAME_swingr01,
FRAME_swingr09,
shambler_frames_swingr,
shambler_run
};
void
sham_swingl9(edict_t* self)
{
if (!self)
{
return;
}
ai_charge(self, 8);
if (self->enemy)
{
float real_enemy_range;
real_enemy_range = realrange(self, self->enemy);
if ((randk() % 2) && self->enemy && real_enemy_range < MELEE_DISTANCE)
{
self->monsterinfo.currentmove = &shambler_attack_swingr;
}
}
}
void
sham_swingr9(edict_t* self)
{
if (!self)
{
return;
}
ai_charge(self, 1);
ai_charge(self, 10);
if (self->enemy)
{
float real_enemy_range;
real_enemy_range = realrange(self, self->enemy);
if ((randk() % 2) && self->enemy && real_enemy_range < MELEE_DISTANCE)
{
self->monsterinfo.currentmove = &shambler_attack_swingl;
}
}
}
void
shambler_melee(edict_t* self)
{
float chance = random();
if (chance > 0.6 || self->health == 600)
{
self->monsterinfo.currentmove = &shambler_attack_smash;
}
else if (chance > 0.3)
{
self->monsterinfo.currentmove = &shambler_attack_swingl;
}
else
{
self->monsterinfo.currentmove = &shambler_attack_swingr;
}
}
//
// death
//
void
shambler_dead(edict_t* self)
{
if (!self)
{
return;
}
VectorSet(self->mins, -16, -16, -24);
VectorSet(self->maxs, 16, 16, -8);
self->movetype = MOVETYPE_TOSS;
self->svflags |= SVF_DEADMONSTER;
self->nextthink = 0;
gi.linkentity(self);
}
static void
shambler_shrink(edict_t* self)
{
self->maxs[2] = 0;
self->svflags |= SVF_DEADMONSTER;
gi.linkentity(self);
}
static mframe_t shambler_frames_death[] = {
{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},
{ai_move, 0, NULL},
{ai_move, 0, NULL},
{ai_move, 0, NULL},
{ai_move, 0, NULL},
{ai_move, 0, NULL}, /* FIXME: thud? */
};
mmove_t shambler_move_death =
{
FRAME_death01,
FRAME_death11,
shambler_frames_death,
shambler_dead
};
void
shambler_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */,
int damage, vec3_t point /* unused */)
{
/* check for gib */
if (self->health <= self->gib_health)
{
gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
/* FIXME: better gibs for shambler, shambler head */
ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2",
damage, GIB_ORGANIC);
ThrowGib(self, "models/objects/gibs/chest/tris.md2",
damage, GIB_ORGANIC);
ThrowHead(self, "models/objects/gibs/head2/tris.md2",
damage, GIB_ORGANIC);
self->deadflag = true;
return;
}
if (self->deadflag)
return;
// regular death
gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0);
self->deadflag = true;
self->takedamage = true;
self->monsterinfo.currentmove = &shambler_move_death;
}
void SP_monster_shambler(edict_t* self)
{
if (!self)
{
return;
}
if (deathmatch->value)
{
G_FreeEdict(self);
return;
}
self->s.modelindex = gi.modelindex("models/monsters/shambler/tris.md2");
VectorSet (self->mins, -32, -32, -24);
VectorSet (self->maxs, 32, 32, 64);
self->movetype = MOVETYPE_STEP;
self->solid = SOLID_BBOX;
gi.modelindex("models/proj/lightning/tris.md2");
sound_pain = gi.soundindex("shambler/shurt2.wav");
sound_idle = gi.soundindex("shambler/sidle.wav");
sound_die = gi.soundindex("shambler/sdeath.wav");
sound_windup = gi.soundindex("shambler/sattck1.wav");
sound_melee1 = gi.soundindex("shambler/melee1.wav");
sound_melee2 = gi.soundindex("shambler/melee2.wav");
sound_sight = gi.soundindex("shambler/ssight.wav");
sound_smack = gi.soundindex("shambler/smack.wav");
sound_boom = gi.soundindex("shambler/sboom.wav");
self->health = 600;
self->gib_health = -60;
self->mass = 500;
self->pain = shambler_pain;
self->die = shambler_die;
self->monsterinfo.stand = shambler_stand;
self->monsterinfo.walk = shambler_walk;
self->monsterinfo.run = shambler_run;
self->monsterinfo.dodge = NULL;
self->monsterinfo.attack = shambler_attack;
self->monsterinfo.melee = shambler_melee;
self->monsterinfo.sight = shambler_sight;
self->monsterinfo.idle = shambler_idle;
self->monsterinfo.blocked = NULL;
gi.linkentity(self);
if (self->spawnflags & 1)
{
self->monsterinfo.aiflags |= AI_IGNORE_SHOTS;
}
self->monsterinfo.currentmove = &shambler_move_stand;
self->monsterinfo.scale = MODEL_SCALE;
walkmonster_start(self);
}

View file

@ -0,0 +1,124 @@
/*
* Copyright (C) 1997-2001 Id Software 30 Inc.
* Copyright (c) ZeniMax Media Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License 30 or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful 30 but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not 30 write to the Free Software
* Foundation 30 Inc. 30 59 Temple Place - Suite 330 30 Boston 30 MA
* 02111-1307 30 USA.
*
* =======================================================================
*
* Tank and Tank Commander animations.
*
* =======================================================================
*/
#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_walk01 18
#define FRAME_walk02 19
#define FRAME_walk03 20
#define FRAME_walk04 21
#define FRAME_walk05 22
#define FRAME_walk06 23
#define FRAME_walk07 24
#define FRAME_walk08 25
#define FRAME_walk09 26
#define FRAME_walk10 27
#define FRAME_walk11 28
#define FRAME_walk12 29
#define FRAME_run01 30
#define FRAME_run02 31
#define FRAME_run03 32
#define FRAME_run04 33
#define FRAME_run05 34
#define FRAME_run06 35
#define FRAME_smash01 36
#define FRAME_smash02 37
#define FRAME_smash03 38
#define FRAME_smash04 39
#define FRAME_smash05 40
#define FRAME_smash06 41
#define FRAME_smash07 42
#define FRAME_smash08 43
#define FRAME_smash09 44
#define FRAME_smash10 45
#define FRAME_smash11 46
#define FRAME_smash12 47
#define FRAME_swingr01 48
#define FRAME_swingr02 49
#define FRAME_swingr03 50
#define FRAME_swingr04 51
#define FRAME_swingr05 52
#define FRAME_swingr06 53
#define FRAME_swingr07 54
#define FRAME_swingr08 55
#define FRAME_swingr09 56
#define FRAME_swingl01 57
#define FRAME_swingl02 58
#define FRAME_swingl03 59
#define FRAME_swingl04 60
#define FRAME_swingl05 61
#define FRAME_swingl06 62
#define FRAME_swingl07 63
#define FRAME_swingl08 64
#define FRAME_swingl09 65
#define FRAME_magic01 66
#define FRAME_magic02 67
#define FRAME_magic03 68
#define FRAME_magic04 69
#define FRAME_magic05 70
#define FRAME_magic06 71
#define FRAME_magic07 72
#define FRAME_magic08 73
#define FRAME_magic09 74
#define FRAME_magic10 75
#define FRAME_magic11 76
#define FRAME_magic12 77
#define FRAME_pain01 78
#define FRAME_pain02 79
#define FRAME_pain03 80
#define FRAME_pain04 81
#define FRAME_pain05 82
#define FRAME_pain06 83
#define FRAME_death01 84
#define FRAME_death02 85
#define FRAME_death03 86
#define FRAME_death04 87
#define FRAME_death05 88
#define FRAME_death06 89
#define FRAME_death07 90
#define FRAME_death08 91
#define FRAME_death09 92
#define FRAME_death10 93
#define FRAME_death11 94
#define MODEL_SCALE 1.000000