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, ¶ms);
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 §or, bool ceiling, world_Material &newMaterial)
666 {
667 LOG_AS("XS_ChangePlaneMaterial");
668 LOG_MAP_MSG_XGDEVONLY2("Sector %i, %s, texture %i",
669 P_ToIndex(§or) << (ceiling ? "ceiling" : "floor") << P_ToIndex(&newMaterial));
670
671 P_SetPtrp(§or, 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 §or, 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(§or) << (ceiling ? "ceiling" : "floor") << newColor.asText());
679
680 float rgb[3];
681 if (isDelta)
682 {
683 P_GetFloatpv(§or, 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(§or, 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, ¶ms);
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, ¶ms);
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, ¶ms);
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, ¶ms);
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, ¶ms);
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, ¶ms);
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, ¶ms);
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