// Emacs style mode select   -*- C++ -*-
//-----------------------------------------------------------------------------
//
// $Id: 21fdf7590dd5350195cf2f6d0ad1450f0a203bd2 $
//
// Copyright (C) 1993-1996 by id Software, Inc.
// Copyright (C) 2006-2026 by The Odamex Team.
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// DESCRIPTION:  none
//	Implements special effects:
//	Texture animation, height or lighting changes
//	 according to adjacent sectors, respective
//	 utility functions, etc.
//
//-----------------------------------------------------------------------------

#pragma once

#include <list>
#include "dsectoreffect.h"

typedef struct movingsector_s
{
	movingsector_s() :
		sector(NULL), moving_ceiling(false), moving_floor(false)
	{}

	sector_t	*sector;
	bool		moving_ceiling;
	bool		moving_floor;
} movingsector_t;

enum stepsize_e
{
	StepSize4,
	StepSize8,
	StepSize16,
	StepSize24,
};

enum motionspeed_e
{
	SpeedSlow,
	SpeedNormal,
	SpeedFast,
	SpeedTurbo,
};

enum doorkind_e
{
	OdCDoor,
	ODoor,
	CdODoor,
	CDoor,
};

enum floortarget_e
{
	FtoHnF,
	FtoLnF,
	FtoNnF,
	FtoLnC,
	FtoC,
	FbyST,
	Fby24,
	Fby32,
};

enum floorchange_e
{
	FNoChg,
	FChgZero,
	FChgTxt,
	FChgTyp,
};

enum ceilingtarget_e
{
	CtoHnC,
	CtoLnC,
	CtoNnC,
	CtoHnF,
	CtoF,
	CbyST,
	Cby24,
	Cby32,
};

enum ceilingchange_e
{
	CNoChg,
	CChgZero,
	CChgTxt,
	CChgTyp,
};

extern std::list<movingsector_t> movingsectors;
extern std::list<sector_t*> specialdoors;
extern bool s_SpecialFromServer;

#define IgnoreSpecial !serverside && !s_SpecialFromServer
#define NO_TEXTURE 0;

#define CEILSPEED FRACUNIT

std::list<movingsector_t>::iterator P_FindMovingSector(sector_t *sector);
void P_AddMovingCeiling(sector_t *sector);
void P_AddMovingFloor(sector_t *sector);
void P_RemoveMovingCeiling(sector_t *sector);
void P_RemoveMovingFloor(sector_t *sector);
bool P_MovingCeilingCompleted(sector_t *sector);
bool P_MovingFloorCompleted(sector_t *sector);
bool P_HandleSpecialRepeat(line_t* line);
void P_ApplySectorDamage(player_t& player, int damage, int leak, int mod = 0);
void P_ApplySectorDamageNoRandom(player_t& player, int damage, int mod = 0);
void P_ApplySectorDamageNoWait(player_t& player, int damage, int mod = 0);
void P_ApplySectorDamageEndLevel(player_t& player);
void P_CollectSecretCommon(sector_t& sector, player_t& player);
int P_FindSectorFromTagOrLine(int tag, const line_t* line, int start);
int P_FindLineFromTag(int tag, int start);
bool P_FloorActive(const sector_t* sec);
bool P_LightingActive(const sector_t* sec);
bool P_CeilingActive(const sector_t* sec);
fixed_t P_ArgToSpeed(byte arg);
bool P_ArgToCrushType(byte arg);
void P_ResetSectorSpecial(sector_t* sector);
void P_CopySectorSpecial(sector_t* dest, sector_t* source);
byte P_ArgToChange(byte arg);
int P_ArgToCrush(byte arg);
int P_FindSectorFromLineTag(const line_t* line, int start);
int P_ArgToCrushMode(byte arg, bool slowdown);
fixed_t P_ArgsToFixed(fixed_t arg_i, fixed_t arg_f);
bool P_CheckTag(line_t* line);
void P_TransferSectorFlags(unsigned int*, unsigned int);

//jff 2/23/98 identify the special classes that can share sectors

typedef enum
{
	floor_special,
	ceiling_special,
	lighting_special
} special_e;

enum crushmode_e
{
	crushDoom = 0,
	crushHexen = 1,
	crushSlowdown = 2,
};

enum lifttarget_e
{
	F2LnF,
	F2NnF,
	F2LnC,
	LnF2HnF,
};

enum LineActivationType
{
	LineCross,
	LineUse,
	LineShoot,
	LinePush,
	LineACS,
};

enum zdoom_lock_t
{
	zk_none = 0,
	zk_red_card = 1,
	zk_blue_card = 2,
	zk_yellow_card = 3,
	zk_red_skull = 4,
	zk_blue_skull = 5,
	zk_yellow_skull = 6,
	zk_any = 100,
	zk_all = 101,
	zk_red = 129,
	zk_blue = 130,
	zk_yellow = 131,
	zk_redx = 132, // not sure why these redundant ones exist
	zk_bluex = 133,
	zk_yellowx = 134,
	zk_each_color = 229,
};

