swwmgz_m/zscript/swwm_funstuff.zsc
Marisa Kirisame 6068047fe8 Fix typo in SUSAN lore entry.
Restore weapon precaching.
Add precaching for gesture items.
Fix broken physics of light actors.
Add light actor physics to confetti.
Fix composite items not keeping their tracers on travel.
Fix ground distance reporting meters when it should be showing kilometers.
Reattach player shadow on travel.
Fix tip 37 erroneously listing slots from 6 and up as FOILINVUL when it should be 7 and up.
2020-12-14 21:03:08 +01:00

1264 lines
29 KiB
Text

// collectible items that may drop sometimes
Class SWWMCollectible : Inventory abstract
{
Mixin SWWMUseToPickup;
int avail;
bool propagated;
Property Availability : avail;
// 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) )
SWWMGesture.SetSpecialGesture(Owner.player.mo,self,true);
else if ( !pickup )
SWWMGesture.SetSpecialGesture(Owner.player.mo,self);
}
// 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;
}
// helper
action void A_FinishGesture()
{
let gest = SWWMGesture(FindInventory("SWWMGesture"));
if ( !gest )
{
ThrowAbortException("Call to A_FinishGesture() without owned SWWMGesture");
return;
}
if ( gest.sstate.Size() > 0 )
{
gest.whichgesture = GS_Null;
gest.whichstate = gest.sstate[0];
gest.whichcaller = gest.scaller[0];
// push back
gest.sstate.Delete(0);
gest.scaller.Delete(0);
let psp = player.FindPSprite(PSP_WEAPON);
psp.caller = gest;
psp.SetState(gest.ResolveState("Ready"));
return;
}
if ( gest.queued )
{
gest.whichstate = null;
gest.whichgesture = gest.nextgesture;
gest.queued = false;
let psp = player.FindPSprite(PSP_WEAPON);
psp.caller = gest;
psp.SetState(gest.ResolveState("Ready"));
return;
}
player.PendingWeapon = gest.formerweapon;
let psp = player.FindPSprite(PSP_WEAPON);
psp.caller = gest;
psp.SetState(gest.ResolveState("Deselect"));
}
}
// 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";
Stamina 4000;
}
}
Class AkariProject : SWWMCollectible
{
Default
{
Tag "$T_AKARIPROJECT";
Inventory.PickupMessage "$T_AKARIPROJECT";
Stamina 2000;
Radius 4;
Height 22;
}
}
Class LoveSignalsCD : SWWMCollectible
{
Default
{
Tag "$T_LOVESIGNALS";
Inventory.PickupMessage "$T_LOVESIGNALS";
Stamina 3000;
}
}
Class NutatcoBar : SWWMCollectible
{
Default
{
Tag "$T_NUTATCO";
Inventory.PickupMessage "$T_NUTATCO";
Stamina 200;
Radius 3;
Height 22;
}
}
Class FrispyCorn : SWWMCollectible
{
Default
{
Tag "$T_FRISPYCORN";
Inventory.PickupMessage "$T_FRISPYCORN";
Stamina 400;
Radius 5;
Height 23;
}
}
// Heretic
Class DemoPlush : SWWMCollectible
{
Default
{
Tag "$T_DEMOPLUSH";
Inventory.PickupMessage "$T_DEMOPLUSH";
SWWMCollectible.Availability AVAIL_Heretic;
Stamina 6000;
Radius 12;
Height 36;
}
}
Class SayaBean : SWWMCollectible
{
Default
{
Tag "$T_SAYABEAN";
Inventory.PickupMessage "$T_SAYABEAN";
SWWMCollectible.Availability AVAIL_Heretic;
Stamina 5000;
}
}
// Hexen
Class KirinCummies : SWWMCollectible
{
Default
{
Tag "$T_PEACH";
Inventory.PickupMessage "$T_PEACH";
SWWMCollectible.Availability AVAIL_Hexen;
Stamina 300;
Radius 3;
Height 21;
}
}
Class MilkBreads : SWWMCollectible
{
Default
{
Tag "$T_MILKBREAD";
Inventory.PickupMessage "$T_MILKBREAD";
SWWMCollectible.Availability AVAIL_Hexen;
Stamina 900;
Radius 4;
Height 21;
}
}
Class KirinManga : SWWMCollectible
{
Default
{
Tag "$T_KIRINMANGA";
Inventory.PickupMessage "$T_KIRINMANGA";
SWWMCollectible.Availability AVAIL_Hexen;
Stamina 1600;
Radius 4;
Height 22;
}
}
Class KirinPlush : SWWMCollectible
{
Default
{
Tag "$T_KIRINPLUSH";
Inventory.PickupMessage "$T_KIRINPLUSH";
SWWMCollectible.Availability AVAIL_Hexen;
Stamina 8000;
Radius 14;
Height 37;
}
}
// 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;
}
if ( pos.z > floorz ) 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;
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;
}
dist -= d.Distance;
if ( (d.HitType != TRACE_HitNone) && (d.HitType != TRACE_HitFloor) )
{
// should only happen if we bounced
dir = d.HitDir-(1.2*hitnormal*(d.HitDir dot hitnormal));
vel = dir*spd;
newpos = d.HitLocation+hitnormal;
}
else 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++;
}
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)?"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;
}
}