diff --git a/.idea/vcs.xml b/.idea/vcs.xml
index 50b07f269..35eb1ddfb 100644
--- a/.idea/vcs.xml
+++ b/.idea/vcs.xml
@@ -2,6 +2,5 @@
-
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index 61c8d8f05..9b385dde5 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -33,7 +33,7 @@ dependencies {
compileOnly("org.spigotmc:spigot-api:1.16.2-R0.1-SNAPSHOT")
compileOnly("org.jetbrains:annotations:20.1.0") // more recent.
implementation("commons-io:commons-io:2.4")
- implementation(name = "Gaea-1.13.0", group = "")
+ compileOnly(name = "Gaea-1.13.0", group = "")
implementation("org.apache.commons:commons-imaging:1.0-alpha2")
compileOnly("com.sk89q.worldedit:worldedit-bukkit:7.2.0-SNAPSHOT")
implementation("org.bstats:bstats-bukkit:1.7")
@@ -86,12 +86,6 @@ val setupServer = tasks.create("setupServer") {
File("${testDir}/eula.txt").writeText("eula=true")
- // Copy Terra into dir
- copy {
- from("${buildDir}/libs/Terra-${versionObj}.jar")
- into("${testDir}/plugins/")
- }
-
// clean up
file("WorldGenTestServer").deleteRecursively()
}
@@ -99,6 +93,15 @@ val setupServer = tasks.create("setupServer") {
val testWithPaper = task(name = "testWithPaper") {
dependsOn(setupServer)
+ //dependsOn(tasks.shadowJar)
+ // Copy Terra into dir
+ doFirst {
+ copy {
+ from("${buildDir}/libs/Terra-${versionObj}.jar")
+ into("${testDir}/plugins/")
+ }
+ }
+
main = "io.papermc.paperclip.Paperclip"
jvmArgs = listOf("-XX:+UseG1GC", "-XX:+ParallelRefProcEnabled", "-XX:MaxGCPauseMillis=200",
"-XX:+UnlockExperimentalVMOptions", "-XX:+DisableExplicitGC", "-XX:+AlwaysPreTouch",
diff --git a/src/main/java/com/dfsek/terra/EventListener.java b/src/main/java/com/dfsek/terra/EventListener.java
index 7dabbc01b..df801638a 100644
--- a/src/main/java/com/dfsek/terra/EventListener.java
+++ b/src/main/java/com/dfsek/terra/EventListener.java
@@ -1,7 +1,7 @@
package com.dfsek.terra;
import com.dfsek.terra.async.AsyncStructureFinder;
-import com.dfsek.terra.config.genconfig.StructureConfig;
+import com.dfsek.terra.config.genconfig.structure.StructureConfig;
import com.dfsek.terra.util.StructureTypeEnum;
import org.bukkit.entity.EnderSignal;
import org.bukkit.entity.Entity;
diff --git a/src/main/java/com/dfsek/terra/async/AsyncStructureFinder.java b/src/main/java/com/dfsek/terra/async/AsyncStructureFinder.java
index 1421156cd..5ef704374 100644
--- a/src/main/java/com/dfsek/terra/async/AsyncStructureFinder.java
+++ b/src/main/java/com/dfsek/terra/async/AsyncStructureFinder.java
@@ -4,7 +4,7 @@ import com.dfsek.terra.Terra;
import com.dfsek.terra.TerraWorld;
import com.dfsek.terra.biome.TerraBiomeGrid;
import com.dfsek.terra.biome.UserDefinedBiome;
-import com.dfsek.terra.config.genconfig.StructureConfig;
+import com.dfsek.terra.config.genconfig.structure.StructureConfig;
import com.dfsek.terra.structure.Structure;
import org.bukkit.Bukkit;
import org.bukkit.Location;
diff --git a/src/main/java/com/dfsek/terra/command/biome/BiomeInfoCommand.java b/src/main/java/com/dfsek/terra/command/biome/BiomeInfoCommand.java
index c6416f825..685e6fd0c 100644
--- a/src/main/java/com/dfsek/terra/command/biome/BiomeInfoCommand.java
+++ b/src/main/java/com/dfsek/terra/command/biome/BiomeInfoCommand.java
@@ -3,7 +3,7 @@ package com.dfsek.terra.command.biome;
import com.dfsek.terra.TerraWorld;
import com.dfsek.terra.biome.UserDefinedBiome;
import com.dfsek.terra.config.base.ConfigPack;
-import com.dfsek.terra.config.genconfig.StructureConfig;
+import com.dfsek.terra.config.genconfig.structure.StructureConfig;
import com.dfsek.terra.config.genconfig.biome.BiomeConfig;
import com.dfsek.terra.config.genconfig.biome.BiomeSnowConfig;
import com.dfsek.terra.config.lang.LangUtil;
diff --git a/src/main/java/com/dfsek/terra/command/structure/LocateCommand.java b/src/main/java/com/dfsek/terra/command/structure/LocateCommand.java
index 2aefbad67..561abeef4 100644
--- a/src/main/java/com/dfsek/terra/command/structure/LocateCommand.java
+++ b/src/main/java/com/dfsek/terra/command/structure/LocateCommand.java
@@ -3,7 +3,7 @@ package com.dfsek.terra.command.structure;
import com.dfsek.terra.Terra;
import com.dfsek.terra.TerraWorld;
import com.dfsek.terra.async.AsyncStructureFinder;
-import com.dfsek.terra.config.genconfig.StructureConfig;
+import com.dfsek.terra.config.genconfig.structure.StructureConfig;
import com.dfsek.terra.config.lang.LangUtil;
import com.dfsek.terra.generation.TerraChunkGenerator;
import org.bukkit.Bukkit;
@@ -12,7 +12,6 @@ import org.bukkit.World;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
-import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import org.polydev.gaea.command.WorldCommand;
diff --git a/src/main/java/com/dfsek/terra/config/base/ConfigPack.java b/src/main/java/com/dfsek/terra/config/base/ConfigPack.java
index 7e92cf092..64b6ae3a2 100644
--- a/src/main/java/com/dfsek/terra/config/base/ConfigPack.java
+++ b/src/main/java/com/dfsek/terra/config/base/ConfigPack.java
@@ -11,7 +11,7 @@ import com.dfsek.terra.config.genconfig.CarverConfig;
import com.dfsek.terra.config.genconfig.FloraConfig;
import com.dfsek.terra.config.genconfig.OreConfig;
import com.dfsek.terra.config.genconfig.PaletteConfig;
-import com.dfsek.terra.config.genconfig.StructureConfig;
+import com.dfsek.terra.config.genconfig.structure.StructureConfig;
import com.dfsek.terra.config.genconfig.TreeConfig;
import com.dfsek.terra.config.genconfig.biome.AbstractBiomeConfig;
import com.dfsek.terra.config.genconfig.biome.BiomeConfig;
diff --git a/src/main/java/com/dfsek/terra/config/genconfig/biome/BiomeConfig.java b/src/main/java/com/dfsek/terra/config/genconfig/biome/BiomeConfig.java
index 662455443..c932628f0 100644
--- a/src/main/java/com/dfsek/terra/config/genconfig/biome/BiomeConfig.java
+++ b/src/main/java/com/dfsek/terra/config/genconfig/biome/BiomeConfig.java
@@ -7,7 +7,7 @@ import com.dfsek.terra.config.TerraConfig;
import com.dfsek.terra.config.base.ConfigPack;
import com.dfsek.terra.config.exception.ConfigException;
import com.dfsek.terra.config.exception.NotFoundException;
-import com.dfsek.terra.config.genconfig.StructureConfig;
+import com.dfsek.terra.config.genconfig.structure.StructureConfig;
import com.dfsek.terra.generation.UserDefinedDecorator;
import com.dfsek.terra.generation.UserDefinedGenerator;
import org.bukkit.configuration.InvalidConfigurationException;
diff --git a/src/main/java/com/dfsek/terra/config/genconfig/structure/EntityFeatureConfig.java b/src/main/java/com/dfsek/terra/config/genconfig/structure/EntityFeatureConfig.java
new file mode 100644
index 000000000..27078743e
--- /dev/null
+++ b/src/main/java/com/dfsek/terra/config/genconfig/structure/EntityFeatureConfig.java
@@ -0,0 +1,59 @@
+package com.dfsek.terra.config.genconfig.structure;
+
+import com.dfsek.terra.Debug;
+import com.dfsek.terra.config.base.ConfigUtil;
+import com.dfsek.terra.config.exception.ConfigException;
+import com.dfsek.terra.structure.features.EntityFeature;
+import com.dfsek.terra.structure.features.Feature;
+import org.bukkit.Material;
+import org.bukkit.configuration.InvalidConfigurationException;
+import org.bukkit.entity.EntityType;
+import org.polydev.gaea.math.Range;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class EntityFeatureConfig implements FeatureConfig {
+ private final EntityFeature feature;
+
+ @SuppressWarnings("unchecked")
+ public EntityFeatureConfig(Map items) throws InvalidConfigurationException {
+ if(! items.containsKey("entity")) throw new ConfigException("No EntityType specified!", "EntityFeature");
+ if(! items.containsKey("amount")) throw new ConfigException("No amount specified!", "EntityFeature");
+ if(! items.containsKey("attempts")) throw new ConfigException("Attempts not specified!", "EntityFeature");
+ if(! items.containsKey("in-height")) throw new ConfigException("Spawn Checking Height not specified!", "EntityFeature");
+ if(! items.containsKey("spawnable-on")) throw new ConfigException("No Spawnable-on materials specified!", "EntityFeature");
+ if(! items.containsKey("spawnable-in")) throw new ConfigException("No Spawnable-in materials specified!", "EntityFeature");
+
+ EntityType type;
+ try {
+ type = EntityType.valueOf((String) items.get("entity"));
+ } catch(IllegalArgumentException e) {
+ throw new InvalidConfigurationException("No such EntityType: " + items.get("entity"));
+ } catch(ClassCastException e) {
+ throw new InvalidConfigurationException("Error in Entity Configuration!");
+ }
+
+ int attempts = (Integer) items.get("attempts");
+ int height = (Integer) items.get("in-height");
+
+ Range amount;
+ try {
+ Map amountMap = (Map) items.get("amount");
+ amount = new Range(amountMap.get("min"), amountMap.get("max"));
+ } catch(ClassCastException e) {
+ throw new InvalidConfigurationException("Error in Amount Configuration!");
+ }
+
+ Set on = ConfigUtil.toBlockData((List) items.get("spawnable-on"), "SpawnableOn", "");
+ Set in = ConfigUtil.toBlockData((List) items.get("spawnable-in"), "SpawnableIn", "");
+
+ this.feature = new EntityFeature(type, amount, attempts, on, in, height);
+ Debug.info("Loaded EntityFeature with type: " + type);
+ }
+ @Override
+ public Feature getFeature() {
+ return feature;
+ }
+}
diff --git a/src/main/java/com/dfsek/terra/config/genconfig/structure/FeatureConfig.java b/src/main/java/com/dfsek/terra/config/genconfig/structure/FeatureConfig.java
new file mode 100644
index 000000000..3bd839ebd
--- /dev/null
+++ b/src/main/java/com/dfsek/terra/config/genconfig/structure/FeatureConfig.java
@@ -0,0 +1,8 @@
+package com.dfsek.terra.config.genconfig.structure;
+
+import com.dfsek.terra.config.TerraConfigSection;
+import com.dfsek.terra.structure.features.Feature;
+
+public interface FeatureConfig {
+ Feature getFeature();
+}
diff --git a/src/main/java/com/dfsek/terra/config/genconfig/StructureConfig.java b/src/main/java/com/dfsek/terra/config/genconfig/structure/StructureConfig.java
similarity index 87%
rename from src/main/java/com/dfsek/terra/config/genconfig/StructureConfig.java
rename to src/main/java/com/dfsek/terra/config/genconfig/structure/StructureConfig.java
index 8591af58d..a44bf7197 100644
--- a/src/main/java/com/dfsek/terra/config/genconfig/StructureConfig.java
+++ b/src/main/java/com/dfsek/terra/config/genconfig/structure/StructureConfig.java
@@ -1,4 +1,4 @@
-package com.dfsek.terra.config.genconfig;
+package com.dfsek.terra.config.genconfig.structure;
import com.dfsek.terra.Debug;
import com.dfsek.terra.config.TerraConfig;
@@ -9,6 +9,7 @@ import com.dfsek.terra.config.exception.NotFoundException;
import com.dfsek.terra.population.StructurePopulator;
import com.dfsek.terra.procgen.GridSpawn;
import com.dfsek.terra.structure.Structure;
+import com.dfsek.terra.structure.features.Feature;
import org.apache.commons.io.FileUtils;
import org.bukkit.configuration.InvalidConfigurationException;
import org.json.simple.parser.ParseException;
@@ -19,7 +20,9 @@ import org.polydev.gaea.structures.loot.LootTable;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Random;
@@ -31,8 +34,11 @@ public class StructureConfig extends TerraConfig {
private final Range searchStart;
private final Range bound;
private final Map loot = new HashMap<>();
+ private final List features;
+
StructurePopulator.SearchType type;
+ @SuppressWarnings("unchecked")
public StructureConfig(File file, ConfigPack config) throws IOException, InvalidConfigurationException {
super(file, config);
if(! contains("id")) throw new ConfigException("No ID specified!", "null");
@@ -76,6 +82,16 @@ public class StructureConfig extends TerraConfig {
}
}
+ features = new ArrayList<>();
+ if(contains("features")) {
+ for(Map, ?> map : getMapList("features")) {
+ for(Map.Entry, ?> entry : map.entrySet()) {
+ if(entry.getKey().equals("ENTITY_FEATURE"))
+ features.add(new EntityFeatureConfig((Map) entry.getValue()).getFeature());
+ }
+ }
+ }
+
spawn = new GridSpawn(getInt("spawn.width", 500), getInt("spawn.padding", 100));
searchStart = new Range(getInt("spawn.start.min", 72), getInt("spawn.start.max", 72));
bound = new Range(getInt("spawn.bound.min", 48), getInt("spawn.bound.max", 72));
@@ -86,6 +102,10 @@ public class StructureConfig extends TerraConfig {
}
}
+ public List getFeatures() {
+ return features;
+ }
+
@Override
public String getID() {
return id;
diff --git a/src/main/java/com/dfsek/terra/generation/TerraChunkGenerator.java b/src/main/java/com/dfsek/terra/generation/TerraChunkGenerator.java
index 10bf04dbc..59bceea0a 100644
--- a/src/main/java/com/dfsek/terra/generation/TerraChunkGenerator.java
+++ b/src/main/java/com/dfsek/terra/generation/TerraChunkGenerator.java
@@ -132,11 +132,9 @@ public class TerraChunkGenerator extends GaeaChunkGenerator {
private void load(World w) {
try {
popMan.loadBlocks(w);
- } catch(IOException e) {
- if(e instanceof FileNotFoundException) {
- LangUtil.log("warning.no-population", Level.WARNING);
- } else e.printStackTrace();
- } catch(ClassNotFoundException e) {
+ } catch(FileNotFoundException e) {
+ LangUtil.log("warning.no-population", Level.WARNING);
+ } catch(IOException | ClassNotFoundException e) {
e.printStackTrace();
}
popMap.put(w, popMan);
diff --git a/src/main/java/com/dfsek/terra/population/StructurePopulator.java b/src/main/java/com/dfsek/terra/population/StructurePopulator.java
index 887953403..b07aca188 100644
--- a/src/main/java/com/dfsek/terra/population/StructurePopulator.java
+++ b/src/main/java/com/dfsek/terra/population/StructurePopulator.java
@@ -6,10 +6,11 @@ import com.dfsek.terra.TerraWorld;
import com.dfsek.terra.biome.TerraBiomeGrid;
import com.dfsek.terra.biome.UserDefinedBiome;
import com.dfsek.terra.config.base.ConfigPack;
-import com.dfsek.terra.config.genconfig.StructureConfig;
+import com.dfsek.terra.config.genconfig.structure.StructureConfig;
import com.dfsek.terra.procgen.math.Vector2;
import com.dfsek.terra.structure.Structure;
import com.dfsek.terra.structure.StructureContainedInventory;
+import com.dfsek.terra.structure.features.Feature;
import com.dfsek.terra.util.structure.RotationUtil;
import org.bukkit.Chunk;
import org.bukkit.Location;
@@ -67,6 +68,7 @@ public class StructurePopulator extends BlockPopulator {
Debug.stack(e);
}
}
+ for(Feature f : conf.getFeatures()) f.apply(struc, spawn, chunk); // Apply features.
break;
}
}
diff --git a/src/main/java/com/dfsek/terra/structure/features/EntityFeature.java b/src/main/java/com/dfsek/terra/structure/features/EntityFeature.java
new file mode 100644
index 000000000..0a57f1d2e
--- /dev/null
+++ b/src/main/java/com/dfsek/terra/structure/features/EntityFeature.java
@@ -0,0 +1,118 @@
+package com.dfsek.terra.structure.features;
+
+import com.dfsek.terra.Debug;
+import com.dfsek.terra.structure.Structure;
+import com.dfsek.terra.structure.StructureInfo;
+import org.bukkit.Chunk;
+import org.bukkit.Location;
+import org.bukkit.Material;
+import org.bukkit.block.Block;
+import org.bukkit.block.BlockFace;
+import org.bukkit.entity.EntityType;
+import org.polydev.gaea.math.MathUtil;
+import org.polydev.gaea.math.Range;
+
+
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+
+public class EntityFeature implements Feature {
+ private final EntityType type;
+ private final Range amount;
+ private final int attempts;
+ private final Set in;
+ private final Set stand;
+ private final int inSize;
+ public EntityFeature(EntityType type, Range amount, int attempts, Set stand, Set in, int inSize) {
+ this.type = type;
+ this.amount = amount;
+ this.attempts = attempts;
+ this.in = in;
+ this.stand = stand;
+ this.inSize = inSize;
+ }
+
+ @Override
+ public void apply(Structure structure, Location l, Chunk chunk) {
+ Random random = new Random(MathUtil.getCarverChunkSeed(chunk.getX(), chunk.getZ(), chunk.getWorld().getSeed()));
+
+ int amountSpawn = amount.get(random);
+
+ StructureInfo info = structure.getStructureInfo();
+ Range x = new Range(0, info.getSizeZ());
+ Range y = new Range(0, info.getSizeY());
+ Range z = new Range(0, info.getSizeZ());
+
+ int cx = info.getCenterX();
+ int cz = info.getCenterZ();
+
+ for(int i = 0; i < amountSpawn && i < attempts; i++) {
+ int yv = y.get(random);
+ Location attempt = l.clone().add(x.get(random)-cx, yv, z.get(random)-cz);
+ if(!isInChunk(chunk, attempt)) continue; // Don't attempt spawn if not in current chunk.
+
+ boolean canSpawn = false;
+ while(yv >= 0 && attempt.getBlockY() >= l.getBlockY()) { // Go down, see if valid spawns exist.
+ canSpawn = true;
+ Block on = attempt.getBlock();
+ attempt.subtract(0, 1, 0);
+ yv--;
+
+ if(!stand.contains(on.getType())) continue;
+
+ for(int j = 1; j < inSize + 1; j++) if(! in.contains(on.getRelative(BlockFace.UP, j).getType())) canSpawn = false;
+
+ if(canSpawn) break;
+ }
+ if(canSpawn) {
+ Debug.info("Spawning entity at " + attempt);
+ chunk.getWorld().spawnEntity(attempt.add(0.5, 1, 0.5), type); // Add 0.5 to X & Z so entity spawns in center of block.
+ }
+ }
+ }
+
+ private static boolean isInChunk(Chunk c, Location l) {
+ return Math.floorDiv(l.getBlockX(), 16) == c.getX() && Math.floorDiv(l.getBlockZ(), 16) == c.getZ();
+ }
+
+ @Override
+ public void apply(Structure structure, Location l, Random random) {
+ int amountSpawn = amount.get(random);
+
+ StructureInfo info = structure.getStructureInfo();
+ Range x = new Range(0, info.getSizeZ());
+ Range y = new Range(0, info.getSizeY());
+ Range z = new Range(0, info.getSizeZ());
+
+ int cx = info.getCenterX();
+ int cz = info.getCenterZ();
+
+ for(int i = 0; i < amountSpawn && i < attempts; i++) {
+ int yv = y.get(random);
+ Location attempt = l.clone().add(x.get(random)-cx, yv, z.get(random)-cz);
+
+ boolean canSpawn = false;
+ while(yv >= 0 && attempt.getBlockY() >= l.getBlockY()) { // Go down, see if valid spawns exist.
+ canSpawn = true;
+ Block on = attempt.getBlock();
+ attempt.subtract(0, 1, 0);
+ yv--;
+
+ if(!stand.contains(on.getType())) continue;
+
+ for(int j = 1; j < inSize + 1; j++) if(! in.contains(on.getRelative(BlockFace.UP, j).getType())) canSpawn = false;
+
+ if(canSpawn) break;
+ }
+ if(canSpawn) {
+ Debug.info("Spawning entity at " + attempt);
+ l.getWorld().spawnEntity(attempt.add(0.5, 1, 0.5), type); // Add 0.5 to X & Z so entity spawns in center of block.
+ }
+ }
+ }
+
+ public static class SpawnRule {
+
+ }
+}
diff --git a/src/main/java/com/dfsek/terra/structure/features/Feature.java b/src/main/java/com/dfsek/terra/structure/features/Feature.java
new file mode 100644
index 000000000..33d51f2c1
--- /dev/null
+++ b/src/main/java/com/dfsek/terra/structure/features/Feature.java
@@ -0,0 +1,12 @@
+package com.dfsek.terra.structure.features;
+
+import com.dfsek.terra.structure.Structure;
+import org.bukkit.Chunk;
+import org.bukkit.Location;
+
+import java.util.Random;
+
+public interface Feature {
+ void apply(Structure structure, Location l, Chunk chunk);
+ void apply(Structure structure, Location l, Random random);
+}
diff --git a/src/main/resources/default-config/structures/single/stronghold.yml b/src/main/resources/default-config/structures/single/stronghold.yml
index 17111a444..4df14f52e 100644
--- a/src/main/resources/default-config/structures/single/stronghold.yml
+++ b/src/main/resources/default-config/structures/single/stronghold.yml
@@ -13,4 +13,31 @@ spawn:
loot:
1: wood_house
2: cobble_house
- 3: cobble_house
\ No newline at end of file
+ 3: cobble_house
+features:
+ - ENTITY_FEATURE:
+ entity: SILVERFISH
+ attempts: 20
+ in-height: 1
+ amount:
+ min: 10
+ max: 15
+ spawnable-on:
+ - "minecraft:stone"
+ - "minecraft:stone_bricks"
+ - "minecraft:mossy_stone_bricks"
+ spawnable-in:
+ - "minecraft:air"
+ - ENTITY_FEATURE:
+ entity: ZOMBIE
+ attempts: 20
+ in-height: 2
+ amount:
+ min: 10
+ max: 15
+ spawnable-on:
+ - "minecraft:stone"
+ - "minecraft:stone_bricks"
+ - "minecraft:mossy_stone_bricks"
+ spawnable-in:
+ - "minecraft:air"
\ No newline at end of file