diff --git a/SilkySouls3/MainWindow.xaml.cs b/SilkySouls3/MainWindow.xaml.cs index a2455f3..4993257 100644 --- a/SilkySouls3/MainWindow.xaml.cs +++ b/SilkySouls3/MainWindow.xaml.cs @@ -90,7 +90,11 @@ public MainWindow() paramService, debugDrawService, chrInsService, spEffectService, eventService, reminderService, itemService); var itemViewModel = new ItemViewModel(itemService, _stateService); - var settingsViewModel = new SettingsViewModel(settingsService, hotkeyManager, _stateService); + var activateOnLaunchManager = new ActivateOnLaunchManager(); + var activateOnLaunchViewModel = new ActivateOnLaunchViewModel(playerViewModel, targetViewModel, + utilityViewModel, travelViewModel, itemViewModel, activateOnLaunchManager, _stateService); + var settingsViewModel = new SettingsViewModel(settingsService, hotkeyManager, _stateService, + activateOnLaunchViewModel); var playerTab = new PlayerTab(playerViewModel); var utilityTab = new UtilityTab(utilityViewModel); @@ -111,6 +115,9 @@ public MainWindow() MainTabControl.Items.Add(new TabItem { Header = "Settings", Content = settingsTab }); settingsViewModel.ApplyStartUpOptions(); + + _stateService.Publish(State.AppStart); + Closing += MainWindow_Closing; _gameLoadedTimer = new DispatcherTimer diff --git a/SilkySouls3/SilkySouls3.csproj b/SilkySouls3/SilkySouls3.csproj index 17c9f6d..9bc97b1 100644 --- a/SilkySouls3/SilkySouls3.csproj +++ b/SilkySouls3/SilkySouls3.csproj @@ -200,6 +200,7 @@ + @@ -211,6 +212,7 @@ + @@ -222,6 +224,9 @@ + + ActivateOnLaunchWindow.xaml + CreateLoadoutWindow.xaml @@ -275,6 +280,7 @@ MainWindow.xaml Code + diff --git a/SilkySouls3/Utilities/ActivateOnLaunchManager.cs b/SilkySouls3/Utilities/ActivateOnLaunchManager.cs new file mode 100644 index 0000000..f4a923a --- /dev/null +++ b/SilkySouls3/Utilities/ActivateOnLaunchManager.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace SilkySouls3.Utilities +{ + public class ActivateOnLaunchManager + { + private readonly Dictionary _values = new(); + + public ActivateOnLaunchManager() + { + Load(); + } + + public bool GetBool(string actionId) + { + return _values.TryGetValue(actionId, out var v) && bool.TryParse(v, out var b) && b; + } + + public void SetBool(string actionId, bool value) + { + _values[actionId] = value.ToString(); + Save(); + } + + public int GetInt(string actionId, int defaultValue = 0) + { + return _values.TryGetValue(actionId, out var v) && int.TryParse(v, out var i) ? i : defaultValue; + } + + public void SetInt(string actionId, int value) + { + _values[actionId] = value.ToString(); + Save(); + } + + public void Save() + { + try + { + SettingsManager.Default.ActivateOnLaunchActionIds = string.Join(",", _values.Select(kv => $"{kv.Key}:{kv.Value}")); + SettingsManager.Default.Save(); + } + catch (Exception ex) + { + Console.WriteLine($@"Error saving activate on launch settings: {ex.Message}"); + } + } + + public void Load() + { + try + { + _values.Clear(); + var raw = SettingsManager.Default.ActivateOnLaunchActionIds; + if (string.IsNullOrEmpty(raw)) return; + + foreach (var token in raw.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) + { + var separatorIndex = token.IndexOf(':'); + if (separatorIndex <= 0) continue; + _values[token.Substring(0, separatorIndex)] = token.Substring(separatorIndex + 1); + } + } + catch (Exception ex) + { + Console.WriteLine($@"Error loading activate on launch settings: {ex.Message}"); + } + } + } +} diff --git a/SilkySouls3/Utilities/SettingsManager.cs b/SilkySouls3/Utilities/SettingsManager.cs index e99a8b4..85de25a 100644 --- a/SilkySouls3/Utilities/SettingsManager.cs +++ b/SilkySouls3/Utilities/SettingsManager.cs @@ -46,6 +46,9 @@ public class SettingsManager public double SpEffectWindowTop { get; set; } public bool SpEffectAlwaysOnTop { get; set; } + public bool ActivateOnLaunchEnabled { get; set; } + public string ActivateOnLaunchActionIds { get; set; } = ""; + private static string SettingsPath => Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "SilkySouls3", diff --git a/SilkySouls3/ViewModels/ActivateOnLaunchViewModel.cs b/SilkySouls3/ViewModels/ActivateOnLaunchViewModel.cs new file mode 100644 index 0000000..fbfdd4a --- /dev/null +++ b/SilkySouls3/ViewModels/ActivateOnLaunchViewModel.cs @@ -0,0 +1,366 @@ +using SilkySouls3.Enums; +using SilkySouls3.Interfaces; +using SilkySouls3.Utilities; + +namespace SilkySouls3.ViewModels +{ + public class ActivateOnLaunchViewModel : BaseViewModel + { + private readonly PlayerViewModel _playerViewModel; + private readonly TargetViewModel _targetViewModel; + private readonly UtilityViewModel _utilityViewModel; + private readonly TravelViewModel _travelViewModel; + private readonly ItemViewModel _itemViewModel; + private readonly ActivateOnLaunchManager _aol; + + public ActivateOnLaunchViewModel(PlayerViewModel playerViewModel, TargetViewModel targetViewModel, + UtilityViewModel utilityViewModel, TravelViewModel travelViewModel, ItemViewModel itemViewModel, + ActivateOnLaunchManager activateOnLaunchManager, IStateService stateService) + { + _playerViewModel = playerViewModel; + _targetViewModel = targetViewModel; + _utilityViewModel = utilityViewModel; + _travelViewModel = travelViewModel; + _itemViewModel = itemViewModel; + _aol = activateOnLaunchManager; + + RegisterActions(); + + stateService.Subscribe(State.AppStart, OnAppStart); + stateService.Subscribe(State.Attached, OnGameAttached); + stateService.Subscribe(State.Loaded, OnGameLoaded); + stateService.Subscribe(State.OnNewGameStart, OnNewGameStart); + } + + #region Properties + + // Master toggle + private bool _isEnabled = SettingsManager.Default.ActivateOnLaunchEnabled; + + public bool IsEnabled + { + get => _isEnabled; + set + { + if (!SetProperty(ref _isEnabled, value)) return; + SettingsManager.Default.ActivateOnLaunchEnabled = value; + SettingsManager.Default.Save(); + } + } + + // Helper macros + private bool Get(string id) => _aol.GetBool(id); + private void Set(string id, bool value) => _aol.SetBool(id, value); + + private bool _isNoDeathChecked; + + public bool IsNoDeathChecked + { + get => _isNoDeathChecked; + set + { + if (SetProperty(ref _isNoDeathChecked, value)) Set(nameof(IsNoDeathChecked), value); + } + } + + private bool _isNoDamageChecked; + + public bool IsNoDamageChecked + { + get => _isNoDamageChecked; + set + { + if (SetProperty(ref _isNoDamageChecked, value)) Set(nameof(IsNoDamageChecked), value); + } + } + + private bool _isInfiniteStaminaChecked; + + public bool IsInfiniteStaminaChecked + { + get => _isInfiniteStaminaChecked; + set + { + if (SetProperty(ref _isInfiniteStaminaChecked, value)) Set(nameof(IsInfiniteStaminaChecked), value); + } + } + + private bool _isNoGoodsConsumeChecked; + + public bool IsNoGoodsConsumeChecked + { + get => _isNoGoodsConsumeChecked; + set + { + if (SetProperty(ref _isNoGoodsConsumeChecked, value)) Set(nameof(IsNoGoodsConsumeChecked), value); + } + } + + private bool _isInfiniteFpChecked; + + public bool IsInfiniteFpChecked + { + get => _isInfiniteFpChecked; + set + { + if (SetProperty(ref _isInfiniteFpChecked, value)) Set(nameof(IsInfiniteFpChecked), value); + } + } + + private bool _isInfiniteDurabilityChecked; + + public bool IsInfiniteDurabilityChecked + { + get => _isInfiniteDurabilityChecked; + set + { + if (SetProperty(ref _isInfiniteDurabilityChecked, value)) Set(nameof(IsInfiniteDurabilityChecked), value); + } + } + + private bool _isOneShotChecked; + + public bool IsOneShotChecked + { + get => _isOneShotChecked; + set + { + if (SetProperty(ref _isOneShotChecked, value)) Set(nameof(IsOneShotChecked), value); + } + } + + private bool _isInvisibleChecked; + + public bool IsInvisibleChecked + { + get => _isInvisibleChecked; + set + { + if (SetProperty(ref _isInvisibleChecked, value)) Set(nameof(IsInvisibleChecked), value); + } + } + + private bool _isSilentChecked; + + public bool IsSilentChecked + { + get => _isSilentChecked; + set + { + if (SetProperty(ref _isSilentChecked, value)) Set(nameof(IsSilentChecked), value); + } + } + + private bool _isNoAmmoConsumeChecked; + + public bool IsNoAmmoConsumeChecked + { + get => _isNoAmmoConsumeChecked; + set + { + if (SetProperty(ref _isNoAmmoConsumeChecked, value)) Set(nameof(IsNoAmmoConsumeChecked), value); + } + } + + private bool _isInfinitePoiseChecked; + + public bool IsInfinitePoiseChecked + { + get => _isInfinitePoiseChecked; + set + { + if (SetProperty(ref _isInfinitePoiseChecked, value)) Set(nameof(IsInfinitePoiseChecked), value); + } + } + + private bool _isNoHitChecked; + + public bool IsNoHitChecked + { + get => _isNoHitChecked; + set + { + if (SetProperty(ref _isNoHitChecked, value)) Set(nameof(IsNoHitChecked), value); + } + } + + private bool _isHealOverTimeChecked; + + public bool IsHealOverTimeChecked + { + get => _isHealOverTimeChecked; + set + { + if (SetProperty(ref _isHealOverTimeChecked, value)) Set(nameof(IsHealOverTimeChecked), value); + } + } + + private bool _isFpRegenChecked; + + public bool IsFpRegenChecked + { + get => _isFpRegenChecked; + set + { + if (SetProperty(ref _isFpRegenChecked, value)) Set(nameof(IsFpRegenChecked), value); + } + } + + private bool _isNoRollChecked; + + public bool IsNoRollChecked + { + get => _isNoRollChecked; + set + { + if (SetProperty(ref _isNoRollChecked, value)) Set(nameof(IsNoRollChecked), value); + } + } + + private bool _isAutoNewGameSevenChecked; + + public bool IsAutoNewGameSevenChecked + { + get => _isAutoNewGameSevenChecked; + set + { + if (SetProperty(ref _isAutoNewGameSevenChecked, value)) Set(nameof(IsAutoNewGameSevenChecked), value); + } + } + + private bool _isTargetOptionsChecked; + + public bool IsTargetOptionsChecked + { + get => _isTargetOptionsChecked; + set + { + if (SetProperty(ref _isTargetOptionsChecked, value)) Set(nameof(IsTargetOptionsChecked), value); + } + } + + private bool _isUnlockFpsChecked; + + public bool IsUnlockFpsChecked + { + get => _isUnlockFpsChecked; + set + { + if (SetProperty(ref _isUnlockFpsChecked, value)) Set(nameof(IsUnlockFpsChecked), value); + } + } + + private int _launchFps; + + public int LaunchFps + { + get => _launchFps; + set + { + if (SetProperty(ref _launchFps, value)) + _aol.SetInt(nameof(LaunchFps), value); + } + } + + private bool _isUnlockBonfiresChecked; + + public bool IsUnlockBonfiresChecked + { + get => _isUnlockBonfiresChecked; + set + { + if (SetProperty(ref _isUnlockBonfiresChecked, value)) Set(nameof(IsUnlockBonfiresChecked), value); + } + } + + private bool _isSpawnWeaponAtStartChecked; + + public bool IsSpawnWeaponAtStartChecked + { + get => _isSpawnWeaponAtStartChecked; + set + { + if (SetProperty(ref _isSpawnWeaponAtStartChecked, value)) Set(nameof(IsSpawnWeaponAtStartChecked), value); + } + } + + #endregion + + private void RegisterActions() + { + _isNoDeathChecked = Get(nameof(IsNoDeathChecked)); + _isNoDamageChecked = Get(nameof(IsNoDamageChecked)); + _isInfiniteStaminaChecked = Get(nameof(IsInfiniteStaminaChecked)); + _isNoGoodsConsumeChecked = Get(nameof(IsNoGoodsConsumeChecked)); + _isInfiniteFpChecked = Get(nameof(IsInfiniteFpChecked)); + _isInfiniteDurabilityChecked = Get(nameof(IsInfiniteDurabilityChecked)); + _isOneShotChecked = Get(nameof(IsOneShotChecked)); + _isInvisibleChecked = Get(nameof(IsInvisibleChecked)); + _isSilentChecked = Get(nameof(IsSilentChecked)); + _isNoAmmoConsumeChecked = Get(nameof(IsNoAmmoConsumeChecked)); + _isInfinitePoiseChecked = Get(nameof(IsInfinitePoiseChecked)); + _isNoHitChecked = Get(nameof(IsNoHitChecked)); + _isHealOverTimeChecked = Get(nameof(IsHealOverTimeChecked)); + _isFpRegenChecked = Get(nameof(IsFpRegenChecked)); + _isNoRollChecked = Get(nameof(IsNoRollChecked)); + _isAutoNewGameSevenChecked = Get(nameof(IsAutoNewGameSevenChecked)); + _isTargetOptionsChecked = Get(nameof(IsTargetOptionsChecked)); + _isUnlockFpsChecked = Get(nameof(IsUnlockFpsChecked)); + _launchFps = _aol.GetInt(nameof(LaunchFps), defaultValue: 75); + _isUnlockBonfiresChecked = Get(nameof(IsUnlockBonfiresChecked)); + _isSpawnWeaponAtStartChecked = Get(nameof(IsSpawnWeaponAtStartChecked)); + } + + private void OnAppStart() + { + if (!IsEnabled) return; + + if (IsNoDeathChecked) _playerViewModel.IsNoDeathEnabled = true; + if (IsNoDamageChecked) _playerViewModel.IsNoDamageEnabled = true; + if (IsInfiniteStaminaChecked) _playerViewModel.IsInfiniteStaminaEnabled = true; + if (IsNoGoodsConsumeChecked) _playerViewModel.IsNoGoodsConsumeEnabled = true; + if (IsInfiniteFpChecked) _playerViewModel.IsInfiniteFpEnabled = true; + if (IsInfiniteDurabilityChecked) _playerViewModel.IsInfiniteDurabilityEnabled = true; + if (IsOneShotChecked) _playerViewModel.IsOneShotEnabled = true; + if (IsInvisibleChecked) _playerViewModel.IsInvisibleEnabled = true; + if (IsSilentChecked) _playerViewModel.IsSilentEnabled = true; + if (IsNoAmmoConsumeChecked) _playerViewModel.IsNoAmmoConsumeEnabled = true; + if (IsInfinitePoiseChecked) _playerViewModel.IsInfinitePoiseEnabled = true; + if (IsNoHitChecked) _playerViewModel.IsNoHitEnabled = true; + if (IsHealOverTimeChecked) _playerViewModel.IsHotEnabled = true; + if (IsFpRegenChecked) _playerViewModel.IsFpRegenEnabled = true; + if (IsNoRollChecked) _playerViewModel.IsNoRollEnabled = true; + if (IsAutoNewGameSevenChecked) _playerViewModel.IsAutoSetNewGameSevenEnabled = true; + } + + private void OnGameAttached() + { + if (!IsEnabled) return; + + if (IsUnlockFpsChecked) + { + _utilityViewModel.Fps = LaunchFps; + _utilityViewModel.IsDbgFpsEnabled = true; + } + } + + private void OnGameLoaded() + { + if (!IsEnabled) return; + + if (IsTargetOptionsChecked) _targetViewModel.IsTargetOptionsEnabled = true; + + // Must be set here (State.Loaded) rather than on State.OnNewGameStart: ItemViewModel's own + // OnNewGameStart handler reads AutoSpawnEnabled on that same event, and Loaded always fires + // before OnNewGameStart within a single load, so this guarantees the flag is set in time. + if (IsSpawnWeaponAtStartChecked) _itemViewModel.AutoSpawnEnabled = true; + } + + private void OnNewGameStart() + { + if (!IsEnabled) return; + + if (IsUnlockBonfiresChecked) _travelViewModel.UnlockBonfiresCommand.Execute(null); + } + } +} diff --git a/SilkySouls3/ViewModels/SettingsViewModel.cs b/SilkySouls3/ViewModels/SettingsViewModel.cs index fb71282..e62dc81 100644 --- a/SilkySouls3/ViewModels/SettingsViewModel.cs +++ b/SilkySouls3/ViewModels/SettingsViewModel.cs @@ -3,9 +3,11 @@ using System.Linq; using System.Windows; using H.Hooks; +using SilkySouls3.Core; using SilkySouls3.Enums; using SilkySouls3.Interfaces; using SilkySouls3.Utilities; +using SilkySouls3.Views; using Key = H.Hooks.Key; namespace SilkySouls3.ViewModels @@ -14,6 +16,8 @@ public class SettingsViewModel : BaseViewModel { private readonly ISettingsService _settingsService; private readonly HotkeyManager _hotkeyManager; + private readonly ActivateOnLaunchViewModel _activateOnLaunchViewModel; + private ActivateOnLaunchWindow _activateOnLaunchWindow; private readonly Dictionary _hotkeyLookup; @@ -130,11 +134,16 @@ public int DefaultSoundVolume #endregion + public System.Windows.Input.ICommand OpenActivateOnLaunchCommand { get; } + public SettingsViewModel(ISettingsService settingsService, HotkeyManager hotkeyManager, - IStateService stateService) + IStateService stateService, ActivateOnLaunchViewModel activateOnLaunchViewModel) { _settingsService = settingsService; _hotkeyManager = hotkeyManager; + _activateOnLaunchViewModel = activateOnLaunchViewModel; + + OpenActivateOnLaunchCommand = new DelegateCommand(OpenActivateOnLaunch); Hotkeys = new Dictionary> { @@ -457,5 +466,22 @@ public void OnAttached() if (IsDefaultSoundChangeEnabled) _settingsService.PatchDefaultSound(DefaultSoundVolume); if (IsDisableMenuMusicEnabled) _settingsService.ToggleDisableMusic(true); } + + private void OpenActivateOnLaunch() + { + if (_activateOnLaunchWindow != null && _activateOnLaunchWindow.IsVisible) + { + _activateOnLaunchWindow.Activate(); + return; + } + + _activateOnLaunchWindow = new ActivateOnLaunchWindow(_activateOnLaunchViewModel) + { + DataContext = _activateOnLaunchViewModel, + Owner = Application.Current.MainWindow + }; + _activateOnLaunchWindow.Closed += (_, _) => _activateOnLaunchWindow = null; + _activateOnLaunchWindow.ShowDialog(); + } } } diff --git a/SilkySouls3/Views/ActivateOnLaunchWindow.xaml b/SilkySouls3/Views/ActivateOnLaunchWindow.xaml new file mode 100644 index 0000000..eda7c10 --- /dev/null +++ b/SilkySouls3/Views/ActivateOnLaunchWindow.xaml @@ -0,0 +1,191 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SilkySouls3/Views/ActivateOnLaunchWindow.xaml.cs b/SilkySouls3/Views/ActivateOnLaunchWindow.xaml.cs new file mode 100644 index 0000000..d2b0811 --- /dev/null +++ b/SilkySouls3/Views/ActivateOnLaunchWindow.xaml.cs @@ -0,0 +1,22 @@ +using System.Windows; +using System.Windows.Input; +using SilkySouls3.ViewModels; + +namespace SilkySouls3.Views +{ + public partial class ActivateOnLaunchWindow + { + public ActivateOnLaunchWindow(ActivateOnLaunchViewModel viewModel) + { + InitializeComponent(); + DataContext = viewModel; + } + + private void TitleBar_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) + { + if (e.ClickCount != 2) DragMove(); + } + + private void CloseButton_Click(object sender, RoutedEventArgs e) => Close(); + } +} diff --git a/SilkySouls3/Views/SettingsTab.xaml b/SilkySouls3/Views/SettingsTab.xaml index e6cf3a2..8f1e68a 100644 --- a/SilkySouls3/Views/SettingsTab.xaml +++ b/SilkySouls3/Views/SettingsTab.xaml @@ -47,6 +47,12 @@ + +