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

Miscellaneous TRR fixes #704

Merged
merged 20 commits into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from 17 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
Binary file modified Deps/TRGE.Coord.dll
Binary file not shown.
Binary file modified Deps/TRGE.Core.dll
Binary file not shown.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,16 @@ For keeping track of levels while you play, check out the [TRRandoTracker](https
![TR1 Rando](Resources/Screenshots/Compressed/TR1_3.jpg)
![TR1 Rando](Resources/Screenshots/Compressed/TR1_4.jpg)
![TR1R Rando](Resources/Screenshots/Compressed/TR1R_1.jpg)
![TR1R Rando](Resources/Screenshots/Compressed/TR1R_2.jpg)
![TR2 Rando](Resources/Screenshots/Compressed/TR2_1.jpg)
![TR2 Rando](Resources/Screenshots/Compressed/TR2_4.jpg)
![TR2 Rando](Resources/Screenshots/Compressed/TR2_5.jpg)
![TR2R Rando](Resources/Screenshots/Compressed/TR2R_1.jpg)
![TR2R Rando](Resources/Screenshots/Compressed/TR2R_2.jpg)
![TR3 Rando](Resources/Screenshots/Compressed/TR3_2.jpg)
![TR3 Rando](Resources/Screenshots/Compressed/TR3_3.jpg)
![TR3R Rando](Resources/Screenshots/Compressed/TR3R_1.jpg)
![TR3R Rando](Resources/Screenshots/Compressed/TR3R_2.jpg)

[View all](Resources/Screenshots).

Expand Down
2 changes: 1 addition & 1 deletion Resources/Documentation/RETURNPATHS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Return paths

Return paths can be added to the majority of levels, which allows repeat exploration without having to save and load. In some cases - such as The River Ganges - it also allows exploring levels fully where it would otherwise not be possible.
Return paths can be added to the majority of levels, which allows repeat exploration without having to save and load. In some cases - such as The River Ganges - it also allows exploring levels fully where it would otherwise not be possible. Return paths cannot currently be applied to TR I-III Remastered.

Following are details on the return paths that are available. See `Global Settings` for managing whether or not these paths are added.

Expand Down
2 changes: 2 additions & 0 deletions Resources/Documentation/SECRETS.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ The options are:
# TR2
The standard Stone, Jade and Gold dragons will be placed in each level.

Note that in TR2 Remastered, secret rewards cannot be changed: you will always receive the same rewards as per OG. In addition, while secrets are added to Dragon's Lair and Home Sweet Home, you do not receive any rewards for collecting all three in each level.

----
## TR3
Secret randomization logic in TR3 works in exactly the same way as TR1.
Expand Down
Binary file added Resources/Screenshots/Compressed/TR1R_2.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Resources/Screenshots/Compressed/TR2R_2.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Resources/Screenshots/Compressed/TR3R_2.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Resources/Screenshots/TR1R_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Resources/Screenshots/TR2R_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Resources/Screenshots/TR3R_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Resources/Using/game.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Resources/Using/trr_appid.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
43 changes: 23 additions & 20 deletions TRDataControl/Data/Remastered/BaseTRRDataCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,30 +40,33 @@ public void SetData(TRDictionary<TKey, TRModel> pdpData, Dictionary<TKey, TAlias

public void SetPDPData(TRDictionary<TKey, TRModel> pdpData, TKey sourceType, TKey destinationType)
{
TKey translatedKey = TranslateKey(sourceType);
if (!_pdpCache.ContainsKey(sourceType))
lock (_pdpCache)
{
string sourceLevel = GetSourceLevel(sourceType)
?? throw new KeyNotFoundException($"Source PDP file for {sourceType} is undefined");

TRPDPControlBase<TKey> control = GetPDPControl();
TRDictionary<TKey, TRModel> models = control.Read(Path.Combine(PDPFolder, Path.GetFileNameWithoutExtension(sourceLevel) + _pdpExt));
if (models.ContainsKey(translatedKey))
{
_pdpCache[sourceType] = models[translatedKey];
}
else if (models.ContainsKey(destinationType))
{
_pdpCache[sourceType] = models[destinationType];
}
else
TKey translatedKey = TranslateKey(sourceType);
if (!_pdpCache.ContainsKey(sourceType))
chreden marked this conversation as resolved.
Show resolved Hide resolved
{
throw new KeyNotFoundException($"Could not load cached PDP data for {sourceType}");
string sourceLevel = GetSourceLevel(sourceType)
?? throw new KeyNotFoundException($"Source PDP file for {sourceType} is undefined");

TRPDPControlBase<TKey> control = GetPDPControl();
TRDictionary<TKey, TRModel> models = control.Read(Path.Combine(PDPFolder, Path.GetFileNameWithoutExtension(sourceLevel) + _pdpExt));
if (models.ContainsKey(translatedKey))
{
_pdpCache[sourceType] = models[translatedKey];
}
else if (models.ContainsKey(destinationType))
{
_pdpCache[sourceType] = models[destinationType];
}
else
{
throw new KeyNotFoundException($"Could not load cached PDP data for {sourceType}");
}
}
}

translatedKey = TranslateKey(destinationType);
pdpData[translatedKey] = _pdpCache[sourceType];
translatedKey = TranslateKey(destinationType);
pdpData[translatedKey] = _pdpCache[sourceType];
}
}

public void SetMapData(Dictionary<TKey, TAlias> mapData, TKey sourceType, TKey destinationType)
Expand Down
26 changes: 21 additions & 5 deletions TRDataControl/Transport/TRDataImporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,17 @@ private void CleanRemovalList()
cleanedEntities.Add(type);
}
}

