Initial commit

This commit is contained in:
dfsek 2021-10-17 15:23:38 -07:00
commit 65b52e76ec
26 changed files with 1157 additions and 0 deletions

View File

@ -0,0 +1,3 @@
# config-structure
Registers the default configuration for Terra Structures, `STRUCTURE`.

View File

@ -0,0 +1,3 @@
dependencies {
"shadedApi"("com.googlecode.json-simple:json-simple:1.1.1")
}

View File

@ -0,0 +1,19 @@
package com.dfsek.terra.addons.structure;
import java.util.Set;
import com.dfsek.terra.api.properties.Properties;
import com.dfsek.terra.api.structure.configured.ConfiguredStructure;
public class BiomeStructures implements Properties {
private final Set<ConfiguredStructure> structures;
public BiomeStructures(Set<ConfiguredStructure> structures) {
this.structures = structures;
}
public Set<ConfiguredStructure> getStructures() {
return structures;
}
}

View File

@ -0,0 +1,23 @@
package com.dfsek.terra.addons.structure;
import com.dfsek.tectonic.annotations.Default;
import com.dfsek.tectonic.annotations.Value;
import com.dfsek.tectonic.loading.object.ObjectTemplate;
import java.util.Collections;
import java.util.Set;
import com.dfsek.terra.api.config.meta.Meta;
import com.dfsek.terra.api.structure.configured.ConfiguredStructure;
public class BiomeStructuresTemplate implements ObjectTemplate<BiomeStructures> {
@Value("structures")
@Default
private @Meta Set<@Meta ConfiguredStructure> structures = Collections.emptySet();
@Override
public BiomeStructures get() {
return new BiomeStructures(structures);
}
}

View File

@ -0,0 +1,29 @@
package com.dfsek.terra.addons.structure;
import com.dfsek.terra.api.Platform;
import com.dfsek.terra.api.addon.TerraAddon;
import com.dfsek.terra.api.addon.annotations.Addon;
import com.dfsek.terra.api.addon.annotations.Author;
import com.dfsek.terra.api.addon.annotations.Version;
import com.dfsek.terra.api.event.events.config.pack.ConfigPackPreLoadEvent;
import com.dfsek.terra.api.event.functional.FunctionalEventHandler;
import com.dfsek.terra.api.inject.annotations.Inject;
import com.dfsek.terra.api.structure.configured.ConfiguredStructure;
@Addon("config-structure")
@Version("1.0.0")
@Author("Terra")
public class StructureAddon extends TerraAddon {
@Inject
private Platform platform;
@Override
public void initialize() {
platform.getEventManager()
.getHandler(FunctionalEventHandler.class)
.register(this, ConfigPackPreLoadEvent.class)
.then(event -> event.getPack().applyLoader(ConfiguredStructure.class, (t, o, l) -> null))
.failThrough();
}
}

View File

@ -0,0 +1,13 @@
package com.dfsek.terra.addons.structure;
import com.dfsek.terra.api.Platform;
import com.dfsek.terra.api.config.ConfigFactory;
import com.dfsek.terra.api.structure.configured.ConfiguredStructure;
public class StructureFactory implements ConfigFactory<StructureTemplate, ConfiguredStructure> {
@Override
public ConfiguredStructure build(StructureTemplate config, Platform platform) {
return new TerraStructure(config.getStructures(), config.getY(), config.getSpawn());
}
}

View File

@ -0,0 +1,53 @@
package com.dfsek.terra.addons.structure;
import com.dfsek.terra.api.Platform;
import net.jafama.FastMath;
import org.jetbrains.annotations.NotNull;
import java.util.Random;
import com.dfsek.terra.api.config.WorldConfig;
import com.dfsek.terra.api.profiler.ProfileFrame;
import com.dfsek.terra.api.structure.configured.ConfiguredStructure;
import com.dfsek.terra.api.structure.rotation.Rotation;
import com.dfsek.terra.api.util.PopulationUtil;
import com.dfsek.terra.api.util.vector.Vector3;
import com.dfsek.terra.api.world.Chunk;
import com.dfsek.terra.api.world.World;
import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
import com.dfsek.terra.api.world.generator.Chunkified;
import com.dfsek.terra.api.world.generator.GenerationStage;
public class StructurePopulator implements GenerationStage, Chunkified {
private final Platform platform;
public StructurePopulator(Platform platform) {
this.platform = platform;
}
@SuppressWarnings("try")
@Override
public void populate(@NotNull World world, @NotNull Chunk chunk) {
try(ProfileFrame ignore = platform.getProfiler().profile("structure")) {
if(world.getConfig().disableStructures()) return;
int cx = (chunk.getX() << 4);
int cz = (chunk.getZ() << 4);
BiomeProvider provider = world.getBiomeProvider();
WorldConfig config = world.getConfig();
for(ConfiguredStructure conf : config.getRegistry(TerraStructure.class).entries()) {
Vector3 spawn = conf.getSpawn().getNearestSpawn(cx + 8, cz + 8, world.getSeed());
if(!provider.getBiome(spawn, world.getSeed()).getContext().get(BiomeStructures.class).getStructures().contains(conf)) {
continue;
}
Random random = new Random(PopulationUtil.getCarverChunkSeed(FastMath.floorDiv(spawn.getBlockX(), 16),
FastMath.floorDiv(spawn.getBlockZ(), 16), world.getSeed()));
conf.getStructure().get(random).generate(spawn.setY(conf.getSpawnStart().get(random)), world, chunk, random,
Rotation.fromDegrees(90 * random.nextInt(4)));
}
}
}
}

