// version 062 (compiling version for saber-blaster(with bullet count)-detonator-jetpack-morsecode, all with sound-effects on // (but-untested as my saber is not yet working) transition between props. // (switching presets sets) (provided by NoSloppy) #ifndef PROPS_MULTI_PROP_H #define PROPS_MULTI_PROP_H #ifdef PROP_INHERIT_PREFIX #error multi_prop.h must be included first #else #define PROP_INHERIT_PREFIX virtual #endif #if NUM_BUTTONS < 2 #error Your prop NEEDS 2 or more buttons to use multi_prop #endif #include "prop_base.h" // Define FakeBladeID structure struct FakeBladeID { // static int return_value; // Holds the current mode ID // // Method to return the current blade ID // float id() { return return_value; } // // Method to set the blade ID based on the mode // static void SetFakeBlade(int id) { // return_value = id; // } // Sabersense code part1 }; // adapted to multi_prop.h // Initialize return_value to a default of 0 - SABER mode // int FakeBladeID::return_value = 0; // // Redefine the blade ID class to use FakeBladeID // #undef BLADE_ID_CLASS_INTERNAL // #define BLADE_ID_CLASS_INTERNAL FakeBladeID // #undef BLADE_ID_CLASS // #define BLADE_ID_CLASS FakeBladeID // // Define sound effects EFFECT(sabermode); EFFECT(blastermode); EFFECT(detonatormode); EFFECT(jetpackmode); /*NO-EFFECT(morsecodemode); No need for a "morsecode.wav" file. This is done using "Beepers" in Morse code. (600Hz is the typical "aviation" morse code audio frequency - 800Hz is the frequency usualy used in movies)*/ void morsecodemode() { // It says -.- = "K" = General invitation to transmit const float morsePattern[] = {0.6,0.2,0.6}; for (float duration : morsePattern) { beeper.Beep(duration,800); // movies morse code frequency beeper.Silence(0.2); } } // Enumeration for modes enum class Prop_Mode { SABER = 0, BLASTER = 1, DETONATOR = 2, JETPACK = 3, // Not fully ready MORSECODE = 4, // Not fully ready }; // Define the duration for TWO_BUTTONS_eXtra_LONG_PUSH (4 sec) #ifndef TWO_BUTTONS_X_LONG_PUSH #define TWO_BUTTONS_X_LONG_PUSH 1000 * 4 #endif // Button combo state management unsigned long holdStartTime = 0; Prop_Mode currentMode = Prop_Mode::SABER; // Initial state is Saber #if defined(INCLUDE_SSD1306) || defined(ENABLE_SSD1306) // Forward declarations for OLED functionality (this is a workaround "hack" for displaying text on OLED) void display_SetMessage(const char* message); #endif namespace MultiPropHelpers { // namespace used to have "setModeMessage" & "announcemode" // available in jetpack_prop.h &/or morsecode_prop.h // Display a mode message on the OLED screen void setModeMessage(const char* message1, const char* message2) { Serial.println(message1); // to check if OLED is present & display message2 on it #if defined(INCLUDE_SSD1306) || defined(ENABLE_SSD1306) display_SetMessage(message2); #endif } // Play a sound for a mode or beep if sound is unavailable void announcemode(Effect* sound_name) { if (!hybrid_font.PlayPolyphonic(sound_name)) { // Use beeper for fallback sounds beeper.Beep(0.05, 2000); beeper.Silence(0.05); beeper.Beep(0.05, 2000); }; } } // Declare Externally Accessible Functions: To make these helpers available outside multi_prop.h: extern void setModeMessage(const char* message1, const char* message2); extern void announcemode(Effect* sound_name); // Helper function to set FakeBladeID and re-detect the blade template void updateBladeIDAndDetect(PropBase* prop_instance,int blade_id) { FakeBladeID::SetFakeBlade(blade_id); // *** Sabersense code part2 *** prop_instance->FindBladeAgain(); // Call FindBladeAgain // SaberBase::DoBladeDetect(true); // Re-detect blades // adapted to multi_prop.h } // Switch modes helper template void swapProp(PropBase* prop_instance,Prop_Mode modenext,const char* message1,const char* message2, int blade_id,EffectType effect) { MultiPropHelpers::setModeMessage(message1,message2); currentMode = modenext; updateBladeIDAndDetect(prop_instance,blade_id); SaberBase::DoEffect(effect,0); } // Function to handle mode switching between props, it was my very repetitive, repeating code! // Set ID 0 for SABER mode, 1 for BLASTER mode, 2 for DETONATOR mode, 3 for JETPACK mode, // 4 for MORSECODE mode, 5 for DROID mode & 6 for VEHICLE mode. template void switchModes(PropBase* prop_instance) { switch (currentMode) { case Prop_Mode::SABER: swapProp(prop_instance,Prop_Mode::BLASTER, "Blaster Mode", "blaster/nmode", 1,EFFECT_BLASTERMODE); break; case Prop_Mode::BLASTER: swapProp(prop_instance,Prop_Mode::DETONATOR,"Detonator Mode", "detonator/nmode", 2,EFFECT_DETONATORMODE); break; case Prop_Mode::DETONATOR: swapProp(prop_instance,Prop_Mode::JETPACK, "Jetpack Mode", "jetpack/nmode", 3,EFFECT_JETPACKMODE); break; case Prop_Mode::JETPACK: swapProp(prop_instance,Prop_Mode::MORSECODE,"Morse Code Mode","morse code/n mode",4,EFFECT_MORSECODEMODE); break; case Prop_Mode::MORSECODE: swapProp(prop_instance,Prop_Mode::SABER, "Saber Mode", "saber/nmode", 0,EFFECT_SABERMODE); break; } } // Template to support multi-prop modes, this is the "prop class" or prop section. // MultiProp class with an Event function that calls switchModes(this) template class MultiProp : public virtual Saber, public virtual Blaster, public virtual Detonator, public virtual Jetpack, public virtual MorseCode { public: const char* name() override { return "MultiProp"; } // Button mapping for 2 and 3-button setups // The "Blaster mapping" uint32_t map_button(uint32_t b) { switch (b) { #if NUM_BUTTONS == 3 case BUTTON_AUX: return BUTTON_FIRE; case BUTTON_AUX2: return BUTTON_MODE_SELECT; #else case BUTTON_POWER: return BUTTON_FIRE; case BUTTON_AUX: return BUTTON_MODE_SELECT; #endif default: return b; } } // The "return to normal mapping" uint32_t reverse_map_button(uint32_t b) { switch (b) { #if NUM_BUTTONS == 3 case BUTTON_FIRE: return BUTTON_AUX; case BUTTON_MODE_SELECT: return BUTTON_AUX2; #else case BUTTON_FIRE: return BUTTON_POWER; case BUTTON_MODE_SELECT: return BUTTON_AUX; #endif default: return b; } } // Event handling for button presses, including combos for switching modes bool Event(enum BUTTON button, EVENT event) override { static bool powerPressed = false; // Tracks BUTTON_POWER press state static bool auxPressed = false; // Tracks BUTTON_AUX press state if (event == EVENT_PRESSED) { if (button == BUTTON_POWER) { powerPressed = true; // BUTTON_POWER is pressed } else if (button == BUTTON_AUX) { auxPressed = true; // BUTTON_AUX is pressed } // Check if both buttons are pressed simultaneously or near-simultaneously if (powerPressed && auxPressed && holdStartTime == 0) { holdStartTime = millis(); // Start the hold timer when both buttons are pressed Serial.println("Dual Long Push Started"); } } else if (event == EVENT_RELEASED) { if (button == BUTTON_POWER) { powerPressed = false; // BUTTON_POWER is released } else if (button == BUTTON_AUX) { auxPressed = false; // BUTTON_AUX is released } // Reset the hold state if either button is released if (!powerPressed || !auxPressed) { holdStartTime = 0; Serial.println("One of the two (or both) button(s) was (were) released too early"); } } // Check if both buttons have been held for the required duration if (powerPressed && auxPressed && (millis() - holdStartTime) >= TWO_BUTTONS_X_LONG_PUSH) { switchModes(this); // Pass "this" MultiProp instance to switchModes & switch the prop mode Serial.println("Both buttons were held long enough for MultiProp to switch to the next Prop_Mode"); return true; // Stop further event processing } // Call the appropriate Event function based on the current mode (Baster or "not Blaster") switch (currentMode) { case Prop_Mode::SABER: return Saber::Event(button, event); case Prop_Mode::BLASTER: { button = static_cast(map_button(button)); // Map modifiers to blaster uint32_t m = current_modifiers; current_modifiers = 0; for (; m; m &= m - 1) current_modifiers |= map_button(m & -m); bool ret = /* . . . . . . */ Blaster::Event(button, event); Serial.println("Blaster map_button"); // Map modifiers back to normal m = current_modifiers; current_modifiers = 0; for (; m; m &= m - 1) current_modifiers |= reverse_map_button(m & -m); return ret; Serial.println("Back to normal map_button"); }; case Prop_Mode::DETONATOR: return Detonator::Event(button, event); case Prop_Mode::JETPACK: return Jetpack::Event(button, event); case Prop_Mode::MORSECODE: return MorseCode::Event(button, event); } return false; // Event was not handled } /******************************************************************************************\ | I tried with lamdbas functions to reduce repetition but it was taking a lot more memmory.| | If you are interested, look at the bottom of this file for my abandoned code. | \******************************************************************************************/ // Overriding Event2 to resolve ambiguity bool Event2(enum BUTTON button, EVENT event, uint32_t modifiers) override { switch (currentMode) { case Prop_Mode::SABER: return Saber::Event2(button, event, modifiers); case Prop_Mode::BLASTER: return Blaster::Event2(button, event, modifiers); case Prop_Mode::DETONATOR: return Detonator::Event2(button, event, modifiers); case Prop_Mode::JETPACK: return Jetpack::Event2(button, event, modifiers); case Prop_Mode::MORSECODE: return MorseCode::Event2(button, event, modifiers); } return false; // Event was not handled } void SetPreset(int preset_num, bool announce) override { switch (currentMode) { case Prop_Mode::SABER: Saber::SetPreset(preset_num, announce); break; case Prop_Mode::BLASTER: Blaster::SetPreset(preset_num, announce); break; case Prop_Mode::DETONATOR: Detonator::SetPreset(preset_num, announce); break; case Prop_Mode::JETPACK: Jetpack::SetPreset(preset_num, announce); break; case Prop_Mode::MORSECODE: MorseCode::SetPreset(preset_num, announce); break; } } void Loop() override { switch (currentMode) { case Prop_Mode::SABER: Saber::Loop(); break; case Prop_Mode::BLASTER: Blaster::Loop(); break; case Prop_Mode::DETONATOR: Detonator::Loop(); break; case Prop_Mode::JETPACK: Jetpack::Loop(); break; case Prop_Mode::MORSECODE: MorseCode::Loop(); break; } } void DoMotion(const Vec3& motion, bool clear) override { switch (currentMode) { case Prop_Mode::SABER: Saber::DoMotion(motion, clear); break; case Prop_Mode::BLASTER: Blaster::DoMotion(motion, clear); break; case Prop_Mode::DETONATOR: Detonator::DoMotion(motion, clear); break; case Prop_Mode::JETPACK: Jetpack::DoMotion(motion, clear); break; case Prop_Mode::MORSECODE: MorseCode::DoMotion(motion, clear); break; } } void Clash(bool stab, float strength) override { switch (currentMode) { case Prop_Mode::SABER: Saber::Clash(stab, strength); break; case Prop_Mode::BLASTER: Blaster::Clash(stab, strength); break; case Prop_Mode::DETONATOR: Detonator::Clash(stab, strength); break; case Prop_Mode::JETPACK: Jetpack::Clash(stab, strength); break; case Prop_Mode::MORSECODE: MorseCode::Clash(stab, strength); break; } } void SB_Effect(EffectType effect, EffectLocation location) override { switch (effect) { case EFFECT_SABERMODE: MultiPropHelpers::announcemode(&SFX_sabermode); return; case EFFECT_BLASTERMODE: MultiPropHelpers::announcemode(&SFX_blastermode); return; case EFFECT_DETONATORMODE: MultiPropHelpers::announcemode(&SFX_detonatormode); return; case EFFECT_JETPACKMODE: MultiPropHelpers::announcemode(&SFX_jetpackmode); return; case EFFECT_MORSECODEMODE: morsecodemode(); /* General invitation to transmit */ return; // = It says -.- = "K" } switch (currentMode) { case Prop_Mode::SABER: Saber::SB_Effect(effect, location); break; case Prop_Mode::BLASTER: Blaster::SB_Effect(effect, location); break; case Prop_Mode::DETONATOR: Detonator::SB_Effect(effect, location); break; case Prop_Mode::JETPACK: Jetpack::SB_Effect(effect, location); break; case Prop_Mode::MORSECODE: MorseCode::SB_Effect(effect, location); break; } } }; #endif // PROPS_MULTI_PROP_H /* At some point in time, I had the following code to reduce repetition, // but it was taking a lot more memory, so I abandoned it. // However this was tested with plug-in 4.1.0, so I might "re-visit" it! // // ************************************************************* // * add this to the top, below "#define PROPS_MULTI_PROP_H": * // * ======================================================== * // * #include // Added for std::unordered_map * // * #include // Added for std::function * // ************************************************************* // Updated repetitive methods to a centralized handler structure struct Handler { std::function Event2; // Pointer to Event2 member function std::function SetPreset; // Pointer to SetPreset member function std::function Loop; // Pointer to Loop member function std::function DoMotion; // Pointer to DoMotion member function std::function Clash; // Pointer to Clash member function // SB_Effect does not "play nice" with "lamdbas bread" (LOTR) handlers (see errors from rev 049 to 053) !!! // SB_Effect must be like Gollum, he doesn't like elven lembas bread either!!! (Yes, I think this is funny!) //std::function SB_Effect; // Pointer to SB_Effect member function }; std::unordered_map handlers; // Helper to create handlers, to use lambdas bread for invoking the member functions // Use Wrappers for Function Calls, use lambda bread functions to wrap the calls. Lambdas bread capture the `this` // pointer and explicitly resolve the ambiguity. This avoids the problem with virtual inheritance. template Handler CreateHandler() { return { [this](enum BUTTON b, EVENT e, uint32_t m) { return static_cast(this)->Event2(b, e, m); }, [this](int p, bool a) { static_cast(this)->SetPreset(p, a); }, [this]() { static_cast(this)->Loop(); }, [this](const Vec3& v, bool c) { static_cast(this)->DoMotion(v, c); }, [this](bool s, float str) { static_cast(this)->Clash(s, str); }, //[this](EffectType e, EffectLocation l) { static_cast(this)->SB_Effect(e, l); } }; } MultiProp() { handlers[Prop_Mode::SABER] = CreateHandler(); handlers[Prop_Mode::BLASTER] = CreateHandler(); handlers[Prop_Mode::DETONATOR] = CreateHandler(); handlers[Prop_Mode::JETPACK] = CreateHandler(); handlers[Prop_Mode::MORSECODE] = CreateHandler(); } // Overriding Event2 to resolve ambiguity // Centralized calls bool Event2(enum BUTTON button, EVENT event, uint32_t modifiers) override { if (handlers[currentMode].Event2) { return (this->*handlers[currentMode].Event2)(button, event, modifiers); } return false; // Event was not handled } void SetPreset(int preset_num, bool announce) override { // Centralized calls if (handlers[currentMode].SetPreset) { (this->*handlers[currentMode].SetPreset)(preset_num, announce); } } void Loop() override { // Centralized calls if (handlers[currentMode].Loop) { (this->*handlers[currentMode].Loop)(); } } void DoMotion(const Vec3& motion, bool clear) override { // Centralized calls if (handlers[currentMode].DoMotion) { (this->*handlers[currentMode].DoMotion)(motion, clear); } } void Clash(bool stab, float strength) override { // Centralized calls if (handlers[currentMode].Clash) { (this->*handlers[currentMode].Clash)(stab, strength); } } // SB_Effect does not want to "play nice" with "lamdbas bread" (LOTR) handlers (see rev 049 to 053) !!! // SB_Effect must be like Gollum, he doesn't like elven lemdbas bread either!!! (Yes I think this is funny!) //void SB_Effect(EffectType effect, EffectLocation location) override { // if (handlers[currentMode].SB_Effect) { // Centralized calls // (this->*handlers[currentMode].SB_Effect)(effect, location); // } //} // so the code still has to be with the full switch with "repetition": void SB_Effect(EffectType effect, EffectLocation location) override { switch (currentMode) { // *** No centralized calls *** case Prop_Mode::SABER: Saber::SB_Effect(effect, location); break; case Prop_Mode::BLASTER: Blaster::SB_Effect(effect, location); break; case Prop_Mode::DETONATOR: Detonator::SB_Effect(effect, location); break; case Prop_Mode::JETPACK: Jetpack::SB_Effect(effect, location); break; case Prop_Mode::MORSECODE: MorseCode::SB_Effect(effect, location); break; } } }; */