Skip to content

Instantly share code, notes, and snippets.

@DavyCraft648
Last active July 30, 2025 12:45
Show Gist options
  • Select an option

  • Save DavyCraft648/663450e8874e4c0adc8607d9c8a5fe15 to your computer and use it in GitHub Desktop.

Select an option

Save DavyCraft648/663450e8874e4c0adc8607d9c8a5fe15 to your computer and use it in GitHub Desktop.
Having problems with custom items and blocks on your server because you use a proxy like WaterdogPE? It can be solved with this plugin!
<?php
declare(strict_types=1);
namespace DavyCraft648\CustomiesProxySync;
use customiesdevs\customies\block\BlockPalette;
use customiesdevs\customies\block\CustomiesBlockFactory;
use customiesdevs\customies\item\CustomiesItemFactory;
use pmmp\thread\ThreadSafeArray;
use pocketmine\block\Block;
use pocketmine\data\bedrock\block\convert\BlockStateReader;
use pocketmine\data\bedrock\block\convert\BlockStateWriter;
use pocketmine\network\mcpe\convert\BlockStateDictionaryEntry;
use pocketmine\network\mcpe\convert\TypeConverter;
use pocketmine\network\mcpe\protocol\ProtocolInfo;
use pocketmine\network\mcpe\protocol\types\ItemTypeEntry;
use pocketmine\scheduler\AsyncTask;
use pocketmine\scheduler\ClosureTask;
use Symfony\Component\Filesystem\Path;
use function array_splice;
use function count;
use function defined;
use function file_get_contents;
use function file_put_contents;
use function gzcompress;
use function gzuncompress;
use function igbinary_serialize;
use function igbinary_unserialize;
/**
* @name CustomiesProxySync
* @version 0.0.1
* @main DavyCraft648\CustomiesProxySync\CustomiesProxySync
* @api 5.0.0
*/
class CustomiesProxySync extends \pocketmine\plugin\PluginBase{
protected function onEnable() : void{
$config = $this->getConfig();
$config->setDefaults([
"type" => "save", // save, load
"file" => Path::join($this->getDataFolder(), "data.dat")
]);
if($config->hasChanged()){
$config->save();
}
$customies = $this->getServer()->getPluginManager()->getPlugin("Customies");
$customies->getScheduler()->cancelAllTasks();
$this->getScheduler()->scheduleDelayedTask(new ClosureTask(function() use ($customies): void{
$customies->getScheduler()->cancelAllTasks();
if($this->getConfig()->get("type") === "save"){
$content = [
"blockPaletteEntries" => igbinary_serialize(CustomiesBlockFactory::getInstance()->getBlockPaletteEntries()),
"customStates" => igbinary_serialize(BlockPalette::getInstance()->getCustomStates()),
"itemTableEntries" => igbinary_serialize(CustomiesItemFactory::getInstance()->getItemTableEntries())
];
file_put_contents($this->getConfig()->get("file"), gzcompress(igbinary_serialize($content)));
}elseif($this->getConfig()->get("type") === "load"){
$content = igbinary_unserialize(gzuncompress(file_get_contents($this->getConfig()->get("file"))));
$blockPaletteEntries = new \ReflectionProperty(CustomiesBlockFactory::class, "blockPaletteEntries");
$blockPaletteEntries->setValue(CustomiesBlockFactory::getInstance(), igbinary_unserialize($content["blockPaletteEntries"]));
$pendingInsert = [];
/** @var BlockStateDictionaryEntry $customState */
foreach(igbinary_unserialize($customStates = $content["customStates"]) as $customState){
$pendingInsert[$customState->getStateName()][] = $customState;
}
(new \ReflectionProperty(BlockPalette::getInstance(), "pendingInsert"))->setValue(BlockPalette::getInstance(), $pendingInsert);
BlockPalette::getInstance()->sortStates();
$asyncPool = $this->getServer()->getAsyncPool();
/** @var \Closure $hook */
foreach((new \ReflectionProperty($asyncPool, "workerStartHooks"))->getValue($asyncPool) as $hook){
if((new \ReflectionFunction($hook))->getName() === "customiesdevs\customies\block\{closure}"){
$asyncPool->removeWorkerStartHook($hook);
break;
}
}
$blocks = (new \ReflectionProperty(CustomiesBlockFactory::getInstance(), "blockFuncs"))->getValue(CustomiesBlockFactory::getInstance());
$asyncPool->addWorkerStartHook(static function (int $worker) use ($asyncPool, $blocks, $customStates): void{
$asyncPool->submitTaskToWorker(new class($blocks, $customStates) extends AsyncTask{
private ThreadSafeArray $blockFuncs;
private ThreadSafeArray $serializer;
private ThreadSafeArray $deserializer;
/**
* @param Closure[] $blockFuncs
* @phpstan-param array<string, array{(Closure(): Block), (Closure(BlockStateWriter): Block), (Closure(Block): BlockStateReader)}> $blockFuncs
*/
public function __construct(array $blockFuncs, private string $customStates){
$this->blockFuncs = new ThreadSafeArray();
$this->serializer = new ThreadSafeArray();
$this->deserializer = new ThreadSafeArray();
foreach($blockFuncs as $identifier => [$blockFunc, $serializer, $deserializer]){
$this->blockFuncs[$identifier] = $blockFunc;
$this->serializer[$identifier] = $serializer;
$this->deserializer[$identifier] = $deserializer;
}
}
public function onRun() : void{
foreach($this->blockFuncs as $identifier => $blockFunc){
// We do not care about the model or creative inventory data in other threads since it is unused outside of
// the main thread.
try{
CustomiesBlockFactory::getInstance()->registerBlock($blockFunc, $identifier, serializer: $this->serializer[$identifier], deserializer: $this->deserializer[$identifier]);
}catch(\Throwable $e){
//\GlobalLogger::get()->logException($e);
}
}
$pendingInsert = [];
/** @var BlockStateDictionaryEntry $customState */
foreach(igbinary_unserialize($this->customStates) as $customState){
$pendingInsert[$customState->getStateName()][] = $customState;
}
(new \ReflectionProperty(BlockPalette::getInstance(), "pendingInsert"))->setValue(BlockPalette::getInstance(), $pendingInsert);
BlockPalette::getInstance()->sortStates();
}
}, $worker);
});
$cachedItemTableEntries = CustomiesItemFactory::getInstance()->getItemTableEntries();
$cachedCount = count($cachedItemTableEntries);
$savedItemTableEntries = igbinary_unserialize($content["itemTableEntries"]);
if($savedItemTableEntries === false || !$savedItemTableEntries[0] instanceof ItemTypeEntry){
throw new \RuntimeException("data.dat needs to be reset: ItemTableEntries must be an ItemTypeEntry");
}
foreach(defined(ProtocolInfo::class . "::ACCEPTED_PROTOCOL") ? ProtocolInfo::ACCEPTED_PROTOCOL : [ProtocolInfo::CURRENT_PROTOCOL] as $protocol){
$typeConverter = TypeConverter::getInstance($protocol);
$dictionary = $typeConverter->getItemTypeDictionary();
$reflection = new \ReflectionClass($dictionary);
$intToString = $reflection->getProperty("intToStringIdMap");
$stringToInt = $reflection->getProperty("stringToIntMap");
$itemTypes = $reflection->getProperty("itemTypes");
/** @var string[] $intToStringValue */
$intToStringValue = $intToString->getValue($dictionary);
/** @var int[] $stringToIntValue */
$stringToIntValue = $stringToInt->getValue($dictionary);
$itemTypesValue = $itemTypes->getValue($dictionary);
array_splice($itemTypesValue, -$cachedCount, $cachedCount);
foreach($savedItemTableEntries as $entry){
$intToStringValue[$entry->getNumericId()] = $entry->getStringId();
$stringToIntValue[$entry->getStringId()] = $entry->getNumericId();
$itemTypesValue[] = $entry;
}
$intToString->setValue($dictionary, $intToStringValue);
$stringToInt->setValue($dictionary, $stringToIntValue);
$itemTypes->setValue($dictionary, $itemTypesValue);
}
(new \ReflectionProperty(CustomiesItemFactory::getInstance(), "itemTableEntries"))->setValue(CustomiesItemFactory::getInstance(), $savedItemTableEntries);
}
}), 2);
}
}
@DavyCraft648
Copy link
Author

DavyCraft648 commented Jul 25, 2023

Having problems with custom items and blocks on your server because you use a proxy like WaterdogPE? It can be solved with this plugin!

You can use this plugin with either NetherGames pocketmine or pm vanilla, but custom blocks/items must be registered using https://github.com/DavyCraft648/Customies-NG.

How to use this?

  1. Turn off all your servers (eg: hub, survival, gravity, parkour).
  2. Add this plugin to the plugins folder in all your servers (hub, survival, gravity, parkour).
  3. Set type at plugin_data/CustomiesProxySync/config.yml to save in the hub server.
  4. Turn on the hub server.
  5. Set type at plugin_data/CustomiesProxySync/config.yml to load again in the hub server.
  6. Adjust the file location at plugin_data/CustomiesProxySync/config.yml:
    if your servers are on the same machine, just match the file location to the config in the hub server. If not, copy the data.dat file that generated in plugin_data/CustomiesProxySync on the hub server to all servers (survival, gravity, parkour).
  7. Turn on all your server (survival, gravity, parkour).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment