Skip to content

Commit

Permalink
Merge pull request #616 from TheTrackerCouncil/update-item-spoilers
Browse files Browse the repository at this point in the history
Update item spoilers
  • Loading branch information
MattEqualsCoder authored Dec 15, 2024
2 parents 9b583ce + 8fd7a98 commit 0add745
Show file tree
Hide file tree
Showing 13 changed files with 143 additions and 26 deletions.
2 changes: 1 addition & 1 deletion src/TrackerCouncil.Smz3.Data.SchemaGenerator/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ private static void CreateTemplates(string outputPath)
// Boss Template
var bossConfig = configProvider.GetBossConfig(new List<string>(), null);
var templateBossConfig = new BossConfig();
templateBossConfig.AddRange(bossConfig.Select(boss => new BossInfo(boss.Boss)));
templateBossConfig.AddRange(bossConfig.Select(boss => new BossInfo() { Boss = boss.Boss }));
var exampleBossConfig = BossConfig.Example();
WriteTemplate(templatePath, "bosses", templateBossConfig, exampleBossConfig);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ public class BossInfo : IMergeable<BossInfo>
{
public BossInfo()
{
Name = [];
}

/// <summary>
Expand Down Expand Up @@ -46,7 +45,7 @@ public BossInfo(string name)
/// <summary>
/// Gets the name of the boss.
/// </summary>
public SchrodingersString Name { get; set; }
public SchrodingersString? Name { get; set; }

/// <summary>
/// Gets the phrases to respond with when the boss has been tracked (but
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,21 @@ public class SpoilerConfig : IMergeable<SpoilerConfig>
/// </summary>
public SchrodingersString? LocationsCleared { get; init; }

/// <summary>
/// Gets the phrases to respond with when an item's location has been cleared.
/// <c>{0}</c> is a placeholder for the name of the item, with "a", "an"
/// or "the" and <c>1</c> is a placeholder for the name of the location where the item was.
/// </summary>
public SchrodingersString? ItemLocationCleared { get; init; }

/// <summary>
/// Gets the phrases to respond with when all locations that have the item are cleared.
/// <c>{0}</c> is a placeholder for the name of the item, with "a", "an"
/// or "the". <c>{1}</c> is a placeholder for a list of locations where the item was (up
/// to 4).
/// </summary>
public SchrodingersString? ItemLocationsCleared { get; init; }

/// <summary>
/// Gets the phrases that spoil the item that is at the requested
/// location.
Expand Down
5 changes: 5 additions & 0 deletions src/TrackerCouncil.Smz3.Data/WorldData/Boss.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,9 @@ public void UpdateAccessibility(Progression actualProgression, Progression withK
/// langword="false"/>.
public bool Is(BossType type, string name)
=> (Type != BossType.None && Type == type) || (Type == BossType.None && Name == name);

/// <summary>
/// Returns a random name from the boss's metadata
/// </summary>
public string RandomName => Metadata.Name?.ToString() ?? Name;
}
15 changes: 15 additions & 0 deletions src/TrackerCouncil.Smz3.Data/WorldData/Location.cs
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,21 @@ public override string ToString()
: $"{Region} - {Name}";
}

/// <summary>
/// Returns a random name from the location's metadata
/// </summary>
public string RandomName
{
get
{
var randomLocationName = Metadata.Name?.ToString() ?? Name;
return Room != null
? $"{Room.RandomName} - {randomLocationName}"
: $"{Region.RandomName} - {randomLocationName}";
}
}


public IHasTreasure? GetTreasureRegion() => Region as IHasTreasure;

public event EventHandler? ClearedUpdated;
Expand Down
10 changes: 10 additions & 0 deletions src/TrackerCouncil.Smz3.Data/WorldData/Regions/IHasBoss.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,14 @@ public void ApplyState(TrackerState? state)
/// <see langword="false"/>.
/// </returns>
bool CanBeatBoss(Progression items);

/// <summary>
/// Returns a randomized name from the metadata for the region
/// </summary>
public string RandomRegionName => Region.RandomName;

/// <summary>
/// Returns a randomized name from the metadata for the boss
/// </summary>
public string RandomBossName => Boss.RandomName;
}
5 changes: 5 additions & 0 deletions src/TrackerCouncil.Smz3.Data/WorldData/Regions/Region.cs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,11 @@ private bool MatchesItemPlacementRule(Item item)
/// <returns>A new string that represents the region.</returns>
public override string ToString() => Name;

/// <summary>
/// Returns a random string from the region's metadata
/// </summary>
public string RandomName => Metadata.Name?.ToString() ?? Name;

/// <summary>
/// Determines whether the region can be entered with the specified
/// items.
Expand Down
2 changes: 2 additions & 0 deletions src/TrackerCouncil.Smz3.Data/WorldData/Room.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,6 @@ public Room(Region region, string name, IMetadataService? metadata, params strin
/// </summary>
/// <returns>A new string that represents this room.</returns>
public override string ToString() => $"{Region} - {Name}";

public string RandomName => $"{Region.RandomName} - {Metadata.Name?.ToString() ?? Name}";
}
64 changes: 55 additions & 9 deletions src/TrackerCouncil.Smz3.Tracking/NaturalLanguage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,45 @@ namespace TrackerCouncil.Smz3.Tracking;
/// </summary>
internal static class NaturalLanguage
{
/// <summary>
/// Returns a string representing a collection of locations as a
/// comma-separated list, but where the last location is separated with
/// "and" instead.
/// </summary>
/// <param name="locations">The locations.</param>
/// <param name="cap">The cap of locations to fully name.</param>
/// <returns>
/// A string containing each item in <paramref name="locations"/> separated
/// with a comma, except for the two items, which are separated with
/// "and".
/// </returns>
/// <example>
/// <c>NaturalLanguage.Join(new[]{"one", "two", "three"})</c> returns
/// <c>"one, two and three"</c>.
/// </example>
public static string Join(ICollection<Location> locations, int cap = 4)
{
if (locations.Count == 0)
{
return string.Empty;
}
else if (locations.Count == 1)
{
return locations.First().RandomName;
}
else if (locations.Count <= cap)
{
var last = locations.Last().RandomName;
var remainder = locations.SkipLast(1).Select(x => x.RandomName);
return $"{string.Join(", ", remainder)} and {last}";
}
else
{
var namedLocations = locations.Take(cap).Select(x => x.RandomName);
return $"{string.Join(", ", namedLocations)} and {locations.Count - cap} other locations";
}
}

/// <summary>
/// Returns a string representing a collection of items as a
/// comma-separated list, but where the last item is separated with
Expand Down Expand Up @@ -57,21 +96,28 @@ public static string Join(IEnumerable<Item> items, Config config)
var item = innerItems.First(); // Just pick the first. It's possible (though unlikely) there's multiple items for a single item type
var count = innerItems.Count();
return (item, count);
});
}).ToList();

var interestingItems = groupedItems.Where(x => x.item.Metadata.IsJunk(config) == false).ToList();
var junkItems = groupedItems.Where(x => x.item.Metadata.IsJunk(config)).ToList();
var interestingItems = groupedItems.Where(x => x.item.Metadata.IsProgression(config)).ToList();
var junkItems = groupedItems.Where(x => !x.item.Metadata.IsProgression(config)).OrderBy(x => x.item.IsDungeonItem).ToList();

if (junkItems.Count == 0)
{
return Join(interestingItems.Select(GetPhrase));
}
else if (interestingItems.Count + junkItems.Count < 5)
{
return Join(interestingItems.Concat(junkItems).Select(GetPhrase));
}

if (interestingItems.Count == 0)
return Join(junkItems.Select(GetPhrase));

if (junkItems.Count > 1)
return Join(interestingItems.Select(GetPhrase).Concat(new[] { $"{junkItems.Count} other items" }));
if (interestingItems.Count <= 3)
{
var numToTake = 3 - interestingItems.Count;
interestingItems.AddRange(junkItems.Take(numToTake));
junkItems = junkItems.Skip(numToTake).ToList();
}

return Join(groupedItems.Select(GetPhrase));
return Join(interestingItems.Select(GetPhrase).Concat([$"{junkItems.Count} other items"]));

