1 /** @file p_xgsec.cpp  Extended generalized sector types.
2  *
3  * @authors Copyright © 2003-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4  * @authors Copyright © 2005-2014 Daniel Swanson <danij@dengine.net>
5  *
6  * @par License
7  * GPL: http://www.gnu.org/licenses/gpl.html
8  *
9  * <small>This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by the
11  * Free Software Foundation; either version 2 of the License, or (at your
12  * option) any later version. This program is distributed in the hope that it
13  * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
15  * Public License for more details. You should have received a copy of the GNU
16  * General Public License along with this program; if not, write to the Free
17  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18  * 02110-1301 USA</small>
19  */
20 
21 #if __JDOOM__ || __JHERETIC__ || __JDOOM64__
22 
23 #include <math.h>
24 #include <ctype.h>
25 #include <stdio.h>
26 #include <string.h>
27 
28 #if __JDOOM__
29 #  include "jdoom.h"
30 #elif __JDOOM64__
31 #  include "jdoom64.h"
32 #elif __JHERETIC__
33 #  include "jheretic.h"
34 #endif
35 
36 #include "dmu_lib.h"
37 #include "p_mapsetup.h"
38 #include "p_xgline.h"
39 #include "p_xgsec.h"
40 #include "g_common.h"
41 #include "p_map.h"
42 #include "mobj.h"
43 #include "p_actor.h"
44 #include "p_mapspec.h"
45 #include "p_sound.h"
46 #include "p_terraintype.h"
47 #include "p_tick.h"
48 
49 #define MAX_VALS        128
50 
51 #define SIGN(x)         ((x)>0? 1 : (x)<0? -1 : 0)
52 
53 #define ISFUNC(fn)      (fn->func && fn->func[fn->pos])
54 #define UPDFUNC(fn)     ((ISFUNC(fn) || fn->link))
55 
56 #define SPREFTYPESTR(reftype) (reftype == SPREF_NONE? "NONE" \
57         : reftype == SPREF_MY_FLOOR? "MY FLOOR" \
58         : reftype == SPREF_MY_CEILING? "MY CEILING" \
59         : reftype == SPREF_ORIGINAL_FLOOR? "ORIGINAL FLOOR" \
60         : reftype == SPREF_ORIGINAL_CEILING? "ORIGINAL CEILING" \
61         : reftype == SPREF_CURRENT_FLOOR? "CURRENT FLOOR" \
62         : reftype == SPREF_CURRENT_CEILING? "CURRENT CEILING" \
63         : reftype == SPREF_HIGHEST_FLOOR? "HIGHEST FLOOR" \
64         : reftype == SPREF_HIGHEST_CEILING? "HIGHEST CEILING" \
65         : reftype == SPREF_LOWEST_FLOOR? "LOWEST FLOOR" \
66         : reftype == SPREF_LOWEST_CEILING? "LOWEST CEILING" \
67         : reftype == SPREF_NEXT_HIGHEST_FLOOR? "NEXT HIGHEST FLOOR" \
68         : reftype == SPREF_NEXT_HIGHEST_CEILING? "NEXT HIGHEST CEILING" \
69         : reftype == SPREF_NEXT_LOWEST_FLOOR? "NEXT LOWEST FLOOR" \
70         : reftype == SPREF_NEXT_LOWEST_CEILING? "NEXT LOWEST CEILING" \
71         : reftype == SPREF_MIN_BOTTOM_MATERIAL? "MIN BOTTOM MATERIAL" \
72         : reftype == SPREF_MIN_MID_MATERIAL? "MIN MIDDLE MATERIAL" \
73         : reftype == SPREF_MIN_TOP_MATERIAL? "MIN TOP MATERIAL" \
74         : reftype == SPREF_MAX_BOTTOM_MATERIAL? "MAX BOTTOM MATERIAL" \
75         : reftype == SPREF_MAX_MID_MATERIAL? "MAX MIDDLE MATERIAL" \
76         : reftype == SPREF_MAX_TOP_MATERIAL? "MAX TOP MATERIAL" \
77         : reftype == SPREF_SECTOR_TAGGED_FLOOR? "SECTOR TAGGED FLOOR" \
78         : reftype == SPREF_LINE_TAGGED_FLOOR? "LINE TAGGED FLOOR" \
79         : reftype == SPREF_TAGGED_FLOOR? "TAGGED FLOOR" \
80         : reftype == SPREF_ACT_TAGGED_FLOOR? "ACT TAGGED FLOOR" \
81         : reftype == SPREF_INDEX_FLOOR? "INDEXED FLOOR" \
82         : reftype == SPREF_SECTOR_TAGGED_CEILING? "SECTOR TAGGED CEILING" \
83         : reftype == SPREF_LINE_TAGGED_CEILING? "LINE TAGGED CEILING" \
84         : reftype == SPREF_TAGGED_CEILING? "TAGGED CEILING" \
85         : reftype == SPREF_ACT_TAGGED_CEILING? "ACT TAGGED CEILING" \
86         : reftype == SPREF_INDEX_CEILING? "INDEXED CEILING" \
87         : reftype == SPREF_BACK_FLOOR? "BACK FLOOR" \
88         : reftype == SPREF_BACK_CEILING? "BACK CEILING" \
89         : reftype == SPREF_SPECIAL? "SPECIAL" \
90         : reftype == SPREF_LINE_ACT_TAGGED_FLOOR? "LINE ACT TAGGED FLOOR" \
91         : reftype == SPREF_LINE_ACT_TAGGED_CEILING? "LINE ACT TAGGED CEILING" : "???")
92 
93 #define TO_DMU_COLOR(x) (x == 0? DMU_COLOR_RED \
94         : x == 1? DMU_COLOR_GREEN \
95         : DMU_COLOR_BLUE)
96 
97 #define TO_DMU_CEILING_COLOR(x) (x == 0? DMU_CEILING_COLOR_RED \
98         : x == 1? DMU_CEILING_COLOR_GREEN \
99         : DMU_CEILING_COLOR_BLUE)
100 
101 #define TO_DMU_FLOOR_COLOR(x) (x == 0? DMU_FLOOR_COLOR_RED \
102         : x == 1? DMU_FLOOR_COLOR_GREEN \
103         : DMU_FLOOR_COLOR_BLUE)
104 
105 void XS_DoChain(Sector *sec, int ch, int activating, void *actThing);
106 
107 /**
108  * Lookup a sectortype_t with the given @a id and if found - copy it into @a outBuffer.
109  *
110  * @param id         Unique identifier of the sector type.
111  * @param outBuffer  If found the sector type info is written here.
112  *
113  * @return  @c true if a sectortype_t was found.
114  */
XS_GetType(int id,sectortype_t & outBuffer)115 bool XS_GetType(int id, sectortype_t &outBuffer)
116 {
117     // Try the DDXGDATA lump first.
118     if(sectortype_t *found = XG_GetLumpSector(id))
119     {
120         std::memcpy(&outBuffer, found, sizeof(outBuffer));
121         return true;
122     }
123 
124     // Try the DED database.
125     return Def_Get(DD_DEF_SECTOR_TYPE, de::String::number(id).toUtf8().constData(), &outBuffer);
126 }
127 
XF_Init(Sector * sec,function_t * fn,char * func,int min,int max,float scale,float offset)128 void XF_Init(Sector *sec, function_t *fn, char *func, int min, int max,
129              float scale, float offset)
130 {
131     xsector_t          *xsec = P_ToXSector(sec);
132 
133     memset(fn, 0, sizeof(*fn));
134 
135     if(!func)
136         return;
137 
138     // Check for links.
139     if(func[0] == '=')
140     {
141         switch(tolower(func[1]))
142         {
143         case 'r':
144             fn->link = &xsec->xg->rgb[0];
145             break;
146 
147         case 'g':
148             fn->link = &xsec->xg->rgb[1];
149             break;
150 
151         case 'b':
152             fn->link = &xsec->xg->rgb[2];
153             break;
154 
155         case 'f':
156             fn->link = &xsec->xg->plane[XGSP_FLOOR];
157             break;
158 
159         case 'c':
160             fn->link = &xsec->xg->plane[XGSP_CEILING];
161             break;
162 
163         case 'l':
164             fn->link = &xsec->xg->light;
165             break;
166 
167         default:
168             Con_Error("XF_Init: Bad linked func (%s).\n", func);
169         }
170         return;
171     }
172 
173     // Check for offsets to current values.
174     if(func[0] == '+')
175     {
176         /**
177          * @note The original value ranges must be maintained due to the cross linking
178          * between sector function types i.e:
179          * - RGB = 0 > 254
180          * - light = 0 > 254
181          * - planeheight = -32768 > 32768
182          */
183         switch(func[1])
184         {
185         case 'r':
186             offset += 255.f * xsec->origRGB[0];
187             break;
188 
189         case 'g':
190             offset += 255.f * xsec->origRGB[1];
191             break;
192 
193         case 'b':
194             offset += 255.f * xsec->origRGB[2];
195             break;
196 
197         case 'l':
198             offset += 255.0f * xsec->origLight;
199             break;
200 
201         case 'f':
202             offset += xsec->SP_floororigheight;
203             break;
204 
205         case 'c':
206             offset += xsec->SP_ceilorigheight;
207             break;
208 
209         default:
210             Con_Error("XF_Init: Bad preset offset (%s).\n", func);
211         }
212         fn->func = func + 2;
213     }
214     else
215     {
216         fn->func = func;
217     }
218 
219     fn->timer = -1; // The first step musn't skip the first value.
220     fn->maxTimer = XG_RandomInt(min, max);
221     fn->minInterval = min;
222     fn->maxInterval = max;
223     fn->scale = scale;
224     fn->offset = offset;
225     // Make sure oldvalue is out of range.
226     fn->oldValue = -scale + offset;
227 }
228 
XLTrav_LineAngle(Line * line,dd_bool dummy,void * context,void * context2,mobj_t * activator)229 int C_DECL XLTrav_LineAngle(Line* line, dd_bool dummy, void* context,
230     void* context2, mobj_t* activator)
231 {
232     DENG_UNUSED(dummy);
233     DENG_UNUSED(activator);
234 
235     Sector* sec = (Sector *) context;
236     coord_t d1[2];
237 
238     if(P_GetPtrp(line, DMU_FRONT_SECTOR) != sec &&
239        P_GetPtrp(line, DMU_BACK_SECTOR)  != sec)
240         return true; // Wrong sector, keep looking.
241 
242     P_GetDoublepv(line, DMU_DXY, d1);
243     *(angle_t*) context2 = M_PointXYToAngle2(0, 0, d1[0], d1[1]);
244 
245     return false; // Stop looking after first hit.
246 }
247 
findXSThinker(thinker_t * th,void * context)248 int findXSThinker(thinker_t *th, void *context)
249 {
250     xsthinker_t *xs = (xsthinker_t *) th;
251     DENG2_ASSERT(xs);
252 
253     if(xs->sector == (Sector *) context)
254     {
255         return true; // Stop iteration, we've found it.
256     }
257 
258     return false; // Continue iteration.
259 }
260 
destroyXSThinker(thinker_t * th,void * context)261 int destroyXSThinker(thinker_t *th, void *context)
262 {
263     xsthinker_t *xs = (xsthinker_t *) th;
264     DENG2_ASSERT(xs);
265 
266     if(xs->sector == (Sector *) context)
267     {
268         Thinker_Remove(&xs->thinker);
269         return true; // Stop iteration, we're done.
270     }
271 
272     return false; // Continue iteration.
273 }
274 
XS_UpdateLight(Sector * sec)275 static void XS_UpdateLight(Sector *sec)
276 {
277     int         i;
278     float       c, lightlevel;
279     xgsector_t *xg;
280     function_t *fn;
281 
282     xg = P_ToXSector(sec)->xg;
283 
284     // Light intensity.
285     fn = &xg->light;
286     if (UPDFUNC(fn))
287     {
288         lightlevel = MINMAX_OF(0, fn->value / 255.f, 1);
289         P_SetFloatp(sec, DMU_LIGHT_LEVEL, lightlevel);
290     }
291 
292     // Red, green and blue.
293     for (i = 0; i < 3; ++i)
294     {
295         fn = &xg->rgb[i];
296         if (UPDFUNC(fn))
297         {
298             c = MINMAX_OF(0, fn->value / 255.f, 1);
299             P_SetFloatp(sec, TO_DMU_COLOR(i), c);
300         }
301     }
302 }
303 
XS_SetSectorType(Sector * sec,int special)304 void XS_SetSectorType(Sector *sec, int special)
305 {
306     LOG_AS("XS_SetSectorType");
307 
308     xsector_t *xsec = P_ToXSector(sec);
309     if(!xsec) return;
310 
311     sectortype_t secType;
312     if(XS_GetType(special, secType))
313     {
314         LOG_MAP_MSG_XGDEVONLY2("Sector %i, type %i", P_ToIndex(sec) << special);
315 
316         xsec->special = special;
317 
318         // All right, do the init.
319         if(!xsec->xg)
320         {
321             xsec->xg = (xgsector_t *) Z_Malloc(sizeof(xgsector_t), PU_MAP, 0);
322         }
323         de::zapPtr(xsec->xg);
324 
325         // Get the type info.
326         std::memcpy(&xsec->xg->info, &secType, sizeof(secType));
327 
328         // Init the state.
329         xgsector_t *xg     = xsec->xg;
330         sectortype_t *info = &xsec->xg->info;
331 
332         // Init timer so ambient doesn't play immediately at map start.
333         xg->timer = XG_RandomInt(FLT2TIC(xg->info.soundInterval[0]),
334                                  FLT2TIC(xg->info.soundInterval[1]));
335 
336         // Light function.
337         XF_Init(sec, &xg->light, info->lightFunc, info->lightInterval[0],
338                 info->lightInterval[1], 255, 0);
339 
340         // Color functions.
341         for(int i = 0; i < 3; ++i)
342         {
343             XF_Init(sec, &xg->rgb[i], info->colFunc[i],
344                     info->colInterval[i][0], info->colInterval[i][1], 255, 0);
345         }
346 
347         // Plane functions / floor.
348         XF_Init(sec, &xg->plane[XGSP_FLOOR], info->floorFunc,
349                 info->floorInterval[0], info->floorInterval[1],
350                 info->floorMul, info->floorOff);
351         XF_Init(sec, &xg->plane[XGSP_CEILING], info->ceilFunc,
352                 info->ceilInterval[0], info->ceilInterval[1],
353                 info->ceilMul, info->ceilOff);
354 
355         // Derive texmove angle from first act-tagged line?
356         if((info->flags & STF_ACT_TAG_MATERIALMOVE) ||
357            (info->flags & STF_ACT_TAG_WIND))
358         {
359             angle_t angle = 0;
360 
361             // -1 to support binary XG data with old flag values.
362             XL_TraverseLines(0, (xgDataLumps? LREF_TAGGED -1: LREF_TAGGED),
363                              info->actTag, sec, &angle,
364                              NULL, XLTrav_LineAngle);
365 
366             // Convert to degrees.
367             if(info->flags & STF_ACT_TAG_MATERIALMOVE)
368             {
369                 info->materialMoveAngle[0] = info->materialMoveAngle[1] =
370                     angle / (float) ANGLE_MAX *360;
371             }
372 
373             if(info->flags & STF_ACT_TAG_WIND)
374             {
375                 info->windAngle = angle / (float) ANGLE_MAX *360;
376             }
377         }
378 
379         // If there is not already an xsthinker for this sector, create one.
380         if(!Thinker_Iterate((thinkfunc_t) XS_Thinker, findXSThinker, sec))
381         {
382             // Not created one yet.
383             ThinkerT<xsthinker_t> xs(Thinker::AllocateMemoryZone);
384             xs.function = XS_Thinker;
385             xs->sector  = sec;
386             Thinker_Add(xs.Thinker::take());
387         }
388     }
389     else
390     {
391         LOG_MAP_MSG_XGDEVONLY2("Sector %i, NORMAL TYPE %i", P_ToIndex(sec) << special);
392 
393         // If there is an xsthinker for this, destroy it.
394         Thinker_Iterate((thinkfunc_t) XS_Thinker, destroyXSThinker, sec);
395 
396         // Free previously allocated XG data.
397         Z_Free(xsec->xg); xsec->xg = nullptr;
398 
399         // Just set it, then. Must be a standard sector type...
400         // Mind you, we're not going to spawn any standard flash funcs
401         // or anything.
402         xsec->special = special;
403     }
404 }
405 
XS_Init()406 void XS_Init()
407 {
408     /*  // Clients rely on the server, they don't do XG themselves.
409     if(IS_CLIENT) return; */
410 
411     if(numsectors <= 0) return;
412 
413     for(int i = 0; i < numsectors; ++i)
414     {
415         Sector *sec     = (Sector *) P_ToPtr(DMU_SECTOR, i);
416         xsector_t *xsec = P_ToXSector(sec);
417 
418         P_GetFloatpv(sec, DMU_COLOR, xsec->origRGB);
419 
420         xsec->SP_floororigheight = P_GetDoublep(sec, DMU_FLOOR_HEIGHT);
421         xsec->SP_ceilorigheight  = P_GetDoublep(sec, DMU_CEILING_HEIGHT);
422         xsec->origLight = P_GetFloatp(sec, DMU_LIGHT_LEVEL);
423 
424         // Initialize XG data for this sector.
425         XS_SetSectorType(sec, xsec->special);
426     }
427 
428     // Run the first tick now, so sector lights are initialized according to the functions.
429     P_IterateThinkers(XS_Thinker, [](thinker_t *th) {
430         XS_Thinker(th);
431         return de::LoopContinue;
432     });
433 }
434 
XS_SectorSound(Sector * sec,int soundId)435 void XS_SectorSound(Sector *sec, int soundId)
436 {
437     LOG_AS("XS_SectorSound");
438     if(!sec || !soundId) return;
439     LOG_MAP_MSG_XGDEVONLY2("Play Sound ID (%i) in Sector ID (%i)", soundId << P_ToIndex(sec));
440     S_SectorSound(sec, soundId);
441 }
442 
XS_PlaneSound(Plane * pln,int soundId)443 void XS_PlaneSound(Plane *pln, int soundId)
444 {
445     LOG_AS("XS_PlaneSound");
446     if(!pln || !soundId) return;
447     LOG_MAP_MSG_XGDEVONLY2("Play Sound ID (%i) in Sector ID (%i)",
448             soundId << P_ToIndex(P_GetPtrp(pln, DMU_SECTOR)));
449     S_PlaneSound(pln, soundId);
450 }
451 
XS_MoverStopped(xgplanemover_t * mover,dd_bool done)452 void XS_MoverStopped(xgplanemover_t *mover, dd_bool done)
453 {
454     DENG2_ASSERT(mover);
455     LOG_AS("XS_MoverStopped");
456     xline_t *origin = P_ToXLine(mover->origin);
457 
458     LOG_MAP_MSG_XGDEVONLY2("Sector %i (done=%i, origin line=%i)",
459            P_ToIndex(mover->sector) << done << P_ToIndex(mover->origin));
460 
461     if(done)
462     {
463         if((mover->flags & PMF_ACTIVATE_WHEN_DONE) && mover->origin)
464         {
465             XL_ActivateLine(true, &origin->xg->info, mover->origin, 0,
466                             XG_DummyThing(), XLE_AUTO);
467         }
468 
469         if((mover->flags & PMF_DEACTIVATE_WHEN_DONE) && mover->origin)
470         {
471             XL_ActivateLine(false, &origin->xg->info, mover->origin, 0,
472                             XG_DummyThing(), XLE_AUTO);
473         }
474 
475         // Remove this thinker.
476         Thinker_Remove((thinker_t *) mover);
477     }
478     else
479     {
480         // Normally we just wait, but if...
481         if((mover->flags & PMF_ACTIVATE_ON_ABORT) && mover->origin)
482         {
483             XL_ActivateLine(true, &origin->xg->info, mover->origin, 0,
484                             XG_DummyThing(), XLE_AUTO);
485         }
486 
487         if((mover->flags & PMF_DEACTIVATE_ON_ABORT) && mover->origin)
488         {
489             XL_ActivateLine(false, &origin->xg->info, mover->origin, 0,
490                             XG_DummyThing(), XLE_AUTO);
491         }
492 
493         if(mover->flags & (PMF_ACTIVATE_ON_ABORT | PMF_DEACTIVATE_ON_ABORT))
494         {
495             // Destroy this mover.
496             Thinker_Remove((thinker_t *) mover);
497         }
498     }
499 }
500 
501 /**
502  * A thinker function for plane movers.
503  */
XS_PlaneMover(xgplanemover_t * mover)504 void XS_PlaneMover(xgplanemover_t *mover)
505 {
506     DENG2_ASSERT(mover && mover->sector);
507     coord_t ceil    = P_GetDoublep(mover->sector, DMU_CEILING_HEIGHT);
508     coord_t floor   = P_GetDoublep(mover->sector, DMU_FLOOR_HEIGHT);
509     xsector_t *xsec = P_ToXSector(mover->sector);
510     dd_bool docrush = (mover->flags & PMF_CRUSH) != 0;
511     dd_bool follows = (mover->flags & PMF_OTHER_FOLLOWS) != 0;
512     dd_bool setorig = (mover->flags & PMF_SET_ORIGINAL) != 0;
513     int res, res2, dir;
514 
515     // Play movesound when timer goes to zero.
516     if(mover->timer-- <= 0)
517     {
518         // Clear the wait flag.
519         if(mover->flags & PMF_WAIT)
520         {
521             mover->flags &= ~PMF_WAIT;
522             // Play a sound.
523             XS_PlaneSound((Plane *) P_GetPtrp(mover->sector, mover->ceiling? DMU_CEILING_PLANE : DMU_FLOOR_PLANE),
524                           mover->startSound);
525         }
526 
527         mover->timer = XG_RandomInt(mover->minInterval, mover->maxInterval);
528         XS_PlaneSound((Plane *) P_GetPtrp(mover->sector, mover->ceiling? DMU_CEILING_PLANE : DMU_FLOOR_PLANE),
529                       mover->moveSound);
530     }
531 
532     // Are we waiting?
533     if(mover->flags & PMF_WAIT) return;
534 
535     // Determine move direction.
536     if((mover->destination - (mover->ceiling ? ceil : floor)) > 0)
537         dir = 1; // Moving up.
538     else
539         dir = -1; // Moving down.
540 
541     // Do the move.
542     res = T_MovePlane(mover->sector, mover->speed, mover->destination,
543                       docrush, mover->ceiling, dir);
544 
545     // Should we update origheight?
546     if(setorig)
547     {
548         xsec->planes[mover->ceiling? PLN_CEILING:PLN_FLOOR].origHeight =
549             P_GetDoublep(mover->sector,
550                         mover->ceiling? DMU_CEILING_HEIGHT:DMU_FLOOR_HEIGHT);
551     }
552 
553     if(follows)
554     {
555         coord_t off = (mover->ceiling? floor - ceil : ceil - floor);
556 
557         res2 = T_MovePlane(mover->sector, mover->speed,
558                            mover->destination + off, docrush,
559                            !mover->ceiling, dir);
560 
561         // Should we update origheight?
562         if(setorig)
563         {
564             xsec->planes[(!mover->ceiling)? PLN_CEILING:PLN_FLOOR].origHeight =
565                 P_GetDoublep(mover->sector,
566                             (!mover->ceiling)? DMU_CEILING_HEIGHT:DMU_FLOOR_HEIGHT);
567         }
568 
569         if(res2 == crushed)
570             res = crushed;
571     }
572 
573     if(res == pastdest)
574     {
575         // Move has finished.
576         XS_MoverStopped(mover, true);
577 
578         // The move is done. Do end stuff.
579         if(mover->setMaterial)
580         {
581             XS_ChangePlaneMaterial(*mover->sector, mover->ceiling, *mover->setMaterial);
582         }
583 
584         if(mover->setSectorType >= 0)
585         {
586             XS_SetSectorType(mover->sector, mover->setSectorType);
587         }
588 
589         // Play sound?
590         XS_PlaneSound((Plane *) P_GetPtrp(mover->sector, mover->ceiling? DMU_CEILING_PLANE : DMU_FLOOR_PLANE),
591                       mover->endSound);
592     }
593     else if(res == crushed)
594     {
595         if(mover->flags & PMF_CRUSH)
596         {
597             // We're crushing things.
598             mover->speed = mover->crushSpeed;
599         }
600         else
601         {
602             // Make sure both the planes are where we started from.
603             if((!mover->ceiling || follows) &&
604                !FEQUAL(P_GetDoublep(mover->sector, DMU_FLOOR_HEIGHT), floor))
605             {
606                 T_MovePlane(mover->sector, mover->speed, floor, docrush, false, -dir);
607             }
608 
609             if((mover->ceiling || follows) &&
610                !FEQUAL(P_GetDoublep(mover->sector, DMU_CEILING_HEIGHT), ceil))
611             {
612                 T_MovePlane(mover->sector, mover->speed, ceil, docrush, true, -dir);
613             }
614 
615             XS_MoverStopped(mover, false);
616         }
617     }
618 }
619 
620 typedef struct {
621     Sector*             sec;
622     dd_bool             ceiling;
623 } stopplanemoverparams_t;
624 
stopPlaneMover(thinker_t * th,void * context)625 static int stopPlaneMover(thinker_t* th, void* context)
626 {
627     stopplanemoverparams_t* params = (stopplanemoverparams_t*) context;
628     xgplanemover_t*     mover = (xgplanemover_t *) th;
629 
630     if(mover->sector == params->sec &&
631        mover->ceiling == params->ceiling)
632     {
633         XS_MoverStopped(mover, false);
634         Thinker_Remove(th); // Remove it.
635     }
636 
637     return false; // Continue iteration.
638 }
639 
640 /**
641  * Returns a new thinker for handling the specified plane. Removes any
642  * existing thinkers associated with the plane.
643  */
XS_GetPlaneMover(Sector * sec,dd_bool ceiling)644 xgplanemover_t *XS_GetPlaneMover(Sector *sec, dd_bool ceiling)
645 {
646     stopplanemoverparams_t params;
647 
648     params.sec = sec;
649     params.ceiling = ceiling;
650     Thinker_Iterate((thinkfunc_t) XS_PlaneMover, stopPlaneMover, &params);
651 
652     // Allocate a new thinker.
653     ThinkerT<xgplanemover_t> mover(Thinker::AllocateMemoryZone);
654     mover.function = (thinkfunc_t) XS_PlaneMover;
655 
656     xgplanemover_t *th = mover.take();
657     th->sector  = sec;
658     th->ceiling = ceiling;
659 
660     Thinker_Add(&th->thinker);
661 
662     return th;
663 }
664 
XS_ChangePlaneMaterial(Sector & sector,bool ceiling,world_Material & newMaterial)665 void XS_ChangePlaneMaterial(Sector &sector, bool ceiling, world_Material &newMaterial)
666 {
667     LOG_AS("XS_ChangePlaneMaterial");
668     LOG_MAP_MSG_XGDEVONLY2("Sector %i, %s, texture %i",
669            P_ToIndex(&sector) << (ceiling ? "ceiling" : "floor") << P_ToIndex(&newMaterial));
670 
671     P_SetPtrp(&sector, ceiling ? DMU_CEILING_MATERIAL : DMU_FLOOR_MATERIAL, &newMaterial);
672 }
673 
XS_ChangePlaneColor(Sector & sector,bool ceiling,de::Vector3f const & newColor,bool isDelta)674 void XS_ChangePlaneColor(Sector &sector, bool ceiling, de::Vector3f const &newColor, bool isDelta)
675 {
676     LOG_AS("XS_ChangePlaneColor");
677     LOG_MAP_MSG_XGDEVONLY2("Sector %i, %s, tintColor:%s",
678            P_ToIndex(&sector) << (ceiling ? "ceiling" : "floor") << newColor.asText());
679 
680     float rgb[3];
681     if (isDelta)
682     {
683         P_GetFloatpv(&sector, ceiling ? DMU_CEILING_COLOR : DMU_FLOOR_COLOR, rgb);
684         for (int i = 0; i < 3; ++i) { rgb[i] += newColor[i]; }
685     }
686     else
687     {
688         newColor.decompose(rgb);
689     }
690     P_SetFloatpv(&sector, ceiling? DMU_CEILING_COLOR : DMU_FLOOR_COLOR, rgb); // will clamp
691 }
692 
FindMaxOf(int * list,uint num)693 uint FindMaxOf(int *list, uint num)
694 {
695     uint            i, idx = 0;
696     int             max = list[0];
697 
698     for(i = 1; i < num; ++i)
699     {
700         if(list[i] > max)
701         {
702             max = list[i];
703             idx = i;
704         }
705     }
706 
707     return idx;
708 }
709 
FindMinOf(int * list,uint num)710 uint FindMinOf(int *list, uint num)
711 {
712     uint        i, idx = 0;
713     int         min = list[0];
714 
715     for(i = 1; i < num; ++i)
716     {
717         if(list[i] < min)
718         {
719             min = list[i];
720             idx = i;
721         }
722     }
723 
724     return idx;
725 }
726 
FindNextOf(int * list,int num,int h)727 int FindNextOf(int *list, int num, int h)
728 {
729     int         i, min = 0, idx = -1;
730 
731     for(i = 0; i < num; ++i)
732     {
733         if(list[i] <= h)
734             continue;
735 
736         if(idx < 0 || list[i] < min)
737         {
738             idx = i;
739             min = list[i];
740         }
741     }
742 
743     return idx;
744 }
745 
FindPrevOf(int * list,int num,int h)746 int FindPrevOf(int *list, int num, int h)
747 {
748     int         i, max = 0, idx = -1;
749 
750     for(i = 0; i < num; ++i)
751     {
752         if(list[i] >= h)
753             continue;
754 
755         if(idx < 0 || list[i] > max)
756         {
757             idx = i;
758             max = list[i];
759         }
760     }
761 
762     return idx;
763 }
764 
765 /**
766  * Really an XL_* function!
767  *
768  * @param part          1=mid, 2=top, 3=bottom.
769  *
770  * @return              @c MAXINT if not height n/a.
771  */
XS_TextureHeight(Line * line,int part)772 int XS_TextureHeight(Line* line, int part)
773 {
774     Side* side;
775     int snum = 0;
776     int minfloor = 0, maxfloor = 0, maxceil = 0;
777     Sector* front = (Sector *) P_GetPtrp(line, DMU_FRONT_SECTOR);
778     Sector* back  = (Sector *) P_GetPtrp(line, DMU_BACK_SECTOR);
779     dd_bool twosided = front && back;
780     world_Material* mat;
781 
782     if(part != LWS_MID && !twosided)
783         return DDMAXINT;
784 
785     if(twosided)
786     {
787         int ffloor = P_GetIntp(front, DMU_FLOOR_HEIGHT);
788         int fceil  = P_GetIntp(front, DMU_CEILING_HEIGHT);
789         int bfloor = P_GetIntp(back,  DMU_FLOOR_HEIGHT);
790         int bceil  = P_GetIntp(back,  DMU_CEILING_HEIGHT);
791 
792         minfloor = ffloor;
793         maxfloor = bfloor;
794         if(part == LWS_LOWER)
795             snum = 0;
796         if(bfloor < minfloor)
797         {
798             minfloor = bfloor;
799             maxfloor = ffloor;
800             if(part == LWS_LOWER)
801                 snum = 1;
802         }
803         maxceil = fceil;
804         if(part == LWS_UPPER)
805             snum = 0;
806         if(bceil > maxceil)
807         {
808             maxceil = bceil;
809             if(part == LWS_UPPER)
810                 snum = 1;
811         }
812     }
813     else
814     {
815         if(P_GetPtrp(line, DMU_FRONT))
816             snum = 0;
817         else
818             snum = 1;
819     }
820 
821     // Which side are we working with?
822     if(snum == 0)
823         side = (Side *) P_GetPtrp(line, DMU_FRONT);
824     else
825         side = (Side *) P_GetPtrp(line, DMU_BACK);
826 
827     // Which section of the wall?
828     switch(part)
829     {
830     case LWS_UPPER:
831         if((mat = (world_Material *) P_GetPtrp(side, DMU_TOP_MATERIAL)))
832             return maxceil - P_GetIntp(mat, DMU_HEIGHT);
833         break;
834 
835     case LWS_MID:
836         if((mat = (world_Material *) P_GetPtrp(side, DMU_MIDDLE_MATERIAL)))
837             return maxfloor + P_GetIntp(mat, DMU_HEIGHT);
838         break;
839 
840     case LWS_LOWER:
841         if((mat = (world_Material *) P_GetPtrp(side, DMU_BOTTOM_MATERIAL)))
842             return minfloor + P_GetIntp(mat, DMU_HEIGHT);
843         break;
844 
845     default:
846         Con_Error("XS_TextureHeight: Invalid wall section %d.", part);
847     }
848 
849     return DDMAXINT;
850 }
851 
852 /**
853  * Returns a pointer to the first sector with the tag.
854  *
855  * NOTE: We cannot use the tagged sector lists here as this can be called
856  * during an iteration at a higher level. Doing so would change the position
857  * of the rover which would affect the other iteration.
858  *
859  * NOTE2: Re-above, obviously that is bad design and should be addressed.
860  */
XS_FindTagged(int tag)861 Sector *XS_FindTagged(int tag)
862 {
863     LOG_AS("XS_FindTagged");
864 
865     int k;
866     int foundcount = 0;
867     int retsectorid = 0;
868     Sector *sec, *retsector;
869 
870     retsector = NULL;
871 
872     for(k = 0; k < numsectors; ++k)
873     {
874         sec = (Sector *) P_ToPtr(DMU_SECTOR, k);
875         if(P_ToXSector(sec)->tag == tag)
876         {
877             if(xgDev)
878             {
879                 if(foundcount == 0)
880                 {
881                     retsector = sec;
882                     retsectorid = k;
883                 }
884             }
885             else
886                 return sec;
887 
888             foundcount++;
889         }
890     }
891 
892     if(xgDev)
893     {
894         if(foundcount > 1)
895         {
896             LOG_MAP_MSG_XGDEVONLY2("More than one sector exists with this tag (%i)!", tag);
897             LOG_MAP_MSG_XGDEVONLY2("The sector with the lowest ID (%i) will be used", retsectorid);
898         }
899 
900         if(retsector)
901             return retsector;
902     }
903 
904     return NULL;
905 }
906 
907 /**
908  * Returns a pointer to the first sector with the specified act tag.
909  */
XS_FindActTagged(int tag)910 Sector *XS_FindActTagged(int tag)
911 {
912     LOG_AS("XS_FindActTagged");
913 
914     int k;
915     int foundcount = 0;
916     int retsectorid = 0;
917     Sector *sec, *retsector;
918     xsector_t *xsec;
919 
920     retsector = NULL;
921 
922     for(k = 0; k < numsectors; ++k)
923     {
924         sec = (Sector *) P_ToPtr(DMU_SECTOR, k);
925         xsec = P_ToXSector(sec);
926         if(xsec->xg)
927         {
928             if(xsec->xg->info.actTag == tag)
929             {
930                 if(xgDev)
931                 {
932                     if(foundcount == 0)
933                     {
934                         retsector = sec;
935                         retsectorid = k;
936                     }
937                 }
938                 else
939                 {
940                     return sec;
941                 }
942 
943                 foundcount++;
944             }
945         }
946     }
947 
948     if(xgDev)
949     {
950         if(foundcount > 1)
951         {
952             LOG_MAP_MSG_XGDEVONLY2("More than one sector exists with this ACT tag (%i)!", tag);
953             LOG_MAP_MSG_XGDEVONLY2("The sector with the lowest ID (%i) will be used", retsectorid);
954         }
955 
956         if(retsector)
957             return retsector;
958     }
959 
960     return NULL;
961 }
962 
963 #define FSETHF_MIN          0x1 // Get min. If not set, get max.
964 
965 typedef struct findsectorextremaltextureheightparams_s {
966     Sector* baseSec;
967     byte flags;
968     int part;
969     coord_t val;
970 } findsectorextremalmaterialheightparams_t;
971 
findSectorExtremalMaterialHeight(void * ptr,void * context)972 int findSectorExtremalMaterialHeight(void* ptr, void* context)
973 {
974     Line* li = (Line*) ptr;
975     findsectorextremalmaterialheightparams_t* params =
976         (findsectorextremalmaterialheightparams_t*) context;
977     coord_t height;
978 
979     // The heights are in real world coordinates.
980     height = XS_TextureHeight(li, params->part);
981     if(params->flags & FSETHF_MIN)
982     {
983         if(height < params->val)
984             params->val = height;
985     }
986     else
987     {
988         if(height > params->val)
989             params->val = height;
990     }
991 
992     return false; // Continue iteration.
993 }
994 
XS_GetPlane(Line * actline,Sector * sector,int ref,int * refdata,coord_t * height,world_Material ** mat,Sector ** planeSector)995 dd_bool XS_GetPlane(Line* actline, Sector* sector, int ref, int* refdata,
996     coord_t* height, world_Material** mat, Sector** planeSector)
997 {
998     LOG_AS("XS_GetPlane");
999 
1000     world_Material *otherMat = NULL;
1001     coord_t otherHeight = 0;
1002     Sector* otherSec = NULL, *iter = NULL;
1003     xline_t* xline = NULL;
1004     char buff[50];
1005 
1006     if(refdata)
1007         sprintf(buff, " : %i", *refdata);
1008 
1009     if(xgDev)
1010     {
1011         LOG_MAP_MSG_XGDEVONLY2("Line %i, sector %i, ref (%s(%i)%s)",
1012                P_ToIndex(actline) << P_ToIndex(sector)
1013                << SPREFTYPESTR(ref) << ref << (refdata? buff : ""));
1014     }
1015 
1016     if(ref == SPREF_NONE || ref == SPREF_SPECIAL)
1017     {
1018         // No reference to anywhere.
1019         return false;
1020     }
1021 
1022     // Init the values to the current sector's floor.
1023     if(height)
1024         *height = P_GetDoublep(sector, DMU_FLOOR_HEIGHT);
1025     if(mat)
1026         *mat = (world_Material*) P_GetPtrp(sector, DMU_FLOOR_MATERIAL);
1027     if(planeSector)
1028         *planeSector = sector;
1029 
1030     // First try the non-comparative, iterative sprefs.
1031     iter = NULL;
1032     switch(ref)
1033     {
1034     case SPREF_SECTOR_TAGGED_FLOOR:
1035     case SPREF_SECTOR_TAGGED_CEILING:
1036         iter = XS_FindTagged(P_ToXSector(sector)->tag);
1037         if(!iter)
1038             return false;
1039         break;
1040 
1041     case SPREF_LINE_TAGGED_FLOOR:
1042     case SPREF_LINE_TAGGED_CEILING:
1043         if(!actline)
1044             return false;
1045 
1046         iter = XS_FindTagged(P_ToXLine(actline)->tag);
1047         if(!iter)
1048             return false;
1049         break;
1050 
1051     case SPREF_TAGGED_FLOOR:
1052     case SPREF_TAGGED_CEILING:
1053         if(!refdata)
1054         {
1055             LOG_MAP_MSG_XGDEVONLY2("%s IS NOT VALID FOR THIS CLASS PARAMETER!", SPREFTYPESTR(ref));
1056             return false;
1057         }
1058 
1059         iter = XS_FindTagged(*refdata);
1060         if(!iter)
1061             return false;
1062         break;
1063 
1064     case SPREF_LINE_ACT_TAGGED_FLOOR:
1065     case SPREF_LINE_ACT_TAGGED_CEILING:
1066         xline = P_ToXLine(actline);
1067 
1068         if(!xline)
1069             return false;
1070 
1071         if(!xline->xg)
1072         {
1073             LOG_MAP_MSG_XGDEVONLY("ACT LINE IS NOT AN XG LINE!");
1074             return false;
1075         }
1076         if(!xline->xg->info.actTag)
1077         {
1078             LOG_MAP_MSG_XGDEVONLY("ACT LINE DOES NOT HAVE AN ACT TAG!");
1079             return false;
1080         }
1081 
1082         iter = XS_FindActTagged(xline->xg->info.actTag);
1083         if(!iter)
1084             return false;
1085         break;
1086 
1087     case SPREF_ACT_TAGGED_FLOOR:
1088     case SPREF_ACT_TAGGED_CEILING:
1089         if(!refdata)
1090         {
1091             LOG_MAP_MSG_XGDEVONLY2("%s IS NOT VALID FOR THIS CLASS PARAMETER!", SPREFTYPESTR(ref));
1092             return false;
1093         }
1094 
1095         iter = XS_FindActTagged(*refdata);
1096         if(!iter)
1097             return false;
1098         break;
1099 
1100     case SPREF_INDEX_FLOOR:
1101     case SPREF_INDEX_CEILING:
1102         if(!refdata || *refdata >= numsectors)
1103             return false;
1104         iter = (Sector *) P_ToPtr(DMU_SECTOR, *refdata);
1105         break;
1106 
1107     default:
1108         // No iteration.
1109         break;
1110     }
1111 
1112     // Did we find the plane through iteration?
1113     if(iter)
1114     {
1115         if(planeSector)
1116             *planeSector = iter;
1117         if((ref >= SPREF_SECTOR_TAGGED_FLOOR && ref <= SPREF_INDEX_FLOOR) ||
1118             ref == SPREF_LINE_ACT_TAGGED_FLOOR)
1119         {
1120             if(height)
1121                 *height = P_GetDoublep(iter, DMU_FLOOR_HEIGHT);
1122             if(mat)
1123                 *mat = (world_Material*) P_GetPtrp(iter, DMU_FLOOR_MATERIAL);
1124         }
1125         else
1126         {
1127             if(height)
1128                 *height = P_GetDoublep(iter, DMU_CEILING_HEIGHT);
1129             if(mat)
1130                 *mat = (world_Material*) P_GetPtrp(iter, DMU_CEILING_MATERIAL);
1131         }
1132 
1133         return true;
1134     }
1135 
1136     if(ref == SPREF_MY_FLOOR)
1137     {
1138         Sector* frontsector;
1139 
1140         if(!actline)
1141             return false;
1142 
1143         frontsector = (Sector *) P_GetPtrp(actline, DMU_FRONT_SECTOR);
1144 
1145         if(!frontsector)
1146             return false;
1147 
1148         // Actline's front floor.
1149         if(height)
1150             *height = P_GetDoublep(frontsector, DMU_FLOOR_HEIGHT);
1151         if(mat)
1152             *mat = (world_Material*) P_GetPtrp(frontsector, DMU_FLOOR_MATERIAL);
1153         if(planeSector)
1154             *planeSector = frontsector;
1155         return true;
1156     }
1157 
1158     if(ref == SPREF_BACK_FLOOR)
1159     {
1160         Sector* backsector;
1161 
1162         if(!actline)
1163             return false;
1164 
1165         backsector = (Sector *) P_GetPtrp(actline, DMU_BACK_SECTOR);
1166 
1167         if(!backsector)
1168             return false;
1169 
1170         // Actline's back floor.
1171         if(height)
1172             *height = P_GetDoublep(backsector, DMU_FLOOR_HEIGHT);
1173         if(mat)
1174             *mat = (world_Material*) P_GetPtrp(backsector, DMU_FLOOR_MATERIAL);
1175         if(planeSector)
1176             *planeSector = backsector;
1177 
1178         return true;
1179     }
1180 
1181     if(ref == SPREF_MY_CEILING)
1182     {
1183         Sector* frontsector;
1184 
1185         if(!actline)
1186             return false;
1187 
1188         frontsector = (Sector *) P_GetPtrp(actline, DMU_FRONT_SECTOR);
1189 
1190         if(!frontsector)
1191             return false;
1192 
1193         // Actline's front ceiling.
1194         if(height)
1195             *height = P_GetDoublep(frontsector, DMU_CEILING_HEIGHT);
1196         if(mat)
1197             *mat = (world_Material *) P_GetPtrp(frontsector, DMU_CEILING_MATERIAL);
1198         if(planeSector)
1199             *planeSector = frontsector;
1200         return true;
1201     }
1202 
1203     if(ref == SPREF_BACK_CEILING)
1204     {
1205         Sector* backsector;
1206 
1207         if(!actline)
1208             return false;
1209 
1210         backsector = (Sector *) P_GetPtrp(actline, DMU_BACK_SECTOR);
1211 
1212         if(!backsector)
1213             return false;
1214 
1215         // Actline's back ceiling.
1216         if(height)
1217             *height = P_GetDoublep(backsector, DMU_CEILING_HEIGHT);
1218         if(mat)
1219             *mat = (world_Material *) P_GetPtrp(backsector, DMU_CEILING_MATERIAL);
1220         if(planeSector)
1221             *planeSector = backsector;
1222         return true;
1223     }
1224 
1225     if(ref == SPREF_ORIGINAL_FLOOR)
1226     {
1227         if(height)
1228             *height = P_ToXSector(sector)->SP_floororigheight;
1229         if(mat)
1230             *mat = (world_Material *) P_GetPtrp(sector, DMU_FLOOR_MATERIAL);
1231         return true;
1232     }
1233 
1234     if(ref == SPREF_ORIGINAL_CEILING)
1235     {
1236         if(height)
1237             *height = P_ToXSector(sector)->SP_ceilorigheight;
1238         if(mat)
1239             *mat = (world_Material *) P_GetPtrp(sector, DMU_CEILING_MATERIAL);
1240         return true;
1241     }
1242 
1243     if(ref == SPREF_CURRENT_FLOOR)
1244     {
1245         if(height)
1246             *height = P_GetDoublep(sector, DMU_FLOOR_HEIGHT);
1247         if(mat)
1248             *mat = (world_Material *) P_GetPtrp(sector, DMU_FLOOR_MATERIAL);
1249         return true;
1250     }
1251 
1252     if(ref == SPREF_CURRENT_CEILING)
1253     {
1254         if(height)
1255             *height = P_GetDoublep(sector, DMU_CEILING_HEIGHT);
1256         if(mat)
1257             *mat = (world_Material *) P_GetPtrp(sector, DMU_CEILING_MATERIAL);
1258         return true;
1259     }
1260 
1261     // Texture height targets?
1262     if(ref >= SPREF_MIN_BOTTOM_MATERIAL && ref <= SPREF_MAX_TOP_MATERIAL)
1263     {
1264         int part;
1265         dd_bool findMin;
1266         findsectorextremalmaterialheightparams_t params;
1267 
1268         // Which part of the wall are we looking at?
1269         if(ref == SPREF_MIN_MID_MATERIAL || ref == SPREF_MAX_MID_MATERIAL)
1270             part = LWS_MID;
1271         else if(ref == SPREF_MIN_TOP_MATERIAL || ref == SPREF_MAX_TOP_MATERIAL)
1272             part = LWS_UPPER;
1273         else // Then it's the bottom.
1274             part = LWS_LOWER;
1275 
1276         if(ref >= SPREF_MIN_BOTTOM_MATERIAL && ref <= SPREF_MIN_TOP_MATERIAL)
1277             findMin = true;
1278         else
1279             findMin = false;
1280 
1281         params.baseSec = sector;
1282         params.part = part;
1283         params.flags = (findMin? FSETHF_MIN : 0);
1284         params.val = (findMin? DDMAXFLOAT : DDMINFLOAT);
1285         P_Iteratep(sector, DMU_LINE, findSectorExtremalMaterialHeight, &params);
1286         if(height)
1287             *height = params.val;
1288 
1289         return true;
1290     }
1291 
1292     // Get the right height and pic.
1293     if(ref == SPREF_HIGHEST_CEILING)
1294     {
1295         otherSec = P_FindSectorSurroundingHighestCeiling(sector, DDMINFLOAT, &otherHeight);
1296         if(otherSec)
1297             otherMat = (world_Material *) P_GetPtrp(otherSec, DMU_CEILING_MATERIAL);
1298     }
1299     else if(ref == SPREF_HIGHEST_FLOOR)
1300     {
1301         otherSec = P_FindSectorSurroundingHighestFloor(sector, DDMINFLOAT, &otherHeight);
1302         if(otherSec)
1303             otherMat = (world_Material *) P_GetPtrp(otherSec, DMU_CEILING_MATERIAL);
1304     }
1305     else if(ref == SPREF_LOWEST_CEILING)
1306     {
1307         otherSec = P_FindSectorSurroundingLowestCeiling(sector, DDMAXFLOAT, &otherHeight);
1308         if(otherSec)
1309             otherMat = (world_Material *) P_GetPtrp(otherSec, DMU_CEILING_MATERIAL);
1310     }
1311     else if(ref == SPREF_LOWEST_FLOOR)
1312     {
1313         otherSec = P_FindSectorSurroundingLowestFloor(sector, DDMAXFLOAT, &otherHeight);
1314         if(otherSec)
1315             otherMat = (world_Material *) P_GetPtrp(otherSec, DMU_FLOOR_MATERIAL);
1316     }
1317     else if(ref == SPREF_NEXT_HIGHEST_CEILING)
1318     {
1319         otherSec = P_FindSectorSurroundingNextHighestCeiling(sector,
1320                         P_GetDoublep(sector, DMU_CEILING_HEIGHT), &otherHeight);
1321         if(otherSec)
1322             otherMat = (world_Material *) P_GetPtrp(otherSec, DMU_CEILING_MATERIAL);
1323     }
1324     else if(ref == SPREF_NEXT_HIGHEST_FLOOR)
1325     {
1326         otherSec = P_FindSectorSurroundingNextHighestFloor(sector,
1327                         P_GetDoublep(sector, DMU_FLOOR_HEIGHT), &otherHeight);
1328         if(otherSec)
1329             otherMat = (world_Material *) P_GetPtrp(otherSec, DMU_FLOOR_MATERIAL);
1330     }
1331     else if(ref == SPREF_NEXT_LOWEST_CEILING)
1332     {
1333         otherSec = P_FindSectorSurroundingNextLowestCeiling(sector,
1334                         P_GetDoublep(sector, DMU_CEILING_HEIGHT), &otherHeight);
1335         if(otherSec)
1336             otherMat = (world_Material *) P_GetPtrp(otherSec, DMU_CEILING_MATERIAL);
1337     }
1338     else if(ref == SPREF_NEXT_LOWEST_FLOOR)
1339     {
1340         otherSec = P_FindSectorSurroundingNextLowestFloor(sector,
1341                         P_GetDoublep(sector, DMU_FLOOR_HEIGHT), &otherHeight);
1342         if(otherSec)
1343             otherMat = (world_Material *) P_GetPtrp(otherSec, DMU_FLOOR_MATERIAL);
1344     }
1345 
1346     // The requested plane was not found.
1347     if(!otherSec)
1348         return false;
1349 
1350     // Set the values.
1351     if(height)
1352         *height = otherHeight;
1353     if(mat)
1354         *mat = otherMat;
1355     if(planeSector)
1356         *planeSector = otherSec;
1357 
1358     return true;
1359 }
1360 
1361 /**
1362  * DJS - Why find the highest??? Surely unlogical to mod authors.
1363  * IMO if a user references multiple sectors, the one with the lowest ID
1364  * should be chosen (the same way it works for FIND(act)TAGGED). If that
1365  * happens to be zero - so be it.
1366  */
XSTrav_HighestSectorType(Sector * sec,dd_bool ceiling,void * context,void * context2,mobj_t * activator)1367 int C_DECL XSTrav_HighestSectorType(Sector *sec, dd_bool ceiling,
1368                                     void *context, void *context2,
1369                                     mobj_t *activator)
1370 {
1371     DENG_UNUSED(ceiling);
1372     DENG_UNUSED(context);
1373     DENG_UNUSED(activator);
1374 
1375     int        *type = (int *) context2;
1376     xsector_t  *xsec = P_ToXSector(sec);
1377 
1378     if(xsec->special > *type)
1379         *type = xsec->special;
1380 
1381     return true; // Keep looking...
1382 }
1383 
XS_InitMovePlane(Line * line)1384 void XS_InitMovePlane(Line *line)
1385 {
1386     xline_t *xline = P_ToXLine(line);
1387 
1388     // fdata keeps track of wait time.
1389     xline->xg->fdata = xline->xg->info.fparm[5];
1390     xline->xg->idata = true; // Play sound.
1391 };
1392 
XSTrav_MovePlane(Sector * sector,dd_bool ceiling,void * context,void * context2,mobj_t *)1393 int C_DECL XSTrav_MovePlane(Sector *sector, dd_bool ceiling, void *context,
1394                             void *context2, mobj_t * /*activator*/)
1395 {
1396     LOG_AS("XSTrav_MovePlane");
1397     DENG2_ASSERT(sector);
1398     Line *line        = (Line *) context;
1399     DENG2_ASSERT(line);
1400     linetype_t *info  = (linetype_t *) context2;
1401     DENG2_ASSERT(info);
1402     xline_t *xline    = P_ToXLine(line);
1403     dd_bool playsound = xline->xg->idata;
1404 
1405     LOG_MAP_MSG_XGDEVONLY2("Sector %i (by line %i of type %i)",
1406            P_ToIndex(sector) << P_ToIndex(line) << info->id);
1407 
1408     // i2: destination type (zero, relative to current, surrounding
1409     //     highest/lowest floor/ceiling)
1410     // i3: flags (PMF_*)
1411     // i4: start sound
1412     // i5: end sound
1413     // i6: move sound
1414     // i7: start material origin (uses same ids as i2)
1415     // i8: start material index (used with PMD_ZERO).
1416     // i9: end material origin (uses same ids as i2)
1417     // i10: end material (used with PMD_ZERO)
1418     // i11 + i12: (plane ref) start sector type
1419     // i13 + i14: (plane ref) end sector type
1420     // f0: move speed (units per tic).
1421     // f1: crush speed (units per tic).
1422     // f2: destination offset
1423     // f3: move sound min interval (seconds)
1424     // f4: move sound max interval (seconds)
1425     // f5: time to wait before starting the move
1426     // f6: wait increment for each plane that gets moved
1427 
1428     xgplanemover_t *mover = XS_GetPlaneMover(sector, ceiling);
1429     if(P_IsDummy(line))
1430     {
1431         LOG_MAP_ERROR("Attempted to use a dummy line as XGPlaneMover origin. "
1432                       "Plane in sector %i will not be moved.") << P_ToIndex(sector);
1433         return true; // Keep looking.
1434     }
1435     mover->origin = line;
1436 
1437     // Setup the thinker and add it to the list.
1438     {
1439     coord_t temp = mover->destination;
1440     XS_GetPlane(line, sector, info->iparm[2], NULL, &temp, 0, 0);
1441     mover->destination = temp + info->fparm[2];
1442     }
1443     mover->speed = info->fparm[0];
1444     mover->crushSpeed = info->fparm[1];
1445     mover->minInterval = FLT2TIC(info->fparm[3]);
1446     mover->maxInterval = FLT2TIC(info->fparm[4]);
1447     mover->flags = info->iparm[3];
1448     mover->endSound = playsound ? info->iparm[5] : 0;
1449     mover->moveSound = playsound ? info->iparm[6] : 0;
1450 
1451     // Change texture at end?
1452     if(info->iparm[9] == SPREF_NONE || info->iparm[9] == SPREF_SPECIAL)
1453     {
1454         mover->setMaterial = (world_Material *) P_ToPtr(DMU_MATERIAL, info->iparm[10]);
1455     }
1456     else
1457     {
1458         if(!XS_GetPlane(line, sector, info->iparm[9], NULL, 0, &mover->setMaterial, 0))
1459             LOG_MAP_MSG_XGDEVONLY("Couldn't find suitable material to set when move ends!");
1460     }
1461 
1462     // Init timer.
1463     mover->timer = XG_RandomInt(mover->minInterval, mover->maxInterval);
1464 
1465     // Do we need to wait before starting the move?
1466     if(xline->xg->fdata > 0)
1467     {
1468         mover->timer = FLT2TIC(xline->xg->fdata);
1469         mover->flags |= PMF_WAIT;
1470     }
1471 
1472     // Increment wait time.
1473     xline->xg->fdata += info->fparm[6];
1474 
1475     // Do start stuff. Play sound?
1476     if(playsound)
1477         XS_PlaneSound((Plane *) P_GetPtrp(sector, ceiling? DMU_CEILING_PLANE : DMU_FLOOR_PLANE),
1478                       info->iparm[4]);
1479 
1480     // Change material at start?
1481     world_Material *mat = nullptr;
1482     if(info->iparm[7] == SPREF_NONE || info->iparm[7] == SPREF_SPECIAL)
1483     {
1484         mat = (world_Material *) P_ToPtr(DMU_MATERIAL, info->iparm[8]);
1485     }
1486     else if(!XS_GetPlane(line, sector, info->iparm[7], nullptr, 0, &mat, 0))
1487     {
1488         LOG_MAP_MSG_XGDEVONLY("Couldn't find suitable material to set when move starts!");
1489     }
1490 
1491     if(mat)
1492     {
1493         XS_ChangePlaneMaterial(*sector, ceiling, *mat);
1494     }
1495 
1496     // Should we play no more sounds?
1497     if(info->iparm[3] & PMF_ONE_SOUND_ONLY)
1498     {
1499         // Sound was played only for the first plane.
1500         xline->xg->idata = false;
1501     }
1502 
1503     // Change sector type right now?
1504     int st = info->iparm[12];
1505     if(info->iparm[11] != LPREF_NONE)
1506     {
1507         if(XL_TraversePlanes
1508            (line, info->iparm[11], info->iparm[12], 0, &st, false, 0,
1509             XSTrav_HighestSectorType))
1510         {
1511             XS_SetSectorType(sector, st);
1512         }
1513         else
1514         {
1515             LOG_MAP_MSG_XGDEVONLY("SECTOR TYPE NOT SET (nothing referenced)");
1516         }
1517     }
1518 
1519     // Change sector type in the end of move?
1520     st = info->iparm[14];
1521     if(info->iparm[13] != LPREF_NONE)
1522     {
1523         if(XL_TraversePlanes
1524            (line, info->iparm[13], info->iparm[14], 0, &st, false, 0,
1525             XSTrav_HighestSectorType))
1526         {   // OK, found one or more.
1527             mover->setSectorType = st;
1528         }
1529         else
1530         {
1531             LOG_MAP_MSG_XGDEVONLY("SECTOR TYPE WON'T BE CHANGED AT END (nothing referenced)");
1532             mover->setSectorType = -1;
1533         }
1534     }
1535     else
1536         mover->setSectorType = -1;
1537 
1538     return true; // Keep looking...
1539 }
1540 
XS_InitStairBuilder(Line *)1541 void XS_InitStairBuilder(Line *)
1542 {
1543     int i;
1544     for(i = 0; i < numsectors; ++i)
1545     {
1546         P_GetXSector(i)->blFlags = 0;
1547     }
1548 }
1549 
XS_DoBuild(Sector * sector,dd_bool ceiling,Line * origin,linetype_t * info,uint stepcount)1550 dd_bool XS_DoBuild(Sector* sector, dd_bool ceiling, Line* origin,
1551                    linetype_t* info, uint stepcount)
1552 {
1553     static coord_t firstheight;
1554 
1555     float waittime;
1556     xsector_t* xsec;
1557     xgplanemover_t* mover;
1558 
1559     if(!sector)
1560         return false;
1561 
1562     xsec = P_ToXSector(sector);
1563 
1564     // Make sure each sector is only processed once.
1565     if(xsec->blFlags & BL_BUILT)
1566         return false; // Already built this one!
1567     xsec->blFlags |= BL_WAS_BUILT;
1568 
1569     // Create a new mover for the plane.
1570     mover = XS_GetPlaneMover(sector, ceiling);
1571     if(P_IsDummy(origin))
1572     {
1573         LOG_MAP_ERROR("Attempted to use a dummy line as XGPlaneMover origin while "
1574                       "building stairs in sector %i.") << P_ToIndex(sector);
1575         return false;
1576     }
1577     mover->origin = origin;
1578 
1579     // Setup the mover.
1580     if(stepcount != 0)
1581         firstheight = P_GetDoublep(sector, (ceiling? DMU_CEILING_HEIGHT:DMU_FLOOR_HEIGHT));
1582 
1583     mover->destination =
1584         firstheight + (stepcount + 1) * info->fparm[1];
1585 
1586     mover->speed = (info->fparm[0] + stepcount * info->fparm[6]);
1587     if(mover->speed <= 0)
1588         mover->speed = 1 / 1000;
1589 
1590     mover->minInterval = FLT2TIC(info->fparm[4]);
1591     mover->maxInterval = FLT2TIC(info->fparm[5]);
1592 
1593     if(info->iparm[8])
1594         mover->flags = PMF_CRUSH;
1595 
1596     mover->endSound = info->iparm[6];
1597     mover->moveSound = info->iparm[7];
1598 
1599     // Wait before starting?
1600     waittime = info->fparm[2] + info->fparm[3] * stepcount;
1601     if(waittime > 0)
1602     {
1603         mover->timer = FLT2TIC(waittime);
1604         mover->flags |= PMF_WAIT;
1605         // Play start sound when waiting ends.
1606         mover->startSound = info->iparm[5];
1607     }
1608     else
1609     {
1610         mover->timer = XG_RandomInt(mover->minInterval, mover->maxInterval);
1611         // Play step start sound immediately.
1612         XS_PlaneSound((Plane *) P_GetPtrp(sector, ceiling? DMU_CEILING_PLANE : DMU_FLOOR_PLANE),
1613                       info->iparm[5]);
1614     }
1615 
1616     // Do start stuff. Play sound?
1617     if(stepcount != 0)
1618     {
1619         // Start building start sound.
1620         XS_PlaneSound((Plane *) P_GetPtrp(sector, ceiling? DMU_CEILING_PLANE : DMU_FLOOR_PLANE),
1621                       info->iparm[4]);
1622     }
1623 
1624     return true; // Building has begun!
1625 }
1626 
1627 #define F_MATERIALSTOP          0x1
1628 #define F_CEILING               0x2
1629 
1630 typedef struct spreadbuildparams_s {
1631     Sector*             baseSec;
1632     world_Material*     baseMat;
1633     byte                flags;
1634     Line*               origin;
1635     linetype_t*         info;
1636     int                 stepCount;
1637     size_t              spreaded;
1638 } spreadbuildparams_t;
1639 
spreadBuild(void * ptr,void * context)1640 int spreadBuild(void *ptr, void *context)
1641 {
1642     Line                *li = (Line*) ptr;
1643     spreadbuildparams_t *params = (spreadbuildparams_t*) context;
1644     Sector              *frontSec, *backSec;
1645 
1646     frontSec = (Sector *) P_GetPtrp(li, DMU_FRONT_SECTOR);
1647     if(!frontSec || frontSec != params->baseSec)
1648         return false;
1649 
1650     backSec = (Sector *) P_GetPtrp(li, DMU_BACK_SECTOR);
1651     if(!backSec)
1652         return false;
1653 
1654     if(params->flags & F_MATERIALSTOP)
1655     {   // Planepic must match.
1656         if(params->flags & F_CEILING)
1657         {
1658             if(P_GetPtrp(params->baseSec, DMU_CEILING_MATERIAL) !=
1659                params->baseMat)
1660                 return false;
1661         }
1662         else
1663         {
1664             if(P_GetPtrp(params->baseSec, DMU_FLOOR_MATERIAL) !=
1665                params->baseMat)
1666                 return false;
1667         }
1668     }
1669 
1670     // Don't spread to sectors which have already spreaded.
1671     if(P_ToXSector(backSec)->blFlags & BL_SPREADED)
1672         return false;
1673 
1674     // Build backsector.
1675     XS_DoBuild(backSec, ((params->flags & F_CEILING)? true : false),
1676                params->origin, params->info, params->stepCount);
1677     params->spreaded++;
1678 
1679     return false; // Continue iteration.
1680 }
1681 
markBuiltSectors(void)1682 static void markBuiltSectors(void)
1683 {
1684     int i;
1685 
1686     // Mark the sectors of the last step as processed.
1687     for(i = 0; i < numsectors; ++i)
1688     {
1689         xsector_t *xsec = P_GetXSector(i);
1690 
1691         if(xsec->blFlags & BL_WAS_BUILT)
1692         {
1693             xsec->blFlags &= ~BL_WAS_BUILT;
1694             xsec->blFlags |= BL_BUILT;
1695         }
1696     }
1697 }
1698 
spreadBuildToNeighborAll(Line * origin,linetype_t * info,dd_bool picstop,dd_bool ceiling,world_Material * myMat,int stepCount)1699 static dd_bool spreadBuildToNeighborAll(Line *origin, linetype_t *info,
1700     dd_bool picstop, dd_bool ceiling, world_Material *myMat, int stepCount)
1701 {
1702     int i;
1703     dd_bool result = false;
1704     spreadbuildparams_t params;
1705 
1706     params.baseMat = myMat;
1707     params.info = info;
1708     params.origin = origin;
1709     params.stepCount = stepCount;
1710     params.flags = 0;
1711     if(picstop)
1712         params.flags |= F_MATERIALSTOP;
1713     if(ceiling)
1714         params.flags |= F_CEILING;
1715 
1716     for(i = 0; i < numsectors; ++i)
1717     {
1718         Sector *sec;
1719         xsector_t *xsec = P_GetXSector(i);
1720 
1721         // Only spread from built sectors (spread only once!).
1722         if(!(xsec->blFlags & BL_BUILT) || xsec->blFlags & BL_SPREADED)
1723             continue;
1724 
1725         xsec->blFlags |= BL_SPREADED;
1726 
1727         // Any 2-sided lines facing the right way?
1728         sec = (Sector *) P_ToPtr(DMU_SECTOR, i);
1729 
1730         params.baseSec = sec;
1731         params.spreaded = 0;
1732 
1733         P_Iteratep(sec, DMU_LINE, spreadBuild, &params);
1734         if(params.spreaded > 0)
1735             result = true;
1736     }
1737 
1738     return result;
1739 }
1740 
1741 #define F_MATERIALSTOP          0x1
1742 #define F_CEILING               0x2
1743 
1744 typedef struct findbuildneighborparams_s {
1745     Sector*             baseSec;
1746     world_Material*     baseMat;
1747     byte                flags;
1748     Line*               origin;
1749     linetype_t*         info;
1750     int                 stepCount;
1751     int                 foundIDX;
1752     Sector*             foundSec;
1753 } findbuildneighborparams_t;
1754 
findBuildNeighbor(void * ptr,void * context)1755 int findBuildNeighbor(void* ptr, void* context)
1756 {
1757     Line*               li = (Line*) ptr;
1758     findbuildneighborparams_t *params =
1759         (findbuildneighborparams_t*) context;
1760     Sector*             frontSec, *backSec;
1761     int                 idx;
1762 
1763     frontSec = (Sector *) P_GetPtrp(li, DMU_FRONT_SECTOR);
1764     if(!frontSec || frontSec != params->baseSec)
1765         return false;
1766 
1767     backSec = (Sector *) P_GetPtrp(li, DMU_BACK_SECTOR);
1768     if(!backSec)
1769         return false;
1770 
1771     if(params->flags & F_MATERIALSTOP)
1772     {   // Planepic must match.
1773         if(params->flags & F_CEILING)
1774         {
1775             if(P_GetPtrp(params->baseSec, DMU_CEILING_MATERIAL) !=
1776                params->baseMat)
1777                 return false;
1778         }
1779         else
1780         {
1781             if(P_GetPtrp(params->baseSec, DMU_FLOOR_MATERIAL) !=
1782                params->baseMat)
1783                 return false;
1784         }
1785     }
1786 
1787     // Don't spread to sectors which have already spreaded.
1788     if(P_ToXSector(backSec)->blFlags & BL_SPREADED)
1789         return false;
1790 
1791     // We need the lowest line number.
1792     idx = P_ToIndex(li);
1793     if(idx < params->foundIDX)
1794     {
1795         params->foundSec = backSec;
1796         params->foundIDX = idx;
1797     }
1798 
1799     return false; // Continue iteration.
1800 }
1801 
spreadBuildToNeighborLowestIDX(Line * origin,linetype_t * info,dd_bool picstop,dd_bool ceiling,world_Material * myMat,int stepcount,Sector ** foundSec)1802 dd_bool spreadBuildToNeighborLowestIDX(Line *origin, linetype_t *info,
1803     dd_bool picstop, dd_bool ceiling, world_Material *myMat, int stepcount,
1804     Sector **foundSec)
1805 {
1806     int i;
1807     dd_bool result = false;
1808     findbuildneighborparams_t params;
1809 
1810     params.baseMat = myMat;
1811     params.info = info;
1812     params.origin = origin;
1813     params.stepCount = stepcount;
1814     params.flags = 0;
1815     if(picstop)
1816         params.flags |= F_MATERIALSTOP;
1817     if(ceiling)
1818         params.flags |= F_CEILING;
1819 
1820     for(i = 0; i < numsectors; ++i)
1821     {
1822         Sector *sec;
1823         xsector_t *xsec = P_GetXSector(i);
1824 
1825         // Only spread from built sectors (spread only once!).
1826         if(!(xsec->blFlags & BL_BUILT) || xsec->blFlags & BL_SPREADED)
1827             continue;
1828 
1829         xsec->blFlags |= BL_SPREADED;
1830 
1831         // Any 2-sided lines facing the right way?
1832         sec = (Sector *) P_ToPtr(DMU_SECTOR, i);
1833 
1834         params.baseSec = sec;
1835         params.foundIDX = numlines;
1836         params.foundSec = NULL;
1837 
1838         P_Iteratep(sec, DMU_LINE, findBuildNeighbor, &params);
1839 
1840         if(params.foundSec)
1841         {
1842             result = true;
1843             *foundSec = params.foundSec;
1844         }
1845     }
1846 
1847     return result;
1848 }
1849 
XSTrav_BuildStairs(Sector * sector,dd_bool ceiling,void * context,void * context2,mobj_t * activator)1850 int C_DECL XSTrav_BuildStairs(Sector *sector, dd_bool ceiling, void *context,
1851                               void *context2, mobj_t *activator)
1852 {
1853     LOG_AS("XSTrav_BuildStairs");
1854 
1855     uint stepCount   = 0;
1856     Line *origin     = (Line *) context;
1857     linetype_t *info = (linetype_t *) context2;
1858     Sector *foundSec = 0;
1859     dd_bool picstop  = info->iparm[2] != 0;
1860     dd_bool spread   = info->iparm[3] != 0;
1861     world_Material *myMat;
1862 
1863     DENG_UNUSED(activator);
1864 
1865     LOG_MAP_MSG_XGDEVONLY2("Sector %i, %s", P_ToIndex(sector) << (ceiling? "ceiling" : "floor"));
1866 
1867     // i2: (true/false) stop when texture changes
1868     // i3: (true/false) spread build?
1869 
1870     myMat = (world_Material *) (ceiling ? P_GetPtrp(sector, DMU_CEILING_MATERIAL) :
1871                                     P_GetPtrp(sector, DMU_FLOOR_MATERIAL));
1872 
1873     // Apply to first step.
1874     XS_DoBuild(sector, ceiling, origin, info, 0);
1875     stepCount++;
1876 
1877     if(spread)
1878     {
1879         dd_bool             found;
1880 
1881         do
1882         {
1883             markBuiltSectors();
1884 
1885             // Scan the sectors for the next ones to spread to.
1886             found = spreadBuildToNeighborAll(origin, info, picstop, ceiling,
1887                                              myMat, stepCount);
1888             stepCount++;
1889         } while(found);
1890     }
1891     else
1892     {
1893         dd_bool             found;
1894 
1895         do
1896         {
1897             found = false;
1898             markBuiltSectors();
1899 
1900             // Scan the sectors for the next ones to spread to.
1901             if(spreadBuildToNeighborLowestIDX(origin, info, picstop,
1902                                               ceiling, myMat, stepCount,
1903                                               &foundSec))
1904             {
1905                 XS_DoBuild(foundSec, ceiling, origin, info, stepCount);
1906                 found = true;
1907             }
1908 
1909             stepCount++;
1910         } while(found);
1911     }
1912 
1913     return true; // Continue searching for planes...
1914 }
1915 
XSTrav_SectorSound(Sector * sec,dd_bool ceiling,void * context,void * context2,mobj_t * activator)1916 int C_DECL XSTrav_SectorSound(Sector* sec, dd_bool ceiling, void* context,
1917                               void* context2, mobj_t* activator)
1918 {
1919     DENG_UNUSED(activator);
1920     DENG_UNUSED(context);
1921     DENG_UNUSED(ceiling);
1922 
1923     linetype_t* info = (linetype_t *) context2;
1924 
1925     /// @c 0= sector
1926     /// @c 1= floor plane
1927     /// @c 2= ceiling plane
1928     if(!info->iparm[3])
1929     {
1930         XS_SectorSound(sec, info->iparm[2]);
1931     }
1932     else
1933     {
1934         Plane* plane = (Plane *) P_GetPtrp(sec, info->iparm[3] == 2? DMU_CEILING_PLANE : DMU_FLOOR_PLANE);
1935         XS_PlaneSound(plane, info->iparm[2]);
1936     }
1937 
1938     return true;
1939 }
1940 
1941 // i2: (spref) material origin
1942 // i3: texture number (flat), used with SPREF_NONE
1943 // i4: tint color red
1944 // i5: tint color green
1945 // i6: tint color blue
1946 // i7: (true/false) set tint color
XSTrav_PlaneMaterial(Sector * sec,dd_bool ceiling,void * context,void * context2,mobj_t *)1947 int C_DECL XSTrav_PlaneMaterial(Sector *sec, dd_bool ceiling, void *context,
1948                                 void *context2, mobj_t * /*activator*/)
1949 {
1950     LOG_AS("XSTrav_PlaneMaterial");
1951     DENG2_ASSERT(sec);
1952     Line *line       = (Line *) context;
1953     DENG2_ASSERT(line);
1954     linetype_t *info = (linetype_t *) context2;
1955     DENG2_ASSERT(info);
1956 
1957     world_Material *mat;
1958     if(info->iparm[2] == SPREF_NONE)
1959     {
1960         mat = (world_Material *) P_ToPtr(DMU_MATERIAL, info->iparm[3]);
1961     }
1962     else if(!XS_GetPlane(line, sec, info->iparm[2], nullptr, 0, &mat, 0))
1963     {
1964         LOG_MAP_MSG_XGDEVONLY2("Sector %i, couldn't find suitable material!", P_ToIndex(sec));
1965     }
1966 
1967     if(mat)
1968     {
1969         XS_ChangePlaneMaterial(*sec, ceiling, *mat);
1970     }
1971 
1972     if(info->iparm[7])
1973     {
1974         de::Vector3f const color(info->iparm[4], info->iparm[5], info->iparm[6]);
1975         XS_ChangePlaneColor(*sec, ceiling, color / 255.f);
1976     }
1977 
1978     return true;
1979 }
1980 
XSTrav_SectorType(Sector * sec,dd_bool,void *,void * context2,mobj_t *)1981 int C_DECL XSTrav_SectorType(Sector* sec, dd_bool /*ceiling*/,
1982                              void* /*context*/, void* context2,
1983                              mobj_t* /*activator*/)
1984 {
1985     linetype_t*         info = (linetype_t *) context2;
1986 
1987     XS_SetSectorType(sec, info->iparm[2]);
1988     return true;
1989 }
1990 
XSTrav_SectorLight(Sector * sector,dd_bool,void * context,void * context2,mobj_t *)1991 int C_DECL XSTrav_SectorLight(Sector* sector, dd_bool /*ceiling*/,
1992                               void* context, void* context2,
1993                               mobj_t* /*activator*/)
1994 {
1995     LOG_AS("XSTrav_SectorLight");
1996 
1997     Line*               line = (Line *) context;
1998     linetype_t*         info = (linetype_t *) context2;
1999     int                 num;
2000     float               usergb[3];
2001     float               lightLevel = 0;
2002 
2003     // i2: (true/false) set level
2004     // i3: (true/false) set RGB
2005     // i4: source of light level (SSREF*)
2006     // i5: offset
2007     // i6: source of RGB (none, my, original)
2008     // i7: red offset
2009     // i8: green offset
2010     // i9: blue offset
2011 
2012     if(info->iparm[2])
2013     {
2014         switch(info->iparm[4])
2015         {
2016         default:
2017         case LIGHTREF_NONE:
2018             lightLevel = 0;
2019             break;
2020 
2021         case LIGHTREF_MY:
2022             {
2023             Sector *frontSec = (Sector *) P_GetPtrp(line, DMU_FRONT_SECTOR);
2024             lightLevel = P_GetFloatp(frontSec, DMU_LIGHT_LEVEL);
2025             }
2026             break;
2027 
2028         case LIGHTREF_BACK:
2029             {
2030             Sector *backSec = (Sector *) P_GetPtrp(line, DMU_BACK_SECTOR);
2031             if(backSec)
2032                 lightLevel = P_GetFloatp(backSec, DMU_LIGHT_LEVEL);
2033             }
2034             break;
2035 
2036         case LIGHTREF_ORIGINAL:
2037             lightLevel = P_ToXSector(sector)->origLight;
2038             break;
2039 
2040         case LIGHTREF_CURRENT:
2041             lightLevel = P_GetFloatp(sector, DMU_LIGHT_LEVEL);
2042             break;
2043 
2044         case LIGHTREF_HIGHEST:
2045             P_FindSectorSurroundingHighestLight(sector, &lightLevel);
2046             break;
2047 
2048         case LIGHTREF_LOWEST:
2049             P_FindSectorSurroundingLowestLight(sector, &lightLevel);
2050             break;
2051 
2052         case LIGHTREF_NEXT_HIGHEST:
2053             {
2054             float currentLevel = P_GetFloatp(sector, DMU_LIGHT_LEVEL);
2055             P_FindSectorSurroundingNextHighestLight(sector, currentLevel, &lightLevel);
2056             if(lightLevel < currentLevel)
2057                 lightLevel = currentLevel;
2058             }
2059             break;
2060 
2061         case LIGHTREF_NEXT_LOWEST:
2062             {
2063             float currentLevel = P_GetFloatp(sector, DMU_LIGHT_LEVEL);
2064             P_FindSectorSurroundingNextLowestLight(sector, currentLevel, &lightLevel);
2065             if(lightLevel > currentLevel)
2066                 lightLevel = currentLevel;
2067             }
2068             break;
2069         }
2070 
2071         // Add the offset.
2072         lightLevel += (float) info->iparm[5] / 255.f;
2073 
2074         // Clamp the result.
2075         if(lightLevel < 0)
2076             lightLevel = 0;
2077         if(lightLevel > 1)
2078             lightLevel = 1;
2079 
2080         // Set the value.
2081         P_SetFloatp(sector, DMU_LIGHT_LEVEL, lightLevel);
2082     }
2083 
2084     if(info->iparm[3])
2085     {
2086         switch(info->iparm[6])
2087         {
2088         case LIGHTREF_MY:
2089             {
2090             Sector *sector = (Sector *) P_GetPtrp(line, DMU_FRONT_SECTOR);
2091 
2092             P_GetFloatpv(sector, DMU_COLOR, usergb);
2093             break;
2094             }
2095         case LIGHTREF_BACK:
2096             {
2097             Sector *sector = (Sector *) P_GetPtrp(line, DMU_BACK_SECTOR);
2098 
2099             if(sector)
2100                 P_GetFloatpv(sector, DMU_COLOR, usergb);
2101             else
2102             {
2103                 LOG_MAP_MSG_XGDEVONLY("Warning, the referenced Line has no back sector. Using default color");
2104                 memset(usergb, 0, sizeof(usergb));
2105             }
2106             break;
2107             }
2108         case LIGHTREF_ORIGINAL:
2109             memcpy(usergb, P_ToXSector(sector)->origRGB, sizeof(float) * 3);
2110             break;
2111 
2112         default:
2113             memset(usergb, 0, sizeof(usergb));
2114             break;
2115         }
2116 
2117         for(num = 0; num < 3; ++num)
2118         {
2119             float f = usergb[num] + (float) info->iparm[7 + num] / 255.f;
2120             if(f < 0)
2121                 f = 0;
2122             if(f > 1)
2123                 f = 1;
2124             P_SetFloatp(sector, TO_DMU_COLOR(num), f);
2125         }
2126     }
2127 
2128     return true;
2129 }
2130 
XSTrav_MimicSector(Sector * sector,dd_bool,void * context,void * context2,mobj_t *)2131 int C_DECL XSTrav_MimicSector(Sector *sector, dd_bool /*ceiling*/,
2132                               void *context, void *context2,
2133                               mobj_t * /*activator*/)
2134 {
2135     LOG_AS("XSTrav_MimicSector");
2136 
2137     Line *line = (Line *) context;
2138     linetype_t *info = (linetype_t *) context2;
2139     Sector *from = NULL;
2140     int refdata;
2141 
2142     // Set the spref data parameter (tag or index).
2143     switch(info->iparm[2])
2144     {
2145     case SPREF_TAGGED_FLOOR:
2146     case SPREF_TAGGED_CEILING:
2147     case SPREF_INDEX_FLOOR:
2148     case SPREF_INDEX_CEILING:
2149     case SPREF_ACT_TAGGED_FLOOR:
2150     case SPREF_ACT_TAGGED_CEILING:
2151         if(info->iparm[3] >= 0)
2152             refdata = info->iparm[3];
2153         break;
2154 
2155     case SPREF_LINE_ACT_TAGGED_FLOOR:
2156     case SPREF_LINE_ACT_TAGGED_CEILING:
2157         if(info->actTag >= 0)
2158             refdata = info->actTag;
2159         break;
2160 
2161     default:
2162         refdata = 0;
2163         break;
2164     }
2165 
2166     // If can't apply to a sector, just skip it.
2167     if(!XS_GetPlane(line, sector, info->iparm[2], &refdata, 0, 0, &from))
2168     {
2169         LOG_MAP_MSG_XGDEVONLY2("No suitable neighbor for %i", P_ToIndex(sector));
2170         return true;
2171     }
2172 
2173     // Mimicing itself is pointless.
2174     if(from == sector)
2175         return true;
2176 
2177     LOG_MAP_MSG_XGDEVONLY2("Sector %i mimicking sector %i", P_ToIndex(sector) << P_ToIndex(from));
2178 
2179     // Copy the properties of the target sector.
2180     P_CopySector(sector, from);
2181 
2182     P_ChangeSector(sector, false /*don't crush*/);
2183 
2184     // Copy type as well.
2185     XS_SetSectorType(sector, P_ToXSector(from)->special);
2186 
2187     if(P_ToXSector(from)->xg)
2188         memcpy(P_ToXSector(sector)->xg, P_ToXSector(from)->xg, sizeof(xgsector_t));
2189 
2190     return true;
2191 }
2192 
XSTrav_Teleport(Sector * sector,dd_bool,void *,void * context2,mobj_t * thing)2193 int C_DECL XSTrav_Teleport(Sector* sector, dd_bool /*ceiling*/, void* /*context*/,
2194                            void* context2, mobj_t* thing)
2195 {
2196     LOG_AS("XSTrav_Teleport");
2197 
2198     mobj_t*         mo = NULL;
2199     dd_bool         ok = false;
2200     linetype_t*     info = (linetype_t *) context2;
2201 
2202     // Don't teleport things marked noteleport!
2203     if(thing->flags2 & MF2_NOTELEPORT)
2204     {
2205         LOG_MAP_MSG_XGDEVONLY2("Activator is unteleportable (THING type %i)", thing->type);
2206         return false;
2207     }
2208 
2209     P_IterateThinkers(P_MobjThinker, [&mo, &ok, sector](thinker_t *th) {
2210         mo = reinterpret_cast<mobj_t *>(th);
2211         if (Mobj_Sector(mo) == sector && mo->type == MT_TELEPORTMAN)
2212         {
2213             ok = true;
2214             return de::LoopAbort;
2215         }
2216         return de::LoopContinue;
2217     });
2218 
2219     if(ok)
2220     {
2221         // We can teleport.
2222         mobj_t *flash;
2223         unsigned an;
2224         coord_t oldpos[3];
2225         coord_t thfloorz, thceilz;
2226         coord_t aboveFloor, fogDelta = 0;
2227         angle_t oldAngle;
2228 
2229         LOG_MAP_MSG_XGDEVONLY2("Sector %i, %s, %s%s",
2230                 P_ToIndex(sector)
2231                 << (info->iparm[2]? "No Flash"   : "")
2232                 << (info->iparm[3]? "Play Sound" : "Silent")
2233                 << (info->iparm[4]? " Stomp"     : ""));
2234 
2235         if(!P_TeleportMove(thing, mo->origin[VX], mo->origin[VY], (info->iparm[4] > 0? 1 : 0)))
2236         {
2237             LOG_MAP_MSG_XGDEVONLY("No free space at teleport exit. Aborting teleport...");
2238             return false;
2239         }
2240 
2241         memcpy(oldpos, thing->origin, sizeof(thing->origin));
2242         oldAngle = thing->angle;
2243         thfloorz = P_GetDoublep(Mobj_Sector(thing), DMU_FLOOR_HEIGHT);
2244         thceilz  = P_GetDoublep(Mobj_Sector(thing), DMU_CEILING_HEIGHT);
2245         aboveFloor = thing->origin[VZ] - thfloorz;
2246 
2247         // Players get special consideration
2248         if(thing->player)
2249         {
2250             if((thing->player->plr->mo->flags2 & MF2_FLY) && aboveFloor)
2251             {
2252                 thing->origin[VZ] = thfloorz + aboveFloor;
2253                 if(thing->origin[VZ] + thing->height > thceilz)
2254                 {
2255                     thing->origin[VZ] = thceilz - thing->height;
2256                 }
2257                 thing->player->viewZ =
2258                     thing->origin[VZ] + thing->player->viewHeight;
2259             }
2260             else
2261             {
2262                 thing->origin[VZ] = thfloorz;
2263                 thing->player->viewZ =
2264                     thing->origin[VZ] + thing->player->viewHeight;
2265                 thing->dPlayer->lookDir = 0; /* $unifiedangles */
2266             }
2267 #if __JHERETIC__
2268             if(!thing->player->powers[PT_WEAPONLEVEL2])
2269 #endif
2270             {
2271                 // Freeze player for about .5 sec
2272                 thing->reactionTime = 18;
2273             }
2274 
2275             //thing->dPlayer->clAngle = thing->angle; /* $unifiedangles */
2276             thing->dPlayer->flags |= DDPF_FIXANGLES | DDPF_FIXORIGIN | DDPF_FIXMOM;
2277         }
2278 #if __JHERETIC__
2279         else if(thing->flags & MF_MISSILE)
2280         {
2281             thing->origin[VZ] = thfloorz + aboveFloor;
2282             if(thing->origin[VZ] + thing->height > thceilz)
2283             {
2284                 thing->origin[VZ] = thceilz - thing->height;
2285             }
2286         }
2287 #endif
2288         else
2289         {
2290             thing->origin[VZ] = thfloorz;
2291         }
2292 
2293         // Spawn flash at the old position?
2294         if(!info->iparm[2])
2295         {
2296             // Old position
2297 #if __JHERETIC__
2298             fogDelta = ((thing->flags & MF_MISSILE)? 0 : TELEFOGHEIGHT);
2299 #endif
2300             if((flash = P_SpawnMobjXYZ(MT_TFOG, oldpos[VX], oldpos[VY],
2301                                       oldpos[VZ] + fogDelta,
2302                                       oldAngle + ANG180, 0)))
2303             {
2304                 // Play a sound?
2305                 if(info->iparm[3])
2306                     S_StartSound(info->iparm[3], flash);
2307             }
2308         }
2309 
2310         an = mo->angle >> ANGLETOFINESHIFT;
2311 
2312         // Spawn flash at the new position?
2313         if(!info->iparm[2])
2314         {
2315             // New position
2316             if((flash = P_SpawnMobjXYZ(MT_TFOG,
2317                                       mo->origin[VX] + 20 * FIX2FLT(finecosine[an]),
2318                                       mo->origin[VY] + 20 * FIX2FLT(finesine[an]),
2319                                       mo->origin[VZ] + fogDelta, mo->angle, 0)))
2320             {
2321                 // Play a sound?
2322                 if(info->iparm[3])
2323                     S_StartSound(info->iparm[3], flash);
2324             }
2325         }
2326 
2327         // Adjust the angle to match that of the teleporter exit
2328         thing->angle = mo->angle;
2329 
2330         // Have we teleported from/to a sector with a non-solid floor?
2331         if(thing->flags2 & MF2_FLOORCLIP)
2332         {
2333             thing->floorClip = 0;
2334 
2335             if(FEQUAL(thing->origin[VZ], P_GetDoublep(Mobj_Sector(thing), DMU_FLOOR_HEIGHT)))
2336             {
2337                 terraintype_t const *tt = P_MobjFloorTerrain(thing);
2338                 if(tt->flags & TTF_FLOORCLIP)
2339                 {
2340                     thing->floorClip = 10;
2341                 }
2342             }
2343         }
2344 
2345         if(thing->flags & MF_MISSILE)
2346         {
2347             an >>= ANGLETOFINESHIFT;
2348             thing->mom[MX] = thing->info->speed * FIX2FLT(finecosine[an]);
2349             thing->mom[MY] = thing->info->speed * FIX2FLT(finesine[an]);
2350         }
2351         else
2352         {
2353             thing->mom[MX] = thing->mom[MY] = thing->mom[MZ] = 0;
2354         }
2355     }
2356     else
2357     {   // Keep looking, there may be another referenced sector we could
2358         // teleport to...
2359         LOG_MAP_MSG_XGDEVONLY2("No teleport exit in referenced sector (ID %i)."
2360                " Continuing search...", P_ToIndex(sector));
2361         return true;
2362     }
2363 
2364     return false; // Only do this once.
2365 }
2366 
XF_FindRewindMarker(char * func,int pos)2367 int XF_FindRewindMarker(char *func, int pos)
2368 {
2369     while(pos > 0 && func[pos] != '>')
2370         pos--;
2371     if(func[pos] == '>')
2372         pos++;
2373 
2374     return pos;
2375 }
2376 
XF_GetCount(function_t * fn,int * pos)2377 int XF_GetCount(function_t * fn, int *pos)
2378 {
2379     char       *end;
2380     int         count;
2381 
2382     count = strtol(fn->func + *pos, &end, 10);
2383     *pos = end - fn->func;
2384 
2385     return count;
2386 }
2387 
XF_GetValue(function_t * fn,int pos)2388 float XF_GetValue(function_t * fn, int pos)
2389 {
2390     int         ch;
2391 
2392     if(fn->func[pos] == '/' || fn->func[pos] == '%')
2393     {
2394         // Exact value.
2395         return strtod(fn->func + pos + 1, 0);
2396     }
2397 
2398     ch = tolower(fn->func[pos]);
2399     // A=0, Z=25.
2400     return (ch - 'a') / 25.0f;
2401 }
2402 
2403 /**
2404  * Returns the position of the next value.
2405  * Repeat counting is handled here.
2406  * Poke should be true only if fn->pos is really about to move.
2407  */
XF_FindNextPos(function_t * fn,int pos,dd_bool poke,Sector * sec)2408 int XF_FindNextPos(function_t *fn, int pos, dd_bool poke, Sector *sec)
2409 {
2410     int startpos = pos;
2411     int c;
2412     char *ptr;
2413 
2414     if(fn->repeat > 0)
2415     {
2416         if(poke)
2417             fn->repeat--;
2418         return pos;
2419     }
2420 
2421     // Skip current.
2422     if(fn->func[pos] == '/' || fn->func[pos] == '%')
2423     {
2424         double dvalue = strtod(fn->func + pos + 1, &ptr);
2425         DENG_UNUSED(dvalue);
2426         pos = ptr - fn->func; // Go to the end.
2427     }
2428     else
2429     {   // It's just a normal character [a-z,A-Z].
2430         pos++;
2431     }
2432 
2433     while(pos != startpos && fn->func[pos])
2434     {
2435         // Check for various special characters.
2436         if(isdigit(fn->func[pos]))
2437         {   // A repeat!
2438             // Move pos to the value to be repeated and set repeat counter.
2439             c = XF_GetCount(fn, &pos) - 1;
2440             if(poke)
2441                 fn->repeat = c;
2442             return pos;
2443         }
2444 
2445         if(fn->func[pos] == '!')
2446         {   // Chain event.
2447             pos++;
2448             c = XF_GetCount(fn, &pos);
2449             if(poke)
2450             {
2451                 // Sector funcs don't have activators.
2452                 XS_DoChain(sec, XSCE_FUNCTION, c, NULL);
2453             }
2454             continue;
2455         }
2456 
2457         if(fn->func[pos] == '#')
2458         {   // Set timer.
2459             pos++;
2460             c = XF_GetCount(fn, &pos);
2461             if(poke)
2462             {
2463                 fn->timer = 0;
2464                 fn->maxTimer = c;
2465             }
2466             continue;
2467         }
2468 
2469         if(fn->func[pos] == '?')
2470         {   // Random timer.
2471             pos++;
2472             c = XF_GetCount(fn, &pos);
2473             if(poke)
2474             {
2475                 fn->timer = 0;
2476                 fn->maxTimer = XG_RandomInt(0, c);
2477             }
2478             continue;
2479         }
2480 
2481         if(fn->func[pos] == '<')
2482         {   // Rewind.
2483             pos = XF_FindRewindMarker(fn->func, pos);
2484             continue;
2485         }
2486 
2487         if(poke)
2488         {
2489             if(islower(fn->func[pos]) || fn->func[pos] == '/')
2490             {
2491                 int     next = XF_FindNextPos(fn, pos, false, sec);
2492 
2493                 if(fn->func[next] == '.')
2494                 {
2495                     pos++;
2496                     continue;
2497                 }
2498                 break;
2499             }
2500         }
2501         else if(fn->func[pos] == '.')
2502         {
2503             break;
2504         }
2505 
2506         // Is it a value, then?
2507         if(isalpha(fn->func[pos]) || fn->func[pos] == '/' ||
2508            fn->func[pos] == '%')
2509             break;
2510 
2511         // A bad character, skip it.
2512         pos++;
2513     }
2514 
2515     return pos;
2516 }
2517 
2518 /**
2519  * Tick the function, update value.
2520  */
XF_Ticker(function_t * fn,Sector * sec)2521 void XF_Ticker(function_t* fn, Sector* sec)
2522 {
2523     int                 next;
2524     float               inter;
2525 
2526     // Store the previous value of the function.
2527     fn->oldValue = fn->value;
2528 
2529     // Is there a function?
2530     if(!ISFUNC(fn) || fn->link)
2531         return;
2532 
2533     // Increment time.
2534     if(fn->timer++ >= fn->maxTimer)
2535     {
2536         fn->timer = 0;
2537         fn->maxTimer = XG_RandomInt(fn->minInterval, fn->maxInterval);
2538 
2539         // Advance to next pos.
2540         fn->pos = XF_FindNextPos(fn, fn->pos, true, sec);
2541     }
2542 
2543     /**
2544      * Syntax:
2545      *
2546      * abcdefghijlkmnopqrstuvwxyz (26)
2547      *
2548      * az.< (fade from 0 to 1, break interpolation and repeat)
2549      * [note that AZ.AZ is the same as AZAZ]
2550      * [also note that a.z is the same as z]
2551      * az.>mz< (fade from 0 to 1, break, repeat fade from 0.5 to 1 to 0.5)
2552      * 10a10z is the same as aaaaaaaaaazzzzzzzzzz
2553      * aB or Ba do not interpolate
2554      * zaN (interpolate from 1 to 0, wait at 0, stay at N)
2555      * za.N (interpolate from 1 to 0, skip to N)
2556      * 1A is the same as A
2557      */
2558 
2559     // Stop?
2560     if(!fn->func[fn->pos])
2561         return;
2562 
2563     if(isupper(fn->func[fn->pos]) || fn->func[fn->pos] == '%')
2564     {   // No interpolation.
2565         fn->value = XF_GetValue(fn, fn->pos);
2566     }
2567     else
2568     {
2569         inter = 0;
2570         next = XF_FindNextPos(fn, fn->pos, false, sec);
2571         if(islower(fn->func[next]) || fn->func[next] == '/')
2572         {
2573             if(fn->maxTimer)
2574                 inter = fn->timer / (float) fn->maxTimer;
2575         }
2576 
2577         fn->value = (1 - inter) * XF_GetValue(fn, fn->pos) +
2578             inter * XF_GetValue(fn, next);
2579     }
2580 
2581     // Scale and offset.
2582     fn->value = fn->value * fn->scale + fn->offset;
2583 }
2584 
XS_UpdatePlanes(Sector * sec)2585 void XS_UpdatePlanes(Sector* sec)
2586 {
2587     int i;
2588     xgsector_t* xg;
2589     function_t* fn;
2590     dd_bool docrush;
2591 
2592     xg = P_ToXSector(sec)->xg;
2593     docrush = (xg->info.flags & STF_CRUSH) != 0;
2594 
2595     // Update floor.
2596     fn = &xg->plane[XGSP_FLOOR];
2597     if(UPDFUNC(fn))
2598     {
2599         // Changed; How different?
2600         i = fn->value - P_GetFloatp(sec, DMU_FLOOR_HEIGHT);
2601         if(i)
2602         {
2603             // Move the floor plane accordingly.
2604             T_MovePlane(sec, abs(i), fn->value, docrush, 0, SIGN(i));
2605         }
2606     }
2607 
2608     // Update celing.
2609     fn = &xg->plane[XGSP_CEILING];
2610     if(UPDFUNC(fn))
2611     {
2612         // Changed; How different?
2613         i = fn->value - P_GetFloatp(sec, DMU_CEILING_HEIGHT);
2614         if(i)
2615         {
2616             // Move the ceiling accordingly.
2617             T_MovePlane(sec, abs(i), fn->value, docrush, 1, SIGN(i));
2618         }
2619     }
2620 }
2621 
XS_DoChain(Sector * sec,int ch,int activating,void * act_thing)2622 void XS_DoChain(Sector *sec, int ch, int activating, void *act_thing)
2623 {
2624     LOG_AS("XS_DoChain");
2625 
2626     xgsector_t *xg;
2627     sectortype_t *info;
2628     float flevtime = TIC2FLT(mapTime);
2629     Line *dummyLine;
2630     xline_t *xdummyLine;
2631     linetype_t *ltype;
2632 
2633     xg = P_ToXSector(sec)->xg;
2634     info = &xg->info;
2635 
2636     if(ch < XSCE_NUM_CHAINS)
2637     {
2638         // How's the counter?
2639         if(!info->count[ch])
2640             return;
2641 
2642         // How's the time?
2643         if(flevtime < info->start[ch] ||
2644            (info->end[ch] > 0 && flevtime > info->end[ch]))
2645             return; // Not operating at this time.
2646 
2647         // Time to try the chain. Reset timer.
2648         xg->chainTimer[ch] =
2649             XG_RandomInt(FLT2TIC(info->interval[ch][0]),
2650                          FLT2TIC(info->interval[ch][1]));
2651     }
2652 
2653     // Prepare the dummies to use for the event.
2654     dummyLine = P_AllocDummyLine();
2655     xdummyLine = P_ToXLine(dummyLine);
2656     xdummyLine->xg = (xgline_t *) Z_Calloc(sizeof(xgline_t), PU_MAP, 0);
2657 
2658     P_SetPtrp(dummyLine, DMU_FRONT_SECTOR, sec);
2659 
2660     xdummyLine->special = (ch == XSCE_FUNCTION ? activating : info->chain[ch]);
2661 
2662     xdummyLine->tag = P_ToXSector(sec)->tag;
2663 
2664     ltype = XL_GetType(xdummyLine->special);
2665     if(!ltype)
2666     {
2667         // What is this? There is no such XG line type.
2668         LOG_MAP_MSG_XGDEVONLY2("Unknown XG line type %i", xdummyLine->special);
2669         // We're done, free the dummy.
2670         Z_Free(xdummyLine->xg);
2671         P_FreeDummyLine(dummyLine);
2672         return;
2673     }
2674 
2675     memcpy(&xdummyLine->xg->info, ltype, sizeof(*ltype));
2676 
2677     if(act_thing)
2678         xdummyLine->xg->activator = act_thing;
2679     else
2680         xdummyLine->xg->activator = NULL;
2681 
2682     xdummyLine->xg->active = (ch == XSCE_FUNCTION ? false : !activating);
2683 
2684     LOG_MAP_MSG_XGDEVONLY2("Dummy line will show up as %i", P_ToIndex(dummyLine));
2685 
2686     // Send the event.
2687     if(XL_LineEvent((ch == XSCE_FUNCTION ? XLE_FUNC : XLE_CHAIN), 0,
2688                     dummyLine, 0, act_thing))
2689     {   // Success!
2690         if(ch < XSCE_NUM_CHAINS)
2691         {
2692             // Decrease counter.
2693             if(info->count[ch] > 0)
2694             {
2695                 info->count[ch]--;
2696 
2697                 LOG_MAP_MSG_XGDEVONLY2("%s, sector %i (activating=%i): Counter now at %i",
2698                         (  ch == XSCE_FLOOR    ? "FLOOR"
2699                          : ch == XSCE_CEILING  ? "CEILING"
2700                          : ch == XSCE_INSIDE   ? "INSIDE"
2701                          : ch == XSCE_TICKER   ? "TICKER"
2702                          : ch == XSCE_FUNCTION ? "FUNCTION" : "???")
2703                         << P_ToIndex(sec)
2704                         << activating << info->count[ch]);
2705             }
2706         }
2707     }
2708 
2709     // We're done, free the dummies.
2710     Z_Free(xdummyLine->xg);
2711     P_FreeDummyLine(dummyLine);
2712 }
2713 
checkChainRequirements(Sector * sec,mobj_t * mo,int ch,dd_bool * activating)2714 static dd_bool checkChainRequirements(Sector *sec, mobj_t *mo, int ch, dd_bool *activating)
2715 {
2716     xgsector_t*         xg;
2717     sectortype_t*       info;
2718     player_t*           player = mo->player;
2719     int                 flags;
2720     dd_bool             typePasses = false;
2721 
2722     xg = P_ToXSector(sec)->xg;
2723     info = &xg->info;
2724     flags = info->chainFlags[ch];
2725 
2726     // Check mobj type.
2727     if((flags & (SCEF_ANY_A | SCEF_ANY_D | SCEF_TICKER_A | SCEF_TICKER_D)) ||
2728        ((flags & (SCEF_PLAYER_A | SCEF_PLAYER_D)) && player) ||
2729        ((flags & (SCEF_OTHER_A | SCEF_OTHER_D)) && !player) ||
2730        ((flags & (SCEF_MONSTER_A | SCEF_MONSTER_D)) && (mo->flags & MF_COUNTKILL)) ||
2731        ((flags & (SCEF_MISSILE_A | SCEF_MISSILE_D)) && (mo->flags & MF_MISSILE)))
2732         typePasses = true;
2733 
2734     if(!typePasses)
2735         return false; // Wrong type.
2736 
2737     // Are we looking for an activation effect?
2738     if(player)
2739         *activating = !(flags & SCEF_PLAYER_D);
2740     else if(mo->flags & MF_COUNTKILL)
2741         *activating = !(flags & SCEF_MONSTER_D);
2742     else if(mo->flags & MF_MISSILE)
2743         *activating = !(flags & SCEF_MISSILE_D);
2744     else if(flags & (SCEF_ANY_A | SCEF_ANY_D))
2745         *activating = !(flags & SCEF_ANY_D);
2746     else
2747         *activating = !(flags & SCEF_OTHER_D);
2748 
2749     // Check for extra requirements (touching).
2750     switch(ch)
2751     {
2752     case XSCE_FLOOR:
2753         // Is it touching the floor?
2754         if(mo->origin[VZ] > P_GetDoublep(sec, DMU_FLOOR_HEIGHT) + 0.0001)
2755             return false;
2756         break;
2757 
2758     case XSCE_CEILING:
2759         // Is it touching the ceiling?
2760         if(mo->origin[VZ] + mo->height < P_GetDoublep(sec, DMU_CEILING_HEIGHT) - 0.0001)
2761             return false;
2762         break;
2763 
2764     default:
2765         break;
2766     }
2767 
2768     return true;
2769 }
2770 
2771 typedef struct {
2772     Sector *sec;
2773     int data;
2774 } xstrav_sectorchainparams_t;
2775 
XSTrav_SectorChain(thinker_t * th,void * context)2776 int XSTrav_SectorChain(thinker_t *th, void *context)
2777 {
2778     xstrav_sectorchainparams_t *params = (xstrav_sectorchainparams_t *) context;
2779     mobj_t *mo = (mobj_t *) th;
2780 
2781     if(params->sec == Mobj_Sector(mo))
2782     {
2783         dd_bool activating;
2784 
2785         if(checkChainRequirements(params->sec, mo, params->data, &activating))
2786             XS_DoChain(params->sec, params->data, activating, mo);
2787     }
2788 
2789     return false; // Continue iteration.
2790 }
2791 
P_ApplyWind(mobj_t * mo,Sector * sec)2792 void P_ApplyWind(mobj_t *mo, Sector *sec)
2793 {
2794     sectortype_t *info;
2795     float ang;
2796 
2797     if(mo->player && (mo->player->plr->flags & DDPF_CAMERA))
2798         return; // Wind does not affect cameras.
2799 
2800     info = &(P_ToXSector(sec)->xg->info);
2801     ang = DD_PI * info->windAngle / 180;
2802 
2803     if(IS_CLIENT)
2804     {
2805         // Clientside wind only affects the local player.
2806         if(!mo->player || mo->player != &players[CONSOLEPLAYER])
2807             return;
2808     }
2809 
2810     // Does wind affect this sort of things?
2811     if(((info->flags & STF_PLAYER_WIND) && mo->player) ||
2812        ((info->flags & STF_OTHER_WIND) && !mo->player) ||
2813        ((info->flags & STF_MONSTER_WIND) && (mo->flags & MF_COUNTKILL)) ||
2814        ((info->flags & STF_MISSILE_WIND) && (mo->flags & MF_MISSILE)))
2815     {
2816         coord_t thfloorz = P_GetDoublep(Mobj_Sector(mo), DMU_FLOOR_HEIGHT);
2817         coord_t thceilz  = P_GetDoublep(Mobj_Sector(mo), DMU_CEILING_HEIGHT);
2818 
2819         if(!(info->flags & (STF_FLOOR_WIND | STF_CEILING_WIND)) ||
2820            ((info->flags & STF_FLOOR_WIND) && mo->origin[VZ] <= thfloorz) ||
2821            ((info->flags & STF_CEILING_WIND) &&
2822             mo->origin[VZ] + mo->height >= thceilz))
2823         {
2824             // Apply vertical wind.
2825             mo->mom[MZ] += info->verticalWind;
2826 
2827             // Horizontal wind.
2828             mo->mom[MX] += cos(ang) * info->windSpeed;
2829             mo->mom[MY] += sin(ang) * info->windSpeed;
2830         }
2831     }
2832 }
2833 
2834 typedef struct {
2835     Sector *sec;
2836 } xstrav_windparams_t;
2837 
XSTrav_Wind(thinker_t * th,void * context)2838 int XSTrav_Wind(thinker_t *th, void *context)
2839 {
2840     xstrav_windparams_t *params = (xstrav_windparams_t *) context;
2841     mobj_t *mo = (mobj_t *) th;
2842 
2843     if(params->sec == Mobj_Sector(mo))
2844     {
2845         P_ApplyWind(mo, params->sec);
2846     }
2847 
2848     return false; // Continue iteration.
2849 }
2850 
2851 /**
2852  * Makes sure the offset is in the range 0..64.
2853  */
XS_ConstrainPlaneOffset(float * offset)2854 void XS_ConstrainPlaneOffset(float *offset)
2855 {
2856     if(*offset > 64)
2857         *offset -= 64;
2858     if(*offset < 0)
2859         *offset += 64;
2860 }
2861 
2862 /**
2863  * XG sectors get to think.
2864  */
XS_Thinker(void * xsThinker)2865 void XS_Thinker(void *xsThinker)
2866 {
2867     xsthinker_t* xs = (xsthinker_t *) xsThinker;
2868     Sector* sector = xs->sector;
2869     xsector_t *xsector = P_ToXSector(sector);
2870     xgsector_t* xg;
2871     sectortype_t* info;
2872     int i;
2873 
2874     if(!xsector) return; // Not an xsector? Most perculiar...
2875 
2876     xg = xsector->xg;
2877     if(!xg) return; // Not an extended sector.
2878 
2879     if(xg->disabled) return; // This sector is disabled.
2880 
2881     info = &xg->info;
2882 
2883     if(!IS_CLIENT)
2884     {
2885         // Function tickers.
2886         for(i = 0; i < 2; ++i)
2887             XF_Ticker(&xg->plane[i], sector);
2888         XF_Ticker(&xg->light, sector);
2889         for(i = 0; i < 3; ++i)
2890             XF_Ticker(&xg->rgb[i], sector);
2891 
2892         // Update linked functions.
2893         for(i = 0; i < 3; ++i)
2894         {
2895             if(i < 2 && xg->plane[i].link)
2896                 xg->plane[i].value = xg->plane[i].link->value;
2897 
2898             if(xg->rgb[i].link)
2899                 xg->rgb[i].value = xg->rgb[i].link->value;
2900         }
2901 
2902         if(xg->light.link)
2903             xg->light.value = xg->light.link->value;
2904 
2905         // Update planes.
2906         XS_UpdatePlanes(sector);
2907 
2908         // Update sector light.
2909         XS_UpdateLight(sector);
2910 
2911         // Decrement chain timers.
2912         for(i = 0; i < XSCE_NUM_CHAINS; ++i)
2913             xg->chainTimer[i]--;
2914 
2915         // Floor chain. Check any mobjs that are touching the floor of the
2916         // sector.
2917         if(info->chain[XSCE_FLOOR] && xg->chainTimer[XSCE_FLOOR] <= 0)
2918         {
2919             xstrav_sectorchainparams_t params;
2920 
2921             params.sec = sector;
2922             params.data = XSCE_FLOOR;
2923             Thinker_Iterate((thinkfunc_t) P_MobjThinker, XSTrav_SectorChain, &params);
2924         }
2925 
2926         // Ceiling chain. Check any mobjs that are touching the ceiling.
2927         if(info->chain[XSCE_CEILING] && xg->chainTimer[XSCE_CEILING] <= 0)
2928         {
2929             xstrav_sectorchainparams_t params;
2930 
2931             params.sec = sector;
2932             params.data = XSCE_CEILING;
2933             Thinker_Iterate((thinkfunc_t) P_MobjThinker, XSTrav_SectorChain, &params);
2934         }
2935 
2936         // Inside chain. Check any sectorlinked mobjs.
2937         if(info->chain[XSCE_INSIDE] && xg->chainTimer[XSCE_INSIDE] <= 0)
2938         {
2939             xstrav_sectorchainparams_t params;
2940 
2941             params.sec = sector;
2942             params.data = XSCE_INSIDE;
2943             Thinker_Iterate((thinkfunc_t) P_MobjThinker, XSTrav_SectorChain, &params);
2944         }
2945 
2946         // Ticker chain. Send an activate event if TICKER_D flag is not set.
2947         if(info->chain[XSCE_TICKER] && xg->chainTimer[XSCE_TICKER] <= 0)
2948         {
2949             XS_DoChain(sector, XSCE_TICKER,
2950                        !(info->chainFlags[XSCE_TICKER] & SCEF_TICKER_D),
2951                        XG_DummyThing());
2952         }
2953 
2954         // Play ambient sounds.
2955         if(xg->info.ambientSound)
2956         {
2957             if(xg->timer-- < 0)
2958             {
2959                 xg->timer = XG_RandomInt(FLT2TIC(xg->info.soundInterval[0]),
2960                                          FLT2TIC(xg->info.soundInterval[1]));
2961                 S_SectorSound(sector, xg->info.ambientSound);
2962             }
2963         }
2964     }
2965 
2966     // Floor Texture movement
2967     if(xg->info.materialMoveSpeed[0] != 0)
2968     {
2969         coord_t floorOffset[2];
2970         double ang = DD_PI * xg->info.materialMoveAngle[0] / 180;
2971 
2972         P_GetDoublepv(sector, DMU_FLOOR_MATERIAL_OFFSET_XY, floorOffset);
2973         floorOffset[VX] -= cos(ang) * xg->info.materialMoveSpeed[0];
2974         floorOffset[VY] -= sin(ang) * xg->info.materialMoveSpeed[0];
2975 
2976         // Set the results
2977         P_SetDoublepv(sector, DMU_FLOOR_MATERIAL_OFFSET_XY, floorOffset);
2978     }
2979 
2980     // Ceiling Texture movement
2981     if(xg->info.materialMoveSpeed[1] != 0)
2982     {
2983         coord_t ceilOffset[2];
2984         double ang = DD_PI * xg->info.materialMoveAngle[1] / 180;
2985 
2986         P_GetDoublepv(sector, DMU_CEILING_MATERIAL_OFFSET_XY, ceilOffset);
2987         ceilOffset[VX] -= cos(ang) * xg->info.materialMoveSpeed[1];
2988         ceilOffset[VY] -= sin(ang) * xg->info.materialMoveSpeed[1];
2989 
2990         // Set the results
2991         P_SetDoublepv(sector, DMU_CEILING_MATERIAL_OFFSET_XY, ceilOffset);
2992     }
2993 
2994     // Wind for all sectorlinked mobjs.
2995     if(xg->info.windSpeed || xg->info.verticalWind)
2996     {
2997         xstrav_windparams_t params;
2998 
2999         params.sec = sector;
3000         Thinker_Iterate((thinkfunc_t) P_MobjThinker, XSTrav_Wind, &params);
3001     }
3002 }
3003 
XS_Gravity(Sector * sec)3004 coord_t XS_Gravity(Sector* sec)
3005 {
3006     xsector_t* xsec;
3007 
3008     if(!sec) return P_GetGravity(); // World gravity.
3009 
3010     xsec = P_ToXSector(sec);
3011     if(!xsec->xg || !(xsec->xg->info.flags & STF_GRAVITY))
3012     {
3013         return P_GetGravity(); // World gravity.
3014     }
3015     else
3016     {   // Sector-specific gravity.
3017         coord_t gravity = xsec->xg->info.gravity;
3018 
3019         // Apply gravity modifier.
3020         if(cfg.common.netGravity != -1)
3021             gravity *= (coord_t) cfg.common.netGravity / 100;
3022 
3023         return gravity;
3024     }
3025 }
3026 
XS_Friction(Sector const * sector)3027 coord_t XS_Friction(Sector const *sector)
3028 {
3029     auto const *xsec = P_ToXSector_const(sector);
3030 
3031     if(!xsec->xg || !(xsec->xg->info.flags & STF_FRICTION))
3032         return FRICTION_NORMAL; // Normal friction.
3033 
3034     return xsec->xg->info.friction;
3035 }
3036 
3037 /**
3038  * During update, definitions are re-read, so the pointers need to be
3039  * updated. However, this is a bit messy operation, prone to errors.
3040  * Instead, we just disable XG...
3041  */
XS_Update(void)3042 void XS_Update(void)
3043 {
3044     int i;
3045     xsector_t  *xsec;
3046 
3047     // It's all PU_MAP memory, so we can just lose it.
3048     for(i = 0; i < numsectors; ++i)
3049     {
3050         xsec = P_ToXSector((Sector *) P_ToPtr(DMU_SECTOR, i));
3051         if(xsec->xg)
3052         {
3053             xsec->xg = 0;
3054             xsec->special = 0;
3055         }
3056     }
3057 }
3058 
3059 /**
3060  * $moveplane: Command line interface to the plane mover.
3061  */
D_CMD(MovePlane)3062 D_CMD(MovePlane)
3063 {
3064     DENG_UNUSED(src);
3065 
3066     dd_bool isCeiling = !stricmp(argv[0], "moveceil");
3067     dd_bool isBoth = !stricmp(argv[0], "movesec");
3068     dd_bool isOffset = false, isCrusher = false;
3069     Sector* sector = NULL;
3070     coord_t units = 0;
3071     float speed = FRACUNIT;
3072     int p = 0;
3073     coord_t floorheight, ceilingheight;
3074     xgplanemover_t* mover;
3075 
3076     if(argc < 2)
3077     {
3078         App_Log(DE2_SCR_NOTE, "Usage: %s (opts)", argv[0]);
3079         App_Log(DE2_LOG_SCR, "Opts can be:");
3080         App_Log(DE2_LOG_SCR, "  here [crush] [off] (z/units) [speed]");
3081         App_Log(DE2_LOG_SCR, "  at (x) (y) [crush] [off] (z/units) [speed]");
3082         App_Log(DE2_LOG_SCR, "  tag (sector-tag) [crush] [off] (z/units) [speed]");
3083         return true;
3084     }
3085 
3086     if(IS_CLIENT)
3087     {
3088         App_Log(DE2_SCR_ERROR, "Clients can't move planes");
3089         return false;
3090     }
3091 
3092     // Which mode?
3093     if(!stricmp(argv[1], "here"))
3094     {
3095         p = 2;
3096         if(!players[CONSOLEPLAYER].plr->mo)
3097             return false;
3098         sector = Mobj_Sector(players[CONSOLEPLAYER].plr->mo);
3099     }
3100     else if(!stricmp(argv[1], "at") && argc >= 4)
3101     {
3102         coord_t point[2];
3103         point[VX] = (coord_t)strtol(argv[2], 0, 0);
3104         point[VY] = (coord_t)strtol(argv[3], 0, 0);
3105         sector = Sector_AtPoint_FixedPrecision(point);
3106 
3107         p = 4;
3108     }
3109     else if(!stricmp(argv[1], "tag") && argc >= 3)
3110     {
3111         int tag = (short) strtol(argv[2], 0, 0);
3112         Sector* sec = NULL;
3113         iterlist_t* list;
3114 
3115         p = 3;
3116         list = P_GetSectorIterListForTag(tag, false);
3117         if(list)
3118         {   // Find the first sector with the tag.
3119             IterList_SetIteratorDirection(list, ITERLIST_FORWARD);
3120             IterList_RewindIterator(list);
3121             while((sec = (Sector *) IterList_MoveIterator(list)) != NULL)
3122             {
3123                 sector = sec;
3124                 break;
3125             }
3126         }
3127     }
3128     else
3129     {   // Unknown mode.
3130         App_Log(DE2_SCR_ERROR, "Unknown mode");
3131         return false;
3132     }
3133 
3134     floorheight   = P_GetDoublep(sector, DMU_FLOOR_HEIGHT);
3135     ceilingheight = P_GetDoublep(sector, DMU_CEILING_HEIGHT);
3136 
3137     // No more arguments?
3138     if(argc == p)
3139     {
3140         App_Log(DE2_LOG_MAP, "Ceiling = %g, Floor = %g", ceilingheight, floorheight);
3141         return true;
3142     }
3143 
3144     // Check for the optional 'crush' parameter.
3145     if(argc >= p + 1 && !stricmp(argv[p], "crush"))
3146     {
3147         isCrusher = true;
3148         ++p;
3149     }
3150 
3151     // Check for the optional 'off' parameter.
3152     if(argc >= p + 1 && !stricmp(argv[p], "off"))
3153     {
3154         isOffset = true;
3155         ++p;
3156     }
3157 
3158     // The amount to move.
3159     if(argc >= p + 1)
3160     {
3161         units = strtod(argv[p++], 0);
3162     }
3163     else
3164     {
3165         App_Log(DE2_SCR_ERROR, "You must specify Z-units");
3166         return false; // Required parameter missing.
3167     }
3168 
3169     // The optional speed parameter.
3170     if(argc >= p + 1)
3171     {
3172         speed = strtod(argv[p++], 0);
3173         // The speed is always positive.
3174         if(speed < 0)
3175             speed = -speed;
3176     }
3177 
3178     // We must now have found the sector to operate on.
3179     if(!sector)
3180         return false;
3181 
3182     mover = XS_GetPlaneMover(sector, isCeiling);
3183 
3184     // Setup the thinker and add it to the list.
3185     mover->destination = units + (isOffset ? (isCeiling ? ceilingheight : floorheight) : 0);
3186 
3187     mover->speed = speed;
3188     if(isCrusher)
3189     {
3190         mover->crushSpeed = speed * .5;  // Crush at half speed.
3191         mover->flags |= PMF_CRUSH;
3192     }
3193     if(isBoth)
3194         mover->flags |= PMF_OTHER_FOLLOWS;
3195 
3196     return true;
3197 }
3198 
3199 #endif
3200