Blaster Config, for 2.2 questions

Good morning, bit of an odd question for you.

My installer and I are working on 2 blasters, using the blaster config set-up. I’ll try forward you a copy.

We are wanting to be able to set up 9 “blade styles”, one for each ROYGBIV color and a silver. The question is can we program a 2.2 to cycle through Sound Banks, within a blade style, to avoid needing 40 bladestyles, to use all the sound fonts.

Should that be impossible, what about setting up the saber color change on the fly feature for a blaster ?

/* Basic blaster prop use.

Default Startup Mode = STUN
Add the following to your config file if so desired:
	#define BLASTER_SHOTS_UNTIL_EMPTY 15 (whatever number)
	#define BLASTER_JAM_PERCENTAGE if this is not defined, random from 0-100%.

Blaster Buttons: FIRE and MODE
(Blaster is always on with power, unless dedicated Power button is installed.)

Cycle Modes -           Click MODE.
Next Preset -           Long click and release MODE.
Previous Preset -       Double click and hold MODE, release after a second.
Reload -                Hold MODE until Reloaded. (Or Click Reload if dedicated button insatlled)
Start/Stop Track -      Double click MODE.
Fire -                  Click FIRE. (Hold to Auto Fire / Rapid Fire)
Clip In -               Clip Detect pad Latched On. ( or Hold Momentary button)
Clip out -              Clip Detect pad Latched Off. ( or release Momentary button)
Unjam -                 Bang the blaster.
- If there's a 3rd button for Power,
Power On / Off -        Click POWER.

Wavs to use for switching Modes:
	mdstun.wav
	mdkill.wav
	mdauto.wav
- If these are not present, mode.wav will be used for all modes.
- If no mode.wav either, then Talkie voice speaks selected mode.
*/
  
#ifndef PROPS_BLASTER_H
#define PROPS_BLASTER_H

#include "prop_base.h"

#define PROP_TYPE Blaster
#define PROP_HAS_BULLET_COUNT

EFFECT(clipin);
EFFECT(clipout);
EFFECT(empty);
EFFECT(full);
EFFECT(jam);
EFFECT(mode);
EFFECT(plioff);
EFFECT(plion);
EFFECT(range);
EFFECT(reload);
EFFECT(stun);
EFFECT(unjam);
EFFECT(mdstun);
EFFECT(mdkill);
EFFECT(mdauto);
// For mode sounds, specific "mdstun", "mdkill", and "mdauto" may be used.
// If just a single "mode" sound for all switches exists, that will be used.
// If no mode sounds exist in the font, a talkie version will speak the mode on switching.
class Blaster : public PROP_INHERIT_PREFIX PropBase {
public:
  Blaster() : PropBase() {}
  const char* name() override { return "Blaster"; }

  // Mode states to handle kill vs stun effects
  enum BlasterMode {
    MODE_STUN,
    MODE_KILL,
    MODE_AUTO
  };

  BlasterMode blaster_mode = MODE_STUN;

  virtual void SetBlasterMode(BlasterMode to_mode) {
    if (!auto_firing_) {
      blaster_mode = to_mode;
      SaberBase::DoEffect(EFFECT_MODE, 0);
    }
  }

  virtual void NextBlasterMode() {
    switch(blaster_mode) {
      case MODE_STUN:
        SetBlasterMode(MODE_KILL);
        return;
      case MODE_KILL:
#ifdef ENABLE_BLASTER_AUTO
        SetBlasterMode(MODE_AUTO);
#else
        SetBlasterMode(MODE_STUN);
#endif
        return;
      case MODE_AUTO:
        SetBlasterMode(MODE_STUN);
        return;
    }
  }

  bool auto_firing_ = false;
  int shots_fired_ = 0;
  bool is_jammed_ = false;

#ifdef BLASTER_SHOTS_UNTIL_EMPTY
  const int max_shots_ = BLASTER_SHOTS_UNTIL_EMPTY;
#else
  const int max_shots_ = -1;
#endif

  virtual int GetBulletCount() {
    return max_shots_ - shots_fired_;
  }

  virtual bool CheckJam(int percent) {
    int random = rand() % 100;
    return random < percent;
  }

