1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-13 19:04:06 +08:00

Merge branch 'master' into editor-timing-timeline

# Conflicts:
#	osu-framework
This commit is contained in:
smoogipooo 2017-09-20 16:19:08 +09:00
commit 6a9572db6c
22 changed files with 299 additions and 121 deletions

View File

@ -29,7 +29,7 @@ namespace osu.Desktop.Deploy
public static string SolutionName = ConfigurationManager.AppSettings["SolutionName"]; public static string SolutionName = ConfigurationManager.AppSettings["SolutionName"];
public static string ProjectName = ConfigurationManager.AppSettings["ProjectName"]; public static string ProjectName = ConfigurationManager.AppSettings["ProjectName"];
public static string NuSpecName = ConfigurationManager.AppSettings["NuSpecName"]; public static string NuSpecName = ConfigurationManager.AppSettings["NuSpecName"];
public static string TargetName = ConfigurationManager.AppSettings["TargetName"]; public static string TargetNames = ConfigurationManager.AppSettings["TargetName"];
public static string PackageName = ConfigurationManager.AppSettings["PackageName"]; public static string PackageName = ConfigurationManager.AppSettings["PackageName"];
public static string IconName = ConfigurationManager.AppSettings["IconName"]; public static string IconName = ConfigurationManager.AppSettings["IconName"];
public static string CodeSigningCertificate = ConfigurationManager.AppSettings["CodeSigningCertificate"]; public static string CodeSigningCertificate = ConfigurationManager.AppSettings["CodeSigningCertificate"];
@ -100,7 +100,8 @@ namespace osu.Desktop.Deploy
updateAssemblyInfo(version); updateAssemblyInfo(version);
write("Running build process..."); write("Running build process...");
runCommand(msbuild_path, $"/v:quiet /m /t:{TargetName.Replace('.', '_')} /p:OutputPath={stagingPath};Targets=\"Clean;Build\";Configuration=Release {SolutionName}.sln"); foreach (string targetName in TargetNames.Split(','))
runCommand(msbuild_path, $"/v:quiet /m /t:{targetName.Replace('.', '_')} /p:OutputPath={stagingPath};Targets=\"Clean;Build\";Configuration=Release {SolutionName}.sln");
write("Creating NuGet deployment package..."); write("Creating NuGet deployment package...");
runCommand(nuget_path, $"pack {NuSpecName} -Version {version} -Properties Configuration=Deploy -OutputDirectory {stagingPath} -BasePath {stagingPath}"); runCommand(nuget_path, $"pack {NuSpecName} -Version {version} -Properties Configuration=Deploy -OutputDirectory {stagingPath} -BasePath {stagingPath}");

View File

@ -23,7 +23,8 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
yield return new Fruit yield return new Fruit
{ {
StartTime = obj.StartTime, StartTime = obj.StartTime,
Position = ((IHasXPosition)obj).X / OsuPlayfield.BASE_SIZE.X NewCombo = (obj as IHasCombo)?.NewCombo ?? false,
X = ((IHasXPosition)obj).X / OsuPlayfield.BASE_SIZE.X
}; };
} }
} }

View File

@ -0,0 +1,38 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
namespace osu.Game.Rulesets.Catch.Beatmaps
{
internal class CatchBeatmapProcessor : BeatmapProcessor<CatchBaseHit>
{
public override void PostProcess(Beatmap<CatchBaseHit> beatmap)
{
if (beatmap.ComboColors.Count == 0)
return;
int comboIndex = 0;
int colourIndex = 0;
CatchBaseHit lastObj = null;
foreach (var obj in beatmap.HitObjects)
{
if (obj.NewCombo)
{
if (lastObj != null) lastObj.LastInCombo = true;
comboIndex = 0;
colourIndex = (colourIndex + 1) % beatmap.ComboColors.Count;
}
obj.ComboIndex = comboIndex++;
obj.ComboColour = beatmap.ComboColors[colourIndex];
lastObj = obj;
}
}
}
}

View File

@ -2,11 +2,23 @@
// 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 osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Catch.Objects namespace osu.Game.Rulesets.Catch.Objects
{ {
public abstract class CatchBaseHit : HitObject public abstract class CatchBaseHit : HitObject, IHasXPosition, IHasCombo
{ {
public float Position { get; set; } public float X { get; set; }
public Color4 ComboColour { get; set; } = Color4.Gray;
public int ComboIndex { get; set; }
public virtual bool NewCombo { get; set; }
/// <summary>
/// The next fruit starts a new combo. Used for explodey.
/// </summary>
public virtual bool LastInCombo { get; set; }
} }
} }

