Class StunnerAmmo : Ammo { double rechargephase, rechargespeed; Default { Inventory.Icon "I_Stun"; Inventory.Amount 10; Inventory.MaxAmount 50; Ammo.BackpackAmount 0; Ammo.BackpackMaxAmount 50; } override void DoEffect() { Super.DoEffect(); if ( rechargespeed <= 0. ) rechargespeed = 2.; rechargephase += 1./rechargespeed; if ( rechargephase < 7 ) return; rechargespeed = max(2.,.2*Amount); rechargephase = 0; if ( Amount >= MaxAmount ) return; let sting = Owner.FindInventory("StingerAmmo"); if ( sting && (sting.Amount > 0) ) { if ( !sv_infiniteammo && !Owner.FindInventory('PowerInfiniteAmmo',true) ) sting.Amount--; Amount++; } } override bool TryPickup( in out Actor toucher ) { if ( !sting_proto ) return false; // not allowed return Super.TryPickup(toucher); } override void Tick() { Super.Tick(); if ( sting_proto ) return; if ( Owner ) Owner.RemoveInventory(self); Destroy(); } } Class StunTrail : Actor { Default { RenderStyle "Add"; Radius 0.1; Height 0; +NOBLOCKMAP; +NOGRAVITY; +DONTSPLASH; +NOTELEPORT; +FORCEXYBILLBOARD; } override void Tick() { Super.Tick(); if ( isFrozen() ) return; A_FadeOut(1./11.,0); } States { Spawn: FATR ABCDE 3 Bright; Stop; } } Class StunLight : PaletteLight { Default { Tag "DYellow"; } } Class StunTracer : LineTracer { Actor owner, ignore; Array ShootThroughList; override ETraceStatus TraceCallback() { if ( Results.HitType == TRACE_HitActor ) { if ( (Results.HitActor == owner) || (Results.HitActor == ignore) ) return TRACE_Skip; if ( Results.HitActor.bSHOOTABLE ) return TRACE_Stop; return TRACE_Skip; } else if ( (Results.HitType == TRACE_HitWall) && (Results.Tier == TIER_Middle) ) { if ( !Results.HitLine.sidedef[1] || (Results.HitLine.Flags&(Line.ML_BlockHitscan|Line.ML_BlockEverything)) ) return TRACE_Stop; ShootThroughList.Push(Results.HitLine); return TRACE_Skip; } return TRACE_Stop; } } Class StunProj : Actor { StunTracer t; Vector3 tracedir; bool moving; double totaldist; override void PostBeginPlay() { Super.PostBeginPlay(); t = new("StunTracer"); t.owner = target; t.ignore = self; moving = true; } override void Tick() { Super.Tick(); if ( isFrozen() ) return; if ( !moving ) { A_FadeOut(1/19.,0); return; } // step trace tracedir = (cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(pitch)); t.ShootThroughList.Clear(); t.Trace(pos,cursector,tracedir,500,0); for ( int i=0; i= 10000.0 ) { // reposition and explode on air SetOrigin(t.Results.HitPos-t.Results.HitVector*4,false); StunExplode(); } else if ( t.Results.HitType == TRACE_HitNone ) { // reposition SetOrigin(t.Results.HitPos+t.Results.HitVector,false); angle = atan2(t.Results.HitVector.y,t.Results.HitVector.x); pitch = asin(-t.Results.HitVector.z); } else if ( t.Results.HitType == TRACE_HitActor ) { // reposition and explode on actor SetOrigin(t.Results.HitPos-t.Results.HitVector*4,false); StunExplode(); Actor a = t.Results.HitActor; a.DamageMobj(self,target,max(1,int(10*specialf1)),'jolted',DMG_USEANGLE,atan2(t.Results.HitVector.y,t.Results.HitVector.x)); if ( !a.bDONTTHRUST ) { UTMainHandler.DoKnockback(a,t.Results.HitVector,(bAMBUSH?-22000:26000)*specialf1); a.vel.z += 2.*specialf1; } angle = atan2(t.Results.HitVector.y,t.Results.HitVector.x); pitch = asin(-t.Results.HitVector.z); } else { // reposition and explode on wall SetOrigin(t.Results.HitPos-t.Results.HitVector*4,false); A_SprayDecal("ShockMark",16); Vector3 HitNormal = t.Results.HitVector; if ( t.Results.HitType == TRACE_HitWall ) { t.Results.HitLine.RemoteActivate(target,t.Results.Side,SPAC_Impact,pos); // calculate normal HitNormal = (-t.Results.HitLine.delta.y,t.Results.HitLine.delta.x,0).unit(); if ( t.Results.Side == 0 ) HitNormal *= -1; } else if ( t.Results.HitType == TRACE_HitFloor ) { if ( t.Results.ffloor ) HitNormal = -t.Results.ffloor.top.Normal; else HitNormal = t.Results.HitSector.floorplane.Normal; } else if ( t.Results.HitType == TRACE_HitCeiling ) { if ( t.Results.ffloor ) HitNormal = -t.Results.ffloor.bottom.Normal; else HitNormal = t.Results.HitSector.ceilingplane.Normal; } StunExplode(); angle = atan2(HitNormal.y,HitNormal.x); pitch = asin(-HitNormal.z); } } void StunExplode() { moving = false; SetStateLabel("Death"); A_QuakeEx(1,1,1,3,0,250,"",QF_RELATIVE|QF_SCALEDOWN,falloff:120,rollintensity:0.2); A_StartSound("stun/hit",CHAN_VOICE,pitch:FRandom[Stunner](1.5,1.9)-0.08*specialf1); A_AlertMonsters(gameinfo.gametype&GAME_Strife?100:0); Vector3 dir = (cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(pitch)); int numpt = Random[ExploS](10,15); for ( int i=0; i0)||!deathmatch)?0:5); StunnerAmmo(weap.Ammo1).rechargespeed = 2.; A_StopSound(CHAN_WEAPONMISC); A_StartSound("stun/fire",CHAN_WEAPON,CHANF_OVERLAP,Dampener.Active(self)?.4:1.,pitch:1.2-invoker.chargesize*0.06); double mult = Amplifier.GetMult(self,int(invoker.ChargeSize*50)+50); invoker.FireEffect(); A_QuakeEx(1+int(0.5*invoker.chargesize),1+int(0.5*invoker.chargesize),1+int(0.5*invoker.chargesize),5+int(1.2*invoker.chargesize),0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:0.05+0.01*invoker.chargesize); UTMainHandler.DoSwing(self,(FRandom[Stunner](-0.04,0.04),FRandom[Stunner](-0.9,-0.4)),1+invoker.chargesize*0.3,-0.1,3,SWING_Spring,3,2.5); A_Overlay(-2,"Null"); Vector3 x, y, z; [x, y, z] = dt_CoordUtil.GetAxes(pitch,angle,roll); Vector3 origin = level.Vec3Offset(Vec2OffsetZ(0,0,player.viewz),x*10-z*5); let p = Spawn("StunProj",origin); p.angle = angle; p.pitch = BulletSlope(); p.target = self; p.specialf1 = invoker.chargesize*mult; p.bAMBUSH = weap.bAltFire; FLineTraceData d; LineTrace(angle,60,BulletSlope(),TRF_ABSPOSITION,origin.z,origin.x,origin.y,d); if ( d.HitType == TRACE_HitActor ) { int dmg = int(12*invoker.chargesize); if ( d.HitLocation.z >= (d.HitActor.pos.z+d.HitActor.height*0.81) ) dmg = d.HitActor.DamageMobj(invoker,self,dmg*2,'Decapitated',DMG_THRUSTLESS); else dmg = d.HitActor.DamageMobj(invoker,self,dmg,'impact',DMG_THRUSTLESS); UTMainHandler.DoKnockback(d.HitActor,x,4000*invoker.chargesize); if ( d.HitActor.bNOBLOOD ) { let p = Spawn("StunnerImpact",d.HitLocation-d.HitDir*4); p.angle = atan2(d.HitDir.y,d.HitDir.x); p.pitch = asin(-d.HitDir.z); } else { d.HitActor.TraceBleed(dmg,invoker); d.HitActor.SpawnBlood(d.HitLocation,atan2(d.HitDir.y,d.HitDir.x)+180,dmg); } } else if ( d.HitType != TRACE_HitNone ) { let p = Spawn("StunnerImpact",d.HitLocation-d.HitDir*4); p.angle = atan2(d.HitDir.y,d.HitDir.x); p.pitch = asin(-d.HitDir.z); if ( d.HitType == TRACE_HitWall ) d.HitLine.RemoteActivate(self,d.LineSide,SPAC_Impact,d.HitLocation-d.HitDir*4); } } action void A_BeginCharge() { let weap = Weapon(invoker); invoker.bCharging = true; weap.DepleteAmmo(weap.bAltFire,true,1); invoker.count = 0; invoker.chargesize = .6; A_StartSound("stun/charge",CHAN_WEAPONMISC,volume:Dampener.Active(self)?.15:1.); A_Overlay(-2,"Sparks"); A_OverlayFlags(-2,PSPF_RenderStyle,true); A_OverlayRenderStyle(-2,STYLE_Add); StunnerAmmo(weap.Ammo1).rechargephase = 0; if ( !Dampener.Active(self) && !(gameinfo.gametype&GAME_Strife) ) A_AlertMonsters(); } action State A_ChargeUp() { if ( !(player.cmd.buttons&(BT_ATTACK|BT_ALTATTACK)) ) return ResolveState("Release"); Weapon weap = Weapon(invoker); if ( !weap ) return ResolveState(null); StunnerAmmo(weap.Ammo1).rechargephase = 0; UTMainHandler.DoSwing(self,(FRandom[Stunner](-1,1),FRandom[Stunner](-1,1)),0.02*invoker.chargesize,0,2,SWING_Spring); A_WeaponOffset(FRandom[Stunner](-1,1)*1.2*invoker.chargesize,32+FRandom[Stunner](-1,1)*1.2*invoker.chargesize); A_SoundVolume(CHAN_WEAPONMISC,Dampener.Active(self)?.15:1.); if ( !Dampener.Active(self) && !(gameinfo.gametype&GAME_Strife) ) A_AlertMonsters(); if ( invoker.chargesize >= 5. ) { invoker.count += 1./35.; if ( invoker.count > 1.5 ) return ResolveState("Release"); return ResolveState(null); } invoker.chargesize += 2.4/35.; invoker.count += 1.2/35.; if ( invoker.count < 0.24 ) return ResolveState(null); invoker.count = 0; if ( !(sv_infiniteammo || FindInventory('PowerInfiniteAmmo',true)) ) { if ( weap.Ammo1.Amount < 1 ) return ResolveState("Release"); weap.Ammo1.Amount--; } return ResolveState(null); } Default { Tag "$T_STUNNER"; Inventory.PickupMessage "$I_STUNNER"; Weapon.UpSound "stun/select"; Weapon.SlotNumber 4; Weapon.SelectionOrder 8000; Weapon.SlotPriority 0.9; Weapon.AmmoType "StunnerAmmo"; Weapon.AmmoUse 1; Weapon.AmmoType2 "StunnerAmmo"; Weapon.AmmoUse2 1; Weapon.AmmoGive 50; UTWeapon.DropAmmo 50; +WEAPON.WIMPY_WEAPON; +NOEXTREMEDEATH; } States { Spawn: STNP A -1; Stop; STNP B -1; Stop; Select: STNS A 1 A_Raise(int.max); Wait; Ready: STNS ABCDEFGHIJKLMNOPQRSTUVWX 2 A_WeaponReady(WRF_NOFIRE); Goto Idle; Idle: STNI A 1 { A_CheckReload(); let weap = Weapon(invoker); if ( weap && weap.Ammo1.Amount > 0 ) A_WeaponReady(); else A_WeaponReady(WRF_NOFIRE); } Wait; Fire: AltFire: #### # 2 A_BeginCharge(); STNF ABCDEFGHIJKLMNOPQRST 2 A_ChargeUp(); Goto Hold; Hold: STNH R 1 A_ChargeUp(); Wait; Release: #### # 2; STNR A 0 A_StunnerFire(); STNR ABC 1; STR2 ABCDE 2; STNF TRPNLJHFDB 1 A_WeaponReady(WRF_NOSWITCH|WRF_DISABLESWITCH); Goto Idle; Deselect: STND ABCDEFGHIJKL 1; STND L 1 A_Lower(int.max); Wait; Sparks: TNT1 A 8; STFF ABCDEFGHIJKLMNOPQRS 2 Bright; STFF J 0 A_JumpIf(invoker.chargesize>=5.,1); Goto Sparks+10; STFF JIHGFEDCBA 2 Bright; Stop; } }