Class MiniAmmo : Ammo { Default { Tag "$T_MINIAMMO"; Inventory.PickupMessage ""; Inventory.Amount 50; Inventory.MaxAmount 200; Ammo.BackpackAmount 25; Ammo.BackpackMaxAmount 400; Ammo.DropAmount 20; } override String PickupMessage() { if ( PickupMsg.Length() > 0 ) return Super.PickupMessage(); return String.Format("%s%d%s",StringTable.Localize("$I_MINIAMMOL"),Amount,StringTable.Localize("$I_MINIAMMOR")); } States { Spawn: MAMO A -1; Stop; } } Class MinigunLight : EnforcerLight { override void PostBeginPlay() { Super.PostBeginPlay(); args[LIGHT_INTENSITY] = Random[Minigun](120,180); } } Class MinigunTracer : Actor { Vector3 dest; Default { RenderStyle "Add"; Radius 0.1; Height 0; +NOCLIP; +NOGRAVITY; +DONTSPLASH; +INTERPOLATEANGLES; +NOTELEPORT; } override void Tick() { Super.Tick(); if ( isFrozen() ) return; Vector3 dir = level.Vec3Diff(pos,dest); if ( dir.length() < 160 ) { Destroy(); return; } dir = dir.unit(); SetOrigin(level.Vec3Offset(pos,dir*160),true); angle = atan2(dir.y,dir.x); pitch = asin(-dir.z); roll += 60; } States { Spawn: TRAC A -1 Bright; Stop; } } Class Minigun : UTWeapon { int bcnt, tcnt; ui Canvas AmmoLed; ui TextureID LedBase, LedFont; override void RenderOverlay( RenderEvent e ) { Vector2 fnt[] = { (0,1), (21,1), (42,1), (63,1), (84,1), (105,1), (126,1), (147,1), (168,1), (189,1) }; if ( !AmmoLed ) AmmoLed = TexMan.GetCanvas("MiniALed"); if ( !LedBase ) LedBase = TexMan.CheckForTexture("models/miniammoledbase.png",TexMan.Type_Any); if ( !LedFont ) LedFont = TexMan.CheckForTexture("models/LEDFont2.png",TexMan.Type_Any); AmmoLed.DrawTexture(LedBase,false,0,0); int dg3 = (Ammo1.Amount/100)%10; int dg2 = (Ammo1.Amount/10)%10; int dg1 = Ammo1.Amount%10; AmmoLed.DrawTexture(LedFont,false,2,14,DTA_SrcX,fnt[dg3].x,DTA_SrcY,fnt[dg3].y,DTA_SrcWidth,20,DTA_SrcHeight,35,DTA_DestWidth,20,DTA_DestHeight,35,DTA_Color,0xFFFF0000); AmmoLed.DrawTexture(LedFont,false,22,14,DTA_SrcX,fnt[dg2].x,DTA_SrcY,fnt[dg2].y,DTA_SrcWidth,20,DTA_SrcHeight,35,DTA_DestWidth,20,DTA_DestHeight,35,DTA_Color,0xFFFF0000); AmmoLed.DrawTexture(LedFont,false,42,14,DTA_SrcX,fnt[dg1].x,DTA_SrcY,fnt[dg1].y,DTA_SrcWidth,20,DTA_SrcHeight,35,DTA_DestWidth,20,DTA_DestHeight,35,DTA_Color,0xFFFF0000); } action void A_FireBullet( bool alt = false ) { Weapon weap = Weapon(invoker); if ( !weap ) return; if ( weap.Ammo1.Amount <= 0 ) return; A_Overlay(-2,"MuzzleFlash",true); A_OverlayFlags(-2,PSPF_RENDERSTYLE|PSPF_FORCESTYLE,true); A_OverlayRenderstyle(-2,STYLE_Add); invoker.bcnt++; if ( (alt && (invoker.bcnt < 2)) || (!alt && (invoker.bcnt < 4)) ) return; invoker.bcnt = 0; if ( !weap.DepleteAmmo(weap.bAltFire,true,1) ) return; invoker.FireEffect(); UTMainHandler.DoFlash(self,Color(32,255,255,0),1); A_AlertMonsters(); if ( alt ) A_QuakeEx(2,2,2,8,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:0.12); else A_QuakeEx(1,1,1,8,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:0.08); let l = Spawn("MinigunLight",pos); l.target = self; if ( !alt ) MinigunLight(l).cnt--; Vector3 x, y, z, x2, y2, z2; [x, y, z] = dt_CoordUtil.GetAxes(pitch,angle,roll); Vector3 origin = level.Vec3Offset(Vec2OffsetZ(0,0,player.viewz),10*x+y*2-z*2); double a = FRandom[Minigun](0,360), s = FRandom[Minigun](0,alt?0.05:0.02); [x2, y2, z2] = dt_CoordUtil.GetAxes(BulletSlope(),angle,roll); Vector3 dir = dt_Utility.ConeSpread(x2,y2,z2,a,s); FLineTraceData d; LineTrace(atan2(dir.y,dir.x),10000,asin(-dir.z),TRF_ABSPOSITION,origin.z,origin.x,origin.y,d); UTBulletTrail.DoTrail(self,origin,dir,10000,alt?5:3); if ( d.HitType == TRACE_HitActor ) { int dmg = Random[Minigun](9,18); // fun fact: the Minigun is one of the few weapons that has actual RNG damage in UT dmg = d.HitActor.DamageMobj(invoker,self,dmg,'shot',DMG_USEANGLE|DMG_THRUSTLESS,atan2(d.HitDir.y,d.HitDir.x)); double mm = 500; if ( FRandom[Minigun](0,1) < 0.2 ) mm *= 2.5; UTMainHandler.DoKnockback(d.HitActor,d.HitDir,dmg*mm); if ( d.HitActor.bNOBLOOD ) { let p = Spawn("BulletImpact",d.HitLocation); p.scale *= 0.75; 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 ) { if ( d.Hit3DFloor ) hitnormal = -d.Hit3DFloor.top.Normal; else hitnormal = d.HitSector.floorplane.Normal; } else if ( d.HitType == TRACE_HitCeiling ) { if ( d.Hit3DFloor ) hitnormal = -d.Hit3DFloor.bottom.Normal; else 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.scale *= 0.75; 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); } if ( !Random[Minigun](0,1) ) { let t = Spawn("MinigunTracer",level.Vec3Offset(origin,x*20)); t.angle = atan2(dir.y,dir.x); t.pitch = asin(-dir.z); MinigunTracer(t).dest = d.HitLocation; } for ( int i=0; i<2; i++ ) { let s = Spawn("UTViewSmoke",origin); UTViewSmoke(s).ofs = (10,2,-2); s.scale *= 1.5; s.alpha *= 0.6; UTViewSmoke(s).vvel += (FRandom[Minigun](0.2,0.8),FRandom[Minigun](-0.3,0.3),FRandom[Minigun](-0.3,0.3)); s.target = self; } origin = level.Vec3Offset(origin,x*8+y*5-z*5); let c = Spawn("UTCasing",origin); c.angle = angle; c.pitch = pitch; c.vel = x*FRandom[Junk](-1.5,1.5)+y*FRandom[Junk](2,4)+z*FRandom[Junk](2,3); c.Scale *= 0.5; } action void A_MinigunRefire() { Weapon weap = Weapon(invoker); if ( !weap || !player ) return; if ( weap.Ammo1.Amount <= 0 ) { A_ClearRefire(); return; } weap.bAltFire = !!(player.cmd.buttons&BT_ALTATTACK); invoker.bcnt = 5; A_Refire("Hold"); } Default { Tag "$T_MINIGUN"; Obituary "$O_MINIGUN"; Inventory.PickupMessage "$I_MINIGUN"; Weapon.UpSound "minigun/select"; Weapon.SlotNumber 7; Weapon.SelectionOrder 3; Weapon.AmmoType "MiniAmmo"; Weapon.AmmoUse 1; Weapon.AmmoType2 "MiniAmmo"; Weapon.AmmoUse2 1; Weapon.AmmoGive 50; Weapon.Kickback 180; UTWeapon.DropAmmo 20; UTWeapon.NameColor "FF FF 00"; } States { Spawn: MGNP A -1; Stop; MGNP B -1; Stop; Select: MGNS A 1 A_Raise(int.max); Wait; Ready: MGNS ABCDEFGHIJKLMNOPQRST 1 A_WeaponReady(WRF_NOFIRE); Idle: MGNI A 1 { A_CheckReload(); A_WeaponReady(); } Wait; FireDummy: TNT1 A 1 { if ( Health <= 0 ) return ResolveState("Null"); let weap = Weapon(invoker); if ( !(player.cmd.buttons&(BT_ATTACK|BT_ALTATTACK)) || (weap.Ammo1.Amount <= 0) ) { player.SetPSprite(PSP_WEAPON,invoker.FindState("Unwind")); return ResolveState("Null"); } return ResolveState(null); } Wait; Fire: AltFire: MGNI A 3 { invoker.bcnt = 5; } Hold: MGNF A 0 { A_StartSound("minigun/fire",CHAN_WEAPONMISC,CHANF_LOOPING); A_Overlay(-9999,"FireDummy"); } MGNF ABCDEFGHIJKLMNOPQRS 1 A_FireBullet(); MGNF A 0 A_JumpIf(player.cmd.buttons&BT_ALTATTACK,1); Goto Hold+1; AltHold: MGNF B 0 A_StartSound("minigun/altfire",CHAN_WEAPONMISC,CHANF_LOOPING); MGNF ADGJMPSCFILORBEHKNQ 1 A_FireBullet(true); Goto AltHold+1; Unwind: MGNU A 0 { A_StopSound(CHAN_WEAPONMISC); A_StartSound("minigun/unwind",CHAN_WEAPON); } MGNU ABCDEFGHIJKLMNOPQRSTUVWXYZ 1 A_MinigunRefire(); MGU2 ABCDEFGHIJKLM 1 A_MinigunRefire(); Goto Idle; Deselect: MGND A 1 A_StopSound(CHAN_WEAPONMISC); MGND BCDEFGHIJ 1; MGND J 1 A_Lower(int.max); Wait; MuzzleFlash: MMUZ # 2 Bright { let psp = player.FindPSprite(OverlayID()); psp.frame = Random[Minigun](0,8); } Stop; } }