Class EClip : MiniAmmo { Default { Tag "Clip"; // "Large Bullets" in UT, but I think that's an oversight, since it's the same as the Minigun ammo Inventory.PickupMessage "You picked up a Clip."; Inventory.Amount 20; Ammo.DropAmount 5; } States { Spawn: ECLP A -1; Stop; } } Class EnforcerLight : DynamicLight { int cnt; Default { DynamicLight.Type "Point"; args 255,224,64,150; } override void Tick() { Super.Tick(); if ( !target ) { Destroy(); return; } if ( target.player ) SetOrigin(target.Vec2OffsetZ(0,0,target.player.viewz),true); else SetOrigin(target.pos,true); if ( cnt++ > 2 ) Destroy(); } } Class BulletImpact : Actor { Default { RenderStyle "Add"; Radius 0.1; Height 0; +NOGRAVITY; +NOCLIP; +DONTSPLASH; Scale 0.25; } override void PostBeginPlay() { Super.PostBeginPlay(); A_SprayDecal("Pock",-20); int numpt = int(Random[Enforcer](5,10)*scale.x*4); Vector3 x = (cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(pitch)); for ( int i=0; i 300 ) A_FadeOut(0.05); return; } heat -= 0.02; if ( heat <= 0 ) return; let s = Spawn("UTSmallSmoke",pos); s.alpha *= heat; } States { Spawn: PCAS A 1 { A_SetAngle(angle+anglevel,SPF_INTERPOLATE); A_SetPitch(pitch+pitchvel,SPF_INTERPOLATE); } Loop; Bounce: PCAS A 0 { pitchvel = FRandom[Junk](10,30)*RandomPick[Junk](-1,1); anglevel = FRandom[Junk](10,30)*RandomPick[Junk](-1,1); vel = (vel.unit()+(FRandom[Junk](-.2,.2),FRandom[Junk](-.2,.2),FRandom[Junk](-.2,.2))).unit()*vel.length(); } Goto Spawn; Death: PCAS A -1 { A_SetPitch(0); A_SetRoll(FRandom[Junk](0,360)); } Stop; } } Class Enforcer : UTWeapon { int ClipCount, SlaveClipCount; bool SlaveActive, SlaveDown, SlaveReload, SlaveAltFire; int SlaveRefire; property ClipCount : ClipCount; property SlaveClipCount : SlaveClipCount; override void PostRender( double lbottom ) { if ( !CVar.GetCVar('flak_enforcerreload').GetBool() ) return; if ( Amount > 1 ) Screen.DrawText(confont,Font.CR_GREEN,Screen.GetWidth()*0.01,lbottom-Screen.GetHeight()*0.01-confont.GetHeight()*2,String.Format("L Clip: %2d / 20\nR Clip: %2d / 20",slaveclipcount,clipcount)); else Screen.DrawText(confont,Font.CR_GREEN,Screen.GetWidth()*0.01,lbottom-Screen.GetHeight()*0.01-confont.GetHeight(),String.Format("Clip: %2d / 20",clipcount)); } override bool HandlePickup( Inventory item ) { if ( item.GetClass() == GetClass() ) { SetTag("Dual Enforcers"); return Super.HandlePickup(item); } return false; } override Inventory CreateTossable( int amt ) { Inventory inv = Super.CreateTossable(amt); if ( inv ) { SetTag("Enforcer"); inv.SetTag("Enforcer"); if ( Owner && (Owner.player.ReadyWeapon == self) ) { // delete the slave overlay PSprite psp; for ( psp = Owner.player.psprites; psp; psp = psp.next ) { if ( (psp.Caller == self) && (psp.id == 2) ) psp.Destroy(); slaveactive = false; slavedown = false; } } } return inv; } action void A_EnforcerRefire( statelabel flash = null, bool slave = false ) { Weapon weap = Weapon(invoker); if ( !weap || !player ) return; if ( slave ) { if ( CVar.GetCVar('flak_enforcerreload').GetBool() && (invoker.slaveclipcount < 5) ) A_PlaySound("enforcer/click",CHAN_6); if ( (invoker.slaveclipcount <= 0) || (weap.Ammo1.Amount <= 0) ) { invoker.slaverefire = 0; return; } bool pending = (player.PendingWeapon != WP_NOCHANGE) && (player.WeaponState & WF_REFIRESWITCHOK); if ( (player.cmd.buttons&BT_ATTACK) && !invoker.slavealtfire && !pending && (player.health > 0) ) { invoker.slaverefire++; if ( player.ReadyWeapon.CheckAmmo(Weapon.PrimaryFire,true) ) player.setpsprite(2,flash?ResolveState(flash):ResolveState("LeftHold")); } else if ( (player.cmd.buttons&BT_ALTATTACK) && invoker.slavealtfire && !pending && (player.health > 0) ) { invoker.slaverefire++; if ( player.ReadyWeapon.CheckAmmo(Weapon.AltFire,true) ) player.setpsprite(2,flash?ResolveState(flash):ResolveState("LeftAltHold")); } else { invoker.slaverefire = 0; player.ReadyWeapon.CheckAmmo(invoker.slavealtfire?Weapon.AltFire:Weapon.PrimaryFire,true); } } else { if ( CVar.GetCVar('flak_enforcerreload').GetBool() && (invoker.clipcount < 5) ) A_PlaySound("enforcer/click",CHAN_WEAPON); if ( (invoker.clipcount <= 0) || (weap.Ammo1.Amount <= 0) ) { A_ClearRefire(); return; } A_Refire(flash); } } action void A_LeftWeaponReady() { Weapon weap = Weapon(invoker); if ( !weap || !player ) return; if ( player.cmd.buttons&BT_ATTACK && !player.ReadyWeapon.bAltFire ) { invoker.slaverefire = 0; invoker.slavealtfire = false; player.setpsprite(2,ResolveState("LeftFire")); } else if ( player.cmd.buttons&BT_ALTATTACK && player.ReadyWeapon.bAltFire ) { invoker.slaverefire = 0; invoker.slavealtfire = true; player.setpsprite(2,ResolveState("LeftAltFire")); } } action void A_EnforcerFire( bool alt = false, bool slave = false ) { Weapon weap = Weapon(invoker); if ( !weap ) return; if ( weap.Ammo1.Amount <= 0 ) return; if ( !weap.DepleteAmmo(weap.bAltFire,true,1) ) return; if ( slave ) { invoker.slaveclipcount--; if ( !CVar.GetCVar('flak_enforcerreload').GetBool() && (invoker.slaveclipcount <=0) ) invoker.slaveclipcount = (weap.Ammo1.Amount>0)?Min(20,weap.Ammo1.Amount):20; } else { invoker.clipcount--; if ( !CVar.GetCVar('flak_enforcerreload').GetBool() && (invoker.clipcount <=0) ) invoker.clipcount = (weap.Ammo1.Amount>0)?Min(20,weap.Ammo1.Amount):20; } invoker.FireEffect(); UTMainHandler.DoFlash(self,Color(32,255,128,0),1); A_PlaySound("enforcer/shoot",slave?CHAN_6:CHAN_WEAPON); A_AlertMonsters(); A_QuakeEx(2,2,2,4,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:0.08); if ( slave ) { if ( alt ) A_Overlay(-3,"LeftAltMuzzleFlash"); else A_Overlay(-3,"LeftMuzzleFlash"); A_OverlayFlags(-3,PSPF_RENDERSTYLE|PSPF_FORCESTYLE,true); A_OverlayRenderstyle(-3,STYLE_Add); UTMainHandler.DoSwing(self,(FRandom[Enforcer](0.5,0.2),FRandom[Enforcer](-0.3,0.2)),2,0,1,SWING_Spring,0,2); } else { if ( alt ) A_Overlay(-2,"AltMuzzleFlash"); else A_Overlay(-2,"MuzzleFlash"); A_OverlayFlags(-2,PSPF_RENDERSTYLE|PSPF_FORCESTYLE,true); A_OverlayRenderstyle(-2,STYLE_Add); UTMainHandler.DoSwing(self,(FRandom[Enforcer](-0.2,-0.5),FRandom[Enforcer](-0.3,0.2)),2,0,1,SWING_Spring,0,2); } Vector3 x, y, z, x2, y2, z2; [x, y, z] = Matrix4.GetAxes(pitch,angle,roll); Vector3 origin = pos+(0,0,player.viewheight)+10.0*x; int ydir = slave?-1:1; if ( alt ) origin = origin-z*3.0+ydir*y*1.0; else origin = origin-z*1.0+ydir*y*4.0; double a = FRandom[Enforcer](0,360), s = FRandom[Enforcer](0,alt?0.08:0.004); if ( invoker.SlaveActive ) s *= 3; [x2, y2, z2] = Matrix4.GetAxes(BulletSlope(),angle,roll); Vector3 dir = (x2+y2*cos(a)*s+z2*sin(a)*s).unit(); FLineTraceData d; LineTrace(atan2(dir.y,dir.x),10000,asin(-dir.z),TRF_ABSPOSITION,origin.z,origin.x,origin.y,d); if ( d.HitType == TRACE_HitActor ) { int dmg = Random[Enforcer](12,17); dmg = d.HitActor.DamageMobj(invoker,self,dmg,'shot',DMG_USEANGLE|DMG_THRUSTLESS,atan2(d.HitDir.y,d.HitDir.x)); double mm = 3000; if ( FRandom[Enforcer](0,1) < 0.2 ) mm *= 5; UTMainHandler.DoKnockback(d.HitActor,d.HitDir,mm); if ( d.HitActor.bNOBLOOD ) { let p = Spawn("BulletImpact",d.HitLocation); p.angle = atan2(d.HitDir.y,d.HitDir.x)+180; p.pitch = asin(d.HitDir.z); } else { d.HitActor.TraceBleed(dmg,self); d.HitActor.SpawnBlood(d.HitLocation,atan2(d.HitDir.y,d.HitDir.x)+180,dmg); } } else if ( d.HitType != TRACE_HitNone ) { Vector3 hitnormal = -d.HitDir; if ( d.HitType == TRACE_HitFloor ) hitnormal = d.HitSector.floorplane.Normal; else if ( d.HitType == TRACE_HitCeiling ) 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("BulletImpact",d.HitLocation+hitnormal*0.01); p.angle = atan2(hitnormal.y,hitnormal.x); p.pitch = asin(-hitnormal.z); if ( d.HitLine ) d.HitLine.RemoteActivate(self,d.LineSide,SPAC_Impact,d.HitLocation); } for ( int i=0; i<3; i++ ) { let s = Spawn("UTViewSmoke",origin); if ( alt ) UTViewSmoke(s).ofs = (10,ydir,-3); else UTViewSmoke(s).ofs = (10,4*ydir,-1); s.target = self; s.alpha *= 0.5; } origin += x*8.0+ydir*y*6.0-z*2.0; let c = Spawn("UTCasing",origin); c.vel = x*FRandom[Junk](-2,2)+y*ydir*FRandom[Junk](3,6)+z*FRandom[Junk](3,5); } override String GetObituary( Actor victim, Actor inflictor, Name mod, bool playerattack ) { if ( Amount > 1 ) return "%k riddled %o full of holes with the Dual Enforcers."; return "%k riddled %o full of holes with the Enforcer."; } override void Travelled() { Super.Travelled(); slaveactive = false; } override void OwnerDied() { Super.OwnerDied(); slaverefire = 0; } Default { Tag "Enforcer"; Inventory.PickupMessage "You picked up another Enforcer!"; Inventory.MaxAmount 2; Inventory.InterHubAmount 2; Weapon.UpSound "enforcer/select"; Weapon.SlotNumber 2; Weapon.SelectionOrder 8; Weapon.AmmoType "MiniAmmo"; Weapon.AmmoUse 1; Weapon.AmmoType2 "MiniAmmo"; Weapon.AmmoUse2 1; Weapon.AmmoGive 30; Weapon.Kickback 180; UTWeapon.DropAmmo 10; Enforcer.ClipCount 20; Enforcer.SlaveClipCount 20; } States { Spawn: ENFP A -1; Stop; ENFP B -1; Stop; Select: ENFS A 1 A_Raise(int.max); Ready: ENFS A 0 { invoker.slavedown = false; if ( !invoker.slaveactive && (CountInv("Enforcer") > 1) ) A_Overlay(2,"LeftReady"); } ENFS ABCDEFGHIJKLMNOPQRSTUVWXYZ 1 A_WeaponReady(WRF_NOFIRE); Idle: ENFI A 0 A_Overlay(-9999,"Dummy"); ENFI AB 30; ENFI A 0 A_Jump(50,"Twiddle"); Goto Idle+1; LeftReady: 2NFS A 0 { A_PlaySound("enforcer/select",CHAN_6); invoker.slaveactive = true; } 2NFS ABCDEFGHIJKLMNOPQRSTUVWXYZ 1 A_JumpIf(invoker.slavedown,"LeftDeselect"); LeftIdle: 2NFI A 0 A_Overlay(-9998,"LeftDummy"); 2NFI AB 30; 2NFI A 0 A_Jump(50,"LeftTwiddle"); Goto LeftIdle+1; Twiddle: ENFT ABCDEFGHIJKLMNOPQRSTUVWXY 2; Goto Idle+1; LeftTwiddle: 2NFT ABCDEFGHIJKLMNOPQRSTUVWXY 2; Goto LeftIdle+1; Dummy: TNT1 A 1 { if ( (invoker.clipcount <= 0) && (invoker.Ammo1.Amount > 0) ) A_Overlay(PSP_WEAPON,"Reload"); else if ( CVar.GetCVar('flak_enforcerreload').GetBool() && ((invoker.clipcount < min(20,invoker.Ammo1.Amount)) || (invoker.slaveclipcount < min(20,invoker.Ammo1.Amount))) ) A_WeaponReady(WRF_ALLOWRELOAD); else A_WeaponReady(); if ( !invoker.slaveactive && (CountInv("Enforcer") > 1) ) A_Overlay(2,"LeftReady"); } Wait; LeftDummy: TNT1 A 1 { if ( health <= 0 ) { invoker.slaveactive = false; A_Overlay(2,"LeftDeselect"); } else if ( CVar.GetCVar('flak_enforcerreload').GetBool() && (invoker.slavereload || (invoker.slaveclipcount < 0)) ) A_Overlay(2,"LeftReload"); else if ( invoker.slavedown ) A_Overlay(2,"LeftDeselect"); else A_LeftWeaponReady(); } Wait; Fire: ENFF A 0 A_Overlay(-9999,"Null"); Hold: ENFF A 0 A_EnforcerFire(); ENFF ABCDEFGHIJ 1; ENFF J 2; ENFF J 0 A_EnforcerRefire(); ENFF J 2; ENFI A 0; Goto Idle; LeftFire: 2NFI A 0 A_Overlay(-9998,"Null"); 2NFI A 5; 2NFI A 0 A_EnforcerRefire(1,true); Goto LeftIdle; LeftHold: 2NFF A 0 A_EnforcerFire(false,true); 2NFF ABCDEFGHIJ 1; 2NFF J 2; 2NFF J 0 A_EnforcerRefire("LeftHold",true); 2NFF J 2; 2NFI A 0; Goto LeftIdle; AltFire: ENFA A 0 A_Overlay(-9999,"Null"); ENFA ABCDEF 1; AltHold: ENFA G 0 A_EnforcerFire(true); ENFA GHIJKLMN 1; ENFA N 1; ENFA G 0 A_EnforcerRefire(); ENFA OPQRSTU 1; Goto Idle; LeftAltFire: 2NFI A 0 A_Overlay(-9998,"Null"); 2NFI A 5; 2NFI A 0 A_EnforcerRefire(1,true); Goto LeftIdle; 2NFA ABCDEF 1; LeftAltHold: 2NFA G 0 A_EnforcerFire(true,true); 2NFA GHIJKLMN 1; 2NFA N 1; 2NFA G 0 A_EnforcerRefire("LeftAltHold",true); 2NFA OPQRSTU 1; Goto LeftIdle; Reload: ENFR A 0 { invoker.slavereload = ((player.cmd.buttons&BT_RELOAD)&&(invoker.slaveclipcount < min(20,invoker.Ammo1.Amount)))||(invoker.slaveclipcount <= 0); return A_JumpIf(invoker.clipcount>=min(20,invoker.Ammo1.Amount),"Idle"); } ENFR A 0 { invoker.clipcount = Min(20,invoker.Ammo1.Amount); A_Overlay(-9999,"Null"); A_PlaySound("enforcer/click",CHAN_WEAPON); } ENFR ABCDEFGHIJKLMNOPQRSTUVWXYZ 1; ENR2 AB 1; ENR2 B 30 A_PlaySound("enforcer/reload",CHAN_WEAPON); ENFS A 0 A_PlaySound("enforcer/select",CHAN_WEAPON); Goto Ready; LeftReload: 2NFR A 0 { invoker.slaveclipcount = Min(20,invoker.Ammo1.Amount); invoker.slavereload = false; A_Overlay(-9998,"Null"); A_PlaySound("enforcer/click",CHAN_6); } 2NFR ABCDEFGHIJKLMNOPQRSTUVWXYZ 1; 2NR2 AB 1; 2NR2 B 30 A_PlaySound("enforcer/reload",CHAN_6); 2NFS A 0 A_PlaySound("enforcer/select",CHAN_6); Goto LeftReady; Deselect: ENFI A 1 { invoker.slavedown = true; } ENFD A 0 A_Overlay(-9999,"Null"); ENFD A 0 A_JumpIf(invoker.slaveactive,"Deselect"); ENFD ABDEGHJK 1; ENFD L 1 A_Lower(int.max); Wait; LeftDeselect: 2NFD A 0 { A_Overlay(-9998,"Null"); invoker.slaveactive = false; } 2NFD ABDEGHJK 1; 2NFD L 0; Stop; MuzzleFlash: EMUZ A 2 Bright { let l = Spawn("EnforcerLight",pos); l.target = self; } Stop; AltMuzzleFlash: EMUZ B 2 Bright { let l = Spawn("EnforcerLight",pos); l.target = self; } Stop; LeftMuzzleFlash: EMUZ C 2 Bright { let l = Spawn("EnforcerLight",pos); l.target = self; } Stop; LeftAltMuzzleFlash: EMUZ D 2 Bright { let l = Spawn("EnforcerLight",pos); l.target = self; } Stop; } }