#!/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

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

class NoiseGenerator:
    def __init__(self, seed=None):
        if seed is not None:
            random.seed(seed)
        self.perm = list(range(256))
        random.shuffle(self.perm)
        self.perm += self.perm
        
    def fade(self, t):
        return t * t * t * (t * (t * 6 - 15) + 10)
    
    def lerp(self, t, a, b):
        return a + t * (b - a)
    
    def grad(self, hash_val, x, y):
        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 0)
        return (u if (h & 1) == 0 else -u) + (v if (h & 2) == 0 else -v)
    
    def noise(self, x, y):
        X = int(x) & 255
        Y = int(y) & 255
        x -= int(x)
        y -= int(y)
        
        u = self.fade(x)
        v = self.fade(y)
        
        A = self.perm[X] + Y
        B = self.perm[X + 1] + Y
        
        return self.lerp(v, 
            self.lerp(u, self.grad(self.perm[A], x, y), 
                         self.grad(self.perm[B], x - 1, y)),
            self.lerp(u, self.grad(self.perm[A + 1], x, y - 1), 
                         self.grad(self.perm[B + 1], x - 1, y - 1)))
    
    def octave_noise(self, x, y, octaves=4, persistence=0.5, scale=1.0):
        value = 0
        amplitude = 1
        frequency = scale
        max_value = 0
        
        for i in range(octaves):
            value += self.noise(x * frequency, y * frequency) * amplitude
            max_value += amplitude
            amplitude *= persistence
            frequency *= 2
        
        return value / max_value

class BiomeManager:
    def __init__(self):
        self.biomes = {
            'ocean': {'color_mod': (0.3, 0.5, 0.8), 'base_height': -15, 'vegetation': 0.0},
            'beach': {'color_mod': (1.0, 0.9, 0.7), 'base_height': 2, 'vegetation': 0.1},
            'plains': {'color_mod': (0.6, 0.8, 0.4), 'base_height': 5, 'vegetation': 0.6},
            'forest': {'color_mod': (0.3, 0.7, 0.3), 'base_height': 8, 'vegetation': 0.9},
            'hills': {'color_mod': (0.5, 0.6, 0.4), 'base_height': 15, 'vegetation': 0.4},
            'mountains': {'color_mod': (0.7, 0.7, 0.8), 'base_height': 25, 'vegetation': 0.1},
            'snow_peaks': {'color_mod': (0.9, 0.9, 1.0), 'base_height': 40, 'vegetation': 0.0},
            'desert': {'color_mod': (1.0, 0.8, 0.3), 'base_height': 4, 'vegetation': 0.05},
            'river': {'color_mod': (0.4, 0.6, 0.9), 'base_height': 1, 'vegetation': 0.3},
            'canyon': {'color_mod': (0.8, 0.4, 0.2), 'base_height': -5, 'vegetation': 0.1}
        }
    
    def get_biome(self, x, z, elevation, temperature, humidity):
        if elevation < -10:
            return 'ocean'
        elif elevation < 3 and self.near_water(x, z):
            return 'beach'
        elif elevation > 35:
            return 'snow_peaks'
        elif elevation > 20:
            return 'mountains'
        elif elevation > 12:
            return 'hills'
        elif temperature < 0.3 and humidity < 0.3:
            return 'desert'
        elif humidity > 0.6:
            return 'forest'
        else:
            return 'plains'
    
    def near_water(self, x, z):
        return False
    
    def get_biome_data(self, biome_name):
        return self.biomes.get(biome_name, self.biomes['plains'])

