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

Update item spoilers #616

Merged
merged 5 commits into from
Dec 15, 2024
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
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