static string GetPhrase((Item item, int count) x)
=> x.count > 1 ? $"{x.count} {x.item.Metadata.Plural ?? $"{x.item.Name}s"}": $"{x.item.Name}";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public void MarkBossAsDefeated(IHasBoss region, float? confidence = null, bool a
{
if (region.BossDefeated && !autoTracked)
{
Tracker.Say(response: Responses.DungeonBossAlreadyCleared, args: [region.Metadata.Name, region.BossMetadata.Name]);
Tracker.Say(response: Responses.DungeonBossAlreadyCleared, args: [region.RandomRegionName, region.RandomBossName]);
return;
}

Expand All @@ -26,7 +26,7 @@ public void MarkBossAsDefeated(IHasBoss region, float? confidence = null, bool a
var addedEvent = History.AddEvent(
HistoryEventType.BeatBoss,
true,
region.BossMetadata.Name.ToString() ?? $"boss of {region.Metadata.Name}"
region.RandomBossName
);

region.Boss.Defeated = true;
Expand All @@ -49,14 +49,14 @@ public void MarkBossAsDefeated(IHasBoss region, float? confidence = null, bool a
}
}

Tracker.Say(response: Responses.DungeonBossCleared, args: [region.Metadata.Name, region.BossMetadata.Name]);
Tracker.Say(response: Responses.DungeonBossCleared, args: [region.RandomRegionName, region.RandomBossName]);
}
else
{
if (!admittedGuilt && region.BossMetadata.WhenTracked != null)
Tracker.Say(response: region.BossMetadata.WhenTracked, args: [region.BossMetadata.Name]);
Tracker.Say(response: region.BossMetadata.WhenTracked, args: [region.RandomBossName]);
else
Tracker.Say(response: region.BossMetadata.WhenDefeated ?? Responses.BossDefeated, args: [region.BossMetadata.Name]);
Tracker.Say(response: region.BossMetadata.WhenDefeated ?? Responses.BossDefeated, args: [region.RandomBossName]);
}

// Auto track the region's reward
Expand Down Expand Up @@ -166,13 +166,13 @@ public void MarkBossAsNotDefeated(IHasBoss region, float? confidence = null)
{
if (!region.BossDefeated)
{
Tracker.Say(response: Responses.DungeonBossNotYetCleared, args: [region.Metadata.Name, region.BossMetadata.Name]);
Tracker.Say(response: Responses.DungeonBossNotYetCleared, args: [region.RandomRegionName, region.RandomBossName]);
return;
}

region.BossDefeated = false;
BossUpdated?.Invoke(this, new BossTrackedEventArgs(region.Boss, confidence, false));
Tracker.Say(response: Responses.DungeonBossUncleared, args: [region.Metadata.Name, region.BossMetadata.Name]);
Tracker.Say(response: Responses.DungeonBossUncleared, args: [region.RandomRegionName, region.RandomBossName]);

// Try to untrack the associated boss reward item
List<Action> undoActions = [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,19 +191,19 @@ private void PlayerTrackedBoss(PlayerTrackedBossEventHandlerArgs args)
TrackerBase.Say(x => x.Multiplayer.OtherPlayerClearedDungeonWithReward,
args: [
args.PhoneticName,
rewardRegion.Metadata.Name, boss.Region.BossMetadata.Name, rewardRegion.RewardMetadata.Name,
rewardRegion.Metadata.Name, boss.Region.RandomBossName, rewardRegion.RewardMetadata.Name,
rewardRegion.RewardMetadata.NameWithArticle
]);
}
else
{
TrackerBase.Say(x => x.Multiplayer.OtherPlayerClearedDungeonWithoutReward,
args: [args.PhoneticName, treasureRegion.Metadata.Name, boss.Region.BossMetadata.Name]);
args: [args.PhoneticName, treasureRegion.Metadata.Name, boss.Region.RandomBossName]);
}
}
else
{
TrackerBase.Say(x => x.Multiplayer.OtherPlayerDefeatedBoss, args: [args.PhoneticName, boss.Metadata.Name]);
TrackerBase.Say(x => x.Multiplayer.OtherPlayerDefeatedBoss, args: [args.PhoneticName, boss.RandomName]);
}

}
Expand Down
17 changes: 15 additions & 2 deletions src/TrackerCouncil.Smz3.Tracking/VoiceCommands/SpoilerModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -218,10 +218,23 @@ private void RevealItemLocation(Item item)
TrackerBase.Say(x => x.Spoilers.ItemNotFound, args: [item.Metadata.NameWithArticle]);
return;
}

// The item exists, but all locations are cleared
else if (locations.Count > 0 && locations.All(x => x.Cleared))
{
// The item exists, but all locations are cleared
TrackerBase.Say(x => x.Spoilers.LocationsCleared, args: [item.Metadata.NameWithArticle]);
// Prioritize locations that haven't been auto tracked
var nonAutoTrackedLocations = locations.Where(x => !x.Autotracked);
var locationsToAnnounce = (nonAutoTrackedLocations.Any() ? nonAutoTrackedLocations : locations).ToList();

if (locationsToAnnounce.Count == 1)
{
TrackerBase.Say(x => x.Spoilers.ItemLocationCleared, args: [item.Metadata.NameWithArticle, locationsToAnnounce.First().RandomName]);
}
else
{
TrackerBase.Say(x => x.Spoilers.ItemLocationsCleared, args: [item.Metadata.NameWithArticle, NaturalLanguage.Join(locationsToAnnounce)]);
}

return;
}

Expand Down
11 changes: 9 additions & 2 deletions src/TrackerCouncil.Smz3.Tracking/VoiceCommands/TrackerModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -487,8 +487,15 @@ protected virtual Choices GetBossNames()
var bossNames = new Choices();
foreach (var boss in TrackerBase.World.AllBosses)
{
foreach (var name in boss.Metadata.Name)
bossNames.Add(new SemanticResultValue(name.Text, boss.Name));
if (boss.Metadata.Name != null)
{
foreach (var name in boss.Metadata.Name)
bossNames.Add(new SemanticResultValue(name.Text, boss.Name));
}
else
{
bossNames.Add(new SemanticResultValue(boss.Name, boss.Name));
}
}
return bossNames;
}
Expand Down

0 comments on commit 0add745

Please sign in to comment.