Class ImpalerAmmo : Ammo { Default { Tag "$T_IMPAMMO"; Inventory.Icon "I_Impale"; Inventory.PickupMessage "$I_IMPAMMO"; Inventory.Amount 3; Inventory.MaxAmount 15; Ammo.BackpackAmount 2; Ammo.BackpackMaxAmount 30; Ammo.DropAmount 3; +INVENTORY.IGNORESKILL; } 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 ) { let r = Spawn((GetClass()=="ImpalerAmmo")?"CellPack":"Cell",pos,ALLOW_REPLACE); r.spawnangle = spawnangle; r.spawnpoint = spawnpoint; r.angle = angle; r.pitch = pitch; r.roll = roll; r.special = special; r.args[0] = args[0]; r.args[1] = args[1]; r.args[2] = args[2]; r.args[3] = args[3]; r.args[4] = args[4]; r.ChangeTid(tid); r.SpawnFlags = SpawnFlags&~MTF_SECRET; r.HandleSpawnFlags(); r.SpawnFlags = SpawnFlags; r.bCountSecret = SpawnFlags&MTF_SECRET; r.vel = vel; r.master = master; r.target = target; r.tracer = tracer; r.bDropped = bDropped; Destroy(); } else { Owner.RemoveInventory(self); Destroy(); } } States { Spawn: IAMO A -1; Stop; } } Class ImpalerAmmo2 : ImpalerAmmo { Default { Tag "$T_IMPAMMO2"; Inventory.PickupMessage "$I_IMPAMMO2"; Inventory.Amount 1; Ammo.DropAmount 1; +INVENTORY.IGNORESKILL; } } Class ImpalerSpark : PulseSpark { States { Spawn: ISPK A 1 Bright { A_FadeOut(FRandom[Pulse](0.,.15)); vel *= .96; } Wait; } } Class ViewImpalerSpark : ViewPulseSpark { States { Spawn: ISPK A 1 Bright A_FadeOut(FRandom[Pulse](0.,.15)); Wait; } } Class ImpalerChunk : StingerChunk { override void Tick() { Super.Tick(); if ( isFrozen() ) return; let c = Spawn("UTSmoke",pos); c.vel = vel*.3; c.SetShade(Color(4,1,3)*Random[Impaler](48,63)); c.bBRIGHT = true; c.alpha *= .5*alpha; c.scale *= .5*scale.x; } } Class ImpalerBurstLight : PaletteLight { Default { Tag "ImpExpl"; Args 0,0,0,50; ReactionTime 15; } } Class ImpalerBoltTracer : LineTracer { Actor ignoreme; Array hitlist; override ETraceStatus TraceCallback() { if ( Results.HitType == TRACE_HitActor ) { if ( Results.HitActor == ignoreme ) return TRACE_Skip; if ( Results.HitActor.bSHOOTABLE ) { let ent = new("HitListEntry"); ent.hitactor = Results.HitActor; ent.hitlocation = Results.HitPos; ent.x = Results.HitVector; hitlist.Push(ent); } 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; return TRACE_Skip; } return TRACE_Stop; } } Class ImpalerBurstBolt : Actor { Vector3 nextpos, nextdir; override void Tick() { Super.Tick(); frame++; if ( frame >= 5 ) frame = 0; } action void A_Trace() { let t = new("ImpalerBoltTracer"); t.hitlist.Clear(); Vector3 x, y, z; [x, y, z] = dt_CoordUtil.GetAxes(pitch,angle,roll); t.Trace(pos,CurSector,x,11.125,0); for ( int i=0; i 5 ) return; let b = Spawn("ImpalerBurstBolt",invoker.nextpos); b.angle = atan2(invoker.nextdir.y,invoker.nextdir.x); b.pitch = asin(-invoker.nextdir.z); b.target = target; b.special1 = special1+1; b.frame = frame; } Default { RenderStyle "Add"; Obituary "$O_IMPALER1"; Radius 0.1; Height 0; +NOGRAVITY; +NOCLIP; +DONTSPLASH; +INTERPOLATEANGLES; +NOTELEPORT; } States { Spawn: PBLT A 1 Bright; PBLT A 1 Bright A_Trace(); PBLT # 1 Bright A_Spread(); PBLT # 1 Bright A_FadeOut(); Wait; Dummy: PBLT ABCDE -1; Stop; } } Class ImpalerBoltHit : Actor { Default { RenderStyle "Add"; Radius 0.1; Height 0; +NOGRAVITY; +NOCLIP; +DONTSPLASH; +FORCEXYBILLBOARD; +NOTELEPORT; Scale 0.3; } override void Tick() { Super.Tick(); if ( !master ) Destroy(); } States { Spawn: #### ABCD 1 Bright; Loop; Dummy: IHIT ABCD -1; ICAP ABCD -1; Stop; } } Class ImpalerBolt : Actor { const beamsize = 20.25; ImpalerBoltTracer t; ImpalerBolt next; StarterImpalerBolt start; Vector3 oldx; Actor weffect; override void OnDestroy() { Super.OnDestroy(); if ( next ) next.Destroy(); if ( weffect ) weffect.Destroy(); A_StopSound(CHAN_BODY); } override void PostBeginPlay() { Super.PostBeginPlay(); t = new("ImpalerBoltTracer"); if ( !(GetClass() is 'StarterImpalerBolt') ) A_PlaySound("impaler/beam",CHAN_BODY,.2,true,2.,pitch:.75); } void CheckBeam( Vector3 x ) { t.HitList.Clear(); if ( bHITOWNER ) t.ignoreme = null; else t.ignoreme = target; t.Trace(pos,cursector,x,beamsize,0); for ( int i=0; i closest) || (tracer.Health <= 0) ) tracer = null; } let bt = BlockThingsIterator.Create(self,closest); while ( bt.Next() ) { let a = bt.Thing; if ( !a || !a.bSHOOTABLE || !a.bISMONSTER || (a.Health <= 0) || target.IsFriend(a) || !CheckSight(a) || (start && (start.Hitlist.Find(a) < start.HitList.Size())) ) continue; Vector3 dirto = level.Vec3Diff(pos,a.Vec3Offset(0,0,a.height/2)); double dist = dirto.length(); dirto /= dist; if ( dirto dot x < .5 ) continue; if ( dist > closest ) continue; tracer = a; closest = dist; } if ( tracer ) special1 = 0; Vector3 normal = -t.Results.HitVector, dir = t.Results.HitVector; if ( t.Results.HitType == TRACE_HitWall ) { normal = (t.Results.HitLine.delta.y,-t.Results.HitLine.delta.x,0).unit(); if ( t.Results.Side ) normal *= -1; t.Results.HitLine.RemoteActivate(target,t.Results.Side,SPAC_Impact,t.Results.HitPos); dir -= 2*normal*(dir dot normal); } else if ( t.Results.HitType == TRACE_HitFloor ) { if ( t.Results.ffloor ) normal = -t.Results.ffloor.top.Normal; else normal = t.Results.HitSector.floorplane.Normal; dir -= 2*normal*(dir dot normal); } else if ( t.Results.HitType == TRACE_HitCeiling ) { if ( t.Results.ffloor ) normal = -t.Results.ffloor.bottom.Normal; else normal = t.Results.HitSector.ceilingplane.Normal; dir -= 2*normal*(dir dot normal); } else { // why the fuck t.Results.HitPos = level.Vec3Offset(pos,x*beamsize); normal *= 0; } if ( t.Results.HitType != TRACE_HitNone ) { A_SprayDecal("BoltScorch",beamsize+8); int numpt = Random[Impaler](10,20)*!Random[Impaler](0,2); for ( int i=0; i 1) ) { dirto = level.Vec3Diff(pos,tracer.Vec3Offset(0,0,tracer.height/2)); distto = dirto.length(); dirto /= distto; distto = 1.-clamp(distto*0.1,0.,.9); } dir = (x+y*cos(a)*s+z*sin(a)*s+dirto*distto).unit(); dir = dir*.5+oldx*.5; oldx = dir; A_SetAngle(atan2(dir.y,dir.x)); A_SetPitch(asin(-dir.z)); CheckBeam(dir); } Default { RenderStyle "Add"; Obituary "$O_IMPALER2"; Radius 0.1; Height 0; +NOGRAVITY; +NOCLIP; +DONTSPLASH; +INTERPOLATEANGLES; +NOTELEPORT; } States { Spawn: PBLT # -1 Bright; Stop; Dummy: PBLT ABCDE -1; Stop; } } Class ImpalerFlare : Actor { Default { RenderStyle "Add"; Scale 0.02; Alpha 0.5; Radius 0.1; Height 0; +NOGRAVITY; +NOCLIP; +DONTSPLASH; +INTERPOLATEANGLES; +NOTELEPORT; +FORCEXYBILLBOARD; +ROLLSPRITE; } States { Spawn: IFLA A -1 Bright; Stop; } } Class StarterImpalerBolt : ImpalerBolt { Array hitlist; Actor flares[2]; override void PostBeginPlay() { Super.PostBeginPlay(); start = self; flares[0] = Spawn("ImpalerFlare",pos); flares[1] = Spawn("ImpalerFlare",pos); oldx = (cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(pitch)); } override void OnDestroy() { Super.OnDestroy(); if ( flares[0] ) flares[0].Destroy(); if ( flares[1] ) flares[1].Destroy(); } override void Tick() { Super.Tick(); frame++; if ( frame > 4 ) frame = 0; if ( isFrozen() ) return; if ( !target ) { Destroy(); return; } Vector3 x, y, z, origin; bRELATIVETOFLOOR = (target.pos.z <= target.floorz); // hack, but kinda works if ( target.player ) { [x, y, z] = dt_CoordUtil.GetAxes(target.pitch,target.angle,target.roll); origin = level.Vec3Offset(target.Vec2OffsetZ(0,0,target.player.viewz),15*x+2*y-2.5*z); } else origin = target.Vec3Offset(0,0,target.missileheight); SetOrigin(origin,true); if ( !flares[0] ) flares[0] = Spawn("ImpalerFlare",pos); if ( !flares[1] ) flares[1] = Spawn("ImpalerFlare",pos); flares[1].bRELATIVETOFLOOR = flares[0].bRELATIVETOFLOOR = bRELATIVETOFLOOR; flares[0].SetOrigin(pos,true); flares[1].SetOrigin(pos,true); flares[0].roll += 15.; flares[1].roll += 5.; flares[0].A_SetScale(0.01+cos(gametic*8)*0.002); flares[1].A_SetScale(0.02+cos(gametic*8)*0.004); double a = FRandom[Impaler](0,360), s = FRandom[Impaler](0.,.1); [x, y, z] = dt_CoordUtil.GetAxes(target.pitch,target.angle,target.roll); Vector3 dir = (x+cos(a)*y*s+sin(a)*z*s).unit(); dir = oldx*.5+dir*.5; oldx = dir; A_SetAngle(atan2(dir.y,dir.x)); A_SetPitch(asin(-dir.z)); hitlist.Clear(); CheckBeam((cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(pitch))); } } Class ImpalerProjectile : Actor { Default { Obituary "$O_IMPALER1"; DamageType 'Impaler'; Speed 30; Radius 4; Height 4; PROJECTILE; +SKYEXPLODE; +EXPLODEONWATER; +FORCERADIUSDMG; +NODAMAGETHRUST; +INTERPOLATEANGLES; } override void PostBeginPlay() { Super.PostBeginPlay(); A_PlaySound("impaler/fly",CHAN_BODY,1.,true,2.); } action void A_ImpalerHit() { A_StopSound(CHAN_BODY); bFORCEXYBILLBOARD = true; scale *= 2.+special1*0.01; A_AlertMonsters(); A_SetRenderStyle(1.,STYLE_Add); A_NoGravity(); A_Explode(50+special1/2,80+special1/5); UTMainHandler.DoBlast(self,80+special1/5,40000); A_QuakeEx(2,2,2,5,0,150+special1/5,"",QF_RELATIVE|QF_SCALEDOWN,falloff:80+special1/2,rollintensity:0.2); A_PlaySound("impaler/hit",CHAN_VOICE); A_SprayDecal("ShockMark",20); let l = Spawn("ImpalerBurstLight",pos); l.Args[3] += special1/5; double ang, pt; int numpt = Random[Impaler](4,8); for ( int i=0; i 0 ) { let b = Spawn("UTBubble",pos); b.vel = vel*0.5; special1 = -30; } roll += 15.; double ang, pt; int numpt; if ( special1 > 80 ) { numpt = special1/90; for ( int i=0; i 120 ) { numpt = special1/120; for ( int i=0; i= (d.HitActor.pos.z+d.HitActor.height*0.8) ) dmg = d.HitActor.DamageMobj(invoker,self,dmg*2,'Decapitated',DMG_USEANGLE|DMG_THRUSTLESS,atan2(d.HitDir.y,d.HitDir.x)); else dmg = d.HitActor.DamageMobj(invoker,self,dmg,'slashed',DMG_USEANGLE|DMG_THRUSTLESS,atan2(d.HitDir.y,d.HitDir.x)); UTMainHandler.DoKnockback(d.HitActor,d.HitDir,12000); if ( d.HitActor.player ) d.HitActor.A_QuakeEx(2,2,2,6,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:0.25); if ( !d.HitActor.bNOBLOOD ) { d.HitActor.TraceBleed(dmg,invoker); d.HitActor.SpawnBlood(d.HitLocation,atan2(d.HitDir.y,d.HitDir.x)+180,dmg); } } else if ( d.HitType == TRACE_HitWall ) d.HitLine.RemoteActivate(self,d.LineSide,SPAC_Impact,d.HitLocation-d.HitDir*4); A_QuakeEx(1,1,1,3,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:0.12); if ( !d.HitActor || d.HitActor.bNOBLOOD ) { A_PlaySound("impaler/wall",CHAN_WEAPON); let p = Spawn("SawImpact",d.HitLocation-d.HitDir*4); p.angle = atan2(d.HitDir.y,d.HitDir.x); p.pitch = asin(-d.HitDir.z); } else A_PlaySound("impaler/flesh",CHAN_WEAPON); A_AlertMonsters(); A_QuakeEx(1,1,1,6,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:0.06); return true; } return false; } action void A_Stab() { invoker.FireEffect(); UTMainHandler.DoSwing(self,(FRandom[Impaler](-1,1),FRandom[Impaler](-1,1)),0.3,-0.2,2,SWING_Spring,0,2); for ( int i=0; i<8; i++ ) if ( TryHit(angle+i*(45./16),15) || TryHit(angle-i*(45./16),15) ) return; } override void DoEffect() { Super.DoEffect(); if ( (Ammo1.Amount <= 0) && (ClipCount <= 0) ) SelectionOrder = 6600; else SelectionOrder = default.SelectionOrder; if ( Owner.player.ReadyWeapon != self ) return; if ( (Owner.waterlevel > 2) && !(level.maptime%5) && HasGem ) ClipCount = max(0,ClipCount-1); let psp = Owner.player.FindPSprite(-2); if ( psp ) psp.alpha = clamp(ClipCount/double(default.ClipCount),0.,1.); } override bool CheckAmmo( int fireMode, bool autoSwitch, bool requireAmmo, int ammocount ) { if ( ClipCount > 0 ) return true; return Super.CheckAmmo(firemode,autoswitch,requireammo,ammocount); } Default { Tag "$T_IMPALER"; Obituary "$O_IMPALERHIT"; Inventory.PickupMessage "$I_IMPALER"; Weapon.UpSound "impaler/select"; Weapon.SlotNumber 7; Weapon.SelectionOrder 700; Weapon.SlotPriority 0.9; Weapon.AmmoType "ImpalerAmmo"; Weapon.AmmoUse 1; Weapon.AmmoType2 "ImpalerAmmo"; Weapon.AmmoUse2 1; Weapon.AmmoGive 6; UTWeapon.DropAmmo 3; Impaler.ClipCount 30; +WEAPON.AMMO_OPTIONAL; +WEAPON.ALT_AMMO_OPTIONAL; } States { Spawn: IMPP A -1; Stop; IMPP B -1; Stop; Select: IMPS A 1 A_Raise(int.max); Wait; Ready: IMPS ABCDEF 3 A_WeaponReady(WRF_NOFIRE); IMPI A 0 { let weap = Weapon(invoker); invoker.HasGem = false; if ( (invoker.ClipCount>=0) || (weap.Ammo1.Amount > 0) ) return ResolveState("Reload"); return ResolveState("Idle"); } Goto Idle; Dummy: TNT1 A 1 { let weap = Weapon(invoker); int flags = 0; if ( (weap.Ammo1.Amount > 0) || (invoker.ClipCount > 0) ) flags |= WRF_ALLOWRELOAD; if ( invoker.HasGem && ((invoker.ClipCount <= 0) || (waterlevel >= 2)) ) flags |= WRF_NOSECONDARY; A_WeaponReady(flags); } Wait; Idle: IMPI A 0 A_Overlay(-9999,"Dummy"); IMPI ABCDEFGH 10; Goto Idle+1; Melee: IMPM A 2 { A_Overlay(-9999,"Null"); A_PlaySound("impaler/stab",CHAN_WEAPON); } IMPM BC 2; IMPM D 2 A_Stab(); IMPM EFGHIJ 2; Goto Idle; Fire: IMPF A 0 { if ( !invoker.HasGem ) return ResolveState("Melee"); A_ImpalerFire(); return ResolveState(null); } IMPF ABCDEFGHI 2; IMPI A 0 A_JumpIfNoAmmo("Idle"); Goto Reload; AltFire: IMPA A 0 { if ( !invoker.HasGem ) return ResolveState("Melee"); A_Overlay(-9999,"Null"); player.SetPSprite(-3,invoker.FindState("GemAltFire")); player.SetPSprite(-2,invoker.FindState("ZapAltFire")); return ResolveState(null); } IMPA ABCDEFGH 2; IMPA I 0 { A_PlaySound("impaler/altfire",CHAN_WEAPON,looping:true); A_StartBeam(); invoker.special1 = 0; } Goto AltHold; AltHold: IMPA IJKLMNOP 2 { A_DrainAmmo(); invoker.special1++; return A_JumpIf((invoker.ClipCount<=0)||!(player.cmd.buttons&BT_ALTATTACK)||(waterlevel>=2),"AltRelease"); } Loop; AltRelease: IMPA Q 0 { player.SetPSprite(-3,invoker.FindState("GemAltRelease")); player.SetPSprite(-2,invoker.FindState("ZapAltRelease")); A_StopBeam(); A_PlaySound("impaler/altend",CHAN_WEAPON,looping:true); } IMPA QRSTUVWX 2; Goto Idle; Reload: IMPG A 0 { A_Overlay(-9999,"Null"); invoker.HasGem = !invoker.HasGem; if ( invoker.HasGem ) { let weap = Weapon(invoker); if ( (invoker.ClipCount < 0) && (weap.Ammo1.Amount > 0) ) { weap.DepleteAmmo(false,true,1); invoker.ClipCount = invoker.default.ClipCount; } A_Overlay(-3,"GemUp"); A_Overlay(-2,"ZapUp"); A_OverlayFlags(-2,PSPF_RenderStyle|PSPF_ForceStyle|PSPF_Alpha|PSPF_ForceAlpha,true); A_OverlayRenderStyle(-2,STYLE_Add); A_PlaySound("impaler/gem",CHAN_WEAPON,looping:true); } else { player.SetPSprite(-3,invoker.FindState("GemDown")); player.SetPSprite(-2,invoker.FindState("ZapDown")); A_PlaySound("impaler/gemdown",CHAN_WEAPON); } if ( self is 'UTPlayer' ) UTPlayer(self).PlayReloading(); } IMPG ABCDE 2; Goto Idle; Deselect: IMPD A 0 A_Overlay(-9999,"Null"); IMPD A 0 A_JumpIf(!invoker.HasGem,"FullDeselect"); IMPG A 0 { invoker.HasGem = false; player.SetPSprite(-3,invoker.FindState("GemDown")); player.SetPSprite(-2,invoker.FindState("ZapDown")); A_PlaySound("impaler/gemdown",CHAN_WEAPON); } IMPG ABCDE 2; Goto FullDeselect; FullDeselect: IMPD ABCDEF 2; IMPD F 1 A_Lower(int.max); Wait; GemUp: IMGS ABCDE 2; Goto GemIdle; GemIdle: IMGI ABCDEFGH 10; Loop; GemDown: IMGD ABCDE 2; Stop; GemAltFire: IMGA ABCDEFGH 2; Goto GemAltHold; GemAltHold: IMGA IJKLMNOP 2; Loop; GemAltRelease: IMGA QRSTUVWX 2; Goto GemIdle; ZapUp: IMZS ABCDE 2 Bright; Goto ZapIdle; ZapIdle: IMZI ABCDEFGH 10 Bright; Loop; ZapDown: IMZD ABCDE 2 Bright; Stop; ZapAltFire: IMZA ABCDEFGH 2 Bright; Goto ZapAltHold; ZapAltHold: IMZA IJKLMNOP 2 Bright; Loop; ZapAltRelease: IMZA QRSTUVWX 2 Bright; Goto ZapIdle; } }