data = $data; $this->length = strlen($data); } public function seek(int $offset): void { if ($offset < 0 || $offset > $this->length) { throw new RuntimeException('Invalid offset inside the .otmm file.'); } $this->offset = $offset; } public function eof(): bool { return $this->offset >= $this->length; } public function remaining(): int { return $this->length - $this->offset; } public function u8(): int { $this->need(1); return ord($this->data[$this->offset++]); } public function u16(): int { $this->need(2); $value = unpack('v', substr($this->data, $this->offset, 2))[1]; $this->offset += 2; return $value; } public function u32(): int { $this->need(4); $value = unpack('V', substr($this->data, $this->offset, 4))[1]; $this->offset += 4; return $value; } public function string(): string { return $this->bytes($this->u16()); } public function bytes(int $length): string { $this->need($length); $value = substr($this->data, $this->offset, $length); $this->offset += $length; return $value; } protected function need(int $bytes): void { if ($bytes < 0 || $this->remaining() < $bytes) { throw new RuntimeException('Unexpected end of the .otmm file.'); } } } function minimap_root(): string { return dirname(__DIR__, 2) . '/' . ZNOTE_MINIMAP_DIR; } function minimap_meta_path(): string { return dirname(__DIR__, 2) . '/' . ZNOTE_MINIMAP_META; } function minimap_data() { static $data = null; if ($data !== null) { return $data; } $data = false; $file = minimap_meta_path(); if (!is_file($file)) { return $data; } $decoded = json_decode((string)file_get_contents($file), true); if (!is_array($decoded) || empty($decoded['floors'])) { return $data; } ksort($decoded['floors'], SORT_NUMERIC); $data = $decoded; return $data; } function minimap_available(): bool { return minimap_data() !== false; } function minimap_color(int $color): array { if ($color >= 0 && $color < 216) { return array( (int)(floor($color / 36) * 51), (int)(floor(($color % 36) / 6) * 51), (int)(($color % 6) * 51) ); } return array(0, 0, 0); } function minimap_tile_image(string $raw, bool &$hasVisible) { $image = imagecreatetruecolor(ZNOTE_MINIMAP_BLOCK, ZNOTE_MINIMAP_BLOCK); imagealphablending($image, false); imagesavealpha($image, true); imagefill($image, 0, 0, imagecolorallocatealpha($image, 0, 0, 0, 127)); $cache = array(); $hasVisible = false; for ($index = 0; $index < ZNOTE_MINIMAP_BLOCK * ZNOTE_MINIMAP_BLOCK; $index++) { $offset = $index * 3; $flags = ord($raw[$offset]); $color = ord($raw[$offset + 1]); if (!($flags & 1) || ($flags & 8) || $color === 255) { continue; } if (!isset($cache[$color])) { list($r, $g, $b) = minimap_color($color); $cache[$color] = imagecolorallocatealpha($image, $r, $g, $b, 0); } imagesetpixel($image, $index % ZNOTE_MINIMAP_BLOCK, (int)floor($index / ZNOTE_MINIMAP_BLOCK), $cache[$color]); $hasVisible = true; } return $image; } function minimap_delete(): void { foreach (glob(minimap_root() . '/*.png') ?: array() as $tile) { @unlink($tile); } @unlink(minimap_meta_path()); } function minimap_import(string $tmpFile, string $originalName, ?string &$error = null): bool { $error = null; if (strtolower((string)pathinfo($originalName, PATHINFO_EXTENSION)) !== 'otmm') { $error = 'Only .otmm minimap files are accepted.'; return false; } if (!is_file($tmpFile) || filesize($tmpFile) < 16) { $error = 'The uploaded file is empty or unreadable.'; return false; } if (filesize($tmpFile) > ZNOTE_MINIMAP_MAX_BYTES) { $error = 'The .otmm file must be ' . (int)(ZNOTE_MINIMAP_MAX_BYTES / 1048576) . ' MB or smaller.'; return false; } if (!function_exists('gzuncompress') || !function_exists('imagecreatetruecolor')) { $error = 'PHP needs the zlib and GD extensions to convert an .otmm minimap.'; return false; } $root = minimap_root(); if (!is_dir($root) && !@mkdir($root, 0755, true) && !is_dir($root)) { $error = 'Could not create ' . ZNOTE_MINIMAP_DIR . '/. Give the web server write access.'; return false; } if (!is_writable($root)) { $error = ZNOTE_MINIMAP_DIR . '/ is not writable by the web server.'; return false; } $data = file_get_contents($tmpFile); if ($data === false) { $error = 'Could not read the uploaded file.'; return false; } @set_time_limit(600); $floors = array(); $tiles = 0; try { $reader = new ZnoteOtmmReader($data); if ($reader->u32() !== ZNOTE_MINIMAP_SIGNATURE) { $error = 'This is not a valid OTMM minimap file.'; return false; } $start = $reader->u16(); $version = $reader->u16(); $reader->u32(); if ($version !== ZNOTE_MINIMAP_VERSION) { $error = 'Unsupported OTMM minimap version.'; return false; } $reader->string(); $reader->seek($start); minimap_delete(); $blocks = 0; while (!$reader->eof()) { if (++$blocks > ZNOTE_MINIMAP_MAX_BLOCK) { minimap_delete(); $error = 'The .otmm minimap contains too many blocks.'; return false; } if ($reader->remaining() < 7) { break; } $x = $reader->u16(); $y = $reader->u16(); $z = $reader->u8(); if ($x >= 65535 || $y >= 65535 || $z > 15) { break; } $length = $reader->u16(); if ($length <= 0 || $length > $reader->remaining()) { minimap_delete(); $error = 'The .otmm minimap contains a corrupt block.'; return false; } $raw = @gzuncompress($reader->bytes($length), ZNOTE_MINIMAP_BLOCK_LEN); if (!is_string($raw) || strlen($raw) !== ZNOTE_MINIMAP_BLOCK_LEN) { continue; } $hasVisible = false; $image = minimap_tile_image($raw, $hasVisible); if (!$hasVisible) { imagedestroy($image); continue; } imagepng($image, $root . '/z' . $z . '-' . $x . '-' . $y . '.png', 6); imagedestroy($image); $tiles++; if (!isset($floors[$z])) { $floors[$z] = array( 'min_x' => $x, 'min_y' => $y, 'max_x' => $x + 63, 'max_y' => $y + 63, 'tiles' => array() ); } $floors[$z]['min_x'] = min($floors[$z]['min_x'], $x); $floors[$z]['min_y'] = min($floors[$z]['min_y'], $y); $floors[$z]['max_x'] = max($floors[$z]['max_x'], $x + 63); $floors[$z]['max_y'] = max($floors[$z]['max_y'], $y + 63); $floors[$z]['tiles'][] = array($x, $y); } } catch (Throwable $e) { minimap_delete(); $error = 'Could not read the .otmm minimap: ' . $e->getMessage(); return false; } if (!$floors) { minimap_delete(); $error = 'No visible minimap tiles were found in this .otmm file.'; return false; } ksort($floors, SORT_NUMERIC); $prepared = array(); foreach ($floors as $z => $floor) { $prepared[(int)$z] = array( 'z' => (int)$z, 'x' => (int)$floor['min_x'], 'y' => (int)$floor['min_y'], 'w' => max(64, (int)($floor['max_x'] - $floor['min_x'] + 1)), 'h' => max(64, (int)($floor['max_y'] - $floor['min_y'] + 1)), 't' => $floor['tiles'] ); } $meta = array( 'date' => time(), 'source' => substr((string)preg_replace('/[^A-Za-z0-9._-]/', '', basename($originalName)), 0, 80), 'tiles' => $tiles, 'floors' => $prepared ); if (file_put_contents(minimap_meta_path(), json_encode($meta)) === false) { minimap_delete(); $error = 'Could not write ' . ZNOTE_MINIMAP_META . '.'; return false; } return true; } function minimap_was_rendered(): bool { return !empty($GLOBALS['__znote_minimap_rendered']); } function minimap_render(string $baseUrl = '', bool $withTitle = true): void { if (minimap_was_rendered()) { return; } $data = minimap_data(); if ($data === false) { return; } $GLOBALS['__znote_minimap_rendered'] = true; $floors = array(); foreach ($data['floors'] as $floor) { $floors[] = array( 'z' => (int)$floor['z'], 'x' => (int)$floor['x'], 'y' => (int)$floor['y'], 't' => $floor['t'] ); } $payload = array( 'base' => (($baseUrl !== '') ? rtrim($baseUrl, '/') . '/' : '') . ZNOTE_MINIMAP_DIR . '/', 'v' => (int)($data['date'] ?? 0), 'start' => 7, 'floors' => $floors ); ?>
Drag to move, scroll or use +/− to zoom, and change floor with the arrows.