Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
188 changes: 74 additions & 114 deletions RandomizerCore/Sidescroll/RoomPool.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using NLog.Targets;
using SD.Tools.Algorithmia.GeneralDataStructures;
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
Expand Down Expand Up @@ -29,35 +27,6 @@ protected RoomPool() { }

protected static readonly IEqualityComparer<byte[]> byteArrayEqualityComparer = new Util.StandardByteArrayEqualityComparer();

public RoomPool(RoomPool target)
{
NormalRooms.AddRange(target.NormalRooms);
Entrances.AddRange(target.Entrances);
BossRooms.AddRange(target.BossRooms);
TbirdRooms.AddRange(target.TbirdRooms);
ItemRooms.AddRange(target.ItemRooms);
VanillaBossRoom = target.VanillaBossRoom;
DefaultUpEntrance = target.DefaultUpEntrance;
DefaultDownBossRoom = target.DefaultDownBossRoom;
foreach (var pair in target.LinkedRooms)
{
LinkedRooms.Add(pair.Key, pair.Value);
}
foreach (var direction in target.ItemRoomsByDirection.Keys)
{
ItemRoomsByDirection[direction] = target.ItemRoomsByDirection[direction];
}
foreach (var shape in target.ItemRoomsByShape.Keys)
{
var ls = target.ItemRoomsByShape[shape];
ItemRoomsByShape[shape] = ls.ToList();
}
foreach (var key in target.DefaultStubsByDirection.Keys)
{
DefaultStubsByDirection.Add(key, target.DefaultStubsByDirection[key]);
}
}

