1 /*
2 ===========================================================================
3 Copyright (C) 1999 - 2005, Id Software, Inc.
4 Copyright (C) 2000 - 2013, Raven Software, Inc.
5 Copyright (C) 2001 - 2013, Activision, Inc.
6 Copyright (C) 2013 - 2015, OpenJK contributors
7
8 This file is part of the OpenJK source code.
9
10 OpenJK is free software; you can redistribute it and/or modify it
11 under the terms of the GNU General Public License version 2 as
12 published by the Free Software Foundation.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, see <http://www.gnu.org/licenses/>.
21 ===========================================================================
22 */
23
24 // g_misc.c
25
26 #include "g_local.h"
27 #include "g_functions.h"
28 #include "g_nav.h"
29 #include "g_items.h"
30 #include "../cgame/cg_local.h"
31 #include "b_local.h"
32
33 extern gentity_t *G_FindDoorTrigger( gentity_t *door );
34 extern void G_SetEnemy( gentity_t *self, gentity_t *enemy );
35 extern void SetMiscModelDefaults( gentity_t *ent, useFunc_t use_func, const char *material, int solid_mask,int animFlag,
36 qboolean take_damage, qboolean damage_model);
37
38 #define MAX_AMMO_GIVE 4
39
40
41
42 /*QUAKED func_group (0 0 0) ?
43 Used to group brushes together just for editor convenience. They are turned into normal brushes by the utilities.
44
45 q3map_onlyvertexlighting 1 = brush only gets vertex lighting (reduces bsp size!)
46 */
47
48
49 /*QUAKED info_null (0 0.5 0) (-4 -4 -4) (4 4 4) LIGHT
50 Used as a positional target for calculations in the utilities (spotlights, etc), but removed during gameplay.
51
52 LIGHT - If this info_null is only targeted by a non-switchable light (a light without a targetname), it does NOT spawn in at all and doesn't count towards the # of entities on the map, even at map spawn/load
53 */
SP_info_null(gentity_t * self)54 void SP_info_null( gentity_t *self ) {
55 if ( (self->spawnflags&1) )
56 {//only used as a light target, so bugger off
57 G_FreeEntity( self );
58 return;
59 }
60 //FIXME: store targetname and vector (origin) in a list for further reference... remove after 1st second of game?
61 G_SetOrigin( self, self->s.origin );
62 self->e_ThinkFunc = thinkF_G_FreeEntity;
63 //Give other ents time to link
64 self->nextthink = level.time + START_TIME_REMOVE_ENTS;
65 }
66
67
68 /*QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4)
69 Used as a positional target for in-game calculation, like jumppad targets.
70 target_position does the same thing
71 */
SP_info_notnull(gentity_t * self)72 void SP_info_notnull( gentity_t *self ){
73 //FIXME: store in ref_tag system?
74 G_SetOrigin( self, self->s.origin );
75 }
76
77
78 /*QUAKED lightJunior (0 0.7 0.3) (-8 -8 -8) (8 8 8) nonlinear angle negative_spot negative_point
79 Non-displayed light that only affects dynamic game models, but does not contribute to lightmaps
80 "light" overrides the default 300 intensity.
81 Nonlinear checkbox gives inverse square falloff instead of linear
82 Angle adds light:surface angle calculations (only valid for "Linear" lights) (wolf)
83 Lights pointed at a target will be spotlights.
84 "radius" overrides the default 64 unit radius of a spotlight at the target point.
85 "fade" falloff/radius adjustment value. multiply the run of the slope by "fade" (1.0f default) (only valid for "Linear" lights) (wolf)
86 */
87
88 /*QUAKED light (0 1 0) (-8 -8 -8) (8 8 8) linear noIncidence START_OFF
89 Non-displayed light.
90 "light" overrides the default 300 intensity. - affects size
91 a negative "light" will subtract the light's color
92 'Linear' checkbox gives linear falloff instead of inverse square
93 'noIncidence' checkbox makes lighting smoother
94 Lights pointed at a target will be spotlights.
95 "radius" overrides the default 64 unit radius of a spotlight at the target point.
96 "scale" multiplier for the light intensity - does not affect size (default 1)
97 greater than 1 is brighter, between 0 and 1 is dimmer.
98 "color" sets the light's color
99 "targetname" to indicate a switchable light - NOTE that all lights with the same targetname will be grouped together and act as one light (ie: don't mix colors, styles or start_off flag)
100 "style" to specify a specify light style, even for switchable lights!
101 "style_off" light style to use when switched off (Only for switchable lights)
102
103 1 FLICKER (first variety)
104 2 SLOW STRONG PULSE
105 3 CANDLE (first variety)
106 4 FAST STROBE
107 5 GENTLE PULSE 1
108 6 FLICKER (second variety)
109 7 CANDLE (second variety)
110 8 CANDLE (third variety)
111 9 SLOW STROBE (fourth variety)
112 10 FLUORESCENT FLICKER
113 11 SLOW PULSE NOT FADE TO BLACK
114 12 FAST PULSE FOR JEREMY
115 13 Test Blending
116 */
misc_lightstyle_set(gentity_t * ent)117 static void misc_lightstyle_set ( gentity_t *ent)
118 {
119 const int mLightStyle = ent->count;
120 const int mLightSwitchStyle = ent->bounceCount;
121 const int mLightOffStyle = ent->fly_sound_debounce_time;
122 if (!ent->misc_dlight_active)
123 { //turn off
124 if (mLightOffStyle) //i have a light style i'd like to use when off
125 {
126 char lightstyle[32];
127 gi.GetConfigstring(CS_LIGHT_STYLES + (mLightOffStyle*3)+0, lightstyle, 32);
128 gi.SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+0, lightstyle);
129
130 gi.GetConfigstring(CS_LIGHT_STYLES + (mLightOffStyle*3)+1, lightstyle, 32);
131 gi.SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+1, lightstyle);
132
133 gi.GetConfigstring(CS_LIGHT_STYLES + (mLightOffStyle*3)+2, lightstyle, 32);
134 gi.SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+2, lightstyle);
135 }else
136 {
137 gi.SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+0, "a");
138 gi.SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+1, "a");
139 gi.SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+2, "a");
140 }
141 }
142 else
143 { //Turn myself on now
144 if (mLightSwitchStyle) //i have a light style i'd like to use when on
145 {
146 char lightstyle[32];
147 gi.GetConfigstring(CS_LIGHT_STYLES + (mLightSwitchStyle*3)+0, lightstyle, 32);
148 gi.SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+0, lightstyle);
149
150 gi.GetConfigstring(CS_LIGHT_STYLES + (mLightSwitchStyle*3)+1, lightstyle, 32);
151 gi.SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+1, lightstyle);
152
153 gi.GetConfigstring(CS_LIGHT_STYLES + (mLightSwitchStyle*3)+2, lightstyle, 32);
154 gi.SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+2, lightstyle);
155 }
156 else
157 {
158 gi.SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+0, "z");
159 gi.SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+1, "z");
160 gi.SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+2, "z");
161 }
162 }
163 }
SP_light(gentity_t * self)164 void SP_light( gentity_t *self ) {
165 if (!self->targetname )
166 {//if i don't have a light style switch, the i go away
167 G_FreeEntity( self );
168 return;
169 }
170
171 G_SpawnInt( "style", "0", &self->count );
172 G_SpawnInt( "switch_style", "0", &self->bounceCount );
173 G_SpawnInt( "style_off", "0", &self->fly_sound_debounce_time );
174 G_SetOrigin( self, self->s.origin );
175 gi.linkentity( self );
176
177 self->e_UseFunc = useF_misc_dlight_use;
178 self->e_clThinkFunc = clThinkF_NULL;
179
180 self->s.eType = ET_GENERAL;
181 self->misc_dlight_active = qfalse;
182 self->svFlags |= SVF_NOCLIENT;
183
184 if ( !(self->spawnflags & 4) )
185 { //turn myself on now
186 self->misc_dlight_active = qtrue;
187 }
188 misc_lightstyle_set (self);
189 }
190
misc_dlight_use(gentity_t * ent,gentity_t * other,gentity_t * activator)191 void misc_dlight_use ( gentity_t *ent, gentity_t *other, gentity_t *activator )
192 {
193 G_ActivateBehavior(ent,BSET_USE);
194
195 ent->misc_dlight_active = (qboolean)!ent->misc_dlight_active; //toggle
196 misc_lightstyle_set (ent);
197 }
198
199
200 /*
201 =================================================================================
202
203 TELEPORTERS
204
205 =================================================================================
206 */
207
TeleportPlayer(gentity_t * player,vec3_t origin,vec3_t angles)208 void TeleportPlayer( gentity_t *player, vec3_t origin, vec3_t angles )
209 {
210 if ( player->NPC && ( player->NPC->aiFlags&NPCAI_FORM_TELE_NAV ) )
211 {
212 //My leader teleported, I was trying to catch up, take this off
213 player->NPC->aiFlags &= ~NPCAI_FORM_TELE_NAV;
214
215 }
216
217 // unlink to make sure it can't possibly interfere with G_KillBox
218 gi.unlinkentity (player);
219
220 VectorCopy ( origin, player->client->ps.origin );
221 player->client->ps.origin[2] += 1;
222 VectorCopy ( player->client->ps.origin, player->currentOrigin );
223
224 // spit the player out
225 AngleVectors( angles, player->client->ps.velocity, NULL, NULL );
226 VectorScale( player->client->ps.velocity, 0, player->client->ps.velocity );
227 //player->client->ps.pm_time = 160; // hold time
228 //player->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
229
230 // toggle the teleport bit so the client knows to not lerp
231 player->client->ps.eFlags ^= EF_TELEPORT_BIT;
232
233 // set angles
234 SetClientViewAngle( player, angles );
235
236 // kill anything at the destination
237 G_KillBox (player);
238
239 // save results of pmove
240 PlayerStateToEntityState( &player->client->ps, &player->s );
241
242 gi.linkentity (player);
243 }
244
TeleportMover(gentity_t * mover,vec3_t origin,vec3_t diffAngles,qboolean snapAngle)245 void TeleportMover( gentity_t *mover, vec3_t origin, vec3_t diffAngles, qboolean snapAngle )
246 {//FIXME: need an effect
247 vec3_t oldAngle, newAngle;
248 float speed;
249
250 // unlink to make sure it can't possibly interfere with G_KillBox
251 gi.unlinkentity (mover);
252
253 //reposition it
254 VectorCopy( origin, mover->s.pos.trBase );
255 VectorCopy( origin, mover->currentOrigin );
256
257 //Maintain their previous speed, but adjusted for new direction
258 if ( snapAngle )
259 {//not a diffAngle, actually an absolute angle
260 vec3_t dir;
261
262 VectorCopy( diffAngles, newAngle );
263 AngleVectors( newAngle, dir, NULL, NULL );
264 VectorNormalize( dir );//necessary?
265 speed = VectorLength( mover->s.pos.trDelta );
266 VectorScale( dir, speed, mover->s.pos.trDelta );
267 mover->s.pos.trTime = level.time;
268
269 VectorSubtract( newAngle, mover->s.apos.trBase, diffAngles );
270 VectorCopy( newAngle, mover->s.apos.trBase );
271 }
272 else
273 {
274 speed = VectorNormalize( mover->s.pos.trDelta );
275
276 vectoangles( mover->s.pos.trDelta, oldAngle );
277 VectorAdd( oldAngle, diffAngles, newAngle );
278
279 AngleVectors( newAngle, mover->s.pos.trDelta, NULL, NULL );
280 VectorNormalize( mover->s.pos.trDelta );
281
282 VectorScale( mover->s.pos.trDelta, speed, mover->s.pos.trDelta );
283 mover->s.pos.trTime = level.time;
284
285 //Maintain their previous angles, but adjusted to new orientation
286 VectorAdd( mover->s.apos.trBase, diffAngles, mover->s.apos.trBase );
287 }
288
289 //Maintain their previous anglespeed, but adjusted to new orientation
290 speed = VectorNormalize( mover->s.apos.trDelta );
291 VectorAdd( mover->s.apos.trDelta, diffAngles, mover->s.apos.trDelta );
292 VectorNormalize( mover->s.apos.trDelta );
293 VectorScale( mover->s.apos.trDelta, speed, mover->s.apos.trDelta );
294
295 mover->s.apos.trTime = level.time;
296
297 //Tell them it was teleported this move
298 mover->s.eFlags |= EF_TELEPORT_BIT;
299
300 // kill anything at the destination
301 //G_KillBox (mover);
302 //FIXME: call touch func instead of killbox?
303
304 gi.linkentity (mover);
305 }
306
teleporter_touch(gentity_t * self,gentity_t * other,trace_t * trace)307 void teleporter_touch (gentity_t *self, gentity_t *other, trace_t *trace)
308 {
309 gentity_t *dest;
310
311 if (!other->client)
312 return;
313 dest = G_PickTarget( self->target );
314 if (!dest) {
315 gi.Printf ("Couldn't find teleporter destination\n");
316 return;
317 }
318
319 TeleportPlayer( other, dest->s.origin, dest->s.angles );
320 }
321
322 /*QUAK-D misc_teleporter (1 0 0) (-32 -32 -24) (32 32 -16)
323 Stepping onto this disc will teleport players to the targeted misc_teleporter_dest object.
324 */
SP_misc_teleporter(gentity_t * ent)325 void SP_misc_teleporter (gentity_t *ent)
326 {
327 gentity_t *trig;
328
329 if (!ent->target)
330 {
331 gi.Printf ("teleporter without a target.\n");
332 G_FreeEntity( ent );
333 return;
334 }
335
336 ent->s.modelindex = G_ModelIndex( "models/objects/dmspot.md3" );
337 ent->s.clientNum = 1;
338 // ent->s.loopSound = G_SoundIndex("sound/world/amb10.wav");
339 ent->contents = CONTENTS_SOLID;
340
341 G_SetOrigin( ent, ent->s.origin );
342
343 VectorSet (ent->mins, -32, -32, -24);
344 VectorSet (ent->maxs, 32, 32, -16);
345 gi.linkentity (ent);
346
347 trig = G_Spawn ();
348 trig->e_TouchFunc = touchF_teleporter_touch;
349 trig->contents = CONTENTS_TRIGGER;
350 trig->target = ent->target;
351 trig->owner = ent;
352 G_SetOrigin( trig, ent->s.origin );
353 VectorSet (trig->mins, -8, -8, 8);
354 VectorSet (trig->maxs, 8, 8, 24);
355 gi.linkentity (trig);
356
357 }
358
359 /*QUAK-D misc_teleporter_dest (1 0 0) (-32 -32 -24) (32 32 -16) - - NODRAW
360 Point teleporters at these.
361 */
SP_misc_teleporter_dest(gentity_t * ent)362 void SP_misc_teleporter_dest( gentity_t *ent ) {
363 if ( ent->spawnflags & 4 ){
364 return;
365 }
366
367 G_SetOrigin( ent, ent->s.origin );
368
369 gi.linkentity (ent);
370 }
371
372
373 //===========================================================
374
375 /*QUAKED misc_model (1 0 0) (-16 -16 -16) (16 16 16) RMG SOLID
376 "model" arbitrary .md3 or .ase file to display
377 "_frame" "x" which frame from an animated md3
378 "modelscale" "x" uniform scale
379 "modelscale_vec" "x y z" scale model in each axis
380 "_remap" "from to" remap a shader in this model
381 turns into BSP triangles - not solid by default (click SOLID or use _clipmodel shader)
382 */
SP_misc_model(gentity_t * ent)383 void SP_misc_model( gentity_t *ent ) {
384 G_FreeEntity( ent );
385 }
386
387 /*QUAKED misc_model_static (1 0 0) (-16 -16 0) (16 16 16)
388 "model" arbitrary .md3 file to display
389 "_frame" "x" which frame from an animated md3
390 "modelscale" "x" uniform scale
391 "modelscale_vec" "x y z" scale model in each axis
392 "zoffset" units to offset vertical culling position by, can be
393 negative or positive. This does not affect the actual
394 position of the model, only the culling position. Use
395 it for models with stupid origins that go below the
396 ground and whatnot.
397
398 loaded as a model in the renderer - does not take up precious bsp space!
399 */
400 extern void CG_CreateMiscEntFromGent(gentity_t *ent, const vec3_t scale, float zOff); //cg_main.cpp
SP_misc_model_static(gentity_t * ent)401 void SP_misc_model_static(gentity_t *ent)
402 {
403 char *value;
404 float temp;
405 float zOff;
406 vec3_t scale;
407
408 G_SpawnString("modelscale_vec", "1 1 1", &value);
409 sscanf( value, "%f %f %f", &scale[ 0 ], &scale[ 1 ], &scale[ 2 ] );
410
411 G_SpawnFloat( "modelscale", "0", &temp);
412 if (temp != 0.0f)
413 {
414 scale[ 0 ] = scale[ 1 ] = scale[ 2 ] = temp;
415 }
416
417 G_SpawnFloat( "zoffset", "0", &zOff);
418
419 if (!ent->model)
420 {
421 Com_Error( ERR_DROP,"misc_model_static at %s with out a MODEL!\n", vtos(ent->s.origin) );
422 }
423 //we can be horrible and cheat since this is SP!
424 CG_CreateMiscEntFromGent(ent, scale, zOff);
425 G_FreeEntity( ent );
426 }
427
428 //===========================================================
429
setCamera(gentity_t * ent)430 void setCamera ( gentity_t *ent )
431 {
432 vec3_t dir;
433 gentity_t *target = 0;
434
435 // frame holds the rotate speed
436 if ( ent->owner->spawnflags & 1 )
437 {
438 ent->s.frame = 25;
439 }
440 else if ( ent->owner->spawnflags & 2 )
441 {
442 ent->s.frame = 75;
443 }
444
445 // clientNum holds the rotate offset
446 ent->s.clientNum = ent->owner->s.clientNum;
447
448 VectorCopy( ent->owner->s.origin, ent->s.origin2 );
449
450 // see if the portal_camera has a target
451 if (ent->owner->target) {
452 target = G_PickTarget( ent->owner->target );
453 }
454 if ( target )
455 {
456 VectorSubtract( target->s.origin, ent->owner->s.origin, dir );
457 VectorNormalize( dir );
458 }
459 else
460 {
461 G_SetMovedir( ent->owner->s.angles, dir );
462 }
463
464 ent->s.eventParm = DirToByte( dir );
465 }
466
cycleCamera(gentity_t * self)467 void cycleCamera( gentity_t *self )
468 {
469 self->owner = G_Find( self->owner, FOFS(targetname), self->target );
470 if ( self->owner == NULL )
471 {
472 //Uh oh! Not targeted at any ents! Or reached end of list? Which is it?
473 //for now assume reached end of list and are cycling
474 self->owner = G_Find( self->owner, FOFS(targetname), self->target );
475 if ( self->owner == NULL )
476 {//still didn't find one
477 gi.Printf( "Couldn't find target for misc_portal_surface\n" );
478 G_FreeEntity( self );
479 return;
480 }
481 }
482 setCamera( self );
483
484 if ( self->e_ThinkFunc == thinkF_cycleCamera )
485 {
486 if ( self->owner->wait > 0 )
487 {
488 self->nextthink = level.time + self->owner->wait;
489 }
490 else
491 {
492 self->nextthink = level.time + self->wait;
493 }
494 }
495 }
496
misc_portal_use(gentity_t * self,gentity_t * other,gentity_t * activator)497 void misc_portal_use( gentity_t *self, gentity_t *other, gentity_t *activator )
498 {
499 cycleCamera( self );
500 }
501
locateCamera(gentity_t * ent)502 void locateCamera( gentity_t *ent )
503 {//FIXME: make this fadeout with distance from misc_camera_portal
504
505 ent->owner = G_Find(NULL, FOFS(targetname), ent->target);
506 if ( !ent->owner )
507 {
508 gi.Printf( "Couldn't find target for misc_portal_surface\n" );
509 G_FreeEntity( ent );
510 return;
511 }
512
513 setCamera( ent );
514
515 if ( !ent->targetname )
516 {//not targetted, so auto-cycle
517 if ( G_Find(ent->owner, FOFS(targetname), ent->target) != NULL )
518 {//targeted at more than one thing
519 ent->e_ThinkFunc = thinkF_cycleCamera;
520 if ( ent->owner->wait > 0 )
521 {
522 ent->nextthink = level.time + ent->owner->wait;
523 }
524 else
525 {
526 ent->nextthink = level.time + ent->wait;
527 }
528 }
529 }
530 }
531
532 /*QUAKED misc_portal_surface (0 0 1) (-8 -8 -8) (8 8 8)
533 The portal surface nearest this entity will show a view from the targeted misc_portal_camera, or a mirror view if untargeted.
534 This must be within 64 world units of the surface!
535
536 targetname - When used, cycles to the next misc_portal_camera it's targeted
537 wait - makes it auto-cycle between all cameras it's pointed at at intevervals of specified number of seconds.
538
539 cameras will be cycled through in the order they were created on the map.
540 */
SP_misc_portal_surface(gentity_t * ent)541 void SP_misc_portal_surface(gentity_t *ent)
542 {
543 VectorClear( ent->mins );
544 VectorClear( ent->maxs );
545 gi.linkentity (ent);
546
547 ent->svFlags = SVF_PORTAL;
548 ent->s.eType = ET_PORTAL;
549 ent->wait *= 1000;
550
551 if ( !ent->target )
552 {//mirror?
553 VectorCopy( ent->s.origin, ent->s.origin2 );
554 }
555 else
556 {
557 ent->e_ThinkFunc = thinkF_locateCamera;
558 ent->nextthink = level.time + 100;
559
560 if ( ent->targetname )
561 {
562 ent->e_UseFunc = useF_misc_portal_use;
563 }
564 }
565 }
566
567 /*QUAKED misc_portal_camera (0 0 1) (-8 -8 -8) (8 8 8) slowrotate fastrotate
568 The target for a misc_portal_surface. You can set either angles or target another entity (NOT an info_null) to determine the direction of view.
569 "roll" an angle modifier to orient the camera around the target vector;
570 */
SP_misc_portal_camera(gentity_t * ent)571 void SP_misc_portal_camera(gentity_t *ent) {
572 float roll;
573
574 VectorClear( ent->mins );
575 VectorClear( ent->maxs );
576 gi.linkentity (ent);
577
578 G_SpawnFloat( "roll", "0", &roll );
579
580 ent->s.clientNum = roll/360.0 * 256;
581 ent->wait *= 1000;
582 }
583
584 void G_SubBSPSpawnEntitiesFromString(const char *entityString, vec3_t posOffset, vec3_t angOffset);
585
586 /*QUAKED misc_bsp (1 0 0) (-16 -16 -16) (16 16 16)
587 "bspmodel" arbitrary .bsp file to display
588 */
SP_misc_bsp(gentity_t * ent)589 void SP_misc_bsp(gentity_t *ent)
590 {
591 char temp[MAX_QPATH];
592 char *out;
593 float newAngle;
594 int tempint;
595
596 G_SpawnFloat( "angle", "0", &newAngle );
597 if (newAngle != 0.0)
598 {
599 ent->s.angles[1] = newAngle;
600 }
601 // don't support rotation any other way
602 ent->s.angles[0] = 0.0;
603 ent->s.angles[2] = 0.0;
604
605 G_SpawnString("bspmodel", "", &out);
606
607 ent->s.eFlags = EF_PERMANENT;
608
609 // Mainly for debugging
610 G_SpawnInt( "spacing", "0", &tempint);
611 ent->s.time2 = tempint;
612 G_SpawnInt( "flatten", "0", &tempint);
613 ent->s.time = tempint;
614
615 Com_sprintf(temp, MAX_QPATH, "#%s", out);
616 gi.SetBrushModel( ent, temp ); // SV_SetBrushModel -- sets mins and maxs
617 G_BSPIndex(temp);
618
619 level.mNumBSPInstances++;
620 Com_sprintf(temp, MAX_QPATH, "%d-", level.mNumBSPInstances);
621 VectorCopy(ent->s.origin, level.mOriginAdjust);
622 level.mRotationAdjust = ent->s.angles[1];
623 level.mTargetAdjust = temp;
624 level.hasBspInstances = qtrue;
625 level.mBSPInstanceDepth++;
626
627 VectorCopy( ent->s.origin, ent->s.pos.trBase );
628 VectorCopy( ent->s.origin, ent->currentOrigin );
629 VectorCopy( ent->s.angles, ent->s.apos.trBase );
630 VectorCopy( ent->s.angles, ent->currentAngles );
631
632 ent->s.eType = ET_MOVER;
633
634 gi.linkentity (ent);
635
636 const char *ents = gi.SetActiveSubBSP(ent->s.modelindex);
637 if (ents)
638 {
639 G_SubBSPSpawnEntitiesFromString(ents, ent->s.origin, ent->s.angles);
640 }
641 gi.SetActiveSubBSP(-1);
642
643 level.mBSPInstanceDepth--;
644 }
645
646 #define MAX_INSTANCE_TYPES 16
647
648 void AddSpawnField(char *field, char *value);
649
650 /*QUAKED terrain (1.0 1.0 1.0) ? NOVEHDMG
651
652 NOVEHDMG - don't damage vehicles upon impact with this terrain
653
654 Terrain entity
655 It will stretch to the full height of the brush
656
657 numPatches - integer number of patches to split the terrain brush into (default 200)
658 terxels - integer number of terxels on a patch side (default 4) (2 <= count <= 8)
659 seed - integer seed for random terrain generation (default 0)
660 textureScale - float scale of texture (default 0.005)
661 heightmap - name of heightmap data image to use, located in heightmaps/xxx.png. (must be PNG format)
662 terrainDef - defines how the game textures the terrain (file is base/ext_data/rmg/xxx.terrain - default is grassyhills)
663 instanceDef - defines which bsp instances appear
664 miscentDef - defines which client models spawn on the terrain (file is base/ext_data/rmg/xxx.miscents)
665 densityMap - how dense the client models are packed
666
667 */
SP_terrain(gentity_t * ent)668 void SP_terrain(gentity_t *ent)
669 {
670 G_FreeEntity( ent );
671 }
672
673 //rww - Called by skyportal entities. This will check through entities and flag them
674 //as portal ents if they are in the same pvs as a skyportal entity and pass
675 //a direct point trace check between origins. I really wanted to use an eFlag for
676 //flagging portal entities, but too many entities like to reset their eFlags.
677 //Note that this was not part of the original wolf sky portal stuff.
G_PortalifyEntities(gentity_t * ent)678 void G_PortalifyEntities(gentity_t *ent)
679 {
680 int i = 0;
681 gentity_t *scan = NULL;
682
683 while (i < MAX_GENTITIES)
684 {
685 scan = &g_entities[i];
686
687 if (scan && scan->inuse && scan->s.number != ent->s.number && gi.inPVS(ent->s.origin, scan->currentOrigin))
688 {
689 trace_t tr;
690
691 gi.trace(&tr, ent->s.origin, vec3_origin, vec3_origin, scan->currentOrigin, ent->s.number, CONTENTS_SOLID, G2_NOCOLLIDE, 0);
692
693 if (tr.fraction == 1.0 || (tr.entityNum == scan->s.number && tr.entityNum != ENTITYNUM_NONE && tr.entityNum != ENTITYNUM_WORLD))
694 {
695 scan->s.isPortalEnt = qtrue; //he's flagged now
696 }
697 }
698
699 i++;
700 }
701
702 ent->e_ThinkFunc = thinkF_G_FreeEntity; //the portal entity is no longer needed because its information is stored in a config string.
703 ent->nextthink = level.time;
704 }
705
706 /*QUAKED misc_skyportal (.6 .7 .7) (-8 -8 0) (8 8 16)
707 To have the portal sky fogged, enter any of the following values:
708 "fogcolor" (r g b) (values 0.0-1.0)
709 "fognear" distance from entity to start fogging
710 "fogfar" distance from entity that fog is opaque
711 rww - NOTE: fog doesn't work with these currently (at least not in this way).
712 Use a fog brush instead.
713 */
SP_misc_skyportal(gentity_t * ent)714 void SP_misc_skyportal (gentity_t *ent)
715 {
716 vec3_t fogv; //----(SA)
717 int fogn; //----(SA)
718 int fogf; //----(SA)
719 int isfog = 0; // (SA)
720
721 isfog += G_SpawnVector ("fogcolor", "0 0 0", fogv);
722 isfog += G_SpawnInt ("fognear", "0", &fogn);
723 isfog += G_SpawnInt ("fogfar", "300", &fogf);
724
725 gi.SetConfigstring( CS_SKYBOXORG, va("%.2f %.2f %.2f %i %.2f %.2f %.2f %i %i", ent->s.origin[0], ent->s.origin[1], ent->s.origin[2], isfog, fogv[0], fogv[1], fogv[2], fogn, fogf ) );
726
727 ent->e_ThinkFunc = thinkF_G_PortalifyEntities;
728 ent->nextthink = level.time + 1050; //give it some time first so that all other entities are spawned.
729 }
730
731 extern qboolean G_ClearViewEntity( gentity_t *ent );
732 extern void G_SetViewEntity( gentity_t *self, gentity_t *viewEntity );
733 extern void SP_fx_runner( gentity_t *ent );
camera_die(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int mod,int dFlags,int hitLoc)734 void camera_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags,int hitLoc )
735 {
736 if ( player && player->client && player->client->ps.viewEntity == self->s.number )
737 {
738 G_UseTargets2( self, player, self->target4 );
739 G_ClearViewEntity( player );
740 G_Sound( player, self->soundPos2 );
741 }
742 G_UseTargets2( self, player, self->closetarget );
743 //FIXME: explosion fx/sound
744 //leave sparks at origin- where base's pole is still at?
745 gentity_t *sparks = G_Spawn();
746 if ( sparks )
747 {
748 sparks->fxFile = "sparks/spark";
749 sparks->delay = 100;
750 sparks->random = 500;
751 sparks->s.angles[0] = 180;//point down
752 VectorCopy( self->s.origin, sparks->s.origin );
753 SP_fx_runner( sparks );
754 }
755
756 //bye!
757 self->takedamage = qfalse;
758 self->contents = 0;
759 self->s.eFlags |= EF_NODRAW;
760 self->s.modelindex = 0;
761 }
762
camera_use(gentity_t * self,gentity_t * other,gentity_t * activator)763 void camera_use( gentity_t *self, gentity_t *other, gentity_t *activator )
764 {
765 if ( !activator || !activator->client || activator->s.number )
766 {//really only usable by the player
767 return;
768 }
769 self->painDebounceTime = level.time + (self->wait*1000);//FRAMETIME*5;//don't check for player buttons for 500 ms
770
771 // FIXME: I guess we are allowing them to switch to a dead camera. Maybe we should conditionally do this though?
772 if ( /*self->health <= 0 ||*/ (player && player->client && player->client->ps.viewEntity == self->s.number) )
773 {//I'm already viewEntity, or I'm destroyed, find next
774 gentity_t *next = NULL;
775 if ( self->target2 != NULL )
776 {
777 next = G_Find( NULL, FOFS(targetname), self->target2 );
778 }
779 if ( next )
780 {//found another one
781 if ( !Q_stricmp( "misc_camera", next->classname ) )
782 {//make sure it's another camera
783 camera_use( next, other, activator );
784 }
785 }
786 else //if ( self->health > 0 )
787 {//I was the last (only?) one, clear out the viewentity
788 G_UseTargets2( self, activator, self->target4 );
789 G_ClearViewEntity( activator );
790 G_Sound( activator, self->soundPos2 );
791 }
792 }
793 else
794 {//set me as view entity
795 G_UseTargets2( self, activator, self->target3 );
796 self->s.eFlags |= EF_NODRAW;
797 self->s.modelindex = 0;
798 G_SetViewEntity( activator, self );
799 G_Sound( activator, self->soundPos1 );
800 }
801 }
802
camera_aim(gentity_t * self)803 void camera_aim( gentity_t *self )
804 {
805 self->nextthink = level.time + FRAMETIME;
806 if ( player && player->client && player->client->ps.viewEntity == self->s.number )
807 {//I am the viewEntity
808 if ( player->client->usercmd.forwardmove || player->client->usercmd.rightmove || player->client->usercmd.upmove )
809 {//player wants to back out of camera
810 G_UseTargets2( self, player, self->target4 );
811 G_ClearViewEntity( player );
812 G_Sound( player, self->soundPos2 );
813 self->painDebounceTime = level.time + (self->wait*1000);//FRAMETIME*5;//don't check for player buttons for 500 ms
814 if ( player->client->usercmd.upmove > 0 )
815 {//stop player from doing anything for a half second after
816 player->aimDebounceTime = level.time + 500;
817 }
818 }
819 else if ( self->painDebounceTime < level.time )
820 {//check for use button
821 if ( (player->client->usercmd.buttons&BUTTON_USE) )
822 {//player pressed use button, wants to cycle to next
823 camera_use( self, player, player );
824 }
825 }
826 else
827 {//don't draw me when being looked through
828 self->s.eFlags |= EF_NODRAW;
829 self->s.modelindex = 0;
830 }
831 }
832 else if ( self->health > 0 )
833 {//still alive, can draw me again
834 self->s.eFlags &= ~EF_NODRAW;
835 self->s.modelindex = self->s.modelindex3;
836 }
837 //update my aim
838 if ( self->target )
839 {
840 gentity_t *targ = G_Find( NULL, FOFS(targetname), self->target );
841 if ( targ )
842 {
843 vec3_t angles, dir;
844 VectorSubtract( targ->currentOrigin, self->currentOrigin, dir );
845 vectoangles( dir, angles );
846 //FIXME: if a G2 model, do a bone override..???
847 VectorCopy( self->currentAngles, self->s.apos.trBase );
848
849 for( int i = 0; i < 3; i++ )
850 {
851 angles[i] = AngleNormalize180( angles[i] );
852 self->s.apos.trDelta[i] = AngleNormalize180( (angles[i]-self->currentAngles[i])*10 );
853 }
854 //VectorSubtract( angles, self->currentAngles, self->s.apos.trDelta );
855 //VectorScale( self->s.apos.trDelta, 10, self->s.apos.trDelta );
856 self->s.apos.trTime = level.time;
857 self->s.apos.trDuration = FRAMETIME;
858 VectorCopy( angles, self->currentAngles );
859
860 if ( DistanceSquared( self->currentAngles, self->lastAngles ) > 0.01f ) // if it moved at all, start a loop sound? not exactly the "bestest" solution
861 {
862 self->s.loopSound = G_SoundIndex( "sound/movers/objects/cameramove_lp2" );
863 }
864 else
865 {
866 self->s.loopSound = 0; // not moving so don't bother
867 }
868
869 VectorCopy( self->currentAngles, self->lastAngles );
870 //G_SetAngles( self, angles );
871 }
872 }
873 }
874 /*QUAKED misc_camera (0 0 1) (-8 -8 -12) (8 8 16) VULNERABLE
875 A model in the world that can be used by the player to look through it's viewpoint
876
877 There will be a video overlay instead of the regular HUD and the FOV will be wider
878
879 VULNERABLE - allow camera to be destroyed
880
881 "target" - camera will remain pointed at this entity (if it's a train or some other moving object, it will keep following it)
882 "target2" - when player is in this camera and hits the use button, it will cycle to this next camera (if no target2, returns to normal view )
883 "target3" - thing to use when player enters this camera view
884 "target4" - thing to use when player leaves this camera view
885 "closetarget" - (sigh...) yet another target, fired this when it's destroyed
886 "wait" - how long to wait between being used (default 0.5)
887 */
SP_misc_camera(gentity_t * self)888 void SP_misc_camera( gentity_t *self )
889 {
890 G_SpawnFloat( "wait", "0.5", &self->wait );
891
892 //FIXME: spawn base, too
893 gentity_t *base = G_Spawn();
894 if ( base )
895 {
896 base->s.modelindex = G_ModelIndex( "models/map_objects/kejim/impcam_base.md3" );
897 VectorCopy( self->s.origin, base->s.origin );
898 base->s.origin[2] += 16;
899 G_SetOrigin( base, base->s.origin );
900 G_SetAngles( base, self->s.angles );
901 gi.linkentity( base );
902 }
903 self->s.modelindex3 = self->s.modelindex = G_ModelIndex( "models/map_objects/kejim/impcam.md3" );
904 self->soundPos1 = G_SoundIndex( "sound/movers/camera_on.mp3" );
905 self->soundPos2 = G_SoundIndex( "sound/movers/camera_off.mp3" );
906 G_SoundIndex( "sound/movers/objects/cameramove_lp2" );
907
908 G_SetOrigin( self, self->s.origin );
909 G_SetAngles( self, self->s.angles );
910 self->s.apos.trType = TR_LINEAR_STOP;//TR_INTERPOLATE;//
911 self->alt_fire = qtrue;
912 VectorSet( self->mins, -8, -8, -12 );
913 VectorSet( self->maxs, 8, 8, 0 );
914 self->contents = CONTENTS_SOLID;
915 gi.linkentity( self );
916
917 self->fxID = G_EffectIndex( "sparks/spark" );
918
919 if ( self->spawnflags & 1 ) // VULNERABLE
920 {
921 self->takedamage = qtrue;
922 }
923
924 self->health = 10;
925 self->e_DieFunc = dieF_camera_die;
926
927 self->e_UseFunc = useF_camera_use;
928
929 self->e_ThinkFunc = thinkF_camera_aim;
930 self->nextthink = level.time + START_TIME_LINK_ENTS;
931 }
932 /*
933 ======================================================================
934
935 SHOOTERS
936
937 ======================================================================
938 */
939
Use_Shooter(gentity_t * ent,gentity_t * other,gentity_t * activator)940 void Use_Shooter( gentity_t *ent, gentity_t *other, gentity_t *activator )
941 {
942 G_ActivateBehavior(ent,BSET_USE);
943 }
944
InitShooter(gentity_t * ent,int weapon)945 void InitShooter( gentity_t *ent, int weapon ) {
946 ent->e_UseFunc = useF_Use_Shooter;
947 ent->s.weapon = weapon;
948
949 RegisterItem( FindItemForWeapon( (weapon_t) weapon ) );
950
951 G_SetMovedir( ent->s.angles, ent->movedir );
952
953 if ( !ent->random ) {
954 ent->random = 1.0;
955 }
956 ent->random = sin( M_PI * ent->random / 180 );
957 // target might be a moving object, so we can't set movedir for it
958 if ( ent->target ) {
959 G_SetEnemy(ent, G_PickTarget( ent->target ));
960 }
961 gi.linkentity( ent );
962 }
963
964 /*QUAK-ED shooter_rocket (1 0 0) (-16 -16 -16) (16 16 16)
965 Fires at either the target or the current direction.
966 "random" the number of degrees of deviance from the taget. (1.0 default)
967 */
SP_shooter_rocket(gentity_t * ent)968 void SP_shooter_rocket( gentity_t *ent )
969 {
970 // InitShooter( ent, WP_TETRION_DISRUPTOR );
971 }
972
973 /*QUAK-ED shooter_plasma (1 0 0) (-16 -16 -16) (16 16 16)
974 Fires at either the target or the current direction.
975 "random" is the number of degrees of deviance from the taget. (1.0 default)
976 */
SP_shooter_plasma(gentity_t * ent)977 void SP_shooter_plasma( gentity_t *ent )
978 {
979 InitShooter( ent, WP_BRYAR_PISTOL);
980 }
981
982 /*QUAK-ED shooter_grenade (1 0 0) (-16 -16 -16) (16 16 16)
983 Fires at either the target or the current direction.
984 "random" is the number of degrees of deviance from the taget. (1.0 default)
985 */
SP_shooter_grenade(gentity_t * ent)986 void SP_shooter_grenade( gentity_t *ent )
987 {
988 // InitShooter( ent, WP_GRENADE_LAUNCHER);
989 }
990
991
992 /*QUAKED object_cargo_barrel1 (1 0 0) (-16 -16 -16) (16 16 29) SMALLER KLINGON NO_SMOKE POWDERKEG
993 Cargo Barrel
994 if given a targetname, using it makes it explode
995
996 SMALLER - (-8, -8, -16) (8, 8, 8)
997 KLINGON - klingon style barrel
998 NO_SMOKE - will not leave lingering smoke cloud when killed
999 POWDERKEG - wooden explosive barrel
1000
1001 health default = 20
1002 splashDamage default = 100
1003 splashRadius default = 200
1004 */
SP_object_cargo_barrel1(gentity_t * ent)1005 void SP_object_cargo_barrel1(gentity_t *ent)
1006 {
1007 if(ent->spawnflags & 8)
1008 {
1009 ent->s.modelindex = G_ModelIndex( "/models/mapobjects/cargo/barrel_wood2.md3" );
1010 // ent->sounds = G_SoundIndex("sound/weapons/explosions/explode3.wav");
1011 }
1012 else if(ent->spawnflags & 2)
1013 {
1014 ent->s.modelindex = G_ModelIndex( "/models/mapobjects/scavenger/k_barrel.md3" );
1015 // ent->sounds = G_SoundIndex("sound/weapons/explosions/explode4.wav");
1016 }
1017 else
1018 {
1019 ent->s.modelindex = G_ModelIndex( va("/models/mapobjects/cargo/barrel%i.md3", Q_irand( 0, 2 )) );
1020 // ent->sounds = G_SoundIndex("sound/weapons/explosions/explode1.wav");
1021 }
1022
1023 ent->contents = CONTENTS_SOLID|CONTENTS_OPAQUE;
1024
1025 if ( ent->spawnflags & 1 )
1026 {
1027 VectorSet (ent->mins, -8, -8, -16);
1028 VectorSet (ent->maxs, 8, 8, 8);
1029 }
1030 else
1031 {
1032 VectorSet (ent->mins, -16, -16, -16);
1033 VectorSet (ent->maxs, 16, 16, 29);
1034 }
1035
1036 G_SetOrigin( ent, ent->s.origin );
1037 VectorCopy( ent->s.angles, ent->s.apos.trBase );
1038
1039 if(!ent->health)
1040 ent->health = 20;
1041
1042 if(!ent->splashDamage)
1043 ent->splashDamage = 100;
1044
1045 if(!ent->splashRadius)
1046 ent->splashRadius = 200;
1047
1048 ent->takedamage = qtrue;
1049
1050 ent->e_DieFunc = dieF_ExplodeDeath_Wait;
1051
1052 if(ent->targetname)
1053 ent->e_UseFunc = useF_GoExplodeDeath;
1054
1055 gi.linkentity (ent);
1056 }
1057
1058
1059 /*QUAKED misc_dlight (0.2 0.8 0.2) (-4 -4 -4) (4 4 4) STARTOFF FADEON FADEOFF PULSE
1060 Dynamic light, toggles on and off when used
1061
1062 STARTOFF - Starts off
1063 FADEON - Fades from 0 Radius to start Radius
1064 FADEOFF - Fades from current Radius to 0 Radius before turning off
1065 PULSE - This flag must be checked if you want it to fade/switch between start and final RGBA, otherwise it will just sit at startRGBA
1066
1067 ownername - Will display the light at the origin of the entity with this targetname
1068
1069 startRGBA - Red Green Blue Radius to start with - This MUST be set or your light won't do anything
1070
1071 These next values are used only if you want to fade/switch between 2 values (PULSE flag on)
1072 finalRGBA - Red Green Blue Radius to end with
1073 speed - how long to take to fade from start to final and final to start. Also how long to fade on and off if appropriate flags are checked (seconds)
1074 finaltime - how long to hold at final (seconds)
1075 starttime - how long to hold at start (seconds)
1076
1077 TODO: Add random to speed/radius?
1078 */
SP_misc_dlight(gentity_t * ent)1079 void SP_misc_dlight(gentity_t *ent)
1080 {
1081 G_SetOrigin( ent, ent->s.origin );
1082 gi.linkentity( ent );
1083
1084 ent->speed *= 1000;
1085 ent->wait *= 1000;
1086 ent->radius *= 1000;
1087
1088 //FIXME: attach self to a train or something?
1089 ent->e_UseFunc = useF_misc_dlight_use;
1090
1091 ent->misc_dlight_active = qfalse;
1092 ent->e_clThinkFunc = clThinkF_NULL;
1093
1094 ent->s.eType = ET_GENERAL;
1095 //Delay first think so we can find owner
1096 if ( ent->ownername )
1097 {
1098 ent->e_ThinkFunc = thinkF_misc_dlight_think;
1099 ent->nextthink = level.time + START_TIME_LINK_ENTS;
1100 }
1101
1102 if ( !(ent->spawnflags & 1) )
1103 {//Turn myself on now
1104 GEntity_UseFunc( ent, ent, ent );
1105 }
1106 }
1107
misc_dlight_use_old(gentity_t * ent,gentity_t * other,gentity_t * activator)1108 void misc_dlight_use_old ( gentity_t *ent, gentity_t *other, gentity_t *activator )
1109 {
1110 G_ActivateBehavior(ent,BSET_USE);
1111
1112 if ( ent->misc_dlight_active )
1113 {//We're on, turn off
1114 if ( ent->spawnflags & 4 )
1115 {//fade off
1116 ent->pushDebounceTime = 3;
1117 }
1118 else
1119 {
1120 ent->misc_dlight_active = qfalse;
1121 ent->e_clThinkFunc = clThinkF_NULL;
1122
1123 ent->s.eType = ET_GENERAL;
1124 ent->svFlags &= ~SVF_BROADCAST;
1125 }
1126 }
1127 else
1128 {
1129 //Start at start regardless of when we were turned off
1130 if ( ent->spawnflags & 4 )
1131 {//fade on
1132 ent->pushDebounceTime = 2;
1133 }
1134 else
1135 {//Just start on
1136 ent->pushDebounceTime = 0;
1137 }
1138 ent->painDebounceTime = level.time;
1139
1140 ent->misc_dlight_active = qtrue;
1141
1142 ent->e_ThinkFunc = thinkF_misc_dlight_think;
1143 ent->nextthink = level.time + FRAMETIME;
1144
1145 ent->e_clThinkFunc = clThinkF_CG_DLightThink;
1146
1147 ent->s.eType = ET_THINKER;
1148 ent->svFlags |= SVF_BROADCAST;// Broadcast to all clients
1149 }
1150 }
1151
misc_dlight_think(gentity_t * ent)1152 void misc_dlight_think ( gentity_t *ent )
1153 {
1154 //Stay Attached to owner
1155 if ( ent->owner )
1156 {
1157 G_SetOrigin( ent, ent->owner->currentOrigin );
1158 gi.linkentity( ent );
1159 }
1160 else if ( ent->ownername )
1161 {
1162 ent->owner = G_Find( NULL, FOFS(targetname), ent->ownername );
1163 ent->ownername = NULL;
1164 }
1165 ent->nextthink = level.time + FRAMETIME;
1166 }
1167
1168
station_pain(gentity_t * self,gentity_t * inflictor,gentity_t * other,const vec3_t point,int damage,int mod,int hitLoc)1169 void station_pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc )
1170 {
1171 // self->s.modelindex = G_ModelIndex("/models/mapobjects/stasis/plugin2_in.md3");
1172 // self->s.eFlags &= ~ EF_ANIM_ALLFAST;
1173 // self->s.eFlags |= EF_ANIM_ONCE;
1174 // gi.linkentity (self);
1175 self->s.modelindex = self->s.modelindex2;
1176 gi.linkentity (self);
1177 }
1178
1179 // --------------------------------------------------------------------
1180 //
1181 // HEALTH/ARMOR plugin functions
1182 //
1183 // --------------------------------------------------------------------
1184
1185 void health_use( gentity_t *self, gentity_t *other, gentity_t *activator);
1186 int ITM_AddArmor (gentity_t *ent, int count);
1187 int ITM_AddHealth (gentity_t *ent, int count);
1188
health_shutdown(gentity_t * self)1189 void health_shutdown( gentity_t *self )
1190 {
1191 if (!(self->s.eFlags & EF_ANIM_ONCE))
1192 {
1193 self->s.eFlags &= ~ EF_ANIM_ALLFAST;
1194 self->s.eFlags |= EF_ANIM_ONCE;
1195
1196 // Switch to and animate its used up model.
1197 if (!Q_stricmp(self->model,"models/mapobjects/stasis/plugin2.md3"))
1198 {
1199 self->s.modelindex = self->s.modelindex2;
1200 }
1201 else if (!Q_stricmp(self->model,"models/mapobjects/borg/plugin2.md3"))
1202 {
1203 self->s.modelindex = self->s.modelindex2;
1204 }
1205 else if (!Q_stricmp(self->model,"models/mapobjects/stasis/plugin2_floor.md3"))
1206 {
1207 self->s.modelindex = self->s.modelindex2;
1208 // G_Sound(self, G_SoundIndex("sound/ambience/stasis/shrinkage1.wav") );
1209 }
1210 else if (!Q_stricmp(self->model,"models/mapobjects/forge/panels.md3"))
1211 {
1212 self->s.modelindex = self->s.modelindex2;
1213 }
1214
1215 gi.linkentity (self);
1216 }
1217 }
1218
health_think(gentity_t * ent)1219 void health_think( gentity_t *ent )
1220 {
1221 int dif;
1222
1223 // He's dead, Jim. Don't give him health
1224 if (ent->enemy->health<1)
1225 {
1226 ent->count = 0;
1227 ent->e_ThinkFunc = thinkF_NULL;
1228 }
1229
1230 // Still has power to give
1231 if (ent->count > 0)
1232 {
1233 // For every 3 points of health, you get 1 point of armor
1234 // BUT!!! after health is filled up, you get the full energy going to armor
1235
1236 dif = ent->enemy->client->ps.stats[STAT_MAX_HEALTH] - ent->enemy->health;
1237
1238 if (dif > 3 )
1239 {
1240 dif= 3;
1241 }
1242 else if (dif < 0)
1243 {
1244 dif= 0;
1245 }
1246
1247 if (dif > ent->count) // Can't give more than count
1248 {
1249 dif = ent->count;
1250 }
1251
1252 if ((ITM_AddHealth (ent->enemy,dif)) && (dif>0))
1253 {
1254 ITM_AddArmor (ent->enemy,1); // 1 armor for every 3 health
1255
1256 ent->count-=dif;
1257 ent->nextthink = level.time + 10;
1258 }
1259 else // User has taken all health he can hold, see about giving it all to armor
1260 {
1261 dif = ent->enemy->client->ps.stats[STAT_MAX_HEALTH] -
1262 ent->enemy->client->ps.stats[STAT_ARMOR];
1263
1264 if (dif > 3)
1265 {
1266 dif = 3;
1267 }
1268 else if (dif < 0)
1269 {
1270 dif= 0;
1271 }
1272
1273 if (ent->count < dif) // Can't give more than count
1274 {
1275 dif = ent->count;
1276 }
1277
1278 if ((!ITM_AddArmor(ent->enemy,dif)) || (dif<=0))
1279 {
1280 ent->e_UseFunc = useF_health_use;
1281 ent->e_ThinkFunc = thinkF_NULL;
1282 }
1283 else
1284 {
1285 ent->count-=dif;
1286 ent->nextthink = level.time + 10;
1287 }
1288 }
1289 }
1290
1291 if (ent->count < 1)
1292 {
1293 health_shutdown(ent);
1294 }
1295 }
1296
misc_model_useup(gentity_t * self,gentity_t * other,gentity_t * activator)1297 void misc_model_useup( gentity_t *self, gentity_t *other, gentity_t *activator)
1298 {
1299 G_ActivateBehavior(self,BSET_USE);
1300
1301 self->s.eFlags &= ~ EF_ANIM_ALLFAST;
1302 self->s.eFlags |= EF_ANIM_ONCE;
1303
1304 // Switch to and animate its used up model.
1305 self->s.modelindex = self->s.modelindex2;
1306
1307 gi.linkentity (self);
1308
1309 // Use target when used
1310 if (self->spawnflags & 8)
1311 {
1312 G_UseTargets( self, activator );
1313 }
1314
1315 self->e_UseFunc = useF_NULL;
1316 self->e_ThinkFunc = thinkF_NULL;
1317 self->nextthink = -1;
1318 }
1319
health_use(gentity_t * self,gentity_t * other,gentity_t * activator)1320 void health_use( gentity_t *self, gentity_t *other, gentity_t *activator)
1321 {//FIXME: Heal entire team? Or only those that are undying...?
1322 int dif;
1323 int dif2;
1324 int hold;
1325
1326 G_ActivateBehavior(self,BSET_USE);
1327
1328 if (self->e_ThinkFunc != thinkF_NULL)
1329 {
1330 self->e_ThinkFunc = thinkF_NULL;
1331 }
1332 else
1333 {
1334
1335 if (other->client)
1336 {
1337 // He's dead, Jim. Don't give him health
1338 if (other->client->ps.stats[STAT_HEALTH]<1)
1339 {
1340 dif = 1;
1341 self->count = 0;
1342 }
1343 else
1344 { // Health
1345 dif = other->client->ps.stats[STAT_MAX_HEALTH] - other->client->ps.stats[STAT_HEALTH];
1346 // Armor
1347 dif2 = other->client->ps.stats[STAT_MAX_HEALTH] - other->client->ps.stats[STAT_ARMOR];
1348 hold = (dif2 - dif);
1349 // For every 3 points of health, you get 1 point of armor
1350 // BUT!!! after health is filled up, you get the full energy going to armor
1351 if (hold>0) // Need more armor than health
1352 {
1353 // Calculate total amount of station energy needed.
1354
1355 hold = dif / 3; // For every 3 points of health, you get 1 point of armor
1356 dif2 -= hold;
1357 dif2 += dif;
1358
1359 dif = dif2;
1360 }
1361 }
1362 }
1363 else
1364 { // Being triggered to be used up
1365 dif = 1;
1366 self->count = 0;
1367 }
1368
1369 // Does player already have full health and full armor?
1370 if (dif > 0)
1371 {
1372 // G_Sound(self, G_SoundIndex("sound/player/suithealth.wav") );
1373
1374 if ((dif >= self->count) || (self->count<1)) // use it all up?
1375 {
1376 health_shutdown(self);
1377 }
1378 // Use target when used
1379 if (self->spawnflags & 8)
1380 {
1381 G_UseTargets( self, activator );
1382 }
1383
1384 self->e_UseFunc = useF_NULL;
1385 self->enemy = other;
1386 self->e_ThinkFunc = thinkF_health_think;
1387 self->nextthink = level.time + 50;
1388 }
1389 else
1390 {
1391 // G_Sound(self, G_SoundIndex("sound/weapons/noammo.wav") );
1392 }
1393 }
1394 }
1395
1396 // --------------------------------------------------------------------
1397 //
1398 // AMMO plugin functions
1399 //
1400 // --------------------------------------------------------------------
1401 void ammo_use( gentity_t *self, gentity_t *other, gentity_t *activator);
1402 int Add_Ammo2 (gentity_t *ent, int ammoType, int count);
1403
ammo_shutdown(gentity_t * self)1404 void ammo_shutdown( gentity_t *self )
1405 {
1406 if (!(self->s.eFlags & EF_ANIM_ONCE))
1407 {
1408 self->s.eFlags &= ~ EF_ANIM_ALLFAST;
1409 self->s.eFlags |= EF_ANIM_ONCE;
1410
1411 gi.linkentity (self);
1412 }
1413 }
ammo_think(gentity_t * ent)1414 void ammo_think( gentity_t *ent )
1415 {
1416 int dif;
1417
1418 // Still has ammo to give
1419 if (ent->count > 0 && ent->enemy )
1420 {
1421 dif = ammoData[AMMO_BLASTER].max - ent->enemy->client->ps.ammo[AMMO_BLASTER];
1422
1423 if (dif > 2 )
1424 {
1425 dif= 2;
1426 }
1427 else if (dif < 0)
1428 {
1429 dif= 0;
1430 }
1431
1432 if (ent->count < dif) // Can't give more than count
1433 {
1434 dif = ent->count;
1435 }
1436
1437 // Give player ammo
1438 if (Add_Ammo2(ent->enemy,AMMO_BLASTER,dif) && (dif!=0))
1439 {
1440 ent->count-=dif;
1441 ent->nextthink = level.time + 10;
1442 }
1443 else // User has taken all ammo he can hold
1444 {
1445 ent->e_UseFunc = useF_ammo_use;
1446 ent->e_ThinkFunc = thinkF_NULL;
1447 }
1448 }
1449
1450 if (ent->count < 1)
1451 {
1452 ammo_shutdown(ent);
1453 }
1454 }
1455
1456 //------------------------------------------------------------
ammo_use(gentity_t * self,gentity_t * other,gentity_t * activator)1457 void ammo_use( gentity_t *self, gentity_t *other, gentity_t *activator)
1458 {
1459 int dif;
1460
1461 G_ActivateBehavior(self,BSET_USE);
1462
1463 if (self->e_ThinkFunc != thinkF_NULL)
1464 {
1465 if (self->e_UseFunc != useF_NULL)
1466 {
1467 self->e_ThinkFunc = thinkF_NULL;
1468 }
1469 }
1470 else
1471 {
1472 if (other->client)
1473 {
1474 dif = ammoData[AMMO_BLASTER].max - other->client->ps.ammo[AMMO_BLASTER];
1475 }
1476 else
1477 { // Being triggered to be used up
1478 dif = 1;
1479 self->count = 0;
1480 }
1481
1482 // Does player already have full ammo?
1483 if (dif > 0)
1484 {
1485 // G_Sound(self, G_SoundIndex("sound/player/suitenergy.wav") );
1486
1487 if ((dif >= self->count) || (self->count<1)) // use it all up?
1488 {
1489 ammo_shutdown(self);
1490 }
1491 }
1492 else
1493 {
1494 // G_Sound(self, G_SoundIndex("sound/weapons/noammo.wav") );
1495 }
1496 // Use target when used
1497 if (self->spawnflags & 8)
1498 {
1499 G_UseTargets( self, activator );
1500 }
1501
1502 self->e_UseFunc = useF_NULL;
1503 G_SetEnemy( self, other );
1504 self->e_ThinkFunc = thinkF_ammo_think;
1505 self->nextthink = level.time + 50;
1506 }
1507 }
1508
1509 //------------------------------------------------------------
mega_ammo_use(gentity_t * self,gentity_t * other,gentity_t * activator)1510 void mega_ammo_use( gentity_t *self, gentity_t *other, gentity_t *activator )
1511 {
1512 G_ActivateBehavior( self, BSET_USE );
1513
1514 // Use target when used
1515 G_UseTargets( self, activator );
1516
1517 // first use, adjust the max ammo a person can hold for each type of ammo
1518 ammoData[AMMO_BLASTER].max = 999;
1519 ammoData[AMMO_POWERCELL].max = 999;
1520
1521 // Set up our count with whatever the max difference will be
1522 if ( other->client->ps.ammo[AMMO_POWERCELL] > other->client->ps.ammo[AMMO_BLASTER] )
1523 self->count = ammoData[AMMO_BLASTER].max - other->client->ps.ammo[AMMO_BLASTER];
1524 else
1525 self->count = ammoData[AMMO_POWERCELL].max - other->client->ps.ammo[AMMO_POWERCELL];
1526
1527 // G_Sound( self, G_SoundIndex("sound/player/superenergy.wav") );
1528
1529 // Clear our usefunc, then think until our ammo is full
1530 self->e_UseFunc = useF_NULL;
1531 G_SetEnemy( self, other );
1532 self->e_ThinkFunc = thinkF_mega_ammo_think;
1533 self->nextthink = level.time + 50;
1534
1535 self->s.frame = 0;
1536 self->s.eFlags |= EF_ANIM_ONCE;
1537 }
1538
1539 //------------------------------------------------------------
mega_ammo_think(gentity_t * self)1540 void mega_ammo_think( gentity_t *self )
1541 {
1542 int ammo_add = 5;
1543
1544 // If the middle model is done animating, and we haven't switched to the last model yet...
1545 // chuck up the last model.
1546
1547 if (!Q_stricmp(self->model,"models/mapobjects/forge/power_up_boss.md3")) // Because the normal forge_ammo model can use this too
1548 {
1549 if ( self->s.frame > 16 && self->s.modelindex != self->s.modelindex2 )
1550 self->s.modelindex = self->s.modelindex2;
1551 }
1552
1553 if ( self->enemy && self->count > 0 )
1554 {
1555 // Add an equal ammount of ammo to each type
1556 self->enemy->client->ps.ammo[AMMO_BLASTER] += ammo_add;
1557 self->enemy->client->ps.ammo[AMMO_POWERCELL] += ammo_add;
1558
1559 // Now cap to prevent overflows
1560 if ( self->enemy->client->ps.ammo[AMMO_BLASTER] > ammoData[AMMO_BLASTER].max )
1561 self->enemy->client->ps.ammo[AMMO_BLASTER] = ammoData[AMMO_BLASTER].max;
1562
1563 if ( self->enemy->client->ps.ammo[AMMO_POWERCELL] > ammoData[AMMO_POWERCELL].max )
1564 self->enemy->client->ps.ammo[AMMO_POWERCELL] = ammoData[AMMO_POWERCELL].max;
1565
1566 // Decrement the count given counter
1567 self->count -= ammo_add;
1568
1569 // If we've given all we should, prevent giving any more, even if they player is no longer full
1570 if ( self->count <= 0 )
1571 {
1572 self->count = 0;
1573 self->e_ThinkFunc = thinkF_NULL;
1574 self->nextthink = -1;
1575 }
1576 else
1577 self->nextthink = 20;
1578 }
1579 }
1580
1581
1582 //------------------------------------------------------------
switch_models(gentity_t * self,gentity_t * other,gentity_t * activator)1583 void switch_models( gentity_t *self, gentity_t *other, gentity_t *activator )
1584 {
1585 // FIXME: need a sound here!!
1586 if ( self->s.modelindex2 )
1587 self->s.modelindex = self->s.modelindex2;
1588 }
1589
1590 //------------------------------------------------------------
touch_ammo_crystal_tigger(gentity_t * self,gentity_t * other,trace_t * trace)1591 void touch_ammo_crystal_tigger( gentity_t *self, gentity_t *other, trace_t *trace )
1592 {
1593 if ( !other->client )
1594 return;
1595
1596 // dead people can't pick things up
1597 if ( other->health < 1 )
1598 return;
1599
1600 // Only player can pick it up
1601 if ( other->s.number != 0 )
1602 {
1603 return;
1604 }
1605
1606 if ( other->client->ps.ammo[ AMMO_POWERCELL ] >= ammoData[AMMO_POWERCELL].max )
1607 {
1608 return; // can't hold any more
1609 }
1610
1611 // Add the ammo
1612 other->client->ps.ammo[AMMO_POWERCELL] += self->owner->count;
1613
1614 if ( other->client->ps.ammo[AMMO_POWERCELL] > ammoData[AMMO_POWERCELL].max )
1615 {
1616 other->client->ps.ammo[AMMO_POWERCELL] = ammoData[AMMO_POWERCELL].max;
1617 }
1618
1619 // Trigger once only
1620 self->e_TouchFunc = touchF_NULL;
1621
1622 // swap the model to the version without the crystal and ditch the infostring
1623 self->owner->s.modelindex = self->owner->s.modelindex2;
1624
1625 // play the normal pickup sound
1626 // G_AddEvent( other, EV_ITEM_PICKUP, ITM_AMMO_CRYSTAL_BORG );
1627
1628 // fire item targets
1629 G_UseTargets( self->owner, other );
1630 }
1631
1632 //------------------------------------------------------------
spawn_ammo_crystal_trigger(gentity_t * ent)1633 void spawn_ammo_crystal_trigger( gentity_t *ent )
1634 {
1635 gentity_t *other;
1636 vec3_t mins, maxs;
1637
1638 // Set the base bounds
1639 VectorCopy( ent->s.origin, mins );
1640 VectorCopy( ent->s.origin, maxs );
1641
1642 // Now add an area of influence around the thing
1643 for ( int i = 0; i < 3; i++ )
1644 {
1645 maxs[i] += 48;
1646 mins[i] -= 48;
1647 }
1648
1649 // create a trigger with this size
1650 other = G_Spawn( );
1651
1652 VectorCopy( mins, other->mins );
1653 VectorCopy( maxs, other->maxs );
1654
1655 // set up the other bits that the engine needs to know
1656 other->owner = ent;
1657 other->contents = CONTENTS_TRIGGER;
1658 other->e_TouchFunc = touchF_touch_ammo_crystal_tigger;
1659
1660 gi.linkentity( other );
1661 }
1662
misc_replicator_item_remove(gentity_t * self,gentity_t * other,gentity_t * activator)1663 void misc_replicator_item_remove ( gentity_t *self, gentity_t *other, gentity_t *activator )
1664 {
1665 self->s.eFlags |= EF_NODRAW;
1666 //self->contents = 0;
1667 self->s.modelindex = 0;
1668 self->e_UseFunc = useF_misc_replicator_item_spawn;
1669 //FIXME: pickup sound?
1670 if ( activator->client )
1671 {
1672 activator->health += 5;
1673 if ( activator->health > activator->client->ps.stats[STAT_MAX_HEALTH] ) // Past max health
1674 {
1675 activator->health = activator->client->ps.stats[STAT_MAX_HEALTH];
1676 }
1677 }
1678 }
1679
misc_replicator_item_finish_spawn(gentity_t * self)1680 void misc_replicator_item_finish_spawn( gentity_t *self )
1681 {
1682 //self->contents = CONTENTS_ITEM;
1683 //FIXME: blinks out for a couple frames when transporter effect is done?
1684 self->e_UseFunc = useF_misc_replicator_item_remove;
1685 }
1686
misc_replicator_item_spawn(gentity_t * self,gentity_t * other,gentity_t * activator)1687 void misc_replicator_item_spawn ( gentity_t *self, gentity_t *other, gentity_t *activator )
1688 {
1689 switch ( Q_irand( 1, self->count ) )
1690 {
1691 case 1:
1692 self->s.modelindex = self->bounceCount;
1693 break;
1694 case 2:
1695 self->s.modelindex = self->fly_sound_debounce_time;
1696 break;
1697 case 3:
1698 self->s.modelindex = self->painDebounceTime;
1699 break;
1700 case 4:
1701 self->s.modelindex = self->disconnectDebounceTime;
1702 break;
1703 case 5:
1704 self->s.modelindex = self->attackDebounceTime;
1705 break;
1706 case 6://max
1707 self->s.modelindex = self->pushDebounceTime;
1708 break;
1709 }
1710 self->s.eFlags &= ~EF_NODRAW;
1711 self->e_ThinkFunc = thinkF_misc_replicator_item_finish_spawn;
1712 self->nextthink = level.time + 4000;//shorter?
1713 self->e_UseFunc = useF_NULL;
1714
1715 gentity_t *tent = G_TempEntity( self->currentOrigin, EV_REPLICATOR );
1716 tent->owner = self;
1717 }
1718
1719 /*QUAK-ED misc_replicator_item (0.2 0.8 0.2) (-4 -4 0) (4 4 8)
1720 When used. this will "spawn" an entity with a random model from the ones provided below...
1721
1722 Using it again removes the item as if it were picked up.
1723
1724 model - first random model key
1725 model2 - second random model key
1726 model3 - third random model key
1727 model4 - fourth random model key
1728 model5 - fifth random model key
1729 model6 - sixth random model key
1730
1731 NOTE: do not skip one of these model names, start with the lowest and fill in each next highest one with a value. A gap will cause the item to not work correctly.
1732
1733 NOTE: if you use an invalid model, it will still try to use it and show the NULL axis model (or nothing at all)
1734
1735 targetname - how you refer to it for using it
1736 */
SP_misc_replicator_item(gentity_t * self)1737 void SP_misc_replicator_item ( gentity_t *self )
1738 {
1739 if ( self->model )
1740 {
1741 self->bounceCount = G_ModelIndex( self->model );
1742 self->count++;
1743 if ( self->model2 )
1744 {
1745 self->fly_sound_debounce_time = G_ModelIndex( self->model2 );
1746 self->count++;
1747 if ( self->target )
1748 {
1749 self->painDebounceTime = G_ModelIndex( self->target );
1750 self->count++;
1751 if ( self->target2 )
1752 {
1753 self->disconnectDebounceTime = G_ModelIndex( self->target2 );
1754 self->count++;
1755 if ( self->target3 )
1756 {
1757 self->attackDebounceTime = G_ModelIndex( self->target3 );
1758 self->count++;
1759 if ( self->target4 )
1760 {
1761 self->pushDebounceTime = G_ModelIndex( self->target4 );
1762 self->count++;
1763 }
1764 }
1765 }
1766 }
1767 }
1768 }
1769 // G_SoundIndex( "sound/movers/switches/replicator.wav" );
1770 self->e_UseFunc = useF_misc_replicator_item_spawn;
1771
1772 self->s.eFlags |= EF_NODRAW;
1773 //self->contents = 0;
1774
1775 VectorSet( self->mins, -4, -4, 0 );
1776 VectorSet( self->maxs, 4, 4, 8 );
1777 G_SetOrigin( self, self->s.origin );
1778 G_SetAngles( self, self->s.angles );
1779
1780 gi.linkentity( self );
1781 }
1782
1783 /*QUAKED misc_trip_mine (0.2 0.8 0.2) (-4 -4 -4) (4 4 4) START_ON BROADCAST START_OFF
1784 Place in a map and point the angles at whatever surface you want it to attach to.
1785
1786 START_ON - If you give it a targetname to make it toggle-able, but want it to start on, set this flag
1787 BROADCAST - ONLY USE THIS IF YOU HAVE TO! causes the trip wire and loop sound to always happen, use this if the beam drops out in certain situations
1788 START_OFF - If you give it a targetname, it will start completely off (laser AND base unit) until used.
1789
1790 The trip mine will attach to that surface and fire it's beam away from the surface at an angle perpendicular to it.
1791
1792 targetname - starts off, when used, turns on (toggles)
1793
1794 FIXME: sometimes we want these to not be shootable... maybe just put them behind a force field?
1795 */
1796 extern void touchLaserTrap( gentity_t *ent, gentity_t *other, trace_t *trace );
1797 extern void CreateLaserTrap( gentity_t *laserTrap, vec3_t start, gentity_t *owner );
1798
misc_trip_mine_activate(gentity_t * self,gentity_t * other,gentity_t * activator)1799 void misc_trip_mine_activate( gentity_t *self, gentity_t *other, gentity_t *activator )
1800 {
1801 if ( self->e_ThinkFunc == thinkF_laserTrapThink )
1802 {
1803 self->s.eFlags &= ~EF_FIRING;
1804 self->s.loopSound = 0;
1805 self->e_ThinkFunc = thinkF_NULL;
1806 self->nextthink = -1;
1807 }
1808 else
1809 {
1810 self->e_ThinkFunc = thinkF_laserTrapThink;
1811 self->nextthink = level.time + FRAMETIME;
1812
1813 self->s.eFlags &= ~EF_NODRAW;
1814 self->contents = CONTENTS_SHOTCLIP;//CAN'T USE CONTENTS_SOLID because only ARCHITECTURE is contents_solid!!!
1815 self->takedamage = qtrue;
1816 }
1817 }
1818
SP_misc_trip_mine(gentity_t * self)1819 void SP_misc_trip_mine( gentity_t *self )
1820 {
1821 vec3_t forward, end;
1822 trace_t trace;
1823
1824 AngleVectors( self->s.angles, forward, NULL, NULL );
1825 VectorMA( self->s.origin, 128, forward, end );
1826
1827 gi.trace( &trace, self->s.origin, vec3_origin, vec3_origin, end, self->s.number, MASK_SHOT, (EG2_Collision)0, 0 );
1828
1829 if ( trace.allsolid || trace.startsolid )
1830 {
1831 Com_Error( ERR_DROP,"misc_trip_mine at %s in solid\n", vtos(self->s.origin) );
1832 G_FreeEntity( self );
1833 return;
1834 }
1835 if ( trace.fraction == 1.0 )
1836 {
1837 Com_Error( ERR_DROP,"misc_trip_mine at %s pointed at no surface\n", vtos(self->s.origin) );
1838 G_FreeEntity( self );
1839 return;
1840 }
1841
1842 RegisterItem( FindItemForWeapon( WP_TRIP_MINE )); //precache the weapon
1843
1844 self->count = 2/*TRIPWIRE_STYLE*/;
1845
1846 vectoangles( trace.plane.normal, end );
1847 G_SetOrigin( self, trace.endpos );
1848 G_SetAngles( self, end );
1849
1850 CreateLaserTrap( self, trace.endpos, self );
1851 touchLaserTrap( self, self, &trace );
1852 self->e_ThinkFunc = thinkF_NULL;
1853 self->nextthink = -1;
1854
1855 if ( !self->targetname || (self->spawnflags&1) )
1856 {//starts on
1857 misc_trip_mine_activate( self, self, self );
1858 }
1859 if ( self->targetname )
1860 {
1861 self->e_UseFunc = useF_misc_trip_mine_activate;
1862 }
1863
1864 if (( self->spawnflags & 2 )) // broadcast...should only be used in very rare cases. could fix networking, perhaps, but james suggested this because it's easier
1865 {
1866 self->svFlags |= SVF_BROADCAST;
1867 }
1868
1869 // Whether to start completelly off or not.
1870 if ( self->targetname && self->spawnflags & 4 )
1871 {
1872 self->s.eFlags = EF_NODRAW;
1873 self->contents = 0;
1874 self->takedamage = qfalse;
1875 }
1876
1877 gi.linkentity( self );
1878 }
1879
1880 /*QUAKED misc_maglock (0 .5 .8) (-8 -8 -8) (8 8 8) x x x x x x x x
1881 Place facing a door (using the angle, not a targetname) and it will lock that door. Can only be destroyed by lightsaber and will automatically unlock the door it's attached to
1882
1883 NOTE: place these half-way in the door to make it flush with the door's surface.
1884
1885 "target" thing to use when destoryed (not doors - it automatically unlocks the door it was angled at)
1886 "health" default is 10
1887 */
maglock_die(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int mod,int dFlags,int hitLoc)1888 void maglock_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod, int dFlags, int hitLoc )
1889 {
1890 //unlock our door if we're the last lock pointed at the door
1891 if ( self->activator )
1892 {
1893 self->activator->lockCount--;
1894 if ( !self->activator->lockCount )
1895 {
1896 self->activator->svFlags &= ~SVF_INACTIVE;
1897 }
1898 }
1899
1900 //use targets
1901 G_UseTargets( self, attacker );
1902 //die
1903 WP_Explode( self );
1904 }
SP_misc_maglock(gentity_t * self)1905 void SP_misc_maglock ( gentity_t *self )
1906 {
1907 //NOTE: May have to make these only work on doors that are either untargeted
1908 // or are targeted by a trigger, not doors fired off by scripts, counters
1909 // or other such things?
1910 self->s.modelindex = G_ModelIndex( "models/map_objects/imp_detention/door_lock.md3" );
1911 self->fxID = G_EffectIndex( "maglock/explosion" );
1912
1913 G_SetOrigin( self, self->s.origin );
1914
1915 self->e_ThinkFunc = thinkF_maglock_link;
1916 //FIXME: for some reason, when you re-load a level, these fail to find their doors...? Random? Testing an additional 200ms after the START_TIME_FIND_LINKS
1917 self->nextthink = level.time + START_TIME_FIND_LINKS+200;//START_TIME_FIND_LINKS;//because we need to let the doors link up and spawn their triggers first!
1918 }
maglock_link(gentity_t * self)1919 void maglock_link( gentity_t *self )
1920 {
1921 //find what we're supposed to be attached to
1922 vec3_t forward, start, end;
1923 trace_t trace;
1924
1925 AngleVectors( self->s.angles, forward, NULL, NULL );
1926 VectorMA( self->s.origin, 128, forward, end );
1927 VectorMA( self->s.origin, -4, forward, start );
1928
1929 gi.trace( &trace, start, vec3_origin, vec3_origin, end, self->s.number, MASK_SHOT, (EG2_Collision)0, 0 );
1930
1931 if ( trace.allsolid || trace.startsolid )
1932 {
1933 Com_Error( ERR_DROP,"misc_maglock at %s in solid\n", vtos(self->s.origin) );
1934 G_FreeEntity( self );
1935 return;
1936 }
1937 if ( trace.fraction == 1.0 )
1938 {
1939 self->e_ThinkFunc = thinkF_maglock_link;
1940 self->nextthink = level.time + 100;
1941 /*
1942 Com_Error( ERR_DROP,"misc_maglock at %s pointed at no surface\n", vtos(self->s.origin) );
1943 G_FreeEntity( self );
1944 */
1945 return;
1946 }
1947 gentity_t *traceEnt = &g_entities[trace.entityNum];
1948 if ( trace.entityNum >= ENTITYNUM_WORLD || !traceEnt || Q_stricmp( "func_door", traceEnt->classname ) )
1949 {
1950 self->e_ThinkFunc = thinkF_maglock_link;
1951 self->nextthink = level.time + 100;
1952 //Com_Error( ERR_DROP,"misc_maglock at %s not pointed at a door\n", vtos(self->s.origin) );
1953 //G_FreeEntity( self );
1954 return;
1955 }
1956
1957 //check the traceEnt, make sure it's a door and give it a lockCount and deactivate it
1958 //find the trigger for the door
1959 self->activator = G_FindDoorTrigger( traceEnt );
1960 if ( !self->activator )
1961 {
1962 self->activator = traceEnt;
1963 }
1964 self->activator->lockCount++;
1965 self->activator->svFlags |= SVF_INACTIVE;
1966
1967 //now position and orient it
1968 vectoangles( trace.plane.normal, end );
1969 G_SetOrigin( self, trace.endpos );
1970 G_SetAngles( self, end );
1971
1972 //make it hittable
1973 //FIXME: if rotated/inclined this bbox may be off... but okay if we're a ghoul model?
1974 //self->s.modelindex = G_ModelIndex( "models/map_objects/imp_detention/door_lock.md3" );
1975 VectorSet( self->mins, -8, -8, -8 );
1976 VectorSet( self->maxs, 8, 8, 8 );
1977 self->contents = CONTENTS_CORPSE;
1978
1979 //make it destroyable
1980 self->flags |= FL_SHIELDED;//only damagable by lightsabers
1981 self->takedamage = qtrue;
1982 self->health = 10;
1983 self->e_DieFunc = dieF_maglock_die;
1984 //self->fxID = G_EffectIndex( "maglock/explosion" );
1985
1986 gi.linkentity( self );
1987 }
1988
1989 /*
1990 ================
1991 EnergyShieldStationSettings
1992 ================
1993 */
EnergyShieldStationSettings(gentity_t * ent)1994 void EnergyShieldStationSettings(gentity_t *ent)
1995 {
1996 G_SpawnInt( "count", "0", &ent->count );
1997
1998 if (!ent->count)
1999 {
2000 switch (g_spskill->integer)
2001 {
2002 case 0: // EASY
2003 ent->count = 100;
2004 break;
2005 case 1: // MEDIUM
2006 ent->count = 75;
2007 break;
2008 default :
2009 case 2: // HARD
2010 ent->count = 50;
2011 break;
2012 }
2013 }
2014 }
2015
2016 /*
2017 ================
2018 shield_power_converter_use
2019 ================
2020 */
shield_power_converter_use(gentity_t * self,gentity_t * other,gentity_t * activator)2021 void shield_power_converter_use( gentity_t *self, gentity_t *other, gentity_t *activator)
2022 {
2023 int dif,add;
2024
2025 if ( !activator || activator->s.number != 0 )
2026 {
2027 //only the player gets to use these
2028 return;
2029 }
2030
2031 G_ActivateBehavior( self,BSET_USE );
2032
2033 if ( self->setTime < level.time )
2034 {
2035 self->setTime = level.time + 100;
2036
2037 dif = 100 - activator->client->ps.stats[STAT_ARMOR]; // FIXME: define for max armor?
2038
2039 if ( dif > 0 && self->count ) // Already at full armor?..and do I even have anything to give
2040 {
2041 if ( dif > MAX_AMMO_GIVE )
2042 {
2043 add = MAX_AMMO_GIVE;
2044 }
2045 else
2046 {
2047 add = dif;
2048 }
2049
2050 if ( self->count < add )
2051 {
2052 add = self->count;
2053 }
2054
2055 self->count -= add;
2056
2057 activator->client->ps.stats[STAT_ARMOR] += add;
2058
2059 self->s.loopSound = G_SoundIndex( "sound/interface/shieldcon_run.wav" );
2060 }
2061
2062 if ( self->count <= 0 )
2063 {
2064 // play empty sound
2065 self->setTime = level.time + 1000; // extra debounce so that the sounds don't overlap too much
2066 G_Sound( self, G_SoundIndex( "sound/interface/shieldcon_empty.mp3" ));
2067 self->s.loopSound = 0;
2068
2069 if ( self->s.eFlags & EF_SHADER_ANIM )
2070 {
2071 self->s.frame = 1;
2072 }
2073 }
2074 else if ( activator->client->ps.stats[STAT_ARMOR] >= 100 ) // FIXME: define for max
2075 {
2076 // play full sound
2077 G_Sound( self, G_SoundIndex( "sound/interface/shieldcon_done.mp3" ));
2078 self->setTime = level.time + 1000; // extra debounce so that the sounds don't overlap too much
2079 self->s.loopSound = 0;
2080 }
2081 }
2082
2083 if ( self->s.loopSound )
2084 {
2085 // we will have to shut of the loop sound, so I guess try and do it intelligently...NOTE: this could get completely stomped every time through the loop
2086 // this is fine, since it just controls shutting off the sound when there are situations that could start the sound but not shut it off
2087 self->e_ThinkFunc = thinkF_poll_converter;
2088 self->nextthink = level.time + 500;
2089 }
2090 else
2091 {
2092 // sound is already off, so we don't need to "think" about it.
2093 self->e_ThinkFunc = thinkF_NULL;
2094 self->nextthink = 0;
2095 }
2096
2097 if ( activator->client->ps.stats[STAT_ARMOR] > 0 )
2098 {
2099 activator->client->ps.powerups[PW_BATTLESUIT] = Q3_INFINITE;
2100 }
2101 }
2102
2103
2104 /*QUAKED misc_model_shield_power_converter (1 0 0) (-16 -16 -16) (16 16 16) x x x USETARGET
2105 model="models/items/psd_big.md3"
2106 Gives shield energy when used.
2107
2108 USETARGET - when used it fires off target
2109
2110 "health" - how much health the model has - default 60 (zero makes non-breakable)
2111 "targetname" - dies and displays damagemodel when used, if any (if not, removes itself)
2112 "target" - what to use when it dies
2113 "paintarget" - target to fire when hit (but not destroyed)
2114 "count" - the amount of ammo given when used (default 100)
2115 */
2116 //------------------------------------------------------------
SP_misc_model_shield_power_converter(gentity_t * ent)2117 void SP_misc_model_shield_power_converter( gentity_t *ent )
2118 {
2119 SetMiscModelDefaults( ent, useF_shield_power_converter_use, "4", CONTENTS_SOLID, 0, qfalse, qfalse );
2120
2121 ent->takedamage = qfalse;
2122
2123 EnergyShieldStationSettings(ent);
2124
2125 G_SoundIndex("sound/interface/shieldcon_run.wav");
2126 G_SoundIndex("sound/interface/shieldcon_done.mp3");
2127 G_SoundIndex("sound/interface/shieldcon_empty.mp3");
2128
2129 ent->s.modelindex = G_ModelIndex("models/items/psd_big.md3"); // Precache model
2130 ent->s.modelindex2 = G_ModelIndex("models/items/psd_big.md3"); // Precache model
2131 }
2132
bomb_planted_use(gentity_t * self,gentity_t * other,gentity_t * activator)2133 void bomb_planted_use( gentity_t *self, gentity_t *other, gentity_t *activator)
2134 {
2135 if ( self->count == 2 )
2136 {
2137 self->s.eFlags &= ~EF_NODRAW;
2138 self->contents = CONTENTS_SOLID;
2139 self->count = 1;
2140 self->s.loopSound = self->noise_index;
2141 }
2142 else if ( self->count == 1 )
2143 {
2144 self->count = 0;
2145 // play disarm sound
2146 self->setTime = level.time + 1000; // extra debounce so that the sounds don't overlap too much
2147 G_Sound( self, G_SoundIndex("sound/weapons/overchargeend"));
2148 self->s.loopSound = 0;
2149
2150 // this pauses the shader on one frame (more or less)
2151 self->s.eFlags |= EF_DISABLE_SHADER_ANIM;
2152
2153 // this starts the animation for the model
2154 self->s.eFlags |= EF_ANIM_ONCE;
2155 self->s.frame = 0;
2156
2157 //use targets
2158 G_UseTargets( self, activator );
2159 }
2160 }
2161
2162 /*QUAKED misc_model_bomb_planted (1 0 0) (-16 -16 0) (16 16 70) x x x USETARGET
2163 model="models/map_objects/factory/bomb_new_deact.md3"
2164 Planted by evil men for evil purposes.
2165
2166 "health" - please don't shoot the thermonuclear device
2167 "forcevisible" - When you turn on force sight (any level), you can see these draw through the entire level...
2168 */
2169 //------------------------------------------------------------
SP_misc_model_bomb_planted(gentity_t * ent)2170 void SP_misc_model_bomb_planted( gentity_t *ent )
2171 {
2172 VectorSet( ent->mins, -16, -16, 0 );
2173 VectorSet( ent->maxs, 16, 16, 70 );
2174
2175 SetMiscModelDefaults( ent, useF_bomb_planted_use, "4", CONTENTS_SOLID, 0, qfalse, qfalse );
2176
2177 ent->takedamage = qfalse;
2178
2179 G_SoundIndex("sound/weapons/overchargeend");
2180
2181 ent->s.modelindex = G_ModelIndex("models/map_objects/factory/bomb_new_deact.md3"); // Precache model
2182 ent->s.modelindex2 = G_ModelIndex("models/map_objects/factory/bomb_new_deact.md3"); // Precache model
2183 ent->noise_index = G_SoundIndex("sound/interface/ammocon_run");
2184 ent->s.loopSound = ent->noise_index;
2185 //ent->s.eFlags |= EF_SHADER_ANIM;
2186 //ent->s.frame = ent->startFrame = 0;
2187 ent->count = 1;
2188
2189 // If we have a targetname, we're are invisible until we are spawned in by being used.
2190 if ( ent->targetname )
2191 {
2192 ent->s.eFlags = EF_NODRAW;
2193 ent->contents = 0;
2194 ent->count = 2;
2195 ent->s.loopSound = 0;
2196 }
2197
2198 int forceVisible = 0;
2199 G_SpawnInt( "forcevisible", "0", &forceVisible );
2200 if ( forceVisible )
2201 {//can see these through walls with force sight, so must be broadcast
2202 //ent->svFlags |= SVF_BROADCAST;
2203 ent->s.eFlags |= EF_FORCE_VISIBLE;
2204 }
2205 }
2206
beacon_deploy(gentity_t * ent)2207 void beacon_deploy( gentity_t *ent )
2208 {
2209 ent->e_ThinkFunc = thinkF_beacon_think;
2210 ent->nextthink = level.time + FRAMETIME * 0.5f;
2211
2212 ent->s.frame = 0;
2213 ent->startFrame = 0;
2214 ent->endFrame = 30;
2215 ent->loopAnim = qfalse;
2216 }
2217
beacon_think(gentity_t * ent)2218 void beacon_think( gentity_t *ent )
2219 {
2220 ent->nextthink = level.time + FRAMETIME * 0.5f;
2221
2222 // Deploy animation complete? Stop thinking and just animate signal forever.
2223 if ( ent->s.frame == 30 )
2224 {
2225 ent->e_ThinkFunc = thinkF_NULL;
2226 ent->nextthink = -1;
2227
2228 ent->startFrame = 31;
2229 ent->endFrame = 60;
2230 ent->loopAnim = qtrue;
2231
2232 ent->s.loopSound = ent->noise_index;
2233 }
2234 }
2235
beacon_use(gentity_t * self,gentity_t * other,gentity_t * activator)2236 void beacon_use( gentity_t *self, gentity_t *other, gentity_t *activator)
2237 {
2238 // Every time it's used it will be toggled on or off.
2239 if ( self->count == 0 )
2240 {
2241 self->s.eFlags &= ~EF_NODRAW;
2242 self->contents = CONTENTS_SOLID;
2243 self->count = 1;
2244 self->svFlags = SVF_ANIMATING;
2245 beacon_deploy( self );
2246 }
2247 else
2248 {
2249 self->s.eFlags = EF_NODRAW;
2250 self->contents = 0;
2251 self->count = 0;
2252 self->s.loopSound = 0;
2253 self->svFlags = 0;
2254 }
2255 }
2256
2257 /*QUAKED misc_model_beacon (1 0 0) (-16 -16 0) (16 16 24) x x x
2258 model="models/map_objects/wedge/beacon.md3"
2259 An animating beacon model.
2260
2261 "forcevisible" - When you turn on force sight (any level), you can see these draw through the entire level...
2262 */
2263 //------------------------------------------------------------
SP_misc_model_beacon(gentity_t * ent)2264 void SP_misc_model_beacon( gentity_t *ent )
2265 {
2266 VectorSet( ent->mins, -16, -16, 0 );
2267 VectorSet( ent->maxs, 16, 16, 24 );
2268
2269 SetMiscModelDefaults( ent, useF_beacon_use, "4", CONTENTS_SOLID, 0, qfalse, qfalse );
2270
2271 ent->takedamage = qfalse;
2272
2273 //G_SoundIndex("sound/weapons/overchargeend");
2274
2275 ent->s.modelindex = G_ModelIndex("models/map_objects/wedge/beacon.md3"); // Precache model
2276 ent->s.modelindex2 = G_ModelIndex("models/map_objects/wedge/beacon.md3"); // Precache model
2277 ent->noise_index = G_SoundIndex("sound/interface/ammocon_run");
2278
2279 // If we have a targetname, we're are invisible until we are spawned in by being used.
2280 if ( ent->targetname )
2281 {
2282 ent->s.eFlags = EF_NODRAW;
2283 ent->contents = 0;
2284 ent->s.loopSound = 0;
2285 ent->count = 0;
2286 }
2287 else
2288 {
2289 ent->count = 1;
2290 beacon_deploy( ent );
2291 }
2292
2293 int forceVisible = 0;
2294 G_SpawnInt( "forcevisible", "0", &forceVisible );
2295 if ( forceVisible )
2296 {//can see these through walls with force sight, so must be broadcast
2297 //ent->svFlags |= SVF_BROADCAST;
2298 ent->s.eFlags |= EF_FORCE_VISIBLE;
2299 }
2300 }
2301
2302 /*QUAKED misc_shield_floor_unit (1 0 0) (-16 -16 0) (16 16 32) x x x USETARGET
2303 model="models/items/a_shield_converter.md3"
2304 Gives shield energy when used.
2305
2306 USETARGET - when used it fires off target
2307
2308 "health" - how much health the model has - default 60 (zero makes non-breakable)
2309 "targetname" - dies and displays damagemodel when used, if any (if not, removes itself)
2310 "target" - what to use when it dies
2311 "paintarget" - target to fire when hit (but not destroyed)
2312 "count" - the amount of ammo given when used (default 100)
2313 */
2314 //------------------------------------------------------------
SP_misc_shield_floor_unit(gentity_t * ent)2315 void SP_misc_shield_floor_unit( gentity_t *ent )
2316 {
2317 VectorSet( ent->mins, -16, -16, 0 );
2318 VectorSet( ent->maxs, 16, 16, 32 );
2319
2320 SetMiscModelDefaults( ent, useF_shield_power_converter_use, "4", CONTENTS_SOLID, 0, qfalse, qfalse );
2321
2322 ent->takedamage = qfalse;
2323
2324 EnergyShieldStationSettings(ent);
2325
2326 G_SoundIndex("sound/interface/shieldcon_run.wav");
2327 G_SoundIndex("sound/interface/shieldcon_done.mp3");
2328 G_SoundIndex("sound/interface/shieldcon_empty.mp3");
2329
2330 ent->s.modelindex = G_ModelIndex( "models/items/a_shield_converter.md3" ); // Precache model
2331 ent->s.eFlags |= EF_SHADER_ANIM;
2332 }
2333
2334
2335 /*
2336 ================
2337 EnergyAmmoShieldStationSettings
2338 ================
2339 */
EnergyAmmoStationSettings(gentity_t * ent)2340 void EnergyAmmoStationSettings(gentity_t *ent)
2341 {
2342 G_SpawnInt( "count", "0", &ent->count );
2343
2344 if (!ent->count)
2345 {
2346 switch (g_spskill->integer)
2347 {
2348 case 0: // EASY
2349 ent->count = 100;
2350 break;
2351 case 1: // MEDIUM
2352 ent->count = 75;
2353 break;
2354 default :
2355 case 2: // HARD
2356 ent->count = 50;
2357 break;
2358 }
2359 }
2360 }
2361
2362 // There has to be an easier way to turn off the looping sound...but
2363 // it's the night before beta and my brain is fried
2364 //------------------------------------------------------------------
poll_converter(gentity_t * self)2365 void poll_converter( gentity_t *self )
2366 {
2367 self->s.loopSound = 0;
2368 self->nextthink = 0;
2369 self->e_ThinkFunc = thinkF_NULL;
2370 }
2371
2372 /*
2373 ================
2374 ammo_power_converter_use
2375 ================
2376 */
ammo_power_converter_use(gentity_t * self,gentity_t * other,gentity_t * activator)2377 void ammo_power_converter_use( gentity_t *self, gentity_t *other, gentity_t *activator)
2378 {
2379 int add;
2380 int difBlaster,difPowerCell,difMetalBolts;
2381 playerState_t *ps;
2382
2383 if ( !activator || activator->s.number != 0 )
2384 {//only the player gets to use these
2385 return;
2386 }
2387
2388 G_ActivateBehavior( self, BSET_USE );
2389
2390 ps = &activator->client->ps;
2391
2392 if ( self->setTime < level.time )
2393 {
2394 difBlaster = ammoData[AMMO_BLASTER].max - ps->ammo[AMMO_BLASTER];
2395 difPowerCell = ammoData[AMMO_POWERCELL].max - ps->ammo[AMMO_POWERCELL];
2396 difMetalBolts = ammoData[AMMO_METAL_BOLTS].max - ps->ammo[AMMO_METAL_BOLTS];
2397
2398 // Has it got any power left...and can we even use any of it?
2399 if ( self->count && ( difBlaster > 0 || difPowerCell > 0 || difMetalBolts > 0 ))
2400 {
2401 // at least one of the ammo types could stand to take on a bit more ammo
2402 self->setTime = level.time + 100;
2403 self->s.loopSound = G_SoundIndex( "sound/interface/ammocon_run.wav" );
2404
2405 // dole out ammo in little packets
2406 if ( self->count > MAX_AMMO_GIVE )
2407 {
2408 add = MAX_AMMO_GIVE;
2409 }
2410 else if ( self->count < 0 )
2411 {
2412 add = 0;
2413 }
2414 else
2415 {
2416 add = self->count;
2417 }
2418
2419 // all weapons fill at same rate...
2420 ps->ammo[AMMO_BLASTER] += add;
2421 ps->ammo[AMMO_POWERCELL] += add;
2422 ps->ammo[AMMO_METAL_BOLTS] += add;
2423
2424 // ...then get clamped to max
2425 if ( ps->ammo[AMMO_BLASTER] > ammoData[AMMO_BLASTER].max )
2426 {
2427 ps->ammo[AMMO_BLASTER] = ammoData[AMMO_BLASTER].max;
2428 }
2429
2430 if ( ps->ammo[AMMO_POWERCELL] > ammoData[AMMO_POWERCELL].max )
2431 {
2432 ps->ammo[AMMO_POWERCELL] = ammoData[AMMO_POWERCELL].max;
2433 }
2434
2435 if ( ps->ammo[AMMO_METAL_BOLTS] > ammoData[AMMO_METAL_BOLTS].max )
2436 {
2437 ps->ammo[AMMO_METAL_BOLTS] = ammoData[AMMO_METAL_BOLTS].max;
2438 }
2439
2440 self->count -= add;
2441 }
2442
2443 if ( self->count <= 0 )
2444 {
2445 // play empty sound
2446 self->setTime = level.time + 1000; // extra debounce so that the sounds don't overlap too much
2447 G_Sound( self, G_SoundIndex( "sound/interface/ammocon_empty.mp3" ));
2448 self->s.loopSound = 0;
2449
2450 if ( self->s.eFlags & EF_SHADER_ANIM )
2451 {
2452 self->s.frame = 1;
2453 }
2454 }
2455 else if ( ps->ammo[AMMO_BLASTER] >= ammoData[AMMO_BLASTER].max
2456 && ps->ammo[AMMO_POWERCELL] >= ammoData[AMMO_POWERCELL].max
2457 && ps->ammo[AMMO_METAL_BOLTS] >= ammoData[AMMO_METAL_BOLTS].max )
2458 {
2459 // play full sound
2460 G_Sound( self, G_SoundIndex( "sound/interface/ammocon_done.wav" ));
2461 self->setTime = level.time + 1000; // extra debounce so that the sounds don't overlap too much
2462 self->s.loopSound = 0;
2463 }
2464 }
2465
2466
2467 if ( self->s.loopSound )
2468 {
2469 // we will have to shut of the loop sound, so I guess try and do it intelligently...NOTE: this could get completely stomped every time through the loop
2470 // this is fine, since it just controls shutting off the sound when there are situations that could start the sound but not shut it off
2471 self->e_ThinkFunc = thinkF_poll_converter;
2472 self->nextthink = level.time + 500;
2473 }
2474 else
2475 {
2476 // sound is already off, so we don't need to "think" about it.
2477 self->e_ThinkFunc = thinkF_NULL;
2478 self->nextthink = 0;
2479 }
2480 }
2481
2482 /*QUAKED misc_model_ammo_power_converter (1 0 0) (-16 -16 -16) (16 16 16) x x x USETARGET
2483 model="models/items/power_converter.md3"
2484 Gives ammo energy when used.
2485
2486 USETARGET - when used it fires off target
2487
2488 "health" - how much health the model has - default 60 (zero makes non-breakable)
2489 "targetname" - dies and displays damagemodel when used, if any (if not, removes itself)
2490 "target" - what to use when it dies
2491 "paintarget" - target to fire when hit (but not destroyed)
2492 "count" - the amount of ammo given when used (default 100)
2493 */
2494 //------------------------------------------------------------
SP_misc_model_ammo_power_converter(gentity_t * ent)2495 void SP_misc_model_ammo_power_converter( gentity_t *ent )
2496 {
2497 SetMiscModelDefaults( ent, useF_ammo_power_converter_use, "4", CONTENTS_SOLID, 0, qfalse, qfalse );
2498
2499 ent->takedamage = qfalse;
2500
2501 EnergyAmmoStationSettings(ent);
2502
2503 G_SoundIndex("sound/interface/ammocon_run.wav");
2504 G_SoundIndex("sound/interface/ammocon_done.mp3");
2505 G_SoundIndex("sound/interface/ammocon_empty.mp3");
2506
2507 ent->s.modelindex = G_ModelIndex("models/items/power_converter.md3"); // Precache model
2508 ent->s.modelindex2 = G_ModelIndex("models/items/power_converter.md3"); // Precache model
2509 }
2510
2511 /*QUAKED misc_ammo_floor_unit (1 0 0) (-16 -16 0) (16 16 32) x x x USETARGET
2512 model="models/items/a_pwr_converter.md3"
2513 Gives ammo energy when used.
2514
2515 USETARGET - when used it fires off target
2516
2517 "health" - how much health the model has - default 60 (zero makes non-breakable)
2518 "targetname" - dies and displays damagemodel when used, if any (if not, removes itself)
2519 "target" - what to use when it dies
2520 "paintarget" - target to fire when hit (but not destroyed)
2521 "count" - the amount of ammo given when used (default 100)
2522 */
2523 //------------------------------------------------------------
SP_misc_ammo_floor_unit(gentity_t * ent)2524 void SP_misc_ammo_floor_unit( gentity_t *ent )
2525 {
2526 VectorSet( ent->mins, -16, -16, 0 );
2527 VectorSet( ent->maxs, 16, 16, 32 );
2528
2529 SetMiscModelDefaults( ent, useF_ammo_power_converter_use, "4", CONTENTS_SOLID, 0, qfalse, qfalse );
2530
2531 ent->takedamage = qfalse;
2532
2533 EnergyAmmoStationSettings(ent);
2534
2535 G_SoundIndex("sound/interface/ammocon_run.wav");
2536 G_SoundIndex("sound/interface/ammocon_done.mp3");
2537 G_SoundIndex("sound/interface/ammocon_empty.mp3");
2538
2539 ent->s.modelindex = G_ModelIndex("models/items/a_pwr_converter.md3"); // Precache model
2540 ent->s.eFlags |= EF_SHADER_ANIM;
2541 }
2542
2543
2544 /*
2545 ================
2546 welder_think
2547 ================
2548 */
welder_think(gentity_t * self)2549 void welder_think( gentity_t *self )
2550 {
2551 self->nextthink = level.time + 200;
2552 vec3_t org,
2553 dir;
2554 mdxaBone_t boltMatrix;
2555
2556 if( self->svFlags & SVF_INACTIVE )
2557 {
2558 return;
2559 }
2560
2561 int newBolt;
2562
2563 // could alternate between the two... or make it random... ?
2564 newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], "*flash" );
2565 // newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], "*flash01" );
2566
2567 if ( newBolt != -1 )
2568 {
2569 G_Sound( self, self->noise_index );
2570 // G_PlayEffect( "blueWeldSparks", self->playerModel, newBolt, self->s.number);
2571 // The welder gets rotated around a lot, and since the origin is offset by 352 I have to make this super expensive call to position the hurt...
2572 gi.G2API_GetBoltMatrix( self->ghoul2, self->playerModel, newBolt,
2573 &boltMatrix, self->currentAngles, self->currentOrigin, (cg.time?cg.time:level.time),
2574 NULL, self->s.modelScale );
2575
2576 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, org );
2577 VectorSubtract( self->currentOrigin, org, dir );
2578 VectorNormalize( dir );
2579 // we want the welder effect to face inwards....
2580 G_PlayEffect( "sparks/blueWeldSparks", org, dir );
2581 G_RadiusDamage( org, self, 10, 45, self, MOD_UNKNOWN );
2582 }
2583
2584 }
2585
2586 /*
2587 ================
2588 welder_use
2589 ================
2590 */
welder_use(gentity_t * self,gentity_t * other,gentity_t * activator)2591 void welder_use( gentity_t *self, gentity_t *other, gentity_t *activator )
2592 {
2593 // Toggle on and off
2594 if( self->spawnflags & 1 )
2595 {
2596 self->nextthink = level.time + FRAMETIME;
2597 }
2598 else
2599 {
2600 self->nextthink = -1;
2601 }
2602 self->spawnflags = (self->spawnflags ^ 1);
2603
2604 }
2605
2606 /*QUAKED misc_model_welder (1 0 0) (-16 -16 -16) (16 16 16) START_OFF
2607 model="models/map_objects/cairn/welder.md3"
2608 When 'on' emits sparks from it's welding points
2609
2610 START_OFF - welder starts off, using it toggles it on/off
2611
2612 */
2613 //------------------------------------------------------------
SP_misc_model_welder(gentity_t * ent)2614 void SP_misc_model_welder( gentity_t *ent )
2615 {
2616 VectorSet( ent->mins, 336, -16, 0 );
2617 VectorSet( ent->maxs, 368, 16, 32 );
2618
2619 SetMiscModelDefaults( ent, useF_welder_use, "4", CONTENTS_SOLID, 0, qfalse, qfalse );
2620
2621 ent->takedamage = qfalse;
2622 ent->contents = 0;
2623 G_EffectIndex( "sparks/blueWeldSparks" );
2624 ent->noise_index = G_SoundIndex( "sound/movers/objects/welding.wav" );
2625
2626 ent->s.modelindex = G_ModelIndex( "models/map_objects/cairn/welder.glm" );
2627 // ent->s.modelindex2 = G_ModelIndex( "models/map_objects/cairn/welder.md3" );
2628 ent->playerModel = gi.G2API_InitGhoul2Model( ent->ghoul2, "models/map_objects/cairn/welder.glm", ent->s.modelindex, NULL_HANDLE, NULL_HANDLE, 0, 0 );
2629 ent->s.radius = 400.0f;// the origin of the welder is offset by approximately 352, so we need the big radius
2630
2631 ent->e_ThinkFunc = thinkF_welder_think;
2632
2633 ent->nextthink = level.time + 1000;
2634 if( ent->spawnflags & 1 )
2635 {
2636 ent->nextthink = -1;
2637 }
2638 }
2639
2640
2641 /*
2642 ================
2643 jabba_cam_use
2644 ================
2645 */
jabba_cam_use(gentity_t * self,gentity_t * other,gentity_t * activator)2646 void jabba_cam_use( gentity_t *self, gentity_t *other, gentity_t *activator )
2647 {
2648 if( self->spawnflags & 1 )
2649 {
2650 self->spawnflags &= ~1;
2651 gi.G2API_SetBoneAnimIndex( &self->ghoul2[self->playerModel], self->rootBone, 15, 0, BONE_ANIM_OVERRIDE_FREEZE, -1.5f, (cg.time?cg.time:level.time), -1, 0 );
2652
2653 }
2654 else
2655 {
2656 self->spawnflags |= 1;
2657 gi.G2API_SetBoneAnimIndex( &self->ghoul2[self->playerModel], self->rootBone, 0, 15, BONE_ANIM_OVERRIDE_FREEZE, 1.5f, (cg.time?cg.time:level.time), -1, 0 );
2658 }
2659 }
2660
2661 /*QUAKED misc_model_jabba_cam (1 0 0) ( 0 -8 0) (60 8 16) EXTENDED
2662 model="models/map_objects/nar_shaddar/jabacam.md3"
2663
2664 The eye camera that popped out of Jabba's front door
2665
2666 EXTENDED - Starts in the extended position
2667
2668 targetname - Toggles it on/off
2669
2670 */
2671 //-----------------------------------------------------
SP_misc_model_jabba_cam(gentity_t * ent)2672 void SP_misc_model_jabba_cam( gentity_t *ent )
2673 //-----------------------------------------------------
2674 {
2675
2676 VectorSet( ent->mins, -60.0f, -8.0f, 0.0f );
2677 VectorSet( ent->maxs, 60.0f, 8.0f, 16.0f );
2678
2679 SetMiscModelDefaults( ent, useF_jabba_cam_use, "4", 0, 0, qfalse, qfalse );
2680 G_SetAngles( ent, ent->s.angles );
2681
2682 ent->s.modelindex = G_ModelIndex( "models/map_objects/nar_shaddar/jabacam/jabacam.glm" );
2683 ent->playerModel = gi.G2API_InitGhoul2Model( ent->ghoul2, "models/map_objects/nar_shaddar/jabacam/jabacam.glm", ent->s.modelindex, NULL_HANDLE, NULL_HANDLE, 0, 0 );
2684 ent->s.radius = 150.0f; //......
2685 VectorSet( ent->s.modelScale, 1.0f, 1.0f, 1.0f );
2686
2687 ent->rootBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "model_root", qtrue );
2688
2689 ent->e_UseFunc = useF_jabba_cam_use;
2690
2691 ent->takedamage = qfalse;
2692
2693 // start extended..
2694 if ( ent->spawnflags & 1 )
2695 {
2696 gi.G2API_SetBoneAnimIndex( &ent->ghoul2[ent->playerModel], ent->rootBone, 0, 15, BONE_ANIM_OVERRIDE_FREEZE, 0.6f, cg.time, -1, -1 );
2697 }
2698
2699 gi.linkentity( ent );
2700 }
2701
2702 //------------------------------------------------------------------------
misc_use(gentity_t * self,gentity_t * other,gentity_t * activator)2703 void misc_use( gentity_t *self, gentity_t *other, gentity_t *activator )
2704 {
2705 misc_model_breakable_die( self, other, activator, 100, MOD_UNKNOWN );
2706 }
2707
2708 /*QUAKED misc_exploding_crate (1 0 0.25) (-24 -24 0) (24 24 64)
2709 model="models/map_objects/nar_shaddar/crate_xplode.md3"
2710 Basic exploding crate
2711
2712 "health" - how much health the model has - default 40 (zero makes non-breakable)
2713
2714 "splashRadius" - radius to do damage in - default 128
2715 "splashDamage" - amount of damage to do when it explodes - default 50
2716
2717 "targetname" - auto-explodes
2718 "target" - what to use when it dies
2719
2720 */
2721 //------------------------------------------------------------
SP_misc_exploding_crate(gentity_t * ent)2722 void SP_misc_exploding_crate( gentity_t *ent )
2723 {
2724 G_SpawnInt( "health", "40", &ent->health );
2725 G_SpawnInt( "splashRadius", "128", &ent->splashRadius );
2726 G_SpawnInt( "splashDamage", "50", &ent->splashDamage );
2727
2728 ent->s.modelindex = G_ModelIndex( "models/map_objects/nar_shaddar/crate_xplode.md3" );
2729 G_SoundIndex("sound/weapons/explosions/cargoexplode.wav");
2730 G_EffectIndex( "chunks/metalexplode" );
2731
2732 VectorSet( ent->mins, -24, -24, 0 );
2733 VectorSet( ent->maxs, 24, 24, 64 );
2734
2735 ent->contents = CONTENTS_SOLID|CONTENTS_OPAQUE|CONTENTS_BODY|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP;//CONTENTS_SOLID;
2736 ent->takedamage = qtrue;
2737
2738 G_SetOrigin( ent, ent->s.origin );
2739 VectorCopy( ent->s.angles, ent->s.apos.trBase );
2740 gi.linkentity (ent);
2741
2742 if ( ent->targetname )
2743 {
2744 ent->e_UseFunc = useF_misc_use;
2745 }
2746
2747 ent->material = MAT_CRATE1;
2748 ent->e_DieFunc = dieF_misc_model_breakable_die;//ExplodeDeath;
2749 }
2750
2751 /*QUAKED misc_gas_tank (1 0 0.25) (-4 -4 0) (4 4 40)
2752 model="models/map_objects/imp_mine/tank.md3"
2753 Basic exploding oxygen tank
2754
2755 "health" - how much health the model has - default 20 (zero makes non-breakable)
2756
2757 "splashRadius" - radius to do damage in - default 48
2758 "splashDamage" - amount of damage to do when it explodes - default 32
2759
2760 "targetname" - auto-explodes
2761 "target" - what to use when it dies
2762
2763 */
2764
gas_random_jet(gentity_t * self)2765 void gas_random_jet( gentity_t *self )
2766 {
2767 vec3_t pt;
2768
2769 VectorCopy( self->currentOrigin, pt );
2770 pt[2] += 50;
2771
2772 G_PlayEffect( "env/mini_gasjet", pt );
2773
2774 self->nextthink = level.time + Q_flrand(0.0f, 1.0f) * 16000 + 12000; // do this rarely
2775 }
2776
2777 //------------------------------------------------------------
GasBurst(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,const vec3_t point,int damage,int mod,int hitLoc)2778 void GasBurst( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod, int hitLoc )
2779 {
2780 vec3_t pt;
2781
2782 VectorCopy( self->currentOrigin, pt );
2783 pt[2] += 46;
2784
2785 G_PlayEffect( "env/mini_flamejet", pt );
2786
2787 // do some damage to anything that may be standing on top of it when it bursts into flame
2788 pt[2] += 32;
2789 G_RadiusDamage( pt, self, 32, 32, self, MOD_UNKNOWN );
2790
2791 // only get one burst
2792 self->e_PainFunc = painF_NULL;
2793 }
2794
2795 //------------------------------------------------------------
SP_misc_gas_tank(gentity_t * ent)2796 void SP_misc_gas_tank( gentity_t *ent )
2797 {
2798 G_SpawnInt( "health", "20", &ent->health );
2799 G_SpawnInt( "splashRadius", "48", &ent->splashRadius );
2800 G_SpawnInt( "splashDamage", "32", &ent->splashDamage );
2801
2802 ent->s.modelindex = G_ModelIndex( "models/map_objects/imp_mine/tank.md3" );
2803 G_SoundIndex("sound/weapons/explosions/cargoexplode.wav");
2804 G_EffectIndex( "chunks/metalexplode" );
2805 G_EffectIndex( "env/mini_flamejet" );
2806 G_EffectIndex( "env/mini_gasjet" );
2807
2808 VectorSet( ent->mins, -4, -4, 0 );
2809 VectorSet( ent->maxs, 4, 4, 40 );
2810
2811 ent->contents = CONTENTS_SOLID;
2812 ent->takedamage = qtrue;
2813
2814 G_SetOrigin( ent, ent->s.origin );
2815 VectorCopy( ent->s.angles, ent->s.apos.trBase );
2816 gi.linkentity (ent);
2817
2818 ent->e_PainFunc = painF_GasBurst;
2819
2820 if ( ent->targetname )
2821 {
2822 ent->e_UseFunc = useF_misc_use;
2823 }
2824
2825 ent->material = MAT_METAL3;
2826
2827 ent->e_DieFunc = dieF_misc_model_breakable_die;
2828
2829 ent->e_ThinkFunc = thinkF_gas_random_jet;
2830 ent->nextthink = level.time + Q_flrand(0.0f, 1.0f) * 12000 + 6000; // do this rarely
2831 }
2832
2833 /*QUAKED misc_crystal_crate (1 0 0.25) (-34 -34 0) (34 34 44) NON_SOLID
2834 model="models/map_objects/imp_mine/crate_open.md3"
2835 Open crate of crystals that explode when shot
2836
2837 NON_SOLID - can only be shot
2838
2839 "health" - how much health the crate has, default 80
2840
2841 "splashRadius" - radius to do damage in, default 80
2842 "splashDamage" - amount of damage to do when it explodes, default 40
2843
2844 "targetname" - auto-explodes
2845 "target" - what to use when it dies
2846
2847 */
2848
2849 //------------------------------------------------------------
CrystalCratePain(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,const vec3_t point,int damage,int mod,int hitLoc)2850 void CrystalCratePain( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod, int hitLoc )
2851 {
2852 vec3_t pt;
2853
2854 VectorCopy( self->currentOrigin, pt );
2855 pt[2] += 36;
2856
2857 G_PlayEffect( "env/crystal_crate", pt );
2858
2859 // do some damage, heh
2860 pt[2] += 32;
2861 G_RadiusDamage( pt, self, 16, 32, self, MOD_UNKNOWN );
2862
2863 }
2864
2865 //------------------------------------------------------------
SP_misc_crystal_crate(gentity_t * ent)2866 void SP_misc_crystal_crate( gentity_t *ent )
2867 {
2868 G_SpawnInt( "health", "80", &ent->health );
2869 G_SpawnInt( "splashRadius", "80", &ent->splashRadius );
2870 G_SpawnInt( "splashDamage", "40", &ent->splashDamage );
2871
2872 ent->s.modelindex = G_ModelIndex( "models/map_objects/imp_mine/crate_open.md3" );
2873 ent->fxID = G_EffectIndex( "thermal/explosion" ); // FIXME: temp
2874 G_EffectIndex( "env/crystal_crate" );
2875 G_SoundIndex("sound/weapons/explosions/cargoexplode.wav");
2876
2877 VectorSet( ent->mins, -34, -34, 0 );
2878 VectorSet( ent->maxs, 34, 34, 44 );
2879
2880 //Blocks movement
2881 ent->contents = CONTENTS_SOLID|CONTENTS_OPAQUE|CONTENTS_BODY|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP;//Was CONTENTS_SOLID, but only architecture should be this
2882
2883 if ( ent->spawnflags & 1 ) // non-solid
2884 { // Override earlier contents flag...
2885 //Can only be shot
2886 ent->contents = CONTENTS_SHOTCLIP;
2887 }
2888
2889
2890 ent->takedamage = qtrue;
2891
2892 G_SetOrigin( ent, ent->s.origin );
2893 VectorCopy( ent->s.angles, ent->s.apos.trBase );
2894 gi.linkentity (ent);
2895
2896 ent->e_PainFunc = painF_CrystalCratePain;
2897
2898 if ( ent->targetname )
2899 {
2900 ent->e_UseFunc = useF_misc_use;
2901 }
2902
2903 ent->material = MAT_CRATE2;
2904
2905 ent->e_DieFunc = dieF_misc_model_breakable_die;
2906 }
2907
2908 /*QUAKED misc_atst_drivable (1 0 0.25) (-40 -40 -24) (40 40 248)
2909 model="models/players/atst/model.glm"
2910
2911 Drivable ATST, when used by player, they become the ATST. When the player hits use again, they get out.
2912
2913 "health" - how much health the atst has - default 800
2914
2915 "target" - what to use when it dies
2916 */
misc_atst_setanim(gentity_t * self,int bone,int anim)2917 void misc_atst_setanim( gentity_t *self, int bone, int anim )
2918 {
2919 if ( bone < 0 || anim < 0 )
2920 {
2921 return;
2922 }
2923 int firstFrame = -1;
2924 int lastFrame = -1;
2925 float animSpeed = 0;
2926 //try to get anim ranges from the animation.cfg for an AT-ST
2927 for ( int i = 0; i < level.numKnownAnimFileSets; i++ )
2928 {
2929 if ( !Q_stricmp( "atst", level.knownAnimFileSets[i].filename ) )
2930 {
2931 firstFrame = level.knownAnimFileSets[i].animations[anim].firstFrame;
2932 lastFrame = firstFrame+level.knownAnimFileSets[i].animations[anim].numFrames;
2933 animSpeed = 50.0f / level.knownAnimFileSets[i].animations[anim].frameLerp;
2934 break;
2935 }
2936 }
2937 if ( firstFrame != -1 && lastFrame != -1 && animSpeed != 0 )
2938 {
2939 if (!gi.G2API_SetBoneAnimIndex( &self->ghoul2[self->playerModel], bone, firstFrame,
2940 lastFrame, BONE_ANIM_OVERRIDE_FREEZE|BONE_ANIM_BLEND, animSpeed,
2941 (cg.time?cg.time:level.time), -1, 150 ))
2942 {
2943 gi.G2API_SetBoneAnimIndex( &self->ghoul2[self->playerModel], bone, firstFrame,
2944 lastFrame, BONE_ANIM_OVERRIDE_FREEZE, animSpeed,
2945 (cg.time?cg.time:level.time), -1, 150 );
2946 }
2947 }
2948 }
misc_atst_die(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int mod,int dFlags,int hitLoc)2949 void misc_atst_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags,int hitLoc )
2950 {//ATST was destroyed while you weren't in it
2951 //can't be used anymore
2952 self->e_UseFunc = useF_NULL;
2953 //sigh... remove contents so we don't block the player's path...
2954 self->contents = CONTENTS_CORPSE;
2955 self->takedamage = qfalse;
2956 self->maxs[2] = 48;
2957
2958 //FIXME: match to slope
2959 vec3_t effectPos;
2960 VectorCopy( self->currentOrigin, effectPos );
2961 effectPos[2] -= 15;
2962 G_PlayEffect( "explosions/droidexplosion1", effectPos );
2963 // G_PlayEffect( "small_chunks", effectPos );
2964 //set these to defaults that work in a worst-case scenario (according to current animation.cfg)
2965 gi.G2API_StopBoneAnimIndex( &self->ghoul2[self->playerModel], self->craniumBone );
2966 misc_atst_setanim( self, self->rootBone, BOTH_DEATH1 );
2967 }
2968
2969 extern void G_DriveATST( gentity_t *ent, gentity_t *atst );
2970 extern void SetClientViewAngle( gentity_t *ent, vec3_t angle );
2971 extern qboolean PM_InSlopeAnim( int anim );
misc_atst_use(gentity_t * self,gentity_t * other,gentity_t * activator)2972 void misc_atst_use( gentity_t *self, gentity_t *other, gentity_t *activator )
2973 {
2974 if ( !activator || activator->s.number )
2975 {//only player can do this
2976 return;
2977 }
2978
2979 int tempLocDmg[HL_MAX];
2980 int hl, tempHealth;
2981
2982 if ( activator->client->NPC_class != CLASS_ATST )
2983 {//get in the ATST
2984 if ( activator->client->ps.groundEntityNum != self->s.number )
2985 {//can only get in if on top of me...
2986 //we *could* even check for the hatch surf...?
2987 return;
2988 }
2989 //copy origin
2990 G_SetOrigin( activator, self->currentOrigin );
2991
2992 //copy angles
2993 VectorCopy( self->s.angles2, self->currentAngles );
2994 G_SetAngles( activator, self->currentAngles );
2995 SetClientViewAngle( activator, self->currentAngles );
2996
2997 //set player to my g2 instance
2998 gi.G2API_StopBoneAnimIndex( &self->ghoul2[self->playerModel], self->craniumBone );
2999 G_DriveATST( activator, self );
3000 activator->activator = self;
3001 self->s.eFlags |= EF_NODRAW;
3002 self->svFlags |= SVF_NOCLIENT;
3003 self->contents = 0;
3004 self->takedamage = qfalse;
3005 //transfer armor
3006 tempHealth = self->health;
3007 self->health = activator->client->ps.stats[STAT_ARMOR];
3008 activator->client->ps.stats[STAT_ARMOR] = tempHealth;
3009 //transfer locationDamage
3010 for ( hl = HL_NONE; hl < HL_MAX; hl++ )
3011 {
3012 tempLocDmg[hl] = activator->locationDamage[hl];
3013 activator->locationDamage[hl] = self->locationDamage[hl];
3014 self->locationDamage[hl] = tempLocDmg[hl];
3015 }
3016 if ( !self->s.number )
3017 {
3018 CG_CenterPrint( "@SP_INGAME_EXIT_VIEW", SCREEN_HEIGHT * 0.95 );
3019 }
3020 }
3021 else
3022 {//get out of ATST
3023 int legsAnim = activator->client->ps.legsAnim;
3024 if ( legsAnim != BOTH_STAND1
3025 && !PM_InSlopeAnim( legsAnim )
3026 && legsAnim != BOTH_TURN_RIGHT1 && legsAnim != BOTH_TURN_LEFT1 )
3027 {//can't get out of it while it's still moving
3028 return;
3029 }
3030 //FIXME: after a load/save, this crashes, BAD... somewhere in G2
3031 G_SetOrigin( self, activator->currentOrigin );
3032 VectorSet( self->currentAngles, 0, activator->client->ps.legsYaw, 0 );
3033 //self->currentAngles[PITCH] = activator->currentAngles[ROLL] = 0;
3034 G_SetAngles( self, self->currentAngles );
3035 VectorCopy( activator->currentAngles, self->s.angles2 );
3036 //remove my G2
3037 if ( self->playerModel >= 0 )
3038 {
3039 gi.G2API_RemoveGhoul2Model( self->ghoul2, self->playerModel );
3040 self->playerModel = -1;
3041 }
3042 //copy player's
3043 gi.G2API_CopyGhoul2Instance( activator->ghoul2, self->ghoul2, -1 );
3044 self->playerModel = 0;//assumption
3045 //reset player to kyle
3046 G_DriveATST( activator, NULL );
3047 activator->activator = NULL;
3048 self->s.eFlags &= ~EF_NODRAW;
3049 self->svFlags &= ~SVF_NOCLIENT;
3050 self->contents = CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP;
3051 self->takedamage = qtrue;
3052 //transfer armor
3053 tempHealth = self->health;
3054 self->health = activator->client->ps.stats[STAT_ARMOR];
3055 activator->client->ps.stats[STAT_ARMOR] = tempHealth;
3056 //transfer locationDamage
3057 for ( hl = HL_NONE; hl < HL_MAX; hl++ )
3058 {
3059 tempLocDmg[hl] = self->locationDamage[hl];
3060 self->locationDamage[hl] = activator->locationDamage[hl];
3061 activator->locationDamage[hl] = tempLocDmg[hl];
3062 }
3063 //link me back in
3064 gi.linkentity ( self );
3065 //put activator on top of me?
3066 vec3_t newOrg = {activator->currentOrigin[0], activator->currentOrigin[1], activator->currentOrigin[2] + (self->maxs[2]-self->mins[2]) + 1 };
3067 G_SetOrigin( activator, newOrg );
3068 //open the hatch
3069 misc_atst_setanim( self, self->craniumBone, BOTH_STAND2 );
3070 gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], "head_hatchcover", 0 );
3071 G_Sound( self, G_SoundIndex( "sound/chars/atst/atst_hatch_open" ));
3072 }
3073 }
3074
SP_misc_atst_drivable(gentity_t * ent)3075 void SP_misc_atst_drivable( gentity_t *ent )
3076 {
3077 extern void NPC_ATST_Precache(void);
3078 extern void NPC_PrecacheAnimationCFG( const char *NPC_type );
3079
3080 ent->s.modelindex = G_ModelIndex( "models/players/atst/model.glm" );
3081 ent->playerModel = gi.G2API_InitGhoul2Model( ent->ghoul2, "models/players/atst/model.glm", ent->s.modelindex, NULL_HANDLE, NULL_HANDLE, 0, 0 );
3082 ent->rootBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "model_root", qtrue );
3083 ent->craniumBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "cranium", qtrue ); //FIXME: need to somehow set the anim/frame to the equivalent of BOTH_STAND1... use to be that BOTH_STAND1 was the first frame of the glm, but not anymore
3084 ent->s.radius = 320;
3085 VectorSet( ent->s.modelScale, 1.0f, 1.0f, 1.0f );
3086
3087 //register my weapons, sounds and model
3088 RegisterItem( FindItemForWeapon( WP_ATST_MAIN )); //precache the weapon
3089 RegisterItem( FindItemForWeapon( WP_ATST_SIDE )); //precache the weapon
3090 //HACKHACKHACKTEMP - until ATST gets real weapons of it's own?
3091 RegisterItem( FindItemForWeapon( WP_EMPLACED_GUN )); //precache the weapon
3092 // RegisterItem( FindItemForWeapon( WP_ROCKET_LAUNCHER )); //precache the weapon
3093 // RegisterItem( FindItemForWeapon( WP_BOWCASTER )); //precache the weapon
3094 //HACKHACKHACKTEMP - until ATST gets real weapons of it's own?
3095
3096 G_SoundIndex( "sound/chars/atst/atst_hatch_open" );
3097 G_SoundIndex( "sound/chars/atst/atst_hatch_close" );
3098
3099 NPC_ATST_Precache();
3100 ent->NPC_type = (char *)"atst";
3101 NPC_PrecacheAnimationCFG( ent->NPC_type );
3102 //open the hatch
3103 misc_atst_setanim( ent, ent->rootBone, BOTH_STAND2 );
3104 gi.G2API_SetSurfaceOnOff( &ent->ghoul2[ent->playerModel], "head_hatchcover", 0 );
3105
3106 VectorSet( ent->mins, ATST_MINS0, ATST_MINS1, ATST_MINS2 );
3107 VectorSet( ent->maxs, ATST_MAXS0, ATST_MAXS1, ATST_MAXS2 );
3108
3109 ent->contents = CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP;
3110 ent->flags |= FL_SHIELDED;
3111 ent->takedamage = qtrue;
3112 if ( !ent->health )
3113 {
3114 ent->health = 800;
3115 }
3116 ent->s.radius = 320;
3117
3118 ent->max_health = ent->health; // cg_draw needs this
3119
3120 G_SetOrigin( ent, ent->s.origin );
3121 G_SetAngles( ent, ent->s.angles );
3122 VectorCopy( ent->currentAngles, ent->s.angles2 );
3123
3124 gi.linkentity ( ent );
3125
3126 //FIXME: test the origin to make sure I'm clear?
3127
3128 ent->e_UseFunc = useF_misc_atst_use;
3129 ent->svFlags |= SVF_PLAYER_USABLE;
3130
3131 //make it able to take damage and die when you're not in it...
3132 //do an explosion and play the death anim, remove use func.
3133 ent->e_DieFunc = dieF_misc_atst_die;
3134 }
3135
3136 extern int G_FindConfigstringIndex( const char *name, int start, int max, qboolean create );
3137
3138 /*QUAKED misc_weather_zone (0 .5 .8) ?
3139 Determines a region to check for weather contents - (optional, used to reduce load times)
3140 Place surrounding your inside/outside brushes. It will not check for weather contents outside of these zones.
3141 */
SP_misc_weather_zone(gentity_t * ent)3142 void SP_misc_weather_zone( gentity_t *ent )
3143 {
3144 gi.SetBrushModel(ent, ent->model);
3145
3146 char temp[256];
3147 sprintf( temp, "zone ( %f %f %f ) ( %f %f %f )",
3148 ent->mins[0], ent->mins[1], ent->mins[2],
3149 ent->maxs[0], ent->maxs[1], ent->maxs[2] );
3150
3151 G_FindConfigstringIndex(temp, CS_WORLD_FX, MAX_WORLD_FX, qtrue);
3152
3153 // gi.WE_AddWeatherZone(ent->mins, ent->maxs);
3154 G_FreeEntity(ent);
3155 }
3156
SP_misc_cubemap(gentity_t * ent)3157 void SP_misc_cubemap( gentity_t *ent )
3158 {
3159 G_FreeEntity( ent );
3160 }
3161