From 2e371e741fa90f820ee55e7bcd449ca054c104d7 Mon Sep 17 00:00:00 2001 From: Romain Beaumont Date: Wed, 26 Aug 2015 20:12:39 +0200 Subject: [PATCH] Continue refactoring : client now in a player object, methods added to that player object, progress on #3 --- README.md | 2 +- {docs => doc}/README.md | 0 doc/api.md | 151 ++++++++++++++++++ docs/api.md | 50 ------ index.js | 10 +- lib/clientPlugins/updatePositions.js | 35 ----- lib/entity.js | 24 +++ lib/playerPlugins/communication.js | 16 ++ lib/playerPlugins/login.js | 227 +++++++++++++++++++++++++++ lib/playerPlugins/updatePositions.js | 34 ++++ lib/serverPlugins/communication.js | 16 -- lib/serverPlugins/login.js | 227 --------------------------- lib/serverPlugins/players.js | 2 +- 13 files changed, 460 insertions(+), 334 deletions(-) rename {docs => doc}/README.md (100%) create mode 100644 doc/api.md delete mode 100644 docs/api.md delete mode 100644 lib/clientPlugins/updatePositions.js create mode 100644 lib/entity.js create mode 100644 lib/playerPlugins/communication.js create mode 100644 lib/playerPlugins/login.js create mode 100644 lib/playerPlugins/updatePositions.js delete mode 100644 lib/serverPlugins/communication.js delete mode 100644 lib/serverPlugins/login.js diff --git a/README.md b/README.md index e309d40..588bd6d 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Before running or building it is recommended that you configure the server in co Documentation for how to operate and how to customize your server are coming soon! ## Dev Documentation -For development see [api.md](docs/api.md) +For development see [api.md](doc/api.md) ## Contributors diff --git a/docs/README.md b/doc/README.md similarity index 100% rename from docs/README.md rename to doc/README.md diff --git a/doc/api.md b/doc/api.md new file mode 100644 index 0000000..8aa81cd --- /dev/null +++ b/doc/api.md @@ -0,0 +1,151 @@ +# API + +## Classes + +### CraftyJS.Entity + +Entities represent players, mobs, and objects. They are emitted +in many events, and you can access your own entity with `bot.entity`. + +#### entity.id + +#### entity.type + +Choices: + + * `player` + * `mob` + * `object` + * `global` - lightning + * `orb` - experience orb. + * `other` - introduced with a recent Minecraft update and not yet recognized or used by a third-party mod + +#### entity.username + +If the entity type is `player`, this field will be set. + +#### entity.mobType + +If the entity type is `mob`, this field will be set. + +#### entity.displayName + +Field set for mob and object. A long name in multiple words. + +#### entity.entityType + +Field set for mob and object. The numerical type of the entity (1,2,...) + +#### entity.kind + +Field set for mob and object. The kind of entity (for example Hostile mobs, Passive mobs, NPCs). + +#### entity.name + +Field set for mob and object. A short name for the entity. + +#### entity.objectType + +If the entity type is `object`, this field will be set. + +#### entity.count + +If the entity type is `orb`, this field will be how much experience you +get from collecting the orb. + +#### entity.position + +#### entity.velocity + +#### entity.yaw + +#### entity.pitch + +#### entity.height + +#### entity.onGround + +#### entity.equipment[5] + + * `0` - held item + * `1` - shoes + * `2` - legging + * `3` - torso + * `4` - head + + +#### entity.heldItem + +Equivalent to `entity.equipment[0]`. + +#### entity.metadata + +See http://wiki.vg/Entities#Entity_Metadata_Format for more details. + +## MCServer + +### CraftyJS.createMCServer(options) + +Create and return an instance of the class MCServer. + +options is an object containing the settings + +### Properties + +#### serv.entityMaxId + +Current maximum entity id + +#### serv.players + +Array of connected players + +#### serv.uuidToPlayer + +Object uuid to players + +#### serv.world + +The map + +### Methods + +#### serv.createLog() + +create the log file + +#### serv.log(message) + +logs a `message` + + +## Player + +### Properties + +#### player.entity + +The entity of the player, of type `CraftyJS.Entity` + + +### Methods + +#### player.login() + +login + +### player.others(client) + +return the other players than `player` + +### Low level properties + +#### player._client + +The internal implementation to communicate with a client + +### Low level methods + +### player._writeOthers(packetName, packetFields) + +write to other players than `player` the packet `packetName` with fields `packetFields` \ No newline at end of file diff --git a/docs/api.md b/docs/api.md deleted file mode 100644 index 2d7b15e..0000000 --- a/docs/api.md +++ /dev/null @@ -1,50 +0,0 @@ -# API - -## MCServer - -### CraftyJS.createMCServer(options) - -Create and return an instance of the class MCServer. - -options is an object containing the settings - -### Properties - -#### serv.entityMaxId - -Current maximum entity id - -#### serv.playersConnected - -Array of connected players - -#### serv.uuidToPlayer - -Object uuid to players - -#### serv.world - -The map - -### Methods - -### serv.login(client) - -login `client` - -### serv.createLog() - -create the log file - -### serv.log(message) - -logs a `message` - -### serv.otherClients(client) - -return the other clients than `client` - -### serv.writeOthers(client,packetName, packetFields) - -write to other clients than `client` the packet `packetName` with fields `packetFields` - diff --git a/index.js b/index.js index ed2676f..8e6e43d 100644 --- a/index.js +++ b/index.js @@ -4,7 +4,7 @@ var util = require('util'); var path = require('path'); var requireIndex = require('requireindex'); var serverPlugins = requireIndex(path.join(__dirname, 'lib', 'serverPlugins')); -var clientPlugins = requireIndex(path.join(__dirname, 'lib', 'clientPlugins')); +var playerPlugins = requireIndex(path.join(__dirname, 'lib', 'playerPlugins')); module.exports = { createMCServer:createMCServer @@ -46,9 +46,11 @@ MCServer.prototype.connect = function(options) { }); self._server.on('login', function (client) { - for(var pluginName in clientPlugins) { - clientPlugins[pluginName](self, client, options); + var player={}; + player._client=client; + for(var pluginName in playerPlugins) { + playerPlugins[pluginName](self, player, options); } - self.login(client); + player.login(); }); }; diff --git a/lib/clientPlugins/updatePositions.js b/lib/clientPlugins/updatePositions.js deleted file mode 100644 index 6292332..0000000 --- a/lib/clientPlugins/updatePositions.js +++ /dev/null @@ -1,35 +0,0 @@ -var vec3 = require("vec3"); - -module.exports=inject; - -function toFixedPosition(p) -{ - return new vec3(Math.floor(p.x*32),Math.floor(p.y*32),Math.floor(p.z*32)) -} - -function inject(serv,client) -{ - client.on('position', function (packet) { - var position = new vec3(packet.x, packet.y, packet.z); - var onGround = packet.onGround; - sendRelativePositionChange(client, toFixedPosition(position), onGround); - }); - - function sendRelativePositionChange(client, newPosition, onGround) { - if (serv.uuidToPlayer[client.uuid].position) { - var diff = newPosition.minus(serv.uuidToPlayer[client.uuid].position); - if (diff.distanceTo(new vec3(0, 0, 0)) != 0) { - - serv.writeOthers(client,'rel_entity_move', { - entityId: client.id, - dX: diff.x, - dY: diff.y, - dZ: diff.z, - onGround: onGround - }); - } - } - serv.uuidToPlayer[client.uuid].position = newPosition; - serv.uuidToPlayer[client.uuid].onGround = onGround; - } -} \ No newline at end of file diff --git a/lib/entity.js b/lib/entity.js new file mode 100644 index 0000000..2d3ae8b --- /dev/null +++ b/lib/entity.js @@ -0,0 +1,24 @@ +var Vec3 = require('vec3').Vec3; + +module.exports = Entity; + +function Entity(id) { + this.id = id; + this.type = null; + this.position = new Vec3(0, 0, 0); + this.velocity = new Vec3(0, 0, 0); + this.yaw = 0; + this.pitch = 0; + this.onGround = true; + this.height = 0; + this.effects = {}; + // 0 = held item, 1-4 = armor slot + this.equipment = new Array(5); + this.heldItem = this.equipment[0]; // shortcut to equipment[0] + this.isValid = true; +} + +Entity.prototype.setEquipment = function(index, item) { + this.equipment[index] = item; + this.heldItem = this.equipment[0]; +}; diff --git a/lib/playerPlugins/communication.js b/lib/playerPlugins/communication.js new file mode 100644 index 0000000..5aa2b92 --- /dev/null +++ b/lib/playerPlugins/communication.js @@ -0,0 +1,16 @@ +module.exports=inject; + +function inject(serv,player) +{ + player._writeOthers=function(packetName, packetFields) { + player.getOthers().forEach(function (otherPlayer) { + otherPlayer._client.write(packetName, packetFields); + }); + }; + + player.getOthers = function() { + return serv.players.filter(function (otherPlayer) { + return otherPlayer != player + }); + }; +} \ No newline at end of file diff --git a/lib/playerPlugins/login.js b/lib/playerPlugins/login.js new file mode 100644 index 0000000..fd6f6d8 --- /dev/null +++ b/lib/playerPlugins/login.js @@ -0,0 +1,227 @@ +var Entity=require("../entity"); + +module.exports=inject; + +function transformUuid(s) +{ + return s.split("-").map(function(item) { return parseInt(item, 16); }); +} + + +function inject(serv,player) +{ + player.login=login; + + function addPlayer() + { + serv.entityMaxId++; + player.entity=new Entity(serv.entityMaxId); + serv.players.push(player); + serv.uuidToPlayer[player._client.uuid] = player; + } + + function sendLogin() + { + // send init data so client will start rendering world + player._client.write('login', { + entityId: player.entity.id, + levelType: 'default', + gameMode: 0, + dimension: 0, + difficulty: 0, + reducedDebugInfo: false, + maxPlayers: serv._server.maxPlayers + }); + } + function sendMap() + { + player._client.write('map_chunk', { + x: 0, + z: 0, + groundUp: true, + bitMap: 0xffff, + chunkData: serv.world.dump() + }); + } + + function sendInitialPosition() + { + player._client.write('position', { + x: 6, + y: 53, + z: 6, + yaw: 0, + pitch: 0, + flags: 0x00 + }); + } + + function updateTime() + { + player._client.write('update_time', { + age: [0, 0], + time: [0, 1] + }); + } + + function updateGameState() + { + player._client.write('game_state_change', { + reason: 3, + gameMode: 0 + }); + } + + function announceLogin() + { + player._client.on('chat', function (data) { + var message = '<' + client.username + '>' + ' ' + data.message; + playerChat(message, client.username); + console.log("[INFO] " + message); + serv.log("[INFO] " + message); + }); + } + + function fillTabList() + { + player._writeOthers('player_info',{ + action: 0, + data: [{ + UUID: transformUuid(player._client.uuid), + name: player._client.username, + properties: [], + gamemode: 0, + ping: 1, + hasDisplayName: true, + displayName: player._client.username + }] + }); + + player._client.write('player_info', { + action: 0, + data: serv.players + .map(function (otherPlayer) { + return { + UUID: transformUuid(otherPlayer._client.uuid), + name: otherPlayer._client.username, + properties: [], + gamemode: 0, + ping: 1, + hasDisplayName: true, + displayName: otherPlayer._client.username + }; + }) + }); + } + + function spawn() + { + player.getOthers().forEach(function (otherPlayer) { + var pos = otherPlayer.entity.position; + player._client.write('named_entity_spawn', { + entityId: otherPlayer.entity.id, + playerUUID: transformUuid(otherPlayer._client.uuid), + x: pos ? pos.x : 6 * 32, + y: pos ? pos.y : 53 * 32, + z: pos ? pos.z : 6 * 32, + yaw: 0, + pitch: 0, + currentItem: 0, + metadata: [] + }); + }); + + player._writeOthers('named_entity_spawn',{ + entityId: player.entity.id, + playerUUID: transformUuid(player._client.uuid), + x: 6 * 32, + y: 53 * 32, + z: 6 * 32, + yaw: 0, + pitch: 0, + currentItem: 0, + metadata: [] + }); + } + + function announceJoin() + { + broadcast(player._client.username + ' joined the game.', "yellow"); + var addr = player._client.socket.remoteAddress + ':' + player._client.socket.remotePort; + console.log("[INFO]: " + player._client.username + ' connected', '(' + addr + ')'); + serv.log("[INFO]: " + player._client.username + ' connected', '(' + addr + ')'); + } + + + function playerChat(message, exclude, username) { + var client; + //translate = username ? 'chat.type.text' : 'chat.type.text'; + username = username || ''; + for(var clientId in server.clients) { + if(!serv._server.clients.hasOwnProperty(clientId)) continue; + + client = serv._server.clients[clientId]; + if(client !== exclude) { + var msg = { + "text": username + message + }; + client.write('chat', { message: JSON.stringify(msg), position: 0 }); + } + } + } + + + + + function broadcast(message, color) { + var client; + for(var clientId in serv._server.clients) { + if(!serv._server.clients.hasOwnProperty(clientId)) continue; + + client = serv._server.clients[clientId]; + var msg = { + "text": message, + "color": color + }; + client.write('chat', { message: JSON.stringify(msg), position: 0 }); + } + } + + + function login() + { + if (serv.uuidToPlayer[player._client.uuid]) { + player._client.end("You are already connected"); + return; + } + addPlayer(); + sendLogin(); + sendMap(); + sendInitialPosition(); + + console.log("[INFO]: position written, player spawning..."); + serv.log("[INFO]: position written, player spawning..."); + + updateTime(); + updateGameState(); + announceLogin(); + fillTabList(); + spawn(); + + announceJoin(); + + + var addr = player._client.socket.remoteAddress + ':' + player._client.socket.remotePort; + player._client.on('end', function () { + broadcast(player._client.username + ' joined the game.', "yellow"); + console.log("[INFO]: " + player._client.username + ' disconnected', '(' + addr + ')'); + serv.log("[INFO]: " + player._client.username + ' disconnected', '(' + addr + ')'); + }); + + + player._client.on('error', function (error) { + console.log('[ERR] ' + error.stack); + serv.log('[ERR]: Client: ' + error.stack); + }); + } +} \ No newline at end of file diff --git a/lib/playerPlugins/updatePositions.js b/lib/playerPlugins/updatePositions.js new file mode 100644 index 0000000..41e3364 --- /dev/null +++ b/lib/playerPlugins/updatePositions.js @@ -0,0 +1,34 @@ +var vec3 = require("vec3"); + +module.exports=inject; + +function toFixedPosition(p) +{ + return new vec3(Math.floor(p.x*32),Math.floor(p.y*32),Math.floor(p.z*32)) +} + +function inject(serv,player) +{ + player._client.on('position', function (packet) { + var position = new vec3(packet.x, packet.y, packet.z); + var onGround = packet.onGround; + sendRelativePositionChange(toFixedPosition(position), onGround); + }); + + function sendRelativePositionChange(newPosition, onGround) { + if (player.entity.position.distanceTo(new vec3(0, 0, 0)) != 0) { + var diff = newPosition.minus(player.entity.position); + if (diff.distanceTo(new vec3(0, 0, 0)) != 0) { + player._writeOthers('rel_entity_move', { + entityId: player.entity.id, + dX: diff.x, + dY: diff.y, + dZ: diff.z, + onGround: onGround + }); + } + } + player.entity.position = newPosition; + player.entity.onGround = onGround; + } +} \ No newline at end of file diff --git a/lib/serverPlugins/communication.js b/lib/serverPlugins/communication.js deleted file mode 100644 index 67b249c..0000000 --- a/lib/serverPlugins/communication.js +++ /dev/null @@ -1,16 +0,0 @@ -module.exports=inject; - -function inject(serv) -{ - serv.writeOthers=function(client,packetName, packetFields) { - serv.otherClients(client).forEach(function (otherClient) { - otherClient.write(packetName, packetFields); - }); - }; - - serv.otherClients = function(client) { - return serv.playersConnected.filter(function (otherClient) { - return otherClient != client - }); - }; -} \ No newline at end of file diff --git a/lib/serverPlugins/login.js b/lib/serverPlugins/login.js deleted file mode 100644 index 1cc4764..0000000 --- a/lib/serverPlugins/login.js +++ /dev/null @@ -1,227 +0,0 @@ -module.exports=inject; - -function transformUuid(s) -{ - return s.split("-").map(function(item) { return parseInt(item, 16); }); -} - - -function inject(serv) -{ - serv.login=login; - - function addPlayer(client) - { - serv.entityMaxId++; - client.id = serv.entityMaxId; - serv.playersConnected.push(client); - console.log(client.uuid); - serv.uuidToPlayer[client.uuid] = client; - } - - function sendLogin(client) - { - // send init data so client will start rendering world - client.write('login', { - entityId: client.id, - levelType: 'default', - gameMode: 0, - dimension: 0, - difficulty: 0, - reducedDebugInfo: false, - maxPlayers: serv._server.maxPlayers - }); - } - function sendMap(client) - { - client.write('map_chunk', { - x: 0, - z: 0, - groundUp: true, - bitMap: 0xffff, - chunkData: serv.world.dump() - }); - } - - function sendInitialPosition(client) - { - client.write('position', { - x: 6, - y: 53, - z: 6, - yaw: 0, - pitch: 0, - flags: 0x00 - }); - } - - function updateTime(client) - { - client.write('update_time', { - age: [0, 0], - time: [0, 1] - }); - } - - function updateGameState(client) - { - client.write('game_state_change', { - reason: 3, - gameMode: 0 - }); - } - - function announceLogin(client) - { - client.on('chat', function (data) { - var message = '<' + client.username + '>' + ' ' + data.message; - playerChat(message, client.username); - console.log("[INFO] " + message); - serv.log("[INFO] " + message); - }); - } - - function fillTabList(client) - { - serv.otherClients(client).forEach(function (otherClient) { - otherClient.write('player_info', { - action: 0, - data: [{ - UUID: transformUuid(client.uuid), - name: client.username, - properties: [], - gamemode: 0, - ping: 1, - hasDisplayName: true, - displayName: client.username - }] - }); - }); - - client.write('player_info', { - action: 0, - data: serv.playersConnected - .map(function (otherClient) { - return { - UUID: transformUuid(otherClient.uuid), - name: otherClient.username, - properties: [], - gamemode: 0, - ping: 1, - hasDisplayName: true, - displayName: otherClient.username - }; - }) - }); - } - - function spawn(client) - { - serv.otherClients(client).forEach(function (otherClient) { - var pos = serv.uuidToPlayer[otherClient.uuid].position; - client.write('named_entity_spawn', { - entityId: otherClient.id, - playerUUID: transformUuid(otherClient.uuid), - x: pos ? pos.x : 6 * 32, - y: pos ? pos.y : 53 * 32, - z: pos ? pos.z : 6 * 32, - yaw: 0, - pitch: 0, - currentItem: 0, - metadata: [] - }); - otherClient.write('named_entity_spawn', { - entityId: client.id, - playerUUID: transformUuid(client.uuid), - x: 6 * 32, - y: 53 * 32, - z: 6 * 32, - yaw: 0, - pitch: 0, - currentItem: 0, - metadata: [] - }); - }); - } - - function announceJoin(client) - { - broadcast(client.username + ' joined the game.', "yellow"); - var addr = client.socket.remoteAddress + ':' + client.socket.remotePort; - console.log("[INFO]: " + client.username + ' connected', '(' + addr + ')'); - serv.log("[INFO]: " + client.username + ' connected', '(' + addr + ')'); - } - - - function playerChat(message, exclude, username) { - var client; - //translate = username ? 'chat.type.text' : 'chat.type.text'; - username = username || ''; - for(var clientId in server.clients) { - if(!serv._server.clients.hasOwnProperty(clientId)) continue; - - client = serv._server.clients[clientId]; - if(client !== exclude) { - var msg = { - "text": username + message - }; - client.write('chat', { message: JSON.stringify(msg), position: 0 }); - } - } - } - - - - - function broadcast(message, color) { - var client; - for(var clientId in serv._server.clients) { - if(!serv._server.clients.hasOwnProperty(clientId)) continue; - - client = serv._server.clients[clientId]; - var msg = { - "text": message, - "color": color - }; - client.write('chat', { message: JSON.stringify(msg), position: 0 }); - } - } - - - function login(client) - { - if (serv.uuidToPlayer[client.uuid]) { - client.end("You are already connected"); - return; - } - addPlayer(client); - sendLogin(client); - sendMap(client); - sendInitialPosition(client); - - console.log("[INFO]: position written, player spawning..."); - serv.log("[INFO]: position written, player spawning..."); - - updateTime(client); - updateGameState(client); - announceLogin(client); - fillTabList(client); - spawn(client); - - announceJoin(client); - - - var addr = client.socket.remoteAddress + ':' + client.socket.remotePort; - client.on('end', function () { - broadcast(client.username + ' joined the game.', "yellow"); - console.log("[INFO]: " + client.username + ' disconnected', '(' + addr + ')'); - serv.log("[INFO]: " + client.username + ' disconnected', '(' + addr + ')'); - }); - - - client.on('error', function (error) { - console.log('[ERR] ' + error.stack); - serv.log('[ERR]: Client: ' + error.stack); - }); - } -} \ No newline at end of file diff --git a/lib/serverPlugins/players.js b/lib/serverPlugins/players.js index 9b58872..31f01aa 100644 --- a/lib/serverPlugins/players.js +++ b/lib/serverPlugins/players.js @@ -3,6 +3,6 @@ module.exports=inject; function inject(serv) { serv.entityMaxId=0; - serv.playersConnected=[]; + serv.players=[]; serv.uuidToPlayer={}; } \ No newline at end of file