Files
quakemapping/mod_xj18/my_progs/ai_enemytarget.qc
2020-01-07 11:54:38 +01:00

186 lines
7.2 KiB
Plaintext

/*======================================================================
Flying units maintain the same Z axis as their enemy targets
This makes it impossible to have flying units above the player
because the engine is constantly moving the monster downwards
This system replaces the enemy target with a fake one which
floats above the real enemy target. This fake marker can be
seen if developer 1 is active, otherwise hidden
The engine will track the fake enemytarget and gameplay functions
can carry on using the real enemy target for calculations
All the gameplay functions should use these self.enemy wrappers
defined below to make sure they return the correct enemy!
======================================================================*/
// Detects enemytarget and returns real ENEMY angles
// Returns the correct takedamage flag, useful for function tests
float() SUB_takedEnemyTarget =
{
if (!self.enemy) return -1;
if (self.enemy.classtype == CT_ENEMYTARGET) return (self.enemy.enemy.takedamage);
else return (self.enemy.takedamage);
};
//----------------------------------------------------------------------
// Detects enemytarget and returns for flags bitwise operation
// Returns the correct entity flags, useful for function tests
float(float bitflag) SUB_flagsEnemyTarget =
{
if (!self.enemy) return -1;
if (self.enemy.classtype == CT_ENEMYTARGET) return (self.enemy.enemy.flags & bitflag);
else return (self.enemy.flags & bitflag);
};
//----------------------------------------------------------------------
// Detects enemytarget and returns real ENEMY angles
// Returns the correct entity angles, useful for function tests
vector() SUB_angEnemyTarget =
{
if (!self.enemy) return VEC_ORIGIN;
if (self.enemy.classtype == CT_ENEMYTARGET) return self.enemy.enemy.angles;
else return self.enemy.angles;
};
//----------------------------------------------------------------------
// Detects enemytarget and returns real ENEMY health
// Returns the correct entity health, useful for function tests
float() SUB_healthEnemyTarget =
{
if (!self.enemy) return -1;
if (self.enemy.classtype == CT_ENEMYTARGET) return self.enemy.enemy.health;
else return self.enemy.health;
};
//----------------------------------------------------------------------
// Detects enemytarget and returns real ENEMY origin
// Returns the correct entity origin, useful for function tests
vector() SUB_orgEnemyTarget =
{
if (!self.enemy) return VEC_ORIGIN;
if (self.enemy.classtype == CT_ENEMYTARGET) return self.enemy.enemy.origin;
else return self.enemy.origin;
};
//----------------------------------------------------------------------
// Detects enemytarget and returns the real ENEMY entity
// Returns the correct entity, useful for function tests
entity() SUB_entEnemyTarget =
{
if (!self.enemy) return world;
if (self.enemy.classtype == CT_ENEMYTARGET) return self.enemy.enemy;
else return self.enemy;
};
//----------------------------------------------------------------------
// Returns TRUE if enemytarget system active
// A simple test to see if enemytarget system is active
float() SUB_EnemyTarget =
{
if (!self.enemy) return -1;
if (self.enemy.classtype == CT_ENEMYTARGET) return TRUE;
else return FALSE;
};
//----------------------------------------------------------------------
// Self = monster; Will switch enemy and enemytarget around (restore)
// Its a good idea to do this when the enemytarget is out of sight
// Will restore the enemy to the ground height (useful for doorways)
void() SUB_switchEnemyTarget =
{
if (self.enemytarget.classtype != CT_ENEMYTARGET) return;
self.enemytarget.state = STATE_OFF;
self.enemy = self.enemytarget.enemy;
};
//----------------------------------------------------------------------
// Self = monster; Will remove enemytarget function safely (delete)
// The enemytarget system should take care of itself and safely delete
// This is a brute force version that will make sure, "its dead jim!"
void() SUB_removeEnemyTarget =
{
if (self.enemytarget.classtype != CT_ENEMYTARGET) return;
self.enemytarget.state = STATE_OFF;
self.enemytarget.think = SUB_Remove;
self.enemytarget.nextthink = time + 0.1;
};
//----------------------------------------------------------------------
// Function called all the time when the enemytarget is active
void() SUB_updateEnemyTarget =
{
// OWNER DIED - Safely remove this entity
if (self.owner.health < 1) {
self.state = STATE_OFF;
self.think = SUB_Remove;
self.nextthink = time + 0.1;
}
// TARGET DIED - Stop tracking, wait for another enemy
else if (self.enemy.health < 1) {
self.state = STATE_OFF;
}
// ENEMY ACTIVE - Update position and check for height change
else if (self.state == STATE_ON) {
// Is there any random variance to the Z location?
if (self.waitmin < time && self.wait > 0) {
self.waitmin = time + self.wait + random()*self.wait;
// Check if the enemy is close? Go to maximum height if inside melee range
self.enemydist = range_distance(SUB_entEnemyTarget(), FALSE);
if (self.enemydist < MONAI_ABOVEMELEE) self.view_ofs_z = self.height;
else self.view_ofs_z = (self.height*0.25) + ((self.height*0.75) * random());
}
// Check for solid/sky origin content
self.lip = pointcontents(self.enemy.origin + self.view_ofs);
if (self.lip == CONTENT_SOLID || self.lip == CONTENT_SKY) {
// Traceline upwards to nearest point
traceline(self.enemy.origin, self.enemy.origin + self.view_ofs, TRUE, self);
setorigin(self,trace_endpos);
}
else setorigin(self, self.enemy.origin + self.view_ofs);
// Next update time tick (def=0.1s)
self.think = SUB_updateEnemyTarget;
self.nextthink = time + 0.1;
}
};
//----------------------------------------------------------------------
void(entity targ, float zoffset, float rndtimer) SUB_setupEnemyTarget =
{
// Don't try to get higher than other flying units
// Two monsters with the same abilty will constantly rise up
if (targ.movetype == MOVETYPE_FLY) return;
// Create the enemytarget if one does not exist
if (!self.enemytarget) {
self.enemytarget = spawn();
self.enemytarget.owner = self;
self.enemytarget.classtype = CT_ENEMYTARGET;
// Show enemytarget system in devmode ONLY (dev helper)
if (developer > 0 && !query_configflag(SVR_DEVHELPER)) {
self.enemytarget.mdl = MODEL_BROKEN;
setmodel(self.enemytarget, self.enemytarget.mdl);
}
setsize (self.enemytarget, VEC_ORIGIN, VEC_ORIGIN);
self.enemytarget.movetype = MOVETYPE_NONE;
self.enemytarget.solid = SOLID_NOT;
// Plenty of functions test for health and damage
self.enemytarget.health = LARGE_TIMER;
self.enemytarget.takedamage = DAMAGE_YES;
}
// If attacking a monster stay up high out its way, else move up and down
if (targ.flags & FL_MONSTER) self.enemytarget.wait = 0;
else if (self.bossflag) self.enemytarget.wait = 0;
else self.enemytarget.wait = rndtimer;
// Setup parameters and enable update routine
self.enemytarget.state = STATE_ON;
self.enemytarget.height = self.enemytarget.view_ofs_z = zoffset;
self.enemytarget.enemy = targ;
self.enemytarget.think = SUB_updateEnemyTarget;
self.enemytarget.nextthink = time + 0.1;
};