/* =========================================================================== Return to Castle Wolfenstein multiplayer GPL Source Code Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). RTCW MP 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 3 of the License, or (at your option) any later version. RTCW MP 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 RTCW MP Source Code. If not, see . In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. =========================================================================== */ /* SP_Tramcar */ #include "g_local.h" // defines #define TRAMCAR_START_ON 1 #define TRAMCAR_TOGGLE 2 #define TRAMCAR_BLOCK_STOPS 4 #define TRAMCAR_LEADER 8 void props_me109_think( gentity_t *ent ); void ExplodePlaneSndFx( gentity_t *self ); void Think_SetupAirplaneWaypoints( gentity_t *ent ); void truck_cam_think( gentity_t *ent ); // extern calls extern void Think_SetupTrainTargets( gentity_t *ent ); extern void Reached_BinaryMover( gentity_t *ent ); extern void MatchTeam( gentity_t *teamLeader, int moverState, int time ); extern void SetMoverState( gentity_t *ent, moverState_t moverState, int time ); extern void Blocked_Door( gentity_t *ent, gentity_t *other ); extern void Think_BeginMoving( gentity_t *ent ); extern void propExplosionLarge( gentity_t *ent ); //////////////////////// // truck states //////////////////////// typedef enum { truck_nosound = 0, truck_idle, truck_gear1, truck_gear2, truck_gear3, truck_reverse, truck_moving, truck_breaking, truck_bouncy1, truck_bouncy2, truck_bouncy3 } truck_states1; ///////////////////////// // truck sounds ///////////////////////// int truck_idle_snd; int truck_gear1_snd; int truck_gear2_snd; int truck_gear3_snd; int truck_reverse_snd; int truck_moving_snd; int truck_breaking_snd; int truck_bouncy1_snd; int truck_bouncy2_snd; int truck_bouncy3_snd; int truck_sound; //////////////////////// // plane states //////////////////////// typedef enum { plane_nosound = 0, plane_idle, plane_flyby1, plane_flyby2, plane_loop, plane_choke, plane_startup } truck_states2; /////////////////////////// // aircraft sounds /////////////////////////// int fploop_snd; int fpchoke_snd; int fpattack_snd; int fpexpdebris_snd; int fpflyby1_snd; int fpflyby2_snd; int fpidle_snd; int fpstartup_snd; /////////////////////////// // airplane parts /////////////////////////// int fuse_part; int wing_part; int tail_part; int nose_part; int crash_part; // functions to be added void InitTramcar( gentity_t *ent ) { vec3_t move; float distance; float light; vec3_t color; qboolean lightSet, colorSet; char *sound; // This is here just for show // if the "model2" key is set, use a seperate model // for drawing, but clip against the brushes if ( ent->model2 ) { ent->s.modelindex2 = G_ModelIndex( ent->model2 ); } if ( !Q_stricmp( ent->classname, "props_me109" ) ) { ent->s.modelindex2 = G_ModelIndex( "models/mapobjects/vehicles/m109s.md3" ); } if ( !Q_stricmp( ent->classname, "truck_cam" ) ) { ent->s.modelindex2 = G_ModelIndex( "models/mapobjects/vehicles/truck_base.md3" ); } // if the "loopsound" key is set, use a constant looping sound when moving if ( G_SpawnString( "noise", "100", &sound ) ) { ent->s.loopSound = G_SoundIndex( sound ); } // if the "color" or "light" keys are set, setup constantLight lightSet = G_SpawnFloat( "light", "100", &light ); colorSet = G_SpawnVector( "color", "1 1 1", color ); if ( lightSet || colorSet ) { int r, g, b, i; r = color[0] * 255; if ( r > 255 ) { r = 255; } g = color[1] * 255; if ( g > 255 ) { g = 255; } b = color[2] * 255; if ( b > 255 ) { b = 255; } i = light / 4; if ( i > 255 ) { i = 255; } ent->s.constantLight = r | ( g << 8 ) | ( b << 16 ) | ( i << 24 ); } ent->use = Use_BinaryMover; // ent->reached = Reached_BinaryMover; ent->moverState = MOVER_POS1; ent->r.svFlags = SVF_USE_CURRENT_ORIGIN; ent->s.eType = ET_MOVER; VectorCopy( ent->pos1, ent->r.currentOrigin ); trap_LinkEntity( ent ); ent->s.pos.trType = TR_STATIONARY; VectorCopy( ent->pos1, ent->s.pos.trBase ); // calculate time to reach second position from speed VectorSubtract( ent->pos2, ent->pos1, move ); distance = VectorLength( move ); if ( !ent->speed ) { ent->speed = 100; } VectorScale( move, ent->speed, ent->s.pos.trDelta ); ent->s.pos.trDuration = distance * 1000 / ent->speed; if ( ent->s.pos.trDuration <= 0 ) { ent->s.pos.trDuration = 1; } } void Calc_Roll( gentity_t *ent ) { gentity_t *target; vec3_t vec; vec3_t forward; vec3_t right; float dot; float dot2; vec3_t tang; target = ent->nextTrain; VectorCopy( ent->r.currentAngles, tang ); tang[ROLL] = 0; AngleVectors( tang, forward, right, NULL ); VectorSubtract( target->nextTrain->nextTrain->s.origin, ent->r.currentOrigin, vec ); VectorNormalize( vec ); dot = DotProduct( vec, forward ); dot2 = DotProduct( vec, right ); ent->angle = (int) ent->angle; if ( dot2 > 0 ) { if ( ent->s.apos.trBase[ROLL] < -( ent->angle * 2 ) ) { ent->s.apos.trBase[ROLL] += 2; } else if ( ent->s.apos.trBase[ROLL] > -( ent->angle * 2 ) ) { ent->s.apos.trBase[ROLL] -= 2; } if ( ent->s.apos.trBase[ROLL] > 90 ) { ent->s.apos.trBase[ROLL] = 90; } } else if ( dot2 < 0 ) { if ( ent->s.apos.trBase[ROLL] > -( ent->angle * 2 ) ) { ent->s.apos.trBase[ROLL] -= 2; } else if ( ent->s.apos.trBase[ROLL] < -( ent->angle * 2 ) ) { ent->s.apos.trBase[ROLL] += 2; } if ( ent->s.apos.trBase[ROLL] < -90 ) { ent->s.apos.trBase[ROLL] = -90; } } else { ent->s.apos.trBase[ROLL] = 0; } // G_Printf ("dot: %5.2f dot2: %5.2f\n", dot, dot2); // VectorCopy (ent->r.currentAngles, ent->TargetAngles); trap_LinkEntity( ent ); ent->nextthink = level.time + 50; } #define MAXCHOICES 8 void GetNextTrack( gentity_t *ent ) { gentity_t *track = NULL; gentity_t *next; gentity_t *choice[MAXCHOICES]; int num_choices = 0; int rval; next = ent->nextTrain; if ( !( next->track ) ) { G_Printf( "NULL track name for %s on %s\n", ent->classname, next->targetname ); return; } while ( 1 ) { track = G_Find( track, FOFS( targetname ), next->track ); if ( !track ) { break; } choice[num_choices++] = track; if ( num_choices == MAXCHOICES ) { break; } } if ( !num_choices ) { G_Printf( "GetNextTrack didnt find a track\n" ); return; } rval = rand() % num_choices; ent->nextTrain = NULL; ent->target = choice[rval]->targetname; } void Reached_Tramcar( gentity_t *ent ) { gentity_t *next; float speed; vec3_t move; float length; // copy the apropriate values next = ent->nextTrain; if ( !next || !next->nextTrain ) { return; // just stop } // Rafael if ( next->wait == -1 && next->count ) { // G_Printf ("stoped wait = -1 count %i\n",next->count); return; } if ( !Q_stricmp( ent->classname, "props_me109" ) ) { vec3_t vec, angles; float diff; if ( next->spawnflags & 8 ) { // laps next->count--; if ( !next->count ) { next->count = next->count2; GetNextTrack( ent ); Think_SetupAirplaneWaypoints( ent ); next = ent->nextTrain; G_Printf( "changed track to %s\n", next->targetname ); } else { G_Printf( "%s lap %i\n", next->targetname, next->count ); } } else if ( ( next->spawnflags & 1 ) && !( next->count ) && ent->health > 0 ) { // SCRIPT flag GetNextTrack( ent ); Think_SetupAirplaneWaypoints( ent ); } else if ( ( next->spawnflags & 2 ) && ( ent->spawnflags & 8 ) && ent->health <= 0 && ent->takedamage ) { // death path ent->takedamage = qfalse; GetNextTrack( ent ); Think_SetupAirplaneWaypoints( ent ); } else if ( ( next->spawnflags & 4 ) ) { // explode the plane ExplodePlaneSndFx( ent ); ent->s.modelindex = crash_part; // spawn the wing at the player effect ent->nextTrain = NULL; G_UseTargets( next, NULL ); return; } VectorSubtract( ent->nextTrain->nextTrain->s.origin, ent->r.currentOrigin, vec ); vectoangles( vec, angles ); diff = AngleSubtract( ent->r.currentAngles [YAW], angles[YAW] ); // diff = AngleSubtract (ent->TargetAngles [YAW], angles[YAW]); ent->rotate[1] = 1; ent->angle = -diff; //if (angles[YAW] == 0) // ent->s.apos.trDuration = ent->s.pos.trDuration; //else // ent->s.apos.trDuration = 1000; { VectorCopy( next->s.origin, ent->pos1 ); VectorCopy( next->nextTrain->s.origin, ent->pos2 ); // if the path_corner has a speed, use that if ( next->speed ) { speed = next->speed; } else { // otherwise use the train's speed speed = ent->speed; } if ( speed < 1 ) { speed = 1; } // calculate duration VectorSubtract( ent->pos2, ent->pos1, move ); length = VectorLength( move ); ent->s.apos.trDuration = length * 1000 / speed; //testing // ent->gDuration = ent->s.apos.trDuration; ent->gDurationBack = ent->gDuration = ent->s.apos.trDuration; // ent->gDeltaBack = ent->gDelta = } VectorClear( ent->s.apos.trDelta ); SetMoverState( ent, MOVER_1TO2ROTATE, level.time ); VectorCopy( ent->r.currentAngles, ent->s.apos.trBase ); trap_LinkEntity( ent ); ent->think = props_me109_think; ent->nextthink = level.time + 50; } else if ( !Q_stricmp( ent->classname, "truck_cam" ) ) { G_Printf( "target: %s\n", next->targetname ); if ( next->spawnflags & 2 ) { // END ent->s.loopSound = 0; // stop sound ent->nextTrain = NULL; return; } else { vec3_t vec, angles; float diff; if ( next->spawnflags & 4 ) { // reverse ent->props_frame_state = truck_reverse; VectorSubtract( ent->r.currentOrigin, ent->nextTrain->nextTrain->s.origin, vec ); } else { ent->props_frame_state = truck_moving; VectorSubtract( ent->nextTrain->nextTrain->s.origin, ent->r.currentOrigin, vec ); } vectoangles( vec, angles ); diff = AngleSubtract( ent->r.currentAngles [YAW], angles[YAW] ); ent->rotate[1] = 1; ent->angle = -diff; if ( angles[YAW] == 0 ) { ent->s.apos.trDuration = ent->s.pos.trDuration; } else { ent->s.apos.trDuration = 1000; } //testing ent->gDuration = ent->s.pos.trDuration; VectorClear( ent->s.apos.trDelta ); SetMoverState( ent, MOVER_1TO2ROTATE, level.time ); VectorCopy( ent->r.currentAngles, ent->s.apos.trBase ); trap_LinkEntity( ent ); } if ( next->wait == -1 ) { ent->props_frame_state = truck_idle; } if ( next->count2 == 1 ) { ent->props_frame_state = truck_gear1; } else if ( next->count2 == 2 ) { ent->props_frame_state = truck_gear2; } else if ( next->count2 == 3 ) { ent->props_frame_state = truck_gear3; } switch ( ent->props_frame_state ) { case truck_idle: ent->s.loopSound = truck_idle_snd; break; case truck_gear1: ent->s.loopSound = truck_gear1_snd; break; case truck_gear2: ent->s.loopSound = truck_gear2_snd; break; case truck_gear3: ent->s.loopSound = truck_gear3_snd; break; case truck_reverse: ent->s.loopSound = truck_reverse_snd; break; case truck_moving: ent->s.loopSound = truck_moving_snd; break; case truck_breaking: ent->s.loopSound = truck_breaking_snd; break; case truck_bouncy1: ent->s.loopSound = truck_bouncy1_snd; break; case truck_bouncy2: ent->s.loopSound = truck_bouncy2_snd; break; case truck_bouncy3: ent->s.loopSound = truck_bouncy3_snd; break; } //testing ent->s.loopSound = truck_sound; ent->think = truck_cam_think; ent->nextthink = level.time + ( FRAMETIME / 2 ); } else if ( !Q_stricmp( ent->classname, "camera_cam" ) ) { } // fire all other targets G_UseTargets( next, NULL ); // set the new trajectory ent->nextTrain = next->nextTrain; if ( next->wait == -1 ) { next->count = 1; } VectorCopy( next->s.origin, ent->pos1 ); VectorCopy( next->nextTrain->s.origin, ent->pos2 ); // if the path_corner has a speed, use that if ( next->speed ) { speed = next->speed; } else { // otherwise use the train's speed speed = ent->speed; } if ( speed < 1 ) { speed = 1; } // calculate duration VectorSubtract( ent->pos2, ent->pos1, move ); length = VectorLength( move ); ent->s.pos.trDuration = length * 1000 / speed; //testing // ent->gDuration = ent->s.pos.trDuration; ent->gDurationBack = ent->gDuration = ent->s.pos.trDuration; // ent->gDeltaBack = ent->gDelta = ; // looping sound if ( next->soundLoop ) { ent->s.loopSound = next->soundLoop; } // start it going SetMoverState( ent, MOVER_1TO2, level.time ); // if there is a "wait" value on the target, don't start moving yet // if ( next->wait ) if ( next->wait && next->wait != -1 ) { ent->nextthink = level.time + next->wait * 1000; ent->think = Think_BeginMoving; ent->s.pos.trType = TR_STATIONARY; } } extern void func_explosive_explode( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ); void Tramcar_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) { gentity_t *slave; func_explosive_explode( self, self, inflictor, 0, 0 ); // link all teammembers for ( slave = self ; slave ; slave = slave->teamchain ) { if ( slave == self ) { continue; } // slaves need to inherit position slave->nextTrain = self->nextTrain; slave->s.pos.trType = self->s.pos.trType; slave->s.pos.trTime = self->s.pos.trTime; slave->s.pos.trDuration = self->s.pos.trDuration; VectorCopy( self->s.pos.trBase, slave->s.pos.trBase ); VectorCopy( self->s.pos.trDelta, slave->s.pos.trDelta ); slave->s.apos.trType = self->s.apos.trType; slave->s.apos.trTime = self->s.apos.trTime; slave->s.apos.trDuration = self->s.apos.trDuration; VectorCopy( self->s.apos.trBase, slave->s.apos.trBase ); VectorCopy( self->s.apos.trDelta, slave->s.apos.trDelta ); slave->think = self->think; slave->nextthink = self->nextthink; VectorCopy( self->pos1, slave->pos1 ); VectorCopy( self->pos2, slave->pos2 ); slave->speed = self->speed; slave->flags &= ~FL_TEAMSLAVE; // make it visible if ( self->use ) { slave->use = self->use; } trap_LinkEntity( slave ); } self->use = NULL; self->is_dead = qtrue; self->takedamage = qfalse; if ( self->nextTrain ) { self->nextTrain = 0; } self->s.loopSound = 0; VectorCopy( self->r.currentOrigin, self->s.pos.trBase ); VectorCopy( self->r.currentAngles, self->s.apos.trBase ); self->flags |= FL_TEAMSLAVE; trap_UnlinkEntity( self ); } void TramCarUse( gentity_t *ent, gentity_t *other, gentity_t *activator ) { gentity_t *next; if ( level.time >= ent->s.pos.trTime + ent->s.pos.trDuration ) { next = ent->nextTrain; if ( next->wait == -1 && next->count ) { next->count = 0; //G_Printf ("Moving next->count %i\n", next->count); } Reached_Tramcar( ent ); } // else // G_Printf ("no can do havent reached yet\n"); } void Blocked_Tramcar( gentity_t *ent, gentity_t *other ) { // remove anything other than a client if ( !other->client ) { // except CTF flags!!!! if ( other->s.eType == ET_ITEM && other->item->giType == IT_TEAM ) { Team_DroppedFlagThink( other ); return; } G_TempEntity( other->s.origin, EV_ITEM_POP ); G_FreeEntity( other ); return; } if ( other->flags & FL_GODMODE ) { other->flags &= ~FL_GODMODE; other->client->ps.stats[STAT_HEALTH] = other->health = 0; } G_Damage( other, ent, ent, NULL, NULL, 99999, 0, MOD_CRUSH ); } /*QUAKED func_tramcar (0 .5 .8) ? START_ON TOGGLE - LEADER health value of 999 will designate the piece as non damageable The leader of the tramcar group must have the leader flag set or you'll end up with co-planer poly heaven. When the health of the Leader of the team hits zero it will unlink and the team will become visible A tramcar is a mover that moves between path_corner target points. all tramcar parts MUST HAVE AN ORIGIN BRUSH. (this is true for all parts) The tramcar spawns at the first target it is pointing at. If you are going to have enemies ride the tramcar it must be placed in its ending position when you bsp the map you can the start it by targeting the desired path_corner "model2" .md3 model to also draw "speed" default 100 "dmg" default 2 "noise" looping sound to play when the train is in motion "target" next path corner "color" constantLight color "light" constantLight radius "type" type of debris ("glass", "wood", "metal", "gibs") default is "wood" "mass" defaults to 75. This determines how much debris is emitted when it explodes. You get one large chunk per 100 of mass (up to 8) and one small chunk per 25 of mass (up to 16). So 800 gives the most. */ void SP_func_tramcar( gentity_t *self ) { int mass; char *type; char *s; char buffer[MAX_QPATH]; VectorClear( self->s.angles ); //if (self->spawnflags & TRAMCAR_BLOCK_STOPS) { // self->damage = 0; // self->s.eFlags |= EF_MOVER_STOP; //} //else { if ( !self->damage ) { self->damage = 100; } //} if ( !self->speed ) { self->speed = 100; } if ( !self->target ) { G_Printf( "func_tramcar without a target at %s\n", vtos( self->r.absmin ) ); G_FreeEntity( self ); return; } if ( self->spawnflags & TRAMCAR_LEADER ) { if ( !self->health ) { self->health = 50; } self->takedamage = qtrue; self->die = Tramcar_die; if ( self->health < 999 ) { self->isProp = qtrue; } } trap_SetBrushModel( self, self->model ); if ( G_SpawnInt( "mass", "75", &mass ) ) { self->count = mass; } else { self->count = 75; } G_SpawnString( "type", "wood", &type ); if ( !Q_stricmp( type,"wood" ) ) { self->key = 0; } else if ( !Q_stricmp( type,"glass" ) ) { self->key = 1; } else if ( !Q_stricmp( type,"metal" ) ) { self->key = 2; } else if ( !Q_stricmp( type,"gibs" ) ) { self->key = 3; } if ( G_SpawnString( "noise", "NOSOUND", &s ) ) { if ( Q_stricmp( s, "nosound" ) ) { Q_strncpyz( buffer, s, sizeof( buffer ) ); self->s.dl_intensity = G_SoundIndex( buffer ); } } else { switch ( self->key ) { case 0: // "wood" self->s.dl_intensity = G_SoundIndex( "sound/world/boardbreak.wav" ); break; case 1: // "glass" self->s.dl_intensity = G_SoundIndex( "sound/world/glassbreak.wav" ); break; case 2: // "metal" self->s.dl_intensity = G_SoundIndex( "sound/world/metalbreak.wav" ); break; case 3: // "gibs" self->s.dl_intensity = G_SoundIndex( "sound/player/gibsplit1.wav" ); break; } } self->s.density = self->count; // pass the "mass" to the client InitTramcar( self ); self->reached = Reached_Tramcar; self->nextthink = level.time + ( FRAMETIME / 2 ); self->think = Think_SetupTrainTargets; self->blocked = Blocked_Tramcar; if ( self->spawnflags & TRAMCAR_TOGGLE ) { self->use = TramCarUse; } } //////////////////////////// // me109 //////////////////////////// void plane_AIScript_AlertEntity( gentity_t *ent ) { // when count reaches 0, the marker is active ent->count--; if ( ent->count <= 0 ) { ent->count = 0; } } /*QUAKED plane_waypoint (.5 .3 0) (-8 -8 -8) (8 8 8) SCRIPTS DIE EXPLODE LAPS ATTACK "count" number of times this waypoint needs to be triggered by an AIScript "alertentity" call before the aircraft changes tracks "track" tells it what track to branch off to there can be several track with the same track name the program will pick one randomly there can be a maximum of eight tracks at any branch the entity will fire its target when reached */ void SP_plane_waypoint( gentity_t *self ) { if ( !self->targetname ) { G_Printf( "plane_waypoint with no targetname at %s\n", vtos( self->s.origin ) ); G_FreeEntity( self ); return; } if ( self->spawnflags & 1 ) { self->AIScript_AlertEntity = plane_AIScript_AlertEntity; } if ( self->count ) { self->count2 = self->count; } if ( self->wait == -1 ) { self->count = 1; } } /*QUAKED props_me109 (.7 .3 .1) (-128 -128 0) (128 128 64) START_ON TOGGLE SPINNINGPROP FIXED_DIE default health = 1000 */ void ExplodePlaneSndFx( gentity_t *self ) { gentity_t *temp; vec3_t dir; gentity_t *part; int i; vec3_t start; temp = G_Spawn(); if ( !temp ) { return; } G_SetOrigin( temp, self->melee->s.pos.trBase ); G_AddEvent( temp, EV_GLOBAL_SOUND, fpexpdebris_snd ); temp->think = G_FreeEntity; temp->nextthink = level.time + 10000; trap_LinkEntity( temp ); // added this because plane may be parked on runway // we may want to add some exotic deaths to parked aircraft if ( self->nextTrain && self->nextTrain->spawnflags & 4 ) { // explode the plane // spawn the wing at the player gentity_t *player; vec3_t vec, ang; player = AICast_FindEntityForName( "player" ); if ( !player ) { return; } VectorSubtract( player->s.origin, self->r.currentOrigin, vec ); vectoangles( vec, ang ); AngleVectors( ang, dir, NULL, NULL ); dir[2] = 1; VectorCopy( self->r.currentOrigin, start ); part = fire_flamebarrel( temp, start, dir ); if ( !part ) { G_Printf( "ExplodePlaneSndFx Failed to spawn part\n" ); return; } part->s.eType = ET_FP_PARTS; part->s.modelindex = wing_part; return; } AngleVectors( self->r.currentAngles, dir, NULL, NULL ); for ( i = 0; i < 4; i++ ) { VectorCopy( self->r.currentOrigin, start ); start[0] += crandom() * 64; start[1] += crandom() * 64; start[2] += crandom() * 32; part = fire_flamebarrel( temp, start, dir ); if ( !part ) { continue; } part->s.eType = ET_FP_PARTS; if ( i == 0 ) { part->s.modelindex = fuse_part; } else if ( i == 1 ) { part->s.modelindex = wing_part; } else if ( i == 2 ) { part->s.modelindex = tail_part; } else { part->s.modelindex = nose_part; } } } void props_me109_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) { G_Printf( "dead\n" ); VectorClear( self->rotate ); VectorSet( self->rotate, 0, 1, 0 ); //sigh if ( self->spawnflags & 8 ) { // FIXED_DIE return; } propExplosionLarge( self ); self->melee->s.loopSound = self->melee->noise_index = 0; ExplodePlaneSndFx( self ); G_FreeEntity( self ); } void props_me109_pain( gentity_t *self, gentity_t *attacker, int damage, vec3_t point ) { vec3_t temp; G_Printf( "pain: health = %i\n", self->health ); VectorCopy( self->r.currentOrigin, temp ); VectorCopy( self->pos3, self->r.currentOrigin ); Spawn_Shard( self, NULL, 6, 999 ); VectorCopy( temp, self->r.currentOrigin ); VectorClear( self->rotate ); VectorSet( self->rotate, 0, 1, 0 ); //sigh } void Plane_Fire_Lead( gentity_t *self ) { vec3_t dir, right; vec3_t pos1, pos2; vec3_t position; AngleVectors( self->r.currentAngles, dir, right, NULL ); VectorCopy( self->r.currentOrigin, position ); VectorMA( position, 64, right, pos1 ); VectorMA( position, -64, right, pos2 ); fire_lead( self, pos1, dir, 12 ); fire_lead( self, pos2, dir, 12 ); } void Plane_Attack( gentity_t *self, qboolean in_PVS ) { if ( self->nextTrain->spawnflags & 16 ) { self->count++; if ( self->count == 3 ) { self->s.density = 8, self->count = 0; if ( in_PVS ) { G_AddEvent( self, EV_GLOBAL_SOUND, fpattack_snd ); } else { G_AddEvent( self, EV_GENERAL_SOUND, fpattack_snd ); } Plane_Fire_Lead( self ); } else { self->s.density = 7; } } else if ( self->spawnflags & 4 ) { // spinning prop self->s.density = 7; } else { self->s.density = 0; } } void props_me109_think( gentity_t *self ) { qboolean in_PVS = qfalse; { gentity_t *player; player = AICast_FindEntityForName( "player" ); if ( player ) { in_PVS = trap_InPVS( player->r.currentOrigin, self->s.pos.trBase ); if ( in_PVS ) { self->melee->s.eType = ET_GENERAL; { float len; vec3_t vec; vec3_t forward; vec3_t dir; vec3_t point; VectorCopy( player->r.currentOrigin, point ); VectorSubtract( player->r.currentOrigin, self->r.currentOrigin, vec ); len = VectorLength( vec ); vectoangles( vec, dir ); AngleVectors( dir, forward, NULL, NULL ); VectorMA( point, len * 0.1, forward, point ); G_SetOrigin( self->melee, point ); } } else { self->melee->s.eType = ET_GENERAL; } trap_LinkEntity( self->melee ); } } Plane_Attack( self, in_PVS ); Calc_Roll( self ); if ( self->health < 250 ) { gentity_t *tent; vec3_t point; VectorCopy( self->r.currentOrigin, point ); tent = G_TempEntity( point, EV_SMOKE ); VectorCopy( point, tent->s.origin ); tent->s.time = 2000; tent->s.time2 = 1000; tent->s.density = 4; tent->s.angles2[0] = 16; tent->s.angles2[1] = 48; tent->s.angles2[2] = 10; self->props_frame_state = plane_choke; self->health--; } if ( self->health > 0 ) { self->nextthink = level.time + 50; if ( self->props_frame_state == plane_choke ) { self->melee->s.loopSound = self->melee->noise_index = fpchoke_snd; } else if ( self->props_frame_state == plane_startup ) { self->melee->s.loopSound = self->melee->noise_index = fpstartup_snd; } else if ( self->props_frame_state == plane_idle ) { self->melee->s.loopSound = self->melee->noise_index = fpidle_snd; } else if ( self->props_frame_state == plane_flyby1 ) { self->melee->s.loopSound = self->melee->noise_index = fpflyby1_snd; } else if ( self->props_frame_state == plane_flyby2 ) { self->melee->s.loopSound = self->melee->noise_index = fpflyby2_snd; } } else { propExplosionLarge( self ); self->melee->s.loopSound = self->melee->noise_index = 0; ExplodePlaneSndFx( self ); G_FreeEntity( self->melee ); G_FreeEntity( self ); } } void Think_SetupAirplaneWaypoints( gentity_t *ent ) { gentity_t *path, *next, *start; ent->nextTrain = G_Find( NULL, FOFS( targetname ), ent->target ); if ( !ent->nextTrain ) { G_Printf( "plane at %s with an unfound target\n", vtos( ent->r.absmin ) ); return; } start = NULL; for ( path = ent->nextTrain ; path != start ; path = next ) { if ( !start ) { start = path; } if ( !path->target ) { G_Printf( "plane at %s without a target\n", vtos( path->s.origin ) ); return; } // find a path_corner among the targets // there may also be other targets that get fired when the corner // is reached next = NULL; do { next = G_Find( next, FOFS( targetname ), path->target ); if ( !next ) { G_Printf( "plane at %s without a target path_corner\n", vtos( path->s.origin ) ); return; } } while ( strcmp( next->classname, "plane_waypoint" ) ); path->nextTrain = next; } if ( ent->spawnflags & 2 ) { // Toggle VectorCopy( ent->nextTrain->s.origin, ent->s.pos.trBase ); VectorCopy( ent->nextTrain->s.origin, ent->r.currentOrigin ); trap_LinkEntity( ent ); } else { Reached_Tramcar( ent ); } } void PlaneUse( gentity_t *ent, gentity_t *other, gentity_t *activator ) { gentity_t *next; if ( level.time >= ent->s.pos.trTime + ent->s.pos.trDuration ) { next = ent->nextTrain; if ( next->wait == -1 && next->count ) { next->count = 0; //G_Printf ("Moving next->count %i\n", next->count); } Reached_Tramcar( ent ); } // else // G_Printf ("no can do havent reached yet\n"); } void InitPlaneSpeaker( gentity_t *ent ) { gentity_t *snd; snd = G_Spawn(); snd->noise_index = fploop_snd; snd->s.eType = ET_SPEAKER; snd->s.eventParm = snd->noise_index; snd->s.frame = 0; snd->s.clientNum = 0; snd->s.loopSound = snd->noise_index; snd->r.svFlags |= SVF_BROADCAST; VectorCopy( ent->s.origin, snd->s.pos.trBase ); ent->melee = snd; trap_LinkEntity( snd ); } void SP_props_me109( gentity_t *ent ) { VectorSet( ent->r.mins, -128, -128, -128 ); VectorSet( ent->r.maxs, 128, 128, 128 ); ent->clipmask = CONTENTS_SOLID; ent->r.contents = CONTENTS_SOLID; ent->r.svFlags = SVF_USE_CURRENT_ORIGIN; ent->s.eType = ET_MOVER; ent->isProp = qtrue; ent->s.modelindex = G_ModelIndex( "models/mapobjects/vehicles/m109.md3" ); if ( !ent->health ) { ent->health = 500; } ent->takedamage = qtrue; ent->die = props_me109_die; ent->pain = props_me109_pain; ent->reached = Reached_Tramcar; ent->nextthink = level.time + ( FRAMETIME / 2 ); ent->think = Think_SetupAirplaneWaypoints; ent->use = PlaneUse; if ( !( ent->speed ) ) { ent->speed = 1000; } G_SetOrigin( ent, ent->s.origin ); G_SetAngle( ent, ent->s.angles ); if ( ent->spawnflags & 4 ) { ent->s.density = 7; } trap_LinkEntity( ent ); fploop_snd = G_SoundIndex( "sound/fighterplane/fploop.wav" ); fpchoke_snd = G_SoundIndex( "sound/fighterplane/fpchoke.wav" ); fpattack_snd = G_SoundIndex( "sound/weapons/mg42/37mm.wav" ); fpexpdebris_snd = G_SoundIndex( "sound/fighterplane/fpexpdebris.wav" ); fpflyby1_snd = G_SoundIndex( "sound/fighterplane/fpflyby1.wav" ); fpflyby2_snd = G_SoundIndex( "sound/fighterplane/fpflyby2.wav" ); fpidle_snd = G_SoundIndex( "sound/fighterplane/fpidle.wav" ); fpstartup_snd = G_SoundIndex( "sound/fighterplane/fpstartup.wav" ); fuse_part = G_ModelIndex( "models/mapobjects/vehicles/m109debris_a.md3" ); wing_part = G_ModelIndex( "models/mapobjects/vehicles/m109debris_b.md3" ); tail_part = G_ModelIndex( "models/mapobjects/vehicles/m109debris_c.md3" ); nose_part = G_ModelIndex( "models/mapobjects/vehicles/m109debris_d.md3" ); crash_part = G_ModelIndex( "models/mapobjects/vehicles/m109crash.md3" ); InitPlaneSpeaker( ent ); } ///////////////////////// // TRUCK DRIVE ///////////////////////// /*QUAKED truck_cam (.7 .3 .1) ? START_ON TOGGLE - - */ void truck_cam_touch( gentity_t *self, gentity_t *other, trace_t *trace ) { gentity_t *player; player = AICast_FindEntityForName( "player" ); if ( player && player != other ) { // G_Printf ("other: %s\n", other->aiName); return; } if ( !self->nextTrain ) { self->touch = NULL; return; } // lock the player to the moving truck { vec3_t point; trap_UnlinkEntity( other ); // VectorCopy ( self->r.currentOrigin, other->client->ps.origin ); VectorCopy( self->r.currentOrigin, point ); point[2] = other->client->ps.origin[2]; VectorCopy( point, other->client->ps.origin ); // save results of pmove BG_PlayerStateToEntityState( &other->client->ps, &other->s, qtrue ); // use the precise origin for linking VectorCopy( other->client->ps.origin, other->r.currentOrigin ); other->client->ps.persistant[PERS_HWEAPON_USE] = 1; trap_LinkEntity( other ); } } void truck_cam_think( gentity_t *ent ) { ent->nextthink = level.time + ( FRAMETIME / 2 ); } void SP_truck_cam( gentity_t *self ) { int mass; VectorClear( self->s.angles ); if ( !self->speed ) { self->speed = 100; } if ( !self->target ) { G_Printf( "truck_cam without a target at %s\n", vtos( self->r.absmin ) ); G_FreeEntity( self ); return; } trap_SetBrushModel( self, self->model ); if ( G_SpawnInt( "mass", "20", &mass ) ) { self->count = mass; } else { self->count = 20; } InitTramcar( self ); self->nextthink = level.time + ( FRAMETIME / 2 ); self->think = Think_SetupTrainTargets; self->touch = truck_cam_touch; self->s.loopSound = 0; self->props_frame_state = 0; self->clipmask = CONTENTS_SOLID; // G_SetOrigin (self, self->s.origin); // G_SetAngle (self, self->s.angles); self->reached = Reached_Tramcar; self->s.density = 6; //start_drive_grind_gears truck_sound = G_SoundIndex( "sound/vehicles/start_drive_grind_gears_01_11k.wav" ); // truck_sound = G_SoundIndex ( "sound/vehicles/tankmove1.wav" ); truck_idle_snd = G_SoundIndex( "sound/vehicles/truckidle.wav" ); truck_gear1_snd = G_SoundIndex( "sound/vehicles/truckgear1.wav" ); truck_gear2_snd = G_SoundIndex( "sound/vehicles/truckgear2.wav" ); truck_gear3_snd = G_SoundIndex( "sound/vehicles/truckgear3.wav" ); truck_reverse_snd = G_SoundIndex( "sound/vehicles/truckreverse.wav" ); truck_moving_snd = G_SoundIndex( "sound/vehicles/truckmoving.wav" ); truck_breaking_snd = G_SoundIndex( "sound/vehicles/truckbreaking.wav" ); truck_bouncy1_snd = G_SoundIndex( "sound/vehicles/truckbouncy1.wav" ); truck_bouncy2_snd = G_SoundIndex( "sound/vehicles/truckbouncy2.wav" ); truck_bouncy3_snd = G_SoundIndex( "sound/vehicles/truckbouncy3.wav" ); } ///////////////////////// // camera cam ///////////////////////// /*QUAKED camera_cam (.5 .7 .3) (-8 -8 -8) (8 8 8) ON TRACKING MOVING - "track" is the targetname of the entity providing the starting direction use an info_notnull */ void delayOnthink( gentity_t *ent ) { if ( ent->melee ) { ent->melee->use( ent->melee, NULL, NULL ); } } void Init_Camera( gentity_t *ent ) { vec3_t move; float distance; ent->moverState = MOVER_POS1; ent->r.svFlags = SVF_USE_CURRENT_ORIGIN; ent->s.eType = ET_MOVER; VectorCopy( ent->pos1, ent->r.currentOrigin ); trap_LinkEntity( ent ); ent->s.pos.trType = TR_STATIONARY; VectorCopy( ent->pos1, ent->s.pos.trBase ); // calculate time to reach second position from speed VectorSubtract( ent->pos2, ent->pos1, move ); distance = VectorLength( move ); if ( !ent->speed ) { ent->speed = 100; } VectorScale( move, ent->speed, ent->s.pos.trDelta ); ent->s.pos.trDuration = distance * 1000 / ent->speed; if ( ent->s.pos.trDuration <= 0 ) { ent->s.pos.trDuration = 1; } } void camera_cam_think( gentity_t *ent ) { gentity_t *player; player = AICast_FindEntityForName( "player" ); if ( !player ) { return; } if ( ent->spawnflags & 2 ) { // tracking vec3_t point; trap_UnlinkEntity( player ); // VectorCopy ( self->r.currentOrigin, other->client->ps.origin ); VectorCopy( ent->r.currentOrigin, point ); point[2] = player->client->ps.origin[2]; VectorCopy( point, player->client->ps.origin ); // save results of pmove BG_PlayerStateToEntityState( &player->client->ps, &player->s, qtrue ); // use the precise origin for linking VectorCopy( player->client->ps.origin, player->r.currentOrigin ); // tracking { gentity_t *target = NULL; vec3_t dang; vec3_t vec; if ( ent->track ) { target = G_Find( NULL, FOFS( targetname ), ent->track ); } if ( target ) { VectorSubtract( target->r.currentOrigin, ent->r.currentOrigin, vec ); vectoangles( vec, dang ); SetClientViewAngle( player, dang ); VectorCopy( ent->r.currentOrigin, ent->s.pos.trBase ); VectorCopy( dang, ent->s.apos.trBase ); trap_LinkEntity( ent ); } } trap_LinkEntity( player ); } ent->nextthink = level.time + ( FRAMETIME / 2 ); } void camera_cam_use( gentity_t *ent, gentity_t *other, gentity_t *activator ) { gentity_t *player; player = AICast_FindEntityForName( "player" ); if ( !player ) { return; } if ( !( ent->spawnflags & 1 ) ) { ent->think = camera_cam_think; ent->nextthink = level.time + ( FRAMETIME / 2 ); ent->spawnflags |= 1; { player->client->ps.persistant[PERS_HWEAPON_USE] = 1; player->client->ps.viewlocked = 4; player->client->ps.viewlocked_entNum = ent->s.number; } } else { ent->spawnflags &= ~1; ent->think = NULL; { player->client->ps.persistant[PERS_HWEAPON_USE] = 0; player->client->ps.viewlocked = 0; player->client->ps.viewlocked_entNum = 0; } } } void camera_cam_firstthink( gentity_t *ent ) { gentity_t *target = NULL; vec3_t dang; vec3_t vec; if ( ent->track ) { target = G_Find( NULL, FOFS( targetname ), ent->track ); } if ( target ) { VectorSubtract( target->s.origin, ent->r.currentOrigin, vec ); vectoangles( vec, dang ); G_SetAngle( ent, dang ); } if ( ent->target ) { ent->nextthink = level.time + ( FRAMETIME / 2 ); ent->think = Think_SetupTrainTargets; } } void SP_camera_cam( gentity_t *ent ) { Init_Camera( ent ); ent->r.svFlags = SVF_USE_CURRENT_ORIGIN; ent->s.eType = ET_MOVER; G_SetOrigin( ent, ent->s.origin ); G_SetAngle( ent, ent->s.angles ); ent->reached = Reached_Tramcar; ent->nextthink = level.time + ( FRAMETIME / 2 ); ent->think = camera_cam_firstthink; ent->use = camera_cam_use; if ( ent->spawnflags & 1 ) { // On gentity_t *delayOn; delayOn = G_Spawn(); delayOn->think = delayOnthink; delayOn->nextthink = level.time + 1000; delayOn->melee = ent; trap_LinkEntity( delayOn ); } } /*QUAKED screen_fade (.3 .7 .9) (-8 -8 -8) (8 8 8) "wait" duration of fade out "delay" duration of fade in 1 = 1 sec .5 = .5 sec defaults are .5 sec */ void screen_fade_use( gentity_t *ent, gentity_t *other, gentity_t *activator ) { if ( ent->spawnflags & 1 ) { // fade out trap_SetConfigstring( CS_SCREENFADE, va( "1 %i %i", level.time + 100, (int) ent->wait ) ); ent->spawnflags &= ~1; } else { // fade in trap_SetConfigstring( CS_SCREENFADE, va( "0 %i %i", level.time + 100, (int) ent->delay ) ); ent->spawnflags |= 1; } } void SP_screen_fade( gentity_t *ent ) { ent->use = screen_fade_use; if ( !ent->wait ) { ent->wait = 500; } if ( !ent->delay ) { ent->delay = 500; } } /*QUAKED camera_reset_player (.5 .7 .3) ? touched will record the players position and fire off its targets and or cameras used will reset the player back to his last position */ void mark_players_pos( gentity_t *ent, gentity_t *other, trace_t *trace ) { gentity_t *player; player = AICast_FindEntityForName( "player" ); if ( player == other ) { VectorCopy( player->r.currentOrigin, ent->s.origin2 ); VectorCopy( player->r.currentAngles, ent->s.angles2 ); G_UseTargets( ent, NULL ); } } void reset_players_pos( gentity_t *ent, gentity_t *other, gentity_t *activator ) { gentity_t *player; player = AICast_FindEntityForName( "player" ); if ( !player ) { return; } trap_UnlinkEntity( player ); VectorCopy( ent->s.origin2, player->client->ps.origin ); // save results of pmove BG_PlayerStateToEntityState( &player->client->ps, &player->s, qtrue ); // use the precise origin for linking VectorCopy( player->client->ps.origin, player->r.currentOrigin ); SetClientViewAngle( player, ent->s.angles2 ); player->client->ps.persistant[PERS_HWEAPON_USE] = 0; player->client->ps.viewlocked = 0; player->client->ps.viewlocked_entNum = 0; trap_LinkEntity( player ); } extern void InitTrigger( gentity_t *self ); void SP_camera_reset_player( gentity_t *ent ) { InitTrigger( ent ); ent->r.contents = CONTENTS_TRIGGER; ent->touch = mark_players_pos; ent->use = reset_players_pos; trap_LinkEntity( ent ); }