View File

@ -0,0 +1,44 @@
package com.dfsek.terra.addons.structure;
import com.dfsek.tectonic.annotations.Final;
import com.dfsek.tectonic.annotations.Value;
import com.dfsek.terra.api.config.AbstractableTemplate;
import com.dfsek.terra.api.config.meta.Meta;
import com.dfsek.terra.api.structure.Structure;
import com.dfsek.terra.api.structure.StructureSpawn;
import com.dfsek.terra.api.util.Range;
import com.dfsek.terra.api.util.collection.ProbabilityCollection;
@SuppressWarnings({ "unused", "FieldMayBeFinal" })
public class StructureTemplate implements AbstractableTemplate {
@Value("id")
@Final
private String id;
@Value("scripts")
private @Meta ProbabilityCollection<@Meta Structure> structure;
@Value("spawn.start")
private @Meta Range y;
@Value("spawn")
private @Meta StructureSpawn spawn;
public String getID() {
return id;
}
public ProbabilityCollection<Structure> getStructures() {
return structure;
}
public Range getY() {
return y;
}
public StructureSpawn getSpawn() {
return spawn;
}
}

View File

@ -0,0 +1,40 @@
package com.dfsek.terra.addons.structure;
import com.dfsek.terra.api.structure.Structure;
import com.dfsek.terra.api.structure.StructureSpawn;
import com.dfsek.terra.api.structure.configured.ConfiguredStructure;
import com.dfsek.terra.api.util.Range;
import com.dfsek.terra.api.util.collection.ProbabilityCollection;
public class TerraStructure implements ConfiguredStructure {
private final ProbabilityCollection<Structure> structure;
private final Range spawnStart;
private final StructureSpawn spawn;
public TerraStructure(ProbabilityCollection<Structure> structures, Range spawnStart, StructureSpawn spawn) {
this.structure = structures;
this.spawnStart = spawnStart;
this.spawn = spawn;
}
@Override
public ProbabilityCollection<Structure> getStructure() {
return structure;
}
@Override
public Range getSpawnStart() {
return spawnStart;
}
@Override
public StructureSpawn getSpawn() {
return spawn;
}
@Override
public String getID() {
return null;
}
}

View File

@ -0,0 +1,111 @@
package com.dfsek.terra.addons.structure.command;
import com.dfsek.terra.api.Platform;
import org.jetbrains.annotations.NotNull;
import java.util.function.Consumer;
import com.dfsek.terra.api.structure.configured.ConfiguredStructure;
import com.dfsek.terra.api.util.vector.Vector3;
import com.dfsek.terra.api.world.World;
import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
public class AsyncStructureFinder implements Runnable {
protected final BiomeProvider provider;
protected final ConfiguredStructure target;
protected final int startRadius;
protected final int maxRadius;
protected final int centerX;
protected final int centerZ;
protected final World world;
protected final Platform platform;
private final Consumer<Vector3> callback;
protected int searchSize = 1;
public AsyncStructureFinder(BiomeProvider provider, ConfiguredStructure target, @NotNull Vector3 origin, World world, int startRadius,
int maxRadius, Consumer<Vector3> callback, Platform platform) {
//setSearchSize(target.getSpawn().getWidth() + 2 * target.getSpawn().getSeparation());
this.provider = provider;
this.target = target;
this.platform = platform;
this.startRadius = startRadius;
this.maxRadius = maxRadius;
this.centerX = origin.getBlockX();
this.centerZ = origin.getBlockZ();
this.world = world;
this.callback = callback;
}
public Vector3 finalizeVector(Vector3 orig) {
return orig;//target.getSpawn().getChunkSpawn(orig.getBlockX(), orig.getBlockZ(), world.getSeed());
}
@Override
public void run() {
int x = centerX;
int z = centerZ;
x /= searchSize;
z /= searchSize;
int run = 1;
boolean toggle = true;
boolean found = false;
main:
for(int i = startRadius; i < maxRadius; i++) {
for(int j = 0; j < run; j++) {
if(isValid(x, z, target)) {
found = true;
break main;
}
if(toggle) x += 1;
else x -= 1;
}
for(int j = 0; j < run; j++) {
if(isValid(x, z, target)) {
found = true;
break main;
}
if(toggle) z += 1;
else z -= 1;
}
run++;
toggle = !toggle;
}
Vector3 finalSpawn = found ? finalizeVector(new Vector3(x, 0, z)) : null;
callback.accept(finalSpawn);
}
public boolean isValid(int x, int z, ConfiguredStructure target) {
//Vector3 spawn = target.getSpawn().getChunkSpawn(x, z, world.getSeed());
//if(!((UserDefinedBiome) provider.getBiome(spawn)).getConfig().getStructures().contains(target)) return false;
//Random random = new Random(PopulationUtil.getCarverChunkSeed(FastMath.floorDiv(spawn.getBlockX(), 16), FastMath.floorDiv(spawn
// .getBlockZ(), 16), world.getSeed()));
//return target.getStructure().get(random).test(spawn.setY(target.getSpawnStart().get(random)), world, random, Rotation
// .fromDegrees(90 * random.nextInt(4)));
return false;
}
public ConfiguredStructure getTarget() {
return target;
}
public World getWorld() {
return world;
}
public BiomeProvider getProvider() {
return provider;
}
public int getSearchSize() {
return searchSize;
}
public void setSearchSize(int searchSize) {
this.searchSize = searchSize;
}
}

