// FIXME: Make consts and enums consistent formatting. // FIXME: Add bulk. 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 KIRI_SPRAY_PRESSURE_DECAY_RATE = 0.1; 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; tag "Spraypaint Can"; } states { spawn: KSPR B -1; stop; ready: KSPR A 1 { A_WeaponReady(WRF_ALL); A_WeaponBusy(false); } goto readyend; altfire: 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.GetPressure()) / float(KIRI_SPRAY_MAXPRESSURE)); pressureIncreaseScale *= pressureIncreaseScale; // Add pressure. int pressure = invoker.GetPressure(); pressure += random(0, KIRI_SPRAY_PRESSUREBUILDSCALE * invoker.weaponstatus[KIRI_SPRAY_WS_PAINT] / KIRI_SPRAY_MAXPAINT); // Cap pressure amount. if(pressure > KIRI_SPRAY_MAXPRESSURE) { pressure = KIRI_SPRAY_MAXPRESSURE; } invoker.SetPressure(pressure); } 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.GetPressure() < 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.SetPressure(invoker.GetPressure() - 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; firemode: 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); } // Why do we store pressure as this weird equation? // // Pressure is stored as a time since the a hypothetical last // "fully charged" time, based on the level time and pressure // decay rate. // // This way we can decay pressure without ticking, important for // spare weapons and stuff in backpacks. int GetPressure() const { int time = level.TotalTime; int lastChargedTime = weaponstatus[KIRI_SPRAY_WS_PRESSURE]; int pressure = int( float(KIRI_SPRAY_MAXPRESSURE) - float(time - lastChargedTime) * KIRI_SPRAY_PRESSURE_DECAY_RATE); if(pressure > KIRI_SPRAY_MAXPRESSURE) { pressure = KIRI_SPRAY_MAXPRESSURE; } if(pressure < 0) { pressure = 0; } return pressure; } void SetPressure(int pressure) { if(pressure > KIRI_SPRAY_MAXPRESSURE) { pressure = KIRI_SPRAY_MAXPRESSURE; } if(pressure < 0) { pressure = 0; } int time = level.TotalTime; int lastChargedTime = round( float(pressure - KIRI_SPRAY_MAXPRESSURE) / float(KIRI_SPRAY_PRESSURE_DECAY_RATE) + time); weaponstatus[KIRI_SPRAY_WS_PRESSURE] = lastChargedTime; } void ResetPressure() { int resetValue = -int(float(KIRI_SPRAY_MAXPRESSURE) / float(KIRI_SPRAY_PRESSURE_DECAY_RATE)); weaponstatus[KIRI_SPRAY_WS_PRESSURE] = resetValue; } override void Consolidate() { ResetPressure(); } override void InitializeWepStats(bool idfa) { super.InitializeWepStats(idfa); SetPressure(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 DrawHUDStuff( HDStatusBar statusBar, HDWeapon weapon, HDPlayerPawn pawn) { // Current pressure (top bar). statusBar.DrawWepNum( GetPressure(), 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 double WeaponBulk() { return 10; } override String GetHelpText() { super.GetHelpText(); int messageIndex = level.time; 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_FIREMODE .. " Cycle pattern (current: "..currentPattern..").\n".. WEPHELP_ALTFIRE .. " Shake the can.\n".. WEPHELP_FIRE .. " "..messages[messageIndex % messages.Size()].."\n"; } } class SprayerDecalSpawner : Actor { default { +nogravity; } states { spawn: TNT1 A -1; 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"; } } class SnekSpray_NBPride : SprayerPattern { default { SprayerPattern.decalName "SnekSpray_NBPride"; } } class SnekSpray_AcePride : SprayerPattern { default { SprayerPattern.decalName "SnekSpray_AcePride"; } } class SnekSpray_ProgressPride : SprayerPattern { default { SprayerPattern.decalName "SnekSpray_ProgressPride"; } } class SnekSpray_DemiPride : SprayerPattern { default { SprayerPattern.decalName "SnekSpray_DemiPride"; } } class SnekSpray_PanPride : SprayerPattern { default { SprayerPattern.decalName "SnekSpray_PanPride"; } } class SnekSpray_SwitchPride : SprayerPattern { default { SprayerPattern.decalName "SnekSpray_SwitchPride"; } } class SnekSpray_BiPride : SprayerPattern { default { SprayerPattern.decalName "SnekSpray_BiPride"; } } class SnekSpray_GayPride : SprayerPattern { default { SprayerPattern.decalName "SnekSpray_GayPride"; } }