swwmgz_m/zscript/weapons/swwm_deathlydeathcannon_fx.zsc

1094 lines
26 KiB
Text

// 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 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 poor sap
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);
foreach ( t:candidates )
{
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 = SWWMUtility.Vec3FromAngles(angle,pitch);
let t = new("YnykronTracer");
t.ignore = target;
t.ShootThroughList.Clear();
t.WaterHitList.Clear();
t.HitList.Clear();
t.Trace(pos,cursector,x,speed,TRACE_HitSky);
foreach ( l:t.ShootThroughList )
{
l.Activate(target,0,SPAC_PCross);
l.Activate(target,0,SPAC_Impact);
}
foreach ( w:t.WaterHitList )
{
let b = Spawn("InvisibleSplasher",w.hitpos);
b.target = target;
b.A_CheckTerrain();
}
for ( int i=0; i<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);
}
foreach ( hit:t.hitlist )
{
if ( YnykronShot(master) && (YnykronShot(master).hitlist.Find(hit.hitactor) < YnykronShot(master).hitlist.Size()) )
continue;
if ( hit.hitactor is 'YnykronSingularityHitbox' )
{
// detonate it instantly
let s = YnykronSingularity(hit.hitactor.target);
s.specialf2 = s.critmass;
let b = Spawn("YnykronImpact",hit.hitlocation);
b.target = target;
b.master = master;
b.angle = atan2(hit.x.y,hit.x.x);
b.pitch = asin(-hit.x.z);
if ( YnykronShot(master) )
{
YnykronShot(master).hitlist.Push(hit.hitactor);
YnykronShot(master).blastcount++;
}
continue;
}
int trad = int(max(hit.hitactor.radius,hit.hitactor.height));
if ( hit.hitactor && YnykronShot(master) )
{
YnykronShot(master).hitlist.Push(hit.hitactor);
if ( hit.hitactor.bBOSS || hit.hitactor.FindInventory("BossMarker") ) YnykronShot(master).hitboss = true;
}
// spawn blast that will propagate
let b = Spawn("YnykronImpact",hit.hitlocation);
b.tracer = hit.hitactor;
b.target = target;
b.master = master;
b.angle = atan2(hit.x.y,hit.x.x);
b.pitch = asin(-hit.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 = SWWMUtility.GetLineTracerHitNormal(t.Results);
if ( t.Results.HitType == TRACE_HitWall ) t.Results.HitLine.RemoteActivate(tracer,t.Results.Side,SPAC_Impact,t.Results.HitPos);
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);
foreach ( l:it.ShootThroughList )
{
l.Activate(target,0,SPAC_PCross);
l.Activate(target,0,SPAC_Impact); // just in case
}
foreach ( w:it.WaterHitList )
{
let b = Spawn("InvisibleSplasher",w.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);
}
foreach ( hit:it.hitlist )
{
if ( YnykronShot(master) && (YnykronShot(master).hitlist.Find(hit.hitactor) < YnykronShot(master).hitlist.Size()) )
continue;
int trad = int(max(hit.hitactor.radius,hit.hitactor.height));
if ( hit.hitactor && YnykronShot(master) )
{
YnykronShot(master).hitlist.Push(hit.hitactor);
if ( hit.hitactor.bBOSS || hit.hitactor.FindInventory("BossMarker") ) YnykronShot(master).hitboss = true;
}
// spawn blast that will propagate
let b = Spawn("YnykronImpact",hit.hitlocation);
b.tracer = hit.hitactor;
b.target = target;
b.master = master;
b.angle = atan2(hit.x.y,hit.x.x);
b.pitch = asin(-hit.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 = SWWMUtility.Vec3FromAngles(angle,pitch);
// 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] = SWWMUtility.GetAxes(angle,pitch,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,-1) )
return;
// we're done here
Destroy();
}
}