Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Relocate awkward enemies #707

Merged
merged 6 commits into from
Jun 17, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- added separate secret audio for TR1 and TR3 when not using reward rooms (#687)
- added an option to shuffle items rather than randomize their types and locations in each level (#625)
- added an option to control weapon allocation in item randomization (#690)
- added an option to move enemies such as eels, whose placement can lead to forced damage or difficulty in passing (#311)
- added Finnish, Portuguese, and Swedish translations to TR1 and added all supported language translations to TRUB (#701)
- fixed several potential key item softlocks in TR2 (#691)
- fixed a key item softlock in Crash Site (#662)
Expand Down
9 changes: 9 additions & 0 deletions Resources/Documentation/ENEMIES.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Jump to:
* [TR1](#tr1)
* [TR2](#tr2)
* [TR3](#tr3)
* [Awkward enemies](#awkward-enemies)

# TR1
The following enemy restrictions are in place when using "Default" restriction mode during randomization.
Expand Down Expand Up @@ -178,6 +179,7 @@ There are further nuances with other enemy types in TR3.
* RXTechFlameLad is hostile to Lara in any level that falls on Meteorite Cavern's sequence.
* Crash site Mercenary and RXTechFlameLad will fight each other. The Mercenary always wins.
* Raptors (288) will attack any other enemy, other than other raptors, unless Lara is the closest target.
* The T-rex will only ever appear in Caves of Kaliya when _No Restrictions_ mode is enabled.

## Spawn Points

Expand Down Expand Up @@ -216,3 +218,10 @@ Because of the increased limits in TR3, most levels have had their number of ene
| CITY.TR2 | 13 | 2 | 6 |
| CHAMBER.TR2 | 7 | 3 | 4 |
| _STPAUL.TR2_ | _2_ | _2_ | _2_ |

# Awkward enemies
Some enemy placements can cause problems by default, such as causing forced damage or difficulty in getting passed them. The UI option to move awkward enemies will - in most cases - move these enemies to better positions.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo passed->past?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks.


- TR1: some T-rex and Torso locations are affected, for example on the narrow corridors leading to the Atlantis main chamber.
- TR2: both types of eel will be moved off the ground in all cases when they are land creatures. This means that they effectively become hazards - they may still be able to snipe Lara if she is jumping, so take care!
- TR3: the T-rex is moved in several instances to avoid blocking corridors and crawlspaces.
3 changes: 3 additions & 0 deletions TRRandomizerCore/Editors/RandomizerSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ public class RandomizerSettings
public bool CrossLevelEnemies { get; set; }
public bool ProtectMonks { get; set; }
public bool DocileWillard { get; set; }
public bool RelocateAwkwardEnemies { get; set; }
public BirdMonsterBehaviour BirdMonsterBehaviour { get; set; }
public bool DefaultChickens => BirdMonsterBehaviour == BirdMonsterBehaviour.Default;
public bool DocileChickens => BirdMonsterBehaviour == BirdMonsterBehaviour.Docile;
Expand Down Expand Up @@ -231,6 +232,7 @@ public void ApplyConfig(Config config)
CrossLevelEnemies = config.GetBool(nameof(CrossLevelEnemies), true);
ProtectMonks = config.GetBool(nameof(ProtectMonks), true);
DocileWillard = config.GetBool(nameof(DocileWillard));
RelocateAwkwardEnemies = config.GetBool(nameof(RelocateAwkwardEnemies), 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);
Expand Down Expand Up @@ -404,6 +406,7 @@ public void StoreConfig(Config config)
config[nameof(CrossLevelEnemies)] = CrossLevelEnemies;
config[nameof(ProtectMonks)] = ProtectMonks;
config[nameof(DocileWillard)] = DocileWillard;
config[nameof(RelocateAwkwardEnemies)] = RelocateAwkwardEnemies;
config[nameof(BirdMonsterBehaviour)] = BirdMonsterBehaviour;
config[nameof(RandoEnemyDifficulty)] = RandoEnemyDifficulty;
config[nameof(DragonSpawnType)] = DragonSpawnType;
Expand Down
32 changes: 31 additions & 1 deletion TRRandomizerCore/Randomizers/Shared/EnemyAllocator.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,31 @@
using TRLevelControl.Model;
using Newtonsoft.Json;
using TRLevelControl.Model;
using TRRandomizerCore.Editors;
using TRRandomizerCore.Helpers;
using TRRandomizerCore.Utilities;

namespace TRRandomizerCore.Randomizers;

public abstract class EnemyAllocator<T>
where T : Enum
{
protected Dictionary<T, List<string>> _gameEnemyTracker;
protected Dictionary<string, List<Location>> _relocations;
protected List<T> _excludedEnemies;
protected HashSet<T> _resultantEnemies;

public RandomizerSettings Settings { get; set; }
public Random Generator { get; set; }
public IEnumerable<string> GameLevels { get; set; }

public EnemyAllocator(TRGameVersion version)
{
string relocFile = $@"Resources\{version}\Locations\enemy_relocations.json";
_relocations = File.Exists(relocFile)
? JsonConvert.DeserializeObject<Dictionary<string, List<Location>>>(File.ReadAllText(relocFile))
: new();
}

public void Initialise()
{
_resultantEnemies = new();
Expand Down Expand Up @@ -92,6 +103,25 @@ protected void SetOneShot<E>(E entity, int index, FDControl floorData)
.ForEach(t => t.OneShot = true);
}

protected void RelocateEnemies<E>(string levelName, List<E> entities)
where E : TREntity<T>
{
if (!Settings.RelocateAwkwardEnemies || !_relocations.ContainsKey(levelName))
{
return;
}

foreach (Location location in _relocations[levelName])
{
E enemy = entities[location.EntityIndex];
if (EqualityComparer<T>.Default.Equals(enemy.TypeID, (T)(object)(uint)location.TargetType))
{
enemy.SetLocation(location);
enemy.X++; // Avoid shifted enemies picking anything up
}
}
}

protected abstract Dictionary<T, List<string>> GetGameTracker();
protected abstract bool IsEnemySupported(string levelName, T type, RandoDifficulty difficulty);
protected abstract Dictionary<T, List<int>> GetRestrictedRooms(string levelName, RandoDifficulty difficulty);
Expand Down
3 changes: 3 additions & 0 deletions TRRandomizerCore/Randomizers/TR1/Shared/TR1EnemyAllocator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public class TR1EnemyAllocator : EnemyAllocator<TR1Type>
public ItemFactory<TR1Entity> ItemFactory { get; set; }

public TR1EnemyAllocator()
: base(TRGameVersion.TR1)
{
_pistolLocations = JsonConvert.DeserializeObject<Dictionary<string, List<Location>>>(File.ReadAllText(@"Resources\TR1\Locations\unarmed_locations.json"));
_eggLocations = JsonConvert.DeserializeObject<Dictionary<string, List<Location>>>(File.ReadAllText(@"Resources\TR1\Locations\egg_locations.json"));
Expand Down Expand Up @@ -526,6 +527,8 @@ public void RandomizeEnemies(string levelName, TR1Level level, EnemyRandomizatio
SetOneShot(currentEntity, entityIndex, level.FloorData);
_resultantEnemies.Add(newType);
}

RelocateEnemies(levelName, level.Entities);
}

private static int GetEntityCount(TR1Level level, TR1Type entityType)
Expand Down
5 changes: 5 additions & 0 deletions TRRandomizerCore/Randomizers/TR2/Shared/TR2EnemyAllocator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ public class TR2EnemyAllocator : EnemyAllocator<TR2Type>
TR2LevelNames.MONASTERY,
};

public TR2EnemyAllocator()
: base(TRGameVersion.TR2) { }

public List<string> DragonLevels { get; set; }
public ItemFactory<TR2Entity> ItemFactory { get; set; }

Expand Down Expand Up @@ -581,6 +584,8 @@ public void RandomizeEnemies(string levelName, TR2Level level, EnemyRandomizatio
LimitFriendlyEnemies(level, enemies.Available.Except(friends).ToList(), friends);
}

RelocateEnemies(levelName, level.Entities);

if (!Settings.AllowEnemyKeyDrops)
{
// Referenced here in case item randomization is not enabled.
Expand Down
3 changes: 3 additions & 0 deletions TRRandomizerCore/Randomizers/TR3/Shared/TR3EnemyAllocator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public class TR3EnemyAllocator : EnemyAllocator<TR3Type>
public ItemFactory<TR3Entity> ItemFactory { get; set; }

public TR3EnemyAllocator()
: base(TRGameVersion.TR3)
{
_pistolLocations = JsonConvert.DeserializeObject<Dictionary<string, List<Location>>>(File.ReadAllText(@"Resources\TR3\Locations\unarmed_locations.json"));
}
Expand Down Expand Up @@ -411,6 +412,8 @@ public void RandomizeEnemies(string levelName, TR3Level level, int levelSequence
_resultantEnemies.Add(newType);
}

RelocateEnemies(levelName, level.Entities);

if (!Settings.AllowEnemyKeyDrops)
{
TR3ItemAllocator allocator = new();
Expand Down
168 changes: 168 additions & 0 deletions TRRandomizerCore/Resources/TR1/Locations/enemy_relocations.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
{
"LEVEL2.PHD": [
{
"X": 31232,
"Y": -7936,
"Z": 36352,
"Room": 67,
"Angle": -32768,
"EntityIndex": 85,
"TargetType": 18
}
],
"LEVEL7A.PHD": [
{
"X": 42496,
"Y": -8448,
"Z": 40448,
"Room": 15,
"Angle": -32768,
"EntityIndex": 21,
"TargetType": 18
},
{
"X": 53760,
"Y": -3584,
"Z": 39424,
"Room": 39,
"Angle": -16384,
"EntityIndex": 43,
"TargetType": 18
},
{
"X": 45568,
"Y": -3328,
"Z": 51712,
"Room": 95,
"Angle": 0,
"EntityIndex": 44,
"TargetType": 18
},
{
"X": 40448,
"Y": -3328,
"Z": 64000,
"Room": 99,
"Angle": -32768,
"EntityIndex": 101,
"TargetType": 18
}
],
"LEVEL7B.PHD": [
{
"X": 54784,
"Y": -3840,
"Z": 75264,
"Room": 70,
"Angle": -32768,
"EntityIndex": 31,
"TargetType": 18
},
{
"X": 54784,
"Y": -3840,
"Z": 74240,
"Room": 70,
"Angle": -32768,
"EntityIndex": 31,
"TargetType": 34
},
{
"X": 38400,
"Y": -4096,
"Z": 62976,
"Room": 77,
"Angle": 0,
"EntityIndex": 34,
"TargetType": 18
},
{
"X": 38400,
"Y": -4096,
"Z": 64000,
"Room": 77,
"Angle": 0,
"EntityIndex": 34,
"TargetType": 34
}
],
"LEVEL8C.PHD": [
{
"X": 47616,
"Y": -768,
"Z": 38400,
"Room": 13,
"EntityIndex": 32,
"TargetType": 18
},
{
"X": 33280,
"Y": 17920,
"Z": 44544,
"Room": 5,
"EntityIndex": 42,
"TargetType": 18
},
{
"X": 31232,
"Y": 17920,
"Z": 44544,
"Room": 5,
"EntityIndex": 42,
"TargetType": 34
}
],
"LEVEL10B.PHD": [
{
"X": 62976,
"Y": 0,
"Z": 46592,
"Room": 38,
"EntityIndex": 0,
"TargetType": 18
},
{
"X": 43520,
"Y": 3328,
"Z": 44544,
"Room": 78,
"EntityIndex": 1,
"TargetType": 18
},
{
"X": 54784,
"Y": 5120,
"Z": 44544,
"Room": 1,
"EntityIndex": 1,
"TargetType": 34
},
{
"X": 53760,
"Y": -13056,
"Z": 56832,
"Room": 47,
"EntityIndex": 8,
"TargetType": 18
},
{
"X": 65024,
"Y": -10240,
"Z": 51712,
"Room": 46,
"Angle": -32768,
"EntityIndex": 9,
"TargetType": 18
}
],
"LEVEL10C.PHD": [
{
"X": 53760,
"Y": 9728,
"Z": 75264,
"Room": 34,
"EntityIndex": 101,
"TargetType": 18
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -1055,10 +1055,18 @@
"Condition": {
"Comments": "If enemy 103's type has been randomized, move him outside for some breathing space.",
"ConditionType": 0,
"Negate": true,
"And": [
{
"ConditionType": 0,
"EntityIndex": 103,
"Room": 110
}
],
"EntityIndex": 103,
"EntityType": 32
},
"OnFalse": [
"OnTrue": [
{
"EMType": 44,
"EntityIndex": 103,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2587,34 +2587,6 @@
}
}
]
},
{
"Condition": {
"Comments": "If enemy 59 is an eel, rotate it to avoid blocking puzzle slots.",
"ConditionType": 0,
"Or": [
{
"ConditionType": 0,
"EntityIndex": 59,
"EntityType": 26
}
],
"EntityIndex": 59,
"EntityType": 27
},
"OnTrue": [
{
"EMType": 44,
"EntityIndex": 59,
"TargetLocation": {
"X": 59904,
"Y": -3584,
"Z": 75264,
"Room": 30,
"Angle": -32768
}
}
]
}
],
"ConditionalOneOf": [],
Expand Down
Loading
Loading