1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 21:02:55 +08:00

Merge branch 'master' into beat-snap-divisor

This commit is contained in:
Dean Herbert 2018-03-22 20:14:50 +09:00 committed by GitHub
commit 92d8781bf0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
57 changed files with 542 additions and 263 deletions

@ -1 +1 @@
Subproject commit cc39713fbf9427aa53df91e27ecd09d15661089f
Subproject commit d8d4f55e10ac553223db75874bae6ae4894b739a

View File

@ -16,29 +16,13 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
{
public override void PostProcess(Beatmap<CatchHitObject> beatmap)
{
if (beatmap.ComboColours.Count == 0)
return;
int index = 0;
int colourIndex = 0;
CatchHitObject lastObj = null;
initialiseHyperDash(beatmap.HitObjects);
base.PostProcess(beatmap);
int index = 0;
foreach (var obj in beatmap.HitObjects)
{
if (obj.NewCombo)
{
if (lastObj != null) lastObj.LastInCombo = true;
colourIndex = (colourIndex + 1) % beatmap.ComboColours.Count;
}
obj.IndexInBeatmap = index++;
obj.ComboColour = beatmap.ComboColours[colourIndex];
lastObj = obj;
}
}
private void initialiseHyperDash(List<CatchHitObject> objects)

View File

@ -3,7 +3,6 @@
using osu.Framework.MathUtils;
using osu.Game.Rulesets.Objects.Types;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Catch.Objects
{
@ -32,25 +31,11 @@ namespace osu.Game.Rulesets.Catch.Objects
AddNested(new Banana
{
Samples = Samples,
ComboColour = getNextComboColour(),
StartTime = i,
X = RNG.NextSingle()
});
}
private Color4 getNextComboColour()
{
switch (RNG.Next(0, 3))
{
default:
return new Color4(255, 240, 0, 255);
case 1:
return new Color4(255, 192, 0, 255);
case 2:
return new Color4(214, 221, 28, 255);
}
}
public double EndTime => StartTime + Duration;
public double Duration { get; set; }

View File

@ -5,24 +5,25 @@ using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Catch.Objects
{
public abstract class CatchHitObject : HitObject, IHasXPosition, IHasCombo
public abstract class CatchHitObject : HitObject, IHasXPosition, IHasComboInformation
{
public const double OBJECT_RADIUS = 44;
public float X { get; set; }
public Color4 ComboColour { get; set; }
public int IndexInBeatmap { get; set; }
public virtual FruitVisualRepresentation VisualRepresentation => (FruitVisualRepresentation)(IndexInBeatmap % 4);
public virtual bool NewCombo { get; set; }
public int IndexInCurrentCombo { get; set; }
public int ComboIndex { get; set; }
/// <summary>
/// The next fruit starts a new combo. Used for explodey.
/// </summary>

View File

@ -8,6 +8,8 @@ using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
using OpenTK;
using osu.Game.Rulesets.Scoring;
using osu.Game.Skinning;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
@ -57,6 +59,14 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
AddJudgement(new Judgement { Result = CheckPosition.Invoke(HitObject) ? HitResult.Perfect : HitResult.Miss });
}
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
{
base.SkinChanged(skin, allowFallback);
if (HitObject is IHasComboInformation combo)
AccentColour = skin.GetValue<SkinConfiguration, Color4>(s => s.ComboColours.Count > 0 ? s.ComboColours[combo.ComboIndex % s.ComboColours.Count] : (Color4?)null) ?? Color4.White;
}
private const float preempt = 1000;
protected override void UpdateState(ArmedState state)

View File

@ -5,28 +5,39 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
public class DrawableDroplet : PalpableCatchHitObject<Droplet>
{
private Pulp pulp;
public DrawableDroplet(Droplet h)
: base(h)
{
Origin = Anchor.Centre;
Size = new Vector2((float)CatchHitObject.OBJECT_RADIUS) / 4;
AccentColour = h.ComboColour;
Masking = false;
}
[BackgroundDependencyLoader]
private void load()
{
InternalChild = new Pulp
InternalChild = pulp = new Pulp
{
AccentColour = AccentColour,
Size = Size
};
}
public override Color4 AccentColour
{
get { return base.AccentColour; }
set
{
base.AccentColour = value;
pulp.AccentColour = AccentColour;
}
}
}
}

View File

@ -24,7 +24,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
Origin = Anchor.Centre;
Size = new Vector2((float)CatchHitObject.OBJECT_RADIUS);
AccentColour = HitObject.ComboColour;
Masking = false;
Rotation = (float)(RNG.NextDouble() - 0.5f) * 40;
@ -33,6 +32,9 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
[BackgroundDependencyLoader]
private void load()
{
// todo: this should come from the skin.
AccentColour = colourForRrepesentation(HitObject.VisualRepresentation);
InternalChildren = new[]
{
createPulp(HitObject.VisualRepresentation),
@ -273,5 +275,31 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
border.Alpha = (float)MathHelper.Clamp((HitObject.StartTime - Time.Current) / 500, 0, 1);
}
private Color4 colourForRrepesentation(FruitVisualRepresentation representation)
{
switch (representation)
{
default:
case FruitVisualRepresentation.Pear:
return new Color4(17, 136, 170, 255);
case FruitVisualRepresentation.Grape:
return new Color4(204, 102, 0, 255);
case FruitVisualRepresentation.Raspberry:
return new Color4(121, 9, 13, 255);
case FruitVisualRepresentation.Pineapple:
return new Color4(102, 136, 0, 255);
case FruitVisualRepresentation.Banana:
switch (RNG.Next(0, 3))
{
default:
return new Color4(255, 240, 0, 255);
case 1:
return new Color4(255, 192, 0, 255);
case 2:
return new Color4(214, 221, 28, 255);
}
}
}
}
}

View File

@ -33,7 +33,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
var catchObject = (DrawableCatchHitObject)h;
catchObject.CheckPosition = o => CheckPosition?.Invoke(o) ?? false;
catchObject.AccentColour = HitObject.ComboColour;
dropletContainer.Add(h);
base.AddNested(h);

View File

