Skip to content

Commit

Permalink
Relocate awkward enemies (#707)
Browse files Browse the repository at this point in the history
Resolves #311.
  • Loading branch information
lahm86 authored Jun 17, 2024
1 parent 9051247 commit d17e7eb
Show file tree
Hide file tree
Showing 15 changed files with 8,023 additions and 34 deletions.
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 past them. The UI option to move awkward enemies will - in most cases - move these enemies to better positions.

- 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

0 comments on commit d17e7eb

Please sign in to comment.