1 // Emacs style mode select	 -*- C++ -*-
2 //-----------------------------------------------------------------------------
3 //
4 // $Id:$
5 //
6 // Copyright (C) 1993-1996 by id Software, Inc.
7 //
8 // This source is available for distribution and/or modification
9 // only under the terms of the DOOM Source Code License as
10 // published by id Software. All rights reserved.
11 //
12 // The source is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License
15 // for more details.
16 //
17 // $Log:$
18 //
19 // DESCRIPTION:
20 //		Implements special effects:
21 //		Texture animation, height or lighting changes
22 //		 according to adjacent sectors, respective
23 //		 utility functions, etc.
24 //		Line Tag handling. Line and Sector triggers.
25 //		Implements donut linedef triggers
26 //		Initializes and implements BOOM linedef triggers for
27 //			Scrollers/Conveyors
28 //			Friction
29 //			Wind/Current
30 //
31 //-----------------------------------------------------------------------------
32 
33 
34 #include <stdlib.h>
35 
36 #include "templates.h"
37 #include "doomdef.h"
38 #include "doomstat.h"
39 #include "d_event.h"
40 #include "gstrings.h"
41 
42 #include "i_system.h"
43 #include "m_argv.h"
44 #include "m_random.h"
45 #include "m_bbox.h"
46 #include "w_wad.h"
47 
48 #include "p_local.h"
49 #include "p_lnspec.h"
50 #include "p_terrain.h"
51 #include "p_acs.h"
52 #include "p_3dmidtex.h"
53 
54 #include "g_game.h"
55 
56 #include "s_sound.h"
57 #include "sc_man.h"
58 #include "gi.h"
59 #include "statnums.h"
60 #include "g_level.h"
61 #include "v_font.h"
62 #include "a_sharedglobal.h"
63 #include "farchive.h"
64 #include "a_keys.h"
65 #include "c_dispatch.h"
66 #include "r_sky.h"
67 
68 // State.
69 #include "r_state.h"
70 
71 #include "c_console.h"
72 
73 #include "r_data/r_interpolate.h"
74 
75 static FRandom pr_playerinspecialsector ("PlayerInSpecialSector");
76 
77 EXTERN_CVAR(Bool, cl_predict_specials)
78 
79 IMPLEMENT_POINTY_CLASS (DScroller)
80  DECLARE_POINTER (m_Interpolations[0])
81  DECLARE_POINTER (m_Interpolations[1])
82  DECLARE_POINTER (m_Interpolations[2])
83 END_POINTERS
84 
85 IMPLEMENT_POINTY_CLASS (DPusher)
86  DECLARE_POINTER (m_Source)
87 END_POINTERS
88 
89 inline FArchive &operator<< (FArchive &arc, DScroller::EScrollType &type)
90 {
91 	BYTE val = (BYTE)type;
92 	arc << val;
93 	type = (DScroller::EScrollType)val;
94 	return arc;
95 }
96 
DScroller()97 DScroller::DScroller ()
98 {
99 }
100 
Serialize(FArchive & arc)101 void DScroller::Serialize (FArchive &arc)
102 {
103 	Super::Serialize (arc);
104 	arc << m_Type
105 		<< m_dx << m_dy
106 		<< m_Affectee
107 		<< m_Control
108 		<< m_LastHeight
109 		<< m_vdx << m_vdy
110 		<< m_Accel
111 		<< m_Parts
112 		<< m_Interpolations[0]
113 		<< m_Interpolations[1]
114 		<< m_Interpolations[2];
115 }
116 
DPusher()117 DPusher::DPusher ()
118 {
119 }
120 
operator <<(FArchive & arc,DPusher::EPusher & type)121 inline FArchive &operator<< (FArchive &arc, DPusher::EPusher &type)
122 {
123 	BYTE val = (BYTE)type;
124 	arc << val;
125 	type = (DPusher::EPusher)val;
126 	return arc;
127 }
128 
Serialize(FArchive & arc)129 void DPusher::Serialize (FArchive &arc)
130 {
131 	Super::Serialize (arc);
132 	arc << m_Type
133 		<< m_Source
134 		<< m_Xmag
135 		<< m_Ymag
136 		<< m_Magnitude
137 		<< m_Radius
138 		<< m_X
139 		<< m_Y
140 		<< m_Affectee;
141 }
142 
143 // killough 3/7/98: Initialize generalized scrolling
144 static void P_SpawnScrollers();
145 static void P_SpawnFriction ();		// phares 3/16/98
146 static void P_SpawnPushers ();		// phares 3/20/98
147 
148 
149 // [RH] Check dmflags for noexit and respond accordingly
CheckIfExitIsGood(AActor * self,level_info_t * info)150 bool CheckIfExitIsGood (AActor *self, level_info_t *info)
151 {
152 	cluster_info_t *cluster;
153 
154 	// The world can always exit itself.
155 	if (self == NULL)
156 		return true;
157 
158 	// We must kill all monsters to exit the level.
159 	if ((dmflags2 & DF2_KILL_MONSTERS) && level.killed_monsters != level.total_monsters)
160 		return false;
161 
162 	// Is this a deathmatch game and we're not allowed to exit?
163 	if ((deathmatch || alwaysapplydmflags) && (dmflags & DF_NO_EXIT))
164 	{
165 		P_DamageMobj (self, self, self, TELEFRAG_DAMAGE, NAME_Exit);
166 		return false;
167 	}
168 	// Is this a singleplayer game and the next map is part of the same hub and we're dead?
169 	if (self->health <= 0 &&
170 		!multiplayer &&
171 		info != NULL &&
172 		info->cluster == level.cluster &&
173 		(cluster = FindClusterInfo(level.cluster)) != NULL &&
174 		cluster->flags & CLUSTER_HUB)
175 	{
176 		return false;
177 	}
178 	if (deathmatch && gameaction != ga_completed)
179 	{
180 		Printf ("%s exited the level.\n", self->player->userinfo.GetName());
181 	}
182 	return true;
183 }
184 
185 
186 //
187 // UTILITIES
188 //
189 
190 //============================================================================
191 //
192 // P_ActivateLine
193 //
194 //============================================================================
195 
P_ActivateLine(line_t * line,AActor * mo,int side,int activationType)196 bool P_ActivateLine (line_t *line, AActor *mo, int side, int activationType)
197 {
198 	int lineActivation;
199 	INTBOOL repeat;
200 	INTBOOL buttonSuccess;
201 	BYTE special;
202 
203 	if (!P_TestActivateLine (line, mo, side, activationType))
204 	{
205 		return false;
206 	}
207 	bool remote = (line->special != 7 && line->special != 8 && (line->special < 11 || line->special > 14));
208 	if (line->locknumber > 0 && !P_CheckKeys (mo, line->locknumber, remote)) return false;
209 	lineActivation = line->activation;
210 	repeat = line->flags & ML_REPEAT_SPECIAL;
211 	buttonSuccess = false;
212 	buttonSuccess = P_ExecuteSpecial(line->special,
213 					line, mo, side == 1, line->args[0],
214 					line->args[1], line->args[2],
215 					line->args[3], line->args[4]);
216 
217 	special = line->special;
218 	if (!repeat && buttonSuccess)
219 	{ // clear the special on non-retriggerable lines
220 		line->special = 0;
221 	}
222 
223 	if (buttonSuccess)
224 	{
225 		if (activationType == SPAC_Use || activationType == SPAC_Impact || activationType == SPAC_Push)
226 		{
227 			P_ChangeSwitchTexture (line->sidedef[0], repeat, special);
228 		}
229 	}
230 	// some old WADs use this method to create walls that change the texture when shot.
231 	else if (activationType == SPAC_Impact &&					// only for shootable triggers
232 		(level.flags2 & LEVEL2_DUMMYSWITCHES) &&				// this is only a compatibility setting for an old hack!
233 		!repeat &&												// only non-repeatable triggers
234 		(special<Generic_Floor || special>Generic_Crusher) &&	// not for Boom's generalized linedefs
235 		special &&												// not for lines without a special
236 		tagManager.LineHasID(line, line->args[0]) &&							// Safety check: exclude edited UDMF linedefs or ones that don't map the tag to args[0]
237 		line->args[0] &&										// only if there's a tag (which is stored in the first arg)
238 		P_FindFirstSectorFromTag (line->args[0]) == -1)			// only if no sector is tagged to this linedef
239 	{
240 		P_ChangeSwitchTexture (line->sidedef[0], repeat, special);
241 		line->special = 0;
242 	}
243 // end of changed code
244 	if (developer && buttonSuccess)
245 	{
246 		Printf ("Line special %d activated on line %i\n", special, int(line - lines));
247 	}
248 	return true;
249 }
250 
251 //============================================================================
252 //
253 // P_TestActivateLine
254 //
255 //============================================================================
256 
P_TestActivateLine(line_t * line,AActor * mo,int side,int activationType)257 bool P_TestActivateLine (line_t *line, AActor *mo, int side, int activationType)
258 {
259  	int lineActivation = line->activation;
260 
261 	if (line->flags & ML_FIRSTSIDEONLY && side == 1)
262 	{
263 		return false;
264 	}
265 
266 	if (lineActivation & SPAC_UseThrough)
267 	{
268 		lineActivation |= SPAC_Use;
269 	}
270 	else if (line->special == Teleport &&
271 		(lineActivation & SPAC_Cross) &&
272 		activationType == SPAC_PCross &&
273 		mo != NULL &&
274 		mo->flags & MF_MISSILE)
275 	{ // Let missiles use regular player teleports
276 		lineActivation |= SPAC_PCross;
277 	}
278 	// BOOM's generalized line types that allow monster use can actually be
279 	// activated by anything except projectiles.
280 	if (lineActivation & SPAC_AnyCross)
281 	{
282 		lineActivation |= SPAC_Cross|SPAC_MCross;
283 	}
284 	if (activationType == SPAC_Use || activationType == SPAC_UseBack)
285 	{
286 		if (!P_CheckSwitchRange(mo, line, side))
287 		{
288 			return false;
289 		}
290 	}
291 
292 	if (activationType == SPAC_Use && (lineActivation & SPAC_MUse) && !mo->player && mo->flags4 & MF4_CANUSEWALLS)
293 	{
294 		return true;
295 	}
296 	if (activationType == SPAC_Push && (lineActivation & SPAC_MPush) && !mo->player && mo->flags2 & MF2_PUSHWALL)
297 	{
298 		return true;
299 	}
300 	if ((lineActivation & activationType) == 0)
301 	{
302 		if (activationType != SPAC_MCross || lineActivation != SPAC_Cross)
303 		{
304 			return false;
305 		}
306 	}
307 	if (activationType == SPAC_AnyCross && (lineActivation & activationType))
308 	{
309 		return true;
310 	}
311 	if (mo && !mo->player &&
312 		!(mo->flags & MF_MISSILE) &&
313 		!(line->flags & ML_MONSTERSCANACTIVATE) &&
314 		(activationType != SPAC_MCross || (!(lineActivation & SPAC_MCross))))
315 	{
316 		// [RH] monsters' ability to activate this line depends on its type
317 		// In Hexen, only MCROSS lines could be activated by monsters. With
318 		// lax activation checks, monsters can also activate certain lines
319 		// even without them being marked as monster activate-able. This is
320 		// the default for non-Hexen maps in Hexen format.
321 		if (!(level.flags2 & LEVEL2_LAXMONSTERACTIVATION))
322 		{
323 			return false;
324 		}
325 		if ((activationType == SPAC_Use || activationType == SPAC_Push)
326 			&& (line->flags & ML_SECRET))
327 			return false;		// never open secret doors
328 
329 		bool noway = true;
330 
331 		switch (activationType)
332 		{
333 		case SPAC_Use:
334 		case SPAC_Push:
335 			switch (line->special)
336 			{
337 			case Door_Raise:
338 				if (line->args[0] == 0 && line->args[1] < 64)
339 					noway = false;
340 				break;
341 			case Teleport:
342 			case Teleport_NoFog:
343 				noway = false;
344 			}
345 			break;
346 
347 		case SPAC_MCross:
348 			if (!(lineActivation & SPAC_MCross))
349 			{
350 				switch (line->special)
351 				{
352 				case Door_Raise:
353 					if (line->args[1] >= 64)
354 					{
355 						break;
356 					}
357 				case Teleport:
358 				case Teleport_NoFog:
359 				case Teleport_Line:
360 				case Plat_DownWaitUpStayLip:
361 				case Plat_DownWaitUpStay:
362 					noway = false;
363 				}
364 			}
365 			else noway = false;
366 			break;
367 
368 		default:
369 			noway = false;
370 		}
371 		return !noway;
372 	}
373 	if (activationType == SPAC_MCross && !(lineActivation & SPAC_MCross) &&
374 		!(line->flags & ML_MONSTERSCANACTIVATE))
375 	{
376 		return false;
377 	}
378 	return true;
379 }
380 
381 //============================================================================
382 //
383 // P_PredictLine
384 //
385 //============================================================================
386 
P_PredictLine(line_t * line,AActor * mo,int side,int activationType)387 bool P_PredictLine(line_t *line, AActor *mo, int side, int activationType)
388 {
389 	int lineActivation;
390 	INTBOOL buttonSuccess;
391 	BYTE special;
392 
393 	// Only predict a very specifc section of specials
394 	if (line->special != Teleport_Line &&
395 		line->special != Teleport)
396 	{
397 		return false;
398 	}
399 
400 	if (!P_TestActivateLine(line, mo, side, activationType) || !cl_predict_specials)
401 	{
402 		return false;
403 	}
404 
405 	if (line->locknumber > 0) return false;
406 	lineActivation = line->activation;
407 	buttonSuccess = false;
408 	buttonSuccess = P_ExecuteSpecial(line->special,
409 		line, mo, side == 1, line->args[0],
410 		line->args[1], line->args[2],
411 		line->args[3], line->args[4]);
412 
413 	special = line->special;
414 
415 	// end of changed code
416 	if (developer && buttonSuccess)
417 	{
418 		Printf("Line special %d predicted on line %i\n", special, int(line - lines));
419 	}
420 	return true;
421 }
422 
423 //
424 // P_PlayerInSpecialSector
425 // Called every tic frame
426 //	that the player origin is in a special sector
427 //
P_PlayerInSpecialSector(player_t * player,sector_t * sector)428 void P_PlayerInSpecialSector (player_t *player, sector_t * sector)
429 {
430 	if (sector == NULL)
431 	{
432 		// Falling, not all the way down yet?
433 		sector = player->mo->Sector;
434 		if (player->mo->Z() != sector->floorplane.ZatPoint(player->mo)
435 			&& !player->mo->waterlevel)
436 		{
437 			return;
438 		}
439 	}
440 
441 	// Has hit ground.
442 	AInventory *ironfeet;
443 
444 	// [RH] Apply any customizable damage
445 	if (sector->damageamount > 0)
446 	{
447 		// Allow subclasses. Better would be to implement it as armor and let that reduce
448 		// the damage as part of the normal damage procedure. Unfortunately, I don't have
449 		// different damage types yet, so that's not happening for now.
450 		for (ironfeet = player->mo->Inventory; ironfeet != NULL; ironfeet = ironfeet->Inventory)
451 		{
452 			if (ironfeet->IsKindOf (RUNTIME_CLASS(APowerIronFeet)))
453 				break;
454 		}
455 
456 		if (sector->Flags & SECF_ENDGODMODE) player->cheats &= ~CF_GODMODE;
457 		if ((ironfeet == NULL || pr_playerinspecialsector() < sector->leakydamage))
458 		{
459 			if (sector->Flags & SECF_HAZARD)
460 			{
461 				player->hazardcount += sector->damageamount;
462 				player->hazardtype = sector->damagetype;
463 				player->hazardinterval = sector->damageinterval;
464 			}
465 			else if (level.time % sector->damageinterval == 0)
466 			{
467 				if (!(player->cheats & (CF_GODMODE|CF_GODMODE2))) P_DamageMobj(player->mo, NULL, NULL, sector->damageamount, sector->damagetype);
468 				if ((sector->Flags & SECF_ENDLEVEL) && player->health <= 10 && (!deathmatch || !(dmflags & DF_NO_EXIT)))
469 				{
470 					G_ExitLevel(0, false);
471 				}
472 				if (sector->Flags & SECF_DMGTERRAINFX)
473 				{
474 					P_HitWater(player->mo, sector, INT_MIN, INT_MIN, INT_MIN, false, true, true);
475 				}
476 			}
477 		}
478 	}
479 	else if (sector->damageamount < 0)
480 	{
481 		if (level.time % sector->damageinterval == 0)
482 		{
483 			P_GiveBody(player->mo, -sector->damageamount, 100);
484 		}
485 	}
486 
487 	if (sector->isSecret())
488 	{
489 		sector->ClearSecret();
490 		P_GiveSecret(player->mo, true, true, int(sector - sectors));
491 	}
492 }
493 
494 //============================================================================
495 //
496 // P_SectorDamage
497 //
498 //============================================================================
499 
DoSectorDamage(AActor * actor,sector_t * sec,int amount,FName type,const PClass * protectClass,int flags)500 static void DoSectorDamage(AActor *actor, sector_t *sec, int amount, FName type, const PClass *protectClass, int flags)
501 {
502 	if (!(actor->flags & MF_SHOOTABLE))
503 		return;
504 
505 	if (!(flags & DAMAGE_NONPLAYERS) && actor->player == NULL)
506 		return;
507 
508 	if (!(flags & DAMAGE_PLAYERS) && actor->player != NULL)
509 		return;
510 
511 	if (!(flags & DAMAGE_IN_AIR) && actor->Z() != sec->floorplane.ZatPoint(actor) && !actor->waterlevel)
512 		return;
513 
514 	if (protectClass != NULL)
515 	{
516 		if (actor->FindInventory(protectClass, !!(flags & DAMAGE_SUBCLASSES_PROTECT)))
517 			return;
518 	}
519 
520 	P_DamageMobj (actor, NULL, NULL, amount, type);
521 }
522 
P_SectorDamage(int tag,int amount,FName type,const PClass * protectClass,int flags)523 void P_SectorDamage(int tag, int amount, FName type, const PClass *protectClass, int flags)
524 {
525 	FSectorTagIterator itr(tag);
526 	int secnum;
527 	while ((secnum = itr.Next()) >= 0)
528 	{
529 		AActor *actor, *next;
530 		sector_t *sec = &sectors[secnum];
531 
532 		// Do for actors in this sector.
533 		for (actor = sec->thinglist; actor != NULL; actor = next)
534 		{
535 			next = actor->snext;
536 			DoSectorDamage(actor, sec, amount, type, protectClass, flags);
537 		}
538 		// If this is a 3D floor control sector, also do for anything in/on
539 		// those 3D floors.
540 		for (unsigned i = 0; i < sec->e->XFloor.attached.Size(); ++i)
541 		{
542 			sector_t *sec2 = sec->e->XFloor.attached[i];
543 
544 			for (actor = sec2->thinglist; actor != NULL; actor = next)
545 			{
546 				next = actor->snext;
547 				// Only affect actors touching the 3D floor
548 				fixed_t z1 = sec->floorplane.ZatPoint(actor);
549 				fixed_t z2 = sec->ceilingplane.ZatPoint(actor);
550 				if (z2 < z1)
551 				{
552 					// Account for Vavoom-style 3D floors
553 					fixed_t zz = z1;
554 					z1 = z2;
555 					z2 = zz;
556 				}
557 				if (actor->Z() + actor->height > z1)
558 				{
559 					// If DAMAGE_IN_AIR is used, anything not beneath the 3D floor will be
560 					// damaged (so, anything touching it or above it). Other 3D floors between
561 					// the actor and this one will not stop this effect.
562 					if ((flags & DAMAGE_IN_AIR) || actor->Z() <= z2)
563 					{
564 						// Here we pass the DAMAGE_IN_AIR flag to disable the floor check, since it
565 						// only works with the real sector's floor. We did the appropriate height checks
566 						// for 3D floors already.
567 						DoSectorDamage(actor, NULL, amount, type, protectClass, flags | DAMAGE_IN_AIR);
568 					}
569 				}
570 			}
571 		}
572 	}
573 }
574 
575 //============================================================================
576 //
577 // P_GiveSecret
578 //
579 //============================================================================
580 
581 CVAR(Bool, showsecretsector, false, 0)
CVAR(Bool,cl_showsecretmessage,true,CVAR_ARCHIVE)582 CVAR(Bool, cl_showsecretmessage, true, CVAR_ARCHIVE)
583 
584 void P_GiveSecret(AActor *actor, bool printmessage, bool playsound, int sectornum)
585 {
586 	if (actor != NULL)
587 	{
588 		if (actor->player != NULL)
589 		{
590 			actor->player->secretcount++;
591 		}
592 		if (cl_showsecretmessage && actor->CheckLocalView(consoleplayer))
593 		{
594 			if (printmessage)
595 			{
596 				if (!showsecretsector || sectornum < 0) C_MidPrint(SmallFont, GStrings["SECRETMESSAGE"]);
597 				else
598 				{
599 					FString s = GStrings["SECRETMESSAGE"];
600 					s.AppendFormat(" (Sector %d)", sectornum);
601 					C_MidPrint(SmallFont, s);
602 				}
603 			}
604 			if (playsound) S_Sound (CHAN_AUTO | CHAN_UI, "misc/secret", 1, ATTN_NORM);
605 		}
606 	}
607 	level.found_secrets++;
608 }
609 
610 //============================================================================
611 //
612 // P_PlayerOnSpecialFlat
613 //
614 //============================================================================
615 
P_PlayerOnSpecialFlat(player_t * player,int floorType)616 void P_PlayerOnSpecialFlat (player_t *player, int floorType)
617 {
618 	if (Terrains[floorType].DamageAmount &&
619 		!(level.time & Terrains[floorType].DamageTimeMask))
620 	{
621 		AInventory *ironfeet = NULL;
622 
623 		if (Terrains[floorType].AllowProtection)
624 		{
625 			for (ironfeet = player->mo->Inventory; ironfeet != NULL; ironfeet = ironfeet->Inventory)
626 			{
627 				if (ironfeet->IsKindOf (RUNTIME_CLASS(APowerIronFeet)))
628 					break;
629 			}
630 		}
631 
632 		int damage = 0;
633 		if (ironfeet == NULL)
634 		{
635 			damage = P_DamageMobj (player->mo, NULL, NULL, Terrains[floorType].DamageAmount,
636 				Terrains[floorType].DamageMOD);
637 		}
638 		if (damage > 0 && Terrains[floorType].Splash != -1)
639 		{
640 			S_Sound (player->mo, CHAN_AUTO,
641 				Splashes[Terrains[floorType].Splash].NormalSplashSound, 1,
642 				ATTN_IDLE);
643 		}
644 	}
645 }
646 
647 
648 
649 //
650 // P_UpdateSpecials
651 // Animate planes, scroll walls, etc.
652 //
EXTERN_CVAR(Float,timelimit)653 EXTERN_CVAR (Float, timelimit)
654 
655 void P_UpdateSpecials ()
656 {
657 	// LEVEL TIMER
658 	if (deathmatch && timelimit)
659 	{
660 		if (level.maptime >= (int)(timelimit * TICRATE * 60))
661 		{
662 			Printf ("%s\n", GStrings("TXT_TIMELIMIT"));
663 			G_ExitLevel(0, false);
664 		}
665 	}
666 }
667 
668 
669 
670 //
671 // SPECIAL SPAWNING
672 //
673 
674 CUSTOM_CVAR (Bool, forcewater, false, CVAR_ARCHIVE|CVAR_SERVERINFO)
675 {
676 	if (gamestate == GS_LEVEL)
677 	{
678 		int i;
679 
680 		for (i = 0; i < numsectors; i++)
681 		{
682 			sector_t *hsec = sectors[i].GetHeightSec();
683 			if (hsec &&
684 				!(sectors[i].heightsec->MoreFlags & SECF_UNDERWATER))
685 			{
686 				if (self)
687 				{
688 					hsec->MoreFlags |= SECF_FORCEDUNDERWATER;
689 				}
690 				else
691 				{
692 					hsec->MoreFlags &= ~SECF_FORCEDUNDERWATER;
693 				}
694 			}
695 		}
696 	}
697 }
698 
699 class DLightTransfer : public DThinker
700 {
DECLARE_CLASS(DLightTransfer,DThinker)701 	DECLARE_CLASS (DLightTransfer, DThinker)
702 
703 	DLightTransfer() {}
704 public:
705 	DLightTransfer (sector_t *srcSec, int target, bool copyFloor);
706 	void Serialize (FArchive &arc);
707 	void Tick ();
708 
709 protected:
710 	static void DoTransfer (int level, int target, bool floor);
711 
712 	sector_t *Source;
713 	int TargetTag;
714 	bool CopyFloor;
715 	short LastLight;
716 };
717 
IMPLEMENT_CLASS(DLightTransfer)718 IMPLEMENT_CLASS (DLightTransfer)
719 
720 void DLightTransfer::Serialize (FArchive &arc)
721 {
722 	Super::Serialize (arc);
723 	if (SaveVersion < 3223)
724 	{
725 		BYTE bytelight;
726 		arc << bytelight;
727 		LastLight = bytelight;
728 	}
729 	else
730 	{
731 		arc << LastLight;
732 	}
733 	arc << Source << TargetTag << CopyFloor;
734 }
735 
DLightTransfer(sector_t * srcSec,int target,bool copyFloor)736 DLightTransfer::DLightTransfer (sector_t *srcSec, int target, bool copyFloor)
737 {
738 	int secnum;
739 
740 	Source = srcSec;
741 	TargetTag = target;
742 	CopyFloor = copyFloor;
743 	DoTransfer (LastLight = srcSec->lightlevel, target, copyFloor);
744 
745 	if (copyFloor)
746 	{
747 		FSectorTagIterator itr(target);
748 		while ((secnum = itr.Next()) >= 0)
749 			sectors[secnum].ChangeFlags(sector_t::floor, 0, PLANEF_ABSLIGHTING);
750 	}
751 	else
752 	{
753 		FSectorTagIterator itr(target);
754 		while ((secnum = itr.Next()) >= 0)
755 			sectors[secnum].ChangeFlags(sector_t::ceiling, 0, PLANEF_ABSLIGHTING);
756 	}
757 	ChangeStatNum (STAT_LIGHTTRANSFER);
758 }
759 
Tick()760 void DLightTransfer::Tick ()
761 {
762 	int light = Source->lightlevel;
763 
764 	if (light != LastLight)
765 	{
766 		LastLight = light;
767 		DoTransfer (light, TargetTag, CopyFloor);
768 	}
769 }
770 
DoTransfer(int level,int target,bool floor)771 void DLightTransfer::DoTransfer (int level, int target, bool floor)
772 {
773 	int secnum;
774 
775 	if (floor)
776 	{
777 		FSectorTagIterator itr(target);
778 		while ((secnum = itr.Next()) >= 0)
779 			sectors[secnum].SetPlaneLight(sector_t::floor, level);
780 	}
781 	else
782 	{
783 		FSectorTagIterator itr(target);
784 		while ((secnum = itr.Next()) >= 0)
785 			sectors[secnum].SetPlaneLight(sector_t::ceiling, level);
786 	}
787 }
788 
789 
790 class DWallLightTransfer : public DThinker
791 {
792 	enum
793 	{
794 		WLF_SIDE1=1,
795 		WLF_SIDE2=2,
796 		WLF_NOFAKECONTRAST=4
797 	};
798 
DECLARE_CLASS(DWallLightTransfer,DThinker)799 	DECLARE_CLASS (DWallLightTransfer, DThinker)
800 	DWallLightTransfer() {}
801 public:
802 	DWallLightTransfer (sector_t *srcSec, int target, BYTE flags);
803 	void Serialize (FArchive &arc);
804 	void Tick ();
805 
806 protected:
807 	static void DoTransfer (short level, int target, BYTE flags);
808 
809 	sector_t *Source;
810 	int TargetID;
811 	short LastLight;
812 	BYTE Flags;
813 };
814 
IMPLEMENT_CLASS(DWallLightTransfer)815 IMPLEMENT_CLASS (DWallLightTransfer)
816 
817 void DWallLightTransfer::Serialize (FArchive &arc)
818 {
819 	Super::Serialize (arc);
820 	if (SaveVersion < 3223)
821 	{
822 		BYTE bytelight;
823 		arc << bytelight;
824 		LastLight = bytelight;
825 	}
826 	else
827 	{
828 		arc << LastLight;
829 	}
830 	arc << Source << TargetID << Flags;
831 }
832 
DWallLightTransfer(sector_t * srcSec,int target,BYTE flags)833 DWallLightTransfer::DWallLightTransfer (sector_t *srcSec, int target, BYTE flags)
834 {
835 	int linenum;
836 	int wallflags;
837 
838 	Source = srcSec;
839 	TargetID = target;
840 	Flags = flags;
841 	DoTransfer (LastLight = srcSec->GetLightLevel(), target, Flags);
842 
843 	if (!(flags & WLF_NOFAKECONTRAST))
844 	{
845 		wallflags = WALLF_ABSLIGHTING;
846 	}
847 	else
848 	{
849 		wallflags = WALLF_ABSLIGHTING | WALLF_NOFAKECONTRAST;
850 	}
851 
852 	FLineIdIterator itr(target);
853 	while ((linenum = itr.Next()) >= 0)
854 	{
855 		if (flags & WLF_SIDE1 && lines[linenum].sidedef[0] != NULL)
856 		{
857 			lines[linenum].sidedef[0]->Flags |= wallflags;
858 		}
859 
860 		if (flags & WLF_SIDE2 && lines[linenum].sidedef[1] != NULL)
861 		{
862 			lines[linenum].sidedef[1]->Flags |= wallflags;
863 		}
864 	}
865 	ChangeStatNum(STAT_LIGHTTRANSFER);
866 }
867 
Tick()868 void DWallLightTransfer::Tick ()
869 {
870 	short light = sector_t::ClampLight(Source->lightlevel);
871 
872 	if (light != LastLight)
873 	{
874 		LastLight = light;
875 		DoTransfer (light, TargetID, Flags);
876 	}
877 }
878 
DoTransfer(short lightlevel,int target,BYTE flags)879 void DWallLightTransfer::DoTransfer (short lightlevel, int target, BYTE flags)
880 {
881 	int linenum;
882 
883 	FLineIdIterator itr(target);
884 	while ((linenum = itr.Next()) >= 0)
885 	{
886 		line_t *line = &lines[linenum];
887 
888 		if (flags & WLF_SIDE1 && line->sidedef[0] != NULL)
889 		{
890 			line->sidedef[0]->SetLight(lightlevel);
891 		}
892 
893 		if (flags & WLF_SIDE2 && line->sidedef[1] != NULL)
894 		{
895 			line->sidedef[1]->SetLight(lightlevel);
896 		}
897 	}
898 }
899 
900 //-----------------------------------------------------------------------------
901 //
902 // Portals
903 //
904 //-----------------------------------------------------------------------------
905 
906 //---------------------------------------------------------------------------
907 // Upper stacks go in the top sector. Lower stacks go in the bottom sector.
908 
SetupFloorPortal(AStackPoint * point)909 static void SetupFloorPortal (AStackPoint *point)
910 {
911 	NActorIterator it (NAME_LowerStackLookOnly, point->tid);
912 	sector_t *Sector = point->Sector;
913 	Sector->SkyBoxes[sector_t::floor] = static_cast<ASkyViewpoint*>(it.Next());
914 	if (Sector->SkyBoxes[sector_t::floor] != NULL && Sector->SkyBoxes[sector_t::floor]->bAlways)
915 	{
916 		Sector->SkyBoxes[sector_t::floor]->Mate = point;
917 		if (Sector->GetAlpha(sector_t::floor) == OPAQUE)
918 			Sector->SetAlpha(sector_t::floor, Scale (point->args[0], OPAQUE, 255));
919 	}
920 }
921 
SetupCeilingPortal(AStackPoint * point)922 static void SetupCeilingPortal (AStackPoint *point)
923 {
924 	NActorIterator it (NAME_UpperStackLookOnly, point->tid);
925 	sector_t *Sector = point->Sector;
926 	Sector->SkyBoxes[sector_t::ceiling] = static_cast<ASkyViewpoint*>(it.Next());
927 	if (Sector->SkyBoxes[sector_t::ceiling] != NULL && Sector->SkyBoxes[sector_t::ceiling]->bAlways)
928 	{
929 		Sector->SkyBoxes[sector_t::ceiling]->Mate = point;
930 		if (Sector->GetAlpha(sector_t::ceiling) == OPAQUE)
931 			Sector->SetAlpha(sector_t::ceiling, Scale (point->args[0], OPAQUE, 255));
932 	}
933 }
934 
P_SetupPortals()935 void P_SetupPortals()
936 {
937 	TThinkerIterator<AStackPoint> it;
938 	AStackPoint *pt;
939 	TArray<AStackPoint *> points;
940 
941 	while ((pt = it.Next()))
942 	{
943 		FName nm = pt->GetClass()->TypeName;
944 		if (nm == NAME_UpperStackLookOnly)
945 		{
946 			SetupFloorPortal(pt);
947 		}
948 		else if (nm == NAME_LowerStackLookOnly)
949 		{
950 			SetupCeilingPortal(pt);
951 		}
952 		pt->special1 = 0;
953 		points.Push(pt);
954 	}
955 }
956 
SetPortal(sector_t * sector,int plane,ASkyViewpoint * portal,fixed_t alpha)957 static void SetPortal(sector_t *sector, int plane, ASkyViewpoint *portal, fixed_t alpha)
958 {
959 	// plane: 0=floor, 1=ceiling, 2=both
960 	if (plane > 0)
961 	{
962 		if (sector->SkyBoxes[sector_t::ceiling] == NULL || !sector->SkyBoxes[sector_t::ceiling]->bAlways)
963 		{
964 			sector->SkyBoxes[sector_t::ceiling] = portal;
965 			if (sector->GetAlpha(sector_t::ceiling) == OPAQUE)
966 				sector->SetAlpha(sector_t::ceiling, alpha);
967 
968 			if (!portal->bAlways) sector->SetTexture(sector_t::ceiling, skyflatnum);
969 		}
970 	}
971 	if (plane == 2 || plane == 0)
972 	{
973 		if (sector->SkyBoxes[sector_t::floor] == NULL || !sector->SkyBoxes[sector_t::floor]->bAlways)
974 		{
975 			sector->SkyBoxes[sector_t::floor] = portal;
976 		}
977 		if (sector->GetAlpha(sector_t::floor) == OPAQUE)
978 			sector->SetAlpha(sector_t::floor, alpha);
979 
980 		if (!portal->bAlways) sector->SetTexture(sector_t::floor, skyflatnum);
981 	}
982 }
983 
CopyPortal(int sectortag,int plane,ASkyViewpoint * origin,fixed_t alpha,bool tolines)984 static void CopyPortal(int sectortag, int plane, ASkyViewpoint *origin, fixed_t alpha, bool tolines)
985 {
986 	int s;
987 	FSectorTagIterator itr(sectortag);
988 	while ((s = itr.Next()) >= 0)
989 	{
990 		SetPortal(&sectors[s], plane, origin, alpha);
991 	}
992 
993 	for (int j=0;j<numlines;j++)
994 	{
995 		// Check if this portal needs to be copied to other sectors
996 		// This must be done here to ensure that it gets done only after the portal is set up
997 		if (lines[j].special == Sector_SetPortal &&
998 			lines[j].args[1] == 1 &&
999 			(lines[j].args[2] == plane || lines[j].args[2] == 3) &&
1000 			lines[j].args[3] == sectortag)
1001 		{
1002 			if (lines[j].args[0] == 0)
1003 			{
1004 				SetPortal(lines[j].frontsector, plane, origin, alpha);
1005 			}
1006 			else
1007 			{
1008 				FSectorTagIterator itr(lines[j].args[0]);
1009 				while ((s = itr.Next()) >= 0)
1010 				{
1011 					SetPortal(&sectors[s], plane, origin, alpha);
1012 				}
1013 			}
1014 		}
1015 	}
1016 }
1017 
P_SpawnPortal(line_t * line,int sectortag,int plane,int alpha)1018 void P_SpawnPortal(line_t *line, int sectortag, int plane, int alpha)
1019 {
1020 	for (int i=0;i<numlines;i++)
1021 	{
1022 		// We must look for the reference line with a linear search unless we want to waste the line ID for it
1023 		// which is not a good idea.
1024 		if (lines[i].special == Sector_SetPortal &&
1025 			lines[i].args[0] == sectortag &&
1026 			lines[i].args[1] == 0 &&
1027 			lines[i].args[2] == plane &&
1028 			lines[i].args[3] == 1)
1029 		{
1030 			// beware of overflows.
1031 			fixed_t x1 = fixed_t((SQWORD(line->v1->x) + SQWORD(line->v2->x)) >> 1);
1032 			fixed_t y1 = fixed_t((SQWORD(line->v1->y) + SQWORD(line->v2->y)) >> 1);
1033 			fixed_t x2 = fixed_t((SQWORD(lines[i].v1->x) + SQWORD(lines[i].v2->x)) >> 1);
1034 			fixed_t y2 = fixed_t((SQWORD(lines[i].v1->y) + SQWORD(lines[i].v2->y)) >> 1);
1035 			fixed_t alpha = Scale (lines[i].args[4], OPAQUE, 255);
1036 
1037 			AStackPoint *anchor = Spawn<AStackPoint>(x1, y1, 0, NO_REPLACE);
1038 			AStackPoint *reference = Spawn<AStackPoint>(x2, y2, 0, NO_REPLACE);
1039 
1040 			reference->Mate = anchor;
1041 			anchor->Mate = reference;
1042 
1043 			// This is so that the renderer can distinguish these portals from
1044 			// the ones spawned with the '*StackLookOnly' things.
1045 			reference->flags |= MF_JUSTATTACKED;
1046 			anchor->flags |= MF_JUSTATTACKED;
1047 
1048 			CopyPortal(sectortag, plane, reference, alpha, false);
1049 			return;
1050 		}
1051 	}
1052 }
1053 
1054 // This searches the viewpoint's sector
1055 // for a skybox line special, gets its tag and transfers the skybox to all tagged sectors.
P_SpawnSkybox(ASkyViewpoint * origin)1056 void P_SpawnSkybox(ASkyViewpoint *origin)
1057 {
1058 	sector_t *Sector = origin->Sector;
1059 	if (Sector == NULL)
1060 	{
1061 		Printf("Sector not initialized for SkyCamCompat\n");
1062 		origin->Sector = Sector = P_PointInSector(origin->X(), origin->Y());
1063 	}
1064 	if (Sector)
1065 	{
1066 		line_t * refline = NULL;
1067 		for (short i = 0; i < Sector->linecount; i++)
1068 		{
1069 			refline = Sector->lines[i];
1070 			if (refline->special == Sector_SetPortal && refline->args[1] == 2)
1071 			{
1072 				// We found the setup linedef for this skybox, so let's use it for our init.
1073 				CopyPortal(refline->args[0], refline->args[2], origin, 0, true);
1074 				return;
1075 			}
1076 		}
1077 	}
1078 }
1079 
1080 
1081 
1082 //
1083 // P_SetSectorDamage
1084 //
1085 // Sets damage properties for one sector. Allows combination of original specials with explicit use of the damage properties
1086 //
1087 
P_SetupSectorDamage(sector_t * sector,int damage,int interval,int leakchance,FName type,int flags)1088 static void P_SetupSectorDamage(sector_t *sector, int damage, int interval, int leakchance, FName type, int flags)
1089 {
1090 	// Only set if damage is not yet initialized. This ensures that UDMF takes precedence over sector specials.
1091 	if (sector->damageamount == 0)
1092 	{
1093 		sector->damageamount = damage;
1094 		sector->damageinterval = MAX(1, interval);
1095 		sector->leakydamage = leakchance;
1096 		sector->damagetype = type;
1097 		sector->Flags = (sector->Flags & ~SECF_DAMAGEFLAGS) | (flags & SECF_DAMAGEFLAGS);
1098 	}
1099 }
1100 
1101 //
1102 // P_InitSectorSpecial
1103 //
1104 // Sets up everything derived from 'sector->special' for one sector
1105 // ('fromload' is necessary to allow conversion upon savegame load.)
1106 //
1107 
P_InitSectorSpecial(sector_t * sector,int special,bool nothinkers)1108 void P_InitSectorSpecial(sector_t *sector, int special, bool nothinkers)
1109 {
1110 	// [RH] All secret sectors are marked with a BOOM-ish bitfield
1111 	if (sector->special & SECRET_MASK)
1112 	{
1113 		sector->Flags |= SECF_SECRET | SECF_WASSECRET;
1114 		level.total_secrets++;
1115 	}
1116 	if (sector->special & FRICTION_MASK)
1117 	{
1118 		sector->Flags |= SECF_FRICTION;
1119 	}
1120 	if (sector->special & PUSH_MASK)
1121 	{
1122 		sector->Flags |= SECF_PUSH;
1123 	}
1124 	if ((sector->special & DAMAGE_MASK) == 0x100)
1125 	{
1126 		P_SetupSectorDamage(sector, 5, 32, 0, NAME_Fire, 0);
1127 	}
1128 	else if ((sector->special & DAMAGE_MASK) == 0x200)
1129 	{
1130 		P_SetupSectorDamage(sector, 10, 32, 0, NAME_Slime, 0);
1131 	}
1132 	else if ((sector->special & DAMAGE_MASK) == 0x300)
1133 	{
1134 		P_SetupSectorDamage(sector, 20, 32, 5, NAME_Slime, 0);
1135 	}
1136 	sector->special &= 0xff;
1137 
1138 	// [RH] Normal DOOM special or BOOM specialized?
1139 	bool keepspecial = false;
1140 	switch (sector->special)
1141 	{
1142 	case Light_Phased:
1143 		if (!nothinkers) new DPhased (sector, 48, 63 - (sector->lightlevel & 63));
1144 		break;
1145 
1146 		// [RH] Hexen-like phased lighting
1147 	case LightSequenceStart:
1148 		if (!nothinkers) new DPhased (sector);
1149 		break;
1150 
1151 	case dLight_Flicker:
1152 		if (!nothinkers) new DLightFlash (sector);
1153 		break;
1154 
1155 	case dLight_StrobeFast:
1156 		if (!nothinkers) new DStrobe (sector, STROBEBRIGHT, FASTDARK, false);
1157 		break;
1158 
1159 	case dLight_StrobeSlow:
1160 		if (!nothinkers) new DStrobe (sector, STROBEBRIGHT, SLOWDARK, false);
1161 		break;
1162 
1163 	case dLight_Strobe_Hurt:
1164 		if (!nothinkers) new DStrobe (sector, STROBEBRIGHT, FASTDARK, false);
1165 		P_SetupSectorDamage(sector, 20, 32, 5, NAME_Slime, 0);
1166 		break;
1167 
1168 	case dDamage_Hellslime:
1169 		P_SetupSectorDamage(sector, 10, 32, 0, NAME_Slime, 0);
1170 		break;
1171 
1172 	case dDamage_Nukage:
1173 		P_SetupSectorDamage(sector, 5, 32, 0, NAME_Slime, 0);
1174 		break;
1175 
1176 	case dLight_Glow:
1177 		if (!nothinkers) new DGlow (sector);
1178 		break;
1179 
1180 	case dSector_DoorCloseIn30:
1181 		P_SpawnDoorCloseIn30 (sector);
1182 		break;
1183 
1184 	case dDamage_End:
1185 		P_SetupSectorDamage(sector, 20, 32, 256, NAME_None, SECF_ENDGODMODE|SECF_ENDLEVEL);
1186 		break;
1187 
1188 	case dLight_StrobeSlowSync:
1189 		if (!nothinkers) new DStrobe (sector, STROBEBRIGHT, SLOWDARK, true);
1190 		break;
1191 
1192 	case dLight_StrobeFastSync:
1193 		if (!nothinkers) new DStrobe (sector, STROBEBRIGHT, FASTDARK, true);
1194 		break;
1195 
1196 	case dSector_DoorRaiseIn5Mins:
1197 		P_SpawnDoorRaiseIn5Mins (sector);
1198 		break;
1199 
1200 	case dFriction_Low:
1201 		sector->friction = FRICTION_LOW;
1202 		sector->movefactor = 0x269;
1203 		sector->Flags |= SECF_FRICTION;
1204 		break;
1205 
1206 	case dDamage_SuperHellslime:
1207 		P_SetupSectorDamage(sector, 20, 32, 5, NAME_Slime, 0);
1208 		break;
1209 
1210 	case dLight_FireFlicker:
1211 		if (!nothinkers) new DFireFlicker (sector);
1212 		break;
1213 
1214 	case dDamage_LavaWimpy:
1215 		P_SetupSectorDamage(sector, 5, 32, 256, NAME_Fire, SECF_DMGTERRAINFX);
1216 		break;
1217 
1218 	case dDamage_LavaHefty:
1219 		P_SetupSectorDamage(sector, 8, 32, 256, NAME_Fire, SECF_DMGTERRAINFX);
1220 		break;
1221 
1222 	case dScroll_EastLavaDamage:
1223 		P_SetupSectorDamage(sector, 5, 32, 256, NAME_Fire, SECF_DMGTERRAINFX);
1224 		if (!nothinkers)
1225 		{
1226 			new DStrobe(sector, STROBEBRIGHT, FASTDARK, false);
1227 			new DScroller(DScroller::sc_floor, -((FRACUNIT / 2) << 3),
1228 				0, -1, int(sector - sectors), 0);
1229 		}
1230 		keepspecial = true;
1231 		break;
1232 
1233 	case hDamage_Sludge:
1234 		P_SetupSectorDamage(sector, 4, 32, 0, NAME_Slime, 0);
1235 		break;
1236 
1237 	case sLight_Strobe_Hurt:
1238 		P_SetupSectorDamage(sector, 5, 32, 0, NAME_Slime, 0);
1239 		if (!nothinkers) new DStrobe (sector, STROBEBRIGHT, FASTDARK, false);
1240 		break;
1241 
1242 	case sDamage_Hellslime:
1243 		P_SetupSectorDamage(sector, 2, 32, 0, NAME_Slime, SECF_HAZARD);
1244 		break;
1245 
1246 	case Damage_InstantDeath:
1247 		// Strife's instant death sector
1248 		P_SetupSectorDamage(sector, TELEFRAG_DAMAGE, 1, 256, NAME_InstantDeath, 0);
1249 		break;
1250 
1251 	case sDamage_SuperHellslime:
1252 		P_SetupSectorDamage(sector, 4, 32, 0, NAME_Slime, SECF_HAZARD);
1253 		break;
1254 
1255 	case Sector_Hidden:
1256 		sector->MoreFlags |= SECF_HIDDEN;
1257 		break;
1258 
1259 	case Sector_Heal:
1260 		// CoD's healing sector
1261 		P_SetupSectorDamage(sector, -1, 32, 0, NAME_None, 0);
1262 		break;
1263 
1264 	case Sky2:
1265 		sector->sky = PL_SKYFLAT;
1266 		break;
1267 
1268 	default:
1269 		if (sector->special >= Scroll_North_Slow &&
1270 			sector->special <= Scroll_SouthWest_Fast)
1271 		{ // Hexen scroll special
1272 			static const signed char hexenScrollies[24][2] =
1273 			{
1274 				{  0,  1 }, {  0,  2 }, {  0,  4 },
1275 				{ -1,  0 }, { -2,  0 }, { -4,  0 },
1276 				{  0, -1 }, {  0, -2 }, {  0, -4 },
1277 				{  1,  0 }, {  2,  0 }, {  4,  0 },
1278 				{  1,  1 }, {  2,  2 }, {  4,  4 },
1279 				{ -1,  1 }, { -2,  2 }, { -4,  4 },
1280 				{ -1, -1 }, { -2, -2 }, { -4, -4 },
1281 				{  1, -1 }, {  2, -2 }, {  4, -4 }
1282 			};
1283 
1284 
1285 			int i = sector->special - Scroll_North_Slow;
1286 			fixed_t dx = hexenScrollies[i][0] * (FRACUNIT/2);
1287 			fixed_t dy = hexenScrollies[i][1] * (FRACUNIT/2);
1288 			if (!nothinkers) new DScroller (DScroller::sc_floor, dx, dy, -1, int(sector-sectors), 0);
1289 		}
1290 		else if (sector->special >= Carry_East5 &&
1291 					sector->special <= Carry_East35)
1292 		{ // Heretic scroll special
1293 			// Only east scrollers also scroll the texture
1294 			if (!nothinkers) new DScroller (DScroller::sc_floor,
1295 				(-FRACUNIT/2)<<(sector->special - Carry_East5),
1296 				0, -1, int(sector-sectors), 0);
1297 		}
1298 		keepspecial = true;
1299 		break;
1300 	}
1301 	if (!keepspecial) sector->special = 0;
1302 }
1303 
1304 //
1305 // P_SpawnSpecials
1306 //
1307 // After the map has been loaded, scan for specials that spawn thinkers
1308 //
1309 
P_SpawnSpecials(void)1310 void P_SpawnSpecials (void)
1311 {
1312 	sector_t *sector;
1313 	int i;
1314 
1315 	P_SetupPortals();
1316 
1317 	//	Init special SECTORs.
1318 	sector = sectors;
1319 	for (i = 0; i < numsectors; i++, sector++)
1320 	{
1321 		if (sector->special == 0)
1322 			continue;
1323 
1324 		P_InitSectorSpecial(sector, sector->special, false);
1325 	}
1326 
1327 	// Init other misc stuff
1328 
1329 	P_SpawnScrollers(); // killough 3/7/98: Add generalized scrollers
1330 	P_SpawnFriction();	// phares 3/12/98: New friction model using linedefs
1331 	P_SpawnPushers();	// phares 3/20/98: New pusher model using linedefs
1332 
1333 	TThinkerIterator<ASkyCamCompat> it2;
1334 	ASkyCamCompat *pt2;
1335 	while ((pt2 = it2.Next()))
1336 	{
1337 		P_SpawnSkybox(pt2);
1338 	}
1339 
1340 	for (i = 0; i < numlines; i++)
1341 	{
1342 		switch (lines[i].special)
1343 		{
1344 			int s;
1345 			sector_t *sec;
1346 
1347 		// killough 3/7/98:
1348 		// support for drawn heights coming from different sector
1349 		case Transfer_Heights:
1350 			{
1351 				sec = lines[i].frontsector;
1352 				if (lines[i].args[1] & 2)
1353 				{
1354 					sec->MoreFlags |= SECF_FAKEFLOORONLY;
1355 				}
1356 				if (lines[i].args[1] & 4)
1357 				{
1358 					sec->MoreFlags |= SECF_CLIPFAKEPLANES;
1359 				}
1360 				if (lines[i].args[1] & 8)
1361 				{
1362 					sec->MoreFlags |= SECF_UNDERWATER;
1363 				}
1364 				else if (forcewater)
1365 				{
1366 					sec->MoreFlags |= SECF_FORCEDUNDERWATER;
1367 				}
1368 				if (lines[i].args[1] & 16)
1369 				{
1370 					sec->MoreFlags |= SECF_IGNOREHEIGHTSEC;
1371 				}
1372 				if (lines[i].args[1] & 32)
1373 				{
1374 					sec->MoreFlags |= SECF_NOFAKELIGHT;
1375 				}
1376 				FSectorTagIterator itr(lines[i].args[0]);
1377 				while ((s = itr.Next()) >= 0)
1378 				{
1379 					sectors[s].heightsec = sec;
1380 					sec->e->FakeFloor.Sectors.Push(&sectors[s]);
1381 					sectors[s].AdjustFloorClip();
1382 				}
1383 				break;
1384 			}
1385 
1386 		// killough 3/16/98: Add support for setting
1387 		// floor lighting independently (e.g. lava)
1388 		case Transfer_FloorLight:
1389 			new DLightTransfer (lines[i].frontsector, lines[i].args[0], true);
1390 			break;
1391 
1392 		// killough 4/11/98: Add support for setting
1393 		// ceiling lighting independently
1394 		case Transfer_CeilingLight:
1395 			new DLightTransfer (lines[i].frontsector, lines[i].args[0], false);
1396 			break;
1397 
1398 		// [Graf Zahl] Add support for setting lighting
1399 		// per wall independently
1400 		case Transfer_WallLight:
1401 			new DWallLightTransfer (lines[i].frontsector, lines[i].args[0], lines[i].args[1]);
1402 			break;
1403 
1404 		case Sector_Attach3dMidtex:
1405 			P_Attach3dMidtexLinesToSector(lines[i].frontsector, lines[i].args[0], lines[i].args[1], !!lines[i].args[2]);
1406 			break;
1407 
1408 		case Sector_SetLink:
1409 			if (lines[i].args[0] == 0)
1410 			{
1411 				P_AddSectorLinks(lines[i].frontsector, lines[i].args[1], lines[i].args[2], lines[i].args[3]);
1412 			}
1413 			break;
1414 
1415 		case Sector_SetPortal:
1416 			// arg 0 = sector tag
1417 			// arg 1 = type
1418 			//	- 0: normal (handled here)
1419 			//	- 1: copy (handled by the portal they copy)
1420 			//	- 2: EE-style skybox (handled by the camera object)
1421 			//	other values reserved for later use
1422 			// arg 2 = 0:floor, 1:ceiling, 2:both
1423 			// arg 3 = 0: anchor, 1: reference line
1424 			// arg 4 = for the anchor only: alpha
1425 			if (lines[i].args[1] == 0 && lines[i].args[3] == 0)
1426 			{
1427 				P_SpawnPortal(&lines[i], lines[i].args[0], lines[i].args[2], lines[i].args[4]);
1428 			}
1429 			break;
1430 
1431 		// [RH] ZDoom Static_Init settings
1432 		case Static_Init:
1433 			switch (lines[i].args[1])
1434 			{
1435 			case Init_Gravity:
1436 				{
1437 					float grav = ((float)P_AproxDistance (lines[i].dx, lines[i].dy)) / (FRACUNIT * 100.0f);
1438 					FSectorTagIterator itr(lines[i].args[0]);
1439 					while ((s = itr.Next()) >= 0)
1440 						sectors[s].gravity = grav;
1441 				}
1442 				break;
1443 
1444 			//case Init_Color:
1445 			// handled in P_LoadSideDefs2()
1446 
1447 			case Init_Damage:
1448 				{
1449 					int damage = P_AproxDistance (lines[i].dx, lines[i].dy) >> FRACBITS;
1450 					FSectorTagIterator itr(lines[i].args[0]);
1451 					while ((s = itr.Next()) >= 0)
1452 					{
1453 						sector_t *sec = &sectors[s];
1454 						sec->damageamount = damage;
1455 						sec->damagetype = NAME_None;
1456 						if (sec->damageamount < 20)
1457 						{
1458 							sec->leakydamage = 0;
1459 							sec->damageinterval = 32;
1460 						}
1461 						else if (sec->damageamount < 50)
1462 						{
1463 							sec->leakydamage = 5;
1464 							sec->damageinterval = 32;
1465 						}
1466 						else
1467 						{
1468 							sec->leakydamage = 256;
1469 							sec->damageinterval = 1;
1470 						}
1471 					}
1472 				}
1473 				break;
1474 
1475 			case Init_SectorLink:
1476 				if (lines[i].args[3] == 0)
1477 					P_AddSectorLinksByID(lines[i].frontsector, lines[i].args[0], lines[i].args[2]);
1478 				break;
1479 
1480 			// killough 10/98:
1481 			//
1482 			// Support for sky textures being transferred from sidedefs.
1483 			// Allows scrolling and other effects (but if scrolling is
1484 			// used, then the same sector tag needs to be used for the
1485 			// sky sector, the sky-transfer linedef, and the scroll-effect
1486 			// linedef). Still requires user to use F_SKY1 for the floor
1487 			// or ceiling texture, to distinguish floor and ceiling sky.
1488 
1489 			case Init_TransferSky:
1490 				{
1491 					FSectorTagIterator itr(lines[i].args[0]);
1492 					while ((s = itr.Next()) >= 0)
1493 						 sectors[s].sky = (i + 1) | PL_SKYFLAT;
1494 					break;
1495 				}
1496 			}
1497 			break;
1498 		}
1499 	}
1500 	// [RH] Start running any open scripts on this map
1501 	FBehavior::StaticStartTypedScripts (SCRIPT_Open, NULL, false);
1502 }
1503 
1504 // killough 2/28/98:
1505 //
1506 // This function, with the help of r_plane.c and r_bsp.c, supports generalized
1507 // scrolling floors and walls, with optional mobj-carrying properties, e.g.
1508 // conveyor belts, rivers, etc. A linedef with a special type affects all
1509 // tagged sectors the same way, by creating scrolling and/or object-carrying
1510 // properties. Multiple linedefs may be used on the same sector and are
1511 // cumulative, although the special case of scrolling a floor and carrying
1512 // things on it, requires only one linedef. The linedef's direction determines
1513 // the scrolling direction, and the linedef's length determines the scrolling
1514 // speed. This was designed so that an edge around the sector could be used to
1515 // control the direction of the sector's scrolling, which is usually what is
1516 // desired.
1517 //
1518 // Process the active scrollers.
1519 //
1520 // This is the main scrolling code
1521 // killough 3/7/98
1522 
1523 // [RH] Compensate for rotated sector textures by rotating the scrolling
1524 // in the opposite direction.
RotationComp(const sector_t * sec,int which,fixed_t dx,fixed_t dy,fixed_t & tdx,fixed_t & tdy)1525 static void RotationComp(const sector_t *sec, int which, fixed_t dx, fixed_t dy, fixed_t &tdx, fixed_t &tdy)
1526 {
1527 	angle_t an = sec->GetAngle(which);
1528 	if (an == 0)
1529 	{
1530 		tdx = dx;
1531 		tdy = dy;
1532 	}
1533 	else
1534 	{
1535 		an = an >> ANGLETOFINESHIFT;
1536 		fixed_t ca = -finecosine[an];
1537 		fixed_t sa = -finesine[an];
1538 		tdx = DMulScale16(dx, ca, -dy, sa);
1539 		tdy = DMulScale16(dy, ca,  dx, sa);
1540 	}
1541 }
1542 
Tick()1543 void DScroller::Tick ()
1544 {
1545 	fixed_t dx = m_dx, dy = m_dy, tdx, tdy;
1546 
1547 	if (m_Control != -1)
1548 	{	// compute scroll amounts based on a sector's height changes
1549 		fixed_t height = sectors[m_Control].CenterFloor () +
1550 						 sectors[m_Control].CenterCeiling ();
1551 		fixed_t delta = height - m_LastHeight;
1552 		m_LastHeight = height;
1553 		dx = FixedMul(dx, delta);
1554 		dy = FixedMul(dy, delta);
1555 	}
1556 
1557 	// killough 3/14/98: Add acceleration
1558 	if (m_Accel)
1559 	{
1560 		m_vdx = dx += m_vdx;
1561 		m_vdy = dy += m_vdy;
1562 	}
1563 
1564 	if (!(dx | dy))			// no-op if both (x,y) offsets are 0
1565 		return;
1566 
1567 	switch (m_Type)
1568 	{
1569 		case sc_side:					// killough 3/7/98: Scroll wall texture
1570 			if (m_Parts & scw_top)
1571 			{
1572 				sides[m_Affectee].AddTextureXOffset(side_t::top, dx);
1573 				sides[m_Affectee].AddTextureYOffset(side_t::top, dy);
1574 			}
1575 			if (m_Parts & scw_mid && (sides[m_Affectee].linedef->backsector == NULL ||
1576 				!(sides[m_Affectee].linedef->flags&ML_3DMIDTEX)))
1577 			{
1578 				sides[m_Affectee].AddTextureXOffset(side_t::mid, dx);
1579 				sides[m_Affectee].AddTextureYOffset(side_t::mid, dy);
1580 			}
1581 			if (m_Parts & scw_bottom)
1582 			{
1583 				sides[m_Affectee].AddTextureXOffset(side_t::bottom, dx);
1584 				sides[m_Affectee].AddTextureYOffset(side_t::bottom, dy);
1585 			}
1586 			break;
1587 
1588 		case sc_floor:						// killough 3/7/98: Scroll floor texture
1589 			RotationComp(&sectors[m_Affectee], sector_t::floor, dx, dy, tdx, tdy);
1590 			sectors[m_Affectee].AddXOffset(sector_t::floor, tdx);
1591 			sectors[m_Affectee].AddYOffset(sector_t::floor, tdy);
1592 			break;
1593 
1594 		case sc_ceiling:					// killough 3/7/98: Scroll ceiling texture
1595 			RotationComp(&sectors[m_Affectee], sector_t::ceiling, dx, dy, tdx, tdy);
1596 			sectors[m_Affectee].AddXOffset(sector_t::ceiling, tdx);
1597 			sectors[m_Affectee].AddYOffset(sector_t::ceiling, tdy);
1598 			break;
1599 
1600 		// [RH] Don't actually carry anything here. That happens later.
1601 		case sc_carry:
1602 			level.Scrolls[m_Affectee].ScrollX += dx;
1603 			level.Scrolls[m_Affectee].ScrollY += dy;
1604 			break;
1605 
1606 		case sc_carry_ceiling:       // to be added later
1607 			break;
1608 	}
1609 }
1610 
1611 //
1612 // Add_Scroller()
1613 //
1614 // Add a generalized scroller to the thinker list.
1615 //
1616 // type: the enumerated type of scrolling: floor, ceiling, floor carrier,
1617 //   wall, floor carrier & scroller
1618 //
1619 // (dx,dy): the direction and speed of the scrolling or its acceleration
1620 //
1621 // control: the sector whose heights control this scroller's effect
1622 //   remotely, or -1 if no control sector
1623 //
1624 // affectee: the index of the affected object (sector or sidedef)
1625 //
1626 // accel: non-zero if this is an accelerative effect
1627 //
1628 
DScroller(EScrollType type,fixed_t dx,fixed_t dy,int control,int affectee,int accel,int scrollpos)1629 DScroller::DScroller (EScrollType type, fixed_t dx, fixed_t dy,
1630 					  int control, int affectee, int accel, int scrollpos)
1631 	: DThinker (STAT_SCROLLER)
1632 {
1633 	m_Type = type;
1634 	m_dx = dx;
1635 	m_dy = dy;
1636 	m_Accel = accel;
1637 	m_Parts = scrollpos;
1638 	m_vdx = m_vdy = 0;
1639 	if ((m_Control = control) != -1)
1640 		m_LastHeight =
1641 			sectors[control].CenterFloor () + sectors[control].CenterCeiling ();
1642 	m_Affectee = affectee;
1643 	m_Interpolations[0] = m_Interpolations[1] = m_Interpolations[2] = NULL;
1644 
1645 	switch (type)
1646 	{
1647 	case sc_carry:
1648 		level.AddScroller (this, affectee);
1649 		break;
1650 
1651 	case sc_side:
1652 		sides[affectee].Flags |= WALLF_NOAUTODECALS;
1653 		if (m_Parts & scw_top)
1654 		{
1655 			m_Interpolations[0] = sides[m_Affectee].SetInterpolation(side_t::top);
1656 		}
1657 		if (m_Parts & scw_mid && (sides[m_Affectee].linedef->backsector == NULL ||
1658 			!(sides[m_Affectee].linedef->flags&ML_3DMIDTEX)))
1659 		{
1660 			m_Interpolations[1] = sides[m_Affectee].SetInterpolation(side_t::mid);
1661 		}
1662 		if (m_Parts & scw_bottom)
1663 		{
1664 			m_Interpolations[2] = sides[m_Affectee].SetInterpolation(side_t::bottom);
1665 		}
1666 		break;
1667 
1668 	case sc_floor:
1669 		m_Interpolations[0] = sectors[affectee].SetInterpolation(sector_t::FloorScroll, false);
1670 		break;
1671 
1672 	case sc_ceiling:
1673 		m_Interpolations[0] = sectors[affectee].SetInterpolation(sector_t::CeilingScroll, false);
1674 		break;
1675 
1676 	default:
1677 		break;
1678 	}
1679 }
1680 
Destroy()1681 void DScroller::Destroy ()
1682 {
1683 	for(int i=0;i<3;i++)
1684 	{
1685 		if (m_Interpolations[i] != NULL)
1686 		{
1687 			m_Interpolations[i]->DelRef();
1688 			m_Interpolations[i] = NULL;
1689 		}
1690 	}
1691 	Super::Destroy();
1692 }
1693 
1694 // Adds wall scroller. Scroll amount is rotated with respect to wall's
1695 // linedef first, so that scrolling towards the wall in a perpendicular
1696 // direction is translated into vertical motion, while scrolling along
1697 // the wall in a parallel direction is translated into horizontal motion.
1698 //
1699 // killough 5/25/98: cleaned up arithmetic to avoid drift due to roundoff
1700 
DScroller(fixed_t dx,fixed_t dy,const line_t * l,int control,int accel,int scrollpos)1701 DScroller::DScroller (fixed_t dx, fixed_t dy, const line_t *l,
1702 					 int control, int accel, int scrollpos)
1703 	: DThinker (STAT_SCROLLER)
1704 {
1705 	fixed_t x = abs(l->dx), y = abs(l->dy), d;
1706 	if (y > x)
1707 		d = x, x = y, y = d;
1708 	d = FixedDiv (x, finesine[(tantoangle[FixedDiv(y,x) >> DBITS] + ANG90)
1709 						  >> ANGLETOFINESHIFT]);
1710 	x = -FixedDiv (FixedMul(dy, l->dy) + FixedMul(dx, l->dx), d);
1711 	y = -FixedDiv (FixedMul(dx, l->dy) - FixedMul(dy, l->dx), d);
1712 
1713 	m_Type = sc_side;
1714 	m_dx = x;
1715 	m_dy = y;
1716 	m_vdx = m_vdy = 0;
1717 	m_Accel = accel;
1718 	m_Parts = scrollpos;
1719 	if ((m_Control = control) != -1)
1720 		m_LastHeight = sectors[control].CenterFloor() + sectors[control].CenterCeiling();
1721 	m_Affectee = int(l->sidedef[0] - sides);
1722 	sides[m_Affectee].Flags |= WALLF_NOAUTODECALS;
1723 	m_Interpolations[0] = m_Interpolations[1] = m_Interpolations[2] = NULL;
1724 
1725 	if (m_Parts & scw_top)
1726 	{
1727 		m_Interpolations[0] = sides[m_Affectee].SetInterpolation(side_t::top);
1728 	}
1729 	if (m_Parts & scw_mid && (sides[m_Affectee].linedef->backsector == NULL ||
1730 		!(sides[m_Affectee].linedef->flags&ML_3DMIDTEX)))
1731 	{
1732 		m_Interpolations[1] = sides[m_Affectee].SetInterpolation(side_t::mid);
1733 	}
1734 	if (m_Parts & scw_bottom)
1735 	{
1736 		m_Interpolations[2] = sides[m_Affectee].SetInterpolation(side_t::bottom);
1737 	}
1738 }
1739 
1740 // Amount (dx,dy) vector linedef is shifted right to get scroll amount
1741 #define SCROLL_SHIFT 5
1742 #define SCROLLTYPE(i) (((i) <= 0) || ((i) & ~7) ? 7 : (i))
1743 
1744 // Initialize the scrollers
P_SpawnScrollers(void)1745 static void P_SpawnScrollers(void)
1746 {
1747 	int i;
1748 	line_t *l = lines;
1749 	TArray<int> copyscrollers;
1750 
1751 	for (i = 0; i < numlines; i++)
1752 	{
1753 		if (lines[i].special == Sector_CopyScroller)
1754 		{
1755 			// don't allow copying the scroller if the sector has the same tag as it would just duplicate it.
1756 			if (!tagManager.SectorHasTag(lines[i].frontsector, lines[i].args[0]))
1757 			{
1758 				copyscrollers.Push(i);
1759 			}
1760 			lines[i].special = 0;
1761 		}
1762 	}
1763 
1764 	for (i = 0; i < numlines; i++, l++)
1765 	{
1766 		fixed_t dx;	// direction and speed of scrolling
1767 		fixed_t dy;
1768 		int control = -1, accel = 0;		// no control sector or acceleration
1769 		int special = l->special;
1770 
1771 		// Check for undefined parameters that are non-zero and output messages for them.
1772 		// We don't report for specials we don't understand.
1773 		if (special != 0)
1774 		{
1775 			int max = LineSpecialsInfo[special] != NULL ? LineSpecialsInfo[special]->map_args : countof(l->args);
1776 			for (unsigned arg = max; arg < countof(l->args); ++arg)
1777 			{
1778 				if (l->args[arg] != 0)
1779 				{
1780 					Printf("Line %d (type %d:%s), arg %u is %d (should be 0)\n",
1781 						i, special, LineSpecialsInfo[special]->name, arg+1, l->args[arg]);
1782 				}
1783 			}
1784 		}
1785 
1786 		// killough 3/7/98: Types 245-249 are same as 250-254 except that the
1787 		// first side's sector's heights cause scrolling when they change, and
1788 		// this linedef controls the direction and speed of the scrolling. The
1789 		// most complicated linedef since donuts, but powerful :)
1790 		//
1791 		// killough 3/15/98: Add acceleration. Types 214-218 are the same but
1792 		// are accelerative.
1793 
1794 		// [RH] Assume that it's a scroller and zero the line's special.
1795 		l->special = 0;
1796 
1797 		dx = dy = 0;	// Shut up, GCC
1798 
1799 		if (special == Scroll_Ceiling ||
1800 			special == Scroll_Floor ||
1801 			special == Scroll_Texture_Model)
1802 		{
1803 			if (l->args[1] & 3)
1804 			{
1805 				// if 1, then displacement
1806 				// if 2, then accelerative (also if 3)
1807 				control = int(l->sidedef[0]->sector - sectors);
1808 				if (l->args[1] & 2)
1809 					accel = 1;
1810 			}
1811 			if (special == Scroll_Texture_Model ||
1812 				l->args[1] & 4)
1813 			{
1814 				// The line housing the special controls the
1815 				// direction and speed of scrolling.
1816 				dx = l->dx >> SCROLL_SHIFT;
1817 				dy = l->dy >> SCROLL_SHIFT;
1818 			}
1819 			else
1820 			{
1821 				// The speed and direction are parameters to the special.
1822 				dx = (l->args[3] - 128) * (FRACUNIT / 32);
1823 				dy = (l->args[4] - 128) * (FRACUNIT / 32);
1824 			}
1825 		}
1826 
1827 		switch (special)
1828 		{
1829 			register int s;
1830 
1831 		case Scroll_Ceiling:
1832 		{
1833 			FSectorTagIterator itr(l->args[0]);
1834 			while ((s = itr.Next()) >= 0)
1835 			{
1836 				new DScroller(DScroller::sc_ceiling, -dx, dy, control, s, accel);
1837 			}
1838 			for (unsigned j = 0; j < copyscrollers.Size(); j++)
1839 			{
1840 				line_t *line = &lines[copyscrollers[j]];
1841 
1842 				if (line->args[0] == l->args[0] && (line->args[1] & 1))
1843 				{
1844 					new DScroller(DScroller::sc_ceiling, -dx, dy, control, int(line->frontsector - sectors), accel);
1845 				}
1846 			}
1847 			break;
1848 		}
1849 
1850 		case Scroll_Floor:
1851 			if (l->args[2] != 1)
1852 			{ // scroll the floor texture
1853 				FSectorTagIterator itr(l->args[0]);
1854 				while ((s = itr.Next()) >= 0)
1855 				{
1856 					new DScroller (DScroller::sc_floor, -dx, dy, control, s, accel);
1857 				}
1858 				for(unsigned j = 0;j < copyscrollers.Size(); j++)
1859 				{
1860 					line_t *line = &lines[copyscrollers[j]];
1861 
1862 					if (line->args[0] == l->args[0] && (line->args[1] & 2))
1863 					{
1864 						new DScroller (DScroller::sc_floor, -dx, dy, control, int(line->frontsector-sectors), accel);
1865 					}
1866 				}
1867 			}
1868 
1869 			if (l->args[2] > 0)
1870 			{ // carry objects on the floor
1871 				FSectorTagIterator itr(l->args[0]);
1872 				while ((s = itr.Next()) >= 0)
1873 				{
1874 					new DScroller (DScroller::sc_carry, dx, dy, control, s, accel);
1875 				}
1876 				for(unsigned j = 0;j < copyscrollers.Size(); j++)
1877 				{
1878 					line_t *line = &lines[copyscrollers[j]];
1879 
1880 					if (line->args[0] == l->args[0] && (line->args[1] & 4))
1881 					{
1882 						new DScroller (DScroller::sc_carry, dx, dy, control, int(line->frontsector-sectors), accel);
1883 					}
1884 				}
1885 			}
1886 			break;
1887 
1888 		// killough 3/1/98: scroll wall according to linedef
1889 		// (same direction and speed as scrolling floors)
1890 		case Scroll_Texture_Model:
1891 		{
1892 			FLineIdIterator itr(l->args[0]);
1893 			while ((s = itr.Next()) >= 0)
1894 			{
1895 				if (s != i)
1896 					new DScroller(dx, dy, lines + s, control, accel);
1897 			}
1898 			break;
1899 		}
1900 
1901 		case Scroll_Texture_Offsets:
1902 			// killough 3/2/98: scroll according to sidedef offsets
1903 			s = int(lines[i].sidedef[0] - sides);
1904 			new DScroller (DScroller::sc_side, -sides[s].GetTextureXOffset(side_t::mid),
1905 				sides[s].GetTextureYOffset(side_t::mid), -1, s, accel, SCROLLTYPE(l->args[0]));
1906 			break;
1907 
1908 		case Scroll_Texture_Left:
1909 			l->special = special;	// Restore the special, for compat_useblocking's benefit.
1910 			s = int(lines[i].sidedef[0] - sides);
1911 			new DScroller (DScroller::sc_side, l->args[0] * (FRACUNIT/64), 0,
1912 						   -1, s, accel, SCROLLTYPE(l->args[1]));
1913 			break;
1914 
1915 		case Scroll_Texture_Right:
1916 			l->special = special;
1917 			s = int(lines[i].sidedef[0] - sides);
1918 			new DScroller (DScroller::sc_side, l->args[0] * (-FRACUNIT/64), 0,
1919 						   -1, s, accel, SCROLLTYPE(l->args[1]));
1920 			break;
1921 
1922 		case Scroll_Texture_Up:
1923 			l->special = special;
1924 			s = int(lines[i].sidedef[0] - sides);
1925 			new DScroller (DScroller::sc_side, 0, l->args[0] * (FRACUNIT/64),
1926 						   -1, s, accel, SCROLLTYPE(l->args[1]));
1927 			break;
1928 
1929 		case Scroll_Texture_Down:
1930 			l->special = special;
1931 			s = int(lines[i].sidedef[0] - sides);
1932 			new DScroller (DScroller::sc_side, 0, l->args[0] * (-FRACUNIT/64),
1933 						   -1, s, accel, SCROLLTYPE(l->args[1]));
1934 			break;
1935 
1936 		case Scroll_Texture_Both:
1937 			s = int(lines[i].sidedef[0] - sides);
1938 			if (l->args[0] == 0) {
1939 				dx = (l->args[1] - l->args[2]) * (FRACUNIT/64);
1940 				dy = (l->args[4] - l->args[3]) * (FRACUNIT/64);
1941 				new DScroller (DScroller::sc_side, dx, dy, -1, s, accel);
1942 			}
1943 			break;
1944 
1945 		default:
1946 			// [RH] It wasn't a scroller after all, so restore the special.
1947 			l->special = special;
1948 			break;
1949 		}
1950 	}
1951 }
1952 
1953 // killough 3/7/98 -- end generalized scroll effects
1954 
1955 ////////////////////////////////////////////////////////////////////////////
1956 //
1957 // FRICTION EFFECTS
1958 //
1959 // phares 3/12/98: Start of friction effects
1960 
1961 // As the player moves, friction is applied by decreasing the x and y
1962 // velocity values on each tic. By varying the percentage of decrease,
1963 // we can simulate muddy or icy conditions. In mud, the player slows
1964 // down faster. In ice, the player slows down more slowly.
1965 //
1966 // The amount of friction change is controlled by the length of a linedef
1967 // with type 223. A length < 100 gives you mud. A length > 100 gives you ice.
1968 //
1969 // Also, each sector where these effects are to take place is given a
1970 // new special type _______. Changing the type value at runtime allows
1971 // these effects to be turned on or off.
1972 //
1973 // Sector boundaries present problems. The player should experience these
1974 // friction changes only when his feet are touching the sector floor. At
1975 // sector boundaries where floor height changes, the player can find
1976 // himself still 'in' one sector, but with his feet at the floor level
1977 // of the next sector (steps up or down). To handle this, Thinkers are used
1978 // in icy/muddy sectors. These thinkers examine each object that is touching
1979 // their sectors, looking for players whose feet are at the same level as
1980 // their floors. Players satisfying this condition are given new friction
1981 // values that are applied by the player movement code later.
1982 
1983 //
1984 // killough 8/28/98:
1985 //
1986 // Completely redid code, which did not need thinkers, and which put a heavy
1987 // drag on CPU. Friction is now a property of sectors, NOT objects inside
1988 // them. All objects, not just players, are affected by it, if they touch
1989 // the sector's floor. Code simpler and faster, only calling on friction
1990 // calculations when an object needs friction considered, instead of doing
1991 // friction calculations on every sector during every tic.
1992 //
1993 // Although this -might- ruin Boom demo sync involving friction, it's the only
1994 // way, short of code explosion, to fix the original design bug. Fixing the
1995 // design bug in Boom's original friction code, while maintaining demo sync
1996 // under every conceivable circumstance, would double or triple code size, and
1997 // would require maintenance of buggy legacy code which is only useful for old
1998 // demos. Doom demos, which are more important IMO, are not affected by this
1999 // change.
2000 //
2001 // [RH] On the other hand, since I've given up on trying to maintain demo
2002 //		sync between versions, these considerations aren't a big deal to me.
2003 //
2004 /////////////////////////////
2005 //
2006 // Initialize the sectors where friction is increased or decreased
2007 
P_SpawnFriction(void)2008 static void P_SpawnFriction(void)
2009 {
2010 	int i;
2011 	line_t *l = lines;
2012 
2013 	for (i = 0 ; i < numlines ; i++,l++)
2014 	{
2015 		if (l->special == Sector_SetFriction)
2016 		{
2017 			int length;
2018 
2019 			if (l->args[1])
2020 			{	// [RH] Allow setting friction amount from parameter
2021 				length = l->args[1] <= 200 ? l->args[1] : 200;
2022 			}
2023 			else
2024 			{
2025 				length = P_AproxDistance(l->dx,l->dy)>>FRACBITS;
2026 			}
2027 
2028 			P_SetSectorFriction (l->args[0], length, false);
2029 			l->special = 0;
2030 		}
2031 	}
2032 }
2033 
P_SetSectorFriction(int tag,int amount,bool alterFlag)2034 void P_SetSectorFriction (int tag, int amount, bool alterFlag)
2035 {
2036 	int s;
2037 	fixed_t friction, movefactor;
2038 
2039 	// An amount of 100 should result in a friction of
2040 	// ORIG_FRICTION (0xE800)
2041 	friction = (0x1EB8*amount)/0x80 + 0xD001;
2042 
2043 	// killough 8/28/98: prevent odd situations
2044 	friction = clamp(friction, 0, FRACUNIT);
2045 
2046 	// The following check might seem odd. At the time of movement,
2047 	// the move distance is multiplied by 'friction/0x10000', so a
2048 	// higher friction value actually means 'less friction'.
2049 	movefactor = FrictionToMoveFactor(friction);
2050 
2051 	FSectorTagIterator itr(tag);
2052 	while ((s = itr.Next()) >= 0)
2053 	{
2054 		// killough 8/28/98:
2055 		//
2056 		// Instead of spawning thinkers, which are slow and expensive,
2057 		// modify the sector's own friction values. Friction should be
2058 		// a property of sectors, not objects which reside inside them.
2059 		// Original code scanned every object in every friction sector
2060 		// on every tic, adjusting its friction, putting unnecessary
2061 		// drag on CPU. New code adjusts friction of sector only once
2062 		// at level startup, and then uses this friction value.
2063 
2064 		sectors[s].friction = friction;
2065 		sectors[s].movefactor = movefactor;
2066 		if (alterFlag)
2067 		{
2068 			// When used inside a script, the sectors' friction flags
2069 			// can be enabled and disabled at will.
2070 			if (friction == ORIG_FRICTION)
2071 			{
2072 				sectors[s].Flags &= ~SECF_FRICTION;
2073 			}
2074 			else
2075 			{
2076 				sectors[s].Flags |= SECF_FRICTION;
2077 			}
2078 		}
2079 	}
2080 }
2081 
2082 //
2083 // phares 3/12/98: End of friction effects
2084 //
2085 ////////////////////////////////////////////////////////////////////////////
2086 
2087 ////////////////////////////////////////////////////////////////////////////
2088 //
2089 // PUSH/PULL EFFECT
2090 //
2091 // phares 3/20/98: Start of push/pull effects
2092 //
2093 // This is where push/pull effects are applied to objects in the sectors.
2094 //
2095 // There are four kinds of push effects
2096 //
2097 // 1) Pushing Away
2098 //
2099 //    Pushes you away from a point source defined by the location of an
2100 //    MT_PUSH Thing. The force decreases linearly with distance from the
2101 //    source. This force crosses sector boundaries and is felt w/in a circle
2102 //    whose center is at the MT_PUSH. The force is felt only if the point
2103 //    MT_PUSH can see the target object.
2104 //
2105 // 2) Pulling toward
2106 //
2107 //    Same as Pushing Away except you're pulled toward an MT_PULL point
2108 //    source. This force crosses sector boundaries and is felt w/in a circle
2109 //    whose center is at the MT_PULL. The force is felt only if the point
2110 //    MT_PULL can see the target object.
2111 //
2112 // 3) Wind
2113 //
2114 //    Pushes you in a constant direction. Full force above ground, half
2115 //    force on the ground, nothing if you're below it (water).
2116 //
2117 // 4) Current
2118 //
2119 //    Pushes you in a constant direction. No force above ground, full
2120 //    force if on the ground or below it (water).
2121 //
2122 // The magnitude of the force is controlled by the length of a controlling
2123 // linedef. The force vector for types 3 & 4 is determined by the angle
2124 // of the linedef, and is constant.
2125 //
2126 // For each sector where these effects occur, the sector special type has
2127 // to have the PUSH_MASK bit set. If this bit is turned off by a switch
2128 // at run-time, the effect will not occur. The controlling sector for
2129 // types 1 & 2 is the sector containing the MT_PUSH/MT_PULL Thing.
2130 
2131 
2132 #define PUSH_FACTOR 7
2133 
2134 /////////////////////////////
2135 //
2136 // Add a push thinker to the thinker list
2137 
DPusher(DPusher::EPusher type,line_t * l,int magnitude,int angle,AActor * source,int affectee)2138 DPusher::DPusher (DPusher::EPusher type, line_t *l, int magnitude, int angle,
2139 				  AActor *source, int affectee)
2140 {
2141 	m_Source = source;
2142 	m_Type = type;
2143 	if (l)
2144 	{
2145 		m_Xmag = l->dx>>FRACBITS;
2146 		m_Ymag = l->dy>>FRACBITS;
2147 		m_Magnitude = P_AproxDistance (m_Xmag, m_Ymag);
2148 	}
2149 	else
2150 	{ // [RH] Allow setting magnitude and angle with parameters
2151 		ChangeValues (magnitude, angle);
2152 	}
2153 	if (source) // point source exist?
2154 	{
2155 		m_Radius = (m_Magnitude) << (FRACBITS+1); // where force goes to zero
2156 		m_X = m_Source->X();
2157 		m_Y = m_Source->Y();
2158 	}
2159 	m_Affectee = affectee;
2160 }
2161 
CheckForSectorMatch(EPusher type,int tag)2162 int DPusher::CheckForSectorMatch (EPusher type, int tag)
2163 {
2164 	if (m_Type == type && tagManager.SectorHasTag(m_Affectee, tag))
2165 		return m_Affectee;
2166 	else
2167 		return -1;
2168 }
2169 
2170 
2171 /////////////////////////////
2172 //
2173 // T_Pusher looks for all objects that are inside the radius of
2174 // the effect.
2175 //
Tick()2176 void DPusher::Tick ()
2177 {
2178 	sector_t *sec;
2179 	AActor *thing;
2180 	msecnode_t *node;
2181 	int xspeed,yspeed;
2182 	int ht;
2183 
2184 	if (!var_pushers)
2185 		return;
2186 
2187 	sec = sectors + m_Affectee;
2188 
2189 	// Be sure the special sector type is still turned on. If so, proceed.
2190 	// Else, bail out; the sector type has been changed on us.
2191 
2192 	if (!(sec->Flags & SECF_PUSH))
2193 		return;
2194 
2195 	// For constant pushers (wind/current) there are 3 situations:
2196 	//
2197 	// 1) Affected Thing is above the floor.
2198 	//
2199 	//    Apply the full force if wind, no force if current.
2200 	//
2201 	// 2) Affected Thing is on the ground.
2202 	//
2203 	//    Apply half force if wind, full force if current.
2204 	//
2205 	// 3) Affected Thing is below the ground (underwater effect).
2206 	//
2207 	//    Apply no force if wind, full force if current.
2208 	//
2209 	// Apply the effect to clipped players only for now.
2210 	//
2211 	// In Phase II, you can apply these effects to Things other than players.
2212 	// [RH] No Phase II, but it works with anything having MF2_WINDTHRUST now.
2213 
2214 	if (m_Type == p_push)
2215 	{
2216 		// Seek out all pushable things within the force radius of this
2217 		// point pusher. Crosses sectors, so use blockmap.
2218 
2219 		FBlockThingsIterator it(FBoundingBox(m_X, m_Y, m_Radius));
2220 		AActor *thing;
2221 
2222 		while ((thing = it.Next()))
2223 		{
2224 			// Normal ZDoom is based only on the WINDTHRUST flag, with the noclip cheat as an exemption.
2225 			bool pusharound = ((thing->flags2 & MF2_WINDTHRUST) && !(thing->flags & MF_NOCLIP));
2226 
2227 			// MBF allows any sentient or shootable thing to be affected, but players with a fly cheat aren't.
2228 			if (compatflags & COMPATF_MBFMONSTERMOVE)
2229 			{
2230 				pusharound = ((pusharound || (thing->IsSentient()) || (thing->flags & MF_SHOOTABLE)) // Add categories here
2231 					&& (!(thing->player && (thing->flags & (MF_NOGRAVITY))))); // Exclude flying players here
2232 			}
2233 
2234 			if ((pusharound) )
2235 			{
2236 				int sx = m_X;
2237 				int sy = m_Y;
2238 				int dist = thing->AproxDistance (sx, sy);
2239 				int speed = (m_Magnitude - ((dist>>FRACBITS)>>1))<<(FRACBITS-PUSH_FACTOR-1);
2240 
2241 				// If speed <= 0, you're outside the effective radius. You also have
2242 				// to be able to see the push/pull source point.
2243 
2244 				if ((speed > 0) && (P_CheckSight (thing, m_Source, SF_IGNOREVISIBILITY)))
2245 				{
2246 					angle_t pushangle = thing->AngleTo(sx, sy);
2247 					if (m_Source->GetClass()->TypeName == NAME_PointPusher)
2248 						pushangle += ANG180;    // away
2249 					pushangle >>= ANGLETOFINESHIFT;
2250 					thing->velx += FixedMul (speed, finecosine[pushangle]);
2251 					thing->vely += FixedMul (speed, finesine[pushangle]);
2252 				}
2253 			}
2254 		}
2255 		return;
2256 	}
2257 
2258 	// constant pushers p_wind and p_current
2259 
2260 	node = sec->touching_thinglist; // things touching this sector
2261 	for ( ; node ; node = node->m_snext)
2262 	{
2263 		thing = node->m_thing;
2264 		if (!(thing->flags2 & MF2_WINDTHRUST) || (thing->flags & MF_NOCLIP))
2265 			continue;
2266 
2267 		sector_t *hsec = sec->GetHeightSec();
2268 		if (m_Type == p_wind)
2269 		{
2270 			if (hsec == NULL)
2271 			{ // NOT special water sector
2272 				if (thing->Z() > thing->floorz) // above ground
2273 				{
2274 					xspeed = m_Xmag; // full force
2275 					yspeed = m_Ymag;
2276 				}
2277 				else // on ground
2278 				{
2279 					xspeed = (m_Xmag)>>1; // half force
2280 					yspeed = (m_Ymag)>>1;
2281 				}
2282 			}
2283 			else // special water sector
2284 			{
2285 				ht = hsec->floorplane.ZatPoint(thing);
2286 				if (thing->Z() > ht) // above ground
2287 				{
2288 					xspeed = m_Xmag; // full force
2289 					yspeed = m_Ymag;
2290 				}
2291 				else if (thing->player->viewz < ht) // underwater
2292 				{
2293 					xspeed = yspeed = 0; // no force
2294 				}
2295 				else // wading in water
2296 				{
2297 					xspeed = (m_Xmag)>>1; // half force
2298 					yspeed = (m_Ymag)>>1;
2299 				}
2300 			}
2301 		}
2302 		else // p_current
2303 		{
2304 			const secplane_t *floor;
2305 
2306 			if (hsec == NULL)
2307 			{ // NOT special water sector
2308 				floor = &sec->floorplane;
2309 			}
2310 			else
2311 			{ // special water sector
2312 				floor = &hsec->floorplane;
2313 			}
2314 			if (thing->Z() > floor->ZatPoint(thing))
2315 			{ // above ground
2316 				xspeed = yspeed = 0; // no force
2317 			}
2318 			else
2319 			{ // on ground/underwater
2320 				xspeed = m_Xmag; // full force
2321 				yspeed = m_Ymag;
2322 			}
2323 		}
2324 		thing->velx += xspeed<<(FRACBITS-PUSH_FACTOR);
2325 		thing->vely += yspeed<<(FRACBITS-PUSH_FACTOR);
2326 	}
2327 }
2328 
2329 /////////////////////////////
2330 //
2331 // P_GetPushThing() returns a pointer to an MT_PUSH or MT_PULL thing,
2332 // NULL otherwise.
2333 
P_GetPushThing(int s)2334 AActor *P_GetPushThing (int s)
2335 {
2336 	AActor* thing;
2337 	sector_t* sec;
2338 
2339 	sec = sectors + s;
2340 	thing = sec->thinglist;
2341 
2342 	while (thing &&
2343 		thing->GetClass()->TypeName != NAME_PointPusher &&
2344 		thing->GetClass()->TypeName != NAME_PointPuller)
2345 	{
2346 		thing = thing->snext;
2347 	}
2348 	return thing;
2349 }
2350 
2351 /////////////////////////////
2352 //
2353 // Initialize the sectors where pushers are present
2354 //
2355 
P_SpawnPushers()2356 static void P_SpawnPushers ()
2357 {
2358 	int i;
2359 	line_t *l = lines;
2360 	register int s;
2361 
2362 	for (i = 0; i < numlines; i++, l++)
2363 	{
2364 		switch (l->special)
2365 		{
2366 		case Sector_SetWind: // wind
2367 		{
2368 			FSectorTagIterator itr(l->args[0]);
2369 			while ((s = itr.Next()) >= 0)
2370 				new DPusher(DPusher::p_wind, l->args[3] ? l : NULL, l->args[1], l->args[2], NULL, s);
2371 			l->special = 0;
2372 			break;
2373 		}
2374 
2375 		case Sector_SetCurrent: // current
2376 		{
2377 			FSectorTagIterator itr(l->args[0]);
2378 			while ((s = itr.Next()) >= 0)
2379 				new DPusher(DPusher::p_current, l->args[3] ? l : NULL, l->args[1], l->args[2], NULL, s);
2380 			l->special = 0;
2381 			break;
2382 		}
2383 
2384 		case PointPush_SetForce: // push/pull
2385 			if (l->args[0]) {	// [RH] Find thing by sector
2386 				FSectorTagIterator itr(l->args[0]);
2387 				while ((s = itr.Next()) >= 0)
2388 				{
2389 					AActor *thing = P_GetPushThing (s);
2390 					if (thing) {	// No MT_P* means no effect
2391 						// [RH] Allow narrowing it down by tid
2392 						if (!l->args[1] || l->args[1] == thing->tid)
2393 							new DPusher (DPusher::p_push, l->args[3] ? l : NULL, l->args[2],
2394 										 0, thing, s);
2395 					}
2396 				}
2397 			} else {	// [RH] Find thing by tid
2398 				AActor *thing;
2399 				FActorIterator iterator (l->args[1]);
2400 
2401 				while ( (thing = iterator.Next ()) )
2402 				{
2403 					if (thing->GetClass()->TypeName == NAME_PointPusher ||
2404 						thing->GetClass()->TypeName == NAME_PointPuller)
2405 					{
2406 						new DPusher (DPusher::p_push, l->args[3] ? l : NULL, l->args[2],
2407 									 0, thing, int(thing->Sector - sectors));
2408 					}
2409 				}
2410 			}
2411 			l->special = 0;
2412 			break;
2413 		}
2414 	}
2415 }
2416 
2417 //
2418 // phares 3/20/98: End of Pusher effects
2419 //
2420 ////////////////////////////////////////////////////////////////////////////
2421 
AdjustFloorClip() const2422 void sector_t::AdjustFloorClip () const
2423 {
2424 	msecnode_t *node;
2425 
2426 	for (node = touching_thinglist; node; node = node->m_snext)
2427 	{
2428 		if (node->m_thing->flags2 & MF2_FLOORCLIP)
2429 		{
2430 			node->m_thing->AdjustFloorClip();
2431 		}
2432 	}
2433 }
2434