public RoomPool(PalaceRooms palaceRooms, int palaceNumber, RandomizerProperties props)
{
Dictionary<Direction, List<(Room, int)>> roomDirectionWeightsByDirection = [];
Expand All @@ -71,96 +40,19 @@ public RoomPool(PalaceRooms palaceRooms, int palaceNumber, RandomizerProperties
//GP also has vanilla rooms added.
|| (palaceNumber == 7 && !props.AllowVanillaRooms && !props.AllowV4Rooms && props.AllowV5_0Rooms))
{
Entrances.AddRange(palaceRooms.Entrances(RoomGroup.VANILLA)
.Where(i => i.PalaceNumber == null || i.PalaceNumber == palaceNumber).ToList());
BossRooms.AddRange(palaceRooms.BossRooms(RoomGroup.VANILLA)
.Where(i => (i.PalaceNumber == null && palaceNumber < 6) || i.PalaceNumber == palaceNumber).ToList());
TbirdRooms.AddRange(palaceRooms.ThunderBirdRooms(RoomGroup.VANILLA)
.Where(i => i.PalaceNumber == null || i.PalaceNumber == palaceNumber).ToList());
ItemRooms.AddRange(palaceRooms.ItemRooms(RoomGroup.VANILLA)
.Where(i => i.PalaceNumber == null || i.PalaceNumber == palaceNumber).ToList());
foreach (var room in palaceRooms.LinkedRooms(RoomGroup.VANILLA))
{
LinkedRooms.Add(room.Key, room.Value);
}
foreach (var direction in DirectionExtensions.ITEM_ROOM_ORIENTATIONS)
{
foreach (Room room in palaceRooms.ItemRoomsByDirection(RoomGroup.VANILLA, direction).ToList())
{
bool[] weights = [room.HasUpExit, room.HasDownExit, room.HasLeftExit, room.HasRightExit, room.IsDropZone];
roomDirectionWeightsByDirection[direction].Add((room, 5 - weights.Count(i => i)));
}
}
foreach(var shape in palaceRooms.ItemRooms(RoomGroup.VANILLA).Select(i => i.CategorizeExits()).Distinct())
{
var newRooms = palaceRooms.ItemRoomsByShape(RoomGroup.VANILLA, shape);
var ls = ItemRoomsByShape.GetValueOrDefault(shape, []);
ls.AddRange(newRooms);
ItemRoomsByShape[shape] = ls;
}
AddRoomGroup(palaceRooms, RoomGroup.VANILLA, palaceNumber, roomDirectionWeightsByDirection);
}

if (props.AllowV4Rooms)
{
Entrances.AddRange(palaceRooms.Entrances(RoomGroup.V4_0)
.Where(i => i.PalaceNumber == null || i.PalaceNumber == palaceNumber).ToList());
BossRooms.AddRange(palaceRooms.BossRooms(RoomGroup.V4_0)
.Where(i => (i.PalaceNumber == null && palaceNumber < 6) || i.PalaceNumber == palaceNumber).ToList());
TbirdRooms.AddRange(palaceRooms.ThunderBirdRooms(RoomGroup.V4_0)
.Where(i => i.PalaceNumber == null || i.PalaceNumber == palaceNumber).ToList());
ItemRooms.AddRange(palaceRooms.ItemRooms(RoomGroup.V4_0)
.Where(i => i.PalaceNumber == null || i.PalaceNumber == palaceNumber).ToList());
foreach (var room in palaceRooms.LinkedRooms(RoomGroup.V4_0))
{
LinkedRooms.Add(room.Key, room.Value);
}
foreach (var direction in DirectionExtensions.ITEM_ROOM_ORIENTATIONS)
{
foreach (Room room in palaceRooms.ItemRoomsByDirection(RoomGroup.V4_0, direction).ToList())
{
bool[] weights = [room.HasUpExit, room.HasDownExit, room.HasLeftExit, room.HasRightExit, room.IsDropZone];
roomDirectionWeightsByDirection[direction].Add((room, 5 - weights.Count(i => i)));
}
}
foreach (var shape in palaceRooms.ItemRooms(RoomGroup.V4_0).Select(i => i.CategorizeExits()).Distinct())
{
var newRooms = palaceRooms.ItemRoomsByShape(RoomGroup.V4_0, shape);
var ls = ItemRoomsByShape.GetValueOrDefault(shape, []);
ls.AddRange(newRooms);
ItemRoomsByShape[shape] = ls;
}
AddRoomGroup(palaceRooms, RoomGroup.V4_0, palaceNumber, roomDirectionWeightsByDirection);
}

if (props.AllowV5_0Rooms)
{
Entrances.AddRange(palaceRooms.Entrances(RoomGroup.V5_0)
.Where(i => i.PalaceNumber == null || i.PalaceNumber == palaceNumber).ToList());
BossRooms.AddRange(palaceRooms.BossRooms(RoomGroup.V5_0)
.Where(i => (i.PalaceNumber == null && palaceNumber < 6) || i.PalaceNumber == palaceNumber).ToList());
TbirdRooms.AddRange(palaceRooms.ThunderBirdRooms(RoomGroup.V5_0)
.Where(i => i.PalaceNumber == null || i.PalaceNumber == palaceNumber).ToList());
ItemRooms.AddRange(palaceRooms.ItemRooms(RoomGroup.V5_0)
.Where(i => i.PalaceNumber == null || i.PalaceNumber == palaceNumber).ToList());
foreach (var room in palaceRooms.LinkedRooms(RoomGroup.V5_0))
{
LinkedRooms.Add(room.Key, room.Value);
}
foreach (var direction in DirectionExtensions.ITEM_ROOM_ORIENTATIONS)
{
foreach (Room room in palaceRooms.ItemRoomsByDirection(RoomGroup.V5_0, direction).ToList())
{
bool[] weights = [room.HasUpExit, room.HasDownExit, room.HasLeftExit, room.HasRightExit, room.IsDropZone];
roomDirectionWeightsByDirection[direction].Add((room, 5 - weights.Count(i => i)));
}
}
foreach (var shape in palaceRooms.ItemRooms(RoomGroup.V5_0).Select(i => i.CategorizeExits()).Distinct())
{
var newRooms = palaceRooms.ItemRoomsByShape(RoomGroup.V5_0, shape);
var ls = ItemRoomsByShape.GetValueOrDefault(shape, []);
ls.AddRange(newRooms);
ItemRoomsByShape[shape] = ls;
}
AddRoomGroup(palaceRooms, RoomGroup.V5_0, palaceNumber, roomDirectionWeightsByDirection);
}

//If we are using these categorized exits to cap paths, there needs to always be a path of each type
//Since vanilla and 4.0 don't normally contain up/down elevator deadends, we add some dummy ones
DefaultStubsByDirection.Add(RoomExitType.DEADEND_EXIT_DOWN, palaceRooms.NormalPalaceRoomsByGroup(RoomGroup.STUBS).Where(i => i.HasDownExit).First());
Expand Down Expand Up @@ -276,6 +168,68 @@ public RoomPool(PalaceRooms palaceRooms, int palaceNumber, RandomizerProperties
}
}

