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= &sectors[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 = &sectors[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 = &sectors[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 = &sectors[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 = &sectors[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, &sectors[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, &sectors[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, &sectors[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, &sectors[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, &sectors[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, &sectors[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, &sectors[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, &sectors[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(&sectors[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(&sectors[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(&sectors[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(&sectors[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(&sector->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 = &sectors[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(&sector->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 = &sectors[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(&sectors[sec], &sectors[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(&sectors[sec], &sectors[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(&sectors[sec], &sectors[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(&sectors[sec], &sectors[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(&sectors[sec], &sectors[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(&sectors[s], &sectors[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(&sectors[s].soundorg, sfx_appear);
7632 					}
7633 				}
7634 			}
7635 			sectors[s].moved = true;
7636 			P_RecalcPrecipInSector(&sectors[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(&sectors[rover->secnum]);
7685 
7686 			if (fadingdata->docolormap)
7687 				P_ResetColormapFader(&sectors[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(&sectors[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(&sectors[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 = &sectors[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