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 #include "g_local.h"
23
24
G_ProjectSource(vec3_t point,vec3_t distance,vec3_t forward,vec3_t right,vec3_t result)25 void G_ProjectSource (vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result)
26 {
27 result[0] = point[0] + forward[0] * distance[0] + right[0] * distance[1];
28 result[1] = point[1] + forward[1] * distance[0] + right[1] * distance[1];
29 result[2] = point[2] + forward[2] * distance[0] + right[2] * distance[1] + distance[2];
30 }
31
32
33 /*
34 =============
35 G_Find
36
37 Searches all active entities for the next one that holds
38 the matching string at fieldofs (use the FOFS() macro) in the structure.
39
40 Searches beginning at the edict after from, or the beginning if NULL
41 NULL will be returned if the end of the list is reached.
42
43 =============
44 */
G_Find(edict_t * from,int fieldofs,char * match)45 edict_t *G_Find (edict_t *from, int fieldofs, char *match)
46 {
47 char *s;
48
49 if (!from)
50 from = g_edicts;
51 else
52 from++;
53
54 for ( ; from < &g_edicts[globals.numEdicts] ; from++)
55 {
56 if (!from->inUse)
57 continue;
58 s = *(char **) ((byte *)from + fieldofs);
59 if (!s)
60 continue;
61 if (!Q_stricmp (s, match))
62 return from;
63 }
64
65 return NULL;
66 }
67
68
69 /*
70 =================
71 findradius
72
73 Returns entities that have origins within a spherical area
74
75 findradius (origin, radius)
76 =================
77 */
findradius(edict_t * from,vec3_t org,float rad)78 edict_t *findradius (edict_t *from, vec3_t org, float rad)
79 {
80 vec3_t eorg;
81 int j;
82
83 if (!from)
84 from = g_edicts;
85 else
86 from++;
87 for ( ; from < &g_edicts[globals.numEdicts]; from++)
88 {
89 if (!from->inUse)
90 continue;
91 if (from->solid == SOLID_NOT)
92 continue;
93 for (j=0 ; j<3 ; j++)
94 eorg[j] = org[j] - (from->s.origin[j] + (from->mins[j] + from->maxs[j])*0.5);
95 if (Vec3Length(eorg) > rad)
96 continue;
97 return from;
98 }
99
100 return NULL;
101 }
102
103
104 /*
105 =============
106 G_PickTarget
107
108 Searches all active entities for the next one that holds
109 the matching string at fieldofs (use the FOFS() macro) in the structure.
110
111 Searches beginning at the edict after from, or the beginning if NULL
112 NULL will be returned if the end of the list is reached.
113
114 =============
115 */
116 #define MAXCHOICES 8
117
G_PickTarget(char * targetname)118 edict_t *G_PickTarget (char *targetname)
119 {
120 edict_t *ent = NULL;
121 int num_choices = 0;
122 edict_t *choice[MAXCHOICES];
123
124 if (!targetname)
125 {
126 gi.dprintf("G_PickTarget called with NULL targetname\n");
127 return NULL;
128 }
129
130 while(1)
131 {
132 ent = G_Find (ent, FOFS(targetname), targetname);
133 if (!ent)
134 break;
135 choice[num_choices++] = ent;
136 if (num_choices == MAXCHOICES)
137 break;
138 }
139
140 if (!num_choices)
141 {
142 gi.dprintf("G_PickTarget: target %s not found\n", targetname);
143 return NULL;
144 }
145
146 return choice[rand() % num_choices];
147 }
148
149
150
Think_Delay(edict_t * ent)151 void Think_Delay (edict_t *ent)
152 {
153 G_UseTargets (ent, ent->activator);
154 G_FreeEdict (ent);
155 }
156
157 /*
158 ==============================
159 G_UseTargets
160
161 the global "activator" should be set to the entity that initiated the firing.
162
163 If self.delay is set, a DelayedUse entity will be created that will actually
164 do the SUB_UseTargets after that many seconds have passed.
165
166 Centerprints any self.message to the activator.
167
168 Search for (string)targetname in all entities that
169 match (string)self.target and call their .use function
170
171 ==============================
172 */
G_UseTargets(edict_t * ent,edict_t * activator)173 void G_UseTargets (edict_t *ent, edict_t *activator)
174 {
175 edict_t *t;
176
177 //
178 // check for a delay
179 //
180 if (ent->delay)
181 {
182 // create a temp object to fire at a later time
183 t = G_Spawn();
184 t->classname = "DelayedUse";
185 t->nextthink = level.time + ent->delay;
186 t->think = Think_Delay;
187 t->activator = activator;
188 if (!activator)
189 gi.dprintf ("Think_Delay with no activator\n");
190 t->message = ent->message;
191 t->target = ent->target;
192 t->killtarget = ent->killtarget;
193 return;
194 }
195
196
197 //
198 // print the message
199 //
200 if ((ent->message) && !(activator->svFlags & SVF_MONSTER))
201 {
202 gi.centerprintf (activator, "%s", ent->message);
203 if (ent->noise_index)
204 gi.sound (activator, CHAN_AUTO, ent->noise_index, 1, ATTN_NORM, 0);
205 else
206 gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0);
207 }
208
209 //
210 // kill killtargets
211 //
212 if (ent->killtarget)
213 {
214 t = NULL;
215 while ((t = G_Find (t, FOFS(targetname), ent->killtarget)))
216 {
217 G_FreeEdict (t);
218 if (!ent->inUse)
219 {
220 gi.dprintf("entity was removed while using killtargets\n");
221 return;
222 }
223 }
224 }
225
226 //
227 // fire targets
228 //
229 if (ent->target)
230 {
231 t = NULL;
232 while ((t = G_Find (t, FOFS(targetname), ent->target)))
233 {
234 // doors fire area portals in a specific way
235 if (!Q_stricmp(t->classname, "func_areaportal") &&
236 (!Q_stricmp(ent->classname, "func_door") || !Q_stricmp(ent->classname, "func_door_rotating")))
237 continue;
238
239 if (t == ent)
240 {
241 gi.dprintf ("WARNING: Entity used itself.\n");
242 }
243 else
244 {
245 if (t->use)
246 t->use (t, ent, activator);
247 }
248 if (!ent->inUse)
249 {
250 gi.dprintf("entity was removed while using targets\n");
251 return;
252 }
253 }
254 }
255 }
256
257
258 /*
259 =============
260 TempVector
261
262 This is just a convenience function
263 for making temporary vectors for function calls
264 =============
265 */
tv(float x,float y,float z)266 float *tv (float x, float y, float z)
267 {
268 static int index;
269 static vec3_t vecs[8];
270 float *v;
271
272 // use an array so that multiple tempvectors won't collide
273 // for a while
274 v = vecs[index];
275 index = (index + 1)&7;
276
277 v[0] = x;
278 v[1] = y;
279 v[2] = z;
280
281 return v;
282 }
283
284
285 /*
286 =============
287 VectorToString
288
289 This is just a convenience function
290 for printing vectors
291 =============
292 */
vtos(vec3_t v)293 char *vtos (vec3_t v)
294 {
295 static int index;
296 static char str[8][32];
297 char *s;
298
299 // use an array so that multiple vtos won't collide
300 s = str[index];
301 index = (index + 1)&7;
302
303 Q_snprintfz (s, 32, "(%i %i %i)", (int)v[0], (int)v[1], (int)v[2]);
304
305 return s;
306 }
307
308
309 vec3_t VEC_UP = {0, -1, 0};
310 vec3_t MOVEDIR_UP = {0, 0, 1};
311 vec3_t VEC_DOWN = {0, -2, 0};
312 vec3_t MOVEDIR_DOWN = {0, 0, -1};
313
G_SetMovedir(vec3_t angles,vec3_t movedir)314 void G_SetMovedir (vec3_t angles, vec3_t movedir)
315 {
316 if (Vec3Compare (angles, VEC_UP))
317 {
318 Vec3Copy (MOVEDIR_UP, movedir);
319 }
320 else if (Vec3Compare (angles, VEC_DOWN))
321 {
322 Vec3Copy (MOVEDIR_DOWN, movedir);
323 }
324 else
325 {
326 Angles_Vectors (angles, movedir, NULL, NULL);
327 }
328
329 Vec3Clear (angles);
330 }
331
332
G_CopyString(char * in)333 char *G_CopyString (char *in)
334 {
335 char *out;
336
337 out = gi.TagMalloc (strlen(in)+1, TAG_LEVEL);
338 strcpy (out, in);
339 return out;
340 }
341
342
G_InitEdict(edict_t * e)343 void G_InitEdict (edict_t *e)
344 {
345 e->inUse = qTrue;
346 e->classname = "noclass";
347 e->gravity = 1.0;
348 e->s.number = e - g_edicts;
349 }
350
351 /*
352 =================
353 G_Spawn
354
355 Either finds a free edict, or allocates a new one.
356 Try to avoid reusing an entity that was recently freed, because it
357 can cause the client to think the entity morphed into something else
358 instead of being removed and recreated, which can cause interpolated
359 angles and bad trails.
360 =================
361 */
G_Spawn(void)362 edict_t *G_Spawn (void)
363 {
364 int i;
365 edict_t *e;
366
367 e = &g_edicts[(int)maxclients->floatVal+1];
368 for ( i=maxclients->floatVal+1 ; i<globals.numEdicts ; i++, e++)
369 {
370 // the first couple seconds of server time can involve a lot of
371 // freeing and allocating, so relax the replacement policy
372 if (!e->inUse && ( e->freetime < 2 || level.time - e->freetime > 0.5 ) )
373 {
374 G_InitEdict (e);
375 return e;
376 }
377 }
378
379 if (i == game.maxentities)
380 gi.error ("ED_Alloc: no free edicts");
381
382 globals.numEdicts++;
383 G_InitEdict (e);
384 return e;
385 }
386
387 /*
388 =================
389 G_FreeEdict
390
391 Marks the edict as free
392 =================
393 */
G_FreeEdict(edict_t * ed)394 void G_FreeEdict (edict_t *ed)
395 {
396 gi.unlinkentity (ed); // unlink from world
397
398 if ((ed - g_edicts) <= (maxclients->floatVal + BODY_QUEUE_SIZE))
399 {
400 // gi.dprintf("tried to free special edict\n");
401 return;
402 }
403
404 memset (ed, 0, sizeof(*ed));
405 ed->classname = "freed";
406 ed->freetime = level.time;
407 ed->inUse = qFalse;
408 }
409
410
411 /*
412 ============
413 G_TouchTriggers
414
415 ============
416 */
G_TouchTriggers(edict_t * ent)417 void G_TouchTriggers (edict_t *ent)
418 {
419 int i, num;
420 edict_t *touch[MAX_CS_EDICTS], *hit;
421
422 // dead things don't activate triggers!
423 if ((ent->client || (ent->svFlags & SVF_MONSTER)) && (ent->health <= 0))
424 return;
425
426 num = gi.BoxEdicts (ent->absMin, ent->absMax, touch, MAX_CS_EDICTS, AREA_TRIGGERS);
427
428 // be careful, it is possible to have an entity in this
429 // list removed before we get to it (killtriggered)
430 for (i=0 ; i<num ; i++)
431 {
432 hit = touch[i];
433 if (!hit->inUse)
434 continue;
435 if (!hit->touch)
436 continue;
437 hit->touch (hit, ent, NULL, NULL);
438 }
439 }
440
441 /*
442 ============
443 G_TouchSolids
444
445 Call after linking a new trigger in during gameplay
446 to force all entities it covers to immediately touch it
447 ============
448 */
G_TouchSolids(edict_t * ent)449 void G_TouchSolids (edict_t *ent)
450 {
451 int i, num;
452 edict_t *touch[MAX_CS_EDICTS], *hit;
453
454 num = gi.BoxEdicts (ent->absMin, ent->absMax, touch, MAX_CS_EDICTS, AREA_SOLID);
455
456 // be careful, it is possible to have an entity in this
457 // list removed before we get to it (killtriggered)
458 for (i=0 ; i<num ; i++)
459 {
460 hit = touch[i];
461 if (!hit->inUse)
462 continue;
463 if (ent->touch)
464 ent->touch (hit, ent, NULL, NULL);
465 if (!ent->inUse)
466 break;
467 }
468 }
469
470
471
472
473 /*
474 ==============================================================================
475
476 Kill box
477
478 ==============================================================================
479 */
480
481 /*
482 =================
483 KillBox
484
485 Kills all entities that would touch the proposed new positioning
486 of ent. Ent should be unlinked before calling this!
487 =================
488 */
KillBox(edict_t * ent)489 qBool KillBox (edict_t *ent)
490 {
491 trace_t tr;
492
493 while (1)
494 {
495 tr = gi.trace (ent->s.origin, ent->mins, ent->maxs, ent->s.origin, NULL, MASK_PLAYERSOLID);
496 if (!tr.ent)
497 break;
498
499 // nail it
500 T_Damage (tr.ent, ent, ent, vec3Origin, ent->s.origin, vec3Origin, 100000, 0, DAMAGE_NO_PROTECTION, MOD_TELEFRAG);
501
502 // if we didn't kill it, fail
503 if (tr.ent->solid)
504 return qFalse;
505 }
506
507 return qTrue; // all clear
508 }
509