djcev.com

//

Git Repos / fte_dogmode / qc / base_monster.qc

Last update to this file was on 2024-04-08 at 15:40.

Show base_monster.qc

//==============================================================================
// base_monster.qc -- base monster class, monster logic
//==============================================================================

//======================================================================
// constants
//======================================================================

const float NO_SIGHT_SOUND = 32; // spawnflags
const float PASSIVE_UNTIL_ATTACKED = 64;
const float PASSIVE_ALWAYS = 128;
const float I_AM_TURRET = 262144; // dumptruck_ds

const float AS_STRAIGHT = 1; // values for attack_state field
const float AS_SLIDING = 2;
const float AS_MELEE = 3;
const float AS_MISSILE = 4;
const float AS_TURRET = 5;

const float RANGE_MELEE = 0; // range values; see ai_range ()
const float RANGE_NEAR = 1;
const float RANGE_MID = 2;
const float RANGE_FAR = 3;

//======================================================================
// globals
//======================================================================

float enemy_vis; // was in fight.qc
float enemy_infront; // was in fight.qc
float enemy_range; // was in fight.qc
float enemy_yaw; // was in fight.qc
float movedist; // base_monster
float sight_entity_time; // was in ai.qc

entity sight_entity; // was in ai.qc

//======================================================================
// fields
//======================================================================

.float attack_elevation; // Preach's Z-aware nades -- CEV
.float attack_state;
.float berserk; // dumptruck_ds
.float drop_item; // key DropStuff
.float infight_mode;
.float keep_ammo; // dumptruck_ds
.float pain_threshold; // dumptruck_ds
.float pausetime;
.float search_time;
.float sight_trigger; // dumptruck_ds
.float spawn_angry; // dumptruck_ds
.float touch_time;

.entity infight_activator;
.entity movetarget;

.string tele_model; // Preach's new fields -- dumptruck_ds
.vector tele_mins; // TODO CEV
.vector tele_maxs;
.float tele_solid;
.float tele_movetype;

.void() think_stand; // the typical th_ monster functions
.void() think_walk;
.void() think_run;
.void() think_missile;
.void() think_melee;
.void() think_turret;

.float() checkattack; // per-monster CheckAttack -- CEV
.void() sightsound; // per-monster SightSound -- CEV

//======================================================================
// forward declarations
//======================================================================

void(float n) monster_update_total;

// base_monster // generic projectile firing
void(vector org, float projspeed) base_monster_fire_flak;
void(vector org, float direct, float splash, float elevation)
base_monster_fire_grenade;
void(vector org, vector dir, float off) base_monster_fire_hknightspell;
void(vector org, vector dir) base_monster_fire_laser;
void(vector org, vector dir, float projspeed) base_monster_fire_lavaball;
void() base_monster_fire_multigrenade;
void(vector org, vector dir, float direct, float splash, float projspeed)
base_monster_fire_rocket;
void(vector org, vector dir, vector spread) base_monster_fire_shotgun;
void(vector org, vector dir, float damage, float projspeed)
base_monster_fire_spike;
void(vector org, vector dir) base_monster_fire_voreball;
void() base_monster_fire_wizardspell;
void(vector offset, float elevation) base_monster_fire_zombiechunk;
float() ai_checkattack; // monster AI
float(entity targ) ai_infront;
float(entity targ) ai_range;
float(entity targ) ai_visible;
void() ai_hunttarget;
void() ai_foundtarget;
float() ai_findtarget;
void() ai_face;
float() ai_facing_ideal;
void(float dist) ai_forward;
void(float dist) ai_back;
void(float dist) ai_pain;
void(float dist) ai_painforward;
void(float dist) ai_walk;
void() ai_stand;
void() ai_turn;
void() ai_run_melee;
void() ai_run_missile;
void() ai_run_slide;
void(float dist) ai_run;
void(float dist) ai_charge;
void() ai_charge_side;
void() ai_melee;
void() ai_melee_side;
void(float normal) sub_attackfinished; // monster subs
void(void() nextfunc) sub_checkrefire;
float(.string fld) sub_fieldistargeted;
float() sub_istargeted;
void() sub_death_use;
void() sub_pain_use;
void() base_monster_think_teleport_go; // monster interaction
void() base_monster_touch;
void() base_monster_use_teledelay;
void() base_monster_use_angry;
void() base_monster_init_teleport_check;// monster initialization
float(entity e, void() start_fn) base_monster_init_teleport;
void(entity e) base_monster_init;
strip void() base_monster;

// base_flymonster
void() base_flymonster_think_start;
void(entity e) base_flymonster_init;
strip void() base_flymonster;

// base_swimmonster
void() base_swimmonster_think_start;
void(entity e) base_swimmonster_init;
strip void() base_swimmonster;

// base_walkmonster
void() base_walkmonster_think_start;
void(entity e) base_walkmonster_init;
strip void() base_walkmonster;

//------------------------------------------------------------------------------

//======================================================================
// .enemy
// Will be world if not currently angry at anyone.
//
// .movetarget
// The next path spot to walk toward. If .enemy, ignore .movetarget.
// When an enemy is killed, the monster will try to return to it's path.
//
// .huntt_ime
// Set to time + something when the player is in sight, but movement
// straight for him is blocked. This causes the monster to use wall
// following code for movement direction instead of sighting on the
// player.
//
// .ideal_yaw
// A yaw angle of the intended direction, which will be turned towards
// at up to 45 deg / state. If the enemy is in view and hunt_time is not
// active, this will be the exact line towards the enemy.
//
// .pausetime
// A monster will leave it's stand state and head towards it's .movetarget
// when time > .pausetime.
//
// walkmove(angle, speed) primitive is all or nothing
//======================================================================

//----------------------------------------------------------------------
// monster_update_total
//
// Call this function to safely update total_monsters when the game is
// in progress. It adds "n" to total_monsters, then notifies all clients
// of the change. -- iw
//----------------------------------------------------------------------
void(float n) monster_update_total =
{
total_monsters = total_monsters + n;

WriteByte (MSG_ALL, SVC_UPDATESTAT);
WriteByte (MSG_ALL, STAT_TOTALMONSTERS);
WriteLong (MSG_ALL, total_monsters);
};

