diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..c9c926e --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,27 @@ +version: 2 + +jobs: + build: + docker: + - image: circleci/openjdk:8-jdk-stretch + + steps: + - checkout + + - restore_cache: + keys: + - v1-dependencies-{{ checksum "pom.xml" }} + + - run: + command: | + mvn dependency:go-offline + mvn clean package + mkdir artifact + cp target/*.jar artifact + - save_cache: + paths: + - ~/.m2 + key: v1-dependencies-{{ checksum "pom.xml" }} + + - store_artifacts: + path: artifact diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..8147bf8 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +*.java text=auto eol=lf diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml new file mode 100644 index 0000000..643f9f3 --- /dev/null +++ b/.github/workflows/maven.yml @@ -0,0 +1,35 @@ +name: Java CI + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + + - name: Set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + + - name: Cache dependencies + uses: actions/cache@v1 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + - name: Build with Maven + run: mvn clean package + + - name: Copy artifacts + run: | + mkdir artifact + cp target/*.jar artifact + - name: Archive artifacts + uses: actions/upload-artifact@v1 + with: + name: artifacts + path: artifact diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5a92ac2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,225 @@ +# Created by https://www.gitignore.io/api/java,maven,eclipse,netbeans,intellij+all +# Edit at https://www.gitignore.io/?templates=java,maven,eclipse,netbeans,intellij+all + +### Eclipse ### +.metadata +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath +.recommenders + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# PyDev specific (Python IDE for Eclipse) +*.pydevproject + +# CDT-specific (C/C++ Development Tooling) +.cproject + +# CDT- autotools +.autotools + +# Java annotation processor (APT) +.factorypath + +# PDT-specific (PHP Development Tools) +.buildpath + +# sbteclipse plugin +.target + +# Tern plugin +.tern-project + +# TeXlipse plugin +.texlipse + +# STS (Spring Tool Suite) +.springBeans + +# Code Recommenders +.recommenders/ + +# Annotation Processing +.apt_generated/ + +# Scala IDE specific (Scala & Java development for Eclipse) +.cache-main +.scala_dependencies +.worksheet + +### Eclipse Patch ### +# Eclipse Core +.project + +# JDT-specific (Eclipse Java Development Tools) +.classpath + +# Annotation Processing +.apt_generated + +.sts4-cache/ + +### Intellij+all ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Intellij+all Patch ### +# Ignores the whole .idea folder and all .iml files +# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 + +.idea/ + +# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 + +*.iml +modules.xml +.idea/misc.xml +*.ipr + +# Sonarlint plugin +.idea/sonarlint + +### Java ### +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +### Maven ### +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +.mvn/wrapper/maven-wrapper.jar + +### NetBeans ### +**/nbproject/private/ +**/nbproject/Makefile-*.mk +**/nbproject/Package-*.bash +build/ +nbbuild/ +dist/ +nbdist/ +.nb-gradle/ + +# End of https://www.gitignore.io/api/java,maven,eclipse,netbeans,intellij+all + +# Windows +desktop.ini +*/desktop.ini +Thumbs.db +*/Thumbs.db +ehthumbs.db +*/ehthumbs.db + +# Mac +.DS_Store +*/.DS_Store +__MACOSX +__MACOSX/* +*/__MACOSX +*/__MACOSX/* + +# Java +*.MF diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..af9aba3 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,11 @@ +language: java + +jdk: + - openjdk8 + +install: + - mvn clean package + +cache: + directories: + - '$HOME/.m2/repository' diff --git a/README.md b/README.md new file mode 100644 index 0000000..2c16ee6 --- /dev/null +++ b/README.md @@ -0,0 +1,32 @@ +# Stronghold Populator +[![Nukkit](https://img.shields.io/badge/Nukkit-1.0-green)](https://github.com/NukkitX/Nukkit) +[![Build](https://img.shields.io/circleci/build/github/wode490390/StrongholdPopulator/master)](https://circleci.com/gh/wode490390/StrongholdPopulator/tree/master) +[![Release](https://img.shields.io/github/v/release/wode490390/StrongholdPopulator)](https://github.com/wode490390/StrongholdPopulator/releases) +[![Release date](https://img.shields.io/github/release-date/wode490390/StrongholdPopulator)](https://github.com/wode490390/StrongholdPopulator/releases) + + +This is a plugin that implements the [stronghold](https://minecraft.gamepedia.com/Stronghold) feature for Nukkit servers. + +![](https://i.loli.net/2020/06/03/JFinuNWUrk5v4HP.png) + +If you found any bugs or have any suggestions, please open an issue on [GitHub Issues](https://github.com/wode490390/StrongholdPopulator/issues). + +If you like this plugin, please star it on [GitHub](https://github.com/wode490390/StrongholdPopulator). + +## Download +- [Releases](https://github.com/wode490390/StrongholdPopulator/releases) +- [Snapshots](https://circleci.com/gh/wode490390/StrongholdPopulator) + +## Compiling +1. Install [Maven](https://maven.apache.org/). +2. Run `mvn clean package`. The compiled JAR can be found in the `target/` directory. + +## Metrics Collection + +This plugin uses [bStats](https://github.com/wode490390/bStats-Nukkit). You can opt out using the global bStats config; see the [official website](https://bstats.org/getting-started) for more details. + + + +###### If I have any grammar and/or term errors, please correct them :) diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..1b99e59 --- /dev/null +++ b/pom.xml @@ -0,0 +1,137 @@ + + + 4.0.0 + + cn.wode490390.nukkit + shpop + jar + 1.0.0 + Stronghold Populator + This is a plugin that implements the stronghold feature for Nukkit servers + http://wode490390.cn/ + 2018 + + + + GNU General Public License, Version 3.0 + http://www.gnu.org/licenses/gpl.html + repo + + + + + GitHub + https://github.com/wode490390/StrongholdPopulator/issues + + + + CircleCI + https://circleci.com/gh/wode490390/StrongholdPopulator + + + + scm:git:https://github.com/wode490390/StrongholdPopulator.git + scm:git:git@github.com:wode490390/StrongholdPopulator.git + https://github.com/wode490390/StrongholdPopulator + + + + + github + github-releases + https://maven.pkg.github.com/wode490390/StrongholdPopulator + + + + + 1.8 + 1.8 + UTF-8 + + + + + nukkitx-repo + https://repo.nukkitx.com/main/ + + + + + + cn.nukkit + nukkit + 1.0-SNAPSHOT + provided + + + com.google.code.findbugs + jsr305 + 3.0.2 + provided + + + + + clean package + wodeStrongholdPopulator-${project.version} + + + . + true + ${basedir}/src/main/resources + + *.yml + + + + + + pl.project13.maven + git-commit-id-plugin + 4.0.0 + + + get-the-git-infos + + revision + + + + + ${project.basedir}/.git + git + yyyy.MM.dd '@' HH:mm:ss z + ${user.timezone} + true + true + ${project.build.outputDirectory}/git.properties + properties + true + false + false + false + false + true + + git.user.* + + + false + 7 + flat + + false + false + 7 + -dirty + * + false + false + + + + + + diff --git a/src/main/java/cn/wode490390/nukkit/shpop/StrongholdPlugin.java b/src/main/java/cn/wode490390/nukkit/shpop/StrongholdPlugin.java new file mode 100644 index 0000000..bec4443 --- /dev/null +++ b/src/main/java/cn/wode490390/nukkit/shpop/StrongholdPlugin.java @@ -0,0 +1,90 @@ +package cn.wode490390.nukkit.shpop; + +import cn.nukkit.event.EventHandler; +import cn.nukkit.event.Listener; +import cn.nukkit.event.level.ChunkPopulateEvent; +import cn.nukkit.event.level.LevelLoadEvent; +import cn.nukkit.event.level.LevelUnloadEvent; +import cn.nukkit.level.Level; +import cn.nukkit.level.generator.Generator; +import cn.nukkit.level.generator.populator.type.Populator; +import cn.nukkit.plugin.PluginBase; +import cn.wode490390.nukkit.shpop.populator.PopulatorStronghold; +import cn.wode490390.nukkit.shpop.scheduler.ChunkPopulationTask; +import cn.wode490390.nukkit.shpop.util.MetricsLite; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +import java.util.List; +import java.util.Map; +import java.util.StringJoiner; + +public class StrongholdPlugin extends PluginBase implements Listener { + + public static final boolean DEBUG = false; + + private static StrongholdPlugin INSTANCE; + + private final Map> populators = Maps.newHashMap(); + + @Override + public void onLoad() { + INSTANCE = this; + } + + @Override + public void onEnable() { + try { + new MetricsLite(this, 7725); + } catch (Throwable ignore) { + + } + + PopulatorStronghold.init(); + + this.getServer().getPluginManager().registerEvents(this, this); + } + + @EventHandler + public void onLevelLoad(LevelLoadEvent event) { + List populators = Lists.newArrayList(); + Level level = event.getLevel(); + Generator generator = level.getGenerator(); + if (generator.getId() != Generator.TYPE_FLAT && generator.getDimension() == Level.DIMENSION_OVERWORLD) { + populators.add(new PopulatorStronghold()); + } + this.populators.put(level, populators); + } + + @EventHandler + public void onChunkPopulate(ChunkPopulateEvent event) { + Level level = event.getLevel(); + List populators = this.populators.get(level); + if (populators != null) { + this.getServer().getScheduler().scheduleAsyncTask(this, new ChunkPopulationTask(level, event.getChunk(), populators)); + } + } + + @EventHandler + public void onLevelUnload(LevelUnloadEvent event) { + this.populators.remove(event.getLevel()); + } + + public static StrongholdPlugin getInstance() { + return INSTANCE; + } + + public static void debug(Object... objs) { + if (DEBUG) { + try { + StringJoiner joiner = new StringJoiner(" "); + for (Object obj : objs) { + joiner.add(String.valueOf(obj)); + } + INSTANCE.getLogger().warning(joiner.toString()); + } catch (Throwable ignore) { + + } + } + } +} diff --git a/src/main/java/cn/wode490390/nukkit/shpop/block/state/BlockState.java b/src/main/java/cn/wode490390/nukkit/shpop/block/state/BlockState.java new file mode 100644 index 0000000..34e88cd --- /dev/null +++ b/src/main/java/cn/wode490390/nukkit/shpop/block/state/BlockState.java @@ -0,0 +1,86 @@ +package cn.wode490390.nukkit.shpop.block.state; + +import cn.nukkit.block.Block; +import cn.nukkit.level.GlobalBlockPalette; +import cn.wode490390.nukkit.shpop.math.Rotation; + +public class BlockState { + + public static final BlockState AIR = new BlockState(0); + + private final int id; + private final int meta; + + public BlockState(int id) { + this(id, 0); + } + + public BlockState(int id, int meta) { + this.id = id; + this.meta = meta; + } + + public int getId() { + return this.id; + } + + public int getMeta() { + return this.meta; + } + + public int getFullId() { + return (this.id << 4) | (this.meta & 0xf); + } + + public int getRuntimeId() { + return GlobalBlockPalette.getOrCreateRuntimeId(this.id, this.meta); + } + + public Block getBlock() { + return Block.get(this.id, this.meta); + } + + public BlockState rotate(Rotation rot) { + switch (rot) { + case CLOCKWISE_90: + return new BlockState(this.id, Rotation.clockwise90(this.id, this.meta)); + case CLOCKWISE_180: + return new BlockState(this.id, Rotation.clockwise180(this.id, this.meta)); + case COUNTERCLOCKWISE_90: + return new BlockState(this.id, Rotation.counterclockwise90(this.id, this.meta)); + case NONE: + default: + return this; + } + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof BlockState) { + BlockState o = (BlockState) obj; + return this.id == o.id && this.meta == o.meta; + } + return false; + } + + @Override + public int hashCode() { + return this.id << 6 | this.meta; + } + + @Override + public String toString() { + return String.format("BlockState(id=%s, meta=%s)", this.id, this.meta); + } + + public static BlockState fromFullId(int fullId) { + return new BlockState(fullId >> 4, fullId & 0xf); + } + + public static BlockState fromHash(int hash) { + return new BlockState(hash >> 6, hash & 0x3f); + } +} diff --git a/src/main/java/cn/wode490390/nukkit/shpop/block/state/Direction.java b/src/main/java/cn/wode490390/nukkit/shpop/block/state/Direction.java new file mode 100644 index 0000000..86fcc61 --- /dev/null +++ b/src/main/java/cn/wode490390/nukkit/shpop/block/state/Direction.java @@ -0,0 +1,14 @@ +package cn.wode490390.nukkit.shpop.block.state; + +//\\ VanillaStates::Direction +public final class Direction { + + public static final int SOUTH = 0b00; + public static final int WEST = 0b01; + public static final int NORTH = 0b10; + public static final int EAST = 0b11; + + private Direction() { + + } +} diff --git a/src/main/java/cn/wode490390/nukkit/shpop/block/state/EndPortalEyeBit.java b/src/main/java/cn/wode490390/nukkit/shpop/block/state/EndPortalEyeBit.java new file mode 100644 index 0000000..ca2c21a --- /dev/null +++ b/src/main/java/cn/wode490390/nukkit/shpop/block/state/EndPortalEyeBit.java @@ -0,0 +1,11 @@ +package cn.wode490390.nukkit.shpop.block.state; + +//\\ VanillaStates::EndPortalEyeBit +public final class EndPortalEyeBit { + + public static final int HAS_EYE = 0b100; + + private EndPortalEyeBit() { + + } +} diff --git a/src/main/java/cn/wode490390/nukkit/shpop/block/state/FacingDirection.java b/src/main/java/cn/wode490390/nukkit/shpop/block/state/FacingDirection.java new file mode 100644 index 0000000..8017eec --- /dev/null +++ b/src/main/java/cn/wode490390/nukkit/shpop/block/state/FacingDirection.java @@ -0,0 +1,16 @@ +package cn.wode490390.nukkit.shpop.block.state; + +//\\ VanillaStates::FacingDirection +public final class FacingDirection { + + public static final int DOWN = 0b000; + public static final int UP = 0b001; + public static final int NORTH = 0b010; + public static final int SOUTH = 0b011; + public static final int WEST = 0b100; + public static final int EAST = 0b101; + + private FacingDirection() { + + } +} diff --git a/src/main/java/cn/wode490390/nukkit/shpop/block/state/TorchFacingDirection.java b/src/main/java/cn/wode490390/nukkit/shpop/block/state/TorchFacingDirection.java new file mode 100644 index 0000000..cd62b0c --- /dev/null +++ b/src/main/java/cn/wode490390/nukkit/shpop/block/state/TorchFacingDirection.java @@ -0,0 +1,16 @@ +package cn.wode490390.nukkit.shpop.block.state; + +//\\ VanillaStates::TorchFacingDirection +public final class TorchFacingDirection { + + public static final int UNKNOWN = 0b000; + public static final int EAST = 0b001; //attached to a block to its WEST + public static final int WEST = 0b010; //attached to a block to its EAST + public static final int SOUTH = 0b011; //attached to a block to its NORTH + public static final int NORTH = 0b100; //attached to a block to its SOUTH + public static final int TOP = 0b101; + + private TorchFacingDirection() { + + } +} diff --git a/src/main/java/cn/wode490390/nukkit/shpop/block/state/UpperBlockBit.java b/src/main/java/cn/wode490390/nukkit/shpop/block/state/UpperBlockBit.java new file mode 100644 index 0000000..40fb19e --- /dev/null +++ b/src/main/java/cn/wode490390/nukkit/shpop/block/state/UpperBlockBit.java @@ -0,0 +1,12 @@ +package cn.wode490390.nukkit.shpop.block.state; + +//\\ VanillaStates::UpperBlockBit +public final class UpperBlockBit { + + public static final int LOWER = 0b0000; + public static final int UPPER = 0b1000; + + private UpperBlockBit() { + + } +} diff --git a/src/main/java/cn/wode490390/nukkit/shpop/block/state/WeirdoDirection.java b/src/main/java/cn/wode490390/nukkit/shpop/block/state/WeirdoDirection.java new file mode 100644 index 0000000..32c1b3a --- /dev/null +++ b/src/main/java/cn/wode490390/nukkit/shpop/block/state/WeirdoDirection.java @@ -0,0 +1,14 @@ +package cn.wode490390.nukkit.shpop.block.state; + +//\\ VanillaStates::WeirdoDirection +public final class WeirdoDirection { + + public static final int EAST = 0b00; + public static final int WEST = 0b01; + public static final int SOUTH = 0b10; + public static final int NORTH = 0b11; + + private WeirdoDirection() { + + } +} diff --git a/src/main/java/cn/wode490390/nukkit/shpop/loot/RandomizableContainer.java b/src/main/java/cn/wode490390/nukkit/shpop/loot/RandomizableContainer.java new file mode 100644 index 0000000..ade3a3b --- /dev/null +++ b/src/main/java/cn/wode490390/nukkit/shpop/loot/RandomizableContainer.java @@ -0,0 +1,148 @@ +package cn.wode490390.nukkit.shpop.loot; + +import cn.nukkit.item.Item; +import cn.nukkit.math.NukkitRandom; +import cn.nukkit.nbt.NBTIO; +import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.nbt.tag.ListTag; +import com.google.common.base.Preconditions; +import com.google.common.collect.Lists; + +import java.util.List; +import java.util.Map; + +public class RandomizableContainer { + + protected final Map, RollEntry> pools; + protected final int size; + + public RandomizableContainer(Map, RollEntry> pools, int size) { + Preconditions.checkNotNull(pools); + this.pools = pools; + this.size = size; + } + + public void create(ListTag list, NukkitRandom random) { + CompoundTag[] tags = new CompoundTag[this.size]; + + this.pools.forEach((pool, roll) -> { + for (int i = roll.getMin() == -1 ? roll.getMax() : random.nextRange(roll.getMin(), roll.getMax()); i > 0; --i) { + int result = random.nextBoundedInt(roll.getTotalWeight()); + for (ItemEntry entry : pool) { + result -= entry.getWeight(); + if (result < 0) { + int index = random.nextBoundedInt(tags.length); + tags[index] = NBTIO.putItemHelper(Item.get(entry.getId(), entry.getMeta(), random.nextRange(entry.getMinCount(), entry.getMaxCount())), index); + break; + } + } + } + }); + + for (int i = 0; i < tags.length; i++) { + if (tags[i] == null) { + list.add(i, NBTIO.putItemHelper(Item.get(Item.AIR), i)); + } else { + list.add(i, tags[i]); + } + } + } + + protected static class RollEntry { + + private final int max; + private final int min; + private final int totalWeight; + + public RollEntry(int max, int totalWeight) { + this(max, -1, totalWeight); + } + + public RollEntry(int max, int min, int totalWeight) { + this.max = max; + this.min = min; + this.totalWeight = totalWeight; + } + + public int getMax() { + return this.max; + } + + public int getMin() { + return this.min; + } + + public int getTotalWeight() { + return this.totalWeight; + } + } + + protected static class ItemEntry { + + private final int id; + private final int meta; + private final int maxCount; + private final int minCount; + private final int weight; + + public ItemEntry(int id, int weight) { + this(id, 0, weight); + } + + public ItemEntry(int id, int meta, int weight) { + this(id, meta, 1, weight); + } + + public ItemEntry(int id, int meta, int maxCount, int weight) { + this(id, meta, maxCount, 1, weight); + } + + public ItemEntry(int id, int meta, int maxCount, int minCount, int weight) { + this.id = id; + this.meta = meta; + this.maxCount = maxCount; + this.minCount = minCount; + this.weight = weight; + } + + public int getId() { + return this.id; + } + + public int getMeta() { + return this.meta; + } + + public int getMaxCount() { + return this.maxCount; + } + + public int getMinCount() { + return this.minCount; + } + + public int getWeight() { + return this.weight; + } + } + + protected static class PoolBuilder { + + private final List pool = Lists.newArrayList(); + private int totalWeight = 0; + + public PoolBuilder register(ItemEntry entry) { + this.pool.add(entry); + this.totalWeight += entry.getWeight(); + return this; + } + + public List build() { + return this.pool; + } + + public int getTotalWeight() { + return this.totalWeight; + } + } +} diff --git a/src/main/java/cn/wode490390/nukkit/shpop/loot/StrongholdCorridorChest.java b/src/main/java/cn/wode490390/nukkit/shpop/loot/StrongholdCorridorChest.java new file mode 100644 index 0000000..b5300cb --- /dev/null +++ b/src/main/java/cn/wode490390/nukkit/shpop/loot/StrongholdCorridorChest.java @@ -0,0 +1,42 @@ +package cn.wode490390.nukkit.shpop.loot; + +import cn.nukkit.inventory.InventoryType; +import cn.nukkit.item.Item; +import com.google.common.collect.Maps; + +//\\ ./data/behavior_packs/vanilla/loot_tables/chests/stronghold_corridor.json +public class StrongholdCorridorChest extends RandomizableContainer { + + private static final StrongholdCorridorChest INSTANCE = new StrongholdCorridorChest(); + + public static StrongholdCorridorChest get() { + return INSTANCE; + } + + private StrongholdCorridorChest() { + super(Maps.newHashMap(), InventoryType.CHEST.getDefaultSize()); + + PoolBuilder pool1 = new PoolBuilder() + .register(new ItemEntry(Item.ENDER_PEARL, 50)) + .register(new ItemEntry(Item.EMERALD, 0, 3, 15)) + .register(new ItemEntry(Item.DIAMOND, 0, 3, 15)) + .register(new ItemEntry(Item.IRON_INGOT, 0, 5, 50)) + .register(new ItemEntry(Item.GOLD_INGOT, 0, 3, 25)) + .register(new ItemEntry(Item.REDSTONE, 0, 9, 4, 25)) + .register(new ItemEntry(Item.BREAD, 0, 3, 75)) + .register(new ItemEntry(Item.APPLE, 0, 3, 75)) + .register(new ItemEntry(Item.IRON_PICKAXE, 25)) + .register(new ItemEntry(Item.IRON_SWORD, 25)) + .register(new ItemEntry(Item.IRON_CHESTPLATE, 25)) + .register(new ItemEntry(Item.IRON_HELMET, 25)) + .register(new ItemEntry(Item.IRON_LEGGINGS, 25)) + .register(new ItemEntry(Item.IRON_BOOTS, 25)) + .register(new ItemEntry(Item.GOLDEN_APPLE, 5)) + .register(new ItemEntry(Item.SADDLE, 5)) + .register(new ItemEntry(Item.IRON_HORSE_ARMOR, 5)) + .register(new ItemEntry(Item.GOLD_HORSE_ARMOR, 5)) + .register(new ItemEntry(Item.DIAMOND_HORSE_ARMOR, 5)) + .register(new ItemEntry(Item.ENCHANTED_BOOK, 6)); //TODO: treasure enchant_with_levels 30 + this.pools.put(pool1.build(), new RollEntry(3, 2, pool1.getTotalWeight())); + } +} diff --git a/src/main/java/cn/wode490390/nukkit/shpop/loot/StrongholdCrossingChest.java b/src/main/java/cn/wode490390/nukkit/shpop/loot/StrongholdCrossingChest.java new file mode 100644 index 0000000..e580eb9 --- /dev/null +++ b/src/main/java/cn/wode490390/nukkit/shpop/loot/StrongholdCrossingChest.java @@ -0,0 +1,31 @@ +package cn.wode490390.nukkit.shpop.loot; + +import cn.nukkit.inventory.InventoryType; +import cn.nukkit.item.Item; +import com.google.common.collect.Maps; + +//\\ ./data/behavior_packs/vanilla/loot_tables/chests/stronghold_crossing.json +public class StrongholdCrossingChest extends RandomizableContainer { + + private static final StrongholdCrossingChest INSTANCE = new StrongholdCrossingChest(); + + public static StrongholdCrossingChest get() { + return INSTANCE; + } + + private StrongholdCrossingChest() { + super(Maps.newHashMap(), InventoryType.CHEST.getDefaultSize()); + + PoolBuilder pool1 = new PoolBuilder() + .register(new ItemEntry(Item.IRON_INGOT, 0, 5, 50)) + .register(new ItemEntry(Item.GOLD_INGOT, 0, 3, 25)) + .register(new ItemEntry(Item.REDSTONE, 0, 9, 4, 25)) + .register(new ItemEntry(Item.COAL, 0, 8, 3, 50)) + .register(new ItemEntry(Item.BREAD, 0, 3, 75)) + .register(new ItemEntry(Item.APPLE, 0, 3, 75)) + .register(new ItemEntry(Item.IRON_PICKAXE, 5)) + .register(new ItemEntry(Item.ENCHANTED_BOOK, 6)) //TODO: treasure enchant_with_levels 30 + .register(new ItemEntry(Item.DYE, 0, 3, 75)); + this.pools.put(pool1.build(), new RollEntry(4, 1, pool1.getTotalWeight())); + } +} diff --git a/src/main/java/cn/wode490390/nukkit/shpop/loot/StrongholdLibraryChest.java b/src/main/java/cn/wode490390/nukkit/shpop/loot/StrongholdLibraryChest.java new file mode 100644 index 0000000..6e87960 --- /dev/null +++ b/src/main/java/cn/wode490390/nukkit/shpop/loot/StrongholdLibraryChest.java @@ -0,0 +1,27 @@ +package cn.wode490390.nukkit.shpop.loot; + +import cn.nukkit.inventory.InventoryType; +import cn.nukkit.item.Item; +import com.google.common.collect.Maps; + +//\\ ./data/behavior_packs/vanilla/loot_tables/chests/stronghold_library.json +public class StrongholdLibraryChest extends RandomizableContainer { + + private static final StrongholdLibraryChest INSTANCE = new StrongholdLibraryChest(); + + public static StrongholdLibraryChest get() { + return INSTANCE; + } + + private StrongholdLibraryChest() { + super(Maps.newHashMap(), InventoryType.CHEST.getDefaultSize()); + + PoolBuilder pool1 = new PoolBuilder() + .register(new ItemEntry(Item.BOOK, 0, 3, 100)) + .register(new ItemEntry(Item.PAPER, 0, 7, 2, 100)) + .register(new ItemEntry(Item.MAP, 5)) //TODO: EMPTY_MAP + .register(new ItemEntry(Item.COMPASS, 5)) + .register(new ItemEntry(Item.ENCHANTED_BOOK, 60)); //TODO: treasure enchant_with_levels 30 + this.pools.put(pool1.build(), new RollEntry(10, 2, pool1.getTotalWeight())); + } +} diff --git a/src/main/java/cn/wode490390/nukkit/shpop/math/BoundingBox.java b/src/main/java/cn/wode490390/nukkit/shpop/math/BoundingBox.java new file mode 100644 index 0000000..7f20a81 --- /dev/null +++ b/src/main/java/cn/wode490390/nukkit/shpop/math/BoundingBox.java @@ -0,0 +1,155 @@ +package cn.wode490390.nukkit.shpop.math; + +import cn.nukkit.math.BlockFace; +import cn.nukkit.math.BlockVector3; +import cn.nukkit.nbt.tag.IntArrayTag; +import com.google.common.base.MoreObjects; + +public class BoundingBox { + + public int x0; + public int y0; + public int z0; + public int x1; + public int y1; + public int z1; + + public BoundingBox() { + + } + + public BoundingBox(int[] array) { + if (array.length == 6) { + this.x0 = array[0]; + this.y0 = array[1]; + this.z0 = array[2]; + this.x1 = array[3]; + this.y1 = array[4]; + this.z1 = array[5]; + } + } + + public BoundingBox(BoundingBox boundingBox) { + this.x0 = boundingBox.x0; + this.y0 = boundingBox.y0; + this.z0 = boundingBox.z0; + this.x1 = boundingBox.x1; + this.y1 = boundingBox.y1; + this.z1 = boundingBox.z1; + } + + public BoundingBox(int x0, int y0, int z0, int x1, int y1, int z1) { + this.x0 = x0; + this.y0 = y0; + this.z0 = z0; + this.x1 = x1; + this.y1 = y1; + this.z1 = z1; + } + + public BoundingBox(BlockVector3 vec0, BlockVector3 vec1) { + this.x0 = Math.min(vec0.getX(), vec1.getX()); + this.y0 = Math.min(vec0.getY(), vec1.getY()); + this.z0 = Math.min(vec0.getZ(), vec1.getZ()); + this.x1 = Math.max(vec0.getX(), vec1.getX()); + this.y1 = Math.max(vec0.getY(), vec1.getY()); + this.z1 = Math.max(vec0.getZ(), vec1.getZ()); + } + + public BoundingBox(int x0, int z0, int x1, int z1) { + this.x0 = x0; + this.z0 = z0; + this.x1 = x1; + this.z1 = z1; + this.y0 = 1; + this.y1 = 512; + } + + public static BoundingBox getUnknownBox() { + return new BoundingBox(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE); + } + + public static BoundingBox orientBox(int x, int y, int z, int xOffset, int yOffset, int zOffset, int xLength, int yLength, int zLength, BlockFace orientation) { + switch (orientation) { + case NORTH: + return new BoundingBox(x + xOffset, y + yOffset, z - zLength + 1 + zOffset, x + xLength - 1 + xOffset, y + yLength - 1 + yOffset, z + zOffset); + case WEST: + return new BoundingBox(x - zLength + 1 + zOffset, y + yOffset, z + xOffset, x + zOffset, y + yLength - 1 + yOffset, z + xLength - 1 + xOffset); + case EAST: + return new BoundingBox(x + zOffset, y + yOffset, z + xOffset, x + zLength - 1 + zOffset, y + yLength - 1 + yOffset, z + xLength - 1 + xOffset); + case SOUTH: + default: + return new BoundingBox(x + xOffset, y + yOffset, z + zOffset, x + xLength - 1 + xOffset, y + yLength - 1 + yOffset, z + zLength - 1 + zOffset); + } + } + + public static BoundingBox createProper(int x0, int y0, int z0, int x1, int y1, int z1) { + return new BoundingBox(Math.min(x0, x1), Math.min(y0, y1), Math.min(z0, z1), Math.max(x0, x1), Math.max(y0, y1), Math.max(z0, z1)); + } + + public boolean intersects(BoundingBox boundingBox) { + return this.x1 >= boundingBox.x0 && this.x0 <= boundingBox.x1 && this.z1 >= boundingBox.z0 && this.z0 <= boundingBox.z1 && this.y1 >= boundingBox.y0 && this.y0 <= boundingBox.y1; + } + + public boolean intersects(int x0, int z0, int x1, int z1) { + return this.x1 >= x0 && this.x0 <= x1 && this.z1 >= z0 && this.z0 <= z1; + } + + public void expand(BoundingBox boundingBox) { + this.x0 = Math.min(this.x0, boundingBox.x0); + this.y0 = Math.min(this.y0, boundingBox.y0); + this.z0 = Math.min(this.z0, boundingBox.z0); + this.x1 = Math.max(this.x1, boundingBox.x1); + this.y1 = Math.max(this.y1, boundingBox.y1); + this.z1 = Math.max(this.z1, boundingBox.z1); + } + + public void move(int x, int y, int z) { + this.x0 += x; + this.y0 += y; + this.z0 += z; + this.x1 += x; + this.y1 += y; + this.z1 += z; + } + + public BoundingBox moved(int x, int y, int z) { + return new BoundingBox(this.x0 + x, this.y0 + y, this.z0 + z, this.x1 + x, this.y1 + y, this.z1 + z); + } + + public boolean isInside(BlockVector3 vec) { + return vec.getX() >= this.x0 && vec.getX() <= this.x1 && vec.getZ() >= this.z0 && vec.getZ() <= this.z1 && vec.getY() >= this.y0 && vec.getY() <= this.y1; + } + + public BlockVector3 getLength() { + return new BlockVector3(this.x1 - this.x0, this.y1 - this.y0, this.z1 - this.z0); + } + + public int getXSpan() { + return this.x1 - this.x0 + 1; + } + + public int getYSpan() { + return this.y1 - this.y0 + 1; + } + + public int getZSpan() { + return this.z1 - this.z0 + 1; + } + + public IntArrayTag createTag() { + return new IntArrayTag("", new int[]{this.x0, this.y0, this.z0, this.x1, this.y1, this.z1}); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("x0", this.x0) + .add("y0", this.y0) + .add("z0", this.z0) + .add("x1", this.x1) + .add("y1", this.y1) + .add("z1", this.z1) + .toString(); + } +} diff --git a/src/main/java/cn/wode490390/nukkit/shpop/math/Rotation.java b/src/main/java/cn/wode490390/nukkit/shpop/math/Rotation.java new file mode 100644 index 0000000..44c7a7a --- /dev/null +++ b/src/main/java/cn/wode490390/nukkit/shpop/math/Rotation.java @@ -0,0 +1,1102 @@ +package cn.wode490390.nukkit.shpop.math; + +import cn.nukkit.block.BlockID; + +public enum Rotation { + NONE, + CLOCKWISE_90, + CLOCKWISE_180, + COUNTERCLOCKWISE_90; + + /** + * Rotate a block's data value 90 degrees (north->east->south->west->north); + * + * @param id the ID of the bock + * @param meta the meta of the block + * @return the new meta + */ + public static int clockwise90(int id, int meta) { + switch (id) { + case BlockID.TORCH: + case BlockID.UNLIT_REDSTONE_TORCH: + case BlockID.REDSTONE_TORCH: + switch (meta) { + case 1: + return 3; + case 2: + return 4; + case 3: + return 2; + case 4: + return 1; + // 5 is vertical + } + break; + + case BlockID.RAIL: + switch (meta) { + case 6: + return 7; + case 7: + return 8; + case 8: + return 9; + case 9: + return 6; + } + /* FALL-THROUGH */ + + case BlockID.POWERED_RAIL: + case BlockID.DETECTOR_RAIL: + case BlockID.ACTIVATOR_RAIL: + switch (meta & 0x7) { + case 0: + return 1 | (meta & ~0x7); + case 1: + return 0 | (meta & ~0x7); + case 2: + return 5 | (meta & ~0x7); + case 3: + return 4 | (meta & ~0x7); + case 4: + return 2 | (meta & ~0x7); + case 5: + return 3 | (meta & ~0x7); + } + break; + + case BlockID.RED_SANDSTONE_STAIRS: + case BlockID.OAK_WOOD_STAIRS: + case BlockID.COBBLESTONE_STAIRS: + case BlockID.BRICK_STAIRS: + case BlockID.STONE_BRICK_STAIRS: + case BlockID.NETHER_BRICKS_STAIRS: + case BlockID.SANDSTONE_STAIRS: + case BlockID.SPRUCE_WOOD_STAIRS: + case BlockID.BIRCH_WOOD_STAIRS: + case BlockID.JUNGLE_WOOD_STAIRS: + case BlockID.QUARTZ_STAIRS: + case BlockID.ACACIA_WOODEN_STAIRS: + case BlockID.DARK_OAK_WOODEN_STAIRS: + case BlockID.PURPUR_STAIRS: + switch (meta) { + case 0: + return 2; + case 1: + return 3; + case 2: + return 1; + case 3: + return 0; + case 4: + return 6; + case 5: + return 7; + case 6: + return 5; + case 7: + return 4; + } + break; + + case BlockID.STONE_BUTTON: + case BlockID.WOODEN_BUTTON: { + int thrown = meta & 0x8; + switch (meta & ~0x8) { + case 2: + return 5 | thrown; + case 3: + return 4 | thrown; + case 4: + return 2 | thrown; + case 5: + return 3 | thrown; + // 0 and 1 are vertical + } + break; + } + + case BlockID.LEVER: { + int thrown = meta & 0x8; + switch (meta & ~0x8) { + case 1: + return 3 | thrown; + case 2: + return 4 | thrown; + case 3: + return 2 | thrown; + case 4: + return 1 | thrown; + case 5: + return 6 | thrown; + case 6: + return 5 | thrown; + case 7: + return 0 | thrown; + case 0: + return 7 | thrown; + } + break; + } + + case BlockID.WOODEN_DOOR_BLOCK: + case BlockID.IRON_DOOR_BLOCK: + case BlockID.SPRUCE_DOOR_BLOCK: + case BlockID.BIRCH_DOOR_BLOCK: + case BlockID.JUNGLE_DOOR_BLOCK: + case BlockID.ACACIA_DOOR_BLOCK: + case BlockID.DARK_OAK_DOOR_BLOCK: + if ((meta & 0x8) != 0) { + // door top halves contain no orientation information + break; + } + + /* FALL-THROUGH */ + + case BlockID.END_PORTAL_FRAME: + case BlockID.COCOA_BLOCK: + case BlockID.TRIPWIRE_HOOK: { + int extra = meta & ~0x3; + int withoutFlags = meta & 0x3; + switch (withoutFlags) { + case 0: + return 1 | extra; + case 1: + return 2 | extra; + case 2: + return 3 | extra; + case 3: + return 0 | extra; + } + break; + } + case BlockID.SIGN_POST: + case BlockID.STANDING_BANNER: + return (meta + 4) % 16; + + case BlockID.LADDER: + case BlockID.WALL_SIGN: + case BlockID.WALL_BANNER: + case BlockID.CHEST: + case BlockID.FURNACE: + case BlockID.BURNING_FURNACE: + case BlockID.ENDER_CHEST: + case BlockID.TRAPPED_CHEST: + case BlockID.HOPPER_BLOCK: { + int extra = meta & 0x8; + int withoutFlags = meta & ~0x8; + switch (withoutFlags) { + case 2: + return 5 | extra; + case 3: + return 4 | extra; + case 4: + return 2 | extra; + case 5: + return 3 | extra; + } + break; + } + case BlockID.DISPENSER: + case BlockID.DROPPER: + case BlockID.END_ROD: + int dispPower = meta & 0x8; + switch (meta & ~0x8) { + case 2: + return 5 | dispPower; + case 3: + return 4 | dispPower; + case 4: + return 2 | dispPower; + case 5: + return 3 | dispPower; + } + break; + + case BlockID.PUMPKIN: + case BlockID.JACK_O_LANTERN: + switch (meta) { + case 0: + return 1; + case 1: + return 2; + case 2: + return 3; + case 3: + return 0; + } + break; + + case BlockID.HAY_BALE: + case BlockID.LOG: + case BlockID.LOG2: + case BlockID.QUARTZ_BLOCK: + case BlockID.PURPUR_BLOCK: + case BlockID.BONE_BLOCK: + if (meta >= 4 && meta <= 11) meta ^= 0xc; + break; + + case BlockID.UNPOWERED_COMPARATOR: + case BlockID.POWERED_COMPARATOR: + case BlockID.UNPOWERED_REPEATER: + case BlockID.POWERED_REPEATER: + int dir = meta & 0x03; + int delay = meta - dir; + switch (dir) { + case 0: + return 1 | delay; + case 1: + return 2 | delay; + case 2: + return 3 | delay; + case 3: + return 0 | delay; + } + break; + + case BlockID.TRAPDOOR: + case BlockID.IRON_TRAPDOOR: + int withoutOrientation = meta & ~0x3; + int orientation = meta & 0x3; + switch (orientation) { + case 0: + return 3 | withoutOrientation; + case 1: + return 2 | withoutOrientation; + case 2: + return 0 | withoutOrientation; + case 3: + return 1 | withoutOrientation; + } + break; + + case BlockID.PISTON: + case BlockID.STICKY_PISTON: + case BlockID.PISTON_HEAD: + case 137: //BlockID.COMMAND_BLOCK + case 188: //BlockID.REPEATING_COMMAND_BLOCK + case 189: //BlockID.CHAIN_COMMAND_BLOCK + final int rest = meta & ~0x7; + switch (meta & 0x7) { + case 2: + return 5 | rest; + case 3: + return 4 | rest; + case 4: + return 2 | rest; + case 5: + return 3 | rest; + } + break; + + case BlockID.BROWN_MUSHROOM_BLOCK: + case BlockID.RED_MUSHROOM_BLOCK: + if (meta >= 10) return meta; + return (meta * 3) % 10; + + case BlockID.VINE: + return ((meta << 1) | (meta >> 3)) & 0xf; + + case BlockID.FENCE_GATE: + case BlockID.FENCE_GATE_SPRUCE: + case BlockID.FENCE_GATE_BIRCH: + case BlockID.FENCE_GATE_JUNGLE: + case BlockID.FENCE_GATE_DARK_OAK: + case BlockID.FENCE_GATE_ACACIA: + return ((meta + 1) & 0x3) | (meta & ~0x3); + + case BlockID.ANVIL: + int damage = meta & ~0x3; + switch (meta & 0x3) { + case 0: + return 3 | damage; + case 2: + return 1 | damage; + case 1: + return 0 | damage; + case 3: + return 2 | damage; + } + break; + + case BlockID.BED_BLOCK: + return meta & ~0x3 | (meta + 1) & 0x3; + + case BlockID.SKULL_BLOCK: + case BlockID.PURPLE_GLAZED_TERRACOTTA: + case BlockID.WHITE_GLAZED_TERRACOTTA: + case BlockID.ORANGE_GLAZED_TERRACOTTA: + case BlockID.MAGENTA_GLAZED_TERRACOTTA: + case BlockID.LIGHT_BLUE_GLAZED_TERRACOTTA: + case BlockID.YELLOW_GLAZED_TERRACOTTA: + case BlockID.LIME_GLAZED_TERRACOTTA: + case BlockID.PINK_GLAZED_TERRACOTTA: + case BlockID.GRAY_GLAZED_TERRACOTTA: + case BlockID.SILVER_GLAZED_TERRACOTTA: + case BlockID.CYAN_GLAZED_TERRACOTTA: + case BlockID.BLUE_GLAZED_TERRACOTTA: + case BlockID.BROWN_GLAZED_TERRACOTTA: + case BlockID.GREEN_GLAZED_TERRACOTTA: + case BlockID.RED_GLAZED_TERRACOTTA: + case BlockID.BLACK_GLAZED_TERRACOTTA: + case BlockID.OBSERVER: + switch (meta) { + case 2: + return 5; + case 3: + return 4; + case 4: + return 2; + case 5: + return 3; + } + break; + + case BlockID.NETHER_PORTAL: + return (meta + 1) & 0x1; + + case BlockID.ITEM_FRAME_BLOCK: + switch (meta) { + case 0: + return 2; + case 1: + return 3; + case 2: + return 1; + case 3: + return 0; + } + break; + + } + + return meta; + } + + /** + * Rotate a block's data value -90 degrees (north<-east<-south<-west<-north); + * + * @param id the ID of the bock + * @param meta the meta of the block + * @return the new meta + */ + public static int counterclockwise90(int id, int meta) { + switch (id) { + case BlockID.TORCH: + case BlockID.UNLIT_REDSTONE_TORCH: + case BlockID.REDSTONE_TORCH: + switch (meta) { + case 3: + return 1; + case 4: + return 2; + case 2: + return 3; + case 1: + return 4; + // 5 is vertical + } + break; + + case BlockID.RAIL: + switch (meta) { + case 7: + return 6; + case 8: + return 7; + case 9: + return 8; + case 6: + return 9; + } + /* FALL-THROUGH */ + + case BlockID.POWERED_RAIL: + case BlockID.DETECTOR_RAIL: + case BlockID.ACTIVATOR_RAIL: + int power = meta & ~0x7; + switch (meta & 0x7) { + case 1: + return 0 | power; + case 0: + return 1 | power; + case 5: + return 2 | power; + case 4: + return 3 | power; + case 2: + return 4 | power; + case 3: + return 5 | power; + } + break; + + case BlockID.RED_SANDSTONE_STAIRS: + case BlockID.OAK_WOOD_STAIRS: + case BlockID.COBBLESTONE_STAIRS: + case BlockID.BRICK_STAIRS: + case BlockID.STONE_BRICK_STAIRS: + case BlockID.NETHER_BRICKS_STAIRS: + case BlockID.SANDSTONE_STAIRS: + case BlockID.SPRUCE_WOOD_STAIRS: + case BlockID.BIRCH_WOOD_STAIRS: + case BlockID.JUNGLE_WOOD_STAIRS: + case BlockID.QUARTZ_STAIRS: + case BlockID.ACACIA_WOODEN_STAIRS: + case BlockID.DARK_OAK_WOODEN_STAIRS: + case BlockID.PURPUR_STAIRS: + switch (meta) { + case 2: + return 0; + case 3: + return 1; + case 1: + return 2; + case 0: + return 3; + case 6: + return 4; + case 7: + return 5; + case 5: + return 6; + case 4: + return 7; + } + break; + + case BlockID.STONE_BUTTON: + case BlockID.WOODEN_BUTTON: { + int thrown = meta & 0x8; + switch (meta & ~0x8) { + case 4: + return 3 | thrown; + case 5: + return 2 | thrown; + case 3: + return 5 | thrown; + case 2: + return 4 | thrown; + // 0 and 1 are vertical + } + break; + } + + case BlockID.LEVER: { + int thrown = meta & 0x8; + switch (meta & ~0x8) { + case 3: + return 1 | thrown; + case 4: + return 2 | thrown; + case 2: + return 3 | thrown; + case 1: + return 4 | thrown; + case 6: + return 5 | thrown; + case 5: + return 6 | thrown; + case 0: + return 7 | thrown; + case 7: + return 0 | thrown; + } + break; + } + + case BlockID.WOODEN_DOOR_BLOCK: + case BlockID.IRON_DOOR_BLOCK: + case BlockID.SPRUCE_DOOR_BLOCK: + case BlockID.BIRCH_DOOR_BLOCK: + case BlockID.JUNGLE_DOOR_BLOCK: + case BlockID.ACACIA_DOOR_BLOCK: + case BlockID.DARK_OAK_DOOR_BLOCK: + if ((meta & 0x8) != 0) { + // door top halves contain no orientation information + break; + } + + /* FALL-THROUGH */ + + case BlockID.END_PORTAL_FRAME: + case BlockID.COCOA_BLOCK: + case BlockID.TRIPWIRE_HOOK: { + int extra = meta & ~0x3; + int withoutFlags = meta & 0x3; + switch (withoutFlags) { + case 1: + return 0 | extra; + case 2: + return 1 | extra; + case 3: + return 2 | extra; + case 0: + return 3 | extra; + } + break; + } + case BlockID.SIGN_POST: + case BlockID.STANDING_BANNER: + return (meta + 12) % 16; + + case BlockID.LADDER: + case BlockID.WALL_SIGN: + case BlockID.WALL_BANNER: + case BlockID.CHEST: + case BlockID.FURNACE: + case BlockID.BURNING_FURNACE: + case BlockID.ENDER_CHEST: + case BlockID.TRAPPED_CHEST: + case BlockID.HOPPER_BLOCK: { + int extra = meta & 0x8; + int withoutFlags = meta & ~0x8; + switch (withoutFlags) { + case 5: + return 2 | extra; + case 4: + return 3 | extra; + case 2: + return 4 | extra; + case 3: + return 5 | extra; + } + break; + } + case BlockID.DISPENSER: + case BlockID.DROPPER: + case BlockID.END_ROD: + int dispPower = meta & 0x8; + switch (meta & ~0x8) { + case 5: + return 2 | dispPower; + case 4: + return 3 | dispPower; + case 2: + return 4 | dispPower; + case 3: + return 5 | dispPower; + } + break; + case BlockID.PUMPKIN: + case BlockID.JACK_O_LANTERN: + switch (meta) { + case 1: + return 0; + case 2: + return 1; + case 3: + return 2; + case 0: + return 3; + } + break; + case BlockID.HAY_BALE: + case BlockID.LOG: + case BlockID.LOG2: + case BlockID.QUARTZ_BLOCK: + case BlockID.PURPUR_BLOCK: + case BlockID.BONE_BLOCK: + if (meta >= 4 && meta <= 11) meta ^= 0xc; + break; + + case BlockID.UNPOWERED_COMPARATOR: + case BlockID.POWERED_COMPARATOR: + case BlockID.UNPOWERED_REPEATER: + case BlockID.POWERED_REPEATER: + int dir = meta & 0x03; + int delay = meta - dir; + switch (dir) { + case 1: + return 0 | delay; + case 2: + return 1 | delay; + case 3: + return 2 | delay; + case 0: + return 3 | delay; + } + break; + + case BlockID.TRAPDOOR: + case BlockID.IRON_TRAPDOOR: + int withoutOrientation = meta & ~0x3; + int orientation = meta & 0x3; + switch (orientation) { + case 3: + return 0 | withoutOrientation; + case 2: + return 1 | withoutOrientation; + case 0: + return 2 | withoutOrientation; + case 1: + return 3 | withoutOrientation; + } + + case BlockID.PISTON: + case BlockID.STICKY_PISTON: + case BlockID.PISTON_HEAD: + case 137: //BlockID.COMMAND_BLOCK + case 188: //BlockID.REPEATING_COMMAND_BLOCK + case 189: //BlockID.CHAIN_COMMAND_BLOCK + final int rest = meta & ~0x7; + switch (meta & 0x7) { + case 5: + return 2 | rest; + case 4: + return 3 | rest; + case 2: + return 4 | rest; + case 3: + return 5 | rest; + } + break; + + case BlockID.BROWN_MUSHROOM_BLOCK: + case BlockID.RED_MUSHROOM_BLOCK: + if (meta >= 10) return meta; + return (meta * 7) % 10; + + case BlockID.VINE: + return ((meta >> 1) | (meta << 3)) & 0xf; + + case BlockID.FENCE_GATE: + case BlockID.FENCE_GATE_SPRUCE: + case BlockID.FENCE_GATE_BIRCH: + case BlockID.FENCE_GATE_JUNGLE: + case BlockID.FENCE_GATE_DARK_OAK: + case BlockID.FENCE_GATE_ACACIA: + return ((meta + 3) & 0x3) | (meta & ~0x3); + + case BlockID.ANVIL: + int damage = meta & ~0x3; + switch (meta & 0x3) { + case 0: + return 1 | damage; + case 2: + return 3 | damage; + case 1: + return 2 | damage; + case 3: + return 0 | damage; + } + break; + + case BlockID.BED_BLOCK: + return meta & ~0x3 | (meta - 1) & 0x3; + + case BlockID.SKULL_BLOCK: + case BlockID.PURPLE_GLAZED_TERRACOTTA: + case BlockID.WHITE_GLAZED_TERRACOTTA: + case BlockID.ORANGE_GLAZED_TERRACOTTA: + case BlockID.MAGENTA_GLAZED_TERRACOTTA: + case BlockID.LIGHT_BLUE_GLAZED_TERRACOTTA: + case BlockID.YELLOW_GLAZED_TERRACOTTA: + case BlockID.LIME_GLAZED_TERRACOTTA: + case BlockID.PINK_GLAZED_TERRACOTTA: + case BlockID.GRAY_GLAZED_TERRACOTTA: + case BlockID.SILVER_GLAZED_TERRACOTTA: + case BlockID.CYAN_GLAZED_TERRACOTTA: + case BlockID.BLUE_GLAZED_TERRACOTTA: + case BlockID.BROWN_GLAZED_TERRACOTTA: + case BlockID.GREEN_GLAZED_TERRACOTTA: + case BlockID.RED_GLAZED_TERRACOTTA: + case BlockID.BLACK_GLAZED_TERRACOTTA: + case BlockID.OBSERVER: + switch (meta) { + case 2: + return 4; + case 3: + return 5; + case 4: + return 3; + case 5: + return 2; + } + break; + + case BlockID.NETHER_PORTAL: + return (meta + 1) & 0x1; + + case BlockID.ITEM_FRAME_BLOCK: + switch (meta) { + case 0: + return 3; + case 1: + return 2; + case 2: + return 0; + case 3: + return 1; + } + break; + + } + + return meta; + } + + /** + * Flip a block's data value. + * + * @param id the ID of the bock + * @param meta the meta of the block + * @return the new meta + */ + public static int clockwise180(int id, int meta) { + switch (id) { + case BlockID.TORCH: + case BlockID.UNLIT_REDSTONE_TORCH: + case BlockID.REDSTONE_TORCH: + switch (meta) { + case 1: + return 2; + case 2: + return 1; + case 3: + return 4; + case 4: + return 3; + // 5 is vertical + } + break; + + case BlockID.RAIL: + switch (meta) { + case 6: + return 8; + case 7: + return 9; + case 8: + return 6; + case 9: + return 7; + } + /* FALL-THROUGH */ + + case BlockID.POWERED_RAIL: + case BlockID.DETECTOR_RAIL: + case BlockID.ACTIVATOR_RAIL: + switch (meta & 0x7) { + case 0: + return 0 | (meta & ~0x7); + case 1: + return 1 | (meta & ~0x7); + case 2: + return 3 | (meta & ~0x7); + case 3: + return 2 | (meta & ~0x7); + case 4: + return 5 | (meta & ~0x7); + case 5: + return 4 | (meta & ~0x7); + } + break; + + case BlockID.RED_SANDSTONE_STAIRS: + case BlockID.OAK_WOOD_STAIRS: + case BlockID.COBBLESTONE_STAIRS: + case BlockID.BRICK_STAIRS: + case BlockID.STONE_BRICK_STAIRS: + case BlockID.NETHER_BRICKS_STAIRS: + case BlockID.SANDSTONE_STAIRS: + case BlockID.SPRUCE_WOOD_STAIRS: + case BlockID.BIRCH_WOOD_STAIRS: + case BlockID.JUNGLE_WOOD_STAIRS: + case BlockID.QUARTZ_STAIRS: + case BlockID.ACACIA_WOODEN_STAIRS: + case BlockID.DARK_OAK_WOODEN_STAIRS: + case BlockID.PURPUR_STAIRS: + switch (meta) { + case 0: + return 1; + case 1: + return 0; + case 2: + return 3; + case 3: + return 2; + case 4: + return 5; + case 5: + return 4; + case 6: + return 7; + case 7: + return 6; + } + break; + + case BlockID.STONE_BUTTON: + case BlockID.WOODEN_BUTTON: { + int thrown = meta & 0x8; + switch (meta & ~0x8) { + case 2: + return 3 | thrown; + case 3: + return 2 | thrown; + case 4: + return 5 | thrown; + case 5: + return 4 | thrown; + // 0 and 1 are vertical + } + break; + } + + case BlockID.LEVER: { + int thrown = meta & 0x8; + switch (meta & ~0x8) { + case 1: + return 2 | thrown; + case 2: + return 1 | thrown; + case 3: + return 4 | thrown; + case 4: + return 3 | thrown; + case 5: + return 5 | thrown; + case 6: + return 6 | thrown; + case 7: + return 7 | thrown; + case 0: + return 0 | thrown; + } + break; + } + + case BlockID.WOODEN_DOOR_BLOCK: + case BlockID.IRON_DOOR_BLOCK: + case BlockID.SPRUCE_DOOR_BLOCK: + case BlockID.BIRCH_DOOR_BLOCK: + case BlockID.JUNGLE_DOOR_BLOCK: + case BlockID.ACACIA_DOOR_BLOCK: + case BlockID.DARK_OAK_DOOR_BLOCK: + if ((meta & 0x8) != 0) { + // door top halves contain no orientation information + break; + } + + /* FALL-THROUGH */ + + case BlockID.END_PORTAL_FRAME: + case BlockID.COCOA_BLOCK: + case BlockID.TRIPWIRE_HOOK: { + int extra = meta & ~0x3; + int withoutFlags = meta & 0x3; + switch (withoutFlags) { + case 0: + return 2 | extra; + case 1: + return 3 | extra; + case 2: + return 0 | extra; + case 3: + return 1 | extra; + } + break; + } + case BlockID.SIGN_POST: + case BlockID.STANDING_BANNER: + return (meta + 8) % 16; + + case BlockID.LADDER: + case BlockID.WALL_SIGN: + case BlockID.WALL_BANNER: + case BlockID.CHEST: + case BlockID.FURNACE: + case BlockID.BURNING_FURNACE: + case BlockID.ENDER_CHEST: + case BlockID.TRAPPED_CHEST: + case BlockID.HOPPER_BLOCK: { + int extra = meta & 0x8; + int withoutFlags = meta & ~0x8; + switch (withoutFlags) { + case 2: + return 3 | extra; + case 3: + return 2 | extra; + case 4: + return 5 | extra; + case 5: + return 4 | extra; + } + break; + } + case BlockID.DISPENSER: + case BlockID.DROPPER: + case BlockID.END_ROD: + int dispPower = meta & 0x8; + switch (meta & ~0x8) { + case 2: + return 3 | dispPower; + case 3: + return 2 | dispPower; + case 4: + return 5 | dispPower; + case 5: + return 4 | dispPower; + } + break; + + case BlockID.PUMPKIN: + case BlockID.JACK_O_LANTERN: + switch (meta) { + case 0: + return 2; + case 1: + return 3; + case 2: + return 0; + case 3: + return 1; + } + break; + + case BlockID.HAY_BALE: + case BlockID.LOG: + case BlockID.LOG2: + case BlockID.QUARTZ_BLOCK: + case BlockID.PURPUR_BLOCK: + case BlockID.BONE_BLOCK: + break; + + case BlockID.UNPOWERED_COMPARATOR: + case BlockID.POWERED_COMPARATOR: + case BlockID.UNPOWERED_REPEATER: + case BlockID.POWERED_REPEATER: + int dir = meta & 0x03; + int delay = meta - dir; + switch (dir) { + case 0: + return 2 | delay; + case 1: + return 3 | delay; + case 2: + return 0 | delay; + case 3: + return 1 | delay; + } + break; + + case BlockID.TRAPDOOR: + case BlockID.IRON_TRAPDOOR: + int withoutOrientation = meta & ~0x3; + int orientation = meta & 0x3; + switch (orientation) { + case 0: + return 1 | withoutOrientation; + case 1: + return 0 | withoutOrientation; + case 2: + return 3 | withoutOrientation; + case 3: + return 2 | withoutOrientation; + } + break; + + case BlockID.PISTON: + case BlockID.STICKY_PISTON: + case BlockID.PISTON_HEAD: + case 137: //BlockID.COMMAND_BLOCK + case 188: //BlockID.REPEATING_COMMAND_BLOCK + case 189: //BlockID.CHAIN_COMMAND_BLOCK + final int rest = meta & ~0x7; + switch (meta & 0x7) { + case 2: + return 3 | rest; + case 3: + return 2 | rest; + case 4: + return 5 | rest; + case 5: + return 4 | rest; + } + break; + + case BlockID.BROWN_MUSHROOM_BLOCK: + case BlockID.RED_MUSHROOM_BLOCK: + if (meta >= 10) return meta; + return (meta * 9) % 10; + + case BlockID.VINE: + return ((meta << 2) | (meta >> 2)) & 0xf; + + case BlockID.FENCE_GATE: + case BlockID.FENCE_GATE_SPRUCE: + case BlockID.FENCE_GATE_BIRCH: + case BlockID.FENCE_GATE_JUNGLE: + case BlockID.FENCE_GATE_DARK_OAK: + case BlockID.FENCE_GATE_ACACIA: + return ((meta + 2) & 0x3) | (meta & ~0x3); + + case BlockID.ANVIL: + int damage = meta & ~0x3; + switch (meta & 0x3) { + case 0: + return 2 | damage; + case 2: + return 0 | damage; + case 1: + return 3 | damage; + case 3: + return 1 | damage; + } + break; + + case BlockID.BED_BLOCK: + return meta & ~0x3 | (meta + 2) & 0x3; + + case BlockID.SKULL_BLOCK: + case BlockID.PURPLE_GLAZED_TERRACOTTA: + case BlockID.WHITE_GLAZED_TERRACOTTA: + case BlockID.ORANGE_GLAZED_TERRACOTTA: + case BlockID.MAGENTA_GLAZED_TERRACOTTA: + case BlockID.LIGHT_BLUE_GLAZED_TERRACOTTA: + case BlockID.YELLOW_GLAZED_TERRACOTTA: + case BlockID.LIME_GLAZED_TERRACOTTA: + case BlockID.PINK_GLAZED_TERRACOTTA: + case BlockID.GRAY_GLAZED_TERRACOTTA: + case BlockID.SILVER_GLAZED_TERRACOTTA: + case BlockID.CYAN_GLAZED_TERRACOTTA: + case BlockID.BLUE_GLAZED_TERRACOTTA: + case BlockID.BROWN_GLAZED_TERRACOTTA: + case BlockID.GREEN_GLAZED_TERRACOTTA: + case BlockID.RED_GLAZED_TERRACOTTA: + case BlockID.BLACK_GLAZED_TERRACOTTA: + case BlockID.OBSERVER: + switch (meta) { + case 2: + return 3; + case 3: + return 2; + case 4: + return 5; + case 5: + return 4; + } + break; + + case BlockID.NETHER_PORTAL: + break; + + case BlockID.ITEM_FRAME_BLOCK: + switch (meta) { + case 0: + return 1; + case 1: + return 0; + case 2: + return 3; + case 3: + return 2; + } + break; + + } + + return meta; + } +} diff --git a/src/main/java/cn/wode490390/nukkit/shpop/populator/PopulatorStronghold.java b/src/main/java/cn/wode490390/nukkit/shpop/populator/PopulatorStronghold.java new file mode 100644 index 0000000..34e31bc --- /dev/null +++ b/src/main/java/cn/wode490390/nukkit/shpop/populator/PopulatorStronghold.java @@ -0,0 +1,237 @@ +package cn.wode490390.nukkit.shpop.populator; + +import cn.nukkit.Server; +import cn.nukkit.level.ChunkManager; +import cn.nukkit.level.Level; +import cn.nukkit.level.biome.EnumBiome; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.level.format.generic.BaseFullChunk; +import cn.nukkit.level.generator.populator.type.Populator; +import cn.nukkit.math.NukkitRandom; +import cn.wode490390.nukkit.shpop.StrongholdPlugin; +import cn.wode490390.nukkit.shpop.math.BoundingBox; +import cn.wode490390.nukkit.shpop.scheduler.CallbackableChunkGenerationTask; +import cn.wode490390.nukkit.shpop.structure.StrongholdPieces; +import cn.wode490390.nukkit.shpop.structure.StructurePiece; +import cn.wode490390.nukkit.shpop.structure.StructureStart; +import com.google.common.collect.Lists; + +import java.util.List; +import java.util.StringJoiner; + +public class PopulatorStronghold extends Populator { + + protected static final int DISTANCE = 32; + protected static final int COUNT = 128; + protected static final int SPREAD = 3; + + protected boolean isSpotSelected = false; + protected long[] strongholdPos = new long[COUNT]; + private final List discoveredStarts = Lists.newArrayList(); + + private final Object lock = new Object(); //\\ (char *)this + 456 + + @Override + public void populate(ChunkManager level, int chunkX, int chunkZ, NukkitRandom random, FullChunk chunk) { + //\\ StrongholdFeature::isFeatureChunk(BiomeSource const &,Random &,ChunkPos const &,uint) + if (VALID_BIOMES[chunk.getBiomeId(7, 7)]) { + //\\ StrongholdFeature *v28 = (StrongholdFeature *)((char *)this + 456); // [rsp+60h] [rbp+8h] + synchronized (this.lock) { //\\ _Mtx_lock((char *)this + 456); + if (!this.isSpotSelected) { + //\\ StrongholdFeature::generatePositions(Random &,BiomeSource const &,uint) + int count = 0; + for (StructureStart start : this.discoveredStarts) { + if (count < COUNT) { + this.strongholdPos[count++] = Level.chunkHash(start.getChunkX(), start.getChunkZ()); + } + } + + NukkitRandom rand = new NukkitRandom(level.getSeed()); + double angle = rand.nextDouble() * Math.PI * 2.0; + int spread = SPREAD; + + if (count < COUNT) { + int spreadCount = 0; + int nextCount = 0; + + for (int i = 0; i < COUNT; ++i) { + double radius = (double) (4 * DISTANCE + DISTANCE * nextCount * 6) + (rand.nextDouble() - 0.5) * (double) DISTANCE * 2.5; + int cx = (int) Math.round(Math.cos(angle) * radius); + int cz = (int) Math.round(Math.sin(angle) * radius); + + if (i >= count) { + this.strongholdPos[i] = Level.chunkHash(cx, cz); + } + + angle += Math.PI * 2.0 / (double) spread; + ++spreadCount; + if (spreadCount == spread) { + ++nextCount; + spreadCount = 0; + spread += 2 * spread / (nextCount + 1); + spread = Math.min(spread, COUNT - i); + angle += rand.nextDouble() * Math.PI * 2.0; + } + } + } + + this.isSpotSelected = true; + + if (StrongholdPlugin.DEBUG) { + StringJoiner joiner = new StringJoiner("\n"); + for (long hash : this.strongholdPos) { + joiner.add((Level.getHashX(hash) << 4) + "," + (Level.getHashZ(hash) << 4)); + } + StrongholdPlugin.debug(this.getClass().getSimpleName(), "StrongholdFeature::generatePositions\n", joiner); + } + } + } //\\ _Mtx_unlock((char *)this + 456) + + long hash = Level.chunkHash(chunkX, chunkZ); + for (long chunkPos : strongholdPos) { + if (hash == chunkPos) { + //\\ StrongholdFeature::createStructureStart(Dimension &,BiomeSource &,Random &,ChunkPos const &) + StrongholdStart start = new StrongholdStart(level, chunkX, chunkZ); + start.generatePieces(level, chunkX, chunkZ); + + if (start.isValid()) { //TODO: serialize nbt + long seed = level.getSeed(); + random.setSeed(seed); + int r1 = random.nextInt(); + int r2 = random.nextInt(); + + BoundingBox boundingBox = start.getBoundingBox(); + for (int cx = boundingBox.x0 >> 4; cx <= boundingBox.x1 >> 4; cx++) { + for (int cz = boundingBox.z0 >> 4; cz <= boundingBox.z1 >> 4; cz++) { + NukkitRandom rand = new NukkitRandom(cx * r1 ^ cz * r2 ^ seed); + int x = cx << 4; + int z = cz << 4; + BaseFullChunk ck = level.getChunk(cx, cz); + if (ck == null) { + ck = chunk.getProvider().getChunk(cx, cz, true); + } + + if (ck.isGenerated()) { + start.postProcess(level, rand, new BoundingBox(x, z, x + 15, z + 15), cx, cz); + } else { + int f_cx = cx; + int f_cz = cz; + Server.getInstance().getScheduler().scheduleAsyncTask(null, new CallbackableChunkGenerationTask<>( + chunk.getProvider().getLevel(), ck, start, + structure -> structure.postProcess(level, rand, new BoundingBox(x, z, x + 15, z + 15), f_cx, f_cz))); + } + } + } + + StrongholdPlugin.debug(this.getClass().getSimpleName(), chunkX << 4, chunkZ << 4); + } + + break; + } + } + } + } + + protected static boolean[] VALID_BIOMES = new boolean[256]; + + public static void init() { + VALID_BIOMES[EnumBiome.PLAINS.id] = true; + VALID_BIOMES[EnumBiome.DESERT.id] = true; + VALID_BIOMES[EnumBiome.EXTREME_HILLS.id] = true; + VALID_BIOMES[EnumBiome.FOREST.id] = true; + VALID_BIOMES[EnumBiome.TAIGA.id] = true; + VALID_BIOMES[EnumBiome.ICE_PLAINS.id] = true; + VALID_BIOMES[13] = true; //SNOWY_MOUNTAINS + VALID_BIOMES[EnumBiome.MUSHROOM_ISLAND.id] = true; + VALID_BIOMES[EnumBiome.MUSHROOM_ISLAND_SHORE.id] = true; + VALID_BIOMES[EnumBiome.DESERT_HILLS.id] = true; + VALID_BIOMES[EnumBiome.FOREST_HILLS.id] = true; + VALID_BIOMES[EnumBiome.TAIGA_HILLS.id] = true; + VALID_BIOMES[EnumBiome.EXTREME_HILLS_EDGE.id] = true; + VALID_BIOMES[EnumBiome.JUNGLE.id] = true; + VALID_BIOMES[EnumBiome.JUNGLE_HILLS.id] = true; + VALID_BIOMES[EnumBiome.JUNGLE_EDGE.id] = true; + VALID_BIOMES[EnumBiome.STONE_BEACH.id] = true; + VALID_BIOMES[EnumBiome.BIRCH_FOREST.id] = true; + VALID_BIOMES[EnumBiome.BIRCH_FOREST_HILLS.id] = true; + VALID_BIOMES[EnumBiome.ROOFED_FOREST.id] = true; + VALID_BIOMES[EnumBiome.COLD_TAIGA.id] = true; + VALID_BIOMES[EnumBiome.COLD_TAIGA_HILLS.id] = true; + VALID_BIOMES[EnumBiome.MEGA_TAIGA.id] = true; + VALID_BIOMES[EnumBiome.MEGA_TAIGA_HILLS.id] = true; + VALID_BIOMES[EnumBiome.EXTREME_HILLS_PLUS.id] = true; + VALID_BIOMES[EnumBiome.SAVANNA.id] = true; + VALID_BIOMES[EnumBiome.SAVANNA_PLATEAU.id] = true; + VALID_BIOMES[EnumBiome.MESA.id] = true; + VALID_BIOMES[EnumBiome.MESA_PLATEAU_F.id] = true; + VALID_BIOMES[EnumBiome.MESA_PLATEAU.id] = true; + VALID_BIOMES[EnumBiome.SUNFLOWER_PLAINS.id] = true; + VALID_BIOMES[EnumBiome.DESERT_M.id] = true; + VALID_BIOMES[EnumBiome.EXTREME_HILLS_M.id] = true; + VALID_BIOMES[EnumBiome.FLOWER_FOREST.id] = true; + VALID_BIOMES[EnumBiome.TAIGA_M.id] = true; + VALID_BIOMES[EnumBiome.ICE_PLAINS_SPIKES.id] = true; + VALID_BIOMES[EnumBiome.JUNGLE_M.id] = true; + VALID_BIOMES[EnumBiome.JUNGLE_EDGE_M.id] = true; + VALID_BIOMES[EnumBiome.BIRCH_FOREST_M.id] = true; + VALID_BIOMES[EnumBiome.BIRCH_FOREST_HILLS_M.id] = true; + VALID_BIOMES[EnumBiome.ROOFED_FOREST_M.id] = true; + VALID_BIOMES[EnumBiome.COLD_TAIGA_M.id] = true; + VALID_BIOMES[EnumBiome.MEGA_SPRUCE_TAIGA.id] = true; + VALID_BIOMES[161] = true; //GIANT_SPRUCE_TAIGA_HILLS + VALID_BIOMES[EnumBiome.EXTREME_HILLS_PLUS_M.id] = true; + VALID_BIOMES[EnumBiome.SAVANNA_M.id] = true; + VALID_BIOMES[EnumBiome.SAVANNA_PLATEAU_M.id] = true; + VALID_BIOMES[EnumBiome.MESA_BRYCE.id] = true; + VALID_BIOMES[EnumBiome.MESA_PLATEAU_F_M.id] = true; + VALID_BIOMES[EnumBiome.MESA_PLATEAU_M.id] = true; + VALID_BIOMES[168] = true; //BAMBOO_JUNGLE + VALID_BIOMES[169] = true; //BAMBOO_JUNGLE_HILLS + + StrongholdPieces.init(); + } + + public class StrongholdStart extends StructureStart { + + public StrongholdStart(ChunkManager level, int chunkX, int chunkZ) { + super(level, chunkX, chunkZ); + } + + @Override + public void generatePieces(ChunkManager level, int chunkX, int chunkZ) { + synchronized (StrongholdPieces.getLock()) { + int count = 0; + long seed = level.getSeed(); + StrongholdPieces.StartPiece start; + + do { + this.pieces.clear(); + this.boundingBox = BoundingBox.getUnknownBox(); + this.random.setSeed(seed + count++); + this.random.setSeed(chunkX * this.random.nextInt() ^ chunkZ * this.random.nextInt() ^ level.getSeed()); + StrongholdPieces.resetPieces(); + + start = new StrongholdPieces.StartPiece(this.random, (chunkX << 4) + 2, (chunkZ << 4) + 2); + this.pieces.add(start); + start.addChildren(start, this.pieces, this.random); + + List children = start.pendingChildren; + while (!children.isEmpty()) { + children.remove(this.random.nextBoundedInt(children.size())) + .addChildren(start, this.pieces, this.random); + } + + this.calculateBoundingBox(); + this.moveBelowSeaLevel(64, this.random, 10); + } while (this.pieces.isEmpty() || start.portalRoomPiece == null); + + PopulatorStronghold.this.discoveredStarts.add(this); + } + } + + @Override //\\ StrongholdStart::getType(void) // 5 + public String getType() { + return "Stronghold"; + } + } +} diff --git a/src/main/java/cn/wode490390/nukkit/shpop/scheduler/BlockActorSpawnTask.java b/src/main/java/cn/wode490390/nukkit/shpop/scheduler/BlockActorSpawnTask.java new file mode 100644 index 0000000..9f69577 --- /dev/null +++ b/src/main/java/cn/wode490390/nukkit/shpop/scheduler/BlockActorSpawnTask.java @@ -0,0 +1,26 @@ +package cn.wode490390.nukkit.shpop.scheduler; + +import cn.nukkit.blockentity.BlockEntity; +import cn.nukkit.level.Level; +import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.plugin.Plugin; +import cn.nukkit.scheduler.PluginTask; +import cn.wode490390.nukkit.shpop.StrongholdPlugin; + +public class BlockActorSpawnTask extends PluginTask { + + private final Level level; + private final CompoundTag nbt; + + public BlockActorSpawnTask(Level level, CompoundTag nbt) { + super(StrongholdPlugin.getInstance()); + this.level = level; + this.nbt = nbt; + } + + @Override + public void onRun(int currentTick) { + BlockEntity.createBlockEntity(this.nbt.getString("id"), + this.level.getChunk(this.nbt.getInt("x") >> 4, this.nbt.getInt("z") >> 4), this.nbt); + } +} diff --git a/src/main/java/cn/wode490390/nukkit/shpop/scheduler/CallbackableChunkGenerationTask.java b/src/main/java/cn/wode490390/nukkit/shpop/scheduler/CallbackableChunkGenerationTask.java new file mode 100644 index 0000000..e7d3459 --- /dev/null +++ b/src/main/java/cn/wode490390/nukkit/shpop/scheduler/CallbackableChunkGenerationTask.java @@ -0,0 +1,63 @@ +package cn.wode490390.nukkit.shpop.scheduler; + +import cn.nukkit.level.Level; +import cn.nukkit.level.format.generic.BaseFullChunk; +import cn.nukkit.level.generator.Generator; +import cn.nukkit.level.generator.SimpleChunkManager; +import cn.nukkit.scheduler.AsyncTask; +import cn.wode490390.nukkit.shpop.structure.StructureStart; + +import java.util.function.Consumer; + +public class CallbackableChunkGenerationTask extends AsyncTask { + + public boolean state = true; + + private final Level level; + private BaseFullChunk chunk; + private final T structure; + private final Consumer callback; + + public CallbackableChunkGenerationTask(Level level, BaseFullChunk chunk, T structure, Consumer callback) { + this.chunk = chunk; + this.level = level; + this.structure = structure; + this.callback = callback; + } + + @Override + public void onRun() { + this.state = false; + + Generator generator = this.level.getGenerator(); + if (generator != null) { + SimpleChunkManager manager = (SimpleChunkManager) generator.getChunkManager(); + if (manager != null) { + manager.cleanChunks(this.level.getSeed()); + synchronized (manager) { + try { + BaseFullChunk chunk = this.chunk; + if (chunk != null) { + synchronized (chunk) { + if (!chunk.isGenerated()) { + manager.setChunk(chunk.getX(), chunk.getZ(), chunk); + generator.generateChunk(chunk.getX(), chunk.getZ()); + chunk = manager.getChunk(chunk.getX(), chunk.getZ()); + chunk.setGenerated(); + } + } + this.chunk = chunk; + this.state = true; + } + } finally { + manager.cleanChunks(this.level.getSeed()); + } + } + } + } + + if (this.state && this.chunk != null) { + this.callback.accept(this.structure); + } + } +} diff --git a/src/main/java/cn/wode490390/nukkit/shpop/scheduler/ChunkPopulationTask.java b/src/main/java/cn/wode490390/nukkit/shpop/scheduler/ChunkPopulationTask.java new file mode 100644 index 0000000..3ede233 --- /dev/null +++ b/src/main/java/cn/wode490390/nukkit/shpop/scheduler/ChunkPopulationTask.java @@ -0,0 +1,30 @@ +package cn.wode490390.nukkit.shpop.scheduler; + +import cn.nukkit.level.ChunkManager; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.level.generator.populator.type.Populator; +import cn.nukkit.math.NukkitRandom; +import cn.nukkit.scheduler.AsyncTask; + +import java.util.Collection; + +public class ChunkPopulationTask extends AsyncTask { + + private final ChunkManager level; + private final FullChunk chunk; + private final Collection populators; + + public ChunkPopulationTask(ChunkManager level, FullChunk chunk, Collection populators) { + this.level = level; + this.chunk = chunk; + this.populators = populators; + } + + @Override + public void onRun() { + int chunkX = this.chunk.getX(); + int chunkZ = this.chunk.getZ(); + NukkitRandom random = new NukkitRandom(0xdeadbeef ^ (chunkX << 8) ^ chunkZ ^ this.level.getSeed()); + this.populators.forEach(populator -> populator.populate(this.level, chunkX, chunkZ, random, this.chunk)); + } +} diff --git a/src/main/java/cn/wode490390/nukkit/shpop/structure/StrongholdPieces.java b/src/main/java/cn/wode490390/nukkit/shpop/structure/StrongholdPieces.java new file mode 100644 index 0000000..33dcf0a --- /dev/null +++ b/src/main/java/cn/wode490390/nukkit/shpop/structure/StrongholdPieces.java @@ -0,0 +1,1523 @@ +package cn.wode490390.nukkit.shpop.structure; + +import cn.nukkit.Server; +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockBricksStone; +import cn.nukkit.block.BlockDoubleSlabStone; +import cn.nukkit.block.BlockFence; +import cn.nukkit.block.BlockMonsterEgg; +import cn.nukkit.block.BlockPlanks; +import cn.nukkit.block.BlockSlabStone; +import cn.nukkit.blockentity.BlockEntity; +import cn.nukkit.entity.mob.EntitySilverfish; +import cn.nukkit.level.ChunkManager; +import cn.nukkit.level.format.generic.BaseFullChunk; +import cn.nukkit.math.BlockFace; +import cn.nukkit.math.BlockVector3; +import cn.nukkit.math.NukkitRandom; +import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.nbt.tag.ListTag; +import cn.wode490390.nukkit.shpop.block.state.BlockState; +import cn.wode490390.nukkit.shpop.block.state.Direction; +import cn.wode490390.nukkit.shpop.block.state.EndPortalEyeBit; +import cn.wode490390.nukkit.shpop.block.state.FacingDirection; +import cn.wode490390.nukkit.shpop.block.state.TorchFacingDirection; +import cn.wode490390.nukkit.shpop.block.state.UpperBlockBit; +import cn.wode490390.nukkit.shpop.block.state.WeirdoDirection; +import cn.wode490390.nukkit.shpop.loot.StrongholdCorridorChest; +import cn.wode490390.nukkit.shpop.loot.StrongholdCrossingChest; +import cn.wode490390.nukkit.shpop.loot.StrongholdLibraryChest; +import cn.wode490390.nukkit.shpop.math.BoundingBox; +import cn.wode490390.nukkit.shpop.scheduler.BlockActorSpawnTask; +import com.google.common.collect.Lists; + +import javax.annotation.Nullable; +import java.util.Iterator; +import java.util.List; + +public class StrongholdPieces { + + private static final BlockState INFESTED_STONE_BRICKS = new BlockState(Block.MONSTER_EGG, BlockMonsterEgg.STONE_BRICK); + private static final BlockState STONE_BRICKS = new BlockState(Block.STONE_BRICKS, BlockBricksStone.NORMAL); + private static final BlockState MOSSY_STONE_BRICKS = new BlockState(Block.STONE_BRICKS, BlockBricksStone.MOSSY); + private static final BlockState CRACKED_STONE_BRICKS = new BlockState(Block.STONE_BRICKS, BlockBricksStone.CRACKED); + private static final BlockState STONE_BRICK_SLAB = new BlockState(Block.STONE_SLAB, BlockSlabStone.STONE_BRICK); + private static final BlockState SMOOTH_STONE_SLAB = new BlockState(Block.STONE_SLAB, BlockSlabStone.STONE); + private static final BlockState SMOOTH_STONE_SLAB_DOUBLE = new BlockState(Block.DOUBLE_STONE_SLAB, BlockDoubleSlabStone.STONE); + private static final BlockState COBBLESTONE = new BlockState(Block.COBBLESTONE); + private static final BlockState WATER = new BlockState(Block.STILL_WATER); + private static final BlockState LAVA = new BlockState(Block.STILL_LAVA); + private static final BlockState OAK_FENCE = new BlockState(Block.FENCE, BlockFence.FENCE_OAK); + private static final BlockState OAK_PLANKS = new BlockState(Block.PLANKS, BlockPlanks.OAK); + private static final BlockState OAK_DOOR = new BlockState(Block.WOOD_DOOR_BLOCK); + private static final BlockState IRON_DOOR = new BlockState(Block.IRON_DOOR_BLOCK); + private static final BlockState IRON_BARS = new BlockState(Block.IRON_BARS); + private static final BlockState TORCH = new BlockState(Block.TORCH, TorchFacingDirection.TOP); + private static final BlockState END_PORTAL = new BlockState(Block.END_PORTAL); + private static final BlockState BOOKSHELF = new BlockState(Block.BOOKSHELF); + private static final BlockState COBWEB = new BlockState(Block.COBWEB); + private static final BlockState SPAWNER = new BlockState(Block.MONSTER_SPAWNER); + + private static final Object lock = new Object(); + + private static final PieceWeight[] STRONGHOLD_PIECE_WEIGHTS = new PieceWeight[]{ + new PieceWeight(Straight.class, 40, 0), + new PieceWeight(PrisonHall.class, 5, 5), + new PieceWeight(LeftTurn.class, 20, 0), + new PieceWeight(RightTurn.class, 20, 0), + new PieceWeight(RoomCrossing.class, 10, 6), + new PieceWeight(StraightStairsDown.class, 5, 5), + new PieceWeight(StairsDown.class, 5, 5), + new PieceWeight(FiveCrossing.class, 5, 4), + new PieceWeight(ChestCorridor.class, 5, 4), + new PieceWeight(Library.class, 10, 2) { + @Override + public boolean doPlace(int genDepth) { + return super.doPlace(genDepth) && genDepth > 4; + } + }, + new PieceWeight(PortalRoom.class, 20, 1) { + @Override + public boolean doPlace(int genDepth) { + return super.doPlace(genDepth) && genDepth > 5; + } + } + }; + + private static List currentPieces; + private static Class imposedPiece; + private static int totalWeight; + + private static final SmoothStoneSelector SMOOTH_STONE_SELECTOR = new SmoothStoneSelector(); + + public static Object getLock() { + return lock; + } + + public static void resetPieces() { + currentPieces = Lists.newArrayList(); + + for (PieceWeight weight : STRONGHOLD_PIECE_WEIGHTS) { + weight.placeCount = 0; + currentPieces.add(weight); + } + + imposedPiece = null; + } + + private static boolean updatePieceWeight() { + boolean success = false; + totalWeight = 0; + + PieceWeight weight; + for (Iterator iterator = currentPieces.iterator(); iterator.hasNext(); totalWeight += weight.weight) { + weight = iterator.next(); + if (weight.maxPlaceCount > 0 && weight.placeCount < weight.maxPlaceCount) { + success = true; + } + } + + return success; + } + + //\\ StrongholdPiece::findAndCreatePieceFactory(std::basic_string,std::allocator> const &,std::vector>,std::allocator>>> &,Random &,int,int,int,int,int) + private static StrongholdPiece findAndCreatePieceFactory(Class pieceClass, List pieces, NukkitRandom random, int x, int y, int z, @Nullable BlockFace orientation, int genDepth) { + if (pieceClass == Straight.class) { + return Straight.createPiece(pieces, random, x, y, z, orientation, genDepth); + } else if (pieceClass == PrisonHall.class) { + return PrisonHall.createPiece(pieces, random, x, y, z, orientation, genDepth); + } else if (pieceClass == LeftTurn.class) { + return LeftTurn.createPiece(pieces, random, x, y, z, orientation, genDepth); + } else if (pieceClass == RightTurn.class) { + return RightTurn.createPiece(pieces, random, x, y, z, orientation, genDepth); + } else if (pieceClass == RoomCrossing.class) { + return RoomCrossing.createPiece(pieces, random, x, y, z, orientation, genDepth); + } else if (pieceClass == StraightStairsDown.class) { + return StraightStairsDown.createPiece(pieces, random, x, y, z, orientation, genDepth); + } else if (pieceClass == StairsDown.class) { + return StairsDown.createPiece(pieces, random, x, y, z, orientation, genDepth); + } else if (pieceClass == FiveCrossing.class) { + return FiveCrossing.createPiece(pieces, random, x, y, z, orientation, genDepth); + } else if (pieceClass == ChestCorridor.class) { + return ChestCorridor.createPiece(pieces, random, x, y, z, orientation, genDepth); + } else if (pieceClass == Library.class) { + return Library.createPiece(pieces, random, x, y, z, orientation, genDepth); + } else if (pieceClass == PortalRoom.class) { + return PortalRoom.createPiece(pieces, x, y, z, orientation, genDepth); + } + return null; + } + + @Nullable //\\ StrongholdPiece::generatePieceFromSmallDoor(SHStartPiece *,std::vector>,std::allocator>>> &,Random const &,int,int,int,int,int) + private static StrongholdPiece generatePieceFromSmallDoor(StartPiece piece, List pieces, NukkitRandom random, int x, int y, int z, BlockFace orientation, int genDepth) { + if (updatePieceWeight()) { + if (imposedPiece != null) { + StrongholdPiece result = findAndCreatePieceFactory(imposedPiece, pieces, random, x, y, z, orientation, genDepth); + imposedPiece = null; + if (result != null) { + return result; + } + } + + for (int i = 0; i < 5; i++) { + int target = random.nextBoundedInt(totalWeight); + + for (PieceWeight weight : currentPieces) { + target -= weight.weight; + + if (target < 0) { + if (!weight.doPlace(genDepth) || weight == piece.previousPiece) { + break; + } + + StrongholdPiece result = findAndCreatePieceFactory(weight.pieceClass, pieces, random, x, y, z, orientation, genDepth); + if (result != null) { + ++weight.placeCount; + piece.previousPiece = weight; + if (!weight.isValid()) { + currentPieces.remove(weight); + } + + return result; + } + } + } + } + + BoundingBox boundingBox = FillerCorridor.findPieceBox(pieces, random, x, y, z, orientation); + if (boundingBox != null && boundingBox.y0 > 1) { + return new FillerCorridor(genDepth, boundingBox, orientation); + } + } + + return null; + } + + @Nullable //\\ StrongholdPiece::generateAndAddPiece(SHStartPiece *,std::vector>,std::allocator>>> &,Random &,int,int,int,int,int) + private static StructurePiece generateAndAddPiece(StartPiece start, List pieces, NukkitRandom random, int x, int y, int z, @Nullable BlockFace orientation, int genDepth) { + if (genDepth > 50) { + return null; + } else if (Math.abs(x - start.getBoundingBox().x0) <= 112 && Math.abs(z - start.getBoundingBox().z0) <= 112) { + StructurePiece piece = generatePieceFromSmallDoor(start, pieces, random, x, y, z, orientation, genDepth + 1); + if (piece != null) { + pieces.add(piece); + start.pendingChildren.add(piece); + } + + return piece; + } else { + return null; + } + } + + static class PieceWeight { + + public final Class pieceClass; + public final int weight; + public int placeCount; + public final int maxPlaceCount; + + public PieceWeight(Class pieceClass, int weight, int maxPlaceCount) { + this.pieceClass = pieceClass; + this.weight = weight; + this.maxPlaceCount = maxPlaceCount; + } + + public boolean doPlace(int genDepth) { + return this.maxPlaceCount == 0 || this.placeCount < this.maxPlaceCount; + } + + public boolean isValid() { + return this.maxPlaceCount == 0 || this.placeCount < this.maxPlaceCount; + } + } + + abstract static class StrongholdPiece extends StructurePiece { + + protected SmallDoorType entryDoor; + + protected StrongholdPiece(int genDepth) { + super(genDepth); + this.entryDoor = SmallDoorType.OPENING; + } + + public StrongholdPiece(CompoundTag tag) { + super(tag); + this.entryDoor = SmallDoorType.OPENING; + this.entryDoor = SmallDoorType.valueOf(tag.getString("EntryDoor")); + } + + @Override //\\ SHStartPiece::getType(void) // 1397248082i64 + public String getType() { + return "SHStart"; + } + + @Override + protected void addAdditionalSaveData(CompoundTag tag) { + tag.putString("EntryDoor", this.entryDoor.name()); + } + + //\\ StrongholdPiece::generateSmallDoor(BlockSource *,Random &,BoundingBox const &,StrongholdPiece::SmallDoorType,int,int,int) + protected void generateSmallDoor(ChunkManager level, NukkitRandom random, BoundingBox boundingBox, SmallDoorType type, int x, int y, int z) { + switch (type) { + case OPENING: + this.generateBox(level, boundingBox, x, y, z, x + 3 - 1, y + 3 - 1, z, BlockState.AIR, BlockState.AIR, false); + break; + case WOOD_DOOR: + this.placeBlock(level, STONE_BRICKS, x, y, z, boundingBox); + this.placeBlock(level, STONE_BRICKS, x, y + 1, z, boundingBox); + this.placeBlock(level, STONE_BRICKS, x, y + 2, z, boundingBox); + this.placeBlock(level, STONE_BRICKS, x + 1, y + 2, z, boundingBox); + this.placeBlock(level, STONE_BRICKS, x + 2, y + 2, z, boundingBox); + this.placeBlock(level, STONE_BRICKS, x + 2, y + 1, z, boundingBox); + this.placeBlock(level, STONE_BRICKS, x + 2, y, z, boundingBox); + + this.placeBlock(level, OAK_DOOR, x + 1, y, z, boundingBox); + this.placeBlock(level, new BlockState(Block.WOOD_DOOR_BLOCK, UpperBlockBit.UPPER), x + 1, y + 1, z, boundingBox); + break; + case GRATES: + this.placeBlock(level, BlockState.AIR, x + 1, y, z, boundingBox); + this.placeBlock(level, BlockState.AIR, x + 1, y + 1, z, boundingBox); + + this.placeBlock(level, IRON_BARS, x, y, z, boundingBox); + this.placeBlock(level, IRON_BARS, x, y + 1, z, boundingBox); + this.placeBlock(level, IRON_BARS, x, y + 2, z, boundingBox); + this.placeBlock(level, IRON_BARS, x + 1, y + 2, z, boundingBox); + this.placeBlock(level, IRON_BARS, x + 2, y + 2, z, boundingBox); + this.placeBlock(level, IRON_BARS, x + 2, y + 1, z, boundingBox); + this.placeBlock(level, IRON_BARS, x + 2, y, z, boundingBox); + break; + case IRON_DOOR: + this.placeBlock(level, STONE_BRICKS, x, y, z, boundingBox); + this.placeBlock(level, STONE_BRICKS, x, y + 1, z, boundingBox); + this.placeBlock(level, STONE_BRICKS, x, y + 2, z, boundingBox); + this.placeBlock(level, STONE_BRICKS, x + 1, y + 2, z, boundingBox); + this.placeBlock(level, STONE_BRICKS, x + 2, y + 2, z, boundingBox); + this.placeBlock(level, STONE_BRICKS, x + 2, y + 1, z, boundingBox); + this.placeBlock(level, STONE_BRICKS, x + 2, y, z, boundingBox); + + this.placeBlock(level, IRON_DOOR, x + 1, y, z, boundingBox); + this.placeBlock(level, new BlockState(Block.IRON_DOOR_BLOCK, UpperBlockBit.UPPER), x + 1, y + 1, z, boundingBox); + + this.placeBlock(level, new BlockState(Block.STONE_BUTTON, FacingDirection.NORTH), x + 2, y + 1, z + 1, boundingBox); + this.placeBlock(level, new BlockState(Block.STONE_BUTTON, FacingDirection.SOUTH), x + 2, y + 1, z - 1, boundingBox); + } + } + + protected SmallDoorType randomSmallDoor(NukkitRandom random) { + int i = random.nextBoundedInt(5); + switch (i) { + case 0: + case 1: + default: + return SmallDoorType.OPENING; + case 2: + return SmallDoorType.WOOD_DOOR; + case 3: + return SmallDoorType.GRATES; + case 4: + return SmallDoorType.IRON_DOOR; + } + } + + @Nullable //\\ StrongholdPiece::generateSmallDoorChildForward(SHStartPiece *,std::vector>,std::allocator>>> &,Random &,int,int) + protected StructurePiece generateSmallDoorChildForward(StartPiece piece, List pieces, NukkitRandom random, int x, int y) { + BlockFace orientation = this.getOrientation(); + if (orientation != null) { + switch (orientation) { + case NORTH: + return generateAndAddPiece(piece, pieces, random, this.boundingBox.x0 + x, this.boundingBox.y0 + y, this.boundingBox.z0 - 1, orientation, this.getGenDepth()); + case SOUTH: + return generateAndAddPiece(piece, pieces, random, this.boundingBox.x0 + x, this.boundingBox.y0 + y, this.boundingBox.z1 + 1, orientation, this.getGenDepth()); + case WEST: + return generateAndAddPiece(piece, pieces, random, this.boundingBox.x0 - 1, this.boundingBox.y0 + y, this.boundingBox.z0 + x, orientation, this.getGenDepth()); + case EAST: + return generateAndAddPiece(piece, pieces, random, this.boundingBox.x1 + 1, this.boundingBox.y0 + y, this.boundingBox.z0 + x, orientation, this.getGenDepth()); + } + } + + return null; + } + + @Nullable //\\ StrongholdPiece::generateSmallDoorChildLeft(SHStartPiece *,std::vector>,std::allocator>>> &,Random &,int,int) + protected StructurePiece generateSmallDoorChildLeft(StartPiece piece, List pieces, NukkitRandom random, int y, int z) { + BlockFace orientation = this.getOrientation(); + if (orientation != null) { + switch (orientation) { + case NORTH: + case SOUTH: + return generateAndAddPiece(piece, pieces, random, this.boundingBox.x0 - 1, this.boundingBox.y0 + y, this.boundingBox.z0 + z, BlockFace.WEST, this.getGenDepth()); + case WEST: + case EAST: + return generateAndAddPiece(piece, pieces, random, this.boundingBox.x0 + z, this.boundingBox.y0 + y, this.boundingBox.z0 - 1, BlockFace.NORTH, this.getGenDepth()); + } + } + + return null; + } + + @Nullable //\\ StrongholdPiece::generateSmallDoorChildRight(SHStartPiece *,std::vector>,std::allocator>>> &,Random &,int,int) + protected StructurePiece generateSmallDoorChildRight(StartPiece piece, List pieces, NukkitRandom random, int y, int z) { + BlockFace orientation = this.getOrientation(); + if (orientation != null) { + switch (orientation) { + case NORTH: + case SOUTH: + return generateAndAddPiece(piece, pieces, random, this.boundingBox.x1 + 1, this.boundingBox.y0 + y, this.boundingBox.z0 + z, BlockFace.EAST, this.getGenDepth()); + case WEST: + case EAST: + return generateAndAddPiece(piece, pieces, random, this.boundingBox.x0 + z, this.boundingBox.y0 + y, this.boundingBox.z1 + 1, BlockFace.SOUTH, this.getGenDepth()); + } + } + + return null; + } + + protected static boolean isOkBox(BoundingBox boundingBox) { + return boundingBox != null && boundingBox.y0 > 10; + } + + public enum SmallDoorType { + OPENING, + WOOD_DOOR, + GRATES, + IRON_DOOR; + } + } + + public static class FillerCorridor extends StrongholdPiece { + + private final int steps; + + public FillerCorridor(int genDepth, BoundingBox boundingBox, BlockFace orientation) { + super(genDepth); + this.setOrientation(orientation); + this.boundingBox = boundingBox; + this.steps = orientation != BlockFace.NORTH && orientation != BlockFace.SOUTH ? boundingBox.getXSpan() : boundingBox.getZSpan(); + } + + public FillerCorridor(CompoundTag tag) { + super(tag); + this.steps = tag.getInt("Steps"); + } + + @Override //\\ SHFillerCorridor::getType(void) // 1397245513i64 + public String getType() { + return "SHFC"; + } + + @Override + protected void addAdditionalSaveData(CompoundTag tag) { + super.addAdditionalSaveData(tag); + tag.putInt("Steps", this.steps); + } + + //\\ SHFillerCorridor::findPieceBox(std::vector>,std::allocator>>> &,Random &,int,int,int,int) + public static BoundingBox findPieceBox(List pieces, NukkitRandom random, int x, int y, int z, BlockFace orientation) { + BoundingBox boundingBox = BoundingBox.orientBox(x, y, z, -1, -1, 0, 5, 5, 4, orientation); + StructurePiece piece = StructurePiece.findCollisionPiece(pieces, boundingBox); + if (piece != null) { + if (piece.getBoundingBox().y0 == boundingBox.y0) { + for (int m = 3; m >= 1; --m) { + boundingBox = BoundingBox.orientBox(x, y, z, -1, -1, 0, 5, 5, m - 1, orientation); + if (!piece.getBoundingBox().intersects(boundingBox)) { + return BoundingBox.orientBox(x, y, z, -1, -1, 0, 5, 5, m, orientation); + } + } + } + + } + return null; + } + + @Override + public boolean postProcess(ChunkManager level, NukkitRandom random, BoundingBox boundingBox, int chunkX, int chunkZ) { + for (int z = 0; z < this.steps; ++z) { + this.placeBlock(level, STONE_BRICKS, 0, 0, z, boundingBox); + this.placeBlock(level, STONE_BRICKS, 1, 0, z, boundingBox); + this.placeBlock(level, STONE_BRICKS, 2, 0, z, boundingBox); + this.placeBlock(level, STONE_BRICKS, 3, 0, z, boundingBox); + this.placeBlock(level, STONE_BRICKS, 4, 0, z, boundingBox); + + for (int y = 1; y <= 3; ++y) { + this.placeBlock(level, STONE_BRICKS, 0, y, z, boundingBox); + this.placeBlock(level, BlockState.AIR, 1, y, z, boundingBox); + this.placeBlock(level, BlockState.AIR, 2, y, z, boundingBox); + this.placeBlock(level, BlockState.AIR, 3, y, z, boundingBox); + this.placeBlock(level, STONE_BRICKS, 4, y, z, boundingBox); + } + + this.placeBlock(level, STONE_BRICKS, 0, 4, z, boundingBox); + this.placeBlock(level, STONE_BRICKS, 1, 4, z, boundingBox); + this.placeBlock(level, STONE_BRICKS, 2, 4, z, boundingBox); + this.placeBlock(level, STONE_BRICKS, 3, 4, z, boundingBox); + this.placeBlock(level, STONE_BRICKS, 4, 4, z, boundingBox); + } + + return true; + } + } + + public static class StairsDown extends StrongholdPiece { + + private final boolean isSource; + + public StairsDown(int genDepth, NukkitRandom random, int x, int z) { + super(genDepth); + this.isSource = true; + this.setOrientation(BlockFace.Plane.HORIZONTAL.random(random)); + this.entryDoor = StrongholdPiece.SmallDoorType.OPENING; + this.boundingBox = new BoundingBox(x, 64, z, x + 5 - 1, 74, z + 5 - 1); + } + + public StairsDown(int genDepth, NukkitRandom random, BoundingBox boundingBox, BlockFace orientation) { + super(genDepth); + this.isSource = false; + this.setOrientation(orientation); + this.entryDoor = this.randomSmallDoor(random); + this.boundingBox = boundingBox; + } + + public StairsDown(CompoundTag tag) { + super(tag); + this.isSource = tag.getBoolean("Source"); + } + + @Override //\\ SHStairsDown::getType(void) // 1397248836i64 + public String getType() { + return "SHSD"; + } + + @Override + protected void addAdditionalSaveData(CompoundTag tag) { + super.addAdditionalSaveData(tag); + tag.putBoolean("Source", this.isSource); + } + + @Override //\\ SHStairsDown::addChildren(StructurePiece *,std::vector>,std::allocator>>> &,Random &) + public void addChildren(StructurePiece piece, List pieces, NukkitRandom random) { + if (this.isSource) { + imposedPiece = FiveCrossing.class; + } + + this.generateSmallDoorChildForward((StartPiece) piece, pieces, random, 1, 1); + } + + //\\ SHStairsDown::createPiece(std::vector>,std::allocator>>> &,Random &,int,int,int,int,int) + public static StairsDown createPiece(List pieces, NukkitRandom random, int x, int y, int z, BlockFace orientation, int genDepth) { + BoundingBox boundingBox = BoundingBox.orientBox(x, y, z, -1, -7, 0, 5, 11, 5, orientation); + return isOkBox(boundingBox) && StructurePiece.findCollisionPiece(pieces, boundingBox) == null ? new StairsDown(genDepth, random, boundingBox, orientation) : null; + } + + @Override //\\ SHStairsDown::postProcess(BlockSource *,Random &,BoundingBox const &) + public boolean postProcess(ChunkManager level, NukkitRandom random, BoundingBox boundingBox, int chunkX, int chunkZ) { + this.generateBox(level, boundingBox, 0, 0, 0, 4, 10, 4, true, random, SMOOTH_STONE_SELECTOR); + this.generateSmallDoor(level, random, boundingBox, this.entryDoor, 1, 7, 0); + this.generateSmallDoor(level, random, boundingBox, SmallDoorType.OPENING, 1, 1, 4); + + this.placeBlock(level, STONE_BRICKS, 2, 6, 1, boundingBox); + this.placeBlock(level, STONE_BRICKS, 1, 5, 1, boundingBox); + this.placeBlock(level, STONE_BRICK_SLAB, 1, 6, 1, boundingBox); + this.placeBlock(level, STONE_BRICKS, 1, 5, 2, boundingBox); + this.placeBlock(level, STONE_BRICKS, 1, 4, 3, boundingBox); + this.placeBlock(level, STONE_BRICK_SLAB, 1, 5, 3, boundingBox); + this.placeBlock(level, STONE_BRICKS, 2, 4, 3, boundingBox); + this.placeBlock(level, STONE_BRICKS, 3, 3, 3, boundingBox); + this.placeBlock(level, STONE_BRICK_SLAB, 3, 4, 3, boundingBox); + this.placeBlock(level, STONE_BRICKS, 3, 3, 2, boundingBox); + this.placeBlock(level, STONE_BRICKS, 3, 2, 1, boundingBox); + this.placeBlock(level, STONE_BRICK_SLAB, 3, 3, 1, boundingBox); + this.placeBlock(level, STONE_BRICKS, 2, 2, 1, boundingBox); + this.placeBlock(level, STONE_BRICKS, 1, 1, 1, boundingBox); + this.placeBlock(level, STONE_BRICK_SLAB, 1, 2, 1, boundingBox); + this.placeBlock(level, STONE_BRICKS, 1, 1, 2, boundingBox); + this.placeBlock(level, STONE_BRICK_SLAB, 1, 1, 3, boundingBox); + return true; + } + } + + public static class StartPiece extends StairsDown { + + public PieceWeight previousPiece; + @Nullable + public PortalRoom portalRoomPiece; + public final List pendingChildren = Lists.newArrayList(); + + public StartPiece(NukkitRandom random, int x, int z) { + super(0, random, x, z); + } + } + + public static class Straight extends StrongholdPiece { + + private final boolean leftChild; + private final boolean rightChild; + + public Straight(int genDepth, NukkitRandom random, BoundingBox boundingBox, BlockFace orientation) { + super(genDepth); + this.setOrientation(orientation); + this.entryDoor = this.randomSmallDoor(random); + this.boundingBox = boundingBox; + this.leftChild = random.nextBoundedInt(2) == 0; + this.rightChild = random.nextBoundedInt(2) == 0; + } + + public Straight(CompoundTag tag) { + super(tag); + this.leftChild = tag.getBoolean("Left"); + this.rightChild = tag.getBoolean("Right"); + } + + @Override //\\ SHStraight::getType(void) // 1397248852i64 + public String getType() { + return "SHS"; + } + + @Override + protected void addAdditionalSaveData(CompoundTag tag) { + super.addAdditionalSaveData(tag); + tag.putBoolean("Left", this.leftChild); + tag.putBoolean("Right", this.rightChild); + } + + @Override + public void addChildren(StructurePiece piece, List pieces, NukkitRandom random) { + this.generateSmallDoorChildForward((StartPiece) piece, pieces, random, 1, 1); + + if (this.leftChild) { + this.generateSmallDoorChildLeft((StartPiece) piece, pieces, random, 1, 2); + } + if (this.rightChild) { + this.generateSmallDoorChildRight((StartPiece) piece, pieces, random, 1, 2); + } + } + + public static Straight createPiece(List pieces, NukkitRandom random, int x, int y, int z, BlockFace orientation, int genDepth) { + BoundingBox boundingBox = BoundingBox.orientBox(x, y, z, -1, -1, 0, 5, 5, 7, orientation); + return isOkBox(boundingBox) && StructurePiece.findCollisionPiece(pieces, boundingBox) == null ? new Straight(genDepth, random, boundingBox, orientation) : null; + } + + @Override + public boolean postProcess(ChunkManager level, NukkitRandom random, BoundingBox boundingBox, int chunkX, int chunkZ) { + this.generateBox(level, boundingBox, 0, 0, 0, 4, 4, 6, true, random, SMOOTH_STONE_SELECTOR); + this.generateSmallDoor(level, random, boundingBox, this.entryDoor, 1, 1, 0); + this.generateSmallDoor(level, random, boundingBox, StrongholdPiece.SmallDoorType.OPENING, 1, 1, 6); + + BlockState torchE = new BlockState(Block.TORCH, TorchFacingDirection.EAST); + BlockState torchW = new BlockState(Block.TORCH, TorchFacingDirection.WEST); + this.maybeGenerateBlock(level, boundingBox, random, 10, 1, 2, 1, torchE); + this.maybeGenerateBlock(level, boundingBox, random, 10, 3, 2, 1, torchW); + this.maybeGenerateBlock(level, boundingBox, random, 10, 1, 2, 5, torchE); + this.maybeGenerateBlock(level, boundingBox, random, 10, 3, 2, 5, torchW); + + if (this.leftChild) { + this.generateBox(level, boundingBox, 0, 1, 2, 0, 3, 4, BlockState.AIR, BlockState.AIR, false); + } + if (this.rightChild) { + this.generateBox(level, boundingBox, 4, 1, 2, 4, 3, 4, BlockState.AIR, BlockState.AIR, false); + } + + return true; + } + } + + public static class ChestCorridor extends StrongholdPiece { + + private boolean hasPlacedChest; + + public ChestCorridor(int genDepth, NukkitRandom random, BoundingBox boundingBox, BlockFace orientation) { + super(genDepth); + this.setOrientation(orientation); + this.entryDoor = this.randomSmallDoor(random); + this.boundingBox = boundingBox; + } + + public ChestCorridor(CompoundTag tag) { + super(tag); + this.hasPlacedChest = tag.getBoolean("Chest"); + } + + @Override //\\ SHChestCorridor::getType(void) // 1397244744i64 + public String getType() { + return "SHCC"; + } + + @Override + protected void addAdditionalSaveData(CompoundTag tag) { + super.addAdditionalSaveData(tag); + tag.putBoolean("Chest", this.hasPlacedChest); + } + + @Override + public void addChildren(StructurePiece piece, List pieces, NukkitRandom random) { + this.generateSmallDoorChildForward((StartPiece) piece, pieces, random, 1, 1); + } + + public static ChestCorridor createPiece(List pieces, NukkitRandom random, int x, int y, int z, BlockFace orientation, int genDepth) { + BoundingBox boundingBox = BoundingBox.orientBox(x, y, z, -1, -1, 0, 5, 5, 7, orientation); + return isOkBox(boundingBox) && StructurePiece.findCollisionPiece(pieces, boundingBox) == null ? new ChestCorridor(genDepth, random, boundingBox, orientation) : null; + } + + @Override + public boolean postProcess(ChunkManager level, NukkitRandom random, BoundingBox boundingBox, int chunkX, int chunkZ) { + this.generateBox(level, boundingBox, 0, 0, 0, 4, 4, 6, true, random, SMOOTH_STONE_SELECTOR); + this.generateSmallDoor(level, random, boundingBox, this.entryDoor, 1, 1, 0); + this.generateSmallDoor(level, random, boundingBox, StrongholdPiece.SmallDoorType.OPENING, 1, 1, 6); + this.generateBox(level, boundingBox, 3, 1, 2, 3, 1, 4, STONE_BRICKS, STONE_BRICKS, false); + + this.placeBlock(level, STONE_BRICK_SLAB, 3, 1, 1, boundingBox); + this.placeBlock(level, STONE_BRICK_SLAB, 3, 1, 5, boundingBox); + this.placeBlock(level, STONE_BRICK_SLAB, 3, 2, 2, boundingBox); + this.placeBlock(level, STONE_BRICK_SLAB, 3, 2, 4, boundingBox); + + for (int i = 2; i <= 4; ++i) { + this.placeBlock(level, STONE_BRICK_SLAB, 2, 1, i, boundingBox); + } + + if (!this.hasPlacedChest && boundingBox.isInside(new BlockVector3(this.getWorldX(3, 3), this.getWorldY(2), this.getWorldZ(3, 3)))) { + this.hasPlacedChest = true; + + BlockFace orientation = this.getOrientation(); + this.placeBlock(level, new BlockState(Block.CHEST, (orientation == null ? BlockFace.NORTH : orientation).getOpposite().getIndex()), 3, 2, 3, boundingBox); + + BlockVector3 vec = new BlockVector3(this.getWorldX(3, 3), this.getWorldY(2), this.getWorldZ(3, 3)); + if (boundingBox.isInside(vec)) { + BaseFullChunk chunk = level.getChunk(vec.x >> 4, vec.z >> 4); + if (chunk != null) { + CompoundTag nbt = BlockEntity.getDefaultCompound(vec.asVector3(), BlockEntity.CHEST); + ListTag itemList = new ListTag<>("Items"); + StrongholdCorridorChest.get().create(itemList, random); + nbt.putList(itemList); + Server.getInstance().getScheduler().scheduleTask(new BlockActorSpawnTask(chunk.getProvider().getLevel(), nbt)); + } + } + } + + return true; + } + } + + public static class StraightStairsDown extends StrongholdPiece { + + public StraightStairsDown(int genDepth, NukkitRandom random, BoundingBox boundingBox, BlockFace orientation) { + super(genDepth); + this.setOrientation(orientation); + this.entryDoor = this.randomSmallDoor(random); + this.boundingBox = boundingBox; + } + + public StraightStairsDown(CompoundTag tag) { + super(tag); + } + + @Override + public String getType() { + return "SHSSD"; + } + + @Override + public void addChildren(StructurePiece piece, List pieces, NukkitRandom random) { + this.generateSmallDoorChildForward((StartPiece) piece, pieces, random, 1, 1); + } + + public static StraightStairsDown createPiece(List pieces, NukkitRandom random, int x, int y, int z, BlockFace orientation, int genDepth) { + BoundingBox boundingBox = BoundingBox.orientBox(x, y, z, -1, -7, 0, 5, 11, 8, orientation); + return isOkBox(boundingBox) && StructurePiece.findCollisionPiece(pieces, boundingBox) == null ? new StraightStairsDown(genDepth, random, boundingBox, orientation) : null; + } + + @Override + public boolean postProcess(ChunkManager level, NukkitRandom random, BoundingBox boundingBox, int chunkX, int chunkZ) { + this.generateBox(level, boundingBox, 0, 0, 0, 4, 10, 7, true, random, SMOOTH_STONE_SELECTOR); + this.generateSmallDoor(level, random, boundingBox, this.entryDoor, 1, 7, 0); + this.generateSmallDoor(level, random, boundingBox, StrongholdPiece.SmallDoorType.OPENING, 1, 1, 7); + + BlockState stairsS = new BlockState(Block.COBBLESTONE_STAIRS, WeirdoDirection.SOUTH); + for (int i = 0; i < 6; ++i) { + this.placeBlock(level, stairsS, 1, 6 - i, 1 + i, boundingBox); + this.placeBlock(level, stairsS, 2, 6 - i, 1 + i, boundingBox); + this.placeBlock(level, stairsS, 3, 6 - i, 1 + i, boundingBox); + + if (i < 5) { + this.placeBlock(level, STONE_BRICKS, 1, 5 - i, 1 + i, boundingBox); + this.placeBlock(level, STONE_BRICKS, 2, 5 - i, 1 + i, boundingBox); + this.placeBlock(level, STONE_BRICKS, 3, 5 - i, 1 + i, boundingBox); + } + } + + return true; + } + } + + public abstract static class Turn extends StrongholdPiece { + + protected Turn(int genDepth) { + super(genDepth); + } + + public Turn(CompoundTag tag) { + super(tag); + } + } + + public static class LeftTurn extends Turn { + + public LeftTurn(int genDepth, NukkitRandom random, BoundingBox boundingBox, BlockFace orientation) { + super(genDepth); + this.setOrientation(orientation); + this.entryDoor = this.randomSmallDoor(random); + this.boundingBox = boundingBox; + } + + public LeftTurn(CompoundTag tag) { + super(tag); + } + + @Override //\\ SHLeftTurn::getType(void) // 1397247060i64 + public String getType() { + return "SHLT"; + } + + @Override + public void addChildren(StructurePiece piece, List pieces, NukkitRandom random) { + BlockFace orientation = this.getOrientation(); + if (orientation != BlockFace.NORTH && orientation != BlockFace.EAST) { + this.generateSmallDoorChildRight((StartPiece) piece, pieces, random, 1, 1); + } else { + this.generateSmallDoorChildLeft((StartPiece) piece, pieces, random, 1, 1); + } + } + + public static LeftTurn createPiece(List pieces, NukkitRandom random, int x, int y, int z, BlockFace orientation, int genDepth) { + BoundingBox boundingBox = BoundingBox.orientBox(x, y, z, -1, -1, 0, 5, 5, 5, orientation); + return isOkBox(boundingBox) && StructurePiece.findCollisionPiece(pieces, boundingBox) == null ? new LeftTurn(genDepth, random, boundingBox, orientation) : null; + } + + @Override + public boolean postProcess(ChunkManager level, NukkitRandom random, BoundingBox boundingBox, int chunkX, int chunkZ) { + this.generateBox(level, boundingBox, 0, 0, 0, 4, 4, 4, true, random, SMOOTH_STONE_SELECTOR); + this.generateSmallDoor(level, random, boundingBox, this.entryDoor, 1, 1, 0); + + BlockFace orientation = this.getOrientation(); + if (orientation != BlockFace.NORTH && orientation != BlockFace.EAST) { + this.generateBox(level, boundingBox, 4, 1, 1, 4, 3, 3, BlockState.AIR, BlockState.AIR, false); + } else { + this.generateBox(level, boundingBox, 0, 1, 1, 0, 3, 3, BlockState.AIR, BlockState.AIR, false); + } + + return true; + } + } + + public static class RightTurn extends Turn { + + public RightTurn(int i, NukkitRandom random, BoundingBox boundingBox, BlockFace orientation) { + super(i); + this.setOrientation(orientation); + this.entryDoor = this.randomSmallDoor(random); + this.boundingBox = boundingBox; + } + + public RightTurn(CompoundTag tag) { + super(tag); + } + + @Override //\\ SHRightTurn::getType(void) // 1397248596i64 + public String getType() { + return "SHRT"; + } + + @Override + public void addChildren(StructurePiece piece, List pieces, NukkitRandom random) { + BlockFace orientation = this.getOrientation(); + if (orientation != BlockFace.NORTH && orientation != BlockFace.EAST) { + this.generateSmallDoorChildLeft((StartPiece) piece, pieces, random, 1, 1); + } else { + this.generateSmallDoorChildRight((StartPiece) piece, pieces, random, 1, 1); + } + } + + public static RightTurn createPiece(List pieces, NukkitRandom random, int x, int y, int z, BlockFace orientation, int genDepth) { + BoundingBox boundingBox = BoundingBox.orientBox(x, y, z, -1, -1, 0, 5, 5, 5, orientation); + return isOkBox(boundingBox) && StructurePiece.findCollisionPiece(pieces, boundingBox) == null ? new RightTurn(genDepth, random, boundingBox, orientation) : null; + } + + @Override + public boolean postProcess(ChunkManager level, NukkitRandom random, BoundingBox boundingBox, int chunkX, int chunkZ) { + this.generateBox(level, boundingBox, 0, 0, 0, 4, 4, 4, true, random, SMOOTH_STONE_SELECTOR); + this.generateSmallDoor(level, random, boundingBox, this.entryDoor, 1, 1, 0); + + BlockFace orientation = this.getOrientation(); + if (orientation != BlockFace.NORTH && orientation != BlockFace.EAST) { + this.generateBox(level, boundingBox, 0, 1, 1, 0, 3, 3, BlockState.AIR, BlockState.AIR, false); + } else { + this.generateBox(level, boundingBox, 4, 1, 1, 4, 3, 3, BlockState.AIR, BlockState.AIR, false); + } + + return true; + } + } + + public static class RoomCrossing extends StrongholdPiece { + + protected final int type; + + public RoomCrossing(int genDepth, NukkitRandom random, BoundingBox boundingBox, BlockFace orientation) { + super(genDepth); + this.setOrientation(orientation); + this.entryDoor = this.randomSmallDoor(random); + this.boundingBox = boundingBox; + this.type = random.nextBoundedInt(5); + } + + public RoomCrossing(CompoundTag tag) { + super(tag); + this.type = tag.getInt("Type"); + } + + @Override //\\ SHRoomCrossing::getType(void) // 1397248579i64 + public String getType() { + return "SHRC"; + } + + @Override + protected void addAdditionalSaveData(CompoundTag tag) { + super.addAdditionalSaveData(tag); + tag.putInt("Type", this.type); + } + + @Override + public void addChildren(StructurePiece piece, List pieces, NukkitRandom random) { + this.generateSmallDoorChildForward((StartPiece) piece, pieces, random, 4, 1); + this.generateSmallDoorChildLeft((StartPiece) piece, pieces, random, 1, 4); + this.generateSmallDoorChildRight((StartPiece) piece, pieces, random, 1, 4); + } + + public static RoomCrossing createPiece(List pieces, NukkitRandom random, int x, int y, int z, BlockFace orientation, int genDepth) { + BoundingBox boundingBox = BoundingBox.orientBox(x, y, z, -4, -1, 0, 11, 7, 11, orientation); + return isOkBox(boundingBox) && StructurePiece.findCollisionPiece(pieces, boundingBox) == null ? new RoomCrossing(genDepth, random, boundingBox, orientation) : null; + } + + @Override + public boolean postProcess(ChunkManager level, NukkitRandom random, BoundingBox boundingBox, int chunkX, int chunkZ) { + this.generateBox(level, boundingBox, 0, 0, 0, 10, 6, 10, true, random, SMOOTH_STONE_SELECTOR); + this.generateSmallDoor(level, random, boundingBox, this.entryDoor, 4, 1, 0); + this.generateBox(level, boundingBox, 4, 1, 10, 6, 3, 10, BlockState.AIR, BlockState.AIR, false); + this.generateBox(level, boundingBox, 0, 1, 4, 0, 3, 6, BlockState.AIR, BlockState.AIR, false); + this.generateBox(level, boundingBox, 10, 1, 4, 10, 3, 6, BlockState.AIR, BlockState.AIR, false); + + switch (this.type) { + case 0: + this.placeBlock(level, STONE_BRICKS, 5, 1, 5, boundingBox); + this.placeBlock(level, STONE_BRICKS, 5, 2, 5, boundingBox); + this.placeBlock(level, STONE_BRICKS, 5, 3, 5, boundingBox); + + this.placeBlock(level, new BlockState(Block.TORCH, TorchFacingDirection.WEST), 4, 3, 5, boundingBox); + this.placeBlock(level, new BlockState(Block.TORCH, TorchFacingDirection.EAST), 6, 3, 5, boundingBox); + this.placeBlock(level, new BlockState(Block.TORCH, TorchFacingDirection.SOUTH), 5, 3, 4, boundingBox); + this.placeBlock(level, new BlockState(Block.TORCH, TorchFacingDirection.NORTH), 5, 3, 6, boundingBox); + + this.placeBlock(level, SMOOTH_STONE_SLAB, 4, 1, 4, boundingBox); + this.placeBlock(level, SMOOTH_STONE_SLAB, 4, 1, 5, boundingBox); + this.placeBlock(level, SMOOTH_STONE_SLAB, 4, 1, 6, boundingBox); + this.placeBlock(level, SMOOTH_STONE_SLAB, 6, 1, 4, boundingBox); + this.placeBlock(level, SMOOTH_STONE_SLAB, 6, 1, 5, boundingBox); + this.placeBlock(level, SMOOTH_STONE_SLAB, 6, 1, 6, boundingBox); + this.placeBlock(level, SMOOTH_STONE_SLAB, 5, 1, 4, boundingBox); + this.placeBlock(level, SMOOTH_STONE_SLAB, 5, 1, 6, boundingBox); + break; + case 1: + for (int i = 0; i < 5; ++i) { + this.placeBlock(level, STONE_BRICKS, 3, 1, 3 + i, boundingBox); + this.placeBlock(level, STONE_BRICKS, 7, 1, 3 + i, boundingBox); + this.placeBlock(level, STONE_BRICKS, 3 + i, 1, 3, boundingBox); + this.placeBlock(level, STONE_BRICKS, 3 + i, 1, 7, boundingBox); + } + + this.placeBlock(level, STONE_BRICKS, 5, 1, 5, boundingBox); + this.placeBlock(level, STONE_BRICKS, 5, 2, 5, boundingBox); + this.placeBlock(level, STONE_BRICKS, 5, 3, 5, boundingBox); + + this.placeBlock(level, WATER, 5, 4, 5, boundingBox); + + BlockState water1 = new BlockState(Block.STILL_WATER, 1); + this.placeBlock(level, water1, 6, 4, 5, boundingBox); + this.placeBlock(level, water1, 4, 4, 5, boundingBox); + this.placeBlock(level, water1, 5, 4, 6, boundingBox); + this.placeBlock(level, water1, 5, 4, 4, boundingBox); + this.placeBlock(level, water1, 6, 1, 4, boundingBox); + this.placeBlock(level, water1, 6, 1, 6, boundingBox); + this.placeBlock(level, water1, 4, 1, 4, boundingBox); + this.placeBlock(level, water1, 4, 1, 6, boundingBox); + + BlockState water9 = new BlockState(Block.STILL_WATER, 9); + for (int y = 1; y < 4; ++y) { + this.placeBlock(level, water9, 6, y, 5, boundingBox); + this.placeBlock(level, water9, 4, y, 5, boundingBox); + this.placeBlock(level, water9, 5, y, 6, boundingBox); + this.placeBlock(level, water9, 5, y, 4, boundingBox); + } + + break; + case 2: + for (int z = 1; z <= 9; ++z) { + this.placeBlock(level, COBBLESTONE, 1, 3, z, boundingBox); + this.placeBlock(level, COBBLESTONE, 9, 3, z, boundingBox); + } + for (int x = 1; x <= 9; ++x) { + this.placeBlock(level, COBBLESTONE, x, 3, 1, boundingBox); + this.placeBlock(level, COBBLESTONE, x, 3, 9, boundingBox); + } + + this.placeBlock(level, COBBLESTONE, 5, 1, 4, boundingBox); + this.placeBlock(level, COBBLESTONE, 5, 1, 6, boundingBox); + this.placeBlock(level, COBBLESTONE, 5, 3, 4, boundingBox); + this.placeBlock(level, COBBLESTONE, 5, 3, 6, boundingBox); + this.placeBlock(level, COBBLESTONE, 4, 1, 5, boundingBox); + this.placeBlock(level, COBBLESTONE, 6, 1, 5, boundingBox); + this.placeBlock(level, COBBLESTONE, 4, 3, 5, boundingBox); + this.placeBlock(level, COBBLESTONE, 6, 3, 5, boundingBox); + + for (int y = 1; y <= 3; ++y) { + this.placeBlock(level, COBBLESTONE, 4, y, 4, boundingBox); + this.placeBlock(level, COBBLESTONE, 6, y, 4, boundingBox); + this.placeBlock(level, COBBLESTONE, 4, y, 6, boundingBox); + this.placeBlock(level, COBBLESTONE, 6, y, 6, boundingBox); + } + + this.placeBlock(level, TORCH, 5, 3, 5, boundingBox); + + for (int z = 2; z <= 8; ++z) { + this.placeBlock(level, OAK_PLANKS, 2, 3, z, boundingBox); + this.placeBlock(level, OAK_PLANKS, 3, 3, z, boundingBox); + + if (z <= 3 || z >= 7) { + this.placeBlock(level, OAK_PLANKS, 4, 3, z, boundingBox); + this.placeBlock(level, OAK_PLANKS, 5, 3, z, boundingBox); + this.placeBlock(level, OAK_PLANKS, 6, 3, z, boundingBox); + } + + this.placeBlock(level, OAK_PLANKS, 7, 3, z, boundingBox); + this.placeBlock(level, OAK_PLANKS, 8, 3, z, boundingBox); + } + + BlockState ladderW = new BlockState(Block.LADDER, FacingDirection.WEST); + this.placeBlock(level, ladderW, 9, 1, 3, boundingBox); + this.placeBlock(level, ladderW, 9, 2, 3, boundingBox); + this.placeBlock(level, ladderW, 9, 3, 3, boundingBox); + + BlockFace orientation = this.getOrientation(); + this.placeBlock(level, new BlockState(Block.CHEST, (orientation == null ? BlockFace.NORTH : orientation).getOpposite().getIndex()), 3, 4, 8, boundingBox); + + BlockVector3 vec = new BlockVector3(this.getWorldX(3, 8), this.getWorldY(4), this.getWorldZ(3, 8)); + if (boundingBox.isInside(vec)) { + BaseFullChunk chunk = level.getChunk(vec.x >> 4, vec.z >> 4); + if (chunk != null) { + CompoundTag nbt = BlockEntity.getDefaultCompound(vec.asVector3(), BlockEntity.CHEST); + ListTag itemList = new ListTag<>("Items"); + StrongholdCrossingChest.get().create(itemList, random); + nbt.putList(itemList); + Server.getInstance().getScheduler().scheduleTask(new BlockActorSpawnTask(chunk.getProvider().getLevel(), nbt)); + } + } + } + + return true; + } + } + + public static class PrisonHall extends StrongholdPiece { + + public PrisonHall(int genDepth, NukkitRandom random, BoundingBox boundingBox, BlockFace orientation) { + super(genDepth); + this.setOrientation(orientation); + this.entryDoor = this.randomSmallDoor(random); + this.boundingBox = boundingBox; + } + + public PrisonHall(CompoundTag tag) { + super(tag); + } + + @Override //\\ SHPrisonHall::getType(void) // 1397248072i64 + public String getType() { + return "SHPH"; + } + + @Override + public void addChildren(StructurePiece piece, List pieces, NukkitRandom random) { + this.generateSmallDoorChildForward((StartPiece) piece, pieces, random, 1, 1); + } + + public static PrisonHall createPiece(List pieces, NukkitRandom random, int x, int y, int z, BlockFace orientation, int genDepth) { + BoundingBox boundingBox = BoundingBox.orientBox(x, y, z, -1, -1, 0, 9, 5, 11, orientation); + return isOkBox(boundingBox) && StructurePiece.findCollisionPiece(pieces, boundingBox) == null ? new PrisonHall(genDepth, random, boundingBox, orientation) : null; + } + + @Override + public boolean postProcess(ChunkManager level, NukkitRandom random, BoundingBox boundingBox, int chunkX, int chunkZ) { + this.generateBox(level, boundingBox, 0, 0, 0, 8, 4, 10, true, random, SMOOTH_STONE_SELECTOR); + this.generateSmallDoor(level, random, boundingBox, this.entryDoor, 1, 1, 0); + this.generateBox(level, boundingBox, 1, 1, 10, 3, 3, 10, BlockState.AIR, BlockState.AIR, false); + + this.generateBox(level, boundingBox, 4, 1, 1, 4, 3, 1, false, random, SMOOTH_STONE_SELECTOR); + this.generateBox(level, boundingBox, 4, 1, 3, 4, 3, 3, false, random, SMOOTH_STONE_SELECTOR); + this.generateBox(level, boundingBox, 4, 1, 7, 4, 3, 7, false, random, SMOOTH_STONE_SELECTOR); + this.generateBox(level, boundingBox, 4, 1, 9, 4, 3, 9, false, random, SMOOTH_STONE_SELECTOR); + + for (int y = 1; y <= 3; ++y) { + this.placeBlock(level, IRON_BARS, 4, y, 4, boundingBox); + this.placeBlock(level, IRON_BARS, 4, y, 5, boundingBox); + this.placeBlock(level, IRON_BARS, 4, y, 6, boundingBox); + this.placeBlock(level, IRON_BARS, 5, y, 5, boundingBox); + this.placeBlock(level, IRON_BARS, 6, y, 5, boundingBox); + this.placeBlock(level, IRON_BARS, 7, y, 5, boundingBox); + } + + this.placeBlock(level, IRON_BARS, 4, 3, 2, boundingBox); + this.placeBlock(level, IRON_BARS, 4, 3, 8, boundingBox); + + BlockState doorW = new BlockState(Block.IRON_DOOR_BLOCK, Direction.WEST); + BlockState doorU = new BlockState(Block.IRON_DOOR_BLOCK, UpperBlockBit.UPPER); + this.placeBlock(level, doorW, 4, 1, 2, boundingBox); + this.placeBlock(level, doorU, 4, 2, 2, boundingBox); + this.placeBlock(level, doorW, 4, 1, 8, boundingBox); + this.placeBlock(level, doorU, 4, 2, 8, boundingBox); + + return true; + } + } + + public static class Library extends StrongholdPiece { + + private final boolean isTall; + + public Library(int genDepth, NukkitRandom random, BoundingBox boundingBox, BlockFace orientation) { + super(genDepth); + this.setOrientation(orientation); + this.entryDoor = this.randomSmallDoor(random); + this.boundingBox = boundingBox; + this.isTall = boundingBox.getYSpan() > 6; + } + + public Library(CompoundTag tag) { + super(tag); + this.isTall = tag.getBoolean("Tall"); + } + + @Override //\\ SHLibrary::getType(void) // 1397247049i64 + public String getType() { + return "SHLi"; + } + + @Override + protected void addAdditionalSaveData(CompoundTag tag) { + super.addAdditionalSaveData(tag); + tag.putBoolean("Tall", this.isTall); + } + + public static Library createPiece(List pieces, NukkitRandom random, int x, int y, int z, BlockFace orientation, int genDepth) { + BoundingBox boundingBox = BoundingBox.orientBox(x, y, z, -4, -1, 0, 14, 11, 15, orientation); + if (!isOkBox(boundingBox) || StructurePiece.findCollisionPiece(pieces, boundingBox) != null) { + boundingBox = BoundingBox.orientBox(x, y, z, -4, -1, 0, 14, 6, 15, orientation); + if (!isOkBox(boundingBox) || StructurePiece.findCollisionPiece(pieces, boundingBox) != null) { + return null; + } + } + + return new Library(genDepth, random, boundingBox, orientation); + } + + @Override + public boolean postProcess(ChunkManager level, NukkitRandom random, BoundingBox boundingBox, int chunkX, int chunkZ) { + int height = 11; + if (!this.isTall) { + height = 6; + } + + this.generateBox(level, boundingBox, 0, 0, 0, 13, height - 1, 14, true, random, SMOOTH_STONE_SELECTOR); + this.generateSmallDoor(level, random, boundingBox, this.entryDoor, 4, 1, 0); + this.generateMaybeBox(level, boundingBox, random, 7, 2, 1, 1, 11, 4, 13, COBWEB, COBWEB, false, false); + + for (int z = 1; z <= 13; ++z) { + if ((z - 1) % 4 == 0) { + this.generateBox(level, boundingBox, 1, 1, z, 1, 4, z, OAK_PLANKS, OAK_PLANKS, false); + this.generateBox(level, boundingBox, 12, 1, z, 12, 4, z, OAK_PLANKS, OAK_PLANKS, false); + this.placeBlock(level, new BlockState(Block.TORCH, TorchFacingDirection.EAST), 2, 3, z, boundingBox); + this.placeBlock(level, new BlockState(Block.TORCH, TorchFacingDirection.WEST), 11, 3, z, boundingBox); + if (this.isTall) { + this.generateBox(level, boundingBox, 1, 6, z, 1, 9, z, OAK_PLANKS, OAK_PLANKS, false); + this.generateBox(level, boundingBox, 12, 6, z, 12, 9, z, OAK_PLANKS, OAK_PLANKS, false); + } + } else { + this.generateBox(level, boundingBox, 1, 1, z, 1, 4, z, BOOKSHELF, BOOKSHELF, false); + this.generateBox(level, boundingBox, 12, 1, z, 12, 4, z, BOOKSHELF, BOOKSHELF, false); + if (this.isTall) { + this.generateBox(level, boundingBox, 1, 6, z, 1, 9, z, BOOKSHELF, BOOKSHELF, false); + this.generateBox(level, boundingBox, 12, 6, z, 12, 9, z, BOOKSHELF, BOOKSHELF, false); + } + } + } + + for (int z = 3; z < 12; z += 2) { + this.generateBox(level, boundingBox, 3, 1, z, 4, 3, z, BOOKSHELF, BOOKSHELF, false); + this.generateBox(level, boundingBox, 6, 1, z, 7, 3, z, BOOKSHELF, BOOKSHELF, false); + this.generateBox(level, boundingBox, 9, 1, z, 10, 3, z, BOOKSHELF, BOOKSHELF, false); + } + + if (this.isTall) { + this.generateBox(level, boundingBox, 1, 5, 1, 3, 5, 13, OAK_PLANKS, OAK_PLANKS, false); + this.generateBox(level, boundingBox, 10, 5, 1, 12, 5, 13, OAK_PLANKS, OAK_PLANKS, false); + this.generateBox(level, boundingBox, 4, 5, 1, 9, 5, 2, OAK_PLANKS, OAK_PLANKS, false); + this.generateBox(level, boundingBox, 4, 5, 12, 9, 5, 13, OAK_PLANKS, OAK_PLANKS, false); + this.placeBlock(level, OAK_PLANKS, 9, 5, 11, boundingBox); + this.placeBlock(level, OAK_PLANKS, 8, 5, 11, boundingBox); + this.placeBlock(level, OAK_PLANKS, 9, 5, 10, boundingBox); + this.generateBox(level, boundingBox, 3, 6, 3, 3, 6, 11, OAK_FENCE, OAK_FENCE, false); + this.generateBox(level, boundingBox, 10, 6, 3, 10, 6, 9, OAK_FENCE, OAK_FENCE, false); + this.generateBox(level, boundingBox, 4, 6, 2, 9, 6, 2, OAK_FENCE, OAK_FENCE, false); + this.generateBox(level, boundingBox, 4, 6, 12, 7, 6, 12, OAK_FENCE, OAK_FENCE, false); + this.placeBlock(level, OAK_FENCE, 3, 6, 2, boundingBox); + this.placeBlock(level, OAK_FENCE, 3, 6, 12, boundingBox); + this.placeBlock(level, OAK_FENCE, 10, 6, 2, boundingBox); + + for (int i = 0; i <= 2; ++i) { + this.placeBlock(level, OAK_FENCE, 8 + i, 6, 12 - i, boundingBox); + if (i != 2) { + this.placeBlock(level, OAK_FENCE, 8 + i, 6, 11 - i, boundingBox); + } + } + + BlockState ladderS = new BlockState(Block.LADDER, FacingDirection.SOUTH); + this.placeBlock(level, ladderS, 10, 1, 13, boundingBox); + this.placeBlock(level, ladderS, 10, 2, 13, boundingBox); + this.placeBlock(level, ladderS, 10, 3, 13, boundingBox); + this.placeBlock(level, ladderS, 10, 4, 13, boundingBox); + this.placeBlock(level, ladderS, 10, 5, 13, boundingBox); + this.placeBlock(level, ladderS, 10, 6, 13, boundingBox); + this.placeBlock(level, ladderS, 10, 7, 13, boundingBox); + + this.placeBlock(level, OAK_FENCE, 6, 9, 7, boundingBox); + this.placeBlock(level, OAK_FENCE, 7, 9, 7, boundingBox); + this.placeBlock(level, OAK_FENCE, 6, 8, 7, boundingBox); + this.placeBlock(level, OAK_FENCE, 7, 8, 7, boundingBox); + this.placeBlock(level, OAK_FENCE, 6, 7, 7, boundingBox); + this.placeBlock(level, OAK_FENCE, 7, 7, 7, boundingBox); + this.placeBlock(level, OAK_FENCE, 5, 7, 7, boundingBox); + this.placeBlock(level, OAK_FENCE, 8, 7, 7, boundingBox); + this.placeBlock(level, OAK_FENCE, 6, 7, 6, boundingBox); + this.placeBlock(level, OAK_FENCE, 6, 7, 8, boundingBox); + this.placeBlock(level, OAK_FENCE, 7, 7, 6, boundingBox); + this.placeBlock(level, OAK_FENCE, 7, 7, 8, boundingBox); + + this.placeBlock(level, TORCH, 5, 8, 7, boundingBox); + this.placeBlock(level, TORCH, 8, 8, 7, boundingBox); + this.placeBlock(level, TORCH, 6, 8, 6, boundingBox); + this.placeBlock(level, TORCH, 6, 8, 8, boundingBox); + this.placeBlock(level, TORCH, 7, 8, 6, boundingBox); + this.placeBlock(level, TORCH, 7, 8, 8, boundingBox); + } + + BlockFace orientation = this.getOrientation(); + BlockState chest = new BlockState(Block.CHEST, (orientation == null ? BlockFace.NORTH : orientation).getOpposite().getIndex()); + this.placeBlock(level, chest, 3, 3, 5, boundingBox); + + BlockVector3 vec = new BlockVector3(this.getWorldX(3, 5), this.getWorldY(3), this.getWorldZ(3, 5)); + if (boundingBox.isInside(vec)) { + BaseFullChunk chunk = level.getChunk(vec.x >> 4, vec.z >> 4); + if (chunk != null) { + CompoundTag nbt = BlockEntity.getDefaultCompound(vec.asVector3(), BlockEntity.CHEST); + ListTag itemList = new ListTag<>("Items"); + StrongholdLibraryChest.get().create(itemList, random); + nbt.putList(itemList); + Server.getInstance().getScheduler().scheduleTask(new BlockActorSpawnTask(chunk.getProvider().getLevel(), nbt)); + } + } + + if (this.isTall) { + this.placeBlock(level, BlockState.AIR, 12, 9, 1, boundingBox); + this.placeBlock(level, chest, 12, 8, 1, boundingBox); + + vec.setComponents(this.getWorldX(12, 1), this.getWorldY(8), this.getWorldZ(12, 1)); + if (boundingBox.isInside(vec)) { + BaseFullChunk chunk = level.getChunk(vec.x >> 4, vec.z >> 4); + if (chunk != null) { + CompoundTag nbt = BlockEntity.getDefaultCompound(vec.asVector3(), BlockEntity.CHEST); + ListTag itemList = new ListTag<>("Items"); + StrongholdLibraryChest.get().create(itemList, random); + nbt.putList(itemList); + Server.getInstance().getScheduler().scheduleTask(new BlockActorSpawnTask(chunk.getProvider().getLevel(), nbt)); + } + } + } + + return true; + } + } + + public static class FiveCrossing extends StrongholdPiece { + + private final boolean leftLow; + private final boolean leftHigh; + private final boolean rightLow; + private final boolean rightHigh; + + public FiveCrossing(int genDepth, NukkitRandom random, BoundingBox boundingBox, BlockFace orientation) { + super(genDepth); + this.setOrientation(orientation); + this.entryDoor = this.randomSmallDoor(random); + this.boundingBox = boundingBox; + this.leftLow = random.nextBoolean(); + this.leftHigh = random.nextBoolean(); + this.rightLow = random.nextBoolean(); + this.rightHigh = random.nextBoundedInt(3) > 0; + } + + public FiveCrossing(CompoundTag tag) { + super(tag); + this.leftLow = tag.getBoolean("leftLow"); + this.leftHigh = tag.getBoolean("leftHigh"); + this.rightLow = tag.getBoolean("rightLow"); + this.rightHigh = tag.getBoolean("rightHigh"); + } + + @Override //\\ SHFiveCrossing::getType(void) // 1397241155i64 + public String getType() { + return "SH5C"; + } + + @Override + protected void addAdditionalSaveData(CompoundTag tag) { + super.addAdditionalSaveData(tag); + tag.putBoolean("leftLow", this.leftLow); + tag.putBoolean("leftHigh", this.leftHigh); + tag.putBoolean("rightLow", this.rightLow); + tag.putBoolean("rightHigh", this.rightHigh); + } + + @Override + public void addChildren(StructurePiece piece, List pieces, NukkitRandom random) { + int lowX = 3; + int highX = 5; + + BlockFace orientation = this.getOrientation(); + if (orientation == BlockFace.WEST || orientation == BlockFace.NORTH) { + lowX = 8 - lowX; + highX = 8 - highX; + } + + this.generateSmallDoorChildForward((StartPiece) piece, pieces, random, 5, 1); + + if (this.leftLow) { + this.generateSmallDoorChildLeft((StartPiece) piece, pieces, random, lowX, 1); + } + if (this.leftHigh) { + this.generateSmallDoorChildLeft((StartPiece) piece, pieces, random, highX, 7); + } + if (this.rightLow) { + this.generateSmallDoorChildRight((StartPiece) piece, pieces, random, lowX, 1); + } + if (this.rightHigh) { + this.generateSmallDoorChildRight((StartPiece) piece, pieces, random, highX, 7); + } + } + + public static FiveCrossing createPiece(List pieces, NukkitRandom random, int x, int y, int z, BlockFace orientation, int genDepth) { + BoundingBox boundingBox = BoundingBox.orientBox(x, y, z, -4, -3, 0, 10, 9, 11, orientation); + return isOkBox(boundingBox) && StructurePiece.findCollisionPiece(pieces, boundingBox) == null ? new FiveCrossing(genDepth, random, boundingBox, orientation) : null; + } + + @Override + public boolean postProcess(ChunkManager level, NukkitRandom random, BoundingBox boundingBox, int chunkX, int chunkZ) { + this.generateBox(level, boundingBox, 0, 0, 0, 9, 8, 10, true, random, SMOOTH_STONE_SELECTOR); + this.generateSmallDoor(level, random, boundingBox, this.entryDoor, 4, 3, 0); + + if (this.leftLow) { + this.generateBox(level, boundingBox, 0, 3, 1, 0, 5, 3, BlockState.AIR, BlockState.AIR, false); + } + if (this.rightLow) { + this.generateBox(level, boundingBox, 9, 3, 1, 9, 5, 3, BlockState.AIR, BlockState.AIR, false); + } + if (this.leftHigh) { + this.generateBox(level, boundingBox, 0, 5, 7, 0, 7, 9, BlockState.AIR, BlockState.AIR, false); + } + if (this.rightHigh) { + this.generateBox(level, boundingBox, 9, 5, 7, 9, 7, 9, BlockState.AIR, BlockState.AIR, false); + } + + this.generateBox(level, boundingBox, 5, 1, 10, 7, 3, 10, BlockState.AIR, BlockState.AIR, false); + + this.generateBox(level, boundingBox, 1, 2, 1, 8, 2, 6, false, random, SMOOTH_STONE_SELECTOR); + this.generateBox(level, boundingBox, 4, 1, 5, 4, 4, 9, false, random, SMOOTH_STONE_SELECTOR); + this.generateBox(level, boundingBox, 8, 1, 5, 8, 4, 9, false, random, SMOOTH_STONE_SELECTOR); + this.generateBox(level, boundingBox, 1, 4, 7, 3, 4, 9, false, random, SMOOTH_STONE_SELECTOR); + this.generateBox(level, boundingBox, 1, 3, 5, 3, 3, 6, false, random, SMOOTH_STONE_SELECTOR); + this.generateBox(level, boundingBox, 1, 3, 4, 3, 3, 4, STONE_BRICK_SLAB, STONE_BRICK_SLAB, false); + this.generateBox(level, boundingBox, 1, 4, 6, 3, 4, 6, STONE_BRICK_SLAB, STONE_BRICK_SLAB, false); + this.generateBox(level, boundingBox, 5, 1, 7, 7, 1, 8, false, random, SMOOTH_STONE_SELECTOR); + this.generateBox(level, boundingBox, 5, 1, 9, 7, 1, 9, STONE_BRICK_SLAB, STONE_BRICK_SLAB, false); + this.generateBox(level, boundingBox, 5, 2, 7, 7, 2, 7, STONE_BRICK_SLAB, STONE_BRICK_SLAB, false); + this.generateBox(level, boundingBox, 4, 5, 7, 4, 5, 9, STONE_BRICK_SLAB, STONE_BRICK_SLAB, false); + this.generateBox(level, boundingBox, 8, 5, 7, 8, 5, 9, STONE_BRICK_SLAB, STONE_BRICK_SLAB, false); + this.generateBox(level, boundingBox, 5, 5, 7, 7, 5, 9, SMOOTH_STONE_SLAB_DOUBLE, SMOOTH_STONE_SLAB_DOUBLE, false); + + this.placeBlock(level, new BlockState(Block.TORCH, TorchFacingDirection.SOUTH), 6, 5, 6, boundingBox); + + return true; + } + } + + public static class PortalRoom extends StrongholdPiece { + + private boolean hasPlacedSpawner; + + public PortalRoom(int genDepth, BoundingBox boundingBox, BlockFace orientation) { + super(genDepth); + this.setOrientation(orientation); + this.boundingBox = boundingBox; + } + + public PortalRoom(CompoundTag tag) { + super(tag); + this.hasPlacedSpawner = tag.getBoolean("Mob"); + } + + @Override + public String getType() { + return "SHPR"; + } + + @Override + protected void addAdditionalSaveData(CompoundTag tag) { + super.addAdditionalSaveData(tag); + tag.putBoolean("Mob", this.hasPlacedSpawner); + } + + @Override + public void addChildren(StructurePiece piece, List pieces, NukkitRandom random) { + if (piece != null) { + ((StartPiece) piece).portalRoomPiece = this; + } + } + + public static PortalRoom createPiece(List pieces, int x, int y, int z, BlockFace orientation, int genDepth) { + BoundingBox boundingBox = BoundingBox.orientBox(x, y, z, -4, -1, 0, 11, 8, 16, orientation); + return isOkBox(boundingBox) && StructurePiece.findCollisionPiece(pieces, boundingBox) == null ? new PortalRoom(genDepth, boundingBox, orientation) : null; + } + + @Override + public boolean postProcess(ChunkManager level, NukkitRandom random, BoundingBox boundingBox, int chunkX, int chunkZ) { + this.generateBox(level, boundingBox, 0, 0, 0, 10, 7, 15, false, random, SMOOTH_STONE_SELECTOR); + this.generateSmallDoor(level, random, boundingBox, StrongholdPiece.SmallDoorType.GRATES, 4, 1, 0); + + int y = 6; + this.generateBox(level, boundingBox, 1, y, 1, 1, y, 14, false, random, SMOOTH_STONE_SELECTOR); + this.generateBox(level, boundingBox, 9, y, 1, 9, y, 14, false, random, SMOOTH_STONE_SELECTOR); + this.generateBox(level, boundingBox, 2, y, 1, 8, y, 2, false, random, SMOOTH_STONE_SELECTOR); + this.generateBox(level, boundingBox, 2, y, 14, 8, y, 14, false, random, SMOOTH_STONE_SELECTOR); + this.generateBox(level, boundingBox, 1, 1, 1, 2, 1, 4, false, random, SMOOTH_STONE_SELECTOR); + this.generateBox(level, boundingBox, 8, 1, 1, 9, 1, 4, false, random, SMOOTH_STONE_SELECTOR); + this.generateBox(level, boundingBox, 1, 1, 1, 1, 1, 3, LAVA, LAVA, false); + this.generateBox(level, boundingBox, 9, 1, 1, 9, 1, 3, LAVA, LAVA, false); + this.generateBox(level, boundingBox, 3, 1, 8, 7, 1, 12, false, random, SMOOTH_STONE_SELECTOR); + this.generateBox(level, boundingBox, 4, 1, 9, 6, 1, 11, LAVA, LAVA, false); + + for (int z = 3; z < 14; z += 2) { + this.generateBox(level, boundingBox, 0, 3, z, 0, 4, z, IRON_BARS, IRON_BARS, false); + this.generateBox(level, boundingBox, 10, 3, z, 10, 4, z, IRON_BARS, IRON_BARS, false); + } + for (int x = 2; x < 9; x += 2) { + this.generateBox(level, boundingBox, x, 3, 15, x, 4, 15, IRON_BARS, IRON_BARS, false); + } + + this.generateBox(level, boundingBox, 4, 1, 5, 6, 1, 7, false, random, SMOOTH_STONE_SELECTOR); + this.generateBox(level, boundingBox, 4, 2, 6, 6, 2, 7, false, random, SMOOTH_STONE_SELECTOR); + this.generateBox(level, boundingBox, 4, 3, 7, 6, 3, 7, false, random, SMOOTH_STONE_SELECTOR); + + BlockState stairsN = new BlockState(Block.STONE_BRICK_STAIRS, WeirdoDirection.NORTH); + for (int x = 4; x <= 6; ++x) { + this.placeBlock(level, stairsN, x, 1, 4, boundingBox); + this.placeBlock(level, stairsN, x, 2, 5, boundingBox); + this.placeBlock(level, stairsN, x, 3, 6, boundingBox); + } + + BlockState frameN = new BlockState(Block.END_PORTAL_FRAME, Direction.NORTH); + BlockState frameS = new BlockState(Block.END_PORTAL_FRAME, Direction.SOUTH); + BlockState frameE = new BlockState(Block.END_PORTAL_FRAME, Direction.EAST); + BlockState frameW = new BlockState(Block.END_PORTAL_FRAME, Direction.WEST); + + boolean actived = true; + boolean[] hasEye = new boolean[12]; + + for (int i = 0; i < hasEye.length; ++i) { + hasEye[i] = random.nextBoundedInt(100) > 90; + actived &= hasEye[i]; + } + + this.placeBlock(level, hasEye[0] ? new BlockState(Block.END_PORTAL_FRAME, frameN.getMeta() | EndPortalEyeBit.HAS_EYE) : frameN, 4, 3, 8, boundingBox); + this.placeBlock(level, hasEye[1] ? new BlockState(Block.END_PORTAL_FRAME, frameN.getMeta() | EndPortalEyeBit.HAS_EYE) : frameN, 5, 3, 8, boundingBox); + this.placeBlock(level, hasEye[2] ? new BlockState(Block.END_PORTAL_FRAME, frameN.getMeta() | EndPortalEyeBit.HAS_EYE) : frameN, 6, 3, 8, boundingBox); + this.placeBlock(level, hasEye[3] ? new BlockState(Block.END_PORTAL_FRAME, frameS.getMeta() | EndPortalEyeBit.HAS_EYE) : frameS, 4, 3, 12, boundingBox); + this.placeBlock(level, hasEye[4] ? new BlockState(Block.END_PORTAL_FRAME, frameS.getMeta() | EndPortalEyeBit.HAS_EYE) : frameS, 5, 3, 12, boundingBox); + this.placeBlock(level, hasEye[5] ? new BlockState(Block.END_PORTAL_FRAME, frameS.getMeta() | EndPortalEyeBit.HAS_EYE) : frameS, 6, 3, 12, boundingBox); + this.placeBlock(level, hasEye[6] ? new BlockState(Block.END_PORTAL_FRAME, frameE.getMeta() | EndPortalEyeBit.HAS_EYE) : frameE, 3, 3, 9, boundingBox); + this.placeBlock(level, hasEye[7] ? new BlockState(Block.END_PORTAL_FRAME, frameE.getMeta() | EndPortalEyeBit.HAS_EYE) : frameE, 3, 3, 10, boundingBox); + this.placeBlock(level, hasEye[8] ? new BlockState(Block.END_PORTAL_FRAME, frameE.getMeta() | EndPortalEyeBit.HAS_EYE) : frameE, 3, 3, 11, boundingBox); + this.placeBlock(level, hasEye[9] ? new BlockState(Block.END_PORTAL_FRAME, frameW.getMeta() | EndPortalEyeBit.HAS_EYE) : frameW, 7, 3, 9, boundingBox); + this.placeBlock(level, hasEye[10] ? new BlockState(Block.END_PORTAL_FRAME, frameW.getMeta() | EndPortalEyeBit.HAS_EYE) : frameW, 7, 3, 10, boundingBox); + this.placeBlock(level, hasEye[11] ? new BlockState(Block.END_PORTAL_FRAME, frameW.getMeta() | EndPortalEyeBit.HAS_EYE) : frameW, 7, 3, 11, boundingBox); + + if (actived) { + this.placeBlock(level, END_PORTAL, 4, 3, 9, boundingBox); + this.placeBlock(level, END_PORTAL, 5, 3, 9, boundingBox); + this.placeBlock(level, END_PORTAL, 6, 3, 9, boundingBox); + this.placeBlock(level, END_PORTAL, 4, 3, 10, boundingBox); + this.placeBlock(level, END_PORTAL, 5, 3, 10, boundingBox); + this.placeBlock(level, END_PORTAL, 6, 3, 10, boundingBox); + this.placeBlock(level, END_PORTAL, 4, 3, 11, boundingBox); + this.placeBlock(level, END_PORTAL, 5, 3, 11, boundingBox); + this.placeBlock(level, END_PORTAL, 6, 3, 11, boundingBox); + } + + if (!this.hasPlacedSpawner) { + BlockVector3 vec = new BlockVector3(this.getWorldX(5, 6), this.getWorldY(3), this.getWorldZ(5, 6)); + if (boundingBox.isInside(vec)) { + this.hasPlacedSpawner = true; + level.setBlockAt(vec.x, vec.y, vec.z, SPAWNER.getId(), SPAWNER.getMeta()); + + BaseFullChunk chunk = level.getChunk(vec.x >> 4, vec.z >> 4); + if (chunk != null) { + Server.getInstance().getScheduler().scheduleTask(new BlockActorSpawnTask(chunk.getProvider().getLevel(), + BlockEntity.getDefaultCompound(vec.asVector3(), BlockEntity.MOB_SPAWNER) + .putInt("EntityId", EntitySilverfish.NETWORK_ID))); + } + } + } + + return true; + } + } + + static class SmoothStoneSelector extends StructurePiece.BlockSelector { + + public void next(NukkitRandom random, int x, int y, int z, boolean hasNext) { + if (hasNext) { + int chance = random.nextBoundedInt(100); + if (chance < 20) { + this.next = CRACKED_STONE_BRICKS; + } else if (chance < 50) { + this.next = MOSSY_STONE_BRICKS; + } else if (chance < 55) { + this.next = INFESTED_STONE_BRICKS; + } else { + this.next = STONE_BRICKS; + } + } else { + this.next = BlockState.AIR; + } + } + } + + public static void init() { + //NOOP + } +} diff --git a/src/main/java/cn/wode490390/nukkit/shpop/structure/StructurePiece.java b/src/main/java/cn/wode490390/nukkit/shpop/structure/StructurePiece.java new file mode 100644 index 0000000..e1c845f --- /dev/null +++ b/src/main/java/cn/wode490390/nukkit/shpop/structure/StructurePiece.java @@ -0,0 +1,365 @@ +package cn.wode490390.nukkit.shpop.structure; + +import cn.nukkit.block.Block; +import cn.nukkit.level.ChunkManager; +import cn.nukkit.level.format.generic.BaseFullChunk; +import cn.nukkit.math.BlockFace; +import cn.nukkit.math.BlockVector3; +import cn.nukkit.math.NukkitRandom; +import cn.nukkit.nbt.tag.CompoundTag; +import cn.wode490390.nukkit.shpop.block.state.BlockState; +import cn.wode490390.nukkit.shpop.block.state.FacingDirection; +import cn.wode490390.nukkit.shpop.block.state.TorchFacingDirection; +import cn.wode490390.nukkit.shpop.math.BoundingBox; +import cn.wode490390.nukkit.shpop.math.Rotation; + +import javax.annotation.Nullable; +import java.util.Iterator; +import java.util.List; + +public abstract class StructurePiece { + + protected ChunkManager level; + protected BoundingBox boundingBox; + @Nullable + private BlockFace orientation; + private Rotation rotation = Rotation.NONE; + protected int genDepth; + + protected StructurePiece(int genDepth) { + this.genDepth = genDepth; + } + + public StructurePiece(CompoundTag tag) { + this(tag.getInt("GD")); + if (tag.contains("BB")) { + this.boundingBox = new BoundingBox(tag.getIntArray("BB")); + } + int orientation = tag.getInt("O"); + this.setOrientation(orientation == -1 ? null : BlockFace.fromHorizontalIndex(orientation)); + } + + public final CompoundTag createTag() { + CompoundTag tag = new CompoundTag(); + tag.putString("id", this.getType()); + tag.put("BB", this.boundingBox.createTag()); + BlockFace orientation = this.getOrientation(); + tag.putInt("O", orientation == null ? -1 : orientation.getHorizontalIndex()); + tag.putInt("GD", this.genDepth); + this.addAdditionalSaveData(tag); + return tag; + } + + protected abstract void addAdditionalSaveData(CompoundTag tag); + + public void addChildren(StructurePiece piece, List pieces, NukkitRandom random) { + //NOOP + } + + public abstract boolean postProcess(ChunkManager level, NukkitRandom random, BoundingBox boundingBox, int chunkX, int chunkZ); + + public BoundingBox getBoundingBox() { + return this.boundingBox; + } + + public int getGenDepth() { + return this.genDepth; + } + + public static StructurePiece findCollisionPiece(List pieces, BoundingBox boundingBox) { + Iterator iterator = pieces.iterator(); + + StructurePiece piece; + do { + if (!iterator.hasNext()) { + return null; + } + + piece = iterator.next(); + } while (piece.getBoundingBox() == null || !piece.getBoundingBox().intersects(boundingBox)); + + return piece; + } + + protected int getWorldX(int x, int z) { + BlockFace orientation = this.getOrientation(); + if (orientation == null) { + return x; + } else { + switch (orientation) { + case NORTH: + case SOUTH: + return this.boundingBox.x0 + x; + case WEST: + return this.boundingBox.x1 - z; + case EAST: + return this.boundingBox.x0 + z; + default: + return x; + } + } + } + + protected int getWorldY(int y) { + return this.getOrientation() == null ? y : y + this.boundingBox.y0; + } + + protected int getWorldZ(int x, int z) { + BlockFace orientation = this.getOrientation(); + if (orientation == null) { + return z; + } else { + switch (orientation) { + case NORTH: + return this.boundingBox.z1 - z; + case SOUTH: + return this.boundingBox.z0 + z; + case WEST: + case EAST: + return this.boundingBox.z0 + x; + default: + return z; + } + } + } + + protected void placeBlock(ChunkManager level, BlockState block, int x, int y, int z, BoundingBox boundingBox) { + BlockVector3 vec = new BlockVector3(this.getWorldX(x, z), this.getWorldY(y), this.getWorldZ(x, z)); + if (boundingBox.isInside(vec)) { + if (this.rotation != Rotation.NONE) { + switch (block.getId()) { + case Block.TORCH: + if (this.rotation == Rotation.CLOCKWISE_90) { + block = block.rotate(this.rotation); + } else if (this.rotation == Rotation.CLOCKWISE_180) { + switch (block.getMeta()) { + case TorchFacingDirection.EAST: + case TorchFacingDirection.WEST: + break; + case TorchFacingDirection.SOUTH: + case TorchFacingDirection.NORTH: + block = block.rotate(this.rotation); + break; + } + } else if (this.rotation == Rotation.COUNTERCLOCKWISE_90) { + switch (block.getMeta()) { + case TorchFacingDirection.EAST: + case TorchFacingDirection.WEST: + block = block.rotate(Rotation.CLOCKWISE_90); + break; + case TorchFacingDirection.SOUTH: + case TorchFacingDirection.NORTH: + block = block.rotate(this.rotation); + break; + } + } + break; + case Block.LADDER: + if (this.rotation == Rotation.CLOCKWISE_90) { + block = block.rotate(this.rotation); + } else if (this.rotation == Rotation.CLOCKWISE_180) { + switch (block.getMeta()) { + case FacingDirection.EAST: + case FacingDirection.WEST: + break; + case FacingDirection.SOUTH: + case FacingDirection.NORTH: + block = block.rotate(this.rotation); + break; + } + } else if (this.rotation == Rotation.COUNTERCLOCKWISE_90) { + switch (block.getMeta()) { + case FacingDirection.EAST: + case FacingDirection.WEST: + block = block.rotate(Rotation.CLOCKWISE_90); + break; + case FacingDirection.SOUTH: + case FacingDirection.NORTH: + block = block.rotate(this.rotation); + break; + } + } + break; + default: + block = block.rotate(this.rotation); + break; + } + } + + level.setBlockAt(vec.x, vec.y, vec.z, block.getId(), block.getMeta()); + } + } + + protected BlockState getBlock(ChunkManager level, int x, int y, int z, BoundingBox boundingBox) { + BlockVector3 vec = new BlockVector3(this.getWorldX(x, z), this.getWorldY(y), this.getWorldZ(x, z)); + return !boundingBox.isInside(vec) ? BlockState.AIR : new BlockState(level.getBlockIdAt(vec.x, vec.y, vec.z), level.getBlockDataAt(vec.x, vec.y, vec.z)); + } + + protected boolean isInterior(ChunkManager level, int x, int y, int z, BoundingBox boundingBox) { + int worldX = this.getWorldX(x, z); + int worldY = this.getWorldY(y + 1); + int worldZ = this.getWorldZ(x, z); + if (!boundingBox.isInside(new BlockVector3(worldX, worldY, worldZ))) { + return false; + } else { + BaseFullChunk chunk = level.getChunk(worldX >> 4, worldZ >> 4); + if (chunk == null) { + return false; + } + return worldY < chunk.getHighestBlockAt(worldX & 0xf, worldZ & 0xf); + } + } + + protected void generateAirBox(ChunkManager level, BoundingBox boundingBox, int x1, int y1, int z1, int x2, int y2, int z2) { + for (int y = y1; y <= y2; ++y) { + for (int x = x1; x <= x2; ++x) { + for (int z = z1; z <= z2; ++z) { + this.placeBlock(level, BlockState.AIR, x, y, z, boundingBox); + } + } + } + } + + protected void generateBox(ChunkManager level, BoundingBox boundingBox, int x1, int y1, int z1, int x2, int y2, int z2, BlockState outsideBlock, BlockState insideBlock, boolean skipAir) { + for (int y = y1; y <= y2; ++y) { + for (int x = x1; x <= x2; ++x) { + for (int z = z1; z <= z2; ++z) { + if (!skipAir || !this.getBlock(level, x, y, z, boundingBox).equals(BlockState.AIR)) { + if (y != y1 && y != y2 && x != x1 && x != x2 && z != z1 && z != z2) { + this.placeBlock(level, insideBlock, x, y, z, boundingBox); + } else { + this.placeBlock(level, outsideBlock, x, y, z, boundingBox); + } + } + } + } + } + } + + protected void generateBox(ChunkManager level, BoundingBox boundingBox, int x1, int y1, int z1, int x2, int y2, int z2, boolean skipAir, NukkitRandom random, StructurePiece.BlockSelector selector) { + for (int y = y1; y <= y2; ++y) { + for (int x = x1; x <= x2; ++x) { + for (int z = z1; z <= z2; ++z) { + if (!skipAir || !this.getBlock(level, x, y, z, boundingBox).equals(BlockState.AIR)) { + selector.next(random, x, y, z, y == y1 || y == y2 || x == x1 || x == x2 || z == z1 || z == z2); + this.placeBlock(level, selector.getNext(), x, y, z, boundingBox); + } + } + } + } + } + + protected void generateMaybeBox(ChunkManager level, BoundingBox boundingBox, NukkitRandom random, int prob, int x1, int y1, int z1, int x2, int y2, int z2, BlockState outsideBlock, BlockState insideBlock, boolean skipAir, boolean checkInterior) { + for (int y = y1; y <= y2; ++y) { + for (int x = x1; x <= x2; ++x) { + for (int z = z1; z <= z2; ++z) { + if (random.nextBoundedInt(100) <= prob && (!skipAir || !this.getBlock(level, x, y, z, boundingBox).equals(BlockState.AIR)) && (!checkInterior || this.isInterior(level, x, y, z, boundingBox))) { + if (y != y1 && y != y2 && x != x1 && x != x2 && z != z1 && z != z2) { + this.placeBlock(level, insideBlock, x, y, z, boundingBox); + } else { + this.placeBlock(level, outsideBlock, x, y, z, boundingBox); + } + } + } + } + } + } + + protected void maybeGenerateBlock(ChunkManager level, BoundingBox boundingBox, NukkitRandom random, int prob, int x, int y, int z, BlockState block) { + if (random.nextBoundedInt(100) < prob) { + this.placeBlock(level, block, x, y, z, boundingBox); + } + } + + protected void generateUpperHalfSphere(ChunkManager level, BoundingBox boundingBox, int x1, int y1, int z1, int x2, int y2, int z2, BlockState block, boolean skipAir) { + float xLen = x2 - x1 + 1; + float yLen = y2 - y1 + 1; + float zLen = z2 - z1 + 1; + float xHalf = x1 + xLen / 2f; + float zHalf = z1 + zLen / 2f; + + for (int y = y1; y <= y2; ++y) { + float dy = (float) (y - y1) / yLen; + for (int x = x1; x <= x2; ++x) { + float dx = ((float) x - xHalf) / (xLen * .5f); + for (int z = z1; z <= z2; ++z) { + float dz = ((float) z - zHalf) / (zLen * .5f); + if (!skipAir || !this.getBlock(level, x, y, z, boundingBox).equals(BlockState.AIR)) { + float d = dx * dx + dy * dy + dz * dz; + if (d <= 1.05f) { + this.placeBlock(level, block, x, y, z, boundingBox); + } + } + } + } + } + } + + protected void fillColumnDown(ChunkManager level, BlockState block, int x, int y, int z, BoundingBox boundingBox) { + int worldX = this.getWorldX(x, z); + int worldY = this.getWorldY(y); + int worldZ = this.getWorldZ(x, z); + if (boundingBox.isInside(new BlockVector3(worldX, worldY, worldZ))) { + BaseFullChunk chunk = level.getChunk(worldX >> 4, worldZ >> 4); + int cx = worldX & 0xf; + int cz = worldZ & 0xf; + int blockId = chunk.getBlockId(cx, worldY, cz); + while ((blockId == Block.AIR || blockId == Block.WATER || blockId == Block.STILL_WATER || blockId == Block.LAVA || blockId == Block.STILL_LAVA) && worldY > 1) { + chunk.setBlock(cx, worldY, cz, block.getId(), block.getMeta()); + blockId = chunk.getBlockId(cx, --worldY, cz); + } + } + } + + public void move(int x, int y, int z) { + this.boundingBox.move(x, y, z); + } + + @Nullable + public BlockFace getOrientation() { + return this.orientation; + } + + public void setOrientation(@Nullable BlockFace orientation) { + this.orientation = orientation; + if (orientation == null) { + this.rotation = Rotation.NONE; + } else { + switch (orientation) { + case SOUTH: + this.rotation = Rotation.CLOCKWISE_180; + break; + case WEST: + this.rotation = Rotation.COUNTERCLOCKWISE_90; + break; + case EAST: + this.rotation = Rotation.CLOCKWISE_90; + break; + case NORTH: + default: + this.rotation = Rotation.NONE; + } + } + } + + public Rotation getRotation() { + return this.rotation; + } + + public abstract String getType(); + + public abstract static class BlockSelector { + + protected BlockState next; + + protected BlockSelector() { + this.next = BlockState.AIR; + } + + public abstract void next(NukkitRandom random, int x, int y, int z, boolean hasNext); + + public BlockState getNext() { + return this.next; + } + } +} diff --git a/src/main/java/cn/wode490390/nukkit/shpop/structure/StructureStart.java b/src/main/java/cn/wode490390/nukkit/shpop/structure/StructureStart.java new file mode 100644 index 0000000..a7b500e --- /dev/null +++ b/src/main/java/cn/wode490390/nukkit/shpop/structure/StructureStart.java @@ -0,0 +1,115 @@ +package cn.wode490390.nukkit.shpop.structure; + +import cn.nukkit.level.ChunkManager; +import cn.nukkit.math.NukkitRandom; +import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.nbt.tag.ListTag; +import cn.wode490390.nukkit.shpop.math.BoundingBox; +import com.google.common.collect.Lists; + +import java.util.List; + +public abstract class StructureStart { + + protected final ChunkManager level; + protected final List pieces = Lists.newArrayList(); + protected BoundingBox boundingBox; + private final int chunkX; + private final int chunkZ; + protected final NukkitRandom random; + + public StructureStart(ChunkManager level, int chunkX, int chunkZ) { + this.level = level; + this.chunkX = chunkX; + this.chunkZ = chunkZ; + this.random = new NukkitRandom(level.getSeed()); + this.random.setSeed(chunkX * this.random.nextInt() ^ chunkZ * this.random.nextInt() ^ level.getSeed()); + this.boundingBox = BoundingBox.getUnknownBox(); + } + + public abstract void generatePieces(ChunkManager level, int chunkX, int chunkZ); + + public BoundingBox getBoundingBox() { + return this.boundingBox; + } + + public List getPieces() { + return this.pieces; + } + + public void postProcess(ChunkManager level, NukkitRandom random, BoundingBox boundingBox, int chunkX, int chunkZ) { + synchronized (this.pieces) { + this.pieces.removeIf(piece -> piece.getBoundingBox().intersects(boundingBox) && !piece.postProcess(level, random, boundingBox, chunkX, chunkZ)); + this.calculateBoundingBox(); + } + } + + protected void calculateBoundingBox() { + this.boundingBox = BoundingBox.getUnknownBox(); + for (StructurePiece piece : this.pieces) { + this.boundingBox.expand(piece.getBoundingBox()); + } + } + + public final CompoundTag createTag() { + CompoundTag tag = new CompoundTag() + .putString("id", this.getType()) + .putInt("ChunkX", this.chunkX) + .putInt("ChunkZ", this.chunkZ) + .put("BB", this.boundingBox.createTag()); + + ListTag children = new ListTag<>("Children"); + for (StructurePiece piece : this.pieces) { + children.add(piece.createTag()); + } + tag.putList(children); + + return tag; + } + + protected void moveBelowSeaLevel(int max, NukkitRandom random, int min) { + int range = max - min; + int y = this.boundingBox.getYSpan() + 1; + if (y < range) { + y += random.nextBoundedInt(range - y); + } + + int offset = y - this.boundingBox.y1; + this.boundingBox.move(0, offset, 0); + + for (StructurePiece piece : this.pieces) { + piece.move(0, offset, 0); + } + } + + protected void moveInsideHeights(NukkitRandom random, int min, int max) { + int range = max - min + 1 - this.boundingBox.getYSpan(); + int y; + if (range > 1) { + y = min + random.nextBoundedInt(range); + } else { + y = min; + } + + int offset = y - this.boundingBox.y0; + this.boundingBox.move(0, offset, 0); + + for (StructurePiece piece : this.pieces) { + piece.move(0, offset, 0); + } + } + + public boolean isValid() { + return !this.pieces.isEmpty(); + } + + public int getChunkX() { + return this.chunkX; + } + + public int getChunkZ() { + return this.chunkZ; + } + + public abstract String getType(); +} diff --git a/src/main/java/cn/wode490390/nukkit/shpop/util/MetricsLite.java b/src/main/java/cn/wode490390/nukkit/shpop/util/MetricsLite.java new file mode 100644 index 0000000..b46a48a --- /dev/null +++ b/src/main/java/cn/wode490390/nukkit/shpop/util/MetricsLite.java @@ -0,0 +1,370 @@ +package cn.wode490390.nukkit.shpop.util; + +import cn.nukkit.Server; +import cn.nukkit.plugin.Plugin; +import cn.nukkit.plugin.service.NKServiceManager; +import cn.nukkit.plugin.service.RegisteredServiceProvider; +import cn.nukkit.plugin.service.ServicePriority; +import cn.nukkit.utils.Config; +import com.google.common.base.Preconditions; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import javax.net.ssl.HttpsURLConnection; +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Modifier; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; +import java.util.UUID; +import java.util.zip.GZIPOutputStream; + +/** + * bStats collects some data for plugin authors. + *

