swwmgz_m/zscript/weapons/swwm_deathlydeathcannon_fx.zsc

2701 lines
68 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Ynykron projectiles and effects
// there was an enemy here, but it's gone now
Class AshenRemains : SWWMNonInteractiveActor
{
override void Tick()
{
if ( freezetics > 0 )
{
freezetics--;
return;
}
if ( isFrozen() ) return;
double fz = CurSector.floorplane.ZAtPoint(pos.xy);
if ( fz != pos.z ) SetOrigin((pos.x,pos.y,fz),true);
special1++;
if ( special1 > 350 ) A_FadeOut(0.01);
}
override void PostBeginPlay()
{
if ( (waterlevel > 0) || GetFloorTerrain().isliquid )
{
Destroy();
return;
}
double fz = CurSector.floorplane.ZAtPoint(pos.xy);
SetZ(fz);
prev.z = fz;
SWWMUtility.SetToSlope(self,FRandom[Ynykron](0,360));
}
default
{
RenderStyle "Shaded";
StencilColor "000000";
}
States
{
Spawn:
XZW1 A -1;
Stop;
}
}
// cheap way to let players know they just got fucking erased from existence
Class PlayerGone : PlayerChunk
{
int deadtimer;
override void DeathThink()
{
deadtimer++;
if ( (deadtimer == 60) && (player == players[consoleplayer]) )
A_StartSound("demolitionist/youdied",CHAN_DEMOVOICE,CHANF_OVERLAP|CHANF_UI);
if ( multiplayer || level.AllowRespawn || sv_singleplayerrespawn || G_SkillPropertyInt(SKILLP_PlayerRespawn) )
{
// standard behaviour, respawn normally
if ( (((player.cmd.buttons&BT_USE) || ((deathmatch || alwaysapplydmflags) && sv_forcerespawn)) && !sv_norespawn)
&& ((Level.maptime >= player.respawn_time) || ((player.cmd.buttons&BT_USE) && !player.Bot)) )
{
player.cls = null;
player.playerstate = PST_REBORN;
if ( special1 > 2 ) special1 = 0;
}
}
else if ( (player.cmd.buttons&BT_USE) && (deadtimer > 120) )
{
// reload save
player.cls = null;
player.playerstate = PST_ENTER;
if ( special1 > 2 ) special1 = 0;
}
// no revive (for obvious reasons)
}
static Actor FeckOff( Actor p )
{
// doesn't affect voodoo dolls (convenient, isn't it?)
if ( !p.player || (p.player.mo != p) ) return p;
let c = PlayerGone(Spawn("PlayerGone",(65535,65535,0)));
c.player = p.player;
c.Health = p.Health;
p.player = null;
c.ObtainInventory(p);
if ( c.player )
{
c.player.mo = c;
c.player.damagecount = 0;
c.player.bonuscount = 0;
c.player.poisoncount = 0;
}
for ( int i=0; i<MAXPLAYERS; i++ )
{
if ( playeringame[i] && (players[i].camera == p) )
players[i].camera = c;
}
return c;
}
States
{
Spawn:
TNT1 A 0;
TNT1 A 1 A_CheckPlayerDone();
Wait;
}
}
Class DSparilHax : Sorcerer2
{
override void PostBeginPlay()
{
Thing_Destroy(0);
A_BossDeath();
Destroy();
}
}
Class IDontFeelSoGood : Thinker
{
Actor goner;
int cnt;
bool silence;
static void DeletThis( Actor whomst, bool silence = false )
{
if ( !whomst || (whomst.Health > 0) ) return;
let ti = ThinkerIterator.Create("IDontFeelSoGood",STAT_USER);
IDontFeelSoGood i;
while ( i = IDontFeelSoGood(ti.Next()) )
if ( i.goner == whomst ) return;
i = new("IDontFeelSoGood");
i.ChangeStatNum(STAT_USER);
i.goner = whomst;
i.silence = silence;
}
override void Tick()
{
if ( !goner )
{
Destroy();
return;
}
if ( goner.Health > 0 )
{
goner.bINVISIBLE = goner.default.bINVISIBLE;
goner.A_ChangeLinkFlags(goner.default.bNOBLOCKMAP);
Destroy();
return;
}
if ( silence ) goner.A_StopAllSounds();
goner.DamageType = 'Massacre'; // prevents enemies such as pain elementals from spawning anything
// special handling for some bosses:
// - D'Sparil does not spawn, he's already gone
// - Korax doesn't leave ghosts
if ( (goner.tics != -1) && !(goner is 'Sorcerer1') && !(goner is 'Korax') ) return;
cnt++;
if ( cnt < 15 ) return;
if ( goner is 'Sorcerer1' )
{
let h = Actor.Spawn("DSparilHax",goner.pos);
h.CopyFriendliness(goner,true);
}
else if ( goner is 'Korax' )
level.ExecuteSpecial(80,goner,null,0,255);
goner.Destroy();
}
}
Class YnykronImpactLight : PaletteLight
{
Default
{
Tag "WhiteExpl,1";
ReactionTime 60;
Args 0,0,0,300;
}
}
Class YnykronSubImpactLight : PaletteLight
{
Default
{
Tag "WhiteExpl,1";
ReactionTime 20;
Args 0,0,0,120;
}
}
Class YnykronShotLight : PaletteLight
{
Default
{
Tag "WhiteExpl";
ReactionTime 100;
Args 0,0,0,800;
}
}
Class YnykronBeamLight : PaletteLight
{
Default
{
Tag "WhiteExpl,1";
ReactionTime 50;
Args 0,0,0,250;
}
}
Class YnykronImpactArm : Actor
{
Default
{
PROJECTILE;
+THRUACTORS;
+BOUNCEONWALLS;
+BOUNCEONFLOORS;
+BOUNCEONCEILINGS;
+CANBOUNCEWATER;
+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("YnykronImpactTrail",pos);
Vector3 pvel = SWWMUtility.Vec3FromAngles(FRandom[ExploS](0,360),FRandom[ExploS](-90,90))*FRandom[ExploS](1,5);
let s = Spawn("SWWMHalfSmoke",pos);
s.vel = pvel+vel*.2;
s.special1 = Random[ExploS](1,3);
s.scale *= 2.4;
s.alpha *= 0.1+.4*(ReactionTime/15.);
}
}
Wait;
}
}
Class YnykronImpactTrail : SWWMNonInteractiveActor
{
Default
{
RenderStyle "Add";
+FORCEXYBILLBOARD;
Scale 2.;
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:
MOXP ABCDEFGHIJKLMNOPQRSTUVWXYZ[\ 1 Bright;
Stop;
}
}
Class YnykronDelayedImpact : SWWMNonInteractiveActor
{
Vector3 ofs;
override void Tick()
{
if ( freezetics > 0 )
{
freezetics--;
return;
}
if ( isFrozen() ) return;
special1++;
if ( special1 < 4 )
{
if ( tracer ) SetOrigin(level.Vec3Offset(tracer.pos,ofs),false);
return;
}
let b = Spawn("YnykronImpact",pos);
b.tracer = tracer;
b.target = target;
b.master = master;
b.angle = angle;
b.pitch = pitch;
b.special1 = special2;
b.special2 = 1;
b.args[0] = args[0];
Destroy();
}
}
Class YnykronImpact : SWWMNonInteractiveActor
{
int rad;
Default
{
Obituary "$O_YNYKRON";
DamageType "Ynykron";
RenderStyle "Add";
Scale 5.;
+FORCEXYBILLBOARD;
+NODAMAGETHRUST;
+FORCERADIUSDMG;
+EXTREMEDEATH;
}
private bool CmpDist( Actor a, Actor b )
{
double dista = Vec3To(a).length();
double distb = Vec3To(b).length();
return (dista > distb);
}
// quicksort (candidates)
private int partition_candidates( Array<Actor> a, int l, int h )
{
Actor pv = a[h];
int i = (l-1);
for ( int j=l; j<=(h-1); j++ )
{
if ( CmpDist(pv,a[j]) )
{
i++;
Actor tmp = a[j];
a[j] = a[i];
a[i] = tmp;
}
}
Actor tmp = a[h];
a[h] = a[i+1];
a[i+1] = tmp;
return i+1;
}
private void qsort_candidates( Array<Actor> a, int l, int h )
{
if ( l >= h ) return;
int p = partition_candidates(a,l,h);
qsort_candidates(a,l,p-1);
qsort_candidates(a,p+1,h);
}
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),2);
SWWMHandler.DoFlash(mo,Color(str/2,255,255,255),10);
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
rad = args[0]+300+10*clamp(special1/10,0,15);
A_QuakeEx(4,4,4,50,0,rad*4,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,falloff:rad*2,rollintensity:.6);
FlashPlayer(60,1200);
if ( tracer )
{
// voodoo dolls just get erased (how convenient)
// otherwise instantly vaporize the fucker
if ( tracer.player && (tracer.player.mo != tracer) )
{
if ( tracer.pos.z < (tracer.floorz+64) )
{
let r = Spawn("AshenRemains",tracer.pos);
r.scale *= tracer.radius/16.;
}
tracer.Destroy();
}
else if ( tracer.CountInv("GrilledCheeseSandwich") > 0 )
{
// force use the sandwich
let gc = GrilledCheeseSandwich(tracer.FindInventory("GrilledCheeseSandwich"));
tracer.A_StartSound(gc.UseSound,CHAN_ITEMEXTRA);
gc.DoTheThing(true);
gc.Amount--;
}
else if ( !tracer.FindInventory("GrilledCheeseSafeguard") )
tracer.DamageMobj(self,target,int.max,'Ynykron',DMG_FORCED|DMG_THRUSTLESS);
if ( tracer && (tracer.Health <= 0) )
{
if ( tracer.player )
{
if ( tracer == target )
{
SWWMUtility.MarkAchievement("oopsie",tracer.player);
target = PlayerGone.FeckOff(tracer);
}
else PlayerGone.FeckOff(tracer);
}
if ( tracer.FindState("YnykronDeath",true) )
tracer.SetStateLabel("YnykronDeath"); // dedicated state
else
{
// poof away manually
tracer.bINVISIBLE = true;
tracer.A_ChangeLinkFlags(true); // remove from blockmap, should guarantee archviles not raising this
IDontFeelSoGood.DeletThis(tracer); // ensures corpse is deleted too
}
if ( tracer.pos.z < (tracer.floorz+64) )
{
let r = Spawn("AshenRemains",tracer.pos);
r.scale *= tracer.radius/16.;
}
if ( (tracer.bIsMonster || tracer.player) && (!target || tracer.IsHostile(target)) && YnykronShot(master) )
YnykronShot(master).enemykills++;
if ( target && tracer.FindInventory("EndgameBossMarker") )
SWWMUtility.MarkAchievement("ligma",target.player);
}
}
if ( YnykronShot(master) )
{
if ( YnykronShot(master).lastimpact < gametic )
{
master.A_StartSound("ynykron/hit",CHAN_VOICE,CHANF_OVERLAP,special1?.4:1.,.0);
YnykronShot(master).lastimpact = gametic+(special1?2:5);
}
}
else A_StartSound("ynykron/hit",CHAN_VOICE,CHANF_OVERLAP,special1?.4:1.,.0);
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);
if ( special2 ) numpt -= 3;
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = SWWMUtility.Vec3FromAngles(FRandom[ExploS](0,360),FRandom[ExploS](-90,90))*FRandom[ExploS](.5,2);
let s = Spawn("SWWMHalfSmoke",pos);
s.vel = pvel;
s.special1 = Random[ExploS](1,8);
s.scale *= 3.;
s.alpha *= .4;
}
if ( special2 && !Random[ExploS](0,4) ) Spawn("YnykronSubImpactLight",pos);
else if ( !special2 ) Spawn("YnykronImpactLight",pos);
let r = Spawn("YnykronImpactRing",pos);
r.special2 = special2;
r.scale *= abs(scale.x)/5.;
numpt = Random[ExploS](special2?-4:0,3);
for ( int i=0; i<numpt; i++ )
{
let s = Spawn("YnykronImpactArm",pos);
s.target = target;
}
let bt = BlockThingsIterator.Create(self,rad+200);
Array<Actor> candidates;
candidates.Clear();
while ( bt.Next() )
{
let t = bt.Thing;
if ( !t || !t.bSHOOTABLE || !SWWMUtility.SphereIntersect(t,pos,rad) || (!SWWMUtility.SphereIntersect(t,pos,100) && !CheckSight(t,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY)) ) continue;
if ( YnykronShot(master) && (YnykronShot(master).hitlist.Find(t) < YnykronShot(master).hitlist.Size()) )
continue;
Vector3 dirto = level.Vec3Diff(pos,t.Vec3Offset(0,0,t.Height/2));
double dist = dirto.length();
if ( (dist > rad/2) && (t == target) ) continue;
candidates.Push(t);
}
qsort_candidates(candidates,0,candidates.Size()-1);
candidates.Resize(2);
for ( int i=0; i<candidates.Size(); i++ )
{
let t = candidates[i];
if ( !t ) continue;
Vector3 dirto = level.Vec3Diff(pos,t.Vec3Offset(0,0,t.Height/2));
double dist = dirto.length();
dirto /= dist;
int trad = int(max(t.radius,t.height));
if ( t && YnykronShot(master) )
{
YnykronShot(master).hitlist.Push(t);
if ( t.bBOSS || t.FindInventory("BossMarker") ) YnykronShot(master).hitboss = true;
}
// spawn blast that will propagate
let b = Spawn("YnykronDelayedImpact",t.pos);
Vector3 ofs = level.Vec3Diff(t.pos,level.Vec3Offset(pos,dirto*min(rad,dist)));
YnykronDelayedImpact(b).ofs = ofs;
b.tracer = t;
b.target = target;
b.master = master;
b.angle = atan2(dirto.y,dirto.x);
b.pitch = asin(-dirto.z);
b.special2 = special1+(Random[Ynykron](0,8)?1:0);
b.special1 = Random[Ynykron](-5,0)-min(special1/2,10);
b.args[0] = trad;
if ( YnykronShot(master) )
YnykronShot(master).blastcount++;
}
}
override void OnDestroy()
{
if ( YnykronShot(master) )
YnykronShot(master).blastcount--;
Super.OnDestroy();
}
States
{
Spawn:
MOXP ABCDEFGHIJKLMNOPQRSTUVWXYZ[\ 3 Bright A_SetTics(special2?1:3);
Stop;
}
}
Class YnykronTracer : LineTracer
{
Actor ignore;
Array<Line> ShootThroughList;
Array<WaterHit> WaterHitList;
Array<HitListEntry> HitList;
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') )
{
let ent = new("HitListEntry");
ent.hitactor = Results.HitActor;
ent.hitlocation = Results.HitPos;
ent.x = Results.HitVector;
hitlist.Push(ent);
}
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 YnykronInWallTracer : YnykronTracer
{
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 )
{
let ent = new("HitListEntry");
ent.hitactor = Results.HitActor;
ent.hitlocation = Results.HitPos;
ent.x = Results.HitVector;
hitlist.Push(ent);
}
return TRACE_Skip;
}
else if ( (Results.HitType == TRACE_HitWall) )
ShootThroughList.Push(Results.HitLine);
return TRACE_Skip;
}
}
Class YnykronBeam : SWWMNonInteractiveActor
{
bool nospread;
void TraceOut()
{
Vector3 x, y, z;
[x, y, z] = swwm_CoordUtil.GetAxes(pitch,angle,roll);
let t = new("YnykronTracer");
t.ignore = target;
t.ShootThroughList.Clear();
t.WaterHitList.Clear();
t.HitList.Clear();
t.Trace(pos,cursector,x,speed,TRACE_HitSky);
for ( int i=0; i<t.ShootThroughList.Size(); i++ )
{
t.ShootThroughList[i].Activate(target,0,SPAC_PCross);
t.ShootThroughList[i].Activate(target,0,SPAC_Impact);
}
for ( int i=0; i<t.WaterHitList.Size(); i++ )
{
let b = Spawn("InvisibleSplasher",t.WaterHitList[i].hitpos);
b.target = target;
b.A_CheckTerrain();
}
for ( int i=0; i<t.Results.Distance; i+=32 )
{
if ( Random[Ynykron](0,5) ) continue;
let b = Spawn("SWWMHalfSmoke",level.Vec3Offset(pos,x*i));
b.Scale *= FRandom[Ynykron](1.6,2.8);
b.special1 = Random[Ynykron](3,5);
b.alpha *= .3;
b.vel += x*FRandom[Ynykron](-.2,.4);
}
for ( int i=0; i<t.HitList.Size(); i++ )
{
if ( YnykronShot(master) && (YnykronShot(master).hitlist.Find(t.HitList[i].hitactor) < YnykronShot(master).hitlist.Size()) )
continue;
if ( t.hitlist[i].hitactor is 'YnykronSingularityHitbox' )
{
// detonate it instantly
let s = YnykronSingularity(t.hitlist[i].hitactor.target);
s.specialf2 = s.critmass;
let b = Spawn("YnykronImpact",t.hitlist[i].hitlocation);
b.target = target;
b.master = master;
b.angle = atan2(t.HitList[i].x.y,t.HitList[i].x.x);
b.pitch = asin(-t.HitList[i].x.z);
if ( YnykronShot(master) )
{
YnykronShot(master).hitlist.Push(t.hitlist[i].hitactor);
YnykronShot(master).blastcount++;
}
continue;
}
int trad = int(max(t.hitlist[i].hitactor.radius,t.hitlist[i].hitactor.height));
if ( t.hitlist[i].hitactor && YnykronShot(master) )
{
YnykronShot(master).hitlist.Push(t.hitlist[i].hitactor);
if ( t.hitlist[i].hitactor.bBOSS || t.hitlist[i].hitactor.FindInventory("BossMarker") ) YnykronShot(master).hitboss = true;
}
// spawn blast that will propagate
let b = Spawn("YnykronImpact",t.hitlist[i].hitlocation);
b.tracer = t.HitList[i].hitactor;
b.target = target;
b.master = master;
b.angle = atan2(t.HitList[i].x.y,t.HitList[i].x.x);
b.pitch = asin(-t.HitList[i].x.z);
b.args[0] = trad;
if ( YnykronShot(master) )
YnykronShot(master).blastcount++;
}
if ( t.Results.HitType == TRACE_HasHitSky )
{
// goodbye
nospread = true;
speed = t.Results.Distance; // shortens in minimap
return;
}
if ( t.Results.HitType != TRACE_HitNone )
{
// hit something
Vector3 norm = -t.Results.HitVector;
if ( t.Results.HitType == TRACE_HitWall )
{
norm = (t.Results.HitLine.delta.y,-t.Results.HitLine.delta.x,0).unit();
if ( t.Results.Side ) norm *= -1;
t.Results.HitLine.RemoteActivate(tracer,t.Results.Side,SPAC_Impact,t.Results.HitPos);
}
else if ( t.Results.HitType == TRACE_HitFloor )
{
if ( t.Results.ffloor ) norm = -t.Results.ffloor.top.Normal;
else norm = t.Results.HitSector.floorplane.Normal;
}
else if ( t.Results.HitType == TRACE_HitCeiling )
{
if ( t.Results.ffloor ) norm = -t.Results.ffloor.bottom.Normal;
else norm = t.Results.HitSector.ceilingplane.Normal;
}
let b = Spawn("YnykronImpact",level.Vec3Offset(t.Results.HitPos,norm*16));
b.target = target;
b.master = master;
b.angle = atan2(norm.y,norm.x);
b.pitch = asin(-norm.z);
b.A_SprayDecal("YnykronBlast",-172);
if ( YnykronShot(master) ) YnykronShot(master).blastcount++;
if ( swwm_omnibust ) BusterWall.Bust(t.Results,int.max,target,t.Results.HitVector,t.Results.HitPos.z);
// find exit point
int maxdist = (25600-special1);
int i;
for ( i=int(speed); i<maxdist; i++ )
{
Vector3 ofs = level.Vec3Offset(pos,x*i);
if ( !level.IsPointInLevel(ofs) ) continue;
// we got out, spawn a beam here (delayed)
let next = Spawn("DelayedWallBeam",ofs);
next.angle = atan2(x.y,x.x);
next.pitch = asin(-x.z);
next.roll = roll;
next.target = target;
next.master = master;
next.special1 = special1+i;
next.special2 = (i+Random[Ynykron](0,10))/50;
break;
}
// extra hit traces
let it = new("YnykronInWallTracer");
it.ignore = target;
it.ShootThroughList.Clear();
it.WaterHitList.Clear();
it.HitList.Clear();
it.Trace(t.Results.HitPos,level.PointInSector(t.Results.HitPos.xy),x,i,TRACE_HitSky);
for ( int i=0; i<it.ShootThroughList.Size(); i++ )
{
it.ShootThroughList[i].Activate(target,0,SPAC_PCross);
it.ShootThroughList[i].Activate(target,0,SPAC_Impact); // just in case
}
for ( int i=0; i<it.WaterHitList.Size(); i++ )
{
let b = Spawn("InvisibleSplasher",it.WaterHitList[i].hitpos);
b.target = target;
b.A_CheckTerrain();
}
for ( int i=0; i<it.Results.Distance; i+=32 )
{
Vector3 ofs = level.Vec3Offset(pos,x*i);
if ( Random[Ynykron](0,5) || !level.IsPointInLevel(ofs) ) continue;
let b = Spawn("SWWMHalfSmoke",ofs);
b.Scale *= FRandom[Ynykron](1.6,2.8);
b.special1 = Random[Ynykron](3,5);
b.alpha *= .3;
b.vel += x*FRandom[Ynykron](-.2,.4);
}
for ( int i=0; i<it.HitList.Size(); i++ )
{
if ( YnykronShot(master) && (YnykronShot(master).hitlist.Find(it.HitList[i].hitactor) < YnykronShot(master).hitlist.Size()) )
continue;
int trad = int(max(it.hitlist[i].hitactor.radius,it.hitlist[i].hitactor.height));
if ( it.hitlist[i].hitactor && YnykronShot(master) )
{
YnykronShot(master).hitlist.Push(it.hitlist[i].hitactor);
if ( it.hitlist[i].hitactor.bBOSS || it.hitlist[i].hitactor.FindInventory("BossMarker") ) YnykronShot(master).hitboss = true;
}
// spawn blast that will propagate
let b = Spawn("YnykronImpact",it.hitlist[i].hitlocation);
b.tracer = it.hitlist[i].hitactor;
b.target = target;
b.master = master;
b.angle = atan2(it.HitList[i].x.y,it.HitList[i].x.x);
b.pitch = asin(-it.HitList[i].x.z);
b.args[0] = trad;
if ( YnykronShot(master) )
YnykronShot(master).blastcount++;
}
nospread = true;
speed = t.Results.Distance; // shortens in minimap
return;
}
if ( ((special1%256) < speed) && !Random[Ynykron](0,20) )
Spawn("YnykronBeamLight",level.Vec3Offset(pos,x*speed/2));
if ( special1 >= 25600 )
{
// end of the line, dissipate
int numpt = Random[Ynykron](4,8);
for ( int i=0; i<numpt; i++ )
{
let b = Spawn("SWWMSmoke",level.Vec3Offset(pos,x*speed));
b.Scale *= FRandom[Ynykron](.6,1.8);
b.special1 = Random[Ynykron](1,2);
b.A_SetRenderStyle(.3,STYLE_AddShaded);
b.SetShade(Color(255,255,255));
b.vel += x*FRandom[Ynykron](0.,5.);
}
nospread = true;
return;
}
}
void SpreadOut()
{
if ( nospread ) return;
Vector3 x, y, z;
[x, y, z] = swwm_CoordUtil.GetAxes(pitch,angle,roll);
// propagate
let next = Spawn("YnykronBeam",level.Vec3Offset(pos,x*speed));
next.angle = atan2(x.y,x.x);
next.pitch = asin(-x.z);
next.roll = roll;
next.target = target;
next.master = master;
next.special1 = special1+int(speed);
next.SetStateLabel("TrailSpawn");
}
override void PostBeginPlay()
{
if ( YnykronShot(master) )
YnykronShot(master).beamcount++;
special2 = Random[Ynykron](-1,0);
TraceOut();
}
override void Tick()
{
if ( freezetics > 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;
}
}
override void OnDestroy()
{
if ( YnykronShot(master) )
YnykronShot(master).beamcount--;
Super.OnDestroy();
}
Default
{
Obituary "$O_YNYKRON";
DamageType "Ynykron";
RenderStyle "Add";
Alpha .4;
Speed 128;
+FORCEXYBILLBOARD;
+ROLLSPRITE;
+ROLLCENTER;
+NODAMAGETHRUST;
+FORCERADIUSDMG;
+FOILINVUL;
+EXTREMEDEATH;
}
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 DelayedWallBeam : SWWMNonInteractiveActor
{
override void PostBeginPlay()
{
if ( YnykronShot(master) )
YnykronShot(master).beamcount++;
}
override void Tick()
{
if ( freezetics > 0 )
{
freezetics--;
return;
}
if ( isFrozen() ) return;
special2--;
if ( special2 > 0 ) return;
if ( YnykronShot(master) )
YnykronShot(master).beamcount--;
let next = Spawn("YnykronBeam",pos);
next.angle = angle;
next.pitch = pitch;
next.roll = roll;
next.target = target;
next.master = master;
next.special1 = special1;
next.SetStateLabel("TrailSpawn");
// exit blast
Vector3 x = SWWMUtility.Vec3FromAngles(angle,pitch);
let b = Spawn("YnykronImpact",level.Vec3Offset(pos,x*16));
b.target = target;
b.master = master;
b.angle = atan2(x.y,x.x);
b.pitch = asin(-x.z);
b.A_SprayDecal("YnykronBlast",-172);
if ( YnykronShot(master) )
YnykronShot(master).blastcount++;
// trace back to get the proper "exit surface" so we can trigger lines if needed
let at = new("AuxiliarySilverBulletTracer");
at.Trace(pos,CurSector,-x,2.,TRACE_NoSky);
if ( at.Results.HitType == TRACE_HitWall )
{
if ( at.Results.HitLine.sidedef[1] )
{
at.Results.HitLine.Activate(target,0,SPAC_PCross);
at.Results.HitLine.Activate(target,0,SPAC_Impact);
}
else at.Results.HitLine.Activate(tracer,at.Results.Side,SPAC_Impact);
}
if ( swwm_omnibust ) BusterWall.Bust(at.Results,int.max,target,-at.Results.HitVector,at.Results.HitPos.z);
Destroy();
}
}
Class YnykronRing : SWWMNonInteractiveActor
{
Default
{
RenderStyle "Add";
Scale .6;
Alpha .05;
+FORCEXYBILLBOARD;
+ROLLSPRITE;
+ROLLCENTER;
}
States
{
Spawn:
XRG4 ABCDEFGHIJKLMNOPQRSTUVWX 3 Bright;
Stop;
}
}
// non-model version, for impacts
Class YnykronImpactRing : SWWMNonInteractiveActor
{
Default
{
RenderStyle "Add";
Scale 2.;
+FORCEXYBILLBOARD;
}
override void Tick()
{
if ( freezetics > 0 )
{
freezetics--;
return;
}
if ( isFrozen() ) return;
A_SetScale(scale.x*(special2?1.06:1.03));
if ( !CheckNoDelay() || (tics == -1) ) return;
if ( tics > 0 ) tics--;
while ( !tics )
{
if ( !SetState(CurState.NextState) )
return;
}
}
States
{
Spawn:
XRG4 ABCDEFGHIJKLMNOPQRSTUVWX 1 Bright A_SetTics(special2?1:2);
Stop;
}
}
Class YnykronShot : SWWMNonInteractiveActor
{
Array<Actor> hitlist;
bool hitboss;
int enemykills;
int beamcount;
int blastcount;
int lastimpact;
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,255,255,255),30);
}
override void PostBeginPlay()
{
A_QuakeEx(6,6,6,150,0,65535,"",QF_RELATIVE|QF_SCALEDOWN,falloff:65535,rollIntensity:1.);
A_StartSound("ynykron/beam",CHAN_VOICE,CHANF_DEFAULT,1.,0.);
FlashPlayer(240,8000);
hitlist.Clear();
beamcount = 0;
blastcount = 0;
int rings = 1;
Vector3 x, y, z, dir;
double a, s;
[x, y, z] = swwm_CoordUtil.GetAxes(pitch,angle,roll);
for ( double i=0; i<.04; i+=.006 )
{
for ( int j=0; j<360; j+=(360/rings) )
{
if ( i==0 ) dir = x; // central beam always precise
else
{
a = j+FRandom[Ynykron](-5.,5.);
s = i+FRandom[Ynykron](-.02,.04);
dir = SWWMUtility.ConeSpread(x,y,z,a,s);
}
let b = Spawn("YnykronBeam",pos);
b.target = target;
b.master = self;
b.angle = atan2(dir.y,dir.x);
b.pitch = asin(-dir.z);
b.roll = FRandom[Ynykron](0,360);
}
rings += 2;
}
Spawn("YnykronShotLight",level.Vec3Offset(pos,x*30));
}
override void Tick()
{
if ( freezetics > 0 )
{
freezetics--;
return;
}
if ( isFrozen() ) return;
// spawn rings
special1++;
if ( !(special1%10) && (special1 <= 30) )
{
Vector3 dir = SWWMUtility.Vec3FromAngles(angle,pitch);
for ( int i=0; i<16; i++ )
{
let r = Spawn("YnykronRing",level.Vec3Offset(pos,dir*(special1*16+i)));
r.scale *= special1/8.;
r.angle = angle;
r.pitch = pitch;
r.roll = FRandom[Ynykron](0,360);
}
}
// wait until we're no longer needed and all effects are over
if ( (beamcount > 0) || (blastcount > 0) )
return;
if ( target && enemykills )
{
if ( (enemykills == 1) && !hitboss )
SWWMUtility.MarkAchievement("oneguy",target.player);
SWWMUtility.AchievementProgress("ezkill",enemykills,target.player);
enemykills = 0;
}
if ( IsActorPlayingSound(CHAN_VOICE) )
return;
// we're done here
Destroy();
}
}
Class YnykronAltTracer : LineTracer
{
Actor ignore;
Array<Line> ShootThroughList;
Array<WaterHit> 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:
DUST 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 = swwm_CoordUtil.GetAxes(pitch,angle,0);
if ( !t ) t = new("YnykronTracer");
t.ShootThroughList.Clear();
t.WaterHitList.Clear();
t.HitList.Clear();
t.Trace(pos,cursector,x,16*scale.x,TRACE_HitSky);
for ( int i=0; i<t.ShootThroughList.Size(); i++ )
{
t.ShootThroughList[i].Activate(target,0,SPAC_PCross);
t.ShootThroughList[i].Activate(target,0,SPAC_Impact);
}
for ( int i=0; i<t.HitList.Size(); i++ )
{
// yoink
SWWMUtility.DoKnockback(t.HitList[i].HitActor,-t.HitList[i].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<numpt; i++ )
{
Vector3 pvel = SWWMUtility.Vec3FromAngles(FRandom[ExploS](0,360),FRandom[ExploS](-90,90))*FRandom[ExploS](3,12);
let s = Spawn("SWWMSmoke",pos);
s.vel = pvel;
s.SetShade(Color(1,1,1)*Random[ExploS](64,224));
s.special1 = Random[ExploS](1,4);
s.scale *= 2.;
s.alpha *= .4;
}
numpt = Random[ExploS](5,10);
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = SWWMUtility.Vec3FromAngles(FRandom[ExploS](0,360),FRandom[ExploS](-90,90))*FRandom[ExploS](2,12);
let s = Spawn("SWWMSpark",pos);
s.vel = pvel;
}
numpt = Random[ExploS](10,15);
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = SWWMUtility.Vec3FromAngles(FRandom[ExploS](0,360),FRandom[ExploS](-90,90))*FRandom[ExploS](2,24);
let s = Spawn("SWWMChip",pos);
s.vel = pvel;
}
Destroy();
}
}
Class YnykronLightningArc : SWWMNonInteractiveActor
{
Vector3 nextpos, nextdir;
Vector3 destpos;
action void A_Trace()
{
let t = new("CandyBeamTracer");
t.hitlist.Clear();
Vector3 x, y, z;
[x, y, z] = swwm_CoordUtil.GetAxes(pitch,angle,roll);
t.ShootThroughList.Clear();
t.Trace(pos,CurSector,x,speed,TRACE_HitSky);
for ( int i=0; i<t.ShootThroughList.Size(); i++ )
{
t.ShootThroughList[i].Activate(target,0,SPAC_PCross);
t.ShootThroughList[i].Activate(target,0,SPAC_Impact);
}
for ( int i=0; i<t.hitlist.Size(); i++ )
{
if ( !t.hitlist[i].hitactor ) continue;
SWWMUtility.DoKnockback(t.hitlist[i].hitactor,-t.hitlist[i].x,GetMissileDamage(0,0)*1000);
let p = SWWMPuff.Setup(t.hitlist[i].hitlocation,t.hitlist[i].x,self,target,t.hitlist[i].hitactor);
t.hitlist[i].hitactor.DamageMobj(p,target,GetMissileDamage(0,0),'Electric',DMG_THRUSTLESS|DMG_INFLICTOR_IS_PUFF);
}
Vector3 normal = -t.Results.HitVector, dir = t.Results.HitVector;
if ( t.Results.HitType != TRACE_HitNone )
{
if ( t.Results.HitType != TRACE_HasHitSky )
{
if ( t.Results.HitType == TRACE_HitWall )
{
normal = (-t.Results.HitLine.delta.y,t.Results.HitLine.delta.x,0).unit();
if ( !t.Results.Side ) normal *= -1;
t.Results.HitLine.RemoteActivate(target,t.Results.Side,SPAC_Impact,t.Results.HitPos);
}
else if ( t.Results.HitType == TRACE_HitFloor )
{
if ( t.Results.ffloor ) normal = -t.Results.ffloor.top.Normal;
else normal = t.Results.HitSector.floorplane.Normal;
}
else if ( t.Results.HitType == TRACE_HitCeiling )
{
if ( t.Results.ffloor ) normal = -t.Results.ffloor.bottom.Normal;
else normal = t.Results.HitSector.ceilingplane.Normal;
}
let s = Spawn("YnykronLightningImpact",level.Vec3Offset(t.Results.HitPos,normal*8));
s.target = target;
s.angle = atan2(normal.y,normal.x);
s.pitch = asin(-normal.z);
if ( swwm_omnibust ) BusterWall.Bust(t.Results,GetMissileDamage(0,0),target,t.Results.HitVector,t.Results.HitPos.z);
}
invoker.nextpos = t.Results.HitPos;
bAMBUSH = true;
speed = t.Results.Distance; // shortens in minimap
return;
}
invoker.nextpos = level.Vec3Offset(pos,x*speed);
double a = FRandom[Ynykron](0,360), s = FRandom[Ynykron](0.,.8);
dir = SWWMUtility.ConeSpread(dir,y,z,a,s);
if ( !special1 )
{
invoker.destpos = level.Vec3Offset(pos,x*10000);
// calculate the closest strike point to home in onto
Vector3 strikepoints[32];
double closest = 10000.;
for ( int i=0; i<32; i++ )
{
a = FRandom[Ynykron](0,360);
s = FRandom[Ynykron](.2,1.);
Vector3 tdir = SWWMUtility.ConeSpread(dir,y,z,a,s);
FLineTraceData d;
LineTrace(atan2(tdir.y,tdir.x),10000,asin(-tdir.z),TRF_THRUACTORS,data:d);
strikepoints[i] = d.HitLocation;
if ( d.Distance < closest )
{
invoker.destpos = strikepoints[i];
closest = d.Distance;
}
}
}
Vector3 dirto = level.Vec3Diff(invoker.nextpos,invoker.destpos);
double dist = dirto.length();
if ( dist > 10 )
{
dirto /= dist;
dir = (dir+.8*dirto*(clamp(1.-(dist/1500.),0.,1.)**1.5)).unit();
}
invoker.nextdir = dir;
}
action void A_Spread( Sound arcsnd )
{
Vector3 tdir = level.Vec3Diff(pos,invoker.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] = swwm_CoordUtil.GetAxes(pitch,angle,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 = invoker.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(),invoker.nextpos);
b.angle = atan2(invoker.nextdir.y,invoker.nextdir.x);
b.pitch = asin(-invoker.nextdir.z);
b.target = target;
b.special1 = special1+1;
YnykronLightningArc(b).destpos = invoker.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] = swwm_CoordUtil.GetAxes(FRandom[ExploS](-90,90),FRandom[ExploS](0,360),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:
DUST 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.scale.y = len;
t.angle = atan2(dir.y,dir.x);
t.pitch = asin(-dir.z)+90;
}
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<numpt; i++ )
{
Vector3 pvel = SWWMUtility.Vec3FromAngles(FRandom[ExploS](0,360),FRandom[ExploS](-90,90))*FRandom[ExploS](4,20);
let s = Spawn("SWWMSmoke",pos);
s.vel = pvel;
s.SetShade(Color(1,1,1)*Random[ExploS](128,160)+Color(28,0,31));
s.A_SetRenderStyle(.2,STYLE_AddShaded);
s.special1 = Random[ExploS](2,5);
s.scale *= 8.;
}
numpt = Random[ExploS](0,4);
for ( int i=0; i<numpt; i++ )
{
double ang = FRandom[ExploS](0,360);
double pt = FRandom[ExploS](-90,90);
double ofs = FRandom[ExploS](0.,30.);
let s = Spawn("YnykronVoidSparkle",level.Vec3Offset(pos,ofs*SWWMUtility.Vec3FromAngles(ang,pt)));
s.angle = ang;
s.pitch = pt;
}
numpt = Random[ExploS](-1,3);
for ( int i=0; i<numpt; i++ )
{
let s = Spawn("YnykronSingularityExplosionArm",pos);
s.target = target;
}
}
States
{
Spawn:
XEX4 AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ[[\\ 1 Bright A_SetScale(scale.x*1.01,scale.y*1.01);
Stop;
}
}
Class YnykronSingularityHitbox : SWWMNonInteractiveActor
{
Default
{
-NOBLOCKMAP;
}
override void Tick()
{
if ( !target || !target.InStateSequence(target.CurState,target.FindState("Spawn")) )
{
Destroy();
return;
}
A_SetSize(32.*target.scale.x,64.*target.scale.y);
SetOrigin(target.Vec3Offset(0,0,-height*.5),true);
}
override bool CanCollideWith( Actor other, bool passive )
{
return false;
}
}
Class YnykronSingularity : SWWMNonInteractiveActor
{
const MAXBEAMS = 10;
const MAXCLOUDS = 300;
Actor beamers[MAXBEAMS];
Actor clouds[MAXCLOUDS];
const MION_CONST = 48538.; // miön v-force compression constant (approximate)
const YNON_CONST = .043738; // ÿnon v-force field constant (approximate)
const AXAN_CONST = 27.2761; // axan u-force field constant (approximate)
const GOAL_MASS = 26741.; // mass required to compensate Ynykron altfire negative energy release (-2.4×10¹⁵ J, approximately)
double critmass; // actual mass required (cummulative with absorbed vortices)
int litetimer;
transient SimpleMoveTracer mt;
override void PostBeginPlay()
{
let g = Spawn("YnykronHalo",pos);
g.target = self;
A_StartSound("ynykron/vortex",CHAN_BODY,CHANF_LOOP,1.,.6);
A_StartSound("ynykron/wind",CHAN_ITEM,CHANF_LOOP,1.,.0);
A_StartSound("ynykron/hit",CHAN_VOICE,CHANF_OVERLAP,1.,.0);
// initial mass and radius
specialf1 = scale.x;
specialf2 = scale.x*scale.x*MION_CONST;
critmass = GOAL_MASS;
litetimer = level.maptime+Random[Ynykron](180,240);
let h = Spawn("YnykronSingularityHitbox",pos);
h.target = self;
}
// lightweight tick, we don't need anything else other than states
override void Tick()
{
prev = pos;
if ( freezetics > 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<numpt; i++ )
{
double ang = FRandom[ExploS](0,360);
double pt = FRandom[ExploS](-90,90);
FLineTraceData d;
double dst = FRandom[ExploS](1500,2000)*scale.x;
LineTrace(ang,dst,pt,TRF_THRUACTORS|TRF_THRUHITSCAN,data:d);
Vector3 norm;
if ( d.HitType == TRACE_HitWall )
{
norm = (d.HitLine.delta.y,-d.HitLine.delta.x,0).unit();
if ( d.LineSide ) norm *= -1;
}
else if ( d.HitType == TRACE_HitFloor )
{
if ( d.Hit3DFloor ) norm = -d.Hit3DFloor.top.Normal;
else norm = d.HitSector.floorplane.Normal;
}
else if ( d.HitType == TRACE_HitCeiling )
{
if ( d.Hit3DFloor ) norm = -d.Hit3DFloor.bottom.Normal;
else norm = d.HitSector.ceilingplane.Normal;
}
else norm = (0,0,0);
let gd = Spawn("GatherDust",level.Vec3Offset(d.HitLocation,norm*8));
gd.target = self;
}
// storm clouds
for ( int i=0; i<MAXCLOUDS; i++ )
{
if ( clouds[i] && (clouds[i].special1 > -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<Actor> contacts;
Array<double> dists;
contacts.Clear();
dists.Clear();
for ( int i=0; i<MAXCLOUDS; i++ )
{
if ( !clouds[i] || (i == which) || (clouds[i].Alpha < .1) ) continue;
double dist = clouds[which].Distance3D(clouds[i]);
if ( dist > (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<contacts.Size(); i++ )
YnykronCloud(contacts[i]).frightening = (1.-dists[i]/(200*scale.x))**.5;
litetimer = level.maptime+Random[Ynykron](120,240);
SWWMUtility.DoExplosion(clouds[which],1200,40000,500,150);
// lightning arcs
int numpt = Random[Ynykron](0,3);
Vector3 dirto = level.Vec3Diff(pos,clouds[which].pos);
dirto /= dirto.length();
for ( int i=0; i<numpt; i++ )
{
let l = Spawn("YnykronLightningArc",clouds[which].pos);
l.angle = atan2(dirto.y,dirto.x)+FRandom[Ynykron](-5,5);
l.pitch = asin(-dirto.z)+FRandom[Ynykron](-5,5);
l.target = target;
}
}
}
}
// ol' crazy purple beams from original
for ( int i=0; i<MAXBEAMS; i++ )
{
if ( beamers[i] || Random[Ynykron](0,40) ) continue;
beamers[i] = Spawn("YnykronVoidBeam",pos);
YnykronVoidBeam(beamers[i]).basebeam = beamers[i];
beamers[i].target = target;
beamers[i].master = self;
}
// suck in reachable targets
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
if ( hnd )
{
for ( int i=0; i<hnd.SuckableActors.Size(); i++ )
{
let a = hnd.SuckableActors[i];
if ( !a ) continue;
if ( a.bDORMANT || (a.Health <= 0) || (a == self) || !SWWMUtility.SphereIntersect(a,pos,20000.*scale.x) || !CheckSight(a,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) )
continue;
if ( (a is 'Inventory') && !a.bDROPPED ) // must be a dropped pickup
continue;
if ( a.player && (a.player.cheats&CF_NOCLIP2) ) // for safety
continue;
if ( (a is 'YnykronSingularity') && a.bNOBLOCKMAP ) // dead singularity, ignore
continue;
double capmass = a.mass;
// succ
if ( SWWMUtility.SphereIntersect(a,pos,32.*scale.x) )
{
// merge with other vortices
if ( a is 'YnykronSingularity' )
{
critmass += YnykronSingularity(a).critmass;
specialf2 += a.specialf2;
a.Destroy();
continue;
}
// don't delet inventory
if ( a is 'Inventory' )
continue;
// delet missile
if ( a.bMISSILE )
{
specialf2 += 5.;
a.Destroy();
continue;
}
// voodoo dolls just get erased (how convenient)
// otherwise instantly vaporize the fucker
if ( a.player && (a.player.mo != a) ) a.Destroy();
else if ( a.CountInv("GrilledCheeseSandwich") > 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();
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<MAXBEAMS; i++ )
{
if ( !beamers[i] ) continue;
YnykronVoidBeam(beamers[i]).maxlife = 0;
}
Spawn("YnykronSingularityRing",pos);
Spawn("YnykronVoidExplLight",pos);
int numpt = Random[ExploS](60,80);
for ( int i=0; i<numpt; i++ )
{
let s = Spawn("YnykronVoidSparkle",pos);
s.angle = FRandom[ExploS](0,360);
s.pitch = FRandom[ExploS](-90,90);
s.scale *= 8.;
s.alpha *= 5.;
}
}
void A_SingularityExtraBlast()
{
special1++;
if ( special1 > 60 )
{
if ( IsActorPlayingSound(CHAN_VOICE) ) return;
for ( int i=0; i<MAXBEAMS; i++ ) if ( beamers[i] ) return;
Destroy();
return;
}
double factor = (60-special1)/60.;
double invfct = 1.-factor;
SWWMUtility.DoExplosion(self,0,-50000*factor,4000*invfct);
FlashPlayer(int(min(100*factor,250)),10000);
FLineTraceData d;
Vector3 HitNormal;
Vector3 origin;
double ang, pt;
int numpt = clamp((60-special1)/4,2,12);
for ( int i=0; i<numpt; i++ )
{
double totaldist = 30+(special1**2.5)*.1;
ang = FRandom[ExploS](0,360);
pt = FRandom[ExploS](-90,90);
origin = pos;
while ( totaldist > 0 )
{
LineTrace(ang,totaldist,pt,TRF_THRUACTORS|TRF_ABSPOSITION,origin.z,origin.x,origin.y,d);
hitnormal = -d.HitDir;
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;
}
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, y, z;
[x, y, z] = swwm_CoordUtil.GetAxes(pitch,angle,roll);
let t = new("YnykronAltTracer");
t.ignore = target;
t.ShootThroughList.Clear();
t.WaterHitList.Clear();
t.Trace(pos,cursector,x,speed,TRACE_HitSky);
for ( int i=0; i<t.ShootThroughList.Size(); i++ )
{
t.ShootThroughList[i].Activate(target,0,SPAC_PCross);
t.ShootThroughList[i].Activate(target,0,SPAC_Impact);
}
for ( int i=0; i<t.WaterHitList.Size(); i++ )
{
let b = Spawn("InvisibleSplasher",t.WaterHitList[i].hitpos);
b.target = target;
b.A_CheckTerrain();
}
for ( int i=0; i<t.Results.Distance; i+=16 )
{
if ( Random[Ynykron](0,2) ) continue;
let b = Spawn("SWWMHalfSmoke",level.Vec3Offset(pos,x*i));
b.Scale *= FRandom[Ynykron](1.6,2.8);
b.special1 = Random[Ynykron](3,5);
b.A_SetRenderStyle(b.alpha*.3,STYLE_Subtract);
b.vel += x*FRandom[Ynykron](-.2,.4);
}
if ( t.Results.HitType == TRACE_HasHitSky )
{
// goodbye
nospread = true;
speed = t.Results.Distance; // shortens in minimap
return;
}
if ( t.Results.HitType != TRACE_HitNone )
{
// hit something
Vector3 norm = -t.Results.HitVector;
if ( t.Results.HitType == TRACE_HitWall )
{
norm = (t.Results.HitLine.delta.y,-t.Results.HitLine.delta.x,0).unit();
if ( t.Results.Side ) norm *= -1;
t.Results.HitLine.RemoteActivate(tracer,t.Results.Side,SPAC_Impact,t.Results.HitPos);
}
else if ( t.Results.HitType == TRACE_HitFloor )
{
if ( t.Results.ffloor ) norm = -t.Results.ffloor.top.Normal;
else norm = t.Results.HitSector.floorplane.Normal;
}
else if ( t.Results.HitType == TRACE_HitCeiling )
{
if ( t.Results.ffloor ) norm = -t.Results.ffloor.bottom.Normal;
else norm = t.Results.HitSector.ceilingplane.Normal;
}
let b = Spawn("YnykronSingularity",level.Vec3Offset(t.Results.HitPos,norm*16));
b.target = target;
b.angle = atan2(norm.y,norm.x);
b.pitch = asin(-norm.z);
nospread = true;
speed = t.Results.Distance; // shortens in minimap
return;
}
if ( special1 >= 25600 )
{
// end of the line, dissipate
int numpt = Random[Ynykron](16,24);
for ( int i=0; i<numpt; i++ )
{
let b = Spawn("SWWMSmoke",level.Vec3Offset(pos,x*speed));
b.Scale *= FRandom[Ynykron](.6,1.8);
b.special1 = Random[Ynykron](1,2);
b.A_SetRenderStyle(.3,STYLE_Subtract);
b.vel += x*FRandom[Ynykron](0.,5.);
}
nospread = true;
return;
}
}
void SpreadOut()
{
if ( nospread ) return;
Vector3 x, y, z;
[x, y, z] = swwm_CoordUtil.GetAxes(pitch,angle,roll);
// propagate
let next = Spawn("YnykronAltBeam",level.Vec3Offset(pos,x*speed));
next.angle = atan2(x.y,x.x);
next.pitch = asin(-x.z);
next.roll = roll;
next.target = target;
next.master = master;
next.special1 = special1+int(speed);
next.SetStateLabel("TrailSpawn");
}
override void PostBeginPlay()
{
special2 = Random[Ynykron](-1,0);
TraceOut();
}
override void Tick()
{
if ( freezetics > 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) ) return;
Destroy();
}
}