mirror of
https://github.com/blendogames/thirtyflightsofloving.git
synced 2024-11-15 00:41:21 +00:00
0d4e872ce9
Added plasma guards (monster_soldier_plasma_re and monster_soldier_plasma_sp) from LM Escape to missionpack DLL. Added Zaero items/weapons to missionpack DLL. Added support for Zaero doors to missionpack DLL. Fixed crash caused by killtargeting sentien (laser edict not freed) in missionpack DLL. Fixed bug with broken Rogue turrets in missionpack DLL. Fixed crash in g_combat.c->M_ReactToDamage() caused by attacker with NULL classname in missionpack DLL.
2126 lines
55 KiB
C
2126 lines
55 KiB
C
#include "g_local.h"
|
|
|
|
#define RFAST -3
|
|
#define RMEDIUM -2
|
|
#define RSLOW -1
|
|
#define STOP 0
|
|
#define SLOW 1
|
|
#define MEDIUM 2
|
|
#define FAST 3
|
|
|
|
// func_tracktrain spawnflags
|
|
#define SF_TRACKTRAIN_NOPITCH 0x0001 // yaw only (maybe roll)
|
|
#define SF_TRACKTRAIN_NOCONTROL 0x0002 // no player control
|
|
#define SF_TRACKTRAIN_ONEWAY 0x0004 // cannot back up
|
|
#define SF_TRACKTRAIN_OTHERMAP 0x0008 // "parent" train resides in another map
|
|
#define SF_TRACKTRAIN_NOHUD 0x0010 // do not display speed on HUD
|
|
#define SF_TRACKTRAIN_INDICATOR 0x0020 // train has an animated speed indicator
|
|
#define SF_TRACKTRAIN_ANIMATED 0x0040 // normal animation
|
|
#define SF_TRACKTRAIN_STARTOFF 0x0080 // starts disabled
|
|
#define SF_TRACKTRAIN_DISABLED 0x1000 // internal use only
|
|
#define SF_TRACKTRAIN_ROLLSPEED 0x2000 // internal use only - roll is a function of speed
|
|
#define SF_TRACKTRAIN_SLOWSTOP 0x4000 // internal use only - train slows to stop after driver death
|
|
#define SF_TRACKTRAIN_ANIMMASK (SF_TRACKTRAIN_INDICATOR | SF_TRACKTRAIN_ANIMATED)
|
|
// path_track spawnflags
|
|
#define SF_PATH_ALTPATH 0x0001 // branch point
|
|
#define SF_PATH_DISABLED 0x0002
|
|
#define SF_PATH_FIREONCE 0x0004 // pathtarget fired only once
|
|
#define SF_PATH_DISABLE_TRAIN 0x0008 // player loses control of train
|
|
#define SF_PATH_ABS_SPEED 0x0010 // speed modifier is absolute rather than a multiplier
|
|
|
|
void tracktrain_next (edict_t *self);
|
|
void tracktrain_reach_dest (edict_t *self);
|
|
|
|
//=============================================================================
|
|
|
|
static float track_AngleMod (float in)
|
|
{
|
|
float out;
|
|
|
|
if (in < 0)
|
|
out = in + 360 * ((int)(in / 360)+1);
|
|
else if (in >= 360)
|
|
out = in - 360 * ((int)(in / 360));
|
|
|
|
return out;
|
|
}
|
|
|
|
static float track_ApproachAngle (float targAngle, float inAngle, float speed)
|
|
{
|
|
float deltaAngle, out;
|
|
|
|
targAngle = track_AngleMod (targAngle);
|
|
out = track_AngleMod (targAngle);
|
|
|
|
deltaAngle = targAngle - out;
|
|
|
|
// fix speed to be non-negative
|
|
if (speed < 0)
|
|
speed *= -1;
|
|
|
|
if (deltaAngle < -180)
|
|
deltaAngle += 360;
|
|
else if (deltaAngle > 180)
|
|
deltaAngle -= 360;
|
|
|
|
if (deltaAngle > speed)
|
|
out += speed;
|
|
else if (deltaAngle < -speed)
|
|
out -= speed;
|
|
else
|
|
out = targAngle;
|
|
|
|
return out;
|
|
}
|
|
|
|
static float track_AngleDistance (float nextAngle, float curAngle)
|
|
{
|
|
float deltaAngle;
|
|
|
|
deltaAngle = nextAngle - curAngle;
|
|
if (deltaAngle < -180)
|
|
deltaAngle += 360;
|
|
else if (deltaAngle > 180)
|
|
deltaAngle -= 360;
|
|
|
|
return deltaAngle;
|
|
}
|
|
|
|
/*=============================================================================
|
|
|
|
PATH_TRACK
|
|
|
|
target: next path_track
|
|
target2: alternate path
|
|
speed: new tracktrain speed
|
|
pathtarget: entity to trigger when touched
|
|
deathtarget: entity to trigger at dead end
|
|
|
|
==============================================================================*/
|
|
|
|
void path_track_use (edict_t *self, edict_t *other, edict_t *activator)
|
|
{
|
|
char *temp;
|
|
|
|
if (self->spawnflags & SF_PATH_ALTPATH)
|
|
{
|
|
temp = self->target;
|
|
self->target = self->target2;
|
|
self->target2 = temp;
|
|
}
|
|
else
|
|
{
|
|
// toggle enabled/disabled
|
|
self->spawnflags ^= SF_PATH_DISABLED;
|
|
}
|
|
}
|
|
|
|
void SP_path_track (edict_t *self)
|
|
{
|
|
if (!self->targetname)
|
|
{
|
|
gi.dprintf ("%s with no targetname at %s\n", self->classname,vtos(self->s.origin));
|
|
G_FreeEdict (self);
|
|
return;
|
|
}
|
|
|
|
self->class_id = ENTITY_PATH_TRACK;
|
|
|
|
self->solid = SOLID_TRIGGER;
|
|
self->use = path_track_use;
|
|
VectorSet (self->mins, -8, -8, -8);
|
|
VectorSet (self->maxs, 8, 8, 8);
|
|
self->svflags |= SVF_NOCLIENT;
|
|
if (!self->count) self->count = -1;
|
|
gi.linkentity (self);
|
|
}
|
|
|
|
/* ============================================================
|
|
|
|
FUNC_TRACKCHANGE
|
|
|
|
height = Travel altitude (viewheight)
|
|
distance = Rotation amount, degrees (moveinfo.distance)
|
|
target = Name of train
|
|
pathtarget = Top path_track
|
|
combattarget = Bottom path_track
|
|
speed = Move/Rotate speed
|
|
"sounds"
|
|
0,1 default
|
|
2-99 custom sound- e.g. plats/pt02_strt.wav, plats/pt02_mid.wav, plats/pt02_end.wav
|
|
|
|
=============================================================*/
|
|
|
|
#define STATE_TOP 0
|
|
#define STATE_MOVEDOWN 1
|
|
#define STATE_BOTTOM 2
|
|
#define STATE_MOVEUP 3
|
|
|
|
#define SF_TRACK_ACTIVATETRAIN 0x0001
|
|
#define SF_TRACK_STARTBOTTOM 0x0008
|
|
#define SF_TRACK_XAXIS 0x0040
|
|
#define SF_TRACK_YAXIS 0x0080
|
|
|
|
|
|
void trackchange_done (edict_t *self)
|
|
{
|
|
edict_t *train = self->target_ent;
|
|
|
|
VectorClear (self->velocity);
|
|
VectorClear (self->avelocity);
|
|
if (self->s.sound && self->moveinfo.sound_end)
|
|
gi.positioned_sound (self->s.origin, self, CHAN_AUTO, self->moveinfo.sound_end, 1, self->attenuation, 0); // was ATTN_NORM
|
|
self->s.sound = 0;
|
|
|
|
if (train)
|
|
{
|
|
VectorClear (train->velocity);
|
|
VectorClear (train->avelocity);
|
|
train->spawnflags &= ~SF_TRACKTRAIN_DISABLED;
|
|
if (self->spawnflags & SF_TRACK_ACTIVATETRAIN)
|
|
{
|
|
train->moveinfo.state = train->moveinfo.prevstate;
|
|
if (train->moveinfo.state && (train->sounds > 0)) {
|
|
train->s.sound = gi.soundindex(va("%sspeed%d.wav", train->source, abs(train->moveinfo.state)));
|
|
#ifdef LOOP_SOUND_ATTENUATION
|
|
train->s.attenuation = train->attenuation;
|
|
#endif
|
|
}
|
|
train->moveinfo.next_speed = train->moveinfo.state * train->moveinfo.speed/3;
|
|
}
|
|
else
|
|
{ // A little redundant, but clear
|
|
train->moveinfo.state = STOP;
|
|
train->moveinfo.next_speed = 0;
|
|
}
|
|
if (self->moveinfo.state == STATE_MOVEUP)
|
|
train->target = self->pathtarget;
|
|
else
|
|
train->target = self->combattarget;
|
|
train->target_ent = G_PickTarget(train->target);
|
|
// If map is constructed properly, train
|
|
// SHOULD be at the target path_track now. But
|
|
// physics may have caused lift to outrun train,
|
|
// so force it:
|
|
VectorCopy (train->target_ent->s.origin, train->s.origin);
|
|
train->s.origin[2] += train->viewheight;
|
|
gi.linkentity (train);
|
|
tracktrain_next (train);
|
|
}
|
|
if (self->moveinfo.state == STATE_MOVEDOWN)
|
|
self->moveinfo.state = STATE_BOTTOM;
|
|
else
|
|
self->moveinfo.state = STATE_TOP;
|
|
gi.linkentity(self);
|
|
}
|
|
|
|
void trackchange_use (edict_t *self, edict_t *other, edict_t *activator)
|
|
{
|
|
float time, tt, tr;
|
|
float tspeed, rspeed;
|
|
|
|
if ((self->moveinfo.state != STATE_TOP) && (self->moveinfo.state != STATE_BOTTOM))
|
|
{
|
|
// already moving
|
|
return;
|
|
}
|
|
|
|
if (self->target)
|
|
{
|
|
edict_t *track;
|
|
|
|
self->target_ent = G_PickTarget(self->target);
|
|
if (self->target_ent)
|
|
{
|
|
// Make sure this sucker is at the path_track it's supposed to
|
|
// be at
|
|
if (self->moveinfo.state == STATE_BOTTOM)
|
|
track = G_PickTarget(self->combattarget);
|
|
else
|
|
track = G_PickTarget(self->pathtarget);
|
|
if (!track || (self->target_ent->target_ent != track))
|
|
self->target_ent = NULL;
|
|
else
|
|
{
|
|
vec3_t v;
|
|
VectorSubtract (track->s.origin, self->target_ent->s.origin, v);
|
|
if (VectorLength(v) > self->target_ent->moveinfo.distance)
|
|
self->target_ent = NULL;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
self->target_ent = NULL;
|
|
|
|
if (self->target_ent)
|
|
self->target_ent->s.sound = 0;
|
|
|
|
// Adjust speed so that "distance" rotation and "height" movement are achieved
|
|
// simultaneously.
|
|
tt = fabs(self->viewheight) / self->speed;
|
|
tr = fabs(self->moveinfo.distance) / self->speed;
|
|
time = max(tt,tr);
|
|
time = 0.1 * ((int)(10.*time-0.5)+1);
|
|
tspeed = -self->viewheight / time;
|
|
rspeed = self->moveinfo.distance / time;
|
|
if (self->moveinfo.state == STATE_BOTTOM)
|
|
{
|
|
tspeed = -tspeed;
|
|
rspeed = -rspeed;
|
|
}
|
|
VectorClear (self->velocity);
|
|
VectorClear (self->avelocity);
|
|
if (self->spawnflags & SF_TRACK_XAXIS)
|
|
self->velocity[0] = tspeed;
|
|
else if (self->spawnflags & SF_TRACK_YAXIS)
|
|
self->velocity[1] = tspeed;
|
|
else
|
|
self->velocity[2] = tspeed;
|
|
|
|
VectorScale (self->movedir, rspeed, self->avelocity);
|
|
|
|
if (self->target_ent)
|
|
{
|
|
VectorCopy (self->velocity, self->target_ent->velocity);
|
|
VectorCopy (self->avelocity, self->target_ent->avelocity);
|
|
self->target_ent->spawnflags |= SF_TRACKTRAIN_DISABLED;
|
|
gi.linkentity (self->target_ent);
|
|
}
|
|
|
|
if (self->moveinfo.state == STATE_TOP)
|
|
self->moveinfo.state = STATE_MOVEDOWN;
|
|
else
|
|
self->moveinfo.state = STATE_MOVEUP;
|
|
|
|
if (self->moveinfo.sound_start)
|
|
gi.positioned_sound (self->s.origin, self, CHAN_AUTO, self->moveinfo.sound_start, 1, self->attenuation, 0); // was ATTN_NORM
|
|
self->s.sound = self->moveinfo.sound_middle;
|
|
#ifdef LOOP_SOUND_ATTENUATION
|
|
self->s.attenuation = self->attenuation;
|
|
#endif
|
|
|
|
self->think = trackchange_done;
|
|
self->nextthink = level.time + time;
|
|
gi.linkentity(self);
|
|
}
|
|
|
|
void SP_func_trackchange (edict_t *self)
|
|
{
|
|
self->class_id = ENTITY_FUNC_TRACKCHANGE;
|
|
|
|
self->movetype = MOVETYPE_PUSH;
|
|
VectorClear (self->s.angles);
|
|
|
|
// set the axis of rotation
|
|
VectorClear (self->movedir);
|
|
if (self->spawnflags & SF_TRACK_XAXIS)
|
|
self->movedir[2] = 1.0;
|
|
else if (self->spawnflags & SF_TRACK_YAXIS)
|
|
self->movedir[0] = 1.0;
|
|
else // Z_AXIS
|
|
self->movedir[1] = 1.0;
|
|
|
|
// Rotation
|
|
if (self->distance)
|
|
st.distance = self->distance;
|
|
self->moveinfo.distance = st.distance;
|
|
|
|
// Travel height
|
|
if (self->height)
|
|
st.height = self->height;
|
|
self->viewheight = st.height;
|
|
|
|
// Max rotation/translation speed
|
|
if (!self->speed)
|
|
self->speed = 100;
|
|
|
|
VectorCopy (self->s.origin, self->pos1);
|
|
VectorCopy (self->pos1, self->pos2);
|
|
if (self->spawnflags & SF_TRACK_XAXIS)
|
|
self->pos2[0] -= self->viewheight;
|
|
else if (self->spawnflags & SF_TRACK_YAXIS)
|
|
self->pos2[1] -= self->viewheight;
|
|
else
|
|
self->pos2[2] -= self->viewheight;
|
|
|
|
self->solid = SOLID_BSP;
|
|
gi.setmodel (self, self->model);
|
|
self->use = trackchange_use;
|
|
|
|
self->postthink = train_move_children; //Knightmare- supports movewith
|
|
|
|
// bottom starters:
|
|
if (self->spawnflags & SF_TRACK_STARTBOTTOM)
|
|
{
|
|
vec3_t temp;
|
|
VectorCopy (self->pos1, temp);
|
|
VectorCopy (self->pos2, self->pos1);
|
|
VectorCopy (temp, self->pos2);
|
|
VectorCopy (self->pos1, self->s.origin);
|
|
VectorMA (self->s.angles, self->moveinfo.distance, self->movedir, self->s.angles);
|
|
self->moveinfo.state = STATE_BOTTOM;
|
|
}
|
|
else
|
|
self->moveinfo.state = STATE_TOP;
|
|
|
|
if (self->sounds > 1 && self->sounds < 100) // custom sounds
|
|
{
|
|
self->moveinfo.sound_start = gi.soundindex (va("plats/pt%02i_strt.wav", self->sounds));
|
|
self->moveinfo.sound_middle = gi.soundindex (va("plats/pt%02i_mid.wav", self->sounds));
|
|
self->moveinfo.sound_end = gi.soundindex (va("plats/pt%02i_end.wav", self->sounds));
|
|
}
|
|
else
|
|
{
|
|
self->moveinfo.sound_start = gi.soundindex ("plats/pt1_strt.wav");
|
|
self->moveinfo.sound_middle = gi.soundindex ("plats/pt1_mid.wav");
|
|
self->moveinfo.sound_end = gi.soundindex ("plats/pt1_end.wav");
|
|
}
|
|
|
|
if (self->attenuation <= 0)
|
|
self->attenuation = ATTN_IDLE;
|
|
|
|
gi.linkentity(self);
|
|
|
|
}
|
|
|
|
|
|
/*=====================================================================================
|
|
func_tracktrain
|
|
|
|
target first path_track stop
|
|
dmg damage applied to blocker
|
|
speed max. speed, default=64
|
|
sounds currently unused
|
|
distance wheelbase, determines turn rate. default=50
|
|
height height of origin above path_tracks, default=4
|
|
roll roll angle while turning, default=0
|
|
*/
|
|
void tracktrain_think (edict_t *self);
|
|
void tracktrain_drive (edict_t *train, edict_t *other )
|
|
{
|
|
vec3_t angles, offset;
|
|
vec3_t f1, l1, u1;
|
|
vec3_t forward, left;
|
|
|
|
if (!(other->svflags & SVF_MONSTER))
|
|
return;
|
|
|
|
if (train->spawnflags & (SF_TRACKTRAIN_NOCONTROL | SF_TRACKTRAIN_STARTOFF))
|
|
return;
|
|
|
|
// See if monster is in driving position
|
|
VectorCopy (train->s.angles, angles);
|
|
VectorNegate (angles, angles);
|
|
AngleVectors (angles, f1, l1, u1);
|
|
|
|
VectorSubtract (other->s.origin, train->s.origin, offset);
|
|
VectorScale (f1, offset[0], f1);
|
|
VectorScale (l1, -offset[1], l1);
|
|
VectorScale (u1, offset[2], u1);
|
|
VectorCopy (f1, offset);
|
|
VectorAdd (offset, l1, offset);
|
|
VectorAdd (offset, u1, offset);
|
|
|
|
// Relax the constraints on driving position just a bit for monsters
|
|
|
|
if (offset[0] < train->bleft[0] - 16)
|
|
return;
|
|
if (offset[1] < train->bleft[1] - 16)
|
|
return;
|
|
if (offset[2] < train->bleft[2] - 16)
|
|
return;
|
|
if (offset[0] > train->tright[0] + 16)
|
|
return;
|
|
if (offset[1] > train->tright[1] + 16)
|
|
return;
|
|
if (offset[2] > train->tright[2] + 16)
|
|
return;
|
|
|
|
train->owner = other;
|
|
other->vehicle = train;
|
|
|
|
// Store the offset and later keep driver at same relative position
|
|
// (with height adjustments for pitch)
|
|
AngleVectors (train->s.angles, forward, left, NULL);
|
|
VectorSubtract (other->s.origin, train->s.origin, train->offset);
|
|
VectorScale (forward, train->offset[0], f1);
|
|
VectorScale (left, train->offset[1], l1);
|
|
VectorCopy (f1, train->offset);
|
|
VectorAdd (train->offset, l1, train->offset);
|
|
train->offset[1] = -train->offset[1];
|
|
train->offset[2] = other->s.origin[2] - train->s.origin[2];
|
|
gi.linkentity(other);
|
|
gi.linkentity(train);
|
|
|
|
other->monsterinfo.pausetime = level.time + 100000000;
|
|
other->monsterinfo.aiflags |= AI_STAND_GROUND;
|
|
other->monsterinfo.stand (other);
|
|
|
|
train->moveinfo.state = FAST;
|
|
train->moveinfo.next_speed = train->moveinfo.speed;
|
|
if (train->sounds) {
|
|
train->s.sound = gi.soundindex(va("%sspeed%d.wav", train->source, abs(train->moveinfo.state)));
|
|
#ifdef LOOP_SOUND_ATTENUATION
|
|
train->s.attenuation = train->attenuation;
|
|
#endif
|
|
}
|
|
else
|
|
train->s.sound = 0;
|
|
train->think = tracktrain_think;
|
|
train->think(train);
|
|
|
|
}
|
|
|
|
void tracktrain_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
|
|
{
|
|
if (self->deathtarget)
|
|
{
|
|
self->target = self->deathtarget;
|
|
G_UseTargets (self, attacker);
|
|
}
|
|
BecomeExplosion1 (self);
|
|
}
|
|
|
|
void tracktrain_disengage (edict_t *train)
|
|
{
|
|
edict_t *driver;
|
|
|
|
driver = train->owner;
|
|
if (!driver) return;
|
|
|
|
if (driver->client)
|
|
{
|
|
vec3_t forward, left, up, f1, l1, u1;
|
|
|
|
driver->movetype = MOVETYPE_WALK;
|
|
AngleVectors (train->s.angles, forward, left, up);
|
|
VectorScale (forward, train->offset[0], f1);
|
|
VectorScale (left, -train->offset[1], l1);
|
|
VectorScale (up, train->offset[2], u1);
|
|
VectorAdd (train->s.origin, f1, driver->s.origin);
|
|
VectorAdd (driver->s.origin, l1, driver->s.origin);
|
|
VectorAdd (driver->s.origin, u1, driver->s.origin);
|
|
driver->s.origin[2] += 16 * ( fabs(up[0]) + fabs(up[1]) );
|
|
VectorCopy (train->velocity, driver->velocity);
|
|
driver->client->vehicle_framenum = level.framenum;
|
|
// turn ON client side prediction for this player
|
|
driver->client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION;
|
|
}
|
|
else
|
|
{
|
|
if (train->moveinfo.state != STOP)
|
|
{
|
|
train->spawnflags |= SF_TRACKTRAIN_SLOWSTOP;
|
|
train->moveinfo.state = STOP;
|
|
train->moveinfo.next_speed = 0;
|
|
train->s.sound = gi.soundindex(va("%sspeed1.wav",train->source));
|
|
#ifdef LOOP_SOUND_ATTENUATION
|
|
train->s.attenuation = train->attenuation;
|
|
#endif
|
|
}
|
|
|
|
driver->movetype = MOVETYPE_STEP;
|
|
if (driver->health > 0)
|
|
VectorCopy (train->velocity, driver->velocity);
|
|
else
|
|
VectorClear (driver->velocity);
|
|
driver->monsterinfo.pausetime = 0;
|
|
driver->monsterinfo.aiflags &= ~AI_STAND_GROUND;
|
|
}
|
|
gi.linkentity (driver);
|
|
|
|
train->owner = NULL;
|
|
driver->vehicle = NULL;
|
|
}
|
|
|
|
void tracktrain_hide (edict_t *self)
|
|
{
|
|
self->solid = SOLID_NOT;
|
|
self->svflags |= SVF_NOCLIENT;
|
|
gi.linkentity(self);
|
|
}
|
|
|
|
void tracktrain_think (edict_t *self)
|
|
{
|
|
float distance, speed, time;
|
|
float yaw, pitch;
|
|
vec3_t forward, left, up, f1, l1, u1, v;
|
|
int i;
|
|
edict_t *ent;
|
|
|
|
if (self->spawnflags & SF_TRACKTRAIN_OTHERMAP)
|
|
{
|
|
if ( (self->spawnflags & SF_TRACKTRAIN_INDICATOR) && !(self->spawnflags & SF_TRACKTRAIN_ANIMATED))
|
|
self->s.frame = 6;
|
|
else
|
|
self->s.effects &= ~(EF_ANIM_ALL | EF_ANIM_ALLFAST);
|
|
self->spawnflags |= SF_TRACKTRAIN_DISABLED;
|
|
self->think = tracktrain_hide;
|
|
self->nextthink = level.time + FRAMETIME;
|
|
VectorClear (self->velocity);
|
|
VectorClear (self->avelocity);
|
|
self->moveinfo.state = self->moveinfo.prevstate = STOP;
|
|
self->s.sound = 0;
|
|
self->moveinfo.current_speed = 0;
|
|
self->owner = NULL;
|
|
gi.linkentity(self);
|
|
return;
|
|
}
|
|
|
|
self->nextthink = level.time + FRAMETIME;
|
|
|
|
if (!self->owner && (self->spawnflags & SF_TRACKTRAIN_DISABLED))
|
|
return;
|
|
|
|
if (self->spawnflags & SF_TRACKTRAIN_ANIMATED)
|
|
{
|
|
if (self->moveinfo.state)
|
|
{
|
|
if (self->spawnflags & SF_TRACKTRAIN_INDICATOR)
|
|
self->s.effects |= EF_ANIM_ALLFAST;
|
|
else
|
|
self->s.effects |= EF_ANIM_ALL;
|
|
}
|
|
else
|
|
self->s.effects &= ~(EF_ANIM_ALL | EF_ANIM_ALLFAST);
|
|
}
|
|
else if (self->spawnflags & SF_TRACKTRAIN_INDICATOR)
|
|
{
|
|
if ((self->moveinfo.state == STOP) && !self->owner)
|
|
{
|
|
if (self->spawnflags & SF_TRACKTRAIN_STARTOFF)
|
|
self->s.frame = 14;
|
|
else
|
|
self->s.frame = 6;
|
|
}
|
|
else if ((level.framenum % 5) == 0)
|
|
self->s.frame = (self->moveinfo.state - RFAST)*2 + (1 - (self->s.frame % 2));
|
|
}
|
|
|
|
AngleVectors (self->s.angles, forward, left, up);
|
|
|
|
if (!(self->spawnflags & SF_TRACKTRAIN_DISABLED))
|
|
{
|
|
VectorCopy (self->velocity, v);
|
|
speed = VectorLength (v);
|
|
if (self->moveinfo.state < STOP)
|
|
speed = -speed;
|
|
self->moveinfo.current_speed = speed;
|
|
if (self->moveinfo.state != STOP)
|
|
self->moveinfo.prevstate = self->moveinfo.state;
|
|
}
|
|
|
|
if (self->owner)
|
|
{
|
|
edict_t *driver = self->owner;
|
|
|
|
// ... then we have a driver
|
|
if ((driver->health <= 0) || (driver->movetype == MOVETYPE_NOCLIP))
|
|
{
|
|
tracktrain_disengage(self);
|
|
return;
|
|
}
|
|
if (driver->client)
|
|
{
|
|
if (driver->client->use)
|
|
{
|
|
// if he's pressing the use key, and he didn't just
|
|
// get on or off, disengage
|
|
if (level.framenum - driver->client->vehicle_framenum > 2)
|
|
{
|
|
VectorCopy (self->velocity, driver->velocity);
|
|
tracktrain_disengage(self);
|
|
return;
|
|
}
|
|
}
|
|
if ( (driver->client->ucmd.sidemove < -199) || (driver->client->ucmd.sidemove > 199) || (driver->client->ucmd.upmove > 0) )
|
|
{
|
|
VectorCopy (self->velocity, driver->velocity);
|
|
tracktrain_disengage(self);
|
|
return;
|
|
}
|
|
}
|
|
if (!(self->spawnflags & SF_TRACKTRAIN_DISABLED))
|
|
{
|
|
if (driver->client)
|
|
{
|
|
if ((driver->client->ucmd.forwardmove > -199) && (driver->client->ucmd.forwardmove < 199))
|
|
self->moveinfo.wait = 0;
|
|
else if (!self->moveinfo.wait)
|
|
{
|
|
if (driver->client->ucmd.forwardmove > 0)
|
|
{
|
|
if (self->moveinfo.state < FAST)
|
|
{
|
|
self->moveinfo.state++;
|
|
self->moveinfo.next_speed = self->moveinfo.state * self->moveinfo.speed/3;
|
|
self->moveinfo.wait = 1;
|
|
if ((self->spawnflags & SF_TRACKTRAIN_ANIMMASK) == SF_TRACKTRAIN_INDICATOR)
|
|
self->s.frame = (self->moveinfo.state - RFAST)*2 + (1 - (self->s.frame % 2));
|
|
if (self->moveinfo.state && (self->sounds > 0)) {
|
|
self->s.sound = gi.soundindex(va("%sspeed%d.wav", self->source, abs(self->moveinfo.state)));
|
|
#ifdef LOOP_SOUND_ATTENUATION
|
|
self->s.attenuation = self->attenuation;
|
|
#endif
|
|
}
|
|
else
|
|
self->s.sound = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( (self->moveinfo.state > STOP) ||
|
|
((self->moveinfo.state > RFAST) && !(self->spawnflags & SF_TRACKTRAIN_ONEWAY)))
|
|
{
|
|
self->moveinfo.state--;
|
|
self->moveinfo.next_speed = self->moveinfo.state * self->moveinfo.speed/3;
|
|
self->moveinfo.wait = 1;
|
|
if ((self->spawnflags & SF_TRACKTRAIN_ANIMMASK) == SF_TRACKTRAIN_INDICATOR)
|
|
self->s.frame = (self->moveinfo.state - RFAST)*2 + (1 - (self->s.frame % 2));
|
|
if (self->moveinfo.state && (self->sounds > 0)) {
|
|
self->s.sound = gi.soundindex(va("%sspeed%d.wav",self->source,abs(self->moveinfo.state)));
|
|
#ifdef LOOP_SOUND_ATTENUATION
|
|
self->s.attenuation = self->attenuation;
|
|
#endif
|
|
}
|
|
else
|
|
self->s.sound = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Driver is monster.
|
|
if (driver->enemy && driver->enemy->inuse)
|
|
{
|
|
qboolean vis;
|
|
|
|
vis = visible(driver,driver->enemy);
|
|
|
|
if (vis)
|
|
self->monsterinfo.search_time = 0;
|
|
|
|
if (self->monsterinfo.search_time == 0)
|
|
{
|
|
float dot, r;
|
|
vec3_t vec;
|
|
|
|
if (vis)
|
|
VectorSubtract (driver->enemy->s.origin, driver->s.origin, vec);
|
|
else
|
|
VectorSubtract (driver->monsterinfo.last_sighting, driver->s.origin, vec);
|
|
|
|
r = VectorNormalize (vec);
|
|
dot = DotProduct(vec,forward);
|
|
if ((r > 2000) && (dot < 0))
|
|
{
|
|
self->moveinfo.state = -self->moveinfo.state;
|
|
self->moveinfo.next_speed = self->moveinfo.state * self->moveinfo.speed/3;
|
|
self->monsterinfo.search_time = level.time;
|
|
tracktrain_next(self);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Check for change in direction
|
|
if ( self->moveinfo.prevstate < STOP && self->moveinfo.state > STOP )
|
|
{
|
|
self->moveinfo.prevstate = self->moveinfo.state;
|
|
tracktrain_next(self);
|
|
return;
|
|
}
|
|
else if (self->moveinfo.prevstate > STOP && self->moveinfo.state < STOP)
|
|
{
|
|
self->moveinfo.prevstate = self->moveinfo.state;
|
|
tracktrain_next(self);
|
|
return;
|
|
}
|
|
|
|
if (self->moveinfo.current_speed < self->moveinfo.next_speed)
|
|
{
|
|
speed = self->moveinfo.current_speed + self->moveinfo.accel/10;
|
|
if (speed > self->moveinfo.next_speed) speed = self->moveinfo.next_speed;
|
|
}
|
|
else if (self->moveinfo.current_speed > self->moveinfo.next_speed)
|
|
{
|
|
speed = self->moveinfo.current_speed - self->moveinfo.decel/10;
|
|
if (speed < self->moveinfo.next_speed) speed = self->moveinfo.next_speed;
|
|
}
|
|
|
|
VectorSubtract (self->moveinfo.end_origin, self->s.origin, v);
|
|
distance = VectorLength (v);
|
|
if (speed != 0)
|
|
{
|
|
time = distance/fabs(speed);
|
|
time = 0.1 * ((int)(10*time - 0.5)+1);
|
|
if ( (time > 0) && (distance > 0) )
|
|
speed = distance/time;
|
|
}
|
|
else
|
|
time = 100000;
|
|
VectorNormalize (v);
|
|
VectorScale (v, fabs(speed), self->velocity);
|
|
|
|
// gi.dprintf("distance to %s=%g, time=%g\n",
|
|
// self->target_ent->targetname,distance,time);
|
|
|
|
gi.linkentity(self);
|
|
}
|
|
|
|
// Set driver velocity, position, and angles
|
|
VectorCopy (self->velocity, driver->velocity);
|
|
|
|
VectorScale (forward, self->offset[0], f1);
|
|
VectorScale (left, -self->offset[1], l1);
|
|
VectorScale (up, self->offset[2], u1);
|
|
VectorAdd (self->s.origin, f1, driver->s.origin);
|
|
VectorAdd (driver->s.origin, l1, driver->s.origin);
|
|
VectorAdd (driver->s.origin, u1, driver->s.origin);
|
|
driver->s.origin[2] += 16 * ( fabs(up[0]) + fabs(up[1]) );
|
|
|
|
yaw = self->avelocity[YAW]*FRAMETIME;
|
|
if (yaw != 0)
|
|
{
|
|
driver->s.angles[YAW] += yaw;
|
|
if (driver->client)
|
|
{
|
|
driver->client->ps.pmove.delta_angles[YAW] += ANGLE2SHORT(yaw);
|
|
driver->client->ps.viewangles[YAW] += yaw;
|
|
}
|
|
}
|
|
pitch = self->avelocity[PITCH]*FRAMETIME;
|
|
if (pitch != 0)
|
|
{
|
|
float delta_yaw;
|
|
|
|
delta_yaw = driver->s.angles[YAW] - self->s.angles[YAW];
|
|
delta_yaw *= M_PI / 180.;
|
|
pitch *= cos(delta_yaw);
|
|
if (driver->client)
|
|
{
|
|
driver->client->ps.pmove.delta_angles[PITCH] += ANGLE2SHORT(pitch);
|
|
driver->client->ps.viewangles[PITCH] += pitch;
|
|
}
|
|
}
|
|
if ((self->moveinfo.state != STOP) || (yaw != 0) || (pitch != 0))
|
|
if (driver->client)
|
|
driver->client->ps.pmove.pm_type = PM_FREEZE;
|
|
|
|
gi.linkentity(driver);
|
|
|
|
}
|
|
else if (self->spawnflags & (SF_TRACKTRAIN_NOCONTROL | SF_TRACKTRAIN_STARTOFF))
|
|
{
|
|
// No driver, either can't be controlled or is "off"
|
|
|
|
if (!(self->spawnflags & SF_TRACKTRAIN_DISABLED))
|
|
{
|
|
self->moveinfo.next_speed = self->moveinfo.state * self->moveinfo.speed/3;
|
|
|
|
if (self->moveinfo.current_speed < self->moveinfo.next_speed)
|
|
{
|
|
speed = self->moveinfo.current_speed + self->moveinfo.accel/10;
|
|
if (speed > self->moveinfo.next_speed) speed = self->moveinfo.next_speed;
|
|
}
|
|
else if (self->moveinfo.current_speed > self->moveinfo.next_speed)
|
|
{
|
|
speed = self->moveinfo.current_speed - self->moveinfo.decel/10;
|
|
if (speed < self->moveinfo.next_speed) speed = self->moveinfo.next_speed;
|
|
}
|
|
|
|
if (speed != 0)
|
|
{
|
|
VectorSubtract (self->moveinfo.end_origin, self->s.origin, v);
|
|
distance = VectorLength (v);
|
|
time = distance/fabs(speed);
|
|
time = 0.1 * ((int)(10*time - 0.5)+1);
|
|
if ( (time > 0) && (distance > 0) )
|
|
speed = distance/time;
|
|
VectorNormalize (v);
|
|
VectorScale (v, fabs(speed), self->velocity);
|
|
gi.linkentity (self);
|
|
}
|
|
|
|
if ( !(self->spawnflags & SF_TRACKTRAIN_NOCONTROL) &&
|
|
(self->spawnflags & SF_TRACKTRAIN_STARTOFF) &&
|
|
self->viewmessage )
|
|
{
|
|
vec3_t angles, offset;
|
|
|
|
// Check for player entering bleft/tright field of train
|
|
VectorCopy (self->s.angles, angles);
|
|
VectorNegate (angles, angles);
|
|
AngleVectors (angles, f1, l1, u1);
|
|
for (i=1, ent=&g_edicts[1] ; i<=maxclients->value ; i++, ent++)
|
|
{
|
|
if (!ent->inuse) continue;
|
|
if (ent->movetype == MOVETYPE_NOCLIP) continue;
|
|
VectorSubtract (ent->s.origin, self->s.origin, offset);
|
|
VectorScale (f1, offset[0], f1);
|
|
VectorScale (l1,-offset[1], l1);
|
|
VectorScale (u1, offset[2], u1);
|
|
VectorCopy (f1, offset);
|
|
VectorAdd (offset, l1, offset);
|
|
VectorAdd (offset, u1, offset);
|
|
if (offset[0] < self->bleft[0])
|
|
continue;
|
|
if (offset[1] < self->bleft[1])
|
|
continue;
|
|
if (offset[2] < self->bleft[2])
|
|
continue;
|
|
if (offset[0] > self->tright[0])
|
|
continue;
|
|
if (offset[1] > self->tright[1])
|
|
continue;
|
|
if (offset[2] > self->tright[2])
|
|
continue;
|
|
|
|
gi.centerprintf(ent,"%s",self->viewmessage);
|
|
self->viewmessage = NULL;
|
|
}
|
|
}
|
|
|
|
if (!speed)
|
|
{
|
|
if (self->viewmessage)
|
|
time = 100000;
|
|
else
|
|
{
|
|
VectorClear (self->avelocity);
|
|
VectorClear (self->avelocity);
|
|
self->nextthink = 0;
|
|
// if (self->movewith_next && (self->movewith_next->movewith_ent == self))
|
|
// set_child_movement(self);
|
|
gi.linkentity(self);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// No driver, CAN be controlled and isn't currently turned off
|
|
//
|
|
self->moveinfo.next_speed = self->moveinfo.state * self->moveinfo.speed/3;
|
|
if (self->moveinfo.current_speed < self->moveinfo.next_speed)
|
|
{
|
|
if (self->spawnflags & SF_TRACKTRAIN_SLOWSTOP)
|
|
speed = self->moveinfo.current_speed + self->moveinfo.accel/25;
|
|
else
|
|
speed = self->moveinfo.current_speed + self->moveinfo.accel/10;
|
|
if (speed > self->moveinfo.next_speed) speed = self->moveinfo.next_speed;
|
|
}
|
|
else if (self->moveinfo.current_speed > self->moveinfo.next_speed)
|
|
{
|
|
if (self->spawnflags & SF_TRACKTRAIN_SLOWSTOP)
|
|
speed = self->moveinfo.current_speed - self->moveinfo.decel/25;
|
|
else
|
|
speed = self->moveinfo.current_speed - self->moveinfo.decel/10;
|
|
if (speed < self->moveinfo.next_speed) speed = self->moveinfo.next_speed;
|
|
}
|
|
|
|
if ( speed != 0 )
|
|
{
|
|
VectorSubtract (self->moveinfo.end_origin, self->s.origin, v);
|
|
distance = VectorNormalize (v);
|
|
time = distance/fabs(speed);
|
|
time = 0.1 * ((int)(10*time - 0.5)+1);
|
|
if ( (time > 0) && (distance > 0) )
|
|
speed = distance/time;
|
|
VectorScale (v, fabs(speed), self->velocity);
|
|
gi.linkentity(self);
|
|
}
|
|
else if (self->moveinfo.current_speed != 0)
|
|
{
|
|
VectorClear (self->velocity);
|
|
self->s.sound = 0;
|
|
gi.linkentity(self);
|
|
time = 100000;
|
|
}
|
|
else
|
|
{
|
|
self->spawnflags &= ~SF_TRACKTRAIN_SLOWSTOP;
|
|
time = 100000;
|
|
}
|
|
|
|
if (!(self->spawnflags & SF_TRACKTRAIN_NOCONTROL))
|
|
{
|
|
vec3_t angles, offset;
|
|
|
|
// Check if a player is in driving position (monsters handled elsewhere)
|
|
|
|
VectorCopy (self->s.angles, angles);
|
|
VectorNegate (angles, angles);
|
|
AngleVectors (angles, f1, l1, u1);
|
|
|
|
// find a player
|
|
for (i=1, ent=&g_edicts[1] ; i<=maxclients->value ; i++, ent++)
|
|
{
|
|
if (!ent->inuse) continue;
|
|
if (ent->movetype == MOVETYPE_NOCLIP) continue;
|
|
if (!ent->client->use && !self->message) continue;
|
|
if (level.framenum - ent->client->vehicle_framenum <= 2) continue;
|
|
|
|
VectorSubtract (ent->s.origin, self->s.origin, offset);
|
|
VectorScale (f1, offset[0], f1);
|
|
VectorScale (l1, -offset[1], l1);
|
|
VectorScale (u1, offset[2], u1);
|
|
VectorCopy (f1, offset);
|
|
VectorAdd (offset, l1, offset);
|
|
VectorAdd (offset, u1, offset);
|
|
// gi.dprintf("offset=%g %g %g\n",offset[0],offset[1],offset[2]);
|
|
if (offset[0] < self->bleft[0])
|
|
continue;
|
|
if (offset[1] < self->bleft[1])
|
|
continue;
|
|
if (offset[2] < self->bleft[2])
|
|
continue;
|
|
if (offset[0] > self->tright[0])
|
|
continue;
|
|
if (offset[1] > self->tright[1])
|
|
continue;
|
|
if (offset[2] > self->tright[2])
|
|
continue;
|
|
|
|
if (self->message)
|
|
{
|
|
gi.centerprintf(ent,"%s",self->message);
|
|
self->message = NULL;
|
|
}
|
|
if (!ent->client->use) continue;
|
|
|
|
// Got a driver!
|
|
ent->client->vehicle_framenum = level.framenum;
|
|
self->owner = ent;
|
|
ent->vehicle = self;
|
|
// turn off client side prediction for this player
|
|
ent->client->ps.pmove.pm_flags |= PMF_NO_PREDICTION;
|
|
|
|
// Store the offset and later keep driver at same relative position
|
|
// (with height adjustments for pitch)
|
|
VectorSubtract (ent->s.origin, self->s.origin, self->offset);
|
|
VectorScale (forward, self->offset[0], f1);
|
|
VectorScale (left, self->offset[1] ,l1);
|
|
VectorCopy (f1, self->offset);
|
|
VectorAdd (self->offset, l1, self->offset);
|
|
self->offset[1] = -self->offset[1];
|
|
self->offset[2] = ent->s.origin[2] - self->s.origin[2];
|
|
gi.linkentity(ent);
|
|
self->moveinfo.wait = 1;
|
|
gi.linkentity(self);
|
|
}
|
|
}
|
|
}
|
|
//if (self->movewith_next && (self->movewith_next->movewith_ent == self))
|
|
// set_child_movement(self);
|
|
|
|
if ( (time < 1.5*FRAMETIME) && !(self->spawnflags & SF_TRACKTRAIN_DISABLED))
|
|
self->think = tracktrain_reach_dest;
|
|
}
|
|
|
|
void tracktrain_blocked (edict_t *self, edict_t *other)
|
|
{
|
|
vec3_t dir;
|
|
int knockback;
|
|
|
|
// Correct owner's velocity
|
|
if (self->owner)
|
|
{
|
|
edict_t *driver = self->owner;
|
|
vec3_t forward, left, up;
|
|
vec3_t f1, l1, u1;
|
|
VectorCopy (self->velocity, driver->velocity);
|
|
AngleVectors (self->s.angles, forward, left, up);
|
|
VectorScale (forward, self->offset[0], f1);
|
|
VectorScale (left, -self->offset[1], l1);
|
|
VectorScale (up, self->offset[2], u1);
|
|
VectorAdd (self->s.origin, f1, driver->s.origin);
|
|
VectorAdd (driver->s.origin, l1, driver->s.origin);
|
|
VectorAdd (driver->s.origin, u1, driver->s.origin);
|
|
driver->s.origin[2] += 16 * ( fabs(up[0]) + fabs(up[1]) );
|
|
gi.linkentity(driver);
|
|
}
|
|
VectorSubtract (other->s.origin, self->s.origin, dir);
|
|
dir[2] += 16;
|
|
VectorNormalize (dir);
|
|
if (!(other->svflags & SVF_MONSTER) && (!other->client) )
|
|
{
|
|
// give it a chance to go away on it's own terms (like gibs)
|
|
T_Damage (other, self, self, dir, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH);
|
|
// if it's still there, nuke it
|
|
if (other)
|
|
{
|
|
// Some of our ents don't have origin near the model
|
|
vec3_t save;
|
|
VectorCopy (other->s.origin, save);
|
|
VectorMA (other->absmin, 0.5, other->size, other->s.origin);
|
|
BecomeExplosion1 (other);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (level.time < self->touch_debounce_time)
|
|
return;
|
|
|
|
if (!self->dmg)
|
|
return;
|
|
|
|
if (other->client && (other->groundentity == self))
|
|
{
|
|
// Don't cream riders who've become embedded - just do minor damage
|
|
// and *maybe* help them get unstuck by pushing them up.
|
|
knockback = 2;
|
|
VectorSet (dir, 0, 0, 1);
|
|
T_Damage (other, self, self, dir, other->s.origin, vec3_origin, 1, knockback, 0, MOD_CRUSH);
|
|
}
|
|
else
|
|
{
|
|
knockback = (int)(fabs(self->moveinfo.current_speed) * other->mass / 300.);
|
|
T_Damage (other, self, self, dir, other->s.origin, vec3_origin, self->dmg, knockback, 0, MOD_CRUSH);
|
|
}
|
|
self->touch_debounce_time = level.time + 0.5;
|
|
}
|
|
|
|
void tracktrain_reach_dest (edict_t *self)
|
|
{
|
|
edict_t *path = self->target_ent;
|
|
|
|
if (path && path->pathtarget)
|
|
{
|
|
char *savetarget;
|
|
|
|
savetarget = path->target;
|
|
path->target = path->pathtarget;
|
|
if (self->owner)
|
|
G_UseTargets (path, self->owner);
|
|
else
|
|
G_UseTargets (path, self);
|
|
path->target = savetarget;
|
|
|
|
// make sure we didn't get killed by a killtarget
|
|
if (!self->inuse)
|
|
return;
|
|
if (path->spawnflags & SF_PATH_FIREONCE)
|
|
path->pathtarget = NULL;
|
|
}
|
|
|
|
if (path && (path->spawnflags & SF_PATH_DISABLE_TRAIN))
|
|
{
|
|
self->spawnflags |= SF_TRACKTRAIN_NOCONTROL;
|
|
if (self->owner)
|
|
tracktrain_disengage(self);
|
|
}
|
|
if (path && path->speed)
|
|
{
|
|
if (path->spawnflags & SF_PATH_ABS_SPEED)
|
|
{
|
|
self->moveinfo.speed = path->speed;
|
|
self->moveinfo.next_speed = self->moveinfo.speed;
|
|
self->moveinfo.state = (self->moveinfo.state >= STOP ? FAST : RFAST);
|
|
}
|
|
else
|
|
{
|
|
self->moveinfo.speed = path->speed * self->speed;
|
|
self->moveinfo.next_speed = self->moveinfo.state * self->moveinfo.speed/3;
|
|
}
|
|
self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed;
|
|
}
|
|
tracktrain_next (self);
|
|
}
|
|
|
|
qboolean is_backing_up (edict_t *train)
|
|
{
|
|
vec3_t forward, v_norm;
|
|
|
|
VectorCopy (train->velocity, v_norm);
|
|
VectorNormalize (v_norm);
|
|
AngleVectors (train->s.angles, forward, NULL, NULL);
|
|
if (DotProduct(forward,v_norm) < 0.)
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
edict_t *NextPathTrack(edict_t *train, edict_t *path)
|
|
{
|
|
edict_t *next=NULL;
|
|
vec3_t forward;
|
|
vec3_t to_next;
|
|
qboolean in_reverse;
|
|
|
|
AngleVectors (train->s.angles ,forward, NULL, NULL);
|
|
|
|
if ( (train->moveinfo.prevstate < STOP && train->moveinfo.state > STOP) ||
|
|
(train->moveinfo.prevstate > STOP && train->moveinfo.state < STOP) )
|
|
{
|
|
next = path->prevpath;
|
|
if (next)
|
|
{
|
|
VectorSubtract (next->s.origin, path->s.origin, to_next);
|
|
VectorNormalize (to_next);
|
|
if ((train->moveinfo.state > STOP) && (DotProduct(forward,to_next) < 0))
|
|
next = NULL;
|
|
else if ((train->moveinfo.state < STOP) && (DotProduct(forward,to_next) > 0))
|
|
next = NULL;
|
|
else
|
|
{
|
|
next->prevpath = path;
|
|
return next;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (train->moveinfo.state == STOP)
|
|
{
|
|
if (is_backing_up(train))
|
|
in_reverse = true;
|
|
else
|
|
in_reverse = false;
|
|
}
|
|
else
|
|
in_reverse = (train->moveinfo.state < STOP ? true : false);
|
|
|
|
if (in_reverse)
|
|
{
|
|
if (path->spawnflags & SF_PATH_ALTPATH)
|
|
{
|
|
next = G_PickTarget (path->target);
|
|
if (next)
|
|
{
|
|
VectorSubtract (next->s.origin, path->s.origin, to_next);
|
|
VectorNormalize (to_next);
|
|
if (DotProduct(forward,to_next) > 0)
|
|
next = NULL;
|
|
}
|
|
}
|
|
|
|
if (!next)
|
|
{
|
|
next = path->prevpath;
|
|
|
|
if (next)
|
|
{
|
|
// Ensure we don't flipflop
|
|
VectorSubtract (next->s.origin, path->s.origin, to_next);
|
|
VectorNormalize (to_next);
|
|
if (DotProduct(forward,to_next) > 0)
|
|
next = NULL;
|
|
}
|
|
|
|
if (!next)
|
|
{
|
|
// Find path_track whose target or target2 is set to
|
|
// the current path_track
|
|
edict_t *e;
|
|
int i;
|
|
for (i=maxclients->value; i<globals.num_edicts && !next; i++)
|
|
{
|
|
e = &g_edicts[i];
|
|
if (!e->inuse)
|
|
continue;
|
|
if (e==path)
|
|
continue;
|
|
if (!e->classname)
|
|
continue;
|
|
if (Q_stricmp(e->classname,"path_track"))
|
|
continue;
|
|
if (e->target && !Q_stricmp(e->target,path->targetname))
|
|
{
|
|
next = e;
|
|
VectorSubtract (next->s.origin, path->s.origin, to_next);
|
|
VectorNormalize (to_next);
|
|
if (DotProduct(forward,to_next) > 0)
|
|
next = NULL;
|
|
// else
|
|
// path->prevpath = next;
|
|
}
|
|
if (!next && e->target2 && !Q_stricmp(e->target2,path->targetname))
|
|
{
|
|
next = e;
|
|
VectorSubtract (next->s.origin, path->s.origin, to_next);
|
|
VectorNormalize (to_next);
|
|
if (DotProduct(forward,to_next) > 0)
|
|
next = NULL;
|
|
// else
|
|
// path->prevpath = next;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!next)
|
|
{
|
|
float dot;
|
|
|
|
// Finally, check this path_track's target and target2
|
|
if (path->target)
|
|
{
|
|
next = G_PickTarget (path->target);
|
|
if (next)
|
|
{
|
|
VectorSubtract (next->s.origin, path->s.origin, to_next);
|
|
VectorNormalize (to_next);
|
|
dot = DotProduct (forward, to_next);
|
|
if (dot > 0)
|
|
next = NULL;
|
|
}
|
|
}
|
|
if (path->target2 && !(path->spawnflags & SF_PATH_ALTPATH))
|
|
{
|
|
edict_t *next2;
|
|
float dot2;
|
|
|
|
next2 = G_PickTarget (path->target2);
|
|
if ( next2 == path )
|
|
next2 = NULL;
|
|
|
|
if (next2)
|
|
{
|
|
VectorSubtract (next2->s.origin, path->s.origin, to_next);
|
|
VectorNormalize (to_next);
|
|
dot2 = DotProduct (forward, to_next);
|
|
if (dot2 > 0)
|
|
next2 = NULL;
|
|
else if (!next)
|
|
{
|
|
next = next2;
|
|
next2 = NULL;
|
|
}
|
|
}
|
|
|
|
if ((next && next2) && (dot2 < dot))
|
|
next = next2;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else // Moving forward
|
|
{
|
|
float dot;
|
|
|
|
if (path->target)
|
|
{
|
|
next = G_PickTarget (path->target);
|
|
if (next)
|
|
{
|
|
VectorSubtract (next->s.origin, path->s.origin, to_next);
|
|
VectorNormalize (to_next);
|
|
dot = DotProduct(forward,to_next);
|
|
if (dot < 0)
|
|
next = NULL;
|
|
}
|
|
}
|
|
if (path->target2 && !(path->spawnflags & SF_PATH_ALTPATH))
|
|
{
|
|
edict_t *next2;
|
|
float dot2;
|
|
|
|
next2 = G_PickTarget (path->target2);
|
|
if ( next2 == path )
|
|
next2 = NULL;
|
|
|
|
if (next2)
|
|
{
|
|
VectorSubtract (next2->s.origin, path->s.origin, to_next);
|
|
VectorNormalize (to_next);
|
|
dot2 = DotProduct (forward,to_next);
|
|
if (dot2 < 0)
|
|
next2 = NULL;
|
|
else if (!next)
|
|
{
|
|
next = next2;
|
|
next2 = NULL;
|
|
}
|
|
}
|
|
|
|
if ((next && next2) && (dot2 > dot))
|
|
next = next2;
|
|
}
|
|
if (next == path)
|
|
next = NULL;
|
|
|
|
if (!next)
|
|
{ // Check for path_tracks that target (or target2) this path_track.
|
|
edict_t *e;
|
|
int i;
|
|
for (i=maxclients->value; i<globals.num_edicts && !next; i++)
|
|
{
|
|
e = &g_edicts[i];
|
|
if (!e->inuse)
|
|
continue;
|
|
if (e==path)
|
|
continue;
|
|
if (!e->classname)
|
|
continue;
|
|
if (Q_stricmp(e->classname,"path_track"))
|
|
continue;
|
|
if (e->target && !Q_stricmp(e->target,path->targetname))
|
|
{
|
|
next = e;
|
|
VectorSubtract (next->s.origin, path->s.origin, to_next);
|
|
VectorNormalize (to_next);
|
|
dot = DotProduct (forward, to_next);
|
|
if (dot < 0)
|
|
next = NULL;
|
|
}
|
|
if (e->target2 && !Q_stricmp(e->target2,path->targetname))
|
|
{
|
|
edict_t *next2;
|
|
float dot2;
|
|
|
|
next2 = e;
|
|
VectorSubtract (next2->s.origin, path->s.origin, to_next);
|
|
VectorNormalize (to_next);
|
|
dot2 = DotProduct (forward, to_next);
|
|
if (dot2 < 0)
|
|
next2 = NULL;
|
|
else if (!next)
|
|
{
|
|
next = next2;
|
|
next2 = NULL;
|
|
}
|
|
if ((next && next2) && (dot2 > dot))
|
|
next = next2;
|
|
}
|
|
}
|
|
}
|
|
// if (next)
|
|
// next->prevpath = path;
|
|
|
|
}
|
|
if (developer->value)
|
|
gi.dprintf("prev=%s, current=%s, next=%s\n",
|
|
(path->prevpath ? path->prevpath->targetname : "nada"),
|
|
path->targetname,
|
|
(next ? next->targetname : "nada"));
|
|
|
|
if (next)
|
|
next->prevpath = path;
|
|
return next;
|
|
}
|
|
|
|
void LookAhead( edict_t *train, vec3_t point, float dist )
|
|
{
|
|
float originalDist = dist;
|
|
float length;
|
|
vec3_t v;
|
|
edict_t *path;
|
|
int n=0;
|
|
|
|
path = train->target_ent;
|
|
if (!path || dist < 0)
|
|
return;
|
|
|
|
while ( dist > 0 )
|
|
{
|
|
n++;
|
|
if (n>20)
|
|
{
|
|
gi.dprintf("WTF??? n=%d\n",n);
|
|
return;
|
|
}
|
|
|
|
VectorSubtract (path->s.origin, point, v);
|
|
length = VectorLength (v);
|
|
if (length >= dist)
|
|
{
|
|
VectorMA (point, dist/length, v, point);
|
|
return;
|
|
}
|
|
dist -= length;
|
|
VectorCopy (path->s.origin, point);
|
|
|
|
// Don't go past a switch
|
|
/* if (path->spawnflags & SF_PATH_ALTPATH)
|
|
{
|
|
return;
|
|
} */
|
|
path = NextPathTrack (train,path);
|
|
if (!path)
|
|
return;
|
|
}
|
|
}
|
|
|
|
void train_angles(edict_t *train)
|
|
{
|
|
vec3_t v, angles;
|
|
|
|
VectorCopy (train->s.origin, v);
|
|
v[2] -= train->viewheight;
|
|
LookAhead(train,v,train->moveinfo.distance);
|
|
v[2] += train->viewheight;
|
|
VectorSubtract (v, train->s.origin, v);
|
|
if ( (train->moveinfo.state < STOP) || (train->moveinfo.state==STOP && is_backing_up(train)) )
|
|
VectorNegate (v, v);
|
|
|
|
// gi.dprintf("v = %g, %g, %g\n",v[0],v[1],v[2]);
|
|
|
|
if (VectorLength(v))
|
|
{
|
|
vectoangles2(v,angles);
|
|
train->ideal_yaw = angles[YAW];
|
|
train->ideal_pitch = angles[PITCH];
|
|
if (train->ideal_pitch < 0) train->ideal_pitch += 360;
|
|
}
|
|
else
|
|
{
|
|
train->ideal_pitch = train->s.angles[PITCH];
|
|
train->ideal_yaw = train->s.angles[YAW];
|
|
}
|
|
|
|
// determine angular velocitys from wheelbase and target angles
|
|
|
|
// gi.dprintf("tracktrain_next: target_ent=%s, ideal_yaw=%g, ideal_pitch=%g\n",
|
|
// (train->target_ent ? train->target_ent->targetname : "none"),
|
|
// train->ideal_yaw, train->ideal_pitch);
|
|
|
|
angles[PITCH] = train->ideal_pitch - train->s.angles[PITCH];
|
|
angles[YAW] = train->ideal_yaw - train->s.angles[YAW];
|
|
AnglesNormalize(angles);
|
|
|
|
// If yaw angle is > 90, we're about to flipflop (there's no way ideal_yaw
|
|
// can be more than 90 because the path_track selection code doesn't
|
|
// allow that
|
|
if ( (angles[YAW] > 90) || (angles[YAW] < -90) )
|
|
{
|
|
angles[YAW] += 180;
|
|
if (angles[PITCH] != 0)
|
|
angles[PITCH] += 180;
|
|
AnglesNormalize(angles);
|
|
}
|
|
train->pitch_speed = fabs(angles[PITCH])*10;
|
|
train->yaw_speed = fabs(angles[YAW])*10;
|
|
}
|
|
|
|
void tracktrain_turn (edict_t *self)
|
|
{
|
|
edict_t *train;
|
|
float cur_yaw, idl_yaw, cur_pitch, idl_pitch;
|
|
float yaw_vel, pitch_vel;
|
|
float Dist_1, Dist_2, Distance;
|
|
float new_speed;
|
|
|
|
train = self->enemy;
|
|
if (!train || !train->inuse)
|
|
return;
|
|
|
|
self->nextthink = level.time + FRAMETIME;
|
|
|
|
if (train->spawnflags & (SF_TRACKTRAIN_DISABLED | SF_TRACKTRAIN_OTHERMAP))
|
|
return;
|
|
|
|
// Train doesn't turn if at a complete stop
|
|
if ((train->velocity[0]==0.) && (train->velocity[1]==0.) && (train->velocity[2]==0.))
|
|
{
|
|
VectorClear (train->avelocity);
|
|
gi.linkentity(train);
|
|
return;
|
|
}
|
|
|
|
train_angles(train);
|
|
|
|
cur_yaw = train->s.angles[YAW];
|
|
idl_yaw = train->ideal_yaw;
|
|
cur_pitch = train->s.angles[PITCH];
|
|
idl_pitch = train->ideal_pitch;
|
|
|
|
// gi.dprintf("current angles=%g %g, ideal angles=%g %g\n",
|
|
// cur_pitch,cur_yaw,idl_pitch,idl_yaw);
|
|
|
|
yaw_vel = train->yaw_speed;
|
|
pitch_vel = train->pitch_speed;
|
|
|
|
if (train->spawnflags & SF_TRACKTRAIN_NOPITCH)
|
|
idl_pitch = cur_pitch;
|
|
|
|
if (cur_yaw == idl_yaw)
|
|
train->avelocity[YAW] = 0;
|
|
else
|
|
{
|
|
if (cur_yaw < idl_yaw)
|
|
{
|
|
Dist_1 = (idl_yaw - cur_yaw)*10;
|
|
Dist_2 = ((360 - idl_yaw) + cur_yaw)*10;
|
|
|
|
if (Dist_1 < Dist_2)
|
|
{
|
|
Distance = Dist_1;
|
|
|
|
if (Distance < yaw_vel)
|
|
yaw_vel = Distance;
|
|
|
|
new_speed = yaw_vel;
|
|
}
|
|
else
|
|
{
|
|
Distance = Dist_2;
|
|
|
|
if (Distance < yaw_vel)
|
|
yaw_vel = Distance;
|
|
|
|
new_speed = -yaw_vel;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Dist_1 = (cur_yaw - idl_yaw)*10;
|
|
Dist_2 = ((360 - cur_yaw) + idl_yaw)*10;
|
|
|
|
if (Dist_1 < Dist_2)
|
|
{
|
|
Distance = Dist_1;
|
|
|
|
if (Distance < yaw_vel)
|
|
yaw_vel = Distance;
|
|
|
|
new_speed = -yaw_vel;
|
|
}
|
|
else
|
|
{
|
|
Distance = Dist_2;
|
|
|
|
if (Distance < yaw_vel)
|
|
yaw_vel = Distance;
|
|
|
|
new_speed = yaw_vel;
|
|
}
|
|
}
|
|
train->avelocity[YAW] = new_speed;
|
|
|
|
// if (developer->value)
|
|
// gi.dprintf ("current yaw: %g ideal yaw: %g yaw speed: %g\n", cur_yaw, idl_yaw, self->enemy->avelocity[1]);
|
|
|
|
if (train->s.angles[YAW] < 0)
|
|
train->s.angles[YAW] += 360;
|
|
|
|
if (train->s.angles[YAW] >= 360)
|
|
train->s.angles[YAW] -= 360;
|
|
}
|
|
|
|
if ( train->roll != 0 )
|
|
{
|
|
float roll;
|
|
|
|
if (train->moveinfo.state < STOP)
|
|
roll = -train->roll;
|
|
else
|
|
roll = train->roll;
|
|
|
|
if (train->spawnflags & SF_TRACKTRAIN_ROLLSPEED)
|
|
roll *= VectorLength(train->velocity)/train->moveinfo.speed;
|
|
|
|
if ( train->avelocity[YAW] < -5 )
|
|
train->avelocity[ROLL] = track_AngleDistance( track_ApproachAngle( -roll, train->s.angles[ROLL], roll*2 ), train->s.angles[ROLL]);
|
|
else if ( train->avelocity[YAW] > 5 )
|
|
train->avelocity[ROLL] = track_AngleDistance( track_ApproachAngle( roll, train->s.angles[ROLL], roll*2 ), train->s.angles[ROLL]);
|
|
else
|
|
train->avelocity[ROLL] = track_AngleDistance( track_ApproachAngle( 0, train->s.angles[ROLL], roll*4 ), train->s.angles[ROLL]) * 4;
|
|
}
|
|
|
|
if (cur_pitch == idl_pitch)
|
|
train->avelocity[PITCH] = 0;
|
|
else
|
|
{
|
|
if (cur_pitch < idl_pitch)
|
|
{
|
|
Dist_1 = (idl_pitch - cur_pitch)*10;
|
|
Dist_2 = ((360 - idl_pitch) + cur_pitch)*10;
|
|
|
|
if (Dist_1 < Dist_2)
|
|
{
|
|
Distance = Dist_1;
|
|
|
|
if (Distance < pitch_vel)
|
|
pitch_vel = Distance;
|
|
|
|
new_speed = pitch_vel;
|
|
}
|
|
else
|
|
{
|
|
Distance = Dist_2;
|
|
|
|
if (Distance < pitch_vel)
|
|
pitch_vel = Distance;
|
|
|
|
new_speed = -pitch_vel;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Dist_1 = (cur_pitch - idl_pitch)*10;
|
|
Dist_2 = ((360 - cur_pitch) + idl_pitch)*10;
|
|
|
|
if (Dist_1 < Dist_2)
|
|
{
|
|
Distance = Dist_1;
|
|
|
|
if (Distance < pitch_vel)
|
|
pitch_vel = Distance;
|
|
|
|
new_speed = -pitch_vel;
|
|
}
|
|
else
|
|
{
|
|
Distance = Dist_2;
|
|
|
|
if (Distance < pitch_vel)
|
|
pitch_vel = Distance;
|
|
|
|
new_speed = pitch_vel;
|
|
}
|
|
}
|
|
train->avelocity[PITCH] = new_speed;
|
|
|
|
if (train->s.angles[PITCH] < 0)
|
|
train->s.angles[PITCH] += 360;
|
|
|
|
if (train->s.angles[PITCH] >= 360)
|
|
train->s.angles[PITCH] -= 360;
|
|
}
|
|
gi.linkentity(train);
|
|
|
|
}
|
|
|
|
|
|
void tracktrain_next (edict_t *self)
|
|
{
|
|
edict_t *ent=NULL;
|
|
vec3_t dest;
|
|
|
|
if (!self->target_ent)
|
|
{
|
|
self->s.sound = 0;
|
|
return;
|
|
}
|
|
|
|
ent = NextPathTrack(self,self->target_ent);
|
|
|
|
if (ent && (ent->spawnflags & SF_PATH_DISABLED))
|
|
ent = NULL;
|
|
|
|
if (!ent)
|
|
{
|
|
// Dead end
|
|
if (self->owner && (self->owner->svflags & SVF_MONSTER) && !self->target_ent->deathtarget )
|
|
{
|
|
// For monster drivers, immediately reverse course at
|
|
// dead ends (but NOT at dead ends that have a "deathtarget",
|
|
// which is usually an indication of a trackchange
|
|
self->moveinfo.prevstate = self->moveinfo.state;
|
|
self->moveinfo.state = -self->moveinfo.state;
|
|
self->moveinfo.next_speed = self->moveinfo.state * self->moveinfo.speed/3;
|
|
self->think = tracktrain_think;
|
|
self->think(self);
|
|
self->monsterinfo.search_time = level.time;
|
|
return;
|
|
}
|
|
|
|
VectorClear (self->velocity);
|
|
VectorClear (self->avelocity);
|
|
self->moveinfo.prevstate = self->moveinfo.state;
|
|
self->moveinfo.state = STOP;
|
|
self->s.sound = 0;
|
|
self->moveinfo.current_speed = 0;
|
|
self->moveinfo.next_speed = 0;
|
|
gi.linkentity(self);
|
|
if (self->owner)
|
|
{
|
|
VectorClear (self->owner->velocity);
|
|
gi.linkentity(self->owner);
|
|
}
|
|
if (self->target_ent->deathtarget)
|
|
{
|
|
char *temp;
|
|
temp = self->target_ent->target;
|
|
self->target_ent->target = self->target_ent->deathtarget;
|
|
G_UseTargets(self->target_ent,self);
|
|
self->target_ent->target = temp;
|
|
}
|
|
self->think = tracktrain_think;
|
|
self->think(self);
|
|
return;
|
|
}
|
|
|
|
self->target_ent = ent;
|
|
self->target = ent->targetname;
|
|
|
|
VectorCopy (ent->s.origin, dest);
|
|
dest[2] += self->viewheight;
|
|
VectorCopy (dest, self->moveinfo.end_origin);
|
|
|
|
train_angles(self);
|
|
|
|
if (!(self->spawnflags & SF_TRACKTRAIN_NOCONTROL) || !(self->spawnflags & SF_TRACKTRAIN_STARTOFF))
|
|
{
|
|
self->think = tracktrain_think;
|
|
self->think(self);
|
|
}
|
|
}
|
|
|
|
void func_tracktrain_find (edict_t *self)
|
|
{
|
|
edict_t *ent;
|
|
edict_t *next;
|
|
vec3_t vec;
|
|
vec3_t daoldorigin;
|
|
|
|
if (!self->target)
|
|
{
|
|
gi.dprintf ("tracktrain_find: no target\n");
|
|
return;
|
|
}
|
|
ent = G_PickTarget (self->target);
|
|
if (!ent)
|
|
{
|
|
gi.dprintf ("tracktrain_find: target %s not found\n", self->target);
|
|
return;
|
|
}
|
|
|
|
if (ent->speed)
|
|
{
|
|
self->moveinfo.speed = ent->speed * self->speed;
|
|
self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed;
|
|
self->moveinfo.next_speed = self->moveinfo.state * self->moveinfo.speed/3;
|
|
}
|
|
|
|
self->target_ent = ent;
|
|
|
|
// Get angles to next path_track
|
|
next = G_PickTarget (ent->target);
|
|
if (!next)
|
|
{
|
|
gi.dprintf ("tracktrain_find: next target %s not found\n", ent->target);
|
|
return;
|
|
}
|
|
VectorSubtract (next->s.origin, ent->s.origin, vec);
|
|
vectoangles2 (vec, self->s.angles);
|
|
|
|
ent->think = tracktrain_turn;
|
|
ent->enemy = self;
|
|
ent->nextthink = level.time + FRAMETIME;
|
|
|
|
VectorCopy (self->s.origin, daoldorigin); //Knightmare- copy old orgin for reference
|
|
VectorCopy (ent->s.origin, self->s.origin);
|
|
self->s.origin[2] += self->viewheight;
|
|
|
|
// Knightmare- move movewith pieces to spawning point
|
|
if (self->targetname)
|
|
{
|
|
edict_t *e = NULL;
|
|
vec3_t dir;
|
|
int i;
|
|
|
|
VectorSubtract (self->s.origin, daoldorigin, dir);
|
|
e = g_edicts+1; // skip the worldspawn
|
|
// cycle through all ents to find ones to move with
|
|
for (i = 1; i < globals.num_edicts; i++, e++)
|
|
{
|
|
if (!e->classname)
|
|
continue;
|
|
if (!e->movewith)
|
|
continue;
|
|
if (!strcmp(e->movewith, self->targetname))
|
|
{
|
|
// Knightmare- save distance moved for turret_breach to add to its firing point
|
|
if (!strcmp(e->classname, "turret_breach") || !strcmp(e->classname, "model_turret"))
|
|
VectorCopy (dir, e->aim_point);
|
|
|
|
VectorAdd (dir, e->s.origin, e->s.origin);
|
|
VectorCopy (e->s.origin, e->s.old_origin);
|
|
//This is now the child's original position
|
|
if ( (e->solid == SOLID_BSP) && strcmp(e->classname,"func_rotating")
|
|
&& strcmp(e->classname,"func_door_rotating"))
|
|
ReInitialize_Entity(e);
|
|
gi.linkentity (e);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if (self->spawnflags & SF_TRACKTRAIN_OTHERMAP)
|
|
{
|
|
self->solid = SOLID_NOT;
|
|
self->svflags |= SVF_NOCLIENT;
|
|
self->spawnflags |= SF_TRACKTRAIN_DISABLED;
|
|
self->nextthink = 0;
|
|
}
|
|
else
|
|
{
|
|
self->nextthink = level.time + FRAMETIME;
|
|
self->think = tracktrain_next;
|
|
}
|
|
gi.linkentity (self);
|
|
}
|
|
|
|
void tracktrain_use (edict_t *self, edict_t *other, edict_t *activator)
|
|
{
|
|
if (self->spawnflags & SF_TRACKTRAIN_STARTOFF)
|
|
{
|
|
if (self->spawnflags & SF_TRACKTRAIN_NOCONTROL)
|
|
{
|
|
self->moveinfo.state = FAST;
|
|
self->moveinfo.next_speed = self->moveinfo.speed;
|
|
if (self->sounds) {
|
|
self->s.sound = gi.soundindex(va("%sspeed%d.wav", self->source, abs(self->moveinfo.state)));
|
|
#ifdef LOOP_SOUND_ATTENUATION
|
|
self->s.attenuation = self->attenuation;
|
|
#endif
|
|
}
|
|
else
|
|
self->s.sound = 0;
|
|
}
|
|
self->spawnflags &= ~SF_TRACKTRAIN_STARTOFF;
|
|
self->think = tracktrain_think;
|
|
self->think(self);
|
|
}
|
|
else
|
|
{
|
|
if (self->owner)
|
|
tracktrain_disengage(self);
|
|
self->moveinfo.state = STOP;
|
|
self->moveinfo.next_speed = 0;
|
|
self->s.sound = 0;
|
|
self->think = NULL;
|
|
self->nextthink = 0;
|
|
self->spawnflags |= SF_TRACKTRAIN_STARTOFF;
|
|
}
|
|
}
|
|
|
|
void SP_func_tracktrain (edict_t *self)
|
|
{
|
|
self->class_id = ENTITY_FUNC_TRACKTRAIN;
|
|
|
|
self->movetype = MOVETYPE_PUSH;
|
|
self->flags |= FL_TRACKTRAIN;
|
|
|
|
VectorClear (self->s.angles);
|
|
self->blocked = tracktrain_blocked;
|
|
if (!self->dmg)
|
|
self->dmg = 100;
|
|
// Wheelbase determines angular velocities
|
|
if (self->distance)
|
|
st.distance = self->distance;
|
|
if (st.distance)
|
|
self->moveinfo.distance = st.distance;
|
|
else
|
|
self->moveinfo.distance = 50;
|
|
|
|
// Origin rides by "height" above path_tracks
|
|
if (self->height)
|
|
st.height = self->height;
|
|
if (st.height)
|
|
self->viewheight = st.height;
|
|
else
|
|
self->viewheight = 4;
|
|
|
|
// Default mass for collisions:
|
|
self->mass = 2000;
|
|
|
|
// Driving position
|
|
if ( (VectorLength(self->bleft) == 0) && (VectorLength(self->tright) == 0))
|
|
{
|
|
VectorSet (self->bleft, -8, -8, -8);
|
|
VectorSet (self->tright, 8, 8, 8);
|
|
}
|
|
VectorAdd (self->bleft, self->tright, self->move_origin);
|
|
VectorScale (self->move_origin ,0.5, self->move_origin);
|
|
|
|
self->solid = SOLID_BSP;
|
|
gi.setmodel (self, self->model);
|
|
|
|
// usermodel (for alias model trains) goes on modelindex2
|
|
if (self->usermodel && strlen(self->usermodel)) {
|
|
char modelname[256];
|
|
// check for "models/" already in path
|
|
if ( !strncmp(self->usermodel, "models/", 7) )
|
|
Com_sprintf(modelname, sizeof(modelname), "%s", self->usermodel);
|
|
else
|
|
Com_sprintf(modelname, sizeof(modelname), "models/%s", self->usermodel);
|
|
self->s.modelindex2 = gi.modelindex (modelname);
|
|
}
|
|
|
|
if (!self->speed)
|
|
self->speed = 100;
|
|
|
|
self->moveinfo.speed = self->speed;
|
|
self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed;
|
|
|
|
if (self->roll_speed)
|
|
{
|
|
self->roll = self->roll_speed;
|
|
self->roll_speed = 0;
|
|
self->spawnflags |= SF_TRACKTRAIN_ROLLSPEED;
|
|
}
|
|
|
|
if (self->health)
|
|
{
|
|
self->die = tracktrain_die;
|
|
self->takedamage = DAMAGE_YES;
|
|
}
|
|
else
|
|
{
|
|
self->die = NULL;
|
|
self->takedamage = DAMAGE_NO;
|
|
}
|
|
|
|
self->spawnflags &= ~SF_TRACKTRAIN_DISABLED; // insurance
|
|
if (self->spawnflags & SF_TRACKTRAIN_NOCONTROL)
|
|
self->spawnflags |= SF_TRACKTRAIN_STARTOFF;
|
|
|
|
self->use = tracktrain_use;
|
|
self->moveinfo.current_speed = 0;
|
|
self->moveinfo.state = STOP;
|
|
self->moveinfo.prevstate = STOP+1; // Assumed, so that initial reverse works correctly
|
|
self->s.sound = 0;
|
|
self->turn_rider = 1;
|
|
VectorClear (self->s.angles);
|
|
|
|
self->postthink = train_move_children; //Knightmare- supports movewith
|
|
|
|
if (self->target)
|
|
{
|
|
self->nextthink = level.time + FRAMETIME;
|
|
self->think = func_tracktrain_find;
|
|
}
|
|
else if (!(self->spawnflags & SF_TRACKTRAIN_OTHERMAP))
|
|
{
|
|
gi.dprintf ("func_tracktrain without a target at %s\n", vtos(self->absmin));
|
|
G_FreeEdict(self);
|
|
return;
|
|
}
|
|
|
|
if (!self->sounds)
|
|
self->sounds = 1;
|
|
if (self->sounds > 0)
|
|
{
|
|
if (self->sounds > 9)
|
|
self->sounds = 9;
|
|
self->source = gi.TagMalloc(10,TAG_LEVEL);
|
|
Com_sprintf(self->source, 10, "train/%d/", self->sounds);
|
|
gi.soundindex(va("%sspeed1.wav",self->source));
|
|
gi.soundindex(va("%sspeed2.wav",self->source));
|
|
gi.soundindex(va("%sspeed3.wav",self->source));
|
|
}
|
|
|
|
if (self->attenuation <= 0)
|
|
self->attenuation = ATTN_IDLE;
|
|
|
|
gi.linkentity (self);
|
|
|
|
}
|
|
|
|
void find_tracktrain (edict_t *self)
|
|
{
|
|
edict_t *train;
|
|
qboolean train_found=false;
|
|
vec3_t forward;
|
|
|
|
// This gives game a chance to put player in place before
|
|
// restarting train
|
|
if (!g_edicts[1].linkcount)
|
|
{
|
|
self->nextthink = level.time + FRAMETIME;
|
|
return;
|
|
}
|
|
|
|
train = G_Find(NULL,FOFS(targetname),self->targetname);
|
|
while (train && !train_found)
|
|
{
|
|
if (!Q_stricmp(train->classname,"func_tracktrain"))
|
|
train_found = true;
|
|
else
|
|
train = G_Find(train,FOFS(targetname),self->targetname);
|
|
}
|
|
if (!train_found)
|
|
{
|
|
gi.dprintf("find_tracktrain: no matching func_tracktrain with targetname=%s\n",
|
|
self->targetname);
|
|
G_FreeEdict(self);
|
|
return;
|
|
}
|
|
train->solid = SOLID_BSP;
|
|
train->svflags &= ~SVF_NOCLIENT;
|
|
train->spawnflags = self->spawnflags;
|
|
train->spawnflags &= ~(SF_TRACKTRAIN_OTHERMAP | SF_TRACKTRAIN_DISABLED);
|
|
VectorCopy (self->s.origin, train->s.origin);
|
|
VectorCopy (self->s.angles, train->s.angles);
|
|
VectorCopy (self->bleft, train->bleft);
|
|
VectorCopy (self->tright, train->tright);
|
|
VectorClear (train->avelocity);
|
|
train->viewheight = self->viewheight;
|
|
train->speed = self->speed;
|
|
train->moveinfo.distance = self->radius;
|
|
train->moveinfo.accel = train->moveinfo.decel = train->moveinfo.speed = train->speed;
|
|
train->moveinfo.state = train->moveinfo.prevstate = self->count;
|
|
train->sounds = self->sounds;
|
|
if (train->sounds > 0)
|
|
{
|
|
train->source = gi.TagMalloc(10,TAG_LEVEL);
|
|
Com_sprintf(train->source, 10, "train/%d/", train->sounds);
|
|
}
|
|
if (train->moveinfo.state && (train->sounds > 0)) {
|
|
train->s.sound = gi.soundindex(va("%sspeed%d.wav", train->source, abs(train->moveinfo.state)));
|
|
#ifdef LOOP_SOUND_ATTENUATION
|
|
train->s.attenuation = train->attenuation;
|
|
#endif
|
|
}
|
|
else
|
|
train->s.sound = 0;
|
|
train->moveinfo.next_speed = train->moveinfo.state * train->moveinfo.speed/3;
|
|
// Assume train was already at it's "next_speed" when the level change occurred
|
|
AngleVectors (train->s.angles, forward, NULL, NULL);
|
|
VectorScale (forward, train->moveinfo.next_speed, train->velocity);
|
|
// Force a wait before taking player input
|
|
train->moveinfo.wait = 1;
|
|
|
|
if (self->style && (self->style <= game.maxclients) && &g_edicts[self->style].inuse)
|
|
{
|
|
train->owner = &g_edicts[self->style];
|
|
train->owner->vehicle = train;
|
|
if (train->owner->client)
|
|
{
|
|
train->owner->client->vehicle_framenum = level.framenum;
|
|
train->owner->client->ps.pmove.pm_flags |= PMF_NO_PREDICTION;
|
|
}
|
|
VectorCopy (self->offset, train->offset);
|
|
}
|
|
else
|
|
train->owner = NULL;
|
|
|
|
gi.linkentity(train);
|
|
if (self->target)
|
|
{
|
|
vec3_t dest;
|
|
|
|
train->target = self->target;
|
|
train->target_ent = G_Find(NULL,FOFS(targetname),train->target);
|
|
VectorCopy (train->target_ent->s.origin, dest);
|
|
dest[2] += train->viewheight;
|
|
VectorCopy (dest, train->moveinfo.end_origin);
|
|
train_angles(train);
|
|
if (!(train->spawnflags & SF_TRACKTRAIN_NOCONTROL) || !(train->spawnflags & SF_TRACKTRAIN_STARTOFF))
|
|
{
|
|
train->think = tracktrain_think;
|
|
train->think(train);
|
|
}
|
|
}
|
|
else
|
|
gi.dprintf("info_train_start with no target\n");
|
|
|
|
G_FreeEdict(self);
|
|
}
|
|
|
|
void SP_info_train_start (edict_t *self)
|
|
{
|
|
if (!self->targetname)
|
|
{
|
|
gi.dprintf("crosslevel train with no targetname\n");
|
|
G_FreeEdict(self);
|
|
}
|
|
self->class_id = ENTITY_INFO_TRAIN_START;
|
|
|
|
self->think = find_tracktrain;
|
|
self->nextthink = level.time + 1;
|
|
}
|