//----------------------------------------------------------------------------- // // 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: // Implements special effects: // Texture animation, height or lighting changes // according to adjacent sectors, respective // utility functions, etc. // Line Tag handling. Line and Sector triggers. // Implements donut linedef triggers // Initializes and implements BOOM linedef triggers for // Scrollers/Conveyors // Friction // Wind/Current // //----------------------------------------------------------------------------- /* For code that originates from ZDoom the following applies: ** **--------------------------------------------------------------------------- ** ** 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 #include "templates.h" #include "doomdef.h" #include "doomstat.h" #include "d_event.h" #include "g_level.h" #include "gstrings.h" #include "events.h" #include "m_random.h" #include "p_local.h" #include "p_spec.h" #include "p_blockmap.h" #include "p_lnspec.h" #include "p_terrain.h" #include "p_acs.h" #include "p_3dmidtex.h" #include "g_game.h" #include "a_sharedglobal.h" #include "a_keys.h" #include "c_dispatch.h" #include "r_sky.h" #include "d_player.h" #include "g_levellocals.h" #include "actorinlines.h" #include "vm.h" #include "p_setup.h" #include "c_console.h" #include "p_spec_thinkers.h" #include "maploader/maploader.h" EXTERN_CVAR(Bool, forcewater) //----------------------------------------------------------------------------- // // Portals // //----------------------------------------------------------------------------- //============================================================================ // // Spawns a single line portal // //============================================================================ void MapLoader::SpawnLinePortal(line_t* line) { // portal destination is special argument #0 line_t* dst = nullptr; if (line->args[2] >= PORTT_VISUAL && line->args[2] <= PORTT_LINKED) { dst = Level->FindPortalDestination(line, line->args[0]); line->portalindex = Level->linePortals.Reserve(1); FLinePortal *port = &Level->linePortals.Last(); memset(port, 0, sizeof(FLinePortal)); port->mOrigin = line; port->mDestination = dst; port->mType = uint8_t(line->args[2]); // range check is done above. if (port->mType == PORTT_LINKED) { // Linked portals have no z-offset ever. port->mAlign = PORG_ABSOLUTE; } else { port->mAlign = uint8_t(line->args[3] >= PORG_ABSOLUTE && line->args[3] <= PORG_CEILING ? line->args[3] : PORG_ABSOLUTE); if (port->mType == PORTT_INTERACTIVE && port->mAlign != PORG_ABSOLUTE) { // Due to the way z is often handled, these pose a major issue for parts of the code that needs to transparently handle interactive portals. Printf(TEXTCOLOR_RED "Warning: z-offsetting not allowed for interactive portals. Changing line %d to teleport-portal!\n", line->Index()); port->mType = PORTT_TELEPORT; } } port->mDefFlags = port->mType == PORTT_VISUAL ? PORTF_VISIBLE : port->mType == PORTT_TELEPORT ? PORTF_TYPETELEPORT : PORTF_TYPEINTERACTIVE; } else if (line->args[2] == PORTT_LINKEDEE && line->args[0] == 0) { // EE-style portals require that the first line ID is identical and the first arg of the two linked linedefs are 0 and 1 respectively. int mytag = Level->GetFirstLineId(line); for (auto &ln : Level->lines) { if (Level->GetFirstLineId(&ln) == mytag && ln.args[0] == 1 && ln.special == Line_SetPortal) { line->portalindex = Level->linePortals.Reserve(1); FLinePortal *port = &Level->linePortals.Last(); memset(port, 0, sizeof(FLinePortal)); port->mOrigin = line; port->mDestination = &ln; port->mType = PORTT_LINKED; port->mAlign = PORG_ABSOLUTE; port->mDefFlags = PORTF_TYPEINTERACTIVE; // we need to create the backlink here, too. ln.portalindex = Level->linePortals.Reserve(1); port = &Level->linePortals.Last(); memset(port, 0, sizeof(FLinePortal)); port->mOrigin = &ln; port->mDestination = line; port->mType = PORTT_LINKED; port->mAlign = PORG_ABSOLUTE; port->mDefFlags = PORTF_TYPEINTERACTIVE; } } } else { // undefined type return; } } //----------------------------------------------------------------------------- // // Lower stacks go in the bottom sector. // //----------------------------------------------------------------------------- void MapLoader::SetupFloorPortal (AActor *point) { auto it = Level->GetActorIterator(NAME_LowerStackLookOnly, point->tid); sector_t *Sector = point->Sector; auto skyv = it.Next(); if (skyv != nullptr) { skyv->target = point; if (Sector->GetAlpha(sector_t::floor) == 1.) Sector->SetAlpha(sector_t::floor, clamp(point->args[0], 0, 255) / 255.); Sector->Portals[sector_t::floor] = Level->GetStackPortal(skyv, sector_t::floor); } } //----------------------------------------------------------------------------- // // Upper stacks go in the top sector. // //----------------------------------------------------------------------------- void MapLoader::SetupCeilingPortal (AActor *point) { auto it = Level->GetActorIterator(NAME_UpperStackLookOnly, point->tid); sector_t *Sector = point->Sector; auto skyv = it.Next(); if (skyv != nullptr) { skyv->target = point; if (Sector->GetAlpha(sector_t::ceiling) == 1.) Sector->SetAlpha(sector_t::ceiling, clamp(point->args[0], 0, 255) / 255.); Sector->Portals[sector_t::ceiling] = Level->GetStackPortal(skyv, sector_t::ceiling); } } //----------------------------------------------------------------------------- // // // //----------------------------------------------------------------------------- void MapLoader::SetupPortals() { auto it = Level->GetThinkerIterator(NAME_StackPoint); AActor *pt; TArray points; while ((pt = it.Next())) { FName nm = pt->GetClass()->TypeName; if (nm == NAME_UpperStackLookOnly) { SetupFloorPortal(pt); } else if (nm == NAME_LowerStackLookOnly) { SetupCeilingPortal(pt); } pt->special1 = 0; points.Push(pt); } // the semantics here are incredibly lax so the final setup can only be done once all portals have been created, // because later stackpoints will happily overwrite info in older ones, if there are multiple links. for (auto &s : Level->sectorPortals) { if (s.mType == PORTS_STACKEDSECTORTHING && s.mSkybox) { for (auto &ss : Level->sectorPortals) { if (ss.mType == PORTS_STACKEDSECTORTHING && ss.mSkybox == s.mSkybox->target) { s.mPartner = unsigned((&ss) - &Level->sectorPortals[0]); } } } } // Now we can finally set the displacement and delete the stackpoint reference. for (auto &s : Level->sectorPortals) { if (s.mType == PORTS_STACKEDSECTORTHING && s.mSkybox) { s.mDisplacement = s.mSkybox->Pos() - s.mSkybox->target->Pos(); s.mSkybox = nullptr; } } } //----------------------------------------------------------------------------- // // // //----------------------------------------------------------------------------- void MapLoader::SetPortal(sector_t *sector, int plane, unsigned pnum, double alpha) { // plane: 0=floor, 1=ceiling, 2=both if (plane > 0) { if (sector->GetPortalType(sector_t::ceiling) == PORTS_SKYVIEWPOINT) { sector->Portals[sector_t::ceiling] = pnum; if (sector->GetAlpha(sector_t::ceiling) == 1.) sector->SetAlpha(sector_t::ceiling, alpha); if (Level->sectorPortals[pnum].mFlags & PORTSF_SKYFLATONLY) sector->SetTexture(sector_t::ceiling, skyflatnum); } } if (plane == 2 || plane == 0) { if (sector->GetPortalType(sector_t::floor) == PORTS_SKYVIEWPOINT) { sector->Portals[sector_t::floor] = pnum; } if (sector->GetAlpha(sector_t::floor) == 1.) sector->SetAlpha(sector_t::floor, alpha); if (Level->sectorPortals[pnum].mFlags & PORTSF_SKYFLATONLY) sector->SetTexture(sector_t::floor, skyflatnum); } } //----------------------------------------------------------------------------- // // // //----------------------------------------------------------------------------- void MapLoader::CopyPortal(int sectortag, int plane, unsigned pnum, double alpha, bool tolines) { int s; auto itr = Level->GetSectorTagIterator(sectortag); while ((s = itr.Next()) >= 0) { SetPortal(&Level->sectors[s], plane, pnum, alpha); } for (auto &line : Level->lines) { // Check if this portal needs to be copied to other sectors // This must be done here to ensure that it gets done only after the portal is set up if (line.special == Sector_SetPortal && line.args[1] == 1 && (line.args[2] == plane || line.args[2] == 3) && line.args[3] == sectortag) { if (line.args[0] == 0) { SetPortal(line.frontsector, plane, pnum, alpha); } else { auto itr = Level->GetSectorTagIterator(line.args[0]); while ((s = itr.Next()) >= 0) { SetPortal(&Level->sectors[s], plane, pnum, alpha); } } } if (tolines && line.special == Sector_SetPortal && line.args[1] == 5 && line.args[3] == sectortag) { if (line.args[0] == 0) { line.portaltransferred = pnum; } else { auto itr = Level->GetLineIdIterator(line.args[0]); while ((s = itr.Next()) >= 0) { Level->lines[s].portaltransferred = pnum; } } } } } //----------------------------------------------------------------------------- // // // //----------------------------------------------------------------------------- void MapLoader::SpawnPortal(line_t *line, int sectortag, int plane, int bytealpha, int linked) { if (plane < 0 || plane > 2 || (linked && plane == 2)) return; for (auto &oline : Level->lines) { // We must look for the reference line with a linear search unless we want to waste the line ID for it // which is not a good idea. if (oline.special == Sector_SetPortal && oline.args[0] == sectortag && oline.args[1] == linked && oline.args[2] == plane && oline.args[3] == 1) { // beware of overflows. DVector2 pos1 = line->v1->fPos() + line->Delta() / 2; DVector2 pos2 = oline.v1->fPos() + oline.Delta() / 2; unsigned pnum = Level->GetPortal(linked ? PORTS_LINKEDPORTAL : PORTS_PORTAL, plane, line->frontsector, oline.frontsector, pos2 - pos1); CopyPortal(sectortag, plane, pnum, bytealpha / 255., false); return; } } } //----------------------------------------------------------------------------- // // This searches the viewpoint's sector // for a skybox line special, gets its tag and transfers the skybox to all tagged sectors. // //----------------------------------------------------------------------------- void MapLoader::SpawnSkybox(AActor *origin) { sector_t *Sector = origin->Sector; if (Sector == NULL) { Printf("Sector not initialized for SkyCamCompat\n"); origin->Sector = Sector = Level->PointInSector(origin->Pos()); } if (Sector) { for(auto refline : Sector->Lines) { if (refline->special == Sector_SetPortal && refline->args[1] == 2) { // We found the setup linedef for this skybox, so let's use it for our init. unsigned pnum = Level->GetSkyboxPortal(origin); CopyPortal(refline->args[0], refline->args[2], pnum, 0, true); return; } } } } //----------------------------------------------------------------------------- // // P_SetSectorDamage // // Sets damage properties for one sector. Allows combination of original specials with explicit use of the damage properties // //----------------------------------------------------------------------------- static void SetupSectorDamage(sector_t *sector, int damage, int interval, int leakchance, FName type, int flags) { // Only set if damage is not yet initialized. This ensures that UDMF takes precedence over sector specials. if (sector->damageamount == 0) { sector->damageamount = damage; sector->damageinterval = MAX(1, interval); sector->leakydamage = leakchance; sector->damagetype = type; sector->Flags = (sector->Flags & ~SECF_DAMAGEFLAGS) | (flags & SECF_DAMAGEFLAGS); } } //----------------------------------------------------------------------------- // // P_InitSectorSpecial // // Sets up everything derived from 'sector->special' for one sector // ('fromload' is necessary to allow conversion upon savegame load.) // //----------------------------------------------------------------------------- void MapLoader::InitSectorSpecial(sector_t *sector, int special) { // [RH] All secret sectors are marked with a BOOM-ish bitfield if (sector->special & SECRET_MASK) { sector->Flags |= SECF_SECRET | SECF_WASSECRET; Level->total_secrets++; } if (sector->special & FRICTION_MASK) { sector->Flags |= SECF_FRICTION; } if (sector->special & PUSH_MASK) { sector->Flags |= SECF_PUSH; } if ((sector->special & DAMAGE_MASK) == 0x100) { SetupSectorDamage(sector, 5, 32, 0, NAME_Fire, 0); } else if ((sector->special & DAMAGE_MASK) == 0x200) { SetupSectorDamage(sector, 10, 32, 0, NAME_Slime, 0); } else if ((sector->special & DAMAGE_MASK) == 0x300) { SetupSectorDamage(sector, 20, 32, 5, NAME_Slime, 0); } sector->special &= 0xff; // [RH] Normal DOOM special or BOOM specialized? bool keepspecial = false; SpawnLights(sector); switch (sector->special) { case dLight_Strobe_Hurt: SetupSectorDamage(sector, 20, 32, 5, NAME_Slime, 0); break; case dDamage_Hellslime: SetupSectorDamage(sector, 10, 32, 0, NAME_Slime, 0); break; case dDamage_Nukage: SetupSectorDamage(sector, 5, 32, 0, NAME_Slime, 0); break; case dSector_DoorCloseIn30: Level->CreateThinker(sector, DDoor::doorWaitClose, 2, 0, 0, 30 * TICRATE); break; case dDamage_End: SetupSectorDamage(sector, 20, 32, 256, NAME_None, SECF_ENDGODMODE|SECF_ENDLEVEL); break; case dSector_DoorRaiseIn5Mins: Level->CreateThinker (sector, DDoor::doorWaitRaise, 2, TICRATE*30/7, 0, 5*60*TICRATE); break; case dFriction_Low: sector->friction = FRICTION_LOW; sector->movefactor = 0x269/65536.; sector->Flags |= SECF_FRICTION; break; case dDamage_SuperHellslime: SetupSectorDamage(sector, 20, 32, 5, NAME_Slime, 0); break; case dDamage_LavaWimpy: SetupSectorDamage(sector, 5, 16, 256, NAME_Fire, SECF_DMGTERRAINFX); break; case dDamage_LavaHefty: SetupSectorDamage(sector, 8, 16, 256, NAME_Fire, SECF_DMGTERRAINFX); break; case dScroll_EastLavaDamage: SetupSectorDamage(sector, 5, 16, 256, NAME_Fire, SECF_DMGTERRAINFX); CreateScroller(EScroll::sc_floor, -4., 0, sector, 0); keepspecial = true; break; case hDamage_Sludge: SetupSectorDamage(sector, 4, 32, 0, NAME_Slime, 0); break; case sLight_Strobe_Hurt: SetupSectorDamage(sector, 5, 32, 0, NAME_Slime, 0); break; case sDamage_Hellslime: SetupSectorDamage(sector, 2, 32, 0, NAME_Slime, SECF_HAZARD); break; case Damage_InstantDeath: // Strife's instant death sector SetupSectorDamage(sector, TELEFRAG_DAMAGE, 1, 256, NAME_InstantDeath, 0); break; case sDamage_SuperHellslime: SetupSectorDamage(sector, 4, 32, 0, NAME_Slime, SECF_HAZARD); break; case Sector_Hidden: sector->MoreFlags |= SECMF_HIDDEN; break; case Sector_Heal: // CoD's healing sector SetupSectorDamage(sector, -1, 32, 0, NAME_None, 0); break; case Sky2: sector->sky = PL_SKYFLAT; break; default: if (sector->special >= Scroll_North_Slow && sector->special <= Scroll_SouthWest_Fast) { // Hexen scroll special static const int8_t hexenScrollies[24][2] = { { 0, 1 }, { 0, 2 }, { 0, 4 }, { -1, 0 }, { -2, 0 }, { -4, 0 }, { 0, -1 }, { 0, -2 }, { 0, -4 }, { 1, 0 }, { 2, 0 }, { 4, 0 }, { 1, 1 }, { 2, 2 }, { 4, 4 }, { -1, 1 }, { -2, 2 }, { -4, 4 }, { -1, -1 }, { -2, -2 }, { -4, -4 }, { 1, -1 }, { 2, -2 }, { 4, -4 } }; int i = sector->special - Scroll_North_Slow; double dx = hexenScrollies[i][0] / 2.; double dy = hexenScrollies[i][1] / 2.; CreateScroller(EScroll::sc_floor, dx, dy, sector, 0); } else if (sector->special >= Carry_East5 && sector->special <= Carry_East35) { // Heretic scroll special // Only east scrollers also scroll the texture CreateScroller(EScroll::sc_floor, -0.5 * (1 << ((sector->special & 0xff) - Carry_East5)), 0, sector, 0); } keepspecial = true; break; } if (!keepspecial) sector->special = 0; } //----------------------------------------------------------------------------- // // P_SpawnSpecials // // After the map has been loaded, scan for specials that spawn thinkers // //----------------------------------------------------------------------------- void MapLoader::SpawnSpecials () { SetupPortals(); for (auto &sec : Level->sectors) { if (sec.special == 0) continue; InitSectorSpecial(&sec, sec.special); } ProcessEDSectors(); // Init other misc stuff SpawnScrollers(); // killough 3/7/98: Add generalized scrollers SpawnFriction(); // phares 3/12/98: New friction model using linedefs SpawnPushers(); // phares 3/20/98: New pusher model using linedefs auto it2 = Level->GetThinkerIterator(NAME_SkyCamCompat); AActor *pt2; while ((pt2 = it2.Next())) { SpawnSkybox(pt2); } for (auto &line : Level->lines) { switch (line.special) { int s; sector_t *sec; // killough 3/7/98: // support for drawn heights coming from different sector case Transfer_Heights: { sec = line.frontsector; if (line.args[1] & 2) { sec->MoreFlags |= SECMF_FAKEFLOORONLY; } if (line.args[1] & 4) { sec->MoreFlags |= SECMF_CLIPFAKEPLANES; } if (line.args[1] & 8) { sec->MoreFlags |= SECMF_UNDERWATER; } else if (forcewater) { sec->MoreFlags |= SECMF_FORCEDUNDERWATER; } if (line.args[1] & 16) { sec->MoreFlags |= SECMF_IGNOREHEIGHTSEC; } else Level->HasHeightSecs = true; if (line.args[1] & 32) { sec->MoreFlags |= SECMF_NOFAKELIGHT; } auto itr = Level->GetSectorTagIterator(line.args[0]); while ((s = itr.Next()) >= 0) { Level->sectors[s].heightsec = sec; sec->e->FakeFloor.Sectors.Push(&Level->sectors[s]); Level->sectors[s].MoreFlags |= (sec->MoreFlags & SECMF_IGNOREHEIGHTSEC); // copy this to the destination sector for easier checking. Level->sectors[s].AdjustFloorClip(); } break; } // killough 3/16/98: Add support for setting // floor lighting independently (e.g. lava) case Transfer_FloorLight: Level->CreateThinker (line.frontsector, line.args[0], true); break; // killough 4/11/98: Add support for setting // ceiling lighting independently case Transfer_CeilingLight: Level->CreateThinker (line.frontsector, line.args[0], false); break; // [Graf Zahl] Add support for setting lighting // per wall independently case Transfer_WallLight: Level->CreateThinker (line.frontsector, line.args[0], line.args[1]); break; case Sector_Attach3dMidtex: P_Attach3dMidtexLinesToSector(line.frontsector, line.args[0], line.args[1], !!line.args[2]); break; case Sector_SetLink: if (line.args[0] == 0) { P_AddSectorLinks(line.frontsector, line.args[1], line.args[2], line.args[3]); } break; case Sector_SetPortal: // arg 0 = sector tag // arg 1 = type // - 0: normal (handled here) // - 1: copy (handled by the portal they copy) // - 2: EE-style skybox (handled by the camera object) // - 3: EE-style flat portal (GZDoom HW renderer only for now) // - 4: EE-style horizon portal (GZDoom HW renderer only for now) // - 5: copy portal to line (GZDoom HW renderer only for now) // - 6: linked portal // other values reserved for later use // arg 2 = 0:floor, 1:ceiling, 2:both // arg 3 = 0: anchor, 1: reference line // arg 4 = for the anchor only: alpha if ((line.args[1] == 0 || line.args[1] == 6) && line.args[3] == 0) { SpawnPortal(&line, line.args[0], line.args[2], line.args[4], line.args[1]); } else if (line.args[1] == 3 || line.args[1] == 4) { unsigned pnum = Level->GetPortal(line.args[1] == 3 ? PORTS_PLANE : PORTS_HORIZON, line.args[2], line.frontsector, NULL, { 0,0 }); CopyPortal(line.args[0], line.args[2], pnum, 0, true); } break; case Line_SetPortal: SpawnLinePortal(&line); break; // [RH] ZDoom Static_Init settings case Static_Init: switch (line.args[1]) { case Init_Gravity: { double grav = line.Delta().Length() / 100.; auto itr = Level->GetSectorTagIterator(line.args[0]); while ((s = itr.Next()) >= 0) Level->sectors[s].gravity = grav; } break; //case Init_Color: // handled in P_LoadSideDefs2() case Init_Damage: { int damage = int(line.Delta().Length()); auto itr = Level->GetSectorTagIterator(line.args[0]); while ((s = itr.Next()) >= 0) { sector_t *sec = &Level->sectors[s]; sec->damageamount = damage; sec->damagetype = NAME_None; if (sec->damageamount < 20) { sec->leakydamage = 0; sec->damageinterval = 32; } else if (sec->damageamount < 50) { sec->leakydamage = 5; sec->damageinterval = 32; } else { sec->leakydamage = 256; sec->damageinterval = 1; } } } break; case Init_SectorLink: if (line.args[3] == 0) P_AddSectorLinksByID(line.frontsector, line.args[0], line.args[2]); break; // killough 10/98: // // Support for sky textures being transferred from sidedefs. // Allows scrolling and other effects (but if scrolling is // used, then the same sector tag needs to be used for the // sky sector, the sky-transfer linedef, and the scroll-effect // linedef). Still requires user to use F_SKY1 for the floor // or ceiling texture, to distinguish floor and ceiling sky. case Init_TransferSky: { auto itr = Level->GetSectorTagIterator(line.args[0]); while ((s = itr.Next()) >= 0) Level->sectors[s].sky = (line.Index() + 1) | PL_SKYFLAT; break; } } break; } } // [RH] Start running any open scripts on this map Level->Behaviors.StartTypedScripts (SCRIPT_Open, NULL, false); } //----------------------------------------------------------------------------- // // // //----------------------------------------------------------------------------- void MapLoader::SpawnFriction() { line_t *l = &Level->lines[0]; for (unsigned i = 0 ; i < Level->lines.Size() ; i++,l++) { if (l->special == Sector_SetFriction) { int length; if (l->args[1]) { // [RH] Allow setting friction amount from parameter length = l->args[1] <= 200 ? l->args[1] : 200; } else { length = int(l->Delta().Length()); } P_SetSectorFriction (Level, l->args[0], length, false); l->special = 0; } } } //========================================================================== // // Creates all 3D floors defined by one linedef // //========================================================================== int MapLoader::Set3DFloor(line_t * line, int param, int param2, int alpha) { int s; int flags; int tag = line->args[0]; sector_t * sec = line->frontsector, *ss; auto itr = Level->GetSectorTagIterator(tag); while ((s = itr.Next()) >= 0) { ss = &Level->sectors[s]; if (param == 0) { flags = FF_EXISTS | FF_RENDERALL | FF_SOLID | FF_INVERTSECTOR; alpha = 255; for (auto l: sec->Lines) { if (l->special == Sector_SetContents && l->frontsector == sec) { alpha = clamp(l->args[1], 0, 100); if (l->args[2] & 1) flags &= ~FF_SOLID; if (l->args[2] & 2) flags |= FF_SEETHROUGH; if (l->args[2] & 4) flags |= FF_SHOOTTHROUGH; if (l->args[2] & 8) flags |= FF_ADDITIVETRANS; if (alpha != 100) flags |= FF_TRANSLUCENT;//|FF_BOTHPLANES|FF_ALLSIDES; if (l->args[0]) { // Yes, Vavoom's 3D-floor definitions suck! // The content list changed in r1783 of Vavoom to be unified // among all its supported games, so it has now ten different // values instead of just five. static uint32_t vavoomcolors[] = { VC_EMPTY, VC_WATER, VC_LAVA, VC_NUKAGE, VC_SLIME, VC_HELLSLIME, VC_BLOOD, VC_SLUDGE, VC_HAZARD, VC_BOOMWATER }; flags |= FF_SWIMMABLE | FF_BOTHPLANES | FF_ALLSIDES | FF_FLOOD; l->frontsector->Colormap.FadeColor = vavoomcolors[l->args[0]] & VC_COLORMASK; l->frontsector->Colormap.FogDensity = 0; } alpha = (alpha * 255) / 100; break; } } } else if (param == 4) { flags = FF_EXISTS | FF_RENDERPLANES | FF_INVERTPLANES | FF_NOSHADE | FF_FIX; if (param2 & 1) flags |= FF_SEETHROUGH; // marker for allowing missing texture checks alpha = 255; } else { static const int defflags[] = { 0, FF_SOLID, FF_SWIMMABLE | FF_BOTHPLANES | FF_ALLSIDES | FF_SHOOTTHROUGH | FF_SEETHROUGH, FF_SHOOTTHROUGH | FF_SEETHROUGH, }; flags = defflags[param & 3] | FF_EXISTS | FF_RENDERALL; if (param & 4) flags |= FF_ALLSIDES | FF_BOTHPLANES; if (param & 16) flags ^= FF_SEETHROUGH; if (param & 32) flags ^= FF_SHOOTTHROUGH; if (param2 & 1) flags |= FF_NOSHADE; if (param2 & 2) flags |= FF_DOUBLESHADOW; if (param2 & 4) flags |= FF_FOG; if (param2 & 8) flags |= FF_THINFLOOR; if (param2 & 16) flags |= FF_UPPERTEXTURE; if (param2 & 32) flags |= FF_LOWERTEXTURE; if (param2 & 64) flags |= FF_ADDITIVETRANS | FF_TRANSLUCENT; // if flooding is used the floor must be non-solid and is automatically made shootthrough and seethrough if ((param2 & 128) && !(flags & FF_SOLID)) flags |= FF_FLOOD | FF_SEETHROUGH | FF_SHOOTTHROUGH; if (param2 & 512) flags |= FF_FADEWALLS; if (param2&1024) flags |= FF_RESET; FTextureID tex = line->sidedef[0]->GetTexture(side_t::top); if (!tex.Exists() && alpha < 255) { alpha = -tex.GetIndex(); } alpha = clamp(alpha, 0, 255); if (alpha == 0) flags &= ~(FF_RENDERALL | FF_BOTHPLANES | FF_ALLSIDES); else if (alpha != 255) flags |= FF_TRANSLUCENT; } P_Add3DFloor(ss, sec, line, flags, alpha); } // To be 100% safe this should be done even if the alpha by texture value isn't used. if (!line->sidedef[0]->GetTexture(side_t::top).isValid()) line->sidedef[0]->SetTexture(side_t::top, FNullTextureID()); return 1; } //========================================================================== // // Spawns 3D floors // //========================================================================== void MapLoader::Spawn3DFloors () { static int flagvals[] = {512, 2+512, 512+1024}; for (auto &line : Level->lines) { switch(line.special) { case ExtraFloor_LightOnly: if (line.args[1] < 0 || line.args[1] > 2) line.args[1] = 0; if (line.args[0] != 0) Set3DFloor(&line, 3, flagvals[line.args[1]], 0); break; case Sector_Set3DFloor: // The flag high-byte/line id is only needed in Hexen format. // UDMF can set both of these parameters without any restriction of the usable values. // In Doom format the translators can take full integers for the tag and the line ID always is the same as the tag. if (Level->maptype == MAPTYPE_HEXEN) { if (line.args[1]&8) { Level->tagManager.AddLineID(line.Index(), line.args[4]); } else { line.args[0]+=256*line.args[4]; line.args[4]=0; } } Set3DFloor(&line, line.args[1]&~8, line.args[2], line.args[3]); break; default: continue; } line.special=0; line.args[0] = line.args[1] = line.args[2] = line.args[3] = line.args[4] = 0; } for (auto &sec : Level->sectors) { P_Recalculate3DFloors(&sec); } } //========================================================================== // // Spawns light effects // //========================================================================== void MapLoader::SpawnLights(sector_t *sector) { const int STROBEBRIGHT = 5; const int FASTDARK = 15; const int SLOWDARK = TICRATE; switch (sector->special) { case Light_Phased: Level->CreateThinker(sector, 48, 63 - (sector->lightlevel & 63)); break; // [RH] Hexen-like phased lighting case LightSequenceStart: Level->CreateThinker(sector)->Propagate(); break; case dLight_Flicker: Level->CreateThinker(sector); break; case dLight_StrobeFast: Level->CreateThinker(sector, STROBEBRIGHT, FASTDARK, false); break; case dLight_StrobeSlow: Level->CreateThinker(sector, STROBEBRIGHT, SLOWDARK, false); break; case dLight_Strobe_Hurt: Level->CreateThinker(sector, STROBEBRIGHT, FASTDARK, false); break; case dLight_Glow: Level->CreateThinker(sector); break; case dLight_StrobeSlowSync: Level->CreateThinker(sector, STROBEBRIGHT, SLOWDARK, true); break; case dLight_StrobeFastSync: Level->CreateThinker(sector, STROBEBRIGHT, FASTDARK, true); break; case dLight_FireFlicker: Level->CreateThinker(sector); break; case dScroll_EastLavaDamage: Level->CreateThinker(sector, STROBEBRIGHT, FASTDARK, false); break; case sLight_Strobe_Hurt: Level->CreateThinker(sector, STROBEBRIGHT, FASTDARK, false); break; default: break; } } ///////////////////////////// // // P_GetPushThing() returns a pointer to an MT_PUSH or MT_PULL thing, // NULL otherwise. AActor *MapLoader::GetPushThing(int s) { AActor* thing; sector_t* sec; sec = &Level->sectors[s]; thing = sec->thinglist; while (thing && thing->GetClass()->TypeName != NAME_PointPusher && thing->GetClass()->TypeName != NAME_PointPuller) { thing = thing->snext; } return thing; } ///////////////////////////// // // Initialize the sectors where pushers are present // void MapLoader::SpawnPushers() { line_t *l = &Level->lines[0]; int s; for (unsigned i = 0; i < Level->lines.Size(); i++, l++) { switch (l->special) { case Sector_SetWind: // wind { auto itr = Level->GetSectorTagIterator(l->args[0]); while ((s = itr.Next()) >= 0) Level->CreateThinker(DPusher::p_wind, l->args[3] ? l : nullptr, l->args[1], l->args[2], nullptr, s); l->special = 0; break; } case Sector_SetCurrent: // current { auto itr = Level->GetSectorTagIterator(l->args[0]); while ((s = itr.Next()) >= 0) Level->CreateThinker(DPusher::p_current, l->args[3] ? l : nullptr, l->args[1], l->args[2], nullptr, s); l->special = 0; break; } case PointPush_SetForce: // push/pull if (l->args[0]) { // [RH] Find thing by sector auto itr = Level->GetSectorTagIterator(l->args[0]); while ((s = itr.Next()) >= 0) { AActor *thing = GetPushThing(s); if (thing) { // No MT_P* means no effect // [RH] Allow narrowing it down by tid if (!l->args[1] || l->args[1] == thing->tid) Level->CreateThinker(DPusher::p_push, l->args[3] ? l : NULL, l->args[2], 0, thing, s); } } } else { // [RH] Find thing by tid AActor *thing; auto iterator = Level->GetActorIterator(l->args[1]); while ((thing = iterator.Next())) { if (thing->GetClass()->TypeName == NAME_PointPusher || thing->GetClass()->TypeName == NAME_PointPuller) { Level->CreateThinker(DPusher::p_push, l->args[3] ? l : NULL, l->args[2], 0, thing, thing->Sector->Index()); } } } l->special = 0; break; } } } //----------------------------------------------------------------------------- // // Initialize the scrollers // //----------------------------------------------------------------------------- void MapLoader::SpawnScrollers() { auto SCROLLTYPE = [](int i) { return EScrollPos((i <= 0) || (i & ~7) ? 7 : i); }; line_t *l = &Level->lines[0]; side_t *side; TArray copyscrollers; for (auto &line : Level->lines) { if (line.special == Sector_CopyScroller) { // don't allow copying the scroller if the sector has the same tag as it would just duplicate it. if (!Level->SectorHasTag(line.frontsector, line.args[0])) { copyscrollers.Push(line.Index()); } line.special = 0; } } for (unsigned i = 0; i < Level->lines.Size(); i++, l++) { double dx; // direction and speed of scrolling double dy; sector_t *control = nullptr; int accel = 0; // no control sector or acceleration int special = l->special; // Check for undefined parameters that are non-zero and output messages for them. // We don't report for specials we don't understand. FLineSpecial *spec = P_GetLineSpecialInfo(special); if (spec != nullptr) { int max = spec->map_args; for (unsigned arg = max; arg < countof(l->args); ++arg) { if (l->args[arg] != 0) { Printf("Line %d (type %d:%s), arg %u is %d (should be 0)\n", i, special, spec->name, arg + 1, l->args[arg]); } } } // killough 3/7/98: Types 245-249 are same as 250-254 except that the // first side's sector's heights cause scrolling when they change, and // this linedef controls the direction and speed of the scrolling. The // most complicated linedef since donuts, but powerful :) // // killough 3/15/98: Add acceleration. Types 214-218 are the same but // are accelerative. // [RH] Assume that it's a scroller and zero the line's special. l->special = 0; dx = dy = 0; // Shut up, GCC if (special == Scroll_Ceiling || special == Scroll_Floor || special == Scroll_Texture_Model) { if (l->args[1] & 3) { // if 1, then displacement // if 2, then accelerative (also if 3) control = l->sidedef[0]->sector; if (l->args[1] & 2) accel = 1; } if (special == Scroll_Texture_Model || l->args[1] & 4) { // The line housing the special controls the // direction and speed of scrolling. dx = l->Delta().X / 32.; dy = l->Delta().Y / 32.; } else { // The speed and direction are parameters to the special. dx = (l->args[3] - 128) / 32.; dy = (l->args[4] - 128) / 32.; } } switch (special) { int s; case Scroll_Ceiling: { auto itr = Level->GetSectorTagIterator(l->args[0]); while ((s = itr.Next()) >= 0) { Level->CreateThinker(EScroll::sc_ceiling, -dx, dy, control, &Level->sectors[s], nullptr, accel); } for (unsigned j = 0; j < copyscrollers.Size(); j++) { line_t *line = &Level->lines[copyscrollers[j]]; if (line->args[0] == l->args[0] && (line->args[1] & 1)) { Level->CreateThinker(EScroll::sc_ceiling, -dx, dy, control, line->frontsector, nullptr, accel); } } break; } case Scroll_Floor: if (l->args[2] != 1) { // scroll the floor texture auto itr = Level->GetSectorTagIterator(l->args[0]); while ((s = itr.Next()) >= 0) { Level->CreateThinker(EScroll::sc_floor, -dx, dy, control, &Level->sectors[s], nullptr, accel); } for (unsigned j = 0; j < copyscrollers.Size(); j++) { line_t *line = &Level->lines[copyscrollers[j]]; if (line->args[0] == l->args[0] && (line->args[1] & 2)) { Level->CreateThinker(EScroll::sc_floor, -dx, dy, control, line->frontsector, nullptr, accel); } } } if (l->args[2] > 0) { // carry objects on the floor auto itr = Level->GetSectorTagIterator(l->args[0]); while ((s = itr.Next()) >= 0) { Level->CreateThinker(EScroll::sc_carry, dx, dy, control, &Level->sectors[s], nullptr, accel); } for (unsigned j = 0; j < copyscrollers.Size(); j++) { line_t *line = &Level->lines[copyscrollers[j]]; if (line->args[0] == l->args[0] && (line->args[1] & 4)) { Level->CreateThinker(EScroll::sc_carry, dx, dy, control, line->frontsector, nullptr, accel); } } } break; // killough 3/1/98: scroll wall according to linedef // (same direction and speed as scrolling floors) case Scroll_Texture_Model: { auto itr = Level->GetLineIdIterator(l->args[0]); while ((s = itr.Next()) >= 0) { if (s != (int)i) Level->CreateThinker(dx, dy, &Level->lines[s], control, accel); } break; } case Scroll_Texture_Offsets: // killough 3/2/98: scroll according to sidedef offsets side = Level->lines[i].sidedef[0]; Level->CreateThinker(EScroll::sc_side, -side->GetTextureXOffset(side_t::mid), side->GetTextureYOffset(side_t::mid), nullptr, nullptr, side, accel, SCROLLTYPE(l->args[0])); break; case Scroll_Texture_Left: l->special = special; // Restore the special, for compat_useblocking's benefit. side = Level->lines[i].sidedef[0]; Level->CreateThinker(EScroll::sc_side, l->args[0] / 64., 0, nullptr, nullptr, side, accel, SCROLLTYPE(l->args[1])); break; case Scroll_Texture_Right: l->special = special; side = Level->lines[i].sidedef[0]; Level->CreateThinker(EScroll::sc_side, -l->args[0] / 64., 0, nullptr, nullptr, side, accel, SCROLLTYPE(l->args[1])); break; case Scroll_Texture_Up: l->special = special; side = Level->lines[i].sidedef[0]; Level->CreateThinker(EScroll::sc_side, 0, l->args[0] / 64., nullptr, nullptr, side, accel, SCROLLTYPE(l->args[1])); break; case Scroll_Texture_Down: l->special = special; side = Level->lines[i].sidedef[0]; Level->CreateThinker(EScroll::sc_side, 0, -l->args[0] / 64., nullptr, nullptr, side, accel, SCROLLTYPE(l->args[1])); break; case Scroll_Texture_Both: side = Level->lines[i].sidedef[0]; if (l->args[0] == 0) { dx = (l->args[1] - l->args[2]) / 64.; dy = (l->args[4] - l->args[3]) / 64.; Level->CreateThinker(EScroll::sc_side, dx, dy, nullptr, nullptr, side, accel); } break; default: // [RH] It wasn't a scroller after all, so restore the special. l->special = special; break; } } } void MapLoader::CreateScroller(EScroll type, double dx, double dy, sector_t *affectee, int accel, EScrollPos scrollpos) { Level->CreateThinker(type, dx, dy, nullptr, affectee, nullptr, accel, scrollpos); }