// ---------------------------------------------------------------------- // Gretchen Counter // ---------------------------------------------------------------------- // // This object clearly looks like it was recycled from a very old // Geiger counter. // // Smells kind of like rotting meat. // // Try not to think about it. // #include "zscript/snektech.zs" const HDLD_KIRI_GRETCHENCOUNTER = "kgc"; enum GretchenCounterStatus { KGC_BATTERY=0, KGC_ACTIVE=1 } class GretchenCounter : HDWeapon { default { +weapon.wimpy_weapon; +inventory.invbar; +hdweapon.droptranslation; +hdweapon.fitsinbackpack; hdweapon.barrelsize 0,0,0; weapon.selectionorder 1014; scale 0.6; inventory.icon "KGCPA0"; inventory.pickupmessage "Picked up a Gretchen Counter."; inventory.pickupsound "derp/crawl"; translation 0; tag "Gretchen Counter"; hdweapon.refid HDLD_KIRI_GRETCHENCOUNTER; } float lastReading; float angularVelocity; float needlePosition; int framesSinceLastBeep; int lastBatteryCheckFrame; override bool AddSpareWeapon(actor newowner) { return AddSpareWeaponRegular(newowner); } override HDWeapon GetSpareWeapon(actor newowner, bool reverse, bool doselect) { return GetSpareWeaponRegular(newowner, reverse, doselect); } int hash(int x) { x = ((x >> 16) ^ x) * 0x45d9f3b; x = ((x >> 16) ^ x) * 0x45d9f3b; x = (x >> 16) ^ x; return x; } bool HasBatteryCharge() { int bat = weaponstatus[KGC_BATTERY]; if(bat > 0) { int timeHash = hash(level.time / 10); if((timeHash % 5) < bat) { return true; } } return false; } void TickBattery() { if(weaponstatus[KGC_ACTIVE] && weaponstatus[KGC_BATTERY] > 0) { // Discharge rate: 1/1000 chance per tick. int r = random(0, 4000); if(r < 2) { weaponstatus[KGC_BATTERY] -= 1; } } } states { spawn: KGCP # 1 { frame = invoker.GetIsActive(); } loop; // Needle overlay states. needle_indicator: KGCN # 1; wait; select0: KGCM # 1 { updateOverlay(); } goto select0big; deselect0: KGCM # 1 { updateOverlay(); } goto deselect0big; user3: #### A 0 A_MagManager("HDBattery"); goto ready; unload: KGCM # 1 offset(0, 20) { updateOverlay(); } KGCM # 1 offset(0, 40) { updateOverlay(); } KGCM # 1 offset(0, 55) { updateOverlay(); } KGCM # 10 { if(invoker.weaponstatus[KGC_BATTERY] != -1) { A_StartSound("weapons/pocket", 9); HDMagAmmo.GiveMag(self, "HDBattery", invoker.weaponstatus[KGC_BATTERY]); invoker.weaponstatus[KGC_BATTERY] = -1; } } KGCM # 1 offset(0, 55) { updateOverlay(); } KGCM # 1 offset(0, 40) { updateOverlay(); } KGCM # 1 offset(0, 20) { updateOverlay(); } KGCM # 1 offset(0, 0) { updateOverlay(); } goto ready; reload: KGCM # 1 offset(0, 20) { updateOverlay(); } KGCM # 1 offset(0, 40) { updateOverlay(); } KGCM # 1 offset(0, 55) { updateOverlay(); } KGCM # 10 { if(CountInv("HDBattery")) { // Unload existing battery. if(invoker.weaponstatus[KGC_BATTERY] != -1) { A_StartSound("weapons/pocket", 9); HDMagAmmo.GiveMag(self, "HDBattery", invoker.weaponstatus[KGC_BATTERY]); invoker.weaponstatus[KGC_BATTERY] = -1; } // Load new mag. A_StartSound("weapons/plasload", 8); HDMagAmmo magAmmo = HDMagAmmo( FindInventory("HDBattery")); if(magAmmo) { invoker.weaponstatus[KGC_BATTERY] = magAmmo.TakeMag(true); } } } KGCM # 1 offset(0, 55) { updateOverlay(); } KGCM # 1 offset(0, 40) { updateOverlay(); } KGCM # 1 offset(0, 20) { updateOverlay(); } KGCM # 1 offset(0, 0) { updateOverlay(); } goto ready; ready: KGCM B 1 { A_Overlay(355, "needle_indicator"); A_OverlayPivotAlign(355, PSPA_CENTER, PSPA_BOTTOM); updateOverlay(); // USER3 = MagManager. // USER4 = Unload. A_WeaponReady(WRF_ALLOWUSER3 | WRF_ALLOWUSER4 | WRF_ALLOWRELOAD); } goto readyend; fire: KGCM # 1 offset(0, 20) { updateOverlay(); } KGCM # 1 offset(0, 40) { updateOverlay(); } KGCM # 1 offset(0, 55) { updateOverlay(); } KGCM # 1 { invoker.weaponstatus[KGC_ACTIVE] = !invoker.weaponstatus[KGC_ACTIVE]; A_StartSound("kiri/gretchencounter_onoff"); updateOverlay(); } KGCM # 1 offset(0, 55) { updateOverlay(); } KGCM # 1 offset(0, 40) { updateOverlay(); } KGCM # 1 offset(0, 20) { updateOverlay(); } KGCM # 1 offset(0, 0) { updateOverlay(); } goto waiting; waiting: KGCM # 1 { updateOverlay(); } KGCM # 1 { updateOverlay(); } KGCM # 1 { updateOverlay(); } KGCM # 1 { updateOverlay(); } KGCM # 1 { updateOverlay(); } KGCM # 1 A_Refire("waiting"); goto ready; readyend: #### # 0 A_ReadyEnd(); #### # 0 A_Jump(256,"ready"); } action void updateOverlay() { float angle_min = -45.0; float angle_max = 45.0; A_OverlayRotate(355, angle_min + (angle_max - angle_min) * (1.0 - invoker.needlePosition)); if(invoker.weaponstatus[KGC_ACTIVE]) { if(invoker.HasBatteryCharge()) { player.getpsprite(PSP_WEAPON).frame = 1; } else { player.getpsprite(PSP_WEAPON).frame = 2; } } else { player.getpsprite(PSP_WEAPON).frame = 0; } player.getpsprite(355).frame = int(invoker.GetIsActive()); } float GetReadingForType(String type_name, float distance_scale) { ThinkerIterator iter = ThinkerIterator.Create(type_name); Actor mo; float totalReading = 0.0; Actor referenceActor = owner; if(!referenceActor) { referenceActor = self; } while(mo = Actor(iter.Next())) { if(mo.health <= 0) { continue; } float dist = referenceActor.Distance3D(mo); // Convert to "meters" (with a minimum). if(dist < 32) { dist = 32; } // Scale distance. dist *= distance_scale; // Apply inverse-square falloff. totalReading += 1.0 / (dist * dist); } return totalReading; } override void Tick() { super.Tick(); UpdateDisplay(); TickBattery(); } override void DrawHUDStuff( HDStatusBar statusBar, HDWeapon weapon, HDPlayerPawn pawn) { if(statusBar.hudlevel == 1) { statusBar.drawbattery( -54, -4, statusBar.DI_SCREEN_CENTER_BOTTOM, reloadorder : true); statusBar.drawnum( pawn.countinv("HDBattery"), -46, -8, statusBar.DI_SCREEN_CENTER_BOTTOM); } int bat = weapon.weaponstatus[KGC_BATTERY]; if(bat > 0) { // Draw battery bar. statusbar.drawwepnum(bat, 20); } else if(bat > -1) { // No battery. statusBar.drawstring( statusBar.mamountfont, "00000", (-16,-9), statusBar.DI_TEXT_ALIGN_RIGHT | statusBar.DI_TRANSLATABLE | statusBar.DI_SCREEN_CENTER_BOTTOM, Font.CR_DARKGRAY); } } bool GetIsActive() { return weaponstatus[KGC_ACTIVE] && HasBatteryCharge(); } override void InitializeWepStats(bool idfa) { weaponstatus[KGC_BATTERY] = 20; weaponstatus[KGC_ACTIVE] = 0; super.InitializeWepStats(idfa); } override double WeaponBulk() { int battery_weight = 0; if(weaponstatus[KGC_BATTERY] != -1) { // Battery bulk = 18, but inside this combines with its // bulk (takes up less room). battery_weight = 10; } // This item's bulk is 20. Battery adds to that. return battery_weight + 20; } void UpdateDisplay() { Actor referenceActor = owner; if(!referenceActor) { referenceActor = self; } // Get the actual reading. float totalReading = 0.0; if(GetIsActive()) { totalReading = GetReadingForType("BFGNecroShard", 1.0) + GetReadingForType("HDBarrel", 2.0); } float pct_reading = lastReading * 2000.0; lastReading = totalReading; // Make clicky noises if we're getting readings. float r = frandom(0.0, 1.0); if(r < pct_reading && GetIsActive()) { referenceActor.A_StartSound("kiri/gretchencounter_click"); } // Apply some physics to the needle rotation. angularvelocity += pct_reading - needlePosition; angularvelocity *= 0.97; // Check to see if the needle was at max last frame. If it // wasn't at max before, and it is now, we need to start // beeping. bool wasNeedleAtMax = (needlePosition >= 0.9); needlePosition += angularvelocity * 0.1; needlePosition = clamp(needlePosition, 0.0, 1.0); // Play a "beep" if the needle is at max, and it's either been // long enough or we weren't at max before. Make it start and // stop. framesSinceLastBeep++; if(!wasNeedleAtMax || framesSinceLastBeep > 15) { if(needlePosition >= 0.9) { referenceActor.A_StartSound("kiri/gretchencounter_blip"); framesSinceLastBeep = 0; } } } override string GetHelpText() { return "" ..WEPHELP_FIRE.." Toggle on/off\n" ..WEPHELP_RELOAD.." Reload battery\n" ..WEPHELP_UNLOAD.." Remove battery\n" ..WEPHELP_MAGMANAGER; } }