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 @@
+
+