Skip to content
Merged
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
153 changes: 85 additions & 68 deletions osu.Game/Online/Chat/StandAloneChatDisplay.cs
Original file line number Diff line number Diff line change
Expand Up @@ -466,93 +466,94 @@ private void processChatCommands(string[] parts)
List<Mod> requiredMods = new List<Mod>();
List<Mod> allowedMods = new List<Mod>();

if (mods.Length == 1 && mods[0].Equals(@"fm", StringComparison.OrdinalIgnoreCase))
foreach (string mod in mods)
{
// hardcode freemod to allow all mods, leaves requiredMods empty
var newAllowedMods = availableMods.Value
.SelectMany(pair => pair.Value)
.SelectMany(ModUtils.FlattenMod)
.Where(mod => ModUtils.IsValidFreeModForMatchType(mod, Client.Room?.Settings.MatchType ?? MatchType.TeamVersus));
if (mod.Length < 2)
{
Logger.Log($@"[!mp mods] Unknown mod '{mod}', ignoring", LoggingTarget.Runtime, LogLevel.Important);
continue;
}

allowedMods.AddRange(newAllowedMods);
}
else // handle regular mods
{
foreach (string mod in mods)
string modAcronym = mod[..2];

if (modAcronym.Equals(@"fm", StringComparison.OrdinalIgnoreCase))
{
if (mod.Length < 2)
{
Logger.Log($@"[!mp mods] Unknown mod '{mod}', ignoring", LoggingTarget.Runtime, LogLevel.Important);
continue;
}
// hardcode freemod to allow all mods, leaves requiredMods empty
var newAllowedMods = availableMods.Value
.SelectMany(pair => pair.Value)
.SelectMany(ModUtils.FlattenMod)
.Where(availableMod =>
ModUtils.IsValidFreeModForMatchType(availableMod, Client.Room?.Settings.MatchType ?? MatchType.TeamVersus));

allowedMods.AddRange(newAllowedMods);
continue;
}

string modAcronym = mod[..2];
var rulesetInstance = RulesetStore.GetRuleset(itemToEdit.RulesetID)?.CreateInstance();
var rulesetInstance = RulesetStore.GetRuleset(itemToEdit.RulesetID)?.CreateInstance();

if (rulesetInstance == null)
{
Logger.Log($@"[!mp mods] Couldn't create ruleset instance with ruleset ID {itemToEdit.RulesetID}, ignoring mod '{mod}'",
LoggingTarget.Runtime, LogLevel.Important);
continue;
}
if (rulesetInstance == null)
{
Logger.Log($@"[!mp mods] Couldn't create ruleset instance with ruleset ID {itemToEdit.RulesetID}, ignoring mod '{mod}'",
LoggingTarget.Runtime, LogLevel.Important);
continue;
}

Mod? modInstance;

// mod with no params
if (mod.Length == 2)
{
modInstance = ParseMod(rulesetInstance, modAcronym, Array.Empty<object>());
if (modInstance != null)
requiredMods.Add(modInstance);
continue;
}

Mod? modInstance;
// mod has parameters
{
JsonNode? modParamsNode;

// mod with no params
if (mod.Length == 2)
try
{
modInstance = ParseMod(rulesetInstance, modAcronym, Array.Empty<object>());
if (modInstance != null)
requiredMods.Add(modInstance);
continue;
modParamsNode = JsonNode.Parse(mod[2..]);
}
catch (JsonException)
{
modParamsNode = null;
}

// mod has parameters
if (modParamsNode is JsonArray modParams)
{
JsonNode? modParamsNode;
List<object> parsedParamsList = new List<object>();

try
{
modParamsNode = JsonNode.Parse(mod[2..]);
}
catch (JsonException)
foreach (JsonNode? node in modParams)
{
modParamsNode = null;
}
if (node?.GetValueKind() is not (JsonValueKind.Number or JsonValueKind.False or JsonValueKind.True))
continue;

if (modParamsNode is JsonArray modParams)
{
List<object> parsedParamsList = new List<object>();
if (node.AsValue().TryGetValue(out int parsedInt))
{
parsedParamsList.Add(parsedInt);
continue;
}

foreach (JsonNode? node in modParams)
if (node.AsValue().TryGetValue(out double parsedDouble))
{
if (node?.GetValueKind() is not (JsonValueKind.Number or JsonValueKind.False or JsonValueKind.True))
continue;

if (node.AsValue().TryGetValue(out int parsedInt))
{
parsedParamsList.Add(parsedInt);
continue;
}

if (node.AsValue().TryGetValue(out double parsedDouble))
{
parsedParamsList.Add(parsedDouble);
continue;
}

if (node.AsValue().TryGetValue(out bool parsedBool))
parsedParamsList.Add(parsedBool);
parsedParamsList.Add(parsedDouble);
continue;
}

modInstance = ParseMod(rulesetInstance, modAcronym, parsedParamsList);
if (modInstance != null)
requiredMods.Add(modInstance);
}
else
{
Logger.Log($@"[!mp mods] Couldn't parse mod parameter(s) '{mod[2..]}', ignoring", LoggingTarget.Runtime, LogLevel.Important);
if (node.AsValue().TryGetValue(out bool parsedBool))
parsedParamsList.Add(parsedBool);
}

modInstance = ParseMod(rulesetInstance, modAcronym, parsedParamsList);
if (modInstance != null)
requiredMods.Add(modInstance);
}
else
{
Logger.Log($@"[!mp mods] Couldn't parse mod parameter(s) '{mod[2..]}', ignoring", LoggingTarget.Runtime, LogLevel.Important);
}
}
}
Expand All @@ -563,6 +564,22 @@ private void processChatCommands(string[] parts)
break;
}

// remove duplicate required mods from allowed mods (if present)
// don't use Mod.Equals, as it will return false if parameters differ
allowedMods.RemoveAll(mod => requiredMods.Any(rMod => rMod.Acronym == mod.Acronym));

// ensure allowed mods are compatible with required mods
// inspired by https://github.com/ppy/osu-server-spectator/blob/156923578a34b4559582df9ccd49fda499b4fa0a/osu.Server.Spectator/Extensions/MultiplayerPlaylistItemExtensions.cs#L88-L92
HashSet<Mod> modsToRemoveFromAllowed = new HashSet<Mod>();

foreach (var allowedMod in allowedMods)
{
if (!ModUtils.CheckCompatibleSet(requiredMods.Concat(new[] { allowedMod }), out var invalidMods))
modsToRemoveFromAllowed.UnionWith(invalidMods.Where(m => allowedMods.Contains(m)));
}

allowedMods.RemoveAll(modsToRemoveFromAllowed.Contains);

// get playlist item to edit:
beatmapLookupCache.GetBeatmapAsync(itemToEdit.BeatmapID).ContinueWith(task => Schedule(() =>
{
Expand Down