View File

@ -0,0 +1,34 @@
package com.dfsek.terra.addons.structure.command.structure;
import com.dfsek.terra.api.command.CommandTemplate;
import com.dfsek.terra.api.command.annotation.Command;
import com.dfsek.terra.api.command.annotation.Subcommand;
import com.dfsek.terra.api.entity.CommandSender;
@Command(
subcommands = {
@Subcommand(
clazz = StructureExportCommand.class,
value = "export",
aliases = "ex"
),
@Subcommand(
clazz = StructureLoadCommand.class,
value = "load",
aliases = "ld"
),
@Subcommand(
clazz = StructureLocateCommand.class,
value = "locate",
aliases = "l"
)
},
usage = "/te structure"
)
public class StructureCommand implements CommandTemplate {
@Override
public void execute(CommandSender sender) {
//LangUtil.send("command.structure.main-menu", sender);
}
}

View File

@ -0,0 +1,106 @@
package com.dfsek.terra.addons.structure.command.structure;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import com.dfsek.terra.api.Platform;
import com.dfsek.terra.api.block.entity.BlockEntity;
import com.dfsek.terra.api.block.entity.Sign;
import com.dfsek.terra.api.block.state.BlockState;
import com.dfsek.terra.api.command.CommandTemplate;
import com.dfsek.terra.api.command.annotation.Argument;
import com.dfsek.terra.api.command.annotation.Command;
import com.dfsek.terra.api.command.annotation.inject.ArgumentTarget;
import com.dfsek.terra.api.command.annotation.type.DebugCommand;
import com.dfsek.terra.api.command.annotation.type.PlayerCommand;
import com.dfsek.terra.api.command.annotation.type.WorldCommand;
import com.dfsek.terra.api.entity.CommandSender;
import com.dfsek.terra.api.entity.Player;
import com.dfsek.terra.api.inject.annotations.Inject;
import com.dfsek.terra.api.util.generic.pair.Pair;
import com.dfsek.terra.api.util.vector.Vector3;
@PlayerCommand
@WorldCommand
@DebugCommand
@Command(arguments = @Argument("id"), usage = "/terra structure export <ID>")
public class StructureExportCommand implements CommandTemplate {
@Inject
private Platform platform;
@ArgumentTarget("id")
private String id;
@Override
public void execute(CommandSender sender) {
Player player = (Player) sender;
Pair<Vector3, Vector3> l = platform.getWorldHandle().getSelectedLocation(player);
Vector3 l1 = l.getLeft();
Vector3 l2 = l.getRight();
StringBuilder scriptBuilder = new StringBuilder("id \"" + id + "\";\nnum y = 0;\n");
int centerX = 0;
int centerY = 0;
int centerZ = 0;
for(int x = l1.getBlockX(); x <= l2.getBlockX(); x++) {
for(int y = l1.getBlockY(); y <= l2.getBlockY(); y++) {
for(int z = l1.getBlockZ(); z <= l2.getBlockZ(); z++) {
BlockEntity state = player.world().getBlockState(x, y, z);
if(state instanceof Sign) {
Sign sign = (Sign) state;
if(sign.getLine(0).equals("[TERRA]") && sign.getLine(1).equals("[CENTER]")) {
centerX = x - l1.getBlockX();
centerY = y - l1.getBlockY();
centerZ = z - l1.getBlockZ();
}
}
}
}
}
for(int x = l1.getBlockX(); x <= l2.getBlockX(); x++) {
for(int y = l1.getBlockY(); y <= l2.getBlockY(); y++) {
for(int z = l1.getBlockZ(); z <= l2.getBlockZ(); z++) {
BlockState data = player.world().getBlockData(x, y, z);
if(data.isStructureVoid()) continue;
BlockEntity state = player.world().getBlockState(x, y, z);
if(state instanceof Sign) {
Sign sign = (Sign) state;
if(sign.getLine(0).equals("[TERRA]")) {
data = platform.getWorldHandle().createBlockData(sign.getLine(2) + sign.getLine(3));
}
}
if(!data.isStructureVoid()) {
scriptBuilder.append("block(").append(x - l1.getBlockX() - centerX).append(", y + ").append(
y - l1.getBlockY() - centerY).append(", ").append(z - l1.getBlockZ() - centerZ).append(", ")
.append("\"");
scriptBuilder.append(data.getAsString()).append("\");\n");
}
}
}
}
File file = new File(platform.getDataFolder() + File.separator + "export" + File.separator + "structures", id + ".tesf");
try {
file.getParentFile().mkdirs();
file.createNewFile();
} catch(IOException e) {
e.printStackTrace();
}
try(BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
writer.write(scriptBuilder.toString());
} catch(IOException e) {
e.printStackTrace();
}
sender.sendMessage("Exported structure to " + file.getAbsolutePath());
}
}