class TerrainGenerator:
    def __init__(self, seed=None):
        self.seed = seed or random.randint(0, 1000000)
        self.noise = NoiseGenerator(self.seed)
        self.biome_manager = BiomeManager()
        self.base_level = -10
        
    def generate_base_terrain(self, x, z):
        continental = self.noise.octave_noise(x * 0.0008, z * 0.0008, 4, 0.7) * 25
        regional = self.noise.octave_noise(x * 0.002, z * 0.002, 3, 0.6) * 15
        local = self.noise.octave_noise(x * 0.008, z * 0.008, 2, 0.5) * 8
        detail = self.noise.octave_noise(x * 0.02, z * 0.02, 2, 0.3) * 3
        
        return continental + regional + local + detail
    
    def generate_mountains(self, x, z, base_elevation):
        ridge_noise = abs(self.noise.octave_noise(x * 0.003, z * 0.003, 4, 0.6))
        mountain_mask = max(0, (base_elevation - 8) / 15.0)
        
        if mountain_mask > 0.3:
            mountain_height = (1 - ridge_noise) * mountain_mask * 35
            return base_elevation + mountain_height
        return base_elevation
    
    def generate_canyons(self, x, z, elevation):
        canyon_noise = abs(self.noise.octave_noise(x * 0.006, z * 0.006, 3, 0.7))
        canyon_mask = self.noise.octave_noise(x * 0.001, z * 0.001, 2, 0.5)
        
        if canyon_mask > 0.4 and elevation > 3:
            canyon_depth = canyon_noise * 20
            return max(self.base_level, elevation - canyon_depth)
        return elevation
    
    def generate_rivers(self, x, z, elevation):
        river_noise = self.noise.octave_noise(x * 0.015, z * 0.015, 2, 0.5)
        
        if abs(river_noise) < 0.08 and elevation > self.base_level:
            distance_to_river = abs(river_noise) * 12
            if distance_to_river < 2:
                river_depth = (2 - distance_to_river) * 1.5
                return max(self.base_level, elevation - river_depth)
        return elevation
    
    def place_lakes(self, x, z, elevation):
        lake_seed = hash((int(x // 400), int(z // 400), 'lake')) % 1000000
        random.seed(lake_seed)
        
        if random.random() < 0.12:
            lake_center_x = (int(x // 400)) * 400 + random.randint(100, 300)
            lake_center_z = (int(z // 400)) * 400 + random.randint(100, 300)
            
            dist_to_center = math.sqrt((x - lake_center_x)**2 + (z - lake_center_z)**2)
            
            if dist_to_center < 35:
                depth_factor = 1 - (dist_to_center / 35)
                lake_depth = depth_factor * 6
                return max(self.base_level, elevation - lake_depth)
        
        return elevation
    
    def generate_temperature(self, x, z):
        latitude_factor = math.sin((z * 0.0008) % (2 * math.pi))
        temp_noise = self.noise.octave_noise(x * 0.002, z * 0.002, 3, 0.5)
        return 0.5 + latitude_factor * 0.3 + temp_noise * 0.2
    
    def generate_humidity(self, x, z):
        humidity_noise = self.noise.octave_noise(x * 0.003, z * 0.003, 3, 0.6)
        return 0.5 + humidity_noise * 0.5
    
    def generate_terrain_height(self, x, z):
        base = self.generate_base_terrain(x, z)
        with_mountains = self.generate_mountains(x, z, base)
        with_canyons = self.generate_canyons(x, z, with_mountains)
        with_rivers = self.generate_rivers(x, z, with_canyons)
        final = self.place_lakes(x, z, with_rivers)
        
        return max(self.base_level, int(final))

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):
        self.flow_timer += dt
        if self.flow_timer > 0.1:
            self.flow_timer = 0
            self.process_flow()
    
    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):
        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
        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):
        glEnable(GL_BLEND)
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
        glPushMatrix()
        glTranslatef(x, y, z)
        
        if ta:
            ta.bind()
            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)

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):
        self.life -= dt
        if self.life <= 0:
            return False
            
        self.x += self.dx * dt
        self.y += self.dy * dt
        self.z += self.dz * dt
        self.dy += self.gravity * dt
        
        bx, by, bz = int(round(self.x)), int(round(self.y)), int(round(self.z))
        
        for block in game.pb:
            if block.x == bx and block.y == by and block.z == bz:
                if self.type == 'fire_arrow' and block.type in ['wood', 'leaves', 'planks']:
                    fire_block = Block(bx, by + 1, bz, 'fire')
                    game.pb.append(fire_block)
                return False
        
        return True
    
    def draw(self):
        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()

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:
            m.init(self.bus)
            self.mods[m.id] = m
            self.order.append(m.id)
            m.enable()
            print(f"✅ Loaded mod: {m.n} v{m.v}")
        except Exception as e:
            print(f"❌ Failed to load mod {m.id}: {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': ['roses.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):
        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()

    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):
        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)
        
    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):
        ct = time.time()
        self.walk_time += dt
        
        if ct - self.last_target_time > random.uniform(5, 15):
            self.direction = random.uniform(0, 2 * math.pi)
            distance = random.uniform(5, 15)
            self.tx = self.x + math.cos(self.direction) * distance
            self.tz = self.z + math.sin(self.direction) * distance
            self.last_target_time = ct
            
        dx, dz = self.tx - self.x, self.tz - self.z
        d = math.sqrt(dx*dx + dz*dz)
        
        if d > 0.5:
            move_speed = self.speed * dt
            self.x += (dx / d) * move_speed
            self.z += (dz / d) * move_speed
            
    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 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 OptimizedChunkMesh:
    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):
        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):
        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)]}
        [self.verts.extend(v) for v in faces[fd]]
        self.inds.extend([bi, bi + 1, bi + 2, bi, bi + 2, bi + 3])
        self.fc += 1
        self.rebuild = True

    def build_vbo(self):
        if not self.verts or not self.inds: return
        self.vc, self.ic = len(self.verts) // 5, len(self.inds)
        try:
            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
            
            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)
            
            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: self.vbo = self.ibo = None

    def draw(self):
        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
            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: self.draw_immediate()

    def draw_immediate(self):
        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: pass

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):
        ct = time.time()
        if ct - self.lut < self.ui: return
        self.lut = ct
        self.time += dt / self.dl
        if self.time >= 1.0: self.time -= 1.0
        self.clc = self.csc = None

    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 = TerrainGenerator()
        self.base_level = -10
        self.gen_terrain()

    def gen_terrain(self):
        for x in range(self.sz):
            for z in range(self.sz):
                wx, wz = self.cx * self.sz + x, self.cz * self.sz + z
                
                terrain_height = self.terrain_gen.generate_terrain_height(wx, wz)
                temperature = self.terrain_gen.generate_temperature(wx, wz)
                humidity = self.terrain_gen.generate_humidity(wx, wz)
                biome = self.terrain_gen.biome_manager.get_biome(wx, wz, terrain_height, temperature, humidity)
                biome_data = self.terrain_gen.biome_manager.get_biome_data(biome)
                
                self.biome_data[(wx, wz)] = {
                    'biome': biome,
                    'temperature': temperature,
                    'humidity': humidity,
                    'color_mod': biome_data['color_mod']
                }
                
                self.generate_terrain_layers(wx, wz, terrain_height, biome)
                
        self.gen_biome_features()
        self.gen_trees()
        self.gen_beenests()
        self.gen_crops()
        self.gen_flowers()
        self.gen_grass_cover()
        if not self.cs and random.random() < 0.02: self.gen_castle()
        self.rebuild = True
    
    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.15
                tree_types = ['oak', 'oak', 'jungle']
            elif biome == 'plains':
                tree_chance = 0.08
                tree_types = ['oak']
            elif biome == 'hills':
                tree_chance = 0.12
                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.3
            elif biome == 'river':
                grass_chance = 0.4
            
            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.1
                flower_types = ['flower', 'flower2', 'roses']
            elif biome == 'forest':
                flower_chance = 0.05
                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):
        if not self.rebuild: return
        self.mesh.clear()
        sb, tb, bb = [], [], []
        for (x, y, z), bt in self.blocks.items():
            if self.is_billboard_block(bt): bb.append(((x, y, z), bt))
            elif self.is_transparent_block(bt): tb.append(((x, y, z), bt))
            else: sb.append(((x, y, z), bt))
        
        for (x, y, z), bt in sb:
            u1, v1, u2, v2 = self.ta.get_uv(bt) if self.ta else (0, 0, 1, 1)
            tint = self.apply_biome_tinting(bt, x, z)
            
            for fd in ['top', 'bottom', 'front', 'back', 'left', 'right']:
                if self.is_face_visible(x, y, z, fd): 
                    self.mesh.add_cube_face(float(x), float(y), float(z), fd, u1, v1, u2, v2)
        
        if hasattr(self, 'cs') and self.cs:
            for s in self.cs:
                if s['material'] != 'air':
                    u1, v1, u2, v2 = self.ta.get_uv(s['material']) if self.ta else (0, 0, 1, 1)
                    x, y, z = s['x'], s['y'], s['z']
                    for fd in ['top', 'bottom', 'front', 'back', 'left', 'right']: 
                        self.mesh.add_cube_face(float(x), float(y), float(z), fd, 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 = self.ta.get_uv('wood') if self.ta else (0, 0, 1, 1)
                tree_height = random.randint(4, 6)
                for yo in range(tree_height):
                    yp = trunk_base + yo
                    for fd in ['top', 'bottom', 'front', 'back', 'left', 'right']: 
                        self.mesh.add_cube_face(float(tx), float(yp), float(tz), fd, wu1, wv1, wu2, wv2)
                ly = trunk_base + tree_height - 1
                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):
                            tb.append(((tx + dx, ly, tz + dz), 'leaves'))
                            if abs(dx) <= 1 and abs(dz) <= 1:
                                tb.append(((tx + dx, ly + 1, tz + dz), 'leaves'))
                tb.append(((tx, ly + 2, tz), 'leaves'))
                
            elif tt in ['pine', 'dark_oak']:
                wood_type = 'dark_wood' if tt == 'dark_oak' else 'dark_wood'
                wu1, wv1, wu2, wv2 = self.ta.get_uv(wood_type) if self.ta else (0, 0, 1, 1)
                th = random.randint(6, 10)
                for yo in range(th):
                    yp = trunk_base + yo
                    for fd in ['top', 'bottom', 'front', 'back', 'left', 'right']: 
                        self.mesh.add_cube_face(float(tx), float(yp), float(tz), fd, wu1, wv1, wu2, wv2)
                
                for level in range(4):
                    ly = trunk_base + th - 1 - 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):
                                tb.append(((tx + dx, ly, tz + dz), 'pine_leaves'))
                                
            elif tt == 'jungle':
                wu1, wv1, wu2, wv2 = self.ta.get_uv('jungle_wood') if self.ta else (0, 0, 1, 1)
                th = random.randint(8, 14)
                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:
                                    for fd in ['top', 'bottom', 'front', 'back', 'left', 'right']: 
                                        self.mesh.add_cube_face(float(tx + dx), float(yp), float(tz + dz), fd, wu1, wv1, wu2, wv2)
                    else:
                        for fd in ['top', 'bottom', 'front', 'back', 'left', 'right']: 
                            self.mesh.add_cube_face(float(tx), float(yp), float(tz), fd, wu1, wv1, wu2, wv2)
                
                ly = trunk_base + th - 2
                for level in range(3):
                    cy = ly + level
                    r = 5 - level
                    for dx in range(-r, r + 1):
                        for dz in range(-r, r + 1):
                            if dx*dx + dz*dz <= r*r:
                                tb.append(((tx + dx, cy, tz + dz), 'jungle_leaves'))
        
        for gx, gz in getattr(self, 'grass_cover', []):
            sh = self.sh.get((gx, gz), 3)
            grass_y = sh + 1
            bb.append(((gx, grass_y, gz), 'grass_cover'))
        
        for bx, bz in self.bn:
            sh = self.sh.get((bx, bz), 3)
            bee_y = sh + 1
            u1, v1, u2, v2 = self.ta.get_uv('beenest') if self.ta else (0, 0, 1, 1)
            for fd in ['top', 'bottom', 'front', 'back', 'left', 'right']: 
                self.mesh.add_cube_face(float(bx), float(bee_y), float(bz), fd, u1, v1, u2, v2)
        
        self.tb, self.bb = tb, bb
        self.rebuild = False

    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 = TerrainGenerator()
        
    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 = 10
        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:
            gh = cm.get_height_at(int(self.x), int(self.z), cm.mod_loader)
            if gh == -1: gh = 5
            self.y = gh + 1 + self.eh
            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
        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)
        
        self.mod_loader = ModLoader(self)
        self.mod_loader.scan_mods()
        
        self.cam, self.pm = Camera(), PlayerModel()
        self.cm = ChunkManager(rd=2)
        self.cm.mod_loader = self.mod_loader
        self.cm.init_blocks(self.mod_loader)
        
        self.dnc = DayNightCycle()
        self.le, self.pb = True, []
        self.ft, self.cd, self.fd, self.show_stats, self.pm_mode = 0, 0, 0, True, "Enhanced"
        self.lpp, self.ema, self.suc = (0, 0), False, 0
        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/60.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):
        st = time.time()
        if int(time.time() * 5) % 5 == 0: self.dnc.update(dt)
        if int(time.time() * 2) % 3 == 0:
            sc = self.dnc.get_sky_color()
            glClearColor(*sc, 1.0)
        if self.mc:
            dx, dz = abs(self.cam.x - self.lpp[0]), abs(self.cam.z - self.lpp[1])
            if dx > 8 or dz > 8:
                self.cm.update_chunks(self.cam.x, self.cam.z, self.mod_loader)
                self.lpp = (self.cam.x, self.cam.z)
            self.cam.update(dt, self.keys, self.cm, self.pb)
            self.pm.update(dt, self.cam.vx, self.cam.vz)
            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)
        
        self.flowing_liquid.update(dt)
        
        for projectile in self.projectiles[:]:
            if not projectile.update(dt, self):
                self.projectiles.remove(projectile)
        
        self.ft = time.time() - st
        fps = 1.0 / max(0.001, self.ft)
        if fps < 20 and not self.ema: self.activate_emergency_mode()

    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 % 200 == 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: self.cd, self.fd = self.cm.draw_chunks(self.cam.x, self.cam.z, self.cam)
        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/60.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/60.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/60.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 = TerrainGenerator()
            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 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()