#!/usr/bin/env python3

import pyglet
from pyglet.gl import *
import math
import random
import time
import os
import ctypes
import json
import importlib.util
import sys
from abc import ABC, abstractmethod
from collections import defaultdict, deque
from typing import Dict, List, Any, Optional, Callable, Union
import traceback
# ============================================================================
# PERFORMANT TERRAIN GENERATION v2.0
# Features:
# - Flat world base with common horizon
# - Carved features: rivers, canyons, scoops, lakes
# - Built features: mountains, hills, plateaus with tessellation
# - Optimized noise generation with caching
# - Fast biome determination with caching
# - Chunk-level height generation
# - Reduced computational overhead
# ============================================================================


try:
    import numpy as np
    NUMPY_AVAILABLE = True
except ImportError:
    NUMPY_AVAILABLE = False
    class np:
        @staticmethod
        def array(x): return x
        @staticmethod
        def random():
            class _r:
                @staticmethod
                def randn(*s): return [[random.gauss(0,1) for _ in range(s[1])] for _ in range(s[0])] if len(s)==2 else [random.gauss(0,1) for _ in range(s[0])]
            return _r()
        @staticmethod
        def zeros(s): return [[0.0 for _ in range(s[1])] for _ in range(s[0])] if isinstance(s,tuple) and len(s)==2 else [0.0 for _ in range(s[0] if isinstance(s,tuple) else s)]
        @staticmethod
        def dot(a,b): return sum(a[i]*b[i] for i in range(len(a)))
        @staticmethod
        def exp(x): return math.exp(x)
        @staticmethod
        def clip(x,mn,mx): return max(mn,min(mx,x))

try:
    from OpenGL.GL import *
    from OpenGL.GLU import *
    OPENGL_AVAILABLE = True
except ImportError:
    OPENGL_AVAILABLE = False

# Supporting classes for PerformantTerrainGenerator






# ============================================================================
# SIMPLE SMOOTH TERRAIN SYSTEM
# Features: Smooth bell-shaped hills and mountains, efficient performance
# ============================================================================


