1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-16 04:32:57 +08:00

Merge remote-tracking branch 'ppy/master' into select_resolution_in_fullscreen_mode

This commit is contained in:
Roman Kapustin 2018-06-21 19:44:29 +03:00
commit 2ecf78e3d2
32 changed files with 1614 additions and 201 deletions

View File

@ -1,29 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [{
"name": "Deploy (Debug)",
"request": "launch",
"type": "mono",
"program": "${workspaceRoot}/bin/Debug/net471/osu.Desktop.Deploy.exe",
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Debug)",
"runtimeExecutable": null,
"env": {},
"console": "internalConsole"
},
{
"name": "Deploy (Release)",
"request": "launch",
"type": "clr",
"program": "${workspaceRoot}/bin/Release/net471/osu.Desktop.Deploy.exe",
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Release)",
"runtimeExecutable": null,
"env": {},
"console": "internalConsole"
}
]
}

View File

@ -1,64 +0,0 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"command": "msbuild",
"type": "shell",
"suppressTaskName": true,
"args": [
"/property:GenerateFullPaths=true",
"/property:DebugType=portable",
"/verbosity:minimal",
"/m" //parallel compiling support.
],
"tasks": [{
"taskName": "Build (Debug)",
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": [
"$msCompile"
]
},
{
"taskName": "Build (Release)",
"group": "build",
"args": [
"/property:Configuration=Release"
],
"problemMatcher": [
"$msCompile"
]
},
{
"taskName": "Clean (Debug)",
"args": [
"/target:Clean"
],
"problemMatcher": [
"$msCompile"
]
},
{
"taskName": "Clean (Release)",
"args": [
"/target:Clean",
"/property:Configuration=Release"
],
"problemMatcher": [
"$msCompile"
]
},
{
"taskName": "Clean All",
"dependsOn": [
"Clean (Debug)",
"Clean (Release)"
],
"problemMatcher": [
"$msCompile"
]
}
]
}

View File

@ -30,6 +30,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.1.1" />
<PackageReference Include="squirrel.windows" Version="1.8.0" Condition="'$(TargetFramework)' == 'net471'" /> <PackageReference Include="squirrel.windows" Version="1.8.0" Condition="'$(TargetFramework)' == 'net471'" />
</ItemGroup> </ItemGroup>
<ItemGroup Label="Resources"> <ItemGroup Label="Resources">

View File

@ -0,0 +1,19 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Catch.Difficulty
{
public class CatchDifficultyAttributes : DifficultyAttributes
{
public double ApproachRate;
public int MaxCombo;
public CatchDifficultyAttributes(Mod[] mods, double starRating)
: base(mods, starRating)
{
}
}
}

View File

@ -1,19 +1,146 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI;
namespace osu.Game.Rulesets.Catch.Difficulty namespace osu.Game.Rulesets.Catch.Difficulty
{ {
public class CatchDifficultyCalculator : DifficultyCalculator public class CatchDifficultyCalculator : DifficultyCalculator
{ {
/// <summary>
/// In milliseconds. For difficulty calculation we will only look at the highest strain value in each time interval of size STRAIN_STEP.
/// This is to eliminate higher influence of stream over aim by simply having more HitObjects with high strain.
/// The higher this value, the less strains there will be, indirectly giving long beatmaps an advantage.
/// </summary>
private const double strain_step = 750;
/// <summary>
/// The weighting of each strain value decays to this number * it's previous value
/// </summary>
private const double decay_weight = 0.94;
private const double star_scaling_factor = 0.145;
public CatchDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) public CatchDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
: base(ruleset, beatmap) : base(ruleset, beatmap)
{ {
} }
protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate) => new DifficultyAttributes(mods, 0); protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate)
{
if (!beatmap.HitObjects.Any())
return new CatchDifficultyAttributes(mods, 0);
var catcher = new CatcherArea.Catcher(beatmap.BeatmapInfo.BaseDifficulty);
float halfCatchWidth = catcher.CatchWidth * 0.5f;
var difficultyHitObjects = new List<CatchDifficultyHitObject>();
foreach (var hitObject in beatmap.HitObjects)
{
// We want to only consider fruits that contribute to the combo. Droplets are addressed as accuracy and spinners are not relevant for "skill" calculations.
if (hitObject is Fruit)
{
difficultyHitObjects.Add(new CatchDifficultyHitObject((CatchHitObject)hitObject, halfCatchWidth));
}
if (hitObject is JuiceStream)
difficultyHitObjects.AddRange(hitObject.NestedHitObjects.OfType<CatchHitObject>().Where(o => !(o is TinyDroplet)).Select(o => new CatchDifficultyHitObject(o, halfCatchWidth)));
}
difficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime.CompareTo(b.BaseHitObject.StartTime));
if (!calculateStrainValues(difficultyHitObjects, timeRate))
return new CatchDifficultyAttributes(mods, 0);
// this is the same as osu!, so there's potential to share the implementation... maybe
double preEmpt = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / timeRate;
double starRating = Math.Sqrt(calculateDifficulty(difficultyHitObjects, timeRate)) * star_scaling_factor;
return new CatchDifficultyAttributes(mods, starRating)
{
ApproachRate = preEmpt > 1200.0 ? -(preEmpt - 1800.0) / 120.0 : -(preEmpt - 1200.0) / 150.0 + 5.0,
MaxCombo = difficultyHitObjects.Count
};
}
private bool calculateStrainValues(List<CatchDifficultyHitObject> objects, double timeRate)
{
CatchDifficultyHitObject lastObject = null;
if (!objects.Any()) return false;
// Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment.
foreach (var currentObject in objects)
{
if (lastObject != null)
currentObject.CalculateStrains(lastObject, timeRate);
lastObject = currentObject;
}
return true;
}
private double calculateDifficulty(List<CatchDifficultyHitObject> objects, double timeRate)
{
// The strain step needs to be adjusted for the algorithm to be considered equal with speed changing mods
double actualStrainStep = strain_step * timeRate;
// Find the highest strain value within each strain step
var highestStrains = new List<double>();
double intervalEndTime = actualStrainStep;
double maximumStrain = 0; // We need to keep track of the maximum strain in the current interval
CatchDifficultyHitObject previousHitObject = null;
foreach (CatchDifficultyHitObject hitObject in objects)
{
// While we are beyond the current interval push the currently available maximum to our strain list
while (hitObject.BaseHitObject.StartTime > intervalEndTime)
{
highestStrains.Add(maximumStrain);
// The maximum strain of the next interval is not zero by default! We need to take the last hitObject we encountered, take its strain and apply the decay
// until the beginning of the next interval.
if (previousHitObject == null)
{
maximumStrain = 0;
}
else
{
double decay = Math.Pow(CatchDifficultyHitObject.DECAY_BASE, (intervalEndTime - previousHitObject.BaseHitObject.StartTime) / 1000);
maximumStrain = previousHitObject.Strain * decay;
}
// Go to the next time interval
intervalEndTime += actualStrainStep;
}
// Obtain maximum strain
maximumStrain = Math.Max(hitObject.Strain, maximumStrain);
previousHitObject = hitObject;
}
// Build the weighted sum over the highest strains for each interval
double difficulty = 0;
double weight = 1;
highestStrains.Sort((a, b) => b.CompareTo(a)); // Sort from highest to lowest strain.
foreach (double strain in highestStrains)
{
difficulty += weight * strain;
weight *= decay_weight;
}
return difficulty;
}
} }
} }

