Files
quakemapping/mod_ad_dev/my_progs/ai_subs.qc
2019-12-30 22:24:44 +01:00

1235 lines
44 KiB
Plaintext

/*======================================================================
AI (monster) FUNCTIONALITY
Visibility checks
- float(entity targ) visible
- float(entity targ, vector s_offset, vector t_offset, float nomonsters) visxray
- float(entity targ) visblocked
- float (entity targ, vector s_ofset, vector t_ofset) visblocked_wide
- float(entity targ) infront
- float(vector org_source, vector org_targ) check_liquidblock
Distance checks
- float(entity targ) range
- float(entity targ, float flat2d) range_distance
- float (entity source, entity targ) rangeattack_check
Angle checks
- float(entity source, entity targ, float offset, float reverse) targangle
- float(entity source, entity targ) targzone
Hunt & Target Enemies
- void() HuntTarget
- void() FoundTarget
- void(float wakeupothers) FoundHuntTarget
- float() FindTarget
AI animation movement (used mostly in monsters QC file)
- void(float dist) ai_forward
- void(float dist) ai_back
- void(float dist) ai_pain
- void(float dist) ai_painforward
- void() ai_turn
- float() FacingIdeal
- void() ai_face
- void() ai_resetangles
- void(float dist) ai_charge
- void(float dist) ai_chargenoturn
- void() ai_charge_side
- void() ai_charge_front
AI ATTACK states
- void() ai_run_melee
- void() ai_run_missile
- void() ai_run_jump
- void(float dist) ai_run_slide
- void(float dist) ai_run_sidestep
- void(float dist) ai_run_backward
AI interactions with Breakable System
- float(entity source, entity targ, float ignorenoshoot) ai_foundbreakable
- void(float brkdmg) ai_damagebreakable
- void(float brkdmg) ai_jumpbreakable
AI melee checks and damage
- float(float attackdist) ai_checkmelee
- void() ai_melee
- void(float dmg_multiplier) ai_meleesmash
- void() ai_melee_side
======================================================================*/
/*======================================================================
check_liquidblock
- Checks pointcontents of source+target because traceline parms
are not 100% reliable for flagging water/air content
- One function check for all the different visibility functions
- Designed to work with the new liquidblock entity flag
======================================================================*/
float(vector org_source, vector org_targ) check_liquidblock =
{
local float pc_source, pc_targ;
// Is r_wateralpha set or monsters forced to check liquid surfaces?
if (liquid_alpha == 1 || self.liquidblock) {
// Check point contents first
pc_source = pointcontents(org_source);
pc_targ = pointcontents(org_targ);
// Are point content different? (like water/air)
if (pc_source != pc_targ) return TRUE;
// Standard traceline check (not 100% reliable)
if (trace_inopen && trace_inwater) return TRUE;
}
// No liquid check required
return FALSE;
};
/*======================================================================
visible (target entity)
Conditions added to water surface check
- most maps are compiled with transparent water nowadays
======================================================================*/
float(entity targ) visible =
{
local vector spot1, spot2;
spot1 = self.origin + self.view_ofs;
spot2 = targ.origin + targ.view_ofs;
traceline (spot1, spot2, TRUE, self); // see through other monsters
// Debug system (enabled via axe) uses funcs from subs_soc.qc
// This will draw a diamond model where the AI is looking
if (self.debuglvl) {
if (!debugent1) debugent1 = spawn_devmarker(self.origin);
spot1 = vectoangles(self.origin - trace_endpos);
makevectors(spot1);
spot2 = trace_endpos + (v_up * -16) + (v_forward * 50);
setorigin(debugent1,'0 0 0');
setorigin(debugent1, spot2);
}
// Check for liquid surface block condition
if (check_liquidblock(spot1, spot2) == TRUE) return FALSE;
if (trace_fraction == 1) return TRUE;
return FALSE;
};
/*======================================================================
visxray (variable options)
Returns TRUE if there is no blocking, FALSE if something in the way
Custom options for variable source/target offsets and monster blocking
(nomonsters) TRUE = no block, FALSE = blocked
self is excluded from any trace starting points
======================================================================*/
float(entity targ, vector s_offset, vector t_offset, float nomonsters) visxray =
{
local vector spot1, spot2;
// Bmodels don't have proper origins
if (self.bsporigin) spot1 = bmodel_origin(self) + s_offset;
else spot1 = self.origin + s_offset;
spot2 = targ.origin + t_offset;
traceline (spot1, spot2, nomonsters, self); // custom options
// Check for liquid surface block condition
if (check_liquidblock(spot1, spot2) == TRUE) return FALSE;
// monster blocking changes which test to use
if (nomonsters) {
// Has the traceline gone from source>target without anything blocking?
if (trace_fraction == 1) return TRUE;
return FALSE;
}
else {
// Is the entity hit by the traceline the targ destination?
if (trace_ent == targ) return TRUE;
return FALSE;
}
};
/*======================================================================
visblocked (target entity)
check to see if sightline is blocked by other monsters
Really only used in CheckAttack functions (was in org ID code)
======================================================================*/
float(entity targ) visblocked =
{
local vector spot1, spot2;
spot1 = self.origin + self.view_ofs;
spot2 = targ.origin + targ.view_ofs;
traceline (spot1, spot2, FALSE, self); // blocked by monsters
// Check for liquid surface block condition
if (check_liquidblock(spot1, spot2) == TRUE) return FALSE;
// Original ID behaviour for this type of function
// Traceline entity hit = enemy?
if (trace_ent == targ) return FALSE;
return TRUE;
};
/*======================================================================
viswide (target entity)
check to see if sightline is blocked by other monsters
extra wide check using 3 traces, +/- 16 left/right and origin
All three traces have to complete for a positive result
======================================================================*/
float (entity targ, vector s_ofset, vector t_ofset) visblocked_wide =
{
local vector spot1, spot2, spot3, tr_line;
local float tr_leftmon, tr_rightmon, tr_result;
tr_line = '0 0 0';
tr_leftmon = tr_rightmon = 0;
makevectors(self.angles);
//------------------------------------------------------------------
// Traceline 1 - MIDDLE
spot1 = self.origin + s_ofset_x * v_forward + s_ofset_y * v_right + s_ofset_z * v_up;
spot2 = targ.origin + t_ofset;
traceline (spot1, spot2, FALSE, self);
// Is trace entity the same as the target entity?
if (trace_ent == targ) tr_line_x = 1;
// Check for liquid surface block condition
// Only need to do this once for the central traceline
// If this fails then the left/right should as well
if (check_liquidblock(spot1, spot2) == TRUE) tr_line_x = 0;
//------------------------------------------------------------------
// Traceline 2 - 32 units LEFT
spot3 = spot1 - (v_right * 16);
traceline (spot3, spot2, FALSE, self);
// Work out if clear shot, hit world or another entity
if (trace_ent == targ) tr_line_y = 1;
else if (trace_ent != world) tr_leftmon = 1;
if (self.debuglvl) {
if (!debugent2) debugent2 = spawn_devmarker(self.origin);
setorigin(debugent2, trace_endpos);
}
//------------------------------------------------------------------
// Traceline 3 - 32 units RIGHT
spot3 = spot1 + (v_right * 16);
traceline (spot3, spot2, FALSE, self);
// Work out if clear shot, hit world or another entity
if (trace_ent == targ) tr_line_z = 1;
else if (trace_ent != world) tr_rightmon = 1;
if (self.debuglvl) {
if (!debugent3) debugent3 = spawn_devmarker(self.origin);
setorigin(debugent3, trace_endpos);
}
//------------------------------------------------------------------
// Default = no clear shot (keep aim/moving)
tr_result = TRUE;
// SIMPLE 3 complete traces = nothing blocking visibility
if (tr_line_x + tr_line_y + tr_line_z == 3) tr_result = FALSE;
// Central trace is good but either side has hit something?
else if (tr_line_x) {
// Hit a monster on either side = always fail!
if (tr_leftmon || tr_rightmon) tr_result = TRUE;
// The crssbow bolt is fired from the LEFT side, only right can be clear
else if (self.classtype == CT_MONDCROSS) {
if (tr_line_y == 0 && tr_line_z == 1) tr_result = FALSE;
}
// Default - space available on either side
else if (tr_line_y + tr_line_z == 1) tr_result = FALSE;
}
//------------------------------------------------------------------
if (self.debuglvl) {
dprint("Trace ("); dprint(ftos(tr_line_x));
dprint(" "); dprint(ftos(tr_line_y));
dprint(" "); dprint(ftos(tr_line_z));
dprint(") Ent ("); dprint(ftos(tr_leftmon));
dprint(" "); dprint(ftos(tr_rightmon));
dprint(") = ("); dprint(ftos(tr_result));
dprint(")\n");
}
return tr_result;
};
/*======================================================================
infront (target entity)
- relies on self being set correctly before entering this function
- returns 1 if the entity is in front (in sight) of self
======================================================================*/
float(entity targ) infront =
{
local vector vec;
local float dot;
makevectors (self.angles); // sets v_forward, etc globals
vec = normalize (targ.origin - self.origin);
dot = vec * v_forward;
if ( dot > 0.3) return TRUE;
else return FALSE;
};
/*======================================================================
range (target entity)
returns the range catagorization of an entity reletive to self
uses constants defined in defs.qc
======================================================================*/
float(entity targ) range =
{
local vector spot1, spot2;
local float r;
spot1 = self.origin + self.view_ofs;
spot2 = targ.origin + targ.view_ofs;
r = vlen (spot1 - spot2);
if (r < MON_RANGE_MELEE) return RANGE_MELEE; // <120 = MELEE
else if (r < MON_RANGE_NEAR) return RANGE_NEAR; // <500 = NEAR
else if (r < MON_RANGE_MID) return RANGE_MID; // <1000 = MID
return RANGE_FAR; // >1000 = FAR
};
/*======================================================================
range_distance (target entity)
returns the vector distance between two points
======================================================================*/
float(entity targ, float flat2d) range_distance =
{
local vector spot1, spot2;
local float r;
spot1 = self.origin + self.view_ofs;
spot2 = targ.origin + targ.view_ofs;
if (flat2d) { spot1_z = spot2_z = 0; }
r = vlen (spot1 - spot2);
return r;
};
/*======================================================================
rangeattack_check
check to see if (source) can get a clear shot at (targ)
Ignores any water/air content checks
======================================================================*/
float (entity source, entity targ) rangeattack_check =
{
local vector spot1, spot2;
local float r;
// origin+offset, otherwise cannot see player up/down slopes
spot1 = source.origin + source.view_ofs;
spot2 = targ.origin + targ.view_ofs;
r = vlen(spot1 - spot2);
// Maximum range attack (was 600 taken from shambler, changed to 1000)
if (r > MON_RANGE_MID) return FALSE;
// Monsters without melee should always range anything really close
if (!source.th_melee && r < MON_RANGE_MELEE) return TRUE;
traceline (spot1, spot2, FALSE, source); // trace hits monsters (infighting)
if (trace_ent != targ) return FALSE; // don't have a clear shot
// Does the world define any water transparency
if (liquid_alpha == 1 || source.liquidblock) {
// sight line crossed contents
if (trace_inopen && trace_inwater) return FALSE;
}
return TRUE;
};
/*======================================================================
targangle
Returns what angle the target entity is relative to the source entity
Useful for checking where the player is located.
source, targ = entity origins to create vector angle
reverse = the creation of the vector can be reversed
offset = shift the final angle clockwise. (frontal sight cone 315-45)
======================================================================*/
float(entity source, entity targ, float offset, float reverse) targangle =
{
local float targ_dir, targ_ang;
// Calculate vector angle between two points (source, target)
if (reverse) targ_dir = vectoyaw(source.origin - targ.origin);
else targ_dir = vectoyaw(targ.origin - source.origin);
targ_ang = anglemod( (source.angles_y - targ_dir) + offset );
return targ_ang;
};
// Modified version of targangle not using entities but supplied origins
// Targets Y angles needs to be supplied as no entity reference available
float(vector source, vector targ, float offset, float reverse, float dst_ang) viewangle =
{
local float targ_dir, targ_ang;
// Calculate vector angle between two points (source, target)
if (reverse) targ_dir = vectoyaw(source - targ);
else targ_dir = vectoyaw(targ - source);
targ_ang = anglemod( (dst_ang - targ_dir) + offset );
return targ_ang;
};
/*======================================================================
targzone (source entity, target entity)
- Checks which zone the target enemy is located in relation to source (self)
0 = Behind (315-45), 1 = Left (45-135), 2 = Front (135-225), Right = (225-315)
======================================================================*/
float(entity source, entity targ) targzone =
{
local float target_ang;
// Work out angle the angle of the player based on the monster (source)
// Add 45 degree offset to shift clockwise the frontal sight cone
target_ang = targangle(source, targ, 45, TRUE);
if (target_ang > 270) return TARGET_RIGHT;
else if (target_ang > 180) return TARGET_FRONT;
else if (target_ang > 90) return TARGET_LEFT;
else return TARGET_BACK;
};
/*======================================================================
HuntTarget
Main routine - constantly running/turning towards the enemy
======================================================================*/
void() HuntTarget =
{
if (self.health < 1) return; // Dead monsters don't hunt!
self.oldorigin = self.origin; // Save origin
self.goalentity = self.enemy; // Focus on enemy
self.ideal_yaw = vectoyaw(self.enemy.origin - self.origin);
// Some monsters have special wakeup animations before attacking
self.nextthink = time + 0.1;
self.think = self.th_run;
// Default 1s wait (NM adjusted) before attacking
if (!self.attack_instant) SUB_AttackFinished (1);
};
/*======================================================================
FoundTarget
- An enemy target has been found, wakeup and hunt target
- Used by FindTarget once an enemy target has been found
- used by T_Damage (combat.qc) for infighting
======================================================================*/
void() FoundTarget =
{
if (self.health < 1) return FALSE; // Dead monsters don't hunt!
// Check for Liquid surface block entity key
// if the monster has range attack, can NOW see through liquid surface
if (self.liquidblock == TRUE && self.th_missile)
self.liquidblock = FALSE;
if (self.enemy.flags & FL_CLIENT) {
sight_entity = self; // Highlight an angry monster for others
sight_entity_time = time; // reset wakeup timer
}
//----------------------------------------------------------------------
// This will cause a group of monsters to all attack the same target
// Using the global sight enemy system which works with FindTarget()
// Always check for an empty sighttarget first because the
// the global sight entity could have an empty targetname
//----------------------------------------------------------------------
if (self.sighttarget != "") {
if (self.enemy.targetname == self.sighttarget) {
// Make sure the sight target is alive before chasing it
self.oldenemy = find(world, targetname, self.sighttarget);
if (self.oldenemy.health > 0) {
if (self.oldenemy.takedamage > DAMAGE_NO) {
sight_entity = self.enemy; // Highlight target for others
sight_entity_time = time; // reset wakeup timer
monster_sightsound(); // Always make wakeup noise!
}
}
self.oldenemy = world;
}
}
self.show_hostile = time + 1; // wake up other monsters
// Only play wakeup sound if player, not for other monsters
if (self.enemy.flags & FL_CLIENT) monster_sightsound();
HuntTarget (); // Keep running after enemy
};
/*======================================================================
FoundHuntTarget
- This is a combination of FoundTarget and HuntTarget
- Does not do any sight sound, needs to be done outside this func
- Does not pause or wait for any monster, straight to combat
- Has no end funtion state, can decide this afterwards
======================================================================*/
void(float wakeupothers) FoundHuntTarget =
{
if (self.health < 1) return; // Dead monsters don't hunt!
if (!self.enemy) return;
// Check for Liquid surface block entity key
// if the monster has range attack, can NOW see through liquid surface
if (self.liquidblock == TRUE && self.th_missile)
self.liquidblock = FALSE;
if (self.enemy.flags & FL_CLIENT && wakeupothers == TRUE) {
sight_entity = self; // Highlight an angry monster for others
sight_entity_time = time; // reset wakeup timer
}
self.show_hostile = time + 1; // wake up other monsters
self.oldorigin = self.origin; // Save origin
self.goalentity = self.enemy; // Focus on enemy
self.ideal_yaw = vectoyaw(self.enemy.origin - self.origin);
SUB_AttackFinished (1); // wait a while before first attack
};
/*======================================================================
FindTarget
- Main routine for finding a PLAYER target
- Returns TRUE if an enemy was sighted
======================================================================*/
float() FindTarget =
{
local entity client, playtarg;
if (self.health < 1) return FALSE; // Dead monsters don't hunt!
//----------------------------------------------------------------------
// Does the global sight entity match the enemy sight group?
// Always check for an empty sighttarget first because the
// the global sight entity could have an empty targetname
//----------------------------------------------------------------------
if (self.sighttarget != "") {
if (sight_entity.targetname == self.sighttarget) {
// Make sure the sight target is alive before chasing it
if (sight_entity.health < 0) self.sighttarget = "";
else {
// If using enerytarget system, switch off tracking entity first
if (self.enemy.classtype == CT_ENEMYTARGET) SUB_switchEnemyTarget();
self.enemy = sight_entity; // Switch to new enemy
FoundTarget (); // Run+turn towards new enemy
return TRUE;
}
}
}
//----------------------------------------------------------------------
// if the first spawnflag bit is set, the monster will only wake up on
// really seeing the player, not another monster getting angry
// Zombies have their ambush/crucified spawnflag mix up fixed
//----------------------------------------------------------------------
if (sight_entity_time >= time - 0.1 && !(self.spawnflags & MON_AMBUSH) ) {
client = sight_entity; // Global variable
if (client.enemy == self.enemy) return TRUE;
}
else {
client = checkclient (); // Find a client in current PVS
if (!client) return FALSE; // If no client found, return
}
//----------------------------------------------------------------------
// Checkclient is *suppose* to find ONLY players in the current PVS
// but it seems the code does something slightly different
// Checkclient will return other 'objects' (monsters) who are mad
// at the player, which is suppose to be caught with sight_enemy
// Find the player entity (client,.enemy,world) for specific checks
//----------------------------------------------------------------------
if (client.flags & FL_CLIENT) playtarg = client;
else {
if (client.enemy) playtarg = client;
else playtarg = find(world,targetname,"player");
}
//----------------------------------------------------------------------
// If current enemy = client (player), already got *that* target
//----------------------------------------------------------------------
if (client == self.enemy) return FALSE;
//----------------------------------------------------------------------
// Check for debug mode, invis artifact, menu system active
//----------------------------------------------------------------------
if (playtarg.flags & FL_NOTARGET) return FALSE;
// Boss monsters can see invisible players!
if (playtarg.items & IT_INVISIBILITY && self.bossflag == FALSE) return FALSE;
if (intermission_running) return FALSE;
//----------------------------------------------------------------------
// Is the client/player >1000 = FAR do nothing, too far away
//----------------------------------------------------------------------
enemy_range = range (client);
if (enemy_range == RANGE_FAR && !self.attack_sniper) return FALSE;
//----------------------------------------------------------------------
// Do not wake up unless the client (player/monster) is visible
//----------------------------------------------------------------------
enemy_vis = visible(client);
if (!enemy_vis) return FALSE;
//----------------------------------------------------------------------
// (Distance >128 && < 500) Is client infront+side and hostile?
// show_hostile flag is set when the player fires a weapon
//----------------------------------------------------------------------
if (enemy_range == RANGE_NEAR) {
if (client.show_hostile < time) {
if (self.sight_nofront == FALSE && !infront (client))
return FALSE;
}
}
//----------------------------------------------------------------------
// (Distance 500+) Is client infront?
//----------------------------------------------------------------------
else if (enemy_range > RANGE_NEAR) {
if (self.sight_nofront == FALSE && !infront (client))
return FALSE;
}
// At this point if the enemy is <120 (melee range) the enemy
// will always wake up and turn around towards the player
//----------------------------------------------------------------------
// Finally got a target, check if its a player
// This is an odd sitation because at this point client *should* be
// the player/client and this code tries to fix the problem by
// searching through enemy of enemy chains
//----------------------------------------------------------------------
self.enemy = client;
if ( !(client.flags & FL_CLIENT) ) {
// Is the enemy of the enemy a player?
self.enemy = self.enemy.enemy;
if ( !(client.enemy.flags & FL_CLIENT) ) {
self.enemy = world;
return FALSE;
}
}
FoundTarget (); // Run+turn towards new enemy
return TRUE; // stop previous routine got a target
};
//======================================================================
// ai_face (generic)
//----------------------------------------------------------------------
void() ai_face =
{
if (self.health < 1) return;// Unusual check, caught elsewhere
self.ideal_yaw = vectoyaw(self.enemy.origin - self.origin);
ChangeYaw (); // Done in code
};
//----------------------------------------------------------------------
// ai_forward
//----------------------------------------------------------------------
void(float dist) ai_forward =
{
walkmove (self.angles_y, dist);
};
//----------------------------------------------------------------------
// ai_forward + ai_face
//----------------------------------------------------------------------
void(float dist) ai_faceforward =
{
ai_face();
walkmove (self.angles_y, dist);
};
//----------------------------------------------------------------------
// ai_back + backface
//----------------------------------------------------------------------
void(float dist) ai_back =
{
walkmove ( (self.angles_y+180), dist);
};
void(float dist) ai_backface =
{
ai_face();
ai_back(dist);
};
//----------------------------------------------------------------------
// ai_pain (does nothing, just uses ai_back instead)
//----------------------------------------------------------------------
void(float dist) ai_pain =
{
ai_back (dist);
};
//----------------------------------------------------------------------
// ai_painforward
//----------------------------------------------------------------------
void(float dist) ai_painforward =
{
walkmove (self.angles_y, dist);
// Not sure why Id used ideal_yaw when other functions use angles_y
// walkmove (self.ideal_yaw, dist);
};
//----------------------------------------------------------------------
// ai_turn (Very costly function, re-using FindTarget again)
//----------------------------------------------------------------------
void() ai_turn =
{
if (FindTarget ()) return;
ChangeYaw (); // Code function
};
//----------------------------------------------------------------------
// FacingIdeal
//----------------------------------------------------------------------
float() FacingIdeal =
{
local float delta;
delta = anglemod(self.angles_y - self.ideal_yaw);
if (delta > 45 && delta < 315) return FALSE;
else return TRUE;
};
//----------------------------------------------------------------------
// ai_trackenemy
//----------------------------------------------------------------------
float() ai_trackenemy =
{
if (visible(self.enemy)) {
self.attack_track = SUB_orgEnemyTarget();
ai_face();
return TRUE;
}
else return FALSE;
};
//----------------------------------------------------------------------
// ai_resetangles
//----------------------------------------------------------------------
void() ai_resetangles =
{
// There is a chance when finished a monster jump that some
// of the angles (X/Z) values are wrong and need resetting
// Should only be used after jump functions
self.angles_x = self.angles_z = 0;
};
//----------------------------------------------------------------------
// ai_charge (generic)
// - The monster is in a melee attack,
// so get as close as possible to .enemy
//----------------------------------------------------------------------
void(float dist) ai_charge =
{
ai_face ();
movetogoal (dist);
};
//----------------------------------------------------------------------
// ai_chargenoturn (generic)
//----------------------------------------------------------------------
void(float dist) ai_chargenoturn =
{
movetogoal (dist);
};
//----------------------------------------------------------------------
// ai_charge_side (generic)
//----------------------------------------------------------------------
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_charge_front (generic)
//----------------------------------------------------------------------
void() ai_charge_front =
{
local float heading;
ai_face(); // Turn towards enemy first
makevectors (self.angles);
heading = vectoyaw(self.enemy.origin - self.origin);
walkmove(heading, 10);
};
/*======================================================================
ai_run_melee
- Turn and close until within an angle to launch a melee attack
======================================================================*/
void() ai_run_melee =
{
self.ideal_yaw = enemy_yaw; // This is defined in ai_run
ChangeYaw (); // Code function
//----------------------------------------------------------------------
// Facing towards the ENEMY target? (ideal_yaw)
//----------------------------------------------------------------------
if (FacingIdeal()) {
self.th_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; // This is defined in ai_run
ChangeYaw (); // Code function
//----------------------------------------------------------------------
// Facing towards the ENEMY target? (ideal_yaw)
//----------------------------------------------------------------------
if (FacingIdeal()) {
self.th_missile ();
self.attack_state = AS_STRAIGHT;
}
};
/*======================================================================
ai_run_jump
- Turn in place until within an angle to jump attack
======================================================================*/
void() ai_run_jump =
{
self.ideal_yaw = enemy_yaw; // This is defined in ai_run
ChangeYaw (); // Code function
//----------------------------------------------------------------------
// Facing towards the ENEMY target? (ideal_yaw)
//----------------------------------------------------------------------
if (FacingIdeal()) {
self.th_jump ();
self.attack_state = AS_STRAIGHT;
}
};
/*======================================================================
ai_run_slide
- Strafe sideways, but stay at aproximately the same range
======================================================================*/
void(float dist) ai_run_slide =
{
local float ofs;
// It is better to re-calcuate this than wait for ai_run to update
self.ideal_yaw = vectoyaw(self.enemy.origin - self.origin);
// self.ideal_yaw = enemy_yaw; // This is defined in ai_run
ChangeYaw (); // Code function
if (self.lefty > 0) ofs = 90;
else ofs = -90;
// Tests if monsters can strafe or not by moving the monster
if (walkmove (self.ideal_yaw + ofs, dist)) return;
// Switch strafe sides for later use
self.lefty = rint(1 - self.lefty);
if (walkmove (self.ideal_yaw - ofs, dist)) return;
// Try moving backwards if both sides are blocked
walkmove (self.ideal_yaw - 180, dist);
};
/*======================================================================
ai_run_sidestep
- Turn 90 degrees and move to the side
======================================================================*/
void(float dist) ai_run_sidestep =
{
local float ofs;
if (self.attack_sidestep < time) {
self.attack_sidestep = time + 4 + random()*4;
self.lefty = rint(1 - self.lefty);
}
if (self.lefty > 0) ofs = 85;
else ofs = -85;
// Work out angle to face for enemy and then turn +/- 90 degrees
// so that the monster is moving sideways to the enemy
self.ideal_yaw = vectoyaw(self.enemy.origin - self.origin) + ofs;
ChangeYaw ();
// Tests if monsters can move sideways
if (walkmove (self.ideal_yaw, dist)) return;
// Switch strafe sides for later use
self.attack_sidestep = -1;
};
/*======================================================================
ai_run_backwards
- Turn 180 degrees and move backwards
======================================================================*/
void(float dist) ai_run_backward =
{
// Work out angle to face for enemy and then turn +/- 90 degrees
// so that the monster is moving sideways to the enemy
self.ideal_yaw = vectoyaw(self.enemy.origin - self.origin) + 180;
ChangeYaw ();
// Tests if monsters can move backwards
if (walkmove (self.ideal_yaw, dist)) return;
// Cannot walk backwards, move sideways instead
self.attack_state = AS_SIDESTEP;
};
/*======================================================================
ai_foundbreakable
Check if the monster is allowed to break the breakable!?!
======================================================================*/
float(entity source, entity targ, float ignorenoshoot) ai_foundbreakable =
{
// Found a breakable in the way?
if (targ.classtype == CT_FUNCBREAK) {
// Check if a monster and nomonster damage/trigger allowed?
if (source.flags & FL_MONSTER && targ.spawnflags & BREAK_NOMONSTER)
return FALSE;
// Ignoring noshoot spawnflag? (designed for jump/missile events)
else if (ignorenoshoot == TRUE) return TRUE;
// Can the breakable be damaged?
else if (targ.spawnflags & BREAK_NOSHOOT) return FALSE;
// Its a breakable that breaks!
else return TRUE;
}
return FALSE;
};
/*======================================================================
ai_immunedamage
Check if a breakable/pushable is immune to damage from monsters
======================================================================*/
float(entity source, entity targ) ai_immunedamage =
{
// Found a breakable?
if (targ.classtype == CT_FUNCBREAK) {
// Check no damage spawnflag?
if (source.flags & FL_MONSTER && targ.spawnflags & BREAK_NOMONSTER)
return TRUE;
}
// Found a pushable?
else if (targ.classtype == CT_FUNCPUSHABLE) {
// Check no damage spawnflag?
if (source.flags & FL_MONSTER && targ.spawnflags & PUSH_NOMONSTER)
return TRUE;
}
return FALSE;
};
/*======================================================================
ai_damagebreakable
Check if the monster (self) can hit any breakables infront (target)
Triple trace infront (self.angles) up,middle,down
If any breakables found, damage them to see if they will break
======================================================================*/
void(float brkdmg) ai_damagebreakable =
{
local vector spot1, spot2;
local float ldmg;
// Setup damage, forward facing vector and initial trace origin
ldmg = (random() + random() + random()) * brkdmg;
makevectors(self.angles);
spot1 = self.origin;
// Trace directly infront of entity using angles
spot2 = spot1 + v_forward * 100;
traceline (spot1, spot2, TRUE, self);
if (ai_foundbreakable(self, trace_ent, FALSE)) {
// Check for any monster damage modifier on breakable
if (trace_ent.brkmondmg>0) ldmg = ldmg * trace_ent.brkmondmg;
T_Damage (trace_ent, self, self, ldmg, DAMARMOR);
}
else {
// Trace upwards
spot2 = spot1 + v_forward * 100 + v_up * 64;
traceline (spot1, spot2, TRUE, self);
if (ai_foundbreakable(self, trace_ent, FALSE)) {
// Check for any monster damage modifier on breakable
if (trace_ent.brkmondmg>0) ldmg = ldmg * trace_ent.brkmondmg;
T_Damage (trace_ent, self, self, ldmg, DAMARMOR);
}
else {
// Trace downwards
spot2 = spot1 + v_forward * 100 - v_up * 64;
traceline (spot1, spot2, TRUE, self);
if (ai_foundbreakable(self, trace_ent, FALSE)) {
// Check for any monster damage modifier on breakable
if (trace_ent.brkmondmg>0) ldmg = ldmg * trace_ent.brkmondmg;
T_Damage (trace_ent, self, self, ldmg, DAMARMOR);
}
}
}
};
/*======================================================================
ai_jumpbreakable
Check if the monster can trigger a breakable from jump attack
======================================================================*/
void(float brkdmg) ai_jumpbreakable =
{
if (ai_foundbreakable(self, other,TRUE) && other.brktrigjump != 0) {
// Found a breakable which is prone to jump damage
trigger_ent(other, self);
}
else ai_damagebreakable(brkdmg); // Damage any breakables
};
/*======================================================================
ai_checkmelee
Check if the monster (self) can attack enemy (target)
and returns TRUE if the monster is within XYZ range
======================================================================*/
float(float attackdist) ai_checkmelee =
{
local vector spot1, spot2;
local float delta, zdiff;
// Calculate distance and z axis difference seperate
spot1 = SUB_orgEnemyTarget();
spot2 = self.origin;
zdiff = fabs(spot1_z - spot2_z);
spot1_z = spot2_z = 0; // Flatten Z axis before vector length
delta = vlen(spot1 - spot2); // Calculate vector distance
// Is the enemy too far away and the zaxis is wrong (too low/high)
if (delta < attackdist && zdiff < MONAI_MELEEZAXIS) return TRUE;
else return FALSE;
};
/*======================================================================
ai_melee (generic)
- slashing type of damage facing forward
really assuming the monster is stationary while attacking
======================================================================*/
void() ai_melee =
{
local float ldmg;
if (!self.enemy) return; // removed before stroke
ai_damagebreakable(10); // Damage any breakables
if (!ai_checkmelee(MONAI_MELEEFRONT)) return; // Too far away
// Can the target bleed? - no blood/damage, quick exit
// This candamage test not in origina id code (sync'd to melee_side)
if (!CanDamage (self.enemy, self)) return;
// 1-9 damage
ldmg = (random() + random() + random()) * 3;
if (ldmg < 1) ldmg = 1;
T_Damage (self.enemy, self, self, ldmg, DAMARMOR);
// Only spawn blood/gore/sound every second
if (self.meleetimer < time) {
self.meleetimer = time + 1;
SpawnMeatSpray (self, self.enemy, random() * 100);
}
// Check for poisonous blades!
if (self.poisonous) PoisonDeBuff(self.enemy);
// Some melee weapons have swing and hit as separate sounds
// This is trigger_once per melee combat swing
// No check if meleehitsound is defined or not
if (self.meleecontact) {
self.meleecontact = FALSE;
sound (self, CHAN_WEAPON, self.meleehitsound, 1, ATTN_NORM);
}
};
/*======================================================================
ai_meleesmash (generic)
- large smashing damage overhead attack, usually a single strike
really assuming the monster is stationary while attacking
======================================================================*/
void(float dmg_multiplier) ai_meleesmash =
{
local float ldmg;
if (!self.enemy) return; // removed before stroke
ai_damagebreakable(3*dmg_multiplier); // Damage any breakables
if (!ai_checkmelee(self.meleerange)) return; // Too far away
// Can the target bleed? - no blood/damage, quick exit
// This candamage test not in origina id code (sync'd to melee_side)
if (!CanDamage (self.enemy, self)) return;
// This function is designed for monsters attacking players or infighting
// If this is infighting do more damage as it will look more impressive
// Also if this damage is enough to kill in a single blow, gib for effect
if (self.enemy.flags & FL_MONSTER) dmg_multiplier = dmg_multiplier * 2;
// 1-9 damage
ldmg = (random() + random() + random()) * dmg_multiplier;
if (ldmg < 1) ldmg = 1;
if (self.enemy.health < ldmg) ldmg = ldmg*3;
T_Damage (self.enemy, self, self, ldmg, DAMARMOR);
// Lots of blood and gore
SpawnMeatSpray (self, self.enemy, crandom() * 100);
SpawnMeatSpray (self, self.enemy, crandom() * 100);
// Check for poisonous blades!
if (self.poisonous) PoisonDeBuff(self.enemy);
// Some melee weapons have swing and hit as separate sounds
// This is trigger_once per melee combat swing
if (self.meleecontact) {
self.meleecontact = FALSE;
if (self.meleehitsound) sound (self, CHAN_WEAPON, self.meleehitsound, 1, ATTN_NORM);
}
};
/*======================================================================
ai_melee_side (generic)
- move forward/side and attack (can cause animation sliding errors)
this is really designed for glancing blows and monsters which are
moving fast like they are charging at the player
======================================================================*/
void() ai_melee_side =
{
local float ldmg;
if (!self.enemy) return; // removed before stroke
ai_damagebreakable(10); // Damage any breakables
ai_charge_side(); // move (20 units) to the side of enemy
if (!ai_checkmelee(MONAI_MELEESIDE)) return; // Too far away
// Can the target bleed? - no blood/damage, quick exit
if (!CanDamage (self.enemy, self)) return;
// 1-9 damage
ldmg = (random() + random() + random()) * 3;
if (ldmg < 1) ldmg = 1;
T_Damage (self.enemy, self, self, ldmg, DAMARMOR);
SpawnMeatSpray (self, self.enemy, random() * 50);
// Check for poisonous blades!
if (self.poisonous) PoisonDeBuff(self.enemy);
// Some melee weapons have swing and hit as separate sounds
// This is trigger_once per melee combat swing
// No check if meleehitsound is defined or not
if (self.meleecontact) {
self.meleecontact = FALSE;
sound (self, CHAN_WEAPON, self.meleehitsound, 1, ATTN_NORM);
}
};
/*======================================================================
ai_shockwave
- produce a large ground slam/shockwave effect
- Used by hammer ogres and golems
======================================================================*/
void() ai_shockwave_think =
{
self.wait = rint((time - self.ltime)*10);
if (self.wait > 6) SUB_Remove();
else {
self.frame = self.wait;
if (self.wait > 4) {
if (!self.delay) {self.delay = time;}
self.alpha = 1-((time - self.delay)*5);
}
self.angles_y = anglemod(self.angles_y + rint(random()*20));
self.nextthink = time + TIME_MINTICK;
}
};
//----------------------------------------------------------------------
void(vector imp_vec, float imp_damage, float imp_radius, float imp_forward, float imp_up) ai_shockwave =
{
local entity swave, sjump;
local vector impact, fvel;
local float vdist, vpercent, vpart;
local float impzdiff;
self.effects = self.effects | EF_MUZZLEFLASH;
// Check if the player is close enough for damage?
self.meleecontact = TRUE;
ai_meleesmash(imp_damage);
self.meleecontact = FALSE;
// Play impact sound and work out where the impact is going to happen
sound (self, CHAN_WEAPON, self.meleehitsound, 1, ATTN_IDLE);
makevectors (self.angles);
// impact = self.origin + v_forward*imp_vec_x + v_right*imp_vec_y + v_up*imp_vec_z;
impact = self.origin + attack_vector(imp_vec);
// Push all entities within a certain radius outwards
// Do this find search before spawning ring and particles
// findradius will find ALL entities regardless of type
sjump = findradius(impact, imp_radius);
while(sjump) {
// Ignore monster (self)
if (sjump != self) {
// Only affect players and other monsters
if (sjump.flags & FL_CLIENT || sjump.flags & FL_MONSTER) {
// Exclude statues, stone/heavy monsters and bosses!
if (sjump.classgroup != CG_STONE && sjump.bossflag == FALSE) {
// Is the entity on the ground?
if (sjump.flags & FL_ONGROUND) {
// Is the entity too far above or below the impact?
impzdiff = fabs(sjump.origin_z - impact_z);
if (impzdiff < MONAI_IMPACTZAXIS) {
// Distance between impact and enemy
vdist = vlen(sjump.origin - impact);
// Percentage of impact force
vpercent = 1 - (vdist / imp_radius);
// Monsters have less impact
if (sjump.flags & FL_MONSTER) vpercent = vpercent * 0.75;
// Ogre facing angle (easier to understand)
makevectors(self.angles);
// Combine forward/up force with existing velocity
fvel = (v_forward * (imp_forward * vpercent)) + (v_up * (imp_up * vpercent));
sjump.velocity = sjump.velocity + fvel;
sjump.flags = sjump.flags - (sjump.flags & FL_ONGROUND);
}
}
}
}
}
sjump = sjump.chain;
}
// Spawn impact model ring on the ground
swave = spawn();
swave.mdl = MODEL_PROJ_RINGSHOCK;
setmodel(swave,swave.mdl);
setsize(swave, VEC_ORIGIN, VEC_ORIGIN);
setorigin(swave, impact); // on floor, slight up (2 pixels)
swave.solid = SOLID_NOT; // No interaction with world
swave.movetype = MOVETYPE_NONE; // Static item, no movement
swave.think = ai_shockwave_think;
swave.nextthink = time + TIME_MINTICK; // High time loop
swave.ltime = swave.nextthink;
// Spawn particle explosion where impact is located
vpart = 64 + rint(random()*64);
particle_explode(impact, vpart, 0.5, PARTICLE_BURST_FIRE, PARTICLE_BURST_SHOCKWAVE);
};