Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 17 additions & 3 deletions src/entities/Player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,19 +48,23 @@ export class Player implements IPlayerForSettings {
private input: PlayerInput;
private interactor = new BlockInteractor();

private initPos: THREE.Vector3 = new THREE.Vector3();

constructor(scene: THREE.Scene) {
this.cameraHelper.visible = false;
this.controls.pointerSpeed = 2;

this.position.set(32, 32, 32);

scene.add(this.camera);
scene.add(this.cameraHelper);
scene.add(this.selectionHelper);

this.input = new PlayerInput();
}

public getInput(): PlayerInput {
return this.input;
}

get position() {
return this.camera.position;
}
Expand All @@ -73,6 +77,16 @@ export class Player implements IPlayerForSettings {
return this.worldVelocityVector;
}

set position(pos: THREE.Vector3) {
this.position = pos;
}

// it should be called only once when world is generated or player respawned
set initPosition(pos: THREE.Vector3) {
this.initPos.copy(pos);
this.position.copy(this.initPos);
}

public applySettings(settings: PlayerSettings) {
this.maxSpeed = settings.maxSpeed;
this.cameraHelper.visible = settings.showCameraHelper;
Expand Down Expand Up @@ -102,7 +116,7 @@ export class Player implements IPlayerForSettings {
const cmd = this.input.read(this.maxSpeed);

if (cmd.resetPressed) {
this.position.set(32, 16, 32);
this.position.copy(this.initPos);
this.velocity.set(0, 0, 0);
}

Expand Down
35 changes: 35 additions & 0 deletions src/entities/PlayerInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,34 @@ export class PlayerInput {
KeyD: false,
Space: false,
KeyR: false,
F5: false,
F9: false,
};

private jumpQueued = false;
private resetQueued = false;

private onSave?: () => void;
private onLoad?: () => void;
private onReset?: () => void;

constructor() {
document.addEventListener('keydown', this.onKeyDown);
document.addEventListener('keyup', this.onKeyUp);
}

public setOnSaveCallback(callback: () => void) {
this.onSave = callback;
}

public setOnLoadCallback(callback: () => void) {
this.onLoad = callback;
}

public setOnResetCallback(callback: () => void) {
this.onReset = callback;
}

public read(maxSpeed: number): PlayerCommand {
let moveZ = 0;
let moveX = 0;
Expand Down Expand Up @@ -51,6 +69,23 @@ export class PlayerInput {

if (event.code === 'KeyR') {
this.resetQueued = true;
if (this.onReset) {
this.onReset();
}
}

if (event.code === 'F5') {
event.preventDefault();
if(this.onSave){
this.onSave();
}
}

if (event.code === 'F9') {
event.preventDefault();
if(this.onLoad){
this.onLoad();
}
}

return;
Expand Down
78 changes: 77 additions & 1 deletion src/game/Game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ export class Game {
height: window.innerHeight,
};

private playerInitPos: THREE.Vector3;

private dropSystem: DropSystem;

private onResizeBound = () => this.onResize();
Expand All @@ -77,13 +79,17 @@ export class Game {
this.settingsApplier = new SettingsApplier(
this.fog,
this.world,
this.player
this.player,
this.world
);
this.gui = new GameGUI(this.settingsStore);
this.gui.build();
this.settingsStore.subscribe((settings) =>
this.settingsApplier.apply(settings)
);
this.world.setOnRegenerate(() => {
this.settingsApplier.respawnPlayerOnHighestBlock();
});

this.hotbarUI = new HotbarUI(this.player.inventory, {
inputBlocked: () =>
Expand Down Expand Up @@ -123,6 +129,12 @@ export class Game {
);

this.setupWorld();

const spawnX = this.settingsStore.settings.player.spawnPoint.x;
const spawnZ = this.settingsStore.settings.player.spawnPoint.z;
this.playerInitPos = this.world.getSpawnPoint(spawnX, spawnZ);
this.player.initPosition = this.playerInitPos;

this.setupStats();
this.sun.setup();
this.fog.setup();
Expand All @@ -131,12 +143,76 @@ export class Game {
window.addEventListener('resize', this.onResizeBound);
document.addEventListener('mousedown', this.onMouseDownBound);
document.addEventListener('mouseup', this.onMouseUpBound);

this.player.getInput().setOnSaveCallback(() => this.saveGame());
this.player.getInput().setOnLoadCallback(() => this.loadGame());
this.player.getInput().setOnResetCallback(() => this.resetGame());
}

public start() {
this.renderLoop();
}

private saveGame() {
const gameState = {
playerPosition: {
x: this.player.position.x,
y: this.player.position.y,
z: this.player.position.z,
},
playerVelocity: {
x: this.player.velocity.x,
y: this.player.velocity.y,
z: this.player.velocity.z,
},
inventory: this.player.inventory.toJSON(),
worldData: this.world.getDataStore().toJSON(),
timestamp: Date.now(),
};

localStorage.setItem('minecraftGameSave', JSON.stringify(gameState));
console.log('Game saved!');
}

private loadGame() {
const saved = localStorage.getItem('minecraftGameSave');
if (!saved) {
console.log('No save found!');
return;
}

try {
const gameState = JSON.parse(saved);

this.player.position.set(
gameState.playerPosition.x,
gameState.playerPosition.y,
gameState.playerPosition.z
);

this.player.velocity.set(
gameState.playerVelocity.x,
gameState.playerVelocity.y,
gameState.playerVelocity.z
);

this.player.inventory.fromJSON(gameState.inventory);
this.world.getDataStore().fromJSON(gameState.worldData);
this.world.regenerateChunks();

console.log('Game loaded!');
} catch (error) {
console.error('Failed to load game:', error);
}
}

private resetGame() {
localStorage.removeItem('minecraftGameSave');
this.world.getDataStore().clear();
this.world.regenerateChunks();
console.log('Game reset! World regenerated.');
}

private setupWorld() {
this.world.generate();
this.scene.add(this.world);
Expand Down
2 changes: 1 addition & 1 deletion src/settings/Settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { GameSettings } from '../types/settings.types';

export const createDefaultSettings = (): GameSettings => ({
fog: { near: 50, far: 100, enabled: true },
player: { maxSpeed: 10, showCameraHelper: false },
player: { spawnPoint: { x: 32, y: 0, z: 32 }, maxSpeed: 10, showCameraHelper: false },
world: {
renderDistance: 2,
seed: 0,
Expand Down
19 changes: 16 additions & 3 deletions src/settings/SettingsApplier.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,41 @@
import type {
IFogForSettings,
IWorldForSettings,
IPlayerForSettings,
GameSettings,
} from '../types/settings.types';
import type { Player } from '../entities/Player';
import type { World } from '../world/World';

export class SettingsApplier {
private fog: IFogForSettings;
private world: IWorldForSettings;
private player: IPlayerForSettings;
private player: Player;
private worldInstance: World;

constructor(
fog: IFogForSettings,
world: IWorldForSettings,
player: IPlayerForSettings
player: Player,
worldInstance: World
) {
this.fog = fog;
this.world = world;
this.player = player;
this.worldInstance = worldInstance;
}

public apply(settings: GameSettings) {
this.fog.applySettings(settings.fog);
this.player.applySettings(settings.player);
this.world.applySettings(settings.world);
}

public respawnPlayerOnHighestBlock() {
const currentX = this.player.position.x;
const currentZ = this.player.position.z;
const spawnPoint = this.worldInstance.getSpawnPoint(currentX, currentZ);
this.player.position.copy(spawnPoint);
this.player.initPosition = spawnPoint;
this.player.velocity.set(0, 0, 0);
}
}
1 change: 1 addition & 0 deletions src/types/settings.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export interface IFogForSettings {
}

export type PlayerSettings = {
spawnPoint: { x: number; y: number; z: number };
maxSpeed: number;
showCameraHelper: boolean;
};
Expand Down
19 changes: 19 additions & 0 deletions src/ui/Inventory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,23 @@ export class Inventory {
stack.count = remaining;
return false;
}

public toJSON(): Slot[] {
return this.slots.map(slot => slot ? { ...slot } : null);
}

public fromJSON(data: Slot[]) {
for (let i = 0; i < Math.min(data.length, this.size); i++) {
const slot = data[i];
if (slot && slot.itemId !== undefined && slot.count !== undefined) {
this.slots[i] = {
itemId: slot.itemId,
count: slot.count,
durability: slot.durability,
};
} else {
this.slots[i] = null;
}
}
}
}
15 changes: 15 additions & 0 deletions src/world/DataStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,21 @@ export class DataStore {
this.data.clear();
}

public toJSON(): Record<string, number> {
const obj: Record<string, number> = {};
this.data.forEach((value, key) => {
obj[key] = value;
});
return obj;
}

public fromJSON(obj: Record<string, number>) {
this.data.clear();
Object.entries(obj).forEach(([key, value]) => {
this.data.set(key, value);
});
}

public set(
chunkX: number,
chunkZ: number,
Expand Down
Loading