@ -60,7 +60,6 @@ namespace osu.Game.Rulesets.Catch.Objects
AddNested(new Fruit
{
Samples = Samples,
ComboColour = ComboColour,
StartTime = StartTime,
X = X
});
@ -90,7 +89,6 @@ namespace osu.Game.Rulesets.Catch.Objects
AddNested(new TinyDroplet
{
StartTime = t,
ComboColour = ComboColour,
X = X + Curve.PositionAt(progress).X / CatchPlayfield.BASE_WIDTH,
Samples = new List<SampleInfo>(Samples.Select(s => new SampleInfo
{
@ -106,7 +104,6 @@ namespace osu.Game.Rulesets.Catch.Objects
AddNested(new Droplet
{
StartTime = time,
ComboColour = ComboColour,
X = X + Curve.PositionAt(distanceProgress).X / CatchPlayfield.BASE_WIDTH,
Samples = new List<SampleInfo>(Samples.Select(s => new SampleInfo
{
@ -123,7 +120,6 @@ namespace osu.Game.Rulesets.Catch.Objects
AddNested(new Fruit
{
Samples = Samples,
ComboColour = ComboColour,
StartTime = spanStartTime + spanDuration,
X = X + Curve.PositionAt(reversed ? 0 : 1).X / CatchPlayfield.BASE_WIDTH
});

View File

@ -6,13 +6,11 @@ using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.MathUtils;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawable;
using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces;
using osu.Game.Tests.Visual;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Catch.Tests
{
@ -62,8 +60,6 @@ namespace osu.Game.Rulesets.Catch.Tests
Scale = 1.5f,
};
fruit.ComboColour = colourForRrepesentation(fruit.VisualRepresentation);
return new DrawableFruit(fruit)
{
Anchor = Anchor.Centre,
@ -74,31 +70,5 @@ namespace osu.Game.Rulesets.Catch.Tests
LifetimeEnd = double.PositiveInfinity,
};
}
private Color4 colourForRrepesentation(FruitVisualRepresentation representation)
{
switch (representation)
{
default:
case FruitVisualRepresentation.Pear:
return new Color4(17, 136, 170, 255);
case FruitVisualRepresentation.Grape:
return new Color4(204, 102, 0, 255);
case FruitVisualRepresentation.Raspberry:
return new Color4(121, 9, 13, 255);
case FruitVisualRepresentation.Pineapple:
return new Color4(102, 136, 0, 255);
case FruitVisualRepresentation.Banana:
switch (RNG.Next(0, 3))
{
default:
return new Color4(255, 240, 0, 255);
case 1:
return new Color4(255, 192, 0, 255);
case 2:
return new Color4(214, 221, 28, 255);
}
}
}
}
}

View File

@ -54,7 +54,6 @@ namespace osu.Game.Rulesets.Catch.UI
if (caughtFruit == null) return;
caughtFruit.AccentColour = fruit.AccentColour;
caughtFruit.RelativePositionAxes = Axes.None;
caughtFruit.Position = new Vector2(MovableCatcher.ToLocalSpace(fruit.ScreenSpaceDrawQuad.Centre).X - MovableCatcher.DrawSize.X / 2, 0);

View File

@ -8,7 +8,6 @@ using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using OpenTK.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Mania.Judgements;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Scoring;
@ -24,7 +23,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
private readonly GlowPiece glowPiece;
private readonly BodyPiece bodyPiece;
private readonly Container<DrawableHoldNoteTick> tickContainer;
private readonly Container fullHeightContainer;
/// <summary>
@ -40,6 +38,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
public DrawableHoldNote(HoldNote hitObject, ManiaAction action)
: base(hitObject, action)
{
Container<DrawableHoldNoteTick> tickContainer;
RelativeSizeAxes = Axes.X;
InternalChildren = new Drawable[]
@ -57,7 +56,14 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.X,
},
tickContainer = new Container<DrawableHoldNoteTick> { RelativeSizeAxes = Axes.Both },
tickContainer = new Container<DrawableHoldNoteTick>
{
RelativeSizeAxes = Axes.Both,
ChildrenEnumerable = HitObject.NestedHitObjects.OfType<HoldNoteTick>().Select(tick => new DrawableHoldNoteTick(tick)
{
HoldStartTime = () => holdStartTime
})
},
head = new DrawableHeadNote(this, action)
{
Anchor = Anchor.TopCentre,
@ -70,16 +76,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
}
};
foreach (var tick in HitObject.NestedHitObjects.OfType<HoldNoteTick>())
{
var drawableTick = new DrawableHoldNoteTick(tick)
{
HoldStartTime = () => holdStartTime
};
tickContainer.Add(drawableTick);
AddNested(drawableTick);
}
foreach (var tick in tickContainer)
AddNested(tick);
AddNested(head);
AddNested(tail);
@ -90,12 +88,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
get { return base.AccentColour; }
set
{
if (base.AccentColour == value)
return;
base.AccentColour = value;
tickContainer.Children.ForEach(t => t.AccentColour = value);
glowPiece.AccentColour = value;
bodyPiece.AccentColour = value;
head.AccentColour = value;

View File

@ -2,7 +2,6 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics;
using OpenTK.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Mania.Objects.Drawables
@ -28,16 +27,5 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
if (action != null)
Action = action.Value;
}
public override Color4 AccentColour
{
get { return base.AccentColour; }
set
{
if (base.AccentColour == value)
return;
base.AccentColour = value;
}
}
}
}

View File

@ -48,13 +48,10 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
get { return base.AccentColour; }
set
{
if (base.AccentColour == value)
return;
base.AccentColour = value;
laneGlowPiece.AccentColour = value;
GlowPiece.AccentColour = value;
headPiece.AccentColour = value;
laneGlowPiece.AccentColour = AccentColour;
GlowPiece.AccentColour = AccentColour;
headPiece.AccentColour = AccentColour;
}
}

View File

@ -13,24 +13,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
public override void PostProcess(Beatmap<OsuHitObject> beatmap)
{
applyStacking(beatmap);
if (beatmap.ComboColours.Count == 0)
return;
int comboIndex = 0;
int colourIndex = 0;
foreach (var obj in beatmap.HitObjects)
{
if (obj.NewCombo)
{
comboIndex = 0;
colourIndex = (colourIndex + 1) % beatmap.ComboColours.Count;
}
obj.IndexInCurrentCombo = comboIndex++;
obj.ComboColour = beatmap.ComboColours[colourIndex];
}
base.PostProcess(beatmap);
}
private void applyStacking(Beatmap<OsuHitObject> beatmap)

View File

@ -8,6 +8,7 @@ using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
using OpenTK;
using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Rulesets.Scoring;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
@ -21,7 +22,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private readonly NumberPiece number;
private readonly GlowPiece glow;
public DrawableHitCircle(HitCircle h) : base(h)
public DrawableHitCircle(HitCircle h)
: base(h)
{
Origin = Anchor.Centre;
@ -30,13 +32,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
InternalChildren = new Drawable[]
{
glow = new GlowPiece
{
Colour = AccentColour
},
glow = new GlowPiece(),
circle = new CirclePiece
{
Colour = AccentColour,
Hit = () =>
{
if (AllJudged)
@ -52,15 +50,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
},
ring = new RingPiece(),
flash = new FlashPiece(),
explode = new ExplodePiece
{
Colour = AccentColour,
},
explode = new ExplodePiece(),
ApproachCircle = new ApproachCircle
{
Alpha = 0,
Scale = new Vector2(4),
Colour = AccentColour,
}
};
@ -70,6 +64,19 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
HitObject.PositionChanged += _ => Position = HitObject.StackedPosition;
}
public override Color4 AccentColour
{
get { return base.AccentColour; }
set
{
base.AccentColour = value;
explode.Colour = AccentColour;
glow.Colour = AccentColour;
circle.Colour = AccentColour;
ApproachCircle.Colour = AccentColour;
}
}
protected override void CheckForJudgements(bool userTriggered, double timeOffset)
{
if (!userTriggered)

View File

@ -5,6 +5,9 @@ using System.ComponentModel;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Framework.Graphics;
using System.Linq;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Skinning;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
@ -15,7 +18,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected DrawableOsuHitObject(OsuHitObject hitObject)
: base(hitObject)
{
AccentColour = HitObject.ComboColour;
Alpha = 0;
}
@ -35,6 +37,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
}
}
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
{
base.SkinChanged(skin, allowFallback);
if (HitObject is IHasComboInformation combo)
AccentColour = skin.GetValue<SkinConfiguration, Color4>(s => s.ComboColours.Count > 0 ? s.ComboColours[combo.ComboIndex % s.ComboColours.Count] : (Color4?)null) ?? Color4.White;
}
protected virtual void UpdatePreemptState() => this.FadeIn(HitObject.TimeFadein);
protected virtual void UpdateCurrentState(ArmedState state)

View File

@ -13,6 +13,7 @@ using osu.Game.Rulesets.Osu.Judgements;
using osu.Framework.Graphics.Primitives;
using osu.Game.Configuration;
using osu.Game.Rulesets.Scoring;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
@ -41,7 +42,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
Body = new SliderBody(s)
{
AccentColour = AccentColour,
PathWidth = s.Scale * 64,
},
ticks = new Container<DrawableSliderTick> { RelativeSizeAxes = Axes.Both },
@ -50,7 +50,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
BypassAutoSizeAxes = Axes.Both,
Scale = new Vector2(s.Scale),
AccentColour = AccentColour,
AlwaysPresent = true,
Alpha = 0
},
@ -87,6 +86,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
HitObject.PositionChanged += _ => Position = HitObject.StackedPosition;
}
public override Color4 AccentColour
{
get { return base.AccentColour; }
set
{
base.AccentColour = value;
Body.AccentColour = AccentColour;
Ball.AccentColour = AccentColour;
}
}
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{

View File

@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Blending = BlendingMode.Additive,
RelativeSizeAxes = Axes.Both,
Alpha = 0.2f,
}, false);
}, s => s.GetTexture("Play/osu/hitcircle") == null);
}
}
}

