// FIXME: Make consts and enums consistent formatting. const KIRI_SPRAY_DISTANCE = 96; const KIRI_SPRAY_SHAKEANIM_MAXANGLE = 20.0; const KIRI_SPRAY_MAXPAINT = 50; const KIRI_SPRAY_MAXPRESSURE = 100; const KIRI_SPRAY_PRESSUREBUILDSCALE = 20; const KIRI_SPRAY_PRESSURE_PER_USE = (KIRI_SPRAY_MAXPRESSURE / 2); const HDLD_KIRI_SPRAYCAN = "ksp"; enum KiriSprayerStatus { KIRI_SPRAY_WS_PAINT = 1, KIRI_SPRAY_WS_PRESSURE = 2 } class Sprayer : HDWeapon { default { radius 2; height 4; +SpriteAngle; Scale 0.4; +hdweapon.fitsinbackpack; +weapon.wimpy_weapon; +INVENTORY.PERSISTENTPOWER; +INVENTORY.INVBAR; // inventory.icon "KSPRB0"; inventory.pickupsound "kiri/spraycan_rattle"; inventory.pickupmessage "Picked up some spraypaint cans."; // inventory.amount 1; // inventory.maxamount 100; hdweapon.refid HDLD_KIRI_SPRAYCAN; } states { spawn: KSPR B -1; stop; ready: KSPR A 1 { A_WeaponReady(WRF_ALL); A_WeaponBusy(false); } goto readyend; reload: KSPR A 1 offset(0, 20) { A_OverlayPivot(0, 0.5, 1.0); A_OverlayRotate(0, 0); A_StartSound( "kiri/spraycan_rattle", CHAN_WEAPON, CHANF_NOSTOP); } KSPR A 1 offset(0, 40) { A_OverlayRotate(0, sin(level.time * 5.0) * KIRI_SPRAY_SHAKEANIM_MAXANGLE * 1.0/3.0); } KSPR A 1 offset(0, 60) { A_OverlayRotate(0, sin(level.time * 5.0) * KIRI_SPRAY_SHAKEANIM_MAXANGLE * 2.0/3.0); } KSPR A 1 offset(0, 80) { A_OverlayRotate(0, sin(level.time * 5.0) * KIRI_SPRAY_SHAKEANIM_MAXANGLE); // Taper off pressure increase amount based on how much // pressure is in there already. float pressureIncreaseScale = 1.0 - ( float(invoker.weaponstatus[KIRI_SPRAY_WS_PRESSURE]) / float(KIRI_SPRAY_MAXPRESSURE)); pressureIncreaseScale *= pressureIncreaseScale; // Add pressure. invoker.weaponstatus[KIRI_SPRAY_WS_PRESSURE] += random(0, KIRI_SPRAY_PRESSUREBUILDSCALE * invoker.weaponstatus[KIRI_SPRAY_WS_PAINT] / KIRI_SPRAY_MAXPAINT); // Cap pressure amount. if(invoker.weaponstatus[KIRI_SPRAY_WS_PRESSURE] > KIRI_SPRAY_MAXPRESSURE) { invoker.weaponstatus[KIRI_SPRAY_WS_PRESSURE] = KIRI_SPRAY_MAXPRESSURE; } } KSPR A 1 offset(0, 60) { A_OverlayRotate(0, sin(level.time * 5.0) * KIRI_SPRAY_SHAKEANIM_MAXANGLE * 2.0/3.0); } KSPR A 1 offset(0, 40) { A_OverlayRotate(0, sin(level.time * 5.0) * KIRI_SPRAY_SHAKEANIM_MAXANGLE * 1.0/3.0); } KSPR A 1 offset(0, 20) { A_OverlayRotate(0, 0); } goto ready; fire: KSPR A 2 { if(invoker.weaponstatus[KIRI_SPRAY_WS_PRESSURE] < KIRI_SPRAY_PRESSURE_PER_USE) { invoker.owner.A_Log("Not enough pressure to spray."); } else { Actor spawnedThing; bool success; float zOffset = GunHeight() * 0.8; FLineTraceData lineTraceData; class sprayerPatternClass = "SprayerPattern"; // Find the spray pattern class that matches the // player's CVar for selected pattern. String currentSprayCVar = CVar.GetCVar( "snektech_spraypattern", invoker.owner.player).GetString(); for(int i = 0; i < AllActorClasses.size(); i++) { class thisPatternClass = (class)(AllActorClasses[i]); if(thisPatternClass) { if(thisPatternClass.GetClassName() == currentSprayCVar) { sprayerPatternClass = (class)(AllActorClasses[i]); break; } } } // Get the actual decal name out of it. let defaultSprayClass = GetDefaultByType(sprayerPatternClass); String actualDecalName = defaultSprayClass.decalName; // Find a wall to spray on. bool traceHit = LineTrace( angle, KIRI_SPRAY_DISTANCE, pitch, flags : TRF_THRUACTORS, offsetz : zOffset, data : lineTraceData); if(traceHit && lineTraceData.HitLine) { A_StartSound( "kiri/spraycan_spray", CHAN_WEAPON); [success, spawnedThing] = A_SpawnItemEx( "SprayerDecalSpawner", xofs : 0, yofs : 0, zofs : zOffset); SprayerDecalSpawner spawner = SprayerDecalSpawner(spawnedThing); if(success && spawner) { spawner.A_SetPitch(pitch); spawner.A_SetAngle(angle); spawner.actualDecalName = actualDecalName; spawner.master = invoker.owner; invoker.weaponstatus[KIRI_SPRAY_WS_PRESSURE] -= KIRI_SPRAY_PRESSURE_PER_USE; invoker.weaponstatus[KIRI_SPRAY_WS_PAINT] -= 1; } } else { invoker.owner.A_Log("Not close enough to a wall to spray."); } } } goto waiting; altfire: KSPR A 2 { invoker.CycleSprayPattern(); } goto waiting; waiting: KSPR A 5; KSPR A 0 A_Refire("waiting"); goto ready; select0: KSPR A 0 offset(0, 120); ---- A 1 A_Raise(12); wait; deselect0: KSPR A 0; ---- A 1 A_Lower(12); wait; } override bool AddSpareWeapon(actor newowner) { return AddSpareWeaponRegular(newowner); } override HDWeapon GetSpareWeapon(actor newowner, bool reverse, bool doselect) { return GetSpareWeaponRegular(newowner, reverse, doselect); } override void InitializeWepStats(bool idfa) { super.InitializeWepStats(idfa); weaponstatus[KIRI_SPRAY_WS_PRESSURE] = 0; // Add a little bit of randomness to the amount of paint in // the can. weaponstatus[KIRI_SPRAY_WS_PAINT] = KIRI_SPRAY_MAXPAINT - random(0, KIRI_SPRAY_MAXPAINT / 10); } override void Tick() { super.Tick(); // Gradually decay pressure. if(random(0, 256) < 4) { weaponstatus[KIRI_SPRAY_WS_PRESSURE] -= 1; if(weaponstatus[KIRI_SPRAY_WS_PRESSURE] < 0) { weaponstatus[KIRI_SPRAY_WS_PRESSURE] = 0; } } } override void DrawHUDStuff( HDStatusBar statusBar, HDWeapon weapon, HDPlayerPawn pawn) { // Current pressure (top bar). statusBar.DrawWepNum( weaponstatus[KIRI_SPRAY_WS_PRESSURE], KIRI_SPRAY_MAXPRESSURE, posy:-10); // Total paint remaining (bottom bar). statusBar.DrawWepNum( weaponstatus[KIRI_SPRAY_WS_PAINT], KIRI_SPRAY_MAXPAINT); } void GetSprayPatternList(Array ret) { ret.clear(); for(int i = 0; i < AllActorClasses.Size(); i++) { class c = AllActorClasses[i]; if(c is "SprayerPattern" && c != "SprayerPattern") { ret.push(c.GetClassName()); } } } void CycleSprayPattern() { // Find the current entry in the list of patterns. String currentPattern = CVar.GetCVar("snektech_spraypattern", owner.player).GetString(); Array patternList; GetSprayPatternList(patternList); int currentIndex = patternList.find(currentPattern); // Make sure we're actually in the list (CVar might not be in // list, due to mods getting shuffled around and whatever). if(currentIndex >= patternList.size()) { currentIndex = -1; } // Increment the index. int newIndex = (currentIndex + 1) % patternList.size(); // Set the new CVar. CVar.GetCVar("snektech_spraypattern", owner.player).SetString(patternList[newIndex]); currentPattern = CVar.GetCVar("snektech_spraypattern", owner.player).GetString(); owner.A_Log("Selected spray pattern: "..currentPattern); // Current pattern is referenced in the help text, so reset // it. A_SetHelpText(); } override String GetHelpText() { super.GetHelpText(); int messageIndex = level.time; //(level.time / 60); Array messages = { "Deface the tyrant's property.", "Engage in civil disobedience.", "Create a beautiful work of art.", "Show the world that you still exist.", "Defy the tyrant." }; String currentPattern = CVar.GetCVar("snektech_spraypattern", owner.player).GetString(); return WEPHELP_ALTFIRE.. " Cycle pattern (current: "..currentPattern..").\n".. WEPHELP_RELOAD .. " Shake the can.\n".. WEPHELP_FIRE .. " "..messages[messageIndex % messages.Size()].."\n"; } } class SprayerDecalSpawner : Actor { default { +nogravity; } states { spawn: TNT1 A 0; stop; } int timeSinceLastSpray; int thisSprayerId; String actualDecalName; override void PostBeginPlay() { Super.PostBeginPlay(); A_SprayDecal(actualDecalName, KIRI_SPRAY_DISTANCE); // Figure out a new ID number. ThinkerIterator iter = ThinkerIterator.Create("SprayerDecalSpawner"); SprayerDecalSpawner otherSpawner; int maxId = 0; while(otherSpawner = SprayerDecalSpawner(iter.Next())) { if(otherSpawner == self) { continue; } // Keep track of the highest ID. if(otherSpawner.thisSprayerId > maxId) { if(otherSpawner.master == master) { maxId = otherSpawner.thisSprayerId; } } } thisSprayerId = maxId + 1; // Clear old sprayers. iter = ThinkerIterator.Create("SprayerDecalSpawner"); while(otherSpawner = SprayerDecalSpawner(iter.Next())) { if(otherSpawner == self) { continue; } if(otherSpawner.thisSprayerId < (thisSprayerId - 5)) { if(otherSpawner.master == master) { otherSpawner.Destroy(); } } } } override void Tick() { super.Tick(); timeSinceLastSpray++; if(timeSinceLastSpray >= 35 * 60) { A_SprayDecal(actualDecalName, KIRI_SPRAY_DISTANCE); timeSinceLastSpray = 0; } } } class SprayerPattern : Actor { string decalName; property decalName:decalName; default { SprayerPattern.decalName "KiriTestDecal"; } } class SnekSpray_TransPride : SprayerPattern { default { SprayerPattern.decalName "SnekSpray_TransPride"; } } class SnekSpray_LesbianPride : SprayerPattern { default { SprayerPattern.decalName "SnekSpray_LesbianPride"; } }