// Mortal Rifle projectiles and effects Class MisterWeaponLight : SWWMWeaponLight { Default { args 64,224,255,150; } } Class MisterCasing : SWWMCasing { Default { BounceSound "mister/casing"; } override void PostBeginPlay() { Super.PostBeginPlay(); heat = 0; } States { Death: XZW1 BCDE -1 { bINTERPOLATEANGLES = false; pitch = roll = 0; angle = FRandom[Junk](0,360); frame = RandomPick[Junk](1,4); } Stop; } } Class MisterGCasing : SWWMCasing { Default { Mass 8; BounceFactor 0.5; WallBounceFactor 0.5; BounceSound "mister/gcasing"; } override void PostBeginPlay() { Super.PostBeginPlay(); heat = 0; } } Class MisterMag : SWWMCasing { Default { Mass 10; BounceFactor 0.4; WallBounceFactor 0.4; BounceSound "mister/mag"; } override void PostBeginPlay() { Super.PostBeginPlay(); heat = 0; } States { Death: XZW1 BC -1 { bINTERPOLATEANGLES = false; pitch = roll = 0; angle = FRandom[Junk](0,360); frame = RandomPick[Junk](1,2); } Stop; } } Class MisterRing : Actor { Default { RenderStyle "Add"; Scale 1.5; Radius .1; Height 0; +NOGRAVITY; +NOBLOCKMAP; +FORCEXYBILLBOARD; +NOTELEPORT; +NOINTERACTION; } override void Tick() { if ( isFrozen() ) return; if ( !CheckNoDelay() || (tics == -1) ) return; if ( tics > 0 ) tics--; while ( !tics ) { if ( !SetState(CurState.NextState) ) return; } } States { Spawn: XRG9 ABCDEFGHIJKLMNOPQRSTUVWX 1 Bright A_SetScale(scale.x*1.08); Stop; } } Class MisterExLight : PaletteLight { Default { Tag "Cyanblu"; ReactionTime 35; Args 0,0,0,250; } } Class MisterExLightBig : MisterExLight { Default { ReactionTime 45; Args 0,0,0,500; } } Class MisterExLightSmall : MisterExLight { Default { ReactionTime 25; Args 0,0,0,120; } } Class MisterExLightTiny : MisterExLight { Default { ReactionTime 15; Args 0,0,0,80; } } Class MisterBulletImpactPop : Actor { Default { RenderStyle "Add"; Scale 6.; Radius .1; Height 0.; +NOGRAVITY; +NOBLOCKMAP; +NODAMAGETHRUST; +FORCERADIUSDMG; +FORCEXYBILLBOARD; +ROLLSPRITE; +ROLLCENTER; +NOTELEPORT; +NOINTERACTION; } override void Tick() { if ( isFrozen() ) return; A_SetScale(scale.x*.9,scale.y*.9); A_FadeOut(.15); if ( !CheckNoDelay() || (tics == -1) ) return; if ( tics > 0 ) tics--; while ( !tics ) { if ( !SetState(CurState.NextState) ) return; } } States { Spawn: BLPF C 5 NoDelay Bright { Scale *= FRandom[ExploS](0.6,1.8); Scale.x *= RandomPick[ExploS](-1,1); Scale.y *= RandomPick[ExploS](-1,1); roll = FRandom[Explos](0,360); } Stop; } } Class MisterFuzzy : Actor { MisterRailCounter mrc; Default { Obituary "$O_MORTALRIFLE"; DamageType "Mortal"; RenderStyle "Add"; Radius .1; Height 0; +NODAMAGETHRUST; +FORCERADIUSDMG; +FOILINVUL; +NOGRAVITY; +NOBLOCKMAP; +DONTSPLASH; +NOINTERACTION; } override void PostBeginPlay() { special1 += Random[ExploS](4,10); specialf1 = special1; vel = SWWMUtility.Vec3FromAngles(angle,pitch)*FRandom[ExploS](8,24); } override void Tick() { if ( isFrozen() ) return; int nhit, nkill; [nhit, nkill] = SWWMUtility.DoExplosion(self,(special2<0)?4:44,3000,80,80,DE_EXTRAZTHRUST|DE_COUNTENEMIES); if ( mrc ) mrc.nkill += nkill; special1--; if ( special1 <= 0 ) { Destroy(); return; } Vector3 dir = vel; double magvel = dir.length(); magvel *= 1.2; if ( magvel > 0. ) { dir /= magvel; dir += .5*SWWMUtility.Vec3FromAngles(FRandom[ExploS](0,360),FRandom[ExploS](-90,90)); dir = dir.unit(); vel = dir*magvel; } FLineTraceData d; Vector3 newpos = pos; newpos.z = clamp(newpos.z,floorz,ceilingz); int nstep = 0; double dist = magvel; while ( dist > 0 ) { // safeguard, too many bounces if ( nstep > MAXBOUNCEPERTIC ) { Destroy(); return; } Vector3 oldpos = newpos; double ang = atan2(dir.y,dir.x); double pt = asin(-dir.z); LineTrace(ang,dist,pt,TRF_THRUACTORS|TRF_THRUHITSCAN|TRF_ABSPOSITION,newpos.z,newpos.x,newpos.y,d); 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; } if ( d.HitType != TRACE_HitNone ) { dist -= d.Distance; // should only happen if we bounced dir = d.HitDir-(FRandom[Puff](1.5,2.)*hitnormal*(d.HitDir dot hitnormal)); vel = dir*magvel; newpos = d.HitLocation+dir; } else { dist = 0.; newpos = level.Vec3Offset(newpos,dir*magvel); } Vector3 traildir = level.Vec3Diff(oldpos,newpos); double len = traildir.length(); if ( len > 0. ) { traildir /= len; for ( double i=0.; i 0 ) tics--; while ( !tics ) { if ( !SetState(CurState.NextState) ) return; } } States { Spawn: TNT1 A 1 NoDelay { A_SetTics(Random[ExploS](1,15)); Scale *= FRandom[ExploS](.5,1.5); Scale.x *= RandomPick[ExploS](-1,1); Scale.y *= RandomPick[ExploS](-1,1); roll = FRandom[ExploS](0,360); } BLPF C 2 Bright { int nhit, nkill; [nhit, nkill] = SWWMUtility.DoExplosion(self,4,2000,50,50,DE_EXTRAZTHRUST|DE_COUNTENEMIES); if ( mrc ) mrc.nkill += nkill; } TNT1 A 1 { let p = Spawn("MisterFuzzyTrail",pos); p.alpha *= 1.5; p.scale *= .5; } Stop; } } Class MisterBulletImpact : Actor { MisterRailCounter mrc; // simplify code by putting this here Default { Obituary "$O_MORTALRIFLE"; DamageType "Mortal"; RenderStyle "Add"; Radius .1; Height 0.; Scale 1.5; +NOGRAVITY; +NOBLOCKMAP; +NODAMAGETHRUST; +FORCERADIUSDMG; +FORCEXYBILLBOARD; +NOTELEPORT; +FOILINVUL; +NOINTERACTION; } virtual void A_BulletExplode() { A_AlertMonsters(swwm_uncapalert?0:4000); SWWMUtility.DoExplosion(self,444,80000,150,150,DE_EXTRAZTHRUST); A_QuakeEx(6,6,6,10,0,400,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,falloff:150,rollintensity:.6); A_StartSound("mister/hitsemi",CHAN_VOICE,attenuation:.3); A_StartSound("mister/hitsemi",CHAN_WEAPON,attenuation:.2); A_SprayDecal("RocketBlast",-172); Scale *= FRandom[ExploS](0.8,1.1); Scale.x *= RandomPick[ExploS](-1,1); Scale.y *= RandomPick[ExploS](-1,1); int numpt = Random[ExploS](20,30); for ( int i=0; i 0 ) tics--; while ( !tics ) { if ( !SetState(CurState.NextState) ) return; } } States { Spawn: TNT1 A 0; XEX7 ABDEGHJKMNPQSTVWYZ\] 1 Bright A_BulletSubExplode(); Stop; } } Class MisterBuckshotImpact : MisterBulletImpact { Default { Scale .8; } override void A_BulletExplode() { A_AlertMonsters(swwm_uncapalert?0:1000); SWWMUtility.DoExplosion(self,444,8000,80,80,DE_EXTRAZTHRUST); A_QuakeEx(2,2,2,5,0,200,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,falloff:60,rollintensity:.2); A_StartSound("mister/hitscatter",CHAN_VOICE,attenuation:.35); A_SprayDecal("ShockMark",-172); Scale *= FRandom[ExploS](0.8,1.1); Scale.x *= RandomPick[ExploS](-1,1); Scale.y *= RandomPick[ExploS](-1,1); int numpt = Random[ExploS](4,8); for ( int i=0; i 0 ) { ExplodeMissile(); return; } ReactionTime--; if ( ReactionTime <= 0 ) { ExplodeMissile(); return; } // proximity check if ( bNoProx ) return; // "safe delay" for main grenade if ( !bAMBUSH && (ReactionTime > default.ReactionTime-20) ) return; let bt = BlockThingsIterator.Create(self,120); while ( bt.Next() ) { let t = bt.Thing; if ( !t || !t.bSHOOTABLE || (!t.bISMONSTER && !(t is 'BossBrain') && !t.player) || (t.Health <= 0) || (target && t.IsFriend(target)) || !SWWMUtility.SphereIntersect(t,level.Vec3Offset(pos,vel),bAMBUSH?80:120) || !CheckSight(t,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) continue; special1++; tracer = t; break; } } // quicksort (seeking candidates) private int partition_candidates( Array a, int l, int h ) { Actor pv = a[h]; int i = (l-1); for ( int j=l; j<=(h-1); j++ ) { if ( Distance3DSquared(pv) > Distance3DSquared(a[j]) ) { i++; Actor tmp = a[j]; a[j] = a[i]; a[i] = tmp; } } Actor tmp = a[h]; a[h] = a[i+1]; a[i+1] = tmp; return i+1; } private void qsort_candidates( Array a, int l, int h ) { if ( l >= h ) return; int p = partition_candidates(a,l,h); qsort_candidates(a,l,p-1); qsort_candidates(a,p+1,h); } virtual void A_GrenadeExplode() { ReactionTime = 0; bForceXYBillboard = true; bRollSprite = false; A_SetRenderStyle(1.0,STYLE_Add); A_SprayDecal("BigRocketBlast",50); A_SetScale(4.); A_NoGravity(); Scale *= FRandom[ExploS](0.8,1.1); Scale.x *= RandomPick[ExploS](-1,1); Scale.y *= RandomPick[ExploS](-1,1); SWWMUtility.DoExplosion(self,444,120000,250,250,DE_EXTRAZTHRUST); A_QuakeEx(8,8,8,20,0,900,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,falloff:300,rollintensity:1.5); A_StartSound("mister/hitgrenade",CHAN_VOICE,attenuation:.3); A_StartSound("mister/hitgrenade",CHAN_WEAPON,attenuation:.2); SetOrigin(Vec3Offset(0,0,Height/2),false); int numpt = Random[ExploS](30,50); for ( int i=0; i candidates; let bt = BlockThingsIterator.Create(self,2000); let tt = new("TargetTracer"); tt.target = target; while ( bt.Next() ) { let t = bt.Thing; if ( !t || (t == tracer) || !t.bSHOOTABLE || (!t.bISMONSTER && !(t is 'BossBrain')) || (t.Health <= 0) || (target && t.IsFriend(target)) || !CheckSight(t,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) continue; // don't seek if enemy is too close to shooter if ( target && SWWMUtility.SphereIntersect(t,target.pos,250) ) continue; // don't seek if shooter is between us and the enemy if ( target ) { Vector3 dirto = level.Vec3Diff(pos,t.Vec3Offset(0,0,t.Height/2)); double distto = dirto.length(); if ( distto > 0 ) { dirto /= distto; if ( tt.Trace(pos,CurSector,dirto,distto,0) ) continue; } } // don't seek if enemy is too close to another candidate // this would cause some of the splash damage potential to go to waste bool tooclose = false; for ( int i=0; i 1 ) qsort_candidates(candidates,0,candidates.Size()-1); // distribute among spawned sub-grenades int k = 0; for ( int i=-45; i<=45; i+=45 ) for ( int j=0; j<360; j+=60 ) { double ang = j; double pt = i; Vector3 dir = SWWMUtility.Vec3FromAngles(ang,pt); let p = MisterSubGrenade(Spawn("MisterSubGrenade",pos)); p.angle = ang; p.pitch = pt; p.vel = dir*p.speed; p.target = target; p.ReactionTime += Random[Mister](-10,10); if ( candidates.Size() > 0 ) { p.seektarget = candidates[k]; k = (k+1)%candidates.Size(); } } Spawn("MisterExLightBig",pos); Spawn("MisterRing",pos); } virtual void A_GrenadeSubExplode() { if ( special2 && (special2 <= 20) ) { SWWMUtility.DoExplosion(self,44,5000+special2*500,100+special2*10,100+special2*10,DE_EXTRAZTHRUST); int numpt = Random[ExploS](special2/2,special2); for ( int i=0; i0)); if ( !seektarget || (seektarget.Health < 0) ) return; // check proximity to seek target if ( SWWMUtility.SphereIntersect(seektarget,level.Vec3Offset(pos,vel),80) ) { special1++; tracer = seektarget; return; } if ( !CheckSight(seektarget,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) return; // sustain reaction time as long as seek target is visible ReactionTime++; // "subtly" steer towards seek target Vector3 dirto = level.Vec3Diff(pos,seektarget.Vec3Offset(0,0,seektarget.Height/2)); double distto = dirto.length(); if ( distto <= 0. ) return; dirto /= distto; double spd = vel.length(); if ( spd <= 0. ) return; vel /= spd; vel = (vel*.7+dirto*.3)*spd; // extra oomph if ( spd < speed ) vel += dirto*max(.5,speed-spd); } override void A_GrenadeExplode() { ReactionTime = 0; bForceXYBillboard = true; bRollSprite = false; A_SetRenderStyle(1.0,STYLE_Add); A_SprayDecal("RocketBlast",50); A_SetScale(2.); A_NoGravity(); Scale *= FRandom[ExploS](0.8,1.1); Scale.x *= RandomPick[ExploS](-1,1); Scale.y *= RandomPick[ExploS](-1,1); SWWMUtility.DoExplosion(self,444,80000,150,150,DE_EXTRAZTHRUST); A_QuakeEx(5,5,5,10,0,500,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,falloff:150,rollintensity:.8); A_StartSound("mister/hitgrenadesub",CHAN_VOICE,attenuation:.5); A_StartSound("mister/hitgrenadesub",CHAN_WEAPON,attenuation:.4); SetOrigin(Vec3Offset(0,0,Height/2),false); int numpt = Random[ExploS](15,25); for ( int i=0; i