//----------------------------------------------------------------------
// class base_monster: base_mapentity
// {
//==============================================================
// Generic Monster Weapon Firing
//==============================================================

//--------------------------------------------------------------
// BDW_OgreFireFlak
//--------------------------------------------------------------
void(vector org, float projspeed) base_monster_fire_flak =
{
local float flakcount = 8;
local vector dir, ang;

self.effects = self.effects | EF_MUZZLEFLASH;
sound (self, CHAN_WEAPON, "weapons/spike2.wav", 1, ATTN_NORM);

// make angles out of the current displacement vector...
ang = vectoangles (self.enemy.origin - self.origin);
// then get the required components...
makevectors (ang);

while (flakcount > 0)
{
// tighter spread...
dir = v_forward * 10 +
crandom() * v_right +
crandom() * v_up;
dir = normalize (dir);
// f*cking hack...is self a v_forward problem?
dir_z *= -1;
// SetSpeed -- CEV
dir *= min (projspeed * (self.proj_speed_mod ?
self.proj_speed_mod : 1), world_maxvelocity);

spawn_projectile_flak (self, org, dir);
flakcount -= 1;
}
};

//--------------------------------------------------------------
// Monster grenade function, incorporates PreachFireGrenade -- CEV
//--------------------------------------------------------------
void(vector org, float direct, float splash, float elevation)
base_monster_fire_grenade =
{
local vector missile_velocity = '0 0 0';

self.effects = self.effects | EF_MUZZLEFLASH;
sound (self, CHAN_WEAPON, "weapons/grenade.wav", 1, ATTN_NORM);

// set missile speed
if (elevation)
{
local vector ang = self.angles;
ang_x = -elevation;
makevectors (ang);
missile_velocity = v_forward * GRENADE_SPEED;
}
else
{
missile_velocity =
normalize (self.enemy.origin - self.origin);
missile_velocity *= 600;
missile_velocity_z = 200;
}

// TODO CEV pass direct and splash damage?
spawn_projectile_grenade (self, org, missile_velocity);
};

//--------------------------------------------------------------
// hknight_shot
//--------------------------------------------------------------
void(vector org, vector dir, float off) base_monster_fire_hknightspell =
{
// set missile speed
dir = normalize (v_forward);
dir_z = 0 - dir_z + (random() - 0.5) * 0.1;
dir *= min (HKNSPELL_SPEED * (self.proj_speed_mod ?
self.proj_speed_mod : 1), world_maxvelocity);

spawn_projectile_hknightspell (self, org, dir, off);
sound_attack (self, CHAN_WEAPON, "hknight/attack1.wav",
1, ATTN_NORM);
};

//--------------------------------------------------------------
// Enforcer lasers, was LaunchLaser
//--------------------------------------------------------------
void(vector org, vector dir) base_monster_fire_laser =
{
self.effects = self.effects | EF_MUZZLEFLASH;
sound_attack (self, CHAN_WEAPON, "enforcer/enfire.wav",
1, ATTN_NORM);

dir = normalize (dir);
// SetSpeed (newmis, vec, projspeed);
dir *= min (LASER_SPEED * (self.proj_speed_mod ?
self.proj_speed_mod : 1), world_maxvelocity);

spawn_projectile_laser (self, org, dir);
};

//--------------------------------------------------------------
void(vector org, vector dir, float projspeed)
base_monster_fire_lavaball =
{
dir = normalize (dir);
// SetSpeed
dir *= min (projspeed * (self.proj_speed_mod ?
self.proj_speed_mod : 1), world_maxvelocity);

spawn_projectile_lavaball (self, org, dir);
};

//--------------------------------------------------------------
// DOE multigrenades
//--------------------------------------------------------------
void() base_monster_fire_multigrenade =
{
local vector missile_velocity;

sound (self, CHAN_WEAPON, "weapons/grenade.wav", 1, ATTN_NORM);

// self.punchangle_x = -2;

// set missile speed
missile_velocity = normalize (self.enemy.origin - self.origin);
missile_velocity *= 600;
missile_velocity_z = 200;

spawn_projectile_multigrenade (self, self.origin + '0 0 16',
missile_velocity);
};

//--------------------------------------------------------------
void(vector org, vector dir, float direct, float splash,
float projspeed) base_monster_fire_rocket =
{
self.effects = self.effects | EF_MUZZLEFLASH;
sound_attack (self, CHAN_WEAPON, "weapons/sgun1.wav",
1, ATTN_NORM);

dir = normalize (dir);
// SetSpeed
dir *= min (projspeed * (self.proj_speed_mod ?
self.proj_speed_mod : 1), world_maxvelocity);


// TODO CEV pass direct & splash damage?
spawn_projectile_rocket (self, org, dir, projspeed);
};

//--------------------------------------------------------------
void(vector org, vector dir, vector spread) base_monster_fire_shotgun =
{
local vector vdir;
local float vspeed;

// 4 bullets instead of 6 -- CEV
for (float i = 0; i < 4; i++)
{
vspeed = crandom () * 10 + BULLET_SPEED;
vdir = dir + crandom() * spread_x * v_right +
crandom() * spread_y * v_up;

vdir *= min (vspeed, world_maxvelocity);

spawn_projectile_bullet (self, org, vdir);
}
};

//--------------------------------------------------------------
void(vector org, vector dir, float damage, float projspeed)
base_monster_fire_spike =
{
dir = normalize (dir);
// SetSpeed
dir *= min (projspeed * (self.proj_speed_mod ?
self.proj_speed_mod : 1), world_maxvelocity);

spawn_projectile_spike (self, org, dir, damage, projspeed);
};