View File

@ -0,0 +1,85 @@
package com.dfsek.terra.addons.structure.command.structure;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
import com.dfsek.terra.addons.structure.command.structure.argument.ScriptArgumentParser;
import com.dfsek.terra.addons.structure.command.structure.completer.RotationCompleter;
import com.dfsek.terra.addons.structure.command.structure.completer.ScriptCompleter;
import com.dfsek.terra.api.Platform;
import com.dfsek.terra.api.command.CommandTemplate;
import com.dfsek.terra.api.command.annotation.Argument;
import com.dfsek.terra.api.command.annotation.Command;
import com.dfsek.terra.api.command.annotation.Switch;
import com.dfsek.terra.api.command.annotation.inject.ArgumentTarget;
import com.dfsek.terra.api.command.annotation.inject.SwitchTarget;
import com.dfsek.terra.api.command.annotation.type.DebugCommand;
import com.dfsek.terra.api.command.annotation.type.PlayerCommand;
import com.dfsek.terra.api.command.annotation.type.WorldCommand;
import com.dfsek.terra.api.command.arg.IntegerArgumentParser;
import com.dfsek.terra.api.entity.CommandSender;
import com.dfsek.terra.api.entity.Player;
import com.dfsek.terra.api.inject.annotations.Inject;
import com.dfsek.terra.api.structure.Structure;
import com.dfsek.terra.api.structure.rotation.Rotation;
@PlayerCommand
@DebugCommand
@WorldCommand
@Command(arguments = {
@Argument(
value = "structure",
tabCompleter = ScriptCompleter.class,
argumentParser = ScriptArgumentParser.class
),
@Argument(
value = "rotation",
required = false,
tabCompleter = RotationCompleter.class,
argumentParser = IntegerArgumentParser.class,
defaultValue = "0"
)
}, switches = @Switch(value = "chunk",
aliases = "c"
), usage = "/terra structure load [ROTATION] [-c]")
public class StructureLoadCommand implements CommandTemplate {
@ArgumentTarget("rotation")
private final Integer rotation = 0;
@SwitchTarget("chunk")
private boolean chunk;
@ArgumentTarget("structure")
private Structure script;
@Inject
private Platform platform;
@Override
public void execute(CommandSender sender) {
Player player = (Player) sender;
long t = System.nanoTime();
Random random = new Random(ThreadLocalRandom.current().nextLong());
Rotation r;
try {
r = Rotation.fromDegrees(rotation);
} catch(Exception e) {
sender.sendMessage("Invalid rotation: " + rotation);
return;
}
if(script == null) {
sender.sendMessage("Invalid structure.");
return;
}
if(this.chunk) {
script.generate(player.position(), player.world(), player.world().getChunkAt(player.position()), random, r);
} else {
script.generate(player.position(), player.world(), random, r);
}
long l = System.nanoTime() - t;
sender.sendMessage("Took " + ((double) l) / 1000000 + "ms");
}
}

View File

