diff --git a/readme.md b/readme.md index 47cac36..2e61e40 100644 --- a/readme.md +++ b/readme.md @@ -1,19 +1,32 @@ # Cuttlefish -A Minecraft server written in Haxe. +A Minecraft: Java Edition server implementation written in Haxe. ## Currently Supported Features - Server List Pinging - - can show any data, including text, online/max player count, protocol version, and sample of online players. -- thats literally it for now + - can show any data, including text (including chat components), online/max player count, protocol version, and sample of online players. +- the very start of getting a player to spawn + - the vanilla client will spawn after a few seconds, but technically it shouldnt + - when it does spawn, its just a void +- Chat Components + - there is a chat component builder class that is used to construct and serialize complex formatting in text. +- NBT + - can currently convert NBT tags to bytes, but reading them from bytes appears to be broken for now ## Features Being Worked On -- login success packet (to login to the server) -- protocol encryption -- some other background stuff +- properly spawning the player +- sending a flat world + +## Planned Features +- authentication and protocol encryption (Piracy is not supported, but impossible to prevent until both of these are added) +- chat +- configuration files for server list ping info and other stuff +- bungeecord/velocity support? +- MiniMessage-like format for easily using advanced chat component features ## Compiling Being written in Haxe, Cuttlefish can be compiled to many different targets. Cuttlefish only supports sys targets that have threading support. This means that the supported targets are currently C++, C#, Hashlink, Java (or JVM bytecode directly), Neko, and Python. NodeJS is not supported because `hxnodejs` does not support threads yet. To test without worrying about downloading anything extra, the following should work: -```git clone https://github.com/LeotomasMC/Cuttlefish.git +``` +git clone https://github.com/LeotomasMC/Cuttlefish.git cd Cuttlefish haxelib install uuid haxelib install hxp @@ -21,3 +34,16 @@ haxelib run hxp --install-hxp-alias hxp test neko ``` the last command `hxp test neko` will compile Cuttlefish for the Neko target, and then run the compiled output automatically. + +If you want to compile for specific targets, use `hxp build`, followed by a list of target flags. The flags for each target are as follows: +| Target Name | Supported Flags | +|---------------|-------------------| +| C++ | `-cpp` `-c++` `-cplusplus` | +| C# | `-csharp` `-cs` | +| Java | `-java` | +| Java Bytecode | `-jvm` | +| Hashlink | `-hl` `-hashlink` | +| Neko | `-neko` | +| Python | `-py` | + +note that some targets may require additional setup. see the haxe target details documentation to setup other targets. diff --git a/src/Chat.hx b/src/Chat.hx index d9e049c..eb7548e 100644 --- a/src/Chat.hx +++ b/src/Chat.hx @@ -2,6 +2,8 @@ package; import entity.Entity; +using StringTools; + /*enum abstract LegacyColor(Int) { var Black = 0x000000; // &0 var DarkBlue = 0x0000aa; // &1 @@ -134,8 +136,15 @@ class ChatComponent { if (this.isStrikethrough != null) json += '"strikethrough":$isStrikethrough,'; if (this.isObfuscated != null) json += '"obfuscated":$isObfuscated,'; if (this.usedColor != null) json += '"color":"${ChatComponent.serializeColor(this.usedColor)}",'; + if (this.usedFont != null) json += '"font":"${this.usedFont.toString()}",'; + if (this.extras != null) json += '"extra":[${[for (c in this.extras) c.serialize()].join(',')}],'; + return json; + } - return json.substr(0, json.length - 2) + '}'; + inline function comp(str:String):String { + if (str.startsWith('{"') && str.endsWith(',')) + str = str.substr(0, str.length - 1) + '}'; + return str; } public static function serializeColor(col:Color):String { @@ -165,6 +174,12 @@ class ChatComponent { class StringComponent extends ChatComponent { public var text:String; + + override public function serialize():String { + var s = super.serialize(); + s += '"text":"$text",'; + return comp(s); + } } class TranslationComponent extends ChatComponent { diff --git a/src/Connection.hx b/src/Connection.hx index 8d50e66..9c50810 100644 --- a/src/Connection.hx +++ b/src/Connection.hx @@ -1,5 +1,8 @@ package; +import Chat; +import Chat.ChatComponent; +import packet.clientbound.LoginDisconnectPacket; import packet.clientbound.PlayLoginPacket; import haxe.Exception; import haxe.io.Bytes; @@ -111,9 +114,12 @@ class Connection { var packet = LoginStartPacket.read(this); var namespace = Bytes.ofString('OfflinePlayer').toHex(); this._player = new Player(Uuid.v3(packet.name, namespace), packet.name); + //new LoginDisconnectPacket(ChatComponent.buildText('kicked').color(Color.Yellow).underline(true).extra(ChatComponent.buildText(' by ').underline(false).strike(true).color(Color.Hex('ff00ff'))).extra(ChatComponent.buildText('haxe!').color(Gold).italic(true)).extra(ChatComponent.buildText(' wow').obfuscate(true).underline(false).color(Hex('40d0e0')).extra(ChatComponent.buildText('trolled').color(LightGreen).obfuscate(false).font(new Identifier('minecraft', 'alt'))))).send(this); new LoginSuccessPacket().send(this, Uuid.parse(player.uuid), player.name); state = Play; - //new PlayLoginPacket().send(this); + + new PlayLoginPacket().send(this); + packet; case Play: null; @@ -129,6 +135,9 @@ class Connection { default: null; } + case 0x0C: + trace('plugin msg'); + null; default: // unknown packet type. we want to handle it though anyways just in case its useful? ServerboundPacket.readUnknownPacket(input, packetLen, packetId); diff --git a/src/Identifier.hx b/src/Identifier.hx index 3360fdf..8f8b77a 100644 --- a/src/Identifier.hx +++ b/src/Identifier.hx @@ -19,4 +19,8 @@ class Identifier { namespace = str.substr(0, colonPos); } } + + public function toString():String { + return '$namespace:$key'; + } } \ No newline at end of file diff --git a/src/NBT.hx b/src/NBT.hx index fb743cf..f8f7792 100644 --- a/src/NBT.hx +++ b/src/NBT.hx @@ -27,7 +27,7 @@ enum Tag { class NBT { public static function writeToStream(out:Output, tag:Tag, inList=false) { function writeName() { - out.writeInt8(tag.getParameters()[0].length); + out.writeInt16(tag.getParameters()[0].length); out.writeString(tag.getParameters()[0]); } switch tag { @@ -65,7 +65,7 @@ class NBT { case TAG_String(_, data): if (!inList) out.writeByte(8); if (!inList) writeName(); - out.writeInt8(data.length); + out.writeInt16(data.length); out.writeString(data); case TAG_List(_, data, type): if (!inList) out.writeByte(9); @@ -95,7 +95,7 @@ class NBT { var tagType = input.readByte(); var tagName = ''; if (!inList && tagType != 0) { - var nameLen = input.readInt8(); + var nameLen = input.readInt16(); tagName = input.readString(nameLen); } diff --git a/src/UnitTest.hx b/src/UnitTest.hx index 49fe755..fa3e3fb 100644 --- a/src/UnitTest.hx +++ b/src/UnitTest.hx @@ -1,7 +1,10 @@ package; +import sys.io.File; +import packet.clientbound.PlayLoginPacket; import haxe.io.BytesOutput; import haxe.io.BytesInput; +import NBT; using StringTools; using VarIntLong; @@ -24,6 +27,8 @@ class UnitTest { testVarInt(2147483647); testVarInt(-1); testVarInt(-2147483648); + + testNBT(); } static function testVarInt(number:Int) { @@ -35,4 +40,22 @@ class UnitTest { trace('${result} == ${number}: ${result == number} ${bytes.toHex()}'); } -} \ No newline at end of file + + static var tag = TAG_Compound('hello world', [TAG_String('name', 'bananrama')]); + + static function testNBT() { + var output = new BytesOutput(); + output.bigEndian = true; + //NBT.writeToStream(output, tag); + NBT.writeToStream(output, PlayLoginPacket.REGISTRY_TAG); + var bytes = output.getBytes(); + trace('write: ${bytes.toHex()}'); + File.write('./test.nbt', true).write(bytes); + var input = new BytesInput(bytes); + var result = NBT.readFromStream(input); + trace(result); + } +} + +// 0a 00 0b 68 65 6c 6c 6f 20 77 6f 72 6c 64 08 00 04 6e 61 6d 65 00 09 42 61 6e 61 6e 72 61 6d 61 00 +// 0a 00 0b 68 65 6c 6c 6f 20 77 6f 72 6c 64 08 00 04 6e 61 6d 65 00 09 62 61 6e 61 6e 72 61 6d 61 00 \ No newline at end of file diff --git a/src/packet/clientbound/LoginDisconnectPacket.hx b/src/packet/clientbound/LoginDisconnectPacket.hx index 368c3c9..30944f3 100644 --- a/src/packet/clientbound/LoginDisconnectPacket.hx +++ b/src/packet/clientbound/LoginDisconnectPacket.hx @@ -2,6 +2,8 @@ package packet.clientbound; import Chat.ChatComponent; +using VarIntLong; + class LoginDisconnectPacket extends ClientboundPacket { public var message:ChatComponent; @@ -12,6 +14,7 @@ class LoginDisconnectPacket extends ClientboundPacket { public function send(to:Connection) { var out = beginSend(to); - out.writeString(message.serialize()); + out.writeVarString(message.serialize()); + finishSend(); } } \ No newline at end of file diff --git a/src/packet/clientbound/PlayLoginPacket.hx b/src/packet/clientbound/PlayLoginPacket.hx index b273c68..25b3d85 100644 --- a/src/packet/clientbound/PlayLoginPacket.hx +++ b/src/packet/clientbound/PlayLoginPacket.hx @@ -55,7 +55,7 @@ class PlayLoginPacket extends ClientboundPacket { TAG_Byte('has_ceiling', 0x00), ])]) ], 10)]); - static var REGISTRY_TAG = TAG_Compound('', [REG_DIMS, REG_BIOMES]); + public static var REGISTRY_TAG = TAG_Compound('', [REG_DIMS, REG_BIOMES]); public function send(client:Connection) { var out = this.beginSend(client); diff --git a/src/packet/clientbound/PlayerPositionPacket.hx b/src/packet/clientbound/PlayerPositionPacket.hx new file mode 100644 index 0000000..bcb2bc0 --- /dev/null +++ b/src/packet/clientbound/PlayerPositionPacket.hx @@ -0,0 +1,2 @@ +package packet.clientbound; + diff --git a/src/packet/clientbound/PluginMessageToClient.hx b/src/packet/clientbound/PluginMessageToClient.hx new file mode 100644 index 0000000..522ad82 --- /dev/null +++ b/src/packet/clientbound/PluginMessageToClient.hx @@ -0,0 +1,5 @@ +package packet.clientbound; + +class PluginMessageToClient extends ClientboundPacket { + +} \ No newline at end of file diff --git a/src/packet/clientbound/StatusResponsePacket.hx b/src/packet/clientbound/StatusResponsePacket.hx index 358b46e..985da3d 100644 --- a/src/packet/clientbound/StatusResponsePacket.hx +++ b/src/packet/clientbound/StatusResponsePacket.hx @@ -1,5 +1,6 @@ package packet.clientbound; +import Chat.ChatComponent; import haxe.io.BytesOutput; import haxe.Json; @@ -25,9 +26,7 @@ class StatusResponsePacket extends ClientboundPacket { } ] }, - description: { - text: "hello from haxe", - }, + description: Json.parse(ChatComponent.buildText('hello from haxe!').color(Hex('ff00ff')).serialize()), previewsChat: false } diff --git a/test.nbt b/test.nbt new file mode 100644 index 0000000..5138e40 Binary files /dev/null and b/test.nbt differ