From ad9d34a7bf30b29b1f6e85a1dc32fa90ed836882 Mon Sep 17 00:00:00 2001 From: gonlad_x Date: Sun, 26 Jul 2026 13:37:23 +0200 Subject: [PATCH 1/4] Fix Travel > Road of Sacrifices > Abyss Watchers warping to Keep Ruins Both bonfires shared the identical BonfireId (3301954) in WarpEntries, so selecting Abyss Watchers made the exact same TravelService.Warp() call as selecting Keep Ruins. Corrected to Abyss Watchers' real bonfireEntityId (3301951), confirmed directly against BonfireWarpParam in Smithbox. Live-tested; warps to the correct arena bonfire now. --- SilkySouls3/Properties/Resources.resx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SilkySouls3/Properties/Resources.resx b/SilkySouls3/Properties/Resources.resx index 715827e..c9092ea 100644 --- a/SilkySouls3/Properties/Resources.resx +++ b/SilkySouls3/Properties/Resources.resx @@ -192,7 +192,7 @@ c3 ret 0,Road of Sacrifices,Keep Ruins,0x2D03,4,3301954 0,Road of Sacrifices,Farron Keep Perimeter,0x2D03,5,3301958 0,Road of Sacrifices,Old Wolf of Farron,0x2D03,3,3301955 -0,Road of Sacrifices,Abyss Watchers,0x2D02,7,3301954 +0,Road of Sacrifices,Abyss Watchers,0x2D02,7,3301951 0,Cathedral of the Deep,Cathedral of the Deep,0x2D03,6,3501953 0,Cathedral of the Deep,Cleansing Chapel,0x3C03,4,3501950 0,Cathedral of the Deep,Deacons of the Deep,0x3C03,7,3501951 From ff951e212c5fd58d840728af44f29ed11f83bb1b Mon Sep 17 00:00:00 2001 From: gonlad_x Date: Sun, 26 Jul 2026 18:30:20 +0200 Subject: [PATCH 2/4] Add per-boss Boss Revive feature (backend) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements single-boss revive (regular + first-encounter variants) for all 25 vanilla bosses, driven by a per-boss event flag dataset embedded in Resources.resx (Models/BossFlag, Models/BossRevive, DataLoader.GetBossRevives). Boss-revive state, commands, and logic live directly on EnemyViewModel alongside every other boss-specific mechanic it already owns (Cinder, Oceiros, Deacons, King of the Storm), rather than as a separate ViewModel — matching the convention the codebase already uses. The Enemies tab UI that exposes this is added in the next commit. Reviving clears a boss's own BossFlags (and, for a first encounter, its FirstEncounterFlags too), regrants Dancer's Basin of Vows on first-encounter revive so her cold-open trigger can retest (FirstEncounterItemId), and clears each boss's bonfire Discovered/Lit flag alongside its Defeat flag so the post-fight bonfire's state stays consistent after a revive. Oceiros' Consumed King's Garden shortcut door is included in his BossFlags since it's governed by its own dedicated flag rather than his Defeat/Encountered flags. Rest on Revive is decoupled from the warp: it only warps the player when they're already standing in the boss's own block, but Rest always fires independently of location whenever the checkbox is on, since resting isn't logically tied to being in the boss's room. Flag data was cross-referenced against vanilla EMEVD scripts and Smithbox param/MSB data rather than trusted from any single modding source; a few flags (Iudex/Champion Gundyr's shared shortcut, Nameless King's first encounter, Pontiff/Aldrich's Encountered flags, Twin Princes/Friede's interaction-range flags) needed correcting after cross-checking multiple sources against each other. --- .gitignore | 4 +- SilkySouls3/MainWindow.xaml.cs | 2 +- SilkySouls3/Models/BossFlag.cs | 8 + SilkySouls3/Models/BossRevive.cs | 20 ++ SilkySouls3/Properties/Resources.Designer.cs | 9 + SilkySouls3/Properties/Resources.resx | 27 ++ SilkySouls3/SilkySouls3.csproj | 2 + SilkySouls3/Utilities/DataLoader.cs | 81 ++++++ SilkySouls3/ViewModels/EnemyViewModel.cs | 281 ++++++++++++++++++- 9 files changed, 431 insertions(+), 3 deletions(-) create mode 100644 SilkySouls3/Models/BossFlag.cs create mode 100644 SilkySouls3/Models/BossRevive.cs diff --git a/.gitignore b/.gitignore index add57be..6dc0dbc 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,6 @@ bin/ obj/ /packages/ riderModule.iml -/_ReSharper.Caches/ \ No newline at end of file +/_ReSharper.Caches/ +/.vs/ +/resources/ diff --git a/SilkySouls3/MainWindow.xaml.cs b/SilkySouls3/MainWindow.xaml.cs index 4993257..8def314 100644 --- a/SilkySouls3/MainWindow.xaml.cs +++ b/SilkySouls3/MainWindow.xaml.cs @@ -88,7 +88,7 @@ public MainWindow() gameTickService, spEffectService); var enemyViewModel = new EnemyViewModel(enemyService, cinderService, hotkeyManager, _stateService, paramService, debugDrawService, chrInsService, spEffectService, eventService, reminderService, - itemService); + itemService, travelService, playerService, _dlcService); var itemViewModel = new ItemViewModel(itemService, _stateService); var activateOnLaunchManager = new ActivateOnLaunchManager(); var activateOnLaunchViewModel = new ActivateOnLaunchViewModel(playerViewModel, targetViewModel, diff --git a/SilkySouls3/Models/BossFlag.cs b/SilkySouls3/Models/BossFlag.cs new file mode 100644 index 0000000..a674ab9 --- /dev/null +++ b/SilkySouls3/Models/BossFlag.cs @@ -0,0 +1,8 @@ +namespace SilkySouls3.Models +{ + public class BossFlag + { + public int EventId { get; set; } + public bool Value { get; set; } + } +} diff --git a/SilkySouls3/Models/BossRevive.cs b/SilkySouls3/Models/BossRevive.cs new file mode 100644 index 0000000..c124923 --- /dev/null +++ b/SilkySouls3/Models/BossRevive.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using System.Numerics; +using SilkySouls3.Enums; + +namespace SilkySouls3.Models +{ + public class BossRevive + { + public DlcRequirement DlcRequirement { get; set; } + public string Area { get; set; } + public string BossName { get; set; } + public int BlockId { get; set; } + public List FirstEncounterFlags { get; set; } + public List BossFlags { get; set; } + public int BonfireId { get; set; } + public Vector3? Coords { get; set; } + public float Angle { get; set; } + public int? FirstEncounterItemId { get; set; } + } +} diff --git a/SilkySouls3/Properties/Resources.Designer.cs b/SilkySouls3/Properties/Resources.Designer.cs index 9ce2b38..6fb2008 100644 --- a/SilkySouls3/Properties/Resources.Designer.cs +++ b/SilkySouls3/Properties/Resources.Designer.cs @@ -905,6 +905,15 @@ internal static string WarpEntries { return ResourceManager.GetString("WarpEntries", resourceCulture); } } + + /// + /// Looks up a localized string similar to the Boss Revives dataset (Area,BossName,BlockId,FirstEncounterFlags,BossFlags,BonfireId,Coords,Angle per row). + /// + internal static string BossRevives { + get { + return ResourceManager.GetString("BossRevives", resourceCulture); + } + } /// /// Looks up a localized string similar to 0,013599D0,Ancient Dragon Greatshield,1,1,2,0 diff --git a/SilkySouls3/Properties/Resources.resx b/SilkySouls3/Properties/Resources.resx index c9092ea..88d3cc6 100644 --- a/SilkySouls3/Properties/Resources.resx +++ b/SilkySouls3/Properties/Resources.resx @@ -1763,6 +1763,33 @@ c3 ret 48 8d 8b 28 0b 00 00 lea rcx,[rbx+0xb28] 48 8d 15 00 00 00 00 lea rdx,[rip+0x0] # e <_main+0xe> e9 00 00 00 00 jmp 13 <_main+0x13> + + + 0,Cemetary of Ash,Iudex Gundyr,671088640,14000801|14000802,14000800|9319|6319|64000260|14000002,4001952,123.96|-63.95|554.54,-2.71 +0,Cemetary of Ash,Champion Gundyr,671088640,14000831,14000830|9320|6320|14000004|64000261,4001953,124.44|-63.98|554.00,-2.95 +0,High Wall of Lothric,Vordt of the Boreal Valley,503316480,13000801,13000800|9301|6301|13000002,3001955,27.61|-25.49|51.48,-0.01 +0,High Wall of Lothric,Dancer of the Boreal Valley,503316480,13000896,13000890|9300|6300|13000885|13000004,3001955,27.54|-8.58|126.75,3.14,1073743941 +0,High Wall of Lothric,Oceiros the Consumed King,503316480,13000838,13000830|9302|6302|63000251|13000001,3001954,-73.99|-49.43|360.94,2.89 +0,Undead Settlement,Curse-Rotted Greatwood,520093696,13100801,13100800|9303|6303|13100001,3101953,134.07|-180.74|-874.74,-0.31 +0,Road of Sacrifices,Crystal Sage,553648128,13300852,13300850|9306|6306|13300002,3301957,-156.24|-249.25|-424.70,1.84 +0,Road of Sacrifices,Abyss Watchers,553648128,13300801,13300800|9307|6307|13300421|13300001,3301958,272.16|-248.78|-485.06,-1.50 +0,Cathedral of the Deep,Deacons of the Deep,587202560,13500801,13500800|9311|6311|13500001,3501950,-449.63|-196.57|-617.35,1.09 +0,Catacombs of Carthus,High Lord Wolnir,637534208,13800801,13800800|9315|6315|13801800|63800561|13800000,3801950,, +0,Catacombs of Carthus,Old Demon King,637534208,13800831,13800830|9317|6317|13800004,3801954,291.62|-326.35|-920.68,-0.36 +0,Irithyll of the Boreal Valley,Pontiff Sulyvahn,620756992,13700851,13700850|9313|6313|13700420|13700421|13700422|13700001,3701955,397.70|-224.75|-1116.94,-0.14 +0,Irithyll of the Boreal Valley,Aldrich Devourer of Gods,620756992,13700801,13700800|9314|6314|13700002,3701953,687.84|-121.36|-1079.03,-1.62 +0,Irithyll Dungeon,Yhorm the Giant,654311424,13900801,13900800|9318|6318|13900001,3901952,121.35|-424.86|-615.54,-2.95 +0,Lothric Castle,Dragonslayer Armour,503382016,,13010800|9308|6308|13010001,3011952,-32.29|59.76|222.63,1.64 +0,Lothric Castle,Twin Princes,570490880,13410831|1400|1401|1415|1418,13410830|9309|6309|13410000,3411951,-24.58|141.97|312.01,-1.98 +0,Archdragon Peak,Ancient Wyvern,536870912,13200801,13200800|9305|6305,3201950,-72.19|29.08|180.67,-2.59 +0,Archdragon Peak,Nameless King,536870912,13200440|13200445,13200850|9304|6304|13200855|13200856|13200862|13200001,3201952,80.56|80.95|345.12,0.57 +0,Kiln of the First Flame,Soul of Cinder,687865856,14100801,14100800|9321|6321|14101100|14100002,4101951,-141.83|1590.25|38.51,-2.84 +1,The Painted World of Ariandel,Sister Friede,754974720,14500801,14500800|9322|6322|14500000|14500162|14500620:1|1720|1721|1735|1738,4501950,-186.99|27.75|-230.16,1.57 +1,The Painted World of Ariandel,Champion's Gravetender,754974720,,14500860|14500861|9323|6323|14500006,4501957,-40.38|-97.91|-311.90,1.38 +2,The Dreg Heap,Demon Prince,838860800,,15000800|9324|6324|15000000,5001953,135.26|-89.80|61.34,-1.25 +2,The Ringed City,Darkeater Midir,855638016,15100851,15100850|9326|6326|15100001,5101953,-484.42|-83.08|-398.50,-2.42 +2,The Ringed City,Halflight Spear of the Church,855638016,15100801,15100800|9325|6325,5101953,-380.77|-55.31|-323.59,2.96 +2,The Ringed City,Slave Knight Gael,855703552,15110801,15110800|9327|6327|15110000,5111951,219.36|527.21|192.72,0.93 0,671088640,4001951 diff --git a/SilkySouls3/SilkySouls3.csproj b/SilkySouls3/SilkySouls3.csproj index 9bc97b1..c2012d9 100644 --- a/SilkySouls3/SilkySouls3.csproj +++ b/SilkySouls3/SilkySouls3.csproj @@ -175,6 +175,8 @@ + + diff --git a/SilkySouls3/Utilities/DataLoader.cs b/SilkySouls3/Utilities/DataLoader.cs index 7798933..7ae9b69 100644 --- a/SilkySouls3/Utilities/DataLoader.cs +++ b/SilkySouls3/Utilities/DataLoader.cs @@ -110,6 +110,87 @@ public static Dictionary> GetWarpLocations() return warpDict; } + public static Dictionary> GetBossRevives() + { + Dictionary> bossRevives = new Dictionary>(); + string csvData = Resources.BossRevives; + if (string.IsNullOrWhiteSpace(csvData)) return bossRevives; + + using StringReader reader = new StringReader(csvData); + string line; + while ((line = reader.ReadLine()) != null) + { + if (string.IsNullOrWhiteSpace(line) || line.StartsWith("#")) continue; + + string[] parts = line.Split(','); + if (parts.Length < 9) continue; + + DlcRequirement dlcRequirement = ParseDlcRequirement(parts[0]); + string area = parts[1]; + + BossRevive boss = new BossRevive + { + DlcRequirement = dlcRequirement, + Area = area, + BossName = parts[2], + BlockId = int.Parse(parts[3], CultureInfo.InvariantCulture), + FirstEncounterFlags = ParseBossFlags(parts[4]), + BossFlags = ParseBossFlags(parts[5]), + BonfireId = int.Parse(parts[6], CultureInfo.InvariantCulture), + Coords = ParseBossCoords(parts[7]), + Angle = string.IsNullOrWhiteSpace(parts[8]) + ? 0f + : float.Parse(parts[8], CultureInfo.InvariantCulture), + FirstEncounterItemId = parts.Length > 9 && int.TryParse(parts[9], NumberStyles.Integer, + CultureInfo.InvariantCulture, out int firstEncounterItemId) + ? firstEncounterItemId + : null + }; + + if (!bossRevives.ContainsKey(area)) + { + bossRevives[area] = new List(); + } + + bossRevives[area].Add(boss); + } + + return bossRevives; + } + + // Each entry is "eventId" (implies clear to OFF) or "eventId:1" (set ON) / "eventId:0" (set OFF, explicit) + private static List ParseBossFlags(string flagData) + { + var flags = new List(); + if (string.IsNullOrWhiteSpace(flagData)) return flags; + + foreach (var part in flagData.Split('|')) + { + if (string.IsNullOrWhiteSpace(part)) continue; + + var pieces = part.Split(':'); + if (int.TryParse(pieces[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out int id)) + { + bool value = pieces.Length > 1 && pieces[1] == "1"; + flags.Add(new BossFlag { EventId = id, Value = value }); + } + } + + return flags; + } + + private static Vector3? ParseBossCoords(string coordData) + { + if (string.IsNullOrWhiteSpace(coordData)) return null; + + string[] parts = coordData.Split('|'); + return new Vector3( + float.Parse(parts[0], CultureInfo.InvariantCulture), + float.Parse(parts[1], CultureInfo.InvariantCulture), + float.Parse(parts[2], CultureInfo.InvariantCulture) + ); + } + public static List GetItemList(string listName) { List items = new List(); diff --git a/SilkySouls3/ViewModels/EnemyViewModel.cs b/SilkySouls3/ViewModels/EnemyViewModel.cs index e599680..6782b24 100644 --- a/SilkySouls3/ViewModels/EnemyViewModel.cs +++ b/SilkySouls3/ViewModels/EnemyViewModel.cs @@ -1,10 +1,17 @@ using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; using System.Windows.Input; +using System.Windows.Media; using SilkySouls3.Core; using SilkySouls3.Data; using SilkySouls3.Enums; using SilkySouls3.GameIds; using SilkySouls3.Interfaces; +using SilkySouls3.Models; using SilkySouls3.Utilities; using static SilkySouls3.Memory.Offsets; @@ -22,6 +29,26 @@ public class EnemyViewModel : BaseViewModel private readonly IEventService _eventService; private readonly IReminderService _reminderService; private readonly IItemService _itemService; + private readonly ITravelService _travelService; + private readonly IPlayerService _playerService; + private readonly IDlcService _dlcService; + private readonly IStateService _stateService; + + private const string BossStatusDead = "Dead"; + private const string BossStatusAlive = "Alive"; + private const string BossStatusFirstEncounter = "Alive, First Encounter"; + + private static readonly SolidColorBrush BossStatusDeadBrush = + (SolidColorBrush)new BrushConverter().ConvertFrom("#e74c3c"); + + private static readonly SolidColorBrush BossStatusAliveBrush = + (SolidColorBrush)new BrushConverter().ConvertFrom("#2ecc71"); + + private static readonly SolidColorBrush BossStatusDefaultBrush = Brushes.White; + + private Dictionary> _bossDict; + private List _allBosses; + private string _preSearchArea; private bool _isFadedIn; private bool _isInHalfLightArena; @@ -48,7 +75,8 @@ public class EnemyViewModel : BaseViewModel public EnemyViewModel(IEnemyService enemyService, ICinderService cinderService, HotkeyManager hotkeyManager, IStateService stateService, IParamService paramService, IDebugDrawService debugDrawService, IChrInsService chrInsService, ISpEffectService spEffectService, IEventService eventService, - IReminderService reminderService, IItemService itemService) + IReminderService reminderService, IItemService itemService, ITravelService travelService, + IPlayerService playerService, IDlcService dlcService) { _enemyService = enemyService; _cinderService = cinderService; @@ -58,6 +86,10 @@ public EnemyViewModel(IEnemyService enemyService, ICinderService cinderService, _eventService = eventService; _reminderService = reminderService; _itemService = itemService; + _travelService = travelService; + _playerService = playerService; + _dlcService = dlcService; + _stateService = stateService; SetCinderPhaseCommand = new DelegateCommand(SetCinderPhase); CastSoulmassCommand = new DelegateCommand(CastSoulmass); @@ -68,10 +100,14 @@ public EnemyViewModel(IEnemyService enemyService, ICinderService cinderService, ForceOceirosPhaseTwoCommand = new DelegateCommand(ForceOceirosPhaseTwo); SkipKingOfTheStormCommand = new DelegateCommand(SkipKingOfTheStorm); DropDeaconSkullsCommand = new DelegateCommand(DropDeaconSkulls); + ReviveBossCommand = new DelegateCommand(ReviveBoss); + ReviveBossFirstEncounterCommand = new DelegateCommand(ReviveBossFirstEncounter); _hotkeyManager = hotkeyManager; _paramService = paramService; + LoadBosses(); + stateService.Subscribe(State.Loaded, OnGameLoaded); stateService.Subscribe(State.NotLoaded, OnGameNotLoaded); stateService.Subscribe(State.FadedIn, OnFadedIn); @@ -118,6 +154,8 @@ public EnemyViewModel(IEnemyService enemyService, ICinderService cinderService, public ICommand ForceOceirosPhaseTwoCommand { get; } public ICommand SkipKingOfTheStormCommand { get; } public ICommand DropDeaconSkullsCommand { get; } + public ICommand ReviveBossCommand { get; } + public ICommand ReviveBossFirstEncounterCommand { get; } #endregion @@ -391,6 +429,120 @@ public float ArgoSpeedMultiplier public bool CanDropDeaconSkulls => _isInCathedralOfTheDeep; + private ObservableCollection _areas = new(); + + public ObservableCollection Areas + { + get => _areas; + private set => SetProperty(ref _areas, value); + } + + private ObservableCollection _areaBosses = new(); + + public ObservableCollection AreaBosses + { + get => _areaBosses; + private set => SetProperty(ref _areaBosses, value); + } + + private string _selectedArea; + + public string SelectedArea + { + get => _selectedArea; + set + { + if (!SetProperty(ref _selectedArea, value) || value == null) return; + + if (IsSearchActive) + { + IsSearchActive = false; + _searchText = string.Empty; + OnPropertyChanged(nameof(SearchText)); + _preSearchArea = null; + } + + UpdateAreaBossesList(); + } + } + + private BossRevive _selectedBoss; + + public BossRevive SelectedBoss + { + get => _selectedBoss; + set + { + if (!SetProperty(ref _selectedBoss, value)) return; + RefreshSelectedBossStatus(); + } + } + + private bool _isSearchActive; + + public bool IsSearchActive + { + get => _isSearchActive; + private set => SetProperty(ref _isSearchActive, value); + } + + private string _searchText = string.Empty; + + public string SearchText + { + get => _searchText; + set + { + if (!SetProperty(ref _searchText, value)) return; + + if (string.IsNullOrEmpty(value)) + { + IsSearchActive = false; + if (_preSearchArea != null) + { + _selectedArea = _preSearchArea; + OnPropertyChanged(nameof(SelectedArea)); + UpdateAreaBossesList(); + _preSearchArea = null; + } + } + else + { + if (!IsSearchActive) + { + _preSearchArea = SelectedArea; + IsSearchActive = true; + } + + ApplyFilter(); + } + } + } + + private bool _isRestOnReviveEnabled; + + public bool IsRestOnReviveEnabled + { + get => _isRestOnReviveEnabled; + set => SetProperty(ref _isRestOnReviveEnabled, value); + } + + private string _selectedBossStatus = string.Empty; + + public string SelectedBossStatus + { + get => _selectedBossStatus; + private set => SetProperty(ref _selectedBossStatus, value); + } + + private SolidColorBrush _selectedBossStatusColor = BossStatusDefaultBrush; + + public SolidColorBrush SelectedBossStatusColor + { + get => _selectedBossStatusColor; + private set => SetProperty(ref _selectedBossStatusColor, value); + } + #endregion #region Private Methods @@ -564,6 +716,7 @@ private void OnGameLoaded() if (Math.Abs(_argoSpeedMultiplier - 1.0f) > float.Epsilon) ApplyArgoSpeed(); AreOptionsEnabled = true; + RefreshSelectedBossStatus(); } private void OnFadedIn() @@ -610,6 +763,8 @@ private void OnGameNotLoaded() _hasPlacedPrismStonesThisLoad = false; OnPropertyChanged(nameof(CanPlacePrismStones)); OnPropertyChanged(nameof(CanDropDeaconSkulls)); + SelectedBossStatus = string.Empty; + SelectedBossStatusColor = BossStatusDefaultBrush; } private void ForceOceirosPhaseTwo() @@ -624,6 +779,130 @@ private void SkipKingOfTheStorm() _chrInsService.SetHp(kots, 0); } + private void LoadBosses() + { + _bossDict = DataLoader.GetBossRevives(); + _allBosses = _bossDict.Values.SelectMany(x => x).ToList(); + + Areas = new ObservableCollection(_bossDict.Keys); + SelectedArea = Areas.FirstOrDefault(); + } + + private void UpdateAreaBossesList() + { + if (string.IsNullOrEmpty(SelectedArea) || !_bossDict.ContainsKey(SelectedArea)) + { + AreaBosses = new ObservableCollection(); + return; + } + + AreaBosses = new ObservableCollection(_bossDict[SelectedArea]); + SelectedBoss = AreaBosses.FirstOrDefault(); + } + + private void ApplyFilter() + { + var searchLower = SearchText.ToLower(); + var matches = _allBosses.Where(b => + b.BossName.ToLower().Contains(searchLower) || + b.Area.ToLower().Contains(searchLower)); + + AreaBosses = new ObservableCollection(matches); + SelectedBoss = AreaBosses.FirstOrDefault(); + } + + private void RefreshSelectedBossStatus() + { + if (!AreOptionsEnabled || SelectedBoss == null) + { + SelectedBossStatus = string.Empty; + SelectedBossStatusColor = BossStatusDefaultBrush; + return; + } + + SelectedBossStatus = GetBossStatus(SelectedBoss); + SelectedBossStatusColor = GetBossStatusColor(SelectedBossStatus); + } + + private string GetBossStatus(BossRevive bossRevive) + { + if (bossRevive.BossFlags == null || bossRevive.BossFlags.Count == 0) return string.Empty; + + var isDead = _eventService.GetEvent(bossRevive.BossFlags[0].EventId); + if (isDead) return BossStatusDead; + + if (bossRevive.FirstEncounterFlags is { Count: > 0 }) + { + var alreadyEncountered = bossRevive.FirstEncounterFlags.All(f => _eventService.GetEvent(f.EventId)); + return alreadyEncountered ? BossStatusAlive : BossStatusFirstEncounter; + } + + return BossStatusAlive; + } + + private static SolidColorBrush GetBossStatusColor(string status) => status switch + { + BossStatusDead => BossStatusDeadBrush, + BossStatusAlive => BossStatusAliveBrush, + BossStatusFirstEncounter => BossStatusAliveBrush, + _ => BossStatusDefaultBrush + }; + + private void SetBossFlags(BossRevive bossRevive, bool isFirstEncounter) + { + if (isFirstEncounter && bossRevive.FirstEncounterFlags != null) + { + foreach (var flag in bossRevive.FirstEncounterFlags) + _eventService.SetEvent(flag.EventId, flag.Value); + } + + foreach (var flag in bossRevive.BossFlags) + _eventService.SetEvent(flag.EventId, flag.Value); + } + + private void ReviveBoss() => Revive(isFirstEncounter: false); + + private void ReviveBossFirstEncounter() => Revive(isFirstEncounter: true); + + private void Revive(bool isFirstEncounter) + { + var bossRevive = SelectedBoss; + if (bossRevive == null || !_dlcService.MeetsRequirement(bossRevive.DlcRequirement)) return; + + SetBossFlags(bossRevive, isFirstEncounter); + RefreshSelectedBossStatus(); + + if (isFirstEncounter && bossRevive.FirstEncounterItemId.HasValue) + _itemService.SpawnItem(bossRevive.FirstEncounterItemId.Value, 1, true, 1); + + bool isInBossArea = _playerService.GetCurrentBlockId() == bossRevive.BlockId; + if (!isInBossArea && !IsRestOnReviveEnabled) return; + + _ = Task.Run(() => + { + if (isInBossArea) + { + if (bossRevive.Coords.HasValue) + _travelService.WarpWithCoords(bossRevive.Coords.Value, bossRevive.Angle, + bossRevive.BonfireId); + else + _travelService.Warp(bossRevive.BonfireId); + } + + if (IsRestOnReviveEnabled) + { + if (isInBossArea) + { + int start = Environment.TickCount; + while (!_stateService.IsFadedIn() && Environment.TickCount - start < 10000) + Thread.Sleep(50); + } + + _playerService.Rest(); + } + }); + } + #endregion } } \ No newline at end of file From 5f3c6abe0bc15b54d93b5ea71eb5f67c80df58a0 Mon Sep 17 00:00:00 2001 From: gonlad_x Date: Sun, 26 Jul 2026 18:30:29 +0200 Subject: [PATCH 3/4] Add Boss Revives tab to Enemies section Nests a "Boss Revives" sub-tab under Enemies, listing bosses by area with search, live Dead/Alive/First-Encounter status, and Revive/Revive-as-First- Encounter buttons plus a Rest-on-Revive checkbox, all bound directly to EnemyViewModel from the previous commit. --- SilkySouls3/Views/EnemyTab.xaml | 237 ++++++++++++++++++++++++++++++++ 1 file changed, 237 insertions(+) diff --git a/SilkySouls3/Views/EnemyTab.xaml b/SilkySouls3/Views/EnemyTab.xaml index 114a9f2..e7de88d 100644 --- a/SilkySouls3/Views/EnemyTab.xaml +++ b/SilkySouls3/Views/EnemyTab.xaml @@ -5,10 +5,14 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:enums="clr-namespace:SilkySouls3.Enums" xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit" + xmlns:converters="clr-namespace:SilkySouls3.Converters" mc:Ignorable="d"> + + + @@ -212,4 +216,237 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +