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/Resources/CubeworldNetworking b/Resources/CubeworldNetworking index b24405c..5323adb 160000 --- a/Resources/CubeworldNetworking +++ b/Resources/CubeworldNetworking @@ -1 +1 @@ -Subproject commit b24405c0d42a1384d3cce28cae56d7858fb568c5 +Subproject commit 5323adb8735c2b78d27fd2add8f4df54addbf546 diff --git a/Resources/Enums.cs b/Resources/Enums.cs index 4fc5fc0..50a073c 100644 --- a/Resources/Enums.cs +++ b/Resources/Enums.cs @@ -58,4 +58,16 @@ public enum BridgeStatus : byte { LoggedIn, Playing, } + public enum RoleID : byte + { + Default, + Vip, + Mod, + Admin + } + public enum PromoteResponse : byte + { + Success, + InvalidTarget + } } diff --git a/Resources/Player.cs b/Resources/Player.cs index 40f9fae..c85b017 100644 --- a/Resources/Player.cs +++ b/Resources/Player.cs @@ -9,6 +9,7 @@ public class Player { public BinaryWriter writer; public BinaryReader reader; public EntityUpdate entity; + public byte Permission; public ushort? tomb; public string MAC; public ushort lastTarget; 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/Database/User.cs b/Server/Database/User.cs index 27d7baf..6608939 100644 --- a/Server/Database/User.cs +++ b/Server/Database/User.cs @@ -23,6 +23,7 @@ public User(string username, string email, string password) { this.Name = username; this.Email = email; this.PasswordHash = Hashing.Hash(password); + this.Permission = 0; } public bool VerifyPassword(string password) { diff --git a/Server/Database/UserDatabase.cs b/Server/Database/UserDatabase.cs index 44c887d..f09cd75 100644 --- a/Server/Database/UserDatabase.cs +++ b/Server/Database/UserDatabase.cs @@ -1,5 +1,6 @@ using Microsoft.EntityFrameworkCore; using Resources; +using System; using System.Linq; namespace Server.Database { @@ -87,5 +88,24 @@ public void BanUser(string entityName, int ipAddress, string targetMac, string r Bans.Add(ban); SaveChanges(); } + public PromoteResponse PromoteUser(string entityName, RoleID roleId) + { + var user = Users.SingleOrDefault(x => x.Name == entityName); + if (user != null) + { + user.Permission = (byte)roleId; + SaveChanges(); + return PromoteResponse.Success; + } + else + { + return PromoteResponse.InvalidTarget; + } + } + public byte getRoleId(string entityName) + { + var user = Users.SingleOrDefault(x => x.Name == entityName); + return user.Permission; + } } } \ No newline at end of file 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/ChatCommands.cs b/Server/Extensions/ChatCommands.cs new file mode 100644 index 0000000..2ad170b --- /dev/null +++ b/Server/Extensions/ChatCommands.cs @@ -0,0 +1,92 @@ +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 ChatCommands { + public static void Init() { + ServerCore.ChatMessageReceived += ParseAsCommand; + } + + private static void ParseAsCommand(string message, Player source) { + if (!message.StartsWith("/")) { + return; + } + var parameters = message.Substring(1).Split(" "); + var command = parameters[0].ToLower(); + switch (command) { + case "kick": + break; + case "btfo": + break; + case "ban": + #region ban + if (!minimumPermission(source,3)) { + 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: + ServerCore.Notify(source, string.Format("unknown command '{0}'", parameters[0])); + break; + } + } + private static Boolean minimumPermission(Player source,int roleId) + { + if(source.Permission >= roleId) + { + return true; + } + return false; + } + } +} diff --git a/Server/Extensions/ConsoleCommands.cs b/Server/Extensions/ConsoleCommands.cs new file mode 100644 index 0000000..35d847e --- /dev/null +++ b/Server/Extensions/ConsoleCommands.cs @@ -0,0 +1,53 @@ +using Resources; +using Server.Database; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Server.Extensions +{ + static class ConsoleCommands + { + public static void analyzeCommand(string consoleCommands) + { + var parameters = consoleCommands.Split(" "); + switch (parameters[0]) + { + case "promote": + if(parameters.Length == 3) + { + int role = int.Parse(parameters[2]); + if (0 <= role && role <= Enum.GetValues(typeof(RoleID)).Length - 1) + { + PromoteResponse response = ServerCore.userDatabase.PromoteUser(parameters[1], (RoleID)role); + if(response == PromoteResponse.Success) + { + var player = ServerCore.players.FirstOrDefault(x => x.entity?.name == parameters[1]); + if (player != null) + { + player.Permission = (byte)role; + } + Log.PrintLn(String.Format("User '{0}' promoted to role {1}!", parameters[1], parameters[2])); + } + else if(response == PromoteResponse.InvalidTarget) + { + Log.PrintLn(String.Format("Error : Invalid target")); + } + } + else + { + Log.PrintLn(String.Format("Error : RoleId out of Range")); + } + } + else + { + Log.PrintLn(String.Format("Syntax : promote [player] [roleId]")); + } + break; + case "default": + break; + } + } + } +} \ No newline at end of file diff --git a/Server/Extensions/Extensions.cs b/Server/Extensions/Extensions.cs new file mode 100644 index 0000000..c14a3c6 --- /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(); + ChatCommands.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/Program.cs b/Server/Program.cs index e4ee40f..8b951fd 100644 --- a/Server/Program.cs +++ b/Server/Program.cs @@ -1,11 +1,13 @@ -using System; +using Server.Extensions; +using System; namespace Server { class Program { static void Main(string[] args) { - Server.Start(12346); + ServerCore.Start(12346); while (true) { - Console.ReadLine(); + + ConsoleCommands.analyzeCommand(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/ServerCore.cs b/Server/ServerCore.cs new file mode 100644 index 0000000..ada329e --- /dev/null +++ b/Server/ServerCore.cs @@ -0,0 +1,281 @@ +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; + +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(); + 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.Permission = userDatabase.getRoleId(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.Text, source); + 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); + 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..f79930e --- /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(string message, 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; + } +}