struct newspecial_s
{
	short special;
	unsigned int flags;
	int damageamount;
	int damageinterval;
	int damageleakrate;
};

#define FLOORSPEED FRACUNIT

bool P_CanUnlockZDoomDoor(player_t* player, zdoom_lock_t lock, bool remote);

// killough 3/7/98: Add generalized scroll effects

class DScroller : public DThinker
{
	DECLARE_SERIAL (DScroller, DThinker)
public:
	enum EScrollType
	{
		sc_side,
		sc_floor,
		sc_ceiling,
		sc_carry,
		sc_carry_ceiling	// killough 4/11/98: carry objects hanging on ceilings

	};

	DScroller(EScrollType type, fixed_t dx, fixed_t dy, int control, int affectee, int accel);
	DScroller(fixed_t dx, fixed_t dy, const line_t *l, int control, int accel);

	void RunThink() override;

	bool AffectsWall(int wallnum) { return m_Type == sc_side && m_Affectee == wallnum; }
	int GetWallNum() { return m_Type == sc_side ? m_Affectee : -1; }
	void SetRate(fixed_t dx, fixed_t dy) { m_dx = dx; m_dy = dy; }
	bool IsType(EScrollType type) const { return type == m_Type; }
	int GetAffectee() const { return m_Affectee; }
	int GetAccel() const { return m_Accel; }
	int GetControl() const { return m_Control; }

	EScrollType GetType() const { return m_Type; }
	fixed_t GetScrollX() const { return m_dx; }
	fixed_t GetScrollY() const { return m_dy; }

protected:
	EScrollType m_Type       = sc_side; // Type of scroll effect
	fixed_t     m_dx         = 0;
	fixed_t     m_dy         = 0;       // (dx,dy) scroll speeds
	int         m_Affectee   = 0;       // Number of affected sidedef, sector, tag, or whatever
	int         m_Control    = 0;       // Control sector (-1 if none) used to control scrolling
	fixed_t     m_LastHeight = 0;       // Last known height of control sector
	fixed_t     m_vdx        = 0;
	fixed_t     m_vdy        = 0;       // Accumulated velocity if accelerative
	int         m_Accel      = 0;       // Whether it's accelerative

private:
	DScroller() = default;
};

inline FArchive &operator<< (FArchive &arc, DScroller::EScrollType type)
{
	return arc << (byte)type;
}
inline FArchive &operator>> (FArchive &arc, DScroller::EScrollType &out)
{
	byte in; arc >> in; out = (DScroller::EScrollType)in; return arc;
}

inline bool P_WallScrollType(DScroller::EScrollType type)
{
	if (type == DScroller::sc_side)
	{
		return true;
	}

	return false;
}

inline bool P_FloorScrollType(DScroller::EScrollType type)
{
	if (type == DScroller::sc_floor)
	{
		return true;
	}

	return false;
}

inline bool P_CeilingScrollType(DScroller::EScrollType type)
{
	if (type == DScroller::sc_ceiling)
	{
		return true;
	}

	return false;
}

// phares 3/20/98: added new model of Pushers for push/pull effects

class DPusher : public DThinker
{
	DECLARE_SERIAL (DPusher, DThinker)
public:
	enum EPusher
	{
		p_push,
		p_pull,
		p_wind,
		p_current
	};

	DPusher() = default;
	DPusher(EPusher type, line_t *l, int magnitude, int angle, AActor *source, int affectee);
	int CheckForSectorMatch (EPusher type, int tag)
	{
		if (m_Type == type && sectors[m_Affectee].tag == tag)
			return m_Affectee;
		else
			return -1;
	}
	void ChangeValues(int magnitude, int angle)
	{
		angle_t ang = (static_cast<angle_t>(angle)<<24) >> ANGLETOFINESHIFT;
		m_Xmag = (magnitude * finecosine[ang]) >> FRACBITS;
		m_Ymag = (magnitude * finesine[ang]) >> FRACBITS;
		m_Magnitude = magnitude;
	}

	void RunThink() override;

protected:
	EPusher m_Type  = p_push;
	AActor::AActorPtr m_Source{}; // Point source if point pusher
	int m_Xmag      = 0;          // X Strength
	int m_Ymag      = 0;          // Y Strength
	int m_Magnitude = 0;          // Vector strength for point pusher
	int m_Radius    = 0;          // Effective radius for point pusher
	int m_X         = 0;          // X of point source if point pusher
	int m_Y         = 0;          // Y of point source if point pusher
	int m_Affectee  = 0;          // Number of affected sector

	friend bool PIT_PushThing (AActor& thing, DPusher* tmpusher);
};

inline FArchive &operator<< (FArchive &arc, DPusher::EPusher type)
{
	return arc << (byte)type;
}
inline FArchive &operator>> (FArchive &arc, DPusher::EPusher &out)
{
	byte in; arc >> in; out = (DPusher::EPusher)in; return arc;
}

bool P_CheckKeys (player_t *p, card_t lock, bool remote);

// Define values for map objects
#define MO_TELEPORTMAN			14


