// Ynykron projectiles and effects (alt-fire) Class YnykronAltTracer : LineTracer { Actor ignore; Array ShootThroughList; Array WaterHitList; override ETraceStatus TraceCallback() { // liquid splashes if ( Results.CrossedWater ) { let hl = new("WaterHit"); hl.sect = Results.CrossedWater; hl.hitpos = Results.CrossedWaterPos; WaterHitList.Push(hl); } else if ( Results.Crossed3DWater ) { let hl = new("WaterHit"); hl.sect = Results.Crossed3DWater; hl.hitpos = Results.Crossed3DWaterPos; WaterHitList.Push(hl); } if ( Results.HitType == TRACE_HitActor ) { if ( Results.HitActor == ignore ) return TRACE_Skip; if ( Results.HitActor.bSHOOTABLE || (Results.HitActor is 'YnykronSingularityHitbox') ) return TRACE_Stop; return TRACE_Skip; } else if ( (Results.HitType == TRACE_HitWall) && (Results.Tier == TIER_Middle) ) { if ( !Results.HitLine.sidedef[1] || (Results.HitLine.Flags&Line.ML_BlockHitscan) ) return TRACE_Stop; ShootThroughList.Push(Results.HitLine); return TRACE_Skip; } return TRACE_Stop; } } Class YnykronHaloTail : SWWMNonInteractiveActor { Default { RenderStyle "Add"; +FORCEXYBILLBOARD; Scale 1.5; } override void Tick() { if ( freezetics > 0 ) { freezetics--; return; } if ( isFrozen() ) return; A_SetScale(scale.x+.5); A_FadeOut(.2); } States { Spawn: MHAL A -1 Bright; Stop; } } Class YnykronHalo : SWWMNonInteractiveActor { Default { RenderStyle "Add"; +FORCEXYBILLBOARD; Scale 1.5; } override void Tick() { if ( freezetics > 0 ) { freezetics--; return; } if ( isFrozen() ) return; if ( !target || target.InStateSequence(target.CurState,target.FindState("Death")) ) { Destroy(); return; } SetOrigin(target.pos,true); A_SetScale(1.5*target.scale.x); let h = Spawn("YnykronHaloTail",pos); h.scale = scale; } States { Spawn: MHAL A -1 Bright; Stop; } } Class GatherDust : SWWMNonInteractiveActor { Mixin SWWMMinimalMovingTick; override void PostBeginPlay() { SetState(FindState("Spawn")+Random[ExploS](0,19)); Scale *= FRandom[ExploS](.75,1.5); Scale.x *= RandomPick[ExploS](-1,1); Scale.y *= RandomPick[ExploS](-1,1); SetShade(Color(4,3,5)*Random[ExploS](5,20)); } void A_Gravitate() { if ( target && !target.InStateSequence(target.CurState,target.FindState("Death")) ) { if ( alpha < .08 ) A_FadeIn(FRandom[ExploS](.0002,.004)); Vector3 dirto = level.Vec3Diff(pos,target.pos); double distto = dirto.length(); if ( distto < (32.*target.scale.x) ) { // sucked in target.specialf2 += FRandom[ExploS](.5,2.); Destroy(); return; } dirto /= distto; double mxdist = 5000.*target.scale.x; vel = dirto*25.*(clamp((mxdist-distto)/mxdist,.5,1.)**4.); vel += target.vel; return; } vel *= .98; A_FadeOut(.002); } Default { RenderStyle "Shaded"; Alpha 0.; Scale 3.; +FORCEXYBILLBOARD; } States { Spawn: BSMK ABCDEFGHIJKLMNOPQRST 1 A_Gravitate(); Wait; } } Class YnykronVoidBeamTail : SWWMNonInteractiveActor { Default { RenderStyle "Add"; +FORCEXYBILLBOARD; } override void Tick() { if ( freezetics > 0 ) { freezetics--; return; } if ( isFrozen() ) return; A_FadeOut(); } States { Spawn: XZW1 # -1 Bright; Stop; } } Class YnykronVoidBeam : SWWMNonInteractiveActor { Actor basebeam, prevbeam, nextbeam; YnykronTracer t; double baseang, basept; int angledir, pitchdir; int maxlen, curlen, segnum; int spreadtimer; int maxlife, lifetimer; Default { DamageType 'YnykronAlt'; Obituary "$O_YNYKRONALT"; RenderStyle "Add"; Alpha 0.; Stamina 3; +INTERPOLATEANGLES; +NODAMAGETHRUST; +FOILINVUL; } override void PostBeginPlay() { angle = baseang = FRandom[Ynykron](0,360); pitch = basept = FRandom[Ynykron](-90,90); angledir = RandomPick[Ynykron](-1,1); pitchdir = RandomPick[Ynykron](-1,1); maxlen = Random[Ynykron](40,60); maxlife = Random[Ynykron](20,30); Scale *= FRandom[Ynykron](.75,1.5); lifetimer = -1; } void A_UpdateBeam() { if ( !master ) { Destroy(); return; } if ( basebeam == self ) { baseang += FRandom[Ynykron](0.,5.)*angledir; basept += FRandom[Ynykron](0.,5.)*pitchdir; Vector3 ofs = master.scale.x*32.*SWWMUtility.Vec3FromAngles(baseang,basept); SetOrigin(level.Vec3Offset(master.pos,ofs),true); double da = deltaangle(angle,baseang+FRandom[Ynykron](-5,5)), dp = deltaangle(pitch,basept+FRandom[Ynykron](-5,5)); angle += da*.2; pitch += dp*.2; if ( lifetimer == -1 ) { A_FadeIn(FRandom[Ynykron](.003,.05)); if ( Alpha >= .2 ) { Alpha = .2; lifetimer = 0; } } else if ( lifetimer == -2 ) A_FadeOut(FRandom[Ynykron](.003,.05)); else { lifetimer++; if ( lifetimer > maxlife ) lifetimer = -2; } // check total length int cnt = 0; for ( YnykronVoidBeam b=self; b; b=YnykronVoidBeam(b.nextbeam) ) cnt++; curlen = cnt; } else if ( !prevbeam ) { Destroy(); return; } Vector3 x = SWWMUtility.Vec3FromAngles(angle,pitch); if ( !t ) t = new("YnykronTracer"); t.ShootThroughList.Clear(); t.WaterHitList.Clear(); t.HitList.Clear(); t.Trace(pos,cursector,x,16*scale.x,TRACE_HitSky); foreach ( l:t.ShootThroughList ) { l.Activate(target,0,SPAC_PCross); l.Activate(target,0,SPAC_Impact); } foreach ( hit:t.hitlist ) { // yoink SWWMUtility.DoKnockback(hit.HitActor,-hit.x,80000*scale.x*alpha); } spreadtimer++; if ( (t.Results.HitType != TRACE_HitNone) || (segnum > maxlen) || (spreadtimer < stamina) ) { // trigger walls if ( t.Results.HitType == TRACE_HitWall ) t.Results.HitLine.RemoteActivate(target,t.Results.Side,SPAC_Impact,t.Results.HitPos); // end of the line if ( nextbeam ) nextbeam.Destroy(); if ( basebeam == self ) frame = 3; else frame = 2; } else { if ( basebeam == self ) frame = 0; else frame = 1; // spawn (or relocate) next beam if ( !nextbeam ) { nextbeam = Spawn("YnykronVoidBeam",level.Vec3Offset(pos,x*16*scale.x)); nextbeam.angle = atan2(t.Results.HitVector.y,t.Results.HitVector.x); nextbeam.pitch = asin(-t.Results.HitVector.z); } else nextbeam.SetOrigin(level.Vec3Offset(pos,x*16*scale.x),true); nextbeam.target = target; nextbeam.master = master; YnykronVoidBeam(nextbeam).basebeam = basebeam; YnykronVoidBeam(nextbeam).prevbeam = self; YnykronVoidBeam(nextbeam).maxlen = maxlen; YnykronVoidBeam(nextbeam).segnum = segnum+1; nextbeam.alpha = alpha*(1.-(segnum/double(maxlen))); nextbeam.scale = scale; nextbeam.frame = 1; Vector3 newdir = t.Results.HitVector; newdir = (newdir+.4*SWWMUtility.Vec3FromAngles(FRandom[Ynykron](0,360),FRandom[Ynykron](-90,90))).unit(); double da = deltaangle(nextbeam.angle,atan2(newdir.y,newdir.x)), dp = deltaangle(nextbeam.pitch,asin(-newdir.z)); nextbeam.angle += da*.2; nextbeam.pitch += dp*.2; } let h = Spawn("YnykronVoidBeamTail",pos); h.angle = angle; h.pitch = pitch; h.alpha = alpha*2.; h.scale = scale; h.frame = frame; } States { Spawn: XZW1 # 1 Bright A_UpdateBeam(); Wait; } } Class YnykronLightningImpact : SWWMNonInteractiveActor { Default { Obituary "$O_YNYKRONALT"; DamageType "Electric"; +FOILINVUL; +FORCERADIUSDMG; +NODAMAGETHRUST; } override void PostBeginPlay() { Super.PostBeginPlay(); SWWMUtility.DoExplosion(self,400,120000,100,40); A_QuakeEx(3,3,3,12,0,800,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,falloff:300,rollIntensity:.4); A_SprayDecal("ShockMark",-172); int numpt = Random[ExploS](8,16); for ( int i=0; i 10 ) { dirto /= dist; dir = (dir+.8*dirto*(clamp(1.-(dist/1500.),0.,1.)**1.5)).unit(); } nextdir = dir; } void A_Spread( Sound arcsnd ) { Vector3 tdir = level.Vec3Diff(pos,nextpos); if ( (GetClass() == 'YnykronLightningArc') && !Random[Ynykron](0,3) ) { for ( int i=0; i<3; i++ ) { let r = Spawn("YnykronLightningArcSub",level.Vec3Offset(pos,tdir*FRandom[Ynykron](0,1))); Vector3 x, y, z; [x, y, z] = SWWMUtility.GetAxes(angle,pitch,roll); double a = FRandom[Ynykron](0,360), s = FRandom[Sparkster](0.,1.); Vector3 sdir = SWWMUtility.ConeSpread(x,y,z,a,s); r.angle = atan2(sdir.y,sdir.x); r.pitch = asin(-sdir.z); r.target = target; r.special1 = 1; YnykronLightningArc(r).destpos = destpos; r.ReactionTime += Random[Ynykron](-3,3); } } if ( arcsnd != "" ) A_StartSound(arcsnd,CHAN_WEAPON); if ( ((ReactionTime > 0) && (special1 > ReactionTime)) || bAMBUSH ) return; let b = Spawn(GetClass(),nextpos); b.angle = atan2(nextdir.y,nextdir.x); b.pitch = asin(-nextdir.z); b.target = target; b.special1 = special1+1; YnykronLightningArc(b).destpos = destpos; b.SetState(b.FindState("Trailer")); } override void PostBeginPlay() { Super.PostBeginPlay(); frame = Random[Ynykron](0,11); } Default { Obituary "$O_YNYKRONALT"; RenderStyle "Add"; DamageFunction 1000; Speed 128; Alpha 2.; +FOILINVUL; } States { Spawn: XZW1 # 0 Bright; XZW1 # 1 Bright A_Trace(); XZW1 # 1 Bright A_Spread("ynykron/vortexarc"); XZW1 # 1 Bright A_FadeOut(); Wait; Trailer: XZW2 # 0 Bright; XZW2 # 1 Bright A_Trace(); XZW2 # 1 Bright A_Spread("ynykron/vortexarc"); XZW2 # 1 Bright A_FadeOut(); Wait; } } Class YnykronLightningArcSub : YnykronLightningArc { Default { DamageFunction 250; Speed 32; ReactionTime 10; } States { Spawn: XZW1 # 0 Bright; XZW1 # 1 Bright A_Trace(); XZW1 # 1 Bright A_Spread(""); XZW1 # 1 Bright A_FadeOut(); Wait; Trailer: XZW2 # 0 Bright; XZW2 # 1 Bright A_Trace(); XZW2 # 1 Bright A_Spread(""); XZW2 # 1 Bright A_FadeOut(); Wait; } } Class YnykronCloud : SWWMNonInteractiveActor { Vector3 gx, gy, gz; double phase; Vector3 dirto; double frightening; // lightning flash Color basecol; double rollvel; override void Tick() { prev = pos; Super.Tick(); } void FlashPlayer( int str, double rad ) { if ( !SWWMUtility.InPlayerFOV(players[consoleplayer],self,rad) ) return; let mo = players[consoleplayer].Camera; double dist = Distance3D(mo); str = int(str*(1.-(dist/rad))); SWWMHandler.DoFlash(mo,Color(str,255,255,255),1); SWWMHandler.DoFlash(mo,Color(str/2,240,224,255),10); } override void PostBeginPlay() { SetState(FindState("Spawn")+Random[ExploS](0,19)); Scale *= FRandom[ExploS](.75,1.5); Scale.x *= RandomPick[ExploS](-1,1); Scale.y *= RandomPick[ExploS](-1,1); if ( master ) Scale *= master.scale.x; // orbit axes [gx, gy, gz] = SWWMUtility.GetAxes(FRandom[ExploS](0,360),FRandom[ExploS](-90,90),FRandom[ExploS](-90,90)); specialf1 = FRandom[ExploS](200,400); scale *= specialf1/200.; specialf2 = FRandom[ExploS](3.,8.)*RandomPick[ExploS](-1,1); special2 = Random[ExploS](10,40); basecol = Color(4,3,5)*Random[ExploS](5,20); SetShade(basecol); rollvel = FRandom[ExploS](.5,3.)*RandomPick[ExploS](-1,1); } void A_Gravitate() { if ( frightening > 0. ) { alpha = max(frightening,.3); int str = int(RandomPick[ExploS](255,240,192,254,248,128,160,204)*frightening); Color litecol = Color(min(255,basecol.r+str),min(255,basecol.g+str),min(255,basecol.b+str)); SetShade(litecol); frightening *= .96; if ( frightening < .1 ) { frightening = 0.; SetShade(basecol); } } roll += rollvel; if ( master && !master.InStateSequence(master.CurState,master.FindState("Death")) ) { if ( special1 == 0 ) { A_FadeIn(FRandom[ExploS](.001,.002)); if ( alpha > .3 ) special1 = 1; } else if ( special1 > 0 ) { special1++; if ( special1 >= special2 ) special1 = -1; } else if ( special1 == -1 ) A_FadeOut(FRandom[ExploS](.003,.006)); dirto = level.Vec3Diff(pos,master.pos); double distto = dirto.length(); dirto /= distto; // orbit Vector3 orbitdir = SWWMUtility.CircleOffset(gx,gy,phase,(specialf1+32)*master.scale.x); SetOrigin(level.Vec3Offset(master.pos,orbitdir),true); phase += FRandom[ExploS](.12,.24)*specialf2*(1.-specialf1/600.); return; } SetOrigin(level.Vec3Offset(pos,-dirto*3.*abs(specialf2)),true); specialf2 *= .98; A_FadeOut(.005); } Default { DamageType 'YnykronAlt'; Obituary "$O_YNYKRONALT"; RenderStyle "Shaded"; Alpha 0.; Scale 3.; +FORCEXYBILLBOARD; +ROLLSPRITE; } States { Spawn: BSMK ABCDEFGHIJKLMNOPQRST 1 A_Gravitate(); Wait; } } Class YnykronLightningLight : PaletteLight { Default { Tag "WhiteExpl"; ReactionTime 12; Args 0,0,0,500; } } Class YnykronLightningLight2 : PaletteLight { Default { Tag "WhiteExpl"; ReactionTime 10; Args 0,0,0,300; } } Class SimpleMoveTracer : LineTracer { override ETraceStatus TraceCallback() { if ( Results.HitType == TRACE_HitActor ) return TRACE_Skip; else if ( (Results.HitType == TRACE_HitWall) && (Results.Tier == TIER_Middle) ) { if ( !Results.HitLine.sidedef[1] || (Results.HitLine.Flags&Line.ML_BLOCKPROJECTILE) ) return TRACE_Stop; return TRACE_Skip; } return TRACE_Stop; } } Class YnykronSingularityRing : SWWMNonInteractiveActor { Default { RenderStyle "Add"; Scale 4.; +FORCEXYBILLBOARD; } States { Spawn: XRG4 AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXX 1 Bright A_SetScale(scale.x*1.05); Stop; } } Class YnykronVoidExplLight : PaletteLight { Default { Tag "Purple"; ReactionTime 60; Args 0,0,0,1200; } } Class YnykronVoidSparkleTrail : SWWMNonInteractiveActor { Default { RenderStyle "Add"; XScale 6.; +FORCEXYBILLBOARD; } override void Tick() { if ( freezetics > 0 ) { freezetics--; return; } if ( isFrozen() ) return; A_SetScale(scale.x*.95,scale.y); A_FadeOut(.04); } States { Spawn: XZW1 A -1 Bright; Stop; } } Class YnykronVoidSparkle : SWWMNonInteractiveActor { Default { RenderStyle "Add"; +ROLLSPRITE; +ROLLCENTER; +FORCEXYBILLBOARD; } override void PostBeginPlay() { Scale *= FRandom[ExploS](.75,1.5); specialf1 = FRandom[ExploS](.95,.98); specialf2 = FRandom[ExploS](.005,.015); vel = SWWMUtility.Vec3FromAngles(angle,pitch)*FRandom[ExploS](4,32); } override void Tick() { if ( freezetics > 0 ) { freezetics--; return; } if ( isFrozen() ) return; A_SetScale(scale.x*specialf1); A_FadeOut(specialf2); Vector3 dir = vel; double magvel = dir.length(); magvel *= .99; if ( magvel > 0. ) { dir /= magvel; dir += .2*SWWMUtility.Vec3FromAngles(FRandom[ExploS](0,360),FRandom[ExploS](-90,90)); vel = dir.unit()*magvel; } Vector3 newpos = level.Vec3Offset(pos,vel); if ( (newpos.z < floorz) || (newpos.z >= ceilingz) ) { vel.z *= -1; newpos = level.Vec3Offset(pos,vel); } if ( !level.IsPointInLevel(newpos) ) { vel.xy *= -1; newpos = level.Vec3Offset(pos,vel); } dir = level.Vec3Diff(newpos,pos); double len = dir.length(); if ( (len > 0.) && (alpha > .1) ) { dir /= len; let t = Spawn("YnykronVoidSparkleTrail",newpos); t.alpha = alpha*.5; t.scale.x *= scale.x; t.speed = len; [t.angle, t.pitch, t.scale.y] = SWWMUtility.CalcYBeam(dir,len); } SetOrigin(newpos,true); } States { Spawn: MOPF A -1 Bright; Stop; } } Class YnykronSingularityExplosionArm : Actor { Default { PROJECTILE; +THRUACTORS; +BOUNCEONWALLS; +BOUNCEONFLOORS; +BOUNCEONCEILINGS; +NODAMAGETHRUST; +FORCERADIUSDMG; -NOGRAVITY; +NOFRICTION; Gravity 0.35; BounceFactor 1.0; Radius 2; Height 4; } override void PostBeginPlay() { Super.PostBeginPlay(); reactiontime = Random[ExploS](10,20); double ang, pt; ang = FRandom[ExploS](0,360); pt = FRandom[ExploS](-90,90); vel = SWWMUtility.Vec3FromAngles(ang,pt)*FRandom[ExploS](20.,40.); } States { Spawn: TNT1 A 1 { A_CountDown(); if ( !(reactiontime%2) ) { Spawn("YnykronSingularityExplosionTrail",pos); Vector3 pvel = SWWMUtility.Vec3FromAngles(FRandom[ExploS](0,360),FRandom[ExploS](-90,90))*FRandom[ExploS](1,5); let s = Spawn("SWWMHalfSmoke",pos); s.SetShade(Color(1,1,1)*Random[ExploS](128,160)+Color(28,0,31)); s.A_SetRenderStyle(s.alpha*(.1+.4*(ReactionTime/15.)),STYLE_AddShaded); s.vel = pvel+vel*.2; s.special1 = Random[ExploS](1,3); s.scale *= 2.4; } } Wait; } } Class YnykronSingularityExplosionTrail : SWWMNonInteractiveActor { Default { RenderStyle "Add"; +FORCEXYBILLBOARD; Scale 3.; Alpha .2; } override void PostBeginPlay() { Super.PostBeginPlay(); Scale *= FRandom[ExploS](0.8,1.1); Scale.x *= RandomPick[ExploS](-1,1); Scale.y *= RandomPick[ExploS](-1,1); } States { Spawn: XEX4 ABCDEFGHIJKLMNOPQRSTUVWXYZ[\ 1 Bright; Stop; } } Class YnykronSingularityExplosion : SWWMNonInteractiveActor { Default { Obituary "$O_YNYKRONALT"; DamageType "YnykronAlt"; RenderStyle "Add"; Scale 5.; +NODAMAGETHRUST; +FORCERADIUSDMG; +FORCEXYBILLBOARD; +FOILINVUL; } override void PostBeginPlay() { Super.PostBeginPlay(); if ( !Random[ExploS](0,5) ) A_StartSound("ynykron/impact",CHAN_VOICE); SWWMUtility.DoExplosion(self,10000,5000,400,200); A_SprayDecal("WumboRocketBlast",-172); Scale *= FRandom[ExploS](0.8,1.1); Scale.x *= RandomPick[ExploS](-1,1); Scale.y *= RandomPick[ExploS](-1,1); int numpt = Random[ExploS](2,4); for ( int i=0; i 0 ) { freezetics--; return; } if ( isFrozen() ) return; Vector3 newpos; if ( !mt ) mt = new("SimpleMoveTracer"); Vector3 dir = vel; double dist = vel.length(); dir /= dist; mt.Trace(pos,CurSector,dir,dist,0); if ( mt.Results.HitType == TRACE_HitNone ) newpos = level.Vec3Offset(pos,vel); else newpos = level.Vec3Offset(mt.Results.HitPos,-mt.Results.HitVector); if ( level.IsPointInLevel(newpos) ) SetOrigin(newpos,true); if ( !CheckNoDelay() || (tics == -1) ) return; if ( tics > 0 ) tics--; while ( !tics ) { if ( !SetState(CurState.NextState) ) return; } } override String GetObituary( Actor victim, Actor inflictor, Name mod, bool playerattack ) { if ( mod == 'Ynykron' ) return StringTable.Localize("$O_YNYKRON"); return Super.GetObituary(victim,inflictor,mod,playerattack); } void A_SingularityTick() { // gather dust particles (minor "background" mass gain) int numpt = int(max(4,20*scale.x)); for ( int i=0; i -1) ) continue; clouds[i] = Spawn("YnykronCloud",pos); clouds[i].target = target; clouds[i].master = self; } // check dense cloud formations for lightning if ( level.maptime > litetimer ) { int which = Random[Ynykron](0,MAXCLOUDS-1); if ( clouds[which] ) { Array contacts; Array dists; contacts.Clear(); dists.Clear(); for ( int i=0; i (200*scale.x) ) continue; contacts.Push(clouds[i]); dists.Push(dist); } if ( (contacts.Size() > 12) && !Random[Ynykron](0,5) ) { Spawn("YnykronLightningLight",clouds[which].pos); Spawn("YnykronLightningLight2",clouds[which].pos); A_StartSound("ynykron/vortexflash",CHAN_WEAPON,CHANF_OVERLAP,1.,0.,FRandom[Ynykron](.9,1.1)); YnykronCloud(clouds[which]).frightening = 1.; YnykronCloud(clouds[which]).FlashPlayer(200,3000); for ( int i=0; i 0 ) { // force use the sandwich, warp to safe spot let gc = GrilledCheeseSandwich(a.FindInventory("GrilledCheeseSandwich")); gc.SafeTeleport(true); a.A_StartSound(gc.UseSound,CHAN_ITEMEXTRA); gc.DoTheThing(true); gc.Amount--; } else if ( !a.FindInventory("GrilledCheeseSafeguard") ) a.DamageMobj(self,target,int.max,'Ynykron',DMG_FORCED|DMG_THRUSTLESS); if ( a && (a.Health <= 0) ) { if ( a.player ) { if ( a == target ) { SWWMUtility.MarkAchievement("oopsie",a.player); target = PlayerGone.FeckOff(a); } else PlayerGone.FeckOff(a); } if ( a.FindState("YnykronAltDeath",true) ) a.SetStateLabel("YnykronAltDeath"); // dedicated state else { // poof away manually a.bINVISIBLE = true; a.A_ChangeLinkFlags(false); // remove from blockmap, should guarantee archviles not raising this IDontFeelSoGood.DeletThis(a,true); // ensures corpse is deleted too } if ( target && a.FindInventory("EndgameBossMarker") ) SWWMUtility.MarkAchievement("ligma",target.player); } if ( !a || (a.Health <= 0) ) specialf2 += min(100.,capmass*.6); // partial absorption continue; } capmass = max(50.,a.mass); Vector3 dirto = level.Vec3Diff(a.Vec3Offset(0,0,a.Height/2),pos); double dist = dirto.length(); dirto /= dist; // rip if ( a && a.bSHOOTABLE && SWWMUtility.SphereIntersect(a,pos,200.*scale.x) ) a.DamageMobj(self,target,int(clamp(10.-dist*.05,0.,5.)),'YnykronAlt',DMG_FORCED|DMG_THRUSTLESS); if ( !a ) continue; // weak gravitational force as per v-field (approximate) double grav = (3.*YNON_CONST*specialf2*capmass)/(dist**.5); // strong gravitational force as per u-field (approximate) grav += (4.*AXAN_CONST*specialf2*capmass)/(dist**2.); // account for ground friction if ( a.pos.z <= a.floorz ) { double frict, movef; [frict, movef] = a.GetFriction(); grav *= movef; if ( !a.player ) dirto.z += .1; } if ( a.mass < LARGE_MASS ) { // v-force field compression (very rough approximation) a.vel *= max(.0,1.-.03*(grav*grav)/(MION_CONST*MION_CONST)); a.vel += dirto*grav/(capmass*GameTicRate); } } } // push away from nearby geometry double pushdist = 50+32*scale.x; for ( int i=0; i<8; i++ ) { double ang = FRandom[ExploS](0,360); double pt = FRandom[ExploS](-90,90); FLineTraceData d; if ( !LineTrace(ang,pushdist,pt,TRF_THRUACTORS|TRF_THRUHITSCAN,data:d) ) continue; double mag = pushdist-d.Distance; vel -= SWWMUtility.Vec3FromAngles(ang,pt)*.02*mag; } double magvel = vel.length(); Vector3 dir; if ( magvel <= double.epsilon ) dir = SWWMUtility.Vec3FromAngles(angle,pitch); else dir = vel/magvel; // wander dir = (dir+SWWMUtility.Vec3FromAngles(FRandom[Ynykron](0,360),FRandom[Ynykron](-90,90))*FRandom[Ynykron](.05,.2)).unit(); magvel *= .93; if ( magvel > 5. ) magvel *= .95; if ( magvel > 20. ) magvel *= .65; if ( magvel < .1 ) magvel = .1; vel = magvel*dir; // calculate target scale based on radius of event horizon specialf1 = (specialf2/MION_CONST)**.5; // shift to target scale // hopefully this supports interpolation someday double diffscale = specialf1-scale.x; if ( abs(diffscale) <= .01 ) A_SetScale(specialf1); else A_SetScale(scale.x+diffscale*.3); // detonate if we reach critical mass if ( specialf2 >= critmass ) SetStateLabel("Death"); } void FlashPlayer( int str, double rad ) { if ( !SWWMUtility.InPlayerFOV(players[consoleplayer],self,rad) ) return; let mo = players[consoleplayer].Camera; double dist = Distance3D(mo); str = int(str*(1.-(dist/rad))); SWWMHandler.DoFlash(mo,Color(str,255,255,255),3); SWWMHandler.DoFlash(mo,Color(str/2,240,224,255),30); } void A_SingularityBlast() { A_ChangeLinkFlags(true); // unlink from blockmap A_AlertMonsters(0,AMF_EMITFROMTARGET); SWWMUtility.DoExplosion(self,int.max,500000,800,400); A_QuakeEx(8,8,8,100,0,65535,"",QF_RELATIVE|QF_SCALEDOWN,falloff:65535,rollIntensity:1.6); A_StopAllSounds(); A_StartSound("ynykron/vortexend",CHAN_VOICE,attenuation:0.); FlashPlayer(250,9000); vel *= 0; // kill off any leftover beams for ( int i=0; i 60 ) { if ( IsActorPlayingSound(CHAN_VOICE,-1) ) return; for ( int i=0; i 0 ) { LineTrace(ang,totaldist,pt,TRF_THRUACTORS|TRF_ABSPOSITION,origin.z,origin.x,origin.y,d); HitNormal = SWWMUtility.GetLineTraceHitNormal(d); totaldist -= d.Distance; if ( totaldist > 0 ) { Vector3 bounced = d.HitDir-(1.2*hitnormal*(d.HitDir dot HitNormal)); ang = atan2(bounced.y,bounced.x); pt = asin(-bounced.z); origin = d.HitLocation+hitnormal; } } let p = Spawn("YnykronSingularityExplosion",d.HitLocation+hitnormal*4); p.angle = atan2(hitnormal.y,hitnormal.x); p.pitch = asin(-hitnormal.z); p.target = target; p.scale *= 1.+special1/40.; } } Default { DamageType 'YnykronAlt'; Obituary "$O_YNYKRONALT"; +MISSILE; Scale .4; } States { Spawn: XZW1 A 1 BRIGHT A_SingularityTick(); Wait; Death: TNT1 A 0 A_SingularityBlast(); TNT1 A 1 A_SingularityExtraBlast(); Wait; } } Class YnykronAltBeam : SWWMNonInteractiveActor { bool nospread; void TraceOut() { Vector3 x = SWWMUtility.Vec3FromAngles(angle,pitch); let t = new("YnykronAltTracer"); t.ignore = target; t.ShootThroughList.Clear(); t.WaterHitList.Clear(); t.Trace(pos,cursector,x,speed,TRACE_HitSky); foreach ( l:t.ShootThroughList ) { l.Activate(target,0,SPAC_PCross); l.Activate(target,0,SPAC_Impact); } foreach ( w:t.WaterHitList ) { let b = Spawn("InvisibleSplasher",w.hitpos); b.target = target; b.A_CheckTerrain(); } for ( int i=0; i= 25600 ) { // end of the line, dissipate int numpt = Random[Ynykron](16,24); for ( int i=0; i 0 ) { freezetics--; return; } if ( isFrozen() ) return; A_FadeOut(FRandom[Ynykron](.01,.02)); special2++; if ( special2 == 1 ) SpreadOut(); if ( !CheckNoDelay() || (tics == -1) ) return; if ( tics > 0 ) tics--; while ( !tics ) { if ( !SetState(CurState.NextState) ) return; } } Default { RenderStyle "Subtract"; Alpha .4; Speed 64; +FORCEXYBILLBOARD; +ROLLSPRITE; +ROLLCENTER; } States { Spawn: XZW1 A -1 Bright NoDelay { return FindState("StarterDev")+Random[Ynykron](0,3)*2; } Stop; TrailSpawn: XZW2 A -1 Bright { return FindState("TrailerDev")+Random[Ynykron](0,3)*2; } Stop; StarterDev: #### # 50 Bright; XZW1 B -1 Bright; Stop; #### # 50 Bright; XZW1 C -1 Bright; Stop; #### # 50 Bright; XZW1 D -1 Bright; Stop; #### # 50 Bright; XZW1 E -1 Bright; Stop; TrailerDev: #### # 50 Bright; XZW2 B -1 Bright; Stop; #### # 50 Bright; XZW2 C -1 Bright; Stop; #### # 50 Bright; XZW2 D -1 Bright; Stop; #### # 50 Bright; XZW2 E -1 Bright; Stop; } } Class YnykronAltShot : SWWMNonInteractiveActor { void FlashPlayer( int str, double rad ) { if ( !SWWMUtility.InPlayerFOV(players[consoleplayer],self,rad) ) return; let mo = players[consoleplayer].Camera; double dist = Distance3D(mo); str = int(str*(1.-(dist/rad))); SWWMHandler.DoFlash(mo,Color(str,255,255,255),5); SWWMHandler.DoFlash(mo,Color(str/2,0,0,0),15); } override void PostBeginPlay() { A_QuakeEx(4,4,4,80,0,65535,"",QF_RELATIVE|QF_SCALEDOWN,falloff:65535,rollIntensity:.8); A_StartSound("ynykron/altbeam",CHAN_VOICE,CHANF_DEFAULT,1.,0.); FlashPlayer(240,8000); let b = Spawn("YnykronAltBeam",pos); b.target = target; b.angle = angle; b.pitch = pitch; } override void Tick() { if ( freezetics > 0 ) { freezetics--; return; } if ( isFrozen() || IsActorPlayingSound(CHAN_VOICE,-1) ) return; Destroy(); } }