From c2731f0276f1a5fbf0caf85069683a2b0e33a5d9 Mon Sep 17 00:00:00 2001 From: Alejandro Liu Date: Fri, 3 Apr 2015 10:48:30 +0200 Subject: [PATCH] Initial commit for 1.4rel --- .gitignore | 5 + README.md | 359 ++++++++++++ build.php | 107 ++++ classlib/autoload.php | 50 ++ classlib/pmimporter/Blocks.php | 86 +++ classlib/pmimporter/Chunk.php | 117 ++++ classlib/pmimporter/Copier.php | 113 ++++ classlib/pmimporter/Entities.php | 51 ++ classlib/pmimporter/ImporterException.php | 5 + classlib/pmimporter/LevelFormat.php | 82 +++ classlib/pmimporter/LevelFormatManager.php | 38 ++ classlib/pmimporter/anvil/Anvil.php | 41 ++ classlib/pmimporter/anvil/Chunk.php | 147 +++++ classlib/pmimporter/anvil/RegionLoader.php | 22 + classlib/pmimporter/blocks.txt | 198 +++++++ .../pmimporter/chunksection/AnvilSection.php | 233 ++++++++ .../pmimporter/chunksection/ChunkSection.php | 181 ++++++ .../chunksection/EmptyChunkSection.php | 116 ++++ classlib/pmimporter/entities.txt | 30 + classlib/pmimporter/generic/BaseFormat.php | 66 +++ classlib/pmimporter/generic/Chunk.php | 160 ++++++ classlib/pmimporter/generic/RegionLoader.php | 167 ++++++ classlib/pmimporter/mcpe020/Chunk.php | 176 ++++++ classlib/pmimporter/mcpe020/McPe020.php | 105 ++++ classlib/pmimporter/mcpe020/RegionLoader.php | 68 +++ classlib/pmimporter/mcregion/Chunk.php | 178 ++++++ classlib/pmimporter/mcregion/McRegion.php | 79 +++ classlib/pmimporter/mcregion/RegionLoader.php | 63 +++ classlib/pmimporter/pm13/Chunk.php | 194 +++++++ classlib/pmimporter/pm13/Pm13.php | 104 ++++ classlib/pmimporter/pm13/RegionLoader.php | 47 ++ classlib/pocketmine/math/Vector3.php | 1 + classlib/pocketmine/nbt/NBT.php | 1 + classlib/pocketmine/nbt/tag/Byte.php | 1 + classlib/pocketmine/nbt/tag/ByteArray.php | 1 + classlib/pocketmine/nbt/tag/Compound.php | 1 + classlib/pocketmine/nbt/tag/Double.php | 1 + classlib/pocketmine/nbt/tag/End.php | 1 + classlib/pocketmine/nbt/tag/Enum.php | 1 + classlib/pocketmine/nbt/tag/Float.php | 1 + classlib/pocketmine/nbt/tag/Int.php | 1 + classlib/pocketmine/nbt/tag/IntArray.php | 1 + classlib/pocketmine/nbt/tag/Long.php | 1 + classlib/pocketmine/nbt/tag/NamedTag.php | 1 + classlib/pocketmine/nbt/tag/Short.php | 1 + classlib/pocketmine/nbt/tag/String.php | 1 + classlib/pocketmine/nbt/tag/Tag.php | 1 + classlib/pocketmine/utils/Binary.php | 1 + classlib/pocketmine_1_3/PocketChunkParser.php | 252 +++++++++ classlib/pocketmine_1_3/pmf/PMF.php | 127 +++++ classlib/pocketmine_1_3/pmf/PMFLevel.php | 526 ++++++++++++++++++ classlib/version.txt | 1 + main.php | 22 + plugin/plugin.yml | 18 + plugin/resources/rules.txt | 7 + plugin/src/ImportMap/Importer.php | 40 ++ plugin/src/ImportMap/Main.php | 91 +++ rules.txt | 7 + scripts/dumpchunk.php | 52 ++ scripts/nbtdump.php | 33 ++ scripts/pmcheck.php | 171 ++++++ scripts/pmconvert.php | 193 +++++++ scripts/pmentities.php | 100 ++++ scripts/pmlevel.php | 118 ++++ version.txt | 1 + 65 files changed, 5164 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 build.php create mode 100644 classlib/autoload.php create mode 100644 classlib/pmimporter/Blocks.php create mode 100644 classlib/pmimporter/Chunk.php create mode 100644 classlib/pmimporter/Copier.php create mode 100644 classlib/pmimporter/Entities.php create mode 100644 classlib/pmimporter/ImporterException.php create mode 100644 classlib/pmimporter/LevelFormat.php create mode 100644 classlib/pmimporter/LevelFormatManager.php create mode 100644 classlib/pmimporter/anvil/Anvil.php create mode 100644 classlib/pmimporter/anvil/Chunk.php create mode 100644 classlib/pmimporter/anvil/RegionLoader.php create mode 100644 classlib/pmimporter/blocks.txt create mode 100644 classlib/pmimporter/chunksection/AnvilSection.php create mode 100644 classlib/pmimporter/chunksection/ChunkSection.php create mode 100644 classlib/pmimporter/chunksection/EmptyChunkSection.php create mode 100644 classlib/pmimporter/entities.txt create mode 100644 classlib/pmimporter/generic/BaseFormat.php create mode 100644 classlib/pmimporter/generic/Chunk.php create mode 100644 classlib/pmimporter/generic/RegionLoader.php create mode 100644 classlib/pmimporter/mcpe020/Chunk.php create mode 100644 classlib/pmimporter/mcpe020/McPe020.php create mode 100644 classlib/pmimporter/mcpe020/RegionLoader.php create mode 100644 classlib/pmimporter/mcregion/Chunk.php create mode 100644 classlib/pmimporter/mcregion/McRegion.php create mode 100644 classlib/pmimporter/mcregion/RegionLoader.php create mode 100644 classlib/pmimporter/pm13/Chunk.php create mode 100644 classlib/pmimporter/pm13/Pm13.php create mode 100644 classlib/pmimporter/pm13/RegionLoader.php create mode 120000 classlib/pocketmine/math/Vector3.php create mode 120000 classlib/pocketmine/nbt/NBT.php create mode 120000 classlib/pocketmine/nbt/tag/Byte.php create mode 120000 classlib/pocketmine/nbt/tag/ByteArray.php create mode 120000 classlib/pocketmine/nbt/tag/Compound.php create mode 120000 classlib/pocketmine/nbt/tag/Double.php create mode 120000 classlib/pocketmine/nbt/tag/End.php create mode 120000 classlib/pocketmine/nbt/tag/Enum.php create mode 120000 classlib/pocketmine/nbt/tag/Float.php create mode 120000 classlib/pocketmine/nbt/tag/Int.php create mode 120000 classlib/pocketmine/nbt/tag/IntArray.php create mode 120000 classlib/pocketmine/nbt/tag/Long.php create mode 120000 classlib/pocketmine/nbt/tag/NamedTag.php create mode 120000 classlib/pocketmine/nbt/tag/Short.php create mode 120000 classlib/pocketmine/nbt/tag/String.php create mode 120000 classlib/pocketmine/nbt/tag/Tag.php create mode 120000 classlib/pocketmine/utils/Binary.php create mode 100644 classlib/pocketmine_1_3/PocketChunkParser.php create mode 100644 classlib/pocketmine_1_3/pmf/PMF.php create mode 100644 classlib/pocketmine_1_3/pmf/PMFLevel.php create mode 120000 classlib/version.txt create mode 100644 main.php create mode 100644 plugin/plugin.yml create mode 100644 plugin/resources/rules.txt create mode 100644 plugin/src/ImportMap/Importer.php create mode 100644 plugin/src/ImportMap/Main.php create mode 100644 rules.txt create mode 100644 scripts/dumpchunk.php create mode 100644 scripts/nbtdump.php create mode 100644 scripts/pmcheck.php create mode 100644 scripts/pmconvert.php create mode 100644 scripts/pmentities.php create mode 100644 scripts/pmlevel.php create mode 100644 version.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..72e8ff4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +bin +pocketmine-src +maps +*.phar +t.sh diff --git a/README.md b/README.md new file mode 100644 index 0000000..36eb590 --- /dev/null +++ b/README.md @@ -0,0 +1,359 @@ +pmimporter +========== + +* Summary: Import world maps into PocketMine-MP 1.4 +* WebSite: [github](https://github.com/alejandroliu/pocketmine-plugins/tree/master/pmimporter) + +Overview +-------- + +* pmconvert - main conversion tool +* pmcheck - read world maps and analyze the block, object composition +* pmentities - dump entity information +* pmlevel - manipulate some level.dat settings +* nbtdump - Dump the contents of NBT files +* dumpchunk - Extract an specific chunk from a map so it can be + processed by nbtdump. + +Description +----------- + +A collection of tools used for importing world maps for use with +PocketMine-MP and Minecraft PE. It can be used from the Command-line +and also as a plugin for PocketMine-MP. + +It supports the following input formats: + +- McRegion (Minecraft PC Edition, PocketMine v1.4) +- Anvil (Minecraft PC Edition) +- PMF (PocketMine v1.3) +- mcpe020 (Minecraft PE 0.2.0-0.8.1) + +Currently, it only support McRegion format for output. + +When importing Minecraft PC Edition world maps (Anvil and McRegion formats) it +will analyze the used blocks to make sure that only blocks supported +by Minecraft PE are generated. It does this by either mapping these +blocks or removing them. This conversion/fitering can be tweaked with +an user provided `rules` file. + +Similarly, Tiles and Entities that are not supported by Minecraft PE +are eliminated. + +This is done because using these unsupported features on a Minecraft +PE client would cause the game to crash. + +Command Usage +------------- + +In general, the command usage is: + +* _path-to-php-executable_ _path-to-pmimporter.phar_ _sub-command_ [options] + +### Sub-commands + +#### PMCONVERT + + pmconvert [-c rules.txt ] [-t count] [-f format] srcpath dstpath + +Converts maps. + +* `-c rules` : Specify a rules conversion file. +* `-t count` : Specifies the number of threads to run. +* `-f format` : Specifies the output format. Defaults to `mcregion`. +* `-o offset` : Specifies the y-offset for the import. A value for + the number of blocks you want to move the world *down*. A negative + value will move the world *up*. Be careful when moving the world + *down* that you do not remove the ground. +* `srcpath` : Directory path to the source world. +* `dstpath` : Directory path to create the new world. + +Also, `pmconvert` allows you to specify special settings to tweak the +format reader/writer code. These settings are specifc to each +`format`. To configure you must pass the option: + +* `--in.setting=value` + for the input format settings. +* `--out.setting-value` + for the output format settings. + +#### PMCHECK + + pmcheck worldpath [--all|[rX,rZ[:cX,cZ[+cX,cZ]]] ...] + +Analyze the number of chunks, blocks, etc in a world map. + +* `worldpath` : Directory path to world to analyze. +* `--all` : If specified it will analyze all regions. +* `rX,rZ` : Region X,Z coordinates. +* `:cX,cZ` : Specify individual chunks (in Chunk offsets, from 0 to + 31) to analyze. +* `+cX,cZ` : Additional chunks to add to totals. + +#### PMLEVEL + + pmlevel worldpath [attr=value] + +Displays and modifies certain level attributes. + +* `worldpath` : Directory path to world to display/modify. +* `attr=value` : Modify the `attr` setting to `value`. + +The following attributes are supported: + +* `spawn=x,y,z` : World spawn point. +* `name=text` : Level name. +* `seed=integer` : Random Seed. +* `generator=name[,version]` : Terrain generator. PocketMine by + default only supports `flat` and `normal`. +* `preset=preset` : Alias for `generatorOptions`. +* `generatorOptions=preset` : Terrain generator `preset` string. + Ignored by the `normal` generator. Used by `flat`. + +#### NBTDUMP + + nbtdump nbt_file + +Dumps the contents of an `NBT` formatted file. + +#### DUMPCHUNK + + dumpchunk worldpath rX,rY:cX,cY + +Arguments are simmilar to `pmcheck`. + +* `worldpath` : Directory path to world to analyze. +* `rX,rZ` : Region X,Z coordinates. +* `:cX,cZ` : Specify individual chunks (in Chunk offsets, from 0 to + 31) to dump. + +Settings +-------- + +Settings are configuration strings that can be used to tweak either +the reading or the writing of maps. These are format specifc. + +## PMF1.3 Settings + +- `Xoff` : offsets chunks in the X direction. Values can be from -15 + to 15, however, unless the values are from 0 (value) to 8, parts of + the maps will be missing (as they will fall outside the region). +- `Zoff` : offsets chunks in the Z direction. Values can be from -15 + to 15, however, unless the values are from 0 (value) to 8, parts of + the maps will be missing (as they will fall outside the region). +- `name` : Changes the reported name of the map. +- `seed` : Changes the reported seed of the map. +- `spawn` : Accepts 3 numbers separated by commas. For example: + `128,64,128`. Changes the reported spawn location. +- `generator` : Changes the reported map generator value. +- `preset` : Changes the reported generator presets value. + +## McPe0.2.0 Settings + +- `Xoff` : offsets chunks in the X direction. Values can be from -15 + to 15, however, unless the values are from 0 (value) to 8, parts of + the maps will be missing (as they will fall outside the region). +- `Zoff` : offsets chunks in the Z direction. Values can be from -15 + to 15, however, unless the values are from 0 (value) to 8, parts of + the maps will be missing (as they will fall outside the region). +- `name` : Changes the reported name of the map. +- `seed` : Changes the reported seed of the map. +- `spawn` : Accepts 3 numbers separated by commas. For example: + `128,64,128`. Changes the reported spawn location. +- `generator` : Changes the reported map generator value. +- `preset` : Changes the reported generator presets value. + +Installation +------------ + +Requirements: + +* This software has only been tested on Linux +* PHP v5.6.0, version used by PocketMine-MP v1.4.1. This one contains + all dependancies. +* PHP CLI API + +Download `pmimporter.phar` and use. It does *not* need to be +installed. If you want to use `pmimporter` as a PocketMine-MP plugin, +copy the phar file to the PocketMine-MP `plugins` directory. + + +Configure translation +--------------------- + +You can configure the translation by providing a `rules` file and +passing it to `pmcovert` with the `-c` option. The format of `rules.txt` +is as follows: + +* comments, start with `;` or `#`. +* `BLOCKS` - indicates the start of a blocks translation rules section. +* `source-block = target-block` is a translation rule. Any block of + type `source-block` is converted to `target-block`. + +There is a default set of conversion rules, but you can tweak it by +using `rules`. + +PocketMine-MP Plugin +-------------------- + +* Summary: Import worlds +* Dependency Plugins: n/a +* PocketMine-MP version: 1.4 - API 1.10.0 +* OptionalPlugins: ManyWorlds +* Categories: World Editing and Management, Admin Tools +* Plugin Access: Commands, World Editing, Manages Worlds + +Basic Usage: + +* /im *path-to-map* *level* + +Runs `pmimporter` from within PocketMine-MP. See `pmimporter` for +more information. + +### Command: + +* im version + +Show the version of the `pmimporter` framework. + +* im *path-to-map* *level* + * path-to-map : Is the file path towards the location of a map. By + default the path is based from the PocketMine directory. You can + also use a absolute path name. + * level : This is the name that the world be given. + +### Configuration + +Because an import will use an `AsyncTask` for quite a while, it is +recommended that you increase the `async-workers` value to +something other than `1`. This setting is in `pocketmine.yml`, in hte +`settings` section. + +You can configure the translation. This plugin will create a +`rules.txt` in its data directory. The format of `rules.txt` +contains: + +### Permission Nodes: + +* im.cmd.im - Allows users to import maps + +### Hints + +Importing maps is not something you would do while playing Minecraft. +If you are using the plugin version, you should only run it on an idle +server. Otherwise, it is better to use use `pmimporter` directly from +the command line instead. You have more options available there. + +Under Linux, `pmimporter` can use multiple threads which can speed-up +things significantly. + +### Permission Nodes: + +* im.cmd.im - Allows users to import maps + +FAQ +--- + +* Q: Why it takes so long? +* A: Because my programming skills suck. I usally start a conversion + and go to bed. When I wake up in the morning the map is ready to + play. +* Q: Why tall builds seem to be chopped off at te top? +* A: That is a limitation of Pocket Edition. It only supports chunks + that are up to 128 blocks high, while the PC edition Anvil worlds + can support up to 256 blocks high. You can shift worlds down by + using the `-o` option. So if you use `-o 40` that will move the + build down 40 blocks. *BE CAREFUL NOT TO REMOVE THE GROUND* +* Q: Why my Anvil format file is not recognized? +* A: That happens with Anvil files that were converted from an + McRegion file. These files contain both `.mcr` and .`mca` files. + These confuses the File format recognizer. You need to delete the + `.mcr` files so the world is recognized as in Anvil format. +* Q: Why I experience glitches when I enter a new world? +* A: This is a Minecraft Pocket Edition limitation. This is made + worse by spawning into a very large chunk (usually very detailed + builds). My recommendation is to change the spawn point to a very + flat (boring) area. Sometimes exting and re-entering the game + helps. +* Q: Why I get corrupted chunks after I modify some (very detailed) areas? +* A: You need to upgrade to PocketMine-MP v1.4.1 +* Q: Why I see some blocks that are not in the original map? +* A: These have to do with how the translation happens. There are + blocks that are not supported by Minecraft Pocket Edition. These + need to be map to a block supported by MCPE. You can tweak this by + modifying the conversion rules. +* Q: Why do converted maps overload my server? +* A: Detailed maps need to be uncompressed by the server. These take + an additional load on the server. + +References +---------- + +* [PocketMine-MP](http://www.pocketmine.net/) +* [Block defintions](https://raw.githubusercontent.com/alejandroliu/pocketmine-plugins/master/pmimporter/classlib/pmimporter/blocks.txt) +* [Minecraft PC data values](http://minecraft.gamepedia.com/Data_values) +* [Minecraft PE data values](http://minecraft.gamepedia.com/Data_values_%28Pocket_Edition%29) + +Issues and Bugs +--------------- + +* Performance is quite poor. It takes me 5 minutes to process a small + map on Linux. Large maps can easily take days. +* The only target format implemented is McRegion. +* Anvil maps are silently truncated to be less than 128 blocks high. + The PocketMine-MP core API only support Y dimensions for 0 to 127. +* PMF v1.3 maps do not provide valid Entity data so it is ignored. +* Entity data is a bit dodgy: + * Dropped items are currently *not* copied. They sometimes crash the + client. + * PocketMine itself has incomplete Entity support, so things are + copied but are usually ignored by PocketMine. +* Conversion table could be better. I am open to suggestions. + +Changes +------ + +* 1.5: ???? + * pmentities fix typos + * minor text info tweaks +* 1.4: Maintenance release + * Added pmentity to dump entity data + * Added region settings to MCPE0.2.0 and PMF1.3 formats. + * Fixed offset functionality. + * Filter out Dropped Item entities. +* 1.3: OldSkool fixes + * Added support for Tiles to PMF maps. + * Added support for Tiles and Entities fo MCPE 0.2.0 maps. + * Fixed HeightMap calculations in PMF and MCPE 0.2.0 formats + * Added `settings` capability to tweak conversion. + * Merged ImportMap and pmimporter into a single Phar file. +* 1.2: Fixes + * pmcheck: show height map statistics. + * pmconvert: offset y coordinates +* 1.1: OldSkool release + * Added support for maps from Minecraft Pocket Edition 0.2.0 - 0.8.1 + * Added support for PMF maps from PocketMine v1.3. +* 1.0 : First release + +Copyright +--------- + +Some of the code used in this program come from PocketMine-MP, +licensed under GPL. + + pmimporter + Copyright (C) 2015 Alejandro Liu + All Rights Reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . diff --git a/build.php b/build.php new file mode 100644 index 0000000..c7650bd --- /dev/null +++ b/build.php @@ -0,0 +1,107 @@ +startBuffering(); + +// set the Phar file stub +// the file stub is merely a small segment of code that gets run initially +// when the Phar file is loaded, and it always ends with a __HALT_COMPILER() + +$p->setStub(''); +if ($plug) $p->setSignatureAlgorithm(Phar::SHA1); + +foreach (['main.php'] as $f) { + echo ("- $f\n"); + $p[$f] = file_get_contents($f); +} + +$help = "Available sub-commands:\n"; +foreach (glob('scripts/*.php') as $f) { + $f = preg_replace('/^scripts\//','',$f); + $f = preg_replace('/\.php$/','',$f); + $help .= "\t$f\n"; +} +$help .= "\tversion\n"; +$help .= "\tplugin\n"; +$help .= "\treadme\n"; +$p['scripts/help.php'] = $help; +$p['scripts/version.php'] = "",$pmversion,$yml); +$p["plugin.yml"] = $yml; +if (preg_match('/\n\s*version: ([^\s]+)\s*\n/',$yml,$mv)) { + echo "Plugin Version: $mv[1]\n"; + $p['scripts/plugin.php'] = "ImportMap v$mv[1]\n"; +} else { + $p['scripts/plugin.php'] = "generic ImportMap\n"; +} + +if ($plug) { + echo("Adding sources...\n"); + $cnt = 0; + foreach(new RecursiveIteratorIterator(new RecursiveDirectoryIterator($plug)) as $s){ + if (!is_file($s)) continue; + $cnt++; + $d = substr($s,strlen($plug)); + if ($d == "plugin.yml") continue; + echo(" [$cnt] $d\n"); + $p->addFile(realpath($s),$d); + } +} + +// COMMENTED THIS OUT AS COMPRESSING WAS GENERATING CORRUPTED ARCHIVES! +//$p->compressFiles(Phar::GZ); + +//Stop buffering write requests to the Phar archive, and save changes to disk +$p->stopBuffering(); +//echo "my.phar archive has been saved"; diff --git a/classlib/autoload.php b/classlib/autoload.php new file mode 100644 index 0000000..de8baba --- /dev/null +++ b/classlib/autoload.php @@ -0,0 +1,50 @@ + 0) + die("PHP Version >5.6.0 required - (Using ".PHP_VERSION.")\n"); +if(php_sapi_name() !== "cli") die("Must run on CLI API php version\n"); + +// Other stuff that we want to pre-load... +require_once(CLASSLIB_DIR."pmimporter/Blocks.php"); +\pmimporter\Blocks::__init(); +require_once(CLASSLIB_DIR."pmimporter/Entities.php"); +\pmimporter\Entities::__init(); + diff --git a/classlib/pmimporter/Blocks.php b/classlib/pmimporter/Blocks.php new file mode 100644 index 0000000..4421036 --- /dev/null +++ b/classlib/pmimporter/Blocks.php @@ -0,0 +1,86 @@ += 0) { + $cname = strtoupper(self::from_camel_case($name)); + define("BL_".$cname,$code); + } else { + self::$blockConv[-$code] = isset($ln[0]) ? $ln[0] : self::INVALID_BLOCK; + } + } + fclose($fp); + } else { + throw new ImporterException("Unable to read blocks.txt\n"); + } + + foreach (["Sign","Chest","Furnace"] as $tile) { + self::$tileIds[$tile] = $tile; + define("TID_".strtoupper(self::from_camel_case($tile)),$tile); + } + } + public static function getTileId($id) { + if (isset(self::$tileIds[$id])) return self::$tileIds[$id]; + return null; + } + public static function getBlockById($id) { + if (isset(self::$blockNames[$id])) return self::$blockNames[$id]; + return null; + } + public static function getBlockByName($name) { + if (isset(self::$blockIds[$name])) return self::$blockIds[$name]; + return null; + } + public static function addRule($cid,$nid) { + if ($cid === null || $nid === null) return; + if ($cid == $nid) return; + if ($cid < 0) $cid = -$cid; + if ($nid < 0) return; + self::$blockConv[$cid] = $nid; + } + + public static function xlateBlock($id) { + if (isset(self::$blockConv[$id])) return self::$blockConv[$id]; + if (isset(self::$blockNames[$id])) return $id; + return self::INVALID_BLOCK; + } +} diff --git a/classlib/pmimporter/Chunk.php b/classlib/pmimporter/Chunk.php new file mode 100644 index 0000000..9d2f3df --- /dev/null +++ b/classlib/pmimporter/Chunk.php @@ -0,0 +1,117 @@ +setPopulated($src->isPopulated()); + $dst->setGenerated($dst->isGenerated()); + + // + // Copy blocks + // + $max_y = 127; + if ($src instanceof \pmimporter\anvil\Chunk) $max_y = 255; + + for ($x = 0;$x < 16;$x++) { + for ($z=0;$z < 16;$z++) { + for ($y=0;$y < 128;$y++) { + + $calc_y = $y + $offset; + if ($calc_y < 0) { + list($id,$meta) = [7,0]; + } elseif ($calc_y > $max_y) { + list($id,$meta) = [0,0]; + } else { + list($id,$meta) = $src->getBlock($x,$calc_y,$z); + } + // if ($id !== Blocks::xlateBlock($id)) ++$converted; + $id = Blocks::xlateBlock($id); + $dst->setBlock($x,$y,$z,$id,$meta); + $dst->setBlockSkyLight($x,$y,$z,$src->getBlockSkyLight($x,$y,$z)); + $dst->setBlockLight($x,$y,$z,$src->getBlockLight($x,$y,$z)); + } + $dst->setBiomeId($x,$z,$src->getBiomeId($x,$z)); + } + } + + // + // Copy data arrays + // + $heights = $src->getHeightMapArray(); + foreach ($heights as $off => $y) { + $x = $off & 0xf; + $z = $off >> 4; + $dst->setHeightMap($x,$z,$y); + } + $colors = $src->getBiomeColorArray(); + foreach ($colors as $off => $color) { + $x = $off & 0xf; + $z = $off >> 4; + $color = $color & 0xFFFFFF; + list($r,$g,$b) = [$color >> 16, ($color >> 8) & 0xFF, $color & 0xFF]; + $dst->setBiomeColor($x,$z,$r,$g,$b); + } + + // Copy Entities + $entities = []; + foreach ($src->getEntities() as $entity) { + if (!isset($entity->id)) continue; + if (Entities::getId($entity->id->getValue()) === null) continue; + if (Entities::getId($entity->id->getValue()) == "Item") continue; + $copy = clone $entity; + if ($offset != 0 && isset($copy->Pos)) { + $copy->Pos[1] = new Double("",$copy->Pos[1]-$offset); + } + $entities[] = $copy; + } + $dst->setEntities($entities); + + // Copy tiles + $tiles = []; + foreach ($src->getTileEntities() as $tile) { + if (!isset($tile->id)) continue; + if (Blocks::getTileId($tile->id->getValue()) === null) continue; + $clone = clone $tile; + if ($offset != 0) { + $clone->y = new Int("y",$clone->y->getValue()-$offset); + } + $tiles[] = $clone; + } + $dst->setTileEntities($tiles); + } + + static public function copyRegion($region,&$srcfmt,&$dstfmt,$cb=null,$offset=0) { + list($rX,$rZ) = $region; + if (is_callable($cb)) call_user_func($cb,"CopyRegionStart","$rX,$rZ"); + + $srcregion = $srcfmt->getRegion($rX,$rZ); + $dstregion = $dstfmt->getRegion($rX,$rZ); + + for ($oX = 0; $oX < 32; $oX++) { + $cX = $rX * 32 + $oX; + for ($oZ = 0; $oZ < 32 ; $oZ++) { + $cZ = $rZ * 32 + $oZ; + if ($srcregion->chunkExists($oX,$oZ)) { + $srcchunk = $srcregion->readChunk($oX,$oZ); + if ($srcchunk->isPopulated() || $srcchunk->isGenerated()) { + $dstchunk = $dstregion->newChunk($cX,$cZ); + if (is_callable($cb)) call_user_func($cb,"CopyChunk","$cX,$cZ"); + self::copyChunk($srcchunk,$dstchunk,$offset); + $dstregion->writeChunk($oX,$oZ,$dstchunk); + } + } + } + } + $dstregion->close(); + $srcregion->close(); + if (is_callable($cb)) call_user_func($cb,"CopyRegionDone","$rX,$rZ"); + } +} diff --git a/classlib/pmimporter/Entities.php b/classlib/pmimporter/Entities.php new file mode 100644 index 0000000..9aaefe1 --- /dev/null +++ b/classlib/pmimporter/Entities.php @@ -0,0 +1,51 @@ + 0) return $id; + return null; + } + public static function getEntityId($name) { + if (isset(self::$entityIds[$name]) && self::$entityIds[$name] > 0) + return self::$entityIds[$name]; + return null; + } + public static function getEntityById($id) { + if (isset(self::$entityNames[$id])) return self::$entityNames[$id]; + return null; + } +} diff --git a/classlib/pmimporter/ImporterException.php b/classlib/pmimporter/ImporterException.php new file mode 100644 index 0000000..270ee7a --- /dev/null +++ b/classlib/pmimporter/ImporterException.php @@ -0,0 +1,5 @@ +regions === null) { + $this->regions = []; + $files = glob($this->getPath()."region/r.*.mca"); + foreach ($files as $f) { + $pp = []; + if (preg_match('/r\.(-?\d+)\.(-?\d+)\.mca$/',$f,$pp)) { + array_shift($pp); + $this->regions[$pp[0].",".$pp[1]] = $pp; + } + } + } + return $this->regions; + } + + public function getRegion($x,$z) { + return new RegionLoader($this,$x,$z,"mca",$this->readOnly); + } +} diff --git a/classlib/pmimporter/anvil/Chunk.php b/classlib/pmimporter/anvil/Chunk.php new file mode 100644 index 0000000..f2f3d8b --- /dev/null +++ b/classlib/pmimporter/anvil/Chunk.php @@ -0,0 +1,147 @@ +nbt->Sections) and ($this->nbt->Sections instanceof Enum)){ + $this->nbt->Sections->setTagType(NBT::TAG_Compound); + } else { + $this->nbt->Sections = new Enum("Sections", []); + $this->nbt->Sections->setTagType(NBT::TAG_Compound); + } + $sections = []; + foreach($this->nbt->Sections as $section){ + if($section instanceof Compound){ + $y = (int) $section["Y"]; + if($y < 16){ + $sections[$y] = new AnvilSection($section); + } + } + } + for($y = 0; $y < 16; ++$y){ + if(!isset($sections[$y])){ + $sections[$y] = new EmptyChunkSection($y); + } + } + $this->sections = $sections; + + unset($this->nbt->Sections); + } + + + + /** + * @param string $data + * @param LevelProvider $provider + * + * @return Chunk + */ + public static function fromBinary($data, LevelProvider $provider = null){ + $nbt = new NBT(NBT::BIG_ENDIAN); + + $nbt->readCompressed($data, ZLIB_ENCODING_DEFLATE); + $chunk = $nbt->getData(); + + if(!isset($chunk->Level) or !($chunk->Level instanceof Compound)){ + return null; + } + + return new Chunk($chunk->Level); + } + + public function toBinary(){ + $nbt = clone $this->getNBT(); + + $nbt->xPos = new Int("xPos", $this->x); + $nbt->zPos = new Int("zPos", $this->z); + + $nbt->Sections = new Enum("Sections", []); + $nbt->Sections->setTagType(NBT::TAG_Compound); + foreach($this->getSections() as $section){ + if($section instanceof EmptyChunkSection){ + continue; + } + $nbt->Sections[$section->getY()] = + new Compound(null, + [ + "Y" => new Byte("Y", $section->getY()), + "Blocks" => new ByteArray("Blocks", $section->getIdArray()), + "Data" => new ByteArray("Data", $section->getDataArray()), + "BlockLight" => new ByteArray("BlockLight", $section->getLightArray()), + "SkyLight" => new ByteArray("SkyLight", $section->getSkyLightArray()) + ]); + } + + $nbt->Biomes = new ByteArray("Biomes", $this->getBiomeIdArray()); + $nbt->BiomeColors = new IntArray("BiomeColors", $this->getBiomeColorArray()); + + $nbt->HeightMap = new IntArray("HeightMap", $this->getHeightMapArray()); + + $entities = []; + + foreach($this->getEntities() as $entity){ + if(!($entity instanceof Player) and !$entity->closed){ + $entity->saveNBT(); + $entities[] = $entity->namedtag; + } + } + + $nbt->Entities = new Enum("Entities", $entities); + $nbt->Entities->setTagType(NBT::TAG_Compound); + + + $tiles = []; + foreach($this->getTiles() as $tile){ + $tile->saveNBT(); + $tiles[] = $tile->namedtag; + } + + $nbt->TileEntities = new Enum("TileEntities", $tiles); + $nbt->TileEntities->setTagType(NBT::TAG_Compound); + $writer = new NBT(NBT::BIG_ENDIAN); + $nbt->setName("Level"); + $writer->setData(new Compound("", ["Level" => $nbt])); + + return $writer->writeCompressed(ZLIB_ENCODING_DEFLATE, RegionLoader::$COMPRESSION_LEVEL); + } + + public function getBlock($x, $y, $z) { + $full = $this->sections[$y >> 4]->getFullBlock($x, $y & 0x0f, $z); + $blockId = $full >> 4; + $meta = $full & 0x0f; + return [$blockId,$meta]; + } + + public function setBlock($x, $y, $z, $blockId = null, $meta = null){ + throw new ImporterException("Unimplemented ".__CLASS__."::".__METHOD__); + } + public function getBlockSkyLight($x, $y, $z) { + return $this->sections[$y >> 4]->getBlockSkyLight($x, $y & 0x0f, $z); + } + public function setBlockSkyLight($x, $y, $z, $level) { + throw new ImporterException("Unimplemented ".__CLASS__."::".__METHOD__); + } + public function getBlockLight($x, $y, $z) { + return $this->sections[$y >> 4]->getBlockLight($x, $y & 0x0f, $z); + + } + public function setBlockLight($x, $y, $z, $level) { + throw new ImporterException("Unimplemented ".__CLASS__."::".__METHOD__); + } +} \ No newline at end of file diff --git a/classlib/pmimporter/anvil/RegionLoader.php b/classlib/pmimporter/anvil/RegionLoader.php new file mode 100644 index 0000000..7c70188 --- /dev/null +++ b/classlib/pmimporter/anvil/RegionLoader.php @@ -0,0 +1,22 @@ +readChunkData($x,$z); + if ($data === null) return null; + return Chunk::fromBinary($data); + } + + public function newChunk($oX,$oZ) { + throw new ImporterException("Unimplemented ".__CLASS__."::".__METHOD__); + } + public function writeChunk($oX,$oZ,$chunk) { + throw new ImporterException("Unimplemented ".__CLASS__."::".__METHOD__); + } + +} diff --git a/classlib/pmimporter/blocks.txt b/classlib/pmimporter/blocks.txt new file mode 100644 index 0000000..4eca61d --- /dev/null +++ b/classlib/pmimporter/blocks.txt @@ -0,0 +1,198 @@ +; +; Block definition table +; +; Ids used in Minecraft PE are > 0. +; Ids not used by Minecraft PE (defined in PC edition) are < 0. +; +;Id Name Conversion +0 Air +1 Stone +2 GrassBlock +3 Dirt +4 Cobblestone +5 WoodenPlank +6 Sapling +7 Bedrock +8 Water +9 StationaryWater +10 Lava +11 StationaryLava +12 Sand +13 Gravel +14 GoldOre +15 IronOre +16 CoalOre +17 Wood +18 Leaves +19 Sponge +20 Glass +21 LapisLazuliOre +22 LapisLazuliBlock +-23 Dispenser 248 +24 Sandstone +-25 NoteBlock 248 +26 Bed +27 PoweredRail +-28 DetectorRail 248 +-29 StickyPiston 248 +30 Cobweb +31 TallGrass +32 DeadBush +-33 Piston 248 +-34 PistonHead 248 +35 Wool +-36 PistonExtension 248 +37 Dandelion +38 Poppy +39 BrownMushroom +40 RedMushroom +41 GoldBlock +42 IronBlock +43 DoubleStoneSlab +44 StoneSlab +45 BrickBlock +46 TNT +47 Bookshelf +48 MossStone +49 Obsidian +50 Torch +51 Fire +52 MonsterSpawner +53 OakWoodStairs +54 Chest +-55 RedstoneWire 0 +56 DiamondOre +57 DiamondBlock +58 CraftingTable +59 Seeds +60 Farmland +61 Furnace +62 BurningFurnace +63 SignPost +64 WoodenDoor +65 Ladder +66 Rail +67 CobblestoneStairs +68 WallSign +-69 Lever 0 +-70 StonePressurePlate 0 +71 IronDoor +-72 WoodenPressurePlate 0 +73 RedstoneOre +74 GlowingRedstoneOre +-75 InactiveRedstoneTorch 50 +-76 ActiveRedstoneTorch 50 +-77 StoneButton 0 +78 SnowCover +79 Ice +80 Snow +81 Cactus +82 Clay +83 SugarCane +-84 Jukebox 248 +85 Fence +86 Pumpkin +87 NetherRack +-88 SoulSand 13 +89 Glowstone +-90 NetherPortal 79 +91 JackoLantern +92 CakeBlock +-93 UnpoweredRepeater 248 +-94 PoweredRepeater 248 +95 InvisibleBedrock +96 Trapdoor +-97 MonsterEgg 248 +98 StoneBrick +99 HugeBrownMushroom +100 HugeRedMushroom +101 IronBars +102 GlassPane +103 Melon +104 PumpkinStem +105 MelonStem +106 Vines +107 FenceGate +108 BrickStairs +109 StoneBrickStairs +110 Mycelium +111 LilyPad +112 NetherBrick +-113 NetherBrickFence 85 +114 NetherBrickStairs +-115 NetherWart 39 +-116 EnchantingTable 58 +-117 BrewingStand 248 +-118 Cauldron 248 +-119 EndPortal 0 +120 EndPortalFrame +121 EndStone +-122 DragonEgg 248 +-123 RedstoneLamp 50 +-124 LitRedstoneLamp 50 +-125 DoubleWoodenSlab 157 +-126 WoodenSlab 158 +127 Cocoa +128 SandstoneStairs +129 EmeraldOre +-130 EnderChest 248 +-131 TripwireHook 0 +-132 Tripwire 0 +133 BlockofEmerald +134 SpruceWoodStairs +135 BirchWoodStairs +136 JungleWoodStairs +-137 CommandBlock 248 +-138 Beacon 51 +139 CobblestoneWall +-140 FlowerPot 2 +141 Carrots +142 Potato +-143 WoodenButton 248 +-144 Skull 91 +-145 Anvil 248 +-146 TrappedChest 54 +-147 LightPressurePlate 0 +-148 HeavyPressurePlate 0 +-149 UnpoweredComparator 0 +-150 PoweredComparator 0 +-151 DaylightDetector 0 +-152 RedstoneBlock 89 +-153 NetherQuartzOre 112 +-154 Hopper 248 +155 BlockofQuartz +156 QuartzStairs +157 WoodenDoubleSlab +158 WoodenSlab +159 StainedClay +-160 StainedGlassPane 102 +-161 AcaciaDarkOakLeaves 18 +-162 AcaciaDarkOakWood 17 +163 AcaciaWoodStairs +164 DarkOakWoodStairs +-165 SlimeBlock 133 +-166 Barrier 95 +-167 IronTrapdoor 96 +-168 Prismarine 247 +-169 SeaLantern 89 +170 HayBlock +171 Carpet +172 HardenedClay +173 BlockofCoal +174 PackedIce +-175 LargeFlowers 32 +-176 StandingBanner 0 +-177 WallBanner 248 +-178 InvertedLightSensor 0 +-179 RedSandstone 24 +-180 RedSandstoneStairs 128 +-181 DoubleRedSanstoneSlab 43 +-182 RedSandstoneSlab 44 +243 Podzol +244 Beetroot +245 StoneCutter +246 GlowingObsidian +247 NetherReactorCore +248 UpdateGameBlock1 +249 UpdateGameBlock2 +255 .name diff --git a/classlib/pmimporter/chunksection/AnvilSection.php b/classlib/pmimporter/chunksection/AnvilSection.php new file mode 100644 index 0000000..e2053d4 --- /dev/null +++ b/classlib/pmimporter/chunksection/AnvilSection.php @@ -0,0 +1,233 @@ +y = (int) $nbt["Y"]; + $this->blocks = (string) $nbt["Blocks"]; + $this->data = (string) $nbt["Data"]; + $this->blockLight = (string) $nbt["BlockLight"]; + $this->skyLight = (string) $nbt["SkyLight"]; + } + + public function getY(){ + return $this->y; + } + + public function getBlockId($x, $y, $z){ + return ord($this->blocks{($y << 8) + ($z << 4) + $x}); + } + + public function setBlockId($x, $y, $z, $id){ + $this->blocks{($y << 8) + ($z << 4) + $x} = chr($id); + } + + public function getBlockData($x, $y, $z){ + $m = ord($this->data{($y << 7) + ($z << 3) + ($x >> 1)}); + if(($x & 1) === 0){ + return $m & 0x0F; + }else{ + return $m >> 4; + } + } + + public function setBlockData($x, $y, $z, $data){ + $i = ($y << 7) + ($z << 3) + ($x >> 1); + $old_m = ord($this->data{$i}); + if(($x & 1) === 0){ + $this->data{$i} = chr(($old_m & 0xf0) | ($data & 0x0f)); + }else{ + $this->data{$i} = chr((($data & 0x0f) << 4) | ($old_m & 0x0f)); + } + } + + public function getBlock($x, $y, $z, &$blockId, &$meta = null){ + $full = $this->getFullBlock($x, $y, $z); + $blockId = $full >> 4; + $meta = $full & 0x0f; + } + + public function getFullBlock($x, $y, $z){ + $i = ($y << 8) + ($z << 4) + $x; + if(($x & 1) === 0){ + return (ord($this->blocks{$i}) << 4) | (ord($this->data{$i >> 1}) & 0x0F); + }else{ + return (ord($this->blocks{$i}) << 4) | (ord($this->data{$i >> 1}) >> 4); + } + } + + public function setBlock($x, $y, $z, $blockId = null, $meta = null){ + $i = ($y << 8) + ($z << 4) + $x; + + $changed = false; + + if($blockId !== null){ + $blockId = chr($blockId); + if($this->blocks{$i} !== $blockId){ + $this->blocks{$i} = $blockId; + $changed = true; + } + } + + if($meta !== null){ + $i >>= 1; + $old_m = ord($this->data{$i}); + if(($x & 1) === 0){ + $this->data{$i} = chr(($old_m & 0xf0) | ($meta & 0x0f)); + if(($old_m & 0x0f) !== $meta){ + $changed = true; + } + }else{ + $this->data{$i} = chr((($meta & 0x0f) << 4) | ($old_m & 0x0f)); + if((($old_m & 0xf0) >> 4) !== $meta){ + $changed = true; + } + } + } + + return $changed; + } + + public function getBlockSkyLight($x, $y, $z){ + $sl = ord($this->skyLight{($y << 7) + ($z << 3) + ($x >> 1)}); + if(($x & 1) === 0){ + return $sl & 0x0F; + }else{ + return $sl >> 4; + } + } + + public function setBlockSkyLight($x, $y, $z, $level){ + $i = ($y << 7) + ($z << 3) + ($x >> 1); + $old_sl = ord($this->skyLight{$i}); + if(($x & 1) === 0){ + $this->skyLight{$i} = chr(($old_sl & 0xf0) | ($level & 0x0f)); + }else{ + $this->skyLight{$i} = chr((($level & 0x0f) << 4) | ($old_sl & 0x0f)); + } + } + + public function getBlockLight($x, $y, $z){ + $l = ord($this->blockLight{($y << 7) + ($z << 3) + ($x >> 1)}); + if(($x & 1) === 0){ + return $l & 0x0F; + }else{ + return $l >> 4; + } + } + + public function setBlockLight($x, $y, $z, $level){ + $i = ($y << 7) + ($z << 3) + ($x >> 1); + $old_l = ord($this->blockLight{$i}); + if(($x & 1) === 0){ + $this->blockLight{$i} = chr(($old_l & 0xf0) | ($level & 0x0f)); + }else{ + $this->blockLight{$i} = chr((($level & 0x0f) << 4) | ($old_l & 0x0f)); + } + } + + public function getBlockIdColumn($x, $z){ + $i = ($z << 4) + $x; + $column = ""; + for($y = 0; $y < 16; ++$y){ + $column .= $this->blocks{($y << 8) + $i}; + } + + return $column; + } + + public function getBlockDataColumn($x, $z){ + $i = ($z << 3) + ($x >> 1); + $column = ""; + if(($x & 1) === 0){ + for($y = 0; $y < 16; $y += 2){ + $column .= ($this->data{($y << 7) + $i} & "\x0f") | chr((ord($this->data{(($y + 1) << 7) + $i}) & 0x0f) << 4); + } + }else{ + for($y = 0; $y < 16; $y += 2){ + $column .= chr((ord($this->data{($y << 7) + $i}) & 0xf0) >> 4) | ($this->data{(($y + 1) << 7) + $i} & "\xf0"); + } + } + + return $column; + } + + public function getBlockSkyLightColumn($x, $z){ + $i = ($z << 3) + ($x >> 1); + $column = ""; + if(($x & 1) === 0){ + for($y = 0; $y < 16; $y += 2){ + $column .= ($this->skyLight{($y << 7) + $i} & "\x0f") | chr((ord($this->skyLight{(($y + 1) << 7) + $i}) & 0x0f) << 4); + } + }else{ + for($y = 0; $y < 16; $y += 2){ + $column .= chr((ord($this->skyLight{($y << 7) + $i}) & 0xf0) >> 4) | ($this->skyLight{(($y + 1) << 7) + $i} & "\xf0"); + } + } + + return $column; + } + + public function getBlockLightColumn($x, $z){ + $i = ($z << 3) + ($x >> 1); + $column = ""; + if(($x & 1) === 0){ + for($y = 0; $y < 16; $y += 2){ + $column .= ($this->blockLight{($y << 7) + $i} & "\x0f") | chr((ord($this->blockLight{(($y + 1) << 7) + $i}) & 0x0f) << 4); + } + }else{ + for($y = 0; $y < 16; $y += 2){ + $column .= chr((ord($this->blockLight{($y << 7) + $i}) & 0xf0) >> 4) | ($this->blockLight{(($y + 1) << 7) + $i} & "\xf0"); + } + } + + return $column; + } + + public function getIdArray(){ + return $this->blocks; + } + + public function getDataArray(){ + return $this->data; + } + + public function getSkyLightArray(){ + return $this->skyLight; + } + + public function getLightArray(){ + return $this->blockLight; + } + +} \ No newline at end of file diff --git a/classlib/pmimporter/chunksection/ChunkSection.php b/classlib/pmimporter/chunksection/ChunkSection.php new file mode 100644 index 0000000..c35a6a9 --- /dev/null +++ b/classlib/pmimporter/chunksection/ChunkSection.php @@ -0,0 +1,181 @@ +y = $y; + } + + final public function getY(){ + return $this->y; + } + + final public function getBlockId($x, $y, $z){ + return 0; + } + + final public function getBlockIdColumn($x, $z){ + return "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; + } + + final public function getBlockDataColumn($x, $z){ + return "\x00\x00\x00\x00\x00\x00\x00\x00"; + } + + final public function getBlockSkyLightColumn($x, $z){ + return "\xff\xff\xff\xff\xff\xff\xff\xff"; + } + + final public function getBlockLightColumn($x, $z){ + return "\x00\x00\x00\x00\x00\x00\x00\x00"; + } + + final public function getFullBlock($x, $y, $z){ + return 0; + } + + final public function getBlock($x, $y, $z, &$id = null, &$meta = null){ + $id = 0; + $meta = 0; + } + + final public function setBlock($x, $y, $z, $id = null, $meta = null){ + throw new ImporterException("Tried to modify an empty Chunk"); + } + + public function getIdArray(){ + return str_repeat("\x00", 4096); + } + + public function getDataArray(){ + return str_repeat("\x00", 2048); + } + + public function getSkyLightArray(){ + return str_repeat("\xff", 2048); + } + + public function getLightArray(){ + return str_repeat("\x00", 2048); + } + + final public function setBlockId($x, $y, $z, $id){ + throw new ImporterException("Tried to modify an empty Chunk"); + } + + final public function getBlockData($x, $y, $z){ + return 0; + } + + final public function setBlockData($x, $y, $z, $data){ + throw new ImporterException("Tried to modify an empty Chunk"); + } + + final public function getBlockLight($x, $y, $z){ + return 0; + } + + final public function setBlockLight($x, $y, $z, $level){ + throw new ImporterException("Tried to modify an empty Chunk"); + } + + final public function getBlockSkyLight($x, $y, $z){ + return 0; + } + + final public function setBlockSkyLight($x, $y, $z, $level){ + throw new ImporterException("Tried to modify an empty Chunk"); + } +} \ No newline at end of file diff --git a/classlib/pmimporter/entities.txt b/classlib/pmimporter/entities.txt new file mode 100644 index 0000000..7100720 --- /dev/null +++ b/classlib/pmimporter/entities.txt @@ -0,0 +1,30 @@ +; +; Entity definition table +; +64 Item +65 PrimedTnt +66 FallingSand + +80 Arrow +81 Snowball +82 ThrownEgg +83 Minecart +84 Painting + +39 Silverfish +38 Enderman +37 Slime +36 PigZombie +35 Spider +34 Skeleton +33 Creeper +32 Zombie + +10 Chicken +11 Cow +12 Pig +13 Sheep +14 Wolf +16 MushroomCow + +15 Villager diff --git a/classlib/pmimporter/generic/BaseFormat.php b/classlib/pmimporter/generic/BaseFormat.php new file mode 100644 index 0000000..e51d5bc --- /dev/null +++ b/classlib/pmimporter/generic/BaseFormat.php @@ -0,0 +1,66 @@ +readOnly = $ro; + if (!is_dir($path)) + throw new ImporterException("$path: path does not exist\n"); + $path = preg_replace('/\/*$/',"",$path).'/'; + $this->path = $path; + + $nbt = new NBT(NBT::BIG_ENDIAN); + $nbt->readCompressed(file_get_contents($this->getPath()."level.dat")); + $levelData = $nbt->getData(); + if ($levelData->Data instanceof Compound){ + $this->levelData = $levelData->Data; + } else { + throw new ImporterException("Invalid level.dat\n"); + } + } + public function getPath() { + return $this->path; + } + public function getName() { + return $this->levelData["LevelName"]; + } + public function getSeed() { + return $this->levelData["RandomSeed"]; + } + public function getSpawn() { + return new Vector3((float)$this->levelData["SpawnX"],(float)$this->levelData["SpawnY"],(float)$this->levelData["SpawnZ"]); + } + public function getGenerator() { + return $this->levelData["generatorName"]; + } + public function getGeneratorOptions() { + return ["preset"=>$this->levelData["generatorOptions"]]; + } + /** + * @return Compound + */ + public function getLevelData(){ + return $this->levelData; + } + public function saveLevelData(){ + $nbt = new NBT(NBT::BIG_ENDIAN); + $nbt->setData(new Compound(null, [ + "Data" => $this->levelData + ])); + $buffer = $nbt->writeCompressed(); + file_put_contents($this->getPath() . "level.dat", $buffer); + } +} diff --git a/classlib/pmimporter/generic/Chunk.php b/classlib/pmimporter/generic/Chunk.php new file mode 100644 index 0000000..aa35123 --- /dev/null +++ b/classlib/pmimporter/generic/Chunk.php @@ -0,0 +1,160 @@ +nbt = $nbt; + + $this->x = $nbt->xPos->getValue(); + $this->z = $nbt->zPos->getValue(); + + if(!isset($this->nbt->Biomes) or !($this->nbt->Biomes instanceof ByteArray)){ + $this->nbt->Biomes = new ByteArray("Biomes", str_repeat("\x01", 256)); + } + $biomeIds = $this->nbt->Biomes->getValue(); + if(strlen($biomeIds) === 256){ + $this->biomeIds = $biomeIds; + }else{ + $this->biomeIds = str_repeat("\x01", 256); + } + unset($this->nbt->Biomes); + + if(!isset($this->nbt->BiomeColors) or !($this->nbt->BiomeColors instanceof IntArray)){ + $this->nbt->BiomeColors = new IntArray("BiomeColors", array_fill(0, 256, Binary::readInt("\x00\x85\xb2\x4a"))); + } + $biomeColors = $this->nbt->BiomeColors->getValue(); + if(count($biomeColors) === 256){ + $this->biomeColors = $biomeColors; + }else{ + $this->biomeColors = array_fill(0, 256, Binary::readInt("\x00\x85\xb2\x4a")); + } + unset($this->nbt->BiomeColors); + + if(!isset($this->nbt->HeightMap) or !($this->nbt->HeightMap instanceof IntArray)){ + $this->nbt->HeightMap = new IntArray("HeightMap", array_fill(0, 256, 127)); + } + $heightMap = $this->nbt->HeightMap->getValue(); + if(count($heightMap) === 256){ + $this->heightMap = $heightMap; + }else{ + $this->heightMap = array_fill(0, 256, 127); + } + unset($this->nbt->HeightMap); + + + if(isset($this->nbt->Entities) and $this->nbt->Entities instanceof Enum){ + $this->nbt->Entities->setTagType(NBT::TAG_Compound); + }else{ + $this->nbt->Entities = new Enum("Entities", []); + $this->nbt->Entities->setTagType(NBT::TAG_Compound); + } + $this->NBTentities = $this->nbt->Entities->getValue(); + unset($this->nbt->Entities); + + if(isset($this->nbt->TileEntities) and $this->nbt->TileEntities instanceof Enum){ + $this->nbt->TileEntities->setTagType(NBT::TAG_Compound); + }else{ + $this->nbt->TileEntities = new Enum("TileEntities", []); + $this->nbt->TileEntities->setTagType(NBT::TAG_Compound); + } + $this->NBTtiles = $this->nbt->TileEntities->getValue(); + unset($this->nbt->TileEntities); + } + + public function getBiomeId($x, $z) { + return ord($this->biomeIds{($z << 4) + $x}); + } + public function setBiomeId($x, $z, $biomeId) { + $this->biomeIds{($z << 4) + $x} = chr($biomeId); + } + public function getBiomeIdArray(){ + return $this->biomeIds; + } + + public function getHeightMap($x, $z) { + return $this->heightMap[($z << 4) + $x]; + } + public function setHeightMap($x, $z, $value) { + $this->heightMap[($z << 4) + $x] = $value; + } + public function getHeightMapArray() { + return $this->heightMap; + } + public function getBiomeColorArray() { + return $this->biomeColors; + } + public function getBiomeColor($x, $z) { + $color = $this->biomeColors[($z << 4) + $x] & 0xFFFFFF; + return [$color >> 16, ($color >> 8) & 0xFF, $color & 0xFF]; + } + public function setBiomeColor($x, $z, $R, $G, $B) { + $this->biomeColors[($z << 4) + $x] = 0 | (($R & 0xFF) << 16) | (($G & 0xFF) << 8) | ($B & 0xFF); + + } + public function getNBT() { + return $this->nbt; + } + public function getEntities() { + return $this->NBTentities; + } + public function setEntities(array $entities = []) { + $this->NBTentities = $entities; + } + public function getTileEntities() { + return $this->NBTtiles; + } + public function setTileEntities(array $tiles = []) { + $this->NBTtiles = $tiles; + } + + /** + * @return bool + */ + public function isPopulated(){ + return $this->nbt["TerrainPopulated"] > 0; + } + /** + * @param int $value + */ + public function setPopulated($value = 1){ + $this->nbt->TerrainPopulated = new Byte("TerrainPopulated", $value); + } + /** + * @return bool + */ + public function isGenerated(){ + return $this->nbt["TerrainPopulated"] > 0 or (isset($this->nbt->TerrainGenerated) and $this->nbt["TerrainGenerated"] > 0); + } + + /** + * @param int $value + */ + public function setGenerated($value = 1){ + $this->nbt->TerrainGenerated = new Byte("TerrainGenerated", $value); + } + + +} \ No newline at end of file diff --git a/classlib/pmimporter/generic/RegionLoader.php b/classlib/pmimporter/generic/RegionLoader.php new file mode 100644 index 0000000..6b7fdb7 --- /dev/null +++ b/classlib/pmimporter/generic/RegionLoader.php @@ -0,0 +1,167 @@ +x = $rX; + $this->z = $rZ; + $this->formatProvider = $fmt; + $this->readOnly = $ro; + + $this->filePath = $this->formatProvider->getPath()."region/r.$rX.$rZ.$ext"; + $exists = file_exists($this->filePath); + if ($ro) { + if (!$exists) { + throw new ImporterException("Region $rX,$rZ does not exist!"); + } + //echo ("OPENING $this->filePath ReadOnly\n"); + $this->filePointer = fopen($this->filePath,"rb"); + } else { + //echo ("OPENING $this->filePath RW\n"); + touch($this->filePath); + $this->filePointer = fopen($this->filePath,"r+b"); + stream_set_write_buffer($this->filePointer,1024*16); // 16KB + } + stream_set_read_buffer($this->filePointer,1024*16); // 16KB + if (!$exists) { + $this->createBlank(); + } else { + $this->loadLocationTable(); + } + } + + public function __destruct() { + if(is_resource($this->filePointer)) { + if (!$this->readOnly) $this->writeLocationTable(); + fclose($this->filePointer); + } + } + + protected function isChunkPresent($index) { + return !($this->locationTable[$index][0] === 0 or $this->locationTable[$index][1] === 0); + } + public function chunkExists($x,$z) { + return $this->isChunkPresent(self::getChunkOffset($x,$z)); + } + + public function readChunkData($x,$z) { + $index = self::getChunkOffset($x, $z); + if($index < 0 or $index >= 4096) return null; + if(!$this->isChunkPresent($index)) return null; + fseek($this->filePointer, $this->locationTable[$index][0] << 12); + $length = Binary::readInt(fread($this->filePointer, 4)); + $compression = ord(fgetc($this->filePointer)); + if($length <= 0 or $length > self::MAX_SECTOR_LENGTH) return null; + return fread($this->filePointer,$length-1); + } + + public function close(){ + if (!$this->readOnly) $this->writeLocationTable(); + fclose($this->filePointer); + $this->levelProvider = null; + } + + protected function createBlank(){ + fseek($this->filePointer, 0); + ftruncate($this->filePointer, 0); + $this->lastSector = 2; + $table = ""; + for($i = 0; $i < 1024; ++$i){ + $this->locationTable[$i] = [0, 0]; + $table .= Binary::writeInt(0); + } + + $time = time(); + for($i = 0; $i < 1024; ++$i){ + $this->locationTable[$i][2] = $time; + $table .= Binary::writeInt($time); + } + + fwrite($this->filePointer, $table, 4096 * 2); + } + + protected function loadLocationTable(){ + fseek($this->filePointer, 0); + $this->lastSector = 2; + + $table = fread($this->filePointer, 4 * 1024 * 2); //1024 records * 4 bytes * 2 times + for($i = 0; $i < 1024; ++$i){ + $index = unpack("N", substr($table, $i << 2, 4))[1]; + $this->locationTable[$i] = [$index >> 8, $index & 0xff, unpack("N", substr($table, 4096 + ($i << 2), 4))[1]]; + if(($this->locationTable[$i][0] + $this->locationTable[$i][1]) > $this->lastSector){ + $this->lastSector = $this->locationTable[$i][0] + $this->locationTable[$i][1]; + } + } + } + protected function writeLocationTable(){ + $write = []; + + for($i = 0; $i < 1024; ++$i){ + $write[] = (($this->locationTable[$i][0] << 8) | $this->locationTable[$i][1]); + } + for($i = 0; $i < 1024; ++$i){ + $write[] = $this->locationTable[$i][2]; + } + fseek($this->filePointer, 0); + fwrite($this->filePointer, pack("N*", ...$write), 4096 * 2); + } + + public function getX(){ + return $this->x; + } + + public function getZ(){ + return $this->z; + } + + protected function writeChunkData($oX,$oZ,$data) { + if ($this->readOnly) { + throw new ImporterException("Error trying to write chunk($oX,$oZ) to read-only Level"); + } + + $length = strlen($data) + 1; + if($length + 4 > self::MAX_SECTOR_LENGTH){ + throw new ImporterException(__CLASS__."::".__METHOD__.": Chunk is too big! ".($length + 4)." > ".self::MAX_SECTOR_LENGTH); + } + $sectors = (int) ceil(($length + 4) / 4096); + $index = self::getChunkOffset($oX, $oZ); + + // We always append data... + $this->locationTable[$index][0] = $this->lastSector; + $this->locationTable[$index][1] = $sectors; + $this->locationTable[$index][2] = time(); + $this->lastSector += $sectors; + + fseek($this->filePointer, $this->locationTable[$index][0] << 12); + fwrite($this->filePointer, str_pad(Binary::writeInt($length) . chr(self::COMPRESSION_ZLIB) . $data, $sectors << 12, "\x00", STR_PAD_RIGHT)); + // Don't update locationTable until the very end! + } +} diff --git a/classlib/pmimporter/mcpe020/Chunk.php b/classlib/pmimporter/mcpe020/Chunk.php new file mode 100644 index 0000000..2ebf8b3 --- /dev/null +++ b/classlib/pmimporter/mcpe020/Chunk.php @@ -0,0 +1,176 @@ +x << 4)+$x; + } + protected function getZPos($z) { + return ($this->z << 4)+$z; + } + + public function __construct($loader,$x,$z,$nbt) { + $this->chunks = $loader->getChunks(); + $this->x = $x; + $this->z = $z; + if (isset($nbt["TileEntities"])) { + $this->tiles = $nbt["TileEntities"]->getValue(); + } + if (isset($nbt["Entities"])) { + $this->entities = $nbt["Entities"]->getValue(); + } + $this->adjX = $loader->getProvider()->getSetting("Xoff") << 4; + $this->adjZ = $loader->getProvider()->getSetting("Zoff") << 4; + $r = $fmt->getSetting("regions"); + if ($r != null) { + if (preg_match('/^\s*(-?\d+)\s*,\s*(-?\d+)\s*$/',$r,$mv)) { + $this->adjX += $mv[1] * (16*32); + $this->adjZ += $mv[2] * (16*32); + } + } + } + + public function getBiomeId($x, $z) { return 1; } + public function setBiomeId($x, $z, $biomeId) { + throw new ImporterException("Unimplemented ".__CLASS__."::".__METHOD__); + } + public function getBiomeIdArray(){ return str_repeat("\x01", 256); } + public function getBiomeColorArray() { + return array_fill(0, 256, Binary::readInt("\x00\x85\xb2\x4a")); + } + public function getBiomeColor($x, $z) { + $color = Binary::readInt("\x00\x85\xb2\x4a") & 0xFFFFFF; + return [$color >> 16, ($color >> 8) & 0xFF, $color & 0xFF]; + } + public function setBiomeColor($x, $z, $R, $G, $B) { + throw new ImporterException("Unimplemented ".__CLASS__."::".__METHOD__); + } + public function getHeightMap($x, $z) { + return $this->chunks->getFloor($this->getXPos($x),$this->getZPos($z)); + } + public function setHeightMap($x, $z, $value) { + throw new ImporterException("Unimplemented ".__CLASS__."::".__METHOD__); + } + public function getHeightMapArray() { + $map = []; + for ($z=0;$z < 16; $z++) { + for ($x=0;$x<16;$x++) { + $map[($z << 4) + $x] = + $this->chunks->getFloor($this->getXPos($x),$this->getZPos($z)); + } + } + return $map; + } + public function getEntities() { + $entities = []; + + $min_x = $this->getXPos(0); $min_z = $this->getZPos(0); + $max_x = $this->getXPos(15); $max_z = $this->getZPos(15); + + foreach ($this->entities as $ent) { + if (!isset($ent->Pos) || !isset($ent->id)) continue; + $id = Entities::getEntityById($ent->id->getValue()); + if ($id == null) continue; + if (count($ent->Pos) != 3) continue; + $x = $ent->Pos[0]; + $y = $ent->Pos[1]; + $z = $ent->Pos[2]; + if ($x < $min_x || $x > $max_x || $z < $min_z || $z > $max_z) continue; + // Conversion + $cc = clone $ent; + $cc->id = new String("id",$id); + $cc->Pos = new Enum("Pos",[new Double(0,$x+$this->adjX), + new Double(0,$y), + new Double(0,$z+$this->adjZ)]); + $entities[] = $cc; + } + return $entities; + } + public function setEntities(array $entities = []) { + throw new ImporterException("Unimplemented ".__CLASS__."::".__METHOD__); + } + public function getTileEntities() { + $tiles = []; + + $min_x = $this->getXPos(0); $min_z = $this->getZPos(0); + $max_x = $this->getXPos(15); $max_z = $this->getZPos(15); + + foreach ($this->tiles as $tile) { + if (isset($tile->x) && isset($tile->y) && isset($tile->z)) { + if ($tile->x->getValue() < $min_x || $tile->x->getValue() > $max_x || + $tile->z->getValue() < $min_z || $tile->z->getValue() > $max_z) + continue; + // Straight copy. + $t = clone $tile; + $t->x = new Int("x",$tile->x->getValue()+$this->adjX); + $t->z = new Int("z",$tile->z->getValue()+$this->adjZ); + if (isset($tile->pairx)) + $t->pairx = new Int("pairx",$tile->pairx->getValue()+$this->adjX); + if (isset($tile->pairz)) + $t->pairz = new Int("pairz",$tile->pairz->getValue()+$this->adjZ); + + $tiles[] = $t; + } + } + return $tiles; + } + public function setTileEntities(array $tiles = []) { + throw new ImporterException("Unimplemented ".__CLASS__."::".__METHOD__); + } + public function isPopulated(){ return 1;} + public function setPopulated($value = 1){ + throw new ImporterException("Unimplemented ".__CLASS__."::".__METHOD__); + } + public function isGenerated(){ return 1; } + public function setGenerated($value = 1){ + throw new ImporterException("Unimplemented ".__CLASS__."::".__METHOD__); + } + public function getBlock($x, $y, $z) { + return $this->chunks->getBlock($this->getXPos($x),$y,$this->getZPos($z)); + } + public function setBlock($x, $y, $z, $blockId = null, $meta = null){ + throw new ImporterException("Unimplemented ".__CLASS__."::".__METHOD__); + } + public function getBlockSkyLight($x, $y, $z) { + return $this->chunks->getBlockSkyLight($this->getXPos($x),$y,$this->getZPos($z)); + } + public function setBlockSkyLight($x, $y, $z, $level) { + throw new ImporterException("Unimplemented ".__CLASS__."::".__METHOD__); + } + public function getBlockLight($x, $y, $z) { + return $this->chunks->getBlockLight($this->getXPos($x),$y,$this->getZPos($z)); + } + public function setBlockLight($x, $y, $z, $level) { + throw new ImporterException("Unimplemented ".__CLASS__."::".__METHOD__); + } + + /** + * @param string $data + * @param LevelProvider $provider + * + * @return Chunk + */ + public static function fromBinary($data) { + throw new ImporterException("Unimplemented ".__CLASS__."::".__METHOD__); + } + public function toBinary(){ + throw new ImporterException("Unimplemented ".__CLASS__."::".__METHOD__); + } +} diff --git a/classlib/pmimporter/mcpe020/McPe020.php b/classlib/pmimporter/mcpe020/McPe020.php new file mode 100644 index 0000000..c4d4a89 --- /dev/null +++ b/classlib/pmimporter/mcpe020/McPe020.php @@ -0,0 +1,105 @@ +path = $path; + + $nbt = new NBT(NBT::LITTLE_ENDIAN); + $nbt->read(substr(file_get_contents($this->getPath()."level.dat"),8)); + $this->levelData = $nbt->getData(); + + if ($settings) { + $this->settings = $settings; + } else { + $this->settings = []; + } + if (!isset($this->settings["Xoff"])) $this->settings["Xoff"] = 0; + if (!isset($this->settings["Zoff"])) $this->settings["Zoff"] = 0; + } + public function getPath() { + return $this->path; + } + + public static function generate($path, $name, Vector3 $spawn, $seed, $generator, array $options = []) { + throw new ImporterException("Unimplemented ".__CLASS__."::".__METHOD__); + } + public function getSetting($attr) { + if (!isset($this->settings[$attr])) return null; + return $this->settings[$attr]; + } + private function getAttr($attr,$nbtattr) { + if (isset($this->settings[$attr])) return $this->settings[$attr]; + return $this->levelData[$nbtattr]; + } + public function getName() { + return $this->getAttr("name","LevelName"); + } + public function getSeed() { + return $this->getAttr("seed","RandomSeed"); + } + private function adjSpawn($dir) { + if (isset($this->settings["spawn".$dir])) + return $this->settings["spawn".$dir]; + $l = $this->levelData["Spawn".$dir] + $this->settings["Xoff"] * 16; + if (isset($this->settings["regions"])) { + if (preg_match('/^\s*(-?\d+)\s*,\s*(-?\d+)\s*$/',$this->settings["regions"],$mv)) { + $l += ($dir == "X" ? $mv[1] : $mv[2])* (16 * 32); + } + } + return $l; + } + public function getSpawn() { + if (isset($this->settings["spawn"])) { + $spawn = explode(',',$this->settings["spawn"],3); + if (count($spawn) == 3) + return new Vector3((float)$spawn[0],(float)$spawn[1],(float)$spawn[2]); + } + return new Vector3((float)$this->adjSpawn("X"),(float)$this->getAttr("spawnY"),(float)$this->adjSpawn("Z")); + } + public function getGenerator() { + if (isset($this->settings["generator"])) + return $this->settings["generator"]; + return "flat"; + } + public function getGeneratorOptions() { + if (isset($this->settings["preset"])) + return ["preset"=>$this->settings["preset"]]; + return ["preset"=>"2;7,55x1,9x3,2;1;"]; + } + + public static function getFormatName() { return "mcpe0.2.0"; } + public static function isValid($path) { + if (file_exists($path."/level.dat") && file_exists($path."/chunks.dat")) { + $dat = file_get_contents($path.'/level.dat'); + if ((Binary::readLInt(substr($dat,0,4)) == 2 + || Binary::readLInt(substr($dat,0,4)) == 3) + && Binary::readLInt(substr($dat,4,4)) == (strlen($dat) - 8)) + return true; + } + return false; + } + public function getRegions() { + if (isset($this->settings["regions"])) { + if (preg_match('/^\s*(\d+)\s*,\s*(\d+)\s*$/',$this->settings["regions"],$mv)) { + return [$mv[1].",".$mv[2] => [$mv[1],$mv[2]]]; + } + } + return ["0,0" => [0,0]]; + } + public function getRegion($x, $z) { + return new RegionLoader($this); + } +} diff --git a/classlib/pmimporter/mcpe020/RegionLoader.php b/classlib/pmimporter/mcpe020/RegionLoader.php new file mode 100644 index 0000000..e925dfb --- /dev/null +++ b/classlib/pmimporter/mcpe020/RegionLoader.php @@ -0,0 +1,68 @@ +formatProvider; } + public function getChunks() { return $this->chunks; } + + public function __construct(LevelFormat $fmt) { + $this->formatProvider = $fmt; + $this->chunks = new PocketChunkParser(); + $this->chunks->loadFile($fmt->getPath().'chunks.dat'); + $this->chunks->loadMap(); + $this->nbt = []; + if (file_exists($fmt->getPath().'entities.dat')) { + $dat = file_get_contents($fmt->getPath().'entities.dat'); + if (substr($dat,0,3) == "ENT" && ord(substr($dat,3,1)) == 0 && + Binary::readLInt(substr($dat,4,4)) == 1 && + Binary::readLInt(substr($dat,8,4)) == (strlen($dat) - 12)) { + $nbt = new NBT(NBT::LITTLE_ENDIAN); + $nbt->read(substr($dat,12)); + $this->nbt = $nbt->getData(); + } + } + } + + public function chunkExists($x,$z) { + $x -= $this->formatProvider->getSetting("Xoff"); + $z -= $this->formatProvider->getSetting("Zoff"); + return ($x < 16 && $z < 16 && $x >= 0 && $z >= 0); + } + public function close(){ + $this->levelProvider = null; + } + public function getX(){ + return 0; + } + public function getZ(){ + return 0; + } + + protected function writeChunkData($oX,$oZ,$data) { + throw new ImporterException("Unimplemented ".__CLASS__."::".__METHOD__); + } + public function readChunkData($x,$z) { + return "$x,$y"; + } + public function newChunk($cX,$cZ) { + throw new ImporterException("Unimplemented ".__CLASS__."::".__METHOD__); + } + public function writeChunk($oX,$oZ,$chunk) { + throw new ImporterException("Unimplemented ".__CLASS__."::".__METHOD__); + } + public function readChunk($x,$z) { + $x -= $this->formatProvider->getSetting("Xoff"); + $z -= $this->formatProvider->getSetting("Zoff"); + return new Chunk($this,$x,$z,$this->nbt); + } +} diff --git a/classlib/pmimporter/mcregion/Chunk.php b/classlib/pmimporter/mcregion/Chunk.php new file mode 100644 index 0000000..75e3cfc --- /dev/null +++ b/classlib/pmimporter/mcregion/Chunk.php @@ -0,0 +1,178 @@ +nbt->Blocks)){ + $this->nbt->Blocks = new ByteArray("Blocks", str_repeat("\x00", 32768)); + } + if(!isset($this->nbt->Data)){ + $this->nbt->Data = new ByteArray("Data", $half = str_repeat("\x00", 16384)); + $this->nbt->SkyLight = new ByteArray("SkyLight", $half); + $this->nbt->BlockLight = new ByteArray("BlockLight", $half); + } + $this->blocks = $this->nbt->Blocks->getValue(); + $this->data = $this->nbt->Data->getValue(); + $this->skyLight = $this->nbt->SkyLight->getValue(); + $this->blockLight = $this->nbt->BlockLight->getValue(); + + unset($this->nbt->Blocks); + unset($this->nbt->Data); + unset($this->nbt->SkyLight); + unset($this->nbt->BlockLight); + } + + public function getBlock($x, $y, $z) { + $i = ($x << 11) | ($z << 7) | $y; + $blockId = ord($this->blocks{$i}); + if (($y & 1) == 0) { + $meta = ord($this->data{$i >> 1}) & 0x0F; + } else { + $meta = ord($this->data{$i >> 1}) >> 4; + } + return [ $blockId,$meta ]; + } + + public function setBlock($x, $y, $z, $blockId = null, $meta = null){ + $i = ($x << 11) | ($z << 7) | $y; + + if($blockId !== null){ + $blockId = chr($blockId); + if($this->blocks{$i} !== $blockId){ + $this->blocks{$i} = $blockId; + } + } + + if($meta !== null){ + $i >>= 1; + $old_m = ord($this->data{$i}); + if(($y & 1) === 0){ + $this->data{$i} = chr(($old_m & 0xf0) | ($meta & 0x0f)); + }else{ + $this->data{$i} = chr((($meta & 0x0f) << 4) | ($old_m & 0x0f)); + } + } + } + public function getBlockSkyLight($x, $y, $z) { + $sl = ord($this->skyLight{($x << 10) | ($z << 6) | ($y >> 1)}); + if(($y & 1) === 0){ + return $sl & 0x0F; + }else{ + return $sl >> 4; + } + } + public function setBlockSkyLight($x, $y, $z, $level) { + $i = ($x << 10) | ($z << 6) | ($y >> 1); + $old_sl = ord($this->skyLight{$i}); + if(($y & 1) === 0){ + $this->skyLight{$i} = chr(($old_sl & 0xf0) | ($level & 0x0f)); + }else{ + $this->skyLight{$i} = chr((($level & 0x0f) << 4) | ($old_sl & 0x0f)); + } + } + public function getBlockLight($x, $y, $z) { + $l = ord($this->blockLight{($x << 10) | ($z << 6) | ($y >> 1)}); + if(($y & 1) === 0){ + return $l & 0x0F; + }else{ + return $l >> 4; + } + } + public function setBlockLight($x, $y, $z, $level) { + $i = ($x << 10) | ($z << 6) | ($y >> 1); + $old_l = ord($this->blockLight{$i}); + if(($y & 1) === 0){ + $this->blockLight{$i} = chr(($old_l & 0xf0) | ($level & 0x0f)); + }else{ + $this->blockLight{$i} = chr((($level & 0x0f) << 4) | ($old_l & 0x0f)); + } + $this->hasChanged = true; + } + + /** + * @param string $data + * @param LevelProvider $provider + * + * @return Chunk + */ + public static function fromBinary($data) { + $nbt = new NBT(NBT::BIG_ENDIAN); + $nbt->readCompressed($data, ZLIB_ENCODING_DEFLATE); + $chunk = $nbt->getData(); + + if(!isset($chunk->Level) or !($chunk->Level instanceof Compound)){ + return null; + } + //$x = $chunk->Level->Blocks->getValue(); + return new Chunk($chunk->Level); + } + + public function toBinary(){ + $nbt = clone $this->getNBT(); + + $nbt->xPos = new Int("xPos", $this->x); + $nbt->zPos = new Int("zPos", $this->z); + + if($this->isGenerated()){ + $nbt->Blocks = new ByteArray("Blocks", $this->blocks); + $nbt->Data = new ByteArray("Data", $this->data); + $nbt->SkyLight = new ByteArray("SkyLight", $this->skyLight); + $nbt->BlockLight = new ByteArray("BlockLight", $this->blockLight); + + $nbt->Biomes = new ByteArray("Biomes", $this->getBiomeIdArray()); + $nbt->BiomeColors = new IntArray("BiomeColors", $this->getBiomeColorArray()); + + $nbt->HeightMap = new IntArray("HeightMap", $this->getHeightMapArray()); + } + + /* + $entities = []; + $nbt->Entities = new Enum("Entitie + + + foreach($this->getEntities() as $entity){ + if(!($entity instanceof Player) and !$entity->closed){ + $entity->saveNBT(); + $entities[] = $entity->namedtag; + } + } + + $nbt->Entities = new Enum("Entities", $entities); + $nbt->Entities->setTagType(NBT::TAG_Compound); + */ + $entities = []; + foreach($this->getEntities() as $entity) { + $entities[] = clone $entity; + } + $nbt->Entities = new Enum("Entities",$entities); + $nbt->Entities->setTagType(NBT::TAG_Compound); + + $tiles = []; + foreach($this->getTileEntities() as $tile) { + $tiles[] = clone $tile; + } + $nbt->TileEntities = new Enum("TileEntities",$tiles); + $nbt->TileEntities->setTagType(NBT::TAG_Compound); + + $writer = new NBT(NBT::BIG_ENDIAN); + $nbt->setName("Level"); + $writer->setData(new Compound("", ["Level" => $nbt])); + return $writer->writeCompressed(ZLIB_ENCODING_DEFLATE, RegionLoader::$COMPRESSION_LEVEL); + } + +} \ No newline at end of file diff --git a/classlib/pmimporter/mcregion/McRegion.php b/classlib/pmimporter/mcregion/McRegion.php new file mode 100644 index 0000000..d0c1b85 --- /dev/null +++ b/classlib/pmimporter/mcregion/McRegion.php @@ -0,0 +1,79 @@ +new Byte("hardcore",0), + "initialized"=>new Byte("initialized",1), + "GameType"=>new Int("GameType",0), + "generatorVersion"=>new Int("generatorVersion",1), // 2 in MCPE + "SpawnX" => new Int("SpawnX",$spawn->x), + "SpawnY" => new Int("SpawnY",$spawn->y), + "SpawnZ" => new Int("SpawnZ",$spawn->z), + "version" => new Int("version",19133), + "DayTime" => new Int("DayTime",0), + "LastPlayed" => new Long("LastPlayed",microtime(true)*1000), + "RandomSeed" => new Long("RandomSeed",$seed), + "SizeOnDisk" => new Long("SizeOnDisk",0), + "Time" => new Long("Time",0), + "generatorName" => new String("generatorName",$generator), + "generatorOptions" => new String("generatorOptions",isset($options["preset"]) ? $options["preset"] : ""), + "LevelName" => new String("LevelName",$name), + "GameRules" => new Compound("GameRules",[]) + ]); + $nbt = new NBT(NBT::BIG_ENDIAN); + $nbt->setData(new Compound(null,["Data"=>$levelData])); + $buffer = $nbt->writeCompressed(); + file_put_contents($path."/level.dat",$buffer); + } + public function getRegions() { + if ($this->regions === null) { + $this->regions = []; + $files = glob($this->getPath()."region/r.*.mcr"); + foreach ($files as $f) { + $pp = []; + if (preg_match('/r\.(-?\d+)\.(-?\d+)\.mcr$/',$f,$pp)) { + array_shift($pp); + $this->regions[$pp[0].",".$pp[1]] = $pp; + } + } + } + return $this->regions; + } + + public function getRegion($x,$z) { + return new RegionLoader($this,$x,$z,"mcr",$this->readOnly); + } +} diff --git a/classlib/pmimporter/mcregion/RegionLoader.php b/classlib/pmimporter/mcregion/RegionLoader.php new file mode 100644 index 0000000..8a4d00a --- /dev/null +++ b/classlib/pmimporter/mcregion/RegionLoader.php @@ -0,0 +1,63 @@ +readChunkData($x,$z); + if ($data === null) return null; + return Chunk::fromBinary($data); + /* + $w = Chunk::fromBinary($data); + if ($w === null) { + $index = self::getChunkOffset($x, $z); + echo("ERROR READING CHUNK $x,$z $index - "); + echo(implode(':',[$this->locationTable[$index][0],$this->locationTable[$index][1]])."\n"); + } + return($w); + */ + } + public function newChunk($cX,$cZ) { + //Allocate space + $nbt = new Compound("Level", []); + $nbt->xPos = new Int("xPos", $cX); + $nbt->zPos = new Int("zPos", $cZ); + $nbt->LastUpdate = new Long("LastUpdate", 0); + $nbt->LightPopulated = new Byte("LightPopulated", 0); + $nbt->TerrainPopulated = new Byte("TerrainPopulated", 0); + $nbt->V = new Byte("V", self::VERSION); + $nbt->InhabitedTime = new Long("InhabitedTime", 0); + $nbt->Biomes = new ByteArray("Biomes", str_repeat(Binary::writeByte(-1), 256)); + $nbt->HeightMap = new IntArray("HeightMap", array_fill(0, 256, 127)); + $nbt->BiomeColors = new IntArray("BiomeColors", array_fill(0, 256, Binary::readInt("\x00\x85\xb2\x4a"))); + + $nbt->Blocks = new ByteArray("Blocks", str_repeat("\x00", 32768)); + $nbt->Data = new ByteArray("Data", $half = str_repeat("\x00", 16384)); + $nbt->SkyLight = new ByteArray("SkyLight", $half); + $nbt->BlockLight = new ByteArray("BlockLight", $half); + + $nbt->Entities = new Enum("Entities", []); + $nbt->Entities->setTagType(NBT::TAG_Compound); + $nbt->TileEntities = new Enum("TileEntities", []); + $nbt->TileEntities->setTagType(NBT::TAG_Compound); + $nbt->TileTicks = new Enum("TileTicks", []); + $nbt->TileTicks->setTagType(NBT::TAG_Compound); + $nbt->setName("Level"); + + return new Chunk($nbt); + } + public function writeChunk($oX,$oZ,$chunk) { + $chunkData = $chunk->toBinary(); + $this->writeChunkData($oX,$oZ,$chunkData); + } +} diff --git a/classlib/pmimporter/pm13/Chunk.php b/classlib/pmimporter/pm13/Chunk.php new file mode 100644 index 0000000..71ac627 --- /dev/null +++ b/classlib/pmimporter/pm13/Chunk.php @@ -0,0 +1,194 @@ +x << 4)+$x; + } + protected function getZPos($z) { + return ($this->z << 4)+$z; + } + + public function __construct(Pm13 $fmt,$x,$z) { + $this->chunks = $fmt->getPMFLevel(); + $this->x = $x; + $this->z = $z; + $this->adjX = $fmt->getSetting("Xoff") << 4; + $this->adjZ = $fmt->getSetting("Zoff") << 4; + $r = $fmt->getSetting("regions"); + if ($r != null) { + if (preg_match('/^\s*(-?\d+)\s*,\s*(-?\d+)\s*$/',$r,$mv)) { + $this->adjX += $mv[1] * (16*32); + $this->adjZ += $mv[2] * (16*32); + } + } + } + + public function getBiomeId($x, $z) { return 1; } + public function setBiomeId($x, $z, $biomeId) { + throw new ImporterException("Unimplemented ".__CLASS__."::".__METHOD__); + } + public function getBiomeIdArray(){ return str_repeat("\x01", 256); } + public function getBiomeColorArray() { + return array_fill(0, 256, Binary::readInt("\x00\x85\xb2\x4a")); + } + public function getBiomeColor($x, $z) { + $color = Binary::readInt("\x00\x85\xb2\x4a") & 0xFFFFFF; + return [$color >> 16, ($color >> 8) & 0xFF, $color & 0xFF]; + } + public function setBiomeColor($x, $z, $R, $G, $B) { + throw new ImporterException("Unimplemented ".__CLASS__."::".__METHOD__); + } + protected function getFloor($x,$z) { + for ($y=127;$y>0;--$y) { + if ($this->chunks->getBlockID($x,$y,$z)) break; + } + return $y; + } + public function getHeightMap($x, $z) { + return $this->getFloor($this->getXPos($x),$this->getZPos($z)); + } + public function setHeightMap($x, $z, $value) { + throw new ImporterException("Unimplemented ".__CLASS__."::".__METHOD__); + } + public function getHeightMapArray() { + $map = []; + for ($z=0;$z < 16; $z++) { + for ($x=0;$x<16;$x++) { + $map[($z << 4) + $x] = + $this->getFloor($this->getXPos($x),$this->getZPos($z)); + } + } + return $map; + } + public function getEntities() { return []; } + public function setEntities(array $entities = []) { + throw new ImporterException("Unimplemented ".__CLASS__."::".__METHOD__); + } + protected static function convertInventory($name,$src) { + $items = []; + if (isset($src)) { + foreach ($src as $sl) { + $items[] = new Compound(false,[new Byte("Count",$sl["Count"]), + new Byte("Slot",$sl["Slot"]), + new Short("id",$sl["id"]), + new Short("Damage",$sl["Damage"])]); + } + } + $nbt = new Enum($name,$items); + $nbt->setTagType(NBT::TAG_Compound); + return $nbt; + } + public function getTileEntities() { + $tiles = []; + $yml = dirname($this->chunks->getFile())."/tiles.yml"; + if (!file_exists($yml)) return $tiles; + $yml = yaml_parse_file($yml); + $min_x = $this->getXPos(0); $min_z = $this->getZPos(0); + $max_x = $this->getXPos(15); $max_z = $this->getZPos(15); + foreach ($yml as $tile) { + if (!isset($tile["id"])) continue; + if (isset($tile["x"]) && isset($tile["y"]) && isset($tile["z"])) { + if ($tile["x"] < $min_x || $tile["x"] > $max_x || + $tile["z"] < $min_z || $tile["z"] > $max_z) continue; + } else { + continue; + } + switch ($tile["id"]) { + case TID_SIGN: + $tiles[] = new Compound("",[new String("id",TID_SIGN), + new String("Text1",$tile["Text1"]), + new String("Text2",$tile["Text2"]), + new String("Text3",$tile["Text3"]), + new String("Text4",$tile["Text4"]), + new Int("x",$tile["x"]+$this->adjX), + new Int("y",$tile["y"]), + new Int("z",$tile["z"]+$this->adjZ)]); + break; + case TID_FURNACE: + $tiles[] = new Compound("",[new String("id",TID_FURNACE), + new Short("BurnTime",$tile["BurnTime"]), + new Short("BurnTicks",$tile["BurnTicks"]), + new Short("CookTime",$tile["CookTime"]), + new Short("CookTimeTotal",$tile["MaxTime"]), + self::convertInventory("Items",$tile["Items"]), + new Int("x",$tile["x"]+$this->adjX), + new Int("y",$tile["y"]), + new Int("z",$tile["z"]+$this->adjZ)]); + break; + case TID_CHEST: + $chest = [new String("id",TID_CHEST), + self::convertInventory("Items",$tile["Items"]), + new Int("x",$tile["x"]+$this->adjX), + new Int("y",$tile["y"]), + new Int("z",$tile["z"]+$this->adjZ)]; + if (isset($tile["pairx"])) + $chest[] = new Int("pairx",$tile["pairx"]+$this->adjX); + if (isset($tile["pairz"])) + $chest[] = new Int("pairz",$tile["pairz"]+$this->adjZ); + $tiles[] = new Compound("",$chest); + break; + default: + // Not supported tile Id + continue; + } + } + return $tiles; + } + public function setTileEntities(array $tiles = []) { + throw new ImporterException("Unimplemented ".__CLASS__."::".__METHOD__); + } + public function isPopulated(){ return 1;} + public function setPopulated($value = 1){ + throw new ImporterException("Unimplemented ".__CLASS__."::".__METHOD__); + } + public function isGenerated(){ return 1; } + public function setGenerated($value = 1){ + throw new ImporterException("Unimplemented ".__CLASS__."::".__METHOD__); + } + public function getBlock($x, $y, $z) { + return $this->chunks->getBlock($this->getXPos($x),$y,$this->getZPos($z)); + } + public function setBlock($x, $y, $z, $blockId = null, $meta = null){ + throw new ImporterException("Unimplemented ".__CLASS__."::".__METHOD__); + } + public function getBlockSkyLight($x, $y, $z) { return 0x0e; } + public function setBlockSkyLight($x, $y, $z, $level) { + throw new ImporterException("Unimplemented ".__CLASS__."::".__METHOD__); + } + public function getBlockLight($x, $y, $z) { return 0x0e; } + public function setBlockLight($x, $y, $z, $level) { + throw new ImporterException("Unimplemented ".__CLASS__."::".__METHOD__); + } + + /** + * @param string $data + * @param LevelProvider $provider + * + * @return Chunk + */ + public static function fromBinary($data) { + throw new ImporterException("Unimplemented ".__CLASS__."::".__METHOD__); + } + public function toBinary(){ + throw new ImporterException("Unimplemented ".__CLASS__."::".__METHOD__); + } +} diff --git a/classlib/pmimporter/pm13/Pm13.php b/classlib/pmimporter/pm13/Pm13.php new file mode 100644 index 0000000..3519ce0 --- /dev/null +++ b/classlib/pmimporter/pm13/Pm13.php @@ -0,0 +1,104 @@ +path = $path; + + $pmfLevel = new PMFLevel($this->getPath()."level.pmf"); + $this->pmfLevel = $pmfLevel; + if ($settings) { + $this->settings = $settings; + } else { + $this->settings = []; + } + if (!isset($this->settings["Xoff"])) $this->settings["Xoff"] = 0; + if (!isset($this->settings["Zoff"])) $this->settings["Zoff"] = 0; + } + public function getPath() { + return $this->path; + } + + public static function generate($path, $name, Vector3 $spawn, $seed, $generator, array $options = []) { + throw new ImporterException("Unimplemented ".__CLASS__."::".__METHOD__); + } + public function getSetting($attr) { + if (!isset($this->settings[$attr])) return null; + return $this->settings[$attr]; + } + private function getAttr($attr) { + if (isset($this->settings[$attr])) return $this->settings[$attr]; + return $this->pmfLevel->getAttr($attr); + } + + public function getName() { + return $this->getAttr("name"); + } + public function getSeed() { + return $this->getAttr("seed"); + } + private function adjSpawn($dir) { + if (isset($this->settings["spawn".$dir])) + return $this->settings["spawn".$dir]; + $l = $this->pmfLevel->getAttr("spawn".$dir)+$this->settings[$dir."off"]*16; + if (isset($this->settings["regions"])) { + if (preg_match('/^\s*(-?\d+)\s*,\s*(-?\d+)\s*$/',$this->settings["regions"],$mv)) { + $l += ($dir == "X" ? $mv[1] : $mv[2])* (16 * 32); + } + } + return $l; + } + + public function getSpawn() { + if (isset($this->settings["spawn"])) { + $spawn = explode(',',$this->settings["spawn"],3); + if (count($spawn) == 3) + return new Vector3((float)$spawn[0],(float)$spawn[1],(float)$spawn[2]); + } + return new Vector3((float)$this->adjSpawn("X"),(float)$this->getAttr("spawnY"),(float)$this->adjSpawn("Z")); + } + public function getGenerator() { + if (isset($this->settings["generator"])) + return $this->settings["generator"]; + return "flat"; + } + public function getGeneratorOptions() { + if (isset($this->settings["preset"])) + return ["preset"=>$this->settings["preset"]]; + return ["preset"=>"2;7,55x1,9x3,2;1;"]; + } + + public static function getFormatName() { return "PMF_1.3"; } + public static function isValid($path) { + return (file_exists($path."/level.pmf") + && file_exists($path."/entities.yml") + && file_exists($path."/tiles.yml") + && is_dir($path."/chunks")); + } + public function getRegions() { + if (isset($this->settings["regions"])) { + if (preg_match('/^\s*(\d+)\s*,\s*(\d+)\s*$/',$this->settings["regions"],$mv)) { + return [$mv[1].",".$mv[2] => [$mv[1],$mv[2]]]; + } + } + return ["0,0" => [0,0]]; + } + public function getRegion($x, $z) { + return new RegionLoader($this); + } + public function getPMFLevel() { return $this->pmfLevel; } +} diff --git a/classlib/pmimporter/pm13/RegionLoader.php b/classlib/pmimporter/pm13/RegionLoader.php new file mode 100644 index 0000000..d2b9838 --- /dev/null +++ b/classlib/pmimporter/pm13/RegionLoader.php @@ -0,0 +1,47 @@ +formatProvider = $fmt; + } + + public function chunkExists($x,$z) { + $x -= $this->formatProvider->getSetting("Xoff"); + $z -= $this->formatProvider->getSetting("Zoff"); + return ($x < 16 && $z < 16 && $x >= 0 && $z >= 0); + } + public function close(){ + $this->levelProvider = null; + } + public function getX(){ + return 0; + } + public function getZ(){ + return 0; + } + + protected function writeChunkData($oX,$oZ,$data) { + throw new ImporterException("Unimplemented ".__CLASS__."::".__METHOD__); + } + public function readChunkData($x,$z) { + return "$x,$y"; + } + public function newChunk($cX,$cZ) { + throw new ImporterException("Unimplemented ".__CLASS__."::".__METHOD__); + } + public function writeChunk($oX,$oZ,$chunk) { + throw new ImporterException("Unimplemented ".__CLASS__."::".__METHOD__); + } + public function readChunk($x,$z) { + $x -= $this->formatProvider->getSetting("Xoff"); + $z -= $this->formatProvider->getSetting("Zoff"); + return new Chunk($this->formatProvider,$x,$z); + } +} diff --git a/classlib/pocketmine/math/Vector3.php b/classlib/pocketmine/math/Vector3.php new file mode 120000 index 0000000..1a53f31 --- /dev/null +++ b/classlib/pocketmine/math/Vector3.php @@ -0,0 +1 @@ +../../../pocketmine-src/math/Vector3.php \ No newline at end of file diff --git a/classlib/pocketmine/nbt/NBT.php b/classlib/pocketmine/nbt/NBT.php new file mode 120000 index 0000000..6c40651 --- /dev/null +++ b/classlib/pocketmine/nbt/NBT.php @@ -0,0 +1 @@ +../../../pocketmine-src/nbt/NBT.php \ No newline at end of file diff --git a/classlib/pocketmine/nbt/tag/Byte.php b/classlib/pocketmine/nbt/tag/Byte.php new file mode 120000 index 0000000..1b76f23 --- /dev/null +++ b/classlib/pocketmine/nbt/tag/Byte.php @@ -0,0 +1 @@ +../../../../pocketmine-src/nbt/tag/Byte.php \ No newline at end of file diff --git a/classlib/pocketmine/nbt/tag/ByteArray.php b/classlib/pocketmine/nbt/tag/ByteArray.php new file mode 120000 index 0000000..7c805be --- /dev/null +++ b/classlib/pocketmine/nbt/tag/ByteArray.php @@ -0,0 +1 @@ +../../../../pocketmine-src/nbt/tag/ByteArray.php \ No newline at end of file diff --git a/classlib/pocketmine/nbt/tag/Compound.php b/classlib/pocketmine/nbt/tag/Compound.php new file mode 120000 index 0000000..5b8a7dd --- /dev/null +++ b/classlib/pocketmine/nbt/tag/Compound.php @@ -0,0 +1 @@ +../../../../pocketmine-src/nbt/tag/Compound.php \ No newline at end of file diff --git a/classlib/pocketmine/nbt/tag/Double.php b/classlib/pocketmine/nbt/tag/Double.php new file mode 120000 index 0000000..33abc98 --- /dev/null +++ b/classlib/pocketmine/nbt/tag/Double.php @@ -0,0 +1 @@ +../../../../pocketmine-src/nbt/tag/Double.php \ No newline at end of file diff --git a/classlib/pocketmine/nbt/tag/End.php b/classlib/pocketmine/nbt/tag/End.php new file mode 120000 index 0000000..f977c83 --- /dev/null +++ b/classlib/pocketmine/nbt/tag/End.php @@ -0,0 +1 @@ +../../../../pocketmine-src/nbt/tag/End.php \ No newline at end of file diff --git a/classlib/pocketmine/nbt/tag/Enum.php b/classlib/pocketmine/nbt/tag/Enum.php new file mode 120000 index 0000000..e2e7486 --- /dev/null +++ b/classlib/pocketmine/nbt/tag/Enum.php @@ -0,0 +1 @@ +../../../../pocketmine-src/nbt/tag/Enum.php \ No newline at end of file diff --git a/classlib/pocketmine/nbt/tag/Float.php b/classlib/pocketmine/nbt/tag/Float.php new file mode 120000 index 0000000..29bf69e --- /dev/null +++ b/classlib/pocketmine/nbt/tag/Float.php @@ -0,0 +1 @@ +../../../../pocketmine-src/nbt/tag/Float.php \ No newline at end of file diff --git a/classlib/pocketmine/nbt/tag/Int.php b/classlib/pocketmine/nbt/tag/Int.php new file mode 120000 index 0000000..c36c4d2 --- /dev/null +++ b/classlib/pocketmine/nbt/tag/Int.php @@ -0,0 +1 @@ +../../../../pocketmine-src/nbt/tag/Int.php \ No newline at end of file diff --git a/classlib/pocketmine/nbt/tag/IntArray.php b/classlib/pocketmine/nbt/tag/IntArray.php new file mode 120000 index 0000000..0aa473a --- /dev/null +++ b/classlib/pocketmine/nbt/tag/IntArray.php @@ -0,0 +1 @@ +../../../../pocketmine-src/nbt/tag/IntArray.php \ No newline at end of file diff --git a/classlib/pocketmine/nbt/tag/Long.php b/classlib/pocketmine/nbt/tag/Long.php new file mode 120000 index 0000000..a43b823 --- /dev/null +++ b/classlib/pocketmine/nbt/tag/Long.php @@ -0,0 +1 @@ +../../../../pocketmine-src/nbt/tag/Long.php \ No newline at end of file diff --git a/classlib/pocketmine/nbt/tag/NamedTag.php b/classlib/pocketmine/nbt/tag/NamedTag.php new file mode 120000 index 0000000..71ddb7a --- /dev/null +++ b/classlib/pocketmine/nbt/tag/NamedTag.php @@ -0,0 +1 @@ +../../../../pocketmine-src/nbt/tag/NamedTag.php \ No newline at end of file diff --git a/classlib/pocketmine/nbt/tag/Short.php b/classlib/pocketmine/nbt/tag/Short.php new file mode 120000 index 0000000..582183d --- /dev/null +++ b/classlib/pocketmine/nbt/tag/Short.php @@ -0,0 +1 @@ +../../../../pocketmine-src/nbt/tag/Short.php \ No newline at end of file diff --git a/classlib/pocketmine/nbt/tag/String.php b/classlib/pocketmine/nbt/tag/String.php new file mode 120000 index 0000000..b26aee2 --- /dev/null +++ b/classlib/pocketmine/nbt/tag/String.php @@ -0,0 +1 @@ +../../../../pocketmine-src/nbt/tag/String.php \ No newline at end of file diff --git a/classlib/pocketmine/nbt/tag/Tag.php b/classlib/pocketmine/nbt/tag/Tag.php new file mode 120000 index 0000000..5041163 --- /dev/null +++ b/classlib/pocketmine/nbt/tag/Tag.php @@ -0,0 +1 @@ +../../../../pocketmine-src/nbt/tag/Tag.php \ No newline at end of file diff --git a/classlib/pocketmine/utils/Binary.php b/classlib/pocketmine/utils/Binary.php new file mode 120000 index 0000000..3d506e5 --- /dev/null +++ b/classlib/pocketmine/utils/Binary.php @@ -0,0 +1 @@ +../../../pocketmine-src/utils/Binary.php \ No newline at end of file diff --git a/classlib/pocketmine_1_3/PocketChunkParser.php b/classlib/pocketmine_1_3/PocketChunkParser.php new file mode 100644 index 0000000..a511953 --- /dev/null +++ b/classlib/pocketmine_1_3/PocketChunkParser.php @@ -0,0 +1,252 @@ +location = array(); + echo(" Loading Chunk Location table..."); + for($offset = 0; $offset < 0x1000; $offset += 4){ + $data = Utils::readLInt(substr($this->raw, $offset, 4)); + $sectors = $data & 0xff; + if($sectors === 0){ + continue; + } + $sectorLocation = $data >> 8; + $this->location[$offset >> 2] = $sectorLocation * $this->sectorLength;//$this->getOffset($X, $Z, $sectors); + } + } + + public function loadFile($file){ + if(file_exists($file.".gz")){ + $this->raw = gzinflate(file_get_contents($file.".gz")); + $r = @gzinflate($this->raw); + if($r !== false and $r != ""){ + $this->raw = $r; + } + @unlink($file.".gz"); + file_put_contents($file, $this->raw); + }elseif(!file_exists($file)){ + return false; + }else{ + $this->raw = file_get_contents($file); + } + $this->file = $file; + $this->chunkLength = $this->sectorLength * ord($this->raw{0}); + return true; + } + + public function loadRaw($raw, $file){ + $this->file = $file; + $this->raw = $raw; + $this->chunkLength = $this->sectorLength * ord($this->raw{0}); + return true; + } + + private function getOffset($X, $Z){ + return $this->location[$X + ($Z << 5)]; + } + + public function getChunk($X, $Z){ + $X = (int) $X; + $Z = (int) $Z; + return substr($this->raw, $this->getOffset($X, $Z), $this->chunkLength); + } + + public function writeChunk($X, $Z){ + $X = (int) $X; + $Z = (int) $Z; + if(!isset($this->map[$X][$Z])){ + return false; + } + $chunk = ""; + foreach($this->map[$X][$Z] as $section => $data){ + for($i = 0; $i < 256; ++$i){ + $chunk .= $data[$i]; + } + } + return Utils::writeLInt(strlen($chunk)).$chunk; + } + + public function parseChunk($X, $Z){ + $X = (int) $X; + $Z = (int) $Z; + $offset = $this->getOffset($X, $Z); + $len = Utils::readLInt(substr($this->raw, $offset, 4)); + $offset += 4; + $chunk = array( + 0 => array(), //Block + 1 => array(), //Data + 2 => array(), //SkyLight + 3 => array(), //BlockLight + ); + foreach($chunk as $section => &$data){ + $l = $section === 0 ? 128:64; + for($i = 0; $i < 256; ++$i){ + $data[$i] = substr($this->raw, $offset, $l); + $offset += $l; + } + } + return $chunk; + } + + public function loadMap(){ + if($this->raw == ""){ + return false; + } + $this->loadLocationTable(); + echo(" Loading chunks...\n"); + for($x = 0; $x < 16; ++$x){ + $this->map[$x] = array(); + for($z = 0; $z < 16; ++$z){ + $this->map[$x][$z] = $this->parseChunk($x, $z); + } + } + $this->raw = b""; + echo("Chunks loaded!\n"); + } + + public function saveMap($final = false){ + echo("Saving chunks...\n"); + + $fp = fopen($this->file, "r+b"); + flock($fp, LOCK_EX); + foreach($this->map as $x => $d){ + foreach($d as $z => $chunk){ + fseek($fp, $this->getOffset($x, $z)); + fwrite($fp, $this->writeChunk($x, $z), $this->chunkLength); + } + } + flock($fp, LOCK_UN); + fclose($fp); + $original = filesize($this->file); + file_put_contents($this->file .".gz", gzdeflate(gzdeflate(file_get_contents($this->file),9),9)); //Double compression for flat maps + $compressed = filesize($this->file .".gz"); + echo("Saved chunks.dat.gz with ".round(($compressed/$original)*100, 2)."% (".round($compressed/1024, 2)."KB) of the original size\n"); + if($final === true){ + @unlink($this->file); + } + } + + public function getFloor($x, $z){ + $X = $x >> 4; + $Z = $z >> 4; + $aX = $x - ($X << 4); + $aZ = $z - ($Z << 4); + $index = $aZ + ($aX << 4); + for($y = 127; $y <= 0; --$y){ + if($this->map[$X][$Z][0][$index]{$y} !== "\x00"){ + break; + } + } + return $y; + } + + public function getBlock($x, $y, $z){ + $x = (int) $x; + $y = (int) $y; + $z = (int) $z; + $X = $x >> 4; + $Z = $z >> 4; + $aX = $x - ($X << 4); + $aZ = $z - ($Z << 4); + $index = $aZ + ($aX << 4); + $block = ord($this->map[$X][$Z][0][$index]{$y}); + $meta = ord($this->map[$X][$Z][1][$index]{$y >> 1}); + if(($y & 1) === 0){ + $meta = $meta & 0x0F; + }else{ + $meta = $meta >> 4; + } + return array($block, $meta); + } + + public function getChunkColumn($X, $Z, $x, $z, $type = 0){ + $index = $z + ($x << 4); + return $this->map[$X][$Z][$type][$index]; + } + + public function setBlock($x, $y, $z, $block, $meta = 0){ + $x = (int) $x; + $y = (int) $y; + $z = (int) $z; + $X = $x >> 4; + $Z = $z >> 4; + $aX = $x - ($X << 4); + $aZ = $z - ($Z << 4); + $index = $aZ + ($aX << 4); + $this->map[$X][$Z][0][$index]{$y} = chr($block); + $old_meta = ord($this->map[$X][$Z][1][$index]{$y >> 1}); + if(($y & 1) === 0){ + $meta = ($old_meta & 0xF0) | ($meta & 0x0F); + }else{ + $meta = (($meta << 4) & 0xF0) | ($old_meta & 0x0F); + } + $this->map[$X][$Z][1][$index]{$y >> 1} = chr($meta); + } + + + public function getBlockSkyLight($x, $y, $z){ + $x = (int) $x; + $y = (int) $y; + $z = (int) $z; + $X = $x >> 4; + $Z = $z >> 4; + $aX = $x - ($X << 4); + $aZ = $z - ($Z << 4); + $index = $aZ + ($aX << 4); + $lite = ord($this->map[$X][$Z][2][$index]{$y >> 1}); + if(($y & 1) === 0){ + $lite = $lite & 0x0F; + }else{ + $lite = $lite >> 4; + } + return $lite; + } + public function getBlockLight($x, $y, $z){ + $x = (int) $x; + $y = (int) $y; + $z = (int) $z; + $X = $x >> 4; + $Z = $z >> 4; + $aX = $x - ($X << 4); + $aZ = $z - ($Z << 4); + $index = $aZ + ($aX << 4); + $lite = ord($this->map[$X][$Z][3][$index]{$y >> 1}); + if(($y & 1) === 0){ + $lite = $lite & 0x0F; + }else{ + $lite = $lite >> 4; + } + return $lite; + } + +} \ No newline at end of file diff --git a/classlib/pocketmine_1_3/pmf/PMF.php b/classlib/pocketmine_1_3/pmf/PMF.php new file mode 100644 index 0000000..837629e --- /dev/null +++ b/classlib/pocketmine_1_3/pmf/PMF.php @@ -0,0 +1,127 @@ +create($file, $type, $version); + }else{ + if($this->load($file) !== true){ + $this->parseInfo(); + } + } + } + + public function getVersion(){ + return $this->version; + } + + public function getType(){ + return $this->type; + } + + public function load($file){ + $this->close(); + $this->file = $file; + if(($this->fp = @fopen($file, "c+b")) !== false){ + fseek($this->fp, 0, SEEK_END); + if(ftell($this->fp) >= 5){ //Header + 2 Bytes + @flock($this->fp, LOCK_EX); + return true; + } + $this->close(); + } + return false; + } + + public function parseInfo(){ + $this->seek(0); + if(fread($this->fp, 3) !== "PMF"){ + return false; + } + $this->version = ord($this->read(1)); + switch($this->version){ + case 0x01: + $this->type = ord($this->read(1)); + break; + default: + echo("[ERROR] Tried loading non-supported PMF version ".$this->version." on file ".$this->file."\n"); + return false; + } + return true; + } + + public function getFile(){ + return $this->file; + } + + public function close(){ + unset($this->version, $this->type, $this->file); + if(is_object($this->fp)){ + @flock($this->fp, LOCK_UN); + fclose($this->fp); + } + } + + public function create($file, $type, $version = self::PMF_CURRENT_VERSION){ + $this->file = $file; + @mkdir(dirname($this->file), 0755, true); + if(!is_resource($this->fp)){ + if(($this->fp = @fopen($file, "c+b")) === false){ + return false; + } + } + $this->seek(0); + $this->write("PMF" . chr((int) $version) . chr((int) $type)); + } + + public function read($length){ + if($length <= 0){ + return ""; + } + if(is_resource($this->fp)){ + return fread($this->fp, (int) $length); + } + return false; + } + + public function write($string, $length = false){ + if(is_resource($this->fp)){ + return ($length === false ? fwrite($this->fp, $string) : fwrite($this->fp, $string, $length)); + } + return false; + } + + public function seek($offset, $whence = SEEK_SET){ + if(is_resource($this->fp)){ + return fseek($this->fp, (int) $offset, (int) $whence); + } + return false; + } + +} \ No newline at end of file diff --git a/classlib/pocketmine_1_3/pmf/PMFLevel.php b/classlib/pocketmine_1_3/pmf/PMFLevel.php new file mode 100644 index 0000000..72c21e8 --- /dev/null +++ b/classlib/pocketmine_1_3/pmf/PMFLevel.php @@ -0,0 +1,526 @@ +levelData[$index])){ + return false; + } + return ($this->levelData[$index]); + } + + public function setData($index, $data){ + if(!isset($this->levelData[$index])){ + return false; + } + $this->levelData[$index] = $data; + return true; + } + + public function close(){ + $chunks = null; + unset($chunks, $chunkChange, $locationTable); + parent::close(); + } + + public function __construct($file, $blank = false){ + if(is_array($blank)){ + $this->create($file, 0); + $this->levelData = $blank; + $this->createBlank(); + $this->isLoaded = true; + $this->log = (int) ((string) log($this->levelData["width"], 2)); + }else{ + if($this->load($file) !== false){ + $this->parseInfo(); + if($this->parseLevel() === false){ + $this->isLoaded = false; + }else{ + $this->isLoaded = true; + $this->log = (int) ((string) log($this->levelData["width"], 2)); + } + }else{ + $this->isLoaded = false; + } + } + } + + public function saveData($locationTable = true){ + $this->levelData["version"] = self::PMF_CURRENT_LEVEL_VERSION; + @ftruncate($this->fp, 5); + $this->seek(5); + $this->write(chr($this->levelData["version"])); + $this->write(Utils::writeShort(strlen($this->levelData["name"])).$this->levelData["name"]); + $this->write(Utils::writeInt($this->levelData["seed"])); + $this->write(Utils::writeInt($this->levelData["time"])); + $this->write(Utils::writeFloat($this->levelData["spawnX"])); + $this->write(Utils::writeFloat($this->levelData["spawnY"])); + $this->write(Utils::writeFloat($this->levelData["spawnZ"])); + $this->write(chr($this->levelData["width"])); + $this->write(chr($this->levelData["height"])); + $extra = gzdeflate($this->levelData["extra"], self::PMF_LEVEL_DEFLATE_LEVEL); + $this->write(Utils::writeShort(strlen($extra)).$extra); + $this->payloadOffset = ftell($this->fp); + + if($locationTable !== false){ + $this->writeLocationTable(); + } + } + + private function createBlank(){ + $this->saveData(false); + $this->locationTable = array(); + $cnt = pow($this->levelData["width"], 2); + @mkdir(dirname($this->file)."/chunks/", 0755); + for($index = 0; $index < $cnt; ++$index){ + $this->chunks[$index] = false; + $this->chunkChange[$index] = false; + $this->locationTable[$index] = array( + 0 => 0, + ); + $this->write(Utils::writeShort(0)); + $X = $Z = null; + $this->getXZ($index, $X, $Z); + @file_put_contents($this->getChunkPath($X, $Z), gzdeflate("", self::PMF_LEVEL_DEFLATE_LEVEL)); + } + if(!file_exists(dirname($this->file)."/entities.yml")){ + $entities = new Config(dirname($this->file)."/entities.yml", CONFIG_YAML); + $entities->save(); + } + if(!file_exists(dirname($this->file)."/tiles.yml")){ + $tiles = new Config(dirname($this->file)."/tiles.yml", CONFIG_YAML); + $tiles->save(); + } + } + + protected function parseLevel(){ + if($this->getType() !== 0x00){ + return false; + } + $this->seek(5); + $this->levelData["version"] = ord($this->read(1)); + if($this->levelData["version"] > self::PMF_CURRENT_LEVEL_VERSION){ + return false; + } + $this->levelData["name"] = $this->read(Utils::readShort($this->read(2), false)); + $this->levelData["seed"] = Utils::readInt($this->read(4)); + $this->levelData["time"] = Utils::readInt($this->read(4)); + $this->levelData["spawnX"] = Utils::readFloat($this->read(4)); + $this->levelData["spawnY"] = Utils::readFloat($this->read(4)); + $this->levelData["spawnZ"] = Utils::readFloat($this->read(4)); + $this->levelData["width"] = ord($this->read(1)); + $this->levelData["height"] = ord($this->read(1)); + if(($this->levelData["width"] !== 16 and $this->levelData["width"] !== 32) or $this->levelData["height"] !== 8){ + return false; + } + $lastseek = ftell($this->fp); + if(($len = $this->read(2)) === false or ($this->levelData["extra"] = @gzinflate($this->read(Utils::readShort($len, false)))) === false){ //Corruption protection + echo("[NOTICE] Empty/corrupt location table detected, forcing recovery\n"); + fseek($this->fp, $lastseek); + $c = gzdeflate(""); + $this->write(Utils::writeShort(strlen($c)).$c); + $this->payloadOffset = ftell($this->fp); + $this->levelData["extra"] = ""; + $cnt = pow($this->levelData["width"], 2); + for($index = 0; $index < $cnt; ++$index){ + $this->write("\x00\xFF"); //Force index recreation + } + fseek($this->fp, $this->payloadOffset); + }else{ + $this->payloadOffset = ftell($this->fp); + } + return $this->readLocationTable(); + } + + public function getIndex($X, $Z){ + $X = (int) $X; + $Z = (int) $Z; + return ($Z << $this->log) + $X; + } + + public function getXZ($index, &$X = null, &$Z = null){ + $X = $index >> $this->log; + $Z = $index & (pow($this->log, 2) - 1); + return array($X, $Z); + } + + private function readLocationTable(){ + $this->locationTable = array(); + $cnt = pow($this->levelData["width"], 2); + $this->seek($this->payloadOffset); + for($index = 0; $index < $cnt; ++$index){ + $this->chunks[$index] = false; + $this->chunkChange[$index] = false; + $this->locationTable[$index] = array( + 0 => Utils::readShort($this->read(2)), //16 bit flags + ); + } + return true; + } + + private function writeLocationTable(){ + $cnt = pow($this->levelData["width"], 2); + @ftruncate($this->fp, $this->payloadOffset); + $this->seek($this->payloadOffset); + for($index = 0; $index < $cnt; ++$index){ + $this->write(Utils::writeShort($this->locationTable[$index][0])); + } + } + + private function getChunkPath($X, $Z){ + return dirname($this->file)."/chunks/".$Z.".".$X.".pmc"; + } + + public function loadChunk($X, $Z){ + $X = (int) $X; + $Z = (int) $Z; + $index = $this->getIndex($X, $Z); + if($this->isChunkLoaded($X, $Z)){ + return true; + }elseif(!isset($this->locationTable[$index])){ + return false; + } + $info = $this->locationTable[$index]; + $this->seek($info[0]); + + $chunk = @gzopen($this->getChunkPath($X, $Z), "rb"); + if($chunk === false){ + return false; + } + $this->chunks[$index] = array(); + $this->chunkChange[$index] = array(-1 => false); + for($Y = 0; $Y < $this->levelData["height"]; ++$Y){ + $t = 1 << $Y; + if(($info[0] & $t) === $t){ + // 4096 + 2048 + 2048, Block Data, Meta, Light + if(strlen($this->chunks[$index][$Y] = gzread($chunk, 8192)) < 8192){ + echo("[NOTICE] Empty corrupt chunk detected [$X,$Z,:$Y], recovering contents\n"); + $this->fillMiniChunk($X, $Z, $Y); + } + }else{ + $this->chunks[$index][$Y] = false; + } + } + @gzclose($chunk); + return true; + } + + public function unloadChunk($X, $Z, $save = true){ + $X = (int) $X; + $Z = (int) $Z; + if(!$this->isChunkLoaded($X, $Z)){ + return false; + }elseif($save !== false){ + $this->saveChunk($X, $Z); + } + $index = $this->getIndex($X, $Z); + $this->chunks[$index] = null; + $this->chunkChange[$index] = null; + unset($this->chunks[$index], $this->chunkChange[$index]); + return true; + } + + public function isChunkLoaded($X, $Z){ + $index = $this->getIndex($X, $Z); + if(!isset($this->chunks[$index]) or $this->chunks[$index] === false){ + return false; + } + return true; + } + + protected function isMiniChunkEmpty($X, $Z, $Y){ + $index = $this->getIndex($X, $Z); + if($this->chunks[$index][$Y] !== false){ + if(substr_count($this->chunks[$index][$Y], "\x00") < 8192){ + return false; + } + } + return true; + } + + protected function fillMiniChunk($X, $Z, $Y){ + if($this->isChunkLoaded($X, $Z) === false){ + return false; + } + $index = $this->getIndex($X, $Z); + $this->chunks[$index][$Y] = str_repeat("\x00", 8192); + $this->chunkChange[$index][-1] = true; + $this->chunkChange[$index][$Y] = 8192; + $this->locationTable[$index][0] |= 1 << $Y; + return true; + } + + public function getMiniChunk($X, $Z, $Y){ + if($this->loadChunk($X, $Z) === false){ + return str_repeat("\x00", 8192); + } + $index = $this->getIndex($X, $Z); + if($this->chunks[$index][$Y] === false){ + return str_repeat("\x00", 8192); + } + return $this->chunks[$index][$Y]; + } + + public function setMiniChunk($X, $Z, $Y, $data){ + if($this->isChunkLoaded($X, $Z) === false){ + $this->loadChunk($X, $Z); + } + if(strlen($data) !== 8192){ + return false; + } + $index = $this->getIndex($X, $Z); + $this->chunks[$index][$Y] = (string) $data; + $this->chunkChange[$index][-1] = true; + $this->chunkChange[$index][$Y] = 8192; + $this->locationTable[$index][0] |= 1 << $Y; + return true; + } + + public function getBlockID($x, $y, $z){ + if($y > 127 or $y < 0 or $x < 0 or $z < 0 or $x > 255 or $z > 255){ + return 0; + } + $X = $x >> 4; + $Z = $z >> 4; + $Y = $y >> 4; + $index = $this->getIndex($X, $Z); + $aX = $x - ($X << 4); + $aZ = $z - ($Z << 4); + $aY = $y - ($Y << 4); + $b = ord($this->chunks[$index][$Y]{(int) ($aY + ($aX << 5) + ($aZ << 9))}); + return $b; + } + + public function setBlockID($x, $y, $z, $block){ + if($y > 127 or $y < 0 or $x < 0 or $z < 0 or $x > 255 or $z > 255){ + return false; + } + $X = $x >> 4; + $Z = $z >> 4; + $Y = $y >> 4; + $block &= 0xFF; + if($X >= 32 or $Z >= 32 or $Y >= $this->levelData["height"] or $y < 0){ + return false; + } + $index = $this->getIndex($X, $Z); + $aX = $x - ($X << 4); + $aZ = $z - ($Z << 4); + $aY = $y - ($Y << 4); + $this->chunks[$index][$Y]{(int) ($aY + ($aX << 5) + ($aZ << 9))} = chr($block); + if(!isset($this->chunkChange[$index][$Y])){ + $this->chunkChange[$index][$Y] = 1; + }else{ + ++$this->chunkChange[$index][$Y]; + } + $this->chunkChange[$index][-1] = true; + return true; + } + + public function getBlockDamage($x, $y, $z){ + if($y > 127 or $y < 0 or $x < 0 or $z < 0 or $x > 255 or $z > 255){ + return 0; + } + $X = $x >> 4; + $Z = $z >> 4; + $Y = $y >> 4; + $index = $this->getIndex($X, $Z); + $aX = $x - ($X << 4); + $aZ = $z - ($Z << 4); + $aY = $y - ($Y << 4); + $m = ord($this->chunks[$index][$Y]{(int) (($aY >> 1) + 16 + ($aX << 5) + ($aZ << 9))}); + if(($y & 1) === 0){ + $m = $m & 0x0F; + }else{ + $m = $m >> 4; + } + return $m; + } + + public function setBlockDamage($x, $y, $z, $damage){ + if($y > 127 or $y < 0 or $x < 0 or $z < 0 or $x > 255 or $z > 255){ + return false; + } + $X = $x >> 4; + $Z = $z >> 4; + $Y = $y >> 4; + $damage &= 0x0F; + if($X >= 32 or $Z >= 32 or $Y >= $this->levelData["height"] or $y < 0){ + return false; + } + $index = $this->getIndex($X, $Z); + $aX = $x - ($X << 4); + $aZ = $z - ($Z << 4); + $aY = $y - ($Y << 4); + $mindex = (int) (($aY >> 1) + 16 + ($aX << 5) + ($aZ << 9)); + $old_m = ord($this->chunks[$index][$Y]{$mindex}); + if(($y & 1) === 0){ + $m = ($old_m & 0xF0) | $damage; + }else{ + $m = ($damage << 4) | ($old_m & 0x0F); + } + + if($old_m != $m){ + $this->chunks[$index][$Y]{$mindex} = chr($m); + if(!isset($this->chunkChange[$index][$Y])){ + $this->chunkChange[$index][$Y] = 1; + }else{ + ++$this->chunkChange[$index][$Y]; + } + $this->chunkChange[$index][-1] = true; + return true; + } + return false; + } + + public function getBlock($x, $y, $z){ + $X = $x >> 4; + $Z = $z >> 4; + $Y = $y >> 4; + if($x < 0 or $z < 0 or $X >= $this->levelData["width"] or $Z >= $this->levelData["width"] or $Y >= $this->levelData["height"] or $y < 0){ + return array(0, 0); + } + $index = $this->getIndex($X, $Z); + if(!isset($this->chunks[$index]) or $this->chunks[$index] === false){ + if($this->loadChunk($X, $Z) === false){ + return array(0, 0); + } + }elseif($this->chunks[$index][$Y] === false){ + return array(0, 0); + } + $aX = $x - ($X << 4); + $aZ = $z - ($Z << 4); + $aY = $y - ($Y << 4); + $b = ord($this->chunks[$index][$Y]{(int) ($aY + ($aX << 5) + ($aZ << 9))}); + $m = ord($this->chunks[$index][$Y]{(int) (($aY >> 1) + 16 + ($aX << 5) + ($aZ << 9))}); + if(($y & 1) === 0){ + $m = $m & 0x0F; + }else{ + $m = $m >> 4; + } + return array($b, $m); + } + + public function setBlock($x, $y, $z, $block, $meta = 0){ + $X = $x >> 4; + $Z = $z >> 4; + $Y = $y >> 4; + $block &= 0xFF; + $meta &= 0x0F; + if($X >= 32 or $Z >= 32 or $Y >= $this->levelData["height"] or $y < 0){ + return false; + } + $index = $this->getIndex($X, $Z); + if(!isset($this->chunks[$index]) or $this->chunks[$index] === false){ + if($this->loadChunk($X, $Z) === false){ + return false; + } + }elseif($this->chunks[$index][$Y] === false){ + $this->fillMiniChunk($X, $Z, $Y); + } + $aX = $x - ($X << 4); + $aZ = $z - ($Z << 4); + $aY = $y - ($Y << 4); + $bindex = (int) ($aY + ($aX << 5) + ($aZ << 9)); + $mindex = (int) (($aY >> 1) + 16 + ($aX << 5) + ($aZ << 9)); + $old_b = ord($this->chunks[$index][$Y]{$bindex}); + $old_m = ord($this->chunks[$index][$Y]{$mindex}); + if(($y & 1) === 0){ + $m = ($old_m & 0xF0) | $meta; + }else{ + $m = ($meta << 4) | ($old_m & 0x0F); + } + + if($old_b !== $block or $old_m !== $m){ + $this->chunks[$index][$Y]{$bindex} = chr($block); + $this->chunks[$index][$Y]{$mindex} = chr($m); + if(!isset($this->chunkChange[$index][$Y])){ + $this->chunkChange[$index][$Y] = 1; + }else{ + ++$this->chunkChange[$index][$Y]; + } + $this->chunkChange[$index][-1] = true; + if($old_b instanceof LiquidBlock) + { + $pos = new Position($x, $y, $z, $this->level); + for($side = 0; $side <= 5; ++$side) + { + $b = $pos->getSide($side); + if($b instanceof LavaBlock) { ServerAPI::request()->api->block->scheduleBlockUpdate(new Position($b, 0, 0, $this->level), 40, BLOCK_UPDATE_NORMAL); } + else { ServerAPI::request()->api->block->scheduleBlockUpdate(new Position($b, 0, 0, $this->level), 10, BLOCK_UPDATE_NORMAL); } + } + } + return true; + } + return false; + } + + public function saveChunk($X, $Z){ + $X = (int) $X; + $Z = (int) $Z; + if(!$this->isChunkLoaded($X, $Z)){ + return false; + } + $index = $this->getIndex($X, $Z); + if(!isset($this->chunkChange[$index]) or $this->chunkChange[$index][-1] === false){//No changes in chunk + return true; + } + + $chunk = @gzopen($this->getChunkPath($X, $Z), "wb".self::PMF_LEVEL_DEFLATE_LEVEL); + $bitmap = 0; + for($Y = 0; $Y < $this->levelData["height"]; ++$Y){ + if($this->chunks[$index][$Y] !== false and ((isset($this->chunkChange[$index][$Y]) and $this->chunkChange[$index][$Y] === 0) or !$this->isMiniChunkEmpty($X, $Z, $Y))){ + gzwrite($chunk, $this->chunks[$index][$Y]); + $bitmap |= 1 << $Y; + }else{ + $this->chunks[$index][$Y] = false; + } + $this->chunkChange[$index][$Y] = 0; + } + $this->chunkChange[$index][-1] = false; + $this->locationTable[$index][0] = $bitmap; + $this->seek($this->payloadOffset + ($index << 1)); + $this->write(Utils::writeShort($this->locationTable[$index][0])); + return true; + } + + public function doSaveRound(){ + foreach($this->chunks as $index => $chunk){ + $this->getXZ($index, $X, $Z); + $this->saveChunk($X, $Z); + } + } + + public function getAttr($attr) { + return $this->levelData[$attr]; + } +} diff --git a/classlib/version.txt b/classlib/version.txt new file mode 120000 index 0000000..aa4e5be --- /dev/null +++ b/classlib/version.txt @@ -0,0 +1 @@ +../version.txt \ No newline at end of file diff --git a/main.php b/main.php new file mode 100644 index 0000000..e3238f1 --- /dev/null +++ b/main.php @@ -0,0 +1,22 @@ + +author: aliuly + +commands: + im: + description: Import Map + usage: "/im " + +permissions: + im.cmd.im: + default: op + description: "Allows users to import maps" diff --git a/plugin/resources/rules.txt b/plugin/resources/rules.txt new file mode 100644 index 0000000..8e665fd --- /dev/null +++ b/plugin/resources/rules.txt @@ -0,0 +1,7 @@ +; +; Sample block translation rules file +; The ";" is a comment +; +; BLOCKS +; GrassBlock = Gravel +; 5 = 4 ;WoodenPlank -> Cobblestone diff --git a/plugin/src/ImportMap/Importer.php b/plugin/src/ImportMap/Importer.php new file mode 100644 index 0000000..3778c17 --- /dev/null +++ b/plugin/src/ImportMap/Importer.php @@ -0,0 +1,40 @@ +args = serialize($args); + } + public function onRun() { + $this->setResult("ABORTED!"); + $args = unserialize($this->args); + $cmd = self::makeCmdLine($args); + echo "CMD> $cmd\n"; + $start = time(); + $fp = popen($cmd,"r"); + while (($c = fread($fp,64)) != false) { + echo($c); + } + pclose($fp); + $end = time(); + $this->setResult("Run-time: ".($end-$start)); + } + public function onCompletion(Server $server) { + $server->broadcastMessage($this->getResult()); + } +} \ No newline at end of file diff --git a/plugin/src/ImportMap/Main.php b/plugin/src/ImportMap/Main.php new file mode 100644 index 0000000..04f24ae --- /dev/null +++ b/plugin/src/ImportMap/Main.php @@ -0,0 +1,91 @@ +isPhar()) + return preg_replace('/\/+$/','',substr($this->getFile(),7)); + //echo "IMPORTER: ".substr($this->getFile(),7)."\n"; + echo dirname(realpath($this->getFile()))."/pmimporter/pmimporter.phar\n"; + return dirname(realpath($this->getFile()))."/pmimporter/pmimporter.phar"; + } + private function schedule($args) { + $this->getServer()->getScheduler()->scheduleAsyncTask(new Importer($args)); + } + private function usage(CommandSender $c) { + $c->sendMessage("Usage:"); + $c->sendMessage("- ".TextFormat::GREEN."/im version".TextFormat::RESET.": get the pmimporter version"); + $c->sendMessage("- ".TextFormat::GREEN."/im path world".TextFormat::RESET.": import map in [path] as level named [world]"); + return true; + } + public function onCommand(CommandSender $sender, Command $cmd, $label, array $args) { + + switch($cmd->getName()) { + case "im": + if (!$sender->hasPermission("im.cmd.im")) { + $sender->sendMessage("You do not have permission to do that."); + return true; + } + if (!isset($args[0])) return $this->usage($sender); + if ($args[0] == "version") { + $sender->sendMessage(Importer::phpRun([$this->importer(),'version'])); + return true; + } + if (!isset($args[1])) return $this->usage($sender); + + $impath = $args[0]; + $world = $args[1]; + + if ($this->getServer()->isLevelGenerated($world)) { + $sender->sendMessage("$world already exists"); + return true; + } + $impath = preg_replace('/\/*$/',"",$impath).'/'; + if (!is_dir($impath)) { + $sender->sendMessage("$impath not found"); + return true; + } + $srcfmt = LevelProviderManager::getProvider($impath); + if (!$srcfmt) { + $sender->sendMessage("$impath: Format not recognized"); + return true; + } + $dstfmt = $this->getServer()->getProperty("level-settings.default-format", "mcregion"); + $dstfmt = LevelProviderManager::getProviderByName($dstfmt); + if ($dstfmt === null) { + $dstfmt = "mcregion"; + } else { + $dstfmt = $dstfmt::getProviderName(); + } + $target = $this->getServer()->getDataPath()."worlds/".$world."/"; + + $sender->sendMessage("Importing $impath to $world in the background"); + $this->getServer()->broadcastMessage("Importing world, expect LAG!"); + $dir =$this->getDataFolder(); + $this->schedule([$this->importer(),'pmconvert', + '-c',$dir.'rules.txt','-f',$dstfmt, + $impath,$target]); + return true; + break; + default: + return false; + } + return true; + } + + public function onEnable() { + @mkdir($this->getDataFolder()); + $this->saveResource('rules.txt',false); + } + public function onDisable() { + $this->getLogger()->info("ImportMap Unloaded!"); + } +} diff --git a/rules.txt b/rules.txt new file mode 100644 index 0000000..8e665fd --- /dev/null +++ b/rules.txt @@ -0,0 +1,7 @@ +; +; Sample block translation rules file +; The ";" is a comment +; +; BLOCKS +; GrassBlock = Gravel +; 5 = 4 ;WoodenPlank -> Cobblestone diff --git a/scripts/dumpchunk.php b/scripts/dumpchunk.php new file mode 100644 index 0000000..387239d --- /dev/null +++ b/scripts/dumpchunk.php @@ -0,0 +1,52 @@ +getRegions(); + + +foreach ($argv as $ppx) { + $ppx = explode(':',$ppx,2); + $pp = array_shift($ppx); + if (!isset($regions[$pp])) die("Region $pp does not exist\n"); + + if (!count($ppx)) die("Must specify the chunk to dump\n"); + + list($rX,$rZ) = $regions[$pp]; + $region = $fmt->getRegion($rX,$rZ); + + foreach (explode('+',$ppx[0]) as $cp) { + $cp = explode(',',$cp); + if (count($cp) != 2) die("Invalid chunk ids: ".$ppx[0].NL); + list($oX,$oZ) = $cp; + if (!is_numeric($oX) || !is_numeric($oZ)) die("Not numeric $oX,$oZ\n"); + if ($oX < 0 || $oZ < 0 || $oX >= 32 || $oZ >= 32) + die("Invalid location $oX,$oZ\n"); + if (!$region->chunkExists($oX,$oZ)) { + echo("chunk($oX,$oZ) does not exist!\n"); + continue; + } + $chunkData = $region->readChunkData($oX,$oZ); + echo $chunkData; + } +} diff --git a/scripts/nbtdump.php b/scripts/nbtdump.php new file mode 100644 index 0000000..f67ea00 --- /dev/null +++ b/scripts/nbtdump.php @@ -0,0 +1,33 @@ +read(substr($dat,8)); +} elseif (substr($dat,0,3) == "ENT" && ord(substr($dat,3,1)) == 0 && + Binary::readLInt(substr($dat,4,4)) == 1 && + Binary::readLInt(substr($dat,8,4)) == (strlen($dat) - 12)) { + // MCPE v0.2.0 entities.dat + $nbt = new NBT(NBT::LITTLE_ENDIAN); + $nbt->read(substr($dat,12)); +} else { + $nbt = new NBT(NBT::BIG_ENDIAN); + $nbt->readCompressed($dat); +} +$levelData = $nbt->getData(); +print_r($levelData); diff --git a/scripts/pmcheck.php b/scripts/pmcheck.php new file mode 100644 index 0000000..8b83e9a --- /dev/null +++ b/scripts/pmcheck.php @@ -0,0 +1,171 @@ +getPath().NL; +echo "Name: ".$fmt->getName().NL; +echo "Format: ".$fmt::getFormatName().NL; +echo "Seed: ".$fmt->getSeed().NL; +echo "Generator: ".$fmt->getGenerator().NL; +$opts = $fmt->getGeneratorOptions(); +if (isset($opts["preset"])) echo "GenOpts: ".$opts["preset"].NL; +$spawn = $fmt->getSpawn(); +echo "Spawn: ".implode(',',[$spawn->getX(),$spawn->getY(),$spawn->getZ()]).NL; + +echo "Regions:"; +$regions = $fmt->getRegions(); +foreach ($regions as $pp) { + list($rX,$rZ) = $pp; + echo " $rX,$rZ"; +} +echo "\n"; + +function incr(&$stats,$attr) { + if (isset($stats[$attr])) { + ++$stats[$attr]; + } else { + $stats[$attr] = 1; + } +} + +function analyze_chunk(Chunk $chunk,&$stats) { + if ($chunk->isPopulated()) incr($stats,"-populated"); + if ($chunk->isGenerated()) incr($stats,"-generated"); + + for ($x = 0;$x < 16;$x++) { + for ($z=0;$z < 16;$z++) { + for ($y=0;$y < 128;$y++) { + list($id,$meta) = $chunk->getBlock($x,$y,$z); + incr($stats,$id); + } + $height = $chunk->getHeightMap($x,$z); + if (!isset($stats["Height:Max"])) { + $stats["Height:Max"] = $height; + } elseif ($height > $stats["Height:Max"]) { + $stats["Height:Max"] = $height; + } + if (!isset($stats["Height:Min"])) { + $stats["Height:Min"] = $height; + } elseif ($height < $stats["Height:Min"]) { + $stats["Height:Min"] = $height; + } + if (!isset($stats["Height:Sum"])) { + $stats["Height:Sum"] = $height; + } else { + $stats["Height:Sum"] += $height; + } + incr($stats,"Height:Count"); + } + } + foreach ($chunk->getEntities() as $entity) { + if (!isset($entity->id)) continue; + if ($entity->id->getValue() == "Item") { + incr($stats,"ENTITY:Item:".$entity->Item->id->getValue()); + continue; + } + incr($stats,"ENTITY:".$entity->id->getValue()); + + } + foreach ($chunk->getTileEntities() as $tile) { + if (!isset($tile->id)) continue; + incr($stats,"TILE:".$tile->id->getValue()); + } +} + +if (isset($argv[0]) && $argv[0] == '--all') { + // Process all regions + $argv = array_keys($regions); + echo "Analyzing ".count($regions)." regions\n"; +} + +foreach ($argv as $ppx) { + $ppx = explode(':',$ppx,2); + $pp = array_shift($ppx); + + if (!isset($regions[$pp])) die("Region $pp does not exist\n"); + echo " Reg: $pp "; + $chunks = 0; + list($rX,$rZ) = $regions[$pp]; + $region = $fmt->getRegion($rX,$rZ); + $chunks = 0; + $stats = []; + + if (count($ppx)) { + foreach (explode('+',$ppx[0]) as $cp) { + $cp = explode(',',$cp); + if (count($cp) != 2) die("Invalid chunk ids: ".$ppx[0].NL); + list($oX,$oZ) = $cp; + if (!is_numeric($oX) || !is_numeric($oZ)) die("Not numeric $oX,$oZ\n"); + if ($oX < 0 || $oZ < 0 || $oX >= 32 || $oZ >= 32) + die("Invalid location $oX,$oZ\n"); + if ($region->chunkExists($oX,$oZ)) { + ++$chunks; + $chunk = $region->readChunk($oX,$oZ); + if ($chunk) + analyze_chunk($chunk,$stats); + else + echo "Unable to read chunk: $oX,$oZ\n"; + } + } + } else { + for ($oX=0;$oX < 32;$oX++) { + //for ($oX=7;$oX < 8;$oX++) { + $cX = $rX*32+$oX; + for ($oZ=0;$oZ < 32; $oZ++) { + if (!($oZ % 16)) echo "."; + //for ($oZ=6;$oZ < 9; $oZ++) { + $cZ = $rZ*32+$oZ; + if ($region->chunkExists($oX,$oZ)) { + ++$chunks; + $chunk = $region->readChunk($oX,$oZ); + if ($chunk) analyze_chunk($chunk,$stats); + } + } + } + } + echo "\n"; + unset($region); + echo " Chunks:\t$chunks\n"; + if (isset($stats["Height:Count"]) && isset($stats["Height:Sum"])) { + $stats["Height:Avg"] = $stats["Height:Sum"]/$stats["Height:Count"]; + unset($stats["Height:Count"]); + unset($stats["Height:Sum"]); + } + $sorted = array_keys($stats); + natsort($sorted); + foreach ($sorted as $k) { + if (is_numeric($k)) { + $v = Blocks::getBlockById($k); + $v = $v !== null ? "$v ($k)" : "*Unsupported* ($k)"; + } else { + $v = $k; + } + echo " $v:\t".$stats[$k].NL; + } +} diff --git a/scripts/pmconvert.php b/scripts/pmconvert.php new file mode 100644 index 0000000..7a561f5 --- /dev/null +++ b/scripts/pmconvert.php @@ -0,0 +1,193 @@ + null, "out" => null ]; + +while (count($argv)) { + if ($argv[0] == "-f") { + array_shift($argv); + $dstformat = array_shift($argv); + if (!isset($dstformat)) die("No format specified\n"); + } elseif ($argv[0] == "-c") { + array_shift($argv); + $rules = array_shift($argv); + if (!isset($rules)) die("No rules file specified\n"); + loadRules($rules); + } elseif ($argv[0] == "-o") { + array_shift($argv); + $offset = array_shift($argv); + if (!isset($offset)) die("No offset specified\n"); + if (!is_numeric($offset)) die("Must specify a number for offset\n"); + } elseif ($argv[0] == "-t") { + array_shift($argv); + $threads = array_shift($argv); + if (!isset($threads)) die("No value specified for -t\n"); + $threads = intval($threads); + if ($threads < 1) die("Invalid thread value $threads\n"); + } elseif (preg_match('/^--(in|out)\.([A-Za-z0-9]+)=(.*)$/',$argv[0],$mv)) { + array_shift($argv); + list(,$mode,$key,$val) = $mv; + if (!$settings[$mode]) $settings[$mode] = []; + $settings[$mode][$key] = $val; + } else { + break; + } +} + +if (!extension_loaded("pcntl")) $threads = 1; + +$srcpath=array_shift($argv); +if (!isset($srcpath)) die("No src path specified\n"); +$srcpath = preg_replace('/\/*$/',"",$srcpath).'/'; +if (!is_dir($srcpath)) die("$srcpath: not found\n"); + +$dstpath=array_shift($argv); +if (!isset($dstpath)) die("No dst path specified\n"); +$dstpath = preg_replace('/\/*$/',"",$dstpath).'/'; +if (file_exists($dstpath)) die("$dstpath: already exist\n"); + +$srcformat = LevelFormatManager::getFormat($srcpath); +if (!$srcformat) die("$srcpath: Format not recognized\n"); + +$dstformat = LevelFormatManager::getFormatByName($dstformat); +if (!$dstformat) die("Output format not recognized\n"); +if ($dstformat !== McRegion::class) die("$dstformat: Format not supported\n"); + +$srcfmt = new $srcformat($srcpath,true,$settings["in"]); +$regions = $srcfmt->getRegions(); +if (!count($regions)) die("No regions found in $srcpath\n"); + +$dstformat::generate($dstpath,basename($dstpath), + $srcfmt->getSpawn(), + $srcfmt->getSeed(), + $srcfmt->getGenerator(), + $srcfmt->getGeneratorOptions()); + +$dstfmt = new $dstformat($dstpath,false,$settings["out"]); + +////////////////////////////////////////////////////////////////////// +function loadRules($file) { + $fp = fopen($file,"r"); + if ($fp === false) die("$file: Unable to open file\n"); + $state = 'blocks'; + while (($ln = fgets($fp)) !== false) { + $ln = preg_replace('/^\s+/','',$ln); + $ln = preg_replace('/\s+$/','',$ln); + if ($ln == '') continue; + if (preg_match('/^[;#]/',$ln)) continue; + if (strtolower($ln) == 'blocks') { + $state = 'blocks'; + } elseif (strtolower($ln) == 'tiles') { + die("Unsupported ruleset: tiles\n"); + } elseif (strtolower($ln) == 'entities') { + die("Unsupported ruleset: entities\n"); + } else { + if ($state == 'blocks') { + $pp = preg_split('/(\s|=)+/',$ln); + if (count($pp) == 1) { + echo("Error parsing line: $ln[0]\n"); + continue; + } else { + for ($i=0;$i<2;++$i) { + if (is_numeric($pp[$i])) continue; + $pp[$i] = Blocks::getBlockByName($pp[$i]); + if ($pp[$i] === null) { + echo("Unknown block type: $ln\n"); + continue; + } + } + Blocks::addRule($pp[0],$pp[1]); + } + } else { + die("Invalid internal state: $state\n"); + } + } + } + fclose($fp); +} + +function pmconvert_status($state,$data) { + switch ($state) { + case "CopyRegionStart": + echo " Reg: $data\n"; + break; + case "CopyChunk": + echo "."; + break; + case "CopyRegionDone": + echo "\n"; + break; + default: + echo "."; + } +} + + +////////////////////////////////////////////////////////////////////// +function copyNextRegion($offset) { + global $regions,$workers; + global $srcfmt,$dstfmt; + + if (!count($regions)) return; + $region = array_pop($regions); + $pid = pcntl_fork(); + + if ($pid == 0) { + echo "spawned: ".getmypid().NL; + Copier::copyRegion($region,$srcfmt,$dstfmt, + __NAMESPACE__."\\pmconvert_status", + $offset); + exit(0); + } elseif ($pid == -1) { + die("Could not fork\n"); + } else { + $workers[$pid] = $region; + } +} +if ($threads == 1) { + foreach ($regions as $region) { + Copier::copyRegion($region,$srcfmt,$dstfmt, + __NAMESPACE__."\\pmconvert_status", + $offset); + } +} else { + echo "Threads: $threads\n"; + $workers = []; + for ($c = $threads;$c--;) { + copyNextRegion($offset); + } + while ($pid = pcntl_wait($rstatus)) { + if (!isset($workers[$pid])) continue; + list($rX,$rZ) = $workers[$pid]; + unset($workers[$pid]); + if (pcntl_wexitstatus($rstatus)) { + echo "$pid ($rX,$rZ) failed\n"; + } else { + echo "$pid ($rX,$rZ) succesful\n"; + } + if (count($regions)) { + copyNextRegion($offset); + } else { + if (!count($workers)) break; + } + } + echo "ALL DONE\n"; +} diff --git a/scripts/pmentities.php b/scripts/pmentities.php new file mode 100644 index 0000000..69bed76 --- /dev/null +++ b/scripts/pmentities.php @@ -0,0 +1,100 @@ +getEntities() as $entity) { + if (!isset($entity->id)) continue; + echo("//// ENTITY ////\n"); + print_r($entity); + } + foreach ($chunk->getTileEntities() as $tile) { + if (!isset($tile->id)) continue; + echo("//// TILE_ENTITY ////\n"); + print_r($tile); + } +} + + +$regions = $fmt->getRegions(); + + +if (isset($argv[0]) && $argv[0] == '--all') { + // Process all regions + $argv = array_keys($regions); + echo "Analyzing ".count($regions)." regions\n"; +} + +if (count($argv) == 0) + die("Must specify --all for all regions, or the specific regions\n"); + +foreach ($argv as $ppx) { + $ppx = explode(':',$ppx,2); + $pp = array_shift($ppx); + + if (!isset($regions[$pp])) die("Region $pp does not exist\n"); + echo " Reg: $pp\n"; + $chunks = 0; + list($rX,$rZ) = $regions[$pp]; + $region = $fmt->getRegion($rX,$rZ); + $chunks = 0; + $stats = []; + + if (count($ppx)) { + foreach (explode('+',$ppx[0]) as $cp) { + $cp = explode(',',$cp); + if (count($cp) != 2) die("Invalid chunk ids: ".$ppx[0].NL); + list($oX,$oZ) = $cp; + if (!is_numeric($oX) || !is_numeric($oZ)) die("Not numeric $oX,$oZ\n"); + if ($oX < 0 || $oZ < 0 || $oX >= 32 || $oZ >= 32) + die("Invalid location $oX,$oZ\n"); + if ($region->chunkExists($oX,$oZ)) { + ++$chunks; + $chunk = $region->readChunk($oX,$oZ); + if ($chunk) + analyze_chunk($chunk,$stats); + else + echo "Unable to read chunk: $oX,$oZ\n"; + } + } + } else { + for ($oX=0;$oX < 32;$oX++) { + $cX = $rX*32+$oX; + for ($oZ=0;$oZ < 32; $oZ++) { + $cZ = $rZ*32+$oZ; + if ($region->chunkExists($oX,$oZ)) { + ++$chunks; + $chunk = $region->readChunk($oX,$oZ); + if ($chunk) analyze_chunk($chunk,$stats); + } + } + } + } +} diff --git a/scripts/pmlevel.php b/scripts/pmlevel.php new file mode 100644 index 0000000..8c78b13 --- /dev/null +++ b/scripts/pmlevel.php @@ -0,0 +1,118 @@ +read(substr($dat,8)); + $ro = true; + $levelData = $nbt->getData(); +} else { + $nbt = new NBT(NBT::BIG_ENDIAN); + $nbt->readCompressed($dat); + $levelData = $nbt->getData()->Data; +} + +$changed = 0; +if ($ro && count($argv)) { + die("This file format is only supported as read-only!\n"); +} else { + foreach ($argv as $kv) { + $kv = explode("=",$kv,2); + if (count($kv) != 2) die("Invalid element: $kv[0]\n"); + list($k,$v) = $kv; + switch ($k) { + case "spawn": + $pos = explode(",",$v); + if (count($pos)!=3) die("Invalid spawn location: ".implode(",",$pos).NL); + list($x,$y,$z) = $pos; + if (($x=intval($x)) != $levelData->SpawnX) { + ++$changed; + $levelData->SpawnX = new Int("SpawnX", (int) $x); + } + if (($y=intval($y)) != $levelData->SpawnY) { + ++$changed; + $levelData->SpawnY = new Int("SpawnY", (int) $y); + } + if (($z=intval($z)) != $levelData->SpawnZ) { + ++$changed; + $levelData->SpawnZ = new Int("SpawnZ", (int) $z); + } + break; + break; + case "name": + // LevelName : string + if ($levelData->LevelName != $v) { + ++$changed; + $levelData->LevelName = new String("LevelName",$v); + } + break; + case "seed": //RandomSeed : Long + $v = intval($v); + if ($levelData->RandomSeed != $v) { + ++$changed; + $levelData->RandomSeed = new Long("RandomSeed",$v); + } + break; + case "generator": // generatorName(String),generatorVersion(Int) + $v = explode(",",$v); + if (count($v) < 1 || count($v) > 2) die("Invalid generator: $kv[0]\n"); + $genName = array_shift($v); + if ($levelData->generatorName != $genName) { + ++$changed; + $levelData->generatorName = new String("generatorName",$genName); + } + if (count($v)) { + $genVersion = intval(array_shift($v)); + if ($levelData->generatorVersion != $genVersion) { + ++$changed; + $levelData->generatorVersion = new Int("generatorVersion",(int)$genVersion); + } + } + break; + case "preset": + case "generatorOptions": // generatorOptions(String) + if ($levelData->generatorOptions != $v) { + ++$changed; + $levelData->generatorOptions = new String("generatorOptions",$v); + } + break; + default: + die("Attribute: $k not supported\n"); + } + } +} + +if ($changed) { + $nbt = new NBT(NBT::BIG_ENDIAN); + $nbt->setData(new Compound(null, ["Data" => $levelData])); + file_put_contents($lvfile,$nbt->writeCompressed()); +} + +echo "World: $wpath\n"; +//echo "version: ".$levelData["version"].NL; +echo "Spawn: ".implode(",",[$levelData->SpawnX,$levelData->SpawnY,$levelData->SpawnZ]).NL; +echo "Name: ".$levelData["LevelName"].NL; +echo "Seed: ".$levelData["RandomSeed"].NL; +echo "Generator: ".$levelData["generatorName"]." v".$levelData["generatorVersion"].NL; +if (isset($levelData["generatorOptions"])) + echo "GenPreset: ".$levelData["generatorOptions"].NL; diff --git a/version.txt b/version.txt new file mode 100644 index 0000000..93f5e18 --- /dev/null +++ b/version.txt @@ -0,0 +1 @@ +pmimporter 1.4rel