swwmgz_m/zscript/swwm_funstuff.zsc
Marisa Kirisame b17fbd9a7d Remove 8+ size variants of shells. Allow dropping of 2/4 golden shells.
Use overlays for Eviscerator casing eject, add random offsets like with Explodium Gun.
Fix Candy Gun not being consistent with new animation changes of Explodium Gun.
Fix Explodium Gun not showing the loaded bullet when checking the chamber.
Give random offsets to Silver Bullet ejection overlays.
Some small texture wrangling.
Add projectile models for Silver Bullet (this will be used in the future).
Remove InterHubAmount from MagAmmo classes (not needed as this property is only used by items with the INVBAR flag).
Reduce BackpackMaxAmount of Ynykron Ammo to 3.
Adjust how Chanceboxes randomize their "dud" probability.
Tweak some of the Chancebox drops.
Set DROPPED flag to false for all Chancebox drops, so they don't get crushed.
Add 25% Crush damage factor to Demolitionist (in a future update, will try to make it so crushers have a chance to break while touching the player).
2021-02-15 23:46:04 +01:00

1478 lines
35 KiB
Text

// collectible items that may drop sometimes
Class SWWMCollectible : Inventory abstract
{
Mixin SWWMUseToPickup;
int avail;
bool propagated;
Class<SWWMItemGesture> gesture;
Property Availability : avail;
Property GestureWeapon : gesture;
// minimum gametype requirements
enum EAvailability
{
AVAIL_Strife = GAME_Strife,
AVAIL_Hexen = AVAIL_Strife|GAME_Hexen,
AVAIL_Heretic = AVAIL_Hexen|GAME_Heretic,
AVAIL_All = AVAIL_Heretic|GAME_DoomChex
};
Default
{
Inventory.PickupSound "menu/buyinv";
Inventory.Amount 1;
Inventory.MaxAmount 1;
Inventory.PickupFlash "SWWMCyanPickupFlash";
SWWMCollectible.Availability AVAIL_All;
+INVENTORY.UNTOSSABLE;
+INVENTORY.UNDROPPABLE;
+INVENTORY.UNCLEARABLE;
+INVENTORY.AUTOACTIVATE;
+FLOATBOB;
FloatBobStrength 0.25;
Radius 8;
Height 24;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
// delet ourselves if wrong iwad
if ( !(gameinfo.gametype&avail) )
Destroy();
}
override bool CanPickup( Actor toucher )
{
// no pickup if wrong iwad
if ( !(gameinfo.gametype&avail) ) return false;
return Super.CanPickup(toucher);
}
override string PickupMessage()
{
if ( Stamina > 0 )
return StringTable.Localize(PickupMsg)..String.Format(" \cj(\cg¥\cf%d\cj)\c-",Stamina);
return Super.PickupMessage();
}
override bool Use( bool pickup )
{
if ( Owner.player && !propagated )
{
if ( pickup && CVar.GetCVar('swwm_collectanim',Owner.player).GetBool() )
SWWMGesture.SetSpecialGesture(Owner.player.mo,gesture);
else if ( !pickup )
SWWMGesture.SetSpecialGesture(Owner.player.mo,gesture,true);
}
// clean up the flag
propagated = false;
return false;
}
override void AttachToOwner( Actor other )
{
Super.AttachToOwner(other);
// we're only attaching to the other players
if ( propagated )
return;
// give credit
if ( other.player && (Stamina > 0) )
{
SWWMScoreObj.Spawn(Stamina,other.Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+other.Height/2));
SWWMCredits.Give(other.player,Stamina);
}
// send to all other players
for ( int i=0; i<MAXPLAYERS; i++ )
{
if ( !playeringame[i] || !players[i].mo || (i == other.PlayerNumber()) )
continue;
let c = SWWMCollectible(Spawn(GetClass(),pos));
c.propagated = true;
if ( !c.CallTryPickup(players[i].mo) )
c.Destroy();
}
}
States
{
Spawn:
XZW1 A -1;
Stop;
}
}
Class SWWMItemGesture : SWWMWeapon abstract
{
SWWMGesture gest; // the base gesture weapon that we got picked from
bool gotused;
// these should prevent autoswitch when out of ammo
override bool ReportHUDAmmo()
{
return false;
}
override bool CheckAmmo( int firemode, bool autoswitch, bool requireammo, int ammocount )
{
return false;
}
override bool Use( bool pickup )
{
return false;
}
override void DoEffect()
{
Super.DoEffect();
if ( gotused )
{
// clear ourselves after use (don't pollute inventory list)
DepleteOrDestroy();
return;
}
if ( !Owner || !Owner.player || (Owner.player.ReadyWeapon != self) )
return;
let psp = Owner.player.FindPSprite(PSP_WEAPON);
if ( !psp ) return;
if ( (Owner.Health <= 0) && (psp.CurState != ResolveState("Deselect")) )
Owner.player.SetPSprite(PSP_WEAPON,ResolveState("Deselect"));
}
action void A_FinishGesture()
{
A_WeaponOffset(0,32); // fix key punch offset
let gest = invoker.gest;
if ( !gest )
{
ThrowAbortException("Call to A_FinishGesture() without owned SWWMGesture");
return;
}
if ( gest.sweapon.Size() > 0 )
{
gest.whichgesture = GS_Null;
gest.whichweapon = gest.sweapon[0];
gest.whichuse = gest.suse[0];
// push back
gest.sweapon.Delete(0);
gest.suse.Delete(0);
// go back to the main gesture
player.ReadyWeapon = gest;
player.SetPSPrite(PSP_WEAPON,gest.ResolveState("Ready"));
invoker.gotused = true;
return;
}
if ( gest.queued )
{
gest.whichweapon = null;
gest.whichgesture = gest.nextgesture;
gest.queued = false;
// go back to the main gesture
player.ReadyWeapon = gest;
player.SetPSPrite(PSP_WEAPON,gest.ResolveState("Ready"));
invoker.gotused = true;
return;
}
// switch to old weapon
player.ReadyWeapon = gest;
player.PendingWeapon = gest.formerweapon;
player.SetPSPrite(PSP_WEAPON,gest.ResolveState("Deselect"));
invoker.gotused = true;
}
Default
{
+WEAPON.CHEATNOTWEAPON;
+WEAPON.NO_AUTO_SWITCH;
+WEAPON.WIMPY_WEAPON;
+SWWMWEAPON.HIDEINMENU;
+INVENTORY.UNDROPPABLE;
+INVENTORY.UNTOSSABLE;
+INVENTORY.UNCLEARABLE;
Weapon.SelectionOrder int.max;
}
States
{
Select:
XZW1 A 1 A_FullRaise();
Goto Ready;
Ready:
Fire:
XZW1 A 1 A_Log("\cgUnimplemented pickup sequence for "..invoker.GetClassName().."\c-");
XZW1 A -1 A_FinishGesture();
Stop;
AltFire:
XZW1 A 1 A_Log("\cgUnimplemented use sequence for "..invoker.GetClassName().."\c-");
XZW1 A -1 A_FinishGesture();
Stop;
Deselect:
XZW1 A -1 A_FullLower();
Stop;
}
}
// April Fools 2020
Class FroggyChair : Actor
{
int cdown;
bool carried;
bool wasonground;
double lastvelz;
Actor lasthit;
Default
{
Tag "$T_FROGGY";
Radius 12;
Height 16;
+SOLID;
+SHOOTABLE;
+NODAMAGE;
+NOBLOOD;
+INTERPOLATEANGLES;
+FORCEPAIN;
}
private void BeginCarry( Actor carrier )
{
if ( !carrier ) return;
carrier.A_StartSound("demolitionist/handsup",CHAN_ITEM,CHANF_OVERLAP);
A_SetRenderStyle(.5,STYLE_Translucent);
A_SetSize(32,32); // easier to deselect
carried = true;
bSOLID = false;
bNOGRAVITY = true;
vel *= 0;
tracer = master = carrier;
lasthit = null;
}
private void EndCarry()
{
if ( master ) master.A_StartSound("demolitionist/handsdown",CHAN_ITEM,CHANF_OVERLAP);
A_SetRenderStyle(1.,STYLE_Normal);
A_SetSize(default.radius,default.height);
carried = false;
bSOLID = true;
bNOGRAVITY = false;
master = null;
vel.z += vel.xy.length()*.2;
lasthit = null;
}
override void Tick()
{
if ( cdown < 80 )
{
cdown++;
if ( cdown == 10 )
Console.MidPrint(newsmallfont,"$D_FROGGY1");
else if ( cdown == 80 )
Console.MidPrint(newsmallfont,"$D_FROGGY2");
}
if ( carried )
{
prev = pos;
if ( !master || (master.Health <= 0) ) EndCarry();
else
{
Vector3 tomove = master.Vec2OffsetZ(cos(master.angle)*40.,sin(master.angle)*40.,master.player.viewz-32.);
Vector3 dirto = level.Vec3Diff(pos,tomove);
double intp = clamp(dirto.length()*.01,.3,.7);
SetOrigin(level.Vec3Offset(pos,dirto*intp),true);
double magvel = dirto.length();
dirto /= magvel;
vel = dirto*min(50,magvel);
double angleto = deltaangle(angle,AngleTo(master));
A_SetAngle(angle+angleto*.3,SPF_INTERPOLATE);
}
return;
}
wasonground = ((pos.z <= floorz) || !TestMobjZ());
lastvelz = vel.z;
Super.Tick();
if ( (pos.z <= floorz) || !TestMobjZ() )
{
if ( !wasonground && (lastvelz < -1) ) A_StartSound("squeak",CHAN_BODY,CHANF_OVERLAP,clamp(-lastvelz*.05,0.,1.));
vel.xy *= .9; // fast friction
}
}
override bool Used( Actor user )
{
if ( carried )
{
if ( user != master ) return false;
if ( TestMobjLocation() && level.IsPointInLevel(pos) )
EndCarry();
}
else BeginCarry(user);
return true;
}
override int DamageMobj( Actor inflictor, Actor source, int damage, Name mod, int flags, double angle )
{
if ( (damage > 0) && (special1 < level.maptime) )
{
special1 = level.maptime+5;
A_StartSound("squeak",CHAN_VOICE);
}
return Super.DamageMobj(inflictor,source,damage,mod,flags,angle);
}
override bool CanCollideWith( Actor other, bool passive )
{
Vector3 dir = vel;
double vsize = dir.length();
// we need to compare Z height because wow thanks
Vector3 diff = level.Vec3Diff(pos,other.pos);
if ( (diff.z > height) || (diff.z < -other.height) ) return false;
if ( vsize > 1 )
{
if ( other == lasthit )
return false;
dir /= vsize;
if ( !passive && other.bSHOOTABLE && (!tracer || !other.IsFriend(tracer)) )
{
lasthit = other;
SWWMUtility.DoKnockback(other,dir,5000*vsize);
Vector3 dirto = level.Vec3Diff(other.Vec3Offset(0,0,other.height/2),Vec3Offset(0,0,height));
double lento = dirto.length();
if ( lento <= double.epsilon )
{
double ang = FRandom[DoBlast](0,360);
double pt = FRandom[DoBlast](-90,90);
dirto = (cos(ang)*cos(pt),sin(ang)*cos(pt),-sin(pt));
}
else dirto /= lento;
vel = (dirto+(0,0,.1))*vsize*.3;
Spawn("SWWMItemFog",pos);
other.DamageMobj(self,tracer,int(10*vsize),'Melee',DMG_THRUSTLESS);
A_StartSound("squeak",CHAN_WEAPON);
return false;
}
if ( other == tracer )
return false;
}
return true;
}
States
{
Spawn:
XZW1 A -1;
Stop;
}
}
// The collectibles
Class GenericCube : SWWMCollectible
{
Default
{
Tag "$T_PERFECTLYGENERIC";
Inventory.PickupMessage "$T_PERFECTLYGENERIC";
SWWMCollectible.GestureWeapon "GenericCubeGesture";
Stamina 1000;
}
}
Class AkariProject : SWWMCollectible
{
Default
{
Tag "$T_AKARIPROJECT";
Inventory.PickupMessage "$T_AKARIPROJECT";
SWWMCollectible.GestureWeapon "AkariProjectGesture";
Stamina 2000;
Radius 4;
Height 22;
}
}
Class LoveSignalsCD : SWWMCollectible
{
Default
{
Tag "$T_LOVESIGNALS";
Inventory.PickupMessage "$T_LOVESIGNALS";
SWWMCollectible.GestureWeapon "LoveSignalsCDGesture";
Stamina 3000;
Radius 4;
Height 21;
}
}
Class NutatcoBar : SWWMCollectible
{
Default
{
Tag "$T_NUTATCO";
Inventory.PickupMessage "$T_NUTATCO";
SWWMCollectible.GestureWeapon "NutatcoBarGesture";
Stamina 200;
Radius 3;
Height 22;
}
}
Class FrispyCorn : SWWMCollectible
{
Default
{
Tag "$T_FRISPYCORN";
Inventory.PickupMessage "$T_FRISPYCORN";
SWWMCollectible.GestureWeapon "FrispyCornGesture";
Stamina 400;
Radius 5;
Height 23;
}
}
Class SayaBean : SWWMCollectible
{
bool callout; // already called the player a perv for loading h-doom
Default
{
Tag "$T_SAYABEAN";
Inventory.PickupMessage "$T_SAYABEAN";
SWWMCollectible.GestureWeapon "SayaBeanGesture";
Stamina 5000;
Radius 6;
Height 23;
}
}
// Heretic
Class DemoPlush : SWWMCollectible
{
Default
{
Tag "$T_DEMOPLUSH";
Inventory.PickupMessage "$T_DEMOPLUSH";
SWWMCollectible.Availability AVAIL_Heretic;
SWWMCollectible.GestureWeapon "DemoPlushGesture";
Stamina 6000;
Radius 12;
Height 36;
}
}
// Hexen
Class KirinCummies : SWWMCollectible
{
Default
{
Tag "$T_PEACH";
Inventory.PickupMessage "$T_PEACH";
SWWMCollectible.Availability AVAIL_Hexen;
SWWMCollectible.GestureWeapon "KirinCummiesGesture";
Stamina 300;
Radius 3;
Height 21;
}
}
Class MilkBreads : SWWMCollectible
{
Default
{
Tag "$T_MILKBREAD";
Inventory.PickupMessage "$T_MILKBREAD";
SWWMCollectible.Availability AVAIL_Hexen;
SWWMCollectible.GestureWeapon "MilkBreadsGesture";
Stamina 900;
Radius 4;
Height 21;
}
}
Class KirinManga : SWWMCollectible
{
Default
{
Tag "$T_KIRINMANGA";
Inventory.PickupMessage "$T_KIRINMANGA";
SWWMCollectible.Availability AVAIL_Hexen;
SWWMCollectible.GestureWeapon "KirinMangaGesture";
Stamina 1600;
Radius 4;
Height 22;
}
}
Class KirinPlush : SWWMCollectible
{
Default
{
Tag "$T_KIRINPLUSH";
Inventory.PickupMessage "$T_KIRINPLUSH";
SWWMCollectible.Availability AVAIL_Hexen;
SWWMCollectible.GestureWeapon "KirinPlushGesture";
Stamina 8000;
Radius 14;
Height 37;
}
}
// TODO them gestures
Class GenericCubeGesture : SWWMItemGesture {}
Class AkariProjectGesture : SWWMItemGesture {}
Class LoveSignalsCDGesture : SWWMItemGesture {}
Class NutatcoBarGesture : SWWMItemGesture {}
Class FrispyCornGesture : SWWMItemGesture {}
Class DemoPlushGesture : SWWMItemGesture {}
Class SayaBeanGesture : SWWMItemGesture {}
Class KirinCummiesGesture : SWWMItemGesture {}
Class MilkBreadsGesture : SWWMItemGesture {}
Class KirinMangaGesture : SWWMItemGesture {}
Class KirinPlushGesture : SWWMItemGesture {}
// yay!
Class FancyConfetti : Actor
{
int deadtimer;
bool dead;
double anglevel, pitchvel, rollvel;
Sector tracksector;
int trackplane;
Default
{
Radius 2;
Height 2;
+NOBLOCKMAP;
+DROPOFF;
+THRUACTORS;
+NOTELEPORT;
+DONTSPLASH;
+INTERPOLATEANGLES;
+ROLLSPRITE;
+ROLLCENTER;
Gravity 0.05;
FloatBobPhase 0;
}
override void PostBeginPlay()
{
deadtimer = Random[Junk](-30,30);
anglevel = FRandom[Junk](3,12)*RandomPick[Junk](-1,1);
pitchvel = FRandom[Junk](3,12)*RandomPick[Junk](-1,1);
rollvel = FRandom[Junk](3,12)*RandomPick[Junk](-1,1);
if ( bAMBUSH ) frame = 0;
else frame = Random[Junk](0,9);
scale *= Frandom[Junk](0.8,1.2);
}
override void Tick()
{
prev = pos; // for interpolation
if ( isFrozen() ) return;
if ( dead )
{
// do nothing but follow floor movement
if ( tracksector )
{
double trackz;
if ( trackplane ) trackz = tracksector.ceilingplane.ZAtPoint(pos.xy);
else trackz = tracksector.floorplane.ZAtPoint(pos.xy);
if ( trackz != pos.z )
{
SetZ(trackz);
UpdateWaterLevel(false);
}
}
deadtimer++;
if ( deadtimer > 300 ) A_FadeOut(0.05);
return;
}
vel.z -= GetGravity();
vel.xy *= .98;
if ( vel.z > 0 ) vel.z *= .98;
// linetrace-based movement (hopefully more reliable than traditional methods)
Vector3 dir = vel;
double spd = vel.length();
dir /= spd;
double dist = spd;
FLineTraceData d;
Vector3 newpos = pos;
newpos.z = clamp(newpos.z,floorz,ceilingz);
int nstep = 0;
while ( dist > 0 )
{
// safeguard, too many bounces
if ( nstep > MAXBOUNCEPERTIC )
{
Destroy();
return;
}
double ang = atan2(dir.y,dir.x);
double pt = asin(-dir.z);
LineTrace(ang,dist,pt,TRF_THRUACTORS|TRF_THRUHITSCAN|TRF_ABSPOSITION,newpos.z,newpos.x,newpos.y,d);
Vector3 hitnormal = -d.HitDir;
if ( d.HitType == TRACE_HitFloor )
{
if ( d.Hit3DFloor ) hitnormal = -d.Hit3DFloor.top.Normal;
else hitnormal = d.HitSector.floorplane.Normal;
}
else if ( d.HitType == TRACE_HitCeiling )
{
if ( d.Hit3DFloor ) hitnormal = -d.Hit3DFloor.bottom.Normal;
else hitnormal = d.HitSector.ceilingplane.Normal;
}
else if ( d.HitType == TRACE_HitWall )
{
hitnormal = (-d.HitLine.delta.y,d.HitLine.delta.x,0).unit();
if ( !d.LineSide ) hitnormal *= -1;
}
if ( (d.HitType != TRACE_HitNone) && (d.HitType != TRACE_HitFloor) )
{
dist -= d.Distance;
// should only happen if we bounced
dir = d.HitDir-(1.2*hitnormal*(d.HitDir dot hitnormal));
vel = dir*spd;
newpos = d.HitLocation+dir;
}
else
{
dist = 0.;
newpos = level.Vec3Offset(newpos,dir*spd);
}
if ( (d.HitType == TRACE_HitFloor) || (newpos.z <= floorz) )
{
// lose speed and die
if ( d.Hit3DFloor )
{
newpos.z = d.Hit3DFloor.top.ZAtPoint(newpos.xy);
tracksector = d.Hit3DFloor.model;
trackplane = 1;
}
else
{
// hacky workaround
if ( !d.HitSector ) d.HitSector = floorsector;
newpos.z = d.HitSector.floorplane.ZAtPoint(newpos.xy);
tracksector = d.HitSector;
trackplane = 0;
}
vel = (0,0,0);
pitch = 0;
roll = 0;
dead = true;
SetStateLabel("Death");
break;
}
nstep++;
}
newpos.z = clamp(newpos.z,floorz,ceilingz);
SetOrigin(newpos,true);
UpdateWaterLevel();
if ( waterlevel > 0 )
{
anglevel *= .98;
pitchvel *= .98;
rollvel *= .98;
}
if ( !CheckNoDelay() || (tics == -1) ) return;
if ( tics > 0 ) tics--;
while ( !tics )
{
if ( !SetState(CurState.NextState) )
return;
}
}
States
{
Spawn:
XZW1 # 1
{
angle += anglevel;
pitch += pitchvel;
roll += rollvel;
}
Wait;
Death:
XZW1 # -1;
Stop;
Dummy:
XZW1 ABCDEFGHIJ -1;
Stop;
}
}
Class SuperFancyTrail : Actor
{
Default
{
RenderStyle "Add";
Radius .1;
Height 0.;
XScale 24.;
+FORCEXYBILLBOARD;
+NOGRAVITY;
+NOBLOCKMAP;
+NOINTERACTION;
+DONTSPLASH;
+NOTELEPORT;
}
override void Tick()
{
if ( isFrozen() ) return;
A_SetScale(scale.x*.95,scale.y);
A_FadeOut(.01);
}
States
{
Spawn:
XZW1 ABCDEFGH -1 Bright;
Stop;
}
}
Class SuperFancySparkle : Actor
{
Default
{
RenderStyle "Add";
Radius 0.1;
Height 0;
Scale .25;
+NOGRAVITY;
+NOBLOCKMAP;
+DONTSPLASH;
+ROLLSPRITE;
+ROLLCENTER;
+INTERPOLATEANGLES;
+FORCEXYBILLBOARD;
+NOINTERACTION;
FloatBobPhase 0;
}
override void PostBeginPlay()
{
Scale *= FRandom[ExploS](.75,1.5);
specialf1 = FRandom[ExploS](.94,.97);
specialf2 = FRandom[ExploS](.004,.012);
double ang = FRandom[ExploS](0,360);
double pt = FRandom[ExploS](-90,30);
vel = (cos(ang)*cos(pt),sin(ang)*cos(pt),sin(-pt))*FRandom[ExploS](2.,16.);
frame = Random[ExploS](0,7);
special1 = RandomPick[ExploS](-1,1)*Random[ExploS](1,6);
roll = FRandom[ExploS](0,360);
}
override void Tick()
{
if ( isFrozen() ) return;
prev = pos;
A_SetScale(scale.x*specialf1);
A_SetRoll(roll+special1,SPF_INTERPOLATE);
A_FadeOut(specialf2);
Vector3 dir = vel;
double magvel = dir.length();
magvel *= .99;
if ( magvel > 0. )
{
dir /= magvel;
dir += .2*(FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1));
vel = dir.unit()*magvel;
}
SetOrigin(level.Vec3Offset(pos,vel),true);
dir = level.Vec3Diff(pos,prev);
double dist = dir.length();
if ( dist < .1 ) return;
dir /= dist;
let t = Spawn("SuperFancyTrail",pos);
t.alpha = alpha*.5;
t.scale.y = dist;
t.scale.x *= scale.x;
t.angle = atan2(dir.y,dir.x);
t.pitch = asin(-dir.z)+90;
t.SetState(t.SpawnState+frame);
}
States
{
Spawn:
BLPF # -1 Bright;
Stop;
}
}
Class SuperPartyLight : PaletteLight
{
Default
{
Tag "SpRainbow";
Args 0,0,0,100;
ReactionTime 40;
}
override void PostBeginPlay()
{
SetTag(String.Format("SpRainbow,%d",Random[ExploS](0,7)));
ReactionTime = Random[ExploS](30,50);
double ang = FRandom[ExploS](0,360);
double pt = FRandom[ExploS](-90,30);
vel = (cos(ang)*cos(pt),sin(ang)*cos(pt),sin(-pt))*FRandom[ExploS](2,10);
Super.PostBeginPlay();
}
override void Tick()
{
Super.Tick();
if ( isFrozen() ) return;
SetOrigin(level.Vec3Offset(pos,vel),true);
}
}
Class PartyTime : Actor
{
bool ignite;
Default
{
Radius .1;
Height 0.;
+NOBLOCKMAP;
+NOTELEPORT;
+DONTSPLASH;
+NOINTERACTION;
}
override void PostBeginPlay()
{
if ( target ) specialf1 = target.Height/2.;
else specialf1 = 16.;
}
override void Tick()
{
if ( ignite )
{
// wait for the sound to stop
if ( !IsActorPlayingSound(CHAN_ITEM) )
Destroy();
return;
}
if ( !target || (target.tics == -1) )
{
// burst into treats!
ignite = true;
A_Confetti();
return;
}
SetOrigin(target.pos,false);
}
action void A_Confetti()
{
if ( !bAMBUSH ) A_StartSound("misc/tada",CHAN_ITEM);
double ang, pt;
int numpt = Random[ExploS](100,120);
if ( bAMBUSH ) numpt *= 4;
for ( int i=0; i<numpt; i++ )
{
ang = FRandom[ExploS](0,360);
pt = FRandom[ExploS](-90,30);
let c = Spawn("FancyConfetti",Vec3Offset(0,0,specialf1));
c.vel = (cos(pt)*cos(ang),cos(pt)*sin(ang),-sin(pt))*FRandom[ExploS](2,8);
if ( bAMBUSH ) c.vel *= 2;
}
if ( !bAMBUSH ) return;
numpt = Random[ExploS](60,90);
for ( int i=0; i<numpt; i++ )
Spawn("SuperFancySparkle",Vec3Offset(0,0,specialf1));
numpt = Random[ExploS](6,9);
for ( int i=0; i<numpt; i++ )
Spawn("SuperPartyLight",Vec3Offset(0,0,specialf1));
}
}
Class ChanceboxSpawner : Actor
{
override void PostBeginPlay()
{
int numbox = 0;
let ti = ThinkerIterator.Create("ChanceboxSpawner");
while ( ti.Next() ) numbox++;
ti = ThinkerIterator.Create("Chancebox");
while ( ti.Next() ) numbox++;
if ( numbox > 3 )
{
// there's three boxes in the map already (plus ourselves)
Destroy();
return;
}
let b = Spawn("Chancebox",pos);
// copy all our stuff
b.spawnangle = spawnangle;
b.angle = angle;
b.pitch = pitch;
b.roll = roll;
b.spawnpoint = spawnpoint;
b.special = special;
for ( int i=0; i<5; i++ )
b.Args[i] = Args[i];
b.spawnflags = spawnflags&~MTF_SECRET;
b.HandleSpawnFlags();
b.bCOUNTSECRET = spawnflags&MTF_SECRET;
b.ChangeTid(tid);
Destroy();
}
Default
{
Radius 12;
Height 20;
}
}
Class BoxSpawnSpot
{
Vector3 pos;
double angle;
}
Class CBoxLight : SpotLightAttenuated
{
Default
{
Args 112,64,224,100;
DynamicLight.SpotInnerAngle 20;
DynamicLight.SpotOuterAngle 80;
+INTERPOLATEANGLES;
}
override void Tick()
{
Super.Tick();
if ( !target || target.InStateSequence(target.CurState,target.FindState("BlowUp")) )
{
Destroy();
return;
}
Vector2 ofs = ((special1<2)?8:-8,(special1%2)?12:-12);
double ang = (special1<2)?0:180;
angle = target.angle+ang;
SetOrigin(target.Vec3Offset(ofs.x*cos(target.angle)-ofs.y*sin(target.angle),ofs.y*cos(target.angle)+ofs.x*sin(target.angle),10),true);
}
}
// Collectible box (recycling of discarded "chance box" item)
Class Chancebox : Actor
{
bool dud; // if true, cannot drop a collectible
bool chanceinit; // internal state has been initialized
static void SpawnChanceboxes()
{
// find all secret sectors, find potential spawn spots within them
// after that, spawn up to 3 boxes total within them
int tboxes = 0;
for ( int i=0; i<level.Sectors.Size(); i++ )
{
if ( !(level.Sectors[i].flags&Sector.SECF_SECRET) ) continue;
Sector s = level.Sectors[i];
// find any spots in the sector that are within it and have no linedefs or blocking actors within a 32 unit box
// start from the center, expand in rings
Vector2 origin = s.centerspot;
double maxradius = 0;
for ( int j=0; j<s.lines.Size(); j++ )
{
Line l = s.lines[j];
double v1len = (l.v1.p-origin).length(),
v2len = (l.v2.p-origin).length();
if ( v1len > maxradius ) maxradius = v1len;
if ( v2len > maxradius ) maxradius = v2len;
}
int rings = 1;
bool spawned = false;
Array<BoxSpawnSpot> spots;
spots.Clear();
for ( double j=0.; j<maxradius; j+=16. )
{
for ( double k=0.; k<360.; k+=(360./rings) )
{
// check spot
Vector3 testpos = (origin.x+j*cos(k),origin.y+j*sin(k),0);
testpos.z = s.floorplane.ZAtPoint(testpos.xy);
if ( (level.PointInSector(testpos.xy) != s) || !level.IsPointInLevel(testpos) ) continue;
double ceil = s.ceilingplane.ZAtPoint(testpos.xy);
bool blocked = false;
BlockLinesIterator bl = BlockLinesIterator.CreateFromPos(testpos,32,32,s);
double tbox[4];
// top, bottom, left, right
tbox[0] = testpos.y+32;
tbox[1] = testpos.y-32;
tbox[2] = testpos.x-32;
tbox[3] = testpos.x+32;
while ( bl.Next() )
{
Line l = bl.CurLine;
if ( !l ) continue;
if ( tbox[2] > l.bbox[3] ) continue;
if ( tbox[3] < l.bbox[2] ) continue;
if ( tbox[0] < l.bbox[1] ) continue;
if ( tbox[1] > l.bbox[0] ) continue;
if ( SWWMUtility.BoxOnLineSide(tbox[0],tbox[1],tbox[2],tbox[3],l) != -1 ) continue;
blocked = true;
break;
}
if ( blocked ) continue;
// check for 3D floors first
int nffloor = s.Get3DFloorCount();
double bceil = ceil;
for ( int l=0; l<nffloor; l++ )
{
if ( !(s.Get3DFloor(l).flags&F3DFloor.FF_SOLID) ) continue;
double fz = s.Get3DFloor(l).top.ZAtPoint(testpos.xy);
if ( fz < testpos.z ) continue;
double cz = ceil;
for ( int l2=0; l2<nffloor; l2++ )
{
if ( !(s.Get3DFloor(l2).flags&F3DFloor.FF_SOLID) ) continue;
if ( (s.Get3DFloor(l2).top.ZAtPoint(testpos.xy) < fz) ) continue;
cz = s.Get3DFloor(l2).bottom.ZAtPoint(testpos.xy);
break;
}
if ( cz < bceil ) bceil = cz;
if ( cz-fz < 56 ) continue; // too short
bool blockedff = false;
BlockThingsIterator bt = BlockThingsIterator.CreateFromPos(testpos.x,testpos.y,fz,56,256,false);
while ( bt.Next() )
{
if ( !bt.Thing ) continue;
if ( abs(bt.Thing.pos.x-testpos.x) > 32+bt.Thing.Radius ) continue;
if ( abs(bt.Thing.pos.y-testpos.y) > 32+bt.Thing.Radius ) continue;
blockedff = true;
break;
}
if ( blockedff ) continue;
let sp = new("BoxSpawnSpot");
sp.pos = (testpos.x,testpos.y,fz);
sp.angle = k+180;
spots.Push(sp);
}
// spawn at the real floor
if ( bceil-testpos.z < 56 ) continue; // too short
// don't spawn on sky or hurtfloors if there are 3D floors
if ( (nffloor > 0) && ((s.GetTexture(0) == skyflatnum) || (s.damageamount > 0)) ) continue;
BlockThingsIterator bt = BlockThingsIterator.CreateFromPos(testpos.x,testpos.y,testpos.z,56,256,false);
while ( bt.Next() )
{
if ( !bt.Thing ) continue;
if ( abs(bt.Thing.pos.x-testpos.x) > 32+bt.Thing.Radius ) continue;
if ( abs(bt.Thing.pos.y-testpos.y) > 32+bt.Thing.Radius ) continue;
blocked = true;
break;
}
if ( blocked ) continue;
let sp = new("BoxSpawnSpot");
sp.pos = testpos;
sp.angle = k+180;
spots.Push(sp);
}
rings += 3;
}
if ( spots.Size() < 10 ) continue;
int ws = Random[Chancebox](0,spots.Size()-1);
let c = Spawn("ChanceboxSpawner",spots[ws].pos);
c.angle = spots[ws].angle;
tboxes++;
if ( tboxes >= 3 ) break; // already spawned 3 boxes in one map (which is a lot)
}
}
action void A_DropSomething()
{
Array<Class <SWWMCollectible> > candidates;
candidates.Clear();
for ( int i=0; i<AllActorClasses.Size(); i++ )
{
let c = (Class<SWWMCollectible>)(AllActorClasses[i]);
if ( !c || (c == 'SWWMCollectible') ) continue;
let def = GetDefaultByType(c);
// check that we can collect it in this IWAD
if ( !(gameinfo.gametype&def.avail) ) continue;
candidates.Push(c);
}
let ti = ThinkerIterator.Create("SWWMCollectible");
SWWMCollectible c;
while ( c = SWWMCollectible(ti.Next()) )
{
int f = candidates.Find(c.GetClass());
if ( f < candidates.Size() )
candidates.Delete(f);
}
if ( (candidates.Size() <= 0) || invoker.dud )
{
// no candidates? just burst into treats
if ( Random[Chancebox](0,1) )
{
let a = Spawn(!Random[Chancebox](0,2)?"GoldShell":(Random[Chancebox](0,1)||(gameinfo.gametype&GAME_Strife))?"GrilledCheeseSandwich":"YnykronAmmo",pos);
a.bDROPPED = false;
a.bNOGRAVITY = false;
a.vel.z = FRandom[Chancebox](2,4);
}
else if ( !Random[Chancebox](0,2) )
{
for ( int i=0; i<=6; i++ )
{
let a = Spawn((i==0)?"CandyGun":"CandyGunBullets",pos);
a.bDROPPED = false;
a.bNOGRAVITY = false;
a.vel.z = FRandom[Chancebox](2,4);
if ( i > 0 ) a.vel.xy = (cos(i*60),sin(i*60))*FRandom[Chancebox](1,2);
}
}
else if ( !Random[Chancebox](0,2) )
{
for ( int i=0; i<3; i++ )
{
let a = Spawn("SilverBullets2",pos);
a.bDROPPED = false;
a.bNOGRAVITY = false;
a.vel.z = FRandom[Chancebox](2,4);
a.vel.xy = (cos(i*120),sin(i*120))*FRandom[Chancebox](1,2);
}
for ( int i=0; i<6; i++ )
{
let a = Spawn("SilverBullets",pos);
a.bDROPPED = false;
a.bNOGRAVITY = false;
a.vel.z = FRandom[Chancebox](2,4);
a.vel.xy = (cos(i*60),sin(i*60))*FRandom[Chancebox](3,4);
}
}
else if ( Random[Chancebox](0,1) )
{
let a = Spawn("HellblazerWarheads",pos);
a.bDROPPED = false;
a.bNOGRAVITY = false;
a.vel.z = FRandom[Chancebox](2,4);
for ( int i=0; i<3; i++ )
{
a = Spawn("HellblazerRavagers",pos);
a.bDROPPED = false;
a.bNOGRAVITY = false;
a.vel.z = FRandom[Chancebox](2,4);
a.vel.xy = (cos(i*120),sin(i*120))*FRandom[Chancebox](1,2);
}
for ( int i=0; i<5; i++ )
{
a = Spawn("HellblazerCrackshots",pos);
a.bDROPPED = false;
a.bNOGRAVITY = false;
a.vel.z = FRandom[Chancebox](2,4);
a.vel.xy = (cos(i*72),sin(i*72))*FRandom[Chancebox](3,4);
}
for ( int i=0; i<8; i++ )
{
a = Spawn("HellblazerMissiles",pos);
a.bDROPPED = false;
a.bNOGRAVITY = false;
a.vel.z = FRandom[Chancebox](2,4);
a.vel.xy = (cos(i*45),sin(i*45))*FRandom[Chancebox](5,6);
}
}
else if ( Random[Chancebox](0,1) )
{
let a = Spawn("BlackShell",pos);
a.bDROPPED = false;
a.bNOGRAVITY = false;
a.vel.z = FRandom[Chancebox](2,4);
for ( int i=0; i<3; i++ )
{
a = Spawn("BlueShell",pos);
a.bDROPPED = false;
a.bNOGRAVITY = false;
a.vel.z = FRandom[Chancebox](2,4);
a.vel.xy = (cos(i*120),sin(i*120))*FRandom[Chancebox](1,2);
}
for ( int i=0; i<8; i++ )
{
a = Spawn((i%2)?"WhiteShell":"PurpleShell",pos);
a.bDROPPED = false;
a.bNOGRAVITY = false;
a.vel.z = FRandom[Chancebox](2,4);
a.vel.xy = (cos(i*72),sin(i*72))*FRandom[Chancebox](3,4);
}
for ( int i=0; i<12; i++ )
{
a = Spawn((i%2)?"RedShell":"GreenShell",pos);
a.bDROPPED = false;
a.bNOGRAVITY = false;
a.vel.z = FRandom[Chancebox](2,4);
a.vel.xy = (cos(i*30),sin(i*30))*FRandom[Chancebox](5,6);
}
}
else if ( Random[Chancebox](0,1) )
{
for ( int i=0; i<20; i++ )
{
let a = Spawn("HealthNuggetItem",pos);
a.bDROPPED = false;
a.bNOGRAVITY = false;
a.vel.z = FRandom[Chancebox](2,8);
a.vel.xy = (cos(i*3.6),sin(i*3.6))*FRandom[Chancebox](1,8);
}
}
else
{
for ( int i=0; i<=15; i++ )
{
let a = Spawn((i==0)?"RefresherItem":(i%3)?"HealthNuggetItem":"ArmorNuggetItem",pos);
a.bDROPPED = false;
a.bNOGRAVITY = false;
a.vel.z = FRandom[Chancebox](2,4);
if ( i > 0 ) a.vel.xy = (cos(i*24),sin(i*24))*FRandom[Chancebox](1,2);
}
}
}
else
{
// pop one at random
let a = Spawn(candidates[Random[Chancebox](0,candidates.Size()-1)],pos);
a.bDROPPED = false;
a.bNOGRAVITY = false;
a.vel.z = FRandom[Chancebox](2,4);
}
int numpt = Random[ExploS](16,32);
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*FRandom[ExploS](.3,8);
let s = Spawn("SWWMHalfSmoke",Vec3Offset(0,0,16));
s.vel = pvel;
s.SetShade(Color(2,1,3)*Random[ExploS](64,85));
s.special1 = Random[ExploS](1,4);
s.alpha *= .4;
s.scale *= 2.4;
}
}
action void A_Confetti()
{
A_StartSound("misc/tada",CHAN_ITEM);
double ang, pt;
int numpt = Random[ExploS](100,120);
for ( int i=0; i<numpt; i++ )
{
ang = FRandom[ExploS](0,360);
pt = FRandom[ExploS](-90,30);
let c = Spawn("FancyConfetti",Vec3Offset(0,0,16));
c.vel = (cos(pt)*cos(ang),cos(pt)*sin(ang),-sin(pt))*FRandom[ExploS](2,8);
}
}
override bool Used( Actor user )
{
// test vertical range
Vector3 diff = level.Vec3Diff(user.Vec3Offset(0,0,user.Height/2),Vec3Offset(0,0,Height/2));
double rang = user.player?PlayerPawn(user.player.mo).UseRange:(user.Height/2);
if ( abs(diff.z) > rang ) return false;
if ( CurState != FindState("Spawn") ) return false;
if ( !chanceinit )
{
int col = 0;
let ti = ThinkerIterator.Create("SWWMCollectible");
SWWMCollectible c;
while ( c = SWWMCollectible(ti.Next()) ) col++;
int tcol = 0;
for ( int i=0; i<AllActorClasses.Size(); i++ )
{
let c = (Class<SWWMCollectible>)(AllActorClasses[i]);
if ( !c || (c == 'SWWMCollectible') ) continue;
let def = GetDefaultByType(c);
// check that we can collect it in this IWAD
if ( !(gameinfo.gametype&def.avail) ) continue;
tcol++;
}
int alldudchance = 5-(4*col)/tcol; // chance for all boxes to be duds (no collectibles)
if ( (col > 0) && !Random[Chancebox](0,alldudchance) )
{
// all boxes are duds
let ti = ThinkerIterator.Create("Chancebox");
Chancebox c;
while ( c = Chancebox(ti.Next()) )
{
c.chanceinit = true;
c.dud = true;
}
}
else if ( Random[Chancebox](0,2) )
{
int nbox = 0, ndud = 0;
// this one's a dud (unless all the others are)
let ti = ThinkerIterator.Create("Chancebox");
Chancebox c;
while ( c = Chancebox(ti.Next()) )
{
if ( c == self ) continue;
nbox++;
if ( c.dud ) ndud++;
}
if ( ndud < nbox ) dud = true;
}
else
{
// the others are duds
let ti = ThinkerIterator.Create("Chancebox");
Chancebox c;
bool onemore = !Random[Chancebox](0,2); // unless...
while ( c = Chancebox(ti.Next()) )
{
if ( (c == self) || c.chanceinit ) continue;
c.chanceinit = true;
if ( onemore )
{
onemore = false;
// this one isn't a dud either (wow, how lucky)
c.dud = false;
}
else c.dud = true; // this one's a dud
}
}
chanceinit = true;
}
if ( bCOUNTITEM )
{
user.player.itemcount++;
level.found_items++;
bCOUNTITEM = false;
}
if ( bCOUNTSECRET )
{
user.GiveSecret();
bCOUNTSECRET = false;
}
if ( special )
{
user.A_CallSpecial(special,args[0],args[1],args[2],args[3],args[4]);
special = 0;
}
SWWMLoreLibrary.Add(user.player,"Chancebox");
specialf2 = AngleTo(user);
SetStateLabel("PreActive");
return true;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
for ( int i=0; i<4; i++ )
{
let l = Spawn("CBoxLight",pos);
l.special1 = i;
l.target = self;
}
let ti = ThinkerIterator.Create("Chancebox");
Chancebox c;
while ( c = Chancebox(ti.Next()) )
{
if ( c.dud || (c.CurState == c.FindState("Spawn")) )
continue;
// automatically become a dud if collectible has been found
dud = true;
break;
}
}
Default
{
Tag "$T_CHANCEBOX";
Radius 12;
Height 20;
+MOVEWITHSECTOR;
+ROLLSPRITE;
+SOLID;
+INTERPOLATEANGLES;
+COUNTITEM;
Species "Chancebox";
}
States
{
Spawn:
XZW1 A -1;
Stop;
PreActive:
XZW1 A 1
{
double delta = deltaangle(angle,specialf2);
int sign = (delta>=0.)?1:-1;
delta = clamp(abs(delta)*.15,.1,10.)*sign;
angle += delta;
return A_JumpIf(abs(deltaangle(angle,specialf2))<1.,"Active");
}
Wait;
Active:
XZW1 A 1
{
angle = specialf2;
specialf1 = angle;
A_StartSound("misc/drumroll",CHAN_WEAPON);
}
XZW1 A 1
{
bINTERPOLATEANGLES = false;
angle = specialf1+FRandom[Chancebox](-5,5);
pitch = FRandom[Chancebox](-5,5);
roll = FRandom[Chancebox](-5,5);
special1++;
return A_JumpIf(special1>40,"BlowUp");
}
Wait;
BlowUp:
XZW2 A 1
{
A_SetSize(12,3);
A_QuakeEx(2,2,2,9,0,500,"",QF_RELATIVE|QF_SCALEDOWN,falloff:200,rollIntensity:.2);
A_StartSound("chancebox/explode",CHAN_VOICE);
angle = specialf1;
pitch = roll = 0;
let t = Spawn("ChanceboxTop",Vec3Offset(0,0,20));
t.angle = angle;
let s1 = Spawn("ChanceboxSide",level.Vec3Offset(pos,(RotateVector((12,0),angle+90),0)));
s1.angle = angle+90;
let s2 = Spawn("ChanceboxSide",level.Vec3Offset(pos,(RotateVector((12,0),angle-90),0)));
s2.angle = angle-90;
A_DropSomething();
}
XZW2 BCDEFGHIJKLMNO 1;
XZW2 P -1 A_Confetti();
Stop;
}
}
// top side of chancebox, shoots upwards
Class ChanceboxTop : Actor
{
Default
{
Radius 12;
Height 3;
Species "Chancebox";
PROJECTILE;
+THRUSPECIES;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
vel = (0,0,20);
}
States
{
Spawn:
XZW1 A 1
{
double magvel = vel.length();
if ( magvel > 0. )
{
magvel = min(60,magvel*1.2);
vel = vel.unit()*magvel;
}
}
Wait;
Death:
TNT1 A 1 A_SpawnItemEx("ExplodiumBulletImpact");
Stop;
}
}
// left/right side of chancebox, shoots forward
Class ChanceboxSide : Actor
{
Default
{
Radius 12;
Height 24;
Species "Chancebox";
PROJECTILE;
+THRUSPECIES;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
vel = (cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(pitch))*20;
}
States
{
Spawn:
XZW1 A 1
{
double magvel = vel.length();
if ( magvel > 0. )
{
magvel = min(60,magvel*1.2);
vel = vel.unit()*magvel;
}
}
Wait;
Death:
TNT1 A 1 A_SpawnItemEx("ExplodiumBulletImpact");
Stop;
}
}