// [RH] If a deathmatch game, checks to see if noexit is enabled.
//		If so, it kills the player and returns false. Otherwise,
//		it returns true, and the player is allowed to live.
bool	CheckIfExitIsGood (AActor *self);

// at game start
void	P_InitPicAnims (void);

// [Blair] ZDoom sector specials
void	P_SpawnZDoomSectorSpecials (void);

// every tic
void	P_UpdateSpecials (void);

// when needed
void    P_CrossSpecialLine (line_t* line, int side, AActor* thing, bool bossaction);
void    P_ShootSpecialLine (AActor* thing, line_t* line);
bool    P_UseSpecialLine (AActor* thing, line_t* line, int side, bool bossaction);
bool    P_PushSpecialLine (AActor* thing, line_t* line, int	side);

void    P_PlayerInZDoomSector (player_t *player);
void P_ApplySectorFriction(int tag, int value, bool use_thinker);

//
// getSector()
// Will return a sector_t*
//	given the number of the current sector,
//	the line number and the side (0/1) that you want.
//
inline sector_t *getSector (int currentSector, int line, int side)
{
	return sides[ (sectors[currentSector].lines[line])->sidenum[side] ].sector;
}


//
// getNextSector()
// Return sector_t * of sector next to current.
// NULL if not two-sided line
//
inline sector_t *getNextSector (const line_t *line, const sector_t *sec)
{
	if (!(line->flags & ML_TWOSIDED))
		return NULL;

	return line->frontsector == sec ? (line->backsector != sec ? line->backsector : NULL)
	                                : line->frontsector;
}


fixed_t	P_FindLowestFloorSurrounding (sector_t *sec);
fixed_t	P_FindHighestFloorSurrounding (sector_t *sec);

fixed_t	P_FindNextHighestFloor (sector_t *sec);
fixed_t P_FindNextLowestFloor (sector_t* sec);

fixed_t	P_FindLowestCeilingSurrounding (sector_t *sec);		// jff 2/04/98
fixed_t	P_FindHighestCeilingSurrounding (sector_t *sec);	// jff 2/04/98

fixed_t P_FindNextLowestCeiling (sector_t *sec);		// jff 2/04/98
fixed_t P_FindNextHighestCeiling (sector_t *sec);	// jff 2/04/98

fixed_t P_FindShortestTextureAround (sector_t *sec);	// jff 2/04/98
fixed_t P_FindShortestUpperAround (sector_t *sec);		// jff 2/04/98

sector_t* P_FindModelFloorSector (fixed_t floordestheight, sector_t *sec);	//jff 02/04/98
sector_t* P_FindModelCeilingSector (fixed_t ceildestheight, sector_t *sec);	//jff 02/04/98

int		P_FindSectorFromTag (int tag, int start);
int		P_FindLineFromID (int id, int start);

int		P_FindMinSurroundingLight (sector_t *sector, int max);

sector_t *P_NextSpecialSector (sector_t *sec, int type, sector_t *back2);	// [RH]



//
// P_LIGHTS
//

class DLighting : public DSectorEffect
{
	DECLARE_SERIAL (DLighting, DSectorEffect);
public:
	explicit DLighting(sector_t *sector);
protected:
	DLighting() = default;
};

class DFireFlicker : public DLighting
{
	DECLARE_SERIAL (DFireFlicker, DLighting)
public:
	explicit DFireFlicker(sector_t *sector);
	DFireFlicker(sector_t *sector, int upper, int lower);
	void RunThink() override;
	int GetMaxLight() const { return m_MaxLight; }
	int GetMinLight() const { return m_MinLight; }
protected:
	int m_Count    = 0;
	int m_MaxLight = 0;
	int m_MinLight = 0;
private:
	DFireFlicker() = default;
};

class DFlicker : public DLighting
{
	DECLARE_SERIAL (DFlicker, DLighting)
public:
	DFlicker(sector_t *sector, int upper, int lower);
	void RunThink() override;
	int GetMaxLight() const { return m_MaxLight; }
	int GetMinLight() const { return m_MinLight; }
protected:
	int m_Count    = 0;
	int m_MaxLight = 0;
	int m_MinLight = 0;
private:
	DFlicker() = default;
};

class DLightFlash : public DLighting
{
	DECLARE_SERIAL (DLightFlash, DLighting)
public:
	explicit DLightFlash(sector_t *sector);
	DLightFlash(sector_t *sector, int min, int max);
	void RunThink() override;
	int GetMaxLight() const { return m_MaxLight; }
	int GetMinLight() const { return m_MinLight; }
protected:
	int m_Count    = 0;
	int m_MaxLight = 0;
	int m_MinLight = 0;
	int m_MaxTime  = 0;
	int m_MinTime  = 0;
private:
	DLightFlash() = default;
};

