diff --git a/osu.Desktop.VisualTests/Tests/TestCaseModSelector.cs b/osu.Desktop.VisualTests/Tests/TestCaseModSelector.cs new file mode 100644 index 0000000000..90d175fdfa --- /dev/null +++ b/osu.Desktop.VisualTests/Tests/TestCaseModSelector.cs @@ -0,0 +1,44 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using OpenTK.Graphics; +using osu.Framework.Logging; +using osu.Framework.Graphics; +using osu.Game.Overlays.Mods; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Colour; +using osu.Framework.GameModes.Testing; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Graphics.Primitives; +using osu.Game.Modes.UI; +using osu.Game.Modes; +using OpenTK; +using osu.Game.Graphics; + +namespace osu.Desktop.VisualTests.Tests +{ + class TestCaseModSelector : TestCase + { + public override string Name => @"Mod Selector"; + + public override string Description => @"Tests the mod selector overlay"; + + private ModSelector modSelector; + + public override void Reset() + { + base.Reset(); + + Add(modSelector = new ModSelector + { + RelativeSizeAxes = Axes.Both, + Origin = Anchor.BottomCentre, + Anchor = Anchor.BottomCentre, + }); + + AddButton("Toggle", modSelector.ToggleVisibility); + } + } +} diff --git a/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj b/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj index cd899d4dd2..1e80d1a50f 100644 --- a/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj +++ b/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj @@ -191,6 +191,7 @@ + diff --git a/osu.Game/Graphics/TextAwesome.cs b/osu.Game/Graphics/TextAwesome.cs index 07df1e1c94..c15f20f20a 100644 --- a/osu.Game/Graphics/TextAwesome.cs +++ b/osu.Game/Graphics/TextAwesome.cs @@ -903,6 +903,6 @@ namespace osu.Game.Graphics fa_osu_mod_spunout = 0xe046, fa_osu_mod_suddendeath = 0xe047, fa_osu_mod_target = 0xe048, - fa_osu_mod_bg = 0xe049, + fa_osu_mod_bg = 0xe04a, } } diff --git a/osu.Game/Modes/Mod.cs b/osu.Game/Modes/Mod.cs new file mode 100644 index 0000000000..3374c1c67f --- /dev/null +++ b/osu.Game/Modes/Mod.cs @@ -0,0 +1,355 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.ComponentModel; +using osu.Game.Graphics; + +namespace osu.Game.Modes +{ + public class Mod + { + public virtual Mods Name + { + get; + } + + public virtual FontAwesome Icon + { + get; + } + + public virtual double ScoreMultiplier(PlayMode mode) + { + return 1; + } + + public virtual bool Ranked(PlayMode mode) + { + return false; + } + } + + public class ModNoFail : Mod + { + public override Mods Name => Mods.NoFail; + public override FontAwesome Icon => FontAwesome.fa_osu_mod_nofail; + public override double ScoreMultiplier(PlayMode mode) => 0.5; + public override bool Ranked(PlayMode mode) => true; + } + + public class ModEasy : Mod + { + public override Mods Name => Mods.Easy; + public override FontAwesome Icon => FontAwesome.fa_osu_mod_easy; + public override double ScoreMultiplier(PlayMode mode) => 0.5; + public override bool Ranked(PlayMode mode) => true; + } + + public class ModHidden : Mod + { + public override Mods Name => Mods.Hidden; + public override FontAwesome Icon => FontAwesome.fa_osu_mod_hidden; + public override double ScoreMultiplier(PlayMode mode) => 1.06; + public override bool Ranked(PlayMode mode) => true; + } + + public class ModHardRock : Mod + { + public override Mods Name => Mods.HardRock; + public override FontAwesome Icon => FontAwesome.fa_osu_mod_hardrock; + public override double ScoreMultiplier(PlayMode mode) => 1.06; + public override bool Ranked(PlayMode mode) => true; + } + + public class ModSuddenDeath : Mod + { + public override Mods Name => Mods.SuddenDeath; + public override FontAwesome Icon => FontAwesome.fa_osu_mod_suddendeath; + public override double ScoreMultiplier(PlayMode mode) => 1; + public override bool Ranked(PlayMode mode) => true; + } + + public class ModDoubleTime : Mod + { + public override Mods Name => Mods.DoubleTime; + public override FontAwesome Icon => FontAwesome.fa_osu_mod_doubletime; + public override double ScoreMultiplier(PlayMode mode) => 1.12; + public override bool Ranked(PlayMode mode) => true; + } + + public class ModRelax : Mod + { + public override Mods Name => Mods.Relax; + public override FontAwesome Icon => FontAwesome.fa_osu_mod_relax; + public override double ScoreMultiplier(PlayMode mode) => 0; + public override bool Ranked(PlayMode mode) => false; + } + + public class ModHalfTime : Mod + { + public override Mods Name => Mods.HalfTime; + public override FontAwesome Icon => FontAwesome.fa_osu_mod_halftime; + public override double ScoreMultiplier(PlayMode mode) => 0.3; + public override bool Ranked(PlayMode mode) => true; + } + + public class ModNightcore : Mod + { + public override Mods Name => Mods.Nightcore; + public override FontAwesome Icon => FontAwesome.fa_osu_mod_nightcore; + public override double ScoreMultiplier(PlayMode mode) => 1.12; + public override bool Ranked(PlayMode mode) => true; + } + + public class ModFlashlight : Mod + { + public override Mods Name => Mods.Flashlight; + public override FontAwesome Icon => FontAwesome.fa_osu_mod_flashlight; + public override double ScoreMultiplier(PlayMode mode) => 1.12; + public override bool Ranked(PlayMode mode) => true; + } + + public class ModAutoplay : Mod + { + public override Mods Name => Mods.Autoplay; + public override FontAwesome Icon => FontAwesome.fa_osu_mod_auto; + public override double ScoreMultiplier(PlayMode mode) => 0; + public override bool Ranked(PlayMode mode) => false; + } + + public class ModSpunOut : Mod + { + public override Mods Name => Mods.SpunOut; + public override FontAwesome Icon => FontAwesome.fa_osu_mod_spunout; + public override double ScoreMultiplier(PlayMode mode) => 0.9; + public override bool Ranked(PlayMode mode) => true; + } + + public class ModRelax2 : Mod + { + public override Mods Name => Mods.Relax2; + public override FontAwesome Icon => FontAwesome.fa_osu_mod_autopilot; + public override double ScoreMultiplier(PlayMode mode) => 0; + public override bool Ranked(PlayMode mode) => false; + } + + public class ModPerfect : Mod + { + public override Mods Name => Mods.Perfect; + public override FontAwesome Icon => FontAwesome.fa_osu_mod_perfect; + public override double ScoreMultiplier(PlayMode mode) => 1; + public override bool Ranked(PlayMode mode) => true; + } + + public class ModKey4 : Mod + { + public override Mods Name => Mods.Key4; + public override FontAwesome Icon => FontAwesome.fa_key; + public override double ScoreMultiplier(PlayMode mode) => 1; + public override bool Ranked(PlayMode mode) => true; + } + + public class ModKey5 : Mod + { + public override Mods Name => Mods.Key5; + public override FontAwesome Icon => FontAwesome.fa_key; + public override double ScoreMultiplier(PlayMode mode) => 1; + public override bool Ranked(PlayMode mode) => true; + } + + public class ModKey6 : Mod + { + public override Mods Name => Mods.Key6; + public override FontAwesome Icon => FontAwesome.fa_key; + public override double ScoreMultiplier(PlayMode mode) => 1; + public override bool Ranked(PlayMode mode) => true; + } + + public class ModKey7 : Mod + { + public override Mods Name => Mods.Key7; + public override FontAwesome Icon => FontAwesome.fa_key; + public override double ScoreMultiplier(PlayMode mode) => 1; + public override bool Ranked(PlayMode mode) => true; + } + + public class ModKey8 : Mod + { + public override Mods Name => Mods.Key8; + public override FontAwesome Icon => FontAwesome.fa_key; + public override double ScoreMultiplier(PlayMode mode) => 1; + public override bool Ranked(PlayMode mode) => true; + } + + public class ModFadeIn : Mod + { + public override Mods Name => Mods.FadeIn; + public override FontAwesome Icon => FontAwesome.fa_osu_mod_hidden; + public override double ScoreMultiplier(PlayMode mode) => 1; + public override bool Ranked(PlayMode mode) => true; + } + + public class ModRandom : Mod + { + public override Mods Name => Mods.Random; + public override FontAwesome Icon => FontAwesome.fa_random; + public override double ScoreMultiplier(PlayMode mode) => 1; + public override bool Ranked(PlayMode mode) => false; + } + + public class ModCinema : Mod + { + public override Mods Name => Mods.Cinema; + public override FontAwesome Icon => FontAwesome.fa_osu_mod_cinema; + public override double ScoreMultiplier(PlayMode mode) => 0; + public override bool Ranked(PlayMode mode) => false; + } + + public class ModTarget : Mod + { + public override Mods Name => Mods.Target; + public override FontAwesome Icon => FontAwesome.fa_osu_mod_target; + public override double ScoreMultiplier(PlayMode mode) => 1; + public override bool Ranked(PlayMode mode) => true; + } + + public class ModKey9 : Mod + { + public override Mods Name => Mods.Key9; + public override FontAwesome Icon => FontAwesome.fa_key; + public override double ScoreMultiplier(PlayMode mode) => 1; + public override bool Ranked(PlayMode mode) => true; + } + + public class ModKeyCoop : Mod + { + public override Mods Name => Mods.KeyCoop; + public override FontAwesome Icon => FontAwesome.fa_key; + public override double ScoreMultiplier(PlayMode mode) => 1; + public override bool Ranked(PlayMode mode) => true; + } + + public class ModKey1 : Mod + { + public override Mods Name => Mods.Key1; + public override FontAwesome Icon => FontAwesome.fa_key; + public override double ScoreMultiplier(PlayMode mode) => 1; + public override bool Ranked(PlayMode mode) => true; + } + + public class ModKey3 : Mod + { + public override Mods Name => Mods.Key3; + public override FontAwesome Icon => FontAwesome.fa_key; + public override double ScoreMultiplier(PlayMode mode) => 1; + public override bool Ranked(PlayMode mode) => true; + } + + public class ModKey2 : Mod + { + public override Mods Name => Mods.Key2; + public override FontAwesome Icon => FontAwesome.fa_key; + public override double ScoreMultiplier(PlayMode mode) => 1; + public override bool Ranked(PlayMode mode) => true; + } + + + [Flags] + public enum Mods + { + None = 0, + + [Description(@"No Fail")] + NoFail = 1 << 0, + + [Description(@"Easy")] + Easy = 1 << 1, + + //NoVideo = 1 << 2, + + [Description(@"Hidden")] + Hidden = 1 << 3, + + [Description(@"Hard Rock")] + HardRock = 1 << 4, + + [Description(@"Sudden Death")] + SuddenDeath = 1 << 5, + + [Description(@"Double Time")] + DoubleTime = 1 << 6, + + [Description(@"Relax")] + Relax = 1 << 7, + + [Description(@"Halftime")] + HalfTime = 1 << 8, + + [Description(@"Nightcore")] + Nightcore = 1 << 9, + + [Description(@"Flashlight")] + Flashlight = 1 << 10, + + [Description(@"Auto")] + Autoplay = 1 << 11, + + [Description(@"Spun Out")] + SpunOut = 1 << 12, + + [Description(@"Autopilot")] + Relax2 = 1 << 13, + + [Description(@"Perfect")] + Perfect = 1 << 14, + + [Description(@"4K")] + Key4 = 1 << 15, + + [Description(@"5K")] + Key5 = 1 << 16, + + [Description(@"6K")] + Key6 = 1 << 17, + + [Description(@"7K")] + Key7 = 1 << 18, + + [Description(@"8K")] + Key8 = 1 << 19, + + [Description(@"Fade In")] + FadeIn = 1 << 20, + + [Description(@"Random")] + Random = 1 << 21, + + [Description(@"Cinema")] + Cinema = 1 << 22, + + [Description(@"Target Practice")] + Target = 1 << 23, + + [Description(@"9K")] + Key9 = 1 << 24, + + [Description(@"Co-Op")] + KeyCoop = 1 << 25, + + [Description(@"1K")] + Key1 = 1 << 26, + + [Description(@"3K")] + Key3 = 1 << 27, + + [Description(@"2K")] + Key2 = 1 << 28, + + LastMod = 1 << 29, + + KeyMod = Key1 | Key2 | Key3 | Key4 | Key5 | Key6 | Key7 | Key8 | Key9 | KeyCoop, + FreeModAllowed = NoFail | Easy | Hidden | HardRock | SuddenDeath | Flashlight | FadeIn | Relax | Relax2 | SpunOut | KeyMod, + ScoreIncreaseMods = Hidden | HardRock | DoubleTime | Flashlight | FadeIn + } +} \ No newline at end of file diff --git a/osu.Game/Modes/UI/ModIcon.cs b/osu.Game/Modes/UI/ModIcon.cs new file mode 100644 index 0000000000..232c12bfc6 --- /dev/null +++ b/osu.Game/Modes/UI/ModIcon.cs @@ -0,0 +1,85 @@ +// Copyright (c) 2007-2016 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK.Graphics; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; + +namespace osu.Game.Modes.UI +{ + public class ModIcon : Container + { + private TextAwesome modIcon, background; + + private float iconSize = 80; + public float IconSize + { + get + { + return iconSize; + } + set + { + iconSize = value; + reapplySize(); + } + } + + private Color4 backgroundColour; + new public Color4 Colour + { + get + { + return backgroundColour; + } + set + { + backgroundColour = value; + background.Colour = value; + } + } + + private FontAwesome icon; + public FontAwesome Icon + { + get + { + return icon; + } + set + { + icon = value; + modIcon.Icon = value; + } + } + + private void reapplySize() + { + background.TextSize = iconSize; + modIcon.TextSize = iconSize - 35; + } + + public ModIcon() + { + Children = new Drawable[] + { + background = new TextAwesome + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Icon = FontAwesome.fa_osu_mod_bg, + Shadow = true, + }, + modIcon = new TextAwesome + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Colour = OsuColour.Gray(84), + }, + }; + + reapplySize(); + } + } +} \ No newline at end of file diff --git a/osu.Game/Overlays/Mods/AssistedSection.cs b/osu.Game/Overlays/Mods/AssistedSection.cs new file mode 100644 index 0000000000..d72769a631 --- /dev/null +++ b/osu.Game/Overlays/Mods/AssistedSection.cs @@ -0,0 +1,68 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Game.Graphics; +using osu.Game.Modes; +using osu.Game.Overlays.Mods; +namespace osu.Game +{ + public class AssistedSection : ModSection + { + public ModButton RelaxButton => Buttons[0]; + public ModButton AutopilotButton => Buttons[1]; + public ModButton TargetPracticeButton => Buttons[2]; + public ModButton SpunOutButton => Buttons[3]; + public ModButton AutoplayCinemaButton => Buttons[4]; + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Colour = colours.Blue; + } + + public AssistedSection() + { + Header = @"Assisted"; + Buttons = new ModButton[] + { + new ModButton + { + Mods = new Mod[] + { + new ModRelax(), + }, + }, + new ModButton + { + Mods = new Mod[] + { + new ModRelax2(), + }, + }, + new ModButton + { + Mods = new Mod[] + { + new ModTarget(), + }, + }, + new ModButton + { + Mods = new Mod[] + { + new ModSpunOut(), + }, + }, + new ModButton + { + Mods = new Mod[] + { + new ModAutoplay(), + new ModCinema(), + }, + }, + }; + } + } +} diff --git a/osu.Game/Overlays/Mods/DifficultyIncreaseSection.cs b/osu.Game/Overlays/Mods/DifficultyIncreaseSection.cs new file mode 100644 index 0000000000..b9884a311c --- /dev/null +++ b/osu.Game/Overlays/Mods/DifficultyIncreaseSection.cs @@ -0,0 +1,70 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Game.Graphics; +using osu.Game.Modes; +using osu.Game.Overlays.Mods; + +namespace osu.Game +{ + public class DifficultyIncreaseSection : ModSection + { + public ModButton HardRockButton => Buttons[0]; + public ModButton SuddenDeathButton => Buttons[1]; + public ModButton DoubleTimeNightcoreButton => Buttons[2]; + public ModButton HiddenButton => Buttons[3]; + public ModButton FlashlightButton => Buttons[4]; + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Colour = colours.Yellow; + } + + public DifficultyIncreaseSection() + { + Header = @"Gameplay Difficulty Increase"; + Buttons = new ModButton[] + { + new ModButton + { + Mods = new Mod[] + { + new ModHardRock(), + }, + }, + new ModButton + { + Mods = new Mod[] + { + new ModSuddenDeath(), + new ModPerfect(), + }, + }, + new ModButton + { + Mods = new Mod[] + { + new ModDoubleTime(), + new ModNightcore(), + }, + }, + new ModButton + { + Mods = new Mod[] + { + new ModHidden(), + }, + }, + new ModButton + { + Mods = new Mod[] + { + new ModFlashlight(), + }, + }, + }; + } + } +} diff --git a/osu.Game/Overlays/Mods/DifficultyReductionSection.cs b/osu.Game/Overlays/Mods/DifficultyReductionSection.cs new file mode 100644 index 0000000000..810f40502b --- /dev/null +++ b/osu.Game/Overlays/Mods/DifficultyReductionSection.cs @@ -0,0 +1,52 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Game.Graphics; +using osu.Game.Modes; +using osu.Game.Overlays.Mods; + +namespace osu.Game +{ + public class DifficultyReductionSection : ModSection + { + public ModButton EasyButton => Buttons[0]; + public ModButton NoFailButton => Buttons[1]; + public ModButton HalfTimeButton => Buttons[2]; + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Colour = colours.Green; + } + + public DifficultyReductionSection() + { + Header = @"Gameplay Difficulty Reduction"; + Buttons = new ModButton[] + { + new ModButton + { + Mods = new Mod[] + { + new ModEasy(), + }, + }, + new ModButton + { + Mods = new Mod[] + { + new ModNoFail(), + }, + }, + new ModButton + { + Mods = new Mod[] + { + new ModHalfTime(), + }, + }, + }; + } + } +} diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs new file mode 100644 index 0000000000..545ea16df3 --- /dev/null +++ b/osu.Game/Overlays/Mods/ModButton.cs @@ -0,0 +1,251 @@ +// Copyright (c) 2007-2016 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using OpenTK; using OpenTK.Graphics; +using OpenTK.Input; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Transformations; using osu.Game.Graphics.Sprites; +using osu.Game.Modes; +using osu.Game.Modes.UI; +namespace osu.Game.Overlays.Mods +{ + public class ModButton : FlowContainer + { + private ModIcon[] icons; + private ModIcon displayIcon + { + get + { + return icons[icons.Length - 1]; + } + } + private SpriteText text; + private Container iconsContainer; + private AudioSample sampleOn, sampleOff; + + public Action Action; // Passed the selected mod or null if none + + private int _selectedMod = -1; + private int selectedMod + { + get + { + return _selectedMod; + } + set + { + if (value == _selectedMod) return; + _selectedMod = value; + + if (value >= Mods.Length) + { + _selectedMod = -1; + } + else if (value <= -2) + { + _selectedMod = Mods.Length - 1; + } + + iconsContainer.RotateTo(Selected ? 5f : 0f, 300, EasingTypes.OutElastic); + iconsContainer.ScaleTo(Selected ? 1.1f : 1f, 300, EasingTypes.OutElastic); + + displaySelectedMod(); + } + } + + public bool Selected + { + get + { + return selectedMod != -1; + } + } + + private Color4 backgroundColour; + public new Color4 Colour + { + get + { + return backgroundColour; + } + set + { + backgroundColour = value; + + foreach (ModIcon icon in icons) + { + icon.Colour = value; + } + } + } + + private Mod[] mods; + public Mod[] Mods + { + get + { + return mods; + } + set + { + mods = value; + createIcons(); + if (value.Length > 0) + { + displayMod(value[0]); + } + } + } + + public Mod SelectedMod + { + get + { + if (selectedMod >= 0) + { + return Mods[selectedMod]; + } + else + { + return null; + } + } + } + + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + sampleOn = audio.Sample.Get(@"Checkbox/check-on"); + sampleOff = audio.Sample.Get(@"Checkbox/check-off"); + } + + protected override bool OnMouseDown(Framework.Input.InputState state, MouseDownEventArgs args) + { + (args.Button == MouseButton.Right ? (Action)SelectPrevious : SelectNext)(); + return true; + } + + public void SelectNext() + { + selectedMod++; + if (selectedMod == -1) + { + sampleOff.Play(); + } + else + { + sampleOn.Play(); + } + + Action?.Invoke(SelectedMod); + } + + public void SelectPrevious() + { + selectedMod--; + if (selectedMod == -1) + { + sampleOff.Play(); + } + else + { + sampleOn.Play(); + } + + Action?.Invoke(SelectedMod); + } + + public void Deselect() + { + selectedMod = -1; + } + + private void displayMod(Mod mod) + { + displayIcon.Icon = mod.Icon; + text.Text = mod.Name.GetDescription(); + } + + private void displaySelectedMod() + { + var modIndex = selectedMod; + if (modIndex <= -1) + { + modIndex = 0; + } + + displayMod(Mods[modIndex]); + } + + private void createIcons() + { + if (Mods.Length > 1) + { + iconsContainer.Add(icons = new ModIcon[] + { + new ModIcon + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Position = new Vector2(1.5f), + }, + new ModIcon + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Position = new Vector2(-1.5f), + }, + }); + } + else + { + iconsContainer.Add(icons = new ModIcon[] + { + new ModIcon + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + AutoSizeAxes = Axes.Both, + }, + }); + } + } + + public ModButton() + { + Direction = FlowDirections.Vertical; + Spacing = new Vector2(0f, -5f); + Size = new Vector2(100f); + + Children = new Drawable[] + { + new Container + { + Size = new Vector2(77f, 80f), + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Children = new Drawable[] + { + iconsContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + } + } + }, + text = new OsuSpriteText + { + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + TextSize = 18, + }, + }; + } + } +} diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs new file mode 100644 index 0000000000..98205559ad --- /dev/null +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -0,0 +1,139 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; +using osu.Game.Graphics.Sprites; +using osu.Game.Modes; + +namespace osu.Game.Overlays.Mods +{ + public class ModSection : Container + { + private OsuSpriteText headerLabel; + + private FlowContainer buttonsContainer; + public FlowContainer ButtonsContainer + { + get + { + return buttonsContainer; + } + } + + public Action Action; + + public Mod[] SelectedMods + { + get + { + List selectedMods = new List(); + + foreach (ModButton button in Buttons) + { + Mod selectedMod = button.SelectedMod; + if (selectedMod != null) + { + selectedMods.Add(selectedMod); + } + } + + return selectedMods.ToArray(); + } + } + + private string header; + public string Header + { + get + { + return header; + } + set + { + header = value; + headerLabel.Text = value; + } + } + + private ModButton[] buttons = {}; + public ModButton[] Buttons + { + get + { + return buttons; + } + set + { + if (value == buttons) return; + buttons = value; + + foreach (ModButton button in value) + { + button.Colour = Colour; + button.Action = buttonPressed; + } + + buttonsContainer.Add(value); + } + } + + private Color4 colour = Color4.White; + new public Color4 Colour + { + get + { + return colour; + } + set + { + if (value == colour) return; + colour = value; + + foreach (ModButton button in buttons) + { + button.Colour = value; + } + } + } + + private void buttonPressed(Mod mod) + { + Action?.Invoke(SelectedMods); + } + + public ModSection() + { + AutoSizeAxes = Axes.Y; + + Children = new Drawable[] + { + headerLabel = new OsuSpriteText + { + Origin = Anchor.TopLeft, + Anchor = Anchor.TopLeft, + Position = new Vector2(0f, 10f), + Font = @"Exo2.0-Bold", + Text = Header, + }, + buttonsContainer = new FlowContainer + { + AutoSizeAxes = Axes.Both, + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + //Direction = FlowDirections.Horizontal, + Spacing = new Vector2(50f, 0f), + Margin = new MarginPadding + { + Top = 16, + }, + }, + }; + } + } +} diff --git a/osu.Game/Overlays/Mods/ModSelector.cs b/osu.Game/Overlays/Mods/ModSelector.cs new file mode 100644 index 0000000000..b5d95d5500 --- /dev/null +++ b/osu.Game/Overlays/Mods/ModSelector.cs @@ -0,0 +1,448 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Linq; +using System.Collections.Generic; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Transformations; +using osu.Game.Graphics; +using osu.Game.Graphics.Backgrounds; +using osu.Game.Graphics.Sprites; +using osu.Game.Modes; +using System; +using osu.Framework.Allocation; + +namespace osu.Game.Overlays.Mods +{ + public class ModSelector : OverlayContainer + { + private readonly int waves_duration = 1000; + private readonly int move_up_duration = 1500; + private readonly int move_up_delay = 200; + private readonly int move_out_duration = 500; + private readonly int button_duration = 1500; + private readonly int ranked_multiplier_duration = 2000; + + private readonly float content_width = 0.8f; + + private OsuSpriteText rankedLabel, multiplierLabel; + private FlowContainer rankedMultiplerContainer; + + private DifficultyReductionSection difficultyReductionSection; + private DifficultyIncreaseSection difficultyIncreaseSection; + private AssistedSection assistedSection; + + private Container contentContainer; + private Container[] waves; + + public Bindable SelectedMods = new Bindable(); + + // TODO: Add the fancy wave transition (https://streamable.com/qk4u) + protected override void PopIn() + { + FadeIn(move_up_duration, EasingTypes.OutQuint); + + Delay(move_up_delay); + Schedule(() => + { + contentContainer.MoveToY(0, move_up_duration, EasingTypes.OutQuint); + + rankedMultiplerContainer.MoveToX(0, ranked_multiplier_duration, EasingTypes.OutQuint); + rankedMultiplerContainer.FadeIn(ranked_multiplier_duration, EasingTypes.OutQuint); + + ModSection[] sections = new ModSection[] { difficultyReductionSection, difficultyIncreaseSection, assistedSection }; + for (int i = 0; i < sections.Length; i++) + { + sections[i].ButtonsContainer.TransformSpacingTo(new Vector2(50f, 0f), button_duration, EasingTypes.OutQuint); + sections[i].ButtonsContainer.MoveToX(0, button_duration, EasingTypes.OutQuint); + sections[i].FadeIn(button_duration, EasingTypes.OutQuint); + } + }); + + for (int i = 0; i < waves.Length; i++) + { + waves[i].MoveToY(-200, waves_duration + ((i + 1) * 500), EasingTypes.OutQuint); + } + } + + protected override void PopOut() + { + FadeOut(move_out_duration, EasingTypes.InSine); + + contentContainer.MoveToY(DrawHeight, move_out_duration, EasingTypes.InSine); + + rankedMultiplerContainer.MoveToX(rankedMultiplerContainer.DrawSize.X, move_out_duration, EasingTypes.InSine); + rankedMultiplerContainer.FadeOut(move_out_duration, EasingTypes.InSine); + + ModSection[] sections = new ModSection[] { difficultyReductionSection, difficultyIncreaseSection, assistedSection }; + for (int i = 0; i < sections.Length; i++) + { + sections[i].ButtonsContainer.TransformSpacingTo(new Vector2(100f, 0f), move_out_duration, EasingTypes.InSine); + sections[i].ButtonsContainer.MoveToX(100f, move_out_duration, EasingTypes.InSine); + sections[i].FadeIn(move_out_duration, EasingTypes.InSine); + } + + for (int i = 0; i < waves.Length; i++) + { + waves[i].MoveToY(DrawHeight + 200); + } + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + waves[0].Colour = colours.BlueLight; + waves[1].Colour = colours.Blue; + waves[2].Colour = colours.BlueDark; + waves[3].Colour = colours.BlueDarker; + } + + private void modButtonPressed(Mod[] sectionSelectedMods) + { + // + // Inverse mod deselection + // + // Hard Rock is the inverse of Easy + // Sudden Death / Perfect is the inverse of No Fail, Relax, AutoPilot, and Auto + // Double Time is the inverse of Half Time + // + // TODO: Probably make a better way for inverse mod handling + // + + foreach (Mod sectionMod in sectionSelectedMods) + { + if (sectionMod.Name == Modes.Mods.HardRock) + { + difficultyReductionSection.EasyButton.Deselect(); + } + else if (sectionMod.Name == Modes.Mods.Easy) + { + difficultyIncreaseSection.HardRockButton.Deselect(); + } + + if (sectionMod.Name == Modes.Mods.SuddenDeath || sectionMod.Name == Modes.Mods.Perfect) + { + difficultyReductionSection.NoFailButton.Deselect(); + assistedSection.RelaxButton.Deselect(); + assistedSection.AutopilotButton.Deselect(); + assistedSection.AutoplayCinemaButton.Deselect(); + } + else if (sectionMod.Name == Modes.Mods.NoFail || sectionMod.Name == Modes.Mods.Relax || sectionMod.Name == Modes.Mods.Relax2 || sectionMod.Name == Modes.Mods.Autoplay || sectionMod.Name == Modes.Mods.Cinema) + { + difficultyIncreaseSection.SuddenDeathButton.Deselect(); + } + + if (sectionMod.Name == Modes.Mods.DoubleTime || sectionMod.Name == Modes.Mods.Nightcore) + { + difficultyReductionSection.HalfTimeButton.Deselect(); + } + else if (sectionMod.Name == Modes.Mods.HalfTime) + { + difficultyIncreaseSection.DoubleTimeNightcoreButton.Deselect(); + } + } + + refreshSelectedMods(); + + double multiplier = 1; + bool ranked = true; + + foreach (Mod mod in SelectedMods.Value) + { + // TODO: Make this take the actual current mode + multiplier *= mod.ScoreMultiplier(PlayMode.Osu); + + if (ranked) + { + ranked = mod.Ranked(PlayMode.Osu); + } + } + + // 1.00x + // 1.05x + // 1.20x + multiplierLabel.Text = string.Format("{0:N2}x", multiplier); + rankedLabel.Text = $"{ranked ? @"Ranked" : @"Unranked"}, Score Multiplier: "; + } + + private void refreshSelectedMods() + { + List selectedMods = new List(); + + foreach (Mod mod in difficultyReductionSection.SelectedMods) + { + selectedMods.Add(mod); + } + + foreach (Mod mod in difficultyIncreaseSection.SelectedMods) + { + selectedMods.Add(mod); + } + + foreach (Mod mod in assistedSection.SelectedMods) + { + selectedMods.Add(mod); + } + + SelectedMods.Value = selectedMods.ToArray(); + } + + public ModSelector() + { + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + Masking = true, + Children = waves = new Container[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Width = 1.5f, + Position = new Vector2(0f), + Rotation = -10f, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + }, + }, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Width = 1.5f, + Position = new Vector2(0f, 50f), + Rotation = 8f, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + }, + }, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Width = 1.5f, + Position = new Vector2(0f, 150f), + Rotation = -5f, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + }, + }, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Width = 1.5f, + Position = new Vector2(0f, 300f), + Rotation = 2f, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + }, + }, + }, + }, + }, + contentContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = new Color4(36, 50, 68, 255) + }, + new Triangles + { + TriangleScale = 5, + RelativeSizeAxes = Axes.Both, + ColourLight = new Color4(53, 66, 82, 255), + ColourDark = new Color4(41, 54, 70, 255), + }, + } + }, + new Container + { + RelativeSizeAxes = Axes.X, + Height = 90, + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(10).Opacity(100), + }, + new FlowContainer + { + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FlowDirections.Vertical, + Width = content_width, + Padding = new MarginPadding + { + Top = 10, + Bottom = 10, + }, + Children = new Drawable[] + { + new OsuSpriteText + { + Font = @"Exo2.0-Bold", + Text = @"Gameplay Mods", + TextSize = 22, + Shadow = true, + Margin = new MarginPadding + { + Bottom = 4, + }, + }, + new OsuSpriteText + { + Text = @"Mods provide different ways to enjoy gameplay. Some have an effect on the score you can achieve during ranked play.", + TextSize = 18, + Shadow = true, + }, + new OsuSpriteText + { + Text = @"Others are just for fun", + TextSize = 18, + Shadow = true, + }, + }, + }, + }, + }, + new FlowContainer + { + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(0f, 10f), + Width = content_width, + Margin = new MarginPadding + { + Top = 100, + }, + Children = new Drawable[] + { + difficultyReductionSection = new DifficultyReductionSection + { + RelativeSizeAxes = Axes.X, + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Action = modButtonPressed, + }, + difficultyIncreaseSection = new DifficultyIncreaseSection + { + RelativeSizeAxes = Axes.X, + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Action = modButtonPressed, + }, + assistedSection = new AssistedSection + { + RelativeSizeAxes = Axes.X, + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Action = modButtonPressed, + }, + }, + }, + new Container + { + RelativeSizeAxes = Axes.X, + Height = 70, + Origin = Anchor.BottomCentre, + Anchor = Anchor.BottomCentre, + Margin = new MarginPadding + { + Bottom = 50, + }, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = new Color4(172, 20, 116, 255), + Alpha = 0.5f, + }, + rankedMultiplerContainer = new FlowContainer + { + Origin = Anchor.BottomCentre, + Anchor = Anchor.BottomCentre, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Width = content_width, + Direction = FlowDirections.Horizontal, + Padding = new MarginPadding + { + Top = 20, + Bottom = 20, + }, + Children = new Drawable[] + { + rankedLabel = new OsuSpriteText + { + Text = @"Ranked, Score Multiplier: ", + TextSize = 30, + Shadow = true, + }, + multiplierLabel = new OsuSpriteText + { + Font = @"Exo2.0-Bold", + Text = @"1.00x", + TextSize = 30, + Shadow = true, + }, + }, + }, + }, + }, + }, + }, + }; + } + } +} diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index e206c5d5fa..6ea6467238 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -29,6 +29,7 @@ using OpenTK.Input; using System.Collections.Generic; using osu.Framework.Graphics.Containers; using osu.Framework.Threading; +using osu.Game.Overlays.Mods; namespace osu.Game.Screens.Select { @@ -44,6 +45,8 @@ namespace osu.Game.Screens.Select private static readonly Vector2 wedged_container_size = new Vector2(0.5f, 225); private BeatmapInfoWedge beatmapInfoWedge; + private ModSelector modSelect; + private static readonly Vector2 background_blur = new Vector2(20); private CancellationTokenSource initialAddSetsTask; @@ -132,6 +135,12 @@ namespace osu.Game.Screens.Select Right = 20, }, }, + modSelect = new ModSelector + { + RelativeSizeAxes = Axes.Both, + Origin = Anchor.BottomCentre, + Anchor = Anchor.BottomCentre, + }, footer = new Footer() { OnBack = Exit, @@ -139,7 +148,7 @@ namespace osu.Game.Screens.Select } }; - footer.AddButton(@"mods", colours.Yellow, null); + footer.AddButton(@"mods", colours.Yellow, modSelect.ToggleVisibility); footer.AddButton(@"random", colours.Green, carousel.SelectRandom); footer.AddButton(@"options", colours.Blue, null); @@ -245,6 +254,12 @@ namespace osu.Game.Screens.Select protected override bool OnExiting(GameMode next) { + if (modSelect.State == Visibility.Visible) + { + modSelect.Hide(); + return true; + } + beatmapInfoWedge.MoveToX(-100, 800, EasingTypes.InQuint); beatmapInfoWedge.RotateTo(10, 800, EasingTypes.InQuint); diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index ed49590dbd..5d26d4b927 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -272,6 +272,14 @@ + + + + + + + + @@ -292,6 +300,9 @@ + + +