diff --git a/Symcol.Core/GameObjects/SymcolHitbox.cs b/Symcol.Core/GameObjects/SymcolHitbox.cs new file mode 100644 index 0000000000..a4da576b68 --- /dev/null +++ b/Symcol.Core/GameObjects/SymcolHitbox.cs @@ -0,0 +1,80 @@ +using osu.Framework.Graphics; +using OpenTK; +using Symcol.Core.Graphics.Containers; + +namespace Symcol.Core.GameObjects +{ + public class SymcolHitbox : SymcolContainer + { + /// + /// whether we want to do hit detection + /// + public int Team { get; set; } + + /// + /// whether we want to do hit detection + /// + public bool HitDetection { get; set; } = true; + + /// + /// the shape of this object (used for hit detection) + /// + 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 + } +} diff --git a/Symcol.Core/Graphics/Containers/SymcolClickableContainer.cs b/Symcol.Core/Graphics/Containers/SymcolClickableContainer.cs new file mode 100644 index 0000000000..4e63117493 --- /dev/null +++ b/Symcol.Core/Graphics/Containers/SymcolClickableContainer.cs @@ -0,0 +1,12 @@ +using osu.Framework.Graphics.Containers; + +namespace Symcol.Core.Graphics.Containers +{ + /// + /// Will support base eden game functions (if we come up with any) + /// + public class SymcolClickableContainer : ClickableContainer + { + + } +} diff --git a/Symcol.Core/Graphics/Containers/SymcolContainer.cs b/Symcol.Core/Graphics/Containers/SymcolContainer.cs new file mode 100644 index 0000000000..e0bdf9503b --- /dev/null +++ b/Symcol.Core/Graphics/Containers/SymcolContainer.cs @@ -0,0 +1,12 @@ +using osu.Framework.Graphics.Containers; + +namespace Symcol.Core.Graphics.Containers +{ + /// + /// Will support base eden game functions (if we come up with any) + /// + public class SymcolContainer : Container + { + + } +} diff --git a/Symcol.Core/Graphics/Containers/SymcolDragContainer.cs b/Symcol.Core/Graphics/Containers/SymcolDragContainer.cs new file mode 100644 index 0000000000..1bc2d86248 --- /dev/null +++ b/Symcol.Core/Graphics/Containers/SymcolDragContainer.cs @@ -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); + } + } +} diff --git a/Symcol.Core/Graphics/UserInterface/SpriteButton.cs b/Symcol.Core/Graphics/UserInterface/SpriteButton.cs new file mode 100644 index 0000000000..a4db27a33c --- /dev/null +++ b/Symcol.Core/Graphics/UserInterface/SpriteButton.cs @@ -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 +{ + /// + /// just a Button with a sprite + /// + 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); + } + } +} diff --git a/Symcol.Core/Graphics/UserInterface/SymcolWindow.cs b/Symcol.Core/Graphics/UserInterface/SymcolWindow.cs new file mode 100644 index 0000000000..be6ee59c4e --- /dev/null +++ b/Symcol.Core/Graphics/UserInterface/SymcolWindow.cs @@ -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 + { + /// + /// Put all your stuff in this + /// + 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; + } + } +} diff --git a/Symcol.Core/Networking/BasicPacket.cs b/Symcol.Core/Networking/BasicPacket.cs new file mode 100644 index 0000000000..248a0328bf --- /dev/null +++ b/Symcol.Core/Networking/BasicPacket.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; + +namespace Symcol.Core.Networking +{ + [Serializable] + public class BasicPacket : Packet + { + /// + /// Ask host if we can connect + /// + public bool Connect; + + /// + /// Tell the host we are breaking up + /// + public bool Disconnect; + + /// + /// Testing Connection + /// + public bool Test; + + /// + /// Send a force exit to others + /// + public bool Abort; + + /// + /// PreLoad the game + /// + public bool LoadGame; + + /// + /// Request a list of all players from Host + /// + public bool RequestPlayerList; + + /// + /// List of players in this match that we should account for + /// + public List PlayerList = new List(); + + /// + /// Tell Host we are PreLoaded + /// + public bool Loaded; + + /// + /// Start the game already! + /// + public bool StartGame; + + /// + /// Send to host when game started + /// + public bool GameStarted; + + public BasicPacket(ClientInfo clientInfo) : base(clientInfo) + { + } + } +} diff --git a/Symcol.Core/Networking/ClientInfo.cs b/Symcol.Core/Networking/ClientInfo.cs new file mode 100644 index 0000000000..08c2d7c1e8 --- /dev/null +++ b/Symcol.Core/Networking/ClientInfo.cs @@ -0,0 +1,23 @@ +using System; + +namespace Symcol.Core.Networking +{ + /// + /// Just a client signature basically + /// + [Serializable] + public class ClientInfo + { + public string IP; + + public int Port; + + public int Ping; + + public int ConncetionTryCount; + + public double LastConnectionTime; + + public double StartedTestConnectionTime; + } +} diff --git a/Symcol.Core/Networking/NetworkingClient.cs b/Symcol.Core/Networking/NetworkingClient.cs new file mode 100644 index 0000000000..1e9af36e6e --- /dev/null +++ b/Symcol.Core/Networking/NetworkingClient.cs @@ -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; + + /// + /// if false we only receive + /// + 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; + + /// + /// Send a Packet somewhere + /// + /// + 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); + } + } + + /// + /// Receive a Packet from somewhere + /// + /// + 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(); + } + } +} diff --git a/Symcol.Core/Networking/NetworkingClientHandler.cs b/Symcol.Core/Networking/NetworkingClientHandler.cs new file mode 100644 index 0000000000..4f4b5ccc87 --- /dev/null +++ b/Symcol.Core/Networking/NetworkingClientHandler.cs @@ -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; + + /// + /// Just a client signature basically + /// + public ClientInfo ClientInfo; + + /// + /// All Connecting clients + /// + public readonly List ConnectingClients = new List(); + + /// + /// All Connected clients + /// + public readonly List ConncetedClients = new List(); + + /// + /// Clients waiting in our match + /// + public readonly List InMatchClients = new List(); + + /// + /// Clients loaded and ready to start + /// + public readonly List LoadedClients = new List(); + + /// + /// Clients ingame playing + /// + public readonly List InGameClients = new List(); + + /// + /// Gets hit when we get a Packet + /// + public Action OnPacketReceive; + + /// + /// (Peer) Call this when we connect to a Host (Includes list of connected peers + Host) + /// + public Action> OnConnectedToHost; + + /// + /// (Host) Whenever a new client Connects + /// + public Action OnClientConnect; + + /// + /// (Host) Whenever a new client Disconnects + /// + public Action OnClientDisconnect; + + /// + /// (Host/Peer) When a new Client joins the game + /// + public Action OnClientJoin; + + /// + /// Receive a full player list + /// + public Action> OnReceivePlayerList; + + /// + /// if we are connected and in a match + /// + public bool InMatch; + + /// + /// Are we in a game + /// + public bool InGame; + + /// + /// Are we loaded and ready to start? + /// + public bool Loaded; + + /// + /// Called to leave an in-progress game + /// + public Action OnAbort; + + /// + /// Called to load the game + /// + public Action> OnLoadGame; + + /// + /// Called to start the game once loaded + /// + 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 playerList = new List + { + 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 playerList = new List + { + 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; + } + } + } + + /// + /// Poke! + /// + /// + 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); + } + + /// + /// Tell peers to start loading game + /// + 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); + } + + /// + /// Call this when the game is Loaded and ready to be started + /// + public virtual void GameLoaded() + { + Loaded = true; + SendToHost(new BasicPacket(ClientInfo) { Loaded = true }); + } + + /// + /// Connects to the Host + /// + public virtual void ConnectToHost() + { + SendToHost(new BasicPacket(ClientInfo) { Connect = true }); + Logger.Log("Attempting conection to Host. . .", LoggingTarget.Network, LogLevel.Verbose); + } + + /// + /// Tell peers to start and starts ours + /// + 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(); + } + + /// + /// Send a Packet to the Host + /// + /// + public void SendToHost(Packet packet) + { + if (SendClient != null) + SendClient.SendPacket(packet); + } + + /// + /// Send a Packet to all Connecting clients + /// + /// + 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); + } + } + + /// + /// Send a Packet to all clients Connected and waiting + /// + /// + 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); + } + } + + /// + /// Send a Packet to all clients In this Match + /// + /// + 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); + } + } + + /// + /// Send a Packet to all clients Loaded + /// + /// + 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); + } + } + + /// + /// Send a Packet to all clients InGame + /// + /// + 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); + } + } + + /// + /// Send a Packet to ALL clients we know + /// + /// + public void SendToAllClients(Packet packet) + { + if (SendClient == null) + { + SendToConnectingClients(packet); + SendToConnectedClients(packet); + } + } + + /// + /// Send tto all but the one that sent it + /// + /// + /// + public void ShareWithOtherPeers(Packet packet) + { + if (SendClient == null) + foreach (ClientInfo clientInfo in InGameClients) + 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); + } + + /// + /// Die + /// + /// + 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 + } +} diff --git a/Symcol.Core/Networking/Packet.cs b/Symcol.Core/Networking/Packet.cs new file mode 100644 index 0000000000..22e4fa2921 --- /dev/null +++ b/Symcol.Core/Networking/Packet.cs @@ -0,0 +1,23 @@ +using System; + +namespace Symcol.Core.Networking +{ + [Serializable] + public class Packet + { + /// + /// Just a Signature + /// + public readonly ClientInfo ClientInfo; + + /// + /// Specify starting size of packet for efficiency + /// + public virtual int PacketSize => 1024; + + public Packet(ClientInfo clientInfo) + { + ClientInfo = clientInfo; + } + } +} diff --git a/Symcol.Core/Properties/AssemblyInfo.cs b/Symcol.Core/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..a3bfd12cc8 --- /dev/null +++ b/Symcol.Core/Properties/AssemblyInfo.cs @@ -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")] diff --git a/Symcol.Core/Symcol.Core.csproj b/Symcol.Core/Symcol.Core.csproj new file mode 100644 index 0000000000..9076a3e53d --- /dev/null +++ b/Symcol.Core/Symcol.Core.csproj @@ -0,0 +1,94 @@ + + + + + Debug + AnyCPU + {F34AC16C-E590-4D70-A069-A748326852BF} + Library + Properties + Symcol.Core + Symcol.Core + v4.6.1 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Cyotek.Drawing.BitmapFont.1.3.4-beta1\lib\net46\Cyotek.Drawing.BitmapFont.dll + + + ..\packages\ManagedBass.2.0.3\lib\net45\ManagedBass.dll + + + + $(SolutionDir)\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll + True + + + $(SolutionDir)\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll + True + + + $(SolutionDir)\packages\OpenTK.3.0.0-git00009\lib\net20\OpenTK.dll + True + + + $(SolutionDir)\packages\SQLite.Net.Core-PCL.3.1.1\lib\portable-win8+net45+wp8+wpa81+MonoAndroid1+MonoTouch1\SQLite.Net.dll + True + + + $(SolutionDir)\packages\SQLite.Net-PCL.3.1.1\lib\net40\SQLite.Net.Platform.Generic.dll + True + + + $(SolutionDir)\packages\SQLite.Net-PCL.3.1.1\lib\net4\SQLite.Net.Platform.Win32.dll + True + + + $(SolutionDir)\packages\SQLiteNetExtensions.1.3.0\lib\portable-net45+netcore45+wpa81+wp8+MonoAndroid1+MonoTouch1\SQLiteNetExtensions.dll + True + + + + + + + + + + + + + + + + + + + + + + + {c76bf5b3-985e-4d39-95fe-97c9c879b83a} + osu.Framework + + + + \ No newline at end of file diff --git a/Symcol.Rulesets.Core/Containers/LinkText.cs b/Symcol.Rulesets.Core/Containers/LinkText.cs new file mode 100644 index 0000000000..bba164e6dd --- /dev/null +++ b/Symcol.Rulesets.Core/Containers/LinkText.cs @@ -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 Content => content ?? (Container)this; + + public override IEnumerable FlowingChildren => Children; + + public string Url + { + set + { + if (value != null) + content.Action = () => Process.Start(value); + } + } + + public LinkText() + { + AddInternal(content = new OsuHoverContainer + { + AutoSizeAxes = Axes.Both, + }); + } + } +} diff --git a/Symcol.Rulesets.Core/Containers/ProfileLink.cs b/Symcol.Rulesets.Core/Containers/ProfileLink.cs new file mode 100644 index 0000000000..efc3be3a47 --- /dev/null +++ b/Symcol.Rulesets.Core/Containers/ProfileLink.cs @@ -0,0 +1,24 @@ +using osu.Game.Users; + +namespace Symcol.Rulesets.Core.Containers +{ + /// + /// TODO: make this more generic + /// + 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; + } + } +} diff --git a/Symcol.Rulesets.Core/Multiplayer/Networking/ChatPacket.cs b/Symcol.Rulesets.Core/Multiplayer/Networking/ChatPacket.cs new file mode 100644 index 0000000000..841de303ca --- /dev/null +++ b/Symcol.Rulesets.Core/Multiplayer/Networking/ChatPacket.cs @@ -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) + { + + } + } +} diff --git a/Symcol.Rulesets.Core/Multiplayer/Networking/RulesetClientInfo.cs b/Symcol.Rulesets.Core/Multiplayer/Networking/RulesetClientInfo.cs new file mode 100644 index 0000000000..d4cae8a947 --- /dev/null +++ b/Symcol.Rulesets.Core/Multiplayer/Networking/RulesetClientInfo.cs @@ -0,0 +1,24 @@ +using Symcol.Core.Networking; +using System; + +namespace Symcol.Rulesets.Core.Multiplayer.Networking +{ + /// + /// Just a client signature basically + /// + [Serializable] + public class RulesetClientInfo : ClientInfo + { + public string Username = ""; + + public int UserID = -1; + + public string UserPic; + + public string UserBackground; + + public string UserCountry; + + public string CountryFlagName; + } +} diff --git a/Symcol.Rulesets.Core/Multiplayer/Networking/RulesetNetworkingClientHandler.cs b/Symcol.Rulesets.Core/Multiplayer/Networking/RulesetNetworkingClientHandler.cs new file mode 100644 index 0000000000..a02662489d --- /dev/null +++ b/Symcol.Rulesets.Core/Multiplayer/Networking/RulesetNetworkingClientHandler.cs @@ -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 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; + } + } + + /// + /// Send Map to Peers + /// + /// + 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; + } + } + } +} diff --git a/Symcol.Rulesets.Core/Multiplayer/Networking/RulesetPacket.cs b/Symcol.Rulesets.Core/Multiplayer/Networking/RulesetPacket.cs new file mode 100644 index 0000000000..ab2f510378 --- /dev/null +++ b/Symcol.Rulesets.Core/Multiplayer/Networking/RulesetPacket.cs @@ -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; + } + } +} diff --git a/Symcol.Rulesets.Core/Multiplayer/Options/MultiplayerDropdownEnumOption.cs b/Symcol.Rulesets.Core/Multiplayer/Options/MultiplayerDropdownEnumOption.cs new file mode 100644 index 0000000000..a6eaa75b0c --- /dev/null +++ b/Symcol.Rulesets.Core/Multiplayer/Options/MultiplayerDropdownEnumOption.cs @@ -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 : MultiplayerOption + where T : struct + { + public readonly Bindable BindableEnum; + + public MultiplayerDropdownEnumOption(Bindable bindable, string name, int quadrant, bool sync = true) : base(name, quadrant, sync) + { + BindableEnum = bindable; + + OptionContainer.Child = new BetterSettingsEnumDropdown + { + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + RelativeSizeAxes = Axes.X, + Bindable = bindable, + }; + } + + private class BetterSettingsEnumDropdown : SettingsEnumDropdown + { + protected override Drawable CreateControl() => new BetterOsuEnumDropdown + { + Margin = new MarginPadding { Top = 5 }, + RelativeSizeAxes = Axes.X, + }; + + private class BetterOsuEnumDropdown : OsuEnumDropdown + { + public BetterOsuEnumDropdown() + { + Menu.MaxHeight = 160; + } + } + } + } +} diff --git a/Symcol.Rulesets.Core/Multiplayer/Options/MultiplayerOption.cs b/Symcol.Rulesets.Core/Multiplayer/Options/MultiplayerOption.cs new file mode 100644 index 0000000000..8130e6a151 --- /dev/null +++ b/Symcol.Rulesets.Core/Multiplayer/Options/MultiplayerOption.cs @@ -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, + } + }; + } + } +} diff --git a/Symcol.Rulesets.Core/Multiplayer/Options/MultiplayerToggleOption.cs b/Symcol.Rulesets.Core/Multiplayer/Options/MultiplayerToggleOption.cs new file mode 100644 index 0000000000..619dd8c4af --- /dev/null +++ b/Symcol.Rulesets.Core/Multiplayer/Options/MultiplayerToggleOption.cs @@ -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 BindableBool; + + public MultiplayerToggleOption(Bindable 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), + }; + } + } +} diff --git a/Symcol.Rulesets.Core/Multiplayer/Pieces/Chat.cs b/Symcol.Rulesets.Core/Multiplayer/Pieces/Chat.cs new file mode 100644 index 0000000000..8844400b61 --- /dev/null +++ b/Symcol.Rulesets.Core/Multiplayer/Pieces/Chat.cs @@ -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(SymcolSetting.PlayerColor); + + private User user; + + private readonly FillFlowContainer 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 + { + 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; + } + } + } +} diff --git a/Symcol.Rulesets.Core/Multiplayer/Pieces/ChatMessage.cs b/Symcol.Rulesets.Core/Multiplayer/Pieces/ChatMessage.cs new file mode 100644 index 0000000000..efca32349c --- /dev/null +++ b/Symcol.Rulesets.Core/Multiplayer/Pieces/ChatMessage.cs @@ -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 + } + }; + } + } +} diff --git a/Symcol.Rulesets.Core/Multiplayer/Pieces/MatchPlayer.cs b/Symcol.Rulesets.Core/Multiplayer/Pieces/MatchPlayer.cs new file mode 100644 index 0000000000..2f4762f5b2 --- /dev/null +++ b/Symcol.Rulesets.Core/Multiplayer/Pieces/MatchPlayer.cs @@ -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, () => { }), + }; + } +} diff --git a/Symcol.Rulesets.Core/Multiplayer/Pieces/MatchPlayerList.cs b/Symcol.Rulesets.Core/Multiplayer/Pieces/MatchPlayerList.cs new file mode 100644 index 0000000000..0aa332c1fd --- /dev/null +++ b/Symcol.Rulesets.Core/Multiplayer/Pieces/MatchPlayerList.cs @@ -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 MatchPlayers = new List(); + + 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(); + } + } +} diff --git a/Symcol.Rulesets.Core/Multiplayer/Pieces/MatchTools.cs b/Symcol.Rulesets.Core/Multiplayer/Pieces/MatchTools.cs new file mode 100644 index 0000000000..8e8d10ff6b --- /dev/null +++ b/Symcol.Rulesets.Core/Multiplayer/Pieces/MatchTools.cs @@ -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 Mode = new Bindable() { Default = MatchScreenMode.MapDetails }; + + public readonly Bindable GameMode = new Bindable() { Default = MatchGamemode.HeadToHead }; + + public readonly OsuTabControl 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 + { + 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(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 + } +} diff --git a/Symcol.Rulesets.Core/Multiplayer/Screens/MatchSongSelect.cs b/Symcol.Rulesets.Core/Multiplayer/Screens/MatchSongSelect.cs new file mode 100644 index 0000000000..018e31315f --- /dev/null +++ b/Symcol.Rulesets.Core/Multiplayer/Screens/MatchSongSelect.cs @@ -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; + } + } +} diff --git a/Symcol.Rulesets.Core/Multiplayer/Screens/MultiPlayer.cs b/Symcol.Rulesets.Core/Multiplayer/Screens/MultiPlayer.cs new file mode 100644 index 0000000000..0503553883 --- /dev/null +++ b/Symcol.Rulesets.Core/Multiplayer/Screens/MultiPlayer.cs @@ -0,0 +1,375 @@ +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 osu.Game.Screens.Play.BreaksOverlay; +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 dimLevel; + private Bindable blurLevel; + private Bindable showStoryboard; + private Bindable mouseWheelDisabled; + private Bindable 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(OsuSetting.DimLevel); + blurLevel = config.GetBindable(OsuSetting.BlurLevel); + showStoryboard = config.GetBindable(OsuSetting.ShowStoryboard); + + mouseWheelDisabled = config.GetBindable(OsuSetting.MouseDisableWheel); + + sampleRestart = audio.Sample.Get(@"Gameplay/restart"); + userAudioOffset = config.GetBindable(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()) + mod.ApplyToScoreProcessor(scoreProcessor); + } + + private void applyRateFromMods() + { + if (sourceClock == null) return; + + sourceClock.Rate = 1; + foreach (var mod in Beatmap.Value.Mods.Value.OfType()) + 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().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; + } +} diff --git a/Symcol.Rulesets.Core/Multiplayer/Screens/RulesetLobbyScreen.cs b/Symcol.Rulesets.Core/Multiplayer/Screens/RulesetLobbyScreen.cs new file mode 100644 index 0000000000..ba7ad6d0e8 --- /dev/null +++ b/Symcol.Rulesets.Core/Multiplayer/Screens/RulesetLobbyScreen.cs @@ -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 list = new List(); + 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 clientInfos) + { + Remove(RulesetNetworkingClientHandler); + MakeCurrent(); + Push(MatchScreen); + } + } +} diff --git a/Symcol.Rulesets.Core/Multiplayer/Screens/RulesetMatchScreen.cs b/Symcol.Rulesets.Core/Multiplayer/Screens/RulesetMatchScreen.cs new file mode 100644 index 0000000000..c9927e6b6b --- /dev/null +++ b/Symcol.Rulesets.Core/Multiplayer/Screens/RulesetMatchScreen.cs @@ -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 playerList) + { + MakeCurrent(); + } + + private void openSongSelect() + { + MatchSongSelect songSelect = new MatchSongSelect(RulesetNetworkingClientHandler); + MakeCurrent(); + Push(songSelect); + songSelect.Action = () => RulesetNetworkingClientHandler.SetMap(songSelect.SelectedMap); + } + } +} diff --git a/Symcol.Rulesets.Core/Multiplayer/Screens/RulesetMultiplayerSelection.cs b/Symcol.Rulesets.Core/Multiplayer/Screens/RulesetMultiplayerSelection.cs new file mode 100644 index 0000000000..5bda26ddfb --- /dev/null +++ b/Symcol.Rulesets.Core/Multiplayer/Screens/RulesetMultiplayerSelection.cs @@ -0,0 +1,99 @@ +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 LobbyItems = new FillFlowContainer + { + 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); + SymcolSettingsSubsection.RulesetMultiplayerSelection = new RulesetMultiplayerSelection(); + SymcolMenu.RulesetMultiplayerScreen = SymcolSettingsSubsection.RulesetMultiplayerSelection; + 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) + } + }; + } + } +} diff --git a/Symcol.Rulesets.Core/Properties/AssemblyInfo.cs b/Symcol.Rulesets.Core/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..08debf866f --- /dev/null +++ b/Symcol.Rulesets.Core/Properties/AssemblyInfo.cs @@ -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")] diff --git a/Symcol.Rulesets.Core/Skinning/SkinElement.cs b/Symcol.Rulesets.Core/Skinning/SkinElement.cs new file mode 100644 index 0000000000..f5ab9a5d13 --- /dev/null +++ b/Symcol.Rulesets.Core/Skinning/SkinElement.cs @@ -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 skinResources; + private static TextureStore skinTextures; + + /// + /// Will attempt to get a skin element fron the skin, if no element is found return the default element + /// + /// + /// + /// + /// + /// + public static Texture GetSkinElement(TextureStore stockTextures, Bindable 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(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; + } + + /// + /// Will attempt to get a skin element from the skin, if no element is found return null + /// + /// + /// + /// + /// + public static Texture GetElement(Bindable 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(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; + } + } +} diff --git a/Symcol.Rulesets.Core/Skinning/SkinIniReader.cs b/Symcol.Rulesets.Core/Skinning/SkinIniReader.cs new file mode 100644 index 0000000000..56a3c752a0 --- /dev/null +++ b/Symcol.Rulesets.Core/Skinning/SkinIniReader.cs @@ -0,0 +1,25 @@ +using osu.Framework.Configuration; +using osu.Framework.Platform; + +namespace Symcol.Rulesets.Core.Skinning +{ + public class SkinConfigReader : IniConfigManager + 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 + } +} diff --git a/Symcol.Rulesets.Core/Symcol.Rulesets.Core.csproj b/Symcol.Rulesets.Core/Symcol.Rulesets.Core.csproj new file mode 100644 index 0000000000..ef0b7530ca --- /dev/null +++ b/Symcol.Rulesets.Core/Symcol.Rulesets.Core.csproj @@ -0,0 +1,98 @@ + + + + + Debug + AnyCPU + {552B5940-C647-4060-AA4D-61BAAC415C72} + Library + Properties + Symcol.Rulesets.Core + Symcol.Rulesets.Core + v4.6.1 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {c76bf5b3-985e-4d39-95fe-97c9c879b83a} + osu.Framework + + + {d9a367c9-4c1a-489f-9b05-a0cea2b53b58} + osu.Game.Resources + + + {2a66dd92-adb1-4994-89e2-c94e04acda0d} + osu.Game + + + {F34AC16C-E590-4D70-A069-A748326852BF} + Symcol.Core + + + + \ No newline at end of file diff --git a/Symcol.Rulesets.Core/SymcolConfigManager.cs b/Symcol.Rulesets.Core/SymcolConfigManager.cs new file mode 100644 index 0000000000..267096c3aa --- /dev/null +++ b/Symcol.Rulesets.Core/SymcolConfigManager.cs @@ -0,0 +1,22 @@ +using osu.Framework.Configuration; +using osu.Framework.Platform; + +namespace Symcol.Rulesets.Core +{ + public class SymcolConfigManager : IniConfigManager + { + protected override string Filename => "symcol.ini"; + + public SymcolConfigManager(Storage storage) : base(storage) { } + + protected override void InitialiseDefaults() + { + Set(SymcolSetting.PlayerColor, "#ffffff"); + } + } + + public enum SymcolSetting + { + PlayerColor + } +} diff --git a/Symcol.Rulesets.Core/SymcolInputManager.cs b/Symcol.Rulesets.Core/SymcolInputManager.cs new file mode 100644 index 0000000000..d374c79a17 --- /dev/null +++ b/Symcol.Rulesets.Core/SymcolInputManager.cs @@ -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 : RulesetInputManager + where T : struct + { + protected virtual bool VectorVideo => false; + + public SymcolInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) : base(ruleset, variant, unique) + { + Child = new VectorVideo(); + } + } +} diff --git a/Symcol.Rulesets.Core/SymcolPlayfield.cs b/Symcol.Rulesets.Core/SymcolPlayfield.cs new file mode 100644 index 0000000000..7d5f223cbf --- /dev/null +++ b/Symcol.Rulesets.Core/SymcolPlayfield.cs @@ -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) + { + } + } +} diff --git a/Symcol.Rulesets.Core/SymcolSettingsSubsection.cs b/Symcol.Rulesets.Core/SymcolSettingsSubsection.cs new file mode 100644 index 0000000000..ee9f457668 --- /dev/null +++ b/Symcol.Rulesets.Core/SymcolSettingsSubsection.cs @@ -0,0 +1,50 @@ +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; + +namespace Symcol.Rulesets.Core +{ + public abstract class SymcolSettingsSubsection : SettingsSubsection + { + public virtual WikiOverlay Wiki => null; + + public virtual RulesetLobbyItem RulesetLobbyItem => null; + + public static RulesetMultiplayerSelection RulesetMultiplayerSelection; + + public static SymcolConfigManager SymcolConfigManager; + + private OsuGame osu; + + public SymcolSettingsSubsection() + { + if (RulesetLobbyItem != null) + RulesetMultiplayerSelection.LobbyItems.Add(RulesetLobbyItem); + + if (RulesetMultiplayerSelection == null) + RulesetMultiplayerSelection = new RulesetMultiplayerSelection(); + SymcolMenu.RulesetMultiplayerScreen = RulesetMultiplayerSelection; + } + + [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); + } + } +} diff --git a/Symcol.Rulesets.Core/VectorVideos/VectorVideo.cs b/Symcol.Rulesets.Core/VectorVideos/VectorVideo.cs new file mode 100644 index 0000000000..47ed04bf9d --- /dev/null +++ b/Symcol.Rulesets.Core/VectorVideos/VectorVideo.cs @@ -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 + } +} diff --git a/Symcol.Rulesets.Core/Wiki/WikiHeader.cs b/Symcol.Rulesets.Core/Wiki/WikiHeader.cs new file mode 100644 index 0000000000..0bd0ab9185 --- /dev/null +++ b/Symcol.Rulesets.Core/Wiki/WikiHeader.cs @@ -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 + } + }; + } + } +} diff --git a/Symcol.Rulesets.Core/Wiki/WikiOptionEnumExplanation.cs b/Symcol.Rulesets.Core/Wiki/WikiOptionEnumExplanation.cs new file mode 100644 index 0000000000..761114b58c --- /dev/null +++ b/Symcol.Rulesets.Core/Wiki/WikiOptionEnumExplanation.cs @@ -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 : Container + where T : struct + { + public OsuTextFlowContainer Description; + + public WikiOptionEnumExplanation(Bindable 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 + { + 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 + } + } + }; + } + } +} diff --git a/Symcol.Rulesets.Core/Wiki/WikiOverlay.cs b/Symcol.Rulesets.Core/Wiki/WikiOverlay.cs new file mode 100644 index 0000000000..ede85c054f --- /dev/null +++ b/Symcol.Rulesets.Core/Wiki/WikiOverlay.cs @@ -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 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 + { + 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 + { + public WikiTabControl() + { + TabContainer.RelativeSizeAxes &= ~Axes.X; + TabContainer.AutoSizeAxes |= Axes.X; + TabContainer.Anchor |= Anchor.x1; + TabContainer.Origin |= Anchor.x1; + } + + protected override TabItem CreateTabItem(WikiSection value) => new WikiTabItem(value); + + protected override Dropdown CreateDropdown() => null; + + private class WikiTabItem : PageTabItem + { + public WikiTabItem(WikiSection value) : base(value) + { + Text.Text = value.Title; + } + } + } + + } +} diff --git a/Symcol.Rulesets.Core/Wiki/WikiParagraph.cs b/Symcol.Rulesets.Core/Wiki/WikiParagraph.cs new file mode 100644 index 0000000000..0914f99c9b --- /dev/null +++ b/Symcol.Rulesets.Core/Wiki/WikiParagraph.cs @@ -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 + }; + } + } +} diff --git a/Symcol.Rulesets.Core/Wiki/WikiSection.cs b/Symcol.Rulesets.Core/Wiki/WikiSection.cs new file mode 100644 index 0000000000..7a298f2f0d --- /dev/null +++ b/Symcol.Rulesets.Core/Wiki/WikiSection.cs @@ -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 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) + } + }; + } + } +} diff --git a/Symcol.Rulesets.Core/Wiki/WikiSubSectionHeader.cs b/Symcol.Rulesets.Core/Wiki/WikiSubSectionHeader.cs new file mode 100644 index 0000000000..16245ba4e8 --- /dev/null +++ b/Symcol.Rulesets.Core/Wiki/WikiSubSectionHeader.cs @@ -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 + }; + } + } +} diff --git a/Symcol.Rulesets.Core/Wiki/WikiSubSectionLinkHeader.cs b/Symcol.Rulesets.Core/Wiki/WikiSubSectionLinkHeader.cs new file mode 100644 index 0000000000..58e8e004f2 --- /dev/null +++ b/Symcol.Rulesets.Core/Wiki/WikiSubSectionLinkHeader.cs @@ -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 + }; + } + } +} diff --git a/osu.Game.Rulesets.Vitaru/Assets/Audio/Samples/deathSound.mp3 b/osu.Game.Rulesets.Vitaru/Assets/Audio/Samples/deathSound.mp3 new file mode 100644 index 0000000000..88fd4bb713 Binary files /dev/null and b/osu.Game.Rulesets.Vitaru/Assets/Audio/Samples/deathSound.mp3 differ diff --git a/osu.Game.Rulesets.Vitaru/Assets/Audio/Samples/gasterFire.mp3 b/osu.Game.Rulesets.Vitaru/Assets/Audio/Samples/gasterFire.mp3 new file mode 100644 index 0000000000..fee9d63be9 Binary files /dev/null and b/osu.Game.Rulesets.Vitaru/Assets/Audio/Samples/gasterFire.mp3 differ diff --git a/osu.Game.Rulesets.Vitaru/Assets/Audio/Samples/gasterReady.mp3 b/osu.Game.Rulesets.Vitaru/Assets/Audio/Samples/gasterReady.mp3 new file mode 100644 index 0000000000..198c443c21 Binary files /dev/null and b/osu.Game.Rulesets.Vitaru/Assets/Audio/Samples/gasterReady.mp3 differ diff --git a/osu.Game.Rulesets.Vitaru/Assets/Audio/Samples/shootSound.mp3 b/osu.Game.Rulesets.Vitaru/Assets/Audio/Samples/shootSound.mp3 new file mode 100644 index 0000000000..65ad88941e Binary files /dev/null and b/osu.Game.Rulesets.Vitaru/Assets/Audio/Samples/shootSound.mp3 differ diff --git a/osu.Game.Rulesets.Vitaru/Assets/Font/vitaruFont.fnt b/osu.Game.Rulesets.Vitaru/Assets/Font/vitaruFont.fnt new file mode 100644 index 0000000000..cb9eed3f34 --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Assets/Font/vitaruFont.fnt @@ -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 \ No newline at end of file diff --git a/osu.Game.Rulesets.Vitaru/Assets/Font/vitaruFont_0.png b/osu.Game.Rulesets.Vitaru/Assets/Font/vitaruFont_0.png new file mode 100644 index 0000000000..d5d80c2ad0 Binary files /dev/null and b/osu.Game.Rulesets.Vitaru/Assets/Font/vitaruFont_0.png differ diff --git a/osu.Game.Rulesets.Vitaru/Assets/Textures/Vitaru@2x.png b/osu.Game.Rulesets.Vitaru/Assets/Textures/Vitaru@2x.png new file mode 100644 index 0000000000..50b3e38755 Binary files /dev/null and b/osu.Game.Rulesets.Vitaru/Assets/Textures/Vitaru@2x.png differ diff --git a/osu.Game.Rulesets.Vitaru/Assets/Textures/VitaruTouhosuModeTrue2560x1440.png b/osu.Game.Rulesets.Vitaru/Assets/Textures/VitaruTouhosuModeTrue2560x1440.png new file mode 100644 index 0000000000..b4d07f4b4a Binary files /dev/null and b/osu.Game.Rulesets.Vitaru/Assets/Textures/VitaruTouhosuModeTrue2560x1440.png differ diff --git a/osu.Game.Rulesets.Vitaru/Assets/Textures/bulletKiai.png b/osu.Game.Rulesets.Vitaru/Assets/Textures/bulletKiai.png new file mode 100644 index 0000000000..26969224e1 Binary files /dev/null and b/osu.Game.Rulesets.Vitaru/Assets/Textures/bulletKiai.png differ diff --git a/osu.Game.Rulesets.Vitaru/Assets/Textures/chen.png b/osu.Game.Rulesets.Vitaru/Assets/Textures/chen.png new file mode 100644 index 0000000000..c9feb548e8 Binary files /dev/null and b/osu.Game.Rulesets.Vitaru/Assets/Textures/chen.png differ diff --git a/osu.Game.Rulesets.Vitaru/Assets/Textures/chenKiai.png b/osu.Game.Rulesets.Vitaru/Assets/Textures/chenKiai.png new file mode 100644 index 0000000000..1bcc766f20 Binary files /dev/null and b/osu.Game.Rulesets.Vitaru/Assets/Textures/chenKiai.png differ diff --git a/osu.Game.Rulesets.Vitaru/Assets/Textures/chenKiaiRight.png b/osu.Game.Rulesets.Vitaru/Assets/Textures/chenKiaiRight.png new file mode 100644 index 0000000000..9d8deda6aa Binary files /dev/null and b/osu.Game.Rulesets.Vitaru/Assets/Textures/chenKiaiRight.png differ diff --git a/osu.Game.Rulesets.Vitaru/Assets/Textures/chenRight.png b/osu.Game.Rulesets.Vitaru/Assets/Textures/chenRight.png new file mode 100644 index 0000000000..46640cc908 Binary files /dev/null and b/osu.Game.Rulesets.Vitaru/Assets/Textures/chenRight.png differ diff --git a/osu.Game.Rulesets.Vitaru/Assets/Textures/crystal.png b/osu.Game.Rulesets.Vitaru/Assets/Textures/crystal.png new file mode 100644 index 0000000000..902e079008 Binary files /dev/null and b/osu.Game.Rulesets.Vitaru/Assets/Textures/crystal.png differ diff --git a/osu.Game.Rulesets.Vitaru/Assets/Textures/enemy.png b/osu.Game.Rulesets.Vitaru/Assets/Textures/enemy.png new file mode 100644 index 0000000000..019131bc94 Binary files /dev/null and b/osu.Game.Rulesets.Vitaru/Assets/Textures/enemy.png differ diff --git a/osu.Game.Rulesets.Vitaru/Assets/Textures/enemyKiai.png b/osu.Game.Rulesets.Vitaru/Assets/Textures/enemyKiai.png new file mode 100644 index 0000000000..0c2ca58e17 Binary files /dev/null and b/osu.Game.Rulesets.Vitaru/Assets/Textures/enemyKiai.png differ diff --git a/osu.Game.Rulesets.Vitaru/Assets/Textures/icon.png b/osu.Game.Rulesets.Vitaru/Assets/Textures/icon.png new file mode 100644 index 0000000000..1ddedfbefc Binary files /dev/null and b/osu.Game.Rulesets.Vitaru/Assets/Textures/icon.png differ diff --git a/osu.Game.Rulesets.Vitaru/Assets/Textures/kaguya.png b/osu.Game.Rulesets.Vitaru/Assets/Textures/kaguya.png new file mode 100644 index 0000000000..f405cf7ce2 Binary files /dev/null and b/osu.Game.Rulesets.Vitaru/Assets/Textures/kaguya.png differ diff --git a/osu.Game.Rulesets.Vitaru/Assets/Textures/kaguyaKiai.png b/osu.Game.Rulesets.Vitaru/Assets/Textures/kaguyaKiai.png new file mode 100644 index 0000000000..25e341ffc8 Binary files /dev/null and b/osu.Game.Rulesets.Vitaru/Assets/Textures/kaguyaKiai.png differ diff --git a/osu.Game.Rulesets.Vitaru/Assets/Textures/kaguyaKiaiRight.png b/osu.Game.Rulesets.Vitaru/Assets/Textures/kaguyaKiaiRight.png new file mode 100644 index 0000000000..3f92d6051f Binary files /dev/null and b/osu.Game.Rulesets.Vitaru/Assets/Textures/kaguyaKiaiRight.png differ diff --git a/osu.Game.Rulesets.Vitaru/Assets/Textures/kaguyaRight.png b/osu.Game.Rulesets.Vitaru/Assets/Textures/kaguyaRight.png new file mode 100644 index 0000000000..6595c3e07f Binary files /dev/null and b/osu.Game.Rulesets.Vitaru/Assets/Textures/kaguyaRight.png differ diff --git a/osu.Game.Rulesets.Vitaru/Assets/Textures/marisa.png b/osu.Game.Rulesets.Vitaru/Assets/Textures/marisa.png new file mode 100644 index 0000000000..ccffd932cd Binary files /dev/null and b/osu.Game.Rulesets.Vitaru/Assets/Textures/marisa.png differ diff --git a/osu.Game.Rulesets.Vitaru/Assets/Textures/marisaKiai.png b/osu.Game.Rulesets.Vitaru/Assets/Textures/marisaKiai.png new file mode 100644 index 0000000000..3074869540 Binary files /dev/null and b/osu.Game.Rulesets.Vitaru/Assets/Textures/marisaKiai.png differ diff --git a/osu.Game.Rulesets.Vitaru/Assets/Textures/marisaKiaiRight.png b/osu.Game.Rulesets.Vitaru/Assets/Textures/marisaKiaiRight.png new file mode 100644 index 0000000000..c2e5f77f55 Binary files /dev/null and b/osu.Game.Rulesets.Vitaru/Assets/Textures/marisaKiaiRight.png differ diff --git a/osu.Game.Rulesets.Vitaru/Assets/Textures/marisaRight.png b/osu.Game.Rulesets.Vitaru/Assets/Textures/marisaRight.png new file mode 100644 index 0000000000..f2ac6b515e Binary files /dev/null and b/osu.Game.Rulesets.Vitaru/Assets/Textures/marisaRight.png differ diff --git a/osu.Game.Rulesets.Vitaru/Assets/Textures/nue.png b/osu.Game.Rulesets.Vitaru/Assets/Textures/nue.png new file mode 100644 index 0000000000..849fd35780 Binary files /dev/null and b/osu.Game.Rulesets.Vitaru/Assets/Textures/nue.png differ diff --git a/osu.Game.Rulesets.Vitaru/Assets/Textures/nueKiai.png b/osu.Game.Rulesets.Vitaru/Assets/Textures/nueKiai.png new file mode 100644 index 0000000000..ae50d8c467 Binary files /dev/null and b/osu.Game.Rulesets.Vitaru/Assets/Textures/nueKiai.png differ diff --git a/osu.Game.Rulesets.Vitaru/Assets/Textures/nueKiaiRight.png b/osu.Game.Rulesets.Vitaru/Assets/Textures/nueKiaiRight.png new file mode 100644 index 0000000000..452d275ca4 Binary files /dev/null and b/osu.Game.Rulesets.Vitaru/Assets/Textures/nueKiaiRight.png differ diff --git a/osu.Game.Rulesets.Vitaru/Assets/Textures/nueRight.png b/osu.Game.Rulesets.Vitaru/Assets/Textures/nueRight.png new file mode 100644 index 0000000000..f53c922a16 Binary files /dev/null and b/osu.Game.Rulesets.Vitaru/Assets/Textures/nueRight.png differ diff --git a/osu.Game.Rulesets.Vitaru/Assets/Textures/player.png b/osu.Game.Rulesets.Vitaru/Assets/Textures/player.png new file mode 100644 index 0000000000..696f68f075 Binary files /dev/null and b/osu.Game.Rulesets.Vitaru/Assets/Textures/player.png differ diff --git a/osu.Game.Rulesets.Vitaru/Assets/Textures/playerKiai.png b/osu.Game.Rulesets.Vitaru/Assets/Textures/playerKiai.png new file mode 100644 index 0000000000..21bc20677a Binary files /dev/null and b/osu.Game.Rulesets.Vitaru/Assets/Textures/playerKiai.png differ diff --git a/osu.Game.Rulesets.Vitaru/Assets/Textures/playerKiaiRight.png b/osu.Game.Rulesets.Vitaru/Assets/Textures/playerKiaiRight.png new file mode 100644 index 0000000000..a9dcd12577 Binary files /dev/null and b/osu.Game.Rulesets.Vitaru/Assets/Textures/playerKiaiRight.png differ diff --git a/osu.Game.Rulesets.Vitaru/Assets/Textures/playerRight.png b/osu.Game.Rulesets.Vitaru/Assets/Textures/playerRight.png new file mode 100644 index 0000000000..4ab761f611 Binary files /dev/null and b/osu.Game.Rulesets.Vitaru/Assets/Textures/playerRight.png differ diff --git a/osu.Game.Rulesets.Vitaru/Assets/Textures/reimu.png b/osu.Game.Rulesets.Vitaru/Assets/Textures/reimu.png new file mode 100644 index 0000000000..696f68f075 Binary files /dev/null and b/osu.Game.Rulesets.Vitaru/Assets/Textures/reimu.png differ diff --git a/osu.Game.Rulesets.Vitaru/Assets/Textures/reimuKiai.png b/osu.Game.Rulesets.Vitaru/Assets/Textures/reimuKiai.png new file mode 100644 index 0000000000..21bc20677a Binary files /dev/null and b/osu.Game.Rulesets.Vitaru/Assets/Textures/reimuKiai.png differ diff --git a/osu.Game.Rulesets.Vitaru/Assets/Textures/reimuKiaiRight.png b/osu.Game.Rulesets.Vitaru/Assets/Textures/reimuKiaiRight.png new file mode 100644 index 0000000000..a9dcd12577 Binary files /dev/null and b/osu.Game.Rulesets.Vitaru/Assets/Textures/reimuKiaiRight.png differ diff --git a/osu.Game.Rulesets.Vitaru/Assets/Textures/reimuRight.png b/osu.Game.Rulesets.Vitaru/Assets/Textures/reimuRight.png new file mode 100644 index 0000000000..4ab761f611 Binary files /dev/null and b/osu.Game.Rulesets.Vitaru/Assets/Textures/reimuRight.png differ diff --git a/osu.Game.Rulesets.Vitaru/Assets/Textures/ring.png b/osu.Game.Rulesets.Vitaru/Assets/Textures/ring.png new file mode 100644 index 0000000000..8b2c1187d3 Binary files /dev/null and b/osu.Game.Rulesets.Vitaru/Assets/Textures/ring.png differ diff --git a/osu.Game.Rulesets.Vitaru/Assets/Textures/sakuya.png b/osu.Game.Rulesets.Vitaru/Assets/Textures/sakuya.png new file mode 100644 index 0000000000..0d20edb20d Binary files /dev/null and b/osu.Game.Rulesets.Vitaru/Assets/Textures/sakuya.png differ diff --git a/osu.Game.Rulesets.Vitaru/Assets/Textures/sakuyaKiai.png b/osu.Game.Rulesets.Vitaru/Assets/Textures/sakuyaKiai.png new file mode 100644 index 0000000000..0bce3affa6 Binary files /dev/null and b/osu.Game.Rulesets.Vitaru/Assets/Textures/sakuyaKiai.png differ diff --git a/osu.Game.Rulesets.Vitaru/Assets/Textures/sakuyaKiaiRight.png b/osu.Game.Rulesets.Vitaru/Assets/Textures/sakuyaKiaiRight.png new file mode 100644 index 0000000000..928ddb57bf Binary files /dev/null and b/osu.Game.Rulesets.Vitaru/Assets/Textures/sakuyaKiaiRight.png differ diff --git a/osu.Game.Rulesets.Vitaru/Assets/Textures/sakuyaRight.png b/osu.Game.Rulesets.Vitaru/Assets/Textures/sakuyaRight.png new file mode 100644 index 0000000000..a906323b0f Binary files /dev/null and b/osu.Game.Rulesets.Vitaru/Assets/Textures/sakuyaRight.png differ diff --git a/osu.Game.Rulesets.Vitaru/Assets/Textures/sign.png b/osu.Game.Rulesets.Vitaru/Assets/Textures/sign.png new file mode 100644 index 0000000000..6be20f4e50 Binary files /dev/null and b/osu.Game.Rulesets.Vitaru/Assets/Textures/sign.png differ diff --git a/osu.Game.Rulesets.Vitaru/Assets/Textures/vortex.png b/osu.Game.Rulesets.Vitaru/Assets/Textures/vortex.png new file mode 100644 index 0000000000..12e145101e Binary files /dev/null and b/osu.Game.Rulesets.Vitaru/Assets/Textures/vortex.png differ diff --git a/osu.Game.Rulesets.Vitaru/Beatmaps/VitaruBeatmapConverter.cs b/osu.Game.Rulesets.Vitaru/Beatmaps/VitaruBeatmapConverter.cs new file mode 100644 index 0000000000..7065909a4f --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Beatmaps/VitaruBeatmapConverter.cs @@ -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 + { + private VitaruGamemode currentGameMode = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.GameMode); + + public static List HitObjectList = new List(); + + protected override IEnumerable ValidConversionTypes { get; } = new[] { typeof(IHasPosition) }; + + private float ar; + private float cs; + + protected override IEnumerable 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 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; + } + } +} diff --git a/osu.Game.Rulesets.Vitaru/Beatmaps/VitaruBeatmapProcessor.cs b/osu.Game.Rulesets.Vitaru/Beatmaps/VitaruBeatmapProcessor.cs new file mode 100644 index 0000000000..1f3f197170 --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Beatmaps/VitaruBeatmapProcessor.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 + { + public override void PostProcess(Beatmap beatmap) + { + if (beatmap.ComboColors.Count == 0) + return; + + int comboIndex = 0; + int colourIndex = 0; + + foreach (var obj in beatmap.HitObjects) + { + if (obj.NewCombo) + { + comboIndex = 0; + colourIndex = (colourIndex + 1) % beatmap.ComboColors.Count; + } + + obj.ComboIndex = comboIndex++; + obj.ComboColour = beatmap.ComboColors[colourIndex]; + } + } + } +} diff --git a/osu.Game.Rulesets.Vitaru/Beatmaps/VitaruDifficultyCalculator.cs b/osu.Game.Rulesets.Vitaru/Beatmaps/VitaruDifficultyCalculator.cs new file mode 100644 index 0000000000..40a82e97bb --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Beatmaps/VitaruDifficultyCalculator.cs @@ -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 +{ + /// + /// Most of this is copied from OsuDifficultyCalculator ATM + /// + public class VitaruDifficultyCalculator : DifficultyCalculator + { + private const double star_scaling_factor = 0.0675; + + internal List DifficultyHitObjects = new List(); + + 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 CreateBeatmapConverter(Beatmap beatmap) => new VitaruBeatmapConverter(); + + public override double Calculate(Dictionary 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.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 highestStrains = new List(); + 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, + }; + } +} diff --git a/osu.Game.Rulesets.Vitaru/Edit/Pieces/PatternEditor.cs b/osu.Game.Rulesets.Vitaru/Edit/Pieces/PatternEditor.cs new file mode 100644 index 0000000000..4e1015b328 --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Edit/Pieces/PatternEditor.cs @@ -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 + }; + } + } +} diff --git a/osu.Game.Rulesets.Vitaru/Edit/VitaruEditPlayfield.cs b/osu.Game.Rulesets.Vitaru/Edit/VitaruEditPlayfield.cs new file mode 100644 index 0000000000..cd9a9e9211 --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Edit/VitaruEditPlayfield.cs @@ -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()); + } + } +} diff --git a/osu.Game.Rulesets.Vitaru/Edit/VitaruEditRulesetContainer.cs b/osu.Game.Rulesets.Vitaru/Edit/VitaruEditRulesetContainer.cs new file mode 100644 index 0000000000..b0055cf9ce --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Edit/VitaruEditRulesetContainer.cs @@ -0,0 +1,16 @@ +using osu.Game.Beatmaps; +using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.Vitaru.UI; + +namespace osu.Game.Rulesets.Vitaru.Edit +{ + public class VitaruEditRulesetContainer : VitaruRulesetContainer + { + public VitaruEditRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset) + : base(ruleset, beatmap, isForCurrentRuleset) + { + } + + protected override Playfield CreatePlayfield() => new VitaruEditPlayfield(); + } +} diff --git a/osu.Game.Rulesets.Vitaru/Edit/VitaruHitObjectComposer.cs b/osu.Game.Rulesets.Vitaru/Edit/VitaruHitObjectComposer.cs new file mode 100644 index 0000000000..899b19f326 --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Edit/VitaruHitObjectComposer.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.Vitaru.Objects; + +namespace osu.Game.Rulesets.Vitaru.Edit +{ + public class VitaruHitObjectComposer : HitObjectComposer + { + public VitaruHitObjectComposer(Ruleset ruleset) : base(ruleset) { } + + protected override RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) => new VitaruEditRulesetContainer(ruleset, beatmap, true); + + protected override IReadOnlyList CompositionTools => new ICompositionTool[] + { + new HitObjectCompositionTool(), + new HitObjectCompositionTool(), + new HitObjectCompositionTool() + }; + } + + public enum EditorConfiguration + { + Simple, + Complex + } +} diff --git a/osu.Game.Rulesets.Vitaru/Judgements/VitaruJudgement.cs b/osu.Game.Rulesets.Vitaru/Judgements/VitaruJudgement.cs new file mode 100644 index 0000000000..ef506eb989 --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Judgements/VitaruJudgement.cs @@ -0,0 +1,16 @@ +using OpenTK; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Vitaru.Objects.Drawables; + +namespace osu.Game.Rulesets.Vitaru.Judgements +{ + public class VitaruJudgement : Judgement + { + /// + /// The positional hit offset. + /// + public Vector2 PositionOffset; + + public ComboResult Combo; + } +} diff --git a/osu.Game.Rulesets.Vitaru/Mods/VitaruMod.cs b/osu.Game.Rulesets.Vitaru/Mods/VitaruMod.cs new file mode 100644 index 0000000000..536f69221a --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Mods/VitaruMod.cs @@ -0,0 +1,81 @@ +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Vitaru.Objects; +using osu.Game.Rulesets.Scoring; +using osu.Game.Users; +using osu.Game.Rulesets.Vitaru.Replays; + +namespace osu.Game.Rulesets.Vitaru.Mods +{ + public class VitaruModNoFail : ModNoFail + { + + } + + public class VitaruModEasy : ModEasy + { + + } + + public class VitaruModHidden : ModHidden + { + public override string Description => @"Play with bullets dissapearing once they leave enemies immediate area"; + public override double ScoreMultiplier => 1.32; + } + + public class VitaruModHardRock : ModHardRock + { + public override double ScoreMultiplier => 1.12; + } + + public class VitaruModSuddenDeath : ModSuddenDeath + { + public override string Description => "Don't get hit"; + } + + public class VitaruModPerfect : ModPerfect + { + public override string Description => "Leave no survivors"; + } + + public class VitaruModDaycore : ModDaycore + { + public override double ScoreMultiplier => 0.4; + } + + public class VitaruModDoubleTime : ModDoubleTime + { + public override double ScoreMultiplier => 1.08; + } + + public class VitaruModHalfTime : ModHalfTime + { + public override double ScoreMultiplier => 0.4; + } + + public class VitaruModNightcore : ModNightcore + { + public override double ScoreMultiplier => 1.08; + } + + public class VitaruModFlashlight : ModFlashlight + { + public override string Description => @"Play with bullets only appearing when they are close"; + public override double ScoreMultiplier => 1.18; + } + + public class VitaruRelax : ModRelax + { + public override string Description => @"Player moves to the cursor instantly"; + public override bool Ranked => false; + } + + public class VitaruModAutoplay : ModAutoplay + {/* + protected override Score CreateReplayScore(Beatmap beatmap) => new Score + { + User = new User { Username = "reimosu!" }, + Replay = new VitaruAutoGenerator(beatmap).Generate(), + }; + */} +} diff --git a/osu.Game.Rulesets.Vitaru/Multi/VitaruClientInfo.cs b/osu.Game.Rulesets.Vitaru/Multi/VitaruClientInfo.cs new file mode 100644 index 0000000000..96bbe1cc9d --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Multi/VitaruClientInfo.cs @@ -0,0 +1,11 @@ +using Symcol.Rulesets.Core.Multiplayer.Networking; +using System; + +namespace osu.Game.Rulesets.Vitaru.Multi +{ + [Serializable] + public class VitaruClientInfo : RulesetClientInfo + { + public VitaruPlayerInformation PlayerInformation; + } +} diff --git a/osu.Game.Rulesets.Vitaru/Multi/VitaruInMatchPacket.cs b/osu.Game.Rulesets.Vitaru/Multi/VitaruInMatchPacket.cs new file mode 100644 index 0000000000..4bfc8e4486 --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Multi/VitaruInMatchPacket.cs @@ -0,0 +1,21 @@ +using Symcol.Core.Networking; +using System; + +namespace osu.Game.Rulesets.Vitaru.Multi +{ + [Serializable] + public class VitaruInMatchPacket : Packet + { + /// + /// This player's information + /// + public VitaruPlayerInformation PlayerInformation; + + public override int PacketSize => 2048; + + public VitaruInMatchPacket(ClientInfo clientInfo) : base(clientInfo) + { + + } + } +} diff --git a/osu.Game.Rulesets.Vitaru/Multi/VitaruLobbyItem.cs b/osu.Game.Rulesets.Vitaru/Multi/VitaruLobbyItem.cs new file mode 100644 index 0000000000..37ec055f25 --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Multi/VitaruLobbyItem.cs @@ -0,0 +1,16 @@ +using osu.Framework.Graphics.Textures; +using Symcol.Rulesets.Core.Multiplayer.Screens; + +namespace osu.Game.Rulesets.Vitaru.Multi +{ + public class VitaruLobbyItem : RulesetLobbyItem + { + public override Texture Icon => VitaruRuleset.VitaruTextures.Get("Vitaru@2x"); + + public override string RulesetName => "Vitaru!"; + + public override Texture Background => VitaruRuleset.VitaruTextures.Get("VitaruTouhosuModeTrue2560x1440"); + + public override RulesetLobbyScreen RulesetLobbyScreen => new VitaruLobbyScreen(); + } +} diff --git a/osu.Game.Rulesets.Vitaru/Multi/VitaruLobbyScreen.cs b/osu.Game.Rulesets.Vitaru/Multi/VitaruLobbyScreen.cs new file mode 100644 index 0000000000..3803924138 --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Multi/VitaruLobbyScreen.cs @@ -0,0 +1,67 @@ +using osu.Framework.Configuration; +using osu.Game.Rulesets.Vitaru.Settings; +using Symcol.Core.Networking; +using Symcol.Rulesets.Core.Multiplayer.Screens; +using System; +using System.Collections.Generic; + +namespace osu.Game.Rulesets.Vitaru.Multi +{ + public class VitaruLobbyScreen : RulesetLobbyScreen + { + public override string RulesetName => "vitaru"; + + public VitaruNetworkingClientHandler VitaruNetworkingClientHandler; + + public override RulesetMatchScreen MatchScreen => new VitaruMatchScreen(VitaruNetworkingClientHandler); + + private readonly Bindable hostIP = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.HostIP); + private readonly Bindable localIP = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.LocalIP); + + protected override void LoadComplete() + { + base.LoadComplete(); + + HostIP.Text = hostIP; + LocalIp.Text = localIP; + } + + protected override void HostGame() + { + if (RulesetNetworkingClientHandler != null) + { + Remove(RulesetNetworkingClientHandler); + VitaruNetworkingClientHandler.Dispose(); + } + VitaruNetworkingClientHandler = new VitaruNetworkingClientHandler(ClientType.Host, LocalIp.Text, Int32.Parse(HostPort.Text)); + RulesetNetworkingClientHandler = VitaruNetworkingClientHandler; + Add(RulesetNetworkingClientHandler); + + List list = new List(); + list.Add(RulesetNetworkingClientHandler.RulesetClientInfo); + + JoinMatch(list); + } + + protected override void DirectConnect() + { + if (RulesetNetworkingClientHandler != null) + { + Remove(RulesetNetworkingClientHandler); + VitaruNetworkingClientHandler.Dispose(); + } + VitaruNetworkingClientHandler = new VitaruNetworkingClientHandler(ClientType.Peer, HostIP.Text, Int32.Parse(HostPort.Text), LocalIp.Text); + VitaruNetworkingClientHandler.OnConnectedToHost += (p) => JoinMatch(p); + RulesetNetworkingClientHandler = VitaruNetworkingClientHandler; + Add(RulesetNetworkingClientHandler); + } + + protected override void Dispose(bool isDisposing) + { + hostIP.Value = HostIP.Text; + localIP.Value = LocalIp.Text; + + base.Dispose(isDisposing); + } + } +} diff --git a/osu.Game.Rulesets.Vitaru/Multi/VitaruMatchScreen.cs b/osu.Game.Rulesets.Vitaru/Multi/VitaruMatchScreen.cs new file mode 100644 index 0000000000..c0b06cf143 --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Multi/VitaruMatchScreen.cs @@ -0,0 +1,82 @@ +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Screens; +using osu.Game.Rulesets.Vitaru.Objects.Characters; +using osu.Game.Rulesets.Vitaru.Scoring; +using osu.Game.Rulesets.Vitaru.Settings; +using osu.Game.Rulesets.Vitaru.UI; +using Symcol.Core.Networking; +using Symcol.Rulesets.Core; +using Symcol.Rulesets.Core.Multiplayer.Options; +using Symcol.Rulesets.Core.Multiplayer.Pieces; +using Symcol.Rulesets.Core.Multiplayer.Screens; +using System.Collections.Generic; + +namespace osu.Game.Rulesets.Vitaru.Multi +{ + public class VitaruMatchScreen : RulesetMatchScreen + { + private readonly Bindable currentCharacter = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.Characters); + private readonly Bindable currentGraphics = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.GraphicsPresets); + private readonly Bindable currentScoringMetric = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.ScoringMetric); + private readonly Bindable currentGameMode = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.GameMode); + private readonly Bindable comboFire = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.ComboFire); + + public readonly VitaruNetworkingClientHandler VitaruNetworkingClientHandler; + + public VitaruMatchScreen(VitaruNetworkingClientHandler vitaruNetworkingClientHandler) : base(vitaruNetworkingClientHandler) + { + VitaruNetworkingClientHandler = vitaruNetworkingClientHandler; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + MatchTools.Mode.ValueChanged += (value) => + { + if (value == MatchScreenMode.RulesetSettings) + MatchTools.SelectedContent.Child = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new MultiplayerToggleOption(comboFire, "Enable Combo Fire", 5, false), + new MultiplayerDropdownEnumOption(currentGraphics, "Graphics", 3, false), + new MultiplayerDropdownEnumOption(currentScoringMetric, "Scoring Metric", 4), + new MultiplayerDropdownEnumOption(currentGameMode, "Vitaru Gamemode", 1), + new MultiplayerDropdownEnumOption(currentCharacter, "Character", 2, false), + } + }; + }; + } + + protected override void OnEntering(Screen last) + { + base.OnEntering(last); + + VitaruPlayfield.LoadPlayerList = new List(); + SymcolPlayfield.RulesetNetworkingClientHandler = VitaruNetworkingClientHandler; + MakeCurrent(); + VitaruNetworkingClientHandler.OnLoadGame = (i) => Load(i); + } + + protected override void OnResuming(Screen last) + { + base.OnResuming(last); + VitaruPlayfield.LoadPlayerList = new List(); + } + + protected override void Load(List playerList) + { + base.Load(playerList); + + foreach (ClientInfo client in playerList) + if (client is VitaruClientInfo vitaruClientInfo) + VitaruPlayfield.LoadPlayerList.Add(vitaruClientInfo); + + Push(new MultiPlayer(VitaruNetworkingClientHandler)); + } + } +} diff --git a/osu.Game.Rulesets.Vitaru/Multi/VitaruNetworkingClientHandler.cs b/osu.Game.Rulesets.Vitaru/Multi/VitaruNetworkingClientHandler.cs new file mode 100644 index 0000000000..7e924f74e2 --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Multi/VitaruNetworkingClientHandler.cs @@ -0,0 +1,77 @@ +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Game.Online.API; +using osu.Game.Rulesets.Vitaru.Objects.Characters; +using osu.Game.Rulesets.Vitaru.Settings; +using Symcol.Core.Networking; +using Symcol.Rulesets.Core.Multiplayer.Networking; + +namespace osu.Game.Rulesets.Vitaru.Multi +{ + public class VitaruNetworkingClientHandler : RulesetNetworkingClientHandler, IOnlineComponent + { + private readonly Bindable currentCharacter = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.Characters); + + public readonly VitaruClientInfo VitaruClientInfo; + + public VitaruNetworkingClientHandler(ClientType type, string ip, int port = 25570, string thisLocalIp = "0.0.0.0") : base(type, ip, port, thisLocalIp) + { + VitaruClientInfo = new VitaruClientInfo() + { + PlayerInformation = new VitaruPlayerInformation(), + Port = port + }; + + RulesetClientInfo = VitaruClientInfo; + ClientInfo = RulesetClientInfo; + + currentCharacter.ValueChanged += character => + { + VitaruClientInfo.PlayerInformation.Character = character; + SendToHost(new VitaruPacket(VitaruClientInfo) { ChangeCharacter = true }); + }; + currentCharacter.TriggerChange(); + + OnPacketReceive += (Packet packet) => + { + if (packet is VitaruPacket vitaruPacket) + if (vitaruPacket.ChangeCharacter) + foreach(ClientInfo clientInfo in ConncetedClients) + if (vitaruPacket.ClientInfo.IP == clientInfo.IP) + { + ConncetedClients.Remove(clientInfo); + InMatchClients.Remove(clientInfo); + ConncetedClients.Add(vitaruPacket.ClientInfo); + InMatchClients.Add(vitaruPacket.ClientInfo); + break; + } + }; + } + + [BackgroundDependencyLoader] + private void load(APIAccess api) + { + api.Register(this); + } + + public new void APIStateChanged(APIAccess api, APIState state) + { + switch (state) + { + default: + VitaruClientInfo.Username = ""; + VitaruClientInfo.UserID = -1; + break; + case APIState.Online: + VitaruClientInfo.Username = api.LocalUser.Value.Username; + VitaruClientInfo.UserID = (int)api.LocalUser.Value.Id; + VitaruClientInfo.UserCountry = api.LocalUser.Value.Country.FullName; + VitaruClientInfo.CountryFlagName = api.LocalUser.Value.Country.FlagName; + VitaruClientInfo.UserPic = api.LocalUser.Value.AvatarUrl; + VitaruClientInfo.UserBackground = api.LocalUser.Value.CoverUrl; + break; + } + VitaruClientInfo.PlayerInformation.PlayerID = VitaruClientInfo.IP + VitaruClientInfo.UserID; + } + } +} diff --git a/osu.Game.Rulesets.Vitaru/Multi/VitaruPacket.cs b/osu.Game.Rulesets.Vitaru/Multi/VitaruPacket.cs new file mode 100644 index 0000000000..da3faa7df2 --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Multi/VitaruPacket.cs @@ -0,0 +1,23 @@ +using Symcol.Core.Networking; +using System; + +namespace osu.Game.Rulesets.Vitaru.Multi +{ + [Serializable] + public class VitaruPacket : Packet + { + public new readonly VitaruClientInfo ClientInfo; + + public override int PacketSize => 8192; + + /// + /// Changing Character? + /// + public bool ChangeCharacter; + + public VitaruPacket(VitaruClientInfo vitaruClientInfo) : base(vitaruClientInfo) + { + ClientInfo = vitaruClientInfo; + } + } +} diff --git a/osu.Game.Rulesets.Vitaru/Multi/VitaruPlayerInformation.cs b/osu.Game.Rulesets.Vitaru/Multi/VitaruPlayerInformation.cs new file mode 100644 index 0000000000..37cac0ec48 --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Multi/VitaruPlayerInformation.cs @@ -0,0 +1,28 @@ +using osu.Game.Rulesets.Vitaru.Objects.Characters; +using System; +using System.Collections.Generic; + +namespace osu.Game.Rulesets.Vitaru.Multi +{ + [Serializable] + public class VitaruPlayerInformation + { + public string PlayerID = "0"; + + public Characters Character; + + public float PlayerX; + + public float PlayerY; + + public float MouseX; + + public float MouseY; + + public float ClockSpeed; + + public Dictionary Actions; + + public VitaruAction PressedAction; + } +} diff --git a/osu.Game.Rulesets.Vitaru/Objects/Bullet.cs b/osu.Game.Rulesets.Vitaru/Objects/Bullet.cs new file mode 100644 index 0000000000..12b4732c08 --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Objects/Bullet.cs @@ -0,0 +1,24 @@ +namespace osu.Game.Rulesets.Vitaru.Objects +{ + public class Bullet : VitaruHitObject + { + public override HitObjectType Type => HitObjectType.Bullet; + + /// + /// Basically just bypasses all hitobject functionality (useful for player bullets) + /// + public bool DummyMode { get; set; } + + public float BulletDamage { get; set; } = 10; + public float BulletSpeed { get; set; } = 1f; + public float BulletDiameter { get; set; } = 16f; + public float BulletAngleRadian { get; set; } + public bool DynamicBulletVelocity { get; set; } + // ReSharper disable once UnusedMember.Global + public bool Piercing { get; set; } = false; + public int Team { get; set; } = -1; + public bool ShootPlayer { get; set; } + public bool ObeyBoundries { get; } = true; + public bool Ghost { get; set; } = false; + } +} diff --git a/osu.Game.Rulesets.Vitaru/Objects/Characters/Enemy.cs b/osu.Game.Rulesets.Vitaru/Objects/Characters/Enemy.cs new file mode 100644 index 0000000000..9009060700 --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Objects/Characters/Enemy.cs @@ -0,0 +1,121 @@ +using OpenTK; +using osu.Framework.Audio.Track; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Textures; +using osu.Game.Rulesets.Vitaru.UI; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Vitaru.Objects.Drawables; +using osu.Game.Rulesets.Vitaru.Settings; +using osu.Framework.Platform; + +namespace osu.Game.Rulesets.Vitaru.Objects.Characters +{ + public class Enemy : VitaruCharacter + { + private readonly GraphicsPresets currentSkin = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.GraphicsPresets); + + public static int EnemyCount; + private readonly DrawablePattern drawablePattern; + + public Enemy(Container parent, Pattern pattern, DrawablePattern drawablePattern) : base(parent) + { + this.drawablePattern = drawablePattern; + AlwaysPresent = true; + CharacterName = "enemy"; + Team = 1; + CharacterColor = pattern.ComboColour; + HitboxWidth = 27; + } + + protected override void LoadComplete() + { + EnemyCount++; + + if (currentSkin == GraphicsPresets.StandardCompetitive) + VisibleHitbox.Alpha = 0.2f; + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + if (isDisposing) + EnemyCount--; + } + + protected override void MovementAnimations() + { + if (CharacterLeftSprite.Texture == null && CharacterRightSprite != null) + { + CharacterLeftSprite.Texture = CharacterRightSprite.Texture; + CharacterLeftSprite.Size = new Vector2(-CharacterLeftSprite.Size.X, CharacterLeftSprite.Size.Y); + } + if (CharacterKiaiLeftSprite.Texture == null && CharacterKiaiRightSprite != null) + { + CharacterKiaiLeftSprite.Texture = CharacterKiaiRightSprite.Texture; + CharacterKiaiLeftSprite.Size = new Vector2(-CharacterKiaiLeftSprite.Size.X, CharacterKiaiLeftSprite.Size.Y); + } + if (Position.X > LastX) + { + if (CharacterLeftSprite.Texture != null) + CharacterLeftSprite.Alpha = 0; + if (CharacterRightSprite?.Texture != null) + CharacterRightSprite.Alpha = 1; + if (CharacterStillSprite.Texture != null) + CharacterStillSprite.Alpha = 0; + if (CharacterKiaiLeftSprite.Texture != null) + CharacterKiaiLeftSprite.Alpha = 0; + if (CharacterKiaiRightSprite?.Texture != null) + CharacterKiaiRightSprite.Alpha = 1; + if (CharacterKiaiStillSprite.Texture != null) + CharacterKiaiStillSprite.Alpha = 0; + } + else if (Position.X < LastX) + { + if (CharacterLeftSprite.Texture != null) + CharacterLeftSprite.Alpha = 1; + if (CharacterRightSprite?.Texture != null) + CharacterRightSprite.Alpha = 0; + if (CharacterStillSprite.Texture != null) + CharacterStillSprite.Alpha = 0; + if (CharacterKiaiLeftSprite.Texture != null) + CharacterKiaiLeftSprite.Alpha = 1; + if (CharacterKiaiRightSprite?.Texture != null) + CharacterKiaiRightSprite.Alpha = 0; + if (CharacterKiaiStillSprite.Texture != null) + CharacterKiaiStillSprite.Alpha = 0; + } + LastX = Position.X; + } + + protected override void LoadAnimationSprites(TextureStore textures, Storage storage) + { + base.LoadAnimationSprites(textures, storage); + CharacterRightSprite.Texture = VitaruSkinElement.LoadSkinElement(CharacterName, storage); + CharacterKiaiRightSprite.Texture = VitaruSkinElement.LoadSkinElement(CharacterName + "Kiai", storage); + } + + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) + { + base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); + + if (effectPoint.KiaiMode && CharacterSprite.Alpha == 1) + { + CharacterSprite.FadeOutFromOne(timingPoint.BeatLength / 4); + CharacterKiai.FadeInFromZero(timingPoint.BeatLength / 4); + } + if (!effectPoint.KiaiMode && CharacterSprite.Alpha == 0) + { + CharacterSprite.FadeInFromZero(timingPoint.BeatLength); + CharacterKiai.FadeOutFromOne(timingPoint.BeatLength); + } + } + + public override void Death() + { + Dead = true; + drawablePattern.PrepPop(); + Hitbox.HitDetection = false; + } + } +} diff --git a/osu.Game.Rulesets.Vitaru/Objects/Characters/Pieces/Crystal.cs b/osu.Game.Rulesets.Vitaru/Objects/Characters/Pieces/Crystal.cs new file mode 100644 index 0000000000..e71a43cb1e --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Objects/Characters/Pieces/Crystal.cs @@ -0,0 +1,35 @@ +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.MathUtils; +using osu.Game.Graphics.Containers; +using OpenTK; + +namespace osu.Game.Rulesets.Vitaru.Objects.Characters.Pieces +{ + public class Crystal : BeatSyncedContainer + { + public Crystal() + { + Alpha = 0; + Child = new Sprite + { + Alpha = 0.8f, + Scale = new Vector2((float)RNG.NextDouble(100, 200) / 300), + Texture = VitaruRuleset.VitaruTextures.Get("crystal") + }; + } + + public void Pop(double duration, Easing easing = Easing.OutQuart) + { + this.MoveTo(new Vector2((float)RNG.NextDouble(-200, 200), (float)RNG.NextDouble(-200, 200)), duration, easing) + .FadeIn(duration / 8); + } + + public void ReCollect(double duration, Easing easing = Easing.InQuart) + { + this.MoveTo(Vector2.Zero, duration, easing) + .Delay(duration - duration / 8) + .FadeOut(duration / 8); + } + } +} diff --git a/osu.Game.Rulesets.Vitaru/Objects/Characters/Pieces/Mask.cs b/osu.Game.Rulesets.Vitaru/Objects/Characters/Pieces/Mask.cs new file mode 100644 index 0000000000..7a6c1e80ee --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Objects/Characters/Pieces/Mask.cs @@ -0,0 +1,9 @@ +using osu.Game.Graphics.Containers; + +namespace osu.Game.Rulesets.Vitaru.Objects.Characters.Pieces +{ + public class Mask : BeatSyncedContainer + { + + } +} diff --git a/osu.Game.Rulesets.Vitaru/Objects/Characters/Pieces/Metranome.cs b/osu.Game.Rulesets.Vitaru/Objects/Characters/Pieces/Metranome.cs new file mode 100644 index 0000000000..7ddad5bd77 --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Objects/Characters/Pieces/Metranome.cs @@ -0,0 +1,84 @@ +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Audio.Track; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.Backgrounds; +using osu.Game.Graphics.Containers; +using osu.Game.Screens.Menu; +using System; + +namespace osu.Game.Rulesets.Vitaru.Objects.Characters.Pieces +{ + public class Metranome : BeatSyncedContainer + { + private readonly Sprite sign; + private readonly LogoVisualisation visualizer; + + public Metranome() + { + AlwaysPresent = true; + Size = new Vector2(120); + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + Alpha = 0; + + Children = new Drawable[] + { + new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.9f), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Masking = true, + + Child = new Triangles + { + ColourDark = Color4.Pink, + ColourLight = Color4.Cyan, + RelativeSizeAxes = Axes.Both + } + }, + sign = new Sprite + { + Colour = Color4.Cyan, + RelativeSizeAxes = Axes.Both, + Texture = VitaruRuleset.VitaruTextures.Get("sign"), + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }, + visualizer = new LogoVisualisation + { + Colour = Color4.DeepPink, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.96f) + } + }; + } + + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) + { + base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); + + float amplitudeAdjust = Math.Min(1, 0.4f + amplitudes.Maximum); + + const double beat_in_time = 60; + + this.ScaleTo(1 - 0.05f * amplitudeAdjust, beat_in_time, Easing.Out); + using (BeginDelayedSequence(beat_in_time)) + this.ScaleTo(1, timingPoint.BeatLength * 2, Easing.OutQuint); + } + + protected override void Update() + { + base.Update(); + + sign.RotateTo(-(float)(Clock.CurrentTime / 1000 * 90) / 2); + } + } +} diff --git a/osu.Game.Rulesets.Vitaru/Objects/Characters/Pieces/Rift.cs b/osu.Game.Rulesets.Vitaru/Objects/Characters/Pieces/Rift.cs new file mode 100644 index 0000000000..e769d9eedb --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Objects/Characters/Pieces/Rift.cs @@ -0,0 +1,31 @@ +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Graphics.Sprites; + +namespace osu.Game.Rulesets.Vitaru.Objects.Characters.Pieces +{ + public class Rift : Sprite + { + public Rift LinkedRift; + + public Rift(Color4 color) + { + AlwaysPresent = true; + + Anchor = Framework.Graphics.Anchor.TopLeft; + Origin = Framework.Graphics.Anchor.Centre; + + Alpha = 0; + Colour = color; + Size = new Vector2(80); + Texture = VitaruRuleset.VitaruTextures.Get("vortex"); + } + + protected override void Update() + { + base.Update(); + + Rotation = (float)(Clock.CurrentTime / -1000 * 90); + } + } +} diff --git a/osu.Game.Rulesets.Vitaru/Objects/Characters/Pieces/Totem.cs b/osu.Game.Rulesets.Vitaru/Objects/Characters/Pieces/Totem.cs new file mode 100644 index 0000000000..c00de25746 --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Objects/Characters/Pieces/Totem.cs @@ -0,0 +1,48 @@ +using OpenTK; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.Containers; +using osu.Game.Rulesets.Vitaru.Objects.Drawables; + +namespace osu.Game.Rulesets.Vitaru.Objects.Characters.Pieces +{ + public class Totem : BeatSyncedContainer + { + public readonly VitaruCharacter ParentCharacter; + public float StartAngle { get; set; } = 0; + + public Totem(VitaruCharacter vitaruCharacter) + { + ParentCharacter = vitaruCharacter; + } + + public void Shoot() + { + DrawableSeekingBullet s; + ParentCharacter.Parent.Add(s = new DrawableSeekingBullet(ParentCharacter.Parent, new SeekingBullet + { + Team = ParentCharacter.Team, + BulletSpeed = 0.8f, + BulletDamage = 5, + ComboColour = ParentCharacter.CharacterColor, + StartAngle = StartAngle, + })); + s.MoveTo(ToSpaceOfOtherDrawable(new Vector2(0, 0), s)); + } + + protected override void LoadComplete() + { + Masking = true; + Size = new Vector2(6); + Origin = Anchor.Centre; + Anchor = Anchor.Centre; + BorderThickness = 2; + BorderColour = ParentCharacter.CharacterColor; + CornerRadius = 3; + Child= new Box + { + RelativeSizeAxes = Axes.Both + }; + } + } +} diff --git a/osu.Game.Rulesets.Vitaru/Objects/Characters/Pieces/UFO.cs b/osu.Game.Rulesets.Vitaru/Objects/Characters/Pieces/UFO.cs new file mode 100644 index 0000000000..1e5f873604 --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Objects/Characters/Pieces/UFO.cs @@ -0,0 +1,69 @@ +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.Containers; + +namespace osu.Game.Rulesets.Vitaru.Objects.Characters.Pieces +{ + public class UFO : BeatSyncedContainer + { + public readonly VitaruPlayer ParentNue; + public readonly UFOType UFOType; + private readonly Color4 color; + + public VitaruPlayer AttachedPlayer; + + public UFO(VitaruPlayer player, UFOType type) + { + ParentNue = player; + UFOType = type; + AttachedPlayer = player; + + switch (type) + { + case UFOType.Mark: + color = Color4.Purple; + break; + case UFOType.Health: + color = Color4.Green; + break; + case UFOType.Energy: + color = Color4.Blue; + break; + case UFOType.Damage: + color = Color4.Red; + break; + } + + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + Masking = true; + Colour = color; + Size = new Vector2(10); + CornerRadius = Size.X / 3; + Alpha = 0.5f; + + Child = new Box + { + RelativeSizeAxes = Axes.Both + }; + + EdgeEffect = new EdgeEffectParameters + { + Radius = Width, + Colour = color.Opacity(0.5f) + }; + } + } + + public enum UFOType + { + Mark, + Health, + Energy, + Damage + } +} diff --git a/osu.Game.Rulesets.Vitaru/Objects/Characters/VitaruCharacter.cs b/osu.Game.Rulesets.Vitaru/Objects/Characters/VitaruCharacter.cs new file mode 100644 index 0000000000..78fce10d79 --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Objects/Characters/VitaruCharacter.cs @@ -0,0 +1,346 @@ +using OpenTK; +using osu.Framework.Graphics; +using OpenTK.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Framework.Allocation; +using osu.Game.Rulesets.Vitaru.Objects.Drawables; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.Containers; +using osu.Game.Rulesets.Vitaru.UI; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Extensions.Color4Extensions; +using Container = osu.Framework.Graphics.Containers.Container; +using Symcol.Core.GameObjects; +using System.ComponentModel; +using osu.Framework.Platform; + +namespace osu.Game.Rulesets.Vitaru.Objects.Characters +{ + public abstract class VitaruCharacter : BeatSyncedContainer + { + protected Sprite CharacterStillSprite; + protected Sprite CharacterRightSprite; + protected Sprite CharacterLeftSprite; + protected Sprite CharacterKiaiStillSprite; + protected Sprite CharacterKiaiRightSprite; + protected Sprite CharacterKiaiLeftSprite; + protected Sprite CharacterSign; + protected Container CharacterKiai; + protected Container CharacterSprite; + public Color4 CharacterColor; + protected string CharacterName = "null"; + public float HitboxWidth { get; set; } = 4; + protected CircularContainer VisibleHitbox; + public SymcolHitbox Hitbox; + public bool CanHeal = false; + protected float LastX; + + /// + /// Should be assigned to only in ctor, and is essential for hit detection + /// + public new readonly Container Parent; + + protected VitaruCharacter(Container parent) + { + Parent = parent; + } + + /// + /// Does animations to better give the illusion of movement (could likely be cleaned up) + /// + protected virtual void MovementAnimations() + { + if (CharacterLeftSprite.Texture == null && CharacterRightSprite != null) + { + CharacterLeftSprite.Texture = CharacterRightSprite.Texture; + CharacterLeftSprite.Size = new Vector2(-CharacterLeftSprite.Size.X, CharacterLeftSprite.Size.Y); + } + if (CharacterKiaiLeftSprite.Texture == null && CharacterKiaiRightSprite != null) + { + CharacterKiaiLeftSprite.Texture = CharacterKiaiRightSprite.Texture; + CharacterKiaiLeftSprite.Size = new Vector2(-CharacterKiaiLeftSprite.Size.X, CharacterKiaiLeftSprite.Size.Y); + } + if (Position.X > LastX) + { + if (CharacterLeftSprite.Texture != null) + CharacterLeftSprite.Alpha = 0; + if (CharacterRightSprite?.Texture != null) + CharacterRightSprite.Alpha = 1; + if (CharacterStillSprite.Texture != null) + CharacterStillSprite.Alpha = 0; + if (CharacterKiaiLeftSprite.Texture != null) + CharacterKiaiLeftSprite.Alpha = 0; + if (CharacterKiaiRightSprite?.Texture != null) + CharacterKiaiRightSprite.Alpha = 1; + if (CharacterKiaiStillSprite.Texture != null) + CharacterKiaiStillSprite.Alpha = 0; + } + else if (Position.X < LastX) + { + if (CharacterLeftSprite.Texture != null) + CharacterLeftSprite.Alpha = 1; + if (CharacterRightSprite?.Texture != null) + CharacterRightSprite.Alpha = 0; + if (CharacterStillSprite.Texture != null) + CharacterStillSprite.Alpha = 0; + if (CharacterKiaiLeftSprite.Texture != null) + CharacterKiaiLeftSprite.Alpha = 1; + if (CharacterKiaiRightSprite?.Texture != null) + CharacterKiaiRightSprite.Alpha = 0; + if (CharacterKiaiStillSprite.Texture != null) + CharacterKiaiStillSprite.Alpha = 0; + } + else + { + if (CharacterLeftSprite.Texture != null) + CharacterLeftSprite.Alpha = 0; + if (CharacterRightSprite?.Texture != null) + CharacterRightSprite.Alpha = 0; + if (CharacterStillSprite.Texture != null) + CharacterStillSprite.Alpha = 1; + if (CharacterKiaiLeftSprite.Texture != null) + CharacterKiaiLeftSprite.Alpha = 0; + if (CharacterKiaiRightSprite?.Texture != null) + CharacterKiaiRightSprite.Alpha = 0; + if (CharacterKiaiStillSprite.Texture != null) + CharacterKiaiStillSprite.Alpha = 1; + } + LastX = Position.X; + } + + protected override void Update() + { + base.Update(); + + if (Health <= 0 && !Dead) + Death(); + + foreach (Drawable draw in Parent) + { + DrawableBullet bullet = draw as DrawableBullet; + if (bullet?.Hitbox != null) + { + ParseBullet(bullet); + if (Hitbox.HitDetect(Hitbox, bullet.Hitbox)) + { + Damage(bullet.Bullet.BulletDamage); + bullet.Bullet.BulletDamage = 0; + bullet.Hit = true; + } + } + + DrawableSeekingBullet seekingBullet = draw as DrawableSeekingBullet; + if (seekingBullet?.Hitbox != null) + { + if (Hitbox.HitDetect(Hitbox, seekingBullet.Hitbox)) + { + Damage(seekingBullet.SeekingBullet.BulletDamage); + seekingBullet.SeekingBullet.BulletDamage = 0; + seekingBullet.Hit = true; + } + } + + DrawableLaser laser = draw as DrawableLaser; + if (laser?.Hitbox != null) + { + if (Hitbox.HitDetect(Hitbox, laser.Hitbox)) + { + Damage(laser.Laser.LaserDamage * (1000 / (float)Clock.ElapsedFrameTime)); + laser.Hit = true; + } + } + } + + MovementAnimations(); + } + + /// + /// Gets called just before hit detection + /// + protected virtual void ParseBullet(DrawableBullet bullet) { } + + protected virtual void LoadAnimationSprites(TextureStore textures, Storage storage) { } + + /// + /// Child loading for all Characters (Enemies, Player, Bosses) + /// + [BackgroundDependencyLoader] + private void load(TextureStore textures, Storage storage) + { + Health = MaxHealth; + //Drawable stuff loading + Origin = Anchor.Centre; + Anchor = Anchor.TopLeft; + Children = new Drawable[] + { + CharacterSign = new Sprite + { + Alpha = 0, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = CharacterColor, + }, + CharacterSprite = new Container + { + Colour = CharacterColor, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Alpha = 1, + Children = new Drawable[] + { + CharacterStillSprite = new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Alpha = 1, + }, + CharacterRightSprite = new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Alpha = 0, + }, + CharacterLeftSprite = new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Alpha = 0, + }, + } + }, + CharacterKiai = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Alpha = 0, + Children = new Drawable[] + { + CharacterKiaiStillSprite = new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Alpha = 1, + }, + CharacterKiaiRightSprite = new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Alpha = 0, + }, + CharacterKiaiLeftSprite = new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Alpha = 0, + }, + } + }, + VisibleHitbox = new CircularContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Alpha = 0, + Size = new Vector2(HitboxWidth), + BorderColour = CharacterColor, + BorderThickness = HitboxWidth / 3, + Masking = true, + + Child = new Box + { + RelativeSizeAxes = Axes.Both + }, + EdgeEffect = new EdgeEffectParameters + { + + Radius = HitboxWidth, + Type = EdgeEffectType.Shadow, + Colour = CharacterColor.Opacity(0.5f) + } + } + }; + + Add(Hitbox = new SymcolHitbox(new Vector2(HitboxWidth)) { Team = Team }); + + if (CharacterName == "player" || CharacterName == "enemy") + CharacterKiai.Colour = CharacterColor; + + CharacterStillSprite.Texture = VitaruSkinElement.LoadSkinElement(CharacterName, storage); + CharacterKiaiStillSprite.Texture = VitaruSkinElement.LoadSkinElement(CharacterName + "Kiai", storage); + CharacterSign.Texture = VitaruSkinElement.LoadSkinElement("sign", storage); + LoadAnimationSprites(textures, storage); + } + + #region eden.Game.GamePieces.Character.cs + /// + /// Maximum health this charcter can have + /// + public float MaxHealth = 100; + + /// + /// The team this character is on, used mostly for Hitbox + /// + public int Team { get; set; } + + /// + /// If this character has hit 0 health + /// + public bool Dead; + + /// + /// the amount of health this character has + /// + public float Health; + + /// + /// Removes "damage" + /// + /// + /// + public virtual float Damage(float damage) + { + Health -= damage; + + if (Health < 0) + { + Health = 0; + Death(); + } + + return Health; + } + + /// + /// Adds "health" + /// + /// + /// + public virtual float Heal(float health) + { + if (Health <= 0 && health > 0) + Revive(); + + Health += health; + + if (Health > MaxHealth) + Health = MaxHealth; + + return Health; + } + + /// + /// Called when this character runs out of health + /// + public virtual void Death() + { + Dead = true; + Expire(); + } + + public virtual void Revive() + { + Dead = false; + } + #endregion + } +} diff --git a/osu.Game.Rulesets.Vitaru/Objects/Characters/VitaruPlayer.cs b/osu.Game.Rulesets.Vitaru/Objects/Characters/VitaruPlayer.cs new file mode 100644 index 0000000000..e6e9a1532b --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Objects/Characters/VitaruPlayer.cs @@ -0,0 +1,1801 @@ +using OpenTK; +using osu.Framework.Graphics; +using System.Collections.Generic; +using OpenTK.Graphics; +using System; +using osu.Game.Rulesets.Vitaru.Objects.Drawables; +using osu.Framework.Audio.Track; +using osu.Framework.Graphics.Textures; +using osu.Game.Rulesets.Vitaru.Settings; +using osu.Game.Rulesets.Vitaru.Scoring; +using osu.Framework.Audio; +using osu.Game.Rulesets.Vitaru.UI; +using osu.Framework.Timing; +using static osu.Game.Rulesets.Vitaru.UI.Cursor.GameplayCursor; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.MathUtils; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Framework.Input.Bindings; +using osu.Game.Graphics.Containers; +using osu.Framework.Platform; +using osu.Game.Rulesets.Vitaru.Objects.Characters.Pieces; +using osu.Game.Rulesets.Vitaru.Multi; +using Symcol.Core.Networking; +using System.ComponentModel; + +namespace osu.Game.Rulesets.Vitaru.Objects.Characters +{ + public class VitaruPlayer : VitaruCharacter, IKeyBindingHandler + { + #region Fields + private readonly Characters currentCharacter; + private readonly GraphicsPresets currentSkin = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.GraphicsPresets); + private readonly ScoringMetric currentScoringMetric = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.ScoringMetric); + private readonly VitaruGamemode currentGameMode = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.GameMode); + + public int ScoreZone = 100; + + public Dictionary Actions = new Dictionary(); + + public VitaruNetworkingClientHandler VitaruNetworkingClientHandler { get; set; } + + public string PlayerID; + + //(MinX,MaxX,MinY,MaxY) + private Vector4 playerBounds = new Vector4(0, 512, 0, 820); + + private const float player_speed = 0.25f; + + public bool Invert { get; set; } + + //Is not Human + public bool Bot { get; set; } + + //Has a parent Player? + public bool Clone { get; set; } + + /// + /// Are we a slave online? + /// + public bool Puppet { get; set; } + + private readonly static List playerList = new List(); + + private readonly Bindable workingBeatmap = new Bindable(); + private List cloneList = new List(); + private readonly List crystalList = new List(); + private VitaruPlayer parentPlayer; + private const float field_of_view = 60; + public float SpeedMultiplier = 1; + private OsuTextFlowContainer textContainer; + + private List ufoList = new List(); + private Framework.Graphics.Containers.Container ufoContainer; + private UFO ufoMark; + private UFO ufoHealth; + private UFO ufoEnergy; + private UFO ufoDamage; + private readonly float originalMaxHealth; + + private bool riftActive; + private Rift riftStart; + private Rift riftEnd; + private double warpTime = double.MinValue; + + private bool vampuric; + + private DrawableLaser drawableLaser; + + private Totem leftTotem; + private Totem rightTotem; + + private Metranome metranome; + public int Combo; + private float damageMultiplier = 1; + private const float hitwindow = 40; + + //Automatic play, ignores player input + public bool Auto { get; set; } + + private double packetTime = double.MinValue; + private double lastQuarterBeat = -1; + private double nextHalfBeat = -1; + private double nextQuarterBeat = -1; + private double beatLength = 1000; + private bool leader; + private double reFrozenTime = double.MaxValue; + private double timeFreezeEndTime = double.MinValue; + private double reFreezeTime = double.MaxValue; + private float originalRate; + public float SetRate = 0.2f; + private float currentRate = 1; + private bool timeFreezeActive; + private bool tabooActive; + private bool ghostActive; + private bool shattered; + private readonly float energyRequired = 50; + private readonly float energyRequiredPerSecond; + private readonly float maxEnergy = 100; + private float healingMultiplier = 1; + private readonly float energyGainMultiplier = 1; + public float Energy; + + //For debug ui only + public static float Energystored; + #endregion + + #region Loading Stuff + public VitaruPlayer(Framework.Graphics.Containers.Container parent, Characters characterOverride, VitaruPlayer parentPlayer = null) : base(parent) + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + playerList.Add(this); + + currentCharacter = characterOverride; + + if (parentPlayer != null) + this.parentPlayer = parentPlayer; + + Actions[VitaruAction.Up] = false; + Actions[VitaruAction.Down] = false; + Actions[VitaruAction.Left] = false; + Actions[VitaruAction.Right] = false; + Actions[VitaruAction.Slow] = false; + Actions[VitaruAction.Fast] = false; + Actions[VitaruAction.Shoot] = false; + + CharacterName = "player"; + Team = 0; + MaxHealth = 100; + Position = new Vector2(256, 700); + + switch (currentCharacter) + { + default: + CharacterColor = Color4.White; + break; + /* + case Characters.Alex: + energyRequired = 20; + maxEnergy = 40; + CharacterColor = Color4.Gold; + //CharacterName = "arysa"; + break; + */ + case Characters.ReimuHakurei: + CharacterColor = Color4.Red; + CharacterName = "reimu"; + break; + case Characters.MarisaKirisame: + CharacterColor = Color4.Black; + CharacterName = "marisa"; + energyRequired = 10; + break; + case Characters.SakuyaIzayoi: + CharacterColor = Color4.Navy; + energyRequired = 2; + energyRequiredPerSecond = 4; + maxEnergy = 24; + CharacterName = "sakuya"; + break; + case Characters.HongMeiling: + + if (!resurrected) + MaxHealth = 0; + else + MaxHealth = 20; + + maxEnergy = 36; + leader = true; + CharacterColor = Color4.Orange; + break; + case Characters.FlandreScarlet: + maxEnergy = 80; + energyRequired = 40; + CharacterColor = Color4.Red; + break; + case Characters.RemiliaScarlet: + CharacterColor = Color4.Pink; + vampuric = true; + maxEnergy = 60; + MaxHealth = 60; + energyRequired = 1; + break; + case Characters.Cirno: + MaxHealth = 80; + maxEnergy = 40; + energyRequired = 40; + CharacterColor = Color4.Blue; + break; + case Characters.TenshiHinanai: + CharacterColor = Color4.DarkBlue; + break; + case Characters.YuyukoSaigyouji: + CharacterColor = Color4.LightBlue; + break; + case Characters.YukariYakumo: + CharacterColor = Color4.DarkViolet; + maxEnergy = 24; + energyRequiredPerSecond = 4; + MaxHealth = 80; + energyRequired = 4; + break; + case Characters.Chen: + CharacterColor = Color4.Green; + CharacterName = "chen"; + break; + case Characters.KokoroHatano: + CharacterColor = Color4.Cyan; + maxEnergy = 36; + break; + case Characters.Kaguya: + CharacterColor = Color4.DarkRed; + CharacterName = "kaguya"; + maxEnergy = 24; + energyRequired = 2; + energyRequiredPerSecond = 2; + break; + case Characters.IbarakiKasen: + CharacterColor = Color4.YellowGreen; + maxEnergy = 8; + energyRequired = 2; + MaxHealth = 40; + break; + case Characters.NueHoujuu: + CharacterColor = Color4.DarkGray; + CharacterName = "nue"; + MaxHealth = 80; + maxEnergy = 24; + energyRequired = 0; + break; + case Characters.AliceMuyart: + MaxHealth = 200; + healingMultiplier = 2; + energyGainMultiplier = 2; + maxEnergy = 200; + energyRequired = 10; + energyRequiredPerSecond = 4; + CharacterColor = Color4.SkyBlue; + break; + case Characters.ArysaMuyart: + break; + } + + originalMaxHealth = MaxHealth; + + if (currentGameMode == VitaruGamemode.Dodge) + playerBounds = new Vector4(0, 512, 0, 384); + } + + protected override void LoadAnimationSprites(TextureStore textures, Storage storage) + { + base.LoadAnimationSprites(textures, storage); + + CharacterRightSprite.Texture = VitaruSkinElement.LoadSkinElement(CharacterName + "Right", storage); + CharacterKiaiRightSprite.Texture = VitaruSkinElement.LoadSkinElement(CharacterName + "KiaiRight", storage); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + if (VitaruNetworkingClientHandler != null) + VitaruNetworkingClientHandler.OnPacketReceive += (p) => packetReceived(p); + + if (Invert) + Rotation += 180; + + if (currentSkin == GraphicsPresets.StandardCompetitive || currentSkin == GraphicsPresets.HighPerformanceCompetitive) + VisibleHitbox.Alpha = 1; + + if (currentGameMode == VitaruGamemode.Touhosu) + { + if (currentCharacter == Characters.ReimuHakurei | currentCharacter == Characters.MarisaKirisame) + { + AddRange(new Drawable[] + { + leftTotem = new Totem(this) + { + Position = new Vector2(-20, -30), + StartAngle = -20, + }, + rightTotem = new Totem(this) + { + Position = new Vector2(20, -30), + StartAngle = 20, + } + }); + } + + Add(textContainer = new OsuTextFlowContainer(t => { t.TextSize = 24; }) + { + Alpha = 0, + Position = new Vector2(0, 48), + Anchor = Anchor.BottomCentre, + Origin = Anchor.TopCentre, + Width = 100, + AutoSizeAxes = Axes.Both, + Text = "" + }); + + if (currentCharacter == Characters.Cirno) + { + for (int i = 0; i < 20; i++) + { + Crystal c = new Crystal { Position = new Vector2((float)RNG.NextDouble(-20, 20), (float)RNG.NextDouble(-40, 40)) }; + crystalList.Add(c); + Add(c); + } + } + + if (currentCharacter == Characters.YukariYakumo) + { + Parent.AddRange(new Drawable[] + { + riftStart = new Rift(Color4.DarkViolet), + riftEnd = new Rift(Color4.DarkRed) + }); + + riftStart.LinkedRift = riftEnd; + riftEnd.LinkedRift = riftStart; + } + + if (currentCharacter == Characters.KokoroHatano) + { + Add(metranome = new Metranome()); + Remove(CharacterSign); + } + + Add(ufoContainer = new Framework.Graphics.Containers.Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }); + + if (currentCharacter == Characters.NueHoujuu) + { + ufoMark = new UFO(this, UFOType.Mark) { Position = new Vector2(0, -60) }; + ufoHealth = new UFO(this, UFOType.Health) { Position = new Vector2(-60, 0) }; + ufoEnergy = new UFO(this, UFOType.Energy) { Position = new Vector2(60, 0) }; + ufoDamage = new UFO(this, UFOType.Damage) { Position = new Vector2(0, 60) }; + + ufoList.Add(ufoMark); + ufoList.Add(ufoHealth); + ufoList.Add(ufoEnergy); + ufoList.Add(ufoDamage); + + foreach (UFO ufo in ufoList) + ufoContainer.Add(ufo); + } + } + } + + protected override void Dispose(bool isDisposing) + { + if (isDisposing) + playerList.Remove(this); + base.Dispose(isDisposing); + } + #endregion + + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) + { + base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); + + float amplitudeAdjust = Math.Min(1, 0.4f + amplitudes.Maximum); + + beatLength = timingPoint.BeatLength; + + if (!Clone && Bot && currentGameMode == VitaruGamemode.Touhosu) + spell(); + + if (Actions[VitaruAction.Shoot] && currentGameMode != VitaruGamemode.Dodge && currentCharacter == Characters.MarisaKirisame | currentCharacter == Characters.ReimuHakurei) + { + leftTotem.Shoot(); + rightTotem.Shoot(); + } + + onHalfBeat(); + lastQuarterBeat = Time.Current; + nextHalfBeat = Time.Current + timingPoint.BeatLength / 2; + nextQuarterBeat = Time.Current + timingPoint.BeatLength / 4; + + const double beat_in_time = 60; + + CharacterSign.ScaleTo(1 - 0.02f * amplitudeAdjust, beat_in_time, Easing.Out); + using (CharacterSign.BeginDelayedSequence(beat_in_time)) + CharacterSign.ScaleTo(1, beatLength * 2, Easing.OutQuint); + + if (effectPoint.KiaiMode && currentGameMode != VitaruGamemode.Touhosu) + { + CharacterSign.FadeTo(0.25f * amplitudeAdjust, beat_in_time, Easing.Out); + using (CharacterSign.BeginDelayedSequence(beat_in_time)) + CharacterSign.FadeOut(beatLength); + } + + if (effectPoint.KiaiMode && CharacterSprite.Alpha == 1) + { + if (!Dead) + { + CharacterKiai.FadeInFromZero(timingPoint.BeatLength / 4); + CharacterSprite.FadeOutFromOne(timingPoint.BeatLength / 4); + } + + if (currentGameMode != VitaruGamemode.Touhosu) + CharacterSign.FadeTo(0.15f , timingPoint.BeatLength / 4); + } + if(!effectPoint.KiaiMode && CharacterKiai.Alpha == 1) + { + if (!Dead) + { + CharacterSprite.FadeInFromZero(timingPoint.BeatLength); + CharacterKiai.FadeOutFromOne(timingPoint.BeatLength); + } + + if (currentGameMode != VitaruGamemode.Touhosu) + CharacterSign.FadeTo(0f, timingPoint.BeatLength); + } + } + + private void onHalfBeat() + { + nextHalfBeat = -1; + + if (Actions[VitaruAction.Shoot] && currentGameMode != VitaruGamemode.Dodge && currentCharacter == Characters.Cirno && !shattered) + patternWave(); + else if (Actions[VitaruAction.Shoot] && currentGameMode != VitaruGamemode.Dodge && currentCharacter != Characters.Cirno) + patternWave(); + + if (CanHeal) + { + CanHeal = false; + + Heal(1 * healingMultiplier); + + if (currentGameMode != VitaruGamemode.Touhosu) + { + CharacterSign.Alpha = 0.2f; + CharacterSign.FadeOut(beatLength / 2); + } + } + } + + private void onQuarterBeat() + { + lastQuarterBeat = nextQuarterBeat; + nextQuarterBeat += beatLength / 4; + } + + protected override void Update() + { + base.Update(); + + if (currentGameMode == VitaruGamemode.Touhosu) + { + speakingUpdate(); + spellUpdate(); + } + + playerInput(); + checkScoreZone(); + + if (nextHalfBeat <= Time.Current && nextHalfBeat != -1) + onHalfBeat(); + + if (nextQuarterBeat <= Time.Current && nextQuarterBeat != -1) + onQuarterBeat(); + + if (CharacterSign.Alpha > 0) + CharacterSign.RotateTo((float)(Clock.CurrentTime / 1000 * 90)); + + if (VitaruNetworkingClientHandler != null && packetTime + 250 <= Time.Current) + { + packetTime = Time.Current; + sendPacket(); + } + } + + protected override void ParseBullet(DrawableBullet bullet) + { + base.ParseBullet(bullet); + + //Not sure why this offset is needed atm + Vector2 object2Pos = bullet.ToSpaceOfOtherDrawable(Vector2.Zero, this) + new Vector2(6); + float distance = (float)Math.Sqrt(Math.Pow(object2Pos.X, 2) + Math.Pow(object2Pos.Y, 2)); + float edgeDistance = distance - (bullet.Width / 2 + Hitbox.Width / 2); + + if (currentCharacter == Characters.Kaguya && ghostActive) + { + Hitbox.HitDetection = true; + if (Hitbox.HitDetect(Hitbox, bullet.Hitbox) && bullet.Bullet.Ghost) + { + Damage(bullet.Bullet.BulletDamage); + bullet.Bullet.BulletDamage = 0; + bullet.Hit = true; + } + Hitbox.HitDetection = false; + } + + if (edgeDistance < 48 && bullet.Bullet.Team != Team) + CanHeal = true; + + if (currentScoringMetric == ScoringMetric.Graze) + { + if (currentGameMode == VitaruGamemode.Dodge) + distance *= 1.5f; + if (distance <= 64 && bullet.ScoreZone < 300) + bullet.ScoreZone = 300; + else if (distance <= 128 && bullet.ScoreZone < 200) + bullet.ScoreZone = 200; + else if (distance <= 256 && bullet.ScoreZone < 100) + bullet.ScoreZone = 100; + else if (bullet.ScoreZone < 50) + bullet.ScoreZone = 50; + } + } + + /// + /// Check to see what kinda points we should award the player + /// + private void checkScoreZone() + { + if (currentScoringMetric != ScoringMetric.Graze) + { + var scoreZone = new Vector2(256, -512); + var distance = (float)Math.Sqrt(Math.Pow(Position.X - scoreZone.X, 2) + Math.Pow(Position.Y - scoreZone.Y, 2)); + + if (distance <= 1024 - 256 - 128) + ScoreZone = 0; + else if (distance <= 1024 - 256) + ScoreZone = 100; + else if (distance <= 1024 - 128) + ScoreZone = 200; + else if (distance <= 1024) + ScoreZone = 300; + else if (distance <= 1024 + 256) + ScoreZone = 200; + else + ScoreZone = 100; + } + } + + [BackgroundDependencyLoader] + private void load(OsuGameBase game) + { + workingBeatmap.BindTo(game.Beatmap); + } + + #region Spell Stuff + + private void spell(bool keyUp = false, VitaruAction action = VitaruAction.Spell) + { + if (Energy >= energyRequired && currentGameMode == VitaruGamemode.Touhosu && !keyUp || currentCharacter == Characters.AliceMuyart && currentGameMode == VitaruGamemode.Touhosu && !keyUp) + { + //if (currentCharacter == Characters.Alex && action == VitaruAction.Spell) + //alexSpell(); + if (currentCharacter == Characters.ReimuHakurei && action == VitaruAction.Spell) + reimuaSpell(); + else if (currentCharacter == Characters.MarisaKirisame && action == VitaruAction.Spell) + marisaSpell(); + else if (currentCharacter == Characters.SakuyaIzayoi && action == VitaruAction.Spell) + sakuyaSpell(); + else if (currentCharacter == Characters.FlandreScarlet && !Clone && !tabooActive && action == VitaruAction.Spell) + flandereSpell(); + else if (currentCharacter == Characters.RemiliaScarlet && action == VitaruAction.Spell) + { + + } + else if (currentCharacter == Characters.YuyukoSaigyouji && action == VitaruAction.Spell) + { + + } + else if (currentCharacter == Characters.YukariYakumo && action == VitaruAction.Spell) + yukariSpell(); + else if (currentCharacter == Characters.Chen && action == VitaruAction.Spell) + { + + } + else if (currentCharacter == Characters.Kaguya && !Clone && !ghostActive && action == VitaruAction.Spell) + kaguyaSpell(); + else if (currentCharacter == Characters.IbarakiKasen && action == VitaruAction.Spell) + ibarakiSpell(); + else if (currentCharacter == Characters.NueHoujuu && action == VitaruAction.Spell | action == VitaruAction.Spell2 | action == VitaruAction.Spell3 | action == VitaruAction.Spell4) + nueSpell(action); + else if (currentCharacter == Characters.AliceMuyart && !Clone) + { + switch (action) + { + case VitaruAction.Spell when Energystored > 2: + ibarakiSpell(2); + patternCircle(); + break; + case VitaruAction.Spell3 when SetRate != 1 && Energystored > 6: + sakuyaSpell(6); + break; + case VitaruAction.Spell2 when !ghostActive && Energystored > 8: + kaguyaSpell(8); + break; + } + } + } + else if (keyUp) + { + switch (currentCharacter) + { + case Characters.SakuyaIzayoi when action == VitaruAction.Spell: + timeFreezeActive = false; + break; + case Characters.YukariYakumo when action == VitaruAction.Spell: + riftActive = false; + break; + case Characters.Kaguya when action == VitaruAction.Spell: + ghostActive = false; + break; + case Characters.AliceMuyart: + if (action == VitaruAction.Spell3) + timeFreezeActive = false; + else if (action == VitaruAction.Spell2) + ghostActive = false; + break; + } + } + } + + private void reimuaSpell(float energyOverride = -1) + { + + } + + private void marisaSpell(float energyOverride = -1) + { + if (energyOverride == -1) + Energy -= energyRequired; + else + Energy -= energyOverride; + + Parent.Add(drawableLaser = new DrawableLaser(Parent, new Laser + { + LaserSize = new Vector2(80, 400), + Team = Team, + ComboColour = CharacterColor, + StartTime = Time.Current, + EndTime = Time.Current + 2000 + })); + drawableLaser.Position = Position; + } + + private void sakuyaSpell(float energyOverride = -1) + { + if (energyOverride == -1) + Energy -= energyRequired; + else + Energy -= energyOverride; + + timeFreezeActive = true; + + if (originalRate == 0) + originalRate = (float)workingBeatmap.Value.Track.Rate; + + currentRate = originalRate * SetRate; + applyToClock(workingBeatmap.Value.Track, currentRate); + + timeFreezeEndTime = Time.Current + 1000; + } + + private void flandereSpell(float energyOverride = -1) + { + if (energyOverride == -1) + Energy -= energyRequired; + else + Energy -= energyOverride; + + tabooActive = true; + for (int i = 1; i < 4; i++) + { + Vector2 position = new Vector2(-40, -20); + if (i == 2) + { + position = new Vector2(40, 0); + } + else if (i == 3) + { + position = new Vector2(80, -20); + } + + VitaruPlayer player; + Parent.Add(player = new VitaruPlayer(Parent, currentCharacter, this) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Bot = true, + Auto = true, + Position = Position + position + }); + cloneList.Add(player); + } + } + + private void remiliaSpell() + { + + } + + private void yukariSpell() + { + riftActive = true; + riftStart.FadeInFromZero(beatLength / 2); + riftStart.Position = new Vector2(Position.X, Position.Y - 64); + riftEnd.FadeInFromZero(beatLength / 2); + riftEnd.Position = VitaruCursor.CenterCircle.ToSpaceOfOtherDrawable(Vector2.Zero, Parent); + } + + private void kokoroSpell() + { + if (currentCharacter == Characters.KokoroHatano) + { + if (Time.Current <= lastQuarterBeat + hitwindow | Time.Current >= nextQuarterBeat - hitwindow) + Combo++; + else + Combo = Math.Max(Combo -= 30, 0); + + if (Combo >= 10) + metranome.Alpha = Math.Min((Combo - 10) / 100, 0.5f); + else + metranome.Alpha = 0; + + Energystored += 0.01f * Combo; + damageMultiplier = Combo * 0.01f + 1; + healingMultiplier = Combo * 0.01f + 1; + } + } + + private void kaguyaSpell(float energyOverride = -1) + { + if (energyOverride == -1) + Energy -= energyRequired; + else + Energy -= energyOverride; + + ghostActive = true; + this.FadeTo(0.5f, beatLength / 2); + Hitbox.HitDetection = false; + + VitaruPlayer player; + Parent.Add(player = new VitaruPlayer(Parent, currentCharacter, this) + { + Alpha = 0, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Position = Position, + Auto = true, + Bot = true, + Clone = true + }); + player.FadeIn(beatLength / 2); + cloneList.Add(player); + } + + private void ibarakiSpell(float energyOverride = -1) + { + if (energyOverride == -1) + Energy -= energyRequired; + else + Energy -= energyOverride; + + Position = VitaruCursor.CenterCircle.ToSpaceOfOtherDrawable(Vector2.Zero, Hitbox); + } + + private void nueSpell(VitaruAction action) + { + Energy -= energyRequired; + + VitaruPlayer closestPlayer = null; + float closestPlayerDistance = 80; + + foreach (VitaruPlayer player in playerList) + { + Vector2 playerPos = VitaruCursor.CenterCircle.ToSpaceOfOtherDrawable(Vector2.Zero, player) + new Vector2(6); + float distance = (float)Math.Sqrt(Math.Pow(playerPos.X, 2) + Math.Pow(playerPos.Y, 2)); + + if (closestPlayerDistance >= distance) + { + closestPlayerDistance = distance; + closestPlayer = player; + } + } + + if (closestPlayer != null) + { + switch (action) + { + case VitaruAction.Spell: + ufoHealth.AttachedPlayer.ufoList.Remove(ufoHealth); + ufoHealth.AttachedPlayer.ufoContainer.Remove(ufoHealth); + closestPlayer.ufoList.Add(ufoHealth); + closestPlayer.ufoContainer.Add(ufoHealth); + ufoHealth.AttachedPlayer = closestPlayer; + break; + case VitaruAction.Spell2: + ufoEnergy.AttachedPlayer.ufoList.Remove(ufoEnergy); + ufoEnergy.AttachedPlayer.ufoContainer.Remove(ufoEnergy); + closestPlayer.ufoList.Add(ufoEnergy); + closestPlayer.ufoContainer.Add(ufoEnergy); + ufoEnergy.AttachedPlayer = closestPlayer; + break; + case VitaruAction.Spell3: + ufoDamage.AttachedPlayer.ufoList.Remove(ufoDamage); + ufoDamage.AttachedPlayer.ufoContainer.Remove(ufoDamage); + closestPlayer.ufoList.Add(ufoDamage); + closestPlayer.ufoContainer.Add(ufoDamage); + ufoDamage.AttachedPlayer = closestPlayer; + break; + case VitaruAction.Spell4: + ufoMark.AttachedPlayer.ufoList.Remove(ufoMark); + ufoMark.AttachedPlayer.ufoContainer.Remove(ufoMark); + closestPlayer.ufoList.Add(ufoMark); + closestPlayer.ufoContainer.Add(ufoMark); + ufoMark.AttachedPlayer = closestPlayer; + break; + } + } + else + { + switch (action) + { + case VitaruAction.Spell: + ufoHealth.AttachedPlayer.ufoList.Remove(ufoHealth); + ufoHealth.AttachedPlayer.ufoContainer.Remove(ufoHealth); + ufoList.Add(ufoHealth); + ufoContainer.Add(ufoHealth); + ufoHealth.AttachedPlayer = this; + break; + case VitaruAction.Spell2: + ufoEnergy.AttachedPlayer.ufoList.Remove(ufoEnergy); + ufoEnergy.AttachedPlayer.ufoContainer.Remove(ufoEnergy); + ufoList.Add(ufoEnergy); + ufoContainer.Add(ufoEnergy); + ufoEnergy.AttachedPlayer = this; + break; + case VitaruAction.Spell3: + ufoDamage.AttachedPlayer.ufoList.Remove(ufoDamage); + ufoDamage.AttachedPlayer.ufoContainer.Remove(ufoDamage); + ufoList.Add(ufoDamage); + ufoContainer.Add(ufoDamage); + ufoDamage.AttachedPlayer = this; + break; + case VitaruAction.Spell4: + ufoMark.AttachedPlayer.ufoList.Remove(ufoMark); + ufoMark.AttachedPlayer.ufoContainer.Remove(ufoMark); + ufoList.Add(ufoMark); + ufoContainer.Add(ufoMark); + ufoMark.AttachedPlayer = this; + break; + } + } + } + + private void spellUpdate() + { + if (currentCharacter != Characters.NueHoujuu) + { + ufoMark = null; + ufoHealth = null; + ufoEnergy = null; + ufoDamage = null; + + foreach (UFO ufo in ufoList) + { + switch (ufo.UFOType) + { + case UFOType.Mark: + ufoMark = ufo; + break; + case UFOType.Health: + ufoHealth = ufo; + break; + case UFOType.Energy: + ufoEnergy = ufo; + break; + case UFOType.Damage: + ufoDamage = ufo; + break; + } + } + } + + if (ufoHealth != null && ufoHealth.ParentNue.Energy >= (float)Clock.ElapsedFrameTime / 1000) + { + MaxHealth = originalMaxHealth + 10; + ufoHealth.ParentNue.Energy -= (float)Clock.ElapsedFrameTime / 1000; + Heal((float)Clock.ElapsedFrameTime / 1000); + } + else + { + MaxHealth = originalMaxHealth; + if (Health > MaxHealth) + Health = MaxHealth; + } + + if (ufoEnergy != null && ufoEnergy.ParentNue.Energy >= (float)Clock.ElapsedFrameTime / 500 && maxEnergy - Energy >= (float)Clock.ElapsedFrameTime / 250) + { + Energy = Math.Min((float)Clock.ElapsedFrameTime / 250 + Energy, maxEnergy); + ufoEnergy.ParentNue.Energy -= (float)Clock.ElapsedFrameTime / 500; + } + else if (CanHeal) + Energy = Math.Min((float)Clock.ElapsedFrameTime / 500 * energyGainMultiplier + Energy, maxEnergy); + + CharacterSign.Alpha = Energy / (maxEnergy * 2); + + if (ghostActive) + Energy -= (float)Clock.ElapsedFrameTime / 1000 * energyRequiredPerSecond; + + if (riftActive) + Energy -= (float)Clock.ElapsedFrameTime / 1000 * energyRequiredPerSecond; + + if (Energy <= 0) + { + Energy = 0; + ghostActive = false; + timeFreezeActive = false; + riftActive = false; + } + + CharacterSign.Alpha = Energy / (maxEnergy * 2); + + foreach (Drawable child in Parent.Children) + if (child is Rift rift) + { + Vector2 riftPos = rift.ToSpaceOfOtherDrawable(Vector2.Zero, Hitbox); + float distance = (float)Math.Sqrt(Math.Pow(riftPos.X + 20, 2) + Math.Pow(riftPos.Y + 20, 2)); + + if (distance <= 32 && warpTime <= Time.Current && rift.Alpha > 0) + { + warpTime = Time.Current + beatLength; + Position = rift.LinkedRift.ToSpaceOfOtherDrawable(Vector2.Zero, Parent); + } + } + + if (timeFreezeEndTime >= Time.Current) + { + if (!timeFreezeActive) + { + currentRate += (float)Clock.ElapsedFrameTime / 100; + if (currentRate > originalRate) + currentRate = originalRate; + applyToClock(workingBeatmap.Value.Track, currentRate); + if (timeFreezeEndTime - 500 <= Time.Current) + { + currentRate = originalRate; + applyToClock(workingBeatmap.Value.Track, currentRate); + } + } + else + { + float energyDrainMultiplier = 0; + if (currentRate < 1) + energyDrainMultiplier = 1 - currentRate; + else if (currentRate >= 1) + energyDrainMultiplier = currentRate - 1; + + Energy -= (float)Clock.ElapsedFrameTime / 1000 * (1 / currentRate) * energyRequiredPerSecond * energyDrainMultiplier; + timeFreezeEndTime = Time.Current + 2000; + currentRate = originalRate * SetRate; + applyToClock(workingBeatmap.Value.Track, currentRate); + } + } + + if (leader && Health > 0) + { + foreach(VitaruPlayer player in playerList) + { + Vector2 otherPlayerPos = player.ToSpaceOfOtherDrawable(Vector2.Zero, this) + new Vector2(6); + float distance = (float)Math.Sqrt(Math.Pow(otherPlayerPos.X, 2) + Math.Pow(otherPlayerPos.Y, 2)); + + if (player.Hitbox.Team == Hitbox.Team && distance <= 128) + { + player.Heal(2 * (float)Clock.ElapsedFrameTime); + } + } + } + + if (tabooActive) + { + if (cloneList.Count == 0) + tabooActive = false; + } + + if (Time.Current >= reFreezeTime) + { + reFreezeTime = double.MaxValue; + + foreach (Crystal crystal in crystalList) + crystal.ReCollect(1000); + + CharacterKiai.Delay(900) + .FadeIn(100); + CharacterSprite.Delay(900) + .FadeIn(100); + } + + if (Time.Current >= reFrozenTime) + { + reFrozenTime = double.MaxValue; + Dead = false; + shattered = false; + Hitbox.HitDetection = true; + } + + if (!riftActive && riftStart != null && riftStart.Alpha == 1) + { + riftEnd.FadeOut(beatLength / 4); + riftStart.FadeOut(beatLength / 4); + } + + if (!ghostActive && Alpha == 0.5f) + { + Hitbox.HitDetection = true; + foreach (VitaruPlayer clone in cloneList) + { + clone.FadeOut(beatLength / 2) + .Delay(beatLength / 2) + .Expire(); + cloneList.Remove(clone); + break; + } + + this.FadeIn(beatLength / 2); + } + + if (ufoList.Count > 0) + ufoContainer.RotateTo((float)(Clock.CurrentTime / -1000 * 90)); + + //just for debugging + Energystored = Energy; + } + + public override float Damage(float damage) + { + if (currentCharacter == Characters.Cirno) + { + Health -= damage; + + if (Health <= 0 && energyRequired <= Energy) + { + Energy -= energyRequired; + shattered = true; + reFreezeTime = Time.Current + beatLength; + reFrozenTime = Time.Current + beatLength * 2; + Hitbox.HitDetection = false; + + foreach (Crystal crystal in crystalList) + crystal.Pop(1000); + CharacterKiai.FadeOut(100); + CharacterSprite.FadeOut(100); + + return Health = MaxHealth; + } + return Health; + } + return base.Damage(damage); + } + + private void applyToClock(IAdjustableClock clock, float speed) + { + var pitchAdjust = clock as IHasPitchAdjust; + if (pitchAdjust != null) + pitchAdjust.PitchAdjust = speed; + SpeedMultiplier = 1 / speed; + foreach (Drawable draw in Parent) + { + VitaruPlayer player = draw as VitaruPlayer; + if (player?.Team == Team && player != this) + player.SpeedMultiplier = SpeedMultiplier / 2 + 0.5f; + } + } + #endregion + + #region Shooting Stuff + private void bulletAddRad(float speed, float angle, Color4 color) + { + DrawableBullet drawableBullet; + + if (Invert) + angle += (float)Math.PI; + + Parent.Add(drawableBullet = new DrawableBullet(Parent, + new Bullet + { + StartTime = Time.Current, + Cs = 1.2f, + DummyMode = true, + ComboColour = color, + BulletAngleRadian = angle, + BulletSpeed = speed, + BulletDiameter = 16, + BulletDamage = 20 * damageMultiplier, + Team = Team, + Ghost = currentCharacter == Characters.Kaguya | currentCharacter == Characters.AliceMuyart + })); + if (vampuric) + drawableBullet.OnHit = () => Heal(0.5f); + drawableBullet.MoveTo(Position); + } + + private void patternWave() + { + const int numberbullets = 3; + float directionModifier = -0.1F; + Color4 color = CharacterColor; + for (int i = 1; i <= numberbullets; i++) + { + if (currentCharacter == Characters.NueHoujuu) + { + if (i == 1) + color = Color4.Red; + else if (i == 2) + color = Color4.Black; + else + color = Color4.Blue; + } + //-90 = up + bulletAddRad(1, MathHelper.DegreesToRadians(-90) + directionModifier, color); + directionModifier += 0.1f; + } + } + + private void patternCircle() + { + int numberbullets = 8; + float directionModifier = (360f / numberbullets); + float direction = MathHelper.DegreesToRadians(-90); + directionModifier = MathHelper.DegreesToRadians(directionModifier); + for (int i = 1; i <= numberbullets; i++) + { + bulletAddRad(1, direction, CharacterColor); + direction += directionModifier; + } + } + #endregion + + public override void Death() + { + if (Bot && Clone) + { + parentPlayer.cloneList.Remove(this); + Expire(); + } + else if (cloneList.Count > 0) + { + foreach(VitaruPlayer player in cloneList) + { + player.Bot = Bot; + player.Auto = Auto; + player.Clone = Clone; + player.Invert = Invert; + cloneList.Remove(player); + player.Actions = Actions; + player.cloneList = cloneList; + + foreach (VitaruPlayer clone in player.cloneList) + clone.parentPlayer = player; + + if (!Bot) + VitaruPlayfield.VitaruPlayer = player; + + Expire(); + break; + } + } + } + + #region Player Input Stuff + /// + /// Moves the player based on player input + /// + private void playerInput() + { + //Handles Player Speed + float yTranslationDistance = player_speed * (float)Clock.ElapsedFrameTime * SpeedMultiplier; + float xTranslationDistance = player_speed * (float)Clock.ElapsedFrameTime * SpeedMultiplier; + Vector2 playerPosition = Position; + + if (Auto) + { + Actions[VitaruAction.Up] = false; + Actions[VitaruAction.Down] = false; + Actions[VitaruAction.Left] = false; + Actions[VitaruAction.Right] = false; + Actions[VitaruAction.Slow] = false; + Actions[VitaruAction.Fast] = false; + Actions[VitaruAction.Shoot] = false; + VisibleHitbox.Alpha = 0; + + bool bulletClose = false; + DrawableBullet closestBullet = null; + float closestBulletEdgeDitance = float.MaxValue; + float closestBulletAngle = 0; + + VitaruPlayer closestPlayerLatterally = null; + float closestPlayerLatteralDistance = float.MaxValue; + + + //bool bulletBehind = false; + float behindBulletEdgeDitance = float.MaxValue; + float behindBulletAngle = 0; + + foreach (Drawable draw in Parent) + if (draw is DrawableBullet) + { + DrawableBullet bullet = draw as DrawableBullet; + if (bullet.Bullet.Team != Team) + { + Vector2 pos = bullet.ToSpaceOfOtherDrawable(Vector2.Zero, this) + new Vector2(6); + float distance = (float)Math.Sqrt(Math.Pow(pos.X, 2) + Math.Pow(pos.Y, 2)); + float edgeDistance = distance - (bullet.Width / 2 + Hitbox.Width / 2); + float angleToBullet = MathHelper.RadiansToDegrees((float)Math.Atan2((bullet.Position.Y - Position.Y), (bullet.Position.X - Position.X))) + 90 + Rotation; + + if (closestBulletAngle < 360 - field_of_view | closestBulletAngle < -field_of_view && closestBulletAngle > field_of_view | closestBulletAngle > 360 + field_of_view) + if (closestBullet.Position.X > Position.X && bullet.Position.X < Position.X || closestBullet.Position.X < Position.X && bullet.Position.X > Position.X) + { + //bulletBehind = true; + behindBulletEdgeDitance = edgeDistance; + behindBulletAngle = angleToBullet; + } + + if (edgeDistance < closestBulletEdgeDitance) + { + closestBulletEdgeDitance = edgeDistance; + closestBullet = bullet; + closestBulletAngle = angleToBullet; + } + } + } + //Lets go after enemy players if possible + else if (draw is VitaruPlayer) + { + VitaruPlayer player = draw as VitaruPlayer; + if (player.Team != Team) + { + float latteralDistance = Position.X - player.Position.X; + + if (latteralDistance < 0) + latteralDistance *= -1; + + if (latteralDistance < closestPlayerLatteralDistance) + { + closestPlayerLatterally = player; + closestPlayerLatteralDistance = latteralDistance; + } + } + } + + if (closestBulletEdgeDitance <= 50) + { + bulletClose = true; + if (closestBulletEdgeDitance <= 30) + { + if (!Invert) + Actions[VitaruAction.Down] = true; + else + Actions[VitaruAction.Up] = true; + + Actions[VitaruAction.Slow] = true; + } + + if (closestBulletAngle > 360 - field_of_view | closestBulletAngle > -field_of_view && closestBulletAngle < field_of_view | closestBulletAngle < 360 + field_of_view) + { + if (closestBullet.X < Position.X) + Actions[VitaruAction.Right] = true; + else + Actions[VitaruAction.Left] = true; + } + } + else if (!bulletClose) + { + if (Position.X > 512 - 100) + Actions[VitaruAction.Left] = true; + else if (Position.X < 100) + Actions[VitaruAction.Right] = true; + else if (closestPlayerLatterally != null) + { + if (Position.X > closestPlayerLatterally.Position.X) + Actions[VitaruAction.Left] = true; + else + Actions[VitaruAction.Right] = true; + } + + Actions[VitaruAction.Slow] = true; + + if (Position.Y < 400 && !Invert || Position.Y < 300 && Invert) + Actions[VitaruAction.Down] = true; + else if (Position.Y > 500 && !Invert || Position.Y > 400 && Invert) + Actions[VitaruAction.Up] = true; + } + + Actions[VitaruAction.Shoot] = true; + + if (Actions[VitaruAction.Slow]) + { + xTranslationDistance /= 2; + yTranslationDistance /= 2; + VisibleHitbox.Alpha = 1; + } + if (Actions[VitaruAction.Fast]) + { + xTranslationDistance *= 2; + yTranslationDistance *= 2; + } + + if (Actions[VitaruAction.Up]) + playerPosition.Y -= yTranslationDistance; + if (Actions[VitaruAction.Left]) + playerPosition.X -= xTranslationDistance; + if (Actions[VitaruAction.Down]) + playerPosition.Y += yTranslationDistance; + if (Actions[VitaruAction.Right]) + playerPosition.X += xTranslationDistance; + + playerPosition = Vector2.ComponentMin(playerPosition, playerBounds.Yw); + playerPosition = Vector2.ComponentMax(playerPosition, playerBounds.Xz); + } + else + { + if (Actions[VitaruAction.Slow]) + { + xTranslationDistance /= 2; + yTranslationDistance /= 2; + } + if (Actions[VitaruAction.Fast]) + { + xTranslationDistance *= 2; + yTranslationDistance *= 2; + } + + if (Actions[VitaruAction.Up]) + playerPosition.Y -= yTranslationDistance; + if (Actions[VitaruAction.Left]) + playerPosition.X -= xTranslationDistance; + if (Actions[VitaruAction.Down]) + playerPosition.Y += yTranslationDistance; + if (Actions[VitaruAction.Right]) + playerPosition.X += xTranslationDistance; + + playerPosition = Vector2.ComponentMin(playerPosition, playerBounds.Yw); + playerPosition = Vector2.ComponentMax(playerPosition, playerBounds.Xz); + } + Position = playerPosition; + } + + public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => true; + + public bool OnPressed(VitaruAction action) + { + if (!Bot && !Puppet) + { + //Keyboard Stuff + if (currentCharacter == Characters.AliceMuyart) + { + if (action == VitaruAction.Increase) + SetRate = Math.Min(SetRate + 0.1f, 1.5f); + if (action == VitaruAction.Decrease) + SetRate = Math.Max(SetRate - 0.1f, 0.1f); + } + else if (currentCharacter == Characters.SakuyaIzayoi) + { + if (action == VitaruAction.Increase) + SetRate = Math.Min(SetRate + 0.2f, 0.8f); + if (action == VitaruAction.Decrease) + SetRate = Math.Max(SetRate - 0.2f, 0.2f); + } + + if (action == VitaruAction.Up) + Actions[VitaruAction.Up] = true; + if (action == VitaruAction.Down) + Actions[VitaruAction.Down] = true; + if (action == VitaruAction.Left) + Actions[VitaruAction.Left] = true; + if (action == VitaruAction.Right) + Actions[VitaruAction.Right] = true; + if (action == VitaruAction.Fast && currentCharacter != Characters.IbarakiKasen) + Actions[VitaruAction.Fast] = true; + if (action == VitaruAction.Slow) + { + if (currentSkin != GraphicsPresets.StandardCompetitive && currentSkin != GraphicsPresets.HighPerformanceCompetitive) + VisibleHitbox.Alpha = 1; + + Actions[VitaruAction.Slow] = true; + } + if (action == VitaruAction.LeftShoot | action == VitaruAction.RightShoot | action == VitaruAction.Shoot | action == VitaruAction.Spell && currentCharacter == Characters.KokoroHatano) + { + kokoroSpell(); + + if (Time.Current <= lastQuarterBeat + hitwindow | Time.Current >= nextHalfBeat - hitwindow) + patternWave(); + } + + //Mouse Stuff + if (action == VitaruAction.Shoot && currentCharacter != Characters.KokoroHatano) + Actions[VitaruAction.Shoot] = true; + + spell(false, action); + sendPacket(); + + return true; + } + return false; + } + + public bool OnReleased(VitaruAction action) + { + if (!Bot && !Puppet) + { + //Keyboard Stuff + if (action == VitaruAction.Up) + Actions[VitaruAction.Up] = false; + if (action == VitaruAction.Down) + Actions[VitaruAction.Down] = false; + if (action == VitaruAction.Left) + Actions[VitaruAction.Left] = false; + if (action == VitaruAction.Right) + Actions[VitaruAction.Right] = false; + if (action == VitaruAction.Fast) + Actions[VitaruAction.Fast] = false; + if (action == VitaruAction.Slow) + { + if (currentSkin != GraphicsPresets.StandardCompetitive && currentSkin != GraphicsPresets.HighPerformanceCompetitive) + VisibleHitbox.Alpha = 0; + + Actions[VitaruAction.Slow] = false; + } + + //Mouse Stuff + if (action == VitaruAction.Shoot) + Actions[VitaruAction.Shoot] = false; + spell(true, action); + sendPacket(); + + return true; + } + return false; + } + #endregion + + #region Networking + private void sendPacket() + { + if (VitaruNetworkingClientHandler != null && !Puppet) + { + VitaruPlayerInformation playerInformation = new VitaruPlayerInformation + { + Character = currentCharacter, + PlayerX = Position.X, + PlayerY = Position.Y, + PlayerID = PlayerID, + Actions = Actions, + ClockSpeed = currentRate + }; + + ClientInfo clientInfo = new ClientInfo + { + IP = VitaruNetworkingClientHandler.ClientInfo.IP, + Port = VitaruNetworkingClientHandler.ClientInfo.Port + }; + + VitaruInMatchPacket packet = new VitaruInMatchPacket(clientInfo) { PlayerInformation = playerInformation }; + + VitaruNetworkingClientHandler.SendToHost(packet); + VitaruNetworkingClientHandler.SendToInGameClients(packet); + } + } + + private void packetReceived(Packet p) + { + if (p is VitaruInMatchPacket packet) + { + if (packet.PlayerInformation.Character == Characters.SakuyaIzayoi | packet.PlayerInformation.Character == Characters.AliceMuyart) + applyToClock(workingBeatmap.Value.Track, packet.PlayerInformation.ClockSpeed); + + if (packet.PlayerInformation.PlayerID == PlayerID && Puppet) + { + Actions = packet.PlayerInformation.Actions; + Position = new Vector2(packet.PlayerInformation.PlayerX, packet.PlayerInformation.PlayerY); + } + + VitaruNetworkingClientHandler.ShareWithOtherPeers(packet); + } + } + #endregion + + #region Touhosu Story + private double startSpeaking = double.MaxValue; + private double lengthOfSpeaking; + + private readonly Bindable familiar = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.Familiar); + private int familiarity; + + private readonly Bindable lastDance = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.LastDance); + private int dance; + + private readonly Bindable insane = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.Insane); + private int insanity; + + private readonly Bindable awoken = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.Awoken); + private int awakening; + + private readonly Bindable sacred = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.Sacred); + private int tresspassing; + + private readonly Bindable resurrected = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.Resurrected); + private int resurrection; + + public void Speak(string text) + { + textContainer.FadeTo(0.5f, 200); + textContainer.Text = text; + startSpeaking = Time.Current; + lengthOfSpeaking = 0; + + int y = 150; + foreach (char i in text) + { + lengthOfSpeaking += y; + y++; + } + } + + private void speakingUpdate() + { + if (Time.Current > startSpeaking + lengthOfSpeaking) + textContainer.FadeOut(200); + + if (workingBeatmap.Value.BeatmapInfo.OnlineBeatmapID == 1371893 && currentCharacter == Characters.ReimuHakurei && !familiar) + { + if (Time.Current >= 5200 && familiarity == 0) + { + Speak("This place. . ."); + familiarity++; + } + if (Time.Current >= 59100 && familiarity == 1) + { + Speak("It seems familiar. . ."); + familiarity++; + } + if (Time.Current >= 93920 && familiarity == 2) + { + Speak("Yes, this is where I got into my first fight!"); + familiarity++; + } + if (Time.Current >= 149572 && familiarity == 3) + { + Speak("Fairies were mad for seemingly no reason,"); + familiarity++; + } + if (Time.Current >= 177398 && familiarity == 4) + { + Speak("I had found out later Marisa had trespassed without even knowing,"); + familiarity++; + } + if (Time.Current >= 205224 && familiarity == 5) + { + Speak("Thankfully she came to help, we fought hard,"); + familiarity++; + } + if (Time.Current >= 233050 && familiarity == 6) + { + Speak("Then the Scarlet sisters came,"); + familiarity++; + } + if (Time.Current >= 246963 && familiarity == 7) + { + Speak("I tried to resolve this, but too much blood had been shed,"); + familiarity++; + } + if (Time.Current >= 274789 && familiarity == 8) + { + Speak("We fled, planning to meet at their mansion later that week,"); + familiarity++; + } + if (Time.Current >= 302615 && familiarity == 9) + { + Speak("That was a mistake."); + familiarity++; + familiar.Value = true; + } + } + + if (false)//workingBeatmap.Value.BeatmapInfo.OnlineBeatmapID == 1548917 && currentCharacter == Characters.KokoroHatano && !lastDance) + { + if (Time.Current >= 1430 && dance == 0) + { + Speak("This is it,"); + dance++; + } + if (Time.Current >= 23760 && dance == 1) + { + Speak("My final act,"); + dance++; + } + if (Time.Current >= 43300 && dance == 2) + { + Speak("My Last Dance."); + dance++; + lastDance.Value = true; + } + } + + if (false)//workingBeatmap.Value.BeatmapInfo.OnlineBeatmapID == 114716 && currentCharacter == Characters.FlandreScarlet && insane) + { + if (Time.Current >= 760 && insanity == 0) + { + Speak("That piano. . ."); + insanity++; + } + if (Time.Current >= 12340 && insanity == 1) + { + Speak("It is driving me insane!"); + insanity++; + } + if (Time.Current >= 28600 && insanity == 2) + { + Speak("Missy please, I am trying to think."); + insanity++; + insane.Value = false; + } + } + + if (false)//workingBeatmap.Value.BeatmapInfo.OnlineBeatmapID == 114716 && currentCharacter == Characters.FlandreScarlet && !insane) + { + if (Time.Current >= 760 && insanity == 0) + { + Speak("That piano really needs to stop. . ."); + insanity++; + } + } + /* + if (workingBeatmap.Value.BeatmapInfo.OnlineBeatmapID == 114716 && currentCharacter == Characters.RemiliaScarlet && !insane) + { + if (Time.Current >= 760 && awakening == 0) + { + Speak("Flandre, what happened to you?"); + awakening++; + } + if (Time.Current >= 12340 && awakening == 1) + { + Speak("Flan, are you there?"); + awakening++; + } + if (Time.Current >= 28600 && awakening == 2) + { + Speak("Its me, your sister Remilia,"); + awakening++; + } + if (Time.Current >= 0 && awakening == 3) + { + Speak("Flan? I know you can hear me."); + awakening++; + } + if (Time.Current >= 0 && awakening == 4) + { + Speak("Please? I need to talk."); + awakening++; + } + if (Time.Current >= 0 && awakening == 5) + { + Speak("I know you're upset,"); + awakening++; + } + if (Time.Current >= 0 && awakening == 6) + { + Speak("But I need my sister back."); + awakening++; + } + if (Time.Current >= 0 && awakening == 7) + { + Speak("What would Hong say if she knew you were this lazy?"); + awakening++; + } + if (Time.Current >= 0 && awakening == 8) + { + //Flandre.Speak("\"Get off your ass\"?"); + awakening++; + } + } + */ + + if (false)//workingBeatmap.Value.BeatmapInfo.OnlineBeatmapID == 148000 && currentCharacter == Characters.Kaguya) + { + if (Time.Current >= 1280 && tresspassing == 0) + { + Speak("What a lovely night it is for a walk."); + tresspassing++; + } + if (Time.Current >= 20860 && tresspassing == 1) + { + Speak("Oh?"); + tresspassing++; + } + if (Time.Current >= 22120 && tresspassing == 2) + { + Speak("Someone has been here already. . ."); + tresspassing++; + } + if (Time.Current >= 37280 && tresspassing == 3) + { + Speak("Thats them over there."); + tresspassing++; + } + if (Time.Current >= 41060 && tresspassing == 4) + { + Speak("Whaaa-"); + tresspassing++; + } + if (Time.Current >= 82740 && tresspassing == 5) + { + Speak("Why are we fighting? What did I do to you?"); + tresspassing++; + } + } + } + #endregion + } + + public enum Characters + { + //Alex, + [Description("Reimu Hakurei")] + ReimuHakurei = 1, + [Description("Marisa Kirisame")] + MarisaKirisame, + [Description("Sakuya Izayoi")] + SakuyaIzayoi, + [Description("Hong Meiling")] + HongMeiling, + [Description("Flandre Scarlet")] + FlandreScarlet, + [Description("Remilia Scarlet")] + RemiliaScarlet, + [Description("Cirno")] + Cirno, + [Description("Tenshi Hinanai")] + TenshiHinanai, + [Description("Yuyuko Saigyouji")] + YuyukoSaigyouji, + [Description("Yukari Yakumo")] + YukariYakumo, + [Description("Ran Yakumo")] + RanYakumo, + [Description("Chen")] + Chen, + [Description("Alice Margatroid")] + AliceMargatroid, + [Description("Komachi Onozuka")] + KomachiOnozuka, + [Description("Byakuren Hijiri")] + ByakurenHijiri, + [Description("Rumia")] + Rumia, + [Description("Sikieiki Yamaxanadu")] + SikieikiYamaxanadu, + [Description("Suwako Moriya")] + SuwakoMoriya, + [Description("Youmu Konpaku")] + YoumuKonpaku, + [Description("Kokoro Hatano")] + KokoroHatano, + [Description("Kaguya")] + Kaguya, + [Description("Ibaraki Kasen")] + IbarakiKasen, + [Description("Nue Houjuu")] + NueHoujuu, + //[Description("Meme")] + //Taikonator, + [Description("Alice Muyart")] + AliceMuyart, + [Description("Arysa Muyart")] + ArysaMuyart + } +} diff --git a/osu.Game.Rulesets.Vitaru/Objects/Drawables/DrawableBullet.cs b/osu.Game.Rulesets.Vitaru/Objects/Drawables/DrawableBullet.cs new file mode 100644 index 0000000000..0bcd38a78a --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Objects/Drawables/DrawableBullet.cs @@ -0,0 +1,276 @@ +using osu.Framework.Graphics; +using OpenTK; +using System; +using osu.Game.Rulesets.Vitaru.Objects.Drawables.Pieces; +using osu.Game.Rulesets.Vitaru.Judgements; +using osu.Game.Rulesets.Vitaru.Settings; +using osu.Game.Rulesets.Vitaru.Scoring; +using osu.Game.Rulesets.Vitaru.UI; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Scoring; +using Symcol.Core.GameObjects; + +namespace osu.Game.Rulesets.Vitaru.Objects.Drawables +{ + public class DrawableBullet : DrawableVitaruHitObject + { + public static int BulletCount; + + private readonly ScoringMetric currentScoringMetric = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.ScoringMetric); + private readonly VitaruGamemode currentGameMode = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.GameMode); + + //Used like a multiple (useful for spells in multiplayer) + public static float BulletSpeedModifier = 1; + + //Playfield size + Margin of 10 on each side + public Vector4 BulletBounds = new Vector4(-10, -10, 520, 830); + + //Result of bulletSpeed + bulletAngle math, should never be modified outside of this class + public Vector2 BulletVelocity; + + //Set to "true" when a judgement should be returned + private bool returnJudgement; + + public bool ReturnGreat = false; + + //Can be set for the Graze ScoringMetric + public int ScoreZone; + + //Should be set to true when a character is hit + public bool Hit; + + //Incase we want to be deleted in the near future + public double BulletDeleteTime = -1; + + private readonly DrawablePattern drawablePattern; + public readonly Bullet Bullet; + + public Action OnHit; + + public SymcolHitbox Hitbox; + + private BulletPiece bulletPiece; + + private bool started; + private bool loaded; + + public DrawableBullet(Container parent, Bullet bullet, DrawablePattern drawablePattern) : base(bullet, parent) + { + AlwaysPresent = true; + Alpha = 0; + + Anchor = Anchor.TopLeft; + Origin = Anchor.Centre; + + BulletCount++; + + Bullet = bullet; + this.drawablePattern = drawablePattern; + + if (currentGameMode == VitaruGamemode.Dodge) + BulletBounds = new Vector4(-10, -10, 522, 394); + } + + public DrawableBullet(Container parent, Bullet bullet) : base(bullet, parent) + { + AlwaysPresent = true; + Alpha = 0; + + Anchor = Anchor.TopLeft; + Origin = Anchor.Centre; + + BulletCount++; + + Bullet = bullet; + + if (currentGameMode == VitaruGamemode.Dodge) + BulletBounds = new Vector4(-10, -10, 522, 394); + } + + /// + /// Called 1 second before the bullet's starttime + /// + private void load() + { + if (!loaded) + { + loaded = true; + + Size = new Vector2(Bullet.BulletDiameter); + Scale = new Vector2(0.1f); + + Children = new Drawable[] + { + bulletPiece = new BulletPiece(this), + Hitbox = new SymcolHitbox(new Vector2(Bullet.BulletDiameter), Shape.Circle) + { + Team = Bullet.Team, + HitDetection = false + } + }; + } + } + + /// + /// Called to unload the bullet for storage + /// + private void unload() + { + if (loaded) + { + loaded = false; + started = false; + returnJudgement = false; + BulletDeleteTime = -1; + Alpha = 0; + + Remove(bulletPiece); + bulletPiece.Dispose(); + Remove(Hitbox); + Hitbox.Dispose(); + ParentContainer.Remove(this); + Dispose(); + } + } + + /// + /// Called once when the bullet starts + /// + private void start() + { + if (!started) + { + Position = Bullet.Position; + Hitbox.HitDetection = true; + started = true; + this.FadeInFromZero(100); + this.ScaleTo(Vector2.One, 100); + BulletVelocity = getBulletVelocity(); + } + } + + protected override void CheckForJudgements(bool userTriggered, double timeOffset) + { + base.CheckForJudgements(userTriggered, timeOffset); + + if (returnJudgement) + { + if (currentScoringMetric == ScoringMetric.ScoreZones) + { + switch (VitaruPlayfield.VitaruPlayer.ScoreZone) + { + case 0: + AddJudgement(new VitaruJudgement { Result = HitResult.Miss }); + break; + case 100: + AddJudgement(new VitaruJudgement { Result = HitResult.Ok }); + break; + case 200: + AddJudgement(new VitaruJudgement { Result = HitResult.Good }); + break; + case 300: + AddJudgement(new VitaruJudgement { Result = HitResult.Great }); + break; + } + } + else if (currentScoringMetric == ScoringMetric.InverseCatch) + { + switch (VitaruPlayfield.VitaruPlayer.ScoreZone) + { + case 0: + AddJudgement(new VitaruJudgement { Result = HitResult.Miss }); + break; + case 100: + AddJudgement(new VitaruJudgement { Result = HitResult.Great }); + break; + case 200: + AddJudgement(new VitaruJudgement { Result = HitResult.Great }); + break; + case 300: + AddJudgement(new VitaruJudgement { Result = HitResult.Great }); + break; + } + } + else if (currentScoringMetric == ScoringMetric.Graze) + { + switch (ScoreZone) + { + case 0: + AddJudgement(new VitaruJudgement { Result = HitResult.Miss }); + break; + case 50: + AddJudgement(new VitaruJudgement { Result = HitResult.Meh }); + break; + case 100: + AddJudgement(new VitaruJudgement { Result = HitResult.Ok }); + break; + case 200: + AddJudgement(new VitaruJudgement { Result = HitResult.Good }); + break; + case 300: + AddJudgement(new VitaruJudgement { Result = HitResult.Great }); + break; + } + } + } + + else if (Hit) + { + if (!Bullet.DummyMode) + AddJudgement(new VitaruJudgement { Result = HitResult.Miss }); + unload(); + } + + else if (ReturnGreat) + { + AddJudgement(new VitaruJudgement { Result = HitResult.Great }); + unload(); + } + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + if(isDisposing) + BulletCount--; + } + + private Vector2 getBulletVelocity() + { + Vector2 velocity = new Vector2(Bullet.BulletSpeed * (float)Math.Cos(Bullet.BulletAngleRadian), Bullet.BulletSpeed * (float)Math.Sin(Bullet.BulletAngleRadian)); + return velocity; + } + + protected override void Update() + { + base.Update(); + + if (OnHit != null && Hit) + { + OnHit(); + OnHit = null; + } + + if (Position.Y >= BulletBounds.Y | Position.X >= BulletBounds.X | Position.Y <= BulletBounds.W | Position.X <= BulletBounds.Z && Time.Current >= Bullet.StartTime | Bullet.DummyMode || !Bullet.ObeyBoundries && Time.Current >= Bullet.StartTime | Bullet.DummyMode) + load(); + + if (BulletDeleteTime <= Time.Current && BulletDeleteTime != -1 || Time.Current < Bullet.StartTime && !Bullet.DummyMode) + unload(); + + if (Time.Current >= Bullet.StartTime) + { + start(); + + float frameTime = (float)Clock.ElapsedFrameTime; + this.MoveToOffset(new Vector2(BulletVelocity.X * BulletSpeedModifier * frameTime, BulletVelocity.Y * BulletSpeedModifier * frameTime)); + + if (Bullet.ObeyBoundries && Position.Y < BulletBounds.Y | Position.X < BulletBounds.X | Position.Y > BulletBounds.W | Position.X > BulletBounds.Z && !returnJudgement) + { + returnJudgement = true; + BulletDeleteTime = Time.Current + TIME_FADEOUT / 12; + this.FadeOutFromOne(TIME_FADEOUT / 12); + } + } + } + } +} diff --git a/osu.Game.Rulesets.Vitaru/Objects/Drawables/DrawableLaser.cs b/osu.Game.Rulesets.Vitaru/Objects/Drawables/DrawableLaser.cs new file mode 100644 index 0000000000..e7fb653785 --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Objects/Drawables/DrawableLaser.cs @@ -0,0 +1,244 @@ +using OpenTK; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Vitaru.Judgements; +using osu.Game.Rulesets.Vitaru.Objects.Drawables.Pieces; +using osu.Game.Rulesets.Vitaru.Scoring; +using osu.Game.Rulesets.Vitaru.Settings; +using osu.Game.Rulesets.Vitaru.UI; +using Symcol.Core.GameObjects; + +namespace osu.Game.Rulesets.Vitaru.Objects.Drawables +{ + public class DrawableLaser : DrawableVitaruHitObject + { + private readonly ScoringMetric currentScoringMetric = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.ScoringMetric); + private VitaruGamemode currentGameMode = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.GameMode); + + //Set to "true" when a judgement should be returned + private bool returnJudgement; + + private bool returnedJudgement; + + public bool ReturnGreat = false; + + //Can be set for the Graze ScoringMetric + public int ScoreZone; + + //Should be set to true when a character is hit + public bool Hit; + + //Incase we want to be deleted in the near future + public double LaserDeleteTime = -1; + + public SymcolHitbox Hitbox; + private LaserPiece laserPiece; + + private readonly DrawablePattern drawablePattern; + public readonly Laser Laser; + + private const float fade_in_time = 200; + private const float fade_out_time = 200; + + private bool started; + private bool loaded; + + public DrawableLaser(Container parent, Laser laser, DrawablePattern drawablePattern) : base(laser, parent) + { + AlwaysPresent = true; + Alpha = 0; + + Anchor = Anchor.TopLeft; + Origin = Anchor.BottomCentre; + + Laser = laser; + this.drawablePattern = drawablePattern; + + Size = new Vector2(Laser.LaserSize.X / 2, Laser.LaserSize.Y / 8); + Rotation = MathHelper.RadiansToDegrees(Laser.LaserAngleRadian); + } + + public DrawableLaser(Container parent, Laser laser) : base(laser, parent) + { + AlwaysPresent = true; + Alpha = 0; + + Anchor = Anchor.TopLeft; + Origin = Anchor.BottomCentre; + + Laser = laser; + + Size = new Vector2(Laser.LaserSize.X / 2, Laser.LaserSize.Y / 8); + Rotation = MathHelper.RadiansToDegrees(Laser.LaserAngleRadian); + } + + /// + /// Called 1 second before the bullet's starttime + /// + private void load() + { + if (!loaded) + { + loaded = true; + + Children = new Drawable[] + { + laserPiece = new LaserPiece(this), + Hitbox = new SymcolHitbox(new Vector2(Laser.LaserSize.X / 2, Laser.LaserSize.Y / 8), Shape.Rectangle) + { + Team = Laser.Team, + HitDetection = false + } + }; + } + } + + /// + /// Called to unload the bullet for storage + /// + private void unload() + { + if (loaded) + { + loaded = false; + started = false; + returnJudgement = false; + LaserDeleteTime = -1; + Alpha = 0; + + Remove(laserPiece); + laserPiece.Dispose(); + Remove(Hitbox); + Hitbox.Dispose(); + ParentContainer.Remove(this); + Dispose(); + } + } + + /// + /// Called once when the bullet starts + /// + private void start() + { + if (!started) + { + Hitbox.HitDetection = true; + started = true; + this.FadeInFromZero(fade_in_time); + this.ResizeTo(Laser.LaserSize, fade_in_time); + laserPiece.ResizeTo(Laser.LaserSize, fade_in_time); + Hitbox.ResizeTo(Laser.LaserSize, fade_in_time); + } + } + + public void End() + { + if (started) + { + started = false; + this.FadeOutFromOne(fade_out_time); + this.ResizeTo(new Vector2(Laser.LaserSize.X / 2, Laser.LaserSize.Y), fade_out_time); + laserPiece.ResizeTo(new Vector2(Laser.LaserSize.X / 2, Laser.LaserSize.Y), fade_out_time); + Hitbox.ResizeTo(new Vector2(Laser.LaserSize.X / 2, Laser.LaserSize.Y), fade_out_time); + LaserDeleteTime = Time.Current + fade_out_time; + } + } + + protected override void CheckForJudgements(bool userTriggered, double timeOffset) + { + base.CheckForJudgements(userTriggered, timeOffset); + + if (returnJudgement) + { + if (currentScoringMetric == ScoringMetric.ScoreZones) + { + switch (VitaruPlayfield.VitaruPlayer.ScoreZone) + { + case 0: + AddJudgement(new VitaruJudgement { Result = HitResult.Miss }); + break; + case 100: + AddJudgement(new VitaruJudgement { Result = HitResult.Ok }); + break; + case 200: + AddJudgement(new VitaruJudgement { Result = HitResult.Good }); + break; + case 300: + AddJudgement(new VitaruJudgement { Result = HitResult.Great }); + break; + } + } + else if (currentScoringMetric == ScoringMetric.InverseCatch) + { + switch (VitaruPlayfield.VitaruPlayer.ScoreZone) + { + case 0: + AddJudgement(new VitaruJudgement { Result = HitResult.Miss }); + break; + case 100: + AddJudgement(new VitaruJudgement { Result = HitResult.Great }); + break; + case 200: + AddJudgement(new VitaruJudgement { Result = HitResult.Great }); + break; + case 300: + AddJudgement(new VitaruJudgement { Result = HitResult.Great }); + break; + } + } + else if (currentScoringMetric == ScoringMetric.Graze) + { + switch (ScoreZone) + { + case 0: + AddJudgement(new VitaruJudgement { Result = HitResult.Miss }); + break; + case 50: + AddJudgement(new VitaruJudgement { Result = HitResult.Meh }); + break; + case 100: + AddJudgement(new VitaruJudgement { Result = HitResult.Ok }); + break; + case 200: + AddJudgement(new VitaruJudgement { Result = HitResult.Good }); + break; + case 300: + AddJudgement(new VitaruJudgement { Result = HitResult.Great }); + break; + } + } + } + + else if (Hit && !returnedJudgement) + { + if (!Laser.DummyMode) + AddJudgement(new VitaruJudgement { Result = HitResult.Miss }); + returnedJudgement = true; + } + + else if (ReturnGreat) + { + AddJudgement(new VitaruJudgement { Result = HitResult.Great }); + unload(); + } + } + + protected override void Update() + { + base.Update(); + + if (Time.Current >= Laser.StartTime | Laser.DummyMode) + load(); + + if (LaserDeleteTime <= Time.Current && LaserDeleteTime != -1 || Time.Current < Laser.StartTime && !Laser.DummyMode) + unload(); + + if (Time.Current >= Laser.StartTime && Time.Current < Laser.EndTime) + start(); + + if (Time.Current >= Laser.EndTime) + End(); + } + } +} diff --git a/osu.Game.Rulesets.Vitaru/Objects/Drawables/DrawablePatterns.cs b/osu.Game.Rulesets.Vitaru/Objects/Drawables/DrawablePatterns.cs new file mode 100644 index 0000000000..978d6617cc --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Objects/Drawables/DrawablePatterns.cs @@ -0,0 +1,359 @@ +using OpenTK; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Vitaru.UI; +using osu.Game.Rulesets.Vitaru.Objects.Characters; +using System; +using osu.Game.Rulesets.Vitaru.Settings; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Extensions.Color4Extensions; + +namespace osu.Game.Rulesets.Vitaru.Objects.Drawables +{ + public class DrawablePattern : DrawableVitaruHitObject + { + private readonly VitaruGamemode currentGameMode = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.GameMode); + + public static int PatternCount; + private readonly Pattern pattern; + private Vector2 patternStartPosition; + private Container energyCircle; + + private bool loaded; + private bool started; + private bool done; + + private int currentRepeat; + + private Enemy enemy; + + private bool prepedToPop; + private bool popped; + + public DrawablePattern(Container parent, Pattern pattern) : base(pattern, parent) + { + AlwaysPresent = true; + + this.pattern = pattern; + + if (!pattern.IsSlider && !pattern.IsSpinner) + this.pattern.EndTime = this.pattern.StartTime + TIME_FADEOUT; + else if (pattern.IsSlider) + this.pattern.EndTime += TIME_FADEOUT; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + PatternCount++; + + if (pattern.IsSlider) + pattern.EndTime = pattern.StartTime + pattern.RepeatCount * pattern.Curve.Distance / pattern.Velocity; + + LifetimeStart = pattern.StartTime - (TIME_PREEMPT + 1000f); + } + + //Should be called when a DrawablePattern is getting ready to become visable as to save on resources before hand + private void load() + { + if (!loaded) + { + if (currentGameMode != VitaruGamemode.Dodge) + { + //load the enemy + ParentContainer.Add(enemy = new Enemy(ParentContainer, pattern, this) + { + Alpha = 0, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Depth = 5, + MaxHealth = pattern.EnemyHealth, + Team = 1 + }); + + Child = energyCircle = new Container + { + Alpha = 0, + Masking = true, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(30), + CornerRadius = 30f / 2, + BorderThickness = 10, + BorderColour = pattern.ComboColour, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both + } + }, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = pattern.ComboColour.Opacity(0.5f), + Radius = Width / 2, + } + }; + enemy.FadeInFromZero(TIME_FADEIN); + enemy.Position = getPatternStartPosition(); + enemy.MoveTo(pattern.Position, TIME_PREEMPT); + } + else + { + Child = energyCircle = new CircularContainer + { + Masking = true, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(20), + BorderThickness = 6, + BorderColour = pattern.ComboColour, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both + } + }, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = pattern.ComboColour.Opacity(0.5f), + Radius = Width / 2, + } + }; + } + + Position = getPatternStartPosition(); + this.MoveTo(pattern.Position, TIME_PREEMPT); + + if (NestedHitObjects != null) + foreach (var o in NestedHitObjects) + { + var b = (DrawableBullet)o; + ParentContainer.Remove(b); + b.Dispose(); + } + + //Load the bullets + foreach (var o in pattern.NestedHitObjects) + { + var b = (Bullet)o; + b.ComboColour = pattern.ComboColour; + DrawableBullet drawableBullet = new DrawableBullet(ParentContainer, b, this); + ParentContainer.Add(drawableBullet); + AddNested(drawableBullet); + } + + loaded = true; + } + } + + private void unload() + { + if (loaded) + { + if (currentGameMode != VitaruGamemode.Dodge) + { + ParentContainer.Remove(enemy); + enemy.Dispose(); + } + + loaded = false; + started = false; + done = false; + } + } + + private Vector2 getPatternStartPosition() + { + if (pattern.Position.X <= 384f / 2 && pattern.Position.Y <= 512f / 2) + patternStartPosition = pattern.Position - new Vector2(384f / 2, 512f / 2); + else if (pattern.Position.X > 384f / 2 && pattern.Position.Y <= 512f / 2) + patternStartPosition = new Vector2(pattern.Position.X + 384f / 2, pattern.Position.Y - 512f / 2); + else if (pattern.Position.X > 384f / 2 && pattern.Position.Y > 512f / 2) + patternStartPosition = pattern.Position + new Vector2(384f / 2, 512f / 2); + else + patternStartPosition = new Vector2(pattern.Position.X - 384f / 2, pattern.Position.Y + 512f / 2); + + return patternStartPosition; + } + + protected override void Update() + { + base.Update(); + + //Used just to keep this Update(); function clean looking + generalUpdateLogic(); + + if (!pattern.IsSlider && !pattern.IsSpinner && loaded) + hitcircleUpdate(); + + if (pattern.IsSlider && loaded) + sliderUpdate(); + + if (pattern.IsSpinner && loaded) + spinnerUpdate(); + } + + private void generalUpdateLogic() + { + if (HitObject.StartTime - TIME_PREEMPT <= Time.Current && Time.Current < pattern.EndTime + TIME_FADEOUT) + load(); + + else + unload(); + + if (currentGameMode != VitaruGamemode.Dodge && prepedToPop && HitObject.StartTime <= Time.Current) + pop(); + } + + /// + /// Will leave and hide + /// + private void end() + { + if (energyCircle.Alpha <= 0) + { + if (currentGameMode != VitaruGamemode.Dodge) + enemy.MoveTo(patternStartPosition, TIME_FADEOUT, Easing.InQuint); + this.MoveTo(patternStartPosition, TIME_FADEOUT, Easing.InQuint); + enemy.ScaleTo(new Vector2(0.5f), TIME_FADEOUT, Easing.InQuint); + enemy.FadeOut(TIME_FADEOUT, Easing.InQuint); + } + else + { + energyCircle.FadeOut(TIME_FADEOUT / 4); + energyCircle.ScaleTo(new Vector2(0.1f), TIME_FADEOUT / 4); + } + } + + public void PrepPop() + { + if (!prepedToPop && !done) + { + double time = pattern.StartTime - Time.Current; + + if (time < 0) + time = 0; + + energyCircle.FadeInFromZero(time); + energyCircle.ScaleTo(Vector2.One, time); + prepedToPop = true; + } + } + + private void pop() + { + if (!popped) + { + enemy.FadeOut(100); + enemy.ScaleTo(new Vector2(1.2f), 100); + popped = true; + } + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + if (isDisposing) + PatternCount--; + } + + private void throwBullets() + { + PlaySamples(); + foreach (var o in NestedHitObjects) + { + var b = (DrawableBullet)o; + if (b.Bullet.StartTime <= Time.Current) + { + b.Position = Position; + try + { + if (b.Bullet.ShootPlayer) + b.Bullet.BulletAngleRadian += pattern.PlayerRelativePositionAngle(VitaruPlayfield.VitaruPlayer.Position, b.Position) - (float)Math.PI / 2; + } + catch { b.Bullet.BulletAngleRadian = 0; } + } + } + } + + /// + /// All the hitcircle stuff + /// + #region Hitcircle Stuff + private void hitcircleUpdate() + { + if (HitObject.StartTime <= Time.Current && !started) + { + started = true; + done = true; + + throwBullets(); + end(); + } + } + #endregion + + /// + /// All The Slider Stuff + /// + #region Slider Stuff + private void sliderUpdate() + { + double progress = MathHelper.Clamp((Time.Current - pattern.StartTime) / pattern.Duration, 0, 1); + int repeat = pattern.RepeatAt(progress); + progress = pattern.ProgressAt(progress); + + if (HitObject.StartTime <= Time.Current && !started) + { + throwBullets(); + started = true; + } + + if (!done && started) + { + Position = pattern.Curve.PositionAt(progress); + if (currentGameMode != VitaruGamemode.Dodge) + enemy.Position = pattern.Curve.PositionAt(progress); + } + + if (repeat > currentRepeat) + { + if (repeat < pattern.RepeatCount) + throwBullets(); + currentRepeat = repeat; + } + + if (pattern.EndTime <= Time.Current && started && !done) + { + end(); + throwBullets(); + done = true; + } + } + #endregion + + /// + /// All the spinner stuff + /// + #region Spinner Stuff + private void spinnerUpdate() + { + if (pattern.StartTime <= Time.Current && !started) + { + throwBullets(); + started = true; + } + if (pattern.EndTime <= Time.Current) + { + done = true; + end(); + } + } + #endregion + } +} diff --git a/osu.Game.Rulesets.Vitaru/Objects/Drawables/DrawableSeekingBullet.cs b/osu.Game.Rulesets.Vitaru/Objects/Drawables/DrawableSeekingBullet.cs new file mode 100644 index 0000000000..25379832cf --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Objects/Drawables/DrawableSeekingBullet.cs @@ -0,0 +1,146 @@ +using OpenTK; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Vitaru.Objects.Characters; +using osu.Game.Rulesets.Vitaru.Objects.Drawables.Pieces; +using osu.Game.Rulesets.Vitaru.Settings; +using Symcol.Core.GameObjects; +using System; + +namespace osu.Game.Rulesets.Vitaru.Objects.Drawables +{ + public class DrawableSeekingBullet : DrawableVitaruHitObject + { + private readonly VitaruGamemode currentGameMode = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.GameMode); + + public VitaruCharacter NearestEnemy; + + private double startTime; + + public readonly SymcolHitbox Hitbox; + + //Result of bulletSpeed + bulletAngle math, should never be modified outside of this class + private Vector2 bulletVelocity; + + //Incase we want to be deleted in the near future + public double BulletDeleteTime = -1; + + //Should be set to true when a character is hit + public bool Hit; + + public readonly SeekingBullet SeekingBullet; + + //Playfield size + Margin of 10 on each side + public Vector4 BulletBounds = new Vector4(-10, -10, 520, 830); + + public DrawableSeekingBullet(Container parent, SeekingBullet seekingBullet) : base(seekingBullet, parent) + { + AlwaysPresent = true; + Alpha = 0; + Scale = new Vector2(0.1f); + Size = new Vector2(20); + + Anchor = Anchor.TopLeft; + Origin = Anchor.Centre; + + this.FadeInFromZero(100); + this.ScaleTo(Vector2.One, 100); + + SeekingBullet = seekingBullet; + + if (currentGameMode == VitaruGamemode.Dodge) + BulletBounds = new Vector4(-10, -10, 522, 394); + + Children = new Drawable[] + { + new SeekingBulletPiece(this), + Hitbox = new SymcolHitbox(Size, Shape.Rectangle) + }; + } + + protected override void LoadComplete() + { + startTime = Time.Current; + } + + private void nearestEnemy() + { + foreach (Drawable draw in ParentContainer.Children) + { + VitaruCharacter enemy = draw as VitaruCharacter; + if (enemy?.Hitbox != null && enemy.Hitbox.Team != SeekingBullet.Team) + { + if (enemy.Alpha > 0) + { + float minDist = float.MaxValue; + Vector2 pos = enemy.ToSpaceOfOtherDrawable(Vector2.Zero, this) + new Vector2(6); + float distance = (float)Math.Sqrt(Math.Pow(pos.X, 2) + Math.Pow(pos.Y, 2)); + if (distance < minDist) + { + NearestEnemy = enemy; + minDist = distance; + } + } + } + } + } + + public float enemyRelativePositionAngle() + { + //Returns a Radian + float enemyAngle = (float)Math.Atan2((NearestEnemy.Position.Y - Position.Y), (NearestEnemy.Position.X - Position.X)); + return enemyAngle; + } + + private Vector2 getBulletVelocity(float angle) + { + Vector2 velocity = new Vector2(SeekingBullet.BulletSpeed * (float)Math.Cos(angle), SeekingBullet.BulletSpeed * (float)Math.Sin(angle)); + return velocity; + } + + private void unload() + { + Alpha = 0; + Expire(); + } + + protected override void Update() + { + base.Update(); + + if (Hit) + unload(); + + Rotation = Rotation + 0.25f; + + if (BulletDeleteTime <= Time.Current && BulletDeleteTime != -1) + unload(); + + if (SeekingBullet.ObeyBoundries && Position.Y < BulletBounds.Y | Position.X < BulletBounds.X | Position.Y > BulletBounds.W | Position.X > BulletBounds.Z && BulletDeleteTime == -1) + { + BulletDeleteTime = Time.Current + TIME_FADEOUT / 12; + this.FadeOutFromOne(TIME_FADEOUT / 12); + } + + //IdleTimer + float frameTime = (float)Clock.ElapsedFrameTime; + bulletVelocity = getBulletVelocity(MathHelper.DegreesToRadians(SeekingBullet.StartAngle - 90)); + + if (startTime + 300 <= Time.Current) + { + nearestEnemy(); + if (NearestEnemy != null && !NearestEnemy.Dead) + { + bulletVelocity = getBulletVelocity(enemyRelativePositionAngle()); + this.MoveToOffset(new Vector2(bulletVelocity.X * DrawableBullet.BulletSpeedModifier * frameTime, bulletVelocity.Y * DrawableBullet.BulletSpeedModifier * frameTime)); + + } + else + this.MoveToOffset(new Vector2(bulletVelocity.X * DrawableBullet.BulletSpeedModifier * frameTime, bulletVelocity.Y * DrawableBullet.BulletSpeedModifier * frameTime)); + } + else + this.MoveToOffset(new Vector2(bulletVelocity.X * DrawableBullet.BulletSpeedModifier * frameTime, bulletVelocity.Y * DrawableBullet.BulletSpeedModifier * frameTime)); + + } + } +} diff --git a/osu.Game.Rulesets.Vitaru/Objects/Drawables/DrawableVitaruHitObject.cs b/osu.Game.Rulesets.Vitaru/Objects/Drawables/DrawableVitaruHitObject.cs new file mode 100644 index 0000000000..3bfd738e73 --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Objects/Drawables/DrawableVitaruHitObject.cs @@ -0,0 +1,38 @@ +using osu.Game.Rulesets.Objects.Drawables; +using System.ComponentModel; + +namespace osu.Game.Rulesets.Vitaru.Objects.Drawables +{ + public class DrawableVitaruHitObject : DrawableHitObject + { + public static float TIME_PREEMPT = 600; + public static float TIME_FADEIN = 300; + public static float TIME_FADEOUT = 1200; + + public readonly Framework.Graphics.Containers.Container ParentContainer; + + public DrawableVitaruHitObject(VitaruHitObject hitObject, Framework.Graphics.Containers.Container parent) : base(hitObject) + { + ParentContainer = parent; + + if (hitObject.Ar != -1) + { + TIME_PREEMPT = hitObject.Ar; + TIME_FADEOUT = hitObject.Ar * 2; + TIME_FADEIN = hitObject.Ar / 2; + } + } + + protected sealed override void UpdateState(ArmedState state) { } + } + + public enum ComboResult + { + [Description(@"")] + None, + [Description(@"Good")] + Good, + [Description(@"Amazing")] + Perfect + } +} diff --git a/osu.Game.Rulesets.Vitaru/Objects/Drawables/DrawableVitaruJudgement.cs b/osu.Game.Rulesets.Vitaru/Objects/Drawables/DrawableVitaruJudgement.cs new file mode 100644 index 0000000000..678e20e6d5 --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Objects/Drawables/DrawableVitaruJudgement.cs @@ -0,0 +1,23 @@ +using osu.Framework.Graphics; +using osu.Game.Rulesets.Vitaru.Judgements; +using OpenTK; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Vitaru.Objects.Drawables +{ + public class DrawableVitaruJudgement : DrawableJudgement + { + public DrawableVitaruJudgement(VitaruJudgement judgement) : base(judgement) + { + } + + protected override void LoadComplete() + { + if (Judgement.Result != HitResult.Miss) + JudgementText.TransformSpacingTo(new Vector2(14, 0), 1800, Easing.OutQuint); + + base.LoadComplete(); + } + } +} diff --git a/osu.Game.Rulesets.Vitaru/Objects/Drawables/Pieces/BulletPiece.cs b/osu.Game.Rulesets.Vitaru/Objects/Drawables/Pieces/BulletPiece.cs new file mode 100644 index 0000000000..afa34b60e6 --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Objects/Drawables/Pieces/BulletPiece.cs @@ -0,0 +1,113 @@ +using osu.Framework.Graphics; +using OpenTK; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.Containers; +using osu.Framework.Audio.Track; +using osu.Game.Beatmaps.ControlPoints; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.MathUtils; +using osu.Game.Rulesets.Vitaru.Settings; +using osu.Framework.Extensions.Color4Extensions; +using OpenTK.Graphics; + +namespace osu.Game.Rulesets.Vitaru.Objects.Drawables.Pieces +{ + public class BulletPiece : BeatSyncedContainer + { + private readonly Characters.Characters currentCharacter = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.Characters); + private readonly GraphicsPresets currentSkin = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.GraphicsPresets); + + private Sprite bulletKiai; + private CircularContainer circle; + private Box box; + + private readonly float randomRotationValue = 1; + private readonly bool randomRotateDirection; + + private readonly DrawableBullet drawableBullet; + + public BulletPiece(DrawableBullet drawableBullet) + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + this.drawableBullet = drawableBullet; + + randomRotationValue = (float)RNG.Next(10, 15) / 10; + randomRotateDirection = RNG.NextBool(); + } + + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) + { + base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); + + if (currentSkin != GraphicsPresets.HighPerformanceCompetitive && currentSkin != GraphicsPresets.HighPerformance) + { + if (effectPoint.KiaiMode && bulletKiai.Alpha == 0) + bulletKiai.FadeInFromZero(timingPoint.BeatLength / 4); + if (!effectPoint.KiaiMode && bulletKiai.Alpha == 1) + bulletKiai.FadeOutFromOne(timingPoint.BeatLength); + } + } + + protected override void Update() + { + base.Update(); + + if (currentSkin != GraphicsPresets.HighPerformanceCompetitive && currentSkin != GraphicsPresets.HighPerformance && bulletKiai.Alpha > 0) + { + if (randomRotateDirection) + bulletKiai.RotateTo((float)(Clock.CurrentTime / 1000 * 90) * randomRotationValue); + else + bulletKiai.RotateTo((float)(Clock.CurrentTime / 1000 * 90) * -1 * randomRotationValue); + } + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Size = new Vector2(drawableBullet.Bullet.BulletDiameter + 12); + + if (currentSkin != GraphicsPresets.HighPerformanceCompetitive && currentSkin != GraphicsPresets.HighPerformance) + Child = bulletKiai = new Sprite + { + //Just to look nice for the time being, will fix the sprite later + Scale = new Vector2(2), + Alpha = 0, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Colour = drawableBullet.Bullet.ComboColour, + Texture = VitaruRuleset.VitaruTextures.Get("bulletKiai"), + }; + + Add(circle = new CircularContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Alpha = 1, + RelativeSizeAxes = Axes.Both, + BorderColour = drawableBullet.Bullet.ComboColour, + BorderThickness = 6, + Masking = true, + + Child = box = new Box + { + RelativeSizeAxes = Axes.Both + } + }); + + if (currentSkin != GraphicsPresets.HighPerformanceCompetitive && currentSkin != GraphicsPresets.HighPerformance) + circle.EdgeEffect = new EdgeEffectParameters + { + Radius = drawableBullet.Bullet.BulletDiameter, + Type = EdgeEffectType.Shadow, + Colour = drawableBullet.Bullet.ComboColour.Opacity(0.2f) + }; + + if (drawableBullet.Bullet.Ghost && currentCharacter == Characters.Characters.Kaguya | currentCharacter == Characters.Characters.AliceMuyart) + box.Colour = Color4.Cyan; + } + } +} diff --git a/osu.Game.Rulesets.Vitaru/Objects/Drawables/Pieces/LaserPiece.cs b/osu.Game.Rulesets.Vitaru/Objects/Drawables/Pieces/LaserPiece.cs new file mode 100644 index 0000000000..ecb84f3162 --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Objects/Drawables/Pieces/LaserPiece.cs @@ -0,0 +1,30 @@ +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.Containers; +using osu.Game.Rulesets.Vitaru.Settings; + +namespace osu.Game.Rulesets.Vitaru.Objects.Drawables.Pieces +{ + public class LaserPiece : BeatSyncedContainer + { + private readonly GraphicsPresets currentSkin = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.GraphicsPresets); + + public LaserPiece(DrawableLaser drawableLaser) + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + Masking = true; + + CornerRadius = 16; + + BorderThickness = 8; + BorderColour = drawableLaser.Laser.ComboColour; + + Child = new Box + { + RelativeSizeAxes = Axes.Both + }; + } + } +} diff --git a/osu.Game.Rulesets.Vitaru/Objects/Drawables/Pieces/SeekingBulletPiece.cs b/osu.Game.Rulesets.Vitaru/Objects/Drawables/Pieces/SeekingBulletPiece.cs new file mode 100644 index 0000000000..83b1bd4ca3 --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Objects/Drawables/Pieces/SeekingBulletPiece.cs @@ -0,0 +1,41 @@ +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.Containers; + +namespace osu.Game.Rulesets.Vitaru.Objects.Drawables.Pieces +{ + public class SeekingBulletPiece : BeatSyncedContainer + { + public SeekingBulletPiece(DrawableSeekingBullet seekingBullet) + { + Masking = true; + RelativeSizeAxes = Axes.Both; + Origin = Anchor.Centre; + Anchor = Anchor.Centre; + BorderThickness = 4; + AlwaysPresent = true; + BorderColour = seekingBullet.SeekingBullet.ComboColour; + CornerRadius = 4; + + Child = new Box + { + RelativeSizeAxes = Axes.Both + }; + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Radius = 8, + Colour = seekingBullet.SeekingBullet.ComboColour.Opacity(0.25f), + }; + } + + protected override void Update() + { + base.Update(); + + this.RotateTo((float)(Clock.CurrentTime / 1000 * 90) * 2); + } + } +} diff --git a/osu.Game.Rulesets.Vitaru/Objects/HitObjectType.cs b/osu.Game.Rulesets.Vitaru/Objects/HitObjectType.cs new file mode 100644 index 0000000000..a20e9c9b7c --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Objects/HitObjectType.cs @@ -0,0 +1,9 @@ +namespace osu.Game.Rulesets.Vitaru.Objects +{ + public enum HitObjectType + { + Pattern, + Bullet, + Laser + } +} diff --git a/osu.Game.Rulesets.Vitaru/Objects/Laser.cs b/osu.Game.Rulesets.Vitaru/Objects/Laser.cs new file mode 100644 index 0000000000..22a50b8415 --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Objects/Laser.cs @@ -0,0 +1,20 @@ +using OpenTK; + +namespace osu.Game.Rulesets.Vitaru.Objects +{ + public class Laser : VitaruHitObject + { + public override HitObjectType Type => HitObjectType.Laser; + + /// + /// Basically just bypasses all hitobject functionality (useful for player bullets) + /// + public bool DummyMode { get; set; } + + public double EndTime { get; set; } + public float LaserDamage { get; set; } = 10; + public Vector2 LaserSize { get; set; } = new Vector2(2, 8); + public float LaserAngleRadian { get; set; } + public int Team { get; set; } = -1; + } +} diff --git a/osu.Game.Rulesets.Vitaru/Objects/Pattern.cs b/osu.Game.Rulesets.Vitaru/Objects/Pattern.cs new file mode 100644 index 0000000000..52e8ffbb17 --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Objects/Pattern.cs @@ -0,0 +1,399 @@ +using OpenTK; +using osu.Game.Audio; +using System.Collections.Generic; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Objects; +using osu.Game.Beatmaps; +using System; + +namespace osu.Game.Rulesets.Vitaru.Objects +{ + public class Pattern : VitaruHitObject + { + public override HitObjectType Type => HitObjectType.Pattern; + + /// + /// All Pattern specific stuff + /// + #region Pattern + public int PatternID { get; set; } + public float PatternSpeed { get; set; } + public float PatternDifficulty { get; set; } = 1; + private float patternAngleRadian { get; set; } = -10; + public float PatternAngleDegree { get; set; } + public float PatternBulletDiameter { get; set; } = 4; + public float PatternDamage { get; set; } = 10; + private bool dynamicPatternVelocity { get; } = false; + public int PatternTeam { get; set; } + private int totalBullets; + private bool shootPlayer; + #endregion + + /// + /// All Slider specific stuff + /// + #region Slider + public bool IsSlider { get; set; } = false; + public List> RepeatSamples { get; set; } = new List>(); + private const float base_scoring_distance = 100; + public double Duration => EndTime - StartTime; + public readonly SliderCurve Curve = new SliderCurve(); + public int RepeatCount { get; set; } = 1; + public double Velocity; + + public override Vector2 EndPosition => PositionAt(1); + public Vector2 PositionAt(double progress) => Curve.PositionAt(ProgressAt(progress)); + + public double ProgressAt(double progress) + { + double p = progress * RepeatCount % 1; + if (RepeatAt(progress) % 2 == 1) + p = 1 - p; + return p; + } + + public int RepeatAt(double progress) => (int)(progress * RepeatCount); + + public List ControlPoints + { + get { return Curve.ControlPoints; } + set { Curve.ControlPoints = value; } + } + + public CurveType CurveType + { + get { return Curve.CurveType; } + set { Curve.CurveType = value; } + } + + public double Distance + { + get { return Curve.Distance; } + set { Curve.Distance = value; } + } + + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + { + base.ApplyDefaultsToSelf(controlPointInfo, difficulty); + + TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); + DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime); + + double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; + + Velocity = scoringDistance / timingPoint.BeatLength; + } + #endregion + + /// + /// All Spinner specific stuff + /// + #region Spinner + public bool IsSpinner { get; set; } + public double EndTime { get; set; } + #endregion + + #region Bullet Loading + public int GetTotalBullets() + { + switch (PatternID) + { + case 1: + totalBullets += (int)PatternDifficulty * 2 + 1; + break; + case 2: + totalBullets += (int)PatternDifficulty + 1; + break; + case 3: + totalBullets += (int)(PatternDifficulty + 2) / 2; + break; + case 4: + totalBullets += (int)(PatternDifficulty * 2) + 3; + break; + case 5: + totalBullets += (int)(30 * (PatternDifficulty / 3) * (Duration / 1000)); + break; + } + + return totalBullets; + } + + public float EnemyHealth { get; set; } = 40; + + protected override void CreateNestedHitObjects() + { + base.CreateNestedHitObjects(); + + createBullets(); + } + + private void createBullets() + { + + var length = Curve.Distance; + var repeatPointDistance = Math.Min(Distance, length); + var repeatDuration = length / Velocity; + int repeatCount = RepeatCount; + + if (IsSlider) + { + repeatCount += 1; + bool sliderStart = false; + for (var repeat = 0; repeat < repeatCount; repeat++) + { + sliderStart = !sliderStart; + for (var d = repeatPointDistance; d <= length; d += repeatPointDistance) + { + var repeatStartTime = StartTime + repeat * repeatDuration; + var distanceProgress = d / length; + + IEnumerable bullets = createPattern(); + + foreach (Bullet b in bullets) + { + if (IsSlider) + { + b.StartTime = repeatStartTime; + + b.Position = Curve.PositionAt(!sliderStart ? distanceProgress : 0); + } + + b.NewCombo = NewCombo; + b.Ar = Ar; + b.Cs = Cs; + b.StackHeight = StackHeight; + + b.ShootPlayer = shootPlayer; + + AddNested(b); + } + } + } + } + else + { + IEnumerable bullets = createPattern(); + + foreach (Bullet b in bullets) + { + b.NewCombo = NewCombo; + b.Ar = Ar; + b.Cs = Cs; + b.StackHeight = StackHeight; + + b.ShootPlayer = shootPlayer; + + AddNested(b); + } + } + } + + private IEnumerable createPattern() + { + if (patternAngleRadian == -10) + patternAngleRadian = MathHelper.DegreesToRadians(PatternAngleDegree - 90); + + float bulletDiameter = PatternBulletDiameter; + bulletDiameter += Cs; + + GetTotalBullets(); + + switch (PatternID) + { + default: + shootPlayer = false; + return patternWave(bulletDiameter); + case 1: + shootPlayer = false; + return patternWave(bulletDiameter); + case 2: + shootPlayer = true; + return PatternLine(bulletDiameter); + case 3: + shootPlayer = true; + return PatternTriangleWave(bulletDiameter); + case 4: + shootPlayer = false; + return PatternCoolWave(bulletDiameter); + case 5: + shootPlayer = true; + //should be PatternSpin() once its fixed + return patternWave(bulletDiameter); + } + } + + /// + /// These will be the base patterns + /// + private List patternWave(float diameter) + { + List bullets = new List(); + int numberOfBullets = (int)PatternDifficulty * 2 + 1; + float directionModifier = -0.1f * ((float)(numberOfBullets - 1) / 2); + for (int i = 1; i <= numberOfBullets; i++) + { + float angle = directionModifier + patternAngleRadian; + bullets.Add(new Bullet + { + StartTime = StartTime, + Position = Position, + ComboColour = ComboColour, + BulletSpeed = PatternSpeed, + BulletAngleRadian = angle, + BulletDiameter = diameter, + BulletDamage = PatternDamage, + DynamicBulletVelocity = dynamicPatternVelocity, + Team = 1, + Ghost = i == ((numberOfBullets - 1) / 2) + 1 + }); + directionModifier += 0.1f; + } + return bullets; + } + public List PatternLine(float diameter) + { + List bullets = new List(); + int numberbullets = (int)PatternDifficulty + 1; + float speed = PatternSpeed; + for (int i = 1; i <= numberbullets; i++) + { + bullets.Add(new Bullet + { + StartTime = StartTime, + Position = Position, + ComboColour = ComboColour, + BulletSpeed = speed, + BulletAngleRadian = patternAngleRadian, + BulletDiameter = diameter, + BulletDamage = PatternDamage, + DynamicBulletVelocity = dynamicPatternVelocity, + Team = 1, + }); + speed += 0.14f; + } + return bullets; + } + public List PatternCoolWave(float diameter) + { + List bullets = new List(); + int numberbullets = (int)(PatternDifficulty * 2) + 3; + float speedModifier = 0.02f * (PatternDifficulty); + float directionModifier = -0.15f * (PatternDifficulty); + for (int i = 1; i <= numberbullets; i++) + { + PatternSpeed = PatternSpeed + Math.Abs(speedModifier); + float angle = directionModifier + patternAngleRadian; + bullets.Add(new Bullet + { + StartTime = StartTime, + Position = Position, + ComboColour = ComboColour, + BulletSpeed = PatternSpeed, + BulletAngleRadian = angle, + BulletDiameter = diameter, + BulletDamage = PatternDamage, + DynamicBulletVelocity = dynamicPatternVelocity, + Team = 1, + }); + speedModifier -= 0.01f; + directionModifier += 0.075f; + } + return bullets; + } + public List PatternTriangleWave(float diameter) + { + List bullets = new List(); + int numberwaves = (int)(PatternDifficulty + 2) / 2; + float originalDirection = 0f; + double duration = Duration / numberwaves; + for (int i = 1; i <= numberwaves; i++) + { + var numberbullets = i; + var speedModifier = 0.30f - (i - 1) * 0.03f; + for (int j = 1; j <= numberbullets; j++) + { + float directionModifier = ((j - 1) * 0.1f); + var speed = PatternSpeed + speedModifier; + float angle = patternAngleRadian + (originalDirection - directionModifier); + bullets.Add(new Bullet + { + StartTime = StartTime, + Position = Position, + ComboColour = ComboColour, + BulletSpeed = speed, + BulletAngleRadian = angle, + BulletDiameter = diameter, + BulletDamage = PatternDamage, + DynamicBulletVelocity = dynamicPatternVelocity, + Team = 1, + }); + } + originalDirection = 0.05f * i; + } + return bullets; + } + + public List PatternCurve(float diameter) + { + List bullets = new List(); + int numberbullets = (int)(PatternDifficulty + 10) / 2; + float originalDirection = 0.01f * ((float)numberbullets / 2); + float speedModifier = 0f; + float directionModifier = 0f; + for (int i = 1; i <= numberbullets; i++) + { + var speed = PatternSpeed + speedModifier; + patternAngleRadian = patternAngleRadian - originalDirection + directionModifier; + directionModifier += 0.015f; + speedModifier -= (i * 0.002f); + bullets.Add(new Bullet + { + StartTime = StartTime, + Position = Position, + ComboColour = ComboColour, + BulletSpeed = speed, + BulletAngleRadian = patternAngleRadian, + BulletDiameter = diameter, + BulletDamage = PatternDamage, + DynamicBulletVelocity = dynamicPatternVelocity, + Team = 1, + }); + } + return bullets; + } + public List PatternCircle(float diameter) + { + List bullets = new List(); + int numberbullets = (int)(PatternDifficulty + 1) * 8; + float directionModifier = (360f / numberbullets); + directionModifier = MathHelper.DegreesToRadians(directionModifier); + for (int i = 1; i <= numberbullets; i++) + { + patternAngleRadian = patternAngleRadian + (directionModifier * (i - 1)); + bullets.Add(new Bullet + { + StartTime = StartTime, + Position = Position, + ComboColour = ComboColour, + BulletSpeed = PatternSpeed, + BulletAngleRadian = patternAngleRadian, + BulletDiameter = diameter, + BulletDamage = PatternDamage, + DynamicBulletVelocity = dynamicPatternVelocity, + Team = 1, + }); + } + return bullets; + } + + //Finds what direction the player is + public float PlayerRelativePositionAngle(Vector2 playerPos, Vector2 enemyPos) + { + //Returns a Radian + var playerAngle = (float)Math.Atan2((playerPos.Y - enemyPos.Y), (playerPos.X - enemyPos.X)); + return playerAngle; + } + #endregion + } +} diff --git a/osu.Game.Rulesets.Vitaru/Objects/SeekingBullet.cs b/osu.Game.Rulesets.Vitaru/Objects/SeekingBullet.cs new file mode 100644 index 0000000000..b773b855aa --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Objects/SeekingBullet.cs @@ -0,0 +1,7 @@ +namespace osu.Game.Rulesets.Vitaru.Objects +{ + public class SeekingBullet : Bullet + { + public float StartAngle { get; set; } + } +} diff --git a/osu.Game.Rulesets.Vitaru/Objects/VitaruConverter.cs b/osu.Game.Rulesets.Vitaru/Objects/VitaruConverter.cs new file mode 100644 index 0000000000..f7d8dc7c10 --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Objects/VitaruConverter.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using osu.Game.Beatmaps; +using osu.Game.Modes.Objects; +using osu.Game.Modes.Osu.Objects; +using osu.Game.Modes.Vitaru.Objects.Characters; + +namespace osu.Game.Modes.Vitaru.Objects +{ + internal class VitaruConverter : HitObjectConverter + { + public override List Convert(Beatmap beatmap) + { + List output = new List(); + + foreach (HitObject i in beatmap.HitObjects) + { + VitaruHitObject h = i as VitaruHitObject; + + if (h == null) + { + OsuHitObject o = i as OsuHitObject; + + if (o == null) throw new HitObjectConvertException(@"Vitaru", i); + + h = new Enemy(); + } + output.Add(h); + } + return output; + } + } +} \ No newline at end of file diff --git a/osu.Game.Rulesets.Vitaru/Objects/VitaruHitObject.cs b/osu.Game.Rulesets.Vitaru/Objects/VitaruHitObject.cs new file mode 100644 index 0000000000..f27ec8544e --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Objects/VitaruHitObject.cs @@ -0,0 +1,46 @@ +using osu.Game.Rulesets.Objects; +using OpenTK; +using OpenTK.Graphics; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Beatmaps; + +namespace osu.Game.Rulesets.Vitaru.Objects +{ + public abstract class VitaruHitObject : HitObject + { + public float BPM; + + public float Ar { get; set; } = -1; + + public float Cs { get; set; } = -1; + + public Vector2 Position { get; set; } + + public Vector2 StackedPosition => Position + StackOffset; + + public virtual Vector2 EndPosition => Position; + + public Vector2 StackedEndPosition => EndPosition + StackOffset; + + public virtual int StackHeight { get; set; } + + public Vector2 StackOffset => new Vector2(0,0); + + public float Scale { get; set; } = 1; + + public abstract HitObjectType Type { get; } + + public Color4 ComboColour { get; set; } + public virtual bool NewCombo { get; set; } + public int ComboIndex { get; set; } + + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + { + base.ApplyDefaultsToSelf(controlPointInfo, difficulty); + + EffectControlPoint effectPoint = controlPointInfo.EffectPointAt(StartTime); + + Scale = (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5) / 2; + } + } +} diff --git a/osu.Game.Rulesets.Vitaru/Objects/VitaruHitObjectDifficulty.cs b/osu.Game.Rulesets.Vitaru/Objects/VitaruHitObjectDifficulty.cs new file mode 100644 index 0000000000..ea09a9cb44 --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Objects/VitaruHitObjectDifficulty.cs @@ -0,0 +1,124 @@ +using OpenTK; +using osu.Game.Rulesets.Vitaru.Beatmaps; +using System; +using System.Diagnostics; + +namespace osu.Game.Rulesets.Vitaru.Objects +{ + //Basically copied from standard atm, definitely needs work + internal class VitaruHitObjectDifficulty + { + /// + /// Factor by how much speed / aim strain decays per second. + /// + /// + /// These values are results of tweaking a lot and taking into account general feedback. + /// Opinionated observation: Speed is easier to maintain than accurate jumps. + /// + internal static readonly double[] DECAY_BASE = { 0.3, 0.15 }; + + /// + /// Pseudo threshold values to distinguish between "singles" and "streams" + /// + /// + /// Of course the border can not be defined clearly, therefore the algorithm has a smooth transition between those values. + /// They also are based on tweaking and general feedback. + /// + private const double stream_spacing_threshold = 110, + single_spacing_threshold = 125; + + /// + /// Scaling values for weightings to keep aim and speed difficulty in balance. + /// + /// + /// Found from testing a very large map pool (containing all ranked maps) and keeping the average values the same. + /// + private static readonly double[] spacing_weight_scaling = { 1400, 26.25 }; + + /// + /// Almost the normed diameter of a hitbox (104 osu pixel). That is -after- position transforming. + /// + private const double almost_diameter = 90; + + internal VitaruHitObject BaseHitObject; + internal double[] Strains = { 1, 1 }; + + internal int MaxCombo = 1; + + private float scalingFactor; + + private Vector2 startPosition = new Vector2(0); + private Vector2 endPosition; + + internal VitaruHitObjectDifficulty(VitaruHitObject baseHitObject) + { + BaseHitObject = baseHitObject; + float hitboxRadius = baseHitObject.Scale * 64; + + // We will scale everything by this factor, so we can assume a uniform HitboxSize among beatmaps. + scalingFactor = 52.0f / hitboxRadius; + if (hitboxRadius < 4) + { + float smallHitboxBonus = Math.Min(30.0f - hitboxRadius, 5.0f) / 50.0f; + scalingFactor *= 1.0f + smallHitboxBonus; + } + + else + endPosition = startPosition; + } + + internal void CalculateStrains(VitaruHitObjectDifficulty previousHitObject, double timeRate) + { + calculateSpecificStrain(previousHitObject, VitaruDifficultyCalculator.DifficultyType.Speed, timeRate); + calculateSpecificStrain(previousHitObject, VitaruDifficultyCalculator.DifficultyType.Aim, timeRate); + } + + // Caution: The subjective values are strong with this one + private static double spacingWeight(double distance, VitaruDifficultyCalculator.DifficultyType type) + { + switch (type) + { + case VitaruDifficultyCalculator.DifficultyType.Speed: + if (distance > single_spacing_threshold) + return 2.5; + else if (distance > stream_spacing_threshold) + return 1.6 + 0.9 * (distance - stream_spacing_threshold) / (single_spacing_threshold - stream_spacing_threshold); + else if (distance > almost_diameter) + return 1.2 + 0.4 * (distance - almost_diameter) / (stream_spacing_threshold - almost_diameter); + else if (distance > almost_diameter / 2) + return 0.95 + 0.25 * (distance - almost_diameter / 2) / (almost_diameter / 2); + else + return 0.95; + + case VitaruDifficultyCalculator.DifficultyType.Aim: + return Math.Pow(distance, 0.99); + } + + Debug.Assert(false, "Invalid vitaru difficulty hit object type."); + return 0; + } + + private void calculateSpecificStrain(VitaruHitObjectDifficulty previousHitObject, VitaruDifficultyCalculator.DifficultyType type, double timeRate) + { + double addition = 0; + double timeElapsed = (BaseHitObject.StartTime - previousHitObject.BaseHitObject.StartTime) / timeRate; + double decay = Math.Pow(DECAY_BASE[(int)type], timeElapsed / 1000); + + if (BaseHitObject.Type == HitObjectType.Pattern) + { + addition = spacingWeight(DistanceTo(previousHitObject), type) * spacing_weight_scaling[(int)type]; + } + + // You will never find maps that require this amongst ranked maps. + addition /= Math.Max(timeElapsed, 50); + + Strains[(int)type] = previousHitObject.Strains[(int)type] * decay + addition; + } + + internal double DistanceTo(VitaruHitObjectDifficulty other) + { + // Scale the distance by hitbox size. + return (startPosition - other.endPosition).Length * scalingFactor; + } + } +} diff --git a/osu.Game.Rulesets.Vitaru/OpenTK.dll.config b/osu.Game.Rulesets.Vitaru/OpenTK.dll.config new file mode 100644 index 0000000000..5620e3d9e2 --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/OpenTK.dll.config @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/osu.Game.Rulesets.Vitaru/Properties/AssemblyInfo.cs b/osu.Game.Rulesets.Vitaru/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..1f5febc1b5 --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Properties/AssemblyInfo.cs @@ -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("osu.Game.Modes.Vitaru")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("osu.Game.Modes.Vitaru")] +[assembly: AssemblyCopyright("Copyright © 2016 - 2017")] +[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("58f6c80c-1253-4a0e-a465-b8c85ebeadf3")] + +// 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")] diff --git a/osu.Game.Rulesets.Vitaru/Replays/VitaruAutoGenerator.cs b/osu.Game.Rulesets.Vitaru/Replays/VitaruAutoGenerator.cs new file mode 100644 index 0000000000..6b9e1219b9 --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Replays/VitaruAutoGenerator.cs @@ -0,0 +1,47 @@ +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.Vitaru.Objects; +using osu.Game.Users; +using System.Collections.Generic; +using osu.Game.Rulesets.Objects.Types; + +namespace osu.Game.Rulesets.Vitaru.Replays +{ + /* + public class VitaruAutoGenerator : AutoGenerator + { + public VitaruAutoGenerator(Beatmap beatmap) : base(beatmap) + { + Replay = new Replay + { + User = new User + { + Username = @"Autoplay", + } + }; + } + + protected Replay Replay; + protected List Frames => Replay.Frames; + + public override Replay Generate() + { + Frames.Add(new ReplayFrame(-100000, null, null, ReplayButtonState.None)); + Frames.Add(new ReplayFrame(Beatmap.HitObjects[0].StartTime - 1000, null, null, ReplayButtonState.None)); + + for (int i = 0; i < Beatmap.HitObjects.Count; i++) + { + VitaruHitObject h = Beatmap.HitObjects[i]; + + IHasEndTime endTimeData = h as IHasEndTime; + + double endTime = endTimeData?.EndTime ?? h.StartTime; + + Frames.Add(new ReplayFrame(endTime, null, null, ReplayButtonState.None)); + } + + return Replay; + } + } + */ +} diff --git a/osu.Game.Rulesets.Vitaru/Replays/VitaruReplayInputHandler.cs b/osu.Game.Rulesets.Vitaru/Replays/VitaruReplayInputHandler.cs new file mode 100644 index 0000000000..0a6742c1f0 --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Replays/VitaruReplayInputHandler.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using osu.Framework.Input; +using osu.Game.Rulesets.Replays; + +namespace osu.Game.Rulesets.Vitaru.Replays +{ + /* + public class VitaruReplayInputHandler : FramedReplayInputHandler + { + public VitaruReplayInputHandler(Replay replay) + : base(replay) + { + } + + public override List GetPendingStates() + { + var actions = new List(); + + return new List { new ReplayState { PressedActions = actions } }; + } + } + */ +} diff --git a/osu.Game.Rulesets.Vitaru/Scoring/VitaruPerformanceCalculator.cs b/osu.Game.Rulesets.Vitaru/Scoring/VitaruPerformanceCalculator.cs new file mode 100644 index 0000000000..14ca6e1e06 --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Scoring/VitaruPerformanceCalculator.cs @@ -0,0 +1,60 @@ +using System.Collections.Generic; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Vitaru.Objects; +using System; +using osu.Game.Rulesets.Vitaru.Beatmaps; +using osu.Game.Rulesets.Mods; +using System.Linq; +using osu.Game.Rulesets.Vitaru.Mods; +using osu.Game.Rulesets.Vitaru.Settings; + +namespace osu.Game.Rulesets.Vitaru.Scoring +{ + public class VitaruPerformanceCalculator : PerformanceCalculator + { + private readonly ScoringMetric currentScoringMetric = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.ScoringMetric); + + private const float pp_multiplier = 1f; + + public static float CurrentPPValue = 0; + public static float MaxPPValue = 0; + + public VitaruPerformanceCalculator(Ruleset ruleset, Beatmap beatmap, Score score) : base(ruleset, beatmap, score) + { + CurrentPPValue = 0; + MaxPPValue = 0; + } + + public override double Calculate(Dictionary categoryRatings = null) + { + Mod[] mods = Score.Mods; + double accuracy = Score.Accuracy; + int scoreMaxCombo = Score.MaxCombo; + float difficulty = 1; + + double ar = Beatmap.BeatmapInfo.BaseDifficulty.ApproachRate; + double cs = Beatmap.BeatmapInfo.BaseDifficulty.CircleSize; + + int count300 = Convert.ToInt32(Score.Statistics[HitResult.Great]); + int count200 = Convert.ToInt32(Score.Statistics[HitResult.Good]); + int count100 = Convert.ToInt32(Score.Statistics[HitResult.Meh]); + //int count10 = Convert.ToInt32(Score.Statistics[HitResult.Tick]); + int countMiss = Convert.ToInt32(Score.Statistics[HitResult.Miss]); + + if (currentScoringMetric == ScoringMetric.ScoreZones) + { + // Don't count scores made with supposedly unranked mods + if (mods.Any(m => !m.Ranked)) + difficulty = 0; + + if (mods.Any(m => m is VitaruModNoFail)) + difficulty *= 0.90f; + } + + return difficulty * Score.TotalScore * pp_multiplier; + } + + protected override BeatmapConverter CreateBeatmapConverter() => new VitaruBeatmapConverter(); + } +} diff --git a/osu.Game.Rulesets.Vitaru/Scoring/VitaruScoreProcessor.cs b/osu.Game.Rulesets.Vitaru/Scoring/VitaruScoreProcessor.cs new file mode 100644 index 0000000000..146a828450 --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Scoring/VitaruScoreProcessor.cs @@ -0,0 +1,94 @@ +using System.Collections.Generic; +using osu.Framework.Extensions; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Vitaru.Judgements; +using osu.Game.Rulesets.Vitaru.Objects; +using osu.Game.Rulesets.Vitaru.Objects.Drawables; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.Judgements; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Vitaru.UI; +using osu.Game.Rulesets.Vitaru.Settings; +using System.Linq; +using osu.Game.Rulesets.Vitaru.Objects.Characters; +using osu.Framework.Graphics; + +namespace osu.Game.Rulesets.Vitaru.Scoring +{ + internal class VitaruScoreProcessor : ScoreProcessor + { + private readonly ScoringMetric currentScoringMetric = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.ScoringMetric); + + public new static int Combo; + + public VitaruScoreProcessor(RulesetContainer rulesetContainer) + : base(rulesetContainer) + { + } + + protected override void Reset(bool storeResults) + { + base.Reset(storeResults); + + Health.Value = 1; + Combo = 0; + + TotalScore.Value = 0; + + scoreResultCounts.Clear(); + comboResultCounts.Clear(); + } + + private readonly Dictionary scoreResultCounts = new Dictionary(); + private readonly Dictionary comboResultCounts = new Dictionary(); + + protected override void SimulateAutoplay(Beatmap beatmap) + { + foreach (var obj in beatmap.HitObjects) + { + var pattern = obj as Pattern; + foreach (var unused in pattern.NestedHitObjects.OfType()) + AddJudgement(new VitaruJudgement { Result = HitResult.Perfect }); + foreach (var unused in pattern.NestedHitObjects.OfType()) + AddJudgement(new VitaruJudgement { Result = HitResult.Perfect }); + } + } + + public override void PopulateScore(Score score) + { + base.PopulateScore(score); + + score.Statistics[HitResult.Great] = scoreResultCounts.GetOrDefault(HitResult.Great); + score.Statistics[HitResult.Good] = scoreResultCounts.GetOrDefault(HitResult.Good); + score.Statistics[HitResult.Ok] = scoreResultCounts.GetOrDefault(HitResult.Ok); + score.Statistics[HitResult.Meh] = scoreResultCounts.GetOrDefault(HitResult.Meh); + score.Statistics[HitResult.Miss] = scoreResultCounts.GetOrDefault(HitResult.Miss); + } + + protected override void OnNewJudgement(Judgement judgement) + { + base.OnNewJudgement(judgement); + + var vitaruJudgement = (VitaruJudgement)judgement; + + if (judgement.Result != HitResult.None) + { + scoreResultCounts[judgement.Result] = scoreResultCounts.GetOrDefault(judgement.Result) + 1; + comboResultCounts[vitaruJudgement.Combo] = comboResultCounts.GetOrDefault(vitaruJudgement.Combo) + 1; + Combo = comboResultCounts[vitaruJudgement.Combo]; + } + + if (VitaruPlayfield.VitaruPlayer != null) + Health.Value = VitaruPlayfield.VitaruPlayer.Health / VitaruPlayfield.VitaruPlayer.MaxHealth; + + } + } + + public enum ScoringMetric + { + Graze, + ScoreZones, + InverseCatch + } +} diff --git a/osu.Game.Rulesets.Vitaru/Settings/VitaruConfigManager.cs b/osu.Game.Rulesets.Vitaru/Settings/VitaruConfigManager.cs new file mode 100644 index 0000000000..ac9cd6e0fe --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Settings/VitaruConfigManager.cs @@ -0,0 +1,126 @@ +using eden.Game.GamePieces; +using osu.Framework.Configuration; +using osu.Framework.Platform; +using osu.Game.Rulesets.Vitaru.Edit; +using osu.Game.Rulesets.Vitaru.Objects.Characters; +using osu.Game.Rulesets.Vitaru.Scoring; + +namespace osu.Game.Rulesets.Vitaru.Settings +{ + public class VitaruConfigManager : IniConfigManager + { + protected override string Filename => @"vitaru.ini"; + + public VitaruConfigManager(Storage storage) : base(storage) { } + + protected override void InitialiseDefaults() + { + Set(VitaruSetting.ScoringMetric, ScoringMetric.Graze); + Set(VitaruSetting.DebugOverlay, false); + Set(VitaruSetting.DebugUIConfiguration, DebugUiConfiguration.PerformanceMetrics); + Set(VitaruSetting.GraphicsPresets, GraphicsPresets.Standard); + Set(VitaruSetting.GameMode, VitaruGamemode.Vitaru); + Set(VitaruSetting.Characters, Characters.ReimuHakurei); + Set(VitaruSetting.EditorConfiguration, EditorConfiguration.Simple); + Set(VitaruSetting.ComboFire, true); + Set(VitaruSetting.ShittyMultiplayer, false); + Set(VitaruSetting.FriendlyPlayerCount, 0, 0, 7); + Set(VitaruSetting.FriendlyPlayerOverride, false); + Set(VitaruSetting.EnemyPlayerCount, 0, 0, 8); + Set(VitaruSetting.EnemyPlayerOverride, false); + + Set(VitaruSetting.PlayerOne, Characters.MarisaKirisame); + Set(VitaruSetting.PlayerTwo, Characters.SakuyaIzayoi); + Set(VitaruSetting.PlayerThree, Characters.FlandreScarlet); + Set(VitaruSetting.PlayerFour, Characters.RemiliaScarlet); + Set(VitaruSetting.PlayerFive, Characters.Cirno); + Set(VitaruSetting.PlayerSix, Characters.TenshiHinanai); + Set(VitaruSetting.PlayerSeven, Characters.YukariYakumo); + + Set(VitaruSetting.EnemyOne, Characters.MarisaKirisame); + Set(VitaruSetting.EnemyTwo, Characters.SakuyaIzayoi); + Set(VitaruSetting.EnemyThree, Characters.FlandreScarlet); + Set(VitaruSetting.EnemyFour, Characters.RemiliaScarlet); + Set(VitaruSetting.EnemyFive, Characters.Cirno); + Set(VitaruSetting.EnemySix, Characters.TenshiHinanai); + Set(VitaruSetting.EnemySeven, Characters.YukariYakumo); + Set(VitaruSetting.EnemyEight, Characters.Chen); + + Set(VitaruSetting.VectorVideos, true); + Set(VitaruSetting.Skin, "default"); + + //Touhosu + Set(VitaruSetting.Familiar, false); + Set(VitaruSetting.LastDance, false); + Set(VitaruSetting.Insane, true); + Set(VitaruSetting.Awoken, false); + Set(VitaruSetting.Sacred, false); + Set(VitaruSetting.Resurrected, false); + Set(VitaruSetting.Revenge, false); + + //Online Multiplayer + Set(VitaruSetting.HostIP, "Host IP Address"); + Set(VitaruSetting.LocalIP, "Your Local IP Address"); + } + + } + + public enum VitaruSetting + { + ScoringMetric, + DebugOverlay, + DebugUIConfiguration, + GraphicsPresets, + GameMode, + Characters, + EditorConfiguration, + ComboFire, + ShittyMultiplayer, + FriendlyPlayerCount, + FriendlyPlayerOverride, + EnemyPlayerCount, + EnemyPlayerOverride, + + //Becuase fuck arrays + PlayerOne, + PlayerTwo, + PlayerThree, + PlayerFour, + PlayerFive, + PlayerSix, + PlayerSeven, + + //See above comment + EnemyOne, + EnemyTwo, + EnemyThree, + EnemyFour, + EnemyFive, + EnemySix, + EnemySeven, + EnemyEight, + + VectorVideos, + Skin, + + //Touhosu + Familiar, + LastDance, + Insane, + Awoken, + Sacred, + Resurrected, + Revenge, + + HostIP, + LocalIP, + } + + public enum GraphicsPresets + { + HighPerformance, + Standard, + StandardCompetitive, + HighPerformanceCompetitive + } +} diff --git a/osu.Game.Rulesets.Vitaru/Settings/VitaruSettings.cs b/osu.Game.Rulesets.Vitaru/Settings/VitaruSettings.cs new file mode 100644 index 0000000000..daeb8a42af --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Settings/VitaruSettings.cs @@ -0,0 +1,353 @@ +using eden.Game.GamePieces; +using OpenTK; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Platform; +using osu.Game.Rulesets.Vitaru.Multi; +using osu.Game.Overlays.Settings; +using osu.Game.Rulesets.Vitaru.Objects.Characters; +using System.Collections.Generic; +using System.Linq; +using Symcol.Rulesets.Core; +using Symcol.Rulesets.Core.Wiki; +using osu.Game.Rulesets.Vitaru.Wiki; +using osu.Game.Rulesets.Vitaru.Scoring; +using osu.Game.Rulesets.Vitaru.Edit; +using osu.Game.Screens.Symcol.Screens; +using Symcol.Rulesets.Core.Multiplayer.Screens; + +namespace osu.Game.Rulesets.Vitaru.Settings +{ + public class VitaruSettings : SymcolSettingsSubsection + { + protected override string Header => "vitaru!"; + + public override WikiOverlay Wiki => vitaruWiki; + + public override RulesetLobbyItem RulesetLobbyItem => vitaruLobby; + + private readonly VitaruWikiOverlay vitaruWiki = new VitaruWikiOverlay(); + + private readonly VitaruLobbyItem vitaruLobby = new VitaruLobbyItem(); + + public static VitaruConfigManager VitaruConfigManager; + + private static VitaruAPIContainer api; + + private Bindable selectedCharacter; + + private FillFlowContainer multiplayerSettings; + private Bindable multiplayer; + private Bindable friendlyPlayerCount; + private Bindable friendlyPlayerOverride; + private FillFlowContainer friendlyPlayerSettings; + private Bindable enemyPlayerCount; + private Bindable enemyPlayerOverride; + private FillFlowContainer enemyPlayerSettings; + + private FillFlowContainer debugUiSettings; + private Bindable showDebugUi; + + private SettingsDropdown skin; + private Bindable currentSkin; + + private const int transition_duration = 400; + + [BackgroundDependencyLoader] + private void load(GameHost host, Storage storage) + { + if (api == null) + Add(api = new VitaruAPIContainer()); + + VitaruConfigManager = new VitaruConfigManager(host.Storage); + + Storage skinsStorage = storage.GetStorageForDirectory("Skins"); + + showDebugUi = VitaruConfigManager.GetBindable(VitaruSetting.DebugOverlay); + selectedCharacter = VitaruConfigManager.GetBindable(VitaruSetting.Characters); + multiplayer = VitaruConfigManager.GetBindable(VitaruSetting.ShittyMultiplayer); + friendlyPlayerCount = VitaruConfigManager.GetBindable(VitaruSetting.FriendlyPlayerCount); + friendlyPlayerOverride = VitaruConfigManager.GetBindable(VitaruSetting.FriendlyPlayerOverride); + enemyPlayerCount = VitaruConfigManager.GetBindable(VitaruSetting.EnemyPlayerCount); + enemyPlayerOverride = VitaruConfigManager.GetBindable(VitaruSetting.EnemyPlayerOverride); + + Children = new Drawable[] + { + new SettingsEnumDropdown + { + LabelText = "Vitaru's current gamemode", + Bindable = VitaruConfigManager.GetBindable(VitaruSetting.GameMode) + }, + new SettingsEnumDropdown + { + LabelText = "Selected Character", + Bindable = selectedCharacter + }, + new SettingsEnumDropdown + { + LabelText = "Graphics Presets", + Bindable = VitaruConfigManager.GetBindable(VitaruSetting.GraphicsPresets) + }, + new SettingsEnumDropdown + { + LabelText = "Current Editor Configuration", + Bindable = VitaruConfigManager.GetBindable(VitaruSetting.EditorConfiguration) + }, + new SettingsEnumDropdown + { + LabelText = "Current Scoring Metric used (Difficulty, Score and PP)", + Bindable = VitaruConfigManager.GetBindable(VitaruSetting.ScoringMetric) + }, + new SettingsCheckbox + { + LabelText = "Enable ComboFire", + Bindable = VitaruConfigManager.GetBindable(VitaruSetting.ComboFire) + }, + new SettingsCheckbox + { + LabelText = "Offline Multiplayer", + Bindable = multiplayer + }, + multiplayerSettings = new FillFlowContainer + { + Direction = FillDirection.Vertical, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + AutoSizeDuration = transition_duration, + AutoSizeEasing = Easing.OutQuint, + Masking = true, + + Children = new Drawable[] + { + new SettingsSlider + { + LabelText = "How many Friends?", + Bindable = friendlyPlayerCount, + }, + new SettingsCheckbox + { + LabelText = "Override Friendly Characters", + Bindable = friendlyPlayerOverride + }, + friendlyPlayerSettings = new FillFlowContainer + { + Direction = FillDirection.Vertical, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + AutoSizeDuration = transition_duration, + AutoSizeEasing = Easing.OutQuint, + Masking = true, + + Children = new Drawable[] + { + new SettingsEnumDropdown + { + LabelText = "PlayerOne override", + Bindable = VitaruConfigManager.GetBindable(VitaruSetting.PlayerOne) + }, + new SettingsEnumDropdown + { + LabelText = "PlayerTwo override", + Bindable = VitaruConfigManager.GetBindable(VitaruSetting.PlayerTwo) + }, + new SettingsEnumDropdown + { + LabelText = "PlayerThree override", + Bindable = VitaruConfigManager.GetBindable(VitaruSetting.PlayerThree) + }, + new SettingsEnumDropdown + { + LabelText = "PlayerFour override", + Bindable = VitaruConfigManager.GetBindable(VitaruSetting.PlayerFour) + }, + new SettingsEnumDropdown + { + LabelText = "PlayerFive override", + Bindable = VitaruConfigManager.GetBindable(VitaruSetting.PlayerFive) + }, + new SettingsEnumDropdown + { + LabelText = "PlayerSix override", + Bindable = VitaruConfigManager.GetBindable(VitaruSetting.PlayerSix) + }, + new SettingsEnumDropdown + { + LabelText = "PlayerSeven override", + Bindable = VitaruConfigManager.GetBindable(VitaruSetting.PlayerSeven) + } + } + }, + new SettingsSlider + { + LabelText = "How many Enemies?", + Bindable = enemyPlayerCount, + }, + new SettingsCheckbox + { + LabelText = "Override Enemy Characters", + Bindable = enemyPlayerOverride + }, + enemyPlayerSettings = new FillFlowContainer + { + Direction = FillDirection.Vertical, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + AutoSizeDuration = transition_duration, + AutoSizeEasing = Easing.OutQuint, + Masking = true, + + Children = new Drawable[] + { + new SettingsEnumDropdown + { + LabelText = "EnemyOne override", + Bindable = VitaruConfigManager.GetBindable(VitaruSetting.EnemyOne) + }, + new SettingsEnumDropdown + { + LabelText = "EnemyTwo override", + Bindable = VitaruConfigManager.GetBindable(VitaruSetting.EnemyTwo) + }, + new SettingsEnumDropdown + { + LabelText = "EnemyThree override", + Bindable = VitaruConfigManager.GetBindable(VitaruSetting.EnemyThree) + }, + new SettingsEnumDropdown + { + LabelText = "EnemyFour override", + Bindable = VitaruConfigManager.GetBindable(VitaruSetting.EnemyFour) + }, + new SettingsEnumDropdown + { + LabelText = "EnemyFive override", + Bindable = VitaruConfigManager.GetBindable(VitaruSetting.EnemyFive) + }, + new SettingsEnumDropdown + { + LabelText = "EnemySix override", + Bindable = VitaruConfigManager.GetBindable(VitaruSetting.EnemySix) + }, + new SettingsEnumDropdown + { + LabelText = "EnemyEight override", + Bindable = VitaruConfigManager.GetBindable(VitaruSetting.EnemySeven) + }, + new SettingsEnumDropdown + { + LabelText = "EnemyEight override", + Bindable = VitaruConfigManager.GetBindable(VitaruSetting.EnemyEight) + } + } + }, + } + }, + new SettingsButton + { + Text = "Open In-game Wiki", + Action = vitaruWiki.Show + }, + new SettingsCheckbox + { + LabelText = "Show Debug UI In-Game", + Bindable = VitaruConfigManager.GetBindable(VitaruSetting.DebugOverlay) + }, + debugUiSettings = new FillFlowContainer + { + Direction = FillDirection.Vertical, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + AutoSizeDuration = transition_duration, + AutoSizeEasing = Easing.OutQuint, + Masking = true, + + Child = new SettingsEnumDropdown + { + LabelText = "What will be displayed on the DebugUI In-Game", + Bindable = VitaruConfigManager.GetBindable(VitaruSetting.DebugUIConfiguration) + } + }, + skin = new SettingsDropdown + { + LabelText = "Current Skin" + }, + new SettingsButton + { + Text = "Open skins folder", + Action = skinsStorage.OpenInNativeExplorer, + } + }; + + List> items = new List> { new KeyValuePair("Default", "default") }; + + try + { + foreach (string skinName in storage.GetDirectories("Skins")) + { + string[] args = skinName.Split('\\'); + items.Add(new KeyValuePair(args.Last(), args.Last())); + } + + skin.Items = items.Distinct().ToList(); + currentSkin = VitaruConfigManager.GetBindable(VitaruSetting.Skin); + skin.Bindable = currentSkin; + + currentSkin.ValueChanged += skin => { VitaruConfigManager.Set(VitaruSetting.Skin, skin); }; + } + catch { } + + //basically just an ingame wiki for the characters + selectedCharacter.ValueChanged += character => + { + if (character == Characters.AliceMuyart | character == Characters.ArysaMuyart && !VitaruAPIContainer.Shawdooow) + { + selectedCharacter.Value = Characters.ReimuHakurei; + character = Characters.ReimuHakurei; + } + }; + selectedCharacter.TriggerChange(); + + multiplayer.ValueChanged += isVisible => + { + multiplayerSettings.ClearTransforms(); + multiplayerSettings.AutoSizeAxes = isVisible ? Axes.Y : Axes.None; + + if (!isVisible) + multiplayerSettings.ResizeHeightTo(0, transition_duration, Easing.OutQuint); + }; + multiplayer.TriggerChange(); + + friendlyPlayerOverride.ValueChanged += isVisible => + { + friendlyPlayerSettings.ClearTransforms(); + friendlyPlayerSettings.AutoSizeAxes = isVisible ? Axes.Y : Axes.None; + + if (!isVisible) + friendlyPlayerSettings.ResizeHeightTo(0, transition_duration, Easing.OutQuint); + }; + friendlyPlayerOverride.TriggerChange(); + + enemyPlayerOverride.ValueChanged += isVisible => + { + enemyPlayerSettings.ClearTransforms(); + enemyPlayerSettings.AutoSizeAxes = isVisible ? Axes.Y : Axes.None; + + if (!isVisible) + enemyPlayerSettings.ResizeHeightTo(0, transition_duration, Easing.OutQuint); + }; + enemyPlayerOverride.TriggerChange(); + + showDebugUi.ValueChanged += isVisible => + { + debugUiSettings.ClearTransforms(); + debugUiSettings.AutoSizeAxes = isVisible ? Axes.Y : Axes.None; + + if (!isVisible) + debugUiSettings.ResizeHeightTo(0, transition_duration, Easing.OutQuint); + }; + showDebugUi.TriggerChange(); + } + } +} diff --git a/osu.Game.Rulesets.Vitaru/Tests/TestCaseVitaruPlayer.cs b/osu.Game.Rulesets.Vitaru/Tests/TestCaseVitaruPlayer.cs new file mode 100644 index 0000000000..b36928e6fb --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Tests/TestCaseVitaruPlayer.cs @@ -0,0 +1,25 @@ +using osu.Game.Tests.Visual; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Vitaru.Objects.Characters; + +namespace osu.Game.Rulesets.Vitaru.Tests +{ + [TestFixture] + internal class TestCaseVitaruPlayer : OsuTestCase + { + [BackgroundDependencyLoader] + private void load(RulesetStore rulesets) + { + Children = new Drawable[] + { + new VitaruPlayer(this, Characters.ReimuHakurei) + { + Alpha = 1, + AlwaysPresent = true, + } + }; + } + } +} diff --git a/osu.Game.Rulesets.Vitaru/UI/ComboFire.cs b/osu.Game.Rulesets.Vitaru/UI/ComboFire.cs new file mode 100644 index 0000000000..e61923d1eb --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/UI/ComboFire.cs @@ -0,0 +1,83 @@ +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.MathUtils; +using osu.Game.Rulesets.Vitaru.Scoring; + +namespace osu.Game.Rulesets.Vitaru.UI +{ + public class ComboFire : Container + { + public ComboFire() + { + Anchor = Anchor.BottomLeft; + Origin = Anchor.BottomLeft; + Height = 500; + + RelativeSizeAxes = Axes.X; + Masking = false; + } + + protected override void Update() + { + base.Update(); + + float spawn = 0; + + if (0 <= (float)Clock.ElapsedFrameTime / 1000 * VitaruScoreProcessor.Combo) + spawn = (float)RNG.NextDouble(0, (float)Clock.ElapsedFrameTime / 1000 * VitaruScoreProcessor.Combo); + + if (spawn > 1f) + addFireParticle(); + } + + private void addFireParticle() + { + Add(new FireParticle(RelativeToAbsoluteFactor.X) + { + Size = new Vector2((float)RNG.NextDouble(40, 120)), + Anchor = Anchor.BottomLeft, + Origin = Anchor.Centre + }); + } + } + + public class FireParticle : Triangle + { + private readonly float randomMovementYValue; + private readonly float randomMovementXValue; + private readonly float width; + + public FireParticle(float width) + { + this.width = width; + + Position = new Vector2((float)RNG.NextDouble(0, width), 60); + Colour = Interpolation.ValueAt(RNG.NextSingle(), Color4.Red, Color4.Yellow, 0, 1); + + randomMovementYValue = -1 * ((float)RNG.NextDouble(10, 40) * 2); + randomMovementXValue = (float)RNG.NextDouble(-10, 10); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + float randomScaleValue = (float)RNG.NextDouble(50, 100) / 500; + Scale = new Vector2(randomScaleValue); + this.ScaleTo(new Vector2(0.001f), 3000); + } + + protected override void Update() + { + base.Update(); + + this.MoveToOffset(new Vector2(randomMovementXValue * ((float)Clock.ElapsedFrameTime / 1000), randomMovementYValue * ((float)Clock.ElapsedFrameTime / 1000))); + + if (Position.X > width + 60 || Scale.X <= 0.005f || Position.X < -60) + Expire(); + } + } +} diff --git a/osu.Game.Rulesets.Vitaru/UI/Cursor/GameplayCursor.cs b/osu.Game.Rulesets.Vitaru/UI/Cursor/GameplayCursor.cs new file mode 100644 index 0000000000..9ff4fb795f --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/UI/Cursor/GameplayCursor.cs @@ -0,0 +1,303 @@ +using osu.Framework.Allocation; +using osu.Framework.Configuration; +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.Input.Bindings; +using osu.Game.Beatmaps; +using osu.Game.Configuration; +using OpenTK; +using OpenTK.Graphics; +using osu.Game.Rulesets.Vitaru.Settings; +using osu.Game.Rulesets.Vitaru.Objects.Characters; +using osu.Game.Graphics; + +namespace osu.Game.Rulesets.Vitaru.UI.Cursor +{ + public class GameplayCursor : CursorContainer, IKeyBindingHandler + { + protected override Drawable CreateCursor() => new VitaruCursor(); + + public GameplayCursor() + { + Masking = false; + } + + public class VitaruCursor : Container + { + private readonly Characters currentCharacter = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.Characters); + + private Container lineContainer; + private Container circleContainer; + public static CircularContainer CenterCircle; + + private SpriteText health; + private SpriteText energy; + private SpriteText speed; + private SpriteText combo; + + private Bindable cursorScale; + private Bindable autoCursorScale; + private Bindable beatmap; + + public VitaruCursor() + { + Origin = Anchor.Centre; + Size = new Vector2(32); + Masking = false; + } + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config, OsuGameBase game, OsuColour osu) + { + Children = new Drawable[] + { + new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(Size.X + Size.X / 3.5f), + Texture = VitaruRuleset.VitaruTextures.Get("ring") + }, + CenterCircle = new CircularContainer + { + Masking = true, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(Size.X / 5), + Child = new Box + { + RelativeSizeAxes = Axes.Both + } + }, + lineContainer = new Container + { + Masking = false, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Rotation = 45, + + Children = new Drawable[] + { + new Container + { + Masking = true, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + CornerRadius = Size.X / 12, + Size = new Vector2(Size.X / 3, Size.X / 7), + Position = new Vector2(Size.X / 3, 0), + Child = new Box + { + RelativeSizeAxes = Axes.Both + } + }, + new Container + { + Masking = true, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + CornerRadius = Size.X / 12, + Size = new Vector2(Size.X / 3, Size.X / 7), + Position = new Vector2(-1 * Size.X / 3, 0), + Rotation = 180, + Child = new Box + { + RelativeSizeAxes = Axes.Both + } + }, + new Container + { + Masking = true, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + CornerRadius = Size.X / 12, + Size = new Vector2(Size.X / 3, Size.X / 7), + Position = new Vector2(0, Size.X / 3), + Rotation = 90, + Child = new Box + { + RelativeSizeAxes = Axes.Both + } + }, + new Container + { + Masking = true, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + CornerRadius = Size.X / 12, + Size = new Vector2(Size.X / 3, Size.X / 7), + Position = new Vector2(0, -1 * Size.X / 3), + Rotation = 270, + Child = new Box + { + RelativeSizeAxes = Axes.Both + } + } + } + }, + circleContainer = new Container + { + Masking = false, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + + Children = new Drawable[] + { + new CircularContainer + { + Masking = true, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(Size.X / 8), + Position = new Vector2(Size.X / 4, 0), + Child = new Box + { + RelativeSizeAxes = Axes.Both + } + }, + new CircularContainer + { + Masking = true, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(Size.X / 8), + Position = new Vector2(-1 * Size.X / 4, 0), + Child = new Box + { + RelativeSizeAxes = Axes.Both + } + }, + new CircularContainer + { + Masking = true, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(Size.X / 8), + Position = new Vector2(0, Size.X / 4), + Child = new Box + { + RelativeSizeAxes = Axes.Both + } + }, + new CircularContainer + { + Masking = true, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(Size.X / 8), + Position = new Vector2(0, -1 * Size.X / 4), + Child = new Box + { + RelativeSizeAxes = Axes.Both + } + } + } + }, + health = new SpriteText + { + Alpha = 0.5f, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Position = new Vector2(-40, 0), + Colour = Color4.Green, + Text = "null" + }, + energy = new SpriteText + { + Alpha = 0.5f, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Position = new Vector2(40, 0), + Colour = Color4.Blue, + Text = "null" + }, + speed = new SpriteText + { + Alpha = 0f, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Position = new Vector2(0, -40), + Colour = Color4.Yellow, + Text = "null" + }, + combo = new SpriteText + { + Alpha = 0f, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Position = new Vector2(0, 40), + Colour = osu.Pink, + Text = "null" + } + }; + + if (currentCharacter == Characters.SakuyaIzayoi | currentCharacter == Characters.AliceMuyart) + speed.Alpha = 0.5f; + + if (currentCharacter == Characters.KokoroHatano) + combo.Alpha = 0.5f; + + beatmap = game.Beatmap.GetBoundCopy(); + beatmap.ValueChanged += v => calculateScale(); + + cursorScale = config.GetBindable(OsuSetting.GameplayCursorSize); + cursorScale.ValueChanged += v => calculateScale(); + + autoCursorScale = config.GetBindable(OsuSetting.AutoCursorSize); + autoCursorScale.ValueChanged += v => calculateScale(); + + calculateScale(); + } + + private void calculateScale() + { + float scale = (float)cursorScale.Value; + + if (autoCursorScale && beatmap.Value != null) + { + // if we have a beatmap available, let's get its circle size to figure out an automatic cursor scale modifier. + scale *= (float)(1 - 0.7 * (1 + beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize - BeatmapDifficulty.DEFAULT_DIFFICULTY) / BeatmapDifficulty.DEFAULT_DIFFICULTY); + } + + Scale = new Vector2(scale); + } + + protected override void Update() + { + base.Update(); + + if (VitaruPlayfield.VitaruPlayer != null) + { + if (VitaruPlayfield.VitaruPlayer.Health > VitaruPlayfield.VitaruPlayer.MaxHealth) + health.Colour = Color4.Blue; + else if (VitaruPlayfield.VitaruPlayer.Health > VitaruPlayfield.VitaruPlayer.MaxHealth / 2) + health.Colour = Color4.Green; + else if (VitaruPlayfield.VitaruPlayer.Health > VitaruPlayfield.VitaruPlayer.MaxHealth / 4) + health.Colour = Color4.Yellow; + else + health.Colour = Color4.Red; + + health.Text = ((int)VitaruPlayfield.VitaruPlayer.Health).ToString(); + energy.Text = ((int)VitaruPlayfield.VitaruPlayer.Energy).ToString(); + speed.Text = VitaruPlayfield.VitaruPlayer.SetRate.ToString(); + combo.Text = VitaruPlayfield.VitaruPlayer.Combo.ToString(); + } + } + } + + public bool OnPressed(VitaruAction action) + { + return false; + } + + public bool OnReleased(VitaruAction action) + { + return false; + } + } +} diff --git a/osu.Game.Rulesets.Vitaru/UI/DebugValueUI.cs b/osu.Game.Rulesets.Vitaru/UI/DebugValueUI.cs new file mode 100644 index 0000000000..75936ad6a5 --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/UI/DebugValueUI.cs @@ -0,0 +1,176 @@ +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Game.Rulesets.Vitaru.Objects.Characters; +using osu.Game.Rulesets.Vitaru.Objects.Drawables; +using osu.Game.Rulesets.Vitaru.Scoring; +using osu.Game.Rulesets.Vitaru.Settings; +using osu.Game.Rulesets.Vitaru.UI; +using Symcol.Core.Networking; + +namespace eden.Game.GamePieces +{ + /// + /// Assign the values here whatever you need for debugging + /// + public class DebugValueUI : Container + { + private DebugUiConfiguration currentConfiguration = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.DebugUIConfiguration); + + //Debug section + private Container debugContainer; + + private SpriteText value1; + private SpriteText value2; + private SpriteText value3; + private SpriteText value4; + private SpriteText value5; + private SpriteText value6; + + protected override void LoadComplete() + { + base.LoadComplete(); + + Size = new Vector2(512, 384); + + Children = new Drawable[] + { + debugContainer = new Container + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Size = new Vector2(300 , 260), + Masking = true, + Depth = 0, + Alpha = 1, + BorderColour = Color4.White, + BorderThickness = 10, + CornerRadius = 20, + Children = new Drawable[] + { + new Box + { + Depth = 0, + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientVertical(Color4.DarkBlue , Color4.Blue), + }, + value1 = new SpriteText + { + Depth = -10, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Position = new Vector2(10 , -100), + TextSize = 25, + Colour = Color4.Green, + Text = "Value 1 Here", + }, + value2 = new SpriteText + { + Depth = -10, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Position = new Vector2(10 , -60), + TextSize = 25, + Colour = Color4.Green, + Text = "Value 2 Here", + }, + value3 = new SpriteText + { + Depth = -10, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Position = new Vector2(10 , -20), + TextSize = 25, + Colour = Color4.Green, + Text = "Value 3 Here", + }, + value4 = new SpriteText + { + Depth = -10, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Position = new Vector2(10 , 20), + TextSize = 25, + Colour = Color4.Green, + Text = "Value 4 Here", + }, + value5 = new SpriteText + { + Depth = -10, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Position = new Vector2(10 , 60), + TextSize = 25, + Colour = Color4.Green, + Text = "Value 5 Here", + }, + value6 = new SpriteText + { + Depth = -10, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Position = new Vector2(10 , 100), + TextSize = 25, + Colour = Color4.Green, + Text = "Value 6 Here", + } + } + } + }; + + DrawableBullet.BulletCount = 0; + DrawablePattern.PatternCount = 0; + Enemy.EnemyCount = 0; + } + + protected override void Update() + { + base.Update(); + + if (currentConfiguration == DebugUiConfiguration.PerformanceMetrics) + { + value1.Text = "Bullets = " + DrawableBullet.BulletCount.ToString(); + value2.Text = "Patterns = " + DrawablePattern.PatternCount.ToString(); + value3.Text = "Enemies = " + Enemy.EnemyCount.ToString(); + value4.Text = "Energy = " + VitaruPlayer.Energystored.ToString(); + value5.Text = "Fps = " + Clock.FramesPerSecond; + } + else if (currentConfiguration == DebugUiConfiguration.PP) + { + value1.Text = "PlayerScoreZone = " + VitaruPlayfield.VitaruPlayer.ScoreZone.ToString(); + + + value4.Text = "CurrentPP = " + VitaruPerformanceCalculator.CurrentPPValue; + value5.Text = "MaxPP = " + VitaruPerformanceCalculator.MaxPPValue; + } + else if (currentConfiguration == DebugUiConfiguration.LaserStuff) + { + value1.Text = "PlayerHealth = " + VitaruPlayfield.VitaruPlayer.Health.ToString(); + } + else if (currentConfiguration == DebugUiConfiguration.Network) + { + value1.Text = "PacketsSent: " + NetworkingClient.SENTPACKETCOUNT; + } + + //Value4.Text = "MouseGridPos = (" + EditorGrid.mousePlace.X.ToString() + ", " + EditorGrid.mousePlace.Y.ToString() + ")"; + + //if you need these for other things, just comment them out, don't delete + //Value5.Text = "RealMousePos = (" + EdenGameScreen.EdenCursor.X.ToString() + ", " + EdenGameScreen.EdenCursor.Y.ToString() + ")"; + + float frameTime = 1000 / (float)Clock.FramesPerSecond; + value6.Text = "Frametime = " + frameTime.ToString(); + } + } + + public enum DebugUiConfiguration + { + LaserStuff, + Network, + PerformanceMetrics, + PP + } +} diff --git a/osu.Game.Rulesets.Vitaru/UI/MirrorField.cs b/osu.Game.Rulesets.Vitaru/UI/MirrorField.cs new file mode 100644 index 0000000000..736e7fbddf --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/UI/MirrorField.cs @@ -0,0 +1,158 @@ +using OpenTK; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.Vitaru.Beatmaps; +using osu.Game.Rulesets.Vitaru.Objects; +using osu.Game.Rulesets.Vitaru.Objects.Characters; +using osu.Game.Rulesets.Vitaru.Objects.Drawables; +using osu.Game.Rulesets.Vitaru.Settings; +using System.Collections.Generic; +using osu.Game.Audio; + +namespace osu.Game.Rulesets.Vitaru.UI +{ + public class MirrorField : Playfield + { + private readonly VitaruGamemode currentGameMode = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.GameMode); + private Characters currentCharacter = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.Characters); + private readonly bool multiplayer = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.ShittyMultiplayer); + private bool enemyPlayerOverride = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.EnemyPlayerOverride); + private readonly Bindable enemyPlayerCount = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.EnemyPlayerCount); + + private readonly Characters enemyOne = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.EnemyOne); + private readonly Characters enemyTwo = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.EnemyTwo); + private readonly Characters enemyThree = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.EnemyThree); + private readonly Characters enemyFour = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.EnemyFour); + private readonly Characters enemyFive = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.EnemyFive); + private readonly Characters enemySix = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.EnemySix); + private readonly Characters enemySeven = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.EnemySeven); + private readonly Characters enemyEight = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.EnemyEight); + + private readonly Container gamePlayfield; + private readonly List enemyList = new List(); + + //public override bool ProvidingUserCursor => false; + + // ReSharper disable once InconsistentNaming + public static Vector2 BASE_SIZE = new Vector2(512, 820); + + //TODO: Delete this + public override Vector2 Size + { + get + { + var parentSize = Parent.DrawSize; + var aspectSize = parentSize.X * 0.75f < parentSize.Y ? new Vector2(parentSize.X, parentSize.X * 0.75f) : new Vector2(parentSize.Y * 5f / 8f, parentSize.Y); + + return new Vector2(aspectSize.X / parentSize.X, aspectSize.Y / parentSize.Y) * base.Size; + } + } + + public MirrorField() : base(BASE_SIZE.X) + { + Position = new Vector2(-40, 400); + Anchor = Anchor.CentreLeft; + Origin = Anchor.CentreLeft; + Rotation = 180; + + AddRange(new Drawable[] + { + gamePlayfield = new Container + { + RelativeSizeAxes = Axes.Both + }, + new Container + { + RelativeSizeAxes = Axes.Both + } + }); + + //Multiplayer testing :P + if (multiplayer && currentGameMode != VitaruGamemode.Dodge) + { + switch (enemyPlayerCount) + { + case 0: + break; + case 1: + enemyList.Add(new VitaruPlayer(gamePlayfield, enemyOne) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(512f / 2, 100), Auto = true, Bot = true, Team = 2 }); + break; + case 2: + enemyList.Add(new VitaruPlayer(gamePlayfield, enemyOne) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(512 - 200, 100), Auto = true, Bot = true, Team = 2 }); + enemyList.Add(new VitaruPlayer(gamePlayfield, enemyTwo) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(200, 100), Auto = true, Bot = true, Team = 2 }); + break; + case 3: + enemyList.Add(new VitaruPlayer(gamePlayfield, enemyOne) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(0, 100), Auto = true, Bot = true, Team = 2 }); + enemyList.Add(new VitaruPlayer(gamePlayfield, enemyTwo) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(200, 100), Auto = true, Bot = true, Team = 2 }); + enemyList.Add(new VitaruPlayer(gamePlayfield, enemyThree) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(512f / 2, 100), Auto = true, Bot = true, Team = 2 }); + break; + case 4: + enemyList.Add(new VitaruPlayer(gamePlayfield, enemyOne) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(0, 100), Auto = true, Bot = true, Team = 2 }); + enemyList.Add(new VitaruPlayer(gamePlayfield, enemyTwo) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(200, 100), Auto = true, Bot = true, Team = 2 }); + enemyList.Add(new VitaruPlayer(gamePlayfield, enemyThree) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(512 - 200, 100), Auto = true, Bot = true, Team = 2 }); + enemyList.Add(new VitaruPlayer(gamePlayfield, enemyFour) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(512, 100), Auto = true, Bot = true, Team = 2 }); + break; + case 5: + enemyList.Add(new VitaruPlayer(gamePlayfield, enemyOne) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(0, 100), Auto = true, Bot = true, Team = 2 }); + enemyList.Add(new VitaruPlayer(gamePlayfield, enemyTwo) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(200, 100), Auto = true, Bot = true, Team = 2 }); + enemyList.Add(new VitaruPlayer(gamePlayfield, enemyThree) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(512f / 2, 100), Auto = true, Bot = true, Team = 2 }); + enemyList.Add(new VitaruPlayer(gamePlayfield, enemyFour) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(512 - 200, 100), Auto = true, Bot = true, Team = 2 }); + enemyList.Add(new VitaruPlayer(gamePlayfield, enemyFive) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(512, 100), Auto = true, Bot = true, Team = 2 }); + break; + case 6: + enemyList.Add(new VitaruPlayer(gamePlayfield, enemyOne) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(0, 100), Auto = true, Bot = true, Team = 2 }); + enemyList.Add(new VitaruPlayer(gamePlayfield, enemyTwo) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(150, 100), Auto = true, Bot = true, Team = 2 }); + enemyList.Add(new VitaruPlayer(gamePlayfield, enemyThree) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(250, 100), Auto = true, Bot = true, Team = 2 }); + enemyList.Add(new VitaruPlayer(gamePlayfield, enemyFour) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(512 - 250, 100), Auto = true, Bot = true, Team = 2 }); + enemyList.Add(new VitaruPlayer(gamePlayfield, enemyFive) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(512 - 150, 100), Auto = true, Bot = true, Team = 2 }); + enemyList.Add(new VitaruPlayer(gamePlayfield, enemySix) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(512, 100), Auto = true, Bot = true, Team = 2 }); + break; + case 7: + enemyList.Add(new VitaruPlayer(gamePlayfield, enemyOne) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(0, 100), Auto = true, Bot = true, Team = 2 }); + enemyList.Add(new VitaruPlayer(gamePlayfield, enemyTwo) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(125, 100), Auto = true, Bot = true, Team = 2 }); + enemyList.Add(new VitaruPlayer(gamePlayfield, enemyThree) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(200, 100), Auto = true, Bot = true, Team = 2 }); + enemyList.Add(new VitaruPlayer(gamePlayfield, enemyFour) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(512f / 2, 100), Auto = true, Bot = true, Team = 2 }); + enemyList.Add(new VitaruPlayer(gamePlayfield, enemyFive) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(512 - 200, 100), Auto = true, Bot = true, Team = 2 }); + enemyList.Add(new VitaruPlayer(gamePlayfield, enemySix) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(512 - 125, 100), Auto = true, Bot = true, Team = 2 }); + enemyList.Add(new VitaruPlayer(gamePlayfield, enemySeven) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(512, 100), Auto = true, Bot = true, Team = 2 }); + break; + case 8: + enemyList.Add(new VitaruPlayer(gamePlayfield, enemyOne) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(0, 100), Auto = true, Bot = true, Team = 2 }); + enemyList.Add(new VitaruPlayer(gamePlayfield, enemyTwo) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(125, 100), Auto = true, Bot = true, Team = 2 }); + enemyList.Add(new VitaruPlayer(gamePlayfield, enemyThree) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(200, 100), Auto = true, Bot = true, Team = 2 }); + enemyList.Add(new VitaruPlayer(gamePlayfield, enemyFour) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(250, 100), Auto = true, Bot = true, Team = 2 }); + enemyList.Add(new VitaruPlayer(gamePlayfield, enemyFive) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(512 - 250, 100), Auto = true, Bot = true, Team = 2 }); + enemyList.Add(new VitaruPlayer(gamePlayfield, enemySix) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(512 - 200, 100), Auto = true, Bot = true, Team = 2 }); + enemyList.Add(new VitaruPlayer(gamePlayfield, enemySeven) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(512 - 125, 100), Auto = true, Bot = true, Team = 2 }); + enemyList.Add(new VitaruPlayer(gamePlayfield, enemyEight) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(512, 100), Auto = true, Bot = true, Team = 2 }); + break; + } + + foreach (VitaruPlayer enemy in enemyList) + gamePlayfield.Add(enemy); + } + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + foreach (var o in VitaruBeatmapConverter.HitObjectList) + { + var p = (Pattern)o; + p.Samples = new List(); + Add(new DrawablePattern(gamePlayfield, p)); + } + VitaruBeatmapConverter.HitObjectList = new List(); + } + + public override void Add(DrawableHitObject h) + { + h.Depth = (float)h.HitObject.StartTime; + base.Add(h); + } + } +} diff --git a/osu.Game.Rulesets.Vitaru/UI/VitaruPlayfield.cs b/osu.Game.Rulesets.Vitaru/UI/VitaruPlayfield.cs new file mode 100644 index 0000000000..d1ce53db0c --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/UI/VitaruPlayfield.cs @@ -0,0 +1,224 @@ +using osu.Framework.Graphics; +using osu.Game.Rulesets.Vitaru.Objects.Drawables; +using OpenTK; +using osu.Game.Rulesets.Vitaru.Judgements; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Vitaru.Settings; +using osu.Game.Rulesets.Vitaru.Objects.Characters; +using osu.Framework.Graphics.Cursor; +using osu.Game.Rulesets.Vitaru.UI.Cursor; +using osu.Framework.Configuration; +using System.Collections.Generic; +using Symcol.Rulesets.Core; +using osu.Game.Rulesets.Vitaru.Multi; +using osu.Framework.Logging; + +namespace osu.Game.Rulesets.Vitaru.UI +{ + public class VitaruPlayfield : SymcolPlayfield + { + private readonly VitaruGamemode currentGameMode = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.GameMode); + private readonly Characters currentCharacter = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.Characters); + private readonly bool multiplayer = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.ShittyMultiplayer); + private bool friendlyPlayerOverride = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.FriendlyPlayerOverride); + private readonly Bindable friendlyPlayerCount = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.FriendlyPlayerCount); + private readonly Bindable enemyPlayerCount = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.EnemyPlayerCount); + + private readonly Characters playerOne = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.PlayerOne); + private readonly Characters playerTwo = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.PlayerTwo); + private readonly Characters playerThree = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.PlayerThree); + private readonly Characters playerFour = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.PlayerFour); + private readonly Characters playerFive = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.PlayerFive); + private readonly Characters playerSix = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.PlayerSix); + private readonly Characters playerSeven = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.PlayerSeven); + + public static Container GamePlayfield; + private readonly MirrorField mirrorPlayfield; + private readonly Container judgementLayer; + private readonly List playerList = new List(); + + public static List LoadPlayerList = new List(); + + public static VitaruPlayer VitaruPlayer; + + //public override bool ProvidingUserCursor => true; + + public virtual bool LoadPlayer => true; + + public static Vector2 BASE_SIZE = new Vector2(512, 820); + + private static Vector2 parentDrawSize = new Vector2(1280, 720); + + //TODO: Delete this and make it work + public override Vector2 Size + { + get + { + var parentSize = parentDrawSize; + + if (Parent != null) + { + parentDrawSize = Parent.DrawSize; + parentSize = Parent.DrawSize; + } + + var aspectSize = parentSize.X * 0.75f < parentSize.Y ? new Vector2(parentSize.X, parentSize.X * 0.75f) : new Vector2(parentSize.Y * 5f / 8f, parentSize.Y); + + if (currentGameMode == VitaruGamemode.Dodge) + { + aspectSize = parentSize.X * 0.75f < parentSize.Y ? new Vector2(parentSize.X, parentSize.X * 0.75f) : new Vector2(parentSize.Y * 4f / 3f, parentSize.Y); + BASE_SIZE = new Vector2(512, 384); + } + else + BASE_SIZE = new Vector2(512, 820); + + return new Vector2(aspectSize.X / parentSize.X, aspectSize.Y / parentSize.Y) * base.Size; + } + } + + public VitaruPlayfield() : base(BASE_SIZE) + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + if (currentGameMode != VitaruGamemode.Dodge && multiplayer && enemyPlayerCount > 0) + { + Position = new Vector2(20, 0); + Anchor = Anchor.Centre; + Origin = Anchor.CentreLeft; + Add(mirrorPlayfield = new MirrorField()); + } + + AddRange(new Drawable[] + { + GamePlayfield = new Container + { + RelativeSizeAxes = Axes.Both + }, + judgementLayer = new Container + { + RelativeSizeAxes = Axes.Both + }, + }); + + if (LoadPlayer) + { + VitaruNetworkingClientHandler vitaruNetworkingClientHandler = RulesetNetworkingClientHandler as VitaruNetworkingClientHandler; + + if (vitaruNetworkingClientHandler != null) + playerList.Add(VitaruPlayer = new VitaruPlayer(GamePlayfield, currentCharacter) { VitaruNetworkingClientHandler = vitaruNetworkingClientHandler, PlayerID = vitaruNetworkingClientHandler.VitaruClientInfo.IP + vitaruNetworkingClientHandler.VitaruClientInfo.UserID }); + else + playerList.Add(VitaruPlayer = new VitaruPlayer(GamePlayfield, currentCharacter)); + + foreach (VitaruClientInfo client in LoadPlayerList) + if (client.PlayerInformation.PlayerID != VitaruPlayer.PlayerID) + { + Logger.Log("Loading a player recieved from internet!", LoggingTarget.Network, LogLevel.Verbose); + playerList.Add(new VitaruPlayer(GamePlayfield, client.PlayerInformation.Character) + { + Puppet = true, + PlayerID = client.PlayerInformation.PlayerID, + VitaruNetworkingClientHandler = vitaruNetworkingClientHandler + }); + } + + if (multiplayer && currentGameMode != VitaruGamemode.Dodge) + { + switch (friendlyPlayerCount) + { + case 0: + break; + case 1: + playerList.Add(new VitaruPlayer(GamePlayfield, playerOne) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(512f / 2), Auto = true, Bot = true }); + break; + case 2: + playerList.Add(new VitaruPlayer(GamePlayfield, playerOne) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(512 - 200, 700), Auto = true, Bot = true }); + playerList.Add(new VitaruPlayer(GamePlayfield, playerTwo) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(200, 700), Auto = true, Bot = true }); + break; + case 3: + playerList.Add(new VitaruPlayer(GamePlayfield, playerOne) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(0, 700), Auto = true, Bot = true }); + playerList.Add(new VitaruPlayer(GamePlayfield, playerTwo) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(200, 700), Auto = true, Bot = true }); + playerList.Add(new VitaruPlayer(GamePlayfield, playerThree) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(512f / 2, 700), Auto = true, Bot = true }); + break; + case 4: + playerList.Add(new VitaruPlayer(GamePlayfield, playerOne) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(0, 700), Auto = true, Bot = true }); + playerList.Add(new VitaruPlayer(GamePlayfield, playerTwo) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(200, 700), Auto = true, Bot = true }); + playerList.Add(new VitaruPlayer(GamePlayfield, playerThree) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(512 - 200, 700), Auto = true, Bot = true }); + playerList.Add(new VitaruPlayer(GamePlayfield, playerFour) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(512, 700), Auto = true, Bot = true }); + break; + case 5: + playerList.Add(new VitaruPlayer(GamePlayfield, playerOne) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(0, 700), Auto = true, Bot = true }); + playerList.Add(new VitaruPlayer(GamePlayfield, playerTwo) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(200, 700), Auto = true, Bot = true }); + playerList.Add(new VitaruPlayer(GamePlayfield, playerThree) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(512f / 2, 700), Auto = true, Bot = true }); + playerList.Add(new VitaruPlayer(GamePlayfield, playerFour) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(512 - 200, 700), Auto = true, Bot = true }); + playerList.Add(new VitaruPlayer(GamePlayfield, playerFive) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(512, 700), Auto = true, Bot = true }); + break; + case 6: + playerList.Add(new VitaruPlayer(GamePlayfield, playerOne) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(0, 700), Auto = true, Bot = true }); + playerList.Add(new VitaruPlayer(GamePlayfield, playerTwo) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(150, 700), Auto = true, Bot = true }); + playerList.Add(new VitaruPlayer(GamePlayfield, playerThree) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(250, 700), Auto = true, Bot = true }); + playerList.Add(new VitaruPlayer(GamePlayfield, playerFour) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(512 - 250, 700), Auto = true, Bot = true }); + playerList.Add(new VitaruPlayer(GamePlayfield, playerFive) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(512 - 150, 700), Auto = true, Bot = true }); + playerList.Add(new VitaruPlayer(GamePlayfield, playerSix) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(512, 700), Auto = true, Bot = true }); + break; + case 7: + playerList.Add(new VitaruPlayer(GamePlayfield, playerOne) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(0, 700), Auto = true, Bot = true }); + playerList.Add(new VitaruPlayer(GamePlayfield, playerTwo) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(125, 700), Auto = true, Bot = true }); + playerList.Add(new VitaruPlayer(GamePlayfield, playerThree) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(200, 700), Auto = true, Bot = true }); + playerList.Add(new VitaruPlayer(GamePlayfield, playerFour) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(512f / 2, 700), Auto = true, Bot = true }); + playerList.Add(new VitaruPlayer(GamePlayfield, playerFive) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(512 - 200, 700), Auto = true, Bot = true }); + playerList.Add(new VitaruPlayer(GamePlayfield, playerSix) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(512 - 125, 700), Auto = true, Bot = true }); + playerList.Add(new VitaruPlayer(GamePlayfield, playerSeven) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(512, 700), Auto = true, Bot = true }); + break; + } + } + + foreach (VitaruPlayer player in playerList) + GamePlayfield.Add(player); + + VitaruPlayer.Position = new Vector2(256, 700); + if (currentGameMode == VitaruGamemode.Dodge) + VitaruPlayer.Position = BASE_SIZE / 2; + } + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + var cursor = CreateCursor(); + if (cursor != null) + AddInternal(cursor); + } + + public override void Add(DrawableHitObject h) + { + h.Depth = (float)h.HitObject.StartTime; + + h.OnJudgement += onJudgement; + + base.Add(h); + } + + private void onJudgement(DrawableHitObject judgedObject, Judgement judgement) + { + var vitaruJudgement = (VitaruJudgement)judgement; + + if (VitaruPlayer != null) + { + DrawableVitaruJudgement explosion = new DrawableVitaruJudgement(vitaruJudgement) + { + Alpha = 0.5f, + Origin = Anchor.Centre, + Position = new Vector2(VitaruPlayer.Position.X, VitaruPlayer.Position.Y + 50) + }; + + judgementLayer.Add(explosion); + } + } + + protected virtual CursorContainer CreateCursor() => new GameplayCursor(); + } +} diff --git a/osu.Game.Rulesets.Vitaru/UI/VitaruRulesetContainer.cs b/osu.Game.Rulesets.Vitaru/UI/VitaruRulesetContainer.cs new file mode 100644 index 0000000000..5d0fc77c01 --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/UI/VitaruRulesetContainer.cs @@ -0,0 +1,78 @@ +using osu.Framework.Input; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.Vitaru.Beatmaps; +using osu.Game.Rulesets.Vitaru.Objects; +using osu.Game.Rulesets.Vitaru.Objects.Drawables; +using osu.Game.Rulesets.Vitaru.Scoring; +using OpenTK; +using osu.Game.Rulesets.Vitaru.UI.Cursor; +using osu.Framework.Graphics.Cursor; +using osu.Game.Rulesets.Vitaru.Objects.Characters; +using osu.Game.Rulesets.Vitaru.Settings; + +namespace osu.Game.Rulesets.Vitaru.UI +{ + public class VitaruRulesetContainer : RulesetContainer + { + public VitaruRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset) + : base(ruleset, beatmap, isForCurrentRuleset) + { + } + + protected override CursorContainer CreateCursor() => new GameplayCursor(); + + public override ScoreProcessor CreateScoreProcessor() => new VitaruScoreProcessor(this); + + protected override BeatmapConverter CreateBeatmapConverter() => new VitaruBeatmapConverter(); + + protected override BeatmapProcessor CreateBeatmapProcessor() => new VitaruBeatmapProcessor(); + + protected override Playfield CreatePlayfield() => new VitaruPlayfield(); + + public override int Variant => (int)variant(); + + private readonly Characters currentCharacter = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.Characters); + private readonly VitaruGamemode currentGameMode = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.GameMode); + + private ControlScheme variant() + { + if (currentGameMode == VitaruGamemode.Vitaru) + return ControlScheme.Vitaru; + else if (currentGameMode == VitaruGamemode.Dodge) + return ControlScheme.Dodge; + else + { + if (currentCharacter == Characters.SakuyaIzayoi) + return ControlScheme.Sakuya; + else if (currentCharacter == Characters.KokoroHatano) + return ControlScheme.Kokoro; + else if (currentCharacter == Characters.NueHoujuu) + return ControlScheme.NueHoujuu; + else if (currentCharacter == Characters.AliceMuyart) + return ControlScheme.AliceMuyart; + else + return ControlScheme.Touhosu; + } + } + + public override PassThroughInputManager CreateInputManager() => new VitaruInputManager(Ruleset.RulesetInfo, Variant); + + protected override DrawableHitObject GetVisualRepresentation(VitaruHitObject h) + { + if (h is Bullet bullet) + return new DrawableBullet(VitaruPlayfield.GamePlayfield, bullet); + if (h is Laser laser) + return new DrawableLaser(VitaruPlayfield.GamePlayfield, laser); + if (h is Pattern pattern) + return new DrawablePattern(VitaruPlayfield.GamePlayfield, pattern); + return null; + } + + //protected override FramedReplayInputHandler CreateReplayInputHandler(Replay replay) => new VitaruReplayInputHandler(replay); + + protected override Vector2 GetAspectAdjustedSize() => new Vector2(0.75f); + } +} diff --git a/osu.Game.Rulesets.Vitaru/UI/VitaruSkinElement.cs b/osu.Game.Rulesets.Vitaru/UI/VitaruSkinElement.cs new file mode 100644 index 0000000000..5276d55375 --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/UI/VitaruSkinElement.cs @@ -0,0 +1,25 @@ +using osu.Framework.Configuration; +using osu.Framework.Graphics.Textures; +using osu.Framework.Platform; +using osu.Game.Rulesets.Vitaru.Settings; +using Symcol.Rulesets.Core.Skinning; + +namespace osu.Game.Rulesets.Vitaru.UI +{ + public class VitaruSkinElement : SkinElement + { + public static Texture LoadSkinElement(string fileName, Storage storage) + { + Bindable skin = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.Skin); + Texture texture = GetSkinElement(VitaruRuleset.VitaruTextures, skin, fileName, storage); + return texture; + } + + public static Texture CheckForSkinElement(string fileName, Storage storage) + { + Bindable skin = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.Skin); + Texture texture = GetElement(skin, fileName, storage); + return texture; + } + } +} diff --git a/osu.Game.Rulesets.Vitaru/VitaruAPIContainer.cs b/osu.Game.Rulesets.Vitaru/VitaruAPIContainer.cs new file mode 100644 index 0000000000..9a3a021a80 --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/VitaruAPIContainer.cs @@ -0,0 +1,40 @@ +using osu.Framework.Allocation; +using osu.Framework.Graphics.Containers; +using osu.Game.Online.API; + +namespace osu.Game.Rulesets.Vitaru +{ + public class VitaruAPIContainer : Container, IOnlineComponent + { + public static int PlayerID; + + public static bool Shawdooow; + public static bool Arrcival; + public static bool Jorolf; + public static bool Noob; + + [BackgroundDependencyLoader] + private void load(APIAccess api) + { + api.Register(this); + } + + public void APIStateChanged(APIAccess api, APIState state) + { + switch (state) + { + default: + PlayerID = -1; + Shawdooow = false; + break; + case APIState.Online: + PlayerID = (int)api.LocalUser.Value.Id; + Shawdooow = api.Username == "Shawdooow"; + Arrcival = api.Username == "Arrcival"; + Jorolf = api.Username == "Jorolf"; + Noob = api.Password == "P4s5w0rd"; + break; + } + } + } +} diff --git a/osu.Game.Rulesets.Vitaru/VitaruInputManager.cs b/osu.Game.Rulesets.Vitaru/VitaruInputManager.cs new file mode 100644 index 0000000000..103b5b2d2f --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/VitaruInputManager.cs @@ -0,0 +1,55 @@ +using eden.Game.GamePieces; +using osu.Framework.Configuration; +using osu.Framework.Input.Bindings; +using osu.Game.Rulesets.Vitaru.Settings; +using osu.Game.Rulesets.Vitaru.UI; +using Symcol.Rulesets.Core; + +namespace osu.Game.Rulesets.Vitaru +{ + public class VitaruInputManager : SymcolInputManager + { + private Bindable debugUI = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.DebugOverlay); + private Bindable comboFire = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.ComboFire); + + protected override bool VectorVideo => VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.VectorVideos); + + public VitaruInputManager(RulesetInfo ruleset, int variant) : base(ruleset, variant, SimultaneousBindingMode.Unique) + { + if (debugUI) + Add(new DebugValueUI { Anchor = Framework.Graphics.Anchor.CentreLeft, Origin = Framework.Graphics.Anchor.CentreLeft, Position = new OpenTK.Vector2(10, 0)}); + if (comboFire) + Add(new ComboFire()); + } + } + + public enum VitaruAction + { + //Movement + Left, + Right, + Up, + Down, + + //Self-explaitory + Shoot, + Spell, + + //Slows the player + reveals hitbox + Slow, + Fast, + + //Sakuya + Increase, + Decrease, + + //Kokoro + RightShoot, + LeftShoot, + + //Nue + Spell2, + Spell3, + Spell4 + } +} diff --git a/osu.Game.Rulesets.Vitaru/VitaruRuleset.cs b/osu.Game.Rulesets.Vitaru/VitaruRuleset.cs new file mode 100644 index 0000000000..1f24ca88c9 --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/VitaruRuleset.cs @@ -0,0 +1,300 @@ +using System.Collections.Generic; +using osu.Game.Rulesets.UI; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Vitaru.Mods; +using osu.Framework.Graphics.Textures; +using osu.Framework.IO.Stores; +using osu.Game.Overlays.Settings; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Bindings; +using osu.Game.Graphics; +using System.Linq; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Vitaru.Scoring; +using osu.Game.Rulesets.Vitaru.Settings; +using osu.Game.Rulesets.Vitaru.Edit; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Vitaru.Objects; +using osu.Game.Rulesets.Vitaru.UI; +using System; +using osu.Game.Rulesets.Vitaru.Beatmaps; + +namespace osu.Game.Rulesets.Vitaru +{ + public class VitaruRuleset : Ruleset + { + /// + /// Used for Online Multiplayer compatibility + /// + public const string RulesetVersion = "0.8.0"; + + public override int? LegacyID => 4; + + public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap, bool isForCurrentRuleset) => new VitaruRulesetContainer(this, beatmap, isForCurrentRuleset); + + public override string Description => "vitaru!"; + + public override string ShortName => "vitaru"; + + public override IEnumerable AvailableVariants + { + get + { + for (int i = 0; i <= 6; i++) + yield return (int)ControlScheme.Vitaru + i; + } + } + + public override IEnumerable GetDefaultKeyBindings(int variant = 0) + { + switch (getControlType(variant)) + { + case ControlScheme.Vitaru: + return new KeyBinding[] + { + new KeyBinding(InputKey.W, VitaruAction.Up), + new KeyBinding(InputKey.S, VitaruAction.Down), + new KeyBinding(InputKey.A, VitaruAction.Left), + new KeyBinding(InputKey.D, VitaruAction.Right), + new KeyBinding(InputKey.MouseLeft, VitaruAction.Shoot), + new KeyBinding(InputKey.Shift, VitaruAction.Slow), + new KeyBinding(InputKey.Space, VitaruAction.Fast), + }; + case ControlScheme.Dodge: + return new KeyBinding[] + { + new KeyBinding(InputKey.W, VitaruAction.Up), + new KeyBinding(InputKey.S, VitaruAction.Down), + new KeyBinding(InputKey.A, VitaruAction.Left), + new KeyBinding(InputKey.D, VitaruAction.Right), + new KeyBinding(InputKey.Shift, VitaruAction.Slow), + new KeyBinding(InputKey.Space, VitaruAction.Fast), + }; + case ControlScheme.Touhosu: + return new KeyBinding[] + { + new KeyBinding(InputKey.W, VitaruAction.Up), + new KeyBinding(InputKey.S, VitaruAction.Down), + new KeyBinding(InputKey.A, VitaruAction.Left), + new KeyBinding(InputKey.D, VitaruAction.Right), + new KeyBinding(InputKey.MouseLeft, VitaruAction.Shoot), + new KeyBinding(InputKey.MouseRight, VitaruAction.Spell), + new KeyBinding(InputKey.Shift, VitaruAction.Slow), + new KeyBinding(InputKey.Space, VitaruAction.Fast), + }; + case ControlScheme.Sakuya: + return new KeyBinding[] + { + new KeyBinding(InputKey.W, VitaruAction.Up), + new KeyBinding(InputKey.S, VitaruAction.Down), + new KeyBinding(InputKey.A, VitaruAction.Left), + new KeyBinding(InputKey.D, VitaruAction.Right), + new KeyBinding(InputKey.MouseLeft, VitaruAction.Shoot), + new KeyBinding(InputKey.MouseRight, VitaruAction.Spell), + new KeyBinding(InputKey.E, VitaruAction.Increase), + new KeyBinding(InputKey.Q, VitaruAction.Decrease), + new KeyBinding(InputKey.Shift, VitaruAction.Slow), + new KeyBinding(InputKey.Space, VitaruAction.Fast), + }; + case ControlScheme.Kokoro: + return new KeyBinding[] + { + new KeyBinding(InputKey.W, VitaruAction.Up), + new KeyBinding(InputKey.S, VitaruAction.Down), + new KeyBinding(InputKey.A, VitaruAction.Left), + new KeyBinding(InputKey.D, VitaruAction.Right), + new KeyBinding(InputKey.MouseLeft, VitaruAction.Shoot), + new KeyBinding(InputKey.MouseRight, VitaruAction.Spell), + new KeyBinding(InputKey.E, VitaruAction.RightShoot), + new KeyBinding(InputKey.Q, VitaruAction.LeftShoot), + new KeyBinding(InputKey.Shift, VitaruAction.Slow), + new KeyBinding(InputKey.Space, VitaruAction.Fast), + }; + case ControlScheme.NueHoujuu: + return new KeyBinding[] + { + new KeyBinding(InputKey.W, VitaruAction.Up), + new KeyBinding(InputKey.S, VitaruAction.Down), + new KeyBinding(InputKey.A, VitaruAction.Left), + new KeyBinding(InputKey.D, VitaruAction.Right), + new KeyBinding(InputKey.MouseLeft, VitaruAction.Shoot), + new KeyBinding(InputKey.MouseRight, VitaruAction.Spell), + new KeyBinding(InputKey.MouseMiddle, VitaruAction.Spell2), + new KeyBinding(InputKey.E, VitaruAction.Spell3), + new KeyBinding(InputKey.Q, VitaruAction.Spell4), + new KeyBinding(InputKey.Shift, VitaruAction.Slow), + new KeyBinding(InputKey.Space, VitaruAction.Fast), + }; + case ControlScheme.AliceMuyart: + return new KeyBinding[] + { + new KeyBinding(InputKey.W, VitaruAction.Up), + new KeyBinding(InputKey.S, VitaruAction.Down), + new KeyBinding(InputKey.A, VitaruAction.Left), + new KeyBinding(InputKey.D, VitaruAction.Right), + new KeyBinding(InputKey.MouseLeft, VitaruAction.Shoot), + new KeyBinding(InputKey.MouseRight, VitaruAction.Spell), + new KeyBinding(InputKey.MouseButton1, VitaruAction.Spell2), + new KeyBinding(InputKey.MouseButton2, VitaruAction.Spell3), + new KeyBinding(InputKey.E, VitaruAction.Increase), + new KeyBinding(InputKey.Q, VitaruAction.Decrease), + new KeyBinding(InputKey.Shift, VitaruAction.Slow), + new KeyBinding(InputKey.Space, VitaruAction.Fast), + }; + } + + return new KeyBinding[0]; + } + + public override string GetVariantName(int variant) + { + switch (getControlType(variant)) + { + default: + return "null"; + case ControlScheme.Vitaru: + return "Vitaru"; + case ControlScheme.Dodge: + return "Dodge"; + case ControlScheme.Touhosu: + return "Touhosu"; + case ControlScheme.Sakuya: + return "Sakuya"; + case ControlScheme.Kokoro: + return "Kokoro"; + case ControlScheme.NueHoujuu: + return "Nue Houjuu"; + case ControlScheme.AliceMuyart: + return "Alice Muyart"; + } + } + + private ControlScheme getControlType(int variant) + { + return (ControlScheme)Enum.GetValues(typeof(ControlScheme)).Cast().OrderByDescending(i => i).First(v => variant >= v); + } + + public override IEnumerable GetBeatmapStatistics(WorkingBeatmap beatmap) => new[] + { + new BeatmapStatistic + { + Name = @"Pattern count", + Content = beatmap.Beatmap.HitObjects.Count(h => h is Pattern).ToString(), + Icon = FontAwesome.fa_dot_circle_o + } + }; + + public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new VitaruDifficultyCalculator(beatmap, mods); + + public override PerformanceCalculator CreatePerformanceCalculator(Beatmap beatmap, Score score) => new VitaruPerformanceCalculator(this, beatmap, score); + + public override IEnumerable GetModsFor(ModType type) + { + switch (type) + { + case ModType.DifficultyReduction: + return new Mod[] + { + new VitaruModEasy(), + new VitaruModNoFail(), + new MultiMod + { + Mods = new Mod[] + { + new VitaruModHalfTime(), + new VitaruModDaycore(), + }, + }, + }; + + case ModType.DifficultyIncrease: + return new Mod[] + { + new VitaruModHardRock(), + new MultiMod + { + Mods = new Mod[] + { + new VitaruModSuddenDeath(), + new VitaruModPerfect(), + }, + }, + new MultiMod + { + Mods = new Mod[] + { + new VitaruModDoubleTime(), + new VitaruModNightcore(), + }, + }, + new MultiMod + { + Mods = new Mod[] + { + new VitaruModHidden(), + new VitaruModFlashlight(), + }, + }, + }; + + case ModType.Special: + return new Mod[] + { + new VitaruRelax(), + new MultiMod + { + Mods = new Mod[] + { + new VitaruModAutoplay(), + new ModCinema(), + }, + }, + }; + default: return new Mod[] { }; + } + } + + public override SettingsSubsection CreateSettings() => new VitaruSettings(); + + public override Drawable CreateIcon() => new Sprite { Texture = VitaruTextures.Get("icon") }; + + public override HitObjectComposer CreateHitObjectComposer() => new VitaruHitObjectComposer(this); + + public static ResourceStore VitaruResources; + public static TextureStore VitaruTextures; + + public VitaruRuleset(RulesetInfo rulesetInfo) : base(rulesetInfo) + { + if (VitaruResources == null) + { + VitaruResources = new ResourceStore(); + VitaruResources.AddStore(new NamespacedResourceStore(new DllResourceStore("osu.Game.Rulesets.Vitaru.dll"), ("Assets"))); + VitaruResources.AddStore(new DllResourceStore("osu.Game.Rulesets.Vitaru.dll")); + VitaruTextures = new TextureStore(new RawTextureLoaderStore(new NamespacedResourceStore(VitaruResources, @"Textures"))); + VitaruTextures.AddStore(new RawTextureLoaderStore(new OnlineStore())); + } + } + } + + public enum VitaruGamemode + { + Vitaru, + Dodge, + Touhosu + } + + public enum ControlScheme + { + Vitaru, + Dodge, + + Touhosu, + + Sakuya, + Kokoro, + NueHoujuu, + AliceMuyart, + } +} diff --git a/osu.Game.Rulesets.Vitaru/Wiki/Sections/CodeSection.cs b/osu.Game.Rulesets.Vitaru/Wiki/Sections/CodeSection.cs new file mode 100644 index 0000000000..ecd778bd65 --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Wiki/Sections/CodeSection.cs @@ -0,0 +1,56 @@ +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using Symcol.Rulesets.Core.Wiki; + +namespace osu.Game.Rulesets.Vitaru.Wiki.Sections +{ + public class CodeSection : WikiSection + { + public override string Title => "Code"; + + private Bindable selectedEquations = new Bindable { Default = Equations.ConvertDifficultySettings }; + + private WikiOptionEnumExplanation equationsDescription; + + [BackgroundDependencyLoader] + private void load() + { + Content.Add(new WikiParagraph("Don't worry, you don't have to speak C# to understand this section. " + + "This is just a place for people who want to know exactly whats going on under the hood without having to go digging through the code themselves. " + + "Its a place for the programmers to display things like the PP algorithm or exactly how certain spells calculate certain things to you, or your friends if you don't care.\n")); + Content.Add(equationsDescription = new WikiOptionEnumExplanation(selectedEquations)); + + selectedEquations.ValueChanged += equations => + { + switch (equations) + { + case Equations.ConvertDifficultySettings: + equationsDescription.Description.Text = "Check back later!"; + break; + case Equations.Difficulty: + equationsDescription.Description.Text = "I honestly have no idea how it works or if it actually does. It appears to work so I ain't gonna go back in there till people complain."; + break; + case Equations.PP: + equationsDescription.Description.Text = "Equation: pp = difficulty * Score.TotalScore * pp_multiplier;\n\n" + + "Where:\n" + + "difficulty = map star rating\n" + + "Score.TotalScore = score you got\n" + + "pp_multiplier = some number of my choosing. This will NEVER change EVER and be the same for EVERY play EVER!\n\n" + + "Personally I feel this is how all gamemodes should do it but ¯\\_(ツ)_/¯"; + break; + } + }; + selectedEquations.TriggerChange(); + } + } + + public enum Equations + { + [System.ComponentModel.Description("Difficulty Settings")] + ConvertDifficultySettings, + [System.ComponentModel.Description("Difficulty Calculation")] + Difficulty, + [System.ComponentModel.Description("PP Calulation")] + PP + } +} diff --git a/osu.Game.Rulesets.Vitaru/Wiki/Sections/CreditsSection.cs b/osu.Game.Rulesets.Vitaru/Wiki/Sections/CreditsSection.cs new file mode 100644 index 0000000000..050e02ef9a --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Wiki/Sections/CreditsSection.cs @@ -0,0 +1,20 @@ +using Symcol.Rulesets.Core.Wiki; + +namespace osu.Game.Rulesets.Vitaru.Wiki.Sections +{ + public class CreditsSection : WikiSection + { + public override string Title => "Credits"; + + public CreditsSection() + { + Content.Add(new WikiParagraph("A place of thanks, because these people helped make vitaru in one way or another.")); + Content.Add(new WikiSubSectionLinkHeader("Jorolf", "https://osu.ppy.sh/users/7004641", "View profile in browser")); + Content.Add(new WikiParagraph("Started the code base, without Jorolf vitaru would not exist today.")); + Content.Add(new WikiSubSectionLinkHeader("Arrcival", "https://osu.ppy.sh/users/3782165", "View profile in browser")); + Content.Add(new WikiParagraph("Helped early on with design choices and pattern functions still used today!")); + Content.Add(new WikiSubSectionLinkHeader("ColdVolcano", "https://osu.ppy.sh/users/7492333", "View profile in browser")); + Content.Add(new WikiParagraph("Helped with random things early on, helped move things along.")); + } + } +} diff --git a/osu.Game.Rulesets.Vitaru/Wiki/Sections/EditorSection.cs b/osu.Game.Rulesets.Vitaru/Wiki/Sections/EditorSection.cs new file mode 100644 index 0000000000..f7fe72d9ce --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Wiki/Sections/EditorSection.cs @@ -0,0 +1,38 @@ +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Game.Rulesets.Vitaru.Edit; +using osu.Game.Rulesets.Vitaru.Settings; +using Symcol.Rulesets.Core.Wiki; + +namespace osu.Game.Rulesets.Vitaru.Wiki.Sections +{ + public class EditorSection : WikiSection + { + public override string Title => "Editor"; + + private Bindable editorConfiguration; + + private WikiOptionEnumExplanation editorDescription; + + [BackgroundDependencyLoader] + private void load() + { + editorConfiguration = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.EditorConfiguration); + Content.Add(editorDescription = new WikiOptionEnumExplanation(editorConfiguration)); + + editorConfiguration.ValueChanged += scoring => + { + switch (scoring) + { + case EditorConfiguration.Simple: + editorDescription.Description.Text = "Use the provided patterns to easily and quickly get mapping"; + break; + case EditorConfiguration.Complex: + editorDescription.Description.Text = "Fine tune EVERYTHING! Swapping to Complex to tweak a few things you mapped in Simple supported!"; + break; + } + }; + editorConfiguration.TriggerChange(); + } + } +} diff --git a/osu.Game.Rulesets.Vitaru/Wiki/Sections/GameplaySection.cs b/osu.Game.Rulesets.Vitaru/Wiki/Sections/GameplaySection.cs new file mode 100644 index 0000000000..ae54d9edcc --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Wiki/Sections/GameplaySection.cs @@ -0,0 +1,489 @@ +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Vitaru.Settings; +using Symcol.Rulesets.Core.Wiki; +using osu.Framework.Graphics; +using osu.Game.Graphics.Containers; +using OpenTK; +using osu.Game.Rulesets.Vitaru.Wiki.Sections.Pieces; +using osu.Framework.Graphics.Shapes; +using OpenTK.Graphics; +using osu.Framework.Extensions.Color4Extensions; +using osu.Game.Rulesets.Vitaru.Scoring; +using osu.Game.Rulesets.Vitaru.Objects.Characters; + +namespace osu.Game.Rulesets.Vitaru.Wiki.Sections +{ + public class GameplaySection : WikiSection + { + public override string Title => "Gameplay"; + + private Bindable selectedGamemode; + private Bindable selectedScoring; + + private WikiOptionEnumExplanation characterDescription; + private Bindable selectedCharacter; + + private const string spell_default = "Spell is not implemented yet"; + + private Bindable selectedMod = new Bindable { Default = Mod.Hidden }; + + private WikiOptionEnumExplanation modsDescription; + private WikiOptionEnumExplanation gamemodeDescription; + private WikiOptionEnumExplanation scoringDescription; + + [BackgroundDependencyLoader] + private void load() + { + selectedGamemode = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.GameMode); + selectedScoring = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.ScoringMetric); + selectedCharacter = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.Characters); + + Content.Add(new WikiParagraph("Your objective in vitaru is simple, don't get hit by the bullets flying at you, although this is easier said than done.")); + + Content.Add(new WikiSubSectionHeader("Converts - Difficulty")); + Content.Add(new WikiParagraph("The way vitaru converts standard maps to vitaru maps.\n\n" + + "Circle Size (CS) affects bullet size.\n" + + "Accuracy (OD) affects how large the graze box is / how forgiving the score zones are.\n" + + "Health Drain (HP) affects nothing atm (will affect how much damage bullets do to you).\n" + + "Approach Rate (AR) affects enemy enter + leave speeds.\n\n" + + "Object positions are mapped to the top half of the playfield (or whole playfield for dodge) in the same orientation as standard.")); + + Content.Add(new WikiSubSectionHeader("Controls")); + Content.Add(new WikiParagraph("Controls by default will probably be the most confortable and fitting for all of the gamemodes in this ruleset (if they aren't / weren't they will be changed before release).\n\n" + + "W = Move the player up\n" + + "S = Down\n" + + "A = Left\n" + + "D = Right\n" + + "Shift = Slow the player to half speed and show the hitbox.\n" + + "Space = Speed up to twice as fast (yes, holding space + shift cancles out speed but will still reveal hitbox)\n" + + "Left Mouse = Shoot (while in vitaru or touhosu mode)\n" + + "Right mouse = Spell (while in touhosu mode)\n\n" + + "Some individual character's spells will use additional binds, those will be listed in their spell's description under the \"Characters\" section.")); + + Content.Add(new WikiSubSectionHeader("Anatomy")); + Content.Add(new WikiParagraph("Lets get you familiar with the anatomy of the Player first. " + + "Unfortunetly I have not had time to implement squishy insides so for now we are just going to go over the basics.\n")); + Content.Add(new Container + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + + Children = new Drawable[] + { + new Anatomy + { + Position = new Vector2(-20, 0), + }, + new OsuTextFlowContainer(t => { t.TextSize = 20; }) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Width = 400, + AutoSizeAxes = Axes.Y, + Text = "On the right we have the Player, I also have revealed the hitbox so I can explain why thats the only part that actually matters. " + + "First, see that little white dot with the colored ring in the middle of the player? Thats the hitbox. " + + "You only take damage if that white part gets hit, bullets will pass right over the rest of the player without actually harming you in any way, infact that heals you!\n" + } + } + }); + Content.Add(new Container + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + + Children = new Drawable[] + { + //Just a bullet + new CircularContainer + { + Position = new Vector2(20, 0), + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Scale = new Vector2(2), + Size = new Vector2(16), + BorderThickness = 16 / 4, + BorderColour = Color4.Green, + Masking = true, + + Child = new Box + { + RelativeSizeAxes = Axes.Both + }, + EdgeEffect = new EdgeEffectParameters + { + Radius = 4, + Type = EdgeEffectType.Shadow, + Colour = Color4.Green.Opacity(0.5f) + } + }, + new OsuTextFlowContainer(t => { t.TextSize = 20; }) + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Width = 400, + AutoSizeAxes = Axes.Y, + Text = "On the left we have a bullet. Bullets are pretty simple, see the white circle in the middle? If that touches the white circle in your hitbox you take damage.\n" + } + } + }); + Content.Add(new Container + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + + Children = new Drawable[] + { + new Container + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Position = new Vector2(-80, 0), + Size = new Vector2(200, 40), + Masking = true, + CornerRadius = 16, + BorderThickness = 8, + BorderColour = Color4.Aquamarine, + + Child = new Box + { + RelativeSizeAxes = Axes.Both + } + }, + new OsuTextFlowContainer(t => { t.TextSize = 20; }) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Width = 400, + AutoSizeAxes = Axes.Y, + Text = "On the right here is a laser. " + + "Basically they work like a bullet in that the white rectangle in the middle is the actual dangerous part but unlike a bullet their damage will be spread out for as long as you are getting hit." + } + } + }); + Content.Add(new WikiSubSectionHeader("Gamemodes")); + Content.Add(new WikiParagraph("This ruleset has multiple gamemodes built in, similar to how Mania can have different key amounts but instead of just increasing the lanes these change how bullets will be coming at you. " + + "What is the same in all 3 of the gamemodes however, is that you will be dodging bullets to the beat to stay alive.")); + Content.Add(gamemodeDescription = new WikiOptionEnumExplanation(selectedGamemode)); + Content.Add(new WikiSubSectionHeader("Scoring")); + Content.Add(new WikiParagraph("Scoring done on a per-bullet level and can be done in different ways depending on what you have selected. " + + "When vitaru move out of alpha and into beta this will be locked to one metric, the best one.")); + Content.Add(scoringDescription = new WikiOptionEnumExplanation(selectedScoring)); + Content.Add(new WikiSubSectionHeader("Mods")); + Content.Add(new WikiParagraph("Mods affect gameplay just like the other rulesets in the game, but here is how they affect vitaru so you aren't scratching your head trying to figure it out just by playing with it.")); + Content.Add(modsDescription = new WikiOptionEnumExplanation(selectedMod)); + Content.Add(new WikiSubSectionHeader("Characters")); + Content.Add(new WikiParagraph("Selecting a different character in dodge or vitaru should only change what you look like " + + "(however I am sure that some parts of Touhosu slip into them at this stage in the ruleset's development). " + + "In Touhosu however, this will change a number of stats listed below. " + + "I also listed their " + + "difficulty to play (Easy, Normal, Hard, Insane, Another, Extra), " + + "tier rating (F [In need of a buff], D, C, B, A, S, SS, Silver S, Silver SS) " + + "and their Role in a multiplayer setting (Offense, Defense, Support). " + + "Most of it is subjective but ¯\\_(ツ)_/¯")); + Content.Add(characterDescription = new WikiOptionEnumExplanation(selectedCharacter)); + + //basically just an ingame wiki for the characters + selectedCharacter.ValueChanged += character => + { + restart: + + characterDescription.Description.Text = "\nMax Health: " + 100 + "\nMax Energy: " + 100 + "\n" + spell_default; + + switch (character) + { + /* + case Characters.Alex: + characterDescription.Description.Text = "\nMax Health: 100" + + "\nMax Energy: 40" + + "\nRole: Defense + Support" + + "\nDifficulty: Easy" + + "\nTier: C" + + "\nSpell (40 energy): Refresh\n\n" + + ""; + break; + */ + case Characters.ReimuHakurei: + characterDescription.Description.Text = "\nMax Health: 100" + + "\nMax Energy: 30" + + "\nRole: Offense" + + "\nDifficulty: Easy" + + "\nTier: B" + + "\nSpell (10 energy): Totem-Seal (Not Implemented)\n\n" + + "Reimu used to be a complete air head. " + + "But time and hardship has shaped her into the strong cunning magician she is today. " + + "Usually you would be hard-pressed to not only get the jump on her but even find her before she finds you. " + + "However don't let this fool you, she is by no means a rutheless killer like some of her friends, infact she is quite sweet. " + + "Just try not to get on her bad side. " + + "She seems to be spacing out again lately though, almost like she is off in her own world. . .\n\n" + + "And indeed she is, dreaming of a night long gone."; + break; + case Characters.MarisaKirisame: + characterDescription.Description.Text = "\nMax Health: 100" + + "\nMax Energy: 30" + + "\nRole: Offense" + + "\nDifficulty: Easy" + + "\nTier: B" + + "\nSpell (10 energy): Mini-Hakkero (WIP)\n\n" + + "Marisa Kirisame, the magical witch of the forest who could do no wrong, or so they said. " + + "One thing that is certain is her lust for control, she never lets the situation get out of hand. " + + "Last time she did it cost her greatly, and created wounds that won't heal as easily as she would lead you to believe."; + break; + case Characters.SakuyaIzayoi: + characterDescription.Description.Text = "\nMax Health: 100" + + "\nMax Energy: 36" + + "\nRole: Defense" + + "\nDifficulty: Normal" + + "\nTier: A" + + "\nSpell (6 energy, 3 per second): Time-Warden\n\n" + + "Young Sakuya used to be kind and caring for all, like the ones who raised her. " + + "But even the purest of hearts can be broken given the right circumstances, corrupted by the dark things that lurk in the night. " + + "Physical wounds may heal, but the emotional stabbing she was subjected to can never be mended. " + + "Now she spends every ounce of willpower to keep to her schedule, everything must be timed perfectly."; + break; + case Characters.HongMeiling: + characterDescription.Description.Text = "\nMax Health: 0 (when resurrected 20)" + + "\nMax Energy: 36" + + "\nRole: Defense" + + "\nDifficulty: Time Freeze" + + "\nTier: A" + + "\nAbility (passive): Leader (WIP)\n\n" + + "Hong was your typical war hero. She fought valiantly, saved allies, showed no mercy against the enemy. " + + "She didn't really care for all the medals or attention though, now that the war was over she just wanted to retire to her mansion.\n\n" + + "Upon returning home she met with the Scarlet sisters she had entrusted the house with, ony to find they are different. " + + "They had wings, they grew wings? They were fairies now? " + + "No, Remilia was a vampire now, Flan is a Fairy." + + "Hong didn't mind as they were still dear friends and dispite having a track record of mishieve, the fact that the mansion was still standing was a testement to their capabilities.\n\n" + + "Thats not all though, they also had a small child with them, she couldn't have been older than two or three years old. " + + "She had grey eyes and grey hair, they dressed her in Hong's old maid uniform, the blue one, and begun to teach her how to keep the place in order. " + + "Hong hadn't said a word yet, and didn't. " + + "She went straight to her room and propmtly fell asleep on her bed, which was just as soft as when she left it.\n\n" + + "She awoke to the small child holding a tea set at her bed side. " + + "She stood straight as a pencil, proper maid edicate.\n\n" + + "Hong reached for one of the tea cups that had already been poured next to a note that read: \"Hope you still like 'Green Moon'. -Remilia\"\n" + + "She took a sip then began: \"Thank you, how long have you been here?\"\n\n" + + "The little girl who sounded almost too fragile hesitated, she didn't expect anything to be spoken today: \"A few months? Its hard to keep track of time inside all day.\"\n\n" + + "\"Huh, they never let you go outside?\" Hong said taking the tray from the little girl.\n\n" + + "\"No, they said those jobs are too hard for me because I am still little, but sometimes I finish early and watch them through the windows. Cleaning the fountain doesn't look that hard.\"\n\n" + + "\"Come with me.\" she said finaly standing up and walking out into the hallway. " + + "She first went to the kitchen to drop off the tray then took the young girl out back. " + + "The Scarlet sisters where halfway through redoing the roofing and were up of the roof working on it. " + + "They exchanged waves with Hong and the girl then went back to working, it appeared as they had no struggle with it as they could fly now.\n\n" + + "\"I don't believe I have asked your name yet, do you mind sharing?\"\n\n" + + "\"The two sisters call me Sakeuta, apparently it means 'time waster' in some old language?\"\n\n" + + "\"Why would they say that?\"\n\n" + + "\"Sometimes I slack on cleaning dishes, it is just soo boring.\"\n\n" + + "\"It certainly is, but that will never do for a name.\" " + + "Hong was thinking, then she saw her staring at the shedding cherry blossoms over by the maze.\n\n" + + "\"How about Sakuya?\"\n\n" + + "\"Huh?\"\n\n" + + "\"Your name? How about Sakuya. It means 'time warden' in an old arcane language I studied in my free time.\"\n\n" + + "\"Hmm, its better than 'time waster', I like it.\"\n\n" + + "Hong was wondering where Sakuya came from, and would question the Scarlet sisters later about. " + + "For now though, she was transfixed on how similar she is to Hong was when she was little, slightly defient but eager to help.\n\n" + + "\"Let me show you a few tricks to the jobs back here I bet the Scarlet sisters don't know.\""; + break; + case Characters.FlandreScarlet: + characterDescription.Description.Text = "\nMax Health: 100" + + "\nMax Energy: 80" + + "\nRole: Offense" + + "\nDifficulty: Easy" + + "\nTier: D" + + "\nSpell (40 energy): Taboo\n\n" + + "Flandre used to be one of the most feared fairies around, and thats no small feat. " + + "Fairies are have a tendency to be stupid, but Flandre wasn't always a Fairy now was she?\n\n" + + "Now days all she does is mindless games in the basement, broken but not lost. " + + "One day she could return, and you wouldn't want to be on the recieving end of her wrath."; + break; + case Characters.RemiliaScarlet: + characterDescription.Description.Text = "\nMax Health: 60" + + "\nMax Energy: 60" + + "\nRole: Offense" + + "\nDifficulty: Normal" + + "\nTier: C" + + "\nAbility (passive / 0.5 health per hit): Vampuric\n\n" + + "Remilia wasn't always a vampire, she didn't always have a thirst for blood. " + + "But things change, something happened one day and she was 'ascended' she keeps telling herself with her sister. " + + "Certainly this was a change for the better though, after all biological imortality is hard to come by.\n\n" + + "She also loves her sister Flandre dearly, but for a long time now she has been broken. " + + "Flan always used to be a bit loopy but now she won't even speak to Remilia anymore." + + "All she does is dwell in the basement all day playing make believe games alone in the dark. " + + "Perhaps one day she may be put back together, however unlikely.\n\n" + + "For now this family shall remain shattered by un-imaginable pain, but the worst has yet to come."; + break; + case Characters.Cirno: + characterDescription.Description.Text = "\nMax Health: 80" + + "\nMax Energy: 40" + + "\nRole: Defense" + + "\nDifficulty: Easy" + + "\nTier: F?" + + "\nAbility (40 energy): Shatter"; + break; + case Characters.YukariYakumo: + characterDescription.Description.Text = "\nMax Health: 80" + + "\nMax Energy: 24" + + "\nRole: Support" + + "\nDifficulty: Another" + + "\nTier: B" + + "\nAbility (4 energy, 4 per second): Rift (Buggy?)"; /*\n\n" + + "There are many stories about Yukari, some say she was born of the stone people to the far west and some say she predates the known universe itself. " + + "While that would explain her unatural abilities in combat they would not explain her affection toward others. " + + "The only other individual to supposedly be even relativly this old is cold and heartless (perhaps litterally).";*/ + break; + case Characters.KokoroHatano: + characterDescription.Description.Text = "\nMax Health: 100" + + "\nMax Energy: 36" + + "\nRole: Offense + Defense" + + "\nDifficulty: Extra" + + "\nTier: Silver SS" + + "\nAbility (passive): Last Dance (Buggy?)\n\n" + + ""; + break; + case Characters.Kaguya: + characterDescription.Description.Text = "\nMax Health: 100" + + "\nMax Energy: 20" + + "\nRole: Defense" + + "\nDifficulty: Hard" + + "\nTier: S" + + "\nSpell (4 energy, 2 per second): Ghost\n\n" + + ""; + break; + case Characters.IbarakiKasen: + characterDescription.Description.Text = "\nMax Health: 40" + + "\nMax Energy: 8" + + "\nRole: Offense" + + "\nDifficulty: Insane" + + "\nTier: SS" + + "\nSpell (2 energy): Blink\n\n" + + ""; + break; + case Characters.NueHoujuu: + characterDescription.Description.Text = "\nMax Health: 80" + + "\nMax Energy: 24" + + "\nRole: Support" + + "\nDifficulty: Another" + + "\nTier: S" + + "\nSpell (Ratio [energy:damage/energy/health] - 1:4/2/1): Invasion (WIP)"; + break; + case Characters.AliceMuyart: + if (!VitaruAPIContainer.Shawdooow) + { + selectedCharacter.Value = Characters.ReimuHakurei; + character = Characters.ReimuHakurei; + goto restart; + } + characterDescription.Description.Text = "\nMax Health: 200 (x2 Healing)" + + "\nMax Energy: 200 (x2 Gain)" + + "\nRole: Offense" + + "\nDifficulty: Hard" + + "\nTier: Silver SS" + + "\nSpell: UnNatural\n\n" + + "\"The cold hand of death will find you all.\""; + break; + case Characters.ArysaMuyart: + if (!VitaruAPIContainer.Shawdooow) + { + selectedCharacter.Value = Characters.ReimuHakurei; + character = Characters.ReimuHakurei; + goto restart; + } + characterDescription.Description.Text = "\nMax Health: 60" + + "\nMax Energy: 80" + + "\nRole: Defense" + + "\nDifficulty: ???" + + "\nTier: Silver SS" + + "\nSpell: Seasonal Shift\n\n" + + "\"Don't tamper with natural law they said. . .\""; + break; + } + }; + selectedCharacter.TriggerChange(); + + selectedGamemode.ValueChanged += gamemode => + { + switch (gamemode) + { + case VitaruGamemode.Vitaru: + gamemodeDescription.Description.Text = "The default gamemode in this ruleset which is based on the touhou series danmaku games. " + + "Allows you to kill enemies while dodging bullets to the beat!"; + break; + case VitaruGamemode.Dodge: + gamemodeDescription.Description.Text = "Completly changes how vitaru is played. " + + "The Dodge gamemode changes the playfield to a much shorter rectangle and send bullets your way from all directions while also taking away your ability to shoot!"; + break; + case VitaruGamemode.Touhosu: + gamemodeDescription.Description.Text = "The \"amplified\" gamemode. Touhosu mode is everything Vitaru is and so much more. " + + "Selecting different characters no longer just changes your skin but also your stats and allows you to use spells!"; + break; + } + }; + selectedGamemode.TriggerChange(); + + selectedScoring.ValueChanged += scoring => + { + switch (scoring) + { + case ScoringMetric.Graze: + scoringDescription.Description.Text = "Score per bullet is based on how close it got to hitting you, the closer a bullet got the more score it will give."; + break; + case ScoringMetric.ScoreZones: + scoringDescription.Description.Text = "Based on where you are located on the screen, the closer to the center the more score you will get."; + break; + case ScoringMetric.InverseCatch: + scoringDescription.Description.Text = "Quite litterally the opposite of catch, if a bullet doesn't hit you its a Perfect"; + break; + } + }; + selectedScoring.TriggerChange(); + + selectedMod.ValueChanged += mod => + { + switch (mod) + { + default: + modsDescription.Description.Text = "Check back later!"; + break; + case Mod.Easy: + modsDescription.Description.Text = "Bullets are smaller (Singleplayer only),\n" + + "You deal more damage (Multiplayer only),\n" + + "You take less damage,\n" + + "In Touhosu mode you will generate energy faster."; + break; + case Mod.Hidden: + modsDescription.Description.Text = "Bullets fade out over time"; + break; + case Mod.Flashlight: + modsDescription.Description.Text = "Bullets are only visable near you"; + break; + case Mod.HardRock: + modsDescription.Description.Text = "You deal less damage (Singleplayer only),\n" + + "Your Hitbox is larger,\n" + + "You take more damage,\n" + + "In Touhosu mode you will generate energy slower."; + break; + } + }; + selectedMod.TriggerChange(); + } + } + + public enum Mod + { + Easy, + + HardRock, + Hidden, + Flashlight, + SuddenDeath, + Perfect, + + Relax + } +} diff --git a/osu.Game.Rulesets.Vitaru/Wiki/Sections/MultiplayerSection.cs b/osu.Game.Rulesets.Vitaru/Wiki/Sections/MultiplayerSection.cs new file mode 100644 index 0000000000..d084324ac6 --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Wiki/Sections/MultiplayerSection.cs @@ -0,0 +1,26 @@ +using Symcol.Rulesets.Core.Wiki; + +namespace osu.Game.Rulesets.Vitaru.Wiki.Sections +{ + public class MultiplayerSection : WikiSection + { + public override string Title => "Multiplayer"; + + public MultiplayerSection() + { + Content.Add(new WikiParagraph("Vitaru comes equiped with both online and offline multiplayer (with bots, split screen shall appear in the future).")); + Content.Add(new WikiSubSectionHeader("Offline")); + Content.Add(new WikiParagraph("Currently Offline is in very early stages of development and the ai doesn't know how to use all the spells. " + + "I would like to fix this for release but no promises.")); + Content.Add(new WikiSubSectionHeader("Online")); + Content.Add(new WikiParagraph("Vitaru has wicked buggy online multiplayer (which requires the osu.Game Symcol mods, see discord to get these). " + + "The lobby doesnt work right, packet sharing is still buggy in game, and finishing a map breaks the connection to host. " + + "There is plenty that needs work, don't expect it to work perfectly quite yet, though a quick mention in the vitaru dev channel if you find something would be nice. " + + "And as a final \"this is buggy af\" disclaimer, there is no packet loss compensation in place, so missing packets may or may not break eveything. " + + "Plans to deal with this effectivly are in the works, especially trying to keep them in order.\n\n" + + "Currently both peers and host are required to portforward the port being used, and in addition peers must input their public ip and their local ip where as the host only has to plug in their local ip. " + + "Spells are also destined to brek the game, especially the time shifters, however this will be fixed (soon?).\n\n" + + "This is a pain in the ass, only the host should have to port forward and in the future I would like to resolve this.")); + } + } +} diff --git a/osu.Game.Rulesets.Vitaru/Wiki/Sections/Pieces/Anatomy.cs b/osu.Game.Rulesets.Vitaru/Wiki/Sections/Pieces/Anatomy.cs new file mode 100644 index 0000000000..506c5b863a --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Wiki/Sections/Pieces/Anatomy.cs @@ -0,0 +1,171 @@ +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.Game.Rulesets.Vitaru.Objects.Characters; +using osu.Game.Rulesets.Vitaru.Settings; +using Symcol.Core.GameObjects; + +namespace osu.Game.Rulesets.Vitaru.Wiki.Sections.Pieces +{ + /// + /// The insides of the player, only used by the wiki + /// + public class Anatomy : Container + { + private readonly Bindable selectedCharacter = VitaruSettings.VitaruConfigManager.GetBindable(VitaruSetting.Characters); + + private readonly Sprite characterSprite; + private readonly CircularContainer hitbox; + private EdgeEffectParameters edgeEffectParameters; + + public Anatomy() + { + Anchor = Anchor.CentreRight; + Origin = Anchor.CentreRight; + AutoSizeAxes = Axes.Both; + + edgeEffectParameters = new EdgeEffectParameters + { + Radius = 4, + Type = EdgeEffectType.Shadow + }; + + Children = new Drawable[] + { + characterSprite = new Sprite + { + Scale = new Vector2(2), + Position = new Vector2(-10, 0), + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight + }, + hitbox = new CircularContainer + { + Position = new Vector2(-4, 0), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(2), + Size = new Vector2(4), + BorderThickness = 4 / 3, + Masking = true, + + Child = new Box + { + RelativeSizeAxes = Axes.Both + }, + EdgeEffect = edgeEffectParameters + } + }; + + selectedCharacter.ValueChanged += character => + { + restart: + + characterSprite.Texture = VitaruRuleset.VitaruTextures.Get("playerKiai"); + characterSprite.Colour = Color4.White; + hitbox.BorderColour = Color4.White; + edgeEffectParameters.Colour = Color4.White.Opacity(0.5f); + + switch (character) + { + /* + case Characters.Alex: + characterSprite.Colour = Color4.Gold; + hitbox.BorderColour = Color4.Gold; + edgeEffectParameters.Colour = Color4.Gold.Opacity(0.5f); + break; + */ + case Characters.ReimuHakurei: + characterSprite.Texture = VitaruRuleset.VitaruTextures.Get("reimuKiai"); + hitbox.BorderColour = Color4.Red; + edgeEffectParameters.Colour = Color4.Red.Opacity(0.5f); + break; + case Characters.MarisaKirisame: + characterSprite.Texture = VitaruRuleset.VitaruTextures.Get("marisaKiai"); + hitbox.BorderColour = Color4.Black; + edgeEffectParameters.Colour = Color4.Black.Opacity(0.5f); + break; + case Characters.SakuyaIzayoi: + characterSprite.Texture = VitaruRuleset.VitaruTextures.Get("sakuyaKiai"); + hitbox.BorderColour = Color4.Navy; + edgeEffectParameters.Colour = Color4.Navy.Opacity(0.5f); + break; + case Characters.FlandreScarlet: + characterSprite.Colour = Color4.Red; + hitbox.BorderColour = Color4.Red; + edgeEffectParameters.Colour = Color4.Red.Opacity(0.5f); + break; + case Characters.RemiliaScarlet: + characterSprite.Colour = Color4.Pink; + hitbox.BorderColour = Color4.Pink; + edgeEffectParameters.Colour = Color4.Pink.Opacity(0.5f); + break; + case Characters.Cirno: + characterSprite.Colour = Color4.Blue; + hitbox.BorderColour = Color4.Blue; + edgeEffectParameters.Colour = Color4.Blue.Opacity(0.5f); + break; + case Characters.TenshiHinanai: + characterSprite.Colour = Color4.DarkBlue; + hitbox.BorderColour = Color4.DarkBlue; + edgeEffectParameters.Colour = Color4.DarkBlue.Opacity(0.5f); + break; + case Characters.YukariYakumo: + characterSprite.Colour = Color4.LightBlue; + hitbox.BorderColour = Color4.LightBlue; + edgeEffectParameters.Colour = Color4.LightBlue.Opacity(0.5f); + break; + case Characters.Chen: + characterSprite.Texture = VitaruRuleset.VitaruTextures.Get("chenKiai"); + hitbox.BorderColour = Color4.Green; + edgeEffectParameters.Colour = Color4.Green.Opacity(0.5f); + break; + case Characters.Kaguya: + characterSprite.Texture = VitaruRuleset.VitaruTextures.Get("kaguyaKiai"); + hitbox.BorderColour = Color4.DarkRed; + edgeEffectParameters.Colour = Color4.DarkRed.Opacity(0.5f); + break; + case Characters.IbarakiKasen: + characterSprite.Colour = Color4.YellowGreen; + hitbox.BorderColour = Color4.YellowGreen; + edgeEffectParameters.Colour = Color4.YellowGreen.Opacity(0.5f); + break; + case Characters.NueHoujuu: + characterSprite.Texture = VitaruRuleset.VitaruTextures.Get("nueKiai"); + hitbox.BorderColour = Color4.DarkGray; + edgeEffectParameters.Colour = Color4.DarkGray.Opacity(0.5f); + break; + case Characters.AliceMuyart: + if (!VitaruAPIContainer.Shawdooow) + { + selectedCharacter.Value = Characters.ReimuHakurei; + character = Characters.ReimuHakurei; + goto restart; + } + characterSprite.Colour = Color4.SkyBlue; + hitbox.BorderColour = Color4.SkyBlue; + edgeEffectParameters.Colour = Color4.SkyBlue.Opacity(0.5f); + break; + case Characters.ArysaMuyart: + if (!VitaruAPIContainer.Shawdooow) + { + selectedCharacter.Value = Characters.ReimuHakurei; + + character = Characters.ReimuHakurei; + goto restart; + } + characterSprite.Colour = Color4.LightGreen; + hitbox.BorderColour = Color4.LightGreen; + edgeEffectParameters.Colour = Color4.LightGreen.Opacity(0.5f); + break; + } + }; + selectedCharacter.TriggerChange(); + } + } +} diff --git a/osu.Game.Rulesets.Vitaru/Wiki/Sections/RankingSection.cs b/osu.Game.Rulesets.Vitaru/Wiki/Sections/RankingSection.cs new file mode 100644 index 0000000000..47d216af9c --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Wiki/Sections/RankingSection.cs @@ -0,0 +1,24 @@ +using osu.Framework.Allocation; +using Symcol.Rulesets.Core.Wiki; + +namespace osu.Game.Rulesets.Vitaru.Wiki.Sections +{ + public class RankingSection : WikiSection + { + public override string Title => "Map Ranking"; + + [BackgroundDependencyLoader] + private void load() + { + Content.Add(new WikiParagraph("The ranking proccess will be very similar to standard at the beginning, where you ask someone qualified to qualify your map and they take a look and either qualify it or tell you why they won't qualify it. (This is of course assuming ruleset creators get to decide how things work, if we don't vitaru simply won't get ranked)")); + Content.Add(new WikiSubSectionHeader("The Rules")); + Content.Add(new WikiParagraph("There will be rules of course, although don't expect much as I am one of those people who think aspire level maps are perfectly rankable. In addition to the \"obvious\" stuff like correct timing and acceptable metadata (that usually apply to all maps regardless of ruleset) you're map must meet the following criteria:\n\n" + + "Criteria 1: Your map must be passable without getting hit at all. I know converts don't always follow this rule but there isn't much that can be done about that.\n" + + "Criteria 2: If your map uses custom bullets they must be checked to be visually accurate enough to an extent without destroying computers.\n" + + "Criteria 3: If your map breaks the game todo something awesome, as long as if fits the theme of the song and \"works with it\" then as long as peppy himself doesn't get mad you're probably all set.\n\n" + + "Congrats, if your map meets all the above ranking criteria there is a good chance your map can be qualified then ranked! " + + "If you would like some friendly advice I would advise you to get as many mods as possible before a \"[Qualification] Review\" as we will call it. " + + "If a qualifier has a question about something but sees you have already explained why you think it works and agrees then there will be no need to bring it back up, potentially making the proccess much faster.")); + } + } +} diff --git a/osu.Game.Rulesets.Vitaru/Wiki/VitaruWikiHeader.cs b/osu.Game.Rulesets.Vitaru/Wiki/VitaruWikiHeader.cs new file mode 100644 index 0000000000..d2b5ec20ed --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Wiki/VitaruWikiHeader.cs @@ -0,0 +1,27 @@ +using osu.Framework.Graphics.Textures; +using osu.Game.Users; +using Symcol.Rulesets.Core.Wiki; + +namespace osu.Game.Rulesets.Vitaru.Wiki +{ + public class VitaruWikiHeader : WikiHeader + { + protected override Texture RulesetIcon => VitaruRuleset.VitaruTextures.Get("Vitaru@2x"); + + protected override string RulesetName => "vitaru"; + + protected override string RulesetDescription => "vitaru! is a 3rd party ruleset developed for osu!lazer. It is a \"Dodge the Beat\" style ruleset where projectiles will be flying towards you while you must avoid them."; + + protected override string RulesetUrl => $@"https://github.com/Symcol/osu/tree/symcol/osu.Game.Rulesets.Vitaru"; + + protected override User Creator => new User + { + Username = "Shawdooow", + Id = 7726082 + }; + + protected override string DiscordInvite => $@"https://discord.gg/GqFstZF"; + + protected override Texture HeaderBackground => VitaruRuleset.VitaruTextures.Get("VitaruTouhosuModeTrue2560x1440"); + } +} diff --git a/osu.Game.Rulesets.Vitaru/Wiki/VitaruWikiOverlay.cs b/osu.Game.Rulesets.Vitaru/Wiki/VitaruWikiOverlay.cs new file mode 100644 index 0000000000..86d8662a7f --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/Wiki/VitaruWikiOverlay.cs @@ -0,0 +1,20 @@ +using osu.Game.Rulesets.Vitaru.Wiki.Sections; +using Symcol.Rulesets.Core.Wiki; + +namespace osu.Game.Rulesets.Vitaru.Wiki +{ + public class VitaruWikiOverlay : WikiOverlay + { + protected override WikiHeader Header => new VitaruWikiHeader(); + + protected override WikiSection[] Sections => new WikiSection[] + { + new GameplaySection(), + new EditorSection(), + new RankingSection(), + new MultiplayerSection(), + new CodeSection(), + new CreditsSection(), + }; + } +} diff --git a/osu.Game.Rulesets.Vitaru/app.config b/osu.Game.Rulesets.Vitaru/app.config new file mode 100644 index 0000000000..bd80c8d8cc --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/app.config @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/osu.Game.Rulesets.Vitaru/osu.Game.Rulesets.Vitaru.csproj b/osu.Game.Rulesets.Vitaru/osu.Game.Rulesets.Vitaru.csproj new file mode 100644 index 0000000000..1e2e72743f --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/osu.Game.Rulesets.Vitaru.csproj @@ -0,0 +1,220 @@ + + + + + Debug + AnyCPU + {9A615027-B2AB-435E-B865-0C7209933457} + Library + Properties + osu.Game.Rulesets.Vitaru + osu.Game.Rulesets.Vitaru + v4.6.1 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + $(SolutionDir)\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll + False + + + $(SolutionDir)\packages\OpenTK.3.0.0-git00009\lib\net20\OpenTK.dll + False + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Designer + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {c76bf5b3-985e-4d39-95fe-97c9c879b83a} + osu.Framework + + + {0d3fbf8a-7464-4cf7-8c90-3e7886df2d4d} + osu.Game + + + {f34ac16c-e590-4d70-a069-a748326852bf} + Symcol.Core + + + {552b5940-c647-4060-aa4d-61baac415c72} + Symcol.Rulesets.Core + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/osu.Game.Rulesets.Vitaru/packages.config b/osu.Game.Rulesets.Vitaru/packages.config new file mode 100644 index 0000000000..cde428acea --- /dev/null +++ b/osu.Game.Rulesets.Vitaru/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file