From b8e5ac99eb40174dfb893d07eaa45be5fe0a26f1 Mon Sep 17 00:00:00 2001 From: lahm86 <33758420+lahm86@users.noreply.github.com> Date: Mon, 25 Apr 2022 20:51:23 +0100 Subject: [PATCH] #327 Killable Chickens Chicken behaviour can now be default, docile or unconditional - the latter meaning that they will be aggressive and the level won't end whey die. This replaced DocileBirdMonsters in the settings, which has been renamed to DocileWillard as it only applies to TR3 now. --- .../Editors/RandomizerSettings.cs | 9 +- .../Helpers/BirdMonsterBehaviour.cs | 7 ++ .../Randomizers/TR2/TR2EnemyRandomizer.cs | 33 ++++-- .../Randomizers/TR3/TR3EnemyRandomizer.cs | 2 +- TRRandomizerCore/TRRandomizerController.cs | 16 ++- TRRandomizerCore/TRRandomizerType.cs | 3 +- TRRandomizerView/Controls/EditorControl.xaml | 1 + TRRandomizerView/Model/ControllerOptions.cs | 46 +++++--- TRRandomizerView/Windows/AdvancedWindow.xaml | 104 ++++++++++++++++-- .../Windows/AdvancedWindow.xaml.cs | 26 +++++ 10 files changed, 198 insertions(+), 49 deletions(-) create mode 100644 TRRandomizerCore/Helpers/BirdMonsterBehaviour.cs diff --git a/TRRandomizerCore/Editors/RandomizerSettings.cs b/TRRandomizerCore/Editors/RandomizerSettings.cs index 6b56d1083..759c5fe38 100644 --- a/TRRandomizerCore/Editors/RandomizerSettings.cs +++ b/TRRandomizerCore/Editors/RandomizerSettings.cs @@ -53,7 +53,8 @@ public class RandomizerSettings public bool UseWireframeLadders { get; set; } public bool CrossLevelEnemies { get; set; } public bool ProtectMonks { get; set; } - public bool DocileBirdMonsters { get; set; } + public bool DocileWillard { get; set; } + public BirdMonsterBehaviour BirdMonsterBehaviour { get; set; } public RandoDifficulty RandoEnemyDifficulty { get; set; } public bool MaximiseDragonAppearance { get; set; } public bool UseEnemyExclusions { get; set; } @@ -131,7 +132,8 @@ public void ApplyConfig(Config config) EnemySeed = config.GetInt(nameof(EnemySeed), defaultSeed); CrossLevelEnemies = config.GetBool(nameof(CrossLevelEnemies), true); ProtectMonks = config.GetBool(nameof(ProtectMonks), true); - DocileBirdMonsters = config.GetBool(nameof(DocileBirdMonsters)); + DocileWillard = config.GetBool(nameof(DocileWillard)); + BirdMonsterBehaviour = (BirdMonsterBehaviour)config.GetEnum(nameof(BirdMonsterBehaviour), typeof(BirdMonsterBehaviour), BirdMonsterBehaviour.Default); RandoEnemyDifficulty = (RandoDifficulty)config.GetEnum(nameof(RandoEnemyDifficulty), typeof(RandoDifficulty), RandoDifficulty.Default); MaximiseDragonAppearance = config.GetBool(nameof(MaximiseDragonAppearance)); UseEnemyExclusions = config.GetBool(nameof(UseEnemyExclusions)); @@ -235,7 +237,8 @@ public void StoreConfig(Config config) config[nameof(EnemySeed)] = EnemySeed; config[nameof(CrossLevelEnemies)] = CrossLevelEnemies; config[nameof(ProtectMonks)] = ProtectMonks; - config[nameof(DocileBirdMonsters)] = DocileBirdMonsters; + config[nameof(DocileWillard)] = DocileWillard; + config[nameof(BirdMonsterBehaviour)] = BirdMonsterBehaviour; config[nameof(RandoEnemyDifficulty)] = RandoEnemyDifficulty; config[nameof(MaximiseDragonAppearance)] = MaximiseDragonAppearance; config[nameof(ExcludedEnemies)] = string.Join(",", ExcludedEnemies); diff --git a/TRRandomizerCore/Helpers/BirdMonsterBehaviour.cs b/TRRandomizerCore/Helpers/BirdMonsterBehaviour.cs new file mode 100644 index 000000000..7835c951e --- /dev/null +++ b/TRRandomizerCore/Helpers/BirdMonsterBehaviour.cs @@ -0,0 +1,7 @@ +namespace TRRandomizerCore.Helpers +{ + public enum BirdMonsterBehaviour + { + Default, Unconditional, Docile + } +} \ No newline at end of file diff --git a/TRRandomizerCore/Randomizers/TR2/TR2EnemyRandomizer.cs b/TRRandomizerCore/Randomizers/TR2/TR2EnemyRandomizer.cs index 75b1942ab..83427f6a4 100644 --- a/TRRandomizerCore/Randomizers/TR2/TR2EnemyRandomizer.cs +++ b/TRRandomizerCore/Randomizers/TR2/TR2EnemyRandomizer.cs @@ -100,7 +100,7 @@ private void RandomizeEnemiesCrossLevel() } // Track enemies whose counts across the game are restricted - _gameEnemyTracker = TR2EnemyUtilities.PrepareEnemyGameTracker(Settings.DocileBirdMonsters, Settings.RandoEnemyDifficulty); + _gameEnemyTracker = TR2EnemyUtilities.PrepareEnemyGameTracker(Settings.BirdMonsterBehaviour == BirdMonsterBehaviour.Docile, Settings.RandoEnemyDifficulty); // #272 Selective enemy pool - convert the shorts in the settings to actual entity types _excludedEnemies = Settings.UseEnemyExclusions ? @@ -195,7 +195,7 @@ private EnemyTransportCollection SelectCrossLevelEnemies(TR2CombinedLevel level, newEntities.Remove(TR2Entities.StickWieldingGoon1); newEntities.Add(newGoon); - if (Settings.DocileBirdMonsters) + if (Settings.BirdMonsterBehaviour == BirdMonsterBehaviour.Docile) { newEntities.Remove(TR2Entities.MaskedGoon1); newEntities.Add(TR2Entities.BirdMonster); @@ -293,13 +293,13 @@ private EnemyTransportCollection SelectCrossLevelEnemies(TR2CombinedLevel level, newEntities.Clear(); newEntities.AddRange(restrictedCombinations[_generator.Next(0, restrictedCombinations.Count)]); } - while (Settings.DocileBirdMonsters && newEntities.Contains(TR2Entities.BirdMonster) && chickenGuisers.All(g => newEntities.Contains(g)) + while (Settings.BirdMonsterBehaviour == BirdMonsterBehaviour.Docile && newEntities.Contains(TR2Entities.BirdMonster) && chickenGuisers.All(g => newEntities.Contains(g)) || (newEntities.Any(_excludedEnemies.Contains) && restrictedCombinations.Any(c => !c.Any(_excludedEnemies.Contains)))); break; } - // If it's the chicken in HSH but we're not using docile, we don't want it ending the level - if (!Settings.DocileBirdMonsters && entity == TR2Entities.BirdMonster && level.Is(TR2LevelNames.HOME) && allEnemies.Except(newEntities).Count() > 1) + // If it's the chicken in HSH with default behaviour, we don't want it ending the level + if (Settings.BirdMonsterBehaviour == BirdMonsterBehaviour.Default && entity == TR2Entities.BirdMonster && level.Is(TR2LevelNames.HOME) && allEnemies.Except(newEntities).Count() > 1) { continue; } @@ -334,7 +334,7 @@ private EnemyTransportCollection SelectCrossLevelEnemies(TR2CombinedLevel level, { // #144 We can include docile chickens provided we aren't including everything // that can be disguised as a chicken. - if (Settings.DocileBirdMonsters) + if (Settings.BirdMonsterBehaviour == BirdMonsterBehaviour.Docile) { bool guisersAvailable = !chickenGuisers.All(g => newEntities.Contains(g)); // If the selected entity is the chicken, it can be added provided there are @@ -381,7 +381,7 @@ private EnemyTransportCollection SelectCrossLevelEnemies(TR2CombinedLevel level, } // #144 Decide at this point who will be guising unless it has already been decided above (e.g. HSH) - if (Settings.DocileBirdMonsters && newEntities.Contains(TR2Entities.BirdMonster) && chickenGuiser == TR2Entities.BirdMonster) + if (Settings.BirdMonsterBehaviour == BirdMonsterBehaviour.Docile && newEntities.Contains(TR2Entities.BirdMonster) && chickenGuiser == TR2Entities.BirdMonster) { int guiserIndex = chickenGuisers.FindIndex(g => !newEntities.Contains(g)); if (guiserIndex != -1) @@ -450,7 +450,7 @@ private void RandomizeEnemiesNatively(TR2CombinedLevel level) List droppableEnemies = TR2EntityUtilities.DroppableEnemyTypes()[level.Name]; List waterEnemies = TR2EntityUtilities.FilterWaterEnemies(availableEnemyTypes); - if (Settings.DocileBirdMonsters && level.Is(TR2LevelNames.CHICKEN)) + if (Settings.BirdMonsterBehaviour == BirdMonsterBehaviour.Docile && level.Is(TR2LevelNames.CHICKEN)) { DisguiseEntity(level, TR2Entities.MaskedGoon1, TR2Entities.BirdMonster); } @@ -738,7 +738,7 @@ private void RandomizeEnemies(TR2CombinedLevel level, EnemyRandomizationCollecti // #144 Disguise something as the Chicken. Pre-checks will have been done to ensure // the guiser is suitable for the level. - if (Settings.DocileBirdMonsters && newEntityType == TR2Entities.BirdMonster) + if (Settings.BirdMonsterBehaviour == BirdMonsterBehaviour.Docile && newEntityType == TR2Entities.BirdMonster) { newEntityType = enemies.BirdMonsterGuiser; } @@ -815,6 +815,17 @@ private void RandomizeEnemies(TR2CombinedLevel level, EnemyRandomizationCollecti } RandomizeEnemyMeshes(level, enemies); + + if (Settings.BirdMonsterBehaviour == BirdMonsterBehaviour.Unconditional && !level.Is(TR2LevelNames.CHICKEN)) + { + TRModel model = Array.Find(level.Data.Models, m => m.ID == (uint)TR2Entities.BirdMonster); + if (model != null) + { + // #327 Trick the game into never reaching the final frame of the death animation. + // This results in a very abrupt death but avoids the level ending. + level.Data.Animations[model.Animation + 20].FrameEnd = level.Data.Animations[model.Animation + 19].FrameEnd; + } + } } private void LimitSkidooEntities(TR2CombinedLevel level) @@ -877,7 +888,7 @@ private void RandomizeEnemyMeshes(TR2CombinedLevel level, EnemyRandomizationColl List laraClones = new List(); const int chance = 2; - if (!Settings.DocileBirdMonsters) + if (Settings.BirdMonsterBehaviour != BirdMonsterBehaviour.Docile) { AddRandomLaraClone(enemies, TR2Entities.MonkWithKnifeStick, laraClones, chance); AddRandomLaraClone(enemies, TR2Entities.MonkWithLongStick, laraClones, chance); @@ -1063,7 +1074,7 @@ internal void ApplyRandomization() All = new List(importedCollection.EntitiesToImport) }; - if (_outer.Settings.DocileBirdMonsters && importedCollection.BirdMonsterGuiser != TR2Entities.BirdMonster) + if (_outer.Settings.BirdMonsterBehaviour == BirdMonsterBehaviour.Docile && importedCollection.BirdMonsterGuiser != TR2Entities.BirdMonster) { _outer.DisguiseEntity(level, importedCollection.BirdMonsterGuiser, TR2Entities.BirdMonster); enemies.BirdMonsterGuiser = importedCollection.BirdMonsterGuiser; diff --git a/TRRandomizerCore/Randomizers/TR3/TR3EnemyRandomizer.cs b/TRRandomizerCore/Randomizers/TR3/TR3EnemyRandomizer.cs index 171ca07f1..2e574bf59 100644 --- a/TRRandomizerCore/Randomizers/TR3/TR3EnemyRandomizer.cs +++ b/TRRandomizerCore/Randomizers/TR3/TR3EnemyRandomizer.cs @@ -191,7 +191,7 @@ private EnemyTransportCollection SelectCrossLevelEnemies(TR3CombinedLevel level) } } - if (!Settings.DocileBirdMonsters || Settings.OneEnemyMode || Settings.IncludedEnemies.Count < newEntities.Capacity) + 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 allEnemies.Remove(TR3Entities.Willie); diff --git a/TRRandomizerCore/TRRandomizerController.cs b/TRRandomizerCore/TRRandomizerController.cs index 4d579c79f..4bf0c174c 100644 --- a/TRRandomizerCore/TRRandomizerController.cs +++ b/TRRandomizerCore/TRRandomizerController.cs @@ -37,6 +37,10 @@ public class TRRandomizerController [TRVersion.TR2] = new List { TRRandomizerType.GlobeDisplay, TRRandomizerType.RewardRooms, TRRandomizerType.VFX + }, + [TRVersion.TR3] = new List + { + TRRandomizerType.BirdMonsterBehaviour } }; @@ -443,10 +447,16 @@ public bool ProtectMonks set => LevelRandomizer.ProtectMonks = value; } - public bool DocileBirdMonsters + public bool DocileWillard + { + get => LevelRandomizer.DocileWillard; + set => LevelRandomizer.DocileWillard = value; + } + + public BirdMonsterBehaviour BirdMonsterBehaviour { - get => LevelRandomizer.DocileBirdMonsters; - set => LevelRandomizer.DocileBirdMonsters = value; + get => LevelRandomizer.BirdMonsterBehaviour; + set => LevelRandomizer.BirdMonsterBehaviour = value; } public RandoDifficulty RandoEnemyDifficulty diff --git a/TRRandomizerCore/TRRandomizerType.cs b/TRRandomizerCore/TRRandomizerType.cs index a13c02cdb..be60afb3b 100644 --- a/TRRandomizerCore/TRRandomizerType.cs +++ b/TRRandomizerCore/TRRandomizerType.cs @@ -28,6 +28,7 @@ public enum TRRandomizerType GlobeDisplay, RewardRooms, VFX, - MaximumDragons + MaximumDragons, + BirdMonsterBehaviour } } \ No newline at end of file diff --git a/TRRandomizerView/Controls/EditorControl.xaml b/TRRandomizerView/Controls/EditorControl.xaml index e51938c3e..fd10f7dec 100644 --- a/TRRandomizerView/Controls/EditorControl.xaml +++ b/TRRandomizerView/Controls/EditorControl.xaml @@ -228,6 +228,7 @@ BoolItemsSource="{Binding Data.EnemyBoolItemControls, Source={StaticResource proxy}}" HasBoolItems="True" HasDifficulty="True" + HasBirdMonsterBehaviour="{Binding Data.IsBirdMonsterBehaviourTypeSupported, Source={StaticResource proxy}}" ControllerProxy="{Binding Data, Source={StaticResource proxy}}"> diff --git a/TRRandomizerView/Model/ControllerOptions.cs b/TRRandomizerView/Model/ControllerOptions.cs index 1a3c6e580..fae859867 100644 --- a/TRRandomizerView/Model/ControllerOptions.cs +++ b/TRRandomizerView/Model/ControllerOptions.cs @@ -25,7 +25,7 @@ public class ControllerOptions : INotifyPropertyChanged private BoolItemControlClass _isHardSecrets, _allowGlitched, _useRewardRoomCameras; private BoolItemControlClass _includeKeyItems; - private BoolItemControlClass _crossLevelEnemies, _protectMonks, _docileBirdMonsters, _maximiseDragonAppearance; + private BoolItemControlClass _crossLevelEnemies, _protectMonks, _docileWillard, _maximiseDragonAppearance; private BoolItemControlClass _persistTextures, _retainKeySpriteTextures, _retainSecretSpriteTextures; private BoolItemControlClass _includeBlankTracks, _changeTriggerTracks, _separateSecretTracks, _changeWeaponSFX, _changeCrashSFX, _changeEnemySFX, _linkCreatureSFX; private BoolItemControlClass _persistOutfits, _removeRobeDagger; @@ -66,6 +66,7 @@ public class ControllerOptions : INotifyPropertyChanged private RandoDifficulty _randoEnemyDifficulty; private ItemDifficulty _randoItemDifficulty; private GlobeDisplayOption _globeDisplayOption; + private BirdMonsterBehaviour _birdMonsterBehaviour; private Language[] _availableLanguages; private Language _gameStringLanguage; @@ -976,12 +977,22 @@ public BoolItemControlClass UseRewardRoomCameras } } - public BoolItemControlClass DocileBirdMonsters + public BoolItemControlClass DocileWillard { - get => _docileBirdMonsters; + get => _docileWillard; set { - _docileBirdMonsters = value; + _docileWillard = value; + FirePropertyChanged(); + } + } + + public BirdMonsterBehaviour BirdMonsterBehaviour + { + get => _birdMonsterBehaviour; + set + { + _birdMonsterBehaviour = value; FirePropertyChanged(); } } @@ -1292,12 +1303,12 @@ public ControllerOptions() Description = "Allow enemy types to appear in any level." }; BindingOperations.SetBinding(CrossLevelEnemies, BoolItemControlClass.IsActiveProperty, randomizeEnemiesBinding); - DocileBirdMonsters = new BoolItemControlClass() + DocileWillard = new BoolItemControlClass() { - Title = "Enable docile bird monsters", - Description = "Randomized bird monsters will not initiate on Lara and will not end the level upon death." + Title = "Enable docile Willard", + Description = "Willard can appear in levels other than Meteorite Cavern but will not attack Lara unless she gets too close." }; - BindingOperations.SetBinding(DocileBirdMonsters, BoolItemControlClass.IsActiveProperty, randomizeEnemiesBinding); + BindingOperations.SetBinding(DocileWillard, BoolItemControlClass.IsActiveProperty, randomizeEnemiesBinding); ProtectMonks = new BoolItemControlClass() { Title = "Avoid having to kill allies", @@ -1306,7 +1317,7 @@ public ControllerOptions() BindingOperations.SetBinding(ProtectMonks, BoolItemControlClass.IsActiveProperty, randomizeEnemiesBinding); MaximiseDragonAppearance = new BoolItemControlClass { - Title = "Maximise dragon spawns", + Title = "Maximize dragon spawns", Description = "Make a best effort attempt to have as many dragons as possible in the game." }; BindingOperations.SetBinding(MaximiseDragonAppearance, BoolItemControlClass.IsActiveProperty, randomizeEnemiesBinding); @@ -1448,7 +1459,7 @@ public ControllerOptions() }; EnemyBoolItemControls = new List() { - _crossLevelEnemies, _docileBirdMonsters, _protectMonks, _maximiseDragonAppearance + _crossLevelEnemies, _docileWillard, _protectMonks, _maximiseDragonAppearance }; TextureBoolItemControls = new List() { @@ -1487,13 +1498,9 @@ private void AdjustAvailableOptions() _useRewardRoomCameras.IsAvailable = IsRewardRoomsTypeSupported; - if (IsRewardRoomsTypeSupported) // i.e. IsTR3 - should make a TR version checker for the UI - { - DocileBirdMonsters.Title = "Enable docile Willard"; - DocileBirdMonsters.Description = "Willard can appear in levels other than Meteorite Cavern but will not attack Lara unless she gets too close."; - } - _maximiseDragonAppearance.IsAvailable = IsOutfitDaggerSupported; + + _docileWillard.IsAvailable = !IsBirdMonsterBehaviourTypeSupported; } public void Load(TRRandomizerController controller) @@ -1562,7 +1569,8 @@ public void Load(TRRandomizerController controller) EnemySeed = _controller.EnemySeed; CrossLevelEnemies.Value = _controller.CrossLevelEnemies; ProtectMonks.Value = _controller.ProtectMonks; - DocileBirdMonsters.Value = _controller.DocileBirdMonsters; + DocileWillard.Value = _controller.DocileWillard; + BirdMonsterBehaviour = _controller.BirdMonsterBehaviour; MaximiseDragonAppearance.Value = _controller.MaximiseDragonAppearance; RandoEnemyDifficulty = _controller.RandoEnemyDifficulty; UseEnemyExclusions = _controller.UseEnemyExclusions; @@ -1838,7 +1846,8 @@ public void Save() _controller.EnemySeed = EnemySeed; _controller.CrossLevelEnemies = CrossLevelEnemies.Value; _controller.ProtectMonks = ProtectMonks.Value; - _controller.DocileBirdMonsters = DocileBirdMonsters.Value; + _controller.DocileWillard = DocileWillard.Value; + _controller.BirdMonsterBehaviour = BirdMonsterBehaviour; _controller.MaximiseDragonAppearance = MaximiseDragonAppearance.Value; _controller.RandoEnemyDifficulty = RandoEnemyDifficulty; _controller.UseEnemyExclusions = UseEnemyExclusions; @@ -1927,6 +1936,7 @@ public void Unload() public bool IsOutfitDaggerSupported => IsRandomizationSupported(TRRandomizerType.OutfitDagger); public bool IsTextTypeSupported => IsRandomizationSupported(TRRandomizerType.Text); public bool IsEnvironmentTypeSupported => IsRandomizationSupported(TRRandomizerType.Environment); + public bool IsBirdMonsterBehaviourTypeSupported => IsRandomizationSupported(TRRandomizerType.BirdMonsterBehaviour); public bool IsDisableDemosTypeSupported => IsRandomizationSupported(TRRandomizerType.DisableDemos); diff --git a/TRRandomizerView/Windows/AdvancedWindow.xaml b/TRRandomizerView/Windows/AdvancedWindow.xaml index fdca7785a..5bb0a36ec 100644 --- a/TRRandomizerView/Windows/AdvancedWindow.xaml +++ b/TRRandomizerView/Windows/AdvancedWindow.xaml @@ -75,6 +75,7 @@ + @@ -182,8 +183,87 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -243,7 +323,7 @@ - @@ -286,7 +366,7 @@ - - - - - - - @@ -790,7 +870,7 @@ - - - SetValue(HasAudioOptionsProperty, value); } + public bool HasBirdMonsterBehaviour + { + get => (bool)GetValue(HasBirdMonsterBehaviourProperty); + set => SetValue(HasBirdMonsterBehaviourProperty, value); + } + public ControllerOptions ControllerProxy { get => (ControllerOptions)GetValue(ControllerProperty); @@ -220,6 +231,21 @@ private void Window_IsVisibleChanged(object sender, DependencyPropertyChangedEve break; } } + if (HasBirdMonsterBehaviour) + { + switch (ControllerProxy.BirdMonsterBehaviour) + { + case BirdMonsterBehaviour.Default: + _defaultBirdBehaviourButton.IsChecked = true; + break; + case BirdMonsterBehaviour.Unconditional: + _unconditionalBirdBehaviourButton.IsChecked = true; + break; + case BirdMonsterBehaviour.Docile: + _docileBirdBehaviourButton.IsChecked = true; + break; + } + } } private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)