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