From 34b0015af6f939ad1e6ad19790ae162e55d4075b Mon Sep 17 00:00:00 2001 From: LabyStudio Date: Fri, 20 May 2022 01:30:31 +0200 Subject: [PATCH] implement creative inventory, implement bedrock, glass and gravel, version 1.0.4 --- README.md | 1 + index.html | 2 +- src/js/Start.js | 3 +- src/js/net/minecraft/client/GameSettings.js | 2 + src/js/net/minecraft/client/GameWindow.js | 26 ++-- src/js/net/minecraft/client/Minecraft.js | 29 +++- .../minecraft/client/entity/PlayerEntity.js | 5 +- src/js/net/minecraft/client/gui/Gui.js | 4 +- .../client/gui/overlay/IngameOverlay.js | 17 ++- .../minecraft/client/gui/screens/GuiChat.js | 2 +- .../client/gui/screens/GuiContainer.js | 136 ++++++++++++++++++ .../client/gui/screens/GuiControls.js | 12 +- .../screens/container/GuiContainerCreative.js | 50 +++++++ .../minecraft/client/inventory/Container.js | 38 +++++ .../minecraft/client/inventory/Inventory.js | 34 +---- src/js/net/minecraft/client/inventory/Slot.js | 10 ++ .../inventory/container/ContainerCreative.js | 78 ++++++++++ .../inventory/container/ContainerPlayer.js | 11 ++ .../inventory/inventory/InventoryBasic.js | 19 +++ .../inventory/inventory/InventoryPlayer.js | 36 +++++ .../minecraft/client/render/BlockRenderer.js | 4 +- .../client/render/gui/FontRenderer.js | 7 +- .../client/render/gui/ItemRenderer.js | 60 ++++++-- .../net/minecraft/client/world/block/Block.js | 2 +- .../client/world/block/BlockRegistry.js | 8 ++ .../client/world/block/sound/SoundGlass.js | 13 ++ .../client/world/block/type/BlockBedrock.js | 9 ++ .../client/world/block/type/BlockGlass.js | 24 ++++ .../client/world/block/type/BlockGravel.js | 12 ++ .../client/world/block/type/BlockLeave.js | 11 ++ .../client/world/block/type/BlockTorch.js | 4 + .../client/world/block/type/BlockWater.js | 2 +- .../client/world/generator/WorldGenerator.js | 6 +- src/resources/gui/container/creative.png | Bin 0 -> 883 bytes src/resources/terrain/terrain.png | Bin 7495 -> 8621 bytes 35 files changed, 601 insertions(+), 76 deletions(-) create mode 100644 src/js/net/minecraft/client/gui/screens/GuiContainer.js create mode 100644 src/js/net/minecraft/client/gui/screens/container/GuiContainerCreative.js create mode 100644 src/js/net/minecraft/client/inventory/Container.js create mode 100644 src/js/net/minecraft/client/inventory/Slot.js create mode 100644 src/js/net/minecraft/client/inventory/container/ContainerCreative.js create mode 100644 src/js/net/minecraft/client/inventory/container/ContainerPlayer.js create mode 100644 src/js/net/minecraft/client/inventory/inventory/InventoryBasic.js create mode 100644 src/js/net/minecraft/client/inventory/inventory/InventoryPlayer.js create mode 100644 src/js/net/minecraft/client/world/block/sound/SoundGlass.js create mode 100644 src/js/net/minecraft/client/world/block/type/BlockBedrock.js create mode 100644 src/js/net/minecraft/client/world/block/type/BlockGlass.js create mode 100644 src/js/net/minecraft/client/world/block/type/BlockGravel.js create mode 100644 src/resources/gui/container/creative.png 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 0000000000000000000000000000000000000000..4776e62959cf290b1909dfb4624eb71762eaefca GIT binary patch literal 883 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K58911MRQ8&P5D>38$lZxy-8q?;Kn_c~qpu?a z!^VE@KZ&eBzFdG$h%1o(`t@sWZtk^f*L-|@`uqDsLqnfEduC>429zrMZRZW7xJrWj zg8#z+!=^Jj=YgV}1s;*b3=Dh+K$tP>S|=w11Jeml7srr_xVJYiX5BFma7%o@&G*&! z`iJjUs26&?OW{=cSDeSjf5b!nrpUJAm2%bP_g-7{AHR23#G-8f|Id~F3mE_0+OEj3 zC;L2$fCIz2OVb&cI20Jx-!f)o5olQbMfCvtf%i{;SDw1n!~Em(+T&TXt)5Erul;_D zL!p5oW~n|F< z2R>cZ{LAs-g3tmpkh{WPdINO}EV!q9U{7=Ztt*66(q#;35%J^aL>8qdsacQs>YS_IUJq!y85}Sb4q9e0DRq54gdfE literal 0 HcmV?d00001 diff --git a/src/resources/terrain/terrain.png b/src/resources/terrain/terrain.png index a530bfef1dfe7e0bdb1f8ab57c01d5a888d10bb0..33634fb9b8292a2bb780322e55d208bcefb3baea 100644 GIT binary patch literal 8621 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 Sv0VK81U%9*cvz)j7yWN4*CPf1