//--------------------------------------------------------------
// ShalMissile
//--------------------------------------------------------------
void(vector org, vector dir) base_monster_fire_voreball =
{
local float basespeed;

self.effects = self.effects | EF_MUZZLEFLASH;
sound_misc (self, CHAN_WEAPON, "shalrath/attack2.wav",
1, ATTN_NORM);

dir = normalize (dir);
// SetSpeed
dir *= min (VOREBALL_SPEED * self.proj_speed_mod,
world_maxvelocity);

if (skill == 3)
basespeed = 350 * self.proj_speed_mod;
else
basespeed = 250 * self.proj_speed_mod;

spawn_projectile_voreball (self, org, dir, basespeed);
};

//--------------------------------------------------------------
// Wiz_StartFast
//--------------------------------------------------------------
void() base_monster_fire_wizardspell =
{
self.v_angle = self.angles;
makevectors (self.angles);

// two nextthinks were present in Wiz_StartFast, I'm leaving
// the last of them -- CEV
// nextthink: time + 0.6,
spawn_projectile_wizardspell (self,
self.origin + '0 0 30' + v_forward * 14 + v_right * 14,
v_right, time + 0.8);

// two nextthinks were present in Wiz_StartFast, I'm leaving
// the last of them -- CEV
// nextthink: time + 1,
spawn_projectile_wizardspell (self,
self.origin + '0 0 30' + v_forward * 14 + v_right * -14,
'0 0 0' - v_right, time + 0.3);
};

//--------------------------------------------------------------
// Throw a zombie gib; incorporates PreachFireGrenade -- CEV
//--------------------------------------------------------------
void(vector offset, float elevation) base_monster_fire_zombiechunk =
{
local vector vel = '0 0 0';

sound_attack (self, CHAN_WEAPON, "zombie/z_shot1.wav",
1, ATTN_NORM);

// set missile speed
if (elevation)
{
// PreachFireZombie -- for Z-Aware Zombies
local vector ang = self.angles;
ang_x = -elevation;
makevectors (ang);
vel = v_forward * ZCHUNK_SPEED;
}
else
{
// calc org
offset = offset_x * v_forward +
offset_y * v_right + (offset_z - 24) * v_up;
local vector org = self.origin + offset;

makevectors (self.angles);
vel = normalize (self.enemy.origin - org);
vel = vel * ZCHUNK_SPEED;
vel_z = 200;
}

spawn_projectile_zombiechunk (self, self.origin + offset, vel);
};

//==============================================================
// Monster AI
//==============================================================

//--------------------------------------------------------------
// ai_checkattack - MonsterCheckAttack & CheckAttack
// override in monster code if other behavior is desired
//
// The player is in view, so decide to move or launch an attack
// Returns FALSE if movement should continue
//--------------------------------------------------------------
float() ai_checkattack =
{
// Drake devkit -- dumptruck_ds
if (cutscene)
if (self.enemy.classtype == CT_INFO_MOVIE_CAMERA)
// Don't attack the camera (player)!
return FALSE;

if (!enemy_vis)
return FALSE;

if (self.checkattack)
return self.checkattack ();

local vector spot1 = '0 0 0';
local vector spot2 = '0 0 0';
local entity targ;
local float chance;

// TODO CEV: fix these class checks, generalize
// call to knight_attack

// dumptruck_ds
if (self.spawnflags & I_AM_TURRET)
{
// dprint ("CheckAttack...\n");
if (self.classtype == CT_MONSTER_ENFORCER)
{
if (self.style == 4)
{
// lightning can only go so far
if (vlen(spot1 - spot2) > 900)
return FALSE;
}
self.attack_state = AS_MISSILE;
}
else if (self.classtype == CT_MONSTER_DEATHKNIGHT)
{
if (self.style == 1)
{
// lightning can only go so far
if (vlen(spot1 - spot2) > 900)
return FALSE;
}
// self.attack_state = AS_MISSILE;
self.think_turret ();
}
else if (self.classtype == CT_MONSTER_VORE)
{
self.think_turret ();
}
else if (self.classtype == CT_MONSTER_ZOMBIE)
{
// dprint ("CheckAttack...\n");
// zombie_turret_missile ();
self.think_turret ();
}
sub_attackfinished (2 * random());
return TRUE;
}

targ = self.enemy;

// see if any entities are in the way of the shot
spot1 = self.origin + self.view_ofs;
spot2 = targ.origin + targ.view_ofs;

traceline (spot1, spot2, FALSE, self);

if ((self.spawnflags & I_AM_TURRET) && (trace_ent != targ))
{
// dprint ("trace_ent...\n");
self.attack_state = AS_TURRET;
return FALSE;
}

if (trace_inopen && trace_inwater)
// sight line crossed contents
return FALSE;

if (trace_ent != targ)
// don't have a clear shot
return FALSE;

if (enemy_range == RANGE_MELEE)
{
// melee attack
if (self.think_melee)
{
// TODO CEV
if (self.classtype == CT_MONSTER_KNIGHT)
monster_knight_attack ();
else
self.think_melee ();
return TRUE;
}
}

// missile attack
if (!self.think_missile)
return FALSE;

if (time < self.attack_finished)
return FALSE;

if (enemy_range == RANGE_FAR)
return FALSE;

if (enemy_range == RANGE_MELEE)
{
chance = 0.9;
self.attack_finished = 0;
}
else if (enemy_range == RANGE_NEAR)
{
if (self.think_melee)
chance = 0.2;
else
chance = 0.4;
}
else if (enemy_range == RANGE_MID)
{
if (self.think_melee)
chance = 0.05;
else
chance = 0.1;
}
else
{
chance = 0;
}

if (random() < chance)
{
self.think_missile ();
sub_attackfinished (2 * random());
return TRUE;
}

return FALSE;
};

//--------------------------------------------------------------
// infront -- return TRUE if targ is in front (in sight) of self
//--------------------------------------------------------------
float(entity targ) ai_infront =
{
local vector vec;

makevectors (self.angles);
vec = normalize (targ.origin - self.origin);

if ((vec * v_forward) > 0.3)
return TRUE;

return FALSE;
};