@ -0,0 +1,75 @@
package com.dfsek.terra.addons.structure.command.structure;
import java.util.Locale;
import com.dfsek.terra.addons.structure.command.AsyncStructureFinder;
import com.dfsek.terra.addons.structure.command.structure.argument.StructureArgumentParser;
import com.dfsek.terra.addons.structure.command.structure.completer.StructureCompleter;
import com.dfsek.terra.api.Platform;
import com.dfsek.terra.api.command.CommandTemplate;
import com.dfsek.terra.api.command.annotation.Argument;
import com.dfsek.terra.api.command.annotation.Command;
import com.dfsek.terra.api.command.annotation.Switch;
import com.dfsek.terra.api.command.annotation.inject.ArgumentTarget;
import com.dfsek.terra.api.command.annotation.inject.SwitchTarget;
import com.dfsek.terra.api.command.annotation.type.PlayerCommand;
import com.dfsek.terra.api.command.annotation.type.WorldCommand;
import com.dfsek.terra.api.command.arg.IntegerArgumentParser;
import com.dfsek.terra.api.entity.CommandSender;
import com.dfsek.terra.api.entity.Player;
import com.dfsek.terra.api.inject.annotations.Inject;
import com.dfsek.terra.api.structure.configured.ConfiguredStructure;
import com.dfsek.terra.api.util.vector.Vector3;
@PlayerCommand
@WorldCommand
@Command(arguments = {
@Argument(
value = "structure",
tabCompleter = StructureCompleter.class,
argumentParser = StructureArgumentParser.class
),
@Argument(
value = "radius",
required = false,
defaultValue = "100",
argumentParser = IntegerArgumentParser.class
)
}, switches = @Switch(
value = "teleport",
aliases = { "t", "tp" }
))
public class StructureLocateCommand implements CommandTemplate {
@Inject
private Platform platform;
@ArgumentTarget("structure")
private ConfiguredStructure structure;
@ArgumentTarget("radius")
private Integer radius;
@SwitchTarget("teleport")
private boolean teleport;
@Override
public void execute(CommandSender sender) {
Player player = (Player) sender;
new Thread(new AsyncStructureFinder(player.world().getBiomeProvider(), structure,
player.position().clone().multiply((1D / platform.getTerraConfig().getBiomeSearchResolution())),
player.world(), 0, radius, location -> {
if(location != null) {
sender.sendMessage(
String.format("The nearest %s is at [%d, ~, %d] (%.1f blocks away)", structure.getID().toLowerCase(Locale.ROOT),
location.getBlockX(), location.getBlockZ(),
location.add(new Vector3(0, player.position().getY(), 0)).distance(player.position())));
if(teleport) {
platform.runPossiblyUnsafeTask(
() -> player.position(new Vector3(location.getX(), player.position().getY(), location.getZ())));
}
} //else LangUtil.send("command.biome.unable-to-locate", sender);
}, platform), "Biome Location Thread").start();
}
}

View File

@ -0,0 +1,19 @@
package com.dfsek.terra.addons.structure.command.structure.argument;
import com.dfsek.terra.api.Platform;
import com.dfsek.terra.api.command.arg.ArgumentParser;
import com.dfsek.terra.api.entity.CommandSender;
import com.dfsek.terra.api.entity.Player;
import com.dfsek.terra.api.inject.annotations.Inject;
import com.dfsek.terra.api.structure.Structure;
public class ScriptArgumentParser implements ArgumentParser<Structure> {
@Inject
private Platform platform;
@Override
public Structure parse(CommandSender sender, String arg) {
return ((Player) sender).world().getConfig().getRegistry(Structure.class).get(arg);
}
}

View File

@ -0,0 +1,19 @@
package com.dfsek.terra.addons.structure.command.structure.argument;
import com.dfsek.terra.api.Platform;
import com.dfsek.terra.api.command.arg.ArgumentParser;
import com.dfsek.terra.api.entity.CommandSender;
import com.dfsek.terra.api.entity.Player;
import com.dfsek.terra.api.inject.annotations.Inject;
import com.dfsek.terra.api.structure.configured.ConfiguredStructure;
public class StructureArgumentParser implements ArgumentParser<ConfiguredStructure> {
@Inject
private Platform platform;
@Override
public ConfiguredStructure parse(CommandSender sender, String arg) {
return ((Player) sender).world().getConfig().getRegistry(ConfiguredStructure.class).get(arg);
}
}

View File

@ -0,0 +1,15 @@
package com.dfsek.terra.addons.structure.command.structure.completer;
import java.util.Arrays;
import java.util.List;
import com.dfsek.terra.api.command.tab.TabCompleter;
import com.dfsek.terra.api.entity.CommandSender;
public class RotationCompleter implements TabCompleter {
@Override
public List<String> complete(CommandSender sender) {
return Arrays.asList("0", "90", "180", "270");
}
}

