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