class DStrobe : public DLighting
{
	DECLARE_SERIAL (DStrobe, DLighting)
public:
	DStrobe(sector_t *sector, int utics, int ltics, bool inSync);
	DStrobe(sector_t *sector, int upper, int lower, int utics, int ltics);
	void RunThink() override;
	int GetMaxLight() const { return m_MaxLight; }
	int GetMinLight() const { return m_MinLight; }
	int GetDarkTime() const { return m_DarkTime; }
	int GetBrightTime() const { return m_BrightTime; }
	int GetCount() const { return m_Count; }
	void SetCount(int count) { m_Count = count; }
protected:
	int m_Count      = 0;
	int m_MinLight   = 0;
	int m_MaxLight   = 0;
	int m_DarkTime   = 0;
	int m_BrightTime = 0;
private:
	DStrobe() = default;
};

class DGlow : public DLighting
{
	DECLARE_SERIAL (DGlow, DLighting)
public:
	explicit DGlow(sector_t *sector);
	void RunThink() override;
protected:
	int m_MinLight  = 0;
	int m_MaxLight  = 0;
	int m_Direction = 0;
private:
	DGlow() = default;
};

// [RH] Glow from Light_Glow and Light_Fade specials
class DGlow2 : public DLighting
{
	DECLARE_SERIAL (DGlow2, DLighting)
public:
	DGlow2(sector_t *sector, int start, int end, int tics, bool oneshot);
	void RunThink() override;
	int GetStart() const { return m_Start; }
	int GetEnd() const { return m_End; }
	int GetMaxTics() const { return m_MaxTics; }
	bool GetOneShot() const { return m_OneShot; }
protected:
	int  m_Start   = 0;
	int  m_End     = 0;
	int  m_MaxTics = 0;
	int  m_Tics    = 0;
	bool m_OneShot = 0;
private:
	DGlow2() = default;
};

// [RH] Phased light thinker
class DPhased : public DLighting
{
	DECLARE_SERIAL (DPhased, DLighting)
public:
	explicit DPhased(sector_t *sector);
	DPhased(sector_t *sector, int baselevel, int phase);
	void RunThink() override;
	byte GetBaseLevel() const { return m_BaseLevel; }
	byte GetPhase() const { return m_Phase; }
protected:
	byte m_BaseLevel = 0;
	byte m_Phase     = 0;
private:
	DPhased() = default;
	DPhased(sector_t *sector, int baselevel);
	int PhaseHelper (sector_t *sector, int index, int light, sector_t *prev);
};

#define GLOWSPEED				8
#define STROBEBRIGHT			5
#define FASTDARK				15
#define SLOWDARK				TICRATE

void	EV_StartLightFlickering (int tag, int upper, int lower);
void	EV_StartLightStrobing (int tag, int upper, int lower, int utics, int ltics);
void	EV_StartLightStrobing (int tag, int utics, int ltics);
void	EV_TurnTagLightsOff (int tag);
void	EV_LightTurnOn (int tag, int bright);
void	EV_LightChange (int tag, int value);
int     EV_LightTurnOnPartway(int tag, int level);

void	P_SpawnGlowingLight (sector_t *sector);

void	EV_StartLightGlowing (int tag, int upper, int lower, int tics);
void	EV_StartLightFading (int tag, int value, int tics);


//
// P_SWITCH
//
typedef struct
{
	OLumpName	name1;
	OLumpName	name2;
	short		episode;

} switchlist_t;

#define STAIR_USE_SPECIALS 1
#define STAIR_SYNC 2

// 1 second, in ticks.
#define BUTTONTIME		TICRATE

void	P_ChangeSwitchTexture (line_t *line, int useAgain, bool playsound);

void	P_InitSwitchList ();

void	P_ProcessSwitchDef ();

short P_GetButtonTexture(const line_t* line);
bool	P_GetButtonInfo (const line_t *line, unsigned &state, unsigned &time);
bool	P_SetButtonInfo (line_t *line, unsigned state, unsigned time);

void	P_UpdateButtons (client_t *cl);

//
// P_PLATS
//
class DPlat : public DMovingFloor
{
	DECLARE_SERIAL (DPlat, DMovingFloor);
public:
	enum EPlatState
	{
		init = 0,
		up,
		down,
		waiting,
		in_stasis,
		midup,
		middown,
		finished,
		destroy,
		state_size
	};

	enum EPlatType
	{
		perpetualRaise,
		downWaitUpStay,
		raiseAndChange,
		raiseToNearestAndChange,
		blazeDWUS,
		genLift, // jff added to support generalized Plat types
		genPerpetual,
		toggleUpDn, // jff 3/14/98 added to support instant toggle type
		platPerpetualRaise,
		platDownWaitUpStay,
		platDownWaitUpStayStone,
		platUpNearestWaitDownStay,
		platUpWaitDownStay,
		platDownByValue,
		platUpByValue,
		platUpByValueStay,
		platRaiseAndStay,
		platToggle,
		platDownToNearestFloor,
		platDownToLowestCeiling,
		platRaiseAndStayLockout
	};

	void RunThink () override;

	void SetState(byte state, int count) { m_Status = (EPlatState)state; m_Count = count; }
	void GetState(byte &state, int &count) { state = (byte)m_Status; count = m_Count; }

	explicit DPlat(sector_t *sector);
	DPlat(sector_t *sector, DPlat::EPlatType type, fixed_t height, int speed, int delay, fixed_t lip);
	DPlat(sector_t* sector, int target, int delay, int speed, int trigger); // [Blair] Boom Generic Plat type
	[[nodiscard]] DPlat* Clone(sector_t* sec) const override;
	friend void P_SetPlatDestroy(DPlat *plat);