//--------------------------------------------------------------
// range
//
// returns the range catagorization of an entity reletive to self
// 0 melee range, will become hostile even if back is turned
// 1 visibility and infront, or visibility and show hostile
// 2 infront and show hostile
// 3 only triggered by damage
//--------------------------------------------------------------
float(entity targ) ai_range =
{
local vector spot1 = self.origin + self.view_ofs;
local vector spot2 = targ.origin + targ.view_ofs;
local float r = vlen (spot1 - spot2);

if (r < 120)
return RANGE_MELEE;
if (r < 500)
return RANGE_NEAR;
if (r < 1000)
return RANGE_MID;
return RANGE_FAR;
};

//--------------------------------------------------------------
// visible - returns TRUE if targ is visible to self, even
// if not infront ()
//--------------------------------------------------------------
float(entity targ) ai_visible =
{
local vector spot1, spot2;

spot1 = self.origin + self.view_ofs;
spot2 = targ.origin + targ.view_ofs;
// see through other monsters
traceline (spot1, spot2, TRUE, self);

if (trace_inopen && trace_inwater)
// sight line crossed contents
return FALSE;

if (trace_fraction == 1)
return TRUE;

return FALSE;
};

//--------------------------------------------------------------
// HuntTarget -- was in ai.qc -- CEV
//--------------------------------------------------------------
void() ai_hunttarget =
{
self.goalentity = self.enemy;
self.ideal_yaw = vectoyaw (self.enemy.origin - self.origin);
self.think = self.think_run;
self.nextthink = time + 0.1;
// wait a while before first attack
sub_attackfinished (1);
};

//--------------------------------------------------------------
// FoundTarget -- was in ai.qc -- CEV
//--------------------------------------------------------------
void() ai_foundtarget =
{
if (self.enemy.classtype == CT_PLAYER)
{
// let other monsters see this monster for a while
sight_entity = self;
sight_entity_time = time;
}

// wake up other monsters
self.show_hostile = time + 1;

if !(self.spawnflags & NO_SIGHT_SOUND ||
self.spawnflags & PASSIVE_ALWAYS ||
self.spawnflags & PASSIVE_UNTIL_ATTACKED)
{
if (self.sightsound)
self.sightsound ();
}

ai_hunttarget ();

// thanks RennyC -- dumptruck_ds
if (self.sight_trigger == 1)
{
activator = self.enemy;
sub_useandforgettargets ();
}
};

//--------------------------------------------------------------
// FindTarget
//
// Self is currently not attacking anything, so try to find a target
//
// Returns TRUE if an enemy was sighted
//
// When a player fires a missile, the point of impact becomes a
// fakeplayer so that monsters that see the impact will respond as
// if they had seen the player.
//
// To avoid spending too much time, only a single client (or
// fakeclient) is checked each frame. This means multi player games
// will have slightly slower noticing monsters.
//--------------------------------------------------------------
float() ai_findtarget =
{
local entity client;
local float r;

// if the first spawnflag bit is set, the monster will only
// wake up on really seeing the player, not another monster
// getting angry

// spawnflags & 3 is a big hack, because zombie crucified
// used the first spawn flag prior to the ambush flag, and
// I forgot about it, so the second spawn flag works as well
if (sight_entity_time >= time - 0.1 && !(self.spawnflags & 3))
{
client = sight_entity;
if (client.enemy == self.enemy)
return TRUE;
}
else
{
client = checkclient ();
if (!client)
// current check entity isn't in PVS
return FALSE;
}

if ((self.spawnflags & PASSIVE_UNTIL_ATTACKED) ||
(self.spawnflags & PASSIVE_ALWAYS))
{
return FALSE;
}

// from Copper -- dumptruck_ds
if (client.flags & FL_NOTARGET ||
client.movetype == MOVETYPE_NOCLIP)
{
return FALSE;
}

if (client.health <= 0)
return FALSE;

if (client.classtype != CT_PLAYER)
if (client.enemy.health <= 0)
return FALSE;

if (client == self.enemy)
return FALSE;

if (client.flags & FL_NOTARGET)
return FALSE;
if (client.items & IT_INVISIBILITY)
return FALSE;

r = ai_range (client);
if (r == RANGE_FAR)
return FALSE;

if (!ai_visible(client))
return FALSE;

if (r == RANGE_NEAR)
{
if (client.show_hostile < time && !ai_infront
(client))
return FALSE;
}
else if (r == RANGE_MID)
{
/*
if (client.show_hostile < time || !ai_infront (client))
*/
if (!ai_infront(client))
return FALSE;
}

// got one
self.enemy = client;
if (self.enemy.classtype != CT_PLAYER)
{
self.enemy = self.enemy.enemy;
if (self.enemy.classtype != CT_PLAYER)
{
self.enemy = world;
return FALSE;
}
}

ai_foundtarget ();

return TRUE;
};

//--------------------------------------------------------------
// ai_face Stay facing the enemy
//--------------------------------------------------------------
void() ai_face =
{
self.ideal_yaw = vectoyaw (self.enemy.origin - self.origin);
changeyaw ();
};

//--------------------------------------------------------------
// FacingIdeal
//--------------------------------------------------------------
float() ai_facing_ideal =
{
local float delta;

delta = anglemod (self.angles_y - self.ideal_yaw);
if (delta > 45 && delta < 315)
return FALSE;
return TRUE;
};

//--------------------------------------------------------------
void(float dist) ai_forward =
{
// dprint ("ai_forward\n");
walkmove (self.angles_y, dist);
};

//--------------------------------------------------------------
void(float dist) ai_back =
{
// dprint ("ai_back\n");
walkmove ((self.angles_y + 180), dist);
};

//--------------------------------------------------------------
// ai_pain -- stagger back a bit
//--------------------------------------------------------------
void(float dist) ai_pain =
{
ai_back (dist);
/*
local float away;

away = anglemod (vectoyaw (self.origin - self.enemy.origin)
+ 180 * (random() - 0.5));

walkmove (away, dist);
*/
};

//--------------------------------------------------------------
// ai_painforward -- stagger back a bit
//--------------------------------------------------------------
void(float dist) ai_painforward =
{
// dprint ("ai_painforward\n");
walkmove (self.ideal_yaw, dist);
};

