// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using System.Drawing; using System.IO; using System.Linq; using Newtonsoft.Json; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Textures; using osu.Framework.Input; using osu.Framework.IO.Stores; using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API.Requests; using osu.Game.Rulesets; using osu.Game.Tournament.Components; using osu.Game.Tournament.IPC; using osuTK.Input; namespace osu.Game.Tournament { public abstract class TournamentGameBase : OsuGameBase { private const string bracket_filename = "bracket.json"; private LadderInfo ladder; private Storage storage; private DependencyContainer dependencies; private readonly Bindable ruleset = new Bindable(); private Bindable windowSize; private FileBasedIPC ipc; protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { return dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); } [BackgroundDependencyLoader] private void load(Storage storage, FrameworkConfigManager frameworkConfig) { Resources.AddStore(new DllResourceStore(@"osu.Game.Tournament.dll")); Fonts.AddStore(new GlyphStore(Resources, @"Resources/Fonts/Aquatico-Regular")); Fonts.AddStore(new GlyphStore(Resources, @"Resources/Fonts/Aquatico-Light")); Textures.AddStore(new TextureLoaderStore(new ResourceStore(new StorageBackedResourceStore(storage)))); this.storage = storage; windowSize = frameworkConfig.GetBindable(FrameworkSetting.WindowedSize); readBracket(); ladder.CurrentMatch.Value = ladder.Pairings.FirstOrDefault(p => p.Current.Value); dependencies.CacheAs(ipc = new FileBasedIPC()); Add(ipc); Add(new OsuButton { Text = "Save Changes", Width = 140, Height = 50, Depth = float.MinValue, Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, Padding = new MarginPadding(10), Action = SaveChanges, }); } private void readBracket() { if (storage.Exists(bracket_filename)) { using (Stream stream = storage.GetStream(bracket_filename, FileAccess.Read, FileMode.Open)) using (var sr = new StreamReader(stream)) ladder = JsonConvert.DeserializeObject(sr.ReadToEnd()); } else { ladder = new LadderInfo(); } dependencies.Cache(ladder); bool addedInfo = false; // assign teams foreach (var pairing in ladder.Pairings) { pairing.Team1.Value = ladder.Teams.FirstOrDefault(t => t.Acronym == pairing.Team1Acronym); pairing.Team2.Value = ladder.Teams.FirstOrDefault(t => t.Acronym == pairing.Team2Acronym); foreach (var conditional in pairing.ConditionalPairings) { conditional.Team1.Value = ladder.Teams.FirstOrDefault(t => t.Acronym == conditional.Team1Acronym); conditional.Team2.Value = ladder.Teams.FirstOrDefault(t => t.Acronym == conditional.Team2Acronym); conditional.Grouping.Value = pairing.Grouping.Value; } } // assign progressions foreach (var pair in ladder.Progressions) { var src = ladder.Pairings.FirstOrDefault(p => p.ID == pair.SourceID); var dest = ladder.Pairings.FirstOrDefault(p => p.ID == pair.TargetID); if (src == null) throw new InvalidOperationException(); if (dest != null) { if (pair.Losers) src.LosersProgression.Value = dest; else src.Progression.Value = dest; } } // link pairings to groupings foreach (var group in ladder.Groupings) foreach (var id in group.Pairings) { var found = ladder.Pairings.FirstOrDefault(p => p.ID == id); if (found != null) { found.Grouping.Value = group; if (group.StartDate.Value > found.Date.Value) found.Date.Value = group.StartDate.Value; } } addedInfo |= addPlayers(); addedInfo |= addBeatmaps(); addedInfo |= addCountries(); if (addedInfo) SaveChanges(); } /// /// Add missing player info based on user IDs. /// /// private bool addPlayers() { bool addedInfo = false; foreach (var t in ladder.Teams) foreach (var p in t.Players) if (string.IsNullOrEmpty(p.Username)) { var req = new GetUserRequest(p.Id); req.Perform(API); p.Username = req.Result.Username; addedInfo = true; } return addedInfo; } /// /// Add missing beatmap info based on beatmap IDs /// private bool addBeatmaps() { bool addedInfo = false; foreach (var g in ladder.Groupings) foreach (var b in g.Beatmaps) if (b.BeatmapInfo == null) { var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = b.ID }); req.Perform(API); b.BeatmapInfo = req.Result?.ToBeatmap(RulesetStore); addedInfo = true; } return addedInfo; } /// /// Add missing country info based on acronyms. /// private bool addCountries() { bool addedInfo = false; List countries; using (Stream stream = Resources.GetStream("Resources/countries.json")) using (var sr = new StreamReader(stream)) countries = JsonConvert.DeserializeObject>(sr.ReadToEnd()); foreach (var t in ladder.Teams) { if (!string.IsNullOrEmpty(t.FullName)) continue; var result = countries.FirstOrDefault(c => c.Acronym == t.Acronym); if (result == null) continue; t.Acronym = result.Acronym; t.FlagName = result.FlagName; t.FullName = result.FullName; addedInfo = true; } return addedInfo; } protected override void LoadComplete() { MenuCursorContainer.Cursor.AlwaysPresent = true; // required for tooltip display MenuCursorContainer.Cursor.Alpha = 0; base.LoadComplete(); } protected override void Update() { base.Update(); var minWidth = (int)(windowSize.Value.Height / 9f * 16 + 400); if (windowSize.Value.Width < minWidth) { // todo: can be removed after ppy/osu-framework#1975 windowSize.Value = Host.Window.ClientSize = new Size(minWidth, windowSize.Value.Height); } } protected virtual void SaveChanges() { using (var stream = storage.GetStream(bracket_filename, FileAccess.Write, FileMode.Create)) using (var sw = new StreamWriter(stream)) { sw.Write(JsonConvert.SerializeObject(ladder, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore, })); } } protected override UserInputManager CreateUserInputManager() => new TournamentInputManager(); private class TournamentInputManager : UserInputManager { protected override MouseButtonEventManager CreateButtonManagerFor(MouseButton button) { switch (button) { case MouseButton.Right: return new RightMouseManager(button); } return base.CreateButtonManagerFor(button); } private class RightMouseManager : MouseButtonEventManager { public RightMouseManager(MouseButton button) : base(button) { } public override bool EnableDrag => true; // allow right-mouse dragging for absolute scroll in scroll containers. public override bool EnableClick => true; public override bool ChangeFocusOnClick => false; } } } }