implement main menu, implement textfield widget, implement create world screen, implement splash screen
@@ -11,7 +11,6 @@
|
||||
|
||||
<div id="content">
|
||||
<div id="canvas-container" class="fullscreen"></div>
|
||||
<span id="pre-status">Loading scripts...</span>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
@@ -2,10 +2,6 @@ import Minecraft from './net/minecraft/client/Minecraft.js';
|
||||
|
||||
class Start {
|
||||
|
||||
constructor(preStatusElementId) {
|
||||
this.preStatusElement = document.getElementById(preStatusElementId);
|
||||
}
|
||||
|
||||
loadTextures(textures) {
|
||||
let resources = [];
|
||||
let index = 0;
|
||||
@@ -37,10 +33,15 @@ class Start {
|
||||
"terrain/terrain.png",
|
||||
"terrain/sun.png",
|
||||
"terrain/moon.png",
|
||||
"char.png"
|
||||
"char.png",
|
||||
"gui/title/minecraft.png",
|
||||
"gui/title/background/panorama_0.png",
|
||||
"gui/title/background/panorama_1.png",
|
||||
"gui/title/background/panorama_2.png",
|
||||
"gui/title/background/panorama_3.png",
|
||||
"gui/title/background/panorama_4.png",
|
||||
"gui/title/background/panorama_5.png"
|
||||
]).then((resources) => {
|
||||
this.preStatusElement.remove();
|
||||
|
||||
// Launch actual game on canvas
|
||||
window.app = new Minecraft(canvasWrapperId, resources);
|
||||
});
|
||||
@@ -48,4 +49,4 @@ class Start {
|
||||
}
|
||||
|
||||
// Launch game
|
||||
new Start("pre-status").launch("canvas-container");
|
||||
new Start().launch("canvas-container");
|
||||
@@ -1,5 +1,6 @@
|
||||
import GuiIngameMenu from "./gui/screens/GuiIngameMenu.js";
|
||||
import Keyboard from "../util/Keyboard.js";
|
||||
import Minecraft from "./Minecraft.js";
|
||||
|
||||
export default class GameWindow {
|
||||
|
||||
@@ -29,17 +30,16 @@ export default class GameWindow {
|
||||
this.canvasItems = document.createElement('canvas');
|
||||
this.wrapper.appendChild(this.canvasItems);
|
||||
|
||||
// On resize
|
||||
let scope = this;
|
||||
let mouseDownInterval = null;
|
||||
|
||||
// Request focus
|
||||
document.onclick = function () {
|
||||
if (scope.minecraft.currentScreen === null) {
|
||||
scope.requestFocus();
|
||||
document.onclick = () => {
|
||||
if (this.minecraft.currentScreen === null) {
|
||||
this.requestFocus();
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('resize', _ => scope.updateWindowSize(), false);
|
||||
window.addEventListener('resize', _ => this.updateWindowSize(), false);
|
||||
|
||||
// Focus listener
|
||||
document.addEventListener('pointerlockchange', _ => this.onFocusChanged(), false);
|
||||
@@ -50,7 +50,7 @@ export default class GameWindow {
|
||||
|
||||
// Handle mouse move on screen
|
||||
if (!(minecraft.currentScreen === null)) {
|
||||
minecraft.currentScreen.mouseDragged(event.x / scope.scaleFactor, event.y / scope.scaleFactor, event.code);
|
||||
minecraft.currentScreen.mouseDragged(event.x / this.scaleFactor, event.y / this.scaleFactor, event.code);
|
||||
}
|
||||
}, false);
|
||||
|
||||
@@ -58,43 +58,51 @@ export default class GameWindow {
|
||||
document.addEventListener('mouseup', event => {
|
||||
// Handle mouse release on screen
|
||||
if (!(minecraft.currentScreen === null)) {
|
||||
minecraft.currentScreen.mouseReleased(event.x / scope.scaleFactor, event.y / scope.scaleFactor, event.code);
|
||||
minecraft.currentScreen.mouseReleased(event.x / this.scaleFactor, event.y / this.scaleFactor, event.code);
|
||||
}
|
||||
|
||||
clearInterval(mouseDownInterval);
|
||||
}, false);
|
||||
|
||||
// Losing focus event
|
||||
this.canvas.addEventListener("mouseout", function () {
|
||||
this.canvas.addEventListener("mouseout", () => {
|
||||
if (minecraft.currentScreen === null) {
|
||||
minecraft.displayScreen(new GuiIngameMenu());
|
||||
}
|
||||
|
||||
clearInterval(mouseDownInterval);
|
||||
});
|
||||
|
||||
// Mouse buttons
|
||||
document.addEventListener('mousedown', function (event) {
|
||||
document.addEventListener('mousedown', event => {
|
||||
// Create sound engine (It has to be created after user interaction)
|
||||
if (!minecraft.soundManager.isCreated()) {
|
||||
minecraft.soundManager.create(minecraft.worldRenderer);
|
||||
}
|
||||
|
||||
// Handle in-game mouse click
|
||||
if (!scope.isMobile) {
|
||||
if (!this.isMobile) {
|
||||
minecraft.onMouseClicked(event.button);
|
||||
|
||||
// Start interval to repeat the mouse event
|
||||
clearInterval(mouseDownInterval);
|
||||
mouseDownInterval = setInterval(() => minecraft.onMouseClicked(event.button), 250);
|
||||
}
|
||||
|
||||
// Handle mouse click on screen
|
||||
if (!(minecraft.currentScreen === null)) {
|
||||
minecraft.currentScreen.mouseClicked(event.x / scope.scaleFactor, event.y / scope.scaleFactor, event.code);
|
||||
minecraft.currentScreen.mouseClicked(event.x / this.scaleFactor, event.y / this.scaleFactor, event.code);
|
||||
}
|
||||
}, false);
|
||||
|
||||
// Mouse scroll
|
||||
document.addEventListener('wheel', function (event) {
|
||||
document.addEventListener('wheel', (event) => {
|
||||
let delta = Math.sign(event.deltaY);
|
||||
minecraft.onMouseScroll(delta);
|
||||
}, false);
|
||||
|
||||
// Keyboard interaction with screen
|
||||
window.addEventListener('keydown', function (event) {
|
||||
window.addEventListener('keydown', (event) => {
|
||||
if (event.code === "F11") {
|
||||
return; // Toggle fullscreen
|
||||
}
|
||||
@@ -104,7 +112,7 @@ export default class GameWindow {
|
||||
|
||||
if (!(minecraft.currentScreen === null)) {
|
||||
// Handle key type on screen
|
||||
minecraft.currentScreen.keyTyped(event.code);
|
||||
minecraft.currentScreen.keyTyped(event.code, event.key);
|
||||
} else if (event.code === 'Escape') {
|
||||
minecraft.displayScreen(new GuiIngameMenu());
|
||||
} else {
|
||||
@@ -112,24 +120,35 @@ export default class GameWindow {
|
||||
}
|
||||
});
|
||||
|
||||
// Keyboard interaction with screen
|
||||
window.addEventListener('keyup', (event) => {
|
||||
// Prevent key
|
||||
event.preventDefault();
|
||||
|
||||
if (!(minecraft.currentScreen === null)) {
|
||||
// Handle key release on screen
|
||||
minecraft.currentScreen.keyReleased(event.code);
|
||||
}
|
||||
});
|
||||
|
||||
// Touch interaction
|
||||
let touchStart;
|
||||
window.addEventListener('touchstart', function (event) {
|
||||
window.addEventListener('touchstart', (event) => {
|
||||
for (let i = 0; i < event.touches.length; i++) {
|
||||
let touch = event.touches[i];
|
||||
|
||||
let x = touch.pageX;
|
||||
let y = touch.pageY;
|
||||
|
||||
let isRightHand = x > scope.wrapper.offsetWidth / 2;
|
||||
let isRightHand = x > this.wrapper.offsetWidth / 2;
|
||||
|
||||
if (isRightHand) {
|
||||
touchStart = Date.now();
|
||||
} else {
|
||||
let tileSize = scope.wrapper.offsetWidth / 8;
|
||||
let tileSize = this.wrapper.offsetWidth / 8;
|
||||
|
||||
let tileX = 0;
|
||||
let tileY = scope.wrapper.offsetHeight - tileSize * 3;
|
||||
let tileY = this.wrapper.offsetHeight - tileSize * 3;
|
||||
|
||||
let relX = x - tileX;
|
||||
let relY = y - tileY;
|
||||
@@ -169,7 +188,7 @@ export default class GameWindow {
|
||||
|
||||
// Touch movement
|
||||
let prevTouch;
|
||||
window.addEventListener('touchmove', function (event) {
|
||||
window.addEventListener('touchmove', (event) => {
|
||||
for (let i = 0; i < event.touches.length; i++) {
|
||||
let touch = event.touches[i];
|
||||
|
||||
@@ -177,20 +196,20 @@ export default class GameWindow {
|
||||
let y = touch.pageY;
|
||||
|
||||
// Right hand
|
||||
let isRightHand = x > scope.wrapper.offsetWidth / 2;
|
||||
let isRightHand = x > this.wrapper.offsetWidth / 2;
|
||||
|
||||
if (isRightHand) {
|
||||
// Player movement
|
||||
if (prevTouch) {
|
||||
scope.mouseMotionX = (x - prevTouch.pageX) * 10;
|
||||
scope.mouseMotionY = -(y - prevTouch.pageY) * 10;
|
||||
this.mouseMotionX = (x - prevTouch.pageX) * 10;
|
||||
this.mouseMotionY = -(y - prevTouch.pageY) * 10;
|
||||
}
|
||||
|
||||
prevTouch = touch;
|
||||
}
|
||||
}
|
||||
});
|
||||
window.addEventListener('touchend', function (event) {
|
||||
window.addEventListener('touchend', (event) => {
|
||||
// Break block
|
||||
if (!prevTouch && touchStart && (Date.now() - touchStart) < 1000) {
|
||||
minecraft.onMouseClicked(2);
|
||||
@@ -204,7 +223,7 @@ export default class GameWindow {
|
||||
let touch = event.changedTouches[i];
|
||||
|
||||
// Left hand
|
||||
let isLeftHand = touch.pageX < scope.wrapper.offsetWidth / 2;
|
||||
let isLeftHand = touch.pageX < this.wrapper.offsetWidth / 2;
|
||||
|
||||
// Release all keys
|
||||
if (isLeftHand) {
|
||||
@@ -326,4 +345,20 @@ export default class GameWindow {
|
||||
return false;
|
||||
}
|
||||
|
||||
close() {
|
||||
this.openUrl(Minecraft.URL_GITHUB);
|
||||
}
|
||||
|
||||
openUrl(url, newTab) {
|
||||
if (newTab) {
|
||||
window.open(url, '_blank').focus();
|
||||
} else {
|
||||
window.location = url;
|
||||
}
|
||||
}
|
||||
|
||||
async getClipboardText() {
|
||||
return navigator.clipboard.readText();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,18 +5,22 @@ import WorldRenderer from "./render/WorldRenderer.js";
|
||||
import ScreenRenderer from "./render/gui/ScreenRenderer.js";
|
||||
import ItemRenderer from "./render/gui/ItemRenderer.js";
|
||||
import IngameOverlay from "./gui/IngameOverlay.js";
|
||||
import GuiLoadingScreen from "./gui/screens/GuiLoadingScreen.js";
|
||||
import PlayerEntity from "./entity/PlayerEntity.js";
|
||||
import SoundManager from "./sound/SoundManager.js";
|
||||
import World from "./world/World.js";
|
||||
import Block from "./world/block/Block.js";
|
||||
import BoundingBox from "../util/BoundingBox.js";
|
||||
import {BlockRegistry} from "./world/block/BlockRegistry.js";
|
||||
import FontRenderer from "./render/gui/FontRenderer.js";
|
||||
import GrassColorizer from "./render/GrassColorizer.js";
|
||||
import GuiMainMenu from "./gui/screens/GuiMainMenu.js";
|
||||
import GuiLoadingScreen from "./gui/screens/GuiLoadingScreen.js";
|
||||
import * as THREE from "../../../../../libraries/three.module.js";
|
||||
|
||||
export default class Minecraft {
|
||||
|
||||
static VERSION = "1.0.0"
|
||||
static URL_GITHUB = "https://github.com/labystudio/js-minecraft";
|
||||
|
||||
/**
|
||||
* Create Minecraft instance and render it on a canvas
|
||||
*/
|
||||
@@ -25,6 +29,8 @@ export default class Minecraft {
|
||||
|
||||
this.currentScreen = null;
|
||||
this.loadingScreen = null;
|
||||
this.world = null;
|
||||
this.player = null;
|
||||
|
||||
this.fps = 0;
|
||||
|
||||
@@ -45,10 +51,6 @@ export default class Minecraft {
|
||||
// Create current screen and overlay
|
||||
this.ingameOverlay = new IngameOverlay(this, this.window);
|
||||
|
||||
// Display loading screen
|
||||
this.loadingScreen = new GuiLoadingScreen();
|
||||
this.loadingScreen.setTitle("Building terrain...");
|
||||
|
||||
this.frames = 0;
|
||||
this.lastTime = Date.now();
|
||||
|
||||
@@ -66,18 +68,10 @@ export default class Minecraft {
|
||||
// Update window size
|
||||
this.window.updateWindowSize();
|
||||
|
||||
// Create world
|
||||
this.world = new World(this);
|
||||
this.worldRenderer.scene.add(this.world.group);
|
||||
|
||||
// Create sound manager
|
||||
this.soundManager = new SoundManager();
|
||||
|
||||
// Create player
|
||||
this.player = new PlayerEntity(this, this.world);
|
||||
this.world.addEntity(this.player);
|
||||
|
||||
this.displayScreen(this.loadingScreen);
|
||||
this.displayScreen(new GuiMainMenu());
|
||||
|
||||
// Initialize
|
||||
this.init();
|
||||
@@ -87,23 +81,53 @@ export default class Minecraft {
|
||||
// Start render loop
|
||||
this.running = true;
|
||||
this.requestNextFrame();
|
||||
}
|
||||
|
||||
// Load spawn chunks and respawn player
|
||||
this.world.findSpawn();
|
||||
this.world.loadSpawnChunks();
|
||||
this.player.respawn();
|
||||
loadWorld(world) {
|
||||
if (world === null) {
|
||||
this.worldRenderer.reset();
|
||||
this.itemRenderer.reset();
|
||||
|
||||
this.world = null;
|
||||
this.player = null;
|
||||
this.loadingScreen = null;
|
||||
this.displayScreen(new GuiMainMenu());
|
||||
} else {
|
||||
// Display loading screen
|
||||
this.loadingScreen = new GuiLoadingScreen();
|
||||
this.loadingScreen.setTitle("Building terrain...");
|
||||
this.displayScreen(this.loadingScreen);
|
||||
|
||||
// Create world
|
||||
this.world = world;
|
||||
this.worldRenderer.scene.add(this.world.group);
|
||||
|
||||
// Create player
|
||||
this.player = new PlayerEntity(this, this.world);
|
||||
this.world.addEntity(this.player);
|
||||
|
||||
// Load spawn chunks and respawn player
|
||||
this.world.findSpawn();
|
||||
this.world.loadSpawnChunks();
|
||||
this.player.respawn();
|
||||
}
|
||||
}
|
||||
|
||||
hasInGameFocus() {
|
||||
return this.window.mouseLocked && this.currentScreen === null;
|
||||
}
|
||||
|
||||
isInGame() {
|
||||
return this.world !== null && this.worldRenderer !== null && this.player !== null;
|
||||
}
|
||||
|
||||
requestNextFrame() {
|
||||
let scope = this;
|
||||
requestAnimationFrame(function () {
|
||||
if (scope.running) {
|
||||
scope.requestNextFrame();
|
||||
scope.onLoop();
|
||||
requestAnimationFrame(() => {
|
||||
if (this.running) {
|
||||
this.requestNextFrame();
|
||||
this.onLoop();
|
||||
} else {
|
||||
this.window.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -132,30 +156,34 @@ export default class Minecraft {
|
||||
}
|
||||
|
||||
onRender(partialTicks) {
|
||||
// Player rotation
|
||||
if (!this.isPaused()) {
|
||||
this.player.turn(this.window.mouseMotionX, this.window.mouseMotionY);
|
||||
if (this.isInGame()) {
|
||||
// Player rotation
|
||||
if (!this.isPaused()) {
|
||||
this.player.turn(this.window.mouseMotionX, this.window.mouseMotionY);
|
||||
|
||||
this.window.mouseMotionX = 0;
|
||||
this.window.mouseMotionY = 0;
|
||||
this.window.mouseMotionX = 0;
|
||||
this.window.mouseMotionY = 0;
|
||||
}
|
||||
|
||||
// Update lights
|
||||
while (this.world.updateLights()) {
|
||||
// Empty
|
||||
}
|
||||
|
||||
// Render the game
|
||||
if (this.hasInGameFocus()) {
|
||||
this.worldRenderer.render(partialTicks);
|
||||
}
|
||||
}
|
||||
|
||||
// Update lights
|
||||
while (this.world.updateLights()) {
|
||||
// Empty
|
||||
}
|
||||
|
||||
// Render the game
|
||||
if (this.hasInGameFocus()) {
|
||||
this.worldRenderer.render(partialTicks);
|
||||
}
|
||||
// Render current screen
|
||||
this.screenRenderer.render(partialTicks);
|
||||
this.itemRenderer.render(partialTicks);
|
||||
}
|
||||
|
||||
displayScreen(screen) {
|
||||
if (typeof screen === "undefined") {
|
||||
console.log("Tried to display an undefined screen");
|
||||
console.error("Tried to display an undefined screen");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -183,7 +211,7 @@ export default class Minecraft {
|
||||
}
|
||||
|
||||
onTick() {
|
||||
if (!this.isPaused()) {
|
||||
if (this.isInGame() && !this.isPaused()) {
|
||||
// Tick world
|
||||
this.world.onTick();
|
||||
|
||||
@@ -194,13 +222,35 @@ export default class Minecraft {
|
||||
this.player.onUpdate();
|
||||
}
|
||||
|
||||
// Tick the screen
|
||||
if (this.currentScreen !== null) {
|
||||
this.currentScreen.updateScreen();
|
||||
}
|
||||
|
||||
// Update loading progress
|
||||
if (!(this.loadingScreen === null)) {
|
||||
let progress = Math.max(0, 1 - this.world.lightUpdateQueue.length / 10000);
|
||||
if (this.loadingScreen !== null && this.isInGame()) {
|
||||
let cameraChunkX = Math.floor(this.player.x) >> 4;
|
||||
let cameraChunkZ = Math.floor(this.player.z) >> 4;
|
||||
|
||||
let renderDistance = WorldRenderer.RENDER_DISTANCE;
|
||||
let requiredChunks = Math.pow(renderDistance * 2 - 1, 2);
|
||||
let loadedChunks = this.world.chunks.size;
|
||||
|
||||
// Load chunks and count
|
||||
setTimeout(() => {
|
||||
for (let x = -renderDistance + 1; x < renderDistance; x++) {
|
||||
for (let z = -renderDistance + 1; z < renderDistance; z++) {
|
||||
this.world.getChunkAt(cameraChunkX + x, cameraChunkZ + z);
|
||||
}
|
||||
}
|
||||
}, 0);
|
||||
|
||||
// Update progress
|
||||
let progress = 1 / requiredChunks * Math.max(0, loadedChunks - this.world.lightUpdateQueue.length / 1000);
|
||||
this.loadingScreen.setProgress(progress);
|
||||
|
||||
// Finish loading
|
||||
if (progress >= 1) {
|
||||
if (progress >= 0.99) {
|
||||
this.loadingScreen = null;
|
||||
this.displayScreen(null);
|
||||
}
|
||||
@@ -308,10 +358,35 @@ export default class Minecraft {
|
||||
}
|
||||
|
||||
onMouseScroll(delta) {
|
||||
this.player.inventory.shiftSelectedSlot(delta);
|
||||
if (this.isInGame()) {
|
||||
this.player.inventory.shiftSelectedSlot(delta);
|
||||
}
|
||||
}
|
||||
|
||||
isPaused() {
|
||||
return !this.hasInGameFocus() && this.loadingScreen === null;
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.running = false;
|
||||
this.worldRenderer.reset();
|
||||
this.itemRenderer.reset();
|
||||
this.screenRenderer.reset();
|
||||
}
|
||||
|
||||
getThreeTexture(id) {
|
||||
if (!(id in this.resources)) {
|
||||
console.error("Texture not found: " + id);
|
||||
return;
|
||||
}
|
||||
|
||||
let image = this.resources[id];
|
||||
let canvas = document.createElement('canvas');
|
||||
let context = canvas.getContext("2d");
|
||||
canvas.width = image.width;
|
||||
canvas.height = image.height;
|
||||
context.imageSmoothingEnabled = false;
|
||||
context.drawImage(image, 0, 0, image.width, image.height);
|
||||
return new THREE.CanvasTexture(canvas);
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,10 @@ export default class Gui {
|
||||
this.minecraft.fontRenderer.drawString(stack, string, x - this.getStringWidth(stack, string) / 2, y, color);
|
||||
}
|
||||
|
||||
drawRightString(stack, string, x, y, color = -1) {
|
||||
this.minecraft.fontRenderer.drawString(stack, string, x - this.getStringWidth(stack, string), y, color);
|
||||
}
|
||||
|
||||
drawString(stack, string, x, y, color = -1) {
|
||||
this.minecraft.fontRenderer.drawString(stack, string, x, y, color);
|
||||
}
|
||||
@@ -43,7 +47,7 @@ export default class Gui {
|
||||
drawBackground(stack, texture, width, height, scale = 2) {
|
||||
let pattern = stack.createPattern(texture, "repeat");
|
||||
stack.save();
|
||||
stack.filter = "brightness(50%)";
|
||||
stack.filter = "brightness(28%)";
|
||||
stack.scale(scale, scale);
|
||||
stack.rect(0, 0, width / scale, height / scale);
|
||||
stack.fillStyle = pattern;
|
||||
|
||||
@@ -12,6 +12,7 @@ export default class GuiScreen extends Gui {
|
||||
this.minecraft = minecraft;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.textureBackground = this.getTexture("gui/background.png");
|
||||
|
||||
this.init();
|
||||
}
|
||||
@@ -32,7 +33,15 @@ export default class GuiScreen extends Gui {
|
||||
}
|
||||
}
|
||||
|
||||
keyTyped(key) {
|
||||
updateScreen() {
|
||||
for (let i in this.buttonList) {
|
||||
let button = this.buttonList[i];
|
||||
|
||||
button.onTick();
|
||||
}
|
||||
}
|
||||
|
||||
keyTyped(key, character) {
|
||||
if (key === "Escape") {
|
||||
this.minecraft.displayScreen(null);
|
||||
return true;
|
||||
@@ -41,7 +50,17 @@ export default class GuiScreen extends Gui {
|
||||
for (let i in this.buttonList) {
|
||||
let button = this.buttonList[i];
|
||||
|
||||
button.keyTyped(key);
|
||||
button.keyTyped(key, character);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
keyReleased(key) {
|
||||
for (let i in this.buttonList) {
|
||||
let button = this.buttonList[i];
|
||||
|
||||
button.keyReleased(key);
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -72,4 +91,14 @@ export default class GuiScreen extends Gui {
|
||||
button.mouseDragged(mouseX, mouseY, mouseButton);
|
||||
}
|
||||
}
|
||||
|
||||
drawDefaultBackground(stack) {
|
||||
if (this.minecraft.isInGame()) {
|
||||
// Render transparent background
|
||||
this.drawRect(stack, 0, 0, this.width, this.height, 'black', 0.6);
|
||||
} else {
|
||||
// Render dirt background
|
||||
this.drawBackground(stack, this.textureBackground, this.width, this.height);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,6 @@ export default class IngameOverlay extends Gui {
|
||||
// Debug
|
||||
this.drawString(stack, fps + " fps," + " " + lightUpdates + " light updates," + " " + chunkUpdates + " chunk updates", 1, 1);
|
||||
this.drawString(stack, x + ", " + y + ", " + z + " (" + (x >> 4) + ", " + (y >> 4) + ", " + (z >> 4) + ")", 1, 1 + 9);
|
||||
this.drawString(stack, "Light: " + lightLevel, 1, 1 + 9 + 10);
|
||||
}
|
||||
|
||||
renderCrosshair(stack, x, y) {
|
||||
|
||||
@@ -16,33 +16,33 @@ export default class GuiControls extends GuiScreen {
|
||||
|
||||
let settings = this.minecraft.settings;
|
||||
|
||||
let scope = this;
|
||||
this.buttonList.push(new GuiSliderButton("Mouse Sensitivity", settings.sensitivity, 50, 150, this.width / 2 - 100, this.height / 2 - 55, 200, 20, function (value) {
|
||||
let y = this.height / 2 - 50;
|
||||
this.buttonList.push(new GuiSliderButton("Mouse Sensitivity", settings.sensitivity, 50, 150, this.width / 2 - 100, y, 200, 20, value => {
|
||||
settings.sensitivity = value;
|
||||
}).setDisplayNameBuilder(function (name, value) {
|
||||
return name + ": " + value + "%";
|
||||
}));
|
||||
|
||||
this.buttonList.push(new GuiKeyButton("Crouch", settings.crouching, this.width / 2 - 100, this.height / 2 - 30, 200, 20, function (key) {
|
||||
this.buttonList.push(new GuiKeyButton("Crouch", settings.crouching, this.width / 2 - 100, y + 24, 200, 20, key => {
|
||||
settings.crouching = key;
|
||||
}));
|
||||
|
||||
this.buttonList.push(new GuiKeyButton("Sprint", settings.sprinting, this.width / 2 - 100, this.height / 2 - 5, 200, 20, function (key) {
|
||||
this.buttonList.push(new GuiKeyButton("Sprint", settings.sprinting, this.width / 2 - 100, y + 24 * 2, 200, 20, key => {
|
||||
settings.sprinting = key;
|
||||
}));
|
||||
|
||||
this.buttonList.push(new GuiKeyButton("Toggle Perspective", settings.togglePerspective, this.width / 2 - 100, this.height / 2 + 20, 200, 20, function (key) {
|
||||
this.buttonList.push(new GuiKeyButton("Toggle Perspective", settings.togglePerspective, this.width / 2 - 100, y + 24 * 3, 200, 20, key => {
|
||||
settings.togglePerspective = key;
|
||||
}));
|
||||
|
||||
this.buttonList.push(new GuiButton("Done", this.width / 2 - 100, this.height / 2 + 70, 200, 20, function () {
|
||||
scope.minecraft.displayScreen(scope.previousScreen);
|
||||
this.buttonList.push(new GuiButton("Done", this.width / 2 - 100, y + 110, 200, 20, () => {
|
||||
this.minecraft.displayScreen(this.previousScreen);
|
||||
}));
|
||||
}
|
||||
|
||||
drawScreen(stack, mouseX, mouseY, partialTicks) {
|
||||
// Background
|
||||
this.drawRect(stack, 0, 0, this.width, this.height, 'black', 0.6);
|
||||
this.drawDefaultBackground(stack);
|
||||
|
||||
// Title
|
||||
this.drawCenteredString(stack, "Controls", this.width / 2, 50);
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import GuiScreen from "../GuiScreen.js";
|
||||
import GuiButton from "../widgets/GuiButton.js";
|
||||
import World from "../../world/World.js";
|
||||
import GuiTextField from "../widgets/GuiTextField.js";
|
||||
import Random from "../../../util/Random.js";
|
||||
|
||||
export default class GuiCreateWorld extends GuiScreen {
|
||||
|
||||
constructor(previousScreen) {
|
||||
super();
|
||||
|
||||
this.previousScreen = previousScreen;
|
||||
}
|
||||
|
||||
init() {
|
||||
super.init();
|
||||
|
||||
let y = this.height / 2 - 50;
|
||||
|
||||
this.fieldSeed = new GuiTextField(this.width / 2 - 100, y + 30, 200, 20)
|
||||
this.fieldSeed.maxLength = 30;
|
||||
this.buttonList.push(this.fieldSeed);
|
||||
|
||||
this.buttonList.push(new GuiButton("Create New World", this.width / 2 - 155, y + 110, 150, 20, () => {
|
||||
let seed = this.fieldSeed.getText();
|
||||
if (seed.length === 0) {
|
||||
seed = new Random().nextLong();
|
||||
}
|
||||
this.minecraft.loadWorld(new World(this.minecraft, seed));
|
||||
}));
|
||||
this.buttonList.push(new GuiButton("Cancel", this.width / 2 + 5, y + 110, 150, 20, () => {
|
||||
this.minecraft.displayScreen(this.previousScreen);
|
||||
}));
|
||||
}
|
||||
|
||||
drawScreen(stack, mouseX, mouseY, partialTicks) {
|
||||
// Background
|
||||
this.drawDefaultBackground(stack);
|
||||
|
||||
// Title
|
||||
this.drawCenteredString(stack, "Create New World", this.width / 2, 50);
|
||||
|
||||
let y = this.height / 2 - 50;
|
||||
|
||||
// Seed
|
||||
this.drawString(stack, "Seed for the World Generator", this.width / 2 - 100, y + 17, -6250336);
|
||||
this.drawString(stack, "Leave blank for a random seed", this.width / 2 - 100, y + 55, -6250336);
|
||||
|
||||
super.drawScreen(stack, mouseX, mouseY, partialTicks);
|
||||
}
|
||||
|
||||
onClose() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import GuiButton from "../widgets/GuiButton.js";
|
||||
import GuiControls from "./GuiControls.js";
|
||||
import GuiScreen from "../GuiScreen.js";
|
||||
import GuiSettings from "./GuiSettings.js";
|
||||
import GuiOptions from "./GuiOptions.js";
|
||||
|
||||
export default class GuiIngameMenu extends GuiScreen {
|
||||
|
||||
@@ -12,17 +11,17 @@ export default class GuiIngameMenu extends GuiScreen {
|
||||
init() {
|
||||
super.init();
|
||||
|
||||
let scope = this;
|
||||
this.buttonList.push(new GuiButton("Back to game", this.width / 2 - 100, this.height / 2 - 20, 200, 20, function () {
|
||||
scope.minecraft.displayScreen(null);
|
||||
let y = this.height / 2 - 30;
|
||||
this.buttonList.push(new GuiButton("Back to game", this.width / 2 - 100, y, 200, 20, () => {
|
||||
this.minecraft.displayScreen(null);
|
||||
}));
|
||||
|
||||
this.buttonList.push(new GuiButton("Settings...", this.width / 2 - 100, this.height / 2 + 20, 98, 20, function () {
|
||||
scope.minecraft.displayScreen(new GuiSettings(scope));
|
||||
this.buttonList.push(new GuiButton("Options...", this.width / 2 - 100, y + 24, 200, 20, () => {
|
||||
this.minecraft.displayScreen(new GuiOptions(this));
|
||||
}));
|
||||
|
||||
this.buttonList.push(new GuiButton("Controls...", this.width / 2 + 2, this.height / 2 + 20, 98, 20, function () {
|
||||
scope.minecraft.displayScreen(new GuiControls(scope));
|
||||
this.buttonList.push(new GuiButton("Save and Quit to Title", this.width / 2 - 100, y + 70, 200, 20, () => {
|
||||
this.minecraft.loadWorld(null);
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ export default class GuiLoadingScreen extends GuiScreen {
|
||||
|
||||
init() {
|
||||
super.init();
|
||||
this.textureBackground = this.getTexture("gui/background.png");
|
||||
}
|
||||
|
||||
drawScreen(stack, mouseX, mouseY, partialTicks) {
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
import GuiScreen from "../GuiScreen.js";
|
||||
import GuiButton from "../widgets/GuiButton.js";
|
||||
import GuiOptions from "./GuiOptions.js";
|
||||
import * as THREE from "../../../../../../../libraries/three.module.js";
|
||||
import {BackSide} from "../../../../../../../libraries/three.module.js";
|
||||
import MathHelper from "../../../util/MathHelper.js";
|
||||
import Minecraft from "../../Minecraft.js";
|
||||
import GuiCreateWorld from "./GuiCreateWorld.js";
|
||||
|
||||
export default class GuiMainMenu extends GuiScreen {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.panoramaTimer = 0;
|
||||
this.splashText = "Minecraft written in JavaScript!";
|
||||
}
|
||||
|
||||
init() {
|
||||
super.init();
|
||||
this.textureLogo = this.getTexture("gui/title/minecraft.png");
|
||||
|
||||
let y = this.height / 4 + 48;
|
||||
|
||||
this.buttonList.push(new GuiButton("Singleplayer", this.width / 2 - 100, y, 200, 20, () => {
|
||||
this.minecraft.displayScreen(new GuiCreateWorld(this));
|
||||
}));
|
||||
this.buttonList.push(new GuiButton("Multiplayer", this.width / 2 - 100, y + 24, 200, 20, () => {
|
||||
|
||||
}).setEnabled(false));
|
||||
this.buttonList.push(new GuiButton("Minecraft Realms", this.width / 2 - 100, y + 24 * 2, 200, 20, () => {
|
||||
|
||||
}).setEnabled(false));
|
||||
this.buttonList.push(new GuiButton("Options...", this.width / 2 - 100, y + 72 + 12, 98, 20, () => {
|
||||
this.minecraft.displayScreen(new GuiOptions(this));
|
||||
}));
|
||||
this.buttonList.push(new GuiButton("Quit Game", this.width / 2 + 2, y + 72 + 12, 98, 20, () => {
|
||||
this.minecraft.stop();
|
||||
}));
|
||||
|
||||
this.initPanoramaRenderer();
|
||||
}
|
||||
|
||||
drawScreen(stack, mouseX, mouseY, partialTicks) {
|
||||
let logoWidth = 274;
|
||||
let x = this.width / 2 - logoWidth / 2;
|
||||
let y = 30;
|
||||
|
||||
// Draw logo
|
||||
this.drawLogo(stack, x, y);
|
||||
|
||||
// Draw version
|
||||
this.drawString(stack, "minecraft-js " + Minecraft.VERSION, 2, this.height - 10, 0xFFFFFF);
|
||||
|
||||
// Draw copyright
|
||||
let mouseOver = mouseX > this.width / 2 + 70 && mouseY > this.height - 20;
|
||||
this.drawRightString(stack, "GitHub @LabyStudio/js-minecraft", this.width - 2, this.height - 10, mouseOver ? 0x0000FF : 0xFFFFFF);
|
||||
|
||||
// Draw buttons
|
||||
super.drawScreen(stack, mouseX, mouseY, partialTicks);
|
||||
|
||||
// Draw splash text
|
||||
this.drawSplash(stack);
|
||||
|
||||
let rotationX = Math.sin((this.panoramaTimer + partialTicks) / 400.0) * 25.0 + 20.0;
|
||||
let rotationY = -(this.panoramaTimer + partialTicks) * 0.1;
|
||||
|
||||
this.camera.aspect = this.width / this.height;
|
||||
this.camera.rotation.x = -MathHelper.toRadians(rotationX + 180);
|
||||
this.camera.rotation.y = -MathHelper.toRadians(rotationY - 180);
|
||||
this.camera.updateProjectionMatrix();
|
||||
this.minecraft.worldRenderer.webRenderer.render(this.scene, this.camera);
|
||||
}
|
||||
|
||||
updateScreen() {
|
||||
this.panoramaTimer++;
|
||||
}
|
||||
|
||||
drawLogo(stack, x, y) {
|
||||
this.drawSprite(stack, this.textureLogo, 0, 0, 155, 44, x, y, 155, 44);
|
||||
this.drawSprite(stack, this.textureLogo, 0, 45, 155, 44, x + 155, y, 155, 44);
|
||||
}
|
||||
|
||||
drawSplash(stack) {
|
||||
let f = 1.8 - Math.abs(Math.sin((new Date().getTime() % 1000) / 1000.0 * Math.PI * 2.0) * 0.1);
|
||||
f = f * 100.0 / (this.getStringWidth(stack, this.splashText) + 32);
|
||||
|
||||
stack.save();
|
||||
stack.translate((this.width / 2 + 90), 70.0, 0.0);
|
||||
stack.rotate(MathHelper.toRadians(-20));
|
||||
stack.scale(f, f, f);
|
||||
|
||||
this.drawCenteredString(stack, this.splashText, 0, -8, -256);
|
||||
stack.restore();
|
||||
}
|
||||
|
||||
keyTyped(key) {
|
||||
// Cancel key inputs
|
||||
}
|
||||
|
||||
mouseClicked(mouseX, mouseY, mouseButton) {
|
||||
super.mouseClicked(mouseX, mouseY, mouseButton);
|
||||
|
||||
// Click on GitHub text
|
||||
let mouseOver = mouseX > this.width / 2 + 70 && mouseY > this.height - 20;
|
||||
if (mouseOver) {
|
||||
this.minecraft.window.openUrl(Minecraft.URL_GITHUB, true);
|
||||
}
|
||||
}
|
||||
|
||||
initPanoramaRenderer() {
|
||||
this.scene = new THREE.Scene();
|
||||
|
||||
// Create cube
|
||||
let geometry = new THREE.BoxBufferGeometry(1, 1, 1);
|
||||
let materials = [
|
||||
new THREE.MeshBasicMaterial({
|
||||
side: BackSide,
|
||||
map: this.minecraft.getThreeTexture("gui/title/background/panorama_1.png")
|
||||
}),
|
||||
new THREE.MeshBasicMaterial({
|
||||
side: BackSide,
|
||||
map: this.minecraft.getThreeTexture("gui/title/background/panorama_3.png")
|
||||
}),
|
||||
new THREE.MeshBasicMaterial({
|
||||
side: BackSide,
|
||||
map: this.minecraft.getThreeTexture("gui/title/background/panorama_4.png")
|
||||
}),
|
||||
new THREE.MeshBasicMaterial({
|
||||
side: BackSide,
|
||||
map: this.minecraft.getThreeTexture("gui/title/background/panorama_5.png")
|
||||
}),
|
||||
new THREE.MeshBasicMaterial({
|
||||
side: BackSide,
|
||||
map: this.minecraft.getThreeTexture("gui/title/background/panorama_0.png")
|
||||
}),
|
||||
new THREE.MeshBasicMaterial({
|
||||
side: BackSide,
|
||||
map: this.minecraft.getThreeTexture("gui/title/background/panorama_2.png")
|
||||
})
|
||||
];
|
||||
|
||||
materials.forEach(material => {
|
||||
material.map.minFilter = THREE.LinearFilter;
|
||||
material.map.magFilter = THREE.LinearFilter;
|
||||
});
|
||||
|
||||
let cube = new THREE.Mesh(geometry, materials);
|
||||
cube.scale.set(-1, -1, -1);
|
||||
this.scene.add(cube);
|
||||
|
||||
this.camera = new THREE.PerspectiveCamera(120, 1, 0.1, 1);
|
||||
this.camera.rotation.order = 'ZYX';
|
||||
|
||||
// Apply blur
|
||||
this.minecraft.window.canvas2d.style.backdropFilter = "saturate(80%) blur(10px)";
|
||||
}
|
||||
|
||||
onClose() {
|
||||
// Remove blur
|
||||
this.minecraft.window.canvas2d.style.backdropFilter = "";
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,9 @@ import GuiScreen from "../GuiScreen.js";
|
||||
import GuiButton from "../widgets/GuiButton.js";
|
||||
import GuiSwitchButton from "../widgets/GuiSwitchButton.js";
|
||||
import GuiSliderButton from "../widgets/GuiSliderButton.js";
|
||||
import GuiControls from "./GuiControls.js";
|
||||
|
||||
export default class GuiSettings extends GuiScreen {
|
||||
export default class GuiOptions extends GuiScreen {
|
||||
|
||||
constructor(previousScreen) {
|
||||
super();
|
||||
@@ -16,26 +17,29 @@ export default class GuiSettings extends GuiScreen {
|
||||
|
||||
let settings = this.minecraft.settings;
|
||||
|
||||
let scope = this;
|
||||
this.buttonList.push(new GuiSwitchButton("Ambient Occlusion", settings.ambientOcclusion, this.width / 2 - 100, this.height / 2 - 30, 200, 20, function (value) {
|
||||
let y = this.height / 2 - 50;
|
||||
this.buttonList.push(new GuiSwitchButton("Ambient Occlusion", settings.ambientOcclusion, this.width / 2 - 100, y, 200, 20, value => {
|
||||
settings.ambientOcclusion = value;
|
||||
scope.minecraft.worldRenderer.rebuildAll();
|
||||
this.minecraft.worldRenderer.rebuildAll();
|
||||
}));
|
||||
this.buttonList.push(new GuiSwitchButton("View Bobbing", settings.viewBobbing, this.width / 2 - 100, this.height / 2 - 5, 200, 20, function (value) {
|
||||
this.buttonList.push(new GuiSwitchButton("View Bobbing", settings.viewBobbing, this.width / 2 - 100, y + 24, 200, 20, value => {
|
||||
settings.viewBobbing = value;
|
||||
}));
|
||||
this.buttonList.push(new GuiSliderButton("FOV", settings.fov, 50, 100, this.width / 2 - 100, this.height / 2 + 20, 200, 20, function (value) {
|
||||
this.buttonList.push(new GuiSliderButton("FOV", settings.fov, 50, 100, this.width / 2 - 100, y + 24 * 2, 200, 20, value => {
|
||||
settings.fov = value;
|
||||
}));
|
||||
this.buttonList.push(new GuiButton("Controls...", this.width / 2 - 100, y + 24 * 3, 200, 20, () => {
|
||||
this.minecraft.displayScreen(new GuiControls(this));
|
||||
}));
|
||||
|
||||
this.buttonList.push(new GuiButton("Done", this.width / 2 - 100, this.height / 2 + 70, 200, 20, function () {
|
||||
scope.minecraft.displayScreen(scope.previousScreen);
|
||||
this.buttonList.push(new GuiButton("Done", this.width / 2 - 100, y + 110, 200, 20, () => {
|
||||
this.minecraft.displayScreen(this.previousScreen);
|
||||
}));
|
||||
}
|
||||
|
||||
drawScreen(stack, mouseX, mouseY, partialTicks) {
|
||||
// Background
|
||||
this.drawRect(stack, 0, 0, this.width, this.height, 'black', 0.6);
|
||||
this.drawDefaultBackground(stack);
|
||||
|
||||
// Title
|
||||
this.drawCenteredString(stack, "Settings", this.width / 2, 50);
|
||||
@@ -28,6 +28,10 @@ export default class GuiButton extends Gui {
|
||||
}
|
||||
}
|
||||
|
||||
onTick() {
|
||||
|
||||
}
|
||||
|
||||
mouseClicked(mouseX, mouseY, mouseButton) {
|
||||
this.onPress();
|
||||
}
|
||||
@@ -40,7 +44,11 @@ export default class GuiButton extends Gui {
|
||||
|
||||
}
|
||||
|
||||
keyTyped(key) {
|
||||
keyTyped(key, character) {
|
||||
|
||||
}
|
||||
|
||||
keyReleased(key) {
|
||||
|
||||
}
|
||||
|
||||
@@ -56,4 +64,9 @@ export default class GuiButton extends Gui {
|
||||
this.drawSprite(stack, textureGui, 200 - width / 2, spriteY, width / 2, 20, x + width / 2, y, width / 2, height);
|
||||
}
|
||||
|
||||
setEnabled(enabled) {
|
||||
this.enabled = enabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import GuiButton from "./GuiButton.js";
|
||||
|
||||
export default class GuiTextField extends GuiButton {
|
||||
|
||||
constructor(x, y, width, height) {
|
||||
super("", x, y, width, height);
|
||||
|
||||
this.text = "";
|
||||
this.isFocused = false;
|
||||
this.cursorCounter = 0;
|
||||
this.maxLength = 80;
|
||||
}
|
||||
|
||||
render(stack, mouseX, mouseY, partialTicks) {
|
||||
let cursorVisible = this.isFocused && Math.floor(this.cursorCounter / 6) % 2 === 0;
|
||||
let textColor = this.enabled ? 14737632 : 7368816;
|
||||
|
||||
// Draw background
|
||||
this.drawRect(stack, this.x - 1, this.y - 1, this.x + this.width + 1, this.y + this.height + 1, '#5f5f60');
|
||||
this.drawRect(stack, this.x, this.y, this.x + this.width, this.y + this.height, 'black');
|
||||
|
||||
// Draw text
|
||||
this.drawString(stack, this.text, this.x + 2, this.y + this.height / 2 - 4, textColor);
|
||||
|
||||
// Draw cursor
|
||||
if (cursorVisible) {
|
||||
this.drawString(stack, "_", this.x + 2 + this.getStringWidth(stack, this.text), this.y + this.height / 2 - 4, textColor);
|
||||
}
|
||||
}
|
||||
|
||||
onTick() {
|
||||
this.cursorCounter++;
|
||||
}
|
||||
|
||||
mouseClicked(mouseX, mouseY, mouseButton) {
|
||||
this.isFocused = true;
|
||||
}
|
||||
|
||||
onPress() {
|
||||
|
||||
}
|
||||
|
||||
keyTyped(key, character) {
|
||||
if (key === "Backspace") {
|
||||
if (this.text.length > 0) {
|
||||
this.text = this.text.substring(0, this.text.length - 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (key === "ShiftLeft") {
|
||||
this.shiftPressed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (key === "ControlLeft") {
|
||||
this.controlPressed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (key === "KeyV" && this.controlPressed) {
|
||||
this.minecraft.window.getClipboardText().then(text => {
|
||||
this.text += text;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (key === "KeyA" && this.controlPressed) {
|
||||
this.text = ""; // TODO: Select all
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.text.length < this.maxLength) {
|
||||
this.text += character;
|
||||
}
|
||||
}
|
||||
|
||||
keyReleased(key) {
|
||||
if (key === "ShiftLeft") {
|
||||
this.shiftPressed = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (key === "ControlLeft") {
|
||||
this.controlPressed = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
getText() {
|
||||
return this.text;
|
||||
}
|
||||
}
|
||||
@@ -21,17 +21,17 @@ export default class WorldRenderer {
|
||||
this.tessellator = new Tessellator();
|
||||
|
||||
// Load terrain texture
|
||||
this.textureTerrain = new THREE.TextureLoader().load('src/resources/terrain/terrain.png');
|
||||
this.textureTerrain = minecraft.getThreeTexture('terrain/terrain.png');
|
||||
this.textureTerrain.magFilter = THREE.NearestFilter;
|
||||
this.textureTerrain.minFilter = THREE.NearestFilter;
|
||||
|
||||
// Load sun texture
|
||||
this.textureSun = new THREE.TextureLoader().load('src/resources/terrain/sun.png');
|
||||
this.textureSun = minecraft.getThreeTexture('terrain/sun.png');
|
||||
this.textureSun.magFilter = THREE.NearestFilter;
|
||||
this.textureSun.minFilter = THREE.NearestFilter;
|
||||
|
||||
// Load moon texture
|
||||
this.textureMoon = new THREE.TextureLoader().load('src/resources/terrain/moon.png');
|
||||
this.textureMoon = minecraft.getThreeTexture('terrain/moon.png');
|
||||
this.textureMoon.magFilter = THREE.NearestFilter;
|
||||
this.textureMoon.minFilter = THREE.NearestFilter;
|
||||
|
||||
@@ -540,13 +540,6 @@ export default class WorldRenderer {
|
||||
let world = this.minecraft.world;
|
||||
let renderDistance = WorldRenderer.RENDER_DISTANCE;
|
||||
|
||||
// Load chunks
|
||||
for (let x = -renderDistance + 1; x < renderDistance; x++) {
|
||||
for (let z = -renderDistance + 1; z < renderDistance; z++) {
|
||||
world.getChunkAt(cameraChunkX + x, cameraChunkZ + z);
|
||||
}
|
||||
}
|
||||
|
||||
// Update chunks
|
||||
for (let [index, chunk] of world.chunks) {
|
||||
let distanceX = Math.abs(cameraChunkX - chunk.x);
|
||||
@@ -563,7 +556,7 @@ export default class WorldRenderer {
|
||||
let chunkSection = chunk.sections[y];
|
||||
|
||||
// Is in camera view check
|
||||
if (this.frustum.intersectsBox(chunkSection.boundingBox)) {
|
||||
if (this.frustum.intersectsBox(chunkSection.boundingBox) && !chunkSection.isEmpty()) {
|
||||
// Make section visible
|
||||
chunkSection.group.visible = true;
|
||||
|
||||
@@ -590,7 +583,7 @@ export default class WorldRenderer {
|
||||
// TODO Implement chunk unloading
|
||||
//let index = chunk.x + (chunk.z << 16);
|
||||
//world.chunks.delete(index);
|
||||
//world.group.add(chunk.group);
|
||||
//world.group.remove(chunk.group);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -602,8 +595,15 @@ export default class WorldRenderer {
|
||||
return distance1 - distance2;
|
||||
});
|
||||
|
||||
// Rebuild 16 chunk sections per frame (An entire chunk)
|
||||
for (let i = 0; i < 16; i++) {
|
||||
// Update render order of chunks
|
||||
world.group.children.sort((a, b) => {
|
||||
let distance1 = Math.floor(Math.pow(a.chunkX - cameraChunkX, 2) + Math.pow(a.chunkZ - cameraChunkZ, 2));
|
||||
let distance2 = Math.floor(Math.pow(b.chunkX - cameraChunkX, 2) + Math.pow(b.chunkZ - cameraChunkZ, 2));
|
||||
return distance2 - distance1;
|
||||
});
|
||||
|
||||
// Rebuild 8 chunk sections each frame
|
||||
for (let i = 0; i < 8; i++) {
|
||||
if (this.chunkSectionUpdateQueue.length !== 0) {
|
||||
let chunkSection = this.chunkSectionUpdateQueue.shift();
|
||||
if (chunkSection != null) {
|
||||
@@ -612,13 +612,6 @@ export default class WorldRenderer {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update render order of chunks
|
||||
world.group.children.sort((a, b) => {
|
||||
let distance1 = Math.floor(Math.pow(a.chunkX - cameraChunkX, 2) + Math.pow(a.chunkZ - cameraChunkZ, 2));
|
||||
let distance2 = Math.floor(Math.pow(b.chunkX - cameraChunkX, 2) + Math.pow(b.chunkZ - cameraChunkZ, 2));
|
||||
return distance2 - distance1;
|
||||
});
|
||||
}
|
||||
|
||||
rebuildAll() {
|
||||
@@ -775,4 +768,12 @@ export default class WorldRenderer {
|
||||
stack.rotateX(MathHelper.toRadians(Math.abs(Math.cos(walked * Math.PI - 0.2) * yaw) * 5.0));
|
||||
stack.rotateX(MathHelper.toRadians(pitch));
|
||||
}
|
||||
|
||||
reset() {
|
||||
if (this.minecraft.world !== null) {
|
||||
this.scene.remove(this.minecraft.world.group);
|
||||
}
|
||||
this.webRenderer.clear();
|
||||
this.overlay.clear();
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ export default class PlayerRenderer extends EntityRenderer {
|
||||
this.worldRenderer = worldRenderer;
|
||||
|
||||
// Load character texture
|
||||
this.textureCharacter = new THREE.TextureLoader().load('src/resources/char.png');
|
||||
this.textureCharacter = worldRenderer.minecraft.getThreeTexture('char.png');
|
||||
this.textureCharacter.magFilter = THREE.NearestFilter;
|
||||
this.textureCharacter.minFilter = THREE.NearestFilter;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import Gui from "../../gui/Gui.js";
|
||||
import MathHelper from "../../../util/MathHelper.js";
|
||||
|
||||
export default class FontRenderer {
|
||||
|
||||
@@ -47,17 +48,18 @@ export default class FontRenderer {
|
||||
}
|
||||
|
||||
drawString(stack, string, x, y, color = -1) {
|
||||
if (!this.isSafari) { // TODO Fix brightness filter on Safari
|
||||
this.drawStringRaw(stack, string, x + 1, y + 1, (color & 0xFCFCFC) >> 2, true);
|
||||
if (!this.isSafari) { // TODO Fix filter on Safari
|
||||
this.drawStringRaw(stack, string, x + 1, y + 1, color, true);
|
||||
}
|
||||
this.drawStringRaw(stack, string, x, y, color, false);
|
||||
this.drawStringRaw(stack, string, x, y, color);
|
||||
}
|
||||
|
||||
drawStringRaw(stack, string, x, y, color = -1, isShadow = true) {
|
||||
drawStringRaw(stack, string, x, y, color = -1, isShadow = false) {
|
||||
stack.save();
|
||||
|
||||
if (isShadow) {
|
||||
stack.filter = "brightness(20%)";
|
||||
// Set color
|
||||
if (color !== -1 || isShadow) {
|
||||
this.setColor(stack, color, isShadow);
|
||||
}
|
||||
|
||||
// For each character
|
||||
@@ -71,7 +73,7 @@ export default class FontRenderer {
|
||||
let nextCharacter = string[i + 1];
|
||||
|
||||
// Change color of string
|
||||
//this.setColor(this.getColorOfCharacter(nextCharacter), isShadow);
|
||||
this.setColor(stack, this.getColorOfCharacter(nextCharacter), isShadow);
|
||||
|
||||
// Skip the color code for rendering
|
||||
i += 1;
|
||||
@@ -138,4 +140,32 @@ export default class FontRenderer {
|
||||
canvas.getContext('2d').drawImage(img, 0, 0, img.width, img.height);
|
||||
return canvas.getContext('2d').getImageData(0, 0, img.width, img.height).data;
|
||||
}
|
||||
|
||||
setColor(stack, color, isShadow = false) {
|
||||
if (isShadow) {
|
||||
color = (color & 0xFCFCFC) >> 2;
|
||||
}
|
||||
|
||||
let r = (color & 0xFF0000) >> 16;
|
||||
let g = (color & 0x00FF00) >> 8;
|
||||
let b = (color & 0x0000FF);
|
||||
let hsv = MathHelper.rgb2hsv(r, g, b);
|
||||
let hue = hsv[0] + 270;
|
||||
let saturation = hsv[1];
|
||||
let brightness = hsv[2] / 255 * 100;
|
||||
|
||||
// TODO fix colors
|
||||
let saturate1 = saturation * 1000;
|
||||
let saturate2 = saturation * 5000;
|
||||
let saturate3 = saturation * 100;
|
||||
|
||||
if (!this.isSafari) { // TODO Fix filter on Safari
|
||||
stack.filter = "sepia()"
|
||||
+ " saturate(" + saturate1 + "%)"
|
||||
+ " hue-rotate(" + hue + "deg)"
|
||||
+ " saturate(" + saturate2 + "%)"
|
||||
+ " brightness(" + brightness + "%)"
|
||||
+ " saturate(" + saturate3 + "%)";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,4 +87,12 @@ export default class ItemRenderer {
|
||||
}
|
||||
this.itemInHand = null;
|
||||
}
|
||||
|
||||
reset() {
|
||||
for (let i in this.items) {
|
||||
this.scene.remove(this.items[i].group);
|
||||
}
|
||||
this.items = [];
|
||||
this.webRenderer.clear();
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,11 @@ export default class ScreenRenderer {
|
||||
}
|
||||
|
||||
initialize() {
|
||||
this.resolution = this.minecraft.isInGame() ? 1 : this.minecraft.window.scaleFactor; // Increase resolution for the splash text
|
||||
|
||||
// Update camera size
|
||||
this.window.canvas2d.width = this.window.width;
|
||||
this.window.canvas2d.height = this.window.height;
|
||||
this.window.canvas2d.width = this.window.width * this.resolution;
|
||||
this.window.canvas2d.height = this.window.height * this.resolution;
|
||||
|
||||
// Get context stack of 2d canvas
|
||||
this.stack2d = this.window.canvas2d.getContext('2d');
|
||||
@@ -21,18 +23,31 @@ export default class ScreenRenderer {
|
||||
let mouseX = this.minecraft.window.mouseX;
|
||||
let mouseY = this.minecraft.window.mouseY;
|
||||
|
||||
this.stack2d.save();
|
||||
this.stack2d.scale(this.resolution, this.resolution, this.resolution);
|
||||
|
||||
// Reset 2d canvas
|
||||
this.stack2d.clearRect(0, 0, this.window.width, this.window.height);
|
||||
|
||||
// Render in-game overlay
|
||||
if (this.minecraft.loadingScreen === null) {
|
||||
this.minecraft.ingameOverlay.render(this.stack2d, mouseX, mouseY, partialTicks);
|
||||
try {
|
||||
// Render in-game overlay
|
||||
if (this.minecraft.isInGame() && this.minecraft.loadingScreen === null) {
|
||||
this.minecraft.ingameOverlay.render(this.stack2d, mouseX, mouseY, partialTicks);
|
||||
}
|
||||
|
||||
// Render current screen
|
||||
if (this.minecraft.currentScreen !== null) {
|
||||
this.minecraft.currentScreen.drawScreen(this.stack2d, mouseX, mouseY, partialTicks)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
// Render current screen
|
||||
if (!(this.minecraft.currentScreen === null)) {
|
||||
this.minecraft.currentScreen.drawScreen(this.stack2d, mouseX, mouseY, partialTicks);
|
||||
}
|
||||
this.stack2d.restore();
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.stack2d.clearRect(0, 0, this.window.width, this.window.height);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -50,12 +50,12 @@ export default class SoundManager {
|
||||
sound.setRefDistance(0.1);
|
||||
sound.setRolloffFactor(6);
|
||||
sound.setFilter(sound.context.createBiquadFilter());
|
||||
sound.setVolume(0);
|
||||
|
||||
// Load sound
|
||||
let scope = this;
|
||||
this.audioLoader.load(path, function (buffer) {
|
||||
this.audioLoader.load(path, buffer => {
|
||||
sound.setBuffer(buffer);
|
||||
scope.scene.add(sound);
|
||||
this.scene.add(sound);
|
||||
});
|
||||
|
||||
return sound;
|
||||
@@ -87,6 +87,7 @@ export default class SoundManager {
|
||||
sound.filters[0].frequency.setValueAtTime(12000 * pitch, sound.context.currentTime);
|
||||
|
||||
// Play sound
|
||||
sound.offset = 0;
|
||||
sound.play();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ import * as THREE from "../../../../../../libraries/three.module.js";
|
||||
|
||||
export default class Chunk {
|
||||
|
||||
static SECTION_AMOUNT = 16;
|
||||
|
||||
constructor(world, x, z) {
|
||||
this.world = world;
|
||||
this.x = x;
|
||||
@@ -21,7 +23,7 @@ export default class Chunk {
|
||||
|
||||
// Initialize sections
|
||||
this.sections = [];
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let y = 0; y < Chunk.SECTION_AMOUNT; y++) {
|
||||
let section = new ChunkSection(world, this, x, y, z);
|
||||
|
||||
this.sections[y] = section;
|
||||
|
||||
@@ -24,7 +24,7 @@ export default class ChunkSection {
|
||||
|
||||
this.group = new THREE.Object3D();
|
||||
this.group.matrixAutoUpdate = false;
|
||||
this.isModified = false;
|
||||
this.isModified = true;
|
||||
|
||||
this.blocks = [];
|
||||
this.blocksData = [];
|
||||
@@ -118,8 +118,8 @@ export default class ChunkSection {
|
||||
|
||||
getTotalLightAt(x, y, z) {
|
||||
let index = y << 8 | z << 4 | x;
|
||||
let skyLight = (!this.empty && index in this.skyLight ? this.skyLight[index] : (this.empty ? 15 : 14)) - this.world.skylightSubtracted;
|
||||
let blockLight = !this.empty && index in this.blockLight ? this.blockLight[index] : 0;
|
||||
let skyLight = (index in this.skyLight ? this.skyLight[index] : (this.empty ? 15 : 14)) - this.world.skylightSubtracted;
|
||||
let blockLight = index in this.blockLight ? this.blockLight[index] : 0;
|
||||
if (blockLight > skyLight) {
|
||||
skyLight = blockLight;
|
||||
}
|
||||
@@ -129,10 +129,10 @@ export default class ChunkSection {
|
||||
getLightAt(sourceType, x, y, z) {
|
||||
let index = y << 8 | z << 4 | x;
|
||||
if (sourceType === EnumSkyBlock.SKY) {
|
||||
return !this.empty && index in this.skyLight ? this.skyLight[index] : (this.empty ? 15 : 14);
|
||||
return index in this.skyLight ? this.skyLight[index] : (this.empty ? 15 : 14);
|
||||
}
|
||||
if (sourceType === EnumSkyBlock.BLOCK) {
|
||||
return !this.empty && index in this.blockLight ? this.blockLight[index] : 0;
|
||||
return index in this.blockLight ? this.blockLight[index] : 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import EnumBlockFace from "../../util/EnumBlockFace.js";
|
||||
import Vector3 from "../../util/Vector3.js";
|
||||
import Vector4 from "../../util/Vector4.js";
|
||||
import MetadataChunkBlock from "../../util/MetadataChunkBlock.js";
|
||||
import Long from "../../../../../../libraries/long.js";
|
||||
import * as THREE from "../../../../../../libraries/three.module.js";
|
||||
import WorldRenderer from "../render/WorldRenderer.js";
|
||||
import Random from "../../util/Random.js";
|
||||
@@ -17,7 +16,7 @@ export default class World {
|
||||
|
||||
static TOTAL_HEIGHT = ChunkSection.SIZE * 8 - 1; // ChunkSection.SIZE * 16 - 1;
|
||||
|
||||
constructor(minecraft, seed = Long.fromInt(Date.now() % 100000)) {
|
||||
constructor(minecraft, seed) {
|
||||
this.minecraft = minecraft;
|
||||
|
||||
this.entities = [];
|
||||
@@ -182,6 +181,13 @@ export default class World {
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip if section has no blocks
|
||||
let section1 = this.getChunkSectionAt(x1 >> 4, y1 >> 4, z1 >> 4);
|
||||
let section2 = this.getChunkSectionAt(x2 >> 4, y2 >> 4, z2 >> 4);
|
||||
if (section1 === section2 && section1.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add light update region to queue
|
||||
if (this.lightUpdateQueue.length < 9999) {
|
||||
this.lightUpdateQueue.push(new MetadataChunkBlock(sourceType, x1, y1, z1, x2, y2, z2));
|
||||
|
||||
@@ -94,4 +94,9 @@ export default class MathHelper {
|
||||
return 0xff000000 | (r << 16) | (g << 8) | (b << 0);
|
||||
}
|
||||
|
||||
static rgb2hsv(r, g, b) {
|
||||
let v = Math.max(r, g, b), c = v - Math.min(r, g, b);
|
||||
let h = c && ((v === r) ? (g - b) / c : ((v === g) ? 2 + (b - r) / c : 4 + (r - g) / c));
|
||||
return [60 * (h < 0 ? h + 6 : h), v && c / v, v];
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 77 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 9.7 KiB |
@@ -1,51 +1,22 @@
|
||||
@font-face {
|
||||
font-family: "Minecraftia";
|
||||
src: url(src/resources/font.ttf);
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
canvas {
|
||||
position: absolute;
|
||||
image-rendering: -moz-crisp-edges; /* Firefox */
|
||||
image-rendering: -moz-crisp-edges; /* Firefox */
|
||||
image-rendering: -webkit-crisp-edges; /* Webkit (Safari) */
|
||||
image-rendering: pixelated; /* Chrome */
|
||||
image-rendering: pixelated; /* Chrome */
|
||||
}
|
||||
|
||||
#background {
|
||||
background-image: url(src/resources/gui/background.png);
|
||||
background-size: 128px 128px;
|
||||
image-rendering: pixelated;
|
||||
filter: brightness(0.5);
|
||||
}
|
||||
|
||||
#pre-status {
|
||||
/* Position */
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
|
||||
/* No select */
|
||||
-webkit-touch-callout: none; /* iOS Safari */
|
||||
-webkit-user-select: none; /* Safari */
|
||||
-khtml-user-select: none; /* Konqueror HTML */
|
||||
-moz-user-select: none; /* Old versions of Firefox */
|
||||
-ms-user-select: none; /* Internet Explorer/Edge */
|
||||
user-select: none;
|
||||
/* Non-prefixed version, currently
|
||||
supported by Chrome, Edge, Opera and Firefox */
|
||||
/* No overflow */
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
|
||||
/* Font */
|
||||
font-size: 25px;
|
||||
font-family: "Minecraftia", sans-serif;
|
||||
text-shadow: 2px 2px #000000;
|
||||
color: white;
|
||||
background-image: url(src/resources/gui/title/splash.png);
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
background-position: center;
|
||||
image-rendering: -moz-crisp-edges; /* Firefox */
|
||||
image-rendering: -webkit-crisp-edges; /* Webkit (Safari) */
|
||||
image-rendering: pixelated; /* Chrome */
|
||||
}
|
||||
|
||||
.fullscreen {
|
||||
|
||||