//--------------------------------------------------------------
// ai_walk -- The monster is walking its beat
//--------------------------------------------------------------
void(float dist) ai_walk =
{
movedist = dist;

if (self.classname == "monster_dragon")
{
movetogoal (dist);
return;
}

// check for noticing a player
if (ai_findtarget())
return;

movetogoal (dist);
};

//--------------------------------------------------------------
// ai_stand -- The monster is staying in one place for a while,
// with slight angle turns
//--------------------------------------------------------------
void() ai_stand =
{
if (ai_findtarget())
return;

if (time > self.pausetime)
{
self.think_walk ();
return;
}
// change angle slightly -- TODO CEV this comment was
// in PD3 source but had no associated code
};

//--------------------------------------------------------------
// ai_turn -- don't move, but turn towards ideal_yaw
//--------------------------------------------------------------
void() ai_turn =
{
if (ai_findtarget())
return;

changeyaw ();
};

//--------------------------------------------------------------
// ai_run_melee
// Turn and close until within an angle to launch a melee attack
//--------------------------------------------------------------
void() ai_run_melee =
{
self.ideal_yaw = enemy_yaw;
changeyaw ();

if (ai_facing_ideal())
{
self.think_melee ();
self.attack_state = AS_STRAIGHT;
}
};

//--------------------------------------------------------------
// ai_run_missile
// Turn in place until within an angle to launch a missile attack
//--------------------------------------------------------------
void() ai_run_missile =
{
self.ideal_yaw = enemy_yaw;
// dprint ("ai_run_missile GO\n");
changeyaw ();
if (ai_facing_ideal())
{
if (self.spawnflags & I_AM_TURRET)
{
self.think_turret ();
// dprint ("th_turret...\n");
}
else
{
self.think_missile ();
// dprint ("th_missile\n");
}
self.attack_state = AS_STRAIGHT;
}
};

//--------------------------------------------------------------
// ai_run_slide
// Strafe sideways, but stay at approximately the same range
//--------------------------------------------------------------
void() ai_run_slide =
{
local float ofs;

self.ideal_yaw = enemy_yaw;
changeyaw ();
if (self.lefty)
ofs = 90;
else
ofs = -90;

if (walkmove(self.ideal_yaw + ofs, movedist))
return;

self.lefty = 1 - self.lefty;

walkmove (self.ideal_yaw - ofs, movedist);
};

//--------------------------------------------------------------
// ai_run -- The monster has an enemy it is trying to kill
//--------------------------------------------------------------
void(float dist) ai_run =
{
// dprint ("ai_run\n");
movedist = dist;
// see if the enemy is dead
if ((self.enemy.health <= 0) ||
(self.spawnflags & PASSIVE_ALWAYS))
{
self.enemy = world;
// FIXME: look all around for other targets
if (self.oldenemy.health > 0)
{
self.enemy = self.oldenemy;
ai_hunttarget ();
}
else
{
if (self.spawnflags & I_AM_TURRET)
self.think_stand ();
else if (self.movetarget)
self.think_walk ();
else
self.think_stand ();
return;
}
}

// wake up other monsters
self.show_hostile = time + 1;

// check knowledge of enemy
enemy_vis = ai_visible (self.enemy);
if (enemy_vis)
self.search_time = time + 5;

// look for other coop players
if (coop && self.search_time < time)
{
if (ai_findtarget())
return;
}

enemy_infront = ai_infront (self.enemy);
enemy_range = ai_range (self.enemy);
enemy_yaw = vectoyaw (self.enemy.origin - self.origin);

if (self.spawnflags & I_AM_TURRET)
{
ai_face ();
}

// if ((self.attack_state == AS_MISSILE) ||
// !(self.spawnflags & I_AM_TURRET))
if (self.attack_state == AS_MISSILE)
{
// dprint ("ai_run_missile... from ai_run\n");
ai_run_missile ();
return;
}
// if ((self.attack_state == AS_MELEE) ||
// !(self.spawnflags & I_AM_TURRET))
if (self.attack_state == AS_MELEE)
{
// dprint ("ai_run_melee\n");
ai_run_melee ();
return;
}

if (ai_checkattack())
// beginning an attack
return;

// if ((self.attack_state == AS_SLIDING) ||
// !(self.spawnflags & I_AM_TURRET))
if (self.attack_state == AS_SLIDING)
{
ai_run_slide ();
return;
}
// part of monster face from TheSolipsist
// urged to change positions
if (time < self.t_length)
{
// if orientation is forced
changeyaw ();

if (walkmove(self.ideal_yaw, dist))
return;

// dodge left
self.ideal_yaw += 30;
changeyaw ();

if (walkmove(self.ideal_yaw, dist))
return;

// dodge right
self.ideal_yaw -= 60;
changeyaw ();
changeyaw ();

if (walkmove(self.ideal_yaw, dist))
return;

// give up
self.ideal_yaw += 30;
changeyaw();
// lose patience
self.touch_time = self.touch_time - 0.1;

return;
}

if !(self.spawnflags & I_AM_TURRET)
// keeps monster from moving to player - dumptruck_ds
// head straight in -- done in C code...
movetogoal (dist);
};

//--------------------------------------------------------------
// ai_charge -- The monster is in a melee attack, so get as close
// as possible to .enemy
//--------------------------------------------------------------
void(float dist) ai_charge =
{
ai_face ();
// done in C code...
movetogoal (dist);
};

//--------------------------------------------------------------
void() ai_charge_side =
{
local vector dtemp;
local float heading;

// aim to the left of the enemy for a flyby
self.ideal_yaw = vectoyaw (self.enemy.origin - self.origin);
changeyaw ();

makevectors (self.angles);
dtemp = self.enemy.origin - 30 * v_right;
heading = vectoyaw (dtemp - self.origin);

walkmove (heading, 20);
};

//--------------------------------------------------------------
// ai_melee
//--------------------------------------------------------------
void() ai_melee =
{
local vector delta;
local float ldmg;

if (!self.enemy)
// removed before stroke
return;

delta = self.enemy.origin - self.origin;

if (vlen(delta) > 60)
return;

ldmg = (random() + random() + random()) * 3;
t_damage2 (self.enemy, self, self, ldmg);
};

