// 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; +CANPASS; +NOBLOCKMONST; +MOVEWITHSECTOR; +SLIDESONWALLS; } private void BeginCarry( Actor carrier ) { if ( !carrier ) return; SWWMLoreLibrary.Add(carrier.player,"FroggyChair"); carrier.A_StartSound("demolitionist/handsup",CHAN_ITEM,CHANF_OVERLAP); if ( carrier is 'Demolitionist' ) Demolitionist(carrier).froggy = self; A_SetRenderStyle(.5,STYLE_Translucent); A_SetSize(default.radius,32); // full body blocks 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); if ( master is 'Demolitionist' ) Demolitionist(master).froggy = null; 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 { Vector2 tofs = AngleToVector(master.angle,40.); Vector3 tomove = master.Vec2OffsetZ(tofs.x,tofs.y,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 ( isFrozen() || (freezetics > 0) ) return; 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; A_SetSize(default.radius,default.height); if ( TestMobjLocation() && level.IsPointInLevel(pos) ) EndCarry(); else A_SetSize(default.radius,32); } else { Vector3 itempos = Vec3Offset(0,0,Height/2), userpos = user.Vec2OffsetZ(0,0,user.player.viewz); // 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; 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 ) { if ( !other.bSHOOTABLE && !other.bSOLID ) return false; 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 = SWWMUtility.Vec3FromAngles(ang,pt); } else dirto /= lento; vel = (dirto+(0,0,.1))*vsize*.3; Spawn("SWWMItemFog",pos); other.DamageMobj(self,tracer,int(2.5*vsize),'Melee',DMG_THRUSTLESS); A_StartSound("squeak",CHAN_WEAPON); return false; } if ( other == tracer ) return false; } return true; } States { Spawn: XZW1 A -1; Stop; } } // a flag Class SWWMFlag : Actor { int seq; int holdtime; bool carried; void ChangeFlag() { seq = (seq+1)%4; switch ( seq ) { case 0: A_ChangeModel("",0,"","",0,"models","SWWMFlag.png"); break; case 1: A_ChangeModel("",0,"","",0,"models","SWWMFlag_Pride.png"); break; case 2: A_ChangeModel("",0,"","",0,"models","SWWMFlag_Trans.png"); break; case 3: A_ChangeModel("",0,"","",0,"models","SWWMFlag_Enby.png"); break; } A_StartSound("bestsound",CHAN_BODY,CHANF_OVERLAP); } void BeginCarry() { master.A_StartSound("demolitionist/handsup",CHAN_ITEM,CHANF_OVERLAP); A_SetScale(.5); A_SetRenderStyle(.5,STYLE_Translucent); carried = true; bSOLID = false; bNOGRAVITY = true; vel *= 0.; } void UpdateCarry() { prev = pos; Vector2 tofs = AngleToVector(master.angle,40.); Vector3 tomove = master.Vec2OffsetZ(tofs.x,tofs.y,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,master.AngleTo(self)); A_SetAngle(angle+angleto*.3,SPF_INTERPOLATE); } void EndCarry() { if ( master ) master.A_StartSound("demolitionist/handsdown",CHAN_ITEM,CHANF_OVERLAP); A_SetScale(1.); A_SetRenderStyle(1.,STYLE_Normal); carried = false; bSOLID = true; bNOGRAVITY = false; vel *= 0.; } override void Tick() { if ( master && (master.Health > 0) && master.player && master.player.usedown ) { if ( carried ) UpdateCarry(); else if ( master.Distance2D(self) <= PlayerPawn(master).UseRange ) { holdtime++; if ( holdtime >= 15 ) BeginCarry(); } } else if ( holdtime > 0 ) { if ( holdtime < 15 ) ChangeFlag(); if ( carried ) EndCarry(); master = null; holdtime = 0; } if ( !carried ) { Super.Tick(); return; } // no need to care about everything when carried if ( !CheckNoDelay() || (tics == -1) ) return; if ( tics > 0 ) tics--; while ( !tics ) { if ( !SetState(CurState.NextState) ) return; } } override bool Used( Actor user ) { if ( carried ) return false; master = user; holdtime = 1; return true; } Default { +SOLID; +NOTELEPORT; +DONTSPLASH; Radius 2; Height 104; } States { Spawn: XZW1 ABCDEFGHIJKLMNOPQRSTUVWXYZ 2; XZW2 ABCDEFGHIJKLMNOPQRSTUVWXYZ 2; XZW3 ABCDEFGH 2; Loop; } } // oof Class SWWMGasCloudSpawner : SWWMNonInteractiveActor { override void Tick() { if ( freezetics > 0 ) { freezetics--; return; } if ( isFrozen() ) return; if ( !(special1%5) ) { Vector3 x = SWWMUtility.Vec3FromAngles(angle,pitch); let c = Spawn("SWWMGasCloud",level.Vec3Offset(pos,x*(20+special1*12))); c.target = target; c.specialf1 = 1+special1/10.; } special1++; if ( special1 > 20 ) Destroy(); } } Class SWWMGasCloud : SWWMNonInteractiveActor { Default { +FORCERADIUSDMG; } override void Tick() { if ( freezetics > 0 ) { freezetics--; return; } if ( isFrozen() ) return; for ( int i=0; i<2; i++ ) { let e = Spawn("SWWMFart",level.Vec3Offset(pos,specialf1*SWWMUtility.Vec3FromAngles(FRandom[ExploS](0,360),FRandom[ExploS](-90,90))*20.)); e.target = target; e.scale *= specialf1; } SWWMUtility.DoExplosion(self,Random[ExploS](2,6),0,60*specialf1,40,DE_NOBLEED|DE_NOSPLASH|DE_THRUWALLS|DE_HOWL,'Gas',target); special1++; if ( special1 >= 90 ) Destroy(); } } Class SWWMFart : SWWMHalfSmoke { Default { RenderStyle "Add"; Alpha .1; } States { Spawn: FRT1 ABCDEFGHIJKLMNOPQRSTUVWXYZ 2 Bright; FRT2 ABCDEFGHI 2 Bright; Stop; } } // yay! Class FancyConfetti : SWWMNonInteractiveActor { int deadtimer; bool dead; double anglevel, pitchvel, rollvel; Sector tracksector; int trackplane; Default { +INTERPOLATEANGLES; +ROLLSPRITE; +ROLLCENTER; Gravity 0.05; } 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,13); scale *= Frandom[Junk](0.8,1.2); } override void Tick() { prev = pos; // for interpolation if ( freezetics > 0 ) { freezetics--; return; } 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); if ( (d.HitType != TRACE_HitNone) && (d.HitType != TRACE_HitFloor) ) { Vector3 hitnormal = SWWMUtility.GetLineTraceHitNormal(d); 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 ABCDEFGHIJKLMN -1; Stop; } } Class SuperFancyTrail : SWWMNonInteractiveActor { Default { RenderStyle "Add"; XScale 24.; +FORCEXYBILLBOARD; } override void Tick() { if ( freezetics > 0 ) { freezetics--; return; } if ( isFrozen() ) return; A_SetScale(scale.x*.95,scale.y); A_FadeOut(.01); } States { Spawn: XZW1 ABCDEFGH -1 Bright; Stop; } } Class SuperFancySparkle : SWWMNonInteractiveActor { Default { RenderStyle "Add"; Scale .25; +ROLLSPRITE; +ROLLCENTER; +INTERPOLATEANGLES; +FORCEXYBILLBOARD; } 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 = SWWMUtility.Vec3FromAngles(ang,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() { prev = pos; if ( freezetics > 0 ) { freezetics--; return; } if ( isFrozen() ) return; 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*SWWMUtility.Vec3FromAngles(FRandom[ExploS](0,360),FRandom[ExploS](-90,90)); 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.x *= scale.x; t.speed = dist; [t.angle, t.pitch, t.scale.y] = SWWMUtility.CalcYBeam(dir,dist); 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 = SWWMUtility.Vec3FromAngles(ang,pt)*FRandom[ExploS](2,10); Super.PostBeginPlay(); } override void Tick() { Super.Tick(); if ( isFrozen() || (freezetics > 0) ) return; SetOrigin(level.Vec3Offset(pos,vel),true); } } Class PartyTime : SWWMNonInteractiveActor { bool ignite; 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,-1) ) Destroy(); return; } if ( !target || (target.tics == -1) ) { // burst into treats! ignite = true; A_Confetti(); return; } SetOrigin(target.pos,false); } void A_Confetti() { if ( !bAMBUSH && !bSTANDSTILL ) A_StartSound("misc/tada",CHAN_ITEM); double ang, pt; int numpt = Random[ExploS](100,120); if ( bAMBUSH ) numpt *= 4; else if ( bSTANDSTILL ) numpt *= 2; for ( int i=0; i= 3 ) { // there's three boxes in the map already let b = Spawn("HealthNuggetItem",pos); SWWMUtility.TransferItemProp(self,b); ClearCounters(); Destroy(); return; } BlockLinesIterator bl = BlockLinesIterator.CreateFromPos(pos,32,32,CurSector); double tbox[4]; // top, bottom, left, right tbox[0] = pos.y+32; tbox[1] = pos.y-32; tbox[2] = pos.x-32; tbox[3] = pos.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; // there isn't enough space to spawn a box here let b = Spawn("HealthNuggetItem",pos); SWWMUtility.TransferItemProp(self,b); ClearCounters(); Destroy(); return; } let b = Spawn("Chancebox",pos); // copy all our stuff SWWMUtility.TransferItemProp(self,b); ClearCounters(); 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)*target.scale.x; double ang = (special1<2)?0:180; angle = target.angle+ang; ofs = RotateVector(ofs,target.angle); SetOrigin(target.Vec3Offset(ofs.x,ofs.y,10*target.scale.y),true); } } Class ChanceboxReward play abstract { // check if this reward can drop (e.g.: if player owns a specific weapon) virtual bool CheckRequirements() { // always available by default return true; } // spawn the reward from this subclass abstract void SpawnReward( Vector3 pos ); // drop at // optionally, set bDROPPED flag on it static void SpawnCenter( Vector3 pos, Class item, bool bDROPPED = false ) { let a = Actor.Spawn(item,pos); a.bDROPPED = bDROPPED; a.vel.z = FRandom[Chancebox](2.,4.); } // drop of in a circle at with FRandom[Chancebox](+1) horizontal speed, and optionally choosing whenever (i%) // circle drops will always have bDROPPED static void SpawnCircle( Vector3 pos, int number, Class item, double minthrow = 1., double maxthrow = 1., Class item2 = null, int item2ratio = 2 ) { Actor a; for ( int i=0; i vipammodrop = null; if ( SWWMUtility.ItemExists("Ynykron",ownedonly:true) && SWWMUtility.CheckNeedsItem("YnykronAmmo",true) && Random[Chancebox](0,1) ) vipammodrop = "YnykronAmmo"; if ( SWWMUtility.ItemExists("RafanKos",ownedonly:true) && SWWMUtility.CheckNeedsItem("UltimateAmmo",true) && Random[Chancebox](0,1) && !vipammodrop ) vipammodrop = "UltimateAmmo"; if ( SWWMUtility.ItemExists("Spreadgun",ownedonly:true) && SWWMUtility.CheckNeedsItem("GoldShell",true) && !vipammodrop ) vipammodrop = "GoldShell"; Class vipitemdrop = null; if ( SWWMUtility.CheckNeedsItem("Mykradvo",true) && !SWWMUtility.ItemExists("Mykradvo",worldonly:true) && Random[Chancebox](0,1) ) vipitemdrop = "Mykradvo"; if ( SWWMUtility.CheckNeedsItem("AngerySigil",true) && !SWWMUtility.ItemExists("AngerySigil",worldonly:true) && Random[Chancebox](0,1) && !vipitemdrop ) vipitemdrop = "AngerySigil"; if ( SWWMUtility.CheckNeedsItem("DivineSprite",true) && !SWWMUtility.ItemExists("DivineSprite",worldonly:true) && Random[Chancebox](0,1) && !vipitemdrop ) vipitemdrop = "DivineSprite"; if ( !vipitemdrop ) vipitemdrop = "GrilledCheeseSandwich"; SpawnCenter(pos,(!Random[Chancebox](0,2)&&vipammodrop)?vipammodrop:vipitemdrop); } } // sometimes mapsets don't have chainsaw spawns for whatever reason // so we drop a hammer as reward Class RewardHammer : ChanceboxReward { override bool CheckRequirements() { return SWWMUtility.CheckNeedsItem("ItamexHammer"); } override void SpawnReward( Vector3 pos ) { SpawnCenter(pos,"ItamexHammer"); } } // mortal rifle ammo treats Class RewardMisterRifle : ChanceboxReward { override bool CheckRequirements() { return SWWMUtility.ItemExists("MisterRifle",ownedonly:true); } override void SpawnReward( Vector3 pos ) { SpawnCenter(pos,"MisterGAmmo"); SpawnCircle(pos,4,"MisterRound"); SpawnCircle(pos,8,"MisterRound",3.); } } // a spare candy gun and some bullets Class RewardCandyGun : ChanceboxReward { override bool CheckRequirements() { return SWWMUtility.ItemExists("CandyGun",ownedonly:true); } override void SpawnReward( Vector3 pos ) { SpawnCenter(pos,"CandyGun"); SpawnCircle(pos,7,"CandyGunBullets"); } } // TODO ray-khom bolts all over // them silver bullets Class RewardSilverBullets : ChanceboxReward { override bool CheckRequirements() { return SWWMUtility.ItemExists("SilverBullet",ownedonly:true); } override void SpawnReward( Vector3 pos ) { SpawnCircle(pos,4,"SilverBullets"); SpawnCircle(pos,6,"SilverBullets",3.); } } // TODO cyan/red plasma cells for the sparkster // buncha spark units Class RewardSparkUnits : ChanceboxReward { override bool CheckRequirements() { return SWWMUtility.ItemExists("Sparkster",ownedonly:true); } override void SpawnReward( Vector3 pos ) { SpawnCenter(pos,"SparkUnit2",true); SpawnCircle(pos,3,"SparkUnit"); } } // assortment of Quadravol cells Class RewardQuadCells : ChanceboxReward { override bool CheckRequirements() { return SWWMUtility.ItemExists("Quadravol",ownedonly:true); } override void SpawnReward( Vector3 pos ) { SpawnCenter(pos,"QuadravolAmmo3",true); SpawnCircle(pos,3,"QuadravolAmmo"); SpawnCircle(pos,6,"QuadravolAmmo",3.); } } // lotta hellblazers Class RewardHellblazers : ChanceboxReward { override bool CheckRequirements() { return SWWMUtility.ItemExists("Hellblazer",ownedonly:true); } override void SpawnReward( Vector3 pos ) { SpawnCenter(pos,"HellblazerMissiles3",true); SpawnCircle(pos,3,"HellblazerMissiles"); SpawnCircle(pos,6,"HellblazerMissiles",3.); } } // BOOLETS Class RewardSheenAmmo : ChanceboxReward { override bool CheckRequirements() { return SWWMUtility.ItemExists("HeavyMahSheenGun",ownedonly:true); } override void SpawnReward( Vector3 pos ) { SpawnCenter(pos,"SheenBigAmmo",true); SpawnCircle(pos,5,"SheenSmallAmmo",3.); SpawnCircle(pos,8,"SheenAmmo3",5.,item2:"SheenAmmo2"); } } // Flak'em Class RewardFlakShells : ChanceboxReward { override bool CheckRequirements() { return SWWMUtility.ItemExists("Eviscerator",ownedonly:true); } override void SpawnReward( Vector3 pos ) { SpawnCenter(pos,"EvisceratorSixPack",true); SpawnCircle(pos,6,"EvisceratorShell",3.); } } // TODO screwbullet droppage for the puntzers // Lotta shells Class RewardShells : ChanceboxReward { override bool CheckRequirements() { return SWWMUtility.ItemExists("Spreadgun",ownedonly:true)||SWWMUtility.ItemExists("Wallbuster",ownedonly:true); } override void SpawnReward( Vector3 pos ) { SpawnCircle(pos,8,"RedShell",2.); SpawnCircle(pos,12,"RedShell",4.,item2:"RedShell"); } } // Buncha nuggets Class RewardNuggets : ChanceboxReward { override void SpawnReward( Vector3 pos ) { SpawnCircle(pos,20,Random[ChanceBox](0,1)?"HealthNuggetItem":"ArmorNuggetItem",1.,7.); } } // Healing surprise Class RewardSuperHealth : ChanceboxReward { override void SpawnReward( Vector3 pos ) { SpawnCenter(pos,"RefresherItem"); SpawnCircle(pos,15,"HealthNuggetItem",3.); } } // 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; foreach ( s:level.Sectors ) { if ( !(s.flags&Sector.SECF_SECRET) ) continue; // 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; foreach ( l:s.lines ) { 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 spots; spots.Clear(); for ( double j=0.; j 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 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 < 60 ) 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,60,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) } } void A_DropSomething() { Array > candidates; candidates.Clear(); foreach ( cls:AllActorClasses ) { let c = (Class)(cls); if ( !c || (c == 'SWWMCollectible') ) continue; let def = GetDefaultByType(c); // check that we can collect it in this IWAD if ( !def.ValidGame() ) 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) || dud ) { // no collectible candidates? just burst into treats if ( (scale.x > .5) && (Random[Chancebox](0,int(9*scale.x*scale.x)) < 3) ) { // spawn another smaller chancebox // (chance increases for the inner box, up until a scale factor of 50% is reached) let a = Spawn("Chancebox",pos+(0,0,3*scale.y)); a.vel.z = FRandom[Chancebox](2,4); a.angle = angle; a.scale *= scale.x-.125; if ( target && (a.scale.x <= .5) ) SWWMUtility.MarkAchievement("matryoshka",target.player); } else { // populate reward choices to randomize Array rewards; foreach ( cls:AllClasses ) { let cls = (Class)(cls); if ( !cls || cls.isAbstract() ) continue; let cr = ChanceboxReward(new(cls)); rewards.Push(cr); } // discard those that don't meet requirements for ( int i=0; i 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; foreach ( cls:AllActorClasses ) { let c = (Class)(cls); if ( !c || (c == 'SWWMCollectible') ) continue; let def = GetDefaultByType(c); // check that we can collect it in this IWAD if ( !def.ValidGame() ) 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"); target = user; 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; } A_SetSize(default.radius*scale.x,default.height*scale.y); } Default { Tag "$T_CHANCEBOX"; Radius 12; Height 20; +MOVEWITHSECTOR; +ROLLSPRITE; +SOLID; +INTERPOLATEANGLES; +COUNTITEM; +CANPASS; 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,pitch:1./scale.x); } 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>int(40*scale.x),"BlowUp"); } Wait; BlowUp: XZW2 A 1 { A_SetSize(default.radius*scale.x,2.5*scale.y); A_QuakeEx(2,2,2,9,0,500,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,falloff:200,rollIntensity:.2); A_StartSound("chancebox/explode",CHAN_VOICE,pitch:1./scale.x); angle = specialf1; pitch = roll = 0; let t = Spawn("ChanceboxTop",Vec3Offset(0,0,20*scale.y)); t.angle = angle; t.scale = scale; let s1 = Spawn("ChanceboxSide",level.Vec3Offset(pos,(RotateVector((12*scale.x,0),angle+90),0))); s1.angle = angle+90; s1.scale = scale; let s2 = Spawn("ChanceboxSide",level.Vec3Offset(pos,(RotateVector((12*scale.x,0),angle-90),0))); s2.angle = angle-90; s2.scale = scale; 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; +NOFRICTION; } override void PostBeginPlay() { Super.PostBeginPlay(); vel = (0,0,20); A_SetSize(default.radius*scale.x,default.height*scale.y); } 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; +NOFRICTION; } override void PostBeginPlay() { Super.PostBeginPlay(); vel = SWWMUtility.Vec3FromAngles(angle,pitch)*20; A_SetSize(default.radius*scale.x,default.height*scale.y); } 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; } }