diff --git a/src/main/java/com/artillexstudios/axminions/config/Skins.java b/src/main/java/com/artillexstudios/axminions/config/Skins.java index c1a2a5a..bae9f0a 100644 --- a/src/main/java/com/artillexstudios/axminions/config/Skins.java +++ b/src/main/java/com/artillexstudios/axminions/config/Skins.java @@ -42,7 +42,6 @@ public final class Skins { Skin skin = Skin.of(route, mapList); SkinRegistry.register(skin); } - // TODO: Refresh minion skins return true; } diff --git a/src/main/java/com/artillexstudios/axminions/database/DataHandler.java b/src/main/java/com/artillexstudios/axminions/database/DataHandler.java index 43bc7b9..068497f 100644 --- a/src/main/java/com/artillexstudios/axminions/database/DataHandler.java +++ b/src/main/java/com/artillexstudios/axminions/database/DataHandler.java @@ -136,12 +136,29 @@ public final class DataHandler { .select() .from(Tables.USERS) .where(Fields.UUID.eq(player.getUniqueId())) + .limit(1) .fetch(); if (!select.isEmpty()) { Record record = select.get(0); LogUtils.debug("User data select record: {}", record); - return new User(record.get(Fields.ID), player.getUniqueId(), player.getName(), texture, record.get(Fields.EXTRA_SLOTS, int.class), record.get(Fields.ISLAND_SLOTS, int.class)); + int ownerId = record.get(Fields.ID); + + Result> minionSelect = DatabaseConnector.getInstance().context() + .selectCount() + .from(Tables.MINIONS) + .where(Fields.OWNER_ID.eq(ownerId)) + .limit(1) + .fetch(); + + if (!minionSelect.isEmpty()) { + Record minionRecord = minionSelect.get(0); + int minionCount = minionRecord.get(0, int.class); + + return new User(ownerId, player.getUniqueId(), player.getName(), texture, minionCount, record.get(Fields.EXTRA_SLOTS, int.class), record.get(Fields.ISLAND_SLOTS, int.class), new ArrayList<>()); + } + + return new User(ownerId, player.getUniqueId(), player.getName(), texture, 0, record.get(Fields.EXTRA_SLOTS, int.class), record.get(Fields.ISLAND_SLOTS, int.class), new ArrayList<>()); } Record1 insert = DatabaseConnector.getInstance().context() @@ -157,12 +174,11 @@ public final class DataHandler { return null; } - return new User(insert.get(Fields.ID), player.getUniqueId(), player.getName(), texture, 0, 0); + return new User(insert.get(Fields.ID), player.getUniqueId(), player.getName(), texture, 0, 0, 0, new ArrayList<>()); }, AsyncUtils.executor()).exceptionallyAsync(throwable -> { log.error("An unexpected error occurred while updating user {}!", player.getName(), throwable); return null; }, AsyncUtils.executor()); - } public static int worldId(World world) { @@ -170,6 +186,7 @@ public final class DataHandler { .select() .from(Tables.WORLDS) .where(Fields.WORLD_UUID.eq(world.getUID())) + .limit(1) .fetch(); if (!select.isEmpty()) { @@ -199,6 +216,7 @@ public final class DataHandler { .and(Fields.LOCATION_X.eq(location.getBlockX())) .and(Fields.LOCATION_Y.eq(location.getBlockY())) .and(Fields.LOCATION_Z.eq(location.getBlockZ()))) + .limit(1) .fetch(); if (!select.isEmpty()) { @@ -228,6 +246,7 @@ public final class DataHandler { .select() .from(Tables.TYPES) .where(Fields.ID.eq((int) id)) + .limit(1) .fetchSingle(Fields.NAME); } @@ -277,6 +296,7 @@ public final class DataHandler { return IntLongPair.of(0, 0); } + List toLoad = new ArrayList<>(); Result locations = DatabaseConnector.getInstance().context() .select() .from(Tables.LOCATIONS) @@ -289,6 +309,7 @@ public final class DataHandler { .select() .from(Tables.MINIONS) .where(Fields.LOCATION_ID.eq(id)) + .limit(1) .fetch(); if (minions.isEmpty()) { @@ -321,10 +342,12 @@ public final class DataHandler { MinionData data = new MinionData(ownerId, type, Direction.entries[facing], null, minionLevel, charge, tool == null ? new ItemStack(Material.AIR) : WrappedItemStack.wrap(tool).toBukkit(), null, MinionData.deserialize(extraData)); Minion minion = new Minion(new Location(world, x + 0.5, y, z + 0.5), data); - MinionWorldCache.add(minion); + toLoad.add(minion); minion.spawn(); loadedMinions++; } + + MinionWorldCache.addAll(toLoad); long took = System.nanoTime() - start; return IntLongPair.of(loadedMinions, took); }, AsyncUtils.executor()).exceptionallyAsync(throwable -> { @@ -340,6 +363,7 @@ public final class DataHandler { .select() .from(Tables.TYPES) .where(Fields.NAME.eq(type.name())) + .limit(1) .fetch(); if (!select.isEmpty()) { @@ -395,7 +419,7 @@ public final class DataHandler { .set(Fields.LEVEL, minion.level()) .set(Fields.CHARGE, minion.charge()) .set(Fields.FACING, minion.facing().ordinal()) - .set(Fields.TOOL, WrappedItemStack.wrap(minion.tool()).serialize()) + .set(Fields.TOOL, minion.tool().getType().isAir() ? new byte[0] : WrappedItemStack.wrap(minion.tool()).serialize()) .set(Fields.EXTRA_DATA, MinionData.serialize(minion.extraData())) .where(Fields.LOCATION_ID.eq(locationId)); diff --git a/src/main/java/com/artillexstudios/axminions/listeners/MinionPlaceListener.java b/src/main/java/com/artillexstudios/axminions/listeners/MinionPlaceListener.java index 6ff84cb..0d9c2c4 100644 --- a/src/main/java/com/artillexstudios/axminions/listeners/MinionPlaceListener.java +++ b/src/main/java/com/artillexstudios/axminions/listeners/MinionPlaceListener.java @@ -82,7 +82,6 @@ public final class MinionPlaceListener implements Listener { } // TODO: level - itemStack.setAmount(itemStack.getAmount() - 1); Location location = LocationUtils.toBlockCenter(clickedBlock.getRelative(event.getBlockFace()).getLocation()); MinionArea area = MinionWorldCache.getArea(location.getWorld()); @@ -97,15 +96,19 @@ public final class MinionPlaceListener implements Listener { return; } + itemStack.setAmount(itemStack.getAmount() - 1); + LogUtils.debug("Minion count for user: " + user.minionCount()); // TODO: Database queries, etc.. MinionData data = new MinionData(user.id(), minionType, Direction.NORTH, null, minionType.level(1), 0, null, null, new HashMap<>()); data.extraData().put("owner_texture", NMSHandlers.getNmsHandler().textures(event.getPlayer()).getKey()); Minion minion = new Minion(location, data); MinionWorldCache.add(minion); + user.minionCount(user.minionCount() + 1); + DataHandler.insertMinion(minion).thenRun(() -> { LogUtils.debug("Inserted minion!"); + minion.spawn(); + area.startTicking(location.getChunk()); }); - minion.spawn(); - area.startTicking(location.getChunk()); } } diff --git a/src/main/java/com/artillexstudios/axminions/listeners/PlayerListener.java b/src/main/java/com/artillexstudios/axminions/listeners/PlayerListener.java index 09f2869..db2802c 100644 --- a/src/main/java/com/artillexstudios/axminions/listeners/PlayerListener.java +++ b/src/main/java/com/artillexstudios/axminions/listeners/PlayerListener.java @@ -1,10 +1,11 @@ package com.artillexstudios.axminions.listeners; import com.artillexstudios.axminions.database.DataHandler; -import com.artillexstudios.axminions.users.User; +import com.artillexstudios.axminions.minions.Minion; +import com.artillexstudios.axminions.minions.MinionWorldCache; import com.artillexstudios.axminions.users.Users; import com.artillexstudios.axminions.utils.LogUtils; -import org.bukkit.entity.Player; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerJoinEvent; @@ -14,9 +15,22 @@ public final class PlayerListener implements Listener { @EventHandler public void onPlayerJoinEvent(PlayerJoinEvent event) { DataHandler.loadUser(event.getPlayer()).thenAccept(user -> { - LogUtils.debug("Loaded user"); + if (user == null) { + LogUtils.warn("Failed to load user data for player {}!", event.getPlayer().getName()); + return; + } + + LogUtils.debug("Loaded user for player: {}", event.getPlayer().getName()); Users.load(user); - // TODO: Update all minions placed by the user + + ObjectArrayList copy = MinionWorldCache.copy(); + for (Minion minion : copy) { + if (minion.ownerId() == user.id()) { + user.minions().add(minion); + minion.extraData().put("owner_texture", user.texture()); + minion.skin(minion.skin()); + } + } }); } } diff --git a/src/main/java/com/artillexstudios/axminions/listeners/WorldListener.java b/src/main/java/com/artillexstudios/axminions/listeners/WorldListener.java index 0fb6f4e..aa1b718 100644 --- a/src/main/java/com/artillexstudios/axminions/listeners/WorldListener.java +++ b/src/main/java/com/artillexstudios/axminions/listeners/WorldListener.java @@ -1,6 +1,12 @@ package com.artillexstudios.axminions.listeners; +import com.artillexstudios.axapi.scheduler.Scheduler; +import com.artillexstudios.axminions.database.DataHandler; +import com.artillexstudios.axminions.minions.MinionArea; import com.artillexstudios.axminions.minions.MinionWorldCache; +import com.artillexstudios.axminions.utils.LogUtils; +import org.bukkit.Bukkit; +import org.bukkit.Chunk; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.world.WorldLoadEvent; @@ -10,7 +16,24 @@ public final class WorldListener implements Listener { @EventHandler public void onWorldLoadEvent(WorldLoadEvent event) { - MinionWorldCache.loadArea(event.getWorld()); + MinionArea area = MinionWorldCache.loadArea(event.getWorld()); + DataHandler.loadMinions(event.getWorld()).toCompletableFuture().thenAccept(loaded -> { + LogUtils.debug("Loaded {} minions in world {} in {} ms!", loaded.firstInt(), event.getWorld().getName(), loaded.secondLong() / 1_000_000); + + if (area == null) { + return; + } + + Scheduler.get().run(() -> { + if (Bukkit.getWorld(event.getWorld().getUID()) == null) { + return; + } + + for (Chunk chunk : event.getWorld().getLoadedChunks()) { + area.startTicking(chunk); + } + }); + }); } @EventHandler diff --git a/src/main/java/com/artillexstudios/axminions/minions/Minion.java b/src/main/java/com/artillexstudios/axminions/minions/Minion.java index 32976ee..e8f1282 100644 --- a/src/main/java/com/artillexstudios/axminions/minions/Minion.java +++ b/src/main/java/com/artillexstudios/axminions/minions/Minion.java @@ -149,6 +149,7 @@ public final class Minion { } public void destroy() { + this.ticking = false; this.entity.remove(); } diff --git a/src/main/java/com/artillexstudios/axminions/minions/MinionSaver.java b/src/main/java/com/artillexstudios/axminions/minions/MinionSaver.java index baf7607..51c436d 100644 --- a/src/main/java/com/artillexstudios/axminions/minions/MinionSaver.java +++ b/src/main/java/com/artillexstudios/axminions/minions/MinionSaver.java @@ -36,6 +36,11 @@ public final class MinionSaver { if (this.future != null && !this.future.isCancelled()) { this.future.cancel(false); this.future = null; + + ObjectArrayList copy = MinionWorldCache.copy(); + DataHandler.saveMinions(copy).toCompletableFuture().thenAccept(pair -> { + LogUtils.debug("Saved {} minions in {} ms!", pair.firstLong(), pair.secondLong() / 1_000_000L); + }).join(); } } } diff --git a/src/main/java/com/artillexstudios/axminions/minions/MinionWorldCache.java b/src/main/java/com/artillexstudios/axminions/minions/MinionWorldCache.java index f8c008f..440a31a 100644 --- a/src/main/java/com/artillexstudios/axminions/minions/MinionWorldCache.java +++ b/src/main/java/com/artillexstudios/axminions/minions/MinionWorldCache.java @@ -6,18 +6,21 @@ import org.bukkit.World; import java.util.Collection; import java.util.IdentityHashMap; +import java.util.List; public final class MinionWorldCache { private static final IdentityHashMap worlds = new IdentityHashMap<>(); private static final ObjectArrayList minions = new ObjectArrayList<>(); - public static void loadArea(World world) { + public static MinionArea loadArea(World world) { if (worlds.containsKey(world)) { LogUtils.warn("An area is already present for world {}", world.getName()); - return; + return null; } - worlds.put(world, new MinionArea()); + MinionArea area = new MinionArea(); + worlds.put(world, area); + return area; } public static void add(Minion minion) { @@ -31,6 +34,17 @@ public final class MinionWorldCache { area.load(minion); } + public static void addAll(List list) { + minions.addAll(list); + MinionArea area = worlds.get(list.get(0).location().getWorld()); + if (area == null) { + LogUtils.error("Tried to add minions to unknown world! {}", list); + return; + } + + area.loadAll(list); + } + public static void remove(Minion minion) { minions.remove(minion); MinionArea area = worlds.get(minion.location().getWorld()); @@ -60,6 +74,7 @@ public final class MinionWorldCache { area.forEachPos(position -> { for (Minion minion : position.minions()) { + minions.remove(minion); minion.destroy(); } }); diff --git a/src/main/java/com/artillexstudios/axminions/minions/actions/collectors/CollectorShape.java b/src/main/java/com/artillexstudios/axminions/minions/actions/collectors/CollectorShape.java index d66b9b7..bebc0f9 100644 --- a/src/main/java/com/artillexstudios/axminions/minions/actions/collectors/CollectorShape.java +++ b/src/main/java/com/artillexstudios/axminions/minions/actions/collectors/CollectorShape.java @@ -4,6 +4,7 @@ import com.artillexstudios.axminions.exception.MinionTickFailException; import com.artillexstudios.axminions.minions.actions.filters.Filter; import com.artillexstudios.axminions.utils.LogUtils; import org.bukkit.Location; +import org.bukkit.World; import org.bukkit.block.BlockFace; import org.bukkit.entity.Entity; @@ -18,6 +19,7 @@ public enum CollectorShape { final int blockX = location.getBlockX(); final int blockY = location.getBlockY(); final int blockZ = location.getBlockZ(); + final World world = location.getWorld(); final double rangeSquared = range * range; final double smallRangeSquared = ((range - 1) * (range - 1)); @@ -39,7 +41,7 @@ public enum CollectorShape { if (distance < rangeSquared && distance < smallRangeSquared) { try { - Location newLocation = new Location(location.getWorld(), x, y, z); + Location newLocation = new Location(world, x, y, z); for (Filter filter : filters) { if (!filter.isAllowed(newLocation)) { continue z; @@ -70,6 +72,7 @@ public enum CollectorShape { final int blockX = location.getBlockX(); final int blockY = location.getBlockY(); final int blockZ = location.getBlockZ(); + final World world = location.getWorld(); final double rangeSquared = range * range; final double smallRangeSquared = ((range - 1) * (range - 1)); @@ -87,7 +90,7 @@ public enum CollectorShape { if (distance < rangeSquared && distance < smallRangeSquared) { try { - Location newLocation = new Location(location.getWorld(), x, blockY, z); + Location newLocation = new Location(world, x, blockY, z); for (Filter filter : filters) { if (!filter.isAllowed(newLocation)) { continue z; @@ -117,6 +120,7 @@ public enum CollectorShape { final int blockX = location.getBlockX(); final int blockY = location.getBlockY(); final int blockZ = location.getBlockZ(); + final World world = location.getWorld(); final int xStart = (int) Math.round(blockX - range); final int xEnd = (int) Math.round(blockX + range); @@ -127,7 +131,7 @@ public enum CollectorShape { z: for (int z = zStart; z <= zEnd; z++) { try { - Location newLocation = new Location(location.getWorld(), x, blockY, z); + Location newLocation = new Location(world, x, blockY, z); for (Filter filter : filters) { if (!filter.isAllowed(newLocation)) { continue z; @@ -156,6 +160,7 @@ public enum CollectorShape { final int blockX = location.getBlockX(); final int blockY = location.getBlockY(); final int blockZ = location.getBlockZ(); + final World world = location.getWorld(); final int xStart = (int) Math.round(blockX - range); final int xEnd = (int) Math.round(blockX + range); @@ -169,7 +174,7 @@ public enum CollectorShape { z: for (int z = zStart; z <= zEnd; z++) { try { - Location newLocation = new Location(location.getWorld(), x, y, z); + Location newLocation = new Location(world, x, y, z); for (Filter filter : filters) { if (!filter.isAllowed(newLocation)) { continue z; diff --git a/src/main/java/com/artillexstudios/axminions/minions/actions/filters/Filter.java b/src/main/java/com/artillexstudios/axminions/minions/actions/filters/Filter.java index 70fc5e5..2371a6d 100644 --- a/src/main/java/com/artillexstudios/axminions/minions/actions/filters/Filter.java +++ b/src/main/java/com/artillexstudios/axminions/minions/actions/filters/Filter.java @@ -1,14 +1,14 @@ package com.artillexstudios.axminions.minions.actions.filters; import com.artillexstudios.axminions.exception.TransformerNotPresentException; +import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap; import java.util.Collection; import java.util.Collections; -import java.util.IdentityHashMap; import java.util.List; public abstract class Filter { - private final IdentityHashMap, Transformer> transformers = new IdentityHashMap<>(); + private final Object2ObjectArrayMap, Transformer> transformers = new Object2ObjectArrayMap<>(); private final Collection> unmodifiableTransformers = Collections.unmodifiableCollection(transformers.values()); public abstract boolean isAllowed(Object object); diff --git a/src/main/java/com/artillexstudios/axminions/minions/actions/filters/implementation/MaterialFilter.java b/src/main/java/com/artillexstudios/axminions/minions/actions/filters/implementation/MaterialFilter.java index 5b0f589..6f4174d 100644 --- a/src/main/java/com/artillexstudios/axminions/minions/actions/filters/implementation/MaterialFilter.java +++ b/src/main/java/com/artillexstudios/axminions/minions/actions/filters/implementation/MaterialFilter.java @@ -9,14 +9,16 @@ import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.block.Block; -import java.util.HashSet; +import java.util.Collections; +import java.util.EnumMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; public final class MaterialFilter extends Filter { - private final HashSet allowed = new HashSet<>(); + private final Set allowed = Collections.newSetFromMap(new EnumMap<>(Material.class)); public MaterialFilter(Map configuration) { this.addTransformer(Location.class, new Transformer() { diff --git a/src/main/java/com/artillexstudios/axminions/users/User.java b/src/main/java/com/artillexstudios/axminions/users/User.java index 3753b37..f76ad31 100644 --- a/src/main/java/com/artillexstudios/axminions/users/User.java +++ b/src/main/java/com/artillexstudios/axminions/users/User.java @@ -1,6 +1,13 @@ package com.artillexstudios.axminions.users; +import com.artillexstudios.axminions.minions.Minion; + +import java.util.List; import java.util.UUID; -public record User(int id, UUID uuid, String name, String texture, int extraSlots, int extraIslandSlots) { +public record User(int id, UUID uuid, String name, String texture, int minionCount, int extraSlots, int extraIslandSlots, List minions) { + + public void minionCount(int minionCount) { + Users.load(new User(this.id, this.uuid, this.name, this.texture, minionCount, this.extraSlots, this.extraIslandSlots, this.minions)); + } } diff --git a/src/main/java/com/artillexstudios/axminions/users/Users.java b/src/main/java/com/artillexstudios/axminions/users/Users.java index 5e5ce7f..f95cbb5 100644 --- a/src/main/java/com/artillexstudios/axminions/users/Users.java +++ b/src/main/java/com/artillexstudios/axminions/users/Users.java @@ -5,7 +5,7 @@ import org.bukkit.entity.Player; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; -public class Users { +public final class Users { private static final ConcurrentHashMap USER_CACHE = new ConcurrentHashMap<>(100); public static void load(User user) { diff --git a/src/main/java/com/artillexstudios/axminions/utils/ChunkPos.java b/src/main/java/com/artillexstudios/axminions/utils/ChunkPos.java index 82b0291..186a571 100644 --- a/src/main/java/com/artillexstudios/axminions/utils/ChunkPos.java +++ b/src/main/java/com/artillexstudios/axminions/utils/ChunkPos.java @@ -20,6 +20,8 @@ public record ChunkPos(int x, int z, AtomicBoolean ticking, ObjectArrayList