//--------------------------------------------------------------
void() ai_melee_side =
{
local vector delta;
local float ldmg;

if (!self.enemy)
// removed before stroke
return;

ai_charge_side ();

delta = self.enemy.origin - self.origin;

if (vlen(delta) > 60)
return;

if (!can_damage(self, self.enemy))
return;

ldmg = (random() + random() + random()) * 3;
t_damage2 (self.enemy, self, self, ldmg);
};

//==============================================================
// SUBS
//==============================================================

//--------------------------------------------------------------
// SUB_AttackFinished
// in nightmare mode, all attack_finished times become 0
// some monsters refire twice automatically
//--------------------------------------------------------------
void(float normal) sub_attackfinished =
{
// refire count for nightmare
self.cnt = 0;
if (skill != 3)
self.attack_finished = time + normal;
};

//--------------------------------------------------------------
void(void() nextfunc) sub_checkrefire =
{
if (skill != 3)
return;
if (self.cnt == 1)
return;
if (!ai_visible(self.enemy))
return;

self.cnt = 1;
self.think = nextfunc;
};

//--------------------------------------------------------------
// SUB_FieldIsTargeted
//
// Return TRUE if the "fld" field of this entity is non-empty and
// matches the target (or target2/3/4 or pain_target) field of
// any other entity, otherwise return FALSE. -- iw
//--------------------------------------------------------------
float(.string fld) sub_fieldistargeted =
{
if (self.fld == "")
return FALSE;

// the following function calls are staggered to avoid
// the silly "return value conflict" problem with
// traditional compilers -- iw

if (find(world, target, self.fld) != world)
return TRUE;

if (find(world, target2, self.fld) != world)
return TRUE;

if (find(world, target3, self.fld) != world)
return TRUE;

if (find(world, target4, self.fld) != world)
return TRUE;

if (find(world, pain_target, self.fld) != world)
return TRUE;

return FALSE;
};

//--------------------------------------------------------------
// SUB_IsTargeted
//
// Return TRUE if the targetname (or targetname2/3/4) field of
// this entity is non-empty and matches the target (or target2/3/4
// or pain_target) field of any other entity, otherwise return
// FALSE. -- iw
//--------------------------------------------------------------
float() sub_istargeted =
{
// the following function calls are staggered to avoid
// the silly "return value conflict" problem with
// traditional compilers -- iw
if (sub_fieldistargeted(targetname))
return TRUE;

if (sub_fieldistargeted(targetname2))
return TRUE;

if (sub_fieldistargeted(targetname3))
return TRUE;

if (sub_fieldistargeted(targetname4))
return TRUE;

return FALSE;
};

//--------------------------------------------------------------
// monster_death_use
//
// When a mosnter dies, it fires all of its targets with the
// current enemy as activator.
//--------------------------------------------------------------
void() sub_death_use =
{
// fall to ground
if (self.flags & FL_FLY)
self.flags = self.flags - FL_FLY;
if (self.flags & FL_SWIM)
self.flags = self.flags - FL_SWIM;

if (!self.target)
return;

if (self.infight_activator)
activator = self.infight_activator;
else
activator = self.enemy;

sub_usetargets ();
};

//--------------------------------------------------------------
// monster_pain_use -- dumptruck_ds
//
// When a monster reaches pain_threshold, it fires all of its
// pain_targets with the current enemy as activator.
//--------------------------------------------------------------
void() sub_pain_use =
{
if (!self.pain_target)
return;

activator = self.enemy;
// SUB_UsePain ();
if (self.pain_target != "")
{
sub_usetarget (self.pain_target, targetname);
sub_usetarget (self.pain_target, targetname2);
sub_usetarget (self.pain_target, targetname3);
sub_usetarget (self.pain_target, targetname4);
}
// dumptruck_ds via Discord - thanks Spike, Snaut and QueenJazz
self.pain_target = "";
};

//==============================================================
// Interaction
//==============================================================

//--------------------------------------------------------------
void() base_monster_think_teleport_go =
{
self.solid = self.tele_solid;
self.movetype = self.tele_movetype;
setmodel (self, self.tele_model);
setsize (self, self.tele_mins, self.tele_maxs);

// ensure touch and use are set; think1 might override -- CEV
self.touch = base_monster_touch;
self.use = base_monster_use_angry;

// fix for cumulative delays for counters etc. -- dumptruck_ds
self.delay = 0;

self.think1 ();

// override the random delay some go functions apply
self.nextthink = time + 0.1;
// TODO CEV what is this bracket doing here?
{
if !(self.spawnflags & SPAWN_SILENTLY)
{
// dumptruck_ds: if wait value is > 0
// spawn silently or use a spawnflag
if (self.wait == 0)
spawn_tfog (self.origin);
spawn_tdeath (self.origin, self);
}
}
};

//--------------------------------------------------------------
// monster_touch -- was in monsters.qc -- CEV
//--------------------------------------------------------------
void() base_monster_touch =
{
// 1998-09-16 Sliding/not-jumping on monsters/boxes/players
// fix by Maddes/Kryten start

// by: Philip Martin aka: Kryten
// When on top of monsters or players you slide. This is a
// QuakeC problem. The function below fixes that problem.
// based on code given to Kryten by: Michael Turitzin (MaNiAc)

// can cause problems for monsters on top of a player,
// so only players
if (other.classtype != CT_PLAYER)
return;

if (other.health <= 0)
return;

if ((!(other.flags & FL_ONGROUND)) &&
((other.absmin_z >= self.absmax_z - 2)))
{
other.flags = other.flags + FL_ONGROUND;
}

// you can add other stuff like pushable players/monsters here

// 1998-09-16 Sliding/not-jumping on monsters/boxes/players
// fix by Maddes/Kryten end
};

//--------------------------------------------------------------
// monster_teleport_delay
//--------------------------------------------------------------
void() base_monster_use_teledelay =
{
// new from Qmaster func coding help thread
self.think = base_monster_think_teleport_go;
if (self.delay == -1)
{
// if delay is set to -1 random delay
// from 0.1 to 1 second - dumptruck_ds
self.nextthink = time + 0.1 + random();
return;
}
self.nextthink = time + 0.1 + self.delay;
};

