// wasn't placeable in UT99 Class WarheadAmmo : Ammo { Default { Tag "$T_WARHEADAMMO"; Inventory.PickupMessage "$I_WARHEADAMMO"; Inventory.Amount 1; Inventory.MaxAmount 2; Ammo.BackpackAmount 0; Ammo.BackpackMaxAmount 3; Ammo.DropAmount 1; Inventory.RespawnTics 2100; +INVENTORY.IGNORESKILL; } States { Spawn: WMIS A -1; Stop; } } Class ShockWave : Actor { double shocksize, olddmgradius; double lifespan; int icount; transient ThinkerIterator t; Default { Obituary "$O_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,falloff:1200,rollIntensity:0.5); } override void Tick() { Super.Tick(); if ( isFrozen() ) return; if ( alpha <= 0 ) return; if ( !t ) t = ThinkerIterator.Create("Actor"); 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,0xf) || (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-a.radius) || (dir dot a.vel <= 0) ) { if ( !a.bDONTTHRUST ) a.vel += dir*((moscale+20)/a.mass); a.DamageMobj(self,target,int(moscale),'RedeemerDeath',DMG_THRUSTLESS); } if ( a.player ) UTMainHandler.DoFlash(a,Color(32,255,255,255),80); } 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 2; Height 4; +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 ( isFrozen() ) return; args[LIGHT_RED] = int(255*lifetime); args[LIGHT_GREEN] = int(192*lifetime); args[LIGHT_BLUE] = int(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 ( isFrozen() ) 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_REDEEMER"; Radius 2; Height 2; Speed 6; DamageType 'RedeemerDeath'; DamageFactor 1000; PROJECTILE; +FORCEXYBILLBOARD; +SKYEXPLODE; +FORCERADIUSDMG; +EXPLODEONWATER; +INTERPOLATEANGLES; } 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 ( isFrozen() ) return; if ( !bMISSILE ) return; if ( vel.length() > 0 ) { if ( waterlevel > 0 ) { vel *= 0.98; if ( vel.length() < 6 ) vel += vel.unit()*0.35; } else if ( vel.length() < 20 ) vel += vel.unit()*0.35; } } 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,180); 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,220); 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, lagroll, lagangle2, lagpitch2, lagroll2, guideangle, guidepitch, guideroll; bool justleft; override void PostBeginPlay() { Super.PostBeginPlay(); if ( target && target.player ) target.player.camera = self; justleft = true; } override void Tick() { Actor.Tick(); if ( justleft && (Distance3D(target) > (2*max(target.radius,target.height))) ) { justleft = false; bHITOWNER = true; } if ( isFrozen() ) 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.; if ( target.player.cmd.buttons&BT_RELOAD ) lagroll = 5; else if ( target.player.cmd.buttons&BT_ZOOM ) lagroll = -5; else lagroll = 0; guideangle = lagangle2*0.95+lagangle*0.05; guidepitch = lagpitch2*0.95+lagpitch*0.05; guideroll = lagroll2*0.95+lagroll*0.05; dt_Quat orient = dt_Quat.create_euler(pitch,angle,roll); dt_Quat angles = dt_Quat.create_euler(guidepitch,guideangle,guideroll); orient = orient.qmul(angles); double npitch, nangle, nroll; [npitch, nangle, nroll] = orient.to_euler(); angle = nangle; pitch = npitch; roll = nroll; vel = (vel+(cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(pitch))*0.8).unit()*11; } lagangle2 = lagangle2*0.95+lagangle*0.05; lagpitch2 = lagpitch2*0.95+lagpitch*0.05; lagroll2 = lagroll2*0.95+lagroll*0.05; } 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, LagRoll, LagRoll2; TextureID reticle1, reticle2, arrow, mark, readout; Font whfont; transient ThinkerIterator t; transient MidTracer tr; Array ta; Shape2D sshape, darrow; bool dodim; RedeemerHUD Init() { reticle1 = TexMan.CheckForTexture("GuidedX1",TexMan.Type_Any); reticle2 = TexMan.CheckForTexture("GuidedX2",TexMan.Type_Any); arrow = TexMan.CheckForTexture("AroDup",TexMan.Type_Any); mark = TexMan.CheckForTexture("Crosshr6",TexMan.Type_Any); readout = TexMan.CheckForTexture("Readout",TexMan.Type_Any); whfont = Font.GetFont('WHFONT'); sshape = new("Shape2D"); sshape.PushCoord((0,0)); sshape.PushCoord((1,0)); sshape.PushCoord((0,1)); sshape.PushCoord((1,1)); sshape.PushTriangle(0,3,1); sshape.PushTriangle(0,2,3); return self; } override bool Tick() { LagRoll = dt_Quat.Normalize180(ViewRoll-LagRoll2); LagRoll2 += dt_Quat.Normalize180(LagRoll-LagRoll2)*0.1; // shootable targetting if ( CVar.GetCVar('flak_redeemerreadout',players[consoleplayer]).GetBool() && !CVar.GetCVar('flak_redeemerreadout_perframe',players[consoleplayer]).GetBool() ) { if ( !t ) t = ThinkerIterator.Create("Actor"); if ( !tr ) tr = new("MidTracer"); 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 = dt_CoordUtil.WorldToScreen(wpos,ViewPos,ViewPitch,ViewAngle,ViewRoll,players[consoleplayer].FOV); if ( spos.z > 1.0 ) continue; TargetActor te = new("TargetActor"); te.vpos = dt_CoordUtil.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; if ( dodim ) Screen.Dim("C8 00 00",0.2,0,0,Screen.GetWidth(),Screen.GetHeight()); // shootable targetting if ( CVar.GetCVar('flak_redeemerreadout',players[consoleplayer]).GetBool() ) { if ( CVar.GetCVar('flak_redeemerreadout_perframe',players[consoleplayer]).GetBool() ) { if ( !t ) t = ThinkerIterator.Create("Actor"); if ( !tr ) tr = new("MidTracer"); 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 = dt_CoordUtil.WorldToScreen(wpos,ViewPos,ViewPitch,ViewAngle,ViewRoll,players[consoleplayer].FOV); if ( spos.z > 1.0 ) continue; TargetActor te = new("TargetActor"); te.vpos = dt_CoordUtil.ToViewport(spos); te.diststr = String.Format("%f",tdir.length()); te.diststr.Replace(".",""); ta.Push(te); } } for ( int i=0; i15); } 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; transient ui CVar deemershader; override void RenderOverlay( RenderEvent e ) { if ( !deemershader ) deemershader = CVar.GetCVar('flak_deemershader',players[consoleplayer]); 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; rhud.dodim = !deemershader.GetBool(); Shader.SetEnabled(players[consoleplayer],"RedeemerView",deemershader.GetBool()); Shader.SetUniform1f(players[consoleplayer],"RedeemerView","Timer",gametic+e.fractic); } else if ( rhud ) { Shader.SetEnabled(players[consoleplayer],"RedeemerView",false); StatusBar.DetachMessage(rhud); rhud.Destroy(); StatusBar.AttachMessage(new("RedeemerHUDStatic").Init(),0,StatusBar.HUDMSGLayer_UnderHUD); } } } Class WarheadLauncher : UTWeapon { transient CVar noswitchdeemer; Actor guided; override void Tick() { Super.Tick(); if ( !Owner || !Owner.player ) return; if ( guided ) crosshair = 99; else crosshair = 0; if ( !noswitchdeemer ) noswitchdeemer = CVar.GetCVar('flak_noswitchdeemer',Owner.player); if ( noswitchdeemer.GetBool() ) SelectionOrder = int.max; else SelectionOrder = 0; } 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); UTMainHandler.DoSwing(self,(FRandom[Warhead](0.6,1.2),FRandom[Warhead](0.2,0.5)),4,-1,3,SWING_Spring,2,5); UTMainHandler.DoSwing(self,(FRandom[Warhead](0.2,0.5),FRandom[Warhead](-0.9,-1.5)),4,-0.6,5,SWING_Spring,3,3); A_AlertMonsters(); A_QuakeEx(6,6,6,20,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:0.2); Vector3 x, y, z; [x, y, z] = dt_CoordUtil.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] = dt_CoordUtil.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