View File

@ -0,0 +1,130 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI;
using OpenTK;
namespace osu.Game.Rulesets.Catch.Difficulty
{
public class CatchDifficultyHitObject
{
internal static readonly double DECAY_BASE = 0.20;
private const float normalized_hitobject_radius = 41.0f;
private const float absolute_player_positioning_error = 16f;
private readonly float playerPositioningError;
internal CatchHitObject BaseHitObject;
/// <summary>
/// Measures jump difficulty. CtB doesn't have something like button pressing speed or accuracy
/// </summary>
internal double Strain = 1;
/// <summary>
/// This is required to keep track of lazy player movement (always moving only as far as necessary)
/// Without this quick repeat sliders / weirdly shaped streams might become ridiculously overrated
/// </summary>
internal float PlayerPositionOffset;
internal float LastMovement;
internal float NormalizedPosition;
internal float ActualNormalizedPosition => NormalizedPosition + PlayerPositionOffset;
internal CatchDifficultyHitObject(CatchHitObject baseHitObject, float catcherWidthHalf)
{
BaseHitObject = baseHitObject;
// We will scale everything by this factor, so we can assume a uniform CircleSize among beatmaps.
float scalingFactor = normalized_hitobject_radius / catcherWidthHalf;
playerPositioningError = absolute_player_positioning_error; // * scalingFactor;
NormalizedPosition = baseHitObject.X * CatchPlayfield.BASE_WIDTH * scalingFactor;
}
private const double direction_change_bonus = 12.5;
internal void CalculateStrains(CatchDifficultyHitObject previousHitObject, double timeRate)
{
// Rather simple, but more specialized things are inherently inaccurate due to the big difference playstyles and opinions make.
// See Taiko feedback thread.
double timeElapsed = (BaseHitObject.StartTime - previousHitObject.BaseHitObject.StartTime) / timeRate;
double decay = Math.Pow(DECAY_BASE, timeElapsed / 1000);
// Update new position with lazy movement.
PlayerPositionOffset =
MathHelper.Clamp(
previousHitObject.ActualNormalizedPosition,
NormalizedPosition - (normalized_hitobject_radius - playerPositioningError),
NormalizedPosition + (normalized_hitobject_radius - playerPositioningError)) // Obtain new lazy position, but be stricter by allowing for an error of a certain degree of the player.
- NormalizedPosition; // Subtract HitObject position to obtain offset
LastMovement = DistanceTo(previousHitObject);
double addition = spacingWeight(LastMovement);
if (NormalizedPosition < previousHitObject.NormalizedPosition)
{
LastMovement = -LastMovement;
}
CatchHitObject previousHitCircle = previousHitObject.BaseHitObject;
double additionBonus = 0;
double sqrtTime = Math.Sqrt(Math.Max(timeElapsed, 25));
// Direction changes give an extra point!
if (Math.Abs(LastMovement) > 0.1)
{
if (Math.Abs(previousHitObject.LastMovement) > 0.1 && Math.Sign(LastMovement) != Math.Sign(previousHitObject.LastMovement))
{
double bonus = direction_change_bonus / sqrtTime;
// Weight bonus by how
double bonusFactor = Math.Min(playerPositioningError, Math.Abs(LastMovement)) / playerPositioningError;
// We want time to play a role twice here!
addition += bonus * bonusFactor;
// Bonus for tougher direction switches and "almost" hyperdashes at this point
if (previousHitCircle != null && previousHitCircle.DistanceToHyperDash <= 10.0f / CatchPlayfield.BASE_WIDTH)
{
additionBonus += 0.3 * bonusFactor;
}
}
// Base bonus for every movement, giving some weight to streams.
addition += 7.5 * Math.Min(Math.Abs(LastMovement), normalized_hitobject_radius * 2) / (normalized_hitobject_radius * 6) / sqrtTime;
}
// Bonus for "almost" hyperdashes at corner points
if (previousHitCircle != null && previousHitCircle.DistanceToHyperDash <= 10.0f / CatchPlayfield.BASE_WIDTH)
{
if (!previousHitCircle.HyperDash)
{
additionBonus += 1.0;
}
else
{
// After a hyperdash we ARE in the correct position. Always!
PlayerPositionOffset = 0;
}
addition *= 1.0 + additionBonus * ((10 - previousHitCircle.DistanceToHyperDash * CatchPlayfield.BASE_WIDTH) / 10);
}
addition *= 850.0 / Math.Max(timeElapsed, 25);
Strain = previousHitObject.Strain * decay + addition;
}
private static double spacingWeight(float distance)
{
return Math.Pow(distance, 1.3) / 500;
}
internal float DistanceTo(CatchDifficultyHitObject other)
{
return Math.Abs(ActualNormalizedPosition - other.ActualNormalizedPosition);
}
}
}

View File

@ -24,6 +24,11 @@ namespace osu.Game.Rulesets.Catch.Objects
public int ComboIndex { get; set; } public int ComboIndex { get; set; }
/// <summary>
/// The distance for a fruit to to next hyper if it's not a hyper.
/// </summary>
public float DistanceToHyperDash { get; set; }
/// <summary> /// <summary>
/// The next fruit starts a new combo. Used for explodey. /// The next fruit starts a new combo. Used for explodey.
/// </summary> /// </summary>

View File