View File

@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
RelativeSizeAxes = Axes.Both
}
}, false);
}, s => s.GetTexture("Play/osu/hitcircle") == null);
}
}
}

View File

@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Texture = textures.Get(name),
Blending = BlendingMode.Additive,
Alpha = 0.5f
}, false);
}, s => s.GetTexture("Play/osu/hitcircle") == null);
}
}
}

View File

@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Colour = Color4.White.Opacity(0.5f),
},
Child = new Box()
}, false),
}, s => s.GetTexture("Play/osu/hitcircle") == null),
number = new OsuSpriteText
{
Text = @"1",

View File

@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
public double? SnakedStart { get; private set; }
public double? SnakedEnd { get; private set; }
private Color4 accentColour;
private Color4 accentColour = Color4.White;
/// <summary>
/// Used to colour the path.
/// </summary>
@ -173,6 +173,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
texture.SetData(upload);
path.Texture = texture;
container.ForceRedraw();
}
private void computeSize()

View File

@ -6,13 +6,11 @@ using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects;
using OpenTK;
using osu.Game.Rulesets.Objects.Types;
using OpenTK.Graphics;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Edit.Types;
namespace osu.Game.Rulesets.Osu.Objects
{
public abstract class OsuHitObject : HitObject, IHasCombo, IHasEditablePosition
public abstract class OsuHitObject : HitObject, IHasComboInformation, IHasPosition
{
public const double OBJECT_RADIUS = 64;
@ -53,10 +51,14 @@ namespace osu.Game.Rulesets.Osu.Objects
public float Scale { get; set; } = 1;
public Color4 ComboColour { get; set; } = Color4.Gray;
public virtual bool NewCombo { get; set; }
public int IndexInCurrentCombo { get; set; }
public int ComboIndex { get; set; }
public bool LastInCombo { get; set; }
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);

View File

@ -99,10 +99,10 @@ namespace osu.Game.Rulesets.Osu.Objects
{
StartTime = StartTime,
Position = Position,
IndexInCurrentCombo = IndexInCurrentCombo,
ComboColour = ComboColour,
Samples = Samples,
SampleControlPoint = SampleControlPoint
SampleControlPoint = SampleControlPoint,
IndexInCurrentCombo = IndexInCurrentCombo,
ComboIndex = ComboIndex,
};
TailCircle = new SliderCircle(this)
@ -110,7 +110,7 @@ namespace osu.Game.Rulesets.Osu.Objects
StartTime = EndTime,
Position = EndPosition,
IndexInCurrentCombo = IndexInCurrentCombo,
ComboColour = ComboColour
ComboIndex = ComboIndex,
};
AddNested(HeadCircle);
@ -160,7 +160,6 @@ namespace osu.Game.Rulesets.Osu.Objects
Position = Position + Curve.PositionAt(distanceProgress),
StackHeight = StackHeight,
Scale = Scale,
ComboColour = ComboColour,
Samples = sampleList
});
}
@ -179,7 +178,6 @@ namespace osu.Game.Rulesets.Osu.Objects
Position = Position + Curve.PositionAt(repeat % 2),
StackHeight = StackHeight,
Scale = Scale,
ComboColour = ComboColour,
Samples = new List<SampleInfo>(RepeatSamples[repeatIndex])
});
}

View File

@ -10,7 +10,6 @@ using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Tests.Visual;
using OpenTK;
using OpenTK.Graphics;
using osu.Game.Rulesets.Osu.Judgements;
using System.Collections.Generic;
using System;
@ -61,7 +60,6 @@ namespace osu.Game.Rulesets.Osu.Tests
{
StartTime = Time.Current + 1000 + timeOffset,
Position = positionOffset.Value,
ComboColour = Color4.LightSeaGreen
};
circle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = circleSize });

View File