+ * Check out https://bStats.org/ to learn more about bStats! + */ +@SuppressWarnings({"WeakerAccess", "unused"}) +public class MetricsLite { + + static { + // You can use the property to disable the check in your test environment + if (System.getProperty("bstats.relocatecheck") == null || !System.getProperty("bstats.relocatecheck").equals("false")) { + // Maven's Relocate is clever and changes strings, too. So we have to use this little "trick" ... :D + final String defaultPackage = new String(new byte[]{'o', 'r', 'g', '.', 'b', 's', 't', 'a', 't', 's', '.', 'n', 'u', 'k', 'k', 'i', 't'}); + final String examplePackage = new String(new byte[]{'y', 'o', 'u', 'r', '.', 'p', 'a', 'c', 'k', 'a', 'g', 'e'}); + // We want to make sure nobody just copy & pastes the example and use the wrong package names + if (MetricsLite.class.getPackage().getName().equals(defaultPackage) || MetricsLite.class.getPackage().getName().equals(examplePackage)) { + throw new IllegalStateException("bStats Metrics class has not been relocated correctly!"); + } + } + } + + // The version of this bStats class + public static final int B_STATS_VERSION = 1; + + // The url to which the data is sent + private static final String URL = "https://bStats.org/submitData/bukkit"; + + // Is bStats enabled on this server? + private boolean enabled; + + // Should failed requests be logged? + private static boolean logFailedRequests; + + // Should the sent data be logged? + private static boolean logSentData; + + // Should the response text be logged? + private static boolean logResponseStatusText; + + // The uuid of the server + private static String serverUUID; + + // The plugin + private final Plugin plugin; + + // The plugin id + private final int pluginId; + + /** + * Class constructor. + * + * @param plugin The plugin which stats should be submitted. + * @param pluginId The id of the plugin. + * It can be found at What is my plugin id? + */ + public MetricsLite(Plugin plugin, int pluginId) { + Preconditions.checkNotNull(plugin); + this.plugin = plugin; + this.pluginId = pluginId; + + // Get the config file + File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats"); + File configFile = new File(bStatsFolder, "config.yml"); + Config config = new Config(configFile); + + // Check the config + LinkedHashMap map = (LinkedHashMap) config.getAll(); + // Every server gets it's unique random id. + if (!config.isString("serverUuid")) { + map.put("serverUuid", UUID.randomUUID().toString()); + } else { + try { + // Check the UUID + UUID.fromString(config.getString("serverUuid")); + } catch (Exception ignored){ + map.put("serverUuid", UUID.randomUUID().toString()); + } + } + // Add default values + if (!config.isBoolean("enabled")) { + map.put("enabled", true); + } + // Should failed request be logged? + if (!config.isBoolean("logFailedRequests")) { + map.put("logFailedRequests", false); + } + // Should the sent data be logged? + if (!config.isBoolean("logSentData")) { + map.put("logSentData", false); + } + // Should the response text be logged? + if (!config.isBoolean("logResponseStatusText")) { + map.put("logResponseStatusText", false); + } + config.setAll(map); + config.save(); + + // Load the data + enabled = config.getBoolean("enabled", true); + serverUUID = config.getString("serverUuid"); + logFailedRequests = config.getBoolean("logFailedRequests", false); + logSentData = config.getBoolean("logSentData", false); + logResponseStatusText = config.getBoolean("logResponseStatusText", false); + + if (enabled) { + boolean found = false; + // Search for all other bStats Metrics classes to see if we are the first one + for (Class service : Server.getInstance().getServiceManager().getKnownService()) { + try { + service.getField("B_STATS_VERSION"); // Our identifier :) + found = true; // We aren't the first + break; + } catch (NoSuchFieldException ignored) { } + } + // Register our service + Server.getInstance().getServiceManager().register(MetricsLite.class, this, plugin, ServicePriority.NORMAL); + if (!found) { + // We are the first! + startSubmitting(); + } + } + } + + /** + * Checks if bStats is enabled. + * + * @return Whether bStats is enabled or not. + */ + public boolean isEnabled() { + return enabled; + } + + /** + * Starts the Scheduler which submits our data every 30 minutes. + */ + private void startSubmitting() { + final Timer timer = new Timer(true); // We use a timer cause want to be independent from the server tps + timer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + if (!plugin.isEnabled()) { // Plugin was disabled + timer.cancel(); + return; + } + // Nevertheless we want our code to run in the Nukkit main thread, so we have to use the Nukkit scheduler + // Don't be afraid! The connection to the bStats server is still async, only the stats collection is sync ;) + Server.getInstance().getScheduler().scheduleTask(plugin, () -> submitData()); + } + }, 1000 * 60 * 5, 1000 * 60 * 30); + // Submit the data every 30 minutes, first time after 5 minutes to give other plugins enough time to start + // WARNING: Changing the frequency has no effect but your plugin WILL be blocked/deleted! + // WARNING: Just don't do it! + } + + /** + * Gets the plugin specific data. + * This method is called using Reflection. + * + * @return The plugin specific data. + */ + public JsonObject getPluginData() { + JsonObject data = new JsonObject(); + + String pluginName = plugin.getName(); + String pluginVersion = plugin.getDescription().getVersion(); + + data.addProperty("pluginName", pluginName); // Append the name of the plugin + data.addProperty("id", pluginId); // Append the id of the plugin + data.addProperty("pluginVersion", pluginVersion); // Append the version of the plugin + + JsonArray customCharts = new JsonArray(); + data.add("customCharts", customCharts); + + return data; + } + + /** + * Gets the server specific data. + * + * @return The server specific data. + */ + private JsonObject getServerData() { + // Minecraft specific data + int playerAmount = Server.getInstance().getOnlinePlayers().size(); + int onlineMode = Server.getInstance().getPropertyBoolean("xbox-auth", false) ? 1 : 0; + String minecraftVersion = Server.getInstance().getVersion(); + String softwareName = Server.getInstance().getName(); + + // OS/Java specific data + String javaVersion = System.getProperty("java.version"); + String osName = System.getProperty("os.name"); + String osArch = System.getProperty("os.arch"); + String osVersion = System.getProperty("os.version"); + int coreCount = Runtime.getRuntime().availableProcessors(); + + JsonObject data = new JsonObject(); + + data.addProperty("serverUUID", serverUUID); + + data.addProperty("playerAmount", playerAmount); + data.addProperty("onlineMode", onlineMode); + data.addProperty("bukkitVersion", minecraftVersion); + data.addProperty("bukkitName", softwareName); + + data.addProperty("javaVersion", javaVersion); + data.addProperty("osName", osName); + data.addProperty("osArch", osArch); + data.addProperty("osVersion", osVersion); + data.addProperty("coreCount", coreCount); + + return data; + } + + /** + * Collects the data and sends it afterwards. + */ + @SuppressWarnings("unchecked") + private void submitData() { + final JsonObject data = getServerData(); + + JsonArray pluginData = new JsonArray(); + // Search for all other bStats Metrics classes to get their plugin data + Server.getInstance().getServiceManager().getKnownService().forEach((service) -> { + try { + service.getField("B_STATS_VERSION"); // Our identifier :) + + List> providers = null; + try { + Field field = Field.class.getDeclaredField("modifiers"); + field.setAccessible(true); + Field handle = NKServiceManager.class.getDeclaredField("handle"); + field.setInt(handle, handle.getModifiers() & ~Modifier.FINAL); + handle.setAccessible(true); + providers = ((Map, List>>) handle.get((NKServiceManager) (Server.getInstance().getServiceManager()))).get(service); + } catch(IllegalAccessException | IllegalArgumentException | SecurityException e) { + // Something went wrong! :( + if (logFailedRequests) { + plugin.getLogger().warning("Failed to link to metrics class " + service.getName(), e); + } + } + + if (providers != null) { + for (RegisteredServiceProvider provider : providers) { + try { + Object plugin = provider.getService().getMethod("getPluginData").invoke(provider.getProvider()); + if (plugin instanceof JsonObject) { + pluginData.add((JsonElement) plugin); + } + } catch (SecurityException | NoSuchMethodException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ignored) { } + } + } + } catch (NoSuchFieldException ignored) { } + }); + + data.add("plugins", pluginData); + + // Create a new thread for the connection to the bStats server + new Thread(() -> { + try { + // Send the data + sendData(plugin, data); + } catch (Exception e) { + // Something went wrong! :( + if (logFailedRequests) { + plugin.getLogger().warning("Could not submit plugin stats of " + plugin.getName(), e); + } + } + }).start(); + } + + /** + * Sends the data to the bStats server. + * + * @param plugin Any plugin. It's just used to get a logger instance. + * @param data The data to send. + * @throws Exception If the request failed. + */ + private static void sendData(Plugin plugin, JsonObject data) throws Exception { + Preconditions.checkNotNull(data); + if (Server.getInstance().isPrimaryThread()) { + throw new IllegalAccessException("This method must not be called from the main thread!"); + } + if (logSentData) { + plugin.getLogger().info("Sending data to bStats: " + data); + } + HttpsURLConnection connection = (HttpsURLConnection) new URL(URL).openConnection(); + + // Compress the data to save bandwidth + byte[] compressedData = compress(data.toString()); + + // Add headers + connection.setRequestMethod("POST"); + connection.addRequestProperty("Accept", "application/json"); + connection.addRequestProperty("Connection", "close"); + connection.addRequestProperty("Content-Encoding", "gzip"); // We gzip our request + connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length)); + connection.setRequestProperty("Content-Type", "application/json"); // We send our data in JSON format + connection.setRequestProperty("User-Agent", "MC-Server/" + B_STATS_VERSION); + + // Send data + connection.setDoOutput(true); + try (DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream())) { + outputStream.write(compressedData); + } + + StringBuilder builder = new StringBuilder(); + try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) { + String line; + while ((line = bufferedReader.readLine()) != null) { + builder.append(line); + } + } + + if (logResponseStatusText) { + plugin.getLogger().info("Sent data to bStats and received response: " + builder); + } + } + + /** + * Gzips the given String. + * + * @param str The string to gzip. + * @return The gzipped String. + * @throws IOException If the compression failed. + */ + private static byte[] compress(final String str) throws IOException { + if (str == null) { + return null; + } + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + try (GZIPOutputStream gzip = new GZIPOutputStream(outputStream)) { + gzip.write(str.getBytes(StandardCharsets.UTF_8)); + } + return outputStream.toByteArray(); + } + +} diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 0000000..6176330 --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1,9 @@ +main: cn.wode490390.nukkit.shpop.StrongholdPlugin +name: "StrongholdPopulator" +description: "This is a plugin that implements the stronghold feature for Nukkit servers" +author: "wode490390" +website: "http://wode490390.cn/" +version: "${pom.version}" +api: ["1.0.0"] +load: STARTUP +softdepend: ["MobPlugin"]