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