1 /*
2 Copyright (C) 1997-2001 Id Software, Inc.
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13 See the GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18
19 */
20 // g_utils.c -- misc utility functions for game module
21
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25
26 #include "g_local.h"
27
28
G_ProjectSource(vec3_t point,vec3_t distance,vec3_t forward,vec3_t right,vec3_t result)29 void G_ProjectSource (vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result)
30 {
31 result[0] = point[0] + forward[0] * distance[0] + right[0] * distance[1];
32 result[1] = point[1] + forward[1] * distance[0] + right[1] * distance[1];
33 result[2] = point[2] + forward[2] * distance[0] + right[2] * distance[1] + distance[2];
34 }
35
36
37 /*
38 =============
39 G_Find
40
41 Searches all active entities for the next one that holds
42 the matching string at fieldofs (use the FOFS() macro) in the structure.
43
44 Searches beginning at the edict after from, or the beginning if NULL
45 NULL will be returned if the end of the list is reached.
46
47 =============
48 */
G_Find(edict_t * from,int fieldofs,char * match)49 edict_t *G_Find (edict_t *from, int fieldofs, char *match)
50 {
51 char *s;
52
53 if (!from)
54 from = g_edicts;
55 else
56 from++;
57
58 for ( ; from < &g_edicts[globals.num_edicts] ; from++)
59 {
60 if (!from->inuse)
61 continue;
62 s = *(char **) ((byte *)from + fieldofs);
63 if (!s)
64 continue;
65 if (!Q_strcasecmp (s, match))
66 return from;
67 }
68
69 return NULL;
70 }
71
72
73 /*
74 =================
75 findradius
76
77 Returns entities that have origins within a spherical area
78
79 findradius (origin, radius)
80 =================
81 */
findradius(edict_t * from,vec3_t org,float rad)82 edict_t *findradius (edict_t *from, vec3_t org, float rad)
83 {
84 vec3_t eorg;
85 int j;
86
87 if (!from)
88 from = g_edicts;
89 else
90 from++;
91 for ( ; from < &g_edicts[globals.num_edicts]; from++)
92 {
93 if (!from->inuse)
94 continue;
95 if (from->solid == SOLID_NOT)
96 continue;
97 for (j=0 ; j<3 ; j++)
98 eorg[j] = org[j] - (from->s.origin[j] + (from->mins[j] + from->maxs[j])*0.5);
99 if (VectorLength(eorg) > rad)
100 continue;
101 return from;
102 }
103
104 return NULL;
105 }
106
107
108 /*
109 =============
110 G_PickTarget
111
112 Searches all active entities for the next one that holds
113 the matching string at fieldofs (use the FOFS() macro) in the structure.
114
115 Searches beginning at the edict after from, or the beginning if NULL
116 NULL will be returned if the end of the list is reached.
117
118 =============
119 */
120 #define MAXCHOICES 8
121
G_PickTarget(char * targetname)122 edict_t *G_PickTarget (char *targetname)
123 {
124 edict_t *ent = NULL;
125 int num_choices = 0;
126 edict_t *choice[MAXCHOICES];
127
128 if (!targetname)
129 {
130 gi.dprintf("G_PickTarget called with NULL targetname\n");
131 return NULL;
132 }
133
134 while(1)
135 {
136 ent = G_Find (ent, FOFS(targetname), targetname);
137 if (!ent)
138 break;
139 choice[num_choices++] = ent;
140 if (num_choices == MAXCHOICES)
141 break;
142 }
143
144 if (!num_choices)
145 {
146 gi.dprintf("G_PickTarget: target %s not found\n", targetname);
147 return NULL;
148 }
149
150 return choice[rand() % num_choices];
151 }
152
153
154
Think_Delay(edict_t * ent)155 void Think_Delay (edict_t *ent)
156 {
157 G_UseTargets (ent, ent->activator);
158 G_FreeEdict (ent);
159 }
160
161 /*
162 ==============================
163 G_UseTargets
164
165 the global "activator" should be set to the entity that initiated the firing.
166
167 If self.delay is set, a DelayedUse entity will be created that will actually
168 do the SUB_UseTargets after that many seconds have passed.
169
170 Centerprints any self.message to the activator.
171
172 Search for (string)targetname in all entities that
173 match (string)self.target and call their .use function
174
175 ==============================
176 */
G_UseTargets(edict_t * ent,edict_t * activator)177 void G_UseTargets (edict_t *ent, edict_t *activator)
178 {
179 edict_t *t;
180
181 //
182 // check for a delay
183 //
184 if (ent->delay)
185 {
186 // create a temp object to fire at a later time
187 t = G_Spawn();
188 t->classname = "DelayedUse";
189 t->nextthink = level.time + ent->delay;
190 t->think = Think_Delay;
191 t->activator = activator;
192 if (!activator)
193 gi.dprintf ("Think_Delay with no activator\n");
194 t->message = ent->message;
195 t->target = ent->target;
196 t->killtarget = ent->killtarget;
197 return;
198 }
199
200
201 //
202 // print the message
203 //
204 if ((ent->message) && !(activator->svflags & SVF_MONSTER))
205 {
206 safe_centerprintf (activator, "%s", ent->message);
207 if (ent->noise_index)
208 gi.sound (activator, CHAN_AUTO, ent->noise_index, 1, ATTN_NORM, 0);
209 else
210 gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0);
211 }
212
213 //
214 // kill killtargets
215 //
216 if (ent->killtarget)
217 {
218 t = NULL;
219 while ((t = G_Find (t, FOFS(targetname), ent->killtarget)))
220 {
221 G_FreeEdict (t);
222 if (!ent->inuse)
223 {
224 gi.dprintf("entity was removed while using killtargets\n");
225 return;
226 }
227 }
228 }
229
230 //
231 // fire targets
232 //
233 if (ent->target)
234 {
235 t = NULL;
236 while ((t = G_Find (t, FOFS(targetname), ent->target)))
237 {
238 // doors fire area portals in a specific way
239 if (!Q_strcasecmp(t->classname, "func_areaportal") &&
240 (!Q_strcasecmp(ent->classname, "func_door") || !Q_strcasecmp(ent->classname, "func_door_rotating")))
241 continue;
242
243 if (t == ent)
244 {
245 gi.dprintf ("WARNING: Entity used itself.\n");
246 }
247 else
248 {
249 if (t->use)
250 t->use (t, ent, activator);
251 }
252 if (!ent->inuse)
253 {
254 gi.dprintf("entity was removed while using targets\n");
255 return;
256 }
257 }
258 }
259 }
260
261
262 /*
263 =============
264 TempVector
265
266 This is just a convenience function
267 for making temporary vectors for function calls
268 =============
269 */
tv(float x,float y,float z)270 float *tv (float x, float y, float z)
271 {
272 static int index;
273 static vec3_t vecs[8];
274 float *v;
275
276 // use an array so that multiple tempvectors won't collide
277 // for a while
278 v = vecs[index];
279 index = (index + 1)&7;
280
281 v[0] = x;
282 v[1] = y;
283 v[2] = z;
284
285 return v;
286 }
287
288
289 /*
290 =============
291 VectorToString
292
293 This is just a convenience function
294 for printing vectors
295 =============
296 */
vtos(vec3_t v)297 char *vtos (vec3_t v)
298 {
299 static int index;
300 static char str[8][32];
301 char *s;
302
303 // use an array so that multiple vtos won't collide
304 s = str[index];
305 index = (index + 1)&7;
306
307 Com_sprintf (s, 32, "(%i %i %i)", (int)v[0], (int)v[1], (int)v[2]);
308
309 return s;
310 }
311
312
313 vec3_t VEC_UP = {0, -1, 0};
314 vec3_t MOVEDIR_UP = {0, 0, 1};
315 vec3_t VEC_DOWN = {0, -2, 0};
316 vec3_t MOVEDIR_DOWN = {0, 0, -1};
317
G_SetMovedir(vec3_t angles,vec3_t movedir)318 void G_SetMovedir (vec3_t angles, vec3_t movedir)
319 {
320 if (VectorCompare (angles, VEC_UP))
321 {
322 VectorCopy (MOVEDIR_UP, movedir);
323 }
324 else if (VectorCompare (angles, VEC_DOWN))
325 {
326 VectorCopy (MOVEDIR_DOWN, movedir);
327 }
328 else
329 {
330 AngleVectors (angles, movedir, NULL, NULL);
331 }
332
333 VectorClear (angles);
334 }
335
336
vectoyaw(vec3_t vec)337 float vectoyaw (vec3_t vec)
338 {
339 float yaw;
340
341 if (/*vec[YAW] == 0 &&*/ vec[PITCH] == 0)
342 {
343 yaw = 0;
344 if (vec[YAW] > 0)
345 yaw = 90;
346 else if (vec[YAW] < 0)
347 yaw = -90;
348 }
349 else
350 {
351 yaw = (int) (atan2(vec[YAW], vec[PITCH]) * 180 / M_PI);
352 if (yaw < 0)
353 yaw += 360;
354 }
355
356 return yaw;
357 }
358
359 // void vectoangles (vec3_t value1, vec3_t angles) Moved to q_shared.c
360
G_CopyString(const char * in)361 char *G_CopyString (const char *in)
362 {
363 char *out;
364
365 out = gi.TagMalloc (strlen(in)+1, TAG_LEVEL);
366 strcpy (out, in);
367 return out;
368 }
369
370
G_InitEdict(edict_t * e)371 void G_InitEdict (edict_t *e)
372 {
373 e->inuse = true;
374 e->classname = "noclass";
375 e->gravity = 1.0;
376 e->s.number = e - g_edicts;
377 }
378
379 /*
380 =================
381 G_Spawn
382
383 Either finds a free edict, or allocates a new one.
384 Try to avoid reusing an entity that was recently freed, because it
385 can cause the client to think the entity morphed into something else
386 instead of being removed and recreated, which can cause interpolated
387 angles and bad trails.
388 =================
389 */
G_Spawn(void)390 edict_t *G_Spawn (void)
391 {
392 int i;
393 edict_t *e;
394
395 e = &g_edicts[g_maxclients->integer+1];
396 for ( i=g_maxclients->integer+1 ; i<globals.num_edicts ; i++, e++)
397 {
398 // the first couple seconds of server time can involve a lot of
399 // freeing and allocating, so relax the replacement policy
400 if (!e->inuse && ( e->freetime < 2 || level.time - e->freetime > 0.5 ) )
401 {
402 G_InitEdict (e);
403 return e;
404 }
405 }
406
407 if (i == game.maxentities)
408 gi.error ("ED_Alloc: no free edicts");
409
410 globals.num_edicts++;
411 G_InitEdict (e);
412 return e;
413 }
414
415 /*
416 =================
417 G_FreeEdict
418
419 Marks the edict as free
420 =================
421 */
G_FreeEdict(edict_t * ed)422 void G_FreeEdict (edict_t *ed)
423 {
424 gi.unlinkentity (ed); // unlink from world
425
426 if ((ed - g_edicts) <= (g_maxclients->value + BODY_QUEUE_SIZE))
427 {
428 // gi.dprintf("tried to free special edict\n");
429 return;
430 }
431
432 memset (ed, 0, sizeof(*ed));
433 ed->classname = "freed";
434 ed->freetime = level.time;
435 ed->inuse = false;
436 }
437
438
439 /*
440 ============
441 G_TouchTriggers
442
443 ============
444 */
G_TouchTriggers(edict_t * ent)445 void G_TouchTriggers (edict_t *ent)
446 {
447 int i, num;
448 edict_t *touch[MAX_EDICTS], *hit;
449
450 // dead things don't activate triggers!
451 if ((ent->client || (ent->svflags & SVF_MONSTER)) && (ent->health <= 0))
452 return;
453
454 num = gi.BoxEdicts (ent->absmin, ent->absmax, touch
455 , MAX_EDICTS, AREA_TRIGGERS);
456
457 // be careful, it is possible to have an entity in this
458 // list removed before we get to it (killtriggered)
459 for (i=0 ; i<num ; i++)
460 {
461 hit = touch[i];
462 if (!hit->inuse)
463 continue;
464 if (!hit->touch)
465 continue;
466 hit->touch (hit, ent, NULL, NULL);
467 }
468 }
469
470 /*
471 ============
472 G_TouchSolids
473
474 Call after linking a new trigger in during gameplay
475 to force all entities it covers to immediately touch it
476 ============
477 */
G_TouchSolids(edict_t * ent)478 void G_TouchSolids (edict_t *ent)
479 {
480 int i, num;
481 edict_t *touch[MAX_EDICTS], *hit;
482
483 num = gi.BoxEdicts (ent->absmin, ent->absmax, touch
484 , MAX_EDICTS, AREA_SOLID);
485
486 // be careful, it is possible to have an entity in this
487 // list removed before we get to it (killtriggered)
488 for (i=0 ; i<num ; i++)
489 {
490 hit = touch[i];
491 if (!hit->inuse)
492 continue;
493 if (ent->touch)
494 ent->touch (hit, ent, NULL, NULL);
495 if (!ent->inuse)
496 break;
497 }
498 }
499
500
501
502
503 /*
504 ==============================================================================
505
506 Kill box
507
508 ==============================================================================
509 */
510
511 /*
512 =================
513 KillBox
514
515 Kills all entities that would touch the proposed new positioning
516 of ent. Ent should be unlinked before calling this!
517 =================
518 */
KillBox(edict_t * ent)519 qboolean KillBox (edict_t *ent)
520 {
521 trace_t tr;
522
523 while (1)
524 {
525 tr = gi.trace (ent->s.origin, ent->mins, ent->maxs, ent->s.origin, NULL, MASK_PLAYERSOLID);
526 if (!tr.ent)
527 break;
528
529 // nail it
530 T_Damage (tr.ent, ent, ent, vec3_origin, ent->s.origin, vec3_origin, 100000, 0, DAMAGE_NO_PROTECTION, MOD_TELEFRAG);
531
532 // if we didn't kill it, fail
533 if (tr.ent->solid)
534 return false;
535 }
536
537 return true; // all clear
538 }
539
540
541 /*
542 =============
543 G_CleanPlayerName
544
545 Removes escape characters from a player's name and prepares it
546 to be displayed in green through safe_centerprintf.
547
548 !Note!: dest must be at least PLAYERNAME_SIZE bytes long.
549 (could be PLAYERNAME_GLYPHS + 1 long, but safer to use max. string length)
550 =============
551 */
G_CleanPlayerName(const char * source,char * dest)552 void G_CleanPlayerName ( const char *source, char *dest )
553 {
554 const char *pch_src = source;
555 char *pch_dst = dest;
556 int count = strlen( pch_src );
557
558 memset( pch_dst, 0, PLAYERNAME_SIZE );
559 while ( count > 0 )
560 {
561 if ( Q_IsColorString( pch_src ) )
562 {
563 pch_src += 2;
564 count -= 2;
565 }
566 else
567 {
568 *pch_dst++ = *pch_src | 0x80;
569 ++pch_src;
570 --count;
571 }
572 }
573 }
574
575