From 70a692172bedfee55e0eed33e60288590cf74669 Mon Sep 17 00:00:00 2001 From: lahm86 <33758420+lahm86@users.noreply.github.com> Date: Thu, 25 Jan 2024 14:17:32 +0000 Subject: [PATCH] Fix enemy-dependent secrets (#603) Resolves #570. --- CHANGELOG.md | 1 + TRRandomizerCore/Editors/TR2RandoEditor.cs | 4 +++- TRRandomizerCore/Helpers/ItemFactory.cs | 5 +++++ .../Randomizers/Shared/SecretPicker.cs | 7 +++++-- .../Randomizers/TR1/TR1SecretRandomizer.cs | 2 +- .../Randomizers/TR2/TR2EnemyRandomizer.cs | 21 +++++++++++++++---- .../Randomizers/TR2/TR2SecretRandomizer.cs | 20 +++++++++++++++++- .../Randomizers/TR3/TR3EnemyRandomizer.cs | 18 +++++++++++++++- .../Randomizers/TR3/TR3SecretRandomizer.cs | 20 +++++++++++++++++- .../Resources/TR2/Locations/locations.json | 8 +++---- 10 files changed, 91 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fcccf48a9..b4adbbb1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ ## [Unreleased](https://github.com/LostArtefacts/TR-Rando/compare/V1.8.3...master) - xxxx-xx-xx +- fixed item locking logic so that secrets that rely on specific enemies will always be obtainable (#570) ## [V1.8.3](https://github.com/LostArtefacts/TR-Rando/compare/V1.8.2...V1.8.3) - 2024-01-21 - fixed incorrect items sometimes being allocated as secret rewards in Thames Wharf (#597) diff --git a/TRRandomizerCore/Editors/TR2RandoEditor.cs b/TRRandomizerCore/Editors/TR2RandoEditor.cs index 027b28d06..a2a5baab9 100644 --- a/TRRandomizerCore/Editors/TR2RandoEditor.cs +++ b/TRRandomizerCore/Editors/TR2RandoEditor.cs @@ -136,6 +136,7 @@ protected override void SaveImpl(AbstractTRScriptEditor scriptEditor, TRSaveMoni if (Settings.DevelopmentMode) { (tr23ScriptEditor.Script as TR23Script).LevelSelectEnabled = true; + (tr23ScriptEditor.Script as TR23Script).DozyEnabled = true; scriptEditor.SaveScript(); } @@ -265,7 +266,8 @@ protected override void SaveImpl(AbstractTRScriptEditor scriptEditor, TRSaveMoni BackupPath = backupDirectory, SaveMonitor = monitor, Settings = Settings, - TextureMonitor = textureMonitor + TextureMonitor = textureMonitor, + ItemFactory = itemFactory, }.Randomize(Settings.EnemySeed); } diff --git a/TRRandomizerCore/Helpers/ItemFactory.cs b/TRRandomizerCore/Helpers/ItemFactory.cs index d1a1b8e65..9847307da 100644 --- a/TRRandomizerCore/Helpers/ItemFactory.cs +++ b/TRRandomizerCore/Helpers/ItemFactory.cs @@ -119,4 +119,9 @@ public bool IsItemLocked(string level, int itemIndex) { return _lockedItems.ContainsKey(level) && _lockedItems[level].Contains(itemIndex); } + + public List GetLockedItems(string level) + { + return _lockedItems.ContainsKey(level) ? new(_lockedItems[level]) : new(); + } } diff --git a/TRRandomizerCore/Randomizers/Shared/SecretPicker.cs b/TRRandomizerCore/Randomizers/Shared/SecretPicker.cs index 6b68b7e39..b54a7df8b 100644 --- a/TRRandomizerCore/Randomizers/Shared/SecretPicker.cs +++ b/TRRandomizerCore/Randomizers/Shared/SecretPicker.cs @@ -214,7 +214,7 @@ private void ResetEvaluators(int totalCount) _distanceDivisor = Math.Max(_minDistanceDivisor, totalCount); } - public void FinaliseSecretPool(IEnumerable usedLocations, string level) + public void FinaliseSecretPool(IEnumerable usedLocations, string level, Func> lockedItemAction) { // Secrets in packs are permitted to enforce level state if (usedLocations.Any(l => l.LevelState == LevelState.Mirrored)) @@ -229,7 +229,10 @@ public void FinaliseSecretPool(IEnumerable usedLocations, string level foreach (Location location in usedLocations.Where(l => l.EntityIndex != -1)) { // Indicates that a secret relies on another item to remain in its original position - ItemFactory.LockItem(level, location.EntityIndex); + foreach (int itemIndex in lockedItemAction(location.EntityIndex)) + { + ItemFactory.LockItem(level, itemIndex); + } } #if DEBUG diff --git a/TRRandomizerCore/Randomizers/TR1/TR1SecretRandomizer.cs b/TRRandomizerCore/Randomizers/TR1/TR1SecretRandomizer.cs index 55d962e9d..7964a5ff5 100644 --- a/TRRandomizerCore/Randomizers/TR1/TR1SecretRandomizer.cs +++ b/TRRandomizerCore/Randomizers/TR1/TR1SecretRandomizer.cs @@ -475,7 +475,7 @@ private void RandomizeSecrets(TR1CombinedLevel level, List pickupTypes, floorData.WriteToLevel(level.Data); AddDamageControl(level, pickedLocations); - _secretPicker.FinaliseSecretPool(pickedLocations, level.Name); + _secretPicker.FinaliseSecretPool(pickedLocations, level.Name, itemIndex => new() { itemIndex }); } private void AddDamageControl(TR1CombinedLevel level, List locations) diff --git a/TRRandomizerCore/Randomizers/TR2/TR2EnemyRandomizer.cs b/TRRandomizerCore/Randomizers/TR2/TR2EnemyRandomizer.cs index 057067c5d..9d9707690 100644 --- a/TRRandomizerCore/Randomizers/TR2/TR2EnemyRandomizer.cs +++ b/TRRandomizerCore/Randomizers/TR2/TR2EnemyRandomizer.cs @@ -1,10 +1,7 @@ using System.Diagnostics; -using System.Numerics; using TRFDControl; -using TRFDControl.FDEntryTypes; using TRFDControl.Utilities; using TRGE.Core; -using TRLevelControl; using TRLevelControl.Helpers; using TRLevelControl.Model; using TRModelTransporter.Packing; @@ -25,6 +22,7 @@ public class TR2EnemyRandomizer : BaseTR2Randomizer internal int MaxPackingAttempts { get; set; } internal TR2TextureMonitorBroker TextureMonitor { get; set; } + public ItemFactory ItemFactory { get; set; } public TR2EnemyRandomizer() { @@ -229,6 +227,20 @@ private EnemyTransportCollection SelectCrossLevelEnemies(TR2CombinedLevel level, } } + // Some secrets may have locked enemies in place - we must retain those types + foreach (int itemIndex in ItemFactory.GetLockedItems(level.Name)) + { + TR2Entity item = level.Data.Entities[itemIndex]; + if (TR2TypeUtilities.IsEnemyType(item.TypeID)) + { + List family = TR2TypeUtilities.GetFamily(TR2TypeUtilities.GetAliasForLevel(level.Name, item.TypeID)); + if (!newEntities.Any(family.Contains)) + { + newEntities.Add(family[_generator.Next(0, family.Count)]); + } + } + } + // Get all other candidate supported enemies List allEnemies = TR2TypeUtilities.GetCandidateCrossLevelEnemies() .FindAll(e => TR2EnemyUtilities.IsEnemySupported(level.Name, e, difficulty, Settings.ProtectMonks)); @@ -607,7 +619,8 @@ private void RandomizeEnemies(TR2CombinedLevel level, EnemyRandomizationCollecti int enemyIndex = level.Data.Entities.IndexOf(currentEntity); // If it's an existing enemy that has to remain in the same spot, skip it - if (TR2EnemyUtilities.IsEnemyRequired(level.Name, currentEntityType)) + if (TR2EnemyUtilities.IsEnemyRequired(level.Name, currentEntityType) + || ItemFactory.IsItemLocked(level.Name, enemyIndex)) { continue; } diff --git a/TRRandomizerCore/Randomizers/TR2/TR2SecretRandomizer.cs b/TRRandomizerCore/Randomizers/TR2/TR2SecretRandomizer.cs index b5e825c89..3a1b20eb7 100644 --- a/TRRandomizerCore/Randomizers/TR2/TR2SecretRandomizer.cs +++ b/TRRandomizerCore/Randomizers/TR2/TR2SecretRandomizer.cs @@ -146,7 +146,25 @@ private void RandomizeSecrets(TR2CombinedLevel level) } AddDamageControl(level, pickedLocations); - _secretPicker.FinaliseSecretPool(pickedLocations, level.Name); + _secretPicker.FinaliseSecretPool(pickedLocations, level.Name, itemIndex => GetDependentLockedItems(level, itemIndex)); + } + + private static List GetDependentLockedItems(TR2CombinedLevel level, int itemIndex) + { + // We may be locking an enemy, so be sure to also lock their pickups. + List items = new() { itemIndex }; + + if (TR2TypeUtilities.IsEnemyType(level.Data.Entities[itemIndex].TypeID)) + { + Location enemyLocation = level.Data.Entities[itemIndex].GetLocation(); + List pickups = level.Data.Entities + .FindAll(e => TR2TypeUtilities.IsAnyPickupType(e.TypeID)) + .FindAll(e => e.GetLocation().IsEquivalent(enemyLocation)); + + items.AddRange(pickups.Select(p => level.Data.Entities.IndexOf(p))); + } + + return items; } private void PlaceSecret(TR2Entity entity, TR2Type type, Location location) diff --git a/TRRandomizerCore/Randomizers/TR3/TR3EnemyRandomizer.cs b/TRRandomizerCore/Randomizers/TR3/TR3EnemyRandomizer.cs index f10d43a8a..6d9592e17 100644 --- a/TRRandomizerCore/Randomizers/TR3/TR3EnemyRandomizer.cs +++ b/TRRandomizerCore/Randomizers/TR3/TR3EnemyRandomizer.cs @@ -189,6 +189,20 @@ private EnemyTransportCollection SelectCrossLevelEnemies(TR3CombinedLevel level) } } + // Some secrets may have locked enemies in place - we must retain those types + foreach (int itemIndex in ItemFactory.GetLockedItems(level.Name)) + { + TR3Entity item = level.Data.Entities[itemIndex]; + if (TR3TypeUtilities.IsEnemyType(item.TypeID)) + { + List family = TR3TypeUtilities.GetFamily(TR3TypeUtilities.GetAliasForLevel(level.Name, item.TypeID)); + if (!newEntities.Any(family.Contains)) + { + newEntities.Add(family[_generator.Next(0, family.Count)]); + } + } + } + if (!Settings.DocileWillard || Settings.OneEnemyMode || Settings.IncludedEnemies.Count < newEntities.Capacity) { // Willie isn't excludable in his own right because supporting a Willie-only game is impossible @@ -445,9 +459,11 @@ private void RandomizeEnemies(TR3CombinedLevel level, EnemyRandomizationCollecti { TR3Type currentEntityType = currentEntity.TypeID; TR3Type newEntityType = currentEntityType; + int enemyIndex = level.Data.Entities.IndexOf(currentEntity); // If it's an existing enemy that has to remain in the same spot, skip it - if (TR3EnemyUtilities.IsEnemyRequired(level.Name, currentEntityType)) + if (TR3EnemyUtilities.IsEnemyRequired(level.Name, currentEntityType) + || ItemFactory.IsItemLocked(level.Name, enemyIndex)) { continue; } diff --git a/TRRandomizerCore/Randomizers/TR3/TR3SecretRandomizer.cs b/TRRandomizerCore/Randomizers/TR3/TR3SecretRandomizer.cs index 72533a27a..d75a6e5aa 100644 --- a/TRRandomizerCore/Randomizers/TR3/TR3SecretRandomizer.cs +++ b/TRRandomizerCore/Randomizers/TR3/TR3SecretRandomizer.cs @@ -402,7 +402,25 @@ private void RandomizeSecrets(TR3CombinedLevel level, List pickupTypes, floorData.WriteToLevel(level.Data); AddDamageControl(level, pickupTypes, pickedLocations); - _secretPicker.FinaliseSecretPool(pickedLocations, level.Name); + _secretPicker.FinaliseSecretPool(pickedLocations, level.Name, itemIndex => GetDependentLockedItems(level, itemIndex)); + } + + private static List GetDependentLockedItems(TR3CombinedLevel level, int itemIndex) + { + // We may be locking an enemy, so be sure to also lock their pickups. + List items = new() { itemIndex }; + + if (TR3TypeUtilities.IsEnemyType(level.Data.Entities[itemIndex].TypeID)) + { + Location enemyLocation = level.Data.Entities[itemIndex].GetLocation(); + List pickups = level.Data.Entities + .FindAll(e => TR3TypeUtilities.IsAnyPickupType(e.TypeID)) + .FindAll(e => e.GetLocation().IsEquivalent(enemyLocation)); + + items.AddRange(pickups.Select(p => level.Data.Entities.IndexOf(p))); + } + + return items; } private void AddDamageControl(TR3CombinedLevel level, List pickupTypes, List locations) diff --git a/TRRandomizerCore/Resources/TR2/Locations/locations.json b/TRRandomizerCore/Resources/TR2/Locations/locations.json index 7844145ae..c2ab8b396 100644 --- a/TRRandomizerCore/Resources/TR2/Locations/locations.json +++ b/TRRandomizerCore/Resources/TR2/Locations/locations.json @@ -1145,7 +1145,7 @@ "Z": 22580, "Room": 91, "Difficulty": "Hard", - "EntityIndex": 79, + "EntityIndex": 78, "PackID": "apel", "RequiresGlitch": true }, @@ -1597,7 +1597,7 @@ "X": 66560, "Y": -840, "Z": 25692, - "Room": 98, + "Room": 103, "Difficulty": "Hard", "PackID": "apel", "RequiresGlitch": true @@ -1635,7 +1635,7 @@ "X": 41877, "Y": -3761, "Z": 31643, - "Room": 130, + "Room": 135, "Difficulty": "Hard", "PackID": "apel", "RequiresGlitch": true @@ -3721,7 +3721,7 @@ "Z": 86650, "Room": 0, "Difficulty": "Hard", - "EntityIndex": 4, + "EntityIndex": 3, "PackID": "apel" }, {