gzdoom/src/p_ceiling.cpp

608 lines
16 KiB
C++

//-----------------------------------------------------------------------------
//
// Copyright 1993-1996 id Software
// Copyright 1994-1996 Raven Software
// Copyright 1998-1998 Chi Hoang, Lee Killough, Jim Flynn, Rand Phares, Ty Halderman
// Copyright 1999-2016 Randy Heit
// Copyright 2002-2016 Christoph Oelckers
//
// 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 3 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.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses/
//
//-----------------------------------------------------------------------------
//
//
// DESCRIPTION: Ceiling animation (lowering, crushing, raising)
//
//-----------------------------------------------------------------------------
#include "doomdef.h"
#include "p_local.h"
#include "s_sound.h"
#include "s_sndseq.h"
#include "doomstat.h"
#include "r_state.h"
#include "gi.h"
#include "serializer.h"
#include "p_spec.h"
#include "g_levellocals.h"
#include "textures.h"
#include "vm.h"
//============================================================================
//
// CEILINGS
//
//============================================================================
IMPLEMENT_CLASS(DCeiling, false, false)
DCeiling::DCeiling ()
{
}
//============================================================================
//
//
//
//============================================================================
void DCeiling::Serialize(FSerializer &arc)
{
Super::Serialize (arc);
arc.Enum("type", m_Type)
("bottomheight", m_BottomHeight)
("topheight", m_TopHeight)
("speed", m_Speed)
("speed1", m_Speed1)
("speed2", m_Speed2)
("crush", m_Crush)
("silent", m_Silent)
("direction", m_Direction)
("texture", m_Texture)
("newspecial", m_NewSpecial)
("tag", m_Tag)
("olddirecton", m_OldDirection)
.Enum("crushmode", m_CrushMode);
}
//============================================================================
//
//
//
//============================================================================
void DCeiling::PlayCeilingSound ()
{
if (m_Sector->Flags & SECF_SILENTMOVE) return;
if (m_Sector->seqType >= 0)
{
SN_StartSequence (m_Sector, CHAN_CEILING, m_Sector->seqType, SEQ_PLATFORM, 0, false);
}
else if (m_Sector->SeqName != NAME_None)
{
SN_StartSequence (m_Sector, CHAN_CEILING, m_Sector->SeqName, 0);
}
else
{
if (m_Silent == 2)
SN_StartSequence (m_Sector, CHAN_CEILING, "Silence", 0);
else if (m_Silent == 1)
SN_StartSequence (m_Sector, CHAN_CEILING, "CeilingSemiSilent", 0);
else
SN_StartSequence (m_Sector, CHAN_CEILING, "CeilingNormal", 0);
}
}
//============================================================================
//
// DCeiling :: Tick
//
//============================================================================
void DCeiling::Tick ()
{
EMoveResult res;
switch (m_Direction)
{
case 0:
// IN STASIS
break;
case 1:
// UP
res = m_Sector->MoveCeiling (m_Speed, m_TopHeight, m_Direction);
if (res == EMoveResult::pastdest)
{
switch (m_Type)
{
case DCeiling::ceilCrushAndRaise:
m_Direction = -1;
m_Speed = m_Speed1;
if (!SN_IsMakingLoopingSound (m_Sector))
PlayCeilingSound ();
break;
// movers with texture change, change the texture then get removed
case genCeilingChgT:
case genCeilingChg0:
m_Sector->SetSpecial(&m_NewSpecial);
// fall through
case genCeilingChg:
m_Sector->SetTexture(sector_t::ceiling, m_Texture);
// fall through
default:
SN_StopSequence (m_Sector, CHAN_CEILING);
Destroy ();
break;
}
}
break;
case -1:
// DOWN
res = m_Sector->MoveCeiling (m_Speed, m_BottomHeight, m_Crush, m_Direction, m_CrushMode == ECrushMode::crushHexen);
if (res == EMoveResult::pastdest)
{
switch (m_Type)
{
case DCeiling::ceilCrushAndRaise:
case DCeiling::ceilCrushRaiseAndStay:
m_Speed = m_Speed2;
m_Direction = 1;
if (!SN_IsMakingLoopingSound (m_Sector))
PlayCeilingSound ();
break;
// in the case of ceiling mover/changer, change the texture
// then remove the active ceiling
case genCeilingChgT:
case genCeilingChg0:
m_Sector->SetSpecial(&m_NewSpecial);
// fall through
case genCeilingChg:
m_Sector->SetTexture(sector_t::ceiling, m_Texture);
// fall through
default:
SN_StopSequence (m_Sector, CHAN_CEILING);
Destroy ();
break;
}
}
else // ( res != pastdest )
{
if (res == EMoveResult::crushed)
{
switch (m_Type)
{
case DCeiling::ceilCrushAndRaise:
case DCeiling::ceilLowerAndCrush:
if (m_CrushMode == ECrushMode::crushSlowdown)
m_Speed = 1. / 8;
break;
default:
break;
}
}
}
break;
}
}
//============================================================================
//
//
//
//============================================================================
DCeiling::DCeiling (sector_t *sec)
: DMovingCeiling (sec)
{
}
DCeiling::DCeiling (sector_t *sec, double speed1, double speed2, int silent)
: DMovingCeiling (sec)
{
m_Crush = -1;
m_CrushMode = ECrushMode::crushDoom;
m_Speed = m_Speed1 = speed1;
m_Speed2 = speed2;
m_Silent = silent;
m_BottomHeight = 0;
m_TopHeight = 0;
m_Direction = 0;
m_Texture = FNullTextureID();
m_Tag = 0;
m_OldDirection = 0;
}
//============================================================================
//
//
//
//============================================================================
bool P_CreateCeiling(sector_t *sec, DCeiling::ECeiling type, line_t *line, int tag,
double speed, double speed2, double height,
int crush, int silent, int change, DCeiling::ECrushMode hexencrush)
{
double targheight = 0; // Silence, GCC
// if ceiling already moving, don't start a second function on it
if (sec->PlaneMoving(sector_t::ceiling))
{
return false;
}
// new door thinker
DCeiling *ceiling = Create<DCeiling> (sec, speed, speed2, silent & ~4);
vertex_t *spot = sec->Lines[0]->v1;
switch (type)
{
case DCeiling::ceilCrushAndRaise:
case DCeiling::ceilCrushRaiseAndStay:
ceiling->m_TopHeight = sec->ceilingplane.fD();
case DCeiling::ceilLowerAndCrush:
targheight = sec->FindHighestFloorPoint (&spot);
targheight += height;
ceiling->m_BottomHeight = sec->ceilingplane.PointToDist (spot, targheight);
ceiling->m_Direction = -1;
break;
case DCeiling::ceilRaiseToHighest:
targheight = sec->FindHighestCeilingSurrounding (&spot);
ceiling->m_TopHeight = sec->ceilingplane.PointToDist (spot, targheight);
ceiling->m_Direction = 1;
break;
case DCeiling::ceilLowerByValue:
targheight = sec->ceilingplane.ZatPoint (spot) - height;
ceiling->m_BottomHeight = sec->ceilingplane.PointToDist (spot, targheight);
ceiling->m_Direction = -1;
break;
case DCeiling::ceilRaiseByValue:
targheight = sec->ceilingplane.ZatPoint (spot) + height;
ceiling->m_TopHeight = sec->ceilingplane.PointToDist (spot, targheight);
ceiling->m_Direction = 1;
break;
case DCeiling::ceilMoveToValue:
{
double diff = height - sec->ceilingplane.ZatPoint (spot);
targheight = height;
if (diff < 0)
{
ceiling->m_BottomHeight = sec->ceilingplane.PointToDist (spot, height);
ceiling->m_Direction = -1;
}
else
{
ceiling->m_TopHeight = sec->ceilingplane.PointToDist (spot, height);
ceiling->m_Direction = 1;
}
}
break;
case DCeiling::ceilLowerToHighestFloor:
targheight = sec->FindHighestFloorSurrounding (&spot) + height;
ceiling->m_BottomHeight = sec->ceilingplane.PointToDist (spot, targheight);
ceiling->m_Direction = -1;
break;
case DCeiling::ceilRaiseToHighestFloor:
targheight = sec->FindHighestFloorSurrounding (&spot);
ceiling->m_TopHeight = sec->ceilingplane.PointToDist (spot, targheight);
ceiling->m_Direction = 1;
break;
case DCeiling::ceilLowerInstant:
targheight = sec->ceilingplane.ZatPoint (spot) - height;
ceiling->m_BottomHeight = sec->ceilingplane.PointToDist (spot, targheight);
ceiling->m_Direction = -1;
ceiling->m_Speed = height;
break;
case DCeiling::ceilRaiseInstant:
targheight = sec->ceilingplane.ZatPoint (spot) + height;
ceiling->m_TopHeight = sec->ceilingplane.PointToDist (spot, targheight);
ceiling->m_Direction = 1;
ceiling->m_Speed = height;
break;
case DCeiling::ceilLowerToNearest:
targheight = sec->FindNextLowestCeiling (&spot);
ceiling->m_BottomHeight = sec->ceilingplane.PointToDist (spot, targheight);
ceiling->m_Direction = -1;
break;
case DCeiling::ceilRaiseToNearest:
targheight = sec->FindNextHighestCeiling (&spot);
ceiling->m_TopHeight = sec->ceilingplane.PointToDist (spot, targheight);
ceiling->m_Direction = 1;
break;
case DCeiling::ceilLowerToLowest:
targheight = sec->FindLowestCeilingSurrounding (&spot);
ceiling->m_BottomHeight = sec->ceilingplane.PointToDist (spot, targheight);
ceiling->m_Direction = -1;
break;
case DCeiling::ceilRaiseToLowest:
targheight = sec->FindLowestCeilingSurrounding (&spot);
ceiling->m_TopHeight = sec->ceilingplane.PointToDist (spot, targheight);
ceiling->m_Direction = 1;
break;
case DCeiling::ceilLowerToFloor:
targheight = sec->FindHighestFloorPoint (&spot) + height;
ceiling->m_BottomHeight = sec->ceilingplane.PointToDist (spot, targheight);
ceiling->m_Direction = -1;
break;
case DCeiling::ceilRaiseToFloor: // [RH] What's this for?
targheight = sec->FindHighestFloorPoint (&spot) + height;
ceiling->m_TopHeight = sec->ceilingplane.PointToDist (spot, targheight);
ceiling->m_Direction = 1;
break;
case DCeiling::ceilLowerToHighest:
targheight = sec->FindHighestCeilingSurrounding (&spot);
ceiling->m_BottomHeight = sec->ceilingplane.PointToDist (spot, targheight);
ceiling->m_Direction = -1;
break;
case DCeiling::ceilLowerByTexture:
targheight = sec->ceilingplane.ZatPoint (spot) - sec->FindShortestUpperAround ();
ceiling->m_BottomHeight = sec->ceilingplane.PointToDist (spot, targheight);
ceiling->m_Direction = -1;
break;
case DCeiling::ceilRaiseByTexture:
targheight = sec->ceilingplane.ZatPoint (spot) + sec->FindShortestUpperAround ();
ceiling->m_TopHeight = sec->ceilingplane.PointToDist (spot, targheight);
ceiling->m_Direction = 1;
break;
default:
break; // Silence GCC
}
ceiling->m_Tag = tag;
ceiling->m_Type = type;
ceiling->m_Crush = crush;
ceiling->m_CrushMode = hexencrush;
// Do not interpolate instant movement ceilings.
// Note for ZDoomGL: Check to make sure that you update the sector
// after the ceiling moves, because it hasn't actually moved yet.
double movedist;
if (ceiling->m_Direction < 0)
{
movedist = sec->ceilingplane.fD() - ceiling->m_BottomHeight;
}
else
{
movedist = ceiling->m_TopHeight - sec->ceilingplane.fD();
}
if (ceiling->m_Speed >= movedist)
{
ceiling->StopInterpolation(true);
if (silent & 4) ceiling->m_Silent = 2;
}
// set texture/type change properties
if (change & 3) // if a texture change is indicated
{
if (change & 4) // if a numeric model change
{
sector_t *modelsec;
//jff 5/23/98 find model with floor at target height if target
//is a floor type
modelsec = (/*type == ceilRaiseToHighest ||*/
type == DCeiling::ceilRaiseToFloor ||
/*type == ceilLowerToHighest ||*/
type == DCeiling::ceilLowerToFloor) ?
sec->FindModelFloorSector (targheight) :
sec->FindModelCeilingSector (targheight);
if (modelsec != NULL)
{
ceiling->m_Texture = modelsec->GetTexture(sector_t::ceiling);
switch (change & 3)
{
case 1: // type is zeroed
ceiling->m_NewSpecial.Clear();
ceiling->m_Type = DCeiling::genCeilingChg0;
break;
case 2: // type is copied
sec->GetSpecial(&ceiling->m_NewSpecial);
ceiling->m_Type = DCeiling::genCeilingChgT;
break;
case 3: // type is left alone
ceiling->m_Type = DCeiling::genCeilingChg;
break;
}
}
}
else if (line) // else if a trigger model change
{
ceiling->m_Texture = line->frontsector->GetTexture(sector_t::ceiling);
switch (change & 3)
{
case 1: // type is zeroed
ceiling->m_NewSpecial.Clear();
ceiling->m_Type = DCeiling::genCeilingChg0;
break;
case 2: // type is copied
line->frontsector->GetSpecial(&ceiling->m_NewSpecial);
ceiling->m_Type = DCeiling::genCeilingChgT;
break;
case 3: // type is left alone
ceiling->m_Type = DCeiling::genCeilingChg;
break;
}
}
}
ceiling->PlayCeilingSound ();
return ceiling != NULL;
}
DEFINE_ACTION_FUNCTION(DCeiling, CreateCeiling)
{
PARAM_PROLOGUE;
PARAM_POINTER_NOT_NULL(sec, sector_t);
PARAM_INT(type);
PARAM_POINTER(ln, line_t);
PARAM_FLOAT(speed);
PARAM_FLOAT(speed2);
PARAM_FLOAT_DEF(height);
PARAM_INT_DEF(crush);
PARAM_INT_DEF(silent);
PARAM_INT_DEF(change);
PARAM_INT_DEF(crushmode);
ACTION_RETURN_BOOL(P_CreateCeiling(sec, (DCeiling::ECeiling)type, ln, 0, speed, speed2, height, crush, silent, change, (DCeiling::ECrushMode)crushmode));
}
//============================================================================
//
// EV_DoCeiling
// Move a ceiling up/down and all around!
//
// [RH] Added tag, speed, speed2, height, crush, silent, change params
//
//============================================================================
bool EV_DoCeiling (DCeiling::ECeiling type, line_t *line,
int tag, double speed, double speed2, double height,
int crush, int silent, int change, DCeiling::ECrushMode hexencrush)
{
int secnum;
bool rtn;
sector_t* sec;
rtn = false;
// check if a manual trigger, if so do just the sector on the backside
if (tag == 0)
{
if (!line || !(sec = line->backsector))
return rtn;
secnum = sec->sectornum;
// [RH] Hack to let manual crushers be retriggerable, too
tag ^= secnum | 0x1000000;
P_ActivateInStasisCeiling (tag);
return P_CreateCeiling(sec, type, line, tag, speed, speed2, height, crush, silent, change, hexencrush);
}
// Reactivate in-stasis ceilings...for certain types.
// This restarts a crusher after it has been stopped
if (type == DCeiling::ceilCrushAndRaise)
{
P_ActivateInStasisCeiling (tag);
}
// affects all sectors with the same tag as the linedef
FSectorTagIterator it(tag);
while ((secnum = it.Next()) >= 0)
{
rtn |= P_CreateCeiling(&level.sectors[secnum], type, line, tag, speed, speed2, height, crush, silent, change, hexencrush);
}
return rtn;
}
//============================================================================
//
// Restart a ceiling that's in-stasis
// [RH] Passed a tag instead of a line and rewritten to use a list
//
//============================================================================
void P_ActivateInStasisCeiling (int tag)
{
DCeiling *scan;
TThinkerIterator<DCeiling> iterator;
while ( (scan = iterator.Next ()) )
{
if (scan->m_Tag == tag && scan->m_Direction == 0)
{
scan->m_Direction = scan->m_OldDirection;
scan->PlayCeilingSound ();
}
}
}
//============================================================================
//
// EV_CeilingCrushStop
// Stop a ceiling from crushing!
// [RH] Passed a tag instead of a line and rewritten to use a list
//
//============================================================================
bool EV_CeilingCrushStop (int tag, bool remove)
{
bool rtn = false;
DCeiling *scan;
TThinkerIterator<DCeiling> iterator;
scan = iterator.Next();
while (scan != nullptr)
{
DCeiling *next = iterator.Next();
if (scan->m_Tag == tag && scan->m_Direction != 0)
{
if (!remove)
{
SN_StopSequence(scan->m_Sector, CHAN_CEILING);
scan->m_OldDirection = scan->m_Direction;
scan->m_Direction = 0; // in-stasis;
}
else
{
scan->Destroy();
}
rtn = true;
}
scan = next;
}
return rtn;
}
bool EV_StopCeiling(int tag, line_t *line)
{
int sec;
FSectorTagIterator it(tag, line);
while ((sec = it.Next()) >= 0)
{
if (level.sectors[sec].ceilingdata)
{
SN_StopSequence(&level.sectors[sec], CHAN_CEILING);
level.sectors[sec].ceilingdata->Destroy();
level.sectors[sec].ceilingdata = nullptr;
}
}
return true;
}