Compare commits
8 Commits
@@ -0,0 +1,80 @@
|
|||||||
|
using osu.Framework.Graphics;
|
||||||
|
using OpenTK;
|
||||||
|
using Symcol.Core.Graphics.Containers;
|
||||||
|
|
||||||
|
namespace Symcol.Core.GameObjects
|
||||||
|
{
|
||||||
|
public class SymcolHitbox : SymcolContainer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// whether we want to do hit detection
|
||||||
|
/// </summary>
|
||||||
|
public int Team { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// whether we want to do hit detection
|
||||||
|
/// </summary>
|
||||||
|
public bool HitDetection { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// the shape of this object (used for hit detection)
|
||||||
|
/// </summary>
|
||||||
|
public Shape Shape { get; }
|
||||||
|
|
||||||
|
public SymcolHitbox(Vector2 size, Shape shape = Shape.Circle)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
|
Shape = shape;
|
||||||
|
Size = size;
|
||||||
|
|
||||||
|
if (Shape == Shape.Circle)
|
||||||
|
Child = new SymcolContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
CornerRadius = Width / 2
|
||||||
|
};
|
||||||
|
else if (Shape == Shape.Rectangle)
|
||||||
|
Child = new SymcolContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HitDetect(SymcolHitbox hitbox1, SymcolHitbox hitbox2)
|
||||||
|
{
|
||||||
|
if (hitbox1.HitDetection && hitbox2.HitDetection && hitbox1.Team != hitbox2.Team)
|
||||||
|
{
|
||||||
|
if (hitbox1.Shape == Shape.Circle && hitbox2.Shape == Shape.Circle)
|
||||||
|
{
|
||||||
|
if (hitbox1.ScreenSpaceDrawQuad.AABB.IntersectsWith(hitbox2.ScreenSpaceDrawQuad.AABB))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (hitbox1.Shape == Shape.Circle && hitbox2.Shape == Shape.Rectangle || hitbox1.Shape == Shape.Rectangle && hitbox2.Shape == Shape.Circle)
|
||||||
|
{
|
||||||
|
if (hitbox1.ScreenSpaceDrawQuad.AABB.IntersectsWith(hitbox2.ScreenSpaceDrawQuad.AABB))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (hitbox1.Shape == Shape.Rectangle && hitbox2.Shape == Shape.Rectangle)
|
||||||
|
{
|
||||||
|
if (hitbox1.ScreenSpaceDrawQuad.AABB.IntersectsWith(hitbox2.ScreenSpaceDrawQuad.AABB))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (hitbox1.Shape == Shape.Complex || hitbox2.Shape == Shape.Complex)
|
||||||
|
foreach (SymcolContainer child1 in hitbox1.Children)
|
||||||
|
foreach (SymcolContainer child2 in hitbox2.Children)
|
||||||
|
if (child1.ScreenSpaceDrawQuad.AABB.IntersectsWith(child2.ScreenSpaceDrawQuad.AABB))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Shape
|
||||||
|
{
|
||||||
|
Circle,
|
||||||
|
Rectangle,
|
||||||
|
Complex
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
|
||||||
|
namespace Symcol.Core.Graphics.Containers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Will support base eden game functions (if we come up with any)
|
||||||
|
/// </summary>
|
||||||
|
public class SymcolClickableContainer : ClickableContainer
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
|
||||||
|
namespace Symcol.Core.Graphics.Containers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Will support base eden game functions (if we come up with any)
|
||||||
|
/// </summary>
|
||||||
|
public class SymcolContainer : Container
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
using OpenTK;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Input;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Symcol.Core.Graphics.Containers
|
||||||
|
{
|
||||||
|
public class SymcolDialContainer : CircularContainer
|
||||||
|
{
|
||||||
|
public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => true;
|
||||||
|
|
||||||
|
private Vector2 mousePosition;
|
||||||
|
|
||||||
|
private float lastAngle;
|
||||||
|
private float currentRotation;
|
||||||
|
public float RotationAbsolute;
|
||||||
|
|
||||||
|
private int completeTick;
|
||||||
|
|
||||||
|
private bool updateCompleteTick() => completeTick != (completeTick = (int)(RotationAbsolute / 360));
|
||||||
|
|
||||||
|
private bool rotationTransferred;
|
||||||
|
|
||||||
|
protected override bool OnMouseMove(InputState state)
|
||||||
|
{
|
||||||
|
mousePosition = Parent.ToLocalSpace(state.Mouse.NativeState.Position);
|
||||||
|
return base.OnMouseMove(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
var thisAngle = -(float)MathHelper.RadiansToDegrees(Math.Atan2(mousePosition.X - DrawSize.X / 2, mousePosition.Y - DrawSize.Y / 2));
|
||||||
|
|
||||||
|
|
||||||
|
if (!rotationTransferred)
|
||||||
|
{
|
||||||
|
currentRotation = Rotation * 2;
|
||||||
|
rotationTransferred = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thisAngle - lastAngle > 180)
|
||||||
|
lastAngle += 360;
|
||||||
|
else if (lastAngle - thisAngle > 180)
|
||||||
|
lastAngle -= 360;
|
||||||
|
|
||||||
|
currentRotation += thisAngle - lastAngle;
|
||||||
|
RotationAbsolute += Math.Abs(thisAngle - lastAngle);
|
||||||
|
|
||||||
|
lastAngle = thisAngle;
|
||||||
|
|
||||||
|
foreach(Drawable drawable in Children)
|
||||||
|
drawable.RotateTo(currentRotation / 2, 200, Easing.OutExpo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
using osu.Framework.Input;
|
||||||
|
using OpenTK;
|
||||||
|
using OpenTK.Input;
|
||||||
|
|
||||||
|
namespace Symcol.Core.Graphics.Containers
|
||||||
|
{
|
||||||
|
public class SymcolDragContainer : SymcolContainer
|
||||||
|
{
|
||||||
|
protected override bool OnDragStart(InputState state) => true;
|
||||||
|
|
||||||
|
public bool AllowLeftClickDrag { get; set; } = true;
|
||||||
|
|
||||||
|
private bool drag;
|
||||||
|
|
||||||
|
private Vector2 startPosition;
|
||||||
|
|
||||||
|
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
|
||||||
|
{
|
||||||
|
startPosition = Position;
|
||||||
|
|
||||||
|
if (args.Button == MouseButton.Left && AllowLeftClickDrag || args.Button == MouseButton.Right)
|
||||||
|
drag = true;
|
||||||
|
|
||||||
|
return base.OnMouseDown(state, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnDrag(InputState state)
|
||||||
|
{
|
||||||
|
if (drag)
|
||||||
|
Position = startPosition + state.Mouse.Position - state.Mouse.PositionMouseDown.GetValueOrDefault();
|
||||||
|
|
||||||
|
return base.OnDrag(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
|
||||||
|
{
|
||||||
|
if (args.Button == MouseButton.Left && AllowLeftClickDrag || args.Button == MouseButton.Right)
|
||||||
|
drag = false;
|
||||||
|
|
||||||
|
return base.OnMouseUp(state, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Graphics.Textures;
|
||||||
|
using osu.Framework.Input;
|
||||||
|
using Symcol.Core.Graphics.Containers;
|
||||||
|
|
||||||
|
namespace Symcol.Core.Graphics.UserInterface
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// just a Button with a sprite
|
||||||
|
/// </summary>
|
||||||
|
public class SpriteButton : SymcolClickableContainer
|
||||||
|
{
|
||||||
|
private readonly string textureName;
|
||||||
|
|
||||||
|
public string Text
|
||||||
|
{
|
||||||
|
get { return spriteText?.Text; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (spriteText != null)
|
||||||
|
spriteText.Text = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly Sprite sprite;
|
||||||
|
private readonly SpriteText spriteText;
|
||||||
|
|
||||||
|
public SpriteButton(string textureName)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
|
this.textureName = textureName;
|
||||||
|
Masking = true;
|
||||||
|
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
sprite = new Sprite
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
FillMode = FillMode.Fill
|
||||||
|
},
|
||||||
|
spriteText = new SpriteText
|
||||||
|
{
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(TextureStore textures)
|
||||||
|
{
|
||||||
|
sprite.Texture = textures.Get(textureName);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnClick(InputState state)
|
||||||
|
{
|
||||||
|
if (Enabled.Value)
|
||||||
|
{
|
||||||
|
var flash = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Alpha = 0.5f
|
||||||
|
};
|
||||||
|
|
||||||
|
Add(flash);
|
||||||
|
|
||||||
|
flash.Blending = BlendingMode.Additive;
|
||||||
|
flash.FadeOut(200);
|
||||||
|
flash.Expire();
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.OnClick(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,158 @@
|
|||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Input;
|
||||||
|
using OpenTK;
|
||||||
|
using OpenTK.Graphics;
|
||||||
|
using OpenTK.Input;
|
||||||
|
using Symcol.Core.Graphics.Containers;
|
||||||
|
|
||||||
|
namespace Symcol.Core.Graphics.UserInterface
|
||||||
|
{
|
||||||
|
public class SymcolWindow : SymcolContainer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Put all your stuff in this
|
||||||
|
/// </summary>
|
||||||
|
public SymcolContainer WindowContent { get; set; }
|
||||||
|
public SpriteText WindowTitle;
|
||||||
|
|
||||||
|
private readonly SymcolContainer topBar;
|
||||||
|
private readonly SymcolClickableContainer minimize;
|
||||||
|
|
||||||
|
public SymcolWindow(Vector2 size)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
CornerRadius = 6;
|
||||||
|
Masking = true;
|
||||||
|
AutoSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
topBar = new SymcolContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Height = 20,
|
||||||
|
Width = size.X,
|
||||||
|
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Colour = Color4.Black,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Alpha = 0.5f
|
||||||
|
},
|
||||||
|
WindowTitle = new SpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
TextSize = 18
|
||||||
|
},
|
||||||
|
new SymcolClickableContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreRight,
|
||||||
|
Origin = Anchor.CentreRight,
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
Width = 30,
|
||||||
|
Action = Close,
|
||||||
|
|
||||||
|
Child = new Box
|
||||||
|
{
|
||||||
|
Colour = Color4.Red,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Alpha = 0.5f
|
||||||
|
}
|
||||||
|
},
|
||||||
|
minimize = new SymcolClickableContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreRight,
|
||||||
|
Origin = Anchor.CentreRight,
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
Width = 30,
|
||||||
|
Position = new Vector2(-30, 0),
|
||||||
|
Action = Minimize,
|
||||||
|
|
||||||
|
Child = new Box
|
||||||
|
{
|
||||||
|
Colour = Color4.White,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Alpha = 0.5f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
WindowContent = new SymcolContainer
|
||||||
|
{
|
||||||
|
Size = size,
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
WindowContent.Position = new Vector2(0, topBar.Height);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void Close()
|
||||||
|
{
|
||||||
|
this.FadeOut(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void Open()
|
||||||
|
{
|
||||||
|
this.FadeIn(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Toggle()
|
||||||
|
{
|
||||||
|
if (Alpha > 0)
|
||||||
|
this.FadeOut(200);
|
||||||
|
else
|
||||||
|
this.FadeIn(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnDragStart(InputState state) => true;
|
||||||
|
|
||||||
|
private bool drag;
|
||||||
|
|
||||||
|
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
|
||||||
|
{
|
||||||
|
if (args.Button == MouseButton.Left)
|
||||||
|
drag = true;
|
||||||
|
|
||||||
|
return base.OnMouseDown(state, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnDrag(InputState state)
|
||||||
|
{
|
||||||
|
if (drag)
|
||||||
|
Position += state.Mouse.Delta;
|
||||||
|
|
||||||
|
return base.OnDrag(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
|
||||||
|
{
|
||||||
|
if (args.Button == MouseButton.Left)
|
||||||
|
drag = false;
|
||||||
|
|
||||||
|
return base.OnMouseUp(state, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Maximize()
|
||||||
|
{
|
||||||
|
WindowContent.FadeIn(200);
|
||||||
|
WindowContent.ScaleTo(Vector2.One, 200);
|
||||||
|
minimize.Action = Minimize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Minimize()
|
||||||
|
{
|
||||||
|
WindowContent.FadeOut(200);
|
||||||
|
WindowContent.ScaleTo(new Vector2(1, 0), 200);
|
||||||
|
minimize.Action = Maximize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Symcol.Core.Networking
|
||||||
|
{
|
||||||
|
[Serializable]
|
||||||
|
public class BasicPacket : Packet
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Ask host if we can connect
|
||||||
|
/// </summary>
|
||||||
|
public bool Connect;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tell the host we are breaking up
|
||||||
|
/// </summary>
|
||||||
|
public bool Disconnect;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Testing Connection
|
||||||
|
/// </summary>
|
||||||
|
public bool Test;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send a force exit to others
|
||||||
|
/// </summary>
|
||||||
|
public bool Abort;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// PreLoad the game
|
||||||
|
/// </summary>
|
||||||
|
public bool LoadGame;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Request a list of all players from Host
|
||||||
|
/// </summary>
|
||||||
|
public bool RequestPlayerList;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List of players in this match that we should account for
|
||||||
|
/// </summary>
|
||||||
|
public List<ClientInfo> PlayerList = new List<ClientInfo>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tell Host we are PreLoaded
|
||||||
|
/// </summary>
|
||||||
|
public bool Loaded;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Start the game already!
|
||||||
|
/// </summary>
|
||||||
|
public bool StartGame;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send to host when game started
|
||||||
|
/// </summary>
|
||||||
|
public bool GameStarted;
|
||||||
|
|
||||||
|
public BasicPacket(ClientInfo clientInfo) : base(clientInfo)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Symcol.Core.Networking
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Just a client signature basically
|
||||||
|
/// </summary>
|
||||||
|
[Serializable]
|
||||||
|
public class ClientInfo
|
||||||
|
{
|
||||||
|
public string IP;
|
||||||
|
|
||||||
|
public int Port;
|
||||||
|
|
||||||
|
public int Ping;
|
||||||
|
|
||||||
|
public int ConncetionTryCount;
|
||||||
|
|
||||||
|
public double LastConnectionTime;
|
||||||
|
|
||||||
|
public double StartedTestConnectionTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
using System.IO;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Runtime.Serialization.Formatters.Binary;
|
||||||
|
|
||||||
|
namespace Symcol.Core.Networking
|
||||||
|
{
|
||||||
|
public class NetworkingClient
|
||||||
|
{
|
||||||
|
public UdpClient UdpClient;
|
||||||
|
|
||||||
|
public IPEndPoint EndPoint;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// if false we only receive
|
||||||
|
/// </summary>
|
||||||
|
public readonly bool Send;
|
||||||
|
|
||||||
|
public readonly int Port;
|
||||||
|
|
||||||
|
public readonly string IP;
|
||||||
|
|
||||||
|
public NetworkingClient(bool send, string ip, int port = 25570)
|
||||||
|
{
|
||||||
|
Port = port;
|
||||||
|
IP = ip;
|
||||||
|
|
||||||
|
if (send)
|
||||||
|
initializeSend();
|
||||||
|
else
|
||||||
|
initializeReceive();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeSend()
|
||||||
|
{
|
||||||
|
UdpClient = new UdpClient(IP, Port);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeReceive()
|
||||||
|
{
|
||||||
|
UdpClient = new UdpClient(Port);
|
||||||
|
EndPoint = new IPEndPoint(IPAddress.Any, Port);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendByte(byte[] data)
|
||||||
|
{
|
||||||
|
UdpClient.Send(data, data.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] receiveByte()
|
||||||
|
{
|
||||||
|
return UdpClient.Receive(ref EndPoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int SENTPACKETCOUNT;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send a Packet somewhere
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="packet"></param>
|
||||||
|
public void SendPacket(Packet packet)
|
||||||
|
{
|
||||||
|
SENTPACKETCOUNT++;
|
||||||
|
using (MemoryStream stream = new MemoryStream())
|
||||||
|
{
|
||||||
|
BinaryFormatter formatter = new BinaryFormatter();
|
||||||
|
formatter.Serialize(stream, packet);
|
||||||
|
|
||||||
|
stream.Position = 0;
|
||||||
|
|
||||||
|
int i = packet.PacketSize;
|
||||||
|
retry:
|
||||||
|
byte[] data = new byte[i];
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
stream.Read(data, 0, (int)stream.Length);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
i *= 2;
|
||||||
|
goto retry;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendByte(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Receive a Packet from somewhere
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Packet ReceivePacket(bool force = false)
|
||||||
|
{
|
||||||
|
if (UdpClient.Available > 0 || force)
|
||||||
|
using (MemoryStream stream = new MemoryStream())
|
||||||
|
{
|
||||||
|
byte[] data = receiveByte();
|
||||||
|
stream.Write(data, 0, data.Length);
|
||||||
|
|
||||||
|
stream.Position = 0;
|
||||||
|
|
||||||
|
BinaryFormatter formatter = new BinaryFormatter();
|
||||||
|
Packet packet = (Packet)formatter.Deserialize(stream);
|
||||||
|
packet.ClientInfo.IP = EndPoint.Address.ToString();
|
||||||
|
|
||||||
|
return packet;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
if (UdpClient != null)
|
||||||
|
UdpClient.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,637 @@
|
|||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Logging;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Symcol.Core.Networking
|
||||||
|
{
|
||||||
|
//TODO: This NEEDS its own clock to avoid fuckery later on with DoubleTime and HalfTime
|
||||||
|
public class NetworkingClientHandler : Container
|
||||||
|
{
|
||||||
|
//30 Seconds by default
|
||||||
|
protected virtual double TimeOutTime => 30000;
|
||||||
|
|
||||||
|
protected readonly NetworkingClient ReceiveClient;
|
||||||
|
|
||||||
|
protected readonly NetworkingClient SendClient;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Just a client signature basically
|
||||||
|
/// </summary>
|
||||||
|
public ClientInfo ClientInfo;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// All Connecting clients
|
||||||
|
/// </summary>
|
||||||
|
public readonly List<ClientInfo> ConnectingClients = new List<ClientInfo>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// All Connected clients
|
||||||
|
/// </summary>
|
||||||
|
public readonly List<ClientInfo> ConncetedClients = new List<ClientInfo>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clients waiting in our match
|
||||||
|
/// </summary>
|
||||||
|
public readonly List<ClientInfo> InMatchClients = new List<ClientInfo>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clients loaded and ready to start
|
||||||
|
/// </summary>
|
||||||
|
public readonly List<ClientInfo> LoadedClients = new List<ClientInfo>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clients ingame playing
|
||||||
|
/// </summary>
|
||||||
|
public readonly List<ClientInfo> InGameClients = new List<ClientInfo>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets hit when we get a Packet
|
||||||
|
/// </summary>
|
||||||
|
public Action<Packet> OnPacketReceive;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// (Peer) Call this when we connect to a Host (Includes list of connected peers + Host)
|
||||||
|
/// </summary>
|
||||||
|
public Action<List<ClientInfo>> OnConnectedToHost;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// (Host) Whenever a new client Connects
|
||||||
|
/// </summary>
|
||||||
|
public Action<ClientInfo> OnClientConnect;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// (Host) Whenever a new client Disconnects
|
||||||
|
/// </summary>
|
||||||
|
public Action<ClientInfo> OnClientDisconnect;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// (Host/Peer) When a new Client joins the game
|
||||||
|
/// </summary>
|
||||||
|
public Action<ClientInfo> OnClientJoin;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Receive a full player list
|
||||||
|
/// </summary>
|
||||||
|
public Action<List<ClientInfo>> OnReceivePlayerList;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// if we are connected and in a match
|
||||||
|
/// </summary>
|
||||||
|
public bool InMatch;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Are we in a game
|
||||||
|
/// </summary>
|
||||||
|
public bool InGame;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Are we loaded and ready to start?
|
||||||
|
/// </summary>
|
||||||
|
public bool Loaded;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called to leave an in-progress game
|
||||||
|
/// </summary>
|
||||||
|
public Action OnAbort;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called to load the game
|
||||||
|
/// </summary>
|
||||||
|
public Action<List<ClientInfo>> OnLoadGame;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called to start the game once loaded
|
||||||
|
/// </summary>
|
||||||
|
public Action StartGame;
|
||||||
|
|
||||||
|
public readonly ClientType ClientType;
|
||||||
|
|
||||||
|
public NetworkingClientHandler(ClientType type, string ip, int port = 25570, string thisLocalIp = "0.0.0.0")
|
||||||
|
{
|
||||||
|
AlwaysPresent = true;
|
||||||
|
|
||||||
|
ClientType = type;
|
||||||
|
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case ClientType.Host:
|
||||||
|
ReceiveClient = new NetworkingClient(false, ip, port);
|
||||||
|
break;
|
||||||
|
case ClientType.Peer:
|
||||||
|
ReceiveClient = new NetworkingClient(false, thisLocalIp, port);
|
||||||
|
SendClient = new NetworkingClient(true, ip, port);
|
||||||
|
break;
|
||||||
|
case ClientType.Server:
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.Log("Created a RulesetNetworkingClientHandler", LoggingTarget.Network, LogLevel.Verbose);
|
||||||
|
|
||||||
|
if (ClientInfo == null)
|
||||||
|
ClientInfo = new ClientInfo
|
||||||
|
{
|
||||||
|
Port = port
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
if (ClientType == ClientType.Peer)
|
||||||
|
ConnectToHost();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
PacketRestart:
|
||||||
|
Packet p = null;
|
||||||
|
|
||||||
|
if (ReceiveClient.UdpClient.Available > 0)
|
||||||
|
p = ReceiveClient.ReceivePacket();
|
||||||
|
|
||||||
|
if (p is BasicPacket packet)
|
||||||
|
{
|
||||||
|
//Hosts
|
||||||
|
if (SendClient == null)
|
||||||
|
{
|
||||||
|
if (packet.Disconnect)
|
||||||
|
{
|
||||||
|
OnClientDisconnect?.Invoke(packet.ClientInfo);
|
||||||
|
foreach (ClientInfo client in ConnectingClients)
|
||||||
|
if (client.IP == packet.ClientInfo.IP)
|
||||||
|
{
|
||||||
|
ConnectingClients.Remove(client);
|
||||||
|
Logger.Log("A Connecting Client has Disconnected", LoggingTarget.Network, LogLevel.Verbose);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
foreach (ClientInfo client in ConncetedClients)
|
||||||
|
if (client.IP == packet.ClientInfo.IP)
|
||||||
|
{
|
||||||
|
ConncetedClients.Remove(client);
|
||||||
|
Logger.Log("A Client has Disconnected", LoggingTarget.Network, LogLevel.Verbose);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
foreach (ClientInfo client in InMatchClients)
|
||||||
|
if (client.IP == packet.ClientInfo.IP)
|
||||||
|
{
|
||||||
|
InMatchClients.Remove(client);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
foreach (ClientInfo client in LoadedClients)
|
||||||
|
if (client.IP == packet.ClientInfo.IP)
|
||||||
|
{
|
||||||
|
LoadedClients.Remove(client);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
foreach (ClientInfo client in InGameClients)
|
||||||
|
if (client.IP == packet.ClientInfo.IP)
|
||||||
|
{
|
||||||
|
InGameClients.Remove(client);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packet.Connect)
|
||||||
|
{
|
||||||
|
packet.ClientInfo.StartedTestConnectionTime = Time.Current;
|
||||||
|
ConnectingClients.Add(packet.ClientInfo);
|
||||||
|
|
||||||
|
NetworkingClient client = new NetworkingClient(true, packet.ClientInfo.IP, packet.ClientInfo.Port);
|
||||||
|
|
||||||
|
List<ClientInfo> playerList = new List<ClientInfo>
|
||||||
|
{
|
||||||
|
ClientInfo
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (ClientInfo clientInfo in ConncetedClients)
|
||||||
|
playerList.Add(clientInfo);
|
||||||
|
|
||||||
|
client.SendPacket(new BasicPacket(ClientInfo)
|
||||||
|
{
|
||||||
|
PlayerList = playerList,
|
||||||
|
Connect = true
|
||||||
|
});
|
||||||
|
|
||||||
|
Logger.Log("A Client is Connecting. . .", LoggingTarget.Network, LogLevel.Verbose);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packet.RequestPlayerList)
|
||||||
|
{
|
||||||
|
NetworkingClient client = new NetworkingClient(true, packet.ClientInfo.IP, packet.ClientInfo.Port);
|
||||||
|
|
||||||
|
List<ClientInfo> playerList = new List<ClientInfo>
|
||||||
|
{
|
||||||
|
ClientInfo
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (ClientInfo clientInfo in ConncetedClients)
|
||||||
|
playerList.Add(clientInfo);
|
||||||
|
|
||||||
|
client.SendPacket(new BasicPacket(ClientInfo)
|
||||||
|
{
|
||||||
|
PlayerList = playerList,
|
||||||
|
RequestPlayerList = true
|
||||||
|
});
|
||||||
|
|
||||||
|
Logger.Log("A Client is Connecting. . .", LoggingTarget.Network, LogLevel.Verbose);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packet.Loaded)
|
||||||
|
foreach (ClientInfo client in InMatchClients)
|
||||||
|
if (client.IP == packet.ClientInfo.IP)
|
||||||
|
{
|
||||||
|
Logger.Log("A Client has Loaded and is ready to start", LoggingTarget.Network, LogLevel.Verbose);
|
||||||
|
InMatchClients.Remove(client);
|
||||||
|
LoadedClients.Add(client);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packet.GameStarted)
|
||||||
|
foreach (ClientInfo client in LoadedClients)
|
||||||
|
if (client.IP == packet.ClientInfo.IP)
|
||||||
|
{
|
||||||
|
Logger.Log("A Client has started!", LoggingTarget.Network, LogLevel.Verbose);
|
||||||
|
LoadedClients.Remove(client);
|
||||||
|
InGameClients.Add(client);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packet.Test)
|
||||||
|
{
|
||||||
|
foreach (ClientInfo client in ConnectingClients)
|
||||||
|
if (client.IP == packet.ClientInfo.IP)
|
||||||
|
{
|
||||||
|
client.Ping = (int)Time.Current - (int)client.StartedTestConnectionTime;
|
||||||
|
ConnectingClients.Remove(client);
|
||||||
|
ConncetedClients.Add(client);
|
||||||
|
InMatchClients.Add(client);
|
||||||
|
OnClientJoin?.Invoke(client);
|
||||||
|
client.LastConnectionTime = Time.Current;
|
||||||
|
client.ConncetionTryCount = 0;
|
||||||
|
Logger.Log("Successfully connected to a Client! Ping: " + client.Ping, LoggingTarget.Network, LogLevel.Verbose);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
foreach (ClientInfo client in ConncetedClients)
|
||||||
|
if (client.IP == packet.ClientInfo.IP)
|
||||||
|
{
|
||||||
|
client.Ping = (int)Time.Current - (int)client.StartedTestConnectionTime;
|
||||||
|
client.LastConnectionTime = Time.Current;
|
||||||
|
client.ConncetionTryCount = 0;
|
||||||
|
Logger.Log("Successfully maintained connection to a Client! Ping: " + client.Ping, LoggingTarget.Network, LogLevel.Verbose);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (InMatchClients.Count == 0 && LoadedClients.Count > 0 && Loaded && !InGame)
|
||||||
|
SendStartGame();
|
||||||
|
|
||||||
|
//Peers
|
||||||
|
else if (SendClient != null)
|
||||||
|
{
|
||||||
|
if (packet.Connect)
|
||||||
|
{
|
||||||
|
if (!InGame && !InMatch)
|
||||||
|
{
|
||||||
|
InMatch = true;
|
||||||
|
OnConnectedToHost?.Invoke(packet.PlayerList);
|
||||||
|
}
|
||||||
|
Logger.Log("Connected to Host!", LoggingTarget.Network, LogLevel.Verbose);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packet.Test)
|
||||||
|
{
|
||||||
|
SendToHost(new BasicPacket(ClientInfo) { Test = true });
|
||||||
|
Logger.Log("Received connection test info from host, returning. . .", LoggingTarget.Network, LogLevel.Verbose);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packet.RequestPlayerList)
|
||||||
|
OnReceivePlayerList?.Invoke(packet.PlayerList);
|
||||||
|
|
||||||
|
if (packet.StartGame)
|
||||||
|
{
|
||||||
|
StartGame?.Invoke();
|
||||||
|
SendToHost(new BasicPacket(ClientInfo) { GameStarted = true });
|
||||||
|
InGame = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packet.Abort)
|
||||||
|
{
|
||||||
|
OnAbort?.Invoke();
|
||||||
|
InGame = false;
|
||||||
|
Loaded = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packet.LoadGame)
|
||||||
|
{
|
||||||
|
Logger.Log("Received instructions to LoadGame for " + packet.PlayerList.Count + " players", LoggingTarget.Network, LogLevel.Verbose);
|
||||||
|
OnLoadGame?.Invoke(packet.PlayerList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p != null)
|
||||||
|
OnPacketReceive?.Invoke(p);
|
||||||
|
|
||||||
|
if (ReceiveClient.UdpClient.Available > 0)
|
||||||
|
goto PacketRestart;
|
||||||
|
|
||||||
|
foreach (ClientInfo client in ConnectingClients)
|
||||||
|
{
|
||||||
|
if (client.LastConnectionTime + TimeOutTime / 10 <= Time.Current && client.ConncetionTryCount == 0)
|
||||||
|
{
|
||||||
|
client.StartedTestConnectionTime = Time.Current;
|
||||||
|
TestConnection(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client.LastConnectionTime + TimeOutTime / 6 <= Time.Current && client.ConncetionTryCount == 1)
|
||||||
|
TestConnection(client);
|
||||||
|
|
||||||
|
if (client.LastConnectionTime + TimeOutTime / 3 <= Time.Current && client.ConncetionTryCount == 2)
|
||||||
|
TestConnection(client);
|
||||||
|
|
||||||
|
if (client.StartedTestConnectionTime + TimeOutTime <= Time.Current)
|
||||||
|
{
|
||||||
|
ConnectingClients.Remove(client);
|
||||||
|
Logger.Log("Connection to a connecting client lost! - " + client.IP + ":" + client.Port, LoggingTarget.Network, LogLevel.Error);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (ClientInfo client in ConncetedClients)
|
||||||
|
{
|
||||||
|
if (client.LastConnectionTime + TimeOutTime / 6 <= Time.Current && client.ConncetionTryCount == 0)
|
||||||
|
{
|
||||||
|
client.StartedTestConnectionTime = Time.Current;
|
||||||
|
TestConnection(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client.LastConnectionTime + TimeOutTime / 3 <= Time.Current && client.ConncetionTryCount == 1)
|
||||||
|
TestConnection(client);
|
||||||
|
|
||||||
|
if (client.LastConnectionTime + TimeOutTime / 2 <= Time.Current && client.ConncetionTryCount == 2)
|
||||||
|
TestConnection(client);
|
||||||
|
|
||||||
|
if (client.StartedTestConnectionTime + TimeOutTime <= Time.Current)
|
||||||
|
{
|
||||||
|
ConncetedClients.Remove(client);
|
||||||
|
InGameClients.Remove(client);
|
||||||
|
LoadedClients.Remove(client);
|
||||||
|
InGameClients.Remove(client);
|
||||||
|
Logger.Log("Connection to a connected client lost! - " + client.IP + ":" + client.Port, LoggingTarget.Network, LogLevel.Error);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Poke!
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="clientInfo"></param>
|
||||||
|
protected void TestConnection(ClientInfo clientInfo)
|
||||||
|
{
|
||||||
|
clientInfo.ConncetionTryCount++;
|
||||||
|
NetworkingClient client = new NetworkingClient(true, clientInfo.IP, clientInfo.Port);
|
||||||
|
client.SendPacket(new BasicPacket(ClientInfo) { Test = true });
|
||||||
|
Logger.Log("Testing a client's connection - " + clientInfo.IP + ":" + clientInfo.Port, LoggingTarget.Network, LogLevel.Verbose);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RequestPlayerList()
|
||||||
|
{
|
||||||
|
BasicPacket packet = new BasicPacket(ClientInfo) { RequestPlayerList = true };
|
||||||
|
SendToHost(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tell peers to start loading game
|
||||||
|
/// </summary>
|
||||||
|
public virtual void StartLoadingGame()
|
||||||
|
{
|
||||||
|
if (SendClient == null)
|
||||||
|
{
|
||||||
|
BasicPacket packet = new BasicPacket(ClientInfo) { LoadGame = true };
|
||||||
|
|
||||||
|
foreach (ClientInfo client in InMatchClients)
|
||||||
|
packet.PlayerList.Add(client);
|
||||||
|
packet.PlayerList.Add(ClientInfo);
|
||||||
|
|
||||||
|
SendToInMatchClients(packet);
|
||||||
|
|
||||||
|
OnLoadGame?.Invoke(packet.PlayerList);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Logger.Log("Called StartLoadingGame - We are not the Host!", LoggingTarget.Network, LogLevel.Verbose);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Call this when the game is Loaded and ready to be started
|
||||||
|
/// </summary>
|
||||||
|
public virtual void GameLoaded()
|
||||||
|
{
|
||||||
|
Loaded = true;
|
||||||
|
SendToHost(new BasicPacket(ClientInfo) { Loaded = true });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Connects to the Host
|
||||||
|
/// </summary>
|
||||||
|
public virtual void ConnectToHost()
|
||||||
|
{
|
||||||
|
SendToHost(new BasicPacket(ClientInfo) { Connect = true });
|
||||||
|
Logger.Log("Attempting conection to Host. . .", LoggingTarget.Network, LogLevel.Verbose);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tell peers to start and starts ours
|
||||||
|
/// </summary>
|
||||||
|
public virtual void SendStartGame()
|
||||||
|
{
|
||||||
|
if (SendClient == null)
|
||||||
|
{
|
||||||
|
SendToLoadedClients(new BasicPacket(ClientInfo) { StartGame = true });
|
||||||
|
InGame = true;
|
||||||
|
Logger.Log("Sending Start Game", LoggingTarget.Network, LogLevel.Verbose);
|
||||||
|
}
|
||||||
|
StartGame?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send a Packet to the Host
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="packet"></param>
|
||||||
|
public void SendToHost(Packet packet)
|
||||||
|
{
|
||||||
|
if (SendClient != null)
|
||||||
|
SendClient.SendPacket(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send a Packet to all Connecting clients
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="packet"></param>
|
||||||
|
public void SendToConnectingClients(Packet packet)
|
||||||
|
{
|
||||||
|
if (SendClient == null)
|
||||||
|
foreach (ClientInfo clientInfo in ConnectingClients)
|
||||||
|
{
|
||||||
|
NetworkingClient client = new NetworkingClient(true, clientInfo.IP, clientInfo.Port);
|
||||||
|
client.SendPacket(packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send a Packet to all clients Connected and waiting
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="packet"></param>
|
||||||
|
public void SendToConnectedClients(Packet packet)
|
||||||
|
{
|
||||||
|
if (SendClient == null)
|
||||||
|
foreach (ClientInfo clientInfo in ConncetedClients)
|
||||||
|
{
|
||||||
|
NetworkingClient client = new NetworkingClient(true, clientInfo.IP, clientInfo.Port);
|
||||||
|
client.SendPacket(packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send a Packet to all clients In this Match
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="packet"></param>
|
||||||
|
public void SendToInMatchClients(Packet packet)
|
||||||
|
{
|
||||||
|
if (SendClient == null)
|
||||||
|
foreach (ClientInfo clientInfo in InMatchClients)
|
||||||
|
{
|
||||||
|
NetworkingClient client = new NetworkingClient(true, clientInfo.IP, clientInfo.Port);
|
||||||
|
client.SendPacket(packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send a Packet to all clients Loaded
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="packet"></param>
|
||||||
|
public void SendToLoadedClients(Packet packet)
|
||||||
|
{
|
||||||
|
if (SendClient == null)
|
||||||
|
foreach (ClientInfo clientInfo in LoadedClients)
|
||||||
|
{
|
||||||
|
NetworkingClient client = new NetworkingClient(true, clientInfo.IP, clientInfo.Port);
|
||||||
|
client.SendPacket(packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send a Packet to all clients InGame
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="packet"></param>
|
||||||
|
public void SendToInGameClients(Packet packet)
|
||||||
|
{
|
||||||
|
if (SendClient == null)
|
||||||
|
foreach (ClientInfo clientInfo in InGameClients)
|
||||||
|
{
|
||||||
|
NetworkingClient client = new NetworkingClient(true, clientInfo.IP, clientInfo.Port);
|
||||||
|
client.SendPacket(packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send a Packet to ALL clients we know
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="packet"></param>
|
||||||
|
public void SendToAllClients(Packet packet)
|
||||||
|
{
|
||||||
|
if (SendClient == null)
|
||||||
|
{
|
||||||
|
SendToConnectingClients(packet);
|
||||||
|
SendToConnectedClients(packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send tto all but the one that sent it
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="packet"></param>
|
||||||
|
/// <param name="playerID"></param>
|
||||||
|
public void ShareWithOtherPeers(Packet packet)
|
||||||
|
{
|
||||||
|
if (SendClient == null)
|
||||||
|
foreach (ClientInfo clientInfo in ConncetedClients)
|
||||||
|
if (packet.ClientInfo.IP != clientInfo.IP)
|
||||||
|
{
|
||||||
|
NetworkingClient client = new NetworkingClient(true, clientInfo.IP, clientInfo.Port);
|
||||||
|
client.SendPacket(packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void AbortGame()
|
||||||
|
{
|
||||||
|
SendToLoadedClients(new BasicPacket(ClientInfo) { Abort = true });
|
||||||
|
SendToInGameClients(new BasicPacket(ClientInfo) { Abort = true });
|
||||||
|
|
||||||
|
restart:
|
||||||
|
foreach (ClientInfo client in LoadedClients)
|
||||||
|
{
|
||||||
|
LoadedClients.Remove(client);
|
||||||
|
InMatchClients.Add(client);
|
||||||
|
goto restart;
|
||||||
|
}
|
||||||
|
foreach (ClientInfo client in InGameClients)
|
||||||
|
{
|
||||||
|
InGameClients.Remove(client);
|
||||||
|
InMatchClients.Add(client);
|
||||||
|
goto restart;
|
||||||
|
}
|
||||||
|
|
||||||
|
InGame = false;
|
||||||
|
Loaded = false;
|
||||||
|
|
||||||
|
OnAbort?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Disconnect()
|
||||||
|
{
|
||||||
|
Packet packet = new BasicPacket(ClientInfo) { Disconnect = true };
|
||||||
|
|
||||||
|
OnAbort?.Invoke();
|
||||||
|
InMatch = false;
|
||||||
|
InGame = false;
|
||||||
|
Loaded = false;
|
||||||
|
|
||||||
|
if (SendClient == null)
|
||||||
|
{
|
||||||
|
SendToConnectingClients(packet);
|
||||||
|
SendToConnectedClients(packet);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
SendToHost(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Die
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="isDisposing"></param>
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
ReceiveClient?.Clear();
|
||||||
|
|
||||||
|
if (SendClient != null)
|
||||||
|
{
|
||||||
|
SendToHost(new BasicPacket(ClientInfo) { Disconnect = true });
|
||||||
|
SendClient.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ClientType
|
||||||
|
{
|
||||||
|
Host,
|
||||||
|
Peer,
|
||||||
|
Server
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Symcol.Core.Networking
|
||||||
|
{
|
||||||
|
[Serializable]
|
||||||
|
public class Packet
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Just a Signature
|
||||||
|
/// </summary>
|
||||||
|
public readonly ClientInfo ClientInfo;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specify starting size of packet for efficiency
|
||||||
|
/// </summary>
|
||||||
|
public virtual int PacketSize => 1024;
|
||||||
|
|
||||||
|
public Packet(ClientInfo clientInfo)
|
||||||
|
{
|
||||||
|
ClientInfo = clientInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
// General Information about an assembly is controlled through the following
|
||||||
|
// set of attributes. Change these attribute values to modify the information
|
||||||
|
// associated with an assembly.
|
||||||
|
[assembly: AssemblyTitle("symcol.Toys")]
|
||||||
|
[assembly: AssemblyDescription("")]
|
||||||
|
[assembly: AssemblyConfiguration("")]
|
||||||
|
[assembly: AssemblyCompany("")]
|
||||||
|
[assembly: AssemblyProduct("symcol.Toys")]
|
||||||
|
[assembly: AssemblyCopyright("Copyright © 2018")]
|
||||||
|
[assembly: AssemblyTrademark("")]
|
||||||
|
[assembly: AssemblyCulture("")]
|
||||||
|
|
||||||
|
// Setting ComVisible to false makes the types in this assembly not visible
|
||||||
|
// to COM components. If you need to access a type in this assembly from
|
||||||
|
// COM, set the ComVisible attribute to true on that type.
|
||||||
|
[assembly: ComVisible(false)]
|
||||||
|
|
||||||
|
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||||
|
[assembly: Guid("f34ac16c-e590-4d70-a069-a748326852bf")]
|
||||||
|
|
||||||
|
// Version information for an assembly consists of the following four values:
|
||||||
|
//
|
||||||
|
// Major Version
|
||||||
|
// Minor Version
|
||||||
|
// Build Number
|
||||||
|
// Revision
|
||||||
|
//
|
||||||
|
// You can specify all the values or you can default the Build and Revision Numbers
|
||||||
|
// by using the '*' as shown below:
|
||||||
|
// [assembly: AssemblyVersion("1.0.*")]
|
||||||
|
[assembly: AssemblyVersion("1.0.0.0")]
|
||||||
|
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||||
|
<PropertyGroup>
|
||||||
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
|
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||||
|
<ProjectGuid>{F34AC16C-E590-4D70-A069-A748326852BF}</ProjectGuid>
|
||||||
|
<OutputType>Library</OutputType>
|
||||||
|
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||||
|
<RootNamespace>Symcol.Core</RootNamespace>
|
||||||
|
<AssemblyName>Symcol.Core</AssemblyName>
|
||||||
|
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
|
||||||
|
<FileAlignment>512</FileAlignment>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
<DebugType>full</DebugType>
|
||||||
|
<Optimize>false</Optimize>
|
||||||
|
<OutputPath>bin\Debug\</OutputPath>
|
||||||
|
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||||
|
<DebugType>pdbonly</DebugType>
|
||||||
|
<Optimize>true</Optimize>
|
||||||
|
<OutputPath>bin\Release\</OutputPath>
|
||||||
|
<DefineConstants>TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="Cyotek.Drawing.BitmapFont, Version=1.0.0.0, Culture=neutral, PublicKeyToken=58daa28b0b2de221, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\packages\Cyotek.Drawing.BitmapFont.1.3.4-beta1\lib\net46\Cyotek.Drawing.BitmapFont.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="ManagedBass, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\packages\ManagedBass.2.0.3\lib\net45\ManagedBass.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="mscorlib" />
|
||||||
|
<Reference Include="Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||||
|
<HintPath>$(SolutionDir)\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||||
|
<Private>True</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="nunit.framework, Version=3.8.1.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
|
||||||
|
<HintPath>$(SolutionDir)\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll</HintPath>
|
||||||
|
<Private>True</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="OpenTK, Version=3.0.0.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4, processorArchitecture=MSIL">
|
||||||
|
<HintPath>$(SolutionDir)\packages\OpenTK.3.0.0-git00009\lib\net20\OpenTK.dll</HintPath>
|
||||||
|
<Private>True</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="SQLite.Net, Version=3.1.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||||
|
<HintPath>$(SolutionDir)\packages\SQLite.Net.Core-PCL.3.1.1\lib\portable-win8+net45+wp8+wpa81+MonoAndroid1+MonoTouch1\SQLite.Net.dll</HintPath>
|
||||||
|
<Private>True</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="SQLite.Net.Platform.Generic, Version=3.1.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||||
|
<HintPath>$(SolutionDir)\packages\SQLite.Net-PCL.3.1.1\lib\net40\SQLite.Net.Platform.Generic.dll</HintPath>
|
||||||
|
<Private>True</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="SQLite.Net.Platform.Win32, Version=3.1.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||||
|
<HintPath>$(SolutionDir)\packages\SQLite.Net-PCL.3.1.1\lib\net4\SQLite.Net.Platform.Win32.dll</HintPath>
|
||||||
|
<Private>True</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="SQLiteNetExtensions, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||||
|
<HintPath>$(SolutionDir)\packages\SQLiteNetExtensions.1.3.0\lib\portable-net45+netcore45+wpa81+wp8+MonoAndroid1+MonoTouch1\SQLiteNetExtensions.dll</HintPath>
|
||||||
|
<Private>True</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System" />
|
||||||
|
<Reference Include="System.Drawing" />
|
||||||
|
<Reference Include="System.Net.Http" />
|
||||||
|
<Reference Include="System.Windows.Forms" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="GameObjects\SymcolHitbox.cs" />
|
||||||
|
<Compile Include="Graphics\Containers\SymcolClickableContainer.cs" />
|
||||||
|
<Compile Include="Graphics\Containers\SymcolDialContainer.cs" />
|
||||||
|
<Compile Include="Graphics\Containers\SymcolDragContainer.cs" />
|
||||||
|
<Compile Include="Graphics\Containers\SymcolContainer.cs" />
|
||||||
|
<Compile Include="Graphics\UserInterface\SpriteButton.cs" />
|
||||||
|
<Compile Include="Graphics\UserInterface\SymcolWindow.cs" />
|
||||||
|
<Compile Include="Networking\BasicPacket.cs" />
|
||||||
|
<Compile Include="Networking\ClientInfo.cs" />
|
||||||
|
<Compile Include="Networking\NetworkingClient.cs" />
|
||||||
|
<Compile Include="Networking\Packet.cs" />
|
||||||
|
<Compile Include="Networking\NetworkingClientHandler.cs" />
|
||||||
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\osu-Framework\osu.Framework\osu.Framework.csproj">
|
||||||
|
<Project>{c76bf5b3-985e-4d39-95fe-97c9c879b83a}</Project>
|
||||||
|
<Name>osu.Framework</Name>
|
||||||
|
</ProjectReference>
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Cursor;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace Symcol.Rulesets.Core.Containers
|
||||||
|
{
|
||||||
|
public class LinkText : OsuSpriteText, IHasTooltip
|
||||||
|
{
|
||||||
|
public string TooltipText => Tooltip;
|
||||||
|
|
||||||
|
public virtual string Tooltip => "";
|
||||||
|
|
||||||
|
private readonly OsuHoverContainer content;
|
||||||
|
|
||||||
|
public override bool HandleKeyboardInput => content.Action != null;
|
||||||
|
public override bool HandleMouseInput => content.Action != null;
|
||||||
|
|
||||||
|
protected override Container<Drawable> Content => content ?? (Container<Drawable>)this;
|
||||||
|
|
||||||
|
public override IEnumerable<Drawable> FlowingChildren => Children;
|
||||||
|
|
||||||
|
public string Url
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value != null)
|
||||||
|
content.Action = () => Process.Start(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public LinkText()
|
||||||
|
{
|
||||||
|
AddInternal(content = new OsuHoverContainer
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using osu.Game.Users;
|
||||||
|
|
||||||
|
namespace Symcol.Rulesets.Core.Containers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// TODO: make this more generic
|
||||||
|
/// </summary>
|
||||||
|
public class ProfileLink : LinkText
|
||||||
|
{
|
||||||
|
public override string Tooltip => "View profile in browser";
|
||||||
|
|
||||||
|
public ProfileLink(User user, bool maintainer = false)
|
||||||
|
{
|
||||||
|
if (!maintainer)
|
||||||
|
Text = "Ruleset Creator: " + user.Username;
|
||||||
|
else
|
||||||
|
Text = "Ruleset Maintainer: " + user.Username;
|
||||||
|
|
||||||
|
Url = $@"https://osu.ppy.sh/users/{user.Id}";
|
||||||
|
Font = @"Exo2.0-RegularItalic";
|
||||||
|
TextSize = 20;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Symcol.Rulesets.Core.HitObjects
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Mostly stuff copied from Container
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TObject"></typeparam>
|
||||||
|
public abstract class DrawableSymcolHitObject<TObject> : DrawableHitObject<TObject>
|
||||||
|
where TObject : HitObject
|
||||||
|
{
|
||||||
|
protected virtual Container<Drawable> Content => new Container();
|
||||||
|
|
||||||
|
public IReadOnlyList<Drawable> Children
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return InternalChildren;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
ChildrenEnumerable = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<Drawable> ChildrenEnumerable
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
Clear();
|
||||||
|
AddRange(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddRange(IEnumerable<Drawable> range)
|
||||||
|
{
|
||||||
|
foreach (Drawable d in range)
|
||||||
|
Add(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Drawable Child
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (Children.Count != 1)
|
||||||
|
throw new InvalidOperationException($"{nameof(Child)} is only available when there's only 1 in {nameof(Children)}!");
|
||||||
|
|
||||||
|
return Children[0];
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
Clear();
|
||||||
|
Add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear() => Clear(true);
|
||||||
|
|
||||||
|
public virtual void Clear(bool disposeChildren)
|
||||||
|
{
|
||||||
|
if (Content != null)
|
||||||
|
Content.Clear(disposeChildren);
|
||||||
|
else
|
||||||
|
ClearInternal(disposeChildren);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected DrawableSymcolHitObject(TObject hitObject)
|
||||||
|
: base(hitObject)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(Drawable drawable)
|
||||||
|
{
|
||||||
|
AddInternal(drawable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Remove(Drawable drawable)
|
||||||
|
{
|
||||||
|
RemoveInternal(drawable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
using Symcol.Core.Networking;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Symcol.Rulesets.Core.Multiplayer.Networking
|
||||||
|
{
|
||||||
|
[Serializable]
|
||||||
|
public class ChatPacket : Packet
|
||||||
|
{
|
||||||
|
public override int PacketSize => 4096;
|
||||||
|
|
||||||
|
public string Author;
|
||||||
|
|
||||||
|
public string AuthorColor;
|
||||||
|
|
||||||
|
public string Message;
|
||||||
|
|
||||||
|
public ChatPacket(ClientInfo clientInfo) : base(clientInfo)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using Symcol.Core.Networking;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Symcol.Rulesets.Core.Multiplayer.Networking
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Just a client signature basically
|
||||||
|
/// </summary>
|
||||||
|
[Serializable]
|
||||||
|
public class RulesetClientInfo : ClientInfo
|
||||||
|
{
|
||||||
|
public string Username = "";
|
||||||
|
|
||||||
|
public int UserID = -1;
|
||||||
|
|
||||||
|
public string UserPic;
|
||||||
|
|
||||||
|
public string UserBackground;
|
||||||
|
|
||||||
|
public string UserCountry;
|
||||||
|
|
||||||
|
public string CountryFlagName;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Game;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Online.API;
|
||||||
|
using Symcol.Core.Networking;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Symcol.Rulesets.Core.Multiplayer.Networking
|
||||||
|
{
|
||||||
|
//TODO: This NEEDS its own clock to avoid fuckery later on with DoubleTime and HalfTime
|
||||||
|
public class RulesetNetworkingClientHandler : NetworkingClientHandler, IOnlineComponent
|
||||||
|
{
|
||||||
|
public RulesetClientInfo RulesetClientInfo;
|
||||||
|
|
||||||
|
public Action<WorkingBeatmap> OnMapChange;
|
||||||
|
|
||||||
|
private OsuGame osu;
|
||||||
|
|
||||||
|
public RulesetNetworkingClientHandler(ClientType type, string ip, int port = 25570, string thisLocalIp = "0.0.0.0") : base(type, ip, port, thisLocalIp)
|
||||||
|
{
|
||||||
|
if (RulesetClientInfo == null)
|
||||||
|
{
|
||||||
|
RulesetClientInfo = new RulesetClientInfo
|
||||||
|
{
|
||||||
|
Port = port
|
||||||
|
};
|
||||||
|
|
||||||
|
ClientInfo = RulesetClientInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send Map to Peers
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="map"></param>
|
||||||
|
public void SetMap(WorkingBeatmap map)
|
||||||
|
{
|
||||||
|
RulesetPacket packet;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
packet = new RulesetPacket(RulesetClientInfo)
|
||||||
|
{
|
||||||
|
OnlineBeatmapSetID = (int)map.BeatmapSetInfo.OnlineBeatmapSetID,
|
||||||
|
OnlineBeatmapID = (int)map.BeatmapInfo.OnlineBeatmapID
|
||||||
|
};
|
||||||
|
SendToInMatchClients(packet);
|
||||||
|
OnMapChange?.Invoke(osu.Beatmap.Value);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
packet = new RulesetPacket(RulesetClientInfo);
|
||||||
|
SendToInMatchClients(packet);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(APIAccess api, OsuGame osu)
|
||||||
|
{
|
||||||
|
api.Register(this);
|
||||||
|
this.osu = osu;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void APIStateChanged(APIAccess api, APIState state)
|
||||||
|
{
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
default:
|
||||||
|
RulesetClientInfo.Username = "";
|
||||||
|
RulesetClientInfo.UserID = -1;
|
||||||
|
break;
|
||||||
|
case APIState.Online:
|
||||||
|
RulesetClientInfo.Username = api.LocalUser.Value.Username;
|
||||||
|
RulesetClientInfo.UserID = (int)api.LocalUser.Value.Id;
|
||||||
|
RulesetClientInfo.UserCountry = api.LocalUser.Value.Country.FullName;
|
||||||
|
RulesetClientInfo.CountryFlagName = api.LocalUser.Value.Country.FlagName;
|
||||||
|
RulesetClientInfo.UserPic = api.LocalUser.Value.AvatarUrl;
|
||||||
|
RulesetClientInfo.UserBackground = api.LocalUser.Value.CoverUrl;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using Symcol.Core.Networking;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Symcol.Rulesets.Core.Multiplayer.Networking
|
||||||
|
{
|
||||||
|
[Serializable]
|
||||||
|
public class RulesetPacket : Packet
|
||||||
|
{
|
||||||
|
public new readonly RulesetClientInfo ClientInfo;
|
||||||
|
|
||||||
|
public override int PacketSize => 4096;
|
||||||
|
|
||||||
|
public int OnlineBeatmapSetID = -1;
|
||||||
|
|
||||||
|
public int OnlineBeatmapID = -1;
|
||||||
|
|
||||||
|
public bool HaveMap;
|
||||||
|
|
||||||
|
public string ChatContent;
|
||||||
|
|
||||||
|
//public string RulesetName = "";
|
||||||
|
|
||||||
|
public RulesetPacket(RulesetClientInfo rulesetClientInfo) : base(rulesetClientInfo)
|
||||||
|
{
|
||||||
|
ClientInfo = rulesetClientInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
using osu.Framework.Configuration;
|
||||||
|
using osu.Game.Overlays.Settings;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
|
||||||
|
namespace Symcol.Rulesets.Core.Multiplayer.Options
|
||||||
|
{
|
||||||
|
public class MultiplayerDropdownEnumOption<T> : MultiplayerOption
|
||||||
|
where T : struct
|
||||||
|
{
|
||||||
|
public readonly Bindable<T> BindableEnum;
|
||||||
|
|
||||||
|
public MultiplayerDropdownEnumOption(Bindable<T> bindable, string name, int quadrant, bool sync = true) : base(name, quadrant, sync)
|
||||||
|
{
|
||||||
|
BindableEnum = bindable;
|
||||||
|
|
||||||
|
OptionContainer.Child = new BetterSettingsEnumDropdown<T>
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopLeft,
|
||||||
|
Origin = Anchor.TopLeft,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Bindable = bindable,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private class BetterSettingsEnumDropdown<T> : SettingsEnumDropdown<T>
|
||||||
|
{
|
||||||
|
protected override Drawable CreateControl() => new BetterOsuEnumDropdown<T>
|
||||||
|
{
|
||||||
|
Margin = new MarginPadding { Top = 5 },
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
};
|
||||||
|
|
||||||
|
private class BetterOsuEnumDropdown<T> : OsuEnumDropdown<T>
|
||||||
|
{
|
||||||
|
public BetterOsuEnumDropdown()
|
||||||
|
{
|
||||||
|
Menu.MaxHeight = 160;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
using OpenTK;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Symcol.Rulesets.Core.Multiplayer.Options
|
||||||
|
{
|
||||||
|
public abstract class MultiplayerOption : Container
|
||||||
|
{
|
||||||
|
protected readonly SpriteText Title;
|
||||||
|
|
||||||
|
protected readonly Container OptionContainer;
|
||||||
|
|
||||||
|
public MultiplayerOption(string name, int quadrant, bool sync = true)
|
||||||
|
{
|
||||||
|
if (quadrant == 1 | quadrant == 3 | quadrant == 5 | quadrant == 7)
|
||||||
|
{
|
||||||
|
switch (quadrant)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
quadrant = 0;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
quadrant = 1;
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
quadrant = 2;
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
quadrant = 3;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Anchor = Anchor.TopLeft;
|
||||||
|
Origin = Anchor.TopLeft;
|
||||||
|
Position = new Vector2(16, 4 + (64 * quadrant));
|
||||||
|
}
|
||||||
|
else if (quadrant == 2 | quadrant == 4 | quadrant == 6 | quadrant == 8)
|
||||||
|
{
|
||||||
|
switch (quadrant)
|
||||||
|
{
|
||||||
|
case 2:
|
||||||
|
quadrant = 0;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
quadrant = 1;
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
quadrant = 2;
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
quadrant = 3;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Anchor = Anchor.TopCentre;
|
||||||
|
Origin = Anchor.TopLeft;
|
||||||
|
Position = new Vector2(22, 4 + (64 * quadrant));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
throw new Exception("Globglogabgalab");
|
||||||
|
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
Width = 0.49f;
|
||||||
|
Height = 80;
|
||||||
|
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
Title = new SpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopLeft,
|
||||||
|
Origin = Anchor.TopLeft,
|
||||||
|
TextSize = 20,
|
||||||
|
Text = name
|
||||||
|
},
|
||||||
|
OptionContainer = new Container
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopLeft,
|
||||||
|
Origin = Anchor.TopLeft,
|
||||||
|
Position = new Vector2(-16, 18),
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
using osu.Framework.Configuration;
|
||||||
|
using osu.Game.Overlays.Settings;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using OpenTK;
|
||||||
|
|
||||||
|
namespace Symcol.Rulesets.Core.Multiplayer.Options
|
||||||
|
{
|
||||||
|
public class MultiplayerToggleOption : MultiplayerOption
|
||||||
|
{
|
||||||
|
public readonly Bindable<bool> BindableBool;
|
||||||
|
|
||||||
|
public MultiplayerToggleOption(Bindable<bool> bindable, string name, int quadrant, bool sync = true) : base(name, quadrant, sync)
|
||||||
|
{
|
||||||
|
BindableBool = bindable;
|
||||||
|
|
||||||
|
Child = new SettingsCheckbox
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopLeft,
|
||||||
|
Origin = Anchor.TopLeft,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Bindable = bindable,
|
||||||
|
LabelText = " " + name,
|
||||||
|
Position = new Vector2(-16, 18),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
using OpenTK;
|
||||||
|
using OpenTK.Graphics;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Logging;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Online.API;
|
||||||
|
using osu.Game.Users;
|
||||||
|
using Symcol.Core.Networking;
|
||||||
|
using Symcol.Rulesets.Core.Multiplayer.Networking;
|
||||||
|
|
||||||
|
namespace Symcol.Rulesets.Core.Multiplayer.Pieces
|
||||||
|
{
|
||||||
|
public class Chat : Container, IOnlineComponent
|
||||||
|
{
|
||||||
|
private readonly RulesetNetworkingClientHandler rulesetNetworkingClientHandler;
|
||||||
|
|
||||||
|
private string playerColorHex = SymcolSettingsSubsection.SymcolConfigManager.GetBindable<string>(SymcolSetting.PlayerColor);
|
||||||
|
|
||||||
|
private User user;
|
||||||
|
|
||||||
|
private readonly FillFlowContainer<ChatMessage> messageContainer;
|
||||||
|
private readonly OsuTextBox textBox;
|
||||||
|
|
||||||
|
public Chat(RulesetNetworkingClientHandler rulesetNetworkingClientHandler)
|
||||||
|
{
|
||||||
|
this.rulesetNetworkingClientHandler = rulesetNetworkingClientHandler;
|
||||||
|
|
||||||
|
rulesetNetworkingClientHandler.OnPacketReceive += (packet) =>
|
||||||
|
{
|
||||||
|
if (packet is ChatPacket chatPacket)
|
||||||
|
Add(chatPacket);
|
||||||
|
if (rulesetNetworkingClientHandler.ClientType == ClientType.Host)
|
||||||
|
rulesetNetworkingClientHandler.ShareWithOtherPeers(packet);
|
||||||
|
};
|
||||||
|
|
||||||
|
Anchor = Anchor.BottomCentre;
|
||||||
|
Origin = Anchor.BottomCentre;
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
Height = 0.46f;
|
||||||
|
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = Color4.Black,
|
||||||
|
Alpha = 0.8f
|
||||||
|
},
|
||||||
|
new OsuScrollContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Height = 0.9f,
|
||||||
|
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
messageContainer = new FillFlowContainer<ChatMessage>
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
textBox = new OsuTextBox
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomCentre,
|
||||||
|
Origin = Anchor.BottomCentre,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Width = 0.98f,
|
||||||
|
Height = 36,
|
||||||
|
Position = new Vector2(0, -12),
|
||||||
|
Colour = Color4.White,
|
||||||
|
Text = "Type here!"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
textBox.OnCommit += (s, r) =>
|
||||||
|
{
|
||||||
|
AddMessage(textBox.Text);
|
||||||
|
textBox.Text = "";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(ChatPacket packet)
|
||||||
|
{
|
||||||
|
ChatMessage message = new ChatMessage(packet);
|
||||||
|
messageContainer.Add(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddMessage(string message)
|
||||||
|
{
|
||||||
|
if (message == "" | message == " ")
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (user != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
OsuColour.FromHex(playerColorHex);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
playerColorHex = "#ffffff";
|
||||||
|
}
|
||||||
|
|
||||||
|
ChatPacket packet = new ChatPacket(rulesetNetworkingClientHandler.ClientInfo)
|
||||||
|
{
|
||||||
|
Author = user.Username,
|
||||||
|
AuthorColor = playerColorHex,
|
||||||
|
Message = message,
|
||||||
|
};
|
||||||
|
|
||||||
|
rulesetNetworkingClientHandler.SendToHost(packet);
|
||||||
|
rulesetNetworkingClientHandler.SendToInMatchClients(packet);
|
||||||
|
Add(packet);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Logger.Log("You must be logged in to message!", LoggingTarget.Network, LogLevel.Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(APIAccess api)
|
||||||
|
{
|
||||||
|
api.Register(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void APIStateChanged(APIAccess api, APIState state)
|
||||||
|
{
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
default:
|
||||||
|
user = null;
|
||||||
|
break;
|
||||||
|
case APIState.Online:
|
||||||
|
user = api.LocalUser.Value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using Symcol.Rulesets.Core.Multiplayer.Networking;
|
||||||
|
|
||||||
|
namespace Symcol.Rulesets.Core.Multiplayer.Pieces
|
||||||
|
{
|
||||||
|
public class ChatMessage : Container
|
||||||
|
{
|
||||||
|
public ChatMessage(ChatPacket packet)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopLeft;
|
||||||
|
Origin = Anchor.TopLeft;
|
||||||
|
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopLeft,
|
||||||
|
Origin = Anchor.TopLeft,
|
||||||
|
Colour = OsuColour.FromHex(packet.AuthorColor),
|
||||||
|
TextSize = 24,
|
||||||
|
Text = packet.Author + ":"
|
||||||
|
},
|
||||||
|
new OsuTextFlowContainer(t => { t.TextSize = 24; })
|
||||||
|
{
|
||||||
|
Position = new OpenTK.Vector2(140, 0),
|
||||||
|
Anchor = Anchor.TopLeft,
|
||||||
|
Origin = Anchor.TopLeft,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Text = packet.Message
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
using OpenTK;
|
||||||
|
using OpenTK.Graphics;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Cursor;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using osu.Framework.Input;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Users;
|
||||||
|
using Symcol.Rulesets.Core.Multiplayer.Networking;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace Symcol.Rulesets.Core.Multiplayer.Pieces
|
||||||
|
{
|
||||||
|
public class MatchPlayer : ClickableContainer, IHasContextMenu
|
||||||
|
{
|
||||||
|
public readonly RulesetClientInfo ClientInfo;
|
||||||
|
|
||||||
|
private readonly Box dim;
|
||||||
|
|
||||||
|
private readonly DrawableFlag countryFlag;
|
||||||
|
private readonly UserCoverBackground profileBackground;
|
||||||
|
private readonly UpdateableAvatar profilePicture;
|
||||||
|
|
||||||
|
public MatchPlayer(RulesetClientInfo clientInfo)
|
||||||
|
{
|
||||||
|
ClientInfo = clientInfo;
|
||||||
|
|
||||||
|
Alpha = 0;
|
||||||
|
Masking = true;
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
Height = 40f;
|
||||||
|
CornerRadius = 10;
|
||||||
|
|
||||||
|
Country country = new Country
|
||||||
|
{
|
||||||
|
FullName = ClientInfo.UserCountry,
|
||||||
|
FlagName = ClientInfo.CountryFlagName,
|
||||||
|
};
|
||||||
|
|
||||||
|
User user = new User
|
||||||
|
{
|
||||||
|
Username = ClientInfo.Username,
|
||||||
|
Id = ClientInfo.UserID,
|
||||||
|
Country = country,
|
||||||
|
AvatarUrl = ClientInfo.UserPic,
|
||||||
|
CoverUrl = ClientInfo.UserBackground,
|
||||||
|
};
|
||||||
|
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
profileBackground = new UserCoverBackground(user)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
FillMode = FillMode.Fill,
|
||||||
|
OnLoadComplete = d => d.FadeInFromZero(200),
|
||||||
|
},
|
||||||
|
dim = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = Color4.Black,
|
||||||
|
Alpha = 0.8f
|
||||||
|
},
|
||||||
|
profilePicture = new UpdateableAvatar
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Size = new Vector2(Height * 0.8f),
|
||||||
|
Position = new Vector2(6, 0),
|
||||||
|
User = user,
|
||||||
|
Masking = true,
|
||||||
|
CornerRadius = 6,
|
||||||
|
},
|
||||||
|
countryFlag = new DrawableFlag(country)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreRight,
|
||||||
|
Origin = Anchor.CentreRight,
|
||||||
|
Size = new Vector2(Height * 0.9f, (Height * 0.9f) * 0.66f),
|
||||||
|
Position = new Vector2(-10, 0)
|
||||||
|
},
|
||||||
|
new SpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Position = new Vector2(Height * 1.1f, 0),
|
||||||
|
TextSize = Height * 0.9f,
|
||||||
|
Text = user.Username
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Action = () =>
|
||||||
|
{
|
||||||
|
Process.Start("https://osu.ppy.sh/users/" + user.Id);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnHover(InputState state)
|
||||||
|
{
|
||||||
|
dim.FadeTo(0.6f, 200);
|
||||||
|
|
||||||
|
return base.OnHover(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnHoverLost(InputState state)
|
||||||
|
{
|
||||||
|
base.OnHoverLost(state);
|
||||||
|
|
||||||
|
dim.FadeTo(0.8f, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MenuItem[] ContextMenuItems => new MenuItem[]
|
||||||
|
{
|
||||||
|
new OsuMenuItem("View Profile", MenuItemType.Standard, () => { }),
|
||||||
|
new OsuMenuItem("Promote to Host", MenuItemType.Highlighted, () => { }),
|
||||||
|
new OsuMenuItem("Kick", MenuItemType.Destructive, () => { }),
|
||||||
|
new OsuMenuItem("Ban", MenuItemType.Destructive, () => { }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
using OpenTK.Graphics;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using OpenTK;
|
||||||
|
using Symcol.Core.Networking;
|
||||||
|
using Symcol.Rulesets.Core.Multiplayer.Networking;
|
||||||
|
|
||||||
|
namespace Symcol.Rulesets.Core.Multiplayer.Pieces
|
||||||
|
{
|
||||||
|
public class MatchPlayerList : Container
|
||||||
|
{
|
||||||
|
private readonly RulesetNetworkingClientHandler rulesetNetworkingClientHandler;
|
||||||
|
|
||||||
|
public readonly List<MatchPlayer> MatchPlayers = new List<MatchPlayer>();
|
||||||
|
|
||||||
|
public readonly FillFlowContainer MatchPlayersContianer;
|
||||||
|
|
||||||
|
public MatchPlayerList(RulesetNetworkingClientHandler rulesetNetworkingClientHandler)
|
||||||
|
{
|
||||||
|
this.rulesetNetworkingClientHandler = rulesetNetworkingClientHandler;
|
||||||
|
|
||||||
|
Masking = true;
|
||||||
|
CornerRadius = 16;
|
||||||
|
Anchor = Anchor.TopLeft;
|
||||||
|
Origin = Anchor.TopLeft;
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
Width = 0.49f;
|
||||||
|
Height = 0.45f;
|
||||||
|
Position = new Vector2(10);
|
||||||
|
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = Color4.Black.Opacity(0.8f)
|
||||||
|
},
|
||||||
|
MatchPlayersContianer = new FillFlowContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Width = 0.98f,
|
||||||
|
Height = 0.96f
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
rulesetNetworkingClientHandler.OnReceivePlayerList += (players) =>
|
||||||
|
{
|
||||||
|
restart:
|
||||||
|
foreach (MatchPlayer matchPlayer in MatchPlayers)
|
||||||
|
foreach (ClientInfo clientInfo in players)
|
||||||
|
if (clientInfo is RulesetClientInfo rulesetClientInfo)
|
||||||
|
if (rulesetClientInfo.IP + rulesetClientInfo.Port != matchPlayer.ClientInfo.IP + matchPlayer.ClientInfo.Port)
|
||||||
|
{
|
||||||
|
Add(rulesetClientInfo);
|
||||||
|
players.Remove(clientInfo);
|
||||||
|
goto restart;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
rulesetNetworkingClientHandler.RequestPlayerList();
|
||||||
|
|
||||||
|
rulesetNetworkingClientHandler.OnClientJoin += (clientInfo) =>
|
||||||
|
{
|
||||||
|
foreach (MatchPlayer matchPlayer in MatchPlayers)
|
||||||
|
if (clientInfo is RulesetClientInfo rulesetClientInfo)
|
||||||
|
if (rulesetClientInfo.IP + rulesetClientInfo.Port != matchPlayer.ClientInfo.IP + matchPlayer.ClientInfo.Port)
|
||||||
|
{
|
||||||
|
Add(rulesetClientInfo);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
rulesetNetworkingClientHandler.OnClientDisconnect += (clientInfo) =>
|
||||||
|
{
|
||||||
|
foreach (MatchPlayer matchPlayer in MatchPlayers)
|
||||||
|
if (clientInfo is RulesetClientInfo rulesetClientInfo)
|
||||||
|
if (rulesetClientInfo.IP + rulesetClientInfo.Port == matchPlayer.ClientInfo.IP + matchPlayer.ClientInfo.Port)
|
||||||
|
{
|
||||||
|
Remove(matchPlayer);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(RulesetClientInfo clientInfo)
|
||||||
|
{
|
||||||
|
MatchPlayer matchPlayer = new MatchPlayer(clientInfo);
|
||||||
|
|
||||||
|
Add(matchPlayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(MatchPlayer matchPlayer)
|
||||||
|
{
|
||||||
|
MatchPlayers.Add(matchPlayer);
|
||||||
|
MatchPlayersContianer.Add(matchPlayer);
|
||||||
|
matchPlayer.FadeInFromZero(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Remove(MatchPlayer matchPlayer)
|
||||||
|
{
|
||||||
|
MatchPlayers.Remove(matchPlayer);
|
||||||
|
matchPlayer.FadeOutFromOne(200)
|
||||||
|
.Expire();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,372 @@
|
|||||||
|
using OpenTK;
|
||||||
|
using OpenTK.Graphics;
|
||||||
|
using osu.Framework.Configuration;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Input;
|
||||||
|
using osu.Framework.MathUtils;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Overlays.Settings;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osu.Game.Screens.Symcol.Pieces;
|
||||||
|
using Symcol.Rulesets.Core.Multiplayer.Options;
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Symcol.Rulesets.Core.Multiplayer.Pieces
|
||||||
|
{
|
||||||
|
public class MatchTools : Container
|
||||||
|
{
|
||||||
|
public readonly Bindable<MatchScreenMode> Mode = new Bindable<MatchScreenMode>() { Default = MatchScreenMode.MapDetails };
|
||||||
|
|
||||||
|
public readonly Bindable<MatchGamemode> GameMode = new Bindable<MatchGamemode>() { Default = MatchGamemode.HeadToHead };
|
||||||
|
|
||||||
|
public readonly OsuTabControl<MatchScreenMode> TabControl;
|
||||||
|
|
||||||
|
public readonly Container SelectedContent;
|
||||||
|
|
||||||
|
public readonly Container MapDetails;
|
||||||
|
|
||||||
|
public Container RulesetSettings;
|
||||||
|
|
||||||
|
public readonly Container SoundBoard;
|
||||||
|
|
||||||
|
private WorkingBeatmap selectedBeatmap;
|
||||||
|
|
||||||
|
private int selectedBeatmapSetID;
|
||||||
|
|
||||||
|
public MatchTools()
|
||||||
|
{
|
||||||
|
Masking = true;
|
||||||
|
CornerRadius = 16;
|
||||||
|
Anchor = Anchor.TopRight;
|
||||||
|
Origin = Anchor.TopRight;
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
Width = 0.49f;
|
||||||
|
Height = 0.45f;
|
||||||
|
Position = new Vector2(-10, 10);
|
||||||
|
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = Color4.Black.Opacity(0.8f)
|
||||||
|
},
|
||||||
|
TabControl = new OsuTabControl<MatchScreenMode>
|
||||||
|
{
|
||||||
|
Position = new Vector2(36, 0),
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Height = 0.08f,
|
||||||
|
Width = 0.6f
|
||||||
|
},
|
||||||
|
SelectedContent = new Container
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomCentre,
|
||||||
|
Origin = Anchor.BottomCentre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Height = 0.92f
|
||||||
|
}
|
||||||
|
};
|
||||||
|
TabControl.Current.Value = MatchScreenMode.MapDetails;
|
||||||
|
|
||||||
|
Mode.ValueChanged += (value) =>
|
||||||
|
{
|
||||||
|
switch (value)
|
||||||
|
{
|
||||||
|
case MatchScreenMode.MapDetails:
|
||||||
|
if (selectedBeatmap != null)
|
||||||
|
SelectedContent.Child = new MapDetailsSection(selectedBeatmap);
|
||||||
|
else if (selectedBeatmapSetID != 0)
|
||||||
|
SelectedContent.Child = new MapDetailsSection(selectedBeatmapSetID);
|
||||||
|
else
|
||||||
|
SelectedContent.Child = new MapDetailsSection(true);
|
||||||
|
break;
|
||||||
|
case MatchScreenMode.MatchSettings:
|
||||||
|
SelectedContent.Child = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new MultiplayerDropdownEnumOption<MatchGamemode>(GameMode, "Match Gamemode", 1)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case MatchScreenMode.SoundBoard:
|
||||||
|
SelectedContent.Child = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Child = new HitSoundBoard
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
ButtonSize = 80
|
||||||
|
}
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Mode.BindTo(TabControl.Current);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void MapChange(WorkingBeatmap workingBeatmap)
|
||||||
|
{
|
||||||
|
if (workingBeatmap == null)
|
||||||
|
{
|
||||||
|
MapChange(-1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedBeatmap = workingBeatmap;
|
||||||
|
selectedBeatmapSetID = (int)workingBeatmap.BeatmapSetInfo.OnlineBeatmapSetID;
|
||||||
|
|
||||||
|
if (Mode.Value == MatchScreenMode.MapDetails)
|
||||||
|
SelectedContent.Child = new MapDetailsSection(selectedBeatmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void MapChange(int onlineBeatmapSetID)
|
||||||
|
{
|
||||||
|
selectedBeatmap = null;
|
||||||
|
selectedBeatmapSetID = onlineBeatmapSetID;
|
||||||
|
|
||||||
|
if (Mode.Value == MatchScreenMode.MapDetails)
|
||||||
|
{
|
||||||
|
if (selectedBeatmapSetID != 0 && selectedBeatmapSetID != -1)
|
||||||
|
SelectedContent.Child = new MapDetailsSection(selectedBeatmapSetID);
|
||||||
|
else
|
||||||
|
SelectedContent.Child = new MapDetailsSection(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MapDetailsSection : ClickableContainer
|
||||||
|
{
|
||||||
|
private Sprite beatmapBG;
|
||||||
|
private SpriteText name;
|
||||||
|
private SpriteText artist;
|
||||||
|
private SpriteText difficulty;
|
||||||
|
private SpriteText time;
|
||||||
|
|
||||||
|
private Box dim;
|
||||||
|
|
||||||
|
public MapDetailsSection(WorkingBeatmap workingBeatmap)
|
||||||
|
{
|
||||||
|
draw();
|
||||||
|
|
||||||
|
HitObject lastObject = workingBeatmap.Beatmap.HitObjects.LastOrDefault();
|
||||||
|
double endTime = (lastObject as IHasEndTime)?.EndTime ?? lastObject?.StartTime ?? 0;
|
||||||
|
|
||||||
|
beatmapBG.Texture = workingBeatmap.Background;
|
||||||
|
name.Text = workingBeatmap.BeatmapSetInfo.Metadata.Title;
|
||||||
|
artist.Text = "By: " + workingBeatmap.BeatmapSetInfo.Metadata.Artist;
|
||||||
|
difficulty.Text = workingBeatmap.BeatmapInfo.Version + " (" + Math.Round(workingBeatmap.BeatmapInfo.StarDifficulty, 2) + " stars) mapped by " + workingBeatmap.BeatmapInfo.Metadata.AuthorString;
|
||||||
|
time.Text = getBPMRange(workingBeatmap.Beatmap) + " bpm for " + TimeSpan.FromMilliseconds(endTime - workingBeatmap.Beatmap.HitObjects.First().StartTime).ToString(@"m\:ss");
|
||||||
|
|
||||||
|
BorderColour = getColour(workingBeatmap.BeatmapInfo);
|
||||||
|
EdgeEffect = new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Radius = 16,
|
||||||
|
Type = EdgeEffectType.Shadow,
|
||||||
|
Colour = getColour(workingBeatmap.BeatmapInfo).Opacity(0.2f)
|
||||||
|
};
|
||||||
|
Action = () => Process.Start("https://osu.ppy.sh/beatmapsets/" + workingBeatmap.BeatmapSetInfo.OnlineBeatmapSetID);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MapDetailsSection(int onlineBeatmapSetID)
|
||||||
|
{
|
||||||
|
draw();
|
||||||
|
name.Text = "Missing Map!";
|
||||||
|
artist.Text = "Click to open in Browser";
|
||||||
|
Action = () => Process.Start("https://osu.ppy.sh/beatmapsets/" + onlineBeatmapSetID);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MapDetailsSection(bool invalid)
|
||||||
|
{
|
||||||
|
draw();
|
||||||
|
name.Text = "Invalid / No Map Selected!";
|
||||||
|
artist.Text = "Don't hit start, weird things might happen";
|
||||||
|
Action = () => Process.Start("https://osu.ppy.sh/home");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void draw()
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
Width = 0.95f;
|
||||||
|
Height = 0.9f;
|
||||||
|
|
||||||
|
Masking = true;
|
||||||
|
BorderColour = Color4.LightBlue;
|
||||||
|
BorderThickness = 4;
|
||||||
|
CornerRadius = 10;
|
||||||
|
|
||||||
|
EdgeEffect = new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Radius = 16,
|
||||||
|
Type = EdgeEffectType.Shadow,
|
||||||
|
Colour = Color4.LightBlue.Opacity(0.2f)
|
||||||
|
};
|
||||||
|
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
beatmapBG = new Sprite
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
FillMode = FillMode.Fill,
|
||||||
|
},
|
||||||
|
dim = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = Color4.Black,
|
||||||
|
Alpha = 0.6f
|
||||||
|
},
|
||||||
|
name = new SpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopLeft,
|
||||||
|
Origin = Anchor.TopLeft,
|
||||||
|
Position = new Vector2(10, 0),
|
||||||
|
Font = @"Exo2.0-SemiBoldItalic",
|
||||||
|
TextSize = 40
|
||||||
|
},
|
||||||
|
artist = new SpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopLeft,
|
||||||
|
Origin = Anchor.TopLeft,
|
||||||
|
Position = new Vector2(10, 38),
|
||||||
|
Font = @"Exo2.0-MediumItalic",
|
||||||
|
TextSize = 24
|
||||||
|
},
|
||||||
|
difficulty = new SpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopLeft,
|
||||||
|
Origin = Anchor.TopLeft,
|
||||||
|
Position = new Vector2(10, 64),
|
||||||
|
Font = "Exo2.0-Bold",
|
||||||
|
TextSize = 16
|
||||||
|
},
|
||||||
|
time = new SpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopLeft,
|
||||||
|
Origin = Anchor.TopLeft,
|
||||||
|
Position = new Vector2(10, 84),
|
||||||
|
TextSize = 16
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnHover(InputState state)
|
||||||
|
{
|
||||||
|
dim.FadeTo(0.4f, 200);
|
||||||
|
|
||||||
|
return base.OnHover(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnHoverLost(InputState state)
|
||||||
|
{
|
||||||
|
base.OnHoverLost(state);
|
||||||
|
|
||||||
|
dim.FadeTo(0.6f, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
//"Borrowed" stuff
|
||||||
|
private string getBPMRange(Beatmap beatmap)
|
||||||
|
{
|
||||||
|
double bpmMax = beatmap.ControlPointInfo.BPMMaximum;
|
||||||
|
double bpmMin = beatmap.ControlPointInfo.BPMMinimum;
|
||||||
|
|
||||||
|
if (Precision.AlmostEquals(bpmMin, bpmMax))
|
||||||
|
return $"{bpmMin:0}";
|
||||||
|
|
||||||
|
return $"{bpmMin:0}-{bpmMax:0} (mostly {beatmap.ControlPointInfo.BPMMode:0})";
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum DifficultyRating
|
||||||
|
{
|
||||||
|
Easy,
|
||||||
|
Normal,
|
||||||
|
Hard,
|
||||||
|
Insane,
|
||||||
|
Expert,
|
||||||
|
ExpertPlus
|
||||||
|
}
|
||||||
|
|
||||||
|
private DifficultyRating getDifficultyRating(BeatmapInfo beatmap)
|
||||||
|
{
|
||||||
|
if (beatmap == null)
|
||||||
|
throw new ArgumentNullException(nameof(beatmap));
|
||||||
|
|
||||||
|
var rating = beatmap.StarDifficulty;
|
||||||
|
|
||||||
|
if (rating < 1.5) return DifficultyRating.Easy;
|
||||||
|
if (rating < 2.25) return DifficultyRating.Normal;
|
||||||
|
if (rating < 3.75) return DifficultyRating.Hard;
|
||||||
|
if (rating < 5.25) return DifficultyRating.Insane;
|
||||||
|
if (rating < 6.75) return DifficultyRating.Expert;
|
||||||
|
return DifficultyRating.ExpertPlus;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Color4 getColour(BeatmapInfo beatmap)
|
||||||
|
{
|
||||||
|
OsuColour palette = new OsuColour();
|
||||||
|
switch (getDifficultyRating(beatmap))
|
||||||
|
{
|
||||||
|
case DifficultyRating.Easy:
|
||||||
|
return palette.Green;
|
||||||
|
default:
|
||||||
|
case DifficultyRating.Normal:
|
||||||
|
return palette.Blue;
|
||||||
|
case DifficultyRating.Hard:
|
||||||
|
return palette.Yellow;
|
||||||
|
case DifficultyRating.Insane:
|
||||||
|
return palette.Pink;
|
||||||
|
case DifficultyRating.Expert:
|
||||||
|
return palette.Purple;
|
||||||
|
case DifficultyRating.ExpertPlus:
|
||||||
|
return palette.Gray0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum MatchGamemode
|
||||||
|
{
|
||||||
|
[System.ComponentModel.Description("Head to Head")]
|
||||||
|
HeadToHead,
|
||||||
|
[System.ComponentModel.Description("Head to Head with Live Spectator")]
|
||||||
|
HeadToHeadSpectator,
|
||||||
|
[System.ComponentModel.Description("Team Versus")]
|
||||||
|
TeamVS,
|
||||||
|
[System.ComponentModel.Description("TAG4")]
|
||||||
|
TAG4,
|
||||||
|
[System.ComponentModel.Description("Team TAG4")]
|
||||||
|
TeamTAG4,
|
||||||
|
[System.ComponentModel.Description("Tourny Mode")]
|
||||||
|
Tournement,
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum MatchScreenMode
|
||||||
|
{
|
||||||
|
[System.ComponentModel.Description("Map Details")]
|
||||||
|
MapDetails,
|
||||||
|
[System.ComponentModel.Description("Match Settings")]
|
||||||
|
MatchSettings,
|
||||||
|
[System.ComponentModel.Description("Ruleset Settings")]
|
||||||
|
RulesetSettings,
|
||||||
|
[System.ComponentModel.Description("Sound Board")]
|
||||||
|
SoundBoard
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Screens.Select;
|
||||||
|
using System;
|
||||||
|
using osu.Game.Screens;
|
||||||
|
using osu.Framework.Screens;
|
||||||
|
using Symcol.Rulesets.Core.Multiplayer.Networking;
|
||||||
|
|
||||||
|
namespace Symcol.Rulesets.Core.Multiplayer.Screens
|
||||||
|
{
|
||||||
|
public class MatchSongSelect : SongSelect
|
||||||
|
{
|
||||||
|
public WorkingBeatmap SelectedMap;
|
||||||
|
|
||||||
|
private bool exiting;
|
||||||
|
|
||||||
|
protected override BackgroundScreen CreateBackground() => null;
|
||||||
|
|
||||||
|
public Action Action;
|
||||||
|
|
||||||
|
public readonly RulesetNetworkingClientHandler RulesetNetworkingClientHandler;
|
||||||
|
|
||||||
|
public MatchSongSelect(RulesetNetworkingClientHandler rulesetNetworkingClientHandler)
|
||||||
|
{
|
||||||
|
RulesetNetworkingClientHandler = rulesetNetworkingClientHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnEntering(Screen last)
|
||||||
|
{
|
||||||
|
Add(RulesetNetworkingClientHandler);
|
||||||
|
base.OnEntering(last);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnSelectionFinalised()
|
||||||
|
{
|
||||||
|
if (!exiting)
|
||||||
|
{
|
||||||
|
RulesetNetworkingClientHandler.OnMapChange?.Invoke(null);
|
||||||
|
SelectedMap = Beatmap.Value;
|
||||||
|
Action();
|
||||||
|
exiting = true;
|
||||||
|
Remove(RulesetNetworkingClientHandler);
|
||||||
|
Exit();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,374 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio;
|
||||||
|
using osu.Framework.Audio.Sample;
|
||||||
|
using osu.Framework.Configuration;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Cursor;
|
||||||
|
using osu.Framework.Input;
|
||||||
|
using osu.Framework.Logging;
|
||||||
|
using osu.Framework.Screens;
|
||||||
|
using osu.Framework.Threading;
|
||||||
|
using osu.Framework.Timing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Cursor;
|
||||||
|
using osu.Game.Online.API;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Screens.Backgrounds;
|
||||||
|
using osu.Game.Screens.Ranking;
|
||||||
|
using osu.Game.Storyboards.Drawables;
|
||||||
|
using OpenTK;
|
||||||
|
using osu.Game.Screens;
|
||||||
|
using osu.Game.Screens.Play;
|
||||||
|
using OpenTK.Input;
|
||||||
|
using Symcol.Rulesets.Core.Multiplayer.Networking;
|
||||||
|
|
||||||
|
namespace Symcol.Rulesets.Core.Multiplayer.Screens
|
||||||
|
{
|
||||||
|
public class MultiPlayer : ScreenWithBeatmapBackground, IProvideCursor
|
||||||
|
{
|
||||||
|
public readonly RulesetNetworkingClientHandler RulesetNetworkingClientHandler;
|
||||||
|
|
||||||
|
protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(Beatmap);
|
||||||
|
|
||||||
|
protected override float BackgroundParallaxAmount => 0.1f;
|
||||||
|
|
||||||
|
public override bool ShowOverlaysOnEnter => false;
|
||||||
|
|
||||||
|
public Action RestartRequested;
|
||||||
|
|
||||||
|
public override bool AllowBeatmapRulesetChange => false;
|
||||||
|
|
||||||
|
public bool HasFailed { get; private set; }
|
||||||
|
|
||||||
|
public bool AllowPause { get; set; } = false;
|
||||||
|
public bool AllowLeadIn { get; set; } = true;
|
||||||
|
public bool AllowResults { get; set; } = true;
|
||||||
|
|
||||||
|
public int RestartCount;
|
||||||
|
|
||||||
|
public CursorContainer Cursor => RulesetContainer.Cursor;
|
||||||
|
public bool ProvidingUserCursor => RulesetContainer?.Cursor != null && !RulesetContainer.HasReplayLoaded.Value;
|
||||||
|
|
||||||
|
private IAdjustableClock sourceClock;
|
||||||
|
private DecoupleableInterpolatingFramedClock adjustableClock;
|
||||||
|
|
||||||
|
private RulesetInfo ruleset;
|
||||||
|
|
||||||
|
private APIAccess api;
|
||||||
|
|
||||||
|
private ScoreProcessor scoreProcessor;
|
||||||
|
protected RulesetContainer RulesetContainer;
|
||||||
|
|
||||||
|
#region User Settings
|
||||||
|
|
||||||
|
private Bindable<double> dimLevel;
|
||||||
|
private Bindable<double> blurLevel;
|
||||||
|
private Bindable<bool> showStoryboard;
|
||||||
|
private Bindable<bool> mouseWheelDisabled;
|
||||||
|
private Bindable<double> userAudioOffset;
|
||||||
|
|
||||||
|
private SampleChannel sampleRestart;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private Container storyboardContainer;
|
||||||
|
private DrawableStoryboard storyboard;
|
||||||
|
|
||||||
|
private HUDOverlay hudOverlay;
|
||||||
|
|
||||||
|
private bool loadedSuccessfully => RulesetContainer?.Objects.Any() == true;
|
||||||
|
|
||||||
|
public MultiPlayer(RulesetNetworkingClientHandler rulesetNetworkingClientHandler)//, WorkingBeatmap beatmap = null)
|
||||||
|
{
|
||||||
|
RulesetNetworkingClientHandler = rulesetNetworkingClientHandler;
|
||||||
|
RulesetNetworkingClientHandler.OnAbort = () => Exit();
|
||||||
|
RulesetNetworkingClientHandler.StartGame = () => start();
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(AudioManager audio, APIAccess api, OsuConfigManager config)
|
||||||
|
{
|
||||||
|
this.api = api;
|
||||||
|
|
||||||
|
dimLevel = config.GetBindable<double>(OsuSetting.DimLevel);
|
||||||
|
blurLevel = config.GetBindable<double>(OsuSetting.BlurLevel);
|
||||||
|
showStoryboard = config.GetBindable<bool>(OsuSetting.ShowStoryboard);
|
||||||
|
|
||||||
|
mouseWheelDisabled = config.GetBindable<bool>(OsuSetting.MouseDisableWheel);
|
||||||
|
|
||||||
|
sampleRestart = audio.Sample.Get(@"Gameplay/restart");
|
||||||
|
userAudioOffset = config.GetBindable<double>(OsuSetting.AudioOffset);
|
||||||
|
|
||||||
|
WorkingBeatmap working = Beatmap.Value;
|
||||||
|
Beatmap beatmap;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
beatmap = working.Beatmap;
|
||||||
|
|
||||||
|
if (beatmap == null)
|
||||||
|
throw new InvalidOperationException("Beatmap was not loaded");
|
||||||
|
|
||||||
|
ruleset = Ruleset.Value ?? beatmap.BeatmapInfo.Ruleset;
|
||||||
|
var rulesetInstance = ruleset.CreateInstance();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
RulesetContainer = rulesetInstance.CreateRulesetContainerWith(working, ruleset.ID == beatmap.BeatmapInfo.Ruleset.ID);
|
||||||
|
}
|
||||||
|
catch (BeatmapInvalidForRulesetException)
|
||||||
|
{
|
||||||
|
// we may fail to create a RulesetContainer if the beatmap cannot be loaded with the user's preferred ruleset
|
||||||
|
// let's try again forcing the beatmap's ruleset.
|
||||||
|
ruleset = beatmap.BeatmapInfo.Ruleset;
|
||||||
|
rulesetInstance = ruleset.CreateInstance();
|
||||||
|
RulesetContainer = rulesetInstance.CreateRulesetContainerWith(Beatmap, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!RulesetContainer.Objects.Any())
|
||||||
|
throw new InvalidOperationException("Beatmap contains no hit objects!");
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.Error(e, "Could not load beatmap sucessfully!");
|
||||||
|
|
||||||
|
//couldn't load, hard abort!
|
||||||
|
Exit();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceClock = (IAdjustableClock)working.Track ?? new StopwatchClock();
|
||||||
|
adjustableClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false };
|
||||||
|
|
||||||
|
var firstObjectTime = RulesetContainer.Objects.First().StartTime;
|
||||||
|
adjustableClock.Seek(AllowLeadIn
|
||||||
|
? Math.Min(0, firstObjectTime - Math.Max(beatmap.ControlPointInfo.TimingPointAt(firstObjectTime).BeatLength * 4, beatmap.BeatmapInfo.AudioLeadIn))
|
||||||
|
: firstObjectTime);
|
||||||
|
|
||||||
|
adjustableClock.ProcessFrame();
|
||||||
|
|
||||||
|
// the final usable gameplay clock with user-set offsets applied.
|
||||||
|
var offsetClock = new FramedOffsetClock(adjustableClock);
|
||||||
|
|
||||||
|
userAudioOffset.ValueChanged += v => offsetClock.Offset = v;
|
||||||
|
userAudioOffset.TriggerChange();
|
||||||
|
|
||||||
|
scoreProcessor = RulesetContainer.CreateScoreProcessor();
|
||||||
|
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Clock = offsetClock,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
storyboardContainer = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Alpha = 0,
|
||||||
|
},
|
||||||
|
RulesetContainer,
|
||||||
|
hudOverlay = new HUDOverlay(scoreProcessor, RulesetContainer, working, offsetClock, adjustableClock)
|
||||||
|
{
|
||||||
|
Clock = Clock, // hud overlay doesn't want to use the audio clock directly
|
||||||
|
ProcessCustomClock = false,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre
|
||||||
|
},
|
||||||
|
new BreakOverlay(beatmap.BeatmapInfo.LetterboxInBreaks, scoreProcessor)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
ProcessCustomClock = false,
|
||||||
|
Breaks = beatmap.Breaks
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (showStoryboard)
|
||||||
|
initializeStoryboard(false);
|
||||||
|
|
||||||
|
// Bind ScoreProcessor to ourselves
|
||||||
|
scoreProcessor.AllJudged += onCompletion;
|
||||||
|
scoreProcessor.Failed += onFail;
|
||||||
|
|
||||||
|
foreach (var mod in Beatmap.Value.Mods.Value.OfType<IApplicableToScoreProcessor>())
|
||||||
|
mod.ApplyToScoreProcessor(scoreProcessor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyRateFromMods()
|
||||||
|
{
|
||||||
|
if (sourceClock == null) return;
|
||||||
|
|
||||||
|
sourceClock.Rate = 1;
|
||||||
|
foreach (var mod in Beatmap.Value.Mods.Value.OfType<IApplicableToClock>())
|
||||||
|
mod.ApplyToClock(sourceClock);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeStoryboard(bool asyncLoad)
|
||||||
|
{
|
||||||
|
if (storyboardContainer == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var beatmap = Beatmap.Value;
|
||||||
|
|
||||||
|
storyboard = beatmap.Storyboard.CreateDrawable();
|
||||||
|
storyboard.Masking = true;
|
||||||
|
|
||||||
|
if (asyncLoad)
|
||||||
|
LoadComponentAsync(storyboard, storyboardContainer.Add);
|
||||||
|
else
|
||||||
|
storyboardContainer.Add(storyboard);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ScheduledDelegate onCompletionEvent;
|
||||||
|
|
||||||
|
private void onCompletion()
|
||||||
|
{
|
||||||
|
// Only show the completion screen if the player hasn't failed
|
||||||
|
if (scoreProcessor.HasFailed || onCompletionEvent != null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ValidForResume = false;
|
||||||
|
|
||||||
|
if (!AllowResults) return;
|
||||||
|
|
||||||
|
using (BeginDelayedSequence(1000))
|
||||||
|
{
|
||||||
|
onCompletionEvent = Schedule(delegate
|
||||||
|
{
|
||||||
|
if (!IsCurrentScreen) return;
|
||||||
|
|
||||||
|
var score = new Score
|
||||||
|
{
|
||||||
|
Beatmap = Beatmap.Value.BeatmapInfo,
|
||||||
|
Ruleset = ruleset
|
||||||
|
};
|
||||||
|
scoreProcessor.PopulateScore(score);
|
||||||
|
score.User = RulesetContainer.Replay?.User ?? api.LocalUser.Value;
|
||||||
|
Push(new Results(score));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool onFail()
|
||||||
|
{
|
||||||
|
if (Beatmap.Value.Mods.Value.OfType<IApplicableFailOverride>().Any(m => !m.AllowFail))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
HasFailed = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnEntering(Screen last)
|
||||||
|
{
|
||||||
|
base.OnEntering(last);
|
||||||
|
|
||||||
|
Add(RulesetNetworkingClientHandler);
|
||||||
|
|
||||||
|
if (!loadedSuccessfully)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Content.Alpha = 0;
|
||||||
|
Content
|
||||||
|
.ScaleTo(0.7f)
|
||||||
|
.ScaleTo(1, 750, Easing.OutQuint)
|
||||||
|
.Delay(250)
|
||||||
|
.FadeIn(250);
|
||||||
|
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
sourceClock.Reset();
|
||||||
|
|
||||||
|
Schedule(() =>
|
||||||
|
{
|
||||||
|
adjustableClock.ChangeSource(sourceClock);
|
||||||
|
applyRateFromMods();
|
||||||
|
|
||||||
|
this.Delay(750).Schedule(() =>
|
||||||
|
{
|
||||||
|
Logger.Log("Client finnished loading", LoggingTarget.Network, LogLevel.Verbose);
|
||||||
|
RulesetNetworkingClientHandler.GameLoaded();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void start()
|
||||||
|
{
|
||||||
|
adjustableClock.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnSuspending(Screen next)
|
||||||
|
{
|
||||||
|
fadeOut();
|
||||||
|
base.OnSuspending(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnExiting(Screen next)
|
||||||
|
{
|
||||||
|
Remove(RulesetNetworkingClientHandler);
|
||||||
|
fadeOut();
|
||||||
|
return base.OnExiting(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateBackgroundElements()
|
||||||
|
{
|
||||||
|
if (!IsCurrentScreen) return;
|
||||||
|
|
||||||
|
base.UpdateBackgroundElements();
|
||||||
|
|
||||||
|
if (ShowStoryboard && storyboard == null)
|
||||||
|
initializeStoryboard(true);
|
||||||
|
|
||||||
|
var beatmap = Beatmap.Value;
|
||||||
|
var storyboardVisible = ShowStoryboard && beatmap.Storyboard.HasDrawable;
|
||||||
|
|
||||||
|
storyboardContainer?
|
||||||
|
.FadeColour(OsuColour.Gray(BackgroundOpacity), BACKGROUND_FADE_DURATION, Easing.OutQuint)
|
||||||
|
.FadeTo(storyboardVisible && BackgroundOpacity > 0 ? 1 : 0, BACKGROUND_FADE_DURATION, Easing.OutQuint);
|
||||||
|
|
||||||
|
if (storyboardVisible && beatmap.Storyboard.ReplacesBackground)
|
||||||
|
Background?.FadeTo(0, BACKGROUND_FADE_DURATION, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fadeOut()
|
||||||
|
{
|
||||||
|
const float fade_out_duration = 250;
|
||||||
|
|
||||||
|
RulesetContainer?.FadeOut(fade_out_duration);
|
||||||
|
Content.FadeOut(fade_out_duration);
|
||||||
|
|
||||||
|
hudOverlay?.ScaleTo(0.7f, fade_out_duration * 3, Easing.In);
|
||||||
|
|
||||||
|
Background?.FadeTo(1f, fade_out_duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
|
||||||
|
{
|
||||||
|
if (args.Key == Key.Escape)
|
||||||
|
BackOut();
|
||||||
|
|
||||||
|
return base.OnKeyDown(state, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void BackOut()
|
||||||
|
{
|
||||||
|
RulesetNetworkingClientHandler.AbortGame();
|
||||||
|
RulesetNetworkingClientHandler.OnAbort();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnWheel(InputState state) => mouseWheelDisabled.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,228 @@
|
|||||||
|
using OpenTK;
|
||||||
|
using OpenTK.Graphics;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using osu.Game.Overlays.Settings;
|
||||||
|
using osu.Game.Screens;
|
||||||
|
using System;
|
||||||
|
using osu.Framework.Screens;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Symcol.Core.Networking;
|
||||||
|
using Symcol.Rulesets.Core.Multiplayer.Networking;
|
||||||
|
|
||||||
|
namespace Symcol.Rulesets.Core.Multiplayer.Screens
|
||||||
|
{
|
||||||
|
public abstract class RulesetLobbyScreen : OsuScreen
|
||||||
|
{
|
||||||
|
public abstract string RulesetName { get; }
|
||||||
|
|
||||||
|
public abstract RulesetMatchScreen MatchScreen { get; }
|
||||||
|
|
||||||
|
public RulesetNetworkingClientHandler RulesetNetworkingClientHandler;
|
||||||
|
|
||||||
|
public readonly SettingsButton HostGameButton;
|
||||||
|
public readonly SettingsButton DirectConnectButton;
|
||||||
|
public readonly SettingsButton JoinGameButton;
|
||||||
|
|
||||||
|
public readonly Container NewGame;
|
||||||
|
protected readonly TextBox HostIP;
|
||||||
|
protected readonly TextBox HostPort;
|
||||||
|
//protected readonly TextBox PublicIp;
|
||||||
|
protected readonly TextBox LocalIp;
|
||||||
|
|
||||||
|
public readonly Container JoinIP;
|
||||||
|
|
||||||
|
public RulesetLobbyScreen()
|
||||||
|
{
|
||||||
|
AlwaysPresent = true;
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
HostGameButton = new SettingsButton
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomLeft,
|
||||||
|
Origin = Anchor.BottomLeft,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Width = 0.3f,
|
||||||
|
Text = "Host Game",
|
||||||
|
Action = HostGame
|
||||||
|
},
|
||||||
|
DirectConnectButton = new SettingsButton
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomCentre,
|
||||||
|
Origin = Anchor.BottomCentre,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Width = 0.3f,
|
||||||
|
Text = "Direct Connect",
|
||||||
|
Action = DirectConnect
|
||||||
|
},
|
||||||
|
JoinGameButton = new SettingsButton
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomRight,
|
||||||
|
Origin = Anchor.BottomRight,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Width = 0.3f,
|
||||||
|
Text = "Join Game"
|
||||||
|
},
|
||||||
|
NewGame = new Container
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Masking = true,
|
||||||
|
Size = new Vector2(400, 300),
|
||||||
|
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Colour = Color4.Blue,
|
||||||
|
RelativeSizeAxes = Axes.Both
|
||||||
|
},
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopLeft,
|
||||||
|
Origin = Anchor.TopLeft,
|
||||||
|
Colour = Color4.Black,
|
||||||
|
Alpha = 0.9f,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Width = 0.48f,
|
||||||
|
Height = 20,
|
||||||
|
},
|
||||||
|
HostIP = new TextBox
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopLeft,
|
||||||
|
Origin = Anchor.TopLeft,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Width = 0.48f,
|
||||||
|
Height = 20,
|
||||||
|
Text = "Host IP Address"
|
||||||
|
},
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopRight,
|
||||||
|
Origin = Anchor.TopRight,
|
||||||
|
Colour = Color4.Black,
|
||||||
|
Alpha = 0.9f,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Width = 0.48f,
|
||||||
|
Height = 20,
|
||||||
|
},
|
||||||
|
HostPort = new TextBox
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopRight,
|
||||||
|
Origin = Anchor.TopRight,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Width = 0.48f,
|
||||||
|
Height = 20,
|
||||||
|
Text = "25570"
|
||||||
|
},
|
||||||
|
/*
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopLeft,
|
||||||
|
Origin = Anchor.TopLeft,
|
||||||
|
Colour = Color4.Black,
|
||||||
|
Alpha = 0.9f,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Position = new Vector2(0, 22),
|
||||||
|
Width = 0.48f,
|
||||||
|
Height = 20,
|
||||||
|
},
|
||||||
|
PublicIp = new TextBox
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopLeft,
|
||||||
|
Origin = Anchor.TopLeft,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Position = new Vector2(0, 22),
|
||||||
|
Width = 0.48f,
|
||||||
|
Height = 20,
|
||||||
|
Text = "You're Public IP Address"
|
||||||
|
},
|
||||||
|
*/
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopLeft,
|
||||||
|
Origin = Anchor.TopLeft,
|
||||||
|
Colour = Color4.Black,
|
||||||
|
Alpha = 0.9f,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Position = new Vector2(0, 44),
|
||||||
|
Width = 0.48f,
|
||||||
|
Height = 20,
|
||||||
|
},
|
||||||
|
LocalIp = new TextBox
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopLeft,
|
||||||
|
Origin = Anchor.TopLeft,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Position = new Vector2(0, 44),
|
||||||
|
Width = 0.48f,
|
||||||
|
Height = 20,
|
||||||
|
Text = "You're Local IP Address"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnEntering(Screen last)
|
||||||
|
{
|
||||||
|
base.OnEntering(last);
|
||||||
|
MakeCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnResuming(Screen last)
|
||||||
|
{
|
||||||
|
base.OnResuming(last);
|
||||||
|
MakeCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnExiting(Screen next)
|
||||||
|
{
|
||||||
|
if (RulesetNetworkingClientHandler != null)
|
||||||
|
{
|
||||||
|
Remove(RulesetNetworkingClientHandler);
|
||||||
|
RulesetNetworkingClientHandler.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.OnExiting(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void HostGame()
|
||||||
|
{
|
||||||
|
if (RulesetNetworkingClientHandler != null)
|
||||||
|
{
|
||||||
|
Remove(RulesetNetworkingClientHandler);
|
||||||
|
RulesetNetworkingClientHandler.Dispose();
|
||||||
|
}
|
||||||
|
Add(RulesetNetworkingClientHandler = new RulesetNetworkingClientHandler(ClientType.Host, LocalIp.Text, Int32.Parse(HostPort.Text)));
|
||||||
|
|
||||||
|
List<ClientInfo> list = new List<ClientInfo>();
|
||||||
|
list.Add(RulesetNetworkingClientHandler.RulesetClientInfo);
|
||||||
|
|
||||||
|
JoinMatch(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void DirectConnect()
|
||||||
|
{
|
||||||
|
if (RulesetNetworkingClientHandler != null)
|
||||||
|
{
|
||||||
|
Remove(RulesetNetworkingClientHandler);
|
||||||
|
RulesetNetworkingClientHandler.Dispose();
|
||||||
|
}
|
||||||
|
Add(RulesetNetworkingClientHandler = new RulesetNetworkingClientHandler(ClientType.Peer, HostIP.Text, Int32.Parse(HostPort.Text), LocalIp.Text));
|
||||||
|
|
||||||
|
RulesetNetworkingClientHandler.OnConnectedToHost += (p) => JoinMatch(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void JoinMatch(List<ClientInfo> clientInfos)
|
||||||
|
{
|
||||||
|
Remove(RulesetNetworkingClientHandler);
|
||||||
|
MakeCurrent();
|
||||||
|
Push(MatchScreen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
using OpenTK;
|
||||||
|
using OpenTK.Graphics;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Screens;
|
||||||
|
using osu.Game;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Overlays.Settings;
|
||||||
|
using osu.Game.Screens;
|
||||||
|
using Symcol.Core.Networking;
|
||||||
|
using Symcol.Rulesets.Core.Multiplayer.Networking;
|
||||||
|
using Symcol.Rulesets.Core.Multiplayer.Pieces;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Symcol.Rulesets.Core.Multiplayer.Screens
|
||||||
|
{
|
||||||
|
public abstract class RulesetMatchScreen : OsuScreen
|
||||||
|
{
|
||||||
|
public readonly RulesetNetworkingClientHandler RulesetNetworkingClientHandler;
|
||||||
|
|
||||||
|
private readonly MatchPlayerList playerList;
|
||||||
|
|
||||||
|
private BeatmapManager beatmaps;
|
||||||
|
|
||||||
|
protected MatchTools MatchTools;
|
||||||
|
|
||||||
|
private readonly Chat chat;
|
||||||
|
|
||||||
|
public RulesetMatchScreen(RulesetNetworkingClientHandler rulesetNetworkingClientHandler)
|
||||||
|
{
|
||||||
|
RulesetNetworkingClientHandler = rulesetNetworkingClientHandler;
|
||||||
|
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new SettingsButton
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Width = 0.35f,
|
||||||
|
Text = "Leave",
|
||||||
|
Action = () => Exit()
|
||||||
|
},
|
||||||
|
new SettingsButton
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Width = 0.3f,
|
||||||
|
Text = "Open Song Select",
|
||||||
|
Action = () => openSongSelect()
|
||||||
|
},
|
||||||
|
new SettingsButton
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreRight,
|
||||||
|
Origin = Anchor.CentreRight,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Width = 0.35f,
|
||||||
|
Text = "Start Match",
|
||||||
|
Action = () => RulesetNetworkingClientHandler.StartLoadingGame()
|
||||||
|
},
|
||||||
|
playerList = new MatchPlayerList(RulesetNetworkingClientHandler),
|
||||||
|
MatchTools = new MatchTools(),
|
||||||
|
chat = new Chat(RulesetNetworkingClientHandler)
|
||||||
|
};
|
||||||
|
|
||||||
|
RulesetNetworkingClientHandler.OnPacketReceive += (Packet packet) =>
|
||||||
|
{
|
||||||
|
if (packet is RulesetPacket rulesetPacket && rulesetPacket.OnlineBeatmapID != -1)
|
||||||
|
foreach (BeatmapSetInfo beatmapSet in beatmaps.GetAllUsableBeatmapSets())
|
||||||
|
if (beatmapSet.OnlineBeatmapSetID == rulesetPacket.OnlineBeatmapSetID)
|
||||||
|
{
|
||||||
|
foreach (BeatmapInfo beatmap in beatmapSet.Beatmaps)
|
||||||
|
if (beatmap.OnlineBeatmapID == rulesetPacket.OnlineBeatmapID)
|
||||||
|
{
|
||||||
|
Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap, Beatmap.Value);
|
||||||
|
Beatmap.Value.Track.Start();
|
||||||
|
MatchTools.MapChange(Beatmap);
|
||||||
|
RulesetNetworkingClientHandler.OnMapChange?.Invoke(Beatmap);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
MatchTools.MapChange(rulesetPacket.OnlineBeatmapSetID);
|
||||||
|
};
|
||||||
|
|
||||||
|
RulesetNetworkingClientHandler.OnMapChange += (beatmap) => MatchTools.MapChange(beatmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
playerList.Add(RulesetNetworkingClientHandler.RulesetClientInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(BeatmapManager beatmaps)
|
||||||
|
{
|
||||||
|
this.beatmaps = beatmaps;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnEntering(Screen last)
|
||||||
|
{
|
||||||
|
base.OnEntering(last);
|
||||||
|
MakeCurrent();
|
||||||
|
Add(RulesetNetworkingClientHandler);
|
||||||
|
RulesetNetworkingClientHandler.OnLoadGame = (i) => Load(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnResuming(Screen last)
|
||||||
|
{
|
||||||
|
base.OnResuming(last);
|
||||||
|
MakeCurrent();
|
||||||
|
if (RulesetNetworkingClientHandler != null)
|
||||||
|
Add(RulesetNetworkingClientHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnSuspending(Screen next)
|
||||||
|
{
|
||||||
|
base.OnSuspending(next);
|
||||||
|
Remove(RulesetNetworkingClientHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnExiting(Screen next)
|
||||||
|
{
|
||||||
|
RulesetNetworkingClientHandler.Disconnect();
|
||||||
|
Remove(RulesetNetworkingClientHandler);
|
||||||
|
RulesetNetworkingClientHandler.Dispose();
|
||||||
|
|
||||||
|
return base.OnExiting(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Load(List<ClientInfo> playerList)
|
||||||
|
{
|
||||||
|
MakeCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void openSongSelect()
|
||||||
|
{
|
||||||
|
MatchSongSelect songSelect = new MatchSongSelect(RulesetNetworkingClientHandler);
|
||||||
|
MakeCurrent();
|
||||||
|
Push(songSelect);
|
||||||
|
songSelect.Action = () => RulesetNetworkingClientHandler.SetMap(songSelect.SelectedMap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
//osu.Game.Screens.Symcol.SymcolMenu
|
||||||
|
//Symcol.Rulesets.Core.SymcolSettingsSubsection
|
||||||
|
//#define SymcolMods
|
||||||
|
|
||||||
|
using OpenTK;
|
||||||
|
using OpenTK.Graphics;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Graphics.Textures;
|
||||||
|
using osu.Framework.Screens;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Screens;
|
||||||
|
using osu.Game.Screens.Symcol;
|
||||||
|
|
||||||
|
namespace Symcol.Rulesets.Core.Multiplayer.Screens
|
||||||
|
{
|
||||||
|
public class RulesetMultiplayerSelection : OsuScreen
|
||||||
|
{
|
||||||
|
public static readonly FillFlowContainer<RulesetLobbyItem> LobbyItems = new FillFlowContainer<RulesetLobbyItem>
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Width = 0.85f,
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
};
|
||||||
|
|
||||||
|
public RulesetMultiplayerSelection()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
Add(LobbyItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnEntering(Screen last)
|
||||||
|
{
|
||||||
|
base.OnEntering(last);
|
||||||
|
foreach (RulesetLobbyItem item in LobbyItems)
|
||||||
|
item.Action = () => Push(item.RulesetLobbyScreen);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnExiting(Screen next)
|
||||||
|
{
|
||||||
|
Remove(LobbyItems);
|
||||||
|
|
||||||
|
#if SymcolMods
|
||||||
|
SymcolSettingsSubsection.RulesetMultiplayerSelection = new RulesetMultiplayerSelection();
|
||||||
|
|
||||||
|
SymcolMenu.RulesetMultiplayerScreen = SymcolSettingsSubsection.RulesetMultiplayerSelection;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return base.OnExiting(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class RulesetLobbyItem : ClickableContainer
|
||||||
|
{
|
||||||
|
public abstract Texture Icon { get; }
|
||||||
|
|
||||||
|
public abstract string RulesetName { get; }
|
||||||
|
|
||||||
|
public virtual Texture Background { get; }
|
||||||
|
|
||||||
|
public abstract RulesetLobbyScreen RulesetLobbyScreen { get; }
|
||||||
|
|
||||||
|
public RulesetLobbyItem()
|
||||||
|
{
|
||||||
|
CornerRadius = 20;
|
||||||
|
Masking = true;
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
Height = 100;
|
||||||
|
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Sprite
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
FillMode = FillMode.Fill,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Texture = Background
|
||||||
|
},
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = Color4.Black,
|
||||||
|
Alpha = 0.5f,
|
||||||
|
},
|
||||||
|
new Sprite
|
||||||
|
{
|
||||||
|
Size = new Vector2(Height),
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Texture = Icon
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomRight,
|
||||||
|
Origin = Anchor.BottomRight,
|
||||||
|
Text = RulesetName,
|
||||||
|
TextSize = 60,
|
||||||
|
Position = new Vector2(-20, 0)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
// General Information about an assembly is controlled through the following
|
||||||
|
// set of attributes. Change these attribute values to modify the information
|
||||||
|
// associated with an assembly.
|
||||||
|
[assembly: AssemblyTitle("Symcol.Rulesets.Core")]
|
||||||
|
[assembly: AssemblyDescription("")]
|
||||||
|
[assembly: AssemblyConfiguration("")]
|
||||||
|
[assembly: AssemblyCompany("")]
|
||||||
|
[assembly: AssemblyProduct("Symcol.Rulesets.Core")]
|
||||||
|
[assembly: AssemblyCopyright("Copyright © Shawdooow 2018")]
|
||||||
|
[assembly: AssemblyTrademark("")]
|
||||||
|
[assembly: AssemblyCulture("")]
|
||||||
|
|
||||||
|
// Setting ComVisible to false makes the types in this assembly not visible
|
||||||
|
// to COM components. If you need to access a type in this assembly from
|
||||||
|
// COM, set the ComVisible attribute to true on that type.
|
||||||
|
[assembly: ComVisible(false)]
|
||||||
|
|
||||||
|
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||||
|
[assembly: Guid("552b5940-c647-4060-aa4d-61baac415c72")]
|
||||||
|
|
||||||
|
// Version information for an assembly consists of the following four values:
|
||||||
|
//
|
||||||
|
// Major Version
|
||||||
|
// Minor Version
|
||||||
|
// Build Number
|
||||||
|
// Revision
|
||||||
|
//
|
||||||
|
// You can specify all the values or you can default the Build and Revision Numbers
|
||||||
|
// by using the '*' as shown below:
|
||||||
|
// [assembly: AssemblyVersion("1.0.*")]
|
||||||
|
[assembly: AssemblyVersion("1.0.0.0")]
|
||||||
|
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
using osu.Framework.Configuration;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Textures;
|
||||||
|
using osu.Framework.IO.Stores;
|
||||||
|
using osu.Framework.Platform;
|
||||||
|
|
||||||
|
namespace Symcol.Rulesets.Core.Skinning
|
||||||
|
{
|
||||||
|
public abstract class SkinElement : Container
|
||||||
|
{
|
||||||
|
private static string loadedSkin;
|
||||||
|
|
||||||
|
private static ResourceStore<byte[]> skinResources;
|
||||||
|
private static TextureStore skinTextures;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Will attempt to get a skin element fron the skin, if no element is found return the default element
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stockTextures"></param>
|
||||||
|
/// <param name="skin"></param>
|
||||||
|
/// <param name="fileName"></param>
|
||||||
|
/// <param name="storage"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static Texture GetSkinElement(TextureStore stockTextures, Bindable<string> skin, string fileName, Storage storage)
|
||||||
|
{
|
||||||
|
Texture texture = null;
|
||||||
|
|
||||||
|
string fileNameHd = fileName + "@2x";
|
||||||
|
|
||||||
|
Storage skinStorage = storage.GetStorageForDirectory("Skins\\" + skin);
|
||||||
|
|
||||||
|
if (skin.Value == "default")
|
||||||
|
{
|
||||||
|
texture = stockTextures.Get(fileName + ".png");
|
||||||
|
|
||||||
|
if (texture == null)
|
||||||
|
texture = stockTextures.Get(fileNameHd + ".png");
|
||||||
|
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loadedSkin != skin.ToString())
|
||||||
|
{
|
||||||
|
loadedSkin = skin.ToString();
|
||||||
|
skinResources = new ResourceStore<byte[]>(new StorageBackedResourceStore(skinStorage));
|
||||||
|
skinTextures = new TextureStore(new RawTextureLoaderStore(skinResources));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skinStorage.Exists(fileNameHd + ".png"))
|
||||||
|
texture = skinTextures.Get(fileNameHd + ".png");
|
||||||
|
else if (skinStorage.Exists(fileName + ".png"))
|
||||||
|
{
|
||||||
|
texture = skinTextures.Get(fileName + ".png");
|
||||||
|
texture.ScaleAdjust = 1f;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
texture = stockTextures.Get(fileNameHd + ".png");
|
||||||
|
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Will attempt to get a skin element from the skin, if no element is found return null
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="skin"></param>
|
||||||
|
/// <param name="fileName"></param>
|
||||||
|
/// <param name="storage"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static Texture GetElement(Bindable<string> skin, string fileName, Storage storage)
|
||||||
|
{
|
||||||
|
Texture texture = null;
|
||||||
|
|
||||||
|
string fileNameHd = fileName + "@2x";
|
||||||
|
|
||||||
|
Storage skinStorage = storage.GetStorageForDirectory("Skins\\" + skin);
|
||||||
|
|
||||||
|
if (loadedSkin != skin.ToString())
|
||||||
|
{
|
||||||
|
loadedSkin = skin.ToString();
|
||||||
|
skinResources = new ResourceStore<byte[]>(new StorageBackedResourceStore(skinStorage));
|
||||||
|
skinTextures = new TextureStore(new RawTextureLoaderStore(skinResources));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skinStorage.Exists(fileNameHd + ".png"))
|
||||||
|
texture = skinTextures.Get(fileNameHd + ".png");
|
||||||
|
else if (skinStorage.Exists(fileName + ".png"))
|
||||||
|
{
|
||||||
|
texture = skinTextures.Get(fileName + ".png");
|
||||||
|
texture.ScaleAdjust = 1f;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
texture = null;
|
||||||
|
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
using osu.Framework.Configuration;
|
||||||
|
using osu.Framework.Platform;
|
||||||
|
|
||||||
|
namespace Symcol.Rulesets.Core.Skinning
|
||||||
|
{
|
||||||
|
public class SkinConfigReader<T> : IniConfigManager<T>
|
||||||
|
where T : struct
|
||||||
|
{
|
||||||
|
protected override string Filename => @"skin.ini";
|
||||||
|
|
||||||
|
public SkinConfigReader(Storage storage) : base(storage) { }
|
||||||
|
|
||||||
|
protected override bool PerformSave() { return false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
//wildly incomplete
|
||||||
|
public enum ClassicIniParameters
|
||||||
|
{
|
||||||
|
Name,
|
||||||
|
Author,
|
||||||
|
CursorRotate,
|
||||||
|
CursorExpand,
|
||||||
|
CursorCentre
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||||
|
<PropertyGroup>
|
||||||
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
|
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||||
|
<ProjectGuid>{552B5940-C647-4060-AA4D-61BAAC415C72}</ProjectGuid>
|
||||||
|
<OutputType>Library</OutputType>
|
||||||
|
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||||
|
<RootNamespace>Symcol.Rulesets.Core</RootNamespace>
|
||||||
|
<AssemblyName>Symcol.Rulesets.Core</AssemblyName>
|
||||||
|
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
|
||||||
|
<FileAlignment>512</FileAlignment>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
<DebugType>full</DebugType>
|
||||||
|
<Optimize>false</Optimize>
|
||||||
|
<OutputPath>bin\Debug\</OutputPath>
|
||||||
|
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||||
|
<DebugType>pdbonly</DebugType>
|
||||||
|
<Optimize>true</Optimize>
|
||||||
|
<OutputPath>bin\Release\</OutputPath>
|
||||||
|
<DefineConstants>TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="OpenTK, Version=3.0.0.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4" />
|
||||||
|
<Reference Include="System" />
|
||||||
|
<Reference Include="System.Core" />
|
||||||
|
<Reference Include="System.Xml.Linq" />
|
||||||
|
<Reference Include="System.Data.DataSetExtensions" />
|
||||||
|
<Reference Include="Microsoft.CSharp" />
|
||||||
|
<Reference Include="System.Data" />
|
||||||
|
<Reference Include="System.Net.Http" />
|
||||||
|
<Reference Include="System.Xml" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="Containers\LinkText.cs" />
|
||||||
|
<Compile Include="Containers\ProfileLink.cs" />
|
||||||
|
<Compile Include="HitObjects\DrawableSymcolHitObject.cs" />
|
||||||
|
<Compile Include="Multiplayer\Pieces\Chat.cs" />
|
||||||
|
<Compile Include="Multiplayer\Pieces\ChatMessage.cs" />
|
||||||
|
<Compile Include="Multiplayer\Networking\ChatPacket.cs" />
|
||||||
|
<Compile Include="Multiplayer\Pieces\MatchTools.cs" />
|
||||||
|
<Compile Include="Multiplayer\Networking\RulesetClientInfo.cs" />
|
||||||
|
<Compile Include="Multiplayer\Pieces\MatchPlayer.cs" />
|
||||||
|
<Compile Include="Multiplayer\Pieces\MatchPlayerList.cs" />
|
||||||
|
<Compile Include="Multiplayer\Options\MultiplayerDropdownEnumOption.cs" />
|
||||||
|
<Compile Include="Multiplayer\Options\MultiplayerOption.cs" />
|
||||||
|
<Compile Include="Multiplayer\Options\MultiplayerToggleOption.cs" />
|
||||||
|
<Compile Include="Multiplayer\Screens\MatchSongSelect.cs" />
|
||||||
|
<Compile Include="Multiplayer\Screens\MultiPlayer.cs" />
|
||||||
|
<Compile Include="Multiplayer\Screens\RulesetLobbyScreen.cs" />
|
||||||
|
<Compile Include="Multiplayer\Screens\RulesetMatchScreen.cs" />
|
||||||
|
<Compile Include="Multiplayer\Screens\RulesetMultiplayerSelection.cs" />
|
||||||
|
<Compile Include="Multiplayer\Networking\RulesetNetworkingClientHandler.cs" />
|
||||||
|
<Compile Include="Multiplayer\Networking\RulesetPacket.cs" />
|
||||||
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
|
<Compile Include="Skinning\SkinElement.cs" />
|
||||||
|
<Compile Include="Skinning\SkinIniReader.cs" />
|
||||||
|
<Compile Include="SymcolConfigManager.cs" />
|
||||||
|
<Compile Include="SymcolPlayfield.cs" />
|
||||||
|
<Compile Include="SymcolSettingsSubsection.cs" />
|
||||||
|
<Compile Include="SymcolInputManager.cs" />
|
||||||
|
<Compile Include="VectorVideos\VectorVideo.cs" />
|
||||||
|
<Compile Include="Wiki\WikiOptionEnumExplanation.cs" />
|
||||||
|
<Compile Include="Wiki\WikiParagraph.cs" />
|
||||||
|
<Compile Include="Wiki\WikiSubSectionHeader.cs" />
|
||||||
|
<Compile Include="Wiki\WikiOverlay.cs" />
|
||||||
|
<Compile Include="Wiki\WikiHeader.cs" />
|
||||||
|
<Compile Include="Wiki\WikiSection.cs" />
|
||||||
|
<Compile Include="Wiki\WikiSubSectionLinkHeader.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\osu-Framework\osu.Framework\osu.Framework.csproj">
|
||||||
|
<Project>{c76bf5b3-985e-4d39-95fe-97c9c879b83a}</Project>
|
||||||
|
<Name>osu.Framework</Name>
|
||||||
|
</ProjectReference>
|
||||||
|
<ProjectReference Include="..\osu-resources\osu.Game.Resources\osu.Game.Resources.csproj">
|
||||||
|
<Project>{d9a367c9-4c1a-489f-9b05-a0cea2b53b58}</Project>
|
||||||
|
<Name>osu.Game.Resources</Name>
|
||||||
|
</ProjectReference>
|
||||||
|
<ProjectReference Include="..\osu.Game\osu.Game.csproj">
|
||||||
|
<Project>{2a66dd92-adb1-4994-89e2-c94e04acda0d}</Project>
|
||||||
|
<Name>osu.Game</Name>
|
||||||
|
</ProjectReference>
|
||||||
|
<ProjectReference Include="..\Symcol.Core\Symcol.Core.csproj">
|
||||||
|
<Project>{F34AC16C-E590-4D70-A069-A748326852BF}</Project>
|
||||||
|
<Name>Symcol.Core</Name>
|
||||||
|
</ProjectReference>
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
using osu.Framework.Configuration;
|
||||||
|
using osu.Framework.Platform;
|
||||||
|
|
||||||
|
namespace Symcol.Rulesets.Core
|
||||||
|
{
|
||||||
|
public class SymcolConfigManager : IniConfigManager<SymcolSetting>
|
||||||
|
{
|
||||||
|
protected override string Filename => "symcol.ini";
|
||||||
|
|
||||||
|
public SymcolConfigManager(Storage storage) : base(storage) { }
|
||||||
|
|
||||||
|
protected override void InitialiseDefaults()
|
||||||
|
{
|
||||||
|
Set(SymcolSetting.PlayerColor, "#ffffff");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum SymcolSetting
|
||||||
|
{
|
||||||
|
PlayerColor
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Input.Bindings;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
using Symcol.Rulesets.Core.VectorVideos;
|
||||||
|
|
||||||
|
namespace Symcol.Rulesets.Core
|
||||||
|
{
|
||||||
|
public class SymcolInputManager<T> : RulesetInputManager<T>
|
||||||
|
where T : struct
|
||||||
|
{
|
||||||
|
protected virtual bool VectorVideo => false;
|
||||||
|
|
||||||
|
public SymcolInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) : base(ruleset, variant, unique)
|
||||||
|
{
|
||||||
|
Child = new VectorVideo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using OpenTK;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
using Symcol.Rulesets.Core.Multiplayer.Networking;
|
||||||
|
|
||||||
|
namespace Symcol.Rulesets.Core
|
||||||
|
{
|
||||||
|
public class SymcolPlayfield : Playfield
|
||||||
|
{
|
||||||
|
public static RulesetNetworkingClientHandler RulesetNetworkingClientHandler;
|
||||||
|
|
||||||
|
public SymcolPlayfield(Vector2 size) : base(size.X)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
//osu.Game.Screens.Symcol.SymcolMenu
|
||||||
|
//Symcol.Rulesets.Core.Multiplayer.Screens.RulesetMultiplayerSelection
|
||||||
|
//#define SymcolMods
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Game;
|
||||||
|
using osu.Game.Overlays.Settings;
|
||||||
|
using Symcol.Rulesets.Core.Wiki;
|
||||||
|
using osu.Game.Screens.Symcol;
|
||||||
|
using Symcol.Rulesets.Core.Multiplayer.Screens;
|
||||||
|
using osu.Framework.Platform;
|
||||||
|
using osu.Framework.Logging;
|
||||||
|
|
||||||
|
namespace Symcol.Rulesets.Core
|
||||||
|
{
|
||||||
|
public abstract class SymcolSettingsSubsection : SettingsSubsection
|
||||||
|
{
|
||||||
|
public virtual WikiOverlay Wiki => null;
|
||||||
|
|
||||||
|
public virtual RulesetLobbyItem RulesetLobbyItem => null;
|
||||||
|
|
||||||
|
#if SymcolMods
|
||||||
|
public static RulesetMultiplayerSelection RulesetMultiplayerSelection;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public static SymcolConfigManager SymcolConfigManager;
|
||||||
|
|
||||||
|
private OsuGame osu;
|
||||||
|
|
||||||
|
public SymcolSettingsSubsection()
|
||||||
|
{
|
||||||
|
|
||||||
|
#if SymcolMods
|
||||||
|
if (RulesetLobbyItem != null)
|
||||||
|
RulesetMultiplayerSelection.LobbyItems.Add(RulesetLobbyItem);
|
||||||
|
|
||||||
|
if (RulesetMultiplayerSelection == null)
|
||||||
|
RulesetMultiplayerSelection = new RulesetMultiplayerSelection();
|
||||||
|
|
||||||
|
SymcolMenu.RulesetMultiplayerScreen = RulesetMultiplayerSelection;
|
||||||
|
#endif
|
||||||
|
#if !SymcolMods
|
||||||
|
Logger.Log("osu.Game mods not installed! Online Multiplayer will not be avalible without them. . .", LoggingTarget.Information, LogLevel.Important);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuGame osu, Storage storage)
|
||||||
|
{
|
||||||
|
this.osu = osu;
|
||||||
|
|
||||||
|
if (SymcolConfigManager == null)
|
||||||
|
SymcolConfigManager = new SymcolConfigManager(storage);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
if (Wiki != null)
|
||||||
|
osu.Add(Wiki);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
using OpenTK;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Logging;
|
||||||
|
using osu.Framework.Platform;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
|
||||||
|
namespace Symcol.Rulesets.Core.VectorVideos
|
||||||
|
{
|
||||||
|
public class VectorVideo : BeatSyncedContainer
|
||||||
|
{
|
||||||
|
public const string FILE_NAME = "VectorVideo.symcol";
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(Storage storage)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void LoadContent(string args)
|
||||||
|
{
|
||||||
|
string[] parameters = args.Split(',');
|
||||||
|
|
||||||
|
ObjectType objectType = ObjectType.LogoVisualizer;
|
||||||
|
Anchor anchor = Anchor.Centre;
|
||||||
|
Anchor origin = Anchor.Centre;
|
||||||
|
|
||||||
|
bool checkingType = false;
|
||||||
|
|
||||||
|
foreach (string parameter in parameters)
|
||||||
|
{
|
||||||
|
string[] subParameters = parameter.Split('=');
|
||||||
|
|
||||||
|
foreach (string subParameter in subParameters)
|
||||||
|
{
|
||||||
|
if (subParameter == "Type")
|
||||||
|
checkingType = true;
|
||||||
|
|
||||||
|
if (checkingType)
|
||||||
|
switch (subParameter)
|
||||||
|
{
|
||||||
|
case "LogoVisualizer":
|
||||||
|
objectType = ObjectType.LogoVisualizer;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadLogoVisualizer()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ObjectType
|
||||||
|
{
|
||||||
|
LogoVisualizer
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Graphics.Textures;
|
||||||
|
using osu.Game.Users;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using OpenTK;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using OpenTK.Graphics;
|
||||||
|
using Symcol.Rulesets.Core.Containers;
|
||||||
|
|
||||||
|
namespace Symcol.Rulesets.Core.Wiki
|
||||||
|
{
|
||||||
|
public abstract class WikiHeader : Container
|
||||||
|
{
|
||||||
|
protected abstract Texture RulesetIcon { get; }
|
||||||
|
|
||||||
|
protected abstract string RulesetName { get; }
|
||||||
|
|
||||||
|
protected abstract string RulesetDescription { get; }
|
||||||
|
|
||||||
|
protected virtual string RulesetUrl => $@"https://osu.ppy.sh/home";
|
||||||
|
|
||||||
|
protected virtual User Creator => null;
|
||||||
|
|
||||||
|
protected virtual User Maintainer => null;
|
||||||
|
|
||||||
|
protected virtual string DiscordInvite => $@"https://discord.gg/ppy";
|
||||||
|
|
||||||
|
protected virtual Texture HeaderBackground => null;
|
||||||
|
|
||||||
|
private const float description_height = 150;
|
||||||
|
private const float description_width = 220;
|
||||||
|
private const float icon_size = 200;
|
||||||
|
private const float header_margin = 50;
|
||||||
|
private const float rulesetname_height = 60;
|
||||||
|
|
||||||
|
public WikiHeader()
|
||||||
|
{
|
||||||
|
Masking = true;
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
Height = header_margin + icon_size + rulesetname_height;
|
||||||
|
|
||||||
|
|
||||||
|
var user = Creator;
|
||||||
|
bool maintainer = false;
|
||||||
|
string userTitle = "Creator";
|
||||||
|
if (Creator == null)
|
||||||
|
{
|
||||||
|
user = Maintainer;
|
||||||
|
maintainer = true;
|
||||||
|
userTitle = "Maintainer";
|
||||||
|
}
|
||||||
|
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Sprite
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
FillMode = FillMode.Fill,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Texture = HeaderBackground
|
||||||
|
},
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = Color4.Black,
|
||||||
|
Alpha = 0.5f,
|
||||||
|
},
|
||||||
|
new Sprite
|
||||||
|
{
|
||||||
|
Size = new Vector2(icon_size),
|
||||||
|
Anchor = Anchor.TopLeft,
|
||||||
|
Origin = Anchor.TopLeft,
|
||||||
|
Texture = RulesetIcon
|
||||||
|
},
|
||||||
|
new LinkText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopLeft,
|
||||||
|
Origin = Anchor.TopLeft,
|
||||||
|
Position = new Vector2(10, icon_size),
|
||||||
|
Text = RulesetName,
|
||||||
|
Url = RulesetUrl,
|
||||||
|
Font = @"Exo2.0-RegularItalic",
|
||||||
|
TextSize = rulesetname_height
|
||||||
|
},
|
||||||
|
new ProfileLink(user, maintainer)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopLeft,
|
||||||
|
Origin = Anchor.TopLeft,
|
||||||
|
Position = new Vector2(10, icon_size + rulesetname_height),
|
||||||
|
},
|
||||||
|
new LinkText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopLeft,
|
||||||
|
Origin = Anchor.TopLeft,
|
||||||
|
Position = new Vector2(10, icon_size + rulesetname_height + 20),
|
||||||
|
Text = userTitle + "'s Discord server",
|
||||||
|
Url = DiscordInvite,
|
||||||
|
Font = @"Exo2.0-RegularItalic",
|
||||||
|
TextSize = 16
|
||||||
|
},
|
||||||
|
new OsuTextFlowContainer(t => { t.TextSize = 20; })
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreRight,
|
||||||
|
Origin = Anchor.CentreRight,
|
||||||
|
Size = new Vector2(description_width, description_height),
|
||||||
|
Text = RulesetDescription
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
using OpenTK;
|
||||||
|
using osu.Framework.Configuration;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Overlays.Settings;
|
||||||
|
|
||||||
|
namespace Symcol.Rulesets.Core.Wiki
|
||||||
|
{
|
||||||
|
public class WikiOptionEnumExplanation<T> : Container
|
||||||
|
where T : struct
|
||||||
|
{
|
||||||
|
public OsuTextFlowContainer Description;
|
||||||
|
|
||||||
|
public WikiOptionEnumExplanation(Bindable<T> bindable)
|
||||||
|
{
|
||||||
|
OsuColour osu = new OsuColour();
|
||||||
|
Anchor = Anchor.TopCentre;
|
||||||
|
Origin = Anchor.TopCentre;
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
Masking = true;
|
||||||
|
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Colour = osu.Yellow,
|
||||||
|
Masking = true,
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
Size = new Vector2(10, 0.98f),
|
||||||
|
CornerRadius = 5,
|
||||||
|
|
||||||
|
Child = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
Position = new Vector2(-10, 0),
|
||||||
|
Anchor = Anchor.TopLeft,
|
||||||
|
Origin = Anchor.TopLeft,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Width = 0.45f,
|
||||||
|
|
||||||
|
Child = new SettingsEnumDropdown<T>
|
||||||
|
{
|
||||||
|
Bindable = bindable
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopRight,
|
||||||
|
Origin = Anchor.TopRight,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Width = 0.45f,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
AutoSizeDuration = 100,
|
||||||
|
AutoSizeEasing = Easing.OutQuint,
|
||||||
|
|
||||||
|
Child = Description = new OsuTextFlowContainer(t => { t.TextSize = 20; })
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreRight,
|
||||||
|
Origin = Anchor.CentreRight,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
using OpenTK.Graphics;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Symcol.Rulesets.Core.Wiki
|
||||||
|
{
|
||||||
|
public abstract class WikiOverlay : WaveOverlayContainer
|
||||||
|
{
|
||||||
|
protected abstract WikiHeader Header { get; }
|
||||||
|
protected abstract WikiSection[] Sections { get; }
|
||||||
|
|
||||||
|
private WikiSection lastSection;
|
||||||
|
private SectionsContainer<WikiSection> sectionsContainer;
|
||||||
|
private WikiTabControl tabs;
|
||||||
|
|
||||||
|
public const float CONTENT_X_MARGIN = 100;
|
||||||
|
|
||||||
|
public WikiOverlay()
|
||||||
|
{
|
||||||
|
FirstWaveColour = OsuColour.Gray(0.4f);
|
||||||
|
SecondWaveColour = OsuColour.Gray(0.3f);
|
||||||
|
ThirdWaveColour = OsuColour.Gray(0.2f);
|
||||||
|
FourthWaveColour = OsuColour.Gray(0.1f);
|
||||||
|
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
RelativePositionAxes = Axes.Both;
|
||||||
|
Width = 0.85f;
|
||||||
|
Anchor = Anchor.TopCentre;
|
||||||
|
Origin = Anchor.TopCentre;
|
||||||
|
|
||||||
|
Masking = true;
|
||||||
|
AlwaysPresent = true;
|
||||||
|
EdgeEffect = new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Colour = Color4.Black.Opacity(0),
|
||||||
|
Type = EdgeEffectType.Shadow,
|
||||||
|
Radius = 10
|
||||||
|
};
|
||||||
|
|
||||||
|
tabs = new WikiTabControl
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Height = 30
|
||||||
|
};
|
||||||
|
|
||||||
|
Add(new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = OsuColour.Gray(0.2f)
|
||||||
|
});
|
||||||
|
|
||||||
|
Add(sectionsContainer = new SectionsContainer<WikiSection>
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
ExpandableHeader = Header,
|
||||||
|
FixedHeader = tabs,
|
||||||
|
HeaderBackground = new Box
|
||||||
|
{
|
||||||
|
Colour = OsuColour.Gray(34),
|
||||||
|
RelativeSizeAxes = Axes.Both
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
sectionsContainer.SelectedSection.ValueChanged += s =>
|
||||||
|
{
|
||||||
|
if (lastSection != s)
|
||||||
|
{
|
||||||
|
lastSection = s;
|
||||||
|
tabs.Current.Value = lastSection;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
tabs.Current.ValueChanged += s =>
|
||||||
|
{
|
||||||
|
if (lastSection == null)
|
||||||
|
{
|
||||||
|
lastSection = sectionsContainer.Children.FirstOrDefault();
|
||||||
|
if (lastSection != null)
|
||||||
|
tabs.Current.Value = lastSection;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (lastSection != s)
|
||||||
|
{
|
||||||
|
lastSection = s;
|
||||||
|
sectionsContainer.ScrollTo(lastSection);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (WikiSection sec in Sections)
|
||||||
|
{
|
||||||
|
if (sec != null)
|
||||||
|
{
|
||||||
|
sectionsContainer.Add(sec);
|
||||||
|
tabs.AddItem(sec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sectionsContainer.ScrollToTop();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void PopIn()
|
||||||
|
{
|
||||||
|
base.PopIn();
|
||||||
|
FadeEdgeEffectTo(0.5f, APPEAR_DURATION, Easing.In);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void PopOut()
|
||||||
|
{
|
||||||
|
base.PopOut();
|
||||||
|
FadeEdgeEffectTo(0, DISAPPEAR_DURATION, Easing.Out);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class WikiTabControl : PageTabControl<WikiSection>
|
||||||
|
{
|
||||||
|
public WikiTabControl()
|
||||||
|
{
|
||||||
|
TabContainer.RelativeSizeAxes &= ~Axes.X;
|
||||||
|
TabContainer.AutoSizeAxes |= Axes.X;
|
||||||
|
TabContainer.Anchor |= Anchor.x1;
|
||||||
|
TabContainer.Origin |= Anchor.x1;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override TabItem<WikiSection> CreateTabItem(WikiSection value) => new WikiTabItem(value);
|
||||||
|
|
||||||
|
protected override Dropdown<WikiSection> CreateDropdown() => null;
|
||||||
|
|
||||||
|
private class WikiTabItem : PageTabItem
|
||||||
|
{
|
||||||
|
public WikiTabItem(WikiSection value) : base(value)
|
||||||
|
{
|
||||||
|
Text.Text = value.Title;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
|
||||||
|
namespace Symcol.Rulesets.Core.Wiki
|
||||||
|
{
|
||||||
|
public class WikiParagraph : Container
|
||||||
|
{
|
||||||
|
public WikiParagraph(string text, float textsize = 20)
|
||||||
|
{
|
||||||
|
paragraphNoMarkdown(text, textsize);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WikiParagraph(string text, bool markdown)
|
||||||
|
{
|
||||||
|
if (!markdown)
|
||||||
|
paragraphNoMarkdown(text, 20);
|
||||||
|
else
|
||||||
|
paragraphMarkdown(text, 20);
|
||||||
|
}
|
||||||
|
public WikiParagraph(string text, float textsize, bool markdown)
|
||||||
|
{
|
||||||
|
if (!markdown)
|
||||||
|
paragraphNoMarkdown(text, textsize);
|
||||||
|
else
|
||||||
|
paragraphMarkdown(text, textsize);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void paragraphNoMarkdown(string text, float textsize)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre;
|
||||||
|
Origin = Anchor.TopCentre;
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
|
||||||
|
Child = new OsuTextFlowContainer(t => { t.TextSize = textsize; })
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Text = text
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void paragraphMarkdown(string text, float textsize)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre;
|
||||||
|
Origin = Anchor.TopCentre;
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
|
||||||
|
Child = new OsuTextFlowContainer(t => { t.TextSize = textsize; })
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Text = text
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
using OpenTK;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
|
||||||
|
namespace Symcol.Rulesets.Core.Wiki
|
||||||
|
{
|
||||||
|
public abstract class WikiSection : FillFlowContainer
|
||||||
|
{
|
||||||
|
public abstract string Title { get; }
|
||||||
|
|
||||||
|
private readonly FillFlowContainer content;
|
||||||
|
|
||||||
|
protected override Container<Drawable> Content => content;
|
||||||
|
|
||||||
|
protected WikiSection()
|
||||||
|
{
|
||||||
|
OsuColour osu = new OsuColour();
|
||||||
|
Direction = FillDirection.Vertical;
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Colour = osu.Yellow,
|
||||||
|
Text = Title,
|
||||||
|
TextSize = 32,
|
||||||
|
Font = @"Exo2.0-Bold",
|
||||||
|
Margin = new MarginPadding
|
||||||
|
{
|
||||||
|
Horizontal = WikiOverlay.CONTENT_X_MARGIN,
|
||||||
|
Vertical = 12
|
||||||
|
}
|
||||||
|
},
|
||||||
|
content = new FillFlowContainer
|
||||||
|
{
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Padding = new MarginPadding
|
||||||
|
{
|
||||||
|
Horizontal = WikiOverlay.CONTENT_X_MARGIN,
|
||||||
|
Bottom = 20
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = 1,
|
||||||
|
Colour = OsuColour.Gray(34),
|
||||||
|
EdgeSmoothness = new Vector2(1)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
|
||||||
|
namespace Symcol.Rulesets.Core.Wiki
|
||||||
|
{
|
||||||
|
public class WikiSubSectionHeader : OsuSpriteText
|
||||||
|
{
|
||||||
|
public WikiSubSectionHeader(string text)
|
||||||
|
{
|
||||||
|
OsuColour osu = new OsuColour();
|
||||||
|
Colour = osu.Pink;
|
||||||
|
Text = text;
|
||||||
|
TextSize = 24;
|
||||||
|
Font = @"Exo2.0-BoldItalic";
|
||||||
|
Margin = new MarginPadding
|
||||||
|
{
|
||||||
|
Vertical = 10
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using Symcol.Rulesets.Core.Containers;
|
||||||
|
|
||||||
|
namespace Symcol.Rulesets.Core.Wiki
|
||||||
|
{
|
||||||
|
public class WikiSubSectionLinkHeader : LinkText
|
||||||
|
{
|
||||||
|
public override string Tooltip => tooltip;
|
||||||
|
|
||||||
|
private string tooltip = "";
|
||||||
|
|
||||||
|
public WikiSubSectionLinkHeader(string text, string url, string tooltip = "")
|
||||||
|
{
|
||||||
|
this.tooltip = tooltip;
|
||||||
|
Url = url;
|
||||||
|
OsuColour osu = new OsuColour();
|
||||||
|
Colour = osu.Pink;
|
||||||
|
Text = text;
|
||||||
|
TextSize = 24;
|
||||||
|
Font = @"Exo2.0-BoldItalic";
|
||||||
|
Margin = new MarginPadding
|
||||||
|
{
|
||||||
|
Vertical = 10
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -241,6 +241,10 @@
|
|||||||
<Project>{f167e17a-7de6-4af5-b920-a5112296c695}</Project>
|
<Project>{f167e17a-7de6-4af5-b920-a5112296c695}</Project>
|
||||||
<Name>osu.Game.Rulesets.Taiko</Name>
|
<Name>osu.Game.Rulesets.Taiko</Name>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
|
<ProjectReference Include="..\osu.Game.Rulesets.Vitaru\osu.Game.Rulesets.Vitaru.csproj">
|
||||||
|
<Project>{9a615027-b2ab-435e-b865-0c7209933457}</Project>
|
||||||
|
<Name>osu.Game.Rulesets.Vitaru</Name>
|
||||||
|
</ProjectReference>
|
||||||
<ProjectReference Include="..\osu.Game.Tests\osu.Game.Tests.csproj">
|
<ProjectReference Include="..\osu.Game.Tests\osu.Game.Tests.csproj">
|
||||||
<Project>{54377672-20b1-40af-8087-5cf73bf3953a}</Project>
|
<Project>{54377672-20b1-40af-8087-5cf73bf3953a}</Project>
|
||||||
<Name>osu.Game.Tests</Name>
|
<Name>osu.Game.Tests</Name>
|
||||||
@@ -249,6 +253,14 @@
|
|||||||
<Project>{2a66dd92-adb1-4994-89e2-c94e04acda0d}</Project>
|
<Project>{2a66dd92-adb1-4994-89e2-c94e04acda0d}</Project>
|
||||||
<Name>osu.Game</Name>
|
<Name>osu.Game</Name>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
|
<ProjectReference Include="..\Symcol.Core\Symcol.Core.csproj">
|
||||||
|
<Project>{f34ac16c-e590-4d70-a069-a748326852bf}</Project>
|
||||||
|
<Name>Symcol.Core</Name>
|
||||||
|
</ProjectReference>
|
||||||
|
<ProjectReference Include="..\Symcol.Rulesets.Core\Symcol.Rulesets.Core.csproj">
|
||||||
|
<Project>{552b5940-c647-4060-aa4d-61baac415c72}</Project>
|
||||||
|
<Name>Symcol.Rulesets.Core</Name>
|
||||||
|
</ProjectReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
info face="vitaru!font" size=100 bold=0 italic=0 charset="" unicode=1 stretchH=100 smooth=1 aa=4 padding=0,0,0,0 spacing=4,4 outline=0
|
||||||
|
common lineHeight=100 base=83 scaleW=1024 scaleH=1024 pages=1 packed=0 alphaChnl=0 redChnl=4 greenChnl=4 blueChnl=4
|
||||||
|
page id=0 file="vitaruFont_0.png"
|
||||||
|
chars count=1
|
||||||
|
char id=57419 x=495 y=537 width=88 height=89 xoffset=0 yoffset=8 xadvance=89 page=0 chnl=15
|
||||||
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 2.0 MiB |
|
After Width: | Height: | Size: 746 B |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 9.2 KiB |
|
After Width: | Height: | Size: 8.2 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 9.2 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 9.6 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 9.6 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 9.6 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 61 KiB |
@@ -0,0 +1,273 @@
|
|||||||
|
using OpenTK;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Vitaru.Objects;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using System;
|
||||||
|
using osu.Game.Audio;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Game.Rulesets.Vitaru.Settings;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Vitaru.Beatmaps
|
||||||
|
{
|
||||||
|
internal class VitaruBeatmapConverter : BeatmapConverter<VitaruHitObject>
|
||||||
|
{
|
||||||
|
private VitaruGamemode currentGameMode = VitaruSettings.VitaruConfigManager.GetBindable<VitaruGamemode>(VitaruSetting.GameMode);
|
||||||
|
|
||||||
|
public static List<HitObject> HitObjectList = new List<HitObject>();
|
||||||
|
|
||||||
|
protected override IEnumerable<Type> ValidConversionTypes { get; } = new[] { typeof(IHasPosition) };
|
||||||
|
|
||||||
|
private float ar;
|
||||||
|
private float cs;
|
||||||
|
|
||||||
|
protected override IEnumerable<VitaruHitObject> ConvertHitObject(HitObject original, Beatmap beatmap)
|
||||||
|
{
|
||||||
|
var curveData = original as IHasCurve;
|
||||||
|
var endTimeData = original as IHasEndTime;
|
||||||
|
var positionData = original as IHasPosition;
|
||||||
|
var comboData = original as IHasCombo;
|
||||||
|
|
||||||
|
List<SampleInfo> samples = original.Samples;
|
||||||
|
|
||||||
|
int difficulty = 2;
|
||||||
|
if (currentGameMode == VitaruGamemode.Dodge)
|
||||||
|
difficulty = 1;
|
||||||
|
|
||||||
|
ar = calculateAr(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate);
|
||||||
|
cs = calculateCs(beatmap.BeatmapInfo.BaseDifficulty.CircleSize);
|
||||||
|
|
||||||
|
bool isLine = samples.Any(s => s.Name == SampleInfo.HIT_WHISTLE);
|
||||||
|
bool isTriangleWave = samples.Any(s => s.Name == SampleInfo.HIT_FINISH);
|
||||||
|
bool isCoolWave = samples.Any(s => s.Name == SampleInfo.HIT_CLAP);
|
||||||
|
|
||||||
|
Pattern p;
|
||||||
|
Pattern a;
|
||||||
|
|
||||||
|
if (curveData != null)
|
||||||
|
{
|
||||||
|
if (isLine)
|
||||||
|
{
|
||||||
|
p = new Pattern
|
||||||
|
{
|
||||||
|
Ar = ar,
|
||||||
|
Cs = cs,
|
||||||
|
StartTime = original.StartTime,
|
||||||
|
Position = positionData?.Position ?? Vector2.Zero,
|
||||||
|
Samples = original.Samples,
|
||||||
|
PatternID = 2,
|
||||||
|
PatternAngleDegree = 180,
|
||||||
|
PatternSpeed = 0.25f,
|
||||||
|
PatternDifficulty = difficulty,
|
||||||
|
PatternBulletDiameter = 8f * difficulty,
|
||||||
|
PatternTeam = 1,
|
||||||
|
EnemyHealth = 60,
|
||||||
|
ControlPoints = curveData.ControlPoints,
|
||||||
|
CurveType = curveData.CurveType,
|
||||||
|
Distance = curveData.Distance,
|
||||||
|
RepeatSamples = curveData.RepeatSamples,
|
||||||
|
RepeatCount = curveData.RepeatCount,
|
||||||
|
NewCombo = comboData?.NewCombo ?? false,
|
||||||
|
IsSlider = true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else if (isTriangleWave)
|
||||||
|
{
|
||||||
|
p = new Pattern
|
||||||
|
{
|
||||||
|
Ar = ar,
|
||||||
|
Cs = cs,
|
||||||
|
StartTime = original.StartTime,
|
||||||
|
Position = positionData?.Position ?? Vector2.Zero,
|
||||||
|
Samples = original.Samples,
|
||||||
|
PatternID = 3,
|
||||||
|
PatternAngleDegree = 180,
|
||||||
|
PatternSpeed = 0.25f,
|
||||||
|
PatternDifficulty = difficulty,
|
||||||
|
PatternBulletDiameter = 8f * difficulty,
|
||||||
|
PatternTeam = 1,
|
||||||
|
EnemyHealth = 60,
|
||||||
|
ControlPoints = curveData.ControlPoints,
|
||||||
|
CurveType = curveData.CurveType,
|
||||||
|
Distance = curveData.Distance,
|
||||||
|
RepeatSamples = curveData.RepeatSamples,
|
||||||
|
RepeatCount = curveData.RepeatCount,
|
||||||
|
NewCombo = comboData?.NewCombo ?? false,
|
||||||
|
IsSlider = true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else if (isCoolWave)
|
||||||
|
{
|
||||||
|
p = new Pattern
|
||||||
|
{
|
||||||
|
Ar = ar,
|
||||||
|
Cs = cs,
|
||||||
|
StartTime = original.StartTime,
|
||||||
|
Position = positionData?.Position ?? Vector2.Zero,
|
||||||
|
Samples = original.Samples,
|
||||||
|
PatternID = 4,
|
||||||
|
PatternAngleDegree = 180,
|
||||||
|
PatternSpeed = 0.25f,
|
||||||
|
PatternDifficulty = difficulty,
|
||||||
|
PatternBulletDiameter = 6f * difficulty,
|
||||||
|
PatternTeam = 1,
|
||||||
|
EnemyHealth = 60,
|
||||||
|
ControlPoints = curveData.ControlPoints,
|
||||||
|
CurveType = curveData.CurveType,
|
||||||
|
Distance = curveData.Distance,
|
||||||
|
RepeatSamples = curveData.RepeatSamples,
|
||||||
|
RepeatCount = curveData.RepeatCount,
|
||||||
|
NewCombo = comboData?.NewCombo ?? false,
|
||||||
|
IsSlider = true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
p = new Pattern
|
||||||
|
{
|
||||||
|
Ar = ar,
|
||||||
|
Cs = cs,
|
||||||
|
StartTime = original.StartTime,
|
||||||
|
Position = positionData?.Position ?? Vector2.Zero,
|
||||||
|
Samples = original.Samples,
|
||||||
|
PatternID = 1,
|
||||||
|
PatternAngleDegree = 180,
|
||||||
|
PatternSpeed = 0.20f,
|
||||||
|
PatternDifficulty = difficulty,
|
||||||
|
PatternBulletDiameter = 8f * difficulty,
|
||||||
|
PatternTeam = 1,
|
||||||
|
EnemyHealth = 60,
|
||||||
|
ControlPoints = curveData.ControlPoints,
|
||||||
|
CurveType = curveData.CurveType,
|
||||||
|
Distance = curveData.Distance,
|
||||||
|
RepeatSamples = curveData.RepeatSamples,
|
||||||
|
RepeatCount = curveData.RepeatCount,
|
||||||
|
NewCombo = comboData?.NewCombo ?? false,
|
||||||
|
IsSlider = true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (endTimeData != null)
|
||||||
|
{
|
||||||
|
p = new Pattern
|
||||||
|
{
|
||||||
|
Ar = ar,
|
||||||
|
Cs = cs,
|
||||||
|
StartTime = original.StartTime,
|
||||||
|
Position = positionData?.Position ?? Vector2.Zero,
|
||||||
|
Samples = original.Samples,
|
||||||
|
IsSpinner = true,
|
||||||
|
PatternSpeed = 0.25f,
|
||||||
|
PatternBulletDiameter = 8f * difficulty,
|
||||||
|
PatternTeam = 1,
|
||||||
|
EnemyHealth = 120,
|
||||||
|
PatternDamage = 5,
|
||||||
|
PatternID = 5,
|
||||||
|
EndTime = endTimeData.EndTime,
|
||||||
|
PatternDifficulty = 2 * difficulty,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (isLine)
|
||||||
|
{
|
||||||
|
p = new Pattern
|
||||||
|
{
|
||||||
|
Ar = ar,
|
||||||
|
Cs = cs,
|
||||||
|
StartTime = original.StartTime,
|
||||||
|
Position = positionData?.Position ?? Vector2.Zero,
|
||||||
|
Samples = original.Samples,
|
||||||
|
PatternID = 2,
|
||||||
|
PatternAngleDegree = 180,
|
||||||
|
PatternSpeed = 0.2f,
|
||||||
|
PatternDifficulty = difficulty * 2,
|
||||||
|
PatternDamage = 8,
|
||||||
|
PatternBulletDiameter = 10f * difficulty,
|
||||||
|
PatternTeam = 1,
|
||||||
|
NewCombo = comboData?.NewCombo ?? false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else if (isTriangleWave)
|
||||||
|
{
|
||||||
|
p = new Pattern
|
||||||
|
{
|
||||||
|
Ar = ar,
|
||||||
|
Cs = cs,
|
||||||
|
StartTime = original.StartTime,
|
||||||
|
Position = positionData?.Position ?? Vector2.Zero,
|
||||||
|
Samples = original.Samples,
|
||||||
|
PatternID = 3,
|
||||||
|
PatternAngleDegree = 180,
|
||||||
|
PatternSpeed = 0.3f,
|
||||||
|
PatternDifficulty = difficulty,
|
||||||
|
PatternBulletDiameter = 10f * difficulty,
|
||||||
|
PatternTeam = 1,
|
||||||
|
NewCombo = comboData?.NewCombo ?? false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else if (isCoolWave)
|
||||||
|
{
|
||||||
|
p = new Pattern
|
||||||
|
{
|
||||||
|
Ar = ar,
|
||||||
|
Cs = cs,
|
||||||
|
StartTime = original.StartTime,
|
||||||
|
Position = positionData?.Position ?? Vector2.Zero,
|
||||||
|
Samples = original.Samples,
|
||||||
|
PatternID = 4,
|
||||||
|
PatternAngleDegree = 180,
|
||||||
|
PatternSpeed = 0.18f,
|
||||||
|
PatternDifficulty = difficulty,
|
||||||
|
PatternBulletDiameter = 10f * difficulty,
|
||||||
|
PatternTeam = 1,
|
||||||
|
NewCombo = comboData?.NewCombo ?? false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
p = new Pattern
|
||||||
|
{
|
||||||
|
Ar = ar,
|
||||||
|
Cs = cs,
|
||||||
|
StartTime = original.StartTime,
|
||||||
|
Position = positionData?.Position ?? Vector2.Zero,
|
||||||
|
Samples = original.Samples,
|
||||||
|
PatternID = 1,
|
||||||
|
PatternAngleDegree = 180,
|
||||||
|
PatternSpeed = 0.28f,
|
||||||
|
PatternDifficulty = difficulty,
|
||||||
|
PatternBulletDiameter = 12f * difficulty,
|
||||||
|
PatternTeam = 1,
|
||||||
|
NewCombo = comboData?.NewCombo ?? false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a = p;
|
||||||
|
HitObjectList.Add(a);
|
||||||
|
yield return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
private float calculateAr(float ar)
|
||||||
|
{
|
||||||
|
if (ar >= 5)
|
||||||
|
{
|
||||||
|
this.ar = 1200 - ((ar - 5) * 150);
|
||||||
|
return this.ar;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.ar = 1800 - (ar * 120);
|
||||||
|
return this.ar;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private float calculateCs(float cs)
|
||||||
|
{
|
||||||
|
this.cs = cs / 4;
|
||||||
|
return this.cs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Vitaru.Objects;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Vitaru.Beatmaps
|
||||||
|
{
|
||||||
|
internal class VitaruBeatmapProcessor : BeatmapProcessor<VitaruHitObject>
|
||||||
|
{
|
||||||
|
public override void PostProcess(Beatmap<VitaruHitObject> 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.ComboIndex = comboIndex++;
|
||||||
|
obj.ComboColour = beatmap.ComboColours[colourIndex];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Vitaru.Objects;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Vitaru.Beatmaps
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Most of this is copied from OsuDifficultyCalculator ATM
|
||||||
|
/// </summary>
|
||||||
|
public class VitaruDifficultyCalculator : DifficultyCalculator<VitaruHitObject>
|
||||||
|
{
|
||||||
|
private const double star_scaling_factor = 0.0675;
|
||||||
|
|
||||||
|
internal List<VitaruHitObjectDifficulty> DifficultyHitObjects = new List<VitaruHitObjectDifficulty>();
|
||||||
|
|
||||||
|
public VitaruDifficultyCalculator(Beatmap beatmap, Mod[] mods) : base(beatmap, mods) { }
|
||||||
|
|
||||||
|
protected override void PreprocessHitObjects()
|
||||||
|
{
|
||||||
|
//foreach (Pattern h in Beatmap.HitObjects)
|
||||||
|
//h.Curve?.Calculate();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override BeatmapConverter<VitaruHitObject> CreateBeatmapConverter(Beatmap beatmap) => new VitaruBeatmapConverter();
|
||||||
|
|
||||||
|
public override double Calculate(Dictionary<string, double> categoryDifficulty = null)
|
||||||
|
{
|
||||||
|
// Fill our custom DifficultyHitObject class, that carries additional information
|
||||||
|
DifficultyHitObjects.Clear();
|
||||||
|
|
||||||
|
foreach (VitaruHitObject hitObject in Beatmap.HitObjects)
|
||||||
|
DifficultyHitObjects.Add(new VitaruHitObjectDifficulty(hitObject));
|
||||||
|
|
||||||
|
// Sort DifficultyHitObjects by StartTime of the HitObjects - just to make sure.
|
||||||
|
DifficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime.CompareTo(b.BaseHitObject.StartTime));
|
||||||
|
|
||||||
|
if (!CalculateStrainValues()) return 0;
|
||||||
|
|
||||||
|
double speedDifficulty = CalculateDifficulty(DifficultyType.Speed) * 0.75f;
|
||||||
|
double aimDifficulty = CalculateDifficulty(DifficultyType.Aim) * 1.5f;
|
||||||
|
|
||||||
|
double speedStars = Math.Sqrt(speedDifficulty) * star_scaling_factor;
|
||||||
|
double aimStars = Math.Sqrt(aimDifficulty) * star_scaling_factor;
|
||||||
|
|
||||||
|
double starRating = aimStars + speedStars + Math.Abs(aimStars - speedStars) / 2;
|
||||||
|
|
||||||
|
if (categoryDifficulty != null)
|
||||||
|
{
|
||||||
|
categoryDifficulty.Add("Aim", aimStars);
|
||||||
|
categoryDifficulty.Add("Speed", speedStars);
|
||||||
|
}
|
||||||
|
|
||||||
|
return starRating;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected bool CalculateStrainValues()
|
||||||
|
{
|
||||||
|
// Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment.
|
||||||
|
using (List<VitaruHitObjectDifficulty>.Enumerator hitObjectsEnumerator = DifficultyHitObjects.GetEnumerator())
|
||||||
|
{
|
||||||
|
|
||||||
|
if (!hitObjectsEnumerator.MoveNext()) return false;
|
||||||
|
|
||||||
|
VitaruHitObjectDifficulty current = hitObjectsEnumerator.Current;
|
||||||
|
|
||||||
|
// First hitObject starts at strain 1. 1 is the default for strain values, so we don't need to set it here. See DifficultyHitObject.
|
||||||
|
while (hitObjectsEnumerator.MoveNext())
|
||||||
|
{
|
||||||
|
var next = hitObjectsEnumerator.Current;
|
||||||
|
next?.CalculateStrains(current, TimeRate);
|
||||||
|
current = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected const double STRAIN_STEP = 200;
|
||||||
|
protected const double DECAY_WEIGHT = 0.75;
|
||||||
|
|
||||||
|
protected double CalculateDifficulty(DifficultyType type)
|
||||||
|
{
|
||||||
|
double actualStrainStep = STRAIN_STEP * TimeRate;
|
||||||
|
|
||||||
|
List<double> highestStrains = new List<double>();
|
||||||
|
double intervalEndTime = actualStrainStep;
|
||||||
|
double maximumStrain = 0;
|
||||||
|
|
||||||
|
VitaruHitObjectDifficulty previousHitObject = null;
|
||||||
|
foreach (VitaruHitObjectDifficulty hitObject in DifficultyHitObjects)
|
||||||
|
{
|
||||||
|
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(VitaruHitObjectDifficulty.DECAY_BASE[(int)type], (intervalEndTime - previousHitObject.BaseHitObject.StartTime) / 1000);
|
||||||
|
maximumStrain = previousHitObject.Strains[(int)type] * decay;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go to the next time interval
|
||||||
|
intervalEndTime += actualStrainStep;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtain maximum strain
|
||||||
|
maximumStrain = Math.Max(hitObject.Strains[(int)type], 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum DifficultyType
|
||||||
|
{
|
||||||
|
Speed = 0,
|
||||||
|
Aim,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
using OpenTK;
|
||||||
|
using OpenTK.Graphics;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using Symcol.Core.Graphics.UserInterface;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Vitaru.Edit.Pieces
|
||||||
|
{
|
||||||
|
public class PatternEditor : SymcolWindow
|
||||||
|
{
|
||||||
|
public PatternEditor() : base(new Vector2(300, 400))
|
||||||
|
{
|
||||||
|
Scale = new Vector2(2);
|
||||||
|
WindowTitle.Text = "Pattern Editor";
|
||||||
|
WindowContent.Child = new Box
|
||||||
|
{
|
||||||
|
Colour = Color4.Black,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Alpha = 0.25f
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using osu.Framework.Graphics.Cursor;
|
||||||
|
using osu.Game.Rulesets.Judgements;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Vitaru.Edit.Pieces;
|
||||||
|
using osu.Game.Rulesets.Vitaru.UI;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Vitaru.Edit
|
||||||
|
{
|
||||||
|
public class VitaruEditPlayfield : VitaruPlayfield
|
||||||
|
{
|
||||||
|
public override bool LoadPlayer => false;
|
||||||
|
|
||||||
|
//public override bool ProvidingUserCursor => false;
|
||||||
|
|
||||||
|
protected override CursorContainer CreateCursor() => null;
|
||||||
|
|
||||||
|
private readonly PatternEditor patternEditor;
|
||||||
|
|
||||||
|
public VitaruEditPlayfield()
|
||||||
|
{
|
||||||
|
Add(patternEditor = new PatternEditor());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||