swwmgz_m/zscript/swwm_funstuff.zsc
Marisa Kirisame a83354beac Spreadgun ammo change: Napalm → Acid flechettes.
Major tweaks to item rearranging for armors/health/etc.
Slight Ynykron singularity optimization.
Reduced blood/gore effects, possibly less perf heavy.
Fix Eviscerator chunks getting stuck in mid-air / bouncing off inventory items.
Adjust Eviscerator chunk penetration.
Adjust max ammo values.
2021-01-21 09:23:24 +01:00

1370 lines
32 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),Font.CR_GOLD);
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;
Default
{
Tag "$T_FROGGY";
Radius 12;
Height 16;
+SOLID;
+SHOOTABLE;
+NODAMAGE;
+NOBLOOD;
+INTERPOLATEANGLES;
}
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;
bSHOOTABLE = false;
bNOGRAVITY = true;
vel *= 0;
master = carrier;
}
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;
bSHOOTABLE = true;
bNOGRAVITY = false;
master = 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 )
{
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);
SetOrigin(level.Vec3Offset(pos,dirto*.3),true);
double magvel = dirto.length();
dirto /= magvel;
vel = dirto*min(20,magvel);
double angleto = deltaangle(angle,AngleTo(master));
A_SetAngle(angle+angleto*.3,SPF_INTERPOLATE);
}
return;
}
Super.Tick();
if ( pos.z <= floorz ) 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;
}
States
{
Spawn:
XZW1 A -1;
Stop;
}
}
// The collectibles
Class MothPlushy : SWWMCollectible
{
// TODO post-release :: 1/64 chance after 5th use, set countdown to 175 to activate contract
int usetimes;
int cdown;
bool mashiro; // contract with white lady is active
override void DoEffect()
{
Super.DoEffect();
if ( cdown > 0 )
{
if ( Owner && Owner.CheckLocalView() )
{
if ( cdown == 105 )
A_StartSound("mashiro/appear",CHAN_ITEMEXTRA,CHANF_OVERLAP|CHANF_UI,1.,0.);
else if ( cdown == 35 )
Console.MidPrint(newsmallfont,"$D_MASHIRO");
}
cdown--;
if ( cdown <= 0 ) mashiro = true;
}
}
Default
{
Tag "$T_MOTHPLUSH";
Inventory.PickupMessage "$T_MOTHPLUSH";
SWWMCollectible.GestureWeapon "MothPlushyGesture";
Stamina 4000;
}
}
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 MothPlushyGesture : 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 SuperFancySparkle : Actor
{
Default
{
RenderStyle "Add";
Radius 0.1;
Height 0;
Scale .2;
+NOGRAVITY;
+NOBLOCKMAP;
+DONTSPLASH;
+ROLLSPRITE;
+ROLLCENTER;
+FORCEXYBILLBOARD;
+NOINTERACTION;
FloatBobPhase 0;
}
override void PostBeginPlay()
{
Scale *= FRandom[ExploS](.75,1.5);
specialf1 = FRandom[ExploS](.95,.98);
specialf2 = FRandom[ExploS](.005,.015);
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](4,20);
frame = Random[ExploS](0,7);
}
override void Tick()
{
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*(FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1));
vel = dir.unit()*magvel;
}
SetOrigin(level.Vec3Offset(pos,vel),true);
}
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 *= 2;
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,80);
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;
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 = true;
a.bNOGRAVITY = false;
a.vel.z = FRandom[Chancebox](2,4);
}
else if ( !Random[Chancebox](0,2) )
{
for ( int i=0; i<=12; i++ )
{
let a = Spawn((i==0)?"CandyGun":"CandyGunBullets",pos);
a.bDROPPED = true;
a.bNOGRAVITY = false;
a.vel.z = FRandom[Chancebox](2,4);
if ( i > 0 ) a.vel.xy = (cos(i*30),sin(i*30))*FRandom[Chancebox](1,2);
}
}
else if ( !Random[Chancebox](0,2) )
{
for ( int i=0; i<3; i++ )
{
let a = Spawn("SilverBullets2",pos);
a.bDROPPED = true;
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 = true;
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 = true;
a.bNOGRAVITY = false;
a.vel.z = FRandom[Chancebox](2,4);
for ( int i=0; i<3; i++ )
{
a = Spawn("HellblazerRavagers",pos);
a.bDROPPED = true;
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 = true;
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 = true;
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 = true;
a.bNOGRAVITY = false;
a.vel.z = FRandom[Chancebox](2,4);
for ( int i=0; i<3; i++ )
{
a = Spawn("BlueShell",pos);
a.bDROPPED = true;
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 = true;
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 = true;
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<100; i++ )
{
let a = Spawn("HealthNuggetItem",pos);
a.bDROPPED = true;
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 = true;
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 = true;
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 ( !dud )
{
if ( !Random[Chancebox](0,2) )
{
// all boxes are duds
let ti = ThinkerIterator.Create("Chancebox");
Chancebox c;
while ( c = Chancebox(ti.Next()) )
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;
while ( c = Chancebox(ti.Next()) )
{
if ( c == self ) continue;
c.dud = true; // we found one, the others just drop goodies
}
}
}
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;
}
}