View File

@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
{ {
public class DrawableFruit : DrawableScrollingHitObject<CatchBaseHit> public class DrawableFruit : DrawableScrollingHitObject<CatchBaseHit>
{ {
private const float pulp_size = 30; private const float pulp_size = 20;
private class Pulp : Circle, IHasAccentColour private class Pulp : Circle, IHasAccentColour
{ {
@ -26,15 +26,26 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
{ {
Size = new Vector2(pulp_size); Size = new Vector2(pulp_size);
EdgeEffect = new EdgeEffectParameters Blending = BlendingMode.Additive;
{ Colour = Color4.White.Opacity(0.9f);
Type = EdgeEffectType.Glow,
Radius = 5,
Colour = AccentColour.Opacity(0.5f),
};
} }
public Color4 AccentColour { get; set; } = Color4.White; private Color4 accentColour;
public Color4 AccentColour
{
get { return accentColour; }
set
{
accentColour = value;
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Radius = 5,
Colour = accentColour.Lighten(100),
};
}
}
} }
@ -42,12 +53,14 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
: base(h) : base(h)
{ {
Origin = Anchor.Centre; Origin = Anchor.Centre;
Size = new Vector2(pulp_size * 2, pulp_size * 2.6f); Size = new Vector2(pulp_size * 2.2f, pulp_size * 2.8f);
RelativePositionAxes = Axes.Both; RelativePositionAxes = Axes.Both;
X = h.Position; X = h.X;
Colour = new Color4(RNG.NextSingle(), RNG.NextSingle(), RNG.NextSingle(), 1); AccentColour = HitObject.ComboColour;
Masking = false;
Rotation = (float)(RNG.NextDouble() - 0.5f) * 40; Rotation = (float)(RNG.NextDouble() - 0.5f) * 40;
} }
@ -71,6 +84,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
RelativePositionAxes = Axes.Both, RelativePositionAxes = Axes.Both,
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,
AccentColour = AccentColour,
Scale = new Vector2(0.6f), Scale = new Vector2(0.6f),
}, },
new Pulp new Pulp
@ -78,6 +92,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
RelativePositionAxes = Axes.Both, RelativePositionAxes = Axes.Both,
Anchor = Anchor.CentreLeft, Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft, Origin = Anchor.CentreLeft,
AccentColour = AccentColour,
Y = -0.08f Y = -0.08f
}, },
new Pulp new Pulp
@ -85,6 +100,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
RelativePositionAxes = Axes.Both, RelativePositionAxes = Axes.Both,
Anchor = Anchor.CentreRight, Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight, Origin = Anchor.CentreRight,
AccentColour = AccentColour,
Y = -0.08f Y = -0.08f
}, },
new Pulp new Pulp
@ -92,6 +108,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
RelativePositionAxes = Axes.Both, RelativePositionAxes = Axes.Both,
Anchor = Anchor.BottomCentre, Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre, Origin = Anchor.BottomCentre,
AccentColour = AccentColour,
}, },
} }
} }

View File

@ -1,7 +1,10 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 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 osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
@ -9,13 +12,22 @@ namespace osu.Game.Rulesets.Catch.Scoring
{ {
internal class CatchScoreProcessor : ScoreProcessor<CatchBaseHit> internal class CatchScoreProcessor : ScoreProcessor<CatchBaseHit>
{ {
public CatchScoreProcessor()
{
}
public CatchScoreProcessor(RulesetContainer<CatchBaseHit> rulesetContainer) public CatchScoreProcessor(RulesetContainer<CatchBaseHit> rulesetContainer)
: base(rulesetContainer) : base(rulesetContainer)
{ {
} }
protected override void SimulateAutoplay(Beatmap<CatchBaseHit> beatmap)
{
foreach (var obj in beatmap.HitObjects)
{
var fruit = obj as Fruit;
if (fruit != null)
AddJudgement(new CatchJudgement { Result = HitResult.Perfect });
}
base.SimulateAutoplay(beatmap);
}
} }
} }

View File

