1 /*
2 ===========================================================================
3 Copyright (C) 1999-2005 Id Software, Inc.
4
5 This file is part of Quake III Arena source code.
6
7 Quake III Arena source code is free software; you can redistribute it
8 and/or modify it under the terms of the GNU General Public License as
9 published by the Free Software Foundation; either version 2 of the License,
10 or (at your option) any later version.
11
12 Quake III Arena source code is distributed in the hope that it will be
13 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with Quake III Arena source code; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 ===========================================================================
21 */
22 //
23 // g_misc.c
24
25 #include "g_local.h"
26
27
28 /*QUAKED func_group (0 0 0) ?
29 Used to group brushes together just for editor convenience. They are turned into normal brushes by the utilities.
30 */
31
32
33 /*QUAKED info_camp (0 0.5 0) (-4 -4 -4) (4 4 4)
34 Used as a positional target for calculations in the utilities (spotlights, etc), but removed during gameplay.
35 */
SP_info_camp(gentity_t * self)36 void SP_info_camp( gentity_t *self ) {
37 G_SetOrigin( self, self->s.origin );
38 }
39
40
41 /*QUAKED info_null (0 0.5 0) (-4 -4 -4) (4 4 4)
42 Used as a positional target for calculations in the utilities (spotlights, etc), but removed during gameplay.
43 */
SP_info_null(gentity_t * self)44 void SP_info_null( gentity_t *self ) {
45 G_FreeEntity( self );
46 }
47
48
49 /*QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4)
50 Used as a positional target for in-game calculation, like jumppad targets.
51 target_position does the same thing
52 */
SP_info_notnull(gentity_t * self)53 void SP_info_notnull( gentity_t *self ){
54 G_SetOrigin( self, self->s.origin );
55 }
56
57
58 /*QUAKED light (0 1 0) (-8 -8 -8) (8 8 8) linear
59 Non-displayed light.
60 "light" overrides the default 300 intensity.
61 Linear checbox gives linear falloff instead of inverse square
62 Lights pointed at a target will be spotlights.
63 "radius" overrides the default 64 unit radius of a spotlight at the target point.
64 */
SP_light(gentity_t * self)65 void SP_light( gentity_t *self ) {
66 G_FreeEntity( self );
67 }
68
69
70
71 /*
72 =================================================================================
73
74 TELEPORTERS
75
76 =================================================================================
77 */
78
TeleportPlayer(gentity_t * player,vec3_t origin,vec3_t angles)79 void TeleportPlayer( gentity_t *player, vec3_t origin, vec3_t angles ) {
80 gentity_t *tent;
81
82 // use temp events at source and destination to prevent the effect
83 // from getting dropped by a second player event
84 if ( player->client->sess.sessionTeam != TEAM_SPECTATOR ) {
85 tent = G_TempEntity( player->client->ps.origin, EV_PLAYER_TELEPORT_OUT );
86 tent->s.clientNum = player->s.clientNum;
87
88 tent = G_TempEntity( origin, EV_PLAYER_TELEPORT_IN );
89 tent->s.clientNum = player->s.clientNum;
90 }
91
92 // unlink to make sure it can't possibly interfere with G_KillBox
93 trap_UnlinkEntity (player);
94
95 VectorCopy ( origin, player->client->ps.origin );
96 player->client->ps.origin[2] += 1;
97
98 // spit the player out
99 AngleVectors( angles, player->client->ps.velocity, NULL, NULL );
100 VectorScale( player->client->ps.velocity, 400, player->client->ps.velocity );
101 player->client->ps.pm_time = 160; // hold time
102 player->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
103
104 // toggle the teleport bit so the client knows to not lerp
105 player->client->ps.eFlags ^= EF_TELEPORT_BIT;
106
107 // set angles
108 SetClientViewAngle( player, angles );
109
110 // kill anything at the destination
111 if ( player->client->sess.sessionTeam != TEAM_SPECTATOR ) {
112 G_KillBox (player);
113 }
114
115 // save results of pmove
116 BG_PlayerStateToEntityState( &player->client->ps, &player->s, qtrue );
117
118 // use the precise origin for linking
119 VectorCopy( player->client->ps.origin, player->r.currentOrigin );
120
121 if ( player->client->sess.sessionTeam != TEAM_SPECTATOR ) {
122 trap_LinkEntity (player);
123 }
124 }
125
126
127 /*QUAKED misc_teleporter_dest (1 0 0) (-32 -32 -24) (32 32 -16)
128 Point teleporters at these.
129 Now that we don't have teleport destination pads, this is just
130 an info_notnull
131 */
SP_misc_teleporter_dest(gentity_t * ent)132 void SP_misc_teleporter_dest( gentity_t *ent ) {
133 }
134
135
136 //===========================================================
137
138 /*QUAKED misc_model (1 0 0) (-16 -16 -16) (16 16 16)
139 "model" arbitrary .md3 file to display
140 */
SP_misc_model(gentity_t * ent)141 void SP_misc_model( gentity_t *ent ) {
142
143 #if 0
144 ent->s.modelindex = G_ModelIndex( ent->model );
145 VectorSet (ent->mins, -16, -16, -16);
146 VectorSet (ent->maxs, 16, 16, 16);
147 trap_LinkEntity (ent);
148
149 G_SetOrigin( ent, ent->s.origin );
150 VectorCopy( ent->s.angles, ent->s.apos.trBase );
151 #else
152 G_FreeEntity( ent );
153 #endif
154 }
155
156 //===========================================================
157
locateCamera(gentity_t * ent)158 void locateCamera( gentity_t *ent ) {
159 vec3_t dir;
160 gentity_t *target;
161 gentity_t *owner;
162
163 owner = G_PickTarget( ent->target );
164 if ( !owner ) {
165 G_Printf( "Couldn't find target for misc_partal_surface\n" );
166 G_FreeEntity( ent );
167 return;
168 }
169 ent->r.ownerNum = owner->s.number;
170
171 // frame holds the rotate speed
172 if ( owner->spawnflags & 1 ) {
173 ent->s.frame = 25;
174 } else if ( owner->spawnflags & 2 ) {
175 ent->s.frame = 75;
176 }
177
178 // swing camera ?
179 if ( owner->spawnflags & 4 ) {
180 // set to 0 for no rotation at all
181 ent->s.powerups = 0;
182 }
183 else {
184 ent->s.powerups = 1;
185 }
186
187 // clientNum holds the rotate offset
188 ent->s.clientNum = owner->s.clientNum;
189
190 VectorCopy( owner->s.origin, ent->s.origin2 );
191
192 // see if the portal_camera has a target
193 target = G_PickTarget( owner->target );
194 if ( target ) {
195 VectorSubtract( target->s.origin, owner->s.origin, dir );
196 VectorNormalize( dir );
197 } else {
198 G_SetMovedir( owner->s.angles, dir );
199 }
200
201 ent->s.eventParm = DirToByte( dir );
202 }
203
204 /*QUAKED misc_portal_surface (0 0 1) (-8 -8 -8) (8 8 8)
205 The portal surface nearest this entity will show a view from the targeted misc_portal_camera, or a mirror view if untargeted.
206 This must be within 64 world units of the surface!
207 */
SP_misc_portal_surface(gentity_t * ent)208 void SP_misc_portal_surface(gentity_t *ent) {
209 VectorClear( ent->r.mins );
210 VectorClear( ent->r.maxs );
211 trap_LinkEntity (ent);
212
213 ent->r.svFlags = SVF_PORTAL;
214 ent->s.eType = ET_PORTAL;
215
216 if ( !ent->target ) {
217 VectorCopy( ent->s.origin, ent->s.origin2 );
218 } else {
219 ent->think = locateCamera;
220 ent->nextthink = level.time + 100;
221 }
222 }
223
224 /*QUAKED misc_portal_camera (0 0 1) (-8 -8 -8) (8 8 8) slowrotate fastrotate noswing
225 The target for a misc_portal_director. You can set either angles or target another entity to determine the direction of view.
226 "roll" an angle modifier to orient the camera around the target vector;
227 */
SP_misc_portal_camera(gentity_t * ent)228 void SP_misc_portal_camera(gentity_t *ent) {
229 float roll;
230
231 VectorClear( ent->r.mins );
232 VectorClear( ent->r.maxs );
233 trap_LinkEntity (ent);
234
235 G_SpawnFloat( "roll", "0", &roll );
236
237 ent->s.clientNum = roll/360.0 * 256;
238 }
239
240 /*
241 ======================================================================
242
243 SHOOTERS
244
245 ======================================================================
246 */
247
Use_Shooter(gentity_t * ent,gentity_t * other,gentity_t * activator)248 void Use_Shooter( gentity_t *ent, gentity_t *other, gentity_t *activator ) {
249 vec3_t dir;
250 float deg;
251 vec3_t up, right;
252
253 // see if we have a target
254 if ( ent->enemy ) {
255 VectorSubtract( ent->enemy->r.currentOrigin, ent->s.origin, dir );
256 VectorNormalize( dir );
257 } else {
258 VectorCopy( ent->movedir, dir );
259 }
260
261 // randomize a bit
262 PerpendicularVector( up, dir );
263 CrossProduct( up, dir, right );
264
265 deg = crandom() * ent->random;
266 VectorMA( dir, deg, up, dir );
267
268 deg = crandom() * ent->random;
269 VectorMA( dir, deg, right, dir );
270
271 VectorNormalize( dir );
272
273 switch ( ent->s.weapon ) {
274 case WP_GRENADE_LAUNCHER:
275 fire_grenade( ent, ent->s.origin, dir );
276 break;
277 case WP_ROCKET_LAUNCHER:
278 fire_rocket( ent, ent->s.origin, dir );
279 break;
280 case WP_PLASMAGUN:
281 fire_plasma( ent, ent->s.origin, dir );
282 break;
283 }
284
285 G_AddEvent( ent, EV_FIRE_WEAPON, 0 );
286 }
287
288
InitShooter_Finish(gentity_t * ent)289 static void InitShooter_Finish( gentity_t *ent ) {
290 ent->enemy = G_PickTarget( ent->target );
291 ent->think = 0;
292 ent->nextthink = 0;
293 }
294
InitShooter(gentity_t * ent,int weapon)295 void InitShooter( gentity_t *ent, int weapon ) {
296 ent->use = Use_Shooter;
297 ent->s.weapon = weapon;
298
299 RegisterItem( BG_FindItemForWeapon( weapon ) );
300
301 G_SetMovedir( ent->s.angles, ent->movedir );
302
303 if ( !ent->random ) {
304 ent->random = 1.0;
305 }
306 ent->random = sin( M_PI * ent->random / 180 );
307 // target might be a moving object, so we can't set movedir for it
308 if ( ent->target ) {
309 ent->think = InitShooter_Finish;
310 ent->nextthink = level.time + 500;
311 }
312 trap_LinkEntity( ent );
313 }
314
315 /*QUAKED shooter_rocket (1 0 0) (-16 -16 -16) (16 16 16)
316 Fires at either the target or the current direction.
317 "random" the number of degrees of deviance from the taget. (1.0 default)
318 */
SP_shooter_rocket(gentity_t * ent)319 void SP_shooter_rocket( gentity_t *ent ) {
320 InitShooter( ent, WP_ROCKET_LAUNCHER );
321 }
322
323 /*QUAKED shooter_plasma (1 0 0) (-16 -16 -16) (16 16 16)
324 Fires at either the target or the current direction.
325 "random" is the number of degrees of deviance from the taget. (1.0 default)
326 */
SP_shooter_plasma(gentity_t * ent)327 void SP_shooter_plasma( gentity_t *ent ) {
328 InitShooter( ent, WP_PLASMAGUN);
329 }
330
331 /*QUAKED shooter_grenade (1 0 0) (-16 -16 -16) (16 16 16)
332 Fires at either the target or the current direction.
333 "random" is the number of degrees of deviance from the taget. (1.0 default)
334 */
SP_shooter_grenade(gentity_t * ent)335 void SP_shooter_grenade( gentity_t *ent ) {
336 InitShooter( ent, WP_GRENADE_LAUNCHER);
337 }
338
339
340 #ifdef MISSIONPACK
PortalDie(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int mod)341 static void PortalDie (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod) {
342 G_FreeEntity( self );
343 //FIXME do something more interesting
344 }
345
346
DropPortalDestination(gentity_t * player)347 void DropPortalDestination( gentity_t *player ) {
348 gentity_t *ent;
349 vec3_t snapped;
350
351 // create the portal destination
352 ent = G_Spawn();
353 ent->s.modelindex = G_ModelIndex( "models/powerups/teleporter/tele_exit.md3" );
354
355 VectorCopy( player->s.pos.trBase, snapped );
356 SnapVector( snapped );
357 G_SetOrigin( ent, snapped );
358 VectorCopy( player->r.mins, ent->r.mins );
359 VectorCopy( player->r.maxs, ent->r.maxs );
360
361 ent->classname = "hi_portal destination";
362 ent->s.pos.trType = TR_STATIONARY;
363
364 ent->r.contents = CONTENTS_CORPSE;
365 ent->takedamage = qtrue;
366 ent->health = 200;
367 ent->die = PortalDie;
368
369 VectorCopy( player->s.apos.trBase, ent->s.angles );
370
371 ent->think = G_FreeEntity;
372 ent->nextthink = level.time + 2 * 60 * 1000;
373
374 trap_LinkEntity( ent );
375
376 player->client->portalID = ++level.portalSequence;
377 ent->count = player->client->portalID;
378
379 // give the item back so they can drop the source now
380 player->client->ps.stats[STAT_HOLDABLE_ITEM] = BG_FindItem( "Portal" ) - bg_itemlist;
381 }
382
383
PortalTouch(gentity_t * self,gentity_t * other,trace_t * trace)384 static void PortalTouch( gentity_t *self, gentity_t *other, trace_t *trace) {
385 gentity_t *destination;
386
387 // see if we will even let other try to use it
388 if( other->health <= 0 ) {
389 return;
390 }
391 if( !other->client ) {
392 return;
393 }
394 // if( other->client->ps.persistant[PERS_TEAM] != self->spawnflags ) {
395 // return;
396 // }
397
398 if ( other->client->ps.powerups[PW_NEUTRALFLAG] ) { // only happens in One Flag CTF
399 Drop_Item( other, BG_FindItemForPowerup( PW_NEUTRALFLAG ), 0 );
400 other->client->ps.powerups[PW_NEUTRALFLAG] = 0;
401 }
402 else if ( other->client->ps.powerups[PW_REDFLAG] ) { // only happens in standard CTF
403 Drop_Item( other, BG_FindItemForPowerup( PW_REDFLAG ), 0 );
404 other->client->ps.powerups[PW_REDFLAG] = 0;
405 }
406 else if ( other->client->ps.powerups[PW_BLUEFLAG] ) { // only happens in standard CTF
407 Drop_Item( other, BG_FindItemForPowerup( PW_BLUEFLAG ), 0 );
408 other->client->ps.powerups[PW_BLUEFLAG] = 0;
409 }
410
411 // find the destination
412 destination = NULL;
413 while( (destination = G_Find(destination, FOFS(classname), "hi_portal destination")) != NULL ) {
414 if( destination->count == self->count ) {
415 break;
416 }
417 }
418
419 // if there is not one, die!
420 if( !destination ) {
421 if( self->pos1[0] || self->pos1[1] || self->pos1[2] ) {
422 TeleportPlayer( other, self->pos1, self->s.angles );
423 }
424 G_Damage( other, other, other, NULL, NULL, 100000, DAMAGE_NO_PROTECTION, MOD_TELEFRAG );
425 return;
426 }
427
428 TeleportPlayer( other, destination->s.pos.trBase, destination->s.angles );
429 }
430
431
PortalEnable(gentity_t * self)432 static void PortalEnable( gentity_t *self ) {
433 self->touch = PortalTouch;
434 self->think = G_FreeEntity;
435 self->nextthink = level.time + 2 * 60 * 1000;
436 }
437
438
DropPortalSource(gentity_t * player)439 void DropPortalSource( gentity_t *player ) {
440 gentity_t *ent;
441 gentity_t *destination;
442 vec3_t snapped;
443
444 // create the portal source
445 ent = G_Spawn();
446 ent->s.modelindex = G_ModelIndex( "models/powerups/teleporter/tele_enter.md3" );
447
448 VectorCopy( player->s.pos.trBase, snapped );
449 SnapVector( snapped );
450 G_SetOrigin( ent, snapped );
451 VectorCopy( player->r.mins, ent->r.mins );
452 VectorCopy( player->r.maxs, ent->r.maxs );
453
454 ent->classname = "hi_portal source";
455 ent->s.pos.trType = TR_STATIONARY;
456
457 ent->r.contents = CONTENTS_CORPSE | CONTENTS_TRIGGER;
458 ent->takedamage = qtrue;
459 ent->health = 200;
460 ent->die = PortalDie;
461
462 trap_LinkEntity( ent );
463
464 ent->count = player->client->portalID;
465 player->client->portalID = 0;
466
467 // ent->spawnflags = player->client->ps.persistant[PERS_TEAM];
468
469 ent->nextthink = level.time + 1000;
470 ent->think = PortalEnable;
471
472 // find the destination
473 destination = NULL;
474 while( (destination = G_Find(destination, FOFS(classname), "hi_portal destination")) != NULL ) {
475 if( destination->count == ent->count ) {
476 VectorCopy( destination->s.pos.trBase, ent->pos1 );
477 break;
478 }
479 }
480
481 }
482 #endif
483