// 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 -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 9; } } 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; } } 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 8; } 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; } 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 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() { A_StartSound("misc/tada",CHAN_ITEM); double ang, pt; int numpt = Random[ExploS](100,120); for ( int i=0; i 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 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 < 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 > candidates; candidates.Clear(); for ( int i=0; i)(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 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; } }