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