  virtual void Fire() {
#ifdef ENABLE_MOTION
#ifdef BLASTER_JAM_PERCENTAGE
    // If we're already jammed then we don't need to recheck. If we're not jammed then check if we just jammed.
    is_jammed_ = is_jammed_ ? true : CheckJam(BLASTER_JAM_PERCENTAGE);

    if (is_jammed_) {
      SaberBase::DoEffect(EFFECT_JAM, 0);
      return;
    }
#endif
#endif

    if (max_shots_ != -1) {
      if (shots_fired_ >= max_shots_) {
        SaberBase::DoEffect(EFFECT_EMPTY, 0);
        return;
      }
    }

    if (blaster_mode == MODE_AUTO) {
      SelectAutoFirePair(); // Set up the autofire pairing if the font suits it.
      SaberBase::SetLockup(LOCKUP_AUTOFIRE);
      SaberBase::DoBeginLockup();
      auto_firing_ = true;
    } else {
      if (blaster_mode == MODE_STUN) {
        SaberBase::DoEffect(EFFECT_STUN, 0);
      } else {
        SaberBase::DoEffect(EFFECT_FIRE, 0);
      }

      shots_fired_++;
    }
  }

  virtual void SelectAutoFirePair() {
    if (!SFX_auto.files_found() || !SFX_blast.files_found()) return;

    int autoCount = SFX_auto.files_found();
    int blastCount = SFX_blast.files_found();
    int pairSelection;

    // If we don't have a matched pair of autos and blasts, then don't override the sequence to get a matched pair.
    if (autoCount == blastCount) {
        pairSelection = rand() % autoCount;
        SFX_auto.Select(pairSelection);
        SFX_blast.Select(pairSelection);
    }
  }

  virtual void Reload() {
    shots_fired_ = 0;
    SaberBase::DoEffect(EFFECT_RELOAD, 0);
  }

  virtual void ClipOut() {
    if (max_shots_ != -1) shots_fired_ = max_shots_;
    SaberBase::DoEffect(EFFECT_CLIP_OUT, 0);
  }

  virtual void ClipIn() {
    SaberBase::DoEffect(EFFECT_CLIP_IN, 0);
  }

  // Pull in parent's SetPreset, but turn the blaster on.
  void SetPreset(int preset_num, bool announce) override {
    PropBase::SetPreset(preset_num, announce);

    if (!SFX_poweron) {
      if (!SaberBase::IsOn()) {
        On();
      }
    }
  }

  void LowBatteryOff() override {
    if (SFX_poweron) {
      PropBase::LowBatteryOff();
    }
  }

  // Self-destruct pulled from detonator 
  bool armed_ = false;

  enum NextAction {
    NEXT_ACTION_NOTHING,
    NEXT_ACTION_ARM,
    NEXT_ACTION_BLOW,
  };

  NextAction next_action_ = NEXT_ACTION_NOTHING;
  uint32_t time_base_;
  uint32_t next_event_time_;
  
  void SetNextAction(NextAction what, uint32_t when) {
    time_base_ = millis();
    next_event_time_ = when;
    next_action_ = what;
  }

  void SetNextActionF(NextAction what, float when) {
    SetNextAction(what, when * 1000);
  }
  
  void PollNextAction() {
    if (millis() - time_base_ > next_event_time_) {
      switch (next_action_) {
        case NEXT_ACTION_NOTHING:
          break;
        case NEXT_ACTION_ARM:
          armed_ = true;
          // TODO: Should we have separate ARMING and ARMED states?
          break;
        case NEXT_ACTION_BLOW:
          Off(OFF_BLAST);
          break;
      }
      next_action_ = NEXT_ACTION_NOTHING;
    }
  }
  
  void beginArm() {
    SaberBase::SetLockup(SaberBase::LOCKUP_ARMED);
    SaberBase::DoBeginLockup();
#ifdef ENABLE_AUDIO    
    float len = hybrid_font.GetCurrentEffectLength();
#else    
    float len = 1.6;
#endif
    SetNextActionF(NEXT_ACTION_ARM, len);
  }

  void selfDestruct() {
    SaberBase::DoEndLockup();
#ifdef ENABLE_AUDIO    
    float len = hybrid_font.GetCurrentEffectLength();
#else    
    float len = 0.0;
#endif
    SaberBase::SetLockup(SaberBase::LOCKUP_NONE);
    if (armed_) {
      SetNextActionF(NEXT_ACTION_BLOW, len);
    } else {
      SetNextAction(NEXT_ACTION_NOTHING, 0);
    }
  }