View File

@ -0,0 +1,23 @@
package com.dfsek.terra.addons.structure.command.structure.completer;
import java.util.List;
import java.util.stream.Collectors;
import com.dfsek.terra.api.Platform;
import com.dfsek.terra.api.command.tab.TabCompleter;
import com.dfsek.terra.api.entity.CommandSender;
import com.dfsek.terra.api.entity.Player;
import com.dfsek.terra.api.inject.annotations.Inject;
import com.dfsek.terra.api.structure.Structure;
public class ScriptCompleter implements TabCompleter {
@Inject
private Platform platform;
@Override
public List<String> complete(CommandSender sender) {
return ((Player) sender).world().getConfig().getRegistry(Structure.class).entries().stream().map(Structure::getID).collect(
Collectors.toList());
}
}

View File

@ -0,0 +1,23 @@
package com.dfsek.terra.addons.structure.command.structure.completer;
import java.util.ArrayList;
import java.util.List;
import com.dfsek.terra.api.Platform;
import com.dfsek.terra.api.command.tab.TabCompleter;
import com.dfsek.terra.api.entity.CommandSender;
import com.dfsek.terra.api.entity.Player;
import com.dfsek.terra.api.inject.annotations.Inject;
import com.dfsek.terra.api.structure.configured.ConfiguredStructure;
public class StructureCompleter implements TabCompleter {
@Inject
private Platform platform;
@Override
public List<String> complete(CommandSender sender) {
Player player = (Player) sender;
return new ArrayList<>(player.world().getConfig().getRegistry(ConfiguredStructure.class).keys());
}
}

View File

@ -0,0 +1,103 @@
package com.dfsek.terra.addons.structure.structures.loot;
import com.dfsek.terra.api.Platform;
import net.jafama.FastMath;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import com.dfsek.terra.addons.structure.structures.loot.functions.AmountFunction;
import com.dfsek.terra.addons.structure.structures.loot.functions.DamageFunction;
import com.dfsek.terra.addons.structure.structures.loot.functions.EnchantFunction;
import com.dfsek.terra.addons.structure.structures.loot.functions.LootFunction;
import com.dfsek.terra.api.inventory.Item;
import com.dfsek.terra.api.inventory.ItemStack;
/**
* Representation of a single item entry within a Loot Table pool.
*/
public class Entry {
private final Item item;
private final long weight;
private final List<LootFunction> functions = new ArrayList<>();
/**
* Instantiates an Entry from a JSON representation.
*
* @param entry The JSON Object to instantiate from.
*/
public Entry(JSONObject entry, Platform platform) {
String id = entry.get("name").toString();
this.item = platform.getItemHandle().createItem(id);
long weight1;
try {
weight1 = (long) entry.get("weight");
} catch(NullPointerException e) {
weight1 = 1;
}
this.weight = weight1;
if(entry.containsKey("functions")) {
for(Object function : (JSONArray) entry.get("functions")) {
switch(((String) ((JSONObject) function).get("function"))) {
case "minecraft:set_count", "set_count" -> {
Object loot = ((JSONObject) function).get("count");
long max, min;
if(loot instanceof Long) {
max = (Long) loot;
min = (Long) loot;
} else {
max = (long) ((JSONObject) loot).get("max");
min = (long) ((JSONObject) loot).get("min");
}
functions.add(new AmountFunction(FastMath.toIntExact(min), FastMath.toIntExact(max)));
}
case "minecraft:set_damage", "set_damage" -> {
long maxDamage = (long) ((JSONObject) ((JSONObject) function).get("damage")).get("max");
long minDamage = (long) ((JSONObject) ((JSONObject) function).get("damage")).get("min");
functions.add(new DamageFunction(FastMath.toIntExact(minDamage), FastMath.toIntExact(maxDamage)));
}
case "minecraft:enchant_with_levels", "enchant_with_levels" -> {
long maxEnchant = (long) ((JSONObject) ((JSONObject) function).get("levels")).get("max");
long minEnchant = (long) ((JSONObject) ((JSONObject) function).get("levels")).get("min");
JSONArray disabled = null;
if(((JSONObject) function).containsKey("disabled_enchants"))
disabled = (JSONArray) ((JSONObject) function).get("disabled_enchants");
functions.add(
new EnchantFunction(FastMath.toIntExact(minEnchant), FastMath.toIntExact(maxEnchant), disabled, platform));
}
}
}
}
}
/**
* Fetches a single ItemStack from the Entry, applying all functions to it.
*
* @param r The Random instance to apply functions with
*
* @return ItemStack - The ItemStack with all functions applied.
*/
public ItemStack getItem(Random r) {
ItemStack item = this.item.newItemStack(1);
for(LootFunction f : functions) {
item = f.apply(item, r);
}
return item;
}
/**
* Gets the weight attribute of the Entry.
*
* @return long - The weight of the Entry.
*/
public long getWeight() {
return this.weight;
}
}

