Skip to content

Commit

Permalink
Merge pull request #461 from Vivelin/fix-crystal-dungeon-tracker-hints
Browse files Browse the repository at this point in the history
Fix crystal dungeon tracker hints
  • Loading branch information
MattEqualsCoder authored Jan 18, 2024
2 parents 7d79cc8 + 5caa04f commit 0231677
Show file tree
Hide file tree
Showing 13 changed files with 238 additions and 157 deletions.
2 changes: 1 addition & 1 deletion src/Randomizer.App/Randomizer.App.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<TargetFramework>net7.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
<ApplicationIcon>chozo20.ico</ApplicationIcon>
<Version>9.7.1</Version>
<Version>9.7.2</Version>
<Title>SMZ3 Cas' Randomizer</Title>
<AssemblyTitle>SMZ3 Cas' Randomizer</AssemblyTitle>
<Authors>Vivelin</Authors>
Expand Down
14 changes: 14 additions & 0 deletions src/Randomizer.Data/WorldData/Location.cs
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,20 @@ public bool CanFill(Item item, Progression items)
return fillable;
}

/// <summary>
/// Returns if the item is potentially important, but not necessarily required
/// </summary>
/// <param name="keysanity">The keysanity mode. If not specified, it'll use the item's world</param>
/// <returns></returns>
public bool IsPotentiallyImportant(KeysanityMode? keysanity = null)
{
keysanity ??= Item.World.Config.KeysanityMode;
return Item.Progression
|| (Item.IsDungeonItem && keysanity is KeysanityMode.Both or KeysanityMode.Zelda)
|| (Item.IsKeycard && keysanity is KeysanityMode.Both or KeysanityMode.SuperMetroid);
}


/// <summary>
/// Returns a string that represents the location.
/// </summary>
Expand Down
34 changes: 27 additions & 7 deletions src/Randomizer.SMZ3.Tracking/VoiceCommands/SpoilerModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using Randomizer.Data.Options;
using Randomizer.Shared.Enums;
using Randomizer.SMZ3.Contracts;
using Randomizer.SMZ3.Infrastructure;

