1 // SONIC ROBO BLAST 2
2 //-----------------------------------------------------------------------------
3 // Copyright (C) 1993-1996 by id Software, Inc.
4 // Copyright (C) 1998-2000 by DooM Legacy Team.
5 // Copyright (C) 1999-2020 by Sonic Team Junior.
6 //
7 // This program is free software distributed under the
8 // terms of the GNU General Public License, version 2.
9 // See the 'LICENSE' file for more details.
10 //-----------------------------------------------------------------------------
11 /// \file p_spec.c
12 /// \brief Implements special effects:
13 /// Texture animation, height or lighting changes
14 /// according to adjacent sectors, respective
15 /// utility functions, etc.
16 /// Line Tag handling. Line and Sector triggers.
17
18 #include "doomdef.h"
19 #include "g_game.h"
20 #include "p_local.h"
21 #include "p_setup.h" // levelflats for flat animation
22 #include "r_data.h"
23 #include "r_textures.h"
24 #include "m_random.h"
25 #include "p_mobj.h"
26 #include "i_system.h"
27 #include "s_sound.h"
28 #include "w_wad.h"
29 #include "z_zone.h"
30 #include "r_main.h" //Two extra includes.
31 #include "r_sky.h"
32 #include "p_polyobj.h"
33 #include "p_slopes.h"
34 #include "hu_stuff.h"
35 #include "v_video.h" // V_AUTOFADEOUT|V_ALLOWLOWERCASE
36 #include "m_misc.h"
37 #include "m_cond.h" //unlock triggers
38 #include "lua_hook.h" // LUAh_LinedefExecute
39 #include "f_finale.h" // control text prompt
40 #include "r_skins.h" // skins
41
42 #ifdef HW3SOUND
43 #include "hardware/hw3sound.h"
44 #endif
45
46 // Not sure if this is necessary, but it was in w_wad.c, so I'm putting it here too -Shadow Hog
47 #include <errno.h>
48
49 mobj_t *skyboxmo[2]; // current skybox mobjs: 0 = viewpoint, 1 = centerpoint
50 mobj_t *skyboxviewpnts[16]; // array of MT_SKYBOX viewpoint mobjs
51 mobj_t *skyboxcenterpnts[16]; // array of MT_SKYBOX centerpoint mobjs
52
53 // Amount (dx, dy) vector linedef is shifted right to get scroll amount
54 #define SCROLL_SHIFT 5
55
56 /** Animated texture descriptor
57 * This keeps track of an animated texture or an animated flat.
58 * \sa P_UpdateSpecials, P_InitPicAnims, animdef_t
59 */
60 typedef struct
61 {
62 SINT8 istexture; ///< ::true for a texture, ::false for a flat
63 INT32 picnum; ///< The end flat number
64 INT32 basepic; ///< The start flat number
65 INT32 numpics; ///< Number of frames in the animation
66 tic_t speed; ///< Number of tics for which each frame is shown
67 } anim_t;
68
69 #if defined(_MSC_VER)
70 #pragma pack(1)
71 #endif
72
73 /** Animated texture definition.
74 * Used for loading an ANIMDEFS lump from a wad.
75 *
76 * Animations are defined by the first and last frame (i.e., flat or texture).
77 * The animation sequence uses all flats between the start and end entry, in
78 * the order found in the wad.
79 *
80 * \sa anim_t
81 */
82 typedef struct
83 {
84 SINT8 istexture; ///< True for a texture, false for a flat.
85 char endname[9]; ///< Name of the last frame, null-terminated.
86 char startname[9]; ///< Name of the first frame, null-terminated.
87 INT32 speed ; ///< Number of tics for which each frame is shown.
88 } ATTRPACK animdef_t;
89
90 #if defined(_MSC_VER)
91 #pragma pack()
92 #endif
93
94 typedef struct
95 {
96 UINT32 count;
97 thinker_t **thinkers;
98 } thinkerlist_t;
99
100 static void P_SpawnScrollers(void);
101 static void P_SpawnFriction(void);
102 static void P_SpawnPushers(void);
103 static void Add_Pusher(pushertype_e type, fixed_t x_mag, fixed_t y_mag, mobj_t *source, INT32 affectee, INT32 referrer, INT32 exclusive, INT32 slider); //SoM: 3/9/2000
104 static void Add_MasterDisappearer(tic_t appeartime, tic_t disappeartime, tic_t offset, INT32 line, INT32 sourceline);
105 static void P_ResetFakeFloorFader(ffloor_t *rover, fade_t *data, boolean finalize);
106 #define P_RemoveFakeFloorFader(l) P_ResetFakeFloorFader(l, NULL, false);
107 static boolean P_FadeFakeFloor(ffloor_t *rover, INT16 sourcevalue, INT16 destvalue, INT16 speed, boolean ticbased, INT32 *timer,
108 boolean doexists, boolean dotranslucent, boolean dolighting, boolean docolormap,
109 boolean docollision, boolean doghostfade, boolean exactalpha);
110 static void P_AddFakeFloorFader(ffloor_t *rover, size_t sectornum, size_t ffloornum,
111 INT16 destvalue, INT16 speed, boolean ticbased, boolean relative,
112 boolean doexists, boolean dotranslucent, boolean dolighting, boolean docolormap,
113 boolean docollision, boolean doghostfade, boolean exactalpha);
114 static void P_ResetColormapFader(sector_t *sector);
115 static void Add_ColormapFader(sector_t *sector, extracolormap_t *source_exc, extracolormap_t *dest_exc,
116 boolean ticbased, INT32 duration);
117 static void P_AddBlockThinker(sector_t *sec, line_t *sourceline);
118 static void P_AddFloatThinker(sector_t *sec, UINT16 tag, line_t *sourceline);
119 //static void P_AddBridgeThinker(line_t *sourceline, sector_t *sec);
120 static void P_AddFakeFloorsByLine(size_t line, ffloortype_e ffloorflags, thinkerlist_t *secthinkers);
121 static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec);
122 static void Add_Friction(INT32 friction, INT32 movefactor, INT32 affectee, INT32 referrer);
123 static void P_AddPlaneDisplaceThinker(INT32 type, fixed_t speed, INT32 control, INT32 affectee, UINT8 reverse);
124
125
126 //SoM: 3/7/2000: New sturcture without limits.
127 static anim_t *lastanim;
128 static anim_t *anims = NULL; /// \todo free leak
129 static size_t maxanims;
130
131 // Animating line specials
132
133 // Init animated textures
134 // - now called at level loading P_SetupLevel()
135
136 static animdef_t *animdefs = NULL;
137
138 // Increase the size of animdefs to make room for a new animation definition
GrowAnimDefs(void)139 static void GrowAnimDefs(void)
140 {
141 maxanims++;
142 animdefs = (animdef_t *)Z_Realloc(animdefs, sizeof(animdef_t)*(maxanims + 1), PU_STATIC, NULL);
143 }
144
145 // A prototype; here instead of p_spec.h, so they're "private"
146 void P_ParseANIMDEFSLump(INT32 wadNum, UINT16 lumpnum);
147 void P_ParseAnimationDefintion(SINT8 istexture);
148
149 /** Sets up texture and flat animations.
150 *
151 * Converts an ::animdef_t array loaded from a lump into
152 * ::anim_t format.
153 *
154 * Issues an error if any animation cycles are invalid.
155 *
156 * \sa P_FindAnimatedFlat, P_SetupLevelFlatAnims
157 * \author Steven McGranahan (original), Shadow Hog (had to rewrite it to handle multiple WADs), JTE (had to rewrite it to handle multiple WADs _correctly_)
158 */
P_InitPicAnims(void)159 void P_InitPicAnims(void)
160 {
161 // Init animation
162 INT32 w; // WAD
163 size_t i;
164
165 I_Assert(animdefs == NULL);
166
167 maxanims = 0;
168
169 for (w = numwadfiles-1; w >= 0; w--)
170 {
171 UINT16 animdefsLumpNum;
172
173 // Find ANIMDEFS lump in the WAD
174 animdefsLumpNum = W_CheckNumForNamePwad("ANIMDEFS", w, 0);
175
176 while (animdefsLumpNum != INT16_MAX)
177 {
178 P_ParseANIMDEFSLump(w, animdefsLumpNum);
179 animdefsLumpNum = W_CheckNumForNamePwad("ANIMDEFS", (UINT16)w, animdefsLumpNum + 1);
180 }
181 }
182
183 // Define the last one
184 animdefs[maxanims].istexture = -1;
185 strncpy(animdefs[maxanims].endname, "", 9);
186 strncpy(animdefs[maxanims].startname, "", 9);
187 animdefs[maxanims].speed = 0;
188
189 if (anims)
190 free(anims);
191
192 anims = (anim_t *)malloc(sizeof (*anims)*(maxanims + 1));
193 if (!anims)
194 I_Error("Not enough free memory for ANIMDEFS data");
195
196 lastanim = anims;
197 for (i = 0; animdefs[i].istexture != -1; i++)
198 {
199 if (animdefs[i].istexture)
200 {
201 if (R_CheckTextureNumForName(animdefs[i].startname) == -1)
202 continue;
203
204 lastanim->picnum = R_TextureNumForName(animdefs[i].endname);
205 lastanim->basepic = R_TextureNumForName(animdefs[i].startname);
206 }
207 else
208 {
209 if ((W_CheckNumForName(animdefs[i].startname)) == LUMPERROR)
210 continue;
211
212 lastanim->picnum = R_GetFlatNumForName(animdefs[i].endname);
213 lastanim->basepic = R_GetFlatNumForName(animdefs[i].startname);
214 }
215
216 lastanim->istexture = animdefs[i].istexture;
217 lastanim->numpics = lastanim->picnum - lastanim->basepic + 1;
218
219 if (lastanim->numpics < 2)
220 {
221 free(anims);
222 I_Error("P_InitPicAnims: bad cycle from %s to %s",
223 animdefs[i].startname, animdefs[i].endname);
224 }
225
226 lastanim->speed = LONG(animdefs[i].speed);
227 lastanim++;
228 }
229 lastanim->istexture = -1;
230 R_ClearTextureNumCache(false);
231
232 // Clear animdefs now that we're done with it.
233 // We'll only be using anims from now on.
234 Z_Free(animdefs);
235 animdefs = NULL;
236 }
237
P_ParseANIMDEFSLump(INT32 wadNum,UINT16 lumpnum)238 void P_ParseANIMDEFSLump(INT32 wadNum, UINT16 lumpnum)
239 {
240 char *animdefsLump;
241 size_t animdefsLumpLength;
242 char *animdefsText;
243 char *animdefsToken;
244 char *p;
245
246 // Since lumps AREN'T \0-terminated like I'd assumed they should be, I'll
247 // need to make a space of memory where I can ensure that it will terminate
248 // correctly. Start by loading the relevant data from the WAD.
249 animdefsLump = (char *)W_CacheLumpNumPwad(wadNum,lumpnum,PU_STATIC);
250 // If that didn't exist, we have nothing to do here.
251 if (animdefsLump == NULL) return;
252 // If we're still here, then it DOES exist; figure out how long it is, and allot memory accordingly.
253 animdefsLumpLength = W_LumpLengthPwad(wadNum,lumpnum);
254 animdefsText = (char *)Z_Malloc((animdefsLumpLength+1)*sizeof(char),PU_STATIC,NULL);
255 // Now move the contents of the lump into this new location.
256 memmove(animdefsText,animdefsLump,animdefsLumpLength);
257 // Make damn well sure the last character in our new memory location is \0.
258 animdefsText[animdefsLumpLength] = '\0';
259 // Finally, free up the memory from the first data load, because we really
260 // don't need it.
261 Z_Free(animdefsLump);
262
263 // Now, let's start parsing this thing
264 p = animdefsText;
265 animdefsToken = M_GetToken(p);
266 while (animdefsToken != NULL)
267 {
268 if (stricmp(animdefsToken, "TEXTURE") == 0)
269 {
270 Z_Free(animdefsToken);
271 P_ParseAnimationDefintion(1);
272 }
273 else if (stricmp(animdefsToken, "FLAT") == 0)
274 {
275 Z_Free(animdefsToken);
276 P_ParseAnimationDefintion(0);
277 }
278 else if (stricmp(animdefsToken, "OSCILLATE") == 0)
279 {
280 // This probably came off the tail of an earlier definition. It's technically legal syntax, but we don't support it.
281 I_Error("Error parsing ANIMDEFS lump: Animation definitions utilizing \"OSCILLATE\" (the animation plays in reverse when it reaches the end) are not supported by SRB2");
282 }
283 else
284 {
285 I_Error("Error parsing ANIMDEFS lump: Expected \"TEXTURE\" or \"FLAT\", got \"%s\"",animdefsToken);
286 }
287 // parse next line
288 while (*p != '\0' && *p != '\n') ++p;
289 if (*p == '\n') ++p;
290 animdefsToken = M_GetToken(p);
291 }
292 Z_Free(animdefsToken);
293 Z_Free((void *)animdefsText);
294 }
295
P_ParseAnimationDefintion(SINT8 istexture)296 void P_ParseAnimationDefintion(SINT8 istexture)
297 {
298 char *animdefsToken;
299 size_t animdefsTokenLength;
300 char *endPos;
301 INT32 animSpeed;
302 size_t i;
303
304 // Startname
305 animdefsToken = M_GetToken(NULL);
306 if (animdefsToken == NULL)
307 {
308 I_Error("Error parsing ANIMDEFS lump: Unexpected end of file where start texture/flat name should be");
309 }
310 if (stricmp(animdefsToken, "OPTIONAL") == 0)
311 {
312 // This is meaningful to ZDoom - it tells the program NOT to bomb out
313 // if the textures can't be found - but it's useless in SRB2, so we'll
314 // just smile, nod, and carry on
315 Z_Free(animdefsToken);
316 animdefsToken = M_GetToken(NULL);
317
318 if (animdefsToken == NULL)
319 {
320 I_Error("Error parsing ANIMDEFS lump: Unexpected end of file where start texture/flat name should be");
321 }
322 else if (stricmp(animdefsToken, "RANGE") == 0)
323 {
324 // Oh. Um. Apparently "OPTIONAL" is a texture name. Naughty.
325 // I should probably handle this more gracefully, but right now
326 // I can't be bothered; especially since ZDoom doesn't handle this
327 // condition at all.
328 I_Error("Error parsing ANIMDEFS lump: \"OPTIONAL\" is a keyword; you cannot use it as the startname of an animation");
329 }
330 }
331 animdefsTokenLength = strlen(animdefsToken);
332 if (animdefsTokenLength>8)
333 {
334 I_Error("Error parsing ANIMDEFS lump: lump name \"%s\" exceeds 8 characters", animdefsToken);
335 }
336
337 // Search for existing animdef
338 for (i = 0; i < maxanims; i++)
339 if (animdefs[i].istexture == istexture // Check if it's the same type!
340 && stricmp(animdefsToken, animdefs[i].startname) == 0)
341 {
342 //CONS_Alert(CONS_NOTICE, "Duplicate animation: %s\n", animdefsToken);
343
344 // If we weren't parsing in reverse order, we would `break` here and parse the new data into the existing slot we found.
345 // Instead, we're just going to skip parsing the rest of this line entirely.
346 Z_Free(animdefsToken);
347 return;
348 }
349
350 // Not found
351 if (i == maxanims)
352 {
353 // Increase the size to make room for the new animation definition
354 GrowAnimDefs();
355 strncpy(animdefs[i].startname, animdefsToken, 9);
356 }
357
358 // animdefs[i].startname is now set to animdefsToken either way.
359 Z_Free(animdefsToken);
360
361 // set texture type
362 animdefs[i].istexture = istexture;
363
364 // "RANGE"
365 animdefsToken = M_GetToken(NULL);
366 if (animdefsToken == NULL)
367 {
368 I_Error("Error parsing ANIMDEFS lump: Unexpected end of file where \"RANGE\" after \"%s\"'s startname should be", animdefs[i].startname);
369 }
370 if (stricmp(animdefsToken, "ALLOWDECALS") == 0)
371 {
372 // Another ZDoom keyword, ho-hum. Skip it, move on to the next token.
373 Z_Free(animdefsToken);
374 animdefsToken = M_GetToken(NULL);
375 }
376 if (stricmp(animdefsToken, "PIC") == 0)
377 {
378 // This is technically legitimate ANIMDEFS syntax, but SRB2 doesn't support it.
379 I_Error("Error parsing ANIMDEFS lump: Animation definitions utilizing \"PIC\" (specific frames instead of a consecutive range) are not supported by SRB2");
380 }
381 if (stricmp(animdefsToken, "RANGE") != 0)
382 {
383 I_Error("Error parsing ANIMDEFS lump: Expected \"RANGE\" after \"%s\"'s startname, got \"%s\"", animdefs[i].startname, animdefsToken);
384 }
385 Z_Free(animdefsToken);
386
387 // Endname
388 animdefsToken = M_GetToken(NULL);
389 if (animdefsToken == NULL)
390 {
391 I_Error("Error parsing ANIMDEFS lump: Unexpected end of file where \"%s\"'s end texture/flat name should be", animdefs[i].startname);
392 }
393 animdefsTokenLength = strlen(animdefsToken);
394 if (animdefsTokenLength>8)
395 {
396 I_Error("Error parsing ANIMDEFS lump: lump name \"%s\" exceeds 8 characters", animdefsToken);
397 }
398 strncpy(animdefs[i].endname, animdefsToken, 9);
399 Z_Free(animdefsToken);
400
401 // "TICS"
402 animdefsToken = M_GetToken(NULL);
403 if (animdefsToken == NULL)
404 {
405 I_Error("Error parsing ANIMDEFS lump: Unexpected end of file where \"%s\"'s \"TICS\" should be", animdefs[i].startname);
406 }
407 if (stricmp(animdefsToken, "RAND") == 0)
408 {
409 // This is technically legitimate ANIMDEFS syntax, but SRB2 doesn't support it.
410 I_Error("Error parsing ANIMDEFS lump: Animation definitions utilizing \"RAND\" (random duration per frame) are not supported by SRB2");
411 }
412 if (stricmp(animdefsToken, "TICS") != 0)
413 {
414 I_Error("Error parsing ANIMDEFS lump: Expected \"TICS\" in animation definition for \"%s\", got \"%s\"", animdefs[i].startname, animdefsToken);
415 }
416 Z_Free(animdefsToken);
417
418 // Speed
419 animdefsToken = M_GetToken(NULL);
420 if (animdefsToken == NULL)
421 {
422 I_Error("Error parsing ANIMDEFS lump: Unexpected end of file where \"%s\"'s animation speed should be", animdefs[i].startname);
423 }
424 endPos = NULL;
425 #ifndef AVOID_ERRNO
426 errno = 0;
427 #endif
428 animSpeed = strtol(animdefsToken,&endPos,10);
429 if (endPos == animdefsToken // Empty string
430 || *endPos != '\0' // Not end of string
431 #ifndef AVOID_ERRNO
432 || errno == ERANGE // Number out-of-range
433 #endif
434 || animSpeed < 0) // Number is not positive
435 {
436 I_Error("Error parsing ANIMDEFS lump: Expected a positive integer for \"%s\"'s animation speed, got \"%s\"", animdefs[i].startname, animdefsToken);
437 }
438 animdefs[i].speed = animSpeed;
439 Z_Free(animdefsToken);
440
441 #ifdef WALLFLATS
442 // hehe... uhh.....
443 if (!istexture)
444 {
445 GrowAnimDefs();
446 M_Memcpy(&animdefs[maxanims-1], &animdefs[i], sizeof(animdef_t));
447 animdefs[maxanims-1].istexture = 1;
448 }
449 #endif
450 }
451
452 /** Checks for flats in levelflats that are part of a flat animation sequence
453 * and sets them up for animation.
454 *
455 * \param animnum Index into ::anims to find flats for.
456 * \sa P_SetupLevelFlatAnims
457 */
P_FindAnimatedFlat(INT32 animnum)458 static inline void P_FindAnimatedFlat(INT32 animnum)
459 {
460 size_t i;
461 lumpnum_t startflatnum, endflatnum;
462 levelflat_t *foundflats;
463
464 foundflats = levelflats;
465 startflatnum = anims[animnum].basepic;
466 endflatnum = anims[animnum].picnum;
467
468 // note: high word of lumpnum is the wad number
469 if ((startflatnum>>16) != (endflatnum>>16))
470 I_Error("AnimatedFlat start %s not in same wad as end %s\n",
471 animdefs[animnum].startname, animdefs[animnum].endname);
472
473 //
474 // now search through the levelflats if this anim flat sequence is used
475 //
476 for (i = 0; i < numlevelflats; i++, foundflats++)
477 {
478 // is that levelflat from the flat anim sequence ?
479 if ((anims[animnum].istexture) && (foundflats->type == LEVELFLAT_TEXTURE)
480 && ((UINT16)foundflats->u.texture.num >= startflatnum && (UINT16)foundflats->u.texture.num <= endflatnum))
481 {
482 foundflats->u.texture.basenum = startflatnum;
483 foundflats->animseq = foundflats->u.texture.num - startflatnum;
484 foundflats->numpics = endflatnum - startflatnum + 1;
485 foundflats->speed = anims[animnum].speed;
486
487 CONS_Debug(DBG_SETUP, "animflat: #%03d name:%.8s animseq:%d numpics:%d speed:%d\n",
488 atoi(sizeu1(i)), foundflats->name, foundflats->animseq,
489 foundflats->numpics,foundflats->speed);
490 }
491 else if ((!anims[animnum].istexture) && (foundflats->type == LEVELFLAT_FLAT)
492 && (foundflats->u.flat.lumpnum >= startflatnum && foundflats->u.flat.lumpnum <= endflatnum))
493 {
494 foundflats->u.flat.baselumpnum = startflatnum;
495 foundflats->animseq = foundflats->u.flat.lumpnum - startflatnum;
496 foundflats->numpics = endflatnum - startflatnum + 1;
497 foundflats->speed = anims[animnum].speed;
498
499 CONS_Debug(DBG_SETUP, "animflat: #%03d name:%.8s animseq:%d numpics:%d speed:%d\n",
500 atoi(sizeu1(i)), foundflats->name, foundflats->animseq,
501 foundflats->numpics,foundflats->speed);
502 }
503 }
504 }
505
506 /** Sets up all flats used in a level.
507 *
508 * \sa P_InitPicAnims, P_FindAnimatedFlat
509 */
P_SetupLevelFlatAnims(void)510 void P_SetupLevelFlatAnims(void)
511 {
512 INT32 i;
513
514 // the original game flat anim sequences
515 for (i = 0; anims[i].istexture != -1; i++)
516 P_FindAnimatedFlat(i);
517 }
518
519 //
520 // UTILITIES
521 //
522
523 #if 0
524 /** Gets a side from a sector line.
525 *
526 * \param currentSector Sector the line is in.
527 * \param line Index of the line within the sector.
528 * \param side 0 for front, 1 for back.
529 * \return Pointer to the side_t of the side you want.
530 * \sa getSector, twoSided, getNextSector
531 */
532 static inline side_t *getSide(INT32 currentSector, INT32 line, INT32 side)
533 {
534 return &sides[(sectors[currentSector].lines[line])->sidenum[side]];
535 }
536
537 /** Gets a sector from a sector line.
538 *
539 * \param currentSector Sector the line is in.
540 * \param line Index of the line within the sector.
541 * \param side 0 for front, 1 for back.
542 * \return Pointer to the ::sector_t of the sector on that side.
543 * \sa getSide, twoSided, getNextSector
544 */
545 static inline sector_t *getSector(INT32 currentSector, INT32 line, INT32 side)
546 {
547 return sides[(sectors[currentSector].lines[line])->sidenum[side]].sector;
548 }
549
550 /** Determines whether a sector line is two-sided.
551 * Uses the Boom method, checking if the line's back side is set to -1, rather
552 * than looking for ::ML_TWOSIDED.
553 *
554 * \param sector The sector.
555 * \param line Line index within the sector.
556 * \return 1 if the sector is two-sided, 0 otherwise.
557 * \sa getSide, getSector, getNextSector
558 */
559 static inline boolean twoSided(INT32 sector, INT32 line)
560 {
561 return (sectors[sector].lines[line])->sidenum[1] != 0xffff;
562 }
563 #endif
564
565 /** Finds sector next to current.
566 *
567 * \param line Pointer to the line to cross.
568 * \param sec Pointer to the current sector.
569 * \return Pointer to a ::sector_t of the adjacent sector, or NULL if the line
570 * is one-sided.
571 * \sa getSide, getSector, twoSided
572 * \author Steven McGranahan
573 */
getNextSector(line_t * line,sector_t * sec)574 static sector_t *getNextSector(line_t *line, sector_t *sec)
575 {
576 if (line->frontsector == sec)
577 {
578 if (line->backsector != sec)
579 return line->backsector;
580 else
581 return NULL;
582 }
583 return line->frontsector;
584 }
585
586 /** Finds lowest floor in adjacent sectors.
587 *
588 * \param sec Sector to start in.
589 * \return Lowest floor height in an adjacent sector.
590 * \sa P_FindHighestFloorSurrounding, P_FindNextLowestFloor,
591 * P_FindLowestCeilingSurrounding
592 */
P_FindLowestFloorSurrounding(sector_t * sec)593 fixed_t P_FindLowestFloorSurrounding(sector_t *sec)
594 {
595 size_t i;
596 line_t *check;
597 sector_t *other;
598 fixed_t floorh;
599
600 floorh = sec->floorheight;
601
602 for (i = 0; i < sec->linecount; i++)
603 {
604 check = sec->lines[i];
605 other = getNextSector(check,sec);
606
607 if (!other)
608 continue;
609
610 if (other->floorheight < floorh)
611 floorh = other->floorheight;
612 }
613 return floorh;
614 }
615
616 /** Finds highest floor in adjacent sectors.
617 *
618 * \param sec Sector to start in.
619 * \return Highest floor height in an adjacent sector.
620 * \sa P_FindLowestFloorSurrounding, P_FindNextHighestFloor,
621 * P_FindHighestCeilingSurrounding
622 */
P_FindHighestFloorSurrounding(sector_t * sec)623 fixed_t P_FindHighestFloorSurrounding(sector_t *sec)
624 {
625 size_t i;
626 line_t *check;
627 sector_t *other;
628 fixed_t floorh = -500*FRACUNIT;
629 INT32 foundsector = 0;
630
631 for (i = 0; i < sec->linecount; i++)
632 {
633 check = sec->lines[i];
634 other = getNextSector(check, sec);
635
636 if (!other)
637 continue;
638
639 if (other->floorheight > floorh || !foundsector)
640 floorh = other->floorheight;
641
642 if (!foundsector)
643 foundsector = 1;
644 }
645 return floorh;
646 }
647
648 /** Finds next highest floor in adjacent sectors.
649 *
650 * \param sec Sector to start in.
651 * \param currentheight Height to start at.
652 * \return Next highest floor height in an adjacent sector, or currentheight
653 * if there are none higher.
654 * \sa P_FindHighestFloorSurrounding, P_FindNextLowestFloor,
655 * P_FindNextHighestCeiling
656 * \author Lee Killough
657 */
P_FindNextHighestFloor(sector_t * sec,fixed_t currentheight)658 fixed_t P_FindNextHighestFloor(sector_t *sec, fixed_t currentheight)
659 {
660 sector_t *other;
661 size_t i;
662 fixed_t height;
663
664 for (i = 0; i < sec->linecount; i++)
665 {
666 other = getNextSector(sec->lines[i],sec);
667 if (other && other->floorheight > currentheight)
668 {
669 height = other->floorheight;
670 while (++i < sec->linecount)
671 {
672 other = getNextSector(sec->lines[i], sec);
673 if (other &&
674 other->floorheight < height &&
675 other->floorheight > currentheight)
676 height = other->floorheight;
677 }
678 return height;
679 }
680 }
681 return currentheight;
682 }
683
684 ////////////////////////////////////////////////////
685 // SoM: Start new Boom functions
686 ////////////////////////////////////////////////////
687
688 /** Finds next lowest floor in adjacent sectors.
689 *
690 * \param sec Sector to start in.
691 * \param currentheight Height to start at.
692 * \return Next lowest floor height in an adjacent sector, or currentheight
693 * if there are none lower.
694 * \sa P_FindLowestFloorSurrounding, P_FindNextHighestFloor,
695 * P_FindNextLowestCeiling
696 * \author Lee Killough
697 */
P_FindNextLowestFloor(sector_t * sec,fixed_t currentheight)698 fixed_t P_FindNextLowestFloor(sector_t *sec, fixed_t currentheight)
699 {
700 sector_t *other;
701 size_t i;
702 fixed_t height;
703
704 for (i = 0; i < sec->linecount; i++)
705 {
706 other = getNextSector(sec->lines[i], sec);
707 if (other && other->floorheight < currentheight)
708 {
709 height = other->floorheight;
710 while (++i < sec->linecount)
711 {
712 other = getNextSector(sec->lines[i], sec);
713 if (other && other->floorheight > height
714 && other->floorheight < currentheight)
715 height = other->floorheight;
716 }
717 return height;
718 }
719 }
720 return currentheight;
721 }
722
723 #if 0
724 /** Finds next lowest ceiling in adjacent sectors.
725 *
726 * \param sec Sector to start in.
727 * \param currentheight Height to start at.
728 * \return Next lowest ceiling height in an adjacent sector, or currentheight
729 * if there are none lower.
730 * \sa P_FindLowestCeilingSurrounding, P_FindNextHighestCeiling,
731 * P_FindNextLowestFloor
732 * \author Lee Killough
733 */
734 static fixed_t P_FindNextLowestCeiling(sector_t *sec, fixed_t currentheight)
735 {
736 sector_t *other;
737 size_t i;
738 fixed_t height;
739
740 for (i = 0; i < sec->linecount; i++)
741 {
742 other = getNextSector(sec->lines[i],sec);
743 if (other && other->ceilingheight < currentheight)
744 {
745 height = other->ceilingheight;
746 while (++i < sec->linecount)
747 {
748 other = getNextSector(sec->lines[i],sec);
749 if (other && other->ceilingheight > height
750 && other->ceilingheight < currentheight)
751 height = other->ceilingheight;
752 }
753 return height;
754 }
755 }
756 return currentheight;
757 }
758
759 /** Finds next highest ceiling in adjacent sectors.
760 *
761 * \param sec Sector to start in.
762 * \param currentheight Height to start at.
763 * \return Next highest ceiling height in an adjacent sector, or currentheight
764 * if there are none higher.
765 * \sa P_FindHighestCeilingSurrounding, P_FindNextLowestCeiling,
766 * P_FindNextHighestFloor
767 * \author Lee Killough
768 */
769 static fixed_t P_FindNextHighestCeiling(sector_t *sec, fixed_t currentheight)
770 {
771 sector_t *other;
772 size_t i;
773 fixed_t height;
774
775 for (i = 0; i < sec->linecount; i++)
776 {
777 other = getNextSector(sec->lines[i], sec);
778 if (other && other->ceilingheight > currentheight)
779 {
780 height = other->ceilingheight;
781 while (++i < sec->linecount)
782 {
783 other = getNextSector(sec->lines[i],sec);
784 if (other && other->ceilingheight < height
785 && other->ceilingheight > currentheight)
786 height = other->ceilingheight;
787 }
788 return height;
789 }
790 }
791 return currentheight;
792 }
793 #endif
794
795 ////////////////////////////
796 // End New Boom functions
797 ////////////////////////////
798
799 /** Finds lowest ceiling in adjacent sectors.
800 *
801 * \param sec Sector to start in.
802 * \return Lowest ceiling height in an adjacent sector.
803 * \sa P_FindHighestCeilingSurrounding, P_FindNextLowestCeiling,
804 * P_FindLowestFloorSurrounding
805 */
P_FindLowestCeilingSurrounding(sector_t * sec)806 fixed_t P_FindLowestCeilingSurrounding(sector_t *sec)
807 {
808 size_t i;
809 line_t *check;
810 sector_t *other;
811 fixed_t height = 32000*FRACUNIT; //SoM: 3/7/2000: Remove ovf
812 INT32 foundsector = 0;
813
814 for (i = 0; i < sec->linecount; i++)
815 {
816 check = sec->lines[i];
817 other = getNextSector(check, sec);
818
819 if (!other)
820 continue;
821
822 if (other->ceilingheight < height || !foundsector)
823 height = other->ceilingheight;
824
825 if (!foundsector)
826 foundsector = 1;
827 }
828 return height;
829 }
830
831 /** Finds Highest ceiling in adjacent sectors.
832 *
833 * \param sec Sector to start in.
834 * \return Highest ceiling height in an adjacent sector.
835 * \sa P_FindLowestCeilingSurrounding, P_FindNextHighestCeiling,
836 * P_FindHighestFloorSurrounding
837 */
P_FindHighestCeilingSurrounding(sector_t * sec)838 fixed_t P_FindHighestCeilingSurrounding(sector_t *sec)
839 {
840 size_t i;
841 line_t *check;
842 sector_t *other;
843 fixed_t height = 0;
844 INT32 foundsector = 0;
845
846 for (i = 0; i < sec->linecount; i++)
847 {
848 check = sec->lines[i];
849 other = getNextSector(check, sec);
850
851 if (!other)
852 continue;
853
854 if (other->ceilingheight > height || !foundsector)
855 height = other->ceilingheight;
856
857 if (!foundsector)
858 foundsector = 1;
859 }
860 return height;
861 }
862
863 #if 0
864 //SoM: 3/7/2000: UTILS.....
865 //
866 // P_FindShortestTextureAround()
867 //
868 // Passed a sector number, returns the shortest lower texture on a
869 // linedef bounding the sector.
870 //
871 //
872 static fixed_t P_FindShortestTextureAround(INT32 secnum)
873 {
874 fixed_t minsize = 32000<<FRACBITS;
875 side_t *side;
876 size_t i;
877 sector_t *sec= §ors[secnum];
878
879 for (i = 0; i < sec->linecount; i++)
880 {
881 if (twoSided(secnum, i))
882 {
883 side = getSide(secnum,i,0);
884 if (side->bottomtexture > 0)
885 if (textureheight[side->bottomtexture] < minsize)
886 minsize = textureheight[side->bottomtexture];
887 side = getSide(secnum,i,1);
888 if (side->bottomtexture > 0)
889 if (textureheight[side->bottomtexture] < minsize)
890 minsize = textureheight[side->bottomtexture];
891 }
892 }
893 return minsize;
894 }
895
896 //SoM: 3/7/2000: Stuff.... (can you tell I'm getting tired? It's 12 : 30!)
897 //
898 // P_FindShortestUpperAround()
899 //
900 // Passed a sector number, returns the shortest upper texture on a
901 // linedef bounding the sector.
902 //
903 //
904 static fixed_t P_FindShortestUpperAround(INT32 secnum)
905 {
906 fixed_t minsize = 32000<<FRACBITS;
907 side_t *side;
908 size_t i;
909 sector_t *sec = §ors[secnum];
910
911 for (i = 0; i < sec->linecount; i++)
912 {
913 if (twoSided(secnum, i))
914 {
915 side = getSide(secnum,i,0);
916 if (side->toptexture > 0)
917 if (textureheight[side->toptexture] < minsize)
918 minsize = textureheight[side->toptexture];
919 side = getSide(secnum,i,1);
920 if (side->toptexture > 0)
921 if (textureheight[side->toptexture] < minsize)
922 minsize = textureheight[side->toptexture];
923 }
924 }
925 return minsize;
926 }
927
928 //SoM: 3/7/2000
929 //
930 // P_FindModelFloorSector()
931 //
932 // Passed a floor height and a sector number, return a pointer to a
933 // a sector with that floor height across the lowest numbered two sided
934 // line surrounding the sector.
935 //
936 // Note: If no sector at that height bounds the sector passed, return NULL
937 //
938 //
939 static sector_t *P_FindModelFloorSector(fixed_t floordestheight, INT32 secnum)
940 {
941 size_t i;
942 sector_t *sec = §ors[secnum];
943
944 for (i = 0; i < sec->linecount; i++)
945 {
946 if (twoSided(secnum, i))
947 {
948 if (getSide(secnum,i,0)->sector-sectors == secnum)
949 sec = getSector(secnum,i,1);
950 else
951 sec = getSector(secnum,i,0);
952
953 if (sec->floorheight == floordestheight)
954 return sec;
955 }
956 }
957 return NULL;
958 }
959
960 //
961 // P_FindModelCeilingSector()
962 //
963 // Passed a ceiling height and a sector number, return a pointer to a
964 // a sector with that ceiling height across the lowest numbered two sided
965 // line surrounding the sector.
966 //
967 // Note: If no sector at that height bounds the sector passed, return NULL
968 //
969 static sector_t *P_FindModelCeilingSector(fixed_t ceildestheight, INT32 secnum)
970 {
971 size_t i;
972 sector_t *sec = §ors[secnum];
973
974 for (i = 0; i < sec->linecount; i++)
975 {
976 if (twoSided(secnum, i))
977 {
978 if (getSide(secnum, i, 0)->sector - sectors == secnum)
979 sec = getSector(secnum, i, 1);
980 else
981 sec = getSector(secnum, i, 0);
982
983 if (sec->ceilingheight == ceildestheight)
984 return sec;
985 }
986 }
987 return NULL;
988 }
989 #endif
990
991 // Parses arguments for parameterized polyobject door types
PolyDoor(line_t * line)992 static boolean PolyDoor(line_t *line)
993 {
994 polydoordata_t pdd;
995
996 pdd.polyObjNum = Tag_FGet(&line->tags); // polyobject id
997
998 switch(line->special)
999 {
1000 case 480: // Polyobj_DoorSlide
1001 pdd.doorType = POLY_DOOR_SLIDE;
1002 pdd.speed = sides[line->sidenum[0]].textureoffset / 8;
1003 pdd.angle = R_PointToAngle2(line->v1->x, line->v1->y, line->v2->x, line->v2->y); // angle of motion
1004 pdd.distance = sides[line->sidenum[0]].rowoffset;
1005
1006 if (line->sidenum[1] != 0xffff)
1007 pdd.delay = sides[line->sidenum[1]].textureoffset >> FRACBITS; // delay in tics
1008 else
1009 pdd.delay = 0;
1010 break;
1011 case 481: // Polyobj_DoorSwing
1012 pdd.doorType = POLY_DOOR_SWING;
1013 pdd.speed = sides[line->sidenum[0]].textureoffset >> FRACBITS; // angular speed
1014 pdd.distance = sides[line->sidenum[0]].rowoffset >> FRACBITS; // angular distance
1015
1016 if (line->sidenum[1] != 0xffff)
1017 pdd.delay = sides[line->sidenum[1]].textureoffset >> FRACBITS; // delay in tics
1018 else
1019 pdd.delay = 0;
1020 break;
1021 default:
1022 return 0; // ???
1023 }
1024
1025 return EV_DoPolyDoor(&pdd);
1026 }
1027
1028 // Parses arguments for parameterized polyobject move specials
PolyMove(line_t * line)1029 static boolean PolyMove(line_t *line)
1030 {
1031 polymovedata_t pmd;
1032
1033 pmd.polyObjNum = Tag_FGet(&line->tags);
1034 pmd.speed = sides[line->sidenum[0]].textureoffset / 8;
1035 pmd.angle = R_PointToAngle2(line->v1->x, line->v1->y, line->v2->x, line->v2->y);
1036 pmd.distance = sides[line->sidenum[0]].rowoffset;
1037
1038 pmd.overRide = (line->special == 483); // Polyobj_OR_Move
1039
1040 return EV_DoPolyObjMove(&pmd);
1041 }
1042
1043 // Makes a polyobject invisible and intangible
1044 // If NOCLIMB is ticked, the polyobject will still be tangible, just not visible.
PolyInvisible(line_t * line)1045 static void PolyInvisible(line_t *line)
1046 {
1047 INT32 polyObjNum = Tag_FGet(&line->tags);
1048 polyobj_t *po;
1049
1050 if (!(po = Polyobj_GetForNum(polyObjNum)))
1051 {
1052 CONS_Debug(DBG_POLYOBJ, "PolyInvisible: bad polyobj %d\n", polyObjNum);
1053 return;
1054 }
1055
1056 // don't allow line actions to affect bad polyobjects
1057 if (po->isBad)
1058 return;
1059
1060 if (!(line->flags & ML_NOCLIMB))
1061 po->flags &= ~POF_SOLID;
1062
1063 po->flags |= POF_NOSPECIALS;
1064 po->flags &= ~POF_RENDERALL;
1065 }
1066
1067 // Makes a polyobject visible and tangible
1068 // If NOCLIMB is ticked, the polyobject will not be tangible, just visible.
PolyVisible(line_t * line)1069 static void PolyVisible(line_t *line)
1070 {
1071 INT32 polyObjNum = Tag_FGet(&line->tags);
1072 polyobj_t *po;
1073
1074 if (!(po = Polyobj_GetForNum(polyObjNum)))
1075 {
1076 CONS_Debug(DBG_POLYOBJ, "PolyVisible: bad polyobj %d\n", polyObjNum);
1077 return;
1078 }
1079
1080 // don't allow line actions to affect bad polyobjects
1081 if (po->isBad)
1082 return;
1083
1084 if (!(line->flags & ML_NOCLIMB))
1085 po->flags |= POF_SOLID;
1086
1087 po->flags &= ~POF_NOSPECIALS;
1088 po->flags |= (po->spawnflags & POF_RENDERALL);
1089 }
1090
1091
1092 // Sets the translucency of a polyobject
1093 // Frontsector floor / 100 = translevel
PolyTranslucency(line_t * line)1094 static void PolyTranslucency(line_t *line)
1095 {
1096 INT32 polyObjNum = Tag_FGet(&line->tags);
1097 polyobj_t *po;
1098 INT32 value;
1099
1100 if (!(po = Polyobj_GetForNum(polyObjNum)))
1101 {
1102 CONS_Debug(DBG_POLYOBJ, "EV_DoPolyObjWaypoint: bad polyobj %d\n", polyObjNum);
1103 return;
1104 }
1105
1106 // don't allow line actions to affect bad polyobjects
1107 if (po->isBad)
1108 return;
1109
1110 // If Front X Offset is specified, use that. Else, use floorheight.
1111 value = (sides[line->sidenum[0]].textureoffset ? sides[line->sidenum[0]].textureoffset : line->frontsector->floorheight) >> FRACBITS;
1112
1113 // If DONTPEGBOTTOM, specify raw translucency value. Else, take it out of 1000.
1114 if (!(line->flags & ML_DONTPEGBOTTOM))
1115 value /= 100;
1116
1117 if (line->flags & ML_EFFECT3) // relative calc
1118 po->translucency += value;
1119 else
1120 po->translucency = value;
1121
1122 po->translucency = max(min(po->translucency, NUMTRANSMAPS), 0);
1123 }
1124
1125 // Makes a polyobject translucency fade and applies tangibility
PolyFade(line_t * line)1126 static boolean PolyFade(line_t *line)
1127 {
1128 INT32 polyObjNum = Tag_FGet(&line->tags);
1129 polyobj_t *po;
1130 polyfadedata_t pfd;
1131 INT32 value;
1132
1133 if (!(po = Polyobj_GetForNum(polyObjNum)))
1134 {
1135 CONS_Debug(DBG_POLYOBJ, "PolyFade: bad polyobj %d\n", polyObjNum);
1136 return 0;
1137 }
1138
1139 // don't allow line actions to affect bad polyobjects
1140 if (po->isBad)
1141 return 0;
1142
1143 // Prevent continuous execs from interfering on an existing fade
1144 if (!(line->flags & ML_EFFECT5)
1145 && po->thinker
1146 && po->thinker->function.acp1 == (actionf_p1)T_PolyObjFade)
1147 {
1148 CONS_Debug(DBG_POLYOBJ, "Line type 492 Executor: Fade PolyObject thinker already exists\n");
1149 return 0;
1150 }
1151
1152 pfd.polyObjNum = polyObjNum;
1153
1154 // If Front X Offset is specified, use that. Else, use floorheight.
1155 value = (sides[line->sidenum[0]].textureoffset ? sides[line->sidenum[0]].textureoffset : line->frontsector->floorheight) >> FRACBITS;
1156
1157 // If DONTPEGBOTTOM, specify raw translucency value. Else, take it out of 1000.
1158 if (!(line->flags & ML_DONTPEGBOTTOM))
1159 value /= 100;
1160
1161 if (line->flags & ML_EFFECT3) // relative calc
1162 pfd.destvalue = po->translucency + value;
1163 else
1164 pfd.destvalue = value;
1165
1166 pfd.destvalue = max(min(pfd.destvalue, NUMTRANSMAPS), 0);
1167
1168 // already equal, nothing to do
1169 if (po->translucency == pfd.destvalue)
1170 return 1;
1171
1172 pfd.docollision = !(line->flags & ML_BOUNCY); // do not handle collision flags
1173 pfd.doghostfade = (line->flags & ML_EFFECT1); // do ghost fade (no collision flags during fade)
1174 pfd.ticbased = (line->flags & ML_EFFECT4); // Speed = Tic Duration
1175
1176 // allow Back Y Offset to be consistent with other fade specials
1177 pfd.speed = (line->sidenum[1] != 0xFFFF && !sides[line->sidenum[0]].rowoffset) ?
1178 abs(sides[line->sidenum[1]].rowoffset>>FRACBITS)
1179 : abs(sides[line->sidenum[0]].rowoffset>>FRACBITS);
1180
1181
1182 return EV_DoPolyObjFade(&pfd);
1183 }
1184
1185 // Parses arguments for parameterized polyobject waypoint movement
PolyWaypoint(line_t * line)1186 static boolean PolyWaypoint(line_t *line)
1187 {
1188 polywaypointdata_t pwd;
1189
1190 pwd.polyObjNum = Tag_FGet(&line->tags);
1191 pwd.speed = sides[line->sidenum[0]].textureoffset / 8;
1192 pwd.sequence = sides[line->sidenum[0]].rowoffset >> FRACBITS; // Sequence #
1193
1194 // Behavior after reaching the last waypoint?
1195 if (line->flags & ML_EFFECT3)
1196 pwd.returnbehavior = PWR_WRAP; // Wrap back to first waypoint
1197 else if (line->flags & ML_EFFECT2)
1198 pwd.returnbehavior = PWR_COMEBACK; // Go through sequence in reverse
1199 else
1200 pwd.returnbehavior = PWR_STOP; // Stop
1201
1202 // Flags
1203 pwd.flags = 0;
1204 if (line->flags & ML_EFFECT1)
1205 pwd.flags |= PWF_REVERSE;
1206 if (line->flags & ML_EFFECT4)
1207 pwd.flags |= PWF_LOOP;
1208
1209 return EV_DoPolyObjWaypoint(&pwd);
1210 }
1211
1212 // Parses arguments for parameterized polyobject rotate specials
PolyRotate(line_t * line)1213 static boolean PolyRotate(line_t *line)
1214 {
1215 polyrotdata_t prd;
1216
1217 prd.polyObjNum = Tag_FGet(&line->tags);
1218 prd.speed = sides[line->sidenum[0]].textureoffset >> FRACBITS; // angular speed
1219 prd.distance = sides[line->sidenum[0]].rowoffset >> FRACBITS; // angular distance
1220
1221 // Polyobj_(OR_)RotateRight have dir == -1
1222 prd.direction = (line->special == 484 || line->special == 485) ? -1 : 1;
1223
1224 // Polyobj_OR types have override set to true
1225 prd.overRide = (line->special == 485 || line->special == 487);
1226
1227 if (line->flags & ML_NOCLIMB)
1228 prd.turnobjs = 0;
1229 else if (line->flags & ML_EFFECT4)
1230 prd.turnobjs = 2;
1231 else
1232 prd.turnobjs = 1;
1233
1234 return EV_DoPolyObjRotate(&prd);
1235 }
1236
1237 // Parses arguments for polyobject flag waving special
PolyFlag(line_t * line)1238 static boolean PolyFlag(line_t *line)
1239 {
1240 polyflagdata_t pfd;
1241
1242 pfd.polyObjNum = Tag_FGet(&line->tags);
1243 pfd.speed = P_AproxDistance(line->dx, line->dy) >> FRACBITS;
1244 pfd.angle = R_PointToAngle2(line->v1->x, line->v1->y, line->v2->x, line->v2->y) >> ANGLETOFINESHIFT;
1245 pfd.momx = sides[line->sidenum[0]].textureoffset >> FRACBITS;
1246
1247 return EV_DoPolyObjFlag(&pfd);
1248 }
1249
1250 // Parses arguments for parameterized polyobject move-by-sector-heights specials
PolyDisplace(line_t * line)1251 static boolean PolyDisplace(line_t *line)
1252 {
1253 polydisplacedata_t pdd;
1254
1255 pdd.polyObjNum = Tag_FGet(&line->tags);
1256
1257 pdd.controlSector = line->frontsector;
1258 pdd.dx = line->dx>>8;
1259 pdd.dy = line->dy>>8;
1260
1261 return EV_DoPolyObjDisplace(&pdd);
1262 }
1263
1264
1265 // Parses arguments for parameterized polyobject rotate-by-sector-heights specials
PolyRotDisplace(line_t * line)1266 static boolean PolyRotDisplace(line_t *line)
1267 {
1268 polyrotdisplacedata_t pdd;
1269 fixed_t anginter, distinter;
1270
1271 pdd.polyObjNum = Tag_FGet(&line->tags);
1272 pdd.controlSector = line->frontsector;
1273
1274 // Rotate 'anginter' interval for each 'distinter' interval from the control sector.
1275 // Use default values if not provided as fallback.
1276 anginter = sides[line->sidenum[0]].rowoffset ? sides[line->sidenum[0]].rowoffset : 90*FRACUNIT;
1277 distinter = sides[line->sidenum[0]].textureoffset ? sides[line->sidenum[0]].textureoffset : 128*FRACUNIT;
1278 pdd.rotscale = FixedDiv(anginter, distinter);
1279
1280 // Same behavior as other rotators when carrying things.
1281 if (line->flags & ML_NOCLIMB)
1282 pdd.turnobjs = 0;
1283 else if (line->flags & ML_EFFECT4)
1284 pdd.turnobjs = 2;
1285 else
1286 pdd.turnobjs = 1;
1287
1288 return EV_DoPolyObjRotDisplace(&pdd);
1289 }
1290
1291
1292 //
1293 // P_RunNightserizeExecutors
1294 //
P_RunNightserizeExecutors(mobj_t * actor)1295 void P_RunNightserizeExecutors(mobj_t *actor)
1296 {
1297 size_t i;
1298
1299 for (i = 0; i < numlines; i++)
1300 {
1301 if (lines[i].special == 323 || lines[i].special == 324)
1302 P_RunTriggerLinedef(&lines[i], actor, NULL);
1303 }
1304 }
1305
1306 //
1307 // P_RunDeNightserizeExecutors
1308 //
P_RunDeNightserizeExecutors(mobj_t * actor)1309 void P_RunDeNightserizeExecutors(mobj_t *actor)
1310 {
1311 size_t i;
1312
1313 for (i = 0; i < numlines; i++)
1314 {
1315 if (lines[i].special == 325 || lines[i].special == 326)
1316 P_RunTriggerLinedef(&lines[i], actor, NULL);
1317 }
1318 }
1319
1320 //
1321 // P_RunNightsLapExecutors
1322 //
P_RunNightsLapExecutors(mobj_t * actor)1323 void P_RunNightsLapExecutors(mobj_t *actor)
1324 {
1325 size_t i;
1326
1327 for (i = 0; i < numlines; i++)
1328 {
1329 if (lines[i].special == 327 || lines[i].special == 328)
1330 P_RunTriggerLinedef(&lines[i], actor, NULL);
1331 }
1332 }
1333
1334 //
1335 // P_RunNightsCapsuleTouchExecutors
1336 //
P_RunNightsCapsuleTouchExecutors(mobj_t * actor,boolean entering,boolean enoughspheres)1337 void P_RunNightsCapsuleTouchExecutors(mobj_t *actor, boolean entering, boolean enoughspheres)
1338 {
1339 size_t i;
1340
1341 for (i = 0; i < numlines; i++)
1342 {
1343 if ((lines[i].special == 329 || lines[i].special == 330)
1344 && ((entering && (lines[i].flags & ML_TFERLINE))
1345 || (!entering && !(lines[i].flags & ML_TFERLINE)))
1346 && ((lines[i].flags & ML_DONTPEGTOP)
1347 || (enoughspheres && !(lines[i].flags & ML_BOUNCY))
1348 || (!enoughspheres && (lines[i].flags & ML_BOUNCY))))
1349 P_RunTriggerLinedef(&lines[i], actor, NULL);
1350 }
1351 }
1352
1353 /** Finds minimum light from an adjacent sector.
1354 *
1355 * \param sector Sector to start in.
1356 * \param max Maximum value to return.
1357 * \return Minimum light value from an adjacent sector, or max if the minimum
1358 * light value is greater than max.
1359 */
P_FindMinSurroundingLight(sector_t * sector,INT32 max)1360 INT32 P_FindMinSurroundingLight(sector_t *sector, INT32 max)
1361 {
1362 size_t i;
1363 INT32 min = max;
1364 line_t *line;
1365 sector_t *check;
1366
1367 for (i = 0; i < sector->linecount; i++)
1368 {
1369 line = sector->lines[i];
1370 check = getNextSector(line,sector);
1371
1372 if (!check)
1373 continue;
1374
1375 if (check->lightlevel < min)
1376 min = check->lightlevel;
1377 }
1378 return min;
1379 }
1380
T_ExecutorDelay(executor_t * e)1381 void T_ExecutorDelay(executor_t *e)
1382 {
1383 if (--e->timer <= 0)
1384 {
1385 if (e->caller && P_MobjWasRemoved(e->caller)) // If the mobj died while we were delaying
1386 P_SetTarget(&e->caller, NULL); // Call with no mobj!
1387 P_ProcessLineSpecial(e->line, e->caller, e->sector);
1388 P_SetTarget(&e->caller, NULL); // Let the mobj know it can be removed now.
1389 P_RemoveThinker(&e->thinker);
1390 }
1391 }
1392
P_AddExecutorDelay(line_t * line,mobj_t * mobj,sector_t * sector)1393 static void P_AddExecutorDelay(line_t *line, mobj_t *mobj, sector_t *sector)
1394 {
1395 executor_t *e;
1396 INT32 delay;
1397
1398 if (udmf)
1399 delay = line->executordelay;
1400 else
1401 {
1402 if (!line->backsector)
1403 I_Error("P_AddExecutorDelay: Line has no backsector!\n");
1404
1405 delay = (line->backsector->ceilingheight >> FRACBITS) + (line->backsector->floorheight >> FRACBITS);
1406 }
1407
1408 e = Z_Calloc(sizeof (*e), PU_LEVSPEC, NULL);
1409
1410 e->thinker.function.acp1 = (actionf_p1)T_ExecutorDelay;
1411 e->line = line;
1412 e->sector = sector;
1413 e->timer = delay;
1414 P_SetTarget(&e->caller, mobj); // Use P_SetTarget to make sure the mobj doesn't get freed while we're delaying.
1415 P_AddThinker(THINK_MAIN, &e->thinker);
1416 }
1417
1418 /** Used by P_RunTriggerLinedef to check a NiGHTS trigger linedef's conditions
1419 *
1420 * \param triggerline Trigger linedef to check conditions for; should NEVER be NULL.
1421 * \param actor Object initiating the action; should not be NULL.
1422 * \sa P_RunTriggerLinedef
1423 */
P_CheckNightsTriggerLine(line_t * triggerline,mobj_t * actor)1424 static boolean P_CheckNightsTriggerLine(line_t *triggerline, mobj_t *actor)
1425 {
1426 INT16 specialtype = triggerline->special;
1427 size_t i;
1428
1429 UINT8 inputmare = max(0, min(255, sides[triggerline->sidenum[0]].textureoffset>>FRACBITS));
1430 UINT8 inputlap = max(0, min(255, sides[triggerline->sidenum[0]].rowoffset>>FRACBITS));
1431
1432 boolean ltemare = triggerline->flags & ML_NOCLIMB;
1433 boolean gtemare = triggerline->flags & ML_BLOCKMONSTERS;
1434 boolean ltelap = triggerline->flags & ML_EFFECT1;
1435 boolean gtelap = triggerline->flags & ML_EFFECT2;
1436
1437 boolean lapfrombonustime = triggerline->flags & ML_EFFECT3;
1438 boolean perglobalinverse = triggerline->flags & ML_DONTPEGBOTTOM;
1439 boolean perglobal = !(triggerline->flags & ML_EFFECT4) && !perglobalinverse;
1440
1441 boolean donomares = triggerline->flags & ML_BOUNCY; // nightserize: run at end of level (no mares)
1442 boolean fromnonights = triggerline->flags & ML_TFERLINE; // nightserize: from non-nights // denightserize: all players no nights
1443 boolean fromnights = triggerline->flags & ML_DONTPEGTOP; // nightserize: from nights // denightserize: >0 players are nights
1444
1445 UINT8 currentmare = UINT8_MAX;
1446 UINT8 currentlap = UINT8_MAX;
1447
1448 // Do early returns for Nightserize
1449 if (specialtype >= 323 && specialtype <= 324)
1450 {
1451 // run only when no mares are found
1452 if (donomares && P_FindLowestMare() != UINT8_MAX)
1453 return false;
1454
1455 // run only if there is a mare present
1456 if (!donomares && P_FindLowestMare() == UINT8_MAX)
1457 return false;
1458
1459 // run only if player is nightserizing from non-nights
1460 if (fromnonights)
1461 {
1462 if (!actor->player)
1463 return false;
1464 else if (actor->player->powers[pw_carry] == CR_NIGHTSMODE)
1465 return false;
1466 }
1467 // run only if player is nightserizing from nights
1468 else if (fromnights)
1469 {
1470 if (!actor->player)
1471 return false;
1472 else if (actor->player->powers[pw_carry] != CR_NIGHTSMODE)
1473 return false;
1474 }
1475 }
1476
1477 // Get current mare and lap (and check early return for DeNightserize)
1478 if (perglobal || perglobalinverse
1479 || (specialtype >= 325 && specialtype <= 326 && (fromnonights || fromnights)))
1480 {
1481 UINT8 playersarenights = 0;
1482
1483 for (i = 0; i < MAXPLAYERS; i++)
1484 {
1485 UINT8 lap;
1486 if (!playeringame[i] || players[i].spectator)
1487 continue;
1488
1489 // denightserize: run only if all players are not nights
1490 if (specialtype >= 325 && specialtype <= 326 && fromnonights
1491 && players[i].powers[pw_carry] == CR_NIGHTSMODE)
1492 return false;
1493
1494 // count number of nights players for denightserize return
1495 if (specialtype >= 325 && specialtype <= 326 && fromnights
1496 && players[i].powers[pw_carry] == CR_NIGHTSMODE)
1497 playersarenights++;
1498
1499 lap = lapfrombonustime ? players[i].marebonuslap : players[i].marelap;
1500
1501 // get highest mare/lap of players
1502 if (perglobal)
1503 {
1504 if (players[i].mare > currentmare || currentmare == UINT8_MAX)
1505 {
1506 currentmare = players[i].mare;
1507 currentlap = UINT8_MAX;
1508 }
1509 if (players[i].mare == currentmare
1510 && (lap > currentlap || currentlap == UINT8_MAX))
1511 currentlap = lap;
1512 }
1513 // get lowest mare/lap of players
1514 else if (perglobalinverse)
1515 {
1516 if (players[i].mare < currentmare || currentmare == UINT8_MAX)
1517 {
1518 currentmare = players[i].mare;
1519 currentlap = UINT8_MAX;
1520 }
1521 if (players[i].mare == currentmare
1522 && (lap < currentlap || currentlap == UINT8_MAX))
1523 currentlap = lap;
1524 }
1525 }
1526
1527 // denightserize: run only if >0 players are nights
1528 if (specialtype >= 325 && specialtype <= 326 && fromnights
1529 && playersarenights < 1)
1530 return false;
1531 }
1532 // get current mare/lap from triggering player
1533 else if (!perglobal && !perglobalinverse)
1534 {
1535 if (!actor->player)
1536 return false;
1537 currentmare = actor->player->mare;
1538 currentlap = lapfrombonustime ? actor->player->marebonuslap : actor->player->marelap;
1539 }
1540
1541 if (lapfrombonustime && !currentlap)
1542 return false; // special case: player->marebonuslap is 0 until passing through on bonus time. Don't trigger lines looking for inputlap 0.
1543
1544 // Compare current mare/lap to input mare/lap based on rules
1545 if (!(specialtype >= 323 && specialtype <= 324 && donomares) // don't return false if donomares and we got this far
1546 && ((ltemare && currentmare > inputmare)
1547 || (gtemare && currentmare < inputmare)
1548 || (!ltemare && !gtemare && currentmare != inputmare)
1549 || (ltelap && currentlap > inputlap)
1550 || (gtelap && currentlap < inputlap)
1551 || (!ltelap && !gtelap && currentlap != inputlap))
1552 )
1553 return false;
1554
1555 return true;
1556 }
1557
1558 /** Used by P_LinedefExecute to check a trigger linedef's conditions
1559 * The linedef executor specials in the trigger linedef's sector are run if all conditions are met.
1560 * Return false cancels P_LinedefExecute, this happens if a condition is not met.
1561 *
1562 * \param triggerline Trigger linedef to check conditions for; should NEVER be NULL.
1563 * \param actor Object initiating the action; should not be NULL.
1564 * \param caller Sector in which the action was started. May be NULL.
1565 * \sa P_ProcessLineSpecial, P_LinedefExecute
1566 */
P_RunTriggerLinedef(line_t * triggerline,mobj_t * actor,sector_t * caller)1567 boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller)
1568 {
1569 sector_t *ctlsector;
1570 fixed_t dist = P_AproxDistance(triggerline->dx, triggerline->dy)>>FRACBITS;
1571 size_t i, linecnt, sectori;
1572 INT16 specialtype = triggerline->special;
1573
1574 /////////////////////////////////////////////////
1575 // Distance-checking/sector trigger conditions //
1576 /////////////////////////////////////////////////
1577
1578 // Linetypes 303 and 304 require a specific
1579 // number, or minimum or maximum, of rings.
1580 if (specialtype == 303 || specialtype == 304)
1581 {
1582 fixed_t rings = 0;
1583
1584 // With the passuse flag, count all player's
1585 // rings.
1586 if (triggerline->flags & ML_EFFECT4)
1587 {
1588 for (i = 0; i < MAXPLAYERS; i++)
1589 {
1590 if (!playeringame[i] || players[i].spectator)
1591 continue;
1592
1593 if (!players[i].mo || ((maptol & TOL_NIGHTS) ? players[i].spheres : players[i].rings) <= 0)
1594 continue;
1595
1596 rings += (maptol & TOL_NIGHTS) ? players[i].spheres : players[i].rings;
1597 }
1598 }
1599 else
1600 {
1601 if (!(actor && actor->player))
1602 return false; // no player to count rings from here, sorry
1603
1604 rings = (maptol & TOL_NIGHTS) ? actor->player->spheres : actor->player->rings;
1605 }
1606
1607 if (triggerline->flags & ML_NOCLIMB)
1608 {
1609 if (rings > dist)
1610 return false;
1611 }
1612 else if (triggerline->flags & ML_BLOCKMONSTERS)
1613 {
1614 if (rings < dist)
1615 return false;
1616 }
1617 else
1618 {
1619 if (rings != dist)
1620 return false;
1621 }
1622 }
1623 else if (specialtype >= 314 && specialtype <= 315)
1624 {
1625 msecnode_t *node;
1626 mobj_t *mo;
1627 INT32 numpush = 0;
1628 INT32 numneeded = dist;
1629
1630 if (!caller)
1631 return false; // we need a calling sector to find pushables in, silly!
1632
1633 // Count the pushables in this sector
1634 node = caller->touching_thinglist; // things touching this sector
1635 while (node)
1636 {
1637 mo = node->m_thing;
1638 if ((mo->flags & MF_PUSHABLE) || ((mo->info->flags & MF_PUSHABLE) && mo->fuse))
1639 numpush++;
1640 node = node->m_thinglist_next;
1641 }
1642
1643 if (triggerline->flags & ML_NOCLIMB) // Need at least or more
1644 {
1645 if (numpush < numneeded)
1646 return false;
1647 }
1648 else if (triggerline->flags & ML_EFFECT4) // Need less than
1649 {
1650 if (numpush >= numneeded)
1651 return false;
1652 }
1653 else // Need exact
1654 {
1655 if (numpush != numneeded)
1656 return false;
1657 }
1658 }
1659 else if (caller)
1660 {
1661 if (GETSECSPECIAL(caller->special, 2) == 6)
1662 {
1663 if (!(ALL7EMERALDS(emeralds)))
1664 return false;
1665 }
1666 else if (GETSECSPECIAL(caller->special, 2) == 7)
1667 {
1668 UINT8 mare;
1669
1670 if (!(maptol & TOL_NIGHTS))
1671 return false;
1672
1673 mare = P_FindLowestMare();
1674
1675 if (triggerline->flags & ML_NOCLIMB)
1676 {
1677 if (!(mare <= dist))
1678 return false;
1679 }
1680 else if (triggerline->flags & ML_BLOCKMONSTERS)
1681 {
1682 if (!(mare >= dist))
1683 return false;
1684 }
1685 else
1686 {
1687 if (!(mare == dist))
1688 return false;
1689 }
1690 }
1691 // If we were not triggered by a sector type especially for the purpose,
1692 // a Linedef Executor linedef trigger is not handling sector triggers properly, return.
1693
1694 else if ((!GETSECSPECIAL(caller->special, 2) || GETSECSPECIAL(caller->special, 2) > 7) && (specialtype > 322))
1695 {
1696 CONS_Alert(CONS_WARNING,
1697 M_GetText("Linedef executor trigger isn't handling sector triggers properly!\nspecialtype = %d, if you are not a dev, report this warning instance\nalong with the wad that caused it!\n"),
1698 specialtype);
1699 return false;
1700 }
1701 }
1702
1703 //////////////////////////////////////
1704 // Miscellaneous trigger conditions //
1705 //////////////////////////////////////
1706
1707 switch (specialtype)
1708 {
1709 case 305: // continuous
1710 case 306: // each time
1711 case 307: // once
1712 if (!(actor && actor->player && actor->player->charability == dist/10))
1713 return false;
1714 break;
1715 case 309: // continuous
1716 case 310: // each time
1717 // Only red team members can activate this.
1718 if (!(actor && actor->player && actor->player->ctfteam == 1))
1719 return false;
1720 break;
1721 case 311: // continuous
1722 case 312: // each time
1723 // Only blue team members can activate this.
1724 if (!(actor && actor->player && actor->player->ctfteam == 2))
1725 return false;
1726 break;
1727 case 317: // continuous
1728 case 318: // once
1729 { // Unlockable triggers required
1730 INT32 trigid = (INT32)(sides[triggerline->sidenum[0]].textureoffset>>FRACBITS);
1731
1732 if ((modifiedgame && !savemoddata) || (netgame || multiplayer))
1733 return false;
1734 else if (trigid < 0 || trigid > 31) // limited by 32 bit variable
1735 {
1736 CONS_Debug(DBG_GAMELOGIC, "Unlockable trigger (sidedef %hu): bad trigger ID %d\n", triggerline->sidenum[0], trigid);
1737 return false;
1738 }
1739 else if (!(unlocktriggers & (1 << trigid)))
1740 return false;
1741 }
1742 break;
1743 case 319: // continuous
1744 case 320: // once
1745 { // An unlockable itself must be unlocked!
1746 INT32 unlockid = (INT32)(sides[triggerline->sidenum[0]].textureoffset>>FRACBITS);
1747
1748 if ((modifiedgame && !savemoddata) || (netgame || multiplayer))
1749 return false;
1750 else if (unlockid < 0 || unlockid >= MAXUNLOCKABLES) // limited by unlockable count
1751 {
1752 CONS_Debug(DBG_GAMELOGIC, "Unlockable check (sidedef %hu): bad unlockable ID %d\n", triggerline->sidenum[0], unlockid);
1753 return false;
1754 }
1755 else if (!(unlockables[unlockid-1].unlocked))
1756 return false;
1757 }
1758 break;
1759 case 321: // continuous
1760 case 322: // each time
1761 // decrement calls left before triggering
1762 if (triggerline->callcount > 0)
1763 {
1764 if (--triggerline->callcount > 0)
1765 return false;
1766 }
1767 break;
1768 case 323: // nightserize - each time
1769 case 324: // nightserize - once
1770 case 325: // denightserize - each time
1771 case 326: // denightserize - once
1772 case 327: // nights lap - each time
1773 case 328: // nights lap - once
1774 case 329: // nights egg capsule touch - each time
1775 case 330: // nights egg capsule touch - once
1776 if (!P_CheckNightsTriggerLine(triggerline, actor))
1777 return false;
1778 break;
1779 case 331: // continuous
1780 case 332: // each time
1781 case 333: // once
1782 if (!(actor && actor->player && ((stricmp(triggerline->text, skins[actor->player->skin].name) == 0) ^ ((triggerline->flags & ML_NOCLIMB) == ML_NOCLIMB))))
1783 return false;
1784 break;
1785 case 334: // object dye - continuous
1786 case 335: // object dye - each time
1787 case 336: // object dye - once
1788 {
1789 INT32 triggercolor = (INT32)sides[triggerline->sidenum[0]].toptexture;
1790 UINT16 color = (actor->player ? actor->player->powers[pw_dye] : actor->color);
1791 boolean invert = (triggerline->flags & ML_NOCLIMB ? true : false);
1792
1793 if (invert ^ (triggercolor != color))
1794 return false;
1795 }
1796 default:
1797 break;
1798 }
1799
1800 /////////////////////////////////
1801 // Processing linedef specials //
1802 /////////////////////////////////
1803
1804 ctlsector = triggerline->frontsector;
1805 sectori = (size_t)(ctlsector - sectors);
1806 linecnt = ctlsector->linecount;
1807
1808 if (triggerline->flags & ML_EFFECT5) // disregard order for efficiency
1809 {
1810 for (i = 0; i < linecnt; i++)
1811 if (ctlsector->lines[i]->special >= 400
1812 && ctlsector->lines[i]->special < 500)
1813 {
1814 if (ctlsector->lines[i]->executordelay)
1815 P_AddExecutorDelay(ctlsector->lines[i], actor, caller);
1816 else
1817 P_ProcessLineSpecial(ctlsector->lines[i], actor, caller);
1818 }
1819 }
1820 else // walk around the sector in a defined order
1821 {
1822 boolean backwards = false;
1823 size_t j, masterlineindex = (size_t)-1;
1824
1825 for (i = 0; i < linecnt; i++)
1826 if (ctlsector->lines[i] == triggerline)
1827 {
1828 masterlineindex = i;
1829 break;
1830 }
1831
1832 #ifdef PARANOIA
1833 if (masterlineindex == (size_t)-1)
1834 {
1835 const size_t li = (size_t)(ctlsector->lines[i] - lines);
1836 I_Error("Line %s isn't linked into its front sector", sizeu1(li));
1837 }
1838 #endif
1839
1840 // i == masterlineindex
1841 for (;;)
1842 {
1843 if (backwards) // v2 to v1
1844 {
1845 for (j = 0; j < linecnt; j++)
1846 {
1847 if (i == j)
1848 continue;
1849 if (ctlsector->lines[i]->v1 == ctlsector->lines[j]->v2)
1850 {
1851 i = j;
1852 break;
1853 }
1854 if (ctlsector->lines[i]->v1 == ctlsector->lines[j]->v1)
1855 {
1856 i = j;
1857 backwards = false;
1858 break;
1859 }
1860 }
1861 if (j == linecnt)
1862 {
1863 const size_t vertexei = (size_t)(ctlsector->lines[i]->v1 - vertexes);
1864 CONS_Debug(DBG_GAMELOGIC, "Warning: Sector %s is not closed at vertex %s (%d, %d)\n",
1865 sizeu1(sectori), sizeu2(vertexei), ctlsector->lines[i]->v1->x, ctlsector->lines[i]->v1->y);
1866 return false; // abort
1867 }
1868 }
1869 else // v1 to v2
1870 {
1871 for (j = 0; j < linecnt; j++)
1872 {
1873 if (i == j)
1874 continue;
1875 if (ctlsector->lines[i]->v2 == ctlsector->lines[j]->v1)
1876 {
1877 i = j;
1878 break;
1879 }
1880 if (ctlsector->lines[i]->v2 == ctlsector->lines[j]->v2)
1881 {
1882 i = j;
1883 backwards = true;
1884 break;
1885 }
1886 }
1887 if (j == linecnt)
1888 {
1889 const size_t vertexei = (size_t)(ctlsector->lines[i]->v1 - vertexes);
1890 CONS_Debug(DBG_GAMELOGIC, "Warning: Sector %s is not closed at vertex %s (%d, %d)\n",
1891 sizeu1(sectori), sizeu2(vertexei), ctlsector->lines[i]->v2->x, ctlsector->lines[i]->v2->y);
1892 return false; // abort
1893 }
1894 }
1895
1896 if (i == masterlineindex)
1897 break;
1898
1899 if (ctlsector->lines[i]->special >= 400
1900 && ctlsector->lines[i]->special < 500)
1901 {
1902 if (ctlsector->lines[i]->executordelay)
1903 P_AddExecutorDelay(ctlsector->lines[i], actor, caller);
1904 else
1905 P_ProcessLineSpecial(ctlsector->lines[i], actor, caller);
1906 }
1907 }
1908 }
1909
1910 // "Trigger on X calls" linedefs reset if noclimb is set
1911 if ((specialtype == 321 || specialtype == 322) && triggerline->flags & ML_NOCLIMB)
1912 triggerline->callcount = sides[triggerline->sidenum[0]].textureoffset>>FRACBITS;
1913 else
1914 // These special types work only once
1915 if (specialtype == 302 // Once
1916 || specialtype == 304 // Ring count - Once
1917 || specialtype == 307 // Character ability - Once
1918 || specialtype == 308 // Race only - Once
1919 || specialtype == 313 // No More Enemies - Once
1920 || specialtype == 315 // No of pushables - Once
1921 || specialtype == 318 // Unlockable trigger - Once
1922 || specialtype == 320 // Unlockable - Once
1923 || specialtype == 321 || specialtype == 322 // Trigger on X calls - Continuous + Each Time
1924 || specialtype == 324 // Nightserize - Once
1925 || specialtype == 326 // DeNightserize - Once
1926 || specialtype == 328 // Nights lap - Once
1927 || specialtype == 330 // Nights Bonus Time - Once
1928 || specialtype == 333 // Skin - Once
1929 || specialtype == 336 // Dye - Once
1930 || specialtype == 399) // Level Load
1931 triggerline->special = 0; // Clear it out
1932
1933 return true;
1934 }
1935
1936 /** Runs a linedef executor.
1937 * Can be called by:
1938 * - a player moving into a special sector or FOF.
1939 * - a pushable object moving into a special sector or FOF.
1940 * - a ceiling or floor movement from a previous linedef executor finishing.
1941 * - any object in a state with the A_LinedefExecute() action.
1942 *
1943 * \param tag Tag of the linedef executor to run.
1944 * \param actor Object initiating the action; should not be NULL.
1945 * \param caller Sector in which the action was started. May be NULL.
1946 * \sa P_ProcessLineSpecial, P_RunTriggerLinedef
1947 * \author Graue <graue@oceanbase.org>
1948 */
P_LinedefExecute(INT16 tag,mobj_t * actor,sector_t * caller)1949 void P_LinedefExecute(INT16 tag, mobj_t *actor, sector_t *caller)
1950 {
1951 size_t masterline;
1952
1953 CONS_Debug(DBG_GAMELOGIC, "P_LinedefExecute: Executing trigger linedefs of tag %d\n", tag);
1954
1955 I_Assert(!actor || !P_MobjWasRemoved(actor)); // If actor is there, it must be valid.
1956
1957 for (masterline = 0; masterline < numlines; masterline++)
1958 {
1959 if (Tag_FGet(&lines[masterline].tags) != tag)
1960 continue;
1961
1962 // "No More Enemies" and "Level Load" take care of themselves.
1963 if (lines[masterline].special == 313
1964 || lines[masterline].special == 399
1965 // Each-time executors handle themselves, too
1966 || lines[masterline].special == 301 // Each time
1967 || lines[masterline].special == 306 // Character ability - Each time
1968 || lines[masterline].special == 310 // CTF Red team - Each time
1969 || lines[masterline].special == 312 // CTF Blue team - Each time
1970 || lines[masterline].special == 322 // Trigger on X calls - Each Time
1971 || lines[masterline].special == 332 // Skin - Each time
1972 || lines[masterline].special == 335)// Dye - Each time
1973 continue;
1974
1975 if (lines[masterline].special < 300
1976 || lines[masterline].special > 399)
1977 continue;
1978
1979 if (!P_RunTriggerLinedef(&lines[masterline], actor, caller))
1980 return; // cancel P_LinedefExecute if function returns false
1981 }
1982 }
1983
1984 //
1985 // P_SwitchWeather
1986 //
1987 // Switches the weather!
1988 //
P_SwitchWeather(INT32 weathernum)1989 void P_SwitchWeather(INT32 weathernum)
1990 {
1991 boolean purge = false;
1992 INT32 swap = 0;
1993
1994 switch (weathernum)
1995 {
1996 case PRECIP_NONE: // None
1997 if (curWeather == PRECIP_NONE)
1998 return; // Nothing to do.
1999 purge = true;
2000 break;
2001 case PRECIP_STORM: // Storm
2002 case PRECIP_STORM_NOSTRIKES: // Storm w/ no lightning
2003 case PRECIP_RAIN: // Rain
2004 if (curWeather == PRECIP_SNOW || curWeather == PRECIP_BLANK || curWeather == PRECIP_STORM_NORAIN)
2005 swap = PRECIP_RAIN;
2006 break;
2007 case PRECIP_SNOW: // Snow
2008 if (curWeather == PRECIP_SNOW)
2009 return; // Nothing to do.
2010 if (curWeather == PRECIP_RAIN || curWeather == PRECIP_STORM || curWeather == PRECIP_STORM_NOSTRIKES || curWeather == PRECIP_BLANK || curWeather == PRECIP_STORM_NORAIN)
2011 swap = PRECIP_SNOW; // Need to delete the other precips.
2012 break;
2013 case PRECIP_STORM_NORAIN: // Storm w/o rain
2014 if (curWeather == PRECIP_SNOW
2015 || curWeather == PRECIP_STORM
2016 || curWeather == PRECIP_STORM_NOSTRIKES
2017 || curWeather == PRECIP_RAIN
2018 || curWeather == PRECIP_BLANK)
2019 swap = PRECIP_STORM_NORAIN;
2020 else if (curWeather == PRECIP_STORM_NORAIN)
2021 return;
2022 break;
2023 case PRECIP_BLANK:
2024 if (curWeather == PRECIP_SNOW
2025 || curWeather == PRECIP_STORM
2026 || curWeather == PRECIP_STORM_NOSTRIKES
2027 || curWeather == PRECIP_RAIN)
2028 swap = PRECIP_BLANK;
2029 else if (curWeather == PRECIP_STORM_NORAIN)
2030 swap = PRECIP_BLANK;
2031 else if (curWeather == PRECIP_BLANK)
2032 return;
2033 break;
2034 default:
2035 CONS_Debug(DBG_GAMELOGIC, "P_SwitchWeather: Unknown weather type %d.\n", weathernum);
2036 break;
2037 }
2038
2039 if (purge)
2040 {
2041 thinker_t *think;
2042 precipmobj_t *precipmobj;
2043
2044 for (think = thlist[THINK_PRECIP].next; think != &thlist[THINK_PRECIP]; think = think->next)
2045 {
2046 if (think->function.acp1 != (actionf_p1)P_NullPrecipThinker)
2047 continue; // not a precipmobj thinker
2048
2049 precipmobj = (precipmobj_t *)think;
2050
2051 P_RemovePrecipMobj(precipmobj);
2052 }
2053 }
2054 else if (swap && !((swap == PRECIP_BLANK && curWeather == PRECIP_STORM_NORAIN) || (swap == PRECIP_STORM_NORAIN && curWeather == PRECIP_BLANK))) // Rather than respawn all that crap, reuse it!
2055 {
2056 thinker_t *think;
2057 precipmobj_t *precipmobj;
2058 state_t *st;
2059
2060 for (think = thlist[THINK_PRECIP].next; think != &thlist[THINK_PRECIP]; think = think->next)
2061 {
2062 if (think->function.acp1 != (actionf_p1)P_NullPrecipThinker)
2063 continue; // not a precipmobj thinker
2064 precipmobj = (precipmobj_t *)think;
2065
2066 if (swap == PRECIP_RAIN) // Snow To Rain
2067 {
2068 precipmobj->flags = mobjinfo[MT_RAIN].flags;
2069 st = &states[mobjinfo[MT_RAIN].spawnstate];
2070 precipmobj->state = st;
2071 precipmobj->tics = st->tics;
2072 precipmobj->sprite = st->sprite;
2073 precipmobj->frame = st->frame;
2074 precipmobj->momz = mobjinfo[MT_RAIN].speed;
2075
2076 precipmobj->precipflags &= ~PCF_INVISIBLE;
2077
2078 precipmobj->precipflags |= PCF_RAIN;
2079 //think->function.acp1 = (actionf_p1)P_RainThinker;
2080 }
2081 else if (swap == PRECIP_SNOW) // Rain To Snow
2082 {
2083 INT32 z;
2084
2085 precipmobj->flags = mobjinfo[MT_SNOWFLAKE].flags;
2086 z = M_RandomByte();
2087
2088 if (z < 64)
2089 z = 2;
2090 else if (z < 144)
2091 z = 1;
2092 else
2093 z = 0;
2094
2095 st = &states[mobjinfo[MT_SNOWFLAKE].spawnstate+z];
2096 precipmobj->state = st;
2097 precipmobj->tics = st->tics;
2098 precipmobj->sprite = st->sprite;
2099 precipmobj->frame = st->frame;
2100 precipmobj->momz = mobjinfo[MT_SNOWFLAKE].speed;
2101
2102 precipmobj->precipflags &= ~(PCF_INVISIBLE|PCF_RAIN);
2103
2104 //think->function.acp1 = (actionf_p1)P_SnowThinker;
2105 }
2106 else if (swap == PRECIP_BLANK || swap == PRECIP_STORM_NORAIN) // Remove precip, but keep it around for reuse.
2107 {
2108 //think->function.acp1 = (actionf_p1)P_NullPrecipThinker;
2109
2110 precipmobj->precipflags |= PCF_INVISIBLE;
2111 }
2112 }
2113 }
2114
2115 switch (weathernum)
2116 {
2117 case PRECIP_SNOW: // snow
2118 curWeather = PRECIP_SNOW;
2119
2120 if (!swap)
2121 P_SpawnPrecipitation();
2122
2123 break;
2124 case PRECIP_RAIN: // rain
2125 {
2126 boolean dontspawn = false;
2127
2128 if (curWeather == PRECIP_RAIN || curWeather == PRECIP_STORM || curWeather == PRECIP_STORM_NOSTRIKES)
2129 dontspawn = true;
2130
2131 curWeather = PRECIP_RAIN;
2132
2133 if (!dontspawn && !swap)
2134 P_SpawnPrecipitation();
2135
2136 break;
2137 }
2138 case PRECIP_STORM: // storm
2139 {
2140 boolean dontspawn = false;
2141
2142 if (curWeather == PRECIP_RAIN || curWeather == PRECIP_STORM || curWeather == PRECIP_STORM_NOSTRIKES)
2143 dontspawn = true;
2144
2145 curWeather = PRECIP_STORM;
2146
2147 if (!dontspawn && !swap)
2148 P_SpawnPrecipitation();
2149
2150 break;
2151 }
2152 case PRECIP_STORM_NOSTRIKES: // storm w/o lightning
2153 {
2154 boolean dontspawn = false;
2155
2156 if (curWeather == PRECIP_RAIN || curWeather == PRECIP_STORM || curWeather == PRECIP_STORM_NOSTRIKES)
2157 dontspawn = true;
2158
2159 curWeather = PRECIP_STORM_NOSTRIKES;
2160
2161 if (!dontspawn && !swap)
2162 P_SpawnPrecipitation();
2163
2164 break;
2165 }
2166 case PRECIP_STORM_NORAIN: // storm w/o rain
2167 curWeather = PRECIP_STORM_NORAIN;
2168
2169 if (!swap)
2170 P_SpawnPrecipitation();
2171
2172 break;
2173 case PRECIP_BLANK:
2174 curWeather = PRECIP_BLANK;
2175
2176 if (!swap)
2177 P_SpawnPrecipitation();
2178
2179 break;
2180 default:
2181 curWeather = PRECIP_NONE;
2182 break;
2183 }
2184 }
2185
2186 /** Gets an object.
2187 *
2188 * \param type Object type to look for.
2189 * \param s Sector number to look in.
2190 * \return Pointer to the first ::type found in the sector.
2191 * \sa P_GetPushThing
2192 */
P_GetObjectTypeInSectorNum(mobjtype_t type,size_t s)2193 static mobj_t *P_GetObjectTypeInSectorNum(mobjtype_t type, size_t s)
2194 {
2195 sector_t *sec = sectors + s;
2196 mobj_t *thing = sec->thinglist;
2197
2198 while (thing)
2199 {
2200 if (thing->type == type)
2201 return thing;
2202 thing = thing->snext;
2203 }
2204 return NULL;
2205 }
2206
2207 /** Processes the line special triggered by an object.
2208 *
2209 * \param line Line with the special command on it.
2210 * \param mo mobj that triggered the line. Must be valid and non-NULL.
2211 * \param callsec sector in which action was initiated; this can be NULL.
2212 * Because of the A_LinedefExecute() action, even if non-NULL,
2213 * this sector might not have the same tag as the linedef executor,
2214 * and it might not have the linedef executor sector type.
2215 * \todo Handle mo being NULL gracefully. T_MoveFloor() and T_MoveCeiling()
2216 * don't have an object to pass.
2217 * \todo Split up into multiple functions.
2218 * \sa P_LinedefExecute
2219 * \author Graue <graue@oceanbase.org>
2220 */
P_ProcessLineSpecial(line_t * line,mobj_t * mo,sector_t * callsec)2221 static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
2222 {
2223 INT32 secnum = -1;
2224 mobj_t *bot = NULL;
2225 mtag_t tag = Tag_FGet(&line->tags);
2226 TAG_ITER_DECLARECOUNTER(0);
2227
2228 I_Assert(!mo || !P_MobjWasRemoved(mo)); // If mo is there, mo must be valid!
2229
2230 if (mo && mo->player && botingame)
2231 bot = players[secondarydisplayplayer].mo;
2232
2233 // note: only commands with linedef types >= 400 && < 500 can be used
2234 switch (line->special)
2235 {
2236 case 400: // Set tagged sector's floor height/pic
2237 EV_DoFloor(line, instantMoveFloorByFrontSector);
2238 break;
2239
2240 case 401: // Set tagged sector's ceiling height/pic
2241 EV_DoCeiling(line, instantMoveCeilingByFrontSector);
2242 break;
2243
2244 case 402: // Set tagged sector's light level
2245 {
2246 INT16 newlightlevel;
2247 INT32 newfloorlightsec, newceilinglightsec;
2248
2249 newlightlevel = line->frontsector->lightlevel;
2250 newfloorlightsec = line->frontsector->floorlightsec;
2251 newceilinglightsec = line->frontsector->ceilinglightsec;
2252
2253 // act on all sectors with the same tag as the triggering linedef
2254 TAG_ITER_SECTORS(0, tag, secnum)
2255 {
2256 if (sectors[secnum].lightingdata)
2257 {
2258 // Stop the lighting madness going on in this sector!
2259 P_RemoveThinker(&((elevator_t *)sectors[secnum].lightingdata)->thinker);
2260 sectors[secnum].lightingdata = NULL;
2261
2262 // No, it's not an elevator_t, but any struct with a thinker_t named
2263 // 'thinker' at the beginning will do here. (We don't know what it
2264 // actually is: could be lightlevel_t, fireflicker_t, glow_t, etc.)
2265 }
2266
2267 sectors[secnum].lightlevel = newlightlevel;
2268 sectors[secnum].floorlightsec = newfloorlightsec;
2269 sectors[secnum].ceilinglightsec = newceilinglightsec;
2270 }
2271 }
2272 break;
2273
2274 case 403: // Move floor, linelen = speed, frontsector floor = dest height
2275 EV_DoFloor(line, moveFloorByFrontSector);
2276 break;
2277
2278 case 404: // Move ceiling, linelen = speed, frontsector ceiling = dest height
2279 EV_DoCeiling(line, moveCeilingByFrontSector);
2280 break;
2281
2282 case 405: // Move floor by front side texture offsets, offset x = speed, offset y = amount to raise/lower
2283 EV_DoFloor(line, moveFloorByFrontTexture);
2284 break;
2285
2286 case 407: // Move ceiling by front side texture offsets, offset x = speed, offset y = amount to raise/lower
2287 EV_DoCeiling(line, moveCeilingByFrontTexture);
2288 break;
2289
2290 /* case 405: // Lower floor by line, dx = speed, dy = amount to lower
2291 EV_DoFloor(line, lowerFloorByLine);
2292 break;
2293
2294 case 406: // Raise floor by line, dx = speed, dy = amount to raise
2295 EV_DoFloor(line, raiseFloorByLine);
2296 break;
2297
2298 case 407: // Lower ceiling by line, dx = speed, dy = amount to lower
2299 EV_DoCeiling(line, lowerCeilingByLine);
2300 break;
2301
2302 case 408: // Raise ceiling by line, dx = speed, dy = amount to raise
2303 EV_DoCeiling(line, raiseCeilingByLine);
2304 break;*/
2305
2306 case 409: // Change tagged sectors' tag
2307 // (formerly "Change calling sectors' tag", but behavior was changed)
2308 {
2309 TAG_ITER_SECTORS(0, tag, secnum)
2310 Tag_SectorFSet(secnum,(INT16)(sides[line->sidenum[0]].textureoffset>>FRACBITS));
2311 break;
2312 }
2313
2314 case 410: // Change front sector's tag
2315 Tag_SectorFSet((UINT32)(line->frontsector - sectors), (INT16)(sides[line->sidenum[0]].textureoffset>>FRACBITS));
2316 break;
2317
2318 case 411: // Stop floor/ceiling movement in tagged sector(s)
2319 TAG_ITER_SECTORS(0, tag, secnum)
2320 {
2321 if (sectors[secnum].floordata)
2322 {
2323 if (sectors[secnum].floordata == sectors[secnum].ceilingdata) // elevator
2324 {
2325 P_RemoveThinker(&((elevator_t *)sectors[secnum].floordata)->thinker);
2326 sectors[secnum].floordata = sectors[secnum].ceilingdata = NULL;
2327 sectors[secnum].floorspeed = sectors[secnum].ceilspeed = 0;
2328 }
2329 else // floormove
2330 {
2331 P_RemoveThinker(&((floormove_t *)sectors[secnum].floordata)->thinker);
2332 sectors[secnum].floordata = NULL;
2333 sectors[secnum].floorspeed = 0;
2334 }
2335 }
2336
2337 if (sectors[secnum].ceilingdata) // ceiling
2338 {
2339 P_RemoveThinker(&((ceiling_t *)sectors[secnum].ceilingdata)->thinker);
2340 sectors[secnum].ceilingdata = NULL;
2341 sectors[secnum].ceilspeed = 0;
2342 }
2343 }
2344 break;
2345
2346 case 412: // Teleport the player or thing
2347 {
2348 mobj_t *dest;
2349
2350 if (!mo) // nothing to teleport
2351 return;
2352
2353 if (line->flags & ML_EFFECT3) // Relative silent teleport
2354 {
2355 fixed_t x, y, z;
2356
2357 x = sides[line->sidenum[0]].textureoffset;
2358 y = sides[line->sidenum[0]].rowoffset;
2359 z = line->frontsector->ceilingheight;
2360
2361 P_UnsetThingPosition(mo);
2362 mo->x += x;
2363 mo->y += y;
2364 mo->z += z;
2365 P_SetThingPosition(mo);
2366
2367 if (mo->player)
2368 {
2369 if (bot) // This might put poor Tails in a wall if he's too far behind! D: But okay, whatever! >:3
2370 P_TeleportMove(bot, bot->x + x, bot->y + y, bot->z + z);
2371 if (splitscreen && mo->player == &players[secondarydisplayplayer] && camera2.chase)
2372 {
2373 camera2.x += x;
2374 camera2.y += y;
2375 camera2.z += z;
2376 camera2.subsector = R_PointInSubsector(camera2.x, camera2.y);
2377 }
2378 else if (camera.chase && mo->player == &players[displayplayer])
2379 {
2380 camera.x += x;
2381 camera.y += y;
2382 camera.z += z;
2383 camera.subsector = R_PointInSubsector(camera.x, camera.y);
2384 }
2385 }
2386 }
2387 else
2388 {
2389 if ((secnum = Tag_Iterate_Sectors(tag, 0)) < 0)
2390 return;
2391
2392 dest = P_GetObjectTypeInSectorNum(MT_TELEPORTMAN, secnum);
2393 if (!dest)
2394 return;
2395
2396 if (bot)
2397 P_Teleport(bot, dest->x, dest->y, dest->z, (line->flags & ML_NOCLIMB) ? mo->angle : dest->angle, (line->flags & ML_BLOCKMONSTERS) == 0, (line->flags & ML_EFFECT4) == ML_EFFECT4);
2398 if (line->flags & ML_BLOCKMONSTERS)
2399 P_Teleport(mo, dest->x, dest->y, dest->z, (line->flags & ML_NOCLIMB) ? mo->angle : dest->angle, false, (line->flags & ML_EFFECT4) == ML_EFFECT4);
2400 else
2401 {
2402 P_Teleport(mo, dest->x, dest->y, dest->z, (line->flags & ML_NOCLIMB) ? mo->angle : dest->angle, true, (line->flags & ML_EFFECT4) == ML_EFFECT4);
2403 // Play the 'bowrwoosh!' sound
2404 S_StartSound(dest, sfx_mixup);
2405 }
2406 }
2407 }
2408 break;
2409
2410 case 413: // Change music
2411 // console player only unless NOCLIMB is set
2412 if ((line->flags & ML_NOCLIMB) || (mo && mo->player && P_IsLocalPlayer(mo->player)) || titlemapinaction)
2413 {
2414 boolean musicsame = (!sides[line->sidenum[0]].text[0] || !strnicmp(sides[line->sidenum[0]].text, S_MusicName(), 7));
2415 UINT16 tracknum = (UINT16)max(sides[line->sidenum[0]].bottomtexture, 0);
2416 INT32 position = (INT32)max(sides[line->sidenum[0]].midtexture, 0);
2417 UINT32 prefadems = (UINT32)max(sides[line->sidenum[0]].textureoffset >> FRACBITS, 0);
2418 UINT32 postfadems = (UINT32)max(sides[line->sidenum[0]].rowoffset >> FRACBITS, 0);
2419 UINT8 fadetarget = (UINT8)max((line->sidenum[1] != 0xffff) ? sides[line->sidenum[1]].textureoffset >> FRACBITS : 0, 0);
2420 INT16 fadesource = (INT16)max((line->sidenum[1] != 0xffff) ? sides[line->sidenum[1]].rowoffset >> FRACBITS : -1, -1);
2421
2422 // Seek offset from current song position
2423 if (line->flags & ML_EFFECT1)
2424 {
2425 // adjust for loop point if subtracting
2426 if (position < 0 && S_GetMusicLength() &&
2427 S_GetMusicPosition() > S_GetMusicLoopPoint() &&
2428 S_GetMusicPosition() + position < S_GetMusicLoopPoint())
2429 position = max(S_GetMusicLength() - (S_GetMusicLoopPoint() - (S_GetMusicPosition() + position)), 0);
2430 else
2431 position = max(S_GetMusicPosition() + position, 0);
2432 }
2433
2434 // Fade current music to target volume (if music won't be changed)
2435 if ((line->flags & ML_EFFECT2) && fadetarget && musicsame)
2436 {
2437 // 0 fadesource means fade from current volume.
2438 // meaning that we can't specify volume 0 as the source volume -- this starts at 1.
2439 if (!fadesource)
2440 fadesource = -1;
2441
2442 if (!postfadems)
2443 S_SetInternalMusicVolume(fadetarget);
2444 else
2445 S_FadeMusicFromVolume(fadetarget, fadesource, postfadems);
2446
2447 if (position)
2448 S_SetMusicPosition(position);
2449 }
2450 // Change the music and apply position/fade operations
2451 else
2452 {
2453 strncpy(mapmusname, sides[line->sidenum[0]].text, 7);
2454 mapmusname[6] = 0;
2455
2456 mapmusflags = tracknum & MUSIC_TRACKMASK;
2457 if (!(line->flags & ML_BLOCKMONSTERS))
2458 mapmusflags |= MUSIC_RELOADRESET;
2459 if (line->flags & ML_BOUNCY)
2460 mapmusflags |= MUSIC_FORCERESET;
2461
2462 mapmusposition = position;
2463
2464 S_ChangeMusicEx(mapmusname, mapmusflags, !(line->flags & ML_EFFECT4), position,
2465 !(line->flags & ML_EFFECT2) ? prefadems : 0,
2466 !(line->flags & ML_EFFECT2) ? postfadems : 0);
2467
2468 if ((line->flags & ML_EFFECT2) && fadetarget)
2469 {
2470 if (!postfadems)
2471 S_SetInternalMusicVolume(fadetarget);
2472 else
2473 S_FadeMusicFromVolume(fadetarget, fadesource, postfadems);
2474 }
2475 }
2476
2477 // Except, you can use the ML_BLOCKMONSTERS flag to change this behavior.
2478 // if (mapmusflags & MUSIC_RELOADRESET) then it will reset the music in G_PlayerReborn.
2479 }
2480 break;
2481
2482 case 414: // Play SFX
2483 {
2484 INT32 sfxnum;
2485
2486 sfxnum = sides[line->sidenum[0]].toptexture;
2487
2488 if (sfxnum == sfx_None)
2489 return; // Do nothing!
2490 if (sfxnum < sfx_None || sfxnum >= NUMSFX)
2491 {
2492 CONS_Debug(DBG_GAMELOGIC, "Line type 414 Executor: sfx number %d is invalid!\n", sfxnum);
2493 return;
2494 }
2495
2496 if (tag != 0) // Do special stuff only if a non-zero linedef tag is set
2497 {
2498 // Play sounds from tagged sectors' origins.
2499 if (line->flags & ML_EFFECT5) // Repeat Midtexture
2500 {
2501 // Additionally play the sound from tagged sectors' soundorgs
2502 sector_t *sec;
2503
2504 TAG_ITER_SECTORS(0, tag, secnum)
2505 {
2506 sec = §ors[secnum];
2507 S_StartSound(&sec->soundorg, sfxnum);
2508 }
2509 }
2510
2511 // Play the sound without origin for anyone, as long as they're inside tagged areas.
2512 else
2513 {
2514 UINT8 i = 0;
2515 mobj_t* camobj = players[displayplayer].mo;
2516 ffloor_t *rover;
2517 boolean foundit = false;
2518
2519 for (i = 0; i < 2; camobj = players[secondarydisplayplayer].mo, i++)
2520 {
2521 if (!camobj)
2522 continue;
2523
2524 if (foundit || Tag_Find(&camobj->subsector->sector->tags, tag))
2525 {
2526 foundit = true;
2527 break;
2528 }
2529
2530 // Only trigger if mobj is touching the tag
2531 for(rover = camobj->subsector->sector->ffloors; rover; rover = rover->next)
2532 {
2533 if (!Tag_Find(&rover->master->frontsector->tags, tag))
2534 continue;
2535
2536 if (camobj->z > P_GetSpecialTopZ(camobj, sectors + rover->secnum, camobj->subsector->sector))
2537 continue;
2538
2539 if (camobj->z + camobj->height < P_GetSpecialBottomZ(camobj, sectors + rover->secnum, camobj->subsector->sector))
2540 continue;
2541
2542 foundit = true;
2543 break;
2544 }
2545 }
2546
2547 if (foundit)
2548 S_StartSound(NULL, sfxnum);
2549 }
2550 }
2551 else
2552 {
2553 if (line->flags & ML_NOCLIMB)
2554 {
2555 // play the sound from nowhere, but only if display player triggered it
2556 if (mo && mo->player && (mo->player == &players[displayplayer] || mo->player == &players[secondarydisplayplayer]))
2557 S_StartSound(NULL, sfxnum);
2558 }
2559 else if (line->flags & ML_EFFECT4)
2560 {
2561 // play the sound from nowhere
2562 S_StartSound(NULL, sfxnum);
2563 }
2564 else if (line->flags & ML_BLOCKMONSTERS)
2565 {
2566 // play the sound from calling sector's soundorg
2567 if (callsec)
2568 S_StartSound(&callsec->soundorg, sfxnum);
2569 else if (mo)
2570 S_StartSound(&mo->subsector->sector->soundorg, sfxnum);
2571 }
2572 else if (mo)
2573 {
2574 // play the sound from mobj that triggered it
2575 S_StartSound(mo, sfxnum);
2576 }
2577 }
2578 }
2579 break;
2580
2581 case 415: // Run a script
2582 if (cv_runscripts.value)
2583 {
2584 INT32 scrnum;
2585 lumpnum_t lumpnum;
2586 char newname[9];
2587
2588 strcpy(newname, G_BuildMapName(gamemap));
2589 newname[0] = 'S';
2590 newname[1] = 'C';
2591 newname[2] = 'R';
2592
2593 scrnum = sides[line->sidenum[0]].textureoffset>>FRACBITS;
2594 if (scrnum < 0 || scrnum > 999)
2595 {
2596 scrnum = 0;
2597 newname[5] = newname[6] = newname[7] = '0';
2598 }
2599 else
2600 {
2601 newname[5] = (char)('0' + (char)((scrnum/100)));
2602 newname[6] = (char)('0' + (char)((scrnum%100)/10));
2603 newname[7] = (char)('0' + (char)(scrnum%10));
2604 }
2605 newname[8] = '\0';
2606
2607 lumpnum = W_CheckNumForName(newname);
2608
2609 if (lumpnum == LUMPERROR || W_LumpLength(lumpnum) == 0)
2610 {
2611 CONS_Debug(DBG_SETUP, "SOC Error: script lump %s not found/not valid.\n", newname);
2612 }
2613 else
2614 COM_BufInsertText(W_CacheLumpNum(lumpnum, PU_CACHE));
2615 }
2616 break;
2617
2618 case 416: // Spawn adjustable fire flicker
2619 TAG_ITER_SECTORS(0, tag, secnum)
2620 {
2621 if (line->flags & ML_NOCLIMB && line->backsector)
2622 {
2623 // Use front sector for min light level, back sector for max.
2624 // This is tricky because P_SpawnAdjustableFireFlicker expects
2625 // the maxsector (second argument) to also be the target
2626 // sector, so we have to do some light level twiddling.
2627 fireflicker_t *flick;
2628 INT16 reallightlevel = sectors[secnum].lightlevel;
2629 sectors[secnum].lightlevel = line->backsector->lightlevel;
2630
2631 flick = P_SpawnAdjustableFireFlicker(line->frontsector, §ors[secnum],
2632 P_AproxDistance(line->dx, line->dy)>>FRACBITS);
2633
2634 // Make sure the starting light level is in range.
2635 if (reallightlevel < flick->minlight)
2636 reallightlevel = (INT16)flick->minlight;
2637 else if (reallightlevel > flick->maxlight)
2638 reallightlevel = (INT16)flick->maxlight;
2639
2640 sectors[secnum].lightlevel = reallightlevel;
2641 }
2642 else
2643 {
2644 // Use front sector for min, target sector for max,
2645 // the same way linetype 61 does it.
2646 P_SpawnAdjustableFireFlicker(line->frontsector, §ors[secnum],
2647 P_AproxDistance(line->dx, line->dy)>>FRACBITS);
2648 }
2649 }
2650 break;
2651
2652 case 417: // Spawn adjustable glowing light
2653 TAG_ITER_SECTORS(0, tag, secnum)
2654 {
2655 if (line->flags & ML_NOCLIMB && line->backsector)
2656 {
2657 // Use front sector for min light level, back sector for max.
2658 // This is tricky because P_SpawnAdjustableGlowingLight expects
2659 // the maxsector (second argument) to also be the target
2660 // sector, so we have to do some light level twiddling.
2661 glow_t *glow;
2662 INT16 reallightlevel = sectors[secnum].lightlevel;
2663 sectors[secnum].lightlevel = line->backsector->lightlevel;
2664
2665 glow = P_SpawnAdjustableGlowingLight(line->frontsector, §ors[secnum],
2666 P_AproxDistance(line->dx, line->dy)>>FRACBITS);
2667
2668 // Make sure the starting light level is in range.
2669 if (reallightlevel < glow->minlight)
2670 reallightlevel = (INT16)glow->minlight;
2671 else if (reallightlevel > glow->maxlight)
2672 reallightlevel = (INT16)glow->maxlight;
2673
2674 sectors[secnum].lightlevel = reallightlevel;
2675 }
2676 else
2677 {
2678 // Use front sector for min, target sector for max,
2679 // the same way linetype 602 does it.
2680 P_SpawnAdjustableGlowingLight(line->frontsector, §ors[secnum],
2681 P_AproxDistance(line->dx, line->dy)>>FRACBITS);
2682 }
2683 }
2684 break;
2685
2686 case 418: // Spawn adjustable strobe flash (unsynchronized)
2687 TAG_ITER_SECTORS(0, tag, secnum)
2688 {
2689 if (line->flags & ML_NOCLIMB && line->backsector)
2690 {
2691 // Use front sector for min light level, back sector for max.
2692 // This is tricky because P_SpawnAdjustableGlowingLight expects
2693 // the maxsector (second argument) to also be the target
2694 // sector, so we have to do some light level twiddling.
2695 strobe_t *flash;
2696 INT16 reallightlevel = sectors[secnum].lightlevel;
2697 sectors[secnum].lightlevel = line->backsector->lightlevel;
2698
2699 flash = P_SpawnAdjustableStrobeFlash(line->frontsector, §ors[secnum],
2700 abs(line->dx)>>FRACBITS, abs(line->dy)>>FRACBITS, false);
2701
2702 // Make sure the starting light level is in range.
2703 if (reallightlevel < flash->minlight)
2704 reallightlevel = (INT16)flash->minlight;
2705 else if (reallightlevel > flash->maxlight)
2706 reallightlevel = (INT16)flash->maxlight;
2707
2708 sectors[secnum].lightlevel = reallightlevel;
2709 }
2710 else
2711 {
2712 // Use front sector for min, target sector for max,
2713 // the same way linetype 602 does it.
2714 P_SpawnAdjustableStrobeFlash(line->frontsector, §ors[secnum],
2715 abs(line->dx)>>FRACBITS, abs(line->dy)>>FRACBITS, false);
2716 }
2717 }
2718 break;
2719
2720 case 419: // Spawn adjustable strobe flash (synchronized)
2721 TAG_ITER_SECTORS(0, tag, secnum)
2722 {
2723 if (line->flags & ML_NOCLIMB && line->backsector)
2724 {
2725 // Use front sector for min light level, back sector for max.
2726 // This is tricky because P_SpawnAdjustableGlowingLight expects
2727 // the maxsector (second argument) to also be the target
2728 // sector, so we have to do some light level twiddling.
2729 strobe_t *flash;
2730 INT16 reallightlevel = sectors[secnum].lightlevel;
2731 sectors[secnum].lightlevel = line->backsector->lightlevel;
2732
2733 flash = P_SpawnAdjustableStrobeFlash(line->frontsector, §ors[secnum],
2734 abs(line->dx)>>FRACBITS, abs(line->dy)>>FRACBITS, true);
2735
2736 // Make sure the starting light level is in range.
2737 if (reallightlevel < flash->minlight)
2738 reallightlevel = (INT16)flash->minlight;
2739 else if (reallightlevel > flash->maxlight)
2740 reallightlevel = (INT16)flash->maxlight;
2741
2742 sectors[secnum].lightlevel = reallightlevel;
2743 }
2744 else
2745 {
2746 // Use front sector for min, target sector for max,
2747 // the same way linetype 602 does it.
2748 P_SpawnAdjustableStrobeFlash(line->frontsector, §ors[secnum],
2749 abs(line->dx)>>FRACBITS, abs(line->dy)>>FRACBITS, true);
2750 }
2751 }
2752 break;
2753
2754 case 420: // Fade light levels in tagged sectors to new value
2755 P_FadeLight(tag,
2756 (line->flags & ML_DONTPEGBOTTOM) ? max(sides[line->sidenum[0]].textureoffset>>FRACBITS, 0) : line->frontsector->lightlevel,
2757 // failsafe: if user specifies Back Y Offset and NOT Front Y Offset, use the Back Offset
2758 // to be consistent with other light and fade specials
2759 (line->flags & ML_DONTPEGBOTTOM) ?
2760 ((line->sidenum[1] != 0xFFFF && !(sides[line->sidenum[0]].rowoffset>>FRACBITS)) ?
2761 max(min(sides[line->sidenum[1]].rowoffset>>FRACBITS, 255), 0)
2762 : max(min(sides[line->sidenum[0]].rowoffset>>FRACBITS, 255), 0))
2763 : abs(P_AproxDistance(line->dx, line->dy))>>FRACBITS,
2764 (line->flags & ML_EFFECT4),
2765 (line->flags & ML_EFFECT5));
2766 break;
2767
2768 case 421: // Stop lighting effect in tagged sectors
2769 TAG_ITER_SECTORS(0, tag, secnum)
2770 if (sectors[secnum].lightingdata)
2771 {
2772 P_RemoveThinker(&((elevator_t *)sectors[secnum].lightingdata)->thinker);
2773 sectors[secnum].lightingdata = NULL;
2774 }
2775 break;
2776
2777 case 422: // Cut away to another view
2778 {
2779 mobj_t *altview;
2780
2781 if ((!mo || !mo->player) && !titlemapinaction) // only players have views, and title screens
2782 return;
2783
2784 if ((secnum = Tag_Iterate_Sectors(tag, 0)) < 0)
2785 return;
2786
2787 altview = P_GetObjectTypeInSectorNum(MT_ALTVIEWMAN, secnum);
2788 if (!altview)
2789 return;
2790
2791 // If titlemap, set the camera ref for title's thinker
2792 // This is not revoked until overwritten; awayviewtics is ignored
2793 if (titlemapinaction)
2794 titlemapcameraref = altview;
2795 else
2796 {
2797 P_SetTarget(&mo->player->awayviewmobj, altview);
2798 mo->player->awayviewtics = P_AproxDistance(line->dx, line->dy)>>FRACBITS;
2799 }
2800
2801
2802 if (line->flags & ML_NOCLIMB) // lets you specify a vertical angle
2803 {
2804 INT32 aim;
2805
2806 aim = sides[line->sidenum[0]].textureoffset>>FRACBITS;
2807 aim = (aim + 360) % 360;
2808 aim *= (ANGLE_90>>8);
2809 aim /= 90;
2810 aim <<= 8;
2811 if (titlemapinaction)
2812 titlemapcameraref->cusval = (angle_t)aim;
2813 else
2814 mo->player->awayviewaiming = (angle_t)aim;
2815 }
2816 else
2817 {
2818 // straight ahead
2819 if (!titlemapinaction)
2820 mo->player->awayviewaiming = 0;
2821 // don't do cusval cause that's annoying
2822 }
2823 }
2824 break;
2825
2826 case 423: // Change Sky
2827 if ((mo && mo->player && P_IsLocalPlayer(mo->player)) || (line->flags & ML_NOCLIMB))
2828 P_SetupLevelSky(sides[line->sidenum[0]].textureoffset>>FRACBITS, (line->flags & ML_NOCLIMB));
2829 break;
2830
2831 case 424: // Change Weather
2832 if (line->flags & ML_NOCLIMB)
2833 {
2834 globalweather = (UINT8)(sides[line->sidenum[0]].textureoffset>>FRACBITS);
2835 P_SwitchWeather(globalweather);
2836 }
2837 else if (mo && mo->player && P_IsLocalPlayer(mo->player))
2838 P_SwitchWeather(sides[line->sidenum[0]].textureoffset>>FRACBITS);
2839 break;
2840
2841 case 425: // Calls P_SetMobjState on calling mobj
2842 if (mo && !mo->player)
2843 P_SetMobjState(mo, sides[line->sidenum[0]].toptexture); //P_AproxDistance(line->dx, line->dy)>>FRACBITS);
2844 break;
2845
2846 case 426: // Moves the mobj to its sector's soundorg and on the floor, and stops it
2847 if (!mo)
2848 return;
2849
2850 if (line->flags & ML_NOCLIMB)
2851 {
2852 P_UnsetThingPosition(mo);
2853 mo->x = mo->subsector->sector->soundorg.x;
2854 mo->y = mo->subsector->sector->soundorg.y;
2855 mo->z = mo->floorz;
2856 P_SetThingPosition(mo);
2857 }
2858
2859 mo->momx = mo->momy = mo->momz = 1;
2860 mo->pmomz = 0;
2861
2862 if (mo->player)
2863 {
2864 mo->player->rmomx = mo->player->rmomy = 1;
2865 mo->player->cmomx = mo->player->cmomy = 0;
2866 P_ResetPlayer(mo->player);
2867 P_SetPlayerMobjState(mo, S_PLAY_STND);
2868
2869 // Reset bot too.
2870 if (bot) {
2871 if (line->flags & ML_NOCLIMB)
2872 P_TeleportMove(bot, mo->x, mo->y, mo->z);
2873 bot->momx = bot->momy = bot->momz = 1;
2874 bot->pmomz = 0;
2875 bot->player->rmomx = bot->player->rmomy = 1;
2876 bot->player->cmomx = bot->player->cmomy = 0;
2877 P_ResetPlayer(bot->player);
2878 P_SetPlayerMobjState(bot, S_PLAY_STND);
2879 }
2880 }
2881 break;
2882
2883 case 427: // Awards points if the mobj is a player
2884 if (mo && mo->player)
2885 P_AddPlayerScore(mo->player, sides[line->sidenum[0]].textureoffset>>FRACBITS);
2886 break;
2887
2888 case 428: // Start floating platform movement
2889 EV_DoElevator(line, elevateContinuous, true);
2890 break;
2891
2892 case 429: // Crush Ceiling Down Once
2893 EV_DoCrush(line, crushCeilOnce);
2894 break;
2895
2896 case 430: // Crush Floor Up Once
2897 EV_DoFloor(line, crushFloorOnce);
2898 break;
2899
2900 case 431: // Crush Floor & Ceiling to middle Once
2901 EV_DoCrush(line, crushBothOnce);
2902 break;
2903
2904 case 432: // Enable 2D Mode (Disable if noclimb)
2905 if (mo && mo->player)
2906 {
2907 if (line->flags & ML_NOCLIMB)
2908 mo->flags2 &= ~MF2_TWOD;
2909 else
2910 mo->flags2 |= MF2_TWOD;
2911
2912 // Copy effect to bot if necessary
2913 // (Teleport them to you so they don't break it.)
2914 if (bot && (bot->flags2 & MF2_TWOD) != (mo->flags2 & MF2_TWOD)) {
2915 bot->flags2 = (bot->flags2 & ~MF2_TWOD) | (mo->flags2 & MF2_TWOD);
2916 P_TeleportMove(bot, mo->x, mo->y, mo->z);
2917 }
2918 }
2919 break;
2920
2921 case 433: // Flip gravity (Flop gravity if noclimb) Works on pushables, too!
2922 if (line->flags & ML_NOCLIMB)
2923 mo->flags2 &= ~MF2_OBJECTFLIP;
2924 else
2925 mo->flags2 |= MF2_OBJECTFLIP;
2926 if (bot)
2927 bot->flags2 = (bot->flags2 & ~MF2_OBJECTFLIP) | (mo->flags2 & MF2_OBJECTFLIP);
2928 break;
2929
2930 case 434: // Custom Power
2931 if (mo && mo->player)
2932 {
2933 mobj_t *dummy = P_SpawnMobj(mo->x, mo->y, mo->z, MT_NULL);
2934
2935 var1 = sides[line->sidenum[0]].toptexture; //(line->dx>>FRACBITS)-1;
2936
2937 if (line->sidenum[1] != 0xffff && line->flags & ML_BLOCKMONSTERS) // read power from back sidedef
2938 var2 = sides[line->sidenum[1]].toptexture;
2939 else if (line->flags & ML_NOCLIMB) // 'Infinite'
2940 var2 = UINT16_MAX;
2941 else
2942 var2 = sides[line->sidenum[0]].textureoffset>>FRACBITS;
2943
2944 P_SetTarget(&dummy->target, mo);
2945 A_CustomPower(dummy);
2946
2947 if (bot) {
2948 P_SetTarget(&dummy->target, bot);
2949 A_CustomPower(dummy);
2950 }
2951 P_RemoveMobj(dummy);
2952 }
2953 break;
2954
2955 case 435: // Change scroller direction
2956 {
2957 scroll_t *scroller;
2958 thinker_t *th;
2959
2960 for (th = thlist[THINK_MAIN].next; th != &thlist[THINK_MAIN]; th = th->next)
2961 {
2962 if (th->function.acp1 != (actionf_p1)T_Scroll)
2963 continue;
2964
2965 scroller = (scroll_t *)th;
2966 if (!Tag_Find(§ors[scroller->affectee].tags, tag))
2967 continue;
2968
2969 scroller->dx = FixedMul(line->dx>>SCROLL_SHIFT, CARRYFACTOR);
2970 scroller->dy = FixedMul(line->dy>>SCROLL_SHIFT, CARRYFACTOR);
2971 }
2972 }
2973 break;
2974
2975 case 436: // Shatter block remotely
2976 {
2977 INT16 sectag = (INT16)(sides[line->sidenum[0]].textureoffset>>FRACBITS);
2978 INT16 foftag = (INT16)(sides[line->sidenum[0]].rowoffset>>FRACBITS);
2979 sector_t *sec; // Sector that the FOF is visible in
2980 ffloor_t *rover; // FOF that we are going to crumble
2981 boolean foundrover = false; // for debug, "Can't find a FOF" message
2982
2983 TAG_ITER_SECTORS(0, sectag, secnum)
2984 {
2985 sec = sectors + secnum;
2986
2987 if (!sec->ffloors)
2988 {
2989 CONS_Debug(DBG_GAMELOGIC, "Line type 436 Executor: Target sector #%d has no FOFs.\n", secnum);
2990 return;
2991 }
2992
2993 for (rover = sec->ffloors; rover; rover = rover->next)
2994 {
2995 if (Tag_Find(&rover->master->frontsector->tags, foftag))
2996 {
2997 foundrover = true;
2998
2999 EV_CrumbleChain(sec, rover);
3000 }
3001 }
3002
3003 if (!foundrover)
3004 {
3005 CONS_Debug(DBG_GAMELOGIC, "Line type 436 Executor: Can't find a FOF control sector with tag %d\n", foftag);
3006 return;
3007 }
3008 }
3009 }
3010 break;
3011
3012 case 437: // Disable Player Controls
3013 if (mo && mo->player)
3014 {
3015 UINT16 fractime = (UINT16)(sides[line->sidenum[0]].textureoffset>>FRACBITS);
3016 if (fractime < 1)
3017 fractime = 1; //instantly wears off upon leaving
3018 if (line->flags & ML_NOCLIMB)
3019 fractime |= 1<<15; //more crazy &ing, as if music stuff wasn't enough
3020 mo->player->powers[pw_nocontrol] = fractime;
3021 if (bot)
3022 bot->player->powers[pw_nocontrol] = fractime;
3023 }
3024 break;
3025
3026 case 438: // Set player scale
3027 if (mo)
3028 {
3029 mo->destscale = FixedDiv(P_AproxDistance(line->dx, line->dy), 100<<FRACBITS);
3030 if (mo->destscale < FRACUNIT/100)
3031 mo->destscale = FRACUNIT/100;
3032 if (mo->player && bot)
3033 bot->destscale = mo->destscale;
3034 }
3035 break;
3036
3037 case 439: // Set texture
3038 {
3039 size_t linenum;
3040 side_t *set = &sides[line->sidenum[0]], *this;
3041 boolean always = !(line->flags & ML_NOCLIMB); // If noclimb: Only change mid texture if mid texture already exists on tagged lines, etc.
3042
3043 for (linenum = 0; linenum < numlines; linenum++)
3044 {
3045 if (lines[linenum].special == 439)
3046 continue; // Don't override other set texture lines!
3047
3048 if (!Tag_Find(&lines[linenum].tags, tag))
3049 continue; // Find tagged lines
3050
3051 // Front side
3052 this = &sides[lines[linenum].sidenum[0]];
3053 if (always || this->toptexture) this->toptexture = set->toptexture;
3054 if (always || this->midtexture) this->midtexture = set->midtexture;
3055 if (always || this->bottomtexture) this->bottomtexture = set->bottomtexture;
3056
3057 if (lines[linenum].sidenum[1] == 0xffff)
3058 continue; // One-sided stops here.
3059
3060 // Back side
3061 this = &sides[lines[linenum].sidenum[1]];
3062 if (always || this->toptexture) this->toptexture = set->toptexture;
3063 if (always || this->midtexture) this->midtexture = set->midtexture;
3064 if (always || this->bottomtexture) this->bottomtexture = set->bottomtexture;
3065 }
3066 }
3067 break;
3068
3069 case 440: // Play race countdown and start Metal Sonic
3070 if (!metalrecording && !metalplayback)
3071 G_DoPlayMetal();
3072 break;
3073
3074 case 441: // Trigger unlockable
3075 if ((!modifiedgame || savemoddata) && !(netgame || multiplayer))
3076 {
3077 INT32 trigid = (INT32)(sides[line->sidenum[0]].textureoffset>>FRACBITS);
3078
3079 if (trigid < 0 || trigid > 31) // limited by 32 bit variable
3080 CONS_Debug(DBG_GAMELOGIC, "Unlockable trigger (sidedef %hu): bad trigger ID %d\n", line->sidenum[0], trigid);
3081 else
3082 {
3083 unlocktriggers |= 1 << trigid;
3084
3085 // Unlocked something?
3086 if (M_UpdateUnlockablesAndExtraEmblems())
3087 {
3088 S_StartSound(NULL, sfx_s3k68);
3089 G_SaveGameData(); // only save if unlocked something
3090 }
3091 }
3092 }
3093
3094 // Execute one time only
3095 line->special = 0;
3096 break;
3097
3098 case 442: // Calls P_SetMobjState on mobjs of a given type in the tagged sectors
3099 {
3100 const mobjtype_t type = (mobjtype_t)sides[line->sidenum[0]].toptexture;
3101 statenum_t state = NUMSTATES;
3102 sector_t *sec;
3103 mobj_t *thing;
3104
3105 if (line->sidenum[1] != 0xffff)
3106 state = (statenum_t)sides[line->sidenum[1]].toptexture;
3107
3108 TAG_ITER_SECTORS(0, tag, secnum)
3109 {
3110 boolean tryagain;
3111 sec = sectors + secnum;
3112 do {
3113 tryagain = false;
3114 for (thing = sec->thinglist; thing; thing = thing->snext)
3115 if (thing->type == type)
3116 {
3117 if (state != NUMSTATES)
3118 {
3119 if (!P_SetMobjState(thing, state)) // set state to specific state
3120 { // mobj was removed
3121 tryagain = true; // snext is corrupt, we'll have to start over.
3122 break;
3123 }
3124 }
3125 else if (!P_SetMobjState(thing, thing->state->nextstate)) // set state to nextstate
3126 { // mobj was removed
3127 tryagain = true; // snext is corrupt, we'll have to start over.
3128 break;
3129 }
3130 }
3131 } while (tryagain);
3132 }
3133 break;
3134 }
3135
3136 case 443: // Calls a named Lua function
3137 if (line->stringargs[0])
3138 LUAh_LinedefExecute(line, mo, callsec);
3139 else
3140 CONS_Alert(CONS_WARNING, "Linedef %s is missing the hook name of the Lua function to call! (This should be given in arg0str)\n", sizeu1(line-lines));
3141 break;
3142
3143 case 444: // Earthquake camera
3144 {
3145 quake.intensity = sides[line->sidenum[0]].textureoffset;
3146 quake.radius = sides[line->sidenum[0]].rowoffset;
3147 quake.time = P_AproxDistance(line->dx, line->dy)>>FRACBITS;
3148
3149 quake.epicenter = NULL; /// \todo
3150
3151 // reasonable defaults.
3152 if (!quake.intensity)
3153 quake.intensity = 8<<FRACBITS;
3154 if (!quake.radius)
3155 quake.radius = 512<<FRACBITS;
3156 break;
3157 }
3158
3159 case 445: // Force block disappear remotely (reappear if noclimb)
3160 {
3161 INT16 sectag = (INT16)(sides[line->sidenum[0]].textureoffset>>FRACBITS);
3162 INT16 foftag = (INT16)(sides[line->sidenum[0]].rowoffset>>FRACBITS);
3163 sector_t *sec; // Sector that the FOF is visible (or not visible) in
3164 ffloor_t *rover; // FOF to vanish/un-vanish
3165 boolean foundrover = false; // for debug, "Can't find a FOF" message
3166 ffloortype_e oldflags; // store FOF's old flags
3167
3168 TAG_ITER_SECTORS(0, sectag, secnum)
3169 {
3170 sec = sectors + secnum;
3171
3172 if (!sec->ffloors)
3173 {
3174 CONS_Debug(DBG_GAMELOGIC, "Line type 445 Executor: Target sector #%d has no FOFs.\n", secnum);
3175 return;
3176 }
3177
3178 for (rover = sec->ffloors; rover; rover = rover->next)
3179 {
3180 if (Tag_Find(&rover->master->frontsector->tags, foftag))
3181 {
3182 foundrover = true;
3183
3184 oldflags = rover->flags;
3185
3186 // Abracadabra!
3187 if (line->flags & ML_NOCLIMB)
3188 rover->flags |= FF_EXISTS;
3189 else
3190 rover->flags &= ~FF_EXISTS;
3191
3192 // if flags changed, reset sector's light list
3193 if (rover->flags != oldflags)
3194 {
3195 sec->moved = true;
3196 P_RecalcPrecipInSector(sec);
3197 }
3198 }
3199 }
3200
3201 if (!foundrover)
3202 {
3203 CONS_Debug(DBG_GAMELOGIC, "Line type 445 Executor: Can't find a FOF control sector with tag %d\n", foftag);
3204 return;
3205 }
3206 }
3207 }
3208 break;
3209
3210 case 446: // Make block fall remotely (acts like FF_CRUMBLE)
3211 {
3212 INT16 sectag = (INT16)(sides[line->sidenum[0]].textureoffset>>FRACBITS);
3213 INT16 foftag = (INT16)(sides[line->sidenum[0]].rowoffset>>FRACBITS);
3214 sector_t *sec; // Sector that the FOF is visible in
3215 ffloor_t *rover; // FOF that we are going to make fall down
3216 boolean foundrover = false; // for debug, "Can't find a FOF" message
3217 player_t *player = NULL; // player that caused FOF to fall
3218 boolean respawn = true; // should the fallen FOF respawn?
3219
3220 if (mo) // NULL check
3221 player = mo->player;
3222
3223 if (line->flags & ML_NOCLIMB) // don't respawn!
3224 respawn = false;
3225
3226 TAG_ITER_SECTORS(0, sectag, secnum)
3227 {
3228 sec = sectors + secnum;
3229
3230 if (!sec->ffloors)
3231 {
3232 CONS_Debug(DBG_GAMELOGIC, "Line type 446 Executor: Target sector #%d has no FOFs.\n", secnum);
3233 return;
3234 }
3235
3236 for (rover = sec->ffloors; rover; rover = rover->next)
3237 {
3238 if (Tag_Find(&rover->master->frontsector->tags, foftag))
3239 {
3240 foundrover = true;
3241
3242 if (line->flags & ML_BLOCKMONSTERS) // FOF flags determine respawn ability instead?
3243 respawn = !(rover->flags & FF_NORETURN) ^ !!(line->flags & ML_NOCLIMB); // no climb inverts
3244
3245 EV_StartCrumble(rover->master->frontsector, rover, (rover->flags & FF_FLOATBOB), player, rover->alpha, respawn);
3246 }
3247 }
3248
3249 if (!foundrover)
3250 {
3251 CONS_Debug(DBG_GAMELOGIC, "Line type 446 Executor: Can't find a FOF control sector with tag %d\n", foftag);
3252 return;
3253 }
3254 }
3255 }
3256 break;
3257
3258 case 447: // Change colormap of tagged sectors!
3259 // Basically this special applies a colormap to the tagged sectors, just like 606 (the colormap linedef)
3260 // Except it is activated by linedef executor, not level load
3261 // This could even override existing colormaps I believe
3262 // -- Monster Iestyn 14/06/18
3263 {
3264 extracolormap_t *source;
3265 if (!udmf)
3266 source = sides[line->sidenum[0]].colormap_data;
3267 else
3268 {
3269 if (!line->args[1])
3270 source = line->frontsector->extra_colormap;
3271 else
3272 {
3273 INT32 sourcesec = Tag_Iterate_Sectors(line->args[1], 0);
3274 if (sourcesec == -1)
3275 {
3276 CONS_Debug(DBG_GAMELOGIC, "Line type 447 Executor: Can't find sector with source colormap (tag %d)!\n", line->args[1]);
3277 return;
3278 }
3279 source = sectors[sourcesec].extra_colormap;
3280 }
3281 }
3282 TAG_ITER_SECTORS(0, line->args[0], secnum)
3283 {
3284 if (sectors[secnum].colormap_protected)
3285 continue;
3286
3287 P_ResetColormapFader(§ors[secnum]);
3288
3289 if (line->args[2] & TMCF_RELATIVE)
3290 {
3291 extracolormap_t *target = (!udmf && (line->flags & ML_TFERLINE) && line->sidenum[1] != 0xFFFF) ?
3292 sides[line->sidenum[1]].colormap_data : sectors[secnum].extra_colormap; // use back colormap instead of target sector
3293
3294 extracolormap_t *exc = R_AddColormaps(
3295 target,
3296 source,
3297 line->args[2] & TMCF_SUBLIGHTR,
3298 line->args[2] & TMCF_SUBLIGHTG,
3299 line->args[2] & TMCF_SUBLIGHTB,
3300 line->args[2] & TMCF_SUBLIGHTA,
3301 line->args[2] & TMCF_SUBFADER,
3302 line->args[2] & TMCF_SUBFADEG,
3303 line->args[2] & TMCF_SUBFADEB,
3304 line->args[2] & TMCF_SUBFADEA,
3305 line->args[2] & TMCF_SUBFADESTART,
3306 line->args[2] & TMCF_SUBFADEEND,
3307 line->args[2] & TMCF_IGNOREFLAGS,
3308 false);
3309
3310 if (!(sectors[secnum].extra_colormap = R_GetColormapFromList(exc)))
3311 {
3312 exc->colormap = R_CreateLightTable(exc);
3313 R_AddColormapToList(exc);
3314 sectors[secnum].extra_colormap = exc;
3315 }
3316 else
3317 Z_Free(exc);
3318 }
3319 else
3320 sectors[secnum].extra_colormap = source;
3321 }
3322 break;
3323 }
3324 case 448: // Change skybox viewpoint/centerpoint
3325 if ((mo && mo->player && P_IsLocalPlayer(mo->player)) || (line->flags & ML_NOCLIMB))
3326 {
3327 INT32 viewid = sides[line->sidenum[0]].textureoffset>>FRACBITS;
3328 INT32 centerid = sides[line->sidenum[0]].rowoffset>>FRACBITS;
3329
3330 if ((line->flags & (ML_EFFECT4|ML_BLOCKMONSTERS)) == ML_EFFECT4) // Solid Midtexture is on but Block Enemies is off?
3331 {
3332 CONS_Alert(CONS_WARNING,
3333 M_GetText("Skybox switch linedef (tag %d) doesn't have anything to do.\nConsider changing the linedef's flag configuration or removing it entirely.\n"),
3334 tag);
3335 }
3336 else
3337 {
3338 // set viewpoint mobj
3339 if (!(line->flags & ML_EFFECT4)) // Solid Midtexture turns off viewpoint setting
3340 {
3341 if (viewid >= 0 && viewid < 16)
3342 skyboxmo[0] = skyboxviewpnts[viewid];
3343 else
3344 skyboxmo[0] = NULL;
3345 }
3346
3347 // set centerpoint mobj
3348 if (line->flags & ML_BLOCKMONSTERS) // Block Enemies turns ON centerpoint setting
3349 {
3350 if (centerid >= 0 && centerid < 16)
3351 skyboxmo[1] = skyboxcenterpnts[centerid];
3352 else
3353 skyboxmo[1] = NULL;
3354 }
3355 }
3356
3357 CONS_Debug(DBG_GAMELOGIC, "Line type 448 Executor: viewid = %d, centerid = %d, viewpoint? = %s, centerpoint? = %s\n",
3358 viewid,
3359 centerid,
3360 ((line->flags & ML_EFFECT4) ? "no" : "yes"),
3361 ((line->flags & ML_BLOCKMONSTERS) ? "yes" : "no"));
3362 }
3363 break;
3364
3365 case 449: // Enable bosses with parameter
3366 {
3367 INT32 bossid = sides[line->sidenum[0]].textureoffset>>FRACBITS;
3368 if (bossid & ~15) // if any bits other than first 16 are set
3369 {
3370 CONS_Alert(CONS_WARNING,
3371 M_GetText("Boss enable linedef (tag %d) has an invalid texture x offset.\nConsider changing it or removing it entirely.\n"),
3372 tag);
3373 break;
3374 }
3375 if (line->flags & ML_NOCLIMB)
3376 {
3377 bossdisabled |= (1<<bossid);
3378 CONS_Debug(DBG_GAMELOGIC, "Line type 449 Executor: bossid disabled = %d", bossid);
3379 }
3380 else
3381 {
3382 bossdisabled &= ~(1<<bossid);
3383 CONS_Debug(DBG_GAMELOGIC, "Line type 449 Executor: bossid enabled = %d", bossid);
3384 }
3385 break;
3386 }
3387
3388 case 450: // Execute Linedef Executor - for recursion
3389 P_LinedefExecute(tag, mo, NULL);
3390 break;
3391
3392 case 451: // Execute Random Linedef Executor
3393 {
3394 INT32 rvalue1 = sides[line->sidenum[0]].textureoffset>>FRACBITS;
3395 INT32 rvalue2 = sides[line->sidenum[0]].rowoffset>>FRACBITS;
3396 INT32 result;
3397
3398 if (rvalue1 <= rvalue2)
3399 result = P_RandomRange(rvalue1, rvalue2);
3400 else
3401 result = P_RandomRange(rvalue2, rvalue1);
3402
3403 P_LinedefExecute((INT16)result, mo, NULL);
3404 break;
3405 }
3406
3407 case 452: // Set FOF alpha
3408 {
3409 INT16 destvalue = line->sidenum[1] != 0xffff ?
3410 (INT16)(sides[line->sidenum[1]].textureoffset>>FRACBITS) : (INT16)(P_AproxDistance(line->dx, line->dy)>>FRACBITS);
3411 INT16 sectag = (INT16)(sides[line->sidenum[0]].textureoffset>>FRACBITS);
3412 INT16 foftag = (INT16)(sides[line->sidenum[0]].rowoffset>>FRACBITS);
3413 sector_t *sec; // Sector that the FOF is visible in
3414 ffloor_t *rover; // FOF that we are going to operate
3415 boolean foundrover = false; // for debug, "Can't find a FOF" message
3416
3417 TAG_ITER_SECTORS(0, sectag, secnum)
3418 {
3419 sec = sectors + secnum;
3420
3421 if (!sec->ffloors)
3422 {
3423 CONS_Debug(DBG_GAMELOGIC, "Line type 452 Executor: Target sector #%d has no FOFs.\n", secnum);
3424 return;
3425 }
3426
3427 for (rover = sec->ffloors; rover; rover = rover->next)
3428 {
3429 if (Tag_Find(&rover->master->frontsector->tags, foftag))
3430 {
3431 foundrover = true;
3432
3433 // If fading an invisible FOF whose render flags we did not yet set,
3434 // initialize its alpha to 1
3435 // for relative alpha calc
3436 if (!(line->flags & ML_NOCLIMB) && // do translucent
3437 (rover->spawnflags & FF_NOSHADE) && // do not include light blocks, which don't set FF_NOSHADE
3438 !(rover->spawnflags & FF_RENDERSIDES) &&
3439 !(rover->spawnflags & FF_RENDERPLANES) &&
3440 !(rover->flags & FF_RENDERALL))
3441 rover->alpha = 1;
3442
3443 P_RemoveFakeFloorFader(rover);
3444 P_FadeFakeFloor(rover,
3445 rover->alpha,
3446 max(1, min(256, (line->flags & ML_EFFECT3) ? rover->alpha + destvalue : destvalue)),
3447 0, // set alpha immediately
3448 false, NULL, // tic-based logic
3449 false, // do not handle FF_EXISTS
3450 !(line->flags & ML_NOCLIMB), // handle FF_TRANSLUCENT
3451 false, // do not handle lighting
3452 false, // do not handle colormap
3453 false, // do not handle collision
3454 false, // do not do ghost fade (no collision during fade)
3455 true); // use exact alpha values (for opengl)
3456 }
3457 }
3458
3459 if (!foundrover)
3460 {
3461 CONS_Debug(DBG_GAMELOGIC, "Line type 452 Executor: Can't find a FOF control sector with tag %d\n", foftag);
3462 return;
3463 }
3464 }
3465 break;
3466 }
3467
3468 case 453: // Fade FOF
3469 {
3470 INT16 destvalue = line->sidenum[1] != 0xffff ?
3471 (INT16)(sides[line->sidenum[1]].textureoffset>>FRACBITS) : (INT16)(line->dx>>FRACBITS);
3472 INT16 speed = line->sidenum[1] != 0xffff ?
3473 (INT16)(abs(sides[line->sidenum[1]].rowoffset>>FRACBITS)) : (INT16)(abs(line->dy)>>FRACBITS);
3474 INT16 sectag = (INT16)(sides[line->sidenum[0]].textureoffset>>FRACBITS);
3475 INT16 foftag = (INT16)(sides[line->sidenum[0]].rowoffset>>FRACBITS);
3476 sector_t *sec; // Sector that the FOF is visible in
3477 ffloor_t *rover; // FOF that we are going to operate
3478 boolean foundrover = false; // for debug, "Can't find a FOF" message
3479 size_t j = 0; // sec->ffloors is saved as ffloor #0, ss->ffloors->next is #1, etc
3480
3481 TAG_ITER_SECTORS(0, sectag, secnum)
3482 {
3483 sec = sectors + secnum;
3484
3485 if (!sec->ffloors)
3486 {
3487 CONS_Debug(DBG_GAMELOGIC, "Line type 453 Executor: Target sector #%d has no FOFs.\n", secnum);
3488 return;
3489 }
3490
3491 for (rover = sec->ffloors; rover; rover = rover->next)
3492 {
3493 if (Tag_Find(&rover->master->frontsector->tags, foftag))
3494 {
3495 foundrover = true;
3496
3497 // Prevent continuous execs from interfering on an existing fade
3498 if (!(line->flags & ML_EFFECT5)
3499 && rover->fadingdata)
3500 //&& ((fade_t*)rover->fadingdata)->timer > (ticbased ? 2 : speed*2))
3501 {
3502 CONS_Debug(DBG_GAMELOGIC, "Line type 453 Executor: Fade FOF thinker already exists, timer: %d\n", ((fade_t*)rover->fadingdata)->timer);
3503 continue;
3504 }
3505
3506 if (speed > 0)
3507 P_AddFakeFloorFader(rover, secnum, j,
3508 destvalue,
3509 speed,
3510 (line->flags & ML_EFFECT4), // tic-based logic
3511 (line->flags & ML_EFFECT3), // Relative destvalue
3512 !(line->flags & ML_BLOCKMONSTERS), // do not handle FF_EXISTS
3513 !(line->flags & ML_NOCLIMB), // do not handle FF_TRANSLUCENT
3514 !(line->flags & ML_EFFECT2), // do not handle lighting
3515 !(line->flags & ML_EFFECT2), // do not handle colormap (ran out of flags)
3516 !(line->flags & ML_BOUNCY), // do not handle collision
3517 (line->flags & ML_EFFECT1), // do ghost fade (no collision during fade)
3518 (line->flags & ML_TFERLINE)); // use exact alpha values (for opengl)
3519 else
3520 {
3521 // If fading an invisible FOF whose render flags we did not yet set,
3522 // initialize its alpha to 1
3523 // for relative alpha calc
3524 if (!(line->flags & ML_NOCLIMB) && // do translucent
3525 (rover->spawnflags & FF_NOSHADE) && // do not include light blocks, which don't set FF_NOSHADE
3526 !(rover->spawnflags & FF_RENDERSIDES) &&
3527 !(rover->spawnflags & FF_RENDERPLANES) &&
3528 !(rover->flags & FF_RENDERALL))
3529 rover->alpha = 1;
3530
3531 P_RemoveFakeFloorFader(rover);
3532 P_FadeFakeFloor(rover,
3533 rover->alpha,
3534 max(1, min(256, (line->flags & ML_EFFECT3) ? rover->alpha + destvalue : destvalue)),
3535 0, // set alpha immediately
3536 false, NULL, // tic-based logic
3537 !(line->flags & ML_BLOCKMONSTERS), // do not handle FF_EXISTS
3538 !(line->flags & ML_NOCLIMB), // do not handle FF_TRANSLUCENT
3539 !(line->flags & ML_EFFECT2), // do not handle lighting
3540 !(line->flags & ML_EFFECT2), // do not handle colormap (ran out of flags)
3541 !(line->flags & ML_BOUNCY), // do not handle collision
3542 (line->flags & ML_EFFECT1), // do ghost fade (no collision during fade)
3543 (line->flags & ML_TFERLINE)); // use exact alpha values (for opengl)
3544 }
3545 }
3546 j++;
3547 }
3548
3549 if (!foundrover)
3550 {
3551 CONS_Debug(DBG_GAMELOGIC, "Line type 453 Executor: Can't find a FOF control sector with tag %d\n", foftag);
3552 return;
3553 }
3554 }
3555 break;
3556 }
3557
3558 case 454: // Stop fading FOF
3559 {
3560 INT16 sectag = (INT16)(sides[line->sidenum[0]].textureoffset>>FRACBITS);
3561 INT16 foftag = (INT16)(sides[line->sidenum[0]].rowoffset>>FRACBITS);
3562 sector_t *sec; // Sector that the FOF is visible in
3563 ffloor_t *rover; // FOF that we are going to operate
3564 boolean foundrover = false; // for debug, "Can't find a FOF" message
3565
3566 TAG_ITER_SECTORS(0, sectag, secnum)
3567 {
3568 sec = sectors + secnum;
3569
3570 if (!sec->ffloors)
3571 {
3572 CONS_Debug(DBG_GAMELOGIC, "Line type 454 Executor: Target sector #%d has no FOFs.\n", secnum);
3573 return;
3574 }
3575
3576 for (rover = sec->ffloors; rover; rover = rover->next)
3577 {
3578 if (Tag_Find(&rover->master->frontsector->tags, foftag))
3579 {
3580 foundrover = true;
3581
3582 P_ResetFakeFloorFader(rover, NULL,
3583 !(line->flags & ML_BLOCKMONSTERS)); // do not finalize collision flags
3584 }
3585 }
3586
3587 if (!foundrover)
3588 {
3589 CONS_Debug(DBG_GAMELOGIC, "Line type 454 Executor: Can't find a FOF control sector with tag %d\n", foftag);
3590 return;
3591 }
3592 }
3593 break;
3594 }
3595
3596 case 455: // Fade colormap
3597 {
3598 extracolormap_t *dest;
3599 if (!udmf)
3600 dest = sides[line->sidenum[0]].colormap_data;
3601 else
3602 {
3603 if (!line->args[1])
3604 dest = line->frontsector->extra_colormap;
3605 else
3606 {
3607 INT32 destsec = Tag_Iterate_Sectors(line->args[1], 0);
3608 if (destsec == -1)
3609 {
3610 CONS_Debug(DBG_GAMELOGIC, "Line type 455 Executor: Can't find sector with destination colormap (tag %d)!\n", line->args[1]);
3611 return;
3612 }
3613 dest = sectors[destsec].extra_colormap;
3614 }
3615 }
3616
3617 TAG_ITER_SECTORS(0, line->args[0], secnum)
3618 {
3619 extracolormap_t *source_exc, *dest_exc, *exc;
3620
3621 if (sectors[secnum].colormap_protected)
3622 continue;
3623
3624 // Don't interrupt ongoing fade
3625 if (!(line->args[3] & TMCF_OVERRIDE)
3626 && sectors[secnum].fadecolormapdata)
3627 //&& ((fadecolormap_t*)sectors[secnum].fadecolormapdata)->timer > (ticbased ? 2 : speed*2))
3628 {
3629 CONS_Debug(DBG_GAMELOGIC, "Line type 455 Executor: Fade color thinker already exists, timer: %d\n", ((fadecolormap_t*)sectors[secnum].fadecolormapdata)->timer);
3630 continue;
3631 }
3632
3633 if (!udmf && (line->flags & ML_TFERLINE)) // use back colormap instead of target sector
3634 sectors[secnum].extra_colormap = (line->sidenum[1] != 0xFFFF) ?
3635 sides[line->sidenum[1]].colormap_data : NULL;
3636
3637 exc = sectors[secnum].extra_colormap;
3638
3639 if (!(line->args[3] & TMCF_FROMBLACK) // Override fade from default rgba
3640 && !R_CheckDefaultColormap(dest, true, false, false)
3641 && R_CheckDefaultColormap(exc, true, false, false))
3642 {
3643 exc = R_CopyColormap(exc, false);
3644 exc->rgba = R_GetRgbaRGB(dest->rgba) + R_PutRgbaA(R_GetRgbaA(exc->rgba));
3645 //exc->fadergba = R_GetRgbaRGB(dest->rgba) + R_PutRgbaA(R_GetRgbaA(exc->fadergba));
3646
3647 if (!(source_exc = R_GetColormapFromList(exc)))
3648 {
3649 exc->colormap = R_CreateLightTable(exc);
3650 R_AddColormapToList(exc);
3651 source_exc = exc;
3652 }
3653 else
3654 Z_Free(exc);
3655
3656 sectors[secnum].extra_colormap = source_exc;
3657 }
3658 else
3659 source_exc = exc ? exc : R_GetDefaultColormap();
3660
3661 if (line->args[3] & TMCF_RELATIVE)
3662 {
3663 exc = R_AddColormaps(
3664 source_exc,
3665 dest,
3666 line->args[3] & TMCF_SUBLIGHTR,
3667 line->args[3] & TMCF_SUBLIGHTG,
3668 line->args[3] & TMCF_SUBLIGHTB,
3669 line->args[3] & TMCF_SUBLIGHTA,
3670 line->args[3] & TMCF_SUBFADER,
3671 line->args[3] & TMCF_SUBFADEG,
3672 line->args[3] & TMCF_SUBFADEB,
3673 line->args[3] & TMCF_SUBFADEA,
3674 line->args[3] & TMCF_SUBFADESTART,
3675 line->args[3] & TMCF_SUBFADEEND,
3676 line->args[3] & TMCF_IGNOREFLAGS,
3677 false);
3678 }
3679 else
3680 exc = R_CopyColormap(dest, false);
3681
3682 if (!(dest_exc = R_GetColormapFromList(exc)))
3683 {
3684 exc->colormap = R_CreateLightTable(exc);
3685 R_AddColormapToList(exc);
3686 dest_exc = exc;
3687 }
3688 else
3689 Z_Free(exc);
3690
3691 Add_ColormapFader(§ors[secnum], source_exc, dest_exc, true, // tic-based timing
3692 line->args[2]);
3693 }
3694 break;
3695 }
3696 case 456: // Stop fade colormap
3697 TAG_ITER_SECTORS(0, line->args[0], secnum)
3698 P_ResetColormapFader(§ors[secnum]);
3699 break;
3700
3701 case 457: // Track mobj angle to point
3702 if (mo)
3703 {
3704 INT32 failureangle = FixedAngle((min(max(abs(sides[line->sidenum[0]].textureoffset>>FRACBITS), 0), 360))*FRACUNIT);
3705 INT32 failuredelay = abs(sides[line->sidenum[0]].rowoffset>>FRACBITS);
3706 INT32 failureexectag = line->sidenum[1] != 0xffff ?
3707 (INT32)(sides[line->sidenum[1]].textureoffset>>FRACBITS) : 0;
3708 boolean persist = (line->flags & ML_EFFECT2);
3709 mobj_t *anchormo;
3710
3711 if ((secnum = Tag_Iterate_Sectors(tag, 0)) < 0)
3712 return;
3713
3714 anchormo = P_GetObjectTypeInSectorNum(MT_ANGLEMAN, secnum);
3715 if (!anchormo)
3716 return;
3717
3718 mo->eflags |= MFE_TRACERANGLE;
3719 P_SetTarget(&mo->tracer, anchormo);
3720 mo->lastlook = persist; // don't disable behavior after first failure
3721 mo->extravalue1 = failureangle; // angle to exceed for failure state
3722 mo->extravalue2 = failureexectag; // exec tag for failure state (angle is not within range)
3723 mo->cusval = mo->cvmem = failuredelay; // cusval = tics to allow failure before line trigger; cvmem = decrement timer
3724 }
3725 break;
3726
3727 case 458: // Stop tracking mobj angle to point
3728 if (mo && (mo->eflags & MFE_TRACERANGLE))
3729 {
3730 mo->eflags &= ~MFE_TRACERANGLE;
3731 P_SetTarget(&mo->tracer, NULL);
3732 mo->lastlook = mo->cvmem = mo->cusval = mo->extravalue1 = mo->extravalue2 = 0;
3733 }
3734 break;
3735
3736 case 459: // Control Text Prompt
3737 // console player only unless NOCLIMB is set
3738 if (mo && mo->player && P_IsLocalPlayer(mo->player) && (!bot || bot != mo))
3739 {
3740 INT32 promptnum = max(0, (sides[line->sidenum[0]].textureoffset>>FRACBITS)-1);
3741 INT32 pagenum = max(0, (sides[line->sidenum[0]].rowoffset>>FRACBITS)-1);
3742 INT32 postexectag = abs((line->sidenum[1] != 0xFFFF) ? sides[line->sidenum[1]].textureoffset>>FRACBITS : tag);
3743
3744 boolean closetextprompt = (line->flags & ML_BLOCKMONSTERS);
3745 //boolean allplayers = (line->flags & ML_NOCLIMB);
3746 boolean runpostexec = (line->flags & ML_EFFECT1);
3747 boolean blockcontrols = !(line->flags & ML_EFFECT2);
3748 boolean freezerealtime = !(line->flags & ML_EFFECT3);
3749 //boolean freezethinkers = (line->flags & ML_EFFECT4);
3750 boolean callbynamedtag = (line->flags & ML_TFERLINE);
3751
3752 if (closetextprompt)
3753 F_EndTextPrompt(false, false);
3754 else
3755 {
3756 if (callbynamedtag && sides[line->sidenum[0]].text && sides[line->sidenum[0]].text[0])
3757 F_GetPromptPageByNamedTag(sides[line->sidenum[0]].text, &promptnum, &pagenum);
3758 F_StartTextPrompt(promptnum, pagenum, mo, runpostexec ? postexectag : 0, blockcontrols, freezerealtime);
3759 }
3760 }
3761 break;
3762
3763 case 460: // Award rings
3764 {
3765 INT16 rings = (sides[line->sidenum[0]].textureoffset>>FRACBITS);
3766 INT32 delay = (sides[line->sidenum[0]].rowoffset>>FRACBITS);
3767 if (mo && mo->player)
3768 {
3769 if (delay <= 0 || !(leveltime % delay))
3770 P_GivePlayerRings(mo->player, rings);
3771 }
3772 }
3773 break;
3774
3775 case 461: // Spawns an object on the map based on texture offsets
3776 {
3777 const mobjtype_t type = (mobjtype_t)(sides[line->sidenum[0]].toptexture);
3778 mobj_t *mobj;
3779
3780 fixed_t x, y, z;
3781 x = sides[line->sidenum[0]].textureoffset;
3782 y = sides[line->sidenum[0]].rowoffset;
3783 z = line->frontsector->floorheight;
3784
3785 if (line->flags & ML_NOCLIMB) // If noclimb is set, spawn randomly within a range
3786 {
3787 if (line->sidenum[1] != 0xffff) // Make sure the linedef has a back side
3788 {
3789 x = P_RandomRange(sides[line->sidenum[0]].textureoffset>>FRACBITS, sides[line->sidenum[1]].textureoffset>>FRACBITS)<<FRACBITS;
3790 y = P_RandomRange(sides[line->sidenum[0]].rowoffset>>FRACBITS, sides[line->sidenum[1]].rowoffset>>FRACBITS)<<FRACBITS;
3791 z = P_RandomRange(line->frontsector->floorheight>>FRACBITS, line->frontsector->ceilingheight>>FRACBITS)<<FRACBITS;
3792 }
3793 else
3794 {
3795 CONS_Alert(CONS_WARNING,"Linedef Type %d - Spawn Object: Linedef is set for random range but has no back side.\n", line->special);
3796 break;
3797 }
3798 }
3799
3800 mobj = P_SpawnMobj(x, y, z, type);
3801 if (mobj)
3802 {
3803 if (line->flags & ML_EFFECT1)
3804 mobj->angle = R_PointToAngle2(line->v1->x, line->v1->y, line->v2->x, line->v2->y);
3805 CONS_Debug(DBG_GAMELOGIC, "Linedef Type %d - Spawn Object: %d spawned at (%d, %d, %d)\n", line->special, mobj->type, mobj->x>>FRACBITS, mobj->y>>FRACBITS, mobj->z>>FRACBITS); //TODO: Convert mobj->type to a string somehow.
3806 }
3807 else
3808 CONS_Alert(CONS_ERROR,"Linedef Type %d - Spawn Object: Object did not spawn!\n", line->special);
3809 }
3810 break;
3811
3812 case 462: // Stop clock (and end level in record attack)
3813 if (G_PlatformGametype())
3814 {
3815 stoppedclock = true;
3816 CONS_Debug(DBG_GAMELOGIC, "Clock stopped!\n");
3817 if (modeattacking)
3818 {
3819 UINT8 i;
3820 for (i = 0; i < MAXPLAYERS; i++)
3821 {
3822 if (!playeringame[i])
3823 continue;
3824 P_DoPlayerExit(&players[i]);
3825 }
3826 }
3827 }
3828 break;
3829
3830 case 463: // Dye object
3831 {
3832 INT32 color = sides[line->sidenum[0]].toptexture;
3833
3834 if (mo)
3835 {
3836 if (color < 0 || color >= numskincolors)
3837 return;
3838
3839 var1 = 0;
3840 var2 = color;
3841 A_Dye(mo);
3842 }
3843 }
3844 break;
3845
3846 case 464: // Trigger Egg Capsule
3847 {
3848 thinker_t *th;
3849 mobj_t *mo2;
3850
3851 // Find the center of the Eggtrap and release all the pretty animals!
3852 // The chimps are my friends.. heeheeheheehehee..... - LouisJM
3853 for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
3854 {
3855 if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
3856 continue;
3857
3858 mo2 = (mobj_t *)th;
3859
3860 if (mo2->type != MT_EGGTRAP)
3861 continue;
3862
3863 if (!mo2->spawnpoint)
3864 continue;
3865
3866 if (mo2->spawnpoint->angle != tag)
3867 continue;
3868
3869 P_KillMobj(mo2, NULL, mo, 0);
3870 }
3871
3872 if (!(line->flags & ML_NOCLIMB))
3873 {
3874 INT32 i;
3875
3876 // Mark all players with the time to exit thingy!
3877 for (i = 0; i < MAXPLAYERS; i++)
3878 {
3879 if (!playeringame[i])
3880 continue;
3881 P_DoPlayerExit(&players[i]);
3882 }
3883 }
3884 }
3885 break;
3886
3887 case 465: // Set linedef executor delay
3888 {
3889 INT32 linenum;
3890 TAG_ITER_DECLARECOUNTER(1);
3891
3892 if (!udmf)
3893 break;
3894
3895 TAG_ITER_LINES(1, line->args[0], linenum)
3896 {
3897 if (line->args[2])
3898 lines[linenum].executordelay += line->args[1];
3899 else
3900 lines[linenum].executordelay = line->args[1];
3901 }
3902 }
3903 break;
3904
3905 case 480: // Polyobj_DoorSlide
3906 case 481: // Polyobj_DoorSwing
3907 PolyDoor(line);
3908 break;
3909 case 482: // Polyobj_Move
3910 case 483: // Polyobj_OR_Move
3911 PolyMove(line);
3912 break;
3913 case 484: // Polyobj_RotateRight
3914 case 485: // Polyobj_OR_RotateRight
3915 case 486: // Polyobj_RotateLeft
3916 case 487: // Polyobj_OR_RotateLeft
3917 PolyRotate(line);
3918 break;
3919 case 488: // Polyobj_Waypoint
3920 PolyWaypoint(line);
3921 break;
3922 case 489:
3923 PolyInvisible(line);
3924 break;
3925 case 490:
3926 PolyVisible(line);
3927 break;
3928 case 491:
3929 PolyTranslucency(line);
3930 break;
3931 case 492:
3932 PolyFade(line);
3933 break;
3934
3935 default:
3936 break;
3937 }
3938 }
3939
3940 //
3941 // P_SetupSignExit
3942 //
3943 // Finds the exit sign in the current sector and
3944 // sets its target to the player who passed the map.
3945 //
P_SetupSignExit(player_t * player)3946 void P_SetupSignExit(player_t *player)
3947 {
3948 mobj_t *thing;
3949 msecnode_t *node = player->mo->subsector->sector->touching_thinglist; // things touching this sector
3950 thinker_t *think;
3951 INT32 numfound = 0;
3952
3953 for (; node; node = node->m_thinglist_next)
3954 {
3955 thing = node->m_thing;
3956 if (thing->type != MT_SIGN)
3957 continue;
3958
3959 if (!numfound
3960 && !(player->mo->target && player->mo->target->type == MT_SIGN)
3961 && !((gametyperules & GTR_FRIENDLY) && (netgame || multiplayer) && cv_exitmove.value))
3962 P_SetTarget(&player->mo->target, thing);
3963
3964 if (thing->state != &states[thing->info->spawnstate])
3965 continue;
3966
3967 P_SetTarget(&thing->target, player->mo);
3968 P_SetObjectMomZ(thing, 12*FRACUNIT, false);
3969 P_SetMobjState(thing, S_SIGNSPIN1);
3970 if (thing->info->seesound)
3971 S_StartSound(thing, thing->info->seesound);
3972
3973 ++numfound;
3974 }
3975
3976 if (numfound)
3977 return;
3978
3979 // didn't find any signposts in the exit sector.
3980 // spin all signposts in the level then.
3981 for (think = thlist[THINK_MOBJ].next; think != &thlist[THINK_MOBJ]; think = think->next)
3982 {
3983 if (think->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
3984 continue;
3985
3986 thing = (mobj_t *)think;
3987 if (thing->type != MT_SIGN)
3988 continue;
3989
3990 if (!numfound
3991 && !(player->mo->target && player->mo->target->type == MT_SIGN)
3992 && !((gametyperules & GTR_FRIENDLY) && (netgame || multiplayer) && cv_exitmove.value))
3993 P_SetTarget(&player->mo->target, thing);
3994
3995 if (thing->state != &states[thing->info->spawnstate])
3996 continue;
3997
3998 P_SetTarget(&thing->target, player->mo);
3999 P_SetObjectMomZ(thing, 12*FRACUNIT, false);
4000 P_SetMobjState(thing, S_SIGNSPIN1);
4001 if (thing->info->seesound)
4002 S_StartSound(thing, thing->info->seesound);
4003
4004 ++numfound;
4005 }
4006 }
4007
4008 //
4009 // P_IsFlagAtBase
4010 //
4011 // Checks to see if a flag is at its base.
4012 //
P_IsFlagAtBase(mobjtype_t flag)4013 boolean P_IsFlagAtBase(mobjtype_t flag)
4014 {
4015 thinker_t *think;
4016 mobj_t *mo;
4017 INT32 specialnum = (flag == MT_REDFLAG) ? 3 : 4;
4018
4019 for (think = thlist[THINK_MOBJ].next; think != &thlist[THINK_MOBJ]; think = think->next)
4020 {
4021 if (think->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
4022 continue;
4023
4024 mo = (mobj_t *)think;
4025
4026 if (mo->type != flag)
4027 continue;
4028
4029 if (GETSECSPECIAL(mo->subsector->sector->special, 4) == specialnum)
4030 return true;
4031 else if (mo->subsector->sector->ffloors) // Check the 3D floors
4032 {
4033 ffloor_t *rover;
4034
4035 for (rover = mo->subsector->sector->ffloors; rover; rover = rover->next)
4036 {
4037 if (!(rover->flags & FF_EXISTS))
4038 continue;
4039
4040 if (GETSECSPECIAL(rover->master->frontsector->special, 4) != specialnum)
4041 continue;
4042
4043 if (!(mo->z <= P_GetSpecialTopZ(mo, sectors + rover->secnum, mo->subsector->sector)
4044 && mo->z >= P_GetSpecialBottomZ(mo, sectors + rover->secnum, mo->subsector->sector)))
4045 continue;
4046
4047 return true;
4048 }
4049 }
4050 }
4051 return false;
4052 }
4053
4054 //
4055 // P_PlayerTouchingSectorSpecial
4056 //
4057 // Replaces the old player->specialsector.
4058 // This allows a player to touch more than
4059 // one sector at a time, if necessary.
4060 //
4061 // Returns a pointer to the first sector of
4062 // the particular type that it finds.
4063 // Returns NULL if it doesn't find it.
4064 //
P_PlayerTouchingSectorSpecial(player_t * player,INT32 section,INT32 number)4065 sector_t *P_PlayerTouchingSectorSpecial(player_t *player, INT32 section, INT32 number)
4066 {
4067 msecnode_t *node;
4068 ffloor_t *rover;
4069
4070 if (!player->mo)
4071 return NULL;
4072
4073 // Check default case first
4074 if (GETSECSPECIAL(player->mo->subsector->sector->special, section) == number)
4075 return player->mo->subsector->sector;
4076
4077 // Hmm.. maybe there's a FOF that has it...
4078 for (rover = player->mo->subsector->sector->ffloors; rover; rover = rover->next)
4079 {
4080 fixed_t topheight, bottomheight;
4081
4082 if (GETSECSPECIAL(rover->master->frontsector->special, section) != number)
4083 continue;
4084
4085 if (!(rover->flags & FF_EXISTS))
4086 continue;
4087
4088 topheight = P_GetSpecialTopZ(player->mo, sectors + rover->secnum, player->mo->subsector->sector);
4089 bottomheight = P_GetSpecialBottomZ(player->mo, sectors + rover->secnum, player->mo->subsector->sector);
4090
4091 // Check the 3D floor's type...
4092 if (rover->flags & FF_BLOCKPLAYER)
4093 {
4094 boolean floorallowed = ((rover->master->frontsector->flags & SF_FLIPSPECIAL_FLOOR) && ((rover->master->frontsector->flags & SF_TRIGGERSPECIAL_HEADBUMP) || !(player->mo->eflags & MFE_VERTICALFLIP)) && (player->mo->z == topheight));
4095 boolean ceilingallowed = ((rover->master->frontsector->flags & SF_FLIPSPECIAL_CEILING) && ((rover->master->frontsector->flags & SF_TRIGGERSPECIAL_HEADBUMP) || (player->mo->eflags & MFE_VERTICALFLIP)) && (player->mo->z + player->mo->height == bottomheight));
4096 // Thing must be on top of the floor to be affected...
4097 if (!(floorallowed || ceilingallowed))
4098 continue;
4099 }
4100 else
4101 {
4102 // Water and DEATH FOG!!! heh
4103 if (player->mo->z > topheight || (player->mo->z + player->mo->height) < bottomheight)
4104 continue;
4105 }
4106
4107 // This FOF has the special we're looking for!
4108 return rover->master->frontsector;
4109 }
4110
4111 for (node = player->mo->touching_sectorlist; node; node = node->m_sectorlist_next)
4112 {
4113 if (GETSECSPECIAL(node->m_sector->special, section) == number)
4114 {
4115 // This sector has the special we're looking for, but
4116 // are we allowed to touch it?
4117 if (node->m_sector == player->mo->subsector->sector
4118 || (node->m_sector->flags & SF_TRIGGERSPECIAL_TOUCH))
4119 return node->m_sector;
4120 }
4121
4122 // Hmm.. maybe there's a FOF that has it...
4123 for (rover = node->m_sector->ffloors; rover; rover = rover->next)
4124 {
4125 fixed_t topheight, bottomheight;
4126
4127 if (GETSECSPECIAL(rover->master->frontsector->special, section) != number)
4128 continue;
4129
4130 if (!(rover->flags & FF_EXISTS))
4131 continue;
4132
4133 topheight = P_GetSpecialTopZ(player->mo, sectors + rover->secnum, player->mo->subsector->sector);
4134 bottomheight = P_GetSpecialBottomZ(player->mo, sectors + rover->secnum, player->mo->subsector->sector);
4135
4136 // Check the 3D floor's type...
4137 if (rover->flags & FF_BLOCKPLAYER)
4138 {
4139 boolean floorallowed = ((rover->master->frontsector->flags & SF_FLIPSPECIAL_FLOOR) && ((rover->master->frontsector->flags & SF_TRIGGERSPECIAL_HEADBUMP) || !(player->mo->eflags & MFE_VERTICALFLIP)) && (player->mo->z == topheight));
4140 boolean ceilingallowed = ((rover->master->frontsector->flags & SF_FLIPSPECIAL_CEILING) && ((rover->master->frontsector->flags & SF_TRIGGERSPECIAL_HEADBUMP) || (player->mo->eflags & MFE_VERTICALFLIP)) && (player->mo->z + player->mo->height == bottomheight));
4141 // Thing must be on top of the floor to be affected...
4142 if (!(floorallowed || ceilingallowed))
4143 continue;
4144 }
4145 else
4146 {
4147 // Water and DEATH FOG!!! heh
4148 if (player->mo->z > topheight || (player->mo->z + player->mo->height) < bottomheight)
4149 continue;
4150 }
4151
4152 // This FOF has the special we're looking for, but are we allowed to touch it?
4153 if (node->m_sector == player->mo->subsector->sector
4154 || (rover->master->frontsector->flags & SF_TRIGGERSPECIAL_TOUCH))
4155 return rover->master->frontsector;
4156 }
4157 }
4158
4159 return NULL;
4160 }
4161
4162 //
4163 // P_ThingIsOnThe3DFloor
4164 //
4165 // This checks whether the mobj is on/in the FOF we want it to be at
4166 // Needed for the "All players" trigger sector specials only
4167 //
P_ThingIsOnThe3DFloor(mobj_t * mo,sector_t * sector,sector_t * targetsec)4168 static boolean P_ThingIsOnThe3DFloor(mobj_t *mo, sector_t *sector, sector_t *targetsec)
4169 {
4170 ffloor_t *rover;
4171 fixed_t top, bottom;
4172
4173 if (!mo->player) // should NEVER happen
4174 return false;
4175
4176 if (!targetsec->ffloors) // also should NEVER happen
4177 return false;
4178
4179 for (rover = targetsec->ffloors; rover; rover = rover->next)
4180 {
4181 if (rover->master->frontsector != sector)
4182 continue;
4183
4184 // we're assuming the FOF existed when the first player touched it
4185 //if (!(rover->flags & FF_EXISTS))
4186 // return false;
4187
4188 top = P_GetSpecialTopZ(mo, sector, targetsec);
4189 bottom = P_GetSpecialBottomZ(mo, sector, targetsec);
4190
4191 // Check the 3D floor's type...
4192 if (rover->flags & FF_BLOCKPLAYER)
4193 {
4194 boolean floorallowed = ((rover->master->frontsector->flags & SF_FLIPSPECIAL_FLOOR) && ((rover->master->frontsector->flags & SF_TRIGGERSPECIAL_HEADBUMP) || !(mo->eflags & MFE_VERTICALFLIP)) && (mo->z == top));
4195 boolean ceilingallowed = ((rover->master->frontsector->flags & SF_FLIPSPECIAL_CEILING) && ((rover->master->frontsector->flags & SF_TRIGGERSPECIAL_HEADBUMP) || (mo->eflags & MFE_VERTICALFLIP)) && (mo->z + mo->height == bottom));
4196 // Thing must be on top of the floor to be affected...
4197 if (!(floorallowed || ceilingallowed))
4198 continue;
4199 }
4200 else
4201 {
4202 // Water and intangible FOFs
4203 if (mo->z > top || (mo->z + mo->height) < bottom)
4204 return false;
4205 }
4206
4207 return true;
4208 }
4209
4210 return false;
4211 }
4212
4213 //
4214 // P_MobjReadyToTrigger
4215 //
4216 // Is player standing on the sector's "ground"?
4217 //
P_MobjReadyToTrigger(mobj_t * mo,sector_t * sec)4218 static boolean P_MobjReadyToTrigger(mobj_t *mo, sector_t *sec)
4219 {
4220 boolean floorallowed = ((sec->flags & SF_FLIPSPECIAL_FLOOR) && ((sec->flags & SF_TRIGGERSPECIAL_HEADBUMP) || !(mo->eflags & MFE_VERTICALFLIP)) && (mo->z == P_GetSpecialBottomZ(mo, sec, sec)));
4221 boolean ceilingallowed = ((sec->flags & SF_FLIPSPECIAL_CEILING) && ((sec->flags & SF_TRIGGERSPECIAL_HEADBUMP) || (mo->eflags & MFE_VERTICALFLIP)) && (mo->z + mo->height == P_GetSpecialTopZ(mo, sec, sec)));
4222 // Thing must be on top of the floor to be affected...
4223 return (floorallowed || ceilingallowed);
4224 }
4225
4226 /** Applies a sector special to a player.
4227 *
4228 * \param player Player in the sector.
4229 * \param sector Sector with the special.
4230 * \param roversector If !NULL, sector is actually an FOF; otherwise, sector
4231 * is being physically contacted by the player.
4232 * \todo Split up into multiple functions.
4233 * \sa P_PlayerInSpecialSector, P_PlayerOnSpecial3DFloor
4234 */
P_ProcessSpecialSector(player_t * player,sector_t * sector,sector_t * roversector)4235 void P_ProcessSpecialSector(player_t *player, sector_t *sector, sector_t *roversector)
4236 {
4237 INT32 i = 0;
4238 INT32 section1, section2, section3, section4;
4239 INT32 special;
4240 mtag_t sectag = Tag_FGet(§or->tags);
4241
4242 section1 = GETSECSPECIAL(sector->special, 1);
4243 section2 = GETSECSPECIAL(sector->special, 2);
4244 section3 = GETSECSPECIAL(sector->special, 3);
4245 section4 = GETSECSPECIAL(sector->special, 4);
4246
4247 // Ignore spectators
4248 if (player->spectator)
4249 return;
4250
4251 // Ignore dead players.
4252 // If this strange phenomenon could be potentially used in levels,
4253 // TODO: modify this to accommodate for it.
4254 if (player->playerstate != PST_LIVE)
4255 return;
4256
4257 // Conveyor stuff
4258 if (section3 == 2 || section3 == 4)
4259 player->onconveyor = section3;
4260
4261 special = section1;
4262
4263 // Process Section 1
4264 switch (special)
4265 {
4266 case 1: // Damage (Generic)
4267 if (roversector || P_MobjReadyToTrigger(player->mo, sector))
4268 P_DamageMobj(player->mo, NULL, NULL, 1, 0);
4269 break;
4270 case 2: // Damage (Water)
4271 if ((roversector || P_MobjReadyToTrigger(player->mo, sector)) && (player->powers[pw_underwater] || player->powers[pw_carry] == CR_NIGHTSMODE))
4272 P_DamageMobj(player->mo, NULL, NULL, 1, DMG_WATER);
4273 break;
4274 case 3: // Damage (Fire)
4275 if (roversector || P_MobjReadyToTrigger(player->mo, sector))
4276 P_DamageMobj(player->mo, NULL, NULL, 1, DMG_FIRE);
4277 break;
4278 case 4: // Damage (Electrical)
4279 if (roversector || P_MobjReadyToTrigger(player->mo, sector))
4280 P_DamageMobj(player->mo, NULL, NULL, 1, DMG_ELECTRIC);
4281 break;
4282 case 5: // Spikes
4283 if (roversector || P_MobjReadyToTrigger(player->mo, sector))
4284 P_DamageMobj(player->mo, NULL, NULL, 1, DMG_SPIKE);
4285 break;
4286 case 6: // Death Pit (Camera Mod)
4287 case 7: // Death Pit (No Camera Mod)
4288 if (roversector || P_MobjReadyToTrigger(player->mo, sector))
4289 {
4290 if (player->quittime)
4291 G_MovePlayerToSpawnOrStarpost(player - players);
4292 else
4293 P_DamageMobj(player->mo, NULL, NULL, 1, DMG_DEATHPIT);
4294 }
4295 break;
4296 case 8: // Instant Kill
4297 if (player->quittime)
4298 G_MovePlayerToSpawnOrStarpost(player - players);
4299 else
4300 P_DamageMobj(player->mo, NULL, NULL, 1, DMG_INSTAKILL);
4301 break;
4302 case 9: // Ring Drainer (Floor Touch)
4303 case 10: // Ring Drainer (No Floor Touch)
4304 if (leveltime % (TICRATE/2) == 0 && player->rings > 0)
4305 {
4306 player->rings--;
4307 S_StartSound(player->mo, sfx_antiri);
4308 }
4309 break;
4310 case 11: // Special Stage Damage
4311 if (player->exiting || player->bot) // Don't do anything for bots or players who have just finished
4312 break;
4313
4314 if (!(player->powers[pw_shield] || player->spheres > 0)) // Don't do anything if no shield or spheres anyway
4315 break;
4316
4317 P_SpecialStageDamage(player, NULL, NULL);
4318 break;
4319 case 12: // Space Countdown
4320 if (!(player->powers[pw_shield] & SH_PROTECTWATER) && !player->powers[pw_spacetime])
4321 player->powers[pw_spacetime] = spacetimetics + 1;
4322 break;
4323 case 13: // Ramp Sector (Increase step-up/down)
4324 case 14: // Non-Ramp Sector (Don't step-down)
4325 case 15: // Bouncy Sector (FOF Control Only)
4326 break;
4327 }
4328
4329 special = section2;
4330
4331 // Process Section 2
4332 switch (special)
4333 {
4334 case 1: // Trigger Linedef Exec (Pushable Objects)
4335 break;
4336 case 2: // Linedef executor requires all players present+doesn't require touching floor
4337 case 3: // Linedef executor requires all players present
4338 /// \todo check continues for proper splitscreen support?
4339 for (i = 0; i < MAXPLAYERS; i++)
4340 {
4341 if (!playeringame[i])
4342 continue;
4343 if (!players[i].mo)
4344 continue;
4345 if (players[i].spectator)
4346 continue;
4347 if (players[i].bot)
4348 continue;
4349 if (G_CoopGametype() && players[i].lives <= 0)
4350 continue;
4351 if (roversector)
4352 {
4353 if (sector->flags & SF_TRIGGERSPECIAL_TOUCH)
4354 {
4355 msecnode_t *node;
4356 for (node = players[i].mo->touching_sectorlist; node; node = node->m_sectorlist_next)
4357 {
4358 if (P_ThingIsOnThe3DFloor(players[i].mo, sector, node->m_sector))
4359 break;
4360 }
4361 if (!node)
4362 goto DoneSection2;
4363 }
4364 else if (players[i].mo->subsector && !P_ThingIsOnThe3DFloor(players[i].mo, sector, players[i].mo->subsector->sector)) // this function handles basically everything for us lmao
4365 goto DoneSection2;
4366 }
4367 else
4368 {
4369 if (players[i].mo->subsector->sector == sector)
4370 ;
4371 else if (sector->flags & SF_TRIGGERSPECIAL_TOUCH)
4372 {
4373 msecnode_t *node;
4374 for (node = players[i].mo->touching_sectorlist; node; node = node->m_sectorlist_next)
4375 {
4376 if (node->m_sector == sector)
4377 break;
4378 }
4379 if (!node)
4380 goto DoneSection2;
4381 }
4382 else
4383 goto DoneSection2;
4384
4385 if (special == 3 && !P_MobjReadyToTrigger(players[i].mo, sector))
4386 goto DoneSection2;
4387 }
4388 }
4389 /* FALLTHRU */
4390 case 4: // Linedef executor that doesn't require touching floor
4391 case 5: // Linedef executor
4392 case 6: // Linedef executor (7 Emeralds)
4393 case 7: // Linedef executor (NiGHTS Mare)
4394 if (!player->bot)
4395 P_LinedefExecute(sectag, player->mo, sector);
4396 break;
4397 case 8: // Tells pushable things to check FOFs
4398 break;
4399 case 9: // Egg trap capsule
4400 {
4401 thinker_t *th;
4402 mobj_t *mo2;
4403 line_t junk;
4404
4405 if (player->bot || sector->ceilingdata || sector->floordata)
4406 return;
4407
4408 // Find the center of the Eggtrap and release all the pretty animals!
4409 // The chimps are my friends.. heeheeheheehehee..... - LouisJM
4410 for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
4411 {
4412 if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
4413 continue;
4414 mo2 = (mobj_t *)th;
4415 if (mo2->type != MT_EGGTRAP)
4416 continue;
4417 P_KillMobj(mo2, NULL, player->mo, 0);
4418 }
4419
4420 // clear the special so you can't push the button twice.
4421 sector->special = 0;
4422
4423 // Initialize my junk
4424 junk.tags.tags = NULL;
4425 junk.tags.count = 0;
4426
4427 // Move the button down
4428 Tag_FSet(&junk.tags, LE_CAPSULE0);
4429 EV_DoElevator(&junk, elevateDown, false);
4430
4431 // Open the top FOF
4432 Tag_FSet(&junk.tags, LE_CAPSULE1);
4433 EV_DoFloor(&junk, raiseFloorToNearestFast);
4434 // Open the bottom FOF
4435 Tag_FSet(&junk.tags, LE_CAPSULE2);
4436 EV_DoCeiling(&junk, lowerToLowestFast);
4437
4438 // Mark all players with the time to exit thingy!
4439 for (i = 0; i < MAXPLAYERS; i++)
4440 {
4441 if (!playeringame[i])
4442 continue;
4443 P_DoPlayerExit(&players[i]);
4444 }
4445 break;
4446 }
4447 case 10: // Special Stage Time/Rings
4448 case 11: // Custom Gravity
4449 break;
4450 case 12: // Lua sector special
4451 break;
4452 }
4453 DoneSection2:
4454
4455 special = section3;
4456
4457 // Process Section 3
4458 switch (special)
4459 {
4460 case 1: // Unused
4461 case 2: // Wind/Current
4462 case 3: // Unused
4463 case 4: // Conveyor Belt
4464 break;
4465
4466 case 5: // Speed pad
4467 if (player->powers[pw_flashing] != 0 && player->powers[pw_flashing] < TICRATE/2)
4468 break;
4469
4470 i = Tag_FindLineSpecial(4, sectag);
4471
4472 if (i != -1)
4473 {
4474 angle_t lineangle;
4475 fixed_t linespeed;
4476 fixed_t sfxnum;
4477
4478 lineangle = R_PointToAngle2(lines[i].v1->x, lines[i].v1->y, lines[i].v2->x, lines[i].v2->y);
4479 linespeed = sides[lines[i].sidenum[0]].textureoffset;
4480
4481 if (linespeed == 0)
4482 {
4483 CONS_Debug(DBG_GAMELOGIC, "ERROR: Speed pad (tag %d) at zero speed.\n", sectag);
4484 break;
4485 }
4486
4487 player->mo->angle = player->drawangle = lineangle;
4488
4489 if (!demoplayback || P_ControlStyle(player) == CS_LMAOGALOG)
4490 P_SetPlayerAngle(player, player->mo->angle);
4491
4492 if (!(lines[i].flags & ML_EFFECT4))
4493 {
4494 P_UnsetThingPosition(player->mo);
4495 if (roversector) // make FOF speed pads work
4496 {
4497 player->mo->x = roversector->soundorg.x;
4498 player->mo->y = roversector->soundorg.y;
4499 }
4500 else
4501 {
4502 player->mo->x = sector->soundorg.x;
4503 player->mo->y = sector->soundorg.y;
4504 }
4505 P_SetThingPosition(player->mo);
4506 }
4507
4508 P_InstaThrust(player->mo, player->mo->angle, linespeed);
4509
4510 if (lines[i].flags & ML_EFFECT5) // Roll!
4511 {
4512 if (!(player->pflags & PF_SPINNING))
4513 player->pflags |= PF_SPINNING;
4514
4515 P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
4516 }
4517
4518 player->powers[pw_flashing] = TICRATE/3;
4519
4520 sfxnum = sides[lines[i].sidenum[0]].toptexture;
4521
4522 if (!sfxnum)
4523 sfxnum = sfx_spdpad;
4524
4525 S_StartSound(player->mo, sfxnum);
4526 }
4527 break;
4528
4529 case 6: // Unused
4530 case 7: // Unused
4531 case 8: // Unused
4532 case 9: // Unused
4533 case 10: // Unused
4534 case 11: // Unused
4535 case 12: // Unused
4536 case 13: // Unused
4537 case 14: // Unused
4538 case 15: // Unused
4539 break;
4540 }
4541
4542 special = section4;
4543
4544 // Process Section 4
4545 switch (special)
4546 {
4547 case 1: // Starpost Activator
4548 {
4549 mobj_t *post = P_GetObjectTypeInSectorNum(MT_STARPOST, sector - sectors);
4550
4551 if (!post)
4552 break;
4553
4554 P_TouchStarPost(post, player, false);
4555 break;
4556 }
4557
4558 case 2: // Special stage GOAL sector / Exit Sector / CTF Flag Return
4559 if (player->bot || !(gametyperules & GTR_ALLOWEXIT))
4560 break;
4561 if (!(maptol & TOL_NIGHTS) && G_IsSpecialStage(gamemap) && player->nightstime > 6)
4562 {
4563 player->nightstime = 6; // Just let P_Ticker take care of the rest.
4564 return;
4565 }
4566
4567 // Exit (for FOF exits; others are handled in P_PlayerThink in p_user.c)
4568 {
4569 INT32 lineindex;
4570
4571 P_DoPlayerFinish(player);
4572
4573 P_SetupSignExit(player);
4574 // important: use sector->tag on next line instead of player->mo->subsector->tag
4575 // this part is different from in P_PlayerThink, this is what was causing
4576 // FOF custom exits not to work.
4577 lineindex = Tag_FindLineSpecial(2, sectag);
4578
4579 if (G_CoopGametype() && lineindex != -1) // Custom exit!
4580 {
4581 // Special goodies with the block monsters flag depending on emeralds collected
4582 if ((lines[lineindex].flags & ML_BLOCKMONSTERS) && ALL7EMERALDS(emeralds))
4583 nextmapoverride = (INT16)(lines[lineindex].frontsector->ceilingheight>>FRACBITS);
4584 else
4585 nextmapoverride = (INT16)(lines[lineindex].frontsector->floorheight>>FRACBITS);
4586
4587 if (lines[lineindex].flags & ML_NOCLIMB)
4588 skipstats = 1;
4589 }
4590 }
4591 break;
4592
4593 case 3: // Red Team's Base
4594 if ((gametyperules & GTR_TEAMFLAGS) && P_IsObjectOnGround(player->mo))
4595 {
4596 if (player->ctfteam == 1 && (player->gotflag & GF_BLUEFLAG))
4597 {
4598 mobj_t *mo;
4599
4600 // Make sure the red team still has their own
4601 // flag at their base so they can score.
4602 if (!P_IsFlagAtBase(MT_REDFLAG))
4603 break;
4604
4605 HU_SetCEchoFlags(V_AUTOFADEOUT|V_ALLOWLOWERCASE);
4606 HU_SetCEchoDuration(5);
4607 HU_DoCEcho(va(M_GetText("\205%s\200\\CAPTURED THE \204BLUE FLAG\200.\\\\\\\\"), player_names[player-players]));
4608
4609 if (splitscreen || players[consoleplayer].ctfteam == 1)
4610 S_StartSound(NULL, sfx_flgcap);
4611 else if (players[consoleplayer].ctfteam == 2)
4612 S_StartSound(NULL, sfx_lose);
4613
4614 mo = P_SpawnMobj(player->mo->x,player->mo->y,player->mo->z,MT_BLUEFLAG);
4615 player->gotflag &= ~GF_BLUEFLAG;
4616 mo->flags &= ~MF_SPECIAL;
4617 mo->fuse = TICRATE;
4618 mo->spawnpoint = bflagpoint;
4619 mo->flags2 |= MF2_JUSTATTACKED;
4620 redscore += 1;
4621 P_AddPlayerScore(player, 250);
4622 }
4623 }
4624 break;
4625
4626 case 4: // Blue Team's Base
4627 if ((gametyperules & GTR_TEAMFLAGS) && P_IsObjectOnGround(player->mo))
4628 {
4629 if (player->ctfteam == 2 && (player->gotflag & GF_REDFLAG))
4630 {
4631 mobj_t *mo;
4632
4633 // Make sure the blue team still has their own
4634 // flag at their base so they can score.
4635 if (!P_IsFlagAtBase(MT_BLUEFLAG))
4636 break;
4637
4638 HU_SetCEchoFlags(V_AUTOFADEOUT|V_ALLOWLOWERCASE);
4639 HU_SetCEchoDuration(5);
4640 HU_DoCEcho(va(M_GetText("\204%s\200\\CAPTURED THE \205RED FLAG\200.\\\\\\\\"), player_names[player-players]));
4641
4642 if (splitscreen || players[consoleplayer].ctfteam == 2)
4643 S_StartSound(NULL, sfx_flgcap);
4644 else if (players[consoleplayer].ctfteam == 1)
4645 S_StartSound(NULL, sfx_lose);
4646
4647 mo = P_SpawnMobj(player->mo->x,player->mo->y,player->mo->z,MT_REDFLAG);
4648 player->gotflag &= ~GF_REDFLAG;
4649 mo->flags &= ~MF_SPECIAL;
4650 mo->fuse = TICRATE;
4651 mo->spawnpoint = rflagpoint;
4652 mo->flags2 |= MF2_JUSTATTACKED;
4653 bluescore += 1;
4654 P_AddPlayerScore(player, 250);
4655 }
4656 }
4657 break;
4658
4659 case 5: // Fan sector
4660 player->mo->momz += mobjinfo[MT_FAN].mass/4;
4661
4662 if (player->mo->momz > mobjinfo[MT_FAN].mass)
4663 player->mo->momz = mobjinfo[MT_FAN].mass;
4664
4665 P_ResetPlayer(player);
4666 if (player->panim != PA_FALL)
4667 P_SetPlayerMobjState(player->mo, S_PLAY_FALL);
4668 break;
4669
4670 case 6: // Super Sonic transformer
4671 if (player->mo->health > 0 && !player->bot && (player->charflags & SF_SUPER) && !player->powers[pw_super] && ALL7EMERALDS(emeralds))
4672 P_DoSuperTransformation(player, true);
4673 break;
4674
4675 case 7: // Make player spin
4676 if (!(player->pflags & PF_SPINNING))
4677 {
4678 player->pflags |= PF_SPINNING;
4679 P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
4680 S_StartAttackSound(player->mo, sfx_spin);
4681
4682 if (abs(player->rmomx) < FixedMul(5*FRACUNIT, player->mo->scale)
4683 && abs(player->rmomy) < FixedMul(5*FRACUNIT, player->mo->scale))
4684 P_InstaThrust(player->mo, player->mo->angle, FixedMul(10*FRACUNIT, player->mo->scale));
4685 }
4686 break;
4687
4688 case 8: // Zoom Tube Start
4689 {
4690 INT32 sequence;
4691 fixed_t speed;
4692 INT32 lineindex;
4693 mobj_t *waypoint = NULL;
4694 angle_t an;
4695
4696 if (player->mo->tracer && player->mo->tracer->type == MT_TUBEWAYPOINT && player->powers[pw_carry] == CR_ZOOMTUBE)
4697 break;
4698
4699 if (player->powers[pw_ignorelatch] & (1<<15))
4700 break;
4701
4702 // Find line #3 tagged to this sector
4703 lineindex = Tag_FindLineSpecial(3, sectag);
4704
4705 if (lineindex == -1)
4706 {
4707 CONS_Debug(DBG_GAMELOGIC, "ERROR: Sector special %d missing line special #3.\n", sector->special);
4708 break;
4709 }
4710
4711 // Grab speed and sequence values
4712 speed = abs(sides[lines[lineindex].sidenum[0]].textureoffset)/8;
4713 sequence = abs(sides[lines[lineindex].sidenum[0]].rowoffset)>>FRACBITS;
4714
4715 if (speed == 0)
4716 {
4717 CONS_Debug(DBG_GAMELOGIC, "ERROR: Waypoint sequence %d at zero speed.\n", sequence);
4718 break;
4719 }
4720
4721 waypoint = P_GetFirstWaypoint(sequence);
4722
4723 if (!waypoint)
4724 {
4725 CONS_Debug(DBG_GAMELOGIC, "ERROR: FIRST WAYPOINT IN SEQUENCE %d NOT FOUND.\n", sequence);
4726 break;
4727 }
4728 else
4729 {
4730 CONS_Debug(DBG_GAMELOGIC, "Waypoint %d found in sequence %d - speed = %d\n", waypoint->health, sequence, speed);
4731 }
4732
4733 an = R_PointToAngle2(player->mo->x, player->mo->y, waypoint->x, waypoint->y) - player->mo->angle;
4734
4735 if (an > ANGLE_90 && an < ANGLE_270 && !(lines[lineindex].flags & ML_EFFECT4))
4736 break; // behind back
4737
4738 P_SetTarget(&player->mo->tracer, waypoint);
4739 player->powers[pw_carry] = CR_ZOOMTUBE;
4740 player->speed = speed;
4741 player->pflags |= PF_SPINNING;
4742 player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE|PF_GLIDING|PF_BOUNCING|PF_SLIDING|PF_CANCARRY);
4743 player->climbing = 0;
4744
4745 if (player->mo->state-states != S_PLAY_ROLL)
4746 {
4747 P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
4748 S_StartSound(player->mo, sfx_spin);
4749 }
4750 }
4751 break;
4752
4753 case 9: // Zoom Tube End
4754 {
4755 INT32 sequence;
4756 fixed_t speed;
4757 INT32 lineindex;
4758 mobj_t *waypoint = NULL;
4759 angle_t an;
4760
4761 if (player->mo->tracer && player->mo->tracer->type == MT_TUBEWAYPOINT && player->powers[pw_carry] == CR_ZOOMTUBE)
4762 break;
4763
4764 if (player->powers[pw_ignorelatch] & (1<<15))
4765 break;
4766
4767 // Find line #3 tagged to this sector
4768 lineindex = Tag_FindLineSpecial(3, sectag);
4769
4770 if (lineindex == -1)
4771 {
4772 CONS_Debug(DBG_GAMELOGIC, "ERROR: Sector special %d missing line special #3.\n", sector->special);
4773 break;
4774 }
4775
4776 // Grab speed and sequence values
4777 speed = -abs(sides[lines[lineindex].sidenum[0]].textureoffset)/8; // Negative means reverse
4778 sequence = abs(sides[lines[lineindex].sidenum[0]].rowoffset)>>FRACBITS;
4779
4780 if (speed == 0)
4781 {
4782 CONS_Debug(DBG_GAMELOGIC, "ERROR: Waypoint sequence %d at zero speed.\n", sequence);
4783 break;
4784 }
4785
4786 waypoint = P_GetLastWaypoint(sequence);
4787
4788 if (!waypoint)
4789 {
4790 CONS_Debug(DBG_GAMELOGIC, "ERROR: LAST WAYPOINT IN SEQUENCE %d NOT FOUND.\n", sequence);
4791 break;
4792 }
4793 else
4794 {
4795 CONS_Debug(DBG_GAMELOGIC, "Waypoint %d found in sequence %d - speed = %d\n", waypoint->health, sequence, speed);
4796 }
4797
4798 an = R_PointToAngle2(player->mo->x, player->mo->y, waypoint->x, waypoint->y) - player->mo->angle;
4799
4800 if (an > ANGLE_90 && an < ANGLE_270 && !(lines[lineindex].flags & ML_EFFECT4))
4801 break; // behind back
4802
4803 P_SetTarget(&player->mo->tracer, waypoint);
4804 player->powers[pw_carry] = CR_ZOOMTUBE;
4805 player->speed = speed;
4806 player->pflags |= PF_SPINNING;
4807 player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE|PF_GLIDING|PF_BOUNCING|PF_SLIDING|PF_CANCARRY);
4808 player->climbing = 0;
4809
4810 if (player->mo->state-states != S_PLAY_ROLL)
4811 {
4812 P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
4813 S_StartSound(player->mo, sfx_spin);
4814 }
4815 }
4816 break;
4817
4818 case 10: // Finish Line
4819 if (((gametyperules & (GTR_RACE|GTR_LIVES)) == GTR_RACE) && !player->exiting)
4820 {
4821 if (player->starpostnum == numstarposts) // Must have touched all the starposts
4822 {
4823 player->laps++;
4824
4825 if (player->powers[pw_carry] == CR_NIGHTSMODE)
4826 player->drillmeter += 48*20;
4827
4828 if (player->laps >= (UINT8)cv_numlaps.value)
4829 CONS_Printf(M_GetText("%s has finished the race.\n"), player_names[player-players]);
4830 else if (player->laps == (UINT8)cv_numlaps.value-1)
4831 CONS_Printf(M_GetText("%s started the \205final lap\200!\n"), player_names[player-players]);
4832 else
4833 CONS_Printf(M_GetText("%s started lap %u\n"), player_names[player-players], (UINT32)player->laps+1);
4834
4835 // Reset starposts (checkpoints) info
4836 player->starpostscale = player->starpostangle = player->starposttime = player->starpostnum = 0;
4837 player->starpostx = player->starposty = player->starpostz = 0;
4838 P_ResetStarposts();
4839
4840 // Play the starpost sound for 'consistency'
4841 S_StartSound(player->mo, sfx_strpst);
4842 }
4843 else if (player->starpostnum)
4844 {
4845 // blatant reuse of a variable that's normally unused in circuit
4846 if (!player->tossdelay)
4847 S_StartSound(player->mo, sfx_lose);
4848 player->tossdelay = 3;
4849 }
4850
4851 if (player->laps >= (unsigned)cv_numlaps.value)
4852 {
4853 if (P_IsLocalPlayer(player))
4854 {
4855 HU_SetCEchoFlags(0);
4856 HU_SetCEchoDuration(5);
4857 HU_DoCEcho("FINISHED!");
4858 }
4859
4860 P_DoPlayerExit(player);
4861 }
4862 }
4863 break;
4864
4865 case 11: // Rope hang
4866 {
4867 INT32 sequence;
4868 fixed_t speed;
4869 INT32 lineindex;
4870 mobj_t *waypointmid = NULL;
4871 mobj_t *waypointhigh = NULL;
4872 mobj_t *waypointlow = NULL;
4873 mobj_t *closest = NULL;
4874 vector3_t p, line[2], resulthigh, resultlow;
4875
4876 if (player->mo->tracer && player->mo->tracer->type == MT_TUBEWAYPOINT && player->powers[pw_carry] == CR_ROPEHANG)
4877 break;
4878
4879 if (player->powers[pw_ignorelatch] & (1<<15))
4880 break;
4881
4882 if (player->mo->momz > 0)
4883 break;
4884
4885 if (player->cmd.buttons & BT_SPIN)
4886 break;
4887
4888 if (!(player->pflags & PF_SLIDING) && player->mo->state == &states[player->mo->info->painstate])
4889 break;
4890
4891 if (player->exiting)
4892 break;
4893
4894 //initialize resulthigh and resultlow with 0
4895 memset(&resultlow, 0x00, sizeof(resultlow));
4896 memset(&resulthigh, 0x00, sizeof(resulthigh));
4897
4898 // Find line #11 tagged to this sector
4899 lineindex = Tag_FindLineSpecial(11, sectag);
4900
4901 if (lineindex == -1)
4902 {
4903 CONS_Debug(DBG_GAMELOGIC, "ERROR: Sector special %d missing line special #11.\n", sector->special);
4904 break;
4905 }
4906
4907 // Grab speed and sequence values
4908 speed = abs(sides[lines[lineindex].sidenum[0]].textureoffset)/8;
4909 sequence = abs(sides[lines[lineindex].sidenum[0]].rowoffset)>>FRACBITS;
4910
4911 if (speed == 0)
4912 {
4913 CONS_Debug(DBG_GAMELOGIC, "ERROR: Waypoint sequence %d at zero speed.\n", sequence);
4914 break;
4915 }
4916
4917 // Find the closest waypoint
4918 // Find the preceding waypoint
4919 // Find the proceeding waypoint
4920 // Determine the closest spot on the line between the three waypoints
4921 // Put player at that location.
4922
4923 waypointmid = P_GetClosestWaypoint(sequence, player->mo);
4924
4925 if (!waypointmid)
4926 {
4927 CONS_Debug(DBG_GAMELOGIC, "ERROR: WAYPOINT(S) IN SEQUENCE %d NOT FOUND.\n", sequence);
4928 break;
4929 }
4930
4931 waypointlow = P_GetPreviousWaypoint(waypointmid, true);
4932 waypointhigh = P_GetNextWaypoint(waypointmid, true);
4933
4934 CONS_Debug(DBG_GAMELOGIC, "WaypointMid: %d; WaypointLow: %d; WaypointHigh: %d\n",
4935 waypointmid->health, waypointlow ? waypointlow->health : -1, waypointhigh ? waypointhigh->health : -1);
4936
4937 // Now we have three waypoints... the closest one we're near, and the one that comes before, and after.
4938 // Next, we need to find the closest point on the line between each set, and determine which one we're
4939 // closest to.
4940
4941 p.x = player->mo->x;
4942 p.y = player->mo->y;
4943 p.z = player->mo->z;
4944
4945 // Waypointmid and Waypointlow:
4946 if (waypointlow)
4947 {
4948 line[0].x = waypointmid->x;
4949 line[0].y = waypointmid->y;
4950 line[0].z = waypointmid->z;
4951 line[1].x = waypointlow->x;
4952 line[1].y = waypointlow->y;
4953 line[1].z = waypointlow->z;
4954
4955 P_ClosestPointOnLine3D(&p, line, &resultlow);
4956 }
4957
4958 // Waypointmid and Waypointhigh:
4959 if (waypointhigh)
4960 {
4961 line[0].x = waypointmid->x;
4962 line[0].y = waypointmid->y;
4963 line[0].z = waypointmid->z;
4964 line[1].x = waypointhigh->x;
4965 line[1].y = waypointhigh->y;
4966 line[1].z = waypointhigh->z;
4967
4968 P_ClosestPointOnLine3D(&p, line, &resulthigh);
4969 }
4970
4971 // 3D support now available. Disregard the previous notice here. -Red
4972
4973 P_UnsetThingPosition(player->mo);
4974 P_ResetPlayer(player);
4975 player->mo->momx = player->mo->momy = player->mo->momz = 0;
4976
4977 if (lines[lineindex].flags & ML_EFFECT1) // Don't wrap
4978 {
4979 mobj_t *highest = P_GetLastWaypoint(sequence);
4980 highest->flags |= MF_SLIDEME;
4981 }
4982
4983 // Changing the conditions on these ifs to fix issues with snapping to the wrong spot -Red
4984 if ((lines[lineindex].flags & ML_EFFECT1) && waypointmid->health == 0)
4985 {
4986 closest = waypointhigh;
4987 player->mo->x = resulthigh.x;
4988 player->mo->y = resulthigh.y;
4989 player->mo->z = resulthigh.z - P_GetPlayerHeight(player);
4990 }
4991 else if ((lines[lineindex].flags & ML_EFFECT1) && waypointmid->health == numwaypoints[sequence] - 1)
4992 {
4993 closest = waypointmid;
4994 player->mo->x = resultlow.x;
4995 player->mo->y = resultlow.y;
4996 player->mo->z = resultlow.z - P_GetPlayerHeight(player);
4997 }
4998 else
4999 {
5000 if (P_AproxDistance(P_AproxDistance(player->mo->x-resultlow.x, player->mo->y-resultlow.y),
5001 player->mo->z-resultlow.z) < P_AproxDistance(P_AproxDistance(player->mo->x-resulthigh.x,
5002 player->mo->y-resulthigh.y), player->mo->z-resulthigh.z))
5003 {
5004 // Line between Mid and Low is closer
5005 closest = waypointmid;
5006 player->mo->x = resultlow.x;
5007 player->mo->y = resultlow.y;
5008 player->mo->z = resultlow.z - P_GetPlayerHeight(player);
5009 }
5010 else
5011 {
5012 // Line between Mid and High is closer
5013 closest = waypointhigh;
5014 player->mo->x = resulthigh.x;
5015 player->mo->y = resulthigh.y;
5016 player->mo->z = resulthigh.z - P_GetPlayerHeight(player);
5017 }
5018 }
5019
5020 P_SetTarget(&player->mo->tracer, closest);
5021 player->powers[pw_carry] = CR_ROPEHANG;
5022
5023 // Option for static ropes.
5024 if (lines[lineindex].flags & ML_NOCLIMB)
5025 player->speed = 0;
5026 else
5027 player->speed = speed;
5028
5029 S_StartSound(player->mo, sfx_s3k4a);
5030
5031 player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE|PF_GLIDING|PF_BOUNCING|PF_SLIDING|PF_CANCARRY);
5032 player->climbing = 0;
5033 P_SetThingPosition(player->mo);
5034 P_SetPlayerMobjState(player->mo, S_PLAY_RIDE);
5035 }
5036 break;
5037 case 12: // Camera noclip
5038 case 13: // Unused
5039 case 14: // Unused
5040 case 15: // Unused
5041 break;
5042 }
5043 }
5044
5045 /** Checks if an object is standing on or is inside a special 3D floor.
5046 * If so, the sector is returned.
5047 *
5048 * \param mo Object to check.
5049 * \return Pointer to the sector with a special type, or NULL if no special 3D
5050 * floors are being contacted.
5051 * \sa P_PlayerOnSpecial3DFloor
5052 */
P_ThingOnSpecial3DFloor(mobj_t * mo)5053 sector_t *P_ThingOnSpecial3DFloor(mobj_t *mo)
5054 {
5055 sector_t *sector;
5056 ffloor_t *rover;
5057 fixed_t topheight, bottomheight;
5058
5059 sector = mo->subsector->sector;
5060 if (!sector->ffloors)
5061 return NULL;
5062
5063 for (rover = sector->ffloors; rover; rover = rover->next)
5064 {
5065 if (!rover->master->frontsector->special)
5066 continue;
5067
5068 if (!(rover->flags & FF_EXISTS))
5069 continue;
5070
5071 topheight = P_GetSpecialTopZ(mo, sectors + rover->secnum, sector);
5072 bottomheight = P_GetSpecialBottomZ(mo, sectors + rover->secnum, sector);
5073
5074 // Check the 3D floor's type...
5075 if (((rover->flags & FF_BLOCKPLAYER) && mo->player)
5076 || ((rover->flags & FF_BLOCKOTHERS) && !mo->player))
5077 {
5078 boolean floorallowed = ((rover->master->frontsector->flags & SF_FLIPSPECIAL_FLOOR) && ((rover->master->frontsector->flags & SF_TRIGGERSPECIAL_HEADBUMP) || !(mo->eflags & MFE_VERTICALFLIP)) && (mo->z == topheight));
5079 boolean ceilingallowed = ((rover->master->frontsector->flags & SF_FLIPSPECIAL_CEILING) && ((rover->master->frontsector->flags & SF_TRIGGERSPECIAL_HEADBUMP) || (mo->eflags & MFE_VERTICALFLIP)) && (mo->z + mo->height == bottomheight));
5080 // Thing must be on top of the floor to be affected...
5081 if (!(floorallowed || ceilingallowed))
5082 continue;
5083 }
5084 else
5085 {
5086 // Water and intangible FOFs
5087 if (mo->z > topheight || (mo->z + mo->height) < bottomheight)
5088 continue;
5089 }
5090
5091 return rover->master->frontsector;
5092 }
5093
5094 return NULL;
5095 }
5096
5097 #define TELEPORTED (player->mo->subsector->sector != originalsector)
5098
5099 /** Checks if a player is standing on or is inside a 3D floor (e.g. water) and
5100 * applies any specials.
5101 *
5102 * \param player Player to check.
5103 * \sa P_ThingOnSpecial3DFloor, P_PlayerInSpecialSector
5104 */
P_PlayerOnSpecial3DFloor(player_t * player,sector_t * sector)5105 static void P_PlayerOnSpecial3DFloor(player_t *player, sector_t *sector)
5106 {
5107 sector_t *originalsector = player->mo->subsector->sector;
5108 ffloor_t *rover;
5109 fixed_t topheight, bottomheight;
5110
5111 for (rover = sector->ffloors; rover; rover = rover->next)
5112 {
5113 if (!rover->master->frontsector->special)
5114 continue;
5115
5116 if (!(rover->flags & FF_EXISTS))
5117 continue;
5118
5119 topheight = P_GetSpecialTopZ(player->mo, sectors + rover->secnum, sector);
5120 bottomheight = P_GetSpecialBottomZ(player->mo, sectors + rover->secnum, sector);
5121
5122 // Check the 3D floor's type...
5123 if (rover->flags & FF_BLOCKPLAYER)
5124 {
5125 boolean floorallowed = ((rover->master->frontsector->flags & SF_FLIPSPECIAL_FLOOR) && ((rover->master->frontsector->flags & SF_TRIGGERSPECIAL_HEADBUMP) || !(player->mo->eflags & MFE_VERTICALFLIP)) && (player->mo->z == topheight));
5126 boolean ceilingallowed = ((rover->master->frontsector->flags & SF_FLIPSPECIAL_CEILING) && ((rover->master->frontsector->flags & SF_TRIGGERSPECIAL_HEADBUMP) || (player->mo->eflags & MFE_VERTICALFLIP)) && (player->mo->z + player->mo->height == bottomheight));
5127 // Thing must be on top of the floor to be affected...
5128 if (!(floorallowed || ceilingallowed))
5129 continue;
5130 }
5131 else
5132 {
5133 // Water and DEATH FOG!!! heh
5134 if (player->mo->z > topheight || (player->mo->z + player->mo->height) < bottomheight)
5135 continue;
5136 }
5137
5138 // This FOF has the special we're looking for, but are we allowed to touch it?
5139 if (sector == player->mo->subsector->sector
5140 || (rover->master->frontsector->flags & SF_TRIGGERSPECIAL_TOUCH))
5141 {
5142 P_ProcessSpecialSector(player, rover->master->frontsector, sector);
5143 if TELEPORTED return;
5144 }
5145 }
5146
5147 // Allow sector specials to be applied to polyobjects!
5148 if (player->mo->subsector->polyList)
5149 {
5150 polyobj_t *po = player->mo->subsector->polyList;
5151 sector_t *polysec;
5152 boolean touching = false;
5153 boolean inside = false;
5154
5155 while (po)
5156 {
5157 if (po->flags & POF_NOSPECIALS)
5158 {
5159 po = (polyobj_t *)(po->link.next);
5160 continue;
5161 }
5162
5163 polysec = po->lines[0]->backsector;
5164
5165 if ((polysec->flags & SF_TRIGGERSPECIAL_TOUCH))
5166 touching = P_MobjTouchingPolyobj(po, player->mo);
5167 else
5168 touching = false;
5169
5170 inside = P_MobjInsidePolyobj(po, player->mo);
5171
5172 if (!(inside || touching))
5173 {
5174 po = (polyobj_t *)(po->link.next);
5175 continue;
5176 }
5177
5178 // We're inside it! Yess...
5179 if (!polysec->special)
5180 {
5181 po = (polyobj_t *)(po->link.next);
5182 continue;
5183 }
5184
5185 if (!(po->flags & POF_TESTHEIGHT)) // Don't do height checking
5186 ;
5187 else if (po->flags & POF_SOLID)
5188 {
5189 boolean floorallowed = ((polysec->flags & SF_FLIPSPECIAL_FLOOR) && ((polysec->flags & SF_TRIGGERSPECIAL_HEADBUMP) || !(player->mo->eflags & MFE_VERTICALFLIP)) && (player->mo->z == polysec->ceilingheight));
5190 boolean ceilingallowed = ((polysec->flags & SF_FLIPSPECIAL_CEILING) && ((polysec->flags & SF_TRIGGERSPECIAL_HEADBUMP) || (player->mo->eflags & MFE_VERTICALFLIP)) && (player->mo->z + player->mo->height == polysec->floorheight));
5191 // Thing must be on top of the floor to be affected...
5192 if (!(floorallowed || ceilingallowed))
5193 {
5194 po = (polyobj_t *)(po->link.next);
5195 continue;
5196 }
5197 }
5198 else
5199 {
5200 // Water and DEATH FOG!!! heh
5201 if (player->mo->z > polysec->ceilingheight || (player->mo->z + player->mo->height) < polysec->floorheight)
5202 {
5203 po = (polyobj_t *)(po->link.next);
5204 continue;
5205 }
5206 }
5207
5208 P_ProcessSpecialSector(player, polysec, sector);
5209 if TELEPORTED return;
5210
5211 po = (polyobj_t *)(po->link.next);
5212 }
5213 }
5214 }
5215
5216 #define VDOORSPEED (FRACUNIT*2)
5217
5218 //
5219 // P_RunSpecialSectorCheck
5220 //
5221 // Helper function to P_PlayerInSpecialSector
5222 //
P_RunSpecialSectorCheck(player_t * player,sector_t * sector)5223 static void P_RunSpecialSectorCheck(player_t *player, sector_t *sector)
5224 {
5225 boolean nofloorneeded = false;
5226 fixed_t f_affectpoint, c_affectpoint;
5227
5228 if (!sector->special) // nothing special, exit
5229 return;
5230
5231 if (GETSECSPECIAL(sector->special, 2) == 9) // Egg trap capsule -- should only be for 3dFloors!
5232 return;
5233
5234 // The list of specials that activate without floor touch
5235 // Check Section 1
5236 switch(GETSECSPECIAL(sector->special, 1))
5237 {
5238 case 2: // Damage (water)
5239 case 8: // Instant kill
5240 case 10: // Ring drainer that doesn't require floor touch
5241 case 12: // Space countdown
5242 nofloorneeded = true;
5243 break;
5244 }
5245
5246 // Check Section 2
5247 switch(GETSECSPECIAL(sector->special, 2))
5248 {
5249 case 2: // Linedef executor (All players needed)
5250 case 4: // Linedef executor
5251 case 6: // Linedef executor (7 Emeralds)
5252 case 7: // Linedef executor (NiGHTS Mare)
5253 nofloorneeded = true;
5254 break;
5255 }
5256
5257 // Check Section 3
5258 /* switch(GETSECSPECIAL(sector->special, 3))
5259 {
5260
5261 }*/
5262
5263 // Check Section 4
5264 switch(GETSECSPECIAL(sector->special, 4))
5265 {
5266 case 2: // Level Exit / GOAL Sector / Flag Return
5267 if (!(maptol & TOL_NIGHTS) && G_IsSpecialStage(gamemap))
5268 {
5269 // Special stage GOAL sector
5270 // requires touching floor.
5271 break;
5272 }
5273 /* FALLTHRU */
5274
5275 case 1: // Starpost activator
5276 case 5: // Fan sector
5277 case 6: // Super Sonic Transform
5278 case 8: // Zoom Tube Start
5279 case 9: // Zoom Tube End
5280 case 10: // Finish line
5281 nofloorneeded = true;
5282 break;
5283 }
5284
5285 if (nofloorneeded)
5286 {
5287 P_ProcessSpecialSector(player, sector, NULL);
5288 return;
5289 }
5290
5291 f_affectpoint = P_GetSpecialBottomZ(player->mo, sector, sector);
5292 c_affectpoint = P_GetSpecialTopZ(player->mo, sector, sector);
5293
5294 {
5295 boolean floorallowed = ((sector->flags & SF_FLIPSPECIAL_FLOOR) && ((sector->flags & SF_TRIGGERSPECIAL_HEADBUMP) || !(player->mo->eflags & MFE_VERTICALFLIP)) && (player->mo->z == f_affectpoint));
5296 boolean ceilingallowed = ((sector->flags & SF_FLIPSPECIAL_CEILING) && ((sector->flags & SF_TRIGGERSPECIAL_HEADBUMP) || (player->mo->eflags & MFE_VERTICALFLIP)) && (player->mo->z + player->mo->height == c_affectpoint));
5297 // Thing must be on top of the floor to be affected...
5298 if (!(floorallowed || ceilingallowed))
5299 return;
5300 }
5301
5302 P_ProcessSpecialSector(player, sector, NULL);
5303 }
5304
5305 /** Checks if the player is in a special sector or FOF and apply any specials.
5306 *
5307 * \param player Player to check.
5308 * \sa P_PlayerOnSpecial3DFloor, P_ProcessSpecialSector
5309 */
P_PlayerInSpecialSector(player_t * player)5310 void P_PlayerInSpecialSector(player_t *player)
5311 {
5312 sector_t *originalsector;
5313 sector_t *loopsector;
5314 msecnode_t *node;
5315
5316 if (!player->mo)
5317 return;
5318
5319 originalsector = player->mo->subsector->sector;
5320
5321 P_PlayerOnSpecial3DFloor(player, originalsector); // Handle FOFs first.
5322 if TELEPORTED return;
5323
5324 P_RunSpecialSectorCheck(player, originalsector);
5325 if TELEPORTED return;
5326
5327 // Iterate through touching_sectorlist for SF_TRIGGERSPECIAL_TOUCH
5328 for (node = player->mo->touching_sectorlist; node; node = node->m_sectorlist_next)
5329 {
5330 loopsector = node->m_sector;
5331
5332 if (loopsector == originalsector) // Don't duplicate
5333 continue;
5334
5335 // Check 3D floors...
5336 P_PlayerOnSpecial3DFloor(player, loopsector);
5337 if TELEPORTED return;
5338
5339 if (!(loopsector->flags & SF_TRIGGERSPECIAL_TOUCH))
5340 continue;
5341
5342 P_RunSpecialSectorCheck(player, loopsector);
5343 if TELEPORTED return;
5344 }
5345 }
5346
5347 #undef TELEPORTED
5348
5349 /** Animate planes, scroll walls, etc. and keeps track of level timelimit and exits if time is up.
5350 *
5351 * \sa P_CheckTimeLimit, P_CheckPointLimit
5352 */
P_UpdateSpecials(void)5353 void P_UpdateSpecials(void)
5354 {
5355 anim_t *anim;
5356 INT32 i;
5357 INT32 pic;
5358 size_t j;
5359
5360 levelflat_t *foundflats; // for flat animation
5361
5362 // LEVEL TIMER
5363 P_CheckTimeLimit();
5364
5365 // POINT LIMIT
5366 P_CheckPointLimit();
5367
5368 // ANIMATE TEXTURES
5369 for (anim = anims; anim < lastanim; anim++)
5370 {
5371 for (i = 0; i < anim->numpics; i++)
5372 {
5373 pic = anim->basepic + ((leveltime/anim->speed + i) % anim->numpics);
5374 if (anim->istexture)
5375 texturetranslation[anim->basepic+i] = pic;
5376 }
5377 }
5378
5379 // ANIMATE FLATS
5380 /// \todo do not check the non-animate flat.. link the animated ones?
5381 /// \note its faster than the original anywaysince it animates only
5382 /// flats used in the level, and there's usually very few of them
5383 foundflats = levelflats;
5384 for (j = 0; j < numlevelflats; j++, foundflats++)
5385 {
5386 if (foundflats->speed) // it is an animated flat
5387 {
5388 // update the levelflat texture number
5389 if ((foundflats->type == LEVELFLAT_TEXTURE) && (foundflats->u.texture.basenum != -1))
5390 foundflats->u.texture.num = foundflats->u.texture.basenum + ((leveltime/foundflats->speed + foundflats->animseq) % foundflats->numpics);
5391 // update the levelflat lump number
5392 else if ((foundflats->type == LEVELFLAT_FLAT) && (foundflats->u.flat.baselumpnum != LUMPERROR))
5393 foundflats->u.flat.lumpnum = foundflats->u.flat.baselumpnum + ((leveltime/foundflats->speed + foundflats->animseq) % foundflats->numpics);
5394 }
5395 }
5396 }
5397
5398 //
5399 // Floor over floors (FOFs), 3Dfloors, 3Dblocks, fake floors (ffloors), rovers, or whatever you want to call them
5400 //
5401
5402 /** Gets the ID number for a 3Dfloor in its target sector.
5403 *
5404 * \param fflr The 3Dfloor we want an ID for.
5405 * \return ID of 3Dfloor in target sector. Note that the first FOF's ID is 0. UINT16_MAX is given if invalid.
5406 * \sa P_GetFFloorByID
5407 */
P_GetFFloorID(ffloor_t * fflr)5408 UINT16 P_GetFFloorID(ffloor_t *fflr)
5409 {
5410 ffloor_t *rover;
5411 sector_t *sec;
5412 UINT16 i = 0;
5413
5414 if (!fflr)
5415 return UINT16_MAX;
5416
5417 sec = fflr->target;
5418
5419 if (!sec->ffloors)
5420 return UINT16_MAX;
5421 for (rover = sec->ffloors; rover; rover = rover->next, i++)
5422 if (rover == fflr)
5423 return i;
5424 return UINT16_MAX;
5425 }
5426
5427 /** Gets a 3Dfloor by control sector.
5428 *
5429 * \param sec Target sector.
5430 * \param sec2 Control sector.
5431 * \return Pointer to found 3Dfloor, or NULL.
5432 * \sa P_GetFFloorByID
5433 */
P_GetFFloorBySec(sector_t * sec,sector_t * sec2)5434 static inline ffloor_t *P_GetFFloorBySec(sector_t *sec, sector_t *sec2)
5435 {
5436 ffloor_t *rover;
5437
5438 if (!sec->ffloors)
5439 return NULL;
5440 for (rover = sec->ffloors; rover; rover = rover->next)
5441 if (rover->secnum == (size_t)(sec2 - sectors))
5442 return rover;
5443 return NULL;
5444 }
5445
5446 /** Gets a 3Dfloor by ID number.
5447 *
5448 * \param sec Target sector.
5449 * \param id ID of 3Dfloor in target sector. Note that the first FOF's ID is 0.
5450 * \return Pointer to found 3Dfloor, or NULL.
5451 * \sa P_GetFFloorBySec, P_GetFFloorID
5452 */
P_GetFFloorByID(sector_t * sec,UINT16 id)5453 ffloor_t *P_GetFFloorByID(sector_t *sec, UINT16 id)
5454 {
5455 ffloor_t *rover;
5456 UINT16 i = 0;
5457
5458 if (!sec->ffloors)
5459 return NULL;
5460 for (rover = sec->ffloors; rover; rover = rover->next)
5461 if (i++ == id)
5462 return rover;
5463 return NULL;
5464 }
5465
5466 /** Adds a newly formed 3Dfloor structure to a sector's ffloors list.
5467 *
5468 * \param sec Target sector.
5469 * \param fflr Newly formed 3Dfloor structure.
5470 * \sa P_AddFakeFloor
5471 */
P_AddFFloorToList(sector_t * sec,ffloor_t * fflr)5472 static inline void P_AddFFloorToList(sector_t *sec, ffloor_t *fflr)
5473 {
5474 ffloor_t *rover;
5475
5476 if (!sec->ffloors)
5477 {
5478 sec->ffloors = fflr;
5479 fflr->next = 0;
5480 fflr->prev = 0;
5481 return;
5482 }
5483
5484 for (rover = sec->ffloors; rover->next; rover = rover->next);
5485
5486 rover->next = fflr;
5487 fflr->prev = rover;
5488 fflr->next = 0;
5489 }
5490
5491 /** Adds a 3Dfloor.
5492 *
5493 * \param sec Target sector.
5494 * \param sec2 Control sector.
5495 * \param master Control linedef.
5496 * \param flags Options affecting this 3Dfloor.
5497 * \param secthinkers List of relevant thinkers sorted by sector. May be NULL.
5498 * \return Pointer to the new 3Dfloor.
5499 * \sa P_AddFFloor, P_AddFakeFloorsByLine, P_SpawnSpecials
5500 */
P_AddFakeFloor(sector_t * sec,sector_t * sec2,line_t * master,ffloortype_e flags,thinkerlist_t * secthinkers)5501 static ffloor_t *P_AddFakeFloor(sector_t *sec, sector_t *sec2, line_t *master, ffloortype_e flags, thinkerlist_t *secthinkers)
5502 {
5503 ffloor_t *fflr;
5504 thinker_t *th;
5505 friction_t *f;
5506 pusher_t *p;
5507 size_t sec2num;
5508 size_t i;
5509
5510 if (sec == sec2)
5511 return NULL; //Don't need a fake floor on a control sector.
5512 if ((fflr = (P_GetFFloorBySec(sec, sec2))))
5513 return fflr; // If this ffloor already exists, return it
5514
5515 if (sec2->ceilingheight < sec2->floorheight)
5516 {
5517 fixed_t tempceiling = sec2->ceilingheight;
5518 //flip the sector around and print an error instead of crashing 12.1.08 -Inuyasha
5519 CONS_Alert(CONS_ERROR, M_GetText("FOF (line %s) has a top height below its bottom.\n"), sizeu1(master - lines));
5520 sec2->ceilingheight = sec2->floorheight;
5521 sec2->floorheight = tempceiling;
5522 }
5523
5524 if (sec2->numattached == 0)
5525 {
5526 sec2->attached = Z_Malloc(sizeof (*sec2->attached) * sec2->maxattached, PU_STATIC, NULL);
5527 sec2->attachedsolid = Z_Malloc(sizeof (*sec2->attachedsolid) * sec2->maxattached, PU_STATIC, NULL);
5528 sec2->attached[0] = sec - sectors;
5529 sec2->numattached = 1;
5530 sec2->attachedsolid[0] = (flags & FF_SOLID);
5531 }
5532 else
5533 {
5534 for (i = 0; i < sec2->numattached; i++)
5535 if (sec2->attached[i] == (size_t)(sec - sectors))
5536 return NULL;
5537
5538 if (sec2->numattached >= sec2->maxattached)
5539 {
5540 sec2->maxattached *= 2;
5541 sec2->attached = Z_Realloc(sec2->attached, sizeof (*sec2->attached) * sec2->maxattached, PU_STATIC, NULL);
5542 sec2->attachedsolid = Z_Realloc(sec2->attachedsolid, sizeof (*sec2->attachedsolid) * sec2->maxattached, PU_STATIC, NULL);
5543 }
5544 sec2->attached[sec2->numattached] = sec - sectors;
5545 sec2->attachedsolid[sec2->numattached] = (flags & FF_SOLID);
5546 sec2->numattached++;
5547 }
5548
5549 // Add the floor
5550 fflr = Z_Calloc(sizeof (*fflr), PU_LEVEL, NULL);
5551 fflr->secnum = sec2 - sectors;
5552 fflr->target = sec;
5553 fflr->bottomheight = &sec2->floorheight;
5554 fflr->bottompic = &sec2->floorpic;
5555 fflr->bottomxoffs = &sec2->floor_xoffs;
5556 fflr->bottomyoffs = &sec2->floor_yoffs;
5557 fflr->bottomangle = &sec2->floorpic_angle;
5558
5559 // Add the ceiling
5560 fflr->topheight = &sec2->ceilingheight;
5561 fflr->toppic = &sec2->ceilingpic;
5562 fflr->toplightlevel = &sec2->lightlevel;
5563 fflr->topxoffs = &sec2->ceiling_xoffs;
5564 fflr->topyoffs = &sec2->ceiling_yoffs;
5565 fflr->topangle = &sec2->ceilingpic_angle;
5566
5567 // Add slopes
5568 fflr->t_slope = &sec2->c_slope;
5569 fflr->b_slope = &sec2->f_slope;
5570 // mark the target sector as having slopes, if the FOF has any of its own
5571 // (this fixes FOF slopes glitching initially at level load in software mode)
5572 if (sec2->hasslope)
5573 sec->hasslope = true;
5574
5575 if ((flags & FF_SOLID) && (master->flags & ML_EFFECT1)) // Block player only
5576 flags &= ~FF_BLOCKOTHERS;
5577
5578 if ((flags & FF_SOLID) && (master->flags & ML_EFFECT2)) // Block all BUT player
5579 flags &= ~FF_BLOCKPLAYER;
5580
5581 fflr->spawnflags = fflr->flags = flags;
5582 fflr->master = master;
5583 fflr->norender = INFTICS;
5584 fflr->fadingdata = NULL;
5585
5586 // Scan the thinkers to check for special conditions applying to this FOF.
5587 // If we have thinkers sorted by sector, just check the relevant ones;
5588 // otherwise, check them all. Apologies for the ugly loop...
5589
5590 sec2num = sec2 - sectors;
5591
5592 // Just initialise both of these to placate the compiler.
5593 i = 0;
5594 th = thlist[THINK_MAIN].next;
5595
5596 for(;;)
5597 {
5598 if(secthinkers)
5599 {
5600 if(i < secthinkers[sec2num].count)
5601 th = secthinkers[sec2num].thinkers[i];
5602 else break;
5603 }
5604 else if (th == &thlist[THINK_MAIN])
5605 break;
5606
5607 // Should this FOF have friction?
5608 if(th->function.acp1 == (actionf_p1)T_Friction)
5609 {
5610 f = (friction_t *)th;
5611
5612 if (f->affectee == (INT32)sec2num)
5613 Add_Friction(f->friction, f->movefactor, (INT32)(sec-sectors), f->affectee);
5614 }
5615 // Should this FOF have wind/current/pusher?
5616 else if(th->function.acp1 == (actionf_p1)T_Pusher)
5617 {
5618 p = (pusher_t *)th;
5619
5620 if (p->affectee == (INT32)sec2num)
5621 Add_Pusher(p->type, p->x_mag<<FRACBITS, p->y_mag<<FRACBITS, p->source, (INT32)(sec-sectors), p->affectee, p->exclusive, p->slider);
5622 }
5623
5624 if(secthinkers) i++;
5625 else th = th->next;
5626 }
5627
5628
5629 if (flags & FF_TRANSLUCENT)
5630 {
5631 if (sides[master->sidenum[0]].toptexture > 0)
5632 fflr->alpha = sides[master->sidenum[0]].toptexture; // for future reference, "#0" is 1, and "#255" is 256. Be warned
5633 else
5634 fflr->alpha = 0x80;
5635 }
5636 else
5637 fflr->alpha = 0xff;
5638
5639 fflr->spawnalpha = fflr->alpha; // save for netgames
5640
5641 if (flags & FF_QUICKSAND)
5642 CheckForQuicksand = true;
5643
5644 if ((flags & FF_BUSTUP) || (flags & FF_SHATTER) || (flags & FF_SPINBUST))
5645 CheckForBustableBlocks = true;
5646
5647 if ((flags & FF_MARIO))
5648 {
5649 if (!(flags & FF_SHATTERBOTTOM)) // Don't change the textures of a brick block, just a question block
5650 P_AddBlockThinker(sec2, master);
5651 CheckForMarioBlocks = true;
5652 }
5653
5654 if ((flags & FF_CRUMBLE))
5655 sec2->crumblestate = CRUMBLE_WAIT;
5656
5657 if ((flags & FF_FLOATBOB))
5658 {
5659 P_AddFloatThinker(sec2, Tag_FGet(&master->tags), master);
5660 CheckForFloatBob = true;
5661 }
5662
5663 P_AddFFloorToList(sec, fflr);
5664
5665 return fflr;
5666 }
5667
5668 //
5669 // SPECIAL SPAWNING
5670 //
5671
5672 /** Adds a float thinker.
5673 * Float thinkers cause solid 3Dfloors to float on water.
5674 *
5675 * \param sec Control sector.
5676 * \param actionsector Target sector.
5677 * \sa P_SpawnSpecials, T_FloatSector
5678 * \author SSNTails <http://www.ssntails.org>
5679 */
P_AddFloatThinker(sector_t * sec,UINT16 tag,line_t * sourceline)5680 static void P_AddFloatThinker(sector_t *sec, UINT16 tag, line_t *sourceline)
5681 {
5682 floatthink_t *floater;
5683
5684 // create and initialize new thinker
5685 floater = Z_Calloc(sizeof (*floater), PU_LEVSPEC, NULL);
5686 P_AddThinker(THINK_MAIN, &floater->thinker);
5687
5688 floater->thinker.function.acp1 = (actionf_p1)T_FloatSector;
5689
5690 floater->sector = sec;
5691 floater->tag = (INT16)tag;
5692 floater->sourceline = sourceline;
5693 }
5694
5695 /**
5696 * Adds a plane displacement thinker.
5697 * Whenever the "control" sector moves,
5698 * the "affectee" sector's floor or ceiling plane moves too!
5699 *
5700 * \param speed Rate of movement relative to control sector
5701 * \param control Control sector.
5702 * \param affectee Target sector.
5703 * \param reverse Reverse direction?
5704 * \sa P_SpawnSpecials, T_PlaneDisplace
5705 * \author Monster Iestyn
5706 */
P_AddPlaneDisplaceThinker(INT32 type,fixed_t speed,INT32 control,INT32 affectee,UINT8 reverse)5707 static void P_AddPlaneDisplaceThinker(INT32 type, fixed_t speed, INT32 control, INT32 affectee, UINT8 reverse)
5708 {
5709 planedisplace_t *displace;
5710
5711 // create and initialize new displacement thinker
5712 displace = Z_Calloc(sizeof (*displace), PU_LEVSPEC, NULL);
5713 P_AddThinker(THINK_MAIN, &displace->thinker);
5714
5715 displace->thinker.function.acp1 = (actionf_p1)T_PlaneDisplace;
5716 displace->affectee = affectee;
5717 displace->control = control;
5718 displace->last_height = sectors[control].floorheight;
5719 displace->speed = speed;
5720 displace->type = type;
5721 displace->reverse = reverse;
5722 }
5723
5724 /** Adds a Mario block thinker, which changes the block's texture between blank
5725 * and ? depending on whether it has contents.
5726 * Needed in case objects respawn inside.
5727 *
5728 * \param sec Control sector.
5729 * \param actionsector Target sector.
5730 * \param sourceline Control linedef.
5731 * \sa P_SpawnSpecials, T_MarioBlockChecker
5732 * \author SSNTails <http://www.ssntails.org>
5733 */
P_AddBlockThinker(sector_t * sec,line_t * sourceline)5734 static void P_AddBlockThinker(sector_t *sec, line_t *sourceline)
5735 {
5736 mariocheck_t *block;
5737
5738 // create and initialize new elevator thinker
5739 block = Z_Calloc(sizeof (*block), PU_LEVSPEC, NULL);
5740 P_AddThinker(THINK_MAIN, &block->thinker);
5741
5742 block->thinker.function.acp1 = (actionf_p1)T_MarioBlockChecker;
5743 block->sourceline = sourceline;
5744
5745 block->sector = sec;
5746 }
5747
5748 /** Adds a raise thinker.
5749 * A raise thinker checks to see if the
5750 * player is standing on its 3D Floor,
5751 * and if so, raises the platform towards
5752 * it's destination. Otherwise, it lowers
5753 * to the lowest nearby height if not
5754 * there already.
5755 *
5756 * \param sec Control sector.
5757 * \sa P_SpawnSpecials, T_RaiseSector
5758 * \author SSNTails <http://www.ssntails.org>
5759 */
P_AddRaiseThinker(sector_t * sec,INT16 tag,fixed_t speed,fixed_t ceilingtop,fixed_t ceilingbottom,boolean lower,boolean spindash)5760 static void P_AddRaiseThinker(sector_t *sec, INT16 tag, fixed_t speed, fixed_t ceilingtop, fixed_t ceilingbottom, boolean lower, boolean spindash)
5761 {
5762 raise_t *raise;
5763
5764 raise = Z_Calloc(sizeof (*raise), PU_LEVSPEC, NULL);
5765 P_AddThinker(THINK_MAIN, &raise->thinker);
5766
5767 raise->thinker.function.acp1 = (actionf_p1)T_RaiseSector;
5768
5769 raise->tag = tag;
5770 raise->sector = sec;
5771
5772 raise->ceilingtop = ceilingtop;
5773 raise->ceilingbottom = ceilingbottom;
5774
5775 raise->basespeed = speed;
5776
5777 if (lower)
5778 raise->flags |= RF_REVERSE;
5779 if (spindash)
5780 raise->flags |= RF_SPINDASH;
5781 }
5782
P_AddAirbob(sector_t * sec,INT16 tag,fixed_t dist,boolean raise,boolean spindash,boolean dynamic)5783 static void P_AddAirbob(sector_t *sec, INT16 tag, fixed_t dist, boolean raise, boolean spindash, boolean dynamic)
5784 {
5785 raise_t *airbob;
5786
5787 airbob = Z_Calloc(sizeof (*airbob), PU_LEVSPEC, NULL);
5788 P_AddThinker(THINK_MAIN, &airbob->thinker);
5789
5790 airbob->thinker.function.acp1 = (actionf_p1)T_RaiseSector;
5791
5792 airbob->tag = tag;
5793 airbob->sector = sec;
5794
5795 airbob->ceilingtop = sec->ceilingheight;
5796 airbob->ceilingbottom = sec->ceilingheight - dist;
5797
5798 airbob->basespeed = FRACUNIT;
5799
5800 if (!raise)
5801 airbob->flags |= RF_REVERSE;
5802 if (spindash)
5803 airbob->flags |= RF_SPINDASH;
5804 if (dynamic)
5805 airbob->flags |= RF_DYNAMIC;
5806 }
5807
5808 /** Adds a thwomp thinker.
5809 * Even thwomps need to think!
5810 *
5811 * \param sec Control sector.
5812 * \sa P_SpawnSpecials, T_ThwompSector
5813 * \author SSNTails <http://www.ssntails.org>
5814 */
P_AddThwompThinker(sector_t * sec,line_t * sourceline,fixed_t crushspeed,fixed_t retractspeed,UINT16 sound)5815 static inline void P_AddThwompThinker(sector_t *sec, line_t *sourceline, fixed_t crushspeed, fixed_t retractspeed, UINT16 sound)
5816 {
5817 thwomp_t *thwomp;
5818
5819 // You *probably* already have a thwomp in this sector. If you've combined it with something
5820 // else that uses the floordata/ceilingdata, you must be weird.
5821 if (sec->floordata || sec->ceilingdata)
5822 return;
5823
5824 // create and initialize new elevator thinker
5825 thwomp = Z_Calloc(sizeof (*thwomp), PU_LEVSPEC, NULL);
5826 P_AddThinker(THINK_MAIN, &thwomp->thinker);
5827
5828 thwomp->thinker.function.acp1 = (actionf_p1)T_ThwompSector;
5829
5830 // set up the fields according to the type of elevator action
5831 thwomp->sourceline = sourceline;
5832 thwomp->sector = sec;
5833 thwomp->crushspeed = crushspeed;
5834 thwomp->retractspeed = retractspeed;
5835 thwomp->direction = 0;
5836 thwomp->floorstartheight = sec->floorheight;
5837 thwomp->ceilingstartheight = sec->ceilingheight;
5838 thwomp->delay = 1;
5839 thwomp->tag = Tag_FGet(&sourceline->tags);
5840 thwomp->sound = sound;
5841
5842 sec->floordata = thwomp;
5843 sec->ceilingdata = thwomp;
5844 // Start with 'resting' texture
5845 sides[sourceline->sidenum[0]].midtexture = sides[sourceline->sidenum[0]].bottomtexture;
5846 }
5847
5848 /** Adds a thinker which checks if any MF_ENEMY objects with health are in the defined area.
5849 * If not, a linedef executor is run once.
5850 *
5851 * \param sourceline Control linedef.
5852 * \sa P_SpawnSpecials, T_NoEnemiesSector
5853 * \author SSNTails <http://www.ssntails.org>
5854 */
P_AddNoEnemiesThinker(line_t * sourceline)5855 static inline void P_AddNoEnemiesThinker(line_t *sourceline)
5856 {
5857 noenemies_t *nobaddies;
5858
5859 // create and initialize new thinker
5860 nobaddies = Z_Calloc(sizeof (*nobaddies), PU_LEVSPEC, NULL);
5861 P_AddThinker(THINK_MAIN, &nobaddies->thinker);
5862
5863 nobaddies->thinker.function.acp1 = (actionf_p1)T_NoEnemiesSector;
5864
5865 nobaddies->sourceline = sourceline;
5866 }
5867
5868 /** Adds a thinker for Each-Time linedef executors. A linedef executor is run
5869 * only when a player enters the area and doesn't run again until they re-enter.
5870 *
5871 * \param sourceline Control linedef.
5872 * \sa P_SpawnSpecials, T_EachTimeThinker
5873 * \author SSNTails <http://www.ssntails.org>
5874 */
P_AddEachTimeThinker(line_t * sourceline)5875 static void P_AddEachTimeThinker(line_t *sourceline)
5876 {
5877 eachtime_t *eachtime;
5878
5879 // create and initialize new thinker
5880 eachtime = Z_Calloc(sizeof (*eachtime), PU_LEVSPEC, NULL);
5881 P_AddThinker(THINK_MAIN, &eachtime->thinker);
5882
5883 eachtime->thinker.function.acp1 = (actionf_p1)T_EachTimeThinker;
5884
5885 eachtime->sourceline = sourceline;
5886 eachtime->triggerOnExit = !!(sourceline->flags & ML_BOUNCY);
5887 }
5888
5889 /** Adds a camera scanner.
5890 *
5891 * \param sourcesec Control sector.
5892 * \param actionsector Target sector.
5893 * \param angle Angle of the source line.
5894 * \sa P_SpawnSpecials, T_CameraScanner
5895 * \author SSNTails <http://www.ssntails.org>
5896 */
P_AddCameraScanner(sector_t * sourcesec,sector_t * actionsector,angle_t angle)5897 static inline void P_AddCameraScanner(sector_t *sourcesec, sector_t *actionsector, angle_t angle)
5898 {
5899 elevator_t *elevator; // Why not? LOL
5900
5901 CONS_Alert(CONS_WARNING, M_GetText("Detected a camera scanner effect (linedef type 5). This effect is deprecated and will be removed in the future!\n"));
5902
5903 // create and initialize new elevator thinker
5904 elevator = Z_Calloc(sizeof (*elevator), PU_LEVSPEC, NULL);
5905 P_AddThinker(THINK_MAIN, &elevator->thinker);
5906
5907 elevator->thinker.function.acp1 = (actionf_p1)T_CameraScanner;
5908 elevator->type = elevateBounce;
5909
5910 // set up the fields according to the type of elevator action
5911 elevator->sector = sourcesec;
5912 elevator->actionsector = actionsector;
5913 elevator->distance = FixedInt(AngleFixed(angle));
5914 }
5915
5916 /** Flashes a laser block.
5917 *
5918 * \param flash Thinker structure for this laser.
5919 * \sa P_AddLaserThinker
5920 * \author SSNTails <http://www.ssntails.org>
5921 */
T_LaserFlash(laserthink_t * flash)5922 void T_LaserFlash(laserthink_t *flash)
5923 {
5924 msecnode_t *node;
5925 mobj_t *thing;
5926 INT32 s;
5927 ffloor_t *fflr;
5928 sector_t *sector;
5929 sector_t *sourcesec = flash->sourceline->frontsector;
5930 fixed_t top, bottom;
5931 TAG_ITER_DECLARECOUNTER(0);
5932
5933 TAG_ITER_SECTORS(0, flash->tag, s)
5934 {
5935 sector = §ors[s];
5936 for (fflr = sector->ffloors; fflr; fflr = fflr->next)
5937 {
5938 if (fflr->master != flash->sourceline)
5939 continue;
5940
5941 if (!(fflr->flags & FF_EXISTS))
5942 break;
5943
5944 if (leveltime & 2)
5945 //fflr->flags |= FF_RENDERALL;
5946 fflr->alpha = 0xB0;
5947 else
5948 //fflr->flags &= ~FF_RENDERALL;
5949 fflr->alpha = 0x90;
5950
5951 top = P_GetFFloorTopZAt (fflr, sector->soundorg.x, sector->soundorg.y);
5952 bottom = P_GetFFloorBottomZAt(fflr, sector->soundorg.x, sector->soundorg.y);
5953 sector->soundorg.z = (top + bottom)/2;
5954 S_StartSound(§or->soundorg, sfx_laser);
5955
5956 // Seek out objects to DESTROY! MUAHAHHAHAHAA!!!*cough*
5957 for (node = sector->touching_thinglist; node && node->m_thing; node = node->m_thinglist_next)
5958 {
5959 thing = node->m_thing;
5960
5961 if (flash->nobosses && thing->flags & MF_BOSS)
5962 continue; // Don't hurt bosses
5963
5964 // Don't endlessly kill egg guard shields (or anything else for that matter)
5965 if (thing->health <= 0)
5966 continue;
5967
5968 top = P_GetSpecialTopZ(thing, sourcesec, sector);
5969 bottom = P_GetSpecialBottomZ(thing, sourcesec, sector);
5970
5971 if (thing->z >= top
5972 || thing->z + thing->height <= bottom)
5973 continue;
5974
5975 if (thing->flags & MF_SHOOTABLE)
5976 P_DamageMobj(thing, NULL, NULL, 1, 0);
5977 else if (thing->type == MT_EGGSHIELD)
5978 P_KillMobj(thing, NULL, NULL, 0);
5979 }
5980
5981 break;
5982 }
5983 }
5984 }
5985
P_AddLaserThinker(INT16 tag,line_t * line,boolean nobosses)5986 static inline void P_AddLaserThinker(INT16 tag, line_t *line, boolean nobosses)
5987 {
5988 laserthink_t *flash = Z_Calloc(sizeof (*flash), PU_LEVSPEC, NULL);
5989
5990 P_AddThinker(THINK_MAIN, &flash->thinker);
5991
5992 flash->thinker.function.acp1 = (actionf_p1)T_LaserFlash;
5993 flash->tag = tag;
5994 flash->sourceline = line;
5995 flash->nobosses = nobosses;
5996 }
5997
5998 //
5999 // P_RunLevelLoadExecutors
6000 //
6001 // After loading/spawning all other specials
6002 // and items, execute these.
6003 //
P_RunLevelLoadExecutors(void)6004 static void P_RunLevelLoadExecutors(void)
6005 {
6006 size_t i;
6007
6008 for (i = 0; i < numlines; i++)
6009 {
6010 if (lines[i].special == 399)
6011 P_RunTriggerLinedef(&lines[i], NULL, NULL);
6012 }
6013 }
6014
6015 /** Before things are loaded, initialises certain stuff in case they're needed
6016 * by P_SpawnSlopes or P_LoadThings. This was split off from
6017 * P_SpawnSpecials, in case you couldn't tell.
6018 *
6019 * \sa P_SpawnSpecials
6020 * \author Monster Iestyn
6021 */
P_InitSpecials(void)6022 void P_InitSpecials(void)
6023 {
6024 // Set the default gravity. Custom gravity overrides this setting.
6025 gravity = mapheaderinfo[gamemap-1]->gravity;
6026
6027 // Defaults in case levels don't have them set.
6028 sstimer = mapheaderinfo[gamemap-1]->sstimer*TICRATE + 6;
6029 ssspheres = mapheaderinfo[gamemap-1]->ssspheres;
6030
6031 CheckForBustableBlocks = CheckForBouncySector = CheckForQuicksand = CheckForMarioBlocks = CheckForFloatBob = CheckForReverseGravity = false;
6032
6033 // Set curWeather
6034 switch (mapheaderinfo[gamemap-1]->weather)
6035 {
6036 case PRECIP_SNOW: // snow
6037 case PRECIP_RAIN: // rain
6038 case PRECIP_STORM: // storm
6039 case PRECIP_STORM_NORAIN: // storm w/o rain
6040 case PRECIP_STORM_NOSTRIKES: // storm w/o lightning
6041 curWeather = mapheaderinfo[gamemap-1]->weather;
6042 break;
6043 default: // blank/none
6044 curWeather = PRECIP_NONE;
6045 break;
6046 }
6047
6048 // Set globalweather
6049 globalweather = mapheaderinfo[gamemap-1]->weather;
6050 }
6051
P_ApplyFlatAlignment(line_t * master,sector_t * sector,angle_t flatangle,fixed_t xoffs,fixed_t yoffs)6052 static void P_ApplyFlatAlignment(line_t *master, sector_t *sector, angle_t flatangle, fixed_t xoffs, fixed_t yoffs)
6053 {
6054 if (!(master->flags & ML_NETONLY)) // Modify floor flat alignment unless ML_NETONLY flag is set
6055 {
6056 sector->floorpic_angle = flatangle;
6057 sector->floor_xoffs += xoffs;
6058 sector->floor_yoffs += yoffs;
6059 }
6060
6061 if (!(master->flags & ML_NONET)) // Modify ceiling flat alignment unless ML_NONET flag is set
6062 {
6063 sector->ceilingpic_angle = flatangle;
6064 sector->ceiling_xoffs += xoffs;
6065 sector->ceiling_yoffs += yoffs;
6066 }
6067
6068 }
6069
6070 /** After the map has loaded, scans for specials that spawn 3Dfloors and
6071 * thinkers.
6072 *
6073 * \todo Split up into multiple functions.
6074 * \todo Get rid of all the magic numbers.
6075 * \todo Potentially use 'fromnetsave' to stop any new thinkers from being created
6076 * as they'll just be erased by UnArchiveThinkers.
6077 * \sa P_SpawnPrecipitation, P_SpawnFriction, P_SpawnPushers, P_SpawnScrollers
6078 */
P_SpawnSpecials(boolean fromnetsave)6079 void P_SpawnSpecials(boolean fromnetsave)
6080 {
6081 sector_t *sector;
6082 size_t i;
6083 INT32 j;
6084 thinkerlist_t *secthinkers;
6085 thinker_t *th;
6086 // This used to be used, and *should* be used in the future,
6087 // but currently isn't.
6088 (void)fromnetsave;
6089
6090 // yep, we do this here - "bossdisabled" is considered an apparatus of specials.
6091 bossdisabled = 0;
6092 stoppedclock = false;
6093
6094 // Init special SECTORs.
6095 sector = sectors;
6096 for (i = 0; i < numsectors; i++, sector++)
6097 {
6098 if (!sector->special)
6099 continue;
6100
6101 // Process Section 1
6102 switch(GETSECSPECIAL(sector->special, 1))
6103 {
6104 case 5: // Spikes
6105 //Terrible hack to replace an even worse hack:
6106 //Spike damage automatically sets SF_TRIGGERSPECIAL_TOUCH.
6107 //Yes, this also affects other specials on the same sector. Sorry.
6108 sector->flags |= SF_TRIGGERSPECIAL_TOUCH;
6109 break;
6110 case 15: // Bouncy sector
6111 CheckForBouncySector = true;
6112 break;
6113 }
6114
6115 // Process Section 2
6116 switch(GETSECSPECIAL(sector->special, 2))
6117 {
6118 case 10: // Time for special stage
6119 sstimer = (sector->floorheight>>FRACBITS) * TICRATE + 6; // Time to finish
6120 ssspheres = sector->ceilingheight>>FRACBITS; // Ring count for special stage
6121 break;
6122
6123 case 11: // Custom global gravity!
6124 gravity = sector->floorheight/1000;
6125 break;
6126 }
6127
6128 // Process Section 3
6129 /* switch(GETSECSPECIAL(player->specialsector, 3))
6130 {
6131
6132 }*/
6133
6134 // Process Section 4
6135 switch(GETSECSPECIAL(sector->special, 4))
6136 {
6137 case 10: // Circuit finish line
6138 if ((gametyperules & (GTR_RACE|GTR_LIVES)) == GTR_RACE)
6139 circuitmap = true;
6140 break;
6141 }
6142 }
6143
6144 P_SpawnScrollers(); // Add generalized scrollers
6145 P_SpawnFriction(); // Friction model using linedefs
6146 P_SpawnPushers(); // Pusher model using linedefs
6147
6148 // Look for thinkers that affect FOFs, and sort them by sector
6149
6150 secthinkers = Z_Calloc(numsectors * sizeof(thinkerlist_t), PU_STATIC, NULL);
6151
6152 // Firstly, find out how many there are in each sector
6153 for (th = thlist[THINK_MAIN].next; th != &thlist[THINK_MAIN]; th = th->next)
6154 {
6155 if (th->function.acp1 == (actionf_p1)T_Friction)
6156 secthinkers[((friction_t *)th)->affectee].count++;
6157 else if (th->function.acp1 == (actionf_p1)T_Pusher)
6158 secthinkers[((pusher_t *)th)->affectee].count++;
6159 }
6160
6161 // Allocate each list, and then zero the count so we can use it to track
6162 // the end of the list as we add the thinkers
6163 for (i = 0; i < numsectors; i++)
6164 if(secthinkers[i].count > 0)
6165 {
6166 secthinkers[i].thinkers = Z_Malloc(secthinkers[i].count * sizeof(thinker_t *), PU_STATIC, NULL);
6167 secthinkers[i].count = 0;
6168 }
6169
6170 // Finally, populate the lists.
6171 for (th = thlist[THINK_MAIN].next; th != &thlist[THINK_MAIN]; th = th->next)
6172 {
6173 size_t secnum = (size_t)-1;
6174
6175 if (th->function.acp1 == (actionf_p1)T_Friction)
6176 secnum = ((friction_t *)th)->affectee;
6177 else if (th->function.acp1 == (actionf_p1)T_Pusher)
6178 secnum = ((pusher_t *)th)->affectee;
6179
6180 if (secnum != (size_t)-1)
6181 secthinkers[secnum].thinkers[secthinkers[secnum].count++] = th;
6182 }
6183
6184
6185 // Init line EFFECTs
6186 for (i = 0; i < numlines; i++)
6187 {
6188 mtag_t tag = Tag_FGet(&lines[i].tags);
6189
6190 if (lines[i].special != 7) // This is a hack. I can at least hope nobody wants to prevent flat alignment in netgames...
6191 {
6192 // set line specials to 0 here too, same reason as above
6193 if (netgame || multiplayer)
6194 {
6195 if (lines[i].flags & ML_NONET)
6196 {
6197 lines[i].special = 0;
6198 continue;
6199 }
6200 }
6201 else if (lines[i].flags & ML_NETONLY)
6202 {
6203 lines[i].special = 0;
6204 continue;
6205 }
6206 }
6207
6208 switch (lines[i].special)
6209 {
6210 INT32 s;
6211 size_t sec;
6212 ffloortype_e ffloorflags;
6213 TAG_ITER_DECLARECOUNTER(0);
6214
6215 case 1: // Definable gravity per sector
6216 sec = sides[*lines[i].sidenum].sector - sectors;
6217 TAG_ITER_SECTORS(0, tag, s)
6218 {
6219 sectors[s].gravity = §ors[sec].floorheight; // This allows it to change in realtime!
6220
6221 if (lines[i].flags & ML_NOCLIMB)
6222 sectors[s].verticalflip = true;
6223 else
6224 sectors[s].verticalflip = false;
6225
6226 CheckForReverseGravity = sectors[s].verticalflip;
6227 }
6228 break;
6229
6230 case 2: // Custom exit
6231 break;
6232
6233 case 3: // Zoom Tube Parameters
6234 break;
6235
6236 case 4: // Speed pad (combines with sector special Section3:5 or Section3:6)
6237 break;
6238
6239 case 5: // Change camera info
6240 sec = sides[*lines[i].sidenum].sector - sectors;
6241 TAG_ITER_SECTORS(0, tag, s)
6242 P_AddCameraScanner(§ors[sec], §ors[s], R_PointToAngle2(lines[i].v2->x, lines[i].v2->y, lines[i].v1->x, lines[i].v1->y));
6243 break;
6244
6245 case 7: // Flat alignment - redone by toast
6246 if ((lines[i].flags & (ML_NETONLY|ML_NONET)) != (ML_NETONLY|ML_NONET)) // If you can do something...
6247 {
6248 angle_t flatangle = InvAngle(R_PointToAngle2(lines[i].v1->x, lines[i].v1->y, lines[i].v2->x, lines[i].v2->y));
6249 fixed_t xoffs;
6250 fixed_t yoffs;
6251
6252 if (lines[i].flags & ML_EFFECT6) // Set offset through x and y texture offsets if ML_EFFECT6 flag is set
6253 {
6254 xoffs = sides[lines[i].sidenum[0]].textureoffset;
6255 yoffs = sides[lines[i].sidenum[0]].rowoffset;
6256 }
6257 else // Otherwise, set calculated offsets such that line's v1 is the apparent origin
6258 {
6259 xoffs = -lines[i].v1->x;
6260 yoffs = lines[i].v1->y;
6261 }
6262
6263 //If no tag is given, apply to front sector
6264 if (tag == 0)
6265 P_ApplyFlatAlignment(lines + i, lines[i].frontsector, flatangle, xoffs, yoffs);
6266 else
6267 {
6268 TAG_ITER_SECTORS(0, tag, s)
6269 P_ApplyFlatAlignment(lines + i, sectors + s, flatangle, xoffs, yoffs);
6270 }
6271 }
6272 else // Otherwise, print a helpful warning. Can I do no less?
6273 CONS_Alert(CONS_WARNING,
6274 M_GetText("Flat alignment linedef (tag %d) doesn't have anything to do.\nConsider changing the linedef's flag configuration or removing it entirely.\n"),
6275 tag);
6276 break;
6277
6278 case 8: // Sector Parameters
6279 TAG_ITER_SECTORS(0, tag, s)
6280 {
6281 if (lines[i].flags & ML_NOCLIMB)
6282 {
6283 sectors[s].flags &= ~SF_FLIPSPECIAL_FLOOR;
6284 sectors[s].flags |= SF_FLIPSPECIAL_CEILING;
6285 }
6286 else if (lines[i].flags & ML_EFFECT4)
6287 sectors[s].flags |= SF_FLIPSPECIAL_BOTH;
6288
6289 if (lines[i].flags & ML_EFFECT3)
6290 sectors[s].flags |= SF_TRIGGERSPECIAL_TOUCH;
6291 if (lines[i].flags & ML_EFFECT2)
6292 sectors[s].flags |= SF_TRIGGERSPECIAL_HEADBUMP;
6293
6294 if (lines[i].flags & ML_EFFECT1)
6295 sectors[s].flags |= SF_INVERTPRECIP;
6296
6297 if (lines[i].frontsector && GETSECSPECIAL(lines[i].frontsector->special, 4) == 12)
6298 sectors[s].camsec = sides[*lines[i].sidenum].sector-sectors;
6299 }
6300 break;
6301
6302 case 9: // Chain Parameters
6303 break;
6304
6305 case 10: // Vertical culling plane for sprites and FOFs
6306 TAG_ITER_SECTORS(0, tag, s)
6307 sectors[s].cullheight = &lines[i]; // This allows it to change in realtime!
6308 break;
6309
6310 case 50: // Insta-Lower Sector
6311 EV_DoFloor(&lines[i], instantLower);
6312 break;
6313
6314 case 51: // Instant raise for ceilings
6315 EV_DoCeiling(&lines[i], instantRaise);
6316 break;
6317
6318 case 52: // Continuously Falling sector
6319 EV_DoContinuousFall(lines[i].frontsector, lines[i].backsector, P_AproxDistance(lines[i].dx, lines[i].dy), (lines[i].flags & ML_NOCLIMB));
6320 break;
6321
6322 case 53: // New super cool and awesome moving floor and ceiling type
6323 case 54: // New super cool and awesome moving floor type
6324 if (lines[i].backsector)
6325 EV_DoFloor(&lines[i], bounceFloor);
6326 if (lines[i].special == 54)
6327 break;
6328 /* FALLTHRU */
6329
6330 case 55: // New super cool and awesome moving ceiling type
6331 if (lines[i].backsector)
6332 EV_DoCeiling(&lines[i], bounceCeiling);
6333 break;
6334
6335 case 56: // New super cool and awesome moving floor and ceiling crush type
6336 case 57: // New super cool and awesome moving floor crush type
6337 if (lines[i].backsector)
6338 EV_DoFloor(&lines[i], bounceFloorCrush);
6339
6340 if (lines[i].special == 57)
6341 break; //only move the floor
6342 /* FALLTHRU */
6343
6344 case 58: // New super cool and awesome moving ceiling crush type
6345 if (lines[i].backsector)
6346 EV_DoCeiling(&lines[i], bounceCeilingCrush);
6347 break;
6348
6349 case 59: // Activate floating platform
6350 EV_DoElevator(&lines[i], elevateContinuous, false);
6351 break;
6352
6353 case 60: // Floating platform with adjustable speed
6354 EV_DoElevator(&lines[i], elevateContinuous, true);
6355 break;
6356
6357 case 61: // Crusher!
6358 EV_DoCrush(&lines[i], crushAndRaise);
6359 break;
6360
6361 case 62: // Crusher (up and then down)!
6362 EV_DoCrush(&lines[i], fastCrushAndRaise);
6363 break;
6364
6365 case 63: // support for drawn heights coming from different sector
6366 sec = sides[*lines[i].sidenum].sector-sectors;
6367 TAG_ITER_SECTORS(0, tag, s)
6368 sectors[s].heightsec = (INT32)sec;
6369 break;
6370
6371 case 64: // Appearing/Disappearing FOF option
6372 if (lines[i].flags & ML_BLOCKMONSTERS) { // Find FOFs by control sector tag
6373 TAG_ITER_SECTORS(0, tag, s)
6374 for (j = 0; (unsigned)j < sectors[s].linecount; j++)
6375 if (sectors[s].lines[j]->special >= 100 && sectors[s].lines[j]->special < 300)
6376 Add_MasterDisappearer(abs(lines[i].dx>>FRACBITS), abs(lines[i].dy>>FRACBITS), abs(sides[lines[i].sidenum[0]].sector->floorheight>>FRACBITS), (INT32)(sectors[s].lines[j]-lines), (INT32)i);
6377 } else // Find FOFs by effect sector tag
6378 {
6379 TAG_ITER_LINES(0, tag, s)
6380 {
6381 if ((size_t)s == i)
6382 continue;
6383 if (Tag_Find(&sides[lines[s].sidenum[0]].sector->tags, Tag_FGet(&sides[lines[i].sidenum[0]].sector->tags)))
6384 Add_MasterDisappearer(abs(lines[i].dx>>FRACBITS), abs(lines[i].dy>>FRACBITS), abs(sides[lines[i].sidenum[0]].sector->floorheight>>FRACBITS), s, (INT32)i);
6385 }
6386 }
6387 break;
6388
6389 case 66: // Displace floor by front sector
6390 TAG_ITER_SECTORS(0, tag, s)
6391 P_AddPlaneDisplaceThinker(pd_floor, P_AproxDistance(lines[i].dx, lines[i].dy)>>8, sides[lines[i].sidenum[0]].sector-sectors, s, !!(lines[i].flags & ML_NOCLIMB));
6392 break;
6393 case 67: // Displace ceiling by front sector
6394 TAG_ITER_SECTORS(0, tag, s)
6395 P_AddPlaneDisplaceThinker(pd_ceiling, P_AproxDistance(lines[i].dx, lines[i].dy)>>8, sides[lines[i].sidenum[0]].sector-sectors, s, !!(lines[i].flags & ML_NOCLIMB));
6396 break;
6397 case 68: // Displace both floor AND ceiling by front sector
6398 TAG_ITER_SECTORS(0, tag, s)
6399 P_AddPlaneDisplaceThinker(pd_both, P_AproxDistance(lines[i].dx, lines[i].dy)>>8, sides[lines[i].sidenum[0]].sector-sectors, s, !!(lines[i].flags & ML_NOCLIMB));
6400 break;
6401
6402 case 100: // FOF (solid, opaque, shadows)
6403 P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL, secthinkers);
6404 break;
6405
6406 case 101: // FOF (solid, opaque, no shadows)
6407 P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_NOSHADE|FF_CUTLEVEL, secthinkers);
6408 break;
6409
6410 case 102: // TL block: FOF (solid, translucent)
6411 ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_NOSHADE|FF_TRANSLUCENT|FF_EXTRA|FF_CUTEXTRA;
6412
6413 // Draw the 'insides' of the block too
6414 if (lines[i].flags & ML_NOCLIMB)
6415 {
6416 ffloorflags |= FF_CUTLEVEL|FF_BOTHPLANES|FF_ALLSIDES;
6417 ffloorflags &= ~(FF_EXTRA|FF_CUTEXTRA);
6418 }
6419
6420 P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
6421 break;
6422
6423 case 103: // Solid FOF with no floor/ceiling (quite possibly useless)
6424 P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERSIDES|FF_NOSHADE|FF_CUTLEVEL, secthinkers);
6425 break;
6426
6427 case 104: // 3D Floor type that doesn't draw sides
6428 // If line has no-climb set, give it shadows, otherwise don't
6429 ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERPLANES|FF_CUTLEVEL;
6430 if (!(lines[i].flags & ML_NOCLIMB))
6431 ffloorflags |= FF_NOSHADE;
6432
6433 P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
6434 break;
6435
6436 case 105: // FOF (solid, invisible)
6437 P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_NOSHADE, secthinkers);
6438 break;
6439
6440 case 120: // Opaque water
6441 ffloorflags = FF_EXISTS|FF_RENDERALL|FF_SWIMMABLE|FF_BOTHPLANES|FF_ALLSIDES|FF_CUTEXTRA|FF_EXTRA|FF_CUTSPRITES;
6442 if (lines[i].flags & ML_NOCLIMB)
6443 ffloorflags |= FF_DOUBLESHADOW;
6444 if (lines[i].flags & ML_EFFECT4)
6445 ffloorflags |= FF_COLORMAPONLY;
6446 if (lines[i].flags & ML_EFFECT5)
6447 ffloorflags |= FF_RIPPLE;
6448 P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
6449 break;
6450
6451 case 121: // TL water
6452 ffloorflags = FF_EXISTS|FF_RENDERALL|FF_TRANSLUCENT|FF_SWIMMABLE|FF_BOTHPLANES|FF_ALLSIDES|FF_CUTEXTRA|FF_EXTRA|FF_CUTSPRITES;
6453 if (lines[i].flags & ML_NOCLIMB)
6454 ffloorflags |= FF_DOUBLESHADOW;
6455 if (lines[i].flags & ML_EFFECT4)
6456 ffloorflags |= FF_COLORMAPONLY;
6457 if (lines[i].flags & ML_EFFECT5)
6458 ffloorflags |= FF_RIPPLE;
6459 P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
6460 break;
6461
6462 case 122: // Opaque water, no sides
6463 ffloorflags = FF_EXISTS|FF_RENDERPLANES|FF_SWIMMABLE|FF_BOTHPLANES|FF_CUTEXTRA|FF_EXTRA|FF_CUTSPRITES;
6464 if (lines[i].flags & ML_NOCLIMB)
6465 ffloorflags |= FF_DOUBLESHADOW;
6466 if (lines[i].flags & ML_EFFECT4)
6467 ffloorflags |= FF_COLORMAPONLY;
6468 if (lines[i].flags & ML_EFFECT5)
6469 ffloorflags |= FF_RIPPLE;
6470 P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
6471 break;
6472
6473 case 123: // TL water, no sides
6474 ffloorflags = FF_EXISTS|FF_RENDERPLANES|FF_TRANSLUCENT|FF_SWIMMABLE|FF_BOTHPLANES|FF_CUTEXTRA|FF_EXTRA|FF_CUTSPRITES;
6475 if (lines[i].flags & ML_NOCLIMB)
6476 ffloorflags |= FF_DOUBLESHADOW;
6477 if (lines[i].flags & ML_EFFECT4)
6478 ffloorflags |= FF_COLORMAPONLY;
6479 if (lines[i].flags & ML_EFFECT5)
6480 ffloorflags |= FF_RIPPLE;
6481 P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
6482 break;
6483
6484 case 124: // goo water
6485 ffloorflags = FF_EXISTS|FF_RENDERALL|FF_TRANSLUCENT|FF_SWIMMABLE|FF_GOOWATER|FF_BOTHPLANES|FF_ALLSIDES|FF_CUTEXTRA|FF_EXTRA|FF_CUTSPRITES;
6486 if (lines[i].flags & ML_NOCLIMB)
6487 ffloorflags |= FF_DOUBLESHADOW;
6488 if (lines[i].flags & ML_EFFECT4)
6489 ffloorflags |= FF_COLORMAPONLY;
6490 if (lines[i].flags & ML_EFFECT5)
6491 ffloorflags |= FF_RIPPLE;
6492 P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
6493 break;
6494
6495 case 125: // goo water, no sides
6496 ffloorflags = FF_EXISTS|FF_RENDERPLANES|FF_TRANSLUCENT|FF_SWIMMABLE|FF_GOOWATER|FF_BOTHPLANES|FF_CUTEXTRA|FF_EXTRA|FF_CUTSPRITES;
6497 if (lines[i].flags & ML_NOCLIMB)
6498 ffloorflags |= FF_DOUBLESHADOW;
6499 if (lines[i].flags & ML_EFFECT4)
6500 ffloorflags |= FF_COLORMAPONLY;
6501 if (lines[i].flags & ML_EFFECT5)
6502 ffloorflags |= FF_RIPPLE;
6503 P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
6504 break;
6505
6506 case 140: // 'Platform' - You can jump up through it
6507 // If line has no-climb set, don't give it shadows, otherwise do
6508 ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_PLATFORM|FF_BOTHPLANES|FF_ALLSIDES;
6509 if (lines[i].flags & ML_NOCLIMB)
6510 ffloorflags |= FF_NOSHADE;
6511
6512 P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
6513 break;
6514
6515 case 141: // Translucent "platform"
6516 // If line has no-climb set, don't give it shadows, otherwise do
6517 ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_PLATFORM|FF_TRANSLUCENT|FF_EXTRA|FF_CUTEXTRA;
6518 if (lines[i].flags & ML_NOCLIMB)
6519 ffloorflags |= FF_NOSHADE;
6520
6521 // Draw the 'insides' of the block too
6522 if (lines[i].flags & ML_EFFECT2)
6523 {
6524 ffloorflags |= FF_CUTLEVEL|FF_BOTHPLANES|FF_ALLSIDES;
6525 ffloorflags &= ~(FF_EXTRA|FF_CUTEXTRA);
6526 }
6527
6528 P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
6529 break;
6530
6531 case 142: // Translucent "platform" with no sides
6532 ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERPLANES|FF_TRANSLUCENT|FF_PLATFORM|FF_EXTRA|FF_CUTEXTRA;
6533 if (lines[i].flags & ML_NOCLIMB) // shade it unless no-climb
6534 ffloorflags |= FF_NOSHADE;
6535
6536 // Draw the 'insides' of the block too
6537 if (lines[i].flags & ML_EFFECT2)
6538 {
6539 ffloorflags |= FF_CUTLEVEL|FF_BOTHPLANES|FF_ALLSIDES;
6540 ffloorflags &= ~(FF_EXTRA|FF_CUTEXTRA);
6541 }
6542
6543 P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
6544 break;
6545
6546 case 143: // 'Reverse platform' - You fall through it
6547 // If line has no-climb set, don't give it shadows, otherwise do
6548 ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_REVERSEPLATFORM|FF_BOTHPLANES|FF_ALLSIDES;
6549 if (lines[i].flags & ML_NOCLIMB)
6550 ffloorflags |= FF_NOSHADE;
6551
6552 P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
6553 break;
6554
6555 case 144: // Translucent "reverse platform"
6556 // If line has no-climb set, don't give it shadows, otherwise do
6557 ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_REVERSEPLATFORM|FF_TRANSLUCENT|FF_EXTRA|FF_CUTEXTRA;
6558 if (lines[i].flags & ML_NOCLIMB)
6559 ffloorflags |= FF_NOSHADE;
6560
6561 // Draw the 'insides' of the block too
6562 if (lines[i].flags & ML_EFFECT2)
6563 {
6564 ffloorflags |= FF_CUTLEVEL|FF_BOTHPLANES|FF_ALLSIDES;
6565 ffloorflags &= ~(FF_EXTRA|FF_CUTEXTRA);
6566 }
6567
6568 P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
6569 break;
6570
6571 case 145: // Translucent "reverse platform" with no sides
6572 ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERPLANES|FF_TRANSLUCENT|FF_REVERSEPLATFORM|FF_EXTRA|FF_CUTEXTRA;
6573 if (lines[i].flags & ML_NOCLIMB) // shade it unless no-climb
6574 ffloorflags |= FF_NOSHADE;
6575
6576 // Draw the 'insides' of the block too
6577 if (lines[i].flags & ML_EFFECT2)
6578 {
6579 ffloorflags |= FF_CUTLEVEL|FF_BOTHPLANES|FF_ALLSIDES;
6580 ffloorflags &= ~(FF_EXTRA|FF_CUTEXTRA);
6581 }
6582
6583 P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
6584 break;
6585
6586 case 146: // Intangible floor/ceiling with solid sides (fences/hoops maybe?)
6587 P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERSIDES|FF_ALLSIDES|FF_INTANGIBLEFLATS, secthinkers);
6588 break;
6589
6590 case 150: // Air bobbing platform
6591 case 151: // Adjustable air bobbing platform
6592 {
6593 fixed_t dist = (lines[i].special == 150) ? 16*FRACUNIT : P_AproxDistance(lines[i].dx, lines[i].dy);
6594 P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL, secthinkers);
6595 P_AddAirbob(lines[i].frontsector, tag, dist, false, !!(lines[i].flags & ML_NOCLIMB), false);
6596 break;
6597 }
6598 case 152: // Adjustable air bobbing platform in reverse
6599 P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL, secthinkers);
6600 P_AddAirbob(lines[i].frontsector, tag, P_AproxDistance(lines[i].dx, lines[i].dy), true, !!(lines[i].flags & ML_NOCLIMB), false);
6601 break;
6602 case 153: // Dynamic Sinking Platform
6603 P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL, secthinkers);
6604 P_AddAirbob(lines[i].frontsector, tag, P_AproxDistance(lines[i].dx, lines[i].dy), false, !!(lines[i].flags & ML_NOCLIMB), true);
6605 break;
6606
6607 case 160: // Float/bob platform
6608 P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_FLOATBOB, secthinkers);
6609 break;
6610
6611 case 170: // Crumbling platform
6612 P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_CRUMBLE, secthinkers);
6613 break;
6614
6615 case 171: // Crumbling platform that will not return
6616 P_AddFakeFloorsByLine(i,
6617 FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_CRUMBLE|FF_NORETURN, secthinkers);
6618 break;
6619
6620 case 172: // "Platform" that crumbles and returns
6621 ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_PLATFORM|FF_CRUMBLE|FF_BOTHPLANES|FF_ALLSIDES;
6622 if (lines[i].flags & ML_NOCLIMB) // shade it unless no-climb
6623 ffloorflags |= FF_NOSHADE;
6624
6625 P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
6626 break;
6627
6628 case 173: // "Platform" that crumbles and doesn't return
6629 ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_PLATFORM|FF_CRUMBLE|FF_NORETURN|FF_BOTHPLANES|FF_ALLSIDES;
6630 if (lines[i].flags & ML_NOCLIMB) // shade it unless no-climb
6631 ffloorflags |= FF_NOSHADE;
6632
6633 P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
6634 break;
6635
6636 case 174: // Translucent "platform" that crumbles and returns
6637 ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_PLATFORM|FF_CRUMBLE|FF_TRANSLUCENT|FF_BOTHPLANES|FF_ALLSIDES;
6638 if (lines[i].flags & ML_NOCLIMB) // shade it unless no-climb
6639 ffloorflags |= FF_NOSHADE;
6640
6641 P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
6642 break;
6643
6644 case 175: // Translucent "platform" that crumbles and doesn't return
6645 ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_PLATFORM|FF_CRUMBLE|FF_NORETURN|FF_TRANSLUCENT|FF_BOTHPLANES|FF_ALLSIDES;
6646 if (lines[i].flags & ML_NOCLIMB) // shade it unless no-climb
6647 ffloorflags |= FF_NOSHADE;
6648
6649 P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
6650 break;
6651
6652 case 176: // Air bobbing platform that will crumble and bob on the water when it falls and hits
6653 P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_FLOATBOB|FF_CRUMBLE, secthinkers);
6654 P_AddAirbob(lines[i].frontsector, tag, 16*FRACUNIT, false, !!(lines[i].flags & ML_NOCLIMB), false);
6655 break;
6656
6657 case 177: // Air bobbing platform that will crumble and bob on
6658 // the water when it falls and hits, then never return
6659 P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_FLOATBOB|FF_CRUMBLE|FF_NORETURN, secthinkers);
6660 P_AddAirbob(lines[i].frontsector, tag, 16*FRACUNIT, false, !!(lines[i].flags & ML_NOCLIMB), false);
6661 break;
6662
6663 case 178: // Crumbling platform that will float when it hits water
6664 P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CRUMBLE|FF_FLOATBOB, secthinkers);
6665 break;
6666
6667 case 179: // Crumbling platform that will float when it hits water, but not return
6668 P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_CRUMBLE|FF_FLOATBOB|FF_NORETURN, secthinkers);
6669 break;
6670
6671 case 180: // Air bobbing platform that will crumble
6672 P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_CRUMBLE, secthinkers);
6673 P_AddAirbob(lines[i].frontsector, tag, 16*FRACUNIT, false, !!(lines[i].flags & ML_NOCLIMB), false);
6674 break;
6675
6676 case 190: // Rising Platform FOF (solid, opaque, shadows)
6677 case 191: // Rising Platform FOF (solid, opaque, no shadows)
6678 case 192: // Rising Platform TL block: FOF (solid, translucent)
6679 case 193: // Rising Platform FOF (solid, invisible)
6680 case 194: // Rising Platform 'Platform' - You can jump up through it
6681 case 195: // Rising Platform Translucent "platform"
6682 {
6683 fixed_t speed = FixedDiv(P_AproxDistance(lines[i].dx, lines[i].dy), 4*FRACUNIT);
6684 fixed_t ceilingtop = P_FindHighestCeilingSurrounding(lines[i].frontsector);
6685 fixed_t ceilingbottom = P_FindLowestCeilingSurrounding(lines[i].frontsector);
6686
6687 ffloorflags = FF_EXISTS|FF_SOLID;
6688 if (lines[i].special != 193)
6689 ffloorflags |= FF_RENDERALL;
6690 if (lines[i].special <= 191)
6691 ffloorflags |= FF_CUTLEVEL;
6692 if (lines[i].special == 192 || lines[i].special == 195)
6693 ffloorflags |= FF_TRANSLUCENT|FF_EXTRA|FF_CUTEXTRA;
6694 if (lines[i].special >= 194)
6695 ffloorflags |= FF_PLATFORM|FF_BOTHPLANES|FF_ALLSIDES;
6696 if (lines[i].special != 190 && (lines[i].special <= 193 || lines[i].flags & ML_NOCLIMB))
6697 ffloorflags |= FF_NOSHADE;
6698 P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
6699
6700 P_AddRaiseThinker(lines[i].frontsector, tag, speed, ceilingtop, ceilingbottom, !!(lines[i].flags & ML_BLOCKMONSTERS), !!(lines[i].flags & ML_NOCLIMB));
6701 break;
6702 }
6703
6704 case 200: // Double light effect
6705 P_AddFakeFloorsByLine(i, FF_EXISTS|FF_CUTSPRITES|FF_DOUBLESHADOW, secthinkers);
6706 break;
6707
6708 case 201: // Light effect
6709 P_AddFakeFloorsByLine(i, FF_EXISTS|FF_CUTSPRITES, secthinkers);
6710 break;
6711
6712 case 202: // Fog
6713 ffloorflags = FF_EXISTS|FF_RENDERALL|FF_FOG|FF_INVERTPLANES|FF_INVERTSIDES|FF_CUTEXTRA|FF_EXTRA|FF_DOUBLESHADOW|FF_CUTSPRITES;
6714 sec = sides[*lines[i].sidenum].sector - sectors;
6715 // SoM: Because it's fog, check for an extra colormap and set the fog flag...
6716 if (sectors[sec].extra_colormap)
6717 sectors[sec].extra_colormap->flags = CMF_FOG;
6718 P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
6719 break;
6720
6721 case 220: // Like opaque water, but not swimmable. (Good for snow effect on FOFs)
6722 P_AddFakeFloorsByLine(i, FF_EXISTS|FF_RENDERALL|FF_BOTHPLANES|FF_ALLSIDES|FF_CUTEXTRA|FF_EXTRA|FF_CUTSPRITES, secthinkers);
6723 break;
6724
6725 case 221: // FOF (intangible, translucent)
6726 // If line has no-climb set, give it shadows, otherwise don't
6727 ffloorflags = FF_EXISTS|FF_RENDERALL|FF_TRANSLUCENT|FF_EXTRA|FF_CUTEXTRA|FF_CUTSPRITES;
6728 if (!(lines[i].flags & ML_NOCLIMB))
6729 ffloorflags |= FF_NOSHADE;
6730
6731 P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
6732 break;
6733
6734 case 222: // FOF with no floor/ceiling (good for GFZGRASS effect on FOFs)
6735 // If line has no-climb set, give it shadows, otherwise don't
6736 ffloorflags = FF_EXISTS|FF_RENDERSIDES|FF_ALLSIDES;
6737 if (!(lines[i].flags & ML_NOCLIMB))
6738 ffloorflags |= FF_NOSHADE|FF_CUTSPRITES;
6739
6740 P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
6741 break;
6742
6743 case 223: // FOF (intangible, invisible) - for combining specials in a sector
6744 P_AddFakeFloorsByLine(i, FF_EXISTS|FF_NOSHADE, secthinkers);
6745 break;
6746
6747 case 250: // Mario Block
6748 ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_MARIO;
6749 if (lines[i].flags & ML_NOCLIMB)
6750 ffloorflags |= FF_SHATTERBOTTOM;
6751 if (lines[i].flags & ML_EFFECT1)
6752 ffloorflags &= ~(FF_SOLID|FF_RENDERALL|FF_CUTLEVEL);
6753
6754 P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
6755 break;
6756
6757 case 251: // A THWOMP!
6758 {
6759 fixed_t crushspeed = (lines[i].flags & ML_EFFECT5) ? lines[i].dy >> 3 : 10*FRACUNIT;
6760 fixed_t retractspeed = (lines[i].flags & ML_EFFECT5) ? lines[i].dx >> 3 : 2*FRACUNIT;
6761 UINT16 sound = (lines[i].flags & ML_EFFECT4) ? sides[lines[i].sidenum[0]].textureoffset >> FRACBITS : sfx_thwomp;
6762 P_AddThwompThinker(lines[i].frontsector, &lines[i], crushspeed, retractspeed, sound);
6763 P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL, secthinkers);
6764 break;
6765 }
6766
6767 case 252: // Shatter block (breaks when touched)
6768 ffloorflags = FF_EXISTS|FF_BLOCKOTHERS|FF_RENDERALL|FF_BUSTUP|FF_SHATTER;
6769 if (lines[i].flags & ML_NOCLIMB)
6770 ffloorflags |= FF_BLOCKPLAYER|FF_SHATTERBOTTOM;
6771
6772 P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
6773 break;
6774
6775 case 253: // Translucent shatter block (see 76)
6776 P_AddFakeFloorsByLine(i, FF_EXISTS|FF_BLOCKOTHERS|FF_RENDERALL|FF_BUSTUP|FF_SHATTER|FF_TRANSLUCENT, secthinkers);
6777 break;
6778
6779 case 254: // Bustable block
6780 ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_BUSTUP;
6781 if (lines[i].flags & ML_NOCLIMB)
6782 ffloorflags |= FF_STRONGBUST;
6783
6784 P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
6785 break;
6786
6787 case 255: // Spin bust block (breaks when jumped or spun downwards onto)
6788 P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_BUSTUP|FF_SPINBUST, secthinkers);
6789 break;
6790
6791 case 256: // Translucent spin bust block (see 78)
6792 P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_BUSTUP|FF_SPINBUST|FF_TRANSLUCENT, secthinkers);
6793 break;
6794
6795 case 257: // Quicksand
6796 ffloorflags = FF_EXISTS|FF_QUICKSAND|FF_RENDERALL|FF_ALLSIDES|FF_CUTSPRITES;
6797 if (lines[i].flags & ML_EFFECT5)
6798 ffloorflags |= FF_RIPPLE;
6799
6800 P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
6801 break;
6802
6803 case 258: // Laser block
6804 P_AddLaserThinker(tag, lines + i, !!(lines[i].flags & ML_EFFECT1));
6805 P_AddFakeFloorsByLine(i, FF_EXISTS|FF_RENDERALL|FF_NOSHADE|FF_EXTRA|FF_CUTEXTRA|FF_TRANSLUCENT, secthinkers);
6806 break;
6807
6808 case 259: // Custom FOF
6809 if (lines[i].sidenum[1] != 0xffff)
6810 {
6811 ffloortype_e fofflags = sides[lines[i].sidenum[1]].toptexture;
6812 P_AddFakeFloorsByLine(i, fofflags, secthinkers);
6813 }
6814 else
6815 I_Error("Custom FOF (tag %d) found without a linedef back side!", tag);
6816 break;
6817
6818 case 300: // Linedef executor (combines with sector special 974/975) and commands
6819 case 302:
6820 case 303:
6821 case 304:
6822
6823 // Charability linedef executors
6824 case 305:
6825 case 307:
6826 break;
6827
6828 case 308: // Race-only linedef executor. Triggers once.
6829 if (!(gametyperules & GTR_RACE))
6830 lines[i].special = 0;
6831 break;
6832
6833 // Linedef executor triggers for CTF teams.
6834 case 309:
6835 case 311:
6836 if (!(gametyperules & GTR_TEAMFLAGS))
6837 lines[i].special = 0;
6838 break;
6839
6840 // Each time executors
6841 case 306:
6842 case 301:
6843 case 310:
6844 case 312:
6845 case 332:
6846 case 335:
6847 P_AddEachTimeThinker(&lines[i]);
6848 break;
6849
6850 // No More Enemies Linedef Exec
6851 case 313:
6852 P_AddNoEnemiesThinker(&lines[i]);
6853 break;
6854
6855 // Pushable linedef executors (count # of pushables)
6856 case 314:
6857 case 315:
6858 break;
6859
6860 // Unlock trigger executors
6861 case 317:
6862 case 318:
6863 break;
6864 case 319:
6865 case 320:
6866 break;
6867
6868 // Trigger on X calls
6869 case 321:
6870 case 322:
6871 if (lines[i].flags & ML_NOCLIMB && sides[lines[i].sidenum[0]].rowoffset > 0) // optional "starting" count
6872 lines[i].callcount = sides[lines[i].sidenum[0]].rowoffset>>FRACBITS;
6873 else
6874 lines[i].callcount = sides[lines[i].sidenum[0]].textureoffset>>FRACBITS;
6875 if (lines[i].special == 322) // Each time
6876 P_AddEachTimeThinker(&lines[i]);
6877 break;
6878
6879 // NiGHTS trigger executors
6880 case 323:
6881 case 324:
6882 case 325:
6883 case 326:
6884 case 327:
6885 case 328:
6886 case 329:
6887 case 330:
6888 break;
6889
6890 // Skin trigger executors
6891 case 331:
6892 case 333:
6893 break;
6894
6895 // Object dye executors
6896 case 334:
6897 case 336:
6898 break;
6899
6900 case 399: // Linedef execute on map load
6901 // This is handled in P_RunLevelLoadExecutors.
6902 break;
6903
6904 case 400:
6905 case 401:
6906 case 402:
6907 case 403:
6908 case 404:
6909 case 405:
6910 case 406:
6911 case 407:
6912 case 408:
6913 case 409:
6914 case 410:
6915 case 411:
6916 case 412:
6917 case 413:
6918 case 414:
6919 case 415:
6920 case 416:
6921 case 417:
6922 case 418:
6923 case 419:
6924 case 420:
6925 case 421:
6926 case 422:
6927 case 423:
6928 case 424:
6929 case 425:
6930 case 426:
6931 case 427:
6932 case 428:
6933 case 429:
6934 case 430:
6935 case 431:
6936 break;
6937
6938 case 449: // Enable bosses with parameter
6939 {
6940 INT32 bossid = sides[*lines[i].sidenum].textureoffset>>FRACBITS;
6941 if (bossid & ~15) // if any bits other than first 16 are set
6942 {
6943 CONS_Alert(CONS_WARNING,
6944 M_GetText("Boss enable linedef (tag %d) has an invalid texture x offset.\nConsider changing it or removing it entirely.\n"),
6945 tag);
6946 break;
6947 }
6948 if (!(lines[i].flags & ML_NOCLIMB))
6949 {
6950 bossdisabled |= (1<<bossid); // gotta disable in the first place to enable
6951 CONS_Debug(DBG_GAMELOGIC, "Line type 449 spawn effect: bossid disabled = %d", bossid);
6952 }
6953 break;
6954 }
6955
6956 // 500 is used for a scroller
6957 // 501 is used for a scroller
6958 // 502 is used for a scroller
6959 // 503 is used for a scroller
6960 // 504 is used for a scroller
6961 // 505 is used for a scroller
6962 // 506 is used for a scroller
6963 // 507 is used for a scroller
6964 // 508 is used for a scroller
6965 // 510 is used for a scroller
6966 // 511 is used for a scroller
6967 // 512 is used for a scroller
6968 // 513 is used for a scroller
6969 // 514 is used for a scroller
6970 // 515 is used for a scroller
6971 // 520 is used for a scroller
6972 // 521 is used for a scroller
6973 // 522 is used for a scroller
6974 // 523 is used for a scroller
6975 // 524 is used for a scroller
6976 // 525 is used for a scroller
6977 // 530 is used for a scroller
6978 // 531 is used for a scroller
6979 // 532 is used for a scroller
6980 // 533 is used for a scroller
6981 // 534 is used for a scroller
6982 // 535 is used for a scroller
6983 // 540 is used for friction
6984 // 541 is used for wind
6985 // 542 is used for upwards wind
6986 // 543 is used for downwards wind
6987 // 544 is used for current
6988 // 545 is used for upwards current
6989 // 546 is used for downwards current
6990 // 547 is used for push/pull
6991
6992 case 600: // floor lighting independently (e.g. lava)
6993 sec = sides[*lines[i].sidenum].sector-sectors;
6994 TAG_ITER_SECTORS(0, tag, s)
6995 sectors[s].floorlightsec = (INT32)sec;
6996 break;
6997
6998 case 601: // ceiling lighting independently
6999 sec = sides[*lines[i].sidenum].sector-sectors;
7000 TAG_ITER_SECTORS(0, tag, s)
7001 sectors[s].ceilinglightsec = (INT32)sec;
7002 break;
7003
7004 case 602: // Adjustable pulsating light
7005 sec = sides[*lines[i].sidenum].sector - sectors;
7006 TAG_ITER_SECTORS(0, tag, s)
7007 P_SpawnAdjustableGlowingLight(§ors[sec], §ors[s],
7008 P_AproxDistance(lines[i].dx, lines[i].dy)>>FRACBITS);
7009 break;
7010
7011 case 603: // Adjustable flickering light
7012 sec = sides[*lines[i].sidenum].sector - sectors;
7013 TAG_ITER_SECTORS(0, tag, s)
7014 P_SpawnAdjustableFireFlicker(§ors[sec], §ors[s],
7015 P_AproxDistance(lines[i].dx, lines[i].dy)>>FRACBITS);
7016 break;
7017
7018 case 604: // Adjustable Blinking Light (unsynchronized)
7019 sec = sides[*lines[i].sidenum].sector - sectors;
7020 TAG_ITER_SECTORS(0, tag, s)
7021 P_SpawnAdjustableStrobeFlash(§ors[sec], §ors[s],
7022 abs(lines[i].dx)>>FRACBITS, abs(lines[i].dy)>>FRACBITS, false);
7023 break;
7024
7025 case 605: // Adjustable Blinking Light (synchronized)
7026 sec = sides[*lines[i].sidenum].sector - sectors;
7027 TAG_ITER_SECTORS(0, tag, s)
7028 P_SpawnAdjustableStrobeFlash(§ors[sec], §ors[s],
7029 abs(lines[i].dx)>>FRACBITS, abs(lines[i].dy)>>FRACBITS, true);
7030 break;
7031
7032 case 606: // HACK! Copy colormaps. Just plain colormaps.
7033 TAG_ITER_SECTORS(0, lines[i].args[0], s)
7034 {
7035 extracolormap_t *exc;
7036
7037 if (sectors[s].colormap_protected)
7038 continue;
7039
7040 if (!udmf)
7041 exc = sides[lines[i].sidenum[0]].colormap_data;
7042 else
7043 {
7044 if (!lines[i].args[1])
7045 exc = lines[i].frontsector->extra_colormap;
7046 else
7047 {
7048 INT32 sourcesec = Tag_Iterate_Sectors(lines[i].args[1], 0);
7049 if (sourcesec == -1)
7050 {
7051 CONS_Debug(DBG_GAMELOGIC, "Line type 606: Can't find sector with source colormap (tag %d)!\n", lines[i].args[1]);
7052 return;
7053 }
7054 exc = sectors[sourcesec].extra_colormap;
7055 }
7056 }
7057 sectors[s].extra_colormap = sectors[s].spawn_extra_colormap = exc;
7058 }
7059 break;
7060
7061 default:
7062 break;
7063 }
7064 }
7065
7066 // Allocate each list
7067 for (i = 0; i < numsectors; i++)
7068 if(secthinkers[i].thinkers)
7069 Z_Free(secthinkers[i].thinkers);
7070
7071 Z_Free(secthinkers);
7072
7073 // haleyjd 02/20/06: spawn polyobjects
7074 Polyobj_InitLevel();
7075
7076 for (i = 0; i < numlines; i++)
7077 {
7078 switch (lines[i].special)
7079 {
7080 case 30: // Polyobj_Flag
7081 PolyFlag(&lines[i]);
7082 break;
7083
7084 case 31: // Polyobj_Displace
7085 PolyDisplace(&lines[i]);
7086 break;
7087
7088 case 32: // Polyobj_RotDisplace
7089 PolyRotDisplace(&lines[i]);
7090 break;
7091 }
7092 }
7093
7094 P_RunLevelLoadExecutors();
7095 }
7096
7097 /** Adds 3Dfloors as appropriate based on a common control linedef.
7098 *
7099 * \param line Control linedef to use.
7100 * \param ffloorflags 3Dfloor flags to use.
7101 * \param secthkiners Lists of thinkers sorted by sector. May be NULL.
7102 * \sa P_SpawnSpecials, P_AddFakeFloor
7103 * \author Graue <graue@oceanbase.org>
7104 */
P_AddFakeFloorsByLine(size_t line,ffloortype_e ffloorflags,thinkerlist_t * secthinkers)7105 static void P_AddFakeFloorsByLine(size_t line, ffloortype_e ffloorflags, thinkerlist_t *secthinkers)
7106 {
7107 TAG_ITER_DECLARECOUNTER(0);
7108 INT32 s;
7109 mtag_t tag = Tag_FGet(&lines[line].tags);
7110 size_t sec = sides[*lines[line].sidenum].sector-sectors;
7111
7112 line_t* li = lines + line;
7113 TAG_ITER_SECTORS(0, tag, s)
7114 P_AddFakeFloor(§ors[s], §ors[sec], li, ffloorflags, secthinkers);
7115 }
7116
7117 /*
7118 SoM: 3/8/2000: General scrolling functions.
7119 T_Scroll,
7120 Add_Scroller,
7121 Add_WallScroller,
7122 P_SpawnScrollers
7123 */
7124
7125 // helper function for T_Scroll
P_DoScrollMove(mobj_t * thing,fixed_t dx,fixed_t dy,INT32 exclusive)7126 static void P_DoScrollMove(mobj_t *thing, fixed_t dx, fixed_t dy, INT32 exclusive)
7127 {
7128 fixed_t fuckaj = 0; // Nov 05 14:12:08 <+MonsterIestyn> I've heard of explicitly defined variables but this is ridiculous
7129 if (thing->player)
7130 {
7131 if (!(dx | dy))
7132 {
7133 thing->player->cmomx = 0;
7134 thing->player->cmomy = 0;
7135 }
7136 else
7137 {
7138 thing->player->cmomx += dx;
7139 thing->player->cmomy += dy;
7140 thing->player->cmomx = FixedMul(thing->player->cmomx, 0xe800);
7141 thing->player->cmomy = FixedMul(thing->player->cmomy, 0xe800);
7142 }
7143 }
7144
7145 if (thing->player && (thing->player->pflags & PF_SPINNING) && (thing->player->rmomx || thing->player->rmomy) && !(thing->player->pflags & PF_STARTDASH))
7146 fuckaj = FixedDiv(549*ORIG_FRICTION,500*FRACUNIT);
7147 else if (thing->friction != ORIG_FRICTION)
7148 fuckaj = thing->friction;
7149
7150 if (fuckaj) {
7151 // refactor thrust for new friction
7152 dx = FixedDiv(dx, CARRYFACTOR);
7153 dy = FixedDiv(dy, CARRYFACTOR);
7154
7155 dx = FixedMul(dx, FRACUNIT-fuckaj);
7156 dy = FixedMul(dy, FRACUNIT-fuckaj);
7157 }
7158
7159 thing->momx += dx;
7160 thing->momy += dy;
7161
7162 if (exclusive)
7163 thing->eflags |= MFE_PUSHED;
7164 }
7165
7166 /** Processes an active scroller.
7167 * This function, with the help of r_plane.c and r_bsp.c, supports generalized
7168 * scrolling floors and walls, with optional mobj-carrying properties, e.g.
7169 * conveyor belts, rivers, etc. A linedef with a special type affects all
7170 * tagged sectors the same way, by creating scrolling and/or object-carrying
7171 * properties. Multiple linedefs may be used on the same sector and are
7172 * cumulative, although the special case of scrolling a floor and carrying
7173 * things on it requires only one linedef.
7174 *
7175 * The linedef's direction determines the scrolling direction, and the
7176 * linedef's length determines the scrolling speed. This was designed so an
7177 * edge around a sector can be used to control the direction of the sector's
7178 * scrolling, which is usually what is desired.
7179 *
7180 * \param s Thinker for the scroller to process.
7181 * \todo Split up into multiple functions.
7182 * \todo Use attached lists to make ::sc_carry_ceiling case faster and
7183 * cleaner.
7184 * \sa Add_Scroller, Add_WallScroller, P_SpawnScrollers
7185 * \author Steven McGranahan
7186 * \author Graue <graue@oceanbase.org>
7187 */
T_Scroll(scroll_t * s)7188 void T_Scroll(scroll_t *s)
7189 {
7190 fixed_t dx = s->dx, dy = s->dy;
7191 boolean is3dblock = false;
7192
7193 if (s->control != -1)
7194 { // compute scroll amounts based on a sector's height changes
7195 fixed_t height = sectors[s->control].floorheight +
7196 sectors[s->control].ceilingheight;
7197 fixed_t delta = height - s->last_height;
7198 s->last_height = height;
7199 dx = FixedMul(dx, delta);
7200 dy = FixedMul(dy, delta);
7201 }
7202
7203 if (s->accel)
7204 {
7205 s->vdx = dx += s->vdx;
7206 s->vdy = dy += s->vdy;
7207 }
7208
7209 // if (!(dx | dy)) // no-op if both (x,y) offsets 0
7210 // return;
7211
7212 switch (s->type)
7213 {
7214 side_t *side;
7215 sector_t *sec;
7216 fixed_t height;
7217 msecnode_t *node;
7218 mobj_t *thing;
7219 line_t *line;
7220 size_t i;
7221 INT32 sect;
7222 ffloor_t *rover;
7223 TAG_ITER_DECLARECOUNTER(0);
7224
7225 case sc_side: // scroll wall texture
7226 side = sides + s->affectee;
7227 side->textureoffset += dx;
7228 side->rowoffset += dy;
7229 break;
7230
7231 case sc_floor: // scroll floor texture
7232 sec = sectors + s->affectee;
7233 sec->floor_xoffs += dx;
7234 sec->floor_yoffs += dy;
7235 break;
7236
7237 case sc_ceiling: // scroll ceiling texture
7238 sec = sectors + s->affectee;
7239 sec->ceiling_xoffs += dx;
7240 sec->ceiling_yoffs += dy;
7241 break;
7242
7243 case sc_carry:
7244 sec = sectors + s->affectee;
7245 height = sec->floorheight;
7246
7247 // sec is the control sector, find the real sector(s) to use
7248 for (i = 0; i < sec->linecount; i++)
7249 {
7250 line = sec->lines[i];
7251
7252 if (line->special < 100 || line->special >= 300)
7253 is3dblock = false;
7254 else
7255 is3dblock = true;
7256
7257 if (!is3dblock)
7258 continue;
7259
7260 TAG_ITER_SECTORS(0, Tag_FGet(&line->tags), sect)
7261 {
7262 sector_t *psec;
7263 psec = sectors + sect;
7264
7265 // Find the FOF corresponding to the control linedef
7266 for (rover = psec->ffloors; rover; rover = rover->next)
7267 {
7268 if (rover->master == sec->lines[i])
7269 break;
7270 }
7271
7272 if (!rover) // This should be impossible, but don't complain if it is the case somehow
7273 continue;
7274
7275 if (!(rover->flags & FF_EXISTS)) // If the FOF does not "exist", we pretend that nobody's there
7276 continue;
7277
7278 for (node = psec->touching_thinglist; node; node = node->m_thinglist_next)
7279 {
7280 thing = node->m_thing;
7281
7282 if (thing->eflags & MFE_PUSHED) // Already pushed this tic by an exclusive pusher.
7283 continue;
7284
7285 height = P_GetSpecialBottomZ(thing, sec, psec);
7286
7287 if (!(thing->flags & MF_NOCLIP)) // Thing must be clipped
7288 if (!(thing->flags & MF_NOGRAVITY || thing->z+thing->height != height)) // Thing must a) be non-floating and have z+height == height
7289 {
7290 // Move objects only if on floor
7291 // non-floating, and clipped.
7292 P_DoScrollMove(thing, dx, dy, s->exclusive);
7293 }
7294 } // end of for loop through touching_thinglist
7295 } // end of loop through sectors
7296 }
7297
7298 if (!is3dblock)
7299 {
7300 for (node = sec->touching_thinglist; node; node = node->m_thinglist_next)
7301 {
7302 thing = node->m_thing;
7303
7304 if (thing->eflags & MFE_PUSHED)
7305 continue;
7306
7307 height = P_GetSpecialBottomZ(thing, sec, sec);
7308
7309 if (!(thing->flags & MF_NOCLIP) &&
7310 (!(thing->flags & MF_NOGRAVITY || thing->z > height)))
7311 {
7312 // Move objects only if on floor or underwater,
7313 // non-floating, and clipped.
7314 P_DoScrollMove(thing, dx, dy, s->exclusive);
7315 }
7316 }
7317 }
7318 break;
7319
7320 case sc_carry_ceiling: // carry on ceiling (FOF scrolling)
7321 sec = sectors + s->affectee;
7322 height = sec->ceilingheight;
7323
7324 // sec is the control sector, find the real sector(s) to use
7325 for (i = 0; i < sec->linecount; i++)
7326 {
7327 line = sec->lines[i];
7328 if (line->special < 100 || line->special >= 300)
7329 is3dblock = false;
7330 else
7331 is3dblock = true;
7332
7333 if (!is3dblock)
7334 continue;
7335 TAG_ITER_SECTORS(0, Tag_FGet(&line->tags), sect)
7336 {
7337 sector_t *psec;
7338 psec = sectors + sect;
7339
7340 // Find the FOF corresponding to the control linedef
7341 for (rover = psec->ffloors; rover; rover = rover->next)
7342 {
7343 if (rover->master == sec->lines[i])
7344 break;
7345 }
7346
7347 if (!rover) // This should be impossible, but don't complain if it is the case somehow
7348 continue;
7349
7350 if (!(rover->flags & FF_EXISTS)) // If the FOF does not "exist", we pretend that nobody's there
7351 continue;
7352
7353 for (node = psec->touching_thinglist; node; node = node->m_thinglist_next)
7354 {
7355 thing = node->m_thing;
7356
7357 if (thing->eflags & MFE_PUSHED)
7358 continue;
7359
7360 height = P_GetSpecialTopZ(thing, sec, psec);
7361
7362 if (!(thing->flags & MF_NOCLIP)) // Thing must be clipped
7363 if (!(thing->flags & MF_NOGRAVITY || thing->z != height))// Thing must a) be non-floating and have z == height
7364 {
7365 // Move objects only if on floor or underwater,
7366 // non-floating, and clipped.
7367 P_DoScrollMove(thing, dx, dy, s->exclusive);
7368 }
7369 } // end of for loop through touching_thinglist
7370 } // end of loop through sectors
7371 }
7372
7373 if (!is3dblock)
7374 {
7375 for (node = sec->touching_thinglist; node; node = node->m_thinglist_next)
7376 {
7377 thing = node->m_thing;
7378
7379 if (thing->eflags & MFE_PUSHED)
7380 continue;
7381
7382 height = P_GetSpecialTopZ(thing, sec, sec);
7383
7384 if (!(thing->flags & MF_NOCLIP) &&
7385 (!(thing->flags & MF_NOGRAVITY || thing->z+thing->height < height)))
7386 {
7387 // Move objects only if on floor or underwater,
7388 // non-floating, and clipped.
7389 P_DoScrollMove(thing, dx, dy, s->exclusive);
7390 }
7391 }
7392 }
7393 break; // end of sc_carry_ceiling
7394 } // end of switch
7395 }
7396
7397 /** Adds a generalized scroller to the thinker list.
7398 *
7399 * \param type The enumerated type of scrolling.
7400 * \param dx x speed of scrolling or its acceleration.
7401 * \param dy y speed of scrolling or its acceleration.
7402 * \param control Sector whose heights control this scroller's effect
7403 * remotely, or -1 if there is no control sector.
7404 * \param affectee Index of the affected object, sector or sidedef.
7405 * \param accel Nonzero for an accelerative effect.
7406 * \sa Add_WallScroller, P_SpawnScrollers, T_Scroll
7407 */
Add_Scroller(INT32 type,fixed_t dx,fixed_t dy,INT32 control,INT32 affectee,INT32 accel,INT32 exclusive)7408 static void Add_Scroller(INT32 type, fixed_t dx, fixed_t dy, INT32 control, INT32 affectee, INT32 accel, INT32 exclusive)
7409 {
7410 scroll_t *s = Z_Calloc(sizeof *s, PU_LEVSPEC, NULL);
7411 s->thinker.function.acp1 = (actionf_p1)T_Scroll;
7412 s->type = type;
7413 s->dx = dx;
7414 s->dy = dy;
7415 s->accel = accel;
7416 s->exclusive = exclusive;
7417 s->vdx = s->vdy = 0;
7418 if ((s->control = control) != -1)
7419 s->last_height = sectors[control].floorheight + sectors[control].ceilingheight;
7420 s->affectee = affectee;
7421 P_AddThinker(THINK_MAIN, &s->thinker);
7422 }
7423
7424 /** Initializes the scrollers.
7425 *
7426 * \todo Get rid of all the magic numbers.
7427 * \sa P_SpawnSpecials, Add_Scroller, Add_WallScroller
7428 */
P_SpawnScrollers(void)7429 static void P_SpawnScrollers(void)
7430 {
7431 size_t i;
7432 line_t *l = lines;
7433 mtag_t tag;
7434
7435 for (i = 0; i < numlines; i++, l++)
7436 {
7437 fixed_t dx = l->dx >> SCROLL_SHIFT; // direction and speed of scrolling
7438 fixed_t dy = l->dy >> SCROLL_SHIFT;
7439 INT32 control = -1, accel = 0; // no control sector or acceleration
7440 INT32 special = l->special;
7441
7442 tag = Tag_FGet(&l->tags);
7443
7444 // These types are same as the ones they get set to except that the
7445 // first side's sector's heights cause scrolling when they change, and
7446 // this linedef controls the direction and speed of the scrolling. The
7447 // most complicated linedef since donuts, but powerful :)
7448
7449 if (special == 515 || special == 512 || special == 522 || special == 532 || special == 504) // displacement scrollers
7450 {
7451 special -= 2;
7452 control = (INT32)(sides[*l->sidenum].sector - sectors);
7453 }
7454 else if (special == 514 || special == 511 || special == 521 || special == 531 || special == 503) // accelerative scrollers
7455 {
7456 special--;
7457 accel = 1;
7458 control = (INT32)(sides[*l->sidenum].sector - sectors);
7459 }
7460 else if (special == 535 || special == 525) // displacement scrollers
7461 {
7462 special -= 2;
7463 control = (INT32)(sides[*l->sidenum].sector - sectors);
7464 }
7465 else if (special == 534 || special == 524) // accelerative scrollers
7466 {
7467 accel = 1;
7468 special--;
7469 control = (INT32)(sides[*l->sidenum].sector - sectors);
7470 }
7471
7472 switch (special)
7473 {
7474 register INT32 s;
7475 TAG_ITER_DECLARECOUNTER(0);
7476
7477 case 513: // scroll effect ceiling
7478 case 533: // scroll and carry objects on ceiling
7479 TAG_ITER_SECTORS(0, tag, s)
7480 Add_Scroller(sc_ceiling, -dx, dy, control, s, accel, l->flags & ML_NOCLIMB);
7481 if (special != 533)
7482 break;
7483 /* FALLTHRU */
7484
7485 case 523: // carry objects on ceiling
7486 dx = FixedMul(dx, CARRYFACTOR);
7487 dy = FixedMul(dy, CARRYFACTOR);
7488 TAG_ITER_SECTORS(0, tag, s)
7489 Add_Scroller(sc_carry_ceiling, dx, dy, control, s, accel, l->flags & ML_NOCLIMB);
7490 break;
7491
7492 case 510: // scroll effect floor
7493 case 530: // scroll and carry objects on floor
7494 TAG_ITER_SECTORS(0, tag, s)
7495 Add_Scroller(sc_floor, -dx, dy, control, s, accel, l->flags & ML_NOCLIMB);
7496 if (special != 530)
7497 break;
7498 /* FALLTHRU */
7499
7500 case 520: // carry objects on floor
7501 dx = FixedMul(dx, CARRYFACTOR);
7502 dy = FixedMul(dy, CARRYFACTOR);
7503 TAG_ITER_SECTORS(0, tag, s)
7504 Add_Scroller(sc_carry, dx, dy, control, s, accel, l->flags & ML_NOCLIMB);
7505 break;
7506
7507 // scroll wall according to linedef
7508 // (same direction and speed as scrolling floors)
7509 case 502:
7510 {
7511 TAG_ITER_LINES(0, tag, s)
7512 if (s != (INT32)i)
7513 {
7514 if (l->flags & ML_EFFECT2) // use texture offsets instead
7515 {
7516 dx = sides[l->sidenum[0]].textureoffset;
7517 dy = sides[l->sidenum[0]].rowoffset;
7518 }
7519 if (l->flags & ML_EFFECT3)
7520 {
7521 if (lines[s].sidenum[1] != 0xffff)
7522 Add_Scroller(sc_side, dx, dy, control, lines[s].sidenum[1], accel, 0);
7523 }
7524 else
7525 Add_Scroller(sc_side, dx, dy, control, lines[s].sidenum[0], accel, 0);
7526 }
7527 break;
7528 }
7529
7530 case 505:
7531 s = lines[i].sidenum[0];
7532 Add_Scroller(sc_side, -sides[s].textureoffset, sides[s].rowoffset, -1, s, accel, 0);
7533 break;
7534
7535 case 506:
7536 s = lines[i].sidenum[1];
7537
7538 if (s != 0xffff)
7539 Add_Scroller(sc_side, -sides[s].textureoffset, sides[s].rowoffset, -1, lines[i].sidenum[0], accel, 0);
7540 else
7541 CONS_Debug(DBG_GAMELOGIC, "Line special 506 (line #%s) missing back side!\n", sizeu1(i));
7542 break;
7543
7544 case 507:
7545 s = lines[i].sidenum[0];
7546
7547 if (lines[i].sidenum[1] != 0xffff)
7548 Add_Scroller(sc_side, -sides[s].textureoffset, sides[s].rowoffset, -1, lines[i].sidenum[1], accel, 0);
7549 else
7550 CONS_Debug(DBG_GAMELOGIC, "Line special 507 (line #%s) missing back side!\n", sizeu1(i));
7551 break;
7552
7553 case 508:
7554 s = lines[i].sidenum[1];
7555
7556 if (s != 0xffff)
7557 Add_Scroller(sc_side, -sides[s].textureoffset, sides[s].rowoffset, -1, s, accel, 0);
7558 else
7559 CONS_Debug(DBG_GAMELOGIC, "Line special 508 (line #%s) missing back side!\n", sizeu1(i));
7560 break;
7561
7562 case 500: // scroll first side
7563 Add_Scroller(sc_side, FRACUNIT, 0, -1, lines[i].sidenum[0], accel, 0);
7564 break;
7565
7566 case 501: // jff 1/30/98 2-way scroll
7567 Add_Scroller(sc_side, -FRACUNIT, 0, -1, lines[i].sidenum[0], accel, 0);
7568 break;
7569 }
7570 }
7571 }
7572
7573 /** Adds master appear/disappear thinker.
7574 *
7575 * \param appeartime tics to be existent
7576 * \param disappeartime tics to be nonexistent
7577 * \param sector pointer to control sector
7578 */
Add_MasterDisappearer(tic_t appeartime,tic_t disappeartime,tic_t offset,INT32 line,INT32 sourceline)7579 static void Add_MasterDisappearer(tic_t appeartime, tic_t disappeartime, tic_t offset, INT32 line, INT32 sourceline)
7580 {
7581 disappear_t *d = Z_Malloc(sizeof *d, PU_LEVSPEC, NULL);
7582
7583 d->thinker.function.acp1 = (actionf_p1)T_Disappear;
7584 d->appeartime = appeartime;
7585 d->disappeartime = disappeartime;
7586 d->offset = offset;
7587 d->affectee = line;
7588 d->sourceline = sourceline;
7589 d->exists = true;
7590 d->timer = 1;
7591
7592 P_AddThinker(THINK_MAIN, &d->thinker);
7593 }
7594
7595 /** Makes a FOF appear/disappear
7596 *
7597 * \param d Disappear thinker.
7598 * \sa Add_MasterDisappearer
7599 */
T_Disappear(disappear_t * d)7600 void T_Disappear(disappear_t *d)
7601 {
7602 if (d->offset && !d->exists)
7603 {
7604 d->offset--;
7605 return;
7606 }
7607
7608 if (--d->timer <= 0)
7609 {
7610 ffloor_t *rover;
7611 register INT32 s;
7612 mtag_t afftag = Tag_FGet(&lines[d->affectee].tags);
7613 TAG_ITER_DECLARECOUNTER(0);
7614
7615 TAG_ITER_SECTORS(0, afftag, s)
7616 {
7617 for (rover = sectors[s].ffloors; rover; rover = rover->next)
7618 {
7619 if (rover->master != &lines[d->affectee])
7620 continue;
7621
7622 if (d->exists)
7623 rover->flags &= ~FF_EXISTS;
7624 else
7625 {
7626 rover->flags |= FF_EXISTS;
7627
7628 if (!(lines[d->sourceline].flags & ML_NOCLIMB))
7629 {
7630 sectors[s].soundorg.z = P_GetFFloorTopZAt(rover, sectors[s].soundorg.x, sectors[s].soundorg.y);
7631 S_StartSound(§ors[s].soundorg, sfx_appear);
7632 }
7633 }
7634 }
7635 sectors[s].moved = true;
7636 P_RecalcPrecipInSector(§ors[s]);
7637 }
7638
7639 if (d->exists)
7640 {
7641 d->timer = d->disappeartime;
7642 d->exists = false;
7643 }
7644 else
7645 {
7646 d->timer = d->appeartime;
7647 d->exists = true;
7648 }
7649 }
7650 }
7651
7652 /** Removes fadingdata from FOF control sector
7653 *
7654 * \param line line to search for target faders
7655 * \param data pointer to set new fadingdata to. Can be NULL to erase.
7656 */
P_ResetFakeFloorFader(ffloor_t * rover,fade_t * data,boolean finalize)7657 static void P_ResetFakeFloorFader(ffloor_t *rover, fade_t *data, boolean finalize)
7658 {
7659 fade_t *fadingdata = (fade_t *)rover->fadingdata;
7660 // find any existing thinkers and remove them, then replace with new data
7661 if (fadingdata != data)
7662 {
7663 if (fadingdata)
7664 {
7665 if (finalize)
7666 P_FadeFakeFloor(rover,
7667 fadingdata->sourcevalue,
7668 fadingdata->alpha >= fadingdata->destvalue ?
7669 fadingdata->alpha - 1 : // trigger fade-out finish
7670 fadingdata->alpha + 1, // trigger fade-in finish
7671 0,
7672 fadingdata->ticbased,
7673 &fadingdata->timer,
7674 fadingdata->doexists,
7675 fadingdata->dotranslucent,
7676 fadingdata->dolighting,
7677 fadingdata->docolormap,
7678 fadingdata->docollision,
7679 fadingdata->doghostfade,
7680 fadingdata->exactalpha);
7681 rover->alpha = fadingdata->alpha;
7682
7683 if (fadingdata->dolighting)
7684 P_RemoveLighting(§ors[rover->secnum]);
7685
7686 if (fadingdata->docolormap)
7687 P_ResetColormapFader(§ors[rover->secnum]);
7688
7689 P_RemoveThinker(&fadingdata->thinker);
7690 }
7691
7692 rover->fadingdata = data;
7693 }
7694 }
7695
P_FadeFakeFloor(ffloor_t * rover,INT16 sourcevalue,INT16 destvalue,INT16 speed,boolean ticbased,INT32 * timer,boolean doexists,boolean dotranslucent,boolean dolighting,boolean docolormap,boolean docollision,boolean doghostfade,boolean exactalpha)7696 static boolean P_FadeFakeFloor(ffloor_t *rover, INT16 sourcevalue, INT16 destvalue, INT16 speed, boolean ticbased, INT32 *timer,
7697 boolean doexists, boolean dotranslucent, boolean dolighting, boolean docolormap,
7698 boolean docollision, boolean doghostfade, boolean exactalpha)
7699 {
7700 boolean stillfading = false;
7701 INT32 alpha;
7702 fade_t *fadingdata = (fade_t *)rover->fadingdata;
7703 (void)docolormap; // *shrug* maybe we can use this in the future. For now, let's be consistent with our other function params
7704
7705 if (rover->master->special == 258) // Laser block
7706 return false;
7707
7708 // If fading an invisible FOF whose render flags we did not yet set,
7709 // initialize its alpha to 1
7710 if (dotranslucent &&
7711 (rover->spawnflags & FF_NOSHADE) && // do not include light blocks, which don't set FF_NOSHADE
7712 !(rover->flags & FF_FOG) && // do not include fog
7713 !(rover->spawnflags & FF_RENDERSIDES) &&
7714 !(rover->spawnflags & FF_RENDERPLANES) &&
7715 !(rover->flags & FF_RENDERALL))
7716 rover->alpha = 1;
7717
7718 if (fadingdata)
7719 alpha = fadingdata->alpha;
7720 else
7721 alpha = rover->alpha;
7722
7723 // routines specific to fade in and fade out
7724 if (!ticbased && alpha == destvalue)
7725 return stillfading;
7726 else if (alpha > destvalue) // fade out
7727 {
7728 // finish fading out
7729 if (speed < 1 || (!ticbased && alpha - speed <= destvalue + speed) ||
7730 (ticbased && (--(*timer) <= 0 || alpha <= destvalue)))
7731 {
7732 alpha = destvalue;
7733
7734 if (docollision)
7735 {
7736 if (rover->spawnflags & FF_SOLID)
7737 rover->flags &= ~FF_SOLID;
7738 if (rover->spawnflags & FF_SWIMMABLE)
7739 rover->flags &= ~FF_SWIMMABLE;
7740 if (rover->spawnflags & FF_QUICKSAND)
7741 rover->flags &= ~FF_QUICKSAND;
7742 if (rover->spawnflags & FF_BUSTUP)
7743 rover->flags &= ~FF_BUSTUP;
7744 if (rover->spawnflags & FF_MARIO)
7745 rover->flags &= ~FF_MARIO;
7746 }
7747 }
7748 else // continue fading out
7749 {
7750 if (!ticbased)
7751 alpha -= speed;
7752 else
7753 {
7754 INT16 delta = abs(destvalue - sourcevalue);
7755 fixed_t factor = min(FixedDiv(speed - (*timer), speed), 1*FRACUNIT);
7756 alpha = max(min(alpha, sourcevalue - (INT16)FixedMul(delta, factor)), destvalue);
7757 }
7758 stillfading = true;
7759 }
7760 }
7761 else // fade in
7762 {
7763 // finish fading in
7764 if (speed < 1 || (!ticbased && alpha + speed >= destvalue - speed) ||
7765 (ticbased && (--(*timer) <= 0 || alpha >= destvalue)))
7766 {
7767 alpha = destvalue;
7768
7769 if (docollision)
7770 {
7771 if (rover->spawnflags & FF_SOLID)
7772 rover->flags |= FF_SOLID;
7773 if (rover->spawnflags & FF_SWIMMABLE)
7774 rover->flags |= FF_SWIMMABLE;
7775 if (rover->spawnflags & FF_QUICKSAND)
7776 rover->flags |= FF_QUICKSAND;
7777 if (rover->spawnflags & FF_BUSTUP)
7778 rover->flags |= FF_BUSTUP;
7779 if (rover->spawnflags & FF_MARIO)
7780 rover->flags |= FF_MARIO;
7781 }
7782 }
7783 else // continue fading in
7784 {
7785 if (!ticbased)
7786 alpha += speed;
7787 else
7788 {
7789 INT16 delta = abs(destvalue - sourcevalue);
7790 fixed_t factor = min(FixedDiv(speed - (*timer), speed), 1*FRACUNIT);
7791 alpha = min(max(alpha, sourcevalue + (INT16)FixedMul(delta, factor)), destvalue);
7792 }
7793 stillfading = true;
7794 }
7795 }
7796
7797 // routines common to both fade in and fade out
7798 if (!stillfading)
7799 {
7800 if (doexists && !(rover->spawnflags & FF_BUSTUP))
7801 {
7802 if (alpha <= 1)
7803 rover->flags &= ~FF_EXISTS;
7804 else
7805 rover->flags |= FF_EXISTS;
7806
7807 // Re-render lighting at end of fade
7808 if (dolighting && !(rover->spawnflags & FF_NOSHADE) && !(rover->flags & FF_EXISTS))
7809 rover->target->moved = true;
7810 }
7811
7812 if (dotranslucent && !(rover->flags & FF_FOG))
7813 {
7814 if (alpha >= 256)
7815 {
7816 if (!(rover->flags & FF_CUTSOLIDS) &&
7817 (rover->spawnflags & FF_CUTSOLIDS))
7818 {
7819 rover->flags |= FF_CUTSOLIDS;
7820 rover->target->moved = true;
7821 }
7822
7823 rover->flags &= ~FF_TRANSLUCENT;
7824 }
7825 else
7826 {
7827 rover->flags |= FF_TRANSLUCENT;
7828
7829 if ((rover->flags & FF_CUTSOLIDS) &&
7830 (rover->spawnflags & FF_CUTSOLIDS))
7831 {
7832 rover->flags &= ~FF_CUTSOLIDS;
7833 rover->target->moved = true;
7834 }
7835 }
7836
7837 if ((rover->spawnflags & FF_NOSHADE) && // do not include light blocks, which don't set FF_NOSHADE
7838 !(rover->spawnflags & FF_RENDERSIDES) &&
7839 !(rover->spawnflags & FF_RENDERPLANES))
7840 {
7841 if (rover->alpha > 1)
7842 rover->flags |= FF_RENDERALL;
7843 else
7844 rover->flags &= ~FF_RENDERALL;
7845 }
7846 }
7847 }
7848 else
7849 {
7850 if (doexists && !(rover->spawnflags & FF_BUSTUP))
7851 {
7852 // Re-render lighting if we haven't yet set FF_EXISTS (beginning of fade)
7853 if (dolighting && !(rover->spawnflags & FF_NOSHADE) && !(rover->flags & FF_EXISTS))
7854 rover->target->moved = true;
7855
7856 rover->flags |= FF_EXISTS;
7857 }
7858
7859 if (dotranslucent && !(rover->flags & FF_FOG))
7860 {
7861 rover->flags |= FF_TRANSLUCENT;
7862
7863 if ((rover->flags & FF_CUTSOLIDS) &&
7864 (rover->spawnflags & FF_CUTSOLIDS))
7865 {
7866 rover->flags &= ~FF_CUTSOLIDS;
7867 rover->target->moved = true;
7868 }
7869
7870 if ((rover->spawnflags & FF_NOSHADE) && // do not include light blocks, which don't set FF_NOSHADE
7871 !(rover->spawnflags & FF_RENDERSIDES) &&
7872 !(rover->spawnflags & FF_RENDERPLANES))
7873 rover->flags |= FF_RENDERALL;
7874 }
7875
7876 if (docollision)
7877 {
7878 if (doghostfade) // remove collision flags during fade
7879 {
7880 if (rover->spawnflags & FF_SOLID)
7881 rover->flags &= ~FF_SOLID;
7882 if (rover->spawnflags & FF_SWIMMABLE)
7883 rover->flags &= ~FF_SWIMMABLE;
7884 if (rover->spawnflags & FF_QUICKSAND)
7885 rover->flags &= ~FF_QUICKSAND;
7886 if (rover->spawnflags & FF_BUSTUP)
7887 rover->flags &= ~FF_BUSTUP;
7888 if (rover->spawnflags & FF_MARIO)
7889 rover->flags &= ~FF_MARIO;
7890 }
7891 else // keep collision during fade
7892 {
7893 if (rover->spawnflags & FF_SOLID)
7894 rover->flags |= FF_SOLID;
7895 if (rover->spawnflags & FF_SWIMMABLE)
7896 rover->flags |= FF_SWIMMABLE;
7897 if (rover->spawnflags & FF_QUICKSAND)
7898 rover->flags |= FF_QUICKSAND;
7899 if (rover->spawnflags & FF_BUSTUP)
7900 rover->flags |= FF_BUSTUP;
7901 if (rover->spawnflags & FF_MARIO)
7902 rover->flags |= FF_MARIO;
7903 }
7904 }
7905 }
7906
7907 if (!(rover->flags & FF_FOG)) // don't set FOG alpha
7908 {
7909 if (!stillfading || exactalpha)
7910 rover->alpha = alpha;
7911 else // clamp fadingdata->alpha to software's alpha levels
7912 {
7913 if (alpha < 12)
7914 rover->alpha = destvalue < 12 ? destvalue : 1; // Don't even draw it
7915 else if (alpha < 38)
7916 rover->alpha = destvalue >= 12 && destvalue < 38 ? destvalue : 25;
7917 else if (alpha < 64)
7918 rover->alpha = destvalue >=38 && destvalue < 64 ? destvalue : 51;
7919 else if (alpha < 89)
7920 rover->alpha = destvalue >= 64 && destvalue < 89 ? destvalue : 76;
7921 else if (alpha < 115)
7922 rover->alpha = destvalue >= 89 && destvalue < 115 ? destvalue : 102;
7923 else if (alpha < 140)
7924 rover->alpha = destvalue >= 115 && destvalue < 140 ? destvalue : 128;
7925 else if (alpha < 166)
7926 rover->alpha = destvalue >= 140 && destvalue < 166 ? destvalue : 154;
7927 else if (alpha < 192)
7928 rover->alpha = destvalue >= 166 && destvalue < 192 ? destvalue : 179;
7929 else if (alpha < 217)
7930 rover->alpha = destvalue >= 192 && destvalue < 217 ? destvalue : 204;
7931 else if (alpha < 243)
7932 rover->alpha = destvalue >= 217 && destvalue < 243 ? destvalue : 230;
7933 else // Opaque
7934 rover->alpha = destvalue >= 243 ? destvalue : 256;
7935 }
7936 }
7937
7938 if (fadingdata)
7939 fadingdata->alpha = alpha;
7940
7941 return stillfading;
7942 }
7943
7944 /** Adds fake floor fader thinker.
7945 *
7946 * \param destvalue transparency value to fade to
7947 * \param speed speed to fade by
7948 * \param ticbased tic-based logic, speed = duration
7949 * \param relative Destvalue is relative to rover->alpha
7950 * \param doexists handle FF_EXISTS
7951 * \param dotranslucent handle FF_TRANSLUCENT
7952 * \param dolighting fade FOF light
7953 * \param docollision handle interactive flags
7954 * \param doghostfade no interactive flags during fading
7955 * \param exactalpha use exact alpha values (opengl)
7956 */
P_AddFakeFloorFader(ffloor_t * rover,size_t sectornum,size_t ffloornum,INT16 destvalue,INT16 speed,boolean ticbased,boolean relative,boolean doexists,boolean dotranslucent,boolean dolighting,boolean docolormap,boolean docollision,boolean doghostfade,boolean exactalpha)7957 static void P_AddFakeFloorFader(ffloor_t *rover, size_t sectornum, size_t ffloornum,
7958 INT16 destvalue, INT16 speed, boolean ticbased, boolean relative,
7959 boolean doexists, boolean dotranslucent, boolean dolighting, boolean docolormap,
7960 boolean docollision, boolean doghostfade, boolean exactalpha)
7961 {
7962 fade_t *d;
7963
7964 // If fading an invisible FOF whose render flags we did not yet set,
7965 // initialize its alpha to 1
7966 if (dotranslucent &&
7967 (rover->spawnflags & FF_NOSHADE) && // do not include light blocks, which don't set FF_NOSHADE
7968 !(rover->spawnflags & FF_RENDERSIDES) &&
7969 !(rover->spawnflags & FF_RENDERPLANES) &&
7970 !(rover->flags & FF_RENDERALL))
7971 rover->alpha = 1;
7972
7973 // already equal, nothing to do
7974 if (rover->alpha == max(1, min(256, relative ? rover->alpha + destvalue : destvalue)))
7975 return;
7976
7977 d = Z_Malloc(sizeof *d, PU_LEVSPEC, NULL);
7978
7979 d->thinker.function.acp1 = (actionf_p1)T_Fade;
7980 d->rover = rover;
7981 d->sectornum = (UINT32)sectornum;
7982 d->ffloornum = (UINT32)ffloornum;
7983
7984 d->alpha = d->sourcevalue = rover->alpha;
7985 d->destvalue = max(1, min(256, relative ? rover->alpha + destvalue : destvalue)); // rover->alpha is 1-256
7986
7987 if (ticbased)
7988 {
7989 d->ticbased = true;
7990 d->timer = d->speed = abs(speed); // use d->speed as total duration
7991 }
7992 else
7993 {
7994 d->ticbased = false;
7995 d->speed = max(1, speed); // minimum speed 1/tic // if speed < 1, alpha is set immediately in thinker
7996 d->timer = -1;
7997 }
7998
7999 d->doexists = doexists;
8000 d->dotranslucent = dotranslucent;
8001 d->dolighting = dolighting;
8002 d->docolormap = docolormap;
8003 d->docollision = docollision;
8004 d->doghostfade = doghostfade;
8005 d->exactalpha = exactalpha;
8006
8007 // find any existing thinkers and remove them, then replace with new data
8008 P_ResetFakeFloorFader(rover, d, false);
8009
8010 // Set a separate thinker for shadow fading
8011 if (dolighting && !(rover->flags & FF_NOSHADE))
8012 {
8013 UINT16 lightdelta = abs(sectors[rover->secnum].spawn_lightlevel - rover->target->lightlevel);
8014 fixed_t alphapercent = min(FixedDiv(d->destvalue, rover->spawnalpha), 1*FRACUNIT); // don't make darker than spawn_lightlevel
8015 fixed_t adjustedlightdelta = FixedMul(lightdelta, alphapercent);
8016
8017 if (rover->target->lightlevel >= sectors[rover->secnum].spawn_lightlevel) // fading out, get lighter
8018 d->destlightlevel = rover->target->lightlevel - adjustedlightdelta;
8019 else // fading in, get darker
8020 d->destlightlevel = rover->target->lightlevel + adjustedlightdelta;
8021
8022 P_FadeLightBySector(§ors[rover->secnum],
8023 d->destlightlevel,
8024 ticbased ? d->timer :
8025 FixedFloor(FixedDiv(abs(d->destvalue - d->alpha), d->speed))/FRACUNIT,
8026 true);
8027 }
8028 else
8029 d->destlightlevel = -1;
8030
8031 // Set a separate thinker for colormap fading
8032 if (docolormap && !(rover->flags & FF_NOSHADE) && sectors[rover->secnum].spawn_extra_colormap && !sectors[rover->secnum].colormap_protected)
8033 {
8034 extracolormap_t *dest_exc,
8035 *source_exc = sectors[rover->secnum].extra_colormap ? sectors[rover->secnum].extra_colormap : R_GetDefaultColormap();
8036
8037 INT32 colordelta = R_GetRgbaA(sectors[rover->secnum].spawn_extra_colormap->rgba); // alpha is from 0
8038 fixed_t alphapercent = min(FixedDiv(d->destvalue, rover->spawnalpha), 1*FRACUNIT); // don't make darker than spawn_lightlevel
8039 fixed_t adjustedcolordelta = FixedMul(colordelta, alphapercent);
8040 INT32 coloralpha;
8041
8042 coloralpha = adjustedcolordelta;
8043
8044 dest_exc = R_CopyColormap(sectors[rover->secnum].spawn_extra_colormap, false);
8045 dest_exc->rgba = R_GetRgbaRGB(dest_exc->rgba) + R_PutRgbaA(coloralpha);
8046
8047 if (!(d->dest_exc = R_GetColormapFromList(dest_exc)))
8048 {
8049 dest_exc->colormap = R_CreateLightTable(dest_exc);
8050 R_AddColormapToList(dest_exc);
8051 d->dest_exc = dest_exc;
8052 }
8053 else
8054 Z_Free(dest_exc);
8055
8056 // If fading from 0, set source_exc rgb same to dest_exc
8057 if (!R_CheckDefaultColormap(d->dest_exc, true, false, false)
8058 && R_CheckDefaultColormap(source_exc, true, false, false))
8059 {
8060 extracolormap_t *exc = R_CopyColormap(source_exc, false);
8061 exc->rgba = R_GetRgbaRGB(d->dest_exc->rgba) + R_PutRgbaA(R_GetRgbaA(source_exc->rgba));
8062 exc->fadergba = R_GetRgbaRGB(d->dest_exc->rgba) + R_PutRgbaA(R_GetRgbaA(source_exc->fadergba));
8063
8064 if (!(source_exc = R_GetColormapFromList(exc)))
8065 {
8066 exc->colormap = R_CreateLightTable(exc);
8067 R_AddColormapToList(exc);
8068 source_exc = exc;
8069 }
8070 else
8071 Z_Free(exc);
8072 }
8073
8074 Add_ColormapFader(§ors[rover->secnum], source_exc, d->dest_exc, true,
8075 ticbased ? d->timer :
8076 FixedFloor(FixedDiv(abs(d->destvalue - d->alpha), d->speed))/FRACUNIT);
8077 }
8078
8079 P_AddThinker(THINK_MAIN, &d->thinker);
8080 }
8081
8082 /** Makes a FOF fade
8083 *
8084 * \param d Fade thinker.
8085 * \sa P_AddFakeFloorFader
8086 */
T_Fade(fade_t * d)8087 void T_Fade(fade_t *d)
8088 {
8089 if (d->rover && !P_FadeFakeFloor(d->rover, d->sourcevalue, d->destvalue, d->speed, d->ticbased, &d->timer,
8090 d->doexists, d->dotranslucent, d->dolighting, d->docolormap, d->docollision, d->doghostfade, d->exactalpha))
8091 {
8092 // Finalize lighting, copypasta from P_AddFakeFloorFader
8093 if (d->dolighting && !(d->rover->flags & FF_NOSHADE) && d->destlightlevel > -1)
8094 sectors[d->rover->secnum].lightlevel = d->destlightlevel;
8095
8096 // Finalize colormap
8097 if (d->docolormap && !(d->rover->flags & FF_NOSHADE) && sectors[d->rover->secnum].spawn_extra_colormap)
8098 sectors[d->rover->secnum].extra_colormap = d->dest_exc;
8099
8100 P_RemoveFakeFloorFader(d->rover);
8101 }
8102 }
8103
P_ResetColormapFader(sector_t * sector)8104 static void P_ResetColormapFader(sector_t *sector)
8105 {
8106 if (sector->fadecolormapdata)
8107 {
8108 // The thinker is the first member in all the action structs,
8109 // so just let the thinker get freed, and that will free the whole
8110 // structure.
8111 P_RemoveThinker(&((elevator_t *)sector->fadecolormapdata)->thinker);
8112 sector->fadecolormapdata = NULL;
8113 }
8114 }
8115
Add_ColormapFader(sector_t * sector,extracolormap_t * source_exc,extracolormap_t * dest_exc,boolean ticbased,INT32 duration)8116 static void Add_ColormapFader(sector_t *sector, extracolormap_t *source_exc, extracolormap_t *dest_exc,
8117 boolean ticbased, INT32 duration)
8118 {
8119 fadecolormap_t *d;
8120
8121 P_ResetColormapFader(sector);
8122
8123 // nothing to do, set immediately
8124 if (!duration || R_CheckEqualColormaps(source_exc, dest_exc, true, true, true))
8125 {
8126 sector->extra_colormap = dest_exc;
8127 return;
8128 }
8129
8130 d = Z_Malloc(sizeof *d, PU_LEVSPEC, NULL);
8131 d->thinker.function.acp1 = (actionf_p1)T_FadeColormap;
8132 d->sector = sector;
8133 d->source_exc = source_exc;
8134 d->dest_exc = dest_exc;
8135
8136 if (ticbased)
8137 {
8138 d->ticbased = true;
8139 d->duration = d->timer = duration;
8140 }
8141 else
8142 {
8143 d->ticbased = false;
8144 d->timer = 256;
8145 d->duration = duration; // use as speed
8146 }
8147
8148 sector->fadecolormapdata = d;
8149 P_AddThinker(THINK_MAIN, &d->thinker);
8150 }
8151
T_FadeColormap(fadecolormap_t * d)8152 void T_FadeColormap(fadecolormap_t *d)
8153 {
8154 if ((d->ticbased && --d->timer <= 0)
8155 || (!d->ticbased && (d->timer -= d->duration) <= 0)) // d->duration used as speed decrement
8156 {
8157 d->sector->extra_colormap = d->dest_exc;
8158 P_ResetColormapFader(d->sector);
8159 }
8160 else
8161 {
8162 extracolormap_t *exc;
8163 INT32 duration = d->ticbased ? d->duration : 256;
8164 fixed_t factor = min(FixedDiv(duration - d->timer, duration), 1*FRACUNIT);
8165 INT16 cr, cg, cb, ca, fadestart, fadeend, flags;
8166 INT32 rgba, fadergba;
8167
8168 // NULL failsafes (or intentionally set to signify default)
8169 if (!d->sector->extra_colormap)
8170 d->sector->extra_colormap = R_GetDefaultColormap();
8171
8172 if (!d->source_exc)
8173 d->source_exc = R_GetDefaultColormap();
8174
8175 if (!d->dest_exc)
8176 d->dest_exc = R_GetDefaultColormap();
8177
8178 // For each var (rgba + fadergba + params = 11 vars), we apply
8179 // percentage fading: currentval = sourceval + (delta * percent of duration elapsed)
8180 // delta is negative when fading out (destval is lower)
8181 // max/min are used to ensure progressive calcs don't go backwards and to cap values to dest.
8182
8183 #define APPLYFADE(dest, src, cur) (\
8184 (dest-src < 0) ? \
8185 max(\
8186 min(cur,\
8187 src + (INT16)FixedMul(dest-src, factor)),\
8188 dest)\
8189 : (dest-src > 0) ? \
8190 min(\
8191 max(cur,\
8192 src + (INT16)FixedMul(dest-src, factor)),\
8193 dest)\
8194 : \
8195 dest\
8196 )
8197
8198 cr = APPLYFADE(R_GetRgbaR(d->dest_exc->rgba), R_GetRgbaR(d->source_exc->rgba), R_GetRgbaR(d->sector->extra_colormap->rgba));
8199 cg = APPLYFADE(R_GetRgbaG(d->dest_exc->rgba), R_GetRgbaG(d->source_exc->rgba), R_GetRgbaG(d->sector->extra_colormap->rgba));
8200 cb = APPLYFADE(R_GetRgbaB(d->dest_exc->rgba), R_GetRgbaB(d->source_exc->rgba), R_GetRgbaB(d->sector->extra_colormap->rgba));
8201 ca = APPLYFADE(R_GetRgbaA(d->dest_exc->rgba), R_GetRgbaA(d->source_exc->rgba), R_GetRgbaA(d->sector->extra_colormap->rgba));
8202
8203 rgba = R_PutRgbaRGBA(cr, cg, cb, ca);
8204
8205 cr = APPLYFADE(R_GetRgbaR(d->dest_exc->fadergba), R_GetRgbaR(d->source_exc->fadergba), R_GetRgbaR(d->sector->extra_colormap->fadergba));
8206 cg = APPLYFADE(R_GetRgbaG(d->dest_exc->fadergba), R_GetRgbaG(d->source_exc->fadergba), R_GetRgbaG(d->sector->extra_colormap->fadergba));
8207 cb = APPLYFADE(R_GetRgbaB(d->dest_exc->fadergba), R_GetRgbaB(d->source_exc->fadergba), R_GetRgbaB(d->sector->extra_colormap->fadergba));
8208 ca = APPLYFADE(R_GetRgbaA(d->dest_exc->fadergba), R_GetRgbaA(d->source_exc->fadergba), R_GetRgbaA(d->sector->extra_colormap->fadergba));
8209
8210 fadergba = R_PutRgbaRGBA(cr, cg, cb, ca);
8211
8212 fadestart = APPLYFADE(d->dest_exc->fadestart, d->source_exc->fadestart, d->sector->extra_colormap->fadestart);
8213 fadeend = APPLYFADE(d->dest_exc->fadeend, d->source_exc->fadeend, d->sector->extra_colormap->fadeend);
8214 flags = abs(factor) > FRACUNIT/2 ? d->dest_exc->flags : d->source_exc->flags; // set new flags halfway through fade
8215
8216 #undef APPLYFADE
8217
8218 //////////////////
8219 // setup new colormap
8220 //////////////////
8221
8222 if (!(d->sector->extra_colormap = R_GetColormapFromListByValues(rgba, fadergba, fadestart, fadeend, flags)))
8223 {
8224 exc = R_CreateDefaultColormap(false);
8225 exc->fadestart = fadestart;
8226 exc->fadeend = fadeend;
8227 exc->flags = flags;
8228 exc->rgba = rgba;
8229 exc->fadergba = fadergba;
8230 exc->colormap = R_CreateLightTable(exc);
8231 R_AddColormapToList(exc);
8232 d->sector->extra_colormap = exc;
8233 }
8234 }
8235 }
8236
8237 /*
8238 SoM: 3/8/2000: Friction functions start.
8239 Add_Friction,
8240 T_Friction,
8241 P_SpawnFriction
8242 */
8243
8244 /** Adds friction thinker.
8245 *
8246 * \param friction Friction value, 0xe800 is normal.
8247 * \param affectee Target sector.
8248 * \param roverfriction FOF or not
8249 * \sa T_Friction, P_SpawnFriction
8250 */
Add_Friction(INT32 friction,INT32 movefactor,INT32 affectee,INT32 referrer)8251 static void Add_Friction(INT32 friction, INT32 movefactor, INT32 affectee, INT32 referrer)
8252 {
8253 friction_t *f = Z_Calloc(sizeof *f, PU_LEVSPEC, NULL);
8254
8255 f->thinker.function.acp1 = (actionf_p1)T_Friction;
8256 f->friction = friction;
8257 f->movefactor = movefactor;
8258 f->affectee = affectee;
8259
8260 if (referrer != -1)
8261 {
8262 f->roverfriction = true;
8263 f->referrer = referrer;
8264 }
8265 else
8266 f->roverfriction = false;
8267
8268 P_AddThinker(THINK_MAIN, &f->thinker);
8269 }
8270
8271 /** Applies friction to all things in a sector.
8272 *
8273 * \param f Friction thinker.
8274 * \sa Add_Friction
8275 */
T_Friction(friction_t * f)8276 void T_Friction(friction_t *f)
8277 {
8278 sector_t *sec, *referrer = NULL;
8279 mobj_t *thing;
8280 msecnode_t *node;
8281
8282 sec = sectors + f->affectee;
8283
8284 // Get FOF control sector
8285 if (f->roverfriction)
8286 referrer = sectors + f->referrer;
8287
8288 // Assign the friction value to players on the floor, non-floating,
8289 // and clipped. Normally the object's friction value is kept at
8290 // ORIG_FRICTION and this thinker changes it for icy or muddy floors.
8291
8292 // When the object is straddling sectors with the same
8293 // floorheight that have different frictions, use the lowest
8294 // friction value (muddy has precedence over icy).
8295
8296 node = sec->touching_thinglist; // things touching this sector
8297 while (node)
8298 {
8299 thing = node->m_thing;
8300 // apparently, all I had to do was comment out part of the next line and
8301 // friction works for all mobj's
8302 // (or at least MF_PUSHABLEs, which is all I care about anyway)
8303 if (!(thing->flags & (MF_NOGRAVITY | MF_NOCLIP)) && thing->z == thing->floorz)
8304 {
8305 if (f->roverfriction)
8306 {
8307 if (thing->floorz != P_GetSpecialTopZ(thing, referrer, sec))
8308 {
8309 node = node->m_thinglist_next;
8310 continue;
8311 }
8312
8313 if ((thing->friction == ORIG_FRICTION) // normal friction?
8314 || (f->friction < thing->friction))
8315 {
8316 thing->friction = f->friction;
8317 if (thing->player)
8318 thing->movefactor = f->movefactor;
8319 }
8320 }
8321 else if (P_GetSpecialBottomZ(thing, sec, sec) == thing->floorz && (thing->friction == ORIG_FRICTION // normal friction?
8322 || f->friction < thing->friction))
8323 {
8324 thing->friction = f->friction;
8325 if (thing->player)
8326 thing->movefactor = f->movefactor;
8327 }
8328 }
8329 node = node->m_thinglist_next;
8330 }
8331 }
8332
8333 /** Spawns all friction effects.
8334 *
8335 * \sa P_SpawnSpecials, Add_Friction
8336 */
P_SpawnFriction(void)8337 static void P_SpawnFriction(void)
8338 {
8339 size_t i;
8340 line_t *l = lines;
8341 mtag_t tag;
8342 register INT32 s;
8343 fixed_t strength; // frontside texture offset controls magnitude
8344 fixed_t friction; // friction value to be applied during movement
8345 INT32 movefactor; // applied to each player move to simulate inertia
8346 TAG_ITER_DECLARECOUNTER(0);
8347
8348 for (i = 0; i < numlines; i++, l++)
8349 if (l->special == 540)
8350 {
8351 tag = Tag_FGet(&l->tags);
8352 strength = sides[l->sidenum[0]].textureoffset>>FRACBITS;
8353 if (strength > 0) // sludge
8354 strength = strength*2; // otherwise, the maximum sludginess value is +967...
8355
8356 // The following might seem odd. At the time of movement,
8357 // the move distance is multiplied by 'friction/0x10000', so a
8358 // higher friction value actually means 'less friction'.
8359 friction = ORIG_FRICTION - (0x1EB8*strength)/0x80; // ORIG_FRICTION is 0xE800
8360
8361 if (friction > FRACUNIT)
8362 friction = FRACUNIT;
8363 if (friction < 0)
8364 friction = 0;
8365
8366 movefactor = FixedDiv(ORIG_FRICTION, friction);
8367 if (movefactor < FRACUNIT)
8368 movefactor = 8*movefactor - 7*FRACUNIT;
8369 else
8370 movefactor = FRACUNIT;
8371
8372 TAG_ITER_SECTORS(0, tag, s)
8373 Add_Friction(friction, movefactor, s, -1);
8374 }
8375 }
8376
8377 /*
8378 SoM: 3/8/2000: Push/Pull/Wind/Current functions.
8379 Add_Pusher,
8380 PIT_PushThing,
8381 T_Pusher,
8382 P_GetPushThing,
8383 P_SpawnPushers
8384 */
8385
8386 #define PUSH_FACTOR 7
8387
8388 /** Adds a pusher.
8389 *
8390 * \param type Type of push/pull effect.
8391 * \param x_mag X magnitude.
8392 * \param y_mag Y magnitude.
8393 * \param source For a point pusher/puller, the source object.
8394 * \param affectee Target sector.
8395 * \param referrer What sector set it
8396 * \sa T_Pusher, P_GetPushThing, P_SpawnPushers
8397 */
Add_Pusher(pushertype_e type,fixed_t x_mag,fixed_t y_mag,mobj_t * source,INT32 affectee,INT32 referrer,INT32 exclusive,INT32 slider)8398 static void Add_Pusher(pushertype_e type, fixed_t x_mag, fixed_t y_mag, mobj_t *source, INT32 affectee, INT32 referrer, INT32 exclusive, INT32 slider)
8399 {
8400 pusher_t *p = Z_Calloc(sizeof *p, PU_LEVSPEC, NULL);
8401
8402 p->thinker.function.acp1 = (actionf_p1)T_Pusher;
8403 p->source = source;
8404 p->type = type;
8405 p->x_mag = x_mag>>FRACBITS;
8406 p->y_mag = y_mag>>FRACBITS;
8407 p->exclusive = exclusive;
8408 p->slider = slider;
8409
8410 if (referrer != -1)
8411 {
8412 p->roverpusher = true;
8413 p->referrer = referrer;
8414 }
8415 else
8416 p->roverpusher = false;
8417
8418 // "The right triangle of the square of the length of the hypotenuse is equal to the sum of the squares of the lengths of the other two sides."
8419 // "Bah! Stupid brains! Don't you know anything besides the Pythagorean Theorem?" - Earthworm Jim
8420 if (type == p_downcurrent || type == p_upcurrent || type == p_upwind || type == p_downwind)
8421 p->magnitude = P_AproxDistance(p->x_mag,p->y_mag)<<(FRACBITS-PUSH_FACTOR);
8422 else
8423 p->magnitude = P_AproxDistance(p->x_mag,p->y_mag);
8424 if (source) // point source exist?
8425 {
8426 // where force goes to zero
8427 if (type == p_push)
8428 p->radius = AngleFixed(source->angle);
8429 else
8430 p->radius = (p->magnitude)<<(FRACBITS+1);
8431
8432 p->x = p->source->x;
8433 p->y = p->source->y;
8434 p->z = p->source->z;
8435 }
8436 p->affectee = affectee;
8437 P_AddThinker(THINK_MAIN, &p->thinker);
8438 }
8439
8440
8441 // PIT_PushThing determines the angle and magnitude of the effect.
8442 // The object's x and y momentum values are changed.
8443 static pusher_t *tmpusher; // pusher structure for blockmap searches
8444
8445 /** Applies a point pusher/puller to a thing.
8446 *
8447 * \param thing Thing to be pushed.
8448 * \return True if the thing was pushed.
8449 * \todo Make a more robust P_BlockThingsIterator() so the hidden parameter
8450 * ::tmpusher won't need to be used.
8451 * \sa T_Pusher
8452 */
PIT_PushThing(mobj_t * thing)8453 static inline boolean PIT_PushThing(mobj_t *thing)
8454 {
8455 if (thing->eflags & MFE_PUSHED)
8456 return false;
8457
8458 if (thing->player && thing->player->powers[pw_carry] == CR_ROPEHANG)
8459 return false;
8460
8461 // Allow this to affect pushable objects at some point?
8462 if (thing->player && (!(thing->flags & (MF_NOGRAVITY | MF_NOCLIP)) || thing->player->powers[pw_carry] == CR_NIGHTSMODE))
8463 {
8464 INT32 dist;
8465 INT32 speed;
8466 INT32 sx, sy, sz;
8467
8468 sx = tmpusher->x;
8469 sy = tmpusher->y;
8470 sz = tmpusher->z;
8471
8472 // don't fade wrt Z if health & 2 (mapthing has multi flag)
8473 if (tmpusher->source->health & 2)
8474 dist = P_AproxDistance(thing->x - sx,thing->y - sy);
8475 else
8476 {
8477 // Make sure the Z is in range
8478 if (thing->z < sz - tmpusher->radius || thing->z > sz + tmpusher->radius)
8479 return false;
8480
8481 dist = P_AproxDistance(P_AproxDistance(thing->x - sx, thing->y - sy),
8482 thing->z - sz);
8483 }
8484
8485 speed = (tmpusher->magnitude - ((dist>>FRACBITS)>>1))<<(FRACBITS - PUSH_FACTOR - 1);
8486
8487 // If speed <= 0, you're outside the effective radius. You also have
8488 // to be able to see the push/pull source point.
8489
8490 // Written with bits and pieces of P_HomingAttack
8491 if ((speed > 0) && (P_CheckSight(thing, tmpusher->source)))
8492 {
8493 if (thing->player->powers[pw_carry] != CR_NIGHTSMODE)
8494 {
8495 // only push wrt Z if health & 1 (mapthing has ambush flag)
8496 if (tmpusher->source->health & 1)
8497 {
8498 fixed_t tmpmomx, tmpmomy, tmpmomz;
8499
8500 tmpmomx = FixedMul(FixedDiv(sx - thing->x, dist), speed);
8501 tmpmomy = FixedMul(FixedDiv(sy - thing->y, dist), speed);
8502 tmpmomz = FixedMul(FixedDiv(sz - thing->z, dist), speed);
8503 if (tmpusher->source->type == MT_PUSH) // away!
8504 {
8505 tmpmomx *= -1;
8506 tmpmomy *= -1;
8507 tmpmomz *= -1;
8508 }
8509
8510 thing->momx += tmpmomx;
8511 thing->momy += tmpmomy;
8512 thing->momz += tmpmomz;
8513
8514 if (thing->player)
8515 {
8516 thing->player->cmomx += tmpmomx;
8517 thing->player->cmomy += tmpmomy;
8518 thing->player->cmomx = FixedMul(thing->player->cmomx, 0xe800);
8519 thing->player->cmomy = FixedMul(thing->player->cmomy, 0xe800);
8520 }
8521 }
8522 else
8523 {
8524 angle_t pushangle;
8525
8526 pushangle = R_PointToAngle2(thing->x, thing->y, sx, sy);
8527 if (tmpusher->source->type == MT_PUSH)
8528 pushangle += ANGLE_180; // away
8529 pushangle >>= ANGLETOFINESHIFT;
8530 thing->momx += FixedMul(speed, FINECOSINE(pushangle));
8531 thing->momy += FixedMul(speed, FINESINE(pushangle));
8532
8533 if (thing->player)
8534 {
8535 thing->player->cmomx += FixedMul(speed, FINECOSINE(pushangle));
8536 thing->player->cmomy += FixedMul(speed, FINESINE(pushangle));
8537 thing->player->cmomx = FixedMul(thing->player->cmomx, 0xe800);
8538 thing->player->cmomy = FixedMul(thing->player->cmomy, 0xe800);
8539 }
8540 }
8541 }
8542 else
8543 {
8544 //NiGHTS-specific handling.
8545 //By default, pushes and pulls only affect the Z-axis.
8546 //By having the ambush flag, it affects the X-axis.
8547 //By having the object special flag, it affects the Y-axis.
8548 fixed_t tmpmomx, tmpmomy, tmpmomz;
8549
8550 if (tmpusher->source->health & 1)
8551 tmpmomx = FixedMul(FixedDiv(sx - thing->x, dist), speed);
8552 else
8553 tmpmomx = 0;
8554
8555 if (tmpusher->source->health & 2)
8556 tmpmomy = FixedMul(FixedDiv(sy - thing->y, dist), speed);
8557 else
8558 tmpmomy = 0;
8559
8560 tmpmomz = FixedMul(FixedDiv(sz - thing->z, dist), speed);
8561
8562 if (tmpusher->source->type == MT_PUSH) // away!
8563 {
8564 tmpmomx *= -1;
8565 tmpmomy *= -1;
8566 tmpmomz *= -1;
8567 }
8568
8569 thing->momx += tmpmomx;
8570 thing->momy += tmpmomy;
8571 thing->momz += tmpmomz;
8572
8573 if (thing->player)
8574 {
8575 thing->player->cmomx += tmpmomx;
8576 thing->player->cmomy += tmpmomy;
8577 thing->player->cmomx = FixedMul(thing->player->cmomx, 0xe800);
8578 thing->player->cmomy = FixedMul(thing->player->cmomy, 0xe800);
8579 }
8580 }
8581 }
8582 }
8583
8584 if (tmpusher->exclusive)
8585 thing->eflags |= MFE_PUSHED;
8586
8587 return true;
8588 }
8589
8590 /** Applies a pusher to all affected objects.
8591 *
8592 * \param p Thinker for the pusher effect.
8593 * \todo Split up into multiple functions.
8594 * \sa Add_Pusher, PIT_PushThing
8595 */
T_Pusher(pusher_t * p)8596 void T_Pusher(pusher_t *p)
8597 {
8598 sector_t *sec, *referrer = NULL;
8599 mobj_t *thing;
8600 msecnode_t *node;
8601 INT32 xspeed = 0,yspeed = 0;
8602 INT32 xl, xh, yl, yh, bx, by;
8603 INT32 radius;
8604 //INT32 ht = 0;
8605 boolean inFOF;
8606 boolean touching;
8607 boolean moved;
8608
8609 xspeed = yspeed = 0;
8610
8611 sec = sectors + p->affectee;
8612
8613 // Be sure the special sector type is still turned on. If so, proceed.
8614 // Else, bail out; the sector type has been changed on us.
8615
8616 if (p->roverpusher)
8617 {
8618 referrer = §ors[p->referrer];
8619
8620 if (GETSECSPECIAL(referrer->special, 3) != 2)
8621 return;
8622 }
8623 else if (GETSECSPECIAL(sec->special, 3) != 2)
8624 return;
8625
8626 // For constant pushers (wind/current) there are 3 situations:
8627 //
8628 // 1) Affected Thing is above the floor.
8629 //
8630 // Apply the full force if wind, no force if current.
8631 //
8632 // 2) Affected Thing is on the ground.
8633 //
8634 // Apply half force if wind, full force if current.
8635 //
8636 // 3) Affected Thing is below the ground (underwater effect).
8637 //
8638 // Apply no force if wind, full force if current.
8639 //
8640 // Apply the effect to clipped players only for now.
8641 //
8642 // In Phase II, you can apply these effects to Things other than players.
8643
8644 if (p->type == p_push)
8645 {
8646
8647 // Seek out all pushable things within the force radius of this
8648 // point pusher. Crosses sectors, so use blockmap.
8649
8650 tmpusher = p; // MT_PUSH/MT_PULL point source
8651 radius = p->radius; // where force goes to zero
8652 tmbbox[BOXTOP] = p->y + radius;
8653 tmbbox[BOXBOTTOM] = p->y - radius;
8654 tmbbox[BOXRIGHT] = p->x + radius;
8655 tmbbox[BOXLEFT] = p->x - radius;
8656
8657 xl = (unsigned)(tmbbox[BOXLEFT] - bmaporgx - MAXRADIUS)>>MAPBLOCKSHIFT;
8658 xh = (unsigned)(tmbbox[BOXRIGHT] - bmaporgx + MAXRADIUS)>>MAPBLOCKSHIFT;
8659 yl = (unsigned)(tmbbox[BOXBOTTOM] - bmaporgy - MAXRADIUS)>>MAPBLOCKSHIFT;
8660 yh = (unsigned)(tmbbox[BOXTOP] - bmaporgy + MAXRADIUS)>>MAPBLOCKSHIFT;
8661 for (bx = xl; bx <= xh; bx++)
8662 for (by = yl; by <= yh; by++)
8663 P_BlockThingsIterator(bx,by, PIT_PushThing);
8664 return;
8665 }
8666
8667 // constant pushers p_wind and p_current
8668 node = sec->touching_thinglist; // things touching this sector
8669 for (; node; node = node->m_thinglist_next)
8670 {
8671 thing = node->m_thing;
8672 if (thing->flags & (MF_NOGRAVITY | MF_NOCLIP)
8673 && !(thing->type == MT_SMALLBUBBLE
8674 || thing->type == MT_MEDIUMBUBBLE
8675 || thing->type == MT_EXTRALARGEBUBBLE))
8676 continue;
8677
8678 if (!((thing->flags & MF_PUSHABLE) || ((thing->info->flags & MF_PUSHABLE) && thing->fuse))
8679 && !(thing->type == MT_PLAYER
8680 || thing->type == MT_SMALLBUBBLE
8681 || thing->type == MT_MEDIUMBUBBLE
8682 || thing->type == MT_EXTRALARGEBUBBLE
8683 || thing->type == MT_LITTLETUMBLEWEED
8684 || thing->type == MT_BIGTUMBLEWEED))
8685 continue;
8686
8687 if (thing->eflags & MFE_PUSHED)
8688 continue;
8689
8690 if (thing->player && thing->player->powers[pw_carry] == CR_ROPEHANG)
8691 continue;
8692
8693 if (thing->player && (thing->state == &states[thing->info->painstate]) && (thing->player->powers[pw_flashing] > (flashingtics/4)*3 && thing->player->powers[pw_flashing] <= flashingtics))
8694 continue;
8695
8696 inFOF = touching = moved = false;
8697
8698 // Find the area that the 'thing' is in
8699 if (p->roverpusher)
8700 {
8701 fixed_t top, bottom;
8702
8703 top = P_GetSpecialTopZ(thing, referrer, sec);
8704 bottom = P_GetSpecialBottomZ(thing, referrer, sec);
8705
8706 if (thing->eflags & MFE_VERTICALFLIP)
8707 {
8708 if (bottom > thing->z + thing->height
8709 || top < (thing->z + (thing->height >> 1)))
8710 continue;
8711
8712 if (thing->z < bottom)
8713 touching = true;
8714
8715 if (thing->z + (thing->height >> 1) > bottom)
8716 inFOF = true;
8717
8718 }
8719 else
8720 {
8721 if (top < thing->z || bottom > (thing->z + (thing->height >> 1)))
8722 continue;
8723 if (thing->z + thing->height > top)
8724 touching = true;
8725
8726 if (thing->z + (thing->height >> 1) < top)
8727 inFOF = true;
8728 }
8729 }
8730 else // Treat the entire sector as one big FOF
8731 {
8732 if (thing->z == P_GetSpecialBottomZ(thing, sec, sec))
8733 touching = true;
8734 else if (p->type != p_current)
8735 inFOF = true;
8736 }
8737
8738 if (!touching && !inFOF) // Object is out of range of effect
8739 continue;
8740
8741 if (p->type == p_wind)
8742 {
8743 if (touching) // on ground
8744 {
8745 xspeed = (p->x_mag)>>1; // half force
8746 yspeed = (p->y_mag)>>1;
8747 moved = true;
8748 }
8749 else if (inFOF)
8750 {
8751 xspeed = (p->x_mag); // full force
8752 yspeed = (p->y_mag);
8753 moved = true;
8754 }
8755 }
8756 else if (p->type == p_upwind)
8757 {
8758 if (touching) // on ground
8759 {
8760 thing->momz += (p->magnitude)>>1;
8761 moved = true;
8762 }
8763 else if (inFOF)
8764 {
8765 thing->momz += p->magnitude;
8766 moved = true;
8767 }
8768 }
8769 else if (p->type == p_downwind)
8770 {
8771 if (touching) // on ground
8772 {
8773 thing->momz -= (p->magnitude)>>1;
8774 moved = true;
8775 }
8776 else if (inFOF)
8777 {
8778 thing->momz -= p->magnitude;
8779 moved = true;
8780 }
8781 }
8782 else // p_current
8783 {
8784 if (!touching && !inFOF) // Not in water at all
8785 xspeed = yspeed = 0; // no force
8786 else // underwater / touching water
8787 {
8788 if (p->type == p_upcurrent)
8789 thing->momz += p->magnitude;
8790 else if (p->type == p_downcurrent)
8791 thing->momz -= p->magnitude;
8792 else
8793 {
8794 xspeed = p->x_mag; // full force
8795 yspeed = p->y_mag;
8796 }
8797 moved = true;
8798 }
8799 }
8800
8801 if (p->type != p_downcurrent && p->type != p_upcurrent
8802 && p->type != p_upwind && p->type != p_downwind)
8803 {
8804 thing->momx += xspeed<<(FRACBITS-PUSH_FACTOR);
8805 thing->momy += yspeed<<(FRACBITS-PUSH_FACTOR);
8806 if (thing->player)
8807 {
8808 thing->player->cmomx += xspeed<<(FRACBITS-PUSH_FACTOR);
8809 thing->player->cmomy += yspeed<<(FRACBITS-PUSH_FACTOR);
8810 thing->player->cmomx = FixedMul(thing->player->cmomx, ORIG_FRICTION);
8811 thing->player->cmomy = FixedMul(thing->player->cmomy, ORIG_FRICTION);
8812 }
8813
8814 // Tumbleweeds bounce a bit...
8815 if (thing->type == MT_LITTLETUMBLEWEED || thing->type == MT_BIGTUMBLEWEED)
8816 thing->momz += P_AproxDistance(xspeed<<(FRACBITS-PUSH_FACTOR), yspeed<<(FRACBITS-PUSH_FACTOR)) >> 2;
8817 }
8818
8819 if (moved)
8820 {
8821 if (p->slider && thing->player)
8822 {
8823 pflags_t jumped = (thing->player->pflags & (PF_JUMPED|PF_NOJUMPDAMAGE));
8824 P_ResetPlayer (thing->player);
8825
8826 if (jumped)
8827 thing->player->pflags |= jumped;
8828
8829 thing->player->pflags |= PF_SLIDING;
8830 thing->angle = R_PointToAngle2 (0, 0, xspeed<<(FRACBITS-PUSH_FACTOR), yspeed<<(FRACBITS-PUSH_FACTOR));
8831
8832 if (!demoplayback || P_ControlStyle(thing->player) == CS_LMAOGALOG)
8833 {
8834 angle_t angle = thing->player->angleturn << 16;
8835 if (thing->angle - angle > ANGLE_180)
8836 P_SetPlayerAngle(thing->player, angle - (angle - thing->angle) / 8);
8837 else
8838 P_SetPlayerAngle(thing->player, angle + (thing->angle - angle) / 8);
8839 //P_SetPlayerAngle(thing->player, thing->angle);
8840 }
8841 }
8842
8843 if (p->exclusive)
8844 thing->eflags |= MFE_PUSHED;
8845 }
8846 }
8847 }
8848
8849
8850 /** Gets a push/pull object.
8851 *
8852 * \param s Sector number to look in.
8853 * \return Pointer to the first ::MT_PUSH or ::MT_PULL object found in the
8854 * sector.
8855 * \sa P_GetTeleportDestThing, P_GetStarpostThing, P_GetAltViewThing
8856 */
P_GetPushThing(UINT32 s)8857 mobj_t *P_GetPushThing(UINT32 s)
8858 {
8859 mobj_t *thing;
8860 sector_t *sec;
8861
8862 sec = sectors + s;
8863 thing = sec->thinglist;
8864 while (thing)
8865 {
8866 switch (thing->type)
8867 {
8868 case MT_PUSH:
8869 case MT_PULL:
8870 return thing;
8871 default:
8872 break;
8873 }
8874 thing = thing->snext;
8875 }
8876 return NULL;
8877 }
8878
8879 /** Spawns pushers.
8880 *
8881 * \todo Remove magic numbers.
8882 * \sa P_SpawnSpecials, Add_Pusher
8883 */
P_SpawnPushers(void)8884 static void P_SpawnPushers(void)
8885 {
8886 size_t i;
8887 line_t *l = lines;
8888 mtag_t tag;
8889 register INT32 s;
8890 mobj_t *thing;
8891 TAG_ITER_DECLARECOUNTER(0);
8892
8893 for (i = 0; i < numlines; i++, l++)
8894 {
8895 tag = Tag_FGet(&l->tags);
8896 switch (l->special)
8897 {
8898 case 541: // wind
8899 TAG_ITER_SECTORS(0, tag, s)
8900 Add_Pusher(p_wind, l->dx, l->dy, NULL, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
8901 break;
8902 case 544: // current
8903 TAG_ITER_SECTORS(0, tag, s)
8904 Add_Pusher(p_current, l->dx, l->dy, NULL, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
8905 break;
8906 case 547: // push/pull
8907 TAG_ITER_SECTORS(0, tag, s)
8908 {
8909 thing = P_GetPushThing(s);
8910 if (thing) // No MT_P* means no effect
8911 Add_Pusher(p_push, l->dx, l->dy, thing, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
8912 }
8913 break;
8914 case 545: // current up
8915 TAG_ITER_SECTORS(0, tag, s)
8916 Add_Pusher(p_upcurrent, l->dx, l->dy, NULL, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
8917 break;
8918 case 546: // current down
8919 TAG_ITER_SECTORS(0, tag, s)
8920 Add_Pusher(p_downcurrent, l->dx, l->dy, NULL, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
8921 break;
8922 case 542: // wind up
8923 TAG_ITER_SECTORS(0, tag, s)
8924 Add_Pusher(p_upwind, l->dx, l->dy, NULL, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
8925 break;
8926 case 543: // wind down
8927 TAG_ITER_SECTORS(0, tag, s)
8928 Add_Pusher(p_downwind, l->dx, l->dy, NULL, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
8929 break;
8930 }
8931 }
8932 }
8933