//--------------------------------------------------------------
// monster_use -- Using a monster makes it angry at the activator
//--------------------------------------------------------------
void() base_monster_use_angry =
{
if (self.enemy)
return;
if (self.health <= 0)
return;
if (activator.items & IT_INVISIBILITY)
return;
if (activator.flags & FL_NOTARGET)
return;
if (activator.movetype == MOVETYPE_NOCLIP)
// Copper -- dumptruck_ds
return FALSE;

if (activator.classtype != CT_PLAYER)
return;

// delay reaction so if the monster is teleported,
// its sound is still heard
self.enemy = activator;
self.think = ai_foundtarget;
self.nextthink = time + 0.1;
};

//==============================================================
// Initialization
//==============================================================

//--------------------------------------------------------------
// monster_teleport_check
//
// This detects and eliminates a common map bug: a trigger-spawned
// monster which can't be activated either because it has no
// targetname or because its targetname isn't targeted by any
// other entity. (This map bug would otherwise make it impossible
// for the player to get 100% kills.) -- iw
//--------------------------------------------------------------
void() base_monster_init_teleport_check =
{
if (!sub_istargeted())
{
dprint (sprintf("base_monster_init_teleport_check: "
"WARNING: removed untargeted trigger-spawned "
"%s at %v\n", self.classname, self.origin));

remove (self);
return;
}

// the targetname appears to be OK, so let's finish
// setting up the trigger-spawned monster -- iw
// qmaster
self.touch = sub_null;
self.use = base_monster_use_teledelay;
monster_update_total (1);
};

//--------------------------------------------------------------
float(entity e, void() start_fn) base_monster_init_teleport =
{
if(!(e.spawnflags & 8))
return FALSE;

// PREACH: This monster is to be teleported in, so hide it
e.tele_model = e.model;
e.tele_mins = e.mins;
e.tele_maxs = e.maxs;
e.tele_solid = e.solid;
e.tele_movetype = e.movetype;

e.model = "";
e.modelindex = 0;
e.solid = SOLID_NOT;
e.movetype = MOVETYPE_NONE;
e.think1 = start_fn;

// wait for other entities to finish spawning, then check that
// something targets self -- iw
e.think = base_monster_init_teleport_check;
e.nextthink = time + 0.1;

return TRUE;
};

//--------------------------------------------------------------
void(entity e) base_monster_init =
{
e.classgroup |= CG_MONSTER;
e.touch = base_monster_touch;
e.use = base_monster_use_angry;
base_mapentity_init (e);
};

//--------------------------------------------------------------
strip void() base_monster =
{
base_monster_init (self);
};
// };

//----------------------------------------------------------------------
// class base_flymonster: base_monster
// {
//==============================================================
// Interaction
//==============================================================

//--------------------------------------------------------------
// flymonster_start_go
//--------------------------------------------------------------
void() base_flymonster_think_start =
{
self.takedamage = DAMAGE_AIM;

self.ideal_yaw = self.angles * '0 1 0';
if (!self.yaw_speed)
self.yaw_speed = 10;
self.view_ofs = '0 0 25';
self.use = base_monster_use_angry;

self.flags |= FL_FLY;
self.flags |= FL_MONSTER;

if (!walkmove(0, 0))
dprint (sprintf("base_flymonster_think_start: "
"monster in wall at %v\n", self.origin));

if (self.target != "")
{
self.goalentity = self.movetarget = find (world,
targetname, self.target);

// this used to be an objerror
if (!self.movetarget)
dprint (sprintf("base_flymonster_think_start: "
"monster at %v can't find target\n",
self.origin));

if (self.movetarget.classtype == CT_PATH_CORNER)
{
self.think_walk ();
}
else
{
self.pausetime = 99999999;
self.think_stand ();
}
}
else
{
self.pausetime = 99999999;
self.think_stand ();
}

// 1998-08-14 Monsters sometimes don't move fix by Lord Sméagol
self.nextthink = time + 0.1 + random() * 0.5;

// dumptruck_ds -- using spawn_angry set to 1 in order to spawn
// in "angry" monsters
// if ((this.spawnflags & 8) && this.spawn_angry == 1)
// {
// monster_use ();
// }

// dumptruck_ds -- this is Shamblernaut's method
local entity pl;

pl = findfloat (world, classtype, CT_PLAYER);

if (self.spawn_angry == 1)
{
activator = pl;
base_monster_use_angry ();
}
};

//==============================================================
// Initialization
//==============================================================

//--------------------------------------------------------------
// flymonster_start
//--------------------------------------------------------------
void(entity e) base_flymonster_init =
{
if (cvar("nomonsters"))
{
remove (e);
return;
}

base_monster_init (e);
e.classgroup |= CG_MONSTER_FLY;

// Preach's tutorial
if (base_monster_init_teleport(e, base_flymonster_think_start))
return;

// 1998-08-14 Monsters sometimes don't move fix by Lord Sméagol
e.flags |= FL_FLY;

total_monsters = total_monsters + 1;
e.think = base_flymonster_think_start;
// spread think times so they don't all happen at same time
// 1998-08-14 Monsters sometimes don't move fix by
// Lord Sméagol start
// e.nextthink = e.nextthink + random() * 0.5;
e.nextthink = time + 0.1 + random() * 0.5;
// 1998-08-14 Monsters sometimes don't move fix by
// Lord Sméagol end
};

//--------------------------------------------------------------
strip void() base_flymonster =
{
base_flymonster_init (self);
};
// };

//----------------------------------------------------------------------
// class base_swimmonster: base_monster
// {
//==============================================================
// Interaction
//==============================================================

