From d86a42eab6d65475cda05a5b362e76e21ee8f537 Mon Sep 17 00:00:00 2001 From: batchprogrammer314 Date: Sat, 24 Jul 2021 23:34:07 -0500 Subject: [PATCH] intelligence pvp thing done --- .../java/net/nuggetmc/ai/TerminatorPlus.java | 7 +- src/main/java/net/nuggetmc/ai/bot/Bot.java | 60 +++- .../java/net/nuggetmc/ai/bot/BotManager.java | 120 +++++-- .../java/net/nuggetmc/ai/bot/agent/Agent.java | 24 ++ .../bot/agent/legacyagent/EnumTargetGoal.java | 4 + .../ai/bot/agent/legacyagent/LegacyAgent.java | 106 ++++-- .../agent/legacyagent/LegacyBlockCheck.java | 11 +- .../agent/legacyagent/ai/ActivationType.java | 9 + .../bot/agent/legacyagent/ai/BotDataType.java | 18 +- .../legacyagent/ai/IntelligenceAgent.java | 335 ++++++++++++++++++ .../bot/agent/legacyagent/ai/NetworkType.java | 5 - .../agent/legacyagent/ai/NeuralNetwork.java | 68 ++-- .../agent/legacyagent/ai/NodeConnections.java | 8 + .../nuggetmc/ai/bot/event/BotDeathEvent.java | 18 + .../ai/bot/event/BotKilledByPlayerEvent.java | 26 ++ .../nuggetmc/ai/command/CommandHandler.java | 7 + .../ai/command/commands/AICommand.java | 137 ++++++- .../ai/command/commands/BotCommand.java | 21 +- .../java/net/nuggetmc/ai/utils/Debugger.java | 7 +- .../java/net/nuggetmc/ai/utils/ItemUtils.java | 59 +++ .../java/net/nuggetmc/ai/utils/MathUtils.java | 94 ++++- .../net/nuggetmc/ai/utils/PlayerUtils.java | 18 + 22 files changed, 1045 insertions(+), 117 deletions(-) create mode 100644 src/main/java/net/nuggetmc/ai/bot/agent/legacyagent/ai/ActivationType.java create mode 100644 src/main/java/net/nuggetmc/ai/bot/agent/legacyagent/ai/IntelligenceAgent.java delete mode 100644 src/main/java/net/nuggetmc/ai/bot/agent/legacyagent/ai/NetworkType.java create mode 100644 src/main/java/net/nuggetmc/ai/bot/event/BotDeathEvent.java create mode 100644 src/main/java/net/nuggetmc/ai/bot/event/BotKilledByPlayerEvent.java create mode 100644 src/main/java/net/nuggetmc/ai/utils/ItemUtils.java diff --git a/src/main/java/net/nuggetmc/ai/TerminatorPlus.java b/src/main/java/net/nuggetmc/ai/TerminatorPlus.java index 08451d8..16020ed 100644 --- a/src/main/java/net/nuggetmc/ai/TerminatorPlus.java +++ b/src/main/java/net/nuggetmc/ai/TerminatorPlus.java @@ -13,6 +13,7 @@ public class TerminatorPlus extends JavaPlugin { private static String version; private BotManager manager; + private CommandHandler handler; public static TerminatorPlus getInstance() { return instance; @@ -26,6 +27,10 @@ public class TerminatorPlus extends JavaPlugin { return manager; } + public CommandHandler getHandler() { + return handler; + } + @Override public void onEnable() { instance = this; @@ -33,7 +38,7 @@ public class TerminatorPlus extends JavaPlugin { // Create Instances this.manager = new BotManager(); - new CommandHandler(this); + this.handler = new CommandHandler(this); // Register event listeners this.registerEvents(manager); diff --git a/src/main/java/net/nuggetmc/ai/bot/Bot.java b/src/main/java/net/nuggetmc/ai/bot/Bot.java index b5a3871..8f57605 100644 --- a/src/main/java/net/nuggetmc/ai/bot/Bot.java +++ b/src/main/java/net/nuggetmc/ai/bot/Bot.java @@ -5,9 +5,11 @@ 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.Agent; import net.nuggetmc.ai.bot.agent.legacyagent.ai.NeuralNetwork; import net.nuggetmc.ai.bot.event.BotDamageByPlayerEvent; import net.nuggetmc.ai.bot.event.BotFallDamageEvent; +import net.nuggetmc.ai.bot.event.BotKilledByPlayerEvent; import net.nuggetmc.ai.utils.*; import org.bukkit.Material; import org.bukkit.SoundCategory; @@ -21,6 +23,7 @@ import org.bukkit.craftbukkit.v1_16_R3.entity.CraftPlayer; import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftItemStack; import org.bukkit.entity.Damageable; import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; import org.bukkit.scheduler.BukkitScheduler; import org.bukkit.util.Vector; @@ -33,6 +36,7 @@ public class Bot extends EntityPlayer { private final TerminatorPlus plugin; private final BukkitScheduler scheduler; + private final Agent agent; private NeuralNetwork network; @@ -48,7 +52,7 @@ public class Bot extends EntityPlayer { return network != null; } - public boolean item; // eventually make this not garbage lol + public ItemStack defaultItem; private boolean shield; private boolean blocking; @@ -57,9 +61,11 @@ public class Bot extends EntityPlayer { private Vector velocity; private Vector oldVelocity; - private final boolean removeOnDeath; + private boolean removeOnDeath; + + private int aliveTicks; + private int kills; - private byte aliveTicks; private byte fireTicks; private byte groundTicks; private byte jumpTicks; @@ -72,6 +78,8 @@ public class Bot extends EntityPlayer { this.plugin = TerminatorPlus.getInstance(); this.scheduler = Bukkit.getScheduler(); + this.agent = plugin.getManager().getAgent(); + this.defaultItem = new ItemStack(Material.AIR); this.velocity = new Vector(0, 0, 0); this.oldVelocity = velocity.clone(); this.noFallTicks = 60; @@ -139,6 +147,10 @@ public class Bot extends EntityPlayer { }; } + public void setDefaultItem(ItemStack item) { + this.defaultItem = item; + } + public Vector getOffset() { return offset; } @@ -160,6 +172,10 @@ public class Bot extends EntityPlayer { velocity.add(vector); } + public int getAliveTicks() { + return aliveTicks; + } + public boolean tickDelay(int i) { return aliveTicks % i == 0; } @@ -385,7 +401,7 @@ public class Bot extends EntityPlayer { punch(); if (entity instanceof Damageable) { - ((Damageable) entity).damage(item ? 2.5 : 0.25, getBukkitEntity()); // fist damage is 0.25 + ((Damageable) entity).damage(ItemUtils.getLegacyAttackDamage(defaultItem), getBukkitEntity()); } } @@ -442,10 +458,6 @@ public class Bot extends EntityPlayer { velocity.setZ(Math.abs(z) < frictionMin ? 0 : z * factor); } - public void despawn() { - getBukkitEntity().remove(); - } - public void removeVisually() { this.removeTab(); this.setDead(); @@ -455,6 +467,10 @@ public class Bot extends EntityPlayer { sendPacket(new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.REMOVE_PLAYER, this)); } + public void setRemoveOnDeath(boolean enabled) { + this.removeOnDeath = enabled; + } + private void setDead() { this.dead = true; this.defaultContainer.b(this); @@ -523,11 +539,16 @@ public class Bot extends EntityPlayer { float damage; - if (attacker instanceof EntityPlayer) { + boolean playerInstance = attacker instanceof EntityPlayer; - BotDamageByPlayerEvent event = new BotDamageByPlayerEvent(this, ((EntityPlayer) attacker).getBukkitEntity(), f); + Player killer; - plugin.getManager().getAgent().onPlayerDamage(event); + if (playerInstance) { + killer = ((EntityPlayer) attacker).getBukkitEntity(); + + BotDamageByPlayerEvent event = new BotDamageByPlayerEvent(this, killer, f); + + agent.onPlayerDamage(event); if (event.isCancelled()) { return false; @@ -535,6 +556,7 @@ public class Bot extends EntityPlayer { damage = event.getDamage(); } else { + killer = null; damage = f; } @@ -545,6 +567,10 @@ public class Bot extends EntityPlayer { } if (damaged && attacker != null) { + if (playerInstance && !isAlive()) { + agent.onBotKilledByPlayer(new BotKilledByPlayerEvent(this, killer)); + } + kb(getLocation(), attacker.getBukkitEntity().getLocation()); } @@ -559,6 +585,14 @@ public class Bot extends EntityPlayer { velocity = vel; } + public int getKills() { + return kills; + } + + public void incrementKills() { + kills++; + } + public Location getLocation() { return getBukkitEntity().getLocation(); } @@ -615,8 +649,8 @@ public class Bot extends EntityPlayer { setItem(item, EnumItemSlot.OFFHAND); } - public void setItem(org.bukkit.inventory.ItemStack item, EnumItemSlot slot) { - if (item == null) item = new org.bukkit.inventory.ItemStack(this.item ? Material.WOODEN_SHOVEL : Material.AIR); + private void setItem(org.bukkit.inventory.ItemStack item, EnumItemSlot slot) { + if (item == null) item = defaultItem; if (slot == EnumItemSlot.MAINHAND) { getBukkitEntity().getInventory().setItemInMainHand(item); diff --git a/src/main/java/net/nuggetmc/ai/bot/BotManager.java b/src/main/java/net/nuggetmc/ai/bot/BotManager.java index 27313c1..cf2edb0 100644 --- a/src/main/java/net/nuggetmc/ai/bot/BotManager.java +++ b/src/main/java/net/nuggetmc/ai/bot/BotManager.java @@ -3,22 +3,22 @@ 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.bot.event.BotDeathEvent; import net.nuggetmc.ai.utils.MojangAPI; import org.bukkit.*; import org.bukkit.craftbukkit.v1_16_R3.entity.CraftPlayer; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; -import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.PlayerDeathEvent; import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.inventory.ItemStack; import org.bukkit.util.Vector; import java.text.NumberFormat; -import java.util.HashSet; -import java.util.Locale; -import java.util.Set; +import java.util.*; +import java.util.stream.Collectors; public class BotManager implements Listener { @@ -27,7 +27,6 @@ public class BotManager implements Listener { private final NumberFormat numberFormat; public boolean joinMessages = false; - public boolean removeOnDeath = true; public BotManager() { this.agent = new LegacyAgent(this); @@ -57,6 +56,10 @@ public class BotManager implements Listener { return null; } + public List fetchNames() { + return bots.stream().map(Bot::getName).collect(Collectors.toList()); + } + public Agent getAgent() { return agent; } @@ -65,44 +68,73 @@ public class BotManager implements Listener { createBots(sender, name, skinName, n, null); } - public void createBots(Player sender, String name, String skinName, int n, NetworkType type) { + public void createBots(Player sender, String name, String skinName, int n, NeuralNetwork network) { long timestamp = System.currentTimeMillis(); if (n < 1) n = 1; - World world = sender.getWorld(); - Location loc = sender.getLocation(); - sender.sendMessage("Creating " + (n == 1 ? "new bot" : ChatColor.RED + numberFormat.format(n) + ChatColor.RESET + " new bots") - + " with name " + ChatColor.GREEN + name + + " with name " + ChatColor.GREEN + name.replace("%", ChatColor.LIGHT_PURPLE + "%" + ChatColor.RESET) + (skinName == null ? "" : ChatColor.RESET + " and skin " + ChatColor.GREEN + skinName) + ChatColor.RESET + "..."); skinName = skinName == null ? name : skinName; - double f = n < 100 ? .004 * n : .4; - - String[] skin = MojangAPI.getSkin(skinName); - - for (int i = 1; i <= n; i++) { - 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()); - bot.setShield(true); - bot.item = true; - } - } - - world.spawnParticle(Particle.CLOUD, loc, 100, 1, 1, 1, 0.5); + createBots(sender.getLocation(), name, MojangAPI.getSkin(skinName), n, network); sender.sendMessage("Process completed (" + ChatColor.RED + ((System.currentTimeMillis() - timestamp) / 1000D) + "s" + ChatColor.RESET + ")."); } + public Set createBots(Location loc, String name, String[] skin, int n, NeuralNetwork network) { + List networks = new ArrayList<>(); + + for (int i = 0; i < n; i++) { + networks.add(network); + } + + return createBots(loc, name, skin, networks); + } + + public Set createBots(Location loc, String name, String[] skin, List networks) { + Set bots = new HashSet<>(); + World world = loc.getWorld(); + + int n = networks.size(); + int i = 1; + + double f = n < 100 ? .004 * n : .4; + + for (NeuralNetwork network : networks) { + Bot bot = Bot.createBot(loc, name.replace("%", String.valueOf(i)), skin); + + if (network != null) { + bot.setNeuralNetwork(network == NeuralNetwork.RANDOM ? NeuralNetwork.generateRandomNetwork() : network); + bot.setShield(true); + bot.setDefaultItem(new ItemStack(Material.WOODEN_AXE)); + bot.setRemoveOnDeath(false); + } + + if (network != null) { + bot.setVelocity(randomVelocity()); + } else if (i > 1) { + bot.setVelocity(randomVelocity().multiply(f)); + } + + bots.add(bot); + i++; + } + + if (world != null) { + world.spawnParticle(Particle.CLOUD, loc, 100, 1, 1, 1, 0.5); + } + + return bots; + } + + private Vector randomVelocity() { + return new Vector(Math.random() - 0.5, 0.5, Math.random() - 0.5).normalize(); + } + public void remove(Bot bot) { bots.remove(bot); } @@ -111,6 +143,23 @@ public class BotManager implements Listener { bots.forEach(Bot::removeVisually); bots.clear(); // Not always necessary, but a good security measure agent.stopAllTasks(); + + System.gc(); + } + + public Bot getBot(Player player) { // potentially memory intensive + Bot bot = null; + + int id = player.getEntityId(); + + for (Bot b : bots) { + if (id == b.getId()) { + bot = b; + break; + } + } + + return bot; } @EventHandler @@ -120,11 +169,12 @@ public class BotManager implements Listener { } @EventHandler - public void onEntityDamage(EntityDamageByEntityEvent event) { - if (!(event.getEntity() instanceof Player)) return; - if (!(event.getDamager() instanceof Player)) return; + public void onDeath(PlayerDeathEvent event) { + Player player = event.getEntity(); + Bot bot = getBot(player); - Player player = (Player) event.getEntity(); - Player damager = (Player) event.getDamager(); + if (bot != null) { + agent.onBotDeath(new BotDeathEvent(event, bot)); + } } } 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 067c43c..e182a30 100644 --- a/src/main/java/net/nuggetmc/ai/bot/agent/Agent.java +++ b/src/main/java/net/nuggetmc/ai/bot/agent/Agent.java @@ -1,10 +1,14 @@ package net.nuggetmc.ai.bot.agent; import net.nuggetmc.ai.TerminatorPlus; +import net.nuggetmc.ai.bot.Bot; import net.nuggetmc.ai.bot.BotManager; import net.nuggetmc.ai.bot.event.BotDamageByPlayerEvent; +import net.nuggetmc.ai.bot.event.BotDeathEvent; import net.nuggetmc.ai.bot.event.BotFallDamageEvent; +import net.nuggetmc.ai.bot.event.BotKilledByPlayerEvent; import org.bukkit.Bukkit; +import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitScheduler; @@ -23,6 +27,8 @@ public abstract class Agent { protected boolean enabled; protected int taskID; + protected boolean drops; + public Agent(BotManager manager) { this.plugin = TerminatorPlus.getInstance(); this.manager = manager; @@ -53,9 +59,27 @@ public abstract class Agent { taskList.clear(); } + public void setDrops(boolean enabled) { + this.drops = enabled; + } + protected abstract void tick(); public void onFallDamage(BotFallDamageEvent event) { } public void onPlayerDamage(BotDamageByPlayerEvent event) { } + + public void onBotDeath(BotDeathEvent event) { } + + public void onBotKilledByPlayer(BotKilledByPlayerEvent event) { + Player player = event.getPlayer(); + + scheduler.runTaskAsynchronously(plugin, () -> { + Bot bot = manager.getBot(player); + + if (bot != null) { + bot.incrementKills(); + } + }); + } } diff --git a/src/main/java/net/nuggetmc/ai/bot/agent/legacyagent/EnumTargetGoal.java b/src/main/java/net/nuggetmc/ai/bot/agent/legacyagent/EnumTargetGoal.java index b352605..5d1d2ca 100644 --- a/src/main/java/net/nuggetmc/ai/bot/agent/legacyagent/EnumTargetGoal.java +++ b/src/main/java/net/nuggetmc/ai/bot/agent/legacyagent/EnumTargetGoal.java @@ -5,6 +5,7 @@ public enum EnumTargetGoal { // TODO USE ORDINAL!!!!! NEAREST_REAL_PLAYER, NEAREST_BOT_DIFFER, NEAREST_BOT, + NEAREST_BOT_DIFFER_ALPHA, NONE; public static EnumTargetGoal of(int n) { @@ -23,6 +24,9 @@ public enum EnumTargetGoal { // TODO USE ORDINAL!!!!! case 4: return NEAREST_BOT; + + case 5: + return NEAREST_BOT_DIFFER_ALPHA; } } } 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 5c7ac73..ca42532 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 @@ -9,7 +9,9 @@ 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.BotDamageByPlayerEvent; +import net.nuggetmc.ai.bot.event.BotDeathEvent; import net.nuggetmc.ai.bot.event.BotFallDamageEvent; +import net.nuggetmc.ai.bot.event.BotKilledByPlayerEvent; import net.nuggetmc.ai.utils.MathUtils; import net.nuggetmc.ai.utils.PlayerUtils; import org.bukkit.*; @@ -234,30 +236,58 @@ public class LegacyAgent extends Agent { if (ai) { NeuralNetwork network = bot.getNeuralNetwork(); - boolean left = network.check(BotNode.LEFT); - boolean right = network.check(BotNode.RIGHT); + if (network.dynamicLR()) { + if (bot.isBlocking()) { + vel.multiply(0.6); + } - if (bot.isBlocking()) { - vel.multiply(0.6); + if (distance <= 6) { + + // positive y rotation means left, negative means right + // if left > right, value will be positive + + double value = network.value(BotNode.LEFT) - network.value(BotNode.RIGHT); + + vel.rotateAroundY(value * Math.PI / 8); + + if (network.check(BotNode.JUMP)) { + bot.jump(vel); + } else { + bot.walk(vel.clone().setY(0)); + scheduler.runTaskLater(plugin, () -> bot.jump(vel), 10); + } + + return; + } } - if (left != right && distance <= 6) { - if (left) { - vel.rotateAroundY(Math.PI / 4); + else { + boolean left = network.check(BotNode.LEFT); + boolean right = network.check(BotNode.RIGHT); + + if (bot.isBlocking()) { + vel.multiply(0.6); } - if (right) { - vel.rotateAroundY(-Math.PI / 4); - } + if (left != right && distance <= 6) { - if (network.check(BotNode.JUMP)) { - bot.jump(vel); - } else { - bot.walk(vel.clone().setY(0)); - scheduler.runTaskLater(plugin, () -> bot.jump(vel), 10); - } + if (left) { + vel.rotateAroundY(Math.PI / 4); + } - return; + if (right) { + vel.rotateAroundY(-Math.PI / 4); + } + + if (network.check(BotNode.JUMP)) { + bot.jump(vel); + } else { + bot.walk(vel.clone().setY(0)); + scheduler.runTaskLater(plugin, () -> bot.jump(vel), 10); + } + + return; + } } } @@ -280,6 +310,13 @@ public class LegacyAgent extends Agent { } } + @Override + public void onBotDeath(BotDeathEvent event) { + if (!drops) { + event.getDrops().clear(); + } + } + @Override public void onPlayerDamage(BotDamageByPlayerEvent event) { Bot bot = event.getBot(); @@ -1023,9 +1060,9 @@ public class LegacyAgent extends Agent { Vector vector = targetLoc.toVector().subtract(bot.getLocation().toVector()).normalize(); vector.multiply(0.8); - Vector move = bot.getVelocity().add(vector); + Vector move = bot.getVelocity().add(vector).setY(0); if (move.length() > 1) move = move.normalize(); - move.multiply(0.75); + move.multiply(0.5); move.setY(0.42); bot.setVelocity(move); @@ -1104,6 +1141,9 @@ public class LegacyAgent extends Agent { case NEAREST_BOT_DIFFER: return nearestBotDiffer(bot, loc); + case NEAREST_BOT_DIFFER_ALPHA: + return nearestBotDifferAlpha(bot, loc); + case NEAREST_BOT: return nearestBot(bot, loc); @@ -1116,7 +1156,7 @@ public class LegacyAgent extends Agent { Player result = null; for (Player player : Bukkit.getOnlinePlayers()) { - if (loc.getWorld() != player.getWorld()) continue; + if (loc.getWorld() != player.getWorld() || player.isDead()) continue; if (result == null || loc.distance(player.getLocation()) < loc.distance(result.getLocation())) { result = player; @@ -1130,7 +1170,7 @@ public class LegacyAgent extends Agent { Player result = null; for (Player player : Bukkit.getOnlinePlayers()) { - if (PlayerUtils.isInvincible(player.getGameMode()) || loc.getWorld() != player.getWorld()) continue; + if (PlayerUtils.isInvincible(player.getGameMode()) || loc.getWorld() != player.getWorld() || player.isDead()) continue; if (result == null || loc.distance(player.getLocation()) < loc.distance(result.getLocation())) { result = player; @@ -1148,7 +1188,7 @@ public class LegacyAgent extends Agent { Player player = otherBot.getBukkitEntity(); - if (loc.getWorld() != player.getWorld()) continue; + if (loc.getWorld() != player.getWorld() || player.isDead()) continue; if (result == null || loc.distance(player.getLocation()) < loc.distance(result.getLocation())) { result = player; @@ -1167,7 +1207,27 @@ public class LegacyAgent extends Agent { Player player = otherBot.getBukkitEntity(); if (!bot.getName().equals(otherBot.getName())) { - if (loc.getWorld() != player.getWorld()) continue; + if (loc.getWorld() != player.getWorld() || player.isDead()) continue; + + if (result == null || loc.distance(player.getLocation()) < loc.distance(result.getLocation())) { + result = player; + } + } + } + + return result; + } + + private Player nearestBotDifferAlpha(Bot bot, Location loc) { + Player result = null; + + for (Bot otherBot : manager.fetch()) { + if (bot == otherBot) continue; + + Player player = otherBot.getBukkitEntity(); + + if (!bot.getName().replaceAll("[^A-Za-z]+", "").equals(otherBot.getName().replaceAll("[^A-Za-z]+", ""))) { + if (loc.getWorld() != player.getWorld() || player.isDead()) continue; if (result == null || loc.distance(player.getLocation()) < loc.distance(result.getLocation())) { result = player; diff --git a/src/main/java/net/nuggetmc/ai/bot/agent/legacyagent/LegacyBlockCheck.java b/src/main/java/net/nuggetmc/ai/bot/agent/legacyagent/LegacyBlockCheck.java index df0554a..a9c6103 100644 --- a/src/main/java/net/nuggetmc/ai/bot/agent/legacyagent/LegacyBlockCheck.java +++ b/src/main/java/net/nuggetmc/ai/bot/agent/legacyagent/LegacyBlockCheck.java @@ -2,6 +2,7 @@ package net.nuggetmc.ai.bot.agent.legacyagent; import net.nuggetmc.ai.TerminatorPlus; import net.nuggetmc.ai.bot.Bot; +import net.nuggetmc.ai.utils.Debugger; import org.bukkit.*; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; @@ -27,6 +28,11 @@ public class LegacyBlockCheck { for (Player all : Bukkit.getOnlinePlayers()) all.playSound(loc, Sound.BLOCK_STONE_PLACE, SoundCategory.BLOCKS, 1, 1); bot.setItem(new ItemStack(Material.COBBLESTONE)); loc.getBlock().setType(Material.COBBLESTONE); + + Block under = loc.clone().add(0, -1, 0).getBlock(); + if (under.getType() == Material.LAVA) { + under.setType(Material.COBBLESTONE); + } } } @@ -41,10 +47,7 @@ public class LegacyBlockCheck { Bukkit.getScheduler().runTaskLater(plugin, () -> { placeFinal(bot, player, block.getLocation()); }, 2); - }/* else { - placeFinal(player, block.getLocation()); - return; - }*/ + } Set face = new HashSet<>(Arrays.asList(loc.clone().add(1, 0, 0).getBlock(), loc.clone().add(-1, 0, 0).getBlock(), diff --git a/src/main/java/net/nuggetmc/ai/bot/agent/legacyagent/ai/ActivationType.java b/src/main/java/net/nuggetmc/ai/bot/agent/legacyagent/ai/ActivationType.java new file mode 100644 index 0000000..034d9f3 --- /dev/null +++ b/src/main/java/net/nuggetmc/ai/bot/agent/legacyagent/ai/ActivationType.java @@ -0,0 +1,9 @@ +package net.nuggetmc.ai.bot.agent.legacyagent.ai; + +public enum ActivationType { + TANH, + SIN_X, + SIN_X2, + COS_X, + COS_X2 +} 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 index fefd46b..3d92a67 100644 --- 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 @@ -1,8 +1,18 @@ package net.nuggetmc.ai.bot.agent.legacyagent.ai; public enum BotDataType { - CRITICAL_HEALTH, - DISTANCE_XZ, - DISTANCE_Y, - ENEMY_BLOCKING + 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; + } } diff --git a/src/main/java/net/nuggetmc/ai/bot/agent/legacyagent/ai/IntelligenceAgent.java b/src/main/java/net/nuggetmc/ai/bot/agent/legacyagent/ai/IntelligenceAgent.java new file mode 100644 index 0000000..c7b0622 --- /dev/null +++ b/src/main/java/net/nuggetmc/ai/bot/agent/legacyagent/ai/IntelligenceAgent.java @@ -0,0 +1,335 @@ +package net.nuggetmc.ai.bot.agent.legacyagent.ai; + +import com.jonahseguin.drink.utils.ChatUtils; +import net.minecraft.server.v1_16_R3.EntityLiving; +import net.nuggetmc.ai.TerminatorPlus; +import net.nuggetmc.ai.bot.Bot; +import net.nuggetmc.ai.bot.BotManager; +import net.nuggetmc.ai.bot.agent.legacyagent.EnumTargetGoal; +import net.nuggetmc.ai.bot.agent.legacyagent.LegacyAgent; +import net.nuggetmc.ai.command.commands.AICommand; +import net.nuggetmc.ai.utils.MathUtils; +import net.nuggetmc.ai.utils.MojangAPI; +import net.nuggetmc.ai.utils.PlayerUtils; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitScheduler; + +import java.text.NumberFormat; +import java.text.SimpleDateFormat; +import java.util.*; + +public class IntelligenceAgent { + + /* + * export all agent data to the plugin folder as separate folder things + * commands /ai stop and /ai pause + * if a session with name already exists keep adding underscores + * /ai conclude or /ai finish + */ + + private final TerminatorPlus plugin; + private final BotManager manager; + private final AICommand aiManager; + private final BukkitScheduler scheduler; + + private LegacyAgent agent; + private Thread thread; + private boolean active; + + private final String name; + + private final String botName; + private final String botSkin; + private final int cutoff; + + private final Map bots; + + private int populationSize; + private int generation; + + private Player primary; + + private final Set users; + + private final Map>>> genProfiles; + + public IntelligenceAgent(AICommand aiManager, int populationSize, String name, String skin) { + this.plugin = TerminatorPlus.getInstance(); + this.manager = plugin.getManager(); + this.aiManager = aiManager; + this.scheduler = Bukkit.getScheduler(); + this.name = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss").format(Calendar.getInstance().getTime()); + this.botName = name; + this.botSkin = skin; + this.bots = new HashMap<>(); + this.users = new HashSet<>(Collections.singletonList(Bukkit.getConsoleSender())); + this.cutoff = 5; + this.genProfiles = new HashMap<>(); + this.populationSize = populationSize; + this.active = true; + + scheduler.runTaskAsynchronously(plugin, () -> { + thread = Thread.currentThread(); + + try { + task(); + } catch (Exception e) { + print(e); + print("The thread has been interrupted."); + print("The session will now close."); + close(); + } + }); + } + + private void task() throws InterruptedException { + setup(); + sleep(1000); + + while (active) { + runGeneration(); + } + + sleep(5000); + close(); + } + + private void runGeneration() throws InterruptedException { + generation++; + + print("Starting generation " + ChatColor.RED + generation + ChatColor.RESET + "..."); + + sleep(2000); + + String skinName = botSkin == null ? this.botName : botSkin; + + print("Fetching skin data for " + ChatColor.GREEN + skinName + ChatColor.RESET + "..."); + + String[] skinData = MojangAPI.getSkin(skinName); + + String botName = this.botName.endsWith("%") ? this.botName : this.botName + "%"; + + print("Creating " + (populationSize == 1 ? "new bot" : ChatColor.RED + NumberFormat.getInstance(Locale.US).format(populationSize) + ChatColor.RESET + " new bots") + + " with name " + ChatColor.GREEN + botName.replace("%", ChatColor.LIGHT_PURPLE + "%" + ChatColor.RESET) + + (botSkin == null ? "" : ChatColor.RESET + " and skin " + ChatColor.GREEN + botSkin) + + ChatColor.RESET + "..."); + + Set>> loadedProfiles = genProfiles.get(generation); + Location loc = PlayerUtils.findAbove(primary.getLocation(), 20); + + scheduler.runTask(plugin, () -> { + Set bots; + + if (loadedProfiles == null) { + bots = manager.createBots(loc, botName, skinData, populationSize, NeuralNetwork.RANDOM); + } else { + List networks = new ArrayList<>(); + loadedProfiles.forEach(profile -> networks.add(NeuralNetwork.createNetworkFromProfile(profile))); + + if (populationSize != networks.size()) { + print("An exception has occured."); + print("The stored population size differs from the size of the stored networks."); + close(); + return; + } + + bots = manager.createBots(loc, botName, skinData, networks); + } + + bots.forEach(bot -> { + String name = bot.getName(); + + while (this.bots.containsKey(name)) { + name += "_"; + } + + this.bots.put(name, bot); + }); + }); + + while (bots.size() != populationSize) { + sleep(1000); + } + + sleep(2000); + print("The bots will now attack each other."); + + agent.setTargetType(EnumTargetGoal.NEAREST_BOT); + + while (aliveCount() > 1) { + sleep(1000); + } + + print("Generation " + ChatColor.RED + generation + ChatColor.RESET + " has ended."); + + HashMap values = new HashMap<>(); + + for (Bot bot : bots.values()) { + values.put(bot, bot.getAliveTicks()); + } + + List> sorted = MathUtils.sortByValue(values); + Set winners = new HashSet<>(); + + int i = 1; + + for (Map.Entry entry : sorted) { + Bot bot = entry.getKey(); + boolean check = i <= cutoff; + if (check) { + print(ChatColor.GRAY + "[" + ChatColor.YELLOW + "#" + i + ChatColor.GRAY + "] " + ChatColor.GREEN + bot.getName() + + ChatUtils.BULLET_FORMATTED + ChatColor.RED + bot.getKills() + " kills"); + winners.add(bot); + } + + i++; + } + + sleep(3000); + + Map>> lists = new HashMap<>(); + + winners.forEach(bot -> { + Map> data = bot.getNeuralNetwork().values(); + + data.forEach((nodeType, node) -> { + if (!lists.containsKey(nodeType)) { + lists.put(nodeType, new HashMap<>()); + } + + Map> nodeValues = lists.get(nodeType); + + node.forEach((dataType, value) -> { + if (!nodeValues.containsKey(dataType)) { + nodeValues.put(dataType, new ArrayList<>()); + } + + nodeValues.get(dataType).add(value); + }); + }); + }); + + Set>> profiles = new HashSet<>(); + + double mutationSize = MathUtils.getMutationSize(generation); + + for (int j = 0; j < populationSize; j++) { + Map> profile = new HashMap<>(); + + lists.forEach((nodeType, map) -> { + Map points = new HashMap<>(); + + map.forEach((dataType, dataPoints) -> { + double value = ((int) (10 * MathUtils.generateConnectionValue(dataPoints, mutationSize))) / 10D; + + points.put(dataType, value); + }); + + profile.put(nodeType, points); + }); + + profiles.add(profile); + } + + genProfiles.put(generation + 1, profiles); + + sleep(2000); + + clearBots(); + + agent.setTargetType(EnumTargetGoal.NONE); + } + + private int aliveCount() { + return (int) bots.values().stream().filter(EntityLiving::isAlive).count(); + } + + private void close() { + aiManager.clearSession(); + stop(); // safety call + } + + public void stop() { + if (this.active) { + this.active = false; + } + + if (!thread.isInterrupted()) { + this.thread.interrupt(); + } + } + + private void sleep(long millis) throws InterruptedException { + Thread.sleep(millis); + } + + public String getName() { + return name; + } + + public void addUser(CommandSender sender) { + users.add(sender); + print(sender.getName() + " has been added to the userlist."); + + if (primary == null && sender instanceof Player) { + setPrimary((Player) sender); + } + } + + public void setPrimary(Player player) { + this.primary = player; + print(player.getName() + " has been set as the primary user."); + } + + private void print(Object... objects) { + String message = ChatColor.DARK_GREEN + "[REINFORCEMENT] " + ChatColor.RESET + String.join(" ", Arrays.stream(objects).map(String::valueOf).toArray(String[]::new)); + users.forEach(u -> u.sendMessage(message)); + // log -> ChatColor.stripColor(message); + } + + private void setup() { + clearBots(); + + if (populationSize < cutoff) { + populationSize = cutoff; + print("The input value for the population size is lower than the cutoff (" + ChatColor.RED + cutoff + ChatColor.RESET + ")!" + + " The new population size is " + ChatColor.RED + populationSize + ChatColor.RESET + "."); + } + + if (!(manager.getAgent() instanceof LegacyAgent)) { + print("The AI manager currently only supports " + ChatColor.AQUA + "LegacyAgent" + ChatColor.RESET + "."); + close(); + return; + } + + agent = (LegacyAgent) manager.getAgent(); + agent.setTargetType(EnumTargetGoal.NONE); + + print("The bot target goal has been set to " + ChatColor.YELLOW + EnumTargetGoal.NONE.name() + ChatColor.RESET + "."); + print("Disabling target offsets..."); + + agent.offsets = false; + + print("Disabling bot drops..."); + + agent.setDrops(false); + + print(ChatColor.GREEN + "Setup is now complete."); + } + + private void clearBots() { + print("Removing all current bots..."); + + int size = manager.fetch().size(); + manager.reset(); + + String formatted = NumberFormat.getNumberInstance(Locale.US).format(size); + print("Removed " + ChatColor.RED + formatted + ChatColor.RESET + " entit" + (size == 1 ? "y" : "ies") + "."); + + bots.clear(); + } +} 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 deleted file mode 100644 index 0514c50..0000000 --- a/src/main/java/net/nuggetmc/ai/bot/agent/legacyagent/ai/NetworkType.java +++ /dev/null @@ -1,5 +0,0 @@ -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 c6302e3..4d8cf15 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 @@ -14,16 +14,31 @@ public class NeuralNetwork { // 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 nodes; - private NeuralNetwork(BotNode... nodes) { - this.nodes = new HashMap<>(); + private final boolean dynamicLR; - Arrays.stream(nodes).forEach(n -> this.nodes.put(n, new NodeConnections())); + public static final NeuralNetwork RANDOM = new NeuralNetwork(new HashMap<>()); + + private NeuralNetwork(Map> 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> profile) { + return new NeuralNetwork(profile); } public static NeuralNetwork generateRandomNetwork() { - return new NeuralNetwork(BotNode.values()); + return new NeuralNetwork(null); } public NodeConnections fetch(BotNode node) { @@ -34,33 +49,46 @@ public class NeuralNetwork { 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 nodes() { + return nodes; + } + + public boolean dynamicLR() { + return dynamicLR; + } + + public Map> values() { + Map> output = new HashMap<>(); + nodes.forEach((nodeType, node) -> output.put(nodeType, node.getValues())); + return output; + } + public String output() { - return generateString(false); + List strings = new ArrayList<>(); + 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, ", ") + "]"; } @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)); - } + nodes.forEach((nodeType, node) -> { + List 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, ",") + "}"); + }); - Collections.sort(strings); - - return "[" + StringUtils.join(strings, ", ") + "]"; + return "NeuralNetwork{nodes:[" + 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 d6e2a87..d5bb1b8 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 @@ -25,6 +25,10 @@ public class NodeConnections { Arrays.stream(BotDataType.values()).forEach(type -> connections.put(type, generateValue())); } + public NodeConnections(Map values) { + this.connections = values; + } + private double generateValue() { return Math.random() * 20 - 10; } @@ -37,6 +41,10 @@ public class NodeConnections { return value; } + public Map getValues() { + return connections; + } + public double getValue(BotDataType dataType) { return connections.get(dataType); } diff --git a/src/main/java/net/nuggetmc/ai/bot/event/BotDeathEvent.java b/src/main/java/net/nuggetmc/ai/bot/event/BotDeathEvent.java new file mode 100644 index 0000000..cc0863a --- /dev/null +++ b/src/main/java/net/nuggetmc/ai/bot/event/BotDeathEvent.java @@ -0,0 +1,18 @@ +package net.nuggetmc.ai.bot.event; + +import net.nuggetmc.ai.bot.Bot; +import org.bukkit.event.entity.PlayerDeathEvent; + +public class BotDeathEvent extends PlayerDeathEvent { + + private final Bot bot; + + public BotDeathEvent(PlayerDeathEvent event, Bot bot) { + super(event.getEntity(), event.getDrops(), event.getDroppedExp(), event.getDeathMessage()); + this.bot = bot; + } + + public Bot getBot() { + return bot; + } +} diff --git a/src/main/java/net/nuggetmc/ai/bot/event/BotKilledByPlayerEvent.java b/src/main/java/net/nuggetmc/ai/bot/event/BotKilledByPlayerEvent.java new file mode 100644 index 0000000..fef40d7 --- /dev/null +++ b/src/main/java/net/nuggetmc/ai/bot/event/BotKilledByPlayerEvent.java @@ -0,0 +1,26 @@ +package net.nuggetmc.ai.bot.event; + +import net.nuggetmc.ai.bot.Bot; +import org.bukkit.entity.Player; + +public class BotKilledByPlayerEvent { + + // eventually also call this event for deaths from other damage causes within combat time + // (like hitting the ground too hard) + + private final Bot bot; + private final Player player; + + public BotKilledByPlayerEvent(Bot bot, Player player) { + this.bot = bot; + this.player = player; + } + + public Bot getBot() { + return bot; + } + + public Player getPlayer() { + return player; + } +} diff --git a/src/main/java/net/nuggetmc/ai/command/CommandHandler.java b/src/main/java/net/nuggetmc/ai/command/CommandHandler.java index d6a7783..ecbe52a 100644 --- a/src/main/java/net/nuggetmc/ai/command/CommandHandler.java +++ b/src/main/java/net/nuggetmc/ai/command/CommandHandler.java @@ -28,11 +28,13 @@ public class CommandHandler { private final DrinkCommandService drink; private final Map, List> help; + private final Map commandMap; public CommandHandler(TerminatorPlus plugin) { this.plugin = plugin; this.drink = (DrinkCommandService) Drink.get(plugin); this.help = new HashMap<>(); + this.commandMap = new HashMap<>(); this.registerCommands(); this.drink.registerCommands(); } @@ -45,10 +47,15 @@ public class CommandHandler { private void registerCommand(@Nonnull CommandInstance handler, @Nonnull String name, @Nullable String... aliases) { handler.setName(name); + commandMap.put(name, handler); drink.register(handler, MANAGE_PERMISSION, name, aliases); setHelp(handler.getClass()); } + public CommandInstance getComand(String name) { + return commandMap.get(name); + } + public void sendRootInfo(CommandInstance commandInstance, CommandSender sender) { sender.sendMessage(ChatUtils.LINE); sender.sendMessage(ChatColor.GOLD + plugin.getName() + ChatUtils.BULLET_FORMATTED + ChatColor.GRAY 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 45441be..9bb3712 100644 --- a/src/main/java/net/nuggetmc/ai/command/commands/AICommand.java +++ b/src/main/java/net/nuggetmc/ai/command/commands/AICommand.java @@ -3,25 +3,44 @@ package net.nuggetmc.ai.command.commands; import com.jonahseguin.drink.annotation.Command; import com.jonahseguin.drink.annotation.OptArg; import com.jonahseguin.drink.annotation.Sender; +import com.jonahseguin.drink.utils.ChatUtils; import net.nuggetmc.ai.TerminatorPlus; +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.NetworkType; +import net.nuggetmc.ai.bot.agent.legacyagent.ai.IntelligenceAgent; +import net.nuggetmc.ai.bot.agent.legacyagent.ai.NeuralNetwork; import net.nuggetmc.ai.command.CommandHandler; import net.nuggetmc.ai.command.CommandInstance; +import net.nuggetmc.ai.utils.MathUtils; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitScheduler; + +import java.util.ArrayList; +import java.util.List; public class AICommand extends CommandInstance { + /* + * ideas + * ability to export neural network data to a text file, and also load from them + * maybe also have a custom extension like .tplus and encrypt it in base64 + */ + + private final TerminatorPlus plugin; private final BotManager manager; - private final Agent agent; + private final BukkitScheduler scheduler; + + private IntelligenceAgent agent; public AICommand(CommandHandler commandHandler) { super(commandHandler); - this.manager = TerminatorPlus.getInstance().getManager(); - this.agent = manager.getAgent(); + this.plugin = TerminatorPlus.getInstance(); + this.manager = plugin.getManager(); + this.scheduler = Bukkit.getScheduler(); } @Command( @@ -37,6 +56,112 @@ public class AICommand extends CommandInstance { usage = " [skin]" ) public void random(@Sender Player sender, int n, String name, @OptArg String skin) { - manager.createBots(sender, name, skin, n, NetworkType.RANDOM); + manager.createBots(sender, name, skin, n, NeuralNetwork.RANDOM); + } + + @Command( + name = "reinforcement", + desc = "Begin an AI training session.", + usage = " [skin]" + ) + public void reinforcement(@Sender Player sender, int populationSize, String name, @OptArg String skin) { + // automatically do the -% thing, store values in map + // for now only 1 session at a time, have a set of commandsenders to see output, including console + // automatically reset all existing bots at the start, set targets towards each other + // also in the future make this a subcommand, with /ai reinforcement defaults, /ai reinforcement begin/start + // or just make /ai defaults with reinforcement options + + if (agent != null) { + sender.sendMessage("A session is already active."); + return; + } + + sender.sendMessage("Starting a new session..."); + + agent = new IntelligenceAgent(this, populationSize, name, skin); + agent.addUser(sender); + } + + @Command( + name = "stop", + desc = "End a currently running AI training session." + ) + public void stop(@Sender CommandSender sender) { + if (agent == null) { + sender.sendMessage("No session is currently active."); + return; + } + + sender.sendMessage("Stopping the current session..."); + String name = agent.getName(); + clearSession(); + + scheduler.runTaskLater(plugin, () -> sender.sendMessage("The session " + ChatColor.YELLOW + name + ChatColor.RESET + " has been closed."), 10); + } + + public void clearSession() { + if (agent != null) { + agent.stop(); + agent = null; + } + } + + public boolean hasActiveSession() { + return agent != null; + } + + @Command( + name = "info", + desc = "Display neural network information about a bot.", + usage = "", + autofill = "infoAutofill" + ) + public void info(@Sender CommandSender sender, String name) { + sender.sendMessage("Processing request..."); + + scheduler.runTaskAsynchronously(plugin, () -> { + try { + Bot bot = manager.getFirst(name); + + if (bot == null) { + sender.sendMessage("Could not find bot " + ChatColor.GREEN + name + ChatColor.RESET + "!"); + return; + } + + if (!bot.hasNeuralNetwork()) { + sender.sendMessage("The bot " + ChatColor.GREEN + name + ChatColor.RESET + " does not have a neural network!"); + return; + } + + NeuralNetwork network = bot.getNeuralNetwork(); + List strings = new ArrayList<>(); + + network.nodes().forEach((nodeType, node) -> { + strings.add(""); + strings.add(ChatColor.YELLOW + "\"" + nodeType.name().toLowerCase() + "\"" + ChatColor.RESET + ":"); + List values = new ArrayList<>(); + node.getValues().forEach((dataType, value) -> values.add(ChatUtils.BULLET_FORMATTED + "node" + + dataType.getShorthand().toUpperCase() + ": " + ChatColor.RED + MathUtils.round2Dec(value))); + strings.addAll(values); + }); + + sender.sendMessage(ChatUtils.LINE); + sender.sendMessage(ChatColor.DARK_GREEN + "NeuralNetwork" + ChatUtils.BULLET_FORMATTED + ChatColor.GRAY + "[" + ChatColor.GREEN + name + ChatColor.GRAY + "]"); + strings.forEach(sender::sendMessage); + sender.sendMessage(ChatUtils.LINE); + } + + catch (Exception e) { + sender.sendMessage(ChatUtils.EXCEPTION_MESSAGE); + } + }); + } + + public List infoAutofill(CommandSender sender, String[] args) { + if (args.length == 2) { + return manager.fetchNames(); + } else { + return null; + } } } 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 9e66376..5e6e32e 100644 --- a/src/main/java/net/nuggetmc/ai/command/commands/BotCommand.java +++ b/src/main/java/net/nuggetmc/ai/command/commands/BotCommand.java @@ -32,6 +32,8 @@ public class BotCommand extends CommandInstance { private final BukkitScheduler scheduler; private final DecimalFormat formatter; + private AICommand aiManager; + public BotCommand(CommandHandler commandHandler) { super(commandHandler); @@ -39,6 +41,8 @@ public class BotCommand extends CommandInstance { this.manager = plugin.getManager(); this.scheduler = Bukkit.getScheduler(); this.formatter = new DecimalFormat("0.##"); + + scheduler.runTask(plugin, () -> aiManager = (AICommand) plugin.getHandler().getComand("ai")); } @Command( @@ -69,7 +73,8 @@ public class BotCommand extends CommandInstance { @Command( name = "info", desc = "Information about loaded bots.", - usage = "[name]" + usage = "[name]", + autofill = "infoAutofill" ) public void info(@Sender CommandSender sender, @OptArg String name) { if (name == null) { @@ -115,11 +120,19 @@ public class BotCommand extends CommandInstance { } catch (Exception e) { - sender.sendMessage(ChatColor.RED + "An exception has occured. Please try again."); + sender.sendMessage(ChatUtils.EXCEPTION_MESSAGE); } }); } + public List infoAutofill(CommandSender sender, String[] args) { + if (args.length == 2) { + return manager.fetchNames(); + } else { + return null; + } + } + @Command( name = "reset", desc = "Remove all loaded bots." @@ -132,6 +145,10 @@ public class BotCommand extends CommandInstance { String formatted = NumberFormat.getNumberInstance(Locale.US).format(size); sender.sendMessage("Removed " + ChatColor.RED + formatted + ChatColor.RESET + " entit" + (size == 1 ? "y" : "ies") + "."); + + if (aiManager.hasActiveSession()) { + Bukkit.dispatchCommand(sender, "ai stop"); + } } @Command( diff --git a/src/main/java/net/nuggetmc/ai/utils/Debugger.java b/src/main/java/net/nuggetmc/ai/utils/Debugger.java index b704a48..8c6382e 100644 --- a/src/main/java/net/nuggetmc/ai/utils/Debugger.java +++ b/src/main/java/net/nuggetmc/ai/utils/Debugger.java @@ -1,5 +1,6 @@ package net.nuggetmc.ai.utils; +import net.minecraft.server.v1_16_R3.EntityLiving; import net.nuggetmc.ai.TerminatorPlus; import net.nuggetmc.ai.bot.Bot; import net.nuggetmc.ai.bot.agent.Agent; @@ -10,11 +11,13 @@ import org.bukkit.command.CommandSender; import org.bukkit.entity.ArmorStand; import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; import org.bukkit.permissions.ServerOperator; import org.bukkit.util.Vector; import java.beans.Statement; import java.util.*; +import java.util.stream.Collectors; public class Debugger { @@ -191,7 +194,7 @@ public class Debugger { } public void item() { - TerminatorPlus.getInstance().getManager().fetch().forEach(b -> b.item = true); + TerminatorPlus.getInstance().getManager().fetch().forEach(b -> b.setDefaultItem(new ItemStack(Material.IRON_SWORD))); } public void j(boolean b) { @@ -215,7 +218,7 @@ public class Debugger { } public void tp() { - Bot bot = MathUtils.getRandomSetElement(TerminatorPlus.getInstance().getManager().fetch()); + Bot bot = MathUtils.getRandomSetElement(TerminatorPlus.getInstance().getManager().fetch().stream().filter(EntityLiving::isAlive).collect(Collectors.toSet())); if (bot == null) { print("Failed to locate a bot."); diff --git a/src/main/java/net/nuggetmc/ai/utils/ItemUtils.java b/src/main/java/net/nuggetmc/ai/utils/ItemUtils.java new file mode 100644 index 0000000..79dda46 --- /dev/null +++ b/src/main/java/net/nuggetmc/ai/utils/ItemUtils.java @@ -0,0 +1,59 @@ +package net.nuggetmc.ai.utils; + +import org.bukkit.inventory.ItemStack; + +public class ItemUtils { + + public static double getLegacyAttackDamage(ItemStack item) { + switch (item.getType()) { + default: + return 0.25; + + case WOODEN_SHOVEL: + case GOLDEN_SHOVEL: + case WOODEN_HOE: + case GOLDEN_HOE: + case STONE_HOE: + case IRON_HOE: + case DIAMOND_HOE: + case NETHERITE_HOE: + return 1; + + case WOODEN_PICKAXE: + case GOLDEN_PICKAXE: + case STONE_SHOVEL: + return 2; + + case WOODEN_AXE: + case GOLDEN_AXE: + case STONE_PICKAXE: + case IRON_SHOVEL: + return 3; + + case WOODEN_SWORD: + case GOLDEN_SWORD: + case STONE_AXE: + case IRON_PICKAXE: + case DIAMOND_SHOVEL: + return 4; + + case STONE_SWORD: + case IRON_AXE: + case DIAMOND_PICKAXE: + case NETHERITE_SHOVEL: + return 5; + + case IRON_SWORD: + case DIAMOND_AXE: + case NETHERITE_PICKAXE: + return 6; + + case DIAMOND_SWORD: + case NETHERITE_AXE: + return 7; + + case NETHERITE_SWORD: + return 8; + } + } +} diff --git a/src/main/java/net/nuggetmc/ai/utils/MathUtils.java b/src/main/java/net/nuggetmc/ai/utils/MathUtils.java index 18d4471..5dd492c 100644 --- a/src/main/java/net/nuggetmc/ai/utils/MathUtils.java +++ b/src/main/java/net/nuggetmc/ai/utils/MathUtils.java @@ -1,11 +1,12 @@ package net.nuggetmc.ai.utils; +import net.nuggetmc.ai.bot.Bot; import org.bukkit.util.NumberConversions; import org.bukkit.util.Vector; import java.text.DecimalFormat; -import java.util.Random; -import java.util.Set; +import java.util.*; +import java.util.stream.Collectors; public class MathUtils { @@ -90,4 +91,93 @@ public class MathUtils { public static String round2Dec(double n) { return FORMATTER_2.format(n); } + + public static List> sortByValue(HashMap hm) { + List> list = new LinkedList<>(hm.entrySet()); + list.sort(Map.Entry.comparingByValue()); + Collections.reverse(list); + return list; + } + + public static double generateConnectionValue(List list, double mutationSize) { + double[] bounds = getBounds(list, mutationSize); + return random(bounds[0], bounds[1]); + } + + public static double generateConnectionValue(List list) { + return generateConnectionValue(list, 0); + } + + public static double random(double low, double high) { + return Math.random() * (high - low) + low; + } + + public static double sum(List list) { + return list.stream().mapToDouble(n -> n).sum(); + } + + public static double min(List 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 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 list) { + return (min(list) + max(list)) / 2D; + } + + public static double distribution(List 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 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; + } } diff --git a/src/main/java/net/nuggetmc/ai/utils/PlayerUtils.java b/src/main/java/net/nuggetmc/ai/utils/PlayerUtils.java index 0985bca..dfdd70c 100644 --- a/src/main/java/net/nuggetmc/ai/utils/PlayerUtils.java +++ b/src/main/java/net/nuggetmc/ai/utils/PlayerUtils.java @@ -2,6 +2,7 @@ package net.nuggetmc.ai.utils; import net.nuggetmc.ai.TerminatorPlus; import org.bukkit.GameMode; +import org.bukkit.Location; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; @@ -49,4 +50,21 @@ public class PlayerUtils { Debugger.log("Failed to fetch from the usercache."); } } + + public static Location findAbove(Location loc, int amount) { + boolean check = false; + + for (int i = 0; i <= amount; i++) { + if (loc.clone().add(0, i, 0).getBlock().getType().isSolid()) { + check = true; + break; + } + } + + if (check) { + return loc; + } else { + return loc.clone().add(0, amount, 0); + } + } }