@ -105,6 +105,11 @@ namespace osu.Game.Rulesets.Catch.UI
public class Catcher : Container, IKeyBindingHandler<CatchAction> public class Catcher : Container, IKeyBindingHandler<CatchAction>
{ {
/// <summary>
/// Width of the area that can be used to attempt catches during gameplay.
/// </summary>
internal float CatchWidth => CATCHER_SIZE * Math.Abs(Scale.X);
private Container<DrawableHitObject> caughtFruit; private Container<DrawableHitObject> caughtFruit;
public Container ExplodingFruitTarget; public Container ExplodingFruitTarget;
@ -232,15 +237,15 @@ namespace osu.Game.Rulesets.Catch.UI
/// <returns>Whether the catch is possible.</returns> /// <returns>Whether the catch is possible.</returns>
public bool AttemptCatch(CatchHitObject fruit) public bool AttemptCatch(CatchHitObject fruit)
{ {
double halfCatcherWidth = CATCHER_SIZE * Math.Abs(Scale.X) * 0.5f; float halfCatchWidth = CatchWidth * 0.5f;
// this stuff wil disappear once we move fruit to non-relative coordinate space in the future. // this stuff wil disappear once we move fruit to non-relative coordinate space in the future.
var catchObjectPosition = fruit.X * CatchPlayfield.BASE_WIDTH; var catchObjectPosition = fruit.X * CatchPlayfield.BASE_WIDTH;
var catcherPosition = Position.X * CatchPlayfield.BASE_WIDTH; var catcherPosition = Position.X * CatchPlayfield.BASE_WIDTH;
var validCatch = var validCatch =
catchObjectPosition >= catcherPosition - halfCatcherWidth && catchObjectPosition >= catcherPosition - halfCatchWidth &&
catchObjectPosition <= catcherPosition + halfCatcherWidth; catchObjectPosition <= catcherPosition + halfCatchWidth;
if (validCatch && fruit.HyperDash) if (validCatch && fruit.HyperDash)
{ {

View File

@ -27,14 +27,12 @@ namespace osu.Game.Rulesets.Taiko
public override IEnumerable<KeyBinding> GetDefaultKeyBindings(int variant = 0) => new[] public override IEnumerable<KeyBinding> GetDefaultKeyBindings(int variant = 0) => new[]
{ {
new KeyBinding(InputKey.MouseLeft, TaikoAction.LeftCentre),
new KeyBinding(InputKey.MouseRight, TaikoAction.LeftRim),
new KeyBinding(InputKey.D, TaikoAction.LeftRim), new KeyBinding(InputKey.D, TaikoAction.LeftRim),
new KeyBinding(InputKey.F, TaikoAction.LeftCentre), new KeyBinding(InputKey.F, TaikoAction.LeftCentre),
new KeyBinding(InputKey.J, TaikoAction.RightCentre), new KeyBinding(InputKey.J, TaikoAction.RightCentre),
new KeyBinding(InputKey.K, TaikoAction.RightRim), new KeyBinding(InputKey.K, TaikoAction.RightRim),
new KeyBinding(InputKey.MouseLeft, TaikoAction.LeftCentre),
new KeyBinding(InputKey.MouseLeft, TaikoAction.RightCentre),
new KeyBinding(InputKey.MouseRight, TaikoAction.LeftRim),
new KeyBinding(InputKey.MouseRight, TaikoAction.RightRim),
}; };
public override IEnumerable<Mod> ConvertLegacyMods(LegacyMods mods) public override IEnumerable<Mod> ConvertLegacyMods(LegacyMods mods)

View File

@ -8,6 +8,8 @@ using osu.Framework.Allocation;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Screens;
using osu.Game.Screens.Backgrounds;
using osu.Game.Screens.Multi.Components; using osu.Game.Screens.Multi.Components;
using osu.Game.Screens.Multi.Screens.Lounge; using osu.Game.Screens.Multi.Screens.Lounge;
using osu.Game.Users; using osu.Game.Users;
@ -198,6 +200,8 @@ namespace osu.Game.Tests.Visual
private class TestLounge : Lounge private class TestLounge : Lounge
{ {
protected override BackgroundScreen CreateBackground() => new BackgroundScreenDefault();
public IEnumerable<DrawableRoom> ChildRooms => RoomsContainer.Children.Where(r => r.MatchingFilter); public IEnumerable<DrawableRoom> ChildRooms => RoomsContainer.Children.Where(r => r.MatchingFilter);
public Room SelectedRoom => Inspector.Room; public Room SelectedRoom => Inspector.Room;

View File

@ -0,0 +1,142 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Game.Beatmaps;
using osu.Game.Online.Multiplayer;
using osu.Game.Rulesets;
using osu.Game.Screens.Multi.Screens.Match;
using osu.Game.Users;
namespace osu.Game.Tests.Visual
{
[TestFixture]
public class TestCaseMatch : OsuTestCase
{
[BackgroundDependencyLoader]
private void load(RulesetStore rulesets)
{
Room room = new Room
{
Name = { Value = @"One Awesome Room" },
Status = { Value = new RoomStatusOpen() },
Availability = { Value = RoomAvailability.Public },
Type = { Value = new GameTypeTeamVersus() },
Beatmap =
{
Value = new BeatmapInfo
{
StarDifficulty = 5.02,
Ruleset = rulesets.GetRuleset(1),
Metadata = new BeatmapMetadata
{
Title = @"Paradigm Shift",
Artist = @"Morimori Atsushi",
AuthorString = @"eiri-",
},
BeatmapSet = new BeatmapSetInfo
{
OnlineInfo = new BeatmapSetOnlineInfo
{
Covers = new BeatmapSetOnlineCovers
{
Cover = @"https://assets.ppy.sh/beatmaps/765055/covers/cover.jpg?1526955337",
},
},
},
},
},
MaxParticipants = { Value = 5 },
Participants =
{
Value = new[]
{
new User
{
Username = @"eiri-",
Id = 3388410,
Country = new Country { FlagName = @"US" },
CoverUrl = @"https://assets.ppy.sh/user-profile-covers/3388410/00a8486a247831e1cc4375db519f611ac970bda8bc0057d78b0f540ea38c3e58.jpeg",
IsSupporter = true,
},
new User
{
Username = @"Nepuri",
Id = 6637817,
Country = new Country { FlagName = @"DE" },
CoverUrl = @"https://assets.ppy.sh/user-profile-covers/6637817/9085fc60248b6b5327a72c1dcdecf2dbedba810ae0ab6bcf7224e46b1339632a.jpeg",
IsSupporter = true,
},
new User
{
Username = @"goheegy",
Id = 8057655,
Country = new Country { FlagName = @"GB" },
CoverUrl = @"https://assets.ppy.sh/user-profile-covers/8057655/21cec27c25a11dc197a4ec6a74253dbabb495949b0e0697113352f12007018c5.jpeg",
},
new User
{
Username = @"Alumetri",
Id = 5371497,
Country = new Country { FlagName = @"RU" },
CoverUrl = @"https://assets.ppy.sh/user-profile-covers/5371497/e023b8c7fbe3613e64bd4856703517ea50fbed8a5805dc9acda9efe9897c67e2.jpeg",
},
}
},
};
Match match = new Match(room);
AddStep(@"show", () => Add(match));
AddStep(@"null beatmap", () => room.Beatmap.Value = null);
AddStep(@"change name", () => room.Name.Value = @"Two Awesome Rooms");
AddStep(@"change status", () => room.Status.Value = new RoomStatusPlaying());
AddStep(@"change availability", () => room.Availability.Value = RoomAvailability.FriendsOnly);
AddStep(@"change type", () => room.Type.Value = new GameTypeTag());
AddStep(@"change beatmap", () => room.Beatmap.Value = new BeatmapInfo
{
StarDifficulty = 4.33,
Ruleset = rulesets.GetRuleset(2),
Metadata = new BeatmapMetadata
{
Title = @"Yasashisa no Riyuu",
Artist = @"ChouCho",
AuthorString = @"celerih",
},
BeatmapSet = new BeatmapSetInfo
{
OnlineInfo = new BeatmapSetOnlineInfo
{
Covers = new BeatmapSetOnlineCovers
{
Cover = @"https://assets.ppy.sh/beatmaps/685391/covers/cover.jpg?1524597970",
},
},
},
});
AddStep(@"null max participants", () => room.MaxParticipants.Value = null);
AddStep(@"change participants", () => room.Participants.Value = new[]
{
new User
{
Username = @"Spectator",
Id = 702598,
Country = new Country { FlagName = @"KR" },
CoverUrl = @"https://assets.ppy.sh/user-profile-covers/702598/3bbf4cb8b8d2cf8b03145000a975ff27e191ab99b0920832e7dd67386280e288.jpeg",
IsSupporter = true,
},
new User
{
Username = @"celerih",
Id = 4696296,
Country = new Country { FlagName = @"CA" },
CoverUrl = @"https://assets.ppy.sh/user-profile-covers/4696296/7f8500731d0ac66d5472569d146a7be07d9460273361913f22c038867baddaef.jpeg",
},
});
AddStep(@"exit", match.Exit);
}
}
}

View File

@ -0,0 +1,43 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Screens.Multi.Screens.Match;
namespace osu.Game.Tests.Visual
{
[TestFixture]
public class TestCaseMatchHeader : OsuTestCase
{
public TestCaseMatchHeader()
{
Header header = new Header();
Add(header);
AddStep(@"set beatmap set", () => header.BeatmapSet = new BeatmapSetInfo
{
OnlineInfo = new BeatmapSetOnlineInfo
{
Covers = new BeatmapSetOnlineCovers
{
Cover = @"https://assets.ppy.sh/beatmaps/760757/covers/cover.jpg?1526944540",
},
},
});
AddStep(@"change beatmap set", () => header.BeatmapSet = new BeatmapSetInfo
{
OnlineInfo = new BeatmapSetOnlineInfo
{
Covers = new BeatmapSetOnlineCovers
{
Cover = @"https://assets.ppy.sh/beatmaps/761883/covers/cover.jpg?1525557400",
},
},
});
AddStep(@"null beatmap set", () => header.BeatmapSet = null);
}
}
}

View File

@ -0,0 +1,57 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Game.Beatmaps;
using osu.Game.Online.Multiplayer;
using osu.Game.Rulesets;
using osu.Game.Screens.Multi.Screens.Match;
namespace osu.Game.Tests.Visual
{
[TestFixture]
public class TestCaseMatchInfo : OsuTestCase
{
[BackgroundDependencyLoader]
private void load(RulesetStore rulesets)
{
Info info = new Info();
Add(info);
AddStep(@"set name", () => info.Name = @"Room Name?");
AddStep(@"set availability", () => info.Availability = RoomAvailability.FriendsOnly);
AddStep(@"set status", () => info.Status = new RoomStatusPlaying());
AddStep(@"set beatmap", () => info.Beatmap = new BeatmapInfo
{
StarDifficulty = 2.4,
Ruleset = rulesets.GetRuleset(0),
Metadata = new BeatmapMetadata
{
Title = @"My Song",
Artist = @"VisualTests",
AuthorString = @"osu!lazer",
},
});
AddStep(@"set type", () => info.Type = new GameTypeTagTeam());
AddStep(@"change name", () => info.Name = @"Room Name!");
AddStep(@"change availability", () => info.Availability = RoomAvailability.InviteOnly);
AddStep(@"change status", () => info.Status = new RoomStatusOpen());
AddStep(@"null beatmap", () => info.Beatmap = null);
AddStep(@"change type", () => info.Type = new GameTypeTeamVersus());
AddStep(@"change beatmap", () => info.Beatmap = new BeatmapInfo
{
StarDifficulty = 4.2,
Ruleset = rulesets.GetRuleset(3),
Metadata = new BeatmapMetadata
{
Title = @"Your Song",
Artist = @"Tester",
AuthorString = @"Someone",
},
});
}
}
}

View File

@ -0,0 +1,56 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Game.Screens.Multi.Screens.Match;
using osu.Game.Users;
namespace osu.Game.Tests.Visual
{
[TestFixture]
public class TestCaseMatchParticipants : OsuTestCase
{
public TestCaseMatchParticipants()
{
Participants participants;
Add(participants = new Participants
{
RelativeSizeAxes = Axes.Both,
});
AddStep(@"set max to null", () => participants.Max = null);
AddStep(@"set users", () => participants.Users = new[]
{
new User
{
Username = @"Feppla",
Id = 4271601,
Country = new Country { FlagName = @"SE" },
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg",
IsSupporter = true,
},
new User
{
Username = @"Xilver",
Id = 3099689,
Country = new Country { FlagName = @"IL" },
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg",
IsSupporter = true,
},
new User
{
Username = @"Wucki",
Id = 5287410,
Country = new Country { FlagName = @"FI" },
CoverUrl = @"https://assets.ppy.sh/user-profile-covers/5287410/5cfeaa9dd41cbce038ecdc9d781396ed4b0108089170bf7f50492ef8eadeb368.jpeg",
IsSupporter = true,
},
});
AddStep(@"set max", () => participants.Max = 10);
AddStep(@"clear users", () => participants.Users = new User[] { });
AddStep(@"set max to null", () => participants.Max = null);
}
}
}

View File

@ -0,0 +1,26 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Graphics.Containers;
using osu.Game.Screens.Backgrounds;
namespace osu.Game.Tests.Visual
{
public class TestCaseParallaxContainer : OsuTestCase
{
public TestCaseParallaxContainer()
{
ParallaxContainer parallax;
Add(parallax = new ParallaxContainer
{
Child = new BackgroundScreenDefault { Alpha = 0.8f }
});
AddStep("default parallax", () => parallax.ParallaxAmount = ParallaxContainer.DEFAULT_PARALLAX_AMOUNT);
AddStep("high parallax", () => parallax.ParallaxAmount = ParallaxContainer.DEFAULT_PARALLAX_AMOUNT * 10);
AddStep("no parallax", () => parallax.ParallaxAmount = 0);
AddStep("negative parallax", () => parallax.ParallaxAmount = -ParallaxContainer.DEFAULT_PARALLAX_AMOUNT);
}
}
}

View File

@ -16,6 +16,9 @@ namespace osu.Game.Graphics.Containers
{ {
public const float DEFAULT_PARALLAX_AMOUNT = 0.02f; public const float DEFAULT_PARALLAX_AMOUNT = 0.02f;
/// <summary>
/// The amount of parallax movement. Negative values will reverse the direction of parallax relative to user input.
/// </summary>
public float ParallaxAmount = DEFAULT_PARALLAX_AMOUNT; public float ParallaxAmount = DEFAULT_PARALLAX_AMOUNT;
private Bindable<bool> parallaxEnabled; private Bindable<bool> parallaxEnabled;
@ -45,7 +48,7 @@ namespace osu.Game.Graphics.Containers
if (!parallaxEnabled) if (!parallaxEnabled)
{ {
content.MoveTo(Vector2.Zero, firstUpdate ? 0 : 1000, Easing.OutQuint); content.MoveTo(Vector2.Zero, firstUpdate ? 0 : 1000, Easing.OutQuint);
content.Scale = new Vector2(1 + ParallaxAmount); content.Scale = new Vector2(1 + System.Math.Abs(ParallaxAmount));
} }
}; };
} }
@ -69,7 +72,7 @@ namespace osu.Game.Graphics.Containers
double elapsed = MathHelper.Clamp(Clock.ElapsedFrameTime, 0, 1000); double elapsed = MathHelper.Clamp(Clock.ElapsedFrameTime, 0, 1000);
content.Position = Interpolation.ValueAt(elapsed, content.Position, offset, 0, 1000, Easing.OutQuint); content.Position = Interpolation.ValueAt(elapsed, content.Position, offset, 0, 1000, Easing.OutQuint);
content.Scale = Interpolation.ValueAt(elapsed, content.Scale, new Vector2(1 + ParallaxAmount), 0, 1000, Easing.OutQuint); content.Scale = Interpolation.ValueAt(elapsed, content.Scale, new Vector2(1 + System.Math.Abs(ParallaxAmount)), 0, 1000, Easing.OutQuint);
} }
firstUpdate = false; firstUpdate = false;

View File

@ -0,0 +1,376 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using osu.Game.Database;
namespace osu.Game.Migrations
{
[DbContext(typeof(OsuDbContext))]
[Migration("20180621044111_UpdateTaikoDefaultBindings")]
partial class UpdateTaikoDefaultBindings
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.1.1-rtm-30846");
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<float>("ApproachRate");
b.Property<float>("CircleSize");
b.Property<float>("DrainRate");
b.Property<float>("OverallDifficulty");
b.Property<double>("SliderMultiplier");
b.Property<double>("SliderTickRate");
b.HasKey("ID");
b.ToTable("BeatmapDifficulty");
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<int>("AudioLeadIn");
b.Property<int>("BaseDifficultyID");
b.Property<int>("BeatDivisor");
b.Property<int>("BeatmapSetInfoID");
b.Property<bool>("Countdown");
b.Property<double>("DistanceSpacing");
b.Property<int>("GridSize");
b.Property<string>("Hash");
b.Property<bool>("Hidden");
b.Property<bool>("LetterboxInBreaks");
b.Property<string>("MD5Hash");
b.Property<int?>("MetadataID");
b.Property<int?>("OnlineBeatmapID");
b.Property<string>("Path");
b.Property<int>("RulesetID");
b.Property<bool>("SpecialStyle");
b.Property<float>("StackLeniency");
b.Property<double>("StarDifficulty");
b.Property<string>("StoredBookmarks");
b.Property<double>("TimelineZoom");
b.Property<string>("Version");
b.Property<bool>("WidescreenStoryboard");
b.HasKey("ID");
b.HasIndex("BaseDifficultyID");
b.HasIndex("BeatmapSetInfoID");
b.HasIndex("Hash");
b.HasIndex("MD5Hash");
b.HasIndex("MetadataID");
b.HasIndex("OnlineBeatmapID")
.IsUnique();
b.HasIndex("RulesetID");
b.ToTable("BeatmapInfo");
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<string>("Artist");
b.Property<string>("ArtistUnicode");
b.Property<string>("AudioFile");
b.Property<string>("AuthorString")
.HasColumnName("Author");
b.Property<string>("BackgroundFile");
b.Property<int>("PreviewTime");
b.Property<string>("Source");
b.Property<string>("Tags");
b.Property<string>("Title");
b.Property<string>("TitleUnicode");
b.HasKey("ID");
b.ToTable("BeatmapMetadata");
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<int>("BeatmapSetInfoID");
b.Property<int>("FileInfoID");
b.Property<string>("Filename")
.IsRequired();
b.HasKey("ID");
b.HasIndex("BeatmapSetInfoID");
b.HasIndex("FileInfoID");
b.ToTable("BeatmapSetFileInfo");
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<bool>("DeletePending");
b.Property<string>("Hash");
b.Property<int?>("MetadataID");
b.Property<int?>("OnlineBeatmapSetID");
b.Property<bool>("Protected");
b.HasKey("ID");
b.HasIndex("DeletePending");
b.HasIndex("Hash")
.IsUnique();
b.HasIndex("MetadataID");
b.HasIndex("OnlineBeatmapSetID")
.IsUnique();
b.ToTable("BeatmapSetInfo");
});
modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<int>("IntKey")
.HasColumnName("Key");
b.Property<int?>("RulesetID");
b.Property<string>("StringValue")
.HasColumnName("Value");
b.Property<int?>("Variant");
b.HasKey("ID");
b.HasIndex("RulesetID", "Variant");
b.ToTable("Settings");
});
modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<int>("IntAction")
.HasColumnName("Action");
b.Property<string>("KeysString")
.HasColumnName("Keys");
b.Property<int?>("RulesetID");
b.Property<int?>("Variant");
b.HasKey("ID");
b.HasIndex("IntAction");
b.HasIndex("RulesetID", "Variant");
b.ToTable("KeyBinding");
});
modelBuilder.Entity("osu.Game.IO.FileInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<string>("Hash");
b.Property<int>("ReferenceCount");
b.HasKey("ID");
b.HasIndex("Hash")
.IsUnique();
b.HasIndex("ReferenceCount");
b.ToTable("FileInfo");
});
modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b =>
{
b.Property<int?>("ID")
.ValueGeneratedOnAdd();
b.Property<bool>("Available");
b.Property<string>("InstantiationInfo");
b.Property<string>("Name");
b.Property<string>("ShortName");
b.HasKey("ID");
b.HasIndex("Available");
b.HasIndex("ShortName")
.IsUnique();
b.ToTable("RulesetInfo");
});
modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<int>("FileInfoID");
b.Property<string>("Filename")
.IsRequired();
b.Property<int>("SkinInfoID");
b.HasKey("ID");
b.HasIndex("FileInfoID");
b.HasIndex("SkinInfoID");
b.ToTable("SkinFileInfo");
});
modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<string>("Creator");
b.Property<bool>("DeletePending");
b.Property<string>("Name");
b.HasKey("ID");
b.ToTable("SkinInfo");
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
{
b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty")
.WithMany()
.HasForeignKey("BaseDifficultyID")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet")
.WithMany("Beatmaps")
.HasForeignKey("BeatmapSetInfoID")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata")
.WithMany("Beatmaps")
.HasForeignKey("MetadataID");
b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset")
.WithMany()
.HasForeignKey("RulesetID")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b =>
{
b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo")
.WithMany("Files")
.HasForeignKey("BeatmapSetInfoID")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
.WithMany()
.HasForeignKey("FileInfoID")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b =>
{
b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata")
.WithMany("BeatmapSets")
.HasForeignKey("MetadataID");
});
modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b =>
{
b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
.WithMany()
.HasForeignKey("FileInfoID")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("osu.Game.Skinning.SkinInfo")
.WithMany("Files")
.HasForeignKey("SkinInfoID")
.OnDelete(DeleteBehavior.Cascade);
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,19 @@
using Microsoft.EntityFrameworkCore.Migrations;
using osu.Framework.Logging;
namespace osu.Game.Migrations
{
public partial class UpdateTaikoDefaultBindings : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql("DELETE FROM KeyBinding WHERE RulesetID = 1");
Logger.Log("osu!taiko bindings have been reset due to new defaults", LoggingTarget.Runtime, LogLevel.Important);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
// we can't really tell if these should be restored or not, so let's just not do so.
}
}
}