//--------------------------------------------------------------
// swimmonster_start_go
//--------------------------------------------------------------
void() base_swimmonster_think_start =
{
if (deathmatch)
{
remove (self);
return;
}

self.takedamage = DAMAGE_AIM;

self.ideal_yaw = self.angles * '0 1 0';
if (!self.yaw_speed)
self.yaw_speed = 10;
self.view_ofs = '0 0 10';
self.use = base_monster_use_angry;

self.flags |= FL_SWIM;
self.flags |= FL_MONSTER;

if (self.target != "")
{
self.goalentity = self.movetarget = find (world,
targetname, self.target);

// this used to be an objerror
if (!self.movetarget)
dprint (sprintf("base_swimmonster_think_start: "
"monster at %v can't find target\n",
self.origin));

self.ideal_yaw = vectoyaw (
self.goalentity.origin - self.origin);
self.think_walk ();
}
else
{
self.pausetime = 99999999;
self.think_stand ();
}

// spread think times so they don't all happen at same time
// 1998-08-14 Monsters sometimes do not move fix
// by Lord Sméagol start
// self.nextthink = self.nextthink + random()*0.5;
self.nextthink = time + 0.1 + random() * 0.5;
// 1998-08-14 Monsters sometimes do not move fix
// by Lord Sméagol end

// dumptruck_ds -- using spawn_angry set to 1 in order to
// spawn in "angry" monsters
// if ((self.spawnflags & 8) && self.spawn_angry == 1)
// {
// monster_use ();
// }

// dumptruck_ds -- this is Shamblernaut's method
local entity pl;

pl = findfloat (world, classtype, CT_PLAYER);

if (self.spawn_angry == 1)
{
activator = pl;
base_monster_use_angry ();
}
};

//==============================================================
// Initialization
//==============================================================

//--------------------------------------------------------------
// swimmonster_start
//--------------------------------------------------------------
void(entity e) base_swimmonster_init =
{
if (cvar("nomonsters"))
{
remove (e);
return;
}

base_monster_init (e);
e.classgroup |= CG_MONSTER_SWIM;

// 1998-08-14 Monsters sometimes don't move fix by Lord Sméagol
e.flags = e.flags | FL_SWIM;

// Preach's tutorial
if (base_monster_init_teleport(e, base_swimmonster_think_start))
return;

total_monsters = total_monsters + 1;
e.think = base_swimmonster_think_start;
// spread think times so they don't all happen at same time
// 1998-08-14 Monsters sometimes do not move fix
// by Lord Sméagol start
// e.nextthink = e.nextthink + random() * 0.5;
e.nextthink = time + 0.1 + random() * 0.5;
// 1998-08-14 Monsters sometimes do not move fix
// by Lord Sméagol end
};

//--------------------------------------------------------------
strip void() base_swimmonster =
{
base_swimmonster_init (self);
};
// };

//----------------------------------------------------------------------
// class base_walkmonster: base_monster
// {
//==============================================================
// Interaction
//==============================================================

//--------------------------------------------------------------
// walkmonster_start_go
//--------------------------------------------------------------
void() base_walkmonster_think_start =
{
// raise off floor a bit
self.origin_z = self.origin_z + 1;

// Preach's "check" here
// if(time <= 0.5)
if(!(self.spawnflags & 8))
{
droptofloor ();
if !(self.spawnflags & I_AM_TURRET)
{
// fixes an incorrect dprint
// -- dumptruck_ds
if (!walkmove(0, 0))
dprint (sprintf("\n\n%s "
"in wall at: %v\n\n",
self.classname, self.origin));
}
}

self.takedamage = DAMAGE_AIM;
self.ideal_yaw = self.angles * '0 1 0';
if (!self.yaw_speed)
self.yaw_speed = 20;
self.view_ofs = '0 0 25';
self.use = base_monster_use_angry;
self.flags |= FL_MONSTER;

if (self.target != "")
{
self.goalentity = self.movetarget = find (
world, targetname, self.target);
self.ideal_yaw = vectoyaw (
self.goalentity.origin - self.origin);

// this used to be an objerror
if (!self.movetarget)
dprint (sprintf("base_walkmonster_think: "
"monster at %v can't find target\n",
self.origin));

if (self.movetarget.classtype == CT_PATH_CORNER)
{
self.think_walk ();
}
else
{
self.pausetime = 99999999;
self.think_stand ();
}
}
else
{
self.pausetime = 99999999;
self.think_stand ();
}

// spread think times so they don't all happen at once
// 1998-08-14 Monsters sometimes do not move fix
// by Lord Sméagol start
// self.nextthink = self.nextthink + random() * 0.5;
self.nextthink = time + 0.1 + random() * 0.5;
// 1998-08-14 Monsters sometimes do not move fixx
// by Lord Sméagol end
// dumptruck_ds -- using spawn_angry set to 1 in
// order to spawn in "angry" monsters
// if ((self.spawnflags & 8) && self.spawn_angry == 1)
// {
// monster_use ();
// }

// dumptruck_ds -- this is Shamblernaut's method
local entity pl;

pl = findfloat (world, classtype, CT_PLAYER);

if (self.spawn_angry == 1)
{
activator = pl;
base_monster_use_angry ();
}
};

//==============================================================
// Initialization
//==============================================================

//--------------------------------------------------------------
// walkmonster_start
//--------------------------------------------------------------
void(entity e) base_walkmonster_init =
{
// Preach's tutorial
if (cvar("nomonsters"))
{
remove (e);
return;
}

base_monster_init (e);

// Preach's tutorial
if (base_monster_init_teleport(e, base_walkmonster_think_start))
return;

// delay drop to floor to make sure all doors have been spawned
// spread think times so they don't all happen at same time

total_monsters = total_monsters + 1;
e.think = base_walkmonster_think_start;
// 1998-08-14 Monsters sometimes do not move fix
// by Lord Sméagol start
// e.nextthink = e.nextthink + random() * 0.5;
e.nextthink = time + 0.1 + random() * 0.5;
// 1998-08-14 Monsters sometimes do not move fix
// by Lord Sméagol end
};

//--------------------------------------------------------------
strip void() base_walkmonster =
{
base_walkmonster_init (self);
};
// };

Return to the top of this page or return to the overview of this repo.

Log base_monster.qc

Return to the top of this page or return to the overview of this repo.