for (int i = cleanedEntities.Count - 1; i >= 0; i--)
Copy link
Collaborator

@chreden chreden Jun 18, 2024

Choose a reason for hiding this comment

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

This seems safer and easier to understand than the reverse iteration through a list that is being modified.

var dependencies = TypesToImport.SelectMany(t => Data.GetDependencies(t)).Select(t => Data.TranslateAlias(t));
TypesToRemove = cleanedEntities.Where(e => !dependencies.Contains(e)).ToList();

Alternatively could the check be added above? Aanother entityClean step - then it just wouldn't be added in the first place.

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, I've gone with the approach suggested. I think it's safest to do this at the end so that the full list is iterated first in case there are two types with the same dependency, like MaskedGoon2 and MaskedGoon3, who both depend on MaskedGoon1. Then we're not doing the same dependency check twice.

{
T type = cleanedEntities[i];
IEnumerable<T> dependencies = TypesToImport.SelectMany(t => Data.GetDependencies(t)).Select(t => Data.TranslateAlias(t));
if (dependencies.Contains(type))
{
cleanedEntities.RemoveAt(i);
}
}

TypesToRemove = cleanedEntities;
}

Expand Down Expand Up @@ -159,17 +170,22 @@ private void CleanAliases()

private void ValidateBlobList(List<T> modelTypes, List<B> importBlobs)
{
Dictionary<T, List<T>> detectedAliases = new();
foreach (T entity in modelTypes)
Dictionary<T, SortedSet<T>> detectedAliases = new();
foreach (T type in modelTypes)
{
if (Data.IsAlias(entity))
T inferredType = type;
if (Data.AliasPriority?.ContainsKey(type) ?? false)
{
inferredType = Data.GetLevelAlias(LevelName, type);
}
if (Data.IsAlias(inferredType))
{
T masterType = Data.TranslateAlias(entity);
T masterType = Data.TranslateAlias(inferredType);
if (!detectedAliases.ContainsKey(masterType))
{
detectedAliases[masterType] = new();
}
detectedAliases[masterType].Add(entity);
detectedAliases[masterType].Add(inferredType);
}
}

Expand Down
6 changes: 5 additions & 1 deletion TRLevelControl/Build/TRSpriteBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ public TRDictionary<T, TRSpriteSequence> ReadSprites(TRLevelReader reader)
{
string sprMarker = new(reader.ReadChars(_sprMarker.Length));
Debug.Assert(sprMarker == _sprMarker);
Debug.Assert(_version != TRGameVersion.TR5 || reader.ReadByte() == 0);
if (_version == TRGameVersion.TR5)
{
byte end = reader.ReadByte();
Debug.Assert(end == 0);
}
}

uint numTextures = reader.ReadUInt32();
Expand Down
3 changes: 2 additions & 1 deletion TRLevelControl/Build/TRTextureBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ public List<TRObjectTexture> ReadObjectTextures(TRLevelReader reader)
Debug.Assert(texMarker == _texMarker);
if (_version == TRGameVersion.TR5)
{
Debug.Assert(reader.ReadByte() == 0);
byte end = reader.ReadByte();
Debug.Assert(end == 0);
}
}