namespace Randomizer.SMZ3.Tracking.VoiceCommands
{
Expand All @@ -37,6 +38,7 @@ public class SpoilerModule : TrackerModule, IOptionalModule
private readonly IRandomizerConfigService _randomizerConfigService;
private readonly bool _isMultiworld;
private readonly IGameHintService _gameHintService;
private readonly PlaythroughService _playthroughService;

/// <summary>
/// Initializes a new instance of the <see cref="SpoilerModule"/> class.
Expand All @@ -47,13 +49,15 @@ public class SpoilerModule : TrackerModule, IOptionalModule
/// <param name="logger">Used to write logging information.</param>
/// <param name="randomizerConfigService">Service for retrieving the randomizer config for the world</param>
/// <param name="gameHintService">Service for getting hints for how important locations are</param>
public SpoilerModule(TrackerBase tracker, IItemService itemService, ILogger<SpoilerModule> logger, IWorldService worldService, IRandomizerConfigService randomizerConfigService, IGameHintService gameHintService)
/// <param name="playthroughService"></param>
public SpoilerModule(TrackerBase tracker, IItemService itemService, ILogger<SpoilerModule> logger, IWorldService worldService, IRandomizerConfigService randomizerConfigService, IGameHintService gameHintService, PlaythroughService playthroughService)
: base(tracker, itemService, worldService, logger)
{
TrackerBase.HintsEnabled = tracker.World.Config is { Race: false, DisableTrackerHints: false } && tracker.Options.HintsEnabled;
TrackerBase.SpoilersEnabled = tracker.World.Config is { Race: false, DisableTrackerSpoilers: false } && tracker.Options.SpoilersEnabled;
_randomizerConfigService = randomizerConfigService;
_gameHintService = gameHintService;
_playthroughService = playthroughService;
_isMultiworld = tracker.World.Config.MultiWorld;
}

Expand Down Expand Up @@ -114,7 +118,27 @@ private void GiveAreaHint(IHasLocations area)
}
else if (TrackerBase.HintsEnabled)
{
var usefulness = _gameHintService.GetUsefulness(locations.ToList(), WorldService.Worlds);
Reward? areaReward = null;
if (area is IHasReward rewardArea && rewardArea.RewardType != RewardType.Agahnim && rewardArea.RewardType != RewardType.None && area is IDungeon dungeon)
{
var bossLocation = area.Locations.First(x => x.Id == dungeon.BossLocationId);

// For pendant dungeons, only factor it in if the player has not gotten it so that they get hints
// that factor in Saha/Ped
if (rewardArea.RewardType is RewardType.PendantBlue or RewardType.PendantGreen
or RewardType.PendantRed && (bossLocation.State.Cleared || bossLocation.State.Autotracked))
{
areaReward = rewardArea.Reward;
}
// For crystal dungeons, always act like the player has them so that it only gives a hint based on
// the actual items in the dungeon
else if (rewardArea.RewardType is RewardType.CrystalBlue or RewardType.CrystalRed)
{
areaReward = rewardArea.Reward;
}
}

var usefulness = _gameHintService.GetUsefulness(locations.ToList(), WorldService.Worlds, areaReward);
if (usefulness == LocationUsefulness.Mandatory)
{
TrackerBase.Say(x => x.Hints.AreaHasSomethingMandatory, area.GetName());
Expand All @@ -130,10 +154,6 @@ private void GiveAreaHint(IHasLocations area)
{
TrackerBase.Say(x => x.Hints.AreaHasJunkAndCrystal, area.GetName());
}
else if (TrackerBase.IsWorth(region.RewardType))
{
TrackerBase.Say(x => x.Hints.AreaWorthComplicated, area.GetName());
}
else
{
TrackerBase.Say(x => x.Hints.AreaHasJunk, area.GetName());
Expand Down Expand Up @@ -901,7 +921,7 @@ public override void AddCommands()
{
if (TrackerBase.World.Config.Race) return;

Playthrough.TryGenerate(new[] { TrackerBase.World }, TrackerBase.World.Config, out _playthrough);
_playthroughService.TryGenerate(new[] { TrackerBase.World }, TrackerBase.World.Config, out _playthrough);

if (!TrackerBase.World.Config.DisableTrackerHints)
{
Expand Down
3 changes: 2 additions & 1 deletion src/Randomizer.SMZ3/Contracts/IGameHintService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@ LocationUsefulness GetLocationUsefulness(Location location, List<World> allWorld
/// </summary>
/// <param name="locations">The location to check</param>
/// <param name="allWorlds">All worlds that are a part of the seed</param>
/// <param name="ignoredReward">Dungeon reward to ignore</param>
/// <returns>How useful the locations are</returns>
LocationUsefulness GetUsefulness(List<Location> locations, List<World> allWorlds);
LocationUsefulness GetUsefulness(List<Location> locations, List<World> allWorlds, Reward? ignoredReward);

/// <summary>
/// Retrieves the text that could be displayed on one of the hint tiles in a player's
Expand Down
19 changes: 12 additions & 7 deletions src/Randomizer.SMZ3/Generation/GameHintService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using Randomizer.Shared.Enums;
using Randomizer.Shared.Models;
using Randomizer.SMZ3.Contracts;
using Randomizer.SMZ3.Infrastructure;

namespace Randomizer.SMZ3.Generation
{
Expand Down Expand Up @@ -62,12 +63,14 @@ public class GameHintService : IGameHintService
private readonly IMetadataService _metadataService;
private readonly GameLinesConfig _gameLines;
private readonly HintTileConfig _hintTileConfig;
private readonly PlaythroughService _playthroughService;
private Random _random;

public GameHintService(Configs configs, IMetadataService metadataService, ILogger<GameHintService> logger)
public GameHintService(Configs configs, IMetadataService metadataService, ILogger<GameHintService> logger, PlaythroughService playthroughService)
{
_gameLines = configs.GameLines;
_logger = logger;
_playthroughService = playthroughService;
_hintTileConfig = configs.HintTileConfig;
_metadataService = metadataService;
_random = new Random();
Expand Down Expand Up @@ -168,7 +171,7 @@ public void GetInGameHints(World hintPlayerWorld, List<World> allWorlds, Playthr
return locationUsefulness.MaxBy(x => (int)x.Usefulness);
}

public LocationUsefulness GetUsefulness(List<Location> locations, List<World> allWorlds)
public LocationUsefulness GetUsefulness(List<Location> locations, List<World> allWorlds, Reward? ignoredReward)
{
var importantLocations = GetImportantLocations(allWorlds);

Expand All @@ -181,7 +184,7 @@ public LocationUsefulness GetUsefulness(List<Location> locations, List<World> al
return LocationUsefulness.Useless;
}

return CheckIfLocationsAreImportant(allWorlds, importantLocations, locations);
return CheckIfLocationsAreImportant(allWorlds, importantLocations, locations, ignoredReward);
}

/// <summary>
Expand Down Expand Up @@ -379,12 +382,14 @@ private PlayerHintTile GetLocationHint(World hintPlayerWorld, List<World> allWor
/// Checks how useful a location is based on if the seed can be completed if we remove those
/// locations from the playthrough and if the items there are at least slightly useful
/// </summary>
private LocationUsefulness CheckIfLocationsAreImportant(List<World> allWorlds, IEnumerable<Location> importantLocations, List<Location> locations)
private LocationUsefulness CheckIfLocationsAreImportant(List<World> allWorlds, IEnumerable<Location> importantLocations, List<Location> locations, Reward? ignoredReward = null)
{
var worldLocations = importantLocations.Except(locations).ToList();
try
{
var spheres = Playthrough.GenerateSpheres(worldLocations);
var localWorld = allWorlds.First(x => x.Id == locations.First().World.Id);
var locationIds = locations.Select(x => x.Id).ToHashSet();
var spheres = _playthroughService.GenerateSpheres(worldLocations, ignoredReward);
var sphereLocations = spheres.SelectMany(x => x.Locations).ToList();

var canBeatGT = CheckSphereLocationCount(sphereLocations, locations, LocationId.GanonsTowerMoldormChest, allWorlds.Count);
Expand Down Expand Up @@ -420,7 +425,7 @@ private LocationUsefulness CheckIfLocationsAreImportant(List<World> allWorlds, I
}
var currentCrystalCount = world.Dungeons.Count(d =>
d.IsCrystalDungeon && sphereLocations.Any(l =>
l.World.Id == world.Id && l.Id == s_dungeonBossLocations[d.GetType()]));
l.World.Id == world.Id && l.Id == s_dungeonBossLocations[d.GetType()])) + (ignoredReward == null ? 0 : 1);
if (currentCrystalCount < numCrystalsNeeded)
{
return LocationUsefulness.Mandatory;
Expand Down Expand Up @@ -595,7 +600,7 @@ private IEnumerable<Location> GetImportantLocations(List<World> allWorlds)
{
// Get the first accessible ammo locations to make sure early ammo isn't considered mandatory when there
// are others available
var spheres = Playthrough.GenerateSpheres(allWorlds.SelectMany(x => x.Locations));
var spheres = _playthroughService.GenerateSpheres(allWorlds.SelectMany(x => x.Locations));
var ammoItemTypes = new List<ItemType>()
{
ItemType.Missile,
Expand Down
7 changes: 5 additions & 2 deletions src/Randomizer.SMZ3/Generation/Smz3MultiplayerRomGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using Randomizer.Shared;
using Randomizer.SMZ3.Contracts;
using Randomizer.SMZ3.FileData;
using Randomizer.SMZ3.Infrastructure;

namespace Randomizer.SMZ3.Generation;

Expand All @@ -19,14 +20,16 @@ public class Smz3MultiplayerRomGenerator : ISeededRandomizer
private readonly IWorldAccessor _worldAccessor;
private readonly ILogger<Smz3MultiplayerRomGenerator> _logger;
private readonly IPatcherService _patcherService;
private readonly PlaythroughService _playthroughService;

public Smz3MultiplayerRomGenerator(MultiplayerFillerFactory fillerFactory, IWorldAccessor worldAccessor,
ILogger<Smz3MultiplayerRomGenerator> logger, IPatcherService patcherService)
ILogger<Smz3MultiplayerRomGenerator> logger, IPatcherService patcherService, PlaythroughService playthroughService)
{
_fillerFactory = fillerFactory;
_worldAccessor = worldAccessor;
_logger = logger;
_patcherService = patcherService;
_playthroughService = playthroughService;
}

public SeedData GenerateSeed(Config config, CancellationToken cancellationToken = default) =>
Expand All @@ -52,7 +55,7 @@ public SeedData GenerateSeed(List<Config> configs, string? seed = "", Cancellati

var filler = _fillerFactory.Create();
filler.Fill(worlds, primaryConfig, cancellationToken);
var playthrough = Playthrough.Generate(worlds, primaryConfig);
var playthrough = _playthroughService.Generate(worlds, primaryConfig);

var seedData = new SeedData
(
Expand Down
7 changes: 5 additions & 2 deletions src/Randomizer.SMZ3/Generation/Smz3Plandomizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Randomizer.Shared;
using Randomizer.SMZ3.Contracts;
using Randomizer.SMZ3.FileData;
using Randomizer.SMZ3.Infrastructure;

namespace Randomizer.SMZ3.Generation
{
Expand All @@ -19,14 +20,16 @@ public class Smz3Plandomizer : IRandomizer
private readonly IWorldAccessor _worldAccessor;
private readonly ILogger<Smz3Plandomizer> _logger;
private readonly IPatcherService _patcherService;
private readonly PlaythroughService _playthroughService;

public Smz3Plandomizer(PlandoFillerFactory fillerFactory, IWorldAccessor worldAccessor,
ILogger<Smz3Plandomizer> logger, IPatcherService patcherService)
ILogger<Smz3Plandomizer> logger, IPatcherService patcherService, PlaythroughService playthroughService)
{
_fillerFactory = fillerFactory;
_worldAccessor = worldAccessor;
_logger = logger;
_patcherService = patcherService;
_playthroughService = playthroughService;
}

public SeedData GenerateSeed(Config config, CancellationToken cancellationToken = default)
Expand All @@ -45,7 +48,7 @@ public SeedData GenerateSeed(Config config, CancellationToken cancellationToken
Playthrough playthrough;
try
{
playthrough = Playthrough.Generate(worlds, config);
playthrough = _playthroughService.Generate(worlds, config);
}
catch (RandomizerGenerationException ex)
{
Expand Down
7 changes: 5 additions & 2 deletions src/Randomizer.SMZ3/Generation/Smz3Randomizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Randomizer.Shared.Enums;
using Randomizer.SMZ3.Contracts;
using Randomizer.SMZ3.FileData;
using Randomizer.SMZ3.Infrastructure;

namespace Randomizer.SMZ3.Generation
{
Expand All @@ -20,14 +21,16 @@ public class Smz3Randomizer : ISeededRandomizer
private readonly ILogger<Smz3Randomizer> _logger;
private readonly IGameHintService _hintService;
private readonly IPatcherService _patcherService;
private readonly PlaythroughService _playthroughService;

public Smz3Randomizer(IFiller filler, IWorldAccessor worldAccessor, IGameHintService gameHintGenerator,
ILogger<Smz3Randomizer> logger, IPatcherService patcherService)
ILogger<Smz3Randomizer> logger, IPatcherService patcherService, PlaythroughService playthroughService)
{
Filler = filler;
_worldAccessor = worldAccessor;
_logger = logger;
_patcherService = patcherService;
_playthroughService = playthroughService;
_hintService = gameHintGenerator;
}

Expand Down Expand Up @@ -99,7 +102,7 @@ public SeedData GenerateSeed(List<Config> configs, string? seed, CancellationTok
Filler.SetRandom(rng);
Filler.Fill(worlds, primaryConfig, cancellationToken);

var playthrough = Playthrough.Generate(worlds, primaryConfig);
var playthrough = _playthroughService.Generate(worlds, primaryConfig);
var seedData = new SeedData
(
guid: Guid.NewGuid().ToString("N"),
Expand Down
Loading

0 comments on commit 0231677

Please sign in to comment.