Files
GameStarter/src/js/net/minecraft/client/Minecraft.js
T

448 lines
14 KiB
JavaScript

import Timer from "../util/Timer.js";
import GameSettings from "./GameSettings.js";
import GameWindow from "./GameWindow.js";
import WorldRenderer from "./render/WorldRenderer.js";
import ScreenRenderer from "./render/gui/ScreenRenderer.js";
import ItemRenderer from "./render/gui/ItemRenderer.js";
import IngameOverlay from "./gui/overlay/IngameOverlay.js";
import PlayerEntity from "./entity/PlayerEntity.js";
import SoundManager from "./sound/SoundManager.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";
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.4"
static URL_GITHUB = "https://github.com/labystudio/js-minecraft";
/**
* Create Minecraft instance and render it on a canvas
*/
constructor(canvasWrapperId, resources) {
this.resources = resources;
this.currentScreen = null;
this.loadingScreen = null;
this.world = null;
this.player = null;
this.fps = 0;
// Tick timer
this.timer = new Timer(20);
this.settings = new GameSettings();
this.settings.load();
// Create window and world renderer
this.window = new GameWindow(this, canvasWrapperId);
// Create renderers
this.worldRenderer = new WorldRenderer(this, this.window);
this.screenRenderer = new ScreenRenderer(this, this.window);
this.itemRenderer = new ItemRenderer(this, this.window);
// Create current screen and overlay
this.ingameOverlay = new IngameOverlay(this, this.window);
// Command handler
this.commandHandler = new CommandHandler(this);
this.frames = 0;
this.lastTime = Date.now();
// Create all blocks
BlockRegistry.create();
this.itemRenderer.initialize();
// Create font renderer
this.fontRenderer = new FontRenderer(this);
// Grass colorizer
this.grassColorizer = new GrassColorizer(this);
this.particleRenderer = new ParticleRenderer(this);
// Update window size
this.window.updateWindowSize();
// Create sound manager
this.soundManager = new SoundManager();
this.displayScreen(new GuiMainMenu());
// Initialize
this.init();
}
init() {
// Start render loop
this.running = true;
this.requestNextFrame();
}
loadWorld(world) {
if (world === null) {
this.worldRenderer.reset();
this.itemRenderer.reset();
this.world.chunks.clear();
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.player.username = "Player" + Math.floor(Math.random() * 100);
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;
}
addMessageToChat(message) {
this.ingameOverlay.chatOverlay.addMessage(message);
}
requestNextFrame() {
requestAnimationFrame(() => {
if (this.running) {
this.requestNextFrame();
this.onLoop();
}
});
}
onLoop() {
// Update the timer
this.timer.advanceTime();
// Call the tick to reach updates 20 per seconds
for (let i = 0; i < this.timer.ticks; i++) {
this.onTick();
}
// Render the game
this.onRender(this.timer.partialTicks);
// Increase rendered frame
this.frames++;
// Loop if a second passed
while (Date.now() >= this.lastTime + 1000) {
this.fps = this.frames;
this.lastTime += 1000;
this.frames = 0;
}
}
onRender(partialTicks) {
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;
}
// 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.error("Tried to display an undefined screen");
return;
}
// Close previous screen
if (this.currentScreen !== null) {
this.currentScreen.onClose();
}
// Switch screen
this.currentScreen = screen;
// Update window size
this.window.updateWindowSize();
// Initialize new screen
if (screen === null) {
this.window.requestFocus();
} else {
this.window.exitFocus();
screen.setup(this, this.window.width, this.window.height);
}
// Update items
this.itemRenderer.rebuildAllItems();
}
onTick() {
if (this.isInGame() && !this.isPaused()) {
// Tick world
this.world.onTick();
// Tick renderer
this.worldRenderer.onTick();
// Tick the player
this.player.onUpdate();
// Tick particle renderer
this.particleRenderer.onTick();
// Tick overlay
this.ingameOverlay.onTick();
}
// Tick the screen
if (this.currentScreen !== null) {
this.currentScreen.updateScreen();
}
// Update loading progress
if (this.loadingScreen !== null && this.isInGame()) {
let cameraChunkX = Math.floor(this.player.x) >> 4;
let cameraChunkZ = Math.floor(this.player.z) >> 4;
let renderDistance = this.settings.viewDistance;
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 >= 0.99) {
this.loadingScreen = null;
this.displayScreen(null);
}
}
}
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) {
if (this.window.mouseLocked) {
let hitResult = this.player.rayTrace(5, this.timer.partialTicks);
// Destroy block
if (button === 0) {
if (hitResult != null) {
// Get previous block
let typeId = this.world.getBlockAt(hitResult.x, hitResult.y, hitResult.z);
let block = Block.getById(typeId);
if (typeId !== 0) {
let soundName = block.getSound().getBreakSound();
// Play sound
this.soundManager.playSound(
soundName,
hitResult.x + 0.5,
hitResult.y + 0.5,
hitResult.z + 0.5,
1.0,
1.0
);
// Spawn particle
this.particleRenderer.spawnBlockBreakParticle(this.world, hitResult.x, hitResult.y, hitResult.z);
// Destroy block
this.world.setBlockAt(hitResult.x, hitResult.y, hitResult.z, 0);
}
}
this.player.swingArm();
}
// Pick block
if (button === 1) {
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);
}
}
}
// Place block
if (button === 2) {
if (hitResult != null) {
let x = hitResult.x + hitResult.face.x;
let y = hitResult.y + hitResult.face.y;
let z = hitResult.z + hitResult.face.z;
let placedBoundingBox = new BoundingBox(x, y, z, x + 1, y + 1, z + 1);
// Don't place blocks if the player is standing there
if (!placedBoundingBox.intersects(this.player.boundingBox)) {
let typeId = this.player.inventory.getItemInSelectedSlot();
// Get previous block
let prevTypeId = this.world.getBlockAt(x, y, z);
if (typeId !== 0 && prevTypeId !== typeId) {
// Place block
this.world.setBlockAt(x, y, z, typeId);
// Swing player arm
this.player.swingArm();
// Handle block abilities
let block = Block.getById(typeId);
block.onBlockPlaced(this.world, x, y, z, hitResult.face);
// Play sound
let sound = block.getSound();
let soundName = sound.getStepSound();
this.soundManager.playSound(
soundName,
hitResult.x + 0.5,
hitResult.y + 0.5,
hitResult.z + 0.5,
1.0,
sound.getPitch() * 0.8
);
}
}
}
}
// Rebuild multiple chunk sections
this.worldRenderer.flushRebuild = true;
}
}
onMouseScroll(delta) {
if (this.isInGame()) {
this.player.inventory.shiftSelectedSlot(delta);
}
}
isPaused() {
return !this.hasInGameFocus() && this.loadingScreen === null;
}
stop() {
if (this.currentScreen !== null) {
this.currentScreen.onClose();
}
this.running = false;
this.worldRenderer.reset();
this.itemRenderer.reset();
this.screenRenderer.reset();
this.window.close();
}
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);
}
}