Expand Down
17 changes: 17 additions & 0 deletions TRLevelControl/Helpers/TR1TypeUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,23 @@ public static class TR1TypeUtilities
{
public static readonly Dictionary<TR1Type, Dictionary<TR1Type, List<string>>> LevelAliases = new()
{
[TR1Type.LaraMiscAnim_H] = new()
{
[TR1Type.LaraMiscAnim_H_General] =
new() { TR1LevelNames.CAVES, TR1LevelNames.VILCABAMBA, TR1LevelNames.FOLLY, TR1LevelNames.COLOSSEUM, TR1LevelNames.CISTERN, TR1LevelNames.TIHOCAN },
[TR1Type.LaraMiscAnim_H_Valley] =
new() { TR1LevelNames.VALLEY },
[TR1Type.LaraMiscAnim_H_Qualopec] =
new() { TR1LevelNames.QUALOPEC },
[TR1Type.LaraMiscAnim_H_Midas] =
new() { TR1LevelNames.MIDAS },
[TR1Type.LaraMiscAnim_H_Sanctuary] =
new() { TR1LevelNames.SANCTUARY },
[TR1Type.LaraMiscAnim_H_Atlantis] =
new() { TR1LevelNames.ATLANTIS },
[TR1Type.LaraMiscAnim_H_Pyramid] =
new() { TR1LevelNames.PYRAMID },
},
[TR1Type.FlyingAtlantean] = new()
{
[TR1Type.BandagedFlyer] =
Expand Down
15 changes: 15 additions & 0 deletions TRLevelControl/Helpers/TR2TypeUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,21 @@ public static class TR2TypeUtilities
[TR2Type.LaraAssault] =
new() { TR2LevelNames.ASSAULT },
},
[TR2Type.LaraMiscAnim_H] = new()
{
[TR2Type.LaraMiscAnim_H_Wall] =
new() { TR2LevelNames.GW },
[TR2Type.LaraMiscAnim_H_Venice] =
new() { TR2LevelNames.BARTOLI },
[TR2Type.LaraMiscAnim_H_Unwater] =
new() { TR2LevelNames.RIG, TR2LevelNames.DA, TR2LevelNames.FATHOMS, TR2LevelNames.DORIA, TR2LevelNames.DECK },
[TR2Type.LaraMiscAnim_H_Ice] =
new() { TR2LevelNames.COT, TR2LevelNames.CHICKEN },
[TR2Type.LaraMiscAnim_H_Xian] =
new() { TR2LevelNames.FLOATER, TR2LevelNames.LAIR },
[TR2Type.LaraMiscAnim_H_HSH] =
new() { TR2LevelNames.HOME }
},
[TR2Type.Barracuda] = new()
{
[TR2Type.BarracudaIce] =
Expand Down
20 changes: 20 additions & 0 deletions TRLevelControl/Model/Common/FloorData/FDControl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,26 @@ public void RemoveEntityTriggers(int entityIndex)
}
}

public List<short> GetTriggerRooms<R>(int entityIndex, List<R> rooms)
where R : TRRoom
{
List<FDTriggerEntry> triggers = GetEntityTriggers(entityIndex);
List<short> triggerRooms = new();
for (short i = 0; i < rooms.Count; i++)
{
foreach (TRRoomSector sector in rooms[i].Sectors.Where(s => s.FDIndex != 0))
{
if (triggers.Any(_entries[sector.FDIndex].Contains))
{
triggerRooms.Add(i);
break;
}
}
}

return triggerRooms;
Copy link
Collaborator

Choose a reason for hiding this comment

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

This could be a fairly simple looking Linq one-liner if Room knew what number room it was - but perhaps something for the future.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, that would be good to implement. I don't think we shuffle rooms around anywhere so it's probably not something that would need management, but can have a think about it for the future. I've tidied it up for now anyway with some Linq so it's less loopy, and there is a test to cover it as well.

}

public TRRoomSector GetRoomSector<R>(int x, int y, int z, short roomNumber, List<R> rooms)
where R : TRRoom
{
Expand Down
3 changes: 2 additions & 1 deletion TRLevelControl/TRGControlBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ public static TRGData Read(string filePath)
public static TRGData Read(Stream stream)
{
using TRLevelReader reader = new(stream);
Debug.Assert(reader.ReadUInt32() == _trgMagic);
uint magic = reader.ReadUInt32();
Debug.Assert(magic == _trgMagic);

TRGData data = new();

Expand Down
3 changes: 3 additions & 0 deletions TRRandomizerCore/Helpers/ChecksumTester.cs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,9 @@ public bool Test(string file)
"79e3f05f963acac6f864ba81555cc15d", // MONASTRY.TR2
"cf4f784bdfa0b5794b3437e4d9ed04d8", // ICECAVE.TR2

// TR2R V1.3+
"ef2f5a3e08bd10655c38707e6657c687", // LEVEL5.TR2

// TR3
"5e11d251ddb12b98ebead1883dc12d2a", // HOUSE.TR2
"9befdc5075fdb84450d2ed0533719b12", // JUNGLE.TR2 (International)
Expand Down
2 changes: 2 additions & 0 deletions TRRandomizerCore/Randomizers/Shared/EnvironmentPicker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ public List<EMEditorSet> GetRandomAny(EMEditorMapping mapping)
{
// Pick a random number of packs to apply, but at least 1
sets = pool.RandomSelection(_generator, _generator.Next(1, pool.Count + 1));
// Ensure original order is kept as some mods rely on other things happening first
sets.Sort((s1, s2) => pool.IndexOf(s1).CompareTo(pool.IndexOf(s2)));
}

return sets;
Expand Down
5 changes: 1 addition & 4 deletions TRRandomizerCore/Randomizers/Shared/ItemAllocator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -326,10 +326,7 @@ public void ApplyItemSwaps(string levelName, List<E> items)
}
}

if (!Settings.AllowEnemyKeyDrops)
{
ExcludeEnemyKeyDrops(items);
}
ExcludeEnemyKeyDrops(items);
}

