diff --git a/README.md b/README.md index f241e32..9c0fb86 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ Click [here](https://labystudio.de/page/minecraft/) for a demo! - Options Screen - Controls Screen - Chat Input Screen + - Creative Inventory Screen - Widgets - Button - KeyBinding diff --git a/index.html b/index.html index b6d3c8e..ab87660 100644 --- a/index.html +++ b/index.html @@ -1,7 +1,7 @@ Minecraft - + diff --git a/src/js/Start.js b/src/js/Start.js index 8d4774b..8ec2533 100644 --- a/src/js/Start.js +++ b/src/js/Start.js @@ -40,7 +40,8 @@ class Start { "gui/title/background/panorama_2.png", "gui/title/background/panorama_3.png", "gui/title/background/panorama_4.png", - "gui/title/background/panorama_5.png" + "gui/title/background/panorama_5.png", + "gui/container/creative.png" ]).then((resources) => { // Launch actual game on canvas window.app = new Minecraft(canvasWrapperId, resources); diff --git a/src/js/net/minecraft/client/GameSettings.js b/src/js/net/minecraft/client/GameSettings.js index 3558302..952543b 100644 --- a/src/js/net/minecraft/client/GameSettings.js +++ b/src/js/net/minecraft/client/GameSettings.js @@ -5,6 +5,7 @@ export default class GameSettings { this.keySprinting = 'ControlLeft'; this.keyTogglePerspective = 'F5'; this.keyOpenChat = 'KeyT'; + this.keyOpenInventory = 'KeyE'; this.thirdPersonView = 0; this.fov = 70; @@ -12,6 +13,7 @@ export default class GameSettings { this.ambientOcclusion = true; this.sensitivity = 100; this.viewDistance = 4; + this.debugOverlay = false; } load() { diff --git a/src/js/net/minecraft/client/GameWindow.js b/src/js/net/minecraft/client/GameWindow.js index 3c510d5..89e1908 100644 --- a/src/js/net/minecraft/client/GameWindow.js +++ b/src/js/net/minecraft/client/GameWindow.js @@ -35,6 +35,10 @@ export default class GameWindow { this.canvasItems = document.createElement('canvas'); this.wrapper.appendChild(this.canvasItems); + this.canvas.addEventListener("webglcontextlost", function (event) { + event.preventDefault(); + }, false); + let mouseDownInterval = null; // Request focus @@ -48,13 +52,16 @@ export default class GameWindow { // Focus listener document.addEventListener('pointerlockchange', _ => this.onFocusChanged(), false); + document.addEventListener('pointerlockerror', e => { + e.preventDefault() + }, false); // Mouse motion document.addEventListener('mousemove', event => { this.onMouseMove(event); // Handle mouse move on screen - if (!(minecraft.currentScreen === null)) { + if (minecraft.currentScreen !== null) { minecraft.currentScreen.mouseDragged(event.x / this.scaleFactor, event.y / this.scaleFactor, event.code); } }, false); @@ -62,7 +69,7 @@ export default class GameWindow { // Mouse release document.addEventListener('mouseup', event => { // Handle mouse release on screen - if (!(minecraft.currentScreen === null)) { + if (minecraft.currentScreen !== null) { minecraft.currentScreen.mouseReleased(event.x / this.scaleFactor, event.y / this.scaleFactor, event.code); } @@ -71,7 +78,7 @@ export default class GameWindow { // Losing focus event this.canvas.addEventListener("mouseout", () => { - if (minecraft.currentScreen === null) { + if (minecraft.currentScreen === null && !this.actualMouseLocked) { minecraft.displayScreen(new GuiIngameMenu()); } @@ -102,19 +109,22 @@ export default class GameWindow { } // Handle mouse click on screen - if (!(minecraft.currentScreen === null)) { + if (minecraft.currentScreen !== null) { minecraft.currentScreen.mouseClicked(event.x / this.scaleFactor, event.y / this.scaleFactor, event.code); } }, false); // Mouse scroll - document.addEventListener('wheel', (event) => { + this.wrapper.addEventListener('wheel', (event) => { + event.preventDefault(); + event.stopPropagation(); + let delta = Math.sign(event.deltaY); minecraft.onMouseScroll(delta); }, false); // Keyboard interaction with screen - window.addEventListener('keydown', (event) => { + window.addEventListener('keydown', event => { if (event.code === "F11") { return; // Toggle fullscreen } @@ -122,7 +132,7 @@ export default class GameWindow { // Prevent key event.preventDefault(); - if (!(minecraft.currentScreen === null)) { + if (minecraft.currentScreen !== null) { // Handle key type on screen minecraft.currentScreen.keyTyped(event.code, event.key); } else if (event.code === 'Escape') { @@ -137,7 +147,7 @@ export default class GameWindow { // Prevent key event.preventDefault(); - if (!(minecraft.currentScreen === null)) { + if (minecraft.currentScreen !== null) { // Handle key release on screen minecraft.currentScreen.keyReleased(event.code); } diff --git a/src/js/net/minecraft/client/Minecraft.js b/src/js/net/minecraft/client/Minecraft.js index 1352b90..f2bbac5 100644 --- a/src/js/net/minecraft/client/Minecraft.js +++ b/src/js/net/minecraft/client/Minecraft.js @@ -18,10 +18,11 @@ import * as THREE from "../../../../../libraries/three.module.js"; import ParticleRenderer from "./render/particle/ParticleRenderer.js"; import GuiChat from "./gui/screens/GuiChat.js"; import CommandHandler from "./command/CommandHandler.js"; +import GuiContainerCreative from "./gui/screens/container/GuiContainerCreative.js"; export default class Minecraft { - static VERSION = "1.0.3" + static VERSION = "1.0.4" static URL_GITHUB = "https://github.com/labystudio/js-minecraft"; /** @@ -113,6 +114,7 @@ export default class Minecraft { // Create player this.player = new PlayerEntity(this, this.world); + this.player.username = "Player" + Math.floor(Math.random() * 100); this.world.addEntity(this.player); // Load spawn chunks and respawn player @@ -199,7 +201,7 @@ export default class Minecraft { } // Close previous screen - if (!(this.currentScreen === null)) { + if (this.currentScreen !== null) { this.currentScreen.onClose(); } @@ -275,20 +277,33 @@ export default class Minecraft { } onKeyPressed(button) { + // Select slot for (let i = 1; i <= 9; i++) { if (button === 'Digit' + i) { this.player.inventory.selectedSlotIndex = i - 1; } } + // Toggle perspective if (button === this.settings.keyTogglePerspective) { this.settings.thirdPersonView = (this.settings.thirdPersonView + 1) % 3; this.settings.save(); } + // Open chat if (button === this.settings.keyOpenChat) { this.displayScreen(new GuiChat()); } + + // Toggle debug overlay + if (button === "F3") { + this.settings.debugOverlay = !this.settings.debugOverlay; + } + + // Open inventory + if (button === this.settings.keyOpenInventory) { + this.displayScreen(new GuiContainerCreative(this.player)); + } } onMouseClicked(button) { @@ -331,6 +346,16 @@ export default class Minecraft { if (hitResult != null) { let typeId = this.world.getBlockAt(hitResult.x, hitResult.y, hitResult.z); if (typeId !== 0) { + // Switch to slot if item is already in hotbar + for (const item of this.player.inventory.items) { + const index = this.player.inventory.items.indexOf(item); + if (item === typeId && index <= 8) { + this.player.inventory.selectedSlotIndex = index; + return; + } + } + + // Set item in hotbar this.player.inventory.setItemInSelectedSlot(typeId); } } diff --git a/src/js/net/minecraft/client/entity/PlayerEntity.js b/src/js/net/minecraft/client/entity/PlayerEntity.js index e693d92..b37dac9 100644 --- a/src/js/net/minecraft/client/entity/PlayerEntity.js +++ b/src/js/net/minecraft/client/entity/PlayerEntity.js @@ -1,10 +1,10 @@ -import Inventory from "../inventory/Inventory.js"; import EntityLiving from "./EntityLiving.js"; import Block from "../world/block/Block.js"; import MathHelper from "../../util/MathHelper.js"; import Keyboard from "../../util/Keyboard.js"; import Vector3 from "../../util/Vector3.js"; import {BlockRegistry} from "../world/block/BlockRegistry.js"; +import InventoryPlayer from "../inventory/inventory/InventoryPlayer.js"; export default class PlayerEntity extends EntityLiving { @@ -13,7 +13,8 @@ export default class PlayerEntity extends EntityLiving { constructor(minecraft, world) { super(minecraft, world); - this.inventory = new Inventory(); + this.inventory = new InventoryPlayer(); + this.username = "Player"; this.collision = false; diff --git a/src/js/net/minecraft/client/gui/Gui.js b/src/js/net/minecraft/client/gui/Gui.js index a3e6537..f1d2161 100644 --- a/src/js/net/minecraft/client/gui/Gui.js +++ b/src/js/net/minecraft/client/gui/Gui.js @@ -20,8 +20,8 @@ export default class Gui { 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); + drawString(stack, string, x, y, color = -1, shadow = true) { + this.minecraft.fontRenderer.drawString(stack, string, x, y, color, shadow); } getStringWidth(stack, string) { diff --git a/src/js/net/minecraft/client/gui/overlay/IngameOverlay.js b/src/js/net/minecraft/client/gui/overlay/IngameOverlay.js index ffae23d..c720c76 100644 --- a/src/js/net/minecraft/client/gui/overlay/IngameOverlay.js +++ b/src/js/net/minecraft/client/gui/overlay/IngameOverlay.js @@ -1,6 +1,7 @@ import Gui from "../Gui.js"; import Block from "../../world/block/Block.js"; import ChatOverlay from "./ChatOverlay.js"; +import Minecraft from "../../Minecraft.js"; export default class IngameOverlay extends Gui { @@ -40,8 +41,12 @@ export default class IngameOverlay extends Gui { let lightLevel = world.getTotalLightAt(x, y, z); // 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); + if (this.minecraft.settings.debugOverlay) { + this.drawString(stack, "js-minecraft " + Minecraft.VERSION, 1, 1); + this.drawString(stack, fps + " fps," + " " + lightUpdates + " light updates," + " " + chunkUpdates + " chunk updates", 1, 1 + 9 * 1); + this.drawString(stack, x + ", " + y + ", " + z + " (" + (x >> 4) + ", " + (y >> 4) + ", " + (z >> 4) + ")", 1, 1 + 9 * 2); + this.drawString(stack, "Light: " + lightLevel, 1, 1 + 9 * 3); + } } onTick() { @@ -65,13 +70,17 @@ export default class IngameOverlay extends Gui { 24, 24 ) + // To make the items darker + let brightness = this.minecraft.isPaused() ? 0.5 : 1; // TODO find a better solution + + this.minecraft.itemRenderer.prepareRender("hotbar"); + // Render items for (let i = 0; i < 9; i++) { let typeId = this.minecraft.player.inventory.getItemInSlot(i); if (typeId !== 0) { - let renderId = "hotbar" + i; let block = Block.getById(typeId); - this.minecraft.itemRenderer.renderItemInGui(renderId, block, Math.floor(x + i * 20 + 11), y + 11); + this.minecraft.itemRenderer.renderItemInGui("hotbar", i, block, Math.floor(x + i * 20 + 11), y + 11, brightness); } } } diff --git a/src/js/net/minecraft/client/gui/screens/GuiChat.js b/src/js/net/minecraft/client/gui/screens/GuiChat.js index 2912fc1..46dabb6 100644 --- a/src/js/net/minecraft/client/gui/screens/GuiChat.js +++ b/src/js/net/minecraft/client/gui/screens/GuiChat.js @@ -47,7 +47,7 @@ export default class GuiChat extends GuiScreen { if (message.startsWith("/")) { this.minecraft.commandHandler.handleMessage(message.substring(1)); } else { - this.minecraft.addMessageToChat(message); + this.minecraft.addMessageToChat("<" + this.minecraft.player.username + "> " + message); } return; } diff --git a/src/js/net/minecraft/client/gui/screens/GuiContainer.js b/src/js/net/minecraft/client/gui/screens/GuiContainer.js new file mode 100644 index 0000000..b92172b --- /dev/null +++ b/src/js/net/minecraft/client/gui/screens/GuiContainer.js @@ -0,0 +1,136 @@ +import GuiScreen from "../GuiScreen.js"; +import Block from "../../world/block/Block.js"; + +export default class GuiContainer extends GuiScreen { + + constructor(container) { + super(); + + this.inventoryWidth = 176; + this.inventoryHeight = 166; + + this.container = container; + + this.hoverSlot = null; + } + + init() { + super.init(); + + this.x = Math.floor((this.width - this.inventoryWidth) / 2); + this.y = Math.floor((this.height - this.inventoryHeight) / 2); + } + + drawScreen(stack, mouseX, mouseY, partialTicks) { + this.drawDefaultBackground(stack); + this.drawInventoryBackground(stack); + this.drawString(stack, "Creative Inventory", this.x + 8, this.y + 6, 0x404040); + + // Rebuild items + if (this.container.dirty) { + this.container.dirty = false; + this.minecraft.itemRenderer.destroy("inventory"); + this.minecraft.itemRenderer.scheduleDirty("hotbar"); + } + + // Draw slots + this.hoverSlot = null; + this.container.slots.forEach(slot => { + this.drawSlot(stack, slot, mouseX, mouseY); + }); + + // Draw item in cursor + let inventoryPlayer = this.minecraft.player.inventory; + let typeId = inventoryPlayer.itemInCursor; + if (typeId !== null && typeId !== 0) { + let block = Block.getById(typeId); + this.minecraft.itemRenderer.zIndex = 10; + this.minecraft.itemRenderer.renderItemInGui( + "inventory", + "cursor", + block, + mouseX, + mouseY + ); + this.minecraft.itemRenderer.zIndex = 0; + } else { + this.minecraft.itemRenderer.destroy("inventory", "cursor"); + } + + // Draw title + this.drawTitle(stack); + + super.drawScreen(stack, mouseX, mouseY, partialTicks); + } + + mouseClicked(mouseX, mouseY, mouseButton) { + super.mouseClicked(mouseX, mouseY, mouseButton); + + for (const slot of this.container.slots) { + if (this.isMouseOverSlot(slot, mouseX, mouseY)) { + this.container.onSlotClick(slot, this.minecraft.player); + } + } + } + + keyTyped(key, character) { + // Swap to slot + for (let i = 1; i <= 9; i++) { + if (key === 'Digit' + i) { + this.container.swapWithHotbar(this.hoverSlot, this.minecraft.player.inventory, i - 1); + } + } + + return super.keyTyped(key, character); + } + + drawSlot(stack, slot, mouseX, mouseY) { + let x = this.x + slot.x; + let y = this.y + slot.y; + + let inventory = slot.inventory; + let typeId = inventory.getItemInSlot(slot.index); + let isMouseOver = this.isMouseOverSlot(slot, mouseX, mouseY); + + // Render item + if (typeId !== null && typeId !== 0) { + let block = Block.getById(typeId); + this.minecraft.itemRenderer.renderItemInGui( + "inventory", + inventory.name + ":" + slot.index, + block, + x + 8, + y + 8, + isMouseOver ? 1.5 : 1 + ); + } + + // Hover rectangle + if (isMouseOver) { + this.drawRect(stack, x, y, x + 16, y + 16, '#ffffff', 0.5); + + this.hoverSlot = slot; + } + } + + onClose() { + super.onClose(); + + this.minecraft.player.inventory.itemInCursor = null; + this.minecraft.itemRenderer.destroy("inventory"); + } + + drawTitle(stack) { + + } + + drawInventoryBackground(stack) { + + } + + isMouseOverSlot(slot, mouseX, mouseY) { + let x = this.x + slot.x; + let y = this.y + slot.y; + return mouseX >= x && mouseX <= x + 16 && mouseY >= y && mouseY <= y + 16; + } +} \ No newline at end of file diff --git a/src/js/net/minecraft/client/gui/screens/GuiControls.js b/src/js/net/minecraft/client/gui/screens/GuiControls.js index d1a2f0d..4072fd0 100644 --- a/src/js/net/minecraft/client/gui/screens/GuiControls.js +++ b/src/js/net/minecraft/client/gui/screens/GuiControls.js @@ -23,22 +23,26 @@ export default class GuiControls extends GuiScreen { return name + ": " + value + "%"; })); - this.buttonList.push(new GuiKeyButton("Crouch", settings.keyCrouching, this.width / 2 - 100, y + 24, 200, 20, key => { + this.buttonList.push(new GuiKeyButton("Crouch", settings.keyCrouching, this.width / 2 - 100, y + 24, 98, 20, key => { settings.keyCrouching = key; })); - this.buttonList.push(new GuiKeyButton("Sprint", settings.keySprinting, this.width / 2 - 100, y + 24 * 2, 200, 20, key => { + this.buttonList.push(new GuiKeyButton("Sprint", settings.keySprinting, this.width / 2 + 2, y + 24, 98, 20, key => { settings.keySprinting = key; })); - this.buttonList.push(new GuiKeyButton("Toggle Perspective", settings.keyTogglePerspective, this.width / 2 - 100, y + 24 * 3, 200, 20, key => { + this.buttonList.push(new GuiKeyButton("Toggle Perspective", settings.keyTogglePerspective, this.width / 2 - 100, y + 24 * 2, 200, 20, key => { settings.keyTogglePerspective = key; })); - this.buttonList.push(new GuiKeyButton("Open Chat", settings.keyOpenChat, this.width / 2 - 100, y + 24 * 4, 200, 20, key => { + this.buttonList.push(new GuiKeyButton("Open Chat", settings.keyOpenChat, this.width / 2 - 100, y + 24 * 3, 200, 20, key => { settings.keyOpenChat = key; })); + this.buttonList.push(new GuiKeyButton("Open Inventory", settings.keyOpenInventory, this.width / 2 - 100, y + 24 * 4, 200, 20, key => { + settings.keyOpenInventory = key; + })); + this.buttonList.push(new GuiButton("Done", this.width / 2 - 100, y + 130, 200, 20, () => { this.minecraft.displayScreen(this.previousScreen); })); diff --git a/src/js/net/minecraft/client/gui/screens/container/GuiContainerCreative.js b/src/js/net/minecraft/client/gui/screens/container/GuiContainerCreative.js new file mode 100644 index 0000000..ccb15f1 --- /dev/null +++ b/src/js/net/minecraft/client/gui/screens/container/GuiContainerCreative.js @@ -0,0 +1,50 @@ +import GuiContainer from "../GuiContainer.js"; +import ContainerCreative from "../../../inventory/container/ContainerCreative.js"; +import InventoryBasic from "../../../inventory/inventory/InventoryBasic.js"; + +export default class GuiContainerCreative extends GuiContainer { + + static inventory = new InventoryBasic(); + + constructor(player) { + super(new ContainerCreative(player)); + + this.inventoryWidth = 195; + this.inventoryHeight = 136; + } + + init() { + this.textureInventory = this.getTexture("gui/container/creative.png"); + + super.init(); + } + + drawTitle(stack) { + this.drawString(stack, "Creative Inventory", this.x + 8, this.y + 6, 0xff404040, false); + } + + drawInventoryBackground(stack) { + this.drawSprite( + stack, + this.textureInventory, + 0, + 0, + this.inventoryWidth, + this.inventoryHeight, + this.x, + this.y, + this.inventoryWidth, + this.inventoryHeight + ); + } + + keyTyped(key, character) { + if (key === this.minecraft.settings.keyOpenInventory) { + this.minecraft.displayScreen(null); + return true; + } + + return super.keyTyped(key, character); + } + +} \ No newline at end of file diff --git a/src/js/net/minecraft/client/inventory/Container.js b/src/js/net/minecraft/client/inventory/Container.js new file mode 100644 index 0000000..3908c87 --- /dev/null +++ b/src/js/net/minecraft/client/inventory/Container.js @@ -0,0 +1,38 @@ +export default class Container { + + constructor() { + this.slots = []; + this.dirty = true; + } + + addSlot(slot) { + this.slots.push(slot); + } + + swapWithHotbar(slot, inventoryPlayer, hotbarIndex) { + let slotInventory = slot.inventory; + + let typeId = slotInventory.getItemInSlot(slot.index); + let hotbarTypeId = inventoryPlayer.getItemInSlot(hotbarIndex); + + slotInventory.setItem(slot.index, hotbarTypeId); + inventoryPlayer.setItem(hotbarIndex, typeId); + + this.dirty = true; + } + + onSlotClick(slot, player) { + let inventoryPlayer = player.inventory; + let typeId = slot.inventory.getItemInSlot(slot.index); + + if(inventoryPlayer.itemInCursor === null || inventoryPlayer.itemInCursor === 0) { + slot.inventory.setItem(slot.index, 0); + inventoryPlayer.itemInCursor = typeId; + } else { + slot.inventory.setItem(slot.index, inventoryPlayer.itemInCursor); + inventoryPlayer.itemInCursor = typeId; + } + this.dirty = true; + } + +} \ No newline at end of file diff --git a/src/js/net/minecraft/client/inventory/Inventory.js b/src/js/net/minecraft/client/inventory/Inventory.js index 9bf9b5d..cc5ac92 100644 --- a/src/js/net/minecraft/client/inventory/Inventory.js +++ b/src/js/net/minecraft/client/inventory/Inventory.js @@ -1,37 +1,15 @@ export default class Inventory { - constructor() { - this.selectedSlotIndex = 0; - this.items = []; - - // Default items in inventory - this.items[0] = 1; - this.items[1] = 2; - this.items[2] = 3; - this.items[3] = 5; - this.items[4] = 17; - this.items[5] = 18; - this.items[6] = 12; - this.items[7] = 50; + constructor(name) { + this.name = name; } - setItemInSelectedSlot(typeId) { - this.items[this.selectedSlotIndex] = typeId; + getItemInSlot(index) { + } - getItemInSelectedSlot() { - return this.getItemInSlot(this.selectedSlotIndex); + setItem(index, typeId) { + } - shiftSelectedSlot(offset) { - if (this.selectedSlotIndex + offset < 0) { - this.selectedSlotIndex = 9 + (this.selectedSlotIndex + offset); - } else { - this.selectedSlotIndex = (this.selectedSlotIndex + offset) % 9; - } - } - - getItemInSlot(slot) { - return this.items.hasOwnProperty(slot) ? this.items[slot] : 0; - } } \ No newline at end of file diff --git a/src/js/net/minecraft/client/inventory/Slot.js b/src/js/net/minecraft/client/inventory/Slot.js new file mode 100644 index 0000000..f8cb01e --- /dev/null +++ b/src/js/net/minecraft/client/inventory/Slot.js @@ -0,0 +1,10 @@ +export default class Slot { + + constructor(inventory, index, x, y) { + this.inventory = inventory; + this.index = index; + this.x = x; + this.y = y; + } + +} \ No newline at end of file diff --git a/src/js/net/minecraft/client/inventory/container/ContainerCreative.js b/src/js/net/minecraft/client/inventory/container/ContainerCreative.js new file mode 100644 index 0000000..5a91d14 --- /dev/null +++ b/src/js/net/minecraft/client/inventory/container/ContainerCreative.js @@ -0,0 +1,78 @@ +import Container from "../Container.js"; +import GuiContainerCreative from "../../gui/screens/container/GuiContainerCreative.js"; +import Slot from "../Slot.js"; +import Block from "../../world/block/Block.js"; +import InventoryPlayer from "../inventory/InventoryPlayer.js"; + +export default class ContainerCreative extends Container { + + constructor(player) { + super(); + + this.itemList = []; + + let playerInventory = player.inventory; + + // Add creative inventory slots + for (let y = 0; y < 5; ++y) { + for (let x = 0; x < 9; ++x) { + this.addSlot(new Slot(GuiContainerCreative.inventory, y * 9 + x, 9 + x * 18, 18 + y * 18)); + } + } + + // Add player hotbar + for (let x = 0; x < 9; ++x) { + this.addSlot(new Slot(playerInventory, x, 9 + x * 18, 112)); + } + + this.initItems(); + this.scrollTo(0); + } + + swapWithHotbar(slot, inventoryPlayer, hotbarIndex) { + let slotInventory = slot.inventory; + let typeId = slotInventory.getItemInSlot(slot.index); + + inventoryPlayer.setItem(hotbarIndex, typeId); + + this.dirty = true; + } + + onSlotClick(slot, player) { + if (slot.inventory instanceof InventoryPlayer) { + super.onSlotClick(slot, player); + } else { + let inventoryPlayer = player.inventory; + inventoryPlayer.itemInCursor = slot.inventory.getItemInSlot(slot.index); + } + this.dirty = true; + } + + scrollTo(scrollOffset) { + let xOffset = (this.itemList.length + 9 - 1) / 9 - 5; + let yOffset = Math.floor((scrollOffset * xOffset) + 0.5); + + if (yOffset < 0) { + yOffset = 0; + } + + for (let y = 0; y < 5; ++y) { + for (let x = 0; x < 9; ++x) { + let index = x + (y + yOffset) * 9; + + if (index >= 0 && index < this.itemList.length) { + GuiContainerCreative.inventory.setItem(x + y * 9, this.itemList[index]); + } else { + GuiContainerCreative.inventory.setItem(x + y * 9, null); + } + } + } + } + + + initItems() { + Block.blocks.forEach((block) => { + this.itemList.push(block.getId()); + }); + } +} \ No newline at end of file diff --git a/src/js/net/minecraft/client/inventory/container/ContainerPlayer.js b/src/js/net/minecraft/client/inventory/container/ContainerPlayer.js new file mode 100644 index 0000000..6bfba22 --- /dev/null +++ b/src/js/net/minecraft/client/inventory/container/ContainerPlayer.js @@ -0,0 +1,11 @@ +import Container from "../Container.js"; + +export default class ContainerPlayer extends Container { + + constructor() { + super(); + + } + + +} \ No newline at end of file diff --git a/src/js/net/minecraft/client/inventory/inventory/InventoryBasic.js b/src/js/net/minecraft/client/inventory/inventory/InventoryBasic.js new file mode 100644 index 0000000..c51dbe8 --- /dev/null +++ b/src/js/net/minecraft/client/inventory/inventory/InventoryBasic.js @@ -0,0 +1,19 @@ +import Inventory from "../Inventory.js"; + +export default class InventoryBasic extends Inventory { + + constructor() { + super("basic"); + + this.items = []; + } + + getItemInSlot(index) { + return this.items[index]; + } + + setItem(index, typeId) { + this.items[index] = typeId; + } + +} \ No newline at end of file diff --git a/src/js/net/minecraft/client/inventory/inventory/InventoryPlayer.js b/src/js/net/minecraft/client/inventory/inventory/InventoryPlayer.js new file mode 100644 index 0000000..663f9d9 --- /dev/null +++ b/src/js/net/minecraft/client/inventory/inventory/InventoryPlayer.js @@ -0,0 +1,36 @@ +import Inventory from "../Inventory.js"; + +export default class InventoryPlayer extends Inventory { + + constructor() { + super("player"); + + this.selectedSlotIndex = 0; + this.itemInCursor = null; + this.items = []; + } + + setItem(index, typeId) { + this.items[index] = typeId === null ? 0 : typeId; + } + + setItemInSelectedSlot(typeId) { + this.items[this.selectedSlotIndex] = typeId; + } + + getItemInSelectedSlot() { + return this.getItemInSlot(this.selectedSlotIndex); + } + + shiftSelectedSlot(offset) { + if (this.selectedSlotIndex + offset < 0) { + this.selectedSlotIndex = 9 + (this.selectedSlotIndex + offset); + } else { + this.selectedSlotIndex = (this.selectedSlotIndex + offset) % 9; + } + } + + getItemInSlot(slot) { + return this.items.hasOwnProperty(slot) ? this.items[slot] : 0; + } +} \ No newline at end of file diff --git a/src/js/net/minecraft/client/render/BlockRenderer.js b/src/js/net/minecraft/client/render/BlockRenderer.js index f3ceed6..2ccd8dd 100644 --- a/src/js/net/minecraft/client/render/BlockRenderer.js +++ b/src/js/net/minecraft/client/render/BlockRenderer.js @@ -161,7 +161,7 @@ export default class BlockRenderer { let block = typeId === 0 ? null : Block.getById(typeId); // Does it contain air? - if (block === null || !block.isSolid()) { + if (block === null || block.isTranslucent()) { // Sum up the light levels totalLightLevel += world.getTotalLightAt(x + offsetX, y + offsetY, z + offsetZ); totalBlocks++; @@ -402,6 +402,6 @@ export default class BlockRenderer { maxV += offset; // Render item - this.addFace(null, EnumBlockFace.NORTH, false, minX, minY, minZ, maxX, maxY, maxZ, minU, minV, maxU, maxV); + this.addFace(null, EnumBlockFace.NORTH, false, 0, 0, 0, minX, minY, minZ, maxX, maxY, maxZ, minU, minV, maxU, maxV); } } \ No newline at end of file diff --git a/src/js/net/minecraft/client/render/gui/FontRenderer.js b/src/js/net/minecraft/client/render/gui/FontRenderer.js index 5b59693..8fba44b 100644 --- a/src/js/net/minecraft/client/render/gui/FontRenderer.js +++ b/src/js/net/minecraft/client/render/gui/FontRenderer.js @@ -7,6 +7,7 @@ export default class FontRenderer { static FIELD_SIZE = 8; static COLOR_CODE_INDEX_LOOKUP = "0123456789abcdef"; + static CHAR_INDEX_LOOKUP = "\u00c0\u00c1\u00c2\u00c8\u00ca\u00cb\u00cd\u00d3\u00d4\u00d5\u00da\u00df\u00e3\u00f5\u011f\u0130\u0131\u0152\u0153\u015e\u015f\u0174\u0175\u017e\u0207\u0000\u0000\u0000\u0000\u0000\u0000\u0000 !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u0000\u00c7\u00fc\u00e9\u00e2\u00e4\u00e0\u00e5\u00e7\u00ea\u00eb\u00e8\u00ef\u00ee\u00ec\u00c4\u00c5\u00c9\u00e6\u00c6\u00f4\u00f6\u00f2\u00fb\u00f9\u00ff\u00d6\u00dc\u00f8\u00a3\u00d8\u00d7\u0192\u00e1\u00ed\u00f3\u00fa\u00f1\u00d1\u00aa\u00ba\u00bf\u00ae\u00ac\u00bd\u00bc\u00a1\u00ab\u00bb\u2591\u2592\u2593\u2502\u2524\u2561\u2562\u2556\u2555\u2563\u2551\u2557\u255d\u255c\u255b\u2510\u2514\u2534\u252c\u251c\u2500\u253c\u255e\u255f\u255a\u2554\u2569\u2566\u2560\u2550\u256c\u2567\u2568\u2564\u2565\u2559\u2558\u2552\u2553\u256b\u256a\u2518\u250c\u2588\u2584\u258c\u2590\u2580\u03b1\u03b2\u0393\u03c0\u03a3\u03c3\u03bc\u03c4\u03a6\u0398\u03a9\u03b4\u221e\u2205\u2208\u2229\u2261\u00b1\u2265\u2264\u2320\u2321\u00f7\u2248\u00b0\u2219\u00b7\u221a\u207f\u00b2\u25a0\u0000"; constructor(minecraft) { this.charWidths = []; @@ -47,8 +48,8 @@ export default class FontRenderer { return 2; } - drawString(stack, string, x, y, color = -1) { - if (!this.isSafari) { // TODO Fix filter on Safari + drawString(stack, string, x, y, color = -1, shadow = true) { + if (!this.isSafari && shadow) { // TODO Fix filter on Safari this.drawStringRaw(stack, string, x + 1, y + 1, color, true); } this.drawStringRaw(stack, string, x, y, color); @@ -67,7 +68,7 @@ export default class FontRenderer { // For each character for (let i = 0; i < string.length; i++) { let character = string[i]; - let index = "\u00c0\u00c1\u00c2\u00c8\u00ca\u00cb\u00cd\u00d3\u00d4\u00d5\u00da\u00df\u00e3\u00f5\u011f\u0130\u0131\u0152\u0153\u015e\u015f\u0174\u0175\u017e\u0207\u0000\u0000\u0000\u0000\u0000\u0000\u0000 !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u0000\u00c7\u00fc\u00e9\u00e2\u00e4\u00e0\u00e5\u00e7\u00ea\u00eb\u00e8\u00ef\u00ee\u00ec\u00c4\u00c5\u00c9\u00e6\u00c6\u00f4\u00f6\u00f2\u00fb\u00f9\u00ff\u00d6\u00dc\u00f8\u00a3\u00d8\u00d7\u0192\u00e1\u00ed\u00f3\u00fa\u00f1\u00d1\u00aa\u00ba\u00bf\u00ae\u00ac\u00bd\u00bc\u00a1\u00ab\u00bb\u2591\u2592\u2593\u2502\u2524\u2561\u2562\u2556\u2555\u2563\u2551\u2557\u255d\u255c\u255b\u2510\u2514\u2534\u252c\u251c\u2500\u253c\u255e\u255f\u255a\u2554\u2569\u2566\u2560\u2550\u256c\u2567\u2568\u2564\u2565\u2559\u2558\u2552\u2553\u256b\u256a\u2518\u250c\u2588\u2584\u258c\u2590\u2580\u03b1\u03b2\u0393\u03c0\u03a3\u03c3\u03bc\u03c4\u03a6\u0398\u03a9\u03b4\u221e\u2205\u2208\u2229\u2261\u00b1\u2265\u2264\u2320\u2321\u00f7\u2248\u00b0\u2219\u00b7\u221a\u207f\u00b2\u25a0\u0000".indexOf(character); + let index = FontRenderer.CHAR_INDEX_LOOKUP.indexOf(character); let code = character.charCodeAt(0); // Handle color codes if character is & diff --git a/src/js/net/minecraft/client/render/gui/ItemRenderer.js b/src/js/net/minecraft/client/render/gui/ItemRenderer.js index 03159a8..b1e939a 100644 --- a/src/js/net/minecraft/client/render/gui/ItemRenderer.js +++ b/src/js/net/minecraft/client/render/gui/ItemRenderer.js @@ -7,13 +7,14 @@ export default class ItemRenderer { this.window = window; this.items = []; + this.zIndex = 0; + + this.scheduledDirty = []; } initialize() { // Create item camera - this.camera = new THREE.OrthographicCamera(0, 0, 0, 0, 0, 300); - this.camera.near = 0; - this.camera.far = 15; + this.camera = new THREE.OrthographicCamera(0, 0, 0, 0, -15, 15); this.camera.rotation.order = 'ZYX'; this.camera.up = new THREE.Vector3(0, 1, 0); @@ -50,33 +51,43 @@ export default class ItemRenderer { this.webRenderer.render(this.scene, this.camera); } - renderItemInGui(renderId, block, x, y) { - let meta = this.items[renderId]; + prepareRender(groupId) { + if (this.scheduledDirty.includes(groupId)) { + this.scheduledDirty.splice(this.scheduledDirty.indexOf(groupId), 1); + this.destroy(groupId); + } + } + + renderItemInGui(groupId, renderId, block, x, y, brightness = 1) { + let pairId = groupId + ':' + renderId; + let meta = this.items[pairId]; if (typeof meta === "undefined") { let meta = {}; - // To make the items darker - let paused = this.minecraft.isPaused(); - // Render item let group = new THREE.Group(); - this.minecraft.worldRenderer.blockRenderer.renderGuiBlock(group, block, x, y, 10, paused ? 0.5 : 1); + this.minecraft.worldRenderer.blockRenderer.renderGuiBlock(group, block, x, y, 10, brightness); + group.position.z = this.zIndex; + group.updateMatrix(); this.scene.add(group); // Create meta + meta.renderId = renderId; + meta.groupId = groupId; meta.group = group; + meta.brightness = brightness; meta.typeId = block.getId(); meta.x = x; meta.y = y; meta.dirty = false; - this.items[renderId] = meta; + this.items[pairId] = meta; } else { // Check if rendered item has changed - if (meta.dirty || meta.typeId !== block.getId() || meta.x !== x || meta.y !== y) { + if (meta.dirty || meta.typeId !== block.getId() || meta.x !== x || meta.y !== y || meta.brightness !== brightness) { // Rebuild item this.scene.remove(meta.group); - delete this.items[renderId]; - this.renderItemInGui(renderId, block, x, y); + delete this.items[pairId]; + this.renderItemInGui(groupId, renderId, block, x, y, brightness); } } } @@ -95,4 +106,27 @@ export default class ItemRenderer { this.items = []; this.webRenderer.clear(); } + + scheduleDirty(groupId) { + if (this.scheduledDirty.includes(groupId)) { + return; + } + this.scheduledDirty.push(groupId); + } + + destroy(groupId, renderId = null) { + let hit = false; + + for (let i in this.items) { + if (this.items[i].groupId === groupId && (renderId === null || this.items[i].renderId === renderId)) { + this.scene.remove(this.items[i].group); + delete this.items[i]; + hit = true; + } + } + + if (hit) { + this.webRenderer.clear(); + } + } } \ No newline at end of file diff --git a/src/js/net/minecraft/client/world/block/Block.js b/src/js/net/minecraft/client/world/block/Block.js index dfe5775..c9edf2f 100644 --- a/src/js/net/minecraft/client/world/block/Block.js +++ b/src/js/net/minecraft/client/world/block/Block.js @@ -49,7 +49,7 @@ export default class Block { shouldRenderFace(world, x, y, z, face) { let typeId = world.getBlockAtFace(x, y, z, face); - return typeId === 0 || !Block.getById(typeId).isSolid(); + return typeId === 0 || Block.getById(typeId).isTranslucent(); } getColor(world, x, y, z, face) { diff --git a/src/js/net/minecraft/client/world/block/BlockRegistry.js b/src/js/net/minecraft/client/world/block/BlockRegistry.js index 7617768..df0eaf9 100644 --- a/src/js/net/minecraft/client/world/block/BlockRegistry.js +++ b/src/js/net/minecraft/client/world/block/BlockRegistry.js @@ -9,6 +9,10 @@ import BlockTorch from "./type/BlockTorch.js"; import Sound from "./sound/Sound.js"; import Block from "./Block.js"; import BlockWood from "./type/BlockWood.js"; +import BlockBedrock from "./type/BlockBedrock.js"; +import BlockGlass from "./type/BlockGlass.js"; +import SoundGlass from "./sound/SoundGlass.js"; +import BlockGravel from "./type/BlockGravel.js"; export class BlockRegistry { @@ -20,14 +24,18 @@ export class BlockRegistry { Block.sounds.grass = new Sound("grass", 1.0); Block.sounds.cloth = new Sound("cloth", 1.0); Block.sounds.sand = new Sound("sand", 1.0); + Block.sounds.glass = new SoundGlass("stone", 1.0); // Blocks BlockRegistry.STONE = new BlockStone(1, 0); BlockRegistry.GRASS = new BlockGrass(2, 1); BlockRegistry.DIRT = new BlockDirt(3, 2); BlockRegistry.WOOD = new BlockWood(5, 10); + BlockRegistry.BEDROCK = new BlockBedrock(7, 11); + BlockRegistry.GRAVEL = new BlockGravel(13, 13); BlockRegistry.LOG = new BlockLog(17, 4); BlockRegistry.LEAVE = new BlockLeave(18, 6); + BlockRegistry.GLASS = new BlockGlass(20, 12); BlockRegistry.WATER = new BlockWater(9, 7); BlockRegistry.SAND = new BlockSand(12, 8) BlockRegistry.TORCH = new BlockTorch(50, 9) diff --git a/src/js/net/minecraft/client/world/block/sound/SoundGlass.js b/src/js/net/minecraft/client/world/block/sound/SoundGlass.js new file mode 100644 index 0000000..015d590 --- /dev/null +++ b/src/js/net/minecraft/client/world/block/sound/SoundGlass.js @@ -0,0 +1,13 @@ +import Sound from "./Sound.js"; + +export default class SoundGlass extends Sound { + + constructor(name, pitch) { + super(name, pitch); + } + + getBreakSound() { + return "random.glass"; + } + +} \ No newline at end of file diff --git a/src/js/net/minecraft/client/world/block/type/BlockBedrock.js b/src/js/net/minecraft/client/world/block/type/BlockBedrock.js new file mode 100644 index 0000000..dbd597d --- /dev/null +++ b/src/js/net/minecraft/client/world/block/type/BlockBedrock.js @@ -0,0 +1,9 @@ +import Block from "../Block.js"; + +export default class BlockBedrock extends Block { + + constructor(id, textureSlotId) { + super(id, textureSlotId); + } + +} \ No newline at end of file diff --git a/src/js/net/minecraft/client/world/block/type/BlockGlass.js b/src/js/net/minecraft/client/world/block/type/BlockGlass.js new file mode 100644 index 0000000..9d5af5a --- /dev/null +++ b/src/js/net/minecraft/client/world/block/type/BlockGlass.js @@ -0,0 +1,24 @@ +import Block from "../Block.js"; + +export default class BlockGlass extends Block { + + constructor(id, textureSlotId) { + super(id, textureSlotId); + + // Sound + this.sound = Block.sounds.glass; + } + + isTranslucent() { + return true; + } + + shouldRenderFace(world, x, y, z, face) { + let typeId = world.getBlockAtFace(x, y, z, face); + return typeId === 0 || typeId !== this.id; + } + + getOpacity() { + return 0; + } +} \ No newline at end of file diff --git a/src/js/net/minecraft/client/world/block/type/BlockGravel.js b/src/js/net/minecraft/client/world/block/type/BlockGravel.js new file mode 100644 index 0000000..a14a6a9 --- /dev/null +++ b/src/js/net/minecraft/client/world/block/type/BlockGravel.js @@ -0,0 +1,12 @@ +import Block from "../Block.js"; + +export default class BlockGravel extends Block { + + constructor(id, textureSlotId) { + super(id, textureSlotId); + + // Sound + this.sound = Block.sounds.gravel; + } + +} \ No newline at end of file diff --git a/src/js/net/minecraft/client/world/block/type/BlockLeave.js b/src/js/net/minecraft/client/world/block/type/BlockLeave.js index a07e067..78764d5 100644 --- a/src/js/net/minecraft/client/world/block/type/BlockLeave.js +++ b/src/js/net/minecraft/client/world/block/type/BlockLeave.js @@ -9,6 +9,11 @@ export default class BlockLeave extends Block { this.sound = Block.sounds.grass; } + // TODO fix transparency of leaves + /*isTranslucent() { + return true; + }*/ + getColor(world, x, y, z, face) { // Inventory items have a default color if (world === null) { @@ -20,6 +25,12 @@ export default class BlockLeave extends Block { return world.minecraft.grassColorizer.getColor(temperature, humidity); } + // TODO fix transparency of leaves + /*shouldRenderFace(world, x, y, z, face) { + let typeId = world.getBlockAtFace(x, y, z, face); + return typeId === 0 || typeId === this.id; + }*/ + getOpacity() { return 0.3; } diff --git a/src/js/net/minecraft/client/world/block/type/BlockTorch.js b/src/js/net/minecraft/client/world/block/type/BlockTorch.js index 830e0d2..6ffcb65 100644 --- a/src/js/net/minecraft/client/world/block/type/BlockTorch.js +++ b/src/js/net/minecraft/client/world/block/type/BlockTorch.js @@ -31,6 +31,10 @@ export default class BlockTorch extends Block { return false; } + isTranslucent() { + return true; + } + getRenderType() { return BlockRenderType.TORCH; } diff --git a/src/js/net/minecraft/client/world/block/type/BlockWater.js b/src/js/net/minecraft/client/world/block/type/BlockWater.js index f8736f4..2ca22b1 100644 --- a/src/js/net/minecraft/client/world/block/type/BlockWater.js +++ b/src/js/net/minecraft/client/world/block/type/BlockWater.js @@ -34,7 +34,7 @@ export default class BlockWater extends Block { getBoundingBox(world, x, y, z) { let box = this.boundingBox.clone(); - if (world.getBlockAt(x, y + 1, z) !== this.id) { + if (world !== null && world.getBlockAt(x, y + 1, z) !== this.id) { box.maxY = 1.0 - 0.12; } return box; diff --git a/src/js/net/minecraft/client/world/generator/WorldGenerator.js b/src/js/net/minecraft/client/world/generator/WorldGenerator.js index c844d8c..8279e5d 100644 --- a/src/js/net/minecraft/client/world/generator/WorldGenerator.js +++ b/src/js/net/minecraft/client/world/generator/WorldGenerator.js @@ -205,8 +205,8 @@ export default class WorldGenerator extends Generator { // For the entire height of the chunk for (let y = 127; y >= 0; y--) { // Set bedrock on floor level - if (y <= (this.random.nextInt(6)) - 1) { - primer.set(x, y, z, BlockRegistry.STONE.getId()); // TODO add bedrock block + if (y <= (this.random.nextInt(6)) - 1 || y === 0) { + primer.set(x, y, z, BlockRegistry.BEDROCK.getId()); continue; } @@ -238,7 +238,7 @@ export default class WorldGenerator extends Generator { // Add gravel patches if (gravelPatchNoise) { topLayerTypeId = 0; - innerLayerTypeId = BlockRegistry.STONE.getId(); // TODO add gravel block + innerLayerTypeId = BlockRegistry.GRAVEL.getId(); } // Add sand patches diff --git a/src/resources/gui/container/creative.png b/src/resources/gui/container/creative.png new file mode 100644 index 0000000..4776e62 Binary files /dev/null and b/src/resources/gui/container/creative.png differ diff --git a/src/resources/terrain/terrain.png b/src/resources/terrain/terrain.png index a530bfe..33634fb 100644 Binary files a/src/resources/terrain/terrain.png and b/src/resources/terrain/terrain.png differ