View File

@ -0,0 +1,73 @@
package com.dfsek.terra.addons.structure.structures.loot;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import com.dfsek.terra.api.Platform;
import com.dfsek.terra.api.inventory.Inventory;
import com.dfsek.terra.api.inventory.ItemStack;
/**
* Class representation of a Loot Table to populate chest loot.
*/
public class LootTableImpl implements com.dfsek.terra.api.structure.LootTable {
private final List<Pool> pools = new ArrayList<>();
/**
* Instantiates a LootTable from a JSON String.
*
* @param json The JSON String representing the loot table.
*
* @throws ParseException if malformed JSON is passed.
*/
public LootTableImpl(String json, Platform platform) throws ParseException {
JSONParser jsonParser = new JSONParser();
Object tableJSON = jsonParser.parse(json);
JSONArray poolArray = (JSONArray) ((JSONObject) tableJSON).get("pools");
for(Object pool : poolArray) {
pools.add(new Pool((JSONObject) pool, platform));
}
}
@Override
public void fillInventory(Inventory i, Random r) {
List<ItemStack> loot = getLoot(r);
for(ItemStack stack : loot) {
int attempts = 0;
while(stack.getAmount() != 0 && attempts < 10) {
ItemStack newStack = stack.getType().newItemStack(stack.getAmount());
newStack.setItemMeta(stack.getItemMeta());
newStack.setAmount(1);
int slot = r.nextInt(i.getSize());
ItemStack slotItem = i.getItem(slot);
if(slotItem == null) {
i.setItem(slot, newStack);
stack.setAmount(stack.getAmount() - 1);
} else if(slotItem.getType().equals(newStack.getType())) {
ItemStack dep = newStack.getType().newItemStack(newStack.getAmount());
dep.setItemMeta(newStack.getItemMeta());
dep.setAmount(newStack.getAmount() + slotItem.getAmount());
i.setItem(slot, dep);
stack.setAmount(stack.getAmount() - 1);
}
attempts++;
}
}
}
@Override
public List<ItemStack> getLoot(Random r) {
List<ItemStack> itemList = new ArrayList<>();
for(Pool pool : pools) {
itemList.addAll(pool.getItems(r));
}
return itemList;
}
}

View File

@ -0,0 +1,63 @@
package com.dfsek.terra.addons.structure.structures.loot;
import com.dfsek.terra.api.Platform;
import net.jafama.FastMath;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import com.dfsek.terra.api.inventory.ItemStack;
import com.dfsek.terra.api.util.collection.ProbabilityCollection;
/**
* Representation of a Loot Table pool, or a set of items to be fetched independently.
*/
public class Pool {
private final int max;
private final int min;
private final ProbabilityCollection<Entry> entries;
/**
* Instantiates a Pool from a JSON representation.
*
* @param pool The JSON Object to instantiate from.
*/
public Pool(JSONObject pool, Platform platform) {
entries = new ProbabilityCollection<>();
Object amount = pool.get("rolls");
if(amount instanceof Long) {
max = FastMath.toIntExact((Long) amount);
min = FastMath.toIntExact((Long) amount);
} else {
max = FastMath.toIntExact((Long) ((JSONObject) amount).get("max"));
min = FastMath.toIntExact((Long) ((JSONObject) amount).get("min"));
}
for(Object entryJSON : (JSONArray) pool.get("entries")) {
Entry entry = new Entry((JSONObject) entryJSON, platform);
entries.add(entry, FastMath.toIntExact(entry.getWeight()));
}
}
/**
* Fetches a list of items from the pool using the provided Random instance.
*
* @param r The Random instance to use.
*
* @return List&lt;ItemStack&gt; - The list of items fetched.
*/
public List<ItemStack> getItems(Random r) {
int rolls = r.nextInt(max - min + 1) + min;
List<ItemStack> items = new ArrayList<>();
for(int i = 0; i < rolls; i++) {
items.add(entries.get(r).getItem(r));
}
return items;
}
}

View File

@ -0,0 +1,40 @@
package com.dfsek.terra.addons.structure.structures.loot.functions;
import java.util.Random;
import com.dfsek.terra.api.inventory.ItemStack;
/**
* Loot LootFunction fot setting the amount of an item.
*/
public class AmountFunction implements LootFunction {
private final int max;
private final int min;
/**
* Instantiates an AmountFunction.
*
* @param min Minimum amount.
* @param max Maximum amount.
*/
public AmountFunction(int min, int max) {
this.min = min;
this.max = max;
}
/**
* Applies the function to an ItemStack.
*
* @param original The ItemStack on which to apply the function.
* @param r The Random instance to use.
*
* @return - ItemStack - The mutated ItemStack.
*/
@Override
public ItemStack apply(ItemStack original, Random r) {
original.setAmount(r.nextInt(max - min + 1) + min);
return original;
}
}

