From 8ddd7a946378f4c0f0711e565d444c2c84936f1a Mon Sep 17 00:00:00 2001 From: James Nash <148967915+jimsworld@users.noreply.github.com> Date: Tue, 11 Mar 2025 11:43:16 +0000 Subject: [PATCH 01/37] Create hev.h First commit. This file is the most up to date version since our last PM. --- hev.h | 417 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 417 insertions(+) create mode 100644 hev.h diff --git a/hev.h b/hev.h new file mode 100644 index 000000000..736ba7802 --- /dev/null +++ b/hev.h @@ -0,0 +1,417 @@ +#ifndef PROPS_HEV_H +#define PROPS_HEV_H +#define PROP_TYPE Hev + +// Half-Life Hazardous Environment Suit Prop +// +// How to use: +// Power button: +// Long-click: on/off +// double-click: sound track on/off +// triple-click: next preset +// AUX button: +// hold button to heal. +// single click: deactivate hazard +// double-click: armor readout +// triple-click: previous preset +// Clash will cause varying damage based on strength +// +// Damage and Hazards are only active when prop is on. + +#include "prop_base.h" +#include + +EFFECT(health); +EFFECT(armor); +EFFECT(stun); +EFFECT(death); +EFFECT(armor_alarm); +EFFECT(armor_depleted); +EFFECT(major); +EFFECT(minor); +EFFECT(morphine); + +class Hev : public PROP_INHERIT_PREFIX PropBase { +public: + Hev() : PropBase() {} + const char* name() override { return "Hev"; } + + int health_ = 100; + int armor_ = 100; + + bool dead_ = false; + bool armor_depleted_ = false; + bool morphine_triggered_ = false; + + enum Hazard { + HAZARD_NONE = 0, + HAZARD_BIO = 1, + HAZARD_RAD = 2, + HAZARD_BLO = 3, + HAZARD_CHE = 4, + HAZARD_HEA = 5, + HAZARD_SHO = 6, + }; + + Hazard current_hazard_ = HAZARD_NONE; + + void LowBatteryOff() override { + if (SFX_poweron) { + PropBase::LowBatteryOff(); + } + } + + + // Calculate Physical Damage ////////////////////////////////////////////////// + void DoDamage(int damage, bool quiet = false) { + int tens = health_ / 10; + if (armor_ >= damage) { + armor_ -= ceilf(damage * 0.80 / 2); // Applies 80% divided by 2 of damage to armor, if armor is enough. + health_ -= ceilf(damage * 0.20); // Applies 20% of damage to health, if armor is enough. + } else { + int excess_physical = damage - armor_; + health_ -= excess_physical; // Only the excess of physical damage impacts health. + armor_ = 0; // Armor is depleted. + } + + // Enforce minimum values + if (health_ < 0) health_ = 0; + if (armor_ < 0) armor_ = 0; + + // Damage + if (!quiet) SaberBase::DoEffect(EFFECT_STUN, 0.0, damage); + + // Death Sound + if (health_ == 0 && !dead_) { // Check flag to avoid death sound spam + SaberBase::DoEffect(EFFECT_EMPTY, 0.0, 100); + dead_ = true; + return; + } + + // Reset flag if health is above 0 + if (health_ > 0) { + dead_ = false; + } + + // Health Alert - only plays when health enters a new multiple of 10 + int new_tens = health_ / 10; + if (tens != new_tens) { + SaberBase::DoEffect(EFFECT_USER1, 0.0, 0); + } + + // Print health and armor + PVLOG_NORMAL << "Health: " << health_ << " / "; + PVLOG_NORMAL << "Armor: " << armor_ << "\n"; + } + + + // Armor Readout ///////////////////////////////////////////////////////////// + void armor_readout() { + PVLOG_NORMAL << "Current Armor: " << armor_ << "\n"; // Debug logging + SFX_armor.SelectFloat(armor_ / 100.0); + hybrid_font.PlayCommon(&SFX_armor); + } + + + // Clashes /////////////////////////////////////////////////////////////////// + uint32_t last_clash_time_ = 0; + const uint32_t CLASH_DEBOUNCE_MS = 100; // Adjust debounce time as needed + + void Clash(bool stab, float strength) override { + // Ignore Clash if within debounce period + uint32_t current_time = millis(); + if (current_time - last_clash_time_ < CLASH_DEBOUNCE_MS) { + return; + } + last_clash_time_ = current_time; + + // Stop Clashes if health is 0 + if (health_ == 0) { + return; + } + + // Strength multiplier + int damage = (int)(strength * 4); + + // Play Clash sounds based on strength + float v = (strength - GetCurrentClashThreshold()) / 3; + SFX_clash.SelectFloat(v); + SFX_clsh.SelectFloat(v); + SFX_stab.SelectFloat(v); + + // Cap damage at 50 + if (damage > 50) { + damage = 50; + } + + // Play Major or Minor Detected Voice Lines + // if (damage >= 25) { + // morphine_triggered_ = true; + // SaberBase::DoEffect(EFFECT_MAJOR, 0.0, damage); + // } else { + // SaberBase::DoEffect(EFFECT_MINOR, 0.0, damage); + // } + + // Play Armor Alarm if damage is 30 or more + if (damage >= 30) { + hybrid_font.PlayPolyphonic(&SFX_armor_alarm); + } + + // Apply Physical Damage and print to log + DoDamage(damage, true); + PVLOG_NORMAL << "Physical Damage: -" << damage << "\n"; + + // No stabs! + PropBase::Clash(false, strength); + } + + + // Swings do nothing! //////////////////////////////////////////////////// + void DoMotion(const Vec3& motion, bool clear) override { + PropBase::DoMotion(Vec3(0), clear); + } + + + // Random Hazards //////////////////////////////////////////////////////////// + uint32_t last_random_draw_ = millis(); + + // Activate a Random Hazard, only if health is above 0 and no Hazard is currently active. + void CheckRandomEvent() { + if (millis() - last_random_draw_ > HEV_RANDOM_EVENT_INTERVAL_MS && health_ > 0 && current_hazard_ == HAZARD_NONE) { + last_random_draw_ = millis(); + if (random(100) < HEV_RANDOM_HAZARD_CHANCE) { + PVLOG_NORMAL << "Activating hazard.\n"; + current_hazard_ = (Hazard)(1 + random(6)); + hazard_decrease_millis_ = millis(); + SaberBase::DoEffect(EFFECT_ALT_SOUND, 0.0, current_hazard_); + } + } + } + + // Decrease armor (if any) or health over time. + uint32_t hazard_decrease_millis_ = millis(); + void HazardDecrease() { + if (current_hazard_ != HAZARD_NONE) { + if (millis() - hazard_decrease_millis_ > HEV_HAZARD_DECREASE_MS) { + hazard_decrease_millis_ = millis(); + + // Play stun sounds if there is armor + if (armor_ > 0) { + armor_--; + SaberBase::DoEffect(EFFECT_STUN, 0.0, 1); + } else { + DoDamage(1); + } + + // Stop the current Hazard if health drops to 0 + if (health_ == 0) { + current_hazard_ = HAZARD_NONE; + SaberBase::DoEffect(EFFECT_ALT_SOUND, 0.0, current_hazard_); + + // Print health and armor + PVLOG_NORMAL << "Health: " << health_ << " / "; + PVLOG_NORMAL << "Armor: " << armor_ << "\n"; + } + } + } + } + + + // Healing /////////////////////////////////////////////////////////////////// + // Increase health (if healing). + uint32_t health_increase_millis_ = millis(); + void IncreaseHealth() { + if (SaberBase::Lockup()) { + if (millis() - health_increase_millis_ > HEV_HEALTH_INCREASE_MS) { + health_increase_millis_ = millis(); + health_++; + PVLOG_NORMAL << "Health: " << health_ << "\n"; + if (health_ >= 100) { + health_ = 100; + SaberBase::DoEndLockup(); + SaberBase::SetLockup(SaberBase::LOCKUP_NONE); + } + } + } + } + + + // Recharging //////////////////////////////////////////////////////////////// + // Increase armor (if recharging). + uint32_t armor_increase_millis_ = millis(); + void IncreaseArmor() { + if (SaberBase::Lockup()) { + if (millis() - armor_increase_millis_ > HEV_ARMOR_INCREASE_MS) { + armor_increase_millis_ = millis(); + armor_++; + PVLOG_NORMAL << "Armor: " << armor_ << "\n"; + if (armor_ >= 100) { + armor_ = 100; + SaberBase::DoEndLockup(); + SaberBase::SetLockup(SaberBase::LOCKUP_NONE); + } + } + } + } + + + // Main Loop ////////////////////////////////////////////////////////////////// + void Loop() override { + CheckRandomEvent(); + HazardDecrease(); + if (SaberBase::Lockup() == SaberBase::LOCKUP_NORMAL) { + IncreaseHealth(); + } else if (SaberBase::Lockup() == SaberBase::LOCKUP_LIGHTNING_BLOCK) { + IncreaseArmor(); + } + PropBase::Loop(); + } + + + // Button Events ///////////////////////////////////////////////////////////// + bool Event2(enum BUTTON button, EVENT event, uint32_t modifiers) override { + switch (EVENTID(button, event, modifiers)) { + + // On/Off long-click + case EVENTID(BUTTON_POWER, EVENT_FIRST_CLICK_LONG, MODE_OFF): + On(); + return true; + case EVENTID(BUTTON_POWER, EVENT_FIRST_CLICK_LONG, MODE_ON): + Off(); + return true; + + // short-click AUX to clear hazard + case EVENTID(BUTTON_AUX, EVENT_FIRST_SAVED_CLICK_SHORT, MODE_ANY_BUTTON | MODE_ON): + case EVENTID(BUTTON_AUX, EVENT_FIRST_SAVED_CLICK_SHORT, MODE_ANY_BUTTON | MODE_OFF): + if (current_hazard_) { + current_hazard_ = HAZARD_NONE; + SaberBase::DoEffect(EFFECT_ALT_SOUND, 0.0, current_hazard_); + return true; + } + // Play a no-hazard sound ? + return true; + + // Double-click power to start/stop track. + case EVENTID(BUTTON_POWER, EVENT_SECOND_SAVED_CLICK_SHORT, MODE_ANY_BUTTON | MODE_ON): + case EVENTID(BUTTON_POWER, EVENT_SECOND_SAVED_CLICK_SHORT, MODE_ANY_BUTTON | MODE_OFF): + StartOrStopTrack(); + return true; + + // Double-click AUX for Armor Readout. + case EVENTID(BUTTON_AUX, EVENT_SECOND_SAVED_CLICK_SHORT, MODE_ANY_BUTTON | MODE_ON): + case EVENTID(BUTTON_AUX, EVENT_SECOND_SAVED_CLICK_SHORT, MODE_ANY_BUTTON | MODE_OFF): + armor_readout(); + return true; + + // Next/Previous preset. Triple-click on either button. + case EVENTID(BUTTON_POWER, EVENT_THIRD_SAVED_CLICK_SHORT, MODE_ANY_BUTTON | MODE_ON): + case EVENTID(BUTTON_POWER, EVENT_THIRD_SAVED_CLICK_SHORT, MODE_ANY_BUTTON | MODE_OFF): + next_preset(); + return true; + case EVENTID(BUTTON_AUX, EVENT_THIRD_SAVED_CLICK_SHORT, MODE_ANY_BUTTON | MODE_ON): + case EVENTID(BUTTON_AUX, EVENT_THIRD_SAVED_CLICK_SHORT, MODE_ANY_BUTTON | MODE_OFF): + previous_preset(); + return true; + + + // Hold AUX to start healing + case EVENTID(BUTTON_AUX, EVENT_HELD_MEDIUM, MODE_ON): + if (!SaberBase::Lockup()) { + SaberBase::SetLockup(SaberBase::LOCKUP_NORMAL); + SaberBase::DoBeginLockup(); + return true; + } + break; + // Release AUX to stop healing (or wait until full). + case EVENTID(BUTTON_AUX, EVENT_RELEASED, MODE_ANY_BUTTON | MODE_ON): + case EVENTID(BUTTON_AUX, EVENT_RELEASED, MODE_ANY_BUTTON | MODE_OFF): + if (SaberBase::Lockup()) { + SaberBase::DoEndLockup(); + SaberBase::SetLockup(SaberBase::LOCKUP_NONE); + return true; + } + break; + + // Hold POWER to start recharging armor + case EVENTID(BUTTON_POWER, EVENT_HELD_MEDIUM, MODE_ON): + if (!SaberBase::Lockup()) { + SaberBase::SetLockup(SaberBase::LOCKUP_LIGHTNING_BLOCK); + SaberBase::DoBeginLockup(); + return true; + } + break; + // Release POWER to stop recharging armor (or wait until full). + case EVENTID(BUTTON_POWER, EVENT_RELEASED, MODE_ANY_BUTTON | MODE_ON): + case EVENTID(BUTTON_POWER, EVENT_RELEASED, MODE_ANY_BUTTON | MODE_OFF): + if (SaberBase::Lockup()) { + SaberBase::DoEndLockup(); + SaberBase::SetLockup(SaberBase::LOCKUP_NONE); + return true; + } + break; + + + +#ifdef BLADE_DETECT_PIN + case EVENTID(BUTTON_BLADE_DETECT, EVENT_LATCH_ON, MODE_ANY_BUTTON | MODE_ON): + case EVENTID(BUTTON_BLADE_DETECT, EVENT_LATCH_ON, MODE_ANY_BUTTON | MODE_OFF): + // Might need to do something cleaner, but let's try this for now. + blade_detected_ = true; + FindBladeAgain(); + SaberBase::DoBladeDetect(true); + return true; + + case EVENTID(BUTTON_BLADE_DETECT, EVENT_LATCH_OFF, MODE_ANY_BUTTON | MODE_ON): + case EVENTID(BUTTON_BLADE_DETECT, EVENT_LATCH_OFF, MODE_ANY_BUTTON | MODE_OFF): + // Might need to do something cleaner, but let's try this for now. + blade_detected_ = false; + FindBladeAgain(); + SaberBase::DoBladeDetect(false); + return true; +#endif + } + return false; + } + + + // Hev effects, auto fire is handled by begin/end lockup + void SB_Effect(EffectType effect, EffectLocation location) override { + switch (effect) { + default: return; + + // Damage Detection Voice Lines + // case EFFECT_MAJOR: + // hybrid_font.PlayCommon(&SFX_major); + // if (morphine_triggered_) { + // hybrid_font.PlayCommon(&SFX_morphine); + // morphine_triggered_ = false; + // } + // return; + + // case EFFECT_MINOR: + // hybrid_font.PlayCommon(&SFX_minor); + // return; + + // Random Hazard Sounds + case EFFECT_STUN: + hybrid_font.PlayCommon(&SFX_stun); + return; + + // Health Alert + case EFFECT_USER1: + if (dead_) return; // Don't queue health sounds if dead + SFX_health.SelectFloat(health_ / 100.0); + SOUNDQ->Play(&SFX_health); + return; + + // Death Sound + case EFFECT_EMPTY: + hybrid_font.PlayCommon(&SFX_death); + return; + + } + } +}; + +#endif + From 0bb235c98265b63c05ee6c2e00a84eb48f24acb8 Mon Sep 17 00:00:00 2001 From: jimsworld Date: Wed, 12 Mar 2025 10:54:04 +0000 Subject: [PATCH 02/37] Move hev.h to props directory --- props/hev.h | 417 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 417 insertions(+) create mode 100644 props/hev.h diff --git a/props/hev.h b/props/hev.h new file mode 100644 index 000000000..736ba7802 --- /dev/null +++ b/props/hev.h @@ -0,0 +1,417 @@ +#ifndef PROPS_HEV_H +#define PROPS_HEV_H +#define PROP_TYPE Hev + +// Half-Life Hazardous Environment Suit Prop +// +// How to use: +// Power button: +// Long-click: on/off +// double-click: sound track on/off +// triple-click: next preset +// AUX button: +// hold button to heal. +// single click: deactivate hazard +// double-click: armor readout +// triple-click: previous preset +// Clash will cause varying damage based on strength +// +// Damage and Hazards are only active when prop is on. + +#include "prop_base.h" +#include + +EFFECT(health); +EFFECT(armor); +EFFECT(stun); +EFFECT(death); +EFFECT(armor_alarm); +EFFECT(armor_depleted); +EFFECT(major); +EFFECT(minor); +EFFECT(morphine); + +class Hev : public PROP_INHERIT_PREFIX PropBase { +public: + Hev() : PropBase() {} + const char* name() override { return "Hev"; } + + int health_ = 100; + int armor_ = 100; + + bool dead_ = false; + bool armor_depleted_ = false; + bool morphine_triggered_ = false; + + enum Hazard { + HAZARD_NONE = 0, + HAZARD_BIO = 1, + HAZARD_RAD = 2, + HAZARD_BLO = 3, + HAZARD_CHE = 4, + HAZARD_HEA = 5, + HAZARD_SHO = 6, + }; + + Hazard current_hazard_ = HAZARD_NONE; + + void LowBatteryOff() override { + if (SFX_poweron) { + PropBase::LowBatteryOff(); + } + } + + + // Calculate Physical Damage ////////////////////////////////////////////////// + void DoDamage(int damage, bool quiet = false) { + int tens = health_ / 10; + if (armor_ >= damage) { + armor_ -= ceilf(damage * 0.80 / 2); // Applies 80% divided by 2 of damage to armor, if armor is enough. + health_ -= ceilf(damage * 0.20); // Applies 20% of damage to health, if armor is enough. + } else { + int excess_physical = damage - armor_; + health_ -= excess_physical; // Only the excess of physical damage impacts health. + armor_ = 0; // Armor is depleted. + } + + // Enforce minimum values + if (health_ < 0) health_ = 0; + if (armor_ < 0) armor_ = 0; + + // Damage + if (!quiet) SaberBase::DoEffect(EFFECT_STUN, 0.0, damage); + + // Death Sound + if (health_ == 0 && !dead_) { // Check flag to avoid death sound spam + SaberBase::DoEffect(EFFECT_EMPTY, 0.0, 100); + dead_ = true; + return; + } + + // Reset flag if health is above 0 + if (health_ > 0) { + dead_ = false; + } + + // Health Alert - only plays when health enters a new multiple of 10 + int new_tens = health_ / 10; + if (tens != new_tens) { + SaberBase::DoEffect(EFFECT_USER1, 0.0, 0); + } + + // Print health and armor + PVLOG_NORMAL << "Health: " << health_ << " / "; + PVLOG_NORMAL << "Armor: " << armor_ << "\n"; + } + + + // Armor Readout ///////////////////////////////////////////////////////////// + void armor_readout() { + PVLOG_NORMAL << "Current Armor: " << armor_ << "\n"; // Debug logging + SFX_armor.SelectFloat(armor_ / 100.0); + hybrid_font.PlayCommon(&SFX_armor); + } + + + // Clashes /////////////////////////////////////////////////////////////////// + uint32_t last_clash_time_ = 0; + const uint32_t CLASH_DEBOUNCE_MS = 100; // Adjust debounce time as needed + + void Clash(bool stab, float strength) override { + // Ignore Clash if within debounce period + uint32_t current_time = millis(); + if (current_time - last_clash_time_ < CLASH_DEBOUNCE_MS) { + return; + } + last_clash_time_ = current_time; + + // Stop Clashes if health is 0 + if (health_ == 0) { + return; + } + + // Strength multiplier + int damage = (int)(strength * 4); + + // Play Clash sounds based on strength + float v = (strength - GetCurrentClashThreshold()) / 3; + SFX_clash.SelectFloat(v); + SFX_clsh.SelectFloat(v); + SFX_stab.SelectFloat(v); + + // Cap damage at 50 + if (damage > 50) { + damage = 50; + } + + // Play Major or Minor Detected Voice Lines + // if (damage >= 25) { + // morphine_triggered_ = true; + // SaberBase::DoEffect(EFFECT_MAJOR, 0.0, damage); + // } else { + // SaberBase::DoEffect(EFFECT_MINOR, 0.0, damage); + // } + + // Play Armor Alarm if damage is 30 or more + if (damage >= 30) { + hybrid_font.PlayPolyphonic(&SFX_armor_alarm); + } + + // Apply Physical Damage and print to log + DoDamage(damage, true); + PVLOG_NORMAL << "Physical Damage: -" << damage << "\n"; + + // No stabs! + PropBase::Clash(false, strength); + } + + + // Swings do nothing! //////////////////////////////////////////////////// + void DoMotion(const Vec3& motion, bool clear) override { + PropBase::DoMotion(Vec3(0), clear); + } + + + // Random Hazards //////////////////////////////////////////////////////////// + uint32_t last_random_draw_ = millis(); + + // Activate a Random Hazard, only if health is above 0 and no Hazard is currently active. + void CheckRandomEvent() { + if (millis() - last_random_draw_ > HEV_RANDOM_EVENT_INTERVAL_MS && health_ > 0 && current_hazard_ == HAZARD_NONE) { + last_random_draw_ = millis(); + if (random(100) < HEV_RANDOM_HAZARD_CHANCE) { + PVLOG_NORMAL << "Activating hazard.\n"; + current_hazard_ = (Hazard)(1 + random(6)); + hazard_decrease_millis_ = millis(); + SaberBase::DoEffect(EFFECT_ALT_SOUND, 0.0, current_hazard_); + } + } + } + + // Decrease armor (if any) or health over time. + uint32_t hazard_decrease_millis_ = millis(); + void HazardDecrease() { + if (current_hazard_ != HAZARD_NONE) { + if (millis() - hazard_decrease_millis_ > HEV_HAZARD_DECREASE_MS) { + hazard_decrease_millis_ = millis(); + + // Play stun sounds if there is armor + if (armor_ > 0) { + armor_--; + SaberBase::DoEffect(EFFECT_STUN, 0.0, 1); + } else { + DoDamage(1); + } + + // Stop the current Hazard if health drops to 0 + if (health_ == 0) { + current_hazard_ = HAZARD_NONE; + SaberBase::DoEffect(EFFECT_ALT_SOUND, 0.0, current_hazard_); + + // Print health and armor + PVLOG_NORMAL << "Health: " << health_ << " / "; + PVLOG_NORMAL << "Armor: " << armor_ << "\n"; + } + } + } + } + + + // Healing /////////////////////////////////////////////////////////////////// + // Increase health (if healing). + uint32_t health_increase_millis_ = millis(); + void IncreaseHealth() { + if (SaberBase::Lockup()) { + if (millis() - health_increase_millis_ > HEV_HEALTH_INCREASE_MS) { + health_increase_millis_ = millis(); + health_++; + PVLOG_NORMAL << "Health: " << health_ << "\n"; + if (health_ >= 100) { + health_ = 100; + SaberBase::DoEndLockup(); + SaberBase::SetLockup(SaberBase::LOCKUP_NONE); + } + } + } + } + + + // Recharging //////////////////////////////////////////////////////////////// + // Increase armor (if recharging). + uint32_t armor_increase_millis_ = millis(); + void IncreaseArmor() { + if (SaberBase::Lockup()) { + if (millis() - armor_increase_millis_ > HEV_ARMOR_INCREASE_MS) { + armor_increase_millis_ = millis(); + armor_++; + PVLOG_NORMAL << "Armor: " << armor_ << "\n"; + if (armor_ >= 100) { + armor_ = 100; + SaberBase::DoEndLockup(); + SaberBase::SetLockup(SaberBase::LOCKUP_NONE); + } + } + } + } + + + // Main Loop ////////////////////////////////////////////////////////////////// + void Loop() override { + CheckRandomEvent(); + HazardDecrease(); + if (SaberBase::Lockup() == SaberBase::LOCKUP_NORMAL) { + IncreaseHealth(); + } else if (SaberBase::Lockup() == SaberBase::LOCKUP_LIGHTNING_BLOCK) { + IncreaseArmor(); + } + PropBase::Loop(); + } + + + // Button Events ///////////////////////////////////////////////////////////// + bool Event2(enum BUTTON button, EVENT event, uint32_t modifiers) override { + switch (EVENTID(button, event, modifiers)) { + + // On/Off long-click + case EVENTID(BUTTON_POWER, EVENT_FIRST_CLICK_LONG, MODE_OFF): + On(); + return true; + case EVENTID(BUTTON_POWER, EVENT_FIRST_CLICK_LONG, MODE_ON): + Off(); + return true; + + // short-click AUX to clear hazard + case EVENTID(BUTTON_AUX, EVENT_FIRST_SAVED_CLICK_SHORT, MODE_ANY_BUTTON | MODE_ON): + case EVENTID(BUTTON_AUX, EVENT_FIRST_SAVED_CLICK_SHORT, MODE_ANY_BUTTON | MODE_OFF): + if (current_hazard_) { + current_hazard_ = HAZARD_NONE; + SaberBase::DoEffect(EFFECT_ALT_SOUND, 0.0, current_hazard_); + return true; + } + // Play a no-hazard sound ? + return true; + + // Double-click power to start/stop track. + case EVENTID(BUTTON_POWER, EVENT_SECOND_SAVED_CLICK_SHORT, MODE_ANY_BUTTON | MODE_ON): + case EVENTID(BUTTON_POWER, EVENT_SECOND_SAVED_CLICK_SHORT, MODE_ANY_BUTTON | MODE_OFF): + StartOrStopTrack(); + return true; + + // Double-click AUX for Armor Readout. + case EVENTID(BUTTON_AUX, EVENT_SECOND_SAVED_CLICK_SHORT, MODE_ANY_BUTTON | MODE_ON): + case EVENTID(BUTTON_AUX, EVENT_SECOND_SAVED_CLICK_SHORT, MODE_ANY_BUTTON | MODE_OFF): + armor_readout(); + return true; + + // Next/Previous preset. Triple-click on either button. + case EVENTID(BUTTON_POWER, EVENT_THIRD_SAVED_CLICK_SHORT, MODE_ANY_BUTTON | MODE_ON): + case EVENTID(BUTTON_POWER, EVENT_THIRD_SAVED_CLICK_SHORT, MODE_ANY_BUTTON | MODE_OFF): + next_preset(); + return true; + case EVENTID(BUTTON_AUX, EVENT_THIRD_SAVED_CLICK_SHORT, MODE_ANY_BUTTON | MODE_ON): + case EVENTID(BUTTON_AUX, EVENT_THIRD_SAVED_CLICK_SHORT, MODE_ANY_BUTTON | MODE_OFF): + previous_preset(); + return true; + + + // Hold AUX to start healing + case EVENTID(BUTTON_AUX, EVENT_HELD_MEDIUM, MODE_ON): + if (!SaberBase::Lockup()) { + SaberBase::SetLockup(SaberBase::LOCKUP_NORMAL); + SaberBase::DoBeginLockup(); + return true; + } + break; + // Release AUX to stop healing (or wait until full). + case EVENTID(BUTTON_AUX, EVENT_RELEASED, MODE_ANY_BUTTON | MODE_ON): + case EVENTID(BUTTON_AUX, EVENT_RELEASED, MODE_ANY_BUTTON | MODE_OFF): + if (SaberBase::Lockup()) { + SaberBase::DoEndLockup(); + SaberBase::SetLockup(SaberBase::LOCKUP_NONE); + return true; + } + break; + + // Hold POWER to start recharging armor + case EVENTID(BUTTON_POWER, EVENT_HELD_MEDIUM, MODE_ON): + if (!SaberBase::Lockup()) { + SaberBase::SetLockup(SaberBase::LOCKUP_LIGHTNING_BLOCK); + SaberBase::DoBeginLockup(); + return true; + } + break; + // Release POWER to stop recharging armor (or wait until full). + case EVENTID(BUTTON_POWER, EVENT_RELEASED, MODE_ANY_BUTTON | MODE_ON): + case EVENTID(BUTTON_POWER, EVENT_RELEASED, MODE_ANY_BUTTON | MODE_OFF): + if (SaberBase::Lockup()) { + SaberBase::DoEndLockup(); + SaberBase::SetLockup(SaberBase::LOCKUP_NONE); + return true; + } + break; + + + +#ifdef BLADE_DETECT_PIN + case EVENTID(BUTTON_BLADE_DETECT, EVENT_LATCH_ON, MODE_ANY_BUTTON | MODE_ON): + case EVENTID(BUTTON_BLADE_DETECT, EVENT_LATCH_ON, MODE_ANY_BUTTON | MODE_OFF): + // Might need to do something cleaner, but let's try this for now. + blade_detected_ = true; + FindBladeAgain(); + SaberBase::DoBladeDetect(true); + return true; + + case EVENTID(BUTTON_BLADE_DETECT, EVENT_LATCH_OFF, MODE_ANY_BUTTON | MODE_ON): + case EVENTID(BUTTON_BLADE_DETECT, EVENT_LATCH_OFF, MODE_ANY_BUTTON | MODE_OFF): + // Might need to do something cleaner, but let's try this for now. + blade_detected_ = false; + FindBladeAgain(); + SaberBase::DoBladeDetect(false); + return true; +#endif + } + return false; + } + + + // Hev effects, auto fire is handled by begin/end lockup + void SB_Effect(EffectType effect, EffectLocation location) override { + switch (effect) { + default: return; + + // Damage Detection Voice Lines + // case EFFECT_MAJOR: + // hybrid_font.PlayCommon(&SFX_major); + // if (morphine_triggered_) { + // hybrid_font.PlayCommon(&SFX_morphine); + // morphine_triggered_ = false; + // } + // return; + + // case EFFECT_MINOR: + // hybrid_font.PlayCommon(&SFX_minor); + // return; + + // Random Hazard Sounds + case EFFECT_STUN: + hybrid_font.PlayCommon(&SFX_stun); + return; + + // Health Alert + case EFFECT_USER1: + if (dead_) return; // Don't queue health sounds if dead + SFX_health.SelectFloat(health_ / 100.0); + SOUNDQ->Play(&SFX_health); + return; + + // Death Sound + case EFFECT_EMPTY: + hybrid_font.PlayCommon(&SFX_death); + return; + + } + } +}; + +#endif + From f420319d254f6d313792f47d13b591195e2890d0 Mon Sep 17 00:00:00 2001 From: jimsworld Date: Wed, 12 Mar 2025 10:54:53 +0000 Subject: [PATCH 03/37] Remove hev.h from root directory --- hev.h | 417 ---------------------------------------------------------- 1 file changed, 417 deletions(-) delete mode 100644 hev.h diff --git a/hev.h b/hev.h deleted file mode 100644 index 736ba7802..000000000 --- a/hev.h +++ /dev/null @@ -1,417 +0,0 @@ -#ifndef PROPS_HEV_H -#define PROPS_HEV_H -#define PROP_TYPE Hev - -// Half-Life Hazardous Environment Suit Prop -// -// How to use: -// Power button: -// Long-click: on/off -// double-click: sound track on/off -// triple-click: next preset -// AUX button: -// hold button to heal. -// single click: deactivate hazard -// double-click: armor readout -// triple-click: previous preset -// Clash will cause varying damage based on strength -// -// Damage and Hazards are only active when prop is on. - -#include "prop_base.h" -#include - -EFFECT(health); -EFFECT(armor); -EFFECT(stun); -EFFECT(death); -EFFECT(armor_alarm); -EFFECT(armor_depleted); -EFFECT(major); -EFFECT(minor); -EFFECT(morphine); - -class Hev : public PROP_INHERIT_PREFIX PropBase { -public: - Hev() : PropBase() {} - const char* name() override { return "Hev"; } - - int health_ = 100; - int armor_ = 100; - - bool dead_ = false; - bool armor_depleted_ = false; - bool morphine_triggered_ = false; - - enum Hazard { - HAZARD_NONE = 0, - HAZARD_BIO = 1, - HAZARD_RAD = 2, - HAZARD_BLO = 3, - HAZARD_CHE = 4, - HAZARD_HEA = 5, - HAZARD_SHO = 6, - }; - - Hazard current_hazard_ = HAZARD_NONE; - - void LowBatteryOff() override { - if (SFX_poweron) { - PropBase::LowBatteryOff(); - } - } - - - // Calculate Physical Damage ////////////////////////////////////////////////// - void DoDamage(int damage, bool quiet = false) { - int tens = health_ / 10; - if (armor_ >= damage) { - armor_ -= ceilf(damage * 0.80 / 2); // Applies 80% divided by 2 of damage to armor, if armor is enough. - health_ -= ceilf(damage * 0.20); // Applies 20% of damage to health, if armor is enough. - } else { - int excess_physical = damage - armor_; - health_ -= excess_physical; // Only the excess of physical damage impacts health. - armor_ = 0; // Armor is depleted. - } - - // Enforce minimum values - if (health_ < 0) health_ = 0; - if (armor_ < 0) armor_ = 0; - - // Damage - if (!quiet) SaberBase::DoEffect(EFFECT_STUN, 0.0, damage); - - // Death Sound - if (health_ == 0 && !dead_) { // Check flag to avoid death sound spam - SaberBase::DoEffect(EFFECT_EMPTY, 0.0, 100); - dead_ = true; - return; - } - - // Reset flag if health is above 0 - if (health_ > 0) { - dead_ = false; - } - - // Health Alert - only plays when health enters a new multiple of 10 - int new_tens = health_ / 10; - if (tens != new_tens) { - SaberBase::DoEffect(EFFECT_USER1, 0.0, 0); - } - - // Print health and armor - PVLOG_NORMAL << "Health: " << health_ << " / "; - PVLOG_NORMAL << "Armor: " << armor_ << "\n"; - } - - - // Armor Readout ///////////////////////////////////////////////////////////// - void armor_readout() { - PVLOG_NORMAL << "Current Armor: " << armor_ << "\n"; // Debug logging - SFX_armor.SelectFloat(armor_ / 100.0); - hybrid_font.PlayCommon(&SFX_armor); - } - - - // Clashes /////////////////////////////////////////////////////////////////// - uint32_t last_clash_time_ = 0; - const uint32_t CLASH_DEBOUNCE_MS = 100; // Adjust debounce time as needed - - void Clash(bool stab, float strength) override { - // Ignore Clash if within debounce period - uint32_t current_time = millis(); - if (current_time - last_clash_time_ < CLASH_DEBOUNCE_MS) { - return; - } - last_clash_time_ = current_time; - - // Stop Clashes if health is 0 - if (health_ == 0) { - return; - } - - // Strength multiplier - int damage = (int)(strength * 4); - - // Play Clash sounds based on strength - float v = (strength - GetCurrentClashThreshold()) / 3; - SFX_clash.SelectFloat(v); - SFX_clsh.SelectFloat(v); - SFX_stab.SelectFloat(v); - - // Cap damage at 50 - if (damage > 50) { - damage = 50; - } - - // Play Major or Minor Detected Voice Lines - // if (damage >= 25) { - // morphine_triggered_ = true; - // SaberBase::DoEffect(EFFECT_MAJOR, 0.0, damage); - // } else { - // SaberBase::DoEffect(EFFECT_MINOR, 0.0, damage); - // } - - // Play Armor Alarm if damage is 30 or more - if (damage >= 30) { - hybrid_font.PlayPolyphonic(&SFX_armor_alarm); - } - - // Apply Physical Damage and print to log - DoDamage(damage, true); - PVLOG_NORMAL << "Physical Damage: -" << damage << "\n"; - - // No stabs! - PropBase::Clash(false, strength); - } - - - // Swings do nothing! //////////////////////////////////////////////////// - void DoMotion(const Vec3& motion, bool clear) override { - PropBase::DoMotion(Vec3(0), clear); - } - - - // Random Hazards //////////////////////////////////////////////////////////// - uint32_t last_random_draw_ = millis(); - - // Activate a Random Hazard, only if health is above 0 and no Hazard is currently active. - void CheckRandomEvent() { - if (millis() - last_random_draw_ > HEV_RANDOM_EVENT_INTERVAL_MS && health_ > 0 && current_hazard_ == HAZARD_NONE) { - last_random_draw_ = millis(); - if (random(100) < HEV_RANDOM_HAZARD_CHANCE) { - PVLOG_NORMAL << "Activating hazard.\n"; - current_hazard_ = (Hazard)(1 + random(6)); - hazard_decrease_millis_ = millis(); - SaberBase::DoEffect(EFFECT_ALT_SOUND, 0.0, current_hazard_); - } - } - } - - // Decrease armor (if any) or health over time. - uint32_t hazard_decrease_millis_ = millis(); - void HazardDecrease() { - if (current_hazard_ != HAZARD_NONE) { - if (millis() - hazard_decrease_millis_ > HEV_HAZARD_DECREASE_MS) { - hazard_decrease_millis_ = millis(); - - // Play stun sounds if there is armor - if (armor_ > 0) { - armor_--; - SaberBase::DoEffect(EFFECT_STUN, 0.0, 1); - } else { - DoDamage(1); - } - - // Stop the current Hazard if health drops to 0 - if (health_ == 0) { - current_hazard_ = HAZARD_NONE; - SaberBase::DoEffect(EFFECT_ALT_SOUND, 0.0, current_hazard_); - - // Print health and armor - PVLOG_NORMAL << "Health: " << health_ << " / "; - PVLOG_NORMAL << "Armor: " << armor_ << "\n"; - } - } - } - } - - - // Healing /////////////////////////////////////////////////////////////////// - // Increase health (if healing). - uint32_t health_increase_millis_ = millis(); - void IncreaseHealth() { - if (SaberBase::Lockup()) { - if (millis() - health_increase_millis_ > HEV_HEALTH_INCREASE_MS) { - health_increase_millis_ = millis(); - health_++; - PVLOG_NORMAL << "Health: " << health_ << "\n"; - if (health_ >= 100) { - health_ = 100; - SaberBase::DoEndLockup(); - SaberBase::SetLockup(SaberBase::LOCKUP_NONE); - } - } - } - } - - - // Recharging //////////////////////////////////////////////////////////////// - // Increase armor (if recharging). - uint32_t armor_increase_millis_ = millis(); - void IncreaseArmor() { - if (SaberBase::Lockup()) { - if (millis() - armor_increase_millis_ > HEV_ARMOR_INCREASE_MS) { - armor_increase_millis_ = millis(); - armor_++; - PVLOG_NORMAL << "Armor: " << armor_ << "\n"; - if (armor_ >= 100) { - armor_ = 100; - SaberBase::DoEndLockup(); - SaberBase::SetLockup(SaberBase::LOCKUP_NONE); - } - } - } - } - - - // Main Loop ////////////////////////////////////////////////////////////////// - void Loop() override { - CheckRandomEvent(); - HazardDecrease(); - if (SaberBase::Lockup() == SaberBase::LOCKUP_NORMAL) { - IncreaseHealth(); - } else if (SaberBase::Lockup() == SaberBase::LOCKUP_LIGHTNING_BLOCK) { - IncreaseArmor(); - } - PropBase::Loop(); - } - - - // Button Events ///////////////////////////////////////////////////////////// - bool Event2(enum BUTTON button, EVENT event, uint32_t modifiers) override { - switch (EVENTID(button, event, modifiers)) { - - // On/Off long-click - case EVENTID(BUTTON_POWER, EVENT_FIRST_CLICK_LONG, MODE_OFF): - On(); - return true; - case EVENTID(BUTTON_POWER, EVENT_FIRST_CLICK_LONG, MODE_ON): - Off(); - return true; - - // short-click AUX to clear hazard - case EVENTID(BUTTON_AUX, EVENT_FIRST_SAVED_CLICK_SHORT, MODE_ANY_BUTTON | MODE_ON): - case EVENTID(BUTTON_AUX, EVENT_FIRST_SAVED_CLICK_SHORT, MODE_ANY_BUTTON | MODE_OFF): - if (current_hazard_) { - current_hazard_ = HAZARD_NONE; - SaberBase::DoEffect(EFFECT_ALT_SOUND, 0.0, current_hazard_); - return true; - } - // Play a no-hazard sound ? - return true; - - // Double-click power to start/stop track. - case EVENTID(BUTTON_POWER, EVENT_SECOND_SAVED_CLICK_SHORT, MODE_ANY_BUTTON | MODE_ON): - case EVENTID(BUTTON_POWER, EVENT_SECOND_SAVED_CLICK_SHORT, MODE_ANY_BUTTON | MODE_OFF): - StartOrStopTrack(); - return true; - - // Double-click AUX for Armor Readout. - case EVENTID(BUTTON_AUX, EVENT_SECOND_SAVED_CLICK_SHORT, MODE_ANY_BUTTON | MODE_ON): - case EVENTID(BUTTON_AUX, EVENT_SECOND_SAVED_CLICK_SHORT, MODE_ANY_BUTTON | MODE_OFF): - armor_readout(); - return true; - - // Next/Previous preset. Triple-click on either button. - case EVENTID(BUTTON_POWER, EVENT_THIRD_SAVED_CLICK_SHORT, MODE_ANY_BUTTON | MODE_ON): - case EVENTID(BUTTON_POWER, EVENT_THIRD_SAVED_CLICK_SHORT, MODE_ANY_BUTTON | MODE_OFF): - next_preset(); - return true; - case EVENTID(BUTTON_AUX, EVENT_THIRD_SAVED_CLICK_SHORT, MODE_ANY_BUTTON | MODE_ON): - case EVENTID(BUTTON_AUX, EVENT_THIRD_SAVED_CLICK_SHORT, MODE_ANY_BUTTON | MODE_OFF): - previous_preset(); - return true; - - - // Hold AUX to start healing - case EVENTID(BUTTON_AUX, EVENT_HELD_MEDIUM, MODE_ON): - if (!SaberBase::Lockup()) { - SaberBase::SetLockup(SaberBase::LOCKUP_NORMAL); - SaberBase::DoBeginLockup(); - return true; - } - break; - // Release AUX to stop healing (or wait until full). - case EVENTID(BUTTON_AUX, EVENT_RELEASED, MODE_ANY_BUTTON | MODE_ON): - case EVENTID(BUTTON_AUX, EVENT_RELEASED, MODE_ANY_BUTTON | MODE_OFF): - if (SaberBase::Lockup()) { - SaberBase::DoEndLockup(); - SaberBase::SetLockup(SaberBase::LOCKUP_NONE); - return true; - } - break; - - // Hold POWER to start recharging armor - case EVENTID(BUTTON_POWER, EVENT_HELD_MEDIUM, MODE_ON): - if (!SaberBase::Lockup()) { - SaberBase::SetLockup(SaberBase::LOCKUP_LIGHTNING_BLOCK); - SaberBase::DoBeginLockup(); - return true; - } - break; - // Release POWER to stop recharging armor (or wait until full). - case EVENTID(BUTTON_POWER, EVENT_RELEASED, MODE_ANY_BUTTON | MODE_ON): - case EVENTID(BUTTON_POWER, EVENT_RELEASED, MODE_ANY_BUTTON | MODE_OFF): - if (SaberBase::Lockup()) { - SaberBase::DoEndLockup(); - SaberBase::SetLockup(SaberBase::LOCKUP_NONE); - return true; - } - break; - - - -#ifdef BLADE_DETECT_PIN - case EVENTID(BUTTON_BLADE_DETECT, EVENT_LATCH_ON, MODE_ANY_BUTTON | MODE_ON): - case EVENTID(BUTTON_BLADE_DETECT, EVENT_LATCH_ON, MODE_ANY_BUTTON | MODE_OFF): - // Might need to do something cleaner, but let's try this for now. - blade_detected_ = true; - FindBladeAgain(); - SaberBase::DoBladeDetect(true); - return true; - - case EVENTID(BUTTON_BLADE_DETECT, EVENT_LATCH_OFF, MODE_ANY_BUTTON | MODE_ON): - case EVENTID(BUTTON_BLADE_DETECT, EVENT_LATCH_OFF, MODE_ANY_BUTTON | MODE_OFF): - // Might need to do something cleaner, but let's try this for now. - blade_detected_ = false; - FindBladeAgain(); - SaberBase::DoBladeDetect(false); - return true; -#endif - } - return false; - } - - - // Hev effects, auto fire is handled by begin/end lockup - void SB_Effect(EffectType effect, EffectLocation location) override { - switch (effect) { - default: return; - - // Damage Detection Voice Lines - // case EFFECT_MAJOR: - // hybrid_font.PlayCommon(&SFX_major); - // if (morphine_triggered_) { - // hybrid_font.PlayCommon(&SFX_morphine); - // morphine_triggered_ = false; - // } - // return; - - // case EFFECT_MINOR: - // hybrid_font.PlayCommon(&SFX_minor); - // return; - - // Random Hazard Sounds - case EFFECT_STUN: - hybrid_font.PlayCommon(&SFX_stun); - return; - - // Health Alert - case EFFECT_USER1: - if (dead_) return; // Don't queue health sounds if dead - SFX_health.SelectFloat(health_ / 100.0); - SOUNDQ->Play(&SFX_health); - return; - - // Death Sound - case EFFECT_EMPTY: - hybrid_font.PlayCommon(&SFX_death); - return; - - } - } -}; - -#endif - From a8964f927ad2af358a4f8b8702f3d08545f21e69 Mon Sep 17 00:00:00 2001 From: jimsworld Date: Wed, 26 Mar 2025 14:28:19 +0000 Subject: [PATCH 04/37] Uploading hev_config.h --- config/hev_config.h | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 config/hev_config.h diff --git a/config/hev_config.h b/config/hev_config.h new file mode 100644 index 000000000..d3028b608 --- /dev/null +++ b/config/hev_config.h @@ -0,0 +1,43 @@ +#ifdef CONFIG_TOP +#include "proffieboard_v2_config.h" +#define NUM_BLADES 1 +#define NUM_BUTTONS 2 +#define VOLUME 30 +const unsigned int maxLedsPerStrip = 144; +#define CLASH_THRESHOLD_G 3.0 +#define ENABLE_AUDIO +#define ENABLE_MOTION +#define ENABLE_WS2811 +#define ENABLE_SD +#define DISABLE_NO_REPEAT_RANDOM +#define HEV_POST_DEATH_COOLDOWN_MS 30000 +#define HEV_RANDOM_EVENT_INTERVAL_MS 30000 +#define HEV_RANDOM_HAZARD_CHANCE 50 +#define HEV_HAZARD_DELAY_MS 6000 +#define HEV_HAZARD_DECREASE_MS 1000 +#define HEV_HEALTH_INCREASE_MS 100 +#define HEV_ARMOR_INCREASE_MS 100 +#endif + +#ifdef CONFIG_PROP +#include "../props/hev.h" +#endif + +#ifdef CONFIG_PRESETS +Preset presets[] = { + { "_hev;common_hev", "common_hev/tracks/hl1_ost/10 Valve Theme [Extended].wav", + StyleNormalPtr(), + } +}; + +BladeConfig blades[] = { + { 10000, WS2811BladePtr<125, WS2811_ACTUALLY_800kHz | WS2811_GRB>(), + CONFIGARRAY(presets) }, +}; + +#endif + +#ifdef CONFIG_BUTTONS +Button PowerButton(BUTTON_POWER, powerButtonPin, "pow"); +Button AuxButton(BUTTON_AUX, auxPin, "aux"); +#endif From 1c653b8b84ceabe3fdab8b7e3e817d9f96d38524 Mon Sep 17 00:00:00 2001 From: jimsworld Date: Thu, 27 Mar 2025 14:26:24 +0000 Subject: [PATCH 05/37] Updated hev.h accounting changes from old branch --- props/hev.h | 149 +++++++++++++++++++++++++++------------------------- 1 file changed, 77 insertions(+), 72 deletions(-) diff --git a/props/hev.h b/props/hev.h index 736ba7802..11b02654a 100644 --- a/props/hev.h +++ b/props/hev.h @@ -26,7 +26,7 @@ EFFECT(armor); EFFECT(stun); EFFECT(death); EFFECT(armor_alarm); -EFFECT(armor_depleted); +EFFECT(armor_compromised); EFFECT(major); EFFECT(minor); EFFECT(morphine); @@ -39,10 +39,6 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { int health_ = 100; int armor_ = 100; - bool dead_ = false; - bool armor_depleted_ = false; - bool morphine_triggered_ = false; - enum Hazard { HAZARD_NONE = 0, HAZARD_BIO = 1, @@ -66,12 +62,17 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { void DoDamage(int damage, bool quiet = false) { int tens = health_ / 10; if (armor_ >= damage) { - armor_ -= ceilf(damage * 0.80 / 2); // Applies 80% divided by 2 of damage to armor, if armor is enough. - health_ -= ceilf(damage * 0.20); // Applies 20% of damage to health, if armor is enough. + armor_ -= ceilf(damage * 0.80 / 2); + health_ -= ceilf(damage * 0.20); + } else if (armor_ > 0) { + int excess_physical = damage - armor_; + health_ -= excess_physical; + // Make armor compromised sound play before setting armor to 0 + SaberBase::DoEffect(EFFECT_USER2, 0.0, 0); + PVLOG_NORMAL << "Armor Compromised!\n"; + armor_ = 0; } else { - int excess_physical = damage - armor_; - health_ -= excess_physical; // Only the excess of physical damage impacts health. - armor_ = 0; // Armor is depleted. + health_ -= damage; } // Enforce minimum values @@ -82,17 +83,11 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { if (!quiet) SaberBase::DoEffect(EFFECT_STUN, 0.0, damage); // Death Sound - if (health_ == 0 && !dead_) { // Check flag to avoid death sound spam + if (health_ == 0) { SaberBase::DoEffect(EFFECT_EMPTY, 0.0, 100); - dead_ = true; return; } - - // Reset flag if health is above 0 - if (health_ > 0) { - dead_ = false; - } - + // Health Alert - only plays when health enters a new multiple of 10 int new_tens = health_ / 10; if (tens != new_tens) { @@ -144,14 +139,6 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { damage = 50; } - // Play Major or Minor Detected Voice Lines - // if (damage >= 25) { - // morphine_triggered_ = true; - // SaberBase::DoEffect(EFFECT_MAJOR, 0.0, damage); - // } else { - // SaberBase::DoEffect(EFFECT_MINOR, 0.0, damage); - // } - // Play Armor Alarm if damage is 30 or more if (damage >= 30) { hybrid_font.PlayPolyphonic(&SFX_armor_alarm); @@ -174,16 +161,27 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { // Random Hazards //////////////////////////////////////////////////////////// uint32_t last_random_draw_ = millis(); + uint32_t post_death_cooldown_ = 0; - // Activate a Random Hazard, only if health is above 0 and no Hazard is currently active. + // Check and activation void CheckRandomEvent() { - if (millis() - last_random_draw_ > HEV_RANDOM_EVENT_INTERVAL_MS && health_ > 0 && current_hazard_ == HAZARD_NONE) { + // Don't allow Hazards if dead or during post-death cooldown. + // This prevents Hazards immediately after healing. + if (health_ == 0 || (post_death_cooldown_ && + (millis() - post_death_cooldown_ < HEV_POST_DEATH_COOLDOWN_MS))) { + return; + } + + // Activate a Hazard, only if health is above 0 and no Hazard is currently active. + if (millis() - last_random_draw_ > HEV_RANDOM_EVENT_INTERVAL_MS && + health_ > 0 && + current_hazard_ == HAZARD_NONE) { last_random_draw_ = millis(); if (random(100) < HEV_RANDOM_HAZARD_CHANCE) { - PVLOG_NORMAL << "Activating hazard.\n"; - current_hazard_ = (Hazard)(1 + random(6)); - hazard_decrease_millis_ = millis(); - SaberBase::DoEffect(EFFECT_ALT_SOUND, 0.0, current_hazard_); + PVLOG_NORMAL << "Activating hazard.\n"; + current_hazard_ = (Hazard)(1 + random(6)); + hazard_decrease_millis_ = millis(); + SaberBase::DoEffect(EFFECT_ALT_SOUND, 0.0, current_hazard_); } } } @@ -199,6 +197,11 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { if (armor_ > 0) { armor_--; SaberBase::DoEffect(EFFECT_STUN, 0.0, 1); + // Check if armor is 0 then play armor compromised sound. + if (armor_ == 0) { + SaberBase::DoEffect(EFFECT_USER2, 0.0, 0); + PVLOG_NORMAL << "Armor Compromised!\n"; + } } else { DoDamage(1); } @@ -207,10 +210,6 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { if (health_ == 0) { current_hazard_ = HAZARD_NONE; SaberBase::DoEffect(EFFECT_ALT_SOUND, 0.0, current_hazard_); - - // Print health and armor - PVLOG_NORMAL << "Health: " << health_ << " / "; - PVLOG_NORMAL << "Armor: " << armor_ << "\n"; } } } @@ -224,6 +223,13 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { if (SaberBase::Lockup()) { if (millis() - health_increase_millis_ > HEV_HEALTH_INCREASE_MS) { health_increase_millis_ = millis(); + + // Start cooldown timer when health increases above 0. + // This starts the check to prevent immediate Hazards after healing. + if (health_ == 0) { + post_death_cooldown_ = millis(); // Start cooldown timer + } + health_++; PVLOG_NORMAL << "Health: " << health_ << "\n"; if (health_ >= 100) { @@ -242,14 +248,16 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { void IncreaseArmor() { if (SaberBase::Lockup()) { if (millis() - armor_increase_millis_ > HEV_ARMOR_INCREASE_MS) { - armor_increase_millis_ = millis(); - armor_++; - PVLOG_NORMAL << "Armor: " << armor_ << "\n"; - if (armor_ >= 100) { - armor_ = 100; - SaberBase::DoEndLockup(); - SaberBase::SetLockup(SaberBase::LOCKUP_NONE); - } + armor_increase_millis_ = millis(); + + armor_++; + PVLOG_NORMAL << "Armor: " << armor_ << "\n"; + + if (armor_ >= 100) { + armor_ = 100; + SaberBase::DoEndLockup(); + SaberBase::SetLockup(SaberBase::LOCKUP_NONE); + } } } } @@ -303,7 +311,7 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { armor_readout(); return true; - // Next/Previous preset. Triple-click on either button. + // Next/Previous preset. Triple-click on either button. case EVENTID(BUTTON_POWER, EVENT_THIRD_SAVED_CLICK_SHORT, MODE_ANY_BUTTON | MODE_ON): case EVENTID(BUTTON_POWER, EVENT_THIRD_SAVED_CLICK_SHORT, MODE_ANY_BUTTON | MODE_OFF): next_preset(); @@ -376,39 +384,36 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { // Hev effects, auto fire is handled by begin/end lockup void SB_Effect(EffectType effect, EffectLocation location) override { + // Don't queue new sounds if dead (except death sound). + // Once dead, if a queued sound is currently playing, allow it to finish + // alongside death sound. However all pending sounds should be cleared. switch (effect) { default: return; - - // Damage Detection Voice Lines - // case EFFECT_MAJOR: - // hybrid_font.PlayCommon(&SFX_major); - // if (morphine_triggered_) { - // hybrid_font.PlayCommon(&SFX_morphine); - // morphine_triggered_ = false; - // } - // return; - - // case EFFECT_MINOR: - // hybrid_font.PlayCommon(&SFX_minor); - // return; - // Random Hazard Sounds + // Random Hazard Sounds case EFFECT_STUN: - hybrid_font.PlayCommon(&SFX_stun); - return; - - // Health Alert + hybrid_font.PlayCommon(&SFX_stun); + return; + + // Armor Compromised Sound + case EFFECT_USER2: + SOUNDQ->Play(&SFX_armor_compromised); + return; + + // Health Alert case EFFECT_USER1: - if (dead_) return; // Don't queue health sounds if dead - SFX_health.SelectFloat(health_ / 100.0); - SOUNDQ->Play(&SFX_health); - return; - - // Death Sound + if (health_ == 0) return; // Don't queue health sounds if dead + SFX_health.SelectFloat(health_ / 100.0); + SOUNDQ->Play(&SFX_health); + return; + + // Death Sound case EFFECT_EMPTY: - hybrid_font.PlayCommon(&SFX_death); - return; - + if (health_ == 0) { + SOUNDQ->clear_pending(); + } + hybrid_font.PlayCommon(&SFX_death); + return; } } }; From 27291d5aeaddac1b4cc38537cf2f0a900f97a4c3 Mon Sep 17 00:00:00 2001 From: jimsworld Date: Thu, 27 Mar 2025 14:29:32 +0000 Subject: [PATCH 06/37] Removed hev_config.h --- config/hev_config.h | 43 ------------------------------------------- 1 file changed, 43 deletions(-) delete mode 100644 config/hev_config.h diff --git a/config/hev_config.h b/config/hev_config.h deleted file mode 100644 index d3028b608..000000000 --- a/config/hev_config.h +++ /dev/null @@ -1,43 +0,0 @@ -#ifdef CONFIG_TOP -#include "proffieboard_v2_config.h" -#define NUM_BLADES 1 -#define NUM_BUTTONS 2 -#define VOLUME 30 -const unsigned int maxLedsPerStrip = 144; -#define CLASH_THRESHOLD_G 3.0 -#define ENABLE_AUDIO -#define ENABLE_MOTION -#define ENABLE_WS2811 -#define ENABLE_SD -#define DISABLE_NO_REPEAT_RANDOM -#define HEV_POST_DEATH_COOLDOWN_MS 30000 -#define HEV_RANDOM_EVENT_INTERVAL_MS 30000 -#define HEV_RANDOM_HAZARD_CHANCE 50 -#define HEV_HAZARD_DELAY_MS 6000 -#define HEV_HAZARD_DECREASE_MS 1000 -#define HEV_HEALTH_INCREASE_MS 100 -#define HEV_ARMOR_INCREASE_MS 100 -#endif - -#ifdef CONFIG_PROP -#include "../props/hev.h" -#endif - -#ifdef CONFIG_PRESETS -Preset presets[] = { - { "_hev;common_hev", "common_hev/tracks/hl1_ost/10 Valve Theme [Extended].wav", - StyleNormalPtr(), - } -}; - -BladeConfig blades[] = { - { 10000, WS2811BladePtr<125, WS2811_ACTUALLY_800kHz | WS2811_GRB>(), - CONFIGARRAY(presets) }, -}; - -#endif - -#ifdef CONFIG_BUTTONS -Button PowerButton(BUTTON_POWER, powerButtonPin, "pow"); -Button AuxButton(BUTTON_AUX, auxPin, "aux"); -#endif From e867fa3224eff3cd47b00353d7f043f10e8c4494 Mon Sep 17 00:00:00 2001 From: jimsworld Date: Fri, 28 Mar 2025 13:53:57 +0000 Subject: [PATCH 07/37] Delay Hazard Damage --- props/hev.h | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/props/hev.h b/props/hev.h index 11b02654a..4e4d2925e 100644 --- a/props/hev.h +++ b/props/hev.h @@ -181,15 +181,29 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { PVLOG_NORMAL << "Activating hazard.\n"; current_hazard_ = (Hazard)(1 + random(6)); hazard_decrease_millis_ = millis(); + hazard_start_delay_ = 0; SaberBase::DoEffect(EFFECT_ALT_SOUND, 0.0, current_hazard_); } } } // Decrease armor (if any) or health over time. + uint32_t hazard_start_delay_ = 0; uint32_t hazard_decrease_millis_ = millis(); void HazardDecrease() { if (current_hazard_ != HAZARD_NONE) { + // Check if this is a new hazard that needs initial delay + if (hazard_start_delay_ == 0) { + hazard_start_delay_ = millis(); + return; + } + + // Wait for initial delay before starting damage + if (millis() - hazard_start_delay_ < HEV_HAZARD_DELAY_MS) { + return; + } + + // Normal damage interval after initial delay if (millis() - hazard_decrease_millis_ > HEV_HAZARD_DECREASE_MS) { hazard_decrease_millis_ = millis(); @@ -209,9 +223,13 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { // Stop the current Hazard if health drops to 0 if (health_ == 0) { current_hazard_ = HAZARD_NONE; + hazard_start_delay_ = 0; // Reset delay for next hazard SaberBase::DoEffect(EFFECT_ALT_SOUND, 0.0, current_hazard_); } } + } else { + // Reset delay when no hazard is active + hazard_start_delay_ = 0; } } From 57ed29461bfa48d53bb0a940e3b10d12e86524a7 Mon Sep 17 00:00:00 2001 From: James Nash <148967915+jimsworld@users.noreply.github.com> Date: Tue, 1 Apr 2025 08:48:07 +0100 Subject: [PATCH 08/37] Update hev.h Removed unnecessary lowbattery function. --- props/hev.h | 7 ------- 1 file changed, 7 deletions(-) diff --git a/props/hev.h b/props/hev.h index 4e4d2925e..b655c24e6 100644 --- a/props/hev.h +++ b/props/hev.h @@ -50,13 +50,6 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { }; Hazard current_hazard_ = HAZARD_NONE; - - void LowBatteryOff() override { - if (SFX_poweron) { - PropBase::LowBatteryOff(); - } - } - // Calculate Physical Damage ////////////////////////////////////////////////// void DoDamage(int damage, bool quiet = false) { From acba6317dde55f5efc0e95e761b5f33a9187a797 Mon Sep 17 00:00:00 2001 From: James Nash <148967915+jimsworld@users.noreply.github.com> Date: Tue, 1 Apr 2025 09:07:26 +0100 Subject: [PATCH 09/37] removed extra empty line --- props/hev.h | 1 - 1 file changed, 1 deletion(-) diff --git a/props/hev.h b/props/hev.h index b655c24e6..386433407 100644 --- a/props/hev.h +++ b/props/hev.h @@ -100,7 +100,6 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { hybrid_font.PlayCommon(&SFX_armor); } - // Clashes /////////////////////////////////////////////////////////////////// uint32_t last_clash_time_ = 0; const uint32_t CLASH_DEBOUNCE_MS = 100; // Adjust debounce time as needed From f5cac7363fab954ca2abf50cba0951dd08544ee4 Mon Sep 17 00:00:00 2001 From: John Doe Date: Thu, 3 Apr 2025 15:10:08 +0100 Subject: [PATCH 10/37] Removed all extra lines --- props/hev.h | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/props/hev.h b/props/hev.h index 386433407..0432b1176 100644 --- a/props/hev.h +++ b/props/hev.h @@ -92,14 +92,12 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { PVLOG_NORMAL << "Armor: " << armor_ << "\n"; } - // Armor Readout ///////////////////////////////////////////////////////////// void armor_readout() { PVLOG_NORMAL << "Current Armor: " << armor_ << "\n"; // Debug logging SFX_armor.SelectFloat(armor_ / 100.0); hybrid_font.PlayCommon(&SFX_armor); } - // Clashes /////////////////////////////////////////////////////////////////// uint32_t last_clash_time_ = 0; const uint32_t CLASH_DEBOUNCE_MS = 100; // Adjust debounce time as needed @@ -144,13 +142,11 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { PropBase::Clash(false, strength); } - // Swings do nothing! //////////////////////////////////////////////////// void DoMotion(const Vec3& motion, bool clear) override { PropBase::DoMotion(Vec3(0), clear); } - // Random Hazards //////////////////////////////////////////////////////////// uint32_t last_random_draw_ = millis(); uint32_t post_death_cooldown_ = 0; @@ -225,7 +221,6 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { } } - // Healing /////////////////////////////////////////////////////////////////// // Increase health (if healing). uint32_t health_increase_millis_ = millis(); @@ -251,7 +246,6 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { } } - // Recharging //////////////////////////////////////////////////////////////// // Increase armor (if recharging). uint32_t armor_increase_millis_ = millis(); @@ -271,7 +265,6 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { } } } - // Main Loop ////////////////////////////////////////////////////////////////// void Loop() override { @@ -285,7 +278,6 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { PropBase::Loop(); } - // Button Events ///////////////////////////////////////////////////////////// bool Event2(enum BUTTON button, EVENT event, uint32_t modifiers) override { switch (EVENTID(button, event, modifiers)) { @@ -331,7 +323,6 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { previous_preset(); return true; - // Hold AUX to start healing case EVENTID(BUTTON_AUX, EVENT_HELD_MEDIUM, MODE_ON): if (!SaberBase::Lockup()) { @@ -367,9 +358,7 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { return true; } break; - - - + #ifdef BLADE_DETECT_PIN case EVENTID(BUTTON_BLADE_DETECT, EVENT_LATCH_ON, MODE_ANY_BUTTON | MODE_ON): case EVENTID(BUTTON_BLADE_DETECT, EVENT_LATCH_ON, MODE_ANY_BUTTON | MODE_OFF): @@ -391,7 +380,6 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { return false; } - // Hev effects, auto fire is handled by begin/end lockup void SB_Effect(EffectType effect, EffectLocation location) override { // Don't queue new sounds if dead (except death sound). @@ -429,4 +417,3 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { }; #endif - From 4bdb790e23903d29a1f4e45eade6ceccd6856d48 Mon Sep 17 00:00:00 2001 From: jimsworld Date: Wed, 9 Apr 2025 14:54:02 +0100 Subject: [PATCH 11/37] Removed /////// from the end of each function name --- props/hev.h | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/props/hev.h b/props/hev.h index 0432b1176..fd955cb36 100644 --- a/props/hev.h +++ b/props/hev.h @@ -51,7 +51,7 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { Hazard current_hazard_ = HAZARD_NONE; - // Calculate Physical Damage ////////////////////////////////////////////////// + // Calculate Physical Damage void DoDamage(int damage, bool quiet = false) { int tens = health_ / 10; if (armor_ >= damage) { @@ -92,13 +92,13 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { PVLOG_NORMAL << "Armor: " << armor_ << "\n"; } - // Armor Readout ///////////////////////////////////////////////////////////// + // Armor Readout void armor_readout() { PVLOG_NORMAL << "Current Armor: " << armor_ << "\n"; // Debug logging SFX_armor.SelectFloat(armor_ / 100.0); hybrid_font.PlayCommon(&SFX_armor); } - // Clashes /////////////////////////////////////////////////////////////////// + // Clashes uint32_t last_clash_time_ = 0; const uint32_t CLASH_DEBOUNCE_MS = 100; // Adjust debounce time as needed @@ -142,12 +142,12 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { PropBase::Clash(false, strength); } - // Swings do nothing! //////////////////////////////////////////////////// + // Swings do nothing! void DoMotion(const Vec3& motion, bool clear) override { PropBase::DoMotion(Vec3(0), clear); } - // Random Hazards //////////////////////////////////////////////////////////// + // Random Hazards uint32_t last_random_draw_ = millis(); uint32_t post_death_cooldown_ = 0; @@ -221,8 +221,7 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { } } - // Healing /////////////////////////////////////////////////////////////////// - // Increase health (if healing). + // Increase health (Hold AUX). uint32_t health_increase_millis_ = millis(); void IncreaseHealth() { if (SaberBase::Lockup()) { @@ -246,8 +245,7 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { } } - // Recharging //////////////////////////////////////////////////////////////// - // Increase armor (if recharging). + // Increase armor (Hold POWER). uint32_t armor_increase_millis_ = millis(); void IncreaseArmor() { if (SaberBase::Lockup()) { @@ -266,7 +264,7 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { } } - // Main Loop ////////////////////////////////////////////////////////////////// + // Main Loop void Loop() override { CheckRandomEvent(); HazardDecrease(); @@ -278,7 +276,7 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { PropBase::Loop(); } - // Button Events ///////////////////////////////////////////////////////////// + // Button Events bool Event2(enum BUTTON button, EVENT event, uint32_t modifiers) override { switch (EVENTID(button, event, modifiers)) { From e7138bca539b41a885689fe67407584ef50512c4 Mon Sep 17 00:00:00 2001 From: jimsworld Date: Wed, 9 Apr 2025 15:52:01 +0100 Subject: [PATCH 12/37] Fixed indentation consistency --- props/hev.h | 173 ++++++++++++++++++++++++++-------------------------- 1 file changed, 87 insertions(+), 86 deletions(-) diff --git a/props/hev.h b/props/hev.h index fd955cb36..b5d297341 100644 --- a/props/hev.h +++ b/props/hev.h @@ -226,21 +226,21 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { void IncreaseHealth() { if (SaberBase::Lockup()) { if (millis() - health_increase_millis_ > HEV_HEALTH_INCREASE_MS) { - health_increase_millis_ = millis(); + health_increase_millis_ = millis(); - // Start cooldown timer when health increases above 0. - // This starts the check to prevent immediate Hazards after healing. - if (health_ == 0) { - post_death_cooldown_ = millis(); // Start cooldown timer - } + // Start cooldown timer when health increases above 0. + // This starts the check to prevent immediate Hazards after healing. + if (health_ == 0) { + post_death_cooldown_ = millis(); // Start cooldown timer + } - health_++; - PVLOG_NORMAL << "Health: " << health_ << "\n"; - if (health_ >= 100) { - health_ = 100; - SaberBase::DoEndLockup(); - SaberBase::SetLockup(SaberBase::LOCKUP_NONE); - } + health_++; + PVLOG_NORMAL << "Health: " << health_ << "\n"; + if (health_ >= 100) { + health_ = 100; + SaberBase::DoEndLockup(); + SaberBase::SetLockup(SaberBase::LOCKUP_NONE); + } } } } @@ -250,16 +250,16 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { void IncreaseArmor() { if (SaberBase::Lockup()) { if (millis() - armor_increase_millis_ > HEV_ARMOR_INCREASE_MS) { - armor_increase_millis_ = millis(); + armor_increase_millis_ = millis(); - armor_++; - PVLOG_NORMAL << "Armor: " << armor_ << "\n"; + armor_++; + PVLOG_NORMAL << "Armor: " << armor_ << "\n"; - if (armor_ >= 100) { - armor_ = 100; - SaberBase::DoEndLockup(); - SaberBase::SetLockup(SaberBase::LOCKUP_NONE); - } + if (armor_ >= 100) { + armor_ = 100; + SaberBase::DoEndLockup(); + SaberBase::SetLockup(SaberBase::LOCKUP_NONE); + } } } } @@ -279,39 +279,38 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { // Button Events bool Event2(enum BUTTON button, EVENT event, uint32_t modifiers) override { switch (EVENTID(button, event, modifiers)) { - // On/Off long-click case EVENTID(BUTTON_POWER, EVENT_FIRST_CLICK_LONG, MODE_OFF): - On(); - return true; + On(); + return true; case EVENTID(BUTTON_POWER, EVENT_FIRST_CLICK_LONG, MODE_ON): - Off(); - return true; + Off(); + return true; - // short-click AUX to clear hazard + // short-click AUX to clear hazard case EVENTID(BUTTON_AUX, EVENT_FIRST_SAVED_CLICK_SHORT, MODE_ANY_BUTTON | MODE_ON): case EVENTID(BUTTON_AUX, EVENT_FIRST_SAVED_CLICK_SHORT, MODE_ANY_BUTTON | MODE_OFF): - if (current_hazard_) { - current_hazard_ = HAZARD_NONE; - SaberBase::DoEffect(EFFECT_ALT_SOUND, 0.0, current_hazard_); - return true; - } - // Play a no-hazard sound ? - return true; - - // Double-click power to start/stop track. + if (current_hazard_) { + current_hazard_ = HAZARD_NONE; + SaberBase::DoEffect(EFFECT_ALT_SOUND, 0.0, current_hazard_); + return true; + } + // Play a no-hazard sound ? + return true; + + // Double-click power to start/stop track. case EVENTID(BUTTON_POWER, EVENT_SECOND_SAVED_CLICK_SHORT, MODE_ANY_BUTTON | MODE_ON): case EVENTID(BUTTON_POWER, EVENT_SECOND_SAVED_CLICK_SHORT, MODE_ANY_BUTTON | MODE_OFF): StartOrStopTrack(); return true; - - // Double-click AUX for Armor Readout. + + // Double-click AUX for Armor Readout. case EVENTID(BUTTON_AUX, EVENT_SECOND_SAVED_CLICK_SHORT, MODE_ANY_BUTTON | MODE_ON): case EVENTID(BUTTON_AUX, EVENT_SECOND_SAVED_CLICK_SHORT, MODE_ANY_BUTTON | MODE_OFF): armor_readout(); return true; - - // Next/Previous preset. Triple-click on either button. + + // Next/Previous preset. Triple-click on either button. case EVENTID(BUTTON_POWER, EVENT_THIRD_SAVED_CLICK_SHORT, MODE_ANY_BUTTON | MODE_ON): case EVENTID(BUTTON_POWER, EVENT_THIRD_SAVED_CLICK_SHORT, MODE_ANY_BUTTON | MODE_OFF): next_preset(); @@ -321,58 +320,60 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { previous_preset(); return true; - // Hold AUX to start healing + // Hold AUX to start healing case EVENTID(BUTTON_AUX, EVENT_HELD_MEDIUM, MODE_ON): - if (!SaberBase::Lockup()) { - SaberBase::SetLockup(SaberBase::LOCKUP_NORMAL); - SaberBase::DoBeginLockup(); - return true; - } - break; - // Release AUX to stop healing (or wait until full). + if (!SaberBase::Lockup()) { + SaberBase::SetLockup(SaberBase::LOCKUP_NORMAL); + SaberBase::DoBeginLockup(); + return true; + } + break; + + // Release AUX to stop healing (or wait until full). case EVENTID(BUTTON_AUX, EVENT_RELEASED, MODE_ANY_BUTTON | MODE_ON): case EVENTID(BUTTON_AUX, EVENT_RELEASED, MODE_ANY_BUTTON | MODE_OFF): - if (SaberBase::Lockup()) { - SaberBase::DoEndLockup(); - SaberBase::SetLockup(SaberBase::LOCKUP_NONE); - return true; - } - break; - - // Hold POWER to start recharging armor + if (SaberBase::Lockup()) { + SaberBase::DoEndLockup(); + SaberBase::SetLockup(SaberBase::LOCKUP_NONE); + return true; + } + break; + + // Hold POWER to start recharging armor case EVENTID(BUTTON_POWER, EVENT_HELD_MEDIUM, MODE_ON): - if (!SaberBase::Lockup()) { - SaberBase::SetLockup(SaberBase::LOCKUP_LIGHTNING_BLOCK); - SaberBase::DoBeginLockup(); - return true; - } - break; - // Release POWER to stop recharging armor (or wait until full). - case EVENTID(BUTTON_POWER, EVENT_RELEASED, MODE_ANY_BUTTON | MODE_ON): - case EVENTID(BUTTON_POWER, EVENT_RELEASED, MODE_ANY_BUTTON | MODE_OFF): - if (SaberBase::Lockup()) { - SaberBase::DoEndLockup(); - SaberBase::SetLockup(SaberBase::LOCKUP_NONE); - return true; - } - break; + if (!SaberBase::Lockup()) { + SaberBase::SetLockup(SaberBase::LOCKUP_LIGHTNING_BLOCK); + SaberBase::DoBeginLockup(); + return true; + } + break; + + // Release POWER to stop recharging armor (or wait until full). + case EVENTID(BUTTON_POWER, EVENT_RELEASED, MODE_ANY_BUTTON | MODE_ON): + case EVENTID(BUTTON_POWER, EVENT_RELEASED, MODE_ANY_BUTTON | MODE_OFF): + if (SaberBase::Lockup()) { + SaberBase::DoEndLockup(); + SaberBase::SetLockup(SaberBase::LOCKUP_NONE); + return true; + } + break; #ifdef BLADE_DETECT_PIN - case EVENTID(BUTTON_BLADE_DETECT, EVENT_LATCH_ON, MODE_ANY_BUTTON | MODE_ON): - case EVENTID(BUTTON_BLADE_DETECT, EVENT_LATCH_ON, MODE_ANY_BUTTON | MODE_OFF): - // Might need to do something cleaner, but let's try this for now. - blade_detected_ = true; - FindBladeAgain(); - SaberBase::DoBladeDetect(true); - return true; - - case EVENTID(BUTTON_BLADE_DETECT, EVENT_LATCH_OFF, MODE_ANY_BUTTON | MODE_ON): - case EVENTID(BUTTON_BLADE_DETECT, EVENT_LATCH_OFF, MODE_ANY_BUTTON | MODE_OFF): - // Might need to do something cleaner, but let's try this for now. - blade_detected_ = false; - FindBladeAgain(); - SaberBase::DoBladeDetect(false); - return true; + case EVENTID(BUTTON_BLADE_DETECT, EVENT_LATCH_ON, MODE_ANY_BUTTON | MODE_ON): + case EVENTID(BUTTON_BLADE_DETECT, EVENT_LATCH_ON, MODE_ANY_BUTTON | MODE_OFF): + // Might need to do something cleaner, but let's try this for now. + blade_detected_ = true; + FindBladeAgain(); + SaberBase::DoBladeDetect(true); + return true; + + case EVENTID(BUTTON_BLADE_DETECT, EVENT_LATCH_OFF, MODE_ANY_BUTTON | MODE_ON): + case EVENTID(BUTTON_BLADE_DETECT, EVENT_LATCH_OFF, MODE_ANY_BUTTON | MODE_OFF): + // Might need to do something cleaner, but let's try this for now. + blade_detected_ = false; + FindBladeAgain(); + SaberBase::DoBladeDetect(false); + return true; #endif } return false; From 2f33e2c957138d90818a2f5cb46399406e083a34 Mon Sep 17 00:00:00 2001 From: jimsworld Date: Thu, 10 Apr 2025 14:45:29 +0100 Subject: [PATCH 13/37] Inverted initial if statement in HazardDecrease - reduced indentation and increased readability --- props/hev.h | 66 ++++++++++++++++++++++++++--------------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/props/hev.h b/props/hev.h index b5d297341..6840d4853 100644 --- a/props/hev.h +++ b/props/hev.h @@ -179,45 +179,45 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { uint32_t hazard_start_delay_ = 0; uint32_t hazard_decrease_millis_ = millis(); void HazardDecrease() { - if (current_hazard_ != HAZARD_NONE) { - // Check if this is a new hazard that needs initial delay - if (hazard_start_delay_ == 0) { - hazard_start_delay_ = millis(); - return; - } + if (current_hazard_ == HAZARD_NONE) { + hazard_start_delay_ = 0; + return; + } - // Wait for initial delay before starting damage - if (millis() - hazard_start_delay_ < HEV_HAZARD_DELAY_MS) { - return; - } + // Check if this is a new hazard that needs initial delay + if (hazard_start_delay_ == 0) { + hazard_start_delay_ = millis(); + return; + } - // Normal damage interval after initial delay - if (millis() - hazard_decrease_millis_ > HEV_HAZARD_DECREASE_MS) { - hazard_decrease_millis_ = millis(); + // Wait for initial delay before starting damage + if (millis() - hazard_start_delay_ < HEV_HAZARD_DELAY_MS) { + return; + } - // Play stun sounds if there is armor - if (armor_ > 0) { - armor_--; - SaberBase::DoEffect(EFFECT_STUN, 0.0, 1); - // Check if armor is 0 then play armor compromised sound. - if (armor_ == 0) { - SaberBase::DoEffect(EFFECT_USER2, 0.0, 0); - PVLOG_NORMAL << "Armor Compromised!\n"; - } - } else { - DoDamage(1); + // Normal damage interval after initial delay + if (millis() - hazard_decrease_millis_ > HEV_HAZARD_DECREASE_MS) { + hazard_decrease_millis_ = millis(); + + // Play stun sounds if there is armor + if (armor_ > 0) { + armor_--; + SaberBase::DoEffect(EFFECT_STUN, 0.0, 1); + // Check if armor is 0 then play armor compromised sound. + if (armor_ == 0) { + SaberBase::DoEffect(EFFECT_USER2, 0.0, 0); + PVLOG_NORMAL << "Armor Compromised!\n"; } + } else { + DoDamage(1); + } - // Stop the current Hazard if health drops to 0 - if (health_ == 0) { - current_hazard_ = HAZARD_NONE; - hazard_start_delay_ = 0; // Reset delay for next hazard - SaberBase::DoEffect(EFFECT_ALT_SOUND, 0.0, current_hazard_); - } + // Stop the current Hazard if health drops to 0 + if (health_ == 0) { + current_hazard_ = HAZARD_NONE; + hazard_start_delay_ = 0; // Reset delay for next hazard + SaberBase::DoEffect(EFFECT_ALT_SOUND, 0.0, current_hazard_); } - } else { - // Reset delay when no hazard is active - hazard_start_delay_ = 0; } } From 145f483d24f1a57dabd6cb6331e339b331d1331e Mon Sep 17 00:00:00 2001 From: jimsworld Date: Mon, 14 Apr 2025 14:37:05 +0100 Subject: [PATCH 14/37] Added descriptions for all sounds currently needed --- props/hev.h | 44 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/props/hev.h b/props/hev.h index 6840d4853..73393c9bf 100644 --- a/props/hev.h +++ b/props/hev.h @@ -1,22 +1,48 @@ -#ifndef PROPS_HEV_H -#define PROPS_HEV_H -#define PROP_TYPE Hev - // Half-Life Hazardous Environment Suit Prop // // How to use: -// Power button: -// Long-click: on/off -// double-click: sound track on/off +// POWER button: +// Long-click: ON/OFF +// hold button to recharge armor +// double-click: toggle track // triple-click: next preset // AUX button: // hold button to heal. // single click: deactivate hazard // double-click: armor readout // triple-click: previous preset -// Clash will cause varying damage based on strength // -// Damage and Hazards are only active when prop is on. +// Sound files needed: +// in.wav, out.wav - for power on/off. Use flashlight sound +// bgnlb.wav, lb.wav, endlb.wave - recharge armor sound sequence +// bgnlock.wav, lock.wav, endlock.wav - heal sound sequence +// font.wav - next/previous preset +// armor_alarm.wav - for when Clash damage is greater than or equal to 30 +// armor_compromised.wav - for when armor falls to 0 +// boot.wav - Bootup welcome message +// death.wav - for when health is 0 +// clash**.wav - physical clash sounds +// health00.wav to health100.wav - for health alert sounds +// armor00.wav to armor100.wav - for armor readout sounds +// +// Random Hazard sounds: +// These are played using the altchng method. So create 7 directories: +// alt00, alt01, alt02, alt03, alt04, alt05, alt06. +// Each directory should contain two folders, altchng and stun. +// In alt01 to alt06, place the hazard detection voice lines (bio, blood toxins, chemical, radiation, shock, fire) in altchng. +// In alt01 to alt06, place appropriate sound effects (spark, burn, geiger counter) in stun. +// In each stun folder, use the same amount of files as the other stun folders. So if you use 4 stun sounds, use 4 in all of them. +// In each altchng folder, use the same amount of files as the other altchng folders. I use 2 in all of them. +// One for the main voice line, the other is the same, but with a follow up health notification for variety. +// For alt00, have the same amount of files as the other alt** folders but leave the files blank. +// +// Notes: +// Clash will cause varying damage based on strength +// Damage and Hazards are only active when prop is ON. + +#ifndef PROPS_HEV_H +#define PROPS_HEV_H +#define PROP_TYPE Hev #include "prop_base.h" #include From 3abec2d0336d0df425118e6bd47eb67e4cd75b2c Mon Sep 17 00:00:00 2001 From: jimsworld Date: Fri, 25 Apr 2025 11:06:11 +0100 Subject: [PATCH 15/37] Introduced HEVTimer class to manage timing events - Added a list of instance variables to track timers. The remaining timers will be implemented properly next. - Added a new method to HEVTimer, expired(), to check if a timer has expired. - Redone the Clash logic to utilise the new HEVTimer class and respect the Debounce period. - Removed logs and cleaned up Clash function. - Removed CLASH_DEBOUNCE_MS from hev.h to hev_config.h. Renamed to HEV_CLASH_DEBOUNCE_MS. Default is 300ms. --- props/hev.h | 74 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 47 insertions(+), 27 deletions(-) diff --git a/props/hev.h b/props/hev.h index 73393c9bf..36e69a554 100644 --- a/props/hev.h +++ b/props/hev.h @@ -1,4 +1,4 @@ -// Half-Life Hazardous Environment Suit Prop +// HALF-LIFE - Hazardous Environment Suit Prop // // How to use: // POWER button: @@ -57,6 +57,26 @@ EFFECT(major); EFFECT(minor); EFFECT(morphine); +struct HEVTimer { + uint32_t start_; + bool active_ = false; + + void reset() { active_ = false; } + void start() { active_ = true; start_ = millis(); } + bool expired(uint32_t timeout) const { + if (!active_) return false; + return (millis() - start_ > timeout); + } + bool check(uint32_t timeout) { + if (!active_) return false; + if (millis() - start_ > timeout) { + reset(); + return true; + } + return false; + } +}; + class Hev : public PROP_INHERIT_PREFIX PropBase { public: Hev() : PropBase() {} @@ -65,6 +85,14 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { int health_ = 100; int armor_ = 100; + HEVTimer clash_timer_; + HEVTimer random_event_timer_; + HEVTimer post_death_cooldown_timer_; + HEVTimer hazard_delay_timer_; + HEVTimer hazard_damage_timer_; + HEVTimer health_increase_timer_; + HEVTimer armor_increase_timer_; + enum Hazard { HAZARD_NONE = 0, HAZARD_BIO = 1, @@ -113,9 +141,10 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { SaberBase::DoEffect(EFFECT_USER1, 0.0, 0); } - // Print health and armor - PVLOG_NORMAL << "Health: " << health_ << " / "; - PVLOG_NORMAL << "Armor: " << armor_ << "\n"; + // Print Damage, Health and Armor + PVLOG_NORMAL << "DAMAGE: -" << damage << "\n"; + PVLOG_NORMAL << "HEALTH: " << health_ << " / "; + PVLOG_NORMAL << "ARMOR: " << armor_ << "\n"; } // Armor Readout @@ -124,25 +153,24 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { SFX_armor.SelectFloat(armor_ / 100.0); hybrid_font.PlayCommon(&SFX_armor); } - // Clashes - uint32_t last_clash_time_ = 0; - const uint32_t CLASH_DEBOUNCE_MS = 100; // Adjust debounce time as needed + // Clashes void Clash(bool stab, float strength) override { - // Ignore Clash if within debounce period - uint32_t current_time = millis(); - if (current_time - last_clash_time_ < CLASH_DEBOUNCE_MS) { - return; + // Check and reset expired timer first + if (clash_timer_.active_ && clash_timer_.expired(HEV_CLASH_DEBOUNCE_MS)) { + clash_timer_.reset(); } - last_clash_time_ = current_time; - // Stop Clashes if health is 0 - if (health_ == 0) { + // Skip if dead or within debounce period + if (health_ == 0 || clash_timer_.active_) { return; } - // Strength multiplier - int damage = (int)(strength * 4); + // Start new debounce period + clash_timer_.start(); + + // Damage is based on strength, capped at 50 + int damage = std::min((int)(strength * 4), 50); // Play Clash sounds based on strength float v = (strength - GetCurrentClashThreshold()) / 3; @@ -150,21 +178,13 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { SFX_clsh.SelectFloat(v); SFX_stab.SelectFloat(v); - // Cap damage at 50 - if (damage > 50) { - damage = 50; - } - - // Play Armor Alarm if damage is 30 or more + // Play Armor Alarm if Damage is 30 or more if (damage >= 30) { hybrid_font.PlayPolyphonic(&SFX_armor_alarm); } - // Apply Physical Damage and print to log + // Apply Damage and forward Clash event. No Stabs! DoDamage(damage, true); - PVLOG_NORMAL << "Physical Damage: -" << damage << "\n"; - - // No stabs! PropBase::Clash(false, strength); } @@ -201,7 +221,7 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { } } - // Decrease armor (if any) or health over time. + // Hazard Damage. Decrease armor (if any) or health over time. uint32_t hazard_start_delay_ = 0; uint32_t hazard_decrease_millis_ = millis(); void HazardDecrease() { From ba7c6144656efbac5dc7cf69bbaa8845a82b061c Mon Sep 17 00:00:00 2001 From: jimsworld Date: Fri, 25 Apr 2025 13:08:11 +0100 Subject: [PATCH 16/37] HEVTimer class reworked: - Removed expired() method and reworked check() - Cleaned up the Clash function to account for the newer check() method --- props/hev.h | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/props/hev.h b/props/hev.h index 36e69a554..da909468b 100644 --- a/props/hev.h +++ b/props/hev.h @@ -63,17 +63,11 @@ struct HEVTimer { void reset() { active_ = false; } void start() { active_ = true; start_ = millis(); } - bool expired(uint32_t timeout) const { - if (!active_) return false; - return (millis() - start_ > timeout); - } bool check(uint32_t timeout) { - if (!active_) return false; - if (millis() - start_ > timeout) { + if (active_ && (millis() - start_ > timeout)) { reset(); - return true; } - return false; + return !active_; } }; @@ -156,13 +150,8 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { // Clashes void Clash(bool stab, float strength) override { - // Check and reset expired timer first - if (clash_timer_.active_ && clash_timer_.expired(HEV_CLASH_DEBOUNCE_MS)) { - clash_timer_.reset(); - } - - // Skip if dead or within debounce period - if (health_ == 0 || clash_timer_.active_) { + // Don't allow Clashes if dead or during debounce period. + if (health_ == 0 || !clash_timer_.check(HEV_CLASH_DEBOUNCE_MS)) { return; } @@ -171,9 +160,9 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { // Damage is based on strength, capped at 50 int damage = std::min((int)(strength * 4), 50); + float v = (strength - GetCurrentClashThreshold()) / 3; // Play Clash sounds based on strength - float v = (strength - GetCurrentClashThreshold()) / 3; SFX_clash.SelectFloat(v); SFX_clsh.SelectFloat(v); SFX_stab.SelectFloat(v); From 8ce74042fada5a5d09edb73c955d8b263c020907 Mon Sep 17 00:00:00 2001 From: jimsworld Date: Thu, 8 May 2025 14:14:07 +0100 Subject: [PATCH 17/37] Updated Random Hazard and Hazard Decrease function - Added a new method in HEVTimer to account for the hazarddecrease() function - The new HEVTimer method is called hazard_sequence() - It adheres and controls the timing for the delay before hazard damage then how frequent damage is applied - By default, the delay is set to 6s to allow for the voice line to finish then damage is applied every 1s - Damage per second will improved in a future update with random values per per damage tick --- props/hev.h | 69 +++++++++++++++++++++++++++-------------------------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/props/hev.h b/props/hev.h index da909468b..308dec252 100644 --- a/props/hev.h +++ b/props/hev.h @@ -69,6 +69,17 @@ struct HEVTimer { } return !active_; } + bool hazard_sequence(uint32_t warning_period, uint32_t damage_interval) { + if (!active_) return false; + uint32_t elapsed_time = millis() - start_; + // Still in warning period, no damage yet + if (elapsed_time < warning_period) { + return false; + } + // Past warning period, check if it's time for damage tick + uint32_t time_after_warning = elapsed_time - warning_period; + return (time_after_warning % damage_interval) == 0; + } }; class Hev : public PROP_INHERIT_PREFIX PropBase { @@ -183,62 +194,50 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { } // Random Hazards - uint32_t last_random_draw_ = millis(); - uint32_t post_death_cooldown_ = 0; - - // Check and activation void CheckRandomEvent() { - // Don't allow Hazards if dead or during post-death cooldown. - // This prevents Hazards immediately after healing. - if (health_ == 0 || (post_death_cooldown_ && - (millis() - post_death_cooldown_ < HEV_POST_DEATH_COOLDOWN_MS))) { + // Skip Hazard check if dead or during post-death cooldown + if (health_ == 0 || !post_death_cooldown_timer_.check(HEV_POST_DEATH_COOLDOWN_MS)) { + return; + } + + // Initialize timer. Stops immediate Hazard at boot + if (!random_event_timer_.active_) { + random_event_timer_.start(); return; } - // Activate a Hazard, only if health is above 0 and no Hazard is currently active. - if (millis() - last_random_draw_ > HEV_RANDOM_EVENT_INTERVAL_MS && - health_ > 0 && + // Check for new Hazard if timer expired and no current Hazard + if (random_event_timer_.check(HEV_RANDOM_EVENT_INTERVAL_MS) && current_hazard_ == HAZARD_NONE) { - last_random_draw_ = millis(); + + // Roll for Random Hazard if (random(100) < HEV_RANDOM_HAZARD_CHANCE) { PVLOG_NORMAL << "Activating hazard.\n"; current_hazard_ = (Hazard)(1 + random(6)); - hazard_decrease_millis_ = millis(); - hazard_start_delay_ = 0; SaberBase::DoEffect(EFFECT_ALT_SOUND, 0.0, current_hazard_); } } } // Hazard Damage. Decrease armor (if any) or health over time. - uint32_t hazard_start_delay_ = 0; - uint32_t hazard_decrease_millis_ = millis(); void HazardDecrease() { + // Reset timer when hazard is cleared if (current_hazard_ == HAZARD_NONE) { - hazard_start_delay_ = 0; + hazard_delay_timer_.reset(); return; } - // Check if this is a new hazard that needs initial delay - if (hazard_start_delay_ == 0) { - hazard_start_delay_ = millis(); + // Start sequence if not running + if (!hazard_delay_timer_.active_) { + hazard_delay_timer_.start(); return; } - // Wait for initial delay before starting damage - if (millis() - hazard_start_delay_ < HEV_HAZARD_DELAY_MS) { - return; - } - - // Normal damage interval after initial delay - if (millis() - hazard_decrease_millis_ > HEV_HAZARD_DECREASE_MS) { - hazard_decrease_millis_ = millis(); - - // Play stun sounds if there is armor + // Check sequence and apply damage + if (hazard_delay_timer_.hazard_sequence(HEV_HAZARD_DELAY_MS, HEV_HAZARD_DECREASE_MS)) { if (armor_ > 0) { armor_--; SaberBase::DoEffect(EFFECT_STUN, 0.0, 1); - // Check if armor is 0 then play armor compromised sound. if (armor_ == 0) { SaberBase::DoEffect(EFFECT_USER2, 0.0, 0); PVLOG_NORMAL << "Armor Compromised!\n"; @@ -247,10 +246,10 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { DoDamage(1); } - // Stop the current Hazard if health drops to 0 + // Clear hazard on death if (health_ == 0) { current_hazard_ = HAZARD_NONE; - hazard_start_delay_ = 0; // Reset delay for next hazard + hazard_delay_timer_.reset(); SaberBase::DoEffect(EFFECT_ALT_SOUND, 0.0, current_hazard_); } } @@ -266,7 +265,7 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { // Start cooldown timer when health increases above 0. // This starts the check to prevent immediate Hazards after healing. if (health_ == 0) { - post_death_cooldown_ = millis(); // Start cooldown timer + post_death_cooldown_timer_.start(); } health_++; @@ -328,6 +327,8 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { if (current_hazard_) { current_hazard_ = HAZARD_NONE; SaberBase::DoEffect(EFFECT_ALT_SOUND, 0.0, current_hazard_); + random_event_timer_.reset(); + random_event_timer_.start(); return true; } // Play a no-hazard sound ? From 98887f4c0fbb9280f2ad7ab0766eb3befe4341d6 Mon Sep 17 00:00:00 2001 From: jimsworld Date: Fri, 9 May 2025 11:28:36 +0100 Subject: [PATCH 18/37] Updated IncreaseHealth and IncreaseArmor functions - Added new method called ready() to check if a specified time interval has elapsed since the timer started. - Added checks on button presses to ensure the health_increase_timer and armor_increase_timer are started and reset correctly. --- props/hev.h | 60 ++++++++++++++++++++++++++--------------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/props/hev.h b/props/hev.h index 308dec252..c6be4fa22 100644 --- a/props/hev.h +++ b/props/hev.h @@ -80,6 +80,9 @@ struct HEVTimer { uint32_t time_after_warning = elapsed_time - warning_period; return (time_after_warning % damage_interval) == 0; } + bool ready(uint32_t interval) { + return active_ && ((millis() - start_) >= interval); + } }; class Hev : public PROP_INHERIT_PREFIX PropBase { @@ -256,45 +259,38 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { } // Increase health (Hold AUX). - uint32_t health_increase_millis_ = millis(); void IncreaseHealth() { - if (SaberBase::Lockup()) { - if (millis() - health_increase_millis_ > HEV_HEALTH_INCREASE_MS) { - health_increase_millis_ = millis(); - - // Start cooldown timer when health increases above 0. - // This starts the check to prevent immediate Hazards after healing. - if (health_ == 0) { - post_death_cooldown_timer_.start(); - } + if (SaberBase::Lockup() && health_increase_timer_.ready(HEV_HEALTH_INCREASE_MS)) { + if (health_ == 0) { + post_death_cooldown_timer_.start(); + } - health_++; - PVLOG_NORMAL << "Health: " << health_ << "\n"; - if (health_ >= 100) { - health_ = 100; - SaberBase::DoEndLockup(); - SaberBase::SetLockup(SaberBase::LOCKUP_NONE); - } + health_++; + PVLOG_NORMAL << "Health: " << health_ << "\n"; + + if (health_ >= 100) { + health_ = 100; + SaberBase::DoEndLockup(); + SaberBase::SetLockup(SaberBase::LOCKUP_NONE); } + + health_increase_timer_.start(); // Restart timer for next health increase } } // Increase armor (Hold POWER). - uint32_t armor_increase_millis_ = millis(); void IncreaseArmor() { - if (SaberBase::Lockup()) { - if (millis() - armor_increase_millis_ > HEV_ARMOR_INCREASE_MS) { - armor_increase_millis_ = millis(); - - armor_++; - PVLOG_NORMAL << "Armor: " << armor_ << "\n"; - - if (armor_ >= 100) { - armor_ = 100; - SaberBase::DoEndLockup(); - SaberBase::SetLockup(SaberBase::LOCKUP_NONE); - } + if (SaberBase::Lockup() && armor_increase_timer_.ready(HEV_ARMOR_INCREASE_MS)) { + armor_++; + PVLOG_NORMAL << "Armor: " << armor_ << "\n"; + + if (armor_ >= 100) { + armor_ = 100; + SaberBase::DoEndLockup(); + SaberBase::SetLockup(SaberBase::LOCKUP_NONE); } + + armor_increase_timer_.start(); } } @@ -361,6 +357,7 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { if (!SaberBase::Lockup()) { SaberBase::SetLockup(SaberBase::LOCKUP_NORMAL); SaberBase::DoBeginLockup(); + health_increase_timer_.start(); return true; } break; @@ -371,6 +368,7 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { if (SaberBase::Lockup()) { SaberBase::DoEndLockup(); SaberBase::SetLockup(SaberBase::LOCKUP_NONE); + health_increase_timer_.reset(); return true; } break; @@ -380,6 +378,7 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { if (!SaberBase::Lockup()) { SaberBase::SetLockup(SaberBase::LOCKUP_LIGHTNING_BLOCK); SaberBase::DoBeginLockup(); + armor_increase_timer_.start(); return true; } break; @@ -390,6 +389,7 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { if (SaberBase::Lockup()) { SaberBase::DoEndLockup(); SaberBase::SetLockup(SaberBase::LOCKUP_NONE); + armor_increase_timer_.reset(); return true; } break; From 086f434616f4853b235d45704a15a9708a650181 Mon Sep 17 00:00:00 2001 From: jimsworld Date: Tue, 13 May 2025 11:56:25 +0100 Subject: [PATCH 19/37] Fixed an issue with Increase Health and Armor - When health and armor was at 100, increasing briefly allowed to go up to 101 before capping to 100. - This was fixed by reordering to check if capped before increasing. - Made the HEVTimer code more consise and added comments. --- props/hev.h | 85 +++++++++++++++++++++++++++-------------------------- 1 file changed, 44 insertions(+), 41 deletions(-) diff --git a/props/hev.h b/props/hev.h index c6be4fa22..9ef93329c 100644 --- a/props/hev.h +++ b/props/hev.h @@ -58,28 +58,29 @@ EFFECT(minor); EFFECT(morphine); struct HEVTimer { - uint32_t start_; + uint32_t start_ = 0; + uint32_t next_tick_ = 0; bool active_ = false; - void reset() { active_ = false; } - void start() { active_ = true; start_ = millis(); } + void reset() { active_ = false; next_tick_ = 0; } + + void start() { active_ = true; start_ = next_tick_ = millis(); } + + // Returns true if timer is inactive or timeout exceeded bool check(uint32_t timeout) { - if (active_ && (millis() - start_ > timeout)) { - reset(); - } - return !active_; + return !active_ || (millis() - start_ > timeout); } - bool hazard_sequence(uint32_t warning_period, uint32_t damage_interval) { + + // Manages hazard warning and damage sequence timing + bool hazard_sequence(uint32_t warning_period, uint32_t tick_interval) { if (!active_) return false; - uint32_t elapsed_time = millis() - start_; - // Still in warning period, no damage yet - if (elapsed_time < warning_period) { - return false; - } - // Past warning period, check if it's time for damage tick - uint32_t time_after_warning = elapsed_time - warning_period; - return (time_after_warning % damage_interval) == 0; + uint32_t now = millis(); + + return (now - start_ >= warning_period) && + ((now >= next_tick_) ? (next_tick_ = now + tick_interval, true) : false); } + + // Returns true if timer is active and interval has elapsed bool ready(uint32_t interval) { return active_ && ((millis() - start_) >= interval); } @@ -260,38 +261,40 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { // Increase health (Hold AUX). void IncreaseHealth() { - if (SaberBase::Lockup() && health_increase_timer_.ready(HEV_HEALTH_INCREASE_MS)) { - if (health_ == 0) { - post_death_cooldown_timer_.start(); - } + if (health_ >= 100) { + SaberBase::DoEndLockup(); + SaberBase::SetLockup(SaberBase::LOCKUP_NONE); + return; + } - health_++; - PVLOG_NORMAL << "Health: " << health_ << "\n"; - - if (health_ >= 100) { - health_ = 100; - SaberBase::DoEndLockup(); - SaberBase::SetLockup(SaberBase::LOCKUP_NONE); - } - - health_increase_timer_.start(); // Restart timer for next health increase + if (!SaberBase::Lockup() || !health_increase_timer_.ready(HEV_HEALTH_INCREASE_MS)) { + return; + } + + if (health_ == 0) { + post_death_cooldown_timer_.start(); } + + health_++; + health_increase_timer_.start(); + PVLOG_NORMAL << "Health: " << health_ << "\n"; } // Increase armor (Hold POWER). void IncreaseArmor() { - if (SaberBase::Lockup() && armor_increase_timer_.ready(HEV_ARMOR_INCREASE_MS)) { - armor_++; - PVLOG_NORMAL << "Armor: " << armor_ << "\n"; - - if (armor_ >= 100) { - armor_ = 100; - SaberBase::DoEndLockup(); - SaberBase::SetLockup(SaberBase::LOCKUP_NONE); - } - - armor_increase_timer_.start(); + if (armor_ >= 100) { + SaberBase::DoEndLockup(); + SaberBase::SetLockup(SaberBase::LOCKUP_NONE); + return; } + + if (!SaberBase::Lockup() || !armor_increase_timer_.ready(HEV_ARMOR_INCREASE_MS)) { + return; + } + + armor_++; + armor_increase_timer_.start(); + PVLOG_NORMAL << "Armor: " << armor_ << "\n"; } // Main Loop From 7f41d4c7d68a7071bc80e8bea51c162887ac9f16 Mon Sep 17 00:00:00 2001 From: jimsworld Date: Thu, 29 May 2025 11:18:46 +0100 Subject: [PATCH 20/37] Utilising prop_base's debounce for Clashes --- props/hev.h | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/props/hev.h b/props/hev.h index 9ef93329c..bd1bc1fb6 100644 --- a/props/hev.h +++ b/props/hev.h @@ -165,13 +165,19 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { // Clashes void Clash(bool stab, float strength) override { - // Don't allow Clashes if dead or during debounce period. - if (health_ == 0 || !clash_timer_.check(HEV_CLASH_DEBOUNCE_MS)) { + // Don't allow Clashes if dead. + if (health_ == 0) { return; } - // Start new debounce period - clash_timer_.start(); + // If HEVTimer and PropBase's clash_timeout_ are true to activate Clash. + // Otherwise return early. + if (clash_timer_.active_ && !clash_timer_.check(this->clash_timeout_)) { + return; + } + + // Forward Clash event. No Stabs! + PropBase::Clash(false, strength); // Damage is based on strength, capped at 50 int damage = std::min((int)(strength * 4), 50); @@ -187,9 +193,9 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { hybrid_font.PlayPolyphonic(&SFX_armor_alarm); } - // Apply Damage and forward Clash event. No Stabs! + // Apply Damage and restart clash timer. DoDamage(damage, true); - PropBase::Clash(false, strength); + clash_timer_.start(); } // Swings do nothing! From af83a2b299f7e1c1688ca59639fe91b75876afe7 Mon Sep 17 00:00:00 2001 From: jimsworld Date: Fri, 20 Jun 2025 15:32:18 +0100 Subject: [PATCH 21/37] Commiting updates to work over weekend. - Made ammendments based on latests reviews. - Added more comments for clarity. - Updated HEVTimer to be more consise and easier to use. --- props/hev.h | 124 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 90 insertions(+), 34 deletions(-) diff --git a/props/hev.h b/props/hev.h index bd1bc1fb6..c4f1d5fa5 100644 --- a/props/hev.h +++ b/props/hev.h @@ -29,12 +29,18 @@ // These are played using the altchng method. So create 7 directories: // alt00, alt01, alt02, alt03, alt04, alt05, alt06. // Each directory should contain two folders, altchng and stun. -// In alt01 to alt06, place the hazard detection voice lines (bio, blood toxins, chemical, radiation, shock, fire) in altchng. -// In alt01 to alt06, place appropriate sound effects (spark, burn, geiger counter) in stun. -// In each stun folder, use the same amount of files as the other stun folders. So if you use 4 stun sounds, use 4 in all of them. -// In each altchng folder, use the same amount of files as the other altchng folders. I use 2 in all of them. -// One for the main voice line, the other is the same, but with a follow up health notification for variety. -// For alt00, have the same amount of files as the other alt** folders but leave the files blank. +// In alt01 to alt06, place the hazard detection voice lines in the altchng dir +// (bio, blood toxins, chemical, radiation, shock, fire). +// In alt01 to alt06, place appropriate sound effects in the stun dir +// (spark, burn, geiger counter). +// In each stun folder, use the same amount of files as the other stun folders. +// So if you use 4 stun sounds, use 4 in all of them. +// In each altchng folder, use the same amount of files as the other altchng folders. +// I use 2 in all of them. +// One for the main voice line, the other is the same, +// but with a follow up health notification for variety. +// For alt00, have the same amount of files as the other alt** folders +// but leave the files blank. // // Notes: // Clash will cause varying damage based on strength @@ -59,49 +65,87 @@ EFFECT(morphine); struct HEVTimer { uint32_t start_ = 0; + + // next_tick_ ensures precise timing for damage events by storing the exact + // timestamp when the next damage should occur, preventing timing drift. + // Mainly used for hazard damage, controlled by HEV_HAZARD_DECREASE_MS. uint32_t next_tick_ = 0; + + // Delay before applying damage. HEV_HAZARD_DELAY_MS + uint32_t delay_ = 0; + + // Time between applying damage. HEV_HAZARD_DECREASE_MS + uint32_t interval_ = 0; + bool active_ = false; void reset() { active_ = false; next_tick_ = 0; } void start() { active_ = true; start_ = next_tick_ = millis(); } + void configure(uint32_t interval, uint32_t delay_time = 0) { + interval_ = interval; + delay_ = delay_time; + } + // Returns true if timer is inactive or timeout exceeded - bool check(uint32_t timeout) { - return !active_ || (millis() - start_ > timeout); + bool check() { + return !active_ || (millis() - start_ > interval_); } - // Manages hazard warning and damage sequence timing - bool hazard_sequence(uint32_t warning_period, uint32_t tick_interval) { + // warning_period is a grace period. It's argument is HEV_HAZARD_DELAY_MS. + // tick_interval uses HEV_HAZARD_DECREASE_MS as an argument. Currently 1000ms. + // + // The hazard sequence method returns true when: + // - The timer is active (active_ is true) + // - The warning period has elapsed (now - start_ >= warning_period) + // - It's time for the next damage tick (now >= next_tick_) + // + // It returns false when: + // - The timer is not active (active_ is false) + // - The warning period hasn't elapsed yet + // - It's not time for the next damage tick + bool hazard_sequence() { if (!active_) return false; uint32_t now = millis(); - return (now - start_ >= warning_period) && - ((now >= next_tick_) ? (next_tick_ = now + tick_interval, true) : false); + return (now - start_ >= delay_) && + ((now >= next_tick_) ? (next_tick_ = now + interval_, true) : false); } // Returns true if timer is active and interval has elapsed - bool ready(uint32_t interval) { - return active_ && ((millis() - start_) >= interval); + bool ready() { + return active_ && ((millis() - start_) >= interval_); } }; class Hev : public PROP_INHERIT_PREFIX PropBase { public: - Hev() : PropBase() {} - const char* name() override { return "Hev"; } - - int health_ = 100; - int armor_ = 100; HEVTimer clash_timer_; - HEVTimer random_event_timer_; HEVTimer post_death_cooldown_timer_; + HEVTimer random_event_timer_; HEVTimer hazard_delay_timer_; HEVTimer hazard_damage_timer_; HEVTimer health_increase_timer_; HEVTimer armor_increase_timer_; + Hev() : PropBase() { + // Configure all timers with their respective timings from hev_config.h + // Clash timer uses clash_timeout_ from PropBase for debounce + clash_timer_.configure(this->clash_timeout_); + post_death_cooldown_timer_.configure(HEV_POST_DEATH_COOLDOWN_MS); + random_event_timer_.configure(HEV_RANDOM_EVENT_INTERVAL_MS); + hazard_delay_timer_.configure(HEV_HAZARD_DECREASE_MS, HEV_HAZARD_DELAY_MS); + health_increase_timer_.configure(HEV_HEALTH_INCREASE_MS); + armor_increase_timer_.configure(HEV_ARMOR_INCREASE_MS); + } + + const char* name() override { return "Hev"; } + + int health_ = 100; + int armor_ = 100; + enum Hazard { HAZARD_NONE = 0, HAZARD_BIO = 1, @@ -165,14 +209,27 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { // Clashes void Clash(bool stab, float strength) override { - // Don't allow Clashes if dead. - if (health_ == 0) { - return; - } - - // If HEVTimer and PropBase's clash_timeout_ are true to activate Clash. - // Otherwise return early. - if (clash_timer_.active_ && !clash_timer_.check(this->clash_timeout_)) { + // Don't process clashes if dead or during cooldown. + // HEV Suit clash detection uses a fundamentally different approach than PropBase. + // The two work together: timer tracks state, timeout defines duration. + // + // 1. PropBase uses direct timestamp comparison: + // - Stores last_clash_ timestamp and clash_timeout_ value. + // - Compares (millis() - last_clash_ < clash_timeout_) on each check. + // - Simple but limited to basic cooldown functionality. + // + // 2. HEV uses object-oriented HEVTimer system: + // - clash_timer_ encapsulates timer state (active flag, start time). + // - clash_timeout_ provides the duration value. + // - Enables more complex patterns like sequences and ready-state checking. + // + // This is advantageous because: + // - HEV's sophisticated hit categorization needs finer state control. + // - Consistent with other HEV timers (health_timer_, armor_timer_, etc.) + // - Provides clear separation between "is a timer running?" and "how long should it run?". + // - Allows more complex timing patterns needed for authentic HEV behavior. + // - Supports future extensions like variable cooldowns based on hit type. + if (health_ == 0 || (clash_timer_.active_ && !clash_timer_.check())) { return; } @@ -206,7 +263,7 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { // Random Hazards void CheckRandomEvent() { // Skip Hazard check if dead or during post-death cooldown - if (health_ == 0 || !post_death_cooldown_timer_.check(HEV_POST_DEATH_COOLDOWN_MS)) { + if (health_ == 0 || !post_death_cooldown_timer_.check()) { return; } @@ -217,8 +274,7 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { } // Check for new Hazard if timer expired and no current Hazard - if (random_event_timer_.check(HEV_RANDOM_EVENT_INTERVAL_MS) && - current_hazard_ == HAZARD_NONE) { + if (random_event_timer_.check() && current_hazard_ == HAZARD_NONE) { // Roll for Random Hazard if (random(100) < HEV_RANDOM_HAZARD_CHANCE) { @@ -244,7 +300,7 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { } // Check sequence and apply damage - if (hazard_delay_timer_.hazard_sequence(HEV_HAZARD_DELAY_MS, HEV_HAZARD_DECREASE_MS)) { + if (hazard_delay_timer_.hazard_sequence()) { if (armor_ > 0) { armor_--; SaberBase::DoEffect(EFFECT_STUN, 0.0, 1); @@ -273,7 +329,7 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { return; } - if (!SaberBase::Lockup() || !health_increase_timer_.ready(HEV_HEALTH_INCREASE_MS)) { + if (!SaberBase::Lockup() || !health_increase_timer_.ready()) { return; } @@ -294,7 +350,7 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { return; } - if (!SaberBase::Lockup() || !armor_increase_timer_.ready(HEV_ARMOR_INCREASE_MS)) { + if (!SaberBase::Lockup() || !armor_increase_timer_.ready()) { return; } From e3956aaad5163629ea58e0e5cae8b6f89b023c62 Mon Sep 17 00:00:00 2001 From: jimsworld Date: Wed, 25 Jun 2025 15:27:13 +0100 Subject: [PATCH 22/37] Created new HEVTimer subclass for finer control. - Renamed all timers by prefixing with timer_ for clarity. - Renamed HEVTimer to HEVTimerBase. - Created new HEVTimerHazard subclass for hazard-specific timing: 1. HEVTimerBase - Handles basic interval timing with: - Tracking active state - Start time - Simple interval checking - Basic configuration 2. HEVTimerHazard - Extends HEVTimerBase for the specific hazard timing needs: - Adds next_tick_ for drift-free recurring events - Adds delay_ for initial grace periods - The specialized hazard_sequence() method --- props/hev.h | 131 +++++++++++++++++++++++++++++----------------------- 1 file changed, 73 insertions(+), 58 deletions(-) diff --git a/props/hev.h b/props/hev.h index c4f1d5fa5..634daff81 100644 --- a/props/hev.h +++ b/props/hev.h @@ -63,42 +63,62 @@ EFFECT(major); EFFECT(minor); EFFECT(morphine); -struct HEVTimer { +struct HEVTimerBase { uint32_t start_ = 0; - // next_tick_ ensures precise timing for damage events by storing the exact - // timestamp when the next damage should occur, preventing timing drift. - // Mainly used for hazard damage, controlled by HEV_HAZARD_DECREASE_MS. - uint32_t next_tick_ = 0; - - // Delay before applying damage. HEV_HAZARD_DELAY_MS - uint32_t delay_ = 0; - // Time between applying damage. HEV_HAZARD_DECREASE_MS uint32_t interval_ = 0; bool active_ = false; - void reset() { active_ = false; next_tick_ = 0; } + void reset() { active_ = false; } - void start() { active_ = true; start_ = next_tick_ = millis(); } + void start() { active_ = true; start_ = millis(); } - void configure(uint32_t interval, uint32_t delay_time = 0) { + void configure(uint32_t interval) { interval_ = interval; - delay_ = delay_time; } - // Returns true if timer is inactive or timeout exceeded + // Returns true if timer is inactive or timeout exceeded. bool check() { return !active_ || (millis() - start_ > interval_); } - // warning_period is a grace period. It's argument is HEV_HAZARD_DELAY_MS. - // tick_interval uses HEV_HAZARD_DECREASE_MS as an argument. Currently 1000ms. + // Returns true if timer is active and interval has elapsed. + bool ready() { + return active_ && ((millis() - start_) >= interval_); + } +}; + +struct HEVTimerHazard : public HEVTimerBase { + // next_tick_ ensures precise timing for damage events by storing the exact + // timestamp when the next damage should occur, preventing timing drift. + // Mainly used for hazard damage, controlled by HEV_HAZARD_DECREASE_MS. + uint32_t next_tick_ = 0; + // Delay before applying damage. HEV_HAZARD_DELAY_MS + uint32_t delay_ = 0; + + void reset() { + HEVTimerBase::reset(); + next_tick_ = 0; + } + + void start() { + HEVTimerBase::start(); + next_tick_ = start_; + } + + void configure(uint32_t interval, uint32_t delay_time = 0) { + HEVTimerBase::configure(interval); + delay_ = delay_time; + } + + // delay_ is a grace period. It's argument is HEV_HAZARD_DELAY_MS. + // interval_ uses HEV_HAZARD_DECREASE_MS as an argument. Currently 1000ms. // // The hazard sequence method returns true when: // - The timer is active (active_ is true) - // - The warning period has elapsed (now - start_ >= warning_period) + // - The grace period has elapsed (now - start_ >= delay_) // - It's time for the next damage tick (now >= next_tick_) // // It returns false when: @@ -112,33 +132,28 @@ struct HEVTimer { return (now - start_ >= delay_) && ((now >= next_tick_) ? (next_tick_ = now + interval_, true) : false); } - - // Returns true if timer is active and interval has elapsed - bool ready() { - return active_ && ((millis() - start_) >= interval_); - } }; class Hev : public PROP_INHERIT_PREFIX PropBase { public: - HEVTimer clash_timer_; - HEVTimer post_death_cooldown_timer_; - HEVTimer random_event_timer_; - HEVTimer hazard_delay_timer_; - HEVTimer hazard_damage_timer_; - HEVTimer health_increase_timer_; - HEVTimer armor_increase_timer_; + HEVTimerBase timer_clash_; + HEVTimerBase timer_post_death_cooldown_; + HEVTimerBase timer_random_event_; + HEVTimerBase timer_hazard_damage_; + HEVTimerBase timer_health_increase_; + HEVTimerBase timer_armor_increase_; + HEVTimerHazard timer_hazard_delay_; Hev() : PropBase() { // Configure all timers with their respective timings from hev_config.h // Clash timer uses clash_timeout_ from PropBase for debounce - clash_timer_.configure(this->clash_timeout_); - post_death_cooldown_timer_.configure(HEV_POST_DEATH_COOLDOWN_MS); - random_event_timer_.configure(HEV_RANDOM_EVENT_INTERVAL_MS); - hazard_delay_timer_.configure(HEV_HAZARD_DECREASE_MS, HEV_HAZARD_DELAY_MS); - health_increase_timer_.configure(HEV_HEALTH_INCREASE_MS); - armor_increase_timer_.configure(HEV_ARMOR_INCREASE_MS); + timer_clash_.configure(this->clash_timeout_); + timer_post_death_cooldown_.configure(HEV_POST_DEATH_COOLDOWN_MS); + timer_random_event_.configure(HEV_RANDOM_EVENT_INTERVAL_MS); + timer_hazard_delay_.configure(HEV_HAZARD_DECREASE_MS, HEV_HAZARD_DELAY_MS); + timer_health_increase_.configure(HEV_HEALTH_INCREASE_MS); + timer_armor_increase_.configure(HEV_ARMOR_INCREASE_MS); } const char* name() override { return "Hev"; } @@ -219,7 +234,7 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { // - Simple but limited to basic cooldown functionality. // // 2. HEV uses object-oriented HEVTimer system: - // - clash_timer_ encapsulates timer state (active flag, start time). + // - timer_clash_ encapsulates timer state (active flag, start time). // - clash_timeout_ provides the duration value. // - Enables more complex patterns like sequences and ready-state checking. // @@ -229,7 +244,7 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { // - Provides clear separation between "is a timer running?" and "how long should it run?". // - Allows more complex timing patterns needed for authentic HEV behavior. // - Supports future extensions like variable cooldowns based on hit type. - if (health_ == 0 || (clash_timer_.active_ && !clash_timer_.check())) { + if (health_ == 0 || (timer_clash_.active_ && !timer_clash_.check())) { return; } @@ -252,7 +267,7 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { // Apply Damage and restart clash timer. DoDamage(damage, true); - clash_timer_.start(); + timer_clash_.start(); } // Swings do nothing! @@ -263,18 +278,18 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { // Random Hazards void CheckRandomEvent() { // Skip Hazard check if dead or during post-death cooldown - if (health_ == 0 || !post_death_cooldown_timer_.check()) { + if (health_ == 0 || !timer_post_death_cooldown_.check()) { return; } // Initialize timer. Stops immediate Hazard at boot - if (!random_event_timer_.active_) { - random_event_timer_.start(); + if (!timer_random_event_.active_) { + timer_random_event_.start(); return; } // Check for new Hazard if timer expired and no current Hazard - if (random_event_timer_.check() && current_hazard_ == HAZARD_NONE) { + if (timer_random_event_.check() && current_hazard_ == HAZARD_NONE) { // Roll for Random Hazard if (random(100) < HEV_RANDOM_HAZARD_CHANCE) { @@ -289,18 +304,18 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { void HazardDecrease() { // Reset timer when hazard is cleared if (current_hazard_ == HAZARD_NONE) { - hazard_delay_timer_.reset(); + timer_hazard_delay_.reset(); return; } // Start sequence if not running - if (!hazard_delay_timer_.active_) { - hazard_delay_timer_.start(); + if (!timer_hazard_delay_.active_) { + timer_hazard_delay_.start(); return; } // Check sequence and apply damage - if (hazard_delay_timer_.hazard_sequence()) { + if (timer_hazard_delay_.hazard_sequence()) { if (armor_ > 0) { armor_--; SaberBase::DoEffect(EFFECT_STUN, 0.0, 1); @@ -315,7 +330,7 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { // Clear hazard on death if (health_ == 0) { current_hazard_ = HAZARD_NONE; - hazard_delay_timer_.reset(); + timer_hazard_delay_.reset(); SaberBase::DoEffect(EFFECT_ALT_SOUND, 0.0, current_hazard_); } } @@ -329,16 +344,16 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { return; } - if (!SaberBase::Lockup() || !health_increase_timer_.ready()) { + if (!SaberBase::Lockup() || !timer_health_increase_.ready()) { return; } if (health_ == 0) { - post_death_cooldown_timer_.start(); + timer_post_death_cooldown_.start(); } health_++; - health_increase_timer_.start(); + timer_health_increase_.start(); PVLOG_NORMAL << "Health: " << health_ << "\n"; } @@ -350,12 +365,12 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { return; } - if (!SaberBase::Lockup() || !armor_increase_timer_.ready()) { + if (!SaberBase::Lockup() || !timer_armor_increase_.ready()) { return; } armor_++; - armor_increase_timer_.start(); + timer_armor_increase_.start(); PVLOG_NORMAL << "Armor: " << armor_ << "\n"; } @@ -388,8 +403,8 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { if (current_hazard_) { current_hazard_ = HAZARD_NONE; SaberBase::DoEffect(EFFECT_ALT_SOUND, 0.0, current_hazard_); - random_event_timer_.reset(); - random_event_timer_.start(); + timer_random_event_.reset(); + timer_random_event_.start(); return true; } // Play a no-hazard sound ? @@ -422,7 +437,7 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { if (!SaberBase::Lockup()) { SaberBase::SetLockup(SaberBase::LOCKUP_NORMAL); SaberBase::DoBeginLockup(); - health_increase_timer_.start(); + timer_health_increase_.start(); return true; } break; @@ -433,7 +448,7 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { if (SaberBase::Lockup()) { SaberBase::DoEndLockup(); SaberBase::SetLockup(SaberBase::LOCKUP_NONE); - health_increase_timer_.reset(); + timer_health_increase_.reset(); return true; } break; @@ -443,7 +458,7 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { if (!SaberBase::Lockup()) { SaberBase::SetLockup(SaberBase::LOCKUP_LIGHTNING_BLOCK); SaberBase::DoBeginLockup(); - armor_increase_timer_.start(); + timer_armor_increase_.start(); return true; } break; @@ -454,7 +469,7 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { if (SaberBase::Lockup()) { SaberBase::DoEndLockup(); SaberBase::SetLockup(SaberBase::LOCKUP_NONE); - armor_increase_timer_.reset(); + timer_armor_increase_.reset(); return true; } break; From 949a8162dc7f1008b423b4cf2eeb32c930b9c1d1 Mon Sep 17 00:00:00 2001 From: jimsworld Date: Wed, 2 Jul 2025 15:18:28 +0100 Subject: [PATCH 23/37] Removed 3rd argument from DoEffect calls: - This fixed the persistent stun02.wav issue used by hazards. - Also removed comment bloat in Clash function. --- props/hev.h | 46 ++++++++++++---------------------------------- 1 file changed, 12 insertions(+), 34 deletions(-) diff --git a/props/hev.h b/props/hev.h index 634daff81..f04f1a1e1 100644 --- a/props/hev.h +++ b/props/hev.h @@ -21,7 +21,7 @@ // armor_compromised.wav - for when armor falls to 0 // boot.wav - Bootup welcome message // death.wav - for when health is 0 -// clash**.wav - physical clash sounds +// clsh**.wav - physical clash sounds // health00.wav to health100.wav - for health alert sounds // armor00.wav to armor100.wav - for armor readout sounds // @@ -179,14 +179,16 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { if (armor_ >= damage) { armor_ -= ceilf(damage * 0.80 / 2); health_ -= ceilf(damage * 0.20); - } else if (armor_ > 0) { + } + else if (armor_ > 0) { int excess_physical = damage - armor_; health_ -= excess_physical; // Make armor compromised sound play before setting armor to 0 - SaberBase::DoEffect(EFFECT_USER2, 0.0, 0); + SaberBase::DoEffect(EFFECT_USER2, 0.0); PVLOG_NORMAL << "Armor Compromised!\n"; armor_ = 0; - } else { + } + else { health_ -= damage; } @@ -195,18 +197,18 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { if (armor_ < 0) armor_ = 0; // Damage - if (!quiet) SaberBase::DoEffect(EFFECT_STUN, 0.0, damage); + if (!quiet) SaberBase::DoEffect(EFFECT_STUN, 0.0); // Death Sound if (health_ == 0) { - SaberBase::DoEffect(EFFECT_EMPTY, 0.0, 100); + SaberBase::DoEffect(EFFECT_EMPTY, 0.0); return; } // Health Alert - only plays when health enters a new multiple of 10 int new_tens = health_ / 10; if (tens != new_tens) { - SaberBase::DoEffect(EFFECT_USER1, 0.0, 0); + SaberBase::DoEffect(EFFECT_USER1, 0.0); } // Print Damage, Health and Armor @@ -225,47 +227,23 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { // Clashes void Clash(bool stab, float strength) override { // Don't process clashes if dead or during cooldown. - // HEV Suit clash detection uses a fundamentally different approach than PropBase. - // The two work together: timer tracks state, timeout defines duration. - // - // 1. PropBase uses direct timestamp comparison: - // - Stores last_clash_ timestamp and clash_timeout_ value. - // - Compares (millis() - last_clash_ < clash_timeout_) on each check. - // - Simple but limited to basic cooldown functionality. - // - // 2. HEV uses object-oriented HEVTimer system: - // - timer_clash_ encapsulates timer state (active flag, start time). - // - clash_timeout_ provides the duration value. - // - Enables more complex patterns like sequences and ready-state checking. - // - // This is advantageous because: - // - HEV's sophisticated hit categorization needs finer state control. - // - Consistent with other HEV timers (health_timer_, armor_timer_, etc.) - // - Provides clear separation between "is a timer running?" and "how long should it run?". - // - Allows more complex timing patterns needed for authentic HEV behavior. - // - Supports future extensions like variable cooldowns based on hit type. if (health_ == 0 || (timer_clash_.active_ && !timer_clash_.check())) { return; } - - // Forward Clash event. No Stabs! + PropBase::Clash(false, strength); - // Damage is based on strength, capped at 50 int damage = std::min((int)(strength * 4), 50); float v = (strength - GetCurrentClashThreshold()) / 3; - // Play Clash sounds based on strength SFX_clash.SelectFloat(v); SFX_clsh.SelectFloat(v); SFX_stab.SelectFloat(v); - // Play Armor Alarm if Damage is 30 or more if (damage >= 30) { hybrid_font.PlayPolyphonic(&SFX_armor_alarm); } - // Apply Damage and restart clash timer. DoDamage(damage, true); timer_clash_.start(); } @@ -318,9 +296,9 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { if (timer_hazard_delay_.hazard_sequence()) { if (armor_ > 0) { armor_--; - SaberBase::DoEffect(EFFECT_STUN, 0.0, 1); + SaberBase::DoEffect(EFFECT_STUN, 0.0); if (armor_ == 0) { - SaberBase::DoEffect(EFFECT_USER2, 0.0, 0); + SaberBase::DoEffect(EFFECT_USER2, 0.0); PVLOG_NORMAL << "Armor Compromised!\n"; } } else { From 764973063cbc946807f7ad3583ebfad1e8f6f977 Mon Sep 17 00:00:00 2001 From: jimsworld Date: Mon, 7 Jul 2025 15:09:04 +0100 Subject: [PATCH 24/37] Updated tweaks and changes to hev.h --- props/hev.h | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/props/hev.h b/props/hev.h index f04f1a1e1..e05ce7d9a 100644 --- a/props/hev.h +++ b/props/hev.h @@ -49,6 +49,8 @@ #ifndef PROPS_HEV_H #define PROPS_HEV_H #define PROP_TYPE Hev +#define LOCKUP_HEALING SaberBase::LOCKUP_NORMAL +#define LOCKUP_FILL_ARMOR SaberBase::LOCKUP_LIGHTNING_BLOCK #include "prop_base.h" #include @@ -66,7 +68,6 @@ EFFECT(morphine); struct HEVTimerBase { uint32_t start_ = 0; - // Time between applying damage. HEV_HAZARD_DECREASE_MS uint32_t interval_ = 0; bool active_ = false; @@ -84,10 +85,13 @@ struct HEVTimerBase { return !active_ || (millis() - start_ > interval_); } - // Returns true if timer is active and interval has elapsed. bool ready() { return active_ && ((millis() - start_) >= interval_); } + + bool running() const { + return active_ && (millis() - start_) <= interval_; + } }; struct HEVTimerHazard : public HEVTimerBase { @@ -100,12 +104,12 @@ struct HEVTimerHazard : public HEVTimerBase { void reset() { HEVTimerBase::reset(); - next_tick_ = 0; + next_tick_ = 0; } void start() { HEVTimerBase::start(); - next_tick_ = start_; + next_tick_ = start_; } void configure(uint32_t interval, uint32_t delay_time = 0) { @@ -316,15 +320,15 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { // Increase health (Hold AUX). void IncreaseHealth() { + if (SaberBase::Lockup() != LOCKUP_HEALING) return; + if (health_ >= 100) { SaberBase::DoEndLockup(); SaberBase::SetLockup(SaberBase::LOCKUP_NONE); return; } - if (!SaberBase::Lockup() || !timer_health_increase_.ready()) { - return; - } + if (timer_health_increase_.running()) return; if (health_ == 0) { timer_post_death_cooldown_.start(); @@ -337,15 +341,15 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { // Increase armor (Hold POWER). void IncreaseArmor() { + if (SaberBase::Lockup() != LOCKUP_FILL_ARMOR) return; + if (armor_ >= 100) { SaberBase::DoEndLockup(); SaberBase::SetLockup(SaberBase::LOCKUP_NONE); return; } - if (!SaberBase::Lockup() || !timer_armor_increase_.ready()) { - return; - } + if (timer_armor_increase_.running()) return; armor_++; timer_armor_increase_.start(); @@ -356,11 +360,8 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { void Loop() override { CheckRandomEvent(); HazardDecrease(); - if (SaberBase::Lockup() == SaberBase::LOCKUP_NORMAL) { - IncreaseHealth(); - } else if (SaberBase::Lockup() == SaberBase::LOCKUP_LIGHTNING_BLOCK) { - IncreaseArmor(); - } + IncreaseHealth(); + IncreaseArmor(); PropBase::Loop(); } From 82098b90a7fb03ce67cd6b06939d6a22d2485dca Mon Sep 17 00:00:00 2001 From: jimsworld Date: Tue, 15 Jul 2025 16:04:38 +0100 Subject: [PATCH 25/37] Submitting changes so far, to work away from home. --- props/hev.h | 81 +++++++++++++++-------------------------------------- 1 file changed, 23 insertions(+), 58 deletions(-) diff --git a/props/hev.h b/props/hev.h index e05ce7d9a..e7cc07888 100644 --- a/props/hev.h +++ b/props/hev.h @@ -57,6 +57,7 @@ EFFECT(health); EFFECT(armor); +EFFECT(hazard); EFFECT(stun); EFFECT(death); EFFECT(armor_alarm); @@ -76,65 +77,19 @@ struct HEVTimerBase { void start() { active_ = true; start_ = millis(); } - void configure(uint32_t interval) { - interval_ = interval; - } + void configure(uint32_t interval) { interval_ = interval; } // Returns true if timer is inactive or timeout exceeded. bool check() { return !active_ || (millis() - start_ > interval_); } - bool ready() { - return active_ && ((millis() - start_) >= interval_); - } - bool running() const { return active_ && (millis() - start_) <= interval_; } -}; - -struct HEVTimerHazard : public HEVTimerBase { - // next_tick_ ensures precise timing for damage events by storing the exact - // timestamp when the next damage should occur, preventing timing drift. - // Mainly used for hazard damage, controlled by HEV_HAZARD_DECREASE_MS. - uint32_t next_tick_ = 0; - // Delay before applying damage. HEV_HAZARD_DELAY_MS - uint32_t delay_ = 0; - - void reset() { - HEVTimerBase::reset(); - next_tick_ = 0; - } - - void start() { - HEVTimerBase::start(); - next_tick_ = start_; - } - - void configure(uint32_t interval, uint32_t delay_time = 0) { - HEVTimerBase::configure(interval); - delay_ = delay_time; - } - // delay_ is a grace period. It's argument is HEV_HAZARD_DELAY_MS. - // interval_ uses HEV_HAZARD_DECREASE_MS as an argument. Currently 1000ms. - // - // The hazard sequence method returns true when: - // - The timer is active (active_ is true) - // - The grace period has elapsed (now - start_ >= delay_) - // - It's time for the next damage tick (now >= next_tick_) - // - // It returns false when: - // - The timer is not active (active_ is false) - // - The warning period hasn't elapsed yet - // - It's not time for the next damage tick - bool hazard_sequence() { - if (!active_) return false; - uint32_t now = millis(); - - return (now - start_ >= delay_) && - ((now >= next_tick_) ? (next_tick_ = now + interval_, true) : false); + bool done() { + return active_ && (millis() - start_) >= interval_; } }; @@ -144,10 +99,10 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { HEVTimerBase timer_clash_; HEVTimerBase timer_post_death_cooldown_; HEVTimerBase timer_random_event_; + HEVTimerBase timer_hazard_delay_; HEVTimerBase timer_hazard_damage_; HEVTimerBase timer_health_increase_; HEVTimerBase timer_armor_increase_; - HEVTimerHazard timer_hazard_delay_; Hev() : PropBase() { // Configure all timers with their respective timings from hev_config.h @@ -155,7 +110,7 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { timer_clash_.configure(this->clash_timeout_); timer_post_death_cooldown_.configure(HEV_POST_DEATH_COOLDOWN_MS); timer_random_event_.configure(HEV_RANDOM_EVENT_INTERVAL_MS); - timer_hazard_delay_.configure(HEV_HAZARD_DECREASE_MS, HEV_HAZARD_DELAY_MS); + timer_hazard_delay_.configure(HEV_HAZARD_DELAY_MS); timer_health_increase_.configure(HEV_HEALTH_INCREASE_MS); timer_armor_increase_.configure(HEV_ARMOR_INCREASE_MS); } @@ -292,12 +247,14 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { // Start sequence if not running if (!timer_hazard_delay_.active_) { + timer_hazard_delay_.configure(HEV_HAZARD_DELAY_MS); + timer_hazard_delay_.configure(HEV_HAZARD_DECREASE_MS); timer_hazard_delay_.start(); return; } // Check sequence and apply damage - if (timer_hazard_delay_.hazard_sequence()) { + if (timer_hazard_delay_.done()) { if (armor_ > 0) { armor_--; SaberBase::DoEffect(EFFECT_STUN, 0.0); @@ -318,6 +275,13 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { } } + // Reset random event timer after revive. + void Revive() { + if (health_ == 0) { + timer_random_event_.reset(); + } + } + // Increase health (Hold AUX). void IncreaseHealth() { if (SaberBase::Lockup() != LOCKUP_HEALING) return; @@ -330,9 +294,7 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { if (timer_health_increase_.running()) return; - if (health_ == 0) { - timer_post_death_cooldown_.start(); - } + Revive(); health_++; timer_health_increase_.start(); @@ -414,7 +376,7 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { // Hold AUX to start healing case EVENTID(BUTTON_AUX, EVENT_HELD_MEDIUM, MODE_ON): if (!SaberBase::Lockup()) { - SaberBase::SetLockup(SaberBase::LOCKUP_NORMAL); + SaberBase::SetLockup(SaberBase::LOCKUP_HEALING); SaberBase::DoBeginLockup(); timer_health_increase_.start(); return true; @@ -435,7 +397,7 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { // Hold POWER to start recharging armor case EVENTID(BUTTON_POWER, EVENT_HELD_MEDIUM, MODE_ON): if (!SaberBase::Lockup()) { - SaberBase::SetLockup(SaberBase::LOCKUP_LIGHTNING_BLOCK); + SaberBase::SetLockup(SaberBase::LOCKUP_FILL_ARMOR); SaberBase::DoBeginLockup(); timer_armor_increase_.start(); return true; @@ -481,7 +443,10 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { // alongside death sound. However all pending sounds should be cleared. switch (effect) { default: return; - + case EFFECT_ALT_SOUND: + SOUNDQ->Play(SoundToPlay(&SFX_hazard)); + return; + // Random Hazard Sounds case EFFECT_STUN: hybrid_font.PlayCommon(&SFX_stun); From 53e0e37fbe7fb53f2c544a83d14a4d9ba5a68b05 Mon Sep 17 00:00:00 2001 From: jimsworld Date: Tue, 15 Jul 2025 19:08:16 +0100 Subject: [PATCH 26/37] Removed HEVTimerHazard: - This class is no longer used, as the hazard delay logic has been refactored into HazardDecrease(). --- props/hev.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/props/hev.h b/props/hev.h index e7cc07888..6d282f414 100644 --- a/props/hev.h +++ b/props/hev.h @@ -248,7 +248,6 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { // Start sequence if not running if (!timer_hazard_delay_.active_) { timer_hazard_delay_.configure(HEV_HAZARD_DELAY_MS); - timer_hazard_delay_.configure(HEV_HAZARD_DECREASE_MS); timer_hazard_delay_.start(); return; } @@ -271,7 +270,11 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { current_hazard_ = HAZARD_NONE; timer_hazard_delay_.reset(); SaberBase::DoEffect(EFFECT_ALT_SOUND, 0.0, current_hazard_); + return; } + + timer_hazard_delay_.configure(HEV_HAZARD_DECREASE_MS); + timer_hazard_delay_.start(); } } From 9060279ae75f7911bcabb11c587d35454230e02a Mon Sep 17 00:00:00 2001 From: jimsworld Date: Wed, 16 Jul 2025 14:52:16 +0100 Subject: [PATCH 27/37] Complete overhaul of top comment block: - Now provides a comprehensive overview of the HEV suit's functionality. --- props/hev.h | 280 +++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 233 insertions(+), 47 deletions(-) diff --git a/props/hev.h b/props/hev.h index 6d282f414..afd27cebd 100644 --- a/props/hev.h +++ b/props/hev.h @@ -1,50 +1,236 @@ -// HALF-LIFE - Hazardous Environment Suit Prop -// -// How to use: -// POWER button: -// Long-click: ON/OFF -// hold button to recharge armor -// double-click: toggle track -// triple-click: next preset -// AUX button: -// hold button to heal. -// single click: deactivate hazard -// double-click: armor readout -// triple-click: previous preset -// -// Sound files needed: -// in.wav, out.wav - for power on/off. Use flashlight sound -// bgnlb.wav, lb.wav, endlb.wave - recharge armor sound sequence -// bgnlock.wav, lock.wav, endlock.wav - heal sound sequence -// font.wav - next/previous preset -// armor_alarm.wav - for when Clash damage is greater than or equal to 30 -// armor_compromised.wav - for when armor falls to 0 -// boot.wav - Bootup welcome message -// death.wav - for when health is 0 -// clsh**.wav - physical clash sounds -// health00.wav to health100.wav - for health alert sounds -// armor00.wav to armor100.wav - for armor readout sounds -// -// Random Hazard sounds: -// These are played using the altchng method. So create 7 directories: -// alt00, alt01, alt02, alt03, alt04, alt05, alt06. -// Each directory should contain two folders, altchng and stun. -// In alt01 to alt06, place the hazard detection voice lines in the altchng dir -// (bio, blood toxins, chemical, radiation, shock, fire). -// In alt01 to alt06, place appropriate sound effects in the stun dir -// (spark, burn, geiger counter). -// In each stun folder, use the same amount of files as the other stun folders. -// So if you use 4 stun sounds, use 4 in all of them. -// In each altchng folder, use the same amount of files as the other altchng folders. -// I use 2 in all of them. -// One for the main voice line, the other is the same, -// but with a follow up health notification for variety. -// For alt00, have the same amount of files as the other alt** folders -// but leave the files blank. -// -// Notes: -// Clash will cause varying damage based on strength -// Damage and Hazards are only active when prop is ON. +//=====================================================================// +// H A L F - L I F E // +// ───────────────────── // +// Hazardous Environment Suit Diagnostics // +// H.E.V. Mk V - Configuration Protocol // +//---------------------------------------------------------------------// +// PROPERTY OF BLACK MESA // +//=====================================================================// + +//=====================================================================// +// // +// H.E.V. SUIT - USAGE GUIDE // +// ───────────────────────── // +// // +// - POWER Button: // +// - Long-click - ON/OFF // +// - Hold - Recharge Armor // +// - Double-click - Toggle track // +// - Triple-click - Next preset // +// - AUX Button: // +// - Hold - Recharge Health // +// - Single-click - Deactivate Hazard // +// - Double-click - Armor Readout // +// - Triple-click - Previous preset // +// // +//---------------------------------------------------------------------// +// PHYSICAL DAMAGE & HAZARD DAMAGE LOGIC // +//---------------------------------------------------------------------// +// // +// - The HEV suit has two main resources: Health and Armor. // +// - Both active only while the suit is ON. // +// - Both have a maximum value of 100. // +// - Random Hazards: // +// - Active only while the suit is ON. // +// - Triggered at intervals with a chance-based system. // +// - Always damages Armor first, then Health. // +// - When Armor is depleted: // +// - Hazards begins directly damaging Health. // +// - Clashes deal full damage to Health. // +// - When Armor is active: // +// - Clashes (physical impacts) are negated: // +// ▪ 80% of the impact is absorbed by Armor. // +// ▪ 20% is applied to Health. // +// - Clash damage is based on force of impact: // +// - Cannot exceed 50 total damage. // +// // +//=====================================================================// + +//=====================================================================// +// // +// AUDIO SYSTEM CATEGORIES // +// ───────────────────────── // +// // +//---------------------- HEV VOICE LINES -----------------------------// +// // +// - armor**.wav - Armor Readouts // +// - health**.wav - Health Alerts // +// - armor_compromised.wav - Plays when armor drops to 0 // +// - boot.wav - HEV welcome message (shortened) // +// - boot_long.wav - HEV welcome message (original) // +// - hazard**.wav - Hazard Alerts // +// - morphine.wav - Plays after a Major Clash // +// // +//-------------------- HEV UI SYSTEM SOUNDS ---------------------------// +// // +// - armor_alarm.wav - Short alert // plays when damage ≥ 30 // +// - battery.wav - Battery SFX // armor pickup // +// - bgnlb.wav - Start armor charge // +// - bgnlock.wav - Start health charge // +// - death.wav - Heart monitor flatline // health = 0 // +// - endlb.wav - End armor charge // +// - endlock.wav - End health charge // +// - font.wav - Weapon select SFX (placeholder) // +// - fuzz**.wav - Subtle alert // precedes armor**.wav // +// - in.wav - Torch ON // +// - lb.wav - Looping armor charge // +// - lock.wav - Looping health charge // +// - medkit.wav - Medkit SFX // health pickup // +// - out.wav - Torch OFF // +// - warning.wav - No Armor SFX // Armor = 0 // +// // +//-------------------- ENVIRONMENTAL EFFECTS --------------------------// +// // +// - stun**.wav - Hazard SFX // alt001–alt006 = hazards // +// - clsh**.wav - Physical impacts, slashes, collisions // +// // +//----------------------------- MISC ----------------------------------// +// // +// - blank.wav - Placeholder inside altchng/ folder // +// // +//------------------ Random Hazard Sounds Setup -----------------------// +// // +// ▪ Create 7 directories: alt00 through alt06 // +// ▪ Each alt**/ folder must contain: // +// ├── hazard00.wav // +// └── hazard01.wav // +// → These are the Hazard Alert (HEV VOICE LINES) for that folder.// +// // +// ▪ Inside each alt**/ folder, create an altchng/ folder: // +// ▪ Inside each altchng/ folder: // +// - Include one file: blank.wav // +// → A silent placeholder used to satisfy the folder structure. // +// ▪ Inside each alt**/ folder, create a stun/ folder: // +// ▪ Inside each stun/ folder: // +// - Include the ENVIRONMENTAL EFFECTS that match // +// the Hazard type (e.g., spark, burn, Geiger counter) // +// // +// ▪ All alt**/ folders (alt00–alt06) must have the **same number** of // +// WAVs inside including **all** sub folders. // +// // +//=====================================================================// + +//=====================================================================// +// TIMER CONFIGURATION // +// ───────────────────────── // +// // +// - timer_clash_ - Debounce timer for physical Clashes. // +// Prevents false Clashes. // +// - timer_post_death_cooldown_ - Cooldown after user revives. // +// Blocks Hazards until timer is done. // +// - timer_random_event_ - Interval timer for Random Hazards. // +// Controls how often Hazards can occur.// +// - timer_hazard_delay_ - Delay between Hazard damage ticks. // +// Manages Hazard damage over time. // +// - timer_health_increase_ - Interval for Health recharge. // +// Controls healing rate. // +// - timer_armor_increase_ - Interval for Armor recharge. // +// Controls Armor recharge rate. // +//=====================================================================// + +//=====================================================================// +// // +// BEHAVIOR BREAKDOWN // +// ───────────────────────── // +// // +//------------------------- Health Alerts -----------------------------// +// // +// ▪ When Health reaches the below thresholds, the suit will // +// randomly say different (HEV VOICE LINES): // +// - ≤= 50: "Seek Medical Attention" (50% chance) // +// - ≤= 30: "Vital Signs Critical" (60% chance) // +// - ≤= 6: "User Death Imminent" (100% chance) // +// // +// ▪ The chances have been configured directly in the sound files, // +// with some wavs as blanks to ensure. // +// ▪ When Health drops to a lower 10th decimal (e.g., from 43 → 36), // +// a Health Alert (health**.wav) will play. // +// ▪ There are no Health Alerts when Health is above 50. // +// ▪ When Health is 0, death.wav will trigger. // +// // +//------------------------- Armor Alerts ------------------------------// +// // +// ▪ When Clash damage ≥ 30: // +// - armor_alarm.wav will play. (HEV UI SOUND) // +// ▪ If Armor reaches 0: // +// - 50% chance to play armor_compromised.wav. (HEV VOICE LINE) // +// // +//-------------------- Physical Clash System --------------------------// +// // +// ▪ Each Clash deals between 1–50 damage based on impact strength. // +// ▪ Currently 8 (ENVIRONMENTAL) Clash sounds available (clsh**.wav): // +// → Divided by Clash Impact and Injury type. // +// ▪ Impact categorised by damage value: // +// - < 25: Minor Clash sounds // +// - ≥ 25: Major Clash sounds // +// ▪ Injury subcategories for both Impact types: // +// - Lacerations // +// - Fractures // +// // +//-------------------- Clash Sound Index Table ------------------------// +// // +// ▪ Minor Clash sounds: // +// - clsh00.wav → Laceration (Low) // +// - clsh01.wav → Laceration (Med) // +// - clsh02.wav → Laceration (High) // +// - clsh03.wav → Fracture (Minor) // +// ▪ Major Clash sounds: // +// - clsh04.wav → Laceration (Low) // +// - clsh05.wav → Laceration (Med) // +// - clsh06.wav → Laceration (High) // +// - clsh07.wav → Fracture (Major) // +// // +// ▪ 44% chance to trigger a (HEV VOICE LINE): // +// - e.g. ”Major Fracture Detected!" // +// // +//-------------------- Morphine Auto-Injection ------------------------// +// // +// ▪ Only if Major Clash (HEV VOICE LINE) plays: // +// → 40% chance to follow-up with another (HEV VOICE LINE): // +// - morphine.wav // +// - This line will be on a cooldown, so as not to be spammed. // +// // +//------------------------ Hazard System ------------------------------// +// // +// ▪ When triggered, randomly selects one of 6 Hazards: // +// → Heat, Shock, Bio, Blood Toxins, Chemical, Radiation. // +// ▪ If Bio, Blood Toxins, Chem, or Radiation triggered, // +// their damage lingers over time. // +// - These 4 Hazards have a 50% chance to let the user know // +// that damage is lingering via a (HEV VOICE LINE). // +// - This has been configured directly in the sound font files. // +// ▪ Heat and Shock do not have lingering damage and should be // +// cleared immediately by the user by clicking AUX. // +// // +//----------------- Quick Healing & Recharging ------------------------// +// // +// ▪ Two recovery options are available, which replicate the small // +// health and armor pickups in-game, the Medkits and Batteries. // +// - Medkit → Immediately recovers 15 Health. // +// - Battery → Immediately recovers 15 Armor. // +// ▪ Both have their own distinct (HEV UI SOUND): // +// - Medkit → medkit.wav // +// - Battery → battery.wav // +// ▪ Medkit sound feedback: // +// - Plays medkit.wav on trigger. // +// - endlock.wav plays if already at max Health. // +// ▪ Battery sound feedback: // +// - Plays battery.wav on trigger. // +// - endlb.wav plays if already at max Armor. // +// - Plays Armor Readout function, and rounds the value to // +// the nearest multiple of 5. // +// // +//----------------------- Armor Readout -------------------------------// +// // +// ▪ Armor Readout is used to hear the current Armor value. // +// ▪ Triggered by double-clicking AUX. // +// ▪ Activates a sequence of (HEV UI SOUND) then (HEV VOICE LINE): // +// → Subtle alert fuzz**.wav → Armor Readout armor**.wav. // +// ▪ If Armor = 0: // +// - Plays warning.wav (HEV UI SOUND) // +// - No (HEV VOICE LINE) // +// // +//=====================================================================// #ifndef PROPS_HEV_H #define PROPS_HEV_H From 18b4fa91c0a1ad0ed18e016d6f2e7d03f3be3261 Mon Sep 17 00:00:00 2001 From: jimsworld Date: Thu, 17 Jul 2025 11:24:39 +0100 Subject: [PATCH 28/37] Comment ammendments. --- props/hev.h | 176 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 128 insertions(+), 48 deletions(-) diff --git a/props/hev.h b/props/hev.h index afd27cebd..7d179dad6 100644 --- a/props/hev.h +++ b/props/hev.h @@ -5,12 +5,15 @@ // H.E.V. Mk V - Configuration Protocol // //---------------------------------------------------------------------// // PROPERTY OF BLACK MESA // +//---------------------------------------------------------------------// +// Created by James Nash with fundamental // +// support and contributions of Fredrik Hubinette // //=====================================================================// //=====================================================================// -// // // H.E.V. SUIT - USAGE GUIDE // // ───────────────────────── // +// --2 BUTTON SETUP-- // // // // - POWER Button: // // - Long-click - ON/OFF // @@ -28,14 +31,14 @@ //---------------------------------------------------------------------// // // // - The HEV suit has two main resources: Health and Armor. // -// - Both active only while the suit is ON. // // - Both have a maximum value of 100. // +// - Both active only while the suit is ON. // // - Random Hazards: // // - Active only while the suit is ON. // -// - Triggered at intervals with a chance-based system. // // - Always damages Armor first, then Health. // +// - Triggered at intervals with a chance-based system. // // - When Armor is depleted: // -// - Hazards begins directly damaging Health. // +// - Hazards begin directly damaging Health. // // - Clashes deal full damage to Health. // // - When Armor is active: // // - Clashes (physical impacts) are negated: // @@ -47,7 +50,6 @@ //=====================================================================// //=====================================================================// -// // // AUDIO SYSTEM CATEGORIES // // ───────────────────────── // // // @@ -90,7 +92,7 @@ // // //------------------ Random Hazard Sounds Setup -----------------------// // // -// ▪ Create 7 directories: alt00 through alt06 // +// ▪ Create 7 directories in root of sound font: alt00 through alt06 // // ▪ Each alt**/ folder must contain: // // ├── hazard00.wav // // └── hazard01.wav // @@ -111,42 +113,58 @@ //=====================================================================// //=====================================================================// -// TIMER CONFIGURATION // -// ───────────────────────── // -// // -// - timer_clash_ - Debounce timer for physical Clashes. // -// Prevents false Clashes. // -// - timer_post_death_cooldown_ - Cooldown after user revives. // -// Blocks Hazards until timer is done. // -// - timer_random_event_ - Interval timer for Random Hazards. // -// Controls how often Hazards can occur.// -// - timer_hazard_delay_ - Delay between Hazard damage ticks. // -// Manages Hazard damage over time. // -// - timer_health_increase_ - Interval for Health recharge. // -// Controls healing rate. // -// - timer_armor_increase_ - Interval for Armor recharge. // -// Controls Armor recharge rate. // -//=====================================================================// - -//=====================================================================// -// // // BEHAVIOR BREAKDOWN // // ───────────────────────── // // // //------------------------- Health Alerts -----------------------------// // // -// ▪ When Health reaches the below thresholds, the suit will // -// randomly say different (HEV VOICE LINES): // -// - ≤= 50: "Seek Medical Attention" (50% chance) // -// - ≤= 30: "Vital Signs Critical" (60% chance) // -// - ≤= 6: "User Death Imminent" (100% chance) // -// // -// ▪ The chances have been configured directly in the sound files, // -// with some wavs as blanks to ensure. // // ▪ When Health drops to a lower 10th decimal (e.g., from 43 → 36), // -// a Health Alert (health**.wav) will play. // -// ▪ There are no Health Alerts when Health is above 50. // -// ▪ When Health is 0, death.wav will trigger. // +// a Health Alert (health**.wav) will play, (HEV VOICE LINE). // +// // +// ▪ The suit will say state different Health Alerts, depending on // +// what health dropped down to. Below are the health thresholds: // +// // +// - ≤ 10: "User Death Imminent" - Range: health01 - health10 // +// - ≤ 30: "Vital Signs Critical" - Range: health11 - health30 // +// - ≤ 50: "Seek Medical Attention" - Range: health31 - health50 // +// // +// ▪ Not all wavs have audio, therefore some wavs are blank, which // +// creates an **element of chance** for Health Alerts to play. // +// This has been configured directly in the health folder in the // +// sound font. // +// // +// ▪ The following table shows which health** files in the font // +// have actual voice lines (✓ = has audio, blank = no audio). // +// +// 1–10: "User Death Imminent" +// health01 ✓ health06 ✓ +// health02 ✓ health07 +// health03 ✓ health08 ✓ +// health04 ✓ health09 ✓ +// health05 ✓ health10 +// +// 11–30: "Vital Signs Critical" +// health11 ✓ health18 ✓ health25 +// health12 ✓ health19 health26 ✓ +// health13 health20 ✓ health27 ✓ +// health14 ✓ health21 ✓ health28 +// health15 ✓ health22 health29 ✓ +// health16 health23 ✓ health30 ✓ +// health17 ✓ health24 +// +// 31–50: "Seek Medical Attention" +// health31 health38 ✓ health45 +// health32 ✓ health39 health46 ✓ +// health33 health40 ✓ health47 +// health34 ✓ health41 health48 ✓ +// health35 health42 ✓ health49 +// health36 ✓ health43 health50 ✓ +// health37 health44 ✓ +// +// ▪ There are no Health Alerts when Health is above 50. So, // +// health51 - health100 are blank. // +// // +// ▪ health00 is blank. When Health is 0, death.wav will play. // // // //------------------------- Armor Alerts ------------------------------// // // @@ -217,7 +235,7 @@ // ▪ Battery sound feedback: // // - Plays battery.wav on trigger. // // - endlb.wav plays if already at max Armor. // -// - Plays Armor Readout function, and rounds the value to // +// - Plays Armor Readout function, but rounds the value to // // the nearest multiple of 5. // // // //----------------------- Armor Readout -------------------------------// @@ -238,6 +256,53 @@ #define LOCKUP_HEALING SaberBase::LOCKUP_NORMAL #define LOCKUP_FILL_ARMOR SaberBase::LOCKUP_LIGHTNING_BLOCK +//=====================================================================// +// HEV SUIT TIMING DEFINES // +// ───────────────────────── // +// // +// Below are the HEV_ #defines which control the timings of how // +// often Hazards, Healing and Armor recharge work in the HEV suit. // +// All times are in (ms). Below values are the default. // +// To fine-tune the behaviour, adjust the values in hev_config.h // +// // +// HEV_RANDOM_EVENT_INTERVAL_MS // +// - How often (ms) to check for a Random Hazard. // +// Higher = less frequent checks. // +// // +// HEV_RANDOM_HAZARD_CHANCE // +// - Chance (0–100) for a Random Hazard to occur each check. // +// Higher = more frequent Hazards. // +// // +// HEV_HAZARD_DELAY_MS // +// - Delay (ms) before Hazard damage starts after checking and // +// triggering Hazard. Allows time for HEV Voice Line to finish. // +// // +// HEV_HAZARD_DECREASE_MS // +// - Time (ms) between each tick of Hazard damage. // +// Lower = faster damage-over-time. // +// // +// HEV_HAZARD_AFTER_REVIVE_MS // +// - Time (ms) after reviving before Hazards can happen again. // +// // +// HEV_HEALTH_INCREASE_MS // +// - Time (ms) between each Health recharge tick (hold AUX). // +// Default from 0 - 100 Health is 10 seconds. // +// Lower = faster healing. // +// // +// HEV_ARMOR_INCREASE_MS // +// - Time (ms) between each armor recharge tick (hold POWER). // +// Default from 0 - 100 Armor is 10 seconds. // +// Lower = faster recharge. // +//=====================================================================// + +#define HEV_RANDOM_EVENT_INTERVAL_MS 60000 +#define HEV_RANDOM_HAZARD_CHANCE 15 +#define HEV_HAZARD_DELAY_MS 6000 +#define HEV_HAZARD_DECREASE_MS 1000 +#define HEV_HAZARD_AFTER_REVIVE_MS 60000 +#define HEV_HEALTH_INCREASE_MS 100 +#define HEV_ARMOR_INCREASE_MS 100 + #include "prop_base.h" #include @@ -282,21 +347,36 @@ struct HEVTimerBase { class Hev : public PROP_INHERIT_PREFIX PropBase { public: + //=====================================================================// + // TIMER CONFIGURATION // + // ───────────────────────── // + // // + // - timer_clash_ - Uses clash_timeout from PropBase. // + // Debounce to prevent false Clashes. // + // - timer_random_event_ - Interval timer for Random Hazards. // + // Controls how often Hazards can occur.// + // - timer_hazard_delay_ - Delay inbetween event trigger and // + // Hazard dps. Gap for voice to finish. // + // - timer_hazard_after_revive_ - Cooldown after user revives. // + // Blocks Hazards until timer is done. // + // - timer_health_increase_ - Interval for Health recharge. // + // Controls healing rate. // + // - timer_armor_increase_ - Interval for Armor recharge. // + // Controls Armor recharge rate. // + //=====================================================================// + HEVTimerBase timer_clash_; - HEVTimerBase timer_post_death_cooldown_; HEVTimerBase timer_random_event_; HEVTimerBase timer_hazard_delay_; - HEVTimerBase timer_hazard_damage_; + HEVTimerBase timer_hazard_after_revive_; HEVTimerBase timer_health_increase_; HEVTimerBase timer_armor_increase_; Hev() : PropBase() { - // Configure all timers with their respective timings from hev_config.h - // Clash timer uses clash_timeout_ from PropBase for debounce timer_clash_.configure(this->clash_timeout_); - timer_post_death_cooldown_.configure(HEV_POST_DEATH_COOLDOWN_MS); timer_random_event_.configure(HEV_RANDOM_EVENT_INTERVAL_MS); timer_hazard_delay_.configure(HEV_HAZARD_DELAY_MS); + timer_hazard_after_revive_.configure(HEV_HAZARD_AFTER_REVIVE_MS); timer_health_increase_.configure(HEV_HEALTH_INCREASE_MS); timer_armor_increase_.configure(HEV_ARMOR_INCREASE_MS); } @@ -400,8 +480,8 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { // Random Hazards void CheckRandomEvent() { - // Skip Hazard check if dead or during post-death cooldown - if (health_ == 0 || !timer_post_death_cooldown_.check()) { + // Skip Hazard check if dead or during revive cooldown + if (health_ == 0 || !timer_hazard_after_revive_.check()) { return; } @@ -636,24 +716,24 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { SOUNDQ->Play(SoundToPlay(&SFX_hazard)); return; - // Random Hazard Sounds + // (ENVIRONMENTAL) Hazard SFX case EFFECT_STUN: hybrid_font.PlayCommon(&SFX_stun); return; - // Armor Compromised Sound + // (HEV VOICE LINE) Armor Compromised case EFFECT_USER2: SOUNDQ->Play(&SFX_armor_compromised); return; - // Health Alert + // (HEV VOICE LINE) Health Alert case EFFECT_USER1: if (health_ == 0) return; // Don't queue health sounds if dead SFX_health.SelectFloat(health_ / 100.0); SOUNDQ->Play(&SFX_health); return; - // Death Sound + // (HEV UI SOUND) Death Sound case EFFECT_EMPTY: if (health_ == 0) { SOUNDQ->clear_pending(); From 9d02aad8f32d60fa3f7171e16b036e04bd5ce92e Mon Sep 17 00:00:00 2001 From: jimsworld Date: Fri, 18 Jul 2025 14:42:53 +0100 Subject: [PATCH 29/37] Updated comments and catergorised EFFECTS - Made terminlogy more consistent. - Added new EFFECTS. - Updated top comment detailing how Minor and Major voice feedback works. --- props/hev.h | 67 +++++++++++++++++++++++++++++++++++------------------ 1 file changed, 44 insertions(+), 23 deletions(-) diff --git a/props/hev.h b/props/hev.h index 7d179dad6..199e80b03 100644 --- a/props/hev.h +++ b/props/hev.h @@ -62,8 +62,12 @@ // - boot_long.wav - HEV welcome message (original) // // - hazard**.wav - Hazard Alerts // // - morphine.wav - Plays after a Major Clash // +// - minor_laceration.wav - Plays minor lacerations detected quote // +// - minor_fracture.wav - Plays minor fractures detected quote // +// - major_laceration.wav - Plays major lacerations detected quote // +// - major_fracture.wav - Plays major fractures detected quote // // // -//-------------------- HEV UI SYSTEM SOUNDS ---------------------------// +//------------------------ HEV UI SOUNDS ------------------------------// // // // - armor_alarm.wav - Short alert // plays when damage ≥ 30 // // - battery.wav - Battery SFX // armor pickup // @@ -104,7 +108,7 @@ // → A silent placeholder used to satisfy the folder structure. // // ▪ Inside each alt**/ folder, create a stun/ folder: // // ▪ Inside each stun/ folder: // -// - Include the ENVIRONMENTAL EFFECTS that match // +// - Include the (ENVIRONMENTAL FX) that match // // the Hazard type (e.g., spark, burn, Geiger counter) // // // // ▪ All alt**/ folders (alt00–alt06) must have the **same number** of // @@ -166,17 +170,17 @@ // // // ▪ health00 is blank. When Health is 0, death.wav will play. // // // -//------------------------- Armor Alerts ------------------------------// +//-------------------------- Armor Alerts -----------------------------// // // // ▪ When Clash damage ≥ 30: // -// - armor_alarm.wav will play. (HEV UI SOUND) // +// - armor_alarm.wav will play. (HEV UI SOUNDS) // // ▪ If Armor reaches 0: // // - 50% chance to play armor_compromised.wav. (HEV VOICE LINE) // // // -//-------------------- Physical Clash System --------------------------// +//--------------------- Physical Clash System -------------------------// // // // ▪ Each Clash deals between 1–50 damage based on impact strength. // -// ▪ Currently 8 (ENVIRONMENTAL) Clash sounds available (clsh**.wav): // +// ▪ 8 (ENVIRONMENTAL FX) Clash sounds available (clsh**.wav): // // → Divided by Clash Impact and Injury type. // // ▪ Impact categorised by damage value: // // - < 25: Minor Clash sounds // @@ -198,15 +202,21 @@ // - clsh06.wav → Laceration (High) // // - clsh07.wav → Fracture (Major) // // // -// ▪ 44% chance to trigger a (HEV VOICE LINE): // -// - e.g. ”Major Fracture Detected!" // +//------------------- Clash Detected Voice Line -----------------------// +// // +// ▪ 44% chance to trigger Clash Detected wav (HEV VOICE LINE): // +// → minor_laceration.wav (Minor Laceration) // +// → minor_fracture.wav (Minor Fracture) // +// → major_laceration.wav (Major Laceration) // +// → major_fracture.wav (Major Fracture) // +// - e.g. ”Major Fracture Detected!" // // // //-------------------- Morphine Auto-Injection ------------------------// // // -// ▪ Only if Major Clash (HEV VOICE LINE) plays: // +// ▪ Only if major_laceration or major_fracture (HEV VOICE LINE) plays:// // → 40% chance to follow-up with another (HEV VOICE LINE): // -// - morphine.wav // -// - This line will be on a cooldown, so as not to be spammed. // +// → morphine.wav // +// - This line will be on a cooldown, so as not to be spammed. // // // //------------------------ Hazard System ------------------------------// // // @@ -226,7 +236,7 @@ // health and armor pickups in-game, the Medkits and Batteries. // // - Medkit → Immediately recovers 15 Health. // // - Battery → Immediately recovers 15 Armor. // -// ▪ Both have their own distinct (HEV UI SOUND): // +// ▪ Both have their own distinct (HEV UI SOUNDS): // // - Medkit → medkit.wav // // - Battery → battery.wav // // ▪ Medkit sound feedback: // @@ -242,10 +252,10 @@ // // // ▪ Armor Readout is used to hear the current Armor value. // // ▪ Triggered by double-clicking AUX. // -// ▪ Activates a sequence of (HEV UI SOUND) then (HEV VOICE LINE): // +// ▪ Activates a sequence of (HEV UI SOUNDS) then (HEV VOICE LINE): // // → Subtle alert fuzz**.wav → Armor Readout armor**.wav. // // ▪ If Armor = 0: // -// - Plays warning.wav (HEV UI SOUND) // +// - Plays warning.wav (HEV UI SOUNDS) // // - No (HEV VOICE LINE) // // // //=====================================================================// @@ -306,17 +316,28 @@ #include "prop_base.h" #include -EFFECT(health); +// HEV VOICE LINES EFFECT(armor); -EFFECT(hazard); -EFFECT(stun); -EFFECT(death); -EFFECT(armor_alarm); +EFFECT(health); EFFECT(armor_compromised); -EFFECT(major); -EFFECT(minor); +EFFECT(hazard); +EFFECT(minor_laceration); +EFFECT(minor_fracture); +EFFECT(major_laceration); +EFFECT(major_fracture); EFFECT(morphine); +// HEV UI SOUNDS +EFFECT(armor_alarm); +EFFECT(battery); +EFFECT(death); +EFFECT(fuzz); +EFFECT(medkit); +EFFECT(warning); + +// ENVIRONMENTAL EFFECTS +EFFECT(stun); + struct HEVTimerBase { uint32_t start_ = 0; @@ -716,7 +737,7 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { SOUNDQ->Play(SoundToPlay(&SFX_hazard)); return; - // (ENVIRONMENTAL) Hazard SFX + // (ENVIRONMENTAL FX) Hazard SFX case EFFECT_STUN: hybrid_font.PlayCommon(&SFX_stun); return; @@ -733,7 +754,7 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { SOUNDQ->Play(&SFX_health); return; - // (HEV UI SOUND) Death Sound + // (HEV UI SOUNDS) Death Sound case EFFECT_EMPTY: if (health_ == 0) { SOUNDQ->clear_pending(); From cce563cd7c0abb471d0e0f223dab7b50cd1602ac Mon Sep 17 00:00:00 2001 From: jimsworld Date: Mon, 21 Jul 2025 14:53:18 +0100 Subject: [PATCH 30/37] Added SB_Effect2 and preceding Armor Readout sfx --- props/hev.h | 46 +++++++++++++++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/props/hev.h b/props/hev.h index 199e80b03..2c0c23d0a 100644 --- a/props/hev.h +++ b/props/hev.h @@ -83,7 +83,7 @@ // - lock.wav - Looping health charge // // - medkit.wav - Medkit SFX // health pickup // // - out.wav - Torch OFF // -// - warning.wav - No Armor SFX // Armor = 0 // +// - armor00.wav - No Armor SFX // Armor = 0 // // // //-------------------- ENVIRONMENTAL EFFECTS --------------------------// // // @@ -255,7 +255,7 @@ // ▪ Activates a sequence of (HEV UI SOUNDS) then (HEV VOICE LINE): // // → Subtle alert fuzz**.wav → Armor Readout armor**.wav. // // ▪ If Armor = 0: // -// - Plays warning.wav (HEV UI SOUNDS) // +// - Plays armor00.wav (HEV UI SOUNDS) // // - No (HEV VOICE LINE) // // // //=====================================================================// @@ -333,7 +333,6 @@ EFFECT(battery); EFFECT(death); EFFECT(fuzz); EFFECT(medkit); -EFFECT(warning); // ENVIRONMENTAL EFFECTS EFFECT(stun); @@ -465,9 +464,22 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { // Armor Readout void armor_readout() { - PVLOG_NORMAL << "Current Armor: " << armor_ << "\n"; // Debug logging - SFX_armor.SelectFloat(armor_ / 100.0); - hybrid_font.PlayCommon(&SFX_armor); + PVLOG_NORMAL << "Current Armor: " << armor_ << "\n"; + + // Play random "fuzz" sound only if armor is above 0. + if (armor_ > 0) { + SOUNDQ->Play(&SFX_fuzz); + + // Play Armor Readout + SFX_armor.SelectFloat(armor_ / 100.0); + SOUNDQ->Play(&SFX_armor); + } + + // If Armor is 0, immediately plays a warning sound. + else { + SFX_armor.SelectFloat(armor_ / 0.0); + hybrid_font.PlayCommon(&SFX_armor); + } } // Clashes @@ -733,28 +745,25 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { // alongside death sound. However all pending sounds should be cleared. switch (effect) { default: return; - case EFFECT_ALT_SOUND: - SOUNDQ->Play(SoundToPlay(&SFX_hazard)); - return; - // (ENVIRONMENTAL FX) Hazard SFX + // (ENVIRONMENTAL FX) Hazard SFX case EFFECT_STUN: hybrid_font.PlayCommon(&SFX_stun); return; - // (HEV VOICE LINE) Armor Compromised + // (HEV VOICE LINE) Armor Compromised case EFFECT_USER2: SOUNDQ->Play(&SFX_armor_compromised); return; - // (HEV VOICE LINE) Health Alert + // (HEV VOICE LINE) Health Alert case EFFECT_USER1: if (health_ == 0) return; // Don't queue health sounds if dead SFX_health.SelectFloat(health_ / 100.0); SOUNDQ->Play(&SFX_health); return; - // (HEV UI SOUNDS) Death Sound + // (HEV UI SOUNDS) Death Sound case EFFECT_EMPTY: if (health_ == 0) { SOUNDQ->clear_pending(); @@ -763,6 +772,17 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { return; } } + + void SB_Effect2(EffectType effect, EffectLocation location) override { + switch (effect) { + default: return; + + // (HEV VOICE LINE ) Hazard Alert + case EFFECT_ALT_SOUND: + SOUNDQ->Play(SoundToPlay(&SFX_hazard)); + return; + } + } }; #endif From 801f1c7d0bfef034cdacec61521cab2439fcf946 Mon Sep 17 00:00:00 2001 From: jimsworld Date: Wed, 23 Jul 2025 13:39:17 +0100 Subject: [PATCH 31/37] Latest updates to props/hev.h: - added #ifndef and #endif to top defines. - made else statements more readable. - removed redundant .done() function and calls, it was only used once in HazardDecrease. - replaced with if (!timer_hazard_delay_.running()). --- props/hev.h | 43 +++++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/props/hev.h b/props/hev.h index 2c0c23d0a..72b1f5ff4 100644 --- a/props/hev.h +++ b/props/hev.h @@ -305,13 +305,27 @@ // Lower = faster recharge. // //=====================================================================// +#ifndef HEV_RANDOM_EVENT_INTERVAL_MS #define HEV_RANDOM_EVENT_INTERVAL_MS 60000 +#endif +#ifndef HEV_RANDOM_HAZARD_CHANCE #define HEV_RANDOM_HAZARD_CHANCE 15 +#endif +#ifndef HEV_HAZARD_DELAY_MS #define HEV_HAZARD_DELAY_MS 6000 +#endif +#ifndef HEV_HAZARD_DECREASE_MS #define HEV_HAZARD_DECREASE_MS 1000 +#endif +#ifndef HEV_HAZARD_AFTER_REVIVE_MS #define HEV_HAZARD_AFTER_REVIVE_MS 60000 +#endif +#ifndef HEV_HEALTH_INCREASE_MS #define HEV_HEALTH_INCREASE_MS 100 +#endif +#ifndef HEV_ARMOR_INCREASE_MS #define HEV_ARMOR_INCREASE_MS 100 +#endif #include "prop_base.h" #include @@ -358,10 +372,6 @@ struct HEVTimerBase { bool running() const { return active_ && (millis() - start_) <= interval_; } - - bool done() { - return active_ && (millis() - start_) >= interval_; - } }; class Hev : public PROP_INHERIT_PREFIX PropBase { @@ -424,18 +434,16 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { if (armor_ >= damage) { armor_ -= ceilf(damage * 0.80 / 2); health_ -= ceilf(damage * 0.20); - } - else if (armor_ > 0) { + + } else if (armor_ > 0) { int excess_physical = damage - armor_; health_ -= excess_physical; // Make armor compromised sound play before setting armor to 0 SaberBase::DoEffect(EFFECT_USER2, 0.0); PVLOG_NORMAL << "Armor Compromised!\n"; armor_ = 0; - } - else { - health_ -= damage; - } + + } else { health_ -= damage; } // Enforce minimum values if (health_ < 0) health_ = 0; @@ -473,10 +481,9 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { // Play Armor Readout SFX_armor.SelectFloat(armor_ / 100.0); SOUNDQ->Play(&SFX_armor); - } - // If Armor is 0, immediately plays a warning sound. - else { + } else { + // If Armor is 0, immediately plays a warning sound. SFX_armor.SelectFloat(armor_ / 0.0); hybrid_font.PlayCommon(&SFX_armor); } @@ -552,17 +559,17 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { } // Check sequence and apply damage - if (timer_hazard_delay_.done()) { + if (!timer_hazard_delay_.running()) { if (armor_ > 0) { armor_--; SaberBase::DoEffect(EFFECT_STUN, 0.0); + if (armor_ == 0) { SaberBase::DoEffect(EFFECT_USER2, 0.0); PVLOG_NORMAL << "Armor Compromised!\n"; } - } else { - DoDamage(1); - } + + } else { DoDamage(1); } // Clear hazard on death if (health_ == 0) { @@ -777,7 +784,7 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { switch (effect) { default: return; - // (HEV VOICE LINE ) Hazard Alert + // (HEV VOICE LINE) Hazard Alert case EFFECT_ALT_SOUND: SOUNDQ->Play(SoundToPlay(&SFX_hazard)); return; From 2ec51e71a1fcd5a8f63869027787645f369ec1ea Mon Sep 17 00:00:00 2001 From: jimsworld Date: Thu, 7 Aug 2025 15:55:12 +0100 Subject: [PATCH 32/37] Refactored DoDamage(): - Rejigged DoDamage() to accept a damage type parameter. - Added DAMAGE_HAZARD and DAMAGE_PHYSICAL constants for clarity. - Updated calls to DoDamage() to specify the damage type. - Added previous_health and previous_armor to help track death sound and armor compromised so they play reliably. --- props/hev.h | 72 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 44 insertions(+), 28 deletions(-) diff --git a/props/hev.h b/props/hev.h index 72b1f5ff4..c0fc67640 100644 --- a/props/hev.h +++ b/props/hev.h @@ -416,6 +416,11 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { int health_ = 100; int armor_ = 100; + enum DamageType { + DAMAGE_PHYSICAL, + DAMAGE_HAZARD, + }; + enum Hazard { HAZARD_NONE = 0, HAZARD_BIO = 1, @@ -429,36 +434,52 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { Hazard current_hazard_ = HAZARD_NONE; // Calculate Physical Damage - void DoDamage(int damage, bool quiet = false) { + void DoDamage(int damage, bool quiet = false, DamageType type = DAMAGE_PHYSICAL) { + int previous_health = health_; + int previous_armor = armor_; int tens = health_ / 10; - if (armor_ >= damage) { - armor_ -= ceilf(damage * 0.80 / 2); - health_ -= ceilf(damage * 0.20); - - } else if (armor_ > 0) { - int excess_physical = damage - armor_; - health_ -= excess_physical; - // Make armor compromised sound play before setting armor to 0 - SaberBase::DoEffect(EFFECT_USER2, 0.0); - PVLOG_NORMAL << "Armor Compromised!\n"; - armor_ = 0; - } else { health_ -= damage; } + // Damage type and calculation + switch (type) { + case DAMAGE_PHYSICAL: + if (armor_ >= damage) { + armor_ -= ceilf(damage * 0.80 / 2); + health_ -= ceilf(damage * 0.20); + } else if (armor_ > 0) { + int excess_physical = damage - armor_; + health_ -= excess_physical; + armor_ = 0; + } else { health_ -= damage; } + break; + + case DAMAGE_HAZARD: + if (armor_ > 0) { + armor_ -= damage; + } else { health_ -= damage; } + break; + } // Enforce minimum values if (health_ < 0) health_ = 0; if (armor_ < 0) armor_ = 0; - // Damage + // (HEV VOICE LINE) Logic for Armor Compromised + if (previous_armor > 0 && armor_ == 0) { + SaberBase::DoEffect(EFFECT_USER2, 0.0); + PVLOG_NORMAL << "Armor Compromised!\n"; + } + + // (ENVIRONMENTAL FX) Damage Sounds if (!quiet) SaberBase::DoEffect(EFFECT_STUN, 0.0); - // Death Sound - if (health_ == 0) { + // (HEV UI SOUNDS) Logic for Death Sound + if (health_ == 0 && previous_health > 0) { SaberBase::DoEffect(EFFECT_EMPTY, 0.0); return; } - // Health Alert - only plays when health enters a new multiple of 10 + // (HEV VOICE LINE) Logic for Health Alert + // Only plays when Health enters a new multiple of 10 int new_tens = health_ / 10; if (tens != new_tens) { SaberBase::DoEffect(EFFECT_USER1, 0.0); @@ -536,9 +557,13 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { // Roll for Random Hazard if (random(100) < HEV_RANDOM_HAZARD_CHANCE) { - PVLOG_NORMAL << "Activating hazard.\n"; + PVLOG_NORMAL << "Activating Hazard.\n"; current_hazard_ = (Hazard)(1 + random(6)); SaberBase::DoEffect(EFFECT_ALT_SOUND, 0.0, current_hazard_); + + } else { + PVLOG_NORMAL << "Skipping Hazard.\n"; + timer_random_event_.start(); } } } @@ -560,16 +585,7 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { // Check sequence and apply damage if (!timer_hazard_delay_.running()) { - if (armor_ > 0) { - armor_--; - SaberBase::DoEffect(EFFECT_STUN, 0.0); - - if (armor_ == 0) { - SaberBase::DoEffect(EFFECT_USER2, 0.0); - PVLOG_NORMAL << "Armor Compromised!\n"; - } - - } else { DoDamage(1); } + DoDamage(1, false, DAMAGE_HAZARD); // Clear hazard on death if (health_ == 0) { From 543b108710638c672b1ac64c18f9be1588252b6a Mon Sep 17 00:00:00 2001 From: jimsworld Date: Fri, 15 Aug 2025 14:52:50 +0100 Subject: [PATCH 33/37] Accounting for latest reviews. --- props/hev.h | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/props/hev.h b/props/hev.h index c0fc67640..c1f43cb6d 100644 --- a/props/hev.h +++ b/props/hev.h @@ -42,7 +42,7 @@ // - Clashes deal full damage to Health. // // - When Armor is active: // // - Clashes (physical impacts) are negated: // -// ▪ 80% of the impact is absorbed by Armor. // +// ▪ 80% is divided in half, then applied to Armor. // // ▪ 20% is applied to Health. // // - Clash damage is based on force of impact: // // - Cannot exceed 50 total damage. // @@ -449,13 +449,17 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { int excess_physical = damage - armor_; health_ -= excess_physical; armor_ = 0; - } else { health_ -= damage; } + } else { + health_ -= damage; + } break; case DAMAGE_HAZARD: if (armor_ > 0) { armor_ -= damage; - } else { health_ -= damage; } + } else { + health_ -= damage; + } break; } @@ -464,7 +468,7 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { if (armor_ < 0) armor_ = 0; // (HEV VOICE LINE) Logic for Armor Compromised - if (previous_armor > 0 && armor_ == 0) { + if (previous_armor > 0 && armor_ == 0 && health_ > 0) { SaberBase::DoEffect(EFFECT_USER2, 0.0); PVLOG_NORMAL << "Armor Compromised!\n"; } From 3a45bda72cc3d0ad5483f9bbc13bd1b4b109020c Mon Sep 17 00:00:00 2001 From: jimsworld Date: Fri, 15 Aug 2025 14:54:15 +0100 Subject: [PATCH 34/37] Minor amendment for Armor Compromised logic. --- props/hev.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/props/hev.h b/props/hev.h index c1f43cb6d..9fbf1a031 100644 --- a/props/hev.h +++ b/props/hev.h @@ -468,7 +468,7 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { if (armor_ < 0) armor_ = 0; // (HEV VOICE LINE) Logic for Armor Compromised - if (previous_armor > 0 && armor_ == 0 && health_ > 0) { + if (previous_armor > 0 && armor_ == 0 && health_ == 0) { SaberBase::DoEffect(EFFECT_USER2, 0.0); PVLOG_NORMAL << "Armor Compromised!\n"; } From c3c3a51b02344e3642354caf0f49900b0e6f6174 Mon Sep 17 00:00:00 2001 From: jimsworld Date: Tue, 4 Nov 2025 12:37:11 +0000 Subject: [PATCH 35/37] Fixed Armor Readout not firing properly. - Removed the 'health at 0' condition preventing armor_readout from playing. --- props/hev.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/props/hev.h b/props/hev.h index 9fbf1a031..6acc81144 100644 --- a/props/hev.h +++ b/props/hev.h @@ -468,7 +468,7 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { if (armor_ < 0) armor_ = 0; // (HEV VOICE LINE) Logic for Armor Compromised - if (previous_armor > 0 && armor_ == 0 && health_ == 0) { + if (previous_armor > 0 && armor_ == 0) { SaberBase::DoEffect(EFFECT_USER2, 0.0); PVLOG_NORMAL << "Armor Compromised!\n"; } From f9d12a75847797b11c426ad77f7a14ef0a2d415c Mon Sep 17 00:00:00 2001 From: jimsworld Date: Fri, 7 Nov 2025 11:37:19 +0000 Subject: [PATCH 36/37] Randomised Hazard Damage interval. - Hazard Damage no longer limited to 1s intervals. --- props/hev.h | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/props/hev.h b/props/hev.h index 6acc81144..7c9340307 100644 --- a/props/hev.h +++ b/props/hev.h @@ -287,9 +287,11 @@ // - Delay (ms) before Hazard damage starts after checking and // // triggering Hazard. Allows time for HEV Voice Line to finish. // // // -// HEV_HAZARD_DECREASE_MS // -// - Time (ms) between each tick of Hazard damage. // -// Lower = faster damage-over-time. // +// HEV_HAZARD_DECREASE_MIN_MS // +// - Minimum time (ms) between each tick of Hazard damage. // +// // +// HEV_HAZARD_DECREASE_MAX_MS // +// - Maximum time (ms) between each tick of Hazard damage. // // // // HEV_HAZARD_AFTER_REVIVE_MS // // - Time (ms) after reviving before Hazards can happen again. // @@ -314,8 +316,11 @@ #ifndef HEV_HAZARD_DELAY_MS #define HEV_HAZARD_DELAY_MS 6000 #endif -#ifndef HEV_HAZARD_DECREASE_MS -#define HEV_HAZARD_DECREASE_MS 1000 +#ifndef HEV_HAZARD_DECREASE_MIN_MS +#define HEV_HAZARD_DECREASE_MIN_MS 1000 +#endif +#ifndef HEV_HAZARD_DECREASE_MAX_MS +#define HEV_HAZARD_DECREASE_MAX_MS 4000 #endif #ifndef HEV_HAZARD_AFTER_REVIVE_MS #define HEV_HAZARD_AFTER_REVIVE_MS 60000 @@ -353,22 +358,19 @@ EFFECT(stun); struct HEVTimerBase { uint32_t start_ = 0; - uint32_t interval_ = 0; - bool active_ = false; void reset() { active_ = false; } - void start() { active_ = true; start_ = millis(); } - void configure(uint32_t interval) { interval_ = interval; } + void configure_random(uint32_t min_ms, uint32_t max_ms) { + interval_ = min_ms + random(max_ms - min_ms + 1); + } - // Returns true if timer is inactive or timeout exceeded. bool check() { return !active_ || (millis() - start_ > interval_); } - bool running() const { return active_ && (millis() - start_) <= interval_; } @@ -385,8 +387,9 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { // Debounce to prevent false Clashes. // // - timer_random_event_ - Interval timer for Random Hazards. // // Controls how often Hazards can occur.// - // - timer_hazard_delay_ - Delay inbetween event trigger and // - // Hazard dps. Gap for voice to finish. // + // - timer_hazard_delay_ - Delay between event trigger and // + // Hazard DPS. Also used as a gap for // + // voice to end before stun sfx start. // // - timer_hazard_after_revive_ - Cooldown after user revives. // // Blocks Hazards until timer is done. // // - timer_health_increase_ - Interval for Health recharge. // @@ -433,7 +436,7 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { Hazard current_hazard_ = HAZARD_NONE; - // Calculate Physical Damage + // Calculate Physical and Hazard Damage void DoDamage(int damage, bool quiet = false, DamageType type = DAMAGE_PHYSICAL) { int previous_health = health_; int previous_armor = armor_; @@ -599,7 +602,11 @@ class Hev : public PROP_INHERIT_PREFIX PropBase { return; } - timer_hazard_delay_.configure(HEV_HAZARD_DECREASE_MS); + // Interval for continuous damage is randomized between min and max + timer_hazard_delay_.configure_random( + HEV_HAZARD_DECREASE_MIN_MS, + HEV_HAZARD_DECREASE_MAX_MS + ); timer_hazard_delay_.start(); } } From 1d8c4647f06cf6baf5642101994d55b2ebddecd9 Mon Sep 17 00:00:00 2001 From: NoSloppy <53964195+NoSloppy@users.noreply.github.com> Date: Fri, 14 Nov 2025 17:26:15 -0600 Subject: [PATCH 37/37] testing --- doc/testing.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/testing.txt diff --git a/doc/testing.txt b/doc/testing.txt new file mode 100644 index 000000000..82f725259 --- /dev/null +++ b/doc/testing.txt @@ -0,0 +1 @@ +testing 1 2 3 \ No newline at end of file