class MinecraftBiomeManager:
    """Minecraft-style biome manager with proper biome distribution"""
    
    def __init__(self):
        # Minecraft-like biomes with proper characteristics
        self.biomes = {
            'ocean': {
                'color_mod': (0.2, 0.4, 0.8),
                'surface_block': 'sand',
                'subsurface_block': 'sand',
                'min_height': 45,
                'max_height': 62,
                'tree_chance': 0.0,
                'flower_chance': 0.0,
                'grass_chance': 0.0
            },
            'deep_ocean': {
                'color_mod': (0.1, 0.2, 0.6),
                'surface_block': 'clay',
                'subsurface_block': 'clay',
                'min_height': 20,
                'max_height': 45,
                'tree_chance': 0.0,
                'flower_chance': 0.0,
                'grass_chance': 0.0
            },
            'beach': {
                'color_mod': (1.0, 0.9, 0.7),
                'surface_block': 'sand',
                'subsurface_block': 'sand',
                'min_height': 62,
                'max_height': 67,
                'tree_chance': 0.001,
                'flower_chance': 0.01,
                'grass_chance': 0.05
            },
            'plains': {
                'color_mod': (0.6, 0.8, 0.4),
                'surface_block': 'grass',
                'subsurface_block': 'dirt',
                'min_height': 63,
                'max_height': 75,
                'tree_chance': 0.002,
                'flower_chance': 0.08,
                'grass_chance': 0.3
            },
            'forest': {
                'color_mod': (0.3, 0.7, 0.3),
                'surface_block': 'grass',
                'subsurface_block': 'dirt',
                'min_height': 63,
                'max_height': 80,
                'tree_chance': 0.15,
                'flower_chance': 0.05,
                'grass_chance': 0.4
            },
            'desert': {
                'color_mod': (1.0, 0.9, 0.6),
                'surface_block': 'sand',
                'subsurface_block': 'sand',
                'min_height': 63,
                'max_height': 78,
                'tree_chance': 0.0,
                'flower_chance': 0.0,
                'grass_chance': 0.0
            },
            'taiga': {
                'color_mod': (0.4, 0.6, 0.4),
                'surface_block': 'grass',
                'subsurface_block': 'dirt',
                'min_height': 63,
                'max_height': 85,
                'tree_chance': 0.12,
                'flower_chance': 0.02,
                'grass_chance': 0.2
            },
            'swamp': {
                'color_mod': (0.4, 0.5, 0.3),
                'surface_block': 'grass',
                'subsurface_block': 'clay',
                'min_height': 61,
                'max_height': 66,
                'tree_chance': 0.08,
                'flower_chance': 0.03,
                'grass_chance': 0.6
            },
            'hills': {
                'color_mod': (0.5, 0.6, 0.4),
                'surface_block': 'grass',
                'subsurface_block': 'dirt',
                'min_height': 70,
                'max_height': 95,
                'tree_chance': 0.1,
                'flower_chance': 0.04,
                'grass_chance': 0.25
            },
            'mountains': {
                'color_mod': (0.7, 0.7, 0.8),
                'surface_block': 'stone',
                'subsurface_block': 'stone',
                'min_height': 85,
                'max_height': 120,
                'tree_chance': 0.03,
                'flower_chance': 0.01,
                'grass_chance': 0.05
            },
            'snowy_mountains': {
                'color_mod': (0.9, 0.9, 1.0),
                'surface_block': 'snow',
                'subsurface_block': 'stone',
                'min_height': 100,
                'max_height': 128,
                'tree_chance': 0.01,
                'flower_chance': 0.0,
                'grass_chance': 0.0
            }
        }
        
        # Cache for biome calculations
        self.biome_cache = {}
        self.temperature_cache = {}
        self.humidity_cache = {}
    
    def get_temperature(self, x, z, noise_gen):
        """Get temperature based on latitude and elevation-like Minecraft"""
        cache_key = (int(x // 4), int(z // 4))
        if cache_key in self.temperature_cache:
            return self.temperature_cache[cache_key]
        
        # Base temperature on Z coordinate (latitude)
        latitude_temp = 0.8 - abs(z * 0.001) % 1.0
        
        # Add noise variation
        temp_noise = noise_gen.simplex_like_noise(x, z, 0.002)
        temperature = latitude_temp + temp_noise * 0.3
        
        # Cache result
        if len(self.temperature_cache) < 1000:
            self.temperature_cache[cache_key] = temperature
        
        return max(0.0, min(1.0, temperature))
    
    def get_humidity(self, x, z, noise_gen):
        """Get humidity based on noise like Minecraft"""
        cache_key = (int(x // 4), int(z // 4))
        if cache_key in self.humidity_cache:
            return self.humidity_cache[cache_key]
        
        # Humidity based on different noise pattern
        humidity = noise_gen.simplex_like_noise(x, z, 0.0015) * 0.5 + 0.5
        
        # Cache result
        if len(self.humidity_cache) < 1000:
            self.humidity_cache[cache_key] = humidity
        
        return max(0.0, min(1.0, humidity))
    
    def determine_biome(self, x, z, height, noise_gen):
        """Determine biome based on temperature, humidity, and height like Minecraft"""
        
        # Ocean biomes based on height
        if height < 50:
            return 'deep_ocean'
        elif height < 62:
            return 'ocean'
        elif height < 67:
            return 'beach'
        
        # Land biomes based on temperature and humidity
        temperature = self.get_temperature(x, z, noise_gen)
        humidity = self.get_humidity(x, z, noise_gen)
        
        # Altitude-based biomes (like Minecraft's altitude zones)
        if height > 110:
            return 'snowy_mountains'
        elif height > 90:
            return 'mountains'
        elif height > 80:
            return 'hills'
        
        # Temperature/humidity based biomes
        if temperature < 0.2:
            return 'taiga'
        elif temperature > 0.8:
            if humidity < 0.3:
                return 'desert'
            else:
                return 'swamp' if humidity > 0.8 else 'plains'
        else:
            if humidity > 0.6:
                return 'forest'
            elif humidity < 0.3:
                return 'plains'
            else:
                return 'forest' if temperature > 0.5 else 'taiga'
    
    def get_biome_data(self, x, z, height, noise_gen):
        """Get complete biome data for a position"""
        cache_key = (int(x // 8), int(z // 8))
        if cache_key in self.biome_cache:
            return self.biome_cache[cache_key]
        
        biome_name = self.determine_biome(x, z, height, noise_gen)
        biome_data = self.biomes[biome_name].copy()
        biome_data['name'] = biome_name
        
        # Cache result
        if len(self.biome_cache) < 500:
            self.biome_cache[cache_key] = biome_data
        
        return biome_data
class MinecraftNoiseGenerator:
    """Minecraft-style noise generator with proper scaling"""
    
    def __init__(self, seed=None):
        if seed is not None:
            random.seed(seed)
        
        # Pre-computed permutation table
        self.perm = list(range(256))
        random.shuffle(self.perm)
        self.perm *= 2
        
        # Efficient caching
        self.height_cache = {}
        self.biome_cache = {}
        self.max_cache_size = 2048
    
    def fade(self, t):
        """Smooth interpolation function"""
        return t * t * t * (t * (t * 6 - 15) + 10)
    
    def lerp(self, a, b, t):
        """Linear interpolation"""
        return a + t * (b - a)
    
    def grad(self, hash_val, x, y, z=0):
        """3D gradient function for better terrain"""
        h = hash_val & 15
        u = x if h < 8 else y
        v = y if h < 4 else (x if h == 12 or h == 14 else z)
        return (u if (h & 1) == 0 else -u) + (v if (h & 2) == 0 else -v)
    
    def noise(self, x, y, z=0):
        """3D Perlin noise like Minecraft"""
        X = int(x) & 255
        Y = int(y) & 255
        Z = int(z) & 255
        
        x -= int(x)
        y -= int(y)
        z -= int(z)
        
        u = self.fade(x)
        v = self.fade(y)
        w = self.fade(z)
        
        A = self.perm[X] + Y
        AA = self.perm[A] + Z
        AB = self.perm[A + 1] + Z
        B = self.perm[X + 1] + Y
        BA = self.perm[B] + Z
        BB = self.perm[B + 1] + Z
        
        return self.lerp(
            self.lerp(
                self.lerp(self.grad(self.perm[AA], x, y, z),
                         self.grad(self.perm[BA], x - 1, y, z), u),
                self.lerp(self.grad(self.perm[AB], x, y - 1, z),
                         self.grad(self.perm[BB], x - 1, y - 1, z), u), v),
            self.lerp(
                self.lerp(self.grad(self.perm[AA + 1], x, y, z - 1),
                         self.grad(self.perm[BA + 1], x - 1, y, z - 1), u),
                self.lerp(self.grad(self.perm[AB + 1], x, y - 1, z - 1),
                         self.grad(self.perm[BB + 1], x - 1, y - 1, z - 1), u), v), w)
    
    def octave_noise(self, x, y, z=0, octaves=4, persistence=0.5, scale=1.0, lacunarity=2.0):
        """Multi-octave noise for natural terrain"""
        value = 0
        amplitude = 1
        frequency = scale
        max_value = 0
        
        for i in range(octaves):
            value += self.noise(x * frequency, y * frequency, z * frequency) * amplitude
            max_value += amplitude
            amplitude *= persistence
            frequency *= lacunarity
        
        return value / max_value if max_value > 0 else 0
    
    def ridged_noise(self, x, y, z=0, octaves=4, scale=1.0):
        """Ridged noise for mountain ridges"""
        value = 0
        amplitude = 1
        frequency = scale
        
        for i in range(octaves):
            n = abs(self.noise(x * frequency, y * frequency, z * frequency))
            n = 1 - n  # Invert for ridges
            n = n * n  # Square for sharper ridges
            value += n * amplitude
            amplitude *= 0.5
            frequency *= 2
        
        return value
    
    def simplex_like_noise(self, x, y, scale=1.0):
        """Simplified simplex-like noise for biome variation"""
        return (self.noise(x * scale, y * scale) + 
                self.noise(x * scale * 2, y * scale * 2) * 0.5 +
                self.noise(x * scale * 4, y * scale * 4) * 0.25) / 1.75
class MinecraftBiomeManager:
    """Minecraft-style biome manager with proper biome distribution"""
    
    def __init__(self):
        # Minecraft-like biomes with proper characteristics
        self.biomes = {
            'ocean': {
                'color_mod': (0.2, 0.4, 0.8),
                'surface_block': 'sand',
                'subsurface_block': 'sand',
                'min_height': 45,
                'max_height': 62,
                'tree_chance': 0.0,
                'flower_chance': 0.0,
                'grass_chance': 0.0
            },
            'deep_ocean': {
                'color_mod': (0.1, 0.2, 0.6),
                'surface_block': 'clay',
                'subsurface_block': 'clay',
                'min_height': 20,
                'max_height': 45,
                'tree_chance': 0.0,
                'flower_chance': 0.0,
                'grass_chance': 0.0
            },
            'beach': {
                'color_mod': (1.0, 0.9, 0.7),
                'surface_block': 'sand',
                'subsurface_block': 'sand',
                'min_height': 62,
                'max_height': 67,
                'tree_chance': 0.001,
                'flower_chance': 0.01,
                'grass_chance': 0.05
            },
            'plains': {
                'color_mod': (0.6, 0.8, 0.4),
                'surface_block': 'grass',
                'subsurface_block': 'dirt',
                'min_height': 63,
                'max_height': 75,
                'tree_chance': 0.002,
                'flower_chance': 0.08,
                'grass_chance': 0.3
            },
            'forest': {
                'color_mod': (0.3, 0.7, 0.3),
                'surface_block': 'grass',
                'subsurface_block': 'dirt',
                'min_height': 63,
                'max_height': 80,
                'tree_chance': 0.15,
                'flower_chance': 0.05,
                'grass_chance': 0.4
            },
            'desert': {
                'color_mod': (1.0, 0.9, 0.6),
                'surface_block': 'sand',
                'subsurface_block': 'sand',
                'min_height': 63,
                'max_height': 78,
                'tree_chance': 0.0,
                'flower_chance': 0.0,
                'grass_chance': 0.0
            },
            'taiga': {
                'color_mod': (0.4, 0.6, 0.4),
                'surface_block': 'grass',
                'subsurface_block': 'dirt',
                'min_height': 63,
                'max_height': 85,
                'tree_chance': 0.12,
                'flower_chance': 0.02,
                'grass_chance': 0.2
            },
            'swamp': {
                'color_mod': (0.4, 0.5, 0.3),
                'surface_block': 'grass',
                'subsurface_block': 'clay',
                'min_height': 61,
                'max_height': 66,
                'tree_chance': 0.08,
                'flower_chance': 0.03,
                'grass_chance': 0.6
            },
            'hills': {
                'color_mod': (0.5, 0.6, 0.4),
                'surface_block': 'grass',
                'subsurface_block': 'dirt',
                'min_height': 70,
                'max_height': 95,
                'tree_chance': 0.1,
                'flower_chance': 0.04,
                'grass_chance': 0.25
            },
            'mountains': {
                'color_mod': (0.7, 0.7, 0.8),
                'surface_block': 'stone',
                'subsurface_block': 'stone',
                'min_height': 85,
                'max_height': 120,
                'tree_chance': 0.03,
                'flower_chance': 0.01,
                'grass_chance': 0.05
            },
            'snowy_mountains': {
                'color_mod': (0.9, 0.9, 1.0),
                'surface_block': 'snow',
                'subsurface_block': 'stone',
                'min_height': 100,
                'max_height': 128,
                'tree_chance': 0.01,
                'flower_chance': 0.0,
                'grass_chance': 0.0
            }
        }
        
        # Cache for biome calculations
        self.biome_cache = {}
        self.temperature_cache = {}
        self.humidity_cache = {}
    
    def get_temperature(self, x, z, noise_gen):
        """Get temperature based on latitude and elevation-like Minecraft"""
        cache_key = (int(x // 4), int(z // 4))
        if cache_key in self.temperature_cache:
            return self.temperature_cache[cache_key]
        
        # Base temperature on Z coordinate (latitude)
        latitude_temp = 0.8 - abs(z * 0.001) % 1.0
        
        # Add noise variation
        temp_noise = noise_gen.simplex_like_noise(x, z, 0.002)
        temperature = latitude_temp + temp_noise * 0.3
        
        # Cache result
        if len(self.temperature_cache) < 1000:
            self.temperature_cache[cache_key] = temperature
        
        return max(0.0, min(1.0, temperature))
    
    def get_humidity(self, x, z, noise_gen):
        """Get humidity based on noise like Minecraft"""
        cache_key = (int(x // 4), int(z // 4))
        if cache_key in self.humidity_cache:
            return self.humidity_cache[cache_key]
        
        # Humidity based on different noise pattern
        humidity = noise_gen.simplex_like_noise(x, z, 0.0015) * 0.5 + 0.5
        
        # Cache result
        if len(self.humidity_cache) < 1000:
            self.humidity_cache[cache_key] = humidity
        
        return max(0.0, min(1.0, humidity))
    
    def determine_biome(self, x, z, height, noise_gen):
        """Determine biome based on temperature, humidity, and height like Minecraft"""
        
        # Ocean biomes based on height
        if height < 50:
            return 'deep_ocean'
        elif height < 62:
            return 'ocean'
        elif height < 67:
            return 'beach'
        
        # Land biomes based on temperature and humidity
        temperature = self.get_temperature(x, z, noise_gen)
        humidity = self.get_humidity(x, z, noise_gen)
        
        # Altitude-based biomes (like Minecraft's altitude zones)
        if height > 110:
            return 'snowy_mountains'
        elif height > 90:
            return 'mountains'
        elif height > 80:
            return 'hills'
        
        # Temperature/humidity based biomes
        if temperature < 0.2:
            return 'taiga'
        elif temperature > 0.8:
            if humidity < 0.3:
                return 'desert'
            else:
                return 'swamp' if humidity > 0.8 else 'plains'
        else:
            if humidity > 0.6:
                return 'forest'
            elif humidity < 0.3:
                return 'plains'
            else:
                return 'forest' if temperature > 0.5 else 'taiga'
    
    def get_biome_data(self, x, z, height, noise_gen):
        """Get complete biome data for a position"""
        cache_key = (int(x // 8), int(z // 8))
        if cache_key in self.biome_cache:
            return self.biome_cache[cache_key]
        
        biome_name = self.determine_biome(x, z, height, noise_gen)
        biome_data = self.biomes[biome_name].copy()
        biome_data['name'] = biome_name
        
        # Cache result
        if len(self.biome_cache) < 500:
            self.biome_cache[cache_key] = biome_data
        
        return biome_data
class MinecraftTerrainGenerator:
    """Minecraft-style terrain generator with 128-block height limit"""
    
    def __init__(self, seed=None):
        self.seed = seed or random.randint(0, 1000000)
        self.noise = MinecraftNoiseGenerator(self.seed)
        self.biome_manager = MinecraftBiomeManager()
        
        # Minecraft-like constants
        self.SEA_LEVEL = 62
        self.WORLD_HEIGHT = 128
        self.BEDROCK_LEVEL = 0
        self.CAVE_LEVEL = 50
        
        print(f"🌍 Minecraft-style Terrain Generator initialized (Height: 0-{self.WORLD_HEIGHT})")
    
    def generate_height_map(self, x, z):
        """Generate Minecraft-style height map"""
        
        # Continental scale noise (large landmasses)
        continental = self.noise.octave_noise(x, z, 0, 6, 0.5, 0.0005, 2.0) * 20
        
        # Regional scale noise (hills and valleys)
        regional = self.noise.octave_noise(x, z, 100, 4, 0.6, 0.002, 2.0) * 15
        
        # Local scale noise (small features)
        local = self.noise.octave_noise(x, z, 200, 3, 0.4, 0.008, 2.0) * 8
        
        # Detail noise (surface variation)
        detail = self.noise.octave_noise(x, z, 300, 2, 0.3, 0.02, 2.0) * 3
        
        # Combine all scales
        base_height = continental + regional + local + detail
        
        # Add sea level and clamp to world bounds
        final_height = self.SEA_LEVEL + base_height
        return max(self.BEDROCK_LEVEL, min(self.WORLD_HEIGHT - 1, int(final_height)))
    
    def generate_terrain_height(self, x, z):
        """Main terrain height generation function"""
        return self.generate_height_map(x, z)
    
    def get_block_type(self, x, y, z, surface_height, biome_data):
        """Get block type for a specific position like Minecraft"""
        
        if y > surface_height:
            # Air blocks above surface
            if y <= self.SEA_LEVEL and surface_height < self.SEA_LEVEL:
                return 'water'
            return None
        
        # Bedrock layer at bottom
        if y <= 2:
            return 'stone'
        
        # Surface layer
        if y == surface_height:
            if surface_height <= self.SEA_LEVEL:
                return biome_data.get('surface_block', 'sand')
            else:
                return biome_data.get('surface_block', 'grass')
        
        # Subsurface layers
        depth_from_surface = surface_height - y
        
        if depth_from_surface <= 3:
            # Top subsurface layer
            if surface_height <= self.SEA_LEVEL:
                return biome_data.get('subsurface_block', 'sand')
            else:
                return biome_data.get('subsurface_block', 'dirt')
        elif depth_from_surface <= 8:
            # Mixed layer
            return 'dirt' if surface_height > self.SEA_LEVEL else 'sand'
        else:
            # Deep stone layer
            return 'stone'
    
    def generate_ore_deposits(self, x, y, z):
        """Generate ore deposits like Minecraft"""
        if y < 2:
            return None
            
        # Gold ore (rare, deep)
        if y < 30 and hash((x, y, z, 'gold')) % 10000 < 5:
            return 'gold'
            
        # Iron ore equivalent (mountain stone)
        if y < 60 and hash((x, y, z, 'iron')) % 1000 < 15:
            return 'mountain_stone'
        
        return None
    
    def generate_caves(self, x, y, z):
        """Generate cave systems"""
        if y < 10 or y > self.CAVE_LEVEL:
            return False
            
        # 3D cave noise
        cave_noise = self.noise.noise(x * 0.02, y * 0.02, z * 0.02)
        cave_noise2 = self.noise.noise(x * 0.015, y * 0.015, z * 0.015)
        
        return (cave_noise > 0.6 and cave_noise2 > 0.5)
    
    def get_biome_info(self, x, z, height):
        """Get biome information for position"""
        return self.biome_manager.get_biome_data(x, z, height, self.noise)
class OptimizedChunkMesh:
    """Optimized chunk mesh with proper texture handling"""
    
    def __init__(self):
        self.vbo = self.ibo = None
        self.vc = self.ic = 0
        self.verts = self.inds = []
        self.rebuild, self.fc = True, 0

    def clear(self):
        """Clear mesh data"""
        self.verts = self.inds = []
        self.vc = self.ic = self.fc = 0
        self.rebuild = True

    def add_cube_face(self, x, y, z, fd, u1, v1, u2, v2):
        """Add cube face with proper texture coordinates"""
        bi = len(self.verts) // 5
        
        faces = {
            'top': [
                (x - 0.5, y + 0.5, z + 0.5, u1, v1),
                (x + 0.5, y + 0.5, z + 0.5, u2, v1),
                (x + 0.5, y + 0.5, z - 0.5, u2, v2),
                (x - 0.5, y + 0.5, z - 0.5, u1, v2)
            ],
            'bottom': [
                (x - 0.5, y - 0.5, z - 0.5, u1, v1),
                (x + 0.5, y - 0.5, z - 0.5, u2, v1),
                (x + 0.5, y - 0.5, z + 0.5, u2, v2),
                (x - 0.5, y - 0.5, z + 0.5, u1, v2)
            ],
            'front': [
                (x - 0.5, y - 0.5, z + 0.5, u1, v1),
                (x + 0.5, y - 0.5, z + 0.5, u2, v1),
                (x + 0.5, y + 0.5, z + 0.5, u2, v2),
                (x - 0.5, y + 0.5, z + 0.5, u1, v2)
            ],
            'back': [
                (x + 0.5, y - 0.5, z - 0.5, u1, v1),
                (x - 0.5, y - 0.5, z - 0.5, u2, v1),
                (x - 0.5, y + 0.5, z - 0.5, u2, v2),
                (x + 0.5, y + 0.5, z - 0.5, u1, v2)
            ],
            'right': [
                (x + 0.5, y - 0.5, z + 0.5, u1, v1),
                (x + 0.5, y - 0.5, z - 0.5, u2, v1),
                (x + 0.5, y + 0.5, z - 0.5, u2, v2),
                (x + 0.5, y + 0.5, z + 0.5, u1, v2)
            ],
            'left': [
                (x - 0.5, y - 0.5, z - 0.5, u1, v1),
                (x - 0.5, y - 0.5, z + 0.5, u2, v1),
                (x - 0.5, y + 0.5, z + 0.5, u2, v2),
                (x - 0.5, y + 0.5, z - 0.5, u1, v2)
            ]
        }
        
        if fd in faces:
            for vertex in faces[fd]:
                self.verts.extend(vertex)
            
            self.inds.extend([bi, bi + 1, bi + 2, bi, bi + 2, bi + 3])
            self.fc += 1
            self.rebuild = True

    def build_vbo(self):
        """Build VBO with texture coordinates"""
        if not self.verts or not self.inds:
            return
            
        self.vc, self.ic = len(self.verts) // 5, len(self.inds)
        
        try:
            # Clean up old buffers
            if self.vbo: 
                try:
                    glDeleteBuffers(1, [self.vbo] if isinstance(self.vbo, int) else self.vbo)
                except:
                    pass
            if self.ibo:
                try:
                    glDeleteBuffers(1, [self.ibo] if isinstance(self.ibo, int) else self.ibo)
                except:
                    pass
            
            # Create vertex buffer
            va = (GLfloat * len(self.verts))(*self.verts)
            vr = glGenBuffers(1)
            self.vbo = vr[0] if isinstance(vr, (list, tuple)) else vr
            glBindBuffer(GL_ARRAY_BUFFER, self.vbo)
            glBufferData(GL_ARRAY_BUFFER, len(self.verts) * 4, va, GL_STATIC_DRAW)
            
            # Create index buffer
            ia = (GLuint * len(self.inds))(*self.inds)
            ir = glGenBuffers(1)
            self.ibo = ir[0] if isinstance(ir, (list, tuple)) else ir
            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self.ibo)
            glBufferData(GL_ELEMENT_ARRAY_BUFFER, len(self.inds) * 4, ia, GL_STATIC_DRAW)
            
            glBindBuffer(GL_ARRAY_BUFFER, 0)
            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)
            self.rebuild = False
            
        except Exception as e:
            print(f"VBO build error: {e}")
            self.vbo = self.ibo = None

    def draw(self):
        """Draw mesh with proper texture coordinates"""
        if self.rebuild:
            self.build_vbo()
            
        if not self.vbo or not self.ibo or self.ic == 0:
            if self.verts and self.inds:
                self.draw_immediate()
            return
            
        try:
            glEnable(GL_TEXTURE_2D)
            glColor4f(1.0, 1.0, 1.0, 1.0)
            
            if not glIsBuffer(self.vbo) or not glIsBuffer(self.ibo):
                self.draw_immediate()
                return
            
            glBindBuffer(GL_ARRAY_BUFFER, self.vbo)
            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self.ibo)
            
            glEnableClientState(GL_VERTEX_ARRAY)
            glEnableClientState(GL_TEXTURE_COORD_ARRAY)
            
            stride = 20  # 5 floats * 4 bytes (x, y, z, u, v)
            glVertexPointer(3, GL_FLOAT, stride, None)
            glTexCoordPointer(2, GL_FLOAT, stride, ctypes.c_void_p(12))
            
            glDrawElements(GL_TRIANGLES, self.ic, GL_UNSIGNED_INT, None)
            
            glDisableClientState(GL_VERTEX_ARRAY)
            glDisableClientState(GL_TEXTURE_COORD_ARRAY)
            
            glBindBuffer(GL_ARRAY_BUFFER, 0)
            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)
            
        except Exception as e:
            print(f"Draw error: {e}")
            self.draw_immediate()

    def draw_immediate(self):
        """Fallback immediate mode rendering"""
        if not self.verts or not self.inds or len(self.verts) < 5:
            return
            
        try:
            glBegin(GL_TRIANGLES)
            for i in range(0, len(self.inds), 3):
                if i + 2 < len(self.inds):
                    for j in range(3):
                        vi, vs = self.inds[i + j], self.inds[i + j] * 5
                        if vs + 4 < len(self.verts):
                            x, y, z, u, v = self.verts[vs:vs + 5]
                            glTexCoord2f(u, v)
                            glVertex3f(x, y, z)
            glEnd()
        except Exception as e:
            print(f"Immediate draw error: {e}")
class FlowingLiquid:
    def __init__(self, game):
        self.game = game
        self.liquid_blocks = {}
        self.flow_queue = deque()
        self.flow_timer = 0
        
    def add_liquid(self, x, y, z, liquid_type='water', level=8):
        key = (x, y, z)
        self.liquid_blocks[key] = {'type': liquid_type, 'level': level}
        self.flow_queue.append(key)
        
    def update(self, dt):
        try:
            # Limit update frequency for better performance
            if hasattr(self, '_last_update'):
                if time.time() - self._last_update < 0.05:  # Limit to 20 FPS
                    return
            self._last_update = time.time()
            
            st = time.time()
            
            # Reduce frequency of expensive operations
            frame_count = getattr(self, '_frame_count', 0) + 1
            self._frame_count = frame_count
            
            # Only update day/night cycle every 5 frames
            if frame_count % 5 == 0 and hasattr(self, 'dnc'):
                try:
                    self.dnc.update(dt)
                except Exception:
                    pass
                    
            # Only update sky color every 10 frames
            if frame_count % 10 == 0 and hasattr(self, 'dnc'):
                try:
                    sc = self.dnc.get_sky_color()
                    glClearColor(*sc, 1.0)
                except Exception:
                    pass
                    
            if hasattr(self, 'mc') and self.mc:
                # Only update chunks when player moves significantly
                if hasattr(self, 'lpp') and hasattr(self, 'cam'):
                    dx, dz = abs(self.cam.x - self.lpp[0]), abs(self.cam.z - self.lpp[1])
                    if dx > 16 or dz > 16:  # Increased threshold
                        try:
                            self.cm.update_chunks(self.cam.x, self.cam.z, self.mod_loader)
                            self.lpp = (self.cam.x, self.cam.z)
                        except Exception:
                            pass
                        
                try:
                    self.cam.update(dt, self.keys, self.cm, self.pb)
                    self.pm.update(dt, self.cam.vx, self.cam.vz)
                except Exception:
                    pass
                
                # Reduce mod event frequency
                if frame_count % 3 == 0 and hasattr(self, 'mod_loader'):
                    try:
                        self.mod_loader.bus.emit('player_update', self.cam.x, self.cam.y, self.cam.z, self.cam.ry, self.cam.rx, dt)
                        self.mod_loader.bus.emit('update', dt, self)
                    except Exception:
                        pass
            
            # Update liquids less frequently
            if frame_count % 2 == 0 and hasattr(self, 'flowing_liquid'):
                try:
                    self.flowing_liquid.update(dt)
                except Exception:
                    pass
            
            # Update projectiles every frame (important for gameplay)
            if hasattr(self, 'projectiles'):
                try:
                    for projectile in self.projectiles[:]:
                        if not projectile.update(dt, self):
                            self.projectiles.remove(projectile)
                except Exception:
                    pass
            
            self.ft = time.time() - st
            fps = 1.0 / max(0.001, self.ft)
            
            # Auto-enable emergency mode for very low FPS
            if fps < 15 and not getattr(self, 'ema', False) and frame_count > 60:
                try:
                    self.activate_emergency_mode()
                except Exception:
                    pass
                    
        except Exception as e:
            print(f"⚠️ Update error: {e}")
            pass
    def process_flow(self):
        if not self.flow_queue:
            return
            
        current_pos = self.flow_queue.popleft()
        if current_pos not in self.liquid_blocks:
            return
            
        x, y, z = current_pos
        current_liquid = self.liquid_blocks[current_pos]
        
        if current_liquid['level'] <= 0:
            del self.liquid_blocks[current_pos]
            return
        
        neighbors = [(x, y-1, z), (x+1, y, z), (x-1, y, z), (x, y, z+1), (x, y, z-1)]
        
        for nx, ny, nz in neighbors:
            if self.can_flow_to(nx, ny, nz):
                if (nx, ny, nz) not in self.liquid_blocks:
                    flow_amount = min(2, current_liquid['level'] // 2)
                    if flow_amount > 0:
                        self.add_liquid(nx, ny, nz, current_liquid['type'], flow_amount)
                        current_liquid['level'] -= flow_amount
                        
                        if ny < y:
                            current_liquid['level'] -= 2
                        
                        if current_liquid['level'] <= 0:
                            del self.liquid_blocks[current_pos]
                            break
    
    def can_flow_to(self, x, y, z):
        if self.is_solid_block(x, y, z):
            return False
        return True
    
    def is_solid_block(self, x, y, z):
        # Check player blocks
        if hasattr(self.game, 'pb'):
            for block in self.game.pb:
                if block.x == x and block.y == y and block.z == z:
                    if block.type not in ['water', 'lava']:
                        return True
        
        # Check chunk blocks
        if hasattr(self.game, 'cm') and hasattr(self.game.cm, 'chunks'):
            chunk_x, chunk_z = self.game.cm.get_chunk_coords(x, z)
            if (chunk_x, chunk_z) in self.game.cm.chunks:
                chunk = self.game.cm.chunks[(chunk_x, chunk_z)]
                if (x, y, z) in chunk.blocks:
                    block_type = chunk.blocks[(x, y, z)]
                    if block_type not in ['water', 'lava']:
                        return True
        
        return False
    
    def draw_liquids(self, ta):
        for (x, y, z), liquid in self.liquid_blocks.items():
            height = liquid['level'] / 8.0
            if liquid['type'] == 'lava':
                self.draw_liquid_block(x, y, z, height, ta, 'lava')
            else:
                self.draw_liquid_block(x, y, z, height, ta, 'water')
    
    def draw_liquid_block(self, x, y, z, height, ta, liquid_type):
        try:
            glEnable(GL_BLEND)
            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
            glPushMatrix()
            glTranslatef(x, y, z)
            
            if ta and hasattr(ta, 'get_uv'):
                u1, v1, u2, v2 = ta.get_uv(liquid_type)
            else:
                u1, v1, u2, v2 = 0, 0, 1, 1
            
            if liquid_type == 'lava':
                glColor4f(1.0, 0.3, 0.0, 0.8)
            else:
                glColor4f(0.3, 0.5, 1.0, 0.6)
            
            glBegin(GL_QUADS)
            glTexCoord2f(u1, v1); glVertex3f(-0.5, height-0.5, 0.5)
            glTexCoord2f(u2, v1); glVertex3f(0.5, height-0.5, 0.5)
            glTexCoord2f(u2, v2); glVertex3f(0.5, height-0.5, -0.5)
            glTexCoord2f(u1, v2); glVertex3f(-0.5, height-0.5, -0.5)
            glEnd()
            
            glPopMatrix()
            glDisable(GL_BLEND)
        except Exception as e:
            # Silently handle drawing errors
            pass
class Projectile:
    def __init__(self, x, y, z, dx, dy, dz, projectile_type='arrow'):
        self.x, self.y, self.z = x, y, z
        self.dx, self.dy, self.dz = dx, dy, dz
        self.type = projectile_type
        self.life = 10.0
        self.gravity = -15.0
        
    def update(self, dt, game):
        try:
            ct = time.time()
            self.transition_time += dt
            
            if self.transition_time > random.uniform(120, 300):
                self.change_weather()
                self.transition_time = 0
                
            if self.weather == 'storm':
                self.lightning_timer += dt
                if self.lightning_timer > random.uniform(5, 15):
                    self.strike_lightning(game)
                    self.lightning_timer = 0
                    
            self.update_particles(dt)
            self.update_fire(dt, game)
        except Exception:
            pass  # Handle errors gracefully
    def draw(self):
        try:
            glDisable(GL_TEXTURE_2D)
            if self.type == 'arrow':
                glColor3f(0.6, 0.4, 0.2)
            else:
                glColor3f(1.0, 0.5, 0.0)
                
            glPushMatrix()
            glTranslatef(self.x, self.y, self.z)
            glBegin(GL_LINES)
            glVertex3f(0, 0, 0)
            glVertex3f(-self.dx * 0.5, -self.dy * 0.5, -self.dz * 0.5)
            glEnd()
            glPopMatrix()
        except:
            pass
class EventBus:
    def __init__(self):
        self.h = defaultdict(list)
        self.d = {}
        
    def on(self, e, f):
        self.h[e].append(f)
        
    def emit(self, e, *a, **k):
        r = []
        for f in self.h[e]:
            try:
                res = f(*a, **k)
                if res is not None: r.append(res)
            except Exception as ex:
                print(f"Event error: {ex}")
        return r
        
    def set(self, k, v): self.d[k] = v
    def get(self, k, d=None): return self.d.get(k, d)

class BaseMod(ABC):
    def __init__(self, id, n, v):
        self.id, self.n, self.v, self.en, self.deps = id, n, v, True, []
        self.bus = None
        
    @abstractmethod
    def init(self, bus): self.bus = bus
    def enable(self): pass
    def disable(self): pass
    def info(self): return {'id': self.id, 'name': self.n, 'version': self.v, 'enabled': self.en}

class BlockMod(BaseMod):
    @abstractmethod
    def blocks(self): pass
    @abstractmethod
    def textures(self): pass

class WorldMod(BaseMod):
    @abstractmethod
    def gen(self, cx, cz, ch): pass

class UIMod(BaseMod):
    @abstractmethod
    def render(self, w, h): pass
    @abstractmethod
    def input(self, sym, mod): pass

class GameMod(BaseMod):
    def place(self, bt, x, y, z): return True
    def break_(self, bt, x, y, z): return True

class ModLoader:
    def __init__(self, g):
        self.g, self.bus, self.mods, self.order = g, EventBus(), {}, []
        self.dir = "mods"
        os.makedirs(self.dir, exist_ok=True)
        self.init_core()
        
    def init_core(self):
        core = [
            CoreBlocksMod(),
            CoreWorldMod(), 
            CoreUIMod(),
            CoreGameplayMod(),
            HerobrineMod(),
            TownsMod(),
            CastlesMod(),
            LakesMod(),
            WeatherMod(),
            WildlifeMod(),
            GrandExchangeMod(),
            CraftingMod()
        ]
        for m in core:
            self.load_mod(m)
            
    def load_mod(self, m):
        try:
            if hasattr(m, 'init'):
                m.init(self.bus)
            self.mods[m.id] = m
            self.order.append(m.id)
            if hasattr(m, 'enable'):
                m.enable()
            print(f"✅ Loaded mod: {getattr(m, 'n', m.id)} v{getattr(m, 'v', '1.0')}")
        except Exception as e:
            print(f"❌ Failed to load mod {getattr(m, 'id', 'unknown')}: {e}")
    def load_from_file(self, fp):
        try:
            spec = importlib.util.spec_from_file_location("mod", fp)
            mod = importlib.util.module_from_spec(spec)
            spec.loader.exec_module(mod)
            if hasattr(mod, 'create_mod'):
                m = mod.create_mod()
                self.load_mod(m)
        except Exception as e:
            print(f"❌ Failed to load mod file {fp}: {e}")
            
    def scan_mods(self):
        if not os.path.exists(self.dir): return
        for f in os.listdir(self.dir):
            if f.endswith('.py'):
                self.load_from_file(os.path.join(self.dir, f))
                
    def get_mods_by_type(self, t):
        return [m for m in self.mods.values() if isinstance(m, t) and m.en]

class CoreBlocksMod(BlockMod):
    def __init__(self):
        super().__init__("core_blocks", "Core Blocks", "1.0")
        
    def init(self, bus):
        super().init(bus)
        
    def blocks(self):
        return {
            'grass': {'solid': True, 'transparent': False, 'billboard': False},
            'grass_cover': {'solid': False, 'transparent': True, 'billboard': True},
            'dirt': {'solid': True, 'transparent': False, 'billboard': False},
            'stone': {'solid': True, 'transparent': False, 'billboard': False},
            'mountain_stone': {'solid': True, 'transparent': False, 'billboard': False},
            'wood': {'solid': True, 'transparent': False, 'billboard': False},
            'jungle_wood': {'solid': True, 'transparent': False, 'billboard': False},
            'dark_wood': {'solid': True, 'transparent': False, 'billboard': False},
            'sand': {'solid': True, 'transparent': False, 'billboard': False},
            'red_sand': {'solid': True, 'transparent': False, 'billboard': False},
            'water': {'solid': False, 'transparent': True, 'billboard': False},
            'lava': {'solid': False, 'transparent': True, 'billboard': False},
            'leaves': {'solid': False, 'transparent': True, 'billboard': False},
            'jungle_leaves': {'solid': False, 'transparent': True, 'billboard': False},
            'pine_leaves': {'solid': False, 'transparent': True, 'billboard': False},
            'bricks': {'solid': True, 'transparent': False, 'billboard': False},
            'beenest': {'solid': True, 'transparent': False, 'billboard': False},
            'gold': {'solid': True, 'transparent': False, 'billboard': False},
            'planks': {'solid': True, 'transparent': False, 'billboard': False},
            'cobblestone': {'solid': True, 'transparent': False, 'billboard': False},
            'fire': {'solid': False, 'transparent': True, 'billboard': True},
            'wool_white': {'solid': True, 'transparent': False, 'billboard': False},
            'wool_red': {'solid': True, 'transparent': False, 'billboard': False},
            'wool_blue': {'solid': True, 'transparent': False, 'billboard': False},
            'wool_green': {'solid': True, 'transparent': False, 'billboard': False},
            'wool_yellow': {'solid': True, 'transparent': False, 'billboard': False},
            'wool_orange': {'solid': True, 'transparent': False, 'billboard': False},
            'wool_purple': {'solid': True, 'transparent': False, 'billboard': False},
            'wool_pink': {'solid': True, 'transparent': False, 'billboard': False},
            'beets': {'solid': False, 'transparent': True, 'billboard': True},
            'carrots': {'solid': False, 'transparent': True, 'billboard': True},
            'roses': {'solid': False, 'transparent': True, 'billboard': True},
            'flower': {'solid': False, 'transparent': True, 'billboard': True},
            'flower2': {'solid': False, 'transparent': True, 'billboard': True},
            'lamps': {'solid': True, 'transparent': False, 'billboard': False},
            'gold_bars': {'solid': True, 'transparent': False, 'billboard': False},
            'golden_sword': {'solid': True, 'transparent': False, 'billboard': False},
            'snow': {'solid': True, 'transparent': False, 'billboard': False},
            'ice': {'solid': True, 'transparent': True, 'billboard': False},
            'clay': {'solid': True, 'transparent': False, 'billboard': False},
            'fire_strike': {'solid': False, 'transparent': True, 'billboard': True},
            'bow': {'solid': False, 'transparent': True, 'billboard': True},
            'arrow': {'solid': False, 'transparent': True, 'billboard': True}
        }
        
    def textures(self):
        return {
            'grass': ['grass.png'], 'grass_cover': ['grass_cover.png'], 'dirt': ['dirt.png'], 'stone': ['stone.png'],
            'mountain_stone': ['mountain_stone.png'], 'wood': ['wood.png'], 'jungle_wood': ['jungle_wood.png'], 
            'dark_wood': ['dark_wood.png'], 'sand': ['sand.png'], 'red_sand': ['red_sand.png'], 'water': ['water.png'], 
            'lava': ['lava.png'], 'fire': ['fire.png'], 'leaves': ['leaves.png'], 'jungle_leaves': ['jungle_leaves.png'], 
            'pine_leaves': ['pine_leaves.png'], 'bricks': ['bricks.png'], 'beenest': ['beenest.png'],
            'gold': ['gold.png'], 'planks': ['planks.png'], 'cobblestone': ['cobblestone.png'],
            'beets': ['beets.png'], 'carrots': ['carrots.png'], 'roses': ['rose.png'],
            'flower': ['flower.png'], 'flower2': ['flower2.png'],
            'wool_white': ['wool_white.png'], 'wool_red': ['wool_red.png'],
            'wool_blue': ['wool_blue.png'], 'wool_green': ['wool_green.png'],
            'wool_yellow': ['wool_yellow.png'], 'wool_orange': ['wool_orange.png'],
            'wool_purple': ['wool_purple.png'], 'wool_pink': ['wool_pink.png'],
            'lamps': ['lamp.png'], 'gold_bars': ['gold_bar.png'],
            'golden_sword': ['golden_sword.png'], 'snow': ['snow.png'],
            'ice': ['ice.png'], 'clay': ['clay.png'], 'fire_strike': ['fire_strike.png'],
            'bow': ['bow.png'], 'arrow': ['arrow.png']
        }

class CoreWorldMod(WorldMod):
    def __init__(self):
        super().__init__("core_world", "Core World Generation", "1.0")
        
    def init(self, bus):
        super().init(bus)
        
    def gen(self, cx, cz, ch):
        return []

class CoreUIMod(UIMod):
    def __init__(self):
        super().__init__("core_ui", "Core UI", "1.0")
        
    def init(self, bus):
        super().init(bus)
        
    def render(self, w, h):
        pass
        
    def input(self, sym, mod):
        return False

class CoreGameplayMod(GameMod):
    def __init__(self):
        super().__init__("core_gameplay", "Core Gameplay", "1.0")
        
    def init(self, bus):
        super().init(bus)

class SimpleNeuralNetwork:
    def __init__(self, is_=5, hs=12, os=1):
        self.is_, self.hs, self.os, self.lr = is_, hs, os, 0.01
        if NUMPY_AVAILABLE:
            self.w1, self.w2 = np.random.randn(is_, hs) * 0.1, np.random.randn(hs, os) * 0.1
            self.b1, self.b2 = np.zeros((1, hs)), np.zeros((1, os))
        else:
            self.w1 = [[random.gauss(0, 0.1) for _ in range(hs)] for _ in range(is_)]
            self.w2 = [[random.gauss(0, 0.1)] for _ in range(hs)]
            self.b1, self.b2 = [0.0] * hs, [0.0]

    def sigmoid(self, x):
        return 1 / (1 + np.exp(-np.clip(x, -500, 500))) if NUMPY_AVAILABLE else ([1 / (1 + math.exp(-max(-500, min(500, val)))) for val in x] if isinstance(x, list) else 1 / (1 + math.exp(-max(-500, min(500, x)))))

    def forward(self, x):
        if NUMPY_AVAILABLE:
            x = np.array(x).reshape(1, -1)
            z1, a1 = np.dot(x, self.w1) + self.b1, self.sigmoid(np.dot(x, self.w1) + self.b1)
            return self.sigmoid(np.dot(a1, self.w2) + self.b2)[0][0]
        else:
            z1 = [sum(x[i] * self.w1[i][j] for i in range(len(x))) + self.b1[j] for j in range(len(self.w1[0]))]
            a1 = self.sigmoid(z1)
            return self.sigmoid(sum(a1[i] * self.w2[i][0] for i in range(len(a1))) + self.b2[0])

    def train(self, x, tgt):
        if not NUMPY_AVAILABLE: return
        x, tgt = np.array(x).reshape(1, -1), np.array([[tgt]])
        z1, a1 = np.dot(x, self.w1) + self.b1, self.sigmoid(np.dot(x, self.w1) + self.b1)
        z2, out = np.dot(a1, self.w2) + self.b2, self.sigmoid(np.dot(a1, self.w2) + self.b2)
        dz2, dw2, db2 = out - tgt, np.dot(a1.T, out - tgt), out - tgt
        da1, dz1 = np.dot(out - tgt, self.w2.T), np.dot(out - tgt, self.w2.T) * a1 * (1 - a1)
        dw1, db1 = np.dot(x.T, dz1), dz1
        self.w1 -= self.lr * dw1
        self.w2 -= self.lr * dw2
        self.b1 -= self.lr * db1
        self.b2 -= self.lr * db2

class Herobrine:
    def __init__(self):
        self.x = self.y = self.z = self.tx = self.tz = 0
        self.vis, self.lst, self.ht, self.nn, self.ph, self.lbe, self.ld = False, 0, 0, SimpleNeuralNetwork(), deque(maxlen=60), [], []
        self.fd, self.hd, self.sl, self.ms, self.ft = 8.0, 15.0, 0.5, 3.0, 0.7
        self.bh, self.bw, self.hs = 1.8, 0.6, 0.3

    def update(self, px, py, pz, pyaw, ppitch, dt):
        try:
            if random.random() > 0.7: return
            ct = time.time()
            self.record_player_state(px, pz, pyaw, ppitch, ct)
            wlb = self.predict_look_back()
            self.update_position(px, py, pz, pyaw, wlb, dt)
            self.update_visibility(px, pz, pyaw, wlb, ct)
            if len(self.lbe) > 3: self.learn_from_events()
        except Exception:
            pass  # Silently handle any errors
    def record_player_state(self, px, pz, yaw, pitch, ts):
        yaw %= 360
        st = {'x': px, 'z': pz, 'yaw': yaw, 'pitch': pitch, 'time': ts, 'yaw_change': 0, 'movement_speed': 0}
        if len(self.ph) > 0:
            prev, dt = self.ph[-1], ts - self.ph[-1]['time']
            if dt > 0:
                yd = yaw - prev['yaw']
                yd = yd - 360 if yd > 180 else yd + 360 if yd < -180 else yd
                st['yaw_change'], st['movement_speed'] = abs(yd) / dt, math.sqrt((px - prev['x'])**2 + (pz - prev['z'])**2) / dt
        self.ph.append(st)
        if len(self.ph) >= 10:
            ryc = [s['yaw_change'] for s in list(self.ph)[-10:]]
            if max(ryc) > 120:
                self.lbe.append({'time': ts, 'intensity': min(max(ryc) / 180, 1.0)})

    def predict_look_back(self):
        if len(self.ph) < 5: return 0.3
        rs = list(self.ph)[-5:]
        ayc, am = sum(s['yaw_change'] for s in rs) / len(rs), sum(s['movement_speed'] for s in rs) / len(rs)
        ct, tslb = time.time(), 30.0
        if self.lbe: tslb = min(30.0, ct - self.lbe[-1]['time'])
        cp = rs[-1]['pitch']
        ya = rs[-1]['yaw_change'] - rs[-2]['yaw_change'] if len(rs) >= 2 else 0
        feats = [ayc / 100, am / 5, tslb / 30, cp / 90, ya / 50]
        try: return max(0.1, min(0.9, self.nn.forward(feats)))
        except: return 0.3

    def update_position(self, px, py, pz, pyaw, wlb, dt):
        yr = math.radians(pyaw)
        bx, bz = -math.sin(yr) * self.fd, math.cos(yr) * self.fd
        nx, nz = (random.gauss(0, 0.3), random.gauss(0, 0.3)) if wlb < 0.5 else (random.gauss(0, 1.0), random.gauss(0, 1.0))
        self.tx, self.tz = px + bx + nx, pz + bz + nz
        dx, dz, d = self.tx - self.x, self.tz - self.z, math.sqrt((self.tx - self.x)**2 + (self.tz - self.z)**2)
        if d > 0.5:
            ms = self.ms * dt * (2.0 if wlb > self.ft else 1.0)
            self.x += (dx / d) * ms
            self.z += (dz / d) * ms
        self.y = py

    def update_visibility(self, px, pz, pyaw, wlb, ct):
        d = math.sqrt((self.x - px)**2 + (self.z - pz)**2)
        dx, dz = self.x - px, self.z - pz
        ath = math.degrees(math.atan2(dx, -dz)) % 360
        pyaw_norm = pyaw % 360
        ad = abs(ath - pyaw_norm)
        if ad > 180: ad = 360 - ad
        in_fov = ad < 60
        if wlb > self.ft or in_fov:
            self.vis, self.ht = False, ct + random.uniform(1.0, 3.0)
        elif d > 3.0 and ct > self.ht:
            self.vis = random.random() < (1.0 - wlb * 0.8)
        if random.random() < 0.001:
            self.vis, self.ht = False, ct + random.uniform(0.5, 2.0)

    def learn_from_events(self):
        ct = time.time()
        self.lbe = [e for e in self.lbe if ct - e['time'] < 60]
        if len(self.lbe) > 0 and len(self.ph) >= 10:
            re = self.lbe[-1]
            pes = [s for s in self.ph if s['time'] < re['time']]
            if len(pes) >= 5:
                sb = pes[-5:]
                ayc, am = sum(s['yaw_change'] for s in sb) / len(sb), sum(s['movement_speed'] for s in sb) / len(sb)
                tg, ap, ya = re['time'] - sb[0]['time'], sum(s['pitch'] for s in sb) / len(sb), sb[-1]['yaw_change'] - sb[0]['yaw_change']
                feats = [ayc / 100, am / 5, tg / 30, ap / 90, ya / 50]
                try: self.nn.train(feats, re['intensity'])
                except: pass
                self.lbe.pop()

    def draw(self, cx, cy, cz):
        if not self.vis: return
        dtc = math.sqrt((self.x - cx)**2 + (self.z - cz)**2)
        if dtc < 2.0: return
        glPushAttrib(GL_ALL_ATTRIB_BITS)
        glPushMatrix()
        try:
            glDisable(GL_TEXTURE_2D)
            glEnable(GL_DEPTH_TEST)
            glEnable(GL_BLEND)
            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
            glTranslatef(self.x, self.y, self.z)
            a = 0.8 if dtc > 10 else 0.6
            glColor4f(0.1, 0.1, 0.1, a)
            self.draw_body()
            glTranslatef(0, self.bh - 0.2, 0)
            self.draw_head()
            if random.random() < 0.1: self.draw_eyes()
        except: pass
        finally:
            glPopMatrix()
            glPopAttrib()

    def draw_body(self):
        w, h, d = self.bw/2, self.bh, self.bw/2
        glBegin(GL_QUADS)
        for f in [(-w,0,d,w,0,d,w,h,d,-w,h,d), (w,0,-d,-w,0,-d,-w,h,-d,w,h,-d), (-w,0,-d,-w,0,d,-w,h,d,-w,h,-d), (w,0,d,w,0,-d,w,h,-d,w,h,d)]:
            [glVertex3f(f[i],f[i+1],f[i+2]) for i in range(0,len(f),3)]
        glEnd()

    def draw_head(self):
        s = self.hs
        glBegin(GL_QUADS)
        for f in [(-s,-s,s,s,-s,s,s,s,s,-s,s,s), (s,-s,-s,-s,-s,-s,-s,s,-s,s,s,-s), (-s,-s,-s,-s,-s,s,-s,s,s,-s,s,-s), (s,-s,s,s,-s,-s,s,s,-s,s,s,s), (-s,s,-s,-s,s,s,s,s,s,s,s,-s)]:
            [glVertex3f(f[i],f[i+1],f[i+2]) for i in range(0,len(f),3)]
        glEnd()

    def draw_eyes(self):
        glColor4f(1.0, 1.0, 1.0, 1.0)
        for x in [-0.1, 0.1]:
            glPushMatrix()
            glTranslatef(x, 0, 0.31)
            glBegin(GL_QUADS)
            for v in [(-0.05,-0.05,0), (0.05,-0.05,0), (0.05,0.05,0), (-0.05,0.05,0)]:
                glVertex3f(*v)
            glEnd()
            glPopMatrix()

    def stats(self):
        return {'pos': (self.x, self.y, self.z), 'vis': self.vis, 'sl': self.sl, 'le': len(self.lbe), 'pa': self.nn.lr if NUMPY_AVAILABLE else 0}

class HerobrineMod(GameMod):
    def __init__(self):
        super().__init__("herobrine", "Herobrine AI", "1.0")
        self.h = None
        self.en = False
        
    def init(self, bus):
        super().init(bus)
        try:
            self.h = Herobrine()
            self.en = True
            bus.on('player_update', self.on_player_update)
            bus.on('render_entities', self.on_render)
            bus.on('toggle_herobrine', self.toggle)
            print("👁️  Herobrine initialized")
        except Exception as e:
            print(f"❌ Herobrine init failed: {e}")
            self.h = None
            self.en = False
            
    def on_player_update(self, px, py, pz, yaw, pitch, dt):
        if self.en and self.h:
            try: self.h.update(px, py, pz, yaw, pitch, dt)
            except: pass
            
    def on_render(self, cx, cy, cz):
        if self.en and self.h:
            try: self.h.draw(cx, cy, cz)
            except: pass
            
    def toggle(self):
        self.en = not self.en
        if self.en and self.h:
            self.h.x, self.h.z, self.h.vis = 0, 0, False
            print("👁️  Herobrine is watching...")
        else:
            print("😌 Herobrine has vanished...")

class GrandExchange:
    def __init__(self):
        self.open = False
        self.items = {}
        self.ui = {}
        self.si = None
        self.sp = 1
        self.mode = 'buy'

    def add_item(self, it, price, seller='player'):
        if it not in self.items: self.items[it] = []
        self.items[it].append({'price': price, 'seller': seller})
        if seller == 'player':
            if it not in self.ui: self.ui[it] = []
            self.ui[it].append({'price': price})

    def remove_item(self, it, price):
        if it in self.items:
            self.items[it] = [i for i in self.items[it] if not (i['price'] == price and i['seller'] == 'player')]
        if it in self.ui:
            self.ui[it] = [i for i in self.ui[it] if i['price'] != price]

    def buy_item(self, it, price, inv):
        if it in self.items:
            for i, item in enumerate(self.items[it]):
                if item['price'] == price and item['seller'] != 'player':
                    if 'gold_bars' in inv and inv['gold_bars']['count'] >= price:
                        inv['gold_bars']['count'] -= price
                        if it not in inv: inv[it] = {'count': 0}
                        inv[it]['count'] += 1
                        del self.items[it][i]
                        return True
        return False

    def get_items_for_sale(self):
        res = {}
        for it, items in self.items.items():
            if items: res[it] = min(item['price'] for item in items)
        return res

    def draw_ui(self, w, h, inv):
        if not self.open: return
        gw, gh = 600, 400
        gx, gy = (w - gw) // 2, (h - gh) // 2
        glDisable(GL_TEXTURE_2D)
        glEnable(GL_BLEND)
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
        glColor4f(0.1, 0.1, 0.1, 0.9)
        glBegin(GL_QUADS)
        glVertex2f(gx, gy)
        glVertex2f(gx + gw, gy)
        glVertex2f(gx + gw, gy + gh)
        glVertex2f(gx, gy + gh)
        glEnd()
        
        title = pyglet.text.Label('GRAND EXCHANGE', font_name='Arial', font_size=16, x=gx + gw//2, y=gy + gh - 30, anchor_x='center', color=(255, 255, 255, 255))
        title.draw()
        
        ml = pyglet.text.Label(f'Mode: {self.mode.upper()} | Gold Bars: {inv.get("gold_bars", {}).get("count", 0)}', font_name='Arial', font_size=12, x=gx + 10, y=gy + gh - 60, color=(255, 255, 255, 255))
        ml.draw()
        
        if self.mode == 'buy':
            ifs = self.get_items_for_sale()
            yo = gy + gh - 90
            for i, (it, price) in enumerate(ifs.items()):
                if i >= 10: break
                bx, by, bw, bh = gx + 10, yo - i * 25, gw - 20, 20
                glColor4f(0.2, 0.4, 0.2, 0.8)
                glBegin(GL_QUADS)
                glVertex2f(bx, by)
                glVertex2f(bx + bw, by)
                glVertex2f(bx + bw, by + bh)
                glVertex2f(bx, by + bh)
                glEnd()
                il = pyglet.text.Label(f'{it.replace("_", " ").title()} - {price} Gold Bars', font_name='Arial', font_size=10, x=bx + 5, y=by + 2, color=(255, 255, 255, 255))
                il.draw()
        
        elif self.mode == 'sell':
            yo = gy + gh - 90
            ai = [k for k, v in inv.items() if v.get('count', 0) > 0 and k != 'gold_bars']
            for i, it in enumerate(ai):
                if i >= 10: break
                bx, by, bw, bh = gx + 10, yo - i * 25, gw - 20, 20
                sel = it == self.si
                glColor4f(0.4, 0.2, 0.2, 0.8) if sel else glColor4f(0.2, 0.2, 0.4, 0.8)
                glBegin(GL_QUADS)
                glVertex2f(bx, by)
                glVertex2f(bx + bw, by)
                glVertex2f(bx + bw, by + bh)
                glVertex2f(bx, by + bh)
                glEnd()
                cnt = inv[it]['count']
                il = pyglet.text.Label(f'{it.replace("_", " ").title()} x{cnt}', font_name='Arial', font_size=10, x=bx + 5, y=by + 2, color=(255, 255, 255, 255))
                il.draw()
            
            if self.si:
                pl = pyglet.text.Label(f'Sell Price: {self.sp} Gold Bars', font_name='Arial', font_size=12, x=gx + 10, y=gy + 50, color=(255, 255, 255, 255))
                pl.draw()
                il = pyglet.text.Label('Use +/- keys to adjust price, ENTER to list item', font_name='Arial', font_size=10, x=gx + 10, y=gy + 30, color=(200, 200, 200, 255))
                il.draw()
        
        cl = pyglet.text.Label('TAB: Switch Mode | 1-9: Select Item | ESC: Close', font_name='Arial', font_size=10, x=gx + 10, y=gy + 10, color=(200, 200, 200, 255))
        cl.draw()
        glDisable(GL_BLEND)

class WeatherSystem:
    def __init__(self):
        self.weather = 'clear'
        self.intensity = 0.0
        self.transition_time = 0.0
        self.lightning_timer = 0.0
        self.last_lightning = 0.0
        self.rain_drops = []
        self.snow_flakes = []
        self.fire_blocks = []
        self.clouds = []
        self.init_clouds()
        
    def init_clouds(self):
        self.clouds = []
        for i in range(15):
            a, alt, d = random.uniform(0, 2 * math.pi), random.uniform(0.3, 0.7), random.uniform(60, 120)
            x = math.cos(a) * math.sin(alt) * d
            y = math.cos(alt) * d * 0.4 + random.uniform(20, 40)
            z = math.sin(a) * math.sin(alt) * d
            self.clouds.append({
                'x': x, 'y': y, 'z': z, 
                'size': random.uniform(8, 15), 
                'speed': random.uniform(0.005, 0.02),
                'offset': random.uniform(0, 2 * math.pi),
                'opacity': random.uniform(0.3, 0.8)
            })
    
    def update(self, dt, game):
        try:
            ct = time.time()
            self.transition_time += dt
            
            if self.transition_time > random.uniform(120, 300):
                self.change_weather()
                self.transition_time = 0
                
            if self.weather == 'storm':
                self.lightning_timer += dt
                if self.lightning_timer > random.uniform(5, 15):
                    self.strike_lightning(game)
                    self.lightning_timer = 0
                    
            self.update_particles(dt)
            self.update_fire(dt, game)
        except Exception:
            pass  # Handle errors gracefully
    def change_weather(self):
        weathers = ['clear', 'rain', 'snow', 'storm']
        self.weather = random.choice(weathers)
        self.intensity = random.uniform(0.3, 1.0)
        print(f"🌤️ Weather changed to: {self.weather}")
        
    def strike_lightning(self, game):
        if not hasattr(game, 'pb'): return
        
        lx = game.cam.x + random.uniform(-50, 50)
        lz = game.cam.z + random.uniform(-50, 50)
        ly = game.cm.get_height_at(int(lx), int(lz), game.mod_loader) + 1
        
        if ly > 0:
            fire_block = Block(int(lx), int(ly), int(lz), 'fire')
            game.pb.append(fire_block)
            self.fire_blocks.append({
                'x': int(lx), 'y': int(ly), 'z': int(lz),
                'life': random.uniform(30, 60),
                'intensity': 1.0
            })
            print(f"⚡ Lightning struck at ({int(lx)}, {int(ly)}, {int(lz)})")
        
        self.last_lightning = time.time()
        
    def update_particles(self, dt):
        if self.weather == 'rain':
            if len(self.rain_drops) < 200:
                for _ in range(5):
                    self.rain_drops.append({
                        'x': random.uniform(-20, 20),
                        'y': random.uniform(20, 50),
                        'z': random.uniform(-20, 20),
                        'vy': random.uniform(-15, -10)
                    })
            for drop in self.rain_drops[:]:
                drop['y'] += drop['vy'] * dt
                if drop['y'] < 0:
                    self.rain_drops.remove(drop)
                    
        elif self.weather == 'snow':
            if len(self.snow_flakes) < 150:
                for _ in range(3):
                    self.snow_flakes.append({
                        'x': random.uniform(-30, 30),
                        'y': random.uniform(20, 40),
                        'z': random.uniform(-30, 30),
                        'vy': random.uniform(-3, -1),
                        'vx': random.uniform(-0.5, 0.5),
                        'vz': random.uniform(-0.5, 0.5)
                    })
            for flake in self.snow_flakes[:]:
                flake['x'] += flake['vx'] * dt
                flake['y'] += flake['vy'] * dt
                flake['z'] += flake['vz'] * dt
                if flake['y'] < 0:
                    self.snow_flakes.remove(flake)
        else:
            self.rain_drops.clear()
            self.snow_flakes.clear()
            
    def update_fire(self, dt, game):
        for fire in self.fire_blocks[:]:
            fire['life'] -= dt
            fire['intensity'] = max(0, fire['life'] / 30.0)
            if fire['life'] <= 0:
                self.fire_blocks.remove(fire)
                if hasattr(game, 'pb'):
                    for block in game.pb[:]:
                        if (block.x == fire['x'] and block.y == fire['y'] and 
                            block.z == fire['z'] and block.type == 'fire'):
                            game.pb.remove(block)
                            break
                            
    def draw_weather(self, cx, cy, cz):
        self.draw_clouds(cx, cy, cz)
        
        if time.time() - self.last_lightning < 0.5:
            glDisable(GL_TEXTURE_2D)
            glEnable(GL_BLEND)
            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
            glColor4f(1.0, 1.0, 1.0, 0.3)
            glBegin(GL_QUADS)
            glVertex3f(cx - 100, cy - 10, cz - 100)
            glVertex3f(cx + 100, cy - 10, cz - 100)
            glVertex3f(cx + 100, cy + 100, cz + 100)
            glVertex3f(cx - 100, cy + 100, cz + 100)
            glEnd()
            glDisable(GL_BLEND)
            
        if self.weather in ['rain', 'storm']:
            self.draw_rain(cx, cy, cz)
        elif self.weather == 'snow':
            self.draw_snow(cx, cy, cz)
            
    def draw_clouds(self, cx, cy, cz):
        ct = time.time()
        glEnable(GL_BLEND)
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
        glDisable(GL_TEXTURE_2D)
        
        weather_color = {
            'clear': (1.0, 1.0, 1.0, 0.7),
            'rain': (0.6, 0.6, 0.7, 0.9),
            'snow': (0.9, 0.9, 1.0, 0.8),
            'storm': (0.3, 0.3, 0.4, 1.0)
        }
        color = weather_color.get(self.weather, (1.0, 1.0, 1.0, 0.7))
        glColor4f(*color)
        
        for cloud in self.clouds:
            ax = cloud['x'] + math.sin(ct * cloud['speed'] + cloud['offset']) * 10
            az = cloud['z'] + math.cos(ct * cloud['speed'] + cloud['offset']) * 5
            ay = cloud['y'] + math.sin(ct * cloud['speed'] * 0.5) * 2
            
            glPushMatrix()
            glTranslatef(cx + ax, ay, cz + az)
            self.draw_cloud_sphere(cloud['size'])
            glPopMatrix()
            
        glDisable(GL_BLEND)
        
    def draw_cloud_sphere(self, r):
        s = 6
        glBegin(GL_TRIANGLES)
        for i in range(s):
            l1, l2 = math.pi * (i / s - 0.5), math.pi * ((i + 1) / s - 0.5)
            for j in range(s):
                lo1, lo2 = 2 * math.pi * j / s, 2 * math.pi * (j + 1) / s
                x1, y1, z1 = r * math.cos(l1) * math.cos(lo1), r * math.sin(l1), r * math.cos(l1) * math.sin(lo1)
                x2, y2, z2 = r * math.cos(l2) * math.cos(lo1), r * math.sin(l2), r * math.cos(l2) * math.sin(lo1)
                x3, y3, z3 = r * math.cos(l1) * math.cos(lo2), r * math.sin(l1), r * math.cos(l1) * math.sin(lo2)
                glVertex3f(x1, y1, z1)
                glVertex3f(x2, y2, z2)
                glVertex3f(x3, y3, z3)
        glEnd()
        
    def draw_rain(self, cx, cy, cz):
        glDisable(GL_TEXTURE_2D)
        glEnable(GL_BLEND)
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
        glColor4f(0.5, 0.7, 1.0, 0.6)
        glLineWidth(1)
        
        glBegin(GL_LINES)
        for drop in self.rain_drops:
            dx, dy, dz = cx + drop['x'], drop['y'], cz + drop['z']
            glVertex3f(dx, dy, dz)
            glVertex3f(dx, dy - 2, dz)
        glEnd()
        glDisable(GL_BLEND)
        
    def draw_snow(self, cx, cy, cz):
        glDisable(GL_TEXTURE_2D)
        glEnable(GL_BLEND)
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
        glColor4f(1.0, 1.0, 1.0, 0.8)
        glPointSize(3)
        
        glBegin(GL_POINTS)
        for flake in self.snow_flakes:
            glVertex3f(cx + flake['x'], flake['y'], cz + flake['z'])
        glEnd()
        glDisable(GL_BLEND)

class WeatherMod(GameMod):
    def __init__(self):
        super().__init__("weather", "Weather System", "1.0")
        self.weather = WeatherSystem()
        
    def init(self, bus):
        super().init(bus)
        bus.on('update', self.on_update)
        bus.on('render_weather', self.on_render)
        
    def on_update(self, dt, game):
        self.weather.update(dt, game)
        
    def on_render(self, cx, cy, cz):
        self.weather.draw_weather(cx, cy, cz)

class Parrot:
    def __init__(self, x, y, z):
        self.x, self.y, self.z = x, y, z
        self.tx, self.ty, self.tz = x, y, z
        self.speed = 8.0
        self.state = 'flying'
        self.perch_time = 0.0
        self.color = random.choice(['red', 'blue', 'green', 'yellow'])
        self.wing_beat = 0.0
        self.target_tree = None
        
    def update(self, dt, trees):
        self.wing_beat += dt * 15.0
        
        if self.state == 'flying':
            dx, dy, dz = self.tx - self.x, self.ty - self.y, self.tz - self.z
            d = math.sqrt(dx*dx + dy*dy + dz*dz)
            
            if d > 0.5:
                move_speed = self.speed * dt
                self.x += (dx / d) * move_speed
                self.y += (dy / d) * move_speed
                self.z += (dz / d) * move_speed
            else:
                if self.target_tree:
                    self.state = 'perched'
                    self.perch_time = 0.0
                else:
                    self.find_new_target(trees)
                    
        elif self.state == 'perched':
            self.perch_time += dt
            if self.perch_time > random.uniform(3, 8):
                self.state = 'flying'
                self.find_new_target(trees)
                
    def find_new_target(self, trees):
        if trees:
            tree = random.choice(trees)
            tx, tz, tt = tree
            self.tx, self.ty, self.tz = tx + random.uniform(-2, 2), 10 + random.uniform(0, 3), tz + random.uniform(-2, 2)
            self.target_tree = tree
        else:
            self.tx = self.x + random.uniform(-20, 20)
            self.ty = 8 + random.uniform(0, 5)
            self.tz = self.z + random.uniform(-20, 20)
            self.target_tree = None
            
    def draw(self):
        glPushMatrix()
        glTranslatef(self.x, self.y, self.z)
        glDisable(GL_TEXTURE_2D)
        
        colors = {
            'red': (1.0, 0.0, 0.0),
            'blue': (0.0, 0.0, 1.0),
            'green': (0.0, 1.0, 0.0),
            'yellow': (1.0, 1.0, 0.0)
        }
        glColor3f(*colors[self.color])
        
        glBegin(GL_QUADS)
        for f in [(-0.1,-0.1,0.2,0.1,-0.1,0.2,0.1,0.1,0.2,-0.1,0.1,0.2)]:
            [glVertex3f(f[i],f[i+1],f[i+2]) for i in range(0,len(f),3)]
        glEnd()
        
        if self.state == 'flying':
            wing_angle = math.sin(self.wing_beat) * 30
            glRotatef(wing_angle, 0, 0, 1)
            
        glColor3f(0.2, 0.2, 0.2)
        glBegin(GL_QUADS)
        glVertex3f(-0.3, 0, 0)
        glVertex3f(-0.1, 0, 0)
        glVertex3f(-0.1, 0.2, 0)
        glVertex3f(-0.3, 0.2, 0)
        glVertex3f(0.1, 0, 0)
        glVertex3f(0.3, 0, 0)
        glVertex3f(0.3, 0.2, 0)
        glVertex3f(0.1, 0.2, 0)
        glEnd()
        
        glPopMatrix()

class Villager:
    def __init__(self, x, y, z):
        self.x, self.y, self.z = x, y, z
        self.tx, self.tz = x, z
        self.speed = 2.0
        self.last_target_time = 0.0
        self.walk_time = 0.0
        self.direction = random.uniform(0, 2 * math.pi)
        
    def update(self, dt):
        try:
            # Limit update frequency for better performance
            if hasattr(self, '_last_update'):
                if time.time() - self._last_update < 0.05:  # Limit to 20 FPS
                    return
            self._last_update = time.time()
            
            st = time.time()
            
            # Reduce frequency of expensive operations
            frame_count = getattr(self, '_frame_count', 0) + 1
            self._frame_count = frame_count
            
            # Only update day/night cycle every 5 frames
            if frame_count % 5 == 0 and hasattr(self, 'dnc'):
                try:
                    self.dnc.update(dt)
                except Exception:
                    pass
                    
            # Only update sky color every 10 frames
            if frame_count % 10 == 0 and hasattr(self, 'dnc'):
                try:
                    sc = self.dnc.get_sky_color()
                    glClearColor(*sc, 1.0)
                except Exception:
                    pass
                    
            if hasattr(self, 'mc') and self.mc:
                # Only update chunks when player moves significantly
                if hasattr(self, 'lpp') and hasattr(self, 'cam'):
                    dx, dz = abs(self.cam.x - self.lpp[0]), abs(self.cam.z - self.lpp[1])
                    if dx > 16 or dz > 16:  # Increased threshold
                        try:
                            self.cm.update_chunks(self.cam.x, self.cam.z, self.mod_loader)
                            self.lpp = (self.cam.x, self.cam.z)
                        except Exception:
                            pass
                        
                try:
                    self.cam.update(dt, self.keys, self.cm, self.pb)
                    self.pm.update(dt, self.cam.vx, self.cam.vz)
                except Exception:
                    pass
                
                # Reduce mod event frequency
                if frame_count % 3 == 0 and hasattr(self, 'mod_loader'):
                    try:
                        self.mod_loader.bus.emit('player_update', self.cam.x, self.cam.y, self.cam.z, self.cam.ry, self.cam.rx, dt)
                        self.mod_loader.bus.emit('update', dt, self)
                    except Exception:
                        pass
            
            # Update liquids less frequently
            if frame_count % 2 == 0 and hasattr(self, 'flowing_liquid'):
                try:
                    self.flowing_liquid.update(dt)
                except Exception:
                    pass
            
            # Update projectiles every frame (important for gameplay)
            if hasattr(self, 'projectiles'):
                try:
                    for projectile in self.projectiles[:]:
                        if not projectile.update(dt, self):
                            self.projectiles.remove(projectile)
                except Exception:
                    pass
            
            self.ft = time.time() - st
            fps = 1.0 / max(0.001, self.ft)
            
            # Auto-enable emergency mode for very low FPS
            if fps < 15 and not getattr(self, 'ema', False) and frame_count > 60:
                try:
                    self.activate_emergency_mode()
                except Exception:
                    pass
                    
        except Exception as e:
            print(f"⚠️ Update error: {e}")
            pass
    def draw(self):
        glPushMatrix()
        glTranslatef(self.x, self.y, self.z)
        glDisable(GL_TEXTURE_2D)
        glColor3f(0.1, 0.1, 0.1)
        
        glPushMatrix()
        glTranslatef(0, 1.7, 0)
        glBegin(GL_QUADS)
        s = 0.3
        for f in [(-s,-s,s,s,-s,s,s,s,s,-s,s,s), (s,-s,-s,-s,-s,-s,-s,s,-s,s,s,-s)]:
            [glVertex3f(f[i],f[i+1],f[i+2]) for i in range(0,len(f),3)]
        glEnd()
        glPopMatrix()
        
        glPushMatrix()
        glTranslatef(0, 1.0, 0)
        glBegin(GL_QUADS)
        w, h, d = 0.3, 0.6, 0.2
        for f in [(-w,0,d,w,0,d,w,h,d,-w,h,d), (w,0,-d,-w,0,-d,-w,h,-d,w,h,-d)]:
            [glVertex3f(f[i],f[i+1],f[i+2]) for i in range(0,len(f),3)]
        glEnd()
        glPopMatrix()
        
        for side in [-0.15, 0.15]:
            glPushMatrix()
            glTranslatef(side, 0.3, 0)
            glBegin(GL_QUADS)
            w, h, d = 0.1, 0.6, 0.1
            for f in [(-w,0,d,w,0,d,w,h,d,-w,h,d), (w,0,-d,-w,0,-d,-w,h,-d,w,h,-d)]:
                [glVertex3f(f[i],f[i+1],f[i+2]) for i in range(0,len(f),3)]
            glEnd()
            glPopMatrix()
            
        glPopMatrix()

class WildlifeMod(GameMod):
    def __init__(self):
        super().__init__("wildlife", "Wildlife System", "1.0")
        self.parrots = []
        self.villagers = []
        self.last_spawn_time = 0.0
        
    def init(self, bus):
        super().init(bus)
        bus.on('update', self.on_update)
        bus.on('render_entities', self.on_render)
        print("🦜 Wildlife system initialized")
        
    def on_update(self, dt, game):
        ct = time.time()
        
        if ct - self.last_spawn_time > 30 and len(self.parrots) < 5:
            self.spawn_parrot_near_trees(game)
            self.last_spawn_time = ct
            
        if ct - self.last_spawn_time > 45 and len(self.villagers) < 3:
            self.spawn_villager_in_town(game)
            
        trees = self.get_nearby_trees(game)
        for parrot in self.parrots[:]:
            parrot.update(dt, trees)
            d = math.sqrt((parrot.x - game.cam.x)**2 + (parrot.z - game.cam.z)**2)
            if d > 100:
                self.parrots.remove(parrot)
                
        for villager in self.villagers[:]:
            villager.update(dt)
            d = math.sqrt((villager.x - game.cam.x)**2 + (villager.z - game.cam.z)**2)
            if d > 100:
                self.villagers.remove(villager)
                
    def get_nearby_trees(self, game):
        trees = []
        pcx, pcz = game.cm.get_chunk_coords(game.cam.x, game.cam.z)
        for dx in range(-2, 3):
            for dz in range(-2, 3):
                chunk_key = (pcx + dx, pcz + dz)
                if chunk_key in game.cm.chunks:
                    chunk = game.cm.chunks[chunk_key]
                    trees.extend(chunk.trees)
        return trees
        
    def spawn_parrot_near_trees(self, game):
        trees = self.get_nearby_trees(game)
        if trees:
            tree = random.choice(trees)
            tx, tz, tt = tree
            parrot = Parrot(tx + random.uniform(-5, 5), 8 + random.uniform(0, 5), tz + random.uniform(-5, 5))
            self.parrots.append(parrot)
            print(f"🦜 Parrot spawned near tree at ({tx}, {tz})")
            
    def spawn_villager_in_town(self, game):
        px, pz = game.cam.x, game.cam.z
        vx = px + random.uniform(-30, 30)
        vz = pz + random.uniform(-30, 30)
        vy = game.cm.get_height_at(int(vx), int(vz), game.mod_loader) + 1
        if vy > 0:
            villager = Villager(vx, vy, vz)
            self.villagers.append(villager)
            print(f"👤 Villager spawned at ({vx:.1f}, {vy:.1f}, {vz:.1f})")
            
    def on_render(self, cx, cy, cz):
        try:
            glEnable(GL_BLEND)
            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
            
            for parrot in self.parrots:
                parrot.draw()
                
            for villager in self.villagers:
                villager.draw()
                
            glDisable(GL_BLEND)
        except:
            pass

class GrandExchangeMod(UIMod):
    def __init__(self):
        super().__init__("grand_exchange", "Grand Exchange", "1.0")
        self.gex = GrandExchange()
        
    def init(self, bus):
        super().init(bus)
        bus.on('render_ui', self.render)
        bus.on('key_press', self.input)
        
    def render(self, w, h):
        inv = self.bus.get('inventory', {})
        self.gex.draw_ui(w, h, inv)
        
    def input(self, sym, mod):
        if sym == pyglet.window.key.G:
            self.gex.open = not self.gex.open
            return True
        if not self.gex.open: return False
        
        if sym == pyglet.window.key.TAB:
            self.gex.mode = 'sell' if self.gex.mode == 'buy' else 'buy'
            return True
        elif sym == pyglet.window.key.PLUS:
            self.gex.sp += 1
            return True
        elif sym == pyglet.window.key.MINUS and self.gex.sp > 1:
            self.gex.sp -= 1
            return True
        elif sym == pyglet.window.key.ENTER and self.gex.si:
            inv = self.bus.get('inventory', {})
            if self.gex.si in inv and inv[self.gex.si]['count'] > 0:
                self.gex.add_item(self.gex.si, self.gex.sp)
                inv[self.gex.si]['count'] -= 1
                print(f"💰 Listed {self.gex.si} for {self.gex.sp} gold bars")
            return True
        elif sym in [pyglet.window.key._1, pyglet.window.key._2, pyglet.window.key._3, pyglet.window.key._4, pyglet.window.key._5, pyglet.window.key._6, pyglet.window.key._7, pyglet.window.key._8]:
            kn = sym - pyglet.window.key._1
            inv = self.bus.get('inventory', {})
            if self.gex.mode == 'buy':
                ifs = list(self.gex.get_items_for_sale().items())
                if kn < len(ifs):
                    it, price = ifs[kn]
                    if self.gex.buy_item(it, price, inv):
                        print(f"💰 Bought {it} for {price} gold bars")
            elif self.gex.mode == 'sell':
                ai = [k for k, v in inv.items() if v.get('count', 0) > 0 and k != 'gold_bars']
                if kn < len(ai): self.gex.si = ai[kn]
            return True
        return False

class CraftingSystem:
    def __init__(self):
        self.recipes = {
            'lamps': {'ingredients': {'planks': 1, 'gold': 1}, 'result': {'lamps': 1}},
            'gold_bars': {'ingredients': {'gold': 4}, 'result': {'gold_bars': 1}},
            'golden_sword': {'ingredients': {'gold_bars': 4}, 'result': {'golden_sword': 1}},
            'bricks': {'ingredients': {'stone': 2}, 'result': {'bricks': 1}},
            'bricks_from_cobblestone': {'ingredients': {'cobblestone': 2}, 'result': {'bricks': 1}},
            'planks': {'ingredients': {'wood': 1}, 'result': {'planks': 4}},
            'bow': {'ingredients': {'wood': 3, 'planks': 2}, 'result': {'bow': 1}},
            'arrow': {'ingredients': {'wood': 1, 'stone': 1}, 'result': {'arrow': 8}},
            'fire_strike': {'ingredients': {'stone': 2, 'gold': 1}, 'result': {'fire_strike': 1}}
        }
        self.open = False

    def can_craft(self, rn, inv):
        if rn not in self.recipes: return False
        recipe = self.recipes[rn]
        for ing, ra in recipe['ingredients'].items():
            if ing not in inv or inv[ing]['count'] < ra: return False
        return True

    def craft_item(self, rn, inv):
        if not self.can_craft(rn, inv): return False
        recipe = self.recipes[rn]
        for ing, ra in recipe['ingredients'].items(): inv[ing]['count'] -= ra
        for ri, ra in recipe['result'].items():
            if ri not in inv: inv[ri] = {'count': 0, 'slot': len(inv)}
            inv[ri]['count'] += ra
        print(f"🔨 Crafted {recipe['result']}")
        return True

    def get_available_recipes(self, inv):
        return [rn for rn in self.recipes if self.can_craft(rn, inv)]

    def draw_ui(self, w, h, inv):
        if not self.open: return
        cw, ch = 400, 300
        cx, cy = (w - cw) // 2, (h - ch) // 2
        glDisable(GL_TEXTURE_2D)
        glEnable(GL_BLEND)
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
        glColor4f(0.2, 0.2, 0.2, 0.9)
        glBegin(GL_QUADS)
        glVertex2f(cx, cy)
        glVertex2f(cx + cw, cy)
        glVertex2f(cx + cw, cy + ch)
        glVertex2f(cx, cy + ch)
        glEnd()
        tl = pyglet.text.Label('CRAFTING', font_name='Arial', font_size=16, x=cx + cw//2, y=cy + ch - 30, anchor_x='center', color=(255, 255, 255, 255))
        tl.draw()
        ar = self.get_available_recipes(inv)
        yo = cy + ch - 60
        for i, rn in enumerate(ar[:8]):
            recipe = self.recipes[rn]
            bx, by, bw, bh = cx + 10, yo - i * 30, cw - 20, 25
            glColor4f(0.3, 0.5, 0.3, 0.8)
            glBegin(GL_QUADS)
            glVertex2f(bx, by)
            glVertex2f(bx + bw, by)
            glVertex2f(bx + bw, by + bh)
            glVertex2f(bx, by + bh)
            glEnd()
            it = " + ".join([f"{a} {i}" for i, a in recipe['ingredients'].items()])
            rt = " + ".join([f"{a} {i}" for i, a in recipe['result'].items()])
            rt = f"{it} → {rt}"
            rl = pyglet.text.Label(rt, font_name='Arial', font_size=10, x=bx + 5, y=by + 5, color=(255, 255, 255, 255))
            rl.draw()
        if not ar:
            nrl = pyglet.text.Label('No recipes available with current items', font_name='Arial', font_size=12, x=cx + cw//2, y=cy + ch//2, anchor_x='center', color=(255, 255, 255, 255))
            nrl.draw()
        else:
            il = pyglet.text.Label('Press 1-8 to craft available recipes', font_name='Arial', font_size=10, x=cx + cw//2, y=cy + 20, anchor_x='center', color=(200, 200, 200, 255))
            il.draw()
        glDisable(GL_BLEND)

class CraftingMod(UIMod):
    def __init__(self):
        super().__init__("crafting", "Crafting System", "1.0")
        self.cs = CraftingSystem()
        
    def init(self, bus):
        super().init(bus)
        bus.on('render_ui', self.render)
        bus.on('key_press', self.input)
        
    def render(self, w, h):
        inv = self.bus.get('inventory', {})
        self.cs.draw_ui(w, h, inv)
        
    def input(self, sym, mod):
        if sym == pyglet.window.key.E:
            self.cs.open = not self.cs.open
            return True
        if not self.cs.open: return False
        
        if sym in [pyglet.window.key._1, pyglet.window.key._2, pyglet.window.key._3, pyglet.window.key._4, pyglet.window.key._5, pyglet.window.key._6, pyglet.window.key._7, pyglet.window.key._8]:
            kn = sym - pyglet.window.key._1
            inv = self.bus.get('inventory', {})
            ar = self.cs.get_available_recipes(inv)
            if kn < len(ar):
                rn = ar[kn]
                self.cs.craft_item(rn, inv)
            return True
        return False

class Town:
    def __init__(self, cx, cz):
        self.cx, self.cz = cx, cz
        self.sz = 60
        self.st = []
        self.des = False
        self.mat = {'foundation': 'stone', 'house_walls': 'wood', 'house_roof': 'planks', 'castle_walls': 'stone', 'castle_towers': 'bricks', 'bank_walls': 'bricks', 'bank_roof': 'planks', 'gex_walls': 'gold', 'gex_roof': 'planks', 'roads': 'cobblestone'}
        self.design()

    def design(self):
        if self.des: return
        self.add_houses()
        self.add_castles()
        self.add_banks()
        self.add_gex()
        self.add_roads()
        self.des = True

    def add_houses(self):
        pos = [(random.randint(-25, 25), random.randint(-25, 25)) for _ in range(12)]
        for hx, hz in pos:
            if abs(hx) < 8 and abs(hz) < 8: continue
            for dx in range(-3, 4):
                for dz in range(-3, 4):
                    self.add_structure('house_foundation', hx + dx, hz + dz, 2, self.mat['foundation'])
            for y in range(1, 3):
                for dx in range(-3, 4):
                    for dz in range(-3, 4):
                        if abs(dx) == 3 or abs(dz) == 3:
                            self.add_structure('house_wall', hx + dx, hz + dz, 2 + y, self.mat['house_walls'])
            for dx in range(-2, 3):
                for dz in range(-2, 3):
                    self.add_structure('house_roof', hx + dx, hz + dz, 5, self.mat['house_roof'])

    def add_castles(self):
        for cx, cz in [(-20, -20), (20, 20)]:
            for y in range(6):
                for dx in range(-6, 7):
                    for dz in range(-6, 7):
                        if abs(dx) == 6 or abs(dz) == 6:
                            self.add_structure('castle_wall', cx + dx, cz + dz, 2 + y, self.mat['castle_walls'])
            for tx, tz in [(-6, -6), (6, -6), (6, 6), (-6, 6)]:
                for y in range(8):
                    for dx in range(-1, 2):
                        for dz in range(-1, 2):
                            self.add_structure('castle_tower', cx + tx + dx, cz + tz + dz, 2 + y, self.mat['castle_towers'])

    def add_banks(self):
        for bx, bz in [(-10, 0), (10, 0)]:
            for y in range(4):
                for dx in range(-3, 4):
                    for dz in range(-3, 4):
                        if y < 3 and (abs(dx) == 3 or abs(dz) == 3):
                            self.add_structure('bank_wall', bx + dx, bz + dz, 2 + y, self.mat['bank_walls'])
            for dx in range(-3, 4):
                for dz in range(-3, 4):
                    self.add_structure('bank_roof', bx + dx, bz + dz, 6, self.mat['bank_roof'])

    def add_gex(self):
        for y in range(4):
            for dx in range(-4, 5):
                for dz in range(-4, 5):
                    if y < 3 and (abs(dx) == 4 or abs(dz) == 4):
                        self.add_structure('gex_wall', dx, dz, 2 + y, self.mat['gex_walls'])
        for dx in range(-4, 5):
            for dz in range(-4, 5):
                self.add_structure('gex_roof', dx, dz, 6, self.mat['gex_roof'])

    def add_roads(self):
        for x in range(-30, 31, 3):
            self.add_structure('road', x, 0, 2, self.mat['roads'])
        for z in range(-30, 31, 3):
            self.add_structure('road', 0, z, 2, self.mat['roads'])

    def add_structure(self, st, x, z, y, mat):
        wx, wz = self.cx + x, self.cz + z
        self.st.append({'type': st, 'x': wx, 'y': y, 'z': wz, 'material': mat})

    def get_structures_for_chunk(self, cx, cz, cs=16):
        csx, cex = cx * cs, cx * cs + cs - 1
        csz, cez = cz * cs, cz * cs + cs - 1
        return [s for s in self.st if csx <= s['x'] <= cex and csz <= s['z'] <= cez]

    def affects_chunk(self, cx, cz, cs=16):
        csx, cex = cx * cs, cx * cs + cs - 1
        csz, cez = cz * cs, cz * cs + cs - 1
        tminx, tmaxx = self.cx - self.sz//2, self.cx + self.sz//2
        tminz, tmaxz = self.cz - self.sz//2, self.cz + self.sz//2
        return not (tmaxx < csx or tminx > cex or tmaxz < csz or tminz > cez)

class TownManager:
    def __init__(self):
        self.towns = []
        self.gr = set()

    def should_gen(self, cx, cz):
        rx, rz = cx // 8, cz // 8
        rk = (rx, rz)
        if rk in self.gr: return None
        self.gr.add(rk)
        tcx, tcz = rx * 8 * 16 + 64, rz * 8 * 16 + 64
        if any(math.sqrt((t.cx - tcx)**2 + (t.cz - tcz)**2) < 1500 for t in self.towns): return None
        random.seed(hash((rx, rz, 'town')) % 2147483647)
        if random.random() < 0.3 or (abs(tcx) < 100 and abs(tcz) < 100):
            town = Town(tcx, tcz)
            self.towns.append(town)
            print(f"🏘️ Generating town at ({tcx}, {tcz})")
            return town
        return None

    def get_structures(self, cx, cz):
        st = []
        for town in self.towns:
            if town.affects_chunk(cx, cz):
                st.extend(town.get_structures_for_chunk(cx, cz))
        return st

class TownsMod(WorldMod):
    def __init__(self):
        super().__init__("towns", "Towns Generator", "1.0")
        self.tm = TownManager()
        
    def init(self, bus):
        super().init(bus)
        
    def gen(self, cx, cz, ch):
        self.tm.should_gen(cx, cz)
        return self.tm.get_structures(cx, cz)

class ElaborateCastle:
    def __init__(self, cx, cz, bh=2):
        self.cx, self.cz, self.bh = cx, cz, bh
        self.ows, self.iws, self.ks = 48, 32, 16
        self.st, self.des = [], False
        self.mat = {'foundation': 'stone', 'walls': 'bricks', 'towers': 'stone', 'keep': 'bricks', 'decorative': 'wood', 'roof': 'planks', 'gate': 'wood'}
        self.design()

    def design(self):
        if self.des: return
        self.add_outer_walls()
        self.add_outer_towers()
        self.add_inner_walls()
        self.add_inner_towers()
        self.add_keep()
        self.add_gatehouse()
        self.add_barracks()
        self.add_chapel()
        self.add_hall()
        self.add_gardens()
        self.des = True

    def add_outer_walls(self):
        hs = self.ows // 2
        wh, wt = 8, 2
        for t in range(wt):
            for x in range(-hs, hs + 1):
                self.add_structure('wall', x, -hs + t, wh, self.mat['walls'])
                self.add_structure('wall', x, hs - t, wh, self.mat['walls'])
            for z in range(-hs + wt, hs - wt + 1):
                self.add_structure('wall', hs - t, z, wh, self.mat['walls'])
                self.add_structure('wall', -hs + t, z, wh, self.mat['walls'])

    def add_outer_towers(self):
        hs = self.ows // 2
        th, ts = 12, 4
        for tx, tz in [(-hs, -hs), (hs, -hs), (hs, hs), (-hs, hs)]:
            self.add_tower(tx, tz, ts, th + 3, self.mat['towers'])
        for tx, tz in [(0, -hs), (0, hs), (-hs, 0), (hs, 0), (-hs//2, -hs), (hs//2, -hs), (-hs//2, hs), (hs//2, hs)]:
            self.add_tower(tx, tz, 3, th, self.mat['towers'])

    def add_inner_walls(self):
        hs = self.iws // 2
        wh = 6
        for x in range(-hs, hs + 1):
            if abs(x) > 3:
                self.add_structure('wall', x, -hs, wh, self.mat['walls'])
                self.add_structure('wall', x, hs, wh, self.mat['walls'])
        for z in range(-hs + 1, hs):
            if abs(z) > 3:
                self.add_structure('wall', hs, z, wh, self.mat['walls'])
                self.add_structure('wall', -hs, z, wh, self.mat['walls'])

    def add_inner_towers(self):
        hs = self.iws // 2
        th = 10
        for tx, tz in [(-hs, -hs), (hs, -hs), (hs, hs), (-hs, hs)]:
            self.add_tower(tx, tz, 3, th, self.mat['towers'])

    def add_keep(self):
        kh = 20
        kr = self.ks // 2
        for level in range(kh):
            lr = max(2, kr - (level - (kh - 4))) if level > kh - 4 else kr
            for x in range(-lr, lr + 1):
                for z in range(-lr, lr + 1):
                    d = math.sqrt(x*x + z*z)
                    if d <= lr and d >= lr - 1:
                        mat = self.mat['keep'] if level < kh - 2 else self.mat['decorative']
                        self.add_structure('keep', x, z, self.bh + level, mat)
        for step in range(4):
            self.add_structure('stairs', 0, kr + 1 + step, self.bh + step, self.mat['walls'])

    def add_gatehouse(self):
        hs = self.ows // 2
        gx, gz = 0, -hs - 1
        for to in [-3, 3]:
            self.add_tower(gx + to, gz, 4, 10, self.mat['towers'])
        for x in range(-2, 3):
            for z in range(-2, 1):
                for y in range(6):
                    mat = self.mat['gate'] if y < 4 else self.mat['walls']
                    self.add_structure('gate', gx + x, gz + z, self.bh + y, mat)
        self.add_structure('gate_opening', gx, gz + 1, self.bh, 'air')

    def add_barracks(self):
        for bx, bz, w, l in [(-20, -15, 6, 8), (15, -20, 8, 6), (-20, 15, 6, 8), (15, 18, 8, 6)]:
            self.add_building(bx, bz, w, l, 4, self.mat['walls'])

    def add_chapel(self):
        cx, cz = -10, 8
        cw, cl, ch = 6, 12, 8
        self.add_building(cx, cz, cw, cl, ch, self.mat['walls'])
        for y in range(ch, ch + 6):
            ss = max(1, 3 - (y - ch))
            for x in range(-ss, ss + 1):
                for z in range(-ss, ss + 1):
                    if abs(x) + abs(z) <= ss:
                        self.add_structure('spire', cx + x, cz + z, self.bh + y, self.mat['decorative'])

    def add_hall(self):
        hx, hz = 8, -5
        hw, hl, hh = 10, 16, 6
        self.add_building(hx, hz, hw, hl, hh, self.mat['walls'])
        for rl in range(2):
            ro = rl + 1
            for x in range(-hw//2 + ro, hw//2 - ro + 1):
                for z in range(-hl//2 + ro, hl//2 - ro + 1):
                    self.add_structure('roof', hx + x, hz + z, self.bh + hh + rl, self.mat['roof'])

    def add_gardens(self):
        for gx, gz in [(-5, -5), (5, -5), (-5, 5), (5, 5), (0, -8), (0, 8), (-8, 0), (8, 0)]:
            for x in range(-2, 3):
                for z in range(-2, 3):
                    if abs(x) + abs(z) <= 2:
                        self.add_structure('garden', gx + x, gz + z, self.bh + 1, self.mat['roof'])

    def add_tower(self, cx, cz, sz, h, mat):
        for level in range(h):
            for x in range(-sz//2, sz//2 + 1):
                for z in range(-sz//2, sz//2 + 1):
                    if level < 2 or abs(x) == sz//2 or abs(z) == sz//2:
                        self.add_structure('tower', cx + x, cz + z, self.bh + level, mat)
        rm = self.mat['roof'] if mat != self.mat['decorative'] else mat
        for x in range(-sz//2, sz//2 + 1):
            for z in range(-sz//2, sz//2 + 1):
                if abs(x) + abs(z) <= sz//2:
                    self.add_structure('tower_roof', cx + x, cz + z, self.bh + h, rm)

    def add_building(self, cx, cz, w, l, h, mat):
        for level in range(h):
            for x in range(-w//2, w//2 + 1):
                for z in range(-l//2, l//2 + 1):
                    if level < 1 or abs(x) == w//2 or abs(z) == l//2:
                        self.add_structure('building', cx + x, cz + z, self.bh + level, mat)

    def add_structure(self, st, x, z, y, mat):
        wx, wz = self.cx + x, self.cz + z
        self.st.append({'type': st, 'x': wx, 'y': y, 'z': wz, 'material': mat})

    def get_structures_for_chunk(self, cx, cz, cs=16):
        csx, cex = cx * cs, cx * cs + cs - 1
        csz, cez = cz * cs, cz * cs + cs - 1
        return [s for s in self.st if csx <= s['x'] <= cex and csz <= s['z'] <= cez]

    def get_bounds(self):
        hs = self.ows // 2
        return {'min_x': self.cx - hs - 5, 'max_x': self.cx + hs + 5, 'min_z': self.cz - hs - 5, 'max_z': self.cz + hs + 5}

    def affects_chunk(self, cx, cz, cs=16):
        bounds = self.get_bounds()
        csx, cex = cx * cs, cx * cs + cs - 1
        csz, cez = cz * cs, cz * cs + cs - 1
        return not (bounds['max_x'] < csx or bounds['min_x'] > cex or bounds['max_z'] < csz or bounds['min_z'] > cez)

class CastleManager:
    def __init__(self):
        self.castles, self.sp, self.md, self.gr = [], 0.08, 200, set()

    def should_gen(self, cx, cz):
        rx, rz = cx // 4, cz // 4
        rk = (rx, rz)
        if rk in self.gr: return None
        self.gr.add(rk)
        ccx, ccz = rx * 4 * 16 + 32, rz * 4 * 16 + 32
        for ec in self.castles:
            if math.sqrt((ec.cx - ccx)**2 + (ec.cz - ccz)**2) < self.md:
                return None
        random.seed(hash((rx, rz, 'castle')) % 2147483647)
        if random.random() < self.sp:
            castle = ElaborateCastle(ccx, ccz)
            self.castles.append(castle)
            print(f"🏰 Generating elaborate castle at ({ccx}, {ccz})")
            return castle
        return None

    def get_structures(self, cx, cz):
        st = []
        for castle in self.castles:
            if castle.affects_chunk(cx, cz):
                st.extend(castle.get_structures_for_chunk(cx, cz))
        return st

class CastlesMod(WorldMod):
    def __init__(self):
        super().__init__("castles", "Castle Generator", "1.0")
        self.cm = CastleManager()
        
    def init(self, bus):
        super().init(bus)
        
    def gen(self, cx, cz, ch):
        self.cm.should_gen(cx, cz)
        return self.cm.get_structures(cx, cz)

class LakesMod(WorldMod):
    def __init__(self):
        super().__init__("lakes", "Lakes & Beaches", "1.0")
        
    def init(self, bus):
        super().init(bus)
        
    def gen(self, cx, cz, ch):
        structures = []
        random.seed(hash((cx, cz, 'lakes_mod')) % 2147483647)
        
        if random.random() < 0.03:
            lx, lz = random.randint(2, 13), random.randint(2, 13)
            lr = random.randint(4, 7)
            lwx, lwz = cx * 16 + lx, cz * 16 + lz
            
            for dx in range(-lr - 4, lr + 5):
                for dz in range(-lr - 4, lr + 5):
                    wx, wz = lwx + dx, lwz + dz
                    dist = math.sqrt(dx * dx + dz * dz)
                    
                    if dist <= lr:
                        structures.append({
                            'type': 'lake_water',
                            'x': wx, 'y': 1, 'z': wz,
                            'material': 'water'
                        })
                        structures.append({
                            'type': 'lake_water',
                            'x': wx, 'y': 2, 'z': wz,
                            'material': 'water'
                        })
                    elif dist <= lr + 3:
                        for y in range(4):
                            structures.append({
                                'type': 'beach_sand',
                                'x': wx, 'y': y, 'z': wz,
                                'material': 'sand'
                            })
                        
                        if random.random() < 0.2 and dist > lr + 2:
                            dh = random.randint(1, 2)
                            for dy in range(dh):
                                structures.append({
                                    'type': 'dune',
                                    'x': wx, 'y': 4 + dy, 'z': wz,
                                    'material': 'sand'
                                })
        
        return structures

class TextureAtlas:
    def __init__(self, tpr=8, ts=32):
        self.tpr, self.ts, self.rows = tpr, ts, 8
        self.w, self.h = self.tpr * self.ts, self.rows * self.ts
        self.coords, self.tex = {}, None
        self.mode = 'normal'

    
    def bind(self):
        """Bind texture atlas"""
        if self.tex and self.tex.id:
            glEnable(GL_TEXTURE_2D)
            glBindTexture(GL_TEXTURE_2D, self.tex.id)
            return True
        return False

    def load_textures(self, block_textures):
        print(f"🔍 Loading textures from assets folder...")
        possible_dirs = ["assets", "./assets", os.path.join(os.getcwd(), "assets"), os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets"), "../assets", "textures", "./textures"]
        assets_dir = None
        for td in possible_dirs:
            if os.path.exists(os.path.abspath(td)):
                assets_dir = os.path.abspath(td)
                break
        if not assets_dir:
            assets_dir = os.path.join(os.getcwd(), "assets")
            os.makedirs(assets_dir, exist_ok=True)
        
        aw, ah = self.w, self.h
        ad = [0] * (aw * ah * 4)
        bt = list(block_textures.keys())
        lc = 0
        
        for i, bt in enumerate(bt):
            if i >= self.tpr * self.rows: break
            r, c = i // self.tpr, i % self.tpr
            u1, v1, u2, v2 = c / self.tpr + 0.005, r / self.rows + 0.005, (c + 1) / self.tpr - 0.005, (r + 1) / self.rows - 0.005
            self.coords[bt] = (u1, v1, u2, v2)
            
            td, tl = None, False
            for fn in block_textures[bt]:
                fp = os.path.join(assets_dir, fn)
                if os.path.exists(fp):
                    try:
                        ti = pyglet.image.load(fp)
                        tgl = ti.get_texture()
                        id = tgl.get_image_data()
                        rd = id.get_data('RGBA', id.width * 4)
                        if id.width != self.ts or id.height != self.ts:
                            rd = self.resize_image(rd, id.width, id.height, self.ts, self.ts)
                        td, tl, lc = list(rd), True, lc + 1
                        break
                    except: continue
            
            if not tl:
                td = list(self.create_procedural_texture(bt))
            
            if td:
                sx, sy = c * self.ts, r * self.ts
                for ty in range(self.ts):
                    for tx in range(self.ts):
                        si, dx, dy = (ty * self.ts + tx) * 4, sx + tx, sy + ty
                        di = (dy * aw + dx) * 4
                        if si + 3 < len(td) and di + 3 < len(ad) and dx < aw and dy < ah:
                            ad[di:di+4] = td[si:si+4]
        
        try:
            ab = bytes(ad)
            ai = pyglet.image.ImageData(aw, ah, 'RGBA', ab)
            self.tex = ai.get_texture()
            if self.tex and self.tex.id:
                glBindTexture(GL_TEXTURE_2D, self.tex.id)
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
                print(f"🎯 TEXTURE ATLAS CREATED: {lc}/{len(bt)} loaded, {aw}x{ah}, ID: {self.tex.id}")
            else: raise Exception("Failed to create OpenGL texture")
        except Exception as e:
            print(f"❌ Atlas creation failed: {e}")
            self.create_fallback_atlas(bt)

    def set_mode(self, mode):
        self.mode = mode
        if mode == '8bit':
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
        elif mode == '16bit':
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
        else:
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)

    def get_fallback_color(self, bt):
        colors = {
            'grass': (50, 200, 50, 255), 'grass_cover': (80, 220, 80, 200), 'dirt': (139, 69, 19, 255), 
            'stone': (128, 128, 128, 255), 'mountain_stone': (90, 90, 100, 255), 'wood': (153, 76, 25, 255), 
            'jungle_wood': (101, 67, 33, 255), 'dark_wood': (83, 53, 10, 255), 'sand': (230, 204, 102, 255), 
            'red_sand': (180, 100, 60, 255), 'water': (25, 76, 204, 180), 'lava': (255, 100, 0, 200), 
            'fire': (255, 100, 0, 200), 'leaves': (25, 204, 25, 180), 'jungle_leaves': (34, 139, 34, 180), 
            'pine_leaves': (0, 100, 0, 180), 'bricks': (180, 80, 70, 255), 'beenest': (255, 255, 0, 255), 
            'gold': (255, 215, 0, 255), 'planks': (160, 82, 45, 255), 'cobblestone': (100, 100, 100, 255), 
            'beets': (128, 0, 128, 255), 'carrots': (255, 165, 0, 255), 'roses': (255, 20, 147, 255), 
            'flower': (255, 192, 203, 255), 'flower2': (138, 43, 226, 255), 'wool_white': (255, 255, 255, 255), 
            'wool_red': (255, 0, 0, 255), 'wool_blue': (0, 0, 255, 255), 'wool_green': (0, 255, 0, 255), 
            'wool_yellow': (255, 255, 0, 255), 'wool_orange': (255, 165, 0, 255), 'wool_purple': (128, 0, 128, 255), 
            'wool_pink': (255, 192, 203, 255), 'lamps': (255, 255, 150, 255), 'gold_bars': (255, 215, 0, 255), 
            'golden_sword': (255, 215, 0, 255), 'snow': (240, 248, 255, 255), 'ice': (173, 216, 230, 200), 
            'clay': (160, 82, 45, 255), 'fire_strike': (255, 200, 0, 255), 'bow': (139, 69, 19, 255), 
            'arrow': (160, 82, 45, 255)
        }
        return colors.get(bt, (255, 255, 255, 255))

    def create_procedural_texture(self, bt):
        color, data = self.get_fallback_color(bt), []
        for y in range(self.ts):
            for x in range(self.ts):
                if bt == 'grass':
                    pc = [max(0, color[i] - 20) if (x + y) % 4 == 0 else min(255, color[i] + 15) if (x * 2 + y) % 6 == 0 else color[i] for i in range(3)] + [255]
                elif bt == 'dirt':
                    noise = (x * 7 + y * 11) % 5
                    pc = [max(0, color[i] - 15) if noise < 2 else min(255, color[i] + 10) if noise == 4 else color[i] for i in range(3)] + [255]
                elif bt == 'mountain_stone':
                    noise = (x * 5 + y * 13) % 7
                    pc = [max(0, color[i] - 25) if noise < 3 else min(255, color[i] + 15) if noise == 6 else color[i] for i in range(3)] + [255]
                elif bt == 'red_sand':
                    noise = (x * 3 + y * 7) % 4
                    pc = [max(0, color[i] - 10) if noise < 2 else min(255, color[i] + 5) if noise == 3 else color[i] for i in range(3)] + [255]
                elif bt == 'bricks':
                    bx, by = x % 8, y % 4
                    pc = [120, 60, 50, 255] if by == 0 or by == 3 or bx == 0 or bx == 7 else color
                elif bt == 'water':
                    wave = int(math.sin(x * 0.3) * math.cos(y * 0.3) * 20)
                    pc = [max(0, min(255, color[i] + wave)) for i in range(3)] + [180]
                elif bt == 'lava':
                    glow = int(math.sin(x * 0.2) * math.cos(y * 0.2) * 40) + random.randint(-10, 10)
                    pc = [max(0, min(255, color[i] + glow)) for i in range(3)] + [200]
                elif bt == 'ice':
                    shimmer = int(math.sin(x * 0.5) * math.cos(y * 0.5) * 30)
                    pc = [max(0, min(255, color[i] + shimmer)) for i in range(3)] + [200]
                elif bt == 'snow':
                    sparkle = 255 if (x + y) % 8 == 0 else color[0]
                    pc = [sparkle, sparkle, min(255, sparkle + 10), 255]
                elif bt == 'leaves':
                    organic = (x * 7 + y * 11) % 6
                    pc = [0, 0, 0, 0] if organic < 2 else [max(0, color[i] - 30) if (x + y) % 3 == 0 else color[i] for i in range(3)] + [180]
                else:
                    pc = [max(0, color[i] - 20) if (x + y) % 4 == 0 else color[i] for i in range(3)] + [255]
                data.extend(pc)
        return bytes(data)

    def resize_image(self, data, ow, oh, nw, nh):
        try:
            if len(data) != ow * oh * 4: return bytes([255, 255, 255, 255] * nw * nh)
            od, nd = list(data), []
            xs, ys = ow / nw, oh / nh
            for ny in range(nh):
                for nx in range(nw):
                    ox, oy = max(0, min(ow - 1, int(nx * xs))), max(0, min(oh - 1, int(ny * ys)))
                    oi = (oy * ow + ox) * 4
                    nd.extend(od[oi:oi + 4] if oi + 3 < len(od) else [255, 255, 255, 255])
            return bytes(nd)
        except: return bytes([255, 255, 255, 255] * nw * nh)

    def create_fallback_atlas(self, bt):
        self.coords = {}
        for i, bt in enumerate(bt):
            if i >= self.tpr * self.rows: break
            r, c = i // self.tpr, i % self.tpr
            u1, v1, u2, v2 = c / self.tpr + 0.005, r / self.rows + 0.005, (c + 1) / self.tpr - 0.005, (r + 1) / self.rows - 0.005
            self.coords[bt] = (u1, v1, u2, v2)
        
        data = []
        for r in range(self.rows):
            for tr in range(self.ts):
                rd = []
                for c in range(self.tpr):
                    bi = r * self.tpr + c
                    if bi < len(bt):
                        bt = bt[bi]
                        btd = self.create_procedural_texture(bt)
                        si, ei = tr * self.ts * 4, tr * self.ts * 4 + self.ts * 4
                        rd.extend(btd[si:ei] if ei <= len(btd) else self.get_fallback_color(bt) * self.ts)
                    else: rd.extend([0, 0, 0, 0] * self.ts)
                data.extend(rd)
        
        image = pyglet.image.ImageData(self.w, self.h, 'RGBA', bytes(data))
        self.tex = image.get_texture()
        glBindTexture(GL_TEXTURE_2D, self.tex.id)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)

    def bind(self):
        if self.tex:
            glEnable(GL_TEXTURE_2D)
            glBindTexture(GL_TEXTURE_2D, self.tex.id)

    def get_uv(self, bt):
        if bt in self.coords: return self.coords[bt]
        if bt == 'bricks' and 'brick' in self.coords: return self.coords['brick']
        if bt == 'brick' and 'bricks' in self.coords: return self.coords['bricks']
        return self.coords.get('grass', (0, 0, 1, 1))


class Skybox:
    def __init__(self):
        self.cp, self.cc = [], 6
        self.gen_clouds()

    def gen_clouds(self):
        self.cp = []
        for i in range(self.cc):
            a, alt, d = random.uniform(0, 2 * math.pi), random.uniform(0.2, 0.6), 80
            x, y, z = math.cos(a) * math.sin(alt) * d, math.cos(alt) * d * 0.4 + 15, math.sin(a) * math.sin(alt) * d
            self.cp.append({'x': x, 'y': y, 'z': z, 'size': random.uniform(2, 5), 'speed': random.uniform(0.01, 0.03), 'offset': random.uniform(0, 2 * math.pi)})

    def get_sky_color(self, tod):
        sa, sh = (tod - 0.25) * 2 * math.pi, math.sin((tod - 0.25) * 2 * math.pi)
        if sh > 0.2: return (0.4, 0.7, 1.0)
        elif sh > -0.2:
            tf = (sh + 0.2) / 0.4
            return (0.6, 0.4 + 0.3 * tf, 0.8 + 0.2 * tf)
        else: return (0.05, 0.05, 0.2)

    def draw(self, cx, cy, cz, tod):
        ol, ot, oc = glIsEnabled(GL_LIGHTING), glIsEnabled(GL_TEXTURE_2D), glIsEnabled(GL_CULL_FACE)
        try: odm = glGetBooleanv(GL_DEPTH_WRITEMASK)[0] if isinstance(glGetBooleanv(GL_DEPTH_WRITEMASK), (list, tuple, bytes)) else glGetBooleanv(GL_DEPTH_WRITEMASK)
        except: odm = GL_TRUE
        try:
            glDepthMask(GL_FALSE)
            glDisable(GL_LIGHTING)
            glDisable(GL_TEXTURE_2D)
            glDisable(GL_CULL_FACE)
            glPushMatrix()
            glTranslatef(cx, cy, cz)
            self.draw_sky_dome(tod)
            if int(time.time() * 2) % 5 == 0: self.draw_clouds(tod)
            glPopMatrix()
        finally:
            glDepthMask(odm)
            if ol: glEnable(GL_LIGHTING)
            if ot: glEnable(GL_TEXTURE_2D)
            if oc: glEnable(GL_CULL_FACE)

    def draw_sky_dome(self, tod):
        sc, hc = self.get_sky_color(tod), [c * 0.7 for c in self.get_sky_color(tod)]
        glBegin(GL_TRIANGLE_FAN)
        glColor3f(*sc)
        glVertex3f(0, 40, 0)
        for i in range(9):
            a = i * 2 * math.pi / 8
            x, z, y = math.cos(a) * 60, math.sin(a) * 60, -5
            glColor3f(*hc)
            glVertex3f(x, y, z)
        glEnd()

    def draw_clouds(self, tod):
        ct = time.time()
        glEnable(GL_BLEND)
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
        sc = self.get_sky_color(tod)
        cc = (1.0, 1.0, 1.0, 0.5) if sc[0] + sc[1] + sc[2] > 1.5 else (0.4, 0.4, 0.5, 0.4)
        glColor4f(*cc)
        for i, c in enumerate(self.cp[:3]):
            ax = c['x'] + math.sin(ct * c['speed'] + c['offset']) * 3
            az = c['z'] + math.cos(ct * c['speed'] + c['offset']) * 2
            glPushMatrix()
            glTranslatef(ax, c['y'], az)
            self.draw_sphere(c['size'])
            glPopMatrix()
        glDisable(GL_BLEND)

    def draw_sphere(self, r):
        s = 4
        glBegin(GL_TRIANGLES)
        for i in range(s):
            l1, l2 = math.pi * (i / s - 0.5), math.pi * ((i + 1) / s - 0.5)
            for j in range(s):
                lo1, lo2 = 2 * math.pi * j / s, 2 * math.pi * (j + 1) / s
                x1, y1, z1 = r * math.cos(l1) * math.cos(lo1), r * math.sin(l1), r * math.cos(l1) * math.sin(lo1)
                x2, y2, z2 = r * math.cos(l2) * math.cos(lo1), r * math.sin(l2), r * math.cos(l2) * math.sin(lo1)
                x3, y3, z3 = r * math.cos(l1) * math.cos(lo2), r * math.sin(l1), r * math.cos(l1) * math.sin(lo2)
                glVertex3f(x1, y1, z1)
                glVertex3f(x2, y2, z2)
                glVertex3f(x3, y3, z3)
        glEnd()

class DayNightCycle:
    def __init__(self):
        self.time, self.dl, self.sb = 0.25, 1200.0, Skybox()
        self.lut, self.ui = 0, 0.1
        self.clc = self.csc = None

    def update(self, dt):
        try:
            # Limit update frequency for better performance
            if hasattr(self, '_last_update'):
                if time.time() - self._last_update < 0.05:  # Limit to 20 FPS
                    return
            self._last_update = time.time()
            
            st = time.time()
            
            # Reduce frequency of expensive operations
            frame_count = getattr(self, '_frame_count', 0) + 1
            self._frame_count = frame_count
            
            # Only update day/night cycle every 5 frames
            if frame_count % 5 == 0 and hasattr(self, 'dnc'):
                try:
                    self.dnc.update(dt)
                except Exception:
                    pass
                    
            # Only update sky color every 10 frames
            if frame_count % 10 == 0 and hasattr(self, 'dnc'):
                try:
                    sc = self.dnc.get_sky_color()
                    glClearColor(*sc, 1.0)
                except Exception:
                    pass
                    
            if hasattr(self, 'mc') and self.mc:
                # Only update chunks when player moves significantly
                if hasattr(self, 'lpp') and hasattr(self, 'cam'):
                    dx, dz = abs(self.cam.x - self.lpp[0]), abs(self.cam.z - self.lpp[1])
                    if dx > 16 or dz > 16:  # Increased threshold
                        try:
                            self.cm.update_chunks(self.cam.x, self.cam.z, self.mod_loader)
                            self.lpp = (self.cam.x, self.cam.z)
                        except Exception:
                            pass
                        
                try:
                    self.cam.update(dt, self.keys, self.cm, self.pb)
                    self.pm.update(dt, self.cam.vx, self.cam.vz)
                except Exception:
                    pass
                
                # Reduce mod event frequency
                if frame_count % 3 == 0 and hasattr(self, 'mod_loader'):
                    try:
                        self.mod_loader.bus.emit('player_update', self.cam.x, self.cam.y, self.cam.z, self.cam.ry, self.cam.rx, dt)
                        self.mod_loader.bus.emit('update', dt, self)
                    except Exception:
                        pass
            
            # Update liquids less frequently
            if frame_count % 2 == 0 and hasattr(self, 'flowing_liquid'):
                try:
                    self.flowing_liquid.update(dt)
                except Exception:
                    pass
            
            # Update projectiles every frame (important for gameplay)
            if hasattr(self, 'projectiles'):
                try:
                    for projectile in self.projectiles[:]:
                        if not projectile.update(dt, self):
                            self.projectiles.remove(projectile)
                except Exception:
                    pass
            
            self.ft = time.time() - st
            fps = 1.0 / max(0.001, self.ft)
            
            # Auto-enable emergency mode for very low FPS
            if fps < 15 and not getattr(self, 'ema', False) and frame_count > 60:
                try:
                    self.activate_emergency_mode()
                except Exception:
                    pass
                    
        except Exception as e:
            print(f"⚠️ Update error: {e}")
            pass

    def get_light_color(self):
        if self.clc: return self.clc
        sa, sh = (self.time - 0.25) * 2 * math.pi, math.sin((self.time - 0.25) * 2 * math.pi)
        if sh > 0:
            i = min(1.0, max(0.7, sh + 0.3))
            color = [i * 0.95, i * 0.95, i * 0.97, 1.0]
        else:
            i = 0.4 + 0.3 * abs(sh)
            color = [i * 0.8, i * 0.8, i * 0.9, 1.0]
        self.clc = color
        return color

    def get_sky_color(self):
        if self.csc: return self.csc
        color = self.sb.get_sky_color(self.time)
        self.csc = color
        return color

    def draw_skybox(self, cx, cy, cz):
        if int(time.time() * 4) % 3 != 0: return
        self.sb.draw(cx, cy, cz, self.time)

    def apply_lighting(self):
        lc = self.get_light_color()
        if not glIsEnabled(GL_LIGHTING):
            glEnable(GL_LIGHTING)
            glEnable(GL_LIGHT0)
        ambient = [c * 0.8 for c in lc[:3]] + [1.0]
        glLightfv(GL_LIGHT0, GL_AMBIENT, (GLfloat * 4)(*ambient))
        lp, diffuse = [0.5, 0.8, 0.3, 0.0], [c * 0.6 for c in lc[:3]] + [1.0]
        glLightfv(GL_LIGHT0, GL_POSITION, (GLfloat * 4)(*lp))
        glLightfv(GL_LIGHT0, GL_DIFFUSE, (GLfloat * 4)(*diffuse))
        if not glIsEnabled(GL_COLOR_MATERIAL):
            glEnable(GL_COLOR_MATERIAL)
            glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE)

class Chunk:
    def __init__(self, cx, cz, sz=16):
        self.cx, self.cz, self.sz = cx, cz, sz
        self.blocks, self.sh = {}, {}
        self.trees, self.bn, self.crops, self.fl = [], [], [], []
        self.castle, self.cs, self.bees = None, [], []
        self.mesh, self.rebuild, self.ta, self.fim = OptimizedChunkMesh(), True, None, False
        self.biome_data = {}
        self.terrain_gen = MinecraftTerrainGenerator()
        self.base_level = 0
        self.gen_terrain()

    def gen_terrain(self):
        """Generate Minecraft-style terrain with 128-block height limit"""
        print(f"🌍 Generating Minecraft-style terrain for chunk ({self.cx}, {self.cz})")
        
        if not hasattr(self, 'terrain_gen'):
            self.terrain_gen = MinecraftTerrainGenerator()
        
        # Generate terrain data for the chunk
        for x in range(self.sz):
            for z in range(self.sz):
                wx, wz = self.cx * self.sz + x, self.cz * self.sz + z
                
                # Generate height map
                surface_height = self.terrain_gen.generate_terrain_height(wx, wz)
                
                # Get biome information
                biome_data = self.terrain_gen.get_biome_info(wx, wz, surface_height)
                
                # Store height and biome data
                self.sh[(wx, wz)] = surface_height
                self.biome_data[(wx, wz)] = biome_data
                
                # Generate blocks from bedrock to surface
                for y in range(0, min(surface_height + 5, 128)):
                    # Check for caves first
                    if self.terrain_gen.generate_caves(wx, y, wz):
                        continue  # Leave as air/water
                    
                    # Check for ores
                    ore_type = self.terrain_gen.generate_ore_deposits(wx, y, wz)
                    if ore_type:
                        self.blocks[(wx, y, wz)] = ore_type
                        continue
                    
                    # Get regular block type
                    block_type = self.terrain_gen.get_block_type(wx, y, wz, surface_height, biome_data)
                    if block_type:
                        self.blocks[(wx, y, wz)] = block_type
                
                # Add water at sea level if needed
                if surface_height < self.terrain_gen.SEA_LEVEL:
                    for y in range(surface_height + 1, self.terrain_gen.SEA_LEVEL + 1):
                        if (wx, y, wz) not in self.blocks:
                            self.blocks[(wx, y, wz)] = 'water'
        
        # Generate features
        self.gen_minecraft_features()
        
        self.rebuild = True
        print(f"✅ Minecraft-style terrain generated for chunk ({self.cx}, {self.cz})")
    
    def gen_minecraft_features(self):
        """Generate Minecraft-style features (trees, flowers, etc.)"""
        self.trees = []
        self.fl = []
        self.grass_cover = []
        
        for (wx, wz), biome_data in self.biome_data.items():
            surface_height = self.sh.get((wx, wz), 0)
            
            # Skip if underwater
            if surface_height < self.terrain_gen.SEA_LEVEL:
                continue
            
            biome_name = biome_data.get('name', 'plains')
            
            # Generate trees based on biome
            tree_chance = biome_data.get('tree_chance', 0.0)
            if random.random() < tree_chance:
                # Determine tree type based on biome
                if biome_name in ['taiga', 'snowy_mountains']:
                    tree_type = 'pine'
                elif biome_name == 'swamp':
                    tree_type = 'dark_oak'
                else:
                    tree_type = 'oak'
                
                # Check if space is available
                if not any(abs(wx - tx) + abs(wz - tz) < 6 for tx, tz, _ in self.trees):
                    self.trees.append((wx, wz, tree_type))
            
            # Generate flowers
            flower_chance = biome_data.get('flower_chance', 0.0)
            if random.random() < flower_chance:
                flower_type = random.choice(['flower', 'flower2', 'roses'])
                if not any(abs(wx - fx) + abs(wz - fz) < 3 for fx, fz, _ in self.fl):
                    self.fl.append((wx, wz, flower_type))
            
            # Generate grass cover
            grass_chance = biome_data.get('grass_chance', 0.0)
            if random.random() < grass_chance:
                if (biome_data.get('surface_block') == 'grass' and 
                    not any(abs(wx - gx) + abs(wz - gz) < 2 for gx, gz in getattr(self, 'grass_cover', []))):
                    self.grass_cover.append((wx, wz))
    def gen_smooth_features(self):
        """Generate features with smooth distribution"""
        self.trees = []
        self.fl = []
        self.grass_cover = []
        
        for (wx, wz), biome_info in self.biome_data.items():
            biome = biome_info['biome']
            surface_height = self.sh.get((wx, wz), 0)
            
            # Trees with biome-appropriate placement
            tree_chance = 0
            tree_type = 'oak'
            
            if biome in ['forest']:
                tree_chance = 12
            elif biome in ['coastal_plains', 'plains']:
                tree_chance = 6
            elif biome in ['foothills']:
                tree_chance = 8
                tree_type = 'pine'
            elif biome in ['mountains']:
                tree_chance = 4
                tree_type = 'pine'
            
            if tree_chance > 0 and hash((wx, wz, 'tree')) % 100 < tree_chance:
                self.trees.append((wx, wz, tree_type))
            
            # Flowers with smooth distribution
            if biome in ['plains', 'coastal_plains', 'forest'] and hash((wx, wz, 'flower')) % 100 < 8:
                flower_type = 'flower' if hash((wx, wz)) % 2 == 0 else 'roses'
                self.fl.append((wx, wz, flower_type))
            
            # Grass cover
            if biome in ['plains', 'coastal_plains', 'forest', 'foothills'] and hash((wx, wz, 'grass')) % 100 < 25:
                if (wx, int(surface_height), wz) in self.blocks and self.blocks[(wx, int(surface_height), wz)] == 'grass':
                    self.grass_cover.append((wx, wz))
    

    def build_mesh(self, block_registry):
        """Build mesh with proper texture handling"""
        if not self.rebuild:
            return
        
        # Clear existing mesh
        if not hasattr(self, 'mesh'):
            self.mesh = OptimizedChunkMesh()
        self.mesh.clear()
        
        # Separate blocks by rendering type
        solid_blocks = []
        transparent_blocks = []
        billboard_blocks = []
        
        for (x, y, z), block_type in self.blocks.items():
            if self.is_billboard_block(block_type):
                billboard_blocks.append(((x, y, z), block_type))
            elif self.is_transparent_block(block_type):
                transparent_blocks.append(((x, y, z), block_type))
            else:
                solid_blocks.append(((x, y, z), block_type))
        
        # Build solid blocks with proper textures
        for (x, y, z), block_type in solid_blocks:
            u1, v1, u2, v2 = self.ta.get_uv(block_type) if self.ta else (0, 0, 1, 1)
            
            # Apply biome tinting
            tint = self.apply_biome_tinting(block_type, x, z)
            
            # Check each face for visibility
            for face_dir in ['top', 'bottom', 'front', 'back', 'left', 'right']:
                if self.is_face_visible(x, y, z, face_dir):
                    self.mesh.add_cube_face(float(x), float(y), float(z), face_dir, u1, v1, u2, v2)
        
        # Store transparent and billboard blocks for later rendering
        self.transparent_blocks = transparent_blocks
        self.billboard_blocks = billboard_blocks
        
        self.rebuild = False
    
    def draw(self, ta, block_registry, cx=0, cy=0, cz=0):
        """Draw chunk with proper texture handling"""
        # Set texture atlas
        if self.ta != ta:
            self.set_ta(ta)
        
        # Build mesh if needed
        if self.rebuild:
            self.build_mesh(block_registry)
        
        # Enable textures and set up rendering state
        glEnable(GL_TEXTURE_2D)
        glEnable(GL_DEPTH_TEST)
        glDepthFunc(GL_LESS)
        glColor4f(1.0, 1.0, 1.0, 1.0)
        
        # Bind texture atlas
        if ta and ta.tex:
            ta.bind()
        
        # Draw solid blocks
        if hasattr(self, 'mesh') and self.mesh:
            self.mesh.draw()
        else:
            # Fallback to immediate mode if mesh fails
            self.draw_immediate_mode(ta, cx, cy, cz)
        
        # Draw transparent blocks
        self.draw_transparent_blocks(ta)
        
        # Draw billboard blocks  
        self.draw_billboard_blocks(ta, cx, cy, cz)
        
        # Draw features
        self.draw_bees()
    
    def draw_immediate_mode(self, ta, cx=0, cy=0, cz=0):
        """Fallback immediate mode rendering with textures"""
        glEnable(GL_TEXTURE_2D)
        glEnable(GL_DEPTH_TEST)
        glColor4f(1.0, 1.0, 1.0, 1.0)
        
        if ta:
            ta.bind()
        
        # Draw solid blocks
        for (x, y, z), block_type in self.blocks.items():
            if not self.is_transparent_block(block_type) and not self.is_billboard_block(block_type):
                u1, v1, u2, v2 = ta.get_uv(block_type) if ta else (0, 0, 1, 1)
                
                # Apply biome tinting
                tint = self.apply_biome_tinting(block_type, x, z)
                glColor3f(*tint)
                
                # Draw textured cube
                self.draw_textured_cube(float(x), float(y), float(z), u1, v1, u2, v2)
                
                # Reset color
                glColor3f(1.0, 1.0, 1.0)
        
        # Draw trees with textures
        for tx, tz, tree_type in self.trees:
            sh = self.sh.get((tx, tz), 3)
            trunk_base = sh + 1
            
            # Tree trunk
            wu1, wv1, wu2, wv2 = ta.get_uv('wood') if ta else (0, 0, 1, 1)
            tree_height = 4
            
            for yo in range(tree_height):
                yp = trunk_base + yo
                self.draw_textured_cube(float(tx), float(yp), float(tz), wu1, wv1, wu2, wv2)
        
        # Draw beenests
        for bx, bz in self.bn:
            sh = self.sh.get((bx, bz), 3)
            bee_y = sh + 1
            u1, v1, u2, v2 = ta.get_uv('beenest') if ta else (0, 0, 1, 1)
            self.draw_textured_cube(float(bx), float(bee_y), float(bz), u1, v1, u2, v2)
        
        # Draw transparent blocks
        glEnable(GL_BLEND)
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
        glDepthMask(GL_FALSE)
        
        # Tree leaves
        for tx, tz, tree_type in self.trees:
            sh = self.sh.get((tx, tz), 3)
            trunk_base = sh + 1
            ly = trunk_base + 3
            
            lu1, lv1, lu2, lv2 = ta.get_uv('leaves') if ta else (0, 0, 1, 1)
            tint = self.apply_biome_tinting('leaves', tx, tz)
            glColor3f(*tint)
            
            # Simple leaf pattern
            for dx in [-2, -1, 0, 1, 2]:
                for dz in [-2, -1, 0, 1, 2]:
                    if abs(dx) + abs(dz) <= 3 and not (dx == 0 and dz == 0):
                        self.draw_textured_cube(float(tx + dx), float(ly), float(tz + dz), lu1, lv1, lu2, lv2)
                        if abs(dx) <= 1 and abs(dz) <= 1:
                            self.draw_textured_cube(float(tx + dx), float(ly + 1), float(tz + dz), lu1, lv1, lu2, lv2)
            
            # Top of tree
            self.draw_textured_cube(float(tx), float(ly + 2), float(tz), lu1, lv1, lu2, lv2)
            glColor3f(1.0, 1.0, 1.0)
        
        glDepthMask(GL_TRUE)
        glDisable(GL_BLEND)
        
        # Draw billboard items
        for cropx, cropz, crop_type in self.crops:
            sh = self.sh.get((cropx, cropz), 3)
            cropy = sh + 1
            u1, v1, u2, v2 = ta.get_uv(crop_type) if ta else (0, 0, 1, 1)
            tint = self.apply_biome_tinting(crop_type, cropx, cropz)
            glColor3f(*tint)
            BillboardRenderer.draw_billboard(float(cropx), float(cropy), float(cropz), 0.8, u1, v1, u2, v2, cx, cy, cz)
            glColor3f(1.0, 1.0, 1.0)
        
        for flowerx, flowerz, flower_type in self.fl:
            sh = self.sh.get((flowerx, flowerz), 3)
            flowery = sh + 1
            u1, v1, u2, v2 = ta.get_uv(flower_type) if ta else (0, 0, 1, 1)
            tint = self.apply_biome_tinting(flower_type, flowerx, flowerz)
            glColor3f(*tint)
            BillboardRenderer.draw_billboard(float(flowerx), float(flowery), float(flowerz), 0.8, u1, v1, u2, v2, cx, cy, cz)
            glColor3f(1.0, 1.0, 1.0)
        
        for grassx, grassz in getattr(self, 'grass_cover', []):
            sh = self.sh.get((grassx, grassz), 3)
            grassy = sh + 1
            u1, v1, u2, v2 = ta.get_uv('grass_cover') if ta else (0, 0, 1, 1)
            tint = self.apply_biome_tinting('grass_cover', grassx, grassz)
            glColor3f(*tint)
            BillboardRenderer.draw_billboard(float(grassx), float(grassy), float(grassz), 0.6, u1, v1, u2, v2, cx, cy, cz)
            glColor3f(1.0, 1.0, 1.0)
    
    def draw_textured_cube(self, x, y, z, u1, v1, u2, v2):
        """Draw a textured cube with proper UV coordinates"""
        glBegin(GL_QUADS)
        
        # Top face
        glNormal3f(0.0, 1.0, 0.0)
        glTexCoord2f(u1, v1); glVertex3f(x - 0.5, y + 0.5, z + 0.5)
        glTexCoord2f(u2, v1); glVertex3f(x + 0.5, y + 0.5, z + 0.5)
        glTexCoord2f(u2, v2); glVertex3f(x + 0.5, y + 0.5, z - 0.5)
        glTexCoord2f(u1, v2); glVertex3f(x - 0.5, y + 0.5, z - 0.5)
        
        # Bottom face
        glNormal3f(0.0, -1.0, 0.0)
        glTexCoord2f(u1, v1); glVertex3f(x - 0.5, y - 0.5, z - 0.5)
        glTexCoord2f(u2, v1); glVertex3f(x + 0.5, y - 0.5, z - 0.5)
        glTexCoord2f(u2, v2); glVertex3f(x + 0.5, y - 0.5, z + 0.5)
        glTexCoord2f(u1, v2); glVertex3f(x - 0.5, y - 0.5, z + 0.5)
        
        # Front face
        glNormal3f(0.0, 0.0, 1.0)
        glTexCoord2f(u1, v1); glVertex3f(x - 0.5, y - 0.5, z + 0.5)
        glTexCoord2f(u2, v1); glVertex3f(x + 0.5, y - 0.5, z + 0.5)
        glTexCoord2f(u2, v2); glVertex3f(x + 0.5, y + 0.5, z + 0.5)
        glTexCoord2f(u1, v2); glVertex3f(x - 0.5, y + 0.5, z + 0.5)
        
        # Back face
        glNormal3f(0.0, 0.0, -1.0)
        glTexCoord2f(u1, v1); glVertex3f(x + 0.5, y - 0.5, z - 0.5)
        glTexCoord2f(u2, v1); glVertex3f(x - 0.5, y - 0.5, z - 0.5)
        glTexCoord2f(u2, v2); glVertex3f(x - 0.5, y + 0.5, z - 0.5)
        glTexCoord2f(u1, v2); glVertex3f(x + 0.5, y + 0.5, z - 0.5)
        
        # Right face
        glNormal3f(1.0, 0.0, 0.0)
        glTexCoord2f(u1, v1); glVertex3f(x + 0.5, y - 0.5, z + 0.5)
        glTexCoord2f(u2, v1); glVertex3f(x + 0.5, y - 0.5, z - 0.5)
        glTexCoord2f(u2, v2); glVertex3f(x + 0.5, y + 0.5, z - 0.5)
        glTexCoord2f(u1, v2); glVertex3f(x + 0.5, y + 0.5, z + 0.5)
        
        # Left face
        glNormal3f(-1.0, 0.0, 0.0)
        glTexCoord2f(u1, v1); glVertex3f(x - 0.5, y - 0.5, z - 0.5)
        glTexCoord2f(u2, v1); glVertex3f(x - 0.5, y - 0.5, z + 0.5)
        glTexCoord2f(u2, v2); glVertex3f(x - 0.5, y + 0.5, z + 0.5)
        glTexCoord2f(u1, v2); glVertex3f(x - 0.5, y + 0.5, z - 0.5)
        
        glEnd()
    
    def is_billboard_block(self, block_type):
        """Check if block should be rendered as billboard"""
        return block_type in ['beets', 'carrots', 'roses', 'flower', 'flower2', 'grass_cover', 'fire']
    
    def is_transparent_block(self, block_type):
        """Check if block is transparent"""
        return block_type in ['water', 'lava', 'leaves', 'jungle_leaves', 'pine_leaves', 'ice']
    
    def gen_simple_features(self):
        """Generate simple trees and features"""
        self.trees = []
        self.fl = []
        self.grass_cover = []
        
        for (wx, wz), biome_info in self.biome_data.items():
            biome = biome_info['biome']
            
            # Simple tree placement
            if biome in ['plains', 'forest', 'hills'] and hash((wx, wz, 'tree')) % 100 < 8:
                self.trees.append((wx, wz, 'oak'))
            
            # Simple flowers
            if biome in ['plains', 'forest'] and hash((wx, wz, 'flower')) % 100 < 5:
                self.fl.append((wx, wz, 'flower'))
            
            # Grass cover
            if biome in ['plains', 'forest'] and hash((wx, wz, 'grass')) % 100 < 15:
                surface_height = self.sh.get((wx, wz), 0)
                if (wx, surface_height, wz) in self.blocks and self.blocks[(wx, surface_height, wz)] == 'grass':
                    self.grass_cover.append((wx, wz))    
    def generate_terrain_layers(self, wx, wz, height, biome):
        final_height = max(self.base_level, min(80, int(height)))
        self.sh[(wx, wz)] = final_height
        
        for y in range(self.base_level, final_height + 3):
            if biome == 'ocean':
                if y <= self.base_level + 2:
                    self.blocks[(wx, y, wz)] = 'clay'
                elif y <= final_height:
                    self.blocks[(wx, y, wz)] = 'water'
            elif biome == 'beach':
                if y <= final_height - 2:
                    self.blocks[(wx, y, wz)] = 'dirt'
                elif y <= final_height:
                    self.blocks[(wx, y, wz)] = 'sand'
            elif biome == 'desert':
                if y <= final_height - 3:
                    self.blocks[(wx, y, wz)] = 'stone'
                elif y <= final_height:
                    self.blocks[(wx, y, wz)] = 'sand'
            elif biome == 'canyon':
                depth_ratio = (final_height - self.base_level) / 20.0
                if y <= final_height - 10:
                    self.blocks[(wx, y, wz)] = 'stone'
                elif y <= final_height - 5:
                    self.blocks[(wx, y, wz)] = 'red_sand'
                elif y <= final_height:
                    self.blocks[(wx, y, wz)] = 'red_sand'
            elif biome == 'mountains':
                if y <= final_height - 5:
                    self.blocks[(wx, y, wz)] = 'stone'
                elif y <= final_height - 2:
                    self.blocks[(wx, y, wz)] = 'mountain_stone'
                elif y <= final_height:
                    self.blocks[(wx, y, wz)] = 'mountain_stone'
            elif biome == 'snow_peaks':
                if y <= final_height - 3:
                    self.blocks[(wx, y, wz)] = 'mountain_stone'
                elif y <= final_height:
                    self.blocks[(wx, y, wz)] = 'snow'
            elif biome == 'river':
                if y <= final_height - 2:
                    self.blocks[(wx, y, wz)] = 'clay'
                elif y <= final_height:
                    self.blocks[(wx, y, wz)] = 'water'
            else:
                if y <= final_height - 4:
                    self.blocks[(wx, y, wz)] = 'stone'
                elif y <= final_height - 1:
                    self.blocks[(wx, y, wz)] = 'dirt'
                elif y == final_height:
                    self.blocks[(wx, y, wz)] = 'grass'
    
    def gen_biome_features(self):
        for (wx, wz), biome_info in self.biome_data.items():
            biome = biome_info['biome']
            
            if biome == 'desert' and random.random() < 0.01:
                h = self.sh.get((wx, wz), 0)
                if h > 0:
                    self.crops.append((wx, wz, 'beets'))
                    
            elif biome == 'snow_peaks' and random.random() < 0.05:
                h = self.sh.get((wx, wz), 0)
                if h > 0:
                    for dy in range(1, random.randint(2, 4)):
                        self.blocks[(wx, h + dy, wz)] = 'ice'
                        
            elif biome == 'river' and random.random() < 0.1:
                h = self.sh.get((wx, wz), 0)
                if h > 0:
                    self.fl.append((wx, wz, random.choice(['flower', 'flower2'])))

    def gen_trees(self):
        self.trees = []
        random.seed(hash((self.cx, self.cz, 'trees')) % 2147483647)
        
        for (wx, wz), biome_info in self.biome_data.items():
            biome = biome_info['biome']
            h = self.sh.get((wx, wz), 0)
            
            if h < 1: continue
            
            tree_chance = 0.0
            tree_types = ['oak']
            
            if biome == 'forest':
                tree_chance = 0.04
                tree_types = ['oak', 'oak', 'jungle']
            elif biome == 'plains':
                tree_chance = 0.04
                tree_types = ['oak']
            elif biome == 'hills':
                tree_chance = 0.06
                tree_types = ['oak', 'pine']
            elif biome == 'mountains':
                tree_chance = 0.05
                tree_types = ['pine', 'pine', 'dark_wood']
            elif biome == 'snow_peaks':
                tree_chance = 0.02
                tree_types = ['pine']
            
            if random.random() < tree_chance:
                too_close = any(abs(wx - tx) + abs(wz - tz) < 8 for tx, tz, _ in self.trees)
                if not too_close:
                    tree_type = random.choice(tree_types) if tree_types else 'oak'
                    tree_type_map = {'dark_wood': 'dark_oak'}
                    display_type = tree_type_map.get(tree_type, tree_type)
                    self.trees.append((wx, wz, display_type))

    def gen_grass_cover(self):
        self.grass_cover = []
        random.seed(hash((self.cx, self.cz, 'grass_cover')) % 2147483647)
        
        for (wx, wz), biome_info in self.biome_data.items():
            biome = biome_info['biome']
            h = self.sh.get((wx, wz), 0)
            
            if h < 1: continue
            
            grass_chance = 0.0
            if biome in ['plains', 'forest', 'hills']:
                grass_chance = 0.15
            elif biome == 'river':
                grass_chance = 0.2
            
            if random.random() < grass_chance:
                too_close = (any(abs(wx - tx) + abs(wz - tz) < 3 for tx, tz, _ in self.trees) or 
                           any(abs(wx - bx) + abs(wz - bz) < 2 for bx, bz in self.bn) or 
                           any(abs(wx - cx) + abs(wz - cz) < 2 for cx, cz, _ in self.crops) or 
                           any(abs(wx - fx) + abs(wz - fz) < 2 for fx, fz, _ in self.fl))
                
                if not too_close and (wx, h, wz) in self.blocks and self.blocks[(wx, h, wz)] == 'grass':
                    self.grass_cover.append((wx, wz))

    def gen_beenests(self):
        self.bn = []
        random.seed(hash((self.cx, self.cz, 'bees')) % 2147483647)
        
        forest_positions = [(wx, wz) for (wx, wz), info in self.biome_data.items() 
                          if info['biome'] in ['forest', 'plains'] and self.sh.get((wx, wz), 0) > 1]
        
        if forest_positions and random.random() < 0.2:
            wx, wz = random.choice(forest_positions)
            too_close = any(abs(wx - bx) + abs(wz - bz) < 8 for bx, bz in self.bn)
            if not too_close:
                self.bn.append((wx, wz))

    def gen_crops(self):
        self.crops = []
        random.seed(hash((self.cx, self.cz, 'crops')) % 2147483647)
        
        suitable_positions = [(wx, wz) for (wx, wz), info in self.biome_data.items() 
                            if info['biome'] in ['plains', 'river'] and self.sh.get((wx, wz), 0) > 1]
        
        for _ in range(2):
            if suitable_positions and random.random() < 0.15:
                wx, wz = random.choice(suitable_positions)
                too_close = (any(abs(wx - tx) + abs(wz - tz) < 6 for tx, tz, _ in self.trees) or 
                           any(abs(wx - bx) + abs(wz - bz) < 4 for bx, bz in self.bn) or 
                           any(abs(wx - cx) + abs(wz - cz) < 3 for cx, cz, _ in self.crops))
                if not too_close:
                    ct = random.choice(['beets', 'carrots'])
                    self.crops.append((wx, wz, ct))

    def gen_flowers(self):
        self.fl = []
        random.seed(hash((self.cx, self.cz, 'flowers')) % 2147483647)
        
        for (wx, wz), biome_info in self.biome_data.items():
            biome = biome_info['biome']
            h = self.sh.get((wx, wz), 0)
            
            if h < 1: continue
            
            flower_chance = 0.0
            flower_types = ['flower']
            
            if biome == 'plains':
                flower_chance = 0.03
                flower_types = ['flower', 'flower2', 'roses']
            elif biome == 'forest':
                flower_chance = 0.03
                flower_types = ['flower', 'roses']
            elif biome == 'river':
                flower_chance = 0.08
                flower_types = ['flower2']
            
            if random.random() < flower_chance:
                too_close = (any(abs(wx - tx) + abs(wz - tz) < 4 for tx, tz, _ in self.trees) or 
                           any(abs(wx - bx) + abs(wz - bz) < 3 for bx, bz in self.bn) or 
                           any(abs(wx - cx) + abs(wz - cz) < 3 for cx, cz, _ in self.crops) or 
                           any(abs(wx - fx) + abs(wz - fz) < 3 for fx, fz, _ in self.fl))
                if not too_close:
                    ft = random.choice(flower_types)
                    self.fl.append((wx, wz, ft))

    def gen_castle(self):
        self.castle = None
        random.seed(hash((self.cx, self.cz, 'castle')) % 2147483647)
        if random.random() < 0.02:
            for _ in range(5):
                lx, lz = random.randint(4, self.sz - 5), random.randint(4, self.sz - 5)
                bh, suitable = None, True
                for dx in range(-3, 4):
                    for dz in range(-3, 4):
                        wx, wz = self.cx * self.sz + lx + dx, self.cz * self.sz + lz + dz
                        h = self.sh.get((wx, wz), 3)
                        if bh is None: bh = h
                        elif abs(h - bh) > 1:
                            suitable = False
                            break
                    if not suitable: break
                if suitable and bh is not None:
                    cwx, cwz = self.cx * self.sz + lx, self.cz * self.sz + lz
                    self.castle = {'x': cwx, 'z': cwz, 'size': 6, 'height': bh}
                    break

    def is_face_visible(self, x, y, z, fd):
        ap = {'top': (x, y + 1, z), 'bottom': (x, y - 1, z), 'front': (x, y, z + 1), 'back': (x, y, z - 1), 'right': (x + 1, y, z), 'left': (x - 1, y, z)}
        ap = ap[fd]
        if fd == 'top' and y == 3: return True
        if fd == 'top' and ap not in self.blocks: return True
        return ap not in self.blocks

    def is_billboard_block(self, bt):
        return bt in ['beets', 'carrots', 'roses', 'flower', 'flower2', 'grass_cover', 'fire', 'fire_strike', 'bow', 'arrow']

    def is_transparent_block(self, bt):
        return bt in ['water', 'lava', 'leaves', 'jungle_leaves', 'pine_leaves', 'fire', 'ice']

    def apply_biome_tinting(self, block_type, world_x, world_z):
        biome_info = self.biome_data.get((world_x, world_z))
        if not biome_info:
            return (1.0, 1.0, 1.0)
            
        color_mod = biome_info['color_mod']
        
        if block_type in ['grass', 'leaves', 'jungle_leaves', 'pine_leaves', 'grass_cover']:
            return color_mod
        elif block_type == 'water':
            biome = biome_info['biome']
            if biome == 'ocean':
                return (0.2, 0.4, 0.8)
            elif biome == 'river':
                return (0.4, 0.6, 0.9)
            else:
                return (0.3, 0.5, 0.8)
        
        return (1.0, 1.0, 1.0)


    def build_mesh(self, block_registry):
        """Build mesh with proper texture handling"""
        if not self.rebuild:
            return
        
        # Clear existing mesh
        if not hasattr(self, 'mesh'):
            self.mesh = OptimizedChunkMesh()
        self.mesh.clear()
        
        # Separate blocks by rendering type
        solid_blocks = []
        transparent_blocks = []
        billboard_blocks = []
        
        for (x, y, z), block_type in self.blocks.items():
            if self.is_billboard_block(block_type):
                billboard_blocks.append(((x, y, z), block_type))
            elif self.is_transparent_block(block_type):
                transparent_blocks.append(((x, y, z), block_type))
            else:
                solid_blocks.append(((x, y, z), block_type))
        
        # Build solid blocks with proper textures
        for (x, y, z), block_type in solid_blocks:
            u1, v1, u2, v2 = self.ta.get_uv(block_type) if self.ta else (0, 0, 1, 1)
            
            # Apply biome tinting
            tint = self.apply_biome_tinting(block_type, x, z)
            
            # Check each face for visibility
            for face_dir in ['top', 'bottom', 'front', 'back', 'left', 'right']:
                if self.is_face_visible(x, y, z, face_dir):
                    self.mesh.add_cube_face(float(x), float(y), float(z), face_dir, u1, v1, u2, v2)
        
        # Store transparent and billboard blocks for later rendering
        self.transparent_blocks = transparent_blocks
        self.billboard_blocks = billboard_blocks
        
        self.rebuild = False
    
    def draw(self, ta, block_registry, cx=0, cy=0, cz=0):
        """Draw chunk with proper texture handling"""
        # Set texture atlas
        if self.ta != ta:
            self.set_ta(ta)
        
        # Build mesh if needed
        if self.rebuild:
            self.build_mesh(block_registry)
        
        # Enable textures and set up rendering state
        glEnable(GL_TEXTURE_2D)
        glEnable(GL_DEPTH_TEST)
        glDepthFunc(GL_LESS)
        glColor4f(1.0, 1.0, 1.0, 1.0)
        
        # Bind texture atlas
        if ta and ta.tex:
            ta.bind()
        
        # Draw solid blocks
        if hasattr(self, 'mesh') and self.mesh:
            self.mesh.draw()
        else:
            # Fallback to immediate mode if mesh fails
            self.draw_immediate_mode(ta, cx, cy, cz)
        
        # Draw transparent blocks
        self.draw_transparent_blocks(ta)
        
        # Draw billboard blocks  
        self.draw_billboard_blocks(ta, cx, cy, cz)
        
        # Draw features
        self.draw_bees()
    
    def draw_immediate_mode(self, ta, cx=0, cy=0, cz=0):
        """Fallback immediate mode rendering with textures"""
        glEnable(GL_TEXTURE_2D)
        glEnable(GL_DEPTH_TEST)
        glColor4f(1.0, 1.0, 1.0, 1.0)
        
        if ta:
            ta.bind()
        
        # Draw solid blocks
        for (x, y, z), block_type in self.blocks.items():
            if not self.is_transparent_block(block_type) and not self.is_billboard_block(block_type):
                u1, v1, u2, v2 = ta.get_uv(block_type) if ta else (0, 0, 1, 1)
                
                # Apply biome tinting
                tint = self.apply_biome_tinting(block_type, x, z)
                glColor3f(*tint)
                
                # Draw textured cube
                self.draw_textured_cube(float(x), float(y), float(z), u1, v1, u2, v2)
                
                # Reset color
                glColor3f(1.0, 1.0, 1.0)
        
        # Draw trees with textures
        for tx, tz, tree_type in self.trees:
            sh = self.sh.get((tx, tz), 3)
            trunk_base = sh + 1
            
            # Tree trunk
            wu1, wv1, wu2, wv2 = ta.get_uv('wood') if ta else (0, 0, 1, 1)
            tree_height = 4
            
            for yo in range(tree_height):
                yp = trunk_base + yo
                self.draw_textured_cube(float(tx), float(yp), float(tz), wu1, wv1, wu2, wv2)
        
        # Draw beenests
        for bx, bz in self.bn:
            sh = self.sh.get((bx, bz), 3)
            bee_y = sh + 1
            u1, v1, u2, v2 = ta.get_uv('beenest') if ta else (0, 0, 1, 1)
            self.draw_textured_cube(float(bx), float(bee_y), float(bz), u1, v1, u2, v2)
        
        # Draw transparent blocks
        glEnable(GL_BLEND)
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
        glDepthMask(GL_FALSE)
        
        # Tree leaves
        for tx, tz, tree_type in self.trees:
            sh = self.sh.get((tx, tz), 3)
            trunk_base = sh + 1
            ly = trunk_base + 3
            
            lu1, lv1, lu2, lv2 = ta.get_uv('leaves') if ta else (0, 0, 1, 1)
            tint = self.apply_biome_tinting('leaves', tx, tz)
            glColor3f(*tint)
            
            # Simple leaf pattern
            for dx in [-2, -1, 0, 1, 2]:
                for dz in [-2, -1, 0, 1, 2]:
                    if abs(dx) + abs(dz) <= 3 and not (dx == 0 and dz == 0):
                        self.draw_textured_cube(float(tx + dx), float(ly), float(tz + dz), lu1, lv1, lu2, lv2)
                        if abs(dx) <= 1 and abs(dz) <= 1:
                            self.draw_textured_cube(float(tx + dx), float(ly + 1), float(tz + dz), lu1, lv1, lu2, lv2)
            
            # Top of tree
            self.draw_textured_cube(float(tx), float(ly + 2), float(tz), lu1, lv1, lu2, lv2)
            glColor3f(1.0, 1.0, 1.0)
        
        glDepthMask(GL_TRUE)
        glDisable(GL_BLEND)
        
        # Draw billboard items
        for cropx, cropz, crop_type in self.crops:
            sh = self.sh.get((cropx, cropz), 3)
            cropy = sh + 1
            u1, v1, u2, v2 = ta.get_uv(crop_type) if ta else (0, 0, 1, 1)
            tint = self.apply_biome_tinting(crop_type, cropx, cropz)
            glColor3f(*tint)
            BillboardRenderer.draw_billboard(float(cropx), float(cropy), float(cropz), 0.8, u1, v1, u2, v2, cx, cy, cz)
            glColor3f(1.0, 1.0, 1.0)
        
        for flowerx, flowerz, flower_type in self.fl:
            sh = self.sh.get((flowerx, flowerz), 3)
            flowery = sh + 1
            u1, v1, u2, v2 = ta.get_uv(flower_type) if ta else (0, 0, 1, 1)
            tint = self.apply_biome_tinting(flower_type, flowerx, flowerz)
            glColor3f(*tint)
            BillboardRenderer.draw_billboard(float(flowerx), float(flowery), float(flowerz), 0.8, u1, v1, u2, v2, cx, cy, cz)
            glColor3f(1.0, 1.0, 1.0)
        
        for grassx, grassz in getattr(self, 'grass_cover', []):
            sh = self.sh.get((grassx, grassz), 3)
            grassy = sh + 1
            u1, v1, u2, v2 = ta.get_uv('grass_cover') if ta else (0, 0, 1, 1)
            tint = self.apply_biome_tinting('grass_cover', grassx, grassz)
            glColor3f(*tint)
            BillboardRenderer.draw_billboard(float(grassx), float(grassy), float(grassz), 0.6, u1, v1, u2, v2, cx, cy, cz)
            glColor3f(1.0, 1.0, 1.0)
    
    def draw_textured_cube(self, x, y, z, u1, v1, u2, v2):
        """Draw a textured cube with proper UV coordinates"""
        glBegin(GL_QUADS)
        
        # Top face
        glNormal3f(0.0, 1.0, 0.0)
        glTexCoord2f(u1, v1); glVertex3f(x - 0.5, y + 0.5, z + 0.5)
        glTexCoord2f(u2, v1); glVertex3f(x + 0.5, y + 0.5, z + 0.5)
        glTexCoord2f(u2, v2); glVertex3f(x + 0.5, y + 0.5, z - 0.5)
        glTexCoord2f(u1, v2); glVertex3f(x - 0.5, y + 0.5, z - 0.5)
        
        # Bottom face
        glNormal3f(0.0, -1.0, 0.0)
        glTexCoord2f(u1, v1); glVertex3f(x - 0.5, y - 0.5, z - 0.5)
        glTexCoord2f(u2, v1); glVertex3f(x + 0.5, y - 0.5, z - 0.5)
        glTexCoord2f(u2, v2); glVertex3f(x + 0.5, y - 0.5, z + 0.5)
        glTexCoord2f(u1, v2); glVertex3f(x - 0.5, y - 0.5, z + 0.5)
        
        # Front face
        glNormal3f(0.0, 0.0, 1.0)
        glTexCoord2f(u1, v1); glVertex3f(x - 0.5, y - 0.5, z + 0.5)
        glTexCoord2f(u2, v1); glVertex3f(x + 0.5, y - 0.5, z + 0.5)
        glTexCoord2f(u2, v2); glVertex3f(x + 0.5, y + 0.5, z + 0.5)
        glTexCoord2f(u1, v2); glVertex3f(x - 0.5, y + 0.5, z + 0.5)
        
        # Back face
        glNormal3f(0.0, 0.0, -1.0)
        glTexCoord2f(u1, v1); glVertex3f(x + 0.5, y - 0.5, z - 0.5)
        glTexCoord2f(u2, v1); glVertex3f(x - 0.5, y - 0.5, z - 0.5)
        glTexCoord2f(u2, v2); glVertex3f(x - 0.5, y + 0.5, z - 0.5)
        glTexCoord2f(u1, v2); glVertex3f(x + 0.5, y + 0.5, z - 0.5)
        
        # Right face
        glNormal3f(1.0, 0.0, 0.0)
        glTexCoord2f(u1, v1); glVertex3f(x + 0.5, y - 0.5, z + 0.5)
        glTexCoord2f(u2, v1); glVertex3f(x + 0.5, y - 0.5, z - 0.5)
        glTexCoord2f(u2, v2); glVertex3f(x + 0.5, y + 0.5, z - 0.5)
        glTexCoord2f(u1, v2); glVertex3f(x + 0.5, y + 0.5, z + 0.5)
        
        # Left face
        glNormal3f(-1.0, 0.0, 0.0)
        glTexCoord2f(u1, v1); glVertex3f(x - 0.5, y - 0.5, z - 0.5)
        glTexCoord2f(u2, v1); glVertex3f(x - 0.5, y - 0.5, z + 0.5)
        glTexCoord2f(u2, v2); glVertex3f(x - 0.5, y + 0.5, z + 0.5)
        glTexCoord2f(u1, v2); glVertex3f(x - 0.5, y + 0.5, z - 0.5)
        
        glEnd()    
    def draw_transparent_blocks(self, ta):
        if not hasattr(self, 'tb') or not self.tb: return
        glEnable(GL_BLEND)
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
        glDepthMask(GL_FALSE)
        if ta: ta.bind()
        for (x, y, z), bt in self.tb:
            u1, v1, u2, v2 = ta.get_uv(bt) if ta else (0, 0, 1, 1)
            tint = self.apply_biome_tinting(bt, x, z)
            glColor3f(*tint)
            
            for fd in ['top', 'bottom', 'front', 'back', 'left', 'right']: 
                self.draw_transparent_face(float(x), float(y), float(z), fd, u1, v1, u2, v2)
        
        glColor3f(1.0, 1.0, 1.0)
        glDepthMask(GL_TRUE)
        glDisable(GL_BLEND)

    def draw_transparent_face(self, x, y, z, fd, u1, v1, u2, v2):
        faces = {'top': [(x - 0.5, y + 0.5, z + 0.5, u1, v1), (x + 0.5, y + 0.5, z + 0.5, u2, v1), (x + 0.5, y + 0.5, z - 0.5, u2, v2), (x - 0.5, y + 0.5, z - 0.5, u1, v2)], 'bottom': [(x - 0.5, y - 0.5, z - 0.5, u1, v1), (x + 0.5, y - 0.5, z - 0.5, u2, v1), (x + 0.5, y - 0.5, z + 0.5, u2, v2), (x - 0.5, y - 0.5, z + 0.5, u1, v2)], 'front': [(x - 0.5, y - 0.5, z + 0.5, u1, v1), (x + 0.5, y - 0.5, z + 0.5, u2, v1), (x + 0.5, y + 0.5, z + 0.5, u2, v2), (x - 0.5, y + 0.5, z + 0.5, u1, v2)], 'back': [(x + 0.5, y - 0.5, z - 0.5, u1, v1), (x - 0.5, y - 0.5, z - 0.5, u2, v1), (x - 0.5, y + 0.5, z - 0.5, u2, v2), (x + 0.5, y + 0.5, z - 0.5, u1, v2)], 'right': [(x + 0.5, y - 0.5, z + 0.5, u1, v1), (x + 0.5, y - 0.5, z - 0.5, u2, v1), (x + 0.5, y + 0.5, z - 0.5, u2, v2), (x + 0.5, y + 0.5, z + 0.5, u1, v2)], 'left': [(x - 0.5, y - 0.5, z - 0.5, u1, v1), (x - 0.5, y - 0.5, z + 0.5, u2, v1), (x - 0.5, y + 0.5, z + 0.5, u2, v2), (x - 0.5, y + 0.5, z - 0.5, u1, v2)]}
        glBegin(GL_QUADS)
        for v in faces[fd]:
            glTexCoord2f(v[3], v[4])
            glVertex3f(v[0], v[1], v[2])
        glEnd()

    def draw_billboard_blocks(self, ta, cx, cy, cz):
        if not hasattr(self, 'bb') or not self.bb: return
        if ta: ta.bind()
        for (x, y, z), bt in self.bb:
            u1, v1, u2, v2 = ta.get_uv(bt) if ta else (0, 0, 1, 1)
            tint = self.apply_biome_tinting(bt, x, z)
            glColor3f(*tint)
            BillboardRenderer.draw_billboard(float(x), float(y), float(z), 0.8, u1, v1, u2, v2, cx, cy, cz)
        glColor3f(1.0, 1.0, 1.0)

    def update_bees(self):
        if not self.bn or random.random() > 0.5: return
        ct = time.time()
        for bee in self.bees[:]:
            bee['time'] += 0.01
            bx, bz = bee['nest']
            r = 2 + math.sin(bee['time'] * 1.5) * 1
            bee['x'], bee['z'] = bx + math.cos(bee['time']) * r, bz + math.sin(bee['time']) * r
            bee['y'] = bee['base_y'] + math.sin(bee['time'] * 2) * 0.3
            if bee['time'] > 15: self.bees.remove(bee)
        for bx, bz in self.bn:
            if random.random() < 0.02 and len(self.bees) < 3:
                sh = self.sh.get((bx, bz), 3)
                bee = {'nest': (bx, bz), 'x': bx, 'y': sh + 2, 'z': bz, 'base_y': sh + 2, 'time': 0, 'size': 0.15}
                self.bees.append(bee)

    def draw_bees(self):
        if not self.bees: return
        ot, oc, ob = glIsEnabled(GL_TEXTURE_2D), glIsEnabled(GL_CULL_FACE), glIsEnabled(GL_BLEND)
        try:
            glDisable(GL_TEXTURE_2D)
            glDisable(GL_CULL_FACE)
            glEnable(GL_BLEND)
            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
            for bee in self.bees:
                glPushMatrix()
                glTranslatef(bee['x'], bee['y'], bee['z'])
                glColor4f(1.0, 1.0, 0.0, 0.8)
                s = bee['size'] / 2
                glBegin(GL_QUADS)
                for v in [(-s, -s, 0), (s, -s, 0), (s, s, 0), (-s, s, 0)]: glVertex3f(*v)
                glEnd()
                glPopMatrix()
        finally:
            if ot: glEnable(GL_TEXTURE_2D)
            if oc: glEnable(GL_CULL_FACE)
            if not ob: glDisable(GL_BLEND)

    def set_ta(self, ta):
        self.ta, self.rebuild = ta, True

    def draw(self, ta, block_registry, cx=0, cy=0, cz=0):
        if self.ta != ta: self.set_ta(ta)
        if self.rebuild: self.build_mesh(block_registry)
        self.update_bees()
        uim = (self.fim or not hasattr(self.mesh, 'vbo') or not self.mesh.vbo or not ta or not ta.tex or len(ta.coords) == 0)
        if uim: self.draw_immediate_mode(ta, cx, cy, cz)
        else:
            oc, ob = glIsEnabled(GL_CULL_FACE), glIsEnabled(GL_BLEND)
            glEnable(GL_DEPTH_TEST)
            glDepthFunc(GL_LESS)
            glEnable(GL_TEXTURE_2D)
            glEnable(GL_CULL_FACE)
            glCullFace(GL_BACK)
            glDisable(GL_BLEND)
            glColor4f(1.0, 1.0, 1.0, 1.0)
            ta.bind()
            self.mesh.draw()
            self.draw_transparent_blocks(ta)
            self.draw_billboard_blocks(ta, cx, cy, cz)
            if not oc: glDisable(GL_CULL_FACE)
            if ob: glEnable(GL_BLEND)
        self.draw_bees()

    def draw_immediate_mode(self, ta, cx=0, cy=0, cz=0):
        oc, ob = glIsEnabled(GL_CULL_FACE), glIsEnabled(GL_BLEND)
        glEnable(GL_DEPTH_TEST)
        glEnable(GL_TEXTURE_2D)
        glEnable(GL_CULL_FACE)
        glDisable(GL_BLEND)
        glColor4f(1.0, 1.0, 1.0, 1.0)
        if ta: ta.bind()
        
        for (x, y, z), bt in self.blocks.items():
            if not self.is_transparent_block(bt) and not self.is_billboard_block(bt):
                u1, v1, u2, v2 = ta.get_uv(bt) if ta else (0, 0, 1, 1)
                tint = self.apply_biome_tinting(bt, x, z)
                glColor3f(*tint)
                self.draw_textured_cube(float(x), float(y), float(z), u1, v1, u2, v2)
                glColor3f(1.0, 1.0, 1.0)
                
        if hasattr(self, 'cs') and self.cs:
            for s in self.cs:
                if s['material'] != 'air':
                    u1, v1, u2, v2 = ta.get_uv(s['material']) if ta else (0, 0, 1, 1)
                    x, y, z = s['x'], s['y'], s['z']
                    self.draw_textured_cube(float(x), float(y), float(z), u1, v1, u2, v2)
                    
        for tx, tz, tt in self.trees:
            sh = self.sh.get((tx, tz), 3)
            trunk_base = sh + 1
            
            if tt == 'oak':
                wu1, wv1, wu2, wv2 = ta.get_uv('wood') if ta else (0, 0, 1, 1)
                for yo in range(4):
                    yp = trunk_base + yo
                    self.draw_textured_cube(float(tx), float(yp), float(tz), wu1, wv1, wu2, wv2)
            elif tt in ['pine', 'dark_oak']:
                wood_type = 'dark_wood'
                wu1, wv1, wu2, wv2 = ta.get_uv(wood_type) if ta else (0, 0, 1, 1)
                th = 7
                for yo in range(th):
                    yp = trunk_base + yo
                    self.draw_textured_cube(float(tx), float(yp), float(tz), wu1, wv1, wu2, wv2)
            elif tt == 'jungle':
                wu1, wv1, wu2, wv2 = ta.get_uv('jungle_wood') if ta else (0, 0, 1, 1)
                th = 10
                for yo in range(th):
                    yp = trunk_base + yo
                    if yo < th - 3:
                        for dx in range(-1, 2):
                            for dz in range(-1, 2):
                                if dx == 0 or dz == 0:
                                    self.draw_textured_cube(float(tx + dx), float(yp), float(tz + dz), wu1, wv1, wu2, wv2)
                    else:
                        self.draw_textured_cube(float(tx), float(yp), float(tz), wu1, wv1, wu2, wv2)
                        
        for bx, bz in self.bn:
            sh = self.sh.get((bx, bz), 3)
            bee_y = sh + 1
            u1, v1, u2, v2 = ta.get_uv('beenest') if ta else (0, 0, 1, 1)
            self.draw_textured_cube(float(bx), float(bee_y), float(bz), u1, v1, u2, v2)
            
        if self.castle:
            cx, cz = self.castle['x'], self.castle['z']
            cs, ch = self.castle['size'], self.castle['height']
            su1, sv1, su2, sv2 = ta.get_uv('stone') if ta else (0, 0, 1, 1)
            wh = 4
            for y in range(wh):
                for dx in range(-cs//2, cs//2 + 1):
                    for dz in range(-cs//2, cs//2 + 1):
                        if abs(dx) == cs//2 or abs(dz) == cs//2:
                            wx, wz, wy = cx + dx, cz + dz, ch + 1 + y
                            self.draw_textured_cube(float(wx), float(wy), float(wz), su1, sv1, su2, sv2)
            th = 6
            corners = [(-cs//2, -cs//2), (cs//2, -cs//2), (cs//2, cs//2), (-cs//2, cs//2)]
            for cdx, cdz in corners:
                for y in range(th):
                    tx, tz, ty = cx + cdx, cz + cdz, ch + 1 + y
                    self.draw_textured_cube(float(tx), float(ty), float(tz), su1, sv1, su2, sv2)
                    
        glEnable(GL_BLEND)
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
        glDepthMask(GL_FALSE)
        for (x, y, z), bt in self.blocks.items():
            if self.is_transparent_block(bt):
                u1, v1, u2, v2 = ta.get_uv(bt) if ta else (0, 0, 1, 1)
                tint = self.apply_biome_tinting(bt, x, z)
                glColor3f(*tint)
                self.draw_textured_cube(float(x), float(y), float(z), u1, v1, u2, v2)
                glColor3f(1.0, 1.0, 1.0)
                
        for tx, tz, tt in self.trees:
            sh = self.sh.get((tx, tz), 3)
            trunk_base = sh + 1
            
            if tt == 'oak':
                ly = trunk_base + 3
                lu1, lv1, lu2, lv2 = ta.get_uv('leaves') if ta else (0, 0, 1, 1)
                tint = self.apply_biome_tinting('leaves', tx, tz)
                glColor3f(*tint)
                for dx in [-2, -1, 0, 1, 2]:
                    for dz in [-2, -1, 0, 1, 2]:
                        if abs(dx) + abs(dz) <= 3 and not (dx == 0 and dz == 0):
                            self.draw_textured_cube(float(tx + dx), float(ly), float(tz + dz), lu1, lv1, lu2, lv2)
                            if abs(dx) <= 1 and abs(dz) <= 1:
                                self.draw_textured_cube(float(tx + dx), float(ly + 1), float(tz + dz), lu1, lv1, lu2, lv2)
                self.draw_textured_cube(float(tx), float(ly + 2), float(tz), lu1, lv1, lu2, lv2)
                glColor3f(1.0, 1.0, 1.0)
                
            elif tt in ['pine', 'dark_oak']:
                th = 7
                ly = trunk_base + th - 1
                lu1, lv1, lu2, lv2 = ta.get_uv('pine_leaves') if ta else (0, 0, 1, 1)
                tint = self.apply_biome_tinting('pine_leaves', tx, tz)
                glColor3f(*tint)
                for level in range(4):
                    cy = ly - level
                    r = 1 + level
                    for dx in range(-r, r + 1):
                        for dz in range(-r, r + 1):
                            if dx*dx + dz*dz <= r*r and not (dx == 0 and dz == 0 and level == 0):
                                self.draw_textured_cube(float(tx + dx), float(cy), float(tz + dz), lu1, lv1, lu2, lv2)
                glColor3f(1.0, 1.0, 1.0)
                
            elif tt == 'jungle':
                th = 10
                ly = trunk_base + th - 2
                lu1, lv1, lu2, lv2 = ta.get_uv('jungle_leaves') if ta else (0, 0, 1, 1)
                tint = self.apply_biome_tinting('jungle_leaves', tx, tz)
                glColor3f(*tint)
                for level in range(3):
                    cy = ly + level
                    r = 4 - level
                    for dx in range(-r, r + 1):
                        for dz in range(-r, r + 1):
                            if dx*dx + dz*dz <= r*r:
                                self.draw_textured_cube(float(tx + dx), float(cy), float(tz + dz), lu1, lv1, lu2, lv2)
                glColor3f(1.0, 1.0, 1.0)
        
        glDepthMask(GL_TRUE)
        glDisable(GL_CULL_FACE)
        
        for cropx, cropz, ct in self.crops:
            sh = self.sh.get((cropx, cropz), 3)
            cropy = sh + 1
            u1, v1, u2, v2 = ta.get_uv(ct) if ta else (0, 0, 1, 1)
            tint = self.apply_biome_tinting(ct, cropx, cropz)
            glColor3f(*tint)
            BillboardRenderer.draw_billboard(float(cropx), float(cropy), float(cropz), 0.8, u1, v1, u2, v2, cx, cy, cz)
            glColor3f(1.0, 1.0, 1.0)
            
        for flowerx, flowerz, ft in self.fl:
            sh = self.sh.get((flowerx, flowerz), 3)
            flowery = sh + 1
            u1, v1, u2, v2 = ta.get_uv(ft) if ta else (0, 0, 1, 1)
            tint = self.apply_biome_tinting(ft, flowerx, flowerz)
            glColor3f(*tint)
            BillboardRenderer.draw_billboard(float(flowerx), float(flowery), float(flowerz), 0.8, u1, v1, u2, v2, cx, cy, cz)
            glColor3f(1.0, 1.0, 1.0)
            
        for grassx, grassz in getattr(self, 'grass_cover', []):
            sh = self.sh.get((grassx, grassz), 3)
            grassy = sh + 1
            u1, v1, u2, v2 = ta.get_uv('grass_cover') if ta else (0, 0, 1, 1)
            tint = self.apply_biome_tinting('grass_cover', grassx, grassz)
            glColor3f(*tint)
            BillboardRenderer.draw_billboard(float(grassx), float(grassy), float(grassz), 0.6, u1, v1, u2, v2, cx, cy, cz)
            glColor3f(1.0, 1.0, 1.0)
            
        glDisable(GL_BLEND)
        if oc: glEnable(GL_CULL_FACE)
        else: glDisable(GL_CULL_FACE)
        if ob: glEnable(GL_BLEND)

    def draw_textured_cube(self, x, y, z, u1, v1, u2, v2):
        glBegin(GL_QUADS)
        glNormal3f(0.0, 1.0, 0.0)
        for v in [(u1, v1, x - 0.5, y + 0.5, z + 0.5), (u2, v1, x + 0.5, y + 0.5, z + 0.5), (u2, v2, x + 0.5, y + 0.5, z - 0.5), (u1, v2, x - 0.5, y + 0.5, z - 0.5)]:
            glTexCoord2f(v[0], v[1])
            glVertex3f(v[2], v[3], v[4])
        glNormal3f(0.0, -1.0, 0.0)
        for v in [(u1, v1, x - 0.5, y - 0.5, z - 0.5), (u2, v1, x + 0.5, y - 0.5, z - 0.5), (u2, v2, x + 0.5, y - 0.5, z + 0.5), (u1, v2, x - 0.5, y - 0.5, z + 0.5)]:
            glTexCoord2f(v[0], v[1])
            glVertex3f(v[2], v[3], v[4])
        glNormal3f(0.0, 0.0, 1.0)
        for v in [(u1, v1, x - 0.5, y - 0.5, z + 0.5), (u2, v1, x + 0.5, y - 0.5, z + 0.5), (u2, v2, x + 0.5, y + 0.5, z + 0.5), (u1, v2, x - 0.5, y + 0.5, z + 0.5)]:
            glTexCoord2f(v[0], v[1])
            glVertex3f(v[2], v[3], v[4])
        glNormal3f(0.0, 0.0, -1.0)
        for v in [(u1, v1, x + 0.5, y - 0.5, z - 0.5), (u2, v1, x - 0.5, y - 0.5, z - 0.5), (u2, v2, x - 0.5, y + 0.5, z - 0.5), (u1, v2, x + 0.5, y + 0.5, z - 0.5)]:
            glTexCoord2f(v[0], v[1])
            glVertex3f(v[2], v[3], v[4])
        glNormal3f(1.0, 0.0, 0.0)
        for v in [(u1, v1, x + 0.5, y - 0.5, z + 0.5), (u2, v1, x + 0.5, y - 0.5, z - 0.5), (u2, v2, x + 0.5, y + 0.5, z - 0.5), (u1, v2, x + 0.5, y + 0.5, z + 0.5)]:
            glTexCoord2f(v[0], v[1])
            glVertex3f(v[2], v[3], v[4])
        glNormal3f(-1.0, 0.0, 0.0)
        for v in [(u1, v1, x - 0.5, y - 0.5, z - 0.5), (u2, v1, x - 0.5, y - 0.5, z + 0.5), (u2, v2, x - 0.5, y + 0.5, z + 0.5), (u1, v2, x - 0.5, y + 0.5, z - 0.5)]:
            glTexCoord2f(v[0], v[1])
            glVertex3f(v[2], v[3], v[4])
        glEnd()

    def get_height_at(self, wx, wz):
        return self.sh.get((wx, wz), -1)

class FrustumCuller:
    def __init__(self):
        self.frustum = [0.0] * 24

    def extract_frustum(self):
        try:
            mv, proj = (GLfloat * 16)(), (GLfloat * 16)()
            glGetFloatv(GL_MODELVIEW_MATRIX, mv)
            glGetFloatv(GL_PROJECTION_MATRIX, proj)
            clip = [0.0] * 16
            clip[0] = mv[0] * proj[0] + mv[1] * proj[4] + mv[2] * proj[8] + mv[3] * proj[12]
            clip[1] = mv[0] * proj[1] + mv[1] * proj[5] + mv[2] * proj[9] + mv[3] * proj[13]
            clip[2] = mv[0] * proj[2] + mv[1] * proj[6] + mv[2] * proj[10] + mv[3] * proj[14]
            clip[3] = mv[0] * proj[3] + mv[1] * proj[7] + mv[2] * proj[11] + mv[3] * proj[15]
            clip[4] = mv[4] * proj[0] + mv[5] * proj[4] + mv[6] * proj[8] + mv[7] * proj[12]
            clip[5] = mv[4] * proj[1] + mv[5] * proj[5] + mv[6] * proj[9] + mv[7] * proj[13]
            clip[6] = mv[4] * proj[2] + mv[5] * proj[6] + mv[6] * proj[10] + mv[7] * proj[14]
            clip[7] = mv[4] * proj[3] + mv[5] * proj[7] + mv[6] * proj[11] + mv[7] * proj[15]
            clip[8] = mv[8] * proj[0] + mv[9] * proj[4] + mv[10] * proj[8] + mv[11] * proj[12]
            clip[9] = mv[8] * proj[1] + mv[9] * proj[5] + mv[10] * proj[9] + mv[11] * proj[13]
            clip[10] = mv[8] * proj[2] + mv[9] * proj[6] + mv[10] * proj[10] + mv[11] * proj[14]
            clip[11] = mv[8] * proj[3] + mv[9] * proj[7] + mv[10] * proj[11] + mv[11] * proj[15]
            clip[12] = mv[12] * proj[0] + mv[13] * proj[4] + mv[14] * proj[8] + mv[15] * proj[12]
            clip[13] = mv[12] * proj[1] + mv[13] * proj[5] + mv[14] * proj[9] + mv[15] * proj[13]
            clip[14] = mv[12] * proj[2] + mv[13] * proj[6] + mv[14] * proj[10] + mv[15] * proj[14]
            clip[15] = mv[12] * proj[3] + mv[13] * proj[7] + mv[14] * proj[11] + mv[15] * proj[15]
            self.frustum[0:4] = [clip[3] - clip[0], clip[7] - clip[4], clip[11] - clip[8], clip[15] - clip[12]]
            self.frustum[4:8] = [clip[3] + clip[0], clip[7] + clip[4], clip[11] + clip[8], clip[15] + clip[12]]
            self.frustum[8:12] = [clip[3] + clip[1], clip[7] + clip[5], clip[11] + clip[9], clip[15] + clip[13]]
            self.frustum[12:16] = [clip[3] - clip[1], clip[7] - clip[5], clip[11] - clip[9], clip[15] - clip[13]]
            self.frustum[16:20] = [clip[3] - clip[2], clip[7] - clip[6], clip[11] - clip[10], clip[15] - clip[14]]
            self.frustum[20:24] = [clip[3] + clip[2], clip[7] + clip[6], clip[11] + clip[10], clip[15] + clip[14]]
            for i in range(6):
                b = i * 4
                l = math.sqrt(self.frustum[b] * self.frustum[b] + self.frustum[b + 1] * self.frustum[b + 1] + self.frustum[b + 2] * self.frustum[b + 2])
                if l > 0:
                    self.frustum[b] /= l
                    self.frustum[b + 1] /= l
                    self.frustum[b + 2] /= l
                    self.frustum[b + 3] /= l
        except: pass

    def sphere_in_frustum(self, x, y, z, r):
        try:
            for i in range(6):
                b = i * 4
                d = (self.frustum[b] * x + self.frustum[b + 1] * y + self.frustum[b + 2] * z + self.frustum[b + 3])
                if d <= -r: return False
            return True
        except: return True

class ChunkManager:
    def __init__(self, rd=2):
        self.chunks, self.rd, self.cs = {}, rd, 16
        self.lpc, self.ta, self.fc = (None, None), TextureAtlas(), FrustumCuller()
        self.vbs, self.mcpf, self.fova = self.test_vbo_support(), 6, 85
        self.pm = 0
        self.block_registry = {}
        self.terrain_generator = MinecraftTerrainGenerator()
        
    def init_blocks(self, mod_loader):
        block_mods = mod_loader.get_mods_by_type(BlockMod)
        for mod in block_mods:
            self.block_registry.update(mod.blocks())
        
        tex_map = {}
        for mod in block_mods:
            tex_map.update(mod.textures())
        
        try:
            self.ta.load_textures(tex_map)
            print(f"✅ Texture loading complete. Available blocks: {list(self.ta.coords.keys())}")
        except Exception as e:
            print(f"❌ Error loading textures: {e}")
            self.create_fallback_atlas()
            
    def test_vbo_support(self):
        try:
            if not hasattr(pyglet.gl, 'glGenBuffers'): return False
            td, ta = [0.0, 0.0, 0.0], (GLfloat * 3)(*[0.0, 0.0, 0.0])
            tvbo = glGenBuffers(1)
            tvbo = tvbo[0] if isinstance(tvbo, (list, tuple)) else tvbo
            glBindBuffer(GL_ARRAY_BUFFER, tvbo)
            glBufferData(GL_ARRAY_BUFFER, 12, ta, GL_STATIC_DRAW)
            glBindBuffer(GL_ARRAY_BUFFER, 0)
            if glIsBuffer(tvbo):
                glDeleteBuffers(1, [tvbo])
                return True
            return False
        except: return False

    def create_fallback_atlas(self):
        try:
            print("Creating fallback texture atlas...")
            bt = list(self.block_registry.keys())
            self.ta.create_fallback_atlas(bt)
            print("✅ Fallback atlas created successfully")
        except Exception as e: print(f"❌ Fallback atlas creation failed: {e}")

    def get_chunk_coords(self, wx, wz):
        return int(wx // self.cs), int(wz // self.cs)

    def get_chunk(self, cx, cz, mod_loader):
        ck = (cx, cz)
        if ck not in self.chunks:
            chunk = Chunk(cx, cz, self.cs)
            chunk.set_ta(self.ta)
            
            world_mods = mod_loader.get_mods_by_type(WorldMod)
            all_structures = []
            for mod in world_mods:
                structures = mod.gen(cx, cz, chunk)
                if structures:
                    all_structures.extend(structures)
            
            chunk.cs = all_structures
            if not self.vbs: chunk.fim = True
            self.chunks[ck] = chunk
        return self.chunks[ck]

    def is_chunk_behind_player(self, cx, cz, px, pz, pyaw):
        ccx, ccz = cx * self.cs + self.cs / 2, cz * self.cs + self.cs / 2
        dx, dz, dtc = ccx - px, ccz - pz, math.sqrt((ccx - px) ** 2 + (ccz - pz) ** 2)
        if dtc < self.cs * 1.5: return False
        yr = math.radians(pyaw)
        fx, fz = math.sin(yr), -math.cos(yr)
        dp, tol = dx * fx + dz * fz, self.cs * 0.8
        return dp < -tol

    def get_chunk_angle_from_player(self, cx, cz, px, pz, pyaw):
        ccx, ccz = cx * self.cs + self.cs / 2, cz * self.cs + self.cs / 2
        dx, dz = ccx - px, ccz - pz
        yr = math.radians(pyaw)
        fx, fz = math.sin(yr), -math.cos(yr)
        cd = math.sqrt(dx * dx + dz * dz)
        if cd == 0: return 0
        dx /= cd
        dz /= cd
        dp = dx * fx + dz * fz
        return math.degrees(math.acos(max(-1, min(1, dp))))

    def update_chunks(self, px, pz, mod_loader):
        pcx, pcz = self.get_chunk_coords(px, pz)
        if self.lpc == (None, None): self.lpc = (pcx, pcz)
        else:
            dx, dz = abs(pcx - self.lpc[0]), abs(pcz - self.lpc[1])
            if dx == 0 and dz == 0: return
        self.lpc = (pcx, pcz)
        for d in range(min(self.rd + 1, 3)):
            for dx in range(-d, d + 1):
                for dz in range(-d, d + 1):
                    if max(abs(dx), abs(dz)) == d:
                        cx, cz = pcx + dx, pcz + dz
                        self.get_chunk(cx, cz, mod_loader)
        ctr = []
        for (cx, cz) in self.chunks.keys():
            dx, dz = abs(cx - pcx), abs(cz - pcz)
            if max(dx, dz) > self.rd: ctr.append((cx, cz))
        for ck in ctr: del self.chunks[ck]

    def draw_chunks(self, px, pz, cam):
        pcx, pcz = self.get_chunk_coords(px, pz)
        self.fc.extract_frustum()
        cd, fd = 0, 0
        vc, pc = [], []
        cx, cy, cz = cam.get_pos()
        for (cx, cz), chunk in self.chunks.items():
            dx, dz, ds = cx - pcx, cz - pcz, (cx - pcx) ** 2 + (cz - pcz) ** 2
            if ds > self.rd * self.rd: continue
            ccx, ccz = cx * self.cs + self.cs / 2, cz * self.cs + self.cs / 2
            dtp = math.sqrt((ccx - px)**2 + (ccz - pz)**2)
            if dtp < self.cs * 1.5:
                pc.append((ds, 0, cx, cz, chunk, ccx, ccz))
                continue
            if self.is_chunk_behind_player(cx, cz, px, pz, cam.ry): continue
            a = self.get_chunk_angle_from_player(cx, cz, px, pz, cam.ry)
            if a > self.fova / 2 + 20: continue
            vc.append((ds, a, cx, cz, chunk, ccx, ccz))
        pc.sort(key=lambda x: x[0])
        vc.sort(key=lambda x: (x[0], x[1]))
        ac = pc + vc
        ctr = ac[:self.mcpf]
        for ds, a, cx, cz, chunk, ccx, ccz in ctr:
            cr = self.cs * 0.7
            ip = ds <= (self.cs * 1.5) ** 2
            if ip or self.fc.sphere_in_frustum(ccx, 15, ccz, cr):
                try:
                    chunk.draw(self.ta, self.block_registry, cx, cy, cz)
                    cd += 1
                    fd += chunk.mesh.fc
                except: pass
        return cd, fd

    def get_height_at(self, wx, wz, mod_loader):
        cx, cz = self.get_chunk_coords(wx, wz)
        chunk = self.get_chunk(cx, cz, mod_loader)
        return chunk.get_height_at(wx, wz)

class Camera:
    def __init__(self):
        self.eh = 1.8
        self.x = self.y = self.z = 0
        self.y = 65
        self.rx = self.ry = 0
        self.cm, self.tpd, self.tph = "first_person", 5.0, 1.0
        self.sp, self.ssp, self.ms = 8.0, 15.0, 0.15
        self.vx = self.vz = self.vy = 0
        self.acc, self.fric, self.msp = 25.0, 12.0, self.sp
        self.grav, self.jsp, self.tv = -30.0, 10.0, -50.0
        self.cr, self.ph, self.og, self.gcd = 0.3, 1.8, False, 0.1
        self.fm, self.fsp, self.init = False, 12.0, False

    def init_pos(self, cm):
        if not self.init and hasattr(cm, 'get_height_at'):
            try:
                gh = cm.get_height_at(int(self.x), int(self.z), getattr(cm, 'mod_loader', None))
                if gh == -1: gh = 65  # Default to above sea level
                self.y = gh + 1 + self.eh
                self.og, self.vy, self.init = True, 0, True
            except Exception:
                self.y = 65  # Safe default height
                self.og, self.vy, self.init = True, 0, True
    def update(self, dt, keys, cm, blocks):
        if not self.init: self.init_pos(cm)
        if keys[pyglet.window.key.LCTRL]: self.msp = self.ssp
        else: self.msp = self.sp
        self.handle_movement_input(dt, keys)
        self.apply_gravity(dt)
        self.apply_movement_with_collision(dt, cm, blocks)

    def handle_movement_input(self, dt, keys):
        yr = math.radians(self.ry)
        fx, fz = math.sin(yr), -math.cos(yr)
        rx, rz = math.cos(yr), math.sin(yr)
        ix = iz = 0
        if keys[pyglet.window.key.W]: ix += fx; iz += fz
        if keys[pyglet.window.key.S]: ix -= fx; iz -= fz
        if keys[pyglet.window.key.A]: ix -= rx; iz -= rz
        if keys[pyglet.window.key.D]: ix += rx; iz += rz
        il = math.sqrt(ix * ix + iz * iz)
        if il > 0:
            ix /= il; iz /= il
            self.vx += ix * self.acc * dt
            self.vz += iz * self.acc * dt
        if il == 0:
            ff = max(0, 1 - self.fric * dt)
            self.vx *= ff; self.vz *= ff
        vl = math.sqrt(self.vx ** 2 + self.vz ** 2)
        if vl > self.msp:
            self.vx = (self.vx / vl) * self.msp
            self.vz = (self.vz / vl) * self.msp
        if keys[pyglet.window.key.SPACE]:
            if self.fm: self.vy = self.fsp
            elif self.og: self.vy = self.jsp; self.og = False
        if keys[pyglet.window.key.LSHIFT] and self.fm: self.vy = -self.fsp
        if self.fm and not keys[pyglet.window.key.SPACE] and not keys[pyglet.window.key.LSHIFT]: self.vy = 0

    def apply_gravity(self, dt):
        if self.fm: return
        if not self.og: self.vy += self.grav * dt
        if self.vy < self.tv: self.vy = self.tv

    def get_ground_height(self, x, z, cm, blocks):
        th = cm.get_height_at(int(x), int(z), cm.mod_loader)
        if th == -1: th = 5
        mh = th + 0.5
        bx, bz = int(round(x)), int(round(z))
        for block in blocks:
            if abs(block.x - bx) < 1 and abs(block.z - bz) < 1:
                bt = block.y + 0.5
                if bt > mh: mh = bt
        return mh

    def check_collision(self, nx, nz, ny, cm, blocks):
        pf, ph = ny - self.eh, ny + (self.ph - self.eh)
        for block in blocks:
            dx, dz = abs(nx - block.x), abs(nz - block.z)
            if (dx < 0.5 + self.cr and dz < 0.5 + self.cr):
                bb, bt = block.y - 0.5, block.y + 0.5
                if pf < bt and ph > bb: return True
        th = cm.get_height_at(int(nx), int(nz), cm.mod_loader)
        if th != -1:
            tt = th + 0.5
            if pf < tt: return True
        return False

    def apply_movement_with_collision(self, dt, cm, blocks):
        nx, nz = self.x + self.vx * dt, self.z + self.vz * dt
        if not self.check_collision(nx, self.z, self.y, cm, blocks): self.x = nx
        else: self.vx = 0
        if not self.check_collision(self.x, nz, self.y, cm, blocks): self.z = nz
        else: self.vz = 0
        ny = self.y + self.vy * dt
        
        # Enforce world height limits (Minecraft-style)
        if ny < 0:
            ny = 0
            self.vy = 0
        elif ny > 128:
            ny = 128
            self.vy = 0
        
        if self.fm:
            if not self.check_collision(self.x, self.z, ny, cm, blocks): self.y = ny
            else: self.vy = 0
            self.og = False
        else:
            gh = self.get_ground_height(self.x, self.z, cm, blocks)
            ty = gh + self.eh
            if ny <= ty + self.gcd:
                self.y, self.vy, self.og = ty, 0, True
            else:
                if not self.check_collision(self.x, self.z, ny, cm, blocks):
                    self.y, self.og = ny, False
                else: self.vy = 0
    def mouse_look(self, dx, dy):
        self.ry += dx * self.ms
        self.rx -= dy * self.ms
        self.rx = max(-90, min(90, self.rx))

    def toggle_cam(self):
        if self.cm == "first_person":
            self.cm = "third_person"
            print("📷 Third-person camera enabled")
        else:
            self.cm = "first_person"
            print("📷 First-person camera enabled")

    def get_pos(self):
        if self.cm == "first_person": return self.x, self.y, self.z
        else:
            yr = math.radians(self.ry)
            cx = self.x - math.sin(yr) * self.tpd
            cy = self.y + self.tph
            cz = self.z + math.cos(yr) * self.tpd
            return cx, cy, cz

    def is_first_person(self):
        return self.cm == "first_person"

    def apply_transform(self):
        if self.cm == "first_person":
            glRotatef(self.rx, 1, 0, 0)
            glRotatef(self.ry, 0, 1, 0)
            glTranslatef(-self.x, -self.y, -self.z)
        else:
            cx, cy, cz = self.get_pos()
            glRotatef(self.rx, 1, 0, 0)
            glRotatef(self.ry, 0, 1, 0)
            glTranslatef(-cx, -cy, -cz)

class PlayerModel:
    def __init__(self):
        self.hs, self.bw, self.bh, self.al, self.ll = 0.4, 0.3, 0.6, 0.5, 0.6
        self.wa, self.a_swing = 0.0, 0.0

    def update(self, dt, vx, vz):
        ws = math.sqrt(vx * vx + vz * vz)
        if ws > 0.1:
            self.wa += dt * ws * 8.0
            self.a_swing = math.sin(self.wa) * 0.5
        else: self.wa = self.a_swing = 0.0

    def draw(self, x, y, z, ry):
        glPushMatrix()
        glTranslatef(x, y - 0.9, z)
        glRotatef(ry, 0, 1, 0)
        glDisable(GL_TEXTURE_2D)
        glEnable(GL_DEPTH_TEST)
        glPushMatrix()
        glTranslatef(0, self.bh + self.hs/2, 0)
        glColor3f(0.9, 0.7, 0.6)
        self.draw_cube(self.hs)
        glPopMatrix()
        glPushMatrix()
        glTranslatef(0, self.bh/2, 0)
        glColor3f(0.2, 0.5, 0.8)
        self.draw_cube_rect(self.bw, self.bh, self.bw * 0.5)
        glPopMatrix()
        for side in [-1, 1]:
            glPushMatrix()
            glTranslatef(side * (self.bw + 0.1), self.bh - 0.1, 0)
            glRotatef(self.a_swing * side * 30, 1, 0, 0)
            glColor3f(0.9, 0.7, 0.6)
            self.draw_cube_rect(0.15, self.al, 0.15)
            glPopMatrix()
        for side in [-1, 1]:
            glPushMatrix()
            glTranslatef(side * 0.1, -self.ll/2, 0)
            glRotatef(-self.a_swing * side * 45, 1, 0, 0)
            glColor3f(0.2, 0.2, 0.8)
            self.draw_cube_rect(0.15, self.ll, 0.15)
            glPopMatrix()
        glPopMatrix()

    def draw_cube(self, sz):
        s = sz / 2
        glBegin(GL_QUADS)
        for f in [(-s,-s,s,s,-s,s,s,s,s,-s,s,s), (s,-s,-s,-s,-s,-s,-s,s,-s,s,s,-s), (-s,-s,-s,-s,-s,s,-s,s,s,-s,s,-s), (s,-s,s,s,-s,-s,s,s,-s,s,s,s), (-s,s,-s,-s,s,s,s,s,s,s,s,-s)]:
            [glVertex3f(f[i],f[i+1],f[i+2]) for i in range(0,len(f),3)]
        glEnd()

    def draw_cube_rect(self, w, h, d):
        w, h, d = w/2, h/2, d/2
        glBegin(GL_QUADS)
        for f in [(-w,-h,d,w,-h,d,w,h,d,-w,h,d), (w,-h,-d,-w,-h,-d,-w,h,-d,w,h,-d), (-w,-h,-d,-w,-h,d,-w,h,d,-w,h,-d), (w,-h,d,w,-h,-d,w,h,-d,w,h,d), (-w,h,d,w,h,d,w,h,-d,-w,h,-d)]:
            [glVertex3f(f[i],f[i+1],f[i+2]) for i in range(0,len(f),3)]
        glEnd()

class BillboardRenderer:
    @staticmethod
    def draw_billboard(x, y, z, sz, u1, v1, u2, v2, cx, cy, cz):
        glPushMatrix()
        glTranslatef(x, y, z)
        dx, dz = cx - x, cz - z
        if dx != 0 or dz != 0:
            a = math.degrees(math.atan2(dx, dz))
            glRotatef(a, 0, 1, 0)
        glEnable(GL_BLEND)
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
        glDisable(GL_CULL_FACE)
        glColor4f(1.0, 1.0, 1.0, 1.0)
        glBegin(GL_QUADS)
        for v in [(u1, v1, -sz/2, -sz/2, 0), (u2, v1, sz/2, -sz/2, 0), (u2, v2, sz/2, sz/2, 0), (u1, v2, -sz/2, sz/2, 0)]:
            glTexCoord2f(v[0], v[1])
            glVertex3f(v[2], v[3], v[4])
        glEnd()
        glRotatef(90, 0, 1, 0)
        glBegin(GL_QUADS)
        for v in [(u1, v1, -sz/2, -sz/2, 0), (u2, v1, sz/2, -sz/2, 0), (u2, v2, sz/2, sz/2, 0), (u1, v2, -sz/2, sz/2, 0)]:
            glTexCoord2f(v[0], v[1])
            glVertex3f(v[2], v[3], v[4])
        glEnd()
        glDisable(GL_BLEND)
        glEnable(GL_CULL_FACE)
        glPopMatrix()

class Block:
    def __init__(self, x, y, z, bt='grass'):
        self.x, self.y, self.z, self.type, self.sz, self.vis, self.hl = x, y, z, bt, 1.0, True, False

    def draw(self, ta):
        if not self.vis: return
        oc, ob, ot = glIsEnabled(GL_CULL_FACE), glIsEnabled(GL_BLEND), glIsEnabled(GL_TEXTURE_2D)
        try:
            glPushMatrix()
            glEnable(GL_TEXTURE_2D)
            glEnable(GL_DEPTH_TEST)
            glEnable(GL_BLEND)
            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
            glDisable(GL_CULL_FACE)
            if ta:
                ta.bind()
                u1, v1, u2, v2 = ta.get_uv(self.type)
            else: u1, v1, u2, v2 = 0.0, 0.0, 1.0, 1.0
            glColor4f(1.2, 1.2, 0.8, 1.0) if self.hl else glColor4f(1.0, 1.0, 1.0, 1.0)
            glTranslatef(float(self.x), float(self.y), float(self.z))
            self._draw_cube_faces(u1, v1, u2, v2)
        except: pass
        finally:
            glPopMatrix()
            glDisable(GL_POLYGON_OFFSET_FILL)
            if oc: glEnable(GL_CULL_FACE)
            else: glDisable(GL_CULL_FACE)
            if ob: glEnable(GL_BLEND)
            else: glDisable(GL_BLEND)
            if not ot: glDisable(GL_TEXTURE_2D)

    def _draw_cube_faces(self, u1, v1, u2, v2):
        glBegin(GL_QUADS)
        glNormal3f(0.0, 1.0, 0.0)
        for v in [(u1, v1, -0.5, 0.5, 0.5), (u2, v1, 0.5, 0.5, 0.5), (u2, v2, 0.5, 0.5, -0.5), (u1, v2, -0.5, 0.5, -0.5)]:
            glTexCoord2f(v[0], v[1])
            glVertex3f(v[2], v[3], v[4])
        glNormal3f(0.0, -1.0, 0.0)
        for v in [(u1, v1, -0.5, -0.5, -0.5), (u2, v1, 0.5, -0.5, -0.5), (u2, v2, 0.5, -0.5, 0.5), (u1, v2, -0.5, -0.5, 0.5)]:
            glTexCoord2f(v[0], v[1])
            glVertex3f(v[2], v[3], v[4])
        glNormal3f(0.0, 0.0, 1.0)
        for v in [(u1, v1, -0.5, -0.5, 0.5), (u2, v1, 0.5, -0.5, 0.5), (u2, v2, 0.5, 0.5, 0.5), (u1, v2, -0.5, 0.5, 0.5)]:
            glTexCoord2f(v[0], v[1])
            glVertex3f(v[2], v[3], v[4])
        glNormal3f(0.0, 0.0, -1.0)
        for v in [(u1, v1, 0.5, -0.5, -0.5), (u2, v1, -0.5, -0.5, -0.5), (u2, v2, -0.5, 0.5, -0.5), (u1, v2, 0.5, 0.5, -0.5)]:
            glTexCoord2f(v[0], v[1])
            glVertex3f(v[2], v[3], v[4])
        glNormal3f(1.0, 0.0, 0.0)
        for v in [(u1, v1, 0.5, -0.5, 0.5), (u2, v1, 0.5, -0.5, -0.5), (u2, v2, 0.5, 0.5, -0.5), (u1, v2, 0.5, 0.5, 0.5)]:
            glTexCoord2f(v[0], v[1])
            glVertex3f(v[2], v[3], v[4])
        glNormal3f(-1.0, 0.0, 0.0)
        for v in [(u1, v1, -0.5, -0.5, -0.5), (u2, v1, -0.5, -0.5, 0.5), (u2, v2, -0.5, 0.5, 0.5), (u1, v2, -0.5, 0.5, -0.5)]:
            glTexCoord2f(v[0], v[1])
            glVertex3f(v[2], v[3], v[4])
        glEnd()

    def set_hl(self, h): self.hl = h
    def set_vis(self, v): self.vis = v
    def get_pos(self): return (self.x, self.y, self.z)
    def set_pos(self, x, y, z): self.x, self.y, self.z = x, y, z

class ModularSandboxGame(pyglet.window.Window):
    def __init__(self):
        try:
            config = pyglet.gl.Config(major_version=2, minor_version=1, depth_size=24, double_buffer=True, sample_buffers=1, samples=4)
            super().__init__(1200, 800, "Enhanced Minecraft Sandbox with Advanced World Generation", resizable=True, config=config)
        except:
            try:
                config = pyglet.gl.Config(depth_size=24, double_buffer=True)
                super().__init__(1200, 800, "Enhanced Minecraft Sandbox with Advanced World Generation", resizable=True, config=config)
            except: super().__init__(1200, 800, "Enhanced Minecraft Sandbox with Advanced World Generation", resizable=True)
        
                # Initialize mod loader first
        self.mod_loader = ModLoader(self)
        self.mod_loader.scan_mods()
        
        # Initialize core systems with error handling
        try:
            self.cam, self.pm = Camera(), PlayerModel()
        except Exception as e:
            print(f"⚠️ Camera/PlayerModel init warning: {e}")
            self.cam, self.pm = Camera(), PlayerModel()
        
        try:
            self.cm = ChunkManager(rd=2)
            self.cm.mod_loader = self.mod_loader
            self.cm.init_blocks(self.mod_loader)
        except Exception as e:
            print(f"⚠️ ChunkManager init warning: {e}")
            self.cm = ChunkManager(rd=1)  # Fallback with lower render distance
            self.cm.mod_loader = self.mod_loader
            self.cm.init_blocks(self.mod_loader)
        
        try:
            self.dnc = DayNightCycle()
        except Exception as e:
            print(f"⚠️ DayNightCycle init warning: {e}")
            self.dnc = DayNightCycle()
        
        # Initialize liquid and projectile systems
        try:
            self.flowing_liquid = FlowingLiquid(self)
            self.projectiles = []
        except Exception as e:
            print(f"⚠️ Liquid/Projectile init warning: {e}")
            self.flowing_liquid = FlowingLiquid(self)
            self.projectiles = []
        
        self.inv = {
            'grass': {'count': 64, 'slot': 0}, 'dirt': {'count': 64, 'slot': 1}, 'stone': {'count': 64, 'slot': 2}, 
            'mountain_stone': {'count': 32, 'slot': 3}, 'wood': {'count': 64, 'slot': 4}, 'jungle_wood': {'count': 32, 'slot': 5}, 
            'dark_wood': {'count': 32, 'slot': 6}, 'sand': {'count': 64, 'slot': 7}, 'red_sand': {'count': 32, 'slot': 8}, 
            'water': {'count': 64, 'slot': 9}, 'lava': {'count': 16, 'slot': 10}, 'leaves': {'count': 64, 'slot': 11}, 
            'jungle_leaves': {'count': 32, 'slot': 12}, 'pine_leaves': {'count': 32, 'slot': 13}, 'bricks': {'count': 64, 'slot': 14}, 
            'beenest': {'count': 64, 'slot': 15}, 'gold': {'count': 64, 'slot': 16}, 'planks': {'count': 64, 'slot': 17}, 
            'cobblestone': {'count': 64, 'slot': 18}, 'fire': {'count': 16, 'slot': 19}, 'wool_white': {'count': 64, 'slot': 20}, 
            'wool_red': {'count': 64, 'slot': 21}, 'wool_blue': {'count': 64, 'slot': 22}, 'wool_green': {'count': 64, 'slot': 23}, 
            'wool_yellow': {'count': 64, 'slot': 24}, 'wool_orange': {'count': 64, 'slot': 25}, 'wool_purple': {'count': 64, 'slot': 26}, 
            'wool_pink': {'count': 64, 'slot': 27}, 'beets': {'count': 64, 'slot': 28}, 'carrots': {'count': 64, 'slot': 29}, 
            'roses': {'count': 64, 'slot': 30}, 'flower': {'count': 64, 'slot': 31}, 'flower2': {'count': 64, 'slot': 32}, 
            'grass_cover': {'count': 64, 'slot': 33}, 'lamps': {'count': 0, 'slot': 34}, 'gold_bars': {'count': 10, 'slot': 35}, 
            'golden_sword': {'count': 0, 'slot': 36}, 'snow': {'count': 32, 'slot': 37}, 'ice': {'count': 32, 'slot': 38}, 
            'clay': {'count': 32, 'slot': 39}, 'fire_strike': {'count': 3, 'slot': 40}, 'bow': {'count': 1, 'slot': 41}, 
            'arrow': {'count': 20, 'slot': 42}
        }
        
        self.hps = [
            ['grass', 'dirt', 'stone', 'mountain_stone', 'wood', 'jungle_wood', 'dark_wood', 'sand'],
            ['red_sand', 'water', 'lava', 'leaves', 'jungle_leaves', 'pine_leaves', 'bricks', 'beenest'],
            ['gold', 'planks', 'cobblestone', 'fire', 'wool_white', 'wool_red', 'wool_blue', 'wool_green'],
            ['wool_yellow', 'wool_orange', 'wool_purple', 'wool_pink', 'beets', 'carrots', 'roses', 'flower'],
            ['flower2', 'grass_cover', 'lamps', 'gold_bars', 'golden_sword', 'snow', 'ice', 'clay'],
            ['fire_strike', 'bow', 'arrow', 'grass', 'dirt', 'stone', 'wood', 'sand']
        ]
        self.chp, self.hss = 0, self.hps[0]
        self.sel_slot, self.sbt = 0, self.hss[0]
        self.mc, self.keys = False, pyglet.window.key.KeyStateHandler()
        self.push_handlers(self.keys)
        
        self.mod_loader.bus.set('inventory', self.inv)
        
        self.setup_opengl()
        ic = 0
        for dx in range(-2, 3):
            for dz in range(-2, 3):
                chunk = self.cm.get_chunk(dx, dz, self.mod_loader)
                ic += 1
        
        gh = self.cm.get_height_at(0, 0, self.mod_loader)
        if gh != -1: self.cam.y = gh + 1 + self.cam.eh
        
        self.tutorial_open = False
        
        self.ui_label = pyglet.text.Label('Enhanced Minecraft Sandbox v2.0 | [H] Help | [I] Tutorial | [ESC] Mouse | Mountains, Canyons, Rivers, Biomes, Flowing Liquids, Fire Strike, Bow & Arrows!', font_name='Arial', font_size=11, x=10, y=self.height - 25, color=(100, 255, 100, 255))
        pyglet.clock.schedule_interval(self.update, 1/30.0)

    def setup_opengl(self):
        try:
            glEnable(GL_DEPTH_TEST)
            glDepthFunc(GL_LESS)
            glDepthMask(GL_TRUE)
            glClearDepth(1.0)
            glEnable(GL_CULL_FACE)
            glCullFace(GL_BACK)
            glDisable(GL_BLEND)
            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
            glShadeModel(GL_SMOOTH)
            glClearColor(0.5, 0.8, 1.0, 1.0)
            glMatrixMode(GL_PROJECTION)
            glLoadIdentity()
            self.setup_perspective()
            glMatrixMode(GL_MODELVIEW)
            glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST)
            print("✅ OpenGL setup complete with enhanced settings")
        except Exception as e:
            print(f"❌ OpenGL setup error: {e}")
            glClearColor(0.5, 0.8, 1.0, 1.0)
            glEnable(GL_DEPTH_TEST)

    def setup_perspective(self):
        try:
            np, fp, fov, aspect = 0.1, 300.0, 75.0, self.width / self.height
            if OPENGL_AVAILABLE: gluPerspective(fov, aspect, np, fp)
            else: self.set_perspective_manual(fov, aspect, np, fp)
        except: self.set_perspective_manual(75, self.width / self.height, 0.1, 300.0)

    def set_perspective_manual(self, fovy, aspect, near, far):
        fr, f = math.radians(fovy), 1.0 / math.tan(math.radians(fovy) / 2.0)
        dr = far - near
        matrix = [f / aspect, 0, 0, 0, 0, f, 0, 0, 0, 0, -(far + near) / dr, -(2 * far * near) / dr, 0, 0, -1, 0]
        glMultMatrixf((GLfloat * 16)(*matrix))

    def update(self, dt):
        try:
            # Limit update frequency for better performance
            if hasattr(self, '_last_update'):
                if time.time() - self._last_update < 0.05:  # Limit to 20 FPS
                    return
            self._last_update = time.time()
            
            st = time.time()
            
            # Reduce frequency of expensive operations
            frame_count = getattr(self, '_frame_count', 0) + 1
            self._frame_count = frame_count
            
            # Only update day/night cycle every 5 frames
            if frame_count % 5 == 0 and hasattr(self, 'dnc'):
                try:
                    self.dnc.update(dt)
                except Exception:
                    pass
                    
            # Only update sky color every 10 frames
            if frame_count % 10 == 0 and hasattr(self, 'dnc'):
                try:
                    sc = self.dnc.get_sky_color()
                    glClearColor(*sc, 1.0)
                except Exception:
                    pass
                    
            if hasattr(self, 'mc') and self.mc:
                # Only update chunks when player moves significantly
                if hasattr(self, 'lpp') and hasattr(self, 'cam'):
                    dx, dz = abs(self.cam.x - self.lpp[0]), abs(self.cam.z - self.lpp[1])
                    if dx > 16 or dz > 16:  # Increased threshold
                        try:
                            self.cm.update_chunks(self.cam.x, self.cam.z, self.mod_loader)
                            self.lpp = (self.cam.x, self.cam.z)
                        except Exception:
                            pass
                        
                try:
                    self.cam.update(dt, self.keys, self.cm, self.pb)
                    self.pm.update(dt, self.cam.vx, self.cam.vz)
                except Exception:
                    pass
                
                # Reduce mod event frequency
                if frame_count % 3 == 0 and hasattr(self, 'mod_loader'):
                    try:
                        self.mod_loader.bus.emit('player_update', self.cam.x, self.cam.y, self.cam.z, self.cam.ry, self.cam.rx, dt)
                        self.mod_loader.bus.emit('update', dt, self)
                    except Exception:
                        pass
            
            # Update liquids less frequently
            if frame_count % 2 == 0 and hasattr(self, 'flowing_liquid'):
                try:
                    self.flowing_liquid.update(dt)
                except Exception:
                    pass
            
            # Update projectiles every frame (important for gameplay)
            if hasattr(self, 'projectiles'):
                try:
                    for projectile in self.projectiles[:]:
                        if not projectile.update(dt, self):
                            self.projectiles.remove(projectile)
                except Exception:
                    pass
            
            self.ft = time.time() - st
            fps = 1.0 / max(0.001, self.ft)
            
            # Auto-enable emergency mode for very low FPS
            if fps < 15 and not getattr(self, 'ema', False) and frame_count > 60:
                try:
                    self.activate_emergency_mode()
                except Exception:
                    pass
                    
        except Exception as e:
            print(f"⚠️ Update error: {e}")
            pass

    def activate_emergency_mode(self):
        self.ema, self.cm.rd, self.cm.mcpf = True, 1, 3
        self.cm.fova, self.pm_mode, self.le = 60, "EMERGENCY", False
        glDisable(GL_LIGHTING)
        self.dnc.sb.cc = 2
        self.mod_loader.bus.emit('toggle_herobrine')
        pyglet.clock.schedule_interval(self.update, 1/30.0)
        print("🚨 Emergency performance mode activated! Reduced render distance and effects")

    def on_draw(self):
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()
        self.cam.apply_transform()
        self.suc += 1
        if self.suc % 400 == 0:
            try: self.dnc.draw_skybox(self.cam.x, self.cam.y, self.cam.z)
            except: pass
        
        self.mod_loader.bus.emit('render_weather', self.cam.x, self.cam.y, self.cam.z)
        if self.le: self.dnc.apply_lighting()
        else: glDisable(GL_LIGHTING)
        try: 
                    try: 
                        self.cd, self.fd = self.cm.draw_chunks(self.cam.x, self.cam.z, self.cam)
                    except: 
                        self.cd = self.fd = 0
        except: self.cd = self.fd = 0
        
        if self.pb:
            oc, ob, oo = glIsEnabled(GL_CULL_FACE), glIsEnabled(GL_BLEND), glIsEnabled(GL_POLYGON_OFFSET_FILL)
            glDisable(GL_CULL_FACE)
            glEnable(GL_TEXTURE_2D)
            glEnable(GL_POLYGON_OFFSET_FILL)
            glPolygonOffset(1.0, 1.0)
            if self.cm.ta: self.cm.ta.bind()
            for block in self.pb:
                try: block.draw(self.cm.ta)
                except: pass
            glDisable(GL_POLYGON_OFFSET_FILL)
            if oc: glEnable(GL_CULL_FACE)
            if ob: glEnable(GL_BLEND)
            if not oo: glDisable(GL_POLYGON_OFFSET_FILL)
        
        self.flowing_liquid.draw_liquids(self.cm.ta)
        
        for projectile in self.projectiles:
            projectile.draw()
        
        self.mod_loader.bus.emit('render_entities', self.cam.x, self.cam.y, self.cam.z)
        
        if not self.cam.is_first_person():
            try: self.pm.draw(self.cam.x, self.cam.y, self.cam.z, self.cam.ry)
            except Exception as e: print(f"⚠️  Player model drawing error: {e}")
        self.setup_2d_rendering()
        try:
            self.ui_label.draw()
            self.draw_hotbar()
            self.mod_loader.bus.emit('render_ui', self.width, self.height)
            if self.mc:
                self.draw_crosshair()
                if self.show_stats: self.draw_performance_stats()
            if self.tutorial_open:
                self.draw_tutorial()
        except: pass
        self.restore_3d_rendering()

    def draw_tutorial(self):
        tw, th = 800, 600
        tx, ty = (self.width - tw) // 2, (self.height - th) // 2
        glDisable(GL_TEXTURE_2D)
        glEnable(GL_BLEND)
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
        glColor4f(0.0, 0.0, 0.2, 0.95)
        glBegin(GL_QUADS)
        glVertex2f(tx, ty)
        glVertex2f(tx + tw, ty)
        glVertex2f(tx + tw, ty + th)
        glVertex2f(tx, ty + th)
        glEnd()
        
        title = pyglet.text.Label('🌍 ENHANCED MINECRAFT SANDBOX TUTORIAL', font_name='Arial', font_size=18, x=tx + tw//2, y=ty + th - 30, anchor_x='center', color=(255, 255, 100, 255))
        title.draw()
        
        sections = [
            "🎮 BASIC CONTROLS:",
            "  ESC - Toggle mouse capture",
            "  WASD - Move around",
            "  Mouse - Look around",
            "  Space - Jump / Fly up",
            "  Left Shift - Fly down (in fly mode)",
            "  Left Ctrl - Sprint",
            "  K - Toggle camera (First/Third person)",
            "  J - Toggle fly mode",
            "",
            "🧱 BUILDING & INVENTORY:",
            "  Left Click - Place block",
            "  Right Click - Remove block / Dig terrain",
            "  1-8 - Select hotbar slots",
            "  TAB - Cycle through blocks",
            "  PageUp/PageDown - Switch hotbar pages",
            "",
            "🔨 CRAFTING & TRADING:",
            "  E - Open/Close crafting interface",
            "  G - Open/Close Grand Exchange",
            "",
            "🏹 NEW COMBAT FEATURES:",
            "  Fire Strike - Creates fire when used",
            "  Bow + Arrows - Shoot projectiles",
            "  Right-click with bow to shoot",
            "",
            "🌊 FLOWING LIQUIDS:",
            "  Water and Lava now flow naturally",
            "  Place water/lava blocks to see flow",
            "",
            "🌍 ENHANCED WORLD FEATURES:",
            "  🏔️ Realistic Mountains with Mountain Stone",
            "  🌊 Large Oceans & Lakes",
            "  🏖️ Sandy Beaches with Red Sand in Canyons",
            "  🗻 Grand Canyons with Layered Rock",
            "  ❄️ Snow Peaks with Ice Formations",
            "  🏜️ Desert Biomes",
            "  💧 Flowing Rivers",
            "  🎨 Biome Color Tinting",
            "",
            "Press I again to close tutorial"
        ]
        
        y_offset = ty + th - 70
        for line in sections:
            if line.startswith("🎮") or line.startswith("🧱") or line.startswith("🔨") or line.startswith("🏹") or line.startswith("🌊") or line.startswith("🌍"):
                color = (255, 200, 100, 255)
                size = 14
            elif line.startswith("  "):
                color = (200, 255, 200, 255)
                size = 11
            else:
                color = (255, 255, 255, 255)
                size = 12
            
            label = pyglet.text.Label(line, font_name='Arial', font_size=size, x=tx + 20, y=y_offset, color=color)
            label.draw()
            y_offset -= 20
        
        glDisable(GL_BLEND)

    def draw_performance_stats(self):
        try:
            fps = 1.0 / max(0.001, self.ft)
            fm = self.ft * 1000
            tc, rd, mc = len(self.cm.chunks), self.cm.rd, self.cm.mcpf
            
            pcx, pcz = self.cm.get_chunk_coords(self.cam.x, self.cam.z)
            current_biome = "Unknown"
            if (pcx, pcz) in self.cm.chunks:
                chunk = self.cm.chunks[(pcx, pcz)]
                wx, wz = int(self.cam.x), int(self.cam.z)
                if (wx, wz) in chunk.biome_data:
                    current_biome = chunk.biome_data[(wx, wz)]['biome'].title()
            
            if self.ema:
                fc, et = (255, 100, 100, 255) if fps < 30 else (255, 200, 100, 255), " [EMERGENCY]"
            elif fps >= 45: fc, et = (100, 255, 100, 255), " [OPTIMAL]"
            elif fps >= 30: fc, et = (255, 200, 100, 255), " [GOOD]"
            else: fc, et = (255, 100, 100, 255), " [POOR]"
            st = f"FPS: {fps:.0f}{et} | Frame: {fm:.1f}ms | Chunks: {self.cd}/{tc}"
            sl = pyglet.text.Label(st, font_name='Arial', font_size=10, x=10, y=self.height - 50, color=fc)
            sl.draw()
            mc = (255, 100, 100, 255) if self.ema else (200, 200, 255, 255)
            ot = f"Mode: {self.pm_mode} | Distance: {rd} | Max/Frame: {mc}"
            ol = pyglet.text.Label(ot, font_name='Arial', font_size=10, x=10, y=self.height - 65, color=mc)
            ol.draw()
            pt = f"Position: ({self.cam.x:.1f}, {self.cam.y:.1f}, {self.cam.z:.1f}) | Biome: {current_biome} | Blocks: {len(self.pb)}"
            pl = pyglet.text.Label(pt, font_name='Arial', font_size=9, x=10, y=self.height - 80, color=(200, 255, 200, 255))
            pl.draw()
            
            tm_mod = next((m for m in self.mod_loader.mods.values() if m.id == 'towns'), None)
            cm_mod = next((m for m in self.mod_loader.mods.values() if m.id == 'castles'), None)
            wl_mod = next((m for m in self.mod_loader.mods.values() if m.id == 'wildlife'), None)
            cc = len(cm_mod.cm.castles) if cm_mod else 0
            tc = len(tm_mod.tm.towns) if tm_mod else 0
            pc = len(wl_mod.parrots) if wl_mod else 0
            vc = len(wl_mod.villagers) if wl_mod else 0
            
            ct = f"🏰 Castles: {cc} | 🏘️ Towns: {tc} | 🦜 Parrots: {pc} | 👤 Villagers: {vc} | 🏹 Projectiles: {len(self.projectiles)}"
            cl = pyglet.text.Label(ct, font_name='Arial', font_size=9, x=10, y=self.height - 95, color=(255, 215, 0, 255))
            cl.draw()
            tm = f"🌊 Liquids: {len(self.flowing_liquid.liquid_blocks)} | 🔥 Fire Strike: {self.inv['fire_strike']['count']} | 🏹 Arrows: {self.inv['arrow']['count']}"
            tml = pyglet.text.Label(tm, font_name='Arial', font_size=9, x=10, y=self.height - 110, color=(150, 255, 150, 255))
            tml.draw()
        except: pass

    def draw_hotbar(self):
        try:
            hw, hh = len(self.hss) * 50 + 10, 80
            hx, hy = (self.width - hw) // 2, 20
            glDisable(GL_TEXTURE_2D)
            glEnable(GL_BLEND)
            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
            glColor4f(0, 0, 0, 0.6)
            glBegin(GL_QUADS)
            glVertex2f(hx, hy)
            glVertex2f(hx + hw, hy)
            glVertex2f(hx + hw, hy + hh)
            glVertex2f(hx, hy + hh)
            glEnd()
            pt = f"Page {self.chp + 1}/{len(self.hps)} (Scroll/PageUp/PageDown to change)"
            pl = pyglet.text.Label(pt, font_name='Arial', font_size=9, x=hx + hw//2, y=hy + hh - 15, anchor_x='center', color=(200, 200, 200, 255))
            pl.draw()
            for i, bt in enumerate(self.hss):
                sx, sy, sz = hx + 5 + i * 50, hy + 5, 40
                glColor4f(0.2, 1.0, 0.2, 0.9) if i == self.sel_slot else glColor4f(0.3, 0.3, 0.3, 0.8)
                glBegin(GL_QUADS)
                glVertex2f(sx, sy)
                glVertex2f(sx + sz, sy)
                glVertex2f(sx + sz, sy + sz)
                glVertex2f(sx, sy + sz)
                glEnd()
                bc = {
                    'grass': (0.2, 1.0, 0.2), 'grass_cover': (0.3, 1.0, 0.3), 'dirt': (0.6, 0.3, 0.1), 
                    'stone': (0.6, 0.6, 0.6), 'mountain_stone': (0.4, 0.4, 0.5), 'wood': (0.8, 0.4, 0.1), 
                    'jungle_wood': (0.4, 0.3, 0.1), 'dark_wood': (0.3, 0.2, 0.05), 'sand': (1.0, 0.9, 0.3), 
                    'red_sand': (0.8, 0.4, 0.2), 'water': (0.2, 0.5, 1.0), 'lava': (1.0, 0.3, 0.0), 
                    'fire': (1.0, 0.4, 0.0), 'leaves': (0.0, 0.8, 0.0), 'jungle_leaves': (0.1, 0.6, 0.1), 
                    'pine_leaves': (0.0, 0.4, 0.0), 'bricks': (0.9, 0.3, 0.2), 'beenest': (1.0, 1.0, 0.0), 
                    'gold': (1.0, 0.8, 0.0), 'planks': (0.7, 0.4, 0.2), 'cobblestone': (0.5, 0.5, 0.5), 
                    'wool_white': (1.0, 1.0, 1.0), 'wool_red': (1.0, 0.2, 0.2), 'wool_blue': (0.2, 0.2, 1.0), 
                    'wool_green': (0.2, 1.0, 0.2), 'wool_yellow': (1.0, 1.0, 0.2), 'wool_orange': (1.0, 0.6, 0.0), 
                    'wool_purple': (0.8, 0.2, 0.8), 'wool_pink': (1.0, 0.7, 0.8), 'beets': (0.5, 0.0, 0.5), 
                    'carrots': (1.0, 0.6, 0.0), 'roses': (1.0, 0.1, 0.6), 'flower': (1.0, 0.7, 0.8), 
                    'flower2': (0.5, 0.2, 0.9), 'lamps': (1.0, 1.0, 0.8), 'gold_bars': (1.0, 0.8, 0.0), 
                    'golden_sword': (1.0, 0.8, 0.0), 'snow': (0.9, 0.9, 1.0), 'ice': (0.7, 0.8, 1.0), 
                    'clay': (0.7, 0.4, 0.2), 'fire_strike': (1.0, 0.8, 0.0), 'bow': (0.6, 0.3, 0.1), 
                    'arrow': (0.7, 0.4, 0.2)
                }
                color = bc.get(bt, (1, 1, 1))
                glColor3f(*color)
                m = 8
                glBegin(GL_QUADS)
                glVertex2f(sx + m, sy + m)
                glVertex2f(sx + sz - m, sy + m)
                glVertex2f(sx + sz - m, sy + sz - m)
                glVertex2f(sx + m, sy + sz - m)
                glEnd()
                if bt in self.inv:
                    c = self.inv[bt]['count']
                    if c > 0:
                        cl = pyglet.text.Label(str(c), font_name='Arial', font_size=8, x=sx + sz - 15, y=sy + 2, color=(255, 255, 255, 255))
                        cl.draw()
                if i == self.sel_slot:
                    nl = pyglet.text.Label(bt.replace('_', ' ').title(), font_name='Arial', font_size=8, x=sx + sz//2, y=sy + sz + 5, anchor_x='center', color=(255, 255, 255, 255))
                    nl.draw()
            glDisable(GL_BLEND)
        except: pass

    def switch_hotbar_page(self, direction):
        self.chp = (self.chp + direction) % len(self.hps)
        self.hss = self.hps[self.chp]
        self.sel_slot, self.sbt = 0, self.hss[0]
        page_names = ["Basic Terrain", "Natural Features", "Building Blocks", "Decorative", "Special Items", "Combat & Tools"]
        print(f"📦 Switched to hotbar page {self.chp + 1}: {page_names[self.chp]} - {', '.join(self.hss)}")

    def draw_crosshair(self):
        cx, cy = self.width // 2, self.height // 2
        try:
            pp, tp = self.get_target_position()
            glColor3f(0, 1, 0)
            sz = 10
        except:
            glColor3f(1, 1, 1)
            sz = 8
        glDisable(GL_TEXTURE_2D)
        glLineWidth(2)
        glBegin(GL_LINES)
        glVertex2f(cx - sz, cy)
        glVertex2f(cx + sz, cy)
        glVertex2f(cx, cy - sz)
        glVertex2f(cx, cy + sz)
        glEnd()

    def setup_2d_rendering(self):
        glMatrixMode(GL_PROJECTION)
        glPushMatrix()
        glLoadIdentity()
        glOrtho(0, self.width, 0, self.height, -1, 1)
        glMatrixMode(GL_MODELVIEW)
        glPushMatrix()
        glLoadIdentity()
        glDisable(GL_DEPTH_TEST)

    def restore_3d_rendering(self):
        glEnable(GL_DEPTH_TEST)
        glPopMatrix()
        glMatrixMode(GL_PROJECTION)
        glPopMatrix()
        glMatrixMode(GL_MODELVIEW)

    def get_ray_direction(self):
        cy, sy = math.cos(math.radians(self.cam.ry)), math.sin(math.radians(self.cam.ry))
        cx, sx = math.cos(math.radians(self.cam.rx)), math.sin(math.radians(self.cam.rx))
        return sy * cx, -sx, -cy * cx

    def get_target_position(self):
        ss, md = 0.4, 8.0
        rdx, rdy, rdz = self.get_ray_direction()
        for i in range(1, int(md / ss)):
            d = i * ss
            tx, ty, tz = self.cam.x + rdx * d, self.cam.y + rdy * d, self.cam.z + rdz * d
            bx, by, bz = int(round(tx)), int(round(ty)), int(round(tz))
            
            hb = any(b.x == bx and b.y == by and b.z == bz for b in self.pb)
            
            chunk_x, chunk_z = self.cm.get_chunk_coords(bx, bz)
            if (chunk_x, chunk_z) in self.cm.chunks:
                chunk = self.cm.chunks[(chunk_x, chunk_z)]
                tb = (bx, by, bz) in chunk.blocks
            else:
                tb = False
                
            if hb or tb:
                pd = max(ss, (i - 1) * ss)
                px, py, pz = self.cam.x + rdx * pd, self.cam.y + rdy * pd, self.cam.z + rdz * pd
                pp = (int(round(px)), int(round(py)), int(round(pz)))
                return pp, (bx, by, bz)
        px, py, pz = self.cam.x + rdx * md, self.cam.y + rdy * md, self.cam.z + rdz * md
        tp = (int(round(px)), int(round(py)), int(round(pz)))
        return tp, tp

    def shoot_projectile(self):
        if self.sbt == 'bow' and self.inv['arrow']['count'] > 0:
            rdx, rdy, rdz = self.get_ray_direction()
            speed = 20.0
            projectile = Projectile(self.cam.x, self.cam.y, self.cam.z, rdx * speed, rdy * speed, rdz * speed, 'arrow')
            self.projectiles.append(projectile)
            self.inv['arrow']['count'] -= 1
            print(f"🏹 Arrow shot! Remaining: {self.inv['arrow']['count']}")
            return True
        return False

    def use_fire_strike(self, x, y, z):
        if self.sbt == 'fire_strike' and self.inv['fire_strike']['count'] > 0:
            fire_block = Block(x, y, z, 'fire')
            self.pb.append(fire_block)
            
            for dx in range(-1, 2):
                for dz in range(-1, 2):
                    if random.random() < 0.3:
                        fire_x, fire_z = x + dx, z + dz
                        fire_y = self.cm.get_height_at(fire_x, fire_z, self.mod_loader) + 1
                        if fire_y > 0:
                            fire_block = Block(fire_x, fire_y, fire_z, 'fire')
                            self.pb.append(fire_block)
            
            self.inv['fire_strike']['count'] -= 1
            print(f"🔥 Fire strike used! Remaining: {self.inv['fire_strike']['count']}")
            return True
        return False

    def on_mouse_press(self, x, y, button, modifiers):
        if not self.mc: return
        
        if button == pyglet.window.mouse.RIGHT and self.sbt == 'bow':
            self.shoot_projectile()
            return
        
        try: pp, tp = self.get_target_position()
        except: return
        
        if button == pyglet.window.mouse.LEFT:
            can_place = True
            for mod in self.mod_loader.get_mods_by_type(GameMod):
                if not mod.place(self.sbt, pp[0], pp[1], pp[2]):
                    can_place = False
                    break
            if can_place and (self.sbt in self.inv and self.inv[self.sbt]['count'] > 0):
                occupied = any(b.x == pp[0] and b.y == pp[1] and b.z == pp[2] for b in self.pb)
                player_collision = self.cam.check_collision(pp[0], pp[2], self.cam.y, self.cm, [])
                if not occupied and not player_collision and pp[1] >= -20:
                    
                    if self.sbt == 'fire_strike':
                        self.use_fire_strike(pp[0], pp[1], pp[2])
                    elif self.sbt in ['water', 'lava']:
                        self.flowing_liquid.add_liquid(pp[0], pp[1], pp[2], self.sbt, 8)
                        self.inv[self.sbt]['count'] -= 1
                    else:
                        nb = Block(*pp, self.sbt)
                        self.pb.append(nb)
                        self.inv[self.sbt]['count'] -= 1
                        
        elif button == pyglet.window.mouse.RIGHT:
            btr = []
            for block in self.pb[:]:
                if block.x == tp[0] and block.y == tp[1] and block.z == tp[2]:
                    can_break = True
                    for mod in self.mod_loader.get_mods_by_type(GameMod):
                        if not mod.break_(block.type, tp[0], tp[1], tp[2]):
                            can_break = False
                            break
                    if can_break:
                        bt = block.type
                        if bt in self.inv: self.inv[bt]['count'] += 1
                        btr.append(block)
            for block in btr: self.pb.remove(block)
            
            chunk_x, chunk_z = self.cm.get_chunk_coords(tp[0], tp[2])
            if (chunk_x, chunk_z) in self.cm.chunks:
                chunk = self.cm.chunks[(chunk_x, chunk_z)]
                if (tp[0], tp[1], tp[2]) in chunk.blocks:
                    bt = chunk.blocks[(tp[0], tp[1], tp[2])]
                    can_break = True
                    for mod in self.mod_loader.get_mods_by_type(GameMod):
                        if not mod.break_(bt, tp[0], tp[1], tp[2]):
                            can_break = False
                            break
                    if can_break:
                        del chunk.blocks[(tp[0], tp[1], tp[2])]
                        chunk.rebuild = True
                        if bt in self.inv: 
                            self.inv[bt]['count'] += 1
                        print(f"⛏️ Dug {bt} at ({tp[0]}, {tp[1]}, {tp[2]})")

    def on_mouse_motion(self, x, y, dx, dy):
        if self.mc: self.cam.mouse_look(dx, dy)

    def on_key_press(self, symbol, modifiers):
        consumed = self.mod_loader.bus.emit('key_press', symbol, modifiers)
        if any(consumed): return
        
        if symbol == pyglet.window.key.ESCAPE:
            self.mc = not self.mc
            self.set_exclusive_mouse(self.mc)
        elif symbol == pyglet.window.key.I:
            self.tutorial_open = not self.tutorial_open
        elif symbol == pyglet.window.key.TAB:
            self.sel_slot = (self.sel_slot + 1) % len(self.hss)
            self.sbt = self.hss[self.sel_slot]
        elif symbol == pyglet.window.key.PAGEUP: self.switch_hotbar_page(-1)
        elif symbol == pyglet.window.key.PAGEDOWN: self.switch_hotbar_page(1)
        elif symbol == pyglet.window.key.K: self.cam.toggle_cam()
        elif symbol in [pyglet.window.key._1, pyglet.window.key._2, pyglet.window.key._3, pyglet.window.key._4, pyglet.window.key._5, pyglet.window.key._6, pyglet.window.key._7, pyglet.window.key._8]:
            kn = symbol - pyglet.window.key._1
            if kn < len(self.hss):
                self.sel_slot, self.sbt = kn, self.hss[kn]
        elif symbol == pyglet.window.key.Q:
            if self.cm.pm == 0:
                self.cm.pm = 1
                self.cm.mcpf, self.cm.rd, self.cm.fova = 8, 3, 85
                self.pm_mode = "Quality"
                pyglet.clock.schedule_interval(self.update, 1/30.0)
            elif self.cm.pm == 1:
                self.cm.pm = 2
                self.cm.mcpf, self.cm.rd, self.cm.fova = 12, 4, 90
                self.pm_mode = "Ultra"
                pyglet.clock.schedule_interval(self.update, 1/30.0)
            else:
                self.cm.pm = 0
                self.cm.mcpf, self.cm.rd, self.cm.fova = 6, 2, 75
                self.pm_mode = "Enhanced"
                pyglet.clock.schedule_interval(self.update, 1/30.0)
            print(f"🎮 Performance mode: {self.pm_mode}")
        elif symbol == pyglet.window.key.Y:
            if self.dnc.dl > 300: self.dnc.dl = 120; print("⏰ Fast day/night cycle")
            elif self.dnc.dl > 120: self.dnc.dl = 60; print("⏰ Very fast day/night cycle")
            else: self.dnc.dl = 1200; print("⏰ Normal day/night cycle")
        elif symbol == pyglet.window.key.Z:
            if self.ema:
                self.ema, self.cm.rd, self.cm.mcpf = False, 3, 8
                self.cm.fova, self.pm_mode = 85, "Enhanced"
                print("✅ Emergency mode disabled")
            else: self.activate_emergency_mode()
        elif symbol == pyglet.window.key.O:
            if self.cm.rd > 1:
                self.cm.rd -= 1
                print(f"👁️  Render distance: {self.cm.rd}")
        elif symbol == pyglet.window.key.P:
            if self.cm.rd < 6:
                self.cm.rd += 1
                print(f"👁️  Render distance: {self.cm.rd}")
        elif symbol == pyglet.window.key.NUM_8:
            self.cm.ta.set_mode('8bit')
            print("🎨 8-bit texture mode enabled")
        elif symbol == pyglet.window.key.NUM_6:
            self.cm.ta.set_mode('16bit')
            print("🎨 16-bit texture mode enabled")
        elif symbol == pyglet.window.key.NUM_3:
            self.cm.ta.set_mode('normal')
            print("🎨 Normal texture mode enabled")
        elif symbol == pyglet.window.key.J:
            self.cam.fm = not self.cam.fm
            print(f"✈️  {'Fly' if self.cam.fm else 'Walking'} mode enabled")
        elif symbol == pyglet.window.key.R:
            self.pb.clear()
            self.flowing_liquid.liquid_blocks.clear()
            self.projectiles.clear()
            for bt in self.inv:
                if bt in ['lamps', 'golden_sword']:
                    self.inv[bt]['count'] = 0
                elif bt == 'gold_bars':
                    self.inv[bt]['count'] = 10
                elif bt in ['fire', 'lava']:
                    self.inv[bt]['count'] = 16
                elif bt == 'fire_strike':
                    self.inv[bt]['count'] = 3
                elif bt == 'bow':
                    self.inv[bt]['count'] = 1
                elif bt == 'arrow':
                    self.inv[bt]['count'] = 20
                elif bt in ['jungle_wood', 'dark_wood', 'jungle_leaves', 'pine_leaves', 'snow', 'ice', 'clay', 'red_sand', 'mountain_stone']:
                    self.inv[bt]['count'] = 32
                else:
                    self.inv[bt]['count'] = 64
            print("🔄 World reset - all blocks cleared and inventory refilled")
        elif symbol == pyglet.window.key.V:
            self.show_stats = not self.show_stats
            print(f"📊 Performance stats: {'ON' if self.show_stats else 'OFF'}")
        elif symbol == pyglet.window.key.N:
            self.dnc.time += 0.25
            if self.dnc.time >= 1.0: self.dnc.time -= 1.0
            print("⏭️  Time skipped")
        elif symbol == pyglet.window.key.L:
            self.le = not self.le
            if not self.le: glDisable(GL_LIGHTING)
            print(f"💡 Dynamic lighting: {'ON' if self.le else 'OFF'}")
        elif symbol == pyglet.window.key.F:
            oc = len(self.cm.chunks)
            self.cm.chunks.clear()
            self.cm.lpc = (None, None)
            self.cm.terrain_generator = MinecraftTerrainGenerator()
            for dx in range(-2, 3):
                for dz in range(-2, 3): self.cm.get_chunk(dx, dz, self.mod_loader)
            gh = self.cm.get_height_at(int(self.cam.x), int(self.cam.z), self.mod_loader)
            if gh != -1:
                self.cam.y = gh + 1 + self.cam.eh
                self.cam.og, self.cam.vy = True, 0
            print(f"🌍 Enhanced world regenerated with new terrain! Replaced {oc} chunks")
        elif symbol == pyglet.window.key.T:
            attempts = 0
            while attempts < 10:
                nx, nz = random.randint(-100, 100), random.randint(-100, 100)
                gh = self.cm.get_height_at(nx, nz, self.mod_loader)
                if gh > 5:
                    self.cam.x, self.cam.z, self.cam.y = nx, nz, gh + 1 + self.cam.eh
                    self.cam.vx = self.cam.vz = self.cam.vy = 0
                    self.cam.og = True
                    
                    pcx, pcz = self.cm.get_chunk_coords(nx, nz)
                    biome_info = "Unknown"
                    if (pcx, pcz) in self.cm.chunks:
                        chunk = self.cm.chunks[(pcx, pcz)]
                        if (nx, nz) in chunk.biome_data:
                            biome_info = chunk.biome_data[(nx, nz)]['biome'].title()
                    
                    print(f"🌟 Teleported to ({nx}, {nz}) | Height: {gh} | Biome: {biome_info}")
                    break
                attempts += 1
        elif symbol == pyglet.window.key.H:
            print("\n" + "="*80)
            print("🌍 ENHANCED MINECRAFT SANDBOX v2.0 - COMPLETE FEATURE GUIDE")
            print("="*80)
            print("🆕 NEW FEATURES IN v2.0:")
            print("  🔥 FIRE STRIKE - Creates fire when used, spreads to nearby areas")
            print("  🏹 BOW & ARROWS - Shoot projectiles with physics")
            print("  🌊 FLOWING LIQUIDS - Water and Lava flow naturally like Minecraft")
            print("  🏔️ MOUNTAIN STONE - Special stone type for realistic mountains")
            print("  🏜️ RED SAND - Found in canyons for layered geological appearance")
            print("  🧊 ICE BLOCKS - Transparent ice found in winter biomes")
            print("  ❄️ SNOW BLOCKS - Solid snow for building in winter areas")
            print("  🟤 CLAY BLOCKS - Found in ocean floors and river beds")
            print()
            print("🏹 COMBAT SYSTEM:")
            print("  Fire Strike - Right-click to create fire, spreads to flammable blocks")
            print("  Bow - Equip bow, then right-click to shoot arrows")
            print("  Arrows - Consumed when shot, affected by gravity")
            print("  Projectiles - Arrows stick in blocks, fire arrows ignite wood")
            print()
            print("🌊 LIQUID PHYSICS:")
            print("  Water - Flows downward and spreads horizontally")
            print("  Lava - Glows and flows like water but more viscous")
            print("  Flow System - Liquids find lowest points and spread naturally")
            print("  Level System - 8 levels per block for realistic flow")
            print()
            print("🌍 ENHANCED WORLD GENERATION:")
            print("  🏔️ Realistic Mountains - Multi-octave noise with mountain stone")
            print("  🌊 Large Oceans & Lakes - 35+ block radius lakes")
            print("  🏖️ Sandy Beaches - Realistic shorelines with proper transitions")
            print("  🗻 Grand Canyons - Deep systems with red sand layering")
            print("  🌲 Advanced Biomes - 10 unique biomes with color variations")
            print("  🎨 Biome Color Tinting - Grass, leaves, water change by location")
            print("  💧 Flowing Rivers - Connect to oceans and lakes naturally")
            print("  ❄️ Winter Biomes - Snow peaks with ice formations")
            print("  🌈 Height Variation - From -10 (ocean floor) to 80+ (peaks)")
            print()
            print("🎮 CONTROLS:")
            print("  ESC - Toggle mouse capture")
            print("  WASD - Move around")
            print("  Mouse - Look around")
            print("  Space - Jump / Fly up")
            print("  Left Shift - Fly down (in fly mode)")
            print("  Left Ctrl - Sprint")
            print("  K - Toggle camera mode (First/Third person)")
            print("  J - Toggle fly mode")
            print("  I - Open/Close interactive tutorial")
            print()
            print("🧱 BUILDING & INVENTORY:")
            print("  Left Click - Place block")
            print("  Right Click - Remove block / Dig terrain / Shoot bow")
            print("  1-8 - Select hotbar slots")
            print("  TAB - Cycle through blocks")
            print("  PageUp/PageDown - Switch hotbar pages (6 pages total)")
            print("    Page 1: Basic Terrain")
            print("    Page 2: Natural Features")  
            print("    Page 3: Building Blocks")
            print("    Page 4: Decorative")
            print("    Page 5: Special Items")
            print("    Page 6: Combat & Tools")
            print()
            print("🔨 CRAFTING & TRADING:")
            print("  E - Open/Close crafting interface")
            print("  G - Open/Close Grand Exchange")
            print("  Craft bows, arrows, fire strikes, and building materials")
            print()
            print("🎯 PERFORMANCE & QUALITY:")
            print("  Q - Cycle performance modes (Enhanced/Quality/Ultra)")
            print("  O/P - Adjust render distance (1-6)")
            print("  V - Toggle performance stats")
            print("  Z - Emergency performance mode")
            print("  8/6/3 - Texture quality (8-bit/16-bit/Normal)")
            print()
            print("🌤️ WORLD CONTROLS:")
            print("  Y - Change day/night speed")
            print("  N - Skip time")
            print("  L - Toggle dynamic lighting")
            print("  R - Reset world and inventory")
            print("  F - Regenerate world with new seed")
            print("  T - Smart teleport to interesting terrain")
            print()
            print("🎪 SPECIAL FEATURES:")
            print("  B - Toggle Herobrine AI")
            print("  M - Show mod information")
            print("  C - Show world/biome/wildlife information")
            print()
            print("💡 GAMEPLAY TIPS:")
            print("  - Use fire strike near wood to create spreading fires")
            print("  - Bow requires arrows - craft more when running low")
            print("  - Water and lava flow - place strategically")
            print("  - Mountain stone appears in high elevation areas")
            print("  - Red sand creates realistic canyon layers")
            print("  - Ice blocks are transparent and found in cold biomes")
            print("  - Each biome has unique block colors and features")
            print("  - Fly mode (J) is perfect for exploring terrain")
            print("  - Tutorial (I) provides interactive feature walkthrough")
            print("="*80)
        elif symbol == pyglet.window.key.B:
            self.mod_loader.bus.emit('toggle_herobrine')
        elif symbol == pyglet.window.key.M:
            print(f"\n🔧 LOADED MODS ({len(self.mod_loader.mods)}):")
            for mod_id, mod in self.mod_loader.mods.items():
                status = "✅" if mod.en else "❌"
                print(f"  {status} {mod.n} v{mod.v} ({mod_id})")
                if isinstance(mod, BlockMod):
                    print(f"    Type: Block Mod - {len(mod.blocks())} blocks")
                elif isinstance(mod, WorldMod):
                    print(f"    Type: World Generation Mod")
                elif isinstance(mod, UIMod):
                    print(f"    Type: UI Mod")
                elif isinstance(mod, GameMod):
                    print(f"    Type: Gameplay Mod")
            print(f"\n📂 ENHANCED WORLD GENERATION v2.0:")
            print(f"  🌍 Advanced terrain with realistic biome transitions")
            print(f"  🏔️ Multi-octave noise for natural mountains")
            print(f"  🌊 Large water bodies with proper flow physics")
            print(f"  🎨 Biome-based color tinting system")
            print(f"  🗻 Canyon and river generation with geological layers")
            print(f"  🏹 Combat system with projectile physics")
            print(f"  🔥 Fire strike with spreading mechanics")
        elif symbol == pyglet.window.key.C:
            tm_mod = next((m for m in self.mod_loader.mods.values() if m.id == 'towns'), None)
            cm_mod = next((m for m in self.mod_loader.mods.values() if m.id == 'castles'), None)
            wl_mod = next((m for m in self.mod_loader.mods.values() if m.id == 'wildlife'), None)
            wt_mod = next((m for m in self.mod_loader.mods.values() if m.id == 'weather'), None)
            
            cc = len(cm_mod.cm.castles) if cm_mod else 0
            tc = len(tm_mod.tm.towns) if tm_mod else 0
            pc = len(wl_mod.parrots) if wl_mod else 0
            vc = len(wl_mod.villagers) if wl_mod else 0
            weather = wt_mod.weather.weather if wt_mod else "unknown"
            
            pcx, pcz = self.cm.get_chunk_coords(self.cam.x, self.cam.z)
            current_biome = "Unknown"
            biome_details = {}
            
            if (pcx, pcz) in self.cm.chunks:
                chunk = self.cm.chunks[(pcx, pcz)]
                wx, wz = int(self.cam.x), int(self.cam.z)
                if (wx, wz) in chunk.biome_data:
                    biome_data = chunk.biome_data[(wx, wz)]
                    current_biome = biome_data['biome'].title()
                    biome_details = {
                        'temperature': biome_data['temperature'],
                        'humidity': biome_data['humidity'],
                        'color_mod': biome_data['color_mod']
                    }
            
            terrain_height = self.cm.get_height_at(int(self.cam.x), int(self.cam.z), self.mod_loader)
            
            print(f"\n🌍 ENHANCED WORLD INFORMATION v2.0:")
            print(f"📍 Current Position: ({self.cam.x:.1f}, {self.cam.y:.1f}, {self.cam.z:.1f})")
            print(f"🏔️ Terrain Height: {terrain_height}")
            print(f"🌿 Current Biome: {current_biome}")
            if biome_details:
                print(f"🌡️ Temperature: {biome_details['temperature']:.2f}")
                print(f"💧 Humidity: {biome_details['humidity']:.2f}")
                print(f"🎨 Color Tint: RGB{biome_details['color_mod']}")
            print(f"🌤️ Current Weather: {weather.title()}")
            print(f"🏰 Elaborate Castles Found: {cc}")
            print(f"🏘️ Towns Found: {tc}")
            print(f"🦜 Active Parrots: {pc}")
            print(f"👤 Active Villagers: {vc}")
            print(f"🏹 Active Projectiles: {len(self.projectiles)}")
            print(f"🌊 Flowing Liquid Blocks: {len(self.flowing_liquid.liquid_blocks)}")
            
            print(f"\n🗺️ WORLD GENERATION FEATURES v2.0:")
            print(f"  🌊 Ocean Depth: Down to -10 blocks")
            print(f"  🏔️ Mountain Height: Up to 80+ blocks")
            print(f"  🏞️ Biome Diversity: 10 unique biomes with color variations")
            print(f"  💧 Water Features: Large lakes (35+ block radius)")
            print(f"  🗻 Terrain Variety: Canyons with red sand, rivers, beaches")
            print(f"  🧱 New Block Types: Mountain stone, red sand, ice, snow, clay")
            print(f"  🔥 Combat Features: Fire strike, bow & arrows with physics")
            print(f"  🌊 Liquid Physics: Realistic water and lava flow")
            
            if cc > 0 and cm_mod:
                print(f"\n🏰 CASTLE DETAILS:")
                for i, castle in enumerate(cm_mod.cm.castles):
                    d = math.sqrt((castle.cx - self.cam.x)**2 + (castle.cz - self.cam.z)**2)
                    print(f"  Castle {i+1}: Center ({castle.cx}, {castle.cz}) - Distance: {d:.1f}m")
                    print(f"    Structures: {len(castle.st)} | Size: {castle.ows}x{castle.ows}")
            
            if tc > 0 and tm_mod:
                print(f"\n🏘️ TOWN DETAILS:")
                for i, town in enumerate(tm_mod.tm.towns):
                    d = math.sqrt((town.cx - self.cam.x)**2 + (town.cz - self.cam.z)**2)
                    print(f"  Town {i+1}: Center ({town.cx}, {town.cz}) - Distance: {d:.1f}m")
            
            if wl_mod:
                print(f"\n🦜 WILDLIFE STATUS:")
                if pc > 0:
                    for i, parrot in enumerate(wl_mod.parrots):
                        d = math.sqrt((parrot.x - self.cam.x)**2 + (parrot.z - self.cam.z)**2)
                        print(f"  Parrot {i+1}: {parrot.color} at distance {d:.1f}m, state: {parrot.state}")
                if vc > 0:
                    for i, villager in enumerate(wl_mod.villagers):
                        d = math.sqrt((villager.x - self.cam.x)**2 + (villager.z - self.cam.z)**2)
                        print(f"  Villager {i+1}: at distance {d:.1f}m")
            
            print(f"\n🎮 GAMEPLAY STATUS:")
            print(f"  🔥 Fire Strikes Remaining: {self.inv['fire_strike']['count']}")
            print(f"  🏹 Arrows Remaining: {self.inv['arrow']['count']}")
            print(f"  🏹 Bows: {self.inv['bow']['count']}")
            print(f"  🌊 Active Liquid Flows: {len(self.flowing_liquid.liquid_blocks)}")
            print(f"  🎯 Active Projectiles: {len(self.projectiles)}")
            
            print(f"\n💡 EXPLORATION SUGGESTIONS:")
            print(f"  🗺️ Try teleporting (T) to discover new biomes and terrain")
            print(f"  ✈️ Use fly mode (J) to explore mountains and canyons safely")
            print(f"  🌊 Follow rivers to find where they connect to lakes or oceans")
            print(f"  🎨 Notice how block colors change between different biomes")
            print(f"  🔥 Use fire strike to create controlled burns")
            print(f"  🏹 Practice archery - arrows are affected by gravity!")
            print(f"  🌊 Experiment with water and lava flow mechanics")

    def on_mouse_scroll(self, x, y, sx, sy):
        if self.mc:
            if sy > 0: self.switch_hotbar_page(1)
            elif sy < 0: self.switch_hotbar_page(-1)

    
    def auto_adjust_performance(self):
        """Automatically adjust performance settings based on FPS"""
        fps = 1.0 / max(0.001, self.ft)
        
        if fps < 25:
            # Reduce quality settings
            self.cm.rd = max(1, self.cm.rd - 1)
            self.cm.mcpf = max(2, self.cm.mcpf - 1)
            self.le = False  # Disable lighting
            glDisable(GL_LIGHTING)
            print(f"🔧 Auto-reduced quality: render distance={self.cm.rd}, chunks/frame={self.cm.mcpf}")
        elif fps > 45 and self.cm.rd < 3:
            # Increase quality if performance allows
            self.cm.rd = min(3, self.cm.rd + 1)
            self.cm.mcpf = min(6, self.cm.mcpf + 1)
            print(f"🔧 Auto-increased quality: render distance={self.cm.rd}, chunks/frame={self.cm.mcpf}")

    def on_resize(self, w, h):
        try:
            glViewport(0, 0, w, h)
            glMatrixMode(GL_PROJECTION)
            glLoadIdentity()
            self.setup_perspective()
            glMatrixMode(GL_MODELVIEW)
        except: pass
        self.ui_label.y = h - 25

def save_example_mods():
    if not os.path.exists("mods"):
        os.makedirs("mods")
        
    enhanced_biome_mod_code = '''
from sandbox_builder import BlockMod

class EnhancedBiomeMod(BlockMod):
    def __init__(self):
        super().__init__("enhanced_biome_blocks", "Enhanced Biome-Specific Blocks", "2.0")
        
    def init(self, bus):
        super().init(bus)
        print("🌿 Enhanced biome blocks mod loaded!")
        
    def blocks(self):
        return {
            'cactus': {'solid': False, 'transparent': True, 'billboard': True},
            'coral': {'solid': True, 'transparent': False, 'billboard': False},
            'bamboo': {'solid': False, 'transparent': True, 'billboard': True},
            'mushroom': {'solid': False, 'transparent': True, 'billboard': True},
            'crystal': {'solid': True, 'transparent': True, 'billboard': False},
            'volcanic_rock': {'solid': True, 'transparent': False, 'billboard': False}
        }
        
    def textures(self):
        return {
            'cactus': ['cactus.png'],
            'coral': ['coral.png'], 
            'bamboo': ['bamboo.png'],
            'mushroom': ['mushroom.png'],
            'crystal': ['crystal.png'],
            'volcanic_rock': ['volcanic_rock.png']
        }

def create_mod():
    return EnhancedBiomeMod()
'''

    combat_mod_code = '''
from sandbox_builder import GameMod
import random

class CombatEnhancementMod(GameMod):
    def __init__(self):
        super().__init__("combat_enhancement", "Combat Enhancement", "2.0")
        
    def init(self, bus):
        super().init(bus)
        bus.on('projectile_hit', self.on_projectile_hit)
        print("⚔️ Combat enhancement mod loaded!")
        
    def on_projectile_hit(self, projectile, target_pos):
        if projectile.type == 'fire_arrow':
            x, y, z = target_pos
            for dx in range(-2, 3):
                for dz in range(-2, 3):
                    if random.random() < 0.4:
                        fire_x, fire_z = x + dx, z + dz
                        print(f"🔥 Fire arrow created blaze at ({fire_x}, {fire_z})")
                        
    def place(self, bt, x, y, z):
        if bt == 'fire_strike':
            print(f"🔥 Fire strike activated at ({x}, {y}, {z})")
        return True

def create_mod():
    return CombatEnhancementMod()
'''

    try:
        with open("mods/enhanced_biome_blocks.py", "w", encoding='utf-8') as f:
            f.write(enhanced_biome_mod_code)
        with open("mods/combat_enhancement.py", "w", encoding='utf-8') as f:
            f.write(combat_mod_code)
        print("📝 Enhanced example mods saved to /mods folder")
    except Exception as e:
        print(f"❌ Failed to save example mods: {e}")

def main():
    try:
        print("🚀 STARTING ENHANCED MINECRAFT SANDBOX v2.0...")
        print(f"📂 Working directory: {os.getcwd()}")
        print(f"📄 Python script location: {os.path.abspath(__file__)}")
        print("\n🔧 SYSTEM CHECK:")
        print(f"   NumPy Available: {'✅ YES' if NUMPY_AVAILABLE else '❌ NO (Basic AI mode)'}")
        print(f"   PyOpenGL Available: {'✅ YES' if OPENGL_AVAILABLE else '❌ NO (Using pyglet OpenGL)'}")
        
        save_example_mods()
        
        game = ModularSandboxGame()
        print("\n✅ ENHANCED GAME v2.0 INITIALIZED!")
        print("\n🆕 NEW FEATURES IN v2.0:")
        print("   🔥 FIRE STRIKE - Creates spreading fire effects")
        print("   🏹 BOW & ARROWS - Projectile combat with physics")
        print("   🌊 FLOWING LIQUIDS - Realistic water and lava flow")
        print("   🏔️ MOUNTAIN STONE - Special stone for realistic peaks")
        print("   🏜️ RED SAND - Canyon layering and desert features")
        print("   🧊 ICE & SNOW - Winter biome building blocks")
        print("   🟤 CLAY - River beds and ocean floor material")
        print("   🎨 ENHANCED BIOMES - Improved color tinting system")
        print("   📚 INTERACTIVE TUTORIAL - Press I for guided walkthrough")
        print("\n🎮 QUICK START CONTROLS:")
        print("   ESC - Capture/release mouse")
        print("   WASD - Move around")
        print("   Mouse - Look around")
        print("   Space - Jump / Fly up")
        print("   K - Toggle camera mode")
        print("   J - Toggle fly mode")
        print("   I - Open interactive tutorial")
        print("   Left Click - Place blocks")
        print("   Right Click - Remove blocks / Shoot bow")
        print("   1-8 - Select different blocks")
        print("   PageUp/PageDown - Switch hotbar pages (6 pages)")
        print("   T - Smart teleport to interesting terrain")
        print("   F - Regenerate world with new terrain")
        print("   H - Show detailed help")
        print("\n🔥 COMBAT FEATURES:")
        print("   Fire Strike - Right-click to create spreading fire")
        print("   Bow - Equip bow, right-click to shoot arrows")
        print("   Arrows - Physics-based projectiles affected by gravity")
        print("   Fire Spread - Fire can spread to flammable blocks")
        print("\n🌊 LIQUID PHYSICS:")
        print("   Water & Lava - Flow naturally downward and spread")
        print("   8 Flow Levels - Realistic liquid depth simulation")
        print("   Flow Queue - Efficient liquid propagation system")
        print("   Place liquids and watch them flow!")
        print("\n🗺️ WORLD GENERATION v2.0:")
        print("   🌍 Enhanced terrain with realistic biome transitions")
        print("   🏔️ Mountains with mountain stone at high elevations")
        print("   🌊 Large lakes spaced realistically apart")
        print("   🏖️ Beaches form naturally around water bodies")
        print("   🗻 Canyons with red sand geological layers")
        print("   💧 Rivers flow toward larger water bodies")
        print("   ❄️ Snow peaks with ice formations")
        print("   🏜️ Desert biomes with appropriate materials")
        print("   🎨 Biome-specific block color variations")
        print("\n🧱 NEW BLOCKS & MATERIALS:")
        print("   🏔️ Mountain Stone - Found in high elevation areas")
        print("   🏜️ Red Sand - Canyon layers and desert features")
        print("   ❄️ Snow - Solid blocks for winter construction")
        print("   🧊 Ice - Transparent blocks in frozen areas")
        print("   🟤 Clay - River beds and ocean floor material")
        print("   🔥 Fire Strike - Creates fire with spreading effects")
        print("   🏹 Bow & Arrows - Combat and hunting tools")
        print("\n🔧 MOD SYSTEM v2.0:")
        print("   💎 Enhanced modular architecture")
        print("   🌍 Advanced terrain generation hooks")
        print("   ⚔️ Combat system integration")
        print("   🌊 Liquid physics API")
        print("   📦 6-page hotbar system")
        print("   📂 Hot-loadable from /mods folder")
        print("   📝 Enhanced example mods included")
        print("\n💡 GAMEPLAY TIPS:")
        print("   - Fire strike spreads to nearby flammable blocks")
        print("   - Bow requires arrows - craft more when needed")
        print("   - Water and lava flow realistically - plan accordingly")
        print("   - Each biome has unique block colors and materials")
        print("   - Mountain stone appears naturally in high areas")
        print("   - Red sand creates realistic canyon geological layers")
        print("   - Ice is transparent and great for windows")
        print("   - Use tutorial (I) for interactive feature guide")
        print("\n🎯 ENHANCED GAME v2.0 READY!")
        print("   Press ESC in-game to capture mouse and start exploring!")
        print("   Press I for interactive tutorial!")
        print("   Try the new combat and liquid systems!")
        print("="*80)
        pyglet.app.run()
    except Exception as e:
        print(f"\n❌ ERROR STARTING ENHANCED GAME v2.0: {e}")
        import traceback
        traceback.print_exc()
        print("\n🛠️  TROUBLESHOOTING:")
        print("   1. Make sure Python has OpenGL support")
        print("   2. Install pyglet: pip install pyglet")
        print("   3. Install numpy (optional but recommended): pip install numpy")
        print("   4. Check your graphics drivers are updated")
        print("   5. Check mod files in /mods folder for syntax errors")
        print("   6. Try reducing performance settings with Q key once started")
        print("   7. Use emergency mode (Z key) if performance is poor")
        input("\nPress Enter to exit...")

if __name__ == "__main__": main()