diff --git a/src/main/java/net/nuggetmc/ai/bot/Bot.java b/src/main/java/net/nuggetmc/ai/bot/Bot.java index e999dae..71614a6 100644 --- a/src/main/java/net/nuggetmc/ai/bot/Bot.java +++ b/src/main/java/net/nuggetmc/ai/bot/Bot.java @@ -5,11 +5,12 @@ import com.mojang.datafixers.util.Pair; import net.minecraft.server.v1_16_R3.Chunk; import net.minecraft.server.v1_16_R3.*; import net.nuggetmc.ai.TerminatorPlus; +import net.nuggetmc.ai.bot.agent.legacyagent.ai.NeuralNetwork; import net.nuggetmc.ai.bot.event.BotFallDamageEvent; import net.nuggetmc.ai.utils.BotUtils; import net.nuggetmc.ai.utils.MathUtils; import net.nuggetmc.ai.utils.MojangAPI; -import net.nuggetmc.ai.utils.StringUtils; +import net.nuggetmc.ai.utils.StringUtilities; import org.bukkit.Material; import org.bukkit.SoundCategory; import org.bukkit.World; @@ -32,12 +33,27 @@ import java.util.UUID; public class Bot extends EntityPlayer { - public boolean item; // eventually make this not garbage lol + private NeuralNetwork network; - public Vector velocity; + public NeuralNetwork getNeuralNetwork() { + return network; + } + + public void setNeuralNetwork(NeuralNetwork network) { + this.network = network; + } + + public boolean hasNeuralNetwork() { + return network != null; + } + + public boolean item; // eventually make this not garbage lol + public boolean shield; + + private Vector velocity; private Vector oldVelocity; - private boolean removeOnDeath; + private final boolean removeOnDeath; private byte aliveTicks; private byte fireTicks; @@ -55,22 +71,23 @@ public class Bot extends EntityPlayer { this.oldVelocity = velocity.clone(); this.noFallTicks = 60; this.fireTicks = 0; + this.removeOnDeath = true; this.offset = MathUtils.circleOffset(3); datawatcher.set(new DataWatcherObject<>(16, DataWatcherRegistry.a), (byte) 0xFF); } public static Bot createBot(Location loc, String name) { - return createBot(loc, name, MojangAPI.getSkin(name), true); + return createBot(loc, name, MojangAPI.getSkin(name)); } - public static Bot createBot(Location loc, String name, String[] skin, boolean removeOnDeath) { + public static Bot createBot(Location loc, String name, String[] skin) { MinecraftServer nmsServer = ((CraftServer) Bukkit.getServer()).getServer(); WorldServer nmsWorld = ((CraftWorld) Objects.requireNonNull(loc.getWorld())).getHandle(); UUID uuid = BotUtils.randomSteveUUID(); - CustomGameProfile profile = new CustomGameProfile(uuid, StringUtils.trim16(name), skin); + CustomGameProfile profile = new CustomGameProfile(uuid, StringUtilities.trim16(name), skin); PlayerInteractManager interactManager = new PlayerInteractManager(nmsWorld); Bot bot = new Bot(nmsServer, nmsWorld, profile, interactManager); @@ -78,7 +95,6 @@ public class Bot extends EntityPlayer { bot.playerConnection = new PlayerConnection(nmsServer, new NetworkManager(EnumProtocolDirection.CLIENTBOUND), bot); bot.setLocation(loc.getX(), loc.getY(), loc.getZ(), loc.getYaw(), loc.getPitch()); bot.getBukkitEntity().setNoDamageTicks(0); - bot.removeOnDeath = removeOnDeath; nmsWorld.addEntity(bot); bot.renderAll(); @@ -268,6 +284,10 @@ public class Bot extends EntityPlayer { return velocity.getY() < -0.8; } + public void block() { + // block for 10 ticks, cooldown for 10 extra ticks (20 total) + } + private void updateLocation() { double y; @@ -321,6 +341,15 @@ public class Bot extends EntityPlayer { jump(new Vector(0, 0.5, 0)); } + public void walk(Vector vel) { + double max = 0.4; + + Vector sum = velocity.clone().add(vel); + if (sum.length() > max) sum.normalize().multiply(max); + + velocity = sum; + } + public void attack(org.bukkit.entity.Entity entity) { faceLocation(entity.getLocation()); punch(); @@ -407,9 +436,11 @@ public class Bot extends EntityPlayer { private void dieCheck() { if (removeOnDeath) { TerminatorPlus plugin = TerminatorPlus.getInstance(); - plugin.getManager().remove(this); - this.removeTab(); + + Bukkit.getScheduler().runTask(plugin, () -> plugin.getManager().remove(this)); Bukkit.getScheduler().runTaskLater(plugin, this::setDead, 30); + + this.removeTab(); } } @@ -477,12 +508,9 @@ public class Bot extends EntityPlayer { } private void kb(Location loc1, Location loc2) { - double kbUp = 0.3; - Vector vel = loc1.toVector().subtract(loc2.toVector()).setY(0).normalize().multiply(0.3); if (isOnGround()) vel.multiply(0.8).setY(0.4); - else if (vel.getY() > kbUp) vel.setY(kbUp); velocity = vel; kbTicks = 10; diff --git a/src/main/java/net/nuggetmc/ai/bot/BotManager.java b/src/main/java/net/nuggetmc/ai/bot/BotManager.java index 2787d94..3200e39 100644 --- a/src/main/java/net/nuggetmc/ai/bot/BotManager.java +++ b/src/main/java/net/nuggetmc/ai/bot/BotManager.java @@ -3,6 +3,8 @@ package net.nuggetmc.ai.bot; import net.minecraft.server.v1_16_R3.PlayerConnection; import net.nuggetmc.ai.bot.agent.Agent; import net.nuggetmc.ai.bot.agent.legacyagent.LegacyAgent; +import net.nuggetmc.ai.bot.agent.legacyagent.ai.NetworkType; +import net.nuggetmc.ai.bot.agent.legacyagent.ai.NeuralNetwork; import net.nuggetmc.ai.utils.MojangAPI; import org.bukkit.*; import org.bukkit.craftbukkit.v1_16_R3.entity.CraftPlayer; @@ -59,6 +61,10 @@ public class BotManager implements Listener { } public void createBots(Player sender, String name, String skinName, int n) { + createBots(sender, name, skinName, n, null); + } + + public void createBots(Player sender, String name, String skinName, int n, NetworkType type) { long timestamp = System.currentTimeMillis(); if (n < 1) n = 1; @@ -78,8 +84,15 @@ public class BotManager implements Listener { String[] skin = MojangAPI.getSkin(skinName); for (int i = 1; i <= n; i++) { - Bot bot = Bot.createBot(loc, name.replace("%", String.valueOf(i)), skin, removeOnDeath); - if (i > 1) bot.setVelocity(new Vector(Math.random() - 0.5, 0.5, Math.random() - 0.5).normalize().multiply(f)); + Bot bot = Bot.createBot(loc, name.replace("%", String.valueOf(i)), skin); + + if (i > 1) { + bot.setVelocity(new Vector(Math.random() - 0.5, 0.5, Math.random() - 0.5).normalize().multiply(f)); + } + + if (type == NetworkType.RANDOM) { + bot.setNeuralNetwork(NeuralNetwork.generateRandomNetwork()); + } } world.spawnParticle(Particle.CLOUD, loc, 100, 1, 1, 1, 0.5); diff --git a/src/main/java/net/nuggetmc/ai/bot/agent/Agent.java b/src/main/java/net/nuggetmc/ai/bot/agent/Agent.java index dfb5dfa..65ea948 100644 --- a/src/main/java/net/nuggetmc/ai/bot/agent/Agent.java +++ b/src/main/java/net/nuggetmc/ai/bot/agent/Agent.java @@ -48,16 +48,11 @@ public abstract class Agent { } public void stopAllTasks() { - taskList.forEach(t -> { - if (!t.isCancelled()) { - t.cancel(); - } - }); + taskList.stream().filter(t -> !t.isCancelled()).forEach(BukkitRunnable::cancel); taskList.clear(); } protected abstract void tick(); - public void onFallDamage(BotFallDamageEvent event) { - } + public void onFallDamage(BotFallDamageEvent event) { } } diff --git a/src/main/java/net/nuggetmc/ai/bot/agent/botagent/BotAgent.java b/src/main/java/net/nuggetmc/ai/bot/agent/botagent/BotAgent.java index 58850a6..0e71271 100644 --- a/src/main/java/net/nuggetmc/ai/bot/agent/botagent/BotAgent.java +++ b/src/main/java/net/nuggetmc/ai/bot/agent/botagent/BotAgent.java @@ -100,7 +100,7 @@ public class BotAgent extends Agent { bot.setItem(null); // method to check item in main hand, bot.getItemInHand() try { - vel.add(bot.velocity); + vel.add(bot.getVelocity()); } catch (IllegalArgumentException e) { if (MathUtils.isNotFinite(vel)) { MathUtils.clean(vel); diff --git a/src/main/java/net/nuggetmc/ai/bot/agent/legacyagent/LegacyAgent.java b/src/main/java/net/nuggetmc/ai/bot/agent/legacyagent/LegacyAgent.java index 4e9249e..a5ef3cb 100644 --- a/src/main/java/net/nuggetmc/ai/bot/agent/legacyagent/LegacyAgent.java +++ b/src/main/java/net/nuggetmc/ai/bot/agent/legacyagent/LegacyAgent.java @@ -5,6 +5,9 @@ import net.minecraft.server.v1_16_R3.PacketPlayOutBlockBreakAnimation; import net.nuggetmc.ai.bot.Bot; import net.nuggetmc.ai.bot.BotManager; import net.nuggetmc.ai.bot.agent.Agent; +import net.nuggetmc.ai.bot.agent.legacyagent.ai.BotData; +import net.nuggetmc.ai.bot.agent.legacyagent.ai.BotNode; +import net.nuggetmc.ai.bot.agent.legacyagent.ai.NeuralNetwork; import net.nuggetmc.ai.bot.event.BotFallDamageEvent; import net.nuggetmc.ai.utils.MathUtils; import net.nuggetmc.ai.utils.PlayerUtils; @@ -19,14 +22,17 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.util.Vector; -import java.util.*; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; // Yes, this code is very unoptimized, I know. public class LegacyAgent extends Agent { private final LegacyBlockCheck blockCheck; - private boolean useAIManipulators; + public boolean offsets = true; public LegacyAgent(BotManager manager) { super(manager); @@ -55,13 +61,7 @@ public class LegacyAgent extends Agent { @Override protected void tick() { - try { - manager.fetch().forEach(this::tickBot); - } catch (ConcurrentModificationException e) { - // Yes this is a really bad way to deal with this issue, but in the future I will have a thing - // where when bots die they will be added to a cleanup cache that will be ticked after this (which will be refactored - // to the BotManager) and will be removed separately from the set. - } + manager.fetch().forEach(this::tickBot); } private void center(Bot bot) { @@ -69,24 +69,24 @@ public class LegacyAgent extends Agent { return; } - final Player playerBot = bot.getBukkitEntity(); + final Player botPlayer = bot.getBukkitEntity(); Location prev = null; - if (btList.containsKey(playerBot)) { - prev = btList.get(playerBot); + if (btList.containsKey(botPlayer)) { + prev = btList.get(botPlayer); } - Location loc = playerBot.getLocation(); + Location loc = botPlayer.getLocation(); if (prev != null) { if (loc.getBlockX() == prev.getBlockX() && loc.getBlockZ() == prev.getBlockZ()) { - btCheck.put(playerBot, true); + btCheck.put(botPlayer, true); } else { - btCheck.put(playerBot, false); + btCheck.put(botPlayer, false); } } - btList.put(playerBot, loc); + btList.put(botPlayer, loc); } private void tickBot(Bot bot) { @@ -110,15 +110,34 @@ public class LegacyAgent extends Agent { fallDamageCheck(bot); miscellaneousChecks(bot, player); - Player playerBot = bot.getBukkitEntity(); - Location target = player.getLocation().add(bot.getOffset()); + Player botPlayer = bot.getBukkitEntity(); - if (bot.tickDelay(3) && !miningAnim.containsKey(playerBot)) { - Location a = playerBot.getEyeLocation(); + Location target = offsets ? player.getLocation().add(bot.getOffset()) : player.getLocation(); + + NeuralNetwork network; + boolean ai = bot.hasNeuralNetwork(); + + if (ai) { + BotData data = BotData.generate(bot, player); + + network = bot.getNeuralNetwork(); + network.feed(data); + } else { + network = null; + } + + if (bot.tickDelay(3) && !miningAnim.containsKey(botPlayer)) { + Location a = botPlayer.getEyeLocation(); Location b = player.getEyeLocation(); Location c1 = player.getLocation(); - if (!LegacyUtils.checkIfBlocksOnVector(a, b) || !LegacyUtils.checkIfBlocksOnVector(a, c1)) { + if (ai) { // force unable to block if they are more than 6/7 blocks away + if (network.check(BotNode.BLOCK) && loc.distance(player.getLocation()) < 6) { + bot.block(); + } + } + + if (LegacyUtils.checkFreeSpace(a, b) || LegacyUtils.checkFreeSpace(a, c1)) { attack(bot, player, loc); } } @@ -126,17 +145,17 @@ public class LegacyAgent extends Agent { boolean waterGround = (LegacyMats.WATER.contains(loc.clone().add(0, -0.1, 0).getBlock().getType()) && !LegacyMats.AIR.contains(loc.clone().add(0, -0.6, 0).getBlock().getType())); - boolean c = false, lc = false; + boolean withinTargetXZ = false, sameXZ = false; - if (btCheck.containsKey(playerBot)) lc = btCheck.get(playerBot); + if (btCheck.containsKey(botPlayer)) sameXZ = btCheck.get(botPlayer); - if (waterGround || bot.isOnGround() || onBoat(playerBot)) { - byte j = 1; + if (waterGround || bot.isOnGround() || onBoat(botPlayer)) { + byte sideResult = 1; - if (towerList.containsKey(playerBot)) { + if (towerList.containsKey(botPlayer)) { if (loc.getBlockY() > player.getLocation().getBlockY()) { - towerList.remove(playerBot); - resetHand(bot, player, playerBot); + towerList.remove(botPlayer); + resetHand(bot, player, botPlayer); } } @@ -144,47 +163,100 @@ public class LegacyAgent extends Agent { if (Math.abs(loc.getBlockX() - target.getBlockX()) <= 3 && Math.abs(loc.getBlockZ() - target.getBlockZ()) <= 3) { - c = true; + withinTargetXZ = true; } - boolean bc = c || lc; + boolean bothXZ = withinTargetXZ || sameXZ; - // make this not destroy in scenarios where the bot can move out of the place - if (checkAt(bot, block, playerBot)) { - return; + if (checkAt(bot, block, botPlayer)) return; + + if (checkFence(bot, loc.getBlock(), botPlayer)) return; + + if (checkDown(bot, botPlayer, player.getLocation(), bothXZ)) return; + + if ((withinTargetXZ || sameXZ) && checkUp(bot, player, botPlayer, target, withinTargetXZ)) return; + + if (bothXZ) sideResult = checkSide(bot, player, botPlayer); + + switch (sideResult) { + case 1: + resetHand(bot, player, botPlayer); + if (!noJump.contains(botPlayer) && !waterGround) move(bot, player, loc, target, ai); + return; + + case 2: + if (!waterGround) move(bot, player, loc, target, ai); } - - else if (checkFence(bot, loc.getBlock(), playerBot)) { - return; - } - - else if (checkDown(bot, playerBot, player.getLocation(), bc)) { - return; - } - - else if ((c || lc) && checkUp(bot, player, playerBot, target, c)) { - return; - } - - else { - if (bc) j = checkSide(bot, player, playerBot); - - switch (j) { - case 1: - resetHand(bot, player, playerBot); - if (!noJump.contains(playerBot)) { - if (!waterGround) move(bot, player, loc, target); - } - return; - - case 2: - if (!waterGround) move(bot, player, loc, target); - return; - } - } - } else if (LegacyMats.WATER.contains(loc.getBlock().getType())) { - swim(bot, target, playerBot, player, LegacyMats.WATER.contains(loc.clone().add(0, -1, 0).getBlock().getType())); } + + else if (LegacyMats.WATER.contains(loc.getBlock().getType())) { + swim(bot, target, botPlayer, player, LegacyMats.WATER.contains(loc.clone().add(0, -1, 0).getBlock().getType())); + } + } + + private void move(Bot bot, Player player, Location loc, Location target, boolean ai) { + Vector position = loc.toVector(); + Vector vel = target.toVector().subtract(position).normalize(); + + if (bot.tickDelay(5)) bot.faceLocation(player.getLocation()); + if (!bot.isOnGround()) return; // calling this a second time later on + + bot.stand(); // eventually create a memory system so packets do not have to be sent every tick + bot.setItem(null); // method to check item in main hand, bot.getItemInHand() + + try { + vel.add(bot.getVelocity()); + } catch (IllegalArgumentException e) { + if (MathUtils.isNotFinite(vel)) { + MathUtils.clean(vel); + } + } + + if (vel.length() > 1) vel.normalize(); + + double distance = loc.distance(target); + + if (distance <= 5) { + vel.multiply(0.3); + } else { + vel.multiply(0.4); + } + + if (slow.contains(bot)) { + vel.setY(0).multiply(0.5); + } else { + vel.setY(0.4); + } + + vel.setY(vel.getY() - Math.random() * 0.05); + + if (ai) { + NeuralNetwork network = bot.getNeuralNetwork(); + + boolean left = network.check(BotNode.LEFT); + boolean right = network.check(BotNode.RIGHT); + + if (left != right && distance <= 6) { + if (left) { + vel.rotateAroundY(Math.PI / 3); + } + + if (right) { + vel.rotateAroundY(-Math.PI / 3); + } + + if (network.check(BotNode.JUMP)) { + bot.jump(vel); + } else { + bot.walk(vel.clone().setY(0)); + scheduler.runTaskLater(plugin, () -> bot.jump(vel), 10); + } + + return; + } + } + + bot.jump(vel); } private void fallDamageCheck(Bot bot) { @@ -295,57 +367,21 @@ public class LegacyAgent extends Agent { } } - private void move(Bot bot, Player player, Location loc, Location target) { - Vector vel = target.toVector().subtract(loc.toVector()).normalize(); - - if (bot.tickDelay(5)) bot.faceLocation(player.getLocation()); - if (!bot.isOnGround()) return; // calling this a second time later on - - bot.stand(); // eventually create a memory system so packets do not have to be sent every tick - bot.setItem(null); // method to check item in main hand, bot.getItemInHand() - - try { - vel.add(bot.velocity); - } catch (IllegalArgumentException e) { - if (MathUtils.isNotFinite(vel)) { - MathUtils.clean(vel); - } - } - - if (vel.length() > 1) vel.normalize(); - - if (loc.distance(target) <= 5) { - vel.multiply(0.3); - } else { - vel.multiply(0.4); - } - - if (slow.contains(bot)) { - vel.setY(0).multiply(0.5); - } else { - vel.setY(0.4); - } - - vel.setY(vel.getY() - Math.random() * 0.05); - - bot.jump(vel); - } - private byte checkSide(Bot npc, Player player, Player playerNPC) { // make it so they don't jump when checking side Location a = playerNPC.getEyeLocation(); Location b = player.getLocation().add(0, 1, 0); - if (npc.getLocation().distance(player.getLocation()) < 2.9 && !LegacyUtils.checkIfBlocksOnVector(a, b)) { + if (npc.getLocation().distance(player.getLocation()) < 2.9 && LegacyUtils.checkFreeSpace(a, b)) { resetHand(npc, player, playerNPC); return 1; } - LegacyLevel h = checkNearby(player, npc); + LegacyLevel level = checkNearby(player, npc); - if (h == null) { + if (level == null) { resetHand(npc, player, playerNPC); return 1; - } else if (h.isSide()) { + } else if (level.isSide()) { return 0; } else { return 2; @@ -560,7 +596,7 @@ public class LegacyAgent extends Agent { private boolean checkDown(Bot npc, Player player, Location loc, boolean c) { // possibly a looser check for c - if (!LegacyUtils.checkIfBlocksOnVector(npc.getLocation(), loc) || !LegacyUtils.checkIfBlocksOnVector(player.getEyeLocation(), loc)) return false; + if (LegacyUtils.checkFreeSpace(npc.getLocation(), loc) || LegacyUtils.checkFreeSpace(player.getEyeLocation(), loc)) return false; if (c && npc.getLocation().getBlockY() > loc.getBlockY() + 1) { Block block = npc.getLocation().add(0, -1, 0).getBlock(); @@ -839,8 +875,8 @@ public class LegacyAgent extends Agent { } private void miscellaneousChecks(Bot bot, Player target) { - Player playerBot = bot.getBukkitEntity(); - World world = playerBot.getWorld(); + Player botPlayer = bot.getBukkitEntity(); + World world = botPlayer.getWorld(); String worldName = world.getName(); Location loc = bot.getLocation(); @@ -914,15 +950,15 @@ public class LegacyAgent extends Agent { } } - if (playerBot.getLocation().getBlockY() <= target.getLocation().getBlockY() + 1) { - if (!miningAnim.containsKey(playerBot)) { - Vector vel = playerBot.getVelocity(); + if (botPlayer.getLocation().getBlockY() <= target.getLocation().getBlockY() + 1) { + if (!miningAnim.containsKey(botPlayer)) { + Vector vel = botPlayer.getVelocity(); double y = vel.getY(); if (y >= -0.6) { if (loc.clone().add(0, -0.6, 0).getBlock().getType() == Material.WATER && !LegacyMats.NO_CRACK.contains(under2Type) - && playerBot.getEyeLocation().getBlock().getType().isAir()) { + && botPlayer.getEyeLocation().getBlock().getType().isAir()) { Block place = loc.clone().add(0, -1, 0).getBlock(); if (LegacyMats.WATER.contains(place.getType())) { diff --git a/src/main/java/net/nuggetmc/ai/bot/agent/legacyagent/LegacyUtils.java b/src/main/java/net/nuggetmc/ai/bot/agent/legacyagent/LegacyUtils.java index 655c4e1..480c040 100644 --- a/src/main/java/net/nuggetmc/ai/bot/agent/legacyagent/LegacyUtils.java +++ b/src/main/java/net/nuggetmc/ai/bot/agent/legacyagent/LegacyUtils.java @@ -11,7 +11,7 @@ import java.lang.reflect.Field; public class LegacyUtils { - public static boolean checkIfBlocksOnVector(Location a, Location b) { + public static boolean checkFreeSpace(Location a, Location b) { Vector v = b.toVector().subtract(a.toVector()); int n = 32; @@ -20,16 +20,18 @@ public class LegacyUtils { double j = Math.floor(v.length() * n); v.multiply(m / v.length()); - for (int i = 0; i <= j; i++) { + org.bukkit.World world = a.getWorld(); + if (world == null) return false; - Block block = a.getWorld().getBlockAt((a.toVector().add(v.clone().multiply(i))).toLocation(a.getWorld())); + for (int i = 0; i <= j; i++) { + Block block = world.getBlockAt((a.toVector().add(v.clone().multiply(i))).toLocation(world)); if (!LegacyMats.AIR.contains(block.getType())) { - return true; + return false; } } - return false; + return true; } public static Sound breakBlockSound(Block block) { diff --git a/src/main/java/net/nuggetmc/ai/bot/agent/legacyagent/ai/BotData.java b/src/main/java/net/nuggetmc/ai/bot/agent/legacyagent/ai/BotData.java index 51c20c6..c3a66a5 100644 --- a/src/main/java/net/nuggetmc/ai/bot/agent/legacyagent/ai/BotData.java +++ b/src/main/java/net/nuggetmc/ai/bot/agent/legacyagent/ai/BotData.java @@ -2,46 +2,51 @@ package net.nuggetmc.ai.bot.agent.legacyagent.ai; import net.nuggetmc.ai.bot.Bot; import net.nuggetmc.ai.utils.MathUtils; +import org.apache.commons.lang.StringUtils; import org.bukkit.Location; 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 float health; - - private final double distXZ; - private final double distY; - - private final boolean enemyBlocking; + private final Map values; private BotData(Bot bot, Player target) { + this.values = new HashMap<>(); + Location a = bot.getLocation(); Location b = target.getLocation(); - this.health = bot.getHealth(); - this.distXZ = Math.sqrt(MathUtils.square(a.getX() - b.getX()) + MathUtils.square(a.getZ() - b.getZ())); - this.distY = b.getY() - a.getY(); - this.enemyBlocking = target.isBlocking(); + 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.isBlocking() ? 1D : 0); } public static BotData generate(Bot bot, Player target) { return new BotData(bot, target); } - public float getHealth() { - return health; + public Map getValues() { + return values; } - public double getDistXZ() { - return distXZ; + public double getValue(BotDataType dataType) { + return values.get(dataType); } - public double getDistY() { - return distY; - } + @Override + public String toString() { + List strings = new ArrayList<>(); - public boolean getEnemyBlocking() { - return enemyBlocking; + values.forEach((type, value) -> strings.add(type.name() + "=" + MathUtils.round2Dec(value))); + + Collections.sort(strings); + + return "BotData{" + StringUtils.join(strings, ",") + "}"; } } diff --git a/src/main/java/net/nuggetmc/ai/bot/agent/legacyagent/ai/BotDataType.java b/src/main/java/net/nuggetmc/ai/bot/agent/legacyagent/ai/BotDataType.java new file mode 100644 index 0000000..fefd46b --- /dev/null +++ b/src/main/java/net/nuggetmc/ai/bot/agent/legacyagent/ai/BotDataType.java @@ -0,0 +1,8 @@ +package net.nuggetmc.ai.bot.agent.legacyagent.ai; + +public enum BotDataType { + CRITICAL_HEALTH, + DISTANCE_XZ, + DISTANCE_Y, + ENEMY_BLOCKING +} diff --git a/src/main/java/net/nuggetmc/ai/bot/agent/legacyagent/ai/BotNode.java b/src/main/java/net/nuggetmc/ai/bot/agent/legacyagent/ai/BotNode.java new file mode 100644 index 0000000..9641cae --- /dev/null +++ b/src/main/java/net/nuggetmc/ai/bot/agent/legacyagent/ai/BotNode.java @@ -0,0 +1,8 @@ +package net.nuggetmc.ai.bot.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) +} diff --git a/src/main/java/net/nuggetmc/ai/bot/agent/legacyagent/ai/NetworkType.java b/src/main/java/net/nuggetmc/ai/bot/agent/legacyagent/ai/NetworkType.java new file mode 100644 index 0000000..0514c50 --- /dev/null +++ b/src/main/java/net/nuggetmc/ai/bot/agent/legacyagent/ai/NetworkType.java @@ -0,0 +1,5 @@ +package net.nuggetmc.ai.bot.agent.legacyagent.ai; + +public enum NetworkType { + RANDOM +} diff --git a/src/main/java/net/nuggetmc/ai/bot/agent/legacyagent/ai/NeuralNetwork.java b/src/main/java/net/nuggetmc/ai/bot/agent/legacyagent/ai/NeuralNetwork.java index f9de0e8..3e99c2b 100644 --- a/src/main/java/net/nuggetmc/ai/bot/agent/legacyagent/ai/NeuralNetwork.java +++ b/src/main/java/net/nuggetmc/ai/bot/agent/legacyagent/ai/NeuralNetwork.java @@ -1,20 +1,64 @@ package net.nuggetmc.ai.bot.agent.legacyagent.ai; +import net.md_5.bungee.api.ChatColor; +import net.nuggetmc.ai.utils.MathUtils; +import net.nuggetmc.ai.utils.StringUtilities; +import org.apache.commons.lang.StringUtils; + +import java.util.*; + public class NeuralNetwork { - private final NodeConnections nodeL; // left strafe - private final NodeConnections nodeR; // right strafe (if L and R are opposite, move forward) - private final NodeConnections nodeB; // block - private final NodeConnections nodeJ; // jump + // 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 - public NeuralNetwork() { - this.nodeL = new NodeConnections(); - this.nodeR = new NodeConnections(); - this.nodeB = new NodeConnections(); - this.nodeJ = new NodeConnections(); + private final Map nodes; + + private NeuralNetwork(BotNode... nodes) { + this.nodes = new HashMap<>(); + + Arrays.stream(nodes).forEach(n -> this.nodes.put(n, new NodeConnections())); + } + + public static NeuralNetwork generateRandomNetwork() { + return new NeuralNetwork(BotNode.values()); + } + + public NodeConnections fetch(BotNode node) { + return nodes.get(node); + } + + public boolean check(BotNode node) { + return nodes.get(node).check(); } public void feed(BotData data) { + nodes.values().forEach(n -> n.test(data)); + } + public String output() { + return generateString(false); + } + + @Override + public String toString() { + return generateString(true); + } + + private String generateString(boolean values) { + List strings = new ArrayList<>(); + + if (values) { + nodes.forEach((type, node) -> { + double value = node.value(); + strings.add(type.name().toLowerCase() + "=" + (value >= 0.5 ? StringUtilities.ON : StringUtilities.OFF) + MathUtils.round2Dec(value) + ChatColor.RESET); + }); + } else { + nodes.forEach((type, node) -> strings.add(type.name().toLowerCase() + "=" + (node.check() ? StringUtilities.ON + "1" : StringUtilities.OFF + "0") + ChatColor.RESET)); + } + + Collections.sort(strings); + + return "[" + StringUtils.join(strings, ", ") + "]"; } } diff --git a/src/main/java/net/nuggetmc/ai/bot/agent/legacyagent/ai/NodeConnections.java b/src/main/java/net/nuggetmc/ai/bot/agent/legacyagent/ai/NodeConnections.java index b111846..d6e2a87 100644 --- a/src/main/java/net/nuggetmc/ai/bot/agent/legacyagent/ai/NodeConnections.java +++ b/src/main/java/net/nuggetmc/ai/bot/agent/legacyagent/ai/NodeConnections.java @@ -1,47 +1,60 @@ package net.nuggetmc.ai.bot.agent.legacyagent.ai; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + public class NodeConnections { - private final double connectionX; // horizontal distance - private final double connectionY; // vertical distance - private final double connectionB; // enemy blocking - private final double connectionH; // health + /* + * 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 connections; + + private boolean active; + + private double value; public NodeConnections() { - this.connectionX = generate(); - this.connectionY = generate(); - this.connectionB = generate(); - this.connectionH = generate(); + this.connections = new HashMap<>(); + + Arrays.stream(BotDataType.values()).forEach(type -> connections.put(type, generateValue())); } - public NodeConnections(double y, double b, double t, double h) { - this.connectionX = t; - this.connectionY = y; - this.connectionB = b; - this.connectionH = h; - } - - private double generate() { + private double generateValue() { return Math.random() * 20 - 10; } - public double getX() { - return connectionX; + public boolean check() { + return active; } - public double getY() { - return connectionY; + public double value() { + return value; } - public double getB() { - return connectionB; + public double getValue(BotDataType dataType) { + return connections.get(dataType); } - public double getH() { - return connectionH; + /* + * 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; } - public boolean test(double y, double b, double t, double h) { - return Math.tanh(y * connectionX + b * connectionY + t * connectionB + h * connectionH) >= 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()); } } diff --git a/src/main/java/net/nuggetmc/ai/command/commands/AICommand.java b/src/main/java/net/nuggetmc/ai/command/commands/AICommand.java index 31d2c4f..45441be 100644 --- a/src/main/java/net/nuggetmc/ai/command/commands/AICommand.java +++ b/src/main/java/net/nuggetmc/ai/command/commands/AICommand.java @@ -5,6 +5,8 @@ import com.jonahseguin.drink.annotation.OptArg; import com.jonahseguin.drink.annotation.Sender; import net.nuggetmc.ai.TerminatorPlus; import net.nuggetmc.ai.bot.BotManager; +import net.nuggetmc.ai.bot.agent.Agent; +import net.nuggetmc.ai.bot.agent.legacyagent.ai.NetworkType; import net.nuggetmc.ai.command.CommandHandler; import net.nuggetmc.ai.command.CommandInstance; import org.bukkit.command.CommandSender; @@ -13,11 +15,13 @@ import org.bukkit.entity.Player; public class AICommand extends CommandInstance { private final BotManager manager; + private final Agent agent; public AICommand(CommandHandler commandHandler) { super(commandHandler); this.manager = TerminatorPlus.getInstance().getManager(); + this.agent = manager.getAgent(); } @Command( @@ -30,9 +34,9 @@ public class AICommand extends CommandInstance { @Command( name = "random", desc = "Create bots with random neural networks, collecting feed data.", - usage = " [skin]" + usage = " [skin]" ) - public void create(@Sender Player sender, String name, @OptArg String skin) { - manager.createBots(sender, name, skin, 1); + public void random(@Sender Player sender, int n, String name, @OptArg String skin) { + manager.createBots(sender, name, skin, n, NetworkType.RANDOM); } } diff --git a/src/main/java/net/nuggetmc/ai/command/commands/BotCommand.java b/src/main/java/net/nuggetmc/ai/command/commands/BotCommand.java index 3172f57..9e66376 100644 --- a/src/main/java/net/nuggetmc/ai/command/commands/BotCommand.java +++ b/src/main/java/net/nuggetmc/ai/command/commands/BotCommand.java @@ -11,7 +11,6 @@ import net.nuggetmc.ai.bot.BotManager; import net.nuggetmc.ai.command.CommandHandler; import net.nuggetmc.ai.command.CommandInstance; import net.nuggetmc.ai.utils.Debugger; -import org.apache.commons.lang.StringUtils; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.Location; diff --git a/src/main/java/net/nuggetmc/ai/utils/Debugger.java b/src/main/java/net/nuggetmc/ai/utils/Debugger.java index 9dc9ee1..678c6bb 100644 --- a/src/main/java/net/nuggetmc/ai/utils/Debugger.java +++ b/src/main/java/net/nuggetmc/ai/utils/Debugger.java @@ -96,6 +96,21 @@ public class Debugger { * DEBUGGER METHODS */ + public void offsets(boolean b) { + Agent agent = TerminatorPlus.getInstance().getManager().getAgent(); + if (!(agent instanceof LegacyAgent)) { + print("This method currently only supports " + ChatColor.AQUA + "LegacyAgent" + ChatColor.RESET + "."); + return; + } + + LegacyAgent legacyAgent = (LegacyAgent) agent; + legacyAgent.offsets = b; + + print("Bot target offsets are now " + + (legacyAgent.offsets ? ChatColor.GREEN + "ENABLED" : ChatColor.RED + "DISABLED") + + ChatColor.RESET + "."); + } + public void confuse(int n) { if (!(sender instanceof Player)) return; diff --git a/src/main/java/net/nuggetmc/ai/utils/MathUtils.java b/src/main/java/net/nuggetmc/ai/utils/MathUtils.java index b261bc2..18d4471 100644 --- a/src/main/java/net/nuggetmc/ai/utils/MathUtils.java +++ b/src/main/java/net/nuggetmc/ai/utils/MathUtils.java @@ -3,12 +3,15 @@ package net.nuggetmc.ai.utils; import org.bukkit.util.NumberConversions; import org.bukkit.util.Vector; +import java.text.DecimalFormat; import java.util.Random; import java.util.Set; 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(); @@ -79,4 +82,12 @@ public class MathUtils { 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); + } } diff --git a/src/main/java/net/nuggetmc/ai/utils/StringUtilities.java b/src/main/java/net/nuggetmc/ai/utils/StringUtilities.java new file mode 100644 index 0000000..ce62633 --- /dev/null +++ b/src/main/java/net/nuggetmc/ai/utils/StringUtilities.java @@ -0,0 +1,13 @@ +package net.nuggetmc.ai.utils; + +import net.md_5.bungee.api.ChatColor; + +public class StringUtilities { + + 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; + } +} diff --git a/src/main/java/net/nuggetmc/ai/utils/StringUtils.java b/src/main/java/net/nuggetmc/ai/utils/StringUtils.java deleted file mode 100644 index 49149c2..0000000 --- a/src/main/java/net/nuggetmc/ai/utils/StringUtils.java +++ /dev/null @@ -1,7 +0,0 @@ -package net.nuggetmc.ai.utils; - -public class StringUtils { - public static String trim16(String str) { - return str.length() > 16 ? str.substring(0, 16) : str; - } -}