479 lines
18 KiB
Plaintext
479 lines
18 KiB
Plaintext
/*======================================================================
|
|
Path_Corner FUNCTIONS
|
|
======================================================================*/
|
|
float PATHC_EXACT = 1; // Exact route logic instead of random (def)
|
|
float PATHC_INSTANT = 2; // Instantly move to this path_corner
|
|
float PATHC_REVERSE = 4; // Train direction is reversed at this corner
|
|
float PATHC_NOPAUSE = 8; // Train does not pause on this corner
|
|
|
|
float PATHC_RESET = 15; // Use to reset spawnflags
|
|
|
|
float PATHC_TARGET1 = 16; // Has target been found
|
|
float PATHC_TARGET2 = 32; // Has target2 been found
|
|
float PATHC_BACKLINK = 64; // Backward path route
|
|
float PATHC_DEADEND = 128; // no targets found
|
|
|
|
float STATE_ROUTE1 = 1; // Route 1 is the priority (forward)
|
|
float STATE_ROUTE2 = 2; // Route 2 is the priority (forward)
|
|
float STATE_ROUTE3 = 3; // Route 3 is the priority (backward)
|
|
float STATE_EXACT = 4; // Switch to exact route selection
|
|
float STATE_RANDOM = 5; // Switch to random route selection
|
|
|
|
/*======================================================================
|
|
/*QUAKED path_corner (0.5 0.3 0) (-8 -8 -24) (8 8 48) EXACT INSTANT REVERSE NOPAUSE
|
|
Node based path routing system
|
|
-------- KEYS --------
|
|
targetname : Name of current path corner
|
|
corner_event : Name of target(s) to trigger when used/touched
|
|
corner_route : Change path_corner route (1-3 routes, 4=exact, 5=random)
|
|
corner_switch: Change spawnflags REVERSE (-1=NO, 1=YES, 2=Toggle)
|
|
corner_pause : Change spawnflags NOPAUSE (-1=NO, 1=YES, 2=Toggle)
|
|
state : Starting route (1=forward, 2=alt forward, 3=backward)
|
|
target : Targetname of FORWARD route
|
|
target2 : Alternative FORWARD route
|
|
targetback : Override default BACKWARD route
|
|
wait : fixed amount of time to pause at corner (=-1 stop)
|
|
delay : random amount of time to pause at corner
|
|
alpha : Override default for visual arrows (def=0.35)
|
|
speed : Override default speed of func_train (def=100)
|
|
-------- SPAWNFLAGS --------
|
|
EXACT : Exact route logic (def=random route choices)
|
|
INSTANT : Move instantly to this corner if next destination
|
|
REVERSE : Train direction is reversed for next corner
|
|
NOPAUSE : Train does not pause on this path corner
|
|
-------- NOTES --------
|
|
Node based path routing system
|
|
|
|
======================================================================*/
|
|
entity(string arrowmdl, vector arroworg) path_corner_spawnarrow =
|
|
{
|
|
local entity arrow;
|
|
arrow = spawn();
|
|
arrow.classtype = CT_ATTACHMENT;
|
|
arrow.movetype = MOVETYPE_NONE;
|
|
arrow.solid = SOLID_NOT;
|
|
setmodel(arrow, arrowmdl);
|
|
setorigin(arrow, arroworg);
|
|
setsize(arrow, VEC_ORIGIN, VEC_ORIGIN);
|
|
// If DP engine active remove particle shadow
|
|
if (engine == ENG_DPEXT) arrow.effects = arrow.effects + EF_NOSHADOW;
|
|
|
|
if (!self.alpha) arrow.alpha = 0.35;
|
|
else arrow.alpha = self.alpha;
|
|
return arrow;
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
void() path_corner_updatearrow =
|
|
{
|
|
// Visual AI pathing only active in dev mode
|
|
if (developer == 0) return;
|
|
if (query_configflag(SVR_DEVHELPER)) return;
|
|
if ( !(self.spawnflags & PATHC_TARGET2) ) return;
|
|
|
|
// Switch colour of arrow based on route logic
|
|
if (self.spawnflags & PATHC_TARGET1 && self.state == STATE_ROUTE1) {
|
|
if (self.attachment) self.attachment.skin = 3;
|
|
if (self.attachment2) self.attachment2.skin = 2;
|
|
if (self.attachment3) self.attachment3.skin = 0;
|
|
}
|
|
else if (self.spawnflags & PATHC_TARGET2 && self.state == STATE_ROUTE2) {
|
|
if (self.attachment) self.attachment.skin = 0;
|
|
if (self.attachment2) self.attachment2.skin = 3;
|
|
if (self.attachment3) self.attachment3.skin = 0;
|
|
}
|
|
else if (self.spawnflags & PATHC_BACKLINK && self.state == STATE_ROUTE3) {
|
|
if (self.attachment) self.attachment.skin = 0;
|
|
if (self.attachment2) self.attachment2.skin = 2;
|
|
if (self.attachment3) self.attachment3.skin = 3;
|
|
}
|
|
else {
|
|
// Default - Random logic has no route highlight
|
|
if (self.attachment) self.attachment.skin = 0;
|
|
if (self.attachment2) self.attachment2.skin = 2;
|
|
if (self.attachment3) self.attachment3.skin = 0;
|
|
}
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
void() path_corner_setuparrow =
|
|
{
|
|
// Visual AI pathing only active in dev mode
|
|
if (developer == 0) return;
|
|
if (query_configflag(SVR_DEVHELPER)) return;
|
|
|
|
// Draw back link arrow
|
|
if (self.spawnflags & PATHC_BACKLINK) {
|
|
self.attachment3 = path_corner_spawnarrow( self.mdl, self.oldorigin);
|
|
self.movedir = normalize(self.movetarget3.oldorigin - self.oldorigin);
|
|
self.attachment3.angles = vectoangles(self.movedir);
|
|
}
|
|
// Missing backlink, this is a bad situation!
|
|
// Highlight this problem with a pointing up RED arrow
|
|
else {
|
|
// There is no back link, so use forward target instead
|
|
if (self.spawnflags & PATHC_TARGET1) self.oldenemy = self.movetarget;
|
|
else self.oldenemy = self;
|
|
self.attachment3 = path_corner_spawnarrow( self.mdl, self.oldorigin);
|
|
self.movedir = normalize(self.oldenemy.oldorigin - self.oldorigin);
|
|
self.attachment3.angles = vectoangles(self.movedir);
|
|
self.attachment3.skin = 4;
|
|
makevectors(self.movedir);
|
|
self.attachment3.angles = self.attachment3.angles + vectoangles(v_up);
|
|
}
|
|
|
|
// Draw route choice 1 (target)
|
|
if (self.spawnflags & PATHC_TARGET1) {
|
|
self.attachment = path_corner_spawnarrow( self.headmdl, self.oldorigin);
|
|
self.movedir = normalize(self.movetarget.oldorigin - self.oldorigin);
|
|
self.attachment.angles = vectoangles(self.movedir);
|
|
}
|
|
// Missing forward link (deadend)
|
|
// Highlight this issue with a pointing up RED arrow
|
|
else {
|
|
// There is no forward link, so use back link instead
|
|
if (self.spawnflags & PATHC_BACKLINK) self.oldenemy = self.movetarget3;
|
|
else self.oldenemy = self;
|
|
self.attachment = path_corner_spawnarrow( self.headmdl, self.oldorigin);
|
|
self.movedir = normalize(self.oldenemy.oldorigin - self.oldorigin);
|
|
self.attachment.angles = vectoangles(self.movedir);
|
|
self.attachment.skin = 4;
|
|
makevectors(self.movedir);
|
|
self.attachment.angles = self.attachment.angles + vectoangles(v_up);
|
|
}
|
|
|
|
// Draw route choice 2 (target2)
|
|
if (self.spawnflags & PATHC_TARGET2) {
|
|
self.attachment2 = path_corner_spawnarrow( self.headmdl, self.oldorigin);
|
|
self.movedir = normalize(self.movetarget2.oldorigin - self.oldorigin);
|
|
self.attachment2.angles = vectoangles(self.movedir);
|
|
self.attachment2.skin = 2;
|
|
}
|
|
|
|
// Update arrows if route logic active
|
|
path_corner_updatearrow();
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
void() path_corner_setuptargets =
|
|
{
|
|
local entity fdest;
|
|
|
|
// Cycle through list for TARGET path_corner
|
|
if (self.target != "") {
|
|
fdest = find (world, targetname, self.target);
|
|
while(fdest) {
|
|
if (fdest.classtype == CT_PATHCORNER) {
|
|
// Update (spawnflags) path_corner and save entity
|
|
self.spawnflags = self.spawnflags | PATHC_TARGET1;
|
|
self.movetarget = fdest;
|
|
fdest = world;
|
|
}
|
|
else fdest = find(fdest, targetname, self.target);
|
|
}
|
|
}
|
|
// Cycle through list for TARGET2 path_corner
|
|
if (self.target2 != "") {
|
|
fdest = find (world, targetname, self.target2);
|
|
while(fdest) {
|
|
if (fdest.classtype == CT_PATHCORNER) {
|
|
// Update (spawnflags) path_corner and save entity
|
|
self.spawnflags = self.spawnflags | PATHC_TARGET2;
|
|
self.movetarget2 = fdest;
|
|
fdest = world;
|
|
}
|
|
else fdest = find(fdest, targetname, self.target2);
|
|
}
|
|
}
|
|
|
|
// Has the path corner back link override key been used?
|
|
// If this override string does not exist, weird things will happen
|
|
if (self.targetback != "") {
|
|
fdest = find (world, targetname, self.targetback);
|
|
while(fdest) {
|
|
// Update (spawnflags) path_corner and save entity
|
|
if (fdest.classtype == CT_PATHCORNER) {
|
|
self.spawnflags = self.spawnflags | PATHC_BACKLINK;
|
|
self.movetarget3 = fdest;
|
|
fdest = world;
|
|
}
|
|
else fdest = find(fdest, targetname, self.targetback);
|
|
}
|
|
}
|
|
else {
|
|
// Setup back link name, if none exist use targetname
|
|
self.targetback = self.targetname;
|
|
|
|
// Cycle through list for BACKLINK path_corner
|
|
fdest = find (world, target, self.targetback);
|
|
while(fdest) {
|
|
// Update (spawnflags) path_corner and save entity
|
|
if (fdest.classtype == CT_PATHCORNER) {
|
|
self.spawnflags = self.spawnflags | PATHC_BACKLINK;
|
|
self.movetarget3 = fdest;
|
|
fdest = world;
|
|
}
|
|
else fdest = find(fdest, target, self.targetback);
|
|
}
|
|
}
|
|
|
|
// Double check TARGET2 before declaring a deadend
|
|
if ( !(self.spawnflags & PATHC_BACKLINK) ) {
|
|
fdest = find (world, target2, self.targetback);
|
|
while(fdest) {
|
|
if (fdest.classtype == CT_PATHCORNER) {
|
|
self.spawnflags = self.spawnflags | PATHC_BACKLINK;
|
|
self.movetarget3 = fdest;
|
|
fdest = world;
|
|
}
|
|
else fdest = find(fdest, target2, self.targetback);
|
|
}
|
|
}
|
|
|
|
// Bad situation : Found a path corner with no back-link
|
|
// label as deadend (direction reverse) and circular link
|
|
if ( !(self.spawnflags & PATHC_BACKLINK) ) {
|
|
dprint("\b[PATH_CORNER]\b ("); dprint(self.targetname);
|
|
dprint(") - Deadend found\n");
|
|
if (self.movetarget) self.movetarget3 = self.movetarget;
|
|
else self.movetarget3 = self;
|
|
self.spawnflags = self.spawnflags | PATHC_DEADEND;
|
|
}
|
|
|
|
// Random time interval as this is not important
|
|
// Generate developer arrow models on path_corner
|
|
if (developer > 0) {
|
|
self.nextthink = time + 0.5 + random();
|
|
self.think = path_corner_setuparrow;
|
|
}
|
|
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
void() path_corner_use =
|
|
{
|
|
// Change spawnflags NOPAUSE (-1=NO, 1=YES, 2=Toggle)
|
|
if (other.corner_pause) {
|
|
// Store the parameter locally so it can modified
|
|
self.corner_pause = other.corner_pause;
|
|
if (self.corner_pause == 2) {
|
|
if (self.spawnflags & PATHC_NOPAUSE) self.corner_pause = 1;
|
|
else self.corner_pause = -1;
|
|
}
|
|
// The toggle state uses the on/off code by storing the value
|
|
// locally and modifying it beforehand
|
|
if (self.corner_pause > 0) {
|
|
if (self.spawnflags & PATHC_NOPAUSE)
|
|
self.spawnflags = self.spawnflags - PATHC_NOPAUSE;
|
|
self.wait = -1;
|
|
}
|
|
else if (self.corner_pause < 0) {
|
|
self.spawnflags = self.spawnflags | PATHC_NOPAUSE;
|
|
self.wait = 0;
|
|
}
|
|
}
|
|
|
|
// Change spawnflags REVERSE (-1=NO, 1=YES, 2=Toggle)
|
|
if (other.corner_switch) {
|
|
// Store the parameter locally so it can modified
|
|
self.corner_switch = other.corner_switch;
|
|
if (self.corner_switch == 2) {
|
|
if (self.spawnflags & PATHC_REVERSE) self.corner_switch = -1;
|
|
else self.corner_switch = 1;
|
|
}
|
|
// The toggle state uses the on/off code by storing the value
|
|
// locally and modifying it beforehand
|
|
if (self.corner_switch > 0) {
|
|
self.spawnflags = self.spawnflags | PATHC_REVERSE;
|
|
}
|
|
else if (self.corner_switch < 0) {
|
|
if (self.spawnflags & PATHC_REVERSE)
|
|
self.spawnflags = self.spawnflags - PATHC_REVERSE;
|
|
}
|
|
}
|
|
|
|
// Change speed of train moving towards path_corner (default=100)
|
|
if (other.corner_speed > 0) self.speed = other.corner_speed;
|
|
|
|
// Changing routes?
|
|
if (other.corner_route) {
|
|
// Double check that the routes exist before setting them
|
|
if (other.corner_route == STATE_ROUTE1 &&
|
|
self.spawnflags & PATHC_TARGET1) self.state = STATE_ROUTE1;
|
|
else if (other.corner_route == STATE_ROUTE2 &&
|
|
self.spawnflags & PATHC_TARGET2) self.state = STATE_ROUTE2;
|
|
else if (other.corner_route == STATE_ROUTE3 &&
|
|
self.spawnflags & PATHC_BACKLINK) self.state = STATE_ROUTE3;
|
|
// If changing routing logic, make sure spawnflag is updated
|
|
else if (other.corner_route == STATE_EXACT) {
|
|
self.spawnflags = self.spawnflags | PATHC_EXACT;
|
|
self.state = STATE_ROUTE1;
|
|
}
|
|
// When routing logic is changed, need to reset state
|
|
else if (other.corner_route == STATE_RANDOM) {
|
|
if (self.spawnflags & PATHC_EXACT)
|
|
self.spawnflags = self.spawnflags - PATHC_EXACT;
|
|
self.state = STATE_ROUTE1;
|
|
}
|
|
dprint("Corner ("); dprint(self.targetname);
|
|
dprint(") Route ("); dprint(ftos(self.state));
|
|
dprint(")\n");
|
|
}
|
|
else {
|
|
// Toggle route selection if EXACT routing logic is active
|
|
// and the path corner has multiple routes available
|
|
if (self.spawnflags & PATHC_EXACT && self.spawnflags & PATHC_TARGET2) {
|
|
if (self.state == STATE_ROUTE1) self.state = STATE_ROUTE2;
|
|
else if (self.state == STATE_ROUTE2) self.state = STATE_ROUTE3;
|
|
else self.state = STATE_ROUTE1;
|
|
}
|
|
}
|
|
|
|
// Update arrows if route logic active
|
|
path_corner_updatearrow();
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
void() path_corner_stop =
|
|
{
|
|
other.goalentity = other.movetarget = world;
|
|
other.pausetime = time + LARGE_TIMER;
|
|
other.think = other.th_stand;
|
|
other.nextthink = time + 0.05;
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
void() path_corner_touch =
|
|
{
|
|
if (!(other.flags & FL_MONSTER)) return; // ONLY Monsters can use path corners
|
|
if (other.health < 1) return; // Dead things don't need path guidance
|
|
if (other.enemy) return; // In combat, no time to follow paths
|
|
if (other.movetarget != self) return; // Suppose to follow this corner?
|
|
|
|
// If ogre, play chainsaw drag sound (50% of the time)
|
|
if (other.classtype == CT_MONOGRE || other.classtype == CT_MONHOGRE)
|
|
if (random() > 0.5) sound (other, CHAN_VOICE, "ogre/ogdrag.wav", 1, ATTN_IDLE);
|
|
|
|
// Fire any event targets (activator = monster)
|
|
if (self.corner_event) trigger_strs(self.corner_event,other);
|
|
// Check if this path_corner is a dead end? (no target)
|
|
if (self.spawnflags & PATHC_DEADEND) path_corner_stop();
|
|
// Has this path corner been linked yet? Try again?
|
|
if ( !(self.spawnflags & PATHC_TARGET1) ) path_corner_setuptargets();
|
|
|
|
// Is there a target to move towards?
|
|
if (self.spawnflags & PATHC_TARGET1) {
|
|
// Check for a second random path?
|
|
if (self.spawnflags & PATHC_TARGET2) {
|
|
// Check route logic (only use forward routes)
|
|
if (self.spawnflags & PATHC_EXACT) {
|
|
if (self.state == STATE_ROUTE1) other.goalentity = self.movetarget;
|
|
else other.goalentity = self.movetarget2;
|
|
}
|
|
else {
|
|
// Default = random routes
|
|
if (random() < 0.5) other.goalentity = self.movetarget;
|
|
else other.goalentity = self.movetarget2;
|
|
}
|
|
}
|
|
// Default, 1 path = 1 choice
|
|
else other.goalentity = self.movetarget;
|
|
// Setup goal for monster to move toward
|
|
other.movetarget = other.goalentity;
|
|
|
|
// Does the monster need to pause at current path_corner?
|
|
if (self.wait < 0) path_corner_stop();
|
|
else if (self.wait > 0) {
|
|
other.pausetime = time + self.wait + random()*self.delay;
|
|
other.think = other.th_stand;
|
|
other.nextthink = time + 0.05;
|
|
}
|
|
// no pause, turn and keep walking towards next goal
|
|
else {
|
|
other.ideal_yaw = vectoyaw(other.goalentity.origin - other.origin);
|
|
other.pausetime = 0;
|
|
}
|
|
}
|
|
// No new target to follow, stand around
|
|
else path_corner_stop();
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
//----------------------------------------------------------------------
|
|
void() path_corner =
|
|
{
|
|
// Really need to prevent path corners with no targetname
|
|
// otherwise it will cause problems with linking later
|
|
if (self.targetname == "") {
|
|
dprint("\b[PATH_CORNER]\b Missing targetname!\n");
|
|
spawn_marker(self.origin, SPNMARK_YELLOW);
|
|
remove(self);
|
|
return;
|
|
}
|
|
|
|
// Cache arrow models if devmode active
|
|
if (developer > 0) {
|
|
self.headmdl = MODEL_CORNER1; // Top of Arrow (forward)
|
|
self.mdl = MODEL_CORNER2; // Bottom of Arrow (backward)
|
|
precache_model(self.headmdl);
|
|
precache_model(self.mdl);
|
|
}
|
|
|
|
// Setup time delay at corner ( wait + delay*random() )
|
|
if (self.wait == 0) self.wait = 0;
|
|
if (self.delay <= 0) self.delay = 0;
|
|
|
|
// Reset all path targets/entities, spawnflags (low 4 bits only)
|
|
self.spawnflags = self.spawnflags & PATHC_RESET;
|
|
self.movetarget = self.movetarget2 = self.movetarget3 = world;
|
|
|
|
self.solid = SOLID_TRIGGER; // always touchable
|
|
self.movetype = MOVETYPE_NONE; // Does not move
|
|
self.classtype = CT_PATHCORNER; // Self identification
|
|
setsize (self, '-8 -8 -16', '8 8 16'); // Size of trigger
|
|
self.touch = path_corner_touch; // Useable by monsters
|
|
self.use = path_corner_use; // Used by trigger events
|
|
|
|
// Work out which (random/toggle) route logic is active
|
|
if (self.spawnflags & PATHC_EXACT) {
|
|
// If state is specified and outside range, reset
|
|
if (self.state < STATE_ROUTE1 || self.state > STATE_ROUTE3)
|
|
self.state = STATE_ROUTE1;
|
|
}
|
|
else self.state = STATE_ROUTE1;
|
|
// Cannot start with route 2 (alt forward) if target2 is empty!?!
|
|
if (self.state == STATE_ROUTE2 && self.target2 == "") self.state = STATE_ROUTE1;
|
|
|
|
// Many of the ID maps have path_corners buried in solid architecture
|
|
// which makes it difficult to see where they are and impossible to draw
|
|
// visual arrows on them.
|
|
//
|
|
// This code checks for solid content and keeps nudging the
|
|
// temporary origin upwards 8 units at a time (max 64 units)
|
|
// If it cannot find any empty space above, it will give up and
|
|
// use the original origin point instead
|
|
//
|
|
self.oldorigin = self.origin;
|
|
if (pointcontents(self.oldorigin) == CONTENT_SOLID) {
|
|
self.lip = 8;
|
|
while (self.lip > 0) {
|
|
self.lip = self.lip - 1;
|
|
self.oldorigin = self.oldorigin + '0 0 8';
|
|
if (pointcontents(self.oldorigin) != CONTENT_SOLID)
|
|
self.lip = -1;
|
|
}
|
|
}
|
|
// Origin is still in something solid, reset
|
|
if (self.lip != -1) self.oldorigin = self.origin;
|
|
|
|
// Check for dead end path_corner
|
|
if (self.target == "") self.spawnflags = self.spawnflags | PATHC_DEADEND;
|
|
|
|
// Allow 1 frame for path_corners to spawn
|
|
// Got to start linking before func_train (2nd frame)
|
|
self.think = path_corner_setuptargets;
|
|
self.nextthink = time + 0.1;
|
|
|
|
};
|