// 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'); } } 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 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'; 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<8; i++ ) A_SpawnParticle("6060FF",0,Random[Warhead](10,30),FRandom[Warhead](2,4),0,taildir.x*32,taildir.y*32,taildir.z*32,taildir.x*2+FRandom[Warhead](-.5,.5),taildir.y*2+FRandom[Warhead](-.5,.5),taildir.z*2+FRandom[Warhead](-.5,.5),accelz:0.2,fadestepf:0); return; } for ( int i=0; i<8; i++ ) A_SpawnParticle("404040",0,20,2,0,taildir.x*32,taildir.y*32,taildir.z*32,taildir.x*2+FRandom[Warhead](-.5,.5),taildir.y*2+FRandom[Warhead](-.5,.5),taildir.z*2+FRandom[Warhead](-.5,.5),accelz:0.1,sizestep:1); for ( int i=0; i<8; i++ ) A_SpawnParticle("FFA020",SPF_FULLBRIGHT,10,6,0,taildir.x*35+FRandom[Warhead](-1,1),taildir.y*35+FRandom[Warhead](-1,1),taildir.z*35+FRandom[Warhead](-1,1),taildir.x*4+FRandom[Warhead](-.25,.25),taildir.y*4+FRandom[Warhead](-.25,.25),taildir.z*4+FRandom[Warhead](-.25,.25)); } 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 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; 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; 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,100,"",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