@ -0,0 +1,23 @@
// Copyright (c) 2007-2017 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.Rulesets.Catch.Objects;
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
public class TestCaseCatchPlayer : Game.Tests.Visual.TestCasePlayer
{
protected override Beatmap CreateBeatmap()
{
var beatmap = new Beatmap();
for (int i = 0; i < 256; i++)
beatmap.HitObjects.Add(new Fruit { X = 0.5f, StartTime = i * 100, NewCombo = i % 8 == 0 });
return beatmap;
}
}
}

View File

@ -22,6 +22,8 @@ namespace osu.Game.Rulesets.Catch.UI
public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(this); public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(this);
protected override BeatmapProcessor<CatchBaseHit> CreateBeatmapProcessor() => new CatchBeatmapProcessor();
protected override BeatmapConverter<CatchBaseHit> CreateBeatmapConverter() => new CatchBeatmapConverter(); protected override BeatmapConverter<CatchBaseHit> CreateBeatmapConverter() => new CatchBeatmapConverter();
protected override Playfield CreatePlayfield() => new CatchPlayfield(); protected override Playfield CreatePlayfield() => new CatchPlayfield();

View File

@ -11,7 +11,6 @@ using osu.Framework.Graphics.Textures;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Framework.MathUtils; using osu.Framework.MathUtils;
using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawable;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using OpenTK; using OpenTK;
@ -20,20 +19,25 @@ namespace osu.Game.Rulesets.Catch.UI
public class CatcherArea : Container public class CatcherArea : Container
{ {
private Catcher catcher; private Catcher catcher;
private Container explodingFruitContainer;
public void Add(DrawableHitObject fruit, Vector2 screenPosition) => catcher.AddToStack(fruit, screenPosition); public void Add(DrawableHitObject fruit, Vector2 screenPosition) => catcher.AddToStack(fruit, screenPosition);
public bool CheckIfWeCanCatch(CatchBaseHit obj) => Math.Abs(catcher.Position.X - obj.Position) < catcher.DrawSize.X / DrawSize.X / 2; public bool CheckIfWeCanCatch(CatchBaseHit obj) => Math.Abs(catcher.Position.X - obj.X) < catcher.DrawSize.X / DrawSize.X / 2;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
Children = new Drawable[] Children = new Drawable[]
{ {
explodingFruitContainer = new Container
{
RelativeSizeAxes = Axes.Both,
},
catcher = new Catcher catcher = new Catcher
{ {
RelativePositionAxes = Axes.Both, RelativePositionAxes = Axes.Both,
Anchor = Anchor.TopLeft, ExplodingFruitTarget = explodingFruitContainer,
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,
X = 0.5f, X = 0.5f,
} }
@ -51,18 +55,30 @@ namespace osu.Game.Rulesets.Catch.UI
{ {
private Texture texture; private Texture texture;
private Container<DrawableHitObject> caughtFruit;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(TextureStore textures) private void load(TextureStore textures)
{ {
texture = textures.Get(@"Play/Catch/fruit-catcher-idle"); texture = textures.Get(@"Play/Catch/fruit-catcher-idle");
Child = createCatcherSprite(); Children = new Drawable[]
{
createCatcherSprite(),
caughtFruit = new Container<DrawableHitObject>
{
Anchor = Anchor.TopCentre,
Origin = Anchor.BottomCentre,
}
};
} }
private int currentDirection; private int currentDirection;
private bool dashing; private bool dashing;
public Container ExplodingFruitTarget;
protected bool Dashing protected bool Dashing
{ {
get { return dashing; } get { return dashing; }
@ -139,16 +155,21 @@ namespace osu.Game.Rulesets.Catch.UI
return false; return false;
} }
/// <summary>
/// The relative space to cover in 1 millisecond. based on 1 game pixel per millisecond as in osu-stable.
/// </summary>
private const double base_speed = 1.0 / 512;
protected override void Update() protected override void Update()
{ {
base.Update(); base.Update();
if (currentDirection == 0) return; if (currentDirection == 0) return;
float speed = Dashing ? 1.5f : 1; double dashModifier = Dashing ? 1 : 0.5;
Scale = new Vector2(Math.Sign(currentDirection), 1); Scale = new Vector2(Math.Sign(currentDirection), 1);
X = (float)MathHelper.Clamp(X + Math.Sign(currentDirection) * Clock.ElapsedFrameTime / 1800 * speed, 0, 1); X = (float)MathHelper.Clamp(X + Math.Sign(currentDirection) * Clock.ElapsedFrameTime * base_speed * dashModifier, 0, 1);
} }
public void AddToStack(DrawableHitObject fruit, Vector2 absolutePosition) public void AddToStack(DrawableHitObject fruit, Vector2 absolutePosition)
@ -163,13 +184,45 @@ namespace osu.Game.Rulesets.Catch.UI
float distance = fruit.DrawSize.X / 2 * fruit.Scale.X; float distance = fruit.DrawSize.X / 2 * fruit.Scale.X;
while (Children.OfType<DrawableFruit>().Any(f => Vector2Extensions.DistanceSquared(f.Position, fruit.Position) < distance * distance)) while (caughtFruit.Any(f => f.LifetimeEnd == double.MaxValue && Vector2Extensions.DistanceSquared(f.Position, fruit.Position) < distance * distance))
{ {
fruit.X += RNG.Next(-5, 5); fruit.X += RNG.Next(-5, 5);
fruit.Y -= RNG.Next(0, 5); fruit.Y -= RNG.Next(0, 5);
} }
Add(fruit); caughtFruit.Add(fruit);
if (((CatchBaseHit)fruit.HitObject).LastInCombo)
explode();
}
private void explode()
{
var fruit = caughtFruit.ToArray();
foreach (var f in fruit)
{
var originalX = f.X * Scale.X;
if (ExplodingFruitTarget != null)
{
f.Anchor = Anchor.TopLeft;
f.Position = caughtFruit.ToSpaceOfOtherDrawable(f.DrawPosition, ExplodingFruitTarget);
caughtFruit.Remove(f);
ExplodingFruitTarget.Add(f);
}
f.MoveToY(f.Y - 50, 250, Easing.OutSine)
.Then()
.MoveToY(f.Y + 50, 500, Easing.InSine);
f.MoveToX(f.X + originalX * 6, 1000);
f.FadeOut(750);
f.Expire();
}
} }
} }
} }