	void PlayPlatSound ();

	fixed_t    m_Speed     = 0;
	fixed_t    m_Low       = 0;
	fixed_t    m_High      = 0;
	int        m_Wait      = 0;
	int        m_Count     = 0;
	EPlatState m_Status    = init;
	EPlatState m_OldStatus = init;
	bool       m_Crush     = false;
	int        m_Tag       = 0;
	EPlatType  m_Type      = perpetualRaise;
	fixed_t    m_Height    = 0;
	fixed_t    m_Lip       = 0;

protected:

	void Reactivate ();
	void Stop ();

private:
	DPlat() = default;

	friend bool	EV_DoPlat (int tag, line_t *line, EPlatType type,
						   fixed_t height, int speed, int delay, fixed_t lip, int change);
	friend void EV_StopPlat (int tag);
	friend void P_ActivateInStasis (int tag);
	friend bool EV_DoGenLift(line_t* line);
};

inline FArchive &operator<< (FArchive &arc, DPlat::EPlatType type)
{
	return arc << (byte)type;
}
inline FArchive &operator>> (FArchive &arc, DPlat::EPlatType &out)
{
	byte in; arc >> in; out = (DPlat::EPlatType)in; return arc;
}
inline FArchive &operator<< (FArchive &arc, DPlat::EPlatState state)
{
	return arc << (byte)state;
}
inline FArchive &operator>> (FArchive &arc, DPlat::EPlatState &out)
{
	byte in; arc >> in; out = (DPlat::EPlatState)in; return arc;
}

//
// [RH]
// P_PILLAR
//

class DPillar : public DMover
{
	DECLARE_SERIAL (DPillar, DMover)
public:
	enum EPillarState
	{
		init = 0,
		finished,
		destroy,
		state_size
	};

	enum EPillar
	{
		pillarBuild,
		pillarOpen

	};

	DPillar() = default;
	DPillar(sector_t* sector, EPillar type, fixed_t speed, fixed_t height,
	        fixed_t height2, int crush, bool hexencrush);
	[[nodiscard]] DPillar* Clone(sector_t* sec) const override;
	friend void P_SetPillarDestroy(DPillar *pillar);
	friend bool EV_DoZDoomPillar(DPillar::EPillar type, line_t* line, int tag,
	                             fixed_t speed, fixed_t floordist, fixed_t ceilingdist,
	                             int crush, bool hexencrush);

	void RunThink () override;
	void PlayPillarSound();

	EPillar m_Type          = pillarBuild;
	fixed_t m_FloorSpeed    = 0;
	fixed_t m_CeilingSpeed  = 0;
	fixed_t m_FloorTarget   = 0;
	fixed_t m_CeilingTarget = 0;
	int     m_Crush         = 0;
	bool    m_HexenCrush    = false;

	EPillarState m_Status   = init;
};

inline FArchive &operator<< (FArchive &arc, DPillar::EPillar type)
{
	return arc << (byte)type;
}
inline FArchive &operator>> (FArchive &arc, DPillar::EPillar &out)
{
	byte in; arc >> in; out = (DPillar::EPillar)in; return arc;
}
inline FArchive &operator<< (FArchive &arc, DPillar::EPillarState state)
{
	return arc << (byte)state;
}
inline FArchive &operator>> (FArchive &arc, DPillar::EPillarState &out)
{
	byte in; arc >> in; out = (DPillar::EPillarState)in; return arc;
}

bool EV_DoPillar (DPillar::EPillar type, int tag, fixed_t speed, fixed_t height,
				  fixed_t height2, bool crush);
void P_SpawnDoorCloseIn30 (sector_t *sec);
void P_SpawnDoorRaiseIn5Mins (sector_t *sec);

//
// P_DOORS
//
class DDoor : public DMovingCeiling
{
	DECLARE_SERIAL (DDoor, DMovingCeiling)
public:
	enum EVlDoor
	{
		doorClose,
		doorOpen,
		doorRaise,
		doorRaiseIn5Mins,
		doorCloseWaitOpen,
		close30ThenOpen,
		blazeRaise,
		blazeOpen,
		blazeClose,
		waitRaiseDoor,
		waitCloseDoor,

		// jff 02/05/98 add generalize door types
		genRaise,
		genBlazeRaise,
		genOpen,
		genBlazeOpen,
		genClose,
		genBlazeClose,
		genCdO,
		genBlazeCdO,
	};

	enum EDoorState
	{
		init = 0,
		opening,
		closing,
		waiting,
		reopening,
		finished,
		destroy,
		state_size
	};

	explicit DDoor(sector_t *sector);
	// Boom Generic Door
	DDoor(sector_t* sec, line_t* ln, int delay, int time, int trigger,
	      int speed);
	// Boom Generic Locked Door
	DDoor(sector_t* sec, line_t* ln, int kind, int trigger, int speed);
	// Boom Compatible DDoor
    DDoor (sector_t *sec, line_t *ln, EVlDoor type, fixed_t speed, int delay);
	// ZDoom Compatible DDoor
	DDoor(sector_t* sec, line_t* ln, EVlDoor type, fixed_t speed, int topwait,
	      byte lighttag, int topcountdown);
	[[nodiscard]] DDoor* Clone(sector_t* sec) const override;

	friend void P_SetDoorDestroy(DDoor *door);

	void RunThink () override;
	void PlayDoorSound();

	EVlDoor    m_Type      = doorClose;
	fixed_t    m_TopHeight = 0;
	fixed_t    m_Speed     = 0;

	// tics to wait at the top
	int        m_TopWait = 0;
	// (keep in case a door going down is reset)
	// when it reaches 0, start going down
	int        m_TopCountdown = 0;

	EDoorState m_Status = init;

    line_t*    m_Line = nullptr;

	int        m_LightTag = 0; // ZDoom compat

protected:
	friend bool	EV_DoDoor (DDoor::EVlDoor type, line_t *line, const AActor *thing,
                                   int tag, int speed, int delay, card_t lock);
    friend bool EV_DoZDoomDoor(DDoor::EVlDoor type, line_t* line, AActor* mo, byte tag,
	                         byte speed_byte, int topwait, zdoom_lock_t lock,
	                         byte lightTag, bool boomgen, int topcountdown);
	friend void P_SpawnDoorCloseIn30(sector_t *sec);
	friend void P_SpawnDoorRaiseIn5Mins(sector_t *sec);

private:
	DDoor() = default;
};

inline FArchive &operator<< (FArchive &arc, DDoor::EVlDoor type)
{
	return arc << (byte)type;
}
inline FArchive &operator>> (FArchive &arc, DDoor::EVlDoor &out)
{
	byte in; arc >> in; out = (DDoor::EVlDoor)in; return arc;
}
inline FArchive &operator<< (FArchive &arc, DDoor::EDoorState state)
{
	return arc << (byte)state;
}
inline FArchive &operator>> (FArchive &arc, DDoor::EDoorState &out)
{
	byte in; arc >> in; out = (DDoor::EDoorState)in; return arc;
}

//
// P_CEILNG
//

// [RH] Changed these
class DCeiling : public DMovingCeiling
{
	DECLARE_SERIAL (DCeiling, DMovingCeiling)
public:
	enum ECeilingState
	{
		init = 0,
		up,
		down,
		waiting,
		finished,
		destroy,
		state_size
	};

	enum ECeiling
	{
		lowerToFloor,
		raiseToHighest,
		lowerToLowest,
		lowerToMaxFloor,
		lowerAndCrush,
		crushAndRaise,
		fastCrushAndRaise,
		silentCrushAndRaise,

		ceilLowerByValue,
		ceilRaiseByValue,
		ceilMoveToValue,
		ceilLowerToHighestFloor,
		ceilLowerInstant,
		ceilRaiseInstant,
		ceilCrushAndRaise,
		ceilLowerAndCrush,
		ceilCrushRaiseAndStay,
		ceilRaiseToNearest,
		ceilLowerToLowest,
		ceilLowerToFloor,

		// The following are only used by Generic_Ceiling
		ceilRaiseToHighest,
		ceilLowerToHighest,
		ceilRaiseToLowest,
		ceilLowerToNearest,
		ceilRaiseToHighestFloor,
		ceilRaiseToFloor,
		ceilRaiseByTexture,
		ceilLowerByTexture,

		genCeiling,
		genCeilingChg0,
		genCeilingChgT,
		genCeilingChg,

		genCrusher,
		genSilentCrusher,
	};

	explicit DCeiling(sector_t *sec);
	DCeiling(sector_t *sec, fixed_t speed1, fixed_t speed2, int silent);
	DCeiling(sector_t* sec, line_t* line, int speed,
	        int target, int crush, int change, int direction, int model);
	DCeiling(sector_t* sec, line_t* line, int silent, int speed);
	[[nodiscard]] DCeiling* Clone(sector_t* sec) const override;
	friend void P_SetCeilingDestroy(DCeiling *ceiling);

	void RunThink () override;
	void PlayCeilingSound();

	ECeiling    m_Type           = lowerToFloor;
	crushmode_e m_CrushMode      = crushDoom;
	fixed_t     m_BottomHeight   = 0;
	fixed_t     m_TopHeight      = 0;
	fixed_t     m_Speed          = 0;
	fixed_t     m_Speed1         = 0; // [RH] dnspeed of crushers
	fixed_t     m_Speed2         = 0; // [RH] upspeed of crushers
	int         m_Crush          = 0;
	int         m_Silent         = 0;
	int         m_Direction      = 0; // 1 = up, 0 = waiting, -1 = down

	// [RH] Need these for BOOM-ish transferring ceilings
	int         m_Texture        = 0;
	short       m_NewSpecial     = 0;
	uint32_t    m_NewFlags       = 0;
	short       m_NewDamageRate  = 0;
	byte        m_NewLeakRate    = 0;
	byte        m_NewDmgInterval = 0;

