Fix structure spawn requirements, begin work on custom trees

This commit is contained in:
dfsek 2020-10-08 01:47:34 -07:00
parent fc53064ee1
commit e6f6a63194
8 changed files with 138 additions and 100 deletions

View File

@ -90,10 +90,6 @@ public class AsyncStructureFinder implements Runnable {
main: for(int y = target.getSearchStart().get(r2); y > 0; y--) {
spawn.setY(y);
if(y > target.getBound().getMax() || y < target.getBound().getMin()) return false;
for(StructureSpawnRequirement s : struc.getSpawns()) {
if(! s.isValidSpawn(spawn)) continue main; // Probably(tm) async safe
if(!b.equals(grid.getBiome(spawn.clone().add(s.getX(), s.getY(), s.getZ()), GenerationPhase.POPULATE))) return false;
}
}
return true;
}

View File

@ -22,6 +22,7 @@ public class LoadCommand extends PlayerCommand {
GaeaStructure struc = GaeaStructure.load(new File(Terra.getInstance().getDataFolder() + File.separator + "export" + File.separator + "structures", args[0] + ".tstructure"));
if("true".equals(args[2])) struc.paste(sender.getLocation(), r);
else struc.paste(sender.getLocation(), sender.getLocation().getChunk(), r);
//sender.sendMessage(String.valueOf(struc.checkSpawns(sender.getLocation(), r)));
} catch(IOException e) {
e.printStackTrace();
LangUtil.send("command.structure.invalid", sender, args[0]);

View File

@ -0,0 +1,66 @@
package com.dfsek.terra.config.genconfig;
import com.dfsek.terra.Debug;
import com.dfsek.terra.config.TerraConfig;
import com.dfsek.terra.config.base.ConfigPack;
import com.dfsek.terra.config.base.ConfigUtil;
import com.dfsek.terra.config.exception.ConfigException;
import com.dfsek.terra.config.exception.NotFoundException;
import com.dfsek.terra.structure.GaeaStructure;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.plugin.java.JavaPlugin;
import org.polydev.gaea.math.ProbabilityCollection;
import org.polydev.gaea.tree.Tree;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Random;
import java.util.Set;
public class TreeConfig extends TerraConfig implements Tree {
private final Set<Material> spawnable;
private final String id;
private final ProbabilityCollection<GaeaStructure> structure = new ProbabilityCollection<>();
public TreeConfig(File file, ConfigPack config) throws IOException, InvalidConfigurationException {
super(file, config);
spawnable = ConfigUtil.toBlockData(getStringList("spawnable"), "spawnable", getID());
if(!contains("id")) throw new ConfigException("No ID specified!", "null");
id = getString("id");
if(!contains("files")) throw new ConfigException("No files specified!", getID());
try {
for(Map.Entry<String, Object> e : Objects.requireNonNull(getConfigurationSection("files")).getValues(false).entrySet()) {
try {
File structureFile = new File(config.getDataFolder() + File.separator + "trees" + File.separator + "data", e.getKey() + ".tstructure");
structure.add(GaeaStructure.load(structureFile), (Integer) e.getValue());
} catch(FileNotFoundException ex) {
Debug.stack(ex);
throw new NotFoundException("Tree Structure File", e.getKey(), getID());
} catch(ClassCastException ex) {
Debug.stack(ex);
throw new ConfigException("Unable to parse Tree configuration! Check YAML syntax.", getID());
}
}
} catch(IOException | NullPointerException e) {
if(ConfigUtil.debug) {
e.printStackTrace();
}
throw new NotFoundException("Tree Structure", getString("file"), getID());
}
}
@Override
public String getID() {
return id;
}
@Override
public boolean plant(Location location, Random random, boolean b, JavaPlugin javaPlugin) {
return false;
}
}

View File

@ -34,17 +34,15 @@ public class StructurePopulator extends BlockPopulator {
Location spawn = conf.getSpawn().getNearestSpawn(cx + 8, cz + 8, world.getSeed()).toLocation(world);
Random r2 = new Random(spawn.hashCode());
GaeaStructure struc = conf.getStructure(r2);
GaeaStructure.Rotation rotation = GaeaStructure.Rotation.fromDegrees(r2.nextInt(4) * 90);
main: for(int y = conf.getSearchStart().get(r2); y > 0; y--) {
if(y > conf.getBound().getMax() || y < conf.getBound().getMin()) continue structure;
spawn.setY(y);
for(StructureSpawnRequirement s : struc.getSpawns()) {
if(! s.isValidSpawn(spawn)) continue main;
if(!b.equals(grid.getBiome(spawn.clone().add(s.getX(), s.getY(), s.getZ()), GenerationPhase.POPULATE))) continue structure;
}
if(!struc.checkSpawns(spawn, rotation)) continue;
double horizontal = struc.getStructureInfo().getMaxHorizontal();
if(Math.abs((cx + 8) - spawn.getBlockX()) <= horizontal && Math.abs((cz + 8) - spawn.getBlockZ()) <= horizontal) {
try(ProfileFuture ignore = TerraProfiler.fromWorld(world).measure("StructurePasteTime")) {
struc.paste(spawn, chunk, GaeaStructure.Rotation.fromDegrees(r2.nextInt(4) * 90));
struc.paste(spawn, chunk, rotation);
break;
}
}

View File

@ -31,8 +31,10 @@ import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.function.Consumer;
@ -41,10 +43,10 @@ import static com.dfsek.terra.util.structure.RotationUtil.*;
public class GaeaStructure implements Serializable {
public static final long serialVersionUID = -6664585217063842035L;
private final StructureContainedBlock[][][] structure;
private final ArrayList<StructureSpawnRequirement> spawns = new ArrayList<>();
private final GaeaStructureInfo structureInfo;
private final String id;
private final UUID uuid;
private final HashSet<StructureContainedBlock> spawns;
@NotNull
public static GaeaStructure load(@NotNull File f) throws IOException {
@ -56,9 +58,10 @@ public class GaeaStructure implements Serializable {
}
public GaeaStructure(@NotNull Location l1, @NotNull Location l2, @NotNull String id) throws InitializationException {
int centerX = -1, centerY = -1, centerZ = -1;
int centerX = -1, centerZ = -1;
this.id = id;
this.uuid = UUID.randomUUID();
this.spawns = new HashSet<>();
if(l1.getX() > l2.getX() || l1.getY() > l2.getY() || l1.getZ() > l2.getZ()) throw new IllegalArgumentException("Invalid locations provided!");
structure = new StructureContainedBlock[l2.getBlockX()-l1.getBlockX()+1][l2.getBlockZ()-l1.getBlockZ()+1][l2.getBlockY()-l1.getBlockY()+1];
for(int x = 0; x <= l2.getBlockX()-l1.getBlockX(); x++) {
@ -68,6 +71,7 @@ public class GaeaStructure implements Serializable {
BlockState state = b.getState();
BlockData d = b.getBlockData();
boolean useState = true;
StructureSpawnRequirement requirement = StructureSpawnRequirement.BLANK;
if(state instanceof Sign) {
Sign s = (Sign) b.getState();
if(s.getLine(0).equals("[TERRA]")) {
@ -76,22 +80,14 @@ public class GaeaStructure implements Serializable {
useState = false;
if(s.getLine(1).equals("[CENTER]")) {
centerX = x;
centerY = y;
centerZ = z;
} else if(s.getLine(1).startsWith("[SPAWN=") && s.getLine(1).endsWith("]")) {
String og = s.getLine(1);
String spawn = og.substring(og.indexOf("=")+1, og.length()-1);
switch(spawn.toUpperCase()) {
case "LAND":
spawns.add(new StructureSpawnRequirement(StructureSpawnRequirement.RequirementType.LAND, x, y, z));
break;
case "OCEAN":
spawns.add(new StructureSpawnRequirement(StructureSpawnRequirement.RequirementType.OCEAN, x, y, z));
break;
case "AIR":
spawns.add(new StructureSpawnRequirement(StructureSpawnRequirement.RequirementType.AIR, x, y, z));
break;
default: throw new InitializationException("Invalid spawn type, " + spawn);
try {
requirement = StructureSpawnRequirement.valueOf(spawn.toUpperCase());
} catch(IllegalArgumentException e) {
throw new InitializationException("Invalid spawn type: " + spawn);
}
}
} catch(IllegalArgumentException e) {
@ -99,16 +95,14 @@ public class GaeaStructure implements Serializable {
}
}
}
structure[x][z][y] = new StructureContainedBlock(x, y, z, useState ? state : null, d);
StructureContainedBlock block = new StructureContainedBlock(x, y, z, useState ? state : null, d, requirement);
if(!requirement.equals(StructureSpawnRequirement.BLANK)) spawns.add(block);
structure[x][z][y] = block;
}
}
}
if(centerX < 0 || centerY < 0 || centerZ < 0) throw new InitializationException("No structure center specified.");
structureInfo = new GaeaStructureInfo(l2.getBlockX()-l1.getBlockX()+1, l2.getBlockY()-l1.getBlockY()+1, l2.getBlockZ()-l1.getBlockZ()+1, new Vector(centerX, centerY, centerZ));
}
public ArrayList<StructureSpawnRequirement> getSpawns() {
return spawns;
if(centerX < 0 || centerZ < 0) throw new InitializationException("No structure center specified.");
structureInfo = new GaeaStructureInfo(l2.getBlockX()-l1.getBlockX()+1, l2.getBlockY()-l1.getBlockY()+1, l2.getBlockZ()-l1.getBlockZ()+1, new Vector2(centerX, centerZ));
}
/**
@ -129,6 +123,13 @@ public class GaeaStructure implements Serializable {
this.executeForBlocksInRange(getRange(Axis.X), getRange(Axis.Y), getRange(Axis.Z), block -> pasteBlock(block, origin, r), r);
}
public boolean checkSpawns(Location origin, Rotation r) {
for(StructureContainedBlock b : spawns) {
Vector2 rot = getRotatedCoords(new Vector2(b.getX()-structureInfo.getCenterX(), b.getZ()-structureInfo.getCenterZ()), r);
if(!b.getRequirement().matches(origin.getWorld(), (int) rot.getX()+origin.getBlockX(), origin.getBlockY(), (int) rot.getZ()+origin.getBlockZ())) return false;
}
return true;
}
@ -158,7 +159,8 @@ public class GaeaStructure implements Serializable {
*/
private void pasteBlock(StructureContainedBlock block, Location origin, Rotation r) {
BlockData data = block.getBlockData().clone();
Block worldBlock = origin.clone().add(block.getX(), block.getY(), block.getZ()).getBlock();
Location loc = origin.clone().add(block.getX(), block.getY(), block.getZ());
Block worldBlock = loc.getBlock();
if(!data.getMaterial().equals(Material.STRUCTURE_VOID)) {
if(data instanceof Rotatable) {
BlockFace rt = getRotatedFace(((Rotatable) data).getRotation(), r);
@ -203,7 +205,7 @@ public class GaeaStructure implements Serializable {
c.add(new Vector2(structureInfo.getCenterX(), structureInfo.getCenterZ()));
if(isInStructure((int) c.getX(), y, (int) c.getZ())) {
StructureContainedBlock b = structure[(int) c.getX()][(int) c.getZ()][y];
exec.accept(new StructureContainedBlock(x - getStructureInfo().getCenterX(), y, z - getStructureInfo().getCenterZ(), b.getState(), b.getBlockData()));
exec.accept(new StructureContainedBlock(x - getStructureInfo().getCenterX(), y, z - getStructureInfo().getCenterZ(), b.getState(), b.getBlockData(), b.getRequirement()));
}
}
}

View File

@ -1,5 +1,6 @@
package com.dfsek.terra.structure;
import com.dfsek.terra.procgen.math.Vector2;
import org.bukkit.util.Vector;
import java.io.Serializable;
@ -11,14 +12,12 @@ public class GaeaStructureInfo implements Serializable {
private final int sizeZ;
private final int centerX;
private final int centerZ;
private final int centerY;
public GaeaStructureInfo(int sizeX, int sizeY, int sizeZ, Vector center) {
public GaeaStructureInfo(int sizeX, int sizeY, int sizeZ, Vector2 center) {
this.sizeX = sizeX;
this.sizeY = sizeY;
this.sizeZ = sizeZ;
this.centerX = center.getBlockX();
this.centerZ = center.getBlockZ();
this.centerY = center.getBlockY();
this.centerX = (int) center.getX();
this.centerZ = (int) center.getZ();
}
public int getSizeX() {
@ -37,10 +36,6 @@ public class GaeaStructureInfo implements Serializable {
return centerX;
}
public int getCenterY() {
return centerY;
}
public int getCenterZ() {
return centerZ;
}

View File

@ -18,7 +18,8 @@ public class StructureContainedBlock implements Serializable {
private final int y;
private final int z;
private final SerializableBlockState state;
public StructureContainedBlock(int x, int y, int z, BlockState state, BlockData d) {
private final StructureSpawnRequirement requirement;
public StructureContainedBlock(int x, int y, int z, BlockState state, BlockData d, StructureSpawnRequirement spawn) {
if(state instanceof Sign) {
this.state = new SerializableSign((org.bukkit.block.Sign) state);
} else this.state = null;
@ -26,8 +27,9 @@ public class StructureContainedBlock implements Serializable {
this.y = y;
this.z = z;
this.bl = new SerializableBlockData(d);
this.requirement = spawn;
}
public StructureContainedBlock(int x, int y, int z, SerializableBlockState state, BlockData d) {
public StructureContainedBlock(int x, int y, int z, SerializableBlockState state, BlockData d, StructureSpawnRequirement spawn) {
if(state instanceof SerializableSign) {
this.state = state;
} else this.state = null;
@ -35,6 +37,11 @@ public class StructureContainedBlock implements Serializable {
this.y = y;
this.z = z;
this.bl = new SerializableBlockData(d);
this.requirement = spawn;
}
public StructureSpawnRequirement getRequirement() {
return requirement;
}
public int getX() {

View File

@ -12,40 +12,7 @@ import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
public class StructureSpawnRequirement implements Serializable {
private static final long serialVersionUID = -175639605885943679L;
private static final transient Map<World, FastNoise> noiseMap = new HashMap<>();
private final RequirementType type;
private final int x, y, z;
public StructureSpawnRequirement(RequirementType type, int x, int y, int z) {
this.type = type;
this.x = x;
this.y = y;
this.z = z;
}
public RequirementType getType() {
return type;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getZ() {
return z;
}
public boolean isValidSpawn(Location origin) {
return type.matches(origin.getWorld(), origin.getBlockX()+x, origin.getBlockY()+y, origin.getBlockZ()+z);
}
public enum RequirementType {
public enum StructureSpawnRequirement implements Serializable {
AIR {
@Override
public boolean matches(World w, int x, int y, int z) {
@ -68,9 +35,15 @@ public class StructureSpawnRequirement implements Serializable {
UserDefinedBiome b = (UserDefinedBiome) TerraWorld.getWorld(w).getGrid().getBiome(x, z, GenerationPhase.POPULATE);
return b.getGenerator().getNoise(getNoise(w), w, x, y, z) > 0;
}
};
public abstract boolean matches(World w, int x, int y, int z);
}, BLANK {
@Override
public boolean matches(World w, int x, int y, int z) {
return true;
}
};
private static final long serialVersionUID = -175639605885943679L;
private static final transient Map<World, FastNoise> noiseMap = new HashMap<>();
public abstract boolean matches(World w, int x, int y, int z);
public static void putNoise(World w, FastNoise noise) {
noiseMap.putIfAbsent(w, noise);
}