version "4.10" enum SnekTechSpawnFlags { SNEKTECH_GRETCHENCOUNTER = 0 } const HDLD_KIRI_GRETCHENCOUNTER = "kgc"; class GretchenCounterEventHandler : EventHandler { bool GetItemEnabled(int item) { int spawnFlags = CVar.GetCVar("snektech_spawnflags").GetInt(); if(spawnFlags & (1 << item)) { return true; } return false; } override void WorldThingSpawned(WorldEvent e) { if(!e.Thing) { return; } // Disable drop useless ammo for batteries. HDBattery bat = HDBattery(e.Thing); if(bat) { bat.ItemsThatUseThis.Push("GretchenCounter"); } // Delete disabled items from backpacks that just spawned. HDBackpack pack = HDBackpack(e.Thing); if(pack && !pack.owner) { if(!GetItemEnabled(SNEKTECH_GRETCHENCOUNTER)) { pack.storage.DestroyItem("GretchenCounter"); } } } override void CheckReplacement(ReplaceEvent e) { if(!e.Replacement) { return; } if(e.isFinal) { return; } if(GetItemEnabled(SNEKTECH_GRETCHENCOUNTER)) { if(e.Replacee == "Stimpack") { if(random() < 12) { e.Replacement = "GretchenCounter"; } } } } } 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); // invoker.UpdateDisplay(); 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_PlaySound("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; } // ThinkerIterator iter = ThinkerIterator.Create("BFGNecroShard"); // Actor mo; // int shardCount = 0; // float totalReading = 0.0; // while(mo = BFGNecroShard(iter.Next())) // { // //shardCount++; // float dist = Distance3D(mo); // // Convert to "meters" (with a minimum). // if(dist < 32) { // dist = 32; // } // dist /= 32.0; // // // Arbitrary scaling. // // dist /= 16.0; // totalReading += 1.0 / (dist * dist); // shardCount += 1; // } float totalReading = 0.0; if(GetIsActive()) { totalReading = GetReadingForType("BFGNecroShard", 1.0) + GetReadingForType("HDBarrel", 2.0); } // lastReading = totalReading * 0.01 + lastReading * 0.99; float pct_reading = lastReading * 2000.0; lastReading = totalReading; float r = frandom(0.0, 1.0); if(r < pct_reading && GetIsActive()) { // console.printf("test: %f %f", r, pct_reading); referenceActor.A_StartSound("kiri/gretchencounter_click"); } // if(pct_reading > 1.0) { // pct_reading = 1.0; // } // String meter_str = ""; // float k; // for(k = 0.0; k < 1.0; k += 0.02) { // if(k < pct_reading) { // meter_str = meter_str.."#"; // } else { // meter_str = meter_str.."_"; // } // } // if(pct_reading > 1.0) { // pct_reading = 1.0; // } // A_OverlayRotate(355, // angle_min + (angle_max - angle_min) * (1.0 - pct_reading)); angularvelocity += pct_reading - needlePosition; angularvelocity *= 0.97; bool wasNeedleAtMax = (needlePosition >= 0.9); needlePosition += angularvelocity * 0.1; needlePosition = clamp(needlePosition, 0.0, 1.0); // console.printf("Ang vel %f", angularvelocity); // console.printf("Shard count: [%s] %f", meter_str, pct_reading); framesSinceLastBeep++; // if(GetIsActive()) { 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; } }