diff --git a/index.html b/index.html index 3a36d9b..23ec6a2 100644 --- a/index.html +++ b/index.html @@ -11,7 +11,6 @@
- Loading scripts...
diff --git a/src/js/Start.js b/src/js/Start.js index 7c05f35..5ac7cb4 100644 --- a/src/js/Start.js +++ b/src/js/Start.js @@ -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"); \ No newline at end of file +new Start().launch("canvas-container"); \ No newline at end of file diff --git a/src/js/net/minecraft/client/GameWindow.js b/src/js/net/minecraft/client/GameWindow.js index bfb5591..67cc982 100644 --- a/src/js/net/minecraft/client/GameWindow.js +++ b/src/js/net/minecraft/client/GameWindow.js @@ -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(); + } + } \ No newline at end of file diff --git a/src/js/net/minecraft/client/Minecraft.js b/src/js/net/minecraft/client/Minecraft.js index 23ee941..c8772ee 100644 --- a/src/js/net/minecraft/client/Minecraft.js +++ b/src/js/net/minecraft/client/Minecraft.js @@ -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); + } } \ No newline at end of file diff --git a/src/js/net/minecraft/client/gui/Gui.js b/src/js/net/minecraft/client/gui/Gui.js index 8709aad..c65c6c6 100644 --- a/src/js/net/minecraft/client/gui/Gui.js +++ b/src/js/net/minecraft/client/gui/Gui.js @@ -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; diff --git a/src/js/net/minecraft/client/gui/GuiScreen.js b/src/js/net/minecraft/client/gui/GuiScreen.js index b9d5416..46caf72 100644 --- a/src/js/net/minecraft/client/gui/GuiScreen.js +++ b/src/js/net/minecraft/client/gui/GuiScreen.js @@ -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); + } + } } \ No newline at end of file diff --git a/src/js/net/minecraft/client/gui/IngameOverlay.js b/src/js/net/minecraft/client/gui/IngameOverlay.js index 6a6a2c2..2fa8c5d 100644 --- a/src/js/net/minecraft/client/gui/IngameOverlay.js +++ b/src/js/net/minecraft/client/gui/IngameOverlay.js @@ -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) { diff --git a/src/js/net/minecraft/client/gui/screens/GuiControls.js b/src/js/net/minecraft/client/gui/screens/GuiControls.js index d4f98bd..53025b2 100644 --- a/src/js/net/minecraft/client/gui/screens/GuiControls.js +++ b/src/js/net/minecraft/client/gui/screens/GuiControls.js @@ -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); diff --git a/src/js/net/minecraft/client/gui/screens/GuiCreateWorld.js b/src/js/net/minecraft/client/gui/screens/GuiCreateWorld.js new file mode 100644 index 0000000..0009dc7 --- /dev/null +++ b/src/js/net/minecraft/client/gui/screens/GuiCreateWorld.js @@ -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() { + + } + +} \ No newline at end of file diff --git a/src/js/net/minecraft/client/gui/screens/GuiIngameMenu.js b/src/js/net/minecraft/client/gui/screens/GuiIngameMenu.js index 93c1bec..3af3df6 100644 --- a/src/js/net/minecraft/client/gui/screens/GuiIngameMenu.js +++ b/src/js/net/minecraft/client/gui/screens/GuiIngameMenu.js @@ -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); })); } diff --git a/src/js/net/minecraft/client/gui/screens/GuiLoadingScreen.js b/src/js/net/minecraft/client/gui/screens/GuiLoadingScreen.js index f6f47e9..739fdb8 100644 --- a/src/js/net/minecraft/client/gui/screens/GuiLoadingScreen.js +++ b/src/js/net/minecraft/client/gui/screens/GuiLoadingScreen.js @@ -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) { diff --git a/src/js/net/minecraft/client/gui/screens/GuiMainMenu.js b/src/js/net/minecraft/client/gui/screens/GuiMainMenu.js new file mode 100644 index 0000000..fabbe84 --- /dev/null +++ b/src/js/net/minecraft/client/gui/screens/GuiMainMenu.js @@ -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 = ""; + } +} \ No newline at end of file diff --git a/src/js/net/minecraft/client/gui/screens/GuiSettings.js b/src/js/net/minecraft/client/gui/screens/GuiOptions.js similarity index 61% rename from src/js/net/minecraft/client/gui/screens/GuiSettings.js rename to src/js/net/minecraft/client/gui/screens/GuiOptions.js index 7df6145..aa1224c 100644 --- a/src/js/net/minecraft/client/gui/screens/GuiSettings.js +++ b/src/js/net/minecraft/client/gui/screens/GuiOptions.js @@ -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); diff --git a/src/js/net/minecraft/client/gui/widgets/GuiButton.js b/src/js/net/minecraft/client/gui/widgets/GuiButton.js index 32eb503..2b183b0 100644 --- a/src/js/net/minecraft/client/gui/widgets/GuiButton.js +++ b/src/js/net/minecraft/client/gui/widgets/GuiButton.js @@ -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; + } + } \ No newline at end of file diff --git a/src/js/net/minecraft/client/gui/widgets/GuiTextField.js b/src/js/net/minecraft/client/gui/widgets/GuiTextField.js new file mode 100644 index 0000000..1b10264 --- /dev/null +++ b/src/js/net/minecraft/client/gui/widgets/GuiTextField.js @@ -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; + } +} \ No newline at end of file diff --git a/src/js/net/minecraft/client/render/WorldRenderer.js b/src/js/net/minecraft/client/render/WorldRenderer.js index 5812fa5..8d1690a 100644 --- a/src/js/net/minecraft/client/render/WorldRenderer.js +++ b/src/js/net/minecraft/client/render/WorldRenderer.js @@ -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(); + } } \ No newline at end of file diff --git a/src/js/net/minecraft/client/render/entity/entity/PlayerRenderer.js b/src/js/net/minecraft/client/render/entity/entity/PlayerRenderer.js index 995e207..da9058a 100644 --- a/src/js/net/minecraft/client/render/entity/entity/PlayerRenderer.js +++ b/src/js/net/minecraft/client/render/entity/entity/PlayerRenderer.js @@ -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; diff --git a/src/js/net/minecraft/client/render/gui/FontRenderer.js b/src/js/net/minecraft/client/render/gui/FontRenderer.js index cdbac30..cd3c72d 100644 --- a/src/js/net/minecraft/client/render/gui/FontRenderer.js +++ b/src/js/net/minecraft/client/render/gui/FontRenderer.js @@ -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 + "%)"; + } + } } \ No newline at end of file diff --git a/src/js/net/minecraft/client/render/gui/ItemRenderer.js b/src/js/net/minecraft/client/render/gui/ItemRenderer.js index ff1c3bd..03159a8 100644 --- a/src/js/net/minecraft/client/render/gui/ItemRenderer.js +++ b/src/js/net/minecraft/client/render/gui/ItemRenderer.js @@ -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(); + } } \ No newline at end of file diff --git a/src/js/net/minecraft/client/render/gui/ScreenRenderer.js b/src/js/net/minecraft/client/render/gui/ScreenRenderer.js index ab5a62e..95bbb45 100644 --- a/src/js/net/minecraft/client/render/gui/ScreenRenderer.js +++ b/src/js/net/minecraft/client/render/gui/ScreenRenderer.js @@ -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); } } \ No newline at end of file diff --git a/src/js/net/minecraft/client/sound/SoundManager.js b/src/js/net/minecraft/client/sound/SoundManager.js index 1e5bf3d..11d855d 100644 --- a/src/js/net/minecraft/client/sound/SoundManager.js +++ b/src/js/net/minecraft/client/sound/SoundManager.js @@ -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(); } } diff --git a/src/js/net/minecraft/client/world/Chunk.js b/src/js/net/minecraft/client/world/Chunk.js index 18fadba..b65827d 100644 --- a/src/js/net/minecraft/client/world/Chunk.js +++ b/src/js/net/minecraft/client/world/Chunk.js @@ -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; diff --git a/src/js/net/minecraft/client/world/ChunkSection.js b/src/js/net/minecraft/client/world/ChunkSection.js index 4a6010a..b64bbb5 100644 --- a/src/js/net/minecraft/client/world/ChunkSection.js +++ b/src/js/net/minecraft/client/world/ChunkSection.js @@ -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; } diff --git a/src/js/net/minecraft/client/world/World.js b/src/js/net/minecraft/client/world/World.js index f3dcf30..f073678 100644 --- a/src/js/net/minecraft/client/world/World.js +++ b/src/js/net/minecraft/client/world/World.js @@ -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)); diff --git a/src/js/net/minecraft/util/MathHelper.js b/src/js/net/minecraft/util/MathHelper.js index 4910e1c..a6a9de0 100644 --- a/src/js/net/minecraft/util/MathHelper.js +++ b/src/js/net/minecraft/util/MathHelper.js @@ -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]; + } } \ No newline at end of file diff --git a/src/resources/font.ttf b/src/resources/font.ttf deleted file mode 100644 index 7247ad7..0000000 Binary files a/src/resources/font.ttf and /dev/null differ diff --git a/src/resources/gui/title/background/panorama_0.png b/src/resources/gui/title/background/panorama_0.png new file mode 100644 index 0000000..6f16dae Binary files /dev/null and b/src/resources/gui/title/background/panorama_0.png differ diff --git a/src/resources/gui/title/background/panorama_1.png b/src/resources/gui/title/background/panorama_1.png new file mode 100644 index 0000000..9008ecf Binary files /dev/null and b/src/resources/gui/title/background/panorama_1.png differ diff --git a/src/resources/gui/title/background/panorama_2.png b/src/resources/gui/title/background/panorama_2.png new file mode 100644 index 0000000..9457011 Binary files /dev/null and b/src/resources/gui/title/background/panorama_2.png differ diff --git a/src/resources/gui/title/background/panorama_3.png b/src/resources/gui/title/background/panorama_3.png new file mode 100644 index 0000000..e07872e Binary files /dev/null and b/src/resources/gui/title/background/panorama_3.png differ diff --git a/src/resources/gui/title/background/panorama_4.png b/src/resources/gui/title/background/panorama_4.png new file mode 100644 index 0000000..3cd82df Binary files /dev/null and b/src/resources/gui/title/background/panorama_4.png differ diff --git a/src/resources/gui/title/background/panorama_5.png b/src/resources/gui/title/background/panorama_5.png new file mode 100644 index 0000000..fd6c627 Binary files /dev/null and b/src/resources/gui/title/background/panorama_5.png differ diff --git a/src/resources/gui/title/minecraft.png b/src/resources/gui/title/minecraft.png new file mode 100644 index 0000000..896f498 Binary files /dev/null and b/src/resources/gui/title/minecraft.png differ diff --git a/src/resources/gui/title/splash.png b/src/resources/gui/title/splash.png new file mode 100644 index 0000000..7b66918 Binary files /dev/null and b/src/resources/gui/title/splash.png differ diff --git a/style.css b/style.css index 5854a84..9e85793 100644 --- a/style.css +++ b/style.css @@ -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 {