求修复PHP8.1minecraft1.8 Java版服务端的问题。

查看 4|回复 0
作者:ufvjogvj   
前几天我使用AI写了一个我的世界PHP8.1语言的Java1.8服务端,后面使用AI添加背包物品栏保存到服务器,下次使用该名字进入服务器加载背包的时候,开始发生各种离奇的错误,并且掉落物系统也是这样,现在这个代码还没有加那些背包和掉落物的东西,有没有好心的人可以加上我想要的功能,或者排查一下这些代码里面有没有什么隐藏问题?
[PHP] 纯文本查看 复制代码createWorldDirectories();
        
        $this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
        if ($this->socket === false) {
            die("创建套接字失败: " . socket_strerror(socket_last_error()));
        }
        
        socket_set_option($this->socket, SOL_SOCKET, SO_REUSEADDR, 1);
        
        if (!socket_bind($this->socket, $host, $port)) {
            die("绑定套接字失败: " . socket_strerror(socket_last_error()));
        }
        
        if (!socket_listen($this->socket)) {
            die("监听套接字失败: " . socket_strerror(socket_last_error()));
        }
        
        socket_set_nonblock($this->socket);
        
        $this->loadWorld();
        echo "Minecraft 1.8 服务器已启动在 $host:$port\n";
        echo "世界大小: {$this->worldSize}x{$this->worldSize} 方块\n";
        echo "世界版本: v{$this->worldVersion} (数据清理优化版)\n";
        echo "输入 'stop' 关闭服务器\n";
        echo "输入 'list' 显示在线玩家\n";
        echo "输入 'say ' 广播消息\n";
        echo "输入 'save' 手动保存世界\n";
        echo "输入 'gc' 强制垃圾回收\n";
        echo "输入 'worldinfo' 显示世界信息\n";
        echo "输入 'repair' 修复世界数据\n";
        echo "输入 'cleanup' 清理无效数据\n";
    }
    private function createWorldDirectories() {
        if (!is_dir($this->worldDir)) {
            mkdir($this->worldDir, 0777, true);
            echo "创建世界目录: {$this->worldDir}\n";
        }
        
        if (!is_dir($this->playerDataDir)) {
            mkdir($this->playerDataDir, 0777, true);
            echo "创建玩家数据目录: {$this->playerDataDir}\n";
        }
    }
    /**
     * 关键修复:改进世界加载逻辑,增加数据清理
     */
    private function loadWorld() {
        // 首先尝试加载主世界文件
        if (file_exists($this->worldFile)) {
            echo "正在从 JSON 加载世界...";
            
            $worldData = file_get_contents($this->worldFile);
            if ($worldData !== false) {
                $data = json_decode($worldData, true);
               
                if ($this->validateWorldData($data)) {
                    $this->world = $data['blocks'];
                    $this->worldSize = $data['worldSize'] ?? 48;
                    $this->entities = $data['entities'] ?? [];
                    $this->worldVersion = $data['version'] ?? 2;
                    $this->worldFullyLoaded = true;
                    
                    // 加载后立即进行数据清理
                    $this->deepCleanWorldData();
                    
                    $blockCount = count($this->world);
                    $nonAirBlocks = 0;
                    foreach ($this->world as $blockId) {
                        if ($blockId != self::BLOCK_AIR) $nonAirBlocks++;
                    }
                    
                    echo "完成 (已加载 $blockCount 个方块, $nonAirBlocks 个非空气方块)\n";
                    echo "世界版本: {$this->worldVersion}, 大小: {$this->worldSize}x{$this->worldSize}\n";
                    
                    // 验证世界完整性
                    $this->validateWorldIntegrity();
                    
                    // 创建备份
                    $this->createWorldBackup();
                    
                    // 修复:确保世界数据完全覆盖默认地形
                    $this->ensureWorldConsistency();
                    return;
                } else {
                    echo "世界文件格式无效,尝试加载备份...\n";
                }
            } else {
                echo "无法读取世界文件,尝试加载备份...\n";
            }
        }
        
        // 如果主文件失败,尝试加载备份
        if (file_exists($this->worldBackupFile)) {
            echo "正在从备份加载世界...";
            
            $backupData = file_get_contents($this->worldBackupFile);
            if ($backupData !== false) {
                $data = json_decode($backupData, true);
               
                if ($this->validateWorldData($data)) {
                    $this->world = $data['blocks'];
                    $this->worldSize = $data['worldSize'] ?? 48;
                    $this->entities = $data['entities'] ?? [];
                    $this->worldVersion = $data['version'] ?? 2;
                    $this->worldFullyLoaded = true;
                    
                    // 加载后立即进行数据清理
                    $this->deepCleanWorldData();
                    
                    $blockCount = count($this->world);
                    $nonAirBlocks = 0;
                    foreach ($this->world as $blockId) {
                        if ($blockId != self::BLOCK_AIR) $nonAirBlocks++;
                    }
                    
                    echo "完成 (从备份加载了 $blockCount 个方块, $nonAirBlocks 个非空气方块)\n";
                    echo "世界版本: {$this->worldVersion}, 大小: {$this->worldSize}x{$this->worldSize}\n";
                    
                    // 验证世界完整性
                    $this->validateWorldIntegrity();
                    
                    // 修复:确保世界数据完全覆盖默认地形
                    $this->ensureWorldConsistency();
                    
                    // 恢复备份到主文件
                    if (copy($this->worldBackupFile, $this->worldFile)) {
                        echo "已从备份恢复世界文件\n";
                    }
                    return;
                }
            }
        }
        
        // 如果都失败,生成新世界
        echo "世界文件不存在或损坏,生成新世界...\n";
        $this->generateFlatWorld();
        $this->saveWorld();
        $this->worldFullyLoaded = true;
    }
    /**
     * 深度清理世界数据 - 新增方法
     * 移除所有无效的方块数据
     */
    private function deepCleanWorldData() {
        echo "执行深度数据清理...";
        $cleanedCount = 0;
        $invalidCoords = 0;
        $invalidBlocks = 0;
        $duplicateCoords = 0;
        
        $cleanedWorld = [];
        $processedCoords = [];
        
        foreach ($this->world as $key => $blockId) {
            // 检查坐标格式
            $coords = explode(',', $key);
            if (count($coords) !== 3) {
                $invalidCoords++;
                continue;
            }
            
            // 验证坐标值
            $x = (int)$coords[0];
            $y = (int)$coords[1];
            $z = (int)$coords[2];
            
            // 检查坐标是否有效
            if ($x = $this->worldSize ||
                $z = $this->worldSize ||
                $y = 128) {
                $invalidCoords++;
                continue;
            }
            
            // 检查方块ID是否有效
            if (!in_array($blockId, self::VALID_BLOCK_IDS)) {
                $invalidBlocks++;
                continue;
            }
            
            // 检查重复坐标
            $coordKey = "$x,$y,$z";
            if (isset($processedCoords[$coordKey])) {
                $duplicateCoords++;
                continue;
            }
            
            $processedCoords[$coordKey] = true;
            $cleanedWorld[$coordKey] = $blockId;
        }
        
        $this->world = $cleanedWorld;
        $cleanedCount = $invalidCoords + $invalidBlocks + $duplicateCoords;
        
        echo "完成 (清理了 $cleanedCount 个无效数据: $invalidCoords 个无效坐标, $invalidBlocks 个无效方块, $duplicateCoords 个重复坐标)\n";
        
        if ($cleanedCount > 0) {
            // 如果有清理操作,立即保存清理后的世界
            $this->saveWorld();
        }
    }
    /**
     * 确保世界数据一致性
     */
    private function ensureWorldConsistency() {
        echo "确保世界数据一致性...";
        $fixedBlocks = 0;
        $missingBlocks = 0;
        
        // 检查世界边界内的所有位置
        for ($x = 0; $x worldSize; $x++) {
            for ($z = 0; $z worldSize; $z++) {
                for ($y = 0; $y world[$key])) {
                        $this->world[$key] = self::BLOCK_AIR;
                        $fixedBlocks++;
                        $missingBlocks++;
                    }
                }
            }
        }
        
        echo "完成 (修复了 $fixedBlocks 个方块, 发现 $missingBlocks 个缺失方块)\n";
    }
    /**
     * 验证世界数据的完整性和正确性
     */
    private function validateWorldData($data) {
        if ($data === null || !is_array($data)) {
            echo "错误: 世界数据不是有效的JSON数组\n";
            return false;
        }
        
        // 检查必需字段
        $requiredFields = ['version', 'worldSize', 'blocks'];
        foreach ($requiredFields as $field) {
            if (!isset($data[$field])) {
                echo "错误: 世界数据缺少必需字段 '$field'\n";
                return false;
            }
        }
        
        // 验证版本兼容性
        $version = $data['version'] ?? 1;
        if ($version  $this->worldVersion + 1) {
            echo "错误: 不兼容的世界版本 $version (当前支持版本: {$this->worldVersion})\n";
            return false;
        }
        
        // 验证世界大小
        $worldSize = $data['worldSize'] ?? 0;
        if (!is_int($worldSize) || $worldSize  1000) {
            echo "错误: 无效的世界大小: $worldSize\n";
            return false;
        }
        
        // 验证方块数据
        $blocks = $data['blocks'] ?? null;
        if (!is_array($blocks)) {
            echo "错误: 方块数据不是数组\n";
            return false;
        }
        
        // 验证方块键值格式
        $invalidEntries = 0;
        foreach ($blocks as $key => $blockId) {
            if (!is_string($key)) {
                $invalidEntries++;
                continue;
            }
            
            if (!is_int($blockId)) {
                $invalidEntries++;
                continue;
            }
            
            // 验证坐标格式
            $coords = explode(',', $key);
            if (count($coords) !== 3) {
                $invalidEntries++;
                continue;
            }
            
            foreach ($coords as $coord) {
                if (!is_numeric($coord)) {
                    $invalidEntries++;
                    continue 2;
                }
            }
            
            // 验证方块ID范围
            if ($blockId  255) {
                $invalidEntries++;
                continue;
            }
        }
        
        if ($invalidEntries > 0) {
            echo "警告: 发现 $invalidEntries 个无效方块条目\n";
        }
        
        return true;
    }
    /**
     * 验证世界完整性
     */
    private function validateWorldIntegrity() {
        $totalExpected = $this->worldSize * $this->worldSize * 128;
        $actual = count($this->world);
        
        echo "世界完整性检查: 实际保存 $actual 个方块, 理论上限 $totalExpected 个方块\n";
        
        if ($actual > $totalExpected) {
            echo "警告: 世界数据可能损坏,方块数量超过理论上限\n";
            // 尝试修复:移除超出范围的方块
            $this->removeOutOfBoundsBlocks();
        }
        
        // 检查是否有无效的方块ID
        $invalidBlockIds = 0;
        foreach ($this->world as $blockId) {
            if (!in_array($blockId, self::VALID_BLOCK_IDS)) {
                $invalidBlockIds++;
            }
        }
        
        if ($invalidBlockIds > 0) {
            echo "警告: 发现 $invalidBlockIds 个无效方块ID\n";
        }
    }
    /**
     * 移除超出世界边界的方块
     */
    private function removeOutOfBoundsBlocks() {
        $removed = 0;
        foreach ($this->world as $key => $value) {
            $coords = explode(',', $key);
            if (count($coords) === 3) {
                $x = (int)$coords[0];
                $y = (int)$coords[1];
                $z = (int)$coords[2];
               
                if ($x = $this->worldSize ||
                    $z = $this->worldSize ||
                    $y = 128) {
                    unset($this->world[$key]);
                    $removed++;
                }
            } else {
                // 坐标格式错误,移除
                unset($this->world[$key]);
                $removed++;
            }
        }
        
        if ($removed > 0) {
            echo "已移除 $removed 个超出边界的方块\n";
        }
    }
    private function createWorldBackup() {
        if (file_exists($this->worldFile)) {
            copy($this->worldFile, $this->worldBackupFile);
        }
    }
    /**
     * 关键修复:改进世界保存,增加数据验证
     */
    private function saveWorld() {
        // 先创建备份
        if (file_exists($this->worldFile)) {
            $this->createWorldBackup();
        }
        
        // 修复:在保存前清理无效方块
        $this->cleanWorldData();
        
        $worldData = [
            'version' => $this->worldVersion,
            'worldSize' => $this->worldSize,
            'blocks' => $this->world,
            'entities' => $this->entities,
            'timestamp' => time(),
            'description' => 'PHP Minecraft Server 1.8 World - Data Cleaned JSON Format',
            'checksum' => $this->calculateWorldChecksum(),
            'totalBlocks' => count($this->world),
            'validated' => true
        ];
        
        $jsonData = json_encode($worldData, JSON_PRETTY_PRINT);
        
        // 验证JSON编码是否成功
        if ($jsonData === false) {
            echo "错误: JSON编码失败: " . json_last_error_msg() . "\n";
            return false;
        }
        
        $result = file_put_contents($this->worldFile, $jsonData, LOCK_EX);
        
        if ($result !== false) {
            $blockCount = count($this->world);
            $nonAirBlocks = 0;
            foreach ($this->world as $blockId) {
                if ($blockId != self::BLOCK_AIR) $nonAirBlocks++;
            }
            
            // 验证保存的文件
            if ($this->verifySavedWorld()) {
                echo "世界已保存到 '{$this->worldFile}' ($blockCount 个方块, $nonAirBlocks 个非空气方块)\n";
                return true;
            } else {
                echo "警告: 世界文件保存后验证失败,可能已损坏\n";
                return false;
            }
        } else {
            echo "保存世界失败! 请检查目录权限。\n";
            return false;
        }
    }
    /**
     * 清理世界数据,移除无效的方块
     */
    private function cleanWorldData() {
        echo "清理世界数据...";
        $cleaned = 0;
        $newWorld = [];
        
        foreach ($this->world as $key => $blockId) {
            $coords = explode(',', $key);
            if (count($coords) === 3) {
                $x = (int)$coords[0];
                $y = (int)$coords[1];
                $z = (int)$coords[2];
               
                // 只保存世界边界内的有效方块
                if ($x >= 0 && $x worldSize &&
                    $z >= 0 && $z worldSize &&
                    $y >= 0 && $y world = $newWorld;
        
        if ($cleaned > 0) {
            echo "清理了 $cleaned 个无效方块\n";
        } else {
            echo "无需清理\n";
        }
    }
    /**
     * 计算世界数据的校验和
     */
    private function calculateWorldChecksum() {
        $data = [
            'blocks' => $this->world,
            'worldSize' => $this->worldSize,
            'version' => $this->worldVersion
        ];
        return md5(serialize($data));
    }
    /**
     * 验证保存的世界文件
     */
    private function verifySavedWorld() {
        if (!file_exists($this->worldFile)) {
            return false;
        }
        
        $content = file_get_contents($this->worldFile);
        if ($content === false) {
            return false;
        }
        
        $data = json_decode($content, true);
        return $this->validateWorldData($data);
    }
    private function savePlayerData($username, $data) {
        $filename = $this->playerDataDir . '/' . $username . '.json';
        
        // 验证玩家数据
        if (!$this->validatePlayerData($data)) {
            echo "玩家数据验证失败: $username\n";
            return false;
        }
        
        $playerData = json_encode($data, JSON_PRETTY_PRINT);
        
        // 创建备份
        $backupFile = $filename . '.backup';
        if (file_exists($filename)) {
            copy($filename, $backupFile);
        }
        
        $result = file_put_contents($filename, $playerData, LOCK_EX);
        
        if ($result === false) {
            echo "保存玩家数据失败: $username\n";
            return false;
        }
        return true;
    }
    private function loadPlayerData($username) {
        $filename = $this->playerDataDir . '/' . $username . '.json';
        $backupFile = $filename . '.backup';
        
        // 首先尝试主文件
        if (file_exists($filename)) {
            $data = file_get_contents($filename);
            if ($data !== false) {
                $playerData = json_decode($data, true);
                if ($playerData !== null && $this->validatePlayerData($playerData)) {
                    return $playerData;
                }
            }
        }
        
        // 如果主文件失败,尝试备份
        if (file_exists($backupFile)) {
            $data = file_get_contents($backupFile);
            if ($data !== false) {
                $playerData = json_decode($data, true);
                if ($playerData !== null && $this->validatePlayerData($playerData)) {
                    echo "从备份加载玩家数据: $username\n";
                    
                    // 恢复备份
                    copy($backupFile, $filename);
                    return $playerData;
                }
            }
        }
        
        return null;
    }
    /**
     * 验证玩家数据
     */
    private function validatePlayerData($data) {
        if (!is_array($data)) {
            return false;
        }
        
        $required = ['x', 'y', 'z'];
        foreach ($required as $field) {
            if (!isset($data[$field]) || !is_numeric($data[$field])) {
                return false;
            }
        }
        
        // 验证坐标范围
        $x = $data['x'];
        $y = $data['y'];
        $z = $data['z'];
        
        $margin = 10;
        if ($x = $this->worldSize + $margin ||
            $z = $this->worldSize + $margin ||
            $y  256) {
            return false;
        }
        
        return true;
    }
    /**
     * 关键修复:重写地形生成 - 生成超平坦地形
     */
    private function generateFlatWorld() {
        $this->world = [];
        
        echo "生成超平坦世界中...\n";
        
        // 生成超平坦世界 - 保存所有方块以确保完整性
        for ($x = 0; $x worldSize; $x++) {
            for ($z = 0; $z worldSize; $z++) {
                for ($y = 0; $y = 1 && $y = 4 && $y setBlock($x, $y, $z, $blockId);
                }
            }
            
            // 显示进度
            if ($x % 16 === 0) {
                $progress = round(($x / $this->worldSize) * 100);
                echo "进度: $progress%\n";
            }
        }
        
        // 生成一些装饰
        $this->generateDecorations();
        
        $totalBlocks = count($this->world);
        $nonAirBlocks = 0;
        foreach ($this->world as $blockId) {
            if ($blockId != self::BLOCK_AIR) $nonAirBlocks++;
        }
        
        echo "世界生成完成 (已生成 $totalBlocks 个方块, $nonAirBlocks 个非空气方块)\n";
    }
    /**
     * 设置方块 - 关键修复:总是保存所有方块,但验证数据有效性
     */
    private function setBlock($x, $y, $z, $blockId) {
        // 验证坐标有效性
        if ($x = $this->worldSize ||
            $z = $this->worldSize ||
            $y = 128) {
            return; // 忽略超出边界的方块
        }
        
        // 验证方块ID有效性
        if (!in_array($blockId, self::VALID_BLOCK_IDS)) {
            echo "警告: 尝试设置无效方块ID: $blockId 在 ($x, $y, $z)\n";
            return;
        }
        
        $key = "$x,$y,$z";
        $this->world[$key] = $blockId;
    }
    /**
     * 获取方块 - 关键修复:只使用保存的世界数据
     */
    private function getBlock($x, $y, $z) {
        // 世界边界检查
        if ($x = $this->worldSize ||
            $z = $this->worldSize ||
            $y = 128) {
            return self::BLOCK_AIR; // 边界外为空气
        }
        
        $key = "$x,$y,$z";
        
        // 关键修复:如果在保存的数据中找到,返回方块ID
        // 如果没有找到,返回空气(而不是默认地形)
        return isset($this->world[$key]) ? $this->world[$key] : self::BLOCK_AIR;
    }
    /**
     * 生成一些装饰物
     */
    private function generateDecorations() {
        $centerX = $this->worldSize / 2;
        $centerZ = $this->worldSize / 2;
        
        // 在中心区域生成一些树
        $treePositions = [
            [$centerX + 5, $centerZ + 5],
            [$centerX - 5, $centerZ - 5],
            [$centerX + 8, $centerZ - 3],
            [$centerX - 7, $centerZ + 6],
            [$centerX + 2, $centerZ - 8]
        ];
        
        foreach ($treePositions as $pos) {
            $treeX = $pos[0];
            $treeZ = $pos[1];
            
            // 确保树的位置在世界范围内
            if ($treeX >= 2 && $treeX worldSize - 2 &&
                $treeZ >= 2 && $treeZ worldSize - 2) {
               
                $this->generateTree($treeX, $treeZ);
            }
        }
        
        // 生成一个小房子在出生点
        $this->generateSpawnHouse($centerX, $centerZ);
    }
    private function generateTree($x, $z) {
        $groundY = 6; // 草方块高度
        
        // 生成树干(橡木)
        $trunkHeight = 5;
        for ($y = $groundY + 1; $y setBlock($x, $y, $z, self::BLOCK_WOOD);
        }
        
        // 生成树叶
        $leavesStartY = $groundY + $trunkHeight - 1;
        $leavesEndY = $groundY + $trunkHeight + 2;
        
        for ($ly = $leavesStartY; $ly = 0 && $leafX worldSize &&
                        $leafZ >= 0 && $leafZ worldSize &&
                        $ly >= 0 && $ly getBlock($leafX, $ly, $leafZ);
                        if ($currentBlock == self::BLOCK_AIR) {
                            $this->setBlock($leafX, $ly, $leafZ, self::BLOCK_LEAVES);
                        }
                    }
                }
            }
        }
    }
    private function generateSpawnHouse($centerX, $centerZ) {
        $houseSize = 5;
        $startX = $centerX - floor($houseSize / 2);
        $startZ = $centerZ - floor($houseSize / 2);
        $wallHeight = 4;
        $groundY = 6;
        
        echo "在出生点生成房屋...\n";
        
        // 地基
        for ($x = $startX; $x setBlock($x, $groundY, $z, self::BLOCK_WOOD_PLANK);
            }
        }
        
        // 墙壁
        for ($y = $groundY + 1; $y setBlock($x, $y, $startZ, self::BLOCK_WOOD);
                $this->setBlock($x, $y, $startZ + $houseSize - 1, self::BLOCK_WOOD);
            }
            for ($z = $startZ; $z setBlock($startX, $y, $z, self::BLOCK_WOOD);
                $this->setBlock($startX + $houseSize - 1, $y, $z, self::BLOCK_WOOD);
            }
        }
        
        // 门(正面中间)
        $doorX = $startX + floor($houseSize / 2);
        $this->setBlock($doorX, $groundY + 1, $startZ, self::BLOCK_AIR);
        $this->setBlock($doorX, $groundY + 2, $startZ, self::BLOCK_AIR);
        
        // 窗户
        $window1X = $startX + 1;
        $window1Z = $startZ + floor($houseSize / 2);
        $this->setBlock($window1X, $groundY + 2, $window1Z, self::BLOCK_GLASS);
        
        $window2X = $startX + $houseSize - 2;
        $this->setBlock($window2X, $groundY + 2, $window1Z, self::BLOCK_GLASS);
        
        // 屋顶
        $roofY = $groundY + $wallHeight + 1;
        for ($x = $startX; $x setBlock($x, $roofY, $z, self::BLOCK_WOOD);
            }
        }
    }
    private function getSpawnHeight($x, $z) {
        // 从最高处开始向下寻找第一个固体方块
        for ($y = 127; $y >= 0; $y--) {
            $blockId = $this->getBlock($x, $y, $z);
            if ($blockId != self::BLOCK_AIR) {
                return $y + 1;
            }
        }
        return 7; // 默认高度
    }
    public function run() {
        $stdin = fopen('php://stdin', 'r');
        stream_set_blocking($stdin, false);
        $lastAutoSave = time();
        $lastMemoryCheck = 0;
        
        while ($this->running) {
            $currentTime = microtime(true);
            $this->loopCount++;
            
            $input = fgets($stdin);
            if ($input) {
                $this->handleConsoleCommand(trim($input));
            }
            if ($currentTime - $this->lastKeepAlive > 20) {
                $this->sendKeepAliveToAll();
                $this->lastKeepAlive = $currentTime;
            }
            if (time() - $lastAutoSave > 120) {
                if ($this->saveWorld()) {
                    echo "世界已自动保存\n";
                } else {
                    echo "世界自动保存失败!\n";
                }
                $lastAutoSave = time();
            }
            if ($this->loopCount % $this->memoryCheckInterval === 0) {
                $this->checkMemoryUsage();
                $lastMemoryCheck = $currentTime;
            }
            $client = @socket_accept($this->socket);
            if ($client !== false) {
                socket_set_nonblock($client);
                $clientId = $this->nextClientId++;
                $this->clients[$clientId] = [
                    'socket' => $client,
                    'state' => 'handshaking',
                    'buffer' => '',
                    'compression' => false,
                    'username' => '',
                    'entityId' => $clientId,
                    'lastKeepAlive' => $currentTime,
                    'connected' => true
                ];
                echo "新客户端连接: $clientId\n";
            }
            $clientIds = array_keys($this->clients);
            foreach ($clientIds as $clientId) {
                if (!isset($this->clients[$clientId])) {
                    continue;
                }
               
                $clientData = &$this->clients[$clientId];
               
                if (!$clientData['connected']) {
                    $this->cleanupClient($clientId);
                    continue;
                }
                if ($currentTime - $clientData['lastKeepAlive'] > 60) {
                    echo "客户端 $clientId 超时\n";
                    $this->safeDisconnectClient($clientId, "超时");
                    continue;
                }
                $data = @socket_read($clientData['socket'], 4096);
               
                if ($data === false) {
                    $error = socket_last_error($clientData['socket']);
                    if ($error !== SOCKET_EWOULDBLOCK) {
                        $this->safeDisconnectClient($clientId, "连接错误: " . socket_strerror($error));
                    }
                    continue;
                }
               
                if ($data === '') {
                    $this->safeDisconnectClient($clientId, "连接已关闭");
                    continue;
                }
                $clientData['buffer'] .= $data;
                $clientData['lastKeepAlive'] = $currentTime;
               
                if (strlen($clientData['buffer']) > 65536) {
                    echo "客户端 $clientId 缓冲区过大,断开连接\n";
                    $this->safeDisconnectClient($clientId, "缓冲区溢出");
                    continue;
                }
               
                $this->processClientData($clientId, $clientData);
            }
            $this->cleanupDisconnectedClients();
            
            usleep(10000);
        }
        fclose($stdin);
    }
    private function checkMemoryUsage() {
        $memoryUsage = memory_get_usage(true);
        $memoryUsageMB = round($memoryUsage / 1024 / 1024, 2);
        $memoryPeak = memory_get_peak_usage(true);
        $memoryPeakMB = round($memoryPeak / 1024 / 1024, 2);
        
        echo "内存使用: {$memoryUsageMB}MB, 峰值: {$memoryPeakMB}MB, 客户端: " . count($this->clients) . "\n";
        
        if ($memoryUsage > 100 * 1024 * 1024) {
            echo "内存使用较高,执行垃圾回收...\n";
            gc_collect_cycles();
            $afterGC = round(memory_get_usage(true) / 1024 / 1024, 2);
            echo "垃圾回收后: {$afterGC}MB\n";
        }
    }
    private function handleConsoleCommand($command) {
        if ($command === 'stop') {
            $this->shutdown();
        } elseif ($command === 'list') {
            $players = array_filter($this->clients, function($client) {
                return $client['state'] === 'play' && !empty($client['username']) && $client['connected'];
            });
            $playerNames = array_map(function($client) {
                return $client['username'];
            }, $players);
            
            if (empty($playerNames)) {
                echo "没有玩家在线\n";
            } else {
                echo "在线玩家 (" . count($playerNames) . "): " . implode(', ', $playerNames) . "\n";
            }
        } elseif (strpos($command, 'say ') === 0) {
            $message = substr($command, 4);
            if (!empty($message)) {
                $this->broadcastMessage("§6[服务器] §f" . $message);
                echo "广播: $message\n";
            }
        } elseif ($command === 'save') {
            if ($this->saveWorld()) {
                echo "世界已手动保存\n";
            } else {
                echo "世界手动保存失败!\n";
            }
        } elseif ($command === 'gc') {
            $before = memory_get_usage(true);
            gc_collect_cycles();
            $after = memory_get_usage(true);
            $freed = round(($before - $after) / 1024 / 1024, 2);
            echo "垃圾回收完成,释放 {$freed}MB 内存\n";
        } elseif ($command === 'worldinfo') {
            $totalBlocks = count($this->world);
            $nonAirBlocks = 0;
            foreach ($this->world as $blockId) {
                if ($blockId != self::BLOCK_AIR) $nonAirBlocks++;
            }
            
            // 检查无效数据
            $invalidData = 0;
            foreach ($this->world as $key => $blockId) {
                $coords = explode(',', $key);
                if (count($coords) !== 3) {
                    $invalidData++;
                    continue;
                }
                if (!in_array($blockId, self::VALID_BLOCK_IDS)) {
                    $invalidData++;
                }
            }
            
            echo "世界信息:\n";
            echo "大小: {$this->worldSize}x{$this->worldSize}\n";
            echo "总方块: $totalBlocks\n";
            echo "非空气方块: $nonAirBlocks\n";
            echo "无效数据: $invalidData\n";
            echo "文件: {$this->worldFile}\n";
            echo "版本: {$this->worldVersion}\n";
            echo "完全加载: " . ($this->worldFullyLoaded ? "是" : "否") . "\n";
        } elseif ($command === 'repair') {
            echo "尝试修复世界数据...\n";
            if ($this->repairWorldData()) {
                echo "世界数据修复完成\n";
            } else {
                echo "世界数据修复失败\n";
            }
        } elseif ($command === 'cleanup') {
            echo "执行数据清理...\n";
            $before = count($this->world);
            $this->deepCleanWorldData();
            $after = count($this->world);
            $cleaned = $before - $after;
            echo "数据清理完成,移除了 $cleaned 个无效方块\n";
            
            // 保存清理后的世界
            if ($this->saveWorld()) {
                echo "清理后的世界已保存\n";
            }
        } elseif (!empty($command)) {
            echo "未知命令: $command\n";
            echo "可用命令: stop, list, say , save, gc, worldinfo, repair, cleanup\n";
        }
    }
    private function repairWorldData() {
        echo "尝试修复世界数据...\n";
        
        $backupExists = file_exists($this->worldBackupFile);
        $mainExists = file_exists($this->worldFile);
        
        if (!$mainExists && !$backupExists) {
            echo "没有可修复的世界文件\n";
            return false;
        }
        
        if ($mainExists) {
            $data = file_get_contents($this->worldFile);
            if ($data !== false) {
                $worldData = json_decode($data, true);
                if ($worldData !== null) {
                    $repaired = false;
                    if (!isset($worldData['version'])) {
                        $worldData['version'] = $this->worldVersion;
                        $repaired = true;
                    }
                    if (!isset($worldData['worldSize'])) {
                        $worldData['worldSize'] = $this->worldSize;
                        $repaired = true;
                    }
                    if (!isset($worldData['blocks']) || !is_array($worldData['blocks'])) {
                        $worldData['blocks'] = [];
                        $repaired = true;
                    }
                    
                    if ($repaired) {
                        $jsonData = json_encode($worldData, JSON_PRETTY_PRINT);
                        if (file_put_contents($this->worldFile, $jsonData, LOCK_EX) !== false) {
                            echo "已修复世界文件格式\n";
                            $this->loadWorld();
                            return true;
                        }
                    } else {
                        echo "世界文件格式正确,无需修复\n";
                        return true;
                    }
                }
            }
        }
        
        if ($backupExists) {
            if (copy($this->worldBackupFile, $this->worldFile)) {
                echo "已从备份恢复世界文件\n";
                $this->loadWorld();
                return true;
            }
        }
        
        return false;
    }
    private function sendKeepAliveToAll() {
        $currentTime = time();
        foreach ($this->clients as $clientId => &$clientData) {
            if ($clientData['state'] === 'play' && $clientData['connected']) {
                $this->sendPacket($clientId, 0x00, $this->writeVarInt($currentTime));
            }
        }
    }
    private function processClientData($clientId, &$clientData) {
        while (strlen($clientData['buffer']) > 0 && $clientData['connected']) {
            $originalBuffer = $clientData['buffer'];
            
            $packetLength = $this->readVarInt($clientData['buffer']);
            if ($packetLength === null) {
                return;
            }
            
            if (strlen($clientData['buffer']) readVarInt($packetData);
                if ($dataLength === null) {
                    continue;
                }
               
                if ($dataLength > 0) {
                    $decompressed = @zlib_decode($packetData);
                    if ($decompressed === false) {
                        echo "解压客户端 $clientId 的数据包失败\n";
                        continue;
                    }
                    $packetData = $decompressed;
                }
            }
            $packetId = $this->readVarInt($packetData);
            if ($packetId === null) {
                continue;
            }
            
            $this->handlePacket($clientId, $clientData, $packetId, $packetData);
        }
    }
    private function handlePacket($clientId, &$clientData, $packetId, $data) {
        if (!$clientData['connected']) {
            return;
        }
        
        switch ($clientData['state']) {
            case 'handshaking':
                $this->handleHandshaking($clientId, $clientData, $packetId, $data);
                break;
            case 'status':
                $this->handleStatus($clientId, $clientData, $packetId, $data);
                break;
            case 'login':
                $this->handleLogin($clientId, $clientData, $packetId, $data);
                break;
            case 'play':
                $this->handlePlay($clientId, $clientData, $packetId, $data);
                break;
        }
    }
    private function handleHandshaking($clientId, &$clientData, $packetId, $data) {
        if ($packetId === 0x00) {
            $protocolVersion = $this->readVarInt($data);
            $serverAddress = $this->readString($data);
            $serverPort = $this->readUnsignedShort($data);
            $nextState = $this->readVarInt($data);
            if ($protocolVersion === null || $serverAddress === null || $serverPort === null || $nextState === null) {
                $this->safeDisconnectClient($clientId, "无效的握手数据");
                return;
            }
            $clientData['state'] = ($nextState === 1) ? 'status' : 'login';
            echo "客户端 $clientId 握手: 协议版本=$protocolVersion, 下一状态=$nextState\n";
        }
    }
    private function handleStatus($clientId, &$clientData, $packetId, $data) {
        if ($packetId === 0x00) {
            $response = json_encode([
                "version" => [
                    "name" => "1.8.x",
                    "protocol" => 47
                ],
                "players" => [
                    "max" => 20,
                    "online" => count(array_filter($this->clients, function($client) {
                        return $client['state'] === 'play' && $client['connected'];
                    })),
                    "sample" => []
                ],
                "description" => [
                    "text" => "PHP Minecraft 服务器 1.8"
                ],
                "favicon" => ""
            ]);
            $this->sendPacket($clientId, 0x00, $this->writeString($response));
        } elseif ($packetId === 0x01) {
            $payload = $this->readLong($data);
            if ($payload !== null) {
                $this->sendPacket($clientId, 0x01, $this->writeLong($payload));
            }
        }
    }
    private function handleLogin($clientId, &$clientData, $packetId, $data) {
        if ($packetId === 0x00) {
            $username = $this->readString($data);
            
            if ($username === null) {
                $this->safeDisconnectClient($clientId, "无效的登录数据");
                return;
            }
            
            echo "客户端 $clientId 登录为: $username\n";
            
            $this->sendPacket($clientId, 0x03, $this->writeVarInt($this->compression_threshold));
            $clientData['compression'] = true;
            
            $uuid = $this->generateUUID($username);
            $loginSuccess = $this->writeString($uuid) . $this->writeString($username);
            $this->sendPacket($clientId, 0x02, $loginSuccess);
            
            $clientData['state'] = 'play';
            $clientData['username'] = $username;
            
            $this->initializePlayer($clientId, $clientData);
        }
    }
    private function generateUUID($username) {
        $data = md5("OfflinePlayer:" . $username, true);
        $data[6] = chr(ord($data[6]) & 0x0f | 0x30);
        $data[8] = chr(ord($data[8]) & 0x3f | 0x80);
        
        $uuid = bin2hex($data);
        return substr($uuid, 0, 8) . '-' .
               substr($uuid, 8, 4) . '-' .
               substr($uuid, 12, 4) . '-' .
               substr($uuid, 16, 4) . '-' .
               substr($uuid, 20, 12);
    }
    private function initializePlayer($clientId, &$clientData) {
        $spawnX = $this->worldSize / 2 + 0.5;
        $spawnZ = $this->worldSize / 2 + 0.5;
        $spawnY = $this->getSpawnHeight(floor($spawnX), floor($spawnZ)) + 1.0;
        
        $playerData = $this->loadPlayerData($clientData['username']);
        if ($playerData !== null) {
            $spawnX = $playerData['x'] ?? $spawnX;
            $spawnY = $playerData['y'] ?? $spawnY;
            $spawnZ = $playerData['z'] ?? $spawnZ;
            echo "已加载玩家 {$clientData['username']} 的位置数据\n";
        }
        
        $this->playerPositions[$clientId] = [
            'x' => $spawnX,
            'y' => $spawnY,
            'z' => $spawnZ,
            'yaw' => 0.0,
            'pitch' => 0.0,
            'onGround' => true
        ];
        
        $joinGame = $this->writeInt($clientData['entityId']) .
                   chr(0x08) .
                   chr(0) .
                   chr(0) .
                   chr(20) .
                   $this->writeString("default") .
                   chr(0);
        
        $this->sendPacket($clientId, 0x01, $joinGame);
        
        $spawnPos = $this->writePosition(floor($spawnX), floor($spawnY) - 1, floor($spawnZ));
        $this->sendPacket($clientId, 0x05, $spawnPos);
        
        $abilities = chr(0x0F) .
                    $this->writeFloat(0.05) .
                    $this->writeFloat(0.1);
        
        $this->sendPacket($clientId, 0x39, $abilities);
        
        $posLook = $this->writeDouble($spawnX) .
                  $this->writeDouble($spawnY) .
                  $this->writeDouble($spawnZ) .
                  $this->writeFloat(0.0) .
                  $this->writeFloat(0.0) .
                  chr(0x00);
        
        $this->sendPacket($clientId, 0x08, $posLook);
        
        $this->sendInitialChunks($clientId);
        
        echo "玩家 {$clientData['username']} 加入了游戏 (创造模式)\n";
        
        $this->broadcastMessage("§e{$clientData['username']} 加入了游戏");
        
        $this->sendMessage($clientId, "§a欢迎来到 PHP Minecraft 服务器!");
        $this->sendMessage($clientId, "§6你是创造模式,可以飞行和放置方块");
        $this->sendMessage($clientId, "§6输入 §a/help §6查看可用命令");
        $this->sendMessage($clientId, "§6世界已加载: {$this->worldSize}x{$this->worldSize}, " . count($this->world) . " 个方块");
        
        if (!$this->worldFullyLoaded) {
            $this->sendMessage($clientId, "§c警告: 世界可能没有完全加载,如果发现问题请重新加入");
        }
    }
    private function sendInitialChunks($clientId) {
        $centerChunkX = floor($this->worldSize / 2) >> 4;
        $centerChunkZ = floor($this->worldSize / 2) >> 4;
        
        for ($dx = -1; $dx sendChunk($clientId, $chunkX, $chunkZ);
            }
        }
    }
    private function sendChunk($clientId, $chunkX, $chunkZ) {
        $chunkData = $this->buildChunkData($chunkX, $chunkZ);
        if ($chunkData !== null) {
            $this->sendPacket($clientId, 0x21, $chunkData);
        }
    }
    private function buildChunkData($chunkX, $chunkZ) {
        $groundUpContinuous = true;
        $primaryBitMask = 0x1F;
        
        $chunkSections = '';
        
        for ($sectionY = 0; $sectionY buildChunkSection($chunkX, $sectionY, $chunkZ);
            $chunkSections .= $sectionData;
        }
        
        $biomeData = str_repeat(chr(1), 256);
        
        $data = $chunkSections . $biomeData;
        
        $chunkPacket = $this->writeInt($chunkX) .
                      $this->writeInt($chunkZ) .
                      chr($groundUpContinuous ? 1 : 0) .
                      $this->writeUnsignedShort($primaryBitMask) .
                      $this->writeVarInt(strlen($data)) .
                      $data;
        
        return $chunkPacket;
    }
    private function buildChunkSection($chunkX, $sectionY, $chunkZ) {
        $blocksPerSection = 16 * 16 * 16;
        $baseY = $sectionY * 16;
        
        $blockData = '';
        $blockLight = str_repeat("\xFF", 2048);
        $skyLight = str_repeat("\xFF", 2048);
        
        for ($y = 0; $y getBlock($worldX, $worldY, $worldZ);
                    $metadata = 0;
                    
                    $value = ($blockId getBlock($worldX, $worldY, $worldZ) != self::BLOCK_AIR) {
                        $nonAirBlocks++;
                    }
                }
            }
        }
        
        $sectionData = $this->writeUnsignedShort($nonAirBlocks) .
                      $blockData .
                      $blockLight .
                      $skyLight;
        
        return $sectionData;
    }
    private function handlePlay($clientId, &$clientData, $packetId, $data) {
        if (!$clientData['connected']) {
            return;
        }
        
        switch ($packetId) {
            case 0x00:
                $keepAliveId = $this->readVarInt($data);
                if ($keepAliveId !== null) {
                    $clientData['lastKeepAlive'] = microtime(true);
                    $this->sendPacket($clientId, 0x00, $this->writeVarInt($keepAliveId));
                }
                break;
               
            case 0x01:
                $message = $this->readString($data);
                if ($message !== null && !empty(trim($message))) {
                    $trimmedMessage = trim($message);
                    echo "来自 {$clientData['username']} 的聊天: $trimmedMessage\n";
                    
                    if (strpos($trimmedMessage, '/') === 0) {
                        $this->handlePlayerCommand($clientId, $clientData, $trimmedMessage);
                    } else {
                        $this->broadcastMessage("§7 §f$trimmedMessage");
                    }
                }
                break;
               
            case 0x04:
                $x = $this->readDouble($data);
                $feetY = $this->readDouble($data);
                $z = $this->readDouble($data);
                $onGround = $this->readBool($data);
               
                if ($x !== null && $feetY !== null && $z !== null && $onGround !== null) {
                    $this->updatePlayerPosition($clientId, $clientData, $x, $feetY, $z, $onGround);
                }
                break;
               
            case 0x05:
                $yaw = $this->readFloat($data);
                $pitch = $this->readFloat($data);
                $onGround = $this->readBool($data);
               
                if ($yaw !== null && $pitch !== null && $onGround !== null) {
                    $this->updatePlayerOrientation($clientId, $yaw, $pitch, $onGround);
                }
                break;
               
            case 0x06:
                $x = $this->readDouble($data);
                $feetY = $this->readDouble($data);
                $z = $this->readDouble($data);
                $yaw = $this->readFloat($data);
                $pitch = $this->readFloat($data);
                $onGround = $this->readBool($data);
               
                if ($x !== null && $feetY !== null && $z !== null &&
                    $yaw !== null && $pitch !== null && $onGround !== null) {
                    
                    $this->updatePlayerPosition($clientId, $clientData, $x, $feetY, $z, $onGround);
                    $this->updatePlayerOrientation($clientId, $yaw, $pitch, $onGround);
                }
                break;
               
            case 0x07:
                $status = $this->readByte($data);
                $location = $this->readPosition($data);
                $face = $this->readByte($data);
               
                if ($status !== null && $location !== null && $face !== null) {
                    $this->handlePlayerDigging($clientId, $clientData, $status, $location, $face);
                }
                break;
               
            case 0x08:
                $location = $this->readPosition($data);
                $face = $this->readByte($data);
                $heldItem = $this->readSlot($data);
                $cursorX = $this->readByte($data);
                $cursorY = $this->readByte($data);
                $cursorZ = $this->readByte($data);
               
                if ($location !== null && $face !== null) {
                    $this->handlePlayerBlockPlacement($clientId, $clientData, $location, $face, $heldItem);
                }
                break;
               
            case 0x0B:
                break;
               
            case 0x0C:
                $entityId = $this->readVarInt($data);
                $actionId = $this->readVarInt($data);
                $actionParam = $this->readVarInt($data);
               
                if ($actionId === 0 || $actionId === 1) {
                    $actionName = $actionId === 0 ? "开始潜行" : "停止潜行";
                    echo "玩家 {$clientData['username']} $actionName\n";
                } elseif ($actionId === 3 || $actionId === 4) {
                    $actionName = $actionId === 3 ? "开始冲刺" : "停止冲刺";
                    echo "玩家 {$clientData['username']} $actionName\n";
                }
                break;
               
            default:
                break;
        }
    }
    private function handlePlayerDigging($clientId, $clientData, $status, $location, $face) {
        list($x, $y, $z) = $location;
        
        if ($status === 0) {
            $blockId = $this->getBlock($x, $y, $z);
            if ($blockId != self::BLOCK_AIR) {
                echo "玩家 {$clientData['username']} 开始挖掘方块在 ($x, $y, $z) - 方块ID: $blockId\n";
               
                $this->setBlock($x, $y, $z, self::BLOCK_AIR);
                $this->broadcastBlockChange($x, $y, $z, self::BLOCK_AIR);
               
                $this->sendMessage($clientId, "§a方块已被破坏");
               
                if ($this->saveWorld()) {
                    $this->sendMessage($clientId, "§a世界已自动保存");
                } else {
                    $this->sendMessage($clientId, "§c世界保存失败,更改可能丢失!");
                }
            }
        }
    }
    private function handlePlayerBlockPlacement($clientId, $clientData, $location, $face, $heldItem) {
        list($x, $y, $z) = $location;
        
        switch ($face) {
            case 0: $y--; break;
            case 1: $y++; break;
            case 2: $z--; break;
            case 3: $z++; break;
            case 4: $x--; break;
            case 5: $x++; break;
        }
        
        if ($x >= 0 && $x worldSize &&
            $z >= 0 && $z worldSize &&
            $y >= 0 && $y getBlock($x, $y, $z) == self::BLOCK_AIR) {
            
            $this->setBlock($x, $y, $z, self::BLOCK_STONE);
            $this->broadcastBlockChange($x, $y, $z, self::BLOCK_STONE);
            
            echo "玩家 {$clientData['username']} 放置方块在 ($x, $y, $z)\n";
            $this->sendMessage($clientId, "§a方块已放置");
            
            if ($this->saveWorld()) {
                $this->sendMessage($clientId, "§a世界已自动保存");
            } else {
                $this->sendMessage($clientId, "§c世界保存失败,更改可能丢失!");
            }
        }
    }
    private function broadcastBlockChange($x, $y, $z, $blockId) {
        $blockChangePacket = $this->writePosition($x, $y, $z) .
                           $this->writeVarInt($blockId);
        
        foreach ($this->clients as $id => $client) {
            if ($client['state'] === 'play' && $client['connected']) {
                $this->sendPacket($id, 0x23, $blockChangePacket);
            }
        }
    }
    private function updatePlayerPosition($clientId, $clientData, $x, $y, $z, $onGround) {
        $margin = 5;
        if ($x = $this->worldSize + $margin ||
            $z = $this->worldSize + $margin ||
            $y  256) {
            
            $spawnX = $this->worldSize / 2 + 0.5;
            $spawnZ = $this->worldSize / 2 + 0.5;
            $spawnY = $this->getSpawnHeight(floor($spawnX), floor($spawnZ)) + 1.0;
            
            $posLook = $this->writeDouble($spawnX) .
                      $this->writeDouble($spawnY) .
                      $this->writeDouble($spawnZ) .
                      $this->writeFloat($this->playerPositions[$clientId]['yaw']) .
                      $this->writeFloat($this->playerPositions[$clientId]['pitch']) .
                      chr(0x00);
            
            $this->sendPacket($clientId, 0x08, $posLook);
            
            $this->playerPositions[$clientId]['x'] = $spawnX;
            $this->playerPositions[$clientId]['y'] = $spawnY;
            $this->playerPositions[$clientId]['z'] = $spawnZ;
            
            $this->sendMessage($clientId, "§c您不能超出世界边界");
            return;
        }
        
        $this->playerPositions[$clientId]['x'] = $x;
        $this->playerPositions[$clientId]['y'] = $y;
        $this->playerPositions[$clientId]['z'] = $z;
        $this->playerPositions[$clientId]['onGround'] = $onGround;
        
        if (!empty($clientData['username']) && time() % 30 === 0) {
            $playerData = [
                'x' => $x,
                'y' => $y,
                'z' => $z,
                'yaw' => $this->playerPositions[$clientId]['yaw'],
                'pitch' => $this->playerPositions[$clientId]['pitch'],
                'lastSave' => time()
            ];
            $this->savePlayerData($clientData['username'], $playerData);
        }
    }
    private function updatePlayerOrientation($clientId, $yaw, $pitch, $onGround) {
        $this->playerPositions[$clientId]['yaw'] = $yaw;
        $this->playerPositions[$clientId]['pitch'] = $pitch;
        $this->playerPositions[$clientId]['onGround'] = $onGround;
    }
    private function handlePlayerCommand($clientId, $clientData, $command) {
        $parts = explode(' ', $command);
        $cmd = strtolower($parts[0]);
        
        switch ($cmd) {
            case '/help':
                $this->sendMessage($clientId, "§6可用命令:");
                $this->sendMessage($clientId, "§a/help §7- 显示此帮助");
                $this->sendMessage($clientId, "§a/list §7- 列出在线玩家");
                $this->sendMessage($clientId, "§a/spawn §7- 传送至出生点");
                $this->sendMessage($clientId, "§a/gamemode  §7- 切换游戏模式");
                $this->sendMessage($clientId, "§a/time set  §7- 设置时间");
                $this->sendMessage($clientId, "§a/save §7- 手动保存世界");
                $this->sendMessage($clientId, "§a/worldinfo §7- 显示世界信息");
                $this->sendMessage($clientId, "§a/repair §7- 尝试修复世界数据");
                $this->sendMessage($clientId, "§a/cleanup §7- 清理无效数据");
                break;
               
            case '/list':
                $players = array_filter($this->clients, function($client) {
                    return $client['state'] === 'play' && !empty($client['username']) && $client['connected'];
                });
                $playerNames = array_map(function($client) {
                    return $client['username'];
                }, $players);
               
                $this->sendMessage($clientId, "§6在线玩家 (§a" . count($playerNames) . "§6): §e" . implode('§7, §e', $playerNames));
                break;
               
            case '/spawn':
                $spawnX = $this->worldSize / 2 + 0.5;
                $spawnZ = $this->worldSize / 2 + 0.5;
                $spawnY = $this->getSpawnHeight(floor($spawnX), floor($spawnZ)) + 1.0;
               
                $posLook = $this->writeDouble($spawnX) .
                          $this->writeDouble($spawnY) .
                          $this->writeDouble($spawnZ) .
                          $this->writeFloat($this->playerPositions[$clientId]['yaw']) .
                          $this->writeFloat($this->playerPositions[$clientId]['pitch']) .
                          chr(0x00);
               
                $this->sendPacket($clientId, 0x08, $posLook);
               
                $this->playerPositions[$clientId]['x'] = $spawnX;
                $this->playerPositions[$clientId]['y'] = $spawnY;
                $this->playerPositions[$clientId]['z'] = $spawnZ;
               
                $this->sendMessage($clientId, "§a已传送至出生点");
                break;
               
            case '/gamemode':
                if (count($parts) sendMessage($clientId, "§c用法: /gamemode ");
                    break;
                }
               
                $mode = intval($parts[1]);
                if ($mode === 0 || $mode === 1) {
                    $gameStateChange = chr(3) . $this->writeFloat($mode);
                    $this->sendPacket($clientId, 0x2B, $gameStateChange);
                    
                    $modeName = $mode === 0 ? "生存" : "创造";
                    $this->sendMessage($clientId, "§a游戏模式已更改为 $modeName 模式");
                } else {
                    $this->sendMessage($clientId, "§c无效的游戏模式。使用 0(生存) 或 1(创造)");
                }
                break;
               
            case '/time':
                if (count($parts) sendMessage($clientId, "§c用法: /time set ");
                    break;
                }
               
                $time = strtolower($parts[2]);
                if ($time === 'day') {
                    $worldAge = 0;
                    $timeOfDay = 0;
                } elseif ($time === 'night') {
                    $worldAge = 0;
                    $timeOfDay = 13000;
                } else {
                    $this->sendMessage($clientId, "§c无效的时间。使用 'day' 或 'night'");
                    break;
                }
               
                $timeUpdate = $this->writeLong($worldAge) . $this->writeLong($timeOfDay);
                $this->broadcastPacket(0x03, $timeUpdate);
               
                $this->sendMessage($clientId, "§a时间已设置为 $time");
                break;
               
            case '/save':
                if ($this->saveWorld()) {
                    $this->sendMessage($clientId, "§a世界保存成功!");
                    $this->broadcastMessage("§6[服务器] §a世界已由 {$clientData['username']} 手动保存");
                } else {
                    $this->sendMessage($clientId, "§c世界保存失败!");
                }
                break;
               
            case '/worldinfo':
                $totalBlocks = count($this->world);
                $nonAirBlocks = 0;
                foreach ($this->world as $blockId) {
                    if ($blockId != self::BLOCK_AIR) $nonAirBlocks++;
                }
               
                $invalidData = 0;
                foreach ($this->world as $key => $blockId) {
                    $coords = explode(',', $key);
                    if (count($coords) !== 3) {
                        $invalidData++;
                        continue;
                    }
                    if (!in_array($blockId, self::VALID_BLOCK_IDS)) {
                        $invalidData++;
                    }
                }
               
                $worldSize = $this->worldSize;
                $this->sendMessage($clientId, "§6世界信息:");
                $this->sendMessage($clientId, "§a大小: §e{$worldSize}x{$worldSize} 方块");
                $this->sendMessage($clientId, "§a总方块: §e$totalBlocks 个");
                $this->sendMessage($clientId, "§a非空气方块: §e$nonAirBlocks 个");
                $this->sendMessage($clientId, "§a无效数据: §e$invalidData 个");
                $this->sendMessage($clientId, "§a文件: §e{$this->worldFile}");
                $this->sendMessage($clientId, "§a版本: §e{$this->worldVersion}");
                $this->sendMessage($clientId, "§a完全加载: §e" . ($this->worldFullyLoaded ? "是" : "否"));
                break;
               
            case '/repair':
                $this->sendMessage($clientId, "§a正在尝试修复世界数据...");
                if ($this->repairWorldData()) {
                    $this->sendMessage($clientId, "§a世界数据修复完成");
                } else {
                    $this->sendMessage($clientId, "§c世界数据修复失败");
                }
                break;
               
            case '/cleanup':
                $this->sendMessage($clientId, "§a正在执行数据清理...");
                $before = count($this->world);
                $this->deepCleanWorldData();
                $after = count($this->world);
                $cleaned = $before - $after;
                $this->sendMessage($clientId, "§a数据清理完成,移除了 $cleaned 个无效方块");
               
                if ($this->saveWorld()) {
                    $this->sendMessage($clientId, "§a清理后的世界已保存");
                }
                break;
               
            default:
                $this->sendMessage($clientId, "§c未知命令。输入 §a/help §c获取帮助。");
                break;
        }
    }
    private function broadcastPacket($packetId, $data) {
        foreach ($this->clients as $id => $client) {
            if ($client['state'] === 'play' && $client['connected']) {
                $this->sendPacket($id, $packetId, $data);
            }
        }
    }
    private function sendMessage($clientId, $message) {
        $chatPacket = $this->writeString(json_encode([
            "text" => $message
        ])) . chr(0);
        
        $this->sendPacket($clientId, 0x02, $chatPacket);
    }
    private function broadcastMessage($message) {
        $chatPacket = $this->writeString(json_encode([
            "text" => $message
        ])) . chr(0);
        
        foreach ($this->clients as $id => $client) {
            if ($client['state'] === 'play' && $client['connected']) {
                $this->sendPacket($id, 0x02, $chatPacket);
            }
        }
    }
    private function sendPacket($clientId, $packetId, $data) {
        if (!isset($this->clients[$clientId]) || !$this->clients[$clientId]['connected']) {
            return false;
        }
        
        $clientData = $this->clients[$clientId];
        $packet = $this->writeVarInt($packetId) . $data;
        
        if ($clientData['compression']) {
            if (strlen($packet) >= $this->compression_threshold) {
                $compressed = @zlib_encode($packet, ZLIB_ENCODING_DEFLATE, 1);
                if ($compressed === false) {
                    echo "压缩客户端 $clientId 的数据包失败\n";
                    return false;
                }
                $packet = $this->writeVarInt(strlen($packet)) . $compressed;
            } else {
                $packet = $this->writeVarInt(0) . $packet;
            }
        }
        
        $fullPacket = $this->writeVarInt(strlen($packet)) . $packet;
        
        $result = @socket_write($clientData['socket'], $fullPacket);
        if ($result === false) {
            $error = socket_last_error($clientData['socket']);
            $errorMsg = socket_strerror($error);
            
            if ($error === 32 || $error === 104) {
                $this->safeDisconnectClient($clientId, "连接已断开 ($errorMsg)");
                return false;
            }
            
            echo "发送客户端 $clientId 错误 [$error]: $errorMsg\n";
            return false;
        }
        
        return true;
    }
    private function safeDisconnectClient($clientId, $reason = "") {
        if (!isset($this->clients[$clientId])) {
            return;
        }
        
        $this->clients[$clientId]['connected'] = false;
        $this->clientTimeouts[$clientId] = microtime(true);
        
        $username = $this->clients[$clientId]['username'];
        if (!empty($username)) {
            echo "玩家 $username 断开连接" . ($reason ? " ($reason)" : "") . "\n";
            $this->broadcastMessage("§e{$username} 离开了游戏");
            
            if (isset($this->playerPositions[$clientId])) {
                $pos = $this->playerPositions[$clientId];
                $playerData = [
                    'x' => $pos['x'],
                    'y' => $pos['y'],
                    'z' => $pos['z'],
                    'yaw' => $pos['yaw'],
                    'pitch' => $pos['pitch'],
                    'lastSave' => time()
                ];
                $this->savePlayerData($username, $playerData);
            }
        } else {
            echo "客户端 $clientId 断开连接" . ($reason ? " ($reason)" : "") . "\n";
        }
    }
    private function cleanupClient($clientId) {
        if (isset($this->clients[$clientId])) {
            @socket_close($this->clients[$clientId]['socket']);
            unset($this->clients[$clientId]);
        }
        
        if (isset($this->playerPositions[$clientId])) {
            unset($this->playerPositions[$clientId]);
        }
        
        if (isset($this->clientTimeouts[$clientId])) {
            unset($this->clientTimeouts[$clientId]);
        }
        
        if (count($this->clients) % 5 === 0) {
            gc_collect_cycles();
        }
    }
    private function cleanupDisconnectedClients() {
        $currentTime = microtime(true);
        $toRemove = [];
        
        foreach ($this->clientTimeouts as $clientId => $timeoutTime) {
            if ($currentTime - $timeoutTime > 1.0) {
                $toRemove[] = $clientId;
            }
        }
        
        foreach ($toRemove as $clientId) {
            $this->cleanupClient($clientId);
        }
    }
    public function shutdown() {
        echo "正在关闭服务器...\n";
        $this->running = false;
        
        $this->broadcastMessage("§c服务器正在关闭");
        
        if ($this->saveWorld()) {
            echo "世界保存成功\n";
        } else {
            echo "世界保存失败!\n";
        }
        
        foreach ($this->clients as $clientId => $clientData) {
            if (!empty($clientData['username']) && isset($this->playerPositions[$clientId])) {
                $pos = $this->playerPositions[$clientId];
                $playerData = [
                    'x' => $pos['x'],
                    'y' => $pos['y'],
                    'z' => $pos['z'],
                    'yaw' => $pos['yaw'],
                    'pitch' => $pos['pitch'],
                    'lastSave' => time()
                ];
                $this->savePlayerData($clientData['username'], $playerData);
            }
        }
        
        foreach ($this->clients as $clientId => $clientData) {
            if ($clientData['connected']) {
                $disconnectMsg = $this->writeString(json_encode([
                    "text" => "服务器已关闭"
                ]));
                $this->sendPacket($clientId, 0x40, $disconnectMsg);
            }
        }
        
        usleep(500000);
        
        foreach (array_keys($this->clients) as $clientId) {
            $this->cleanupClient($clientId);
        }
        
        socket_close($this->socket);
        echo "服务器已停止。\n";
        exit(0);
    }
    private function readVarInt(&$data) {
        $value = 0;
        $shift = 0;
        
        do {
            if (strlen($data) === 0) return null;
            $byte = ord($data[0]);
            $data = substr($data, 1);
            $value |= ($byte & 0x7F)  35) return null;
        } while ($byte & 0x80);
        
        return $value;
    }
    private function writeVarInt($value) {
        $bytes = '';
        do {
            $byte = $value & 0x7F;
            $value >>= 7;
            if ($value !== 0) $byte |= 0x80;
            $bytes .= chr($byte);
        } while ($value !== 0);
        return $bytes;
    }
    private function readString(&$data) {
        $length = $this->readVarInt($data);
        if ($length === null || strlen($data) writeVarInt(strlen($string)) . $string;
    }
    private function readUnsignedShort(&$data) {
        if (strlen($data) = 0x80000000) $value -= 0x100000000;
        $data = substr($data, 4);
        return $value;
    }
    private function writeInt($value) {
        return pack('N', $value);
    }
    private function readLong(&$data) {
        if (strlen($data) > 32) & 0xFFFFFFFF;
        $low = $value & 0xFFFFFFFF;
        return pack('NN', $high, $low);
    }
    private function readDouble(&$data) {
        if (strlen($data) writeLong((($x & 0x3FFFFFF) readLong($data);
        if ($val === null) return null;
        
        $x = $val >> 38;
        $y = ($val >> 26) & 0xFFF;
        $z = $val > 38;
        
        if ($x >= 0x2000000) $x -= 0x4000000;
        if ($y >= 0x800) $y -= 0x1000;
        if ($z >= 0x2000000) $z -= 0x4000000;
        
        return [$x, $y, $z];
    }
    private function readSlot(&$data) {
        $itemId = $this->readShort($data);
        if ($itemId === null) return null;
        
        if ($itemId !== -1) {
            $this->readByte($data);
            $this->readShort($data);
            $nbtLength = $this->readShort($data);
            if ($nbtLength > 0) {
                $this->readBytes($data, $nbtLength);
            }
        }
        
        return $itemId;
    }
    private function readShort(&$data) {
        if (strlen($data) = 0x8000) $value -= 0x10000;
        $data = substr($data, 2);
        return $value;
    }
    private function readBytes(&$data, $length) {
        if (strlen($data) run();
} catch (Exception $e) {
    echo "服务器错误: " . $e->getMessage() . "\n";
    exit(1);
}
?>

方块, 世界

您需要登录后才可以回帖 登录 | 立即注册

返回顶部