Frantic attempt at preventing desyncs by adding "FloatBobPhase 0" to a lot of actors. Boss brains count towards kills (prevents players from getting an "all kills" bonus without having actually beaten the boss proper).
1151 lines
26 KiB
Text
1151 lines
26 KiB
Text
// collectible items that may drop sometimes
|
|
|
|
Class SWWMCollectible : Inventory abstract
|
|
{
|
|
Mixin SWWMUseToPickup;
|
|
|
|
int avail, gesture;
|
|
bool propagated;
|
|
|
|
Property Availability : avail;
|
|
Property PickupGesture : 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;
|
|
+FLOATBOB;
|
|
FloatBobStrength 0.25;
|
|
Radius 10;
|
|
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 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();
|
|
}
|
|
// pickup gesture (has to be -100 or under, which is the range reserved for these)
|
|
if ( !other.player || !CVar.GetCVar('swwm_collectanim',other.player).GetBool() || (gesture > -100) )
|
|
return;
|
|
SWWMGesture.SetGesture(PlayerPawn(other),gesture);
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
XZW1 A -1;
|
|
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
|
|
{
|
|
int cdown;
|
|
|
|
Default
|
|
{
|
|
Tag "$T_MOTHPLUSH";
|
|
Inventory.PickupMessage "$T_MOTHPLUSH";
|
|
Stamina 4000;
|
|
}
|
|
override void AttachToOwner( Actor other )
|
|
{
|
|
Super.AttachToOwner(other);
|
|
if ( !propagated )
|
|
cdown = 175;
|
|
}
|
|
override void DoEffect()
|
|
{
|
|
Super.DoEffect();
|
|
if ( cdown <= 0 ) return;
|
|
if ( Owner && Owner.CheckLocalView() )
|
|
{
|
|
if ( cdown == 105 )
|
|
A_StartSound("mashiro/appear",CHAN_ITEMEXTRA,CHANF_OVERLAP|CHANF_UI,1.,0.);
|
|
if ( cdown == 35 )
|
|
Console.MidPrint(newsmallfont,"$D_MASHIRO");
|
|
}
|
|
cdown--;
|
|
}
|
|
}
|
|
Class AkariProject : SWWMCollectible
|
|
{
|
|
Default
|
|
{
|
|
Tag "$T_AKARIPROJECT";
|
|
Inventory.PickupMessage "$T_AKARIPROJECT";
|
|
Stamina 2000;
|
|
Radius 6;
|
|
}
|
|
}
|
|
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 4;
|
|
}
|
|
}
|
|
Class FrispyCorn : SWWMCollectible
|
|
{
|
|
Default
|
|
{
|
|
Tag "$T_FRISPYCORN";
|
|
Inventory.PickupMessage "$T_FRISPYCORN";
|
|
Stamina 400;
|
|
Radius 8;
|
|
}
|
|
}
|
|
// Heretic
|
|
Class DemoPlush : SWWMCollectible
|
|
{
|
|
Default
|
|
{
|
|
Tag "$T_DEMOPLUSH";
|
|
Inventory.PickupMessage "$T_DEMOPLUSH";
|
|
SWWMCollectible.Availability AVAIL_Heretic;
|
|
Stamina 6000;
|
|
Radius 10;
|
|
}
|
|
}
|
|
Class SayaBean : SWWMCollectible
|
|
{
|
|
int cdown;
|
|
|
|
Default
|
|
{
|
|
Tag "$T_SAYABEAN";
|
|
Inventory.PickupMessage "$T_SAYABEAN";
|
|
SWWMCollectible.Availability AVAIL_Heretic;
|
|
Stamina 5000;
|
|
}
|
|
override void AttachToOwner( Actor other )
|
|
{
|
|
Super.AttachToOwner(other);
|
|
if ( !propagated )
|
|
cdown = 70;
|
|
}
|
|
override void DoEffect()
|
|
{
|
|
Super.DoEffect();
|
|
if ( cdown <= 0 ) return;
|
|
if ( Owner && Owner.CheckLocalView() )
|
|
{
|
|
if ( cdown == 35 )
|
|
A_StartSound("saya/giggle",CHAN_ITEMEXTRA,CHANF_OVERLAP|CHANF_UI,1.,0.);
|
|
}
|
|
cdown--;
|
|
}
|
|
}
|
|
// Hexen
|
|
Class KirinCummies : SWWMCollectible
|
|
{
|
|
Default
|
|
{
|
|
Tag "$T_PEACH";
|
|
Inventory.PickupMessage "$T_PEACH";
|
|
SWWMCollectible.Availability AVAIL_Hexen;
|
|
Stamina 300;
|
|
Radius 4;
|
|
}
|
|
}
|
|
Class MilkBreads : SWWMCollectible
|
|
{
|
|
Default
|
|
{
|
|
Tag "$T_MILKBREAD";
|
|
Inventory.PickupMessage "$T_MILKBREAD";
|
|
SWWMCollectible.Availability AVAIL_Hexen;
|
|
Stamina 900;
|
|
Radius 6;
|
|
}
|
|
}
|
|
Class KirinManga : SWWMCollectible
|
|
{
|
|
Default
|
|
{
|
|
Tag "$T_KIRINMANGA";
|
|
Inventory.PickupMessage "$T_KIRINMANGA";
|
|
SWWMCollectible.Availability AVAIL_Hexen;
|
|
Stamina 1600;
|
|
Radius 4;
|
|
}
|
|
}
|
|
Class KirinPlush : SWWMCollectible
|
|
{
|
|
int cdown;
|
|
|
|
Default
|
|
{
|
|
Tag "$T_KIRINPLUSH";
|
|
Inventory.PickupMessage "$T_KIRINPLUSH";
|
|
SWWMCollectible.Availability AVAIL_Hexen;
|
|
Stamina 8000;
|
|
Radius 12;
|
|
}
|
|
override void AttachToOwner( Actor other )
|
|
{
|
|
Super.AttachToOwner(other);
|
|
if ( !propagated )
|
|
cdown = 70;
|
|
}
|
|
override void DoEffect()
|
|
{
|
|
Super.DoEffect();
|
|
if ( cdown <= 0 ) return;
|
|
if ( Owner && Owner.CheckLocalView() )
|
|
{
|
|
if ( cdown == 35 )
|
|
A_StartSound("kirin/giggle",CHAN_ITEMEXTRA,CHANF_OVERLAP|CHANF_UI,1.,0.);
|
|
}
|
|
cdown--;
|
|
}
|
|
}
|
|
|
|
// yay!
|
|
Class FancyConfetti : Actor
|
|
{
|
|
int deadtimer;
|
|
double anglevel, pitchvel, rollvel;
|
|
|
|
Default
|
|
{
|
|
Radius 2;
|
|
Height 2;
|
|
+NOBLOCKMAP;
|
|
+DROPOFF;
|
|
+MOVEWITHSECTOR;
|
|
+THRUACTORS;
|
|
+NOTELEPORT;
|
|
+DONTSPLASH;
|
|
+INTERPOLATEANGLES;
|
|
+ROLLSPRITE;
|
|
+ROLLCENTER;
|
|
+SLIDESONWALLS;
|
|
Gravity 0.05;
|
|
FloatBobPhase 0;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.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()
|
|
{
|
|
Super.Tick();
|
|
if ( isFrozen() ) return;
|
|
vel.xy *= 0.98;
|
|
if ( vel.z > 0 ) vel.z *= 0.98;
|
|
if ( CurState == ResolveState("Death") )
|
|
{
|
|
deadtimer++;
|
|
if ( deadtimer > 300 ) A_FadeOut(0.05);
|
|
return;
|
|
}
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
XZW1 # 1
|
|
{
|
|
angle += anglevel;
|
|
pitch += pitchvel;
|
|
roll += rollvel;
|
|
return A_JumpIf(pos.z<=floorz,"Death");
|
|
}
|
|
Loop;
|
|
Death:
|
|
XZW1 # -1
|
|
{
|
|
A_Stop();
|
|
pitch = roll = 0;
|
|
}
|
|
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 < 32 ) continue; // too short
|
|
bool blockedff = false;
|
|
BlockThingsIterator bt = BlockThingsIterator.CreateFromPos(testpos.x,testpos.y,fz,32,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 < 32 ) 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,32,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;
|
|
}
|
|
}
|