/// Initializes a new <see cref="RoomPool"/> by copying an existing instance.
/// Collections are shallow-cloned. Contained <see cref="Room"/> objects are
/// shared between instances.
public RoomPool(RoomPool target)
{
NormalRooms.AddRange(target.NormalRooms);
Entrances.AddRange(target.Entrances);
BossRooms.AddRange(target.BossRooms);
TbirdRooms.AddRange(target.TbirdRooms);
ItemRooms.AddRange(target.ItemRooms);

VanillaBossRoom = target.VanillaBossRoom;
DefaultUpEntrance = target.DefaultUpEntrance;
DefaultDownBossRoom = target.DefaultDownBossRoom;

foreach (var pair in target.LinkedRooms)
{
LinkedRooms.Add(pair.Key, pair.Value);
}
foreach (var pair in target.ItemRoomsByDirection)
{
ItemRoomsByDirection[pair.Key] = (TableWeightedRandom<Room>)pair.Value.Clone();
}
foreach (var pair in target.ItemRoomsByShape)
{
ItemRoomsByShape[pair.Key] = [.. pair.Value];
}
foreach (var pair in target.DefaultStubsByDirection)
{
DefaultStubsByDirection.Add(pair.Key, pair.Value);
}
}

private void AddRoomGroup(PalaceRooms palaceRooms, RoomGroup group, int palaceNumber, Dictionary<Direction, List<(Room, int)>> roomDirectionWeightsByDirection)
{
Entrances.AddRange(palaceRooms.Entrances(group).Where(room => (room.PalaceNumber == null && palaceNumber < 7) || room.PalaceNumber == palaceNumber));
ItemRooms.AddRange(palaceRooms.ItemRooms(group).Where(room => (room.PalaceNumber == null && palaceNumber < 7) || room.PalaceNumber == palaceNumber));
BossRooms.AddRange(palaceRooms.BossRooms(group).Where(room => (room.PalaceNumber == null && palaceNumber < 6) || room.PalaceNumber == palaceNumber));
TbirdRooms.AddRange(palaceRooms.ThunderBirdRooms(group).Where(room => (room.PalaceNumber == null && palaceNumber == 7) || room.PalaceNumber == palaceNumber));
foreach (var pair in palaceRooms.LinkedRooms(group))
{
LinkedRooms[pair.Key] = pair.Value;
}
foreach (var direction in DirectionExtensions.ITEM_ROOM_ORIENTATIONS)
{
foreach (var room in palaceRooms.ItemRoomsByDirection(group, direction))
{
// give item rooms lower weights if they have more exits
bool[] hasExits = [room.HasUpExit, room.HasDownExit, room.HasLeftExit, room.HasRightExit, room.IsDropZone];
roomDirectionWeightsByDirection[direction].Add((room, 5 - hasExits.Count(i => i)));
}
}
var exitTypes = palaceRooms.ItemRooms(group).Select(r => r.CategorizeExits()).Distinct();
foreach (var shape in exitTypes)
{
var newRooms = palaceRooms.ItemRoomsByShape(group, shape);
var ls = ItemRoomsByShape.GetValueOrDefault(shape, []);
ls.AddRange(newRooms);
ItemRoomsByShape[shape] = ls;
}
}

public IEnumerable<RoomExitType> GetItemRoomShapes()
{
var keys = ItemRoomsByShape.Keys;
Expand Down Expand Up @@ -400,7 +354,7 @@ public Dictionary<RoomExitType, List<Room>> CategorizeNormalRoomExits(bool linkR

value.Add(room);
}

return categorizedRooms;
}