  void Loop() override {
    PropBase::Loop();
    PollNextAction();
  }

  // Make clash do nothing except unjam if jammed.
  void Clash(bool stab, float strength) override {
    if (is_jammed_) {
      is_jammed_ = false;
      SaberBase::DoEffect(EFFECT_UNJAM, 0);
    }
  }

  // Make swings do nothing
  void DoMotion(const Vec3& motion, bool clear) override {
    PropBase::DoMotion(Vec3(0), clear);
  }

  bool Event2(enum BUTTON button, EVENT event, uint32_t modifiers) override {
    switch (EVENTID(button, event, modifiers)) {

      case EVENTID(BUTTON_MODE_SELECT, EVENT_FIRST_SAVED_CLICK_SHORT, MODE_ON):
        NextBlasterMode();
        return true;
      case EVENTID(BUTTON_MODE_SELECT, EVENT_FIRST_CLICK_LONG, MODE_ON):
        next_preset();
        return true;

      case EVENTID(BUTTON_MODE_SELECT, EVENT_SECOND_CLICK_LONG, MODE_ON):
        previous_preset();
        return true;
        
      case EVENTID(BUTTON_RELOAD, EVENT_PRESSED, MODE_ON):
      case EVENTID(BUTTON_MODE_SELECT, EVENT_HELD_MEDIUM, MODE_ON):
        Reload();
        return true;

      case EVENTID(BUTTON_MODE_SELECT, EVENT_DOUBLE_CLICK, MODE_ON):
        StartOrStopTrack();
        return true;

      case EVENTID(BUTTON_FIRE, EVENT_PRESSED, MODE_ON):
        Fire();
        return true;

      case EVENTID(BUTTON_FIRE, EVENT_RELEASED, MODE_ON):
        if (blaster_mode == MODE_AUTO) {
          if (SaberBase::Lockup()) {
            SaberBase::DoEndLockup();
            SaberBase::SetLockup(SaberBase::LOCKUP_NONE);
            auto_firing_ = false;
          }
        }
        return true;

      case EVENTID(BUTTON_CLIP_DETECT, EVENT_PRESSED, MODE_ON):
      case EVENTID(BUTTON_CLIP_DETECT, EVENT_LATCH_ON, MODE_ON):
        ClipIn();
        return true;

      case EVENTID(BUTTON_CLIP_DETECT, EVENT_RELEASED, MODE_ON):
      case EVENTID(BUTTON_CLIP_DETECT, EVENT_LATCH_OFF, MODE_ON):
        ClipOut();
        return true;

      // In the event of the presence of a power button, let it control the power on events.
      case EVENTID(BUTTON_POWER, EVENT_PRESSED, MODE_OFF):
        On();
        return true;

      case EVENTID(BUTTON_POWER, EVENT_PRESSED, MODE_ON):
        Off();
        return true;
		    
  #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;
  }

   // Blaster effects, auto fire is handled by begin/end lockup
  void SB_Effect(EffectType effect, float location) override {
    switch (effect) {
      default: return;
      case EFFECT_STUN: hybrid_font.PlayCommon(&SFX_stun); return;
      case EFFECT_FIRE: hybrid_font.PlayCommon(&SFX_blast); return;
      case EFFECT_CLIP_IN: hybrid_font.PlayCommon(&SFX_clipin); return;
      case EFFECT_CLIP_OUT: hybrid_font.PlayCommon(&SFX_clipout); return;
      case EFFECT_RELOAD: hybrid_font.PlayCommon(&SFX_reload); return;
      case EFFECT_MODE: SayMode(); return;
      case EFFECT_RANGE: hybrid_font.PlayCommon(&SFX_range); return;
      case EFFECT_EMPTY: hybrid_font.PlayCommon(&SFX_empty); return;
      case EFFECT_FULL: hybrid_font.PlayCommon(&SFX_full); return;
      case EFFECT_JAM: hybrid_font.PlayCommon(&SFX_jam); return;
      case EFFECT_UNJAM: hybrid_font.PlayCommon(&SFX_unjam); return;
      case EFFECT_PLI_ON: hybrid_font.PlayCommon(&SFX_plion); return;
      case EFFECT_PLI_OFF: hybrid_font.PlayCommon(&SFX_plioff); return;
  
    }
  }

  void SayMode() {
    switch (blaster_mode) {
      case MODE_STUN:
        if (SFX_mdstun) {
          hybrid_font.PlayCommon(&SFX_mdstun);
        } else if (SFX_mode) {
          hybrid_font.PlayCommon(&SFX_mode);
        } else {
          talkie.Say(spSTUN);
        }
      break;
      case MODE_KILL:
        if (SFX_mdkill) {
          hybrid_font.PlayCommon(&SFX_mdkill);
        } else if (SFX_mode) {
          hybrid_font.PlayCommon(&SFX_mode);
        } else {
          talkie.Say(spKILL);      
        }
      break;
      case MODE_AUTO:
        if (SFX_mdauto) {
          hybrid_font.PlayCommon(&SFX_mdauto);
        } else if (SFX_mode) {
          hybrid_font.PlayCommon(&SFX_mode);
        } else {
          talkie.Say(spAUTOFIRE);       
        }
      break;
    }
  }
};

#endif

Both of these are entirely possible, but neither is actually supported in the current blaster prop, so it would require some code changes.

My (and other people on this forum) can help if you need it.
Not sure why you posted the blaster prop file though?

Its the prop my installer currently used in others, I’m not familiar with blaster side at all. :sweat_smile:

What can we provide to help with the process?

Our first choice is only needing to have 9 styles, being able to cycle thru sound banks on the SD card.

We would love it to make it so a long press/Hold on Mode and Trigger cycle to the next sound folder.

Well the first thing to do is to figure out exactly how you want it to work.
Like, do you want the font bank changing to work like edit mode where you go into a menu to change things, or should it be more like “triple-click the power button to change font” ?
Do you want the changed font to save, or revert back when you move to the next preset?
How do you imagine things working when you add a new font to the sd card? Should it automatically be added to the list, or do you think the font names should have pre-defined names, like “bank1”, “bank2”, etc., and only those fonts would be included when going to the next/previous font? ProffieOS has a way to find all fonts on the SD card, but it may not work that well for blaster fonts since they don’t always have a hum file…

I think there’s termnology confusion.
I think what you are saying you want is just like a saber, where you have 9 presets, and then just cycle forward and backward through them? Each preset would have its own font for sounds and style for the color/effects etc…

Unless you want to have a single preset and be able to cycle through a ColorSelect list, or fancy alt sounds stuff, but less likely that’s what you mean.

I have a pretty comprehensive blaster prop file in the works that can certainly change presets…

Thats a start! Ideally we were thinking we only needed 9 set-ups, for each major color plus a silvery white. We were hoping to find a way to change sound fonts within a preset while in use, like sabers do colors.

Without blade effects, we didnt see any need for more then 1 of each energy color.

We found a google drive of shared blaster sounds (so have a lot of great sound styles to play with), and wanted something easier then making 40 presets. Ideally using a two-button (hold Mode + Pull trigger) to cycle to the next sound bank when in use, probably easier then using an “on the fly” menu, we’re not sure.

Believe it or not, that kinda IS the easy way.
I think a little step-by-step of how you imagine it working would be good.

Like, when you say you want to change sound fonts, do you want the “blade” (barrell emitter) to stay the same color?
Do you then want to change the color separately while the font remains the same?

The thing is that anything is really possible, and not difficult, it just comes down to how you want it to work or you specifically.

edit - Reading it again, I think you want 9 presets, each with an established color, and then be able to change the sounds while in any of those presets. IS that correct?

I think you could either:

  • Choose the sounds while having a single color set in 9 presets

This would mean one color per preset, and then set up the fonts to all be available via alt-sounds setup, where you have all the fonts in altNNN subfolders, then write blade styles that drives the choosing of the fonts with some sort of button command (this is using new features and requires a bit of tinkering)

  • Choose the color while having a single font set in N presets (one preset for each font)

This would be a more traditional approach and would be just making a preset for each font, and then having the same blade style in each one which contains a ColorSelect<> or EffectSequence<> list for your 9 colors/styles/looks., then you can just cycle though which color you want for that sound. you could also use RotateColorsX<> that would give you the full spectrum of color choice instead of just 9, but you would need to have a prop that supports Color Change mode.

  • Combine both above options to be available in each preset

You could use a Color change / select method on the colors and also use alt-sounds to change fonts to give you any combination of the two.

Again, not anything difficult, just need to choose a method.

Thanks Nosloppy! We have a working config and will continue to tweak. We will keep our eyes open for your new one, and definitely give it a spin too!