From 1fe50b5c32375cfbbc1502a968be5263a7219a20 Mon Sep 17 00:00:00 2001 From: Marisa the Magician Date: Thu, 15 Dec 2022 14:31:43 +0100 Subject: [PATCH] Split item code. --- language.version | 4 +- zscript.txt | 4 + zscript/items/swwm_lamp.zsc | 682 ++++ zscript/items/swwm_miscitems.zsc | 840 +++++ zscript/items/swwm_powerups.zsc | 2806 ----------------- zscript/items/swwm_powerups_vip.zsc | 1289 ++++++++ .../weapons/swwm_deathlydeathcannon_altfx.zsc | 1586 ++++++++++ .../weapons/swwm_deathlydeathcannon_fx.zsc | 1585 ---------- 8 files changed, 4403 insertions(+), 4393 deletions(-) create mode 100644 zscript/items/swwm_lamp.zsc create mode 100644 zscript/items/swwm_miscitems.zsc create mode 100644 zscript/items/swwm_powerups_vip.zsc create mode 100644 zscript/weapons/swwm_deathlydeathcannon_altfx.zsc diff --git a/language.version b/language.version index 1f0d0af3d..b46af7f34 100644 --- a/language.version +++ b/language.version @@ -1,3 +1,3 @@ [default] -SWWM_MODVER="\cyDEMOLITIONIST \cw1.3pre r692 \cu(Thu 15 Dec 14:07:15 CET 2022)\c-"; -SWWM_SHORTVER="\cw1.3pre r692 \cu(2022-12-15 14:07:15)\c-"; +SWWM_MODVER="\cyDEMOLITIONIST \cw1.3pre r693 \cu(Thu 15 Dec 14:31:43 CET 2022)\c-"; +SWWM_SHORTVER="\cw1.3pre r693 \cu(2022-12-15 14:31:43)\c-"; diff --git a/zscript.txt b/zscript.txt index e1f03661e..28c76e466 100644 --- a/zscript.txt +++ b/zscript.txt @@ -95,7 +95,10 @@ version "4.10" #include "zscript/items/swwm_health.zsc" #include "zscript/items/swwm_armor.zsc" #include "zscript/items/swwm_armor_filtered.zsc" // Hexen fuckery +#include "zscript/items/swwm_miscitems.zsc" +#include "zscript/items/swwm_lamp.zsc" #include "zscript/items/swwm_powerups.zsc" +#include "zscript/items/swwm_powerups_vip.zsc" #include "zscript/items/swwm_ammoitems.zsc" #include "zscript/items/swwm_ammoextra.zsc" #include "zscript/items/swwm_ammospawn.zsc" @@ -129,6 +132,7 @@ version "4.10" #include "zscript/weapons/swwm_tastytreat_fx.zsc" #include "zscript/weapons/swwm_deathlydeathcannon.zsc" #include "zscript/weapons/swwm_deathlydeathcannon_fx.zsc" +#include "zscript/weapons/swwm_deathlydeathcannon_altfx.zsc" // split due to size #include "zscript/weapons/swwm_weapons_hud.zsc" #include "zscript/weapons/swwm_weapons_canvas.zsc" // monsters diff --git a/zscript/items/swwm_lamp.zsc b/zscript/items/swwm_lamp.zsc new file mode 100644 index 000000000..dba37f2ee --- /dev/null +++ b/zscript/items/swwm_lamp.zsc @@ -0,0 +1,682 @@ +// Lämp + +Class LampMoth : Actor +{ + Actor lamp; + Vector3 trail, ofs; + int lifespan; + + Default + { + Tag "$T_MOTH"; + Radius 2; + Height 4; + Speed 2; + DamageFunction 1; + MeleeRange 16; + Mass 10; + Health 50; + DeathSound "moth/die"; + BloodColor "20 10 10"; + MONSTER; + -COUNTKILL; + +THRUACTORS; + +NOGRAVITY; + +NOTELEPORT; + +FLOAT; + +NOPAIN; + +FRIENDLY; + +LOOKALLAROUND; + +QUICKTORETALIATE; + +INTERPOLATEANGLES; + +NOBLOCKMONST; + } + override string GetObituary( Actor victim, Actor inflictor, Name mod, bool playerattack ) + { + if ( inflictor && (inflictor != self) ) + { + if ( inflictor == master ) return StringTable.Localize("$O_MOTHSELF"); // not likely to happen + else return StringTable.Localize("$O_MOTH"); + } + return StringTable.Localize("$O_MOTH2"); + } + override void PostBeginPlay() + { + Super.PostBeginPlay(); + A_StartSound("moth/fly",CHAN_BODY,CHANF_LOOP,.02,4.,FRandom[Moth](.8,1.2)); + if ( master && master.player ) SetFriendPlayer(master.player); + else bFRIENDLY = false; + } + override int DamageMobj( Actor inflictor, Actor source, int damage, Name mod, int flags, double angle ) + { + // no hurt moff + if ( source && IsFriend(source) ) damage = 0; + return Super.DamageMobj(inflictor,source,damage,mod,flags,angle); + } + bool isEntranced() + { + if ( !lamp ) + { + // look for nearby lamps + let bi = BlockThingsIterator.Create(self,250); + double mindist = 62500.; + while ( bi.Next() ) + { + if ( !bi.Thing || !(bi.Thing is 'CompanionLamp') ) continue; + Actor a = bi.Thing; + double dist = Distance3DSquared(a); + if ( (a.frame == 0) || (dist > mindist) && !CheckSight(a,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) continue; + mindist = dist; + lamp = a; + master = a.target; + if ( CompanionLamp(lamp).moff.Find(self) == CompanionLamp(lamp).moff.Size() ) + CompanionLamp(lamp).moff.Push(self); + if ( master && master.player ) SetFriendPlayer(master.player); + else bFRIENDLY = false; + } + } + if ( !lamp || (lamp.frame == 0) || (Distance3DSquared(lamp) > 62500) || !CheckSight(lamp,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) return false; + if ( target && (target.Health > 0) && CheckSight(target) ) return false; + return true; + } + void A_SmoothWander() + { + if ( level.Vec3Diff(pos,trail).length() < speed ) + { + double ang = FRandom[Moth](0,360); + double pt = FRandom[Moth](-30,30); + double dist = FRandom[Moth](20,40); + ofs = SWWMUtility.Vec3FromAngles(ang,pt)*dist; + } + Vector3 newpos = level.Vec3Offset(pos,ofs); + if ( level.IsPointInLevel(newpos) ) trail = newpos; + if ( vel.length() > 0 ) + { + Vector3 uvel = vel.unit(); + angle += Clamp(deltaangle(angle,atan2(uvel.y,uvel.x)),-5.,5.); + pitch += Clamp(deltaangle(pitch,asin(-uvel.z)),-5.,5.); + } + vel *= .8; + Vector3 dir = level.Vec3Diff(pos,trail); + if ( dir.length() > 0 ) vel += dir.unit()*clamp(dir.length()*.05,.4*speed,.5*speed); + } + void A_SmoothChase() + { + if ( !target || (target.Health <= 0) ) + { + A_ClearTarget(); + SetStateLabel("Spawn"); + return; + } + if ( CheckMeleeRange() ) + { + SetStateLabel("Melee"); + return; + } + Vector3 dest = target.Vec3Offset(0,0,target.height*.75); + Vector3 dir = level.Vec3Diff(pos,dest); + if ( dir.length() > 0 ) + { + Vector3 dirunit = dir.unit(); + FLineTraceData d; + LineTrace(atan2(dirunit.y,dirunit.x),dir.length(),asin(-dirunit.z),data:d); + if ( (d.HitType != TRACE_HitActor) && (d.HitActor != target) ) + { + A_Chase(); + return; + } + } + if ( vel.length() > 0 ) + { + Vector3 uvel = vel.unit(); + angle = atan2(uvel.y,uvel.x); + pitch = asin(-uvel.z); + } + vel *= .8; + if ( dir.length() > 0 ) vel += dir.unit()*clamp(dir.length()*.02,.3*speed,2.*speed); + } + void A_FollowLamp() + { + if ( !lamp ) + { + SetStateLabel("Spawn"); + return; + } + double dst = level.Vec3Diff(pos,trail).length(); + if ( (dst < speed) || (dst > 50) ) + { + double ang = FRandom[Moth](0,360); + double pt = FRandom[Moth](-30,30); + double dist = FRandom[Moth](20,30); + ofs = SWWMUtility.Vec3FromAngles(ang,pt)*dist; + } + Vector3 newpos = level.Vec3Offset(lamp.Vec3Offset(0,0,lamp.height/2),ofs); + if ( level.IsPointInLevel(newpos) ) trail = newpos; + if ( vel.length() > 0 ) + { + Vector3 uvel = vel.unit(); + angle = atan2(uvel.y,uvel.x); + pitch = asin(-uvel.z); + } + vel *= .8; + Vector3 dir = level.Vec3Diff(pos,trail); + if ( dir.length() > 0 ) vel += dir.unit()*clamp(dir.length()*.02,.4*speed,2.*speed); + Vector3 diff = level.Vec3Diff(pos,lamp.pos); + if ( (diff.x > -8) && (diff.x < 8) && (diff.y > -8) && (diff.y < 8) && (diff.z > -4) && (diff.z < lamp.height+4) ) + { + if ( diff.x < 0 ) vel.x -= .2; + else vel.x += .2; + if ( diff.y < 0 ) vel.y -= .2; + else vel.y += .2; + if ( diff.z < 0 ) vel.z -= .2; + else vel.z += .2; + } + } + void A_SmoothMove() + { + if ( vel.length() > 0 ) + { + Vector3 uvel = vel.unit(); + angle = atan2(uvel.y,uvel.x); + pitch = asin(-uvel.z); + } + vel *= .8; + } + void A_Scrape() + { + if ( CheckMeleeRange() ) + { + A_FaceTarget(0,0); + lifespan -= 5; + Vector3 awaydir = level.Vec3Diff(target.Vec3Offset(0,0,target.height),pos).unit(); + vel += awaydir*8.; + int dmg = target.DamageMobj(self,master?master:Actor(self),GetMissileDamage(0,0),'Melee',Random[Moth](0,8)?DMG_NO_PAIN:0); + if ( (dmg > 0) && target && !target.bNOBLOOD && !target.bDORMANT && !target.bINVULNERABLE ) + { + target.TraceBleed(dmg,self); + target.SpawnBlood(pos,atan2(awaydir.y,awaydir.x)+180,dmg); + } + A_StartSound("moth/scrape",CHAN_WEAPON,CHANF_OVERLAP,.2,2.5); + DamageMobj(target,target,1,'Melee'); + } + } + override void Tick() + { + Super.Tick(); + if ( isFrozen() || (freezetics > 0) ) return; + if ( isEntranced() ) + { + lifespan = 100; + return; + } + if ( target && (target.Health > 0) ) lifespan = max(20,lifespan); + lifespan--; + if ( lifespan <= 0 ) + { + let s = Spawn("SWWMSmallSmoke",pos); + s.alpha *= .3; + Destroy(); + } + } + States + { + Spawn: + XZW1 B 0 A_JumpIf(isEntranced(),"See.Entranced"); + XZW1 BC 1 + { + A_SmoothWander(); + A_Look(); + } + Loop; + See: // go for enemies + XZW1 B 0 A_JumpIf(isEntranced(),"See.Entranced"); + XZW1 BC 1 A_SmoothChase(); + Loop; + See.Entranced: // follow the lamp + XZW1 B 0 A_JumpIf(!isEntranced(),"Spawn"); + XZW1 BC 1 A_FollowLamp(); + Loop; + Melee: + XZW1 B 0 A_Scrape(); + XZW1 BCBC 1 A_SmoothMove(); + Goto See; + Death: + TNT1 A 1 + { + A_StartSound("moth/die",CHAN_VOICE,CHANF_OVERLAP,.6,2.5); + let s = Spawn("SWWMSmallSmoke",pos); + s.alpha *= .3; + } + Stop; + } +} + +Class LampMoth2 : LampMoth +{ + Default + { + Tag "$T_WMOTH"; + DamageFunction 3; + Speed 3; + Scale 1.5; + Health 200; + } +} + +Class LampMashiro : Actor abstract +{ +// +// ~nothing here yet, but she will make an appearance someday~ +// +// ⠀⠀⠀⠀⠤⠀⠄⠀⠀⠀⠳⠀⠂⠀⠀⠀⠀⠀⠀⠀⠀⣀⣴⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⡣⣁⢌⡀⢄⠁⡁⠝⢿⣿⣿⣿⡻⣿⣿⣷⣦ +// ⠀⠀⠠⠀⠠⠀⠀⡀⠘⣠⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣊⢇⠠⢐⢄⡙⢿⣿⣿⣾⠫⣻⣿ +// ⠀⡄⠂⢴⠠⠌⠰⢇⠀⠀⠀⠀⠀⠀⠀⢀⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⡀⠣⡣⡱⣌⠻⣿⣿⣿⣿⣿ +// ⠀⠁⠛⠂⠀⠉⡍⠀⠀⠀⠀⠀⠀⠀⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧⠘⣮⣾⡮⢦⡹⣿⣿⣿⣿ +// ⠄⠠⠉⢼⡇⠶⠀⠀⠀⠀⠀⠀⢠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡆⢿⣿⣿⡦⢣⡉⢝⢝⢽ +// ⠀⠀⠚⠃⠀⠀⡀⠀⠀⠀⠀⣠⣿⣿⣿⣿⣿⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧⢸⣿⣿⣿⢀⢃⠀⡁⠑ +// ⠰⠀⠂⠁⠀⠀⡆⠀⠀⠀⣴⣿⣿⣿⣿⣿⠋⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡈⣿⣿⣿⣇⠆⢃⠈⡇ +// ⠀⠀⠀⢠⠄⣠⣇⠀⢀⣾⣿⣿⣿⣿⣿⡃⢀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⣿⣿⣿⣿⢔⢌⢆⢑ +// ⠀⠀⠀⢀⣶⣿⣷⢀⣾⣿⣿⣿⣿⡯⡊⠀⢸⡏⣟⣻⣽⣭⣽⣛⡛⠿⠿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡯⡇⣿⣿⣿⣿⣗⢕⢕⠄ +// ⠀⢠⣶⣿⣿⢿⢃⣾⣿⣿⣿⣿⡫⡪⠀⠀⢸⡇⣿⣿⣿⣿⣿⣿⣿⣦⡘⠄⢹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡯⡇⣿⣿⣿⣿⣗⢔⢕⢅ +// ⠀⣺⣿⡉⠕⠁⣼⣿⣿⣿⡿⡫⡪⠂⠀⢔⢸⢀⠸⣿⣿⣿⣿⣿⣿⣿⣿⣶⣸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣪⠃⣿⣿⣿⣿⡟⢄⣡⣵ +// ⡛⠟⢋⠀⠀⢸⣿⣿⣿⣿⢠⢫⡊⠀⡔⢵⡈⢠⢣⡹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠡⠚⠓⠪⠉⠛⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡫⡎⣸⣿⣿⣿⡟⢠⢃⣂⡿ +// ⡃⡊⢃⡔⡠⣿⣿⣿⣿⡇⣜⡘⢀⢜⡀⡅⡆⠘⢐⠁⠙⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢾⠿⢿⣿⣿⣿⣿⣷⣾⣽⡻⢿⣿⣿⣿⣿⣿⣿⣿⡿⣛⣿⣿⣟⡜⠠⣻⣿⣿⡟⠴⠃⠉⠁⠀ +// ⡺⢠⡾⣸⡷⣿⣿⣿⣿⠂⣓⠂⡎⡺⠪⠒⠃⠀⠀⠀⠀⠀⠙⠿⣯⢻⣿⣿⣿⡟⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣵⣾⣿⡷⣲⣿⣿⣿⣿⣿⣿⣿⣷⣝⢿⣿⣿⣿⡿⡫⡺⣽⣿⣿⡗⠀⢸⣿⣿⠏⠀⠀⠀⠀⠀⠀ +// ⣴⣿⢣⣿⢳⣿⣿⣿⡿⡨⡊⠠⡪⡢⠂⠀⠀⠀⠀⠀⢀⣀⠀⠀⠘⢧⡻⣿⣿⡇⢿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣿⣿⣵⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⢳⣝⠿⡫⡪⣪⡮⣾⣿⡟⢠⠅⣿⠟⠋⠀⠀⠀⠀⠀⠀⠀ +// ⣿⡏⣾⢣⡾⣿⣿⣿⡇⢪⠂⡨⡠⠀⠀⠀⠀⢀⡴⠋⠉⠈⠉⢦⡀⠠⢅⠹⣿⢰⢹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢟⣫⣾⡿⣫⣦⠸⢊⡄⣼⣿⡟⢠⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +// ⡿⢸⣏⣾⣇⣿⣿⣿⡇⢵⢀⢓⠀⠀⠀⠀⠀⡮⠀⠀⠀⠀⠀⠈⣇⠀⠧⡗⡈⢿⣺⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠟⣯⣾⣿⡿⠛⠊⣬⠴⢪⠋⣼⣿⠟⢔⢝⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +// ⠃⡿⣸⣿⣿⢸⣿⣿⡇⡊⢰⢱⢂⡀⠀⠀⠀⢳⠀⠀⠀⠀⠀⢰⡇⠀⣸⢐⡈⢄⢿⠿⠿⢿⡿⠽⢫⣵⣿⡿⠿⠛⠛⠙⠉⠁⠀⠞⠙⠃⠍⡁⠖⡪⡫⡪⣻⢁⣾⡿⢃⢞⢕⡝⢰⣆⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +// ⢸⢣⣿⣿⡗⡈⢿⣿⡇⠀⢸⣿⣷⣵⣄⠀⠀⠈⠲⣠⣠⣠⡴⠋⠀⠀⢒⢔⢂⢀⢎⢇⣓⡕⠂⡚⡩⢡⢰⢂⣓⡣⣢⠒⠀⠀⠀⠀⠀⠀⠀⠈⠢⡈⡲⡑⢡⣾⠟⡠⡣⣂⢇⠃⣿⣿⣷⣦⡀⠀⠀⠀⠀⠀⠀⠀ +// ⡟⣾⣿⡿⣸⢸⡘⣿⣷⠀⢸⣿⣿⣿⣿⣿⣦⣄⣀⣀⣀⣀⣀⣠⣤⣜⢔⢕⢕⢔⢕⢕⣗⣇⣳⡪⣺⢐⢱⢑⢒⠖⠁⠀⠀⢀⠄⠔⠖⢦⣀⠀⠀⠰⠌⣴⠟⡡⡪⡪⡪⡜⡜⣸⣿⣿⣿⣿⡿⡢⠀⠀⠀⠀⠀⠀ +// ⢱⣿⣿⡣⢑⠔⡕⠘⣿⡀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣷⣷⣵⣕⣥⣣⣣⡣⡪⣐⢱⢠⡃⠀⠀⠀⣴⠁⠀⠀⠀⠀⠸⣆⠀⠀⠘⠀⡪⡪⡪⡪⡪⣸⢡⣿⣿⣿⣿⣿⡯⣺⡀⢤⡀⠀⠀⠀ +// ⣿⣿⣟⣝⣝⣺⠁⠀⠘⣧⢘⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣷⣇⣣⠣⡘⡀⠀⠀⠀⡟⠀⠀⠀⠀⠀⠨⡗⠀⠀⠀⠈⢮⠺⡪⢊⢫⠃⣾⣿⣿⣿⣿⣿⡪⡏⣦⡀⢹⣷⣤⣀ +// ⣿⣿⢑⢇⢆⠆⠀⠀⣸⣎⠸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣮⣧⣀⠀⠀⢻⡀⠀⠀⠀⢀⡼⠃⠀⠀⠀⠀⠸⢘⢜⡐⡕⣸⣿⣿⣿⣿⣿⣃⣟⣱⣿⣿⣧⢻⣿⣿ +// ⣿⣟⢕⢕⢼⠀⠀⠀⣿⣿⣧⡹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡟⢡⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄⠀⠉⠓⠒⠓⠋⠀⠀⠀⠀⠀⠠⠀⠱⡱⡨⢣⣿⣿⣿⣿⣿⢗⡯⣱⣿⣿⣿⢇⣄⠃⠙ +// ⣿⡒⡢⡢⡃⠀⠀⠀⣿⣿⣿⣷⣝⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣤⣄⣀⣀⣀⣀⢀⣀⡀⡠⣀⡢⡪⠂⣽⣿⣿⣿⣿⣟⡕⣺⣿⣿⣿⣏⣿⣷⢔⡄ +// ⡿⡸⢰⢔⠂⠀⠀⠀⢿⣿⣿⣿⣿⣯⣻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣯⢮⡊⡠⣰⣿⣿⣿⣿⡿⣡⣿⣿⣿⣿⡟⣾⣿⣿⢵⠅ +// ⣟⢕⢝⣗⠀⠀⠀⡹⡘⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⣫⡾⢱⣿⣿⣿⣿⡟⣼⣿⣿⣿⣿⡿⣹⣿⣿⡟⡹⢰ +// ⢕⢕⢕⢕⠠⡢⣪⢂⢇⢹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⣫⣾⣿⢣⣿⣿⣿⣿⣿⣻⣿⣿⣿⣿⡿⣱⣿⣿⣿⡫⡇⣼ +// ⢕⢕⢕⠅⣘⡪⡚⡔⠍⠂⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⣫⣾⣿⢟⢁⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢣⣿⣿⣿⣏⡼⢠⣿ +// ⢕⢕⢕⠀⣖⡪⡪⣂⠀⠐⠈⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⢟⣫⣵⡿⢟⣫⡴⢁⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠃⣸⣿⣿⣿⢒⠇⣼⣿ +// ⢕⢅⢕⠨⣃⢪⢨⢂⠀⠀⠀⠈⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣄⣳⢰⢬⡙⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣍⣛⠻⠿⠿⠿⢛⣯⡭⠶⣞⣛⣭⣵⣾⣿⠿⢁⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠃⢀⣿⣿⣿⡯⡝⢰⣿⣿ +// ⢝⢕⠇⢸⠱⡑⡁⠀⣀⠀⠀⠀⠈⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣷⣬⣃⣹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣶⣾⣿⣿⣿⣿⣿⣿⣿⡿⡱⢃⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠃⠀⣼⣿⣿⣿⣸⢡⣿⣿⣿ +// ⢕⢕⠅⢜⠕⡠⣢⣾⣿⣷⣤⣄⢀⣬⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⢛⠴⢡⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠁⠀⢠⣿⣿⣿⢝⢡⣿⣿⣿⣿ +// ⢕⢕⠅⣕⢥⡶⢀⠻⣿⣿⣿⣟⢿⣿⣿⣻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⢫⡐⣕⢡⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡟⠀⠀⢀⣿⣿⣿⠏⢁⣿⣿⣿⣿⣿ +// ⢕⢕⢠⢗⣛⣓⣁⢱⣾⣿⣿⣿⣷⣷⣽⣿⡻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢛⢔⠕⠓⢠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠀⠀⢀⣾⣿⡿⡣⢎⣿⣿⣿⣿⣿⣿ +// ⡕⡕⠠⡅⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡝⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠋⠐⣁⡴⡴⢣⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠃⠀⠀⢠⣾⠿⣫⠞⣱⣿⣿⣿⣿⣿⠟⡡ +// ⢅⢇⢘⢕⢍⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣎⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠿⠿⠛⠛⠛⠉⠁⠀⢀⢜⢕⢅⠕⣡⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠁⠀⠀⠐⡫⡠⠜⣡⣾⣿⣿⡿⠟⠫⠦⠪⠊ +// ⢕⢕⢸⣵⣷⣾⣿⣿⣿⣿⣟⢿⣿⣿⣿⣿⣿⣿⣿⣦⣤⡩⣉⣉⡩⣉⣉⠉⠉⠈⠀⠀⠀⠀⠀⠀⠀⢀⣀⡀⠀⠀⣤⢔⢗⢕⠕⣰⣿⣿⣿⣿⣿⣿⣿⣿⡿⢋⢔⢕⠕⣓⠏⣊⠰⠻⠟⠛⣉⠅⢄⠂⡪⡪⡪⠨ +// +// ~it actually won't be her, but one can dream~ +// +} + +Class CompanionLamp : Actor +{ + Vector3 Trail; + Array moff; + Actor parent; + bool justteleport; + + Default + { + Tag "$T_LAMP"; + +NOGRAVITY; + +NOTELEPORT; + +DONTSPLASH; + +INTERPOLATEANGLES; + +LOOKALLAROUND; + +FRIENDLY; + +NOBLOCKMONST; + Radius 4; + Height 16; + } + // random chance to spawn moths + void A_Moth() + { + // count up + special1++; + for ( int i=0; i= 30) ) return; + // spawn a moth at a random offset + double ang = FRandom[Moth](0,360); + double pt = FRandom[Moth](-30,30); + double dist = FRandom[Moth](10,30); + Vector3 ofs = SWWMUtility.Vec3FromAngles(ang,pt)*dist; + Vector3 spawnpos = level.Vec3Offset(Vec3Offset(0,0,height/2),ofs); + if ( !level.IsPointInLevel(spawnpos) ) return; + // higher chance of white moths if carrying the Mashiro plush + //int mchance = parent.FindInventory("MothPlushy")?3:9; + let m = LampMoth(Spawn(Random[Moth](0,9)?"LampMoth":"LampMoth2",spawnpos)); + if ( !m.TestMobjLocation() ) + { + m.Destroy(); + return; + } + let s = Spawn("SWWMSmallSmoke",m.pos); + s.alpha *= .3; + m.master = parent; + m.lamp = self; + m.trail = m.pos; + moff.Push(m); + SWWMUtility.AchievementProgressInc("moth",1,parent.player); + } + override void PostBeginPlay() + { + Super.PostBeginPlay(); + if ( !parent || !SWWMLamp(master) ) + { + Destroy(); + return; + } + Spawn("SWWMItemFog",pos); + Trail = pos; + } + + override void Tick() + { + Super.Tick(); + if ( !parent || !SWWMLamp(master) ) + { + Destroy(); + return; + } + if ( isFrozen() || (freezetics > 0) ) return; + // update trailing position + bool foundspot = false; + for ( int i=0; i<180; i+=5 ) + { + for ( int j=1; j>=-1; j-=2 ) + { + double ang = (parent.angle-180)+i*j; + Vector3 testpos = level.Vec3Offset(parent.pos,SWWMUtility.RotateVector3((32,0,parent.height-8+1.5*sin(level.maptime*3.)),ang)); + if ( !level.IsPointInLevel(testpos) ) continue; + Vector3 oldpos = pos; + Vector3 oldprev = prev; + Actor oldblockingmobj = blockingmobj; + Line oldblockingline = blockingline; + Sector oldblockingfloor = blockingfloor, oldblockingceiling = blockingceiling; + SetOrigin(testpos,false); + if ( !TestMobjLocation() || SWWMUtility.BlockingLineIsBlocking(self,Line.ML_BLOCKING|Line.ML_BLOCKEVERYTHING) || BlockingFloor || BlockingCeiling ) + { + SetOrigin(oldpos,false); + prev = oldprev; + blockingmobj = oldblockingmobj; + blockingline = oldblockingline; + blockingfloor = oldblockingfloor; + blockingceiling = oldblockingceiling; + continue; + } + SetOrigin(oldpos,false); + prev = oldprev; + blockingmobj = oldblockingmobj; + blockingline = oldblockingline; + blockingfloor = oldblockingfloor; + blockingceiling = oldblockingceiling; + Trail = testpos; + foundspot = true; + } + // check at most for a 45 degree offset + if ( foundspot && (i > 45) ) break; + } + Vector3 diff = level.Vec3Diff(pos,parent.pos); + if ( (diff.length() > 400) || justteleport ) + { + Vector3 rel = level.Vec3Diff(pos,trail); + justteleport = false; + Actor f = Spawn("SWWMItemFog",pos); + f.A_StartSound("lamp/disappear",CHAN_VOICE); + // carry over the moths + foreach ( m:moff ) + { + if ( !m ) continue; + Vector3 whereto = level.Vec3Offset(m.pos,rel); + if ( !level.IsPointInLevel(whereto) ) + continue; + Vector3 oldp = m.pos; + m.SetOrigin(whereto,false); + if ( !m.TestMobjLocation() ) + m.SetOrigin(oldp,false); + } + SetOrigin(trail,false); + angle = AngleTo(parent); + vel *= 0.; + f = Spawn("SWWMItemFog",pos); + f.A_StartSound("lamp/appear",CHAN_VOICE); + return; + } + angle += Clamp(deltaangle(angle,AngleTo(parent)),-5.,5.); + vel *= .8; + bool blocked = false; + if ( SWWMUtility.BlockingLineIsBlocking(self,Line.ML_BLOCKING|Line.ML_BLOCKEVERYTHING) ) + { + // push away from wall + Vector3 normal = (-BlockingLine.delta.y,BlockingLine.delta.x,0).unit(); + if ( !SWWMUtility.PointOnLineSide(pos.xy,BlockingLine) ) normal *= -1; + vel += 4.*normal; + blocked = true; + } + if ( BlockingFloor ) + { + // push away from floor + Vector3 normal = BlockingFloor.floorplane.Normal; + // find closest 3d floor for its normal + for ( int i=0; i -16) && (diff.x < 16) && (diff.y > -16) && (diff.y < 16) && (diff.z > -16) && (diff.z < parent.height+8) ) + { + if ( diff.x < 0 ) vel.x -= .2; + else vel.x += .2; + if ( diff.y < 0 ) vel.y -= .2; + else vel.y += .2; + if ( diff.z < 0 ) vel.z -= .2; + else vel.z += .2; + blocked = true; + } + if ( blocked ) return; + Vector3 dir = level.Vec3Diff(pos,trail); + if ( dir.length() > 0 ) + vel += dir.unit()*min(dir.length()*.05,20.); + } + States + { + Spawn: + XZW1 A 1 + { + if ( SWWMLamp(master) && SWWMLamp(master).bActive ) + { + A_StartSound("lamp/on",CHAN_ITEMEXTRA,CHANF_OVERLAP); + return ResolveState("Active"); + } + return ResolveState(null); + } + Wait; + Active: + XZW1 B 1 + { + A_Moth(); + if ( !SWWMLamp(master) || !SWWMLamp(master).bActive ) + { + A_StartSound("lamp/off",CHAN_ITEMEXTRA,CHANF_OVERLAP); + return ResolveState("Spawn"); + } + return ResolveState(null); + } + Wait; + } +} + +Class SWWMLamp : Inventory +{ + Mixin SWWMOverlapPickupSound; + Mixin SWWMUseToPickup; + Mixin SWWMRespawn; + Mixin SWWMPickupGlow; + + bool bActive, bActivated; + TextureID OnIcon; + Actor thelamp; + int charge; + + Property Charge : charge; + + override Inventory CreateCopy( Actor other ) + { + // additional lore + SWWMLoreLibrary.Add(other.player,"MothLamp"); + return Super.CreateCopy(other); + } + override bool HandlePickup( Inventory item ) + { + // add charge + if ( item.GetClass() == GetClass() ) + { + if ( (Charge >= Default.Charge) && (Amount+item.Amount > MaxAmount) ) + { + // sell excess + int sellprice = abs(Stamina)/2; + SWWMScoreObj.Spawn(sellprice,level.Vec3Offset(Owner.pos,SWWMUtility.Vec3FromAngles(FRandom[ScoreBits](0,360),FRandom[ScoreBits](-90,90))*8.+(0,0,Owner.Height/2))); + SWWMCredits.Give(Owner.player,sellprice); + if ( Owner.player ) + { + if ( Owner.player == players[consoleplayer] ) Console.Printf(StringTable.Localize(SWWMUtility.SellFemaleItem(item)?"$SWWM_SELLEXTRA_FEM":"$SWWM_SELLEXTRA"),GetTag(),sellprice); + else Console.Printf(StringTable.Localize(SWWMUtility.SellFemaleItem(item)?"$SWWM_SELLEXTRAREM_FEM":"$SWWM_SELLEXTRAREM"),Owner.player.GetUserName(),GetTag(),sellprice); + } + } + else if ( Charge > 0 ) + { + int AddCharge = Charge+SWWMLamp(item).Charge; + Charge = min(Default.Charge,AddCharge); + // if there's charge to spare, increase amount + if ( AddCharge > Charge ) + { + if ( (Amount > 0) && (Amount+item.Amount < 0) ) Amount = int.max; + Amount = min(MaxAmount,Amount+item.Amount); + Charge = AddCharge-Charge; + } + } + else + { + if ( (Amount > 0) && (Amount+item.Amount < 0) ) Amount = int.max; + // new copy, increase and take its charge + Amount = min(MaxAmount,Amount+item.Amount); + Charge = SWWMLamp(item).Charge; + } + item.bPickupGood = true; + return true; + } + return Super.HandlePickup(item); + } + override bool Use( bool pickup ) + { + if ( pickup && !deathmatch ) return false; + bActivated = true; + bActive = !bActive; + if ( !OnIcon ) OnIcon = TexMan.CheckForTexture("graphics/HUD/Icons/I_Lamp.png"); + Icon = bActive?OnIcon:default.Icon; + // don't consume on use + Amount++; + return true; + } + override bool ShouldSpawn() + { + if ( deathmatch ) return false; + return Super.ShouldSpawn(); + } + override void PreTravelled() + { + // remove the lamp + if ( thelamp ) thelamp.Destroy(); + } + override void DoEffect() + { + Super.DoEffect(); + if ( !thelamp && bActivated ) + { + thelamp = Spawn("CompanionLamp",level.Vec3Offset(Owner.pos,SWWMUtility.RotateVector3((20,0,24),Owner.angle))); + CompanionLamp(thelamp).parent = Owner; + thelamp.master = self; + let f = Spawn("SWWMItemFog",thelamp.pos); + f.A_StartSound("lamp/appear",CHAN_VOICE); + } + if ( bActive && !(level.maptime%35) && !isFrozen() ) Charge--; + if ( Charge <= 0 ) + { + Amount--; + if ( Amount <= 0 ) DepleteOrDestroy(); + else Charge = default.Charge; + } + } + override void DetachFromOwner() + { + Super.DetachFromOwner(); + if ( thelamp ) + { + let f = Spawn("SWWMItemFog",thelamp.pos); + f.A_StartSound("lamp/disappear",CHAN_VOICE); + thelamp.Destroy(); + } + Icon = default.Icon; + bActive = false; + bActivated = false; + } + clearscope bool isBlinking() const + { + return ( (Charge < 10) && (level.maptime&8) ); + } + Default + { + Tag "$T_LAMP"; + Inventory.Icon "graphics/HUD/Icons/I_LampOff.png"; + Inventory.PickupSound "misc/p_pkup"; + Inventory.PickupMessage "$I_LAMP"; + Inventory.Amount 1; + Inventory.MaxAmount 5; + Inventory.InterHubAmount 5; + Inventory.PickupFlash "SWWMPurplePickupFlash"; + +INVENTORY.ALWAYSPICKUP; + +INVENTORY.AUTOACTIVATE; + +INVENTORY.INVBAR; + +COUNTITEM; + +INVENTORY.BIGPOWERUP; + +FLOATBOB; + +DONTGIB; + FloatBobStrength 0.25; + SWWMLamp.Charge 100; + Stamina 70000; + } + States + { + Spawn: + XZW1 A -1; + Stop; + } +} diff --git a/zscript/items/swwm_miscitems.zsc b/zscript/items/swwm_miscitems.zsc new file mode 100644 index 000000000..b5ea7dac8 --- /dev/null +++ b/zscript/items/swwm_miscitems.zsc @@ -0,0 +1,840 @@ +// Other items + +Class Omnisight : Inventory +{ + Mixin SWWMOverlapPickupSound; + Mixin SWWMUseToPickup; + Mixin SWWMRespawn; + + override bool TryPickup( in out Actor toucher ) + { + if ( !level.allmap ) + { + Actor rt = toucher; + if ( toucher.player ) rt = toucher.player.mo; + if ( rt.player == players[consoleplayer] ) + { + rt.A_StartSound("powerup/omnisight",CHAN_ITEMEXTRA,CHANF_OVERLAP); + // automatically zoom out so the player can know how far this goes + CVar.FindCVar('swwm_mm_zoom').SetFloat(2.); + } + if ( rt is 'Demolitionist' ) + Demolitionist(rt).lastbump *= 1.1; + level.allmap = true; + // activate all interest markers + let ti = ThinkerIterator.Create("SWWMInterestMarker",STAT_MAPMARKER); + Actor a; + while ( a = Actor(ti.Next()) ) a.bDORMANT = false; + // "spread" to all players + for ( int i=0; i=2)?default.Icon:(primetim>=6)?(bFailed?OnIcon[1]:OnIcon[2]):OnIcon[0]; + if ( !bFailed && (primetim == 20) ) Owner.A_StartSound("hahaha/hahaha",CHAN_POWERUP,CHANF_OVERLAP); + primetim++; + if ( (primetim <= 20) || (!bFailed && (primetim <= 50)) ) return; + primetim = 0; + bPrimed = false; + Icon = default.Icon; + if ( bFailed ) return; + Vector3 safepos; + double safeangle; + if ( deathmatch ) [safepos, safeangle] = level.PickDeathmatchStart(); + else [safepos, safeangle] = level.PickPlayerStart(Owner.PlayerNumber()); + if ( !level.useplayerstartz ) safepos.z = ONFLOORZ; + Vector3 oldpos = Owner.pos; + if ( !Owner.Teleport(safepos,safeangle,0) ) + { + if ( Owner is 'Demolitionist' ) + Demolitionist(Owner).lastbump *= .95; + Owner.A_StartSound("powerup/tethererror",CHAN_ITEMEXTRA,CHANF_OVERLAP); + if ( Owner.player == players[consoleplayer] ) Console.Printf(StringTable.Localize("$D_TETHERFAIL")); + return; + } + let s = Spawn("DemolitionistShockwave",oldpos); + s.target = Owner; + s.special1 = 120; + s.A_AlertMonsters(0,AMF_EMITFROMTARGET); + s = Spawn("DemolitionistShockwave",Owner.pos); + s.target = Owner; + s.special1 = 120; + s.A_AlertMonsters(0,AMF_EMITFROMTARGET); + if ( Owner.player == players[consoleplayer] ) + { + Owner.A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP); + Owner.A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP,pitch:.7); + Owner.A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP,pitch:.4); + } + SWWMHandler.DoFlash(Owner,Color(255,255,255,255),10); + SWWMHandler.DoFlash(Owner,Color(255,128,192,255),30); + Owner.GiveBody(100,100); + SWWMUtility.AchievementProgressInc("sneaky",1,Owner.player); + Amount--; + if ( Amount <= 0 ) DepleteOrDestroy(); + } + + override bool Use( bool pickup ) + { + if ( pickup || bPrimed ) return false; + if ( !OnIcon[0] ) OnIcon[0] = TexMan.CheckForTexture("graphics/HUD/Icons/I_SafetyOn.png"); + if ( !OnIcon[1] ) OnIcon[1] = TexMan.CheckForTexture("graphics/HUD/Icons/I_SafetyNo.png"); + if ( !OnIcon[2] ) OnIcon[2] = TexMan.CheckForTexture("graphics/HUD/Icons/I_SafetyYes.png"); + bPrimed = true; + primetim = 0; + Vector3 safepos; + if ( deathmatch ) safepos = level.PickDeathmatchStart(); + else safepos = level.PickPlayerStart(Owner.PlayerNumber()); + if ( !level.useplayerstartz ) + { + // find the floor for proper distance check + let s = level.PointInSector(safepos.xy); + safepos.z = s.floorplane.ZAtPoint(safepos.xy); + } + bFailed = (level.Vec3Diff(Owner.pos,safepos).length() < 400); + if ( (Owner.player == players[consoleplayer]) || bBigPowerup ) Owner.A_StartSound(bFailed?"powerup/tetherfail":"powerup/tetheruse",CHAN_ITEMEXTRA,CHANF_OVERLAP); + // don't consume on use, will happen later + Amount++; + return true; + } + + Default + { + Tag "$T_SAFETY"; + Stamina 240000; + Inventory.Icon "graphics/HUD/Icons/I_Safety.png"; + Inventory.PickupSound "misc/p_pkup"; + Inventory.UseSound ""; + Inventory.PickupMessage "$T_SAFETY"; + Inventory.MaxAmount 5; + Inventory.InterHubAmount 5; + Inventory.PickupFlash "SWWMPurplePickupFlash"; + +INVENTORY.ALWAYSPICKUP; + +INVENTORY.AUTOACTIVATE; + +INVENTORY.INVBAR; + +COUNTITEM; + +INVENTORY.BIGPOWERUP; + +FLOATBOB; + +DONTGIB; + FloatBobStrength 0.25; + } + States + { + Spawn: + XZW1 A 33; + XZW1 B 2; + Loop; + } +} + +// TBD 1.4 Ballsy Bomb + +Class BallImpact : SWWMNonInteractiveActor +{ + override void PostBeginPlay() + { + Super.PostBeginPlay(); + A_QuakeEx(3,3,3,12,0,200,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,falloff:100,rollIntensity:.3); + A_StartSound("leadball/hit",CHAN_VOICE); + A_SprayDecal("WallCrack",-20); + int numpt = Random[Spreadgun](5,10); + Vector3 x = SWWMUtility.Vec3FromAngles(angle,pitch); + for ( int i=0; i 0) && victim ) + { + victim.A_StartSound("leadball/hitf",CHAN_DAMAGE,CHANF_OVERLAP,(vel.length()/30.)**.5); + victim.TraceBleed(newdmg,self); + victim.SpawnBlood(pos,atan2(dir.y,dir.x),dmg); + } + } + else + { + A_StartSound("leadball/hit",CHAN_VOICE,CHANF_OVERLAP,(vel.length()/30.)**.5); + if ( victim ) victim.A_StartSound("leadball/hit",CHAN_DAMAGE,CHANF_OVERLAP,(vel.length()/30.)**.5); + if ( vel.length() > 15. ) + { + let s = Spawn("BallImpact",pos); + s.angle = atan2(dir.y,dir.x); + s.pitch = asin(-dir.z); + } + } + // make it so the crit does not propagate to friendlies unless we bonked a friend (you monster!) + if ( crit ) + SWWMUtility.DoExplosion(self,dmg/2,25000,150,80,((victim.isFriend(target))?0:DE_NOHURTFRIEND)|DE_NONEXPLOSIVE,crit?'CriticalConcussion':'Concussion',target,DMG_FOILINVUL); + if ( crit && victim && (victim.Health <= 0) && (victim.bBOSS || victim.FindInventory("BossMarker")) && target ) + SWWMUtility.MarkAchievement("clonk",target.player); + // only rip shootables + if ( (slamforce > girth) && is_schutt ) + { + vel *= .7; + return 1; + } + // force bounce + BlockingMobj = victim; + A_HandleBounce(); + lasthit = victim; + // pretend to pass through + return 1; + } + override void PostBeginPlay() + { + Super.PostBeginPlay(); + A_StartSound("leadball/fly",CHAN_WEAPON,CHANF_LOOP,.6,3.,2.); + heat = 1.; + } + override void Tick() + { + oldvel = vel; + Super.Tick(); + if ( isFrozen() || (freezetics > 0) ) return; + if ( InStateSequence(CurState,ResolveState("Death")) ) + { + deadtimer++; + if ( deadtimer > 300 ) + { + let numpt = Random[Spreadgun](3,6); + for ( int i=0; i 15 ) + { + let s = Spawn("BallImpact",pos); + s.angle = atan2(HitNormal.y,HitNormal.x); + s.pitch = asin(-HitNormal.z); + } + } + gravity = .35; + if ( (vel.length() < 5) && (pos.z <= floorz) ) + { + ClearBounce(); + ExplodeMissile(); + } + } + States + { + Spawn: + XZW1 A -1; + Stop; + Bounce: + XZW1 A 0 A_HandleBounce(); + Goto Spawn; + Death: + XZW1 A -1 + { + bMOVEWITHSECTOR = true; + A_StopSound(CHAN_WEAPON); + } + Stop; + } +} + +// TBD 1.4 Battle-Boi Sentry + +Class SaltTracer : LineTracer +{ + Actor ignore; + Array ShootThroughList; + Array WaterHitList; + + override ETraceStatus TraceCallback() + { + // liquid splashes + if ( Results.CrossedWater ) + { + let hl = new("WaterHit"); + hl.sect = Results.CrossedWater; + hl.hitpos = Results.CrossedWaterPos; + WaterHitList.Push(hl); + } + else if ( Results.Crossed3DWater ) + { + let hl = new("WaterHit"); + hl.sect = Results.Crossed3DWater; + hl.hitpos = Results.Crossed3DWaterPos; + WaterHitList.Push(hl); + } + if ( Results.HitType == TRACE_HitActor ) + { + if ( 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) ) + return TRACE_Stop; + ShootThroughList.Push(Results.HitLine); + return TRACE_Skip; + } + return TRACE_Stop; + } +} + +Class SaltLight : PaletteLight +{ + Default + { + Tag "SaltExpl,1"; + ReactionTime 30; + Args 0,0,0,240; + } +} +Class SaltLight2 : PaletteLight +{ + Default + { + Tag "SaltExpl"; + ReactionTime 30; + Args 0,0,0,70; + } +} + +Class SaltImpact : SWWMNonInteractiveActor +{ + Default + { + Obituary "$O_SALTSHOT"; + DamageType "Salt"; + RenderStyle "Add"; + Scale 1.8; + +NODAMAGETHRUST; + +FORCERADIUSDMG; + +FORCEXYBILLBOARD; + +FOILINVUL; + } + override void PostBeginPlay() + { + Super.PostBeginPlay(); + A_AlertMonsters(swwm_uncapalert?0:6000,AMF_EMITFROMTARGET); + SWWMUtility.DoExplosion(self,30+special2*4,15000,100,40); + A_QuakeEx(3,3,3,10,0,250,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,falloff:150,rollintensity:0.2); + A_StartSound("saltshot/hit",CHAN_VOICE,attenuation:.35); + A_SprayDecal("ShockMarkSmall",-172); + A_SprayDecal("SaltMark",-172); + Scale *= FRandom[ExploS](0.8,1.1); + int numpt = Random[ExploS](5,9)-special1; + for ( int i=0; i 20) && !Random[Spreadgun](0,800/args[0]) ) + { + let i = Spawn("SaltImpact",level.Vec3Offset(pos,x*speed)); + i.angle = atan2(x.y,x.x); + i.pitch = asin(-x.z); + i.target = target; + i.special1 = (Stamina-9)/4; + i.special2 = Accuracy; + i.args[0] = args[1]; + return; + } + // next beam + if ( !(special2%4) && !Random[Spreadgun](0,Stamina) ) + Spawn("SaltLight",level.Vec3Offset(pos,x*speed/2)); + let next = Spawn("SaltBeam",level.Vec3Offset(pos,x*speed)); + double a = FRandom[Spreadgun](0,360), s = FRandom[Spreadgun](0,.06); + Vector3 dir = SWWMUtility.ConeSpread(x,y,z,a,s); + next.angle = atan2(dir.y,dir.x); + next.pitch = asin(-dir.z); + next.target = target; + next.special2 = (special2+1)%10; + next.args[0] = args[0]+1; + next.args[1] = args[1]; + next.SetStateLabel("TrailSpawn"); + } + + override void PostBeginPlay() + { + Super.PostBeginPlay(); + if ( !Random[Spreadgun](0,3) ) + A_StartSound("saltshot/trail",CHAN_VOICE,CHANF_DEFAULT,.3,4.); + } + + override void Tick() + { + if ( freezetics > 0 ) + { + freezetics--; + return; + } + if ( isFrozen() ) return; + A_FadeOut(.04); + if ( Random[Spreadgun](-2,args[2]/10) == 0 ) + SWWMUtility.DoExplosion(self,5+Accuracy,5000,speed,flags:DE_HOWL|DE_NONEXPLOSIVE,ignoreme:target); + if ( ((special2%4) || args[2]) && !special1 ) SpreadOut(); + args[2]++; + if ( !CheckNoDelay() || (tics == -1) ) return; + if ( tics > 0 ) tics--; + while ( !tics ) + { + if ( !SetState(CurState.NextState) ) + return; + } + } + + States + { + Spawn: + XZW1 A -1 Bright NoDelay + { + return FindState("StarterDev")+Random[Spreadgun](0,11)*2; + } + Stop; + TrailSpawn: + XZW2 A -1 Bright + { + return FindState("TrailerDev")+Random[Spreadgun](0,11)*2; + } + Stop; + StarterDev: + #### # 25 Bright; + XZW1 B -1 Bright; + Stop; + #### # 25 Bright; + XZW1 C -1 Bright; + Stop; + #### # 25 Bright; + XZW1 D -1 Bright; + Stop; + #### # 25 Bright; + XZW1 E -1 Bright; + Stop; + #### # 25 Bright; + XZW1 F -1 Bright; + Stop; + #### # 25 Bright; + XZW1 G -1 Bright; + Stop; + #### # 25 Bright; + XZW1 H -1 Bright; + Stop; + #### # 25 Bright; + XZW1 I -1 Bright; + Stop; + #### # 25 Bright; + XZW1 J -1 Bright; + Stop; + #### # 25 Bright; + XZW1 K -1 Bright; + Stop; + #### # 25 Bright; + XZW1 L -1 Bright; + Stop; + #### # 25 Bright; + XZW1 M -1 Bright; + Stop; + TrailerDev: + #### # 25 Bright; + XZW2 B -1 Bright; + Stop; + #### # 25 Bright; + XZW2 C -1 Bright; + Stop; + #### # 25 Bright; + XZW2 D -1 Bright; + Stop; + #### # 25 Bright; + XZW2 E -1 Bright; + Stop; + #### # 25 Bright; + XZW2 F -1 Bright; + Stop; + #### # 25 Bright; + XZW2 G -1 Bright; + Stop; + #### # 25 Bright; + XZW2 H -1 Bright; + Stop; + #### # 25 Bright; + XZW2 I -1 Bright; + Stop; + #### # 25 Bright; + XZW2 J -1 Bright; + Stop; + #### # 25 Bright; + XZW2 K -1 Bright; + Stop; + #### # 25 Bright; + XZW2 L -1 Bright; + Stop; + #### # 25 Bright; + XZW2 M -1 Bright; + Stop; + } +} diff --git a/zscript/items/swwm_powerups.zsc b/zscript/items/swwm_powerups.zsc index 860039d43..655a8de86 100644 --- a/zscript/items/swwm_powerups.zsc +++ b/zscript/items/swwm_powerups.zsc @@ -1161,753 +1161,6 @@ Class Ragekit : Inventory } } -Class Omnisight : Inventory -{ - Mixin SWWMOverlapPickupSound; - Mixin SWWMUseToPickup; - Mixin SWWMRespawn; - - override bool TryPickup( in out Actor toucher ) - { - if ( !level.allmap ) - { - Actor rt = toucher; - if ( toucher.player ) rt = toucher.player.mo; - if ( rt.player == players[consoleplayer] ) - { - rt.A_StartSound("powerup/omnisight",CHAN_ITEMEXTRA,CHANF_OVERLAP); - // automatically zoom out so the player can know how far this goes - CVar.FindCVar('swwm_mm_zoom').SetFloat(2.); - } - if ( rt is 'Demolitionist' ) - Demolitionist(rt).lastbump *= 1.1; - level.allmap = true; - // activate all interest markers - let ti = ThinkerIterator.Create("SWWMInterestMarker",STAT_MAPMARKER); - Actor a; - while ( a = Actor(ti.Next()) ) a.bDORMANT = false; - // "spread" to all players - for ( int i=0; i mindist) && !CheckSight(a,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) continue; - mindist = dist; - lamp = a; - master = a.target; - if ( CompanionLamp(lamp).moff.Find(self) == CompanionLamp(lamp).moff.Size() ) - CompanionLamp(lamp).moff.Push(self); - if ( master && master.player ) SetFriendPlayer(master.player); - else bFRIENDLY = false; - } - } - if ( !lamp || (lamp.frame == 0) || (Distance3DSquared(lamp) > 62500) || !CheckSight(lamp,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) return false; - if ( target && (target.Health > 0) && CheckSight(target) ) return false; - return true; - } - void A_SmoothWander() - { - if ( level.Vec3Diff(pos,trail).length() < speed ) - { - double ang = FRandom[Moth](0,360); - double pt = FRandom[Moth](-30,30); - double dist = FRandom[Moth](20,40); - ofs = SWWMUtility.Vec3FromAngles(ang,pt)*dist; - } - Vector3 newpos = level.Vec3Offset(pos,ofs); - if ( level.IsPointInLevel(newpos) ) trail = newpos; - if ( vel.length() > 0 ) - { - Vector3 uvel = vel.unit(); - angle += Clamp(deltaangle(angle,atan2(uvel.y,uvel.x)),-5.,5.); - pitch += Clamp(deltaangle(pitch,asin(-uvel.z)),-5.,5.); - } - vel *= .8; - Vector3 dir = level.Vec3Diff(pos,trail); - if ( dir.length() > 0 ) vel += dir.unit()*clamp(dir.length()*.05,.4*speed,.5*speed); - } - void A_SmoothChase() - { - if ( !target || (target.Health <= 0) ) - { - A_ClearTarget(); - SetStateLabel("Spawn"); - return; - } - if ( CheckMeleeRange() ) - { - SetStateLabel("Melee"); - return; - } - Vector3 dest = target.Vec3Offset(0,0,target.height*.75); - Vector3 dir = level.Vec3Diff(pos,dest); - if ( dir.length() > 0 ) - { - Vector3 dirunit = dir.unit(); - FLineTraceData d; - LineTrace(atan2(dirunit.y,dirunit.x),dir.length(),asin(-dirunit.z),data:d); - if ( (d.HitType != TRACE_HitActor) && (d.HitActor != target) ) - { - A_Chase(); - return; - } - } - if ( vel.length() > 0 ) - { - Vector3 uvel = vel.unit(); - angle = atan2(uvel.y,uvel.x); - pitch = asin(-uvel.z); - } - vel *= .8; - if ( dir.length() > 0 ) vel += dir.unit()*clamp(dir.length()*.02,.3*speed,2.*speed); - } - void A_FollowLamp() - { - if ( !lamp ) - { - SetStateLabel("Spawn"); - return; - } - double dst = level.Vec3Diff(pos,trail).length(); - if ( (dst < speed) || (dst > 50) ) - { - double ang = FRandom[Moth](0,360); - double pt = FRandom[Moth](-30,30); - double dist = FRandom[Moth](20,30); - ofs = SWWMUtility.Vec3FromAngles(ang,pt)*dist; - } - Vector3 newpos = level.Vec3Offset(lamp.Vec3Offset(0,0,lamp.height/2),ofs); - if ( level.IsPointInLevel(newpos) ) trail = newpos; - if ( vel.length() > 0 ) - { - Vector3 uvel = vel.unit(); - angle = atan2(uvel.y,uvel.x); - pitch = asin(-uvel.z); - } - vel *= .8; - Vector3 dir = level.Vec3Diff(pos,trail); - if ( dir.length() > 0 ) vel += dir.unit()*clamp(dir.length()*.02,.4*speed,2.*speed); - Vector3 diff = level.Vec3Diff(pos,lamp.pos); - if ( (diff.x > -8) && (diff.x < 8) && (diff.y > -8) && (diff.y < 8) && (diff.z > -4) && (diff.z < lamp.height+4) ) - { - if ( diff.x < 0 ) vel.x -= .2; - else vel.x += .2; - if ( diff.y < 0 ) vel.y -= .2; - else vel.y += .2; - if ( diff.z < 0 ) vel.z -= .2; - else vel.z += .2; - } - } - void A_SmoothMove() - { - if ( vel.length() > 0 ) - { - Vector3 uvel = vel.unit(); - angle = atan2(uvel.y,uvel.x); - pitch = asin(-uvel.z); - } - vel *= .8; - } - void A_Scrape() - { - if ( CheckMeleeRange() ) - { - A_FaceTarget(0,0); - lifespan -= 5; - Vector3 awaydir = level.Vec3Diff(target.Vec3Offset(0,0,target.height),pos).unit(); - vel += awaydir*8.; - int dmg = target.DamageMobj(self,master?master:Actor(self),GetMissileDamage(0,0),'Melee',Random[Moth](0,8)?DMG_NO_PAIN:0); - if ( (dmg > 0) && target && !target.bNOBLOOD && !target.bDORMANT && !target.bINVULNERABLE ) - { - target.TraceBleed(dmg,self); - target.SpawnBlood(pos,atan2(awaydir.y,awaydir.x)+180,dmg); - } - A_StartSound("moth/scrape",CHAN_WEAPON,CHANF_OVERLAP,.2,2.5); - DamageMobj(target,target,1,'Melee'); - } - } - override void Tick() - { - Super.Tick(); - if ( isFrozen() || (freezetics > 0) ) return; - if ( isEntranced() ) - { - lifespan = 100; - return; - } - if ( target && (target.Health > 0) ) lifespan = max(20,lifespan); - lifespan--; - if ( lifespan <= 0 ) - { - let s = Spawn("SWWMSmallSmoke",pos); - s.alpha *= .3; - Destroy(); - } - } - States - { - Spawn: - XZW1 B 0 A_JumpIf(isEntranced(),"See.Entranced"); - XZW1 BC 1 - { - A_SmoothWander(); - A_Look(); - } - Loop; - See: // go for enemies - XZW1 B 0 A_JumpIf(isEntranced(),"See.Entranced"); - XZW1 BC 1 A_SmoothChase(); - Loop; - See.Entranced: // follow the lamp - XZW1 B 0 A_JumpIf(!isEntranced(),"Spawn"); - XZW1 BC 1 A_FollowLamp(); - Loop; - Melee: - XZW1 B 0 A_Scrape(); - XZW1 BCBC 1 A_SmoothMove(); - Goto See; - Death: - TNT1 A 1 - { - A_StartSound("moth/die",CHAN_VOICE,CHANF_OVERLAP,.6,2.5); - let s = Spawn("SWWMSmallSmoke",pos); - s.alpha *= .3; - } - Stop; - } -} - -Class LampMoth2 : LampMoth -{ - Default - { - Tag "$T_WMOTH"; - DamageFunction 3; - Speed 3; - Scale 1.5; - Health 200; - } -} - -Class LampMashiro : Actor abstract -{ -// -// ~nothing here yet, but she will make an appearance someday~ -// -// ⠀⠀⠀⠀⠤⠀⠄⠀⠀⠀⠳⠀⠂⠀⠀⠀⠀⠀⠀⠀⠀⣀⣴⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⡣⣁⢌⡀⢄⠁⡁⠝⢿⣿⣿⣿⡻⣿⣿⣷⣦ -// ⠀⠀⠠⠀⠠⠀⠀⡀⠘⣠⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣊⢇⠠⢐⢄⡙⢿⣿⣿⣾⠫⣻⣿ -// ⠀⡄⠂⢴⠠⠌⠰⢇⠀⠀⠀⠀⠀⠀⠀⢀⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⡀⠣⡣⡱⣌⠻⣿⣿⣿⣿⣿ -// ⠀⠁⠛⠂⠀⠉⡍⠀⠀⠀⠀⠀⠀⠀⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧⠘⣮⣾⡮⢦⡹⣿⣿⣿⣿ -// ⠄⠠⠉⢼⡇⠶⠀⠀⠀⠀⠀⠀⢠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡆⢿⣿⣿⡦⢣⡉⢝⢝⢽ -// ⠀⠀⠚⠃⠀⠀⡀⠀⠀⠀⠀⣠⣿⣿⣿⣿⣿⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧⢸⣿⣿⣿⢀⢃⠀⡁⠑ -// ⠰⠀⠂⠁⠀⠀⡆⠀⠀⠀⣴⣿⣿⣿⣿⣿⠋⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡈⣿⣿⣿⣇⠆⢃⠈⡇ -// ⠀⠀⠀⢠⠄⣠⣇⠀⢀⣾⣿⣿⣿⣿⣿⡃⢀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⣿⣿⣿⣿⢔⢌⢆⢑ -// ⠀⠀⠀⢀⣶⣿⣷⢀⣾⣿⣿⣿⣿⡯⡊⠀⢸⡏⣟⣻⣽⣭⣽⣛⡛⠿⠿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡯⡇⣿⣿⣿⣿⣗⢕⢕⠄ -// ⠀⢠⣶⣿⣿⢿⢃⣾⣿⣿⣿⣿⡫⡪⠀⠀⢸⡇⣿⣿⣿⣿⣿⣿⣿⣦⡘⠄⢹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡯⡇⣿⣿⣿⣿⣗⢔⢕⢅ -// ⠀⣺⣿⡉⠕⠁⣼⣿⣿⣿⡿⡫⡪⠂⠀⢔⢸⢀⠸⣿⣿⣿⣿⣿⣿⣿⣿⣶⣸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣪⠃⣿⣿⣿⣿⡟⢄⣡⣵ -// ⡛⠟⢋⠀⠀⢸⣿⣿⣿⣿⢠⢫⡊⠀⡔⢵⡈⢠⢣⡹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠡⠚⠓⠪⠉⠛⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡫⡎⣸⣿⣿⣿⡟⢠⢃⣂⡿ -// ⡃⡊⢃⡔⡠⣿⣿⣿⣿⡇⣜⡘⢀⢜⡀⡅⡆⠘⢐⠁⠙⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢾⠿⢿⣿⣿⣿⣿⣷⣾⣽⡻⢿⣿⣿⣿⣿⣿⣿⣿⡿⣛⣿⣿⣟⡜⠠⣻⣿⣿⡟⠴⠃⠉⠁⠀ -// ⡺⢠⡾⣸⡷⣿⣿⣿⣿⠂⣓⠂⡎⡺⠪⠒⠃⠀⠀⠀⠀⠀⠙⠿⣯⢻⣿⣿⣿⡟⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣵⣾⣿⡷⣲⣿⣿⣿⣿⣿⣿⣿⣷⣝⢿⣿⣿⣿⡿⡫⡺⣽⣿⣿⡗⠀⢸⣿⣿⠏⠀⠀⠀⠀⠀⠀ -// ⣴⣿⢣⣿⢳⣿⣿⣿⡿⡨⡊⠠⡪⡢⠂⠀⠀⠀⠀⠀⢀⣀⠀⠀⠘⢧⡻⣿⣿⡇⢿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣿⣿⣵⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⢳⣝⠿⡫⡪⣪⡮⣾⣿⡟⢠⠅⣿⠟⠋⠀⠀⠀⠀⠀⠀⠀ -// ⣿⡏⣾⢣⡾⣿⣿⣿⡇⢪⠂⡨⡠⠀⠀⠀⠀⢀⡴⠋⠉⠈⠉⢦⡀⠠⢅⠹⣿⢰⢹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢟⣫⣾⡿⣫⣦⠸⢊⡄⣼⣿⡟⢠⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ -// ⡿⢸⣏⣾⣇⣿⣿⣿⡇⢵⢀⢓⠀⠀⠀⠀⠀⡮⠀⠀⠀⠀⠀⠈⣇⠀⠧⡗⡈⢿⣺⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠟⣯⣾⣿⡿⠛⠊⣬⠴⢪⠋⣼⣿⠟⢔⢝⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ -// ⠃⡿⣸⣿⣿⢸⣿⣿⡇⡊⢰⢱⢂⡀⠀⠀⠀⢳⠀⠀⠀⠀⠀⢰⡇⠀⣸⢐⡈⢄⢿⠿⠿⢿⡿⠽⢫⣵⣿⡿⠿⠛⠛⠙⠉⠁⠀⠞⠙⠃⠍⡁⠖⡪⡫⡪⣻⢁⣾⡿⢃⢞⢕⡝⢰⣆⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀ -// ⢸⢣⣿⣿⡗⡈⢿⣿⡇⠀⢸⣿⣷⣵⣄⠀⠀⠈⠲⣠⣠⣠⡴⠋⠀⠀⢒⢔⢂⢀⢎⢇⣓⡕⠂⡚⡩⢡⢰⢂⣓⡣⣢⠒⠀⠀⠀⠀⠀⠀⠀⠈⠢⡈⡲⡑⢡⣾⠟⡠⡣⣂⢇⠃⣿⣿⣷⣦⡀⠀⠀⠀⠀⠀⠀⠀ -// ⡟⣾⣿⡿⣸⢸⡘⣿⣷⠀⢸⣿⣿⣿⣿⣿⣦⣄⣀⣀⣀⣀⣀⣠⣤⣜⢔⢕⢕⢔⢕⢕⣗⣇⣳⡪⣺⢐⢱⢑⢒⠖⠁⠀⠀⢀⠄⠔⠖⢦⣀⠀⠀⠰⠌⣴⠟⡡⡪⡪⡪⡜⡜⣸⣿⣿⣿⣿⡿⡢⠀⠀⠀⠀⠀⠀ -// ⢱⣿⣿⡣⢑⠔⡕⠘⣿⡀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣷⣷⣵⣕⣥⣣⣣⡣⡪⣐⢱⢠⡃⠀⠀⠀⣴⠁⠀⠀⠀⠀⠸⣆⠀⠀⠘⠀⡪⡪⡪⡪⡪⣸⢡⣿⣿⣿⣿⣿⡯⣺⡀⢤⡀⠀⠀⠀ -// ⣿⣿⣟⣝⣝⣺⠁⠀⠘⣧⢘⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣷⣇⣣⠣⡘⡀⠀⠀⠀⡟⠀⠀⠀⠀⠀⠨⡗⠀⠀⠀⠈⢮⠺⡪⢊⢫⠃⣾⣿⣿⣿⣿⣿⡪⡏⣦⡀⢹⣷⣤⣀ -// ⣿⣿⢑⢇⢆⠆⠀⠀⣸⣎⠸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣮⣧⣀⠀⠀⢻⡀⠀⠀⠀⢀⡼⠃⠀⠀⠀⠀⠸⢘⢜⡐⡕⣸⣿⣿⣿⣿⣿⣃⣟⣱⣿⣿⣧⢻⣿⣿ -// ⣿⣟⢕⢕⢼⠀⠀⠀⣿⣿⣧⡹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡟⢡⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄⠀⠉⠓⠒⠓⠋⠀⠀⠀⠀⠀⠠⠀⠱⡱⡨⢣⣿⣿⣿⣿⣿⢗⡯⣱⣿⣿⣿⢇⣄⠃⠙ -// ⣿⡒⡢⡢⡃⠀⠀⠀⣿⣿⣿⣷⣝⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣤⣄⣀⣀⣀⣀⢀⣀⡀⡠⣀⡢⡪⠂⣽⣿⣿⣿⣿⣟⡕⣺⣿⣿⣿⣏⣿⣷⢔⡄ -// ⡿⡸⢰⢔⠂⠀⠀⠀⢿⣿⣿⣿⣿⣯⣻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣯⢮⡊⡠⣰⣿⣿⣿⣿⡿⣡⣿⣿⣿⣿⡟⣾⣿⣿⢵⠅ -// ⣟⢕⢝⣗⠀⠀⠀⡹⡘⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⣫⡾⢱⣿⣿⣿⣿⡟⣼⣿⣿⣿⣿⡿⣹⣿⣿⡟⡹⢰ -// ⢕⢕⢕⢕⠠⡢⣪⢂⢇⢹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⣫⣾⣿⢣⣿⣿⣿⣿⣿⣻⣿⣿⣿⣿⡿⣱⣿⣿⣿⡫⡇⣼ -// ⢕⢕⢕⠅⣘⡪⡚⡔⠍⠂⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⣫⣾⣿⢟⢁⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢣⣿⣿⣿⣏⡼⢠⣿ -// ⢕⢕⢕⠀⣖⡪⡪⣂⠀⠐⠈⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⢟⣫⣵⡿⢟⣫⡴⢁⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠃⣸⣿⣿⣿⢒⠇⣼⣿ -// ⢕⢅⢕⠨⣃⢪⢨⢂⠀⠀⠀⠈⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣄⣳⢰⢬⡙⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣍⣛⠻⠿⠿⠿⢛⣯⡭⠶⣞⣛⣭⣵⣾⣿⠿⢁⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠃⢀⣿⣿⣿⡯⡝⢰⣿⣿ -// ⢝⢕⠇⢸⠱⡑⡁⠀⣀⠀⠀⠀⠈⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣷⣬⣃⣹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣶⣾⣿⣿⣿⣿⣿⣿⣿⡿⡱⢃⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠃⠀⣼⣿⣿⣿⣸⢡⣿⣿⣿ -// ⢕⢕⠅⢜⠕⡠⣢⣾⣿⣷⣤⣄⢀⣬⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⢛⠴⢡⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠁⠀⢠⣿⣿⣿⢝⢡⣿⣿⣿⣿ -// ⢕⢕⠅⣕⢥⡶⢀⠻⣿⣿⣿⣟⢿⣿⣿⣻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⢫⡐⣕⢡⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡟⠀⠀⢀⣿⣿⣿⠏⢁⣿⣿⣿⣿⣿ -// ⢕⢕⢠⢗⣛⣓⣁⢱⣾⣿⣿⣿⣷⣷⣽⣿⡻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢛⢔⠕⠓⢠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠀⠀⢀⣾⣿⡿⡣⢎⣿⣿⣿⣿⣿⣿ -// ⡕⡕⠠⡅⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡝⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠋⠐⣁⡴⡴⢣⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠃⠀⠀⢠⣾⠿⣫⠞⣱⣿⣿⣿⣿⣿⠟⡡ -// ⢅⢇⢘⢕⢍⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣎⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠿⠿⠛⠛⠛⠉⠁⠀⢀⢜⢕⢅⠕⣡⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠁⠀⠀⠐⡫⡠⠜⣡⣾⣿⣿⡿⠟⠫⠦⠪⠊ -// ⢕⢕⢸⣵⣷⣾⣿⣿⣿⣿⣟⢿⣿⣿⣿⣿⣿⣿⣿⣦⣤⡩⣉⣉⡩⣉⣉⠉⠉⠈⠀⠀⠀⠀⠀⠀⠀⢀⣀⡀⠀⠀⣤⢔⢗⢕⠕⣰⣿⣿⣿⣿⣿⣿⣿⣿⡿⢋⢔⢕⠕⣓⠏⣊⠰⠻⠟⠛⣉⠅⢄⠂⡪⡪⡪⠨ -// -// ~it actually won't be her, but one can dream~ -// -} - -Class CompanionLamp : Actor -{ - Vector3 Trail; - Array moff; - Actor parent; - bool justteleport; - - Default - { - Tag "$T_LAMP"; - +NOGRAVITY; - +NOTELEPORT; - +DONTSPLASH; - +INTERPOLATEANGLES; - +LOOKALLAROUND; - +FRIENDLY; - +NOBLOCKMONST; - Radius 4; - Height 16; - } - // random chance to spawn moths - void A_Moth() - { - // count up - special1++; - for ( int i=0; i= 30) ) return; - // spawn a moth at a random offset - double ang = FRandom[Moth](0,360); - double pt = FRandom[Moth](-30,30); - double dist = FRandom[Moth](10,30); - Vector3 ofs = SWWMUtility.Vec3FromAngles(ang,pt)*dist; - Vector3 spawnpos = level.Vec3Offset(Vec3Offset(0,0,height/2),ofs); - if ( !level.IsPointInLevel(spawnpos) ) return; - // higher chance of white moths if carrying the Mashiro plush - //int mchance = parent.FindInventory("MothPlushy")?3:9; - let m = LampMoth(Spawn(Random[Moth](0,9)?"LampMoth":"LampMoth2",spawnpos)); - if ( !m.TestMobjLocation() ) - { - m.Destroy(); - return; - } - let s = Spawn("SWWMSmallSmoke",m.pos); - s.alpha *= .3; - m.master = parent; - m.lamp = self; - m.trail = m.pos; - moff.Push(m); - SWWMUtility.AchievementProgressInc("moth",1,parent.player); - } - override void PostBeginPlay() - { - Super.PostBeginPlay(); - if ( !parent || !SWWMLamp(master) ) - { - Destroy(); - return; - } - Spawn("SWWMItemFog",pos); - Trail = pos; - } - - override void Tick() - { - Super.Tick(); - if ( !parent || !SWWMLamp(master) ) - { - Destroy(); - return; - } - if ( isFrozen() || (freezetics > 0) ) return; - // update trailing position - bool foundspot = false; - for ( int i=0; i<180; i+=5 ) - { - for ( int j=1; j>=-1; j-=2 ) - { - double ang = (parent.angle-180)+i*j; - Vector3 testpos = level.Vec3Offset(parent.pos,SWWMUtility.RotateVector3((32,0,parent.height-8+1.5*sin(level.maptime*3.)),ang)); - if ( !level.IsPointInLevel(testpos) ) continue; - Vector3 oldpos = pos; - Vector3 oldprev = prev; - Actor oldblockingmobj = blockingmobj; - Line oldblockingline = blockingline; - Sector oldblockingfloor = blockingfloor, oldblockingceiling = blockingceiling; - SetOrigin(testpos,false); - if ( !TestMobjLocation() || SWWMUtility.BlockingLineIsBlocking(self,Line.ML_BLOCKING|Line.ML_BLOCKEVERYTHING) || BlockingFloor || BlockingCeiling ) - { - SetOrigin(oldpos,false); - prev = oldprev; - blockingmobj = oldblockingmobj; - blockingline = oldblockingline; - blockingfloor = oldblockingfloor; - blockingceiling = oldblockingceiling; - continue; - } - SetOrigin(oldpos,false); - prev = oldprev; - blockingmobj = oldblockingmobj; - blockingline = oldblockingline; - blockingfloor = oldblockingfloor; - blockingceiling = oldblockingceiling; - Trail = testpos; - foundspot = true; - } - // check at most for a 45 degree offset - if ( foundspot && (i > 45) ) break; - } - Vector3 diff = level.Vec3Diff(pos,parent.pos); - if ( (diff.length() > 400) || justteleport ) - { - Vector3 rel = level.Vec3Diff(pos,trail); - justteleport = false; - Actor f = Spawn("SWWMItemFog",pos); - f.A_StartSound("lamp/disappear",CHAN_VOICE); - // carry over the moths - foreach ( m:moff ) - { - if ( !m ) continue; - Vector3 whereto = level.Vec3Offset(m.pos,rel); - if ( !level.IsPointInLevel(whereto) ) - continue; - Vector3 oldp = m.pos; - m.SetOrigin(whereto,false); - if ( !m.TestMobjLocation() ) - m.SetOrigin(oldp,false); - } - SetOrigin(trail,false); - angle = AngleTo(parent); - vel *= 0.; - f = Spawn("SWWMItemFog",pos); - f.A_StartSound("lamp/appear",CHAN_VOICE); - return; - } - angle += Clamp(deltaangle(angle,AngleTo(parent)),-5.,5.); - vel *= .8; - bool blocked = false; - if ( SWWMUtility.BlockingLineIsBlocking(self,Line.ML_BLOCKING|Line.ML_BLOCKEVERYTHING) ) - { - // push away from wall - Vector3 normal = (-BlockingLine.delta.y,BlockingLine.delta.x,0).unit(); - if ( !SWWMUtility.PointOnLineSide(pos.xy,BlockingLine) ) normal *= -1; - vel += 4.*normal; - blocked = true; - } - if ( BlockingFloor ) - { - // push away from floor - Vector3 normal = BlockingFloor.floorplane.Normal; - // find closest 3d floor for its normal - for ( int i=0; i -16) && (diff.x < 16) && (diff.y > -16) && (diff.y < 16) && (diff.z > -16) && (diff.z < parent.height+8) ) - { - if ( diff.x < 0 ) vel.x -= .2; - else vel.x += .2; - if ( diff.y < 0 ) vel.y -= .2; - else vel.y += .2; - if ( diff.z < 0 ) vel.z -= .2; - else vel.z += .2; - blocked = true; - } - if ( blocked ) return; - Vector3 dir = level.Vec3Diff(pos,trail); - if ( dir.length() > 0 ) - vel += dir.unit()*min(dir.length()*.05,20.); - } - States - { - Spawn: - XZW1 A 1 - { - if ( SWWMLamp(master) && SWWMLamp(master).bActive ) - { - A_StartSound("lamp/on",CHAN_ITEMEXTRA,CHANF_OVERLAP); - return ResolveState("Active"); - } - return ResolveState(null); - } - Wait; - Active: - XZW1 B 1 - { - A_Moth(); - if ( !SWWMLamp(master) || !SWWMLamp(master).bActive ) - { - A_StartSound("lamp/off",CHAN_ITEMEXTRA,CHANF_OVERLAP); - return ResolveState("Spawn"); - } - return ResolveState(null); - } - Wait; - } -} - -Class SWWMLamp : Inventory -{ - Mixin SWWMOverlapPickupSound; - Mixin SWWMUseToPickup; - Mixin SWWMRespawn; - Mixin SWWMPickupGlow; - - bool bActive, bActivated; - TextureID OnIcon; - Actor thelamp; - int charge; - - Property Charge : charge; - - override Inventory CreateCopy( Actor other ) - { - // additional lore - SWWMLoreLibrary.Add(other.player,"MothLamp"); - return Super.CreateCopy(other); - } - override bool HandlePickup( Inventory item ) - { - // add charge - if ( item.GetClass() == GetClass() ) - { - if ( (Charge >= Default.Charge) && (Amount+item.Amount > MaxAmount) ) - { - // sell excess - int sellprice = abs(Stamina)/2; - SWWMScoreObj.Spawn(sellprice,level.Vec3Offset(Owner.pos,SWWMUtility.Vec3FromAngles(FRandom[ScoreBits](0,360),FRandom[ScoreBits](-90,90))*8.+(0,0,Owner.Height/2))); - SWWMCredits.Give(Owner.player,sellprice); - if ( Owner.player ) - { - if ( Owner.player == players[consoleplayer] ) Console.Printf(StringTable.Localize(SWWMUtility.SellFemaleItem(item)?"$SWWM_SELLEXTRA_FEM":"$SWWM_SELLEXTRA"),GetTag(),sellprice); - else Console.Printf(StringTable.Localize(SWWMUtility.SellFemaleItem(item)?"$SWWM_SELLEXTRAREM_FEM":"$SWWM_SELLEXTRAREM"),Owner.player.GetUserName(),GetTag(),sellprice); - } - } - else if ( Charge > 0 ) - { - int AddCharge = Charge+SWWMLamp(item).Charge; - Charge = min(Default.Charge,AddCharge); - // if there's charge to spare, increase amount - if ( AddCharge > Charge ) - { - if ( (Amount > 0) && (Amount+item.Amount < 0) ) Amount = int.max; - Amount = min(MaxAmount,Amount+item.Amount); - Charge = AddCharge-Charge; - } - } - else - { - if ( (Amount > 0) && (Amount+item.Amount < 0) ) Amount = int.max; - // new copy, increase and take its charge - Amount = min(MaxAmount,Amount+item.Amount); - Charge = SWWMLamp(item).Charge; - } - item.bPickupGood = true; - return true; - } - return Super.HandlePickup(item); - } - override bool Use( bool pickup ) - { - if ( pickup && !deathmatch ) return false; - bActivated = true; - bActive = !bActive; - if ( !OnIcon ) OnIcon = TexMan.CheckForTexture("graphics/HUD/Icons/I_Lamp.png"); - Icon = bActive?OnIcon:default.Icon; - // don't consume on use - Amount++; - return true; - } - override bool ShouldSpawn() - { - if ( deathmatch ) return false; - return Super.ShouldSpawn(); - } - override void PreTravelled() - { - // remove the lamp - if ( thelamp ) thelamp.Destroy(); - } - override void DoEffect() - { - Super.DoEffect(); - if ( !thelamp && bActivated ) - { - thelamp = Spawn("CompanionLamp",level.Vec3Offset(Owner.pos,SWWMUtility.RotateVector3((20,0,24),Owner.angle))); - CompanionLamp(thelamp).parent = Owner; - thelamp.master = self; - let f = Spawn("SWWMItemFog",thelamp.pos); - f.A_StartSound("lamp/appear",CHAN_VOICE); - } - if ( bActive && !(level.maptime%35) && !isFrozen() ) Charge--; - if ( Charge <= 0 ) - { - Amount--; - if ( Amount <= 0 ) DepleteOrDestroy(); - else Charge = default.Charge; - } - } - override void DetachFromOwner() - { - Super.DetachFromOwner(); - if ( thelamp ) - { - let f = Spawn("SWWMItemFog",thelamp.pos); - f.A_StartSound("lamp/disappear",CHAN_VOICE); - thelamp.Destroy(); - } - Icon = default.Icon; - bActive = false; - bActivated = false; - } - clearscope bool isBlinking() const - { - return ( (Charge < 10) && (level.maptime&8) ); - } - Default - { - Tag "$T_LAMP"; - Inventory.Icon "graphics/HUD/Icons/I_LampOff.png"; - Inventory.PickupSound "misc/p_pkup"; - Inventory.PickupMessage "$I_LAMP"; - Inventory.Amount 1; - Inventory.MaxAmount 5; - Inventory.InterHubAmount 5; - Inventory.PickupFlash "SWWMPurplePickupFlash"; - +INVENTORY.ALWAYSPICKUP; - +INVENTORY.AUTOACTIVATE; - +INVENTORY.INVBAR; - +COUNTITEM; - +INVENTORY.BIGPOWERUP; - +FLOATBOB; - +DONTGIB; - FloatBobStrength 0.25; - SWWMLamp.Charge 100; - Stamina 70000; - } - States - { - Spawn: - XZW1 A -1; - Stop; - } -} - Class BarrierLight : PointLightAttenuated { Default @@ -2193,2062 +1446,3 @@ Class EBarrier : Inventory } Class EBarrierX : SWWMItemOverlay {} - -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 = SWWMUtility.PitchTo(t,targ,.5); - 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=2)?default.Icon:(primetim>=6)?(bFailed?OnIcon[1]:OnIcon[2]):OnIcon[0]; - if ( !bFailed && (primetim == 20) ) Owner.A_StartSound("hahaha/hahaha",CHAN_POWERUP,CHANF_OVERLAP); - primetim++; - if ( (primetim <= 20) || (!bFailed && (primetim <= 50)) ) return; - primetim = 0; - bPrimed = false; - Icon = default.Icon; - if ( bFailed ) return; - Vector3 safepos; - double safeangle; - if ( deathmatch ) [safepos, safeangle] = level.PickDeathmatchStart(); - else [safepos, safeangle] = level.PickPlayerStart(Owner.PlayerNumber()); - if ( !level.useplayerstartz ) safepos.z = ONFLOORZ; - Vector3 oldpos = Owner.pos; - if ( !Owner.Teleport(safepos,safeangle,0) ) - { - if ( Owner is 'Demolitionist' ) - Demolitionist(Owner).lastbump *= .95; - Owner.A_StartSound("powerup/tethererror",CHAN_ITEMEXTRA,CHANF_OVERLAP); - if ( Owner.player == players[consoleplayer] ) Console.Printf(StringTable.Localize("$D_TETHERFAIL")); - return; - } - let s = Spawn("DemolitionistShockwave",oldpos); - s.target = Owner; - s.special1 = 120; - s.A_AlertMonsters(0,AMF_EMITFROMTARGET); - s = Spawn("DemolitionistShockwave",Owner.pos); - s.target = Owner; - s.special1 = 120; - s.A_AlertMonsters(0,AMF_EMITFROMTARGET); - if ( Owner.player == players[consoleplayer] ) - { - Owner.A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP); - Owner.A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP,pitch:.7); - Owner.A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP,pitch:.4); - } - SWWMHandler.DoFlash(Owner,Color(255,255,255,255),10); - SWWMHandler.DoFlash(Owner,Color(255,128,192,255),30); - Owner.GiveBody(100,100); - SWWMUtility.AchievementProgressInc("sneaky",1,Owner.player); - Amount--; - if ( Amount <= 0 ) DepleteOrDestroy(); - } - - override bool Use( bool pickup ) - { - if ( pickup || bPrimed ) return false; - if ( !OnIcon[0] ) OnIcon[0] = TexMan.CheckForTexture("graphics/HUD/Icons/I_SafetyOn.png"); - if ( !OnIcon[1] ) OnIcon[1] = TexMan.CheckForTexture("graphics/HUD/Icons/I_SafetyNo.png"); - if ( !OnIcon[2] ) OnIcon[2] = TexMan.CheckForTexture("graphics/HUD/Icons/I_SafetyYes.png"); - bPrimed = true; - primetim = 0; - Vector3 safepos; - if ( deathmatch ) safepos = level.PickDeathmatchStart(); - else safepos = level.PickPlayerStart(Owner.PlayerNumber()); - if ( !level.useplayerstartz ) - { - // find the floor for proper distance check - let s = level.PointInSector(safepos.xy); - safepos.z = s.floorplane.ZAtPoint(safepos.xy); - } - bFailed = (level.Vec3Diff(Owner.pos,safepos).length() < 400); - if ( (Owner.player == players[consoleplayer]) || bBigPowerup ) Owner.A_StartSound(bFailed?"powerup/tetherfail":"powerup/tetheruse",CHAN_ITEMEXTRA,CHANF_OVERLAP); - // don't consume on use, will happen later - Amount++; - return true; - } - - Default - { - Tag "$T_SAFETY"; - Stamina 240000; - Inventory.Icon "graphics/HUD/Icons/I_Safety.png"; - Inventory.PickupSound "misc/p_pkup"; - Inventory.UseSound ""; - Inventory.PickupMessage "$T_SAFETY"; - Inventory.MaxAmount 5; - Inventory.InterHubAmount 5; - Inventory.PickupFlash "SWWMPurplePickupFlash"; - +INVENTORY.ALWAYSPICKUP; - +INVENTORY.AUTOACTIVATE; - +INVENTORY.INVBAR; - +COUNTITEM; - +INVENTORY.BIGPOWERUP; - +FLOATBOB; - +DONTGIB; - FloatBobStrength 0.25; - } - States - { - Spawn: - XZW1 A 33; - XZW1 B 2; - Loop; - } -} - -Class AngeryLight : PointLightAttenuated -{ - Default - { - Args 224,0,255,80; - } - 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); - args[LIGHT_INTENSITY] = Random[Invinciball](10,12)*8; - bDORMANT = Powerup(master).isBlinking(); - } -} -Class AngerySnd : SWWMNonInteractiveActor -{ - override void Tick() - { - if ( !target || !master ) - { - Destroy(); - return; - } - SetOrigin(target.pos,true); - if ( players[consoleplayer].Camera == target ) - { - A_SoundVolume(CHAN_VOICE,0.); - A_SoundVolume(CHAN_7,.5); - } - else - { - A_SoundVolume(CHAN_VOICE,.4); - A_SoundVolume(CHAN_7,0.); - } - } - override void PostBeginPlay() - { - Super.PostBeginPlay(); - A_StartSound("powerup/devastationact",CHAN_VOICE,CHANF_LOOP,.4,1.5); - A_StartSound("powerup/devastationact",CHAN_7,CHANF_LOOP,.5,ATTN_NONE); - } - override void OnDestroy() - { - Super.OnDestroy(); - A_StopSound(CHAN_VOICE); - A_StopSound(CHAN_7); - } -} -Class AngeryPower : Powerup -{ - Mixin SWWMShadedPowerup; - - Actor l, snd; - int lasteffect; - transient int lastpulse; - - Default - { - Powerup.Duration -50; - Inventory.Icon "graphics/HUD/Icons/I_Devastation.png"; - Powerup.Color "C0 00 FF", 0.2; - +INVENTORY.ADDITIVETIME; - } - - override void InitEffect() - { - Super.InitEffect(); - if ( !Owner ) return; - Owner.A_AlertMonsters(swwm_uncapalert?0:5000); - 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.); - lasteffect = int.min; - lastpulse = max(lastpulse,gametic+35); - if ( Owner is 'Demolitionist' ) - Demolitionist(Owner).lastbump *= .95; - l = Spawn("AngeryLight",Owner.pos); - l.target = Owner; - l.master = self; - } - override void DoEffect() - { - Super.DoEffect(); - if ( !Owner ) return; - if ( !snd ) snd = Spawn("AngerySnd",Owner.pos); - snd.target = Owner; - snd.master = self; - } - - override void EndEffect() - { - Super.EndEffect(); - if ( !Owner ) return; - Owner.A_StartSound("powerup/devastationend",CHAN_ITEMEXTRA,CHANF_OVERLAP); - SWWMHandler.DoFlash(Owner,Color(128,224,0,255),30); - Owner.A_QuakeEx(4,4,4,20,0,1,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,rollIntensity:1.); - Owner.A_AlertMonsters(swwm_uncapalert?0:2000); - if ( Owner is 'Demolitionist' ) - Demolitionist(Owner).lastbump *= .9; - if ( (EffectTics <= 0) && Owner && Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$D_DEVASTATION")); - } - - void DoHitFX() - { - if ( level.maptime <= lasteffect+5 ) return; - Owner.A_AlertMonsters(swwm_uncapalert?0:5000); - SWWMHandler.DoFlash(Owner,Color(64,224,0,255),10); - Owner.A_QuakeEx(8,8,8,Random[Rage](3,8),0,1,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,rollIntensity:1.); - Owner.A_StartSound("powerup/devastationhit",CHAN_POWERUP,CHANF_OVERLAP); - lasteffect = level.maptime; - lastpulse = max(lastpulse,gametic+35); - if ( Owner is 'Demolitionist' ) - Demolitionist(Owner).lastbump *= .9; - } - - override void ModifyDamage( int damage, Name damageType, out int newdamage, bool passive, Actor inflictor, Actor source, int flags ) - { - if ( passive || (damage <= 0) ) return; - if ( damageType == 'Mortal' ) // can only be in 4s - { - // max cap is the closest combination of 4s smaller than (2^31-1) - if ( damage > 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 0) && victim ) - { - victim.A_StartSound("leadball/hitf",CHAN_DAMAGE,CHANF_OVERLAP,(vel.length()/30.)**.5); - victim.TraceBleed(newdmg,self); - victim.SpawnBlood(pos,atan2(dir.y,dir.x),dmg); - } - } - else - { - A_StartSound("leadball/hit",CHAN_VOICE,CHANF_OVERLAP,(vel.length()/30.)**.5); - if ( victim ) victim.A_StartSound("leadball/hit",CHAN_DAMAGE,CHANF_OVERLAP,(vel.length()/30.)**.5); - if ( vel.length() > 15. ) - { - let s = Spawn("BallImpact",pos); - s.angle = atan2(dir.y,dir.x); - s.pitch = asin(-dir.z); - } - } - // make it so the crit does not propagate to friendlies unless we bonked a friend (you monster!) - if ( crit ) - SWWMUtility.DoExplosion(self,dmg/2,25000,150,80,((victim.isFriend(target))?0:DE_NOHURTFRIEND)|DE_NONEXPLOSIVE,crit?'CriticalConcussion':'Concussion',target,DMG_FOILINVUL); - if ( crit && victim && (victim.Health <= 0) && (victim.bBOSS || victim.FindInventory("BossMarker")) && target ) - SWWMUtility.MarkAchievement("clonk",target.player); - // only rip shootables - if ( (slamforce > girth) && is_schutt ) - { - vel *= .7; - return 1; - } - // force bounce - BlockingMobj = victim; - A_HandleBounce(); - lasthit = victim; - // pretend to pass through - return 1; - } - override void PostBeginPlay() - { - Super.PostBeginPlay(); - A_StartSound("leadball/fly",CHAN_WEAPON,CHANF_LOOP,.6,3.,2.); - heat = 1.; - } - override void Tick() - { - oldvel = vel; - Super.Tick(); - if ( isFrozen() || (freezetics > 0) ) return; - if ( InStateSequence(CurState,ResolveState("Death")) ) - { - deadtimer++; - if ( deadtimer > 300 ) - { - let numpt = Random[Spreadgun](3,6); - for ( int i=0; i 15 ) - { - let s = Spawn("BallImpact",pos); - s.angle = atan2(HitNormal.y,HitNormal.x); - s.pitch = asin(-HitNormal.z); - } - } - gravity = .35; - if ( (vel.length() < 5) && (pos.z <= floorz) ) - { - ClearBounce(); - ExplodeMissile(); - } - } - States - { - Spawn: - XZW1 A -1; - Stop; - Bounce: - XZW1 A 0 A_HandleBounce(); - Goto Spawn; - Death: - XZW1 A -1 - { - bMOVEWITHSECTOR = true; - A_StopSound(CHAN_WEAPON); - } - Stop; - } -} - -// TBD 1.4 Battle-Boi Sentry - -Class SaltTracer : LineTracer -{ - Actor ignore; - Array ShootThroughList; - Array WaterHitList; - - override ETraceStatus TraceCallback() - { - // liquid splashes - if ( Results.CrossedWater ) - { - let hl = new("WaterHit"); - hl.sect = Results.CrossedWater; - hl.hitpos = Results.CrossedWaterPos; - WaterHitList.Push(hl); - } - else if ( Results.Crossed3DWater ) - { - let hl = new("WaterHit"); - hl.sect = Results.Crossed3DWater; - hl.hitpos = Results.Crossed3DWaterPos; - WaterHitList.Push(hl); - } - if ( Results.HitType == TRACE_HitActor ) - { - if ( 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) ) - return TRACE_Stop; - ShootThroughList.Push(Results.HitLine); - return TRACE_Skip; - } - return TRACE_Stop; - } -} - -Class SaltLight : PaletteLight -{ - Default - { - Tag "SaltExpl,1"; - ReactionTime 30; - Args 0,0,0,240; - } -} -Class SaltLight2 : PaletteLight -{ - Default - { - Tag "SaltExpl"; - ReactionTime 30; - Args 0,0,0,70; - } -} - -Class SaltImpact : SWWMNonInteractiveActor -{ - Default - { - Obituary "$O_SALTSHOT"; - DamageType "Salt"; - RenderStyle "Add"; - Scale 1.8; - +NODAMAGETHRUST; - +FORCERADIUSDMG; - +FORCEXYBILLBOARD; - +FOILINVUL; - } - override void PostBeginPlay() - { - Super.PostBeginPlay(); - A_AlertMonsters(swwm_uncapalert?0:6000,AMF_EMITFROMTARGET); - SWWMUtility.DoExplosion(self,30+special2*4,15000,100,40); - A_QuakeEx(3,3,3,10,0,250,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,falloff:150,rollintensity:0.2); - A_StartSound("saltshot/hit",CHAN_VOICE,attenuation:.35); - A_SprayDecal("ShockMarkSmall",-172); - A_SprayDecal("SaltMark",-172); - Scale *= FRandom[ExploS](0.8,1.1); - int numpt = Random[ExploS](5,9)-special1; - for ( int i=0; i 20) && !Random[Spreadgun](0,800/args[0]) ) - { - let i = Spawn("SaltImpact",level.Vec3Offset(pos,x*speed)); - i.angle = atan2(x.y,x.x); - i.pitch = asin(-x.z); - i.target = target; - i.special1 = (Stamina-9)/4; - i.special2 = Accuracy; - i.args[0] = args[1]; - return; - } - // next beam - if ( !(special2%4) && !Random[Spreadgun](0,Stamina) ) - Spawn("SaltLight",level.Vec3Offset(pos,x*speed/2)); - let next = Spawn("SaltBeam",level.Vec3Offset(pos,x*speed)); - double a = FRandom[Spreadgun](0,360), s = FRandom[Spreadgun](0,.06); - Vector3 dir = SWWMUtility.ConeSpread(x,y,z,a,s); - next.angle = atan2(dir.y,dir.x); - next.pitch = asin(-dir.z); - next.target = target; - next.special2 = (special2+1)%10; - next.args[0] = args[0]+1; - next.args[1] = args[1]; - next.SetStateLabel("TrailSpawn"); - } - - override void PostBeginPlay() - { - Super.PostBeginPlay(); - if ( !Random[Spreadgun](0,3) ) - A_StartSound("saltshot/trail",CHAN_VOICE,CHANF_DEFAULT,.3,4.); - } - - override void Tick() - { - if ( freezetics > 0 ) - { - freezetics--; - return; - } - if ( isFrozen() ) return; - A_FadeOut(.04); - if ( Random[Spreadgun](-2,args[2]/10) == 0 ) - SWWMUtility.DoExplosion(self,5+Accuracy,5000,speed,flags:DE_HOWL|DE_NONEXPLOSIVE,ignoreme:target); - if ( ((special2%4) || args[2]) && !special1 ) SpreadOut(); - args[2]++; - if ( !CheckNoDelay() || (tics == -1) ) return; - if ( tics > 0 ) tics--; - while ( !tics ) - { - if ( !SetState(CurState.NextState) ) - return; - } - } - - States - { - Spawn: - XZW1 A -1 Bright NoDelay - { - return FindState("StarterDev")+Random[Spreadgun](0,11)*2; - } - Stop; - TrailSpawn: - XZW2 A -1 Bright - { - return FindState("TrailerDev")+Random[Spreadgun](0,11)*2; - } - Stop; - StarterDev: - #### # 25 Bright; - XZW1 B -1 Bright; - Stop; - #### # 25 Bright; - XZW1 C -1 Bright; - Stop; - #### # 25 Bright; - XZW1 D -1 Bright; - Stop; - #### # 25 Bright; - XZW1 E -1 Bright; - Stop; - #### # 25 Bright; - XZW1 F -1 Bright; - Stop; - #### # 25 Bright; - XZW1 G -1 Bright; - Stop; - #### # 25 Bright; - XZW1 H -1 Bright; - Stop; - #### # 25 Bright; - XZW1 I -1 Bright; - Stop; - #### # 25 Bright; - XZW1 J -1 Bright; - Stop; - #### # 25 Bright; - XZW1 K -1 Bright; - Stop; - #### # 25 Bright; - XZW1 L -1 Bright; - Stop; - #### # 25 Bright; - XZW1 M -1 Bright; - Stop; - TrailerDev: - #### # 25 Bright; - XZW2 B -1 Bright; - Stop; - #### # 25 Bright; - XZW2 C -1 Bright; - Stop; - #### # 25 Bright; - XZW2 D -1 Bright; - Stop; - #### # 25 Bright; - XZW2 E -1 Bright; - Stop; - #### # 25 Bright; - XZW2 F -1 Bright; - Stop; - #### # 25 Bright; - XZW2 G -1 Bright; - Stop; - #### # 25 Bright; - XZW2 H -1 Bright; - Stop; - #### # 25 Bright; - XZW2 I -1 Bright; - Stop; - #### # 25 Bright; - XZW2 J -1 Bright; - Stop; - #### # 25 Bright; - XZW2 K -1 Bright; - Stop; - #### # 25 Bright; - XZW2 L -1 Bright; - Stop; - #### # 25 Bright; - XZW2 M -1 Bright; - Stop; - } -} diff --git a/zscript/items/swwm_powerups_vip.zsc b/zscript/items/swwm_powerups_vip.zsc new file mode 100644 index 000000000..f4b1c8bb8 --- /dev/null +++ b/zscript/items/swwm_powerups_vip.zsc @@ -0,0 +1,1289 @@ +// 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 = SWWMUtility.PitchTo(t,targ,.5); + 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 ShootThroughList; + Array WaterHitList; + + override ETraceStatus TraceCallback() + { + // liquid splashes + if ( Results.CrossedWater ) + { + let hl = new("WaterHit"); + hl.sect = Results.CrossedWater; + hl.hitpos = Results.CrossedWaterPos; + WaterHitList.Push(hl); + } + else if ( Results.Crossed3DWater ) + { + let hl = new("WaterHit"); + hl.sect = Results.Crossed3DWater; + hl.hitpos = Results.Crossed3DWaterPos; + WaterHitList.Push(hl); + } + if ( Results.HitType == TRACE_HitActor ) + { + if ( Results.HitActor == ignore ) return TRACE_Skip; + if ( Results.HitActor.bSHOOTABLE || (Results.HitActor is 'YnykronSingularityHitbox') ) 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) ) + return TRACE_Stop; + ShootThroughList.Push(Results.HitLine); + return TRACE_Skip; + } + return TRACE_Stop; + } +} + +Class YnykronHaloTail : SWWMNonInteractiveActor +{ + Default + { + RenderStyle "Add"; + +FORCEXYBILLBOARD; + Scale 1.5; + } + override void Tick() + { + if ( freezetics > 0 ) + { + freezetics--; + return; + } + if ( isFrozen() ) return; + A_SetScale(scale.x+.5); + A_FadeOut(.2); + } + States + { + Spawn: + MHAL A -1 Bright; + Stop; + } +} + +Class YnykronHalo : SWWMNonInteractiveActor +{ + Default + { + RenderStyle "Add"; + +FORCEXYBILLBOARD; + Scale 1.5; + } + override void Tick() + { + if ( freezetics > 0 ) + { + freezetics--; + return; + } + if ( isFrozen() ) return; + if ( !target || target.InStateSequence(target.CurState,target.FindState("Death")) ) + { + Destroy(); + return; + } + SetOrigin(target.pos,true); + A_SetScale(1.5*target.scale.x); + let h = Spawn("YnykronHaloTail",pos); + h.scale = scale; + } + States + { + Spawn: + MHAL A -1 Bright; + Stop; + } +} + +Class GatherDust : SWWMNonInteractiveActor +{ + Mixin SWWMMinimalMovingTick; + + override void PostBeginPlay() + { + SetState(FindState("Spawn")+Random[ExploS](0,19)); + Scale *= FRandom[ExploS](.75,1.5); + Scale.x *= RandomPick[ExploS](-1,1); + Scale.y *= RandomPick[ExploS](-1,1); + SetShade(Color(4,3,5)*Random[ExploS](5,20)); + } + + void A_Gravitate() + { + if ( target && !target.InStateSequence(target.CurState,target.FindState("Death")) ) + { + if ( alpha < .08 ) A_FadeIn(FRandom[ExploS](.0002,.004)); + Vector3 dirto = level.Vec3Diff(pos,target.pos); + double distto = dirto.length(); + if ( distto < (32.*target.scale.x) ) + { + // sucked in + target.specialf2 += FRandom[ExploS](.5,2.); + Destroy(); + return; + } + dirto /= distto; + double mxdist = 5000.*target.scale.x; + vel = dirto*25.*(clamp((mxdist-distto)/mxdist,.5,1.)**4.); + vel += target.vel; + return; + } + vel *= .98; + A_FadeOut(.002); + } + + Default + { + RenderStyle "Shaded"; + Alpha 0.; + Scale 3.; + +FORCEXYBILLBOARD; + } + States + { + Spawn: + DUST ABCDEFGHIJKLMNOPQRST 1 A_Gravitate(); + Wait; + } +} + +Class YnykronVoidBeamTail : SWWMNonInteractiveActor +{ + Default + { + RenderStyle "Add"; + +FORCEXYBILLBOARD; + } + override void Tick() + { + if ( freezetics > 0 ) + { + freezetics--; + return; + } + if ( isFrozen() ) return; + A_FadeOut(); + } + States + { + Spawn: + XZW1 # -1 Bright; + Stop; + } +} + +Class YnykronVoidBeam : SWWMNonInteractiveActor +{ + Actor basebeam, prevbeam, nextbeam; + YnykronTracer t; + double baseang, basept; + int angledir, pitchdir; + int maxlen, curlen, segnum; + int spreadtimer; + int maxlife, lifetimer; + + Default + { + DamageType 'YnykronAlt'; + Obituary "$O_YNYKRONALT"; + RenderStyle "Add"; + Alpha 0.; + Stamina 3; + +INTERPOLATEANGLES; + +NODAMAGETHRUST; + +FOILINVUL; + } + + override void PostBeginPlay() + { + angle = baseang = FRandom[Ynykron](0,360); + pitch = basept = FRandom[Ynykron](-90,90); + angledir = RandomPick[Ynykron](-1,1); + pitchdir = RandomPick[Ynykron](-1,1); + maxlen = Random[Ynykron](40,60); + maxlife = Random[Ynykron](20,30); + Scale *= FRandom[Ynykron](.75,1.5); + lifetimer = -1; + } + + void A_UpdateBeam() + { + if ( !master ) + { + Destroy(); + return; + } + if ( basebeam == self ) + { + baseang += FRandom[Ynykron](0.,5.)*angledir; + basept += FRandom[Ynykron](0.,5.)*pitchdir; + Vector3 ofs = master.scale.x*32.*SWWMUtility.Vec3FromAngles(baseang,basept); + SetOrigin(level.Vec3Offset(master.pos,ofs),true); + double da = deltaangle(angle,baseang+FRandom[Ynykron](-5,5)), + dp = deltaangle(pitch,basept+FRandom[Ynykron](-5,5)); + angle += da*.2; + pitch += dp*.2; + if ( lifetimer == -1 ) + { + A_FadeIn(FRandom[Ynykron](.003,.05)); + if ( Alpha >= .2 ) + { + Alpha = .2; + lifetimer = 0; + } + } + else if ( lifetimer == -2 ) A_FadeOut(FRandom[Ynykron](.003,.05)); + else + { + lifetimer++; + if ( lifetimer > maxlife ) lifetimer = -2; + } + // check total length + int cnt = 0; + for ( YnykronVoidBeam b=self; b; b=YnykronVoidBeam(b.nextbeam) ) cnt++; + curlen = cnt; + } + else if ( !prevbeam ) + { + Destroy(); + return; + } + Vector3 x = SWWMUtility.Vec3FromAngles(angle,pitch); + if ( !t ) t = new("YnykronTracer"); + t.ShootThroughList.Clear(); + t.WaterHitList.Clear(); + t.HitList.Clear(); + t.Trace(pos,cursector,x,16*scale.x,TRACE_HitSky); + foreach ( l:t.ShootThroughList ) + { + l.Activate(target,0,SPAC_PCross); + l.Activate(target,0,SPAC_Impact); + } + foreach ( hit:t.hitlist ) + { + // yoink + SWWMUtility.DoKnockback(hit.HitActor,-hit.x,80000*scale.x*alpha); + } + spreadtimer++; + if ( (t.Results.HitType != TRACE_HitNone) || (segnum > maxlen) || (spreadtimer < stamina) ) + { + // trigger walls + if ( t.Results.HitType == TRACE_HitWall ) + t.Results.HitLine.RemoteActivate(target,t.Results.Side,SPAC_Impact,t.Results.HitPos); + // end of the line + if ( nextbeam ) nextbeam.Destroy(); + if ( basebeam == self ) frame = 3; + else frame = 2; + } + else + { + if ( basebeam == self ) frame = 0; + else frame = 1; + // spawn (or relocate) next beam + if ( !nextbeam ) + { + nextbeam = Spawn("YnykronVoidBeam",level.Vec3Offset(pos,x*16*scale.x)); + nextbeam.angle = atan2(t.Results.HitVector.y,t.Results.HitVector.x); + nextbeam.pitch = asin(-t.Results.HitVector.z); + } + else nextbeam.SetOrigin(level.Vec3Offset(pos,x*16*scale.x),true); + nextbeam.target = target; + nextbeam.master = master; + YnykronVoidBeam(nextbeam).basebeam = basebeam; + YnykronVoidBeam(nextbeam).prevbeam = self; + YnykronVoidBeam(nextbeam).maxlen = maxlen; + YnykronVoidBeam(nextbeam).segnum = segnum+1; + nextbeam.alpha = alpha*(1.-(segnum/double(maxlen))); + nextbeam.scale = scale; + nextbeam.frame = 1; + Vector3 newdir = t.Results.HitVector; + newdir = (newdir+.4*SWWMUtility.Vec3FromAngles(FRandom[Ynykron](0,360),FRandom[Ynykron](-90,90))).unit(); + double da = deltaangle(nextbeam.angle,atan2(newdir.y,newdir.x)), + dp = deltaangle(nextbeam.pitch,asin(-newdir.z)); + nextbeam.angle += da*.2; + nextbeam.pitch += dp*.2; + } + let h = Spawn("YnykronVoidBeamTail",pos); + h.angle = angle; + h.pitch = pitch; + h.alpha = alpha*2.; + h.scale = scale; + h.frame = frame; + } + + States + { + Spawn: + XZW1 # 1 Bright A_UpdateBeam(); + Wait; + } +} + +Class YnykronLightningImpact : SWWMNonInteractiveActor +{ + Default + { + Obituary "$O_YNYKRONALT"; + DamageType "Electric"; + +FOILINVUL; + +FORCERADIUSDMG; + +NODAMAGETHRUST; + } + override void PostBeginPlay() + { + Super.PostBeginPlay(); + SWWMUtility.DoExplosion(self,400,120000,100,40); + A_QuakeEx(3,3,3,12,0,800,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,falloff:300,rollIntensity:.4); + A_SprayDecal("ShockMark",-172); + int numpt = Random[ExploS](8,16); + for ( int i=0; i 10 ) + { + dirto /= dist; + dir = (dir+.8*dirto*(clamp(1.-(dist/1500.),0.,1.)**1.5)).unit(); + } + nextdir = dir; + } + void A_Spread( Sound arcsnd ) + { + Vector3 tdir = level.Vec3Diff(pos,nextpos); + if ( (GetClass() == 'YnykronLightningArc') && !Random[Ynykron](0,3) ) + { + for ( int i=0; i<3; i++ ) + { + let r = Spawn("YnykronLightningArcSub",level.Vec3Offset(pos,tdir*FRandom[Ynykron](0,1))); + Vector3 x, y, z; + [x, y, z] = SWWMUtility.GetAxes(angle,pitch,roll); + double a = FRandom[Ynykron](0,360), s = FRandom[Sparkster](0.,1.); + Vector3 sdir = SWWMUtility.ConeSpread(x,y,z,a,s); + r.angle = atan2(sdir.y,sdir.x); + r.pitch = asin(-sdir.z); + r.target = target; + r.special1 = 1; + YnykronLightningArc(r).destpos = destpos; + r.ReactionTime += Random[Ynykron](-3,3); + } + } + if ( arcsnd != "" ) A_StartSound(arcsnd,CHAN_WEAPON); + if ( ((ReactionTime > 0) && (special1 > ReactionTime)) || bAMBUSH ) return; + let b = Spawn(GetClass(),nextpos); + b.angle = atan2(nextdir.y,nextdir.x); + b.pitch = asin(-nextdir.z); + b.target = target; + b.special1 = special1+1; + YnykronLightningArc(b).destpos = destpos; + b.SetState(b.FindState("Trailer")); + } + override void PostBeginPlay() + { + Super.PostBeginPlay(); + frame = Random[Ynykron](0,11); + } + Default + { + Obituary "$O_YNYKRONALT"; + RenderStyle "Add"; + DamageFunction 1000; + Speed 128; + Alpha 2.; + +FOILINVUL; + } + States + { + Spawn: + XZW1 # 0 Bright; + XZW1 # 1 Bright A_Trace(); + XZW1 # 1 Bright A_Spread("ynykron/vortexarc"); + XZW1 # 1 Bright A_FadeOut(); + Wait; + Trailer: + XZW2 # 0 Bright; + XZW2 # 1 Bright A_Trace(); + XZW2 # 1 Bright A_Spread("ynykron/vortexarc"); + XZW2 # 1 Bright A_FadeOut(); + Wait; + } +} + +Class YnykronLightningArcSub : YnykronLightningArc +{ + Default + { + DamageFunction 250; + Speed 32; + ReactionTime 10; + } + States + { + Spawn: + XZW1 # 0 Bright; + XZW1 # 1 Bright A_Trace(); + XZW1 # 1 Bright A_Spread(""); + XZW1 # 1 Bright A_FadeOut(); + Wait; + Trailer: + XZW2 # 0 Bright; + XZW2 # 1 Bright A_Trace(); + XZW2 # 1 Bright A_Spread(""); + XZW2 # 1 Bright A_FadeOut(); + Wait; + } +} + +Class YnykronCloud : SWWMNonInteractiveActor +{ + Vector3 gx, gy, gz; + double phase; + Vector3 dirto; + double frightening; // lightning flash + Color basecol; + double rollvel; + + override void Tick() + { + prev = pos; + Super.Tick(); + } + + 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,255,255,255),1); + SWWMHandler.DoFlash(mo,Color(str/2,240,224,255),10); + } + + override void PostBeginPlay() + { + SetState(FindState("Spawn")+Random[ExploS](0,19)); + Scale *= FRandom[ExploS](.75,1.5); + Scale.x *= RandomPick[ExploS](-1,1); + Scale.y *= RandomPick[ExploS](-1,1); + if ( master ) Scale *= master.scale.x; + // orbit axes + [gx, gy, gz] = SWWMUtility.GetAxes(FRandom[ExploS](0,360),FRandom[ExploS](-90,90),FRandom[ExploS](-90,90)); + specialf1 = FRandom[ExploS](200,400); + scale *= specialf1/200.; + specialf2 = FRandom[ExploS](3.,8.)*RandomPick[ExploS](-1,1); + special2 = Random[ExploS](10,40); + basecol = Color(4,3,5)*Random[ExploS](5,20); + SetShade(basecol); + rollvel = FRandom[ExploS](.5,3.)*RandomPick[ExploS](-1,1); + } + + void A_Gravitate() + { + if ( frightening > 0. ) + { + alpha = max(frightening,.3); + int str = int(RandomPick[ExploS](255,240,192,254,248,128,160,204)*frightening); + Color litecol = Color(min(255,basecol.r+str),min(255,basecol.g+str),min(255,basecol.b+str)); + SetShade(litecol); + frightening *= .96; + if ( frightening < .1 ) + { + frightening = 0.; + SetShade(basecol); + } + } + roll += rollvel; + if ( master && !master.InStateSequence(master.CurState,master.FindState("Death")) ) + { + if ( special1 == 0 ) + { + A_FadeIn(FRandom[ExploS](.001,.002)); + if ( alpha > .3 ) special1 = 1; + } + else if ( special1 > 0 ) + { + special1++; + if ( special1 >= special2 ) + special1 = -1; + } + else if ( special1 == -1 ) A_FadeOut(FRandom[ExploS](.003,.006)); + dirto = level.Vec3Diff(pos,master.pos); + double distto = dirto.length(); + dirto /= distto; + // orbit + Vector3 orbitdir = SWWMUtility.CircleOffset(gx,gy,phase,(specialf1+32)*master.scale.x); + SetOrigin(level.Vec3Offset(master.pos,orbitdir),true); + phase += FRandom[ExploS](.12,.24)*specialf2*(1.-specialf1/600.); + return; + } + SetOrigin(level.Vec3Offset(pos,-dirto*3.*abs(specialf2)),true); + specialf2 *= .98; + A_FadeOut(.005); + } + + Default + { + DamageType 'YnykronAlt'; + Obituary "$O_YNYKRONALT"; + RenderStyle "Shaded"; + Alpha 0.; + Scale 3.; + +FORCEXYBILLBOARD; + +ROLLSPRITE; + } + States + { + Spawn: + DUST ABCDEFGHIJKLMNOPQRST 1 A_Gravitate(); + Wait; + } +} + +Class YnykronLightningLight : PaletteLight +{ + Default + { + Tag "WhiteExpl"; + ReactionTime 12; + Args 0,0,0,500; + } +} +Class YnykronLightningLight2 : PaletteLight +{ + Default + { + Tag "WhiteExpl"; + ReactionTime 10; + Args 0,0,0,300; + } +} + +Class SimpleMoveTracer : LineTracer +{ + override ETraceStatus TraceCallback() + { + if ( Results.HitType == TRACE_HitActor ) return TRACE_Skip; + else if ( (Results.HitType == TRACE_HitWall) && (Results.Tier == TIER_Middle) ) + { + if ( !Results.HitLine.sidedef[1] || (Results.HitLine.Flags&Line.ML_BLOCKPROJECTILE) ) + return TRACE_Stop; + return TRACE_Skip; + } + return TRACE_Stop; + } +} + +Class YnykronSingularityRing : SWWMNonInteractiveActor +{ + Default + { + RenderStyle "Add"; + Scale 4.; + +FORCEXYBILLBOARD; + } + States + { + Spawn: + XRG4 AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXX 1 Bright A_SetScale(scale.x*1.05); + Stop; + } +} + +Class YnykronVoidExplLight : PaletteLight +{ + Default + { + Tag "Purple"; + ReactionTime 60; + Args 0,0,0,1200; + } +} + +Class YnykronVoidSparkleTrail : SWWMNonInteractiveActor +{ + Default + { + RenderStyle "Add"; + XScale 6.; + +FORCEXYBILLBOARD; + } + override void Tick() + { + if ( freezetics > 0 ) + { + freezetics--; + return; + } + if ( isFrozen() ) return; + A_SetScale(scale.x*.95,scale.y); + A_FadeOut(.04); + } + States + { + Spawn: + XZW1 A -1 Bright; + Stop; + } +} + +Class YnykronVoidSparkle : SWWMNonInteractiveActor +{ + Default + { + RenderStyle "Add"; + +ROLLSPRITE; + +ROLLCENTER; + +FORCEXYBILLBOARD; + } + override void PostBeginPlay() + { + Scale *= FRandom[ExploS](.75,1.5); + specialf1 = FRandom[ExploS](.95,.98); + specialf2 = FRandom[ExploS](.005,.015); + vel = SWWMUtility.Vec3FromAngles(angle,pitch)*FRandom[ExploS](4,32); + } + override void Tick() + { + if ( freezetics > 0 ) + { + freezetics--; + return; + } + if ( isFrozen() ) return; + A_SetScale(scale.x*specialf1); + A_FadeOut(specialf2); + Vector3 dir = vel; + double magvel = dir.length(); + magvel *= .99; + if ( magvel > 0. ) + { + dir /= magvel; + dir += .2*SWWMUtility.Vec3FromAngles(FRandom[ExploS](0,360),FRandom[ExploS](-90,90)); + vel = dir.unit()*magvel; + } + Vector3 newpos = level.Vec3Offset(pos,vel); + if ( (newpos.z < floorz) || (newpos.z >= ceilingz) ) + { + vel.z *= -1; + newpos = level.Vec3Offset(pos,vel); + } + if ( !level.IsPointInLevel(newpos) ) + { + vel.xy *= -1; + newpos = level.Vec3Offset(pos,vel); + } + dir = level.Vec3Diff(newpos,pos); + double len = dir.length(); + if ( (len > 0.) && (alpha > .1) ) + { + dir /= len; + let t = Spawn("YnykronVoidSparkleTrail",newpos); + t.alpha = alpha*.5; + t.scale.x *= scale.x; + t.speed = len; + [t.angle, t.pitch, t.scale.y] = SWWMUtility.CalcYBeam(dir,len); + } + SetOrigin(newpos,true); + } + States + { + Spawn: + MOPF A -1 Bright; + Stop; + } +} +Class YnykronSingularityExplosionArm : Actor +{ + Default + { + PROJECTILE; + +THRUACTORS; + +BOUNCEONWALLS; + +BOUNCEONFLOORS; + +BOUNCEONCEILINGS; + +NODAMAGETHRUST; + +FORCERADIUSDMG; + -NOGRAVITY; + +NOFRICTION; + Gravity 0.35; + BounceFactor 1.0; + Radius 2; + Height 4; + } + override void PostBeginPlay() + { + Super.PostBeginPlay(); + reactiontime = Random[ExploS](10,20); + double ang, pt; + ang = FRandom[ExploS](0,360); + pt = FRandom[ExploS](-90,90); + vel = SWWMUtility.Vec3FromAngles(ang,pt)*FRandom[ExploS](20.,40.); + } + States + { + Spawn: + TNT1 A 1 + { + A_CountDown(); + if ( !(reactiontime%2) ) + { + Spawn("YnykronSingularityExplosionTrail",pos); + Vector3 pvel = SWWMUtility.Vec3FromAngles(FRandom[ExploS](0,360),FRandom[ExploS](-90,90))*FRandom[ExploS](1,5); + let s = Spawn("SWWMHalfSmoke",pos); + s.SetShade(Color(1,1,1)*Random[ExploS](128,160)+Color(28,0,31)); + s.A_SetRenderStyle(s.alpha*(.1+.4*(ReactionTime/15.)),STYLE_AddShaded); + s.vel = pvel+vel*.2; + s.special1 = Random[ExploS](1,3); + s.scale *= 2.4; + } + } + Wait; + } +} + +Class YnykronSingularityExplosionTrail : SWWMNonInteractiveActor +{ + Default + { + RenderStyle "Add"; + +FORCEXYBILLBOARD; + Scale 3.; + Alpha .2; + } + override void PostBeginPlay() + { + Super.PostBeginPlay(); + Scale *= FRandom[ExploS](0.8,1.1); + Scale.x *= RandomPick[ExploS](-1,1); + Scale.y *= RandomPick[ExploS](-1,1); + } + States + { + Spawn: + XEX4 ABCDEFGHIJKLMNOPQRSTUVWXYZ[\ 1 Bright; + Stop; + } +} + +Class YnykronSingularityExplosion : SWWMNonInteractiveActor +{ + Default + { + Obituary "$O_YNYKRONALT"; + DamageType "YnykronAlt"; + RenderStyle "Add"; + Scale 5.; + +NODAMAGETHRUST; + +FORCERADIUSDMG; + +FORCEXYBILLBOARD; + +FOILINVUL; + } + override void PostBeginPlay() + { + Super.PostBeginPlay(); + if ( !Random[ExploS](0,5) ) + A_StartSound("ynykron/impact",CHAN_VOICE); + SWWMUtility.DoExplosion(self,10000,5000,400,200); + A_SprayDecal("WumboRocketBlast",-172); + Scale *= FRandom[ExploS](0.8,1.1); + Scale.x *= RandomPick[ExploS](-1,1); + Scale.y *= RandomPick[ExploS](-1,1); + int numpt = Random[ExploS](2,4); + for ( int i=0; i 0 ) + { + freezetics--; + return; + } + if ( isFrozen() ) return; + Vector3 newpos; + if ( !mt ) mt = new("SimpleMoveTracer"); + Vector3 dir = vel; + double dist = vel.length(); + dir /= dist; + mt.Trace(pos,CurSector,dir,dist,0); + if ( mt.Results.HitType == TRACE_HitNone ) newpos = level.Vec3Offset(pos,vel); + else newpos = level.Vec3Offset(mt.Results.HitPos,-mt.Results.HitVector); + if ( level.IsPointInLevel(newpos) ) + SetOrigin(newpos,true); + if ( !CheckNoDelay() || (tics == -1) ) return; + if ( tics > 0 ) tics--; + while ( !tics ) + { + if ( !SetState(CurState.NextState) ) + return; + } + } + + override String GetObituary( Actor victim, Actor inflictor, Name mod, bool playerattack ) + { + if ( mod == 'Ynykron' ) + return StringTable.Localize("$O_YNYKRON"); + return Super.GetObituary(victim,inflictor,mod,playerattack); + } + + void A_SingularityTick() + { + // gather dust particles (minor "background" mass gain) + int numpt = int(max(4,20*scale.x)); + for ( int i=0; i -1) ) continue; + clouds[i] = Spawn("YnykronCloud",pos); + clouds[i].target = target; + clouds[i].master = self; + } + // check dense cloud formations for lightning + if ( level.maptime > litetimer ) + { + int which = Random[Ynykron](0,MAXCLOUDS-1); + if ( clouds[which] ) + { + Array contacts; + Array dists; + contacts.Clear(); + dists.Clear(); + for ( int i=0; i (200*scale.x) ) continue; + contacts.Push(clouds[i]); + dists.Push(dist); + } + if ( (contacts.Size() > 12) && !Random[Ynykron](0,5) ) + { + Spawn("YnykronLightningLight",clouds[which].pos); + Spawn("YnykronLightningLight2",clouds[which].pos); + A_StartSound("ynykron/vortexflash",CHAN_WEAPON,CHANF_OVERLAP,1.,0.,FRandom[Ynykron](.9,1.1)); + YnykronCloud(clouds[which]).frightening = 1.; + YnykronCloud(clouds[which]).FlashPlayer(200,3000); + for ( int i=0; i 0 ) + { + // force use the sandwich, warp to safe spot + let gc = GrilledCheeseSandwich(a.FindInventory("GrilledCheeseSandwich")); + gc.SafeTeleport(true); + a.A_StartSound(gc.UseSound,CHAN_ITEMEXTRA); + gc.DoTheThing(true); + gc.Amount--; + } + else if ( !a.FindInventory("GrilledCheeseSafeguard") ) + a.DamageMobj(self,target,int.max,'Ynykron',DMG_FORCED|DMG_THRUSTLESS); + if ( a && (a.Health <= 0) ) + { + if ( a.player ) + { + if ( a == target ) + { + SWWMUtility.MarkAchievement("oopsie",a.player); + target = PlayerGone.FeckOff(a); + } + else PlayerGone.FeckOff(a); + } + if ( a.FindState("YnykronAltDeath",true) ) + a.SetStateLabel("YnykronAltDeath"); // dedicated state + else + { + // poof away manually + a.bINVISIBLE = true; + a.A_ChangeLinkFlags(false); // remove from blockmap, should guarantee archviles not raising this + IDontFeelSoGood.DeletThis(a,true); // ensures corpse is deleted too + } + if ( target && a.FindInventory("EndgameBossMarker") ) + SWWMUtility.MarkAchievement("ligma",target.player); + } + if ( !a || (a.Health <= 0) ) + specialf2 += min(100.,capmass*.6); // partial absorption + continue; + } + capmass = max(50.,a.mass); + Vector3 dirto = level.Vec3Diff(a.Vec3Offset(0,0,a.Height/2),pos); + double dist = dirto.length(); + dirto /= dist; + // rip + if ( a && a.bSHOOTABLE && SWWMUtility.SphereIntersect(a,pos,200.*scale.x) ) + a.DamageMobj(self,target,int(clamp(10.-dist*.05,0.,5.)),'YnykronAlt',DMG_FORCED|DMG_THRUSTLESS); + if ( !a ) continue; + // weak gravitational force as per v-field (approximate) + double grav = (3.*YNON_CONST*specialf2*capmass)/(dist**.5); + // strong gravitational force as per u-field (approximate) + grav += (4.*AXAN_CONST*specialf2*capmass)/(dist**2.); + // account for ground friction + if ( a.pos.z <= a.floorz ) + { + double frict, movef; + [frict, movef] = a.GetFriction(); + grav *= movef; + if ( !a.player ) dirto.z += .1; + } + if ( a.mass < LARGE_MASS ) + { + // v-force field compression (very rough approximation) + a.vel *= max(.0,1.-.03*(grav*grav)/(MION_CONST*MION_CONST)); + a.vel += dirto*grav/(capmass*GameTicRate); + } + } + } + // push away from nearby geometry + double pushdist = 50+32*scale.x; + for ( int i=0; i<8; i++ ) + { + double ang = FRandom[ExploS](0,360); + double pt = FRandom[ExploS](-90,90); + FLineTraceData d; + if ( !LineTrace(ang,pushdist,pt,TRF_THRUACTORS|TRF_THRUHITSCAN,data:d) ) continue; + double mag = pushdist-d.Distance; + vel -= SWWMUtility.Vec3FromAngles(ang,pt)*.02*mag; + } + double magvel = vel.length(); + Vector3 dir; + if ( magvel <= double.epsilon ) dir = SWWMUtility.Vec3FromAngles(angle,pitch); + else dir = vel/magvel; + // wander + dir = (dir+SWWMUtility.Vec3FromAngles(FRandom[Ynykron](0,360),FRandom[Ynykron](-90,90))*FRandom[Ynykron](.05,.2)).unit(); + magvel *= .93; + if ( magvel > 5. ) magvel *= .95; + if ( magvel > 20. ) magvel *= .65; + if ( magvel < .1 ) magvel = .1; + vel = magvel*dir; + // calculate target scale based on radius of event horizon + specialf1 = (specialf2/MION_CONST)**.5; + // shift to target scale + // hopefully this supports interpolation someday + double diffscale = specialf1-scale.x; + if ( abs(diffscale) <= .01 ) A_SetScale(specialf1); + else A_SetScale(scale.x+diffscale*.3); + // detonate if we reach critical mass + if ( specialf2 >= critmass ) + SetStateLabel("Death"); + } + + 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,255,255,255),3); + SWWMHandler.DoFlash(mo,Color(str/2,240,224,255),30); + } + void A_SingularityBlast() + { + A_ChangeLinkFlags(true); // unlink from blockmap + A_AlertMonsters(0,AMF_EMITFROMTARGET); + SWWMUtility.DoExplosion(self,int.max,500000,800,400); + A_QuakeEx(8,8,8,100,0,65535,"",QF_RELATIVE|QF_SCALEDOWN,falloff:65535,rollIntensity:1.6); + A_StopAllSounds(); + A_StartSound("ynykron/vortexend",CHAN_VOICE,attenuation:0.); + FlashPlayer(250,9000); + vel *= 0; + // kill off any leftover beams + for ( int i=0; i 60 ) + { + if ( IsActorPlayingSound(CHAN_VOICE,-1) ) return; + for ( int i=0; i 0 ) + { + LineTrace(ang,totaldist,pt,TRF_THRUACTORS|TRF_ABSPOSITION,origin.z,origin.x,origin.y,d); + 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; + } + totaldist -= d.Distance; + if ( totaldist > 0 ) + { + Vector3 bounced = d.HitDir-(1.2*hitnormal*(d.HitDir dot HitNormal)); + ang = atan2(bounced.y,bounced.x); + pt = asin(-bounced.z); + origin = d.HitLocation+hitnormal; + } + } + let p = Spawn("YnykronSingularityExplosion",d.HitLocation+hitnormal*4); + p.angle = atan2(hitnormal.y,hitnormal.x); + p.pitch = asin(-hitnormal.z); + p.target = target; + p.scale *= 1.+special1/40.; + } + } + + Default + { + DamageType 'YnykronAlt'; + Obituary "$O_YNYKRONALT"; + +MISSILE; + Scale .4; + } + States + { + Spawn: + XZW1 A 1 BRIGHT A_SingularityTick(); + Wait; + Death: + TNT1 A 0 A_SingularityBlast(); + TNT1 A 1 A_SingularityExtraBlast(); + Wait; + } +} + +Class YnykronAltBeam : SWWMNonInteractiveActor +{ + bool nospread; + + void TraceOut() + { + Vector3 x = SWWMUtility.Vec3FromAngles(angle,pitch); + let t = new("YnykronAltTracer"); + t.ignore = target; + t.ShootThroughList.Clear(); + t.WaterHitList.Clear(); + t.Trace(pos,cursector,x,speed,TRACE_HitSky); + foreach ( l:t.ShootThroughList ) + { + l.Activate(target,0,SPAC_PCross); + l.Activate(target,0,SPAC_Impact); + } + foreach ( w:t.WaterHitList ) + { + let b = Spawn("InvisibleSplasher",w.hitpos); + b.target = target; + b.A_CheckTerrain(); + } + for ( int i=0; i= 25600 ) + { + // end of the line, dissipate + int numpt = Random[Ynykron](16,24); + for ( int i=0; i 0 ) + { + freezetics--; + return; + } + if ( isFrozen() ) return; + A_FadeOut(FRandom[Ynykron](.01,.02)); + special2++; + if ( special2 == 1 ) + SpreadOut(); + if ( !CheckNoDelay() || (tics == -1) ) return; + if ( tics > 0 ) tics--; + while ( !tics ) + { + if ( !SetState(CurState.NextState) ) + return; + } + } + + Default + { + RenderStyle "Subtract"; + Alpha .4; + Speed 64; + +FORCEXYBILLBOARD; + +ROLLSPRITE; + +ROLLCENTER; + } + States + { + Spawn: + XZW1 A -1 Bright NoDelay + { + return FindState("StarterDev")+Random[Ynykron](0,3)*2; + } + Stop; + TrailSpawn: + XZW2 A -1 Bright + { + return FindState("TrailerDev")+Random[Ynykron](0,3)*2; + } + Stop; + StarterDev: + #### # 50 Bright; + XZW1 B -1 Bright; + Stop; + #### # 50 Bright; + XZW1 C -1 Bright; + Stop; + #### # 50 Bright; + XZW1 D -1 Bright; + Stop; + #### # 50 Bright; + XZW1 E -1 Bright; + Stop; + TrailerDev: + #### # 50 Bright; + XZW2 B -1 Bright; + Stop; + #### # 50 Bright; + XZW2 C -1 Bright; + Stop; + #### # 50 Bright; + XZW2 D -1 Bright; + Stop; + #### # 50 Bright; + XZW2 E -1 Bright; + Stop; + } +} + +Class YnykronAltShot : SWWMNonInteractiveActor +{ + 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,255,255,255),5); + SWWMHandler.DoFlash(mo,Color(str/2,0,0,0),15); + } + override void PostBeginPlay() + { + A_QuakeEx(4,4,4,80,0,65535,"",QF_RELATIVE|QF_SCALEDOWN,falloff:65535,rollIntensity:.8); + A_StartSound("ynykron/altbeam",CHAN_VOICE,CHANF_DEFAULT,1.,0.); + FlashPlayer(240,8000); + let b = Spawn("YnykronAltBeam",pos); + b.target = target; + b.angle = angle; + b.pitch = pitch; + } + override void Tick() + { + if ( freezetics > 0 ) + { + freezetics--; + return; + } + if ( isFrozen() || IsActorPlayingSound(CHAN_VOICE,-1) ) return; + Destroy(); + } +} diff --git a/zscript/weapons/swwm_deathlydeathcannon_fx.zsc b/zscript/weapons/swwm_deathlydeathcannon_fx.zsc index 39bbf310b..136a7fa6c 100644 --- a/zscript/weapons/swwm_deathlydeathcannon_fx.zsc +++ b/zscript/weapons/swwm_deathlydeathcannon_fx.zsc @@ -1107,1588 +1107,3 @@ Class YnykronShot : SWWMNonInteractiveActor Destroy(); } } - -Class YnykronAltTracer : LineTracer -{ - Actor ignore; - Array ShootThroughList; - Array WaterHitList; - - override ETraceStatus TraceCallback() - { - // liquid splashes - if ( Results.CrossedWater ) - { - let hl = new("WaterHit"); - hl.sect = Results.CrossedWater; - hl.hitpos = Results.CrossedWaterPos; - WaterHitList.Push(hl); - } - else if ( Results.Crossed3DWater ) - { - let hl = new("WaterHit"); - hl.sect = Results.Crossed3DWater; - hl.hitpos = Results.Crossed3DWaterPos; - WaterHitList.Push(hl); - } - if ( Results.HitType == TRACE_HitActor ) - { - if ( Results.HitActor == ignore ) return TRACE_Skip; - if ( Results.HitActor.bSHOOTABLE || (Results.HitActor is 'YnykronSingularityHitbox') ) 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) ) - return TRACE_Stop; - ShootThroughList.Push(Results.HitLine); - return TRACE_Skip; - } - return TRACE_Stop; - } -} - -Class YnykronHaloTail : SWWMNonInteractiveActor -{ - Default - { - RenderStyle "Add"; - +FORCEXYBILLBOARD; - Scale 1.5; - } - override void Tick() - { - if ( freezetics > 0 ) - { - freezetics--; - return; - } - if ( isFrozen() ) return; - A_SetScale(scale.x+.5); - A_FadeOut(.2); - } - States - { - Spawn: - MHAL A -1 Bright; - Stop; - } -} - -Class YnykronHalo : SWWMNonInteractiveActor -{ - Default - { - RenderStyle "Add"; - +FORCEXYBILLBOARD; - Scale 1.5; - } - override void Tick() - { - if ( freezetics > 0 ) - { - freezetics--; - return; - } - if ( isFrozen() ) return; - if ( !target || target.InStateSequence(target.CurState,target.FindState("Death")) ) - { - Destroy(); - return; - } - SetOrigin(target.pos,true); - A_SetScale(1.5*target.scale.x); - let h = Spawn("YnykronHaloTail",pos); - h.scale = scale; - } - States - { - Spawn: - MHAL A -1 Bright; - Stop; - } -} - -Class GatherDust : SWWMNonInteractiveActor -{ - Mixin SWWMMinimalMovingTick; - - override void PostBeginPlay() - { - SetState(FindState("Spawn")+Random[ExploS](0,19)); - Scale *= FRandom[ExploS](.75,1.5); - Scale.x *= RandomPick[ExploS](-1,1); - Scale.y *= RandomPick[ExploS](-1,1); - SetShade(Color(4,3,5)*Random[ExploS](5,20)); - } - - void A_Gravitate() - { - if ( target && !target.InStateSequence(target.CurState,target.FindState("Death")) ) - { - if ( alpha < .08 ) A_FadeIn(FRandom[ExploS](.0002,.004)); - Vector3 dirto = level.Vec3Diff(pos,target.pos); - double distto = dirto.length(); - if ( distto < (32.*target.scale.x) ) - { - // sucked in - target.specialf2 += FRandom[ExploS](.5,2.); - Destroy(); - return; - } - dirto /= distto; - double mxdist = 5000.*target.scale.x; - vel = dirto*25.*(clamp((mxdist-distto)/mxdist,.5,1.)**4.); - vel += target.vel; - return; - } - vel *= .98; - A_FadeOut(.002); - } - - Default - { - RenderStyle "Shaded"; - Alpha 0.; - Scale 3.; - +FORCEXYBILLBOARD; - } - States - { - Spawn: - DUST ABCDEFGHIJKLMNOPQRST 1 A_Gravitate(); - Wait; - } -} - -Class YnykronVoidBeamTail : SWWMNonInteractiveActor -{ - Default - { - RenderStyle "Add"; - +FORCEXYBILLBOARD; - } - override void Tick() - { - if ( freezetics > 0 ) - { - freezetics--; - return; - } - if ( isFrozen() ) return; - A_FadeOut(); - } - States - { - Spawn: - XZW1 # -1 Bright; - Stop; - } -} - -Class YnykronVoidBeam : SWWMNonInteractiveActor -{ - Actor basebeam, prevbeam, nextbeam; - YnykronTracer t; - double baseang, basept; - int angledir, pitchdir; - int maxlen, curlen, segnum; - int spreadtimer; - int maxlife, lifetimer; - - Default - { - DamageType 'YnykronAlt'; - Obituary "$O_YNYKRONALT"; - RenderStyle "Add"; - Alpha 0.; - Stamina 3; - +INTERPOLATEANGLES; - +NODAMAGETHRUST; - +FOILINVUL; - } - - override void PostBeginPlay() - { - angle = baseang = FRandom[Ynykron](0,360); - pitch = basept = FRandom[Ynykron](-90,90); - angledir = RandomPick[Ynykron](-1,1); - pitchdir = RandomPick[Ynykron](-1,1); - maxlen = Random[Ynykron](40,60); - maxlife = Random[Ynykron](20,30); - Scale *= FRandom[Ynykron](.75,1.5); - lifetimer = -1; - } - - void A_UpdateBeam() - { - if ( !master ) - { - Destroy(); - return; - } - if ( basebeam == self ) - { - baseang += FRandom[Ynykron](0.,5.)*angledir; - basept += FRandom[Ynykron](0.,5.)*pitchdir; - Vector3 ofs = master.scale.x*32.*SWWMUtility.Vec3FromAngles(baseang,basept); - SetOrigin(level.Vec3Offset(master.pos,ofs),true); - double da = deltaangle(angle,baseang+FRandom[Ynykron](-5,5)), - dp = deltaangle(pitch,basept+FRandom[Ynykron](-5,5)); - angle += da*.2; - pitch += dp*.2; - if ( lifetimer == -1 ) - { - A_FadeIn(FRandom[Ynykron](.003,.05)); - if ( Alpha >= .2 ) - { - Alpha = .2; - lifetimer = 0; - } - } - else if ( lifetimer == -2 ) A_FadeOut(FRandom[Ynykron](.003,.05)); - else - { - lifetimer++; - if ( lifetimer > maxlife ) lifetimer = -2; - } - // check total length - int cnt = 0; - for ( YnykronVoidBeam b=self; b; b=YnykronVoidBeam(b.nextbeam) ) cnt++; - curlen = cnt; - } - else if ( !prevbeam ) - { - Destroy(); - return; - } - Vector3 x = SWWMUtility.Vec3FromAngles(angle,pitch); - if ( !t ) t = new("YnykronTracer"); - t.ShootThroughList.Clear(); - t.WaterHitList.Clear(); - t.HitList.Clear(); - t.Trace(pos,cursector,x,16*scale.x,TRACE_HitSky); - foreach ( l:t.ShootThroughList ) - { - l.Activate(target,0,SPAC_PCross); - l.Activate(target,0,SPAC_Impact); - } - foreach ( hit:t.hitlist ) - { - // yoink - SWWMUtility.DoKnockback(hit.HitActor,-hit.x,80000*scale.x*alpha); - } - spreadtimer++; - if ( (t.Results.HitType != TRACE_HitNone) || (segnum > maxlen) || (spreadtimer < stamina) ) - { - // trigger walls - if ( t.Results.HitType == TRACE_HitWall ) - t.Results.HitLine.RemoteActivate(target,t.Results.Side,SPAC_Impact,t.Results.HitPos); - // end of the line - if ( nextbeam ) nextbeam.Destroy(); - if ( basebeam == self ) frame = 3; - else frame = 2; - } - else - { - if ( basebeam == self ) frame = 0; - else frame = 1; - // spawn (or relocate) next beam - if ( !nextbeam ) - { - nextbeam = Spawn("YnykronVoidBeam",level.Vec3Offset(pos,x*16*scale.x)); - nextbeam.angle = atan2(t.Results.HitVector.y,t.Results.HitVector.x); - nextbeam.pitch = asin(-t.Results.HitVector.z); - } - else nextbeam.SetOrigin(level.Vec3Offset(pos,x*16*scale.x),true); - nextbeam.target = target; - nextbeam.master = master; - YnykronVoidBeam(nextbeam).basebeam = basebeam; - YnykronVoidBeam(nextbeam).prevbeam = self; - YnykronVoidBeam(nextbeam).maxlen = maxlen; - YnykronVoidBeam(nextbeam).segnum = segnum+1; - nextbeam.alpha = alpha*(1.-(segnum/double(maxlen))); - nextbeam.scale = scale; - nextbeam.frame = 1; - Vector3 newdir = t.Results.HitVector; - newdir = (newdir+.4*SWWMUtility.Vec3FromAngles(FRandom[Ynykron](0,360),FRandom[Ynykron](-90,90))).unit(); - double da = deltaangle(nextbeam.angle,atan2(newdir.y,newdir.x)), - dp = deltaangle(nextbeam.pitch,asin(-newdir.z)); - nextbeam.angle += da*.2; - nextbeam.pitch += dp*.2; - } - let h = Spawn("YnykronVoidBeamTail",pos); - h.angle = angle; - h.pitch = pitch; - h.alpha = alpha*2.; - h.scale = scale; - h.frame = frame; - } - - States - { - Spawn: - XZW1 # 1 Bright A_UpdateBeam(); - Wait; - } -} - -Class YnykronLightningImpact : SWWMNonInteractiveActor -{ - Default - { - Obituary "$O_YNYKRONALT"; - DamageType "Electric"; - +FOILINVUL; - +FORCERADIUSDMG; - +NODAMAGETHRUST; - } - override void PostBeginPlay() - { - Super.PostBeginPlay(); - SWWMUtility.DoExplosion(self,400,120000,100,40); - A_QuakeEx(3,3,3,12,0,800,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,falloff:300,rollIntensity:.4); - A_SprayDecal("ShockMark",-172); - int numpt = Random[ExploS](8,16); - for ( int i=0; i 10 ) - { - dirto /= dist; - dir = (dir+.8*dirto*(clamp(1.-(dist/1500.),0.,1.)**1.5)).unit(); - } - nextdir = dir; - } - void A_Spread( Sound arcsnd ) - { - Vector3 tdir = level.Vec3Diff(pos,nextpos); - if ( (GetClass() == 'YnykronLightningArc') && !Random[Ynykron](0,3) ) - { - for ( int i=0; i<3; i++ ) - { - let r = Spawn("YnykronLightningArcSub",level.Vec3Offset(pos,tdir*FRandom[Ynykron](0,1))); - Vector3 x, y, z; - [x, y, z] = SWWMUtility.GetAxes(angle,pitch,roll); - double a = FRandom[Ynykron](0,360), s = FRandom[Sparkster](0.,1.); - Vector3 sdir = SWWMUtility.ConeSpread(x,y,z,a,s); - r.angle = atan2(sdir.y,sdir.x); - r.pitch = asin(-sdir.z); - r.target = target; - r.special1 = 1; - YnykronLightningArc(r).destpos = destpos; - r.ReactionTime += Random[Ynykron](-3,3); - } - } - if ( arcsnd != "" ) A_StartSound(arcsnd,CHAN_WEAPON); - if ( ((ReactionTime > 0) && (special1 > ReactionTime)) || bAMBUSH ) return; - let b = Spawn(GetClass(),nextpos); - b.angle = atan2(nextdir.y,nextdir.x); - b.pitch = asin(-nextdir.z); - b.target = target; - b.special1 = special1+1; - YnykronLightningArc(b).destpos = destpos; - b.SetState(b.FindState("Trailer")); - } - override void PostBeginPlay() - { - Super.PostBeginPlay(); - frame = Random[Ynykron](0,11); - } - Default - { - Obituary "$O_YNYKRONALT"; - RenderStyle "Add"; - DamageFunction 1000; - Speed 128; - Alpha 2.; - +FOILINVUL; - } - States - { - Spawn: - XZW1 # 0 Bright; - XZW1 # 1 Bright A_Trace(); - XZW1 # 1 Bright A_Spread("ynykron/vortexarc"); - XZW1 # 1 Bright A_FadeOut(); - Wait; - Trailer: - XZW2 # 0 Bright; - XZW2 # 1 Bright A_Trace(); - XZW2 # 1 Bright A_Spread("ynykron/vortexarc"); - XZW2 # 1 Bright A_FadeOut(); - Wait; - } -} - -Class YnykronLightningArcSub : YnykronLightningArc -{ - Default - { - DamageFunction 250; - Speed 32; - ReactionTime 10; - } - States - { - Spawn: - XZW1 # 0 Bright; - XZW1 # 1 Bright A_Trace(); - XZW1 # 1 Bright A_Spread(""); - XZW1 # 1 Bright A_FadeOut(); - Wait; - Trailer: - XZW2 # 0 Bright; - XZW2 # 1 Bright A_Trace(); - XZW2 # 1 Bright A_Spread(""); - XZW2 # 1 Bright A_FadeOut(); - Wait; - } -} - -Class YnykronCloud : SWWMNonInteractiveActor -{ - Vector3 gx, gy, gz; - double phase; - Vector3 dirto; - double frightening; // lightning flash - Color basecol; - double rollvel; - - override void Tick() - { - prev = pos; - Super.Tick(); - } - - 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,255,255,255),1); - SWWMHandler.DoFlash(mo,Color(str/2,240,224,255),10); - } - - override void PostBeginPlay() - { - SetState(FindState("Spawn")+Random[ExploS](0,19)); - Scale *= FRandom[ExploS](.75,1.5); - Scale.x *= RandomPick[ExploS](-1,1); - Scale.y *= RandomPick[ExploS](-1,1); - if ( master ) Scale *= master.scale.x; - // orbit axes - [gx, gy, gz] = SWWMUtility.GetAxes(FRandom[ExploS](0,360),FRandom[ExploS](-90,90),FRandom[ExploS](-90,90)); - specialf1 = FRandom[ExploS](200,400); - scale *= specialf1/200.; - specialf2 = FRandom[ExploS](3.,8.)*RandomPick[ExploS](-1,1); - special2 = Random[ExploS](10,40); - basecol = Color(4,3,5)*Random[ExploS](5,20); - SetShade(basecol); - rollvel = FRandom[ExploS](.5,3.)*RandomPick[ExploS](-1,1); - } - - void A_Gravitate() - { - if ( frightening > 0. ) - { - alpha = max(frightening,.3); - int str = int(RandomPick[ExploS](255,240,192,254,248,128,160,204)*frightening); - Color litecol = Color(min(255,basecol.r+str),min(255,basecol.g+str),min(255,basecol.b+str)); - SetShade(litecol); - frightening *= .96; - if ( frightening < .1 ) - { - frightening = 0.; - SetShade(basecol); - } - } - roll += rollvel; - if ( master && !master.InStateSequence(master.CurState,master.FindState("Death")) ) - { - if ( special1 == 0 ) - { - A_FadeIn(FRandom[ExploS](.001,.002)); - if ( alpha > .3 ) special1 = 1; - } - else if ( special1 > 0 ) - { - special1++; - if ( special1 >= special2 ) - special1 = -1; - } - else if ( special1 == -1 ) A_FadeOut(FRandom[ExploS](.003,.006)); - dirto = level.Vec3Diff(pos,master.pos); - double distto = dirto.length(); - dirto /= distto; - // orbit - Vector3 orbitdir = SWWMUtility.CircleOffset(gx,gy,phase,(specialf1+32)*master.scale.x); - SetOrigin(level.Vec3Offset(master.pos,orbitdir),true); - phase += FRandom[ExploS](.12,.24)*specialf2*(1.-specialf1/600.); - return; - } - SetOrigin(level.Vec3Offset(pos,-dirto*3.*abs(specialf2)),true); - specialf2 *= .98; - A_FadeOut(.005); - } - - Default - { - DamageType 'YnykronAlt'; - Obituary "$O_YNYKRONALT"; - RenderStyle "Shaded"; - Alpha 0.; - Scale 3.; - +FORCEXYBILLBOARD; - +ROLLSPRITE; - } - States - { - Spawn: - DUST ABCDEFGHIJKLMNOPQRST 1 A_Gravitate(); - Wait; - } -} - -Class YnykronLightningLight : PaletteLight -{ - Default - { - Tag "WhiteExpl"; - ReactionTime 12; - Args 0,0,0,500; - } -} -Class YnykronLightningLight2 : PaletteLight -{ - Default - { - Tag "WhiteExpl"; - ReactionTime 10; - Args 0,0,0,300; - } -} - -Class SimpleMoveTracer : LineTracer -{ - override ETraceStatus TraceCallback() - { - if ( Results.HitType == TRACE_HitActor ) return TRACE_Skip; - else if ( (Results.HitType == TRACE_HitWall) && (Results.Tier == TIER_Middle) ) - { - if ( !Results.HitLine.sidedef[1] || (Results.HitLine.Flags&Line.ML_BLOCKPROJECTILE) ) - return TRACE_Stop; - return TRACE_Skip; - } - return TRACE_Stop; - } -} - -Class YnykronSingularityRing : SWWMNonInteractiveActor -{ - Default - { - RenderStyle "Add"; - Scale 4.; - +FORCEXYBILLBOARD; - } - States - { - Spawn: - XRG4 AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXX 1 Bright A_SetScale(scale.x*1.05); - Stop; - } -} - -Class YnykronVoidExplLight : PaletteLight -{ - Default - { - Tag "Purple"; - ReactionTime 60; - Args 0,0,0,1200; - } -} - -Class YnykronVoidSparkleTrail : SWWMNonInteractiveActor -{ - Default - { - RenderStyle "Add"; - XScale 6.; - +FORCEXYBILLBOARD; - } - override void Tick() - { - if ( freezetics > 0 ) - { - freezetics--; - return; - } - if ( isFrozen() ) return; - A_SetScale(scale.x*.95,scale.y); - A_FadeOut(.04); - } - States - { - Spawn: - XZW1 A -1 Bright; - Stop; - } -} - -Class YnykronVoidSparkle : SWWMNonInteractiveActor -{ - Default - { - RenderStyle "Add"; - +ROLLSPRITE; - +ROLLCENTER; - +FORCEXYBILLBOARD; - } - override void PostBeginPlay() - { - Scale *= FRandom[ExploS](.75,1.5); - specialf1 = FRandom[ExploS](.95,.98); - specialf2 = FRandom[ExploS](.005,.015); - vel = SWWMUtility.Vec3FromAngles(angle,pitch)*FRandom[ExploS](4,32); - } - override void Tick() - { - if ( freezetics > 0 ) - { - freezetics--; - return; - } - if ( isFrozen() ) return; - A_SetScale(scale.x*specialf1); - A_FadeOut(specialf2); - Vector3 dir = vel; - double magvel = dir.length(); - magvel *= .99; - if ( magvel > 0. ) - { - dir /= magvel; - dir += .2*SWWMUtility.Vec3FromAngles(FRandom[ExploS](0,360),FRandom[ExploS](-90,90)); - vel = dir.unit()*magvel; - } - Vector3 newpos = level.Vec3Offset(pos,vel); - if ( (newpos.z < floorz) || (newpos.z >= ceilingz) ) - { - vel.z *= -1; - newpos = level.Vec3Offset(pos,vel); - } - if ( !level.IsPointInLevel(newpos) ) - { - vel.xy *= -1; - newpos = level.Vec3Offset(pos,vel); - } - dir = level.Vec3Diff(newpos,pos); - double len = dir.length(); - if ( (len > 0.) && (alpha > .1) ) - { - dir /= len; - let t = Spawn("YnykronVoidSparkleTrail",newpos); - t.alpha = alpha*.5; - t.scale.x *= scale.x; - t.speed = len; - [t.angle, t.pitch, t.scale.y] = SWWMUtility.CalcYBeam(dir,len); - } - SetOrigin(newpos,true); - } - States - { - Spawn: - MOPF A -1 Bright; - Stop; - } -} -Class YnykronSingularityExplosionArm : Actor -{ - Default - { - PROJECTILE; - +THRUACTORS; - +BOUNCEONWALLS; - +BOUNCEONFLOORS; - +BOUNCEONCEILINGS; - +NODAMAGETHRUST; - +FORCERADIUSDMG; - -NOGRAVITY; - +NOFRICTION; - Gravity 0.35; - BounceFactor 1.0; - Radius 2; - Height 4; - } - override void PostBeginPlay() - { - Super.PostBeginPlay(); - reactiontime = Random[ExploS](10,20); - double ang, pt; - ang = FRandom[ExploS](0,360); - pt = FRandom[ExploS](-90,90); - vel = SWWMUtility.Vec3FromAngles(ang,pt)*FRandom[ExploS](20.,40.); - } - States - { - Spawn: - TNT1 A 1 - { - A_CountDown(); - if ( !(reactiontime%2) ) - { - Spawn("YnykronSingularityExplosionTrail",pos); - Vector3 pvel = SWWMUtility.Vec3FromAngles(FRandom[ExploS](0,360),FRandom[ExploS](-90,90))*FRandom[ExploS](1,5); - let s = Spawn("SWWMHalfSmoke",pos); - s.SetShade(Color(1,1,1)*Random[ExploS](128,160)+Color(28,0,31)); - s.A_SetRenderStyle(s.alpha*(.1+.4*(ReactionTime/15.)),STYLE_AddShaded); - s.vel = pvel+vel*.2; - s.special1 = Random[ExploS](1,3); - s.scale *= 2.4; - } - } - Wait; - } -} - -Class YnykronSingularityExplosionTrail : SWWMNonInteractiveActor -{ - Default - { - RenderStyle "Add"; - +FORCEXYBILLBOARD; - Scale 3.; - Alpha .2; - } - override void PostBeginPlay() - { - Super.PostBeginPlay(); - Scale *= FRandom[ExploS](0.8,1.1); - Scale.x *= RandomPick[ExploS](-1,1); - Scale.y *= RandomPick[ExploS](-1,1); - } - States - { - Spawn: - XEX4 ABCDEFGHIJKLMNOPQRSTUVWXYZ[\ 1 Bright; - Stop; - } -} - -Class YnykronSingularityExplosion : SWWMNonInteractiveActor -{ - Default - { - Obituary "$O_YNYKRONALT"; - DamageType "YnykronAlt"; - RenderStyle "Add"; - Scale 5.; - +NODAMAGETHRUST; - +FORCERADIUSDMG; - +FORCEXYBILLBOARD; - +FOILINVUL; - } - override void PostBeginPlay() - { - Super.PostBeginPlay(); - if ( !Random[ExploS](0,5) ) - A_StartSound("ynykron/impact",CHAN_VOICE); - SWWMUtility.DoExplosion(self,10000,5000,400,200); - A_SprayDecal("WumboRocketBlast",-172); - Scale *= FRandom[ExploS](0.8,1.1); - Scale.x *= RandomPick[ExploS](-1,1); - Scale.y *= RandomPick[ExploS](-1,1); - int numpt = Random[ExploS](2,4); - for ( int i=0; i 0 ) - { - freezetics--; - return; - } - if ( isFrozen() ) return; - Vector3 newpos; - if ( !mt ) mt = new("SimpleMoveTracer"); - Vector3 dir = vel; - double dist = vel.length(); - dir /= dist; - mt.Trace(pos,CurSector,dir,dist,0); - if ( mt.Results.HitType == TRACE_HitNone ) newpos = level.Vec3Offset(pos,vel); - else newpos = level.Vec3Offset(mt.Results.HitPos,-mt.Results.HitVector); - if ( level.IsPointInLevel(newpos) ) - SetOrigin(newpos,true); - if ( !CheckNoDelay() || (tics == -1) ) return; - if ( tics > 0 ) tics--; - while ( !tics ) - { - if ( !SetState(CurState.NextState) ) - return; - } - } - - override String GetObituary( Actor victim, Actor inflictor, Name mod, bool playerattack ) - { - if ( mod == 'Ynykron' ) - return StringTable.Localize("$O_YNYKRON"); - return Super.GetObituary(victim,inflictor,mod,playerattack); - } - - void A_SingularityTick() - { - // gather dust particles (minor "background" mass gain) - int numpt = int(max(4,20*scale.x)); - for ( int i=0; i -1) ) continue; - clouds[i] = Spawn("YnykronCloud",pos); - clouds[i].target = target; - clouds[i].master = self; - } - // check dense cloud formations for lightning - if ( level.maptime > litetimer ) - { - int which = Random[Ynykron](0,MAXCLOUDS-1); - if ( clouds[which] ) - { - Array contacts; - Array dists; - contacts.Clear(); - dists.Clear(); - for ( int i=0; i (200*scale.x) ) continue; - contacts.Push(clouds[i]); - dists.Push(dist); - } - if ( (contacts.Size() > 12) && !Random[Ynykron](0,5) ) - { - Spawn("YnykronLightningLight",clouds[which].pos); - Spawn("YnykronLightningLight2",clouds[which].pos); - A_StartSound("ynykron/vortexflash",CHAN_WEAPON,CHANF_OVERLAP,1.,0.,FRandom[Ynykron](.9,1.1)); - YnykronCloud(clouds[which]).frightening = 1.; - YnykronCloud(clouds[which]).FlashPlayer(200,3000); - for ( int i=0; i 0 ) - { - // force use the sandwich, warp to safe spot - let gc = GrilledCheeseSandwich(a.FindInventory("GrilledCheeseSandwich")); - gc.SafeTeleport(true); - a.A_StartSound(gc.UseSound,CHAN_ITEMEXTRA); - gc.DoTheThing(true); - gc.Amount--; - } - else if ( !a.FindInventory("GrilledCheeseSafeguard") ) - a.DamageMobj(self,target,int.max,'Ynykron',DMG_FORCED|DMG_THRUSTLESS); - if ( a && (a.Health <= 0) ) - { - if ( a.player ) - { - if ( a == target ) - { - SWWMUtility.MarkAchievement("oopsie",a.player); - target = PlayerGone.FeckOff(a); - } - else PlayerGone.FeckOff(a); - } - if ( a.FindState("YnykronAltDeath",true) ) - a.SetStateLabel("YnykronAltDeath"); // dedicated state - else - { - // poof away manually - a.bINVISIBLE = true; - a.A_ChangeLinkFlags(false); // remove from blockmap, should guarantee archviles not raising this - IDontFeelSoGood.DeletThis(a,true); // ensures corpse is deleted too - } - if ( target && a.FindInventory("EndgameBossMarker") ) - SWWMUtility.MarkAchievement("ligma",target.player); - } - if ( !a || (a.Health <= 0) ) - specialf2 += min(100.,capmass*.6); // partial absorption - continue; - } - capmass = max(50.,a.mass); - Vector3 dirto = level.Vec3Diff(a.Vec3Offset(0,0,a.Height/2),pos); - double dist = dirto.length(); - dirto /= dist; - // rip - if ( a && a.bSHOOTABLE && SWWMUtility.SphereIntersect(a,pos,200.*scale.x) ) - a.DamageMobj(self,target,int(clamp(10.-dist*.05,0.,5.)),'YnykronAlt',DMG_FORCED|DMG_THRUSTLESS); - if ( !a ) continue; - // weak gravitational force as per v-field (approximate) - double grav = (3.*YNON_CONST*specialf2*capmass)/(dist**.5); - // strong gravitational force as per u-field (approximate) - grav += (4.*AXAN_CONST*specialf2*capmass)/(dist**2.); - // account for ground friction - if ( a.pos.z <= a.floorz ) - { - double frict, movef; - [frict, movef] = a.GetFriction(); - grav *= movef; - if ( !a.player ) dirto.z += .1; - } - if ( a.mass < LARGE_MASS ) - { - // v-force field compression (very rough approximation) - a.vel *= max(.0,1.-.03*(grav*grav)/(MION_CONST*MION_CONST)); - a.vel += dirto*grav/(capmass*GameTicRate); - } - } - } - // push away from nearby geometry - double pushdist = 50+32*scale.x; - for ( int i=0; i<8; i++ ) - { - double ang = FRandom[ExploS](0,360); - double pt = FRandom[ExploS](-90,90); - FLineTraceData d; - if ( !LineTrace(ang,pushdist,pt,TRF_THRUACTORS|TRF_THRUHITSCAN,data:d) ) continue; - double mag = pushdist-d.Distance; - vel -= SWWMUtility.Vec3FromAngles(ang,pt)*.02*mag; - } - double magvel = vel.length(); - Vector3 dir; - if ( magvel <= double.epsilon ) dir = SWWMUtility.Vec3FromAngles(angle,pitch); - else dir = vel/magvel; - // wander - dir = (dir+SWWMUtility.Vec3FromAngles(FRandom[Ynykron](0,360),FRandom[Ynykron](-90,90))*FRandom[Ynykron](.05,.2)).unit(); - magvel *= .93; - if ( magvel > 5. ) magvel *= .95; - if ( magvel > 20. ) magvel *= .65; - if ( magvel < .1 ) magvel = .1; - vel = magvel*dir; - // calculate target scale based on radius of event horizon - specialf1 = (specialf2/MION_CONST)**.5; - // shift to target scale - // hopefully this supports interpolation someday - double diffscale = specialf1-scale.x; - if ( abs(diffscale) <= .01 ) A_SetScale(specialf1); - else A_SetScale(scale.x+diffscale*.3); - // detonate if we reach critical mass - if ( specialf2 >= critmass ) - SetStateLabel("Death"); - } - - 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,255,255,255),3); - SWWMHandler.DoFlash(mo,Color(str/2,240,224,255),30); - } - void A_SingularityBlast() - { - A_ChangeLinkFlags(true); // unlink from blockmap - A_AlertMonsters(0,AMF_EMITFROMTARGET); - SWWMUtility.DoExplosion(self,int.max,500000,800,400); - A_QuakeEx(8,8,8,100,0,65535,"",QF_RELATIVE|QF_SCALEDOWN,falloff:65535,rollIntensity:1.6); - A_StopAllSounds(); - A_StartSound("ynykron/vortexend",CHAN_VOICE,attenuation:0.); - FlashPlayer(250,9000); - vel *= 0; - // kill off any leftover beams - for ( int i=0; i 60 ) - { - if ( IsActorPlayingSound(CHAN_VOICE,-1) ) return; - for ( int i=0; i 0 ) - { - LineTrace(ang,totaldist,pt,TRF_THRUACTORS|TRF_ABSPOSITION,origin.z,origin.x,origin.y,d); - 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; - } - totaldist -= d.Distance; - if ( totaldist > 0 ) - { - Vector3 bounced = d.HitDir-(1.2*hitnormal*(d.HitDir dot HitNormal)); - ang = atan2(bounced.y,bounced.x); - pt = asin(-bounced.z); - origin = d.HitLocation+hitnormal; - } - } - let p = Spawn("YnykronSingularityExplosion",d.HitLocation+hitnormal*4); - p.angle = atan2(hitnormal.y,hitnormal.x); - p.pitch = asin(-hitnormal.z); - p.target = target; - p.scale *= 1.+special1/40.; - } - } - - Default - { - DamageType 'YnykronAlt'; - Obituary "$O_YNYKRONALT"; - +MISSILE; - Scale .4; - } - States - { - Spawn: - XZW1 A 1 BRIGHT A_SingularityTick(); - Wait; - Death: - TNT1 A 0 A_SingularityBlast(); - TNT1 A 1 A_SingularityExtraBlast(); - Wait; - } -} - -Class YnykronAltBeam : SWWMNonInteractiveActor -{ - bool nospread; - - void TraceOut() - { - Vector3 x = SWWMUtility.Vec3FromAngles(angle,pitch); - let t = new("YnykronAltTracer"); - t.ignore = target; - t.ShootThroughList.Clear(); - t.WaterHitList.Clear(); - t.Trace(pos,cursector,x,speed,TRACE_HitSky); - foreach ( l:t.ShootThroughList ) - { - l.Activate(target,0,SPAC_PCross); - l.Activate(target,0,SPAC_Impact); - } - foreach ( w:t.WaterHitList ) - { - let b = Spawn("InvisibleSplasher",w.hitpos); - b.target = target; - b.A_CheckTerrain(); - } - for ( int i=0; i= 25600 ) - { - // end of the line, dissipate - int numpt = Random[Ynykron](16,24); - for ( int i=0; i 0 ) - { - freezetics--; - return; - } - if ( isFrozen() ) return; - A_FadeOut(FRandom[Ynykron](.01,.02)); - special2++; - if ( special2 == 1 ) - SpreadOut(); - if ( !CheckNoDelay() || (tics == -1) ) return; - if ( tics > 0 ) tics--; - while ( !tics ) - { - if ( !SetState(CurState.NextState) ) - return; - } - } - - Default - { - RenderStyle "Subtract"; - Alpha .4; - Speed 64; - +FORCEXYBILLBOARD; - +ROLLSPRITE; - +ROLLCENTER; - } - States - { - Spawn: - XZW1 A -1 Bright NoDelay - { - return FindState("StarterDev")+Random[Ynykron](0,3)*2; - } - Stop; - TrailSpawn: - XZW2 A -1 Bright - { - return FindState("TrailerDev")+Random[Ynykron](0,3)*2; - } - Stop; - StarterDev: - #### # 50 Bright; - XZW1 B -1 Bright; - Stop; - #### # 50 Bright; - XZW1 C -1 Bright; - Stop; - #### # 50 Bright; - XZW1 D -1 Bright; - Stop; - #### # 50 Bright; - XZW1 E -1 Bright; - Stop; - TrailerDev: - #### # 50 Bright; - XZW2 B -1 Bright; - Stop; - #### # 50 Bright; - XZW2 C -1 Bright; - Stop; - #### # 50 Bright; - XZW2 D -1 Bright; - Stop; - #### # 50 Bright; - XZW2 E -1 Bright; - Stop; - } -} - -Class YnykronAltShot : SWWMNonInteractiveActor -{ - 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,255,255,255),5); - SWWMHandler.DoFlash(mo,Color(str/2,0,0,0),15); - } - override void PostBeginPlay() - { - A_QuakeEx(4,4,4,80,0,65535,"",QF_RELATIVE|QF_SCALEDOWN,falloff:65535,rollIntensity:.8); - A_StartSound("ynykron/altbeam",CHAN_VOICE,CHANF_DEFAULT,1.,0.); - FlashPlayer(240,8000); - let b = Spawn("YnykronAltBeam",pos); - b.target = target; - b.angle = angle; - b.pitch = pitch; - } - override void Tick() - { - if ( freezetics > 0 ) - { - freezetics--; - return; - } - if ( isFrozen() || IsActorPlayingSound(CHAN_VOICE,-1) ) return; - Destroy(); - } -}