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