1 /** @file p_floor.cpp  Common playsim routines relating to moving floors.
2  *
3  * @authors Copyright © 2003-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4  * @authors Copyright © 2006-2013 Daniel Swanson <danij@dengine.net>
5  * @authors Copyright © 2006 Martin Eyre <martineyre@btinternet.com>
6  * @authors Copyright © 2003-2005 Samuel Villarreal <svkaiser@gmail.com>
7  * @authors Copyright © 1999 Chi Hoang, Lee Killough, Jim Flynn, Rand Phares, Ty Halderman (PrBoom 2.2.6)
8  * @authors Copyright © 1999-2000 Jess Haas, Nicolas Kalkhof, Colin Phipps, Florian Schulze (PrBoom 2.2.6)
9  * @authors Copyright © 1993-1996 id Software, Inc.
10  *
11  * @par License
12  * GPL: http://www.gnu.org/licenses/gpl.html
13  *
14  * <small>This program is free software; you can redistribute it and/or modify
15  * it under the terms of the GNU General Public License as published by the
16  * Free Software Foundation; either version 2 of the License, or (at your
17  * option) any later version. This program is distributed in the hope that it
18  * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
19  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
20  * Public License for more details. You should have received a copy of the GNU
21  * General Public License along with this program; if not, write to the Free
22  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
23  * 02110-1301 USA</small>
24  */
25 
26 #include "common.h"
27 #include "p_floor.h"
28 
29 #include "dmu_lib.h"
30 #include "p_map.h"
31 #include "p_mapspec.h"
32 #include "p_tick.h"
33 #if __JHEXEN__ || __JDOOM64__
34 #include "p_ceiling.h"
35 #endif
36 #include "p_sound.h"
37 #include "p_saveg.h"
38 
39 #if __JHERETIC__
40 # define SFX_FLOORMOVE          (SFX_DORMOV)
41 #else
42 # define SFX_FLOORMOVE          (SFX_STNMOV)
43 #endif
44 
45 #if __JHEXEN__
46 #define STAIR_SECTOR_TYPE       26
47 #define STAIR_QUEUE_SIZE        32
48 #endif
49 
50 #if __JHEXEN__
51 typedef struct stairqueue_s {
52     Sector *sector;
53     int type;
54     coord_t height;
55 } stairqueue_t;
56 
57 // Global vars for stair building, in a struct for neatness.
58 typedef struct stairdata_s {
59     coord_t stepDelta;
60     int direction;
61     float speed;
62     world_Material *material;
63     int startDelay;
64     int startDelayDelta;
65     int textureChange;
66     coord_t startHeight;
67 } stairdata_t;
68 #endif
69 
70 #if __JHEXEN__
71 static void enqueueStairSector(Sector *sec, int type, coord_t height);
72 #endif
73 
74 #if __JHEXEN__
75 stairdata_t stairData;
76 stairqueue_t stairQueue[STAIR_QUEUE_SIZE];
77 static int stairQueueHead;
78 static int stairQueueTail;
79 #endif
80 
T_MovePlane(Sector * sector,float speed,coord_t dest,int crush,int isCeiling,int direction)81 result_e T_MovePlane(Sector *sector, float speed, coord_t dest, int crush,
82     int isCeiling, int direction)
83 {
84     dd_bool flag;
85     coord_t lastpos;
86     coord_t floorheight, ceilingheight;
87     int ptarget = (isCeiling? DMU_CEILING_TARGET_HEIGHT : DMU_FLOOR_TARGET_HEIGHT);
88     int pspeed = (isCeiling? DMU_CEILING_SPEED : DMU_FLOOR_SPEED);
89 
90     // Let the engine know about the movement of this plane.
91     P_SetDoublep(sector, ptarget, dest);
92     P_SetFloatp(sector, pspeed, speed);
93 
94     floorheight = P_GetDoublep(sector, DMU_FLOOR_HEIGHT);
95     ceilingheight = P_GetDoublep(sector, DMU_CEILING_HEIGHT);
96 
97     switch(isCeiling)
98     {
99     case 0:
100         // FLOOR
101         switch(direction)
102         {
103         case -1:
104             // DOWN
105             if(floorheight - speed < dest)
106             {
107                 // The move is complete.
108                 lastpos = floorheight;
109                 P_SetDoublep(sector, DMU_FLOOR_HEIGHT, dest);
110                 flag = P_ChangeSector(sector, crush);
111                 if(flag)
112                 {
113                     // Oh no, the move failed.
114                     P_SetDoublep(sector, DMU_FLOOR_HEIGHT, lastpos);
115                     P_SetDoublep(sector, ptarget, lastpos);
116                     P_ChangeSector(sector, crush);
117                 }
118 #if __JHEXEN__
119                 P_SetFloatp(sector, pspeed, 0);
120 #endif
121                 return pastdest;
122             }
123             else
124             {
125                 lastpos = floorheight;
126                 P_SetDoublep(sector, DMU_FLOOR_HEIGHT, floorheight - speed);
127                 flag = P_ChangeSector(sector, crush);
128                 if(flag)
129                 {
130                     P_SetDoublep(sector, DMU_FLOOR_HEIGHT, lastpos);
131                     P_SetDoublep(sector, ptarget, lastpos);
132 #if __JHEXEN__
133                     P_SetFloatp(sector, pspeed, 0);
134 #endif
135                     P_ChangeSector(sector, crush);
136                     return crushed;
137                 }
138             }
139             break;
140 
141         case 1:
142             // UP
143             if(floorheight + speed > dest)
144             {
145                 // The move is complete.
146                 lastpos = floorheight;
147                 P_SetDoublep(sector, DMU_FLOOR_HEIGHT, dest);
148                 flag = P_ChangeSector(sector, crush);
149                 if(flag)
150                 {
151                     // Oh no, the move failed.
152                     P_SetDoublep(sector, DMU_FLOOR_HEIGHT, lastpos);
153                     P_SetDoublep(sector, ptarget, lastpos);
154                     P_ChangeSector(sector, crush);
155                 }
156 #if __JHEXEN__
157                 P_SetFloatp(sector, pspeed, 0);
158 #endif
159                 return pastdest;
160             }
161             else
162             {
163                 // COULD GET CRUSHED
164                 lastpos = floorheight;
165                 P_SetDoublep(sector, DMU_FLOOR_HEIGHT, floorheight + speed);
166                 flag = P_ChangeSector(sector, crush);
167                 if(flag)
168                 {
169 #if !__JHEXEN__
170                     if(crush)
171                         return crushed;
172 #endif
173                     P_SetDoublep(sector, DMU_FLOOR_HEIGHT, lastpos);
174                     P_SetDoublep(sector, ptarget, lastpos);
175 #if __JHEXEN__
176                     P_SetFloatp(sector, pspeed, 0);
177 #endif
178                     P_ChangeSector(sector, crush);
179                     return crushed;
180                 }
181             }
182             break;
183 
184         default:
185             break;
186         }
187         break;
188 
189     case 1:
190         // CEILING
191         switch(direction)
192         {
193         case -1:
194             // DOWN
195             if(ceilingheight - speed < dest)
196             {
197                 // The move is complete.
198                 lastpos = ceilingheight;
199                 P_SetDoublep(sector, DMU_CEILING_HEIGHT, dest);
200                 flag = P_ChangeSector(sector, crush);
201                 if(flag)
202                 {
203                     P_SetDoublep(sector, DMU_CEILING_HEIGHT, lastpos);
204                     P_SetDoublep(sector, ptarget, lastpos);
205                     P_ChangeSector(sector, crush);
206                 }
207 #if __JHEXEN__
208                 P_SetFloatp(sector, pspeed, 0);
209 #endif
210                 return pastdest;
211             }
212             else
213             {
214                 // COULD GET CRUSHED
215                 lastpos = ceilingheight;
216                 P_SetDoublep(sector, DMU_CEILING_HEIGHT, ceilingheight - speed);
217                 flag = P_ChangeSector(sector, crush);
218                 if(flag)
219                 {
220 #if !__JHEXEN__
221                     if(crush)
222                         return crushed;
223 #endif
224                     P_SetDoublep(sector, DMU_CEILING_HEIGHT, lastpos);
225                     P_SetDoublep(sector, ptarget, lastpos);
226 #if __JHEXEN__
227                     P_SetFloatp(sector, pspeed, 0);
228 #endif
229                     P_ChangeSector(sector, crush);
230                     return crushed;
231                 }
232             }
233             break;
234 
235         case 1:
236             // UP
237             if(ceilingheight + speed > dest)
238             {
239                 // The move is complete.
240                 lastpos = ceilingheight;
241                 P_SetDoublep(sector, DMU_CEILING_HEIGHT, dest);
242                 flag = P_ChangeSector(sector, crush);
243                 if(flag)
244                 {
245                     P_SetDoublep(sector, DMU_CEILING_HEIGHT, lastpos);
246                     P_SetDoublep(sector, ptarget, lastpos);
247                     P_ChangeSector(sector, crush);
248                 }
249 #if __JHEXEN__
250                 P_SetFloatp(sector, pspeed, 0);
251 #endif
252                 return pastdest;
253             }
254             else
255             {
256                 lastpos = ceilingheight;
257                 P_SetDoublep(sector, DMU_CEILING_HEIGHT, ceilingheight + speed);
258                 flag = P_ChangeSector(sector, crush);
259             }
260             break;
261 
262         default:
263             break;
264         }
265         break;
266 
267     default:
268         break;
269     }
270 
271     return ok;
272 }
273 
274 /**
275  * Move a floor to it's destination (up or down).
276  */
T_MoveFloor(void * floorThinkerPtr)277 void T_MoveFloor(void *floorThinkerPtr)
278 {
279     floor_t *floor = (floor_t *)floorThinkerPtr;
280     result_e res;
281 
282 #if __JHEXEN__
283     if(floor->resetDelayCount)
284     {
285         floor->resetDelayCount--;
286         if(!floor->resetDelayCount)
287         {
288             floor->floorDestHeight = floor->resetHeight;
289             floor->state = ((floor->state == FS_UP)? FS_DOWN : FS_UP);
290             floor->resetDelay = 0;
291             floor->delayCount = 0;
292             floor->delayTotal = 0;
293         }
294     }
295     if(floor->delayCount)
296     {
297         floor->delayCount--;
298         if(!floor->delayCount && floor->material)
299         {
300             P_SetPtrp(floor->sector, DMU_FLOOR_MATERIAL, floor->material);
301         }
302         return;
303     }
304 #endif
305 
306     res =
307         T_MovePlane(floor->sector, floor->speed, floor->floorDestHeight,
308                     floor->crush, 0, floor->state);
309 
310 #if __JHEXEN__
311     if(floor->type == FT_RAISEBUILDSTEP)
312     {
313         if((floor->state == FS_UP &&
314             P_GetDoublep(floor->sector, DMU_FLOOR_HEIGHT) >= floor->stairsDelayHeight) ||
315            (floor->state == FS_DOWN &&
316             P_GetDoublep(floor->sector, DMU_FLOOR_HEIGHT) <= floor->stairsDelayHeight))
317         {
318             floor->delayCount = floor->delayTotal;
319             floor->stairsDelayHeight += floor->stairsDelayHeightDelta;
320         }
321     }
322 #endif
323 
324 #if !__JHEXEN__
325     if(!(mapTime & 7))
326         S_PlaneSound((Plane *)P_GetPtrp(floor->sector, DMU_FLOOR_PLANE), SFX_FLOORMOVE);
327 #endif
328 
329     if(res == pastdest)
330     {
331         xsector_t *xsec = P_ToXSector(floor->sector);
332         P_SetFloatp(floor->sector, DMU_FLOOR_SPEED, 0);
333 
334 #if __JHEXEN__
335         SN_StopSequence((mobj_t *)P_GetPtrp(floor->sector, DMU_EMITTER));
336 #else
337 #  if __JHERETIC__
338         if(floor->type == FT_RAISEBUILDSTEP)
339 #  endif
340             S_PlaneSound((Plane *)P_GetPtrp(floor->sector, DMU_FLOOR_PLANE), SFX_PSTOP);
341 
342 #endif
343 #if __JHEXEN__
344         if(floor->delayTotal)
345             floor->delayTotal = 0;
346 
347         if(floor->resetDelay)
348         {
349             //          floor->resetDelayCount = floor->resetDelay;
350             //          floor->resetDelay = 0;
351             return;
352         }
353 #endif
354         xsec->specialData = NULL;
355 #if __JHEXEN__
356         if(floor->material)
357         {
358             P_SetPtrp(floor->sector, DMU_FLOOR_MATERIAL, floor->material);
359         }
360 #else
361         if(floor->state == FS_UP)
362         {
363             switch(floor->type)
364             {
365             case FT_RAISEDONUT:
366                 xsec->special = floor->newSpecial;
367                 P_SetPtrp(floor->sector, DMU_FLOOR_MATERIAL, floor->material);
368                 break;
369 
370             default:
371                 break;
372             }
373         }
374         else if(floor->state == FS_DOWN)
375         {
376             switch(floor->type)
377             {
378             case FT_LOWERANDCHANGE:
379                 xsec->special = floor->newSpecial;
380                 P_SetPtrp(floor->sector, DMU_FLOOR_MATERIAL, floor->material);
381                 break;
382 
383             default:
384                 break;
385             }
386         }
387 #endif
388         P_NotifySectorFinished(P_ToXSector(floor->sector)->tag);
389         Thinker_Remove(&floor->thinker);
390     }
391 }
392 
write(MapStateWriter * msw) const393 void floor_s::write(MapStateWriter *msw) const
394 {
395     Writer1 *writer = msw->writer();
396 
397     Writer_WriteByte(writer, 3); // Write a version byte.
398 
399     // Note we don't bother to save a byte to tell if the function
400     // is present as we ALWAYS add one when loading.
401 
402     Writer_WriteByte(writer, (byte) type);
403 
404     Writer_WriteInt32(writer, P_ToIndex(sector));
405 
406     Writer_WriteByte(writer, (byte) crush);
407 
408     Writer_WriteInt32(writer, (int) state);
409     Writer_WriteInt32(writer, newSpecial);
410 
411     Writer_WriteInt16(writer, msw->serialIdFor(material));
412 
413     Writer_WriteInt16(writer, (int) floorDestHeight);
414     Writer_WriteInt32(writer, FLT2FIX(speed));
415 
416 #if __JHEXEN__
417     Writer_WriteInt32(writer, delayCount);
418     Writer_WriteInt32(writer, delayTotal);
419     Writer_WriteInt32(writer, FLT2FIX(stairsDelayHeight));
420     Writer_WriteInt32(writer, FLT2FIX(stairsDelayHeightDelta));
421     Writer_WriteInt32(writer, FLT2FIX(resetHeight));
422     Writer_WriteInt16(writer, resetDelay);
423     Writer_WriteInt16(writer, resetDelayCount);
424 #endif
425 }
426 
read(MapStateReader * msr)427 int floor_s::read(MapStateReader *msr)
428 {
429     Reader1 *reader = msr->reader();
430     int mapVersion = msr->mapVersion();
431 
432 #if __JHEXEN__
433     if(mapVersion >= 4)
434 #else
435     if(mapVersion >= 5)
436 #endif
437     {   // Note: the thinker class byte has already been read.
438         byte ver = Reader_ReadByte(reader); // version byte.
439 
440         type                   = floortype_e(Reader_ReadByte(reader));
441         sector                 = (Sector *)P_ToPtr(DMU_SECTOR, Reader_ReadInt32(reader));
442         DENG_ASSERT(sector != 0);
443         crush                  = dd_bool(Reader_ReadByte(reader));
444         state                  = floorstate_e(Reader_ReadInt32(reader));
445         newSpecial             = Reader_ReadInt32(reader);
446 
447         if(ver >= 2)
448         {
449             material = (world_Material *) msr->material(Reader_ReadInt16(reader), 0);
450         }
451         else
452         {
453             // Flat number is an absolute lump index.
454             de::Uri uri("Flats:", CentralLumpIndex()[Reader_ReadInt16(reader)].name().fileNameWithoutExtension());
455             material = (world_Material *)P_ToPtr(DMU_MATERIAL, Materials_ResolveUri(reinterpret_cast<uri_s *>(&uri)));
456         }
457 
458         floorDestHeight        = (float) Reader_ReadInt16(reader);
459         speed                  = FIX2FLT(Reader_ReadInt32(reader));
460 
461 #if __JHEXEN__
462         delayCount             = Reader_ReadInt32(reader);
463         delayTotal             = Reader_ReadInt32(reader);
464         stairsDelayHeight      = FIX2FLT(Reader_ReadInt32(reader));
465         stairsDelayHeightDelta = FIX2FLT(Reader_ReadInt32(reader));
466         resetHeight            = FIX2FLT(Reader_ReadInt32(reader));
467         resetDelay             = Reader_ReadInt16(reader);
468         resetDelayCount        = Reader_ReadInt16(reader);
469 #endif
470     }
471     else
472     {
473         // Its in the old format which serialized floor_t
474         // Padding at the start (an old thinker_t struct)
475         byte junk[16]; // sizeof thinker_t
476         Reader_Read(reader, junk, 16);
477 
478         // Start of used data members.
479 #if __JHEXEN__
480         // A 32bit pointer to sector, serialized.
481         sector                 = (Sector *)P_ToPtr(DMU_SECTOR, (int) Reader_ReadInt32(reader));
482         DENG_ASSERT(sector != 0);
483 
484         type                   = floortype_e(Reader_ReadInt32(reader));
485         crush                  = Reader_ReadInt32(reader);
486 #else
487         type                   = floortype_e(Reader_ReadInt32(reader));
488         crush                  = Reader_ReadInt32(reader);
489 
490         // A 32bit pointer to sector, serialized.
491         sector                 = (Sector *)P_ToPtr(DMU_SECTOR, (int) Reader_ReadInt32(reader));
492         DENG_ASSERT(sector != 0);
493 #endif
494         state                  = floorstate_e(Reader_ReadInt32(reader));
495         newSpecial             = Reader_ReadInt32(reader);
496 
497         // Flat number is an absolute lump index.
498         de::Uri uri("Flats:", CentralLumpIndex()[Reader_ReadInt16(reader)].name().fileNameWithoutExtension());
499         material               = (world_Material *)P_ToPtr(DMU_MATERIAL, Materials_ResolveUri(reinterpret_cast<uri_s *>(&uri)));
500 
501         floorDestHeight        = FIX2FLT((fixed_t) Reader_ReadInt32(reader));
502         speed                  = FIX2FLT((fixed_t) Reader_ReadInt32(reader));
503 
504 #if __JHEXEN__
505         delayCount             = Reader_ReadInt32(reader);
506         delayTotal             = Reader_ReadInt32(reader);
507         stairsDelayHeight      = FIX2FLT((fixed_t) Reader_ReadInt32(reader));
508         stairsDelayHeightDelta = FIX2FLT((fixed_t) Reader_ReadInt32(reader));
509         resetHeight            = FIX2FLT((fixed_t) Reader_ReadInt32(reader));
510         resetDelay             = Reader_ReadInt16(reader);
511         resetDelayCount        = Reader_ReadInt16(reader);
512         /*textureChange        =*/ Reader_ReadByte(reader);
513 #endif
514     }
515 
516     P_ToXSector(sector)->specialData = this;
517     thinker.function = T_MoveFloor;
518 
519     return true; // Add this thinker.
520 }
521 
522 struct findlineinsectorsmallestbottommaterialparams_t
523 {
524     Sector *baseSec;
525     int minSize;
526     Line *foundLine;
527 };
528 
findLineInSectorSmallestBottomMaterial(void * ptr,void * context)529 int findLineInSectorSmallestBottomMaterial(void *ptr, void *context)
530 {
531     Line *li = (Line *) ptr;
532     findlineinsectorsmallestbottommaterialparams_t *params = (findlineinsectorsmallestbottommaterialparams_t *) context;
533 
534     Sector *frontSec = (Sector *)P_GetPtrp(li, DMU_FRONT_SECTOR);
535     Sector *backSec  = (Sector *)P_GetPtrp(li, DMU_BACK_SECTOR);
536 
537     if(frontSec && backSec)
538     {
539         Side *side = (Side *)P_GetPtrp(li, DMU_FRONT);
540         world_Material *mat = (world_Material *)P_GetPtrp(side, DMU_BOTTOM_MATERIAL);
541 
542         /**
543          * Emulate DOOM.exe behaviour. In the instance where no material is
544          * present, the height is taken from the very first texture.
545          */
546         if(!mat)
547         {
548             uri_s *textureUrn = Uri_NewWithPath2("urn:Textures:0", RC_NULL);
549             mat = DD_MaterialForTextureUri(textureUrn);
550             Uri_Delete(textureUrn);
551         }
552 
553         if(mat)
554         {
555             int height = P_GetIntp(mat, DMU_HEIGHT);
556 
557             if(height < params->minSize)
558             {
559                 params->minSize = height;
560                 params->foundLine = li;
561             }
562         }
563 
564         side = (Side *)P_GetPtrp(li, DMU_BACK);
565         mat  = (world_Material *)P_GetPtrp(side, DMU_BOTTOM_MATERIAL);
566         if(!mat)
567         {
568             uri_s *textureUrn = Uri_NewWithPath2("urn:Textures:0", RC_NULL);
569             mat = DD_MaterialForTextureUri(textureUrn);
570             Uri_Delete(textureUrn);
571         }
572 
573         if(mat)
574         {
575             int height = P_GetIntp(mat, DMU_HEIGHT);
576 
577             if(height < params->minSize)
578             {
579                 params->minSize = height;
580                 params->foundLine = li;
581             }
582         }
583     }
584 
585     return false; // Continue iteration.
586 }
587 
P_FindLineInSectorSmallestBottomMaterial(Sector * sec,int * val)588 Line* P_FindLineInSectorSmallestBottomMaterial(Sector *sec, int *val)
589 {
590     findlineinsectorsmallestbottommaterialparams_t params;
591 
592     params.baseSec = sec;
593     params.minSize = DDMAXINT;
594     params.foundLine = NULL;
595     P_Iteratep(sec, DMU_LINE, findLineInSectorSmallestBottomMaterial, &params);
596 
597     if(val)
598         *val = params.minSize;
599 
600     return params.foundLine;
601 }
602 
603 #if __JDOOM__ || __JDOOM64__ || __JHERETIC__
604 /**
605  * Find the first sector which shares a border to the specified sector
606  * and whose floor height matches that specified.
607  *
608  * @note Behaviour here is dependant upon the order of the sector-linked
609  * Lines list. This is necessary to emulate the flawed algorithm used in
610  * DOOM.exe In addition, this algorithm was further broken in Heretic as
611  * the test which compares floor heights was removed.
612  *
613  * @warning DO NOT USE THIS ANYWHERE ELSE!
614  */
615 
616 typedef struct findfirstneighbouratfloorheightparams_s {
617     Sector* baseSec;
618     coord_t height;
619     Sector* foundSec;
620 } findfirstneighbouratfloorheightparams_t;
621 
findFirstNeighbourAtFloorHeight(void * ptr,void * context)622 static int findFirstNeighbourAtFloorHeight(void* ptr, void* context)
623 {
624     Line* ln = (Line*) ptr;
625     findfirstneighbouratfloorheightparams_t* params =
626         (findfirstneighbouratfloorheightparams_t*) context;
627     Sector* other;
628 
629     other = P_GetNextSector(ln, params->baseSec);
630 # if __JDOOM__ || __JDOOM64__
631     if(other && FEQUAL(P_GetDoublep(other, DMU_FLOOR_HEIGHT), params->height))
632 # elif __JHERETIC__
633     if(other)
634 # endif
635     {
636         params->foundSec = other;
637         return true; // Stop iteration.
638     }
639 
640     return false; // Continue iteration.
641 }
642 
findSectorSurroundingAtFloorHeight(Sector * sec,coord_t height)643 static Sector *findSectorSurroundingAtFloorHeight(Sector *sec, coord_t height)
644 {
645     findfirstneighbouratfloorheightparams_t params;
646 
647     params.baseSec = sec;
648     params.foundSec = NULL;
649     params.height = height;
650     P_Iteratep(sec, DMU_LINE, findFirstNeighbourAtFloorHeight, &params);
651     return params.foundSec;
652 }
653 #endif
654 
655 #if __JHEXEN__
EV_DoFloor(Line *,byte * args,floortype_e floortype)656 int EV_DoFloor(Line * /*line*/, byte* args, floortype_e floortype)
657 #else
658 int EV_DoFloor(Line *line, floortype_e floortype)
659 #endif
660 {
661 #if !__JHEXEN__
662     Sector *frontsector;
663 #endif
664 #if __JHEXEN__
665     int tag = (int) args[0];
666 #else
667     int tag = P_ToXLine(line)->tag;
668 #endif
669 
670 #if __JDOOM64__
671     // jd64 > bitmip? wha?
672     coord_t bitmipL = 0, bitmipR = 0;
673     Side *front = (Side *)P_GetPtrp(line, DMU_FRONT);
674     Side *back  = (Side *)P_GetPtrp(line, DMU_BACK);
675 
676     bitmipL = P_GetDoublep(front, DMU_MIDDLE_MATERIAL_OFFSET_X);
677     if(back)
678         bitmipR = P_GetDoublep(back, DMU_MIDDLE_MATERIAL_OFFSET_X);
679     // < d64tc
680 #endif
681 
682     iterlist_t *list = P_GetSectorIterListForTag(tag, false);
683     if(!list) return 0;
684 
685     int rtn = 0;
686     floor_t *floor = 0;
687 
688     IterList_SetIteratorDirection(list, ITERLIST_FORWARD);
689     IterList_RewindIterator(list);
690 
691     Sector *sec;
692     while((sec = (Sector *)IterList_MoveIterator(list)))
693     {
694         xsector_t *xsec = P_ToXSector(sec);
695         // If already moving, keep going...
696         if(xsec->specialData)
697             continue;
698         rtn = 1;
699 
700         // New floor thinker.
701         floor = (floor_t *)Z_Calloc(sizeof(*floor), PU_MAP, 0);
702         floor->thinker.function = T_MoveFloor;
703         Thinker_Add(&floor->thinker);
704         xsec->specialData = floor;
705 
706         floor->type = floortype;
707         floor->crush = false;
708 #if __JHEXEN__
709         floor->speed = (float) args[1] * (1.0 / 8);
710         if(floortype == FT_LOWERMUL8INSTANT ||
711            floortype == FT_RAISEMUL8INSTANT)
712         {
713             floor->speed = 2000;
714         }
715 #endif
716         switch(floortype)
717         {
718         case FT_LOWER:
719             floor->state = FS_DOWN;
720             floor->sector = sec;
721 #if !__JHEXEN__
722             floor->speed = FLOORSPEED;
723 # if __JDOOM64__
724             floor->speed *= 4;
725 # endif
726 #endif
727             P_FindSectorSurroundingHighestFloor(sec, -500, &floor->floorDestHeight);
728             break;
729 
730         case FT_LOWERTOLOWEST:
731             floor->state = FS_DOWN;
732             floor->sector = sec;
733 #if !__JHEXEN__
734             floor->speed = FLOORSPEED;
735 # if __JDOOM64__
736             floor->speed *= 4;
737 # endif
738 #endif
739             P_FindSectorSurroundingLowestFloor(sec,
740                 P_GetDoublep(sec, DMU_FLOOR_HEIGHT), &floor->floorDestHeight);
741             break;
742 #if __JHEXEN__
743         case FT_LOWERBYVALUE:
744             floor->state = FS_DOWN;
745             floor->sector = sec;
746             floor->floorDestHeight =
747                 P_GetDoublep(sec, DMU_FLOOR_HEIGHT) - (coord_t) args[2];
748             break;
749 
750         case FT_LOWERMUL8INSTANT:
751         case FT_LOWERBYVALUEMUL8:
752             floor->state = FS_DOWN;
753             floor->sector = sec;
754             floor->floorDestHeight =
755                 P_GetDoublep(sec, DMU_FLOOR_HEIGHT) - (coord_t) args[2] * 8;
756             break;
757 #endif
758 #if !__JHEXEN__
759         case FT_LOWERTURBO:
760             floor->state = FS_DOWN;
761             floor->sector = sec;
762             floor->speed = FLOORSPEED * 4;
763             P_FindSectorSurroundingHighestFloor(sec, -500, &floor->floorDestHeight);
764 # if __JHERETIC__
765             floor->floorDestHeight += 8;
766 # else
767             if(!FEQUAL(floor->floorDestHeight, P_GetDoublep(sec, DMU_FLOOR_HEIGHT)))
768                 floor->floorDestHeight += 8;
769 # endif
770             break;
771 #endif
772 #if __JDOOM64__
773         case FT_TOHIGHESTPLUS8: // jd64
774             floor->state = FS_DOWN;
775             floor->sector = sec;
776             floor->speed = FLOORSPEED;
777             P_FindSectorSurroundingHighestFloor(sec, -500, &floor->floorDestHeight);
778             if(!FEQUAL(floor->floorDestHeight, P_GetDoublep(sec, DMU_FLOOR_HEIGHT)))
779                 floor->floorDestHeight += 8;
780             break;
781 
782         case FT_TOHIGHESTPLUSBITMIP: // jd64
783             if(bitmipR > 0)
784             {
785                 floor->state = FS_DOWN;
786                 floor->sector = sec;
787                 floor->speed = FLOORSPEED * bitmipL;
788                 P_FindSectorSurroundingHighestFloor(sec, -500, &floor->floorDestHeight);
789 
790                 if(!FEQUAL(floor->floorDestHeight, P_GetDoublep(sec, DMU_FLOOR_HEIGHT)))
791                     floor->floorDestHeight += bitmipR;
792             }
793             else
794             {
795                 floor->state = FS_UP;
796                 floor->sector = sec;
797                 floor->speed = FLOORSPEED * bitmipL;
798                 floor->floorDestHeight =
799                     P_GetDoublep(floor->sector, DMU_FLOOR_HEIGHT) - bitmipR;
800             }
801             break;
802 
803         case FT_CUSTOMCHANGESEC: // jd64
804             floor->state = FS_UP;
805             floor->sector = sec;
806             floor->speed = FLOORSPEED * 16;
807             floor->floorDestHeight = P_GetDoublep(floor->sector, DMU_FLOOR_HEIGHT);
808 
809             /// @fixme Should not clear the special like this!
810             P_ToXSector(sec)->special = bitmipR;
811             break;
812 #endif
813         case FT_RAISEFLOORCRUSH:
814 #if __JHEXEN__
815             floor->crush = (int) args[2]; // arg[2] = crushing value
816 #else
817             floor->crush = true;
818 #endif
819             floor->state = FS_UP;
820             floor->sector = sec;
821 #if !__JHEXEN__
822             floor->speed = FLOORSPEED;
823 # if __JDOOM64__
824             floor->speed *= 4;
825 # endif
826 #endif
827 #if __JHEXEN__
828             floor->floorDestHeight = P_GetDoublep(sec, DMU_CEILING_HEIGHT)-8;
829 #else
830             P_FindSectorSurroundingLowestCeiling(sec, (coord_t) MAXINT, &floor->floorDestHeight);
831 
832             if(floor->floorDestHeight > P_GetDoublep(sec, DMU_CEILING_HEIGHT))
833                 floor->floorDestHeight = P_GetDoublep(sec, DMU_CEILING_HEIGHT);
834 
835             floor->floorDestHeight -= 8 * (floortype == FT_RAISEFLOORCRUSH);
836 #endif
837             break;
838 
839         case FT_RAISEFLOOR:
840             floor->state = FS_UP;
841             floor->sector = sec;
842 #if !__JHEXEN__
843             floor->speed = FLOORSPEED;
844 # if __JDOOM64__
845             floor->speed *= 4;
846 # endif
847 #endif
848             P_FindSectorSurroundingLowestCeiling(sec, (coord_t) MAXINT, &floor->floorDestHeight);
849 
850             if(floor->floorDestHeight > P_GetDoublep(sec, DMU_CEILING_HEIGHT))
851                 floor->floorDestHeight = P_GetDoublep(sec, DMU_CEILING_HEIGHT);
852 
853 #if !__JHEXEN__
854             floor->floorDestHeight -= 8 * (floortype == FT_RAISEFLOORCRUSH);
855 #endif
856             break;
857 
858 #if __JDOOM__ || __JDOOM64__
859         case FT_RAISEFLOORTURBO:
860             floor->state = FS_UP;
861             floor->sector = sec;
862             floor->speed = FLOORSPEED * 4;
863 # if __JDOOM64__
864             floor->speed *= 2;
865 # endif
866             {
867             coord_t floorHeight, nextFloor;
868 
869             floorHeight = P_GetDoublep(sec, DMU_FLOOR_HEIGHT);
870             if(P_FindSectorSurroundingNextHighestFloor(sec, floorHeight, &nextFloor))
871                 floor->floorDestHeight = nextFloor;
872             else
873                 floor->floorDestHeight = floorHeight;
874             }
875             break;
876 #endif
877 
878         case FT_RAISEFLOORTONEAREST:
879             floor->state = FS_UP;
880             floor->sector = sec;
881 #if !__JHEXEN__
882             floor->speed = FLOORSPEED;
883 # if __JDOOM64__
884             floor->speed *= 8;
885 # endif
886 #endif
887             {
888             coord_t floorHeight, nextFloor;
889 
890             floorHeight = P_GetDoublep(sec, DMU_FLOOR_HEIGHT);
891             if(P_FindSectorSurroundingNextHighestFloor(sec, floorHeight, &nextFloor))
892                 floor->floorDestHeight = nextFloor;
893             else
894                 floor->floorDestHeight = floorHeight;
895             }
896             break;
897 
898 #if __JHEXEN__
899         case FT_RAISEFLOORBYVALUE:
900             floor->state = FS_UP;
901             floor->sector = sec;
902             floor->floorDestHeight =
903                 P_GetDoublep(sec, DMU_FLOOR_HEIGHT) + (coord_t) args[2];
904             break;
905 
906         case FT_RAISEMUL8INSTANT:
907         case FT_RAISEBYVALUEMUL8:
908             floor->state = FS_UP;
909             floor->sector = sec;
910             floor->floorDestHeight =
911                 P_GetDoublep(sec, DMU_FLOOR_HEIGHT) + (coord_t) args[2] * 8;
912             break;
913 
914         case FT_TOVALUEMUL8:
915             floor->sector = sec;
916             floor->floorDestHeight = (coord_t) args[2] * 8;
917             if(args[3])
918                 floor->floorDestHeight = -floor->floorDestHeight;
919 
920             if(floor->floorDestHeight > P_GetDoublep(sec, DMU_FLOOR_HEIGHT))
921                 floor->state = FS_UP;
922             else if(floor->floorDestHeight < P_GetDoublep(sec, DMU_FLOOR_HEIGHT))
923                 floor->state = FS_DOWN;
924             else
925                 rtn = 0; // Already at lowest position.
926 
927             break;
928 #endif
929 #if !__JHEXEN__
930         case FT_RAISE24:
931             floor->state = FS_UP;
932             floor->sector = sec;
933             floor->speed = FLOORSPEED;
934 # if __JDOOM64__
935             floor->speed *= 8;
936 # endif
937             floor->floorDestHeight =
938                 P_GetDoublep(floor->sector, DMU_FLOOR_HEIGHT) + 24;
939             break;
940 #endif
941 #if !__JHEXEN__
942         case FT_RAISE24ANDCHANGE:
943             floor->state = FS_UP;
944             floor->sector = sec;
945             floor->speed = FLOORSPEED;
946 # if __JDOOM64__
947             floor->speed *= 8;
948 # endif
949             floor->floorDestHeight =
950                 P_GetDoublep(floor->sector, DMU_FLOOR_HEIGHT) + 24;
951 
952             frontsector = (Sector *)P_GetPtrp(line, DMU_FRONT_SECTOR);
953 
954             P_SetPtrp(sec, DMU_FLOOR_MATERIAL,
955                       P_GetPtrp(frontsector, DMU_FLOOR_MATERIAL));
956 
957             xsec->special = P_ToXSector(frontsector)->special;
958             break;
959 
960 #if __JDOOM__ || __JDOOM64__
961         case FT_RAISE512:
962             floor->state = FS_UP;
963             floor->sector = sec;
964             floor->speed = FLOORSPEED;
965             floor->floorDestHeight =
966                 P_GetDoublep(floor->sector, DMU_FLOOR_HEIGHT) + 512;
967             break;
968 #endif
969 
970 # if __JDOOM64__
971         case FT_RAISE32: // jd64
972             floor->state = FS_UP;
973             floor->sector = sec;
974             floor->speed = FLOORSPEED * 8;
975             floor->floorDestHeight =
976                 P_GetDoublep(floor->sector, DMU_FLOOR_HEIGHT) + 32;
977             break;
978 # endif
979         case FT_RAISETOTEXTURE:
980             {
981             int             minSize = DDMAXINT;
982 
983             floor->state = FS_UP;
984             floor->sector = sec;
985             floor->speed = FLOORSPEED;
986             P_FindLineInSectorSmallestBottomMaterial(sec, &minSize);
987             floor->floorDestHeight =
988                 P_GetDoublep(floor->sector, DMU_FLOOR_HEIGHT) +
989                     (coord_t) minSize;
990             }
991             break;
992 
993         case FT_LOWERANDCHANGE:
994             floor->state = FS_DOWN;
995             floor->sector = sec;
996             floor->speed = FLOORSPEED;
997             P_FindSectorSurroundingLowestFloor(sec,
998                 P_GetDoublep(sec, DMU_FLOOR_HEIGHT), &floor->floorDestHeight);
999             floor->material = (world_Material *)P_GetPtrp(sec, DMU_FLOOR_MATERIAL);
1000 
1001             {
1002             Sector* otherSec = findSectorSurroundingAtFloorHeight(sec,
1003                 floor->floorDestHeight);
1004 
1005             if(otherSec)
1006             {
1007                 floor->material = (world_Material *)P_GetPtrp(otherSec, DMU_FLOOR_MATERIAL);
1008                 floor->newSpecial = P_ToXSector(otherSec)->special;
1009             }
1010             }
1011             break;
1012 #endif
1013         default:
1014 #if __JHEXEN__
1015             rtn = 0;
1016 #endif
1017             break;
1018         }
1019     }
1020 
1021 #if __JHEXEN__
1022     if(rtn && floor)
1023     {
1024         SN_StartSequence((mobj_t *)P_GetPtrp(floor->sector, DMU_EMITTER),
1025                          SEQ_PLATFORM + P_ToXSector(floor->sector)->seqType);
1026     }
1027 #endif
1028 
1029     return rtn;
1030 }
1031 
1032 #if __JHEXEN__
1033 struct findsectorneighborsforstairbuildparams_t
1034 {
1035     int type;
1036     coord_t height;
1037 };
1038 
1039 /// @return  @c 0= Continue iteration.
findSectorNeighborsForStairBuild(void * ptr,void * context)1040 static int findSectorNeighborsForStairBuild(void *ptr, void *context)
1041 {
1042     Line *li = (Line *) ptr;
1043     findsectorneighborsforstairbuildparams_t *params = (findsectorneighborsforstairbuildparams_t *) context;
1044 
1045     Sector *frontSec = (Sector *)P_GetPtrp(li, DMU_FRONT_SECTOR);
1046     if(!frontSec) return false;
1047 
1048     Sector *backSec = (Sector *)P_GetPtrp(li, DMU_BACK_SECTOR);
1049     if(!backSec) return false;
1050 
1051     xsector_t *xsec = P_ToXSector(frontSec);
1052     if(xsec->special == params->type + STAIR_SECTOR_TYPE && !xsec->specialData &&
1053        P_GetPtrp(frontSec, DMU_FLOOR_MATERIAL) == stairData.material &&
1054        P_GetIntp(frontSec, DMU_VALID_COUNT) != VALIDCOUNT)
1055     {
1056         enqueueStairSector(frontSec, params->type ^ 1, params->height);
1057         P_SetIntp(frontSec, DMU_VALID_COUNT, VALIDCOUNT);
1058     }
1059 
1060     xsec = P_ToXSector(backSec);
1061     if(xsec->special == params->type + STAIR_SECTOR_TYPE && !xsec->specialData &&
1062        P_GetPtrp(backSec, DMU_FLOOR_MATERIAL) == stairData.material &&
1063        P_GetIntp(backSec, DMU_VALID_COUNT) != VALIDCOUNT)
1064     {
1065         enqueueStairSector(backSec, params->type ^ 1, params->height);
1066         P_SetIntp(backSec, DMU_VALID_COUNT, VALIDCOUNT);
1067     }
1068 
1069     return false;
1070 }
1071 #endif
1072 
1073 #if __JDOOM__ || __JDOOM64__ || __JHERETIC__
1074 /**
1075  * @note Behaviour here is dependant upon the order of the sector-linked
1076  * LineDefs list. This is necessary to emulate the flawed algorithm used in
1077  * DOOM.exe In addition, this algorithm was further broken in Heretic as the
1078  * test which compares floor heights was removed.
1079  *
1080  * @warning DO NOT USE THIS ANYWHERE ELSE!
1081  */
1082 struct spreadsectorparams_t
1083 {
1084     Sector *baseSec;
1085     world_Material *material;
1086     Sector *foundSec;
1087     coord_t height, stairSize;
1088 };
1089 
1090 /// @return  0= continue iteration; otherwise stop.
findAdjacentSectorForSpread(void * ptr,void * context)1091 static int findAdjacentSectorForSpread(void *ptr, void *context)
1092 {
1093     Line *li = (Line *) ptr;
1094     spreadsectorparams_t *params = (spreadsectorparams_t *) context;
1095 
1096     if(!(P_ToXLine(li)->flags & ML_TWOSIDED))
1097         return false;
1098 
1099     Sector *frontSec = (Sector *)P_GetPtrp(li, DMU_FRONT_SECTOR);
1100     if(!frontSec) return false;
1101 
1102     if(params->baseSec != frontSec)
1103         return false;
1104 
1105     Sector *backSec = (Sector *)P_GetPtrp(li, DMU_BACK_SECTOR);
1106     if(!backSec) return false;
1107 
1108     if(P_GetPtrp(backSec, DMU_FLOOR_MATERIAL) != params->material)
1109         return false;
1110 
1111     /**
1112      * @note The placement of this step height addition is vital to ensure
1113      * the exact behaviour of the original DOOM algorithm. Logically this
1114      * should occur after the test below...
1115      */
1116     params->height += params->stairSize;
1117 
1118     xsector_t *xsec = P_ToXSector(backSec);
1119     if(xsec->specialData)
1120         return false;
1121 
1122     // This looks good.
1123     params->foundSec = backSec;
1124     return true;
1125 }
1126 #endif
1127 
1128 #if __JDOOM__ || __JDOOM64__ || __JHERETIC__
EV_BuildStairs(Line * line,stair_e type)1129 int EV_BuildStairs(Line *line, stair_e type)
1130 {
1131     xsector_t *xsec;
1132     coord_t height = 0, stairsize = 0;
1133     float speed = 0;
1134 
1135     iterlist_t *list = P_GetSectorIterListForTag(P_ToXLine(line)->tag, false);
1136     if(!list) return 0;
1137 
1138     int rtn = 0;
1139 
1140     IterList_SetIteratorDirection(list, ITERLIST_FORWARD);
1141     IterList_RewindIterator(list);
1142 
1143     spreadsectorparams_t params;
1144     Sector *sec;
1145     while((sec = (Sector *)IterList_MoveIterator(list)))
1146     {
1147         xsec = P_ToXSector(sec);
1148 
1149         // Already moving? If so, keep going...
1150         if(xsec->specialData)
1151             continue;
1152 
1153         // New floor thinker.
1154         rtn = 1;
1155         floor_t *floor = (floor_t *)Z_Calloc(sizeof(*floor), PU_MAP, 0);
1156         floor->thinker.function = T_MoveFloor;
1157         Thinker_Add(&floor->thinker);
1158 
1159         xsec->specialData = floor;
1160         floor->state = FS_UP;
1161         floor->sector = sec;
1162         switch(type)
1163         {
1164 #if __JHERETIC__
1165         case build8:
1166             stairsize = 8;
1167             break;
1168         case build16:
1169             stairsize = 16;
1170             break;
1171 #else
1172         case build8:
1173             speed = FLOORSPEED * .25;
1174             stairsize = 8;
1175             break;
1176         case turbo16:
1177             speed = FLOORSPEED * 4;
1178             stairsize = 16;
1179             break;
1180 #endif
1181         default:
1182             break;
1183         }
1184 #if __JHERETIC__
1185         floor->type = FT_RAISEBUILDSTEP;
1186         speed = FLOORSPEED;
1187         floor->speed = speed;
1188 #else
1189         floor->speed = speed;
1190 #endif
1191         height = P_GetDoublep(sec, DMU_FLOOR_HEIGHT) + stairsize;
1192         floor->floorDestHeight = height;
1193 
1194         // Find next sector to raise.
1195         // 1. Find 2-sided line with a front side in the same sector.
1196         // 2. Other side is the next sector to raise.
1197         params.baseSec   = sec;
1198         params.material  = (world_Material *)P_GetPtrp(sec, DMU_FLOOR_MATERIAL);
1199         params.foundSec  = 0;
1200         params.height    = height;
1201         params.stairSize = stairsize;
1202 
1203         while(P_Iteratep(params.baseSec, DMU_LINE, findAdjacentSectorForSpread, &params))
1204         {
1205             // We found another sector to spread to.
1206             floor = (floor_t *)Z_Calloc(sizeof(*floor), PU_MAP, 0);
1207             floor->thinker.function = T_MoveFloor;
1208             Thinker_Add(&floor->thinker);
1209 
1210             P_ToXSector(params.foundSec)->specialData = floor;
1211 #if __JHERETIC__
1212             floor->type = FT_RAISEBUILDSTEP;
1213 #endif
1214             floor->state = FS_UP;
1215             floor->speed = speed;
1216             floor->sector = params.foundSec;
1217             floor->floorDestHeight = params.height;
1218 
1219             // Prepare for the next pass.
1220             params.baseSec = params.foundSec;
1221             params.foundSec = NULL;
1222         }
1223     }
1224 
1225     return rtn;
1226 }
1227 #endif
1228 
1229 #if __JHEXEN__
enqueueStairSector(Sector * sec,int type,coord_t height)1230 static void enqueueStairSector(Sector* sec, int type, coord_t height)
1231 {
1232     if((stairQueueTail + 1) % STAIR_QUEUE_SIZE == stairQueueHead)
1233     {
1234         Con_Error("BuildStairs:  Too many branches located.\n");
1235     }
1236     stairQueue[stairQueueTail].sector = sec;
1237     stairQueue[stairQueueTail].type = type;
1238     stairQueue[stairQueueTail].height = height;
1239 
1240     stairQueueTail = (stairQueueTail + 1) % STAIR_QUEUE_SIZE;
1241 }
1242 
dequeueStairSector(int * type,coord_t * height)1243 static Sector* dequeueStairSector(int* type, coord_t* height)
1244 {
1245     Sector* sec;
1246 
1247     if(stairQueueHead == stairQueueTail)
1248     {
1249         // Queue is empty.
1250         return NULL;
1251     }
1252 
1253     *type = stairQueue[stairQueueHead].type;
1254     *height = stairQueue[stairQueueHead].height;
1255     sec = stairQueue[stairQueueHead].sector;
1256     stairQueueHead = (stairQueueHead + 1) % STAIR_QUEUE_SIZE;
1257 
1258     return sec;
1259 }
1260 
processStairSector(Sector * sec,int type,coord_t height,stairs_e stairsType,int delay,int resetDelay)1261 static void processStairSector(Sector *sec, int type, coord_t height,
1262     stairs_e stairsType, int delay, int resetDelay)
1263 {
1264     findsectorneighborsforstairbuildparams_t params;
1265 
1266     height += stairData.stepDelta;
1267 
1268     floor_t *floor = (floor_t *)Z_Calloc(sizeof(*floor), PU_MAP, 0);
1269     floor->thinker.function = T_MoveFloor;
1270     Thinker_Add(&floor->thinker);
1271     P_ToXSector(sec)->specialData = floor;
1272     floor->type = FT_RAISEBUILDSTEP;
1273     floor->state = (stairData.direction == -1? FS_DOWN : FS_UP);
1274     floor->sector = sec;
1275     floor->floorDestHeight = height;
1276     switch(stairsType)
1277     {
1278     case STAIRS_NORMAL:
1279         floor->speed = stairData.speed;
1280         if(delay)
1281         {
1282             floor->delayTotal = delay;
1283             floor->stairsDelayHeight = P_GetDoublep(sec, DMU_FLOOR_HEIGHT) + stairData.stepDelta;
1284             floor->stairsDelayHeightDelta = stairData.stepDelta;
1285         }
1286         floor->resetDelay = resetDelay;
1287         floor->resetDelayCount = resetDelay;
1288         floor->resetHeight = P_GetDoublep(sec, DMU_FLOOR_HEIGHT);
1289         break;
1290 
1291     case STAIRS_SYNC:
1292         floor->speed =
1293             stairData.speed * ((height - stairData.startHeight) / stairData.stepDelta);
1294         floor->resetDelay = delay; //arg4
1295         floor->resetDelayCount = delay;
1296         floor->resetHeight = P_GetDoublep(sec, DMU_FLOOR_HEIGHT);
1297         break;
1298 
1299     default:
1300         break;
1301     }
1302 
1303     SN_StartSequence((mobj_t *)P_GetPtrp(sec, DMU_EMITTER),
1304                      SEQ_PLATFORM + P_ToXSector(sec)->seqType);
1305 
1306     params.type   = type;
1307     params.height = height;
1308 
1309     // Find all neigboring sectors with sector special equal to type and add
1310     // them to the stairbuild queue.
1311     P_Iteratep(sec, DMU_LINE, findSectorNeighborsForStairBuild, &params);
1312 }
1313 #endif
1314 
1315 /**
1316  * @param direction  Positive = up. Negative = down.
1317  */
1318 #if __JHEXEN__
EV_BuildStairs(Line *,byte * args,int direction,stairs_e stairsType)1319 int EV_BuildStairs(Line * /*line*/, byte *args, int direction, stairs_e stairsType)
1320 {
1321     // Set global stairs variables
1322     stairData.textureChange = 0;
1323     stairData.direction = direction;
1324     stairData.stepDelta = stairData.direction * (coord_t) args[2];
1325     stairData.speed = (float) args[1] * (1.0 / 8);
1326 
1327     int resetDelay = (int) args[4];
1328     int delay      = (int) args[3];
1329     if(stairsType == STAIRS_PHASED)
1330     {
1331         stairData.startDelay = stairData.startDelayDelta = (int) args[3];
1332         resetDelay = stairData.startDelayDelta;
1333         delay = 0;
1334         stairData.textureChange = (int) args[4];
1335     }
1336 
1337     VALIDCOUNT++;
1338 
1339     iterlist_t *list = P_GetSectorIterListForTag((int) args[0], false);
1340     if(!list) return 0;
1341 
1342     IterList_SetIteratorDirection(list, ITERLIST_FORWARD);
1343     IterList_RewindIterator(list);
1344 
1345     Sector *sec;
1346     while((sec = (Sector *)IterList_MoveIterator(list)))
1347     {
1348         stairData.material    = (world_Material *)P_GetPtrp(sec, DMU_FLOOR_MATERIAL);
1349         stairData.startHeight = P_GetDoublep(sec, DMU_FLOOR_HEIGHT);
1350 
1351         // ALREADY MOVING?  IF SO, KEEP GOING...
1352         if(P_ToXSector(sec)->specialData)
1353             continue; // Already moving, so keep going...
1354 
1355         enqueueStairSector(sec, 0, P_GetDoublep(sec, DMU_FLOOR_HEIGHT));
1356         P_ToXSector(sec)->special = 0;
1357     }
1358 
1359     int type;
1360     coord_t height;
1361     while((sec = dequeueStairSector(&type, &height)))
1362     {
1363         processStairSector(sec, type, height, stairsType, delay, resetDelay);
1364     }
1365 
1366     return 1;
1367 }
1368 #endif
1369 
1370 #if __JDOOM__ || __JDOOM64__ || __JHERETIC__
1371 struct findfirsttwosidedparams_t
1372 {
1373     Sector *sector;
1374     Line *foundLine;
1375 };
1376 
findFirstTwosided(void * ptr,void * context)1377 static int findFirstTwosided(void *ptr, void *context)
1378 {
1379     Line *li = (Line *) ptr;
1380     findfirsttwosidedparams_t *params = (findfirsttwosidedparams_t *) context;
1381 
1382     Sector *backSec = (Sector *)P_GetPtrp(li, DMU_BACK_SECTOR);
1383 
1384     if(!(P_ToXLine(li)->flags & ML_TWOSIDED))
1385         return false;
1386 
1387     if(backSec && !(params->sector && backSec == params->sector))
1388     {
1389         params->foundLine = li;
1390         return true; // Stop iteration, this will do.
1391     }
1392 
1393     return false; // Continue iteration.
1394 }
1395 #endif
1396 
1397 #if __JDOOM__ || __JDOOM64__ || __JHERETIC__
EV_DoDonut(Line * line)1398 int EV_DoDonut(Line *line)
1399 {
1400     iterlist_t *list = P_GetSectorIterListForTag(P_ToXLine(line)->tag, false);
1401     if(!list) return 0;
1402 
1403     int rtn = 0;
1404 
1405     IterList_SetIteratorDirection(list, ITERLIST_FORWARD);
1406     IterList_RewindIterator(list);
1407 
1408     Sector *sec;
1409     while((sec = (Sector *)IterList_MoveIterator(list)))
1410     {
1411         findfirsttwosidedparams_t params;
1412 
1413         // Already moving? If so, keep going...
1414         if(P_ToXSector(sec)->specialData)
1415             continue;
1416 
1417         rtn = 1;
1418         Sector *outer = 0, *ring = 0;
1419 
1420         params.sector    = 0;
1421         params.foundLine = 0;
1422 
1423         if(P_Iteratep(sec, DMU_LINE, findFirstTwosided, &params))
1424         {
1425             ring = (Sector *)P_GetPtrp(params.foundLine, DMU_BACK_SECTOR);
1426             if(ring == sec)
1427             {
1428                 ring = (Sector *)P_GetPtrp(params.foundLine, DMU_FRONT_SECTOR);
1429             }
1430 
1431             params.sector = sec;
1432             params.foundLine = NULL;
1433             if(P_Iteratep(ring, DMU_LINE, findFirstTwosided, &params))
1434             {
1435                 outer = (Sector *)P_GetPtrp(params.foundLine, DMU_BACK_SECTOR);
1436             }
1437         }
1438 
1439         if(outer && ring)
1440         {
1441             // Found both parts of the donut.
1442             coord_t destHeight = P_GetDoublep(outer, DMU_FLOOR_HEIGHT);
1443 
1444             // Spawn rising slime.
1445             floor_t *floor = (floor_t *)Z_Calloc(sizeof(*floor), PU_MAP, 0);
1446             floor->thinker.function = T_MoveFloor;
1447             Thinker_Add(&floor->thinker);
1448 
1449             P_ToXSector(ring)->specialData = floor;
1450 
1451             floor->type            = FT_RAISEDONUT;
1452             floor->crush           = false;
1453             floor->state           = FS_UP;
1454             floor->sector          = ring;
1455             floor->speed           = FLOORSPEED * .5;
1456             floor->material        = (world_Material *)P_GetPtrp(outer, DMU_FLOOR_MATERIAL);
1457             floor->newSpecial      = 0;
1458             floor->floorDestHeight = destHeight;
1459 
1460             // Spawn lowering donut-hole.
1461             floor = (floor_t *)Z_Calloc(sizeof(*floor), PU_MAP, 0);
1462             floor->thinker.function = T_MoveFloor;
1463             Thinker_Add(&floor->thinker);
1464 
1465             P_ToXSector(sec)->specialData = floor;
1466             floor->type            = FT_LOWER;
1467             floor->crush           = false;
1468             floor->state           = FS_DOWN;
1469             floor->sector          = sec;
1470             floor->speed           = FLOORSPEED * .5;
1471             floor->floorDestHeight = destHeight;
1472         }
1473     }
1474 
1475     return rtn;
1476 }
1477 #endif
1478 
1479 #if __JHEXEN__
stopFloorCrush(thinker_t * th,void * context)1480 static int stopFloorCrush(thinker_t *th, void *context)
1481 {
1482     dd_bool *found = (dd_bool *) context;
1483     floor_t *floor = (floor_t *) th;
1484 
1485     if(floor->type == FT_RAISEFLOORCRUSH)
1486     {
1487         // Completely remove the crushing floor
1488         SN_StopSequence((mobj_t *)P_GetPtrp(floor->sector, DMU_EMITTER));
1489         P_ToXSector(floor->sector)->specialData = nullptr;
1490         P_NotifySectorFinished(P_ToXSector(floor->sector)->tag);
1491         Thinker_Remove(&floor->thinker);
1492         (*found) = true;
1493     }
1494 
1495     return false; // Continue iteration.
1496 }
1497 
EV_FloorCrushStop(Line *,byte *)1498 int EV_FloorCrushStop(Line * /*line*/, byte * /*args*/)
1499 {
1500     dd_bool found = false;
1501 
1502     Thinker_Iterate(T_MoveFloor, stopFloorCrush, &found);
1503 
1504     return (found? 1 : 0);
1505 }
1506 #endif
1507 
1508 #if __JHEXEN__ || __JDOOM64__
1509 # if __JHEXEN__
EV_DoFloorAndCeiling(Line * line,byte * args,int ftype,int ctype)1510 int EV_DoFloorAndCeiling(Line *line, byte *args, int ftype, int ctype)
1511 # else
1512 int EV_DoFloorAndCeiling(Line* line, int ftype, int ctype)
1513 # endif
1514 {
1515 # if __JHEXEN__
1516     int tag = (int) args[0];
1517 # else
1518     int tag = P_ToXLine(line)->tag;
1519 # endif
1520 
1521     iterlist_t *list = P_GetSectorIterListForTag(tag, false);
1522     if(!list) return 0;
1523 
1524     /**
1525      * @attention Kludge:
1526      * Due to the fact that sectors can only have one special thinker
1527      * linked at a time, this routine manually removes the link before
1528      * then creating a second thinker for the sector.
1529      * In order to commonize this we should maintain seperate links in
1530      * xsector_t for each type of special (not thinker type) i.e:
1531      *
1532      *   floor, ceiling, lightlevel
1533      *
1534      * @note: Floor and ceiling are capable of moving at different speeds
1535      * and with different target heights, we must remain compatible.
1536      */
1537 
1538 # if __JHEXEN__
1539     dd_bool floor = EV_DoFloor(line, args, floortype_e(ftype));
1540 # else
1541     dd_bool floor = EV_DoFloor(line, floortype_e(ftype));
1542 # endif
1543 
1544     IterList_SetIteratorDirection(list, ITERLIST_FORWARD);
1545     IterList_RewindIterator(list);
1546 
1547     Sector *sec;
1548     while((sec = (Sector *)IterList_MoveIterator(list)))
1549     {
1550         P_ToXSector(sec)->specialData = 0;
1551     }
1552 
1553 # if __JHEXEN__
1554     dd_bool ceiling = EV_DoCeiling(line, args, ceilingtype_e(ctype));
1555 # else
1556     dd_bool ceiling = EV_DoCeiling(line, ceilingtype_e(ctype));
1557 # endif
1558     // < KLUDGE
1559 
1560     return (floor | ceiling);
1561 }
1562 #endif
1563