	// ID
	int         m_Tag            = 0;
	int         m_OldDirection   = 0;

	ECeilingState m_Status       = init;

protected:


private:
	DCeiling() = default;

	friend bool EV_DoCeiling (DCeiling::ECeiling type, line_t *line,
		int tag, fixed_t speed, fixed_t speed2, fixed_t height,
		bool crush, int silent, int change);
	friend bool EV_CeilingCrushStop (int tag);
	friend bool P_ActivateInStasisCeiling (int tag);
	friend bool EV_ZDoomCeilingCrushStop(int tag, bool remove);
};

inline FArchive &operator<< (FArchive &arc, DCeiling::ECeiling type)
{
	return arc << (byte)type;
}
inline FArchive &operator>> (FArchive &arc, DCeiling::ECeiling &type)
{
	byte in; arc >> in; type = (DCeiling::ECeiling)in; return arc;
}
inline FArchive &operator<< (FArchive &arc, DCeiling::ECeilingState state)
{
	return arc << (byte)state;
}
inline FArchive &operator>> (FArchive &arc, DCeiling::ECeilingState &out)
{
	byte in; arc >> in; out = (DCeiling::ECeilingState)in; return arc;
}


//
// P_FLOOR
//

class DFloor : public DMovingFloor
{
	DECLARE_SERIAL (DFloor, DMovingFloor)
public:
	enum EFloorState
	{
		init = 0,
		up,
		down,
		waiting,
		finished,
		destroy,
		state_size
	};

	enum EFloor
	{
		floorLowerToLowest,
		floorLowerToNearest,
		floorLowerToHighest,
		floorLowerByValue,
		floorRaiseByValue,
		floorRaiseToHighest,
		floorRaiseToNearest,
		floorRaiseAndCrush,
		floorRaiseAndCrushDoom,
		floorCrushStop,
		floorLowerInstant,
		floorRaiseInstant,
		floorMoveToValue,
		floorRaiseToLowestCeiling,
		floorRaiseByTexture,

		floorLowerAndChange,
		floorRaiseAndChange,

		floorRaiseToLowest,
		floorRaiseToCeiling,
		floorLowerToLowestCeiling,
		floorLowerByTexture,
		floorLowerToCeiling,

		donutRaise,

		genBuildStair,
		buildStair,
		waitStair,
		resetStair,

		// Not to be used as parameters to EV_DoFloor()
		genFloor,
		genFloorChg0,
		genFloorChgT,
		genFloorChg
	};

	// [RH] Changed to use Hexen-ish specials
	enum EStair
	{
		buildUp,
		buildDown
	};

	explicit DFloor(sector_t *sec);
	DFloor(sector_t *sec, DFloor::EFloor floortype, const line_t *line, fixed_t speed,
		   fixed_t height, bool crush, int change);
	DFloor(sector_t* sec, const line_t* line, int speed,
	       int target, int crush, int change, int direction, int model);
	DFloor(sector_t* sec, DFloor::EFloor floortype, const line_t* line, fixed_t speed,
	               fixed_t height, int crush, int change, bool hexencrush,
	               bool hereticlower);
	[[nodiscard]] DFloor* Clone(sector_t* sec) const override;
	friend void P_SetFloorDestroy(DFloor *floor);
	friend bool EV_DoGenFloor(const line_t* line);
	friend bool EV_DoGenStairs(const line_t* line);

	void RunThink () override;
	void PlayFloorSound();

	EFloor      m_Type            = floorLowerToLowest;
	EFloorState m_Status          = init;
	int         m_Crush           = 0;
	bool        m_HexenCrush      = false;
	int         m_Direction       = 0;
	short       m_NewSpecial      = 0;
	uint32_t    m_NewFlags        = 0;
	short       m_NewDamageRate   = 0;
	byte        m_NewLeakRate     = 0;
	byte        m_NewDmgInterval  = 0;
	short       m_Texture         = 0;
	fixed_t     m_FloorDestHeight = 0;
	fixed_t     m_Speed           = 0;

	// [RH] New parameters used to reset and delay stairs
	int         m_ResetCount      = 0;
	int         m_OrgHeight       = 0;
	int         m_Delay           = 0;
	int         m_PauseTime       = 0;
	int         m_StepTime        = 0;
	int         m_PerStepTime     = 0;

	const line_t *m_Line          = nullptr;

protected:
	friend bool EV_BuildStairs(int tag, DFloor::EStair type, const line_t *line,
		fixed_t stairsize, fixed_t speed, int delay, int reset, int igntxt,
		int usespecials);
	friend bool EV_DoFloor(DFloor::EFloor floortype, line_t *line, int tag,
		fixed_t speed, fixed_t height, bool crush, int change);
	friend bool EV_DoDonut(line_t* line);
	friend bool EV_DoZDoomDonut(int tag, line_t* line, fixed_t pillarspeed,
	                            fixed_t slimespeed);
	friend bool P_SpawnDonut(int tag, line_t* line, fixed_t pillarspeed, fixed_t slimespeed);
	friend bool EV_DoZDoomFloor(DFloor::EFloor floortype, line_t* line, int tag,
	                            fixed_t speed, fixed_t height, int crush, int change,
	                            bool hexencrush, bool hereticlower);