View File

@ -1,11 +1,9 @@
// <auto-generated /> // <auto-generated />
using System;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage;
using osu.Game.Database; using osu.Game.Database;
using System;
namespace osu.Game.Migrations namespace osu.Game.Migrations
{ {
@ -16,7 +14,7 @@ namespace osu.Game.Migrations
{ {
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder modelBuilder
.HasAnnotation("ProductVersion", "2.0.3-rtm-10026"); .HasAnnotation("ProductVersion", "2.1.1-rtm-30846");
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b =>
{ {

View File

@ -101,6 +101,8 @@ namespace osu.Game
public OsuGame(string[] args = null) public OsuGame(string[] args = null)
{ {
this.args = args; this.args = args;
forwardLoggedErrorsToNotifications();
} }
public void ToggleSettings() => settings.ToggleVisibility(); public void ToggleSettings() => settings.ToggleVisibility();
@ -305,8 +307,6 @@ namespace osu.Game
Depth = -6, Depth = -6,
}, overlayContent.Add); }, overlayContent.Add);
forwardLoggedErrorsToNotifications();
dependencies.Cache(settings); dependencies.Cache(settings);
dependencies.Cache(onscreenDisplay); dependencies.Cache(onscreenDisplay);
dependencies.Cache(social); dependencies.Cache(social);
@ -394,31 +394,40 @@ namespace osu.Game
private void forwardLoggedErrorsToNotifications() private void forwardLoggedErrorsToNotifications()
{ {
int recentErrorCount = 0; int recentLogCount = 0;
const double debounce = 5000; const double debounce = 5000;
Logger.NewEntry += entry => Logger.NewEntry += entry =>
{ {
if (entry.Level < LogLevel.Error || entry.Target == null) return; if (entry.Level < LogLevel.Important || entry.Target == null) return;
if (recentErrorCount < 2) const int short_term_display_limit = 3;
if (recentLogCount < short_term_display_limit)
{ {
notifications.Post(new SimpleNotification Schedule(() => notifications.Post(new SimpleNotification
{ {
Icon = FontAwesome.fa_bomb, Icon = entry.Level == LogLevel.Important ? FontAwesome.fa_exclamation_circle : FontAwesome.fa_bomb,
Text = (recentErrorCount == 0 ? entry.Message : "Subsequent errors occurred and have been logged.") + "\nClick to view log files.", Text = entry.Message,
}));
}
else if (recentLogCount == short_term_display_limit)
{
Schedule(() => notifications.Post(new SimpleNotification
{
Icon = FontAwesome.fa_ellipsis_h,
Text = "Subsequent messages have been logged. Click to view log files.",
Activated = () => Activated = () =>
{ {
Host.Storage.GetStorageForDirectory("logs").OpenInNativeExplorer(); Host.Storage.GetStorageForDirectory("logs").OpenInNativeExplorer();
return true; return true;
} }
}); }));
} }
Interlocked.Increment(ref recentErrorCount); Interlocked.Increment(ref recentLogCount);
Scheduler.AddDelayed(() => Interlocked.Decrement(ref recentLogCount), debounce);
Scheduler.AddDelayed(() => Interlocked.Decrement(ref recentErrorCount), debounce);
}; };
} }

View File

@ -59,7 +59,7 @@ namespace osu.Game.Overlays.Notifications
} }
}); });
Content.Add(textDrawable = new OsuTextFlowContainer(t => t.TextSize = 16) Content.Add(textDrawable = new OsuTextFlowContainer(t => t.TextSize = 14)
{ {
Colour = OsuColour.Gray(128), Colour = OsuColour.Gray(128),
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,

View File

@ -185,9 +185,9 @@ namespace osu.Game.Screens.Edit
protected override bool OnScroll(InputState state) protected override bool OnScroll(InputState state)
{ {
if (state.Mouse.ScrollDelta.X + state.Mouse.ScrollDelta.Y > 0) if (state.Mouse.ScrollDelta.X + state.Mouse.ScrollDelta.Y > 0)
clock.SeekBackward(true); clock.SeekBackward(!clock.IsRunning);
else else
clock.SeekForward(true); clock.SeekForward(!clock.IsRunning);
return true; return true;
} }

View File

@ -164,7 +164,7 @@ namespace osu.Game.Screens.Multi.Screens.Lounge
// open the room if its selected and is clicked again // open the room if its selected and is clicked again
if (room.State == SelectionState.Selected) if (room.State == SelectionState.Selected)
Push(new Match()); Push(new Match.Match(room.Room));
} }
private class RoomsFilterContainer : FillFlowContainer<DrawableRoom>, IHasFilterableChildren private class RoomsFilterContainer : FillFlowContainer<DrawableRoom>, IHasFilterableChildren

View File

@ -1,37 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Framework.Screens;
using osu.Game.Screens.Backgrounds;
using osu.Game.Screens.Play;
using osu.Game.Screens.Select;
using OpenTK.Graphics;
namespace osu.Game.Screens.Multi.Screens
{
public class Match : ScreenWhiteBox
{
protected override IEnumerable<Type> PossibleChildren => new[] {
typeof(MatchSongSelect),
typeof(Player),
};
protected override BackgroundScreen CreateBackground() => new BackgroundScreenCustom(@"Backgrounds/bg4");
protected override void OnEntering(Screen last)
{
base.OnEntering(last);
Background.FadeColour(Color4.DarkGray, 500);
}
protected override bool OnExiting(Screen next)
{
Background.FadeColour(Color4.White, 500);
return base.OnExiting(next);
}
}
}

View File

@ -0,0 +1,184 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.SearchableList;
using OpenTK.Graphics;
namespace osu.Game.Screens.Multi.Screens.Match
{
public class Header : Container
{
public const float HEIGHT = 200;
private readonly Box tabStrip;
private readonly UpdateableBeatmapSetCover cover;
public readonly PageTabControl<MatchHeaderPage> Tabs;
public BeatmapSetInfo BeatmapSet
{
set => cover.BeatmapSet = value;
}
public Action OnRequestSelectBeatmap;
public Header()
{
RelativeSizeAxes = Axes.X;
Height = HEIGHT;
BeatmapSelectButton beatmapButton;
Children = new Drawable[]
{
cover = new UpdateableBeatmapSetCover
{
RelativeSizeAxes = Axes.Both,
Masking = true,
},
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0), Color4.Black.Opacity(0.5f)),
},
tabStrip = new Box
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
Height = 1,
},
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Horizontal = SearchableListOverlay.WIDTH_PADDING },
Children = new Drawable[]
{
new Container
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
RelativeSizeAxes = Axes.Y,
Width = 200,
Padding = new MarginPadding { Vertical = 5 },
Child = beatmapButton = new BeatmapSelectButton
{
RelativeSizeAxes = Axes.Both,
},
},
Tabs = new PageTabControl<MatchHeaderPage>
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
},
},
},
};
beatmapButton.Action = () => OnRequestSelectBeatmap?.Invoke();
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
tabStrip.Colour = colours.Yellow;
}
private class BeatmapSelectButton : OsuClickableContainer
{
private const float corner_radius = 5;
private const float bg_opacity = 0.5f;
private const float transition_duration = 100;
private readonly Box bg;
private readonly Container border;
public BeatmapSelectButton()
{
Masking = true;
CornerRadius = corner_radius;
Children = new Drawable[]
{
bg = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
Alpha = bg_opacity,
},
new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Font = @"Exo2.0-Bold",
Text = "Select Beatmap",
},
border = new Container
{
RelativeSizeAxes = Axes.Both,
Masking = true,
CornerRadius = corner_radius,
BorderThickness = 4,
Alpha = 0,
Child = new Box // needs a child to show the border
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
AlwaysPresent = true
},
},
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
border.BorderColour = colours.Yellow;
}
protected override bool OnHover(InputState state)
{
border.FadeIn(transition_duration);
return base.OnHover(state);
}
protected override void OnHoverLost(InputState state)
{
base.OnHoverLost(state);
border.FadeOut(transition_duration);
}
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
{
bg.FadeTo(0.75f, 1000, Easing.Out);
return base.OnMouseDown(state, args);
}
protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
{
bg.FadeTo(bg_opacity, transition_duration);
return base.OnMouseUp(state, args);
}
}
}
public enum MatchHeaderPage
{
Settings,
Room,
}
}

