Skip to content

Commit

Permalink
Refactor: allow usage in browser (#633)
Browse files Browse the repository at this point in the history
* Refactor: allow usage in browser

* move method to player

* fix bad require

* fix standard

* add basic tsconfig

* remove unused dep: didn't notice because of packages resorted in d

* [skip ci] rm comment

* remove mocking remoteAddress

* make event: playerChangeRenderDistance, up docs

* introduce pluginsReady prop + event

* revert auto-fix changes

* doc player.save

* dont init player on its disconnect

* seems got the problem

* [skip ci] remove server.options

* don't mock socket

* add serv.formatMessage

---------

Co-authored-by: Romain Beaumont <romain.rom1@gmail.com>
  • Loading branch information
zardoy and rom1504 authored Dec 31, 2023
1 parent c1d381e commit 768b69b
Show file tree
Hide file tree
Showing 16 changed files with 585 additions and 449 deletions.
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
package-lock=false
public-hoist-pattern=*
81 changes: 60 additions & 21 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,19 @@
- [MCServer](#mcserver)
- [Flying-squid.createMCServer(options)](#flying-squidcreatemcserveroptions)
- [Properties](#properties)
- [serv.pluginsReady](#servpluginsready)
- [serv.entityMaxId](#serventitymaxid)
- [serv.players](#servplayers)
- [serv.uuidToPlayer](#servuuidtoplayer)
- [serv.overworld](#servoverworld)
- [serv.netherworld](#servnetherworld)
- [serv.endworld](#servendworld)
- [serv.entities](#serventities)
- [serv.bannedPlayers](#servbannedplayers)
- [serv.time](#servtime)
- [serv.tickCount](#servtickcount)
- [serv.doDaylightCycle](#servdodaylightcycle)
- [serv.plugins](#servplugins)
- [serv.commands](#servcommands)
- [serv.tabComplete](#servtabcomplete)
- [Events](#events)
- ["error" (error)](#error-error)
Expand All @@ -38,7 +39,9 @@
- ["newPlayer" (player)](#newplayer-player)
- ["banned" (banner,bannedUsername,reason)](#banned-bannerbannedusernamereason)
- ["tick" (count)](#tick-count)
- ["pluginsReady"](#pluginsready)
- [Methods](#methods)
- [serv.formatMessage(message)](#servformatmessagemessage)
- [serv.createLog()](#servcreatelog)
- [serv.log(message)](#servlogmessage)
- [serv.info(message)](#servinfomessage)
Expand Down Expand Up @@ -66,12 +69,13 @@
- [server.playNoteBlock(world, position, pitch)](#serverplaynoteblockworld-position-pitch)
- [server.getNote(note)](#servergetnotenote)
- [server.emitParticle(particle, world, position, opt)](#serveremitparticleparticle-world-position-opt)
- [serv.selectorString(str, pos, world, allowUser = true, ctxEntityId)](#servselectorstringstr-pos-world-allowuser--true-ctxentityid)
- [Low level methods](#low-level-methods)
- [server._writeAll(packetName, packetFields)](#server_writeallpacketname-packetfields)
- [server._writeArray(packetName, packetFields, playerArray)](#server_writearraypacketname-packetfields-playerarray)
- [server._writeNearby(packetName, packetFields, loc)](#server_writenearbypacketname-packetfields-loc)
- [serv._loadPlayerChunk(chunkX, chunkZ, player)](#serv_loadplayerchunk)
- [serv._unloadPlayerChunk(chunkX, chunkZ, player)](#serv_unloadplayerchunk)
- [serv._loadPlayerChunk(chunkX, chunkZ, player)](#serv_loadplayerchunkchunkx-chunkz-player)
- [serv._unloadPlayerChunk(chunkX, chunkZ, player)](#serv_unloadplayerchunkchunkx-chunkz-player)
- [Entity](#entity-1)
- [Properties](#properties-1)
- [entity.id](#entityid)
Expand Down Expand Up @@ -125,6 +129,8 @@
- ["disconnected"](#disconnected)
- ["chat" (message)](#chat-message)
- ["kicked" (kicker,reason)](#kicked-kickerreason)
- ["change_world"](#change_world)
- ["playerChangeRenderDistance" (newDistance=player.view, unloadFirst=false)](#playerchangerenderdistance-newdistanceplayerview-unloadfirstfalse)
- ["positionChanged"](#positionchanged)
- [Behaviors](#behaviors-1)
- ["move"](#move-1)
Expand All @@ -144,6 +150,7 @@
- ["attack"](#attack)
- ["requestRespawn"](#requestrespawn)
- [Methods](#methods-2)
- [player.save()](#playersave)
- [player.login()](#playerlogin)
- [player.ban(reason)](#playerbanreason)
- [player.kick(reason)](#playerkickreason)
Expand All @@ -152,14 +159,15 @@
- [player.changeBlock(position,blockType,blockData)](#playerchangeblockpositionblocktypeblockdata)
- [player.sendBlock(position,blockType,blockData)](#playersendblockpositionblocktypeblockdata)
- [player.sendBlockAction(position,actionId,actionParam,blockType)](#playersendblockactionpositionactionidactionparamblocktype)
- [player.sendBrand(brand = 'flying-squid')](#playersendbrandbrand--flying-squid)
- [player.sendInitialPosition()](#playersendinitialposition)
- [player.setGameMode(gameMode)](#playersetgamemodegamemode)
- [player.handleCommand(command)](#playerhandlecommandcommand)
- [player.setBlock(position,blockType,blockData)](#playersetblockpositionblocktypeblockdata)
- [player.setBlockAction(position,actionId,actionParam)](#playersetblockactionpositionactionidactionparam)
- [player.updateHealth(health)](#playerupdatehealthhealth)
- [player.updateFood(health)](#playerupdatefoodfood)
- [player.updateFoodSaturation(health)](#playerupdatefoodsaturationfoodsaturation)
- [player.updateFood(food)](#playerupdatefoodfood)
- [player.updateFoodSaturation(foodSaturation)](#playerupdatefoodsaturationfoodsaturation)
- [player.changeWorld(world, opt)](#playerchangeworldworld-opt)
- [player.spawnAPlayer(spawnedPlayer)](#playerspawnaplayerspawnedplayer)
- [player.updateAndSpawnNearbyPlayers()](#playerupdateandspawnnearbyplayers)
Expand All @@ -171,8 +179,8 @@
- [Low level properties](#low-level-properties)
- [player._client](#player_client)
- [Low level methods](#low-level-methods-1)
- [player._unloadChunk(chunkX, chunkZ)](#player_unloadchunk)
- [player._unloadAllChunks](#player_unloadallchunks)
- [player._unloadChunk(chunkX, chunkZ)](#player_unloadchunkchunkx-chunkz)
- [player._unloadAllChunks()](#player_unloadallchunks)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

Expand Down Expand Up @@ -254,6 +262,10 @@ Options is an object containing the following properties:

### Properties

#### serv.pluginsReady

`true` if all async plugins are ready and the server is ready to accept connections.

#### serv.entityMaxId

The current maximum ID (i.e. the last entity that was spawned has that id)
Expand Down Expand Up @@ -321,7 +333,7 @@ serv.commands.add({
parse(str) {
const args = str.split(' ');
if(args.length != 1) return false;

return {pseudo:args[0]};
},
action({pseudo}, ctx) {
Expand Down Expand Up @@ -368,8 +380,16 @@ Fires when `player` login, allow external player plugins.

Fires when one tick has passed (default is 50ms). count is the total world ticks (same as serv.tickCount)

#### "pluginsReady"

Emitted when `serv.pluginsReady` is set to `true`.

### Methods

#### serv.formatMessage(message)

You can override this function so you can process the message before sending it to the console.

#### serv.createLog()

Creates the log file
Expand Down Expand Up @@ -478,7 +498,7 @@ Sends a block action to all players of the same world.

#### server.playSound(sound, world, position, opt)

Plays `sound` (string, google "minecraft sound list") to all players in `opt.radius`.
Plays `sound` (string, google "minecraft sound list") to all players in `opt.radius`.
If position is null, will play at the location of every player (taking into account whitelist and blacklist).

Opt:
Expand Down Expand Up @@ -644,16 +664,16 @@ List of entities that the entity believes is nearby.

Behaviors are very interesting. Let me explain to you how they work:

Behaviors are a special type of event. They are editable and allow defaults to be cancellable making the powerful
for plugins to take control of and interact with each other. Three different events get called
Behaviors are a special type of event. They are editable and allow defaults to be cancellable making the powerful
for plugins to take control of and interact with each other. Three different events get called
for a behavior:
- EVENTNAME_cancel
- EVENTNAME
- EVENTNAME_done

EVENTNAME_cancel passses the paramaters `data` (object of all info about behavior. Changing the data could have effects on outcome) and `cancel`, a function. This event is run before the default action. If `cancel()` is called, it will cancel the default action. More on this later.

EVENTNAME passes `data` as well as `cancelled` so plugins can check if the default behavior has been cancelled. This is event is run
EVENTNAME passes `data` as well as `cancelled` so plugins can check if the default behavior has been cancelled. This is event is run
before the default action.

EVENTNAME_done passes `data` and `cancelled`. This event is run before the default action.
Expand All @@ -674,9 +694,9 @@ player.on('move', ({position}, cancelled) => {
})
```

When a player normally moves, the server saves their position and sends it to all clients. Therefore, if a "move" behavior was truly cancelled,
the player would be able to move freely while the server and other players would see the player stationary. This doesn't happen because
behaviors can have "default cancel functions". In the case of a player's "move", the default cancel function sends them back where they
When a player normally moves, the server saves their position and sends it to all clients. Therefore, if a "move" behavior was truly cancelled,
the player would be able to move freely while the server and other players would see the player stationary. This doesn't happen because
behaviors can have "default cancel functions". In the case of a player's "move", the default cancel function sends them back where they
came from. To prevent this from happening, use the "preventDefaultCancel" paramater: cancel(false);

Plugin C
Expand All @@ -686,10 +706,10 @@ player.on('move_cancel', ({position}, cancel) => {
});
```

If we keep Plugin B and replace Plugin A with Plugin C, we'll see that the player can move freely but will not receive the
If we keep Plugin B and replace Plugin A with Plugin C, we'll see that the player can move freely but will not receive the
word "HI" and other players will be unable to see their movements.

Finally, there is hidden cancel. This is the second parameter in cancel, and allows plugins to hide the fact that they cancelled
Finally, there is hidden cancel. This is the second parameter in cancel, and allows plugins to hide the fact that they cancelled
the default action from other plugins. It's best not to use this, but I know somebody will someday need this.

Plugin D
Expand All @@ -699,7 +719,7 @@ player.on('move_cancel', ({position}, cancel) => {
})
```

Using Plugin B and D together, the player will be able to move freely and will be spammed with "HI", however the server will not store
Using Plugin B and D together, the player will be able to move freely and will be spammed with "HI", however the server will not store
their position and other players will not see the player move.

#### FORMAT
Expand Down Expand Up @@ -794,7 +814,7 @@ Level of xp the player has. Set this with player.setXpLevel()

### Events

#### "connected"
#### "connected"

Fires when the player is connected

Expand All @@ -814,6 +834,14 @@ Fires when the player says `message`.

`kicker` kick the player with `reason`

#### "change_world"

World of the player has been changed.

#### "playerChangeRenderDistance" (newDistance=player.view, unloadFirst=false)

Emit this event to change player render distance.

#### "positionChanged"

fires when the position changes in small amounts (walking, running, or flying)
Expand Down Expand Up @@ -1004,6 +1032,17 @@ Cancelled: Nothing. You monster.

### Methods

#### player.save()

If `worldFolder` option is set, save player's data into `<worldFolder>/playerdata/<UUID>.dat`. Returns promise.
Example: save all players data to disk:

```js
for (const player of serv.players) {
player.save()
}
```

#### player.login()

login
Expand All @@ -1028,7 +1067,7 @@ sends `message` to the player

change the block at position `position` to `blockType` and `blockData`

this will not change the block for the user himself. It is mainly useful when a user places a block
this will not change the block for the user himself. It is mainly useful when a user places a block
and only needs to send it to other players on the server

#### player.sendBlock(position,blockType,blockData)
Expand All @@ -1041,7 +1080,7 @@ this will not make any changes on the server's world and only sends it to the us

Set the block action at position `position` to `actionId` and `actionParam`.

``blockType`` is only required when the block at the location is a fake block.
``blockType`` is only required when the block at the location is a fake block.
This will only be caused by using ``player.sendBlock``.

This will not make any changes to the server's world and only sends it to the user as a local action.
Expand Down
30 changes: 16 additions & 14 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@
"email": "romain.rom1@gmail.com"
}
],
"bin": {
"flying-squid": "app.js"
},
"bin": "app.js",
"scripts": {
"prepublishOnly": "cp docs/README.md README.md",
"lint": "standard test/*.test.js src/**/*.js src/**/**/*.js src/*.js examples/*.js *.js",
Expand All @@ -21,7 +19,11 @@
"test": "npm run mocha_test",
"pretest": "npm run lint"
},
"keywords": [],
"keywords": [
"browser",
"minecraft server",
"java minecraft server"
],
"license": "MIT",
"engines": {
"node": ">=8"
Expand All @@ -32,21 +34,21 @@
"diamond-square": "^1.2.0",
"emit-then": "^2.0.0",
"event-promise": "^0.0.1",
"exit-hook": "^2.2.1",
"flatmap": "^0.0.3",
"long": "^5.1.0",
"minecraft-data": "^3.0.0",
"minecraft-protocol": "^1.15.0",
"mkdirp": "^2.1.3",
"minecraft-data": "^3.42.1",
"minecraft-protocol": "^1.44.0",
"moment": "^2.10.6",
"needle": "^2.5.0",
"node-gzip": "^1.1.2",
"prismarine-chunk": "^1.20.1",
"prismarine-entity": "^2.0.0",
"prismarine-item": "^1.5.0",
"prismarine-nbt": "^2.0.0",
"prismarine-provider-anvil": "^2.3.0",
"prismarine-windows": "^2.0.0",
"prismarine-world": "^3.0.0",
"prismarine-chunk": "^1.34.0",
"prismarine-entity": "^2.2.0",
"prismarine-item": "^1.14.0",
"prismarine-nbt": "^2.2.1",
"prismarine-provider-anvil": "^2.7.0",
"prismarine-windows": "^2.8.0",
"prismarine-world": "^3.6.2",
"random-seed": "^0.3.0",
"range": "^0.0.3",
"readline": "^1.3.0",
Expand Down
12 changes: 12 additions & 0 deletions src/globals.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// make process.platform also accept browser
declare namespace NodeJS {
interface Process {
platform: string;
browser?: boolean
}

}
interface NodeRequire {
// webpack bundling
context: (path: string, deep: boolean, filter: RegExp) => { keys: () => string[]; (id: string): any };
}
27 changes: 17 additions & 10 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
if (typeof process !== 'undefined' && parseInt(process.versions.node.split('.')[0]) < 18) {
if (typeof process !== 'undefined' && !process.browser && process.platform !== 'browser' && parseInt(process.versions.node.split('.')[0]) < 18) {
console.error('[\x1b[31mCRITICAL\x1b[0m] Node.JS 18 or newer is required')
console.error('[\x1b[31mCRITICAL\x1b[0m] You can download the new version from https://nodejs.org/')
console.error(`[\x1b[31mCRITICAL\x1b[0m] Your current Node.JS version is: ${process.versions.node}`)
process.exit(1)
}

const mc = require('minecraft-protocol')
const { createServer } = require('minecraft-protocol')

const EventEmitter = require('events').EventEmitter
const path = require('path')
const requireIndex = require('./lib/requireindex')
const supportedVersions = require('./lib/version').supportedVersions
const Command = require('./lib/command')
const plugins = require('./lib/plugins')
require('emit-then').register()
if (process.env.NODE_ENV === 'dev') {
require('longjohn')
Expand Down Expand Up @@ -38,8 +38,10 @@ function createMCServer (options) {

class MCServer extends EventEmitter {
constructor () {
plugins.initPlugins()
super()
this._server = null
this.pluginsReady = false
}

connect (options) {
Expand All @@ -48,13 +50,18 @@ class MCServer extends EventEmitter {
throw new Error(`Version ${version.minecraftVersion} is not supported.`)
}
this.supportFeature = feature => supportFeature(feature, version.majorVersion)

const plugins = requireIndex(path.join(__dirname, 'lib', 'plugins'))
this.commands = new Command({})
this._server = mc.createServer(options)
Object.keys(plugins)
.filter(pluginName => plugins[pluginName].server !== undefined)
.forEach(pluginName => plugins[pluginName].server(this, options))
this._server = createServer(options)

const promises = []
for (const plugin of plugins.builtinPlugins) {
promises.push(plugin.server?.(this, options))
}
Promise.all(promises).then(() => {
this.emit('pluginsReady')
this.pluginsReady = true
})

if (options.logging === true) this.createLog()
this._server.on('error', error => this.emit('error', error))
this._server.on('listening', () => this.emit('listening', this._server.socketServer.address().port))
Expand Down
Loading

0 comments on commit 768b69b

Please sign in to comment.