@ -117,7 +117,6 @@ namespace osu.Game.Rulesets.Osu.Tests
{
StartTime = Time.Current + 1000,
Position = new Vector2(-(distance / 2), 0),
ComboColour = Color4.LightSeaGreen,
ControlPoints = new List<Vector2>
{
Vector2.Zero,
@ -138,7 +137,6 @@ namespace osu.Game.Rulesets.Osu.Tests
{
StartTime = Time.Current + 1000,
Position = new Vector2(-200, 0),
ComboColour = Color4.LightSeaGreen,
ControlPoints = new List<Vector2>
{
Vector2.Zero,
@ -162,7 +160,6 @@ namespace osu.Game.Rulesets.Osu.Tests
CurveType = CurveType.Linear,
StartTime = Time.Current + 1000,
Position = new Vector2(-200, 0),
ComboColour = Color4.LightSeaGreen,
ControlPoints = new List<Vector2>
{
Vector2.Zero,
@ -189,7 +186,6 @@ namespace osu.Game.Rulesets.Osu.Tests
CurveType = CurveType.Bezier,
StartTime = Time.Current + 1000,
Position = new Vector2(-200, 0),
ComboColour = Color4.LightSeaGreen,
ControlPoints = new List<Vector2>
{
Vector2.Zero,
@ -215,7 +211,6 @@ namespace osu.Game.Rulesets.Osu.Tests
CurveType = CurveType.Linear,
StartTime = Time.Current + 1000,
Position = new Vector2(0, 0),
ComboColour = Color4.LightSeaGreen,
ControlPoints = new List<Vector2>
{
Vector2.Zero,
@ -245,7 +240,6 @@ namespace osu.Game.Rulesets.Osu.Tests
{
StartTime = Time.Current + 1000,
Position = new Vector2(-100, 0),
ComboColour = Color4.LightSeaGreen,
CurveType = CurveType.Catmull,
ControlPoints = new List<Vector2>
{

View File

@ -20,11 +20,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
public class DrawableDrumRoll : DrawableTaikoHitObject<DrumRoll>
{
/// <summary>
/// Number of rolling hits required to reach the dark/final accent colour.
/// Number of rolling hits required to reach the dark/final colour.
/// </summary>
private const int rolling_hits_for_dark_accent = 5;
private Color4 accentDarkColour;
private const int rolling_hits_for_engaged_colour = 5;
/// <summary>
/// Rolling number of tick hits. This increases for hits and decreases for misses.
@ -53,11 +51,14 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
public override bool OnPressed(TaikoAction action) => false;
private Color4 colourIdle;
private Color4 colourEngaged;
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
MainPiece.AccentColour = AccentColour = colours.YellowDark;
accentDarkColour = colours.YellowDarker;
MainPiece.AccentColour = colourIdle = colours.YellowDark;
colourEngaged = colours.YellowDarker;
}
private void onTickJudgement(DrawableHitObject obj, Judgement judgement)
@ -67,10 +68,10 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
else
rollingHits--;
rollingHits = MathHelper.Clamp(rollingHits, 0, rolling_hits_for_dark_accent);
rollingHits = MathHelper.Clamp(rollingHits, 0, rolling_hits_for_engaged_colour);
Color4 newAccent = Interpolation.ValueAt((float)rollingHits / rolling_hits_for_dark_accent, AccentColour, accentDarkColour, 0, 1);
MainPiece.FadeAccent(newAccent, 100);
Color4 newColour = Interpolation.ValueAt((float)rollingHits / rolling_hits_for_engaged_colour, colourIdle, colourEngaged, 0, 1);
MainPiece.FadeAccent(newColour, 100);
}
protected override void CheckForJudgements(bool userTriggered, double timeOffset)

View File

@ -11,6 +11,7 @@ using osu.Game.Audio;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Beatmaps.Formats;
using osu.Game.Beatmaps.Timing;
using osu.Game.Skinning;
namespace osu.Game.Tests.Beatmaps.Formats
{
@ -163,7 +164,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
[Test]
public void TestDecodeBeatmapColors()
{
var decoder = new LegacyBeatmapDecoder();
var decoder = new LegacySkinDecoder();
using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
using (var stream = new StreamReader(resStream))
{

View File

@ -12,7 +12,6 @@ using osu.Game.IO.Serialization;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Tests.Resources;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Tests.Beatmaps.Formats
{
@ -89,24 +88,6 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual(2, difficulty.SliderTickRate);
}
[Test]
public void TestDecodeColors()
{
var beatmap = decodeAsJson(normal);
Color4[] expected =
{
new Color4(142, 199, 255, 255),
new Color4(255, 128, 128, 255),
new Color4(128, 255, 255, 255),
new Color4(128, 255, 128, 255),
new Color4(255, 187, 255, 255),
new Color4(255, 177, 140, 255),
};
Assert.AreEqual(expected.Length, beatmap.ComboColours.Count);
for (int i = 0; i < expected.Length; i++)
Assert.AreEqual(expected[i], beatmap.ComboColours[i]);
}
[Test]
public void TestDecodeHitObjects()
{

View File

@ -68,7 +68,7 @@ namespace osu.Game.Tests.Visual
IDatabaseContextFactory factory = new SingletonContextFactory(new OsuDbContext());
dependencies.Cache(rulesets = new RulesetStore(factory));
dependencies.Cache(manager = new BeatmapManager(storage, factory, rulesets, null)
dependencies.Cache(manager = new BeatmapManager(storage, factory, rulesets, null, null)
{
DefaultBeatmap = defaultBeatmap = game.Beatmap.Default
});

View File

@ -1,7 +1,6 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK.Graphics;
using osu.Game.Beatmaps.Timing;
using osu.Game.Rulesets.Objects;
using System.Collections.Generic;
@ -9,7 +8,6 @@ using System.Linq;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.IO.Serialization;
using Newtonsoft.Json;
using osu.Game.Beatmaps.Formats;
using osu.Game.IO.Serialization.Converters;
namespace osu.Game.Beatmaps
@ -17,21 +15,13 @@ namespace osu.Game.Beatmaps
/// <summary>
/// A Beatmap containing converted HitObjects.
/// </summary>
public class Beatmap<T> : IJsonSerializable, IHasComboColours
public class Beatmap<T> : IJsonSerializable
where T : HitObject
{
public BeatmapInfo BeatmapInfo = new BeatmapInfo();
public ControlPointInfo ControlPointInfo = new ControlPointInfo();
public List<BreakPeriod> Breaks = new List<BreakPeriod>();
public List<Color4> ComboColours { get; set; } = new List<Color4>
{
new Color4(17, 136, 170, 255),
new Color4(102, 136, 0, 255),
new Color4(204, 102, 0, 255),
new Color4(121, 9, 13, 255)
};
[JsonIgnore]
public BeatmapMetadata Metadata => BeatmapInfo?.Metadata ?? BeatmapInfo?.BeatmapSet?.Metadata;
@ -56,7 +46,6 @@ namespace osu.Game.Beatmaps
BeatmapInfo = original?.BeatmapInfo.DeepClone() ?? BeatmapInfo;
ControlPointInfo = original?.ControlPointInfo ?? ControlPointInfo;
Breaks = original?.Breaks ?? Breaks;
ComboColours = original?.ComboColours ?? ComboColours;
HitObjects = original?.HitObjects ?? HitObjects;
if (original == null && Metadata == null)

View File

@ -57,7 +57,6 @@ namespace osu.Game.Beatmaps
beatmap.ControlPointInfo = original.ControlPointInfo;
beatmap.HitObjects = original.HitObjects.SelectMany(h => convert(h, original)).ToList();
beatmap.Breaks = original.Breaks;
beatmap.ComboColours = original.ComboColours;
return beatmap;
}

View File

@ -8,6 +8,7 @@ using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using osu.Framework.Audio;
using osu.Framework.Extensions;
using osu.Framework.Logging;
using osu.Framework.Platform;
@ -55,6 +56,8 @@ namespace osu.Game.Beatmaps
private readonly APIAccess api;
private readonly AudioManager audioManager;
private readonly List<DownloadBeatmapSetRequest> currentDownloads = new List<DownloadBeatmapSetRequest>();
/// <summary>
@ -62,7 +65,7 @@ namespace osu.Game.Beatmaps
/// </summary>
public Func<Storage> GetStableStorage { private get; set; }
public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, APIAccess api, IIpcHost importHost = null)
public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, APIAccess api, AudioManager audioManager, IIpcHost importHost = null)
: base(storage, contextFactory, new BeatmapStore(contextFactory), importHost)
{
beatmaps = (BeatmapStore)ModelStore;
@ -71,6 +74,7 @@ namespace osu.Game.Beatmaps
this.rulesets = rulesets;
this.api = api;
this.audioManager = audioManager;
}
protected override void Populate(BeatmapSetInfo model, ArchiveReader archive)
@ -217,7 +221,7 @@ namespace osu.Game.Beatmaps
if (beatmapInfo.Metadata == null)
beatmapInfo.Metadata = beatmapInfo.BeatmapSet.Metadata;
WorkingBeatmap working = new BeatmapManagerWorkingBeatmap(Files.Store, beatmapInfo);
WorkingBeatmap working = new BeatmapManagerWorkingBeatmap(Files.Store, beatmapInfo, audioManager);
previous?.TransferTo(working);

View File

@ -4,12 +4,14 @@
using System;
using System.IO;
using System.Linq;
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Textures;
using osu.Framework.IO.Stores;
using osu.Framework.Logging;
using osu.Game.Beatmaps.Formats;
using osu.Game.Graphics.Textures;
using osu.Game.Skinning;
using osu.Game.Storyboards;
namespace osu.Game.Beatmaps
@ -19,11 +21,13 @@ namespace osu.Game.Beatmaps
protected class BeatmapManagerWorkingBeatmap : WorkingBeatmap
{
private readonly IResourceStore<byte[]> store;
private readonly AudioManager audioManager;
public BeatmapManagerWorkingBeatmap(IResourceStore<byte[]> store, BeatmapInfo beatmapInfo)
public BeatmapManagerWorkingBeatmap(IResourceStore<byte[]> store, BeatmapInfo beatmapInfo, AudioManager audioManager)
: base(beatmapInfo)
{
this.store = store;
this.audioManager = audioManager;
}
protected override Beatmap GetBeatmap()
@ -100,6 +104,22 @@ namespace osu.Game.Beatmaps
return storyboard;
}
protected override Skin GetSkin()
{
Skin skin;
try
{
skin = new LegacyBeatmapSkin(BeatmapInfo, store, audioManager);
}
catch (Exception e)
{
Logger.Error(e, "Skin failed to load");
skin = new DefaultSkin();
}
return skin;
}
}
}
}

View File

@ -1,7 +1,9 @@
// 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.Linq;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Beatmaps
{
@ -19,6 +21,29 @@ namespace osu.Game.Beatmaps
/// </para>
/// </summary>
/// <param name="beatmap">The Beatmap to process.</param>
public virtual void PostProcess(Beatmap<TObject> beatmap) { }
public virtual void PostProcess(Beatmap<TObject> beatmap)
{
IHasComboInformation lastObj = null;
foreach (var obj in beatmap.HitObjects.OfType<IHasComboInformation>())
{
if (obj.NewCombo)
{
obj.IndexInCurrentCombo = 0;
if (lastObj != null)
{
lastObj.LastInCombo = true;
obj.ComboIndex = lastObj.ComboIndex + 1;
}
}
else if (lastObj != null)
{
obj.IndexInCurrentCombo = lastObj.IndexInCurrentCombo + 1;
obj.ComboIndex = lastObj.ComboIndex;
}
lastObj = obj;
}
}
}
}

View File

@ -14,6 +14,7 @@ using osu.Framework.IO.File;
using System.IO;
using osu.Game.IO.Serialization;
using System.Diagnostics;
using osu.Game.Skinning;
namespace osu.Game.Beatmaps
{
@ -40,6 +41,7 @@ namespace osu.Game.Beatmaps
track = new AsyncLazy<Track>(populateTrack);
waveform = new AsyncLazy<Waveform>(populateWaveform);
storyboard = new AsyncLazy<Storyboard>(populateStoryboard);
skin = new AsyncLazy<Skin>(populateSkin);
}
/// <summary>
@ -56,6 +58,7 @@ namespace osu.Game.Beatmaps
protected abstract Beatmap GetBeatmap();
protected abstract Texture GetBackground();
protected abstract Track GetTrack();
protected virtual Skin GetSkin() => new DefaultSkin();
protected virtual Waveform GetWaveform() => new Waveform();
protected virtual Storyboard GetStoryboard() => new Storyboard { BeatmapInfo = BeatmapInfo };
@ -109,6 +112,13 @@ namespace osu.Game.Beatmaps
private Storyboard populateStoryboard() => GetStoryboard();
public bool SkinLoaded => skin.IsResultAvailable;
public Skin Skin => skin.Value.Result;
public async Task<Skin> GetSkinAsync() => await skin.Value;
private readonly AsyncLazy<Skin> skin;
private Skin populateSkin() => GetSkin();
public void TransferTo(WorkingBeatmap other)
{
if (track.IsResultAvailable && Track != null && BeatmapInfo.AudioEquals(other.BeatmapInfo))
@ -123,6 +133,7 @@ namespace osu.Game.Beatmaps
if (BackgroundLoaded) Background?.Dispose();
if (WaveformLoaded) Waveform?.Dispose();
if (StoryboardLoaded) Storyboard?.Dispose();
if (SkinLoaded) Skin?.Dispose();
}
/// <summary>

View File

@ -10,6 +10,8 @@ namespace osu.Game.Database
/// </summary>
/// <typeparam name="TFile">The model representing a file.</typeparam>
public interface IHasFiles<TFile>
where TFile : INamedFileInfo
{
List<TFile> Files { get; set; }
}

View File

@ -30,6 +30,9 @@ namespace osu.Game.Graphics.UserInterface
}
}
// We may not be focused yet, but we need to handle keyboard input to be able to request focus
public override bool HandleKeyboardInput => HoldFocus || base.HandleKeyboardInput;
protected override void OnFocus(InputState state)
{
base.OnFocus(state);

View File

@ -302,6 +302,21 @@ namespace osu.Game
};
}
var singleDisplaySideOverlays = new OverlayContainer[] { settings, notifications };
foreach (var overlay in singleDisplaySideOverlays)
{
overlay.StateChanged += state =>
{
if (state == Visibility.Hidden) return;
foreach (var c in singleDisplaySideOverlays)
{
if (c == overlay) continue;
c.State = Visibility.Hidden;
}
};
}
// eventually informational overlays should be displayed in a stack, but for now let's only allow one to stay open at a time.
var informationalOverlays = new OverlayContainer[] { beatmapSetOverlay, userProfile };
foreach (var overlay in informationalOverlays)

View File

@ -105,6 +105,7 @@ namespace osu.Game
runMigrations();
dependencies.Cache(SkinManager = new SkinManager(Host.Storage, contextFactory, Host, Audio));
dependencies.CacheAs<ISkinSource>(SkinManager);
var api = new APIAccess(LocalConfig);
@ -113,7 +114,7 @@ namespace osu.Game
dependencies.Cache(RulesetStore = new RulesetStore(contextFactory));
dependencies.Cache(FileStore = new FileStore(contextFactory, Host.Storage));
dependencies.Cache(BeatmapManager = new BeatmapManager(Host.Storage, contextFactory, RulesetStore, api, Host));
dependencies.Cache(BeatmapManager = new BeatmapManager(Host.Storage, contextFactory, RulesetStore, api, Audio, Host));
dependencies.Cache(ScoreStore = new ScoreStore(Host.Storage, contextFactory, Host, BeatmapManager, RulesetStore));
dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore));
dependencies.Cache(SettingsStore = new SettingsStore(contextFactory));

View File

@ -6,7 +6,6 @@ using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives;
using osu.Game.Audio;
using osu.Game.Graphics;
@ -19,7 +18,7 @@ using OpenTK.Graphics;
namespace osu.Game.Rulesets.Objects.Drawables
{
public abstract class DrawableHitObject : CompositeDrawable, IHasAccentColour
public abstract class DrawableHitObject : SkinReloadableDrawable, IHasAccentColour
{
public readonly HitObject HitObject;

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
namespace osu.Game.Rulesets.Objects.Types
{
/// <summary>
/// A HitObject that is part of a combo and has extended information about its position relative to other combo objects.
/// </summary>
public interface IHasComboIndex : IHasCombo
{
/// <summary>
/// The offset of this hitobject in the current combo.
/// </summary>
int IndexInCurrentCombo { get; set; }
/// <summary>
/// The offset of this hitobject in the current combo.
/// </summary>
int ComboIndex { get; set; }
/// <summary>
/// Whether this is the last object in the current combo.
/// </summary>
bool LastInCombo { get; set; }
}
}

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
namespace osu.Game.Rulesets.Objects.Types
{
/// <summary>
/// A HitObject that is part of a combo and has extended information about its position relative to other combo objects.
/// </summary>
public interface IHasComboInformation : IHasCombo
{
/// <summary>
/// The offset of this hitobject in the current combo.
/// </summary>
int IndexInCurrentCombo { get; set; }
/// <summary>
/// The offset of this combo in relation to the beatmap.
/// </summary>
int ComboIndex { get; set; }
/// <summary>
/// Whether this is the last object in the current combo.
/// </summary>
bool LastInCombo { get; set; }
}
}

View File

@ -26,6 +26,7 @@ using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Ranking;
using osu.Game.Skinning;
using osu.Game.Storyboards.Drawables;
namespace osu.Game.Screens.Play
@ -163,7 +164,11 @@ namespace osu.Game.Screens.Play
RelativeSizeAxes = Axes.Both,
Alpha = 0,
},
RulesetContainer,
new LocalSkinOverrideContainer(working.Skin)
{
RelativeSizeAxes = Axes.Both,
Child = RulesetContainer
},
new SkipOverlay(firstObjectTime)
{
Clock = Clock, // skip button doesn't want to use the audio clock directly

View File

@ -139,8 +139,7 @@ namespace osu.Game.Screens.Play
{
// as the pushDebounce below has a delay, we need to keep checking and cancel a future debounce
// if we become unready for push during the delay.
pushDebounce?.Cancel();
pushDebounce = null;
cancelLoad();
return;
}
@ -172,10 +171,23 @@ namespace osu.Game.Screens.Play
}
}
private void cancelLoad()
{
pushDebounce?.Cancel();
pushDebounce = null;
}
protected override void OnSuspending(Screen next)
{
base.OnSuspending(next);
cancelLoad();
}
protected override bool OnExiting(Screen next)
{
Content.ScaleTo(0.7f, 150, Easing.InQuint);
this.FadeOut(150);
cancelLoad();
return base.OnExiting(next);
}

View File

@ -3,6 +3,8 @@
using osu.Framework.Audio.Sample;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Textures;
using OpenTK.Graphics;
namespace osu.Game.Skinning
{
@ -11,17 +13,22 @@ namespace osu.Game.Skinning
public DefaultSkin()
: base(SkinInfo.Default)
{
Configuration = new SkinConfiguration();
Configuration = new SkinConfiguration
{
ComboColours =
{
new Color4(17, 136, 170, 255),
new Color4(102, 136, 0, 255),
new Color4(204, 102, 0, 255),
new Color4(121, 9, 13, 255)
}
};
}
public override Drawable GetDrawableComponent(string componentName)
{
return null;
}
public override Drawable GetDrawableComponent(string componentName) => null;
public override SampleChannel GetSample(string sampleName)
{
return null;
}
public override Texture GetTexture(string componentName) => null;
public override SampleChannel GetSample(string sampleName) => null;
}
}

View File

@ -0,0 +1,28 @@
// 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.Audio.Sample;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Textures;
namespace osu.Game.Skinning
{
/// <summary>
/// Provides access to skinnable elements.
/// </summary>
public interface ISkinSource
{
event Action SourceChanged;
Drawable GetDrawableComponent(string componentName);
Texture GetTexture(string componentName);
SampleChannel GetSample(string sampleName);
TValue GetValue<TConfiguration, TValue>(Func<TConfiguration, TValue> query) where TConfiguration : SkinConfiguration where TValue : class;
TValue? GetValue<TConfiguration, TValue>(Func<TConfiguration, TValue?> query) where TConfiguration : SkinConfiguration where TValue : struct;
}
}

View File

@ -0,0 +1,20 @@
// 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.Audio;
using osu.Framework.IO.Stores;
using osu.Game.Beatmaps;
namespace osu.Game.Skinning
{
public class LegacyBeatmapSkin : LegacySkin
{
public LegacyBeatmapSkin(BeatmapInfo beatmap, IResourceStore<byte[]> storage, AudioManager audioManager)
: base(createSkinInfo(beatmap), new LegacySkinResourceStore<BeatmapSetFileInfo>(beatmap.BeatmapSet, storage), audioManager, beatmap.Path)
{
}
private static SkinInfo createSkinInfo(BeatmapInfo beatmap) =>
new SkinInfo { Name = beatmap.ToString(), Creator = beatmap.Metadata.Author.ToString() };
}
}

View File

@ -10,29 +10,32 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.IO.Stores;
using osu.Game.Database;
namespace osu.Game.Skinning
{
public class LegacySkin : Skin
{
private readonly TextureStore textures;
protected TextureStore Textures;
private readonly SampleManager samples;
protected SampleManager Samples;
public LegacySkin(SkinInfo skin, IResourceStore<byte[]> storage, AudioManager audioManager)
: base(skin)
: this(skin, new LegacySkinResourceStore<SkinFileInfo>(skin, storage), audioManager, "skin.ini")
{
storage = new LegacySkinResourceStore(skin, storage);
samples = audioManager.GetSampleManager(storage);
textures = new TextureStore(new RawTextureLoaderStore(storage));
Stream stream = storage.GetStream("skin.ini");
}
protected LegacySkin(SkinInfo skin, IResourceStore<byte[]> storage, AudioManager audioManager, string filename) : base(skin)
{
Stream stream = storage.GetStream(filename);
if (stream != null)
using (StreamReader reader = new StreamReader(stream))
Configuration = new LegacySkinDecoder().Decode(reader);
else
Configuration = new SkinConfiguration();
Samples = audioManager.GetSampleManager(storage);
Textures = new TextureStore(new RawTextureLoaderStore(storage));
}
public override Drawable GetDrawableComponent(string componentName)
@ -53,17 +56,20 @@ namespace osu.Game.Skinning
break;
}
var texture = textures.Get(componentName);
var texture = GetTexture(componentName);
if (texture == null) return null;
return new Sprite { Texture = texture };
}
public override SampleChannel GetSample(string sampleName) => samples.Get(sampleName);
public override Texture GetTexture(string componentName) => Textures.Get(componentName);
private class LegacySkinResourceStore : IResourceStore<byte[]>
public override SampleChannel GetSample(string sampleName) => Samples.Get(sampleName);
protected class LegacySkinResourceStore<T> : IResourceStore<byte[]>
where T : INamedFileInfo
{
private readonly SkinInfo skin;
private readonly IHasFiles<T> source;
private readonly IResourceStore<byte[]> underlyingStore;
private string getPathForFile(string filename)
@ -72,14 +78,14 @@ namespace osu.Game.Skinning
string lastPiece = filename.Split('/').Last();
var file = skin.Files.FirstOrDefault(f =>
var file = source.Files.FirstOrDefault(f =>
string.Equals(hasExtension ? f.Filename : Path.GetFileNameWithoutExtension(f.Filename), lastPiece, StringComparison.InvariantCultureIgnoreCase));
return file?.FileInfo.StoragePath;
}
public LegacySkinResourceStore(SkinInfo skin, IResourceStore<byte[]> underlyingStore)
public LegacySkinResourceStore(IHasFiles<T> source, IResourceStore<byte[]> underlyingStore)
{
this.skin = skin;
this.source = source;
this.underlyingStore = underlyingStore;
}

View File

@ -0,0 +1,72 @@
// 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.Audio.Sample;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Textures;
namespace osu.Game.Skinning
{
public class LocalSkinOverrideContainer : Container, ISkinSource
{
public event Action SourceChanged;
public Drawable GetDrawableComponent(string componentName) => source.GetDrawableComponent(componentName) ?? fallbackSource?.GetDrawableComponent(componentName);
public Texture GetTexture(string componentName) => source.GetTexture(componentName) ?? fallbackSource.GetTexture(componentName);
public SampleChannel GetSample(string sampleName) => source.GetSample(sampleName) ?? fallbackSource?.GetSample(sampleName);
public TValue? GetValue<TConfiguration, TValue>(Func<TConfiguration, TValue?> query) where TConfiguration : SkinConfiguration where TValue : struct
{
TValue? val = null;
var conf = (source as Skin)?.Configuration as TConfiguration;
if (conf != null)
val = query?.Invoke(conf);
return val ?? fallbackSource?.GetValue(query);
}
public TValue GetValue<TConfiguration, TValue>(Func<TConfiguration, TValue> query) where TConfiguration : SkinConfiguration where TValue : class
{
TValue val = null;
var conf = (source as Skin)?.Configuration as TConfiguration;
if (conf != null)
val = query?.Invoke(conf);
return val ?? fallbackSource?.GetValue(query);
}
private readonly ISkinSource source;
private ISkinSource fallbackSource;
public LocalSkinOverrideContainer(ISkinSource source)
{
this.source = source;
}
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
{
var dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
fallbackSource = dependencies.Get<ISkinSource>();
if (fallbackSource != null)
fallbackSource.SourceChanged += () => SourceChanged?.Invoke();
dependencies.CacheAs<ISkinSource>(this);
return dependencies;
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (fallbackSource != null)
fallbackSource.SourceChanged -= SourceChanged;
}
}
}

View File

@ -1,24 +1,60 @@
// 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.Audio.Sample;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Textures;
namespace osu.Game.Skinning
{
public abstract class Skin
public abstract class Skin : IDisposable, ISkinSource
{
public readonly SkinInfo SkinInfo;
public virtual SkinConfiguration Configuration { get; protected set; }
public event Action SourceChanged;
public abstract Drawable GetDrawableComponent(string componentName);
public abstract SampleChannel GetSample(string sampleName);
public abstract Texture GetTexture(string componentName);
public TValue GetValue<TConfiguration, TValue>(Func<TConfiguration, TValue> query) where TConfiguration : SkinConfiguration where TValue : class
=> Configuration is TConfiguration conf ? query?.Invoke(conf) : null;
public TValue? GetValue<TConfiguration, TValue>(Func<TConfiguration, TValue?> query) where TConfiguration : SkinConfiguration where TValue : struct
=> Configuration is TConfiguration conf ? query?.Invoke(conf) : null;
protected Skin(SkinInfo skin)
{
SkinInfo = skin;
}
#region Disposal
~Skin()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private bool isDisposed;
protected virtual void Dispose(bool isDisposing)
{
if (isDisposed)
return;
isDisposed = true;
}
#endregion
}
}

View File

@ -7,14 +7,17 @@ using System.Linq;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Textures;
using osu.Framework.Platform;
using osu.Game.Database;
using osu.Game.IO.Archives;
namespace osu.Game.Skinning
{
public class SkinManager : ArchiveModelManager<SkinInfo, SkinFileInfo>
public class SkinManager : ArchiveModelManager<SkinInfo, SkinFileInfo>, ISkinSource
{
private readonly AudioManager audio;
@ -89,6 +92,8 @@ namespace osu.Game.Skinning
{
if (skin.SkinInfo != CurrentSkinInfo.Value)
throw new InvalidOperationException($"Setting {nameof(CurrentSkin)}'s value directly is not supported. Use {nameof(CurrentSkinInfo)} instead.");
SourceChanged?.Invoke();
};
// migrate older imports which didn't have access to skin.ini
@ -108,5 +113,17 @@ namespace osu.Game.Skinning
/// <param name="query">The query.</param>
/// <returns>The first result for the provided query, or null if no results were found.</returns>
public SkinInfo Query(Expression<Func<SkinInfo, bool>> query) => ModelStore.ConsumableItems.AsNoTracking().FirstOrDefault(query);
public event Action SourceChanged;
public Drawable GetDrawableComponent(string componentName) => CurrentSkin.Value.GetDrawableComponent(componentName);
public Texture GetTexture(string componentName) => CurrentSkin.Value.GetTexture(componentName);
public SampleChannel GetSample(string sampleName) => CurrentSkin.Value.GetSample(sampleName);
public TValue GetValue<TConfiguration, TValue>(Func<TConfiguration, TValue> query) where TConfiguration : SkinConfiguration where TValue : class => CurrentSkin.Value.GetValue(query);
public TValue? GetValue<TConfiguration, TValue>(Func<TConfiguration, TValue?> query) where TConfiguration : SkinConfiguration where TValue : struct => CurrentSkin.Value.GetValue(query);
}
}

View File

@ -1,8 +1,8 @@
// 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.Configuration;
using osu.Framework.Graphics.Containers;
namespace osu.Game.Skinning
@ -12,33 +12,36 @@ namespace osu.Game.Skinning
/// </summary>
public abstract class SkinReloadableDrawable : CompositeDrawable
{
private Bindable<Skin> skin;
private readonly Func<ISkinSource, bool> allowFallback;
private ISkinSource skin;
/// <summary>
/// Whether fallback to default skin should be allowed if the custom skin is missing this resource.
/// </summary>
private readonly bool allowDefaultFallback;
private bool allowDefaultFallback => allowFallback == null || allowFallback.Invoke(skin);
/// <summary>
/// Create a new <see cref="SkinReloadableDrawable"/>
/// </summary>
/// <param name="fallback">Whether fallback to default skin should be allowed if the custom skin is missing this resource.</param>
protected SkinReloadableDrawable(bool fallback = true)
protected SkinReloadableDrawable(Func<ISkinSource, bool> allowFallback = null)
{
allowDefaultFallback = fallback;
this.allowFallback = allowFallback;
}
[BackgroundDependencyLoader]
private void load(SkinManager skinManager)
private void load(ISkinSource source)
{
skin = skinManager.CurrentSkin.GetBoundCopy();
skin.ValueChanged += skin => SkinChanged(skin, allowDefaultFallback || skin.SkinInfo == SkinInfo.Default);
skin = source;
skin.SourceChanged += onChange;
}
private void onChange() => SkinChanged(skin, allowDefaultFallback);
protected override void LoadAsyncComplete()
{
base.LoadAsyncComplete();
skin.TriggerChange();
onChange();
}
/// <summary>
@ -46,7 +49,7 @@ namespace osu.Game.Skinning
/// </summary>
/// <param name="skin">The new skin.</param>
/// <param name="allowFallback">Whether fallback to default skin should be allowed if the custom skin is missing this resource.</param>
protected virtual void SkinChanged(Skin skin, bool allowFallback)
protected virtual void SkinChanged(ISkinSource skin, bool allowFallback)
{
}
}

View File

@ -9,8 +9,8 @@ namespace osu.Game.Skinning
{
public class SkinnableDrawable : SkinnableDrawable<Drawable>
{
public SkinnableDrawable(string name, Func<string, Drawable> defaultImplementation, bool fallback = true, bool restrictSize = true)
: base(name, defaultImplementation, fallback, restrictSize)
public SkinnableDrawable(string name, Func<string, Drawable> defaultImplementation, Func<ISkinSource, bool> allowFallback = null, bool restrictSize = true)
: base(name, defaultImplementation, allowFallback, restrictSize)
{
}
}
@ -31,7 +31,7 @@ namespace osu.Game.Skinning
/// <param name="defaultImplementation">A function to create the default skin implementation of this element.</param>
/// <param name="fallback">Whther to fallback to the default implementation when a custom skin is specified but not implementation is present.</param>
/// <param name="restrictSize">Whether a user-skin drawable should be limited to the size of our parent.</param>
public SkinnableDrawable(string name, Func<string, T> defaultImplementation, bool fallback = true, bool restrictSize = true) : base(fallback)
public SkinnableDrawable(string name, Func<string, T> defaultImplementation, Func<ISkinSource, bool> allowFallback = null, bool restrictSize = true) : base(allowFallback)
{
componentName = name;
createDefault = defaultImplementation;
@ -40,7 +40,7 @@ namespace osu.Game.Skinning
RelativeSizeAxes = Axes.Both;
}
protected override void SkinChanged(Skin skin, bool allowFallback)
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
{
var drawable = skin.GetDrawableComponent(componentName);
if (drawable != null)

View File

@ -31,7 +31,7 @@ namespace osu.Game.Skinning
public void Play() => channels?.ForEach(c => c.Play());
protected override void SkinChanged(Skin skin, bool allowFallback)
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
{
channels = samples.Select(s =>
{

View File

@ -374,6 +374,7 @@
<Compile Include="Overlays\Social\SocialPanel.cs" />
<Compile Include="Rulesets\Mods\IApplicableToDrawableHitObject.cs" />
<Compile Include="Rulesets\Objects\HitWindows.cs" />
<Compile Include="Rulesets\Objects\Types\IHasComboInformation.cs" />
<Compile Include="Rulesets\Replays\Legacy\LegacyReplayFrame.cs" />
<Compile Include="Rulesets\Replays\Legacy\ReplayButtonState.cs" />
<Compile Include="Rulesets\Replays\ReplayFrame.cs" />
@ -873,9 +874,12 @@
<Compile Include="Screens\Tournament\Teams\DrawingsTeam.cs" />
<Compile Include="Screens\Tournament\Teams\ITeamList.cs" />
<Compile Include="Screens\Tournament\Teams\StorageBackedTeamList.cs" />
<Compile Include="Skinning\LegacyBeatmapSkin.cs" />
<Compile Include="Skinning\DefaultSkin.cs" />
<Compile Include="Skinning\ISkinSource.cs" />
<Compile Include="Skinning\LegacySkin.cs" />
<Compile Include="Skinning\LegacySkinDecoder.cs" />
<Compile Include="Skinning\LocalSkinOverrideContainer.cs" />
<Compile Include="Skinning\Skin.cs" />
<Compile Include="Skinning\SkinConfiguration.cs" />
<Compile Include="Skinning\SkinFileInfo.cs" />