View File

@ -0,0 +1,210 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Multiplayer;
using osu.Game.Overlays.SearchableList;
using osu.Game.Screens.Multi.Components;
using OpenTK;
namespace osu.Game.Screens.Multi.Screens.Match
{
public class Info : Container
{
public const float HEIGHT = 128;
private readonly OsuSpriteText name, availabilityStatus;
private readonly BeatmapTypeInfo beatmapTypeInfo;
private readonly ReadyButton readyButton;
private OsuColour colours;
public Bindable<bool> Ready => readyButton.Ready;
public string Name
{
set { name.Text = value; }
}
private RoomAvailability availability;
public RoomAvailability Availability
{
set
{
if (value == availability) return;
availability = value;
if (IsLoaded)
updateAvailabilityStatus();
}
}
private RoomStatus status;
public RoomStatus Status
{
set
{
if (value == status) return;
status = value;
if (IsLoaded)
updateAvailabilityStatus();
}
}
public BeatmapInfo Beatmap
{
set { beatmapTypeInfo.Beatmap = value; }
}
public GameType Type
{
set { beatmapTypeInfo.Type = value; }
}
public Info()
{
RelativeSizeAxes = Axes.X;
Height = HEIGHT;
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.FromHex(@"28242d"),
},
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Horizontal = SearchableListOverlay.WIDTH_PADDING },
Children = new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X,
Padding = new MarginPadding { Vertical = 20 },
Children = new Drawable[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
name = new OsuSpriteText
{
TextSize = 30,
},
availabilityStatus = new OsuSpriteText
{
TextSize = 14,
},
},
},
beatmapTypeInfo = new BeatmapTypeInfo
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
},
},
},
readyButton = new ReadyButton
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
RelativeSizeAxes = Axes.Y,
Size = new Vector2(200, 1),
Padding = new MarginPadding { Vertical = 10 },
},
},
},
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
this.colours = colours;
}
protected override void LoadComplete()
{
base.LoadComplete();
updateAvailabilityStatus();
}
private void updateAvailabilityStatus()
{
if (status != null)
{
availabilityStatus.FadeColour(status.GetAppropriateColour(colours), 100);
availabilityStatus.Text = $"{availability.GetDescription()}, {status.Message}";
}
}
private class ReadyButton : TriangleButton
{
public readonly Bindable<bool> Ready = new Bindable<bool>();
protected override SpriteText CreateText() => new OsuSpriteText
{
Depth = -1,
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Font = @"Exo2.0-Light",
TextSize = 30,
};
[BackgroundDependencyLoader]
private void load()
{
BackgroundColour = OsuColour.FromHex(@"1187aa");
Triangles.ColourLight = OsuColour.FromHex(@"277b9c");
Triangles.ColourDark = OsuColour.FromHex(@"1f6682");
Triangles.TriangleScale = 1.5f;
Container active;
Add(active = new Container
{
RelativeSizeAxes = Axes.Both,
Alpha = 0f,
Child = new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0.15f,
Blending = BlendingMode.Additive,
},
});
Action = () => Ready.Value = !Ready.Value;
Ready.BindValueChanged(value =>
{
if (value)
{
Text = "Not Ready";
active.FadeIn(200);
}
else
{
Text = "Ready";
active.FadeOut(200);
}
}, true);
}
}
}
}

