diff --git a/src/g_shared/a_sharedglobal.h b/src/g_shared/a_sharedglobal.h index acb4faf0f..0c9e723b6 100644 --- a/src/g_shared/a_sharedglobal.h +++ b/src/g_shared/a_sharedglobal.h @@ -106,11 +106,7 @@ class ASkyCamCompat : public ASkyViewpoint DECLARE_CLASS (ASkyCamCompat, ASkyViewpoint) public: - void BeginPlay () - { - // Do not call the SkyViewpoint's super method because it would trash our setup - AActor::BeginPlay(); - } + void BeginPlay(); }; diff --git a/src/g_shared/a_skies.cpp b/src/g_shared/a_skies.cpp index a8c8ebd55..a34a30956 100644 --- a/src/g_shared/a_skies.cpp +++ b/src/g_shared/a_skies.cpp @@ -54,6 +54,7 @@ void ASkyViewpoint::BeginPlay () { level.DefaultSkybox = this; } + special1 = SKYBOX_SKYVIEWPOINT; } void ASkyViewpoint::Serialize (FArchive &arc) @@ -85,6 +86,13 @@ void ASkyViewpoint::Destroy () IMPLEMENT_CLASS (ASkyCamCompat) +void ASkyCamCompat::BeginPlay() +{ + // Do not call the SkyViewpoint's super method because it would trash our setup + AActor::BeginPlay(); + special1 = SKYBOX_SKYVIEWPOINT; +} + //--------------------------------------------------------------------------- @@ -154,6 +162,7 @@ void AStackPoint::BeginPlay () AActor::BeginPlay (); bAlways = true; + special1 = SKYBOX_STACKEDSECTORTHING; } //--------------------------------------------------------------------------- diff --git a/src/g_strife/a_thingstoblowup.cpp b/src/g_strife/a_thingstoblowup.cpp index a1899e60b..4cf4f6861 100644 --- a/src/g_strife/a_thingstoblowup.cpp +++ b/src/g_strife/a_thingstoblowup.cpp @@ -104,6 +104,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_LightGoesOut) sec->floorplane.d = sec->floorplane.PointToDist (spot, newheight); fixed_t newtheight = sec->floorplane.Zat0(); sec->ChangePlaneTexZ(sector_t::floor, newtheight - oldtheight); + sec->CheckPortalPlane(sector_t::floor); for (int i = 0; i < 8; ++i) { diff --git a/src/p_enemy.cpp b/src/p_enemy.cpp index ad286fdd4..eaf729771 100644 --- a/src/p_enemy.cpp +++ b/src/p_enemy.cpp @@ -2582,6 +2582,11 @@ static bool P_CheckForResurrection(AActor *self, bool usevilestates) abs(corpsehit->Y() - viletry.y) > maxdist) continue; // not actually touching // Let's check if there are floors in between the archvile and its target + + // if in a different section of the map, only consider possible if a line of sight exists. + if (corpsehit->Sector->PortalGroup != self->Sector->PortalGroup && !P_CheckSight(self, corpsehit)) + continue; + sector_t *vilesec = self->Sector; sector_t *corpsec = corpsehit->Sector; // We only need to test if at least one of the sectors has a 3D floor. diff --git a/src/p_map.cpp b/src/p_map.cpp index 6416e0a6e..0e3cb6617 100644 --- a/src/p_map.cpp +++ b/src/p_map.cpp @@ -5532,6 +5532,7 @@ bool P_ChangeSector(sector_t *sector, int crunch, int amt, int floorOrCeil, bool } } } while (n); + sec->CheckPortalPlane(!floorOrCeil); } } P_Recalculate3DFloors(sector); // Must recalculate the 3d floor and light lists @@ -5595,6 +5596,8 @@ bool P_ChangeSector(sector_t *sector, int crunch, int amt, int floorOrCeil, bool } } while (n); // repeat from scratch until all things left are marked valid + sector->CheckPortalPlane(floorOrCeil); // check for portal obstructions after everything is done. + if (!cpos.nofit && !isreset /* && sector->MoreFlags & (SECF_UNDERWATERMASK)*/) { // If this is a control sector for a deep water transfer, all actors in affected diff --git a/src/p_sectors.cpp b/src/p_sectors.cpp index a1cf2077f..a10568388 100644 --- a/src/p_sectors.cpp +++ b/src/p_sectors.cpp @@ -871,6 +871,19 @@ int sector_t::GetTerrain(int pos) const return terrainnum[pos] >= 0 ? terrainnum[pos] : TerrainTypes[GetTexture(pos)]; } +void sector_t::CheckPortalPlane(int plane) +{ + ASkyViewpoint *portal = SkyBoxes[plane]; + if (!portal || portal->special1 != SKYBOX_LINKEDPORTAL) return; + + fixed_t planeh = planes[plane].TexZ; + int obstructed = PLANEF_OBSTRUCTED * (plane == sector_t::floor ? + planeh > portal->threshold : planeh < portal->threshold); + planes[plane].Flags = (planes[plane].Flags & ~PLANEF_OBSTRUCTED) | obstructed; +} + + + FArchive &operator<< (FArchive &arc, secspecial_t &p) { if (SaveVersion < 4529) diff --git a/src/p_spec.cpp b/src/p_spec.cpp index 01ef4a8f6..e66049640 100644 --- a/src/p_spec.cpp +++ b/src/p_spec.cpp @@ -1036,15 +1036,16 @@ static void CopyPortal(int sectortag, int plane, ASkyViewpoint *origin, fixed_t } } -void P_SpawnPortal(line_t *line, int sectortag, int plane, int alpha) +void P_SpawnPortal(line_t *line, int sectortag, int plane, int alpha, int linked) { + if (plane < 0 || plane > 2 || (linked && plane == 2)) return; for (int i=0;iv1->y) + SQWORD(line->v2->y)) >> 1); fixed_t x2 = fixed_t((SQWORD(lines[i].v1->x) + SQWORD(lines[i].v2->x)) >> 1); fixed_t y2 = fixed_t((SQWORD(lines[i].v1->y) + SQWORD(lines[i].v2->y)) >> 1); + fixed_t z = linked ? line->frontsector->planes[plane].TexZ : 0; // the map's sector height defines the portal plane for linked portals + fixed_t alpha = Scale (lines[i].args[4], OPAQUE, 255); AStackPoint *anchor = Spawn(x1, y1, 0, NO_REPLACE); AStackPoint *reference = Spawn(x2, y2, 0, NO_REPLACE); + reference->special1 = linked ? SKYBOX_LINKEDPORTAL : SKYBOX_PORTAL; + anchor->special1 = SKYBOX_ANCHOR; + // store the portal displacement in the unused scaleX/Y members of the portal reference actor. + anchor->scaleX = -(reference->scaleX = x2 - x1); + anchor->scaleY = -(reference->scaleY = y2 - y1); + anchor->threshold = reference->threshold = z; reference->Mate = anchor; anchor->Mate = reference; @@ -1445,15 +1454,17 @@ void P_SpawnSpecials (void) // - 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 (HW renderer only for now) - // - 4: EE-style horizon portal (HW renderer only for now) + // - 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 (lines[i].args[1] == 0 && lines[i].args[3] == 0) + if ((lines[i].args[1] == 0 || lines[i].args[1] == 6) && lines[i].args[3] == 0) { - P_SpawnPortal(&lines[i], lines[i].args[0], lines[i].args[2], lines[i].args[4]); + P_SpawnPortal(&lines[i], lines[i].args[0], lines[i].args[2], lines[i].args[4], lines[i].args[1]); } else if (lines[i].args[1] == 3 || lines[i].args[1] == 4) { @@ -1542,6 +1553,7 @@ void P_SpawnSpecials (void) // [RH] Start running any open scripts on this map FBehavior::StaticStartTypedScripts (SCRIPT_Open, NULL, false); P_FinalizePortals(); + P_CreateLinkedPortals(); } // killough 2/28/98: diff --git a/src/portal.cpp b/src/portal.cpp index 7f08e6d6e..d9156514d 100644 --- a/src/portal.cpp +++ b/src/portal.cpp @@ -8,10 +8,15 @@ #include "p_tags.h" #include "farchive.h" #include "v_text.h" +#include "a_sharedglobal.h" +#include "i_system.h" +#include "c_dispatch.h" // simulation recurions maximum CVAR(Int, sv_portal_recursions, 4, CVAR_ARCHIVE|CVAR_SERVERINFO) +FDisplacementTable Displacements; + TArray linePortals; @@ -148,7 +153,19 @@ void P_UpdatePortal(FLinePortal *port) else { port->mFlags = port->mDefFlags; - } + if (port->mType == PORTT_LINKED) + { + if (linePortals[port->mDestination->portalindex].mType != PORTT_LINKED) + { + port->mType = PORTT_INTERACTIVE; // linked portals must be two-way. + } + else + { + port->mXDisplacement = port->mDestination->v2->x - port->mOrigin->v1->x; + port->mYDisplacement = port->mDestination->v2->y - port->mOrigin->v1->y; + } + } + } } void P_FinalizePortals() @@ -504,3 +521,336 @@ bool PortalTracer::TraceStep() return (oDepth != depth); // if a portal has been found, return false } + + +//============================================================================ +// +// CollectSectors +// +// Collects all sectors that are connected to any sector belonging to a portal +// because they all will need the same displacement values +// +//============================================================================ + +static bool CollectSectors(int groupid, sector_t *origin) +{ + if (origin->PortalGroup != 0) return false; // already processed + origin->PortalGroup = groupid; + + TArray list(16); + list.Push(origin); + + for (unsigned i = 0; i < list.Size(); i++) + { + sector_t *sec = list[i]; + + for (int j = 0; j < sec->linecount; j++) + { + line_t *line = sec->lines[j]; + sector_t *other = line->frontsector == sec ? line->backsector : line->frontsector; + if (other != NULL && other != sec && other->PortalGroup != groupid) + { + other->PortalGroup = groupid; + list.Push(other); + } + } + } + return true; +} + + +//============================================================================ +// +// AddDisplacementForPortal +// +// Adds the displacement for one portal to the displacement array +// (one version for sector to sector plane, one for line to line portals) +// +//============================================================================ + +static void AddDisplacementForPortal(AStackPoint *portal) +{ + int thisgroup = portal->Mate->Sector->PortalGroup; + int othergroup = portal->Sector->PortalGroup; + if (thisgroup == othergroup) + { + Printf("Portal between sectors %d and %d has both sides in same group and will be disabled\n", portal->Sector->sectornum, portal->Mate->Sector->sectornum); + portal->special1 = portal->Mate->special1 = SKYBOX_PORTAL; + return; + } + if (thisgroup <= 0 || thisgroup >= Displacements.size || othergroup <= 0 || othergroup >= Displacements.size) + { + Printf("Portal between sectors %d and %d has invalid group and will be disabled\n", portal->Sector->sectornum, portal->Mate->Sector->sectornum); + portal->special1 = portal->Mate->special1 = SKYBOX_PORTAL; + return; + } + + FDisplacement & disp = Displacements(thisgroup, othergroup); + if (!disp.isSet) + { + disp.x = portal->scaleX; + disp.y = portal->scaleY; + disp.isSet = true; + } + else + { + if (disp.x != portal->scaleX || disp.y != portal->scaleY) + { + Printf("Portal between sectors %d and %d has displacement mismatch and will be disabled\n", portal->Sector->sectornum, portal->Mate->Sector->sectornum); + portal->special1 = portal->Mate->special1 = SKYBOX_PORTAL; + return; + } + } +} + + +static void AddDisplacementForPortal(FLinePortal *portal) +{ + int thisgroup = portal->mOrigin->frontsector->PortalGroup; + int othergroup = portal->mDestination->frontsector->PortalGroup; + if (thisgroup == othergroup) + { + Printf("Portal between lines %d and %d has both sides in same group\n", int(portal->mOrigin-lines), int(portal->mDestination-lines)); + portal->mType = linePortals[portal->mDestination->portalindex].mType = PORTT_TELEPORT; + return; + } + if (thisgroup <= 0 || thisgroup >= Displacements.size || othergroup <= 0 || othergroup >= Displacements.size) + { + Printf("Portal between lines %d and %d has invalid group\n", int(portal->mOrigin - lines), int(portal->mDestination - lines)); + portal->mType = linePortals[portal->mDestination->portalindex].mType = PORTT_TELEPORT; + return; + } + + FDisplacement & disp = Displacements(thisgroup, othergroup); + if (!disp.isSet) + { + disp.x = portal->mXDisplacement; + disp.y = portal->mYDisplacement; + disp.isSet = true; + } + else + { + if (disp.x != portal->mXDisplacement || disp.y != portal->mYDisplacement) + { + Printf("Portal between lines %d and %d has displacement mismatch\n", int(portal->mOrigin - lines), int(portal->mDestination - lines)); + portal->mType = linePortals[portal->mDestination->portalindex].mType = PORTT_TELEPORT; + return; + } + } +} + +//============================================================================ +// +// ConnectGroups +// +// Do the indirect connections. This loop will run until it cannot find any new connections +// +//============================================================================ + +static bool ConnectGroups() +{ + // Now + BYTE indirect = 1; + bool bogus = false; + bool changed; + do + { + changed = false; + for (int x = 1; x < Displacements.size; x++) + { + for (int y = 1; y < Displacements.size; y++) + { + FDisplacement &dispxy = Displacements(x, y); + if (dispxy.isSet) + { + for (int z = 1; z < Displacements.size; z++) + { + FDisplacement &dispyz = Displacements(y, z); + if (dispyz.isSet) + { + FDisplacement &dispxz = Displacements(x, z); + if (dispxz.isSet) + { + if (dispxy.x + dispyz.x != dispxz.x || dispxy.y + dispyz.y != dispxz.y) + { + bogus = true; + } + } + else + { + dispxz.x = dispxy.x + dispyz.x; + dispxz.y = dispxy.y + dispyz.y; + dispxz.isSet = true; + dispxz.indirect = indirect; + changed = true; + } + } + } + } + } + } + indirect++; + } while (changed); + return bogus; +} + + +//============================================================================ +// +// P_CreateLinkedPortals +// +// Creates the data structures needed for linked portals +// Removes portals from sloped sectors (as they cannot work on them) +// Group all sectors connected to one side of the portal +// Caclculate displacements between all created groups. +// +// Portals with the same offset but different anchors will not be merged. +// +//============================================================================ + +void P_CreateLinkedPortals() +{ + TThinkerIterator it; + AStackPoint *mo; + TArray orgs; + int id = 0; + bool bogus = false; + + while ((mo = it.Next())) + { + if (mo->special1 == SKYBOX_LINKEDPORTAL) + { + if (mo->Mate != NULL) + { + orgs.Push(mo); + mo->reactiontime = ++id; + } + else + { + // this should never happen, but if it does, the portal needs to be removed + mo->Destroy(); + } + } + } + if (orgs.Size() == 0) + { + return; + } + for (int i = 0; i < numsectors; i++) + { + for (int j = 0; j < 2; j++) + { + ASkyViewpoint *box = sectors[i].SkyBoxes[j]; + if (box != NULL && box->special1 == SKYBOX_LINKEDPORTAL) + { + secplane_t &plane = j == 0 ? sectors[i].floorplane : sectors[i].ceilingplane; + if (plane.a || plane.b) + { + // The engine cannot deal with portals on a sloped plane. + sectors[i].SkyBoxes[j] = NULL; + Printf("Portal on %s of sector %d is sloped and will be disabled\n", j==0? "floor":"ceiling", i); + } + } + } + } + + // Group all sectors, starting at each portal origin. + id = 1; + for (unsigned i = 0; i < orgs.Size(); i++) + { + if (CollectSectors(id, orgs[i]->Sector)) id++; + if (CollectSectors(id, orgs[i]->Mate->Sector)) id++; + } + for (unsigned i = 0; i < linePortals.Size(); i++) + { + if (linePortals[i].mType == PORTT_LINKED) + { + if (CollectSectors(id, linePortals[i].mOrigin->frontsector)) id++; + if (CollectSectors(id, linePortals[i].mDestination->frontsector)) id++; + } + } + + Displacements.Create(id); + // Check for leftover sectors that connect to a portal + for (int i = 0; ispecial1 == SKYBOX_LINKEDPORTAL && box->Sector->PortalGroup == 0) + { + CollectSectors(box->Sector->PortalGroup, box->Sector); + box = box->Mate; + if (box->special1 == SKYBOX_LINKEDPORTAL && box->Sector->PortalGroup == 0) + { + CollectSectors(box->Sector->PortalGroup, box->Sector); + } + } + } + } + } + for (unsigned i = 0; i < orgs.Size(); i++) + { + AddDisplacementForPortal(orgs[i]); + } + for (unsigned i = 0; i < linePortals.Size(); i++) + { + if (linePortals[i].mType == PORTT_LINKED) + { + AddDisplacementForPortal(&linePortals[i]); + } + } + + for (int x = 1; x < Displacements.size; x++) + { + for (int y = x + 1; y < Displacements.size; y++) + { + FDisplacement &dispxy = Displacements(x, y); + FDisplacement &dispyx = Displacements(y, x); + if (dispxy.isSet && dispyx.isSet && + (dispxy.x != -dispyx.x || dispxy.y != -dispyx.y)) + { + Printf("Link offset mismatch between groups %d and %d\n", x, y); // need to find some sectors to report. + bogus = true; + } + // todo: Find sectors that have no group but belong to a portal. + } + } + bogus |= ConnectGroups(); + if (bogus) + { + // todo: disable all portals whose offsets do not match the associated groups + } + + // reject would just get in the way when checking sight through portals. + if (rejectmatrix != NULL) + { + delete[] rejectmatrix; + rejectmatrix = NULL; + } + // finally we must flag all planes which are obstructed by the sector's own ceiling or floor. + for (int i = 0; i < numsectors; i++) + { + sectors[i].CheckPortalPlane(sector_t::floor); + sectors[i].CheckPortalPlane(sector_t::ceiling); + } + //BuildBlockmap(); +} + +CCMD(dumplinktable) +{ + for (int x = 1; x < Displacements.size; x++) + { + for (int y = 1; y < Displacements.size; y++) + { + FDisplacement &disp = Displacements(x, y); + Printf("%c%c(%6d, %6d)", TEXTCOLOR_ESCAPE, 'C' + disp.indirect, disp.x >> FRACBITS, disp.y >> FRACBITS); + } + Printf("\n"); + } +} + + + diff --git a/src/portal.h b/src/portal.h index 53fb1c16e..893f3ae69 100644 --- a/src/portal.h +++ b/src/portal.h @@ -8,6 +8,30 @@ #include "p_local.h" #include "m_bbox.h" +struct FDisplacement +{ + fixed_t x, y; + bool isSet; + BYTE indirect; // just for illustration. +}; + +struct FDisplacementTable +{ + TArray data; + int size; + + void Create(int numgroups) + { + data.Resize(numgroups*numgroups); + memset(&data[0], 0, numgroups*numgroups*sizeof(data[0])); + size = numgroups; + } + + FDisplacement &operator()(int x, int y) + { + return data[x + size*y]; + } +}; enum { @@ -53,6 +77,7 @@ extern TArray linePortals; void P_SpawnLinePortal(line_t* line); void P_FinalizePortals(); bool P_ChangePortal(line_t *ln, int thisid, int destid); +void P_CreateLinkedPortals(); /* code ported from prototype */ diff --git a/src/r_bsp.cpp b/src/r_bsp.cpp index f073498d4..4951acdcd 100644 --- a/src/r_bsp.cpp +++ b/src/r_bsp.cpp @@ -1094,7 +1094,7 @@ void R_Subsector (subsector_t *sub) } skybox = frontsector->GetSkyBox(sector_t::ceiling); - if (skybox != NULL && skybox->special1 != SKYBOX_MAP) skybox = NULL; // HW renderer only. + if (skybox != NULL && skybox->special1 >= SKYBOX_PLANE) skybox = NULL; // skip unsupported portal types ceilingplane = frontsector->ceilingplane.PointOnSide(viewx, viewy, viewz) > 0 || frontsector->GetTexture(sector_t::ceiling) == skyflatnum || @@ -1137,7 +1137,7 @@ void R_Subsector (subsector_t *sub) // killough 10/98: add support for skies transferred from sidedefs skybox = frontsector->GetSkyBox(sector_t::floor); - if (skybox != NULL && skybox->special1 != SKYBOX_MAP) skybox = NULL; // HW renderer only. + if (skybox != NULL && skybox->special1 >= SKYBOX_PLANE) skybox = NULL; // skip unsupported portal types floorplane = frontsector->floorplane.PointOnSide(viewx, viewy, viewz) > 0 || // killough 3/7/98 frontsector->GetTexture(sector_t::floor) == skyflatnum || diff --git a/src/r_data/r_interpolate.cpp b/src/r_data/r_interpolate.cpp index 4e0781a98..7e0f4ca6f 100644 --- a/src/r_data/r_interpolate.cpp +++ b/src/r_data/r_interpolate.cpp @@ -469,6 +469,7 @@ void DSectorPlaneInterpolation::Restore() sector->SetPlaneTexZ(sector_t::ceiling, baktexz, true); } P_RecalculateAttached3DFloors(sector); + sector->CheckPortalPlane(ceiling? sector_t::ceiling : sector_t::floor); } //========================================================================== @@ -505,6 +506,7 @@ void DSectorPlaneInterpolation::Interpolate(fixed_t smoothratio) *pheight = oldheight + FixedMul(bakheight - oldheight, smoothratio); sector->SetPlaneTexZ(pos, oldtexz + FixedMul(baktexz - oldtexz, smoothratio), true); P_RecalculateAttached3DFloors(sector); + sector->CheckPortalPlane(pos); } } diff --git a/src/r_defs.h b/src/r_defs.h index fda3ab37f..ab78f9296 100644 --- a/src/r_defs.h +++ b/src/r_defs.h @@ -60,6 +60,18 @@ enum extern size_t MaxDrawSegs; +enum +{ + SKYBOX_ANCHOR = -1, + SKYBOX_SKYVIEWPOINT = 0, // a regular skybox + SKYBOX_STACKEDSECTORTHING, // stacked sectors with the thing method + SKYBOX_PORTAL, // stacked sectors with Sector_SetPortal + SKYBOX_LINKEDPORTAL, // linked portal (interactive) + SKYBOX_PLANE, // EE-style plane portal (not implemented in SW renderer) + SKYBOX_HORIZON, // EE-style horizon portal (not implemented in SW renderer) +}; + + // // INTERNAL MAP TYPES // used by play and refresh @@ -545,6 +557,7 @@ struct sector_t DInterpolation *SetInterpolation(int position, bool attach); ASkyViewpoint *GetSkyBox(int which); + void CheckPortalPlane(int plane); enum { @@ -779,6 +792,22 @@ struct sector_t Flags &= ~SECF_SPECIALFLAGS; } + inline bool PortalBlocksView(int plane) + { + return !!(planes[plane].Flags & (PLANEF_NORENDER | PLANEF_DISABLED | PLANEF_OBSTRUCTED)); + } + + inline bool PortalBlocksMovement(int plane) + { + return !!(planes[plane].Flags & (PLANEF_NOPASS | PLANEF_DISABLED | PLANEF_OBSTRUCTED)); + } + + inline bool PortalBlocksSound(int plane) + { + return !!(planes[plane].Flags & (PLANEF_BLOCKSOUND | PLANEF_DISABLED | PLANEF_OBSTRUCTED)); + } + + int GetTerrain(int pos) const; void TransferSpecial(sector_t *model); @@ -871,6 +900,7 @@ struct sector_t // [RH] The sky box to render for this sector. NULL means use a // regular sky. TObjPtr SkyBoxes[2]; + int PortalGroup; int sectornum; // for comparing sector copies diff --git a/wadsrc/static/confont.lmp b/wadsrc/static/confont.lmp index 3d46a3ca4..6b68df2e3 100644 Binary files a/wadsrc/static/confont.lmp and b/wadsrc/static/confont.lmp differ diff --git a/wadsrc/static/xlat/eternity.txt b/wadsrc/static/xlat/eternity.txt index 59351e1e2..0a98ed8d8 100644 --- a/wadsrc/static/xlat/eternity.txt +++ b/wadsrc/static/xlat/eternity.txt @@ -131,11 +131,11 @@ enum 356 = 0, Polyobj_RotateLeft(0) 357 = 0, Polyobj_OR_RotateLeft(0) -// Eternity's linked portals, vertical link version (floor-to-ceiling) (NOTE: Type needs changing!) -358 = 0, Sector_SetPortal(tag, 0, 1, 1, 0) // "Portal_AnchoredCeiling" -359 = 0, Sector_SetPortal(tag, 0, 0, 1, 0) // "Portal_AnchoredFloor" -360 = 0, Sector_SetPortal(tag, 0, 1, 0, 0) // "Portal_AnchorLine" -361 = 0, Sector_SetPortal(tag, 0, 0, 0, 0) // "Portal_AnchorLineFloor" +// Eternity's linked portals, vertical link version (floor-to-ceiling) +358 = 0, Sector_SetPortal(tag, 6, 1, 1, 0) // "Portal_AnchoredCeiling" +359 = 0, Sector_SetPortal(tag, 6, 0, 1, 0) // "Portal_AnchoredFloor" +360 = 0, Sector_SetPortal(tag, 6, 1, 0, 0) // "Portal_AnchorLine" +361 = 0, Sector_SetPortal(tag, 6, 0, 0, 0) // "Portal_AnchorLineFloor" // Even more parameterized linedefs 362 = 0, Pillar_Build(0)