mirror of
https://github.com/ZDoom/qzdoom.git
synced 2024-11-25 05:21:02 +00:00
66d28a24b8
Needless to say, this is simply too volatile and would require constant active maintenance, not to mention a huge amount of work up front to get going. It also hid a nasty problem with the Destroy method. Due to the way the garbage collector works, Destroy cannot be exposed to scripts as-is. It may be called from scripts but it may not be overridden from scripts because the garbage collector can call this function after all data needed for calling a scripted override has already been destroyed because if that data is also being collected there is no guarantee that proper order of destruction is observed. So for now Destroy is just a normal native method to scripted classes
434 lines
10 KiB
C++
434 lines
10 KiB
C++
// Emacs style mode select -*- C++ -*-
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
// $Id:$
|
|
//
|
|
// Copyright (C) 1993-1996 by id Software, Inc.
|
|
//
|
|
// This source is available for distribution and/or modification
|
|
// only under the terms of the DOOM Source Code License as
|
|
// published by id Software. All rights reserved.
|
|
//
|
|
// The source is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License
|
|
// for more details.
|
|
//
|
|
// $Log:$
|
|
//
|
|
// DESCRIPTION:
|
|
// Plats (i.e. elevator platforms) code, raising/lowering.
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include "i_system.h"
|
|
#include "m_random.h"
|
|
#include "doomdef.h"
|
|
#include "p_local.h"
|
|
#include "p_lnspec.h"
|
|
#include "s_sndseq.h"
|
|
#include "doomstat.h"
|
|
#include "r_state.h"
|
|
#include "gi.h"
|
|
#include "serializer.h"
|
|
#include "p_spec.h"
|
|
|
|
static FRandom pr_doplat ("DoPlat");
|
|
|
|
IMPLEMENT_CLASS(DPlat, false, false)
|
|
|
|
DPlat::DPlat ()
|
|
{
|
|
}
|
|
|
|
void DPlat::Serialize(FSerializer &arc)
|
|
{
|
|
Super::Serialize (arc);
|
|
arc.Enum("type", m_Type)
|
|
("speed", m_Speed)
|
|
("low", m_Low)
|
|
("high", m_High)
|
|
("wait", m_Wait)
|
|
("count", m_Count)
|
|
.Enum("status", m_Status)
|
|
.Enum("oldstatus", m_OldStatus)
|
|
("crush", m_Crush)
|
|
("tag", m_Tag);
|
|
}
|
|
|
|
void DPlat::PlayPlatSound (const char *sound)
|
|
{
|
|
if (m_Sector->Flags & SECF_SILENTMOVE) return;
|
|
|
|
if (m_Sector->seqType >= 0)
|
|
{
|
|
SN_StartSequence (m_Sector, CHAN_FLOOR, m_Sector->seqType, SEQ_PLATFORM, 0);
|
|
}
|
|
else if (m_Sector->SeqName != NAME_None)
|
|
{
|
|
SN_StartSequence (m_Sector, CHAN_FLOOR, m_Sector->SeqName, 0);
|
|
}
|
|
else
|
|
{
|
|
SN_StartSequence (m_Sector, CHAN_FLOOR, sound, 0);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Move a plat up and down
|
|
//
|
|
void DPlat::Tick ()
|
|
{
|
|
EMoveResult res;
|
|
|
|
switch (m_Status)
|
|
{
|
|
case up:
|
|
res = m_Sector->MoveFloor (m_Speed, m_High, m_Crush, 1, false);
|
|
|
|
if (res == EMoveResult::crushed && (m_Crush == -1))
|
|
{
|
|
m_Count = m_Wait;
|
|
m_Status = down;
|
|
PlayPlatSound ("Platform");
|
|
}
|
|
else if (res == EMoveResult::pastdest)
|
|
{
|
|
SN_StopSequence (m_Sector, CHAN_FLOOR);
|
|
if (m_Type != platToggle)
|
|
{
|
|
m_Count = m_Wait;
|
|
m_Status = waiting;
|
|
|
|
switch (m_Type)
|
|
{
|
|
case platRaiseAndStayLockout:
|
|
// Instead of keeping the dead thinker like Heretic did let's
|
|
// better use a flag to avoid problems elsewhere. For example,
|
|
// keeping the thinker would make tagwait wait indefinitely.
|
|
m_Sector->planes[sector_t::floor].Flags |= PLANEF_BLOCKED;
|
|
case platRaiseAndStay:
|
|
case platDownByValue:
|
|
case platDownWaitUpStay:
|
|
case platDownWaitUpStayStone:
|
|
case platUpByValueStay:
|
|
case platDownToNearestFloor:
|
|
case platDownToLowestCeiling:
|
|
Destroy ();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_OldStatus = m_Status; //jff 3/14/98 after action wait
|
|
m_Status = in_stasis; //for reactivation of toggle
|
|
}
|
|
}
|
|
break;
|
|
|
|
case down:
|
|
res = m_Sector->MoveFloor (m_Speed, m_Low, -1, -1, false);
|
|
|
|
if (res == EMoveResult::pastdest)
|
|
{
|
|
SN_StopSequence (m_Sector, CHAN_FLOOR);
|
|
// if not an instant toggle, start waiting
|
|
if (m_Type != platToggle) //jff 3/14/98 toggle up down
|
|
{ // is silent, instant, no waiting
|
|
m_Count = m_Wait;
|
|
m_Status = waiting;
|
|
|
|
switch (m_Type)
|
|
{
|
|
case platUpWaitDownStay:
|
|
case platUpNearestWaitDownStay:
|
|
case platUpByValue:
|
|
Destroy ();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{ // instant toggles go into stasis awaiting next activation
|
|
m_OldStatus = m_Status; //jff 3/14/98 after action wait
|
|
m_Status = in_stasis; //for reactivation of toggle
|
|
}
|
|
}
|
|
else if (res == EMoveResult::crushed && m_Crush < 0 && m_Type != platToggle)
|
|
{
|
|
m_Status = up;
|
|
m_Count = m_Wait;
|
|
PlayPlatSound ("Platform");
|
|
}
|
|
|
|
//jff 1/26/98 remove the plat if it bounced so it can be tried again
|
|
//only affects plats that raise and bounce
|
|
|
|
// remove the plat if it's a pure raise type
|
|
switch (m_Type)
|
|
{
|
|
case platUpByValueStay:
|
|
case platRaiseAndStay:
|
|
case platRaiseAndStayLockout:
|
|
Destroy ();
|
|
default:
|
|
break;
|
|
}
|
|
|
|
break;
|
|
|
|
case waiting:
|
|
if (m_Count > 0 && !--m_Count)
|
|
{
|
|
if (m_Sector->floorplane.fD() == m_Low)
|
|
m_Status = up;
|
|
else
|
|
m_Status = down;
|
|
|
|
if (m_Type == platToggle)
|
|
SN_StartSequence (m_Sector, CHAN_FLOOR, "Silence", 0);
|
|
else
|
|
PlayPlatSound ("Platform");
|
|
}
|
|
break;
|
|
|
|
case in_stasis:
|
|
break;
|
|
}
|
|
}
|
|
|
|
DPlat::DPlat (sector_t *sector)
|
|
: DMovingFloor (sector)
|
|
{
|
|
}
|
|
|
|
//
|
|
// Do Platforms
|
|
// [RH] Changed amount to height and added delay,
|
|
// lip, change, tag, and speed parameters.
|
|
//
|
|
bool EV_DoPlat (int tag, line_t *line, DPlat::EPlatType type, double height,
|
|
double speed, int delay, int lip, int change)
|
|
{
|
|
DPlat *plat;
|
|
int secnum;
|
|
sector_t *sec;
|
|
bool rtn = false;
|
|
bool manual = false;
|
|
double newheight = 0;
|
|
vertex_t *spot;
|
|
|
|
if (tag != 0)
|
|
{
|
|
// Activate all <type> plats that are in_stasis
|
|
switch (type)
|
|
{
|
|
case DPlat::platToggle:
|
|
rtn = true;
|
|
case DPlat::platPerpetualRaise:
|
|
P_ActivateInStasis (tag);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
// [RH] If tag is zero, use the sector on the back side
|
|
// of the activating line (if any).
|
|
FSectorTagIterator itr(tag, line);
|
|
while ((secnum = itr.Next()) >= 0)
|
|
{
|
|
sec = §ors[secnum];
|
|
|
|
if (sec->PlaneMoving(sector_t::floor))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Find lowest & highest floors around sector
|
|
rtn = true;
|
|
plat = new DPlat (sec);
|
|
|
|
plat->m_Type = type;
|
|
plat->m_Crush = -1;
|
|
plat->m_Tag = tag;
|
|
plat->m_Speed = speed;
|
|
plat->m_Wait = delay;
|
|
|
|
//jff 1/26/98 Avoid raise plat bouncing a head off a ceiling and then
|
|
//going down forever -- default lower to plat height when triggered
|
|
plat->m_Low = sec->floorplane.fD();
|
|
|
|
if (change)
|
|
{
|
|
if (line)
|
|
sec->SetTexture(sector_t::floor, line->sidedef[0]->sector->GetTexture(sector_t::floor));
|
|
if (change == 1) sec->ClearSpecial();
|
|
}
|
|
|
|
switch (type)
|
|
{
|
|
case DPlat::platRaiseAndStay:
|
|
case DPlat::platRaiseAndStayLockout:
|
|
newheight = sec->FindNextHighestFloor (&spot);
|
|
plat->m_High = sec->floorplane.PointToDist (spot, newheight);
|
|
plat->m_Low = sec->floorplane.fD();
|
|
plat->m_Status = DPlat::up;
|
|
plat->PlayPlatSound ("Floor");
|
|
sec->ClearSpecial();
|
|
break;
|
|
|
|
case DPlat::platUpByValue:
|
|
case DPlat::platUpByValueStay:
|
|
newheight = sec->floorplane.ZatPoint (sec->centerspot) + height;
|
|
plat->m_High = sec->floorplane.PointToDist (sec->centerspot, newheight);
|
|
plat->m_Low = sec->floorplane.fD();
|
|
plat->m_Status = DPlat::up;
|
|
plat->PlayPlatSound ("Floor");
|
|
break;
|
|
|
|
case DPlat::platDownByValue:
|
|
newheight = sec->floorplane.ZatPoint (sec->centerspot) - height;
|
|
plat->m_Low = sec->floorplane.PointToDist (sec->centerspot, newheight);
|
|
plat->m_High = sec->floorplane.fD();
|
|
plat->m_Status = DPlat::down;
|
|
plat->PlayPlatSound ("Floor");
|
|
break;
|
|
|
|
case DPlat::platDownWaitUpStay:
|
|
case DPlat::platDownWaitUpStayStone:
|
|
newheight = sec->FindLowestFloorSurrounding (&spot) + lip;
|
|
plat->m_Low = sec->floorplane.PointToDist (spot, newheight);
|
|
|
|
if (plat->m_Low < sec->floorplane.fD())
|
|
plat->m_Low = sec->floorplane.fD();
|
|
|
|
plat->m_High = sec->floorplane.fD();
|
|
plat->m_Status = DPlat::down;
|
|
plat->PlayPlatSound (type == DPlat::platDownWaitUpStay ? "Platform" : "Floor");
|
|
break;
|
|
|
|
case DPlat::platUpNearestWaitDownStay:
|
|
newheight = sec->FindNextHighestFloor (&spot);
|
|
// Intentional fall-through
|
|
|
|
case DPlat::platUpWaitDownStay:
|
|
if (type == DPlat::platUpWaitDownStay)
|
|
{
|
|
newheight = sec->FindHighestFloorSurrounding (&spot);
|
|
}
|
|
plat->m_High = sec->floorplane.PointToDist (spot, newheight);
|
|
plat->m_Low = sec->floorplane.fD();
|
|
|
|
if (plat->m_High > sec->floorplane.fD())
|
|
plat->m_High = sec->floorplane.fD();
|
|
|
|
plat->m_Status = DPlat::up;
|
|
plat->PlayPlatSound ("Platform");
|
|
break;
|
|
|
|
case DPlat::platPerpetualRaise:
|
|
newheight = sec->FindLowestFloorSurrounding (&spot) + lip;
|
|
plat->m_Low = sec->floorplane.PointToDist (spot, newheight);
|
|
|
|
if (plat->m_Low < sec->floorplane.fD())
|
|
plat->m_Low = sec->floorplane.fD();
|
|
|
|
newheight = sec->FindHighestFloorSurrounding (&spot);
|
|
plat->m_High = sec->floorplane.PointToDist (spot, newheight);
|
|
|
|
if (plat->m_High > sec->floorplane.fD())
|
|
plat->m_High = sec->floorplane.fD();
|
|
|
|
plat->m_Status = pr_doplat() & 1 ? DPlat::up : DPlat::down;
|
|
|
|
plat->PlayPlatSound ("Platform");
|
|
break;
|
|
|
|
case DPlat::platToggle: //jff 3/14/98 add new type to support instant toggle
|
|
plat->m_Crush = 10; //jff 3/14/98 crush anything in the way
|
|
|
|
// set up toggling between ceiling, floor inclusive
|
|
newheight = sec->FindLowestCeilingPoint (&spot);
|
|
plat->m_Low = sec->floorplane.PointToDist (spot, newheight);
|
|
plat->m_High = sec->floorplane.fD();
|
|
plat->m_Status = DPlat::down;
|
|
SN_StartSequence (sec, CHAN_FLOOR, "Silence", 0);
|
|
break;
|
|
|
|
case DPlat::platDownToNearestFloor:
|
|
newheight = sec->FindNextLowestFloor (&spot) + lip;
|
|
plat->m_Low = sec->floorplane.PointToDist (spot, newheight);
|
|
plat->m_Status = DPlat::down;
|
|
plat->m_High = sec->floorplane.fD();
|
|
plat->PlayPlatSound ("Platform");
|
|
break;
|
|
|
|
case DPlat::platDownToLowestCeiling:
|
|
newheight = sec->FindLowestCeilingSurrounding (&spot);
|
|
plat->m_Low = sec->floorplane.PointToDist (spot, newheight);
|
|
plat->m_High = sec->floorplane.fD();
|
|
|
|
if (plat->m_Low < sec->floorplane.fD())
|
|
plat->m_Low = sec->floorplane.fD();
|
|
|
|
plat->m_Status = DPlat::down;
|
|
plat->PlayPlatSound ("Platform");
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
return rtn;
|
|
}
|
|
|
|
void DPlat::Reactivate ()
|
|
{
|
|
if (m_Type == platToggle) //jff 3/14/98 reactivate toggle type
|
|
m_Status = m_OldStatus == up ? down : up;
|
|
else
|
|
m_Status = m_OldStatus;
|
|
}
|
|
|
|
void P_ActivateInStasis (int tag)
|
|
{
|
|
DPlat *scan;
|
|
TThinkerIterator<DPlat> iterator;
|
|
|
|
while ( (scan = iterator.Next ()) )
|
|
{
|
|
if (scan->m_Tag == tag && scan->m_Status == DPlat::in_stasis)
|
|
scan->Reactivate ();
|
|
}
|
|
}
|
|
|
|
void DPlat::Stop ()
|
|
{
|
|
m_OldStatus = m_Status;
|
|
m_Status = in_stasis;
|
|
}
|
|
|
|
void EV_StopPlat (int tag, bool remove)
|
|
{
|
|
DPlat *scan;
|
|
TThinkerIterator<DPlat> iterator;
|
|
|
|
scan = iterator.Next();
|
|
while (scan != nullptr)
|
|
{
|
|
DPlat *next = iterator.Next();
|
|
if (scan->m_Status != DPlat::in_stasis && scan->m_Tag == tag)
|
|
{
|
|
if (!remove) scan->Stop();
|
|
else scan->Destroy();
|
|
}
|
|
scan = next;
|
|
}
|
|
}
|
|
|