Files
nagi-bench/outputs/mimo-v2-5-pro/claude-code-max/mythos-craft.html
T

1145 lines
43 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mythos Craft</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { overflow: hidden; background: #000; font-family: 'Courier New', monospace; }
#blocker {
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0,0,0,0.7); display: flex; align-items: center;
justify-content: center; z-index: 100; cursor: pointer;
}
#instructions {
color: #fff; text-align: center; padding: 40px;
background: rgba(0,0,0,0.8); border: 2px solid #4a9eff;
border-radius: 10px; max-width: 500px;
}
#instructions h1 { font-size: 36px; color: #4a9eff; margin-bottom: 20px; }
#instructions p { margin: 10px 0; line-height: 1.6; }
#instructions .key {
display: inline-block; background: #333; padding: 2px 8px;
border-radius: 4px; border: 1px solid #666; margin: 0 2px;
}
#crosshair {
position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
width: 20px; height: 20px; z-index: 10; pointer-events: none;
}
#crosshair::before, #crosshair::after {
content: ''; position: absolute; background: rgba(255,255,255,0.8);
}
#crosshair::before { width: 2px; height: 20px; left: 9px; top: 0; }
#crosshair::after { width: 20px; height: 2px; top: 9px; left: 0; }
#hud {
position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%);
display: flex; gap: 4px; z-index: 10; background: rgba(0,0,0,0.5);
padding: 8px; border-radius: 8px;
}
.slot {
width: 50px; height: 50px; border: 2px solid #555;
display: flex; align-items: center; justify-content: center;
cursor: pointer; position: relative; background: rgba(0,0,0,0.3);
}
.slot.active { border-color: #4a9eff; background: rgba(74,158,255,0.2); }
.slot .block-preview { width: 36px; height: 36px; border-radius: 4px; }
.slot .slot-num {
position: absolute; top: 2px; left: 4px; color: #aaa;
font-size: 10px;
}
#info {
position: fixed; top: 10px; left: 10px; color: #fff;
font-size: 14px; z-index: 10; text-shadow: 1px 1px 2px #000;
}
#info div { margin: 4px 0; }
#minimap {
position: fixed; top: 10px; right: 10px; width: 150px; height: 150px;
border: 2px solid #4a9eff; z-index: 10; background: #1a1a2e;
}
#landmark-indicator {
position: fixed; top: 50%; left: 50%; transform: translate(-50%, -80px);
color: #ffd700; font-size: 16px; z-index: 10; text-shadow: 1px 1px 2px #000;
pointer-events: none; text-align: center; display: none;
}
</style>
</head>
<body>
<div id="blocker">
<div id="instructions">
<h1>🏛️ Mythos Craft</h1>
<p>A Minecraft-style voxel world with mythic structures</p>
<p style="margin-top: 20px;">
<span class="key">W</span><span class="key">A</span><span class="key">S</span><span class="key">D</span> Move
<span class="key">Space</span> Jump
</p>
<p>
<span class="key">Mouse</span> Look
<span class="key">Left Click</span> Break
<span class="key">Right Click</span> Place
</p>
<p>
<span class="key">1-9</span> Select Block
<span class="key">G</span> Teleport to Landmark
</p>
<p style="color: #ffd700; margin-top: 20px;">Click to start</p>
</div>
</div>
<div id="crosshair"></div>
<div id="hud"></div>
<div id="info">
<div>FPS: <span id="fps">0</span></div>
<div>Position: <span id="pos">0, 0, 0</span></div>
<div>Block: <span id="block-name">Grass</span></div>
</div>
<canvas id="minimap"></canvas>
<div id="landmark-indicator"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
// ============================================================
// PERLIN NOISE IMPLEMENTATION
// ============================================================
class PerlinNoise {
constructor(seed = 0) {
this.p = new Uint8Array(512);
const perm = new Uint8Array(256);
for (let i = 0; i < 256; i++) perm[i] = i;
// Fisher-Yates shuffle with seed
let s = seed;
for (let i = 255; i > 0; i--) {
s = (s * 16807 + 0) % 2147483647;
const j = s % (i + 1);
[perm[i], perm[j]] = [perm[j], perm[i]];
}
for (let i = 0; i < 512; i++) {
this.p[i] = perm[i & 255];
}
}
fade(t) { return t * t * t * (t * (t * 6 - 15) + 10); }
lerp(t, a, b) { return a + t * (b - a); }
grad(hash, x, y, z) {
const h = hash & 15;
const u = h < 8 ? x : y;
const v = h < 4 ? y : (h === 12 || h === 14 ? x : z);
return ((h & 1) === 0 ? u : -u) + ((h & 2) === 0 ? v : -v);
}
noise(x, y, z) {
const X = Math.floor(x) & 255;
const Y = Math.floor(y) & 255;
const Z = Math.floor(z) & 255;
x -= Math.floor(x);
y -= Math.floor(y);
z -= Math.floor(z);
const u = this.fade(x);
const v = this.fade(y);
const w = this.fade(z);
const A = this.p[X] + Y;
const AA = this.p[A] + Z;
const AB = this.p[A + 1] + Z;
const B = this.p[X + 1] + Y;
const BA = this.p[B] + Z;
const BB = this.p[B + 1] + Z;
return this.lerp(w,
this.lerp(v,
this.lerp(u, this.grad(this.p[AA], x, y, z), this.grad(this.p[BA], x-1, y, z)),
this.lerp(u, this.grad(this.p[AB], x, y-1, z), this.grad(this.p[BB], x-1, y-1, z))
),
this.lerp(v,
this.lerp(u, this.grad(this.p[AA+1], x, y, z-1), this.grad(this.p[BA+1], x-1, y, z-1)),
this.lerp(u, this.grad(this.p[AB+1], x, y-1, z-1), this.grad(this.p[BB+1], x-1, y-1, z-1))
)
);
}
octave(x, y, z, octaves = 4, persistence = 0.5) {
let total = 0, frequency = 1, amplitude = 1, maxValue = 0;
for (let i = 0; i < octaves; i++) {
total += this.noise(x * frequency, y * frequency, z * frequency) * amplitude;
maxValue += amplitude;
amplitude *= persistence;
frequency *= 2;
}
return total / maxValue;
}
}
// ============================================================
// BLOCK TYPES
// ============================================================
const BLOCKS = {
AIR: 0,
GRASS: 1,
DIRT: 2,
STONE: 3,
SAND: 4,
WATER: 5,
WOOD: 6,
LEAVES: 7,
COBBLESTONE: 8,
SNOW: 9,
CRYSTAL: 10,
GOLD: 11,
OBSIDIAN: 12
};
const BLOCK_COLORS = {
[BLOCKS.GRASS]: { top: 0x4CAF50, side: 0x8B6914, bottom: 0x8B6914 },
[BLOCKS.DIRT]: { top: 0x8B6914, side: 0x8B6914, bottom: 0x8B6914 },
[BLOCKS.STONE]: { top: 0x808080, side: 0x808080, bottom: 0x808080 },
[BLOCKS.SAND]: { top: 0xF4D03F, side: 0xF4D03F, bottom: 0xF4D03F },
[BLOCKS.WATER]: { top: 0x3498DB, side: 0x2980B9, bottom: 0x2471A3 },
[BLOCKS.WOOD]: { top: 0x6D4C41, side: 0x5D4037, bottom: 0x6D4C41 },
[BLOCKS.LEAVES]: { top: 0x2E7D32, side: 0x2E7D32, bottom: 0x2E7D32 },
[BLOCKS.COBBLESTONE]: { top: 0x757575, side: 0x757575, bottom: 0x757575 },
[BLOCKS.SNOW]: { top: 0xFFFFFF, side: 0xE8E8E8, bottom: 0xE8E8E8 },
[BLOCKS.CRYSTAL]: { top: 0x00BCD4, side: 0x00ACC1, bottom: 0x0097A7 },
[BLOCKS.GOLD]: { top: 0xFFD700, side: 0xFFC107, bottom: 0xFFB300 },
[BLOCKS.OBSIDIAN]: { top: 0x1A1A2E, side: 0x16213E, bottom: 0x0F3460 }
};
const BLOCK_NAMES = {
[BLOCKS.GRASS]: 'Grass', [BLOCKS.DIRT]: 'Dirt', [BLOCKS.STONE]: 'Stone',
[BLOCKS.SAND]: 'Sand', [BLOCKS.WATER]: 'Water', [BLOCKS.WOOD]: 'Wood',
[BLOCKS.LEAVES]: 'Leaves', [BLOCKS.COBBLESTONE]: 'Cobblestone',
[BLOCKS.SNOW]: 'Snow', [BLOCKS.CRYSTAL]: 'Crystal', [BLOCKS.GOLD]: 'Gold',
[BLOCKS.OBSIDIAN]: 'Obsidian'
};
// ============================================================
// GAME STATE
// ============================================================
const CHUNK_SIZE = 16;
const RENDER_DISTANCE = 4;
const WORLD_SEED = 42;
const noise = new PerlinNoise(WORLD_SEED);
const chunks = new Map();
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x87CEEB);
scene.fog = new THREE.Fog(0x87CEEB, 50, 120);
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 200);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
document.body.appendChild(renderer.domElement);
// Player state
const player = {
position: new THREE.Vector3(0, 40, 0),
velocity: new THREE.Vector3(0, 0, 0),
rotation: { x: 0, y: 0 },
onGround: false,
selectedSlot: 0,
speed: 5,
jumpForce: 8,
gravity: -20
};
const inventory = [
BLOCKS.GRASS, BLOCKS.DIRT, BLOCKS.STONE, BLOCKS.SAND,
BLOCKS.WOOD, BLOCKS.LEAVES, BLOCKS.COBBLESTONE, BLOCKS.SNOW, BLOCKS.CRYSTAL
];
const keys = {};
let isLocked = false;
let lastTime = performance.now();
let frameCount = 0;
let fps = 0;
// ============================================================
// MYTHIC LANDMARKS
// ============================================================
const landmarks = [
{ name: 'Crystal Tower', x: 50, z: 50, teleportY: 45 },
{ name: 'Stone Circle', x: -60, z: 40, teleportY: 35 },
{ name: 'Dragon Skeleton', x: 40, z: -70, teleportY: 40 },
{ name: 'Sky Ruins', x: -50, z: -60, teleportY: 60 }
];
let currentLandmark = -1;
// ============================================================
// TEXTURE GENERATION
// ============================================================
function createBlockTexture(blockType, face) {
const canvas = document.createElement('canvas');
canvas.width = 16;
canvas.height = 16;
const ctx = canvas.getContext('2d');
const colors = BLOCK_COLORS[blockType];
const color = face === 'top' ? colors.top : (face === 'bottom' ? colors.bottom : colors.side);
// Base color
ctx.fillStyle = `#${color.toString(16).padStart(6, '0')}`;
ctx.fillRect(0, 0, 16, 16);
// Add texture details
if (blockType === BLOCKS.GRASS && face === 'top') {
// Grass blades
ctx.fillStyle = '#388E3C';
for (let i = 0; i < 20; i++) {
const x = Math.random() * 16;
const y = Math.random() * 16;
ctx.fillRect(x, y, 1, 2);
}
} else if (blockType === BLOCKS.GRASS && face === 'side') {
// Dirt with grass top
ctx.fillStyle = '#4CAF50';
ctx.fillRect(0, 0, 16, 3);
} else if (blockType === BLOCKS.STONE || blockType === BLOCKS.COBBLESTONE) {
// Stone cracks
ctx.fillStyle = '#616161';
for (let i = 0; i < 5; i++) {
ctx.beginPath();
ctx.moveTo(Math.random() * 16, Math.random() * 16);
ctx.lineTo(Math.random() * 16, Math.random() * 16);
ctx.stroke();
}
} else if (blockType === BLOCKS.WOOD && face === 'side') {
// Wood grain
ctx.fillStyle = '#4E342E';
for (let y = 0; y < 16; y += 4) {
ctx.fillRect(0, y, 16, 1);
}
} else if (blockType === BLOCKS.LEAVES) {
// Leaf holes
ctx.fillStyle = '#1B5E20';
for (let i = 0; i < 8; i++) {
ctx.fillRect(Math.random() * 14, Math.random() * 14, 2, 2);
}
} else if (blockType === BLOCKS.CRYSTAL) {
// Crystal shine
ctx.fillStyle = 'rgba(255,255,255,0.3)';
ctx.fillRect(2, 2, 4, 4);
ctx.fillRect(10, 8, 3, 3);
} else if (blockType === BLOCKS.GOLD) {
// Gold sparkle
ctx.fillStyle = 'rgba(255,255,255,0.4)';
ctx.fillRect(4, 4, 2, 2);
ctx.fillRect(10, 10, 2, 2);
}
const texture = new THREE.CanvasTexture(canvas);
texture.magFilter = THREE.NearestFilter;
texture.minFilter = THREE.NearestFilter;
return texture;
}
function createBlockMaterial(blockType) {
return {
top: new THREE.MeshLambertMaterial({ map: createBlockTexture(blockType, 'top') }),
side: new THREE.MeshLambertMaterial({ map: createBlockTexture(blockType, 'side') }),
bottom: new THREE.MeshLambertMaterial({ map: createBlockTexture(blockType, 'bottom') })
};
}
const blockMaterials = {};
for (const type of Object.values(BLOCKS)) {
if (type !== BLOCKS.AIR) {
blockMaterials[type] = createBlockMaterial(type);
}
}
// ============================================================
// CHUNK MANAGEMENT
// ============================================================
function getChunkKey(cx, cz) {
return `${cx},${cz}`;
}
function getHeight(x, z) {
// Multi-octave terrain
const base = noise.octave(x * 0.01, 0, z * 0.01, 4, 0.5) * 20 + 25;
const mountains = Math.max(0, noise.octave(x * 0.005, 0, z * 0.005, 3, 0.6)) * 30;
return Math.floor(base + mountains);
}
function getBiome(x, z) {
const temp = noise.octave(x * 0.003, 0, z * 0.003, 2, 0.5);
const moisture = noise.octave(x * 0.004, 100, z * 0.004, 2, 0.5);
if (temp > 0.3) return 'desert';
if (temp < -0.3) return 'snow';
if (moisture > 0.2) return 'forest';
return 'plains';
}
function generateChunk(cx, cz) {
const key = getChunkKey(cx, cz);
if (chunks.has(key)) return chunks.get(key);
const chunk = {
blocks: new Uint8Array(CHUNK_SIZE * 256 * CHUNK_SIZE),
mesh: null,
dirty: true
};
// Generate terrain
for (let x = 0; x < CHUNK_SIZE; x++) {
for (let z = 0; z < CHUNK_SIZE; z++) {
const worldX = cx * CHUNK_SIZE + x;
const worldZ = cz * CHUNK_SIZE + z;
const height = getHeight(worldX, worldZ);
const biome = getBiome(worldX, worldZ);
for (let y = 0; y < 256; y++) {
const idx = x + y * CHUNK_SIZE + z * CHUNK_SIZE * 256;
if (y === 0) {
chunk.blocks[idx] = BLOCKS.STONE;
} else if (y < height - 4) {
chunk.blocks[idx] = BLOCKS.STONE;
} else if (y < height) {
chunk.blocks[idx] = biome === 'desert' ? BLOCKS.SAND : BLOCKS.DIRT;
} else if (y === height) {
if (biome === 'desert') chunk.blocks[idx] = BLOCKS.SAND;
else if (biome === 'snow') chunk.blocks[idx] = BLOCKS.SNOW;
else chunk.blocks[idx] = BLOCKS.GRASS;
} else if (y <= 20 && y > height) {
chunk.blocks[idx] = BLOCKS.WATER;
} else {
chunk.blocks[idx] = BLOCKS.AIR;
}
}
// Trees
if (height > 20 && biome === 'forest' && Math.random() < 0.03) {
const treeHeight = 4 + Math.floor(Math.random() * 3);
for (let ty = 1; ty <= treeHeight; ty++) {
const idx = x + (height + ty) * CHUNK_SIZE + z * CHUNK_SIZE * 256;
chunk.blocks[idx] = BLOCKS.WOOD;
}
// Leaves
for (let lx = -2; lx <= 2; lx++) {
for (let lz = -2; lz <= 2; lz++) {
for (let ly = treeHeight - 1; ly <= treeHeight + 1; ly++) {
const nx = x + lx, nz = z + lz;
if (nx >= 0 && nx < CHUNK_SIZE && nz >= 0 && nz < CHUNK_SIZE) {
const idx = nx + (height + ly) * CHUNK_SIZE + nz * CHUNK_SIZE * 256;
if (chunk.blocks[idx] === BLOCKS.AIR) {
chunk.blocks[idx] = BLOCKS.LEAVES;
}
}
}
}
}
}
}
}
chunks.set(key, chunk);
return chunk;
}
function getBlock(x, y, z) {
if (y < 0 || y >= 256) return BLOCKS.AIR;
const cx = Math.floor(x / CHUNK_SIZE);
const cz = Math.floor(z / CHUNK_SIZE);
const key = getChunkKey(cx, cz);
const chunk = chunks.get(key);
if (!chunk) return BLOCKS.AIR;
const lx = ((x % CHUNK_SIZE) + CHUNK_SIZE) % CHUNK_SIZE;
const lz = ((z % CHUNK_SIZE) + CHUNK_SIZE) % CHUNK_SIZE;
return chunk.blocks[lx + y * CHUNK_SIZE + lz * CHUNK_SIZE * 256];
}
function setBlock(x, y, z, type) {
if (y < 0 || y >= 256) return;
const cx = Math.floor(x / CHUNK_SIZE);
const cz = Math.floor(z / CHUNK_SIZE);
const key = getChunkKey(cx, cz);
let chunk = chunks.get(key);
if (!chunk) {
chunk = generateChunk(cx, cz);
}
const lx = ((x % CHUNK_SIZE) + CHUNK_SIZE) % CHUNK_SIZE;
const lz = ((z % CHUNK_SIZE) + CHUNK_SIZE) % CHUNK_SIZE;
chunk.blocks[lx + y * CHUNK_SIZE + lz * CHUNK_SIZE * 256] = type;
chunk.dirty = true;
// Mark adjacent chunks dirty if on border
if (lx === 0) markChunkDirty(cx - 1, cz);
if (lx === CHUNK_SIZE - 1) markChunkDirty(cx + 1, cz);
if (lz === 0) markChunkDirty(cx, cz - 1);
if (lz === CHUNK_SIZE - 1) markChunkDirty(cx, cz + 1);
}
function markChunkDirty(cx, cz) {
const key = getChunkKey(cx, cz);
const chunk = chunks.get(key);
if (chunk) chunk.dirty = true;
}
function buildChunkMesh(chunk, cx, cz) {
if (chunk.mesh) {
scene.remove(chunk.mesh);
chunk.mesh.geometry.dispose();
}
const geometry = new THREE.BufferGeometry();
const vertices = [];
const normals = [];
const uvs = [];
const indices = [];
const colors = [];
for (let x = 0; x < CHUNK_SIZE; x++) {
for (let y = 0; y < 256; y++) {
for (let z = 0; z < CHUNK_SIZE; z++) {
const block = chunk.blocks[x + y * CHUNK_SIZE + z * CHUNK_SIZE * 256];
if (block === BLOCKS.AIR) continue;
const worldX = cx * CHUNK_SIZE + x;
const worldZ = cz * CHUNK_SIZE + z;
const blockColors = BLOCK_COLORS[block];
// Check each face
const faces = [
{ dir: [0, 1, 0], color: blockColors.top, normal: [0, 1, 0] },
{ dir: [0, -1, 0], color: blockColors.bottom, normal: [0, -1, 0] },
{ dir: [1, 0, 0], color: blockColors.side, normal: [1, 0, 0] },
{ dir: [-1, 0, 0], color: blockColors.side, normal: [-1, 0, 0] },
{ dir: [0, 0, 1], color: blockColors.side, normal: [0, 0, 1] },
{ dir: [0, 0, -1], color: blockColors.side, normal: [0, 0, -1] }
];
for (const face of faces) {
const nx = worldX + face.dir[0];
const ny = y + face.dir[1];
const nz = worldZ + face.dir[2];
const neighbor = getBlock(nx, ny, nz);
if (neighbor === BLOCKS.AIR || (neighbor === BLOCKS.WATER && block !== BLOCKS.WATER)) {
const vertexIndex = vertices.length / 3;
// Add face vertices
if (face.dir[1] === 1) { // Top
vertices.push(worldX, y + 1, worldZ);
vertices.push(worldX + 1, y + 1, worldZ);
vertices.push(worldX + 1, y + 1, worldZ + 1);
vertices.push(worldX, y + 1, worldZ + 1);
} else if (face.dir[1] === -1) { // Bottom
vertices.push(worldX, y, worldZ + 1);
vertices.push(worldX + 1, y, worldZ + 1);
vertices.push(worldX + 1, y, worldZ);
vertices.push(worldX, y, worldZ);
} else if (face.dir[0] === 1) { // Right
vertices.push(worldX + 1, y, worldZ);
vertices.push(worldX + 1, y + 1, worldZ);
vertices.push(worldX + 1, y + 1, worldZ + 1);
vertices.push(worldX + 1, y, worldZ + 1);
} else if (face.dir[0] === -1) { // Left
vertices.push(worldX, y, worldZ + 1);
vertices.push(worldX, y + 1, worldZ + 1);
vertices.push(worldX, y + 1, worldZ);
vertices.push(worldX, y, worldZ);
} else if (face.dir[2] === 1) { // Front
vertices.push(worldX + 1, y, worldZ + 1);
vertices.push(worldX + 1, y + 1, worldZ + 1);
vertices.push(worldX, y + 1, worldZ + 1);
vertices.push(worldX, y, worldZ + 1);
} else { // Back
vertices.push(worldX, y, worldZ);
vertices.push(worldX, y + 1, worldZ);
vertices.push(worldX + 1, y + 1, worldZ);
vertices.push(worldX + 1, y, worldZ);
}
// Add normals
for (let i = 0; i < 4; i++) {
normals.push(...face.normal);
}
// Add colors
const r = ((face.color >> 16) & 0xFF) / 255;
const g = ((face.color >> 8) & 0xFF) / 255;
const b = (face.color & 0xFF) / 255;
for (let i = 0; i < 4; i++) {
colors.push(r, g, b);
}
// Add UVs
uvs.push(0, 0, 1, 0, 1, 1, 0, 1);
// Add indices
indices.push(
vertexIndex, vertexIndex + 1, vertexIndex + 2,
vertexIndex, vertexIndex + 2, vertexIndex + 3
);
}
}
}
}
}
if (vertices.length === 0) return;
geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
geometry.setAttribute('normal', new THREE.Float32BufferAttribute(normals, 3));
geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2));
geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
geometry.setIndex(indices);
const material = new THREE.MeshLambertMaterial({ vertexColors: true });
chunk.mesh = new THREE.Mesh(geometry, material);
scene.add(chunk.mesh);
chunk.dirty = false;
}
// ============================================================
// MYTHIC STRUCTURES
// ============================================================
function buildCrystalTower(cx, cz) {
const baseX = cx * CHUNK_SIZE + 7;
const baseZ = cz * CHUNK_SIZE + 7;
const baseY = getHeight(baseX, baseZ);
// Crystal tower
for (let y = 0; y < 20; y++) {
const radius = Math.max(1, 4 - Math.floor(y / 5));
for (let dx = -radius; dx <= radius; dx++) {
for (let dz = -radius; dz <= radius; dz++) {
if (dx * dx + dz * dz <= radius * radius) {
setBlock(baseX + dx, baseY + y, baseZ + dz, BLOCKS.CRYSTAL);
}
}
}
}
// Top beacon
for (let y = 20; y < 25; y++) {
setBlock(baseX, baseY + y, baseZ, BLOCKS.GOLD);
}
}
function buildStoneCircle(cx, cz) {
const baseX = cx * CHUNK_SIZE + 8;
const baseZ = cz * CHUNK_SIZE + 8;
const baseY = getHeight(baseX, baseZ);
// Stone circle
for (let i = 0; i < 8; i++) {
const angle = (i / 8) * Math.PI * 2;
const x = Math.round(baseX + Math.cos(angle) * 6);
const z = Math.round(baseZ + Math.sin(angle) * 6);
// Standing stones
for (let y = 0; y < 5; y++) {
setBlock(x, baseY + y, z, BLOCKS.COBBLESTONE);
}
// Cap stone
setBlock(x, baseY + 5, z, BLOCKS.COBBLESTONE);
}
// Central altar
for (let dx = -1; dx <= 1; dx++) {
for (let dz = -1; dz <= 1; dz++) {
setBlock(baseX + dx, baseY, baseZ + dz, BLOCKS.OBSIDIAN);
setBlock(baseX + dx, baseY + 1, baseZ + dz, BLOCKS.GOLD);
}
}
}
function buildDragonSkeleton(cx, cz) {
const baseX = cx * CHUNK_SIZE + 4;
const baseZ = cz * CHUNK_SIZE + 4;
const baseY = getHeight(baseX, baseZ);
// Spine
for (let i = 0; i < 12; i++) {
const x = baseX + i;
const y = baseY + Math.floor(Math.sin(i * 0.3) * 2) + 2;
setBlock(x, y, baseZ, BLOCKS.SNOW);
setBlock(x, y + 1, baseZ, BLOCKS.SNOW);
}
// Ribs
for (let i = 2; i < 10; i += 2) {
const x = baseX + i;
const y = baseY + Math.floor(Math.sin(i * 0.3) * 2) + 2;
for (let dz = -3; dz <= 3; dz++) {
if (Math.abs(dz) === 3) {
setBlock(x, y - 1, baseZ + dz, BLOCKS.SNOW);
setBlock(x, y - 2, baseZ + dz, BLOCKS.SNOW);
}
}
}
// Skull
for (let dx = 0; dx < 3; dx++) {
for (let dy = 0; dy < 3; dy++) {
for (let dz = -1; dz <= 1; dz++) {
setBlock(baseX - 3 + dx, baseY + 3 + dy, baseZ + dz, BLOCKS.SNOW);
}
}
}
// Eye sockets
setBlock(baseX - 2, baseY + 4, baseZ - 1, BLOCKS.OBSIDIAN);
setBlock(baseX - 2, baseY + 4, baseZ + 1, BLOCKS.OBSIDIAN);
// Wings
for (let i = 0; i < 6; i++) {
const x = baseX + 4 + i;
const y = baseY + 5 + i;
setBlock(x, y, baseZ - 4, BLOCKS.SNOW);
setBlock(x, y, baseZ + 4, BLOCKS.SNOW);
}
}
function buildSkyRuins(cx, cz) {
const baseX = cx * CHUNK_SIZE + 6;
const baseZ = cz * CHUNK_SIZE + 6;
const baseY = getHeight(baseX, baseZ) + 15;
// Floating platform
for (let dx = -5; dx <= 5; dx++) {
for (let dz = -5; dz <= 5; dz++) {
if (Math.abs(dx) === 5 || Math.abs(dz) === 5) {
setBlock(baseX + dx, baseY, baseZ + dz, BLOCKS.COBBLESTONE);
setBlock(baseX + dx, baseY + 1, baseZ + dz, BLOCKS.COBBLESTONE);
}
}
}
// Corner pillars
const corners = [[-4, -4], [-4, 4], [4, -4], [4, 4]];
for (const [dx, dz] of corners) {
for (let y = -5; y <= 3; y++) {
setBlock(baseX + dx, baseY + y, baseZ + dz, BLOCKS.COBBLESTONE);
}
}
// Central structure
for (let dx = -2; dx <= 2; dx++) {
for (let dz = -2; dz <= 2; dz++) {
for (let y = 2; y <= 6; y++) {
if (Math.abs(dx) === 2 || Math.abs(dz) === 2) {
setBlock(baseX + dx, baseY + y, baseZ + dz, BLOCKS.COBBLESTONE);
}
}
}
}
// Crystal on top
setBlock(baseX, baseY + 7, baseZ, BLOCKS.CRYSTAL);
setBlock(baseX, baseY + 8, baseZ, BLOCKS.GOLD);
}
// ============================================================
// LIGHTING
// ============================================================
const ambientLight = new THREE.AmbientLight(0x606060);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(50, 100, 50);
directionalLight.castShadow = true;
scene.add(directionalLight);
const hemisphereLight = new THREE.HemisphereLight(0x87CEEB, 0x8B6914, 0.3);
scene.add(hemisphereLight);
// ============================================================
// CONTROLS
// ============================================================
document.addEventListener('click', () => {
if (!isLocked) {
renderer.domElement.requestPointerLock();
}
});
document.addEventListener('pointerlockchange', () => {
isLocked = document.pointerLockElement === renderer.domElement;
document.getElementById('blocker').style.display = isLocked ? 'none' : 'flex';
});
document.addEventListener('mousemove', (e) => {
if (!isLocked) return;
player.rotation.y -= e.movementX * 0.002;
player.rotation.x -= e.movementY * 0.002;
player.rotation.x = Math.max(-Math.PI / 2, Math.min(Math.PI / 2, player.rotation.x));
});
document.addEventListener('keydown', (e) => {
keys[e.code] = true;
// Number keys for inventory
if (e.code >= 'Digit1' && e.code <= 'Digit9') {
player.selectedSlot = parseInt(e.code.slice(5)) - 1;
updateHUD();
}
// G key for teleport
if (e.code === 'KeyG') {
currentLandmark = (currentLandmark + 1) % landmarks.length;
const lm = landmarks[currentLandmark];
player.position.set(lm.x, lm.teleportY, lm.z);
player.velocity.set(0, 0, 0);
showLandmarkIndicator(lm.name);
}
});
document.addEventListener('keyup', (e) => keys[e.code] = false);
// Mouse click for breaking/placing
document.addEventListener('mousedown', (e) => {
if (!isLocked) return;
const direction = new THREE.Vector3(0, 0, -1);
direction.applyQuaternion(camera.quaternion);
const raycaster = new THREE.Raycaster(camera.position, direction, 0, 8);
// Simple raycast
let hitPos = null;
let hitNormal = null;
for (let t = 0; t < 8; t += 0.1) {
const x = Math.floor(camera.position.x + direction.x * t);
const y = Math.floor(camera.position.y + direction.y * t);
const z = Math.floor(camera.position.z + direction.z * t);
if (getBlock(x, y, z) !== BLOCKS.AIR) {
hitPos = { x, y, z };
hitNormal = {
x: Math.floor(camera.position.x + direction.x * (t - 0.1)) - x,
y: Math.floor(camera.position.y + direction.y * (t - 0.1)) - y,
z: Math.floor(camera.position.z + direction.z * (t - 0.1)) - z
};
break;
}
}
if (hitPos) {
if (e.button === 0) { // Left click - break
setBlock(hitPos.x, hitPos.y, hitPos.z, BLOCKS.AIR);
} else if (e.button === 2) { // Right click - place
const px = hitPos.x + hitNormal.x;
const py = hitPos.y + hitNormal.y;
const pz = hitPos.z + hitNormal.z;
// Don't place inside player
const playerBlockX = Math.floor(player.position.x);
const playerBlockY = Math.floor(player.position.y);
const playerBlockZ = Math.floor(player.position.z);
if (!(px === playerBlockX && (py === playerBlockY || py === playerBlockY - 1) && pz === playerBlockZ)) {
setBlock(px, py, pz, inventory[player.selectedSlot]);
}
}
}
});
// Prevent context menu
document.addEventListener('contextmenu', (e) => e.preventDefault());
// ============================================================
// HUD
// ============================================================
function createHUD() {
const hud = document.getElementById('hud');
hud.innerHTML = '';
for (let i = 0; i < 9; i++) {
const slot = document.createElement('div');
slot.className = `slot ${i === player.selectedSlot ? 'active' : ''}`;
const num = document.createElement('span');
num.className = 'slot-num';
num.textContent = i + 1;
slot.appendChild(num);
const preview = document.createElement('div');
preview.className = 'block-preview';
const color = BLOCK_COLORS[inventory[i]];
preview.style.background = `#${color.top.toString(16).padStart(6, '0')}`;
slot.appendChild(preview);
slot.addEventListener('click', () => {
player.selectedSlot = i;
updateHUD();
});
hud.appendChild(slot);
}
}
function updateHUD() {
const slots = document.querySelectorAll('.slot');
slots.forEach((slot, i) => {
slot.className = `slot ${i === player.selectedSlot ? 'active' : ''}`;
});
document.getElementById('block-name').textContent = BLOCK_NAMES[inventory[player.selectedSlot]];
}
// ============================================================
// MINIMAP
// ============================================================
function updateMinimap() {
const canvas = document.getElementById('minimap');
const ctx = canvas.getContext('2d');
canvas.width = 150;
canvas.height = 150;
const scale = 2;
const centerX = player.position.x;
const centerZ = player.position.z;
// Draw terrain
for (let px = 0; px < 150; px++) {
for (let pz = 0; pz < 150; pz++) {
const worldX = centerX + (px - 75) * scale;
const worldZ = centerZ + (pz - 75) * scale;
const height = getHeight(worldX, worldZ);
const biome = getBiome(worldX, worldZ);
let color;
if (height <= 20) color = '#3498DB';
else if (biome === 'desert') color = '#F4D03F';
else if (biome === 'snow') color = '#ECF0F1';
else if (biome === 'forest') color = '#27AE60';
else color = '#2ECC71';
ctx.fillStyle = color;
ctx.fillRect(px, pz, 1, 1);
}
}
// Draw landmarks
ctx.fillStyle = '#FFD700';
for (const lm of landmarks) {
const px = 75 + (lm.x - centerX) / scale;
const pz = 75 + (lm.z - centerZ) / scale;
if (px >= 0 && px < 150 && pz >= 0 && pz < 150) {
ctx.beginPath();
ctx.arc(px, pz, 3, 0, Math.PI * 2);
ctx.fill();
}
}
// Draw player
ctx.fillStyle = '#FF0000';
ctx.beginPath();
ctx.arc(75, 75, 4, 0, Math.PI * 2);
ctx.fill();
// Draw direction
ctx.strokeStyle = '#FF0000';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(75, 75);
ctx.lineTo(75 + Math.sin(player.rotation.y) * 10, 75 - Math.cos(player.rotation.y) * 10);
ctx.stroke();
}
// ============================================================
// LANDMARK INDICATOR
// ============================================================
let landmarkTimeout = null;
function showLandmarkIndicator(name) {
const indicator = document.getElementById('landmark-indicator');
indicator.textContent = `📍 ${name}`;
indicator.style.display = 'block';
if (landmarkTimeout) clearTimeout(landmarkTimeout);
landmarkTimeout = setTimeout(() => {
indicator.style.display = 'none';
}, 2000);
}
// ============================================================
// PHYSICS
// ============================================================
function updatePhysics(dt) {
// Movement
const moveSpeed = player.speed * dt;
const forward = new THREE.Vector3(
-Math.sin(player.rotation.y) * moveSpeed,
0,
-Math.cos(player.rotation.y) * moveSpeed
);
const right = new THREE.Vector3(
Math.cos(player.rotation.y) * moveSpeed,
0,
-Math.sin(player.rotation.y) * moveSpeed
);
if (keys['KeyW']) player.position.add(forward);
if (keys['KeyS']) player.position.sub(forward);
if (keys['KeyA']) player.position.sub(right);
if (keys['KeyD']) player.position.add(right);
// Gravity
player.velocity.y += player.gravity * dt;
player.position.y += player.velocity.y * dt;
// Collision detection
const playerHeight = 1.8;
const playerWidth = 0.3;
// Check ground
const feetY = player.position.y - playerHeight;
const blockBelow = getBlock(
Math.floor(player.position.x),
Math.floor(feetY),
Math.floor(player.position.z)
);
if (blockBelow !== BLOCKS.AIR && blockBelow !== BLOCKS.WATER) {
player.position.y = Math.floor(feetY) + 1 + playerHeight;
player.velocity.y = 0;
player.onGround = true;
} else {
player.onGround = false;
}
// Jump
if (keys['Space'] && player.onGround) {
player.velocity.y = player.jumpForce;
player.onGround = false;
}
// Water physics
if (getBlock(Math.floor(player.position.x), Math.floor(player.position.y - 1), Math.floor(player.position.z)) === BLOCKS.WATER) {
player.velocity.y *= 0.9;
player.velocity.y += 2 * dt;
}
// Prevent falling through world
if (player.position.y < -10) {
player.position.set(0, 40, 0);
player.velocity.set(0, 0, 0);
}
}
// ============================================================
// GAME LOOP
// ============================================================
function updateChunks() {
const playerCX = Math.floor(player.position.x / CHUNK_SIZE);
const playerCZ = Math.floor(player.position.z / CHUNK_SIZE);
// Generate/load chunks
for (let dx = -RENDER_DISTANCE; dx <= RENDER_DISTANCE; dx++) {
for (let dz = -RENDER_DISTANCE; dz <= RENDER_DISTANCE; dz++) {
const cx = playerCX + dx;
const cz = playerCZ + dz;
const chunk = generateChunk(cx, cz);
// Build landmarks on first generation
if (chunk.dirty) {
// Check if this chunk should have a landmark
for (const lm of landmarks) {
const lmCX = Math.floor(lm.x / CHUNK_SIZE);
const lmCZ = Math.floor(lm.z / CHUNK_SIZE);
if (lmCX === cx && lmCZ === cz) {
if (lm.name === 'Crystal Tower') buildCrystalTower(cx, cz);
else if (lm.name === 'Stone Circle') buildStoneCircle(cx, cz);
else if (lm.name === 'Dragon Skeleton') buildDragonSkeleton(cx, cz);
else if (lm.name === 'Sky Ruins') buildSkyRuins(cx, cz);
}
}
buildChunkMesh(chunk, cx, cz);
}
}
}
// Unload distant chunks
for (const [key, chunk] of chunks) {
const [cx, cz] = key.split(',').map(Number);
if (Math.abs(cx - playerCX) > RENDER_DISTANCE + 2 || Math.abs(cz - playerCZ) > RENDER_DISTANCE + 2) {
if (chunk.mesh) {
scene.remove(chunk.mesh);
chunk.mesh.geometry.dispose();
}
chunks.delete(key);
}
}
}
function animate() {
requestAnimationFrame(animate);
const now = performance.now();
const dt = Math.min((now - lastTime) / 1000, 0.1);
lastTime = now;
// FPS counter
frameCount++;
if (frameCount % 30 === 0) {
fps = Math.round(1 / dt);
document.getElementById('fps').textContent = fps;
}
if (isLocked) {
updatePhysics(dt);
updateChunks();
// Update camera
camera.position.copy(player.position);
camera.rotation.order = 'YXZ';
camera.rotation.y = player.rotation.y;
camera.rotation.x = player.rotation.x;
// Update info
document.getElementById('pos').textContent =
`${Math.floor(player.position.x)}, ${Math.floor(player.position.y)}, ${Math.floor(player.position.z)}`;
// Update minimap every 10 frames
if (frameCount % 10 === 0) {
updateMinimap();
}
}
renderer.render(scene, camera);
}
// ============================================================
// INITIALIZATION
// ============================================================
function init() {
createHUD();
updateHUD();
// Find safe spawn point
const spawnX = 0, spawnZ = 0;
const spawnY = getHeight(spawnX, spawnZ) + 2;
player.position.set(spawnX, spawnY, spawnZ);
// Generate initial chunks
updateChunks();
// Start game loop
animate();
console.log('Mythos Craft initialized');
console.log('Landmarks:', landmarks.map(l => l.name).join(', '));
}
// Handle window resize
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
// Start
init();
</script>
</body>
</html>