1 // g_utils.c -- misc utility functions for game module
2
3 #include "g_local.h"
4
5
G_ProjectSource(vec3_t point,vec3_t distance,vec3_t forward,vec3_t right,vec3_t result)6 void G_ProjectSource (vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result)
7 {
8 result[0] = point[0] + forward[0] * distance[0] + right[0] * distance[1];
9 result[1] = point[1] + forward[1] * distance[0] + right[1] * distance[1];
10 result[2] = point[2] + forward[2] * distance[0] + right[2] * distance[1] + distance[2];
11 }
12
G_ProjectSource2(vec3_t point,vec3_t distance,vec3_t forward,vec3_t right,vec3_t up,vec3_t result)13 void G_ProjectSource2 (vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t up, vec3_t result)
14 {
15 result[0] = point[0] + forward[0] * distance[0] + right[0] * distance[1] + up[0] * distance[2];
16 result[1] = point[1] + forward[1] * distance[0] + right[1] * distance[1] + up[1] * distance[2];
17 result[2] = point[2] + forward[2] * distance[0] + right[2] * distance[1] + up[2] * distance[2];
18 }
19
20 /*
21 =============
22 G_Find
23
24 Searches all active entities for the next one that holds
25 the matching string at fieldofs (use the FOFS() macro) in the structure.
26
27 Searches beginning at the edict after from, or the beginning if NULL
28 NULL will be returned if the end of the list is reached.
29
30 =============
31 */
G_Find(edict_t * from,int fieldofs,char * match)32 edict_t *G_Find (edict_t *from, int fieldofs, char *match)
33 {
34 char *s;
35
36 if (!from)
37 from = g_edicts;
38 else
39 from++;
40
41 for ( ; from < &g_edicts[globals.num_edicts] ; from++)
42 {
43 if (!from->inuse)
44 continue;
45 s = *(char **) ((byte *)from + fieldofs);
46 if (!s)
47 continue;
48 if (!Q_stricmp (s, match))
49 return from;
50 }
51
52 return NULL;
53 }
54
55
56 /*
57 =================
58 findradius
59
60 Returns entities that have origins within a spherical area
61
62 findradius (origin, radius)
63 =================
64 */
findradius(edict_t * from,vec3_t org,float rad)65 edict_t *findradius (edict_t *from, vec3_t org, float rad)
66 {
67 vec3_t eorg;
68 int j;
69
70 if (!from)
71 from = g_edicts;
72 else
73 from++;
74 for ( ; from < &g_edicts[globals.num_edicts]; from++)
75 {
76 if (!from->inuse)
77 continue;
78 if (from->solid == SOLID_NOT)
79 continue;
80 for (j=0 ; j<3 ; j++)
81 eorg[j] = org[j] - (from->s.origin[j] + (from->mins[j] + from->maxs[j])*0.5);
82 if (VectorLength(eorg) > rad)
83 continue;
84 return from;
85 }
86
87 return NULL;
88 }
89
90 /*
91 =================
92 findradius2
93
94 Returns entities that have origins within a spherical area
95
96 ROGUE - tweaks for performance for tesla specific code
97 only returns entities that can be damaged
98 only returns entities that are SVF_DAMAGEABLE
99
100 findradius2 (origin, radius)
101 =================
102 */
findradius2(edict_t * from,vec3_t org,float rad)103 edict_t *findradius2 (edict_t *from, vec3_t org, float rad)
104 {
105 // rad must be positive
106 vec3_t eorg;
107 int j;
108
109 if (!from)
110 from = g_edicts;
111 else
112 from++;
113 for ( ; from < &g_edicts[globals.num_edicts]; from++)
114 {
115 if (!from->inuse)
116 continue;
117 if (from->solid == SOLID_NOT)
118 continue;
119 if (!from->takedamage)
120 continue;
121 if (!(from->svflags & SVF_DAMAGEABLE))
122 continue;
123 for (j=0 ; j<3 ; j++)
124 eorg[j] = org[j] - (from->s.origin[j] + (from->mins[j] + from->maxs[j])*0.5);
125 if (VectorLength(eorg) > rad)
126 continue;
127 return from;
128 }
129
130 return NULL;
131 }
132
133
134 /*
135 =============
136 G_PickTarget
137
138 Searches all active entities for the next one that holds
139 the matching string at fieldofs (use the FOFS() macro) in the structure.
140
141 Searches beginning at the edict after from, or the beginning if NULL
142 NULL will be returned if the end of the list is reached.
143
144 =============
145 */
146 #define MAXCHOICES 8
147
G_PickTarget(char * targetname)148 edict_t *G_PickTarget (char *targetname)
149 {
150 edict_t *ent = NULL;
151 int num_choices = 0;
152 edict_t *choice[MAXCHOICES];
153
154 if (!targetname)
155 {
156 gi.dprintf("G_PickTarget called with NULL targetname\n");
157 return NULL;
158 }
159
160 while(1)
161 {
162 ent = G_Find (ent, FOFS(targetname), targetname);
163 if (!ent)
164 break;
165 choice[num_choices++] = ent;
166 if (num_choices == MAXCHOICES)
167 break;
168 }
169
170 if (!num_choices)
171 {
172 gi.dprintf("G_PickTarget: target %s not found\n", targetname);
173 return NULL;
174 }
175
176 return choice[rand() % num_choices];
177 }
178
179
180
Think_Delay(edict_t * ent)181 void Think_Delay (edict_t *ent)
182 {
183 G_UseTargets (ent, ent->activator);
184 G_FreeEdict (ent);
185 }
186
187 /*
188 ==============================
189 G_UseTargets
190
191 the global "activator" should be set to the entity that initiated the firing.
192
193 If self.delay is set, a DelayedUse entity will be created that will actually
194 do the SUB_UseTargets after that many seconds have passed.
195
196 Centerprints any self.message to the activator.
197
198 Search for (string)targetname in all entities that
199 match (string)self.target and call their .use function
200
201 ==============================
202 */
G_UseTargets(edict_t * ent,edict_t * activator)203 void G_UseTargets (edict_t *ent, edict_t *activator)
204 {
205 edict_t *t;
206 edict_t *master;
207 qboolean done = false;
208
209 //
210 // check for a delay
211 //
212 if (ent->delay)
213 {
214 // create a temp object to fire at a later time
215 t = G_Spawn();
216 t->classname = "DelayedUse";
217 t->nextthink = level.time + ent->delay;
218 t->think = Think_Delay;
219 t->activator = activator;
220 if (!activator)
221 gi.dprintf ("Think_Delay with no activator\n");
222 t->message = ent->message;
223 t->target = ent->target;
224 t->killtarget = ent->killtarget;
225 return;
226 }
227
228
229 //
230 // print the message
231 //
232 if ((ent->message) && !(activator->svflags & SVF_MONSTER))
233 {
234 #ifdef WITH_ACEBOT
235 safe_centerprintf
236 #else
237 gi.centerprintf
238 #endif
239 (activator, "%s", ent->message);
240 if (ent->noise_index)
241 gi.sound (activator, CHAN_AUTO, ent->noise_index, 1, ATTN_NORM, 0);
242 else
243 gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0);
244 }
245
246 //
247 // kill killtargets
248 //
249 if (ent->killtarget)
250 {
251 t = NULL;
252 while ((t = G_Find (t, FOFS(targetname), ent->killtarget)))
253 {
254 // PMM - if this entity is part of a train, cleanly remove it
255 if (t->flags & FL_TEAMSLAVE)
256 {
257 // if ((g_showlogic) && (g_showlogic->value))
258 // gi.dprintf ("Removing %s from train!\n", t->classname);
259
260 if (t->teammaster)
261 {
262 master = t->teammaster;
263 while (!done)
264 {
265 if (master->teamchain == t)
266 {
267 master->teamchain = t->teamchain;
268 done = true;
269 }
270 master = master->teamchain;
271 if (!master)
272 {
273 // if ((g_showlogic) && (g_showlogic->value))
274 // gi.dprintf ("Couldn't find myself in master's chain, ignoring!\n");
275 }
276 }
277 }
278 else
279 {
280 // if ((g_showlogic) && (g_showlogic->value))
281 // gi.dprintf ("No master to free myself from, ignoring!\n");
282 }
283 }
284 // PMM
285 G_FreeEdict (t);
286 if (!ent->inuse)
287 {
288 gi.dprintf("entity was removed while using killtargets\n");
289 return;
290 }
291 }
292 }
293
294 //
295 // fire targets
296 //
297 if (ent->target)
298 {
299 t = NULL;
300 while ((t = G_Find (t, FOFS(targetname), ent->target)))
301 {
302 // doors fire area portals in a specific way
303 if (!Q_stricmp(t->classname, "func_areaportal") &&
304 (!Q_stricmp(ent->classname, "func_door") || !Q_stricmp(ent->classname, "func_door_rotating")))
305 continue;
306
307 if (t == ent)
308 {
309 gi.dprintf ("WARNING: Entity used itself.\n");
310 }
311 else
312 {
313 if (t->use)
314 t->use (t, ent, activator);
315 }
316 if (!ent->inuse)
317 {
318 gi.dprintf("entity was removed while using targets\n");
319 return;
320 }
321 }
322 }
323 }
324
325
326 /*
327 =============
328 TempVector
329
330 This is just a convenience function
331 for making temporary vectors for function calls
332 =============
333 */
tv(float x,float y,float z)334 float *tv (float x, float y, float z)
335 {
336 static int index;
337 static vec3_t vecs[8];
338 float *v;
339
340 // use an array so that multiple tempvectors won't collide
341 // for a while
342 v = vecs[index];
343 index = (index + 1)&7;
344
345 v[0] = x;
346 v[1] = y;
347 v[2] = z;
348
349 return v;
350 }
351
352
353 /*
354 =============
355 VectorToString
356
357 This is just a convenience function
358 for printing vectors
359 =============
360 */
vtos(vec3_t v)361 char *vtos (vec3_t v)
362 {
363 static int index;
364 static char str[8][32];
365 char *s;
366
367 // use an array so that multiple vtos won't collide
368 s = str[index];
369 index = (index + 1)&7;
370
371 Com_sprintf (s, 32, "(%i %i %i)", (int)v[0], (int)v[1], (int)v[2]);
372
373 return s;
374 }
375
376
377 vec3_t VEC_UP = {0, -1, 0};
378 vec3_t MOVEDIR_UP = {0, 0, 1};
379 vec3_t VEC_DOWN = {0, -2, 0};
380 vec3_t MOVEDIR_DOWN = {0, 0, -1};
381
G_SetMovedir(vec3_t angles,vec3_t movedir)382 void G_SetMovedir (vec3_t angles, vec3_t movedir)
383 {
384 if (VectorCompare (angles, VEC_UP))
385 {
386 VectorCopy (MOVEDIR_UP, movedir);
387 }
388 else if (VectorCompare (angles, VEC_DOWN))
389 {
390 VectorCopy (MOVEDIR_DOWN, movedir);
391 }
392 else
393 {
394 AngleVectors (angles, movedir, NULL, NULL);
395 }
396
397 VectorClear (angles);
398 }
399
400
vectoyaw(vec3_t vec)401 float vectoyaw (vec3_t vec)
402 {
403 float yaw;
404
405 // PMM - fixed to correct for pitch of 0
406 if (/*vec[YAW] == 0 &&*/ vec[PITCH] == 0)
407 if (vec[YAW] == 0)
408 yaw = 0;
409 else if (vec[YAW] > 0)
410 yaw = 90;
411 else
412 yaw = 270;
413 else
414 {
415 yaw = (int) (atan2(vec[YAW], vec[PITCH]) * 180 / M_PI);
416 if (yaw < 0)
417 yaw += 360;
418 }
419
420 return yaw;
421 }
422
vectoyaw2(vec3_t vec)423 float vectoyaw2 (vec3_t vec)
424 {
425 float yaw;
426
427 // PMM - fixed to correct for pitch of 0
428 if (/*vec[YAW] == 0 &&*/ vec[PITCH] == 0)
429 if (vec[YAW] == 0)
430 yaw = 0;
431 else if (vec[YAW] > 0)
432 yaw = 90;
433 else
434 yaw = 270;
435 else
436 {
437 yaw = (atan2(vec[YAW], vec[PITCH]) * 180 / M_PI);
438 if (yaw < 0)
439 yaw += 360;
440 }
441
442 return yaw;
443 }
444
445
vectoangles(vec3_t value1,vec3_t angles)446 void vectoangles (vec3_t value1, vec3_t angles)
447 {
448 float forward;
449 float yaw, pitch;
450
451 if (value1[1] == 0 && value1[0] == 0)
452 {
453 yaw = 0;
454 if (value1[2] > 0)
455 pitch = 90;
456 else
457 pitch = 270;
458 }
459 else
460 {
461 // PMM - fixed to correct for pitch of 0
462 if (value1[0])
463 yaw = (int) (atan2(value1[1], value1[0]) * 180 / M_PI);
464 else if (value1[1] > 0)
465 yaw = 90;
466 else
467 yaw = 270;
468 if (yaw < 0)
469 yaw += 360;
470
471 forward = sqrt (value1[0]*value1[0] + value1[1]*value1[1]);
472 pitch = (int) (atan2(value1[2], forward) * 180 / M_PI);
473 if (pitch < 0)
474 pitch += 360;
475 }
476
477 angles[PITCH] = -pitch;
478 angles[YAW] = yaw;
479 angles[ROLL] = 0;
480 }
481
vectoangles2(vec3_t value1,vec3_t angles)482 void vectoangles2 (vec3_t value1, vec3_t angles)
483 {
484 float forward;
485 float yaw, pitch;
486
487 if (value1[1] == 0 && value1[0] == 0)
488 {
489 yaw = 0;
490 if (value1[2] > 0)
491 pitch = 90;
492 else
493 pitch = 270;
494 }
495 else
496 {
497 // PMM - fixed to correct for pitch of 0
498 if (value1[0])
499 yaw = (atan2(value1[1], value1[0]) * 180 / M_PI);
500 else if (value1[1] > 0)
501 yaw = 90;
502 else
503 yaw = 270;
504
505 if (yaw < 0)
506 yaw += 360;
507
508 forward = sqrt (value1[0]*value1[0] + value1[1]*value1[1]);
509 pitch = (atan2(value1[2], forward) * 180 / M_PI);
510 if (pitch < 0)
511 pitch += 360;
512 }
513
514 angles[PITCH] = -pitch;
515 angles[YAW] = yaw;
516 angles[ROLL] = 0;
517 }
518
G_CopyString(char * in)519 char *G_CopyString (char *in)
520 {
521 char *out;
522
523 out = gi.TagMalloc (strlen(in)+1, TAG_LEVEL);
524 strcpy (out, in);
525 return out;
526 }
527
528
G_InitEdict(edict_t * e)529 void G_InitEdict (edict_t *e)
530 {
531 // ROGUE
532 // FIXME -
533 // this fixes a bug somewhere that is settling "nextthink" for an entity that has
534 // already been released. nextthink is being set to FRAMETIME after level.time,
535 // since freetime = nextthink - 0.1
536 if (e->nextthink)
537 {
538 // if ((g_showlogic) && (g_showlogic->value))
539 // gi.dprintf ("G_SPAWN: Fixed bad nextthink time\n");
540 e->nextthink = 0;
541 }
542 // ROGUE
543
544 e->inuse = true;
545 e->classname = "noclass";
546 e->gravity = 1.0;
547 e->s.number = e - g_edicts;
548
549 //PGM - do this before calling the spawn function so it can be overridden.
550 #ifdef ROGUE_GRAVITY
551 e->gravityVector[0] = 0.0;
552 e->gravityVector[1] = 0.0;
553 e->gravityVector[2] = -1.0;
554 #endif
555 //PGM
556 }
557
558 /*
559 =================
560 G_Spawn
561
562 Either finds a free edict, or allocates a new one.
563 Try to avoid reusing an entity that was recently freed, because it
564 can cause the client to think the entity morphed into something else
565 instead of being removed and recreated, which can cause interpolated
566 angles and bad trails.
567 =================
568 */
G_Spawn(void)569 edict_t *G_Spawn (void)
570 {
571 int i;
572 edict_t *e;
573
574 e = &g_edicts[(int)maxclients->value+1];
575 for ( i=maxclients->value+1 ; i<globals.num_edicts ; i++, e++)
576 {
577 // the first couple seconds of server time can involve a lot of
578 // freeing and allocating, so relax the replacement policy
579 if (!e->inuse && ( e->freetime < 2 || level.time - e->freetime > 0.5 ) )
580 {
581 G_InitEdict (e);
582 return e;
583 }
584 }
585
586 if (i == game.maxentities)
587 gi.error ("ED_Alloc: no free edicts");
588
589 globals.num_edicts++;
590 G_InitEdict (e);
591 return e;
592 }
593
594 /*
595 =================
596 G_FreeEdict
597
598 Marks the edict as free
599 =================
600 */
G_FreeEdict(edict_t * ed)601 void G_FreeEdict (edict_t *ed)
602 {
603 gi.unlinkentity (ed); // unlink from world
604
605 if ((ed - g_edicts) <= (maxclients->value + BODY_QUEUE_SIZE))
606 {
607 // gi.dprintf("tried to free special edict\n");
608 return;
609 }
610
611 memset (ed, 0, sizeof(*ed));
612 ed->classname = "freed";
613 ed->freetime = level.time;
614 ed->inuse = false;
615 }
616
617
618 /*
619 ============
620 G_TouchTriggers
621
622 ============
623 */
G_TouchTriggers(edict_t * ent)624 void G_TouchTriggers (edict_t *ent)
625 {
626 int i, num;
627 edict_t *touch[MAX_EDICTS], *hit;
628
629 // dead things don't activate triggers!
630 if ((ent->client || (ent->svflags & SVF_MONSTER)) && (ent->health <= 0))
631 return;
632
633 num = gi.BoxEdicts (ent->absmin, ent->absmax, touch
634 , MAX_EDICTS, AREA_TRIGGERS);
635
636 // be careful, it is possible to have an entity in this
637 // list removed before we get to it (killtriggered)
638 for (i=0 ; i<num ; i++)
639 {
640 hit = touch[i];
641 if (!hit->inuse)
642 continue;
643 if (!hit->touch)
644 continue;
645 hit->touch (hit, ent, NULL, NULL);
646 }
647 }
648
649 /*
650 ============
651 G_TouchSolids
652
653 Call after linking a new trigger in during gameplay
654 to force all entities it covers to immediately touch it
655 ============
656 */
G_TouchSolids(edict_t * ent)657 void G_TouchSolids (edict_t *ent)
658 {
659 int i, num;
660 edict_t *touch[MAX_EDICTS], *hit;
661
662 num = gi.BoxEdicts (ent->absmin, ent->absmax, touch
663 , MAX_EDICTS, AREA_SOLID);
664
665 // be careful, it is possible to have an entity in this
666 // list removed before we get to it (killtriggered)
667 for (i=0 ; i<num ; i++)
668 {
669 hit = touch[i];
670 if (!hit->inuse)
671 continue;
672 if (ent->touch)
673 ent->touch (hit, ent, NULL, NULL);
674 if (!ent->inuse)
675 break;
676 }
677 }
678
679
680
681
682 /*
683 ==============================================================================
684
685 Kill box
686
687 ==============================================================================
688 */
689
690 /*
691 =================
692 KillBox
693
694 Kills all entities that would touch the proposed new positioning
695 of ent. Ent should be unlinked before calling this!
696 =================
697 */
KillBox(edict_t * ent)698 qboolean KillBox (edict_t *ent)
699 {
700 trace_t tr;
701
702 while (1)
703 {
704 tr = gi.trace (ent->s.origin, ent->mins, ent->maxs, ent->s.origin, NULL, MASK_PLAYERSOLID);
705 if (!tr.ent)
706 break;
707
708 // nail it
709 T_Damage (tr.ent, ent, ent, vec3_origin, ent->s.origin, vec3_origin, 100000, 0, DAMAGE_NO_PROTECTION, MOD_TELEFRAG);
710
711 // if we didn't kill it, fail
712 if (tr.ent->solid)
713 return false;
714 }
715
716 return true; // all clear
717 }
718
GameDirRelativePath(char * filename,char * output)719 void GameDirRelativePath(char *filename, char *output)
720 {
721 cvar_t *basedir, *gamedir;
722
723 basedir = gi.cvar("basedir", "", 0);
724 gamedir = gi.cvar("gamedir", "", 0);
725 if(strlen(gamedir->string))
726 sprintf(output,"%s/%s/%s",basedir->string,gamedir->string,filename);
727 else
728 sprintf(output,"%s/%s",basedir->string,filename);
729 }
730