Minecraft Websocket 服务器

这两天学习了nodejs,于是想着做点什么来实践一下,正好对Minecraft 基岩版的websocket功能比较感兴趣,于是做了一个websocket服务器的demo,可以用来获得游戏内的信息以及执行命令。

不少人已经在bds服务器上尝试过使用wsserver/connect命令了吧?但是这条命令不知道为什么是不能直接使用的,要想使用这条命令我们需要做以下准备。

做完上述操作之后就可以使用/connect命令了,当然如果只是输入/connect,会提示Command version mismatch,这里我们暂时不管它。

我使用的使nodejs+typescript的方式创建了这个demo,只有3个文件。注释已经写的很清楚了,有问题就留下评论吧。

app.ts server.ts packet.ts

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
//app.ts 用于启动程序,端口可以在这里设置
import {WSServer} from './server';
let server = new WSServer();
server.listen(6800);</code></pre>

//server.ts 包含ws服务器类,用于创建server对象
import { Packet,UnSubscribe,Subscribe,CommandPacket } from './packet/packet';
import { Server } from 'ws';
import {EventEmitter} from 'events';
import { Socket } from 'net';
import {createInterface} from 'readline';
//import {v4} from 'uuid';
let uuid4 = require('uuid/v4');
//创建一个负责输入输出的对象
const rl = createInterface({
    input: process.stdin,
    output: process.stdout
});

export class WSServer extends EventEmitter{
    //strictPropertyInitialization:false
    _socket:Server;

    public listen(port:number):void{
        let server:WSServer = this;

        this._socket = new Server({port:port});
        //当有客户端连接时回调
        console.log("WS开始监听" + port);
        this._socket.on("connection",socket => {
            console.log("客户端连接");

            //发送subscribe包建立监听
            registerSubscribe(socket,"PlayerMessage");
            registerSubscribe(socket,"BlockPlaced");

            //socket收到信息时回调
            socket.on("message", message => {
                console.log("接收到客户端的信息");

                let data = JSON.parse(message as string);
                let msgPurpose = data.header.messagePurpose;
                if(msgPurpose == "error"){
                    console.log("出现错误:", data);
                }
                else if(msgPurpose == "event"){
                    console.log(data.body.eventName);
                    //发送unsubscribe包,取消对该事件的监听,具体表现为服务端只能获得一次客户端的方块放置事件
                    if (data.body.eventName == "BlockPlaced"){
                        sendCommand(socket,<code>say 发送unsubscribe包解除对方块放置事件的监听</code>);
                        unRegisterSubscribe(socket,"BlockPlaced");
                    }

                }
                else if(msgPurpose == "commandResponse"){
                    console.log("命令返回:" + data.body.statusCode);
                }

            });
            //接收到控制台的发送信息事件
            server.on("sendMsg",msg=>{
                console.log("[sendMsg]:" + msg);
                sendCommand(socket,<code>say ${msg}</code>);
            });

            socket.on("error",err=>{
                console.log("建立的socket出现错误" + err.message);
            });

            socket.on("close", () => {console.log("客户端断开连接")});

        });

        this._socket.on("error",error=>{
            console.log(<code>出现错误${error}</code>);
        });

        //持续获得用户输入
        rl.on('line', (input) => {
            console.log(<code>[consoleInput]${input}</code>);
            let [cmd,content] = input.split(":");
            if(cmd == "send"){
                //触发sendMsg事件
                server.emit("sendMsg",content);
            }
            else if(cmd == "exit"){
                process.exit();
            }
        });

    }
}

function registerSubscribe(socket:any,eventName:string):void{
    let packet:Subscribe = new Subscribe(eventName);
    socket.send(JSON.stringify(packet));
}

function unRegisterSubscribe(socket:any,eventName:string):void{
    let packet:UnSubscribe = new UnSubscribe(eventName);
    socket.send(JSON.stringify(packet));
}

function sendCommand(socket:any,command:string):void{
    let packet:CommandPacket = new CommandPacket(command);
    socket.send(JSON.stringify(packet));
}</code></pre>
<pre><code class="language-javascript">//packet 这里是三种数据包的类
let uuid4 = require('uuid/v4');
//数据包
export abstract class Packet{
    body:{};
    header:{};
}

export class Subscribe extends Packet{
    body:{
        eventName: string;
    };
    header:{
        requestId: string;
        messagePurpose: string;
        version: number;
        messageType: string;
    };

    constructor(eventName:string){
        super();
        this.body = {
            eventName:eventName
        };

        this.header = {
            requestId: uuid4(),
            messagePurpose: "subscribe",
            version: 1,
            messageType: "commandRequest"
        };
    }
}

export class UnSubscribe extends Packet{
    body:{
        eventName: string;
    };
    header:{
        requestId: string;
        messagePurpose: string;
        version: number;
        messageType: string;
    }

    constructor(eventName:string,uuid = uuid4()){
        super();

        this.body = {
            eventName: eventName
        };

        this.header = {
            requestId: uuid,
            messagePurpose: "unsubscribe",
            version: 1,
            messageType: "commandRequest"
        }

    }
}

export class CommandPacket extends Packet{
    body:{};
    header:{};

    constructor(cmd:string){
        super();
        this.body = {
            origin: 
            {
                type: "player"
            },
            commandLine: cmd,
            version: 1
        };

        this.header = {
            requestId: uuid4(),
            messagePurpose: "commandRequest",
            version: 1,
            messageType: "commandRequest"
        };
    }
}

如果想部署一个ws服务器试试看

1
2
3
4
git clone https://github.com/haojie06/BedrockWsServer.git
cd BedrockWsServer
npm install
node build/app.js

#默认使用6800端口```

之后我们在游戏里即可使用 /connect ip:6800 连接ws服务器了,但是不知道为什么,我在自己的电脑(win10)上运行的ws服务器可以通过浏览器访问,但是无法在游戏中连接,在我将程序放到远程的服务器上才正常连接。

在这个demo中,我尝试发送/接收了mc中文wiki ws教程中提到的几种数据包

在后台输入exit退出程序。

写这个小程序算是加深了一些我对nodejs的了解,Minecraft基岩版的websocket虽然看上去可以获得很多信息,但是在我看来,它的功能还是相当有限的,因为有几个巨大的限制

因为这些限制,我暂时想不出什么有趣的应用方向(也许之后我会慢慢扩展这个项目),不过github上依旧有几个比较成熟的基于Minecraft websocket的项目可以了解一下

comments powered by Disqus
本站访客数:
Built with Hugo
主题 StackJimmy 设计