/* Copyright (C) 1997-2001 Id Software, Inc. Copyright (C) 2000-2002 Mr. Hyde and Mad Dog This program 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. This program 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 this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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); /*============================================================================= Utility functions shamelessly ripped from HL source ===============================================================================*/ float UTIL_AngleMod(float a) { if (a < 0) { a = a + 360 * ((int)(a / 360) + 1); } else if (a >= 360) { a = a - 360 * ((int)(a / 360)); } // a = (360.0/65536) * ((int)(a*(65536/360.0)) & 65535); return a; } float UTIL_ApproachAngle( float target, float value, float speed ) { float delta; target = UTIL_AngleMod( target ); value = UTIL_AngleMod( target ); delta = target - value; // Speed is assumed to be positive if ( speed < 0 ) speed = -speed; if ( delta < -180 ) delta += 360; else if ( delta > 180 ) delta -= 360; if ( delta > speed ) value += speed; else if ( delta < -speed ) value -= speed; else value = target; return value; } float UTIL_AngleDistance( float next, float cur ) { float delta = next - cur; if ( delta < -180 ) delta += 360; else if ( delta > 180 ) delta -= 360; return delta; } /*============================================================================= 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, ATTN_NORM, 0); 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))); 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, ATTN_NORM, 0); self->s.sound = self->moveinfo.sound_middle; 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; // 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"); } 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))); 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)); } 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))); 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))); 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; 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) { 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); } } } 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; iinuse) 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; iinuse) 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] = UTIL_AngleDistance( UTIL_ApproachAngle( -roll, train->s.angles[ROLL], roll*2 ), train->s.angles[ROLL]); else if ( train->avelocity[YAW] > 5 ) train->avelocity[ROLL] = UTIL_AngleDistance( UTIL_ApproachAngle( roll, train->s.angles[ROLL], roll*2 ), train->s.angles[ROLL]); else train->avelocity[ROLL] = UTIL_AngleDistance( UTIL_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; 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 (ent->s.origin, self->s.origin); self->s.origin[2] += self->viewheight; 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))); 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); 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); 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); sprintf(self->source,"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)); } 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); sprintf(train->source,"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))); 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; }