1 /** @file p_actor.cpp Common code relating to mobj management.
2 *
3 * @authors Copyright © 2003-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4 * @authors Copyright © 2006-2013 Daniel Swanson <danij@dengine.net>
5 *
6 * @par License
7 * GPL: http://www.gnu.org/licenses/gpl.html
8 *
9 * <small>This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by the
11 * Free Software Foundation; either version 2 of the License, or (at your
12 * option) any later version. This program is distributed in the hope that it
13 * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
15 * Public License for more details. You should have received a copy of the GNU
16 * General Public License along with this program; if not, write to the Free
17 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18 * 02110-1301 USA</small>
19 */
20
21 #include <cstdio>
22 #include <cstring>
23
24 #include "common.h"
25 #include "gamesession.h"
26 #include "p_tick.h"
27 #include "p_actor.h"
28
29 #include "p_actor.h"
30
31 #if __JDOOM64__
32 # define RESPAWNTICS (4 * TICSPERSEC)
33 #else
34 # define RESPAWNTICS (30 * TICSPERSEC)
35 #endif
36
37 typedef struct spawnqueuenode_s {
38 int startTime;
39 int minTics; ///< Minimum number of tics before respawn.
40 void (*callback) (mobj_t *mo, void *context);
41 void* context;
42
43 coord_t pos[3];
44 angle_t angle;
45 mobjtype_t type;
46 int spawnFlags; ///< MSF_* flags
47
48 struct spawnqueuenode_s *next;
49 } spawnqueuenode_t;
50
51 static spawnqueuenode_t *spawnQueueHead, *unusedNodes;
52
P_SpawnTelefog(mobj_t * mo,void *)53 void P_SpawnTelefog(mobj_t *mo, void * /*context*/)
54 {
55 #if __JDOOM__ || __JDOOM64__
56 S_StartSound(SFX_ITMBK, mo);
57 #else
58 S_StartSound(SFX_RESPAWN, mo);
59 #endif
60
61 # if __JDOOM64__
62 mo->translucency = 255;
63 mo->spawnFadeTics = 0;
64 mo->intFlags |= MIF_FADE;
65 # elif __JDOOM__
66 // Spawn the item teleport fog at the new spot.
67 P_SpawnMobj(MT_IFOG, mo->origin, mo->angle, 0);
68 # endif
69 }
70
P_MobjRemove(mobj_t * mo,dd_bool noRespawn)71 void P_MobjRemove(mobj_t *mo, dd_bool noRespawn)
72 {
73 #if !defined(__JDOOM__) && !defined(__JDOOM64__)
74 DENG_UNUSED(noRespawn);
75 #endif
76
77 if (mo->ddFlags & DDMF_REMOTE)
78 goto justDoIt;
79
80 #if __JDOOM__ || __JDOOM64__
81 if (!noRespawn)
82 {
83 if (
84 # if __JDOOM__
85 // Only respawn items in deathmatch 2 and optionally in coop.
86 (gfw_Rule(deathmatch) == 2 ||
87 (cfg.coopRespawnItems && IS_NETGAME && !gfw_Rule(deathmatch))) &&
88 # endif /*#elif __JDOOM64__
89 (spot->flags & MTF_RESPAWN) &&
90 # endif*/
91 (mo->flags & MF_SPECIAL) && !(mo->flags & MF_DROPPED)
92 # if __JDOOM__ || __JDOOM64__
93 && (mo->type != MT_INV) && (mo->type != MT_INS)
94 # endif
95 )
96 {
97 P_DeferSpawnMobj3fv(RESPAWNTICS, mobjtype_t(mo->type),
98 mo->spawnSpot.origin, mo->spawnSpot.angle,
99 mo->spawnSpot.flags, P_SpawnTelefog, NULL);
100 }
101 }
102 #endif
103
104 #if __JHEXEN__
105 if ((mo->flags & MF_COUNTKILL) && (mo->flags & MF_CORPSE))
106 {
107 P_RemoveCorpseInQueue(mo);
108 }
109
110 P_MobjRemoveFromTIDList(mo);
111 #endif
112
113 justDoIt:
114 Mobj_Destroy(mo);
115 }
116
P_MobjLink(struct mobj_s * mobj)117 void P_MobjLink(struct mobj_s *mobj)
118 {
119 DENG_ASSERT(mobj != 0);
120 Mobj_Link(mobj, MLF_BLOCKMAP | (!(mobj->flags & MF_NOSECTOR)? MLF_SECTOR : 0));
121 }
122
P_MobjUnlink(struct mobj_s * mobj)123 void P_MobjUnlink(struct mobj_s *mobj)
124 {
125 Mobj_Unlink(mobj);
126 }
127
P_MobjSetSRVO(mobj_t * mo,coord_t stepx,coord_t stepy)128 void P_MobjSetSRVO(mobj_t *mo, coord_t stepx, coord_t stepy)
129 {
130 DENG_ASSERT(mo != 0);
131 mo->srvo[VX] = -stepx;
132 mo->srvo[VY] = -stepy;
133 }
134
P_MobjSetSRVOZ(mobj_t * mo,coord_t stepz)135 void P_MobjSetSRVOZ(mobj_t *mo, coord_t stepz)
136 {
137 DENG_ASSERT(mo != 0);
138 mo->srvo[VZ] = -stepz;
139 }
140
P_MobjAngleSRVOTicker(mobj_t * mo)141 void P_MobjAngleSRVOTicker(mobj_t *mo)
142 {
143 #define MIN_STEP (10 * ANGLE_1) >> 16 ///< Degrees per tic
144 #define MAX_STEP ANG90 >> 16
145
146 DENG_ASSERT(mo != 0);
147
148 // Check requirements.
149 if(mo->flags & MF_MISSILE || !(mo->flags & MF_COUNTKILL))
150 {
151 mo->visAngle = mo->angle >> 16;
152 return; // This is not for us.
153 }
154
155 short target = mo->angle >> 16;
156 short diff = target - mo->visAngle;
157
158 short step = 0;
159 if(mo->turnTime)
160 {
161 if(mo->tics) step = de::abs(diff) / mo->tics;
162 else step = de::abs(diff);
163
164 if(!step) step = 1;
165 }
166 else
167 {
168 // Calculate a good step size.
169 // Thing height and diff taken into account.
170 int hgt = (int) mo->height;
171 hgt = MINMAX_OF(30, hgt, 60);
172
173 int lstep = de::abs(diff) * 8 / hgt;
174 lstep = MINMAX_OF(MIN_STEP, lstep, MAX_STEP);
175
176 step = lstep;
177 }
178
179 // Do the step.
180 if(de::abs(diff) <= step)
181 mo->visAngle = target;
182 else if(diff > 0)
183 mo->visAngle += step;
184 else if(diff < 0)
185 mo->visAngle -= step;
186
187 #undef MAX_STEP
188 #undef MIN_STEP
189 }
190
P_MobjClearSRVO(mobj_t * mo)191 void P_MobjClearSRVO(mobj_t *mo)
192 {
193 DENG_ASSERT(mo != 0);
194 std::memset(mo->srvo, 0, sizeof(mo->srvo));
195 }
196
P_MobjIsCamera(mobj_t const * mo)197 dd_bool P_MobjIsCamera(mobj_t const *mo)
198 {
199 // Client mobjs do not have thinkers and thus cannot be cameras.
200 return (mo && mo->thinker.function && mo->player &&
201 (mo->player->plr->flags & DDPF_CAMERA));
202 }
203
Mobj_IsCrunchable(mobj_t * mobj)204 dd_bool Mobj_IsCrunchable(mobj_t *mobj)
205 {
206 DENG_ASSERT(mobj != 0);
207
208 #if __JDOOM__ || __JDOOM64__
209 return mobj->health <= 0 && (cfg.gibCrushedNonBleeders || !(mobj->flags & MF_NOBLOOD));
210 #elif __JHEXEN__
211 return mobj->health <= 0 && (mobj->flags & MF_CORPSE) != 0;
212 #else
213 return mobj->health <= 0;
214 #endif
215 }
216
Mobj_IsDroppedItem(mobj_t * mobj)217 dd_bool Mobj_IsDroppedItem(mobj_t *mobj)
218 {
219 DENG_ASSERT(mobj != 0);
220 #if __JHEXEN__
221 return (mobj->flags2 & MF2_DROPPED) != 0;
222 #else
223 return (mobj->flags & MF_DROPPED) != 0;
224 #endif
225 }
226
P_MobjFloorTerrain(mobj_t const * mobj)227 terraintype_t const *P_MobjFloorTerrain(mobj_t const *mobj)
228 {
229 return P_PlaneMaterialTerrainType(Mobj_Sector(mobj), PLN_FLOOR);
230 }
231
P_UpdateHealthBits(mobj_t * mo)232 void P_UpdateHealthBits(mobj_t *mo)
233 {
234 if(!mo || !mo->info) return;
235
236 if(mo->info->spawnHealth > 0)
237 {
238 mo->selector &= DDMOBJ_SELECTOR_MASK; // Clear high byte.
239
240 int sel = (mo->health << 3) / mo->info->spawnHealth;
241 sel = MINMAX_OF(0, sel, 7);
242
243 mo->selector |= sel << DDMOBJ_SELECTOR_SHIFT;
244 }
245 }
246
P_GetState(mobjtype_t type,statename_t name)247 statenum_t P_GetState(mobjtype_t type, statename_t name)
248 {
249 if(type < MT_FIRST || type >= Get(DD_NUMMOBJTYPES)) return S_NULL;
250 if(name < 0 || name >= STATENAMES_COUNT) return S_NULL;
251
252 return statenum_t(MOBJINFO[type].states[name]);
253 }
254
P_RipperBlood(mobj_t * actor)255 void P_RipperBlood(mobj_t *actor)
256 {
257 DENG_ASSERT(actor != 0);
258
259 coord_t pos[3];
260 pos[VX] = actor->origin[VX];
261 pos[VY] = actor->origin[VY];
262 pos[VZ] = actor->origin[VZ];
263
264 pos[VX] += FIX2FLT((P_Random() - P_Random()) << 12);
265 pos[VY] += FIX2FLT((P_Random() - P_Random()) << 12);
266 pos[VZ] += FIX2FLT((P_Random() - P_Random()) << 12);
267
268 if(mobj_t *mo = P_SpawnMobj(MT_BLOOD, pos, actor->angle, 0))
269 {
270 #if __JHERETIC__
271 mo->flags |= MF_NOGRAVITY;
272 #endif
273 mo->mom[MX] = actor->mom[MX] / 2;
274 mo->mom[MY] = actor->mom[MY] / 2;
275 mo->tics += P_Random() & 3;
276 }
277 }
278
allocateNode()279 static spawnqueuenode_t *allocateNode()
280 {
281 #define SPAWNQUEUENODE_BATCHSIZE 32
282
283 spawnqueuenode_t *node;
284
285 if(unusedNodes)
286 {
287 // There are existing nodes we can re-use.
288 node = unusedNodes;
289 unusedNodes = unusedNodes->next;
290 node->next = 0;
291 }
292 else
293 {
294 // We need to allocate more.
295 spawnqueuenode_t *storage = (spawnqueuenode_t *)
296 Z_Malloc(sizeof(*node) * SPAWNQUEUENODE_BATCHSIZE, PU_GAMESTATIC, 0);
297
298 // Add all but one to the unused node list.
299 for(int i = 0; i < SPAWNQUEUENODE_BATCHSIZE-1; ++i)
300 {
301 node = storage++;
302 node->next = unusedNodes;
303 unusedNodes = node;
304 }
305
306 node = storage;
307 }
308
309 return node;
310
311 #undef SPAWNQUEUENODE_BATCHSIZE
312 }
313
freeNode(spawnqueuenode_t * node,bool recycle)314 static void freeNode(spawnqueuenode_t *node, bool recycle)
315 {
316 if(!node) return;
317
318 // Find this node in the spawn queue and unlink it if found.
319 if(spawnQueueHead)
320 {
321 if(spawnQueueHead == node)
322 {
323 spawnQueueHead = spawnQueueHead->next;
324 }
325 else
326 {
327 for(spawnqueuenode_t *other = spawnQueueHead; other->next; other = other->next)
328 {
329 if(other->next == node)
330 other->next = other->next->next;
331 }
332 }
333 }
334
335 if(recycle)
336 {
337 // Recycle this node for later use.
338 node->next = unusedNodes;
339 unusedNodes = node;
340 return;
341 }
342
343 Z_Free(node);
344 }
345
dequeueSpawn()346 static spawnqueuenode_t *dequeueSpawn()
347 {
348 spawnqueuenode_t *node = spawnQueueHead;
349 if(spawnQueueHead)
350 spawnQueueHead = spawnQueueHead->next;
351 return node;
352 }
353
emptySpawnQueue(bool recycle)354 static void emptySpawnQueue(bool recycle)
355 {
356 if(spawnQueueHead)
357 {
358 while(spawnqueuenode_t *node = dequeueSpawn())
359 {
360 freeNode(node, recycle);
361 }
362 }
363 spawnQueueHead = 0;
364 }
365
enqueueSpawn(int minTics,mobjtype_t type,coord_t x,coord_t y,coord_t z,angle_t angle,int spawnFlags,void (* callback)(mobj_t * mo,void * context),void * context)366 static void enqueueSpawn(int minTics, mobjtype_t type, coord_t x, coord_t y,
367 coord_t z, angle_t angle, int spawnFlags,
368 void (*callback) (mobj_t *mo, void *context), void *context)
369 {
370 spawnqueuenode_t *spawn = allocateNode();
371
372 spawn->type = type;
373 spawn->pos[VX] = x;
374 spawn->pos[VY] = y;
375 spawn->pos[VZ] = z;
376 spawn->angle = angle;
377 spawn->spawnFlags = spawnFlags;
378
379 spawn->startTime = mapTime;
380 spawn->minTics = minTics;
381
382 spawn->callback = callback;
383 spawn->context = context;
384
385 if(spawnQueueHead)
386 {
387 // Find the correct insertion point.
388 if(spawnQueueHead->next)
389 {
390 spawnqueuenode_t *other = spawnQueueHead;
391
392 while(other->next && other->next->minTics - (mapTime - other->next->startTime) <= minTics)
393 {
394 other = other->next;
395 }
396
397 spawn->next = (other->next? other->next : 0);
398 other->next = spawn;
399 }
400 else
401 {
402 // After or before the head?
403 if(spawnQueueHead->minTics - (mapTime - spawnQueueHead->startTime) <= minTics)
404 {
405 spawn->next = 0;
406 spawnQueueHead->next = spawn;
407 }
408 else
409 {
410 spawn->next = spawnQueueHead;
411 spawnQueueHead = spawn;
412 }
413 }
414 }
415 else
416 {
417 spawn->next = 0;
418 spawnQueueHead = spawn;
419 }
420 }
421
P_DeferSpawnMobj3f(int minTics,mobjtype_t type,coord_t x,coord_t y,coord_t z,angle_t angle,int spawnFlags,void (* callback)(mobj_t * mo,void * context),void * context)422 void P_DeferSpawnMobj3f(int minTics, mobjtype_t type, coord_t x, coord_t y, coord_t z,
423 angle_t angle, int spawnFlags, void (*callback) (mobj_t *mo, void *context),
424 void* context)
425 {
426 if(minTics > 0)
427 {
428 enqueueSpawn(minTics, type, x, y, z, angle, spawnFlags, callback,
429 context);
430 }
431 // Spawn immediately.
432 else if(mobj_t *mo = P_SpawnMobjXYZ(type, x, y, z, angle, spawnFlags))
433 {
434 if(callback)
435 callback(mo, context);
436 }
437 }
438
P_DeferSpawnMobj3fv(int minTics,mobjtype_t type,coord_t const pos[3],angle_t angle,int spawnFlags,void (* callback)(mobj_t * mo,void * context),void * context)439 void P_DeferSpawnMobj3fv(int minTics, mobjtype_t type, coord_t const pos[3], angle_t angle,
440 int spawnFlags, void (*callback) (mobj_t* mo, void* context), void* context)
441 {
442 if(minTics > 0)
443 {
444 enqueueSpawn(minTics, type, pos[VX], pos[VY], pos[VZ], angle,
445 spawnFlags, callback, context);
446 }
447 // Spawn immediately.
448 else if(mobj_t *mo = P_SpawnMobj(type, pos, angle, spawnFlags))
449 {
450 if(callback)
451 callback(mo, context);
452 }
453 }
454
processOneSpawnTask()455 static mobj_t *processOneSpawnTask()
456 {
457 mobj_t *mo = 0;
458
459 // Anything due to spawn?
460 if(spawnQueueHead && mapTime - spawnQueueHead->startTime >= spawnQueueHead->minTics)
461 {
462 spawnqueuenode_t *spawn = dequeueSpawn();
463 DENG_ASSERT(spawn != 0);
464
465 // Spawn it.
466 if((mo = P_SpawnMobj(spawn->type, spawn->pos, spawn->angle, spawn->spawnFlags)))
467 {
468 if(spawn->callback)
469 spawn->callback(mo, spawn->context);
470 }
471
472 freeNode(spawn, true);
473 }
474
475 return mo;
476 }
477
478 /// @note Called 35 times per second by P_DoTick.
P_ProcessDeferredSpawns()479 void P_ProcessDeferredSpawns()
480 {
481 while(processOneSpawnTask())
482 {}
483 }
484
P_PurgeDeferredSpawns()485 void P_PurgeDeferredSpawns()
486 {
487 emptySpawnQueue(true);
488 }
489
490 #ifdef __JHEXEN__
491
492 /// @todo Remove fixed limit.
493 #define MAX_TID_COUNT 200
494
495 static int TIDList[MAX_TID_COUNT + 1]; // +1 for termination marker
496 static mobj_t *TIDMobj[MAX_TID_COUNT];
497
insertThinkerInIdListWorker(thinker_t * th,void * context)498 static int insertThinkerInIdListWorker(thinker_t *th, void *context)
499 {
500 mobj_t *mo = (mobj_t *)th;
501 int *count = (int *) context;
502
503 if(mo->tid != 0)
504 {
505 // Add to list.
506 if(*count == MAX_TID_COUNT)
507 {
508 Con_Error("P_CreateTIDList: MAX_TID_COUNT (%d) exceeded.", MAX_TID_COUNT);
509 }
510
511 TIDList[*count] = mo->tid;
512 TIDMobj[(*count)++] = mo;
513 }
514
515 return false; // Continue iteration.
516 }
517
P_CreateTIDList()518 void P_CreateTIDList()
519 {
520 int count = 0;
521 Thinker_Iterate(P_MobjThinker, insertThinkerInIdListWorker, &count);
522
523 // Add termination marker
524 TIDList[count] = 0;
525 }
526
P_MobjInsertIntoTIDList(mobj_t * mo,int tid)527 void P_MobjInsertIntoTIDList(mobj_t *mo, int tid)
528 {
529 DENG_ASSERT(mo != 0);
530
531 int index = -1;
532 int i = 0;
533 for(; TIDList[i] != 0; ++i)
534 {
535 if(TIDList[i] == -1)
536 {
537 // Found empty slot
538 index = i;
539 break;
540 }
541 }
542
543 if(index == -1)
544 {
545 // Append required
546 if(i == MAX_TID_COUNT)
547 {
548 Con_Error("P_MobjInsertIntoTIDList: MAX_TID_COUNT (%d) exceeded.", MAX_TID_COUNT);
549 }
550 index = i;
551 TIDList[index + 1] = 0;
552 }
553
554 mo->tid = tid;
555 TIDList[index] = tid;
556 TIDMobj[index] = mo;
557 }
558
P_MobjRemoveFromTIDList(mobj_t * mo)559 void P_MobjRemoveFromTIDList(mobj_t *mo)
560 {
561 if(!mo || !mo->tid)
562 return;
563
564 for(int i = 0; TIDList[i] != 0; ++i)
565 {
566 if(TIDMobj[i] == mo)
567 {
568 TIDList[i] = -1;
569 TIDMobj[i] = 0;
570 mo->tid = 0;
571 return;
572 }
573 }
574
575 mo->tid = 0;
576 }
577
P_FindMobjFromTID(int tid,int * searchPosition)578 mobj_t *P_FindMobjFromTID(int tid, int *searchPosition)
579 {
580 DENG_ASSERT(searchPosition != 0);
581
582 for(int i = *searchPosition + 1; TIDList[i] != 0; ++i)
583 {
584 if(TIDList[i] == tid)
585 {
586 *searchPosition = i;
587 return TIDMobj[i];
588 }
589 }
590
591 *searchPosition = -1;
592 return 0;
593 }
594
595 #endif // __JHEXEN__
596