1085 lines
26 KiB
Text
1085 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;
|
|
}
|
|
Array<Actor> candidates;
|
|
candidates.Clear();
|
|
BlockThingsIterator bt;
|
|
// gather first, making sure to traverse through all portal groups just in case
|
|
for ( int i=0; i<level.GetPortalGroupCount(); i++ )
|
|
{
|
|
Vector2 disp = level.GetDisplacement(CurSector.PortalGroup,i);
|
|
bt = BlockThingsIterator.CreateFromPos(pos.x+disp.x,pos.y+disp.y,pos.z,rad,rad,false);
|
|
foreach ( t,p,f:bt )
|
|
{
|
|
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;
|
|
if ( candidates.Find(t) >= candidates.Size() )
|
|
candidates.Push(t);
|
|
}
|
|
bt.Destroy();
|
|
}
|
|
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
|
|
{
|
|
Array<Line> ShootThroughList;
|
|
Array<WaterHit> WaterHitList;
|
|
Array<HitListEntry> HitList;
|
|
|
|
override ETraceStatus TraceCallback()
|
|
{
|
|
// liquid splashes
|
|
if ( Results.CrossedWater )
|
|
{
|
|
let hl = new("WaterHit");
|
|
hl.hitpos = Results.CrossedWaterPos;
|
|
WaterHitList.Push(hl);
|
|
}
|
|
else if ( Results.Crossed3DWater )
|
|
{
|
|
let hl = new("WaterHit");
|
|
hl.hitpos = Results.Crossed3DWaterPos;
|
|
WaterHitList.Push(hl);
|
|
}
|
|
if ( Results.HitType == TRACE_HitActor )
|
|
{
|
|
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.hitpos = Results.CrossedWaterPos;
|
|
WaterHitList.Push(hl);
|
|
}
|
|
else if ( Results.Crossed3DWater )
|
|
{
|
|
let hl = new("WaterHit");
|
|
hl.hitpos = Results.Crossed3DWaterPos;
|
|
WaterHitList.Push(hl);
|
|
}
|
|
if ( Results.HitType == TRACE_HitActor )
|
|
{
|
|
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.ShootThroughList.Clear();
|
|
t.WaterHitList.Clear();
|
|
t.HitList.Clear();
|
|
t.Trace(pos,cursector,x,speed,0,ignore:target);
|
|
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_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.ShootThroughList.Clear();
|
|
it.WaterHitList.Clear();
|
|
it.HitList.Clear();
|
|
it.Trace(t.Results.HitPos,level.PointInSector(t.Results.HitPos.xy),x,i,0,ignore:target);
|
|
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.,0,ignoreallactors:true);
|
|
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 dir;
|
|
double a, s;
|
|
let [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) )
|
|
return;
|
|
// we're done here
|
|
Destroy();
|
|
}
|
|
}
|