View File

@ -0,0 +1,81 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Online.Multiplayer;
using osu.Game.Screens.Select;
using osu.Game.Users;
namespace osu.Game.Screens.Multi.Screens.Match
{
public class Match : MultiplayerScreen
{
private readonly Room room;
private readonly Participants participants;
private readonly Bindable<string> nameBind = new Bindable<string>();
private readonly Bindable<RoomStatus> statusBind = new Bindable<RoomStatus>();
private readonly Bindable<RoomAvailability> availabilityBind = new Bindable<RoomAvailability>();
private readonly Bindable<GameType> typeBind = new Bindable<GameType>();
private readonly Bindable<BeatmapInfo> beatmapBind = new Bindable<BeatmapInfo>();
private readonly Bindable<int?> maxParticipantsBind = new Bindable<int?>();
private readonly Bindable<IEnumerable<User>> participantsBind = new Bindable<IEnumerable<User>>();
protected override Container<Drawable> TransitionContent => participants;
public override string Type => "room";
public override string Title => room.Name.Value;
public Match(Room room)
{
this.room = room;
Header header;
Info info;
Children = new Drawable[]
{
header = new Header(),
info = new Info
{
Margin = new MarginPadding { Top = Header.HEIGHT },
},
participants = new Participants
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = Header.HEIGHT + Info.HEIGHT },
},
};
header.OnRequestSelectBeatmap = () => Push(new MatchSongSelect());
beatmapBind.BindTo(room.Beatmap);
beatmapBind.BindValueChanged(b =>
{
header.BeatmapSet = b?.BeatmapSet;
info.Beatmap = b;
}, true);
nameBind.BindTo(room.Name);
nameBind.BindValueChanged(n => info.Name = n, true);
statusBind.BindTo(room.Status);
statusBind.BindValueChanged(s => info.Status = s, true);
availabilityBind.BindTo(room.Availability);
availabilityBind.BindValueChanged(a => info.Availability = a, true);
typeBind.BindTo(room.Type);
typeBind.BindValueChanged(t => info.Type = t, true);
maxParticipantsBind.BindTo(room.MaxParticipants);
maxParticipantsBind.BindValueChanged(m => { participants.Max = m; }, true);
participantsBind.BindTo(room.Participants);
participantsBind.BindValueChanged(p => participants.Users = p, true);
}
}
}