View File

@ -16,7 +16,7 @@
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType> <DebugType>full</DebugType>
<Optimize>false</Optimize> <Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath> <OutputPath>..\osu.Game\bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants> <DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
@ -26,7 +26,7 @@
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType> <DebugType>pdbonly</DebugType>
<Optimize>true</Optimize> <Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath> <OutputPath>..\osu.Game\bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants> <DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
@ -47,6 +47,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Beatmaps\CatchBeatmapConverter.cs" /> <Compile Include="Beatmaps\CatchBeatmapConverter.cs" />
<Compile Include="Beatmaps\CatchBeatmapProcessor.cs" />
<Compile Include="CatchDifficultyCalculator.cs" /> <Compile Include="CatchDifficultyCalculator.cs" />
<Compile Include="CatchInputManager.cs" /> <Compile Include="CatchInputManager.cs" />
<Compile Include="Scoring\CatchScoreProcessor.cs" /> <Compile Include="Scoring\CatchScoreProcessor.cs" />
@ -57,6 +58,7 @@
<Compile Include="Objects\Fruit.cs" /> <Compile Include="Objects\Fruit.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Tests\TestCaseCatcher.cs" /> <Compile Include="Tests\TestCaseCatcher.cs" />
<Compile Include="Tests\TestCaseCatchPlayer.cs" />
<Compile Include="UI\CatcherArea.cs" /> <Compile Include="UI\CatcherArea.cs" />
<Compile Include="UI\CatchRulesetContainer.cs" /> <Compile Include="UI\CatchRulesetContainer.cs" />
<Compile Include="UI\CatchPlayfield.cs" /> <Compile Include="UI\CatchPlayfield.cs" />

View File

@ -16,7 +16,7 @@
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType> <DebugType>full</DebugType>
<Optimize>false</Optimize> <Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath> <OutputPath>..\osu.Game\bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants> <DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
@ -26,7 +26,7 @@
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType> <DebugType>pdbonly</DebugType>
<Optimize>true</Optimize> <Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath> <OutputPath>..\osu.Game\bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants> <DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>

View File

