// version 059 (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) /* multi_prop.h allows for 5 (maybe more coming) discrete prop files to be used, alternating on a 2 buttons extra long push (4 seconds - customisable with #define TWO_BUTTONS_X_LONG_PUSH 1000 * 4) with or without a blade attached. Blade detect function has no effect on multi_prop.h (other than playing blade in/out wav) Your prop NEEDS to have two or more buttons. Multi_prop.h MUST be declared before the other props. Props MUST be declared in the right order & one and only one of each type MUST be included. This prop is for fun and is not Star Wars cannon. It only exists to add extra functionality to your saber, if you so choose. Thank you for reading. You can find all 5 sound files (created by NoSloppy) used in multi_prop.h on my dropbox here: https://www.dropbox.com/scl/fi/p2pj9os5v4seel0wmzwcz/Multi_prop_sounds_by_NoSloppy.zip?rlkey=hi6589mexymnx4jmxhtwqjfu0&st=3klofs5m&dl=0 How to use: add this to your config #ifdef CONFIG_PROP //What to use in PROP_TYPE MultiProp <...> #include "../props/multi_prop.h" //use MultiProp <...> (choose only 1 saber & 1 blaster) //#include "../props/saber_caiwyn_buttons.h" //use CaiwynButtons <-- works alone | NOT compiling with multi_prop.h (I don't know why!) //#include "../props/saber.h" //use Saber | Compiling with multi_prop.h //#include "../props/saber_BC_buttons.h" //use SaberBCButtons | Compiling with multi_prop.h #include "../props/saber_fett263_buttons.h" //use SaberFett263Buttons | Compiling with multi_prop.h //#include "../props/saber_sa22c_buttons.h" //use SaberSA22CButtons | Compiling with multi_prop.h //#include "../props/saber_shtok_buttons.h" //use SaberShtokButtons | Compiling with multi_prop.h //#include "../props/blaster.h" //use Blaster | compiling with multi_prop.h (works with or without "bullet count" in CONFIG_BOTTOM) // IMPORTANT NOTE: blaster_BC_buttons.h (at the time of writing) is not "stock" compatible with multi_prop.h // A small modification needs to be added to it's code. I will let NoSloppy decide for the best course of action // to modify his prop. I do have "my version" of blaster_BC_buttons.h working, so it is possible. #include "../props/blaster_BC_buttons.h" //use BlasterBC (for old) | Compiling with multi_prop.h (after modifications to BlasterBC on line 152 //use BlasterBCButtons | from "#ifndef PROPS_DUAL_PROP_H" to "#if !defined(PROPS_MULTI_PROP_H) && !defined(PROPS_DUAL_PROP_H)" //(for new version) | Also need to add in "../common/saber_base.h" on line 105 "DEFINE_EFFECT(DESTRUCT)" or BlasterBC will just not compile ! // | (MUST have "bullets counts" code defined in CONFIG_BOTTOM) #include "../props/detonator.h" //use Detonator | Compiling with multi_prop.h #include "../props/jetpack_prop.h" //use Jetpack | Compiling with multi_prop.h #include "../props/morsecode_prop.h" //use MorseCode | Compiling with multi_prop.h //#include "../props/droid_prop.h" //In progress but very far from ready (plays with droids sounds) //#include "../props/vehicle_prop.h" //To be created (find a better name - plays with SW vehicles sounds and their weapons - ships, speeders, walkers, pod-racers, ...) #undef PROP_TYPE #define PROP_TYPE MultiProp #endif // also you will need to set your sets of preset(s) & sets of blade(s) arrays (0, 1, 2, 3, 4) // accordingly (same-ish as if using blade id - but this is not compatible with using the "real" blade id). */ #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(detmode); EFFECT(jetmode); //"NO_EFFECT"(morsecodemode); // No need for a "morsecode.wav" file. This is done using "Beepers" in Morse code. // Enumeration for modes enum class Prop_Mode { SABER = 0, BLASTER = 1, DETONATOR = 2, JETPACK = 3, // Not fully ready MORSECODE = 4, // Not fully ready //DROID = 6, // Uncomment when ready to implement Droid functionality //VEHICLE = 7, // Uncomment if implementing Vehicle (I don't know what this prop should do, // but it would be cool to play with STARWARS "vehicles" sounds: // speeders, pod-racers, TIE's, and any other ships that we can think of!) }; // 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 namespace display { namespace ssd1306 { void display_SetMessage(const char* message); } } #endif namespace MultiPropHelpers { // namespace used to have "setModeMessage" & "announcemode" // available in jetpack_prop.h & 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::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 } // Optimized Morse Code Beeping // (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); beeper.Silence(0.2); } } // Switch modes helper template void swapProp(PropBase* prop_instance,Prop_Mode modenext,Effect* sound_name,const char* message1,const char* message2, int blade_id) { MultiPropHelpers::setModeMessage(message1,message2); currentMode = modenext; updateBladeIDAndDetect(prop_instance,blade_id); MultiPropHelpers::announcemode(sound_name); } // 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. template void switchModes(PropBase* prop_instance) { switch (currentMode) { case Prop_Mode::SABER: swapProp(prop_instance,Prop_Mode::BLASTER, &SFX_blastermode,"Blaster Mode", "blaster/nmode", 1); break; case Prop_Mode::BLASTER: swapProp(prop_instance,Prop_Mode::DETONATOR,&SFX_detmode, "Detonator Mode", "detonator/nmode", 2); break; case Prop_Mode::DETONATOR: swapProp(prop_instance,Prop_Mode::JETPACK, &SFX_jetmode, "Jetpack Mode", "jetpack/nmode", 3); break; case Prop_Mode::JETPACK: MultiPropHelpers::setModeMessage( /* . . . . . . . . . . */ "Morse Code Mode","morse code/n mode"); currentMode = Prop_Mode::MORSECODE; updateBladeIDAndDetect(prop_instance, /* . . . . . . . . . . . . . . . . . . . . . . . . . . */ 4); //No "SFX" for MorseCode, it is in morse code ! morsecodemode(); // It says -.- = "K" = General invitation to transmit break; case Prop_Mode::MORSECODE: swapProp(prop_instance,Prop_Mode::SABER, &SFX_sabermode, "Saber Mode", "saber/nmode", 0); 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 } } 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; } } // 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 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); // 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; }; 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 (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. // // ************************************************************* // * 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 it is funny!) //std::function SB_Effect; // Pointer to SB_Effect member function }; std::unordered_map handlers; // Helper to create handlers, to use lambdas for invoking the member functions // Use Wrappers for Function Calls, use lambda functions to wrap the calls. Lambdas 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 "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" (But at least, I tried to make it look prety): 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; } } }; */