├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ ├── crash.md │ └── security-vulnerability.md ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ ├── draft-release.yml │ ├── main.yml │ └── support.yml ├── .php-cs-fixer.php ├── .poggit.yml ├── LICENSE ├── README.md ├── build └── generate-registry-annotations.php ├── composer.json ├── composer.lock ├── icon.gif ├── phpstan-baseline.php ├── phpstan.neon.dist ├── plugin.yml └── src ├── Main.php ├── event ├── DimensionPortalsEvent.php ├── WorldListener.php └── player │ ├── PlayerCreateEndPortalEvent.php │ ├── PlayerCreateNetherPortalEvent.php │ ├── PlayerCreatePortalEvent.php │ ├── PlayerEnterPortalEvent.php │ └── PlayerPortalTeleportEvent.php ├── exoblock ├── EndPortalExoBlock.php ├── EndPortalFrameExoBlock.php ├── ExoBlock.php ├── ExoBlockEventHandler.php ├── ExoBlockFactory.php ├── NetherPortalExoBlock.php ├── NetherPortalFrameExoBlock.php └── PortalExoBlock.php ├── player ├── PlayerDimensionChangeListener.php ├── PlayerInstance.php ├── PlayerListener.php ├── PlayerManager.php └── PlayerPortalInfo.php ├── utils ├── DimensionChunkCache.php └── WorldUtils.php ├── vanilla ├── ExtraVanillaBlocks.php ├── ExtraVanillaData.php └── ExtraVanillaItems.php └── world ├── DimensionalWorld.php ├── DimensionalWorldManager.php ├── converter └── DimensionalFormatConverter.php ├── data └── DimensionalBedrockWorldData.php ├── generator ├── Decorator.php ├── Environment.php ├── Populator.php ├── VanillaBiomeGrid.php ├── VanillaGenerator.php ├── biome │ ├── BiomeClimate.php │ ├── BiomeClimateManager.php │ └── BiomeIds.php ├── biomegrid │ ├── BiomeGrid.php │ ├── ConstantBiomeMapLayer.php │ ├── MapLayer.php │ └── utils │ │ └── MapLayerPair.php ├── ender │ ├── EnderGenerator.php │ └── populator │ │ └── EnderPilar.php ├── nether │ ├── NetherGenerator.php │ ├── WorldType.php │ ├── decorator │ │ ├── FireDecorator.php │ │ ├── GlowstoneDecorator.php │ │ └── MushroomDecorator.php │ └── populator │ │ ├── NetherPopulator.php │ │ ├── OrePopulator.php │ │ └── biome │ │ ├── OrePopulator.php │ │ └── utils │ │ └── OreTypeHolder.php ├── noise │ ├── bukkit │ │ ├── BaseOctaveGenerator.php │ │ ├── BasePerlinNoiseGenerator.php │ │ ├── NoiseGenerator.php │ │ ├── OctaveGenerator.php │ │ ├── PerlinNoiseGenerator.php │ │ ├── SimplexNoiseGenerator.php │ │ └── SimplexOctaveGenerator.php │ └── glowstone │ │ ├── PerlinNoise.php │ │ ├── PerlinOctaveGenerator.php │ │ ├── SimplexNoise.php │ │ └── SimplexOctaveGenerator.php ├── object │ ├── OreType.php │ ├── OreVein.php │ └── TerrainObject.php └── utils │ ├── NetherWorldOctaves.php │ ├── WorldOctaves.php │ └── preset │ ├── GeneratorPreset.php │ └── SimpleGeneratorPreset.php └── provider ├── DimensionLevelDBProvider.php ├── DimensionProviderManagerEntry.php ├── DimensionalWorldProviderManager.php ├── EnderAnvilProvider.php ├── NetherAnvilProvider.php ├── ReadOnlyWorldProviderManagerEntry.php └── RewritableWorldProviderManagerEntry.php /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [ jasonw4331 ] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: jasonwynn10 5 | custom: https://www.buymeacoffee.com/jasonwynn10 -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Unexpected non-crash behaviour 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### Issue description 11 | 12 | - Expected result: What were you expecting to happen? 13 | - Actual result: What actually happened? 14 | 15 | ### Steps to reproduce the issue 16 | 17 | 1. ... 18 | 2. ... 19 | 20 | ### OS and versions 21 | 22 | 23 | 24 | * Plugin Version: 25 | * PocketMine-MP: 26 | * PHP: 27 | * Game version: PE/Win10 (delete as appropriate) 28 | 29 | ### Other Plugins 30 | 31 | 32 | 33 | - If you remove all other plugins, does the issue still occur? Yes/No (delete as appropriate) 34 | - If the issue is **not** reproducible without other plugins: 35 | - Have you asked for help in the community discord before creating an issue? 36 | - Can you provide sample, *minimal* reproducing code for the issue? If so, paste it in the bottom section. 37 | 38 | ### Console error, backtrace or other files 39 | 40 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Help & support on Discord 4 | url: https://discord.gg/R7kdetE 5 | about: We don't accept support requests on the issue tracker. Please try asking on Discord instead. 6 | - name: Help & support on forums 7 | url: https://forums.pmmp.io 8 | about: We don't accept support requests on the issue tracker. Please try asking on forums instead. 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/crash.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Crash 3 | about: Report a crash caused by SimpleReplies 4 | title: Server crashed 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | Link to crashdump: 13 | 14 | 15 | 16 | ### Additional comments (optional) -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/security-vulnerability.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Security vulnerability 3 | about: 'Bug or exploit that can be used to attack servers (hint: don\'t report it 4 | on a public issue tracker)' 5 | title: '' 6 | labels: 'Auto: Spam' 7 | assignees: '' 8 | 9 | --- 10 | 11 | Please DO NOT report security vulnerabilities here. 12 | Instead, contact @jasonw#4331 on discord directly, IN PRIVATE. -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | 3 | 4 | 5 | ### Relevant issues 6 | 7 | 8 | 14 | 15 | ## Changes 16 | 17 | ### API changes 18 | 19 | 20 | 21 | ### Behavioural changes 22 | 23 | 24 | 25 | ## Backwards compatibility 26 | 27 | 28 | 29 | ## Follow-up 30 | 31 | 32 | 41 | 42 | ## Tests 43 | 44 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: composer 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "10:00" 8 | open-pull-requests-limit: 2 9 | - package-ecosystem: github-actions 10 | directory: "/" 11 | schedule: 12 | interval: daily 13 | -------------------------------------------------------------------------------- /.github/workflows/draft-release.yml: -------------------------------------------------------------------------------- 1 | name: Draft release 2 | 3 | on: 4 | push: 5 | tags: "*" 6 | 7 | jobs: 8 | draft: 9 | name: Create GitHub Draft Release 10 | if: "startsWith(github.event.head_commit.message, 'Release ')" 11 | runs-on: ubuntu-latest 12 | strategy: 13 | fail-fast: false 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | 18 | - name: Setup PHP 19 | uses: shivammathur/setup-php@v2 20 | with: 21 | php-version: 8.1 22 | ini-values: phar.readonly=0 23 | coverage: none 24 | 25 | - name: Get composer cache directory 26 | id: composer-cache 27 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 28 | 29 | - name: Cache composer dependencies 30 | uses: actions/cache@v3 31 | with: 32 | path: ${{ steps.composer-cache.outputs.dir }} 33 | # Use composer.json for key, if composer.lock is not committed. 34 | # key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} 35 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 36 | restore-keys: ${{ runner.os }}-composer- 37 | 38 | - name: Install Composer dependencies 39 | run: composer install --no-progress --no-dev --prefer-dist --optimize-autoloader --ignore-platform-reqs 40 | 41 | - name: Download Pharynx phar builder 42 | run: wget https://github.com/SOF3/pharynx/releases/latest/download/pharynx.phar -O pharynx.phar 43 | 44 | - id: get-manifest 45 | run: | 46 | echo "NAME=$(grep '^name:' plugin.yml | cut -d' ' -f2- | xargs)" >> $GITHUB_OUTPUT 47 | echo "PRERELEASE=$(grep '^version:' plugin.yml | cut -d' ' -f2- | xargs | grep -E 'alpha|beta|pre' && echo 'true')" >> $GITHUB_OUTPUT 48 | echo "API=$(grep '^api:' plugin.yml | cut -d' ' -f2- | xargs)" >> $GITHUB_OUTPUT 49 | sed -i '/src-namespace-prefix/d' plugin.yml 50 | sed -i "s/version: .*/version: ${{ github.ref_name }}/g" plugin.yml 51 | 52 | - name: Build plugin archive 53 | run: php pharynx.phar -i . -f LICENSE -c -p=${{ github.workspace }}/${{ steps.get-manifest.outputs.NAME }}.phar 54 | 55 | - name: Upload release artifacts 56 | uses: actions/upload-artifact@v3 57 | with: 58 | name: release_artifacts 59 | path: | 60 | ${{ github.workspace }}/${{ steps.get-manifest.outputs.NAME }}.phar 61 | 62 | - name: Create draft release 63 | uses: ncipollo/release-action@v1 64 | with: 65 | artifacts: ${{ github.workspace }}/${{ steps.get-manifest.outputs.NAME }}.phar 66 | commit: ${{ github.sha }} 67 | draft: true 68 | name: ${{ steps.get-manifest.outputs.NAME }} v${{ github.ref_name }} 69 | prerelease: ${{ steps.get-manifest.outputs.PRERELEASE }} 70 | tag: ${{ github.ref_name }} 71 | #token: ${{ secrets.GITHUB_TOKEN }} 72 | body: | 73 | **For PocketMine API ${{ steps.get-manifest.outputs.API }}** -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | 8 | jobs: 9 | PHPStan_Analyze: 10 | name: PHPStan Analysis 11 | runs-on: ubuntu-latest 12 | strategy: 13 | fail-fast: false 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | 18 | - name: Setup PHP and tools 19 | uses: shivammathur/setup-php@v2 20 | with: 21 | php-version: 8.1 22 | tools: phpstan 23 | coverage: none 24 | extensions: chunkutils2-pmmp/ext-chunkutils2@0.3.5 25 | 26 | - name: Get composer cache directory 27 | id: composer-cache 28 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 29 | 30 | - name: Cache composer dependencies 31 | uses: actions/cache@v3 32 | with: 33 | path: ${{ steps.composer-cache.outputs.dir }} 34 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 35 | restore-keys: ${{ runner.os }}-composer- 36 | 37 | - name: Install Composer dependencies 38 | run: composer install --no-progress --prefer-dist --optimize-autoloader --ignore-platform-reqs 39 | 40 | - name: Run PhpStan 41 | run: phpstan analyze --no-progress src -c phpstan.neon.dist 42 | 43 | codestyle: 44 | name: Code Style checks 45 | runs-on: ubuntu-latest 46 | strategy: 47 | fail-fast: false 48 | 49 | steps: 50 | - uses: actions/checkout@v4 51 | 52 | - name: Setup PHP and tools 53 | uses: shivammathur/setup-php@v2 54 | with: 55 | tools: php-cs-fixer 56 | coverage: none 57 | 58 | - name: Run PHP-CS-Fixer 59 | run: php-cs-fixer fix --dry-run --diff --config=.php-cs-fixer.php -------------------------------------------------------------------------------- /.github/workflows/support.yml: -------------------------------------------------------------------------------- 1 | name: 'Manage support request issues' 2 | 3 | on: 4 | issues: 5 | types: [ labeled, unlabeled, reopened ] 6 | 7 | jobs: 8 | support: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: dessant/support-requests@v3 12 | with: 13 | github-token: ${{ github.token }} 14 | support-label: "Support request" 15 | issue-comment: > 16 | Hi, we only accept **bug reports** on this issue tracker, but this issue looks like a support request. 17 | 18 | 19 | Instead of creating an issue, try the following: 20 | 21 | - Ask the community on our [Discord server](https://discord.gg/R7kdetE) or the [PocketMine Forums](https://forums.pmmp.io) 22 | 23 | - Ask in the PocketMine community [Discord server](https://discord.gg/bmSAZBG) 24 | 25 | 26 | [Discord](https://discord.gg/R7kdetE) | [Forums](https://forums.pmmp.io) 27 | 28 | close-issue: true 29 | lock-issue: false 30 | -------------------------------------------------------------------------------- /.php-cs-fixer.php: -------------------------------------------------------------------------------- 1 | in(__DIR__ . '/src'); 5 | 6 | return (new PhpCsFixer\Config) 7 | ->setRiskyAllowed(true) 8 | ->setRules([ 9 | 'align_multiline_comment' => [ 10 | 'comment_type' => 'phpdocs_only' 11 | ], 12 | 'array_indentation' => true, 13 | 'array_syntax' => [ 14 | 'syntax' => 'short' 15 | ], 16 | 'binary_operator_spaces' => [ 17 | 'default' => 'single_space' 18 | ], 19 | 'blank_line_after_namespace' => true, 20 | 'blank_line_after_opening_tag' => true, 21 | 'blank_line_before_statement' => [ 22 | 'statements' => [ 23 | 'declare' 24 | ] 25 | ], 26 | 'cast_spaces' => [ 27 | 'space' => 'single' 28 | ], 29 | 'concat_space' => [ 30 | 'spacing' => 'one' 31 | ], 32 | 'declare_strict_types' => true, 33 | 'elseif' => true, 34 | 'fully_qualified_strict_types' => true, 35 | 'global_namespace_import' => [ 36 | 'import_constants' => true, 37 | 'import_functions' => true, 38 | 'import_classes' => null, 39 | ], 40 | 'indentation_type' => true, 41 | 'logical_operators' => true, 42 | 'native_constant_invocation' => [ 43 | 'scope' => 'namespaced' 44 | ], 45 | 'native_function_invocation' => [ 46 | 'scope' => 'namespaced', 47 | 'include' => ['@all'], 48 | ], 49 | 'new_with_braces' => [ 50 | 'named_class' => true, 51 | 'anonymous_class' => false, 52 | ], 53 | 'no_closing_tag' => true, 54 | 'no_empty_phpdoc' => true, 55 | 'no_extra_blank_lines' => true, 56 | 'no_superfluous_phpdoc_tags' => [ 57 | 'allow_mixed' => true, 58 | ], 59 | 'no_trailing_whitespace' => true, 60 | 'no_trailing_whitespace_in_comment' => true, 61 | 'no_whitespace_in_blank_line' => true, 62 | 'no_unused_imports' => true, 63 | 'ordered_imports' => [ 64 | 'imports_order' => [ 65 | 'class', 66 | 'function', 67 | 'const', 68 | ], 69 | 'sort_algorithm' => 'alpha' 70 | ], 71 | 'phpdoc_align' => [ 72 | 'align' => 'vertical', 73 | 'tags' => [ 74 | 'param', 75 | ] 76 | ], 77 | 'phpdoc_line_span' => [ 78 | 'property' => 'single', 79 | 'method' => null, 80 | 'const' => null 81 | ], 82 | 'phpdoc_trim' => true, 83 | 'phpdoc_trim_consecutive_blank_line_separation' => true, 84 | 'return_type_declaration' => [ 85 | 'space_before' => 'one' 86 | ], 87 | 'single_blank_line_at_eof' => true, 88 | 'single_import_per_statement' => true, 89 | 'strict_param' => true, 90 | 'unary_operator_spaces' => true, 91 | ]) 92 | ->setFinder($finder) 93 | ->setIndent("\t") 94 | ->setLineEnding("\n"); 95 | -------------------------------------------------------------------------------- /.poggit.yml: -------------------------------------------------------------------------------- 1 | --- # Poggit-CI Manifest. Open the CI at https://poggit.pmmp.io/ci/jasonw4331/NativeDimensions 2 | projects: 3 | NativeDimensions: 4 | path: "" 5 | libs: 6 | - src: muqsit/simplepackethandler/SimplePacketHandler 7 | branch: pm5 8 | version: ^0.1.4 9 | icon: "icon.gif" 10 | excludeDirs: 11 | - .github 12 | - build 13 | excludeFiles: 14 | - .gitignore 15 | - CONTRIBUTING.md 16 | - SECURITY.md 17 | - phpstan.neon.dist 18 | - phpstan-baseline.php 19 | - .php-cs-fixer.php 20 | ... 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NativeDimensions 2 | [![Discord](https://img.shields.io/badge/chat-on%20discord-7289da.svg)](https://discord.gg/R7kdetE) 3 | [![Poggit-Ci](https://poggit.pmmp.io/ci.shield/jasonw4331/NativeDimensions/NativeDimensions)](https://poggit.pmmp.io/ci/jasonw4331/NativeDimensions/NativeDimensions) 4 | [![Download count](https://poggit.pmmp.io/shield.dl.total/NativeDimensions)](https://poggit.pmmp.io/p/NativeDimensions) 5 | 6 | # Intro 7 | Minecraft places all dimensions inside of a single world folder, but pocketmine doesn't use the data - until now. 8 | 9 | This plugin was built to add the missing functionality for pocketmine to load multiple dimensions from a single world save and have everything be accessible. 10 | 11 | This plugin even converts old anvil worlds to new leveldb worlds by itself! 12 | 13 | # Features 14 | * Automatically reads dimension data from anvil worlds 15 | * Automatically reads and writes dimension data to and from leveldb worlds 16 | * Automatic Anvil to LevelDB world conversion 17 | 18 | # Future Additions 19 | * Leveldb nether portal mapping 20 | * Batch writes for more efficiency 21 | 22 | # About 23 | This plugin has been a long-running project to allow the use of dimensions from within one world save instead of having multiple. This is a proof of concept for the first approach @dktapps laid out [in this RFC](https://forums.pmmp.io/threads/dimensions-support-and-new-level-dimensions-api.361/). 24 | -------------------------------------------------------------------------------- /build/generate-registry-annotations.php: -------------------------------------------------------------------------------- 1 | $member){ 64 | $reflect = new \ReflectionClass($member); 65 | while($reflect !== false && $reflect->isAnonymous()){ 66 | $reflect = $reflect->getParentClass(); 67 | } 68 | if($reflect === false){ 69 | $typehint = "object"; 70 | }elseif($reflect->getNamespaceName() === $namespaceName){ 71 | $typehint = $reflect->getShortName(); 72 | }else{ 73 | $typehint = '\\' . $reflect->getName(); 74 | } 75 | $accessor = mb_strtoupper($name); 76 | $memberLines[$accessor] = sprintf($lineTmpl, $accessor, $typehint); 77 | } 78 | ksort($memberLines, SORT_STRING); 79 | 80 | foreach($memberLines as $line){ 81 | $lines[] = $line; 82 | } 83 | $lines[] = " */"; 84 | return implode("\n", $lines); 85 | } 86 | 87 | function processFile(string $file) : void{ 88 | $contents = file_get_contents($file); 89 | if($contents === false){ 90 | throw new \RuntimeException("Failed to get contents of $file"); 91 | } 92 | 93 | if(preg_match("/(*ANYCRLF)^namespace (.+);$/m", $contents, $matches) !== 1 || preg_match('/(*ANYCRLF)^((final|abstract)\s+)?class /m', $contents) !== 1){ 94 | return; 95 | } 96 | $shortClassName = basename($file, ".php"); 97 | $className = $matches[1] . "\\" . $shortClassName; 98 | if(!class_exists($className)){ 99 | return; 100 | } 101 | $reflect = new \ReflectionClass($className); 102 | $docComment = $reflect->getDocComment(); 103 | if($docComment === false || preg_match("/(*ANYCRLF)^\s*\*\s*@generate-registry-docblock$/m", $docComment) !== 1){ 104 | return; 105 | } 106 | echo "Found registry in $file\n"; 107 | 108 | $replacement = generateMethodAnnotations($matches[1], $className::getAll()); 109 | 110 | $newContents = str_replace($docComment, $replacement, $contents); 111 | if($newContents !== $contents){ 112 | echo "Writing changed file $file\n"; 113 | file_put_contents($file, $newContents); 114 | }else{ 115 | echo "No changes made to file $file\n"; 116 | } 117 | } 118 | 119 | require dirname(__DIR__) . '/vendor/autoload.php'; 120 | //require dirname(__DIR__) . '/../../vendor/autoload.php'; 121 | 122 | if(is_dir($argv[1])){ 123 | /** @var string $file */ 124 | foreach(new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($argv[1], \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_PATHNAME)) as $file){ 125 | if(!str_ends_with($file, ".php")){ 126 | continue; 127 | } 128 | 129 | processFile($file); 130 | } 131 | }else{ 132 | processFile($argv[1]); 133 | } 134 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jasonw4331/nativedimensions", 3 | "description": "A plugin which loads dimensions within worlds and provides an API for managing dimensions across worlds", 4 | "type": "library", 5 | "license": "lgpl-3.0-or-later", 6 | "authors": [{ 7 | "name": "jasonw4331", 8 | "email": "jasonwynn10@gmail.com" 9 | }], 10 | "minimum-stability": "beta", 11 | "prefer-stable": true, 12 | "autoload": { 13 | "psr-4": { 14 | "jasonw4331\\NativeDimensions\\": "/src" 15 | } 16 | }, 17 | "require-dev": { 18 | "phpstan/phpstan": "^1", 19 | "pocketmine/pocketmine-mp": "^4.0|^5.0", 20 | "phpstan/phpstan-strict-rules": "^1.0", 21 | "phpstan/extension-installer": "^1.0", 22 | "friendsofphp/php-cs-fixer": "^3.11" 23 | }, 24 | "config": { 25 | "allow-plugins": { 26 | "phpstan/extension-installer": true 27 | } 28 | }, 29 | "scripts": { 30 | "make-baseline": "@php ./vendor/bin/phpstan analyze -b phpstan-baseline.php -c phpstan.neon.dist --allow-empty-baseline", 31 | "fix-codestyle": "@php ./vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.php --verbose --diff" 32 | } 33 | } -------------------------------------------------------------------------------- /icon.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jasonw4331/NativeDimensions/41d0b1272bcf9989a96660b2085d17cc385b9538/icon.gif -------------------------------------------------------------------------------- /phpstan-baseline.php: -------------------------------------------------------------------------------- 1 | '#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:setBlockAt\\(\\) expects int, float\\|int given\\.$#', 6 | 'count' => 2, 7 | 'path' => __DIR__ . '/src/Main.php', 8 | ]; 9 | $ignoreErrors[] = [ 10 | 'message' => '#^Parameter \\#2 \\$needle of function str_contains expects string, mixed given\\.$#', 11 | 'count' => 1, 12 | 'path' => __DIR__ . '/src/Main.php', 13 | ]; 14 | $ignoreErrors[] = [ 15 | 'message' => '#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:setBlockAt\\(\\) expects int, float\\|int given\\.$#', 16 | 'count' => 2, 17 | 'path' => __DIR__ . '/src/Main.php', 18 | ]; 19 | $ignoreErrors[] = [ 20 | 'message' => '#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:setBlockAt\\(\\) expects int, float\\|int given\\.$#', 21 | 'count' => 2, 22 | 'path' => __DIR__ . '/src/Main.php', 23 | ]; 24 | $ignoreErrors[] = [ 25 | 'message' => '#^Strict comparison using \\!\\=\\= between jasonw4331\\\\NativeDimensions\\\\world\\\\DimensionalWorld and null will always evaluate to true\\.$#', 26 | 'count' => 1, 27 | 'path' => __DIR__ . '/src/Main.php', 28 | ]; 29 | $ignoreErrors[] = [ 30 | 'message' => '#^Parameter \\#1 \\$world of static method jasonw4331\\\\NativeDimensions\\\\Main\\:\\:makeEndSpawn\\(\\) expects jasonw4331\\\\NativeDimensions\\\\world\\\\DimensionalWorld, pocketmine\\\\world\\\\World given\\.$#', 31 | 'count' => 1, 32 | 'path' => __DIR__ . '/src/block/EndPortal.php', 33 | ]; 34 | $ignoreErrors[] = [ 35 | 'message' => '#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#', 36 | 'count' => 12, 37 | 'path' => __DIR__ . '/src/block/NetherPortal.php', 38 | ]; 39 | $ignoreErrors[] = [ 40 | 'message' => '#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#', 41 | 'count' => 12, 42 | 'path' => __DIR__ . '/src/block/NetherPortal.php', 43 | ]; 44 | $ignoreErrors[] = [ 45 | 'message' => '#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#', 46 | 'count' => 12, 47 | 'path' => __DIR__ . '/src/block/NetherPortal.php', 48 | ]; 49 | $ignoreErrors[] = [ 50 | 'message' => '#^Call to an undefined method pocketmine\\\\block\\\\Block\\:\\:isSameType\\(\\)\\.$#', 51 | 'count' => 1, 52 | 'path' => __DIR__ . '/src/event/DimensionListener.php', 53 | ]; 54 | $ignoreErrors[] = [ 55 | 'message' => '#^Cannot call method getId\\(\\) on pocketmine\\\\player\\\\Player\\|null\\.$#', 56 | 'count' => 2, 57 | 'path' => __DIR__ . '/src/event/DimensionListener.php', 58 | ]; 59 | $ignoreErrors[] = [ 60 | 'message' => '#^Cannot call method getNetworkSession\\(\\) on pocketmine\\\\player\\\\Player\\|null\\.$#', 61 | 'count' => 1, 62 | 'path' => __DIR__ . '/src/event/DimensionListener.php', 63 | ]; 64 | $ignoreErrors[] = [ 65 | 'message' => '#^Cannot call method getPosition\\(\\) on pocketmine\\\\block\\\\Bed\\|null\\.$#', 66 | 'count' => 1, 67 | 'path' => __DIR__ . '/src/event/DimensionListener.php', 68 | ]; 69 | $ignoreErrors[] = [ 70 | 'message' => '#^Cannot call method getPosition\\(\\) on pocketmine\\\\player\\\\Player\\|null\\.$#', 71 | 'count' => 1, 72 | 'path' => __DIR__ . '/src/event/DimensionListener.php', 73 | ]; 74 | $ignoreErrors[] = [ 75 | 'message' => '#^Cannot call method getWorld\\(\\) on pocketmine\\\\player\\\\Player\\|null\\.$#', 76 | 'count' => 3, 77 | 'path' => __DIR__ . '/src/event/DimensionListener.php', 78 | ]; 79 | $ignoreErrors[] = [ 80 | 'message' => '#^Parameter \\#1 \\$direction of method jasonw4331\\\\NativeDimensions\\\\event\\\\DimensionListener\\:\\:testDirectionForObsidian\\(\\) expects int, int\\|null given\\.$#', 81 | 'count' => 1, 82 | 'path' => __DIR__ . '/src/event/DimensionListener.php', 83 | ]; 84 | $ignoreErrors[] = [ 85 | 'message' => '#^Parameter \\#1 \\$side of method pocketmine\\\\world\\\\Position\\:\\:getSide\\(\\) expects int, int\\|null given\\.$#', 86 | 'count' => 1, 87 | 'path' => __DIR__ . '/src/event/DimensionListener.php', 88 | ]; 89 | $ignoreErrors[] = [ 90 | 'message' => '#^Undefined variable\\: \\$widthA$#', 91 | 'count' => 1, 92 | 'path' => __DIR__ . '/src/event/DimensionListener.php', 93 | ]; 94 | $ignoreErrors[] = [ 95 | 'message' => '#^Undefined variable\\: \\$widthB$#', 96 | 'count' => 1, 97 | 'path' => __DIR__ . '/src/event/DimensionListener.php', 98 | ]; 99 | $ignoreErrors[] = [ 100 | 'message' => '#^Method jasonw4331\\\\NativeDimensions\\\\world\\\\DimensionalWorld\\:\\:getDimensionId\\(\\) should return 0\\|1\\|2 but returns int\\.$#', 101 | 'count' => 1, 102 | 'path' => __DIR__ . '/src/world/DimensionalWorld.php', 103 | ]; 104 | $ignoreErrors[] = [ 105 | 'message' => '#^Method jasonw4331\\\\NativeDimensions\\\\world\\\\DimensionalWorld\\:\\:getEnd\\(\\) should return pocketmine\\\\world\\\\World but returns pocketmine\\\\world\\\\World\\|null\\.$#', 106 | 'count' => 1, 107 | 'path' => __DIR__ . '/src/world/DimensionalWorld.php', 108 | ]; 109 | $ignoreErrors[] = [ 110 | 'message' => '#^Method jasonw4331\\\\NativeDimensions\\\\world\\\\DimensionalWorld\\:\\:getNether\\(\\) should return pocketmine\\\\world\\\\World but returns pocketmine\\\\world\\\\World\\|null\\.$#', 111 | 'count' => 1, 112 | 'path' => __DIR__ . '/src/world/DimensionalWorld.php', 113 | ]; 114 | $ignoreErrors[] = [ 115 | 'message' => '#^Method jasonw4331\\\\NativeDimensions\\\\world\\\\DimensionalWorld\\:\\:getOverworld\\(\\) should return pocketmine\\\\world\\\\World but returns pocketmine\\\\world\\\\World\\|null\\.$#', 116 | 'count' => 1, 117 | 'path' => __DIR__ . '/src/world/DimensionalWorld.php', 118 | ]; 119 | $ignoreErrors[] = [ 120 | 'message' => '#^Method pocketmine\\\\world\\\\format\\\\io\\\\WorldData\\:\\:getSpawn\\(\\) invoked with 1 parameter, 0 required\\.$#', 121 | 'count' => 1, 122 | 'path' => __DIR__ . '/src/world/DimensionalWorld.php', 123 | ]; 124 | $ignoreErrors[] = [ 125 | 'message' => '#^Method pocketmine\\\\world\\\\format\\\\io\\\\WorldData\\:\\:save\\(\\) invoked with 1 parameter, 0 required\\.$#', 126 | 'count' => 1, 127 | 'path' => __DIR__ . '/src/world/DimensionalWorld.php', 128 | ]; 129 | $ignoreErrors[] = [ 130 | 'message' => '#^Call to an undefined method pocketmine\\\\world\\\\format\\\\io\\\\WritableWorldProvider\\:\\:getDatabase\\(\\)\\.$#', 131 | 'count' => 2, 132 | 'path' => __DIR__ . '/src/world/DimensionalWorldManager.php', 133 | ]; 134 | $ignoreErrors[] = [ 135 | 'message' => '#^Method jasonw4331\\\\NativeDimensions\\\\world\\\\provider\\\\DimensionProviderManagerEntry\\:\\:fromPath\\(\\) invoked with 4 parameters, 2\\-3 required\\.$#', 136 | 'count' => 2, 137 | 'path' => __DIR__ . '/src/world/DimensionalWorldManager.php', 138 | ]; 139 | $ignoreErrors[] = [ 140 | 'message' => '#^Parameter \\#3 \\$provider of class jasonw4331\\\\NativeDimensions\\\\world\\\\DimensionalWorld constructor expects pocketmine\\\\world\\\\format\\\\io\\\\WritableWorldProvider, pocketmine\\\\world\\\\format\\\\io\\\\WorldProvider given\\.$#', 141 | 'count' => 2, 142 | 'path' => __DIR__ . '/src/world/DimensionalWorldManager.php', 143 | ]; 144 | $ignoreErrors[] = [ 145 | 'message' => '#^Property jasonw4331\\\\NativeDimensions\\\\world\\\\DimensionalWorldManager\\:\\:\\$worlds \\(array\\\\) does not accept array\\\\.$#', 146 | 'count' => 1, 147 | 'path' => __DIR__ . '/src/world/DimensionalWorldManager.php', 148 | ]; 149 | $ignoreErrors[] = [ 150 | 'message' => '#^jasonw4331\\\\NativeDimensions\\\\world\\\\DimensionalWorldManager\\:\\:__construct\\(\\) does not call parent constructor from pocketmine\\\\world\\\\WorldManager\\.$#', 151 | 'count' => 1, 152 | 'path' => __DIR__ . '/src/world/DimensionalWorldManager.php', 153 | ]; 154 | $ignoreErrors[] = [ 155 | 'message' => '#^Call to an undefined method pocketmine\\\\world\\\\format\\\\io\\\\LoadedChunkData\\:\\:getChunk\\(\\)\\.$#', 156 | 'count' => 1, 157 | 'path' => __DIR__ . '/src/world/converter/DimensionalFormatConverter.php', 158 | ]; 159 | $ignoreErrors[] = [ 160 | 'message' => '#^Call to an undefined method pocketmine\\\\world\\\\format\\\\io\\\\WritableWorldProvider\\:\\:getDatabase\\(\\)\\.$#', 161 | 'count' => 4, 162 | 'path' => __DIR__ . '/src/world/converter/DimensionalFormatConverter.php', 163 | ]; 164 | $ignoreErrors[] = [ 165 | 'message' => '#^Method jasonw4331\\\\NativeDimensions\\\\world\\\\converter\\\\DimensionalFormatConverter\\:\\:generateNew\\(\\) should return array\\ but returns array\\\\.$#', 166 | 'count' => 1, 167 | 'path' => __DIR__ . '/src/world/converter/DimensionalFormatConverter.php', 168 | ]; 169 | $ignoreErrors[] = [ 170 | 'message' => '#^Method pocketmine\\\\world\\\\format\\\\io\\\\WorldData\\:\\:save\\(\\) invoked with 1 parameter, 0 required\\.$#', 171 | 'count' => 1, 172 | 'path' => __DIR__ . '/src/world/converter/DimensionalFormatConverter.php', 173 | ]; 174 | $ignoreErrors[] = [ 175 | 'message' => '#^Parameter \\#2 \\$logger of method jasonw4331\\\\NativeDimensions\\\\world\\\\provider\\\\RewritableWorldProviderManagerEntry\\:\\:fromPath\\(\\) expects Logger, int given\\.$#', 176 | 'count' => 6, 177 | 'path' => __DIR__ . '/src/world/converter/DimensionalFormatConverter.php', 178 | ]; 179 | $ignoreErrors[] = [ 180 | 'message' => '#^Parameter \\#3 \\$chunkData of method jasonw4331\\\\NativeDimensions\\\\world\\\\provider\\\\DimensionLevelDBProvider\\:\\:saveChunk\\(\\) expects pocketmine\\\\world\\\\format\\\\io\\\\ChunkData, pocketmine\\\\world\\\\format\\\\io\\\\LoadedChunkData given\\.$#', 181 | 'count' => 1, 182 | 'path' => __DIR__ . '/src/world/converter/DimensionalFormatConverter.php', 183 | ]; 184 | $ignoreErrors[] = [ 185 | 'message' => '#^Cannot call method generateValues\\(\\) on jasonw4331\\\\NativeDimensions\\\\world\\\\generator\\\\biomegrid\\\\MapLayer\\|null\\.$#', 186 | 'count' => 1, 187 | 'path' => __DIR__ . '/src/world/generator/VanillaGenerator.php', 188 | ]; 189 | $ignoreErrors[] = [ 190 | 'message' => '#^Variable \\$y might not be defined\\.$#', 191 | 'count' => 1, 192 | 'path' => __DIR__ . '/src/world/generator/ender/populator/EnderPilar.php', 193 | ]; 194 | $ignoreErrors[] = [ 195 | 'message' => '#^Parameter \\#4 \\$biomeId of method pocketmine\\\\world\\\\format\\\\Chunk\\:\\:setBiomeId\\(\\) expects int, int\\|null given\\.$#', 196 | 'count' => 1, 197 | 'path' => __DIR__ . '/src/world/generator/nether/NetherGenerator.php', 198 | ]; 199 | $ignoreErrors[] = [ 200 | 'message' => '#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\ChunkManager\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#', 201 | 'count' => 1, 202 | 'path' => __DIR__ . '/src/world/generator/nether/decorator/GlowstoneDecorator.php', 203 | ]; 204 | $ignoreErrors[] = [ 205 | 'message' => '#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\ChunkManager\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#', 206 | 'count' => 1, 207 | 'path' => __DIR__ . '/src/world/generator/nether/decorator/GlowstoneDecorator.php', 208 | ]; 209 | $ignoreErrors[] = [ 210 | 'message' => '#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\ChunkManager\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#', 211 | 'count' => 1, 212 | 'path' => __DIR__ . '/src/world/generator/nether/decorator/GlowstoneDecorator.php', 213 | ]; 214 | $ignoreErrors[] = [ 215 | 'message' => '#^jasonw4331\\\\NativeDimensions\\\\world\\\\generator\\\\nether\\\\populator\\\\OrePopulator\\:\\:__construct\\(\\) does not call parent constructor from jasonw4331\\\\NativeDimensions\\\\world\\\\generator\\\\nether\\\\populator\\\\biome\\\\OrePopulator\\.$#', 216 | 'count' => 1, 217 | 'path' => __DIR__ . '/src/world/generator/nether/populator/OrePopulator.php', 218 | ]; 219 | $ignoreErrors[] = [ 220 | 'message' => '#^Variable variables are not allowed\\.$#', 221 | 'count' => 1, 222 | 'path' => __DIR__ . '/src/world/generator/noise/bukkit/SimplexNoiseGenerator.php', 223 | ]; 224 | $ignoreErrors[] = [ 225 | 'message' => '#^jasonw4331\\\\NativeDimensions\\\\world\\\\generator\\\\noise\\\\glowstone\\\\PerlinNoise\\:\\:__construct\\(\\) does not call parent constructor from jasonw4331\\\\NativeDimensions\\\\world\\\\generator\\\\noise\\\\bukkit\\\\BasePerlinNoiseGenerator\\.$#', 226 | 'count' => 1, 227 | 'path' => __DIR__ . '/src/world/generator/noise/glowstone/PerlinNoise.php', 228 | ]; 229 | $ignoreErrors[] = [ 230 | 'message' => '#^Static property jasonw4331\\\\NativeDimensions\\\\world\\\\generator\\\\object\\\\TerrainObject\\:\\:\\$PLANT_TYPES is never read, only written\\.$#', 231 | 'count' => 1, 232 | 'path' => __DIR__ . '/src/world/generator/object/TerrainObject.php', 233 | ]; 234 | $ignoreErrors[] = [ 235 | 'message' => '#^Part \\$value \\(mixed\\) of encapsed string cannot be cast to string\\.$#', 236 | 'count' => 1, 237 | 'path' => __DIR__ . '/src/world/generator/utils/preset/SimpleGeneratorPreset.php', 238 | ]; 239 | $ignoreErrors[] = [ 240 | 'message' => '#^Call to an undefined method pocketmine\\\\world\\\\format\\\\io\\\\ChunkData\\:\\:getChunk\\(\\)\\.$#', 241 | 'count' => 1, 242 | 'path' => __DIR__ . '/src/world/provider/DimensionLevelDBProvider.php', 243 | ]; 244 | $ignoreErrors[] = [ 245 | 'message' => '#^Class pocketmine\\\\world\\\\format\\\\io\\\\ChunkData constructor invoked with 3 parameters, 4 required\\.$#', 246 | 'count' => 1, 247 | 'path' => __DIR__ . '/src/world/provider/DimensionLevelDBProvider.php', 248 | ]; 249 | $ignoreErrors[] = [ 250 | 'message' => '#^Parameter \\#1 \\$subChunks of class pocketmine\\\\world\\\\format\\\\io\\\\ChunkData constructor expects array\\, pocketmine\\\\world\\\\format\\\\Chunk given\\.$#', 251 | 'count' => 1, 252 | 'path' => __DIR__ . '/src/world/provider/DimensionLevelDBProvider.php', 253 | ]; 254 | $ignoreErrors[] = [ 255 | 'message' => '#^Parameter \\#2 \\$populated of class pocketmine\\\\world\\\\format\\\\io\\\\ChunkData constructor expects bool, array\\ given\\.$#', 256 | 'count' => 1, 257 | 'path' => __DIR__ . '/src/world/provider/DimensionLevelDBProvider.php', 258 | ]; 259 | $ignoreErrors[] = [ 260 | 'message' => '#^jasonw4331\\\\NativeDimensions\\\\world\\\\provider\\\\DimensionLevelDBProvider\\:\\:__construct\\(\\) does not call parent constructor from pocketmine\\\\world\\\\format\\\\io\\\\leveldb\\\\LevelDB\\.$#', 261 | 'count' => 1, 262 | 'path' => __DIR__ . '/src/world/provider/DimensionLevelDBProvider.php', 263 | ]; 264 | $ignoreErrors[] = [ 265 | 'message' => '#^Parameter \\#2 \\$logger of class jasonw4331\\\\NativeDimensions\\\\world\\\\provider\\\\DimensionLevelDBProvider constructor expects Logger, int given\\.$#', 266 | 'count' => 1, 267 | 'path' => __DIR__ . '/src/world/provider/DimensionalWorldProviderManager.php', 268 | ]; 269 | $ignoreErrors[] = [ 270 | 'message' => '#^Parameter \\#3 \\$dimension of class jasonw4331\\\\NativeDimensions\\\\world\\\\provider\\\\DimensionLevelDBProvider constructor expects int, LevelDB\\|null given\\.$#', 271 | 'count' => 1, 272 | 'path' => __DIR__ . '/src/world/provider/DimensionalWorldProviderManager.php', 273 | ]; 274 | $ignoreErrors[] = [ 275 | 'message' => '#^Argument of an invalid type array\\\\|false supplied for foreach, only iterables are supported\\.$#', 276 | 'count' => 1, 277 | 'path' => __DIR__ . '/src/world/provider/EnderAnvilProvider.php', 278 | ]; 279 | $ignoreErrors[] = [ 280 | 'message' => '#^Cannot access offset 1 on mixed\\.$#', 281 | 'count' => 2, 282 | 'path' => __DIR__ . '/src/world/provider/EnderAnvilProvider.php', 283 | ]; 284 | $ignoreErrors[] = [ 285 | 'message' => '#^Cannot access offset 2 on mixed\\.$#', 286 | 'count' => 2, 287 | 'path' => __DIR__ . '/src/world/provider/EnderAnvilProvider.php', 288 | ]; 289 | $ignoreErrors[] = [ 290 | 'message' => '#^Cannot cast mixed to int\\.$#', 291 | 'count' => 4, 292 | 'path' => __DIR__ . '/src/world/provider/EnderAnvilProvider.php', 293 | ]; 294 | $ignoreErrors[] = [ 295 | 'message' => '#^Argument of an invalid type array\\\\|false supplied for foreach, only iterables are supported\\.$#', 296 | 'count' => 1, 297 | 'path' => __DIR__ . '/src/world/provider/NetherAnvilProvider.php', 298 | ]; 299 | $ignoreErrors[] = [ 300 | 'message' => '#^Cannot access offset 1 on mixed\\.$#', 301 | 'count' => 2, 302 | 'path' => __DIR__ . '/src/world/provider/NetherAnvilProvider.php', 303 | ]; 304 | $ignoreErrors[] = [ 305 | 'message' => '#^Cannot access offset 2 on mixed\\.$#', 306 | 'count' => 2, 307 | 'path' => __DIR__ . '/src/world/provider/NetherAnvilProvider.php', 308 | ]; 309 | $ignoreErrors[] = [ 310 | 'message' => '#^Cannot cast mixed to int\\.$#', 311 | 'count' => 4, 312 | 'path' => __DIR__ . '/src/world/provider/NetherAnvilProvider.php', 313 | ]; 314 | $ignoreErrors[] = [ 315 | 'message' => '#^Parameter \\#3 \\$db of closure expects LevelDB, LevelDB\\|null given\\.$#', 316 | 'count' => 1, 317 | 'path' => __DIR__ . '/src/world/provider/RewritableWorldProviderManagerEntry.php', 318 | ]; 319 | $ignoreErrors[] = [ 320 | 'message' => '#^Property jasonw4331\\\\NativeDimensions\\\\world\\\\provider\\\\RewritableWorldProviderManagerEntry\\:\\:\\$hasDimensions has no type specified\\.$#', 321 | 'count' => 1, 322 | 'path' => __DIR__ . '/src/world/provider/RewritableWorldProviderManagerEntry.php', 323 | ]; 324 | 325 | return ['parameters' => ['ignoreErrors' => $ignoreErrors]]; 326 | -------------------------------------------------------------------------------- /phpstan.neon.dist: -------------------------------------------------------------------------------- 1 | includes: 2 | - phpstan-baseline.php 3 | 4 | parameters: 5 | level: 9 6 | paths: 7 | - src -------------------------------------------------------------------------------- /plugin.yml: -------------------------------------------------------------------------------- 1 | name: NativeDimensions 2 | main: jasonw4331\NativeDimensions\Main 3 | src-namespace-prefix: jasonw4331\NativeDimensions 4 | version: 1.0.0 5 | api: 5.0.0 6 | description: "A plugin which loads dimensions within worlds and provides an API for managing dimensions across worlds" 7 | author: "jasonw4331" 8 | load: POSTWORLD -------------------------------------------------------------------------------- /src/event/DimensionPortalsEvent.php: -------------------------------------------------------------------------------- 1 | getServer()->getPluginManager()->registerEvents($this, $plugin); 21 | } 22 | 23 | /** 24 | * @param EntityTeleportEvent $event 25 | * @priority MONITOR 26 | */ 27 | public function onEntityTeleport(EntityTeleportEvent $event) : void{ 28 | $player = $event->getEntity(); 29 | if($player instanceof Player){ 30 | /** @var DimensionalWorld $from_world */ 31 | $from_world = $event->getFrom()->getWorld(); 32 | $to = $event->getTo(); 33 | /** @var DimensionalWorld $to_world */ 34 | $to_world = $to->getWorld(); 35 | if($from_world->dimensionId !== $to_world->dimensionId){ 36 | // Player can be null if a plugin teleports the player before PlayerLoginEvent @ MONITOR 37 | PlayerManager::getNullable($player)?->onBeginDimensionChange($to_world->dimensionId, $to->asVector3(), !$player->isAlive()); 38 | } 39 | } 40 | } 41 | 42 | public function onSleep(PlayerBedEnterEvent $event) : void{ 43 | $bed = $event->getBed(); 44 | if(!$bed instanceof Bed) 45 | return; 46 | $pos = $bed->isHeadPart() ? $bed->getPosition() : $bed->getOtherHalf()->getPosition(); 47 | /** @var DimensionalWorld $world */ 48 | $world = $pos->getWorld(); 49 | if($world->getOverworld() !== $world){ 50 | $event->cancel(); 51 | $explosion = new Explosion($pos, 5, $event->getBed()); 52 | $explosion->explodeA(); 53 | $explosion->explodeB(); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /src/event/player/PlayerCreateEndPortalEvent.php: -------------------------------------------------------------------------------- 1 | $frame_blocks 22 | * @param BlockTransaction $transaction 23 | */ 24 | public function __construct( 25 | readonly public Player $player, 26 | readonly public Position $block_pos, 27 | readonly public array $frame_blocks, 28 | readonly public BlockTransaction $transaction 29 | ){} 30 | 31 | final public function getPlayer() : Player{ 32 | return $this->player; 33 | } 34 | 35 | final public function getBlockPos() : Position{ 36 | return $this->block_pos; 37 | } 38 | } -------------------------------------------------------------------------------- /src/event/player/PlayerEnterPortalEvent.php: -------------------------------------------------------------------------------- 1 | player; 26 | } 27 | 28 | public function getBlock() : PortalExoBlock{ 29 | return $this->block; 30 | } 31 | } -------------------------------------------------------------------------------- /src/event/player/PlayerPortalTeleportEvent.php: -------------------------------------------------------------------------------- 1 | player; 27 | } 28 | 29 | public function getBlock() : PortalExoBlock{ 30 | return $this->block; 31 | } 32 | 33 | public function getTarget() : Location{ 34 | return $this->target->asLocation(); 35 | } 36 | 37 | public function setTarget(Location $target) : void{ 38 | $this->target = $target->asLocation(); 39 | } 40 | } -------------------------------------------------------------------------------- /src/exoblock/EndPortalExoBlock.php: -------------------------------------------------------------------------------- 1 | getSide($side); 31 | $type_id = $transaction->fetchBlockAt($side_pos->x, $side_pos->y, $side_pos->z)->getTypeId(); 32 | if($type_id !== VanillaBlocks::END_PORTAL_FRAME()->getTypeId() && $type_id !== ExtraVanillaBlocks::END_PORTAL()->getTypeId()){ 33 | return false; 34 | } 35 | } 36 | return true; 37 | } 38 | 39 | public function update(Block $wrapping) : bool{ 40 | $pos = $wrapping->getPosition(); 41 | if(!$this->meetsSupportConditions(new BlockTransaction($pos->getWorld()), $pos)){ 42 | WorldUtils::removeTouchingBlocks($pos->getWorld(), ExtraVanillaBlocks::END_PORTAL()->getTypeId(), $pos, Facing::HORIZONTAL)?->apply(); 43 | } 44 | return false; 45 | } 46 | } -------------------------------------------------------------------------------- /src/exoblock/EndPortalFrameExoBlock.php: -------------------------------------------------------------------------------- 1 | hasEye()){ 24 | if($item->getTypeId() === ExtraVanillaItems::ENDER_EYE()->getTypeId()){ 25 | $pos = $wrapping->getPosition(); 26 | $transaction = new BlockTransaction($pos->getWorld()); 27 | $transaction->addBlockAt($pos->x, $pos->y, $pos->z, (clone $wrapping)->setEye(true)); 28 | $center = $this->findPortalCenterFromFrame($wrapping); 29 | if($center !== null){ 30 | $frame_blocks = $this->collectFrameBlocks($transaction, $center); 31 | if($frame_blocks !== null){ 32 | $this->createPortal($transaction, $center); 33 | ($ev = new PlayerCreateEndPortalEvent($player, $pos, $frame_blocks, $transaction))->call(); 34 | if($ev->isCancelled()){ 35 | return true; 36 | } 37 | } 38 | } 39 | if($transaction->apply()){ 40 | $item->pop(); 41 | } 42 | } 43 | }elseif($item->getTypeId() !== ExtraVanillaItems::ENDER_EYE()->getTypeId()){ 44 | $pos = $wrapping->getPosition(); 45 | $world = $pos->getWorld(); 46 | $transaction = new BlockTransaction($world); 47 | $transaction->addBlockAt($pos->x, $pos->y, $pos->z, (clone $wrapping)->setEye(false)); 48 | $center = $this->findPortalCenterFromFrame($wrapping); 49 | if($center !== null && $this->collectFrameBlocks($transaction, $center) === null){ 50 | $this->destroyPortal($transaction, $center); 51 | $transaction->apply(); 52 | $world->dropItem($pos->add(0.5, 0.75, 0.5), ExtraVanillaItems::ENDER_EYE()); 53 | } 54 | return true; 55 | } 56 | return false; 57 | } 58 | 59 | public function update(Block $wrapping) : bool{ 60 | return false; 61 | } 62 | 63 | public function onPlayerMoveInside(Player $player, Block $block) : void{ 64 | } 65 | 66 | public function onPlayerMoveOutside(Player $player, Block $block) : void{ 67 | } 68 | 69 | public function findPortalCenterFromFrame(EndPortalFrame $block) : ?Vector3{ 70 | $facing = $block->getFacing(); 71 | $pos = $block->getPosition(); 72 | $left = $block->getSide(Facing::rotateY($facing, false))->hasSameTypeId($block); 73 | $right = $block->getSide(Facing::rotateY($facing, true))->hasSameTypeId($block); 74 | if($left && $right){ 75 | return $pos->getSide($facing, 2); 76 | } 77 | if($left){ 78 | return $pos->getSide($facing, 2)->getSide(Facing::rotateY($facing, false)); 79 | } 80 | if($right){ 81 | return $pos->getSide($facing, 2)->getSide(Facing::rotateY($facing, true)); 82 | } 83 | $facing_block = $block->getSide($facing); 84 | if($facing_block->getSide(Facing::rotateY($facing, false))->hasSameTypeId($block)){ 85 | return $pos->getSide($facing, 2)->getSide(Facing::rotateY($facing, true)); 86 | } 87 | if($facing_block->getSide(Facing::rotateY($facing, true))->hasSameTypeId($block)){ 88 | return $pos->getSide($facing, 2)->getSide(Facing::rotateY($facing, false)); 89 | } 90 | return null; 91 | } 92 | 93 | /** 94 | * @param BlockTransaction $transaction 95 | * @param Vector3 $center 96 | * 97 | * @return list|null 98 | */ 99 | public function collectFrameBlocks(BlockTransaction $transaction, Vector3 $center) : ?array{ 100 | $blocks = []; 101 | foreach(Facing::HORIZONTAL as $side){ 102 | $pos = $center->getSide($side, 2); 103 | $left = $pos->getSide(Facing::rotateY($side, false)); 104 | $right = $pos->getSide(Facing::rotateY($side, true)); 105 | foreach([ 106 | $transaction->fetchBlockAt($pos->x, $pos->y, $pos->z), 107 | $transaction->fetchBlockAt($left->x, $left->y, $left->z), 108 | $transaction->fetchBlockAt($right->x, $right->y, $right->z) 109 | ] as $block){ 110 | if(!($block instanceof EndPortalFrame) || !$block->hasEye()){ 111 | return null; 112 | } 113 | $blocks[] = $block; 114 | } 115 | } 116 | return $blocks; 117 | } 118 | 119 | public function createPortal(BlockTransaction $transaction, Vector3 $center) : void{ 120 | $type_id = ExtraVanillaBlocks::END_PORTAL()->getTypeId(); 121 | for($i = -1; $i <= 1; ++$i){ 122 | for($j = -1; $j <= 1; ++$j){ 123 | if($transaction->fetchBlockAt($center->x + $i, $center->y, $center->z + $j) !== $type_id){ 124 | $transaction->addBlockAt($center->x + $i, $center->y, $center->z + $j, $this->portal_block); 125 | } 126 | } 127 | } 128 | } 129 | 130 | public function destroyPortal(BlockTransaction $transaction, Vector3 $center) : void{ 131 | $type_id = ExtraVanillaBlocks::END_PORTAL()->getTypeId(); 132 | for($i = -1; $i <= 1; ++$i){ 133 | for($j = -1; $j <= 1; ++$j){ 134 | if($transaction->fetchBlockAt($center->x + $i, $center->y, $center->z + $j)->getTypeId() === $type_id){ 135 | $transaction->addBlockAt($center->x + $i, $center->y, $center->z + $j, VanillaBlocks::AIR()); 136 | } 137 | } 138 | } 139 | } 140 | } -------------------------------------------------------------------------------- /src/exoblock/ExoBlock.php: -------------------------------------------------------------------------------- 1 | getBlock(); 20 | $exo_block = ExoBlockFactory::get($block); 21 | if($exo_block !== null && $exo_block->update($block)){ 22 | $event->cancel(); 23 | } 24 | } 25 | 26 | /** 27 | * @param PlayerInteractEvent $event 28 | * @priority NORMAL 29 | */ 30 | public function onPlayerInteract(PlayerInteractEvent $event) : void{ 31 | if($event->getAction() === PlayerInteractEvent::RIGHT_CLICK_BLOCK){ 32 | $block = $event->getBlock(); 33 | $exo_block = ExoBlockFactory::get($block); 34 | if($exo_block !== null && $exo_block->interact($block, $event->getPlayer(), $event->getItem(), $event->getFace())){ 35 | $event->cancel(); 36 | } 37 | } 38 | } 39 | 40 | /** 41 | * @param PlayerMoveEvent $event 42 | * @priority MONITOR 43 | */ 44 | public function onPlayerMove(PlayerMoveEvent $event) : void{ 45 | $from = $event->getFrom(); 46 | $from_f = $from->floor(); 47 | 48 | $to = $event->getTo(); 49 | $to_f = $to->floor(); 50 | 51 | if(!$from_f->equals($to_f)){ 52 | $player = $event->getPlayer(); 53 | 54 | $from_block = ExoBlockFactory::get($block = $from->world->getBlockAt($from_f->x, $from_f->y, $from_f->z)); 55 | $from_block?->onPlayerMoveOutside($player, $block); 56 | 57 | $to_block = ExoBlockFactory::get($block = $to->world->getBlockAt($to_f->x, $to_f->y, $to_f->z)); 58 | $to_block?->onPlayerMoveInside($player, $block); 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /src/exoblock/ExoBlockFactory.php: -------------------------------------------------------------------------------- 1 | getServer()->getPluginManager()->registerEvents(new ExoBlockEventHandler(), $loader); 20 | self::initNether(); 21 | self::initEnd(); 22 | } 23 | 24 | private static function initNether() : void{ 25 | self::register( 26 | new NetherPortalFrameExoBlock( 27 | 24, 28 | 24 29 | ), 30 | VanillaBlocks::OBSIDIAN() 31 | ); 32 | self::register(new NetherPortalExoBlock(), VanillaBlocks::NETHER_PORTAL()); 33 | } 34 | 35 | private static function initEnd() : void{ 36 | self::register(new EndPortalFrameExoBlock(), VanillaBlocks::END_PORTAL_FRAME()); 37 | self::register(new EndPortalExoBlock(), ExtraVanillaBlocks::END_PORTAL()); 38 | } 39 | 40 | public static function register(ExoBlock $exo_block, Block $block) : void{ 41 | self::$blocks[$block->getStateId()] = $exo_block; 42 | foreach(RuntimeBlockStateRegistry::getInstance()->getAllKnownStates() as $state){ 43 | if($state->getTypeId() === $block->getTypeId()){ 44 | self::$blocks[$state->getStateId()] = $exo_block; 45 | } 46 | } 47 | } 48 | 49 | public static function get(Block $block) : ?ExoBlock{ 50 | return self::$blocks[$block->getStateId()] ?? null; 51 | } 52 | } -------------------------------------------------------------------------------- /src/exoblock/NetherPortalExoBlock.php: -------------------------------------------------------------------------------- 1 | y < World::Y_MAX - 1){ 35 | $faces[] = Facing::UP; 36 | } 37 | if($pos->y > World::Y_MIN){ 38 | $faces[] = Facing::DOWN; 39 | } 40 | $portal_block = $transaction->fetchBlockAt($pos->x, $pos->y, $pos->z); 41 | if($portal_block instanceof NetherPortal){ 42 | $axis = $portal_block->getAxis(); 43 | }else{ 44 | $axis = Axis::Z; 45 | } 46 | if($axis === Axis::Z){ 47 | $faces[] = Facing::SOUTH; 48 | $faces[] = Facing::NORTH; 49 | }else{ 50 | assert($axis === Axis::X); 51 | $faces[] = Facing::WEST; 52 | $faces[] = Facing::EAST; 53 | } 54 | foreach($faces as $face){ 55 | $side_pos = $pos->getSide($face); 56 | $block = $transaction->fetchBlockAt($side_pos->x, $side_pos->y, $side_pos->z); 57 | if(!$this->isValid($block)){ 58 | return false; 59 | } 60 | } 61 | return true; 62 | } 63 | 64 | public function update(Block $wrapping) : bool{ 65 | assert($wrapping instanceof NetherPortal); 66 | $pos = $wrapping->getPosition(); 67 | $world = $pos->getWorld(); 68 | if(!$this->meetsSupportConditions(new BlockTransaction($world), $pos)){ 69 | $check_sides = [Facing::UP, Facing::DOWN]; 70 | $axis = $wrapping->getAxis(); 71 | if($axis === Axis::X){ 72 | $check_sides[] = Facing::EAST; 73 | $check_sides[] = Facing::WEST; 74 | }else{ 75 | assert($axis === Axis::Z); 76 | $check_sides[] = Facing::NORTH; 77 | $check_sides[] = Facing::SOUTH; 78 | } 79 | return WorldUtils::removeTouchingBlocks($world, ExtraVanillaBlocks::END_PORTAL()->getTypeId(), $pos, $check_sides)?->apply() ?? false; 80 | } 81 | return false; 82 | } 83 | 84 | public function interact(Block $wrapping, Player $player, Item $item, int $face) : bool{ 85 | return false; 86 | } 87 | 88 | public function isValid(Block $block) : bool{ 89 | $blockId = $block->getTypeId(); 90 | return $blockId === VanillaBlocks::END_PORTAL_FRAME()->getTypeId() || $blockId === ExtraVanillaBlocks::END_PORTAL()->getTypeId(); 91 | } 92 | } -------------------------------------------------------------------------------- /src/exoblock/NetherPortalFrameExoBlock.php: -------------------------------------------------------------------------------- 1 | length_squared = (new Vector2($max_portal_height, $max_portal_width))->lengthSquared(); 30 | } 31 | 32 | public function interact(Block $wrapping, Player $player, Item $item, int $face) : bool{ 33 | if($item->getTypeId() === ItemTypeIds::FLINT_AND_STEEL){ 34 | $affectedBlock = $wrapping->getSide($face); 35 | if($affectedBlock->getTypeId() === BlockTypeIds::AIR){ 36 | $world = $player->getWorld(); 37 | $pos = $affectedBlock->getPosition(); 38 | $transaction = $this->fill($world, $pos, Axis::X, $frame_blocks) ?? $this->fill($world, $pos, Axis::Z, $frame_blocks); 39 | if($transaction !== null){ 40 | ($ev = new PlayerCreateNetherPortalEvent($player, $wrapping->getPosition(), $frame_blocks, $transaction))->call(); 41 | if(!$ev->isCancelled()){ 42 | $transaction->apply(); 43 | return true; 44 | } 45 | } 46 | } 47 | } 48 | return false; 49 | } 50 | 51 | public function update(Block $wrapping) : bool{ 52 | return false; 53 | } 54 | 55 | public function onPlayerMoveInside(Player $player, Block $block) : void{ 56 | } 57 | 58 | public function onPlayerMoveOutside(Player $player, Block $block) : void{ 59 | } 60 | 61 | /** 62 | * @param World $world 63 | * @param Vector3 $origin 64 | * @param Axis::X|Axis::Z $axis 65 | * @param list|null $frame_blocks 66 | * @param-out list $frame_blocks 67 | * 68 | * @return BlockTransaction|null 69 | */ 70 | public function fill(World $world, Vector3 $origin, int $axis, ?array &$frame_blocks) : ?BlockTransaction{ 71 | $visits = new SplQueue(); 72 | $visits->enqueue($origin); 73 | $portal_block = (clone ExtraVanillaBlocks::END_PORTAL())->setAxis($axis); 74 | $portal_block_id = $portal_block->getTypeId(); 75 | $transaction = new BlockTransaction($world); 76 | $changed = 0; 77 | $frame_blocks = []; 78 | while(!$visits->isEmpty()){ 79 | /** @var Vector3 $coordinates */ 80 | $coordinates = $visits->dequeue(); 81 | if($origin->distanceSquared($coordinates) >= $this->length_squared){ 82 | return null; 83 | } 84 | 85 | if($transaction->fetchBlockAt($coordinates->x, $coordinates->y, $coordinates->z)->getTypeId() === $portal_block_id){ 86 | continue; 87 | } 88 | 89 | $block = $world->getBlockAt($coordinates->x, $coordinates->y, $coordinates->z); 90 | $block_type_id = $block->getTypeId(); 91 | if($block_type_id === BlockTypeIds::AIR){ 92 | $transaction->addBlockAt($coordinates->x, $coordinates->y, $coordinates->z, $portal_block); 93 | if($axis === Axis::Z){ 94 | $visits->enqueue($coordinates->getSide(Facing::NORTH)); 95 | $visits->enqueue($coordinates->getSide(Facing::SOUTH)); 96 | }else{ 97 | assert($axis === Axis::X); 98 | $visits->enqueue($coordinates->getSide(Facing::WEST)); 99 | $visits->enqueue($coordinates->getSide(Facing::EAST)); 100 | } 101 | $visits->enqueue($coordinates->getSide(Facing::UP)); 102 | $visits->enqueue($coordinates->getSide(Facing::DOWN)); 103 | $changed++; 104 | }elseif($block_type_id !== VanillaBlocks::END_PORTAL_FRAME()->getTypeId()){ 105 | return null; 106 | }else{ 107 | $frame_blocks[] = $block; 108 | } 109 | } 110 | return $changed > 0 ? $transaction : null; 111 | } 112 | } -------------------------------------------------------------------------------- /src/exoblock/PortalExoBlock.php: -------------------------------------------------------------------------------- 1 | onEnterPortal($this, $block->getPosition()); 25 | } 26 | 27 | public function onPlayerMoveOutside(Player $player, Block $block) : void{ 28 | PlayerManager::get($player)->onLeavePortal(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/player/PlayerDimensionChangeListener.php: -------------------------------------------------------------------------------- 1 | isChangingDimension()){ 23 | assert(method_exists($event, "cancel")); 24 | $event->cancel(); 25 | return true; 26 | } 27 | return false; 28 | } 29 | 30 | /** 31 | * @param EntityDamageEvent $event 32 | * @priority LOW 33 | */ 34 | public function onEntityDamage(EntityDamageEvent $event) : void{ 35 | $entity = $event->getEntity(); 36 | if($entity instanceof Player && $this->cancelIfChangingDimension($entity, $event)){ 37 | return; 38 | } 39 | if($event instanceof EntityDamageByEntityEvent){ 40 | $damager = $event->getDamager(); 41 | if($damager instanceof Player){ 42 | $this->cancelIfChangingDimension($damager, $event); 43 | } 44 | } 45 | } 46 | 47 | /** 48 | * @param PlayerInteractEvent $event 49 | * @priority LOW 50 | */ 51 | public function onPlayerInteract(PlayerInteractEvent $event) : void{ 52 | $this->cancelIfChangingDimension($event->getPlayer(), $event); 53 | } 54 | 55 | /** 56 | * @param PlayerItemUseEvent $event 57 | * @priority LOW 58 | */ 59 | public function onPlayerItemUse(PlayerItemUseEvent $event) : void{ 60 | $this->cancelIfChangingDimension($event->getPlayer(), $event); 61 | } 62 | 63 | /** 64 | * @param PlayerMoveEvent $event 65 | * @priority LOW 66 | */ 67 | public function onPlayerMove(PlayerMoveEvent $event) : void{ 68 | $this->cancelIfChangingDimension($event->getPlayer(), $event); 69 | } 70 | 71 | /** 72 | * @param BlockPlaceEvent $event 73 | * @priority LOW 74 | */ 75 | public function onBlockPlace(BlockPlaceEvent $event) : void{ 76 | $this->cancelIfChangingDimension($event->getPlayer(), $event); 77 | } 78 | 79 | /** 80 | * @param BlockBreakEvent $event 81 | * @priority LOW 82 | */ 83 | public function onBlockBreak(BlockBreakEvent $event) : void{ 84 | $this->cancelIfChangingDimension($event->getPlayer(), $event); 85 | } 86 | } -------------------------------------------------------------------------------- /src/player/PlayerInstance.php: -------------------------------------------------------------------------------- 1 | player = $player; 32 | $this->logger = new PrefixedLogger($logger, $player->getName()); 33 | 34 | static $_chunksPerTick = null; 35 | $_chunksPerTick ??= new ReflectionProperty(Player::class, "chunksPerTick"); 36 | $this->_chunksPerTick = $_chunksPerTick; 37 | } 38 | 39 | public function onEnterPortal(PortalExoBlock $block, Position $position) : void{ 40 | ($ev = new PlayerEnterPortalEvent($this->player, $block, $position, $this->player->isCreative() ? 0 : $block->teleportation_duration))->call(); 41 | if(!$ev->isCancelled()){ 42 | $this->in_portal = new PlayerPortalInfo($block, $position, $ev->teleport_duration); 43 | PlayerManager::scheduleTicking($this->player); 44 | } 45 | } 46 | 47 | public function onLeavePortal() : void{ 48 | PlayerManager::stopTicking($this->player); 49 | $this->in_portal = null; 50 | } 51 | 52 | public function onBeginDimensionChange(int $network_dimension_id, Vector3 $position, bool $respawn) : void{ 53 | $session = $this->player->getNetworkSession(); 54 | PlayerManager::$_changing_dimension_sessions[spl_object_id($session)] = true; 55 | $this->changing_dimension = true; 56 | $this->chunks_per_tick_before_change = $this->_chunksPerTick->getValue($this->player); 57 | if($this->chunks_per_tick_before_change < 40){ 58 | $this->_chunksPerTick->setValue($this->player, 40); 59 | } 60 | $session->sendDataPacket(ChangeDimensionPacket::create($network_dimension_id, $position, $respawn)); 61 | PlayerManager::$_queue_fast_dimension_change_request[$id = $this->player->getId()] = $id; 62 | $this->logger->debug("Started changing dimension (network_dimension_id: {$network_dimension_id}, position: {$position->asVector3()}, respawn: " . ($respawn ? "true" : "false") . ")"); 63 | } 64 | 65 | public function onEndDimensionChange() : void{ 66 | $session = $this->player->getNetworkSession(); 67 | unset(PlayerManager::$_changing_dimension_sessions[spl_object_id($session)]); 68 | $this->changing_dimension = false; 69 | $this->_chunksPerTick->setValue($this->player, $this->chunks_per_tick_before_change); 70 | $this->logger->debug("Stopped changing dimension"); 71 | } 72 | 73 | /** 74 | * Returns whether the player is on the dimension 75 | * changing screen. 76 | * 77 | * @return bool 78 | */ 79 | public function isChangingDimension() : bool{ 80 | return $this->changing_dimension; 81 | } 82 | 83 | public function tick() : void{ 84 | if($this->in_portal->tick()){ 85 | $this->teleport(); 86 | $this->onLeavePortal(); 87 | } 88 | } 89 | 90 | private function teleport() : void{ 91 | $to = $this->in_portal->block->getTargetWorldDimensionId(); 92 | $world = match ($to) { 93 | DimensionIds::OVERWORLD => $this->player->getWorld()->getOverworld(), 94 | DimensionIds::NETHER => $this->player->getWorld()->getNether(), 95 | DimensionIds::THE_END => $this->player->getWorld()->getEnd(), 96 | default => throw new \Error("Unknown dimension ID: $to") 97 | }; 98 | $target = Location::fromObject($world->getSpawnLocation(), $world, 0.0, 0.0); 99 | ($ev = new PlayerPortalTeleportEvent($this->player, $this->in_portal->block, $this->in_portal->block_position, $target))->call(); 100 | if(!$ev->isCancelled()){ 101 | $this->player->teleport($ev->target); 102 | } 103 | } 104 | } -------------------------------------------------------------------------------- /src/player/PlayerListener.php: -------------------------------------------------------------------------------- 1 | getPlayer(), $this->logger); 24 | } 25 | 26 | /** 27 | * @param PlayerQuitEvent $event 28 | * @priority MONITOR 29 | */ 30 | public function onPlayerQuit(PlayerQuitEvent $event) : void{ 31 | PlayerManager::destroy($event->getPlayer()); 32 | } 33 | } -------------------------------------------------------------------------------- /src/player/PlayerManager.php: -------------------------------------------------------------------------------- 1 | getServer()->getPluginManager()->registerEvents(new PlayerListener($plugin->getLogger()), $plugin); 37 | $plugin->getServer()->getPluginManager()->registerEvents(new PlayerDimensionChangeListener(), $plugin); 38 | 39 | SimplePacketHandler::createInterceptor($plugin)->interceptOutgoing(static function(StartGamePacket $packet, NetworkSession $target) : bool{ 40 | $dimensionId = $target->getPlayer()->getWorld()->dimensionId; 41 | if($dimensionId !== $packet->levelSettings->spawnSettings->getDimension()){ 42 | $pk = clone $packet; 43 | $pk->levelSettings->spawnSettings = new SpawnSettings( 44 | $packet->levelSettings->spawnSettings->getBiomeType(), 45 | $packet->levelSettings->spawnSettings->getBiomeName(), 46 | $dimensionId 47 | ); 48 | $target->sendDataPacket($pk); 49 | return false; 50 | } 51 | return true; 52 | })->interceptIncoming(static function(MovePlayerPacket $packet, NetworkSession $origin) : bool{ 53 | return !isset(self::$_changing_dimension_sessions[spl_object_id($origin)]); 54 | })->interceptIncoming(static function(PlayerAuthInputPacket $packet, NetworkSession $origin) : bool{ 55 | return !isset(self::$_changing_dimension_sessions[spl_object_id($origin)]); 56 | }); 57 | 58 | SimplePacketHandler::createMonitor($plugin)->monitorIncoming(static function(PlayerActionPacket $packet, NetworkSession $origin) : void{ 59 | if($packet->action === PlayerAction::DIMENSION_CHANGE_ACK){ 60 | $player = $origin->getPlayer(); 61 | if($player !== null && $player->isConnected()){ 62 | PlayerManager::get($player)->onEndDimensionChange(); 63 | } 64 | } 65 | }); 66 | 67 | $plugin->getScheduler()->scheduleRepeatingTask(new ClosureTask(static function() : void{ 68 | foreach(self::$ticking as $player_id){ 69 | self::$players[$player_id]->tick(); 70 | } 71 | foreach(self::$_queue_fast_dimension_change_request as $id){ 72 | if(isset(self::$players[$id])){ 73 | $player = self::$players[$id]->player; 74 | $location = BlockPosition::fromVector3($player->getLocation()); 75 | $player->getNetworkSession()->sendDataPacket(PlayerActionPacket::create($player->getId(), PlayerAction::DIMENSION_CHANGE_ACK, $location, $location, 0)); 76 | } 77 | } 78 | self::$_queue_fast_dimension_change_request = []; 79 | }), 1); 80 | } 81 | 82 | public static function create(Player $player, Logger $logger) : void{ 83 | self::$players[$player->getId()] = new PlayerInstance($player, $logger); 84 | } 85 | 86 | public static function destroy(Player $player) : void{ 87 | self::stopTicking($player); 88 | unset(self::$players[$player->getId()]); 89 | } 90 | 91 | public static function get(Player $player) : PlayerInstance{ 92 | return self::getNullable($player); 93 | } 94 | 95 | public static function getNullable(Player $player) : ?PlayerInstance{ 96 | return self::$players[$player->getId()] ?? null; 97 | } 98 | 99 | public static function scheduleTicking(Player $player) : void{ 100 | $player_id = $player->getId(); 101 | self::$ticking[$player_id] = $player_id; 102 | } 103 | 104 | public static function stopTicking(Player $player) : void{ 105 | unset(self::$ticking[$player->getId()]); 106 | unset(self::$_changing_dimension_sessions[spl_object_id($player->getNetworkSession())]); 107 | } 108 | } -------------------------------------------------------------------------------- /src/player/PlayerPortalInfo.php: -------------------------------------------------------------------------------- 1 | duration === $this->max_duration){ 22 | $this->duration = 0; 23 | return true; 24 | } 25 | 26 | ++$this->duration; 27 | return false; 28 | } 29 | } -------------------------------------------------------------------------------- /src/utils/DimensionChunkCache.php: -------------------------------------------------------------------------------- 1 | newInstanceWithoutConstructor(); 25 | $instance->dimension_id = $dimension_id; 26 | $_world->setValue($instance, $_world->getValue($parent)); 27 | $_compressor->setValue($instance, $_compressor->getValue($parent)); 28 | $_caches->setValue($instance, $_caches->getValue($parent)); 29 | $_hits->setValue($instance, $_hits->getValue($parent)); 30 | $_misses->setValue($instance, $_misses->getValue($parent)); 31 | return $instance; 32 | } 33 | 34 | /** @var DimensionIds::* */ 35 | public int $dimension_id; 36 | 37 | public function request(int $chunkX, int $chunkZ) : CompressBatchPromise{ 38 | static $_world = new ReflectionProperty(ChunkCache::class, "world"); 39 | static $_caches = new ReflectionProperty(ChunkCache::class, "caches"); 40 | static $_hits = new ReflectionProperty(ChunkCache::class, "hits"); 41 | static $_misses = new ReflectionProperty(ChunkCache::class, "misses"); 42 | static $_compressor = new ReflectionProperty(ChunkCache::class, "compressor"); 43 | 44 | $world = $_world->getValue($this); 45 | $caches = $_caches->getValue($this); 46 | $hits = $_hits->getValue($this); 47 | $misses = $_misses->getValue($this); 48 | $compressor = $_compressor->getValue($this); 49 | 50 | $world->registerChunkListener($this, $chunkX, $chunkZ); 51 | $chunk = $world->getChunk($chunkX, $chunkZ); 52 | if($chunk === null){ 53 | throw new \InvalidArgumentException("Cannot request an unloaded chunk"); 54 | } 55 | $chunkHash = World::chunkHash($chunkX, $chunkZ); 56 | 57 | if(isset($caches[$chunkHash])){ 58 | ++$hits; 59 | $_hits->setValue($this, $hits); 60 | return $caches[$chunkHash]; 61 | } 62 | 63 | ++$misses; 64 | $_misses->setValue($this, $misses); 65 | 66 | $world->timings->syncChunkSendPrepare->startTiming(); 67 | try{ 68 | $caches[$chunkHash] = new CompressBatchPromise(); 69 | $_caches->setValue($this, $caches); 70 | 71 | $world->getServer()->getAsyncPool()->submitTask( 72 | new ChunkRequestTask( 73 | $chunkX, 74 | $chunkZ, 75 | $this->dimension_id, 76 | $chunk, 77 | $caches[$chunkHash], 78 | $compressor 79 | ) 80 | ); 81 | 82 | return $caches[$chunkHash]; 83 | }finally{ 84 | $world->timings->syncChunkSendPrepare->stopTiming(); 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /src/utils/WorldUtils.php: -------------------------------------------------------------------------------- 1 | > $check_sides 21 | * 22 | * @return BlockTransaction|null 23 | */ 24 | public static function removeTouchingBlocks(World $world, int $find_block_id, Vector3 $origin, array $check_sides) : ?BlockTransaction{ 25 | $visits = new SplQueue(); 26 | $visits->enqueue($origin); 27 | $air = VanillaBlocks::AIR(); 28 | $transaction = new BlockTransaction($world); 29 | $filled = 0; 30 | while(!$visits->isEmpty()){ 31 | /** @var Vector3 $coordinates */ 32 | $coordinates = $visits->dequeue(); 33 | if($transaction->fetchBlockAt($coordinates->x, $coordinates->y, $coordinates->z)->getTypeId() !== $find_block_id){ 34 | continue; 35 | } 36 | $transaction->addBlockAt($coordinates->x, $coordinates->y, $coordinates->z, $air); 37 | $filled++; 38 | foreach($check_sides as $side){ 39 | $visits->enqueue($coordinates->getSide($side)); 40 | } 41 | } 42 | return $filled > 0 ? $transaction : null; 43 | } 44 | } -------------------------------------------------------------------------------- /src/vanilla/ExtraVanillaBlocks.php: -------------------------------------------------------------------------------- 1 | 34 | */ 35 | public static function getAll() : array{ 36 | //phpstan doesn't support generic traits yet :( 37 | /** @var Block[] $result */ 38 | $result = self::_registryGetAll(); 39 | return $result; 40 | } 41 | 42 | protected static function setup() : void{ 43 | self::register("end_portal", new class(new BlockIdentifier(BlockTypeIds::newId()), "End Portal", new BlockTypeInfo(BlockBreakInfo::indestructible())) extends Transparent{ 44 | public function getLightLevel() : int{ 45 | return 15; 46 | } 47 | 48 | protected function recalculateCollisionBoxes() : array{ 49 | return [AxisAlignedBB::one()->trim(Facing::UP, 1 / 4)]; 50 | } 51 | }); 52 | } 53 | } -------------------------------------------------------------------------------- /src/vanilla/ExtraVanillaData.php: -------------------------------------------------------------------------------- 1 | addWorkerStartHook(function(int $worker) use($pool) : void{ 24 | $pool->submitTaskToWorker(new class extends AsyncTask{ 25 | public function onRun() : void{ 26 | ExtraVanillaData::registerOnCurrentThread(); 27 | } 28 | }, $worker); 29 | }); 30 | } 31 | 32 | public static function registerOnCurrentThread() : void{ 33 | self::registerBlocks(); 34 | self::registerItems(); 35 | } 36 | 37 | private static function registerBlocks() : void{ 38 | self::registerSimpleBlock(BlockTypeNames::END_PORTAL, ExtraVanillaBlocks::END_PORTAL(), ["end_portal"]); 39 | } 40 | 41 | private static function registerItems() : void{ 42 | self::registerSimpleItem(ItemTypeNames::ENDER_EYE, ExtraVanillaItems::ENDER_EYE(), ["ender_eye"]); 43 | } 44 | 45 | /** 46 | * @param string[] $stringToItemParserNames 47 | */ 48 | private static function registerSimpleBlock(string $id, Block $block, array $stringToItemParserNames) : void{ 49 | RuntimeBlockStateRegistry::getInstance()->register($block); 50 | 51 | GlobalBlockStateHandlers::getDeserializer()->mapSimple($id, fn() => clone $block); 52 | GlobalBlockStateHandlers::getSerializer()->mapSimple($block, $id); 53 | 54 | foreach($stringToItemParserNames as $name){ 55 | StringToItemParser::getInstance()->registerBlock($name, fn() => clone $block); 56 | } 57 | } 58 | 59 | /** 60 | * @param string[] $stringToItemParserNames 61 | */ 62 | private static function registerSimpleItem(string $id, Item $item, array $stringToItemParserNames) : void{ 63 | GlobalItemDataHandlers::getDeserializer()->map($id, fn() => clone $item); 64 | GlobalItemDataHandlers::getSerializer()->map($item, fn() => new SavedItemData($id)); 65 | 66 | foreach($stringToItemParserNames as $name){ 67 | StringToItemParser::getInstance()->register($name, fn() => clone $item); 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /src/vanilla/ExtraVanillaItems.php: -------------------------------------------------------------------------------- 1 | 28 | */ 29 | public static function getAll() : array{ 30 | //phpstan doesn't support generic traits yet :( 31 | /** @var Item[] $result */ 32 | $result = self::_registryGetAll(); 33 | return $result; 34 | } 35 | 36 | protected static function setup() : void{ 37 | self::register("ender_eye", new Item(new ItemIdentifier(ItemTypeIds::newId()), "Eye of Ender")); 38 | } 39 | } -------------------------------------------------------------------------------- /src/world/DimensionalWorld.php: -------------------------------------------------------------------------------- 1 | dimensionId === DimensionIds::OVERWORLD) 23 | return $this; 24 | return $this->getServer()->getWorldManager()->getWorld($this->getId() + (DimensionIds::OVERWORLD - $this->dimensionId)); 25 | } 26 | 27 | public function getNether() : World{ 28 | if($this->dimensionId === DimensionIds::NETHER) 29 | return $this; 30 | return $this->getServer()->getWorldManager()->getWorld($this->getId() + (DimensionIds::NETHER - $this->dimensionId)); 31 | } 32 | 33 | public function getEnd() : World{ 34 | if($this->dimensionId === DimensionIds::THE_END) 35 | return $this; 36 | return $this->getServer()->getWorldManager()->getWorld($this->getId() + (DimensionIds::THE_END - $this->dimensionId)); 37 | } 38 | 39 | public function save(bool $force = false) : bool{ 40 | 41 | if(!$this->getAutoSave() && !$force){ 42 | return false; 43 | } 44 | 45 | (new WorldSaveEvent($this))->call(); 46 | 47 | $this->getProvider()->getWorldData()->setTime($this->getTime()); 48 | $this->saveChunks(); 49 | $this->getProvider()->getWorldData()->save($this->dimensionId); 50 | 51 | return true; 52 | } 53 | 54 | public function getSpawnLocation() : Position{ 55 | return Position::fromObject($this->getProvider()->getWorldData()->getSpawn($this->dimensionId), $this); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/world/converter/DimensionalFormatConverter.php: -------------------------------------------------------------------------------- 1 | logger = new PrefixedLogger($logger, "World Converter: " . $oldProviders[DimensionIds::OVERWORLD]->getWorldData()->getName()); 50 | 51 | if(!file_exists($backupPath)){ 52 | @mkdir($backupPath, 0777, true); 53 | } 54 | $nextSuffix = ""; 55 | do{ 56 | $this->backupPath = Path::join($backupPath, basename($this->oldProviders[DimensionIds::OVERWORLD]->getPath()) . $nextSuffix); 57 | $nextSuffix = "_" . crc32(random_bytes(4)); 58 | }while(file_exists($this->backupPath)); 59 | } 60 | 61 | /** 62 | * @return WritableWorldProvider[] 63 | */ 64 | public function execute() : array{ 65 | [$overworld, $nether, $end] = $this->generateNew(); 66 | 67 | $this->populateLevelData($overworld->getWorldData()); 68 | $this->convertTerrain($overworld); 69 | $this->convertTerrain($nether); 70 | $this->convertTerrain($end); 71 | 72 | $path = $this->oldProviders[DimensionIds::OVERWORLD]->getPath(); 73 | $this->oldProviders[DimensionIds::OVERWORLD]->close(); 74 | $overworld->close(); 75 | $nether->close(); 76 | $end->close(); 77 | 78 | $this->logger->info("Backing up pre-conversion world to " . $this->backupPath); 79 | if(!@rename($path, $this->backupPath)){ 80 | $this->logger->warning("Moving old world files for backup failed, attempting copy instead. This might take a long time."); 81 | Filesystem::recursiveCopy($path, $this->backupPath); 82 | Filesystem::recursiveUnlink($path); 83 | } 84 | if(!@rename($overworld->getPath(), $path)){ 85 | //we don't expect this to happen because worlds/ should most likely be all on the same FS, but just in case... 86 | $this->logger->debug("Relocation of new world files to location failed, attempting copy and delete instead"); 87 | Filesystem::recursiveCopy($overworld->getPath(), $path); 88 | Filesystem::recursiveUnlink($overworld->getPath()); 89 | } 90 | 91 | $this->logger->info("Conversion completed"); 92 | return [ 93 | $overworld = $this->newProvider->fromPath($path, new \PrefixedLogger($this->logger, "World Provider: {$overworld->getWorldData()->getName()}"), DimensionIds::OVERWORLD), 94 | $this->newProvider->fromPath($path, new \PrefixedLogger($this->logger, "World Provider: {$nether->getWorldData()->getName()}"), DimensionIds::NETHER, $overworld->getDatabase()), 95 | $this->newProvider->fromPath($path, new \PrefixedLogger($this->logger, "World Provider: {$end->getWorldData()->getName()}"), DimensionIds::THE_END, $overworld->getDatabase()), 96 | ]; 97 | } 98 | 99 | /** 100 | * @return DimensionLevelDBProvider[] 101 | */ 102 | private function generateNew() : array{ 103 | $this->logger->info("Generating new world"); 104 | $data = $this->oldProviders[DimensionIds::OVERWORLD]->getWorldData(); 105 | 106 | $convertedOutput = rtrim($this->oldProviders[DimensionIds::OVERWORLD]->getPath(), "/" . DIRECTORY_SEPARATOR) . "_converted" . DIRECTORY_SEPARATOR; 107 | if(file_exists($convertedOutput)){ 108 | $this->logger->info("Found previous conversion attempt, deleting..."); 109 | Filesystem::recursiveUnlink($convertedOutput); 110 | } 111 | $this->newProvider->generate($convertedOutput, $data->getName(), WorldCreationOptions::create() 112 | //TODO: defaulting to NORMAL here really isn't very good behaviour, but it's consistent with what pocketmine already 113 | //did previously; besides, WorldManager checks for unknown generators before this is reached anyway. 114 | ->setGeneratorClass(GeneratorManager::getInstance()->getGenerator($data->getGenerator())?->getGeneratorClass() ?? Normal::class) 115 | ->setGeneratorOptions($data->getGeneratorOptions()) 116 | ->setSeed($data->getSeed()) 117 | ->setSpawnPosition($data->getSpawn()) 118 | ->setDifficulty($data->getDifficulty()) 119 | ); 120 | 121 | return [ 122 | $overworld = $this->newProvider->fromPath($convertedOutput, new PrefixedLogger($this->logger, "World Provider: {$data->getName()}"), DimensionIds::OVERWORLD), 123 | $this->newProvider->fromPath($convertedOutput, new PrefixedLogger($this->logger, "World Provider: {$data->getName()}"), DimensionIds::NETHER, $overworld->getDatabase()), 124 | $this->newProvider->fromPath($convertedOutput, new PrefixedLogger($this->logger, "World Provider: {$data->getName()}"), DimensionIds::THE_END, $overworld->getDatabase()), 125 | ]; 126 | } 127 | 128 | private function populateLevelData(WorldData $data) : void{ 129 | $this->logger->info("Converting world manifest"); 130 | $oldData = $this->oldProviders[DimensionIds::OVERWORLD]->getWorldData(); 131 | $data->setDifficulty($oldData->getDifficulty()); 132 | $data->setLightningLevel($oldData->getLightningLevel()); 133 | $data->setLightningTime($oldData->getLightningTime()); 134 | $data->setRainLevel($oldData->getRainLevel()); 135 | $data->setRainTime($oldData->getRainTime()); 136 | $data->setSpawn($oldData->getSpawn()); 137 | $data->setTime($oldData->getTime()); 138 | 139 | $data->save(DimensionIds::OVERWORLD); 140 | $this->logger->info("Finished converting manifest"); 141 | //TODO: add more properties as-needed 142 | } 143 | 144 | private function convertTerrain(DimensionLevelDBProvider $new) : void{ 145 | $this->logger->info("Calculating chunk count"); 146 | $provider = $this->oldProviders[$new->getDimensionId()]; 147 | $count = $provider->calculateChunkCount(); 148 | $this->logger->info("Discovered $count chunks"); 149 | 150 | $counter = 0; 151 | 152 | $start = microtime(true); 153 | $thisRound = $start; 154 | foreach($provider->getAllChunks(true, $this->logger) as $coords => $loadedChunkData){ 155 | [$chunkX, $chunkZ] = $coords; 156 | $new->saveChunk($chunkX, $chunkZ, $loadedChunkData->getData(), Chunk::DIRTY_FLAGS_ALL); 157 | $counter++; 158 | if(($counter % $this->chunksPerProgressUpdate) === 0){ 159 | $time = microtime(true); 160 | $diff = $time - $thisRound; 161 | $thisRound = $time; 162 | $this->logger->info("Converted $counter / $count chunks (" . floor($this->chunksPerProgressUpdate / $diff) . " chunks/sec)"); 163 | } 164 | } 165 | $total = microtime(true) - $start; 166 | $this->logger->info("Converted $counter / $counter chunks in " . round($total, 3) . " seconds (" . floor($counter / $total) . " chunks/sec)"); 167 | } 168 | 169 | public function getBackupPath() : string{ 170 | return $this->backupPath; 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/world/data/DimensionalBedrockWorldData.php: -------------------------------------------------------------------------------- 1 | compoundTag->getInt(self::TAG_NETHER_SCALE, -1) === -1){ 17 | $this->compoundTag->setInt(self::TAG_NETHER_SCALE, 8); 18 | } 19 | parent::fix(); 20 | } 21 | 22 | public function setGenerator(string $generatorName) : void{ 23 | $this->compoundTag->setString(self::TAG_GENERATOR_NAME, $generatorName); 24 | $this->fix(); 25 | } 26 | 27 | public function save(int $dimension = 0) : void{ 28 | if($dimension !== 0) 29 | return; 30 | parent::save(); 31 | } 32 | 33 | /** 34 | * @phpstan-param DimensionIds::* $dimension 35 | */ 36 | public function getSpawn(int $dimension = 0) : Vector3{ 37 | if($dimension === DimensionIds::THE_END) 38 | return new Vector3(100, 50, 0); 39 | return new Vector3($this->compoundTag->getInt("SpawnX"), $this->compoundTag->getInt("SpawnY"), $this->compoundTag->getInt("SpawnZ")); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/world/generator/Decorator.php: -------------------------------------------------------------------------------- 1 | amount = $amount; 18 | } 19 | 20 | abstract public function decorate(ChunkManager $world, Random $random, int $chunk_x, int $chunk_z, Chunk $chunk) : void; 21 | 22 | public function populate(ChunkManager $world, Random $random, int $chunk_x, int $chunk_z, Chunk $chunk) : void{ 23 | for($i = 0; $i < $this->amount; ++$i){ 24 | $this->decorate($world, $random, $chunk_x, $chunk_z, $chunk); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/world/generator/Environment.php: -------------------------------------------------------------------------------- 1 | self::OVERWORLD, 15 | "nether" => self::NETHER, 16 | "end", "the_end" => self::THE_END, 17 | default => throw new InvalidArgumentException("Could not convert string \"{$string}\" to a " . self::class . " constant") 18 | }; 19 | } 20 | 21 | public const OVERWORLD = 0; 22 | public const NETHER = -1; 23 | public const THE_END = 1; 24 | 25 | private function __construct(){ 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/world/generator/Populator.php: -------------------------------------------------------------------------------- 1 | biomes) ? $this->biomes[$hash] & 0xFF : null; 19 | } 20 | 21 | public function setBiome(int $x, int $z, int $biome_id) : void{ 22 | $this->biomes[$x | $z << Chunk::COORD_BIT_SIZE] = $biome_id; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/world/generator/VanillaGenerator.php: -------------------------------------------------------------------------------- 1 | toString()); 34 | $this->biome_grid = MapLayer::initialize($seed, $environment, $world_type ?? WorldType::NORMAL); 35 | } 36 | 37 | /** 38 | * @return int[] 39 | */ 40 | public function getBiomeGridAtLowerRes(int $x, int $z, int $size_x, int $size_z) : array{ 41 | return $this->biome_grid->low_resolution->generateValues($x, $z, $size_x, $size_z); 42 | } 43 | 44 | /** 45 | * @return int[] 46 | */ 47 | public function getBiomeGrid(int $x, int $z, int $size_x, int $size_z) : array{ 48 | return $this->biome_grid->high_resolution->generateValues($x, $z, $size_x, $size_z); 49 | } 50 | 51 | protected function addPopulators(Populator ...$populators) : void{ 52 | array_push($this->populators, ...$populators); 53 | } 54 | 55 | /** 56 | * @return T 57 | */ 58 | abstract protected function createWorldOctaves() : WorldOctaves; 59 | 60 | public function generateChunk(ChunkManager $world, int $chunkX, int $chunkZ) : void{ 61 | $biomes = new VanillaBiomeGrid(); 62 | $biome_values = $this->biome_grid->high_resolution->generateValues($chunkX * 16, $chunkZ * 16, 16, 16); 63 | for($i = 0, $biome_values_c = count($biome_values); $i < $biome_values_c; ++$i){ 64 | $biomes->biomes[$i] = $biome_values[$i]; 65 | } 66 | 67 | $this->generateChunkData($world, $chunkX, $chunkZ, $biomes); 68 | } 69 | 70 | abstract protected function generateChunkData(ChunkManager $world, int $chunk_x, int $chunk_z, VanillaBiomeGrid $biomes) : void; 71 | 72 | /** 73 | * @return T 74 | */ 75 | final protected function getWorldOctaves() : WorldOctaves{ 76 | return $this->octave_cache ??= $this->createWorldOctaves(); 77 | } 78 | 79 | /** 80 | * @return Populator[] 81 | */ 82 | public function getDefaultPopulators() : array{ 83 | return $this->populators; 84 | } 85 | 86 | public function populateChunk(ChunkManager $world, int $chunk_x, int $chunk_z) : void{ 87 | /** @var Chunk $chunk */ 88 | $chunk = $world->getChunk($chunk_x, $chunk_z); 89 | foreach($this->populators as $populator){ 90 | $populator->populate($world, $this->random, $chunk_x, $chunk_z, $chunk); 91 | } 92 | } 93 | 94 | public function getMaxY() : int{ 95 | return World::Y_MAX; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/world/generator/biome/BiomeClimate.php: -------------------------------------------------------------------------------- 1 | setScale(1 / 8.0); 21 | 22 | self::$default = new BiomeClimate(0.5, 0.5, true); 23 | 24 | self::register(new BiomeClimate(0.8, 0.4, true), 25 | BiomeIds::PLAINS, 26 | BiomeIds::SUNFLOWER_PLAINS, 27 | BiomeIds::BEACH 28 | ); 29 | 30 | self::register(new BiomeClimate(2.0, 0.0, false), 31 | BiomeIds::DESERT, 32 | BiomeIds::DESERT_HILLS, 33 | BiomeIds::DESERT_MUTATED, 34 | BiomeIds::MESA, 35 | BiomeIds::MESA_BRYCE, 36 | BiomeIds::MESA_PLATEAU, 37 | BiomeIds::MESA_PLATEAU_STONE, 38 | BiomeIds::MESA_PLATEAU_MUTATED, 39 | BiomeIds::MESA_PLATEAU_STONE_MUTATED, 40 | BiomeIds::HELL 41 | ); 42 | 43 | self::register(new BiomeClimate(0.2, 0.3, true), 44 | BiomeIds::EXTREME_HILLS, 45 | BiomeIds::EXTREME_HILLS_PLUS_TREES, 46 | BiomeIds::EXTREME_HILLS_MUTATED, 47 | BiomeIds::EXTREME_HILLS_PLUS_TREES_MUTATED, 48 | BiomeIds::STONE_BEACH, 49 | BiomeIds::EXTREME_HILLS_EDGE 50 | ); 51 | 52 | self::register(new BiomeClimate(0.7, 0.8, true), 53 | BiomeIds::FOREST, 54 | BiomeIds::FOREST_HILLS, 55 | BiomeIds::FLOWER_FOREST, 56 | BiomeIds::ROOFED_FOREST, 57 | BiomeIds::ROOFED_FOREST_MUTATED 58 | ); 59 | 60 | self::register(new BiomeClimate(0.6, 0.6, true), 61 | BiomeIds::BIRCH_FOREST, 62 | BiomeIds::BIRCH_FOREST_HILLS, 63 | BiomeIds::BIRCH_FOREST_MUTATED, 64 | BiomeIds::BIRCH_FOREST_HILLS_MUTATED 65 | ); 66 | 67 | self::register(new BiomeClimate(0.25, 0.8, true), 68 | BiomeIds::TAIGA, 69 | BiomeIds::TAIGA_HILLS, 70 | BiomeIds::TAIGA_MUTATED, 71 | BiomeIds::REDWOOD_TAIGA_MUTATED, 72 | BiomeIds::REDWOOD_TAIGA_HILLS_MUTATED 73 | ); 74 | 75 | self::register(new BiomeClimate(0.8, 0.9, true), 76 | BiomeIds::SWAMPLAND, 77 | BiomeIds::SWAMPLAND_MUTATED 78 | ); 79 | 80 | self::register(new BiomeClimate(0.0, 0.5, true), 81 | BiomeIds::ICE_PLAINS, 82 | BiomeIds::ICE_MOUNTAINS, 83 | BiomeIds::ICE_PLAINS_SPIKES, 84 | BiomeIds::FROZEN_RIVER, 85 | BiomeIds::FROZEN_OCEAN 86 | ); 87 | 88 | self::register(new BiomeClimate(0.9, 1.0, true), BiomeIds::MUSHROOM_ISLAND, BiomeIds::MUSHROOM_ISLAND_SHORE); 89 | self::register(new BiomeClimate(0.05, 0.3, true), BiomeIds::COLD_BEACH); 90 | self::register(new BiomeClimate(0.95, 0.9, true), BiomeIds::JUNGLE_HILLS, BiomeIds::JUNGLE_MUTATED); 91 | self::register(new BiomeClimate(0.95, 0.8, true), BiomeIds::JUNGLE_EDGE, BiomeIds::JUNGLE_EDGE_MUTATED); 92 | self::register(new BiomeClimate(-0.5, 0.4, true), BiomeIds::COLD_TAIGA, BiomeIds::COLD_TAIGA_HILLS, BiomeIds::COLD_TAIGA_MUTATED); 93 | self::register(new BiomeClimate(0.3, 0.8, true), BiomeIds::MEGA_TAIGA, BiomeIds::MEGA_TAIGA_HILLS); 94 | self::register(new BiomeClimate(1.2, 0.0, false), BiomeIds::SAVANNA); 95 | self::register(new BiomeClimate(1.1, 0.0, false), BiomeIds::SAVANNA_MUTATED); 96 | self::register(new BiomeClimate(1.0, 0.0, false), BiomeIds::SAVANNA_PLATEAU); 97 | self::register(new BiomeClimate(0.5, 0.0, false), BiomeIds::SAVANNA_PLATEAU_MUTATED); 98 | self::register(new BiomeClimate(0.5, 0.5, false), BiomeIds::SKY); 99 | } 100 | 101 | public static function register(BiomeClimate $climate, int ...$biome_ids) : void{ 102 | foreach($biome_ids as $biomeId){ 103 | self::$climates[$biomeId] = $climate; 104 | } 105 | } 106 | 107 | public static function get(int $biome) : BiomeClimate{ 108 | return self::$climates[$biome] ?? self::$default; 109 | } 110 | 111 | public static function getBiomeTemperature(int $biome) : float{ 112 | return self::get($biome)->temperature; 113 | } 114 | 115 | public static function getBiomeHumidity(int $biome) : float{ 116 | return self::get($biome)->humidity; 117 | } 118 | 119 | public static function isWet(int $biome) : bool{ 120 | return self::getBiomeHumidity($biome) > 0.85; 121 | } 122 | 123 | public static function isCold(int $biome, int $x, int $y, int $z) : bool{ 124 | return self::getVariatedTemperature($biome, $x, $y, $z) < 0.15; 125 | } 126 | 127 | public static function isRainy(int $biome, int $x, int $y, int $z) : bool{ 128 | return self::get($biome)->rainy && !self::isCold($biome, $x, $y, $z); 129 | } 130 | 131 | public static function isSnowy(int $biome, int $x, int $y, int $z) : bool{ 132 | return self::get($biome)->rainy && self::isCold($biome, $x, $y, $z); 133 | } 134 | 135 | private static function getVariatedTemperature(int $biome, int $x, int $y, int $z) : float{ 136 | $temp = self::get($biome)->temperature; 137 | if($y > 64){ 138 | $variation = self::$noise_gen->noise($x, $z, 0, 0.5, 2.0, false) * 4.0; 139 | return $temp - ($variation + (float) ($y - 64)) * 0.05 / 30.0; 140 | } 141 | 142 | return $temp; 143 | } 144 | } 145 | 146 | BiomeClimateManager::init(); 147 | -------------------------------------------------------------------------------- /src/world/generator/biome/BiomeIds.php: -------------------------------------------------------------------------------- 1 | biome = $biome; 14 | } 15 | 16 | public function generateValues(int $x, int $z, int $size_x, int $size_z) : array{ 17 | $values = []; 18 | for($i = 0; $i < $size_z; ++$i){ 19 | for($j = 0; $j < $size_x; ++$j){ 20 | $values[$j + $i * $size_x] = $this->biome; 21 | } 22 | } 23 | 24 | return $values; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/world/generator/biomegrid/MapLayer.php: -------------------------------------------------------------------------------- 1 | random = new Random(); 26 | $this->seed = $seed; 27 | } 28 | 29 | public function setCoordsSeed(int $x, int $z) : void{ 30 | $this->random->setSeed($this->seed); 31 | $this->random->setSeed($x * $this->random->nextInt() + $z * $this->random->nextInt() ^ $this->seed); 32 | } 33 | 34 | public function nextInt(int $max) : int{ 35 | return $this->random->nextBoundedInt($max); 36 | } 37 | 38 | /** 39 | * @return int[] 40 | */ 41 | abstract public function generateValues(int $x, int $z, int $size_x, int $size_z) : array; 42 | } 43 | -------------------------------------------------------------------------------- /src/world/generator/biomegrid/utils/MapLayerPair.php: -------------------------------------------------------------------------------- 1 | . 19 | */ 20 | 21 | declare(strict_types=1); 22 | 23 | namespace jasonw4331\NativeDimensions\world\generator\ender; 24 | 25 | use jasonw4331\NativeDimensions\world\generator\ender\populator\EnderPilar; 26 | use pocketmine\block\Block; 27 | use pocketmine\block\BlockTypeIds; 28 | use pocketmine\block\VanillaBlocks; 29 | use pocketmine\data\bedrock\BiomeIds; 30 | use pocketmine\world\ChunkManager; 31 | use pocketmine\world\format\Chunk; 32 | use pocketmine\world\format\PalettedBlockArray; 33 | use pocketmine\world\format\SubChunk; 34 | use pocketmine\world\generator\Generator; 35 | use pocketmine\world\generator\noise\Simplex; 36 | use pocketmine\world\generator\populator\Populator; 37 | use function abs; 38 | 39 | class EnderGenerator extends Generator { 40 | public const MIN_BASE_ISLAND_HEIGHT = 54; 41 | public const MAX_BASE_ISLAND_HEIGHT = 55; 42 | public const NOISE_SIZE = 12; 43 | 44 | public const CENTER_X = 255; 45 | public const CENTER_Z = 255; 46 | public const ISLAND_RADIUS = 100; 47 | 48 | private Simplex $noiseBase; 49 | 50 | /** @var Populator[] */ 51 | private array $populators = []; 52 | 53 | public function __construct(int $seed, string $preset) { 54 | parent::__construct($seed, $preset); 55 | 56 | $this->noiseBase = new Simplex($this->random, 4, 1 / 16, 1 / 64); 57 | $this->populators[] = new EnderPilar(); 58 | } 59 | 60 | public function generateChunk(ChunkManager $world, int $chunkX, int $chunkZ) : void { 61 | $this->random->setSeed(0xdeadbeef ^ ($chunkX << 8) ^ $chunkZ ^ $this->seed); 62 | 63 | /** @phpstan-var Chunk $chunk */ 64 | $chunk = $world->getChunk($chunkX, $chunkZ); 65 | foreach($chunk->getSubChunks() as $y => $subChunk) { 66 | $chunk->setSubChunk($y, new SubChunk( 67 | BlockTypeIds::AIR << Block::INTERNAL_STATE_DATA_BITS, 68 | [], 69 | new PalettedBlockArray(BiomeIds::THE_END) 70 | )); 71 | } 72 | $noise = $this->noiseBase->getFastNoise2D(16, 16, 2, $chunkX * 16, 0, $chunkZ * 16); 73 | 74 | $endStone = VanillaBlocks::END_STONE()->getStateId(); 75 | 76 | $baseX = $chunkX * Chunk::EDGE_LENGTH; 77 | $baseZ = $chunkZ * Chunk::EDGE_LENGTH; 78 | for($x = 0; $x < 16; ++$x) { 79 | $absoluteX = $baseX + $x; 80 | for($z = 0; $z < 16; ++$z) { 81 | $absoluteZ = $baseZ + $z; 82 | 83 | if(($absoluteX - self::CENTER_X) ** 2 + ($absoluteZ - self::CENTER_Z) ** 2 > self::ISLAND_RADIUS ** 2) { 84 | continue; 85 | } 86 | 87 | // @phpstan-ignore-next-line 88 | $noiseValue = (int) abs($noise[$x][$z] * self::NOISE_SIZE); // wtf 89 | for($y = 0; $y < $noiseValue; ++$y) { 90 | $chunk->setBlockStateId($x, self::MAX_BASE_ISLAND_HEIGHT + $y, $z, $endStone); 91 | } 92 | 93 | $reversedNoiseValue = self::NOISE_SIZE - $noiseValue; 94 | for($y = 0; $y < $reversedNoiseValue; ++$y) { 95 | $chunk->setBlockStateId($x, self::MIN_BASE_ISLAND_HEIGHT - $y, $z, $endStone); 96 | } 97 | 98 | for($y = self::MIN_BASE_ISLAND_HEIGHT; $y < self::MAX_BASE_ISLAND_HEIGHT; ++$y) { 99 | $chunk->setBlockStateId($x, $y, $z, $endStone); 100 | } 101 | } 102 | } 103 | } 104 | 105 | public function populateChunk(ChunkManager $world, int $chunkX, int $chunkZ) : void { 106 | foreach($this->populators as $populator) { 107 | $this->random->setSeed(0xdeadbeef ^ ($chunkX << 8) ^ $chunkZ ^ $this->seed); 108 | $populator->populate($world, $chunkX, $chunkZ, $this->random); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/world/generator/ender/populator/EnderPilar.php: -------------------------------------------------------------------------------- 1 | . 19 | */ 20 | 21 | declare(strict_types=1); 22 | 23 | namespace jasonw4331\NativeDimensions\world\generator\ender\populator; 24 | 25 | use jasonw4331\NativeDimensions\world\generator\ender\EnderGenerator; 26 | use pocketmine\block\VanillaBlocks; 27 | use pocketmine\utils\AssumptionFailedError; 28 | use pocketmine\utils\Random; 29 | use pocketmine\world\ChunkManager; 30 | use pocketmine\world\format\Chunk; 31 | use pocketmine\world\format\SubChunk; 32 | use pocketmine\world\generator\populator\Populator; 33 | 34 | class EnderPilar implements Populator { 35 | public const MIN_RADIUS = 3; 36 | public const MAX_RADIUS = 5; 37 | 38 | public function populate(ChunkManager $world, int $chunkX, int $chunkZ, Random $random) : void { 39 | if($random->nextBoundedInt(10) > 0) { 40 | return; 41 | } 42 | 43 | $chunk = $world->getChunk($chunkX, $chunkZ); 44 | if($chunk === null) { 45 | throw new AssumptionFailedError("Populated chunk is null"); 46 | } 47 | 48 | $bound = 16 - self::MAX_RADIUS * 2; 49 | 50 | $relativeX = self::MAX_RADIUS + $random->nextBoundedInt($bound); 51 | $relativeZ = self::MAX_RADIUS + $random->nextBoundedInt($bound); 52 | 53 | $centerY = $this->getWorkableBlockAt($chunk, $relativeX, $relativeZ) - 1; 54 | 55 | $air = VanillaBlocks::AIR()->getStateId(); 56 | if($chunk->getBlockStateId($relativeX, $centerY, $relativeZ) === $air) { 57 | return; 58 | } 59 | 60 | $centerX = $chunkX * SubChunk::EDGE_LENGTH + $relativeX; 61 | $centerZ = $chunkZ * SubChunk::EDGE_LENGTH + $relativeZ; 62 | 63 | $height = $random->nextRange(28, 50); 64 | $radius = $random->nextRange(3, 5); 65 | $radiusSquared = ($radius ** 2) - 1; 66 | 67 | $obsidian = VanillaBlocks::OBSIDIAN(); 68 | for($x = 0; $x <= $radius; ++$x) { 69 | $xSquared = $x ** 2; 70 | for($z = 0; $z <= $radius; ++$z) { 71 | if($xSquared + $z ** 2 >= $radiusSquared) { 72 | break; 73 | } 74 | 75 | for($y = 0; $y < $height; ++$y) { 76 | $world->setBlockAt($centerX + $x, $centerY + $y, $centerZ + $z, $obsidian); 77 | $world->setBlockAt($centerX - $x, $centerY + $y, $centerZ + $z, $obsidian); 78 | $world->setBlockAt($centerX + $x, $centerY + $y, $centerZ - $z, $obsidian); 79 | $world->setBlockAt($centerX - $x, $centerY + $y, $centerZ - $z, $obsidian); 80 | } 81 | } 82 | } 83 | } 84 | 85 | private function getWorkableBlockAt(Chunk $chunk, int $x, int $z) : int { 86 | $air = VanillaBlocks::AIR()->getStateId(); 87 | for($y = EnderGenerator::MAX_BASE_ISLAND_HEIGHT, $maxY = EnderGenerator::MAX_BASE_ISLAND_HEIGHT + EnderGenerator::NOISE_SIZE; $y <= $maxY; ++$y ) { 88 | if($chunk->getBlockStateId($x, $y, $z) === $air) { 89 | return $y; 90 | } 91 | } 92 | 93 | return $y; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/world/generator/nether/NetherGenerator.php: -------------------------------------------------------------------------------- 1 | > 25 | */ 26 | class NetherGenerator extends VanillaGenerator{ 27 | 28 | protected const COORDINATE_SCALE = 684.412; 29 | protected const HEIGHT_SCALE = 2053.236; 30 | protected const HEIGHT_NOISE_SCALE_X = 100.0; 31 | protected const HEIGHT_NOISE_SCALE_Z = 100.0; 32 | protected const DETAIL_NOISE_SCALE_X = 80.0; 33 | protected const DETAIL_NOISE_SCALE_Y = 60.0; 34 | protected const DETAIL_NOISE_SCALE_Z = 80.0; 35 | protected const SURFACE_SCALE = 0.0625; 36 | 37 | /** 38 | * @param int $i 0-4 39 | * @param int $j 0-4 40 | * @param int $k 0-32 41 | */ 42 | private static function densityHash(int $i, int $j, int $k) : int{ 43 | return ($k << 6) | ($j << 3) | $i; 44 | } 45 | 46 | protected int $bedrock_roughness = 5; 47 | 48 | public function __construct(int $seed, string $preset_string){ 49 | $preset = SimpleGeneratorPreset::parse($preset_string); 50 | parent::__construct( 51 | $seed, 52 | $preset->exists("environment") ? Environment::fromString($preset->getString("environment")) : Environment::NETHER, 53 | $preset->exists("worldtype") ? WorldType::fromString($preset->getString("worldtype")) : null, 54 | $preset 55 | ); 56 | $this->addPopulators(new NetherPopulator($this->getMaxY())); // This isn't faithful to original code. Was $world->getWorldHeight() 57 | } 58 | 59 | public function getBedrockRoughness() : int{ 60 | return $this->bedrock_roughness; 61 | } 62 | 63 | public function setBedrockRoughness(int $bedrock_roughness) : void{ 64 | $this->bedrock_roughness = $bedrock_roughness; 65 | } 66 | 67 | public function getMaxY() : int{ 68 | return 128; 69 | } 70 | 71 | protected function generateChunkData(ChunkManager $world, int $chunk_x, int $chunk_z, VanillaBiomeGrid $biomes) : void{ 72 | $this->generateRawTerrain($world, $chunk_x, $chunk_z); 73 | $cx = $chunk_x << Chunk::COORD_BIT_SIZE; 74 | $cz = $chunk_z << Chunk::COORD_BIT_SIZE; 75 | 76 | $octaves = $this->getWorldOctaves(); 77 | 78 | $surface_noise = $octaves->surface->getFractalBrownianMotion($cx, $cz, 0, 0.5, 2.0); 79 | $soul_sand_noise = $octaves->soul_sand->getFractalBrownianMotion($cx, $cz, 0, 0.5, 2.0); 80 | $grave_noise = $octaves->gravel->getFractalBrownianMotion($cx, 0, $cz, 0.5, 2.0); 81 | 82 | /** @var Chunk $chunk */ 83 | $chunk = $world->getChunk($chunk_x, $chunk_z); 84 | 85 | $min_y = $world->getMinY(); 86 | $max_y = $world->getMaxY(); 87 | for($x = 0; $x < 16; ++$x){ 88 | for($z = 0; $z < 16; ++$z){ 89 | $id = $biomes->getBiome($x, $z); 90 | for($y = $min_y; $y < $max_y; ++$y){ 91 | $chunk->setBiomeId($x, $y, $z, $id); 92 | } 93 | $this->generateTerrainColumn($world, $cx + $x, $cz + $z, $surface_noise[$x | $z << Chunk::COORD_BIT_SIZE], $soul_sand_noise[$x | $z << Chunk::COORD_BIT_SIZE], $grave_noise[$x | $z << Chunk::COORD_BIT_SIZE]); 94 | } 95 | } 96 | } 97 | 98 | protected function createWorldOctaves() : NetherWorldOctaves{ 99 | $seed = new Random($this->random->getSeed()); 100 | 101 | $height = PerlinOctaveGenerator::fromRandomAndOctaves($seed, 16, 5, 1, 5); 102 | $height->x_scale = static::HEIGHT_NOISE_SCALE_X; 103 | $height->z_scale = static::HEIGHT_NOISE_SCALE_Z; 104 | 105 | $roughness = PerlinOctaveGenerator::fromRandomAndOctaves($seed, 16, 5, 17, 5); 106 | $roughness->x_scale = static::COORDINATE_SCALE; 107 | $roughness->y_scale = static::HEIGHT_SCALE; 108 | $roughness->z_scale = static::COORDINATE_SCALE; 109 | 110 | $roughness2 = PerlinOctaveGenerator::fromRandomAndOctaves($seed, 16, 5, 17, 5); 111 | $roughness2->x_scale = static::COORDINATE_SCALE; 112 | $roughness2->y_scale = static::HEIGHT_SCALE; 113 | $roughness2->z_scale = static::COORDINATE_SCALE; 114 | 115 | $detail = PerlinOctaveGenerator::fromRandomAndOctaves($seed, 8, 5, 17, 5); 116 | $detail->x_scale = static::COORDINATE_SCALE / static::DETAIL_NOISE_SCALE_X; 117 | $detail->y_scale = static::HEIGHT_SCALE / static::DETAIL_NOISE_SCALE_Y; 118 | $detail->z_scale = static::COORDINATE_SCALE / static::DETAIL_NOISE_SCALE_Z; 119 | 120 | $surface = PerlinOctaveGenerator::fromRandomAndOctaves($seed, 4, 16, 16, 1); 121 | $surface->setScale(static::SURFACE_SCALE); 122 | 123 | $soulsand = PerlinOctaveGenerator::fromRandomAndOctaves($seed, 4, 16, 16, 1); 124 | $soulsand->x_scale = static::SURFACE_SCALE / 2.0; 125 | $soulsand->y_scale = static::SURFACE_SCALE / 2.0; 126 | 127 | $gravel = PerlinOctaveGenerator::fromRandomAndOctaves($seed, 4, 16, 1, 16); 128 | $gravel->x_scale = static::SURFACE_SCALE / 2.0; 129 | $gravel->z_scale = static::SURFACE_SCALE / 2.0; 130 | 131 | return new NetherWorldOctaves($height, $roughness, $roughness2, $detail, $surface, $soulsand, $gravel); 132 | } 133 | 134 | private function generateRawTerrain(ChunkManager $world, int $chunk_x, int $chunk_z) : void{ 135 | $density = $this->generateTerrainDensity($chunk_x << 2, $chunk_z << 2); 136 | 137 | $nether_rack = VanillaBlocks::NETHERRACK()->getStateId(); 138 | $still_lava = VanillaBlocks::LAVA()->getStillForm()->getStateId(); 139 | 140 | /** @var Chunk $chunk */ 141 | $chunk = $world->getChunk($chunk_x, $chunk_z); 142 | 143 | for($i = 0; $i < 5 - 1; ++$i){ 144 | for($j = 0; $j < 5 - 1; ++$j){ 145 | for($k = 0; $k < 17 - 1; ++$k){ 146 | $d1 = $density[self::densityHash($i, $j, $k)]; 147 | $d2 = $density[self::densityHash($i + 1, $j, $k)]; 148 | $d3 = $density[self::densityHash($i, $j + 1, $k)]; 149 | $d4 = $density[self::densityHash($i + 1, $j + 1, $k)]; 150 | $d5 = ($density[self::densityHash($i, $j, $k + 1)] - $d1) / 8; 151 | $d6 = ($density[self::densityHash($i + 1, $j, $k + 1)] - $d2) / 8; 152 | $d7 = ($density[self::densityHash($i, $j + 1, $k + 1)] - $d3) / 8; 153 | $d8 = ($density[self::densityHash($i + 1, $j + 1, $k + 1)] - $d4) / 8; 154 | 155 | for($l = 0; $l < 8; ++$l){ 156 | $d9 = $d1; 157 | $d10 = $d3; 158 | 159 | $y_pos = $l + ($k << 3); 160 | $y_block_pos = $y_pos & 0xf; 161 | $sub_chunk = $chunk->getSubChunk($y_pos >> Chunk::COORD_BIT_SIZE); 162 | 163 | for($m = 0; $m < 4; ++$m){ 164 | $dens = $d9; 165 | for($n = 0; $n < 4; ++$n){ 166 | // any density higher than 0 is ground, any density lower or equal 167 | // to 0 is air (or lava if under the lava level). 168 | if($dens > 0){ 169 | $sub_chunk->setBlockStateId($m + ($i << 2), $y_block_pos, $n + ($j << 2), $nether_rack); 170 | }elseif($l + ($k << 3) < 32){ 171 | $sub_chunk->setBlockStateId($m + ($i << 2), $y_block_pos, $n + ($j << 2), $still_lava); 172 | } 173 | // interpolation along z 174 | $dens += ($d10 - $d9) / 4; 175 | } 176 | // interpolation along x 177 | $d9 += ($d2 - $d1) / 4; 178 | // interpolate along z 179 | $d10 += ($d4 - $d3) / 4; 180 | } 181 | // interpolation along y 182 | $d1 += $d5; 183 | $d3 += $d7; 184 | $d2 += $d6; 185 | $d4 += $d8; 186 | } 187 | } 188 | } 189 | } 190 | } 191 | 192 | /** 193 | * @return float[] 194 | */ 195 | private function generateTerrainDensity(int $x, int $z) : array{ 196 | $octaves = $this->getWorldOctaves(); 197 | $height_noise = $octaves->height->getFractalBrownianMotion($x, 0, $z, 0.5, 2.0); 198 | $roughness_noise = $octaves->roughness->getFractalBrownianMotion($x, 0, $z, 0.5, 2.0); 199 | $roughness_noise_2 = $octaves->roughness_2->getFractalBrownianMotion($x, 0, $z, 0.5, 2.0); 200 | $detail_noise = $octaves->detail->getFractalBrownianMotion($x, 0, $z, 0.5, 2.0); 201 | 202 | $k_max = $octaves->detail->size_y; 203 | 204 | static $nv = null; 205 | if($nv === null){ 206 | $nv = []; 207 | for($i = 0; $i < $k_max; ++$i){ 208 | $nv[$i] = cos($i * M_PI * 6.0 / $k_max) * 2.0; 209 | $nh = $i > $k_max / 2 ? $k_max - 1 - $i : $i; 210 | if($nh < 4.0){ 211 | $nh = 4.0 - $nh; 212 | $nv[$i] -= $nh * $nh * $nh * 10.0; 213 | } 214 | } 215 | } 216 | 217 | $index = 0; 218 | $index_height = 0; 219 | 220 | $density = []; 221 | 222 | for($i = 0; $i < 5; ++$i){ 223 | for($j = 0; $j < 5; ++$j){ 224 | 225 | $noise_h = $height_noise[$index_height++] / 8000.0; 226 | if($noise_h < 0){ 227 | $noise_h = -$noise_h; 228 | } 229 | $noise_h = $noise_h * 3.0 - 3.0; 230 | if($noise_h < 0){ 231 | $noise_h = max($noise_h * 0.5, -1) / 1.4 * 0.5; 232 | }else{ 233 | $noise_h = min($noise_h, 1) / 6.0; 234 | } 235 | 236 | $noise_h = $noise_h * $k_max / 16.0; 237 | for($k = 0; $k < $k_max; ++$k){ 238 | $noise_r = $roughness_noise[$index] / 512.0; 239 | $noise_r_2 = $roughness_noise_2[$index] / 512.0; 240 | $noise_d = ($detail_noise[$index] / 10.0 + 1.0) / 2.0; 241 | $nh = $nv[$k]; 242 | // linear interpolation 243 | $dens = $noise_d < 0 ? $noise_r : ($noise_d > 1 ? $noise_r_2 : $noise_r + ($noise_r_2 - $noise_r) * $noise_d); 244 | $dens -= $nh; 245 | ++$index; 246 | $k_cap = $k_max - 4; 247 | if($k > $k_cap){ 248 | $lowering = ($k - $k_cap) / 3.0; 249 | $dens = $dens * (1.0 - $lowering) + $lowering * -10.0; 250 | } 251 | $density[self::densityHash($i, $j, $k)] = $dens; 252 | } 253 | } 254 | } 255 | 256 | return $density; 257 | } 258 | 259 | public function generateTerrainColumn(ChunkManager $world, int $x, int $z, float $surface_noise, float $soul_sand_noise, float $grave_noise) : void{ 260 | $soul_sand = $soul_sand_noise + $this->random->nextFloat() * 0.2 > 0; 261 | $gravel = $grave_noise + $this->random->nextFloat() * 0.2 > 0; 262 | 263 | $surface_height = (int) ($surface_noise / 3.0 + 3.0 + $this->random->nextFloat() * 0.25); 264 | $deep = -1; 265 | $world_height = $this->getMaxY(); 266 | $world_height_m1 = $world_height - 1; 267 | 268 | $block_bedrock = VanillaBlocks::BEDROCK()->getStateId(); 269 | $block_air = VanillaBlocks::AIR()->getStateId(); 270 | $block_nether_rack = VanillaBlocks::NETHERRACK()->getStateId(); 271 | $block_gravel = VanillaBlocks::GRAVEL()->getStateId(); 272 | $block_soul_sand = VanillaBlocks::SOUL_SAND()->getStateId(); 273 | 274 | $top_mat = $block_nether_rack; 275 | $ground_mat = $block_nether_rack; 276 | 277 | /** @var Chunk $chunk */ 278 | $chunk = $world->getChunk($x >> Chunk::COORD_BIT_SIZE, $z >> Chunk::COORD_BIT_SIZE); 279 | $chunk_block_x = $x & Chunk::COORD_MASK; 280 | $chunk_block_z = $z & Chunk::COORD_MASK; 281 | 282 | for($y = $world_height_m1; $y >= 0; --$y){ 283 | if($y <= $this->random->nextBoundedInt($this->bedrock_roughness) || $y >= $world_height_m1 - $this->random->nextBoundedInt($this->bedrock_roughness)){ 284 | $chunk->setBlockStateId($chunk_block_x, $y, $chunk_block_z, $block_bedrock); 285 | continue; 286 | } 287 | $mat = $chunk->getBlockStateId($chunk_block_x, $y, $chunk_block_z); 288 | if($mat === $block_air){ 289 | $deep = -1; 290 | }elseif($mat === $block_nether_rack){ 291 | if($deep === -1){ 292 | if($surface_height <= 0){ 293 | $top_mat = $block_air; 294 | $ground_mat = $block_nether_rack; 295 | }elseif($y >= 60 && $y <= 65){ 296 | $top_mat = $block_nether_rack; 297 | $ground_mat = $block_nether_rack; 298 | if($gravel){ 299 | $top_mat = $block_gravel; 300 | $ground_mat = $block_nether_rack; 301 | } 302 | if($soul_sand){ 303 | $top_mat = $block_soul_sand; 304 | $ground_mat = $block_soul_sand; 305 | } 306 | } 307 | 308 | $deep = $surface_height; 309 | if($y >= 63){ 310 | $chunk->setBlockStateId($chunk_block_x, $y, $chunk_block_z, $top_mat); 311 | }else{ 312 | $chunk->setBlockStateId($chunk_block_x, $y, $chunk_block_z, $ground_mat); 313 | } 314 | }elseif($deep > 0){ 315 | --$deep; 316 | $chunk->setBlockStateId($chunk_block_x, $y, $chunk_block_z, $ground_mat); 317 | } 318 | } 319 | } 320 | } 321 | } 322 | -------------------------------------------------------------------------------- /src/world/generator/nether/WorldType.php: -------------------------------------------------------------------------------- 1 | self::AMPLIFIED, 15 | "default_1_1", "version_1_1" => self::VERSION_1_1, 16 | "flat" => self::FLAT, 17 | "largebiomes", "large_biomes" => self::LARGE_BIOMES, 18 | "normal" => self::NORMAL, 19 | default => throw new InvalidArgumentException("Could not convert string \"{$string}\" to a " . self::class . " constant") 20 | }; 21 | } 22 | 23 | public const AMPLIFIED = "AMPLIFIED"; 24 | public const FLAT = "FLAT"; 25 | public const LARGE_BIOMES = "LARGEBIOMES"; 26 | public const NORMAL = "DEFAULT"; 27 | public const VERSION_1_1 = "DEFAULT_1_1"; 28 | 29 | private function __construct(){ 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/world/generator/nether/decorator/FireDecorator.php: -------------------------------------------------------------------------------- 1 | nextBoundedInt(1 + $random->nextBoundedInt(10)); 18 | 19 | $height = $world->getMaxY(); 20 | $source_y_margin = 8 * ($height >> 7); 21 | 22 | for($j = 0; $j < $amount; ++$j){ 23 | $source_x = ($chunk_x << Chunk::COORD_BIT_SIZE) + $random->nextBoundedInt(16); 24 | $source_z = ($chunk_z << Chunk::COORD_BIT_SIZE) + $random->nextBoundedInt(16); 25 | $source_y = 4 + $random->nextBoundedInt($source_y_margin); 26 | 27 | for($i = 0; $i < 64; ++$i){ 28 | $x = $source_x + $random->nextBoundedInt(8) - $random->nextBoundedInt(8); 29 | $z = $source_z + $random->nextBoundedInt(8) - $random->nextBoundedInt(8); 30 | $y = $source_y + $random->nextBoundedInt(4) - $random->nextBoundedInt(4); 31 | 32 | $block = $world->getBlockAt($x, $y, $z); 33 | $block_below = $world->getBlockAt($x, $y - 1, $z); 34 | if( 35 | $y < $height && 36 | $block->getTypeId() === BlockTypeIds::AIR && 37 | $block_below->getTypeId() === BlockTypeIds::NETHERRACK 38 | ){ 39 | $world->setBlockAt($x, $y, $z, VanillaBlocks::FIRE()); 40 | } 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/world/generator/nether/decorator/GlowstoneDecorator.php: -------------------------------------------------------------------------------- 1 | variable_amount ? 1 + $random->nextBoundedInt(1 + $random->nextBoundedInt(10)) : 10; 27 | 28 | $height = $world->getMaxY(); 29 | $source_y_margin = 8 * ($height >> 7); 30 | 31 | for($i = 0; $i < $amount; ++$i){ 32 | $source_x = ($chunk_x << Chunk::COORD_BIT_SIZE) + $random->nextBoundedInt(16); 33 | $source_z = ($chunk_z << Chunk::COORD_BIT_SIZE) + $random->nextBoundedInt(16); 34 | $source_y = 4 + $random->nextBoundedInt($height - $source_y_margin); 35 | 36 | $block = $world->getBlockAt($source_x, $source_y, $source_z); 37 | if( 38 | $block->getTypeId() !== BlockTypeIds::AIR || 39 | $world->getBlockAt($source_x, $source_y + 1, $source_z)->getTypeId() !== BlockTypeIds::NETHERRACK 40 | ){ 41 | continue; 42 | } 43 | 44 | $world->setBlockAt($source_x, $source_y, $source_z, VanillaBlocks::GLOWSTONE()); 45 | 46 | for($j = 0; $j < 1500; ++$j){ 47 | $x = $source_x + $random->nextBoundedInt(8) - $random->nextBoundedInt(8); 48 | $z = $source_z + $random->nextBoundedInt(8) - $random->nextBoundedInt(8); 49 | $y = $source_y - $random->nextBoundedInt(12); 50 | $block = $world->getBlockAt($x, $y, $z); 51 | if($block->getTypeId() !== BlockTypeIds::AIR){ 52 | continue; 53 | } 54 | 55 | $glowstone_block_count = 0; 56 | $vector = new Vector3($x, $y, $z); 57 | foreach(self::SIDES as $face){ 58 | $pos = $vector->getSide($face); 59 | if($world->getBlockAt($pos->x, $pos->y, $pos->z)->getTypeId() === BlockTypeIds::GLOWSTONE){ 60 | ++$glowstone_block_count; 61 | } 62 | } 63 | 64 | if($glowstone_block_count === 1){ 65 | $world->setBlockAt($x, $y, $z, VanillaBlocks::GLOWSTONE()); 66 | } 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/world/generator/nether/decorator/MushroomDecorator.php: -------------------------------------------------------------------------------- 1 | */ 18 | private static array $MATERIALS; 19 | 20 | public static function init() : void{ 21 | self::$MATERIALS = []; 22 | foreach([BlockTypeIds::NETHERRACK, BlockTypeIds::NETHER_QUARTZ_ORE, BlockTypeIds::SOUL_SAND, BlockTypeIds::GRAVEL] as $block_id){ 23 | self::$MATERIALS[$block_id] = $block_id; 24 | } 25 | } 26 | 27 | public function __construct( 28 | private Block $type 29 | ){ 30 | } 31 | 32 | public function decorate(ChunkManager $world, Random $random, int $chunk_x, int $chunk_z, Chunk $chunk) : void{ 33 | $height = $world->getMaxY(); 34 | 35 | $source_x = ($chunk_x << Chunk::COORD_BIT_SIZE) + $random->nextBoundedInt(16); 36 | $source_z = ($chunk_z << Chunk::COORD_BIT_SIZE) + $random->nextBoundedInt(16); 37 | $source_y = $random->nextBoundedInt($height); 38 | 39 | for($i = 0; $i < 64; ++$i){ 40 | $x = $source_x + $random->nextBoundedInt(8) - $random->nextBoundedInt(8); 41 | $z = $source_z + $random->nextBoundedInt(8) - $random->nextBoundedInt(8); 42 | $y = $source_y + $random->nextBoundedInt(4) - $random->nextBoundedInt(4); 43 | 44 | $block = $world->getBlockAt($x, $y, $z); 45 | $block_below = $world->getBlockAt($x, $y - 1, $z); 46 | if( 47 | $y < $height && 48 | $block->getTypeId() === BlockTypeIds::AIR && 49 | array_key_exists($block_below->getTypeId(), self::$MATERIALS) 50 | ){ 51 | $world->setBlockAt($x, $y, $z, $this->type); 52 | } 53 | } 54 | } 55 | } 56 | 57 | MushroomDecorator::init(); 58 | -------------------------------------------------------------------------------- /src/world/generator/nether/populator/NetherPopulator.php: -------------------------------------------------------------------------------- 1 | ore_populator = new OrePopulator($world_height); 35 | $this->in_ground_populators[] = $this->ore_populator; 36 | 37 | $this->fire_decorator = new FireDecorator(); 38 | $this->glowstone_decorator_1 = new GlowstoneDecorator(true); 39 | $this->glowstone_decorator_2 = new GlowstoneDecorator(); 40 | $this->brown_mushroom_decorator = new MushroomDecorator(VanillaBlocks::BROWN_MUSHROOM()); 41 | $this->red_mushroom_decorator = new MushroomDecorator(VanillaBlocks::RED_MUSHROOM()); 42 | 43 | array_push($this->on_ground_populators, 44 | $this->fire_decorator, 45 | $this->glowstone_decorator_1, 46 | $this->glowstone_decorator_2, 47 | $this->fire_decorator, 48 | $this->brown_mushroom_decorator, 49 | $this->red_mushroom_decorator 50 | ); 51 | 52 | $this->fire_decorator->setAmount(1); 53 | $this->glowstone_decorator_1->setAmount(1); 54 | $this->glowstone_decorator_2->setAmount(1); 55 | $this->brown_mushroom_decorator->setAmount(1); 56 | $this->red_mushroom_decorator->setAmount(1); 57 | } 58 | 59 | public function populate(ChunkManager $world, Random $random, int $chunk_x, int $chunk_z, Chunk $chunk) : void{ 60 | $this->populateInGround($world, $random, $chunk_x, $chunk_z, $chunk); 61 | $this->populateOnGround($world, $random, $chunk_x, $chunk_z, $chunk); 62 | } 63 | 64 | private function populateInGround(ChunkManager $world, Random $random, int $chunk_x, int $chunk_z, Chunk $chunk) : void{ 65 | foreach($this->in_ground_populators as $populator){ 66 | $populator->populate($world, $random, $chunk_x, $chunk_z, $chunk); 67 | } 68 | } 69 | 70 | private function populateOnGround(ChunkManager $world, Random $random, int $chunk_x, int $chunk_z, Chunk $chunk) : void{ 71 | foreach($this->on_ground_populators as $populator){ 72 | $populator->populate($world, $random, $chunk_x, $chunk_z, $chunk); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/world/generator/nether/populator/OrePopulator.php: -------------------------------------------------------------------------------- 1 | addOre(new OreType(VanillaBlocks::NETHER_QUARTZ_ORE(), 10, $world_height - (10 * ($world_height >> 7)), 13, BlockTypeIds::NETHERRACK), 16); 21 | $this->addOre(new OreType(VanillaBlocks::MAGMA(), 26, 32 + (5 * ($world_height >> 7)), 32, BlockTypeIds::NETHERRACK), 16); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/world/generator/nether/populator/biome/OrePopulator.php: -------------------------------------------------------------------------------- 1 | addOre(new OreType(VanillaBlocks::DIRT(), 0, 256, 32), 10); 27 | $this->addOre(new OreType(VanillaBlocks::GRAVEL(), 0, 256, 32), 8); 28 | $this->addOre(new OreType(VanillaBlocks::GRANITE(), 0, 80, 32), 10); 29 | $this->addOre(new OreType(VanillaBlocks::DIORITE(), 0, 80, 32), 10); 30 | $this->addOre(new OreType(VanillaBlocks::ANDESITE(), 0, 80, 32), 10); 31 | $this->addOre(new OreType(VanillaBlocks::COAL_ORE(), 0, 128, 16), 20); 32 | $this->addOre(new OreType(VanillaBlocks::IRON_ORE(), 0, 64, 8), 20); 33 | $this->addOre(new OreType(VanillaBlocks::GOLD_ORE(), 0, 32, 8), 2); 34 | $this->addOre(new OreType(VanillaBlocks::REDSTONE_ORE(), 0, 16, 7), 8); 35 | $this->addOre(new OreType(VanillaBlocks::DIAMOND_ORE(), 0, 16, 7), 1); 36 | $this->addOre(new OreType(VanillaBlocks::LAPIS_LAZULI_ORE(), 16, 16, 6), 1); 37 | } 38 | 39 | protected function addOre(OreType $type, int $value) : void{ 40 | $this->ores[] = new OreTypeHolder($type, $value); 41 | } 42 | 43 | public function populate(ChunkManager $world, Random $random, int $chunk_x, int $chunk_z, Chunk $chunk) : void{ 44 | $cx = $chunk_x << Chunk::COORD_BIT_SIZE; 45 | $cz = $chunk_z << Chunk::COORD_BIT_SIZE; 46 | 47 | foreach($this->ores as $ore_type_holder){ 48 | for($n = 0; $n < $ore_type_holder->value; ++$n){ 49 | $source_x = $cx + $random->nextBoundedInt(16); 50 | $source_z = $cz + $random->nextBoundedInt(16); 51 | $source_y = $ore_type_holder->type->getRandomHeight($random); 52 | (new OreVein($ore_type_holder->type))->generate($world, $random, $source_x, $source_y, $source_z); 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/world/generator/nether/populator/biome/utils/OreTypeHolder.php: -------------------------------------------------------------------------------- 1 | 24 | * This is the equivalent to setting each coordinate to the specified 25 | * value. 26 | * 27 | * @param float $scale New value to scale each coordinate by 28 | */ 29 | public function setScale(float $scale) : void{ 30 | $this->x_scale = $scale; 31 | $this->y_scale = $scale; 32 | $this->z_scale = $scale; 33 | } 34 | 35 | /** 36 | * Gets a clone of the individual octaves used within this generator 37 | * 38 | * @return NoiseGenerator[] clone of the individual octaves 39 | */ 40 | public function getOctaves() : array{ 41 | $octaves = []; 42 | foreach($this->octaves as $key => $value){ 43 | $octaves[$key] = clone $value; 44 | } 45 | 46 | return $octaves; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/world/generator/noise/bukkit/BasePerlinNoiseGenerator.php: -------------------------------------------------------------------------------- 1 | perm[$i] = $p[$i & 255]; 43 | } 44 | }else{ 45 | $this->offset_x = $rand->nextFloat() * 256; 46 | $this->offset_y = $rand->nextFloat() * 256; 47 | $this->offset_z = $rand->nextFloat() * 256; 48 | 49 | for($i = 0; $i < 256; ++$i){ 50 | $this->perm[$i] = $rand->nextBoundedInt(256); 51 | } 52 | 53 | for($i = 0; $i < 256; ++$i){ 54 | $pos = $rand->nextBoundedInt(256 - $i) + $i; 55 | $old = $this->perm[$i]; 56 | 57 | $this->perm[$i] = $this->perm[$pos]; 58 | $this->perm[$pos] = $old; 59 | $this->perm[$i + 256] = $this->perm[$i]; 60 | } 61 | } 62 | } 63 | 64 | public function noise3d(float $x, float $y = 0.0, float $z = 0.0) : float{ 65 | $x += $this->offset_x; 66 | $y += $this->offset_y; 67 | $z += $this->offset_z; 68 | 69 | $floor_x = self::floor($x); 70 | $floor_y = self::floor($y); 71 | $floor_z = self::floor($z); 72 | 73 | // Find unit cube containing the point 74 | $X = $floor_x & 255; 75 | $Y = $floor_y & 255; 76 | $Z = $floor_z & 255; 77 | 78 | // Get relative xyz coordinates of the point within the cube 79 | $x -= $floor_x; 80 | $y -= $floor_y; 81 | $z -= $floor_z; 82 | 83 | // Compute fade curves for xyz 84 | $fX = self::fade($x); 85 | $fY = self::fade($y); 86 | $fZ = self::fade($z); 87 | 88 | // Hash coordinates of the cube corners 89 | $A = $this->perm[$X] + $Y; 90 | $AA = $this->perm[$A] + $Z; 91 | $AB = $this->perm[$A + 1] + $Z; 92 | $B = $this->perm[$X + 1] + $Y; 93 | $BA = $this->perm[$B] + $Z; 94 | $BB = $this->perm[$B + 1] + $Z; 95 | 96 | return self::lerp($fZ, self::lerp($fY, self::lerp($fX, self::grad($this->perm[$AA], $x, $y, $z), 97 | self::grad($this->perm[$BA], $x - 1, $y, $z)), 98 | self::lerp($fX, self::grad($this->perm[$AB], $x, $y - 1, $z), 99 | self::grad($this->perm[$BB], $x - 1, $y - 1, $z))), 100 | self::lerp($fY, self::lerp($fX, self::grad($this->perm[$AA + 1], $x, $y, $z - 1), 101 | self::grad($this->perm[$BA + 1], $x - 1, $y, $z - 1)), 102 | self::lerp($fX, self::grad($this->perm[$AB + 1], $x, $y - 1, $z - 1), 103 | self::grad($this->perm[$BB + 1], $x - 1, $y - 1, $z - 1)))); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/world/generator/noise/bukkit/NoiseGenerator.php: -------------------------------------------------------------------------------- 1 | = 0 ? (int) $x : (int) $x - 1; 18 | } 19 | 20 | protected static function fade(float $x) : float{ 21 | return $x * $x * $x * ($x * ($x * 6 - 15) + 10); 22 | } 23 | 24 | protected static function lerp(float $x, float $y, float $z) : float{ 25 | return $y + $x * ($z - $y); 26 | } 27 | 28 | protected static function grad(int $hash, float $x, float $y, float $z) : float{ 29 | $hash &= 15; 30 | $u = $hash < 8 ? $x : $y; 31 | $v = $hash < 4 ? $y : ($hash === 12 || $hash === 14 ? $x : $z); 32 | return (($hash & 1) === 0 ? $u : -$u) + (($hash & 2) === 0 ? $v : -$v); 33 | } 34 | 35 | /** 36 | * Computes and returns the 3D noise for the given coordinates in 3D space 37 | * 38 | * @param float $x X coordinate 39 | * @param float $y Y coordinate 40 | * @param float $z Z coordinate 41 | * 42 | * @return float at given location, from range -1 to 1 43 | */ 44 | abstract public function noise3d(float $x, float $y = 0.0, float $z = 0.0) : float; 45 | } 46 | -------------------------------------------------------------------------------- /src/world/generator/noise/bukkit/OctaveGenerator.php: -------------------------------------------------------------------------------- 1 | x_scale; 29 | $y *= $this->y_scale; 30 | $z *= $this->z_scale; 31 | 32 | foreach($this->octaves as $octave){ 33 | $result += $octave->noise3d($x * $freq, $y * $freq, $z * $freq) * $amp; 34 | $max += $amp; 35 | $freq *= $frequency; 36 | $amp *= $amplitude; 37 | } 38 | 39 | if($normalized){ 40 | $result /= $max; 41 | } 42 | 43 | return $result; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/world/generator/noise/bukkit/PerlinNoiseGenerator.php: -------------------------------------------------------------------------------- 1 | noise3d($x, $y, $z); 20 | } 21 | 22 | /** 23 | * Generates noise for the 3D coordinates using the specified number of 24 | * octaves and parameters 25 | * 26 | * @param float $x X-coordinate 27 | * @param float $y Y-coordinate 28 | * @param float $z Z-coordinate 29 | * @param int $octaves Number of octaves to use 30 | * @param float $frequency How much to alter the frequency by each octave 31 | * @param float $amplitude How much to alter the amplitude by each octave 32 | * 33 | * @return float resulting noise 34 | */ 35 | public static function getNoise(float $x, float $y, float $z, int $octaves, float $frequency, float $amplitude) : float{ 36 | return self::getInstance()->noise($x, $y, $z, $octaves, $frequency, $amplitude); 37 | } 38 | 39 | public function noise3d(float $x, float $y = 0.0, float $z = 0.0) : float{ 40 | $x += $this->offset_x; 41 | $y += $this->offset_y; 42 | $z += $this->offset_z; 43 | 44 | $floor_x = self::floor($x); 45 | $floor_y = self::floor($y); 46 | $floor_z = self::floor($z); 47 | 48 | // Find unit cube containing the point 49 | $X = $floor_x & 255; 50 | $Y = $floor_y & 255; 51 | $Z = $floor_z & 255; 52 | 53 | // Get relative xyz coordinates of the point within the cube 54 | $x -= $floor_x; 55 | $y -= $floor_y; 56 | $z -= $floor_z; 57 | 58 | // Compute fade curves for xyz 59 | $fX = self::fade($x); 60 | $fY = self::fade($y); 61 | $fZ = self::fade($z); 62 | 63 | // Hash coordinates of the cube corners 64 | $A = $this->perm[$X] + $Y; 65 | $AA = $this->perm[$A] + $Z; 66 | $AB = $this->perm[$A + 1] + $Z; 67 | $B = $this->perm[$X + 1] + $Y; 68 | $BA = $this->perm[$B] + $Z; 69 | $BB = $this->perm[$B + 1] + $Z; 70 | 71 | return self::lerp($fZ, self::lerp($fY, self::lerp($fX, self::grad($this->perm[$AA], $x, $y, $z), 72 | self::grad($this->perm[$BA], $x - 1, $y, $z)), 73 | self::lerp($fX, self::grad($this->perm[$AB], $x, $y - 1, $z), 74 | self::grad($this->perm[$BB], $x - 1, $y - 1, $z))), 75 | self::lerp($fY, self::lerp($fX, self::grad($this->perm[$AA + 1], $x, $y, $z - 1), 76 | self::grad($this->perm[$BA + 1], $x - 1, $y, $z - 1)), 77 | self::lerp($fX, self::grad($this->perm[$AB + 1], $x, $y - 1, $z - 1), 78 | self::grad($this->perm[$BB + 1], $x - 1, $y - 1, $z - 1)))); 79 | } 80 | 81 | /** 82 | * Generates noise for the 3D coordinates using the specified number of 83 | * octaves and parameters 84 | * 85 | * @param float $x X-coordinate 86 | * @param float $y Y-coordinate 87 | * @param float $z Z-coordinate 88 | * @param int $octaves Number of octaves to use 89 | * @param float $frequency How much to alter the frequency by each octave 90 | * @param float $amplitude How much to alter the amplitude by each octave 91 | * @param bool $normalized If true, normalize the value to [-1, 1] 92 | * 93 | * @return float Resulting noise 94 | */ 95 | public function noise(float $x, float $y, float $z, int $octaves, float $frequency, float $amplitude, bool $normalized = false) : float{ 96 | $result = 0.0; 97 | $amp = 1.0; 98 | $freq = 1.0; 99 | $max = 0.0; 100 | 101 | for($i = 0; $i < $octaves; ++$i){ 102 | $result += $this->noise3d($x * $freq, $y * $freq, $z * $freq) * $amp; 103 | $max += $amp; 104 | $freq *= $frequency; 105 | $amp *= $amplitude; 106 | } 107 | 108 | if($normalized){ 109 | $result /= $max; 110 | } 111 | 112 | return $result; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/world/generator/noise/bukkit/SimplexOctaveGenerator.php: -------------------------------------------------------------------------------- 1 | w_scale = $scale; 38 | } 39 | 40 | /** 41 | * Generates noise for the 3D coordinates using the specified number of 42 | * octaves and parameters 43 | * 44 | * @param float $x X-coordinate 45 | * @param float $y Y-coordinate 46 | * @param float $z Z-coordinate 47 | * @param float $frequency How much to alter the frequency by each octave 48 | * @param float $amplitude How much to alter the amplitude by each octave 49 | * @param bool $normalized If true, normalize the value to [-1, 1] 50 | * 51 | * @return float resulting noise 52 | */ 53 | public function octaveNoise(float $x, float $y, float $z, float $frequency, float $amplitude, bool $normalized) : float{ 54 | $result = 0.0; 55 | $amp = 1.0; 56 | $freq = 1.0; 57 | $max = 0.0; 58 | 59 | $x *= $this->x_scale; 60 | $y *= $this->y_scale; 61 | $z *= $this->z_scale; 62 | 63 | foreach($this->octaves as $octave){ 64 | $result += $octave->noise3d($x * $freq, $y * $freq, $z * $freq) * $amp; 65 | $max += $amp; 66 | $freq *= $frequency; 67 | $amp *= $amplitude; 68 | } 69 | 70 | if($normalized){ 71 | $result /= $max; 72 | } 73 | 74 | return $result; 75 | } 76 | 77 | /** 78 | * Generates noise for the 3D coordinates using the specified number of 79 | * octaves and parameters 80 | * 81 | * @param float $x X-coordinate 82 | * @param float $y Y-coordinate 83 | * @param float $z Z-coordinate 84 | * @param float $w W-coordinate 85 | * @param float $frequency How much to alter the frequency by each octave 86 | * @param float $amplitude How much to alter the amplitude by each octave 87 | * @param bool $normalized If true, normalize the value to [-1, 1] 88 | * 89 | * @return float resulting noise 90 | */ 91 | public function noise(float $x, float $y, float $z, float $w, float $frequency, float $amplitude, bool $normalized = false) : float{ 92 | $result = 0.0; 93 | $amp = 1.0; 94 | $freq = 1.0; 95 | $max = 0.0; 96 | 97 | $x *= $this->x_scale; 98 | $y *= $this->y_scale; 99 | $z *= $this->z_scale; 100 | $w *= $this->w_scale; 101 | 102 | /** @var SimplexNoiseGenerator $octave */ 103 | foreach($this->octaves as $octave){ 104 | $result += $octave->noise($x * $freq, $y * $freq, $z * $freq, $w * $freq) * $amp; 105 | $max += $amp; 106 | $freq *= $frequency; 107 | $amp *= $amplitude; 108 | } 109 | 110 | if($normalized){ 111 | $result /= $max; 112 | } 113 | 114 | return $result; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/world/generator/noise/glowstone/PerlinNoise.php: -------------------------------------------------------------------------------- 1 | offset_x = $rand->nextFloat() * 256; 21 | $this->offset_y = $rand->nextFloat() * 256; 22 | $this->offset_z = $rand->nextFloat() * 256; 23 | 24 | // The only reason why I'm re-implementing the constructor code is that I've read 25 | // on at least 3 different sources that the permutation table should initially be 26 | // populated with indices. 27 | // "The permutation table is his answer to the issue of random numbers. 28 | // First take an array of decent length, usually 256 values. Fill it sequentially with each 29 | // number in that range: so index 1 gets 1, index 8 gets 8, index 251 gets 251, etc... 30 | // Then randomly shuffle the values so you have a table of 256 random values, but only 31 | // contains the values between 0 and 255." 32 | // source: https://code.google.com/p/fractalterraingeneration/wiki/Perlin_Noise 33 | for($i = 0; $i < 256; ++$i){ 34 | $this->perm[$i] = $i; 35 | } 36 | 37 | for($i = 0; $i < 256; ++$i){ 38 | $pos = $rand->nextBoundedInt(256 - $i) + $i; 39 | $old = $this->perm[$i]; 40 | $this->perm[$i] = $this->perm[$pos]; 41 | $this->perm[$pos] = $old; 42 | $this->perm[$i + 256] = $this->perm[$i]; 43 | } 44 | } 45 | 46 | public static function floor(float $x) : int{ 47 | $floored = (int) $x; 48 | return $x < $floored ? $floored - 1 : $floored; 49 | } 50 | 51 | /** 52 | * Generates a rectangular section of this generator's noise. 53 | * 54 | * @param float[] $noise the output of the previous noise layer 55 | * @param float $x the X offset 56 | * @param float $y the Y offset 57 | * @param float $z the Z offset 58 | * @param int $size_x the size on the X axis 59 | * @param int $size_y the size on the Y axis 60 | * @param int $size_z the size on the Z axis 61 | * @param float $scale_x the X scale parameter 62 | * @param float $scale_y the Y scale parameter 63 | * @param float $scale_z the Z scale parameter 64 | * @param float $amplitude the amplitude parameter 65 | * 66 | * @return float[] noise with this layer of noise added 67 | */ 68 | public function getNoise(array &$noise, float $x, float $y, float $z, int $size_x, int $size_y, int $size_z, float $scale_x, float $scale_y, float $scale_z, float $amplitude) : array{ 69 | if($size_y === 1){ 70 | return $this->get2dNoise($noise, $x, $z, $size_x, $size_z, $scale_x, $scale_z, $amplitude); 71 | } 72 | 73 | return $this->get3dNoise($noise, $x, $y, $z, $size_x, $size_y, $size_z, $scale_x, $scale_y, $scale_z, $amplitude); 74 | } 75 | 76 | /** 77 | * @param float[] $noise 78 | * 79 | * @return float[] 80 | */ 81 | protected function get2dNoise(array &$noise, float $x, float $z, int $size_x, int $size_z, float $scale_x, float $scale_z, float $amplitude) : array{ 82 | $index = -1; 83 | for($i = 0; $i < $size_x; ++$i){ 84 | $dx = $x + $this->offset_x + $i * $scale_x; 85 | $floor_x = self::floor($dx); 86 | $ix = $floor_x & 255; 87 | $dx -= $floor_x; 88 | $fx = self::fade($dx); 89 | for($j = 0; $j < $size_z; ++$j){ 90 | $dz = $z + $this->offset_z + $j * $scale_z; 91 | $floor_z = self::floor($dz); 92 | $iz = $floor_z & 255; 93 | $dz -= $floor_z; 94 | $fz = self::fade($dz); 95 | // Hash coordinates of the square corners 96 | $a = $this->perm[$ix]; 97 | $aa = $this->perm[$a] + $iz; 98 | $b = $this->perm[$ix + 1]; 99 | $ba = $this->perm[$b] + $iz; 100 | $x1 = self::lerp($fx, self::grad($this->perm[$aa], $dx, 0, $dz), self::grad($this->perm[$ba], $dx - 1, 0, $dz)); 101 | $x2 = self::lerp($fx, self::grad($this->perm[$aa + 1], $dx, 0, $dz - 1), 102 | self::grad($this->perm[$ba + 1], $dx - 1, 0, $dz - 1)); 103 | 104 | $noise[++$index] += self::lerp($fz, $x1, $x2) * $amplitude; 105 | } 106 | } 107 | 108 | return $noise; 109 | } 110 | 111 | /** 112 | * @param float[] $noise 113 | * 114 | * @return float[] 115 | */ 116 | protected function get3dNoise(array &$noise, float $x, float $y, float $z, int $size_x, int $size_y, int $size_z, float $scale_x, float $scale_y, float $scale_z, float $amplitude) : array{ 117 | $n = -1; 118 | $x1 = 0; 119 | $x2 = 0; 120 | $x3 = 0; 121 | $x4 = 0; 122 | $index = -1; 123 | for($i = 0; $i < $size_x; ++$i){ 124 | $dx = $x + $this->offset_x + $i * $scale_x; 125 | $floor_x = self::floor($dx); 126 | $ix = $floor_x & 255; 127 | $dx -= $floor_x; 128 | $fx = self::fade($dx); 129 | for($j = 0; $j < $size_z; ++$j){ 130 | $dz = $z + $this->offset_z + $j * $scale_z; 131 | $floor_z = self::floor($dz); 132 | $iz = $floor_z & 255; 133 | $dz -= $floor_z; 134 | $fz = self::fade($dz); 135 | for($k = 0; $k < $size_y; ++$k){ 136 | $dy = $y + $this->offset_y + $k * $scale_y; 137 | $floor_y = self::floor($dy); 138 | $iy = $floor_y & 255; 139 | $dy -= $floor_y; 140 | $fy = self::fade($dy); 141 | if($k === 0 || $iy !== $n){ 142 | $n = $iy; 143 | // Hash coordinates of the cube corners 144 | $a = $this->perm[$ix] + $iy; 145 | $aa = $this->perm[$a] + $iz; 146 | $ab = $this->perm[$a + 1] + $iz; 147 | $b = $this->perm[$ix + 1] + $iy; 148 | $ba = $this->perm[$b] + $iz; 149 | $bb = $this->perm[$b + 1] + $iz; 150 | $x1 = self::lerp($fx, self::grad($this->perm[$aa], $dx, $dy, $dz), self::grad($this->perm[$ba], $dx - 1, $dy, $dz)); 151 | $x2 = self::lerp($fx, self::grad($this->perm[$ab], $dx, $dy - 1, $dz), 152 | self::grad($this->perm[$bb], $dx - 1, $dy - 1, $dz)); 153 | $x3 = self::lerp($fx, self::grad($this->perm[$aa + 1], $dx, $dy, $dz - 1), 154 | self::grad($this->perm[$ba + 1], $dx - 1, $y, $dz - 1)); 155 | $x4 = self::lerp($fx, self::grad($this->perm[$ab + 1], $dx, $dy - 1, $dz - 1), 156 | self::grad($this->perm[$bb + 1], $dx - 1, $dy - 1, $dz - 1)); 157 | } 158 | $y1 = self::lerp($fy, $x1, $x2); 159 | $y2 = self::lerp($fy, $x3, $x4); 160 | 161 | $noise[++$index] += self::lerp($fz, $y1, $y2) * $amplitude; 162 | } 163 | } 164 | } 165 | 166 | return $noise; 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/world/generator/noise/glowstone/PerlinOctaveGenerator.php: -------------------------------------------------------------------------------- 1 | = 0 ? (int) $x : (int) $x - 1; 29 | } 30 | 31 | public static function fromRandomAndOctaves(Random $random, int $octaves, int $size_x, int $size_y, int $size_z) : self{ 32 | return new PerlinOctaveGenerator(self::createOctaves($random, $octaves), $size_x, $size_y, $size_z); 33 | } 34 | 35 | public int $size_x; 36 | public int $size_y; 37 | public int $size_z; 38 | 39 | /** @var float[] */ 40 | protected array $noise; 41 | 42 | /** 43 | * Creates a generator for multiple layers of Perlin noise. 44 | * 45 | * @param NoiseGenerator[] $octaves the noise generators 46 | * @param int $size_x the size on the X axis 47 | * @param int $size_y the size on the Y axis 48 | * @param int $size_z the size on the Z axis 49 | */ 50 | public function __construct(array $octaves, int $size_x, int $size_y, int $size_z){ 51 | parent::__construct($octaves); 52 | $this->size_x = $size_x; 53 | $this->size_y = $size_y; 54 | $this->size_z = $size_z; 55 | $this->noise = array_fill(0, $size_x * $size_y * $size_z, 0.0); 56 | } 57 | 58 | /** 59 | * Generates multiple layers of noise. 60 | * 61 | * @param float $x the starting X coordinate 62 | * @param float $y the starting Y coordinate 63 | * @param float $z the starting Z coordinate 64 | * @param float $lacunarity layer n's frequency as a fraction of layer {@code n - 1}'s frequency 65 | * @param float $persistence layer n's amplitude as a multiple of layer {@code n - 1}'s amplitude 66 | * 67 | * @return float[] the noise array 68 | */ 69 | public function getFractalBrownianMotion(float $x, float $y, float $z, float $lacunarity, float $persistence) : array{ 70 | $this->noise = array_fill(0, $this->size_x * $this->size_y * $this->size_z, 0.0); 71 | 72 | $freq = 1; 73 | $amp = 1; 74 | 75 | $x *= $this->x_scale; 76 | $y *= $this->y_scale; 77 | $z *= $this->z_scale; 78 | 79 | // fBm 80 | // the noise have to be periodic over x and z axis: otherwise it can go crazy with high 81 | // input, leading to strange oddities in terrain generation like the old minecraft farland 82 | // symptoms. 83 | /** @var PerlinNoise $octave */ 84 | foreach($this->octaves as $octave){ 85 | $dx = $x * $freq; 86 | $dz = $z * $freq; 87 | // compute integer part 88 | $lx = self::floor($dx); 89 | $lz = self::floor($dz); 90 | // compute fractional part 91 | $dx -= $lx; 92 | $dz -= $lz; 93 | // wrap integer part to 0..16777216 94 | $lx %= 16777216; 95 | $lz %= 16777216; 96 | // add to fractional part 97 | $dx += $lx; 98 | $dz += $lz; 99 | 100 | $dy = $y * $freq; 101 | $this->noise = $octave->getNoise($this->noise, $dx, $dy, $dz, $this->size_x, $this->size_y, $this->size_z, $this->x_scale * $freq, $this->y_scale * $freq, $this->z_scale * $freq, $amp); 102 | $freq *= $lacunarity; 103 | $amp *= $persistence; 104 | } 105 | 106 | return $this->noise; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/world/generator/noise/glowstone/SimplexNoise.php: -------------------------------------------------------------------------------- 1 | perm_mod_12[$i] = $this->perm[$i] % 12; 44 | } 45 | } 46 | 47 | public static function floor(float $x) : int{ 48 | return $x > 0 ? (int) $x : (int) $x - 1; 49 | } 50 | 51 | protected static function dot(Grad $g, float $x, float $y, float $z = 0.0) : float{ 52 | return $g->x * $x + $g->y * $y + $g->z * $z; 53 | } 54 | 55 | /** 56 | * @param float[] $noise 57 | * 58 | * @return float[] 59 | */ 60 | protected function get2dNoise(array &$noise, float $x, float $z, int $size_x, int $size_y, float $scale_x, float $scale_y, float $amplitude) : array{ 61 | $index = -1; 62 | for($i = 0; $i < $size_y; ++$i){ 63 | $zin = $this->offset_y + ($z + $i) * $scale_y; 64 | for($j = 0; $j < $size_x; ++$j){ 65 | $xin = $this->offset_x + ($x + $j) * $scale_x; 66 | $noise[++$index] += $this->simplex2D($xin, $zin) * $amplitude; 67 | } 68 | } 69 | return $noise; 70 | } 71 | 72 | /** 73 | * @param float[] $noise 74 | * 75 | * @return float[] 76 | */ 77 | protected function get3dNoise(array &$noise, float $x, float $y, float $z, int $size_x, int $size_y, int $sizeZ, float $scale_x, float $scale_y, float $scale_z, float $amplitude) : array{ 78 | $index = -1; 79 | for($i = 0; $i < $sizeZ; ++$i){ 80 | $zin = $this->offset_z + ($z + $i) * $scale_z; 81 | for($j = 0; $j < $size_x; ++$j){ 82 | $xin = $this->offset_x + ($x + $j) * $scale_x; 83 | for($k = 0; $k < $size_y; ++$k){ 84 | $yin = $this->offset_y + ($y + $k) * $scale_y; 85 | $noise[++$index] += $this->simplex3D($xin, $yin, $zin) * $amplitude; 86 | } 87 | } 88 | } 89 | return $noise; 90 | } 91 | 92 | public function noise3d(float $xin, float $yin = 0.0, float $zin = 0.0) : float{ 93 | if($yin === 0.0){ 94 | return parent::noise3d($xin, $yin, $zin); 95 | } 96 | 97 | $xin += $this->offset_x; 98 | $yin += $this->offset_y; 99 | if($xin === 0.0){ 100 | return $this->simplex2D($xin, $yin); 101 | } 102 | 103 | $zin += $this->offset_z; 104 | return $this->simplex3D($xin, $yin, $zin); 105 | } 106 | 107 | private function simplex2D(float $xin, float $yin) : float{ 108 | // Skew the input space to determine which simplex cell we're in 109 | $s = ($xin + $yin) * self::F2; // Hairy factor for 2D 110 | $i = self::floor($xin + $s); 111 | $j = self::floor($yin + $s); 112 | $t = ($i + $j) * self::G2; 113 | $dx0 = $i - $t; // Unskew the cell origin back to (x,y) space 114 | $dy0 = $j - $t; 115 | $x0 = $xin - $dx0; // The x,y distances from the cell origin 116 | $y0 = $yin - $dy0; 117 | 118 | // For the 2D case, the simplex shape is an equilateral triangle. 119 | 120 | // Determine which simplex we are in. 121 | $i1 = 0; // Offsets for second (middle) corner of simplex in (i,j) coords 122 | $j1 = 0; 123 | if($x0 > $y0){ 124 | $i1 = 1; // lower triangle, XY order: (0,0)->(1,0)->(1,1) 125 | $j1 = 0; 126 | }else{ 127 | $i1 = 0; // upper triangle, YX order: (0,0)->(0,1)->(1,1) 128 | $j1 = 1; 129 | } 130 | 131 | // A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and 132 | // a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where 133 | // c = (3-sqrt(3))/6 134 | 135 | $x1 = $x0 - $i1 + self::G2; // Offsets for middle corner in (x,y) unskewed coords 136 | $y1 = $y0 - $j1 + self::G2; 137 | $x2 = $x0 + self::G22; // Offsets for last corner in (x,y) unskewed coords 138 | $y2 = $y0 + self::G22; 139 | 140 | // Work out the hashed gradient indices of the three simplex corners 141 | $ii = $i & 255; 142 | $jj = $j & 255; 143 | $gi0 = $this->perm_mod_12[$ii + $this->perm[$jj]]; 144 | $gi1 = $this->perm_mod_12[$ii + $i1 + $this->perm[$jj + $j1]]; 145 | $gi2 = $this->perm_mod_12[$ii + 1 + $this->perm[$jj + 1]]; 146 | 147 | // Calculate the contribution from the three corners 148 | $t0 = 0.5 - $x0 * $x0 - $y0 * $y0; 149 | $n0 = 0.0; 150 | if($t0 < 0){ 151 | $n0 = 0.0; 152 | }else{ 153 | $t0 *= $t0; 154 | $n0 = $t0 * $t0 * self::dot(self::$grad_3[$gi0], $x0, $y0); // (x,y) of grad_3 used for 2D gradient 155 | } 156 | 157 | $t1 = 0.5 - $x1 * $x1 - $y1 * $y1; 158 | $n1 = 0.0; 159 | if($t1 < 0){ 160 | $n1 = 0.0; 161 | }else{ 162 | $t1 *= $t1; 163 | $n1 = $t1 * $t1 * self::dot(self::$grad_3[$gi1], $x1, $y1); 164 | } 165 | 166 | $t2 = 0.5 - $x2 * $x2 - $y2 * $y2; 167 | $n2 = 0; 168 | if($t2 < 0){ 169 | $n2 = 0.0; 170 | }else{ 171 | $t2 *= $t2; 172 | $n2 = $t2 * $t2 * self::dot(self::$grad_3[$gi2], $x2, $y2); 173 | } 174 | 175 | // Add contributions from each corner to get the final noise value. 176 | // The result is scaled to return values in the interval [-1,1]. 177 | return 70.0 * ($n0 + $n1 + $n2); 178 | } 179 | 180 | private function simplex3D(float $xin, float $yin, float $zin) : float{ 181 | // Skew the input space to determine which simplex cell we're in 182 | $s = ($xin + $yin + $zin) * self::F3; // Very nice and simple skew factor for 3D 183 | $i = self::floor($xin + $s); 184 | $j = self::floor($yin + $s); 185 | $k = self::floor($zin + $s); 186 | $t = ($i + $j + $k) * self::G3; 187 | $dx0 = $i - $t; // Unskew the cell origin back to (x,y,z) space 188 | $dy0 = $j - $t; 189 | $dz0 = $k - $t; 190 | 191 | // For the 3D case, the simplex shape is a slightly irregular tetrahedron. 192 | 193 | $i1 = 0; // Offsets for second corner of simplex in (i,j,k) coords 194 | $j1 = 0; 195 | $k1 = 0; 196 | $i2 = 0; // Offsets for third corner of simplex in (i,j,k) coords 197 | $j2 = 0; 198 | $k2 = 0; 199 | 200 | $x0 = $xin - $dx0; // The x,y,z distances from the cell origin 201 | $y0 = $yin - $dy0; 202 | $z0 = $zin - $dz0; 203 | // Determine which simplex we are in 204 | if($x0 >= $y0){ 205 | if($y0 >= $z0){ 206 | $i1 = 1; // X Y Z order 207 | $j1 = 0; 208 | $k1 = 0; 209 | $i2 = 1; 210 | $j2 = 1; 211 | $k2 = 0; 212 | }elseif($x0 >= $z0){ 213 | $i1 = 1; // X Z Y order 214 | $j1 = 0; 215 | $k1 = 0; 216 | $i2 = 1; 217 | $j2 = 0; 218 | $k2 = 1; 219 | }else{ 220 | $i1 = 0; // Z X Y order 221 | $j1 = 0; 222 | $k1 = 1; 223 | $i2 = 1; 224 | $j2 = 0; 225 | $k2 = 1; 226 | } 227 | }else{ // x0perm_mod_12[$ii + $this->perm[$jj + $this->perm[$kk]]]; 268 | $gi1 = $this->perm_mod_12[$ii + $i1 + $this->perm[$jj + $j1 + $this->perm[$kk + $k1]]]; 269 | $gi2 = $this->perm_mod_12[$ii + $i2 + $this->perm[$jj + $j2 + $this->perm[$kk + $k2]]]; 270 | $gi3 = $this->perm_mod_12[$ii + 1 + $this->perm[$jj + 1 + $this->perm[$kk + 1]]]; 271 | 272 | // Calculate the contribution from the four corners 273 | $t0 = 0.5 - $x0 * $x0 - $y0 * $y0 - $z0 * $z0; 274 | $n0 = 0.0; // Noise contributions from the four corners 275 | if($t0 < 0){ 276 | $n0 = 0.0; 277 | }else{ 278 | $t0 *= $t0; 279 | $n0 = $t0 * $t0 * self::dot(self::$grad_3[$gi0], $x0, $y0, $z0); 280 | } 281 | 282 | $t1 = 0.5 - $x1 * $x1 - $y1 * $y1 - $z1 * $z1; 283 | $n1 = 0.0; 284 | if($t1 < 0){ 285 | $n1 = 0.0; 286 | }else{ 287 | $t1 *= $t1; 288 | $n1 = $t1 * $t1 * self::dot(self::$grad_3[$gi1], $x1, $y1, $z1); 289 | } 290 | 291 | $t2 = 0.5 - $x2 * $x2 - $y2 * $y2 - $z2 * $z2; 292 | $n2 = 0.0; 293 | if($t2 < 0){ 294 | $n2 = 0.0; 295 | }else{ 296 | $t2 *= $t2; 297 | $n2 = $t2 * $t2 * self::dot(self::$grad_3[$gi2], $x2, $y2, $z2); 298 | } 299 | 300 | $x3 = $x0 + self::G33; // Offsets for last corner in (x,y,z) coords 301 | $y3 = $y0 + self::G33; 302 | $z3 = $z0 + self::G33; 303 | $t3 = 0.5 - $x3 * $x3 - $y3 * $y3 - $z3 * $z3; 304 | $n3 = 0.0; 305 | if($t3 < 0){ 306 | $n3 = 0.0; 307 | }else{ 308 | $t3 *= $t3; 309 | $n3 = $t3 * $t3 * self::dot(self::$grad_3[$gi3], $x3, $y3, $z3); 310 | } 311 | 312 | // Add contributions from each corner to get the final noise value. 313 | // The result is scaled to stay just inside [-1,1] 314 | return 32.0 * ($n0 + $n1 + $n2 + $n3); 315 | } 316 | } 317 | 318 | // Inner class to speed up gradient computations 319 | // (array access is a lot slower than member access) 320 | final class Grad{ 321 | 322 | public function __construct( 323 | public float $x, 324 | public float $y, 325 | public float $z 326 | ){ 327 | } 328 | } 329 | 330 | SimplexNoise::init(); 331 | -------------------------------------------------------------------------------- /src/world/generator/noise/glowstone/SimplexOctaveGenerator.php: -------------------------------------------------------------------------------- 1 | noise = array_fill(0, $this->size_x * $this->size_y * $this->size_z, 0.0); 31 | 32 | $freq = 1.0; 33 | $amp = 1.0; 34 | 35 | // fBm 36 | /** @var SimplexNoise $octave */ 37 | foreach($this->octaves as $octave){ 38 | $this->noise = $octave->getNoise($this->noise, $x, $y, $z, $this->size_x, $this->size_y, $this->size_z, $this->x_scale * $freq, $this->y_scale * $freq, $this->z_scale * $freq, 0.55 / $amp); 39 | $freq *= $lacunarity; 40 | $amp *= $persistence; 41 | } 42 | 43 | return $this->noise; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/world/generator/object/OreType.php: -------------------------------------------------------------------------------- 1 | amount = $amount + 1; 34 | } 35 | 36 | /** 37 | * Generates a random height at which a vein of this ore can spawn. 38 | * 39 | * @param Random $random the PRNG to use 40 | * 41 | * @return int a random height for this ore 42 | */ 43 | public function getRandomHeight(Random $random) : int{ 44 | return $this->min_y === $this->max_y 45 | ? $random->nextBoundedInt($this->min_y) + $random->nextBoundedInt($this->min_y) 46 | : $random->nextBoundedInt($this->max_y - $this->min_y) + $this->min_y; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/world/generator/object/OreVein.php: -------------------------------------------------------------------------------- 1 | type = $oreType->type; 44 | $this->amount = $oreType->amount; 45 | $this->target_type = $oreType->target_type; 46 | } 47 | 48 | public function generate(ChunkManager $world, Random $random, int $source_x, int $source_y, int $source_z) : bool{ 49 | $angle = $random->nextFloat() * M_PI; 50 | $dx1 = $source_x + sin($angle) * $this->amount / 8.0; 51 | $dx2 = $source_x - sin($angle) * $this->amount / 8.0; 52 | $dz1 = $source_z + cos($angle) * $this->amount / 8.0; 53 | $dz2 = $source_z - cos($angle) * $this->amount / 8.0; 54 | $dy1 = $source_y + $random->nextBoundedInt(3) - 2; 55 | $dy2 = $source_y + $random->nextBoundedInt(3) - 2; 56 | $succeeded = false; 57 | for($i = 0; $i < $this->amount; ++$i){ 58 | $origin_x = $dx1 + ($dx2 - $dx1) * $i / $this->amount; 59 | $origin_y = $dy1 + ($dy2 - $dy1) * $i / $this->amount; 60 | $origin_z = $dz1 + ($dz2 - $dz1) * $i / $this->amount; 61 | $q = $random->nextFloat() * $this->amount / 16.0; 62 | $radius_h = (sin($i * M_PI / $this->amount) + 1 * $q + 1) / 2.0; 63 | $radius_v = (sin($i * M_PI / $this->amount) + 1 * $q + 1) / 2.0; 64 | 65 | $min_x = (int) ($origin_x - $radius_h); 66 | $max_x = (int) ($origin_x + $radius_h); 67 | 68 | $min_y = (int) ($origin_y - $radius_v); 69 | $max_y = (int) ($origin_y + $radius_v); 70 | 71 | $min_z = (int) ($origin_z - $radius_h); 72 | $max_z = (int) ($origin_z + $radius_h); 73 | 74 | for($x = $min_x; $x <= $max_x; ++$x){ 75 | // scale the center of x to the range [-1, 1] within the circle 76 | $squared_normalized_x = self::normalizedSquaredCoordinate($origin_x, $radius_h, $x); 77 | if($squared_normalized_x >= 1){ 78 | continue; 79 | } 80 | for($y = $min_y; $y <= $max_y; ++$y){ 81 | $squared_normalized_y = self::normalizedSquaredCoordinate($origin_y, $radius_v, $y); 82 | if($squared_normalized_x + $squared_normalized_y >= 1){ 83 | continue; 84 | } 85 | for($z = $min_z; $z <= $max_z; ++$z){ 86 | $squared_normalized_z = self::normalizedSquaredCoordinate($origin_z, $radius_h, $z); 87 | if($squared_normalized_x + $squared_normalized_y + $squared_normalized_z < 1 && $world->getBlockAt($x, $y, $z)->getTypeId() === $this->target_type){ 88 | $world->setBlockAt($x, $y, $z, $this->type); 89 | $succeeded = true; 90 | } 91 | } 92 | } 93 | } 94 | } 95 | 96 | return $succeeded; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/world/generator/object/TerrainObject.php: -------------------------------------------------------------------------------- 1 | getBlockAt($x, $cur_y, $z); 38 | if(!($block instanceof Flowable)){ 39 | break; 40 | } 41 | $world->setBlockAt($x, $cur_y, $z, VanillaBlocks::AIR()); 42 | $changed = true; 43 | ++$cur_y; 44 | } 45 | 46 | return $changed; 47 | } 48 | 49 | /** 50 | * Generates this feature. 51 | * 52 | * @param ChunkManager $world the world to generate in 53 | * @param Random $random the PRNG that will choose the size and a few details of the shape 54 | * @param int $source_x the base X coordinate 55 | * @param int $source_y the base Y coordinate 56 | * @param int $source_z the base Z coordinate 57 | * 58 | * @return bool if successfully generated 59 | */ 60 | abstract public function generate(ChunkManager $world, Random $random, int $source_x, int $source_y, int $source_z) : bool; 61 | } 62 | 63 | TerrainObject::init(); 64 | -------------------------------------------------------------------------------- /src/world/generator/utils/NetherWorldOctaves.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class NetherWorldOctaves extends WorldOctaves{ 20 | 21 | /** 22 | * @param T $height 23 | * @param U $roughness 24 | * @param U $roughness_2 25 | * @param V $detail 26 | * @param W $surface 27 | * @param X $soul_sand 28 | * @param Y $gravel 29 | */ 30 | public function __construct( 31 | OctaveGenerator $height, 32 | OctaveGenerator $roughness, 33 | OctaveGenerator $roughness_2, 34 | OctaveGenerator $detail, 35 | OctaveGenerator $surface, 36 | public OctaveGenerator $soul_sand, 37 | public OctaveGenerator $gravel 38 | ){ 39 | parent::__construct($height, $roughness, $roughness_2, $detail, $surface); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/world/generator/utils/WorldOctaves.php: -------------------------------------------------------------------------------- 1 | $data 59 | */ 60 | public function __construct( 61 | private array $data 62 | ){ 63 | } 64 | 65 | public function exists(string $property) : bool{ 66 | return array_key_exists($property, $this->data); 67 | } 68 | 69 | public function get(string $property) : mixed{ 70 | return $this->data[$property] ?? null; 71 | } 72 | 73 | public function getInt(string $property) : int{ 74 | $value = $this->get($property); 75 | if(!is_int($value)){ 76 | throw new InvalidArgumentException("{$property} is not an integer"); 77 | } 78 | return $value; 79 | } 80 | 81 | public function getFloat(string $property) : float{ 82 | $value = $this->get($property); 83 | if(!is_float($value)){ 84 | throw new InvalidArgumentException("{$property} is not a float"); 85 | } 86 | return $value; 87 | } 88 | 89 | public function getString(string $property) : string{ 90 | $value = $this->get($property); 91 | if(!is_string($value)){ 92 | throw new InvalidArgumentException("{$property} is not a string"); 93 | } 94 | return $value; 95 | } 96 | 97 | public function toString() : string{ 98 | $string = ""; 99 | foreach($this->data as $property => $value){ 100 | $string .= "{$property}={$value},"; 101 | } 102 | return rtrim($string, ","); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/world/provider/DimensionProviderManagerEntry.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | protected array $providers = []; 23 | 24 | private RewritableWorldProviderManagerEntry $default; 25 | 26 | public function __construct(){ 27 | $leveldb = new RewritableWorldProviderManagerEntry(DimensionLevelDBProvider::isValid(...), fn(string $path, Logger $logger, int $dimension, ?LevelDB $db) => new DimensionLevelDBProvider($path, $logger, $dimension, $db), DimensionLevelDBProvider::generate(...)); 28 | $this->default = $leveldb; 29 | $this->addProvider($leveldb, "leveldb"); 30 | 31 | $this->addProvider(new ReadOnlyWorldProviderManagerEntry(Anvil::isValid(...), function(string $path, Logger $logger, int $dimension = 0){ 32 | return match ($dimension) { 33 | DimensionIds::OVERWORLD => new Anvil($path, $logger), 34 | DimensionIds::NETHER => new NetherAnvilProvider($path, $logger), 35 | DimensionIds::THE_END => new EnderAnvilProvider($path, $logger), 36 | default => throw new \UnexpectedValueException("Invalid dimension Id") 37 | }; 38 | }), "anvil"); 39 | $this->addProvider(new ReadOnlyWorldProviderManagerEntry(McRegion::isValid(...), fn(string $path, \Logger $logger) => new McRegion($path, $logger)), "mcregion"); 40 | $this->addProvider(new ReadOnlyWorldProviderManagerEntry(PMAnvil::isValid(...), fn(string $path, \Logger $logger) => new PMAnvil($path, $logger)), "pmanvil"); 41 | } 42 | 43 | /** 44 | * Returns the default format used to generate new worlds. 45 | */ 46 | public function getDefault() : RewritableWorldProviderManagerEntry{ 47 | return $this->default; 48 | } 49 | 50 | public function setDefault(RewritableWorldProviderManagerEntry $class) : void{ 51 | $this->default = $class; 52 | } 53 | 54 | public function addProvider(DimensionProviderManagerEntry $providerEntry, string $name, bool $overwrite = false) : void{ 55 | $name = strtolower($name); 56 | if(!$overwrite && isset($this->providers[$name])){ 57 | throw new \InvalidArgumentException("Alias \"$name\" is already assigned"); 58 | } 59 | 60 | $this->providers[$name] = $providerEntry; 61 | } 62 | 63 | /** 64 | * Returns a WorldProvider class for this path, or null 65 | * 66 | * @return DimensionProviderManagerEntry[] 67 | * @phpstan-return array 68 | */ 69 | public function getMatchingProviders(string $path) : array{ 70 | $result = []; 71 | foreach(Utils::stringifyKeys($this->providers) as $alias => $providerEntry){ 72 | if($providerEntry->isValid($path)){ 73 | $result[$alias] = $providerEntry; 74 | } 75 | } 76 | return $result; 77 | } 78 | 79 | /** 80 | * @return DimensionProviderManagerEntry[] 81 | * @phpstan-return array 82 | */ 83 | public function getAvailableProviders() : array{ 84 | return $this->providers; 85 | } 86 | 87 | /** 88 | * Returns a WorldProvider by name, or null if not found 89 | */ 90 | public function getProviderByName(string $name) : ?DimensionProviderManagerEntry{ 91 | return $this->providers[trim(strtolower($name))] ?? null; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/world/provider/EnderAnvilProvider.php: -------------------------------------------------------------------------------- 1 | path, "dim1", "region", "r.$regionX.$regionZ." . static::getRegionFileExtension()); 21 | } 22 | 23 | public static function isValid(string $path) : bool{ 24 | if(file_exists(Path::join($path, "level.dat")) && is_dir($regionPath = Path::join($path, "dim1", "region"))){ 25 | foreach(scandir($regionPath, SCANDIR_SORT_NONE) as $file){ 26 | $extPos = strrpos($file, "."); 27 | if($extPos !== false && substr($file, $extPos + 1) === static::getRegionFileExtension()){ 28 | //we don't care if other region types exist, we only care if this format is possible 29 | return true; 30 | } 31 | } 32 | } 33 | 34 | return false; 35 | } 36 | 37 | private function createRegionIterator() : \RegexIterator{ 38 | return new \RegexIterator( 39 | new \FilesystemIterator( 40 | Path::join($this->path, 'dim1', 'region'), 41 | \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::UNIX_PATHS 42 | ), 43 | '/\/r\.(-?\d+)\.(-?\d+)\.' . static::getRegionFileExtension() . '$/', 44 | \RegexIterator::GET_MATCH 45 | ); 46 | } 47 | 48 | public function getAllChunks(bool $skipCorrupted = false, ?\Logger $logger = null) : \Generator{ 49 | $iterator = $this->createRegionIterator(); 50 | 51 | foreach($iterator as $region){ 52 | $regionX = ((int) $region[1]); 53 | $regionZ = ((int) $region[2]); 54 | $rX = $regionX << 5; 55 | $rZ = $regionZ << 5; 56 | 57 | for($chunkX = $rX; $chunkX < $rX + 32; ++$chunkX){ 58 | for($chunkZ = $rZ; $chunkZ < $rZ + 32; ++$chunkZ){ 59 | try{ 60 | $chunk = $this->loadChunk($chunkX, $chunkZ); 61 | if($chunk !== null){ 62 | yield [$chunkX, $chunkZ] => $chunk; 63 | } 64 | }catch(CorruptedChunkException $e){ 65 | if(!$skipCorrupted){ 66 | throw $e; 67 | } 68 | if($logger !== null){ 69 | $logger->error("Skipped corrupted chunk $chunkX $chunkZ (" . $e->getMessage() . ")"); 70 | } 71 | } 72 | } 73 | } 74 | 75 | $this->unloadRegion($regionX, $regionZ); 76 | } 77 | } 78 | 79 | public function calculateChunkCount() : int{ 80 | $count = 0; 81 | foreach($this->createRegionIterator() as $region){ 82 | $regionX = ((int) $region[1]); 83 | $regionZ = ((int) $region[2]); 84 | $count += $this->loadRegion($regionX, $regionZ)->calculateChunkCount(); 85 | $this->unloadRegion($regionX, $regionZ); 86 | } 87 | return $count; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/world/provider/NetherAnvilProvider.php: -------------------------------------------------------------------------------- 1 | path, "dim-1", "region", "r.$regionX.$regionZ." . static::getRegionFileExtension()); 21 | } 22 | 23 | public static function isValid(string $path) : bool{ 24 | if(file_exists(Path::join($path, "level.dat")) && is_dir($regionPath = Path::join($path, "dim-1", "region"))){ 25 | foreach(scandir($regionPath, SCANDIR_SORT_NONE) as $file){ 26 | $extPos = strrpos($file, "."); 27 | if($extPos !== false && substr($file, $extPos + 1) === static::getRegionFileExtension()){ 28 | //we don't care if other region types exist, we only care if this format is possible 29 | return true; 30 | } 31 | } 32 | } 33 | 34 | return false; 35 | } 36 | 37 | private function createRegionIterator() : \RegexIterator{ 38 | return new \RegexIterator( 39 | new \FilesystemIterator( 40 | Path::join($this->path, 'dim-1', 'region'), 41 | \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::UNIX_PATHS 42 | ), 43 | '/\/r\.(-?\d+)\.(-?\d+)\.' . static::getRegionFileExtension() . '$/', 44 | \RegexIterator::GET_MATCH 45 | ); 46 | } 47 | 48 | public function getAllChunks(bool $skipCorrupted = false, ?\Logger $logger = null) : \Generator{ 49 | $iterator = $this->createRegionIterator(); 50 | 51 | foreach($iterator as $region){ 52 | $regionX = ((int) $region[1]); 53 | $regionZ = ((int) $region[2]); 54 | $rX = $regionX << 5; 55 | $rZ = $regionZ << 5; 56 | 57 | for($chunkX = $rX; $chunkX < $rX + 32; ++$chunkX){ 58 | for($chunkZ = $rZ; $chunkZ < $rZ + 32; ++$chunkZ){ 59 | try{ 60 | $chunk = $this->loadChunk($chunkX, $chunkZ); 61 | if($chunk !== null){ 62 | yield [$chunkX, $chunkZ] => $chunk; 63 | } 64 | }catch(CorruptedChunkException $e){ 65 | if(!$skipCorrupted){ 66 | throw $e; 67 | } 68 | if($logger !== null){ 69 | $logger->error("Skipped corrupted chunk $chunkX $chunkZ (" . $e->getMessage() . ")"); 70 | } 71 | } 72 | } 73 | } 74 | 75 | $this->unloadRegion($regionX, $regionZ); 76 | } 77 | } 78 | 79 | public function calculateChunkCount() : int{ 80 | $count = 0; 81 | foreach($this->createRegionIterator() as $region){ 82 | $regionX = ((int) $region[1]); 83 | $regionZ = ((int) $region[2]); 84 | $count += $this->loadRegion($regionX, $regionZ)->calculateChunkCount(); 85 | $this->unloadRegion($regionX, $regionZ); 86 | } 87 | return $count; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/world/provider/ReadOnlyWorldProviderManagerEntry.php: -------------------------------------------------------------------------------- 1 | fromPath = $fromPath; 23 | } 24 | 25 | public function fromPath(string $path, Logger $logger, int $dimension = 0) : WorldProvider{ return ($this->fromPath)($path, $logger, $dimension); } 26 | } 27 | -------------------------------------------------------------------------------- /src/world/provider/RewritableWorldProviderManagerEntry.php: -------------------------------------------------------------------------------- 1 | fromPath = $fromPath; 32 | $this->generate = $generate; 33 | } 34 | 35 | public function fromPath(string $path, Logger $logger, int $dimension = 0, LevelDB $db = null) : WritableWorldProvider{ 36 | return ($this->fromPath)($path, $logger, $dimension, $db); 37 | } 38 | 39 | /** 40 | * Generates world manifest files and any other things needed to initialize a new world on disk 41 | */ 42 | public function generate(string $path, string $name, WorldCreationOptions $options) : void{ 43 | ($this->generate)($path, $name, $options); 44 | } 45 | } 46 | --------------------------------------------------------------------------------