@ -16,10 +16,6 @@ namespace osu.Game.Rulesets.Osu.Scoring
{ {
internal class OsuScoreProcessor : ScoreProcessor<OsuHitObject> internal class OsuScoreProcessor : ScoreProcessor<OsuHitObject>
{ {
public OsuScoreProcessor()
{
}
public OsuScoreProcessor(RulesetContainer<OsuHitObject> rulesetContainer) public OsuScoreProcessor(RulesetContainer<OsuHitObject> rulesetContainer)
: base(rulesetContainer) : base(rulesetContainer)
{ {

View File

@ -17,7 +17,7 @@
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType> <DebugType>full</DebugType>
<Optimize>false</Optimize> <Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath> <OutputPath>..\osu.Game\bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants> <DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
@ -27,7 +27,7 @@
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType> <DebugType>pdbonly</DebugType>
<Optimize>true</Optimize> <Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath> <OutputPath>..\osu.Game\bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants> <DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>

View File

@ -16,7 +16,7 @@
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType> <DebugType>full</DebugType>
<Optimize>false</Optimize> <Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath> <OutputPath>..\osu.Game\bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants> <DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
@ -26,7 +26,7 @@
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType> <DebugType>pdbonly</DebugType>
<Optimize>true</Optimize> <Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath> <OutputPath>..\osu.Game\bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants> <DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>

View File

@ -429,6 +429,15 @@ namespace osu.Game.Beatmaps
if (beatmapSet != null) if (beatmapSet != null)
{ {
Undelete(beatmapSet); Undelete(beatmapSet);
// ensure all files are present and accessible
foreach (var f in beatmapSet.Files)
{
if (!storage.Exists(f.FileInfo.StoragePath))
using (Stream s = reader.GetStream(f.Filename))
files.Add(s, false);
}
return beatmapSet; return beatmapSet;
} }

View File

@ -25,7 +25,7 @@ namespace osu.Game.Beatmaps
{ {
BeatmapInfo = beatmapInfo; BeatmapInfo = beatmapInfo;
BeatmapSetInfo = beatmapInfo.BeatmapSet; BeatmapSetInfo = beatmapInfo.BeatmapSet;
Metadata = beatmapInfo.Metadata ?? BeatmapSetInfo.Metadata; Metadata = beatmapInfo.Metadata ?? BeatmapSetInfo?.Metadata ?? new BeatmapMetadata();
Mods.ValueChanged += mods => applyRateAdjustments(); Mods.ValueChanged += mods => applyRateAdjustments();
} }

View File

@ -78,33 +78,33 @@ namespace osu.Game.IO
} }
} }
public FileInfo Add(Stream data) public FileInfo Add(Stream data, bool reference = true)
{ {
string hash = data.ComputeSHA2Hash(); string hash = data.ComputeSHA2Hash();
var existing = Connection.Table<FileInfo>().Where(f => f.Hash == hash).FirstOrDefault(); var existing = Connection.Table<FileInfo>().Where(f => f.Hash == hash).FirstOrDefault();
var info = existing ?? new FileInfo { Hash = hash }; var info = existing ?? new FileInfo { Hash = hash };
if (existing != null)
string path = Path.Combine(prefix, info.StoragePath);
// we may be re-adding a file to fix missing store entries.
if (!Storage.Exists(path))
{ {
info = existing; data.Seek(0, SeekOrigin.Begin);
using (var output = Storage.GetStream(path, FileAccess.Write))
data.CopyTo(output);
data.Seek(0, SeekOrigin.Begin);
} }
else
{
string path = Path.Combine(prefix, info.StoragePath);
data.Seek(0, SeekOrigin.Begin);
if (!Storage.Exists(path))
using (var output = Storage.GetStream(path, FileAccess.Write))
data.CopyTo(output);
data.Seek(0, SeekOrigin.Begin);
if (existing == null)
Connection.Insert(info); Connection.Insert(info);
}
Reference(info); if (reference || existing == null)
Reference(info);
return info; return info;
} }

View File

@ -231,7 +231,7 @@ namespace osu.Game
LocalConfig.Save(); LocalConfig.Save();
} }
connection.Dispose(); connection?.Dispose();
base.Dispose(isDisposing); base.Dispose(isDisposing);
} }

View File

