//----------------------------------------------------------------------------- // // Copyright 1993-1996 id Software // 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: Door animation code (opening/closing) // [RH] Removed sliding door code and simplified for Hexen-ish specials // //----------------------------------------------------------------------------- #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 "a_keys.h" #include "serializer.h" #include "d_player.h" #include "p_spec.h" #include "g_levellocals.h" //============================================================================ // // VERTICAL DOORS // //============================================================================ IMPLEMENT_CLASS(DDoor, false, false) DDoor::DDoor () { } void DDoor::Serialize(FSerializer &arc) { Super::Serialize (arc); arc.Enum("type", m_Type) ("topdist", m_TopDist) ("botspot", m_BotSpot) ("botdist", m_BotDist) ("oldfloordist", m_OldFloorDist) ("speed", m_Speed) ("direction", m_Direction) ("topwait", m_TopWait) ("topcountdown", m_TopCountdown) ("lighttag", m_LightTag); } //============================================================================ // // T_VerticalDoor // //============================================================================ void DDoor::Tick () { EMoveResult res; // Adjust bottom height - but only if there isn't an active lift attached to the floor. if (m_Sector->floorplane.fD() != m_OldFloorDist) { if (!m_Sector->floordata || !m_Sector->floordata->IsKindOf(RUNTIME_CLASS(DPlat)) || !(barrier_cast(m_Sector->floordata))->IsLift()) { m_OldFloorDist = m_Sector->floorplane.fD(); m_BotDist = m_Sector->ceilingplane.PointToDist (m_BotSpot, m_Sector->floorplane.ZatPoint (m_BotSpot)); } } switch (m_Direction) { case 0: // WAITING if (!--m_TopCountdown) { switch (m_Type) { case doorRaise: m_Direction = -1; // time to go back down DoorSound (false); break; case doorCloseWaitOpen: m_Direction = 1; DoorSound (true); break; default: break; } } break; case 2: // INITIAL WAIT if (!--m_TopCountdown) { switch (m_Type) { case doorWaitRaise: m_Direction = 1; m_Type = doorRaise; DoorSound (true); break; default: break; } } break; case -1: // DOWN res = m_Sector->MoveCeiling (m_Speed, m_BotDist, -1, m_Direction, false); // killough 10/98: implement gradual lighting effects if (m_LightTag != 0 && m_TopDist != -m_Sector->floorplane.fD()) { EV_LightTurnOnPartway (m_LightTag, (m_Sector->ceilingplane.fD() + m_Sector->floorplane.fD()) / (m_TopDist + m_Sector->floorplane.fD())); } if (res == EMoveResult::pastdest) { SN_StopSequence (m_Sector, CHAN_CEILING); switch (m_Type) { case doorRaise: case doorClose: m_Sector->ceilingdata = nullptr; //jff 2/22/98 Destroy (); // unlink and free break; case doorCloseWaitOpen: m_Direction = 0; m_TopCountdown = m_TopWait; break; default: break; } } else if (res == EMoveResult::crushed) { switch (m_Type) { case doorClose: // DO NOT GO BACK UP! break; default: m_Direction = 1; DoorSound (true); break; } } break; case 1: // UP res = m_Sector->MoveCeiling (m_Speed, m_TopDist, -1, m_Direction, false); // killough 10/98: implement gradual lighting effects if (m_LightTag != 0 && m_TopDist != -m_Sector->floorplane.fD()) { EV_LightTurnOnPartway (m_LightTag, (m_Sector->ceilingplane.fD() + m_Sector->floorplane.fD()) / (m_TopDist + m_Sector->floorplane.fD())); } if (res == EMoveResult::pastdest) { SN_StopSequence (m_Sector, CHAN_CEILING); switch (m_Type) { case doorRaise: m_Direction = 0; // wait at top m_TopCountdown = m_TopWait; break; case doorCloseWaitOpen: case doorOpen: m_Sector->ceilingdata = nullptr; //jff 2/22/98 Destroy (); // unlink and free break; default: break; } } else if (res == EMoveResult::crushed) { switch (m_Type) { case doorRaise: case doorWaitRaise: m_Direction = -1; DoorSound(false); break; default: break; } } break; } } //============================================================================ // // [RH] DoorSound: Plays door sound depending on direction and speed // // If curseq is non-NULL, then it will check if the desired sound sequence // will result in a different command stream than the current one. If not, // then it does nothing. // //============================================================================ void DDoor::DoorSound(bool raise, DSeqNode *curseq) const { int choice; // For multiple-selection sound sequences, the following choices are used: // 0 Opening // 1 Closing // 2 Opening fast // 3 Closing fast choice = !raise; if (m_Sector->Flags & SECF_SILENTMOVE) return; if (m_Speed >= 8) { choice += 2; } if (m_Sector->seqType >= 0) { if (curseq == NULL || !SN_AreModesSame(m_Sector->seqType, SEQ_DOOR, choice, curseq->GetModeNum())) { SN_StartSequence(m_Sector, CHAN_CEILING, m_Sector->seqType, SEQ_DOOR, choice); } } else if (m_Sector->SeqName != NAME_None) { if (curseq == NULL || !SN_AreModesSame(m_Sector->SeqName, choice, curseq->GetModeNum())) { SN_StartSequence(m_Sector, CHAN_CEILING, m_Sector->SeqName, choice); } } else { const char *snd; switch (gameinfo.gametype) { default: /* Doom and Hexen */ snd = "DoorNormal"; break; case GAME_Heretic: snd = "HereticDoor"; break; case GAME_Strife: snd = "DoorSmallMetal"; // Search the front top textures of 2-sided lines on the door sector // for a door sound to use. for (auto line : m_Sector->Lines) { const char *texname; if (line->backsector == NULL) continue; FTexture *tex = TexMan.GetTexture(line->sidedef[0]->GetTexture(side_t::top)); texname = tex ? tex->GetName().GetChars() : NULL; if (texname != NULL && texname[0] == 'D' && texname[1] == 'O' && texname[2] == 'R') { switch (texname[3]) { case 'S': snd = "DoorStone"; break; case 'M': if (texname[4] == 'L') { snd = "DoorLargeMetal"; } break; case 'W': if (texname[4] == 'L') { snd = "DoorLargeWood"; } else { snd = "DoorSmallWood"; } break; } } } break; } if (curseq == NULL || !SN_AreModesSame(snd, choice, curseq->GetModeNum())) { SN_StartSequence(m_Sector, CHAN_CEILING, snd, choice); } } } DDoor::DDoor (sector_t *sector) : DMovingCeiling (sector) { } //============================================================================ // // [RH] SpawnDoor: Helper function for EV_DoDoor // //============================================================================ DDoor::DDoor (sector_t *sec, EVlDoor type, double speed, int delay, int lightTag, int topcountdown) : DMovingCeiling (sec), m_Type (type), m_Speed (speed), m_TopWait (delay), m_TopCountdown(topcountdown), m_LightTag (lightTag) { vertex_t *spot; double height; if (i_compatflags & COMPATF_NODOORLIGHT) { m_LightTag = 0; } switch (type) { case doorClose: m_Direction = -1; height = FindLowestCeilingSurrounding (sec, &spot); m_TopDist = sec->ceilingplane.PointToDist (spot, height - 4); DoorSound (false); break; case doorOpen: case doorRaise: m_Direction = 1; height = FindLowestCeilingSurrounding (sec, &spot); m_TopDist = sec->ceilingplane.PointToDist (spot, height - 4); if (m_TopDist != sec->ceilingplane.fD()) DoorSound (true); break; case doorCloseWaitOpen: m_TopDist = sec->ceilingplane.fD(); m_Direction = -1; DoorSound (false); break; case doorWaitRaise: m_Direction = 2; height = FindLowestCeilingSurrounding (sec, &spot); m_TopDist = sec->ceilingplane.PointToDist (spot, height - 4); break; case doorWaitClose: m_Direction = 0; m_Type = DDoor::doorRaise; height = FindHighestFloorPoint (sec, &m_BotSpot); m_BotDist = sec->ceilingplane.PointToDist (m_BotSpot, height); m_OldFloorDist = sec->floorplane.fD(); m_TopDist = sec->ceilingplane.fD(); break; } if (!m_Sector->floordata || !m_Sector->floordata->IsKindOf(RUNTIME_CLASS(DPlat)) || !(barrier_cast(m_Sector->floordata))->IsLift()) { height = FindHighestFloorPoint (sec, &m_BotSpot); m_BotDist = sec->ceilingplane.PointToDist (m_BotSpot, height); } else { height = FindLowestCeilingPoint(sec, &m_BotSpot); m_BotDist = sec->ceilingplane.PointToDist (m_BotSpot, height); } m_OldFloorDist = sec->floorplane.fD(); } //============================================================================ // // [RH] Merged EV_VerticalDoor and EV_DoLockedDoor into EV_DoDoor // and made them more general to support the new specials. // //============================================================================ bool EV_DoDoor (DDoor::EVlDoor type, line_t *line, AActor *thing, int tag, double speed, int delay, int lock, int lightTag, bool boomgen, int topcountdown) { bool rtn = false; int secnum; sector_t* sec; if (lock != 0 && !P_CheckKeys (thing, lock, tag != 0)) return false; if (tag == 0) { // [RH] manual door if (!line) return false; // if the wrong side of door is pushed, give oof sound if (line->sidedef[1] == NULL) // killough { S_Sound (thing, CHAN_VOICE, "*usefail", 1, ATTN_NORM); return false; } // get the sector on the second side of activating linedef sec = line->sidedef[1]->sector; secnum = sec->sectornum; // if door already has a thinker, use it if (sec->PlaneMoving(sector_t::ceiling)) { // Boom used remote door logic for generalized doors, even if they are manual if (boomgen) return false; if (sec->ceilingdata->IsKindOf (RUNTIME_CLASS(DDoor))) { DDoor *door = barrier_cast(sec->ceilingdata); // ONLY FOR "RAISE" DOORS, NOT "OPEN"s if (door->m_Type == DDoor::doorRaise && type == DDoor::doorRaise) { if (door->m_Direction == -1) { door->m_Direction = 1; // go back up door->DoorSound (true); // [RH] Make noise } else if (!(line->activation & (SPAC_Push|SPAC_MPush))) // [RH] activate push doors don't go back down when you // run into them (otherwise opening them would be // a real pain). { if (!thing->player || thing->player->Bot != NULL) return false; // JDC: bad guys never close doors //Added by MC: Neither do bots. door->m_Direction = -1; // start going down immediately // Start the door close sequence. door->DoorSound(false, SN_CheckSequence(sec, CHAN_CEILING)); return true; } else { return false; } } } return false; } if (Create (sec, type, speed, delay, lightTag, topcountdown)) rtn = true; } else { // [RH] Remote door FSectorTagIterator it(tag); while ((secnum = it.Next()) >= 0) { sec = &level.sectors[secnum]; // if the ceiling is already moving, don't start the door action if (sec->PlaneMoving(sector_t::ceiling)) continue; if (Create(sec, type, speed, delay, lightTag, topcountdown)) rtn = true; } } return rtn; } //============================================================================ // // animated doors // //============================================================================ IMPLEMENT_CLASS(DAnimatedDoor, false, false) DAnimatedDoor::DAnimatedDoor () { } DAnimatedDoor::DAnimatedDoor (sector_t *sec) : DMovingCeiling (sec, false) { } void DAnimatedDoor::Serialize(FSerializer &arc) { Super::Serialize (arc); arc("line1", m_Line1) ("line2", m_Line2) ("frame", m_Frame) ("timer", m_Timer) ("botdist", m_BotDist) ("status", m_Status) ("speed", m_Speed) ("delay", m_Delay) ("dooranim", m_DoorAnim) ("setblock1", m_SetBlocking1) ("setblock2", m_SetBlocking2) ("type", m_Type); } //============================================================================ // // Starts a closing action on an animated door // //============================================================================ bool DAnimatedDoor::StartClosing () { // CAN DOOR CLOSE? if (m_Sector->touching_thinglist != NULL) { return false; } double topdist = m_Sector->ceilingplane.fD(); if (m_Sector->MoveCeiling (2048., m_BotDist, 0, -1, false) == EMoveResult::crushed) { return false; } m_Sector->MoveCeiling (2048., topdist, 1); m_Line1->flags |= ML_BLOCKING; m_Line2->flags |= ML_BLOCKING; if (m_DoorAnim->CloseSound != NAME_None) { SN_StartSequence (m_Sector, CHAN_CEILING, m_DoorAnim->CloseSound, 1); } m_Status = Closing; m_Timer = m_Speed; return true; } //============================================================================ // // // //============================================================================ void DAnimatedDoor::Tick () { if (m_DoorAnim == NULL) { // can only happen when a bad savegame is loaded. Destroy(); return; } switch (m_Status) { case Dead: m_Sector->ceilingdata = nullptr; Destroy (); break; case Opening: if (!m_Timer--) { if (++m_Frame >= m_DoorAnim->NumTextureFrames) { // IF DOOR IS DONE OPENING... m_Line1->flags &= ~ML_BLOCKING; m_Line2->flags &= ~ML_BLOCKING; if (m_Delay == 0) { m_Sector->ceilingdata = nullptr; Destroy (); break; } m_Timer = m_Delay; m_Status = Waiting; } else { // IF DOOR NEEDS TO ANIMATE TO NEXT FRAME... m_Timer = m_Speed; m_Line1->sidedef[0]->SetTexture(side_t::mid, m_DoorAnim->TextureFrames[m_Frame]); m_Line1->sidedef[1]->SetTexture(side_t::mid, m_DoorAnim->TextureFrames[m_Frame]); m_Line2->sidedef[0]->SetTexture(side_t::mid, m_DoorAnim->TextureFrames[m_Frame]); m_Line2->sidedef[1]->SetTexture(side_t::mid, m_DoorAnim->TextureFrames[m_Frame]); } } break; case Waiting: // IF DOOR IS DONE WAITING... if (m_Type == adClose || !m_Timer--) { if (!StartClosing()) { m_Timer = m_Delay; } } break; case Closing: if (!m_Timer--) { if (--m_Frame < 0) { // IF DOOR IS DONE CLOSING... m_Sector->MoveCeiling (2048., m_BotDist, -1); m_Sector->ceilingdata = nullptr; Destroy (); // Unset blocking flags on lines that didn't start with them. Since the // ceiling is down now, we shouldn't need this flag anymore to keep things // from getting through. if (!m_SetBlocking1) { m_Line1->flags &= ~ML_BLOCKING; } if (!m_SetBlocking2) { m_Line2->flags &= ~ML_BLOCKING; } break; } else { // IF DOOR NEEDS TO ANIMATE TO NEXT FRAME... m_Timer = m_Speed; m_Line1->sidedef[0]->SetTexture(side_t::mid, m_DoorAnim->TextureFrames[m_Frame]); m_Line1->sidedef[1]->SetTexture(side_t::mid, m_DoorAnim->TextureFrames[m_Frame]); m_Line2->sidedef[0]->SetTexture(side_t::mid, m_DoorAnim->TextureFrames[m_Frame]); m_Line2->sidedef[1]->SetTexture(side_t::mid, m_DoorAnim->TextureFrames[m_Frame]); } } break; } } //============================================================================ // // // //============================================================================ DAnimatedDoor::DAnimatedDoor (sector_t *sec, line_t *line, int speed, int delay, FDoorAnimation *anim, DAnimatedDoor::EADType type) : DMovingCeiling (sec, false) { double topdist; FTextureID picnum; m_DoorAnim = anim; m_Line1 = line; m_Line2 = line; for (auto l : sec->Lines) { if (l == line) continue; if (l->sidedef[0]->GetTexture(side_t::top) == line->sidedef[0]->GetTexture(side_t::top)) { m_Line2 = l; break; } } auto &tex1 = m_Line1->sidedef[0]->textures; tex1[side_t::mid].InitFrom(tex1[side_t::top]); auto &tex2 = m_Line2->sidedef[0]->textures; tex2[side_t::mid].InitFrom(tex2[side_t::top]); picnum = tex1[side_t::top].texture; FTexture *tex = TexMan.GetTexture(picnum); topdist = tex ? tex->GetDisplayHeight() : 64; topdist = m_Sector->ceilingplane.fD() - topdist * m_Sector->ceilingplane.fC(); m_Type = type; m_Status = type == adClose? Waiting : Opening; m_Speed = speed; m_Delay = delay; m_Timer = m_Speed; m_Frame = 0; m_SetBlocking1 = !!(m_Line1->flags & ML_BLOCKING); m_SetBlocking2 = !!(m_Line2->flags & ML_BLOCKING); m_Line1->flags |= ML_BLOCKING; m_Line2->flags |= ML_BLOCKING; m_BotDist = m_Sector->ceilingplane.fD(); m_Sector->MoveCeiling (2048., topdist, 1); if (type == adOpenClose) { if (m_DoorAnim->OpenSound != NAME_None) { SN_StartSequence(m_Sector, CHAN_INTERIOR, m_DoorAnim->OpenSound, 1); } } } //============================================================================ // // EV_SlidingDoor : slide a door horizontally // (animate midtexture, then set noblocking line) // //============================================================================ bool EV_SlidingDoor (line_t *line, AActor *actor, int tag, int speed, int delay, DAnimatedDoor::EADType type) { sector_t *sec; int secnum; bool rtn; secnum = -1; rtn = false; if (tag == 0) { // Manual sliding door sec = line->backsector; // Make sure door isn't already being animated if (sec->ceilingdata != NULL ) { if (actor->player == NULL) return false; if (sec->ceilingdata->IsA (RUNTIME_CLASS(DAnimatedDoor))) { DAnimatedDoor *door = barrier_cast(sec->ceilingdata); if (door->m_Status == DAnimatedDoor::Waiting) { return door->StartClosing(); } } return false; } FDoorAnimation *anim = TexMan.FindAnimatedDoor (line->sidedef[0]->GetTexture(side_t::top)); if (anim != NULL) { Create(sec, line, speed, delay, anim, type); return true; } return false; } FSectorTagIterator it(tag); while ((secnum = it.Next()) >= 0) { sec = &level.sectors[secnum]; if (sec->ceilingdata != NULL) { continue; } for (auto line : sec->Lines) { if (line->backsector == NULL) { continue; } FDoorAnimation *anim = TexMan.FindAnimatedDoor (line->sidedef[0]->GetTexture(side_t::top)); if (anim != NULL) { rtn = true; Create(sec, line, speed, delay, anim, type); break; } } } return rtn; }