View File

@ -0,0 +1,47 @@
package com.dfsek.terra.addons.structure.structures.loot.functions;
import java.util.Random;
import com.dfsek.terra.api.inventory.ItemStack;
import com.dfsek.terra.api.inventory.item.Damageable;
import com.dfsek.terra.api.inventory.item.ItemMeta;
/**
* Loot LootFunction for setting the damage on items in Loot Tables
*/
public class DamageFunction implements LootFunction {
private final int max;
private final int min;
/**
* Instantiates a DamageFunction.
*
* @param min Minimum amount of damage (percentage, out of 100)
* @param max Maximum amount of damage (percentage, out of 100)
*/
public DamageFunction(int min, int max) {
this.min = min;
this.max = max;
}
/**
* Applies the function to an ItemStack.
*
* @param original The ItemStack on which to apply the function.
* @param r The Random instance to use.
*
* @return - ItemStack - The mutated ItemStack.
*/
@Override
public ItemStack apply(ItemStack original, Random r) {
if(original == null) return null;
if(!original.isDamageable()) return original;
ItemMeta meta = original.getItemMeta();
double itemDurability = (r.nextDouble() * (max - min)) + min;
Damageable damage = (Damageable) meta;
damage.setDamage((int) (original.getType().getMaxDurability() - (itemDurability / 100) * original.getType().getMaxDurability()));
original.setItemMeta((ItemMeta) damage);
return original;
}
}

View File

@ -0,0 +1,72 @@
package com.dfsek.terra.addons.structure.structures.loot.functions;
import com.dfsek.terra.api.Platform;
import net.jafama.FastMath;
import org.json.simple.JSONArray;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import com.dfsek.terra.api.inventory.ItemStack;
import com.dfsek.terra.api.inventory.item.Enchantment;
import com.dfsek.terra.api.inventory.item.ItemMeta;
public class EnchantFunction implements LootFunction {
private final int min;
private final int max;
private final JSONArray disabled;
private final Platform platform;
public EnchantFunction(int min, int max, JSONArray disabled, Platform platform) {
this.max = max;
this.min = min;
this.disabled = disabled;
this.platform = platform;
}
/**
* Applies the function to an ItemStack.
*
* @param original The ItemStack on which to apply the function.
* @param r The Random instance to use.
*
* @return - ItemStack - The mutated ItemStack.
*/
@Override
public ItemStack apply(ItemStack original, Random r) {
if(original.getItemMeta() == null) return original;
double enchant = (r.nextDouble() * (max - min)) + min;
List<Enchantment> possible = new ArrayList<>();
for(Enchantment ench : platform.getItemHandle().getEnchantments()) {
if(ench.canEnchantItem(original) && (disabled == null || !this.disabled.contains(ench.getID()))) {
possible.add(ench);
}
}
int numEnchant = (r.nextInt((int) FastMath.abs(enchant)) / 10 + 1);
Collections.shuffle(possible);
ItemMeta meta = original.getItemMeta();
iter:
for(int i = 0; i < numEnchant && i < possible.size(); i++) {
Enchantment chosen = possible.get(i);
for(Enchantment ench : meta.getEnchantments().keySet()) {
if(chosen.conflictsWith(ench)) continue iter;
}
int lvl = r.nextInt(1 + (int) (((enchant / 40 > 1) ? 1 : enchant / 40) * (chosen.getMaxLevel())));
try {
meta.addEnchantment(chosen, FastMath.max(lvl, 1));
} catch(IllegalArgumentException e) {
platform.logger().warning(
"Attempted to enchant " + original.getType() + " with " + chosen + " at level " + FastMath.max(lvl, 1) +
", but an unexpected exception occurred! Usually this is caused by a misbehaving enchantment plugin.");
}
}
original.setItemMeta(meta);
return original;
}
}

View File

@ -0,0 +1,22 @@
package com.dfsek.terra.addons.structure.structures.loot.functions;
import java.util.Random;
import com.dfsek.terra.api.inventory.ItemStack;
/**
* Interface for mutating items in Loot Tables.
*/
public interface LootFunction {
/**
* Applies the function to an ItemStack.
*
* @param original The ItemStack on which to apply the function.
* @param r The Random instance to use.
*
* @return - ItemStack - The mutated ItemStack.
*/
ItemStack apply(ItemStack original, Random r);
}