@ -6,7 +6,6 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using osu.Framework.Development;
using osu.Game.Database; using osu.Game.Database;
using SQLite.Net; using SQLite.Net;
@ -17,7 +16,7 @@ namespace osu.Game.Rulesets
/// </summary> /// </summary>
public class RulesetStore : DatabaseBackedStore public class RulesetStore : DatabaseBackedStore
{ {
private readonly List<Ruleset> instances = new List<Ruleset>(); private static readonly Dictionary<Assembly, Type> loaded_assemblies = new Dictionary<Assembly, Type>();
public IEnumerable<RulesetInfo> AllRulesets => Query<RulesetInfo>().Where(r => r.Available); public IEnumerable<RulesetInfo> AllRulesets => Query<RulesetInfo>().Where(r => r.Available);
@ -25,11 +24,20 @@ namespace osu.Game.Rulesets
{ {
} }
static RulesetStore()
{
AppDomain.CurrentDomain.AssemblyResolve += currentDomain_AssemblyResolve;
foreach (string file in Directory.GetFiles(Environment.CurrentDirectory, $"{ruleset_library_prefix}.*.dll"))
loadRulesetFromFile(file);
}
private static Assembly currentDomain_AssemblyResolve(object sender, ResolveEventArgs args) => loaded_assemblies.Keys.FirstOrDefault(a => a.FullName == args.Name);
private const string ruleset_library_prefix = "osu.Game.Rulesets"; private const string ruleset_library_prefix = "osu.Game.Rulesets";
protected override void Prepare(bool reset = false) protected override void Prepare(bool reset = false)
{ {
instances.Clear();
Connection.CreateTable<RulesetInfo>(); Connection.CreateTable<RulesetInfo>();
@ -38,73 +46,59 @@ namespace osu.Game.Rulesets
Connection.DeleteAll<RulesetInfo>(); Connection.DeleteAll<RulesetInfo>();
} }
// todo: don't do this on deploy var instances = loaded_assemblies.Values.Select(r => (Ruleset)Activator.CreateInstance(r, new RulesetInfo()));
var sln = DebugUtils.GetSolutionPath();
if (sln != null) Connection.RunInTransaction(() =>
{ {
foreach (string dir in Directory.GetDirectories(sln, $"{ruleset_library_prefix}.*")) //add all legacy modes in correct order
foreach (string file in Directory.GetFiles(Path.Combine(dir, "bin", DebugUtils.IsDebug ? "Debug" : "Release"), $"{ruleset_library_prefix}.*.dll")) foreach (var r in instances.Where(r => r.LegacyID >= 0).OrderBy(r => r.LegacyID))
loadRulesetFromFile(file);
}
foreach (string file in Directory.GetFiles(Environment.CurrentDirectory, $"{ruleset_library_prefix}.*.dll"))
loadRulesetFromFile(file);
Connection.BeginTransaction();
//add all legacy modes in correct order
foreach (var r in instances.Where(r => r.LegacyID >= 0).OrderBy(r => r.LegacyID))
{
Connection.InsertOrReplace(createRulesetInfo(r));
}
//add any other modes
foreach (var r in instances.Where(r => r.LegacyID < 0))
{
var us = createRulesetInfo(r);
var existing = Query<RulesetInfo>().Where(ri => ri.InstantiationInfo == us.InstantiationInfo).FirstOrDefault();
if (existing == null)
Connection.Insert(us);
}
//perform a consistency check
foreach (var r in Query<RulesetInfo>())
{
try
{ {
r.CreateInstance(); Connection.InsertOrReplace(createRulesetInfo(r));
r.Available = true;
}
catch
{
r.Available = false;
} }
Connection.Update(r); //add any other modes
} foreach (var r in instances.Where(r => r.LegacyID < 0))
{
var us = createRulesetInfo(r);
Connection.Commit(); var existing = Query<RulesetInfo>().Where(ri => ri.InstantiationInfo == us.InstantiationInfo).FirstOrDefault();
if (existing == null)
Connection.Insert(us);
}
});
Connection.RunInTransaction(() =>
{
//perform a consistency check
foreach (var r in Query<RulesetInfo>())
{
try
{
r.CreateInstance();
r.Available = true;
}
catch
{
r.Available = false;
}
Connection.Update(r);
}
});
} }
private void loadRulesetFromFile(string file) private static void loadRulesetFromFile(string file)
{ {
var filename = Path.GetFileNameWithoutExtension(file); var filename = Path.GetFileNameWithoutExtension(file);
if (instances.Any(i => i.GetType().Namespace == filename)) if (loaded_assemblies.Values.Any(t => t.Namespace == filename))
return; return;
try try
{ {
var assembly = Assembly.LoadFile(file); var assembly = Assembly.LoadFrom(file);
var rulesets = assembly.GetTypes().Where(t => t.IsSubclassOf(typeof(Ruleset))); loaded_assemblies[assembly] = assembly.GetTypes().First(t => t.IsSubclassOf(typeof(Ruleset)));
if (rulesets.Count() != 1)
return;
instances.Add((Ruleset)Activator.CreateInstance(rulesets.First(), new RulesetInfo()));
} }
catch (Exception) { } catch (Exception) { }
} }

