// VIP powerups Class TendrilTracer : LineTracer { Actor ignore; Array ShootThroughList; Array HitList; override ETraceStatus TraceCallback() { if ( Results.HitType == TRACE_HitActor ) { if ( Results.HitActor == ignore ) 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 ) { ShootThroughList.Push(Results.HitLine); return TRACE_Skip; } return TRACE_Skip; } } // main heatseeker Class MykradvoTendril : SWWMNonInteractiveActor { Vector3 nextpos, nextdir; void A_Trace() { tics = bMISSILEMORE?2:1; Vector3 x, y, z; [x, y, z] = SWWMUtility.GetAxes(angle,pitch,roll); if ( !bSTANDSTILL ) { let t = new("TendrilTracer"); t.ignore = target; t.hitlist.Clear(); t.ShootThroughList.Clear(); t.Trace(pos,CurSector,x,speed,0); foreach ( l:t.ShootThroughList ) { l.Activate(target,0,SPAC_PCross); l.Activate(target,0,SPAC_Impact); } foreach ( hit:t.hitlist ) { if ( hit.hitactor.IsFriend(target) ) continue; if ( (hit.hitactor == tracer) && bMISSILEMORE ) bMISSILEEVENMORE = true; // we split int dmg = (hit.hitactor.bBOSS||hit.hitactor.FindInventory("BossMarker"))?(GetMissileDamage(0,0)*4):max(hit.hitactor.Health,GetMissileDamage(0,0)); SWWMUtility.DoKnockback(hit.hitactor,-hit.x+(0,0,.5),((hit.hitactor.Health-dmg)<=0)?60000:8000); let p = SWWMPuff.Setup(hit.hitlocation,hit.x,self,target,hit.hitactor); hit.hitactor.DamageMobj(p,target,dmg,'Plasma',DMG_THRUSTLESS|DMG_INFLICTOR_IS_PUFF); if ( hit.hitactor && hit.hitactor.bISMONSTER && !Random[Mykradvo](0,3) ) hit.hitactor.Howl(); } } nextpos = level.Vec3Offset(pos,x*speed); if ( !bSTANDSTILL && (!tracer || !tracer.bSHOOTABLE || (tracer.Health <= 0) || ((tracer.bBOSS || tracer.FindInventory("BossMarker")) && !bMISSILEMORE)) ) { ReactionTime--; if ( ReactionTime <= 0 ) { bAMBUSH = true; return; } } double a = FRandom[Mykradvo](0,360), s = FRandom[Mykradvo](0.,bSTANDSTILL?3.:bMISSILEMORE?.75:1.5); Vector3 dir = SWWMUtility.ConeSpread(x,y,z,a,s); if ( tracer ) { Vector3 destofs = bMISSILEMORE?tracer.Vec3Offset(0,0,tracer.Height/2.):tracer.Vec3Offset(FRandom[Mykradvo](-1.2,1.2)*tracer.Radius,FRandom[Mykradvo](-1.2,1.2)*tracer.Radius,FRandom[Mykradvo](-.1,1.1)*tracer.height); Vector3 dirto = level.Vec3Diff(nextpos,destofs); double dist = dirto.length(); if ( dist > 1 ) { dirto /= dist; dir = (dir+dirto*(clamp(1.-(dist/4000.),.25,1.)**1.5)).unit(); } // early split if ( dist < speed ) bMISSILEEVENMORE = true; } nextdir = dir; } void A_Spread() { if ( bMISSILEMORE && bMISSILEEVENMORE ) { // spread into sub-tendrils Vector3 x, y, z; [x, y, z] = SWWMUtility.GetAxes(angle,pitch,roll); int ntendies = tracer?clamp(tracer.GetSpawnHealth()/400,2,10):2; for ( int i=0; i ReactionTime)) ) { if ( !bSTANDSTILL ) { int numpt = bMISSILEMORE?9:3; Vector3 x, y, z; [x, y, z] = SWWMUtility.GetAxes(angle,pitch,roll); for ( int i=0; i 0 ) { freezetics--; return; } if ( isFrozen() ) return; A_FadeOut(bMISSILEMORE?.05:bSTANDSTILL?.2:.1); if ( !CheckNoDelay() || (tics == -1) ) return; if ( tics > 0 ) tics--; while ( !tics ) { if ( !SetState(CurState.NextState) ) return; } } Default { Obituary "$O_MYKRADVO"; RenderStyle "Add"; DamageFunction 100; ReactionTime 8; Speed 64; +INTERPOLATEANGLES; +FOILINVUL; +MISSILEMORE; } States { Spawn: TNT1 A 0 Bright; XZW1 A 1 Bright A_Trace(); XZW1 A 1 Bright { A_Spread(); return FindState("Fade")+Random[Mykradvo](0,11)*2; } Stop; Fade: #### # 20 Bright; XZW1 B -1 Bright; Stop; #### # 20 Bright; XZW1 C -1 Bright; Stop; #### # 20 Bright; XZW1 D -1 Bright; Stop; #### # 20 Bright; XZW1 E -1 Bright; Stop; #### # 20 Bright; XZW1 F -1 Bright; Stop; #### # 20 Bright; XZW1 G -1 Bright; Stop; #### # 20 Bright; XZW1 H -1 Bright; Stop; #### # 20 Bright; XZW1 I -1 Bright; Stop; #### # 20 Bright; XZW1 J -1 Bright; Stop; #### # 20 Bright; XZW1 K -1 Bright; Stop; #### # 20 Bright; XZW1 L -1 Bright; Stop; #### # 20 Bright; XZW1 M -1 Bright; Stop; } } // sub seekers Class MykradvoSmallTendril : MykradvoTendril { Default { Speed 16; DamageFunction 10; ReactionTime 20; -MISSILEMORE; } States { Fade: #### # 10 Bright; XZW1 B -1 Bright; Stop; #### # 10 Bright; XZW1 C -1 Bright; Stop; #### # 10 Bright; XZW1 D -1 Bright; Stop; #### # 10 Bright; XZW1 E -1 Bright; Stop; #### # 10 Bright; XZW1 F -1 Bright; Stop; #### # 10 Bright; XZW1 G -1 Bright; Stop; #### # 10 Bright; XZW1 H -1 Bright; Stop; #### # 10 Bright; XZW1 I -1 Bright; Stop; #### # 10 Bright; XZW1 J -1 Bright; Stop; #### # 10 Bright; XZW1 K -1 Bright; Stop; #### # 10 Bright; XZW1 L -1 Bright; Stop; #### # 10 Bright; XZW1 M -1 Bright; Stop; } } // non-hurting non-seekers Class MykradvoSmallNullTendril : MykradvoSmallTendril { Default { Speed 8; ReactionTime 6; +STANDSTILL; } States { Fade: #### # 5 Bright; XZW1 B -1 Bright; Stop; #### # 5 Bright; XZW1 C -1 Bright; Stop; #### # 5 Bright; XZW1 D -1 Bright; Stop; #### # 5 Bright; XZW1 E -1 Bright; Stop; #### # 5 Bright; XZW1 F -1 Bright; Stop; #### # 5 Bright; XZW1 G -1 Bright; Stop; #### # 5 Bright; XZW1 H -1 Bright; Stop; #### # 5 Bright; XZW1 I -1 Bright; Stop; #### # 5 Bright; XZW1 J -1 Bright; Stop; #### # 5 Bright; XZW1 K -1 Bright; Stop; #### # 5 Bright; XZW1 L -1 Bright; Stop; #### # 5 Bright; XZW1 M -1 Bright; Stop; } } Class MykradvoBurstLight : PaletteLight { Default { Tag "Purple"; ReactionTime 60; Args 0,0,0,400; } } // 'splode Class MykradvoBurst : SWWMNonInteractiveActor { Array targets; int nstep; Default { RenderStyle "Add"; +FORCEXYBILLBOARD; Scale 1.4; } void FlashPlayer( int str, double rad ) { if ( !SWWMUtility.InPlayerFOV(players[consoleplayer],self,rad) ) return; let mo = players[consoleplayer].Camera; double dist = Distance3D(mo); str = int(str*(1.-(dist/rad))); SWWMHandler.DoFlash(mo,Color(str,250,240,255),5); SWWMHandler.DoFlash(mo,Color(str,128,0,255),15); } override void PostBeginPlay() { nstep = clamp(targets.Size()/10,1,5); A_AlertMonsters(swwm_uncapalert?0:8000); A_QuakeEx(9,9,9,80,0,3000,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,falloff:1000,rollintensity:2.); A_StartSound("powerup/mykradvo",CHAN_BODY,CHANF_DEFAULT,1.,.25); A_StartSound("powerup/mykradvo",CHAN_VOICE,CHANF_DEFAULT,1.,.25); FlashPlayer(100,1500); int numpt = Random[ExploS](20,30); for ( int i=0; i 0 ) { freezetics--; return; } if ( isFrozen() ) return; if ( (special1++)%3 ) { if ( targets.Size() > 0 ) { int numpt = Random[ExploS](2,4); for ( int j=0; j 0) ) { let t = Spawn("MykradvoTendril",pos); t.angle = t.AngleTo(targ); t.pitch = t.PitchTo(targ,0,targ.Height/2); t.target = target; t.tracer = targ; } else i--; targets.Delete(0); } } if ( !CheckNoDelay() || (tics == -1) ) return; if ( tics > 0 ) tics--; while ( !tics ) { if ( !SetState(CurState.NextState) ) return; } } States { Spawn: XEX4 ABCDEFGHIJKLMNOPQRSTUVWXYZ[\ 2 Bright; TNT1 A 1 A_JumpIf(!IsActorPlayingSound(CHAN_VOICE,-1)&&!(targets.Size()),1); Wait; TNT1 A 1; Stop; } } Class Mykradvo : Inventory { Mixin SWWMAutoUseFix; Mixin SWWMOverlapPickupSound; Mixin SWWMUseToPickup; Mixin SWWMRespawn; Mixin SWWMPickupGlow; Actor ringa[2]; Array targets; // quicksort (targets) private bool CmpDist( Actor ref, Vector3 a, Vector3 b ) { double dista = level.Vec3Diff(ref.pos,a).length(); double distb = level.Vec3Diff(ref.pos,b).length(); return (dista < distb); } private int partition_targets( Array a, int l, int h, Actor ref ) { Actor pv = a[h]; int i = (l-1); for ( int j=l; j<=(h-1); j++ ) { if ( CmpDist(ref,a[j].pos,pv.pos) ) { 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_targets( Array a, int l, int h, Actor ref ) { if ( l >= h ) return; int p = partition_targets(a,l,h,ref); qsort_targets(a,l,p-1,ref); qsort_targets(a,p+1,h,ref); } bool FindTargets( Actor t ) { targets.Clear(); // search all actively hostile enemies within 50m let ti = ThinkerIterator.Create("Actor"); Actor a; while ( a=Actor(ti.Next()) ) { // must be an active, shootable live monster if ( !a.bISMONSTER || !a.bSHOOTABLE || a.bDORMANT || (a.Health <= 0) ) continue; // skip non-hostiles if ( a.IsFriend(t) ) continue; // is targetting us and is within 10m // or // is visible and is within 100m if ( ((a.target == t) && SWWMUtility.SphereIntersect(a,t.pos,320)) || (t.CheckSight(a,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) && SWWMUtility.SphereIntersect(a,t.pos,3200)) ) targets.Push(a); } // sorted, so the closest take priority qsort_targets(targets,0,targets.Size()-1,t); return (targets.Size() > 0); } override bool Use( bool pickup ) { if ( pickup && !deathmatch ) return false; if ( pickup && ((Owner.player == players[consoleplayer]) || bBigPowerup) ) Owner.A_StartSound(UseSound,CHAN_ITEMEXTRA,CHANF_OVERLAP); Vector3 spawnpos = Owner.Vec3Angle(15,Owner.angle,Owner.Height*.7); if ( !FindTargets(Owner) ) { int numpt = Random[ExploS](8,12); let f = Spawn("SWWMPurplePickupFlash",spawnpos-(0,0,16)); f.Scale *= .5; for ( int i=0; i 44444444 ) newdamage = 444444444; else newdamage = damage*10+4; } else { // (2^31-1)/25 : guarantee that it caps rather than overflowing if ( damage > 85899345 ) newdamage = int.max; else newdamage = damage*25; } // don't play hit fx for wall busting, as it'll be done manually if the bust goes through if ( damageType != 'Wallbust' ) DoHitFX(); } } Class AngerySigil : Inventory { Mixin SWWMAutoUseFix; Mixin SWWMOverlapPickupSound; Mixin SWWMUseToPickup; Mixin SWWMRespawn; Mixin SWWMPickupGlow; override bool Use( bool pickup ) { if ( pickup && !deathmatch ) return false; if ( pickup && ((Owner.player == players[consoleplayer]) || bBigPowerup) ) Owner.A_StartSound(UseSound,CHAN_ITEMEXTRA,CHANF_OVERLAP); SWWMUtility.AchievementProgressInc("deva",1,Owner.player); let r = AngeryPower(Owner.FindInventory("AngeryPower")); if ( r ) { r.EffectTics += r.default.EffectTics; SWWMHandler.DoFlash(Owner,Color(64,224,0,255),30); Owner.A_QuakeEx(8,8,8,20,0,1,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,rollIntensity:1.); if ( Owner is 'Demolitionist' ) Demolitionist(Owner).lastbump *= .95; } else Owner.GiveInventory("AngeryPower",1); return true; } override void PreTravelled() { if ( tracer ) tracer.Destroy(); } override void Travelled() { Super.Travelled(); if ( tracer ) return; tracer = Spawn("AngerySigilX",pos); tracer.angle = angle; tracer.target = self; tracer.FloatBobPhase = FloatBobPhase; } override void PostBeginPlay() { Super.PostBeginPlay(); tracer = Spawn("AngerySigilX",pos); tracer.angle = angle; tracer.target = self; tracer.FloatBobPhase = FloatBobPhase; } Default { Tag "$T_DEVASTATION"; Stamina -1500000; Inventory.Icon "graphics/HUD/Icons/I_Devastation.png"; Inventory.PickupSound "misc/p_pkup_vip"; Inventory.UseSound "powerup/devastation"; Inventory.PickupMessage "$T_DEVASTATION"; Inventory.MaxAmount 3; Inventory.InterHubAmount 3; Inventory.PickupFlash "SWWMPurplePickupFlash"; +INVENTORY.ALWAYSPICKUP; +INVENTORY.AUTOACTIVATE; +INVENTORY.INVBAR; +COUNTITEM; +INVENTORY.BIGPOWERUP; +FLOATBOB; +DONTGIB; FloatBobStrength 0.25; } States { Spawn: XZW1 A -1; Stop; } } Class AngerySigilX : SWWMItemOverlay { Default { Scale .5; Alpha .35; RenderStyle "Subtract"; +FORCEXYBILLBOARD; } override void PostBeginPlay() { Super.PostBeginPlay(); A_StartSound("powerup/devastationamb",CHAN_VOICE,CHANF_LOOP,attenuation:2.); WorldOffset = (0,0,20); } override void Tick() { if ( !target ) { Destroy(); return; } prev = target.prev; if ( (target.pos != pos) || (target.vel != (0,0,0)) ) SetOrigin(target.pos+vel,true); if ( angle != target.angle ) A_SetAngle(target.angle,SPF_INTERPOLATE); FloatBobPhase = target.FloatBobPhase; bool bOldInvis = bInvisible; bInvisible = target.bInvisible||Inventory(target).Owner; if ( bInvisible != bOldInvis ) { SetState(SpawnState+bInvisible); A_SoundVolume(CHAN_VOICE,bInvisible?0.:1.); } } States { Spawn: BLPS C -1 Bright; TNT1 A -1; Stop; } } Class DivineSpriteLight : PointLightAttenuated { Default { Args 255,255,255,100; } override void Tick() { Super.Tick(); if ( !target || !master ) { Destroy(); return; } if ( target.player ) SetOrigin(SWWMUtility.GetFireOffset(target,10,0,0),true); else SetOrigin(target.Vec3Angle(10,target.angle,target.missileheight),true); double vol = clamp((target.Health-1000)/6000.,0.,1.); int lv = clamp(int(vol*255),0,255); args[LIGHT_RED] = lv; args[LIGHT_GREEN] = lv; args[LIGHT_BLUE] = lv; args[LIGHT_INTENSITY] = Random[Invinciball](10,12)*10; } } Class DivineSpriteSnd : SWWMNonInteractiveActor { override void Tick() { if ( !target || !master ) { Destroy(); return; } SetOrigin(target.pos,true); double vol = clamp((DivineSpriteEffect(master).AlphInter.GetValue()-1000.)/9000.,0.,1.); if ( players[consoleplayer].Camera == target ) { A_SoundVolume(CHAN_VOICE,0.); A_SoundVolume(CHAN_7,.8*vol); } else { A_SoundVolume(CHAN_VOICE,.4*vol); A_SoundVolume(CHAN_7,0.); } } override void PostBeginPlay() { Super.PostBeginPlay(); A_StartSound("powerup/divineact",CHAN_VOICE,CHANF_LOOP,.4,1.5); A_StartSound("powerup/divineact",CHAN_7,CHANF_LOOP,.8,ATTN_NONE); } override void OnDestroy() { Super.OnDestroy(); A_StopSound(CHAN_VOICE); A_StopSound(CHAN_7); } } Class DivineSpriteEffect : Inventory { int healcnt; int healtim; bool bHealDone; Actor l, snd; DynamicValueInterpolator AlphInter; FSpawnParticleParams flare; Property HealTimer : healtim; default { Inventory.Icon "graphics/HUD/Icons/I_Divine.png"; DivineSpriteEffect.HealTimer 1750; +INVENTORY.UNDROPPABLE; +INVENTORY.UNTOSSABLE; } clearscope bool isBlinking() const { return ( (healtim <= BLINKTHRESHOLD) && (healtim&8) ); } override Color GetBlend() { if ( swwm_shaders ) return 0; if ( !AlphInter ) AlphInter = DynamicValueInterpolator.Create(Owner.Health,.1,1,100); double alph = clamp((AlphInter.GetValue()-1000.)/6000.,0.,1.); return Color(int(64*alph),255,255,255); } override void Travelled() { Super.Travelled(); bHealDone = true; } override void DoEffect() { Super.DoEffect(); if ( !l ) l = Spawn("DivineSpriteLight",Owner.pos); l.target = Owner; l.master = self; if ( !snd ) snd = Spawn("DivineSpriteSnd",Owner.pos); snd.target = Owner; snd.master = self; int numpt = Random[ExploS](5,10); if ( !AlphInter ) AlphInter = DynamicValueInterpolator.Create(Owner.Health,.1,1,100); AlphInter.Update(Owner.Health); double alph = clamp((AlphInter.GetValue()-1000.)/6000.,0.,1.); double scl = clamp((AlphInter.GetValue()-1000.)/6000.,2.,4.); if ( !flare.texture ) { flare.color1 = "White"; flare.texture = TexMan.CheckForTexture("graphics/Particles/xflare.png"); flare.style = STYLE_AddShaded; flare.flags = SPF_FULLBRIGHT; flare.lifetime = 30; flare.fadestep = -1; flare.accel = (0,0,.05); } flare.size = scl; flare.sizestep = -scl/30.; flare.startalpha = alph; for ( int i=0; i