mirror of
https://github.com/blendogames/thirtyflightsofloving.git
synced 2024-11-14 16:40:57 +00:00
45a81cf57b
Cleaned up strig handling functions in game DLLs. Refactored ThrowGib(), ThrowHead() and ThrowDebris() functions in missionpack DLL.
2139 lines
55 KiB
C
2139 lines
55 KiB
C
/*
|
|
===========================================================================
|
|
Copyright (C) 1997-2001 Id Software, Inc.
|
|
Copyright (C) 2000-2002 Mr. Hyde and Mad Dog
|
|
|
|
This file is part of Lazarus Quake 2 Mod source code.
|
|
|
|
Lazarus Quake 2 Mod source code is free software; you can redistribute it
|
|
and/or modify it under the terms of the GNU General Public License as
|
|
published by the Free Software Foundation; either version 2 of the License,
|
|
or (at your option) any later version.
|
|
|
|
Lazarus Quake 2 Mod source code is distributed in the hope that it will be
|
|
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Lazarus Quake 2 Mod source code; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
===========================================================================
|
|
*/
|
|
|
|
#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
|
|
self->moveinfo.distance = st.distance;
|
|
|
|
// Travel 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;
|
|
|
|
#ifdef POSTTHINK_CHILD_MOVEMENT
|
|
self->postthink = set_child_movement; // Knightmare- supports movewith
|
|
#endif // POSTTHINK_CHILD_MOVEMENT
|
|
|
|
// 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;
|
|
|
|
safe_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;
|
|
#ifndef POSTTHINK_CHILD_MOVEMENT
|
|
if (self->movewith_next && (self->movewith_next->movewith_ent == self))
|
|
set_child_movement (self);
|
|
#endif // POSTTHINK_CHILD_MOVEMENT
|
|
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)
|
|
{
|
|
safe_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);
|
|
}
|
|
}
|
|
}
|
|
#ifndef POSTTHINK_CHILD_MOVEMENT
|
|
if (self->movewith_next && (self->movewith_next->movewith_ent == self))
|
|
set_child_movement (self);
|
|
#endif // POSTTHINK_CHILD_MOVEMENT
|
|
|
|
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->movewith_next && (self->movewith_next->movewith_ent == self))
|
|
{
|
|
edict_t *e = NULL;
|
|
vec3_t dir;
|
|
|
|
VectorSubtract (self->s.origin, daoldorigin, dir);
|
|
for (e = self->movewith_next; e; e = e->movewith_next)
|
|
{
|
|
if (!e->inuse)
|
|
break;
|
|
if (!e->classname)
|
|
continue;
|
|
|
|
// 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->offset);
|
|
|
|
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 (st.distance)
|
|
self->moveinfo.distance = st.distance;
|
|
else
|
|
self->moveinfo.distance = 50;
|
|
|
|
// Origin rides by "height" above path_tracks
|
|
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);
|
|
|
|
#ifdef POSTTHINK_CHILD_MOVEMENT
|
|
self->postthink = set_child_movement; // Knightmare- supports movewith
|
|
#endif // POSTTHINK_CHILD_MOVEMENT
|
|
|
|
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;
|
|
}
|