Class UTRocketAmmo : Ammo { Default { Tag "$T_ROCKETAMMO"; Inventory.PickupMessage "$I_ROCKETAMMO"; Inventory.Amount 12; Inventory.MaxAmount 48; Ammo.BackpackAmount 6; Ammo.BackpackMaxAmount 96; Ammo.DropAmount 3; } States { Spawn: RPAK A -1; Stop; } } // There was no single rocket ammo in UT, so this one is also just improvised like the Redeemer ammo pickup Class UTRocketAmmo2 : UTRocketAmmo { Default { Tag "$T_ROCKETAMMO2"; Inventory.PickupMessage "$I_ROCKETAMMO2"; Inventory.Amount 1; Ammo.DropAmount 1; +INVENTORY.IGNORESKILL; } States { Spawn: RCKT A -1; Stop; } } Class RocketLight : DynamicLight { Default { DynamicLight.Type "Point"; Args 255,224,128,32; } override void Tick() { Super.Tick(); if ( !target ) { Destroy(); return; } SetOrigin(target.pos,true); } } Class UTRocketTrail : Actor { Default { RenderStyle "Add"; Radius 0.1; Height 0; +NOBLOCKMAP; +NOGRAVITY; +DONTSPLASH; +FORCEXYBILLBOARD; +NOTELEPORT; Scale 0.7; } override void PostBeginPlay() { Super.PostBeginPlay(); let l = Spawn("RocketLight",pos); l.target = self; } override void Tick() { Super.Tick(); if ( !target || target.InStateSequence(target.CurState,target.FindState("Death")) ) { Destroy(); return; } SetOrigin(target.pos,true); } States { Spawn: RFLA A -1 Bright; Stop; } } Class RocketExplLight : PaletteLight { Default { Args 0,0,0,120; ReactionTime 20; } } Class UTRocket : Actor { Vector3 InitialDir, Acceleration; int ticcnt; Default { Obituary "$O_EIGHTBALL"; DamageType 'RocketDeath'; Radius 2; Height 2; Speed 15; PROJECTILE; +SKYEXPLODE; +EXPLODEONWATER; +SEEKERMISSILE; +FORCERADIUSDMG; +NODAMAGETHRUST; +INTERPOLATEANGLES; } override void PostBeginPlay() { Super.PostBeginPlay(); let l = Spawn("UTRocketTrail",pos); l.target = self; A_PlaySound("utrl/fly",CHAN_VOICE,1.0,true,2.5,pitch:1.5625); if ( tracer ) vel *= 0.9; Acceleration = vel.unit()*50; } action void A_RocketExplode( int dmg, int rad ) { bFORCEXYBILLBOARD = true; A_SetRenderStyle(1.0,STYLE_Add); A_SprayDecal("RocketBlast",50); A_NoGravity(); A_SetScale(0.75); UTMainHandler.DoBlast(self,rad,80000); A_Explode(dmg,rad); A_QuakeEx(3,3,3,8,0,rad+50,"",QF_RELATIVE|QF_SCALEDOWN,falloff:rad,rollIntensity:0.2); A_PlaySound("utrl/explode",CHAN_VOICE); A_AlertMonsters(); Spawn("RocketExplLight",pos); int numpt = Random[Eightball](15,30); for ( int i=0; i 0 ) { double MagnitudeVel = Vel.length(); SeekingDir = (SeekingDir*0.5*MagnitudeVel+Vel).unit(); Vel = MagnitudeVel * SeekingDir; invoker.Acceleration = 25 * SeekingDir; } } } States { Spawn: RCKT B 1 { roll += 30; if ( invoker.ticcnt++ > 3 ) { invoker.ticcnt = 0; A_RocketSeek(); } vel += invoker.Acceleration/TICRATE; if ( vel.length() > 30. ) vel = Vel.unit()*30.; Vector3 dir = vel.unit(); if ( waterlevel <= 0 ) vel = dir*min(vel.length()+1,24); angle = atan2(dir.y,dir.x); pitch = asin(-dir.z); for ( int i=0; i<3; i++ ) { let s = Spawn("UTSmoke",pos); s.vel = (FRandom[Eightball](-0.2,0.2),FRandom[Eightball](-0.2,0.2),FRandom[Eightball](-0.2,0.2)); s.vel += vel*0.1; } } Wait; Death: TNT1 A 0 A_RocketExplode(100,160); SSMX ABCDEFGHIJ 2 Bright; Stop; } } Class UTGrenade : UTRocket { double rollvel, pitchvel, anglevel; Default { DamageType 'GrenadeDeath'; -NOGRAVITY; +USEBOUNCESTATE; -EXPLODEONWATER; +CANBOUNCEWATER; +NOEXPLODEFLOOR; +DONTBOUNCEONSHOOTABLES; BounceType "Hexen"; WallBounceFactor 0.75; BounceFactor 0.75; ReactionTime 85; Speed 15; Gravity 0.35; } override void PostBeginPlay() { Actor.PostBeginPlay(); rollvel = FRandom[Eightball](-8,8); pitchvel = FRandom[Eightball](-8,8); anglevel = FRandom[Eightball](-8,8); ReactionTime += Random[Eightball](0,20); } States { Spawn: RCKT A 1 { if ( !bNOGRAVITY ) { angle += anglevel; pitch += pitchvel; roll += rollvel; } let s = Spawn("UTSmoke",pos); s.scale *= 2.0; s.alpha *= 0.6; s.vel = (FRandom[Eightball](-0.1,0.1),FRandom[Eightball](-0.1,0.1),FRandom[Eightball](-0.1,0.3)); s.vel += vel*0.05; s.SetShade("000000"); A_Countdown(); } Wait; Bounce: RCKT A 0 { bHITOWNER = true; A_PlaySound("utrl/bounce"); rollvel = FRandom[Eightball](-16,16); pitchvel = FRandom[Eightball](-16,16); anglevel = FRandom[Eightball](-16,16); if ( vel.z > 10 ) vel.z = 0.5*(10+vel.z); else if ( BlockingFloor && (vel.xy.length() < 0.5) ) { vel *= 0; bNOGRAVITY = true; bMOVEWITHSECTOR = true; ClearBounce(); } } Goto Spawn; Death: TNT1 A 0 A_RocketExplode(120,140); Goto Super::Death+1; } } Class UTRocketLauncher : UTWeapon { bool LockedOn; Actor LockedTarget; TextureID lockontex; int locktics; bool bSingleRocket; override void PostBeginPlay() { Super.PostBeginPlay(); lockontex = TexMan.CheckForTexture("Crosshr6",TexMan.Type_Any); } override void PreRender( double lbottom ) { if ( LockedTarget ) Screen.DrawTexture(lockontex,false,Screen.GetWidth()*0.5,Screen.GetHeight()*0.5); } override void Tick() { Super.Tick(); if ( !Owner ) return; if ( Owner.Health <= 0 ) { LockedTarget = null; LockedOn = false; crosshair = 0; return; } if ( LockedOn && (!LockedTarget || (LockedTarget.Health <= 0) || !LockedTarget.bIsMonster || LockedTarget.bKilled || LockedTarget.bCorpse || !LockedTarget.bShootable || (Owner.player.ReadyWeapon != self)) ) { LockedTarget = null; LockedOn = false; if ( Owner.player.ReadyWeapon == self ) Owner.A_PlaySound("utrl/seeklost",CHAN_WEAPON); } if ( LockedTarget ) crosshair = 99; else crosshair = 0; } // consumes 1 ammo action void A_LoadRocket( bool checktarget = true ) { Weapon weap = Weapon(invoker); if ( !weap ) return; if ( weap.Ammo1.Amount <= 0 ) return; if ( !weap.DepleteAmmo(weap.bAltFire,true,1) ) return; if ( weap.bAltFire ) { invoker.LockedTarget = null; invoker.LockedOn = false; } if ( checktarget && !weap.bAltFire ) A_CheckTarget(); invoker.special1++; } // refire that is ignored if there's no ammo action void A_LoadedRefire( statelabel flash = null ) { Weapon weap = Weapon(invoker); if ( !weap ) return; if ( weap.Ammo1.Amount <= 0 ) return; A_Refire(flash); } // fire all the rockets (or grenades) action void A_FireRockets( int num ) { Weapon weap = Weapon(invoker); if ( !weap ) return; invoker.special1 = 0; if ( weap.bAltFire ) A_PlaySound("utrl/altfire",CHAN_WEAPON); else A_PlaySound("utrl/fire",CHAN_WEAPON); invoker.FireEffect(); UTMainHandler.DoFlash(self,Color(64,255,0,0),1); if ( self is 'UTPlayer' ) UTPlayer(self).PlayAttacking3(); A_AlertMonsters(); A_QuakeEx(2+num,2+num,2+num,6+num,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:0.1+num*0.05); for ( int i=0; i1)?12:0); Vector3 dir = (x2+cos(a)*y2*s*0.004+sin(a)*z2*s*0.004).unit(); p = Spawn("UTGrenade",level.Vec3Offset(origin,cos(a)*y*s+sin(a)*z*s)); p.vel = x*(vel dot x)*0.4 + dir*p.speed*FRandom[Eightball](1.0,1.2); p.vel.z += 3.5; p.target = self; } } else if ( num <= 1 ) { // single rocket p = Spawn("UTRocket",level.Vec3Offset(origin,cos(a)*y*s+sin(a)*z*s)); p.vel = x2*p.speed; p.target = self; p.tracer = invoker.LockedTarget; } else if ( player.cmd.buttons&BT_ALTATTACK ) { // rockets ("tight wad" as UT calls it) double step = 360/num; a = 90; s = (num>1)?6:0; for ( int i=0; i 2000 ) continue; if ( reldir.unit() dot viewdir < 0.99 ) continue; if ( reldist < closest ) { closest = reldist; invoker.LockedTarget = a; } } if ( invoker.LockedTarget ) A_PlaySound("utrl/seeklock",CHAN_WEAPON); else if ( invoker.LockedOn ) A_PlaySound("utrl/seeklost",CHAN_WEAPON); if ( invoker.LockedTarget ) invoker.LockedOn = true; } action bool FireCheck() { let weap = Weapon(invoker); if ( !weap ) return false; if ( weap.bAltFire ) return !(player.cmd.buttons&BT_ALTATTACK); else return !(player.cmd.buttons&BT_ATTACK); } Default { Tag "$T_EIGHTBALL"; Inventory.PickupMessage "$I_EIGHTBALL"; Weapon.UpSound "utrl/select"; Weapon.SlotNumber 9; Weapon.SelectionOrder 1; Weapon.AmmoType "UTRocketAmmo"; Weapon.AmmoUse 1; Weapon.AmmoType2 "UTRocketAmmo"; Weapon.AmmoUse2 1; Weapon.AmmoGive 6; UTWeapon.DropAmmo 3; } States { Spawn: EBLP A -1; Stop; EBLP B -1; Stop; Select: EBLS A 1 A_Raise(int.max); Wait; Ready: EBLS ABCDEFGHIJKLMNOPQRST 1 A_WeaponReady(WRF_NOFIRE); Idle: EBLI A 1 { invoker.locktics = 0; invoker.special1 = 0; A_CheckReload(); A_WeaponReady(WRF_ALLOWRELOAD); } EBLI A 1 { A_CheckReload(); A_WeaponReady(WRF_ALLOWRELOAD); invoker.locktics++; if ( invoker.locktics > 42 ) { invoker.locktics = 0; A_CheckTarget(); } } Wait; Reload: EBLI A 5 { A_PlaySound("utrl/load",CHAN_6,0.3); if ( invoker.bSingleRocket = !invoker.bSingleRocket ) A_Print(StringTable.Localize("$M_SINGLEROCKETON")); else A_Print(StringTable.Localize("$M_SINGLEROCKETOFF")); } Goto Idle; Fire: AltFire: // one is loaded already EBLI A 1 A_LoadRocket(false); EBLI A 2 A_JumpIf((!invoker.bAltFire&&invoker.bSingleRocket)||(invoker.Ammo1.Amount<=0)||FireCheck(),"FireOne"); EBLI A 0; // no tween // load two EBR1 A 0 A_PlaySound("utrl/rotate",CHAN_6,0.1); EBR1 ABCDEFG 2 A_JumpIf(FireCheck(),"FireOne"); EBL2 A 0 A_PlaySound("utrl/load",CHAN_6); EBL2 ABCDEFG 3 A_JumpIf(FireCheck(),"FireOne"); EBR2 A 0 { A_LoadRocket(); return A_JumpIf((invoker.Ammo1.Amount<=0)||FireCheck(),"FireTwo"); } // load three EBR2 A 0 A_PlaySound("utrl/rotate",CHAN_6,0.1); EBR2 ABCDEFG 2 A_JumpIf(FireCheck(),"FireTwo"); EBL3 A 0 A_PlaySound("utrl/load",CHAN_6); EBL3 ABCDEFG 3 A_JumpIf(FireCheck(),"FireTwo"); EBR3 A 0 { A_LoadRocket(); return A_JumpIf((invoker.Ammo1.Amount<=0)||FireCheck(),"FireThree"); } // load four EBR3 A 0 A_PlaySound("utrl/rotate",CHAN_6,0.1); EBR3 ABCDEFG 2 A_JumpIf(FireCheck(),"FireThree"); EBL4 A 0 A_PlaySound("utrl/load",CHAN_6); EBL4 ABCDEFG 3 A_JumpIf(FireCheck(),"FireThree"); EBR4 A 0 { A_LoadRocket(); return A_JumpIf((invoker.Ammo1.Amount<=0)||FireCheck(),"FireFour"); } // load five EBR4 A 0 A_PlaySound("utrl/rotate",CHAN_6,0.1); EBR4 ABCDEFG 2 A_JumpIf(FireCheck(),"FireFour"); EBL5 A 0 A_PlaySound("utrl/load",CHAN_6); EBL5 ABCDEFG 3 A_JumpIf(FireCheck(),"FireFour"); EBR5 A 0 { A_LoadRocket(); return A_JumpIf((invoker.Ammo1.Amount<=0)||FireCheck(),"FireFive"); } // load six EBR5 A 0 A_PlaySound("utrl/rotate",CHAN_6,0.1); EBR5 ABCDEFG 2 A_JumpIf(FireCheck(),"FireFive"); EBL6 A 0 A_PlaySound("utrl/load",CHAN_6); EBL6 ABCDEF 3 A_JumpIf(FireCheck(),"FireFive"); EBL6 F 0 A_LoadRocket(); Goto FireSix; FireOne: EBF1 A 0 A_FireRockets(1); EBF1 ABCDEFGH 2; EBL1 A 0 A_CheckReload(); EBL1 A 2 A_PlaySound("utrl/load",CHAN_6); EBL1 BCDEFG 2; EBLI A 0; Goto Idle; FireTwo: EBF2 A 0 A_FireRockets(2); EBF2 ABCDEFGHIJK 2; EBL1 A 0 A_CheckReload(); EBL1 A 2 A_PlaySound("utrl/load",CHAN_6); EBL1 BCDEFG 2; EBLI A 0; Goto Idle; FireThree: EBF3 A 0 A_FireRockets(3); EBF3 ABCDEFGHIJ 2; EBL1 A 0 A_CheckReload(); EBL1 A 2 A_PlaySound("utrl/load",CHAN_6); EBL1 BCDEFG 2; EBLI A 0; Goto Idle; FireFour: EBF4 A 0 A_FireRockets(4); EBF4 ABCDEFGHIJK 2; EBL1 A 0 A_CheckReload(); EBL1 A 2 A_PlaySound("utrl/load",CHAN_6); EBL1 BCDEFG 2; EBLI A 0; Goto Idle; FireFive: EBF5 A 0 A_FireRockets(5); EBF5 ABCDEFGHIJKLM 2; EBL1 A 0 A_CheckReload(); EBL1 A 2 A_PlaySound("utrl/load",CHAN_6); EBL1 BCDEFG 2; EBLI A 0; Goto Idle; FireSix: EBF6 A 0 A_FireRockets(6); EBF6 ABCDEFGHIJKLMNOP 2; EBL1 A 0 A_CheckReload(); EBL1 A 2 A_PlaySound("utrl/load",CHAN_6); EBL1 BCDEFG 2; EBLI A 0; Goto Idle; Deselect: EBLD ABCDEFGHIJK 1; EBLD K 1 A_Lower(int.max); Wait; } }