swwmgz_m/zscript/weapons/swwm_danmaku_fx.zsc

658 lines
17 KiB
Text

// Eviscerator projectiles and effects
Class EvisceratorChunkLight : PointLightAttenuated
{
Default
{
Args 255,224,128,16;
}
override void Tick()
{
Super.Tick();
if ( !EvisceratorChunk(target) || EvisceratorChunk(target).justdied )
{
Destroy();
return;
}
if ( isFrozen() || (freezetics > 0) ) return;
SetOrigin(target.pos,true);
double intst = clamp((.7-EvisceratorChunk(target).lifetime)/.7,0.,1.);
args[LIGHT_RED] = int(255*intst);
args[LIGHT_GREEN] = int(224*intst);
args[LIGHT_BLUE] = int(128*intst);
}
}
Class ChunkImpact : SWWMNonInteractiveActor
{
override void PostBeginPlay()
{
Super.PostBeginPlay();
A_SprayDecal("WallCrack",-20);
int numpt = Random[Eviscerator](-1,2);
Vector3 x = SWWMUtility.Vec3FromAngles(angle,pitch);
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = (x+SWWMUtility.Vec3FromAngles(FRandom[Eviscerator](0,360),FRandom[Eviscerator](-90,90))*.8).unit()*FRandom[Eviscerator](.1,1.2);
let s = Spawn("SWWMSmoke",pos);
s.vel = pvel;
s.scale *= .6;
s.special1 = Random[Eviscerator](0,1);
s.SetShade(Color(1,1,1)*Random[Eviscerator](96,192));
}
numpt = Random[Eviscerator](-2,2);
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = SWWMUtility.Vec3FromAngles(FRandom[Eviscerator](0,360),FRandom[Eviscerator](-90,90))*FRandom[Eviscerator](2,8);
let s = Spawn("SWWMSpark",pos);
s.vel = pvel;
}
numpt = Random[Eviscerator](-2,2);
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = SWWMUtility.Vec3FromAngles(FRandom[Eviscerator](0,360),FRandom[Eviscerator](-90,90))*FRandom[Eviscerator](1,4);
let s = Spawn("SWWMChip",pos);
s.vel = pvel;
}
Destroy();
}
}
Class EvisceratorChunkGlow : SWWMNonInteractiveActor
{
Default
{
RenderStyle "Add";
Scale .25;
+FORCEXYBILLBOARD;
}
override void Tick()
{
if ( freezetics > 0 )
{
freezetics--;
return;
}
if ( isFrozen() ) return;
if ( !EvisceratorChunk(target) || EvisceratorChunk(target).justdied )
{
Scale *= .9;
A_FadeOut();
return;
}
SetOrigin(target.pos,true);
alpha = clamp((.7-EvisceratorChunk(target).lifetime)/.7,0.,1.);
if ( alpha <= 0. ) Destroy();
}
States
{
Spawn:
ETRL A -1 Bright;
Stop;
}
}
Class EvisceratorChunkTrail : SWWMNonInteractiveActor
{
Default
{
RenderStyle "Add";
XScale 8.;
+FORCEXYBILLBOARD;
}
override void Tick()
{
if ( freezetics > 0 )
{
freezetics--;
return;
}
if ( isFrozen() ) return;
A_SetScale(scale.x*(.6+specialf1),scale.y);
A_FadeOut(.1+specialf2);
}
States
{
Spawn:
XZW1 ABCDEFGHIJ -1 Bright;
Stop;
}
}
Class EvisceratorChunk : Actor
{
Mixin SWWMMissileFix;
Actor lasthit;
double anglevel, pitchvel, rollvel;
double lifetime, lifespeed;
Vector3 oldvel;
bool justdied;
int trailcolor;
Default
{
Obituary "$O_EVISCERATOR";
Radius 2;
Height 4;
Speed 50;
DamageFunction int(clamp((vel.length()-5)*.2,0,15)+(max(0,1-lifetime)**5)*10);
DamageType 'Shrapnel';
BounceFactor 1.0;
WallBounceFactor 1.0;
PROJECTILE;
+USEBOUNCESTATE;
+BOUNCEONWALLS;
+BOUNCEONFLOORS;
+BOUNCEONCEILINGS;
+ALLOWBOUNCEONACTORS;
+NODAMAGETHRUST;
+DONTBOUNCEONSKY;
+CANBOUNCEWATER;
+INTERPOLATEANGLES;
+ROLLSPRITE;
+ROLLCENTER;
Scale 0.4;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
let l = Spawn("EvisceratorChunkLight",pos);
l.target = self;
let t = Spawn("EvisceratorChunkGlow",pos);
t.target = self;
lifespeed = FRandom[Eviscerator](0.01,0.02)*clamp((vel.length()/75.)**2,1.,2.);
anglevel = FRandom[Eviscerator](50,100)*RandomPick[Eviscerator](-1,1);
pitchvel = FRandom[Eviscerator](50,100)*RandomPick[Eviscerator](-1,1);
rollvel = FRandom[Eviscerator](50,100)*RandomPick[Eviscerator](-1,1);
scale *= Frandom[Eviscerator](0.8,1.2);
frame = Random[Eviscerator](0,7);
}
override void Tick()
{
static const name tls[] =
{
'HotMetal0', 'HotMetal1', 'HotMetal2', 'HotMetal3',
'HotMetal4', 'HotMetal5', 'HotMetal6', 'HotMetal7'
};
oldvel = vel;
Super.Tick();
// somehow checking the state does not work all the time
// so instead I have to set a bool at the start of XDeath,
// otherwise there is a single puff of smoke at the LAST tic
// of the state, there is no logical explanation for this,
// I guess I can blame graf, randi, or whoever else
if ( isFrozen() || (freezetics > 0) || justdied ) return;
lifetime += lifespeed;
if ( waterlevel > 0 ) lifetime = max(.7,lifetime);
A_SetTranslation(tls[clamp(int(lifetime*10),0,7)]);
if ( !Random[Eviscerator](0,2) && (lifetime < .7) )
{
let s = Spawn("SWWMHalfSmoke",pos);
s.vel = .2*vel+SWWMUtility.Vec3FromAngles(FRandom[Eviscerator](0,360),FRandom[Eviscerator](-90,90));
s.scale *= .5;
s.alpha *= scale.x*max(0,.7-lifetime)*1.5;
}
if ( !InStateSequence(CurState,FindState("Death")) )
{
angle += anglevel;
pitch += pitchvel;
roll += rollvel;
double alph = clamp((.7-lifetime)/.7,0,1.);
if ( alph < 0 ) return;
Vector3 dir = level.Vec3Diff(pos,prev);
double dist = dir.length();
if ( dist < 1. ) return;
dir /= dist;
let t = Spawn("EvisceratorChunkTrail",pos);
t.alpha = alph;
[t.angle, t.pitch, t.scale.y] = SWWMUtility.CalcYBeam(dir,dist);
t.SetState(t.SpawnState+trailcolor);
if ( trailcolor > 0 )
{
// custom trails last longer
t.specialf1 = .3;
t.specialf2 = -.05;
}
}
}
void A_HandleBounce()
{
Vector3 HitNormal = (0,0,0);
F3DFloor ff;
if ( BlockingFloor )
{
// find closest 3d floor for its normal
for ( int i=0; i<BlockingFloor.Get3DFloorCount(); i++ )
{
if ( !(BlockingFloor.Get3DFloor(i).flags&F3DFloor.FF_SOLID) ) continue;
if ( !(BlockingFloor.Get3DFloor(i).top.ZAtPoint(pos.xy) ~== floorz) ) continue;
ff = BlockingFloor.Get3DFloor(i);
break;
}
if ( ff ) HitNormal = -ff.top.Normal;
else HitNormal = BlockingFloor.floorplane.Normal;
}
else if ( BlockingCeiling )
{
// find closest 3d floor for its normal
for ( int i=0; i<BlockingCeiling.Get3DFloorCount(); i++ )
{
if ( !(BlockingCeiling.Get3DFloor(i).flags&F3DFloor.FF_SOLID) ) continue;
if ( !(BlockingCeiling.Get3DFloor(i).bottom.ZAtPoint(pos.xy) ~== ceilingz) ) continue;
ff = BlockingCeiling.Get3DFloor(i);
break;
}
if ( ff ) HitNormal = -ff.bottom.Normal;
else HitNormal = BlockingCeiling.ceilingplane.Normal;
}
else if ( BlockingLine )
{
HitNormal = (-BlockingLine.delta.y,BlockingLine.delta.x,0).unit();
if ( !SWWMUtility.PointOnLineSide(pos.xy,BlockingLine) )
HitNormal *= -1;
}
else if ( BlockingMobj )
{
Vector3 diff = level.Vec3Diff(BlockingMobj.Vec3Offset(0,0,BlockingMobj.Height/2),pos);
HitNormal = diff.unit();
}
else if ( vel.length() > 0. ) HitNormal = vel.unit();
if ( swwm_omnibust ) BusterWall.ProjectileBust(self,GetMissileDamage(0,0),oldvel.unit());
// undo the bounce, we need to hook in our own
vel = oldvel;
// re-do the bounce with our formula
Vector3 RealHitNormal = HitNormal;
double dfact = clamp((oldvel.length()/75.)**2.,.02,.6);
HitNormal = (HitNormal+SWWMUtility.Vec3FromAngles(FRandom[Eviscerator](0,360),FRandom[Eviscerator](-90,90))*dfact).unit();
if ( (HitNormal dot RealHitNormal) < 0 ) HitNormal *= -.5;
vel = FRandom[Eviscerator](.8,.95)*((vel dot HitNormal)*HitNormal*(FRandom[Eviscerator](-1.8,-1.))+vel);
bHITOWNER = true;
lasthit = null;
if ( (vel.length() > 20) && !Random[Eviscerator](0,2) )
{
let l = Spawn("ChunkImpact",pos);
l.angle = atan2(RealHitNormal.y,RealHitNormal.x);
l.pitch = asin(-RealHitNormal.z);
A_StartSound("eviscerator/hith",CHAN_WEAPON,CHANF_OVERLAP,.5);
}
A_Gravity();
gravity = clamp(.35-vel.length()/200.,.15,.35);
anglevel = FRandom[Eviscerator](50,100)*RandomPick[Eviscerator](-1,1)*(vel.length()/speed);
pitchvel = FRandom[Eviscerator](50,100)*RandomPick[Eviscerator](-1,1)*(vel.length()/speed);
rollvel = FRandom[Eviscerator](50,100)*RandomPick[Eviscerator](-1,1)*(vel.length()/speed);
A_StartSound("eviscerator/hit",CHAN_WEAPON,CHANF_OVERLAP,.3);
if ( vel.length() < 3 )
{
A_Stop();
ClearBounce();
ExplodeMissile();
}
}
override bool CanCollideWith( Actor other, bool passive )
{
// safer to do here
if ( !(other.bSHOOTABLE && other.bSOLID) || ((vel.length() <= 5) && other.bSHOOTABLE) || ((other == target) && !bHITOWNER) || (other == lasthit) )
return false;
return true;
}
override int SpecialMissileHit( Actor victim )
{
// directly bounce off shootable solids
if ( !victim.bSHOOTABLE )
{
if ( bSOLID )
{
BlockingMobj = victim;
A_HandleBounce();
lasthit = victim;
}
return 1;
}
// with this we can guarantee that the chunk won't just keep on dealing damage
// this is something I wish Unreal's boulders did
lasthit = victim;
// don't knock back if already dead
int oldamt = SWWMDamageAccumulator.GetAmount(victim);
if ( victim.health-oldamt > 0 ) SWWMUtility.DoKnockback(victim,vel.unit(),15000);
// gather damage
int dmg = GetMissileDamage(0,0);
SWWMDamageAccumulator.Accumulate(victim,dmg,self,target,damagetype);
int amt = SWWMDamageAccumulator.GetAmount(victim);
// pass through if it's already dead
// + random chance relative to health
int posthealth = victim.health-amt;
double hratio = posthealth/double(victim.GetSpawnHealth());
if ( (posthealth <= 0) || (FRandom[Eviscerator](hratio,1.) < .7) )
{
if ( !victim.bNOBLOOD && !victim.bDORMANT && !victim.bINVULNERABLE )
{
victim.SpawnBlood(pos,AngleTo(victim),dmg);
A_StartSound("eviscerator/hitf",CHAN_WEAPON,CHANF_OVERLAP,.1);
}
else
{
let l = Spawn("ChunkImpact",pos);
l.angle = angle+180;
l.pitch = -pitch;
A_StartSound("eviscerator/hith",CHAN_WEAPON,CHANF_OVERLAP,.1);
}
vel *= FRandom[Eviscerator](.8,.9); // reduce velocity as it rips
A_Gravity();
gravity = clamp(.35-vel.length()/200.,.15,.35);
return 1;
}
// HACK
if ( !victim.bNOBLOOD && !victim.bDORMANT && !victim.bINVULNERABLE )
{
victim.SpawnBlood(pos,AngleTo(victim),dmg);
A_StartSound("eviscerator/hitf",CHAN_WEAPON,CHANF_OVERLAP,.1);
ExplodeMissile(null,victim);
}
else
{
BlockingMobj = victim;
A_HandleBounce();
lasthit = victim;
}
return 1;
}
States
{
Spawn:
XZW1 # -1;
Stop;
Bounce:
XZW1 # 0 A_HandleBounce();
Goto Spawn;
Death:
XZW1 # 0
{
bMOVEWITHSECTOR = true;
A_SetTics(Random[Eviscerator](30,50));
}
XZW1 # 1 A_FadeOut();
Wait;
XDeath:
TNT1 A 35 { invoker.justdied = true; }
Stop;
}
}
Class EvisceratorProjSmoke : SWWMNonInteractiveActor
{
double lifetime, lifespeed;
override void PostBeginPlay()
{
lifetime = 0;
lifespeed = FRandom[Eviscerator](0.004,0.008);
}
override void Tick()
{
if ( freezetics > 0 )
{
freezetics--;
return;
}
if ( isFrozen() ) return;
lifetime += lifespeed;
let s = Spawn("SWWMSmoke",pos);
s.vel = SWWMUtility.Vec3FromAngles(FRandom[Eviscerator](0,360),FRandom[Eviscerator](-90,90))*.5;
s.vel.z += 2.;
s.alpha = scale.x;
s.SetShade(Color(1,1,1)*Random[Eviscerator](160,255));
scale.x = max(0,1-lifetime);
if ( scale.x <= 0 ) Destroy();
}
}
Class EvisceratorProjLight : PaletteLight
{
Default
{
Args 0,0,0,140;
ReactionTime 20;
}
}
Class EvisceratorProj : Actor
{
Mixin SWWMMissileFix;
double heat;
Vector3 startpos;
Default
{
Obituary "$O_EVISCERATOR";
DamageType 'Explosive';
Radius 2;
Height 4;
Gravity 0.35;
Speed 60;
PROJECTILE;
-NOGRAVITY;
+EXPLODEONWATER;
+HITTRACER;
+FORCERADIUSDMG;
+NODAMAGETHRUST;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
startpos = pos;
if ( waterlevel <= 0 ) vel.z += 3;
heat = 1.5;
}
action void A_EvisExplode()
{
if ( target && tracer && (tracer.bIsMonster||tracer.player) && tracer.IsHostile(target) )
{
double dist = level.Vec3Diff(pos,invoker.startpos).length();
SWWMUtility.AchievementProgress("lead",int(dist),target.player);
}
bForceXYBillboard = true;
A_SetRenderStyle(1.0,STYLE_Add);
A_SprayDecal("BigRocketBlast",50);
A_NoGravity();
A_SetScale(3.);
SWWMUtility.DoExplosion(self,120,120000,150,80);
A_QuakeEx(6,6,6,20,0,1200,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,falloff:300,rollIntensity:.7);
A_StartSound("eviscerator/shell",CHAN_WEAPON,attenuation:.5);
A_StartSound("eviscerator/shell",CHAN_VOICE,attenuation:.3);
A_AlertMonsters(swwm_uncapalert?0:3000);
if ( !Tracer ) Spawn("EvisceratorProjSmoke",pos);
Spawn("EvisceratorProjLight",pos);
Vector3 x, y, z;
double a, s;
[x, y, z] = swwm_CoordUtil.GetAxes(pitch,angle,roll);
EvisceratorChunk p;
Vector3 spawnofs;
if ( BlockingMobj ) spawnofs = (0,0,0);
else if ( BlockingFloor ) spawnofs = BlockingFloor.floorplane.Normal*4;
else if ( BlockingCeiling ) spawnofs = BlockingCeiling.ceilingplane.Normal*4;
else if ( BlockingLine )
{
spawnofs = (-BlockingLine.delta.y,BlockingLine.delta.x,0).unit()*4;
if ( !SWWMUtility.PointOnLineSide(pos.xy,BlockingLine) )
spawnofs *= -1;
}
int trail = 0;
if ( target && target.player ) trail = CVar.GetCVar('swwm_funtrails',target.player).GetInt();
if ( trail == 8 ) trail = Random[Eviscerator](0,7);
for ( int i=0; i<30; i++ )
{
p = EvisceratorChunk(Spawn("EvisceratorChunk",level.Vec3Offset(pos,spawnofs)));
p.bHITOWNER = true;
a = FRandom[Eviscerator](0,360);
s = FRandom[Eviscerator](0,.4);
Vector3 dir = SWWMUtility.ConeSpread(x,y,z,a,s);
p.angle = atan2(dir.y,dir.x);
p.pitch = -asin(dir.z);
p.vel = SWWMUtility.Vec3FromAngles(p.angle,p.pitch)*(p.speed+FRandom[Eviscerator](-5,20));
p.target = target;
if ( trail < 8 ) p.trailcolor = max(0,trail);
else if ( trail == 9 ) p.trailcolor = (i%6)+2;
else if ( trail == 10 )
{
switch ( i%6 )
{
case 0:
case 3:
p.trailcolor = 8;
break;
case 1:
case 4:
p.trailcolor = 9;
break;
case 2:
p.trailcolor = 1;
break;
}
}
}
int numpt = Random[Eviscerator](10,15);
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = SWWMUtility.Vec3FromAngles(FRandom[ExploS](0,360),FRandom[ExploS](-90,90))*FRandom[ExploS](1,3);
let s = Spawn("SWWMSmoke",pos);
s.vel = pvel;
s.SetShade(Color(1,1,1)*Random[ExploS](64,224));
s.special1 = Random[ExploS](1,2);
s.scale *= 2.4;
s.alpha *= .4;
}
numpt = Random[Eviscerator](8,12);
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = SWWMUtility.Vec3FromAngles(FRandom[Eviscerator](0,360),FRandom[Eviscerator](-90,90))*FRandom[Eviscerator](2,8);
let s = Spawn("SWWMSpark",level.Vec3Offset(pos,spawnofs));
s.vel = pvel;
}
numpt = Random[Eviscerator](8,16);
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = SWWMUtility.Vec3FromAngles(FRandom[Eviscerator](0,360),FRandom[Eviscerator](-90,90))*FRandom[Eviscerator](6,16);
let s = Spawn("SWWMChip",level.Vec3Offset(pos,spawnofs));
s.vel = pvel;
s.scale *= FRandom[Eviscerator](0.9,1.8);
}
Spawn("EvisceratorRing",pos);
if ( swwm_omnibust ) BusterWall.ProjectileBust(self,150,SWWMUtility.Vec3FromAngles(angle,pitch));
}
action void A_SubExpl()
{
special1++;
if ( special1 > 8 ) return;
int numpt = Random[Eviscerator](0,8-special1);
double ang, pt;
for ( int i=0; i<numpt; i++ )
{
ang = FRandom[Eviscerator](0,360);
pt = FRandom[Eviscerator](-90,90);
FLineTraceData d;
Vector3 HitNormal;
LineTrace(ang,FRandom[Eviscerator](10,30)+10*special1,pt,TRF_THRUACTORS,data: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;
}
let p = Spawn("EvisceratorSubExpl",d.HitLocation+hitnormal*4);
p.angle = atan2(hitnormal.y,hitnormal.x);
p.pitch = asin(-hitnormal.z);
p.target = target;
p.scale *= 2-special1*.1;
p.alpha *= 1-special1*.1;
}
}
States
{
Spawn:
XZW1 A 1
{
invoker.heat -= 0.004+0.0004*vel.length();
if ( invoker.heat > 0 )
{
let s = Spawn("SWWMHalfSmoke",pos);
s.alpha *= heat;
}
}
Wait;
Death:
TNT1 A 0 A_EvisExplode();
XSEX ABCDEFGHIJKLMNOPQRS 2 Bright A_Subexpl();
Stop;
}
}
Class EvisceratorSubExpl : SWWMNonInteractiveActor
{
Default
{
RenderStyle "Add";
Scale 2.;
Alpha .6;
+FORCEXYBILLBOARD;
}
States
{
Spawn:
XSEX ABCDEFGHIJKLMNOPQRS 1 Bright;
Stop;
}
}
Class EvisceratorRing : SWWMNonInteractiveActor
{
Default
{
RenderStyle "Add";
Scale 4.;
+FORCEXYBILLBOARD;
}
States
{
Spawn:
XRG0 ABCDEFGHIJKLMNOPQRSTUVWX 1 Bright A_SetScale(scale.x*1.01);
Stop;
}
}
Class EvisceratorCasing : SWWMCasing
{
Default
{
Mass 10;
BounceFactor 0.4;
WallBounceFactor 0.4;
BounceSound "eviscerator/casing";
}
States
{
Death:
XZW1 BC -1
{
bINTERPOLATEANGLES = false;
pitch = roll = 0;
angle = FRandom[Junk](0,360);
frame = RandomPick[Junk](1,2);
}
Stop;
}
}