View File

@ -1,6 +1,7 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 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 osu.Framework.Platform; using osu.Framework.Platform;
using osu.Framework.Testing; using osu.Framework.Testing;
@ -10,7 +11,7 @@ namespace osu.Game.Tests.Visual
{ {
public override void RunTest() public override void RunTest()
{ {
using (var host = new HeadlessGameHost(realtime: false)) using (var host = new HeadlessGameHost(AppDomain.CurrentDomain.FriendlyName.Replace(' ', '-'), realtime: false))
host.Run(new OsuTestCaseTestRunner(this)); host.Run(new OsuTestCaseTestRunner(this));
} }

View File

@ -2,9 +2,11 @@
// 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.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.MathUtils; using osu.Framework.MathUtils;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.IO;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Screens.Select; using osu.Game.Screens.Select;
using osu.Game.Screens.Select.Filter; using osu.Game.Screens.Select.Filter;
@ -14,13 +16,20 @@ namespace osu.Game.Tests.Visual
{ {
internal class TestCasePlaySongSelect : OsuTestCase internal class TestCasePlaySongSelect : OsuTestCase
{ {
private readonly BeatmapManager manager; private BeatmapManager manager;
public override string Description => @"with fake data"; public override string Description => @"with fake data";
private readonly RulesetStore rulesets; private RulesetStore rulesets;
public TestCasePlaySongSelect() private DependencyContainer dependencies;
private FileStore files;
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(parent);
[BackgroundDependencyLoader]
private void load()
{ {
PlaySongSelect songSelect; PlaySongSelect songSelect;
@ -31,8 +40,9 @@ namespace osu.Game.Tests.Visual
var backingDatabase = storage.GetDatabase(@"client"); var backingDatabase = storage.GetDatabase(@"client");
backingDatabase.CreateTable<StoreVersion>(); backingDatabase.CreateTable<StoreVersion>();
rulesets = new RulesetStore(backingDatabase); dependencies.Cache(rulesets = new RulesetStore(backingDatabase));
manager = new BeatmapManager(storage, null, backingDatabase, rulesets, null); dependencies.Cache(files = new FileStore(backingDatabase, storage));
dependencies.Cache(manager = new BeatmapManager(storage, files, backingDatabase, rulesets, null));
for (int i = 0; i < 100; i += 10) for (int i = 0; i < 100; i += 10)
manager.Import(createTestBeatmapSet(i)); manager.Import(createTestBeatmapSet(i));

View File

@ -16,7 +16,7 @@ using OpenTK.Graphics;
namespace osu.Game.Tests.Visual namespace osu.Game.Tests.Visual
{ {
internal class TestCasePlayer : OsuTestCase public class TestCasePlayer : OsuTestCase
{ {
protected Player Player; protected Player Player;
private RulesetStore rulesets; private RulesetStore rulesets;
@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual
loadPlayerFor(rulesets.Query<RulesetInfo>().First()); loadPlayerFor(rulesets.Query<RulesetInfo>().First());
} }
private void loadPlayerFor(RulesetInfo r) protected virtual Beatmap CreateBeatmap()
{ {
Beatmap beatmap; Beatmap beatmap;
@ -53,6 +53,13 @@ namespace osu.Game.Tests.Visual
using (var reader = new StreamReader(stream)) using (var reader = new StreamReader(stream))
beatmap = BeatmapDecoder.GetDecoder(reader).Decode(reader); beatmap = BeatmapDecoder.GetDecoder(reader).Decode(reader);
return beatmap;
}
private void loadPlayerFor(RulesetInfo r)
{
var beatmap = CreateBeatmap();
beatmap.BeatmapInfo.Ruleset = r; beatmap.BeatmapInfo.Ruleset = r;
var instance = r.CreateInstance(); var instance = r.CreateInstance();