mirror of
https://github.com/osukey/osukey.git
synced 2025-08-04 15:16:38 +09:00
Merge branch 'master' into leaderboard-scores
This commit is contained in:
@ -49,9 +49,18 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
public string Path { get; set; }
|
||||
|
||||
[JsonProperty("file_md5")]
|
||||
[JsonProperty("file_sha2")]
|
||||
public string Hash { get; set; }
|
||||
|
||||
public bool Hidden { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// MD5 is kept for legacy support (matching against replays, osu-web-10 etc.).
|
||||
/// </summary>
|
||||
[Indexed]
|
||||
[JsonProperty("file_md5")]
|
||||
public string MD5Hash { get; set; }
|
||||
|
||||
// General
|
||||
public int AudioLeadIn { get; set; }
|
||||
public bool Countdown { get; set; }
|
||||
|
@ -33,11 +33,21 @@ namespace osu.Game.Beatmaps
|
||||
/// </summary>
|
||||
public event Action<BeatmapSetInfo> BeatmapSetAdded;
|
||||
|
||||
/// <summary>
|
||||
/// Fired when a single difficulty has been hidden.
|
||||
/// </summary>
|
||||
public event Action<BeatmapInfo> BeatmapHidden;
|
||||
|
||||
/// <summary>
|
||||
/// Fired when a <see cref="BeatmapSetInfo"/> is removed from the database.
|
||||
/// </summary>
|
||||
public event Action<BeatmapSetInfo> BeatmapSetRemoved;
|
||||
|
||||
/// <summary>
|
||||
/// Fired when a single difficulty has been restored.
|
||||
/// </summary>
|
||||
public event Action<BeatmapInfo> BeatmapRestored;
|
||||
|
||||
/// <summary>
|
||||
/// A default representation of a WorkingBeatmap to use when no beatmap is available.
|
||||
/// </summary>
|
||||
@ -71,6 +81,8 @@ namespace osu.Game.Beatmaps
|
||||
beatmaps = new BeatmapStore(connection);
|
||||
beatmaps.BeatmapSetAdded += s => BeatmapSetAdded?.Invoke(s);
|
||||
beatmaps.BeatmapSetRemoved += s => BeatmapSetRemoved?.Invoke(s);
|
||||
beatmaps.BeatmapHidden += b => BeatmapHidden?.Invoke(b);
|
||||
beatmaps.BeatmapRestored += b => BeatmapRestored?.Invoke(b);
|
||||
|
||||
this.storage = storage;
|
||||
this.files = files;
|
||||
@ -162,24 +174,34 @@ namespace osu.Game.Beatmaps
|
||||
// If we have an ID then we already exist in the database.
|
||||
if (beatmapSetInfo.ID != 0) return;
|
||||
|
||||
lock (beatmaps)
|
||||
beatmaps.Add(beatmapSetInfo);
|
||||
beatmaps.Add(beatmapSetInfo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete a beatmap from the manager.
|
||||
/// Is a no-op for already deleted beatmaps.
|
||||
/// </summary>
|
||||
/// <param name="beatmapSet">The beatmap to delete.</param>
|
||||
/// <param name="beatmapSet">The beatmap set to delete.</param>
|
||||
public void Delete(BeatmapSetInfo beatmapSet)
|
||||
{
|
||||
lock (beatmaps)
|
||||
if (!beatmaps.Delete(beatmapSet)) return;
|
||||
if (!beatmaps.Delete(beatmapSet)) return;
|
||||
|
||||
if (!beatmapSet.Protected)
|
||||
files.Dereference(beatmapSet.Files.Select(f => f.FileInfo).ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete a beatmap difficulty.
|
||||
/// </summary>
|
||||
/// <param name="beatmap">The beatmap difficulty to hide.</param>
|
||||
public void Hide(BeatmapInfo beatmap) => beatmaps.Hide(beatmap);
|
||||
|
||||
/// <summary>
|
||||
/// Restore a beatmap difficulty.
|
||||
/// </summary>
|
||||
/// <param name="beatmap">The beatmap difficulty to restore.</param>
|
||||
public void Restore(BeatmapInfo beatmap) => beatmaps.Restore(beatmap);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="BeatmapSetInfo"/> to a usable state if it has previously been deleted but not yet purged.
|
||||
/// Is a no-op for already usable beatmaps.
|
||||
@ -187,8 +209,7 @@ namespace osu.Game.Beatmaps
|
||||
/// <param name="beatmapSet">The beatmap to restore.</param>
|
||||
public void Undelete(BeatmapSetInfo beatmapSet)
|
||||
{
|
||||
lock (beatmaps)
|
||||
if (!beatmaps.Undelete(beatmapSet)) return;
|
||||
if (!beatmaps.Undelete(beatmapSet)) return;
|
||||
|
||||
if (!beatmapSet.Protected)
|
||||
files.Reference(beatmapSet.Files.Select(f => f.FileInfo).ToArray());
|
||||
@ -248,6 +269,13 @@ namespace osu.Game.Beatmaps
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refresh an existing instance of a <see cref="BeatmapSetInfo"/> from the store.
|
||||
/// </summary>
|
||||
/// <param name="beatmapSet">A stale instance.</param>
|
||||
/// <returns>A fresh instance.</returns>
|
||||
public BeatmapSetInfo Refresh(BeatmapSetInfo beatmapSet) => QueryBeatmapSet(s => s.ID == beatmapSet.ID);
|
||||
|
||||
/// <summary>
|
||||
/// Perform a lookup query on available <see cref="BeatmapSetInfo"/>s.
|
||||
/// </summary>
|
||||
@ -255,7 +283,7 @@ namespace osu.Game.Beatmaps
|
||||
/// <returns>Results from the provided query.</returns>
|
||||
public List<BeatmapSetInfo> QueryBeatmapSets(Expression<Func<BeatmapSetInfo, bool>> query)
|
||||
{
|
||||
lock (beatmaps) return beatmaps.QueryAndPopulate(query);
|
||||
return beatmaps.QueryAndPopulate(query);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -265,15 +293,12 @@ namespace osu.Game.Beatmaps
|
||||
/// <returns>The first result for the provided query, or null if no results were found.</returns>
|
||||
public BeatmapInfo QueryBeatmap(Func<BeatmapInfo, bool> query)
|
||||
{
|
||||
lock (beatmaps)
|
||||
{
|
||||
BeatmapInfo set = beatmaps.Query<BeatmapInfo>().FirstOrDefault(query);
|
||||
BeatmapInfo set = beatmaps.Query<BeatmapInfo>().FirstOrDefault(query);
|
||||
|
||||
if (set != null)
|
||||
beatmaps.Populate(set);
|
||||
if (set != null)
|
||||
beatmaps.Populate(set);
|
||||
|
||||
return set;
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -372,6 +397,7 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
beatmap.BeatmapInfo.Path = name;
|
||||
beatmap.BeatmapInfo.Hash = ms.ComputeSHA2Hash();
|
||||
beatmap.BeatmapInfo.MD5Hash = ms.ComputeMD5Hash();
|
||||
|
||||
// TODO: Diff beatmap metadata with set metadata and leave it here if necessary
|
||||
beatmap.BeatmapInfo.Metadata = null;
|
||||
|
@ -16,11 +16,14 @@ namespace osu.Game.Beatmaps
|
||||
public event Action<BeatmapSetInfo> BeatmapSetAdded;
|
||||
public event Action<BeatmapSetInfo> BeatmapSetRemoved;
|
||||
|
||||
public event Action<BeatmapInfo> BeatmapHidden;
|
||||
public event Action<BeatmapInfo> BeatmapRestored;
|
||||
|
||||
/// <summary>
|
||||
/// The current version of this store. Used for migrations (see <see cref="PerformMigration(int, int)"/>).
|
||||
/// The initial version is 1.
|
||||
/// </summary>
|
||||
protected override int StoreVersion => 2;
|
||||
protected override int StoreVersion => 4;
|
||||
|
||||
public BeatmapStore(SQLiteConnection connection)
|
||||
: base(connection)
|
||||
@ -77,6 +80,14 @@ namespace osu.Game.Beatmaps
|
||||
// cannot migrate; breaking underlying changes.
|
||||
Reset();
|
||||
break;
|
||||
case 3:
|
||||
// Added MD5Hash column to BeatmapInfo
|
||||
Connection.MigrateTable<BeatmapInfo>();
|
||||
break;
|
||||
case 4:
|
||||
// Added Hidden column to BeatmapInfo
|
||||
Connection.MigrateTable<BeatmapInfo>();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -96,7 +107,7 @@ namespace osu.Game.Beatmaps
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete a <see cref="BeatmapSetInfo"/> to the database.
|
||||
/// Delete a <see cref="BeatmapSetInfo"/> from the database.
|
||||
/// </summary>
|
||||
/// <param name="beatmapSet">The beatmap to delete.</param>
|
||||
/// <returns>Whether the beatmap's <see cref="BeatmapSetInfo.DeletePending"/> was changed.</returns>
|
||||
@ -127,6 +138,38 @@ namespace osu.Game.Beatmaps
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hide a <see cref="BeatmapInfo"/> in the database.
|
||||
/// </summary>
|
||||
/// <param name="beatmap">The beatmap to hide.</param>
|
||||
/// <returns>Whether the beatmap's <see cref="BeatmapInfo.Hidden"/> was changed.</returns>
|
||||
public bool Hide(BeatmapInfo beatmap)
|
||||
{
|
||||
if (beatmap.Hidden) return false;
|
||||
|
||||
beatmap.Hidden = true;
|
||||
Connection.Update(beatmap);
|
||||
|
||||
BeatmapHidden?.Invoke(beatmap);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restore a previously hidden <see cref="BeatmapInfo"/>.
|
||||
/// </summary>
|
||||
/// <param name="beatmap">The beatmap to restore.</param>
|
||||
/// <returns>Whether the beatmap's <see cref="BeatmapInfo.Hidden"/> was changed.</returns>
|
||||
public bool Restore(BeatmapInfo beatmap)
|
||||
{
|
||||
if (!beatmap.Hidden) return false;
|
||||
|
||||
beatmap.Hidden = false;
|
||||
Connection.Update(beatmap);
|
||||
|
||||
BeatmapRestored?.Invoke(beatmap);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void cleanupPendingDeletions()
|
||||
{
|
||||
Connection.RunInTransaction(() =>
|
||||
|
@ -10,4 +10,4 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
/// </summary>
|
||||
public double SpeedMultiplier = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -30,11 +30,15 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
public abstract class DifficultyCalculator<T> : DifficultyCalculator where T : HitObject
|
||||
{
|
||||
protected readonly Beatmap Beatmap;
|
||||
|
||||
protected List<T> Objects;
|
||||
|
||||
protected DifficultyCalculator(Beatmap beatmap)
|
||||
{
|
||||
Objects = CreateBeatmapConverter().Convert(beatmap, true).HitObjects;
|
||||
Beatmap = beatmap;
|
||||
|
||||
Objects = CreateBeatmapConverter().Convert(beatmap).HitObjects;
|
||||
|
||||
foreach (var h in Objects)
|
||||
h.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.Difficulty);
|
||||
|
@ -11,6 +11,8 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
{
|
||||
public class BeatmapGroup : IStateful<BeatmapGroupState>
|
||||
{
|
||||
public event Action<BeatmapGroupState> StateChanged;
|
||||
|
||||
public BeatmapPanel SelectedPanel;
|
||||
|
||||
/// <summary>
|
||||
@ -23,19 +25,26 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
/// </summary>
|
||||
public Action<BeatmapInfo> StartRequested;
|
||||
|
||||
public BeatmapSetHeader Header;
|
||||
public Action<BeatmapSetInfo> DeleteRequested;
|
||||
|
||||
private BeatmapGroupState state;
|
||||
public Action<BeatmapSetInfo> RestoreHiddenRequested;
|
||||
|
||||
public Action<BeatmapInfo> HideDifficultyRequested;
|
||||
|
||||
public BeatmapSetHeader Header;
|
||||
|
||||
public List<BeatmapPanel> BeatmapPanels;
|
||||
|
||||
public BeatmapSetInfo BeatmapSet;
|
||||
|
||||
private BeatmapGroupState state;
|
||||
public BeatmapGroupState State
|
||||
{
|
||||
get { return state; }
|
||||
set
|
||||
{
|
||||
state = value;
|
||||
|
||||
switch (value)
|
||||
{
|
||||
case BeatmapGroupState.Expanded:
|
||||
@ -54,7 +63,8 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
panel.State = PanelSelectedState.Hidden;
|
||||
break;
|
||||
}
|
||||
state = value;
|
||||
|
||||
StateChanged?.Invoke(state);
|
||||
}
|
||||
}
|
||||
|
||||
@ -66,14 +76,17 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
Header = new BeatmapSetHeader(beatmap)
|
||||
{
|
||||
GainedSelection = headerGainedSelection,
|
||||
DeleteRequested = b => DeleteRequested(b),
|
||||
RestoreHiddenRequested = b => RestoreHiddenRequested(b),
|
||||
RelativeSizeAxes = Axes.X,
|
||||
};
|
||||
|
||||
BeatmapSet.Beatmaps = BeatmapSet.Beatmaps.OrderBy(b => b.StarDifficulty).ToList();
|
||||
BeatmapSet.Beatmaps = BeatmapSet.Beatmaps.Where(b => !b.Hidden).OrderBy(b => b.StarDifficulty).ToList();
|
||||
BeatmapPanels = BeatmapSet.Beatmaps.Select(b => new BeatmapPanel(b)
|
||||
{
|
||||
Alpha = 0,
|
||||
GainedSelection = panelGainedSelection,
|
||||
HideRequested = p => HideDifficultyRequested?.Invoke(p),
|
||||
StartRequested = p => { StartRequested?.Invoke(p.Beatmap); },
|
||||
RelativeSizeAxes = Axes.X,
|
||||
}).ToList();
|
||||
@ -81,6 +94,7 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
Header.AddDifficultyIcons(BeatmapPanels);
|
||||
}
|
||||
|
||||
|
||||
private void headerGainedSelection(BeatmapSetHeader panel)
|
||||
{
|
||||
State = BeatmapGroupState.Expanded;
|
||||
|
@ -5,6 +5,7 @@ using System;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Backgrounds;
|
||||
@ -14,16 +15,20 @@ using OpenTK.Graphics;
|
||||
using osu.Framework.Input;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
|
||||
namespace osu.Game.Beatmaps.Drawables
|
||||
{
|
||||
public class BeatmapPanel : Panel
|
||||
public class BeatmapPanel : Panel, IHasContextMenu
|
||||
{
|
||||
public BeatmapInfo Beatmap;
|
||||
private readonly Sprite background;
|
||||
|
||||
public Action<BeatmapPanel> GainedSelection;
|
||||
public Action<BeatmapPanel> StartRequested;
|
||||
public Action<BeatmapPanel> EditRequested;
|
||||
public Action<BeatmapInfo> HideRequested;
|
||||
|
||||
private readonly Triangles triangles;
|
||||
private readonly StarCounter starCounter;
|
||||
|
||||
@ -148,5 +153,12 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public MenuItem[] ContextMenuItems => new MenuItem[]
|
||||
{
|
||||
new OsuMenuItem("Play", MenuItemType.Highlighted, () => StartRequested?.Invoke(this)),
|
||||
new OsuMenuItem("Edit", MenuItemType.Standard, () => EditRequested?.Invoke(this)),
|
||||
new OsuMenuItem("Hide", MenuItemType.Destructive, () => HideRequested?.Invoke(Beatmap)),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -3,22 +3,31 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
|
||||
namespace osu.Game.Beatmaps.Drawables
|
||||
{
|
||||
public class BeatmapSetHeader : Panel
|
||||
public class BeatmapSetHeader : Panel, IHasContextMenu
|
||||
{
|
||||
public Action<BeatmapSetHeader> GainedSelection;
|
||||
|
||||
public Action<BeatmapSetInfo> DeleteRequested;
|
||||
|
||||
public Action<BeatmapSetInfo> RestoreHiddenRequested;
|
||||
|
||||
private readonly SpriteText title;
|
||||
private readonly SpriteText artist;
|
||||
|
||||
@ -148,5 +157,23 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
foreach (var p in panels)
|
||||
difficultyIcons.Add(new DifficultyIcon(p.Beatmap));
|
||||
}
|
||||
|
||||
public MenuItem[] ContextMenuItems
|
||||
{
|
||||
get
|
||||
{
|
||||
List<MenuItem> items = new List<MenuItem>();
|
||||
|
||||
if (State == PanelSelectedState.NotSelected)
|
||||
items.Add(new OsuMenuItem("Expand", MenuItemType.Highlighted, () => State = PanelSelectedState.Selected));
|
||||
|
||||
if (beatmap.BeatmapSetInfo.Beatmaps.Any(b => b.Hidden))
|
||||
items.Add(new OsuMenuItem("Restore all hidden", MenuItemType.Standard, () => RestoreHiddenRequested?.Invoke(beatmap.BeatmapSetInfo)));
|
||||
|
||||
items.Add(new OsuMenuItem("Delete", MenuItemType.Destructive, () => DeleteRequested?.Invoke(beatmap.BeatmapSetInfo)));
|
||||
|
||||
return items.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -33,7 +33,8 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
Normal,
|
||||
Hard,
|
||||
Insane,
|
||||
Expert
|
||||
Expert,
|
||||
ExpertPlus
|
||||
}
|
||||
|
||||
private DifficultyRating getDifficultyRating(BeatmapInfo beatmap)
|
||||
@ -44,7 +45,8 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
if (rating < 2.25) return DifficultyRating.Normal;
|
||||
if (rating < 3.75) return DifficultyRating.Hard;
|
||||
if (rating < 5.25) return DifficultyRating.Insane;
|
||||
return DifficultyRating.Expert;
|
||||
if (rating < 6.75) return DifficultyRating.Expert;
|
||||
return DifficultyRating.ExpertPlus;
|
||||
}
|
||||
|
||||
private Color4 getColour(BeatmapInfo beatmap)
|
||||
@ -55,12 +57,14 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
return palette.Green;
|
||||
default:
|
||||
case DifficultyRating.Normal:
|
||||
return palette.Yellow;
|
||||
return palette.Blue;
|
||||
case DifficultyRating.Hard:
|
||||
return palette.Pink;
|
||||
return palette.Yellow;
|
||||
case DifficultyRating.Insane:
|
||||
return palette.Purple;
|
||||
return palette.Pink;
|
||||
case DifficultyRating.Expert:
|
||||
return palette.Purple;
|
||||
case DifficultyRating.ExpertPlus:
|
||||
return palette.Gray0;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using osu.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@ -15,6 +16,8 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
{
|
||||
public const float MAX_HEIGHT = 80;
|
||||
|
||||
public event Action<PanelSelectedState> StateChanged;
|
||||
|
||||
public override bool RemoveWhenNotAlive => false;
|
||||
|
||||
private readonly Container nestedContainer;
|
||||
@ -77,11 +80,15 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
|
||||
set
|
||||
{
|
||||
if (state == value) return;
|
||||
if (state == value)
|
||||
return;
|
||||
|
||||
var last = state;
|
||||
state = value;
|
||||
|
||||
ApplyState(last);
|
||||
|
||||
StateChanged?.Invoke(State);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,6 @@ using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.Play;
|
||||
|
||||
namespace osu.Game.Beatmaps
|
||||
{
|
||||
@ -74,8 +73,6 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
public override string Description => "dummy";
|
||||
|
||||
public override IEnumerable<KeyCounter> CreateGameplayKeys() => new List<KeyCounter>();
|
||||
|
||||
public DummyRuleset(RulesetInfo rulesetInfo)
|
||||
: base(rulesetInfo)
|
||||
{
|
||||
|
@ -280,7 +280,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
|
||||
double time = double.Parse(split[0].Trim(), NumberFormatInfo.InvariantInfo);
|
||||
double beatLength = double.Parse(split[1].Trim(), NumberFormatInfo.InvariantInfo);
|
||||
double speedMultiplier = beatLength < 0 ? -beatLength / 100.0 : 1;
|
||||
double speedMultiplier = beatLength < 0 ? 100.0 / -beatLength : 1;
|
||||
|
||||
TimeSignatures timeSignature = TimeSignatures.SimpleQuadruple;
|
||||
if (split.Length >= 3)
|
||||
|
Reference in New Issue
Block a user