1 /** @file p_spec.cpp  Special map actions.
2  *
3  * @authors Copyright © 2003-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4  * @authors Copyright © 2005-2014 Daniel Swanson <danij@dengine.net>
5  * @authors Copyright © 1999 Activision
6  *
7  * @par License
8  * GPL: http://www.gnu.org/licenses/gpl.html
9  *
10  * <small>This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by the
12  * Free Software Foundation; either version 2 of the License, or (at your
13  * option) any later version. This program is distributed in the hope that it
14  * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
15  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
16  * Public License for more details. You should have received a copy of the GNU
17  * General Public License along with this program; if not, write to the Free
18  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
19  * 02110-1301 USA</small>
20  */
21 
22 #include "jhexen.h"
23 #include "p_spec.h"
24 
25 #include <acs/interpreter.h>
26 
27 #include <cstdio>
28 #include <cstring>
29 #include "acs/system.h"
30 #include "dmu_lib.h"
31 #include "d_netsv.h"
32 #include "g_common.h"
33 #include "gamesession.h"
34 #include "lightninganimator.h"
35 #include "p_inventory.h"
36 #include "player.h"
37 #include "p_map.h"
38 #include "p_mapsetup.h"
39 #include "p_mapspec.h"
40 #include "p_ceiling.h"
41 #include "p_door.h"
42 #include "p_plat.h"
43 #include "p_floor.h"
44 #include "p_scroll.h"
45 #include "p_switch.h"
46 #include "p_user.h"
47 #include "polyobjs.h"
48 
49 using namespace de;
50 using namespace common;
51 
acscriptSys()52 static inline acs::System &acscriptSys()
53 {
54     return gfw_Session()->acsSystem();
55 }
56 
57 LightningAnimator lightningAnimator;
58 
59 ThinkerT<mobj_t> lavaInflictor;
60 
P_LavaInflictor()61 mobj_t *P_LavaInflictor()
62 {
63     return lavaInflictor;
64 }
65 
P_InitLava()66 void P_InitLava()
67 {
68     lavaInflictor = ThinkerT<mobj_t>();
69 
70     lavaInflictor->type = MT_CIRCLEFLAME;
71     lavaInflictor->flags2 = MF2_FIREDAMAGE | MF2_NODMGTHRUST;
72 }
73 
EV_SectorSoundChange(byte * args)74 dd_bool EV_SectorSoundChange(byte *args)
75 {
76     if(!args[0]) return false;
77 
78     dd_bool result = false;
79 
80     if(iterlist_t *list = P_GetSectorIterListForTag((int) args[0], false))
81     {
82         IterList_SetIteratorDirection(list, ITERLIST_FORWARD);
83         IterList_RewindIterator(list);
84         Sector *sec = 0;
85         while((sec = (Sector *)IterList_MoveIterator(list)))
86         {
87             P_ToXSector(sec)->seqType = seqtype_t(args[1]);
88             result = true;
89         }
90     }
91 
92     return result;
93 }
94 
CheckedLockedDoor(mobj_t * mo,byte lock)95 static dd_bool CheckedLockedDoor(mobj_t *mo, byte lock)
96 {
97     DENG2_ASSERT(mo != 0);
98 
99     if(!mo->player) return false;
100     if(!lock) return true;
101 
102     if(!(mo->player->keys & (1 << (lock - 1))))
103     {
104         char LockedBuffer[80];
105         sprintf(LockedBuffer, "YOU NEED THE %s\n", GET_TXT(TextKeyMessages[lock - 1]));
106 
107         P_SetMessage(mo->player, LockedBuffer);
108         S_StartSound(SFX_DOOR_LOCKED, mo);
109         return false;
110     }
111 
112     return true;
113 }
114 
EV_LineSearchForPuzzleItem(Line * line,byte *,mobj_t * mo)115 dd_bool EV_LineSearchForPuzzleItem(Line *line, byte * /*args*/, mobj_t *mo)
116 {
117     if(!mo || !mo->player || !line)
118         return false;
119 
120     inventoryitemtype_t type = inventoryitemtype_t(IIT_FIRSTPUZZITEM + P_ToXLine(line)->arg1);
121     if(type < IIT_FIRSTPUZZITEM)
122         return false;
123 
124     // Search player's inventory for puzzle items
125     return P_InventoryUse(mo->player - players, type, false);
126 }
127 
isThingSpawnEventAllowed()128 static bool isThingSpawnEventAllowed()
129 {
130     if (gameMode == hexen_deathkings && acs::Interpreter::currentScriptNumber == 255)
131     {
132         // This is the auto-respawn script.
133         if (randf() >= float(cfg.deathkingsAutoRespawnChance) / 100.0f)
134         {
135             App_Log(DE2_MAP_VERBOSE, "Monster autorespawn suppressed in ACS script 255");
136             return false;
137         }
138     }
139     return true;
140 }
141 
P_ExecuteLineSpecial(int special,byte args[5],Line * line,int side,mobj_t * mo)142 dd_bool P_ExecuteLineSpecial(int special, byte args[5], Line *line, int side, mobj_t *mo)
143 {
144     dd_bool success = false;
145 
146     App_Log(DE2_MAP_VERBOSE, "Executing line special %i, mobj:%i", special, mo? mo->thinker.id : 0);
147 
148     switch(special)
149     {
150     case 1: // Poly Start Line
151         break;
152 
153     case 2: // Poly Rotate Left
154         success = EV_RotatePoly(line, args, 1, false);
155         break;
156 
157     case 3: // Poly Rotate Right
158         success = EV_RotatePoly(line, args, -1, false);
159         break;
160 
161     case 4: // Poly Move
162         success = EV_MovePoly(line, args, false, false);
163         break;
164 
165     case 5: // Poly Explicit Line:  Only used in initialization
166         break;
167 
168     case 6: // Poly Move Times 8
169         success = EV_MovePoly(line, args, true, false);
170         break;
171 
172     case 7: // Poly Door Swing
173         success = EV_OpenPolyDoor(line, args, PODOOR_SWING);
174         break;
175 
176     case 8: // Poly Door Slide
177         success = EV_OpenPolyDoor(line, args, PODOOR_SLIDE);
178         break;
179 
180     case 10: // Door Close
181         success = EV_DoDoor(line, args, DT_CLOSE);
182         break;
183 
184     case 11: // Door Open
185         if(!args[0])
186         {
187             success = EV_VerticalDoor(line, mo);
188         }
189         else
190         {
191             success = EV_DoDoor(line, args, DT_OPEN);
192         }
193         break;
194 
195     case 12: // Door Raise
196         if(!args[0])
197         {
198             success = EV_VerticalDoor(line, mo);
199         }
200         else
201         {
202             success = EV_DoDoor(line, args, DT_NORMAL);
203         }
204         break;
205 
206     case 13: // Door Locked_Raise
207         if(CheckedLockedDoor(mo, args[3]))
208         {
209             if(!args[0])
210             {
211                 success = EV_VerticalDoor(line, mo);
212             }
213             else
214             {
215                 success = EV_DoDoor(line, args, DT_NORMAL);
216             }
217         }
218         break;
219 
220     case 20: // Floor Lower by Value
221         success = EV_DoFloor(line, args, FT_LOWERBYVALUE);
222         break;
223 
224     case 21: // Floor Lower to Lowest
225         success = EV_DoFloor(line, args, FT_LOWERTOLOWEST);
226         break;
227 
228     case 22: // Floor Lower to Nearest
229         success = EV_DoFloor(line, args, FT_LOWER);
230         break;
231 
232     case 23: // Floor Raise by Value
233         success = EV_DoFloor(line, args, FT_RAISEFLOORBYVALUE);
234         break;
235 
236     case 24: // Floor Raise to Highest
237         success = EV_DoFloor(line, args, FT_RAISEFLOOR);
238         break;
239 
240     case 25: // Floor Raise to Nearest
241         success = EV_DoFloor(line, args, FT_RAISEFLOORTONEAREST);
242         break;
243 
244     case 26: // Stairs Build Down Normal
245         success = EV_BuildStairs(line, args, -1, STAIRS_NORMAL);
246         break;
247 
248     case 27: // Build Stairs Up Normal
249         success = EV_BuildStairs(line, args, 1, STAIRS_NORMAL);
250         break;
251 
252     case 28: // Floor Raise and Crush
253         success = EV_DoFloor(line, args, FT_RAISEFLOORCRUSH);
254         break;
255 
256     case 29: // Build Pillar (no crushing)
257         success = EV_BuildPillar(line, args, false);
258         break;
259 
260     case 30: // Open Pillar
261         success = EV_OpenPillar(line, args);
262         break;
263 
264     case 31: // Stairs Build Down Sync
265         success = EV_BuildStairs(line, args, -1, STAIRS_SYNC);
266         break;
267 
268     case 32: // Build Stairs Up Sync
269         success = EV_BuildStairs(line, args, 1, STAIRS_SYNC);
270         break;
271 
272     case 35: // Raise Floor by Value Times 8
273         success = EV_DoFloor(line, args, FT_RAISEBYVALUEMUL8);
274         break;
275 
276     case 36: // Lower Floor by Value Times 8
277         success = EV_DoFloor(line, args, FT_LOWERBYVALUEMUL8);
278         break;
279 
280     case 40: // Ceiling Lower by Value
281         success = EV_DoCeiling(line, args, CT_LOWERBYVALUE);
282         break;
283 
284     case 41: // Ceiling Raise by Value
285         success = EV_DoCeiling(line, args, CT_RAISEBYVALUE);
286         break;
287 
288     case 42: // Ceiling Crush and Raise
289         success = EV_DoCeiling(line, args, CT_CRUSHANDRAISE);
290         break;
291 
292     case 43: // Ceiling Lower and Crush
293         success = EV_DoCeiling(line, args, CT_LOWERANDCRUSH);
294         break;
295 
296     case 44: // Ceiling Crush Stop
297         success = P_CeilingDeactivate((short) args[0]);
298         break;
299 
300     case 45: // Ceiling Crush Raise and Stay
301         success = EV_DoCeiling(line, args, CT_CRUSHRAISEANDSTAY);
302         break;
303 
304     case 46: // Floor Crush Stop
305         success = EV_FloorCrushStop(line, args);
306         break;
307 
308     case 60: // Plat Perpetual Raise
309         success = EV_DoPlat(line, args, PT_PERPETUALRAISE, 0);
310         break;
311 
312     case 61: // Plat Stop
313         P_PlatDeactivate((short) args[0]);
314         break;
315 
316     case 62: // Plat Down-Wait-Up-Stay
317         success = EV_DoPlat(line, args, PT_DOWNWAITUPSTAY, 0);
318         break;
319 
320     case 63: // Plat Down-by-Value*8-Wait-Up-Stay
321         success = EV_DoPlat(line, args, PT_DOWNBYVALUEWAITUPSTAY, 0);
322         break;
323 
324     case 64: // Plat Up-Wait-Down-Stay
325         success = EV_DoPlat(line, args, PT_UPWAITDOWNSTAY, 0);
326         break;
327 
328     case 65: // Plat Up-by-Value*8-Wait-Down-Stay
329         success = EV_DoPlat(line, args, PT_UPBYVALUEWAITDOWNSTAY, 0);
330         break;
331 
332     case 66: // Floor Lower Instant * 8
333         success = EV_DoFloor(line, args, FT_LOWERMUL8INSTANT);
334         break;
335 
336     case 67: // Floor Raise Instant * 8
337         success = EV_DoFloor(line, args, FT_RAISEMUL8INSTANT);
338         break;
339 
340     case 68: // Floor Move to Value * 8
341         success = EV_DoFloor(line, args, FT_TOVALUEMUL8);
342         break;
343 
344     case 69: // Ceiling Move to Value * 8
345         success = EV_DoCeiling(line, args, CT_MOVETOVALUEMUL8);
346         break;
347 
348     case 70: // Teleport
349         if(side == 0)
350         {   // Only teleport when crossing the front side of a line
351             success = EV_Teleport(args[0], mo, true);
352         }
353         break;
354 
355     case 71: // Teleport, no fog
356         if(side == 0)
357         {   // Only teleport when crossing the front side of a line
358             success = EV_Teleport(args[0], mo, false);
359         }
360         break;
361 
362     case 72: // Thrust Mobj
363         if(!side) // Only thrust on side 0
364         {
365             P_ThrustMobj(mo, args[0] * (ANGLE_90 / 64), (float) args[1]);
366             success = 1;
367         }
368         break;
369 
370     case 73: // Damage Mobj
371         if(args[0])
372         {
373             P_DamageMobj(mo, NULL, NULL, args[0], false);
374         }
375         else
376         {   // If arg1 is zero, then guarantee a kill
377             P_DamageMobj(mo, NULL, NULL, 10000, false);
378         }
379         success = 1;
380         break;
381 
382     case 74: // Teleport_NewMap
383         if(side == 0) // Only teleport when crossing the front side of a line
384         {
385             // Players must be alive to teleport
386             if(!(mo && mo->player && mo->player->playerState == PST_DEAD))
387             {
388                 // Assume the referenced map is from the current episode.
389                 dint epIdx  = gfw_Session()->episodeId().toInt();
390                 if(epIdx > 0) epIdx -= 1;
391                 dint mapIdx = args[0];
392                 if(mapIdx > 0) mapIdx -= 1;
393                 G_SetGameActionMapCompleted(G_ComposeMapUri(epIdx, mapIdx), args[1]);
394                 success = true;
395             }
396         }
397         break;
398 
399     case 75: // Teleport_EndGame
400         if(side == 0)  // Only teleport when crossing the front side of a line
401         {
402             // Players must be alive to teleport
403             if(!(mo && mo->player && mo->player->playerState == PST_DEAD))
404             {
405                 success = true;
406                 if(gfw_Rule(deathmatch))
407                 {
408                     // Winning in deathmatch goes back to the first map of the current episode.
409                     G_SetGameActionMapCompleted(de::makeUri(gfw_Session()->episodeDef()->gets("startMap")));
410                 }
411                 else
412                 {
413                     // Passing a URI with an empty path starts the Finale
414                     G_SetGameActionMapCompleted(de::makeUri("Maps:"));
415                 }
416             }
417         }
418         break;
419 
420     case 83: // ACS_LockedExecute
421 
422         // Only players can operate locks.
423         if(!mo || !mo->player) break;
424 
425         // Is a lock in effect?
426         if(int lock = args[4])
427         {
428             // Does the player possess the necessary key(s)?
429             if(!(mo->player->keys & (1 << (lock - 1))))
430             {
431                 auto const msg = String("You need the ") + String(GET_TXT(TextKeyMessages[lock - 1]));
432                 P_SetMessage(mo->player, msg.toUtf8().constData());
433                 S_StartSound(SFX_DOOR_LOCKED, mo);
434                 break;
435             }
436         }
437 
438         // Intentional fall-through.
439 
440     case 80: /* ACS_Execute */ {
441         dint const scriptNumber = args[0];
442         acs::Script::Args const scriptArgs(&args[2], 3);
443 
444         // Assume the referenced map is from the current episode.
445         dint epIdx  = gfw_Session()->episodeId().toInt();
446         if(epIdx > 0) epIdx -= 1;
447 
448         dint mapIdx = args[1];
449         de::Uri const mapUri = (mapIdx == 0? gfw_Session()->mapUri()
450                                            : G_ComposeMapUri(epIdx, mapIdx - 1) );
451         if(gfw_Session()->mapUri() == mapUri)
452         {
453             if(acscriptSys().hasScript(scriptNumber))
454             {
455                success = acscriptSys().script(scriptNumber).start(scriptArgs, mo, line, side);
456             }
457         }
458         else
459         {
460             success = acscriptSys().deferScriptStart(mapUri, scriptNumber, scriptArgs);
461         }
462         break; }
463 
464     case 81: /* ACS_Suspend */ {
465         int const scriptNumber = args[0];
466         if(acscriptSys().hasScript(scriptNumber))
467         {
468             success = acscriptSys().script(scriptNumber).suspend();
469         }
470         break; }
471 
472     case 82: /* ACS_Terminate */ {
473         int const scriptNumber = args[0];
474         if(acscriptSys().hasScript(scriptNumber))
475         {
476             success = acscriptSys().script(scriptNumber).terminate();
477         }
478         break; }
479 
480     case 90: // Poly Rotate Left Override
481         success = EV_RotatePoly(line, args, 1, true);
482         break;
483 
484     case 91: // Poly Rotate Right Override
485         success = EV_RotatePoly(line, args, -1, true);
486         break;
487 
488     case 92: // Poly Move Override
489         success = EV_MovePoly(line, args, false, true);
490         break;
491 
492     case 93: // Poly Move Times 8 Override
493         success = EV_MovePoly(line, args, true, true);
494         break;
495 
496     case 94: // Build Pillar Crush
497         success = EV_BuildPillar(line, args, true);
498         break;
499 
500     case 95: // Lower Floor and Ceiling
501         success = EV_DoFloorAndCeiling(line, args, FT_LOWERBYVALUE, CT_LOWERBYVALUE);
502         break;
503 
504     case 96: // Raise Floor and Ceiling
505         success = EV_DoFloorAndCeiling(line, args, FT_RAISEFLOORBYVALUE, CT_RAISEBYVALUE);
506         break;
507 
508     case 109: // Force Lightning
509         success = true;
510         ::lightningAnimator.triggerFlash();
511         break;
512 
513     case 110: // Light Raise by Value
514         success = EV_SpawnLight(line, args, LITE_RAISEBYVALUE);
515         break;
516 
517     case 111: // Light Lower by Value
518         success = EV_SpawnLight(line, args, LITE_LOWERBYVALUE);
519         break;
520 
521     case 112: // Light Change to Value
522         success = EV_SpawnLight(line, args, LITE_CHANGETOVALUE);
523         break;
524 
525     case 113: // Light Fade
526         success = EV_SpawnLight(line, args, LITE_FADE);
527         break;
528 
529     case 114: // Light Glow
530         success = EV_SpawnLight(line, args, LITE_GLOW);
531         break;
532 
533     case 115: // Light Flicker
534         success = EV_SpawnLight(line, args, LITE_FLICKER);
535         break;
536 
537     case 116: // Light Strobe
538         success = EV_SpawnLight(line, args, LITE_STROBE);
539         break;
540 
541     case 120: // Quake Tremor
542         success = A_LocalQuake(args, mo);
543         break;
544 
545     case 129: // UsePuzzleItem
546         success = EV_LineSearchForPuzzleItem(line, args, mo);
547         break;
548 
549     case 130: // Thing_Activate
550         success = EV_ThingActivate(args[0]);
551         break;
552 
553     case 131: // Thing_Deactivate
554         success = EV_ThingDeactivate(args[0]);
555         break;
556 
557     case 132: // Thing_Remove
558         success = EV_ThingRemove(args[0]);
559         break;
560 
561     case 133: // Thing_Destroy
562         success = EV_ThingDestroy(args[0]);
563         break;
564 
565     case 134: // Thing_Projectile
566         success = EV_ThingProjectile(args, 0);
567         break;
568 
569     case 135: // Thing_Spawn
570         if (isThingSpawnEventAllowed())
571         {
572             success = EV_ThingSpawn(args, 1);
573         }
574         else
575         {
576             success = 1;
577         }
578         break;
579 
580     case 136: // Thing_ProjectileGravity
581         success = EV_ThingProjectile(args, 1);
582         break;
583 
584     case 137: // Thing_SpawnNoFog
585         if (isThingSpawnEventAllowed())
586         {
587             success = EV_ThingSpawn(args, 0);
588         }
589         else
590         {
591             success = 1;
592         }
593         break;
594 
595     case 138: // Floor_Waggle
596         success = EV_StartFloorWaggle(args[0], args[1], args[2], args[3], args[4]);
597         break;
598 
599     case 140: // Sector_SoundChange
600         success = EV_SectorSoundChange(args);
601         break;
602 
603     default:
604         break;
605     }
606 
607     return success;
608 }
609 
P_ActivateLine(Line * line,mobj_t * mo,int side,int activationType)610 dd_bool P_ActivateLine(Line *line, mobj_t *mo, int side, int activationType)
611 {
612     // Clients do not activate lines.
613     if(IS_CLIENT) return false;
614 
615     xline_t *xline           = P_ToXLine(line);
616     int const lineActivation = GET_SPAC(xline->flags);
617     if(lineActivation != activationType)
618         return false;
619 
620     if(!mo->player && !(mo->flags & MF_MISSILE))
621     {
622         // Currently, monsters can only activate the MCROSS activation type.
623         if(lineActivation != SPAC_MCROSS) return false;
624 
625         // Never DT_OPEN secret doors
626         if(xline->flags & ML_SECRET) return false;
627     }
628 
629     bool const repeat        = ((xline->flags & ML_REPEAT_SPECIAL)? true : false);
630     bool const buttonSuccess = P_ExecuteLineSpecial(xline->special, &xline->arg1, line, side, mo);
631 
632     if(!repeat && buttonSuccess)
633     {
634         // Clear the special on non-retriggerable lines.
635         xline->special = 0;
636     }
637 
638     if((lineActivation == SPAC_USE || lineActivation == SPAC_IMPACT) &&
639        buttonSuccess)
640     {
641         P_ToggleSwitch((Side *)P_GetPtrp(line, DMU_FRONT), 0, false,
642                        repeat? BUTTONTIME : 0);
643     }
644 
645     return true;
646 }
647 
648 /**
649  * @note Called every tic frame that the player origin is in a special sector.
650  */
P_PlayerInSpecialSector(player_t * player)651 void P_PlayerInSpecialSector(player_t *player)
652 {
653     DENG2_ASSERT(player);
654     static coord_t const pushTab[3] = {
655         1.0 / 32 * 5,
656         1.0 / 32 * 10,
657         1.0 / 32 * 25
658     };
659 
660     Sector *sec = Mobj_Sector(player->plr->mo);
661     if(!de::fequal(player->plr->mo->origin[VZ], P_GetDoublep(sec, DMU_FLOOR_HEIGHT)))
662         return; // Player is not touching the floor
663 
664     xsector_t *xsec = P_ToXSector(sec);
665     switch(xsec->special)
666     {
667     case 9: // SecretArea
668         if(!IS_CLIENT)
669         {
670             player->secretCount++;
671             player->update |= PSF_COUNTERS;
672             xsec->special = 0;
673         }
674         break;
675 
676     case 201:
677     case 202:
678     case 203: // Scroll_North_xxx
679         P_Thrust(player, ANG90, pushTab[xsec->special - 201]);
680         break;
681 
682     case 204:
683     case 205:
684     case 206: // Scroll_East_xxx
685         P_Thrust(player, 0, pushTab[xsec->special - 204]);
686         break;
687 
688     case 207:
689     case 208:
690     case 209: // Scroll_South_xxx
691         P_Thrust(player, ANG270, pushTab[xsec->special - 207]);
692         break;
693 
694     case 210:
695     case 211:
696     case 212: // Scroll_West_xxx
697         P_Thrust(player, ANG180, pushTab[xsec->special - 210]);
698         break;
699 
700     case 213:
701     case 214:
702     case 215: // Scroll_NorthWest_xxx
703         P_Thrust(player, ANG90 + ANG45, pushTab[xsec->special - 213]);
704         break;
705 
706     case 216:
707     case 217:
708     case 218: // Scroll_NorthEast_xxx
709         P_Thrust(player, ANG45, pushTab[xsec->special - 216]);
710         break;
711 
712     case 219:
713     case 220:
714     case 221: // Scroll_SouthEast_xxx
715         P_Thrust(player, ANG270 + ANG45, pushTab[xsec->special - 219]);
716         break;
717 
718     case 222:
719     case 223:
720     case 224: // Scroll_SouthWest_xxx
721         P_Thrust(player, ANG180 + ANG45, pushTab[xsec->special - 222]);
722         break;
723 
724     case 40:
725     case 41:
726     case 42:
727     case 43:
728     case 44:
729     case 45:
730     case 46:
731     case 47:
732     case 48:
733     case 49:
734     case 50:
735     case 51:
736         // Wind specials are handled in (P_mobj):P_MobjMoveXY
737         break;
738 
739     case 26: // Stairs_Special1
740     case 27: // Stairs_Special2
741         // Used in (P_floor):ProcessStairSector
742         break;
743 
744     case 198: // Lightning Special
745     case 199: // Lightning Flash special
746     case 200: // Sky2
747         // Used in (R_plane):R_Drawplanes
748         break;
749 
750     default:
751         break;
752     }
753 }
754 
P_PlayerOnSpecialFloor(player_t * player)755 void P_PlayerOnSpecialFloor(player_t *player)
756 {
757     DENG2_ASSERT(player);
758     mobj_t *plrMo           = player->plr->mo;
759     terraintype_t const *tt = P_MobjFloorTerrain(plrMo);
760     DENG2_ASSERT(tt);
761 
762     if(!(tt->flags & TTF_DAMAGING))
763         return;
764 
765     if(plrMo->origin[VZ] > P_GetDoublep(Mobj_Sector(plrMo), DMU_FLOOR_HEIGHT))
766     {
767         return; // Player is not touching the floor
768     }
769 
770     if(!(mapTime & 31))
771     {
772         P_DamageMobj(plrMo, P_LavaInflictor(), nullptr, 10, false);
773         S_StartSound(SFX_LAVA_SIZZLE, plrMo);
774     }
775 }
776 
P_SpawnSectorSpecialThinkers()777 void P_SpawnSectorSpecialThinkers()
778 {
779     // Clients do not spawn sector specials.
780     if(IS_CLIENT) return;
781 
782     for(int i = 0; i < numsectors; ++i)
783     {
784         Sector *sec     = (Sector *)P_ToPtr(DMU_SECTOR, i);
785         xsector_t *xsec = P_ToXSector(sec);
786 
787         // XG sector types override the game's built-in types.
788         //if(xsec->xg) continue;
789 
790         switch(xsec->special)
791         {
792         default: break;
793 
794         case 1: ///< Phased light.
795             // Static base, use sector->lightLevel as the index.
796             P_SpawnPhasedLight(sec, (80.f / 255.0f), -1);
797             break;
798 
799         case 2: ///< Phased light sequence start.
800             P_SpawnLightSequence(sec, 1);
801             break;
802         }
803     }
804 }
805 
P_SpawnLineSpecialThinkers()806 void P_SpawnLineSpecialThinkers()
807 {
808     // Stub.
809 }
810 
P_SpawnAllSpecialThinkers()811 void P_SpawnAllSpecialThinkers()
812 {
813     P_SpawnSectorSpecialThinkers();
814     P_SpawnLineSpecialThinkers();
815 }
816 
P_InitLightning()817 void P_InitLightning()
818 {
819     ::lightningAnimator.initForMap();
820 }
821 
P_AnimateLightning()822 void P_AnimateLightning()
823 {
824     ::lightningAnimator.advanceTime();
825 }
826 
P_StartACScript(int scriptNumber,byte const args[],mobj_t * activator,Line * line,int side)827 dd_bool P_StartACScript(int scriptNumber, byte const args[],
828     mobj_t *activator, Line *line, int side)
829 {
830     if(acscriptSys().hasScript(scriptNumber))
831     {
832         return acscriptSys().script(scriptNumber)
833                                 .start(acs::Script::Args(args, 4), activator, line, side);
834     }
835     return false;
836 }
837