Expand All @@ -416,4 +370,10 @@ public List<Room> GetNormalRoomsForExitType(RoomExitType exitType, bool linkRoom
return roomType == exitType;
}).ToList();
}

public void RefillNormalRoomsForExitType(RoomPool rooms, RoomExitType exitType)
{
var originalRooms = rooms.GetNormalRoomsForExitType(exitType);
NormalRooms.AddRange(originalRooms);
}
}
33 changes: 16 additions & 17 deletions RandomizerCore/Sidescroll/ShapeFirstCoordinatePalaceGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ internal override async Task<Palace> GeneratePalace(RandomizerProperties props,
{
bool duplicateProtection = (props.NoDuplicateRooms || props.NoDuplicateRoomsBySideview) && AllowDuplicatePrevention(props, palaceNumber);
Palace palace = new(palaceNumber);
Dictionary<RoomExitType, List<Room>> roomsByExitType;
RoomPool roomPool = new(rooms);
var itemRoomSelector = GetItemRoomSelectionStrategy();
// var palaceGroup = Util.AsPalaceGrouping(palaceNumber);
Expand Down Expand Up @@ -71,9 +70,18 @@ internal override async Task<Palace> GeneratePalace(RandomizerProperties props,
return palace;
}

//Add rooms
roomsByExitType = roomPool.CategorizeNormalRoomExits(true);
var roomsByExitTypeUnmodified = roomPool.CategorizeNormalRoomExits();
Dictionary<RoomExitType, bool> stubOnlyExitTypes = new();
bool IsStubOnly(RoomExitType exitType)
{
if (stubOnlyExitTypes.TryGetValue(exitType, out var cached)) { return cached; }
roomsByExitTypeUnmodified.TryGetValue(exitType, out var roomList);
bool stubOnly = roomList == null || roomList.Count == 0;
stubOnlyExitTypes[exitType] = stubOnly;
return stubOnly;
}

//Add rooms
foreach (KeyValuePair<Coord, RoomExitType> item in shape.OrderBy(i => i.Key.X).ThenByDescending(i => i.Key.Y))
{
if (prepopulatedCoordinates.Contains(item.Key))
Expand All @@ -83,28 +91,19 @@ internal override async Task<Palace> GeneratePalace(RandomizerProperties props,
var (x, y) = item.Key;
RoomExitType roomExitType = item.Value;

bool stubOnly;
List<Room>? roomCandidates;
bool stubOnly = IsStubOnly(roomExitType);
List<Room>? roomCandidates = stubOnly ? null : roomPool.GetNormalRoomsForExitType(roomExitType);
Room? newRoom = null;
if (!stubOnlyExitTypes.TryGetValue(roomExitType, out stubOnly))
{
roomsByExitType.TryGetValue(roomExitType, out roomCandidates);
stubOnly = roomCandidates == null || roomCandidates.Count == 0;
stubOnlyExitTypes[roomExitType] = stubOnly;
}
else
{
roomCandidates = stubOnly ? null : roomsByExitType.GetValueOrDefault(roomExitType);
}

if (!stubOnly)
{
Debug.Assert(roomCandidates != null);
if (duplicateProtection && roomCandidates!.Count == 0)
{
logger.Debug($"Shape-first palace ran out of rooms of exit type: {roomExitType} in palace {palaceNumber}. Starting to use duplicate rooms.");
roomPool.RefillNormalRoomsForExitType(rooms, roomExitType);
roomCandidates = roomPool.GetNormalRoomsForExitType(roomExitType, true);
Debug.Assert(roomCandidates.Count() > 0);
roomsByExitType[roomExitType] = roomCandidates;
logger.Debug($"RandomWalk ran out of rooms of exit type: {roomExitType} in palace {palaceNumber}. Starting to use duplicate rooms.");
}
roomCandidates!.FisherYatesShuffle(r);
Room? upRoom = palace.AllRooms.FirstOrDefault(i => i.coords == new Coord(x, y + 1));
Expand Down
Loading