├── .poggit.yml ├── LICENSE ├── MIE_Converter ├── README.md ├── icon.png ├── plugin.yml └── src │ └── FaigerSYS │ └── MIE_Converter │ ├── MIE_Converter.php │ └── MapImageUtils.php ├── MIE_Protector ├── README.md ├── icon.png ├── plugin.yml └── src │ └── FaigerSYS │ └── MIE_Protector │ └── MIE_Protector.php ├── MapImageEngine ├── README.md ├── icon.png ├── plugin.yml ├── resources │ ├── instructions │ │ ├── ell.txt │ │ ├── eng.txt │ │ ├── kor.txt │ │ └── rus.txt │ └── strings │ │ ├── ell.ini │ │ ├── eng.ini │ │ ├── kor.ini │ │ └── rus.ini └── src │ └── FaigerSYS │ └── MapImageEngine │ ├── MapImageEngine.php │ ├── TranslateStrings.php │ ├── command │ └── MapImageEngineCommand.php │ ├── item │ └── FilledMap.php │ ├── packet │ └── CustomClientboundMapItemDataPacket.php │ └── storage │ ├── ImageStorage.php │ ├── MapImage.php │ ├── MapImageChunk.php │ └── OldFormatConverter.php └── README.md /.poggit.yml: -------------------------------------------------------------------------------- 1 | --- # Poggit-CI Manifest. Open the CI at https://poggit.pmmp.io/ci/FaigerSYS/MapImageEngine 2 | branches: 3 | - master 4 | projects: 5 | MapImageEngine: 6 | path: MapImageEngine/ 7 | icon: /MapImageEngine/icon.png 8 | MIE_Protector: 9 | path: MIE_Protector/ 10 | icon: /MIE_Protector/icon.png 11 | MIE_Converter: 12 | path: MIE_Converter/ 13 | icon: /MIE_Converter/icon.png 14 | ... 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /MIE_Converter/README.md: -------------------------------------------------------------------------------- 1 | # MIE_Converter ![MIE_Converter](https://poggit.pmmp.io/ci.badge/FaigerSYS/MapImageEngine/MIE_Converter) 2 | 3 | This plugin used as offline converter for your images. You can find additional information in the MapImageEngine's instructions -------------------------------------------------------------------------------- /MIE_Converter/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FaigerSYS/MapImageEngine/bd83f15fd782b2c3b177a2fcb473862bca57b59f/MIE_Converter/icon.png -------------------------------------------------------------------------------- /MIE_Converter/plugin.yml: -------------------------------------------------------------------------------- 1 | name: MIE_Converter 2 | main: FaigerSYS\MIE_Converter\MIE_Converter 3 | version: "1.0.2" 4 | api: ["3.0.0", "4.0.0"] 5 | load: POSTWORLD 6 | author: FaigerSYS 7 | description: "Addon for MapImageEngine to convert images to .mie format" 8 | 9 | commands: 10 | mieconvert: 11 | description: "Convert image to .mie format" 12 | permission: "mapimageengine.convert" 13 | 14 | permissions: 15 | mapimageengine.convert: 16 | default: op 17 | description: "Allows command usage (/mieconvert)" 18 | -------------------------------------------------------------------------------- /MIE_Converter/src/FaigerSYS/MIE_Converter/MIE_Converter.php: -------------------------------------------------------------------------------- 1 | getLogger()->info(CLR::GOLD . 'MIE_Converter enabling...'); 17 | 18 | if (!extension_loaded('gd')) { 19 | $this->getLogger()->warning('Your PHP binary does not contains the GD library required for image parsing'); 20 | $this->getLogger()->warning('Install GD, or use the online converter (website provided in the MapImageEngine\'s instructions)'); 21 | 22 | $this->getServer()->getPluginManager()->disablePlugin($this); 23 | return; 24 | } 25 | 26 | @mkdir($path = $this->getDataFolder()); 27 | @mkdir($path . 'to_convert'); 28 | @mkdir($path . 'converted'); 29 | 30 | $this->getLogger()->info(CLR::GOLD . 'MIE_Converter enabled! MIEI version: ' . MapImageUtils::CURRENT_VERSION); 31 | } 32 | 33 | public function onCommand(CommandSender $sender, Command $command, string $label, array $args) : bool { 34 | $x = array_shift($args); 35 | $y = array_shift($args); 36 | $path = implode(' ', $args); 37 | 38 | if (!is_numeric($x) || !is_numeric($y) || !strlen($path)) { 39 | $sender->sendMessage(self::MSG_PREFIX . 'Usage: /' . $label . ' '); 40 | $sender->sendMessage(CLR::GRAY . 'Notice: place image into the "to_convert" folder. Image name = full file name'); 41 | return true; 42 | } 43 | 44 | $x = (int) $x; 45 | $y = (int) $y; 46 | 47 | if ($x < 0 || $y < 0) { 48 | $sender->sendMessage(self::MSG_PREFIX . 'The count of blocks must be greater than 0!'); 49 | return true; 50 | } 51 | 52 | if ($path[0] !== '/') { 53 | $path = $this->getDataFolder() . 'to_convert/' . $path; 54 | } 55 | 56 | $data = @file_get_contents($path); 57 | if ($data === false) { 58 | $sender->sendMessage(self::MSG_PREFIX . 'File not found!'); 59 | return true; 60 | } 61 | 62 | $image = @imagecreatefromstring($data); 63 | if (!is_resource($image)) { 64 | $sender->sendMessage(self::MSG_PREFIX . 'File is not an image, or image has unsupported by your GD library format! Convert image to supported format (e.g. PNG) and try again, or use online converter'); 65 | return true; 66 | } 67 | 68 | $sender->sendMessage(self::MSG_PREFIX . 'Converting image...'); 69 | 70 | $data = MapImageUtils::generateImageData($image, $x, $y, 7); 71 | imagedestroy($image); 72 | 73 | $path = $this->getDataFolder() . 'converted/' . pathinfo($path)['filename'] . '_' . $x . 'x' . $y . '.miei'; 74 | file_put_contents($path, $data); 75 | 76 | $sender->sendMessage(self::MSG_PREFIX . 'Done! Image location: ' . CLR::WHITE . $path); 77 | return true; 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /MIE_Converter/src/FaigerSYS/MIE_Converter/MapImageUtils.php: -------------------------------------------------------------------------------- 1 | put('MIEI'); 30 | $data->putInt(self::CURRENT_VERSION); 31 | $data->putByte((int) ($compression_level > 0)); 32 | 33 | $buffer = new BinaryStream(); 34 | $buffer->put(UUID::fromRandom()->toBinary()); 35 | $buffer->putInt($blocks_width); 36 | $buffer->putInt($blocks_height); 37 | 38 | for ($y_b = 0; $y_b < $blocks_height; $y_b++) { 39 | for ($x_b = 0; $x_b < $blocks_width; $x_b++) { 40 | $buffer->putInt($chunk_width); 41 | $buffer->putInt($chunk_height); 42 | 43 | for ($y = 0; $y < $chunk_width; $y++) { 44 | for ($x = 0; $x < $chunk_height; $x++) { 45 | $color = imagecolorsforindex($image, imagecolorat($image, $x + ($x_b * $chunk_width), $y + ($y_b * $chunk_height))); 46 | $color = chr($color['red']) . chr($color['green']) . chr($color['blue']) . chr($color['alpha'] === 0 ? 255 : ~$color['alpha'] << 1 & 0xff); 47 | 48 | $buffer->put($color); 49 | } 50 | } 51 | } 52 | } 53 | 54 | imagedestroy($image); 55 | 56 | $buffer = $buffer->buffer; 57 | if ($compression_level > 0) { 58 | $buffer = zlib_encode($buffer, ZLIB_ENCODING_DEFLATE, min($compression_level, 9)); 59 | } 60 | $data->put($buffer); 61 | 62 | return $data->buffer; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /MIE_Protector/README.md: -------------------------------------------------------------------------------- 1 | # MIE_Protector ![MIE_Protector](https://poggit.pmmp.io/ci.badge/FaigerSYS/MapImageEngine/MIE_Protector) 2 | 3 | This plugin used as protection for MapImageEngine images (protection from rotation and destruction)
4 | To bypass protection, player must have **mapimageengine.bypassprotect** permission -------------------------------------------------------------------------------- /MIE_Protector/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FaigerSYS/MapImageEngine/bd83f15fd782b2c3b177a2fcb473862bca57b59f/MIE_Protector/icon.png -------------------------------------------------------------------------------- /MIE_Protector/plugin.yml: -------------------------------------------------------------------------------- 1 | name: MIE_Protector 2 | main: FaigerSYS\MIE_Protector\MIE_Protector 3 | version: "1.0.4" 4 | api: ["3.0.0", "4.0.0"] 5 | load: POSTWORLD 6 | author: FaigerSYS 7 | description: "Addon for MapImageEngine to protect the image (frame) from rotation and destruction" 8 | 9 | permissions: 10 | mapimageengine.bypassprotect: 11 | default: op 12 | description: "Bypass image protection" 13 | -------------------------------------------------------------------------------- /MIE_Protector/src/FaigerSYS/MIE_Protector/MIE_Protector.php: -------------------------------------------------------------------------------- 1 | getLogger()->info(CLR::GOLD . 'MIE_Protector enabling...'); 18 | 19 | $this->getServer()->getPluginManager()->registerEvents($this, $this); 20 | 21 | $this->getLogger()->info(CLR::GOLD . 'MIE_Protector enabled!'); 22 | } 23 | 24 | /** 25 | * @priority LOW 26 | * @ignoreCancelled 27 | */ 28 | public function onClick(PlayerInteractEvent $e) { 29 | $block = $e->getBlock(); 30 | $frame = $block->getLevel()->getTile($block); 31 | if ($frame instanceof ItemFrame && $frame->getItem() instanceof FilledMap && !$e->getPlayer()->hasPermission('mapimageengine.bypassprotect')) { 32 | $e->setCancelled(true); 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /MapImageEngine/README.md: -------------------------------------------------------------------------------- 1 | # MapImageEngine [![](https://poggit.pmmp.io/shield.state/MapImageEngine)](https://poggit.pmmp.io/p/MapImageEngine) 2 | 3 | Plugin that allows you to add full-color images to your server!
4 | Instructions: [click here](https://github.com/FaigerSYS/MapImageEngine/tree/master/MapImageEngine/resources/instructions)! -------------------------------------------------------------------------------- /MapImageEngine/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FaigerSYS/MapImageEngine/bd83f15fd782b2c3b177a2fcb473862bca57b59f/MapImageEngine/icon.png -------------------------------------------------------------------------------- /MapImageEngine/plugin.yml: -------------------------------------------------------------------------------- 1 | name: MapImageEngine 2 | main: FaigerSYS\MapImageEngine\MapImageEngine 3 | version: "1.1.3" 4 | api: ["3.0.0"] 5 | load: POSTWORLD 6 | author: FaigerSYS 7 | description: "Image engine for MC:BE servers" 8 | 9 | permissions: 10 | mapimageengine: 11 | default: op 12 | description: "Allows command usage (/mapimageengine)" 13 | -------------------------------------------------------------------------------- /MapImageEngine/resources/instructions/ell.txt: -------------------------------------------------------------------------------- 1 | ; Translated by nikoskon 2 | 3 | Αυτό το plugin δημιουργήθηκε απο τον FaigerSYS 4 | Ιστοσελίδα Github: https://github.com/FaigerSYS/MapImageEngine 5 | 6 | Αν βρήκατε ένα σφάλμα, ενημερώστε μας! Υποβάλετε νέα βλάβη στο GitHub 7 | 8 | Πώς να φτιάξετε τη δική σας εικόνα: 9 | 1. Μετατρέψτε οποιαδήποτε μια εικόνα ('png', 'jpg' κα.)... 10 | - Χρησιμοποιώντας το MIE_Converter plugin: https://github.com/FaigerSYS/MapImageEngine/tree/master/MIE_Converter 11 | - Χρησιμοποιώντας μετατροπέα στο διαδίκτυο (αν το plugin δεν λειτουργεί): https://faigersys.github.io/mie-converter 12 | 1.1. Δεν χρειάζεται να αλλάξετε το μέγεθος της εικόνας σε 128x128 . Ο ίδιος ο μετατροπέας θα αλλάξει το μέγεθος! 13 | 1.2. Δεν χρειάζεται να κομματιάσετε την εικόνα. Απλά επιλέξτε το μέγεθος που θέλετε σε κύβους! 14 | 2. Η μετατρεπόμενη εικόνα μεταφέρεται στο φάκελο 'images' του plugin 15 | 3. Εγινε! Τώρα μπορείτε να χρησιμοποιήσετε αυτήν την εικόνα 16 | 17 | Πώς να εγκαταστήσετε την εικόνα: 18 | 1. Εισάγετε '/mie list' για να δείτε τη λίστα με τις διαθέσιμες εικόνες 19 | 2. Επιλέξτε μία από αυτές και εισάγετε '/mie place <όνομα εικόνας>' 20 | 2.1. Μπορείτε να χρησιμοποιήσετε σημαίες για εύκολη εγκατάσταση εικόνων (Εισάγετε την εντολή ('/mie place') για να πάρετε μια πλήρη περιγραφή) 21 | 2.2. Για να φύγετε τη λειτουργία τοποθέτησης εισάγετε την εντολή '/mie exit' 22 | 3. Ακολουθήστε τις οδηγίες και η εικόνα θα εγκατασταθεί! 23 | 24 | Για να δείτε την πλήρη λίστα των εντολών, πληκτρολογήστε '/mie' 25 | 26 | Καλή τύχη! 27 | -------------------------------------------------------------------------------- /MapImageEngine/resources/instructions/eng.txt: -------------------------------------------------------------------------------- 1 | This plugin was made by FaigerSYS 2 | Github page: https://github.com/FaigerSYS/MapImageEngine 3 | 4 | If you found a bug, please let me know! Submit new issue on GitHub 5 | 6 | How to make own image: 7 | 1. Convert any image ('png', 'jpg' etc.)... 8 | - Using MIE_Converter plugin: https://github.com/FaigerSYS/MapImageEngine/tree/master/MIE_Converter 9 | - Using online converter (if the plugin does not work): https://faigersys.github.io/mie-converter 10 | 1.1. You don't need to change image size to 128x128. Converter itself will change the size 11 | 1.2. You don't need to chunk image. Just select required size in blocks 12 | 2. Move converted image to folder 'images' of this plugin 13 | 3. Done! Now you can use this image 14 | 15 | How to install image: 16 | 1. Enter '/mie list' to view the list of available images 17 | 2. Choose one of them and enter '/mie place ' 18 | 2.1. You can use flags for easy installation of images (enter command without arguments ('/mie place') to get a full description) 19 | 2.2. To leave the placement mode enter '/mie exit' 20 | 3. Follow the instructions and the picture will be installed 21 | 22 | To view the full list of commands, enter '/mie' 23 | 24 | Good luck! 25 | -------------------------------------------------------------------------------- /MapImageEngine/resources/instructions/kor.txt: -------------------------------------------------------------------------------- 1 | 이 플러그인은 FaigerSYS가 작성했습니다 2 | GitHub 페이지: https://github.com/FaigerSYS/MapImageEngine 3 | 4 | 버그를 발견하면 제가 알 수 있게 해주세요! GitHub에 새 이슈를 제출하세요 5 | 6 | 자신의 이미지 제작 방법: 7 | 1. 이미지 파일을 변환하세요('png', 'jpg' 기타 등등)... 8 | - MIE_Convert 플러그인 사용: https://github.com/FaigerSYS/MapImageEngine/tree/master/MIE_Converter 9 | - 온라인 변환기 사용(플러그인 미작동 시): https://faigersys.github.io/mie-converter 10 | 1.1 이미지 크기는 128x128로 변경하지 않아도 됩니다. 변환기가 크기를 변경합니다 11 | 1.2 이미지는 나누지 않아도 됩니다. 블록으로 필요한 크기를 선택하기만 하면 됩니다 12 | 2. 변환된 이미지 파일을 이 플러그인의 'images' 폴더에 넣어주세요 13 | 3. 완료! 이제 이 이미지를 사용할 수 있습니다 14 | 15 | 이미지 설치 방법: 16 | 1.'/mie list'를 사용해 사용할 수 있는 이미지 파일 목록을 찾아주세요 17 | 2. 이 중 하나를 선택해서 '/mie place <이미지 이름>'을 입력해주세요 18 | 2.1 이미지의 쉬운 설치를 위해 플래그를 사용할 수 있습니다(전체 설명을 보려면 ('/mie place')를 인수 없이 입력하세요) 19 | 2.2 배치 모드를 마치려면 '/mie exit'를 입력하세요 20 | 3. 설명을 따르면 이미지가 설치됩니다 21 | 22 | 명령어의 모든 목록을 보려면 '/mie'를 입력해주세요 23 | 24 | 행운을 빕니다! 25 | -------------------------------------------------------------------------------- /MapImageEngine/resources/instructions/rus.txt: -------------------------------------------------------------------------------- 1 | Этот плагин создан FaigerSYS 2 | 3 | Если вы заметили какой-либо баг - сразу сообщайте 4 | 5 | Как сделать свою картинку: 6 | 1. Конвертируйте любую картинку ('png', 'jpg' и т.д.)... 7 | - Используя плагин MIE_Converter: https://github.com/FaigerSYS/MapImageEngine/tree/master/MIE_Converter 8 | - Используя онлайн-конвертер (если плагин не сработал): https://faigersys.github.io/mie-converter 9 | 1.1. Не обязательно изменять размер картинки именно 128x128 . Сайт всё сделает сам! 10 | 1.2. Не обязательно резать картинку на несколько частей. В конвертере уже предоставлены средства для этого 11 | 2. Конвертинованную картинку переместите в папку 'images' этого плагина 12 | 3. Готово! Теперь эту картинку можно использовать в плагине 13 | 14 | Как установить картинку: 15 | 1. Введите '/mie list' для получения списка доступных картинок 16 | 2. Выберите одну из них и введите команду '/mie place <название>' 17 | 2.1. Для удобства установки картинки вы можете использовать флаги (введите команду без аргументов ('/mie place') для их полного описания) 18 | 2.2. Для выхода из режима установки картинки введите '/mie exit' 19 | 3. Следуйте инструкции что вы увидите после введения команды и картинка будет установлена 20 | 21 | Полный список команд доступен по команде '/mie' 22 | 23 | Приятного использования! 24 | -------------------------------------------------------------------------------- /MapImageEngine/resources/strings/ell.ini: -------------------------------------------------------------------------------- 1 | ; Translated by nikoskon 2 | 3 | plugin-loader.loading="Ενεργοποίηση MapImageEngine..." 4 | plugin-loader.reloading="Επαναφόρτωση MapImageEngine..." 5 | plugin-loader.info-instruction="Ειδοποίηση #1: Bρείτε τις οδηγίες στον φάκελο του plugin" 6 | plugin-loader.info-long-loading="Ειδοποίηση #2: μετά την εγκατάσταση νέων εικόνων, η ενεργοποίηση μπορεί να διαρκέσει περισσότερο από το συνηθισμένο, αλλά μετά θα είναι ως συνήθως" 7 | plugin-loader.loaded="Το MapImageEngine ενεργοποιήθηκε!" 8 | plugin-loader.reloaded="Το MapImageEngine επαναφορτώθηκε!" 9 | 10 | image-loader.prefix="Η εικόνα \"%s\": " 11 | image-loader.err-name-exists="ίδιο όνομα έχει ήδη καταχωριστεί" 12 | image-loader.err-image-exists="η εικόνα υπάρχει ήδη" 13 | image-loader.err-corrupted="το αρχείο είναι κατεστραμμένο" 14 | ; image-loader.err-unsupported-api="Το API δεν είναι συμβατό. Ενημερώστε την εικόνα σας" 15 | 16 | command.desc="Εντολή MapImageEngine" 17 | command.desc.list="λάβετε μια λίστα με τις διαθέσιμες εικόνες" 18 | command.desc.place="μπείτε σε λειτουργία τοποθέτησης εικόνας" 19 | command.desc.exit="βγείται απο λειτουργία τοποθέτησης εικόνας" 20 | 21 | command.usage="Χρήση: " 22 | 23 | command.in-game="Εκτελέστε αυτήν την εντολή στο παιχνίδι" 24 | 25 | command.list="Διαθέσιμες εικόνες: " 26 | command.list.no-images="Δεν έχετε ακόμα εικόνες" 27 | 28 | command.place.usage="<όνομα εικόνας> [σημαίες]" 29 | command.place.usage.flags="Διαθέσιμες σημαίες: " 30 | command.place.usage.flags.pretty="Εμφάνιση τετραγώνων για βολικό προσανατολισμό" 31 | command.place.usage.flags.auto="Αυτόματη λειτουργία (τοποθετήστε μια εικόνα σε δύο κλικ)" 32 | command.place.not-found="Η εικόνα \"%s\" δεν βρέθηκε!" 33 | command.place.placing="Τοποθέτιση εικόνας!" 34 | command.place.click-top-left="Κάντε κλικ στην επάνω αριστερή γωνία ..." 35 | command.place.click-bottom-right="Κάντε κλικ στην κάτω δεξιά γωνία ..." 36 | command.place.placing-info="Η τοποθέτηση ξεκινά από την πάνω αριστερή γωνία (x * y)" 37 | command.place.click="Κάντε κλικ στο πλαίσιο (item frame) (%d * %d)" 38 | command.place.not-frame="Αυτό δεν είναι ένα πλαίσιο (item frame)!" 39 | command.place.width-not-match="Το πλάτος δεν ταιριάζει με το πλάτος της εικόνας!" 40 | command.place.height-not-match="Το ύψος δεν ταιριάζει με το ύψος της εικόνας!" 41 | command.place.invalid-upper-corner="Η επάνω γωνία δεν μπορεί να είναι πιο χαμηλά από την κάτω γωνία!" 42 | command.place.no-frames="Πρέπει να τοποθετήσετε τα πλαίσια (item frames) στην επιλεγμένη περιοχή! Τοποθετήστε όλα τα πλαίσια και δοκιμάστε ξανά" 43 | command.place.not-flat="Η επιλεγμένη περιοχή πρέπει να είναι επίπεδη!" 44 | command.place.success="Η εικόνα τοποθετήθηκε με επιτυχία ;)" 45 | 46 | command.exit="Φύγατε τη λειτουργία τοποθέτησης" 47 | command.exit.not-allowed="Πρέπει να εισάγετε τον τρόπο τοποθέτησης πριν φύγετε" 48 | -------------------------------------------------------------------------------- /MapImageEngine/resources/strings/eng.ini: -------------------------------------------------------------------------------- 1 | plugin-loader.loading="MapImageEngine enabling..." 2 | plugin-loader.reloading="MapImageEngine reloading..." 3 | plugin-loader.info-instruction="Notice #1: find the instructions in the plugin folder" 4 | plugin-loader.info-long-loading="Notice #2: after installing new images or updating plugin enabling may take longer, but then it will be as usual" 5 | plugin-loader.info-1.1-update="Notice #3: after installing MapImageEngine v1.1 you must place all your previously placed images again" 6 | plugin-loader.loaded="MapImageEngine enabled!" 7 | plugin-loader.reloaded="MapImageEngine reloaded!" 8 | 9 | image-loader.prefix="Image \"%s\": " 10 | image-loader.success="image loaded" 11 | image-loader.err-name-exists="similar name already registered" 12 | image-loader.err-image-exists="image already exists" 13 | image-loader.err-corrupted="file corrupted" 14 | image-loader.err-unsupported-api="API not supported. Please update your plugin to the latest version (and convert image again if didn't help)" 15 | image-loader.converted="image was converted to new format! You can find the original in the 'images/old_images' folder" 16 | image-loader.not-converted="image cannot be converted to new format! Convert the original image again using the online converter or MIE_Converter plugin" 17 | image-loader.cache-not-supported="It seems our custom packet for caching support is incompatible with your server software. Loading images may be slower than usual" 18 | 19 | command.desc="MapImageEngine command" 20 | command.desc.list="get list of available images" 21 | command.desc.place="enter placement mode" 22 | command.desc.exit="leave placement mode" 23 | 24 | command.usage="Usage: " 25 | 26 | command.in-game="Run this command in-game" 27 | 28 | command.list="Available images: " 29 | command.list.no-images="You do not have any images yet" 30 | 31 | command.place.usage=" [flags]" 32 | command.place.usage.flags="Available flags: " 33 | command.place.usage.flags.pretty="Show squares for convenient orientation" 34 | command.place.usage.flags.auto="Automatic mode (place an image in two clicks)" 35 | command.place.not-found="Image \"%s\" not found!" 36 | command.place.placing="Placing image!" 37 | command.place.click-top-left="Click on the top left corner..." 38 | command.place.click-bottom-right="Click on the bottom right corner..." 39 | command.place.placing-info="Placement starts from the top left corner (x * y)" 40 | command.place.click="Click on the frame (%d * %d)" 41 | command.place.not-frame="This is not a frame!" 42 | command.place.width-not-match="Width does not match the width of the image!" 43 | command.place.height-not-match="Height does not match the height of the image!" 44 | command.place.invalid-upper-corner="The upper corner cannot be lower than the bottom corner!" 45 | command.place.no-frames="You must place the frames in the selected area! Place all frames and try again" 46 | command.place.not-flat="The selected area must be flat!" 47 | command.place.success="Image succesfully placed ;)" 48 | 49 | command.exit="You left the placement mode" 50 | command.exit.not-allowed="You must enter placement mode before leaving it" 51 | -------------------------------------------------------------------------------- /MapImageEngine/resources/strings/kor.ini: -------------------------------------------------------------------------------- 1 | plugin-loader.loading="MapImageEngine 활성화 중..." 2 | plugin-loader.reloading="MapImageEngine 새로 고침 중..." 3 | plugin-loader.info-instruction="알림 #1: 플러그인 폴더에서 사용 방법을 확인하세요" 4 | plugin-loader.info-long-loading="알림 #2: 새로운 이미지를 설치하거나 플러그인을 업데이트하면 활성화하는 데 오래 걸릴 수 있으나, 그다음부터는 평소처럼 활성화될 것입니다" 5 | plugin-loader.info-1.1-update="알림 #3: MapImageEngine v1.1을 설치한 후 이전에 배치한 모든 이미지를 다시 배치해야 합니다" 6 | plugin-loader.loaded="MapImageEngine이 활성화되었습니다!" 7 | plugin-loader.reloaded="MapImageEngine이 새로 고침 되었습니다!" 8 | 9 | image-loader.prefix="이미지 \"%s\": " 10 | image-loader.success="이미지가 로드되었습니다" 11 | image-loader.err-name-exists="비슷한 이름이 이미 존재합니다" 12 | image-loader.err-image-exists="이미지가 이미 존재합니다" 13 | image-loader.err-corrupted="파일이 손상되었습니다" 14 | image-loader.err-unsupported-api="API가 지원되지 않습니다. 플러그인을 최신 버전으로 업데이트 해주세요 (그리고 도움이 되지 않았다면 이미지를 다시 변환하세요)" 15 | image-loader.converted="이미지가 새로운 형식으로 변환되었습니다! 'images/old_images' 폴더에서 원본을 찾을 수 있습니다" 16 | image-loader.not-converted="이미지가 새로운 형식으로 변환될 수 없습니다! 온라인 변환기나 MIE_Converter 플러그인을 사용해 이미지를 다시 변환하세요" 17 | 18 | command.desc="MapImageEngine 명령어" 19 | command.desc.list="사용할 수 있는 이미지의 목록을 가져옵니다" 20 | command.desc.place="배치 모드로 전환합니다" 21 | command.desc.exit="배치 모드를 종료합니다" 22 | 23 | command.usage="사용법: " 24 | 25 | command.in-game="게임 안에서 실행해야 합니다" 26 | 27 | command.list="사용할 수 있는 이미지: " 28 | command.list.no-images="사용할 수 있는 이미지가 없습니다" 29 | 30 | command.place.usage="<이미지 이름> [플래그]" 31 | command.place.usage.flags="사용할 수 있는 플래그: " 32 | command.place.usage.flags.pretty="간편한 방향을 위해 사각형을 보여줍니다" 33 | command.place.usage.flags.auto="자동 모드(두번 클릭해 이미지를 배치합니다)" 34 | command.place.not-found="이미지 \"%s\"이(가) 존재하지 않습니다!" 35 | command.place.placing="이미지 배치 중!" 36 | command.place.click-top-left="왼쪽 상단 구석을 선택하세요..." 37 | command.place.click-bottom-right="오른쪽 하단 구석을 선택하세요..." 38 | command.place.placing-info="배치는 왼쪽 상단 구석에서부터 시작합니다(x * y)" 39 | command.place.click="액자를 클릭하세요(%d * %d)" 40 | command.place.not-frame="액자가 아닙니다!" 41 | command.place.width-not-match="너비가 이미지의 너비와 맞지 않습니다!" 42 | command.place.height-not-match="높이가 이미지의 높이와 맞지 않습니다!" 43 | command.place.invalid-upper-corner="위에 있는 구석이 아래에 있는 구석보다 낮을 수 없습니다!" 44 | command.place.no-frames="선택된 영역에 액자를 배치해야 합니다! 모든 액자를 배치하고 다시 시도하세요" 45 | command.place.not-flat="선택된 지역은 평평해야 합니다!" 46 | command.place.success="이미지가 성공적으로 배치되었습니다 ;)" 47 | 48 | command.exit="배치 모드를 나갔습니다" 49 | command.exit.not-allowed="배치 모드를 나가기 전에 이미 배치 모드에 진입해야 합니다" 50 | -------------------------------------------------------------------------------- /MapImageEngine/resources/strings/rus.ini: -------------------------------------------------------------------------------- 1 | plugin-loader.loading="MapImageEngine загружается..." 2 | plugin-loader.reloading="MapImageEngine перезагружается..." 3 | plugin-loader.info-instruction="Напоминание #1: вы можете найти инструкцию в папке плагина" 4 | plugin-loader.info-long-loading="Напоминание #2: После добавления новых картинок или обновления плагина загрузка может идти дольше, но потом будет как обычно" 5 | plugin-loader.info-1.1-update="Напоминание #3: после установки MapImageEngine v1.1 вы должны снова установить на рамки все ранее установленные картинки" 6 | plugin-loader.loaded="MapImageEngine загружен!" 7 | plugin-loader.reloaded="MapImageEngine перезагружен!" 8 | 9 | image-loader.prefix="Картинка \"%s\": " 10 | image-loader.success="картинка загружена" 11 | image-loader.err-name-exists="подобное имя уже было зарегестрировано" 12 | image-loader.err-image-exists="такая картинка уже существует" 13 | image-loader.err-corrupted="файл поврежден" 14 | image-loader.err-unsupported-api="версия API не поддерживается. Обновите плагин до последней версии (и конвертируйте картинку снова если не помогло)" 15 | image-loader.converted="картинка была конвертирована в новый формат! Вы можете найти старую в папке './images/old_images'" 16 | image-loader.not-converted="картинка не была конвертирована в новый формат! Конвертируйте её оригинал снова используя онлайн-конвертер или плагин" 17 | 18 | command.desc="Команда MapImageEngine" 19 | command.desc.list="получить список доступных картинок" 20 | command.desc.place="ввойти в режим установки картинки" 21 | command.desc.exit="выйти с режима установки картинки" 22 | 23 | command.usage="Использование: " 24 | 25 | command.in-game="Выполните эту команду в игре" 26 | 27 | command.list="Доступные картинки: " 28 | command.list.no-images="У вас ещё нету каких-либо картинок" 29 | 30 | command.place.usage="<название> [флаги]" 31 | command.place.usage.flags="Доступные флаги: " 32 | command.place.usage.flags.pretty=" показывать квадраты для удобной ориентации по картинке" 33 | command.place.usage.flags.auto=" автоматический режим (установка картинки в два клика)" 34 | command.place.not-found="Картинка \"%s\" не найдена!" 35 | command.place.placing="Идёт установка изображения!" 36 | command.place.click-top-left="Нажмите на левый верхний угол..." 37 | command.place.click-bottom-right="Нажмите на правый нижний угол..." 38 | command.place.placing-info="Установка начинается с левого верхнего угла (x * y)" 39 | command.place.click="Нажмите на рамку (%d * %d)" 40 | command.place.not-frame="Это не рамка!" 41 | command.place.width-not-match="Ширина не совпадает с высотой картинки!" 42 | command.place.height-not-match="Высота не совпадает с высотой картинки!" 43 | command.place.invalid-upper-corner="Верхний угол не может быть ниже нижнего угла!" 44 | command.place.no-frames="Вы должны установить в выбранной области рамки! Установите все и попробуйте ещё раз" 45 | command.place.not-flat="Выбранная область должна быть плоской!" 46 | command.place.success="Картинка успешно установлена ;)" 47 | 48 | command.exit="Вы вышли из режима установки" 49 | command.exit.not-allowed="Вы должны быть в режиме установки чтобы выйти с него" 50 | -------------------------------------------------------------------------------- /MapImageEngine/src/FaigerSYS/MapImageEngine/MapImageEngine.php: -------------------------------------------------------------------------------- 1 | getLogger()->info(CLR::GOLD . TS::translate($is_reload ? 'plugin-loader.reloading' : 'plugin-loader.loading')); 50 | $this->getLogger()->info(CLR::AQUA . TS::translate('plugin-loader.info-instruction')); 51 | $this->getLogger()->info(CLR::AQUA . TS::translate('plugin-loader.info-long-loading')); 52 | $this->getLogger()->info(CLR::AQUA . TS::translate('plugin-loader.info-1.1-update')); 53 | 54 | if ($is_reload) { 55 | $this->storage = $old_plugin->storage; 56 | } 57 | 58 | $this->getServer()->getPluginManager()->registerEvents($this, $this); 59 | 60 | @mkdir($path = $this->getDataFolder()); 61 | 62 | @mkdir($dir = $path . 'instructions/'); 63 | foreach (scandir($r_dir = $this->getFile() . '/resources/instructions/') as $file) { 64 | if ($file[0] !== '.') { 65 | copy($r_dir . $file, $dir . $file); 66 | } 67 | } 68 | 69 | @mkdir($path . 'images'); 70 | @mkdir($path . 'images/old_files'); 71 | @mkdir($path . 'cache'); 72 | 73 | if (self::$is_custom_pk_suppoted === null) { 74 | self::$is_custom_pk_suppoted = CustomClientboundMapItemDataPacket::checkCompatiblity(); 75 | } 76 | 77 | $this->loadImages($is_reload); 78 | 79 | $this->getServer()->getCommandMap()->register('mapimageengine', new MapImageEngineCommand()); 80 | 81 | ItemFactory::registerItem(new FilledMap(), true); 82 | 83 | $this->getLogger()->info(CLR::GOLD . TS::translate($is_reload ? 'plugin-loader.reloaded' : 'plugin-loader.loaded')); 84 | } 85 | 86 | private function loadImages(bool $is_reload = false) { 87 | $path = $this->getDataFolder() . 'images/'; 88 | $storage = $this->storage ?? new ImageStorage; 89 | 90 | $files = array_filter( 91 | scandir($path), 92 | function ($file) use ($path) { 93 | return is_file($path . $file) && substr($file, -5, 5) === '.miei'; 94 | } 95 | ); 96 | 97 | $old_files_path = $path . 'old_files/'; 98 | $old_files = array_filter( 99 | scandir($path), 100 | function ($file) use ($path) { 101 | return is_file($path . $file) && substr($file, -4, 4) === '.mie'; 102 | } 103 | ); 104 | foreach ($old_files as $old_file) { 105 | $new_data = OldFormatConverter::tryConvert(file_get_contents($path . $old_file)); 106 | if ($new_data !== null) { 107 | $this->getLogger()->notice(TS::translate('image-loader.prefix', $old_file) . TS::translate('image-loader.converted')); 108 | 109 | $basename = pathinfo($old_file, PATHINFO_BASENAME); 110 | $new_path = $old_files_path . $basename; 111 | $i = 0; 112 | while (file_exists($new_path)) { 113 | $new_path = $old_files_path . $basename . '.' . ++$i; 114 | } 115 | rename($path . $old_file, $new_path); 116 | 117 | $filename = pathinfo($old_file, PATHINFO_FILENAME); 118 | $extension = '.miei'; 119 | $new_file = $filename . $extension; 120 | $i = 0; 121 | while (file_exists($path . $new_file)) { 122 | $new_file = $filename . '_' . ++$i . $extension; 123 | } 124 | file_put_contents($path . $new_file, $new_data); 125 | 126 | unset($new_data); 127 | 128 | $files[] = $new_file; 129 | } else { 130 | $this->getLogger()->warning(TS::translate('image-loader.prefix', $old_file) . TS::translate('image-loader.not-converted')); 131 | } 132 | } 133 | 134 | if (!self::isCustomPacketSupported()) { 135 | $this->getLogger()->warning(TS::translate('image-loader.cache-not-supported')); 136 | } 137 | 138 | foreach ($files as $file) { 139 | $image = MapImage::fromBinary(file_get_contents($path . $file), $state); 140 | if ($image !== null) { 141 | $name = substr($file, 0, -5); 142 | $state = $storage->registerImage($image, true, $name); 143 | switch ($state) { 144 | case ImageStorage::R_OK: 145 | $this->getLogger()->info(CLR::GREEN . TS::translate('image-loader.prefix', $file) . TS::translate('image-loader.success')); 146 | break; 147 | 148 | case ImageStorage::R_UUID_EXISTS: 149 | !$is_reload && $this->getLogger()->info(TS::translate('image-loader.prefix', $file) . TS::translate('image-loader.err-image-exists')); 150 | break; 151 | 152 | case ImageStorage::R_NAME_EXISTS: 153 | case ImageStorage::R_INVALID_NAME: 154 | $this->getLogger()->warning(TS::translate('image-loader.prefix', $file) . TS::translate('image-loader.err-name-exists')); 155 | break; 156 | } 157 | } else { 158 | switch ($state) { 159 | case MapImage::R_CORRUPTED: 160 | $this->getLogger()->warning(TS::translate('image-loader.prefix', $file) . TS::translate('image-loader.err-corrupted')); 161 | break; 162 | 163 | case MapImage::R_UNSUPPORTED_API: 164 | $this->getLogger()->warning(TS::translate('image-loader.prefix', $file) . TS::translate('image-loader.err-unsupported-api')); 165 | break; 166 | } 167 | } 168 | } 169 | 170 | $this->storage = $storage; 171 | } 172 | 173 | public function getImageStorage() : ImageStorage { 174 | return $this->storage; 175 | } 176 | 177 | /** 178 | * @ignoreCancelled true 179 | */ 180 | public function onRequest(DataPacketReceiveEvent $e) { 181 | if ($e->getPacket() instanceof MapInfoRequestPacket) { 182 | $pk = $this->getImageStorage()->getCachedPacket($e->getPacket()->mapId); 183 | if ($pk !== null) { 184 | $e->getPlayer()->dataPacket($pk); 185 | } 186 | $e->setCancelled(true); 187 | } 188 | } 189 | 190 | /** 191 | * @priority LOW 192 | */ 193 | public function onChunkLoad(ChunkLoadEvent $e) { 194 | foreach ($e->getChunk()->getTiles() as $frame) { 195 | if ($frame instanceof ItemFrame) { 196 | $item = $frame->getItem(); 197 | if ($item instanceof FilledMap) { 198 | $frame->setItem($item); 199 | } 200 | } 201 | } 202 | } 203 | 204 | public static function getInstance() : MapImageEngine { 205 | return self::$instance; 206 | } 207 | 208 | public static function isCustomPacketSupported() : bool { 209 | return self::$is_custom_pk_suppoted; 210 | } 211 | 212 | } 213 | -------------------------------------------------------------------------------- /MapImageEngine/src/FaigerSYS/MapImageEngine/TranslateStrings.php: -------------------------------------------------------------------------------- 1 | getLanguage()->getLang(); 15 | $owner = MapImageEngine::getInstance(); 16 | 17 | $default_strings = parse_ini_string(stream_get_contents($owner->getResource('strings/' . self::DEFAULT_LANG . '.ini'))); 18 | if ($strings = $owner->getResource('strings/' . $lang . '.ini')) { 19 | $strings = parse_ini_string(stream_get_contents($strings)) + $default_strings; 20 | } else { 21 | $strings = $default_strings; 22 | } 23 | 24 | self::$strings = $strings; 25 | } 26 | 27 | public static function translate(string $str, ...$args) { 28 | return sprintf(self::$strings[$str] ?? $str, ...$args); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /MapImageEngine/src/FaigerSYS/MapImageEngine/command/MapImageEngineCommand.php: -------------------------------------------------------------------------------- 1 | getPlugin()->getServer()->getPluginManager()->registerEvents($this, $this->getPlugin()); 36 | 37 | parent::__construct('mapimageengine', TS::translate('command.desc'), null, ['mie']); 38 | $this->setPermission('mapimageengine'); 39 | } 40 | 41 | public function execute(CommandSender $sender, string $label, array $args) { 42 | if (!$this->testPermission($sender)) { 43 | return; 44 | } 45 | 46 | if (!$sender instanceof Player) { 47 | $sender->sendMessage(self::MSG_PREFIX . TS::translate('command.in-game')); 48 | return; 49 | } 50 | 51 | $cmd = array_shift($args); 52 | switch ($cmd) { 53 | case 'list': 54 | $list = $this->getPlugin()->getImageStorage()->getNamedImages(); 55 | if (empty($list)) { 56 | $sender->sendMessage(self::MSG_PREFIX . TS::translate('command.list.no-images')); 57 | } else { 58 | $new_list = []; 59 | foreach ($list as $name => $image) { 60 | $w = $image->getBlocksWidth(); 61 | $h = $image->getBlocksHeight(); 62 | 63 | $new_list[] = $name . CLR::RESET . ' ' . CLR::AQUA . '(' . CLR::DARK_GREEN . $w . CLR::AQUA . 'x'. CLR::DARK_GREEN . $h . CLR::AQUA . ')'; 64 | } 65 | 66 | $list = CLR::WHITE . CLR::ITALIC . implode(CLR::GRAY . ', ' . CLR::WHITE . CLR::ITALIC, $new_list) . CLR::GRAY; 67 | $sender->sendMessage(self::MSG_PREFIX . TS::translate('command.list') . $list); 68 | } 69 | break; 70 | 71 | case 'place': 72 | $image_name = (string) array_shift($args); 73 | if (!strlen($image_name)) { 74 | $sender->sendMessage(self::MSG_PREFIX . TS::translate('command.usage') . '/' . $label . ' place ' . TS::translate('command.place.usage')); 75 | $sender->sendMessage(CLR::GRAY . TS::translate('command.place.usage.flags')); 76 | $sender->sendMessage(CLR::GRAY . ' pretty - ' . TS::translate('command.place.usage.flags.pretty')); 77 | $sender->sendMessage(CLR::GRAY . ' auto - ' . TS::translate('command.place.usage.flags.auto')); 78 | } else { 79 | $image = $this->getPlugin()->getImageStorage()->getImageByName($image_name); 80 | if (!$image) { 81 | $sender->sendMessage(self::MSG_PREFIX . TS::translate('command.place.not-found', $image_name)); 82 | } else { 83 | $this->cache[$sender->getName()] = [ 84 | 'image_hash' => $image->getHashedUUID(), 85 | 'pretty' => in_array('pretty', $args), 86 | 'auto' => in_array('auto', $args), 87 | 'placed' => 0, 88 | 'x_count' => $image->getBlocksWidth(), 89 | 'y_count' => $image->getBlocksHeight() 90 | ]; 91 | 92 | $this->processPlaceMessage($sender); 93 | } 94 | } 95 | break; 96 | 97 | case 'exit': 98 | $name = $sender->getName(); 99 | if (isset($this->cache[$name])) { 100 | unset($this->cache[$name]); 101 | $sender->sendMessage(self::MSG_PREFIX . TS::translate('command.exit')); 102 | } else { 103 | $sender->sendMessage(self::MSG_PREFIX . TS::translate('command.exit.not-allowed')); 104 | } 105 | break; 106 | 107 | default: 108 | $sender->sendMessage(self::MSG_PREFIX . TS::translate('command.usage')); 109 | $sender->sendMessage(CLR::GRAY . ' /' . $label . ' list - ' . TS::translate('command.desc.list')); 110 | $sender->sendMessage(CLR::GRAY . ' /' . $label . ' place - ' . TS::translate('command.desc.place')); 111 | $sender->sendMessage(CLR::GRAY . ' /' . $label . ' exit - ' . TS::translate('command.desc.exit')); 112 | } 113 | } 114 | 115 | /** 116 | * @priority LOW 117 | * @ignoreCancelled true 118 | */ 119 | public function onTouch(PlayerInteractEvent $e) { 120 | if ($e->getAction() !== PlayerInteractEvent::RIGHT_CLICK_BLOCK) { 121 | return; 122 | } 123 | 124 | $player = $e->getPlayer(); 125 | $name = $player->getName(); 126 | 127 | if (isset($this->cache[$name])) { 128 | $block = $e->getBlock(); 129 | $level = $block->getLevel(); 130 | 131 | $frame = $level->getTile($block); 132 | if (!($frame instanceof ItemFrame)) { 133 | $player->sendMessage(self::MSG_PREFIX . TS::translate('command.place.not-frame')); 134 | } else { 135 | $data = &$this->cache[$name]; 136 | 137 | if ($data['auto']) { 138 | if (!isset($data['p1'])) { 139 | $data['p1'] = $block; 140 | $this->processPlaceMessage($player); 141 | } else { 142 | $p1 = $data['p1']; 143 | $p2 = $block; 144 | 145 | $x1 = $p1->getX(); 146 | $y1 = $p1->getY(); 147 | $z1 = $p1->getZ(); 148 | $x2 = $p2->getX(); 149 | $y2 = $p2->getY(); 150 | $z2 = $p2->getZ(); 151 | 152 | if ($y1 < $y2) { 153 | $player->sendMessage(self::MSG_PREFIX . TS::translate('command.place.invalid-upper-corner')); 154 | } else if ($y1 - $y2 + 1 !== $data['y_count']) { 155 | $player->sendMessage(self::MSG_PREFIX . TS::translate('command.place.height-not-match')); 156 | } else { 157 | $x = $x1; 158 | $z = $z1; 159 | $a = null; 160 | if ($x1 === $x2) { 161 | $a = &$z; 162 | $from = $z1; 163 | $to = $z2; 164 | } else if ($z1 === $z2) { 165 | $a = &$x; 166 | $from = $x1; 167 | $to = $x2; 168 | } else { 169 | $player->sendMessage(self::MSG_PREFIX . TS::translate('command.place.not-flat')); 170 | } 171 | 172 | if (abs($to - $from) + 1 !== $data['x_count']) { 173 | $player->sendMessage(self::MSG_PREFIX . TS::translate('command.place.width-not-match')); 174 | } else if ($a !== null) { 175 | $x_b = -1; 176 | for ($a = $from; $from < $to ? $a <= $to : $a >= $to; $from < $to ? $a++ : $a--) { 177 | $y_b = -1; 178 | $x_b++; 179 | for ($y = $y1; $y >= $y2; $y--) { 180 | $y_b++; 181 | 182 | $frame = $level->getTile(new Vector3($x, $y, $z)); 183 | if (!($frame instanceof ItemFrame)) { 184 | $player->sendMessage(self::MSG_PREFIX . TS::translate('command.place.no-frames')); 185 | break 2; 186 | } 187 | 188 | $map = new FilledMap(); 189 | $map->setImageData($data['image_hash'], $x_b, $y_b); 190 | 191 | $frame->setItem($map); 192 | } 193 | } 194 | $player->sendMessage(self::MSG_PREFIX . TS::translate('command.place.success')); 195 | } 196 | } 197 | 198 | unset($this->cache[$name]); 199 | } 200 | } else { 201 | $x = $data['placed'] % $data['x_count']; 202 | $y = floor($data['placed'] / $data['x_count']); 203 | 204 | $map = new FilledMap(); 205 | $map->setImageData($data['image_hash'], $x, $y); 206 | 207 | $frame->setItem($map); 208 | 209 | if (++$data['placed'] === ($data['x_count'] * $data['y_count'])) { 210 | $player->sendMessage(self::MSG_PREFIX . TS::translate('command.place.success')); 211 | unset($this->cache[$name]); 212 | } else { 213 | $this->processPlaceMessage($player); 214 | } 215 | } 216 | } 217 | 218 | $e->setCancelled(true); 219 | } 220 | } 221 | 222 | public function onQuit(PlayerQuitEvent $e) { 223 | unset($this->cache[$e->getPlayer()->getName()]); 224 | } 225 | 226 | private function processPlaceMessage(Player $player) { 227 | $name = $player->getName(); 228 | $data = &$this->cache[$name]; 229 | 230 | $player->sendMessage(''); 231 | $player->sendMessage(self::MSG_PREFIX . TS::translate('command.place.placing')); 232 | 233 | if ($data['auto']) { 234 | if (!isset($data['p1'])) { 235 | $player->sendMessage(CLR::GRAY . TS::translate('command.place.click-top-left')); 236 | } else { 237 | $player->sendMessage(CLR::GRAY . TS::translate('command.place.click-bottom-right')); 238 | } 239 | } else { 240 | $player->sendMessage(CLR::GRAY . TS::translate('command.place.placing-info')); 241 | 242 | $x = (int) $data['placed'] % $data['x_count']; 243 | $y = (int) ($data['placed'] / $data['x_count']); 244 | 245 | if ($data['pretty']) { 246 | $block = "\xe2\xac\x9b"; 247 | 248 | for ($y_b = 0; $y_b < $data['y_count']; $y_b++) { 249 | $line = CLR::WHITE; 250 | for ($x_b = 0; $x_b < $data['x_count']; $x_b++) { 251 | $line .= ($x_b === $x && $y_b === $y) ? CLR::GREEN . $block . CLR::WHITE : $block; 252 | } 253 | 254 | $player->sendMessage($line); 255 | } 256 | } 257 | 258 | $player->sendMessage(CLR::GRAY . TS::translate('command.place.click', $x + 1, $y + 1)); 259 | } 260 | } 261 | 262 | public function getPlugin() : Plugin { 263 | return MapImageEngine::getInstance(); 264 | } 265 | 266 | } 267 | -------------------------------------------------------------------------------- /MapImageEngine/src/FaigerSYS/MapImageEngine/item/FilledMap.php: -------------------------------------------------------------------------------- 1 | updateMapData(); 23 | 24 | return $this; 25 | } 26 | 27 | public function setNamedTag(CompoundTag $tag) : Item { 28 | parent::setNamedTag($tag); 29 | $this->updateMapData(); 30 | 31 | return $this; 32 | } 33 | 34 | protected function updateMapData() { 35 | $plugin = MapImageEngine::getInstance(); 36 | if (!$plugin) { 37 | return; 38 | } 39 | 40 | $mie_data = $this->getImageData(); 41 | if (!is_array($mie_data)) { 42 | return; 43 | } 44 | 45 | $map_id = 0; 46 | 47 | $api = $mie_data['api'] ?? -1;; 48 | if (in_array($api, self::SUPPORTED_MAP_API)) { 49 | $image = $plugin->getImageStorage()->getImage($mie_data['image_hash']); 50 | if ($image) { 51 | $chunk = $image->getChunk($mie_data['x_block'], $mie_data['y_block']); 52 | if ($chunk) { 53 | $map_id = $chunk->getMapId(); 54 | } 55 | } 56 | } 57 | 58 | $tag = $this->getNamedTag(); 59 | $tag->setLong('map_uuid', $map_id, true); 60 | parent::setNamedTag($tag); 61 | } 62 | 63 | public function setImageData(string $image_hash, int $block_x, int $block_y) { 64 | $tag = $this->getNamedTag(); 65 | $tag->setString('mie_data', json_encode([ 66 | 'api' => self::CURRENT_MAP_API, 67 | 'image_hash' => $image_hash, 68 | 'x_block' => $block_x, 69 | 'y_block' => $block_y 70 | ])); 71 | parent::setNamedTag($tag); 72 | 73 | $this->updateMapData(); 74 | } 75 | 76 | public function getImageData() { 77 | $tag = $this->getNamedTag(); 78 | if ($tag->hasTag('mie_data', StringTag::class)) { 79 | return json_decode($tag->getString('mie_data'), true); 80 | } 81 | } 82 | 83 | public function getImageHash() { 84 | return $this->getImageData()['image_hash'] ?? null; 85 | } 86 | 87 | public function getImageChunkX() { 88 | return $this->getImageData()['x_block'] ?? null; 89 | } 90 | 91 | public function getImageChunkY() { 92 | return $this->getImageData()['y_block'] ?? null; 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /MapImageEngine/src/FaigerSYS/MapImageEngine/packet/CustomClientboundMapItemDataPacket.php: -------------------------------------------------------------------------------- 1 | mapId = $this->getEntityUniqueId(); 80 | $this->type = $this->getUnsignedVarInt(); 81 | $this->dimensionId = $this->getByte(); 82 | $this->isLocked = $this->getBool(); 83 | 84 | if (($this->type & 0x08) !== 0) { 85 | $count = $this->getUnsignedVarInt(); 86 | for ($i = 0; $i < $count; ++$i) { 87 | $this->eids[] = $this->getEntityUniqueId(); 88 | } 89 | } 90 | 91 | if (($this->type & (0x08 | self::BITFLAG_DECORATION_UPDATE | self::BITFLAG_TEXTURE_UPDATE)) !== 0) { 92 | $this->scale = $this->getByte(); 93 | } 94 | 95 | if (($this->type & self::BITFLAG_DECORATION_UPDATE) !== 0) { 96 | for ($i = 0, $count = $this->getUnsignedVarInt(); $i < $count; ++$i) { 97 | $object = new MapTrackedObject(); 98 | $object->type = $this->getLInt(); 99 | if ($object->type === MapTrackedObject::TYPE_BLOCK) { 100 | $this->getBlockPosition($object->x, $object->y, $object->z); 101 | } elseif ($object->type === MapTrackedObject::TYPE_ENTITY) { 102 | $object->entityUniqueId = $this->getEntityUniqueId(); 103 | } else { 104 | throw new \UnexpectedValueException("Unknown map object type"); 105 | } 106 | $this->trackedEntities[] = $object; 107 | } 108 | 109 | for ($i = 0, $count = $this->getUnsignedVarInt(); $i < $count; ++$i) { 110 | $this->decorations[$i]["rot"] = $this->getByte(); 111 | $this->decorations[$i]["img"] = $this->getByte(); 112 | $this->decorations[$i]["xOffset"] = $this->getByte(); 113 | $this->decorations[$i]["yOffset"] = $this->getByte(); 114 | $this->decorations[$i]["label"] = $this->getString(); 115 | 116 | $this->decorations[$i]["color"] = $this->getUnsignedVarInt(); 117 | } 118 | } 119 | 120 | if (($this->type & self::BITFLAG_TEXTURE_UPDATE) !== 0) { 121 | $this->width = $this->getVarInt(); 122 | $this->height = $this->getVarInt(); 123 | $this->xOffset = $this->getVarInt(); 124 | $this->yOffset = $this->getVarInt(); 125 | 126 | $count = $this->getUnsignedVarInt(); 127 | assert($count === $this->width * $this->height); 128 | 129 | $this->colors = $this->get($count); 130 | } 131 | } 132 | 133 | protected function encodePayload() : void { 134 | $this->putEntityUniqueId($this->mapId); 135 | 136 | $type = 0; 137 | if (($eidsCount = count($this->eids)) > 0) { 138 | $type |= 0x08; 139 | } 140 | if (($decorationCount = count($this->decorations)) > 0) { 141 | $type |= self::BITFLAG_DECORATION_UPDATE; 142 | } 143 | if (!empty($this->colors)) { 144 | $type |= self::BITFLAG_TEXTURE_UPDATE; 145 | } 146 | 147 | $this->putUnsignedVarInt($type); 148 | $this->putByte($this->dimensionId); 149 | $this->putBool($this->isLocked); 150 | 151 | if (($type & 0x08) !== 0) { 152 | $this->putUnsignedVarInt($eidsCount); 153 | foreach ($this->eids as $eid) { 154 | $this->putEntityUniqueId($eid); 155 | } 156 | } 157 | 158 | if (($type & (0x08 | self::BITFLAG_TEXTURE_UPDATE | self::BITFLAG_DECORATION_UPDATE)) !== 0) { 159 | $this->putByte($this->scale); 160 | } 161 | 162 | if (($type & self::BITFLAG_DECORATION_UPDATE) !== 0) { 163 | $this->putUnsignedVarInt(count($this->trackedEntities)); 164 | foreach ($this->trackedEntities as $object) { 165 | $this->putLInt($object->type); 166 | if ($object->type === MapTrackedObject::TYPE_BLOCK) { 167 | $this->putBlockPosition($object->x, $object->y, $object->z); 168 | } elseif ($object->type === MapTrackedObject::TYPE_ENTITY) { 169 | $this->putEntityUniqueId($object->entityUniqueId); 170 | } else { 171 | throw new \UnexpectedValueException("Unknown map object type"); 172 | } 173 | } 174 | 175 | $this->putUnsignedVarInt($decorationCount); 176 | foreach ($this->decorations as $decoration) { 177 | $this->putByte($decoration["rot"]); 178 | $this->putByte($decoration["img"]); 179 | $this->putByte($decoration["xOffset"]); 180 | $this->putByte($decoration["yOffset"]); 181 | $this->putString($decoration["label"]); 182 | 183 | $this->putUnsignedVarInt($decoration["color"]); 184 | } 185 | } 186 | 187 | if (($type & self::BITFLAG_TEXTURE_UPDATE) !== 0) { 188 | $this->putVarInt($this->width); 189 | $this->putVarInt($this->height); 190 | $this->putVarInt($this->xOffset); 191 | $this->putVarInt($this->yOffset); 192 | 193 | $this->putUnsignedVarInt($this->width * $this->height); 194 | 195 | $this->put($this->colors); 196 | } 197 | } 198 | 199 | public function handle(NetworkSession $session) : bool { 200 | return true; 201 | } 202 | 203 | public static function prepareColors(array $colors, int $width, int $height) { 204 | $buffer = new BinaryStream; 205 | for ($y = 0; $y < $height; $y++) { 206 | for ($x = 0; $x < $width; $x++) { 207 | $buffer->putUnsignedVarInt($colors[$y][$x]); 208 | } 209 | } 210 | return $buffer->buffer; 211 | } 212 | 213 | public static function checkCompatiblity() : bool { 214 | $original = new ClientboundMapItemDataPacket(); 215 | $custom = new CustomClientboundMapItemDataPacket(); 216 | 217 | $original->mapId = $custom->mapId = 1; 218 | $original->dimensionId = $custom->dimensionId = DimensionIds::OVERWORLD; 219 | $original->eids = $custom->eids = []; 220 | $original->scale = $custom->scale = 0; 221 | $original->trackedEntities = $custom->trackedEntities = []; 222 | $original->decorations = $custom->decorations = []; 223 | $original->width = $custom->width = 128; 224 | $original->height = $custom->height = 128; 225 | $original->xOffset = $custom->xOffset = 0; 226 | $original->yOffset = $custom->yOffset = 0; 227 | 228 | $color = new Color(0xff, 0xee, 0xdd); 229 | $original->colors = array_fill(0, 128, array_fill(0, 128, $color)); 230 | $custom->colors = str_repeat(Binary::writeUnsignedVarInt($color->toABGR()), 128 * 128); 231 | 232 | $original->encode(); 233 | $custom->encode(); 234 | return $original->buffer === $custom->buffer; 235 | } 236 | 237 | } 238 | -------------------------------------------------------------------------------- /MapImageEngine/src/FaigerSYS/MapImageEngine/storage/ImageStorage.php: -------------------------------------------------------------------------------- 1 | images[$spl_hash])) { 42 | return self::R_ALREADY_REGISTERED; 43 | } 44 | 45 | $hash = $image->getHashedUUID(); 46 | if (isset($this->hashes[$hash])) { 47 | return self::R_UUID_EXISTS; 48 | } 49 | 50 | if ($name !== null) { 51 | $name = strtr($name, ' ', '_'); 52 | if (!strlen($name)) { 53 | return self::R_INVALID_NAME; 54 | } 55 | if (isset($this->names[$name])) { 56 | return self::R_NAME_EXISTS; 57 | } 58 | $this->names[$name] = $hash; 59 | } 60 | 61 | $this->images[$spl_hash] = $image; 62 | $this->hashes[$hash] = $spl_hash; 63 | 64 | if ($cache_packets) { 65 | $this->regeneratePacketsCache($image); 66 | } 67 | 68 | return self::R_OK; 69 | } 70 | 71 | /** 72 | * Unregisters image 73 | * 74 | * @param MapImage $image 75 | */ 76 | public function unregisterImage(MapImage $image) { 77 | $spl_hash = spl_object_hash($image); 78 | if (!isset($this->images[$spl_hash])) { 79 | return; 80 | } 81 | $hash = $image->getHashedUUID(); 82 | 83 | $this->removePacketsCache($image); 84 | 85 | foreach ($this->names as $name => $o_spl_hash) { 86 | if ($spl_hash === $o_spl_hash) { 87 | unset($this->names[$name]); 88 | } 89 | } 90 | foreach ($this->names as $name => $o_spl_hash) { 91 | if ($spl_hash === $o_spl_hash) { 92 | unset($this->names[$name]); 93 | } 94 | } 95 | unset($this->hashes[$hash]); 96 | unset($this->images[$spl_hash]); 97 | } 98 | 99 | /** 100 | * Regenerates map image packets cache 101 | * 102 | * @param MapImage $image 103 | * @param int $chunk_x 104 | * @param int $chunk_y 105 | */ 106 | public function regeneratePacketsCache(MapImage $image = null, int $chunk_x = null, int $chunk_y = null) { 107 | if ($image === null) { 108 | $this->cache = []; 109 | foreach ($this->images as $image) { 110 | $this->regeneratePacketsCache($image); 111 | } 112 | } else { 113 | if (!isset($this->images[spl_object_hash($image)])) { 114 | return; 115 | } 116 | 117 | if ($chunk_x === null && $chunk_y === null) { 118 | foreach ($image->getChunks() as $chunks) { 119 | foreach ($chunks as $chunk) { 120 | $pk = new BatchPacket(); 121 | $pk->setCompressionLevel(7); 122 | if (MapImageEngine::isCustomPacketSupported()) { 123 | $pk->addPacket($chunk->generateCustomMapImagePacket()); 124 | } else { 125 | $pk->addPacket($chunk->generateMapImagePacket()); 126 | } 127 | $pk->encode(); 128 | $this->packet_cache[$chunk->getMapId()] = $pk; 129 | } 130 | } 131 | } else { 132 | $chunk = $image->getChunk($chunk_x, $chunk_y); 133 | if ($chunk !== null) { 134 | $pk = new BatchPacket(); 135 | $pk->setCompressionLevel(7); 136 | if (MapImageEngine::isCustomPacketSupported()) { 137 | $pk->addPacket($chunk->generateCustomMapImagePacket()); 138 | } else { 139 | $pk->addPacket($chunk->generateMapImagePacket()); 140 | } 141 | $pk->encode(); 142 | $this->packet_cache[$chunk->getMapId()] = $pk; 143 | } 144 | } 145 | } 146 | } 147 | 148 | /** 149 | * Removes map image packets from cache 150 | * 151 | * @param MapImage $image 152 | * @param int $chunk_x 153 | * @param int $chunk_y 154 | */ 155 | public function removePacketsCache(MapImage $image, int $chunk_x = null, int $chunk_y = null) { 156 | if (!isset($this->images[spl_object_hash($image)])) { 157 | return; 158 | } 159 | 160 | if ($chunk_x === null && $chunk_y === null) { 161 | foreach ($image->getChunks() as $chunks) { 162 | foreach ($chunks as $chunk) { 163 | unset($this->packet_cache[$chunk->getMapId()]); 164 | } 165 | } 166 | } else { 167 | $chunk = $image->getChunk($chunk_x, $chunk_y); 168 | if ($chunk !== null) { 169 | unset($this->packet_cache[$chunk->getMapId()]); 170 | } 171 | } 172 | } 173 | 174 | /** 175 | * Removes map image packet with specified map ID 176 | * 177 | * @param int $map_id 178 | */ 179 | public function removePacketCache(int $map_id) { 180 | unset($this->packet_cache[$map_id]); 181 | } 182 | 183 | /** 184 | * Returns map image with specified UUID hash 185 | * 186 | * @param string $uuid_hash 187 | * 188 | * @return MapImage|null 189 | */ 190 | public function getImage(string $uuid_hash) { 191 | return $this->images[$this->hashes[$uuid_hash] ?? null] ?? null; 192 | } 193 | 194 | /** 195 | * Returns map image with specified name 196 | * 197 | * @param string $name 198 | * 199 | * @return MapImage|null 200 | */ 201 | public function getImageByName(string $name) { 202 | return $this->getImage($this->names[strtr($name, ' ', '_')] ?? ''); 203 | } 204 | 205 | /** 206 | * Returns all of map images 207 | * 208 | * @return MapImage[] 209 | */ 210 | public function getImages() : array { 211 | return $this->images; 212 | } 213 | 214 | /** 215 | * Returns all of map images that have name 216 | * 217 | * @return MapImage[] 218 | */ 219 | public function getNamedImages() : array { 220 | return array_map( 221 | function ($hash) { 222 | return $this->getImage($hash); 223 | }, 224 | $this->names 225 | ); 226 | } 227 | 228 | /** 229 | * Returns cached batched map image packet 230 | * 231 | * @param int $map_id 232 | * 233 | * @return BatchPacket 234 | */ 235 | public function getCachedPacket(int $map_id) { 236 | if (isset($this->packet_cache[$map_id])) { 237 | return clone $this->packet_cache[$map_id]; 238 | } 239 | } 240 | 241 | } 242 | -------------------------------------------------------------------------------- /MapImageEngine/src/FaigerSYS/MapImageEngine/storage/MapImage.php: -------------------------------------------------------------------------------- 1 | blocks_width = $blocks_width; 61 | $this->blocks_height = $blocks_height; 62 | $this->uuid = $uuid ?? UUID::fromRandom(); 63 | $this->default_chunk_width = $default_chunk_width; 64 | $this->default_chunk_height = $default_chunk_height; 65 | $this->setChunks($chunks); 66 | } 67 | 68 | /** 69 | * Returns image blocks width 70 | * 71 | * @return int 72 | */ 73 | public function getBlocksWidth() : int { 74 | return $this->blocks_width; 75 | } 76 | 77 | /** 78 | * Returns image blocks height 79 | * 80 | * @return int 81 | */ 82 | public function getBlocksHeight() : int { 83 | return $this->blocks_height; 84 | } 85 | 86 | /** 87 | * Sets image blocks width 88 | * 89 | * @param int $blocks_width 90 | */ 91 | public function setBlocksWidth(int $blocks_width) { 92 | if ($blocks_width < 0) { 93 | throw new \InvalidArgumentException('Blocks width must be greater than 0'); 94 | } 95 | $this->blocks_width = $blocks_width; 96 | $this->checkChunks(); 97 | } 98 | 99 | /** 100 | * Sets image blocks height 101 | * 102 | * @param int $blocks_width 103 | */ 104 | public function setBlocksHeight(int $blocks_height) { 105 | if ($blocks_height < 0) { 106 | throw new \InvalidArgumentException('Blocks height must be greater than 0'); 107 | } 108 | $this->blocks_height = $blocks_height; 109 | $this->checkChunks(); 110 | } 111 | 112 | /** 113 | * Returns the image chunk at specified position 114 | * 115 | * @param int $block_x 116 | * @param int $block_y 117 | * 118 | * @return MapImageChunk|null 119 | */ 120 | public function getChunk(int $block_x, int $block_y) { 121 | return $this->chunks[$block_y][$block_x] ?? null; 122 | } 123 | 124 | /** 125 | * Returns all image chunks 126 | * 127 | * @return MapImageChunk|null 128 | */ 129 | public function getChunks() : array { 130 | return $this->chunks; 131 | } 132 | 133 | /** 134 | * Sets the image chunk at specified position 135 | * 136 | * @param int $block_x 137 | * @param int $block_y 138 | * @param MapImageChunk $chunk 139 | */ 140 | public function setChunk(int $block_x, int $block_y, MapImageChunk $chunk) { 141 | if ($block_x < 0 || $block_y < 0) { 142 | throw new \InvalidArgumentException('Block X/Y must be greater than 0'); 143 | } 144 | if ($block_x >= $this->blocks_width) { 145 | throw new \InvalidArgumentException('Block X cannot be greater than width'); 146 | } 147 | if ($block_y >= $this->blocks_height) { 148 | throw new \InvalidArgumentException('Block Y cannot be greater than height'); 149 | } 150 | 151 | $this->chunks[$block_y][$block_x] = $chunk; 152 | } 153 | 154 | /** 155 | * Rewrites all image chunks 156 | * 157 | * @param MapImageChunk[][] $chunks 158 | */ 159 | public function setChunks(array $chunks) { 160 | $this->chunks = $chunks; 161 | $this->checkChunks(); 162 | } 163 | 164 | /** 165 | * Generates bathed packet for all of image chunks 166 | * 167 | * @param int $compression_level 168 | * 169 | * @return BatchPacket 170 | */ 171 | public function generateBatchedMapImagesPacket(int $compression_level = 6) { 172 | $pk = new BatchPacket(); 173 | $pk->setCompressionLevel($compression_level); 174 | foreach ($this->chunks as $chunk) { 175 | $pk->addPacket($chunk->generateMapImagePacket()); 176 | } 177 | return $pk; 178 | } 179 | 180 | /** 181 | * Generates bathed packet for all of image chunks 182 | * 183 | * @param int $compression_level 184 | * 185 | * @return BatchPacket 186 | */ 187 | public function generateBatchedCustomMapImagesPacket(int $compression_level = 6) { 188 | $pk = new BatchPacket(); 189 | $pk->setCompressionLevel($compression_level); 190 | foreach ($this->chunks as $chunk) { 191 | $pk->addPacket($chunk->generateCustomMapImagePacket()); 192 | } 193 | return $pk; 194 | } 195 | 196 | /** 197 | * Returns the image UUID 198 | * 199 | * @return UUID 200 | */ 201 | public function getUUID() : UUID { 202 | return $this->uuid; 203 | } 204 | 205 | /** 206 | * Returns the image UUID hash 207 | * 208 | * @return string 209 | */ 210 | public function getHashedUUID() : string { 211 | return hash('sha1', $this->uuid->toBinary()); 212 | } 213 | 214 | /** 215 | * Creates new MapImage object from MIE image binary 216 | * 217 | * @param stirng $buffer 218 | * @param int &$state 219 | * 220 | * @return MapImage|null 221 | */ 222 | public static function fromBinary(string $buffer, &$state = null) { 223 | try { 224 | $buffer = new BinaryStream($buffer); 225 | 226 | $header = $buffer->get(4); 227 | if ($header !== 'MIEI') { 228 | $state = self::R_CORRUPTED; 229 | return; 230 | } 231 | 232 | $api = $buffer->getInt(); 233 | if (!in_array($api, self::SUPPORTED_VERSIONS)) { 234 | $state = self::R_UNSUPPORTED_VERSIONS; 235 | return; 236 | } 237 | 238 | $is_compressed = $buffer->getByte(); 239 | if ($is_compressed) { 240 | $buffer = $buffer->get(true); 241 | $buffer = @zlib_decode($buffer); 242 | if ($buffer === false) { 243 | $state = self::R_CORRUPTED; 244 | return; 245 | } 246 | 247 | $buffer = new BinaryStream($buffer); 248 | } 249 | 250 | $uuid = UUID::fromBinary($buffer->get(16), 4); 251 | 252 | $blocks_width = $buffer->getInt(); 253 | $blocks_height = $buffer->getInt(); 254 | 255 | $chunks = []; 256 | for ($block_y = 0; $block_y < $blocks_height; $block_y++) { 257 | for ($block_x = 0; $block_x < $blocks_width; $block_x++) { 258 | $chunk_width = $buffer->getInt(); 259 | $chunk_height = $buffer->getInt(); 260 | $chunk_data = $buffer->get($chunk_width * $chunk_height * 4); 261 | 262 | $chunks[$block_y][$block_x] = new MapImageChunk($chunk_width, $chunk_height, $chunk_data); 263 | } 264 | } 265 | 266 | $state = self::R_OK; 267 | return new MapImage($blocks_width, $blocks_height, $chunks, $uuid); 268 | } catch (\Throwable $e) { 269 | $state = self::R_CORRUPTED; 270 | } 271 | } 272 | 273 | private function checkChunks() { 274 | $chunks = $this->chunks; 275 | $this->chunks = []; 276 | for ($y = 0; $y < $this->blocks_height; $y++) { 277 | for ($x = 0; $x < $this->blocks_width; $x++) { 278 | $this->chunks[$y][$x] = ($chunks[$y][$x] ?? null) instanceof MapImageChunk ? $chunks[$y][$x] : MapImageChunk::generateImageChunk($this->default_chunk_width, $this->default_chunk_height); 279 | } 280 | } 281 | } 282 | 283 | public function __clone() { 284 | for ($y = 0; $y < $this->blocks_height; $y++) { 285 | for ($x = 0; $x < $this->blocks_width; $x++) { 286 | $this->chunks[$y][$x] = clone $this->chunks[$y][$x]; 287 | } 288 | } 289 | $this->uuid = UUID::fromRandom(); 290 | } 291 | 292 | } 293 | -------------------------------------------------------------------------------- /MapImageEngine/src/FaigerSYS/MapImageEngine/storage/MapImageChunk.php: -------------------------------------------------------------------------------- 1 | width = $width; 48 | $this->height = $height; 49 | $this->map_id = $map_id ?? Entity::$entityCount++; 50 | $this->data = new BinaryStream($data); 51 | } 52 | 53 | /** 54 | * Returns map image chunk map ID 55 | * 56 | * @return int 57 | */ 58 | public function getMapId() : int { 59 | return $this->map_id; 60 | } 61 | 62 | /** 63 | * Sets map image chunk map ID 64 | * 65 | * @return int 66 | */ 67 | public function setMapId(int $map_id) { 68 | $this->map_id = $map_id; 69 | } 70 | 71 | /** 72 | * Returns map image chunk width 73 | * 74 | * @return int 75 | */ 76 | public function getWidth() : int { 77 | return $this->width; 78 | } 79 | 80 | /** 81 | * Returns map image chunk height 82 | * 83 | * @return int 84 | */ 85 | public function getHeight() : int { 86 | return $this->height; 87 | } 88 | 89 | /** 90 | * Returns RGBA color at specified position 91 | * 92 | * @return int 93 | */ 94 | public function getRGBA(int $x, int $y) : int { 95 | $this->data->offset = $this->getStartOffset($x, $y); 96 | return (int) $this->data->getInt(); 97 | } 98 | 99 | /** 100 | * Sets RBGA color at specified position 101 | * 102 | * @param int $x 103 | * @param int $y 104 | * @param int $color 105 | */ 106 | public function setRGBA(int $x, int $y, int $color) { 107 | $pos = $this->getStartOffset($x, $y); 108 | $this->data->buffer[$pos++] = chr($color & 0xff); 109 | $this->data->buffer[$pos++] = chr($color >> 8 & 0xff); 110 | $this->data->buffer[$pos++] = chr($color >> 16 & 0xff); 111 | $this->data->buffer[$pos] = chr($color >> 24 & 0xff); 112 | } 113 | 114 | /** 115 | * Returns ABGR color at specified position 116 | * 117 | * @return int 118 | */ 119 | public function getABGR(int $x, int $y) : int { 120 | $this->data->offset = $this->getStartOffset($x, $y); 121 | return (int) $this->data->getLInt() & 0xffffffff; 122 | } 123 | 124 | /** 125 | * Sets ABGR color at specified position 126 | * 127 | * @param int $x 128 | * @param int $y 129 | * @param int $color 130 | */ 131 | public function setABGR(int $x, int $y, int $color) { 132 | $pos = $this->getStartOffset($x, $y); 133 | $this->data->buffer[$pos++] = chr($color >> 24 & 0xff); 134 | $this->data->buffer[$pos++] = chr($color >> 16 & 0xff); 135 | $this->data->buffer[$pos++] = chr($color >> 8 & 0xff); 136 | $this->data->buffer[$pos] = chr($color & 0xff); 137 | } 138 | 139 | /** 140 | * Returns array of Color objects 141 | * 142 | * @return array 143 | */ 144 | public function toArrayColor() : array { 145 | $colors = []; 146 | $this->data->offset = 0; 147 | for ($y = 0; $y < $this->height; $y++) { 148 | for ($x = 0; $x < $this->width; $x++) { 149 | $color = $this->data->getInt(); 150 | $colors[$y][$x] = new Color($color >> 24 & 0xff, $color >> 16 & 0xff, $color >> 8 & 0xff, $color & 0xff); 151 | } 152 | } 153 | return $colors; 154 | } 155 | 156 | /** 157 | * Returns RGBA colors array 158 | * 159 | * @return array 160 | */ 161 | public function toArrayRGBA() : array { 162 | $colors = []; 163 | $this->data->offset = 0; 164 | for ($y = 0; $y < $this->height; $y++) { 165 | for ($x = 0; $x < $this->width; $x++) { 166 | $colors[$y][$x] = (int) $this->data->getInt(); 167 | } 168 | } 169 | 170 | return $colors; 171 | } 172 | 173 | /** 174 | * Returns pretty RGBA colors array 175 | * 176 | * @return array 177 | */ 178 | public function toArrayPrettyRGBA() : array { 179 | $colors = []; 180 | $this->data->offset = 0; 181 | for ($y = 0; $y < $this->height; $y++) { 182 | for ($x = 0; $x < $this->width; $x++) { 183 | $colors[$y][$x] = [ 184 | 'r' => $this->data->getByte(), 185 | 'g' => $this->data->getByte(), 186 | 'b' => $this->data->getByte(), 187 | 'a' => $this->data->getByte() 188 | ]; 189 | } 190 | } 191 | 192 | return $colors; 193 | } 194 | 195 | /** 196 | * Returns ABGR colors array 197 | * 198 | * @return array 199 | */ 200 | public function toArrayABGR() : array { 201 | $colors = []; 202 | $this->data->offset = 0; 203 | for ($y = 0; $y < $this->height; $y++) { 204 | for ($x = 0; $x < $this->width; $x++) { 205 | $colors[$y][$x] = $this->data->getLInt() & 0xffffffff; 206 | } 207 | } 208 | 209 | return $colors; 210 | } 211 | 212 | /** 213 | * Returns RGBA colors binary 214 | * 215 | * @return string 216 | */ 217 | public function toBinaryRGBA() : string { 218 | return $this->data->buffer; 219 | } 220 | 221 | /** 222 | * Generates map image packet 223 | * 224 | * @param int $compression_level 225 | * @param int $map_id 226 | * @param bool $use_cache 227 | * 228 | * @return ClientboundMapItemDataPacket 229 | */ 230 | public function generateMapImagePacket(int $map_id = null) { 231 | $pk = new ClientboundMapItemDataPacket; 232 | $pk->mapId = $map_id ?? $this->map_id; 233 | $pk->scale = 0; 234 | $pk->width = $this->width; 235 | $pk->height = $this->height; 236 | $pk->colors = $this->toArrayColor(); 237 | return $pk; 238 | } 239 | 240 | /** 241 | * Generates custom map image packet 242 | * 243 | * @param int $compression_level 244 | * @param int $map_id 245 | * @param bool $use_cache 246 | * 247 | * @return CustomClientboundMapItemDataPacket 248 | */ 249 | public function generateCustomMapImagePacket(int $map_id = null, bool $use_cache = true) { 250 | $pk = new CustomClientboundMapItemDataPacket; 251 | $pk->mapId = $map_id ?? $this->map_id; 252 | $pk->scale = 0; 253 | $pk->width = $this->width; 254 | $pk->height = $this->height; 255 | 256 | $colors = null; 257 | $generate_cache = false; 258 | 259 | if ($use_cache) { 260 | $cache_hash = hash('md5', $this->width . '.' . $this->height . '.' . hash('md5', $this->data->buffer)); 261 | $cache_path = MapImageEngine::getInstance()->getDataFolder() . 'cache/' . $cache_hash; 262 | $generate_cache = true; 263 | if (file_exists($cache_path) && is_file($cache_path)) { 264 | $cache_buffer = new BinaryStream(file_get_contents($cache_path)); 265 | 266 | $cache_api = $cache_buffer->getInt(); 267 | if ($cache_api === self::CACHE_API) { 268 | $colors = $cache_buffer->get(true); 269 | $generate_cache = false; 270 | } 271 | } 272 | } 273 | 274 | if ($colors === null) { 275 | $colors = CustomClientboundMapItemDataPacket::prepareColors($this->toArrayABGR(), $this->width, $this->height); 276 | } 277 | 278 | if ($generate_cache) { 279 | $cache_buffer = new BinaryStream; 280 | $cache_buffer->putInt(self::CACHE_API); 281 | $cache_buffer->put($colors); 282 | 283 | file_put_contents($cache_path, $cache_buffer->buffer); 284 | } 285 | 286 | $pk->colors = $colors; 287 | 288 | return $pk; 289 | } 290 | 291 | /** 292 | * Creates a new map image chunk from the RGBA color array 293 | * 294 | * @param int $width 295 | * @param int $height 296 | * @param array $colors 297 | * 298 | * @return MapImageChunk 299 | */ 300 | public static function fromArrayRGBA(int $width, int $height, array $colors) { 301 | if ($width < 0 || $height < 0) { 302 | throw new \InvalidArgumentException('Width/height must be greater than 0'); 303 | } 304 | 305 | $data = new BinaryStream; 306 | 307 | for ($y = 0; $y < $height; $y++) { 308 | for ($x = 0; $x < $width; $x++) { 309 | if (!is_int($colors[$y][$x] ?? null)) { 310 | throw new \InvalidArgumentException('Color is corrupted on [X: ' . $x . ', Y: ' . $y . ']'); 311 | } 312 | 313 | $data->putInt($colors[$y][$x]); 314 | } 315 | } 316 | 317 | return new MapImageChunk($width, $height, $data->buffer); 318 | } 319 | 320 | /** 321 | * Creates a new map image chunk from the ABGR colors array 322 | * 323 | * @param int $width 324 | * @param int $height 325 | * @param array $colors 326 | * 327 | * @return MapImageChunk 328 | */ 329 | public static function fromArrayABGR(int $width, int $height, array $colors) { 330 | if ($width < 0 || $height < 0) { 331 | throw new \InvalidArgumentException('Width/height must be greater than 0'); 332 | } 333 | 334 | $data = new BinaryStream; 335 | 336 | for ($y = 0; $y < $height; $y++) { 337 | for ($x = 0; $x < $width; $x++) { 338 | if (!is_int($colors[$y][$x] ?? null)) { 339 | throw new \InvalidArgumentException('Color is corrupted on [X: ' . $x . ', Y: ' . $y . ']'); 340 | } 341 | 342 | $data->putLInt($colors[$y][$x]); 343 | } 344 | } 345 | 346 | return new MapImageChunk($width, $height, $data->buffer); 347 | } 348 | 349 | 350 | /** 351 | * Creates a new map image chunk with the specified color 352 | * 353 | * @param int $width 354 | * @param int $height 355 | * @param int $fill_color 356 | * 357 | * @return MapImageChunk 358 | */ 359 | public static function generateImageChunk(int $width, int $height, int $fill_color = 0) { 360 | if ($width < 0 || $height < 0) { 361 | throw new \InvalidArgumentException('Width/height must be greater than 0'); 362 | } 363 | 364 | return new MapImageChunk($width, $height, str_repeat(Binary::writeInt($fill_color), $width * $height)); 365 | } 366 | 367 | private function getStartOffset(int $x, int $y) : int { 368 | if ($x < 0 || $y < 0) { 369 | throw new \InvalidArgumentException('X/Y must be greater than 0'); 370 | } 371 | if ($x >= $this->width) { 372 | throw new \InvalidArgumentException('X cannot be greater than width'); 373 | } 374 | if ($y >= $this->height) { 375 | throw new \InvalidArgumentException('Y cannot be greater than height'); 376 | } 377 | 378 | return ($y * $this->width) + $x; 379 | } 380 | 381 | public function __clone() { 382 | $this->map_id = Entity::$entityCount++; 383 | } 384 | 385 | } 386 | -------------------------------------------------------------------------------- /MapImageEngine/src/FaigerSYS/MapImageEngine/storage/OldFormatConverter.php: -------------------------------------------------------------------------------- 1 | put('MIEI'); 15 | $buffer->putInt(MapImage::CURRENT_VERSION); 16 | $buffer->putByte(0); 17 | $buffer->put(UUID::fromRandom()->toBinary()); 18 | 19 | $image = @gzinflate($data); 20 | if ($image) { 21 | $image = json_decode($image, true); 22 | if (!is_array($image)) { 23 | return; 24 | } 25 | 26 | $b_height = count($image['blocks']); 27 | $b_width = count($image['blocks'][0]); 28 | 29 | $buffer->putInt($b_width); 30 | $buffer->putInt($b_height); 31 | 32 | for ($b_y = 0; $b_y < $b_height; $b_y++) { 33 | for ($b_x = 0; $b_x < $b_width; $b_x++) { 34 | $chunk = json_decode(gzinflate(base64_decode($image['blocks'][$b_y][$b_x]))); 35 | if (!is_array($chunk)) { 36 | return; 37 | } 38 | 39 | $chunk = MapImageChunk::fromArrayABGR(128, 128, $chunk)->toBinaryRGBA(); 40 | 41 | $buffer->putInt(128); 42 | $buffer->putInt(128); 43 | $buffer->put($chunk); 44 | } 45 | } 46 | } else { 47 | return; 48 | } 49 | 50 | return $buffer->buffer; 51 | } catch (\Throwable $e) { 52 | return; 53 | } 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### This repository contains some plugins: 2 | 3 | Plugin|Description 4 | ------|----------- 5 | [MapImageEngine](https://github.com/FaigerSYS/MapImageEngine/tree/master/MapImageEngine)|Image engine for MCPE 6 | MIE_Animations|Animated images (not soon) 7 | [MIE_Protector](https://github.com/FaigerSYS/MapImageEngine/tree/master/MIE_Protector)|Addon for MapImageEngine to protect the image (frame) from rotation and destruction 8 | [MIE_Converter](https://github.com/FaigerSYS/MapImageEngine/tree/master/MIE_Converter)|Addon for MapImageEngine to convert images to .mie format 9 | 10 | I can not translate the plugin into all languages. I can also have grammatical mistakes in English, so any "language" contribution is welcomed :) 11 | 12 | ## Best users 13 | |#|User|Shox's| 14 | |---|---|---| 15 | |1|Encritary|500| 16 | |2|Yexeed|125| 17 | --------------------------------------------------------------------------------