-
Notifications
You must be signed in to change notification settings - Fork 14
/
Copy pathclientmc.js
196 lines (161 loc) · 6.66 KB
/
clientmc.js
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
193
194
195
196
'use strict';
const websocket_stream = require('websocket-stream');
const webworkify = require('webworkify');
const workerstream = require('workerstream');
const typedArrayToBuffer = require('typedarray-to-buffer');
const mcData = require('./mcdata');
const EventEmitter = require('events').EventEmitter;
module.exports = function(game, opts) {
return new ClientMC(game, opts);
};
module.exports.pluginInfo = {
loadAfter: [
'voxel-land',
'voxel-player',
'voxel-registry',
'voxel-console',
'voxel-commands',
'voxel-reach',
'voxel-decals',
'voxel-sfx',
'voxel-carry',
'voxel-use',
'voxel-inventory-hotbar',
]
};
class ClientMC extends EventEmitter
{
constructor(game, opts) {
super();
this.game = game;
this.opts = opts;
this.registry = game.plugins.get('voxel-registry');
if (!this.registry) throw new Error('voxel-clientmc requires voxel-registry plugin');
if (this.game.voxels.voxelIndex) { // ndarray voxel removes this in https://github.com/maxogden/voxel/pull/18 TODO: better detection?
throw new Error('voxel-clientmc requires voxel-engine with ndarray support');
}
this.console = game.plugins.get('voxel-console'); // optional
this.commands = game.plugins.get('voxel-commands'); // optional
this.reachPlugin = game.plugins.get('voxel-reach');
if (!this.reachPlugin) throw new Error('voxel-clientmc requires voxel-reach plugin');
this.decalsPlugin = game.plugins.get('voxel-decals');
if (!this.decalsPlugin) throw new Error('voxel-clientmc requires voxel-decals plugin');
this.sfxPlugin = game.plugins.get('voxel-sfx'); // optional
this.carryPlugin = game.plugins.get('voxel-carry'); // optional
this.usePlugin = game.plugins.get('voxel-use');
if (!this.usePlugin) throw new Error('voxel-clientmc requires voxel-use plugin');
this.hotbar = game.plugins.get('voxel-inventory-hotbar'); // optional
opts.url = opts.url || 'ws://'+document.location.hostname+':24444/server';
// Translate network block indices to our block names
// http://minecraft.gamepedia.com/Data_values#Block_IDs http://minecraft-ids.grahamedgecombe.com/
// TODO: get translation table from network protocol? I think Forge supports custom blocks with the map sent over the network?
opts.mcBlocks = opts.mcBlocks || mcData.mcBlockID2Voxel;
// webworker -> main thread handler callbacks (augmented by plugins)
this.handlers = {
packet: (event) => {
this.websocketStream.write(typedArrayToBuffer(event.data));
},
error: (event) => {
this.console.log('Disconnected with error: ' + event.error);
this.game.plugins.disable('voxel-clientmc');
},
close: (event) => {
this.console.log('Websocket closed');
this.game.plugins.disable('voxel-clientmc');
}
};
require('./position.js')(this);
require('./kick.js')(this);
require('./chunks.js')(this);
require('./dig.js')(this);
require('./use.js')(this);
require('./block_break_animation.js')(this);
require('./sound.js')(this);
require('./chat.js')(this);
require('./inventory.js')(this);
require('./resource_pack.js')(this);
this.enable();
}
enable() {
// Register our own blocks and items which aren't provided by other more specialized plugins
const inertBlockProps = mcData.inertBlockProps;
Object.keys(inertBlockProps).forEach((name) => {
const props = inertBlockProps[name];
this.registry.registerBlock(name, props);
});
const inertItemProps = mcData.inertItemProps;
Object.keys(inertItemProps).forEach((name) => {
const props = inertItemProps[name];
this.registry.registerItem(name, props);
});
window.addEventListener('hashchange', this.onhashchange = (event) => {
console.log('Hash changed, reloading: ',event);
// Reload the page to allow reconnecting to the server with the new credentials
// TODO: only reconnect to server, without refreshing
window.location.reload();
});
// Begin connecting to server after voxel-engine is initialized,
// since it shows chunks (game.showChunk) which requires engine initialization,
// but plugins are "enabled" before the engine fully is
this.game.on('engine-init', this.connectServer.bind(this));
}
disable() {
this.log('voxel-clientmc disabling');
this.game.voxels.removeListener('missingChunk', this.missingChunk);
this.game.plugins.get('voxel-console').widget.removeListener('input', this.onConsoleInput);
this.ws.end();
if (this.clearPositionUpdateTimer) this.clearPositionUpdateTimer();
window.removeEventListener('hashchange', this.onhashchange);
// TODO: unregister inert items/blocks
}
// TODO: refactor further into modules
connectServer() {
this.log('voxel-clientmc connecting...');
this.game.plugins.disable('voxel-land'); // also provides chunks, use ours instead
//this.game.plugins.get('voxel-player').homePosition = [-248, 77, -198] // can't do this TODO
//this.game.plugins.get('voxel-player').moveTo -251, 81, -309
// login credential
let username;
const hash = document.location.hash;
if (hash.length < 2) {
// try anonymous auth
username = 'user1';
} else {
username = hash.substring(1); // remove #
}
this.websocketStream = websocket_stream(this.opts.url);
this.websocketStream.on('connect', () => {
console.log('websocketStream connected, launching worker');
this.mfworker = webworkify(require('./mf-worker.js'));
this.mfworkerStream = workerstream(this.mfworker);
// pass some useful data to the worker
this.mfworkerStream.write({cmd: 'setVariables',
username: username,
translateBlockIDs: this.translateBlockIDs,
reverseBlockIDs: this.reverseBlockIDs,
defaultBlockID: this.defaultBlockID,
chunkSize: this.game.chunkSize,
chunkPad: this.game.chunkPad,
chunkPadHalf: this.game.voxels.chunkPadHalf,
chunkMask: this.game.voxels.chunkMask,
chunkBits: this.game.voxels.chunkBits,
arrayTypeSize: this.game.arrayType.BYTES_PER_ELEMENT
});
// handle outgoing mfworker data and commands
this.mfworkerStream.on('data', (event) => {
//console.log('mfworkerStream event',event);
const cmd = event.cmd;
const f = this.handlers[cmd];
if (!f) {
console.log('Unhandled mfworker cmd',cmd,event);
return;
}
// call method on ourthis with arguments
f.call(this, event);
});
// pipe incoming wsmc data to mfworker
this.websocketStream.pipe(this.mfworkerStream);
});
this.emit('connectServer');
}
}