flak_m/zscript/warheadlauncher.zsc
Marisa Kirisame 5baca999b5 Made weapons switchable while selecting.
Added footsteps.
Some minor tweaks.
Listed known bugs in readme.
2018-05-30 17:52:20 +02:00

689 lines
16 KiB
Text

// shouldn't be placed in the world (it wasn't in UT99)
Class WarheadAmmo : Ammo
{
Default
{
Tag "Redeemer Missile";
Inventory.PickupMessage "You picked up a Redeemer Missile.";
Inventory.Amount 1;
Inventory.MaxAmount 2;
Ammo.BackpackAmount 0;
Ammo.BackpackMaxAmount 2;
Ammo.DropAmount 1;
Inventory.RespawnTics 2100;
}
States
{
Spawn:
WMIS A -1;
Stop;
}
}
Class ShockWave : Actor
{
double shocksize, olddmgradius;
double lifespan;
int icount;
ThinkerIterator t;
Default
{
Obituary "%o was vaporized by %k's Redeemer!!";
RenderStyle "Add";
Radius 0.1;
Height 0;
Scale 1.0;
ReactionTime 60;
+NOBLOCKMAP;
+NOGRAVITY;
+DONTSPLASH;
}
override void PostBeginPlay()
{
lifespan = ReactionTime;
A_PlaySound("warhead/explode",CHAN_VOICE,attenuation:ATTN_NONE);
A_QuakeEx(9,9,9,100,0,12000,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:0.5);
t = ThinkerIterator.Create("Actor");
}
override void Tick()
{
Super.Tick();
if ( globalfreeze || level.frozen ) return;
if ( alpha <= 0 ) return;
icount++;
if ( icount == 4 ) Spawn("WarheadSubExplosion",pos);
lifespan--;
alpha -= 1./ReactionTime;
shocksize = 13*(ReactionTime-lifespan)+3.5/(lifespan/ReactionTime+0.05);
A_SetScale(shocksize*0.25);
double dmgradius = shocksize*1.5;
Actor a;
t.Reinit();
while ( a = Actor(t.Next()) )
{
if ( !a.bShootable || !CheckSight(a) || (Distance3D(a) > dmgradius) ) continue;
Vector3 dir = Vec3To(a);
double dist = max(1,dir.length());
dir = dir/dist+(0,0,0.3);
double moscale = max(0,1100-0.22*dist);
if ( (dist > olddmgradius) || (dir dot a.vel < 0) )
{
a.vel += dir*(moscale/a.mass+20);
a.DamageMobj(self,target,moscale,'RedeemerDeath',DMG_THRUSTLESS);
}
}
olddmgradius = dmgradius;
}
States
{
Spawn:
RWAV A 100 Bright;
Stop;
}
}
Class WarheadSubExplosion : Actor
{
Default
{
Renderstyle "Add";
Scale 2.8;
+NOGRAVITY;
+NOCLIP;
+DONTSPLASH;
+FORCEXYBILLBOARD;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
Spawn("WarheadExplodLight",pos);
}
States
{
Spawn:
WE__ ABCDEFGHIJKLMNOPR 3 Bright;
Stop;
}
}
Class WarheadHitbox : Actor
{
Default
{
Radius 8;
Height 8;
+SHOOTABLE;
+NOGRAVITY;
+NOCLIP;
+DONTSPLASH;
+NOBLOOD;
}
override int DamageMobj( Actor inflictor, Actor source, int damage, Name mod, int flags, double angle )
{
if ( (damage > 5) && target )
{
target.bAMBUSH = true;
target.ExplodeMissile(null,inflictor);
}
return 0;
}
override void Tick()
{
Super.Tick();
if ( !target )
{
Destroy();
return;
}
SetOrigin(target.pos-(0,0,height*0.5),true);
}
States
{
Spawn:
TNT1 A 10 A_AlertMonsters(0,AMF_TARGETEMITTER);
Wait;
}
}
Class WarheadExplodLight : DynamicLight
{
double lifetime;
Default
{
DynamicLight.Type "Point";
ReactionTime 50;
Args 255,192,128,300;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
lifetime = 1.0;
}
override void Tick()
{
Super.Tick();
if ( globalfreeze || level.frozen ) return;
args[LIGHT_RED] = 255*lifetime;
args[LIGHT_GREEN] = 192*lifetime;
args[LIGHT_BLUE] = 128*lifetime;
lifetime -= 1./ReactionTime;
if ( lifetime <= 0 ) Destroy();
}
}
Class WarheadLight : DynamicLight
{
Default
{
DynamicLight.Type "Point";
Args 255,224,192,70;
}
override void Tick()
{
Super.Tick();
if ( !target )
{
Destroy();
return;
}
Vector3 taildir = -(cos(target.angle)*cos(target.pitch),sin(target.angle)*cos(target.pitch),-sin(target.pitch));
SetOrigin(target.Vec3Offset(taildir.x*20,taildir.y*20,taildir.z*20),true);
args[LIGHT_INTENSITY] = Random[Warhead](6,8)*10;
}
}
Class WarheadTrail : Actor
{
Default
{
RenderStyle "Add";
Radius 0.1;
Height 0;
+NOGRAVITY;
+NOCLIP;
+DONTSPLASH;
+FORCEXYBILLBOARD;
+ROLLSPRITE;
+ROLLCENTER;
Scale 0.2;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
double ang, pt;
scale *= FRandom[Puff](0.5,1.5);
alpha *= FRandom[Puff](0.5,1.5);
ang = FRandom[Puff](0,360);
pt = FRandom[Puff](-90,90);
vel += (cos(pt)*cos(ang),cos(pt)*sin(ang),-sin(pt))*FRandom[Puff](0.2,0.8);
roll = FRandom[Puff](0,360);
}
override void Tick()
{
Super.Tick();
if ( level.frozen || globalfreeze ) return;
vel *= 0.99;
A_FadeOut(0.1);
}
States
{
Spawn:
RTRL A -1 Bright;
Stop;
}
}
Class WarShell : Actor
{
double destangle, destpitch;
Actor l, b;
Default
{
Obituary "%o was vaporized by %k's Redeemer!!";
Radius 4;
Height 4;
Speed 2;
DamageType 'RedeemerDeath';
DamageFactor 1000;
PROJECTILE;
+FORCEXYBILLBOARD;
+SKYEXPLODE;
+FORCERADIUSDMG;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
l = Spawn("Warheadlight",pos);
l.target = self;
b = Spawn("WarheadHitbox",pos);
b.target = self;
A_PlaySound("warhead/fly",CHAN_VOICE,1.0,true);
destangle = angle;
destpitch = pitch;
}
override int SpecialMissileHit( Actor victim )
{
if ( victim == b ) return 1;
return -1;
}
override void Tick()
{
Super.Tick();
if ( globalfreeze || level.frozen ) return;
if ( !bMISSILE ) return;
if ( vel.length() > 0 )
{
if ( waterlevel > 0 )
{
vel *= 0.98;
if ( vel.length() < 5 ) vel += vel.unit()*0.5;
}
else if ( vel.length() < 10 ) vel += vel.unit()*0.5;
}
}
action void A_Trail()
{
Vector3 taildir = -(cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(pitch));
if ( waterlevel > 0 )
{
for ( int i=0; i<4; i++ )
{
let s = Spawn("UTBubble",pos+taildir*32+(FRandom[Warhead](-.5,.5),FRandom[Warhead](-.5,.5),FRandom[Warhead](-.5,.5)));
s.vel = taildir*2;
}
return;
}
for ( int i=0; i<4; i++ )
{
let s = Spawn("UTSmoke",pos+taildir*32+(FRandom[Warhead](-.5,.5),FRandom[Warhead](-.5,.5),FRandom[Warhead](-.5,.5)));
s.vel = taildir*2;
s.SetShade("404040");
}
for ( int i=0; i<4; i++ )
{
let s = Spawn("WarheadTrail",pos+taildir*35+(FRandom[Warhead](-1,1),FRandom[Warhead](-1,1),FRandom[Warhead](-1,1)));
s.vel = taildir*4;
}
}
action void A_Vaporize()
{
if ( invoker.l ) invoker.l.Destroy();
if ( invoker.b ) invoker.b.Destroy();
A_SetScale(2.0);
A_Explode(1000,300);
A_SprayDecal("BigBlast");
A_QuakeEx(8,8,8,20,0,300,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:0.35);
A_PlaySound("shock/hit",CHAN_VOICE,attenuation:0.5);
A_AlertMonsters();
A_SetRenderStyle(1.0,STYLE_Add);
Spawn("WarheadExplodLight",pos);
let s = Spawn("ShockWave",pos);
s.target = target;
}
action void A_Intercepted()
{
if ( invoker.l ) invoker.l.Destroy();
if ( invoker.b ) invoker.b.Destroy();
A_Explode(1000,350);
A_SprayDecal("BigBlast");
A_QuakeEx(8,8,8,20,0,300,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:0.35);
A_PlaySound("shock/hit",CHAN_VOICE,attenuation:0.5);
A_AlertMonsters();
A_SetRenderStyle(1.0,STYLE_Add);
Spawn("WarheadExplodLight",pos);
}
States
{
Spawn:
WMIS A 1 A_Trail();
Wait;
Death:
TNT1 A 0 A_JumpIf(bAMBUSH,"Death.Intercept");
TNT1 A 0 A_Vaporize();
NE__ ABCDEFGHIJKLMNOPR 3 Bright;
Stop;
Death.Intercept:
TNT1 A 0 A_Intercepted();
WE__ ABCDEFGHIJKLMNOPR 3 Bright;
Stop;
}
}
Class GuidedWarShell : WarShell
{
double lagangle, lagpitch, lagangle2, lagpitch2;
double guideangle, guidepitch, lastguideroll;
override void PostBeginPlay()
{
Super.PostBeginPlay();
if ( target && target.player ) target.player.camera = self;
guideangle = angle;
guidepitch = pitch;
}
override void Tick()
{
Actor.Tick();
if ( globalfreeze || level.frozen ) return;
if ( !bMISSILE ) return;
if ( !target || !target.player || (target.Health <= 0) )
{
bAMBUSH = true;
ExplodeMissile();
return;
}
if ( target.player.cmd.buttons&BT_ATTACK )
{
ExplodeMissile();
return;
}
if ( vel.length() > 0 )
{
lagangle = target.player.cmd.yaw/128.;
lagpitch = -target.player.cmd.pitch/128.;
guideangle += lagangle2*0.95+lagangle*0.05;
guidepitch += lagpitch2*0.95+lagpitch*0.05;
guidepitch = Clamp(guidepitch,-89,89);
double guideroll = -lagangle2*15;
Vector3 dir = (cos(guideangle)*cos(guidepitch),sin(guideangle)*cos(guidepitch),-sin(guidepitch));
destangle = atan2(dir.y,dir.x);
destpitch = asin(-dir.z);
double destroll = lastguideroll*0.9+guideroll*0.1;
A_SetAngle(destangle,SPF_INTERPOLATE);
A_SetPitch(destpitch,SPF_INTERPOLATE);
A_SetRoll(destroll,SPF_INTERPOLATE);
vel = vel.length()*(cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(pitch));
if ( waterlevel > 0 )
{
vel *= 0.98;
if ( vel.length() < 5 ) vel += vel.unit()*0.5;
}
else if ( vel.length() < 10 ) vel += vel.unit()*0.5;
}
lagangle2 = lagangle2*0.95+lagangle*0.05;
lagpitch2 = lagpitch2*0.95+lagpitch*0.05;
lastguideroll = roll*0.98;
}
States
{
Death:
TNT1 A 0
{
if ( WarheadLauncher(master) )
WarheadLauncher(master).Guided = null;
if ( target && target.player && target.player.camera == self ) target.player.camera = target;
}
Goto Super::Death;
}
}
Class MidTracer : 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] ) return TRACE_Stop;
return TRACE_Skip;
}
return TRACE_Stop;
}
}
Class TargetActor
{
Vector2 vpos;
String diststr;
}
Class RedeemerHUD : HUDMessageBase
{
Actor Camera;
Vector3 ViewPos;
double ViewAngle, ViewPitch, ViewRoll;
TextureID reticle, mark, readout;
Font whfont;
ThinkerIterator t;
MidTracer tr;
Array<TargetActor> ta;
RedeemerHUD Init()
{
reticle = TexMan.CheckForTexture("GuidedX",TexMan.Type_Any);
mark = TexMan.CheckForTexture("Crosshr6",TexMan.Type_Any);
readout = TexMan.CheckForTexture("Readout",TexMan.Type_Any);
whfont = Font.GetFont('WHFONT');
t = ThinkerIterator.Create("Actor");
tr = new("MidTracer");
return self;
}
override bool Tick()
{
// shootable targetting
if ( CVar.GetCVar('flak_redeemerreadout',players[consoleplayer]).GetBool() )
{
t.Reinit();
ta.Clear();
Actor a;
Vector3 vdir = (cos(ViewAngle)*cos(ViewPitch),sin(ViewAngle)*cos(ViewPitch),-sin(ViewPitch));
while ( a = Actor(t.Next()) )
{
Vector3 tdir = Level.Vec3Diff(ViewPos,a.Pos+(0,0,a.Height*0.5));
if ( !a.bSHOOTABLE || (a.Health <= 0) || ((Camera is 'GuidedWarShell') && (a == GuidedWarShell(Camera).b)) || (tdir.length() > 2000) || (acos(tdir.unit() dot vdir) > players[consoleplayer].FOV) || tr.Trace(ViewPos,Camera.CurSector,tdir.unit(),tdir.length(),0) ) continue;
Vector3 wpos = ViewPos+tdir;
Vector3 spos = mkCoordUtil.WorldToScreen(wpos,ViewPos,ViewPitch,ViewAngle,ViewRoll,players[consoleplayer].FOV);
if ( spos.z > 1.0 ) continue;
TargetActor te = new("TargetActor");
te.vpos = mkCoordUtil.ToViewport(spos);
te.diststr = String.Format("%f",tdir.length());
te.diststr.Replace(".","");
ta.Push(te);
}
}
return !Camera;
}
override void Draw( int bottom, int visibility )
{
if ( visibility != StatusBar.HUDMSGLayer_UnderHUD ) return;
Screen.Dim("Red",0.5,0,0,Screen.GetWidth(),Screen.GetHeight());
// shootable targetting
if ( CVar.GetCVar('flak_redeemerreadout',players[consoleplayer]).GetBool() )
{
for ( int i=0; i<ta.Size(); i++ )
{
Screen.DrawTexture(mark,false,ta[i].vpos.x,ta[i].vpos.y);
Screen.DrawText(whfont,Font.CR_UNTRANSLATED,(ta[i].vpos.x-whfont.StringWidth(ta[i].diststr)/2)-12,ta[i].vpos.y+8,ta[i].diststr);
}
}
// other stuff
Screen.DrawTexture(reticle,false,320,240,DTA_VirtualWidth,640,DTA_VirtualHeight,480);
int numreadouts = Screen.GetHeight()/128+2;
for ( int i=0; i<numreadouts; i++ )
{
int scroll = (gametic*5)%128;
Screen.DrawTexture(readout,false,0,i*128-scroll);
}
}
}
Class RedeemerHUDStatic : HUDMessageBase
{
int tickcnt;
TextureID tx;
RedeemerHUDStatic Init()
{
tx = TexMan.CheckForTexture("static1",TexMan.Type_Any);
tickcnt = 0;
return self;
}
override bool Tick()
{
return (++tickcnt>15);
}
override void Draw( int bottom, int visibility )
{
if ( visibility != StatusBar.HUDMSGLayer_UnderHUD ) return;
double sw, sh;
sw = 256.;
sh = sw*(Screen.GetHeight()/double(Screen.GetWidth()));
Screen.DrawTexture(tx,true,0,0,DTA_VirtualWidthF,sw,DTA_VirtualHeightF,sh,DTA_KeepRatio,true);
}
}
Class RedeemerHUDHandler : EventHandler
{
ui RedeemerHUD rhud;
override void RenderOverlay( RenderEvent e )
{
if ( e.Camera is 'GuidedWarShell' )
{
if ( !rhud )
{
rhud = new("RedeemerHUD").Init();
StatusBar.AttachMessage(rhud,0,StatusBar.HUDMSGLayer_UnderHUD);
}
rhud.Camera = e.Camera;
rhud.ViewPos = e.ViewPos;
rhud.ViewAngle = e.ViewAngle;
rhud.ViewPitch = e.ViewPitch;
rhud.ViewRoll = e.ViewRoll;
}
else if ( rhud )
{
StatusBar.DetachMessage(rhud);
rhud.Destroy();
StatusBar.AttachMessage(new("RedeemerHUDStatic").Init(),0,StatusBar.HUDMSGLayer_UnderHUD);
}
}
}
Class WarheadLauncher : UTWeapon replaces BFG9000
{
Actor guided;
action void A_WarheadFire()
{
Weapon weap = Weapon(invoker);
if ( !weap ) return;
if ( weap.Ammo1.Amount <= 0 ) return;
if ( !weap.DepleteAmmo(weap.bAltFire,true,1) ) return;
A_PlaySound("warhead/fire",CHAN_WEAPON);
invoker.FireEffect();
UTMainHandler.DoFlash(self,Color(128,255,128,128),1);
A_AlertMonsters();
A_QuakeEx(6,6,6,20,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:0.2);
Vector3 x, y, z;
[x, y, z] = Matrix4.GetAxes(pitch,angle,roll);
vel -= x*10;
Vector3 origin = (pos.x,pos.y,player.viewz)+10.0*x+2.0*y-2.0*z;
Actor p = Spawn("WarShell",origin);
p.angle = angle;
p.pitch = BulletSlope();
p.vel = (cos(p.angle)*cos(p.pitch),sin(p.angle)*cos(p.pitch),-sin(p.pitch))*p.speed;
p.target = self;
}
action void A_WarheadSmoke()
{
Weapon weap = Weapon(invoker);
if ( !weap ) return;
Vector3 x, y, z;
[x, y, z] = Matrix4.GetAxes(pitch,angle,roll);
vel -= x*0.2;
Vector3 origin = (pos.x,pos.y,player.viewz)+10.0*x+2.0*y-2.0*z;
int numpt = Random[Warhead](10,20);
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = (x+(FRandom[Warhead](-.8,.8),FRandom[Warhead](-.8,.8),FRandom[Warhead](-.8,.8))).unit()*FRandom[Warhead](1,2);
let s = Spawn("UTSmoke",origin);
s.vel = pvel;
s.SetShade(Color(1,1,1)*Random[Warhead](32,128));
}
}
action void A_WarheadAlt()
{
Weapon weap = Weapon(invoker);
if ( !weap ) return;
if ( weap.Ammo1.Amount <= 0 ) return;
if ( !weap.DepleteAmmo(weap.bAltFire,true,1) ) return;
A_PlaySound("warhead/fire",CHAN_WEAPON);
invoker.FireEffect();
UTMainHandler.DoFlash(self,Color(128,255,128,128),1);
A_AlertMonsters();
A_QuakeEx(6,6,6,20,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:0.2);
Vector3 x, y, z;
[x, y, z] = Matrix4.GetAxes(pitch,angle,roll);
vel -= x*10;
Vector3 origin = (pos.x,pos.y,player.viewz)+10.0*x+2.0*y-2.0*z;
Actor p = Spawn("GuidedWarShell",origin);
p.angle = angle;
p.pitch = BulletSlope();
p.vel = (cos(p.angle)*cos(p.pitch),sin(p.angle)*cos(p.pitch),-sin(p.pitch))*p.speed;
p.target = self;
p.master = invoker;
invoker.guided = p;
}
// disallow dropping while guiding
override Inventory CreateTossable( int amt )
{
if ( guided ) return null;
return Super.CreateTossable();
}
Default
{
Tag "Redeemer";
Inventory.PickupMessage "You got the Redeemer.";
Weapon.UpSound "warhead/select";
Weapon.SlotNumber 0;
Weapon.SelectionOrder 0;
Weapon.AmmoType "WarheadAmmo";
Weapon.AmmoUse 1;
Weapon.AmmoType2 "WarheadAmmo";
Weapon.AmmoUse2 1;
Weapon.AmmoGive 1;
Inventory.RespawnTics 2100;
+INVENTORY.ALWAYSPICKUP;
+WEAPON.NOAUTOFIRE;
}
States
{
Spawn:
RDMP A -1;
Stop;
RDMP B -1;
Stop;
Ready:
WARS ABCDEFGHIJKLMNO 1 A_WeaponReady(WRF_NOFIRE);
Idle:
WARI A 1
{
A_CheckReload();
A_WeaponReady();
}
Wait;
Fire:
WARF A 4 A_WarheadFire();
WARF BCDEFG 4 A_WarheadSmoke();
WARI A 5;
Goto Idle;
AltFire:
WARF A 4 A_WarheadAlt();
WARF BCDEFG 4 A_WarheadSmoke();
WARI A 1 A_JumpIf(!invoker.Guided,1);
Wait;
WARI A 30;
Goto Idle;
Select:
WARS A 1 A_Raise(int.max);
Wait;
Deselect:
WARD ABCDEFG 2;
WARD G 1 A_Lower(int.max);
Wait;
}
}