// map interaction/info functions enum EExitType { ET_Normal, ET_Secret, ET_EndGame, ET_NewMap, }; extend Class SWWMUtility { // Thanks to ZZYZX and Nash static play void SetToSlopeSpecific( Actor a, double dang, SecPlane plane, bool flipnorm ) { Vector3 fnormal; if ( flipnorm ) fnormal = -plane.Normal; else fnormal = plane.Normal; vector2 fnormalp1 = ((fnormal.x != 0) || (fnormal.y != 0))?(fnormal.x,fnormal.y).Unit():(0,0); vector2 fnormalp2 = ((fnormal.x,fnormal.y).Length(),fnormal.z); double fang = atan2(fnormalp1.y,fnormalp1.x); // floor angle (not pitch!) double fpitch = atan2(fnormalp2.x,fnormalp2.y); // floor pitch double ddiff1 = sin(fang-dang); double ddiff2 = cos(fang-dang); a.pitch = fpitch*ddiff2; a.roll = -fpitch*ddiff1; a.angle = dang; } static play void SetToSlope( Actor a, double dang, bool ceil = false ) { Sector sect; SecPlane plane; Vector3 fnormal; bool flipnorm; if ( ceil ) { sect = a.CeilingSector; plane = sect.ceilingplane; flipnorm = true; fnormal = -sect.ceilingplane.Normal; } else { sect = a.FloorSector; plane = sect.floorplane; flipnorm = false; fnormal = sect.floorplane.Normal; } // find closest 3d floor for its normal F3DFloor ff; for ( int i=0; i= 10) && (l.special <= 13)) || (!part && (l.special >= 20) && (l.special <= 25)) || (!part && (l.special == 28)) || ((l.special >= 29) && (l.special <= 30)) || (!part && (l.special >= 35) && (l.special <= 37)) || (part && (l.special >= 40) && (l.special <= 45)) || (!part && (l.special == 46)) || (part && (l.special == 47)) || (!part && (l.special >= 60) && (l.special <= 68)) || (part && (l.special == 69)) || ((l.special >= 94) && (l.special <= 96)) || (part && (l.special == 97)) || (!part && (l.special == 99)) || (part && (l.special == 104)) || (part && (l.special >= 105) && (l.special <= 106)) || (part && (l.special >= 168) && (l.special <= 169)) || (!part && (l.special == 172)) || (part && (l.special >= 192) && (l.special <= 199)) || (!part && (l.special == 200)) || (part && (l.special >= 201) && (l.special <= 202)) || (!part && (l.special == 203)) || (part && (l.special == 205)) || (!part && (l.special >= 206) && (l.special <= 207)) || (!part && (l.special == 228)) || (!part && (l.special >= 230) && (l.special <= 231)) || (!part && (l.special >= 238) && (l.special <= 242)) || ((l.special >= 245) && (l.special <= 247)) || (part && (l.special == 249)) || (!part && (l.special >= 250) && (l.special <= 251)) || (part && (l.special >= 251) && (l.special <= 255)) || (!part && (l.special >= 256) && (l.special <= 261)) || (part && (l.special >= 262) && (l.special <= 269)) || (!part && (l.special == 275)) || (part && (l.special == 276)) || (!part && (l.special == 279)) || (part && (l.special == 280)) ) { let si = level.CreateSectorTagIterator(l.Args[0],l); int idx; while ( (idx = si.Next()) != -1 ) if ( level.Sectors[idx] == s ) return true; } } ti = ThinkerIterator.Create("Actor"); Actor a; while ( a = Actor(ti.Next()) ) { if ( !a.special || !a.Args[0] ) continue; if ( (part && (a.special >= 10) && (a.special <= 13)) || (!part && (a.special >= 20) && (a.special <= 25)) || (!part && (a.special == 28)) || ((a.special >= 29) && (a.special <= 30)) || (!part && (a.special >= 35) && (a.special <= 37)) || (part && (a.special >= 40) && (a.special <= 45)) || (!part && (a.special == 46)) || (part && (a.special == 47)) || (!part && (a.special >= 60) && (a.special <= 68)) || (part && (a.special == 69)) || ((a.special >= 94) && (a.special <= 96)) || (part && (a.special == 97)) || (!part && (a.special == 99)) || (part && (a.special == 104)) || (part && (a.special >= 105) && (a.special <= 106)) || (part && (a.special >= 168) && (a.special <= 169)) || (!part && (a.special == 172)) || (part && (a.special >= 192) && (a.special <= 199)) || (!part && (a.special == 200)) || (part && (a.special >= 201) && (a.special <= 202)) || (!part && (a.special == 203)) || (part && (a.special == 205)) || (!part && (a.special >= 206) && (a.special <= 207)) || (!part && (a.special == 228)) || (!part && (a.special >= 230) && (a.special <= 231)) || (!part && (a.special >= 238) && (a.special <= 242)) || ((a.special >= 245) && (a.special <= 247)) || (part && (a.special == 249)) || (!part && (a.special >= 250) && (a.special <= 251)) || (part && (a.special >= 251) && (a.special <= 255)) || (!part && (a.special >= 256) && (a.special <= 261)) || (part && (a.special >= 262) && (a.special <= 269)) || (!part && (a.special == 275)) || (part && (a.special == 276)) || (!part && (a.special == 279)) || (part && (a.special == 280)) ) { let si = level.CreateSectorTagIterator(a.Args[0]); int idx; while ( (idx = si.Next()) != -1 ) if ( level.Sectors[idx] == s ) return true; } } return false; } // the stupidest thing ever, it's called BlockingLine but it's not always blocking us static bool BlockingLineIsBlocking( Actor a, int blockflags = Line.ML_BLOCKEVERYTHING, Line testline = null ) { Line l = testline?testline:a.BlockingLine; // not blocked if ( !l ) return false; // one-sided always blocking if ( !l.sidedef[1] ) return true; // same for block everything lines if ( l.flags&blockflags ) return true; // lower and upper bounds hit? double afloor = l.frontsector.floorplane.ZAtPoint(a.pos.xy), bfloor = l.backsector.floorplane.ZAtPoint(a.pos.xy), aceil = l.frontsector.ceilingplane.ZAtPoint(a.pos.xy), bceil = l.backsector.ceilingplane.ZAtPoint(a.pos.xy); if ( (min(a.pos.z+a.height,a.ceilingz) > min(aceil,bceil)) || (max(a.pos.z,a.floorz) < max(afloor,bfloor)) ) return true; // solid 3d floor bounds hit? for ( int i=0; i ceil) && (a.pos.z < floor) ) return true; } for ( int i=0; i ceil) && (a.pos.z < floor) ) return true; } return false; } static Vector3 UseLinePos( Line l ) { Vector3 al, ah, bl, bh; if ( !l.sidedef[1] ) { // just the whole line al = (l.v1.p,l.frontsector.floorplane.ZatPoint(l.v1.p)); ah = (l.v1.p,l.frontsector.ceilingplane.ZatPoint(l.v1.p)); bl = (l.v2.p,l.frontsector.floorplane.ZatPoint(l.v2.p)); bh = (l.v2.p,l.frontsector.ceilingplane.ZatPoint(l.v2.p)); return (al+ah+bl+bh)*.25; } SecPlane highestfloor, lowestfloor, lowestceiling, highestceiling; if ( (l.frontsector.floorplane.ZatPoint(l.v1.p) > l.backsector.floorplane.ZatPoint(l.v1.p)) && (l.frontsector.floorplane.ZatPoint(l.v2.p) > l.backsector.floorplane.ZatPoint(l.v2.p)) ) { highestfloor = l.frontsector.floorplane; lowestfloor = l.backsector.floorplane; } else { highestfloor = l.backsector.floorplane; lowestfloor = l.frontsector.floorplane; } if ( (l.frontsector.ceilingplane.ZatPoint(l.v1.p) < l.backsector.ceilingplane.ZatPoint(l.v1.p)) && (l.frontsector.ceilingplane.ZatPoint(l.v2.p) < l.backsector.ceilingplane.ZatPoint(l.v2.p)) ) { lowestceiling = l.frontsector.ceilingplane; highestceiling = l.backsector.ceilingplane; } else { lowestceiling = l.backsector.ceilingplane; highestceiling = l.frontsector.ceilingplane; } // try to guess what the part that triggers this is if ( l.Activation&SPAC_Cross ) { // pick the "intersection" al = (l.v1.p,highestfloor.ZatPoint(l.v1.p)); ah = (l.v1.p,lowestceiling.ZatPoint(l.v1.p)); bl = (l.v2.p,highestfloor.ZatPoint(l.v2.p)); bh = (l.v2.p,lowestceiling.ZatPoint(l.v2.p)); return (al+ah+bl+bh)*.25; } // check if lower part available al = (l.v1.p,lowestfloor.ZatPoint(l.v1.p)); ah = (l.v1.p,highestfloor.ZatPoint(l.v1.p)); bl = (l.v2.p,lowestfloor.ZatPoint(l.v2.p)); bh = (l.v2.p,highestfloor.ZatPoint(l.v2.p)); if ( ((al-ah).length() > 0) && ((bl-bh).length() > 0) ) return (al+ah+bl+bh)*.25; // check if upper part available al = (l.v1.p,lowestceiling.ZatPoint(l.v1.p)); ah = (l.v1.p,highestceiling.ZatPoint(l.v1.p)); bl = (l.v2.p,lowestceiling.ZatPoint(l.v2.p)); bh = (l.v2.p,highestceiling.ZatPoint(l.v2.p)); if ( ((al-ah).length() > 0) && ((bl-bh).length() > 0) ) return (al+ah+bl+bh)*.25; // check for 3d floors bool floorfound = false; Vector3 fal, fah, fbl, fbh; for ( int i=0; i ah.z) && (fbh.z > bh.z) && (fal.z > al.z) && (fbl.z > bl.z) ) continue; al = fal; ah = fah; bl = fbl; bh = fbh; floorfound = true; } if ( floorfound ) return (al+ah+bl+bh)*.25; for ( int i=0; i ah.z) && (fbh.z > bh.z) && (fal.z > al.z) && (fbl.z > bl.z) ) continue; al = fal; ah = fah; bl = fbl; bh = fbh; floorfound = true; } if ( floorfound ) return (al+ah+bl+bh)*.25; // check for midtex if ( !l.sidedef[0].GetTexture(1).IsNull() ) { double ofs = l.sidedef[0].GetTextureYOffset(1); Vector2 siz = TexMan.GetScaledSize(l.sidedef[0].GetTexture(1)); Vector2 tofs = TexMan.GetScaledOffset(l.sidedef[0].GetTexture(1)); ofs += tofs.y; ofs *= l.sidedef[0].GetTextureYScale(1); siz.y *= l.sidedef[0].GetTextureYScale(1); if ( l.flags&Line.ML_DONTPEGBOTTOM ) { al = (l.v1.p,highestfloor.ZAtPoint(l.v1.p)+ofs); bl = (l.v2.p,highestfloor.ZAtPoint(l.v2.p)+ofs); ah = al+(0,0,siz.y); bh = bl+(0,0,siz.y); } else { ah = (l.v1.p,lowestceiling.ZAtPoint(l.v1.p)+ofs); bh = (l.v2.p,lowestceiling.ZAtPoint(l.v2.p)+ofs); al = ah-(0,0,siz.y); bl = bh-(0,0,siz.y); } return (al+ah+bl+bh)*.25; } if ( !l.sidedef[1].GetTexture(1).IsNull() ) { double ofs = l.sidedef[1].GetTextureYOffset(1); Vector2 siz = TexMan.GetScaledSize(l.sidedef[1].GetTexture(1)); Vector2 tofs = TexMan.GetScaledOffset(l.sidedef[1].GetTexture(1)); ofs += tofs.y; ofs *= l.sidedef[1].GetTextureYScale(1); siz.y *= l.sidedef[1].GetTextureYScale(1); if ( l.flags&Line.ML_DONTPEGBOTTOM ) { al = (l.v1.p,highestfloor.ZAtPoint(l.v1.p)+ofs); bl = (l.v2.p,highestfloor.ZAtPoint(l.v2.p)+ofs); ah = al+(0,0,siz.y); bh = bl+(0,0,siz.y); } else { ah = (l.v1.p,lowestceiling.ZAtPoint(l.v1.p)+ofs); bh = (l.v2.p,lowestceiling.ZAtPoint(l.v2.p)+ofs); al = ah-(0,0,siz.y); bl = bh-(0,0,siz.y); } return (al+ah+bl+bh)*.25; } // just use the intersection al = (l.v1.p,highestfloor.ZatPoint(l.v1.p)); ah = (l.v1.p,lowestceiling.ZatPoint(l.v1.p)); bl = (l.v2.p,highestfloor.ZatPoint(l.v2.p)); bh = (l.v2.p,lowestceiling.ZatPoint(l.v2.p)); return (al+ah+bl+bh)*.25; } // gets the hit normal vector for projectiles and hitscan // bNoBounce: actor didn't just bounce, meaning the fallback normal shouldn't be the inverse of velocity static Vector3 GetActorHitNormal( Actor a, bool bNoBounce = false ) { Vector3 HitNormal = (0,0,0); F3DFloor ff; if ( a.BlockingMobj ) { let mo = a.BlockingMobj; Vector3 diff = level.Vec3Diff(mo.pos,a.pos); if ( diff.x >= mo.radius ) HitNormal += (1,0,0); else if ( diff.x <= -mo.radius ) HitNormal += (-1,0,0); if ( diff.y >= mo.radius ) HitNormal += (0,1,0); else if ( diff.y <= -mo.radius ) HitNormal += (0,-1,0); if ( diff.z >= mo.height ) HitNormal += (0,0,1); else if ( diff.z <= 0. ) HitNormal += (0,0,-1); double len = HitNormal.length(); if ( len < double.epsilon ) HitNormal = Vec3FromAngles(FRandom[ExploS](0,360),FRandom[ExploS](-90,90)); else HitNormal /= len; } else if ( a.BlockingFloor ) { // find closest 3d floor for its normal for ( int i=0; i 0. ) HitNormal = bNoBounce?(-a.vel/len):(a.vel/len); } return HitNormal; } static Vector3 GetLineTraceHitNormal( FLineTraceData d ) { Vector3 HitNormal = (0,0,0); if ( d.HitType == TRACE_HitActor ) { let mo = d.HitActor; Vector3 diff = level.Vec3Diff(mo.pos,d.HitLocation); if ( diff.x >= mo.radius ) HitNormal += (1,0,0); else if ( diff.x <= -mo.radius ) HitNormal += (-1,0,0); if ( diff.y >= mo.radius ) HitNormal += (0,1,0); else if ( diff.y <= -mo.radius ) HitNormal += (0,-1,0); if ( diff.z >= mo.height ) HitNormal += (0,0,1); else if ( diff.z <= 0. ) HitNormal += (0,0,-1); double len = HitNormal.length(); if ( len < double.epsilon ) HitNormal = Vec3FromAngles(FRandom[ExploS](0,360),FRandom[ExploS](-90,90)); else HitNormal /= len; } else if ( d.HitType == TRACE_HitFloor ) { if ( d.Hit3DFloor ) HitNormal = -d.Hit3DFloor.top.Normal; else HitNormal = d.HitSector.floorplane.Normal; } else if ( d.HitType == TRACE_HitCeiling ) { if ( d.Hit3DFloor ) HitNormal = -d.Hit3DFloor.bottom.Normal; else HitNormal = d.HitSector.ceilingplane.Normal; } else if ( d.HitType == TRACE_HitWall ) { HitNormal = (-d.HitLine.delta.y,d.HitLine.delta.x,0).unit(); if ( !d.LineSide ) HitNormal *= -1; } else HitNormal = -d.HitDir; return HitNormal; } static Vector3 GetLineTracerHitNormal( TraceResults r ) { Vector3 HitNormal = (0,0,0); if ( r.HitType == TRACE_HitActor ) { let mo = r.HitActor; Vector3 diff = level.Vec3Diff(mo.pos,r.HitPos); if ( diff.x >= mo.radius ) HitNormal += (1,0,0); else if ( diff.x <= -mo.radius ) HitNormal += (-1,0,0); if ( diff.y >= mo.radius ) HitNormal += (0,1,0); else if ( diff.y <= -mo.radius ) HitNormal += (0,-1,0); if ( diff.z >= mo.height ) HitNormal += (0,0,1); else if ( diff.z <= 0. ) HitNormal += (0,0,-1); double len = HitNormal.length(); if ( len < double.epsilon ) HitNormal = Vec3FromAngles(FRandom[ExploS](0,360),FRandom[ExploS](-90,90)); else HitNormal /= len; } else if ( r.HitType == TRACE_HitFloor ) { if ( r.ffloor ) HitNormal = -r.ffloor.top.Normal; else HitNormal = r.HitSector.floorplane.Normal; } else if ( r.HitType == TRACE_HitCeiling ) { if ( r.ffloor ) HitNormal = -r.ffloor.bottom.Normal; else HitNormal = r.HitSector.ceilingplane.Normal; } else if ( r.HitType == TRACE_HitWall ) { HitNormal = (-r.HitLine.delta.y,r.HitLine.delta.x,0).unit(); if ( !r.Side ) HitNormal *= -1; } else HitNormal = -r.HitVector; return HitNormal; } static bool, TextureID DefaceTexture( TextureID checkme ) { String tn = TexMan.GetName(checkme); // special case: alt texture names in Doom 2 In Spain Only if ( (tn ~== "MARBFAC2") || (tn ~== "SP_MAR01") ) return true, TexMan.CheckForTexture("defaced_MARBFAC2"); if ( (tn ~== "MARBFAC3") || (tn ~== "SP_MAR02")) return true, TexMan.CheckForTexture("defaced_MARBFAC3"); if ( (tn ~== "MARBFAC4") || (tn ~== "SP_MAR03") ) return true, TexMan.CheckForTexture("defaced_MARBFAC4"); if ( (tn ~== "MARBFACE") || (tn ~== "SP_MAR04") ) return true, TexMan.CheckForTexture("defaced_MARBFACE"); if ( (tn ~== "ZZWOLF2") ) return true, TexMan.CheckForTexture("defaced_ZZWOLF2"); if ( (tn ~== "ZZWOLF3") ) return true, TexMan.CheckForTexture("defaced_ZZWOLF3"); if ( (tn ~== "ZZWOLF4") ) return true, TexMan.CheckForTexture("defaced_ZZWOLF4"); if ( (tn ~== "ZZWOLF6") ) return true, TexMan.CheckForTexture("defaced_ZZWOLF6"); if ( (tn ~== "ZZWOLF7") ) return true, TexMan.CheckForTexture("defaced_ZZWOLF7"); if ( (tn ~== "ZZWOLF12") ) return true, TexMan.CheckForTexture("defaced_ZZWOLF12"); if ( (tn ~== "ZZWOLF13") ) return true, TexMan.CheckForTexture("defaced_ZZWOLF13"); return false, checkme; } // iterate through polyobjects and see if this line is part of one (returning which, if any) static bool IsPolyLine( Line l, out swwm_PolyobjectHandle o ) { let pi = swwm_PolyobjectIterator.Create(); swwm_PolyobjectHandle p; while ( p = pi.Next() ) { if ( p.Lines.Find(l) >= p.Lines.Size() ) continue; o = p; return true; } o = null; return false; } // checks if the specified world coordinate is inside the polyobject // this check is very naive but it should handle most "normal" shapes // (yeah, sorry if you somehow want to play this mod with lilith.pk3) static bool PointInPolyobj( Vector2 p, swwm_PolyobjectHandle o ) { // first pass, find which vertex out of all lines is closest Vertex v = o.StartLine.v1; double dist = (v.p-p).length(); foreach ( l:o.Lines ) { double dist2 = (l.v1.p-p).length(); if ( dist2 < dist ) { v = l.v1; dist = dist2; } dist2 = (l.v2.p-p).length(); if ( dist2 < dist ) { v = l.v2; dist = dist2; } } // second pass, find which two lines share that vertex // (in theory there should only be two) Line a = null, b = null; foreach ( l:o.Lines ) { if ( (l.v1 == v) || (l.v2 == v) ) { if ( !a ) a = l; else if ( !b ) b = l; else break; } } // is the point behind both lines? return (Level.PointOnLineSide(p,a) && Level.PointOnLineSide(p,b)); } static bool SameSpecial( Line a, Line b ) { if ( a.special != b.special ) return false; for ( int i=0; i<5; i++ ) { if ( a.args[i] != b.args[i] ) return false; } return true; } }