swwmgz_m/zscript/swwm_deathlydeathcannon.zsc
Marisa Kirisame 334d0a6d17 Added "All Clear" score bonus for fully 100%-ing a level.
Merge separate palettes into multi-palettes, adapt PaletteLight to handle this.
Added a couple extra voice sounds to the Demolitionist.
2020-12-05 18:05:13 +01:00

3209 lines
81 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 Artifact (from UnSX Series, featured in SWWM Platinum as a secret weapon)
// Slot 0, replaces BFG9000, Firemace, Wraithverge (arc)
// cheap way to let players know they just got fucking erased from existence
Class PlayerGone : PlayerChunk
{
int deadtimer;
override void DeathThink()
{
player.damagecount--;
player.poisoncount--;
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) && !player.Bot) || ((deathmatch || alwaysapplydmflags) && sv_forcerespawn && (Level.maptime >= player.respawn_time))) && !sv_norespawn )
{
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 void FeckOff( Actor p )
{
// doesn't affect voodoo dolls (convenient, isn't it?)
if ( !p.player || (p.player.mo != p) ) return;
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;
for ( int i=0; i<MAXPLAYERS; i++ )
{
if ( playeringame[i] && (players[i].camera == p) )
players[i].camera = c;
}
}
}
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();
// 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,400;
}
}
Class YnykronSubImpactLight : PaletteLight
{
Default
{
Tag "WhiteExpl,1";
ReactionTime 20;
Args 0,0,0,200;
}
}
Class YnykronShotLight : PaletteLight
{
Default
{
Tag "WhiteExpl";
ReactionTime 100;
Args 0,0,0,1200;
}
}
Class YnykronBeamLight : PaletteLight
{
Default
{
Tag "WhiteExpl,1";
ReactionTime 50;
Args 0,0,0,600;
}
}
Class YnykronImpactArm : Actor
{
Default
{
PROJECTILE;
+THRUACTORS;
+BOUNCEONWALLS;
+BOUNCEONFLOORS;
+BOUNCEONCEILINGS;
+NODAMAGETHRUST;
+FORCERADIUSDMG;
-NOGRAVITY;
Gravity 0.35;
BounceFactor 1.0;
Radius 4;
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 = (cos(ang)*cos(pt),sin(ang)*cos(pt),-sin(pt))*FRandom[ExploS](20.,40.);
}
States
{
Spawn:
TNT1 A 1
{
A_CountDown();
if ( !(reactiontime%2) )
{
Spawn("YnykronImpactTrail",pos);
Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*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 : Actor
{
Default
{
RenderStyle "Add";
+NOBLOCKMAP;
+NOGRAVITY;
+FORCEXYBILLBOARD;
+NOTELEPORT;
+NOINTERACTION;
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);
}
override void Tick()
{
if ( isFrozen() ) return;
if ( !CheckNoDelay() || (tics == -1) ) return;
if ( tics > 0 ) tics--;
while ( !tics )
{
if ( !SetState(CurState.NextState) )
return;
}
}
States
{
Spawn:
MOXP ABCDEFGHIJKLMNOPQRSTUVWXYZ[\ 1 Bright;
Stop;
}
}
Class YnykronDelayedImpact : Actor
{
Vector3 ofs;
Default
{
+NOBLOCKMAP;
+NOGRAVITY;
+NOTELEPORT;
+NOINTERACTION;
}
override void Tick()
{
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 : Actor
{
int rad;
Default
{
Obituary "$O_YNYKRON";
DamageType "Ynykron";
RenderStyle "Add";
Scale 5.;
+NOGRAVITY;
+NOBLOCKMAP;
+NOTELEPORT;
+DONTSPLASH;
+FORCEXYBILLBOARD;
+NOINTERACTION;
+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();
// no need to call A_AlertMonsters if all monsters on the entire map were already alerted by the initial shot
if ( swwm_ynykronalert && (!special2 || swwm_extraalert) ) A_AlertMonsters();
rad = args[0]+300+10*clamp(special1/10,0,15);
A_QuakeEx(4,4,4,50,0,rad*4,"",QF_RELATIVE|QF_SCALEDOWN,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) ) 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 ) 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 ( 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 = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*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 ) Spawn("YnykronSubImpactLight",pos);
else 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;
}
if ( swwm_capmcrange && (special1 > 2) ) return;
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);
// 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();
}
override void Tick()
{
if ( isFrozen() ) return;
if ( !CheckNoDelay() || (tics == -1) ) return;
if ( tics > 0 ) tics--;
while ( !tics )
{
if ( !SetState(CurState.NextState) )
return;
}
}
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 )
{
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 : Actor
{
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,128,TRACE_HitSky);
for ( int i=0; i<t.ShootThroughList.Size(); i++ )
{
t.ShootThroughList[i].Activate(target,0,SPAC_PCross);
if ( t.ShootThroughList[i].special == GlassBreak ) // fuck glass
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.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;
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);
// 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;
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=128; 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.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);
// 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;
return;
}
if ( ((special1%256) < 128) && !Random[Ynykron](0,10) )
Spawn("YnykronBeamLight",level.Vec3Offset(pos,x*64));
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*128));
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*128));
next.angle = atan2(x.y,x.x);
next.pitch = asin(-x.z);
next.roll = roll;
next.target = target;
next.master = master;
next.special1 = special1+128;
next.SetStateLabel("TrailSpawn");
}
override void PostBeginPlay()
{
if ( YnykronShot(master) )
YnykronShot(master).beamcount++;
special2 = Random[Ynykron](-1,0);
TraceOut();
}
override void Tick()
{
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";
Radius .1;
Height 0;
Alpha .4;
+NOGRAVITY;
+NOBLOCKMAP;
+NOCLIP;
+NOTELEPORT;
+DONTSPLASH;
+FORCEXYBILLBOARD;
+ROLLSPRITE;
+ROLLCENTER;
+NODAMAGETHRUST;
+FORCERADIUSDMG;
+FOILINVUL;
+EXTREMEDEATH;
+NOINTERACTION;
}
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 : Actor
{
Default
{
Radius 0.1;
Height 0;
+NOGRAVITY;
+NOBLOCKMAP;
+NOTELEPORT;
+DONTSPLASH;
+NOINTERACTION;
}
override void PostBeginPlay()
{
if ( YnykronShot(master) )
YnykronShot(master).beamcount++;
}
override void Tick()
{
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 = (cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(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);
if ( at.Results.HitLine.special == GlassBreak ) // fuck glass
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 : Actor
{
Default
{
RenderStyle "Add";
Radius 0.1;
Height 0;
Scale .6;
Alpha .05;
+NOGRAVITY;
+NOBLOCKMAP;
+FORCEXYBILLBOARD;
+NOTELEPORT;
+DONTSPLASH;
+ROLLSPRITE;
+ROLLCENTER;
+NOINTERACTION;
}
override void Tick()
{
if ( isFrozen() ) return;
if ( !CheckNoDelay() || (tics == -1) ) return;
if ( tics > 0 ) tics--;
while ( !tics )
{
if ( !SetState(CurState.NextState) )
return;
}
}
States
{
Spawn:
XRG4 ABCDEFGHIJKLMNOPQRSTUVWX 3 Bright;
Stop;
}
}
// non-model version, for impacts
Class YnykronImpactRing : Actor
{
Default
{
RenderStyle "Add";
Radius 0.1;
Height 0;
Scale 2.;
+NOGRAVITY;
+NOBLOCKMAP;
+FORCEXYBILLBOARD;
+NOTELEPORT;
+DONTSPLASH;
+NOINTERACTION;
}
override void Tick()
{
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 : Actor
{
Array<Actor> hitlist;
int beamcount;
int blastcount;
int lastimpact;
Default
{
Radius .1;
Height 0;
+NOGRAVITY;
+NOBLOCKMAP;
+NOTELEPORT;
+DONTSPLASH;
+NOINTERACTION;
}
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,65536,"",QF_RELATIVE|QF_SCALEDOWN,falloff:65536,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 = (x+y*cos(a)*s+z*sin(a)*s).unit();
}
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 ( isFrozen() ) return;
// spawn rings
special1++;
if ( !(special1%10) && (special1 <= 30) )
{
Vector3 dir = (cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(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 ( IsActorPlayingSound(CHAN_VOICE) || (beamcount > 0) || (blastcount > 0) )
{
special1 = min(special1,50);
return;
}
// we're done here, but wait for a while just in case
if ( special1 > 350 ) 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 ) 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 : Actor
{
Default
{
RenderStyle "Add";
+NOGRAVITY;
+DONTSPLASH;
+NOTELEPORT;
+NOBLOCKMAP;
+NOINTERACTION;
+FORCEXYBILLBOARD;
Radius .1;
Height 0.;
Scale 1.5;
}
override void Tick()
{
if ( isFrozen() ) return;
A_SetScale(scale.x+.5);
A_FadeOut(.2);
}
States
{
Spawn:
MHAL A -1 Bright;
Stop;
}
}
Class YnykronHalo : Actor
{
Default
{
RenderStyle "Add";
+NOGRAVITY;
+DONTSPLASH;
+NOTELEPORT;
+NOBLOCKMAP;
+NOINTERACTION;
+FORCEXYBILLBOARD;
Radius .1;
Height 0.;
Scale 1.5;
}
override void Tick()
{
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 : Actor
{
override void Tick()
{
if ( isFrozen() ) return;
SetOrigin(level.Vec3Offset(pos,vel),true);
if ( !CheckNoDelay() || (tics == -1) ) return;
if ( tics > 0 ) tics--;
while ( !tics )
{
if ( !SetState(CurState.NextState) )
return;
}
}
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,5.);
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";
Radius .1;
Height 0.;
Alpha 0.;
Scale 3.;
+NOGRAVITY;
+NOBLOCKMAP;
+DONTSPLASH;
+NOTELEPORT;
+NOINTERACTION;
+FORCEXYBILLBOARD;
+NOCLIP;
}
States
{
Spawn:
DUST ABCDEFGHIJKLMNOPQRST 1 A_Gravitate();
Wait;
}
}
Class YnykronVoidBeamTail : Actor
{
Default
{
RenderStyle "Add";
+NOGRAVITY;
+DONTSPLASH;
+NOTELEPORT;
+NOBLOCKMAP;
+NOINTERACTION;
+FORCEXYBILLBOARD;
Radius .1;
Height 0.;
}
override void Tick()
{
if ( isFrozen() ) return;
A_FadeOut();
}
States
{
Spawn:
XZW1 # -1 Bright;
Stop;
}
}
Class YnykronVoidBeam : Actor
{
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";
Radius .1;
Height 0.;
Alpha 0.;
Stamina 3;
+NOGRAVITY;
+NOBLOCKMAP;
+DONTSPLASH;
+NOTELEPORT;
+NOINTERACTION;
+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.*(cos(baseang)*cos(basept),sin(baseang)*cos(basept),-sin(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);
if ( t.ShootThroughList[i].special == GlassBreak ) // fuck glass
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*(FRandom[Ynykron](-1,1),FRandom[Ynykron](-1,1),FRandom[Ynykron](-1,1))).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;
}
override void Tick()
{
if ( isFrozen() ) return;
if ( !CheckNoDelay() || (tics == -1) ) return;
if ( tics > 0 ) tics--;
while ( !tics )
{
if ( !SetState(CurState.NextState) )
return;
}
}
States
{
Spawn:
XZW1 # 1 Bright A_UpdateBeam();
Wait;
}
}
Class YnykronLightningImpact : Actor
{
Default
{
Obituary "$O_YNYKRONALT";
DamageType "Electric";
Radius .1;
Height 0;
+FOILINVUL;
+FORCERADIUSDMG;
+NODAMAGETHRUST;
+NOINTERACTION;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
SWWMUtility.DoExplosion(self,400,120000,100,40);
A_QuakeEx(3,3,3,12,0,800,"",QF_RELATIVE|QF_SCALEDOWN,falloff:300,rollIntensity:.4);
A_SprayDecal("ShockMark",-172);
int numpt = Random[ExploS](8,16);
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*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 = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*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 = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*FRandom[ExploS](2,24);
let s = Spawn("SWWMChip",pos);
s.vel = pvel;
}
Destroy();
}
}
Class YnykronLightningArc : Actor
{
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);
if ( t.ShootThroughList[i].special == GlassBreak ) // fuck glass
t.ShootThroughList[i].Activate(target,0,SPAC_Impact);
}
for ( int i=0; i<t.hitlist.Size(); i++ )
{
SWWMUtility.DoKnockback(t.hitlist[i].hitactor,-t.hitlist[i].x,GetMissileDamage(0,0)*1000);
t.hitlist[i].hitactor.DamageMobj(self,target,GetMissileDamage(0,0),'Electric',DMG_THRUSTLESS);
}
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;
return;
}
invoker.nextpos = level.Vec3Offset(pos,x*speed);
double a = FRandom[Ynykron](0,360), s = FRandom[Ynykron](0.,.8);
dir = (dir+cos(a)*y*s+sin(a)*z*s).unit();
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 = (dir+cos(a)*y*s+sin(a)*z*s).unit();
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 = (x+y*cos(a)*s+z*sin(a)*s).unit();
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 ( ((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"));
if ( arcsnd != "" ) A_StartSound(arcsnd,CHAN_WEAPON);
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
frame = Random[Ynykron](0,11);
}
override void Tick()
{
if ( isFrozen() ) return;
if ( !CheckNoDelay() || (tics == -1) ) return;
if ( tics > 0 ) tics--;
while ( !tics )
{
if ( !SetState(CurState.NextState) )
return;
}
}
Default
{
Obituary "$O_YNYKRONALT";
RenderStyle "Add";
DamageFunction 1000;
Speed 128;
Radius .1;
Height 0;
Alpha 2.;
+NOGRAVITY;
+NOCLIP;
+DONTSPLASH;
+NOTELEPORT;
+FOILINVUL;
+NOINTERACTION;
}
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 : Actor
{
Vector3 gx, gy, gz;
double phase;
Vector3 dirto;
double frightening; // lightning flash
Color basecol;
double rollvel;
override void Tick()
{
prev = pos;
if ( isFrozen() ) return;
if ( !CheckNoDelay() || (tics == -1) ) return;
if ( tics > 0 ) tics--;
while ( !tics )
{
if ( !SetState(CurState.NextState) )
return;
}
}
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 = (gx*cos(phase)+gy*sin(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";
Radius .1;
Height 0.;
Alpha 0.;
Scale 3.;
+NOGRAVITY;
+NOBLOCKMAP;
+DONTSPLASH;
+NOTELEPORT;
+NOINTERACTION;
+FORCEXYBILLBOARD;
+ROLLSPRITE;
+NOCLIP;
}
States
{
Spawn:
DUST ABCDEFGHIJKLMNOPQRST 1 A_Gravitate();
Wait;
}
}
Class YnykronLightningLight : PaletteLight
{
Default
{
Tag "WhiteExpl";
ReactionTime 12;
Args 0,0,0,900;
}
}
Class YnykronLightningLight2 : PaletteLight
{
Default
{
Tag "WhiteExpl";
ReactionTime 10;
Args 0,0,0,500;
}
}
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 : Actor
{
Default
{
RenderStyle "Add";
Radius 0.1;
Height 0;
Scale 4.;
+NOGRAVITY;
+NOBLOCKMAP;
+FORCEXYBILLBOARD;
+NOTELEPORT;
+DONTSPLASH;
+NOINTERACTION;
}
override void Tick()
{
if ( isFrozen() ) return;
A_SetScale(scale.x*1.05);
if ( !CheckNoDelay() || (tics == -1) ) return;
if ( tics > 0 ) tics--;
while ( !tics )
{
if ( !SetState(CurState.NextState) )
return;
}
}
States
{
Spawn:
XRG4 ABCDEFGHIJKLMNOPQRSTUVWX 2 Bright;
Stop;
}
}
Class YnykronVoidExplLight : PaletteLight
{
Default
{
Tag "Purple";
ReactionTime 60;
Args 0,0,0,1200;
}
}
Class YnykronVoidSparkle : Actor
{
Default
{
RenderStyle "Add";
Radius 0.1;
Height 0;
+NOGRAVITY;
+NOBLOCKMAP;
+DONTSPLASH;
+ROLLSPRITE;
+ROLLCENTER;
+FORCEXYBILLBOARD;
+NOINTERACTION;
}
override void PostBeginPlay()
{
Scale *= FRandom[ExploS](.75,1.5);
specialf1 = FRandom[ExploS](.95,.98);
specialf2 = FRandom[ExploS](.005,.015);
vel = (cos(angle)*cos(pitch),sin(angle)*cos(pitch),sin(-pitch))*FRandom[ExploS](1,16);
}
override void Tick()
{
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*(FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1));
vel = dir.unit()*magvel;
}
SetOrigin(level.Vec3Offset(pos,vel),true);
}
States
{
Spawn:
MOPF A -1 Bright;
Stop;
}
}
Class YnykronSingularityExplosionArm : Actor
{
Default
{
PROJECTILE;
+THRUACTORS;
+BOUNCEONWALLS;
+BOUNCEONFLOORS;
+BOUNCEONCEILINGS;
+NODAMAGETHRUST;
+FORCERADIUSDMG;
-NOGRAVITY;
Gravity 0.35;
BounceFactor 1.0;
Radius 4;
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 = (cos(ang)*cos(pt),sin(ang)*cos(pt),-sin(pt))*FRandom[ExploS](20.,40.);
}
States
{
Spawn:
TNT1 A 1
{
A_CountDown();
if ( !(reactiontime%2) )
{
Spawn("YnykronSingularityExplosionTrail",pos);
Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*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 : Actor
{
Default
{
RenderStyle "Add";
+NOBLOCKMAP;
+NOGRAVITY;
+FORCEXYBILLBOARD;
+NOTELEPORT;
+NOINTERACTION;
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);
}
override void Tick()
{
if ( isFrozen() ) return;
if ( !CheckNoDelay() || (tics == -1) ) return;
if ( tics > 0 ) tics--;
while ( !tics )
{
if ( !SetState(CurState.NextState) )
return;
}
}
States
{
Spawn:
XEX4 ABCDEFGHIJKLMNOPQRSTUVWXYZ[\ 1 Bright;
Stop;
}
}
Class YnykronSingularityExplosion : Actor
{
Default
{
Obituary "$O_YNYKRONALT";
DamageType "YnykronAlt";
RenderStyle "Add";
Scale 5.;
Radius 0.1;
Height 0;
+NOGRAVITY;
+NOBLOCKMAP;
+NODAMAGETHRUST;
+FORCERADIUSDMG;
+FORCEXYBILLBOARD;
+NOTELEPORT;
+FOILINVUL;
+NOINTERACTION;
}
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](8,12);
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*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](5,10);
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*(cos(ang)*cos(pt),sin(ang)*cos(pt),sin(-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;
}
}
override void Tick()
{
if ( isFrozen() ) return;
scale *= 1.01;
if ( !CheckNoDelay() || (tics == -1) ) return;
if ( tics > 0 ) tics--;
while ( !tics )
{
if ( !SetState(CurState.NextState) )
return;
}
}
States
{
Spawn:
XEX4 ABCDEFGHIJKLMNOPQRSTUVWXYZ[\ 2 Bright;
Stop;
}
}
Class YnykronSingularity : Actor
{
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);
}
// lightweight tick, we don't need anything else other than states
override void Tick()
{
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 )
{
hnd.SuckableActors.Delete(i--);
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.player && (a.player.cheats&CF_NOCLIP2) ) // for safety
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;
}
// delet missile
if ( a.bMISSILE )
{
specialf2 += 10.;
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 ) PlayerGone.FeckOff(a);
// poof away, always
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 ( !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*TICRATE);
}
}
}
// 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 -= (cos(ang)*cos(pt),sin(ang)*cos(pt),-sin(pt))*.02*mag;
}
double magvel = vel.length();
Vector3 dir;
if ( magvel <= double.epsilon ) dir = (cos(angle)*cos(pitch),sin(angle)+cos(pitch),-sin(pitch));
else dir = vel/magvel;
// wander
dir = (dir+(FRandom[Ynykron](-1,1),FRandom[Ynykron](-1,1),FRandom[Ynykron](-1,1))*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_AlertMonsters();
SWWMUtility.DoExplosion(self,int.max,500000,800,400);
A_QuakeEx(8,8,8,100,0,65536,"",QF_RELATIVE|QF_SCALEDOWN,falloff:65536,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](40,60);
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(special1/6,2,8);
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";
+NOGRAVITY;
+MISSILE;
+DROPOFF;
+DONTSPLASH;
+NOTELEPORT;
+NOINTERACTION;
Radius .1;
Height 0.;
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 : Actor
{
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,64,TRACE_HitSky);
for ( int i=0; i<t.ShootThroughList.Size(); i++ )
{
t.ShootThroughList[i].Activate(target,0,SPAC_PCross);
if ( t.ShootThroughList[i].special == GlassBreak ) // fuck glass
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.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;
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;
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*64));
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*64));
next.angle = atan2(x.y,x.x);
next.pitch = asin(-x.z);
next.roll = roll;
next.target = target;
next.master = master;
next.special1 = special1+64;
next.SetStateLabel("TrailSpawn");
}
override void PostBeginPlay()
{
special2 = Random[Ynykron](-1,0);
TraceOut();
}
override void Tick()
{
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";
Radius .1;
Height 0;
Alpha .4;
+NOGRAVITY;
+NOBLOCKMAP;
+NOCLIP;
+NOTELEPORT;
+DONTSPLASH;
+FORCEXYBILLBOARD;
+ROLLSPRITE;
+ROLLCENTER;
+NOINTERACTION;
}
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 : Actor
{
Default
{
Radius .1;
Height 0;
+NOGRAVITY;
+NOBLOCKMAP;
+NOTELEPORT;
+DONTSPLASH;
+NOINTERACTION;
}
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,65536,"",QF_RELATIVE|QF_SCALEDOWN,falloff:65536,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 ( isFrozen() || IsActorPlayingSound(CHAN_VOICE) ) return;
Destroy();
}
}
Class Ynykron : SWWMWeapon
{
transient ui TextureID WeaponBox, ChargeBar[2], BoxSide[2];
transient ui Font TewiFont;
transient ui DynamicValueInterpolator ChargeInter;
enum EChargeState
{
CS_IDLE,
CS_CHARGING,
CS_READY,
CS_DISCHARGING
};
int chargestate;
double chargelevel;
bool inverted, invertreload;
double ventalpha, ventfade;
int ventcooldown;
int clipcount;
Property ClipCount : clipcount;
override void DrawWeapon( double TicFrac, double bx, double by, Vector2 hs, Vector2 ss )
{
if ( !WeaponBox ) WeaponBox = TexMan.CheckForTexture("graphics/HUD/YnykronDisplay.png",TexMan.Type_Any);
if ( !ChargeBar[0] ) ChargeBar[0] = TexMan.CheckForTexture("graphics/HUD/YnykronBarA.png",TexMan.Type_Any);
if ( !ChargeBar[1] ) ChargeBar[1] = TexMan.CheckForTexture("graphics/HUD/YnykronBarB.png",TexMan.Type_Any);
if ( !BoxSide[0] ) BoxSide[0] = TexMan.CheckForTexture("graphics/HUD/YnykronSideA.png",TexMan.Type_Any);
if ( !BoxSide[1] ) BoxSide[1] = TexMan.CheckForTexture("graphics/HUD/YnykronSideB.png",TexMan.Type_Any);
if ( !TewiFont ) TewiFont = Font.GetFont('TewiShaded');
Screen.DrawTexture(WeaponBox,false,bx-33,by-44,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true);
int chg = clamp(ChargeInter?ChargeInter.GetValue():int(chargelevel*10),0,400);
int ct = int(((by-2)-chg/10.)*hs.y);
Screen.DrawText(TewiFont,Font.CR_FIRE,bx-30,by-16,String.Format("%d",Ammo1.Amount),DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true);
Screen.DrawTexture(BoxSide[inverted],false,bx-23,by-31,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_ColorOverlay,clipcount?Color(0,0,0,0):Color(128,0,0,0));
Screen.DrawTexture(ChargeBar[inverted],false,bx-6,by-42,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_ColorOverlay,(chargestate==CS_READY)?Color(int(clamp(sin((level.maptime+TicFrac)*8)*40+24,0.,64.)),255,255,255):Color(0,0,0,0),DTA_ClipTop,ct);
}
override void HudTick()
{
Super.HudTick();
if ( !ChargeInter ) ChargeInter = DynamicValueInterpolator.Create(int(chargelevel*10),.5,1,400);
ChargeInter.Update(int(chargelevel*10));
}
override bool ReportHUDAmmo()
{
if ( clipcount > 0 ) return true;
return Super.ReportHUDAmmo();
}
override bool CheckAmmo( int firemode, bool autoswitch, bool requireammo, int ammocount )
{
if ( sv_infiniteammo || Owner.FindInventory('PowerInfiniteAmmo',true) ) return true;
if ( (fireMode == PrimaryFire) || (fireMode == AltFire) )
return ((clipcount > 0) || (Ammo1.Amount > 0));
return Super.CheckAmmo(firemode,autoswitch,requireammo,ammocount);
}
override void Travelled()
{
Super.Travelled();
if ( Owner.player && (Owner.player.Readyweapon == self) )
{
Owner.A_StartSound("ynykron/idle",CHAN_WEAPONEXTRA,CHANF_LOOP,.3,4.);
if ( chargestate > CS_IDLE )
Owner.A_StartSound("ynykron/ready",CHAN_WEAPONEXTRA2,CHANF_LOOP,(.025*chargelevel)**3.,2.);
}
}
override Vector3 GetTraceOffset()
{
return (15.,4.,-1.);
}
action void A_YnykronFire()
{
A_SWWMFlash();
A_StopSound(CHAN_WEAPONEXTRA2);
A_StartSound(invoker.inverted?"ynykron/altfire":"ynykron/fire",CHAN_WEAPON,CHANF_OVERLAP,1.,.2);
if ( !swwm_ynykronalert )
{
// global alert
int ns = level.Sectors.Size();
for ( int i=0; i<ns; i++ )
{
Sector s = level.Sectors[i];
for ( Actor a=s.thinglist; a; a=a.snext )
a.LastHeard = self;
}
}
else A_AlertMonsters(); // full range alert
A_QuakeEx(9,9,9,4,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:2.5);
A_ZoomFactor(.7,ZOOM_INSTANT);
A_ZoomFactor(1.);
A_PlayerFire();
SWWMHandler.DoFlash(self,Color(120,255,255,255),30);
A_Overlay(PSP_WEAPON+1,"FireSmoke");
A_OverlayFlags(PSP_WEAPON+1,PSPF_RENDERSTYLE|PSPF_ALPHA|PSPF_FORCESTYLE|PSPF_FORCEALPHA,true);
A_OverlayRenderStyle(PSP_WEAPON+1,STYLE_Add);
A_OverlayAlpha(PSP_WEAPON+1,0.);
invoker.chargestate = CS_IDLE;
invoker.clipcount = 0;
Vector3 x, y, z, origin;
[x, y, z] = swwm_CoordUtil.GetAxes(pitch,angle,roll);
origin = level.Vec3Offset(Vec2OffsetZ(0,0,player.viewz),x*15+y*4-z);
Actor s;
if ( invoker.inverted ) s = Spawn("YnykronAltShot",origin);
else s = Spawn("YnykronShot",origin);
s.target = self;
s.angle = angle;
s.pitch = BulletSlope();
invoker.specialf1 = 1.;
A_Overlay(PSP_WEAPON+3,"FireBlast");
}
action void A_Backblast()
{
Vector3 x, y, z, origin;
[x, y, z] = swwm_CoordUtil.GetAxes(pitch,angle,roll);
origin = level.Vec3Offset(Vec2OffsetZ(0,0,player.viewz),-x*15+y*4-z);
int numpt = Random[Ynykron](10,20);
for ( int i=0; i<numpt; i++ )
{
let s = Spawn("SWWMSmoke",origin);
s.scale *= 3.;
s.alpha *= .4*invoker.specialf1;
s.special1 = Random[Ynykron](2,8);
s.vel += x*FRandom[Ynykron](-40.,-4.)*invoker.specialf1+y*FRandom[Ynykron](-1.,1.)+z*FRandom[Ynykron](-1.,1.);
}
invoker.specialf1 -= .2;
}
override void DoEffect()
{
Super.DoEffect();
if ( !Owner || !Owner.player ) return;
if ( chargestate == CS_IDLE ) chargelevel = 0.;
else if ( chargestate == CS_CHARGING )
{
chargelevel = min(chargelevel*0.997+.2,40.);
if ( chargelevel >= 40. )
{
if ( Owner.player == players[consoleplayer] )
Console.Printf(StringTable.Localize("$SWWM_YNYKRONREADY"));
chargestate = CS_READY;
}
if ( Owner.player.ReadyWeapon == self )
Owner.A_SoundVolume(CHAN_WEAPONEXTRA2,(.025*chargelevel)**3.);
}
if ( Owner.player.ReadyWeapon != self ) return;
let pspm = Owner.player.FindPSprite(PSP_WEAPON);
if ( pspm )
{
double shiver = (chargelevel*.025)**2.;
pspm.x = FRandom[Shivers](-1.,1.)*shiver;
pspm.y = 32+FRandom[Shivers](-1.,1.)*shiver;
}
let psp = Owner.player.FindPSprite(PSP_WEAPON+1);
if ( !psp ) return;
ventalpha = clamp(ventalpha+ventfade,0.,1.);
psp.alpha = ventalpha;
if ( chargestate == CS_DISCHARGING )
{
chargelevel = max(chargelevel*1.005-.24,0.);
if ( chargelevel <= 0. ) chargestate = CS_IDLE;
if ( Owner.player.ReadyWeapon == self )
Owner.A_SoundVolume(CHAN_WEAPONEXTRA2,(.025*chargelevel)**3.);
}
}
Default
{
Tag "$T_YNYKRON";
Inventory.PickupMessage "$T_YNYKRON";
Obituary "$O_YNYKRON";
Inventory.Icon "graphics/HUD/Icons/W_Ynykron.png";
Weapon.SlotNumber 0;
Weapon.SelectionOrder 9000;
Weapon.UpSound "ynykron/select";
Stamina 5000000;
Weapon.AmmoType1 "YnykronAmmo";
Weapon.AmmoGive1 1;
SWWMWeapon.DropAmmoType "YnykronAmmo";
+SWWMWEAPON.NOFIRSTGIVE;
Ynykron.ClipCount 1;
+WEAPON.BFG;
+WEAPON.EXPLOSIVE;
Radius 32;
Height 36;
}
States
{
Spawn:
XZW1 A -1;
Stop;
Select:
XZW2 L 2
{
invoker.ventcooldown = Random[Ynykron](6,15);
A_StartSound("ynykron/idle",CHAN_WEAPONEXTRA,CHANF_LOOP,.3,4.);
if ( invoker.chargelevel > 0. )
A_StartSound("ynykron/ready",CHAN_WEAPONEXTRA2,CHANF_LOOP,(.025*invoker.chargelevel)**3.,2.);
A_FullRaise();
}
XZW2 MNOPQRSTUVWXYZ 2;
XZW3 A 2;
Goto Ready;
Ready:
XZW2 A 1
{
if ( invoker.chargestate == CS_DISCHARGING )
return ResolveState("Discharge");
int flg = WRF_ALLOWZOOM|WRF_ALLOWUSER1;
if ( (invoker.chargestate > CS_IDLE) || ((invoker.clipcount <= 0) && ((invoker.Ammo1.Amount > 0) || sv_infiniteammo || FindInventory('PowerInfiniteAmmo',true))) )
flg |= WRF_ALLOWRELOAD;
if ( (invoker.chargestate > CS_IDLE) || ((invoker.clipcount <= 0) && (invoker.Ammo1.Amount <= 0) && !sv_infiniteammo && !FindInventory('PowerInfiniteAmmo',true)) )
flg |= WRF_NOSECONDARY;
if ( invoker.chargestate == CS_CHARGING )
flg |= WRF_NOPRIMARY;
A_WeaponReady(flg);
if ( invoker.chargelevel >= 20. )
{
invoker.ventcooldown--;
if ( invoker.ventcooldown <= 0 )
return ResolveState("ReadyVent");
}
if ( player.cmd.buttons&BT_ATTACK )
invoker.CheckAmmo(EitherFire,true);
return ResolveState(null);
}
Wait;
ReadyVent:
XZW2 A 1
{
invoker.ventcooldown = Random[Ynykron](10,15)*5+2*(40-int(invoker.chargelevel));
A_Overlay(PSP_WEAPON+1,"ReadyVentSmoke");
A_OverlayFlags(PSP_WEAPON+1,PSPF_RENDERSTYLE|PSPF_ALPHA|PSPF_FORCESTYLE|PSPF_FORCEALPHA,true);
A_OverlayRenderStyle(PSP_WEAPON+1,STYLE_Add);
A_OverlayAlpha(PSP_WEAPON+1,0.);
A_WeaponReady(WRF_NOFIRE|WRF_NOSWITCH);
A_StartSound("ynykron/ventopen",CHAN_WEAPON,CHANF_OVERLAP);
}
XZWA ABCDEF 2 A_WeaponReady(WRF_NOFIRE|WRF_NOSWITCH);
XZWA G 2
{
A_WeaponReady(WRF_NOFIRE|WRF_NOSWITCH);
A_StartSound("ynykron/ventclose",CHAN_WEAPON,CHANF_OVERLAP);
}
XZWA HI 2 A_WeaponReady(WRF_NOFIRE|WRF_NOSWITCH);
Goto Ready;
ReadyVentSmoke:
XZWB D 1
{
invoker.ventfade = .3;
A_StartSound("ynykron/puff",CHAN_WEAPON,CHANF_OVERLAP);
}
XZWB EF 2;
XZWB G 2
{
invoker.ventfade = -.1;
}
XZWB HIJKLM 2;
Stop;
Fire:
XZW2 A 1
{
if ( (invoker.clipcount <= 0) && ((invoker.Ammo1.Amount > 0) || sv_infiniteammo || FindInventory('PowerInfiniteAmmo',true)) )
return ResolveState("Reload");
if ( invoker.chargestate == CS_IDLE )
return ResolveState("Charge");
A_YnykronFire();
A_StartSound("ynykron/ventopen",CHAN_WEAPON,CHANF_OVERLAP);
return ResolveState(null);
}
XZW3 JK 2;
XZW3 LMNOP 3;
XZW3 Q 3 A_StartSound("ynykron/ventclose",CHAN_WEAPON,CHANF_OVERLAP);
XZW3 R 3;
Goto Ready;
FireBlast:
TNT1 AAAAA 1 A_Backblast();
Stop;
FireSmoke:
XZWA J 1
{
invoker.ventfade = .3;
A_StartSound("ynykron/puff",CHAN_WEAPON,CHANF_OVERLAP);
}
XZWA KLMNO 2;
XZWA P 3
{
invoker.ventfade = -.1;
}
XZWA QRS 3;
Stop;
AltFire:
XZW2 A 2
{
if ( (invoker.clipcount <= 0) && ((invoker.Ammo1.Amount > 0) || sv_infiniteammo || FindInventory('PowerInfiniteAmmo',true)) )
{
invoker.invertreload = true;
return ResolveState("Reload");
}
A_StartSound("ynykron/meleestart",CHAN_WEAPON,CHANF_OVERLAP);
return A_JumpIf(invoker.inverted,"TakeInverted");
}
TakeNormal:
XZW3 STU 2;
XZW3 V 2
{
A_StartSound("ynykron/magout",CHAN_WEAPON,CHANF_OVERLAP);
A_PlayerReload();
}
XZW3 WXYZ 2;
XZW4 ABCDEFGHIJKLMNO 2;
XZW4 P 0
{
invoker.inverted = true;
}
Goto PutInverted;
TakeInverted:
XZW5 PQRS 2;
XZW5 T 2
{
A_StartSound("ynykron/magout",CHAN_WEAPON,CHANF_OVERLAP);
A_PlayerReload();
}
XZW5 UVWXYZ 2;
XZW6 ABCDEFGHIJKLM 2;
XZW6 N 0
{
invoker.inverted = false;
}
Goto PutNormal;
PutNormal:
XZW4 PQRS 2;
XZW4 T 2 A_StartSound("ynykron/magin",CHAN_WEAPON,CHANF_OVERLAP);
XZW4 UVWXYZ 2;
XZW5 A 2 A_StartSound("ynykron/meleeend",CHAN_WEAPON,CHANF_OVERLAP);
XZW5 BCDEFGHIJK 2;
XZW5 L 4;
Goto Ready;
PutInverted:
XZW6 NOPQ 2;
XZW6 R 2 A_StartSound("ynykron/magin",CHAN_WEAPON,CHANF_OVERLAP);
XZW6 STUVWXY 2;
XZW6 Z 2 A_StartSound("ynykron/meleeend",CHAN_WEAPON,CHANF_OVERLAP);
XZW7 ABCDEFGHIJ 2;
XZW7 K 4;
XZW5 P 0;
Goto Ready;
Discharge:
XZW2 A 2;
XZW7 NO 2;
XZW7 P 2 A_StartSound("ynykron/latch",CHAN_WEAPON,CHANF_OVERLAP);
XZW7 Q 2
{
invoker.chargestate = CS_DISCHARGING;
A_Overlay(PSP_WEAPON+1,"DischargeSmoke");
A_OverlayFlags(PSP_WEAPON+1,PSPF_RENDERSTYLE|PSPF_ALPHA|PSPF_FORCESTYLE|PSPF_FORCEALPHA,true);
A_OverlayRenderStyle(PSP_WEAPON+1,STYLE_Add);
A_OverlayAlpha(PSP_WEAPON+1,0.);
A_WeaponReady(WRF_NOFIRE|WRF_NOSWITCH);
A_StartSound("ynykron/ventopen",CHAN_WEAPON,CHANF_OVERLAP);
}
XZW7 RSTUV 2 A_WeaponReady(WRF_NOFIRE|WRF_NOSWITCH);
XZW7 W 2
{
A_WeaponReady(WRF_NOFIRE|WRF_NOSWITCH);
return A_JumpIf(invoker.chargestate==CS_IDLE,1);
}
Wait;
XZW7 W 3 A_WeaponReady(WRF_NOFIRE|WRF_NOSWITCH);
XZW7 X 2
{
A_WeaponReady(WRF_NOFIRE|WRF_NOSWITCH);
A_StartSound("ynykron/ventclose",CHAN_WEAPON,CHANF_OVERLAP);
}
XZW7 YZ 2 A_WeaponReady(WRF_NOFIRE|WRF_NOSWITCH);
Goto Ready;
DischargeSmoke:
XZWA T 2
{
invoker.ventfade = .3;
A_StartSound("ynykron/puffing",CHAN_WEAPONEXTRA3,CHANF_LOOP);
}
XZWA UVWXY 2;
XZWA Z 2 A_JumpIf(invoker.chargestate==CS_IDLE,1);
Wait;
XZWA Z 3
{
invoker.ventfade = -.1;
A_SoundVolume(CHAN_WEAPONEXTRA3,.8);
A_StartSound("ynykron/puffend",CHAN_WEAPON,CHANF_OVERLAP);
}
XZWB A 2 A_SoundVolume(CHAN_WEAPONEXTRA3,.6);
XZWB B 2 A_SoundVolume(CHAN_WEAPONEXTRA3,.4);
XZWB C 2 A_SoundVolume(CHAN_WEAPONEXTRA3,.2);
XZWB D 2 A_StopSound(CHAN_WEAPONEXTRA3);
Stop;
Charge:
XZW2 A 2;
XZW3 BC 2;
XZW3 D 2 A_StartSound("ynykron/latch",CHAN_WEAPON,CHANF_OVERLAP);
XZW3 EFGHI 2;
XZW2 A 0
{
invoker.chargestate = CS_CHARGING;
invoker.ventcooldown = Random[Ynykron](6,15);
A_StartSound("ynykron/ready",CHAN_WEAPONEXTRA2,CHANF_LOOP,.01,2.);
}
Goto Ready;
Reload:
XZW2 A 2
{
if ( invoker.chargestate>CS_IDLE ) return ResolveState("Discharge");
if ( invoker.inverted ) return ResolveState("UnloadInverted");
A_StartSound("ynykron/meleestart",CHAN_WEAPON,CHANF_OVERLAP);
return ResolveState(null);
}
UnloadNormal:
XZW3 STU 2;
XZW3 V 2 A_StartSound("ynykron/magout",CHAN_WEAPON,CHANF_OVERLAP);
XZW3 WXYZ 2;
XZW4 ABCDEFGHIJKLMNO 2;
XZW4 P 0
{
if ( !sv_infiniteammo && !FindInventory('PowerInfiniteAmmo',true) )
invoker.Ammo1.Amount = max(invoker.Ammo1.Amount-1,0);
invoker.clipcount = 1;
invoker.inverted = false;
// no mag is dropped, depleted crystals are hazardous and should be disposed of properly
if ( invoker.invertreload )
{
invoker.invertreload = false;
invoker.inverted = true;
return ResolveState("PutInverted");
}
return ResolveState(null);
}
Goto PutNormal;
UnloadInverted:
XZW5 PQRS 2;
XZW5 T 2 A_StartSound("ynykron/magout",CHAN_WEAPON,CHANF_OVERLAP);
XZW5 UVWXYZ 2;
XZW6 ABCDEFGHIJKLM 2;
XZW6 N 0
{
if ( !sv_infiniteammo && !FindInventory('PowerInfiniteAmmo',true) )
invoker.Ammo1.Amount = max(invoker.Ammo1.Amount-1,0);
invoker.clipcount = 1;
invoker.inverted = false;
// no mag is dropped, depleted crystals are hazardous and should be disposed of properly
if ( invoker.invertreload )
{
invoker.invertreload = false;
invoker.inverted = true;
return ResolveState("PutInverted");
}
return ResolveState(null);
}
Goto PutNormal;
Zoom:
XZW2 A 2
{
invoker.ventcooldown = Random[Ynykron](6,15);
A_StartSound("ynykron/checkout",CHAN_WEAPON,CHANF_OVERLAP);
A_PlayerCheckGun();
}
XZW8 ABCDEFGHIJKLMNOPQRSTUVW 2;
XZW8 X 4; // smoothen more
Goto Ready;
User1:
XZW2 A 2
{
invoker.ventcooldown = Random[Ynykron](6,15);
A_StartSound("ynykron/meleestart",CHAN_WEAPON,CHANF_OVERLAP);
A_StartSound("demolitionist/wswing",CHAN_WEAPON,CHANF_OVERLAP);
A_PlayerMelee();
}
XZW8 YZ 2;
XZW9 AB 2;
XZW9 C 1 A_Parry(9);
XZW9 DE 1;
XZW9 F 1 A_Melee(100,"demolitionist/whitl",1.5);
XZW9 GHIJK 1;
XZW9 LMNO 2;
XZW9 P 2 A_StartSound("ynykron/meleeend",CHAN_WEAPON,CHANF_OVERLAP);
XZW9 QRSTUVWXYZ 2;
Goto Ready;
Deselect:
XZW2 A 2 A_StartSound("ynykron/deselect",CHAN_WEAPON,CHANF_OVERLAP);
XZW2 BCDEFGHIJK 2;
XZW2 L -1
{
A_StopSound(CHAN_WEAPONEXTRA);
A_StopSound(CHAN_WEAPONEXTRA2);
A_FullLower();
}
Stop;
Flash:
XZWZ A 2 Bright
{
let l = Spawn("SWWMWeaponLight",pos);
l.args[0] = 255;
l.args[1] = 255;
l.args[2] = 255;
l.args[3] = 500;
l.target = self;
}
Stop;
}
}