1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-13 22:43:24 +08:00

Compare commits

...

8 Commits

207 changed files with 17530 additions and 1 deletions
+80
View File
@@ -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;
}
}
}
+63
View File
@@ -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)
{
}
}
}
+23
View File
@@ -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;
}
}
+119
View File
@@ -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
}
}
+23
View File
@@ -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;
}
}
}
+35
View File
@@ -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")]
+95
View File
@@ -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();
}
}
}
+15
View File
@@ -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
}
}
+114
View File
@@ -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
}
}
};
}
}
}
+147
View File
@@ -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
};
}
}
}
+59
View File
@@ -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
};
}
}
}
+12
View File
@@ -241,6 +241,10 @@
<Project>{f167e17a-7de6-4af5-b920-a5112296c695}</Project>
<Name>osu.Game.Rulesets.Taiko</Name>
</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">
<Project>{54377672-20b1-40af-8087-5cf73bf3953a}</Project>
<Name>osu.Game.Tests</Name>
@@ -249,6 +253,14 @@
<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>
<ProjectReference Include="..\Symcol.Rulesets.Core\Symcol.Rulesets.Core.csproj">
<Project>{552b5940-c647-4060-aa4d-61baac415c72}</Project>
<Name>Symcol.Rulesets.Core</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<!-- 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
Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 746 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

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());
}
}
}

Some files were not shown because too many files have changed in this diff Show More