Moved to gradle & started work on separating API and plugin
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
package net.nuggetmc.tplus.api;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.nuggetmc.tplus.api.agent.legacyagent.ai.NeuralNetwork;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.inventory.EquipmentSlot;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
public interface Terminator {
|
||||
|
||||
String getBotName();
|
||||
|
||||
GameProfile getGameProfile();
|
||||
|
||||
NeuralNetwork getNeuralNetwork();
|
||||
|
||||
void setNeuralNetwork(NeuralNetwork neuralNetwork);
|
||||
|
||||
boolean hasNeuralNetwork();
|
||||
|
||||
Location getLocation();
|
||||
|
||||
float getHealth();
|
||||
|
||||
float getMaxHealth();
|
||||
|
||||
void ignite();
|
||||
|
||||
boolean isOnFire();
|
||||
|
||||
boolean isFalling();
|
||||
|
||||
boolean isBlocking();
|
||||
|
||||
void block(int length, int cooldown);
|
||||
|
||||
boolean isInWater();
|
||||
|
||||
void jump(Vector velocity);
|
||||
|
||||
void jump();
|
||||
|
||||
void walk(Vector velocity);
|
||||
|
||||
void look(BlockFace face);
|
||||
|
||||
void attack(Entity target);
|
||||
|
||||
void attemptBlockPlace(Location loc, Material type, boolean down);
|
||||
|
||||
void punch();
|
||||
|
||||
void swim();
|
||||
|
||||
void sneak();
|
||||
|
||||
void stand();
|
||||
|
||||
void addFriction(double factor);
|
||||
|
||||
void removeVisually();
|
||||
|
||||
int getKills();
|
||||
|
||||
void incrementKills();
|
||||
|
||||
void setItem(ItemStack item);
|
||||
|
||||
void setItem(ItemStack item, EquipmentSlot slot);
|
||||
|
||||
void setItemOffhand(ItemStack item);
|
||||
|
||||
void setDefaultItem(ItemStack item);
|
||||
|
||||
Vector getOffset();
|
||||
|
||||
Vector getVelocity();
|
||||
|
||||
void setVelocity(Vector velocity);
|
||||
|
||||
void addVelocity(Vector velocity);
|
||||
|
||||
int getAliveTicks();
|
||||
|
||||
boolean tickDelay(int ticks);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package net.nuggetmc.tplus.api.agent.legacyagent.ai;
|
||||
|
||||
public enum ActivationType {
|
||||
TANH,
|
||||
SIN_X,
|
||||
SIN_X2,
|
||||
COS_X,
|
||||
COS_X2
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package net.nuggetmc.tplus.api.agent.legacyagent.ai;
|
||||
|
||||
import net.nuggetmc.tplus.api.Terminator;
|
||||
import net.nuggetmc.tplus.utils.MathUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.LivingEntity;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
// If this is laggy, try only instantiating this once and update it instead of creating a new instance every tick
|
||||
public class BotData {
|
||||
|
||||
private final Map<BotDataType, Double> values;
|
||||
|
||||
private BotData(Terminator bot, LivingEntity target) {
|
||||
this.values = new HashMap<>();
|
||||
|
||||
Location a = bot.getLocation();
|
||||
Location b = target.getLocation();
|
||||
|
||||
float health = bot.getHealth();
|
||||
|
||||
values.put(BotDataType.CRITICAL_HEALTH, health >= 5 ? 0 : 5D - health);
|
||||
values.put(BotDataType.DISTANCE_XZ, Math.sqrt(MathUtils.square(a.getX() - b.getX()) + MathUtils.square(a.getZ() - b.getZ())));
|
||||
values.put(BotDataType.DISTANCE_Y, b.getY() - a.getY());
|
||||
values.put(BotDataType.ENEMY_BLOCKING, target instanceof Player && ((Player) target).isBlocking() ? 1D : 0);
|
||||
}
|
||||
|
||||
public static BotData generate(Terminator bot, LivingEntity target) {
|
||||
return new BotData(bot, target);
|
||||
}
|
||||
|
||||
public Map<BotDataType, Double> getValues() {
|
||||
return values;
|
||||
}
|
||||
|
||||
public double getValue(BotDataType dataType) {
|
||||
return values.get(dataType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
List<String> strings = new ArrayList<>();
|
||||
|
||||
values.forEach((type, value) -> strings.add(type.name() + "=" + MathUtils.round2Dec(value)));
|
||||
|
||||
Collections.sort(strings);
|
||||
|
||||
return "BotData{" + StringUtils.join(strings, ",") + "}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package net.nuggetmc.tplus.api.agent.legacyagent.ai;
|
||||
|
||||
public enum BotDataType {
|
||||
CRITICAL_HEALTH("h"),
|
||||
DISTANCE_XZ("xz"),
|
||||
DISTANCE_Y("y"),
|
||||
ENEMY_BLOCKING("b");
|
||||
|
||||
private final String shorthand;
|
||||
|
||||
BotDataType(String shorthand) {
|
||||
this.shorthand = shorthand;
|
||||
}
|
||||
|
||||
public String getShorthand() {
|
||||
return shorthand;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package net.nuggetmc.tplus.api.agent.legacyagent.ai;
|
||||
|
||||
public enum BotNode {
|
||||
BLOCK, // block (can't attack while blocking)
|
||||
JUMP, // jump
|
||||
LEFT, // left strafe
|
||||
RIGHT // right strafe (if L and R are opposite, move forward)
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package net.nuggetmc.tplus.api.agent.legacyagent.ai;
|
||||
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.nuggetmc.tplus.utils.MathUtils;
|
||||
import net.nuggetmc.tplus.utils.ChatUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class NeuralNetwork {
|
||||
|
||||
// thinking about making an enum called BotNode, and have a map here, .check(Node.L) or fetch
|
||||
// also randomize activation point between 0 and 0.5
|
||||
// randomize the blocking length and cooldown
|
||||
// also the XZ offset randomizers!! (or maybe just turn them off entirely, that works too)
|
||||
|
||||
// b, j, l, r, ox, oz, av, bl, bc, dlr
|
||||
|
||||
private final Map<BotNode, NodeConnections> nodes;
|
||||
|
||||
private final boolean dynamicLR;
|
||||
|
||||
public static final NeuralNetwork RANDOM = new NeuralNetwork(new HashMap<>());
|
||||
|
||||
private NeuralNetwork(Map<BotNode, Map<BotDataType, Double>> profile) {
|
||||
this.nodes = new HashMap<>();
|
||||
this.dynamicLR = true;
|
||||
|
||||
if (profile == null) {
|
||||
Arrays.stream(BotNode.values()).forEach(n -> this.nodes.put(n, new NodeConnections()));
|
||||
} else {
|
||||
profile.forEach((nodeType, map) -> nodes.put(nodeType, new NodeConnections(map)));
|
||||
}
|
||||
}
|
||||
|
||||
public static NeuralNetwork createNetworkFromProfile(Map<BotNode, Map<BotDataType, Double>> profile) {
|
||||
return new NeuralNetwork(profile);
|
||||
}
|
||||
|
||||
public static NeuralNetwork generateRandomNetwork() {
|
||||
return new NeuralNetwork(null);
|
||||
}
|
||||
|
||||
public NodeConnections fetch(BotNode node) {
|
||||
return nodes.get(node);
|
||||
}
|
||||
|
||||
public boolean check(BotNode node) {
|
||||
return nodes.get(node).check();
|
||||
}
|
||||
|
||||
public double value(BotNode node) {
|
||||
return nodes.get(node).value();
|
||||
}
|
||||
|
||||
public void feed(BotData data) {
|
||||
nodes.values().forEach(n -> n.test(data));
|
||||
}
|
||||
|
||||
public Map<BotNode, NodeConnections> nodes() {
|
||||
return nodes;
|
||||
}
|
||||
|
||||
public boolean dynamicLR() {
|
||||
return dynamicLR;
|
||||
}
|
||||
|
||||
public Map<BotNode, Map<BotDataType, Double>> values() {
|
||||
Map<BotNode, Map<BotDataType, Double>> output = new HashMap<>();
|
||||
nodes.forEach((nodeType, node) -> output.put(nodeType, node.getValues()));
|
||||
return output;
|
||||
}
|
||||
|
||||
public String output() {
|
||||
List<String> strings = new ArrayList<>();
|
||||
nodes.forEach((type, node) -> strings.add(type.name().toLowerCase() + "=" + (node.check() ? ChatUtils.ON + "1" : ChatUtils.OFF + "0") + ChatColor.RESET));
|
||||
Collections.sort(strings);
|
||||
return "[" + StringUtils.join(strings, ", ") + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
List<String> strings = new ArrayList<>();
|
||||
|
||||
nodes.forEach((nodeType, node) -> {
|
||||
List<String> values = new ArrayList<>();
|
||||
values.add("name=\"" + nodeType.name().toLowerCase() + "\"");
|
||||
node.getValues().forEach((dataType, value) -> values.add(dataType.getShorthand() + "=" + MathUtils.round2Dec(value)));
|
||||
strings.add("{" + StringUtils.join(values, ",") + "}");
|
||||
});
|
||||
|
||||
return "NeuralNetwork{nodes:[" + StringUtils.join(strings, ",") + "]}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package net.nuggetmc.tplus.api.agent.legacyagent.ai;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
public class NodeConnections {
|
||||
|
||||
/*
|
||||
* more node ideas
|
||||
* how much the bot is to the left or right of target (not horizontal distance)
|
||||
* bot velocity?
|
||||
* if the player is temporarily invincible (has damage ticks > 0)
|
||||
*/
|
||||
|
||||
private final Map<BotDataType, Double> connections;
|
||||
|
||||
private boolean active;
|
||||
|
||||
private double value;
|
||||
|
||||
public NodeConnections() {
|
||||
this.connections = new HashMap<>();
|
||||
|
||||
Arrays.stream(BotDataType.values()).forEach(type -> connections.put(type, generateValue()));
|
||||
}
|
||||
|
||||
public NodeConnections(Map<BotDataType, Double> values) {
|
||||
this.connections = values;
|
||||
}
|
||||
|
||||
private double generateValue() {
|
||||
return ThreadLocalRandom.current().nextDouble(-10, 10);
|
||||
}
|
||||
|
||||
public boolean check() {
|
||||
return active;
|
||||
}
|
||||
|
||||
public double value() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public Map<BotDataType, Double> getValues() {
|
||||
return connections;
|
||||
}
|
||||
|
||||
public double getValue(BotDataType dataType) {
|
||||
return connections.get(dataType);
|
||||
}
|
||||
|
||||
/*
|
||||
* maybe a sinusoidal activation function?
|
||||
* maybe generate a random activation function?
|
||||
* definitely something less.. broad
|
||||
*/
|
||||
public void test(BotData data) {
|
||||
this.activationFunction(data);
|
||||
this.active = this.value >= 0.5;
|
||||
}
|
||||
|
||||
/*
|
||||
* try sin, sin x^2, cos, cos x^2
|
||||
*/
|
||||
private void activationFunction(BotData data) {
|
||||
this.value = Math.tanh(data.getValues().entrySet().stream().mapToDouble(entry -> connections.get(entry.getKey()) * entry.getValue()).sum());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package net.nuggetmc.tplus.utils;
|
||||
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
import java.util.Locale;
|
||||
|
||||
public class ChatUtils {
|
||||
public static final String LINE = ChatColor.GRAY + "------------------------------------------------";
|
||||
public static final String BULLET = "▪";
|
||||
public static final String BULLET_FORMATTED = ChatColor.GRAY + " ▪ " + ChatColor.RESET;
|
||||
public static final String EXCEPTION_MESSAGE = ChatColor.RED + "An exception has occured. Please try again.";
|
||||
|
||||
public static final NumberFormat NUMBER_FORMAT = NumberFormat.getNumberInstance(Locale.US);
|
||||
|
||||
public static final String ON = ChatColor.GREEN.toString();
|
||||
public static final String OFF = ChatColor.GRAY.toString();
|
||||
|
||||
public static String trim16(String str) {
|
||||
return str.length() > 16 ? str.substring(0, 16) : str;
|
||||
}
|
||||
|
||||
public static String camelToDashed(String input) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
|
||||
for (char ch : input.toCharArray()) {
|
||||
if (Character.isUpperCase(ch)) {
|
||||
result.append("-").append(Character.toLowerCase(ch));
|
||||
} else {
|
||||
result.append(ch);
|
||||
}
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
package net.nuggetmc.tplus.utils;
|
||||
|
||||
import net.nuggetmc.tplus.api.Terminator;
|
||||
import org.bukkit.util.NumberConversions;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class MathUtils {
|
||||
|
||||
public static final Random RANDOM = new Random();
|
||||
public static final DecimalFormat FORMATTER_1 = new DecimalFormat("0.#");
|
||||
public static final DecimalFormat FORMATTER_2 = new DecimalFormat("0.##");
|
||||
|
||||
public static float[] fetchYawPitch(Vector dir) {
|
||||
double x = dir.getX();
|
||||
double z = dir.getZ();
|
||||
|
||||
float[] out = new float[2];
|
||||
|
||||
if (x == 0.0D && z == 0.0D) {
|
||||
out[1] = (float) (dir.getY() > 0.0D ? -90 : 90);
|
||||
} else {
|
||||
double theta = Math.atan2(-x, z);
|
||||
out[0] = (float) Math.toDegrees((theta + 6.283185307179586D) % 6.283185307179586D);
|
||||
|
||||
double x2 = NumberConversions.square(x);
|
||||
double z2 = NumberConversions.square(z);
|
||||
double xz = Math.sqrt(x2 + z2);
|
||||
out[1] = (float) Math.toDegrees(Math.atan(-dir.getY() / xz));
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
public static float fetchPitch(Vector dir) {
|
||||
double x = dir.getX();
|
||||
double z = dir.getZ();
|
||||
|
||||
float result;
|
||||
|
||||
if (x == 0.0D && z == 0.0D) {
|
||||
result = (float) (dir.getY() > 0.0D ? -90 : 90);
|
||||
} else {
|
||||
double x2 = NumberConversions.square(x);
|
||||
double z2 = NumberConversions.square(z);
|
||||
double xz = Math.sqrt(x2 + z2);
|
||||
result = (float) Math.toDegrees(Math.atan(-dir.getY() / xz));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Vector circleOffset(double r) {
|
||||
double rad = 2 * Math.random() * Math.PI;
|
||||
|
||||
double x = r * Math.random() * Math.cos(rad);
|
||||
double z = r * Math.random() * Math.sin(rad);
|
||||
|
||||
return new Vector(x, 0, z);
|
||||
}
|
||||
|
||||
public static boolean isNotFinite(Vector vector) {
|
||||
return !NumberConversions.isFinite(vector.getX()) || !NumberConversions.isFinite(vector.getY()) || !NumberConversions.isFinite(vector.getZ());
|
||||
}
|
||||
|
||||
public static void clean(Vector vector) {
|
||||
if (!NumberConversions.isFinite(vector.getX())) vector.setX(0);
|
||||
if (!NumberConversions.isFinite(vector.getY())) vector.setY(0);
|
||||
if (!NumberConversions.isFinite(vector.getZ())) vector.setZ(0);
|
||||
}
|
||||
|
||||
public static <E> E getRandomSetElement(Set<E> set) {
|
||||
return set.isEmpty() ? null : set.stream().skip(RANDOM.nextInt(set.size())).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
public static double square(double n) {
|
||||
return n * n;
|
||||
}
|
||||
|
||||
public static String round1Dec(double n) {
|
||||
return FORMATTER_1.format(n);
|
||||
}
|
||||
|
||||
public static String round2Dec(double n) {
|
||||
return FORMATTER_2.format(n);
|
||||
}
|
||||
|
||||
public static List<Map.Entry<Terminator, Integer>> sortByValue(HashMap<Terminator, Integer> hm) {
|
||||
List<Map.Entry<Terminator, Integer>> list = new LinkedList<>(hm.entrySet());
|
||||
list.sort(Map.Entry.comparingByValue());
|
||||
Collections.reverse(list);
|
||||
return list;
|
||||
}
|
||||
|
||||
public static double generateConnectionValue(List<Double> list, double mutationSize) {
|
||||
double[] bounds = getBounds(list, mutationSize);
|
||||
return random(bounds[0], bounds[1]);
|
||||
}
|
||||
|
||||
public static double generateConnectionValue(List<Double> list) {
|
||||
return generateConnectionValue(list, 0);
|
||||
}
|
||||
|
||||
public static double random(double low, double high) {
|
||||
return Math.random() * (high - low) + low;
|
||||
}
|
||||
|
||||
public static double sum(List<Double> list) {
|
||||
return list.stream().mapToDouble(n -> n).sum();
|
||||
}
|
||||
|
||||
public static double min(List<Double> list) {
|
||||
if (list.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
double min = Double.MAX_VALUE;
|
||||
|
||||
for (double n : list) {
|
||||
if (n < min) {
|
||||
min = n;
|
||||
}
|
||||
}
|
||||
|
||||
return min;
|
||||
}
|
||||
|
||||
public static double max(List<Double> list) {
|
||||
if (list.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
double max = 0;
|
||||
|
||||
for (double n : list) {
|
||||
if (n > max) {
|
||||
max = n;
|
||||
}
|
||||
}
|
||||
|
||||
return max;
|
||||
}
|
||||
|
||||
public static double getMidValue(List<Double> list) {
|
||||
return (min(list) + max(list)) / 2D;
|
||||
}
|
||||
|
||||
public static double distribution(List<Double> list, double mid) {
|
||||
return Math.sqrt(sum(list.stream().map(n -> Math.pow(n - mid, 2)).collect(Collectors.toList())) / list.size());
|
||||
}
|
||||
|
||||
public static double[] getBounds(List<Double> list, double mutationSize) {
|
||||
double mid = getMidValue(list);
|
||||
double dist = distribution(list, mid);
|
||||
double p = mutationSize * dist / Math.sqrt(list.size());
|
||||
|
||||
return new double[]{
|
||||
mid - p,
|
||||
mid + p
|
||||
};
|
||||
}
|
||||
|
||||
public static double getMutationSize(int generation) {
|
||||
int shift = 4;
|
||||
|
||||
if (generation <= shift + 1) {
|
||||
return 7.38905609893;
|
||||
}
|
||||
|
||||
double a = 0.8;
|
||||
double b = -8.5 - shift;
|
||||
double c = 2;
|
||||
|
||||
return Math.pow(a, generation + b) + c;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user