diff --git a/Bridge/Bridge.csproj b/Bridge/Bridge.csproj index 1ccf748..63f1953 100644 --- a/Bridge/Bridge.csproj +++ b/Bridge/Bridge.csproj @@ -71,7 +71,12 @@ + + + + + Form @@ -112,7 +117,7 @@ - + FormChat.cs diff --git a/Bridge/BridgeCore.cs b/Bridge/BridgeCore.cs new file mode 100644 index 0000000..61f917b --- /dev/null +++ b/Bridge/BridgeCore.cs @@ -0,0 +1,570 @@ +using System; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Windows.Forms; +using System.Threading; +using System.Drawing; +using System.Collections.Generic; +using System.Linq; +using System.Net.NetworkInformation; + +using Resources; +using Resources.Packet; +using Resources.Datagram; + +namespace Bridge { + public static partial class BridgeCore { + public static UdpClient udpToServer; + public static TcpClient tcpToServer, tcpToClient; + public static TcpListener tcpListener = new TcpListener(IPAddress.Parse("127.0.0.1"), 12345); //hardcoded because client port can't be changed + public static BinaryWriter swriter, cwriter; + public static BinaryReader sreader, creader; + public static ushort guid; + public static int mapseed; + public static FormMain form; + public static Dictionary dynamicEntities = new Dictionary(); + public static Mutex outgoingMutex = new Mutex(); + public static BridgeStatus status = BridgeStatus.Offline; + + public static void Connect() { + form.Log("connecting...", Color.DarkGray); + string serverIP = Config.serverIP; + int serverPort = Config.serverPort; + + try { + tcpToServer = new TcpClient(); + tcpToServer.Connect(serverIP, serverPort); + + udpToServer = new UdpClient(tcpToServer.Client.LocalEndPoint as IPEndPoint); + udpToServer.Connect(serverIP, serverPort); + SendUDP(new byte[1] { (byte)DatagramID.HolePunch }); + } + catch (SocketException) {//connection refused + Disconnect(); + form.Log("failed\n", Color.Red); + var result = MessageBox.Show("Unable to connect to Exceed. Please try again later.", "Connection Error", MessageBoxButtons.RetryCancel); + if (result == DialogResult.Retry) Connect(); + return; + } + Stream stream = tcpToServer.GetStream(); + swriter = new BinaryWriter(stream); + sreader = new BinaryReader(stream); + form.Log("connected\n", Color.Green); + new Thread(new ThreadStart(ListenFromServerTCP)).Start(); + status = BridgeStatus.Connected; + + form.Log("checking version...", Color.DarkGray); + swriter.Write((byte)0);//packetID + swriter.Write(Config.bridgeVersion); + } + public static void Disconnect() { + status = BridgeStatus.Offline; + form.Invoke(new Action(() => form.listBoxPlayers.Items.Clear())); + form.Invoke(new Action(() => form.OnLogout())); + LingerOption lingerOption = new LingerOption(true, 0); + try { + udpToServer.Close(); + udpToServer = null; + } + catch { } + try { + tcpToServer.LingerState = lingerOption; + tcpToServer.Client.Close(); + tcpToServer.Close(); + udpToServer = null; + } + catch { } + try { + tcpToClient.LingerState = lingerOption; + tcpToClient.Client.Close(); + tcpToClient.Close(); + udpToServer = null; + } + catch { } + dynamicEntities.Clear(); + } + + public static void Login(string name, string password) { + form.Log("logging in...", Color.DarkGray); + swriter.Write((byte)ServerPacketID.Login); + swriter.Write(name); + swriter.Write(Hashing.Hash(password)); + swriter.Write(NetworkInterface.GetAllNetworkInterfaces().Where(nic => nic.OperationalStatus == OperationalStatus.Up).Select(nic => nic.GetPhysicalAddress().ToString()).FirstOrDefault()); + } + public static void Logout() { + swriter.Write((byte)ServerPacketID.Logout); + if (status == BridgeStatus.Playing) tcpToClient.Close(); + status = BridgeStatus.Connected; + } + public static void Register(string name, string email, string password) { + swriter.Write((byte)ServerPacketID.Register); + swriter.Write(name); + swriter.Write(email); + swriter.Write(Hashing.Hash(password)); + } + + public static void ListenFromClientTCP() { + try { + tcpListener.Start(); + } + catch (SocketException ex) { + var result = MessageBox.Show(ex.Message + "\n\nCan't start listening for the client, probably because the CubeWorld default port (12345) is already in use by another program. Do you have a CubeWorld server or another instance of the bridge already running on your computer?\n\nIf you don't know how to fix this, restarting your computer will likely help", "Error", MessageBoxButtons.RetryCancel); + if (result == DialogResult.Retry) ListenFromClientTCP(); + return; + } + while (true) { + tcpToClient = tcpListener.AcceptTcpClient(); + ClientConnected?.Invoke(); + tcpToClient.ReceiveTimeout = 500; + tcpToClient.SendTimeout = 500; + tcpToClient.NoDelay = true; + var stream = tcpToClient.GetStream(); + creader = new BinaryReader(stream); + cwriter = new BinaryWriter(stream); + + if (status != BridgeStatus.LoggedIn) { + SendToClient(new ProtocolVersion() { + version = 42069, + }); + form.Log("client rejected\n", Color.Red); + ClientDisconnected?.Invoke(); + continue; + } + status = BridgeStatus.Playing; + try { + while (true) ProcessClientPacket(creader.ReadInt32()); + } + catch (ObjectDisposedException) { } + catch (IOException) { } + tcpToClient.Close(); + switch (status) { + case BridgeStatus.Offline://server crashed + break; + case BridgeStatus.Connected://player logged out + break; + case BridgeStatus.LoggedIn://kicked + goto default; + case BridgeStatus.Playing: //client disconnected himself + status = BridgeStatus.LoggedIn; + SendUDP(new RemoveDynamicEntity() { Guid = guid }.data); + break; + default: + //this shouldnt happen + break; + } + dynamicEntities.Remove(guid); + ClientDisconnected?.Invoke(); + } + } + private static void ListenFromServerTCP() { + try { + while (true) ProcessServerPacket(sreader.ReadByte()); //we can use byte here because it doesn't contain vanilla packets + } + catch (IOException) { + form.Log("Connection to Server lost\n", Color.Red); + Disconnect(); + Connect(); + } + } + private static void ListenFromServerUDP() { + while (true) { + IPEndPoint source = null; + byte[] datagram = null; + try { + datagram = udpToServer.Receive(ref source); + } + catch (SocketException) {//when UDPclient is closed + return; + } + ProcessDatagram(datagram); + } + } + + private static void ProcessDatagram(byte[] datagram) { + var serverUpdate = new ServerUpdate(); + bool writeServerUpdate = false; + switch ((DatagramID)datagram[0]) { + case DatagramID.DynamicUpdate: + #region entityUpdate + var entityUpdate = new EntityUpdate(datagram); + EntityUpdateReceived?.Invoke(entityUpdate); + if (status == BridgeStatus.Playing) { + SendToClient(entityUpdate); + } + if (dynamicEntities.ContainsKey(entityUpdate.guid)) { + entityUpdate.Merge(dynamicEntities[entityUpdate.guid]); + } + else { + dynamicEntities.Add(entityUpdate.guid, entityUpdate); + } + break; + #endregion + case DatagramID.Attack: + #region attack + var attack = new Attack(datagram); + AttackReceived?.Invoke(attack); + var hit = new Hit() { + target = attack.Target, + damage = attack.Damage, + direction = attack.Direction, + critical = attack.Critical, + stuntime = attack.Stuntime, + position = dynamicEntities[attack.Target].position, + isYellow = attack.Skill, + type = attack.Type, + showlight = attack.ShowLight, + }; + serverUpdate.hits.Add(hit); + writeServerUpdate = true; + break; + #endregion + case DatagramID.Projectile: + #region Projectile + var projectile = new Projectile(datagram); + ProjectileReceived?.Invoke(projectile); + var shoot = new Shoot() { + attacker = projectile.Source, + position = projectile.Position, + velocity = projectile.Velocity, + scale = projectile.Scale, + particles = projectile.Particles, + mana = projectile.Particles, + projectile = projectile.Type, + chunkX = (int)projectile.Position.x / 0x1000000, + chunkY = (int)projectile.Position.y / 0x1000000 + }; + var angle = Math.Atan2(shoot.velocity.y, shoot.velocity.x); + shoot.position.x += (long)(Math.Cos(angle) * 0x10000); + shoot.position.y += (long)(Math.Sin(angle) * 0x10000); + + serverUpdate.shoots.Add(shoot); + writeServerUpdate = true; + break; + #endregion + case DatagramID.Proc: + #region proc + var proc = new Proc(datagram); + PassiveProcReceived?.Invoke(proc); + var passiveProc = new PassiveProc() { + target = proc.Target, + type = proc.Type, + modifier = proc.Modifier, + duration = proc.Duration + }; + serverUpdate.passiveProcs.Add(passiveProc); + writeServerUpdate = true; + break; + #endregion + case DatagramID.Chat: + #region chat + var chat = new Chat(datagram); + ChatMessageReceived?.Invoke(chat); + if (status == BridgeStatus.Playing) { + var chatMessage = new ChatMessage() { + sender = chat.Sender, + message = chat.Text + }; + SendToClient(chatMessage); + } + break; + #endregion + case DatagramID.Time: + #region time + var inGameTime = new InGameTime(datagram); + InGameTimeReceived?.Invoke(inGameTime); + if (status == BridgeStatus.Playing) { + var time = new Time() { + time = inGameTime.Milliseconds + }; + SendToClient(time); + } + break; + #endregion + case DatagramID.Interaction: + #region interaction + var interaction = new Interaction(datagram); + InteractionReceived?.Invoke(interaction); + var entityAction = new EntityAction() { + chunkX = interaction.ChunkX, + chunkY = interaction.ChunkY, + index = interaction.Index, + type = ActionType.StaticInteraction + }; + writeServerUpdate = true; + break; + #endregion + case DatagramID.StaticUpdate: + #region staticUpdate + var staticUpdate = new StaticUpdate(datagram); + StaticUpdateReceived?.Invoke(staticUpdate); + var staticEntity = new ServerUpdate.StaticEntity() { + chunkX = (int)(staticUpdate.Position.x / (65536 * 256)), + chunkY = (int)(staticUpdate.Position.y / (65536 * 256)), + id = staticUpdate.Id, + type = staticUpdate.Type, + position = staticUpdate.Position, + rotation = (int)staticUpdate.Direction, + size = staticUpdate.Size, + closed = staticUpdate.Closed, + time = staticUpdate.Time, + guid = staticUpdate.User + }; + serverUpdate.statics.Add(staticEntity); + writeServerUpdate = true; + break; + #endregion + case DatagramID.Block: + //var block = new Block(datagram); + //TODO + break; + case DatagramID.Particle: + #region particle + var particleDatagram = new Particle(datagram); + ParticleReceived?.Invoke(particleDatagram); + var particleSubPacket = new ServerUpdate.Particle() { + position = particleDatagram.Position, + velocity = particleDatagram.Velocity, + color = new Resources.Utilities.FloatVector() { + x = particleDatagram.Color.R / 255, + y = particleDatagram.Color.G / 255, + z = particleDatagram.Color.B / 255 + }, + alpha = particleDatagram.Color.A / 255, + size = particleDatagram.Size, + count = particleDatagram.Count, + type = particleDatagram.Type, + spread = particleDatagram.Spread + }; + serverUpdate.particles.Add(particleSubPacket); + writeServerUpdate = true; + break; + #endregion + case DatagramID.RemoveDynamicEntity: + #region RemoveDynamicEntity + var rde = new RemoveDynamicEntity(datagram); + DynamicEntityRemoved?.Invoke(rde); + entityUpdate = new EntityUpdate() { + guid = rde.Guid, + hostility = (Hostility)255, //workaround for DC because i dont like packet2 + HP = 0 + }; + if (status == BridgeStatus.Playing) { + SendToClient(entityUpdate); + } + dynamicEntities.Remove(rde.Guid); + break; + #endregion + case DatagramID.SpecialMove: + #region speicalMove + var specialMove = new SpecialMove(datagram); + SpecialMoveReceived?.Invoke(specialMove); + break; + #endregion + default: + form.Log("unknown datagram ID: " + datagram[0], Color.Red); + break; + } + if (status == BridgeStatus.Playing && writeServerUpdate) SendToClient(serverUpdate); + } + private static void ProcessClientPacket(int packetID) { + switch ((PacketID)packetID) { + case PacketID.EntityUpdate: + #region entityUpdate + var entityUpdate = new EntityUpdate(creader); + EntityUpdateSent?.Invoke(entityUpdate); + if (dynamicEntities.ContainsKey(entityUpdate.guid)) { + entityUpdate.Filter(dynamicEntities[entityUpdate.guid]); + entityUpdate.Merge(dynamicEntities[entityUpdate.guid]); + } + else { + dynamicEntities.Add(entityUpdate.guid, entityUpdate); + } + if (!entityUpdate.IsEmpty) { + SendUDP(entityUpdate.CreateDatagram()); + } + break; + #endregion + case PacketID.EntityAction: + #region entity action + var entityAction = new EntityAction(creader); + EntityActionSent?.Invoke(entityAction); + break; + #endregion + case PacketID.Hit: + #region hit + var hit = new Hit(creader); + HitSent?.Invoke(hit); + var attack = new Attack() { + Target = (ushort)hit.target, + Damage = hit.damage, + Direction = hit.direction, + Stuntime = hit.stuntime, + Skill = hit.isYellow, + Type = hit.type, + ShowLight = hit.showlight, + Critical = hit.critical + }; + SendUDP(attack.data); + break; + #endregion + case PacketID.PassiveProc: + #region passiveProc + var passiveProc = new PassiveProc(creader); + PassiveProcSent?.Invoke(passiveProc); + var proc = new Proc() { + Target = (ushort)passiveProc.target, + Type = passiveProc.type, + Modifier = passiveProc.modifier, + Duration = passiveProc.duration + }; + SendUDP(proc.data); + break; + #endregion + case PacketID.Shoot: + #region shoot + var shoot = new Shoot(creader); + ShotSent?.Invoke(shoot); + var projectile = new Projectile() { + Position = shoot.position, + Velocity = shoot.velocity, + Scale = shoot.scale, + Particles = shoot.particles, + Type = shoot.projectile, + Source = (ushort)shoot.attacker, + }; + SendUDP(projectile.data); + break; + #endregion + case PacketID.Chat: + #region chat + var chatMessage = new ChatMessage(creader); + ChatMessageSent?.Invoke(chatMessage); + var chat = new Chat() { + Sender = guid,//client doesn't send this + Text = chatMessage.message + }; + SendUDP(chat.data); + break; + #endregion + case PacketID.Chunk: + #region chunk + var chunk = new Chunk(creader); + ChunkDiscovered?.Invoke(chunk); + break; + #endregion + case PacketID.Sector: + #region sector + var sector = new Sector(creader); + SectorDiscovered?.Invoke(sector); + break; + #endregion + case PacketID.Version: + #region version + var version = new ProtocolVersion(creader); + VersionSent?.Invoke(version); + if (version.version != 3) { + version.version = 3; + SendToClient(version); + } + else { + SendToClient(new Join() { + guid = guid, + junk = new byte[0x1168] + }); + SendToClient(new MapSeed() { + seed = mapseed + }); + foreach (var dynamicEntity in dynamicEntities.Values.ToList()) { + SendToClient(dynamicEntity); + } + } + break; + #endregion + default: + form.Log("unknown client packet\n", Color.Magenta); + break; + } + } + private static void ProcessServerPacket(int packetID) { + switch ((ServerPacketID)packetID) { + case ServerPacketID.VersionCheck: + #region VersionCheck + if (!sreader.ReadBoolean()) { + form.Log("mismatch\n", Color.Red); + var b = MessageBox.Show("your bridge is outdated. Please download the newest version.\n\nGo to download page now?", "version mismatch", MessageBoxButtons.YesNo); + if (b == DialogResult.Yes) { + System.Diagnostics.Process.Start("https://github.com/LastExceed/Exceed/releases"); + } + return; + } + form.Log("match\n", Color.Green); + form.Invoke(new Action(() => form.buttonLoginRegister.Enabled = true)); + new Thread(new ThreadStart(ListenFromServerUDP)).Start(); + break; + #endregion + case ServerPacketID.Login: + #region Login + var authResponse = (AuthResponse)sreader.ReadByte(); + if (authResponse == AuthResponse.Success) { + status = BridgeStatus.LoggedIn; + guid = sreader.ReadUInt16(); + mapseed = sreader.ReadInt32(); + } + form.Invoke(new Action(() => form.register.OnLoginResponse(authResponse))); + break; + #endregion + case ServerPacketID.Register: + #region Register + switch ((RegisterResponse)sreader.ReadByte()) { + case RegisterResponse.Success: + form.Log("account registered\n", Color.DarkGray); + form.Invoke(new Action(() => form.register.SetLayout(false))); + break; + case RegisterResponse.UsernameTaken: + MessageBox.Show("this username is already in use"); + form.Invoke(new Action(() => form.register.buttonRegister.Enabled = true)); + break; + case RegisterResponse.EmailTaken: + MessageBox.Show("there is already an account associated to this email"); + form.Invoke(new Action(() => form.register.buttonRegister.Enabled = true)); + break; + } + break; + #endregion + case ServerPacketID.Kick: + #region Kick + status = BridgeStatus.LoggedIn; + tcpToClient.Close(); + break; + #endregion + case ServerPacketID.BTFO: + #region BTFO + status = BridgeStatus.Connected; + form.Invoke(new Action(() => form.OnLogout())); + CwRam.memory.process.Kill(); + var reason = sreader.ReadString(); + MessageBox.Show(reason); + break; + #endregion + default: + MessageBox.Show("unknown server packet received"); + break; + } + } + + public static void SendUDP(byte[] data) { + udpToServer.Send(data, data.Length); + } + public static void SendToClient(Packet packet) { + outgoingMutex.WaitOne(); + try { + packet.Write(cwriter); + } + catch (IOException) { + //handled in reading thread + } + outgoingMutex.ReleaseMutex(); + } + } +} diff --git a/Bridge/BridgeCore_Events.cs b/Bridge/BridgeCore_Events.cs new file mode 100644 index 0000000..b4c452a --- /dev/null +++ b/Bridge/BridgeCore_Events.cs @@ -0,0 +1,57 @@ +using Resources.Datagram; +using Resources.Packet; +using System; + +namespace Bridge { + public delegate void EntityUpdateSentEventHandler(EntityUpdate entityUpdate); + public delegate void EntityActionSentEventHandler(EntityAction entityAction); + public delegate void HitSentHandler(Hit hit); + public delegate void PassiveProcSentEventHandler(PassiveProc passiveProc); + public delegate void ShotSentHandler(Shoot shoot); + public delegate void ChatMessageSentEventHandler(ChatMessage chatMessage); + public delegate void ChunkDiscoveredEventHandler(Chunk chunk); + public delegate void SectorDiscoveredEventHandler(Sector sector); + public delegate void VersionSentEventHandler(ProtocolVersion protocolVersion); + + public delegate void EntityUpdateReceivedEventHandler(EntityUpdate entityUpdate); + public delegate void AttackReceivedEventHandler(Attack attack); + public delegate void ProjectileReceivedEventHandler(Projectile projectile); + public delegate void PassiveProcReceivedEventHandler(Proc proc); + public delegate void ChatMessageReceivedEventHandler(Chat chat); + public delegate void InGameTimeReceivedEventHandler(InGameTime inGameTime); + public delegate void InteractionReceivedEventHandler(Interaction interaction); + public delegate void StaticUpdateReceivedEventHandler(StaticUpdate staticUpdate); + public delegate void ParticleReceivedEventHandler(Particle particle); + public delegate void DynamicEntityRemovedEventHandler(RemoveDynamicEntity removeDynamicEntity); + public delegate void SpecialMoveReceiveddEventHandler(SpecialMove specialMove); + + public delegate void ClientConnectedEventHandler(); + public delegate void ClientDisconnectedEventHandler(); + + public static partial class BridgeCore { + public static event EntityUpdateSentEventHandler EntityUpdateSent; + public static event EntityActionSentEventHandler EntityActionSent; + public static event HitSentHandler HitSent; + public static event PassiveProcSentEventHandler PassiveProcSent; + public static event ShotSentHandler ShotSent; + public static event ChatMessageSentEventHandler ChatMessageSent; + public static event ChunkDiscoveredEventHandler ChunkDiscovered; + public static event SectorDiscoveredEventHandler SectorDiscovered; + public static event VersionSentEventHandler VersionSent; + + public static event EntityUpdateReceivedEventHandler EntityUpdateReceived; + public static event AttackReceivedEventHandler AttackReceived; + public static event ProjectileReceivedEventHandler ProjectileReceived; + public static event PassiveProcReceivedEventHandler PassiveProcReceived; + public static event ChatMessageReceivedEventHandler ChatMessageReceived; + public static event InGameTimeReceivedEventHandler InGameTimeReceived; + public static event InteractionReceivedEventHandler InteractionReceived; + public static event StaticUpdateReceivedEventHandler StaticUpdateReceived; + public static event ParticleReceivedEventHandler ParticleReceived; + public static event DynamicEntityRemovedEventHandler DynamicEntityRemoved; + public static event SpecialMoveReceiveddEventHandler SpecialMoveReceived; + + public static event ClientConnectedEventHandler ClientConnected; + public static event ClientDisconnectedEventHandler ClientDisconnected; + } +} diff --git a/Bridge/BridgeTCPUDP.cs b/Bridge/BridgeTCPUDP.cs deleted file mode 100644 index f6ff3a1..0000000 --- a/Bridge/BridgeTCPUDP.cs +++ /dev/null @@ -1,1033 +0,0 @@ -using System; -using System.IO; -using System.Net; -using System.Net.Sockets; -using System.Windows.Forms; -using System.Threading; -using System.Drawing; -using System.Collections.Generic; -using System.Linq; -using System.Net.NetworkInformation; - -using Resources; -using Resources.Utilities; -using Resources.Packet; -using Resources.Datagram; - -namespace Bridge { - static class BridgeTCPUDP { - public static UdpClient udpToServer; - public static TcpClient tcpToServer, tcpToClient; - public static TcpListener tcpListener = new TcpListener(IPAddress.Parse("127.0.0.1"), 12345); //hardcoded because client port can't be changed - public static BinaryWriter swriter, cwriter; - public static BinaryReader sreader, creader; - public static ushort guid; - public static int mapseed; - public static FormMain form; - public static Dictionary dynamicEntities = new Dictionary(); - public static ushort lastTarget; - public static Mutex outgoingMutex = new Mutex(); - public static BridgeStatus status = BridgeStatus.Offline; - - public static void Connect() { - form.Log("connecting...", Color.DarkGray); - string serverIP = Config.serverIP; - int serverPort = Config.serverPort; - - try { - tcpToServer = new TcpClient(); - tcpToServer.Connect(serverIP, serverPort); - - udpToServer = new UdpClient(tcpToServer.Client.LocalEndPoint as IPEndPoint); - udpToServer.Connect(serverIP, serverPort); - SendUDP(new byte[1] { (byte)DatagramID.HolePunch }); - } - catch (SocketException) {//connection refused - Disconnect(); - form.Log("failed\n", Color.Red); - var result = MessageBox.Show("Unable to connect to Exceed. Please try again later.", "Connection Error", MessageBoxButtons.RetryCancel); - if (result == DialogResult.Retry) Connect(); - return; - } - Stream stream = tcpToServer.GetStream(); - swriter = new BinaryWriter(stream); - sreader = new BinaryReader(stream); - form.Log("connected\n", Color.Green); - new Thread(new ThreadStart(ListenFromServerTCP)).Start(); - status = BridgeStatus.Connected; - - form.Log("checking version...", Color.DarkGray); - swriter.Write((byte)0);//packetID - swriter.Write(Config.bridgeVersion); - } - public static void Disconnect() { - status = BridgeStatus.Offline; - form.Invoke(new Action(() => form.listBoxPlayers.Items.Clear())); - form.Invoke(new Action(() => form.OnLogout())); - LingerOption lingerOption = new LingerOption(true, 0); - try { - udpToServer.Close(); - udpToServer = null; - } - catch { } - try { - tcpToServer.LingerState = lingerOption; - tcpToServer.Client.Close(); - tcpToServer.Close(); - udpToServer = null; - } - catch { } - try { - tcpToClient.LingerState = lingerOption; - tcpToClient.Client.Close(); - tcpToClient.Close(); - udpToServer = null; - } - catch { } - dynamicEntities.Clear(); - } - - public static void Login(string name, string password) { - form.Log("logging in...", Color.DarkGray); - swriter.Write((byte)ServerPacketID.Login); - swriter.Write(name); - swriter.Write(Hashing.Hash(password)); - swriter.Write(NetworkInterface.GetAllNetworkInterfaces().Where(nic => nic.OperationalStatus == OperationalStatus.Up).Select(nic => nic.GetPhysicalAddress().ToString()).FirstOrDefault()); - } - public static void Logout() { - swriter.Write((byte)ServerPacketID.Logout); - if (status == BridgeStatus.Playing) tcpToClient.Close(); - status = BridgeStatus.Connected; - } - public static void Register(string name, string email, string password) { - swriter.Write((byte)ServerPacketID.Register); - swriter.Write(name); - swriter.Write(email); - swriter.Write(Hashing.Hash(password)); - } - - public static void ListenFromClientTCP() { - try { - tcpListener.Start(); - } - catch (SocketException ex) { - var result = MessageBox.Show(ex.Message + "\n\nCan't start listening for the client, probably because the CubeWorld default port (12345) is already in use by another program. Do you have a CubeWorld server or another instance of the bridge already running on your computer?\n\nIf you don't know how to fix this, restarting your computer will likely help", "Error", MessageBoxButtons.RetryCancel); - if (result == DialogResult.Retry) ListenFromClientTCP(); - return; - } - while (true) { - tcpToClient = tcpListener.AcceptTcpClient(); - form.Log("client connected\n", Color.Green); - tcpToClient.ReceiveTimeout = 500; - tcpToClient.SendTimeout = 500; - - tcpToClient.NoDelay = true; - Stream stream = tcpToClient.GetStream(); - creader = new BinaryReader(stream); - cwriter = new BinaryWriter(stream); - - if (status != BridgeStatus.LoggedIn) { - SendToClient(new ProtocolVersion() { - version = 42069, - }); - form.Log("client rejected\n", Color.Red); - continue; - } - status = BridgeStatus.Playing; - try { - while (true) ProcessClientPacket(creader.ReadInt32()); - } - catch (ObjectDisposedException) { } - catch (IOException) { } - tcpToClient.Close(); - switch (status) { - case BridgeStatus.Offline://server crashed - break; - case BridgeStatus.Connected://player logged out - break; - case BridgeStatus.LoggedIn://kicked - goto default; - case BridgeStatus.Playing: //client disconnected himself - status = BridgeStatus.LoggedIn; - SendUDP(new RemoveDynamicEntity() { Guid = guid }.data); - break; - default: - //this shouldnt happen - break; - } - dynamicEntities.Remove(guid); - form.Log("client disconnected\n", Color.Red); - RefreshPlayerlist(); - } - } - private static void ListenFromServerTCP() { - try { - while (true) ProcessServerPacket(sreader.ReadByte()); //we can use byte here because it doesn't contain vanilla packets - } - catch (IOException) { - form.Log("Connection to Server lost\n", Color.Red); - Disconnect(); - Connect(); - } - } - private static void ListenFromServerUDP() { - while (true) { - IPEndPoint source = null; - byte[] datagram = null; - try { - datagram = udpToServer.Receive(ref source); - } - catch (SocketException) {//when UDPclient is closed - return; - } - ProcessDatagram(datagram); - } - } - - private static void ProcessDatagram(byte[] datagram) { - var serverUpdate = new ServerUpdate(); - bool writeServerUpdate = false; - switch ((DatagramID)datagram[0]) { - case DatagramID.DynamicUpdate: - #region entityUpdate - var entityUpdate = new EntityUpdate(datagram); - if (status == BridgeStatus.Playing) { - if (entityUpdate.guid == guid) { - CwRam.Teleport(entityUpdate.position); - break; - } - SendToClient(entityUpdate); - } - - if (dynamicEntities.ContainsKey(entityUpdate.guid)) { - entityUpdate.Merge(dynamicEntities[entityUpdate.guid]); - } - else { - dynamicEntities.Add(entityUpdate.guid, entityUpdate); - } - - if (entityUpdate.name != null) { - RefreshPlayerlist(); - } - break; - #endregion - case DatagramID.Attack: - #region attack - var attack = new Attack(datagram); - CwRam.Knockback(attack.Direction); - var hit = new Hit() { - target = attack.Target, - damage = attack.Damage, - direction = attack.Direction, - critical = attack.Critical, - stuntime = attack.Stuntime, - position = dynamicEntities[attack.Target].position, - isYellow = attack.Skill, - type = attack.Type, - showlight = attack.ShowLight, - }; - serverUpdate.hits.Add(hit); - writeServerUpdate = true; - break; - #endregion - case DatagramID.Projectile: - #region Projectile - var projectile = new Projectile(datagram); - - var shoot = new Shoot() { - attacker = projectile.Source, - position = projectile.Position, - velocity = projectile.Velocity, - scale = projectile.Scale, - particles = projectile.Particles, - mana = projectile.Particles, - projectile = projectile.Type, - chunkX = (int)projectile.Position.x / 0x1000000, - chunkY = (int)projectile.Position.y / 0x1000000 - }; - var angle = Math.Atan2(shoot.velocity.y, shoot.velocity.x); - shoot.position.x += (long)(Math.Cos(angle) * 0x10000); - shoot.position.y += (long)(Math.Sin(angle) * 0x10000); - - serverUpdate.shoots.Add(shoot); - writeServerUpdate = true; - break; - #endregion - case DatagramID.Proc: - #region proc - var proc = new Proc(datagram); - if (proc.Type == ProcType.Poison && proc.Target == guid) { - var su = new ServerUpdate(); - su.hits.Add(new Hit() { - damage = proc.Modifier, - target = guid, - position = dynamicEntities[guid].position, - }); - bool tick() { - bool f = status == BridgeStatus.Playing && dynamicEntities[guid].HP > 0; - if (f) { - SendToClient(su); - } - return !f; - } - Tools.DoLater(tick, 500, 7); - } - var passiveProc = new PassiveProc() { - target = proc.Target, - type = proc.Type, - modifier = proc.Modifier, - duration = proc.Duration - }; - serverUpdate.passiveProcs.Add(passiveProc); - writeServerUpdate = true; - break; - #endregion - case DatagramID.Chat: - #region chat - var chat = new Chat(datagram); - var chatMessage = new ChatMessage() { - sender = chat.Sender, - message = chat.Text - }; - if (status == BridgeStatus.Playing) SendToClient(chatMessage); - if (chat.Sender == 0) { - form.Log(chat.Text + "\n", Color.Magenta); - } - else { - form.Log(dynamicEntities[chat.Sender].name + ": ", Color.Cyan); - form.Log(chat.Text + "\n", Color.White); - } - break; - #endregion - case DatagramID.Time: - #region time - var igt = new InGameTime(datagram); - - var time = new Time() { - time = igt.Milliseconds - }; - if (status == BridgeStatus.Playing) SendToClient(time); - break; - #endregion - case DatagramID.Interaction: - #region interaction - var interaction = new Interaction(datagram); - var entityAction = new EntityAction() { - chunkX = interaction.ChunkX, - chunkY = interaction.ChunkY, - index = interaction.Index, - type = ActionType.StaticInteraction - }; - writeServerUpdate = true; - break; - #endregion - case DatagramID.StaticUpdate: - #region staticUpdate - var staticUpdate = new StaticUpdate(datagram); - - var staticEntity = new ServerUpdate.StaticEntity() { - chunkX = (int)(staticUpdate.Position.x / (65536 * 256)), - chunkY = (int)(staticUpdate.Position.y / (65536 * 256)), - id = staticUpdate.Id, - type = staticUpdate.Type, - position = staticUpdate.Position, - rotation = (int)staticUpdate.Direction, - size = staticUpdate.Size, - closed = staticUpdate.Closed, - time = staticUpdate.Time, - guid = staticUpdate.User - }; - serverUpdate.statics.Add(staticEntity); - writeServerUpdate = true; - break; - #endregion - case DatagramID.Block: - //var block = new Block(datagram); - //TODO - break; - case DatagramID.Particle: - #region particle - var particleDatagram = new Particle(datagram); - - var particleSubPacket = new ServerUpdate.Particle() { - position = particleDatagram.Position, - velocity = particleDatagram.Velocity, - color = new Resources.Utilities.FloatVector() { - x = particleDatagram.Color.R / 255, - y = particleDatagram.Color.G / 255, - z = particleDatagram.Color.B / 255 - }, - alpha = particleDatagram.Color.A / 255, - size = particleDatagram.Size, - count = particleDatagram.Count, - type = particleDatagram.Type, - spread = particleDatagram.Spread - }; - serverUpdate.particles.Add(particleSubPacket); - break; - #endregion - case DatagramID.RemoveDynamicEntity: - #region RemoveDynamicEntity - var rde = new RemoveDynamicEntity(datagram); - entityUpdate = new EntityUpdate() { - guid = rde.Guid, - hostility = (Hostility)255, //workaround for DC because i dont like packet2 - HP = 0 - }; - if (status == BridgeStatus.Playing) SendToClient(entityUpdate); - dynamicEntities.Remove(rde.Guid); - RefreshPlayerlist(); - break; - #endregion - case DatagramID.SpecialMove: - #region speicalMove - var specialMove = new SpecialMove(datagram); - switch (specialMove.Id) { - case SpecialMoveID.Taunt: - if (dynamicEntities.ContainsKey(specialMove.Guid)) { - if (status == BridgeStatus.Playing) { - CwRam.Teleport(dynamicEntities[specialMove.Guid].position); - CwRam.Freeze(5000); - } - } - break; - case SpecialMoveID.CursedArrow: - break; - case SpecialMoveID.ArrowRain: - break; - case SpecialMoveID.Shrapnel: - var su = new ServerUpdate(); - var blood_hit = new Hit() { - damage = 5f, - target = specialMove.Guid, - }; - su.hits.Add(blood_hit); - var blood_particles = new ServerUpdate.Particle() { - count = 10, - spread = 2f, - type = ParticleType.Normal, - size = 0.1f, - velocity = new FloatVector() { - z = 1f, - }, - color = new FloatVector() { - x = 1f, - y = 0f, - z = 0f - }, - alpha = 1f, - }; - su.particles.Add(blood_particles); - bool tick() { - bool f = status == BridgeStatus.Playing && dynamicEntities[specialMove.Guid].HP > 0; - if (f) { - blood_hit.position = blood_particles.position = dynamicEntities[specialMove.Guid].position; - SendToClient(su); - } - return !f; - } - Tools.DoLater(tick, 50, 100); - break; - case SpecialMoveID.SmokeBomb: - serverUpdate.particles.Add(new ServerUpdate.Particle() { - count = 1000, - spread = 5f, - type = ParticleType.NoGravity, - size = 5f, - velocity = new Resources.Utilities.FloatVector(), - color = new Resources.Utilities.FloatVector() { - x = 1f, - y = 1f, - z = 1f - }, - alpha = 1f, - position = dynamicEntities[specialMove.Guid].position - }); - writeServerUpdate = true; - break; - case SpecialMoveID.IceWave: - if (specialMove.Guid != guid) {//distance small enough - CwRam.Freeze(3000); - } - serverUpdate.particles.Add(new ServerUpdate.Particle() { - count = 100, - spread = 4f, - type = ParticleType.NoGravity, - size = 0.3f, - velocity = new FloatVector(), - color = new FloatVector() { - x = 0f, - y = 1f, - z = 1f - }, - alpha = 1f, - position = dynamicEntities[specialMove.Guid].position - }); - serverUpdate.particles.Add(new ServerUpdate.Particle() { - count = 100, - spread = 10f, - type = ParticleType.NoGravity, - size = 0.1f, - velocity = new FloatVector(), - color = new FloatVector() { - x = 1f, - y = 1f, - z = 1f - }, - alpha = 1f, - position = dynamicEntities[specialMove.Guid].position - }); - serverUpdate.particles.Add(new ServerUpdate.Particle() { - count = 100, - spread = 2f, - type = ParticleType.NoGravity, - size = 0.7f, - velocity = new FloatVector(), - color = new FloatVector() { - x = 0f, - y = 0f, - z = 1f - }, - alpha = 1f, - position = dynamicEntities[specialMove.Guid].position - }); - writeServerUpdate = true; - break; - case SpecialMoveID.Confusion: - break; - case SpecialMoveID.ShadowStep: - break; - default: - break; - } - break; - #endregion - default: - form.Log("unknown datagram ID: " + datagram[0], Color.Red); - break; - } - if (status == BridgeStatus.Playing && writeServerUpdate) SendToClient(serverUpdate); - } - private static void ProcessClientPacket(int packetID) { - switch ((PacketID)packetID) { - case PacketID.EntityUpdate: - #region entityUpdate - var entityUpdate = new EntityUpdate(creader); - if (dynamicEntities.ContainsKey(entityUpdate.guid)) { - entityUpdate.Filter(dynamicEntities[entityUpdate.guid]); - entityUpdate.Merge(dynamicEntities[entityUpdate.guid]); - } - else { - dynamicEntities.Add(entityUpdate.guid, entityUpdate); - } - if (entityUpdate.name != null) { - RefreshPlayerlist(); - } - if (!entityUpdate.IsEmpty) { - SendUDP(entityUpdate.CreateDatagram()); - } - break; - #endregion - case PacketID.EntityAction: - #region entity action - EntityAction entityAction = new EntityAction(creader); - switch (entityAction.type) { - case ActionType.Talk: - #region Talk - break; - #endregion - case ActionType.StaticInteraction: - #region StaticInteraction - ChatMessage x = new ChatMessage() { - message = "You can't use this, your hands are too small.", - sender = 0 - }; - SendToClient(x); - break; - #endregion - case ActionType.PickUp: - #region PickUp - break; - #endregion - case ActionType.Drop: //send item back to dropper because dropping is disabled to prevent chatspam - #region Drop - if (form.radioButtonDestroy.Checked) { - SendToClient(new ChatMessage() { - message = "item destroyed", - sender = 0, - }); - } - else { - var serverUpdate = new ServerUpdate(); - var pickup = new ServerUpdate.Pickup() { - guid = guid, - item = entityAction.item - }; - serverUpdate.pickups.Add(pickup); - if (form.radioButtonDuplicate.Checked) { - serverUpdate.pickups.Add(pickup); - } - SendToClient(serverUpdate); - } - break; - #endregion - case ActionType.CallPet: - #region CallPet - break; - #endregion - default: - //unknown type - break; - } - break; - #endregion - case PacketID.Hit: - #region hit - var hit = new Hit(creader); - var attack = new Attack() { - Target = (ushort)hit.target, - Damage = hit.damage, - Direction = hit.direction, - Stuntime = hit.stuntime, - Skill = hit.isYellow, - Type = hit.type, - ShowLight = hit.showlight, - Critical = hit.critical - }; - SendUDP(attack.data); - lastTarget = attack.Target; - break; - #endregion - case PacketID.PassiveProc: - #region passiveProc - var passiveProc = new PassiveProc(creader); - switch (passiveProc.type) { - case ProcType.Bulwalk: - SendToClient(new ChatMessage() { - message = string.Format("bulwalk: {0}% dmg reduction", 1.0f - passiveProc.modifier), - sender = 0, - }); - break; - case ProcType.WarFrenzy: - CwRam.PlayerEntity.BossBuff = true; - bool DisableBossBuff() { - bool f = status == BridgeStatus.Playing && dynamicEntities[guid].HP > 0; - if (f) { - CwRam.PlayerEntity.BossBuff = false; - } - return !f; - } - Tools.DoLater(DisableBossBuff, passiveProc.duration, 1); - break; - case ProcType.Camouflage: - break; - case ProcType.Poison: - break; - case ProcType.UnknownA: - break; - case ProcType.ManaShield: - SendToClient(new ChatMessage() { - message = string.Format("manashield: {0}", passiveProc.modifier), - sender = 0, - }); - break; - case ProcType.UnknownB: - break; - case ProcType.UnknownC: - break; - case ProcType.FireSpark: - break; - case ProcType.Intuition: - break; - case ProcType.Elusiveness: - break; - case ProcType.Swiftness: - break; - default: - break; - } - var proc = new Proc() { - Target = (ushort)passiveProc.target, - Type = passiveProc.type, - Modifier = passiveProc.modifier, - Duration = passiveProc.duration - }; - SendUDP(proc.data); - break; - #endregion - case PacketID.Shoot: - #region shoot - var shoot = new Shoot(creader); - var projectile = new Projectile() { - Position = shoot.position, - Velocity = shoot.velocity, - Scale = shoot.scale, - Particles = shoot.particles, - Type = shoot.projectile, - Source = (ushort)shoot.attacker, - }; - SendUDP(projectile.data); - break; - #endregion - case PacketID.Chat: - #region chat - var chatMessage = new ChatMessage(creader); - if (chatMessage.message.ToLower() == @"/plane") { - Console.Beep(); - var serverUpdate = new ServerUpdate() { - blockDeltas = new Vox("model.vox").Parse(), - }; - foreach (var block in serverUpdate.blockDeltas) { - block.position.x += 0x802080;//(int)(dynamicEntities[guid].position.x / 0x10000);//8286946; - block.position.y += 0x802080;//(int)(dynamicEntities[guid].position.y / 0x10000);//8344456; - block.position.z += 150;// (int)(dynamicEntities[guid].position.z / 0x10000);//220; - } - SendToClient(serverUpdate); - } - if (chatMessage.message.ToLower() == @"/spawn") { - CwRam.Teleport(new LongVector() { - x = 0x8020800000, - y = 0x8020800000, - z = 0, - }); - break; - } - var chat = new Chat() { - Sender = guid,//client doesn't send this - Text = chatMessage.message - }; - SendUDP(chat.data); - break; - #endregion - case PacketID.Chunk: - #region chunk - var chunk = new Chunk(creader); - break; - #endregion - case PacketID.Sector: - #region sector - var sector = new Sector(creader); - break; - #endregion - case PacketID.Version: - #region version - var version = new ProtocolVersion(creader); - if (version.version != 3) { - version.version = 3; - SendToClient(version); - } - else { - SendToClient(new Join() { - guid = guid, - junk = new byte[0x1168] - }); - SendToClient(new MapSeed() { - seed = mapseed - }); - foreach (var dynamicEntity in dynamicEntities.Values.ToList()) { - SendToClient(dynamicEntity); - } - } - break; - #endregion - default: - form.Log("unknown client packet\n", Color.Magenta); - break; - } - } - private static void ProcessServerPacket(int packetID) { - switch ((ServerPacketID)packetID) { - case ServerPacketID.VersionCheck: - #region VersionCheck - if (!sreader.ReadBoolean()) { - form.Log("mismatch\n", Color.Red); - var b = MessageBox.Show("your bridge is outdated. Please download the newest version.\n\nGo to download page now?", "version mismatch", MessageBoxButtons.YesNo); - if (b == DialogResult.Yes) { - System.Diagnostics.Process.Start("https://github.com/LastExceed/Exceed/releases"); - } - return; - } - form.Log("match\n", Color.Green); - form.Invoke(new Action(() => form.buttonLoginRegister.Enabled = true)); - new Thread(new ThreadStart(ListenFromServerUDP)).Start(); - break; - #endregion - case ServerPacketID.Login: - #region Login - var authResponse = (AuthResponse)sreader.ReadByte(); - if (authResponse == AuthResponse.Success) { - status = BridgeStatus.LoggedIn; - guid = sreader.ReadUInt16(); - mapseed = sreader.ReadInt32(); - } - form.Invoke(new Action(() => form.register.OnLoginResponse(authResponse))); - break; - #endregion - case ServerPacketID.Register: - #region Register - switch ((RegisterResponse)sreader.ReadByte()) { - case RegisterResponse.Success: - form.Log("account registered\n", Color.DarkGray); - form.Invoke(new Action(() => form.register.SetLayout(false))); - break; - case RegisterResponse.UsernameTaken: - MessageBox.Show("this username is already in use"); - form.Invoke(new Action(() => form.register.buttonRegister.Enabled = true)); - break; - case RegisterResponse.EmailTaken: - MessageBox.Show("there is already an account associated to this email"); - form.Invoke(new Action(() => form.register.buttonRegister.Enabled = true)); - break; - } - break; - #endregion - case ServerPacketID.Kick: - #region Kick - status = BridgeStatus.LoggedIn; - tcpToClient.Close(); - break; - #endregion - case ServerPacketID.BTFO: - #region BTFO - status = BridgeStatus.Connected; - form.Invoke(new Action(() => form.OnLogout())); - CwRam.memory.process.Kill(); - var reason = sreader.ReadString(); - MessageBox.Show(reason); - break; - #endregion - default: - MessageBox.Show("unknown server packet received"); - break; - } - } - - public static void RefreshPlayerlist() { - form.Invoke((Action)form.listBoxPlayers.Items.Clear); - foreach (var dynamicEntity in dynamicEntities.Values.ToList()) { - if (dynamicEntity.hostility == Hostility.Player) { - form.Invoke(new Action(() => form.listBoxPlayers.Items.Add(dynamicEntity.name))); - } - } - } - - public static void OnHotkey(HotkeyID hotkey) { - if (CwRam.AnyInterfaceOpen) return; - if (hotkey == HotkeyID.TeleportToTown) { - CwRam.SetMode(Mode.Teleport_To_City, 0); - return; - } - var notification = new ChatMessage() { - sender = 0, - }; - bool spec = dynamicEntities[guid].specialization == 1; - switch (dynamicEntities[guid].entityClass) { - case EntityClass.Rogue when spec: - #region ninja - if (hotkey == HotkeyID.CtrlSpace) { - #region dash - notification.message = "using [dash]"; - CwRam.SetMode(Mode.Spin_Run, 0); - #endregion - } - else { - #region blink - notification.message = "using [blink]"; - if (dynamicEntities.ContainsKey(lastTarget)) { - CwRam.Teleport(dynamicEntities[lastTarget].position); - } - #endregion - } - break; - #endregion - case EntityClass.Rogue: - #region assassin - if (hotkey == HotkeyID.CtrlSpace) { - #region confusion - notification.message = "TODO: [confusion]"; - var specialMove = new SpecialMove() { - Guid = guid, - Id = SpecialMoveID.Confusion, - }; - SendUDP(specialMove.data); - #endregion - } - else { - #region shadow step - notification.message = "TOD: [shadow step]"; - var specialMove = new SpecialMove() { - Guid = guid, - Id = SpecialMoveID.ShadowStep, - }; - SendUDP(specialMove.data); - #endregion - } - break; - #endregion - case EntityClass.Warrior when spec: - #region guardian - if (hotkey == HotkeyID.CtrlSpace) { - #region taunt - notification.message = "using [taunt]"; - var specialMove = new SpecialMove() { - Guid = lastTarget, - Id = SpecialMoveID.Taunt, - }; - SendUDP(specialMove.data); - #endregion - } - else { - #region steel wall - notification.message = "using [steel wall]"; - CwRam.SetMode(Mode.Boss_Skill_Block, 0); - #endregion - } - break; - #endregion - case EntityClass.Warrior: - #region berserk - if (hotkey == HotkeyID.CtrlSpace) { - #region boulder toss - notification.message = "using [boulder toss]"; - CwRam.SetMode(Mode.Boulder_Toss, 0); - #endregion - } - else { - #region earth shatter - notification.message = "using [earth shatter]"; - CwRam.SetMode(Mode.Earth_Shatter, 0); - #endregion - } - break; - #endregion - case EntityClass.Mage when spec: - #region watermage - if (hotkey == HotkeyID.CtrlSpace) { - #region splash - notification.message = "using [splash]"; - CwRam.SetMode(Mode.Splash, 0); - #endregion - } - else { - #region ice wave - notification.message = "using [ice wave]"; - var specialMove = new SpecialMove() { - Guid = guid, - Id = SpecialMoveID.IceWave, - }; - SendUDP(specialMove.data); - #endregion - } - break; - #endregion - case EntityClass.Mage: - #region firemage - if (hotkey == HotkeyID.CtrlSpace) { - #region lava - notification.message = "using [lava]"; - CwRam.SetMode(Mode.Lava, 0); - #endregion - } - else { - #region beam - notification.message = "using [fire ray]"; - CwRam.SetMode(Mode.FireRay, 0); - #endregion - } - break; - #endregion - case EntityClass.Ranger when spec: - #region scout - if (hotkey == HotkeyID.CtrlSpace) { - #region shrapnel - notification.message = "using [shrapnel] bleeding test"; - var specialMove = new SpecialMove() { - Guid = guid, - Id = SpecialMoveID.Shrapnel, - }; - SendUDP(specialMove.data); - #endregion - } - else { - #region smoke bomb - notification.message = "using [smoke bomb]"; - var specialMove = new SpecialMove() { - Guid = guid, - Id = SpecialMoveID.SmokeBomb, - }; - SendUDP(specialMove.data); - - var fakeSmoke = new ServerUpdate(); - fakeSmoke.particles.Add(new ServerUpdate.Particle() { - count = 1000, - spread = 5f, - type = ParticleType.NoGravity, - size = 0.3f, - velocity = new Resources.Utilities.FloatVector(), - color = new Resources.Utilities.FloatVector() { - x = 1f, - y = 1f, - z = 1f - }, - alpha = 1f, - position = dynamicEntities[specialMove.Guid].position - }); - SendToClient(fakeSmoke); - #endregion - } - break; - #endregion - case EntityClass.Ranger: - #region sniper - if (hotkey == HotkeyID.CtrlSpace) { - #region cursed arrow - //TODO - notification.message = "TODO: [cursed arrow]"; - #endregion - } - else { - #region arrow rain - //TODO - notification.message = "TODO: [arrow rain]"; - //const int rainSize = 7; - //for (int x = 0; x < rainSize; x++) { - // for (int y = 0; y < rainSize; y++) { - // var projectile = new Projectile() { - // Scale = 1f, - // Type = ProjectileType.Arrow, - // Source = guid, - // Velocity = new FloatVector() { x = 0, y = 0, z = -1f }, - // Position = new LongVector() { - // x = 0x8020800000,//dynamicEntities[guid].position.x + (long)((dynamicEntities[guid].rayHit.x + x - rainSize / 2) * 0x10000), - // y = 0x8020800000,//dynamicEntities[guid].position.y + (long)((dynamicEntities[guid].rayHit.y + y - rainSize / 2) * 0x10000), - // z = 0x01000000,//dynamicEntities[guid].position.z + (long)((dynamicEntities[guid].rayHit.z + 10) * 0x10000), - // } - // }; - // SendUDP(projectile.data); - // ProcessDatagram(projectile.data); - // } - //} - #endregion - } - break; - #endregion - } - CwRam.memory.WriteInt(CwRam.EntityStart + 0x1164, 3);//mana cubes - SendToClient(notification); - } - - private static void SendUDP(byte[] data) { - udpToServer.Send(data, data.Length); - } - private static void SendToClient(Packet packet) { - outgoingMutex.WaitOne(); - try { - packet.Write(cwriter); - } - catch (IOException) { - //handled in reading thread - } - outgoingMutex.ReleaseMutex(); - } - } -} diff --git a/Bridge/Extensions/ExtensionsCore.cs b/Bridge/Extensions/ExtensionsCore.cs new file mode 100644 index 0000000..db7fda9 --- /dev/null +++ b/Bridge/Extensions/ExtensionsCore.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Bridge.Extensions { + public static class ExtensionsCore { + public static void Init() { + Logging.Init(); + NameSubjectToChange.Init(); + SpecialMoves.Init(); + } + } +} diff --git a/Bridge/Extensions/Logging.cs b/Bridge/Extensions/Logging.cs new file mode 100644 index 0000000..ed91b3e --- /dev/null +++ b/Bridge/Extensions/Logging.cs @@ -0,0 +1,57 @@ +using Resources; +using Resources.Datagram; +using Resources.Packet; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Bridge.Extensions { + public static class Logging { + public static void Init() { + BridgeCore.ChatMessageReceived += ChatLog; + BridgeCore.ClientConnected += LogConnect; + BridgeCore.ClientDisconnected += LogDisconnect; + + BridgeCore.EntityUpdateReceived += CheckForNewName; + BridgeCore.EntityUpdateSent += CheckForNewName; + BridgeCore.DynamicEntityRemoved += RemoveNameFromList; + BridgeCore.ClientDisconnected += RefreshPlayerlist; + } + + private static void ChatLog(Chat chat) { + if (chat.Sender == 0) { + BridgeCore.form.Log(chat.Text + "\n", Color.Magenta); + } + else { + BridgeCore.form.Log(BridgeCore.dynamicEntities[chat.Sender].name + ": ", Color.Cyan); + BridgeCore.form.Log(chat.Text + "\n", Color.White); + } + } + private static void LogConnect() { + BridgeCore.form.Log("client connected\n", Color.Green); + } + private static void LogDisconnect() { + BridgeCore.form.Log("client disconnected\n", Color.Red); + } + + private static void CheckForNewName(EntityUpdate entityUpdate) { + if (entityUpdate.name != null) { + RefreshPlayerlist(); + } + } + private static void RemoveNameFromList(RemoveDynamicEntity rde) { + RefreshPlayerlist(); + } + private static void RefreshPlayerlist() { + BridgeCore.form.Invoke((Action)BridgeCore.form.listBoxPlayers.Items.Clear); + foreach (var dynamicEntity in BridgeCore.dynamicEntities.Values.ToList()) { + if (dynamicEntity.hostility == Hostility.Player) { + BridgeCore.form.Invoke(new Action(() => BridgeCore.form.listBoxPlayers.Items.Add(dynamicEntity.name))); + } + } + } + } +} diff --git a/Bridge/Extensions/NameSubjectToChange.cs b/Bridge/Extensions/NameSubjectToChange.cs new file mode 100644 index 0000000..daef5f1 --- /dev/null +++ b/Bridge/Extensions/NameSubjectToChange.cs @@ -0,0 +1,167 @@ +using Resources; +using Resources.Datagram; +using Resources.Packet; +using Resources.Utilities; +using System; +using System.Drawing; +using System.Linq; + +namespace Bridge.Extensions { + public static class NameSubjectToChange { + public static void Init() { + BridgeCore.EntityUpdateReceived += Teleport; + BridgeCore.AttackReceived += Knockback; + BridgeCore.PassiveProcReceived += Poison; + BridgeCore.EntityActionSent += HandleEntityAction; + } + + private static void Teleport(EntityUpdate entityUpdate) { + if (BridgeCore.status == BridgeStatus.Playing) { + if (entityUpdate.guid == BridgeCore.guid) { + CwRam.Teleport(entityUpdate.position); + //todo: prevent packet from being sent to client + } + } + } + private static void Knockback(Attack attack) { + CwRam.Knockback(attack.Direction); + } + private static void Poison(Proc proc) { + if (proc.Type == ProcType.Poison && proc.Target == BridgeCore.guid) { + var su = new ServerUpdate(); + su.hits.Add(new Hit() { + damage = proc.Modifier, + target = BridgeCore.guid, + position = BridgeCore.dynamicEntities[BridgeCore.guid].position, + }); + bool tick() { + bool f = BridgeCore.status == BridgeStatus.Playing && BridgeCore.dynamicEntities[BridgeCore.guid].HP > 0; + if (f) { + BridgeCore.SendToClient(su); + } + return !f; + } + Tools.DoLater(tick, 500, 7); + } + } + + private static void HandleEntityAction(EntityAction entityAction) { + switch (entityAction.type) { + case ActionType.Talk: + #region Talk + break; + #endregion + case ActionType.StaticInteraction: + #region StaticInteraction + ChatMessage x = new ChatMessage() { + message = "static interation is disabled", + sender = 0 + }; + BridgeCore.SendToClient(x); + break; + #endregion + case ActionType.PickUp: + #region PickUp + break; + #endregion + case ActionType.Drop: //send item back to dropper because dropping is disabled to prevent chatspam + #region Drop + if (BridgeCore.form.radioButtonDestroy.Checked) { + BridgeCore.SendToClient(new ChatMessage() { + message = "item destroyed", + sender = 0, + }); + } + else { + var serverUpdate = new ServerUpdate(); + var pickup = new ServerUpdate.Pickup() { + guid = BridgeCore.guid, + item = entityAction.item + }; + serverUpdate.pickups.Add(pickup); + if (BridgeCore.form.radioButtonDuplicate.Checked) { + serverUpdate.pickups.Add(pickup); + } + BridgeCore.SendToClient(serverUpdate); + } + break; + #endregion + case ActionType.CallPet: + #region CallPet + break; + #endregion + default: + //unknown type + break; + } + } + private static void HandlePassiveProc(PassiveProc passiveProc) { + switch (passiveProc.type) { + case ProcType.Bulwalk: + BridgeCore.SendToClient(new ChatMessage() { + message = string.Format("bulwalk: {0}% dmg reduction", 1.0f - passiveProc.modifier), + sender = 0, + }); + break; + case ProcType.WarFrenzy: + CwRam.PlayerEntity.BossBuff = true; + bool DisableBossBuff() { + bool f = BridgeCore.status == BridgeStatus.Playing && BridgeCore.dynamicEntities[BridgeCore.guid].HP > 0; + if (f) { + CwRam.PlayerEntity.BossBuff = false; + } + return !f; + } + Tools.DoLater(DisableBossBuff, passiveProc.duration, 1); + break; + case ProcType.Camouflage: + break; + case ProcType.Poison: + break; + case ProcType.UnknownA: + break; + case ProcType.ManaShield: + BridgeCore.SendToClient(new ChatMessage() { + message = string.Format("manashield: {0}", passiveProc.modifier), + sender = 0, + }); + break; + case ProcType.UnknownB: + break; + case ProcType.UnknownC: + break; + case ProcType.FireSpark: + break; + case ProcType.Intuition: + break; + case ProcType.Elusiveness: + break; + case ProcType.Swiftness: + break; + default: + break; + } + } + private static void ClientSideChatCommands(ChatMessage chatMessage) { + if (chatMessage.message.ToLower() == @"/plane") { + Console.Beep(); + var serverUpdate = new ServerUpdate() { + blockDeltas = new Vox("model.vox").Parse(), + }; + foreach (var block in serverUpdate.blockDeltas) { + block.position.x += 0x802080;//(int)(dynamicEntities[guid].position.x / 0x10000);//8286946; + block.position.y += 0x802080;//(int)(dynamicEntities[guid].position.y / 0x10000);//8344456; + block.position.z += 150;// (int)(dynamicEntities[guid].position.z / 0x10000);//220; + } + BridgeCore.SendToClient(serverUpdate); + } + if (chatMessage.message.ToLower() == @"/spawn") { + CwRam.Teleport(new LongVector() { + x = 0x8020800000, + y = 0x8020800000, + z = 0, + }); + } + } + } +} diff --git a/Bridge/Extensions/SpecialMoves.cs b/Bridge/Extensions/SpecialMoves.cs new file mode 100644 index 0000000..0da06c8 --- /dev/null +++ b/Bridge/Extensions/SpecialMoves.cs @@ -0,0 +1,375 @@ +using Resources; +using Resources.Datagram; +using Resources.Packet; +using Resources.Utilities; +using System.Windows.Forms; + +namespace Bridge.Extensions { + public static class SpecialMoves { + private static ushort lastTarget; + + public static void Init() { + BridgeCore.HitSent += RememberLastTarget; + BridgeCore.SpecialMoveReceived += HandleSpecialMoves; + BridgeCore.ClientConnected += EnableHotkeys; + BridgeCore.ClientDisconnected += DisableHotkeys; + } + + private static void RememberLastTarget(Hit hit) { + lastTarget = (ushort)hit.target; + } + private static void HandleSpecialMoves(SpecialMove specialMove) { + switch (specialMove.Id) { + case SpecialMoveID.Taunt: + if (BridgeCore.dynamicEntities.ContainsKey(specialMove.Guid)) { + if (BridgeCore.status == BridgeStatus.Playing) { + CwRam.Teleport(BridgeCore.dynamicEntities[specialMove.Guid].position); + CwRam.Freeze(5000); + } + } + break; + case SpecialMoveID.CursedArrow: + break; + case SpecialMoveID.ArrowRain: + break; + case SpecialMoveID.Shrapnel: + var su = new ServerUpdate(); + var blood_hit = new Hit() { + damage = 5f, + target = specialMove.Guid, + }; + su.hits.Add(blood_hit); + var blood_particles = new ServerUpdate.Particle() { + count = 10, + spread = 2f, + type = ParticleType.Normal, + size = 0.1f, + velocity = new FloatVector() { + z = 1f, + }, + color = new FloatVector() { + x = 1f, + y = 0f, + z = 0f + }, + alpha = 1f, + }; + su.particles.Add(blood_particles); + bool tick() { + bool f = BridgeCore.status == BridgeStatus.Playing && BridgeCore.dynamicEntities[specialMove.Guid].HP > 0; + if (f) { + blood_hit.position = blood_particles.position = BridgeCore.dynamicEntities[specialMove.Guid].position; + BridgeCore.SendToClient(su); + } + return !f; + } + Tools.DoLater(tick, 50, 100); + break; + case SpecialMoveID.SmokeBomb: + var serverUpdate = new ServerUpdate(); + serverUpdate.particles.Add(new ServerUpdate.Particle() { + count = 1000, + spread = 5f, + type = ParticleType.NoGravity, + size = 5f, + velocity = new FloatVector(), + color = new FloatVector() { + x = 1f, + y = 1f, + z = 1f + }, + alpha = 1f, + position = BridgeCore.dynamicEntities[specialMove.Guid].position + }); + if (BridgeCore.status == BridgeStatus.Playing) { + BridgeCore.SendToClient(serverUpdate); + } + break; + case SpecialMoveID.IceWave: + if (specialMove.Guid != BridgeCore.guid) {//distance small enough + CwRam.Freeze(3000); + } + serverUpdate = new ServerUpdate(); + serverUpdate.particles.Add(new ServerUpdate.Particle() { + count = 100, + spread = 4f, + type = ParticleType.NoGravity, + size = 0.3f, + velocity = new FloatVector(), + color = new FloatVector() { + x = 0f, + y = 1f, + z = 1f + }, + alpha = 1f, + position = BridgeCore.dynamicEntities[specialMove.Guid].position + }); + serverUpdate.particles.Add(new ServerUpdate.Particle() { + count = 100, + spread = 10f, + type = ParticleType.NoGravity, + size = 0.1f, + velocity = new FloatVector(), + color = new FloatVector() { + x = 1f, + y = 1f, + z = 1f + }, + alpha = 1f, + position = BridgeCore.dynamicEntities[specialMove.Guid].position + }); + serverUpdate.particles.Add(new ServerUpdate.Particle() { + count = 100, + spread = 2f, + type = ParticleType.NoGravity, + size = 0.7f, + velocity = new FloatVector(), + color = new FloatVector() { + x = 0f, + y = 0f, + z = 1f + }, + alpha = 1f, + position = BridgeCore.dynamicEntities[specialMove.Guid].position + }); + if (BridgeCore.status == BridgeStatus.Playing) { + BridgeCore.SendToClient(serverUpdate); + } + break; + case SpecialMoveID.Confusion: + break; + case SpecialMoveID.ShadowStep: + break; + default: + break; + } + } + + private static void EnableHotkeys() { + BridgeCore.form.keyboardHook.KeyboardChanged += OnKeyboardChanged; + } + private static void DisableHotkeys() { + BridgeCore.form.keyboardHook.KeyboardChanged -= OnKeyboardChanged; + } + private static void OnKeyboardChanged(Keys key, bool isDown) { + switch (key) { + case Keys.D4 when isDown: + OnHotkey(HotkeyID.CtrlSpace); + break; + case Keys.D5 when isDown: + OnHotkey(HotkeyID.SpecialMove2); + break; + case Keys.D0 when isDown: + OnHotkey(HotkeyID.TeleportToTown); + break; + default: + break; + } + } + private static void OnHotkey(HotkeyID hotkey) { + if (CwRam.AnyInterfaceOpen) return; + + if (hotkey == HotkeyID.TeleportToTown) { + CwRam.SetMode(Mode.Teleport_To_City, 0); + return; + } + var notification = new ChatMessage() { + sender = 0, + }; + bool spec = BridgeCore.dynamicEntities[BridgeCore.guid].specialization == 1; + switch (BridgeCore.dynamicEntities[BridgeCore.guid].entityClass) { + case EntityClass.Rogue when spec: + #region ninja + if (hotkey == HotkeyID.CtrlSpace) { + #region dash + notification.message = "using [dash]"; + CwRam.SetMode(Mode.Spin_Run, 0); + #endregion + } + else { + #region blink + notification.message = "using [blink]"; + if (BridgeCore.dynamicEntities.ContainsKey(lastTarget)) { + CwRam.Teleport(BridgeCore.dynamicEntities[lastTarget].position); + } + #endregion + } + break; + #endregion + case EntityClass.Rogue: + #region assassin + if (hotkey == HotkeyID.CtrlSpace) { + #region confusion + notification.message = "TODO: [confusion]"; + var specialMove = new SpecialMove() { + Guid = BridgeCore.guid, + Id = SpecialMoveID.Confusion, + }; + BridgeCore.SendUDP(specialMove.data); + #endregion + } + else { + #region shadow step + notification.message = "TOD: [shadow step]"; + var specialMove = new SpecialMove() { + Guid = BridgeCore.guid, + Id = SpecialMoveID.ShadowStep, + }; + BridgeCore.SendUDP(specialMove.data); + #endregion + } + break; + #endregion + case EntityClass.Warrior when spec: + #region guardian + if (hotkey == HotkeyID.CtrlSpace) { + #region taunt + notification.message = "using [taunt]"; + var specialMove = new SpecialMove() { + Guid = lastTarget, + Id = SpecialMoveID.Taunt, + }; + BridgeCore.SendUDP(specialMove.data); + #endregion + } + else { + #region steel wall + notification.message = "using [steel wall]"; + CwRam.SetMode(Mode.Boss_Skill_Block, 0); + #endregion + } + break; + #endregion + case EntityClass.Warrior: + #region berserk + if (hotkey == HotkeyID.CtrlSpace) { + #region boulder toss + notification.message = "using [boulder toss]"; + CwRam.SetMode(Mode.Boulder_Toss, 0); + #endregion + } + else { + #region earth shatter + notification.message = "using [earth shatter]"; + CwRam.SetMode(Mode.Earth_Shatter, 0); + #endregion + } + break; + #endregion + case EntityClass.Mage when spec: + #region watermage + if (hotkey == HotkeyID.CtrlSpace) { + #region splash + notification.message = "using [splash]"; + CwRam.SetMode(Mode.Splash, 0); + #endregion + } + else { + #region ice wave + notification.message = "using [ice wave]"; + var specialMove = new SpecialMove() { + Guid = BridgeCore.guid, + Id = SpecialMoveID.IceWave, + }; + BridgeCore.SendUDP(specialMove.data); + #endregion + } + break; + #endregion + case EntityClass.Mage: + #region firemage + if (hotkey == HotkeyID.CtrlSpace) { + #region lava + notification.message = "using [lava]"; + CwRam.SetMode(Mode.Lava, 0); + #endregion + } + else { + #region beam + notification.message = "using [fire ray]"; + CwRam.SetMode(Mode.FireRay, 0); + #endregion + } + break; + #endregion + case EntityClass.Ranger when spec: + #region scout + if (hotkey == HotkeyID.CtrlSpace) { + #region shrapnel + notification.message = "using [shrapnel] bleeding test"; + var specialMove = new SpecialMove() { + Guid = BridgeCore.guid, + Id = SpecialMoveID.Shrapnel, + }; + BridgeCore.SendUDP(specialMove.data); + #endregion + } + else { + #region smoke bomb + notification.message = "using [smoke bomb]"; + var specialMove = new SpecialMove() { + Guid = BridgeCore.guid, + Id = SpecialMoveID.SmokeBomb, + }; + BridgeCore.SendUDP(specialMove.data); + + var fakeSmoke = new ServerUpdate(); + fakeSmoke.particles.Add(new ServerUpdate.Particle() { + count = 1000, + spread = 5f, + type = ParticleType.NoGravity, + size = 0.3f, + velocity = new Resources.Utilities.FloatVector(), + color = new Resources.Utilities.FloatVector() { + x = 1f, + y = 1f, + z = 1f + }, + alpha = 1f, + position = BridgeCore.dynamicEntities[specialMove.Guid].position + }); + BridgeCore.SendToClient(fakeSmoke); + #endregion + } + break; + #endregion + case EntityClass.Ranger: + #region sniper + if (hotkey == HotkeyID.CtrlSpace) { + #region cursed arrow + //TODO + notification.message = "TODO: [cursed arrow]"; + #endregion + } + else { + #region arrow rain + //TODO + notification.message = "TODO: [arrow rain]"; + //const int rainSize = 7; + //for (int x = 0; x < rainSize; x++) { + // for (int y = 0; y < rainSize; y++) { + // var projectile = new Projectile() { + // Scale = 1f, + // Type = ProjectileType.Arrow, + // Source = guid, + // Velocity = new FloatVector() { x = 0, y = 0, z = -1f }, + // Position = new LongVector() { + // x = 0x8020800000,//dynamicEntities[guid].position.x + (long)((dynamicEntities[guid].rayHit.x + x - rainSize / 2) * 0x10000), + // y = 0x8020800000,//dynamicEntities[guid].position.y + (long)((dynamicEntities[guid].rayHit.y + y - rainSize / 2) * 0x10000), + // z = 0x01000000,//dynamicEntities[guid].position.z + (long)((dynamicEntities[guid].rayHit.z + 10) * 0x10000), + // } + // }; + // SendUDP(projectile.data); + // ProcessDatagram(projectile.data); + // } + //} + #endregion + } + break; + #endregion + } + CwRam.memory.WriteInt(CwRam.EntityStart + 0x1164, 3);//mana cubes + BridgeCore.SendToClient(notification); + } + } +} diff --git a/Bridge/FormMain.cs b/Bridge/FormMain.cs index f0d29c4..f2f611d 100644 --- a/Bridge/FormMain.cs +++ b/Bridge/FormMain.cs @@ -1,4 +1,5 @@ -using ReadWriteProcessMemory; +using Bridge.Extensions; +using ReadWriteProcessMemory; using System; using System.Diagnostics; using System.Drawing; @@ -12,8 +13,8 @@ public partial class FormMain : Form { public FormRegister register = new FormRegister(); public FormChat chat = new FormChat(); public FormRankings rankings = new FormRankings(); + public KeyboardHook keyboardHook; private bool processAttached = false; - private KeyboardHook keyboardHook; public FormMain(string[]args) { InitializeComponent(); @@ -22,11 +23,12 @@ private void FormMain_Shown(object sender, EventArgs e) { chat.Show(); chat.Top = this.Top; chat.Left = Left + Width; - BridgeTCPUDP.form = this; + BridgeCore.form = this; CwRam.formMain = this; - new Thread(BridgeTCPUDP.ListenFromClientTCP).Start(); - new Thread(BridgeTCPUDP.Connect).Start(); keyboardHook = new KeyboardHook(); + new Thread(BridgeCore.ListenFromClientTCP).Start(); + new Thread(BridgeCore.Connect).Start(); + ExtensionsCore.Init(); } private void timerSearchProcess_Tick(object sender, EventArgs e) { if (processAttached) { @@ -39,7 +41,7 @@ private void timerSearchProcess_Tick(object sender, EventArgs e) { } else { CwRam.RemoveFog(); - if (BridgeTCPUDP.status == Resources.BridgeStatus.Playing) CwRam.SetName(linkLabelUser.Text); + if (BridgeCore.status == Resources.BridgeStatus.Playing) CwRam.SetName(linkLabelUser.Text); } } else { @@ -108,7 +110,7 @@ private void buttonLoginRegister_Click(object sender, EventArgs e) { } private void contextMenuStripUser_ItemClicked(object sender, ToolStripItemClickedEventArgs e) { - BridgeTCPUDP.Logout(); + BridgeCore.Logout(); OnLogout(); } public void OnLogout() { diff --git a/Bridge/FormMap.cs b/Bridge/FormMap.cs index 4ebc3ae..2b66665 100644 --- a/Bridge/FormMap.cs +++ b/Bridge/FormMap.cs @@ -13,7 +13,7 @@ public FormMap() { private void Refreshtimer_Tick(object sender, EventArgs e) { Controls.Clear(); playercollection.Clear(); - foreach (var entity in BridgeTCPUDP.dynamicEntities.Values.ToList()) { + foreach (var entity in BridgeCore.dynamicEntities.Values.ToList()) { if (entity.hostility == Resources.Hostility.Player) { Label playerlabel = new Label { Left = (int)entity.position.x / 0x10000, diff --git a/Bridge/FormRegister.cs b/Bridge/FormRegister.cs index aaff13a..6ca0d7f 100644 --- a/Bridge/FormRegister.cs +++ b/Bridge/FormRegister.cs @@ -41,7 +41,7 @@ private void textBoxEmail_TextChanged(object sender, EventArgs e) { private void buttonRegister_Click(object sender, EventArgs e) { buttonRegister.Enabled = false; - BridgeTCPUDP.Register(textBoxUsername.Text, textBoxEmail.Text, textBoxPassword.Text); + BridgeCore.Register(textBoxUsername.Text, textBoxEmail.Text, textBoxPassword.Text); } private void buttonCreate_Click(object sender, EventArgs e) { @@ -77,39 +77,39 @@ private void buttonLogin_Click(object sender, EventArgs e) { textBoxUsername.ReadOnly = true; textBoxEmail.ReadOnly = true; textBoxPassword.ReadOnly = true; - BridgeTCPUDP.Login(textBoxUsername.Text, textBoxPassword.Text); + BridgeCore.Login(textBoxUsername.Text, textBoxPassword.Text); } public void OnLoginResponse(AuthResponse authResponse) { switch (authResponse) { case AuthResponse.Success: - BridgeTCPUDP.form.Log("success\n", Color.Green); - BridgeTCPUDP.form.buttonLoginRegister.Visible = false; - BridgeTCPUDP.form.linkLabelUser.Text = textBoxUsername.Text; - BridgeTCPUDP.form.linkLabelUser.Visible = true; - BridgeTCPUDP.form.buttonClan.Enabled = true; - BridgeTCPUDP.form.buttonClan.Visible = false; - BridgeTCPUDP.form.linkLabelClan.Text = "Prisoners of Irreality"; - BridgeTCPUDP.form.linkLabelClan.Visible = true; + BridgeCore.form.Log("success\n", Color.Green); + BridgeCore.form.buttonLoginRegister.Visible = false; + BridgeCore.form.linkLabelUser.Text = textBoxUsername.Text; + BridgeCore.form.linkLabelUser.Visible = true; + BridgeCore.form.buttonClan.Enabled = true; + BridgeCore.form.buttonClan.Visible = false; + BridgeCore.form.linkLabelClan.Text = "Prisoners of Irreality"; + BridgeCore.form.linkLabelClan.Visible = true; this.Close(); break; case AuthResponse.UnknownUser: - BridgeTCPUDP.form.Log("username does not exist\n", Color.Red); + BridgeCore.form.Log("username does not exist\n", Color.Red); goto default; case AuthResponse.WrongPassword: - BridgeTCPUDP.form.Log("wrong password\n", Color.Red); + BridgeCore.form.Log("wrong password\n", Color.Red); goto default; case AuthResponse.Banned: - BridgeTCPUDP.form.Log("you are banned\n", Color.Red); + BridgeCore.form.Log("you are banned\n", Color.Red); goto default; case AuthResponse.AccountAlreadyActive: - BridgeTCPUDP.form.Log("account already in use\n", Color.Red); + BridgeCore.form.Log("account already in use\n", Color.Red); goto default; case AuthResponse.Unverified: - BridgeTCPUDP.form.Log("unverified (this shouldnt happen)\n", Color.Red); + BridgeCore.form.Log("unverified (this shouldnt happen)\n", Color.Red); goto default; case AuthResponse.UserAlreadyLoggedIn: - BridgeTCPUDP.form.Log("you are already logged in (this shouldn't happen)\n", Color.Red); + BridgeCore.form.Log("you are already logged in (this shouldn't happen)\n", Color.Red); goto default; default: buttonLogin.Enabled = true; diff --git a/Bridge/KeyboardHook.cs b/Bridge/KeyboardHook.cs index 7d3fcdb..c7b4d9c 100644 --- a/Bridge/KeyboardHook.cs +++ b/Bridge/KeyboardHook.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Runtime.InteropServices; -using System.Text; using System.Threading.Tasks; using System.Windows.Forms; @@ -92,25 +90,12 @@ private int KeybHookProc(int Code, int W, int L) { private Dictionary keyboardState = new Dictionary(); private void OnKey(Keys key, bool isDown) { if (GetForegroundWindow() != CwRam.memory.process.MainWindowHandle) return; - if (!keyboardState.ContainsKey(key)) keyboardState.Add(key, !isDown); if (keyboardState[key] == isDown) return; keyboardState[key] = isDown; - - if (!isDown) return;//temp fix to prevent activation on release - switch (key) { - case Keys.D4: - BridgeTCPUDP.OnHotkey(Resources.HotkeyID.CtrlSpace); - break; - case Keys.D5: - BridgeTCPUDP.OnHotkey(Resources.HotkeyID.SpecialMove2); - break; - case Keys.D0: - BridgeTCPUDP.OnHotkey(Resources.HotkeyID.TeleportToTown); - break; - default: - break; - } + KeyboardChanged?.Invoke(key, isDown); } + public delegate void KeyboardChangedEventHandler(Keys key, bool isDown); + public event KeyboardChangedEventHandler KeyboardChanged; } } diff --git a/Resources/Config.cs b/Resources/Config.cs index 3bc30fb..eb03c47 100644 --- a/Resources/Config.cs +++ b/Resources/Config.cs @@ -2,7 +2,7 @@ static public class Config { public const int mapseed = 8710; //hardcoded for now public const int bridgeVersion = 16; - public const string serverIP = "pb97.ddns.net";//temp + public const string serverIP = "localhost";//temp public const int serverPort = 12346; } } diff --git a/Server/Addon/ZoxModel.cs b/Resources/ZoxModel.cs similarity index 98% rename from Server/Addon/ZoxModel.cs rename to Resources/ZoxModel.cs index b2c3f28..ed311ea 100644 --- a/Server/Addon/ZoxModel.cs +++ b/Resources/ZoxModel.cs @@ -3,7 +3,7 @@ using Resources.Utilities; using Resources.Packet; -namespace Server.Addon { +namespace Resources { class ZoxModel { public string Creator { get; set; } public byte Height { get; set; } diff --git a/Server/Addon/AntiCheat.cs b/Server/Extensions/AntiCheat.cs similarity index 68% rename from Server/Addon/AntiCheat.cs rename to Server/Extensions/AntiCheat.cs index d3c707c..a09b08e 100644 --- a/Server/Addon/AntiCheat.cs +++ b/Server/Extensions/AntiCheat.cs @@ -1,17 +1,31 @@ using Resources; using Resources.Packet; +using System; +using System.Collections.Generic; +using System.Text; -namespace Server.Addon { - class AntiCheat { - public static string Inspect(EntityUpdate current, EntityUpdate previous) { +namespace Server.Extensions { + public static class AntiCheat { + public static void Init() { + ServerCore.EntityUpdated += That; + } + + private static void That(EntityUpdate entityUpdate, Player player) { + string ACmessage = Inspect(entityUpdate, player.entity); + if (ACmessage != null) { + ServerCore.Kick(player, ACmessage); + } + } + + private static string Inspect(EntityUpdate current, EntityUpdate previous) { if (current.guid != previous.guid) { return "guid"; } if (current.hostility != null && current.hostility != 0) { return "hostility"; } - if(current.appearance != null) { - if((current.appearance.character_size.x != 0.8000000119f && current.appearance.character_size.x != 0.9600000381f && current.appearance.character_size.x != 1.039999962f) || + if (current.appearance != null) { + if ((current.appearance.character_size.x != 0.8000000119f && current.appearance.character_size.x != 0.9600000381f && current.appearance.character_size.x != 1.039999962f) || (current.appearance.character_size.y != 0.8000000119f && current.appearance.character_size.y != 0.9600000381f && current.appearance.character_size.y != 1.039999962f) || (current.appearance.character_size.z != 1.799999952f && current.appearance.character_size.z != 2.160000086f && current.appearance.character_size.z != 2.339999914f) || (current.appearance.head_size != 0.8999999762f && current.appearance.head_size != 1.00999999f) || @@ -26,13 +40,13 @@ public static string Inspect(EntityUpdate current, EntityUpdate previous) { return "appearance"; } } - if(current.charge != null && current.charge > 1){ //(current.MP ?? previous.MP)) { + if (current.charge != null && current.charge > 1) { //(current.MP ?? previous.MP)) { return "MP charge"; } - if(current.HP != null && current.HP > 3333) { + if (current.HP != null && current.HP > 3333) { return "HP"; } - if(current.MP != null && current.MP > 1) { + if (current.MP != null && current.MP > 1) { return "MP"; } //if(current.multipliers != null) { @@ -44,27 +58,27 @@ public static string Inspect(EntityUpdate current, EntityUpdate previous) { // return "multipliers"; // } //} - if(current.level != null && current.level > 500) { + if (current.level != null && current.level > 500) { return "level"; } - if(current.consumable != null) { - if(current.consumable.type == ItemType.Food && + if (current.consumable != null) { + if (current.consumable.type == ItemType.Food && current.consumable.subtype == 1 || current.consumable.level > 647 || current.consumable.rarity != 0) { //return "consumable"; } } - if(current.equipment != null) { - foreach(Item item in current.equipment) { - if(item.type != 0 && + if (current.equipment != null) { + foreach (Item item in current.equipment) { + if (item.type != 0 && (item.level > 647 || (byte)item.rarity > 4)) { return "equipment"; } } } - if(current.skillDistribution != null) { - if(current.skillDistribution.petmaster + + if (current.skillDistribution != null) { + if (current.skillDistribution.petmaster + current.skillDistribution.petriding + current.skillDistribution.sailing + current.skillDistribution.climbing + @@ -82,4 +96,3 @@ public static string Inspect(EntityUpdate current, EntityUpdate previous) { } } } -//block diff --git a/Server/Extensions/Balancing.cs b/Server/Extensions/Balancing.cs new file mode 100644 index 0000000..11a01be --- /dev/null +++ b/Server/Extensions/Balancing.cs @@ -0,0 +1,34 @@ +using Resources; +using Resources.Datagram; + +namespace Server.Extensions { + public static class Balancing { + public static void Init() { + ServerCore.EntityAttacked += CutDmgInHalf; + } + + private static void CutDmgInHalf(Attack attack, Player player) { + if (attack.Damage > 0) { + attack.Damage *= 0.5f; //dmg + } + //else { + // attack.Damage *= 0.333333f; //heal + //} + var target = ServerCore.dynamicEntities[attack.Target]; + var dmgReductionByShield = 0f; + if (target.equipment[(int)Equipment.LeftWeapon].subtype == (byte)ItemSubtypeWeapon.Shield) { + dmgReductionByShield += 0.25f; + } + if (target.equipment[(int)Equipment.RightWeapon].subtype == (byte)ItemSubtypeWeapon.Shield) { + dmgReductionByShield += 0.25f; + } + if (attack.Damage > 0) { + attack.Damage *= 1 - dmgReductionByShield; + } + Log.PrintLn(player.entity.name + " " + attack.Target + " " + attack.Damage + " " + attack.Critical, System.ConsoleColor.Magenta); + if (attack.Target == player.entity.guid) {//players can't damage themselves. this prevents double self heals since selfheal is already applied locally + attack.Damage = 0; + } + } + } +} diff --git a/Server/Extensions/ChatMessage.cs b/Server/Extensions/ChatMessage.cs new file mode 100644 index 0000000..5dcfb31 --- /dev/null +++ b/Server/Extensions/ChatMessage.cs @@ -0,0 +1,25 @@ +using Resources; +using Resources.Datagram; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Server.Extensions +{ + public static class ChatMessage + { + public static void Init() + { + ServerCore.ChatMessageReceived += BroadcastMessage; + } + public static void Desactivate() + { + ServerCore.ChatMessageReceived -= BroadcastMessage; + } + private static void BroadcastMessage(Chat chat, Player source) + { + ServerCore.BroadcastUDP(chat.data, null); + } + + } +} diff --git a/Server/Extensions/Extensions.cs b/Server/Extensions/Extensions.cs new file mode 100644 index 0000000..141f95e --- /dev/null +++ b/Server/Extensions/Extensions.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Server.Extensions { + public static class Extensions { + public static void Init() { + AntiCheat.Init(); + Pvp.Init(); + ChatMessage.Init(); + SpecialMoves.Init(); + Balancing.Init(); + } + } +} diff --git a/Server/Extensions/Models.cs b/Server/Extensions/Models.cs new file mode 100644 index 0000000..51e9357 --- /dev/null +++ b/Server/Extensions/Models.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Server.Extensions { + public static class Models { + public static void Init() { + + } + + private static void Placeholder() { + #region models + //var rnd = new Random(); + //for (int i = 8286946; i < 8286946 + 512; i++) { + // for (int j = 8344456; j < 8344456 + 512; j++) { + // var block = new ServerUpdate.BlockDelta() { + // color = new Resources.Utilities.ByteVector() { + // x = 0, + // y = 0, + // z = (byte)rnd.Next(0, 255), + // }, + // type = BlockType.solid, + // position = new Resources.Utilities.IntVector() { + // x = i, + // y = j, + // z = 208, + // }, + // }; + // worldUpdate.blockDeltas.Add(block); + // } + //} + //x = 543093329157, + //y = 546862296355, + //z = 14423162 + //ZoxModel model = JsonConvert.DeserializeObject(File.ReadAllText("models/Fulcnix_exceedspawn.zox")); + //model.Parse(worldUpdate, 8286883, 8344394, 200); + //model = JsonConvert.DeserializeObject(File.ReadAllText("models/Aster_Tavern2.zox")); + //model.Parse(worldUpdate, 8287010, 8344432, 200); + //model = JsonConvert.DeserializeObject(File.ReadAllText("models/Aster_Tavern1.zox")); + //model.Parse(worldUpdate, 8286919, 8344315, 212); + //model = JsonConvert.DeserializeObject(File.ReadAllText("models/arena/aster_arena.zox")); + //model.Parse(worldUpdate, 8286775, 8344392, 207); + //model = JsonConvert.DeserializeObject(File.ReadAllText("models/michael_project1.zox")); + //model.Parse(worldUpdate, 8286898, 8344375, 213); + //model = JsonConvert.DeserializeObject(File.ReadAllText("models/arena/fulcnix_hall.zox")); + //model.Parse(worldUpdate, 8286885, 8344505, 208); + //model = JsonConvert.DeserializeObject(File.ReadAllText("models/arena/fulcnix_hall.zox")); + //model.Parse(worldUpdate, 8286885, 8344629, 208); + //model = JsonConvert.DeserializeObject(File.ReadAllText("models/Tiecz_MountainArena.zox")); + //model.Parse(worldUpdate, 8286885, 8344759, 208); + ////8397006, 8396937, 127 //near spawn + //model = JsonConvert.DeserializeObject(File.ReadAllText("models/Aster_CloudyDay11.zox")); + //model.Parse(worldUpdate, 8286770, 8344262, 207); + //model = JsonConvert.DeserializeObject(File.ReadAllText("models/Aster_CloudyDay12.zox")); + //model.Parse(worldUpdate, 8286770, 8344136, 207); + //model = JsonConvert.DeserializeObject(File.ReadAllText("models/Aster_CloudyDay13.zox")); + //model.Parse(worldUpdate, 8286770, 8344010, 207); + //model = JsonConvert.DeserializeObject(File.ReadAllText("models/Aster_CloudyDay14.zox")); + //model.Parse(worldUpdate, 8286770, 8344010, 333); + //model = JsonConvert.DeserializeObject(File.ReadAllText("models/Aster_CloudyDay01.zox")); + //model.Parse(worldUpdate, 8286644, 8344010, 333); + //model = JsonConvert.DeserializeObject(File.ReadAllText("models/Aster_CloudyDay02.zox")); + //model.Parse(worldUpdate, 8286118, 8344010, 333); + //model = JsonConvert.DeserializeObject(File.ReadAllText("models/Aster_CloudyDay03.zox")); + //model.Parse(worldUpdate, 8285992, 8344010, 333); + //model = JsonConvert.DeserializeObject(File.ReadAllText("models/Aster_CloudyDay04.zox")); + //model.Parse(worldUpdate, 8285992, 8344136, 333); + //model = JsonConvert.DeserializeObject(File.ReadAllText("models/Aster_CloudyDay05.zox")); + //model.Parse(worldUpdate, 8285992, 8344262, 333); + //model = JsonConvert.DeserializeObject(File.ReadAllText("models/Aster_CloudyDay06.zox")); + //model.Parse(worldUpdate, 8286118, 8344262, 333); + //model = JsonConvert.DeserializeObject(File.ReadAllText("models/Aster_CloudyDay07.zox")); + //model.Parse(worldUpdate, 8286118, 8344136, 333); + //model = JsonConvert.DeserializeObject(File.ReadAllText("models/Aster_CloudyDay08.zox")); + //model.Parse(worldUpdate, 8286244, 8344136, 333); + //model = JsonConvert.DeserializeObject(File.ReadAllText("models/Aster_CloudyDay09.zox")); + //model.Parse(worldUpdate, 8286244, 8344262, 333); + //model = JsonConvert.DeserializeObject(File.ReadAllText("models/Aster_CloudyDay10.zox")); + //model.Parse(worldUpdate, 8286770, 8344262, 333); + #endregion + } + } +} diff --git a/Server/Extensions/Pvp.cs b/Server/Extensions/Pvp.cs new file mode 100644 index 0000000..fe9a664 --- /dev/null +++ b/Server/Extensions/Pvp.cs @@ -0,0 +1,17 @@ +using Resources; +using Resources.Packet; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Server.Extensions { + public static class Pvp { + public static void Init() { + ServerCore.EntityUpdated += EnableFriendlyFireFlag; + } + + private static void EnableFriendlyFireFlag(EntityUpdate entityUpdate, Player player) { + entityUpdate.entityFlags |= 1 << 5; + } + } +} diff --git a/Server/Extensions/SpecialMoves.cs b/Server/Extensions/SpecialMoves.cs new file mode 100644 index 0000000..b57e9de --- /dev/null +++ b/Server/Extensions/SpecialMoves.cs @@ -0,0 +1,40 @@ +using Resources; +using Resources.Datagram; +using Resources.Packet; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Server.Extensions { + public static class SpecialMoves { + public static void Init() { + ServerCore.SpecialMoveUsed += OnSpecialMove; + } + + private static void OnSpecialMove(SpecialMove specialMove, Player source) { + switch (specialMove.Id) { + case SpecialMoveID.Taunt: + var target = ServerCore.players.FirstOrDefault(p => p.entity.guid == specialMove.Guid); + if (target != null) { + specialMove.Guid = (ushort)source.entity.guid; + ServerCore.SendUDP(specialMove.data, target); + } + break; + case SpecialMoveID.SmokeBomb: + ServerCore.BroadcastUDP(specialMove.data, source); + break; + case SpecialMoveID.CursedArrow: + case SpecialMoveID.ArrowRain: + case SpecialMoveID.Shrapnel: + case SpecialMoveID.IceWave: + case SpecialMoveID.Confusion: + case SpecialMoveID.ShadowStep: + ServerCore.BroadcastUDP(specialMove.data); + break; + default: + break; + } + } + } +} diff --git a/Server/Extensions/Tombstones.cs b/Server/Extensions/Tombstones.cs new file mode 100644 index 0000000..bb7bad5 --- /dev/null +++ b/Server/Extensions/Tombstones.cs @@ -0,0 +1,45 @@ +using Resources; +using Resources.Datagram; +using Resources.Packet; +using Resources.Utilities; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Server.Extensions { + public static class Tombstones { + public static void Init() { + ServerCore.EntityUpdated += SpawnTomb; + } + + private static void SpawnTomb(EntityUpdate entityUpdate, Player player) { + if (entityUpdate.HP <= 0 && (player.entity.HP > 0 || player.entity.HP == null)) { + var tombstone = new EntityUpdate() { + guid = ServerCore.AssignGuid(), + position = entityUpdate.position ?? player.entity.position, + hostility = Hostility.Neutral, + entityType = EntityType.None, + appearance = new EntityUpdate.Appearance() { + character_size = new FloatVector() { + x = 1, + y = 1, + z = 1, + }, + head_model = 2155, + head_size = 1 + }, + HP = 100, + name = "tombstone" + }; + player.tomb = (ushort)tombstone.guid; + ServerCore.BroadcastUDP(tombstone.CreateDatagram()); + } + else if (player.entity.HP <= 0 && entityUpdate.HP > 0 && player.tomb != null) { + var rde = new RemoveDynamicEntity() { + Guid = (ushort)player.tomb, + }; + ServerCore.BroadcastUDP(rde.data); + } + } + } +} diff --git a/Server/Plugins/Arena/ArenaConfig.cs b/Server/Plugins/Arena/ArenaConfig.cs new file mode 100644 index 0000000..9ae0861 --- /dev/null +++ b/Server/Plugins/Arena/ArenaConfig.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; + +namespace Server.Plugins.Arena +{ + static public class ArenaConfig + { + public const string pluginName = "Arena"; + public static List pluginDependencies = new List{ + "BaseServer" + }; + } +} diff --git a/Server/Plugins/Arena/ArenaCore.cs b/Server/Plugins/Arena/ArenaCore.cs new file mode 100644 index 0000000..8791145 --- /dev/null +++ b/Server/Plugins/Arena/ArenaCore.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.EntityFrameworkCore; +using Resources; +using Server.Plugins.Arena.Database; +using Server.Plugins.Arena.Resources; + +namespace Server.Plugins.Arena +{ + class ArenaCore : PluginBase + { + public ArenaDatabase ArenaDatabase; + CommandsBase command; + public ArenaCore() + { + ArenaDatabase = new ArenaDatabase(); + ArenaDatabase.Database.Migrate(); + command = new Commands(this); + pluginName = ArenaConfig.pluginName; + } + public override Boolean hasCommands() + { + return true; + } + public override Boolean analyzeCommand(string message, Player source) + { + return command.ParseAsCommand(message, source); + } + public override List checkDependencies() + { + return ArenaConfig.pluginDependencies; + } + } +} diff --git a/Server/Plugins/Arena/Database/ArenaDatabase.cs b/Server/Plugins/Arena/Database/ArenaDatabase.cs new file mode 100644 index 0000000..52bf8d4 --- /dev/null +++ b/Server/Plugins/Arena/Database/ArenaDatabase.cs @@ -0,0 +1,173 @@ +using Microsoft.EntityFrameworkCore; +using Server.Plugins.Arena.Resources; +using Resources; +using System.Linq; +using System.IO; +using System; + +namespace Server.Plugins.Arena.Database +{ + public class ArenaDatabase : DbContext + { + const string dbFileName = "dbArena.sqlite"; + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder.UseSqlite($"Data Source={dbFileName}"); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + { + var arenas = modelBuilder.Entity(); + arenas.HasKey(x => x.Id); + arenas.Property(x => x.Name).IsRequired(); + } + } + public DbSet Arenas { get; set; } + public ArenaEntity setupArena; + public ArenaEntity FindArena(uint id = 0) + { + var arena = Arenas.SingleOrDefault(x => x.ArenaId == id); + return arena; + } + public ArenaEntity FetchRandomArena() + { + int range = Arenas.Count(); + Random rnd = new Random(); + int id = rnd.Next(0, range); + var arena = Arenas.SingleOrDefault(x => x.ArenaId == id); + return arena; + } + public ArenaResponse ResetArena() + { + Arenas.RemoveRange(Arenas.ToList()); + SaveChanges(); + return ArenaResponse.ArenaReset; + } + public ArenaResponse launchSetupArena(string name = null) + { + if (setupArena == null) + { + if (name == null) + { + name = "arena" + Arenas.Count().ToString(); + } + setupArena = new ArenaEntity((uint)Arenas.Count(), name); + return ArenaResponse.SetupLaunched; + } + return ArenaResponse.SetupAlreadyLaunched; + } + public ArenaResponse setNameArena(string arenaName = null) + { + if (setupArena != null) + { + if (arenaName == null) + { + arenaName = "arena" + Arenas.Count().ToString(); + } + setupArena.Name = arenaName; + return ArenaResponse.SetupNameSet; + } + return ArenaResponse.SetupEmpty; + } + public ArenaResponse setPositionSetupArena(long[] position, ArenaPositionName arenaPositionName) + { + if (setupArena != null) + { + switch (arenaPositionName) + { + case ArenaPositionName.player1: + setupArena.SpawnPosition1 = encodePosition(position); + break; + case ArenaPositionName.player2: + setupArena.SpawnPosition2 = encodePosition(position); + break; + case ArenaPositionName.spectator: + setupArena.SpawnPosition3 = encodePosition(position); + break; + } + return ArenaResponse.SetupPositionSet; + } + return ArenaResponse.SetupEmpty; + } + public ArenaResponse SaveDuelArena() + { + if (setupArena == null) + { + return ArenaResponse.SetupEmpty; + } + if (isValid(setupArena)) + { + Arenas.Add(setupArena); + SaveChanges(); + setupArena = null; + return ArenaResponse.ArenaCreated; + } + return ArenaResponse.SetupIncomplete; + } + + public ArenaResponse RemoveDuelArena(uint id = 0) + { + var arena = Arenas.SingleOrDefault(x => x.ArenaId == id); + if (arena == null) + { + return ArenaResponse.ArenaNotFound; + } + Arenas.Remove(arena); + SaveChanges(); + + return ArenaResponse.ArenaDeleted; + } + public ArenaResponse RemoveDuelArena(string name = null) + { + var arena = Arenas.SingleOrDefault(x => x.Name == name); + if (arena == null) + { + return ArenaResponse.ArenaNotFound; + } + Arenas.Remove(arena); + SaveChanges(); + return ArenaResponse.ArenaDeleted; + } + public void listArena() + { + var arenas = Arenas.ToList(); + foreach (ArenaEntity arena in arenas) + { + //Console.WriteLine(String.Format("Arena {0} : Name -> {1} , X -> {2}, Y -> {3}, Z -> {4}", arena.ArenaId, arena.Name, arena.X, arena.Y, arena.Z)); + } + } + public Boolean isValid(ArenaEntity arena) + { + if (arena.Name != null && arena.SpawnPosition1 != null && arena.SpawnPosition2 != null && arena.SpawnPosition3 != null) + { + if (arena.ArenaId == 0 && Arenas.Count() > 0) + { + return false; + } + else + { + return true; + } + } + return false; + } + public static byte[] encodePosition(long[] position) + { + byte[] encodedPosition = new byte[8 * position.Length]; + Array.Copy(BitConverter.GetBytes(position[0]), 0, encodedPosition, 0, 8); + Array.Copy(BitConverter.GetBytes(position[1]), 0, encodedPosition, 8, 8); + Array.Copy(BitConverter.GetBytes(position[2]), 0, encodedPosition, 16, 8); + return encodedPosition; + } + public static long[] decodePosition(byte[] encodedPosition) + { + long[] decodedPosition = new long[3]; + decodedPosition[0] = BitConverter.ToInt64(encodedPosition, 0); + decodedPosition[1] = BitConverter.ToInt64(encodedPosition, 8); + decodedPosition[2] = BitConverter.ToInt64(encodedPosition, 16); + return decodedPosition; + } + } +} diff --git a/Server/Plugins/Arena/Database/ArenaEntity.cs b/Server/Plugins/Arena/Database/ArenaEntity.cs new file mode 100644 index 0000000..5e3def3 --- /dev/null +++ b/Server/Plugins/Arena/Database/ArenaEntity.cs @@ -0,0 +1,43 @@ +using Server.Plugins.Arena.Resources; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Server.Plugins.Arena.Database +{ + public class ArenaEntity + { + public uint Id { get; set; } + public uint ArenaId { get; set; } + public string Name { get; set; } + public byte[] SpawnPosition1 { get; set; } + public byte[] SpawnPosition2 { get; set; } + public byte[] SpawnPosition3 { get; set; } + + public ArenaEntity() + { + } + public ArenaEntity(uint ArenaId, string Name) + { + this.ArenaId = ArenaId; + this.Name = Name; + } + public long[] getPosition(ArenaPositionName arenaPositionName) + { + var position = ArenaDatabase.decodePosition(SpawnPosition1); + switch (arenaPositionName) + { + case ArenaPositionName.player1: + position = ArenaDatabase.decodePosition(SpawnPosition1); + break; + case ArenaPositionName.player2: + position = ArenaDatabase.decodePosition(SpawnPosition2); + break; + case ArenaPositionName.spectator: + position = ArenaDatabase.decodePosition(SpawnPosition3); + break; + } + return position; + } + } +} diff --git a/Server/Plugins/Arena/Migrations/ArenaDatabaseModelSnapshot.cs b/Server/Plugins/Arena/Migrations/ArenaDatabaseModelSnapshot.cs new file mode 100644 index 0000000..e7255a8 --- /dev/null +++ b/Server/Plugins/Arena/Migrations/ArenaDatabaseModelSnapshot.cs @@ -0,0 +1,44 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using Server.Database; +using Server.Plugins.Arena.Database; +using System; + +namespace Server.Plugins.Arena.Migrations +{ + [DbContext(typeof(ArenaDatabase))] + partial class ArenaDatabaseModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.2-rtm-10011"); + + modelBuilder.Entity("Server.Database.ArenaEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Name") + .IsRequired(); + + b.Property("SpawnPosition1"); + + b.Property("SpawnPosition2"); + + b.Property("SpawnPosition3"); + + b.HasKey("Id"); + + b.ToTable("Arenas"); + }); + +#pragma warning restore 612, 618 + } + } +} diff --git a/Server/Plugins/Arena/Migrations/arena_initialCreate.Designer.cs b/Server/Plugins/Arena/Migrations/arena_initialCreate.Designer.cs new file mode 100644 index 0000000..37e502e --- /dev/null +++ b/Server/Plugins/Arena/Migrations/arena_initialCreate.Designer.cs @@ -0,0 +1,45 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using Server.Database; +using System; +using Server.Plugins.Arena.Database; +namespace Server.Plugins.Arena.Migrations +{ + [DbContext(typeof(ArenaDatabase))] + [Migration("Arena_initialCreate")] + partial class initialArenaCreate + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.2-rtm-10011"); + + modelBuilder.Entity("Server.Database.ArenaEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ArenaId"); + + b.Property("Name") + .IsRequired(); + + b.Property("SpawnPosition1"); + + b.Property("SpawnPosition2"); + + b.Property("SpawnPosition3"); + + b.HasKey("Id"); + + b.ToTable("Arenas"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Server/Plugins/Arena/Migrations/arena_initialCreate.cs b/Server/Plugins/Arena/Migrations/arena_initialCreate.cs new file mode 100644 index 0000000..fb86047 --- /dev/null +++ b/Server/Plugins/Arena/Migrations/arena_initialCreate.cs @@ -0,0 +1,35 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System; +using System.Collections.Generic; + +namespace Server.Plugins.Arena.Migrations +{ + public partial class initialArenaCreate : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Arenas", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ArenaId = table.Column(nullable: false), + Name = table.Column(nullable: false), + SpawnPosition1 = table.Column(nullable: false), // Spawn position of Player 1 + SpawnPosition2 = table.Column(nullable: false), // Spawn position of Player 2 + SpawnPosition3 = table.Column(nullable: false) // Spawn position of Spectator + }, + constraints: table => + { + table.PrimaryKey("PK_Arenas", x => x.Id); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Arenas"); + } + } +} diff --git a/Server/Plugins/Arena/Resources/Commands.cs b/Server/Plugins/Arena/Resources/Commands.cs new file mode 100644 index 0000000..d8db78e --- /dev/null +++ b/Server/Plugins/Arena/Resources/Commands.cs @@ -0,0 +1,233 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Resources; +using Server.Plugins.Arena.Database; +using Server.Plugins.Arena.Resources; +namespace Server.Plugins.Arena.Resources +{ + class Commands : CommandsBase + { + public Commands(PluginBase plugin) : base(plugin) + { + } + public override Boolean ParseAsCommand(string message, Player source) + { + message = message.ToLower(); + var commandName = message.Substring(1).Split(" ")[0]; + if (!(commandName == pluginRef.getName().ToLower())) + { + return false; + } + if (message.Length == 1 + pluginRef.getName().Length) + { + ServerCore.Notify(source, CommandsMessages.getMessage(CommandsMessages.baseSyntax)); + return true; + } + var parameters = message.Substring(2 + pluginRef.getName().Length).Split(" "); + ArenaResponse response = ArenaResponse.Null; + long[] currentPos = new long[3]; + switch (parameters[0]) + { + case "setup": + #region setup + if (source.entity.name != "BLACKROCK" && source.entity.name != "BLIZZY") + { + ServerCore.Notify(source, CommandsMessages.getMessage(CommandsBaseMessages.baseNoPermission)); + break; + } + string arenaName = null; + if (parameters.Length == 2) + { + arenaName = parameters[1]; + } + else if (parameters.Length > 2) + { + ServerCore.Notify(source, CommandsMessages.getMessage(CommandsMessages.setupSyntax)); + break; + } + response = ((ArenaCore)pluginRef).ArenaDatabase.launchSetupArena(arenaName); + if (response == ArenaResponse.SetupLaunched) + { + ServerCore.Notify(source, CommandsMessages.getMessage(CommandsMessages.setupInitialized)); + } + else if (response == ArenaResponse.SetupAlreadyLaunched) + { + ServerCore.Notify(source, CommandsMessages.getMessage(CommandsMessages.setupAlreadyInitialized)); + } + break; + #endregion + case "set": + #region setup-set + if (source.entity.name != "BLACKROCK" && source.entity.name != "BLIZZY") + { + ServerCore.Notify(source, CommandsMessages.getMessage(CommandsBaseMessages.baseNoPermission)); + break; + } + if (parameters.Length == 2) + { + currentPos = new long[3] + { + source.entity.position.x, + source.entity.position.y, + source.entity.position.z + }; + switch (parameters[1]) + { + case "player1": + response = ((ArenaCore)pluginRef).ArenaDatabase.setPositionSetupArena(currentPos, ArenaPositionName.player1); + break; + case "player2": + response = ((ArenaCore)pluginRef).ArenaDatabase.setPositionSetupArena(currentPos, ArenaPositionName.player2); + break; + case "spectator": + response = ((ArenaCore)pluginRef).ArenaDatabase.setPositionSetupArena(currentPos, ArenaPositionName.spectator); + break; + default: + ServerCore.Notify(source, CommandsMessages.getMessage(CommandsMessages.setSyntax)); + break; + } + if (response == ArenaResponse.SetupPositionSet) + { + ServerCore.Notify(source, CommandsMessages.getMessage(string.Format(CommandsMessages.setPositionSuccess, parameters[1]))); + } + else if (response == ArenaResponse.SetupEmpty) + { + ServerCore.Notify(source, CommandsMessages.getMessage(CommandsMessages.baseSetupNotInitialized)); + } + } + else + { + ServerCore.Notify(source, CommandsMessages.getMessage(CommandsMessages.setSyntax)); + } + break; + #endregion + case "name": + #region setup-name + if (source.entity.name != "BLACKROCK" && source.entity.name != "BLIZZY") + { + ServerCore.Notify(source, CommandsMessages.getMessage(CommandsBaseMessages.baseNoPermission)); + break; + } + if (parameters.Length == 2) + { + response = ((ArenaCore)pluginRef).ArenaDatabase.setNameArena(parameters[1]); + if (response == ArenaResponse.SetupNameSet) + { + ServerCore.Notify(source, CommandsMessages.getMessage(CommandsMessages.nameChangeSuccess)); + } + else if (response == ArenaResponse.SetupEmpty) + { + ServerCore.Notify(source, CommandsMessages.getMessage(CommandsMessages.baseSetupNotInitialized)); + } + } + else + { + ServerCore.Notify(source, CommandsMessages.getMessage(CommandsMessages.createSyntax)); + } + break; + #endregion + case "create": + #region arena-create + if (source.entity.name != "BLACKROCK" && source.entity.name != "BLIZZY") + { + ServerCore.Notify(source, CommandsMessages.getMessage(CommandsBaseMessages.baseNoPermission)); + break; + } + if (parameters.Length > 1) + { + ServerCore.Notify(source, CommandsMessages.getMessage(CommandsMessages.createSyntax)); + break; + } + response = ((ArenaCore)pluginRef).ArenaDatabase.SaveDuelArena(); + if (response == ArenaResponse.ArenaCreated) + { + ServerCore.Notify(source, CommandsMessages.getMessage(CommandsMessages.createSuccess)); + } + else if (response == ArenaResponse.SetupIncomplete) + { + ServerCore.Notify(source, CommandsMessages.getMessage(CommandsMessages.createIncomplete)); + } + else if (response == ArenaResponse.SetupEmpty) + { + ServerCore.Notify(source, CommandsMessages.getMessage(CommandsMessages.baseSetupNotInitialized)); + } + break; + #endregion + case "delete": + #region arena-delete + if (source.entity.name != "BLACKROCK" && source.entity.name != "BLIZZY") + { + ServerCore.Notify(source, CommandsMessages.getMessage(CommandsBaseMessages.baseNoPermission)); + break; + } + if (parameters.Length == 2) + { + bool isId = int.TryParse(parameters[1], out int n); + if (isId) + { + var id = Int32.Parse(parameters[1]); + response = ((ArenaCore)pluginRef).ArenaDatabase.RemoveDuelArena((uint)id); + } + else + { + response = ((ArenaCore)pluginRef).ArenaDatabase.RemoveDuelArena(parameters[1]); + } + switch (response) + { + case ArenaResponse.ArenaDeleted: + ServerCore.Notify(source, CommandsMessages.getMessage(CommandsMessages.deleteSuccess)); + break; + case ArenaResponse.ArenaNotFound: + ServerCore.Notify(source, CommandsMessages.getMessage(CommandsMessages.deleteNotFound)); + break; + } + } + else + { + ServerCore.Notify(source, CommandsMessages.getMessage(CommandsMessages.deleteSyntax)); + } + break; + #endregion + case "reset": + #region reset + if (source.entity.name != "BLACKROCK" && source.entity.name != "BLIZZY") + { + ServerCore.Notify(source, CommandsMessages.getMessage(CommandsBaseMessages.baseNoPermission)); + break; + } + if (parameters.Length > 1) + { + ServerCore.Notify(source, CommandsMessages.getMessage(CommandsMessages.resetSyntax)); + break; + } + response = ((ArenaCore)pluginRef).ArenaDatabase.ResetArena(); + if (response == ArenaResponse.ArenaReset) + { + ServerCore.Notify(source, CommandsMessages.getMessage(CommandsMessages.resetSuccess)); + } + #endregion + break; + case "help": + #region arena-help + if (source.entity.name != "BLACKROCK" && source.entity.name != "BLIZZY") + { + ServerCore.Notify(source, CommandsMessages.getMessage(CommandsBaseMessages.baseNoPermission)); + break; + } + ServerCore.Notify(source, CommandsMessages.getMessage(CommandsMessages.setupSyntax)); + ServerCore.Notify(source, CommandsMessages.getMessage(CommandsMessages.setSyntax)); + ServerCore.Notify(source, CommandsMessages.getMessage(CommandsMessages.nameSyntax)); + ServerCore.Notify(source, CommandsMessages.getMessage(CommandsMessages.createSyntax)); + ServerCore.Notify(source, CommandsMessages.getMessage(CommandsMessages.deleteSyntax)); + ServerCore.Notify(source, CommandsMessages.getMessage(CommandsMessages.resetSyntax)); + break; + #endregion + default: + return false; + } + return true; + } + } +} diff --git a/Server/Plugins/Arena/Resources/CommandsMessages.cs b/Server/Plugins/Arena/Resources/CommandsMessages.cs new file mode 100644 index 0000000..5b1f08e --- /dev/null +++ b/Server/Plugins/Arena/Resources/CommandsMessages.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Server.Plugins.Arena.Resources +{ + static class CommandsMessages + { + // Base Command + public static readonly string baseSyntax = "Type '/arena help' for further informations about this plugin"; + public static readonly string baseSetupNotInitialized = "The Setup Arena hasn't been initialized"; + + // Setup + public static readonly string setupSyntax = "Syntax: /arena setup"; + public static readonly string setupInitialized = "The Setup Arena has been initialized !"; + public static readonly string setupAlreadyInitialized = "The Setup Arena has already been initialized."; + + // Set + public static readonly string setSyntax = "Syntax : /arena set player1|player2|spectator"; + public static readonly string setPositionSuccess = "Parameters {0} has been successfully updated"; + + // Name + public static readonly string nameSyntax = "Syntax : /arena name [newName]"; + public static readonly string nameChangeSuccess = "The Setup Arena's name has been changed !"; + + // Create + public static readonly string createSyntax = "Syntax : /arena create"; + public static readonly string createSuccess = "Arena has been successfully saved and is operational !"; + public static readonly string createIncomplete = "Setup Arena is incomplete !"; + + // Delete + public static readonly string deleteSyntax = "Syntax : /duel arena-delete [id] or /duel arena-delete [name] "; + public static readonly string deleteSuccess = " Arena deleted !"; + public static readonly string deleteNotFound = "Invalid id or arena's name"; + + // Reset + public static readonly string resetSyntax= "Syntax : /arena reset"; + public static readonly string resetSuccess = "Arena's list reset !"; + + public static string getMessage(String message) + { + return String.Format("[{0}] " + message, ArenaConfig.pluginName); + } + } +} diff --git a/Server/Plugins/Arena/Resources/Enum.cs b/Server/Plugins/Arena/Resources/Enum.cs new file mode 100644 index 0000000..4881771 --- /dev/null +++ b/Server/Plugins/Arena/Resources/Enum.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Server.Plugins.Arena.Resources +{ + public enum ArenaResponse : byte + { + Null, + SetupLaunched, + SetupAlreadyLaunched, + SetupEmpty, + SetupNameSet, + SetupPositionSet, + SetupIncomplete, + ArenaCreated, + ArenaDeleted, + ArenaNotFound, + ArenaListEmpty, + ArenaReset + } + public enum ArenaPositionName : byte + { + player1, + player2, + spectator + } +} diff --git a/Server/Plugins/BaseServer/BaseServerConfig.cs b/Server/Plugins/BaseServer/BaseServerConfig.cs new file mode 100644 index 0000000..8e1393d --- /dev/null +++ b/Server/Plugins/BaseServer/BaseServerConfig.cs @@ -0,0 +1,7 @@ +namespace Server.Plugins.BaseServer +{ + static public class BaseServerConfig + { + public const string pluginName = "BaseServer"; + } +} diff --git a/Server/Plugins/BaseServer/BaseServerCore.cs b/Server/Plugins/BaseServer/BaseServerCore.cs new file mode 100644 index 0000000..a7a68b5 --- /dev/null +++ b/Server/Plugins/BaseServer/BaseServerCore.cs @@ -0,0 +1,25 @@ +using System; +using Resources; +using Server.Plugins.BaseServer.Resources; +using Server.Plugins.BaseServer.Scripts; +namespace Server.Plugins.BaseServer +{ + class BaseServerCore : PluginBase + { + CommandsBase command; + public BaseServerCore() + { + command = new Commands(this); + ChatManager chatManager = new ChatManager(); + pluginName = BaseServerConfig.pluginName; + } + public override Boolean hasCommands() + { + return true; + } + public override Boolean analyzeCommand(string message,Player source) + { + return command.ParseAsCommand(message, source); + } + } +} diff --git a/Server/Plugins/BaseServer/Resources/Commands.cs b/Server/Plugins/BaseServer/Resources/Commands.cs new file mode 100644 index 0000000..7965c68 --- /dev/null +++ b/Server/Plugins/BaseServer/Resources/Commands.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Resources; +using Resources.Datagram; + +namespace Server.Plugins.BaseServer.Resources +{ + class Commands : CommandsBase + { + public Commands(PluginBase plugin) : base(plugin) + { + } + public override Boolean ParseAsCommand(string message, Player source) + { + var parameters = message.Substring(1).Split(" "); + var command = parameters[0].ToLower(); + switch (command) + { + case "kick": + case "btfo": + case "ban": + #region ban + if (source.entity.name != "BLACKROCK") + { + ServerCore.Notify(source, "no permission"); + break; + } + if (parameters.Length == 1) + { + ServerCore.Notify(source, string.Format("usage example: /kick blackrock")); + break; + } + var target = ServerCore.players.FirstOrDefault(x => x.entity.name.Contains(parameters[1])); + if (target == null) + { + ServerCore.Notify(source, "invalid target"); + break; + }; + var reason = "no reason specified"; + if (parameters.Length > 2) + { + reason = parameters[2]; + } + if (command == "kick") + { + ServerCore.Kick(target, reason); + break; + } + target.writer.Write((byte)ServerPacketID.BTFO); + target.writer.Write(reason); + if (command == "ban") + { + ServerCore.userDatabase.BanUser(target.entity.name, (int)target.IP.Address, target.MAC, reason); + } + ServerCore.RemovePlayerEntity(target, false); + break; + #endregion + case "bleeding": + break; + case "time": + #region time + if (parameters.Length == 1) + { + ServerCore.Notify(source, string.Format("usage example: /time 12:00")); + break; + } + var clock = parameters[1].Split(":"); + if (clock.Length < 2 || + !int.TryParse(clock[0], out int hour) || + !int.TryParse(clock[1], out int minute)) + { + ServerCore.Notify(source, string.Format("invalid syntax")); + break; + } + var inGameTime = new InGameTime() + { + Milliseconds = (hour * 60 + minute) * 60000, + }; + ServerCore.SendUDP(inGameTime.data, source); + break; + #endregion + default: + return false; + } + return true; + } + } +} diff --git a/Server/Plugins/BaseServer/Scripts/ChatManager.cs b/Server/Plugins/BaseServer/Scripts/ChatManager.cs new file mode 100644 index 0000000..cf522e1 --- /dev/null +++ b/Server/Plugins/BaseServer/Scripts/ChatManager.cs @@ -0,0 +1,39 @@ +using System; +using Resources; +using Resources.Datagram; + +namespace Server.Plugins.BaseServer.Scripts +{ + public class ChatManager + { + public ChatManager() + { + ServerCore.ChatMessageReceived += analyzeMessage; + } + private static void analyzeMessage(Chat chat, Player source) + { + var message = chat.Text; + Log.Print(ServerCore.dynamicEntities[chat.Sender].name + ": ", ConsoleColor.Cyan); + Log.PrintLn(chat.Text, ConsoleColor.White, false); + if (!message.StartsWith("/")) + { + ServerCore.BroadcastUDP(chat.data, null); //pass to all players + return; + } + Boolean CommandFound = false; + foreach (PluginBase plugin in PluginsCore.pluginsWithCommands) + { + CommandFound = plugin.analyzeCommand(message, source); + if (CommandFound == true) + { + break; + } + } + if (!CommandFound) + { + var commandName = message.Substring(1).Split(" ")[0]; + ServerCore.Notify(source, CommandsBaseMessages.getMessage(String.Format(CommandsBaseMessages.baseUnknowCommand, commandName))); + } + } + } +} diff --git a/Server/Plugins/CommandsBase.cs b/Server/Plugins/CommandsBase.cs new file mode 100644 index 0000000..1b13f11 --- /dev/null +++ b/Server/Plugins/CommandsBase.cs @@ -0,0 +1,20 @@ +using Resources; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Server.Plugins +{ + abstract class CommandsBase + { + public PluginBase pluginRef; + protected CommandsBase(PluginBase plugin) + { + pluginRef = plugin; + } + public virtual Boolean ParseAsCommand(string message,Player source) + { + return false; + } + } +} diff --git a/Server/Plugins/CommandsBaseMessages.cs b/Server/Plugins/CommandsBaseMessages.cs new file mode 100644 index 0000000..ab707a9 --- /dev/null +++ b/Server/Plugins/CommandsBaseMessages.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Server.Plugins +{ + class CommandsBaseMessages + { + public static readonly string baseUnknowCommand = "Unknown command : '{0}'"; + public static readonly string baseNoPermission = "You don't have sufficient permission for this command"; + public static string getMessage(String message) + { + return String.Format("[{0}] " + message, "Server"); + } + } +} diff --git a/Server/Plugins/Duel/DuelConfig.cs b/Server/Plugins/Duel/DuelConfig.cs new file mode 100644 index 0000000..7c1dc4b --- /dev/null +++ b/Server/Plugins/Duel/DuelConfig.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Server.Plugins.Duel +{ + static public class DuelConfig + { + public const string pluginName = "Duel"; + public static List pluginDependencies = new List{ + "BaseServer", + "Arena" + }; + public const int maxRequestTime = 30; + } +} diff --git a/Server/Plugins/Duel/DuelCore.cs b/Server/Plugins/Duel/DuelCore.cs new file mode 100644 index 0000000..f928b2f --- /dev/null +++ b/Server/Plugins/Duel/DuelCore.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using Resources; + +namespace Server.Plugins.Duel +{ + class DuelCore : PluginBase + { + public volatile List duels = new List(); + public List players = new List(); + CommandsBase command; + public DuelCore() + { + command = new Commands(this); + pluginName = "Duel"; + } + public override Boolean hasCommands() + { + return true; + } + public override Boolean analyzeCommand(string message, Player source) + { + return command.ParseAsCommand(message, source); + } + public override List checkDependencies() + { + return DuelConfig.pluginDependencies; + } + } +} diff --git a/Server/Plugins/Duel/Resources/Commands.cs b/Server/Plugins/Duel/Resources/Commands.cs new file mode 100644 index 0000000..035653c --- /dev/null +++ b/Server/Plugins/Duel/Resources/Commands.cs @@ -0,0 +1,156 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using Resources; +using Server.Plugins.Duel.Resources; +using CommandsBaseMessages = Server.Plugins.CommandsBaseMessages; +namespace Server.Plugins.Duel +{ + class Commands : CommandsBase + { + + public Commands(PluginBase plugin) : base(plugin) + { + } + public override Boolean ParseAsCommand(string message, Player sourceTemp) + { + PlayerDuel target; + PlayerDuel source = ((DuelCore)pluginRef).players.FirstOrDefault(x => x.entity.guid == sourceTemp.entity.guid); + DuelSystem duelFinder; + message = message.ToLower(); + var commandName = message.Substring(1).Split(" ")[0]; + if (!(commandName == pluginRef.getName().ToLower())) + { + return false; + } + if (message.Length == 1 + pluginRef.getName().Length) + { + ServerCore.Notify(source, CommandsMessages.getMessage(CommandsMessages.baseSyntax)); + return true; + } + var parameters = message.Substring(2 + pluginRef.getName().Length).Split(" "); + switch (parameters[0]) + { + case "help": + #region help + ServerCore.Notify(source, CommandsMessages.getMessage(CommandsMessages.startSyntax)); + ServerCore.Notify(source, CommandsMessages.getMessage(CommandsMessages.acceptSyntax)); + ServerCore.Notify(source, CommandsMessages.getMessage(CommandsMessages.refuseSyntax)); + ServerCore.Notify(source, CommandsMessages.getMessage(CommandsMessages.stopSyntax)); + ServerCore.Notify(source, CommandsMessages.getMessage(CommandsMessages.spectateSyntax)); + break; + #endregion + case "stop": + #region stop + duelFinder = ((DuelCore)pluginRef).duels.LastOrDefault(x => x.player1.entity.name.Contains(source.entity.name) || x.player2.entity.name.Contains(source.entity.name)); + if (duelFinder != null) + { + duelFinder.Stop(); + } + else + { + ServerCore.Notify(source, CommandsMessages.getMessage(CommandsMessages.baseNoDuelRequestFound)); + } + break; + #endregion + case "start": + #region start + if (parameters.Length == 3) + { + target = ((DuelCore)pluginRef).players.FirstOrDefault(x => x.entity.name.Contains(parameters[1])); + if (target == null) + { + ServerCore.Notify(source, CommandsMessages.getMessage(CommandsMessages.baseInvalidTarget)); + break; + } + else if (target == source && source.entity.name != "BLIZZY") + { + ServerCore.Notify(source, CommandsMessages.getMessage(CommandsMessages.startSelfError)); + break; + } + else if (source.Duel == true) + { + ServerCore.Notify(source, CommandsMessages.getMessage(CommandsMessages.baseAlreadyDueling)); + break; + } + else if (target.Duel == true) + { + ServerCore.Notify(source, CommandsMessages.getMessage(CommandsMessages.startTargetAlreadyDueling)); + break; + } + ServerCore.Notify(source, CommandsMessages.getMessage(String.Format(CommandsMessages.startAcceptMessage,source.entity.name))); + var duel = new DuelSystem(pluginRef,source, target, DateTimeOffset.UtcNow.ToUnixTimeSeconds()); + ((DuelCore)pluginRef).duels.Add(duel); + new Thread(() => duel.RunDuel()).Start(); + } + else + { + ServerCore.Notify(source, CommandsMessages.getMessage(CommandsMessages.startSyntax)); + } + break; + #endregion + case "accept": + #region accept + duelFinder = ((DuelCore)pluginRef).duels.LastOrDefault(x => x.player2.entity.name.Contains(source.entity.name)); + if (duelFinder != null && source.Duel == null) + { + duelFinder.AcceptDuel(); + } + else if (source.Duel == true) + { + ServerCore.Notify(source, CommandsMessages.getMessage(CommandsMessages.baseAlreadyDueling)); + } + else + { + ServerCore.Notify(source, CommandsMessages.getMessage(CommandsMessages.baseNoDuelRequestFound)); + } + break; + #endregion + case "refuse": + #region refuse + duelFinder = ((DuelCore)pluginRef).duels.LastOrDefault(x => x.player2.entity.name.Contains(source.entity.name)); + if (duelFinder != null && source.Duel == null) + { + duelFinder.RefuseDuel(); + } + else if (source.Duel == true) + { + ServerCore.Notify(source, CommandsMessages.getMessage(CommandsMessages.baseAlreadyDueling)); + } + else + { + ServerCore.Notify(source, CommandsMessages.getMessage(CommandsMessages.baseNoDuelRequestFound)); + } + break; + #endregion + case "spectate": + #region spectate + if (parameters.Length == 2) + { + var name = parameters[1]; + duelFinder = ((DuelCore)pluginRef).duels.LastOrDefault(x => x.player1.entity.name.Contains(name) || x.player2.entity.name.Contains(name)); + if (duelFinder != null && source.Duel == null) + { + duelFinder.Spectate(source); + ServerCore.Notify(source, CommandsMessages.getMessage(String.Format(CommandsMessages.spectateSuccess, name))); + } + else + { + ServerCore.Notify(source, CommandsMessages.getMessage(CommandsMessages.baseInvalidTarget)); + } + } + else + { + ServerCore.Notify(source, CommandsMessages.getMessage(CommandsMessages.spectateSyntax)); + } + #endregion + break; + default: + return false; + } + return true; + } + } +} diff --git a/Server/Plugins/Duel/Resources/CommandsMessages.cs b/Server/Plugins/Duel/Resources/CommandsMessages.cs new file mode 100644 index 0000000..ffd2f51 --- /dev/null +++ b/Server/Plugins/Duel/Resources/CommandsMessages.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Server.Plugins.Duel.Resources +{ + static class CommandsMessages + { + // Base Command + public static readonly string baseSyntax = "Type '/duel help' for further informations about this plugin"; + public static readonly string baseInvalidTarget = "Invalid target"; + public static readonly string baseAlreadyDueling = "You're already involved in a duel"; + public static readonly string baseNoDuelRequestFound = "No duel request found"; + // Start + public static readonly string startSyntax = "Syntax : /duel start [player2]"; + public static readonly string startSelfError = "Unfortunatly, you can't duel yourself"; + public static readonly string startTargetAlreadyDueling = "The target is already involved in a duel"; + public static readonly string startAcceptMessage = "{0} wants to duel you ! /duel accept or /duel refuse"; + + // Duel Accept + public static readonly string acceptSyntax = "Syntax : /duel accept"; + // Duel Refuse + public static readonly string refuseSyntax = "Syntax : /duel refuse"; + + // Duel Stop + public static readonly string stopSyntax = "Syntax : /duel stop"; + // Spectate + public static readonly string spectateSyntax = "Syntax : /duel spectate [player]"; + public static readonly string spectateSuccess = "You are spectating the duel of {0} !"; + + public static string getMessage(String message) + { + return String.Format("[{0}] " + message, DuelConfig.pluginName); + } + } +} diff --git a/Server/Plugins/Duel/Resources/PlayerDuel.cs b/Server/Plugins/Duel/Resources/PlayerDuel.cs new file mode 100644 index 0000000..0b5f293 --- /dev/null +++ b/Server/Plugins/Duel/Resources/PlayerDuel.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Net.Sockets; +using System.Text; +using Resources; + +namespace Server.Plugins.Duel +{ + class PlayerDuel : Player + { + public bool? Duel; + public bool? PreparingTime; + public PlayerDuel(TcpClient tcpClient) : base(tcpClient) + { + } + } +} diff --git a/Server/Plugins/Duel/Scripts/DuelSystem.cs b/Server/Plugins/Duel/Scripts/DuelSystem.cs new file mode 100644 index 0000000..2e6abc7 --- /dev/null +++ b/Server/Plugins/Duel/Scripts/DuelSystem.cs @@ -0,0 +1,199 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Resources; +using System.Threading.Tasks; +using Server.Plugins.Arena.Database; + +namespace Server.Plugins.Duel +{ + class DuelSystem + { + private PluginBase pluginRef; + private volatile Boolean ongoing; // status of the duel + private volatile Boolean stop; // Token for stopping duel/thread + public Player winner; + public ArenaEntity arena; + private List Spectators; + public ArenaDatabase ArenaDatabase; + public Player player1; + private long[] storedPos1; // initial player1 position + private float? storedHp1; // initial player1 position + public Player player2; + private long[] storedPos2; // initial player2 position + private float? storedHp2; // initial player2 position + private volatile int request_state;// 0 waiting | 1 accepted | 2 refused + private long requestInitialTime; + private Boolean WaitingResponse() + { + #region waitingResponse + while (DateTimeOffset.UtcNow.ToUnixTimeSeconds() < this.requestInitialTime + DuelConfig.maxRequestTime && this.request_state == 0 && stop == false) + { + System.Threading.Thread.Sleep(1000); + } + if (stop == true) + { + return false; + } + switch (this.request_state) + { + case 1: + NotifyPlayers("[Duel] Duel is starting"); + this.ongoing = true; + break; + case 2: + ServerCore.Notify(this.player1, string.Format("[Duel] {0} refused the duel", this.player2.entity.name)); + break; + default: + ServerCore.Notify(this.player1, string.Format("[Duel] {0} didn't respond within the 30s limit", this.player2.entity.name)); + break; + } + return ongoing; + #endregion + } + public DuelSystem(PluginBase pluginRef,Player player1, Player player2, long requestInitialTime) + { + #region createDuel + this.pluginRef = pluginRef; + this.ongoing = false; + this.player1 = player1; + this.player2 = player2; + this.requestInitialTime = requestInitialTime; + this.ArenaDatabase = new ArenaDatabase(); + this.Spectators = new List(); + #endregion + } + public void PickArena() + { + #region PickArena + this.arena = ArenaDatabase.FetchRandomArena(); + if (this.arena == null) + { + NotifyPlayers("[Duel] An error occured : the arena's list is empty"); + this.stop = true; + } + #endregion + } + public void SetPlayersState() + { + #region setPlayersState + this.storedPos1 = new long[]{ + this.player1.entity.position.x, + this.player1.entity.position.y, + this.player1.entity.position.z, + }; + this.storedPos2 = new long[]{ + this.player2.entity.position.x, + this.player2.entity.position.y, + this.player2.entity.position.z, + }; + this.storedHp1 = this.player1.entity.HP; + this.storedHp2 = this.player2.entity.HP; + // this.player1.Duel = true; + // this.player2.Duel = true; + // Server.TeleportPlayer(this.arena.getPosition(ArenaPositionName.player1), this.player1); + // Server.TeleportPlayer(this.arena.getPosition(ArenaPositionName.player2), this.player2); + #endregion + } + public void LaunchPreparingTime() + { + #region launchPreparingTime + // player1.PreparingTime = true; + // player2.PreparingTime = true; + this.player1.entity.hostility = Hostility.Neutral; + this.player2.entity.hostility = Hostility.Neutral; + NotifyPlayers("[Duel] Duel is starting in 10sd"); + System.Threading.Thread.Sleep(10000); + // Server.setHostility(Hostility.Player, this.player1); + this.player1.entity.hostility = Hostility.Player; + this.player2.entity.hostility = Hostility.Player; + // player1.PreparingTime = null; + // player2.PreparingTime = null; + NotifyPlayers("[Duel] Go !"); + #endregion + } + public void RestorePlayersState() + { + #region restorePlayerState + // Server.setHp(this.storedHp1, this.player1); + // Server.setHp(this.storedHp2, this.player2); + // Server.TeleportPlayer(this.storedPos1, this.player1); + // Server.TeleportPlayer(this.storedPos2, this.player2); + // this.player1.Duel = null; + // this.player2.Duel = null; + #endregion + } + private void ManageDuel() + { + #region ManageDuel + PickArena(); + if (this.arena != null) + { + SetPlayersState(); + LaunchPreparingTime(); + } + while (this.ongoing == true && this.stop == false) + { + if (this.player1.entity.HP <= 0 || this.player2.entity.HP <= 0) + { + this.winner = this.player1.entity.HP <= 0 ? this.player2 : this.player1; + this.ongoing = false; + } + System.Threading.Thread.Sleep(1000); + } + if (this.stop == false) + { + NotifyPlayers(String.Format("[Duel] {0} won this duel", this.winner.entity.name)); + RestorePlayersState(); + } + else + { + if (this.arena != null) + { + RestorePlayersState(); + } + } + ((DuelCore)pluginRef).duels.Remove(this); + #endregion + } + public void Stop() + { + #region stop + NotifyPlayers(String.Format("[Duel] The duel is cancelled")); + this.stop = true; + #endregion + } + public void AcceptDuel() + { + this.request_state = 1; + } + public void RefuseDuel() + { + this.request_state = 2; + } + public void Spectate(Player player) + { + // Server.TeleportPlayer(this.arena.getPosition(ArenaPositionName.spectator), player); + this.Spectators.Add(player); + } + public void NotifyPlayers(string message) + { + ServerCore.Notify(this.player1, message); + ServerCore.Notify(this.player2, message); + } + public void RunDuel() + { + #region runDuel + Boolean player2Accepted = WaitingResponse(); + if (player2Accepted) + { + ManageDuel(); + } + else + { + ((DuelCore)pluginRef).duels.Remove(this); + } + #endregion + } + } +} diff --git a/Server/Plugins/PluginBase.cs b/Server/Plugins/PluginBase.cs new file mode 100644 index 0000000..4183eb3 --- /dev/null +++ b/Server/Plugins/PluginBase.cs @@ -0,0 +1,29 @@ +using Resources; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Server.Plugins +{ + public class PluginBase + { + public String pluginName; + public virtual Boolean hasCommands() + { + return false; + } + public String getName() + { + return pluginName; + } + public virtual Boolean analyzeCommand(string message, Player source) + { + return false; + } + public virtual List checkDependencies() + { + var depencies = new List(); + return depencies; + } + } +} diff --git a/Server/Plugins/PluginsConfig.cs b/Server/Plugins/PluginsConfig.cs new file mode 100644 index 0000000..6146aec --- /dev/null +++ b/Server/Plugins/PluginsConfig.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; + +namespace Server.Plugins +{ + class PluginsConfig + { + public static List pluginsName = new List{ + "BaseServer", + "Duel", + "Arena" + }; + } +} diff --git a/Server/Plugins/PluginsCore.cs b/Server/Plugins/PluginsCore.cs new file mode 100644 index 0000000..bf4f625 --- /dev/null +++ b/Server/Plugins/PluginsCore.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Linq; +using Server.Extensions; + +namespace Server.Plugins +{ + public static class PluginsCore + { + public static List pluginsList; + public static List pluginsWithCommands; + public static List DependenciesList; + public static void Init() + { + pluginsList = new List(); + pluginsWithCommands = new List(); + foreach (String pluginName in PluginsConfig.pluginsName) + { + var pluginPath = Type.GetType("Server.Plugins." + pluginName + "." + pluginName + "Core"); + if (pluginPath != null) + { + var plugin = (PluginBase)Activator.CreateInstance(pluginPath); + if (plugin.hasCommands()) + { + pluginsWithCommands.Add(plugin); + } + Log.PrintLn(String.Format("Plugin '{0}' has been successfully initialized !",pluginName), ConsoleColor.Green); + pluginsList.Add(plugin); + } + else + { + Log.PrintLn(String.Format("Plugin '{0}' cannot be found !", pluginName), ConsoleColor.Red); + } + } + foreach(PluginBase plugin in pluginsList) + { + var dependencies = plugin.checkDependencies(); + foreach(string dependency in dependencies) + { + if(!pluginsList.Select(p => p.pluginName).Contains(dependency)) + { + Log.PrintLn(String.Format("Plugin '{0}' require the initialization of plugin '{1}' !", plugin.pluginName,dependency), ConsoleColor.Red); + } + } + } + if(pluginsWithCommands.Count > 0) + { + ChatMessage.Desactivate(); + } + } + } +} diff --git a/Server/Program.cs b/Server/Program.cs index e4ee40f..2596c96 100644 --- a/Server/Program.cs +++ b/Server/Program.cs @@ -3,7 +3,7 @@ namespace Server { class Program { static void Main(string[] args) { - Server.Start(12346); + ServerCore.Start(12346); while (true) { Console.ReadLine(); } diff --git a/Server/Server.cs b/Server/Server.cs deleted file mode 100644 index 92772fa..0000000 --- a/Server/Server.cs +++ /dev/null @@ -1,473 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Net; -using System.Net.Sockets; -using System.IO; - -using Server.Addon; -using Server.Database; - -using Microsoft.EntityFrameworkCore; - -using Resources; -using Resources.Datagram; -using Resources.Packet; -using Resources.Utilities; - -namespace Server { - public static class Server { - public static UdpClient udpClient; - public static TcpListener tcpListener; - public static List players = new List(); - public static Dictionary dynamicEntities = new Dictionary(); - public static UserDatabase Database; - - public static void Start(int port) { - Log.PrintLn("server starting..."); - Database = new UserDatabase(); - Database.Database.Migrate(); //Ensure database exists - - #region models - //var rnd = new Random(); - //for (int i = 8286946; i < 8286946 + 512; i++) { - // for (int j = 8344456; j < 8344456 + 512; j++) { - // var block = new ServerUpdate.BlockDelta() { - // color = new Resources.Utilities.ByteVector() { - // x = 0, - // y = 0, - // z = (byte)rnd.Next(0, 255), - // }, - // type = BlockType.solid, - // position = new Resources.Utilities.IntVector() { - // x = i, - // y = j, - // z = 208, - // }, - // }; - // worldUpdate.blockDeltas.Add(block); - // } - //} - //x = 543093329157, - //y = 546862296355, - //z = 14423162 - //ZoxModel model = JsonConvert.DeserializeObject(File.ReadAllText("models/Fulcnix_exceedspawn.zox")); - //model.Parse(worldUpdate, 8286883, 8344394, 200); - //model = JsonConvert.DeserializeObject(File.ReadAllText("models/Aster_Tavern2.zox")); - //model.Parse(worldUpdate, 8287010, 8344432, 200); - //model = JsonConvert.DeserializeObject(File.ReadAllText("models/Aster_Tavern1.zox")); - //model.Parse(worldUpdate, 8286919, 8344315, 212); - //model = JsonConvert.DeserializeObject(File.ReadAllText("models/arena/aster_arena.zox")); - //model.Parse(worldUpdate, 8286775, 8344392, 207); - //model = JsonConvert.DeserializeObject(File.ReadAllText("models/michael_project1.zox")); - //model.Parse(worldUpdate, 8286898, 8344375, 213); - //model = JsonConvert.DeserializeObject(File.ReadAllText("models/arena/fulcnix_hall.zox")); - //model.Parse(worldUpdate, 8286885, 8344505, 208); - //model = JsonConvert.DeserializeObject(File.ReadAllText("models/arena/fulcnix_hall.zox")); - //model.Parse(worldUpdate, 8286885, 8344629, 208); - //model = JsonConvert.DeserializeObject(File.ReadAllText("models/Tiecz_MountainArena.zox")); - //model.Parse(worldUpdate, 8286885, 8344759, 208); - ////8397006, 8396937, 127 //near spawn - //model = JsonConvert.DeserializeObject(File.ReadAllText("models/Aster_CloudyDay11.zox")); - //model.Parse(worldUpdate, 8286770, 8344262, 207); - //model = JsonConvert.DeserializeObject(File.ReadAllText("models/Aster_CloudyDay12.zox")); - //model.Parse(worldUpdate, 8286770, 8344136, 207); - //model = JsonConvert.DeserializeObject(File.ReadAllText("models/Aster_CloudyDay13.zox")); - //model.Parse(worldUpdate, 8286770, 8344010, 207); - //model = JsonConvert.DeserializeObject(File.ReadAllText("models/Aster_CloudyDay14.zox")); - //model.Parse(worldUpdate, 8286770, 8344010, 333); - //model = JsonConvert.DeserializeObject(File.ReadAllText("models/Aster_CloudyDay01.zox")); - //model.Parse(worldUpdate, 8286644, 8344010, 333); - //model = JsonConvert.DeserializeObject(File.ReadAllText("models/Aster_CloudyDay02.zox")); - //model.Parse(worldUpdate, 8286118, 8344010, 333); - //model = JsonConvert.DeserializeObject(File.ReadAllText("models/Aster_CloudyDay03.zox")); - //model.Parse(worldUpdate, 8285992, 8344010, 333); - //model = JsonConvert.DeserializeObject(File.ReadAllText("models/Aster_CloudyDay04.zox")); - //model.Parse(worldUpdate, 8285992, 8344136, 333); - //model = JsonConvert.DeserializeObject(File.ReadAllText("models/Aster_CloudyDay05.zox")); - //model.Parse(worldUpdate, 8285992, 8344262, 333); - //model = JsonConvert.DeserializeObject(File.ReadAllText("models/Aster_CloudyDay06.zox")); - //model.Parse(worldUpdate, 8286118, 8344262, 333); - //model = JsonConvert.DeserializeObject(File.ReadAllText("models/Aster_CloudyDay07.zox")); - //model.Parse(worldUpdate, 8286118, 8344136, 333); - //model = JsonConvert.DeserializeObject(File.ReadAllText("models/Aster_CloudyDay08.zox")); - //model.Parse(worldUpdate, 8286244, 8344136, 333); - //model = JsonConvert.DeserializeObject(File.ReadAllText("models/Aster_CloudyDay09.zox")); - //model.Parse(worldUpdate, 8286244, 8344262, 333); - //model = JsonConvert.DeserializeObject(File.ReadAllText("models/Aster_CloudyDay10.zox")); - //model.Parse(worldUpdate, 8286770, 8344262, 333); - #endregion - - udpClient = new UdpClient(new IPEndPoint(IPAddress.Any, port)); - new Thread(new ThreadStart(ListenUDP)).Start(); - tcpListener = new TcpListener(IPAddress.Any, port); - tcpListener.Start(); - new Thread(new ThreadStart(ListenTCP)).Start(); - Log.PrintLn("loading completed"); - } - - private static void ListenTCP() { - var player = new Player(tcpListener.AcceptTcpClient()); - new Thread(new ThreadStart(ListenTCP)).Start(); - Log.PrintLn(player.IP + " connected", ConsoleColor.Blue); - try { - while (true) ProcessPacket(player.reader.ReadByte(), player); - } - catch (IOException) { - if (player.entity != null) { - RemovePlayerEntity(player, false); - } - players.Remove(player); - Log.PrintLn(player.IP + " disconnected", ConsoleColor.Red); - } - } - private static void ListenUDP() { - IPEndPoint source = null; - while (true) { - byte[] datagram = udpClient.Receive(ref source); - var player = players.FirstOrDefault(x => (x.RemoteEndPoint).Equals(source)); - if (player != null && player.entity != null) { - try { - ProcessDatagram(datagram, player); - } - catch (IndexOutOfRangeException) { - Kick(player, "invalid data received"); - } - } - } - } - - private static void SendUDP(byte[] data, Player target) { - udpClient.Send(data, data.Length, target.RemoteEndPoint); - } - private static void BroadcastUDP(byte[] data, Player toSkip = null) { - foreach (var player in players) { - if (player != toSkip) { - SendUDP(data, player); - } - } - } - - private static void ProcessPacket(byte packetID, Player source) { - switch ((ServerPacketID)packetID) { - case ServerPacketID.VersionCheck: - #region VersionCheck - source.writer.Write((byte)ServerPacketID.VersionCheck); - if (source.reader.ReadInt32() != Config.bridgeVersion) { - source.writer.Write(false); - //close connection - break; - } - source.writer.Write(true); - players.Add(source); - foreach (EntityUpdate entity in dynamicEntities.Values) { - SendUDP(entity.CreateDatagram(), source); - } - break; - #endregion - case ServerPacketID.Login://login - #region login - string username = source.reader.ReadString(); - string password = source.reader.ReadString(); - source.MAC = source.reader.ReadString(); - - AuthResponse authResponse; - if (!players.Contains(source)) { - //musnt login without checking bridge version first - authResponse = AuthResponse.Unverified; - } - else if (source.entity != null) { - //already logged in into another account - authResponse = AuthResponse.UserAlreadyLoggedIn; - } - else if (players.FirstOrDefault(x => x.entity?.name == username) != null) { - //another user is already logged into this account - authResponse = AuthResponse.AccountAlreadyActive; - } - else { - authResponse = Database.AuthUser(username, password, (int)source.IP.Address, source.MAC); - } - source.writer.Write((byte)ServerPacketID.Login); - source.writer.Write((byte)authResponse); - if (authResponse != AuthResponse.Success) break; - - source.entity = new EntityUpdate() { - guid = AssignGuid(), - name = username, - }; - source.writer.Write((ushort)source.entity.guid); - source.writer.Write(Config.mapseed); - - dynamicEntities.Add((ushort)source.entity.guid, source.entity); - - Log.PrintLn(source.IP + " logged in as " + username, ConsoleColor.Green); - break; - #endregion - case ServerPacketID.Logout: - #region logout - if (source.entity == null) break;//not logged in - RemovePlayerEntity(source, false); - Log.PrintLn(source.IP + " logged out", ConsoleColor.Yellow); - break; - #endregion - case ServerPacketID.Register: - #region Register - username = source.reader.ReadString(); - var email = source.reader.ReadString(); - password = source.reader.ReadString(); - - RegisterResponse registerResponse; - if (!Tools.alphaNumericRegex.IsMatch(username) || !Tools.validEmailRegex.IsMatch(email)) { - registerResponse = RegisterResponse.InvalidInput; - } - else { - registerResponse = Database.RegisterUser(username, email, password); - } - source.writer.Write((byte)ServerPacketID.Register); - source.writer.Write((byte)registerResponse); - if (registerResponse == RegisterResponse.Success) { - Log.PrintLn(source.IP + " registered as " + username, ConsoleColor.Cyan); - } - break; - #endregion - default: - Log.PrintLn($"unknown packetID {packetID} received from {source.IP}", ConsoleColor.Magenta); - source.tcpClient.Close(); - break; - } - } - private static void ProcessDatagram(byte[] datagram, Player source) { - switch ((DatagramID)datagram[0]) { - case DatagramID.DynamicUpdate: - #region entityUpdate - var entityUpdate = new EntityUpdate(datagram); - #region antiCheat - string ACmessage = AntiCheat.Inspect(entityUpdate, source.entity); - if (ACmessage != null) Kick(source, ACmessage); - #endregion - #region announce - if (entityUpdate.name != null) { - //Announce.Join(entityUpdate.name, player.entityData.name, players); - } - #endregion - #region pvp - entityUpdate.entityFlags |= 1 << 5; //enable friendly fire flag for pvp - #endregion - #region tombstone - if (entityUpdate.HP <= 0 && (source.entity.HP > 0 || source.entity.HP == null)) { - var tombstone = new EntityUpdate() { - guid = AssignGuid(), - position = entityUpdate.position ?? source.entity.position, - hostility = Hostility.Neutral, - entityType = EntityType.None, - appearance = new EntityUpdate.Appearance() { - character_size = new FloatVector() { - x = 1, - y = 1, - z = 1, - }, - head_model = 2155, - head_size = 1 - }, - HP = 100, - name = "tombstone" - }; - source.tomb = (ushort)tombstone.guid; - BroadcastUDP(tombstone.CreateDatagram()); - } - else if (source.entity.HP <= 0 && entityUpdate.HP > 0 && source.tomb != null) { - var rde = new RemoveDynamicEntity() { - Guid = (ushort)source.tomb, - }; - BroadcastUDP(rde.data); - } - #endregion - entityUpdate.Merge(source.entity); - BroadcastUDP(entityUpdate.CreateDatagram(), source); - break; - #endregion - case DatagramID.Attack: - #region attack - var attack = new Attack(datagram); - source.lastTarget = attack.Target; - var target = players.FirstOrDefault(p => p.entity?.guid == attack.Target); - if (target != null) SendUDP(attack.data, target); - break; - #endregion - case DatagramID.Projectile: - #region Projectile - var projectile = new Projectile(datagram); - BroadcastUDP(projectile.data, source); //pass to all players except source - break; - #endregion - case DatagramID.Proc: - #region proc - var proc = new Proc(datagram); - BroadcastUDP(proc.data, source); //pass to all players except source - break; - #endregion - case DatagramID.Chat: - #region chat - var chat = new Chat(datagram); - - if (chat.Text.StartsWith("/")) { - var parameters = chat.Text.Substring(1).Split(" "); - var command = parameters[0].ToLower(); - switch (command) { - case "kick": - case "btfo": - case "ban": - #region ban - if (source.entity.name != "BLACKROCK") { - Notify(source, "no permission"); - break; - } - if (parameters.Length == 1) { - Notify(source, string.Format("usage example: /kick blackrock")); - break; - } - target = players.FirstOrDefault(x => x.entity.name.Contains(parameters[1])); - if (target == null) { - Notify(source, "invalid target"); - break; - }; - var reason = "no reason specified"; - if (parameters.Length > 2) { - reason = parameters[2]; - } - if (command == "kick") { - Kick(target, reason); - break; - } - target.writer.Write((byte)ServerPacketID.BTFO); - target.writer.Write(reason); - if (command == "ban") { - Database.BanUser(target.entity.name, (int)target.IP.Address, target.MAC, reason); - } - RemovePlayerEntity(target, false); - break; - #endregion - case "bleeding": - - break; - case "time": - #region time - if (parameters.Length == 1) { - Notify(source, string.Format("usage example: /time 12:00")); - break; - } - var clock = parameters[1].Split(":"); - if (clock.Length < 2 || - !int.TryParse(clock[0], out int hour) || - !int.TryParse(clock[1], out int minute)) { - Notify(source, string.Format("invalid syntax")); - break; - } - var inGameTime = new InGameTime() { - Milliseconds = (hour * 60 + minute) * 60000, - }; - SendUDP(inGameTime.data, source); - break; - #endregion - default: - Notify(source, string.Format("unknown command '{0}'", parameters[0])); - break; - } - break; - } - Log.Print(dynamicEntities[chat.Sender].name + ": ", ConsoleColor.Cyan); - Log.PrintLn(chat.Text, ConsoleColor.White, false); - - BroadcastUDP(chat.data, null); //pass to all players - break; - #endregion - case DatagramID.Interaction: - #region interaction - var interaction = new Interaction(datagram); - BroadcastUDP(interaction.data, source); //pass to all players except source - break; - #endregion - case DatagramID.RemoveDynamicEntity: - #region removeDynamicEntity - var remove = new RemoveDynamicEntity(datagram); - RemovePlayerEntity(source, true); - break; - #endregion - case DatagramID.SpecialMove: - #region specialMove - var specialMove = new SpecialMove(datagram); - switch (specialMove.Id) { - case SpecialMoveID.Taunt: - target = players.FirstOrDefault(p => p.entity.guid == specialMove.Guid); - if (target != null) { - specialMove.Guid = (ushort)source.entity.guid; - SendUDP(specialMove.data, target); - } - break; - case SpecialMoveID.SmokeBomb: - BroadcastUDP(specialMove.data, source); - break; - case SpecialMoveID.CursedArrow: - case SpecialMoveID.ArrowRain: - case SpecialMoveID.Shrapnel: - case SpecialMoveID.IceWave: - case SpecialMoveID.Confusion: - case SpecialMoveID.ShadowStep: - BroadcastUDP(specialMove.data); - break; - default: - break; - } - break; - #endregion - case DatagramID.HolePunch: - break; - default: - Log.PrintLn($"unknown DatagramID {datagram[0]} received from {source.IP}", ConsoleColor.Magenta); - Kick(source, "invalid data received"); - break; - } - } - - public static void RemovePlayerEntity(Player player, bool createNewEntity) { - var rde = new RemoveDynamicEntity() { - Guid = (ushort)player.entity.guid, - }; - BroadcastUDP(rde.data, player); - if (player.tomb != null) { - rde.Guid = (ushort)player.tomb; - BroadcastUDP(rde.data); - player.tomb = null; - } - if (createNewEntity) { - player.entity = new EntityUpdate() { - guid = player.entity.guid - }; - } - else { - dynamicEntities.Remove((ushort)player.entity.guid); - player.entity = null; - } - } - public static void Kick(Player target, string reason) { - Notify(target, "you got kicked: " + reason); - target.writer.Write((byte)ServerPacketID.Kick); - RemovePlayerEntity(target, true); - } - - public static ushort AssignGuid() { - ushort newGuid = 1; - while (dynamicEntities.ContainsKey(newGuid)) newGuid++; - return newGuid; - } - - public static void Notify(Player target, string message) { - var chat = new Chat() { - Sender = 0, - Text = message, - }; - SendUDP(chat.data, target); - } - } -} diff --git a/Server/Server.csproj b/Server/Server.csproj index 25b0da1..42f01ea 100644 --- a/Server/Server.csproj +++ b/Server/Server.csproj @@ -22,4 +22,8 @@ + + + + diff --git a/Server/ServerCore.cs b/Server/ServerCore.cs new file mode 100644 index 0000000..47b1386 --- /dev/null +++ b/Server/ServerCore.cs @@ -0,0 +1,278 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Net; +using System.Net.Sockets; +using System.IO; +using Server.Database; + +using Microsoft.EntityFrameworkCore; + +using Resources; +using Resources.Datagram; +using Resources.Packet; +using Resources.Utilities; +using Server.Plugins; +using Server.Extensions; +namespace Server { + public static partial class ServerCore { + public static UdpClient udpClient; + public static TcpListener tcpListener; + public static List players = new List(); + public static Dictionary dynamicEntities = new Dictionary(); + public static UserDatabase userDatabase; + public static void Start(int port) { + Log.PrintLn("server starting..."); + userDatabase = new UserDatabase(); + userDatabase.Database.Migrate(); //Ensure database exists + udpClient = new UdpClient(new IPEndPoint(IPAddress.Any, port)); + new Thread(new ThreadStart(ListenUDP)).Start(); + tcpListener = new TcpListener(IPAddress.Any, port); + tcpListener.Start(); + new Thread(new ThreadStart(ListenTCP)).Start(); + + Extensions.Extensions.Init(); + PluginsCore.Init(); + Log.PrintLn("loading completed"); + } + + private static void ListenTCP() { + var player = new Player(tcpListener.AcceptTcpClient()); + new Thread(new ThreadStart(ListenTCP)).Start(); + Log.PrintLn(player.IP + " connected", ConsoleColor.Blue); + try { + while (true) ProcessPacket(player.reader.ReadByte(), player); + } + catch (IOException) { + if (player.entity != null) { + RemovePlayerEntity(player, false); + } + players.Remove(player); + Log.PrintLn(player.IP + " disconnected", ConsoleColor.Red); + } + } + private static void ListenUDP() { + IPEndPoint source = null; + while (true) { + byte[] datagram = udpClient.Receive(ref source); + var player = players.FirstOrDefault(x => (x.RemoteEndPoint).Equals(source)); + if (player != null && player.entity != null) { + try { + ProcessDatagram(datagram, player); + } + catch (IndexOutOfRangeException) { + Kick(player, "invalid data received"); + } + } + } + } + + public static void SendUDP(byte[] data, Player target) { + udpClient.Send(data, data.Length, target.RemoteEndPoint); + } + public static void BroadcastUDP(byte[] data, Player toSkip = null) { + foreach (var player in players.ToList()) { + if (player != toSkip) { + SendUDP(data, player); + } + } + } + + private static void ProcessPacket(byte packetID, Player source) { + switch ((ServerPacketID)packetID) { + case ServerPacketID.VersionCheck: + #region VersionCheck + source.writer.Write((byte)ServerPacketID.VersionCheck); + if (source.reader.ReadInt32() != Config.bridgeVersion) { + source.writer.Write(false); + //close connection + break; + } + source.writer.Write(true); + players.Add(source); + foreach (EntityUpdate entity in dynamicEntities.Values) { + SendUDP(entity.CreateDatagram(), source); + } + break; + #endregion + case ServerPacketID.Login://login + #region login + string username = source.reader.ReadString(); + string password = source.reader.ReadString(); + source.MAC = source.reader.ReadString(); + + AuthResponse authResponse; + if (!players.Contains(source)) { + //musnt login without checking bridge version first + authResponse = AuthResponse.Unverified; + } + else if (source.entity != null) { + //already logged in into another account + authResponse = AuthResponse.UserAlreadyLoggedIn; + } + else if (players.FirstOrDefault(x => x.entity?.name == username) != null) { + //another user is already logged into this account + authResponse = AuthResponse.AccountAlreadyActive; + } + else { + authResponse = userDatabase.AuthUser(username, password, (int)source.IP.Address, source.MAC); + } + source.writer.Write((byte)ServerPacketID.Login); + source.writer.Write((byte)authResponse); + if (authResponse != AuthResponse.Success) break; + + source.entity = new EntityUpdate() { + guid = AssignGuid(), + name = username, + }; + source.writer.Write((ushort)source.entity.guid); + source.writer.Write(Config.mapseed); + + dynamicEntities.Add((ushort)source.entity.guid, source.entity); + + Log.PrintLn(source.IP + " logged in as " + username, ConsoleColor.Green); + break; + #endregion + case ServerPacketID.Logout: + #region logout + if (source.entity == null) break;//not logged in + RemovePlayerEntity(source, false); + Log.PrintLn(source.IP + " logged out", ConsoleColor.Yellow); + break; + #endregion + case ServerPacketID.Register: + #region Register + username = source.reader.ReadString(); + var email = source.reader.ReadString(); + password = source.reader.ReadString(); + + RegisterResponse registerResponse; + if (!Tools.alphaNumericRegex.IsMatch(username) || !Tools.validEmailRegex.IsMatch(email)) { + registerResponse = RegisterResponse.InvalidInput; + } + else { + registerResponse = userDatabase.RegisterUser(username, email, password); + } + source.writer.Write((byte)ServerPacketID.Register); + source.writer.Write((byte)registerResponse); + if (registerResponse == RegisterResponse.Success) { + Log.PrintLn(source.IP + " registered as " + username, ConsoleColor.Cyan); + } + break; + #endregion + default: + Log.PrintLn($"unknown packetID {packetID} received from {source.IP}", ConsoleColor.Magenta); + source.tcpClient.Close(); + break; + } + } + private static void ProcessDatagram(byte[] datagram, Player source) { + switch ((DatagramID)datagram[0]) { + case DatagramID.DynamicUpdate: + #region entityUpdate + var entityUpdate = new EntityUpdate(datagram); + EntityUpdated?.Invoke(entityUpdate, source); + entityUpdate.Merge(source.entity); + BroadcastUDP(entityUpdate.CreateDatagram(), source); + break; + #endregion + case DatagramID.Attack: + #region attack + var attack = new Attack(datagram); + EntityAttacked?.Invoke(attack, source); + source.lastTarget = attack.Target; + var target = players.FirstOrDefault(p => p.entity?.guid == attack.Target); + if (target != null) SendUDP(attack.data, target); + break; + #endregion + case DatagramID.Projectile: + #region Projectile + var projectile = new Projectile(datagram); + ProjectileCreated?.Invoke(projectile, source); + BroadcastUDP(projectile.data, source); //pass to all players except source + break; + #endregion + case DatagramID.Proc: + #region proc + var proc = new Proc(datagram); + PassiveProcced?.Invoke(proc, source); + BroadcastUDP(proc.data, source); //pass to all players except source + break; + #endregion + case DatagramID.Chat: + #region chat + var chat = new Chat(datagram); + ChatMessageReceived?.Invoke(chat, source); + break; + #endregion + case DatagramID.Interaction: + #region interaction + var interaction = new Interaction(datagram); + EntityInteracted?.Invoke(interaction, source); + BroadcastUDP(interaction.data, source); //pass to all players except source + break; + #endregion + case DatagramID.RemoveDynamicEntity: + #region removeDynamicEntity + var remove = new RemoveDynamicEntity(datagram); + EntityRemoved?.Invoke(remove, source); + RemovePlayerEntity(source, true); + break; + #endregion + case DatagramID.SpecialMove: + #region specialMove + var specialMove = new SpecialMove(datagram); + SpecialMoveUsed?.Invoke(specialMove, source); + break; + #endregion + case DatagramID.HolePunch: + break; + default: + Log.PrintLn($"unknown DatagramID {datagram[0]} received from {source.IP}", ConsoleColor.Magenta); + Kick(source, "invalid data received"); + break; + } + } + + public static void RemovePlayerEntity(Player player, bool createNewEntity) { + var rde = new RemoveDynamicEntity() { + Guid = (ushort)player.entity.guid, + }; + BroadcastUDP(rde.data, player); + if (player.tomb != null) { + rde.Guid = (ushort)player.tomb; + BroadcastUDP(rde.data); + player.tomb = null; + } + if (createNewEntity) { + player.entity = new EntityUpdate() { + guid = player.entity.guid + }; + } + else { + dynamicEntities.Remove((ushort)player.entity.guid); + player.entity = null; + } + } + public static void Kick(Player target, string reason) { + Notify(target, "you got kicked: " + reason); + target.writer.Write((byte)ServerPacketID.Kick); + RemovePlayerEntity(target, true); + } + + public static ushort AssignGuid() { + ushort newGuid = 1; + while (dynamicEntities.ContainsKey(newGuid)) newGuid++; + return newGuid; + } + + public static void Notify(Player target, string message) { + var chat = new Chat() { + Sender = 0, + Text = message, + }; + SendUDP(chat.data, target); + } + } +} diff --git a/Server/ServerCore_Events.cs b/Server/ServerCore_Events.cs new file mode 100644 index 0000000..033a71d --- /dev/null +++ b/Server/ServerCore_Events.cs @@ -0,0 +1,28 @@ +using Resources; +using Resources.Datagram; +using Resources.Packet; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Server { + public delegate void EntityUpdatedEventHandler(EntityUpdate entityUpdate, Player source); + public delegate void EntityAttackedEventHandler(Attack datagram, Player source); + public delegate void ProjectileCreatedEventHandler(Projectile datagram, Player source); + public delegate void PassiveProccedEventHandler(Proc datagram, Player source); + public delegate void ChatMessageReceivedEventHandler(Chat chat, Player source); + public delegate void EntityInteractedEventHandler(Interaction datagram, Player source); + public delegate void EntityRemovedEventHandler(RemoveDynamicEntity datagram, Player source); + public delegate void SpecialMoveUsedEventHandler(SpecialMove datagram, Player source); + + public static partial class ServerCore { + public static event EntityUpdatedEventHandler EntityUpdated; + public static event EntityAttackedEventHandler EntityAttacked; + public static event ProjectileCreatedEventHandler ProjectileCreated; + public static event PassiveProccedEventHandler PassiveProcced; + public static event ChatMessageReceivedEventHandler ChatMessageReceived; + public static event EntityInteractedEventHandler EntityInteracted; + public static event EntityRemovedEventHandler EntityRemoved; + public static event SpecialMoveUsedEventHandler SpecialMoveUsed; + } +}