/* ** p_linkedsectors.cpp ** ** Linked sector movement ** **--------------------------------------------------------------------------- ** Copyright 2008 Christoph Oelckers ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions ** are met: ** ** 1. Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** 2. Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** 3. The name of the author may not be used to endorse or promote products ** derived from this software without specific prior written permission. ** ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. **--------------------------------------------------------------------------- ** */ #include "templates.h" #include "p_local.h" #include "p_lnspec.h" #include "p_spec.h" #include "g_levellocals.h" enum { // The different movement types. LINK_NONE=0, LINK_FLOOR=1, LINK_CEILING=2, LINK_BOTH=3, LINK_FLOORMIRRORFLAG=4, LINK_CEILINGMIRRORFLAG=8, LINK_FLOORMIRROR=5, LINK_CEILINGMIRROR=10, LINK_BOTHMIRROR=15, LINK_FLOORBITS=5, LINK_CEILINGBITS=10, LINK_FLAGMASK = 15 }; //============================================================================ // // Checks whether the other sector is linked to this one // Used to ignore linked sectors in the FindNextLowest/Highest* // functions // // NOTE: After looking at Eternity's code I discovered that this check // is not done for the FindNext* function but instead only for // FindLowestCeilingSurrounding where IMO it makes much less sense // because the most frequent use of this feature is most likely lifts // with a more detailed surface. Needs to be investigated! // //============================================================================ bool sector_t::IsLinked(sector_t *other, bool ceiling) const { extsector_t::linked::plane &scrollplane = ceiling? e->Linked.Ceiling : e->Linked.Floor; int flag = ceiling? LINK_CEILING : LINK_FLOOR; for(unsigned i = 0; i < scrollplane.Sectors.Size(); i++) { if (other == scrollplane.Sectors[i].Sector && scrollplane.Sectors[i].Type & flag) return true; } return false; } DEFINE_ACTION_FUNCTION(_Sector, isLinked) { PARAM_SELF_STRUCT_PROLOGUE(sector_t); PARAM_POINTER(other, sector_t); PARAM_BOOL(ceiling); ACTION_RETURN_BOOL(self->IsLinked(other, ceiling)); } //============================================================================ // // Helper functions for P_MoveLinkedSectors // //============================================================================ static bool MoveCeiling(sector_t *sector, int crush, double move, bool instant) { sector->ceilingplane.ChangeHeight (move); sector->ChangePlaneTexZ(sector_t::ceiling, move); if (P_ChangeSector(sector, crush, move, 1, true, instant)) return false; // Don't let the ceiling go below the floor if (!sector->ceilingplane.isSlope() && !sector->floorplane.isSlope() && !sector->PortalIsLinked(sector_t::floor) && sector->GetPlaneTexZ(sector_t::floor) > sector->GetPlaneTexZ(sector_t::ceiling)) return false; return true; } static bool MoveFloor(sector_t *sector, int crush, double move, bool instant) { sector->floorplane.ChangeHeight (move); sector->ChangePlaneTexZ(sector_t::floor, move); if (P_ChangeSector(sector, crush, move, 0, true, instant)) return false; // Don't let the floor go above the ceiling if (!sector->ceilingplane.isSlope() && !sector->floorplane.isSlope() && !sector->PortalIsLinked(sector_t::ceiling) && sector->GetPlaneTexZ(sector_t::floor) > sector->GetPlaneTexZ(sector_t::ceiling)) return false; return true; } //============================================================================ // // P_MoveLinkedSectors // // Moves all floors/ceilings linked to the control sector // Important: All sectors must complete their movement // even if a previous one already failed. // //============================================================================ bool P_MoveLinkedSectors(sector_t *sector, int crush, double move, bool ceiling, bool instant) { extsector_t::linked::plane &scrollplane = ceiling? sector->e->Linked.Ceiling : sector->e->Linked.Floor; bool ok = true; for(unsigned i = 0; i < scrollplane.Sectors.Size(); i++) { switch(scrollplane.Sectors[i].Type) { case LINK_FLOOR: ok &= MoveFloor(scrollplane.Sectors[i].Sector, crush, move, instant); break; case LINK_CEILING: ok &= MoveCeiling(scrollplane.Sectors[i].Sector, crush, move, instant); break; case LINK_BOTH: if (move < 0) { ok &= MoveFloor(scrollplane.Sectors[i].Sector, crush, move, instant); ok &= MoveCeiling(scrollplane.Sectors[i].Sector, crush, move, instant); } else { ok &= MoveCeiling(scrollplane.Sectors[i].Sector, crush, move, instant); ok &= MoveFloor(scrollplane.Sectors[i].Sector, crush, move, instant); } break; case LINK_FLOORMIRROR: ok &= MoveFloor(scrollplane.Sectors[i].Sector, crush, -move, instant); break; case LINK_CEILINGMIRROR: ok &= MoveCeiling(scrollplane.Sectors[i].Sector, crush, -move, instant); break; case LINK_BOTHMIRROR: if (move > 0) { ok &= MoveFloor(scrollplane.Sectors[i].Sector, crush, -move, instant); ok &= MoveCeiling(scrollplane.Sectors[i].Sector, crush, -move, instant); } else { ok &= MoveCeiling(scrollplane.Sectors[i].Sector, crush, -move, instant); ok &= MoveFloor(scrollplane.Sectors[i].Sector, crush, -move, instant); } break; case LINK_FLOOR+LINK_CEILINGMIRROR: ok &= MoveFloor(scrollplane.Sectors[i].Sector, crush, move, instant); ok &= MoveCeiling(scrollplane.Sectors[i].Sector, crush, -move, instant); break; case LINK_CEILING+LINK_FLOORMIRROR: ok &= MoveFloor(scrollplane.Sectors[i].Sector, crush, -move, instant); ok &= MoveCeiling(scrollplane.Sectors[i].Sector, crush, move, instant); break; default: // all other types are invalid and have to be elimintated in the attachment stage break; } } return ok; } //============================================================================ // // P_StartLinkedSectorInterpolations // // Starts interpolators for every sector plane is being changed by moving // this sector // //============================================================================ void P_StartLinkedSectorInterpolations(TArray &list, sector_t *sector, bool ceiling) { extsector_t::linked::plane &scrollplane = ceiling? sector->e->Linked.Ceiling : sector->e->Linked.Floor; for(unsigned i = 0; i < scrollplane.Sectors.Size(); i++) { if (scrollplane.Sectors[i].Type & LINK_FLOOR) { list.Push(scrollplane.Sectors[i].Sector->SetInterpolation(sector_t::FloorMove, false)); } if (scrollplane.Sectors[i].Type & LINK_CEILING) { list.Push(scrollplane.Sectors[i].Sector->SetInterpolation(sector_t::CeilingMove, false)); } } } //============================================================================ // // AddSingleSector // // Adds a single sector to a scroll plane. Checks for invalid // flag combinations if the sector is already added and removes it if necessary. // //============================================================================ static void AddSingleSector(extsector_t::linked::plane &scrollplane, sector_t *sector, int movetype) { // First we have to check the list if the sector is already in it // If so the move type must be adjusted for(unsigned i = 0; i < scrollplane.Sectors.Size(); i++) { if (scrollplane.Sectors[i].Sector == sector) { if (movetype & LINK_FLOORBITS) { scrollplane.Sectors[i].Type &= ~LINK_FLOORBITS; } if (movetype & LINK_CEILINGBITS) { scrollplane.Sectors[i].Type &= ~LINK_CEILINGBITS; } scrollplane.Sectors[i].Type |= movetype; return; } } // The sector hasn't been attached yet so do it now FLinkedSector newlink = {sector, movetype}; scrollplane.Sectors.Push(newlink); } //============================================================================ // // RemoveTaggedSectors // // Remove all sectors with the given tag from the link list // //============================================================================ static void RemoveTaggedSectors(extsector_t::linked::plane &scrollplane, int tag) { for(int i = scrollplane.Sectors.Size()-1; i>=0; i--) { if (tagManager.SectorHasTag(scrollplane.Sectors[i].Sector, tag)) { scrollplane.Sectors.Delete(i); } } } //============================================================================ // // P_AddSectorLink // // Links sector planes to a control sector based on the sector's tag // //============================================================================ bool P_AddSectorLinks(sector_t *control, int tag, INTBOOL ceiling, int movetype) { int param = movetype; // can't be done if the control sector is moving. if ((ceiling && control->PlaneMoving(sector_t::ceiling)) || (!ceiling && control->PlaneMoving(sector_t::floor))) return false; // Make sure we have only valid combinations movetype &= LINK_FLAGMASK; if ((movetype & LINK_FLOORMIRROR) == LINK_FLOORMIRRORFLAG) movetype &= ~LINK_FLOORMIRRORFLAG; if ((movetype & LINK_CEILINGMIRROR) == LINK_CEILINGMIRRORFLAG) movetype &= ~LINK_CEILINGMIRRORFLAG; // Don't remove any sector if the parameter is invalid. // Addition may still be performed based on the given value. if (param != 0 && movetype == 0) return false; extsector_t::linked::plane &scrollplane = ceiling? control->e->Linked.Ceiling : control->e->Linked.Floor; if (movetype > 0) { int sec; FSectorTagIterator itr(tag); while ((sec = itr.Next()) >= 0) { // Don't attach to self (but allow attaching to this sector's oposite plane. if (control == &level.sectors[sec]) { if (ceiling == sector_t::floor && movetype & LINK_FLOOR) continue; if (ceiling == sector_t::ceiling && movetype & LINK_CEILING) continue; } AddSingleSector(scrollplane, &level.sectors[sec], movetype); } } else { RemoveTaggedSectors(scrollplane, tag); } return true; } //============================================================================ // // P_AddSectorLinksByID // // Links sector planes to a control sector based on a control linedef // touching the sectors. This is the method Eternity uses and is here // mostly so that the Eternity line types can be emulated // //============================================================================ void P_AddSectorLinksByID(sector_t *control, int id, INTBOOL ceiling) { extsector_t::linked::plane &scrollplane = ceiling? control->e->Linked.Ceiling : control->e->Linked.Floor; FLineIdIterator itr(id); int line; while ((line = itr.Next()) >= 0) { line_t *ld = &level.lines[line]; if (ld->special == Static_Init && ld->args[1] == Init_SectorLink) { int movetype = ld->args[3]; // [GZ] Eternity does allow the attached sector to be the control sector, // this permits elevator effects (ceiling attached to floors), so instead // of checking whether the two sectors are the same, we prevent a plane // from being attached to itself. This should be enough to do the trick. if (ld->frontsector == control) { if (ceiling) movetype &= ~LINK_CEILING; else movetype &= ~LINK_FLOOR; } // Make sure we have only valid combinations movetype &= LINK_FLAGMASK; if ((movetype & LINK_FLOORMIRROR) == LINK_FLOORMIRRORFLAG) movetype &= ~LINK_FLOORMIRRORFLAG; if ((movetype & LINK_CEILINGMIRROR) == LINK_CEILINGMIRRORFLAG) movetype &= ~LINK_CEILINGMIRRORFLAG; if (movetype != 0 && ld->frontsector != NULL)//&& ld->frontsector != control) Needs to be allowed! { AddSingleSector(scrollplane, ld->frontsector, movetype); } } } }