View File

@ -0,0 +1,74 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Overlays.SearchableList;
using osu.Game.Screens.Multi.Components;
using osu.Game.Users;
using OpenTK;
namespace osu.Game.Screens.Multi.Screens.Match
{
public class Participants : Container
{
private readonly ParticipantCount count;
private readonly FillFlowContainer<UserPanel> usersFlow;
public IEnumerable<User> Users
{
set {
usersFlow.Children = value.Select(u => new UserPanel(u)
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Width = 300,
OnLoadComplete = d => d.FadeInFromZero(60),
}).ToList();
count.Count = value.Count();
}
}
public int? Max
{
set => count.Max = value;
}
public Participants()
{
Child = new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Horizontal = SearchableListOverlay.WIDTH_PADDING },
Children = new Drawable[]
{
new ScrollContainer
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = 10 },
Children = new Drawable[]
{
count = new ParticipantCount
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
},
usersFlow = new FillFlowContainer<UserPanel>
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Spacing = new Vector2(5),
Padding = new MarginPadding { Top = 40 },
LayoutDuration = 200,
LayoutEasing = Easing.OutQuint,
},
},
},
},
};
}
}
}

View File

@ -1,20 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
namespace osu.Game.Screens.Multi.Screens
{
public class MatchCreate : ScreenWhiteBox
{
protected override IEnumerable<Type> PossibleChildren => new[] {
typeof(Match)
};
public MatchCreate()
{
ValidForResume = false;
}
}
}

