using System.Collections.Generic; using System.Linq; using BizHawk.Common; using BizHawk.Emulation.Common; namespace BizHawk.Client.Common { public interface ISticky : IController { bool StickyIsInEffect(string button); } /// /// Used by input display, to determine if either autofire or regular stickies /// are "in effect" because we color this scenario differently /// public class StickyOrAdapter : IController { public ControllerDefinition Definition => Source.Definition; public bool IsPressed(string button) { return Source.StickyIsInEffect(button) || SourceStickyOr.StickyIsInEffect(button); } // pass floats solely from the original source // this works in the code because SourceOr is the autofire controller public float GetFloat(string name) { int i = Source.Definition.FloatControls.IndexOf(name); return Source.Definition.FloatRanges[i].Mid; // Floats don't make sense in sticky land } public ISticky Source { get; set; } public ISticky SourceStickyOr { get; set; } } public class StickyXorAdapter : ISticky, IController { /// /// Determines if a sticky is current mashing the button itself, /// If sticky is not set then false, if set, it returns true if the Source is not pressed, else false /// public bool StickyIsInEffect(string button) { if (IsSticky(button)) { return !Source.IsPressed(button); } return false; } public ControllerDefinition Definition => Source.Definition; public bool IsPressed(string button) { var source = Source.IsPressed(button); source ^= stickySet.Contains(button); return source; } public float GetFloat(string name) { var val = _floatSet[name]; if (val.HasValue) { return val.Value; } if (Source == null) { return 0; } return Source.GetFloat(name); } public IController Source { get; set; } public bool Locked { get; set; } // Pretty much a hack, private List _justPressed = new List(); protected readonly HashSet stickySet = new HashSet(); // if SetFloat() is called (typically virtual pads), then that float will entirely override the Source input // otherwise, the source is passed thru. protected readonly WorkingDictionary _floatSet = new WorkingDictionary(); public void SetFloat(string name, float? value) { if (value.HasValue) { _floatSet[name] = value; } else { _floatSet.Remove(name); } } public void ClearStickyFloats() { _floatSet.Clear(); } public void SetSticky(string button, bool isSticky) { if (isSticky) { stickySet.Add(button); } else { stickySet.Remove(button); } } public void Unset(string button) { stickySet.Remove(button); _floatSet.Remove(button); } public bool IsSticky(string button) { return stickySet.Contains(button); } public HashSet CurrentStickies => stickySet; public void ClearStickies() { stickySet.Clear(); _floatSet.Clear(); } public void MassToggleStickyState(List buttons) { foreach (var button in buttons.Where(button => !_justPressed.Contains(button))) { if (stickySet.Contains(button)) { stickySet.Remove(button); } else { stickySet.Add(button); } } _justPressed = buttons; } } public class AutoFireStickyXorAdapter : ISticky, IController { /// /// Determines if a sticky is current mashing the button itself, /// If sticky is not set then false, if set, it returns true if the Source is not pressed, else false /// public bool StickyIsInEffect(string button) { if (IsSticky(button)) { return !Source.IsPressed(button); } return false; } public ControllerDefinition Definition => Source.Definition; public bool IsPressed(string button) { var source = Source.IsPressed(button); bool patternValue = false; if (_boolPatterns.ContainsKey(button)) { // I can't figure a way to determine right here if it should Peek or Get. patternValue = _boolPatterns[button].PeekNextValue(); } source ^= patternValue; return source; } public float GetFloat(string name) { if (_floatPatterns.ContainsKey(name)) { return _floatPatterns[name].PeekNextValue(); } if (Source == null) { return 0; } return Source.GetFloat(name); } // TODO: Change the AutoHold adapter to be one of these, with an 'Off' value of 0? // Probably would have slightly lower performance, but it seems weird to have such a similar class that is only used once. private int On; private int Off; public void SetOnOffPatternFromConfig() { On = Global.Config.AutofireOn < 1 ? 0 : Global.Config.AutofireOn; Off = Global.Config.AutofireOff < 1 ? 0 : Global.Config.AutofireOff; } private readonly WorkingDictionary _boolPatterns = new WorkingDictionary(); private readonly WorkingDictionary _floatPatterns = new WorkingDictionary(); public AutoFireStickyXorAdapter() { On = 1; Off = 1; } public IController Source { get; set; } public bool Locked { get; set; } // Pretty much a hack, public void SetFloat(string name, float? value, AutoPatternFloat pattern = null) { if (value.HasValue) { if (pattern == null) { pattern = new AutoPatternFloat(value.Value, On, 0, Off); } _floatPatterns[name] = pattern; } else { _floatPatterns.Remove(name); } } public void ClearStickyFloats() { _floatPatterns.Clear(); } public void SetSticky(string button, bool isSticky, AutoPatternBool pattern = null) { if (isSticky) { if (pattern == null) { pattern = new AutoPatternBool(On, Off); } _boolPatterns[button] = pattern; } else { _boolPatterns.Remove(button); } } public void Unset(string button) { _boolPatterns.Remove(button); _floatPatterns.Remove(button); } public bool IsSticky(string button) { return _boolPatterns.ContainsKey(button) || _floatPatterns.ContainsKey(button); } public HashSet CurrentStickies => new HashSet(_boolPatterns.Keys); public void ClearStickies() { _boolPatterns.Clear(); _floatPatterns.Clear(); } public void IncrementLoops(bool lagged) { for (int i = 0; i < _boolPatterns.Count; i++) { _boolPatterns.ElementAt(i).Value.GetNextValue(lagged); } for (int i = 0; i < _floatPatterns.Count; i++) { _floatPatterns.ElementAt(i).Value.GetNextValue(lagged); } } private List _justPressed = new List(); public void MassToggleStickyState(List buttons) { foreach (var button in buttons.Where(button => !_justPressed.Contains(button))) { SetSticky(button, !_boolPatterns.ContainsKey(button)); } _justPressed = buttons; } } }