diff --git a/.gitignore b/.gitignore
index 3ff87f1..a0031ec 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
node_modules/
package-lock.json
-.vscode
\ No newline at end of file
+.vscode
+public
\ No newline at end of file
diff --git a/README.md b/README.md
index 320d7c4..c334a34 100644
--- a/README.md
+++ b/README.md
@@ -1,23 +1,33 @@
-# prismarine-template
-[![NPM version](https://img.shields.io/npm/v/prismarine-template.svg)](http://npmjs.com/package/prismarine-template)
-[![Build Status](https://github.com/PrismarineJS/prismarine-template/workflows/CI/badge.svg)](https://github.com/PrismarineJS/prismarine-template/actions?query=workflow%3A%22CI%22)
+# prismarine-web-client
+[![NPM version](https://img.shields.io/npm/v/prismarine-web-client.svg)](http://npmjs.com/package/prismarine-web-client)
+[![Build Status](https://github.com/PrismarineJS/prismarine-web-client/workflows/CI/badge.svg)](https://github.com/PrismarineJS/prismarine-web-client/actions?query=workflow%3A%22CI%22)
[![Discord](https://img.shields.io/badge/chat-on%20discord-brightgreen.svg)](https://discord.gg/GsEFRM8)
-[![Gitter](https://img.shields.io/badge/chat-on%20gitter-brightgreen.svg)](https://gitter.im/PrismarineJS/general)
-[![Irc](https://img.shields.io/badge/chat-on%20irc-brightgreen.svg)](https://irc.gitter.im/)
-[![Try it on gitpod](https://img.shields.io/badge/try-on%20gitpod-brightgreen.svg)](https://gitpod.io/#https://github.com/PrismarineJS/prismarine-template)
+[![Try it on gitpod](https://img.shields.io/badge/try-on%20gitpod-brightgreen.svg)](https://gitpod.io/#https://github.com/PrismarineJS/prismarine-web-client)
-A template repository to make it easy to create new prismarine repo
+A minecraft client running in a web page.
-## Usage
+It runs mineflayer in the browser which connects to a websocket minecraft server.
+It provides a simple websocket to tcp proxy as a backend to make it possible to connect to any minecraft server.
+
+## Features
+
+* display blocks
+* display entities as colored rectangles
+* movement sync
+
+## Roadmap
+
+* chat
+* block placing and breaking
+
+## Run
```js
-const template = require('prismarine-template')
-
-template.helloWorld()
+npm install
+npm run build-start
```
-## API
+Then connect to http://localhost:8080
+
-### helloWorld()
-Prints hello world
diff --git a/dns.js b/dns.js
new file mode 100644
index 0000000..be7206a
--- /dev/null
+++ b/dns.js
@@ -0,0 +1,39 @@
+/* global XMLHttpRequest */
+
+// Custom DNS resolver made by SiebeDW. Powered by google dns.
+// Supported: SRV (not all errors support)
+module.exports.resolveSrv = function (hostname, callback) {
+ const Http = new XMLHttpRequest()
+ const url = `https://dns.google.com/resolve?name=${hostname}&type=SRV`
+ Http.open('GET', url)
+ Http.responseType = 'json'
+ Http.send()
+
+ Http.onload = function () {
+ const response = Http.response
+ if (response.Status === 3) {
+ const err = new Error('querySrv ENOTFOUND')
+ err.code = 'ENOTFOUND'
+ callback(err)
+ return
+ }
+ if (!response.Answer || response.Answer.length < 1) {
+ const err = new Error('querySrv ENODATA')
+ err.code = 'ENODATA'
+ callback(err)
+ return
+ }
+ const willreturn = []
+ response.Answer.forEach(function (object) {
+ const data = object.data.split(' ')
+ willreturn.push({
+ priority: data[0],
+ weight: data[1],
+ port: data[2],
+ name: data[3]
+ })
+ })
+ console.log(willreturn)
+ callback(null, willreturn)
+ }
+}
diff --git a/example.js b/example.js
deleted file mode 100644
index ceadd46..0000000
--- a/example.js
+++ /dev/null
@@ -1,3 +0,0 @@
-const template = require('prismarine-template')
-
-template.helloWorld()
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..beddd64
--- /dev/null
+++ b/index.html
@@ -0,0 +1,30 @@
+
+
+
+ Prismarine Viewer
+
+
+
+
+
+
diff --git a/index.js b/index.js
index 4bd4c75..23ee193 100644
--- a/index.js
+++ b/index.js
@@ -1,9 +1,132 @@
-if (typeof process !== 'undefined' && parseInt(process.versions.node.split('.')[0]) < 14) {
- console.error('Your node version is currently', process.versions.node)
- console.error('Please update it to a version >= 14.x.x from https://nodejs.org/')
- process.exit(1)
-}
+/* global THREE, prompt */
-module.exports.helloWorld = function () {
- console.log('Hello world !')
+// Workaround for process.versions.node not existing in the browser
+process.versions.node = '14.0.0'
+
+const mineflayer = require('mineflayer')
+const { WorldView, Viewer } = require('prismarine-viewer/viewer')
+global.THREE = require('three')
+
+async function main () {
+ const viewDistance = 6
+ const host = prompt('Host', '95.111.249.143')
+ const port = parseInt(prompt('Port', '10000'))
+ const username = prompt('Username', 'pviewer_person')
+ let password = prompt('Password (blank for offline)')
+ password = password === '' ? undefined : password
+ console.log(`connecting to ${host} ${port} with ${username}`)
+
+ const bot = mineflayer.createBot({
+ host,
+ port,
+ username,
+ password
+ })
+
+ bot.on('end', () => {
+ console.log('disconnected')
+ })
+
+ bot.once('spawn', () => {
+ console.log('bot spawned - starting viewer')
+
+ const version = bot.version
+
+ const center = bot.entity.position
+
+ const worldView = new WorldView(bot.world, viewDistance, center)
+
+ // Create three.js context, add to page
+ const renderer = new THREE.WebGLRenderer()
+ renderer.setPixelRatio(window.devicePixelRatio || 1)
+ renderer.setSize(window.innerWidth, window.innerHeight)
+ document.body.appendChild(renderer.domElement)
+
+ // Create viewer
+ const viewer = new Viewer(renderer)
+ viewer.setVersion(version)
+
+ worldView.listenToBot(bot)
+ worldView.init(bot.entity.position)
+
+ function botPosition () {
+ viewer.setFirstPersonCamera(bot.entity.position, bot.entity.yaw, bot.entity.pitch)
+ worldView.updatePosition(bot.entity.position)
+ }
+
+ bot.on('move', botPosition)
+
+ // Link WorldView and Viewer
+ viewer.listen(worldView)
+ viewer.camera.position.set(center.x, center.y, center.z)
+
+ function moveCallback (e) {
+ bot.entity.pitch -= e.movementY * 0.01
+ bot.entity.yaw -= e.movementX * 0.01
+ viewer.setFirstPersonCamera(bot.entity.position, bot.entity.yaw, bot.entity.pitch)
+ }
+ function changeCallback () {
+ if (document.pointerLockElement === renderer.domElement ||
+ document.mozPointerLockElement === renderer.domElement ||
+ document.webkitPointerLockElement === renderer.domElement) {
+ document.addEventListener('mousemove', moveCallback, false)
+ } else {
+ document.removeEventListener('mousemove', moveCallback, false)
+ }
+ }
+ document.addEventListener('pointerlockchange', changeCallback, false)
+ document.addEventListener('mozpointerlockchange', changeCallback, false)
+ document.addEventListener('webkitpointerlockchange', changeCallback, false)
+ renderer.domElement.requestPointerLock = renderer.domElement.requestPointerLock ||
+ renderer.domElement.mozRequestPointerLock ||
+ renderer.domElement.webkitRequestPointerLock
+ document.addEventListener('mousedown', (e) => {
+ renderer.domElement.requestPointerLock()
+ })
+
+ document.addEventListener('contextmenu', (e) => e.preventDefault(), false)
+ document.addEventListener('keydown', (e) => {
+ console.log(e.code)
+ if (e.code === 'KeyW') {
+ bot.setControlState('forward', true)
+ } else if (e.code === 'KeyS') {
+ bot.setControlState('back', true)
+ } else if (e.code === 'KeyA') {
+ bot.setControlState('right', true)
+ } else if (e.code === 'KeyD') {
+ bot.setControlState('left', true)
+ } else if (e.code === 'Space') {
+ bot.setControlState('jump', true)
+ } else if (e.code === 'ShiftLeft') {
+ bot.setControlState('sneak', true)
+ } else if (e.code === 'ControlLeft') {
+ bot.setControlState('sprint', true)
+ }
+ }, false)
+ document.addEventListener('keyup', (e) => {
+ if (e.code === 'KeyW') {
+ bot.setControlState('forward', false)
+ } else if (e.code === 'KeyS') {
+ bot.setControlState('back', false)
+ } else if (e.code === 'KeyA') {
+ bot.setControlState('right', false)
+ } else if (e.code === 'KeyD') {
+ bot.setControlState('left', false)
+ } else if (e.code === 'Space') {
+ bot.setControlState('jump', false)
+ } else if (e.code === 'ShiftLeft') {
+ bot.setControlState('sneak', false)
+ } else if (e.code === 'ControlLeft') {
+ bot.setControlState('sprint', false)
+ }
+ }, false)
+
+ // Browser animation loop
+ const animate = () => {
+ window.requestAnimationFrame(animate)
+ renderer.render(viewer.scene, viewer.camera)
+ }
+ animate()
+ })
}
+main()
diff --git a/package.json b/package.json
index 3f1a05d..50f0e3a 100644
--- a/package.json
+++ b/package.json
@@ -1,31 +1,50 @@
{
- "name": "prismarine-template",
- "version": "1.0.0",
- "description": "A template repository to make it easy to create new prismarine repo",
- "main": "index.js",
- "scripts": {
- "test": "jest --verbose",
- "pretest": "npm run lint",
- "lint": "standard",
- "fix": "standard --fix"
- },
- "repository": {
- "type": "git",
- "url": "git+https://github.com/PrismarineJS/prismarine-template.git"
- },
- "keywords": [
- "prismarine",
- "template"
- ],
- "author": "Romain Beaumont",
- "license": "MIT",
- "bugs": {
- "url": "https://github.com/PrismarineJS/prismarine-template/issues"
- },
- "homepage": "https://github.com/PrismarineJS/prismarine-template#readme",
- "devDependencies": {
- "jest": "^26.1.0",
- "prismarine-template": "file:.",
- "standard": "^16.0.1"
- }
+ "name": "web_client",
+ "private": true,
+ "version": "1.0.0",
+ "description": "web_client",
+ "main": "index.js",
+ "scripts": {
+ "prepare": "webpack",
+ "start": "webpack serve",
+ "prod-start": "node server.js",
+ "build-start": "npm run prepare && npm run prod-start",
+ "lint": "standard",
+ "fix": "standard --fix",
+ "test": "npm run lint && mocha"
+ },
+ "dependencies": {
+ "assert": "^2.0.0",
+ "browserify-zlib": "^0.2.0",
+ "buffer": "^6.0.3",
+ "clean-webpack-plugin": "^3.0.0",
+ "compression": "^1.7.4",
+ "constants-browserify": "^1.0.0",
+ "copy-webpack-plugin": "^7.0.0",
+ "crypto-browserify": "^3.12.0",
+ "events": "^3.2.0",
+ "express": "^4.17.1",
+ "http-browserify": "^1.7.0",
+ "https-browserify": "^1.0.0",
+ "memfs": "^3.2.0",
+ "mineflayer": "^2.39.2",
+ "net-browserify": "^0.2.4",
+ "os-browserify": "^0.3.0",
+ "path-browserify": "^1.0.1",
+ "prismarine-viewer": "^1.14.0",
+ "process": "^0.11.10",
+ "request": "^2.88.2",
+ "stream-browserify": "^3.0.0",
+ "three": "^0.124.0",
+ "timers-browserify": "^2.0.12",
+ "webpack": "^5.11.0",
+ "webpack-cli": "^4.2.0",
+ "webpack-dev-server": "^3.11.0"
+ },
+ "devDependencies": {
+ "http-server": "^0.12.3",
+ "lodash-webpack-plugin": "^0.11.6",
+ "mocha": "^8.3.0",
+ "standard": "^16.0.3"
+ }
}
diff --git a/perf_hooks_replacement.js b/perf_hooks_replacement.js
new file mode 100644
index 0000000..69b0e2e
--- /dev/null
+++ b/perf_hooks_replacement.js
@@ -0,0 +1 @@
+module.exports.performance = window.performance
diff --git a/server.js b/server.js
new file mode 100644
index 0000000..0163072
--- /dev/null
+++ b/server.js
@@ -0,0 +1,76 @@
+const express = require('express')
+const netApi = require('net-browserify')
+const bodyParser = require('body-parser')
+const request = require('request')
+const compression = require('compression')
+
+// Create our app
+const app = express()
+
+app.use(function (req, res, next) {
+ res.header('Access-Control-Allow-Origin', req.get('Origin') || '*')
+ res.header('Access-Control-Allow-Credentials', 'true')
+ res.header('Access-Control-Allow-Methods', 'GET,HEAD,PUT,PATCH,POST,DELETE')
+ res.header('Access-Control-Expose-Headers', 'Content-Length')
+ res.header(
+ 'Access-Control-Allow-Headers',
+ 'Accept, Authorization, Content-Type, X-Requested-With, Range'
+ )
+ if (req.method === 'OPTIONS') {
+ return res.send(200)
+ } else {
+ return next()
+ }
+})
+
+app.use(compression())
+app.use(netApi())
+app.use(express.static('./public'))
+
+app.use(bodyParser.json({ limit: '100kb' }))
+
+app.all('*', function (req, res, next) {
+ // Set CORS headers: allow all origins, methods, and headers: you may want to lock this down in a production environment
+ res.header('Access-Control-Allow-Origin', '*')
+ res.header('Access-Control-Allow-Methods', 'GET, PUT, PATCH, POST, DELETE')
+ res.header(
+ 'Access-Control-Allow-Headers',
+ req.header('access-control-request-headers')
+ )
+
+ if (req.method === 'OPTIONS') {
+ // CORS Preflight
+ res.send()
+ } else {
+ const targetURL = req.header('Target-URL')
+ if (!targetURL) {
+ res.status(404).send({ error: '404 Not Found' })
+ return
+ }
+ const newHeaders = req.headers
+ newHeaders.host = targetURL
+ .replace('https://', '')
+ .replace('http://', '')
+ .split('/')[0]
+ request(
+ {
+ url: targetURL + req.url,
+ method: req.method,
+ json: req.body,
+ headers: req.headers
+ },
+ function (error, response, body) {
+ if (error) {
+ console.error(error)
+ console.error('error: ' + response.statusCode)
+ }
+ // console.log(body);
+ }
+ ).pipe(res)
+ }
+})
+
+// Start the server
+const server = app.listen(8080, function () {
+ console.log('Server listening on port ' + server.address().port)
+})
diff --git a/test/basic.test.js b/test/basic.test.js
index 968ec21..d461258 100644
--- a/test/basic.test.js
+++ b/test/basic.test.js
@@ -1,7 +1,7 @@
-/* eslint-env jest */
+/* eslint-env mocha */
describe('basic', () => {
- test('test', () => {
+ it('test', () => {
})
})
diff --git a/webpack.config.js b/webpack.config.js
new file mode 100644
index 0000000..12b1fa2
--- /dev/null
+++ b/webpack.config.js
@@ -0,0 +1,82 @@
+const webpack = require('webpack')
+const path = require('path')
+const LodashModuleReplacementPlugin = require('lodash-webpack-plugin')
+const CopyPlugin = require('copy-webpack-plugin')
+const { CleanWebpackPlugin } = require('clean-webpack-plugin')
+
+const config = {
+ // devtool: 'inline-source-map',
+ // mode: 'development',
+ mode: 'production',
+ entry: path.resolve(__dirname, './index.js'),
+ output: {
+ path: path.resolve(__dirname, './public'),
+ filename: './index.js'
+ },
+ resolve: {
+ alias: {
+ 'minecraft-protocol': path.resolve(
+ __dirname,
+ 'node_modules/minecraft-protocol/src/index.js'
+ ), // Hack to allow creating the client in a browser
+ express: false,
+ net: 'net-browserify',
+ fs: 'memfs'
+ },
+ fallback: {
+ zlib: require.resolve('browserify-zlib'),
+ stream: require.resolve('stream-browserify'),
+ buffer: require.resolve('buffer/'),
+ events: require.resolve('events/'),
+ assert: require.resolve('assert/'),
+ crypto: require.resolve('crypto-browserify'),
+ path: require.resolve('path-browserify'),
+ constants: require.resolve('constants-browserify'),
+ os: require.resolve('os-browserify/browser'),
+ http: require.resolve('http-browserify'),
+ https: require.resolve('https-browserify'),
+ timers: require.resolve('timers-browserify'),
+ // fs: require.resolve("fs-memory/singleton"),
+ child_process: false,
+ perf_hooks: path.resolve(__dirname, 'perf_hooks_replacement.js'),
+ dns: path.resolve(__dirname, 'dns.js')
+ }
+ },
+ plugins: [
+ // fix "process is not defined" error:
+ new CleanWebpackPlugin(),
+ new webpack.ProvidePlugin({
+ process: 'process/browser'
+ }),
+ new webpack.ProvidePlugin({
+ Buffer: ['buffer', 'Buffer']
+ }),
+ new webpack.NormalModuleReplacementPlugin(
+ /prismarine-viewer[/|\\]viewer[/|\\]lib[/|\\]utils/,
+ './utils.web.js'
+ ),
+ new CopyPlugin({
+ patterns: [
+ { from: 'index.html', to: './index.html' },
+ { from: 'node_modules/prismarine-viewer/public/blocksStates/', to: './blocksStates/' },
+ { from: 'node_modules/prismarine-viewer/public/textures/', to: './textures/' },
+ { from: 'node_modules/prismarine-viewer/public/worker.js', to: './' },
+ { from: 'node_modules/prismarine-viewer/public/supportedVersions.json', to: './' }
+ ]
+ }),
+ new webpack.optimize.ModuleConcatenationPlugin(),
+ new LodashModuleReplacementPlugin()
+ ],
+ devServer: {
+ contentBase: path.resolve(__dirname, './public'),
+ compress: true,
+ inline: true,
+ // open: true,
+ hot: true,
+ watchOptions: {
+ ignored: /node_modules/
+ }
+ }
+}
+
+module.exports = config