added fire damage
This commit is contained in:
@@ -2,23 +2,24 @@ package net.nuggetmc.ai;
|
|||||||
|
|
||||||
import net.nuggetmc.ai.bot.BotManager;
|
import net.nuggetmc.ai.bot.BotManager;
|
||||||
import net.nuggetmc.ai.command.CommandHandler;
|
import net.nuggetmc.ai.command.CommandHandler;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
public class PlayerAI extends JavaPlugin {
|
public class PlayerAI extends JavaPlugin {
|
||||||
|
|
||||||
public static final double VERSION = 3.0;
|
|
||||||
|
|
||||||
private static PlayerAI instance;
|
private static PlayerAI instance;
|
||||||
|
private static String version;
|
||||||
|
|
||||||
private CommandHandler handler;
|
|
||||||
private BotManager manager;
|
private BotManager manager;
|
||||||
|
|
||||||
public static PlayerAI getInstance() {
|
public static PlayerAI getInstance() {
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CommandHandler getHandler() {
|
public static String getVersion() {
|
||||||
return handler;
|
return version;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BotManager getManager() {
|
public BotManager getManager() {
|
||||||
@@ -28,13 +29,14 @@ public class PlayerAI extends JavaPlugin {
|
|||||||
@Override
|
@Override
|
||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
instance = this;
|
instance = this;
|
||||||
|
version = this.getDescription().getVersion();
|
||||||
|
|
||||||
// Create Instances
|
// Create Instances
|
||||||
this.manager = new BotManager();
|
this.manager = new BotManager();
|
||||||
this.handler = new CommandHandler(this);
|
new CommandHandler(this);
|
||||||
|
|
||||||
// Register all the things
|
// Register event listeners
|
||||||
this.registerEvents();
|
this.registerEvents(manager);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -42,7 +44,7 @@ public class PlayerAI extends JavaPlugin {
|
|||||||
manager.reset();
|
manager.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void registerEvents() {
|
private void registerEvents(Listener... listeners) {
|
||||||
getServer().getPluginManager().registerEvents(manager, this);
|
Arrays.stream(listeners).forEach(li -> this.getServer().getPluginManager().registerEvents(li, this));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import net.minecraft.server.v1_16_R3.*;
|
|||||||
import net.nuggetmc.ai.PlayerAI;
|
import net.nuggetmc.ai.PlayerAI;
|
||||||
import net.nuggetmc.ai.utils.BotUtils;
|
import net.nuggetmc.ai.utils.BotUtils;
|
||||||
import net.nuggetmc.ai.utils.MathUtils;
|
import net.nuggetmc.ai.utils.MathUtils;
|
||||||
import net.nuggetmc.ai.utils.MojangAPI;
|
|
||||||
import org.bukkit.Material;
|
import org.bukkit.Material;
|
||||||
import org.bukkit.SoundCategory;
|
import org.bukkit.SoundCategory;
|
||||||
import org.bukkit.World;
|
import org.bukkit.World;
|
||||||
@@ -18,35 +17,44 @@ import org.bukkit.craftbukkit.v1_16_R3.CraftWorld;
|
|||||||
import org.bukkit.craftbukkit.v1_16_R3.entity.CraftEntity;
|
import org.bukkit.craftbukkit.v1_16_R3.entity.CraftEntity;
|
||||||
import org.bukkit.craftbukkit.v1_16_R3.entity.CraftPlayer;
|
import org.bukkit.craftbukkit.v1_16_R3.entity.CraftPlayer;
|
||||||
import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftItemStack;
|
import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftItemStack;
|
||||||
|
import org.bukkit.entity.Damageable;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.util.Vector;
|
import org.bukkit.util.Vector;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.Collections;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public class Bot extends EntityPlayer {
|
public class Bot extends EntityPlayer {
|
||||||
|
|
||||||
public Vector velocity;
|
public Vector velocity;
|
||||||
|
private Vector oldVelocity;
|
||||||
|
|
||||||
|
private boolean removeOnDeath;
|
||||||
|
|
||||||
private byte aliveTicks;
|
private byte aliveTicks;
|
||||||
private byte kbTicks;
|
private byte fireTicks;
|
||||||
private byte jumpTicks;
|
|
||||||
private byte groundTicks;
|
private byte groundTicks;
|
||||||
|
private byte jumpTicks;
|
||||||
|
private byte kbTicks;
|
||||||
|
private byte noFallTicks;
|
||||||
|
|
||||||
private final Vector offset;
|
private final Vector offset;
|
||||||
|
|
||||||
public Bot(MinecraftServer minecraftServer, WorldServer worldServer, GameProfile profile, PlayerInteractManager manager) {
|
private Bot(MinecraftServer minecraftServer, WorldServer worldServer, GameProfile profile, PlayerInteractManager manager) {
|
||||||
super(minecraftServer, worldServer, profile, manager);
|
super(minecraftServer, worldServer, profile, manager);
|
||||||
|
|
||||||
this.velocity = new Vector(0, 0, 0);
|
this.velocity = new Vector(0, 0, 0);
|
||||||
|
this.oldVelocity = velocity.clone();
|
||||||
|
this.noFallTicks = 60;
|
||||||
|
this.fireTicks = 0;
|
||||||
this.offset = MathUtils.circleOffset(3);
|
this.offset = MathUtils.circleOffset(3);
|
||||||
|
|
||||||
datawatcher.set(new DataWatcherObject<>(16, DataWatcherRegistry.a), (byte) 0xFF);
|
datawatcher.set(new DataWatcherObject<>(16, DataWatcherRegistry.a), (byte) 0xFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Bot createBot(Location loc, String name, String[] skin) {
|
public static Bot createBot(Location loc, String name, String[] skin, boolean removeOnDeath) {
|
||||||
MinecraftServer nmsServer = ((CraftServer) Bukkit.getServer()).getServer();
|
MinecraftServer nmsServer = ((CraftServer) Bukkit.getServer()).getServer();
|
||||||
WorldServer nmsWorld = ((CraftWorld) Objects.requireNonNull(loc.getWorld())).getHandle();
|
WorldServer nmsWorld = ((CraftWorld) Objects.requireNonNull(loc.getWorld())).getHandle();
|
||||||
|
|
||||||
@@ -60,6 +68,7 @@ public class Bot extends EntityPlayer {
|
|||||||
bot.playerConnection = new PlayerConnection(nmsServer, new NetworkManager(EnumProtocolDirection.CLIENTBOUND), bot);
|
bot.playerConnection = new PlayerConnection(nmsServer, new NetworkManager(EnumProtocolDirection.CLIENTBOUND), bot);
|
||||||
bot.setLocation(loc.getX(), loc.getY(), loc.getZ(), loc.getYaw(), loc.getPitch());
|
bot.setLocation(loc.getX(), loc.getY(), loc.getZ(), loc.getYaw(), loc.getPitch());
|
||||||
bot.getBukkitEntity().setNoDamageTicks(0);
|
bot.getBukkitEntity().setNoDamageTicks(0);
|
||||||
|
bot.removeOnDeath = removeOnDeath;
|
||||||
nmsWorld.addEntity(bot);
|
nmsWorld.addEntity(bot);
|
||||||
|
|
||||||
bot.renderAll();
|
bot.renderAll();
|
||||||
@@ -69,19 +78,12 @@ public class Bot extends EntityPlayer {
|
|||||||
return bot;
|
return bot;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Vector prevVel = new Vector(0, 0, 0);
|
|
||||||
public int velCount;
|
|
||||||
|
|
||||||
public static Bot createBot(Location loc, String name, String skinName) {
|
|
||||||
return createBot(loc, name, MojangAPI.getSkin(skinName));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void renderAll() {
|
private void renderAll() {
|
||||||
Packet<?>[] packets = getRenderPackets();
|
Packet<?>[] packets = getRenderPackets();
|
||||||
Bukkit.getOnlinePlayers().forEach(p -> render(((CraftPlayer) p).getHandle().playerConnection, packets, false));
|
Bukkit.getOnlinePlayers().forEach(p -> render(((CraftPlayer) p).getHandle().playerConnection, packets, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void render(PlayerConnection connection, Packet<?>[] packets, boolean login) {
|
private void render(PlayerConnection connection, Packet<?>[] packets, boolean login) {
|
||||||
connection.sendPacket(packets[0]);
|
connection.sendPacket(packets[0]);
|
||||||
connection.sendPacket(packets[1]);
|
connection.sendPacket(packets[1]);
|
||||||
connection.sendPacket(packets[2]);
|
connection.sendPacket(packets[2]);
|
||||||
@@ -133,15 +135,23 @@ public class Bot extends EntityPlayer {
|
|||||||
return aliveTicks % i == 0;
|
return aliveTicks % i == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void sendPacket(Packet<?> packet) {
|
||||||
|
Bukkit.getOnlinePlayers().forEach(p -> ((CraftPlayer) p).getHandle().playerConnection.sendPacket(packet));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void tick() {
|
public void tick() {
|
||||||
super.tick();
|
super.tick();
|
||||||
|
|
||||||
|
if (!isAlive()) return;
|
||||||
|
|
||||||
aliveTicks++;
|
aliveTicks++;
|
||||||
|
|
||||||
|
if (fireTicks > 0) --fireTicks;
|
||||||
if (noDamageTicks > 0) --noDamageTicks;
|
if (noDamageTicks > 0) --noDamageTicks;
|
||||||
if (kbTicks > 0) --kbTicks;
|
|
||||||
if (jumpTicks > 0) --jumpTicks;
|
if (jumpTicks > 0) --jumpTicks;
|
||||||
|
if (kbTicks > 0) --kbTicks;
|
||||||
|
if (noFallTicks > 0) --noFallTicks;
|
||||||
|
|
||||||
if (checkGround()) {
|
if (checkGround()) {
|
||||||
if (groundTicks < 5) groundTicks++;
|
if (groundTicks < 5) groundTicks++;
|
||||||
@@ -149,15 +159,12 @@ public class Bot extends EntityPlayer {
|
|||||||
groundTicks = 0;
|
groundTicks = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Player player = getBukkitEntity();
|
|
||||||
if (player.isDead()) return;
|
|
||||||
|
|
||||||
updateLocation();
|
updateLocation();
|
||||||
|
|
||||||
double health = player.getHealth();
|
float health = getHealth();
|
||||||
double maxHealth = player.getHealthScale();
|
float maxHealth = getMaxHealth();
|
||||||
double regenAmount = 0.05;
|
float regenAmount = 0.05f;
|
||||||
double amount;
|
float amount;
|
||||||
|
|
||||||
if (health < maxHealth - regenAmount) {
|
if (health < maxHealth - regenAmount) {
|
||||||
amount = health + regenAmount;
|
amount = health + regenAmount;
|
||||||
@@ -165,14 +172,62 @@ public class Bot extends EntityPlayer {
|
|||||||
amount = maxHealth;
|
amount = maxHealth;
|
||||||
}
|
}
|
||||||
|
|
||||||
player.setHealth(amount);
|
setHealth(amount);
|
||||||
|
|
||||||
|
fireDamageCheck();
|
||||||
|
|
||||||
|
if (!isAlive()) return;
|
||||||
|
|
||||||
|
fallDamageCheck();
|
||||||
|
|
||||||
|
oldVelocity = velocity.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fireDamageCheck() {
|
||||||
|
Material type = getLocation().getBlock().getType();
|
||||||
|
|
||||||
|
boolean lava = type == org.bukkit.Material.LAVA;
|
||||||
|
|
||||||
|
if (lava || type == org.bukkit.Material.FIRE || type == Material.SOUL_FIRE) {
|
||||||
|
ignite();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (noDamageTicks == 0) {
|
||||||
|
if (lava) {
|
||||||
|
damageEntity(DamageSource.LAVA, 4);
|
||||||
|
noDamageTicks = 12;
|
||||||
|
} else if (fireTicks > 1) {
|
||||||
|
damageEntity(DamageSource.FIRE, 1);
|
||||||
|
noDamageTicks = 20;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fireTicks == 1) {
|
||||||
|
setOnFirePackets(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ignite() {
|
||||||
|
if (fireTicks <= 1) setOnFirePackets(true);
|
||||||
|
fireTicks = 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnFirePackets(boolean onFire) {
|
||||||
|
datawatcher.set(new DataWatcherObject<>(0, DataWatcherRegistry.a), onFire ? (byte) 1 : (byte) 0);
|
||||||
|
sendPacket(new PacketPlayOutEntityMetadata(getId(), datawatcher, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fallDamageCheck() {
|
||||||
|
if (groundTicks == 0 || noFallTicks != 0) return;
|
||||||
|
double y = oldVelocity.getY();
|
||||||
|
if (y >= -0.8 || BotUtils.NO_FALL.contains(getLocation().getBlock().getType())) return;
|
||||||
|
|
||||||
|
damageEntity(DamageSource.FALL, (float) Math.pow(3.6, -y));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateLocation() {
|
private void updateLocation() {
|
||||||
double y;
|
double y;
|
||||||
|
|
||||||
// Check to reset y velocity if staying in the same position
|
|
||||||
|
|
||||||
if (isInWater()) {
|
if (isInWater()) {
|
||||||
y = Math.min(velocity.getY() + 0.1, 0.1);
|
y = Math.min(velocity.getY() + 0.1, 0.1);
|
||||||
addFriction(0.8);
|
addFriction(0.8);
|
||||||
@@ -222,10 +277,13 @@ public class Bot extends EntityPlayer {
|
|||||||
jump(new Vector(0, 0.5, 0));
|
jump(new Vector(0, 0.5, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void attack(org.bukkit.entity.Entity entity) {
|
public void attack(org.bukkit.entity.Entity entity) { // TODO botfight fix
|
||||||
faceLocation(entity.getLocation());
|
faceLocation(entity.getLocation());
|
||||||
punch();
|
punch();
|
||||||
attack(((CraftEntity) entity).getHandle());
|
|
||||||
|
if (entity instanceof Damageable) {
|
||||||
|
((Damageable) entity).damage(2, getBukkitEntity());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void punch() {
|
public void punch() {
|
||||||
@@ -285,13 +343,42 @@ public class Bot extends EntityPlayer {
|
|||||||
getBukkitEntity().remove();
|
getBukkitEntity().remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void remove() {
|
public void removeVisually() {
|
||||||
for (Player player : Bukkit.getOnlinePlayers()) {
|
this.removeTab();
|
||||||
PlayerConnection connection = ((CraftPlayer) player).getHandle().playerConnection;
|
this.setDead();
|
||||||
connection.sendPacket(new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.REMOVE_PLAYER, this));
|
}
|
||||||
}
|
|
||||||
|
|
||||||
getBukkitEntity().remove();
|
private void removeTab() {
|
||||||
|
sendPacket(new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.REMOVE_PLAYER, this));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setDead() {
|
||||||
|
this.dead = true;
|
||||||
|
this.defaultContainer.b(this);
|
||||||
|
if (this.activeContainer != null) {
|
||||||
|
this.activeContainer.b(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void dieCheck() {
|
||||||
|
if (removeOnDeath) {
|
||||||
|
PlayerAI plugin = PlayerAI.getInstance();
|
||||||
|
plugin.getManager().remove(this);
|
||||||
|
this.removeTab();
|
||||||
|
Bukkit.getScheduler().runTaskLater(plugin, this::setDead, 30);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void die() {
|
||||||
|
super.die();
|
||||||
|
this.dieCheck();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void die(DamageSource damageSource) {
|
||||||
|
super.die(damageSource);
|
||||||
|
this.dieCheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -380,15 +467,10 @@ public class Bot extends EntityPlayer {
|
|||||||
yaw = vals[0];
|
yaw = vals[0];
|
||||||
pitch = vals[1];
|
pitch = vals[1];
|
||||||
|
|
||||||
PacketPlayOutEntityHeadRotation packet = new PacketPlayOutEntityHeadRotation(getBukkitEntity().getHandle(), (byte) (yaw * 256 / 360f));
|
sendPacket(new PacketPlayOutEntityHeadRotation(getBukkitEntity().getHandle(), (byte) (yaw * 256 / 360f)));
|
||||||
Bukkit.getOnlinePlayers().forEach(p -> ((CraftPlayer) p).getHandle().playerConnection.sendPacket(packet));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setYawPitch(yaw, pitch);
|
setYawPitch(yaw, pitch);
|
||||||
|
|
||||||
// this causes a lot of lag lol
|
|
||||||
//PacketPlayOutEntity.PacketPlayOutEntityLook packet = new PacketPlayOutEntity.PacketPlayOutEntityLook(getId(), (byte) (yaw * 256 / 360f), (byte) (pitch * 256 / 360f), isOnGround());
|
|
||||||
//Bukkit.getOnlinePlayers().forEach(p -> ((CraftPlayer) p).getHandle().playerConnection.sendPacket(packet));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void attemptBlockPlace(Location loc, Material type) {
|
public void attemptBlockPlace(Location loc, Material type) {
|
||||||
@@ -407,14 +489,11 @@ public class Bot extends EntityPlayer {
|
|||||||
public void setItem(org.bukkit.inventory.ItemStack item) {
|
public void setItem(org.bukkit.inventory.ItemStack item) {
|
||||||
if (item == null) item = new org.bukkit.inventory.ItemStack(Material.AIR);
|
if (item == null) item = new org.bukkit.inventory.ItemStack(Material.AIR);
|
||||||
|
|
||||||
CraftPlayer player = getBukkitEntity();
|
getBukkitEntity().getInventory().setItemInMainHand(item);
|
||||||
player.getInventory().setItemInMainHand(item);
|
|
||||||
|
|
||||||
List<Pair<EnumItemSlot, ItemStack>> equipment = new ArrayList<>();
|
sendPacket(new PacketPlayOutEntityEquipment(getId(), new ArrayList<>(Collections.singletonList(
|
||||||
equipment.add(new Pair<>(EnumItemSlot.MAINHAND, CraftItemStack.asNMSCopy(item)));
|
new Pair<>(EnumItemSlot.MAINHAND, CraftItemStack.asNMSCopy(item))
|
||||||
|
))));
|
||||||
PacketPlayOutEntityEquipment packet = new PacketPlayOutEntityEquipment(player.getEntityId(), equipment);
|
|
||||||
Bukkit.getOnlinePlayers().forEach(p -> ((CraftPlayer) p).getHandle().playerConnection.sendPacket(packet));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void swim() {
|
public void swim() {
|
||||||
@@ -437,8 +516,7 @@ public class Bot extends EntityPlayer {
|
|||||||
|
|
||||||
private void registerPose(EntityPose pose) {
|
private void registerPose(EntityPose pose) {
|
||||||
datawatcher.set(DataWatcherRegistry.s.a(6), pose);
|
datawatcher.set(DataWatcherRegistry.s.a(6), pose);
|
||||||
PacketPlayOutEntityMetadata packet = new PacketPlayOutEntityMetadata(getId(), datawatcher, false);
|
sendPacket(new PacketPlayOutEntityMetadata(getId(), datawatcher, false));
|
||||||
Bukkit.getOnlinePlayers().forEach(p -> ((CraftPlayer) p).getHandle().playerConnection.sendPacket(packet));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -23,12 +23,14 @@ import java.util.Set;
|
|||||||
public class BotManager implements Listener {
|
public class BotManager implements Listener {
|
||||||
|
|
||||||
private final Agent agent;
|
private final Agent agent;
|
||||||
|
private final Set<Bot> bots;
|
||||||
private final NumberFormat numberFormat;
|
private final NumberFormat numberFormat;
|
||||||
|
|
||||||
private final Set<Bot> bots = new HashSet<>();
|
private boolean removeOnDeath = true;
|
||||||
|
|
||||||
public BotManager() {
|
public BotManager() {
|
||||||
this.agent = new LegacyAgent(this);
|
this.agent = new LegacyAgent(this);
|
||||||
|
this.bots = new HashSet<>();
|
||||||
this.numberFormat = NumberFormat.getInstance(Locale.US);
|
this.numberFormat = NumberFormat.getInstance(Locale.US);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,7 +69,7 @@ public class BotManager implements Listener {
|
|||||||
String[] skin = MojangAPI.getSkin(skinName);
|
String[] skin = MojangAPI.getSkin(skinName);
|
||||||
|
|
||||||
for (int i = 0; i < n; i++) {
|
for (int i = 0; i < n; i++) {
|
||||||
Bot bot = Bot.createBot(loc, name, skin);
|
Bot bot = Bot.createBot(loc, name, skin, removeOnDeath);
|
||||||
if (i > 0) bot.setVelocity(new Vector(Math.random() - 0.5, 0.5, Math.random() - 0.5).normalize().multiply(f));
|
if (i > 0) bot.setVelocity(new Vector(Math.random() - 0.5, 0.5, Math.random() - 0.5).normalize().multiply(f));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,8 +83,8 @@ public class BotManager implements Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void reset() {
|
public void reset() {
|
||||||
bots.forEach(Bot::remove);
|
bots.forEach(Bot::removeVisually);
|
||||||
bots.clear();
|
bots.clear(); // Not always necessary, but a good security measure
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
|
|||||||
@@ -17,10 +17,7 @@ import org.bukkit.inventory.ItemStack;
|
|||||||
import org.bukkit.scheduler.BukkitRunnable;
|
import org.bukkit.scheduler.BukkitRunnable;
|
||||||
import org.bukkit.util.Vector;
|
import org.bukkit.util.Vector;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.*;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
// Yes, this code is very unoptimized, I know.
|
// Yes, this code is very unoptimized, I know.
|
||||||
public class LegacyAgent extends Agent {
|
public class LegacyAgent extends Agent {
|
||||||
@@ -49,9 +46,17 @@ public class LegacyAgent extends Agent {
|
|||||||
public final Set<Bot> noFace = new HashSet<>();
|
public final Set<Bot> noFace = new HashSet<>();
|
||||||
public final Set<Player> noJump = new HashSet<>();
|
public final Set<Player> noJump = new HashSet<>();
|
||||||
|
|
||||||
|
public final Set<Bot> slow = new HashSet<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void tick() {
|
protected void tick() {
|
||||||
manager.fetch().forEach(this::tickBot);
|
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.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void center(Bot bot) {
|
private void center(Bot bot) {
|
||||||
@@ -88,7 +93,7 @@ public class LegacyAgent extends Agent {
|
|||||||
|
|
||||||
Location loc = bot.getLocation();
|
Location loc = bot.getLocation();
|
||||||
|
|
||||||
Player player = nearestPlayer(loc);
|
Player player = nearestPlayer(bot, loc);
|
||||||
if (player == null) {
|
if (player == null) {
|
||||||
// LESSLAG if (bot.tickDelay(20))
|
// LESSLAG if (bot.tickDelay(20))
|
||||||
stopMining(bot);
|
stopMining(bot);
|
||||||
@@ -169,9 +174,41 @@ public class LegacyAgent extends Agent {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}/* else if (lc && !bot.isOnGround()) {
|
} else if (LegacyMats.WATER.contains(loc.getBlock().getType())) {
|
||||||
moveSmall(bot, loc, target);
|
swim(bot, target, playerBot, player, LegacyMats.WATER.contains(loc.clone().add(0, -1, 0).getBlock().getType()));
|
||||||
}*/
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void swim(Bot bot, Location loc, Player playerNPC, Player ply, boolean anim) {
|
||||||
|
playerNPC.setSneaking(false);
|
||||||
|
|
||||||
|
Location at = bot.getLocation();
|
||||||
|
|
||||||
|
Vector vector = loc.toVector().subtract(at.toVector());
|
||||||
|
if (at.getBlockY() < ply.getLocation().getBlockY()) {
|
||||||
|
vector.setY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
vector.normalize().multiply(0.05);
|
||||||
|
vector.setY(vector.getY() * 1.2);
|
||||||
|
|
||||||
|
if (miningAnim.containsKey(playerNPC)) {
|
||||||
|
BukkitRunnable task = miningAnim.get(playerNPC);
|
||||||
|
if (task != null) {
|
||||||
|
task.cancel();
|
||||||
|
miningAnim.remove(playerNPC);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (anim) {
|
||||||
|
bot.swim();
|
||||||
|
} else {
|
||||||
|
vector.setY(0);
|
||||||
|
vector.multiply(0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
bot.faceLocation(ply.getLocation());
|
||||||
|
bot.addVelocity(vector);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void stopMining(Bot bot) {
|
private void stopMining(Bot bot) {
|
||||||
@@ -222,7 +259,11 @@ public class LegacyAgent extends Agent {
|
|||||||
vel.multiply(0.4);
|
vel.multiply(0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
vel.setY(0.4);
|
if (slow.contains(bot)) {
|
||||||
|
vel.setY(0).multiply(0.5);
|
||||||
|
} else {
|
||||||
|
vel.setY(0.4);
|
||||||
|
}
|
||||||
|
|
||||||
bot.jump(vel);
|
bot.jump(vel);
|
||||||
}
|
}
|
||||||
@@ -421,7 +462,7 @@ public class LegacyAgent extends Agent {
|
|||||||
vector.multiply(0.1);
|
vector.multiply(0.1);
|
||||||
vector.setY(0.5);
|
vector.setY(0.5);
|
||||||
|
|
||||||
npc.setVelocity(npc.getVelocity().add(vector));
|
npc.addVelocity(vector);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -444,7 +485,7 @@ public class LegacyAgent extends Agent {
|
|||||||
vector.multiply(0.1);
|
vector.multiply(0.1);
|
||||||
vector.setY(0);
|
vector.setY(0);
|
||||||
|
|
||||||
npc.setVelocity(npc.getVelocity().add(vector));
|
npc.addVelocity(vector);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -761,7 +802,11 @@ public class LegacyAgent extends Agent {
|
|||||||
bot.attack(player);
|
bot.attack(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Player nearestPlayer(Location loc) {
|
private Player nearestPlayer(Bot bot, Location loc) {
|
||||||
|
return nearestBot(bot, loc);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Player nearestRealPlayer(Location loc) {
|
||||||
Player result = null;
|
Player result = null;
|
||||||
|
|
||||||
for (Player player : Bukkit.getOnlinePlayers()) {
|
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||||
@@ -774,4 +819,24 @@ public class LegacyAgent extends Agent {
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Player nearestBot(Bot bot, Location loc) {
|
||||||
|
Player result = null;
|
||||||
|
|
||||||
|
for (Bot otherBot : manager.fetch()) {
|
||||||
|
if (bot == otherBot) continue;
|
||||||
|
|
||||||
|
Player player = otherBot.getBukkitEntity();
|
||||||
|
|
||||||
|
if (!bot.getName().equals(otherBot.getName())) {
|
||||||
|
if (loc.getWorld() != player.getWorld()) continue;
|
||||||
|
|
||||||
|
if (result == null || loc.distance(player.getLocation()) < loc.distance(result.getLocation())) {
|
||||||
|
result = player;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -130,15 +130,15 @@ public class LegacyBlockCheck {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void clutch(Bot bot, Player target) {
|
public void clutch(Bot bot, Player target) {
|
||||||
Player player = bot.getBukkitEntity();
|
Location botLoc = bot.getLocation();
|
||||||
|
|
||||||
Material type = player.getLocation().add(0, -1, 0).getBlock().getType();
|
Material type = botLoc.clone().add(0, -1, 0).getBlock().getType();
|
||||||
Material type2 = player.getLocation().add(0, -2, 0).getBlock().getType();
|
Material type2 = botLoc.clone().add(0, -2, 0).getBlock().getType();
|
||||||
|
|
||||||
if (!(LegacyMats.SPAWN.contains(type) && LegacyMats.SPAWN.contains(type2))) return;
|
if (!(LegacyMats.SPAWN.contains(type) && LegacyMats.SPAWN.contains(type2))) return;
|
||||||
|
|
||||||
if (target.getLocation().getBlockY() >= player.getLocation().getBlockY()) {
|
if (target.getLocation().getBlockY() >= botLoc.getBlockY()) {
|
||||||
Location loc = player.getLocation().add(0, -1, 0);
|
Location loc = botLoc.clone().add(0, -1, 0);
|
||||||
|
|
||||||
Set<Block> face = new HashSet<>(Arrays.asList(
|
Set<Block> face = new HashSet<>(Arrays.asList(
|
||||||
loc.clone().add(1, 0, 0).getBlock(),
|
loc.clone().add(1, 0, 0).getBlock(),
|
||||||
@@ -155,23 +155,25 @@ public class LegacyBlockCheck {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (at != null) {
|
if (at != null) {
|
||||||
|
agent.slow.add(bot);
|
||||||
//agent.slow.add(player);
|
|
||||||
agent.noFace.add(bot);
|
agent.noFace.add(bot);
|
||||||
|
|
||||||
Bukkit.getScheduler().runTaskLater(plugin, () -> {
|
Bukkit.getScheduler().runTaskLater(plugin, () -> {
|
||||||
bot.stand();
|
bot.stand();
|
||||||
//agent.slow.remove(player);
|
agent.slow.remove(bot);
|
||||||
}, 12);
|
}, 12);
|
||||||
|
|
||||||
Bukkit.getScheduler().runTaskLater(plugin, () -> {
|
Bukkit.getScheduler().runTaskLater(plugin, () -> {
|
||||||
agent.noFace.remove(bot);
|
agent.noFace.remove(bot);
|
||||||
}, 15);
|
}, 15);
|
||||||
|
|
||||||
|
Location faceLoc = at.clone().add(0, -1.5, 0);
|
||||||
|
|
||||||
|
bot.faceLocation(faceLoc);
|
||||||
bot.look(BlockFace.DOWN);
|
bot.look(BlockFace.DOWN);
|
||||||
|
|
||||||
Bukkit.getScheduler().runTaskLater(plugin, () -> {
|
Bukkit.getScheduler().runTaskLater(plugin, () -> {
|
||||||
bot.look(BlockFace.DOWN);
|
bot.faceLocation(faceLoc);
|
||||||
}, 1);
|
}, 1);
|
||||||
|
|
||||||
bot.punch();
|
bot.punch();
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ public class LegacyMats {
|
|||||||
));
|
));
|
||||||
|
|
||||||
public static final Set<Material> WATER = new HashSet<>(Arrays.asList(
|
public static final Set<Material> WATER = new HashSet<>(Arrays.asList(
|
||||||
|
Material.WATER,
|
||||||
Material.SEAGRASS,
|
Material.SEAGRASS,
|
||||||
Material.TALL_SEAGRASS,
|
Material.TALL_SEAGRASS,
|
||||||
Material.KELP,
|
Material.KELP,
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import com.jonahseguin.drink.Drink;
|
|||||||
import com.jonahseguin.drink.annotation.Command;
|
import com.jonahseguin.drink.annotation.Command;
|
||||||
import com.jonahseguin.drink.command.DrinkCommandService;
|
import com.jonahseguin.drink.command.DrinkCommandService;
|
||||||
import com.jonahseguin.drink.utils.ChatUtils;
|
import com.jonahseguin.drink.utils.ChatUtils;
|
||||||
import net.nuggetmc.ai.PlayerAI;
|
|
||||||
import net.nuggetmc.ai.command.commands.MainCommand;
|
import net.nuggetmc.ai.command.commands.MainCommand;
|
||||||
import org.bukkit.ChatColor;
|
import org.bukkit.ChatColor;
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@@ -20,7 +20,7 @@ public class CommandHandler {
|
|||||||
private final DrinkCommandService drink;
|
private final DrinkCommandService drink;
|
||||||
private final Map<Class<? extends CommandInstance>, List<String>> help;
|
private final Map<Class<? extends CommandInstance>, List<String>> help;
|
||||||
|
|
||||||
public CommandHandler(PlayerAI plugin) {
|
public CommandHandler(JavaPlugin plugin) {
|
||||||
drink = (DrinkCommandService) Drink.get(plugin);
|
drink = (DrinkCommandService) Drink.get(plugin);
|
||||||
drink.register(new MainCommand(this), "playerai.manage", "bot", "playerai", "pai", "ai", "npc");
|
drink.register(new MainCommand(this), "playerai.manage", "bot", "playerai", "pai", "ai", "npc");
|
||||||
drink.registerCommands();
|
drink.registerCommands();
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package net.nuggetmc.ai.command.commands;
|
package net.nuggetmc.ai.command.commands;
|
||||||
|
|
||||||
import com.jonahseguin.drink.annotation.*;
|
import com.jonahseguin.drink.annotation.Command;
|
||||||
|
import com.jonahseguin.drink.annotation.OptArg;
|
||||||
|
import com.jonahseguin.drink.annotation.Sender;
|
||||||
|
import com.jonahseguin.drink.annotation.Text;
|
||||||
import com.jonahseguin.drink.utils.ChatUtils;
|
import com.jonahseguin.drink.utils.ChatUtils;
|
||||||
import net.nuggetmc.ai.PlayerAI;
|
import net.nuggetmc.ai.PlayerAI;
|
||||||
import net.nuggetmc.ai.bot.BotManager;
|
import net.nuggetmc.ai.bot.BotManager;
|
||||||
@@ -16,14 +19,12 @@ import java.util.Locale;
|
|||||||
|
|
||||||
public class MainCommand extends CommandInstance {
|
public class MainCommand extends CommandInstance {
|
||||||
|
|
||||||
private PlayerAI plugin;
|
private final BotManager manager;
|
||||||
private BotManager manager;
|
|
||||||
|
|
||||||
public MainCommand(CommandHandler commandHandler) {
|
public MainCommand(CommandHandler commandHandler) {
|
||||||
super(commandHandler);
|
super(commandHandler);
|
||||||
|
|
||||||
this.plugin = PlayerAI.getInstance();
|
this.manager = PlayerAI.getInstance().getManager();
|
||||||
this.manager = plugin.getManager();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Command(
|
@Command(
|
||||||
@@ -31,7 +32,7 @@ public class MainCommand extends CommandInstance {
|
|||||||
)
|
)
|
||||||
public void root(@Sender Player sender) {
|
public void root(@Sender Player sender) {
|
||||||
sender.sendMessage(ChatUtils.LINE);
|
sender.sendMessage(ChatUtils.LINE);
|
||||||
sender.sendMessage(ChatColor.GOLD + "PlayerAI" + ChatColor.GRAY + " [" + ChatColor.RED + "v" + PlayerAI.VERSION + ChatColor.GRAY + "]");
|
sender.sendMessage(ChatColor.GOLD + "PlayerAI" + ChatColor.GRAY + " [" + ChatColor.RED + "v" + PlayerAI.getVersion() + ChatColor.GRAY + "]");
|
||||||
commandHandler.getHelp(getClass()).forEach(sender::sendMessage);
|
commandHandler.getHelp(getClass()).forEach(sender::sendMessage);
|
||||||
sender.sendMessage(ChatUtils.LINE);
|
sender.sendMessage(ChatUtils.LINE);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,25 @@
|
|||||||
package net.nuggetmc.ai.utils;
|
package net.nuggetmc.ai.utils;
|
||||||
|
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.Material;
|
||||||
import org.bukkit.block.Block;
|
import org.bukkit.block.Block;
|
||||||
import org.bukkit.util.BoundingBox;
|
import org.bukkit.util.BoundingBox;
|
||||||
import org.bukkit.util.Vector;
|
import org.bukkit.util.Vector;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public class BotUtils {
|
public class BotUtils {
|
||||||
|
|
||||||
|
public static final Set<Material> NO_FALL = new HashSet<>(Arrays.asList(
|
||||||
|
Material.WATER,
|
||||||
|
Material.LAVA,
|
||||||
|
Material.TWISTING_VINES,
|
||||||
|
Material.VINE
|
||||||
|
));
|
||||||
|
|
||||||
public static UUID randomSteveUUID() {
|
public static UUID randomSteveUUID() {
|
||||||
UUID uuid = UUID.randomUUID();
|
UUID uuid = UUID.randomUUID();
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package net.nuggetmc.ai.utils;
|
package net.nuggetmc.ai.utils;
|
||||||
|
|
||||||
|
import net.minecraft.server.v1_16_R3.*;
|
||||||
import net.nuggetmc.ai.PlayerAI;
|
import net.nuggetmc.ai.PlayerAI;
|
||||||
import net.nuggetmc.ai.bot.Bot;
|
import net.nuggetmc.ai.bot.Bot;
|
||||||
import net.nuggetmc.ai.bot.agent.Agent;
|
import net.nuggetmc.ai.bot.agent.Agent;
|
||||||
@@ -8,6 +9,7 @@ import org.bukkit.ChatColor;
|
|||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import org.bukkit.World;
|
import org.bukkit.World;
|
||||||
import org.bukkit.command.CommandSender;
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.craftbukkit.v1_16_R3.entity.CraftPlayer;
|
||||||
import org.bukkit.entity.ArmorStand;
|
import org.bukkit.entity.ArmorStand;
|
||||||
import org.bukkit.entity.EntityType;
|
import org.bukkit.entity.EntityType;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
@@ -23,7 +25,7 @@ public class Debugger {
|
|||||||
|
|
||||||
private static final String PREFIX = ChatColor.YELLOW + "[DEBUG] " + ChatColor.RESET;
|
private static final String PREFIX = ChatColor.YELLOW + "[DEBUG] " + ChatColor.RESET;
|
||||||
|
|
||||||
private CommandSender sender;
|
private final CommandSender sender;
|
||||||
|
|
||||||
public Debugger(CommandSender sender) {
|
public Debugger(CommandSender sender) {
|
||||||
this.sender = sender;
|
this.sender = sender;
|
||||||
@@ -35,7 +37,7 @@ public class Debugger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static String[] formStringArray(Object[] objects) {
|
private static String[] formStringArray(Object[] objects) {
|
||||||
return Arrays.stream(objects).map(Object::toString).toArray(String[]::new);
|
return Arrays.stream(objects).map(String::valueOf).toArray(String[]::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void print(Object... objects) {
|
private void print(Object... objects) {
|
||||||
@@ -75,6 +77,50 @@ public class Debugger {
|
|||||||
return list.toArray();
|
return list.toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void fire(String content) {
|
||||||
|
Object[] obj = buildObjects(content);
|
||||||
|
|
||||||
|
if (obj.length != 1) {
|
||||||
|
print("Invalid arguments!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean b = Boolean.parseBoolean((String) obj[0]);
|
||||||
|
|
||||||
|
PlayerAI.getInstance().getManager().fetch().forEach(bot -> bot.setOnFirePackets(b));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendPackets(String content) {
|
||||||
|
Object[] obj = buildObjects(content);
|
||||||
|
|
||||||
|
if (obj.length != 1) {
|
||||||
|
print("Invalid arguments!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte b;
|
||||||
|
|
||||||
|
try {
|
||||||
|
b = Byte.parseByte((String) obj[0]);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
print("Invalid arguments!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerAI.getInstance().getManager().fetch().forEach(bot -> {
|
||||||
|
/*DataWatcher datawatcher = bot.getDataWatcher();
|
||||||
|
datawatcher.set(DataWatcherRegistry.s.a(6), EntityPose.DYING);
|
||||||
|
PacketPlayOutEntityMetadata metadata = new PacketPlayOutEntityMetadata(bot.getId(), datawatcher, false);
|
||||||
|
Bukkit.getOnlinePlayers().forEach(p -> ((CraftPlayer) p).getHandle().playerConnection.sendPacket(metadata));
|
||||||
|
|
||||||
|
PacketPlayOutEntityStatus packet = new PacketPlayOutEntityStatus(bot, b);
|
||||||
|
Bukkit.getOnlinePlayers().forEach(p -> ((CraftPlayer) p).getHandle().playerConnection.sendPacket(packet));*/
|
||||||
|
bot.setHealth(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
Debugger.log("PACKETS_SENT");
|
||||||
|
}
|
||||||
|
|
||||||
public void trackYVel() {
|
public void trackYVel() {
|
||||||
if (!(sender instanceof Player)) return;
|
if (!(sender instanceof Player)) return;
|
||||||
|
|
||||||
@@ -92,7 +138,7 @@ public class Debugger {
|
|||||||
public void t(String content) {
|
public void t(String content) {
|
||||||
Object[] obj = buildObjects(content);
|
Object[] obj = buildObjects(content);
|
||||||
|
|
||||||
if (obj.length != 1 || obj[0] instanceof Boolean) {
|
if (obj.length != 1) {
|
||||||
print("Invalid arguments!");
|
print("Invalid arguments!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ public class PlayerUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isVulnerableGameMode(GameMode mode) {
|
public static boolean isVulnerableGameMode(GameMode mode) {
|
||||||
return mode == GameMode.SURVIVAL || mode == GameMode.ADVENTURE;
|
return mode == GameMode.SURVIVAL || mode == GameMode.ADVENTURE || mode == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setAllTargetable(boolean b) {
|
public static void setAllTargetable(boolean b) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
name: PlayerAI
|
name: PlayerAI
|
||||||
main: net.nuggetmc.ai.PlayerAI
|
main: net.nuggetmc.ai.PlayerAI
|
||||||
version: 1.0
|
version: 3.0-BETA
|
||||||
api-version: 1.16
|
api-version: 1.16
|
||||||
author: HorseNuggets
|
author: HorseNuggets
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user