From 1c1658a21395aee348f5d338f8d9143ec0d94701 Mon Sep 17 00:00:00 2001 From: lahm86 <33758420+lahm86@users.noreply.github.com> Date: Wed, 19 Jun 2024 19:46:36 +0100 Subject: [PATCH] Add option to hide dead T-rexes (#709) Resolves #708. --- .../Editors/RandomizerSettings.cs | 3 ++ .../TR1/Remastered/TR1REnemyRandomizer.cs | 40 +++++++++++++++++++ .../TR1/Locations/enemy_relocations.json | 36 ++++++++++++++--- TRRandomizerCore/TRRandomizerController.cs | 6 +++ TRRandomizerCore/TRRandomizerType.cs | 1 + TRRandomizerCore/TRVersionSupport.cs | 1 + TRRandomizerView/Model/ControllerOptions.cs | 24 ++++++++++- 7 files changed, 104 insertions(+), 7 deletions(-) diff --git a/TRRandomizerCore/Editors/RandomizerSettings.cs b/TRRandomizerCore/Editors/RandomizerSettings.cs index fb16e808..d4dbedc7 100644 --- a/TRRandomizerCore/Editors/RandomizerSettings.cs +++ b/TRRandomizerCore/Editors/RandomizerSettings.cs @@ -73,6 +73,7 @@ public class RandomizerSettings public bool ProtectMonks { get; set; } public bool DocileWillard { get; set; } public bool RelocateAwkwardEnemies { get; set; } + public bool HideDeadTrexes { get; set; } public BirdMonsterBehaviour BirdMonsterBehaviour { get; set; } public bool DefaultChickens => BirdMonsterBehaviour == BirdMonsterBehaviour.Default; public bool DocileChickens => BirdMonsterBehaviour == BirdMonsterBehaviour.Docile; @@ -233,6 +234,7 @@ public void ApplyConfig(Config config) ProtectMonks = config.GetBool(nameof(ProtectMonks), true); DocileWillard = config.GetBool(nameof(DocileWillard)); RelocateAwkwardEnemies = config.GetBool(nameof(RelocateAwkwardEnemies), true); + HideDeadTrexes = config.GetBool(nameof(HideDeadTrexes), true); BirdMonsterBehaviour = (BirdMonsterBehaviour)config.GetEnum(nameof(BirdMonsterBehaviour), typeof(BirdMonsterBehaviour), BirdMonsterBehaviour.Default); RandoEnemyDifficulty = (RandoDifficulty)config.GetEnum(nameof(RandoEnemyDifficulty), typeof(RandoDifficulty), RandoDifficulty.Default); DragonSpawnType = (DragonSpawnType)config.GetEnum(nameof(DragonSpawnType), typeof(DragonSpawnType), DragonSpawnType.Default); @@ -407,6 +409,7 @@ public void StoreConfig(Config config) config[nameof(ProtectMonks)] = ProtectMonks; config[nameof(DocileWillard)] = DocileWillard; config[nameof(RelocateAwkwardEnemies)] = RelocateAwkwardEnemies; + config[nameof(HideDeadTrexes)] = HideDeadTrexes; config[nameof(BirdMonsterBehaviour)] = BirdMonsterBehaviour; config[nameof(RandoEnemyDifficulty)] = RandoEnemyDifficulty; config[nameof(DragonSpawnType)] = DragonSpawnType; diff --git a/TRRandomizerCore/Randomizers/TR1/Remastered/TR1REnemyRandomizer.cs b/TRRandomizerCore/Randomizers/TR1/Remastered/TR1REnemyRandomizer.cs index cc965e3b..92735c9f 100644 --- a/TRRandomizerCore/Randomizers/TR1/Remastered/TR1REnemyRandomizer.cs +++ b/TRRandomizerCore/Randomizers/TR1/Remastered/TR1REnemyRandomizer.cs @@ -1,5 +1,6 @@ using TRDataControl; using TRGE.Core; +using TRLevelControl; using TRLevelControl.Helpers; using TRLevelControl.Model; using TRRandomizerCore.Helpers; @@ -12,6 +13,7 @@ namespace TRRandomizerCore.Randomizers; public class TR1REnemyRandomizer : BaseTR1RRandomizer { private static readonly List _tihocanEndEnemies = new() { 73, 74, 82 }; + private const int _trexDeathAnimation = 10; private TR1EnemyAllocator _allocator; @@ -111,7 +113,9 @@ private void RandomizeEnemies(TR1RCombinedLevel level, EnemyRandomizationCollect private void ApplyPostRandomization(TR1RCombinedLevel level, EnemyRandomizationCollection enemies) { UpdateAtlanteanPDP(level, enemies); + HideTrexDeath(level); AdjustTihocanEnding(level); + AdjustScionEnding(level); AddUnarmedLevelAmmo(level); } @@ -126,6 +130,31 @@ private void UpdateAtlanteanPDP(TR1RCombinedLevel level, EnemyRandomizationColle DataCache.SetPDPData(level.PDPData, TR1Type.ShootingAtlantean_N, TR1Type.ShootingAtlantean_N); } + private void HideTrexDeath(TR1RCombinedLevel level) + { + if (!Settings.HideDeadTrexes || !level.Data.Models.ContainsKey(TR1Type.TRex)) + { + return; + } + + // Push T-rexes down on death, which ultimately disables their collision. Shift the final frame + // to the absolute maximum so it's not visible. + TRSetPositionCommand cmd = new() + { + Y = (short)level.Data.Rooms.Max(r => Math.Abs(r.Info.YBottom - r.Info.YTop)), + }; + + void UpdateModel(TRModel model) + { + TRAnimation deathAnimation = model.Animations[_trexDeathAnimation]; + deathAnimation.Commands.Add(cmd); + deathAnimation.Frames[^1].OffsetY = short.MaxValue; + } + + UpdateModel(level.Data.Models[TR1Type.TRex]); + UpdateModel(level.PDPData[TR1Type.TRex]); + } + private void AdjustTihocanEnding(TR1RCombinedLevel level) { if (!level.Is(TR1LevelNames.TIHOCAN) @@ -139,6 +168,17 @@ private void AdjustTihocanEnding(TR1RCombinedLevel level) level.Data.Entities.AddRange(TR1ItemAllocator.TihocanEndItems); } + private static void AdjustScionEnding(TR1RCombinedLevel level) + { + if (level.Data.Models.ContainsKey(TR1Type.ScionPiece4_S_P) + && (level.Data.Models.ContainsKey(TR1Type.TRex) || level.Data.Models.ContainsKey(TR1Type.Adam))) + { + // Ensure the scion is shootable in Atlantis. This is handled in OG with an environment condition, + // but support for PDP isn't there yet. + level.PDPData.ChangeKey(TR1Type.ScionPiece4_S_P, TR1Type.ScionPiece3_S_P); + } + } + private void AddUnarmedLevelAmmo(TR1RCombinedLevel level) { if (!level.Script.RemovesWeapons) diff --git a/TRRandomizerCore/Resources/TR1/Locations/enemy_relocations.json b/TRRandomizerCore/Resources/TR1/Locations/enemy_relocations.json index 42fab091..a4c46edc 100644 --- a/TRRandomizerCore/Resources/TR1/Locations/enemy_relocations.json +++ b/TRRandomizerCore/Resources/TR1/Locations/enemy_relocations.json @@ -10,13 +10,39 @@ "TargetType": 18 } ], + "LEVEL6.PHD": [ + { + "X": 36352, + "Y": -4352, + "Z": 37376, + "Room": 27, + "EntityIndex": 67, + "TargetType": 18 + }, + { + "X": 81408, + "Y": -4096, + "Z": 39424, + "Room": 6, + "Angle": 0, + "EntityIndex": 21, + "TargetType": 18 + }, + { + "X": 50688, + "Y": -2048, + "Z": 32256, + "Room": 19, + "EntityIndex": 26, + "TargetType": 18 + } + ], "LEVEL7A.PHD": [ { - "X": 42496, - "Y": -8448, - "Z": 40448, - "Room": 15, - "Angle": -32768, + "X": 40448, + "Y": -5632, + "Z": 42496, + "Room": 14, "EntityIndex": 21, "TargetType": 18 }, diff --git a/TRRandomizerCore/TRRandomizerController.cs b/TRRandomizerCore/TRRandomizerController.cs index a50ab1c3..deb7cac5 100644 --- a/TRRandomizerCore/TRRandomizerController.cs +++ b/TRRandomizerCore/TRRandomizerController.cs @@ -1238,6 +1238,12 @@ public bool RelocateAwkwardEnemies set => LevelRandomizer.RelocateAwkwardEnemies = value; } + public bool HideDeadTrexes + { + get => LevelRandomizer.HideDeadTrexes; + set => LevelRandomizer.HideDeadTrexes = value; + } + public BirdMonsterBehaviour BirdMonsterBehaviour { get => LevelRandomizer.BirdMonsterBehaviour; diff --git a/TRRandomizerCore/TRRandomizerType.cs b/TRRandomizerCore/TRRandomizerType.cs index c518c176..b194db95 100644 --- a/TRRandomizerCore/TRRandomizerType.cs +++ b/TRRandomizerCore/TRRandomizerType.cs @@ -69,4 +69,5 @@ public enum TRRandomizerType BlankTracks, TextureSwap, Wireframe, + HideDeadTrexes, } diff --git a/TRRandomizerCore/TRVersionSupport.cs b/TRRandomizerCore/TRVersionSupport.cs index f928df5f..aee76050 100644 --- a/TRRandomizerCore/TRVersionSupport.cs +++ b/TRRandomizerCore/TRVersionSupport.cs @@ -68,6 +68,7 @@ internal class TRVersionSupport TRRandomizerType.GlitchedSecrets, TRRandomizerType.HardSecrets, TRRandomizerType.HiddenEnemies, + TRRandomizerType.HideDeadTrexes, TRRandomizerType.Item, TRRandomizerType.KeyItems, TRRandomizerType.Secret, diff --git a/TRRandomizerView/Model/ControllerOptions.cs b/TRRandomizerView/Model/ControllerOptions.cs index 8afb6179..d73b886c 100644 --- a/TRRandomizerView/Model/ControllerOptions.cs +++ b/TRRandomizerView/Model/ControllerOptions.cs @@ -38,7 +38,7 @@ public class ControllerOptions : INotifyPropertyChanged private bool _useRewardRoomCameras; private uint _minSecretCount, _maxSecretCount; private BoolItemControlClass _includeKeyItems, _allowReturnPathLocations, _includeExtraPickups, _randomizeItemTypes, _randomizeItemLocations, _allowEnemyKeyDrops, _maintainKeyContinuity, _oneItemDifficulty; - private BoolItemControlClass _crossLevelEnemies, _protectMonks, _docileWillard, _swapEnemyAppearance, _allowEmptyEggs, _hideEnemies, _removeLevelEndingLarson, _giveUnarmedItems, _relocateAwkwardEnemies, _unrestrictedEnemyDifficulty; + private BoolItemControlClass _crossLevelEnemies, _protectMonks, _docileWillard, _swapEnemyAppearance, _allowEmptyEggs, _hideEnemies, _removeLevelEndingLarson, _giveUnarmedItems, _relocateAwkwardEnemies, _hideDeadTrexes, _unrestrictedEnemyDifficulty; private BoolItemControlClass _persistTextures, _randomizeWaterColour, _retainLevelTextures, _retainKeySpriteTextures, _retainSecretSpriteTextures, _retainEnemyTextures, _retainLaraTextures; private BoolItemControlClass _changeAmbientTracks, _includeBlankTracks, _changeTriggerTracks, _separateSecretTracks, _changeWeaponSFX, _changeCrashSFX, _changeEnemySFX, _changeDoorSFX, _linkCreatureSFX, _randomizeWibble; private BoolItemControlClass _persistOutfits, _removeRobeDagger, _allowGymOutfit; @@ -2517,6 +2517,16 @@ public BoolItemControlClass RelocateAwkwardEnemies } } + public BoolItemControlClass HideDeadTrexes + { + get => _hideDeadTrexes; + set + { + _hideDeadTrexes = value; + FirePropertyChanged(); + } + } + public BirdMonsterBehaviour BirdMonsterBehaviour { get => _birdMonsterBehaviour; @@ -3162,6 +3172,12 @@ public ControllerOptions() HelpURL = "https://github.com/LostArtefacts/TR-Rando/blob/master/Resources/Documentation/ENEMIES.md#awkward-enemies", }; BindingOperations.SetBinding(RelocateAwkwardEnemies, BoolItemControlClass.IsActiveProperty, randomizeEnemiesBinding); + HideDeadTrexes = new() + { + Title = "Hide dead T-rexes", + Description = "T-rexes retain collision on death in TR1R, so this option will move them out of the way when killed.", + }; + BindingOperations.SetBinding(HideDeadTrexes, BoolItemControlClass.IsActiveProperty, randomizeEnemiesBinding); ProtectMonks = new BoolItemControlClass() { Title = "Avoid having to kill allies", @@ -3467,7 +3483,7 @@ public ControllerOptions() }; EnemyBoolItemControls = new() { - _crossLevelEnemies, _docileWillard, _protectMonks, _swapEnemyAppearance, _allowEmptyEggs, _hideEnemies, _relocateAwkwardEnemies, _removeLevelEndingLarson, _giveUnarmedItems,_allowEnemyKeyDrops, _unrestrictedEnemyDifficulty + _crossLevelEnemies, _docileWillard, _protectMonks, _swapEnemyAppearance, _allowEmptyEggs, _hideEnemies, _relocateAwkwardEnemies, _hideDeadTrexes, _removeLevelEndingLarson, _giveUnarmedItems,_allowEnemyKeyDrops, _unrestrictedEnemyDifficulty }; TextureBoolItemControls = new() { @@ -3565,6 +3581,7 @@ private void AdjustAvailableOptions() _protectMonks.IsAvailable = !IsTR1; _docileWillard.IsAvailable = IsTR3; + _hideDeadTrexes.IsAvailable = IsHideDeadTrexesTypeSupported; _includeKeyItems.IsAvailable = IsKeyItemTypeSupported; _maintainKeyContinuity.IsAvailable = IsKeyContinuityTypeSupported; @@ -3694,6 +3711,7 @@ public void Load(TRRandomizerController controller) ProtectMonks.Value = _controller.ProtectMonks; DocileWillard.Value = _controller.DocileWillard; RelocateAwkwardEnemies.Value = _controller.RelocateAwkwardEnemies; + HideDeadTrexes.Value = _controller.HideDeadTrexes; BirdMonsterBehaviours = Enum.GetValues(); BirdMonsterBehaviour = _controller.BirdMonsterBehaviour; DragonSpawnTypes = Enum.GetValues(); @@ -4010,6 +4028,7 @@ public void Save() _controller.ProtectMonks = ProtectMonks.Value; _controller.DocileWillard = DocileWillard.Value; _controller.RelocateAwkwardEnemies = RelocateAwkwardEnemies.Value; + _controller.HideDeadTrexes = HideDeadTrexes.Value; _controller.BirdMonsterBehaviour = BirdMonsterBehaviour; _controller.DragonSpawnType = DragonSpawnType; _controller.SwapEnemyAppearance = SwapEnemyAppearance.Value; @@ -4257,6 +4276,7 @@ public void Unload() public bool IsKeyItemTexturesTypeSupported => IsRandomizationSupported(TRRandomizerType.KeyItemTextures); public bool IsWaterColourTypeSupported => IsRandomizationSupported(TRRandomizerType.WaterColour); public bool IsAtlanteanEggBehaviourTypeSupported => IsRandomizationSupported(TRRandomizerType.AtlanteanEggBehaviour); + public bool IsHideDeadTrexesTypeSupported => IsRandomizationSupported(TRRandomizerType.HideDeadTrexes); public bool IsHiddenEnemiesTypeSupported => IsRandomizationSupported(TRRandomizerType.HiddenEnemies); public bool IsLarsonBehaviourTypeSupported => IsRandomizationSupported(TRRandomizerType.LarsonBehaviour); public bool IsClonedEnemiesTypeSupported => IsRandomizationSupported(TRRandomizerType.ClonedEnemies);