View File

@ -10,9 +10,6 @@ namespace osu.Game.Screens.Multi.Screens
{ {
public abstract class MultiplayerScreen : OsuScreen public abstract class MultiplayerScreen : OsuScreen
{ {
private const Easing in_easing = Easing.OutQuint;
private const Easing out_easing = Easing.InSine;
protected virtual Container<Drawable> TransitionContent => Content; protected virtual Container<Drawable> TransitionContent => Content;
/// <summary> /// <summary>
@ -24,16 +21,15 @@ namespace osu.Game.Screens.Multi.Screens
{ {
base.OnEntering(last); base.OnEntering(last);
TransitionContent.MoveToX(200); Content.FadeInFromZero(WaveContainer.APPEAR_DURATION, Easing.OutQuint);
TransitionContent.FadeInFromZero(WaveContainer.APPEAR_DURATION, Easing.OutQuint);
TransitionContent.FadeInFromZero(WaveContainer.APPEAR_DURATION, in_easing); TransitionContent.MoveToX(200).MoveToX(0, WaveContainer.APPEAR_DURATION, Easing.OutQuint);
TransitionContent.MoveToX(0, WaveContainer.APPEAR_DURATION, in_easing);
} }
protected override bool OnExiting(Screen next) protected override bool OnExiting(Screen next)
{ {
Content.FadeOut(WaveContainer.DISAPPEAR_DURATION, out_easing); Content.FadeOut(WaveContainer.DISAPPEAR_DURATION, Easing.OutQuint);
TransitionContent.MoveToX(200, WaveContainer.DISAPPEAR_DURATION, out_easing); TransitionContent.MoveToX(200, WaveContainer.DISAPPEAR_DURATION, Easing.OutQuint);
return base.OnExiting(next); return base.OnExiting(next);
} }
@ -42,16 +38,16 @@ namespace osu.Game.Screens.Multi.Screens
{ {
base.OnResuming(last); base.OnResuming(last);
Content.FadeIn(WaveContainer.APPEAR_DURATION, in_easing); Content.FadeIn(WaveContainer.APPEAR_DURATION, Easing.OutQuint);
TransitionContent.MoveToX(0, WaveContainer.APPEAR_DURATION, in_easing); TransitionContent.MoveToX(0, WaveContainer.APPEAR_DURATION, Easing.OutQuint);
} }
protected override void OnSuspending(Screen next) protected override void OnSuspending(Screen next)
{ {
base.OnSuspending(next); base.OnSuspending(next);
Content.FadeOut(WaveContainer.DISAPPEAR_DURATION, out_easing); Content.FadeOut(WaveContainer.DISAPPEAR_DURATION, Easing.OutQuint);
TransitionContent.MoveToX(-200, WaveContainer.DISAPPEAR_DURATION, out_easing); TransitionContent.MoveToX(-200, WaveContainer.DISAPPEAR_DURATION, Easing.OutQuint);
} }
} }
} }

View File

@ -7,12 +7,7 @@ namespace osu.Game.Screens.Select
{ {
protected override bool OnStart() protected override bool OnStart()
{ {
Schedule(() => if (IsCurrentScreen) Exit();
{
// needs to be scheduled else we enter an infinite feedback loop.
if (IsCurrentScreen) Exit();
});
return true; return true;
} }
} }

View File

@ -3,6 +3,7 @@
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Graphics;
using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Backgrounds;
namespace osu.Game.Tests namespace osu.Game.Tests
@ -13,7 +14,11 @@ namespace osu.Game.Tests
{ {
base.LoadComplete(); base.LoadComplete();
LoadComponentAsync(new BackgroundScreenDefault { Depth = 10 }, AddInternal); LoadComponentAsync(new BackgroundScreenDefault
{
Colour = OsuColour.Gray(0.5f),
Depth = 10
}, AddInternal);
// Have to construct this here, rather than in the constructor, because // Have to construct this here, rather than in the constructor, because
// we depend on some dependencies to be loaded within OsuGameBase.load(). // we depend on some dependencies to be loaded within OsuGameBase.load().