// Quadravol projectiles and effects Class QuadravolCasing : SWWMCasing { Default { BounceSound "quadshot/casing"; } override void PostBeginPlay() { Super.PostBeginPlay(); heat = 0; } } Class QuadExplLight : PaletteLight { Default { Tag "QuadExpl"; Args 0,0,0,150; ReactionTime 20; } } Class QuadExplLight2 : PaletteLight { Default { Tag "QuadExpl"; Args 0,0,0,250; ReactionTime 25; } } Class QuadExplLight3 : PaletteLight { Default { Tag "QuadExpl"; Args 0,0,0,300; ReactionTime 30; } } Class QuadFlare : SWWMNonInteractiveActor { bool bBig; Default { RenderStyle "Add"; Alpha .5; +FORCEXYBILLBOARD; } override void Tick() { prev = pos; if ( freezetics > 0 ) { freezetics--; return; } if ( isFrozen() ) return; if ( !master || !master.bMISSILE ) { if ( !bBig ) { bBig = true; Scale *= 3.; } Scale *= .97; A_FadeOut(.03); return; } SetOrigin(master.pos,true); } States { Spawn: QFLR A -1 Bright; Stop; } } Class QuadTrail : SWWMNonInteractiveActor { Mixin SWWMMinimalMovingTick; Default { RenderStyle "Add"; Alpha .5; Scale 1.5; +FORCEXYBILLBOARD; +ROLLSPRITE; +ROLLCENTER; } override void PostBeginPlay() { Super.PostBeginPlay(); Scale.x *= RandomPick[ExploS](-1,1); Scale.y *= RandomPick[ExploS](-1,1); roll = FRandom[ExploS](0,360); SetState(SpawnState+Random[ExploS](0,19)); } void A_Flame() { vel *= .9; A_SetScale(scale.x*.96); A_FadeOut(.05); } States { Spawn: XFIR ABCDEFGHIJKLMNOPQRST 1 Bright A_Flame(); Loop; } } Class QuadEmber : SWWMNonInteractiveActor { Vector3 freq, amp, ph; Default { RenderStyle "Add"; Scale .3; +FORCEXYBILLBOARD; } override void PostBeginPlay() { Scale *= FRandom[ExploS](.5,1.5); double ang = FRandom[ExploS](0,360), pt = FRandom[ExploS](-90,90); vel = SWWMUtility.Vec3FromAngles(ang,pt)*FRandom[ExploS](4.,8.); freq = (FRandom[ExploS](.5,3.),FRandom[ExploS](.5,3.),FRandom[ExploS](.5,3.)); amp = (FRandom[ExploS](.5,2.),FRandom[ExploS](.5,2.),FRandom[ExploS](.5,2.)); ph = (FRandom[ExploS](0.,360.),FRandom[ExploS](0.,360.),FRandom[ExploS](0.,360.)); specialf1 = FRandom[ExploS](.98,.99); specialf2 = FRandom[ExploS](.98,.99); } override void Tick() { prev = pos; if ( freezetics > 0 ) { freezetics--; return; } if ( isFrozen() ) return; if ( !InStateSequence(CurState,FindState("Death")) ) { vel *= .95; vel += .2*(sin(ph.x)*amp.x,sin(ph.y)*amp.y,sin(ph.z)*amp.z); vel.z -= .05*clamp(2.-amp.length(),0.,2.); ph.x += freq.x*(360./GameTicRate); ph.y += freq.y*(360./GameTicRate); ph.z += freq.z*(360./GameTicRate); freq *= specialf1; amp *= specialf2; Vector3 newpos = level.Vec3Offset(pos,vel); if ( !level.IsPointInLevel(newpos) ) { vel *= 0; SetStateLabel("Death"); return; } SetOrigin(newpos,true); UpdateWaterLevel(); if ( waterlevel > 0 ) { vel *= 0; SetStateLabel("Death"); return; } A_FadeOut(FRandom[ExploS](.002,.005)); } else A_FadeOut(FRandom[ExploS](.02,.05)); frame = clamp(int(round(4-alpha*4)),0,4); } States { Spawn: QEMB # -1 Bright; Stop; Death: QEMB # -1 Bright; Stop; } } Class QuadExplRing : SWWMNonInteractiveActor { Default { RenderStyle "Add"; Scale 1.2; +FORCEXYBILLBOARD; } States { Spawn: XRG8 ACEGIKMOQSUW 1 Bright A_SetScale(scale.x*1.05); Stop; } } Class QuadProj : Actor { meta double drift, accelrate, maxspeed; Property Drift: drift; Property AccelRate: accelrate; Property MaxSpeed: maxspeed; override void PostBeginPlay() { Super.PostBeginPlay(); A_StartSound("quadshot/fly",CHAN_BODY,CHANF_LOOP); let t = Spawn("QuadFlare",pos); t.master = self; t.scale = scale*2.; } // acceleration, drift and speed limit void A_QuadMove() { A_QuadTrail(); let [x, y, z] = SWWMUtility.GetAxes(angle,pitch,roll); double a = FRandom[Quadravol](0.,360.), s = FRandom[Quadravol](0.,drift); vel += x*(accelrate/GameTicRate); vel += SWWMUtility.CircleOffset(y,z,a,s); double magvel = vel.length(); Vector3 dir = (magvel<=0.)?x:(vel/magvel); if ( magvel > maxspeed ) magvel = maxspeed; vel = dir*magvel; speed = magvel; } virtual void A_QuadTrail() { let s = Spawn("QuadTrail",pos); s.scale *= .6; s.vel = vel*.2; s = Spawn("SWWMHalfSmoke",pos); s.vel = SWWMUtility.Vec3FromAngles(FRandom[Quadravol](0,360),FRandom[Quadravol](-90,90))*.4; s.vel += vel*.3; s.alpha *= .4; int numpt = Random[Quadravol](5,15); for ( int i=0; i 0 ) cnt--; else { cnt = 10; if ( Owner.bSHOOTABLE && (Owner.Health > 0) && (amount > 0) ) { int flg = DMG_THRUSTLESS; if ( Owner is 'Centaur' ) flg |= DMG_FOILINVUL; // you're on fire, that shield is worthless Owner.DamageMobj(self,instigator,clamp(int(amount*.15),1,50),'Fire',flg); if ( Owner && Owner.bISMONSTER && !Random[FlameT](0,3) ) Owner.Howl(); } if ( !Owner ) { Destroy(); return; } // damage nearby actors if ( amount > 0 ) SWWMUtility.DoExplosion(Owner,amount,0,Owner.radius+40+amount/5,Owner.radius+20,DE_NOBLEED|DE_NOSPLASH|DE_HOWL|DE_CENTERHEIGHT|DE_NONEXPLOSIVE,'Fire',null,DMG_THRUSTLESS,instigator,self); // in rare cases the owner may stop existing after the DoExplosion call if ( !Owner ) { Destroy(); return; } } double mult = max(Owner.radius,Owner.height)/30.; if ( lite ) lite.A_SoundVolume(CHAN_VOICE,min(1.,mult*amount/80.)); if ( level.maptime%5 ) return; int numpt = clamp(int(Random[FlameT](2,4)*amount*.02),1,4); numpt = int(clamp(numpt*mult**.5,1,5)); for ( int i=0; i 0 ) { let c = Spawn("OnFireTrail",tpos); c.scale *= max(.35,mult*.6); c.vel = Owner.vel*.5+SWWMUtility.Vec3FromAngles(ang,pt)*FRandom[FlameT](.5,2.)*c.scale.x; } if ( Random[FlameT](0,3) ) continue; let s = Spawn("SWWMHalfSmoke",tpos); s.scale *= max(.35,mult*.5); s.alpha *= min(amount+30,100)*.005; s.vel = Owner.vel*.5+SWWMUtility.Vec3FromAngles(ang,pt)*FRandom[FlameT](.2,.6)*s.scale.x; } } static OnFire Apply( Actor victim, Actor instigator, int amount ) { let t = OnFire(victim.FindInventory("OnFire")); if ( t ) { t.instigator = instigator; t.amount = min(t.maxamount,t.amount+amount); t.cnt = min(t.cnt,5); return t; } t = OnFire(Spawn("ONFire")); t.AttachToOwner(victim); t.instigator = instigator; t.amount = min(t.maxamount,amount); t.cnt = 1; t.oangle = victim.angle; double mult = max(victim.radius,victim.height)/30.; t.lite.A_StartSound("misc/flame",CHAN_VOICE,CHANF_LOOP); t.lite.A_SoundVolume(CHAN_VOICE,min(1.,mult*amount/80.)); return t; } static clearscope OnFire IsOnFire( Actor victim ) { let t = OnFire(victim.FindInventory("OnFire")); if ( t && (t.amount > 0) ) return t; return null; } } Class OnFireTrail : SWWMNonInteractiveActor { Mixin SWWMMinimalMovingWaterTick; override void PostBeginPlay() { Super.PostBeginPlay(); Scale.x *= RandomPick[ExploS](-1,1); Scale.y *= RandomPick[ExploS](-1,1); roll = FRandom[ExploS](0,360); SetState(SpawnState+Random[ExploS](0,39)); } void A_Flame() { special1++; if ( waterlevel > 0 ) vel *= .9; else { vel *= .98; vel.z += .1+.2*abs(scale.x); } if ( !Random[FlameT](0,int(40*(default.alpha-alpha))) ) { let s = Spawn("SWWMHalfSmoke",pos); s.vel = SWWMUtility.Vec3FromAngles(FRandom[FlameT](0,360),FRandom[FlameT](-90,90))*.2; s.vel += vel*.3; s.alpha *= alpha*.5; s.scale *= .5+abs(scale.x*2)*(.5+special1/6.); s.special1 = 1; } A_SetScale(scale.x*.98,scale.y*.98); A_FadeOut(.01); } Default { RenderStyle "Add"; Speed 2; Alpha .3; Scale .6; +FORCEXYBILLBOARD; +ROLLSPRITE; +ROLLCENTER; } States { Spawn: XFIR AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTT 1 Bright A_Flame(); Loop; } }