  private:
	DFloor() = default;
};

inline FArchive &operator<< (FArchive &arc, DFloor::EFloor type)
{
	return arc << (byte)type;
}
inline FArchive &operator>> (FArchive &arc, DFloor::EFloor &type)
{
	byte in; arc >> in; type = (DFloor::EFloor)in; return arc;
}
inline FArchive &operator<< (FArchive &arc, DFloor::EFloorState state)
{
	return arc << (byte)state;
}
inline FArchive &operator>> (FArchive &arc, DFloor::EFloorState &out)
{
	byte in; arc >> in; out = (DFloor::EFloorState)in; return arc;
}

class DElevator : public DMover
{
	DECLARE_SERIAL (DElevator, DMover)
public:
	enum EElevatorState
	{
		init = 0,
		finished,
		destroy,
		state_size
	};

	enum EElevator
	{
		elevateUp,
		elevateDown,
		elevateCurrent,
		// [RH] For FloorAndCeiling_Raise/Lower
		elevateRaise,
		elevateLower
	};

	explicit DElevator(sector_t *sec);
	[[nodiscard]] DElevator* Clone(sector_t* sec) const override;
	friend void P_SetElevatorDestroy(DElevator *elevator);

	void RunThink () override;
	void PlayElevatorSound();

	EElevator m_Type              = elevateUp;
	int       m_Direction         = 0;
	fixed_t   m_FloorDestHeight   = 0;
	fixed_t   m_CeilingDestHeight = 0;
	fixed_t   m_Speed             = 0;

	EElevatorState m_Status = init;

protected:
	friend bool EV_DoElevator (const line_t *line, DElevator::EElevator type, fixed_t speed,
		fixed_t height, int tag);
    friend bool EV_DoZDoomElevator(const line_t* line, DElevator::EElevator type, fixed_t speed,
	                        fixed_t height, int tag);

private:
	DElevator() = default;
};

inline FArchive &operator<< (FArchive &arc, DElevator::EElevator type)
{
	return arc << (byte)type;
}
inline FArchive &operator>> (FArchive &arc, DElevator::EElevator &out)
{
	byte in; arc >> in; out = (DElevator::EElevator)in; return arc;
}
inline FArchive &operator<< (FArchive &arc, DElevator::EElevatorState state)
{
	return arc << (byte)state;
}
inline FArchive &operator>> (FArchive &arc, DElevator::EElevatorState &out)
{
	byte in; arc >> in; out = (DElevator::EElevatorState)in; return arc;
}

// Waggle
/*
class DWaggle : public DMover
{
	DECLARE_SERIAL(DWaggle, DMover)
  public:
	enum EWaggleState
	{
		init = 0,
		expand,
		reduce,
		stable,
		finished,
		destroy,
		state_size
	};
	DWaggle(sector_t* sec);
	DWaggle(sector_t* sector, int height, int speed, int offset, int timer,
	                 bool ceiling);
	DWaggle* Clone(sector_t* sec) const override;
	friend void P_SetWaggleDestroy(DWaggle* waggle);

	void RunThink() override;

	fixed_t m_OriginalHeight;
	fixed_t m_Accumulator;
	fixed_t m_AccDelta;
	fixed_t m_TargetScale;
	fixed_t m_Scale;
	fixed_t m_ScaleDelta;
	fixed_t m_StartTic; // [Blair] Client will predict a created (or serialized) waggle based on the start tic.
	int m_Ticker;
	int m_State;
	bool m_Ceiling;
	DWaggle();

  protected:
	friend bool EV_StartPlaneWaggle(int tag, line_t* line, int height, int speed,
	                                int offset, int timer, bool ceiling);
};
*/
//jff 3/15/98 pure texture/type change for better generalized support
enum EChange
{
	trigChangeOnly,
	numChangeOnly
};

bool EV_DoChange (line_t *line, EChange changetype, int tag);



//
// P_TELEPT
//
bool EV_Teleport (int tid, int tag, int arg0, int side, AActor *thing, int nostop);
bool EV_LineTeleport (line_t *line, int side, AActor *thing);
bool EV_SilentTeleport(int tid, int useangle, int tag, int keepheight, line_t* line,
                       int side, AActor* thing);
bool EV_SilentLineTeleport (line_t *line, int side, AActor *thing, int id,
							bool reverse);

//
// [RH] ACS (see also p_acs.h)
//

bool P_StartScript (AActor *who, line_t *where, int script, const char *map, int lineSide,
					int arg0, int arg1, int arg2, int always);
void P_SuspendScript (int script, const char *map);
void P_TerminateScript (int script, const char *map);
void P_StartOpenScripts (void);
void P_DoDeferedScripts (void);


//
// [RH] p_quake.c
//
bool P_StartQuake (int tid, int intensity, int duration, int damrad, int tremrad);

// [AM] Trigger actor specials.
bool A_TriggerAction(AActor *mo, AActor *triggerer, int activationType);