protected List<E> GetPickups(string levelName, List<E> items, bool isUnarmed)
Expand Down
4 changes: 2 additions & 2 deletions TRRandomizerCore/Randomizers/Shared/LocationPicker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public class LocationPicker : IRouteManager
private Random _generator;

public Func<Location, bool> TriggerTestAction { get; set; }
public Func<Location, bool, bool> KeyItemTestAction { get; set; }
public Func<Location, bool, List<short>, bool> KeyItemTestAction { get; set; }
public List<ExtRoomInfo> RoomInfos { get; set; }
public int LevelSize { get; private set; }

Expand Down Expand Up @@ -138,7 +138,7 @@ public Location GetKeyItemLocation<T>(int keyItemID, TREntity<T> entity, bool ha
continue;
}

if (KeyItemTestAction != null && !KeyItemTestAction(newLocation, hasPickupTrigger))
if (KeyItemTestAction != null && !KeyItemTestAction(newLocation, hasPickupTrigger, roomPool))
{
continue;
}
Expand Down
42 changes: 32 additions & 10 deletions TRRandomizerCore/Randomizers/Shared/TextureAllocator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class TextureAllocator<T, R>
where R : Enum
{
private readonly TRTexInfo<T> _texInfo;
private readonly Dictionary<TRRScriptedLevel, TRGData> _trgData;
private readonly Dictionary<TRRScriptedLevel, TRGData> _trgData, _cutTrgData;
private readonly Dictionary<TRRScriptedLevel, Dictionary<T, R>> _mapData;

public Random Generator { get; set; }
Expand All @@ -22,6 +22,7 @@ public TextureAllocator(TRGameVersion version)
{
_texInfo = JsonConvert.DeserializeObject<TRTexInfo<T>>(File.ReadAllText($@"Resources\{version}\Textures\texinfo.json"));
_trgData = new();
_cutTrgData = new();
_mapData = new();
}

Expand All @@ -31,6 +32,11 @@ public void LoadData(TRRScriptedLevel level, TRGData trgData, Dictionary<T, R> m
_mapData[level] = mapData;
}

public void LoadCutData(TRRScriptedLevel parentLevel, TRGData trgData)
{
_cutTrgData[parentLevel] = trgData;
}

public void Allocate(Func<TRRScriptedLevel, TRGData, Dictionary<T, R>, bool> saveData)
{
Dictionary<TRRScriptedLevel, List<ushort>> textureCache = _trgData.ToDictionary(l => l.Key, l => l.Value.Textures.ToList());
Expand All @@ -48,7 +54,7 @@ public void Allocate(Func<TRRScriptedLevel, TRGData, Dictionary<T, R>, bool> sav
while (levelSwaps.Any(l => levels.IndexOf(l) == levelSwaps.IndexOf(l)));
}

foreach (var (level, trgData) in _trgData)
Dictionary<T, R> AllocateLevel(TRRScriptedLevel level, TRGData trgData)
{
List<ushort> baseTextures = new();
List<ushort> newTextures = new();
Expand All @@ -60,13 +66,15 @@ public void Allocate(Func<TRRScriptedLevel, TRGData, Dictionary<T, R>, bool> sav
else if (Settings.TextureMode == TextureMode.Game)
{
TRRScriptedLevel nextLevel = levelSwaps[levels.IndexOf(level)];
baseTextures.AddRange(textureCache[nextLevel]);
newTextures = textureCache[nextLevel];

while (newTextures.Count < trgData.Textures.Count)
List<ushort> textureSet = textureCache[nextLevel].RandomSelection(Generator,
Math.Min(trgData.Textures.Count, textureCache[nextLevel].Distinct().Count()));
while (textureSet.Count < trgData.Textures.Count)
{
newTextures.Add(newTextures.RandomItem(Generator));
textureSet.Add(textureCache[nextLevel].RandomItem(Generator));
}

baseTextures.AddRange(textureCache[nextLevel]);
newTextures.AddRange(textureSet);
}
else
{
Expand Down Expand Up @@ -108,16 +116,30 @@ public void Allocate(Func<TRRScriptedLevel, TRGData, Dictionary<T, R>, bool> sav
animatedIndices.ForEach(i => newTextures[i] = defaultTexture);
}

Dictionary<T, R> itemRemap = Settings.TextureMode == TextureMode.Game && Settings.MatchTextureItems
trgData.Textures.Clear();
trgData.Textures.AddRange(newTextures);

return Settings.TextureMode == TextureMode.Game && Settings.MatchTextureItems
? RemapItems(level, levelSwaps[levels.IndexOf(level)])
: null;
}

trgData.Textures.Clear();
trgData.Textures.AddRange(newTextures);
foreach (var (level, trgData) in _trgData)
{
Dictionary<T, R> itemRemap = AllocateLevel(level, trgData);
if (!saveData(level, trgData, itemRemap))
{
break;
}

if (_cutTrgData.ContainsKey(level))
{
AllocateLevel(level, _cutTrgData[level]);
if (!saveData(level.CutSceneLevel as TRRScriptedLevel, _cutTrgData[level], null))
{
break;
}
}
}
}

Expand Down
11 changes: 5 additions & 6 deletions TRRandomizerCore/Randomizers/TR1/Classic/TR1ItemRandomizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,11 @@ public void FinalizeRandomization()
else
{
_allocator.RandomizeKeyItems(_levelInstance.Name, _levelInstance.Data, _levelInstance.Script.OriginalSequence);
}

if (Settings.AllowEnemyKeyDrops)
{
UpdateEnemyItemDrops(_levelInstance, _levelInstance.Data.Entities
.Where(e => TR1TypeUtilities.IsKeyItemType(e.TypeID)));
if (Settings.AllowEnemyKeyDrops)
{
UpdateEnemyItemDrops(_levelInstance, _levelInstance.Data.Entities
.Where(e => TR1TypeUtilities.IsKeyItemType(e.TypeID)));
}
}

SaveLevelInstance();
Expand Down
Loading
Loading