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 #include "g_local.h"
25 #include "ghoul2/G2.h"
26 #include "qcommon/q_shared.h"
27
28 /*
29
30 Items are any object that a player can touch to gain some effect.
31
32 Pickup will return the number of seconds until they should respawn.
33
34 all items should pop when dropped in lava or slime
35
36 Respawnable items don't actually go away when picked up, they are
37 just made invisible and untouchable. This allows them to ride
38 movers and respawn appropriately.
39 */
40
41
42 #define RESPAWN_ARMOR 20
43 #define RESPAWN_TEAM_WEAPON 30
44 #define RESPAWN_HEALTH 30
45 #define RESPAWN_AMMO 40
46 #define RESPAWN_HOLDABLE 60
47 #define RESPAWN_MEGAHEALTH 120
48 #define RESPAWN_POWERUP 120
49
50 // Item Spawn flags
51 #define ITMSF_SUSPEND 1
52 #define ITMSF_NOPLAYER 2
53 #define ITMSF_ALLOWNPC 4
54 #define ITMSF_NOTSOLID 8
55 #define ITMSF_VERTICAL 16
56 #define ITMSF_INVISIBLE 32
57
58 extern gentity_t *droppedRedFlag;
59 extern gentity_t *droppedBlueFlag;
60
61
62 //======================================================================
63 #define MAX_MEDPACK_HEAL_AMOUNT 25
64 #define MAX_MEDPACK_BIG_HEAL_AMOUNT 50
65 #define MAX_SENTRY_DISTANCE 256
66
67 // For more than four players, adjust the respawn times, up to 1/4.
adjustRespawnTime(float preRespawnTime,int itemType,int itemTag)68 int adjustRespawnTime(float preRespawnTime, int itemType, int itemTag)
69 {
70 float respawnTime = preRespawnTime;
71
72 if (itemType == IT_WEAPON)
73 {
74 if (itemTag == WP_THERMAL ||
75 itemTag == WP_TRIP_MINE ||
76 itemTag == WP_DET_PACK)
77 { //special case for these, use ammo respawn rate
78 respawnTime = RESPAWN_AMMO;
79 }
80 }
81
82 if (!g_adaptRespawn.integer)
83 {
84 return((int)respawnTime);
85 }
86
87 if (level.numPlayingClients > 4)
88 { // Start scaling the respawn times.
89 if (level.numPlayingClients > 32)
90 { // 1/4 time minimum.
91 respawnTime *= 0.25;
92 }
93 else if (level.numPlayingClients > 12)
94 { // From 12-32, scale from 0.5 to 0.25;
95 respawnTime *= 20.0 / (float)(level.numPlayingClients + 8);
96 }
97 else
98 { // From 4-12, scale from 1.0 to 0.5;
99 respawnTime *= 8.0 / (float)(level.numPlayingClients + 4);
100 }
101 }
102
103 if (respawnTime < 1.0)
104 { // No matter what, don't go lower than 1 second, or the pickups become very noisy!
105 respawnTime = 1.0;
106 }
107
108 return ((int)respawnTime);
109 }
110
111
112 #define SHIELD_HEALTH 250
113 #define SHIELD_HEALTH_DEC 10 // 25 seconds
114 #define MAX_SHIELD_HEIGHT 254
115 #define MAX_SHIELD_HALFWIDTH 255
116 #define SHIELD_HALFTHICKNESS 4
117 #define SHIELD_PLACEDIST 64
118
119 #define SHIELD_SIEGE_HEALTH 2000
120 #define SHIELD_SIEGE_HEALTH_DEC (SHIELD_SIEGE_HEALTH/25) // still 25 seconds.
121
122 static qhandle_t shieldLoopSound=0;
123 static qhandle_t shieldAttachSound=0;
124 static qhandle_t shieldActivateSound=0;
125 static qhandle_t shieldDeactivateSound=0;
126 static qhandle_t shieldDamageSound=0;
127
128
ShieldRemove(gentity_t * self)129 void ShieldRemove(gentity_t *self)
130 {
131 self->think = G_FreeEntity;
132 self->nextthink = level.time + 100;
133
134 // Play kill sound...
135 G_AddEvent(self, EV_GENERAL_SOUND, shieldDeactivateSound);
136 self->s.loopSound = 0;
137 self->s.loopIsSoundset = qfalse;
138
139 return;
140 }
141
142
143 // Count down the health of the shield.
ShieldThink(gentity_t * self)144 void ShieldThink(gentity_t *self)
145 {
146 self->s.trickedentindex = 0;
147
148 if ( level.gametype == GT_SIEGE )
149 {
150 self->health -= SHIELD_SIEGE_HEALTH_DEC;
151 }
152 else
153 {
154 self->health -= SHIELD_HEALTH_DEC;
155 }
156 self->nextthink = level.time + 1000;
157 if (self->health <= 0)
158 {
159 ShieldRemove(self);
160 }
161 return;
162 }
163
164
165 // The shield was damaged to below zero health.
ShieldDie(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int mod)166 void ShieldDie(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod)
167 {
168 // Play damaging sound...
169 G_AddEvent(self, EV_GENERAL_SOUND, shieldDamageSound);
170
171 ShieldRemove(self);
172 }
173
174
175 // The shield had damage done to it. Make it flicker.
ShieldPain(gentity_t * self,gentity_t * attacker,int damage)176 void ShieldPain(gentity_t *self, gentity_t *attacker, int damage)
177 {
178 // Set the itemplaceholder flag to indicate the the shield drawing that the shield pain should be drawn.
179 self->think = ShieldThink;
180 self->nextthink = level.time + 400;
181
182 // Play damaging sound...
183 G_AddEvent(self, EV_GENERAL_SOUND, shieldDamageSound);
184
185 self->s.trickedentindex = 1;
186
187 return;
188 }
189
190
191 // Try to turn the shield back on after a delay.
ShieldGoSolid(gentity_t * self)192 void ShieldGoSolid(gentity_t *self)
193 {
194 trace_t tr;
195
196 // see if we're valid
197 self->health--;
198 if (self->health <= 0)
199 {
200 ShieldRemove(self);
201 return;
202 }
203
204 trap->Trace (&tr, self->r.currentOrigin, self->r.mins, self->r.maxs, self->r.currentOrigin, self->s.number, CONTENTS_BODY, qfalse, 0, 0 );
205 if(tr.startsolid)
206 { // gah, we can't activate yet
207 self->nextthink = level.time + 200;
208 self->think = ShieldGoSolid;
209 trap->LinkEntity((sharedEntity_t *)self);
210 }
211 else
212 { // get hard... huh-huh...
213 self->s.eFlags &= ~EF_NODRAW;
214
215 self->r.contents = CONTENTS_SOLID;
216 self->nextthink = level.time + 1000;
217 self->think = ShieldThink;
218 self->takedamage = qtrue;
219 trap->LinkEntity((sharedEntity_t *)self);
220
221 // Play raising sound...
222 G_AddEvent(self, EV_GENERAL_SOUND, shieldActivateSound);
223 self->s.loopSound = shieldLoopSound;
224 self->s.loopIsSoundset = qfalse;
225 }
226
227 return;
228 }
229
230
231 // Turn the shield off to allow a friend to pass through.
ShieldGoNotSolid(gentity_t * self)232 void ShieldGoNotSolid(gentity_t *self)
233 {
234 // make the shield non-solid very briefly
235 self->r.contents = 0;
236 self->s.eFlags |= EF_NODRAW;
237 // nextthink needs to have a large enough interval to avoid excess accumulation of Activate messages
238 self->nextthink = level.time + 200;
239 self->think = ShieldGoSolid;
240 self->takedamage = qfalse;
241 trap->LinkEntity((sharedEntity_t *)self);
242
243 // Play kill sound...
244 G_AddEvent(self, EV_GENERAL_SOUND, shieldDeactivateSound);
245 self->s.loopSound = 0;
246 self->s.loopIsSoundset = qfalse;
247 }
248
249
250 // Somebody (a player) has touched the shield. See if it is a "friend".
ShieldTouch(gentity_t * self,gentity_t * other,trace_t * trace)251 void ShieldTouch(gentity_t *self, gentity_t *other, trace_t *trace)
252 {
253 if (level.gametype >= GT_TEAM)
254 { // let teammates through
255 // compare the parent's team to the "other's" team
256 if (self->parent && ( self->parent->client) && (other->client))
257 {
258 if (OnSameTeam(self->parent, other))
259 {
260 ShieldGoNotSolid(self);
261 }
262 }
263 }
264 else
265 {//let the person who dropped the shield through
266 if (self->parent && self->parent->s.number == other->s.number)
267 {
268 ShieldGoNotSolid(self);
269 }
270 }
271 }
272
273
274 // After a short delay, create the shield by expanding in all directions.
CreateShield(gentity_t * ent)275 void CreateShield(gentity_t *ent)
276 {
277 trace_t tr;
278 vec3_t mins, maxs, end, posTraceEnd, negTraceEnd, start;
279 int height, posWidth, negWidth, halfWidth = 0;
280 qboolean xaxis;
281 int paramData = 0;
282 // static int shieldID;
283
284 // trace upward to find height of shield
285 VectorCopy(ent->r.currentOrigin, end);
286 end[2] += MAX_SHIELD_HEIGHT;
287 trap->Trace (&tr, ent->r.currentOrigin, NULL, NULL, end, ent->s.number, MASK_SHOT, qfalse, 0, 0 );
288 height = (int)(MAX_SHIELD_HEIGHT * tr.fraction);
289
290 // use angles to find the proper axis along which to align the shield
291 VectorSet(mins, -SHIELD_HALFTHICKNESS, -SHIELD_HALFTHICKNESS, 0);
292 VectorSet(maxs, SHIELD_HALFTHICKNESS, SHIELD_HALFTHICKNESS, height);
293 VectorCopy(ent->r.currentOrigin, posTraceEnd);
294 VectorCopy(ent->r.currentOrigin, negTraceEnd);
295
296 if ((int)(ent->s.angles[YAW]) == 0) // shield runs along y-axis
297 {
298 posTraceEnd[1]+=MAX_SHIELD_HALFWIDTH;
299 negTraceEnd[1]-=MAX_SHIELD_HALFWIDTH;
300 xaxis = qfalse;
301 }
302 else // shield runs along x-axis
303 {
304 posTraceEnd[0]+=MAX_SHIELD_HALFWIDTH;
305 negTraceEnd[0]-=MAX_SHIELD_HALFWIDTH;
306 xaxis = qtrue;
307 }
308
309 // trace horizontally to find extend of shield
310 // positive trace
311 VectorCopy(ent->r.currentOrigin, start);
312 start[2] += (height>>1);
313 trap->Trace (&tr, start, 0, 0, posTraceEnd, ent->s.number, MASK_SHOT, qfalse, 0, 0 );
314 posWidth = MAX_SHIELD_HALFWIDTH * tr.fraction;
315 // negative trace
316 trap->Trace (&tr, start, 0, 0, negTraceEnd, ent->s.number, MASK_SHOT, qfalse, 0, 0 );
317 negWidth = MAX_SHIELD_HALFWIDTH * tr.fraction;
318
319 // kef -- monkey with dimensions and place origin in center
320 halfWidth = (posWidth + negWidth)>>1;
321 if (xaxis)
322 {
323 ent->r.currentOrigin[0] = ent->r.currentOrigin[0] - negWidth + halfWidth;
324 }
325 else
326 {
327 ent->r.currentOrigin[1] = ent->r.currentOrigin[1] - negWidth + halfWidth;
328 }
329 ent->r.currentOrigin[2] += (height>>1);
330
331 // set entity's mins and maxs to new values, make it solid, and link it
332 if (xaxis)
333 {
334 VectorSet(ent->r.mins, -halfWidth, -SHIELD_HALFTHICKNESS, -(height>>1));
335 VectorSet(ent->r.maxs, halfWidth, SHIELD_HALFTHICKNESS, height>>1);
336 }
337 else
338 {
339 VectorSet(ent->r.mins, -SHIELD_HALFTHICKNESS, -halfWidth, -(height>>1));
340 VectorSet(ent->r.maxs, SHIELD_HALFTHICKNESS, halfWidth, height);
341 }
342 ent->clipmask = MASK_SHOT;
343
344 // Information for shield rendering.
345
346 // xaxis - 1 bit
347 // height - 0-254 8 bits
348 // posWidth - 0-255 8 bits
349 // negWidth - 0 - 255 8 bits
350
351 paramData = (xaxis << 24) | (height << 16) | (posWidth << 8) | (negWidth);
352 ent->s.time2 = paramData;
353
354 if ( level.gametype == GT_SIEGE )
355 {
356 ent->health = ceil((float)(SHIELD_SIEGE_HEALTH*1));
357 }
358 else
359 {
360 ent->health = ceil((float)(SHIELD_HEALTH*1));
361 }
362
363 ent->s.time = ent->health;//???
364 ent->pain = ShieldPain;
365 ent->die = ShieldDie;
366 ent->touch = ShieldTouch;
367
368 // see if we're valid
369 trap->Trace (&tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, ent->r.currentOrigin, ent->s.number, CONTENTS_BODY, qfalse, 0, 0 );
370
371 if (tr.startsolid)
372 { // Something in the way!
373 // make the shield non-solid very briefly
374 ent->r.contents = 0;
375 ent->s.eFlags |= EF_NODRAW;
376 // nextthink needs to have a large enough interval to avoid excess accumulation of Activate messages
377 ent->nextthink = level.time + 200;
378 ent->think = ShieldGoSolid;
379 ent->takedamage = qfalse;
380 trap->LinkEntity((sharedEntity_t *)ent);
381 }
382 else
383 { // Get solid.
384 ent->r.contents = CONTENTS_PLAYERCLIP|CONTENTS_SHOTCLIP;//CONTENTS_SOLID;
385
386 ent->nextthink = level.time;
387 ent->think = ShieldThink;
388
389 ent->takedamage = qtrue;
390 trap->LinkEntity((sharedEntity_t *)ent);
391
392 // Play raising sound...
393 G_AddEvent(ent, EV_GENERAL_SOUND, shieldActivateSound);
394 ent->s.loopSound = shieldLoopSound;
395 ent->s.loopIsSoundset = qfalse;
396 }
397
398 ShieldGoSolid(ent);
399
400 return;
401 }
402
PlaceShield(gentity_t * playerent)403 qboolean PlaceShield(gentity_t *playerent)
404 {
405 static const gitem_t *shieldItem = NULL;
406 gentity_t *shield = NULL;
407 trace_t tr;
408 vec3_t fwd, pos, dest, mins = {-4,-4, 0}, maxs = {4,4,4};
409 static qboolean registered = qfalse;
410
411 if ( !registered )
412 {
413 shieldLoopSound = G_SoundIndex("sound/movers/doors/forcefield_lp.wav");
414 shieldAttachSound = G_SoundIndex("sound/weapons/detpack/stick.wav");
415 shieldActivateSound = G_SoundIndex("sound/movers/doors/forcefield_on.wav");
416 shieldDeactivateSound = G_SoundIndex("sound/movers/doors/forcefield_off.wav");
417 shieldDamageSound = G_SoundIndex("sound/effects/bumpfield.wav");
418 shieldItem = BG_FindItemForHoldable(HI_SHIELD);
419 registered = qtrue;
420 }
421
422 // can we place this in front of us?
423 AngleVectors (playerent->client->ps.viewangles, fwd, NULL, NULL);
424 fwd[2] = 0;
425 VectorMA(playerent->client->ps.origin, SHIELD_PLACEDIST, fwd, dest);
426 trap->Trace (&tr, playerent->client->ps.origin, mins, maxs, dest, playerent->s.number, MASK_SHOT, qfalse, 0, 0 );
427 if (tr.fraction > 0.9)
428 {//room in front
429 VectorCopy(tr.endpos, pos);
430 // drop to floor
431 VectorSet( dest, pos[0], pos[1], pos[2] - 4096 );
432 trap->Trace( &tr, pos, mins, maxs, dest, playerent->s.number, MASK_SOLID, qfalse, 0, 0 );
433 if ( !tr.startsolid && !tr.allsolid )
434 {
435 // got enough room so place the portable shield
436 shield = G_Spawn();
437
438 // Figure out what direction the shield is facing.
439 if (fabs(fwd[0]) > fabs(fwd[1]))
440 { // shield is north/south, facing east.
441 shield->s.angles[YAW] = 0;
442 }
443 else
444 { // shield is along the east/west axis, facing north
445 shield->s.angles[YAW] = 90;
446 }
447 shield->think = CreateShield;
448 shield->nextthink = level.time + 500; // power up after .5 seconds
449 shield->parent = playerent;
450
451 // Set team number.
452 shield->s.otherEntityNum2 = playerent->client->sess.sessionTeam;
453
454 shield->s.eType = ET_SPECIAL;
455 shield->s.modelindex = HI_SHIELD; // this'll be used in CG_Useable() for rendering.
456 shield->classname = shieldItem->classname;
457
458 shield->r.contents = CONTENTS_TRIGGER;
459
460 shield->touch = 0;
461 // using an item causes it to respawn
462 shield->use = 0; //Use_Item;
463
464 // allow to ride movers
465 shield->s.groundEntityNum = tr.entityNum;
466
467 G_SetOrigin( shield, tr.endpos );
468
469 shield->s.eFlags &= ~EF_NODRAW;
470 shield->r.svFlags &= ~SVF_NOCLIENT;
471
472 trap->LinkEntity ((sharedEntity_t *)shield);
473
474 shield->s.owner = playerent->s.number;
475 shield->s.shouldtarget = qtrue;
476 if (level.gametype >= GT_TEAM)
477 {
478 shield->s.teamowner = playerent->client->sess.sessionTeam;
479 }
480 else
481 {
482 shield->s.teamowner = 16;
483 }
484
485 // Play placing sound...
486 G_AddEvent(shield, EV_GENERAL_SOUND, shieldAttachSound);
487
488 return qtrue;
489 }
490 }
491 // no room
492 return qfalse;
493 }
494
ItemUse_Binoculars(gentity_t * ent)495 void ItemUse_Binoculars(gentity_t *ent)
496 {
497 if (!ent || !ent->client)
498 {
499 return;
500 }
501
502 if (ent->client->ps.weaponstate != WEAPON_READY)
503 { //So we can't fool it and reactivate while switching to the saber or something.
504 return;
505 }
506
507 /*
508 if (ent->client->ps.weapon == WP_SABER)
509 { //No.
510 return;
511 }
512 */
513
514 if (ent->client->ps.zoomMode == 0) // not zoomed or currently zoomed with the disruptor
515 {
516 ent->client->ps.zoomMode = 2;
517 ent->client->ps.zoomLocked = qfalse;
518 ent->client->ps.zoomFov = 40.0f;
519 }
520 else if (ent->client->ps.zoomMode == 2)
521 {
522 ent->client->ps.zoomMode = 0;
523 ent->client->ps.zoomTime = level.time;
524 }
525 }
526
ItemUse_Shield(gentity_t * ent)527 void ItemUse_Shield(gentity_t *ent)
528 {
529 PlaceShield(ent);
530 }
531
532 //--------------------------
533 // PERSONAL ASSAULT SENTRY
534 //--------------------------
535
536 #define PAS_DAMAGE 2
537
SentryTouch(gentity_t * ent,gentity_t * other,trace_t * trace)538 void SentryTouch(gentity_t *ent, gentity_t *other, trace_t *trace)
539 {
540 return;
541 }
542
543 //----------------------------------------------------------------
pas_fire(gentity_t * ent)544 void pas_fire( gentity_t *ent )
545 //----------------------------------------------------------------
546 {
547 vec3_t fwd, myOrg, enOrg;
548
549 VectorCopy(ent->r.currentOrigin, myOrg);
550 myOrg[2] += 24;
551
552 VectorCopy(ent->enemy->client->ps.origin, enOrg);
553 enOrg[2] += 24;
554
555 VectorSubtract(enOrg, myOrg, fwd);
556 VectorNormalize(fwd);
557
558 myOrg[0] += fwd[0]*16;
559 myOrg[1] += fwd[1]*16;
560 myOrg[2] += fwd[2]*16;
561
562 WP_FireTurretMissile(&g_entities[ent->genericValue3], myOrg, fwd, qfalse, 10, 2300, MOD_SENTRY, ent );
563
564 G_RunObject(ent);
565 }
566
567 #define TURRET_RADIUS 800
568
569 //-----------------------------------------------------
pas_find_enemies(gentity_t * self)570 static qboolean pas_find_enemies( gentity_t *self )
571 //-----------------------------------------------------
572 {
573 qboolean found = qfalse;
574 int count, i;
575 float bestDist = TURRET_RADIUS*TURRET_RADIUS;
576 float enemyDist;
577 vec3_t enemyDir, org, org2;
578 gentity_t *entity_list[MAX_GENTITIES], *target;
579 trace_t tr;
580
581 if ( self->aimDebounceTime > level.time ) // time since we've been shut off
582 {
583 // We were active and alert, i.e. had an enemy in the last 3 secs
584 if ( self->painDebounceTime < level.time )
585 {
586 G_Sound(self, CHAN_BODY, G_SoundIndex( "sound/chars/turret/ping.wav" ));
587 self->painDebounceTime = level.time + 1000;
588 }
589 }
590
591 VectorCopy(self->s.pos.trBase, org2);
592
593 count = G_RadiusList( org2, TURRET_RADIUS, self, qtrue, entity_list );
594
595 for ( i = 0; i < count; i++ )
596 {
597 target = entity_list[i];
598
599 if ( !target->client )
600 {
601 continue;
602 }
603 if ( target == self || !target->takedamage || target->health <= 0 || ( target->flags & FL_NOTARGET ))
604 {
605 continue;
606 }
607 if ( self->alliedTeam && target->client->sess.sessionTeam == self->alliedTeam )
608 {
609 continue;
610 }
611 if (self->genericValue3 == target->s.number)
612 {
613 continue;
614 }
615 if ( !trap->InPVS( org2, target->r.currentOrigin ))
616 {
617 continue;
618 }
619
620 if (target->s.eType == ET_NPC &&
621 target->s.NPC_class == CLASS_VEHICLE)
622 { //don't get mad at vehicles, silly.
623 continue;
624 }
625
626 if ( target->client )
627 {
628 VectorCopy( target->client->ps.origin, org );
629 }
630 else
631 {
632 VectorCopy( target->r.currentOrigin, org );
633 }
634
635 trap->Trace( &tr, org2, NULL, NULL, org, self->s.number, MASK_SHOT, qfalse, 0, 0 );
636
637 if ( !tr.allsolid && !tr.startsolid && ( tr.fraction == 1.0 || tr.entityNum == target->s.number ))
638 {
639 // Only acquire if have a clear shot, Is it in range and closer than our best?
640 VectorSubtract( target->r.currentOrigin, self->r.currentOrigin, enemyDir );
641 enemyDist = VectorLengthSquared( enemyDir );
642
643 if ( enemyDist < bestDist )// all things equal, keep current
644 {
645 if ( self->attackDebounceTime + 100 < level.time )
646 {
647 // We haven't fired or acquired an enemy in the last 2 seconds-start-up sound
648 G_Sound( self, CHAN_BODY, G_SoundIndex( "sound/chars/turret/startup.wav" ));
649
650 // Wind up turrets for a bit
651 self->attackDebounceTime = level.time + 900 + Q_flrand(0.0f, 1.0f) * 200;
652 }
653
654 G_SetEnemy( self, target );
655 bestDist = enemyDist;
656 found = qtrue;
657 }
658 }
659 }
660
661 return found;
662 }
663
664 //---------------------------------
pas_adjust_enemy(gentity_t * ent)665 void pas_adjust_enemy( gentity_t *ent )
666 //---------------------------------
667 {
668 trace_t tr;
669 qboolean keep = qtrue;
670
671 if ( ent->enemy->health <= 0 )
672 {
673 keep = qfalse;
674 }
675 else
676 {
677 vec3_t org, org2;
678
679 VectorCopy(ent->s.pos.trBase, org2);
680
681 if ( ent->enemy->client )
682 {
683 VectorCopy( ent->enemy->client->ps.origin, org );
684 org[2] -= 15;
685 }
686 else
687 {
688 VectorCopy( ent->enemy->r.currentOrigin, org );
689 }
690
691 trap->Trace( &tr, org2, NULL, NULL, org, ent->s.number, MASK_SHOT, qfalse, 0, 0 );
692
693 if ( tr.allsolid || tr.startsolid || tr.fraction < 0.9f || tr.entityNum == ent->s.number )
694 {
695 if (tr.entityNum != ent->enemy->s.number)
696 {
697 // trace failed
698 keep = qfalse;
699 }
700 }
701 }
702
703 if ( keep )
704 {
705 //ent->bounceCount = level.time + 500 + Q_flrand(0.0f, 1.0f) * 150;
706 }
707 else if ( ent->bounceCount < level.time && ent->enemy ) // don't ping pong on and off
708 {
709 ent->enemy = NULL;
710 // shut-down sound
711 G_Sound( ent, CHAN_BODY, G_SoundIndex( "sound/chars/turret/shutdown.wav" ));
712
713 ent->bounceCount = level.time + 500 + Q_flrand(0.0f, 1.0f) * 150;
714
715 // make turret play ping sound for 5 seconds
716 ent->aimDebounceTime = level.time + 5000;
717 }
718 }
719
720 #define TURRET_DEATH_DELAY 2000
721 #define TURRET_LIFETIME 60000
722
723 void turret_die(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod);
724
sentryExpire(gentity_t * self)725 void sentryExpire(gentity_t *self)
726 {
727 turret_die(self, self, self, 1000, MOD_UNKNOWN);
728 }
729
730 //---------------------------------
pas_think(gentity_t * ent)731 void pas_think( gentity_t *ent )
732 //---------------------------------
733 {
734 qboolean moved;
735 float diffYaw, diffPitch;
736 vec3_t enemyDir, org;
737 vec3_t frontAngles, backAngles;
738 vec3_t desiredAngles;
739 int iEntityList[MAX_GENTITIES];
740 int numListedEntities;
741 int i = 0;
742 qboolean clTrapped = qfalse;
743 vec3_t testMins, testMaxs;
744
745 testMins[0] = ent->r.currentOrigin[0] + ent->r.mins[0]+4;
746 testMins[1] = ent->r.currentOrigin[1] + ent->r.mins[1]+4;
747 testMins[2] = ent->r.currentOrigin[2] + ent->r.mins[2]+4;
748
749 testMaxs[0] = ent->r.currentOrigin[0] + ent->r.maxs[0]-4;
750 testMaxs[1] = ent->r.currentOrigin[1] + ent->r.maxs[1]-4;
751 testMaxs[2] = ent->r.currentOrigin[2] + ent->r.maxs[2]-4;
752
753 numListedEntities = trap->EntitiesInBox( testMins, testMaxs, iEntityList, MAX_GENTITIES );
754
755 while (i < numListedEntities)
756 {
757 if (iEntityList[i] < MAX_CLIENTS)
758 { //client stuck inside me. go nonsolid.
759 int clNum = iEntityList[i];
760
761 numListedEntities = trap->EntitiesInBox( g_entities[clNum].r.absmin, g_entities[clNum].r.absmax, iEntityList, MAX_GENTITIES );
762
763 i = 0;
764 while (i < numListedEntities)
765 {
766 if (iEntityList[i] == ent->s.number)
767 {
768 clTrapped = qtrue;
769 break;
770 }
771 i++;
772 }
773 break;
774 }
775
776 i++;
777 }
778
779 if (clTrapped)
780 {
781 ent->r.contents = 0;
782 ent->s.fireflag = 0;
783 ent->nextthink = level.time + FRAMETIME;
784 return;
785 }
786 else
787 {
788 ent->r.contents = CONTENTS_SOLID;
789 }
790
791 if (!g_entities[ent->genericValue3].inuse || !g_entities[ent->genericValue3].client ||
792 g_entities[ent->genericValue3].client->sess.sessionTeam != ent->genericValue2)
793 {
794 ent->think = G_FreeEntity;
795 ent->nextthink = level.time;
796 return;
797 }
798
799 // G_RunObject(ent);
800
801 if ( !ent->damage )
802 {
803 ent->damage = 1;
804 ent->nextthink = level.time + FRAMETIME;
805 return;
806 }
807
808 if ((ent->genericValue8+TURRET_LIFETIME) < level.time)
809 {
810 G_Sound( ent, CHAN_BODY, G_SoundIndex( "sound/chars/turret/shutdown.wav" ));
811 ent->s.bolt2 = ENTITYNUM_NONE;
812 ent->s.fireflag = 2;
813
814 ent->think = sentryExpire;
815 ent->nextthink = level.time + TURRET_DEATH_DELAY;
816 return;
817 }
818
819 ent->nextthink = level.time + FRAMETIME;
820
821 if ( ent->enemy )
822 {
823 // make sure that the enemy is still valid
824 pas_adjust_enemy( ent );
825 }
826
827 if (ent->enemy)
828 {
829 if (!ent->enemy->client)
830 {
831 ent->enemy = NULL;
832 }
833 else if (ent->enemy->s.number == ent->s.number)
834 {
835 ent->enemy = NULL;
836 }
837 else if (ent->enemy->health < 1)
838 {
839 ent->enemy = NULL;
840 }
841 }
842
843 if ( !ent->enemy )
844 {
845 pas_find_enemies( ent );
846 }
847
848 if (ent->enemy)
849 {
850 ent->s.bolt2 = ent->enemy->s.number;
851 }
852 else
853 {
854 ent->s.bolt2 = ENTITYNUM_NONE;
855 }
856
857 moved = qfalse;
858 diffYaw = 0.0f; diffPitch = 0.0f;
859
860 ent->speed = AngleNormalize360( ent->speed );
861 ent->random = AngleNormalize360( ent->random );
862
863 if ( ent->enemy )
864 {
865 // ...then we'll calculate what new aim adjustments we should attempt to make this frame
866 // Aim at enemy
867 if ( ent->enemy->client )
868 {
869 VectorCopy( ent->enemy->client->ps.origin, org );
870 }
871 else
872 {
873 VectorCopy( ent->enemy->r.currentOrigin, org );
874 }
875
876 VectorSubtract( org, ent->r.currentOrigin, enemyDir );
877 vectoangles( enemyDir, desiredAngles );
878
879 diffYaw = AngleSubtract( ent->speed, desiredAngles[YAW] );
880 diffPitch = AngleSubtract( ent->random, desiredAngles[PITCH] );
881 }
882 else
883 {
884 // no enemy, so make us slowly sweep back and forth as if searching for a new one
885 diffYaw = sin( level.time * 0.0001f + ent->count ) * 2.0f;
886 }
887
888 if ( fabs(diffYaw) > 0.25f )
889 {
890 moved = qtrue;
891
892 if ( fabs(diffYaw) > 10.0f )
893 {
894 // cap max speed
895 ent->speed += (diffYaw > 0.0f) ? -10.0f : 10.0f;
896 }
897 else
898 {
899 // small enough
900 ent->speed -= diffYaw;
901 }
902 }
903
904
905 if ( fabs(diffPitch) > 0.25f )
906 {
907 moved = qtrue;
908
909 if ( fabs(diffPitch) > 4.0f )
910 {
911 // cap max speed
912 ent->random += (diffPitch > 0.0f) ? -4.0f : 4.0f;
913 }
914 else
915 {
916 // small enough
917 ent->random -= diffPitch;
918 }
919 }
920
921 // the bone axes are messed up, so hence some dumbness here
922 VectorSet( frontAngles, -ent->random, 0.0f, 0.0f );
923 VectorSet( backAngles, 0.0f, 0.0f, ent->speed );
924
925 if ( moved )
926 {
927 //ent->s.loopSound = G_SoundIndex( "sound/chars/turret/move.wav" );
928 }
929 else
930 {
931 ent->s.loopSound = 0;
932 ent->s.loopIsSoundset = qfalse;
933 }
934
935 if ( ent->enemy && ent->attackDebounceTime < level.time )
936 {
937 ent->count--;
938
939 if ( ent->count )
940 {
941 pas_fire( ent );
942 ent->s.fireflag = 1;
943 ent->attackDebounceTime = level.time + 200;
944 }
945 else
946 {
947 //ent->nextthink = 0;
948 G_Sound( ent, CHAN_BODY, G_SoundIndex( "sound/chars/turret/shutdown.wav" ));
949 ent->s.bolt2 = ENTITYNUM_NONE;
950 ent->s.fireflag = 2;
951
952 ent->think = sentryExpire;
953 ent->nextthink = level.time + TURRET_DEATH_DELAY;
954 }
955 }
956 else
957 {
958 ent->s.fireflag = 0;
959 }
960 }
961
962 //------------------------------------------------------------------------------------------------------------
turret_die(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int mod)963 void turret_die(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod)
964 //------------------------------------------------------------------------------------------------------------
965 {
966 // Turn off the thinking of the base & use it's targets
967 self->think = 0;//NULL;
968 self->use = 0;//NULL;
969
970 if ( self->target )
971 {
972 G_UseTargets( self, attacker );
973 }
974
975 if (!g_entities[self->genericValue3].inuse || !g_entities[self->genericValue3].client)
976 {
977 G_FreeEntity(self);
978 return;
979 }
980
981 // clear my data
982 self->die = 0;//NULL;
983 self->takedamage = qfalse;
984 self->health = 0;
985
986 // hack the effect angle so that explode death can orient the effect properly
987 VectorSet( self->s.angles, 0, 0, 1 );
988
989 G_PlayEffect(EFFECT_EXPLOSION_PAS, self->s.pos.trBase, self->s.angles);
990 G_RadiusDamage(self->s.pos.trBase, &g_entities[self->genericValue3], 30, 256, self, self, MOD_UNKNOWN);
991
992 g_entities[self->genericValue3].client->ps.fd.sentryDeployed = qfalse;
993
994 //ExplodeDeath( self );
995 G_FreeEntity( self );
996 }
997
turret_free(gentity_t * self)998 void turret_free(gentity_t *self)
999 {
1000 if (!g_entities[self->genericValue3].inuse || !g_entities[self->genericValue3].client)
1001 {
1002 G_FreeEntity(self);
1003 return;
1004 }
1005
1006 g_entities[self->genericValue3].client->ps.fd.sentryDeployed = qfalse;
1007
1008 G_FreeEntity( self );
1009 }
1010
1011 #define TURRET_AMMO_COUNT 40
1012
1013 //---------------------------------
SP_PAS(gentity_t * base)1014 void SP_PAS( gentity_t *base )
1015 //---------------------------------
1016 {
1017 if ( base->count == 0 )
1018 {
1019 // give ammo
1020 base->count = TURRET_AMMO_COUNT;
1021 }
1022
1023 base->s.bolt1 = 1; //This is a sort of hack to indicate that this model needs special turret things done to it
1024 base->s.bolt2 = ENTITYNUM_NONE; //store our current enemy index
1025
1026 base->damage = 0; // start animation flag
1027
1028 VectorSet( base->r.mins, -8, -8, 0 );
1029 VectorSet( base->r.maxs, 8, 8, 24 );
1030
1031 G_RunObject(base);
1032
1033 base->think = pas_think;
1034 base->nextthink = level.time + FRAMETIME;
1035
1036 if ( !base->health )
1037 {
1038 base->health = 50;
1039 }
1040
1041 base->takedamage = qtrue;
1042 base->die = turret_die;
1043
1044 base->physicsObject = qtrue;
1045
1046 G_Sound( base, CHAN_BODY, G_SoundIndex( "sound/chars/turret/startup.wav" ));
1047 }
1048
1049 //------------------------------------------------------------------------
ItemUse_Sentry(gentity_t * ent)1050 void ItemUse_Sentry( gentity_t *ent )
1051 //------------------------------------------------------------------------
1052 {
1053 vec3_t fwd, fwdorg;
1054 vec3_t yawonly;
1055 vec3_t mins, maxs;
1056 gentity_t *sentry;
1057
1058 if (!ent || !ent->client)
1059 {
1060 return;
1061 }
1062
1063 VectorSet( mins, -8, -8, 0 );
1064 VectorSet( maxs, 8, 8, 24 );
1065
1066
1067 yawonly[ROLL] = 0;
1068 yawonly[PITCH] = 0;
1069 yawonly[YAW] = ent->client->ps.viewangles[YAW];
1070
1071 AngleVectors(yawonly, fwd, NULL, NULL);
1072
1073 fwdorg[0] = ent->client->ps.origin[0] + fwd[0]*64;
1074 fwdorg[1] = ent->client->ps.origin[1] + fwd[1]*64;
1075 fwdorg[2] = ent->client->ps.origin[2] + fwd[2]*64;
1076
1077 sentry = G_Spawn();
1078
1079 sentry->classname = "sentryGun";
1080 sentry->s.modelindex = G_ModelIndex("models/items/psgun.glm"); //replace ASAP
1081
1082 sentry->s.g2radius = 30.0f;
1083 sentry->s.modelGhoul2 = 1;
1084
1085 G_SetOrigin(sentry, fwdorg);
1086 sentry->parent = ent;
1087 sentry->r.contents = CONTENTS_SOLID;
1088 sentry->s.solid = 2;
1089 sentry->clipmask = MASK_SOLID;
1090 VectorCopy(mins, sentry->r.mins);
1091 VectorCopy(maxs, sentry->r.maxs);
1092 sentry->genericValue3 = ent->s.number;
1093 sentry->genericValue2 = ent->client->sess.sessionTeam; //so we can remove ourself if our owner changes teams
1094 sentry->genericValue15 = HI_SENTRY_GUN;
1095 sentry->r.absmin[0] = sentry->s.pos.trBase[0] + sentry->r.mins[0];
1096 sentry->r.absmin[1] = sentry->s.pos.trBase[1] + sentry->r.mins[1];
1097 sentry->r.absmin[2] = sentry->s.pos.trBase[2] + sentry->r.mins[2];
1098 sentry->r.absmax[0] = sentry->s.pos.trBase[0] + sentry->r.maxs[0];
1099 sentry->r.absmax[1] = sentry->s.pos.trBase[1] + sentry->r.maxs[1];
1100 sentry->r.absmax[2] = sentry->s.pos.trBase[2] + sentry->r.maxs[2];
1101 sentry->s.eType = ET_GENERAL;
1102 sentry->s.pos.trType = TR_GRAVITY;//STATIONARY;
1103 sentry->s.pos.trTime = level.time;
1104 sentry->touch = SentryTouch;
1105 sentry->nextthink = level.time;
1106 sentry->genericValue4 = ENTITYNUM_NONE; //genericValue4 used as enemy index
1107
1108 sentry->genericValue5 = 1000;
1109
1110 sentry->genericValue8 = level.time;
1111
1112 sentry->alliedTeam = ent->client->sess.sessionTeam;
1113
1114 ent->client->ps.fd.sentryDeployed = qtrue;
1115
1116 trap->LinkEntity((sharedEntity_t *)sentry);
1117
1118 sentry->s.owner = ent->s.number;
1119 sentry->s.shouldtarget = qtrue;
1120 if (level.gametype >= GT_TEAM)
1121 {
1122 sentry->s.teamowner = ent->client->sess.sessionTeam;
1123 }
1124 else
1125 {
1126 sentry->s.teamowner = 16;
1127 }
1128
1129 SP_PAS( sentry );
1130 }
1131
1132 extern gentity_t *NPC_SpawnType( gentity_t *ent, char *npc_type, char *targetname, qboolean isVehicle );
ItemUse_Seeker(gentity_t * ent)1133 void ItemUse_Seeker(gentity_t *ent)
1134 {
1135 if ( level.gametype == GT_SIEGE && d_siegeSeekerNPC.integer )
1136 {//actualy spawn a remote NPC
1137 gentity_t *remote = NPC_SpawnType( ent, "remote", NULL, qfalse );
1138 if ( remote && remote->client )
1139 {//set it to my team
1140 remote->s.owner = remote->r.ownerNum = ent->s.number;
1141 remote->activator = ent;
1142 if ( ent->client->sess.sessionTeam == TEAM_BLUE )
1143 {
1144 remote->client->playerTeam = NPCTEAM_PLAYER;
1145 }
1146 else if ( ent->client->sess.sessionTeam == TEAM_RED )
1147 {
1148 remote->client->playerTeam = NPCTEAM_ENEMY;
1149 }
1150 else
1151 {
1152 remote->client->playerTeam = NPCTEAM_NEUTRAL;
1153 }
1154 }
1155 }
1156 else
1157 {
1158 ent->client->ps.eFlags |= EF_SEEKERDRONE;
1159 ent->client->ps.droneExistTime = level.time + 30000;
1160 ent->client->ps.droneFireTime = level.time + 1500;
1161 }
1162 }
1163
MedPackGive(gentity_t * ent,int amount)1164 static void MedPackGive(gentity_t *ent, int amount)
1165 {
1166 if (!ent || !ent->client)
1167 {
1168 return;
1169 }
1170
1171 if (ent->health <= 0 ||
1172 ent->client->ps.stats[STAT_HEALTH] <= 0 ||
1173 (ent->client->ps.eFlags & EF_DEAD))
1174 {
1175 return;
1176 }
1177
1178 if (ent->health >= ent->client->ps.stats[STAT_MAX_HEALTH])
1179 {
1180 return;
1181 }
1182
1183 ent->health += amount;
1184
1185 if (ent->health > ent->client->ps.stats[STAT_MAX_HEALTH])
1186 {
1187 ent->health = ent->client->ps.stats[STAT_MAX_HEALTH];
1188 }
1189 }
1190
ItemUse_MedPack_Big(gentity_t * ent)1191 void ItemUse_MedPack_Big(gentity_t *ent)
1192 {
1193 MedPackGive(ent, MAX_MEDPACK_BIG_HEAL_AMOUNT);
1194 }
1195
ItemUse_MedPack(gentity_t * ent)1196 void ItemUse_MedPack(gentity_t *ent)
1197 {
1198 MedPackGive(ent, MAX_MEDPACK_HEAL_AMOUNT);
1199 }
1200
1201 #define JETPACK_TOGGLE_TIME 1000
Jetpack_Off(gentity_t * ent)1202 void Jetpack_Off(gentity_t *ent)
1203 { //create effects?
1204 assert(ent && ent->client);
1205
1206 if (!ent->client->jetPackOn)
1207 { //aready off
1208 return;
1209 }
1210
1211 ent->client->jetPackOn = qfalse;
1212 }
1213
Jetpack_On(gentity_t * ent)1214 void Jetpack_On(gentity_t *ent)
1215 { //create effects?
1216 assert(ent && ent->client);
1217
1218 if (ent->client->jetPackOn)
1219 { //aready on
1220 return;
1221 }
1222
1223 if (ent->client->ps.fd.forceGripBeingGripped >= level.time)
1224 { //can't turn on during grip interval
1225 return;
1226 }
1227
1228 if (ent->client->ps.fallingToDeath)
1229 { //too late!
1230 return;
1231 }
1232
1233 G_Sound(ent, CHAN_AUTO, G_SoundIndex("sound/boba/JETON"));
1234
1235 ent->client->jetPackOn = qtrue;
1236 }
1237
ItemUse_Jetpack(gentity_t * ent)1238 void ItemUse_Jetpack( gentity_t *ent )
1239 {
1240 assert(ent && ent->client);
1241
1242 if (ent->client->jetPackToggleTime >= level.time)
1243 {
1244 return;
1245 }
1246
1247 if (ent->health <= 0 ||
1248 ent->client->ps.stats[STAT_HEALTH] <= 0 ||
1249 (ent->client->ps.eFlags & EF_DEAD) ||
1250 ent->client->ps.pm_type == PM_DEAD)
1251 { //can't use it when dead under any circumstances.
1252 return;
1253 }
1254
1255 if (!ent->client->jetPackOn &&
1256 ent->client->ps.jetpackFuel < 5)
1257 { //too low on fuel to start it up
1258 return;
1259 }
1260
1261 if (ent->client->jetPackOn)
1262 {
1263 Jetpack_Off(ent);
1264 }
1265 else
1266 {
1267 Jetpack_On(ent);
1268 }
1269
1270 ent->client->jetPackToggleTime = level.time + JETPACK_TOGGLE_TIME;
1271 }
1272
1273 #define CLOAK_TOGGLE_TIME 1000
1274 extern void Jedi_Cloak( gentity_t *self );
1275 extern void Jedi_Decloak( gentity_t *self );
ItemUse_UseCloak(gentity_t * ent)1276 void ItemUse_UseCloak( gentity_t *ent )
1277 {
1278 assert(ent && ent->client);
1279
1280 if (ent->client->cloakToggleTime >= level.time)
1281 {
1282 return;
1283 }
1284
1285 if (ent->health <= 0 ||
1286 ent->client->ps.stats[STAT_HEALTH] <= 0 ||
1287 (ent->client->ps.eFlags & EF_DEAD) ||
1288 ent->client->ps.pm_type == PM_DEAD)
1289 { //can't use it when dead under any circumstances.
1290 return;
1291 }
1292
1293 if (!ent->client->ps.powerups[PW_CLOAKED] &&
1294 ent->client->ps.cloakFuel < 5)
1295 { //too low on fuel to start it up
1296 return;
1297 }
1298
1299 if ( ent->client->ps.powerups[PW_CLOAKED] )
1300 {//decloak
1301 Jedi_Decloak( ent );
1302 }
1303 else
1304 {//cloak
1305 Jedi_Cloak( ent );
1306 }
1307
1308 ent->client->cloakToggleTime = level.time + CLOAK_TOGGLE_TIME;
1309 }
1310
1311 #define TOSSED_ITEM_STAY_PERIOD 20000
1312 #define TOSSED_ITEM_OWNER_NOTOUCH_DUR 1000
1313
SpecialItemThink(gentity_t * ent)1314 void SpecialItemThink(gentity_t *ent)
1315 {
1316 float gravity = 3.0f;
1317 float mass = 0.09f;
1318 float bounce = 1.1f;
1319
1320 if (ent->genericValue5 < level.time)
1321 {
1322 ent->think = G_FreeEntity;
1323 ent->nextthink = level.time;
1324 return;
1325 }
1326
1327 G_RunExPhys(ent, gravity, mass, bounce, qfalse, NULL, 0);
1328 VectorCopy(ent->r.currentOrigin, ent->s.origin);
1329 ent->nextthink = level.time + 50;
1330 }
1331
G_SpecialSpawnItem(gentity_t * ent,gitem_t * item)1332 void G_SpecialSpawnItem(gentity_t *ent, gitem_t *item)
1333 {
1334 RegisterItem( item );
1335 ent->item = item;
1336
1337 //go away if no one wants me
1338 ent->genericValue5 = level.time + TOSSED_ITEM_STAY_PERIOD;
1339 ent->think = SpecialItemThink;
1340 ent->nextthink = level.time + 50;
1341 ent->clipmask = MASK_SOLID;
1342
1343 ent->physicsBounce = 0.50; // items are bouncy
1344 VectorSet (ent->r.mins, -8, -8, -0);
1345 VectorSet (ent->r.maxs, 8, 8, 16);
1346
1347 ent->s.eType = ET_ITEM;
1348 ent->s.modelindex = ent->item - bg_itemlist; // store item number in modelindex
1349
1350 ent->r.contents = CONTENTS_TRIGGER;
1351 ent->touch = Touch_Item;
1352
1353 //can't touch owner for x seconds
1354 ent->genericValue11 = ent->r.ownerNum;
1355 ent->genericValue10 = level.time + TOSSED_ITEM_OWNER_NOTOUCH_DUR;
1356
1357 //so we know to remove when picked up, not respawn
1358 ent->genericValue9 = 1;
1359
1360 //kind of a lame value to use, but oh well. This means don't
1361 //pick up this item clientside with prediction, because we
1362 //aren't sending over all the data necessary for the player
1363 //to know if he can.
1364 ent->s.brokenLimbs = 1;
1365
1366 //since it uses my server-only physics
1367 ent->s.eFlags |= EF_CLIENTSMOOTH;
1368 }
1369
1370 #define DISP_HEALTH_ITEM "item_medpak_instant"
1371 #define DISP_AMMO_ITEM "ammo_all"
1372
G_PrecacheDispensers(void)1373 void G_PrecacheDispensers(void)
1374 {
1375 gitem_t *item;
1376
1377 item = BG_FindItem(DISP_HEALTH_ITEM);
1378 if (item)
1379 {
1380 RegisterItem(item);
1381 }
1382
1383 item = BG_FindItem(DISP_AMMO_ITEM);
1384 if (item)
1385 {
1386 RegisterItem(item);
1387 }
1388 }
1389
ItemUse_UseDisp(gentity_t * ent,int type)1390 void ItemUse_UseDisp(gentity_t *ent, int type)
1391 {
1392 gitem_t *item = NULL;
1393 gentity_t *eItem;
1394
1395 if (!ent->client ||
1396 ent->client->tossableItemDebounce > level.time)
1397 { //can't use it again yet
1398 return;
1399 }
1400
1401 if (ent->client->ps.weaponTime > 0 ||
1402 ent->client->ps.forceHandExtend != HANDEXTEND_NONE)
1403 { //busy doing something else
1404 return;
1405 }
1406
1407 ent->client->tossableItemDebounce = level.time + TOSS_DEBOUNCE_TIME;
1408
1409 if (type == HI_HEALTHDISP)
1410 {
1411 item = BG_FindItem(DISP_HEALTH_ITEM);
1412 }
1413 else
1414 {
1415 item = BG_FindItem(DISP_AMMO_ITEM);
1416 }
1417
1418 if (item)
1419 {
1420 vec3_t fwd, pos;
1421 gentity_t *te;
1422
1423 eItem = G_Spawn();
1424 eItem->r.ownerNum = ent->s.number;
1425 eItem->classname = item->classname;
1426
1427 VectorCopy(ent->client->ps.origin, pos);
1428 pos[2] += ent->client->ps.viewheight;
1429
1430 G_SetOrigin(eItem, pos);
1431 VectorCopy(eItem->r.currentOrigin, eItem->s.origin);
1432 trap->LinkEntity((sharedEntity_t *)eItem);
1433
1434 G_SpecialSpawnItem(eItem, item);
1435
1436 AngleVectors(ent->client->ps.viewangles, fwd, NULL, NULL);
1437 VectorScale(fwd, 128.0f, eItem->epVelocity);
1438 eItem->epVelocity[2] = 16.0f;
1439
1440 // G_SetAnim( ent, NULL, SETANIM_TORSO, BOTH_THERMAL_THROW, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0 );
1441
1442 te = G_TempEntity( ent->client->ps.origin, EV_LOCALTIMER );
1443 te->s.time = level.time;
1444 te->s.time2 = TOSS_DEBOUNCE_TIME;
1445 te->s.owner = ent->client->ps.clientNum;
1446 }
1447 }
1448
1449
1450 //===============================================
1451 //Portable E-Web -rww
1452 //===============================================
1453 //put the e-web away/remove it from the owner
EWebDisattach(gentity_t * owner,gentity_t * eweb)1454 void EWebDisattach(gentity_t *owner, gentity_t *eweb)
1455 {
1456 owner->client->ewebIndex = 0;
1457 owner->client->ps.emplacedIndex = 0;
1458 if (owner->health > 0)
1459 {
1460 owner->client->ps.stats[STAT_WEAPONS] = eweb->genericValue11;
1461 }
1462 else
1463 {
1464 owner->client->ps.stats[STAT_WEAPONS] = 0;
1465 }
1466 eweb->think = G_FreeEntity;
1467 eweb->nextthink = level.time;
1468 }
1469
1470 //precache misc e-web assets
EWebPrecache(void)1471 void EWebPrecache(void)
1472 {
1473 RegisterItem( BG_FindItemForWeapon(WP_TURRET) );
1474 G_EffectIndex("detpack/explosion.efx");
1475 G_EffectIndex("turret/muzzle_flash.efx");
1476 }
1477
1478 //e-web death
1479 #define EWEB_DEATH_RADIUS 128
1480 #define EWEB_DEATH_DMG 90
1481
1482 extern void BG_CycleInven(playerState_t *ps, int direction); //bg_misc.c
1483
EWebDie(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int mod)1484 void EWebDie(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod)
1485 {
1486 vec3_t fxDir;
1487
1488 G_RadiusDamage(self->r.currentOrigin, self, EWEB_DEATH_DMG, EWEB_DEATH_RADIUS, self, self, MOD_SUICIDE);
1489
1490 VectorSet(fxDir, 1.0f, 0.0f, 0.0f);
1491 G_PlayEffect(EFFECT_EXPLOSION_DETPACK, self->r.currentOrigin, fxDir);
1492
1493 if (self->r.ownerNum != ENTITYNUM_NONE)
1494 {
1495 gentity_t *owner = &g_entities[self->r.ownerNum];
1496
1497 if (owner->inuse && owner->client)
1498 {
1499 EWebDisattach(owner, self);
1500
1501 //make sure it resets next time we spawn one in case we someone obtain one before death
1502 owner->client->ewebHealth = -1;
1503
1504 //take it away from him, it is gone forever.
1505 owner->client->ps.stats[STAT_HOLDABLE_ITEMS] &= ~(1<<HI_EWEB);
1506
1507 if (owner->client->ps.stats[STAT_HOLDABLE_ITEM] > 0 &&
1508 bg_itemlist[owner->client->ps.stats[STAT_HOLDABLE_ITEM]].giType == IT_HOLDABLE &&
1509 bg_itemlist[owner->client->ps.stats[STAT_HOLDABLE_ITEM]].giTag == HI_EWEB)
1510 { //he has it selected so deselect it and select the first thing available
1511 owner->client->ps.stats[STAT_HOLDABLE_ITEM] = 0;
1512 BG_CycleInven(&owner->client->ps, 1);
1513 }
1514 }
1515 }
1516 }
1517
1518 //e-web pain
EWebPain(gentity_t * self,gentity_t * attacker,int damage)1519 void EWebPain(gentity_t *self, gentity_t *attacker, int damage)
1520 {
1521 //update the owner's health status of me
1522 if (self->r.ownerNum != ENTITYNUM_NONE)
1523 {
1524 gentity_t *owner = &g_entities[self->r.ownerNum];
1525
1526 if (owner->inuse && owner->client)
1527 {
1528 owner->client->ewebHealth = self->health;
1529 }
1530 }
1531 }
1532
1533 //special routine for tracking angles between client and server
EWeb_SetBoneAngles(gentity_t * ent,char * bone,vec3_t angles)1534 void EWeb_SetBoneAngles(gentity_t *ent, char *bone, vec3_t angles)
1535 {
1536 int *thebone = &ent->s.boneIndex1;
1537 int *firstFree = NULL;
1538 int i = 0;
1539 int boneIndex = G_BoneIndex(bone);
1540 int flags, up, right, forward;
1541 vec3_t *boneVector = &ent->s.boneAngles1;
1542 vec3_t *freeBoneVec = NULL;
1543
1544 while (thebone)
1545 {
1546 if (!*thebone && !firstFree)
1547 { //if the value is 0 then this index is clear, we can use it if we don't find the bone we want already existing.
1548 firstFree = thebone;
1549 freeBoneVec = boneVector;
1550 }
1551 else if (*thebone)
1552 {
1553 if (*thebone == boneIndex)
1554 { //this is it
1555 break;
1556 }
1557 }
1558
1559 switch (i)
1560 {
1561 case 0:
1562 thebone = &ent->s.boneIndex2;
1563 boneVector = &ent->s.boneAngles2;
1564 break;
1565 case 1:
1566 thebone = &ent->s.boneIndex3;
1567 boneVector = &ent->s.boneAngles3;
1568 break;
1569 case 2:
1570 thebone = &ent->s.boneIndex4;
1571 boneVector = &ent->s.boneAngles4;
1572 break;
1573 default:
1574 thebone = NULL;
1575 boneVector = NULL;
1576 break;
1577 }
1578
1579 i++;
1580 }
1581
1582 if (!thebone)
1583 { //didn't find it, create it
1584 if (!firstFree)
1585 { //no free bones.. can't do a thing then.
1586 Com_Printf("WARNING: E-Web has no free bone indexes\n");
1587 return;
1588 }
1589
1590 thebone = firstFree;
1591
1592 *thebone = boneIndex;
1593 boneVector = freeBoneVec;
1594 }
1595
1596 //If we got here then we have a vector and an index.
1597
1598 //Copy the angles over the vector in the entitystate, so we can use the corresponding index
1599 //to set the bone angles on the client.
1600 VectorCopy(angles, *boneVector);
1601
1602 //Now set the angles on our server instance if we have one.
1603
1604 if (!ent->ghoul2)
1605 {
1606 return;
1607 }
1608
1609 flags = BONE_ANGLES_POSTMULT;
1610 up = POSITIVE_Y;
1611 right = NEGATIVE_Z;
1612 forward = NEGATIVE_X;
1613
1614 //first 3 bits is forward, second 3 bits is right, third 3 bits is up
1615 ent->s.boneOrient = ((forward)|(right<<3)|(up<<6));
1616
1617 trap->G2API_SetBoneAngles( ent->ghoul2,
1618 0,
1619 bone,
1620 angles,
1621 flags,
1622 up,
1623 right,
1624 forward,
1625 NULL,
1626 100,
1627 level.time );
1628 }
1629
1630 //start an animation on model_root both server side and client side
EWeb_SetBoneAnim(gentity_t * eweb,int startFrame,int endFrame)1631 void EWeb_SetBoneAnim(gentity_t *eweb, int startFrame, int endFrame)
1632 {
1633 //set info on the entity so it knows to start the anim on the client next snapshot.
1634 eweb->s.eFlags |= EF_G2ANIMATING;
1635
1636 if (eweb->s.torsoAnim == startFrame && eweb->s.legsAnim == endFrame)
1637 { //already playing this anim, let's flag it to restart
1638 eweb->s.torsoFlip = !eweb->s.torsoFlip;
1639 }
1640 else
1641 {
1642 eweb->s.torsoAnim = startFrame;
1643 eweb->s.legsAnim = endFrame;
1644 }
1645
1646 //now set the animation on the server ghoul2 instance.
1647 assert(eweb->ghoul2);
1648 trap->G2API_SetBoneAnim(eweb->ghoul2, 0, "model_root", startFrame, endFrame,
1649 (BONE_ANIM_OVERRIDE_FREEZE|BONE_ANIM_BLEND), 1.0f, level.time, -1, 100);
1650 }
1651
1652 //fire a shot off
1653 #define EWEB_MISSILE_DAMAGE 20
EWebFire(gentity_t * owner,gentity_t * eweb)1654 void EWebFire(gentity_t *owner, gentity_t *eweb)
1655 {
1656 mdxaBone_t boltMatrix;
1657 gentity_t *missile;
1658 vec3_t p, d, bPoint;
1659
1660 if (eweb->genericValue10 == -1)
1661 { //oh no
1662 assert(!"Bad e-web bolt");
1663 return;
1664 }
1665
1666 //get the muzzle point
1667 trap->G2API_GetBoltMatrix(eweb->ghoul2, 0, eweb->genericValue10, &boltMatrix, eweb->s.apos.trBase, eweb->r.currentOrigin, level.time, NULL, eweb->modelScale);
1668 BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, p);
1669 BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_Y, d);
1670
1671 //Start the thing backwards into the bounding box so it can't start inside other solid things
1672 VectorMA(p, -16.0f, d, bPoint);
1673
1674 //create the missile
1675 missile = CreateMissile( bPoint, d, 1200.0f, 10000, owner, qfalse );
1676
1677 missile->classname = "generic_proj";
1678 missile->s.weapon = WP_TURRET;
1679
1680 missile->damage = EWEB_MISSILE_DAMAGE;
1681 missile->dflags = DAMAGE_DEATH_KNOCKBACK;
1682 missile->methodOfDeath = MOD_TURBLAST;
1683 missile->clipmask = (MASK_SHOT|CONTENTS_LIGHTSABER);
1684
1685 //ignore the e-web entity
1686 missile->passThroughNum = eweb->s.number+1;
1687
1688 //times it can bounce before it dies
1689 missile->bounceCount = 8;
1690
1691 //play the muzzle flash
1692 vectoangles(d, d);
1693 G_PlayEffectID(G_EffectIndex("turret/muzzle_flash.efx"), p, d);
1694 }
1695
1696 //lock the owner into place relative to the cannon pos
EWebPositionUser(gentity_t * owner,gentity_t * eweb)1697 void EWebPositionUser(gentity_t *owner, gentity_t *eweb)
1698 {
1699 mdxaBone_t boltMatrix;
1700 vec3_t p, d;
1701 trace_t tr;
1702
1703 trap->G2API_GetBoltMatrix(eweb->ghoul2, 0, eweb->genericValue9, &boltMatrix, eweb->s.apos.trBase, eweb->r.currentOrigin, level.time, NULL, eweb->modelScale);
1704 BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, p);
1705 BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_X, d);
1706
1707 VectorMA(p, 32.0f, d, p);
1708 p[2] = eweb->r.currentOrigin[2];
1709
1710 p[2] += 4.0f;
1711
1712 trap->Trace(&tr, owner->client->ps.origin, owner->r.mins, owner->r.maxs, p, owner->s.number, MASK_PLAYERSOLID, qfalse, 0, 0);
1713
1714 if (!tr.startsolid && !tr.allsolid && tr.fraction == 1.0f)
1715 { //all clear, we can move there
1716 vec3_t pDown;
1717
1718 VectorCopy(p, pDown);
1719 pDown[2] -= 7.0f;
1720 trap->Trace(&tr, p, owner->r.mins, owner->r.maxs, pDown, owner->s.number, MASK_PLAYERSOLID, qfalse, 0, 0);
1721
1722 if (!tr.startsolid && !tr.allsolid)
1723 {
1724 VectorSubtract(owner->client->ps.origin, tr.endpos, d);
1725 if (VectorLength(d) > 1.0f)
1726 { //we moved, do some animating
1727 vec3_t dAng;
1728 int aFlags = SETANIM_FLAG_HOLD;
1729
1730 vectoangles(d, dAng);
1731 dAng[YAW] = AngleSubtract(owner->client->ps.viewangles[YAW], dAng[YAW]);
1732 if (dAng[YAW] > 0.0f)
1733 {
1734 if (owner->client->ps.legsAnim == BOTH_STRAFE_RIGHT1)
1735 { //reset to change direction
1736 aFlags |= SETANIM_FLAG_OVERRIDE;
1737 }
1738 G_SetAnim(owner, &owner->client->pers.cmd, SETANIM_LEGS, BOTH_STRAFE_LEFT1, aFlags, 0);
1739 }
1740 else
1741 {
1742 if (owner->client->ps.legsAnim == BOTH_STRAFE_LEFT1)
1743 { //reset to change direction
1744 aFlags |= SETANIM_FLAG_OVERRIDE;
1745 }
1746 G_SetAnim(owner, &owner->client->pers.cmd, SETANIM_LEGS, BOTH_STRAFE_RIGHT1, aFlags, 0);
1747 }
1748 }
1749 else if (owner->client->ps.legsAnim == BOTH_STRAFE_RIGHT1 || owner->client->ps.legsAnim == BOTH_STRAFE_LEFT1)
1750 { //don't keep animating in place
1751 owner->client->ps.legsTimer = 0;
1752 }
1753
1754 G_SetOrigin(owner, tr.endpos);
1755 VectorCopy(tr.endpos, owner->client->ps.origin);
1756 }
1757 }
1758 else
1759 { //can't move here.. stop using the thing I guess
1760 EWebDisattach(owner, eweb);
1761 }
1762 }
1763
1764 //keep the bone angles updated based on the owner's look dir
EWebUpdateBoneAngles(gentity_t * owner,gentity_t * eweb)1765 void EWebUpdateBoneAngles(gentity_t *owner, gentity_t *eweb)
1766 {
1767 vec3_t yAng;
1768 float ideal;
1769 float incr;
1770 const float turnCap = 4.0f; //max degrees we can turn per update
1771
1772 VectorClear(yAng);
1773 ideal = AngleSubtract(owner->client->ps.viewangles[YAW], eweb->s.angles[YAW]);
1774 incr = AngleSubtract(ideal, eweb->angle);
1775
1776 if (incr > turnCap)
1777 {
1778 incr = turnCap;
1779 }
1780 else if (incr < -turnCap)
1781 {
1782 incr = -turnCap;
1783 }
1784
1785 eweb->angle += incr;
1786
1787 yAng[0] = eweb->angle;
1788 EWeb_SetBoneAngles(eweb, "cannon_Yrot", yAng);
1789
1790 EWebPositionUser(owner, eweb);
1791 if (!owner->client->ewebIndex)
1792 { //was removed during position function
1793 return;
1794 }
1795
1796 VectorClear(yAng);
1797 yAng[2] = AngleSubtract(owner->client->ps.viewangles[PITCH], eweb->s.angles[PITCH])*0.8f;
1798 EWeb_SetBoneAngles(eweb, "cannon_Xrot", yAng);
1799 }
1800
1801 //keep it updated
1802 extern int BG_EmplacedView(vec3_t baseAngles, vec3_t angles, float *newYaw, float constraint); //bg_misc.c
1803
EWebThink(gentity_t * self)1804 void EWebThink(gentity_t *self)
1805 {
1806 qboolean killMe = qfalse;
1807 const float gravity = 3.0f;
1808 const float mass = 0.09f;
1809 const float bounce = 1.1f;
1810
1811 if (self->r.ownerNum == ENTITYNUM_NONE)
1812 {
1813 killMe = qtrue;
1814 }
1815 else
1816 {
1817 gentity_t *owner = &g_entities[self->r.ownerNum];
1818
1819 if (!owner->inuse || !owner->client || owner->client->pers.connected != CON_CONNECTED ||
1820 owner->client->ewebIndex != self->s.number || owner->health < 1)
1821 {
1822 killMe = qtrue;
1823 }
1824 else if (owner->client->ps.emplacedIndex != self->s.number)
1825 { //just go back to the inventory then
1826 EWebDisattach(owner, self);
1827 return;
1828 }
1829
1830 if (!killMe)
1831 {
1832 float yaw;
1833
1834 if (BG_EmplacedView(owner->client->ps.viewangles, self->s.angles, &yaw, self->s.origin2[0]))
1835 {
1836 owner->client->ps.viewangles[YAW] = yaw;
1837 }
1838 owner->client->ps.weapon = WP_EMPLACED_GUN;
1839 owner->client->ps.stats[STAT_WEAPONS] = WP_EMPLACED_GUN;
1840
1841 if (self->genericValue8 < level.time)
1842 { //make sure the anim timer is done
1843 EWebUpdateBoneAngles(owner, self);
1844 if (!owner->client->ewebIndex)
1845 { //was removed during position function
1846 return;
1847 }
1848
1849 if (owner->client->pers.cmd.buttons & BUTTON_ATTACK)
1850 {
1851 if (self->genericValue5 < level.time)
1852 { //we can fire another shot off
1853 EWebFire(owner, self);
1854
1855 //cheap firing anim
1856 EWeb_SetBoneAnim(self, 2, 4);
1857 self->genericValue3 = 1;
1858
1859 //set fire debounce time
1860 self->genericValue5 = level.time + 100;
1861 }
1862 }
1863 else if (self->genericValue5 < level.time && self->genericValue3)
1864 { //reset the anim back to non-firing
1865 EWeb_SetBoneAnim(self, 0, 1);
1866 self->genericValue3 = 0;
1867 }
1868 }
1869 }
1870 }
1871
1872 if (killMe)
1873 { //something happened to the owner, let's explode
1874 EWebDie(self, self, self, 999, MOD_SUICIDE);
1875 return;
1876 }
1877
1878 //run some physics on it real quick so it falls and stuff properly
1879 G_RunExPhys(self, gravity, mass, bounce, qfalse, NULL, 0);
1880
1881 self->nextthink = level.time;
1882 }
1883
1884 #define EWEB_HEALTH 200
1885
1886 //spawn and set up an e-web ent
EWeb_Create(gentity_t * spawner)1887 gentity_t *EWeb_Create(gentity_t *spawner)
1888 {
1889 const char *modelName = "models/map_objects/hoth/eweb_model.glm";
1890 int failSound = G_SoundIndex("sound/interface/shieldcon_empty");
1891 gentity_t *ent;
1892 trace_t tr;
1893 vec3_t fAng, fwd, pos, downPos, s;
1894 vec3_t mins, maxs;
1895
1896 VectorSet(mins, -32, -32, -24);
1897 VectorSet(maxs, 32, 32, 24);
1898
1899 VectorSet(fAng, 0, spawner->client->ps.viewangles[1], 0);
1900 AngleVectors(fAng, fwd, 0, 0);
1901
1902 VectorCopy(spawner->client->ps.origin, s);
1903 //allow some fudge
1904 s[2] += 12.0f;
1905
1906 VectorMA(s, 48.0f, fwd, pos);
1907
1908 trap->Trace(&tr, s, mins, maxs, pos, spawner->s.number, MASK_PLAYERSOLID, qfalse, 0, 0);
1909
1910 if (tr.allsolid || tr.startsolid || tr.fraction != 1.0f)
1911 { //can't spawn here, we are in solid
1912 G_Sound(spawner, CHAN_AUTO, failSound);
1913 return NULL;
1914 }
1915
1916 ent = G_Spawn();
1917
1918 ent->clipmask = MASK_PLAYERSOLID;
1919 ent->r.contents = MASK_PLAYERSOLID;
1920
1921 ent->physicsObject = qtrue;
1922
1923 //for the sake of being able to differentiate client-side between this and an emplaced gun
1924 ent->s.weapon = WP_NONE;
1925
1926 VectorCopy(pos, downPos);
1927 downPos[2] -= 18.0f;
1928 trap->Trace(&tr, pos, mins, maxs, downPos, spawner->s.number, MASK_PLAYERSOLID, qfalse, 0, 0);
1929
1930 if (tr.startsolid || tr.allsolid || tr.fraction == 1.0f || tr.entityNum < ENTITYNUM_WORLD)
1931 { //didn't hit ground.
1932 G_FreeEntity(ent);
1933 G_Sound(spawner, CHAN_AUTO, failSound);
1934 return NULL;
1935 }
1936
1937 VectorCopy(tr.endpos, pos);
1938
1939 G_SetOrigin(ent, pos);
1940
1941 VectorCopy(fAng, ent->s.apos.trBase);
1942 VectorCopy(fAng, ent->r.currentAngles);
1943
1944 ent->s.owner = spawner->s.number;
1945 ent->s.teamowner = spawner->client->sess.sessionTeam;
1946
1947 ent->takedamage = qtrue;
1948
1949 if (spawner->client->ewebHealth <= 0)
1950 { //refresh the owner's e-web health if its last e-web did not exist or was killed
1951 spawner->client->ewebHealth = EWEB_HEALTH;
1952 }
1953
1954 //resume health of last deployment
1955 ent->maxHealth = EWEB_HEALTH;
1956 ent->health = spawner->client->ewebHealth;
1957 G_ScaleNetHealth(ent);
1958
1959 ent->die = EWebDie;
1960 ent->pain = EWebPain;
1961
1962 ent->think = EWebThink;
1963 ent->nextthink = level.time;
1964
1965 //set up the g2 model info
1966 ent->s.modelGhoul2 = 1;
1967 ent->s.g2radius = 128;
1968 ent->s.modelindex = G_ModelIndex((char *)modelName);
1969
1970 trap->G2API_InitGhoul2Model(&ent->ghoul2, modelName, 0, 0, 0, 0, 0);
1971
1972 if (!ent->ghoul2)
1973 { //should not happen, but just to be safe.
1974 G_FreeEntity(ent);
1975 return NULL;
1976 }
1977
1978 //initialize bone angles
1979 EWeb_SetBoneAngles(ent, "cannon_Yrot", vec3_origin);
1980 EWeb_SetBoneAngles(ent, "cannon_Xrot", vec3_origin);
1981
1982 ent->genericValue10 = trap->G2API_AddBolt(ent->ghoul2, 0, "*cannonflash"); //muzzle bolt
1983 ent->genericValue9 = trap->G2API_AddBolt(ent->ghoul2, 0, "cannon_Yrot"); //for placing the owner relative to rotation
1984
1985 //set the constraints for this guy as an emplaced weapon, and his constraint angles
1986 ent->s.origin2[0] = 360.0f; //360 degrees in either direction
1987
1988 VectorCopy(fAng, ent->s.angles); //consider "angle 0" for constraint
1989
1990 //angle of y rot bone
1991 ent->angle = 0.0f;
1992
1993 ent->r.ownerNum = spawner->s.number;
1994 trap->LinkEntity((sharedEntity_t *)ent);
1995
1996 //store off the owner's current weapons, we will be forcing him to use the "emplaced" weapon
1997 ent->genericValue11 = spawner->client->ps.stats[STAT_WEAPONS];
1998
1999 //start the "unfolding" anim
2000 EWeb_SetBoneAnim(ent, 4, 20);
2001 //don't allow use until the anim is done playing (rough time estimate)
2002 ent->genericValue8 = level.time + 500;
2003
2004 VectorCopy(mins, ent->r.mins);
2005 VectorCopy(maxs, ent->r.maxs);
2006
2007 return ent;
2008 }
2009
2010 #define EWEB_USE_DEBOUNCE 1000
2011 //use the e-web
ItemUse_UseEWeb(gentity_t * ent)2012 void ItemUse_UseEWeb(gentity_t *ent)
2013 {
2014 if (ent->client->ewebTime > level.time)
2015 { //can't use again yet
2016 return;
2017 }
2018
2019 if (ent->client->ps.weaponTime > 0 ||
2020 ent->client->ps.forceHandExtend != HANDEXTEND_NONE)
2021 { //busy doing something else
2022 return;
2023 }
2024
2025 if (ent->client->ps.emplacedIndex && !ent->client->ewebIndex)
2026 { //using an emplaced gun already that isn't our own e-web
2027 return;
2028 }
2029
2030 if (ent->client->ewebIndex)
2031 { //put it away
2032 EWebDisattach(ent, &g_entities[ent->client->ewebIndex]);
2033 }
2034 else
2035 { //create it
2036 gentity_t *eweb = EWeb_Create(ent);
2037
2038 if (eweb)
2039 { //if it's null the thing couldn't spawn (probably no room)
2040 ent->client->ewebIndex = eweb->s.number;
2041 ent->client->ps.emplacedIndex = eweb->s.number;
2042 }
2043 }
2044
2045 ent->client->ewebTime = level.time + EWEB_USE_DEBOUNCE;
2046 }
2047 //===============================================
2048 //End E-Web
2049 //===============================================
2050
2051
Pickup_Powerup(gentity_t * ent,gentity_t * other)2052 int Pickup_Powerup( gentity_t *ent, gentity_t *other ) {
2053 int quantity;
2054 int i;
2055 gclient_t *client;
2056
2057 if ( !other->client->ps.powerups[ent->item->giTag] ) {
2058 // round timing to seconds to make multiple powerup timers
2059 // count in sync
2060 other->client->ps.powerups[ent->item->giTag] =
2061 level.time - ( level.time % 1000 );
2062
2063 G_LogWeaponPowerup(other->s.number, ent->item->giTag);
2064 }
2065
2066 if ( ent->count ) {
2067 quantity = ent->count;
2068 } else {
2069 quantity = ent->item->quantity;
2070 }
2071
2072 other->client->ps.powerups[ent->item->giTag] += quantity * 1000;
2073
2074 if (ent->item->giTag == PW_YSALAMIRI)
2075 {
2076 other->client->ps.powerups[PW_FORCE_ENLIGHTENED_LIGHT] = 0;
2077 other->client->ps.powerups[PW_FORCE_ENLIGHTENED_DARK] = 0;
2078 other->client->ps.powerups[PW_FORCE_BOON] = 0;
2079 }
2080
2081 // give any nearby players a "denied" anti-reward
2082 for ( i = 0 ; i < level.maxclients ; i++ ) {
2083 vec3_t delta;
2084 float len;
2085 vec3_t forward;
2086 trace_t tr;
2087
2088 client = &level.clients[i];
2089 if ( client == other->client ) {
2090 continue;
2091 }
2092 if ( client->pers.connected == CON_DISCONNECTED ) {
2093 continue;
2094 }
2095 if ( client->ps.stats[STAT_HEALTH] <= 0 ) {
2096 continue;
2097 }
2098
2099 // if same team in team game, no sound
2100 // cannot use OnSameTeam as it expects to g_entities, not clients
2101 if ( level.gametype >= GT_TEAM && other->client->sess.sessionTeam == client->sess.sessionTeam ) {
2102 continue;
2103 }
2104
2105 // if too far away, no sound
2106 VectorSubtract( ent->s.pos.trBase, client->ps.origin, delta );
2107 len = VectorNormalize( delta );
2108 if ( len > 192 ) {
2109 continue;
2110 }
2111
2112 // if not facing, no sound
2113 AngleVectors( client->ps.viewangles, forward, NULL, NULL );
2114 if ( DotProduct( delta, forward ) < 0.4 ) {
2115 continue;
2116 }
2117
2118 // if not line of sight, no sound
2119 trap->Trace( &tr, client->ps.origin, NULL, NULL, ent->s.pos.trBase, ENTITYNUM_NONE, CONTENTS_SOLID, qfalse, 0, 0 );
2120 if ( tr.fraction != 1.0 ) {
2121 continue;
2122 }
2123
2124 // anti-reward
2125 client->ps.persistant[PERS_PLAYEREVENTS] ^= PLAYEREVENT_DENIEDREWARD;
2126 }
2127 return RESPAWN_POWERUP;
2128 }
2129
2130 //======================================================================
2131
Pickup_Holdable(gentity_t * ent,gentity_t * other)2132 int Pickup_Holdable( gentity_t *ent, gentity_t *other ) {
2133
2134 other->client->ps.stats[STAT_HOLDABLE_ITEM] = ent->item - bg_itemlist;
2135
2136 other->client->ps.stats[STAT_HOLDABLE_ITEMS] |= (1 << ent->item->giTag);
2137
2138 G_LogWeaponItem(other->s.number, ent->item->giTag);
2139
2140 return adjustRespawnTime(RESPAWN_HOLDABLE, ent->item->giType, ent->item->giTag);
2141 }
2142
2143
2144 //======================================================================
2145
Add_Ammo(gentity_t * ent,int weapon,int count)2146 void Add_Ammo (gentity_t *ent, int weapon, int count)
2147 {
2148 int max = ammoData[weapon].max;
2149
2150 if (ent->client->ps.eFlags & EF_DOUBLE_AMMO)
2151 { // fix: double ammo for siege
2152 max *= 2;
2153 }
2154
2155 if ( ent->client->ps.ammo[weapon] < max )
2156 {
2157 ent->client->ps.ammo[weapon] += count;
2158 if ( ent->client->ps.ammo[weapon] > max )
2159 {
2160 ent->client->ps.ammo[weapon] = max;
2161 }
2162 }
2163 }
2164
Pickup_Ammo(gentity_t * ent,gentity_t * other)2165 int Pickup_Ammo (gentity_t *ent, gentity_t *other)
2166 {
2167 int quantity;
2168
2169 if ( ent->count ) {
2170 quantity = ent->count;
2171 } else {
2172 quantity = ent->item->quantity;
2173 }
2174
2175 if (ent->item->giTag == -1)
2176 { //an ammo_all, give them a bit of everything
2177 if ( level.gametype == GT_SIEGE ) // complaints that siege tech's not giving enough ammo. Does anything else use ammo all?
2178 {
2179 Add_Ammo(other, AMMO_BLASTER, 100);
2180 Add_Ammo(other, AMMO_POWERCELL, 100);
2181 Add_Ammo(other, AMMO_METAL_BOLTS, 100);
2182 Add_Ammo(other, AMMO_ROCKETS, 5);
2183 if (other->client->ps.stats[STAT_WEAPONS] & (1<<WP_DET_PACK))
2184 {
2185 Add_Ammo(other, AMMO_DETPACK, 2);
2186 }
2187 if (other->client->ps.stats[STAT_WEAPONS] & (1<<WP_THERMAL))
2188 {
2189 Add_Ammo(other, AMMO_THERMAL, 2);
2190 }
2191 if (other->client->ps.stats[STAT_WEAPONS] & (1<<WP_TRIP_MINE))
2192 {
2193 Add_Ammo(other, AMMO_TRIPMINE, 2);
2194 }
2195 }
2196 else
2197 {
2198 Add_Ammo(other, AMMO_BLASTER, 50);
2199 Add_Ammo(other, AMMO_POWERCELL, 50);
2200 Add_Ammo(other, AMMO_METAL_BOLTS, 50);
2201 Add_Ammo(other, AMMO_ROCKETS, 2);
2202 }
2203 }
2204 else
2205 {
2206 Add_Ammo (other, ent->item->giTag, quantity);
2207 }
2208
2209 return adjustRespawnTime(RESPAWN_AMMO, ent->item->giType, ent->item->giTag);
2210 }
2211
2212 //======================================================================
2213
2214
Pickup_Weapon(gentity_t * ent,gentity_t * other)2215 int Pickup_Weapon (gentity_t *ent, gentity_t *other) {
2216 int quantity;
2217
2218 if ( ent->count < 0 ) {
2219 quantity = 0; // None for you, sir!
2220 } else {
2221 if ( ent->count ) {
2222 quantity = ent->count;
2223 } else {
2224 quantity = ent->item->quantity;
2225 }
2226
2227 // dropped items and teamplay weapons always have full ammo
2228 if ( ! (ent->flags & FL_DROPPED_ITEM) && level.gametype != GT_TEAM ) {
2229 // respawning rules
2230
2231 // New method: If the player has less than half the minimum, give them the minimum, else add 1/2 the min.
2232
2233 // drop the quantity if the already have over the minimum
2234 if ( other->client->ps.ammo[ ent->item->giTag ] < quantity*0.5 ) {
2235 quantity = quantity - other->client->ps.ammo[ ent->item->giTag ];
2236 } else {
2237 quantity = quantity*0.5; // only add half the value.
2238 }
2239
2240 // Old method: If the player has less than the minimum, give them the minimum, else just add 1.
2241 /*
2242 // drop the quantity if the already have over the minimum
2243 if ( other->client->ps.ammo[ ent->item->giTag ] < quantity ) {
2244 quantity = quantity - other->client->ps.ammo[ ent->item->giTag ];
2245 } else {
2246 quantity = 1; // only add a single shot
2247 }
2248 */
2249 }
2250 }
2251
2252 // add the weapon
2253 other->client->ps.stats[STAT_WEAPONS] |= ( 1 << ent->item->giTag );
2254
2255 //Add_Ammo( other, ent->item->giTag, quantity );
2256 Add_Ammo( other, weaponData[ent->item->giTag].ammoIndex, quantity );
2257
2258 G_LogWeaponPickup(other->s.number, ent->item->giTag);
2259
2260 // team deathmatch has slow weapon respawns
2261 if ( level.gametype == GT_TEAM )
2262 {
2263 return adjustRespawnTime(RESPAWN_TEAM_WEAPON, ent->item->giType, ent->item->giTag);
2264 }
2265
2266 return adjustRespawnTime(g_weaponRespawn.integer, ent->item->giType, ent->item->giTag);
2267 }
2268
2269
2270 //======================================================================
2271
Pickup_Health(gentity_t * ent,gentity_t * other)2272 int Pickup_Health (gentity_t *ent, gentity_t *other) {
2273 int max;
2274 int quantity;
2275
2276 // small and mega healths will go over the max
2277 if ( ent->item->quantity != 5 && ent->item->quantity != 100 ) {
2278 max = other->client->ps.stats[STAT_MAX_HEALTH];
2279 } else {
2280 max = other->client->ps.stats[STAT_MAX_HEALTH] * 2;
2281 }
2282
2283 if ( ent->count ) {
2284 quantity = ent->count;
2285 } else {
2286 quantity = ent->item->quantity;
2287 }
2288
2289 other->health += quantity;
2290
2291 if (other->health > max ) {
2292 other->health = max;
2293 }
2294 other->client->ps.stats[STAT_HEALTH] = other->health;
2295
2296 if ( ent->item->quantity == 100 ) { // mega health respawns slow
2297 return RESPAWN_MEGAHEALTH;
2298 }
2299
2300 return adjustRespawnTime(RESPAWN_HEALTH, ent->item->giType, ent->item->giTag);
2301 }
2302
2303 //======================================================================
2304
Pickup_Armor(gentity_t * ent,gentity_t * other)2305 int Pickup_Armor( gentity_t *ent, gentity_t *other )
2306 {
2307 other->client->ps.stats[STAT_ARMOR] += ent->item->quantity;
2308 if ( other->client->ps.stats[STAT_ARMOR] > other->client->ps.stats[STAT_MAX_HEALTH] * ent->item->giTag )
2309 {
2310 other->client->ps.stats[STAT_ARMOR] = other->client->ps.stats[STAT_MAX_HEALTH] * ent->item->giTag;
2311 }
2312
2313 return adjustRespawnTime(RESPAWN_ARMOR, ent->item->giType, ent->item->giTag);
2314 }
2315
2316 //======================================================================
2317
2318 /*
2319 ===============
2320 RespawnItem
2321 ===============
2322 */
RespawnItem(gentity_t * ent)2323 void RespawnItem( gentity_t *ent ) {
2324 // randomly select from teamed entities
2325 if (ent->team) {
2326 gentity_t *master;
2327 int count;
2328 int choice;
2329
2330 if ( !ent->teammaster ) {
2331 trap->Error( ERR_DROP, "RespawnItem: bad teammaster");
2332 }
2333 master = ent->teammaster;
2334
2335 for (count = 0, ent = master; ent; ent = ent->teamchain, count++)
2336 ;
2337
2338 choice = rand() % count;
2339
2340 for (count = 0, ent = master; count < choice; ent = ent->teamchain, count++)
2341 ;
2342 }
2343
2344 ent->r.contents = CONTENTS_TRIGGER;
2345 //ent->s.eFlags &= ~EF_NODRAW;
2346 ent->s.eFlags &= ~(EF_NODRAW | EF_ITEMPLACEHOLDER);
2347 ent->r.svFlags &= ~SVF_NOCLIENT;
2348 trap->LinkEntity ((sharedEntity_t *)ent);
2349
2350 if ( ent->item->giType == IT_POWERUP ) {
2351 // play powerup spawn sound to all clients
2352 gentity_t *te;
2353
2354 // if the powerup respawn sound should Not be global
2355 if (ent->speed) {
2356 te = G_TempEntity( ent->s.pos.trBase, EV_GENERAL_SOUND );
2357 }
2358 else {
2359 te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_SOUND );
2360 }
2361 te->s.eventParm = G_SoundIndex( "sound/items/respawn1" );
2362 te->r.svFlags |= SVF_BROADCAST;
2363 }
2364
2365 // play the normal respawn sound only to nearby clients
2366 G_AddEvent( ent, EV_ITEM_RESPAWN, 0 );
2367
2368 ent->nextthink = 0;
2369 }
2370
CheckItemCanBePickedUpByNPC(gentity_t * item,gentity_t * pickerupper)2371 qboolean CheckItemCanBePickedUpByNPC( gentity_t *item, gentity_t *pickerupper )
2372 {
2373 if ( (item->flags&FL_DROPPED_ITEM)
2374 && item->activator != &g_entities[0]
2375 && pickerupper->s.number
2376 && pickerupper->s.weapon == WP_NONE
2377 && pickerupper->enemy
2378 && pickerupper->painDebounceTime < level.time
2379 && pickerupper->NPC && pickerupper->NPC->surrenderTime < level.time //not surrendering
2380 && !(pickerupper->NPC->scriptFlags&SCF_FORCED_MARCH) //not being forced to march
2381 /*&& item->item->giTag != INV_SECURITY_KEY*/ )
2382 {//non-player, in combat, picking up a dropped item that does NOT belong to the player and it *not* a security key
2383 if ( level.time - item->s.time < 3000 )//was 5000
2384 {
2385 return qfalse;
2386 }
2387 return qtrue;
2388 }
2389 return qfalse;
2390 }
2391
2392 /*
2393 ===============
2394 Touch_Item
2395 ===============
2396 */
Touch_Item(gentity_t * ent,gentity_t * other,trace_t * trace)2397 void Touch_Item (gentity_t *ent, gentity_t *other, trace_t *trace) {
2398 int respawn;
2399 qboolean predict;
2400
2401 if (ent->genericValue10 > level.time &&
2402 other &&
2403 other->s.number == ent->genericValue11)
2404 { //this is the ent that we don't want to be able to touch us for x seconds
2405 return;
2406 }
2407
2408 if (ent->s.eFlags & EF_ITEMPLACEHOLDER)
2409 {
2410 return;
2411 }
2412
2413 if (ent->s.eFlags & EF_NODRAW)
2414 {
2415 return;
2416 }
2417
2418 if (ent->item->giType == IT_WEAPON &&
2419 ent->s.powerups &&
2420 ent->s.powerups < level.time)
2421 {
2422 ent->s.generic1 = 0;
2423 ent->s.powerups = 0;
2424 }
2425
2426 if (!other->client)
2427 return;
2428 if (other->health < 1)
2429 return; // dead people can't pickup
2430
2431 if (ent->item->giType == IT_POWERUP &&
2432 (ent->item->giTag == PW_FORCE_ENLIGHTENED_LIGHT || ent->item->giTag == PW_FORCE_ENLIGHTENED_DARK))
2433 {
2434 if (ent->item->giTag == PW_FORCE_ENLIGHTENED_LIGHT)
2435 {
2436 if (other->client->ps.fd.forceSide != FORCE_LIGHTSIDE)
2437 {
2438 return;
2439 }
2440 }
2441 else
2442 {
2443 if (other->client->ps.fd.forceSide != FORCE_DARKSIDE)
2444 {
2445 return;
2446 }
2447 }
2448 }
2449
2450 // the same pickup rules are used for client side and server side
2451 if ( !BG_CanItemBeGrabbed( level.gametype, &ent->s, &other->client->ps ) ) {
2452 return;
2453 }
2454
2455
2456 if ( other->client->NPC_class == CLASS_ATST ||
2457 other->client->NPC_class == CLASS_GONK ||
2458 other->client->NPC_class == CLASS_MARK1 ||
2459 other->client->NPC_class == CLASS_MARK2 ||
2460 other->client->NPC_class == CLASS_MOUSE ||
2461 other->client->NPC_class == CLASS_PROBE ||
2462 other->client->NPC_class == CLASS_PROTOCOL ||
2463 other->client->NPC_class == CLASS_R2D2 ||
2464 other->client->NPC_class == CLASS_R5D2 ||
2465 other->client->NPC_class == CLASS_SEEKER ||
2466 other->client->NPC_class == CLASS_REMOTE ||
2467 other->client->NPC_class == CLASS_RANCOR ||
2468 other->client->NPC_class == CLASS_WAMPA ||
2469 //other->client->NPC_class == CLASS_JAWA || //FIXME: in some cases it's okay?
2470 other->client->NPC_class == CLASS_UGNAUGHT || //FIXME: in some cases it's okay?
2471 other->client->NPC_class == CLASS_SENTRY )
2472 {//FIXME: some flag would be better
2473 //droids can't pick up items/weapons!
2474 return;
2475 }
2476
2477 if ( CheckItemCanBePickedUpByNPC( ent, other ) )
2478 {
2479 if ( other->NPC && other->NPC->goalEntity && other->NPC->goalEntity->enemy == ent )
2480 {//they were running to pick me up, they did, so clear goal
2481 other->NPC->goalEntity = NULL;
2482 other->NPC->squadState = SQUAD_STAND_AND_SHOOT;
2483 }
2484 }
2485 else if ( !(ent->spawnflags & ITMSF_ALLOWNPC) )
2486 {// NPCs cannot pick it up
2487 if ( other->s.eType == ET_NPC )
2488 {// Not the player?
2489 qboolean dontGo = qfalse;
2490 if (ent->item->giType == IT_AMMO &&
2491 ent->item->giTag == -1 &&
2492 other->s.NPC_class == CLASS_VEHICLE &&
2493 other->m_pVehicle &&
2494 other->m_pVehicle->m_pVehicleInfo->type == VH_WALKER)
2495 { //yeah, uh, atst gets healed by these things
2496 if (other->maxHealth &&
2497 other->health < other->maxHealth)
2498 {
2499 other->health += 80;
2500 if (other->health > other->maxHealth)
2501 {
2502 other->health = other->maxHealth;
2503 }
2504 G_ScaleNetHealth(other);
2505 dontGo = qtrue;
2506 }
2507 }
2508
2509 if (!dontGo)
2510 {
2511 return;
2512 }
2513 }
2514 }
2515
2516 G_LogPrintf( "Item: %i %s\n", other->s.number, ent->item->classname );
2517
2518 predict = other->client->pers.predictItemPickup;
2519
2520 // call the item-specific pickup function
2521 switch( ent->item->giType ) {
2522 case IT_WEAPON:
2523 respawn = Pickup_Weapon(ent, other);
2524 // predict = qfalse;
2525 predict = qtrue;
2526 break;
2527 case IT_AMMO:
2528 respawn = Pickup_Ammo(ent, other);
2529 if (ent->item->giTag == AMMO_THERMAL || ent->item->giTag == AMMO_TRIPMINE || ent->item->giTag == AMMO_DETPACK)
2530 {
2531 int weapForAmmo = 0;
2532
2533 if (ent->item->giTag == AMMO_THERMAL)
2534 {
2535 weapForAmmo = WP_THERMAL;
2536 }
2537 else if (ent->item->giTag == AMMO_TRIPMINE)
2538 {
2539 weapForAmmo = WP_TRIP_MINE;
2540 }
2541 else
2542 {
2543 weapForAmmo = WP_DET_PACK;
2544 }
2545
2546 if (other && other->client && other->client->ps.ammo[weaponData[weapForAmmo].ammoIndex] > 0 )
2547 {
2548 other->client->ps.stats[STAT_WEAPONS] |= (1 << weapForAmmo);
2549 }
2550 }
2551 // predict = qfalse;
2552 predict = qtrue;
2553 break;
2554 case IT_ARMOR:
2555 respawn = Pickup_Armor(ent, other);
2556 // predict = qfalse;
2557 predict = qtrue;
2558 break;
2559 case IT_HEALTH:
2560 respawn = Pickup_Health(ent, other);
2561 // predict = qfalse;
2562 predict = qtrue;
2563 break;
2564 case IT_POWERUP:
2565 respawn = Pickup_Powerup(ent, other);
2566 predict = qfalse;
2567 // predict = qtrue;
2568 break;
2569 case IT_TEAM:
2570 respawn = Pickup_Team(ent, other);
2571 break;
2572 case IT_HOLDABLE:
2573 respawn = Pickup_Holdable(ent, other);
2574 break;
2575 default:
2576 return;
2577 }
2578
2579 if ( !respawn ) {
2580 return;
2581 }
2582
2583 // play the normal pickup sound
2584 if (predict) {
2585 if (other->client)
2586 {
2587 BG_AddPredictableEventToPlayerstate( EV_ITEM_PICKUP, ent->s.number, &other->client->ps);
2588 }
2589 else
2590 {
2591 G_AddPredictableEvent( other, EV_ITEM_PICKUP, ent->s.number );
2592 }
2593 } else {
2594 G_AddEvent( other, EV_ITEM_PICKUP, ent->s.number );
2595 }
2596
2597 // powerup pickups are global broadcasts
2598 if ( /*ent->item->giType == IT_POWERUP ||*/ ent->item->giType == IT_TEAM) {
2599 // if we want the global sound to play
2600 if (!ent->speed) {
2601 gentity_t *te;
2602
2603 te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_ITEM_PICKUP );
2604 te->s.eventParm = ent->s.modelindex;
2605 te->r.svFlags |= SVF_BROADCAST;
2606 } else {
2607 gentity_t *te;
2608
2609 te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_ITEM_PICKUP );
2610 te->s.eventParm = ent->s.modelindex;
2611 // only send this temp entity to a single client
2612 te->r.svFlags |= SVF_SINGLECLIENT;
2613 te->r.singleClient = other->s.number;
2614 }
2615 }
2616
2617 // fire item targets
2618 G_UseTargets (ent, other);
2619
2620 // wait of -1 will not respawn
2621 if ( ent->wait == -1 ) {
2622 ent->r.svFlags |= SVF_NOCLIENT;
2623 ent->s.eFlags |= EF_NODRAW;
2624 ent->r.contents = 0;
2625 ent->unlinkAfterEvent = qtrue;
2626 return;
2627 }
2628
2629 // non zero wait overrides respawn time
2630 if ( ent->wait ) {
2631 respawn = ent->wait;
2632 }
2633
2634 // random can be used to vary the respawn time
2635 if ( ent->random ) {
2636 respawn += Q_flrand(-1.0f, 1.0f) * ent->random;
2637 if ( respawn < 1 ) {
2638 respawn = 1;
2639 }
2640 }
2641
2642 // dropped items will not respawn
2643 if ( ent->flags & FL_DROPPED_ITEM ) {
2644 ent->freeAfterEvent = qtrue;
2645 }
2646
2647 // picked up items still stay around, they just don't
2648 // draw anything. This allows respawnable items
2649 // to be placed on movers.
2650 if (!(ent->flags & FL_DROPPED_ITEM) && (ent->item->giType==IT_WEAPON || ent->item->giType==IT_POWERUP))
2651 {
2652 ent->s.eFlags |= EF_ITEMPLACEHOLDER;
2653 ent->s.eFlags &= ~EF_NODRAW;
2654 }
2655 else
2656 {
2657 ent->s.eFlags |= EF_NODRAW;
2658 ent->r.svFlags |= SVF_NOCLIENT;
2659 }
2660 ent->r.contents = 0;
2661
2662 if (ent->genericValue9)
2663 { //dropped item, should be removed when picked up
2664 ent->think = G_FreeEntity;
2665 ent->nextthink = level.time;
2666 return;
2667 }
2668
2669 // ZOID
2670 // A negative respawn times means to never respawn this item (but don't
2671 // delete it). This is used by items that are respawned by third party
2672 // events such as ctf flags
2673 if ( respawn <= 0 ) {
2674 ent->nextthink = 0;
2675 ent->think = 0;
2676 } else {
2677 ent->nextthink = level.time + respawn * 1000;
2678 ent->think = RespawnItem;
2679 }
2680 trap->LinkEntity( (sharedEntity_t *)ent );
2681 }
2682
2683
2684 //======================================================================
2685
2686 /*
2687 ================
2688 LaunchItem
2689
2690 Spawns an item and tosses it forward
2691 ================
2692 */
LaunchItem(gitem_t * item,vec3_t origin,vec3_t velocity)2693 gentity_t *LaunchItem( gitem_t *item, vec3_t origin, vec3_t velocity ) {
2694 gentity_t *dropped;
2695
2696 dropped = G_Spawn();
2697
2698 dropped->s.eType = ET_ITEM;
2699 dropped->s.modelindex = item - bg_itemlist; // store item number in modelindex
2700 if (dropped->s.modelindex < 0)
2701 {
2702 dropped->s.modelindex = 0;
2703 }
2704 dropped->s.modelindex2 = 1; // This is non-zero is it's a dropped item
2705
2706 dropped->classname = item->classname;
2707 dropped->item = item;
2708 VectorSet (dropped->r.mins, -ITEM_RADIUS, -ITEM_RADIUS, -ITEM_RADIUS);
2709 VectorSet (dropped->r.maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS);
2710
2711 dropped->r.contents = CONTENTS_TRIGGER;
2712
2713 dropped->touch = Touch_Item;
2714
2715 G_SetOrigin( dropped, origin );
2716 dropped->s.pos.trType = TR_GRAVITY;
2717 dropped->s.pos.trTime = level.time;
2718 VectorCopy( velocity, dropped->s.pos.trDelta );
2719
2720 dropped->flags |= FL_BOUNCE_HALF;
2721 if ((level.gametype == GT_CTF || level.gametype == GT_CTY) && item->giType == IT_TEAM) { // Special case for CTF flags
2722 dropped->think = Team_DroppedFlagThink;
2723 dropped->nextthink = level.time + 30000;
2724 Team_CheckDroppedItem( dropped );
2725
2726 //rww - so bots know
2727 if (dropped->item->giTag == PW_REDFLAG)
2728 {
2729 droppedRedFlag = dropped;
2730 }
2731 else if (dropped->item->giTag == PW_BLUEFLAG)
2732 {
2733 droppedBlueFlag = dropped;
2734 }
2735 } else { // auto-remove after 30 seconds
2736 dropped->think = G_FreeEntity;
2737 dropped->nextthink = level.time + 30000;
2738 }
2739
2740 dropped->flags = FL_DROPPED_ITEM;
2741
2742 if (item->giType == IT_WEAPON || item->giType == IT_POWERUP)
2743 {
2744 dropped->s.eFlags |= EF_DROPPEDWEAPON;
2745 }
2746
2747 vectoangles(velocity, dropped->s.angles);
2748 dropped->s.angles[PITCH] = 0;
2749
2750 if (item->giTag == WP_TRIP_MINE ||
2751 item->giTag == WP_DET_PACK)
2752 {
2753 dropped->s.angles[PITCH] = -90;
2754 }
2755
2756 if (item->giTag != WP_BOWCASTER &&
2757 item->giTag != WP_DET_PACK &&
2758 item->giTag != WP_THERMAL)
2759 {
2760 dropped->s.angles[ROLL] = -90;
2761 }
2762
2763 dropped->physicsObject = qtrue;
2764
2765 trap->LinkEntity ((sharedEntity_t *)dropped);
2766
2767 return dropped;
2768 }
2769
2770 /*
2771 ================
2772 Drop_Item
2773
2774 Spawns an item and tosses it forward
2775 ================
2776 */
Drop_Item(gentity_t * ent,gitem_t * item,float angle)2777 gentity_t *Drop_Item( gentity_t *ent, gitem_t *item, float angle ) {
2778 vec3_t velocity;
2779 vec3_t angles;
2780
2781 VectorCopy( ent->s.apos.trBase, angles );
2782 angles[YAW] += angle;
2783 angles[PITCH] = 0; // always forward
2784
2785 AngleVectors( angles, velocity, NULL, NULL );
2786 VectorScale( velocity, 150, velocity );
2787 velocity[2] += 200 + Q_flrand(-1.0f, 1.0f) * 50;
2788
2789 return LaunchItem( item, ent->s.pos.trBase, velocity );
2790 }
2791
2792
2793 /*
2794 ================
2795 Use_Item
2796
2797 Respawn the item
2798 ================
2799 */
Use_Item(gentity_t * ent,gentity_t * other,gentity_t * activator)2800 void Use_Item( gentity_t *ent, gentity_t *other, gentity_t *activator ) {
2801 RespawnItem( ent );
2802 }
2803
2804 //======================================================================
2805
2806 /*
2807 ================
2808 FinishSpawningItem
2809
2810 Traces down to find where an item should rest, instead of letting them
2811 free fall from their spawn points
2812 ================
2813 */
FinishSpawningItem(gentity_t * ent)2814 void FinishSpawningItem( gentity_t *ent ) {
2815 trace_t tr;
2816 vec3_t dest;
2817 // gitem_t *item;
2818
2819 // VectorSet( ent->r.mins, -ITEM_RADIUS, -ITEM_RADIUS, -ITEM_RADIUS );
2820 // VectorSet( ent->r.maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS );
2821
2822 if (level.gametype == GT_SIEGE)
2823 { //in siege remove all powerups
2824 if (ent->item->giType == IT_POWERUP)
2825 {
2826 G_FreeEntity(ent);
2827 return;
2828 }
2829 }
2830
2831 if (level.gametype != GT_JEDIMASTER)
2832 {
2833 if (HasSetSaberOnly())
2834 {
2835 if (ent->item->giType == IT_AMMO)
2836 {
2837 G_FreeEntity( ent );
2838 return;
2839 }
2840
2841 if (ent->item->giType == IT_HOLDABLE)
2842 {
2843 if (ent->item->giTag == HI_SEEKER ||
2844 ent->item->giTag == HI_SHIELD ||
2845 ent->item->giTag == HI_SENTRY_GUN)
2846 {
2847 G_FreeEntity( ent );
2848 return;
2849 }
2850 }
2851 }
2852 }
2853 else
2854 { //no powerups in jedi master
2855 if (ent->item->giType == IT_POWERUP)
2856 {
2857 G_FreeEntity(ent);
2858 return;
2859 }
2860 }
2861
2862 if (level.gametype == GT_HOLOCRON)
2863 {
2864 if (ent->item->giType == IT_POWERUP)
2865 {
2866 if (ent->item->giTag == PW_FORCE_ENLIGHTENED_LIGHT ||
2867 ent->item->giTag == PW_FORCE_ENLIGHTENED_DARK)
2868 {
2869 G_FreeEntity(ent);
2870 return;
2871 }
2872 }
2873 }
2874
2875 if (g_forcePowerDisable.integer)
2876 { //if force powers disabled, don't add force powerups
2877 if (ent->item->giType == IT_POWERUP)
2878 {
2879 if (ent->item->giTag == PW_FORCE_ENLIGHTENED_LIGHT ||
2880 ent->item->giTag == PW_FORCE_ENLIGHTENED_DARK ||
2881 ent->item->giTag == PW_FORCE_BOON)
2882 {
2883 G_FreeEntity(ent);
2884 return;
2885 }
2886 }
2887 }
2888
2889 if (level.gametype == GT_DUEL || level.gametype == GT_POWERDUEL)
2890 {
2891 if ( ent->item->giType == IT_ARMOR ||
2892 ent->item->giType == IT_HEALTH ||
2893 (ent->item->giType == IT_HOLDABLE && (ent->item->giTag == HI_MEDPAC || ent->item->giTag == HI_MEDPAC_BIG)) )
2894 {
2895 G_FreeEntity(ent);
2896 return;
2897 }
2898 }
2899
2900 if (level.gametype != GT_CTF &&
2901 level.gametype != GT_CTY &&
2902 ent->item->giType == IT_TEAM)
2903 {
2904 int killMe = 0;
2905
2906 switch (ent->item->giTag)
2907 {
2908 case PW_REDFLAG:
2909 killMe = 1;
2910 break;
2911 case PW_BLUEFLAG:
2912 killMe = 1;
2913 break;
2914 case PW_NEUTRALFLAG:
2915 killMe = 1;
2916 break;
2917 default:
2918 break;
2919 }
2920
2921 if (killMe)
2922 {
2923 G_FreeEntity( ent );
2924 return;
2925 }
2926 }
2927
2928 VectorSet (ent->r.mins, -8, -8, -0);
2929 VectorSet (ent->r.maxs, 8, 8, 16);
2930
2931 ent->s.eType = ET_ITEM;
2932 ent->s.modelindex = ent->item - bg_itemlist; // store item number in modelindex
2933 ent->s.modelindex2 = 0; // zero indicates this isn't a dropped item
2934
2935 ent->r.contents = CONTENTS_TRIGGER;
2936 ent->touch = Touch_Item;
2937 // useing an item causes it to respawn
2938 ent->use = Use_Item;
2939
2940 // create a Ghoul2 model if the world model is a glm
2941 /* item = &bg_itemlist[ ent->s.modelindex ];
2942 if (!Q_stricmp(&item->world_model[0][strlen(item->world_model[0]) - 4], ".glm"))
2943 {
2944 trap->G2API_InitGhoul2Model(&ent->s, item->world_model[0], G_ModelIndex(item->world_model[0] ), 0, 0, 0, 0);
2945 ent->s.radius = 60;
2946 }
2947 */
2948 if ( ent->spawnflags & ITMSF_SUSPEND ) {
2949 // suspended
2950 G_SetOrigin( ent, ent->s.origin );
2951 } else {
2952 // drop to floor
2953
2954 //if it is directly even with the floor it will return startsolid, so raise up by 0.1
2955 //and temporarily subtract 0.1 from the z maxs so that going up doesn't push into the ceiling
2956 ent->s.origin[2] += 0.1f;
2957 ent->r.maxs[2] -= 0.1f;
2958
2959 VectorSet( dest, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2] - 4096 );
2960 trap->Trace( &tr, ent->s.origin, ent->r.mins, ent->r.maxs, dest, ent->s.number, MASK_SOLID, qfalse, 0, 0 );
2961 if ( tr.startsolid ) {
2962 trap->Print ("FinishSpawningItem: %s startsolid at %s\n", ent->classname, vtos(ent->s.origin));
2963 G_FreeEntity( ent );
2964 return;
2965 }
2966
2967 //add the 0.1 back after the trace
2968 ent->r.maxs[2] += 0.1f;
2969
2970 // allow to ride movers
2971 ent->s.groundEntityNum = tr.entityNum;
2972
2973 G_SetOrigin( ent, tr.endpos );
2974 }
2975
2976 // team slaves and targeted items aren't present at start
2977 if ( ( ent->flags & FL_TEAMSLAVE ) || ent->targetname ) {
2978 ent->s.eFlags |= EF_NODRAW;
2979 ent->r.contents = 0;
2980 return;
2981 }
2982
2983 // powerups don't spawn in for a while
2984 /*
2985 if ( ent->item->giType == IT_POWERUP ) {
2986 float respawn;
2987
2988 respawn = 45 + Q_flrand(-1.0f, 1.0f) * 15;
2989 ent->s.eFlags |= EF_NODRAW;
2990 ent->r.contents = 0;
2991 ent->nextthink = level.time + respawn * 1000;
2992 ent->think = RespawnItem;
2993 return;
2994 }
2995 */
2996
2997 trap->LinkEntity ((sharedEntity_t *)ent);
2998 }
2999
3000
3001 qboolean itemRegistered[MAX_ITEMS];
3002
3003 /*
3004 ==================
3005 G_CheckTeamItems
3006 ==================
3007 */
G_CheckTeamItems(void)3008 void G_CheckTeamItems( void ) {
3009
3010 // Set up team stuff
3011 Team_InitGame();
3012
3013 if( level.gametype == GT_CTF || level.gametype == GT_CTY ) {
3014 gitem_t *item;
3015
3016 // check for the two flags
3017 item = BG_FindItem( "team_CTF_redflag" );
3018 if ( !item || !itemRegistered[ item - bg_itemlist ] ) {
3019 trap->Print( S_COLOR_YELLOW "WARNING: No team_CTF_redflag in map\n" );
3020 }
3021 item = BG_FindItem( "team_CTF_blueflag" );
3022 if ( !item || !itemRegistered[ item - bg_itemlist ] ) {
3023 trap->Print( S_COLOR_YELLOW "WARNING: No team_CTF_blueflag in map\n" );
3024 }
3025 }
3026 }
3027
3028 /*
3029 ==============
3030 ClearRegisteredItems
3031 ==============
3032 */
ClearRegisteredItems(void)3033 void ClearRegisteredItems( void ) {
3034 memset( itemRegistered, 0, sizeof( itemRegistered ) );
3035
3036 // players always start with the base weapon
3037 RegisterItem( BG_FindItemForWeapon( WP_BRYAR_PISTOL ) );
3038 RegisterItem( BG_FindItemForWeapon( WP_STUN_BATON ) );
3039 RegisterItem( BG_FindItemForWeapon( WP_MELEE ) );
3040 RegisterItem( BG_FindItemForWeapon( WP_SABER ) );
3041
3042 if (level.gametype == GT_SIEGE)
3043 { //kind of cheesy, maybe check if siege class with disp's is gonna be on this map too
3044 G_PrecacheDispensers();
3045 }
3046 }
3047
3048 /*
3049 ===============
3050 RegisterItem
3051
3052 The item will be added to the precache list
3053 ===============
3054 */
RegisterItem(gitem_t * item)3055 void RegisterItem( gitem_t *item ) {
3056 if ( !item ) {
3057 trap->Error( ERR_DROP, "RegisterItem: NULL" );
3058 }
3059 itemRegistered[ item - bg_itemlist ] = qtrue;
3060 }
3061
3062
3063 /*
3064 ===============
3065 SaveRegisteredItems
3066
3067 Write the needed items to a config string
3068 so the client will know which ones to precache
3069 ===============
3070 */
SaveRegisteredItems(void)3071 void SaveRegisteredItems( void ) {
3072 char string[MAX_ITEMS+1];
3073 int i;
3074 int count;
3075
3076 count = 0;
3077 for ( i = 0 ; i < bg_numItems ; i++ ) {
3078 if ( itemRegistered[i] ) {
3079 count++;
3080 string[i] = '1';
3081 } else {
3082 string[i] = '0';
3083 }
3084 }
3085 string[ bg_numItems ] = 0;
3086
3087 // trap->Print( "%i items registered\n", count );
3088 trap->SetConfigstring(CS_ITEMS, string);
3089 }
3090
3091 /*
3092 ============
3093 G_ItemDisabled
3094 ============
3095 */
G_ItemDisabled(gitem_t * item)3096 int G_ItemDisabled( gitem_t *item ) {
3097
3098 char name[128];
3099
3100 Com_sprintf(name, sizeof(name), "disable_%s", item->classname);
3101 return trap->Cvar_VariableIntegerValue( name );
3102 }
3103
3104 /*
3105 ============
3106 G_SpawnItem
3107
3108 Sets the clipping size and plants the object on the floor.
3109
3110 Items can't be immediately dropped to floor, because they might
3111 be on an entity that hasn't spawned yet.
3112 ============
3113 */
G_SpawnItem(gentity_t * ent,gitem_t * item)3114 void G_SpawnItem (gentity_t *ent, gitem_t *item) {
3115 int wDisable = 0;
3116
3117 G_SpawnFloat( "random", "0", &ent->random );
3118 G_SpawnFloat( "wait", "0", &ent->wait );
3119
3120 if (level.gametype == GT_DUEL || level.gametype == GT_POWERDUEL)
3121 {
3122 wDisable = g_duelWeaponDisable.integer;
3123 }
3124 else
3125 {
3126 wDisable = g_weaponDisable.integer;
3127 }
3128
3129 if (item->giType == IT_WEAPON &&
3130 wDisable &&
3131 (wDisable & (1 << item->giTag)))
3132 {
3133 if (level.gametype != GT_JEDIMASTER)
3134 {
3135 G_FreeEntity( ent );
3136 return;
3137 }
3138 }
3139
3140 RegisterItem( item );
3141 if ( G_ItemDisabled(item) )
3142 return;
3143
3144 ent->item = item;
3145 // some movers spawn on the second frame, so delay item
3146 // spawns until the third frame so they can ride trains
3147 ent->nextthink = level.time + FRAMETIME * 2;
3148 ent->think = FinishSpawningItem;
3149
3150 ent->physicsBounce = 0.50; // items are bouncy
3151
3152 if ( item->giType == IT_POWERUP ) {
3153 G_SoundIndex( "sound/items/respawn1" );
3154 G_SpawnFloat( "noglobalsound", "0", &ent->speed);
3155 }
3156 }
3157
3158
3159 /*
3160 ================
3161 G_BounceItem
3162
3163 ================
3164 */
G_BounceItem(gentity_t * ent,trace_t * trace)3165 void G_BounceItem( gentity_t *ent, trace_t *trace ) {
3166 vec3_t velocity;
3167 float dot;
3168 int hitTime;
3169
3170 // reflect the velocity on the trace plane
3171 hitTime = level.previousTime + ( level.time - level.previousTime ) * trace->fraction;
3172 BG_EvaluateTrajectoryDelta( &ent->s.pos, hitTime, velocity );
3173 dot = DotProduct( velocity, trace->plane.normal );
3174 VectorMA( velocity, -2*dot, trace->plane.normal, ent->s.pos.trDelta );
3175
3176 // cut the velocity to keep from bouncing forever
3177 VectorScale( ent->s.pos.trDelta, ent->physicsBounce, ent->s.pos.trDelta );
3178
3179 if ((ent->s.weapon == WP_DET_PACK && ent->s.eType == ET_GENERAL && ent->physicsObject))
3180 { //detpacks only
3181 if (ent->touch)
3182 {
3183 ent->touch(ent, &g_entities[trace->entityNum], trace);
3184 return;
3185 }
3186 }
3187
3188 // check for stop
3189 if ( trace->plane.normal[2] > 0 && ent->s.pos.trDelta[2] < 40 ) {
3190 trace->endpos[2] += 1.0; // make sure it is off ground
3191 SnapVector( trace->endpos );
3192 G_SetOrigin( ent, trace->endpos );
3193 ent->s.groundEntityNum = trace->entityNum;
3194 return;
3195 }
3196
3197 VectorAdd( ent->r.currentOrigin, trace->plane.normal, ent->r.currentOrigin);
3198 VectorCopy( ent->r.currentOrigin, ent->s.pos.trBase );
3199 ent->s.pos.trTime = level.time;
3200
3201 if (ent->s.eType == ET_HOLOCRON ||
3202 (ent->s.shouldtarget && ent->s.eType == ET_GENERAL && ent->physicsObject))
3203 { //holocrons and sentry guns
3204 if (ent->touch)
3205 {
3206 ent->touch(ent, &g_entities[trace->entityNum], trace);
3207 }
3208 }
3209 }
3210
3211
3212 /*
3213 ================
3214 G_RunItem
3215
3216 ================
3217 */
G_RunItem(gentity_t * ent)3218 void G_RunItem( gentity_t *ent ) {
3219 vec3_t origin;
3220 trace_t tr;
3221 int contents;
3222 int mask;
3223
3224 // if groundentity has been set to ENTITYNUM_NONE, it may have been pushed off an edge
3225 if ( ent->s.groundEntityNum == ENTITYNUM_NONE ) {
3226 if ( ent->s.pos.trType != TR_GRAVITY ) {
3227 ent->s.pos.trType = TR_GRAVITY;
3228 ent->s.pos.trTime = level.time;
3229 }
3230 }
3231
3232 if ( ent->s.pos.trType == TR_STATIONARY ) {
3233 // check think function
3234 G_RunThink( ent );
3235 return;
3236 }
3237
3238 // get current position
3239 BG_EvaluateTrajectory( &ent->s.pos, level.time, origin );
3240
3241 // trace a line from the previous position to the current position
3242 if ( ent->clipmask ) {
3243 mask = ent->clipmask;
3244 } else {
3245 mask = MASK_PLAYERSOLID & ~CONTENTS_BODY;//MASK_SOLID;
3246 }
3247 trap->Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin, ent->r.ownerNum, mask, qfalse, 0, 0 );
3248
3249 VectorCopy( tr.endpos, ent->r.currentOrigin );
3250
3251 if ( tr.startsolid ) {
3252
3253 tr.fraction = 0;
3254 }
3255
3256 trap->LinkEntity( (sharedEntity_t *)ent ); // FIXME: avoid this for stationary?
3257
3258 // check think function
3259 G_RunThink( ent );
3260
3261 if ( tr.fraction == 1 ) {
3262 return;
3263 }
3264
3265 // if it is in a nodrop volume, remove it
3266 contents = trap->PointContents( ent->r.currentOrigin, -1 );
3267 if ( contents & CONTENTS_NODROP ) {
3268 if (ent->item && ent->item->giType == IT_TEAM) {
3269 Team_FreeEntity(ent);
3270 } else if(ent->genericValue15 == HI_SENTRY_GUN) {
3271 turret_free(ent);
3272 } else {
3273 G_FreeEntity( ent );
3274 }
3275 return;
3276 }
3277
3278 G_BounceItem( ent, &tr );
3279 }
3280
3281