From 8e7774897d11623260f24d88ef0b059cde8bbff5 Mon Sep 17 00:00:00 2001 From: Johny Muffin Date: Tue, 2 Jan 2024 00:15:01 +1000 Subject: [PATCH 1/3] Factions Import Code Code currently hasn't been tested --- pom.xml | 8 +- .../johnymuffin/jvillage/beta/JVUtility.java | 11 + .../johnymuffin/jvillage/beta/JVillage.java | 217 ++++++++++++++++-- .../beta/commands/JVilageAdminCMD.java | 10 +- .../beta/config/JVillageLanguage.java | 4 + .../beta/config/JVillageSettings.java | 6 +- src/main/resources/plugin.yml | 5 + 7 files changed, 241 insertions(+), 20 deletions(-) diff --git a/pom.xml b/pom.xml index 130f9df..880d5a7 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ groupId JVillage - 1.0.12-SNAPSHOT + 1.0.13-SNAPSHOT 8 @@ -105,6 +105,12 @@ 3.1.0 + + com.massivecraft + factions + 1.5.1 + + \ No newline at end of file diff --git a/src/main/java/com/johnymuffin/jvillage/beta/JVUtility.java b/src/main/java/com/johnymuffin/jvillage/beta/JVUtility.java index df713a9..a663304 100644 --- a/src/main/java/com/johnymuffin/jvillage/beta/JVUtility.java +++ b/src/main/java/com/johnymuffin/jvillage/beta/JVUtility.java @@ -3,6 +3,7 @@ import com.johnymuffin.jvillage.beta.models.VCords; import com.johnymuffin.jvillage.beta.models.Village; import com.johnymuffin.jvillage.beta.models.chunk.VChunk; +import com.projectposeidon.api.PoseidonUUID; import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; @@ -94,5 +95,15 @@ public static String formatUsernames(JVillage plugin, UUID[] players) { return stringBuilder.toString(); } + public static UUID getUUIDFromPoseidonCache(String username) { + UUID uuid = PoseidonUUID.getPlayerUUIDFromCache(username, true); + + if(uuid == null) { + uuid = PoseidonUUID.getPlayerUUIDFromCache(username, false); + } + + return uuid; + } + } diff --git a/src/main/java/com/johnymuffin/jvillage/beta/JVillage.java b/src/main/java/com/johnymuffin/jvillage/beta/JVillage.java index 5b97713..d9d7e53 100644 --- a/src/main/java/com/johnymuffin/jvillage/beta/JVillage.java +++ b/src/main/java/com/johnymuffin/jvillage/beta/JVillage.java @@ -25,12 +25,18 @@ import com.johnymuffin.jvillage.beta.routes.api.v1.JVillageGetVillageRoute; import com.johnymuffin.jvillage.beta.tasks.AutoClaimingTask; import com.legacyminecraft.poseidon.event.PoseidonCustomListener; +import com.massivecraft.factions.FLocation; +import com.massivecraft.factions.FPlayer; +import com.massivecraft.factions.Faction; +import com.massivecraft.factions.Factions; +import com.massivecraft.factions.struct.Role; import com.palmergames.bukkit.towny.Towny; import com.palmergames.bukkit.towny.object.Resident; import com.palmergames.bukkit.towny.object.Town; import com.palmergames.bukkit.towny.object.TownBlock; import com.palmergames.bukkit.towny.object.TownyWorld; import com.projectposeidon.api.PoseidonUUID; +import com.projectposeidon.johnymuffin.UUIDManager; import com.sk89q.worldguard.bukkit.WorldGuardPlugin; import com.sk89q.worldguard.protection.ApplicableRegionSet; import com.sk89q.worldguard.protection.managers.RegionManager; @@ -44,13 +50,13 @@ import org.bukkit.plugin.java.JavaPlugin; import java.io.File; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.UUID; +import java.lang.reflect.Field; +import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; +import static com.johnymuffin.jvillage.beta.JVUtility.getUUIDFromPoseidonCache; + public class JVillage extends JavaPlugin implements ClaimManager, PoseidonCustomListener { //Basic Plugin Info private static JVillage plugin; @@ -472,6 +478,15 @@ public boolean townyImport() { return false; } + UUID placeholderUUID = UUID.fromString(settings.getConfigString("settings.import-placeholder-account.uuid")); + String placeholderUsername = settings.getConfigString("settings.import-placeholder-account.name"); + //Add UUID to Poseidon UUIDManager + try { + UUIDManager.getInstance().receivedUUID(placeholderUsername, placeholderUUID, (System.currentTimeMillis() / 1000L), true); + } catch (Exception exception) { + logger(Level.WARNING, "Could not add placeholder UUID to Poseidon UUIDCache. This could cause Unknown User to be shown for some imported Villages."); + } + Towny towny = (Towny) getServer().getPluginManager().getPlugin("Towny"); Fundamentals fundamentals = (Fundamentals) getServer().getPluginManager().getPlugin("Fundamentals"); @@ -527,10 +542,9 @@ public boolean townyImport() { townOwnerUUID = PoseidonUUID.getPlayerUUIDFromCache(townOwnerUsername, false); } if (townOwnerUUID == null) { -// log.warning("[" + pluginName + "] Could not find UUID for town owner: " + townOwnerUsername + ". The town will not be imported."); -// townsSkipped++; - townOwnerUUID = UUID.fromString("ca5c33ce-5825-45e3-ab92-0127c05c2016"); //UUID for jetpackingwolf - continue; + log.warning("[" + pluginName + "] Could not find UUID for town owner: " + townOwnerUsername + ". The town will not be imported."); + townsSkipped++; + townOwnerUUID = placeholderUUID; //UUID for jetpackingwolf } //Import Assistants @@ -538,7 +552,7 @@ public boolean townyImport() { String assistantUsername = resident.getName(); UUID assistantUUID = fundamentals.getPlayerCache().getUUIDFromUsername(assistantUsername); if (assistantUUID == null) { -// log.warning("[" + pluginName + "] Could not find UUID for town assistant: " + assistantUsername + ". The assistant will not be imported for town " + newTownName + "."); + log.warning("[" + pluginName + "] Could not find UUID for town assistant: " + assistantUsername + ". The assistant will not be imported for town " + newTownName + "."); continue; } assistants.add(assistantUUID); @@ -549,7 +563,7 @@ public boolean townyImport() { String residentUsername = resident.getName(); UUID residentUUID = fundamentals.getPlayerCache().getUUIDFromUsername(residentUsername); if (residentUUID == null) { -// log.warning("[" + pluginName + "] Could not find UUID for town resident: " + residentUsername + ". The resident will not be imported for town " + newTownName + "."); + log.warning("[" + pluginName + "] Could not find UUID for town resident: " + residentUsername + ". The resident will not be imported for town " + newTownName + "."); residentsSkipped++; continue; } @@ -589,10 +603,7 @@ public boolean townyImport() { try { townSpawn = new VCords(town.getSpawn().getBlockX(), town.getSpawn().getBlockY(), town.getSpawn().getBlockZ(), town.getSpawn().getWorld().getName()); } catch (Exception exception) { -// exception.printStackTrace(); -// log.warning("[" + pluginName + "] Could not find town spawn for town " + newTownName + ". The town will not be imported."); -// townsSkipped++; -// continue; + log.warning("[" + pluginName + "] Could not find town spawn for town " + newTownName + ". World spawn will be used instead."); logger(Level.WARNING, "Could not find town spawn for town " + newTownName + ". The town spawn will be set to the world spawn."); townSpawn = new VCords(Bukkit.getWorlds().get(0).getSpawnLocation().getBlockX(), Bukkit.getWorlds().get(0).getSpawnLocation().getBlockY(), Bukkit.getWorlds().get(0).getSpawnLocation().getBlockZ(), Bukkit.getWorlds().get(0).getName()); } @@ -618,8 +629,6 @@ public boolean townyImport() { for (UUID residentUUID : residents) { village.addMember(residentUUID); } - //Register claims - logger(Level.INFO, "Towny Import: Imported town " + newTownName + " with " + townClaims.size() + " claims. Owner Name: " + townOwnerUsername + " Assistants: " + assistants.size() + " Residents: " + residents.size()); } @@ -657,6 +666,182 @@ public boolean townyImport() { } + public boolean factionsImport() { + logger(Level.INFO, "Factions Import: Starting import of Factions data."); + + UUID placeholderUUID = UUID.fromString(settings.getConfigString("settings.import-placeholder-account.uuid")); + String placeholderUsername = settings.getConfigString("settings.import-placeholder-account.name"); + //Add UUID to Poseidon UUIDManager + try { + UUIDManager.getInstance().receivedUUID(placeholderUsername, placeholderUUID, (System.currentTimeMillis() / 1000L), true); + } catch (Exception exception) { + logger(Level.WARNING, "Could not add placeholder UUID to Poseidon UUIDCache. This could cause Unknown User to be shown for some imported Villages."); + } + + if (Bukkit.getServer().getPluginManager().getPlugin("Factions") == null) { + log.log(Level.WARNING, "[" + pluginName + "] Factions not found, cancelling towny import."); + return false; + } + + Factions factions = (Factions) Bukkit.getServer().getPluginManager().getPlugin("Factions"); + + int factionsImported = 0; + int factionsSkipped = 0; + int membersImported = 0; + int membersSkipped = 0; + int claimsImported = 0; + + + Set factionTags = factions.getFactionTags(); + + for (String tag : factionTags) { + Faction faction = Faction.findByTag(tag); + String originalFactionName = faction.getTag(); + String newVillageName = originalFactionName; + + int i = 0; + while (!this.villageNameAvailable(newVillageName)) { + i++; + newVillageName = originalFactionName + "-" + i; + } + + //Print out the new name if it was changed + if (!originalFactionName.equals(newVillageName)) { + logger(Level.INFO, "Faction Import: Village " + originalFactionName + " already exists. Changing name to " + newVillageName); + } + + //Get Village UUID + UUID factionUUID = UUID.randomUUID(); + + + UUID villageOwnerUUID = null; + ArrayList assistants = new ArrayList<>(); + ArrayList members = new ArrayList<>(); + + Set factionMembers = factions.getPlayersInFaction(tag); + + + String ownerUsername = null; + ArrayList assistantUsernames = new ArrayList<>(); + ArrayList memberUsernames = new ArrayList<>(); + + //Find owner + for (String member : factionMembers) { + FPlayer fPlayer = FPlayer.find(member); + if (fPlayer.getRole() == Role.ADMIN) { + ownerUsername = fPlayer.getName(); + } else if (fPlayer.getRole() == Role.MODERATOR) { + assistantUsernames.add(fPlayer.getName()); + } else { + memberUsernames.add(fPlayer.getName()); + } + } + + //Skip if owner is null + if (ownerUsername == null) { + log.warning("[" + pluginName + "] Could not find owner for faction " + newVillageName + ". The faction will not be imported."); + factionsSkipped++; + continue; + } + + villageOwnerUUID = getUUIDFromPoseidonCache(ownerUsername); + + if (villageOwnerUUID == null) { + log.warning("[" + pluginName + "] Could not find UUID for faction (" + newVillageName + ") owner: " + ownerUsername + ". Ownership will be given to " + placeholderUsername + " (" + placeholderUUID + ")."); + villageOwnerUUID = placeholderUUID; + } + + //Import Assistants + for (String assistantUsername : assistantUsernames) { + UUID assistantUUID = getUUIDFromPoseidonCache(assistantUsername); + if (assistantUUID == null) { + log.warning("[" + pluginName + "] Could not find UUID for faction (" + newVillageName + ") assistant: " + assistantUsername + ". The assistant will not be imported."); + continue; + } + assistants.add(assistantUUID); + } + + //Import Members + for (String memberUsername : memberUsernames) { + UUID memberUUID = getUUIDFromPoseidonCache(memberUsername); + if (memberUUID == null) { + log.warning("[" + pluginName + "] Could not find UUID for faction (" + newVillageName + ") member: " + memberUsername + ". The member will not be imported."); + continue; + } + members.add(memberUUID); + membersImported++; + } + + + //Import Claims + + //Use reflection to get claimOwnership field from Faction + Map> claimOwnership; + Field claimOwnershipField = null; + try { + claimOwnershipField = faction.getClass().getDeclaredField("claimOwnership"); + claimOwnershipField.setAccessible(true); + claimOwnership = (Map>) claimOwnershipField.get(faction); + } catch (Exception e) { + log.severe("[" + pluginName + "] Could not get claimOwnership field from Faction class. Factions import will not continue. Please note this import function is only compatible with Java 8 due to the use of reflection."); + e.printStackTrace(); + return false; + } + + if (claimOwnership == null) { + log.warning("[" + pluginName + "] claimOwnership variable is null. Factions import will not continue."); + return false; + } + + ArrayList villageClaims = new ArrayList<>(); + + for (Map.Entry> entry : claimOwnership.entrySet()) { + FLocation fLocation = entry.getKey(); + VChunk vChunk = new VChunk(fLocation.getWorldName(), (int) fLocation.getX(), (int) fLocation.getZ()); + villageClaims.add(vChunk); + claimsImported++; + } + + //Skip if no claims + if (villageClaims.size() == 0) { + log.warning("[" + pluginName + "] Faction " + newVillageName + " has no claims. The faction will not be imported."); + factionsSkipped++; + continue; + } + + + //Faction Spawn + Location factionSpawn = faction.getHome(); + + VChunk spawnChunk = new VChunk(factionSpawn); + + + //Create Village + Village village = new Village(plugin, newVillageName, factionUUID, villageOwnerUUID, spawnChunk, new VCords(factionSpawn)); + factionsImported++; + this.villageMap.addVillageToMap(village); + //Register claims + for (VChunk vChunk : villageClaims) { + village.addClaim(new VClaim(village, vChunk)); + } + //Register assistants + for (UUID assistantUUID : assistants) { + village.addAssistant(assistantUUID); + } + //Register members + for (UUID memberUUID : members) { + village.addMember(memberUUID); + } + + logger(Level.INFO, "Faction Import: Imported faction " + newVillageName + " with " + villageClaims.size() + " claims, " + assistants.size() + " assistants, and " + members.size() + " members."); + } + + logger(Level.INFO, "Faction Import: Imported " + factionsImported + " factions with " + claimsImported + " claims and " + membersImported + " members. Skipped " + factionsSkipped + " factions."); + + + return true; + } + // private WorldClaimManager getWorldClaimManager(String world, boolean generate) { // world = world.toLowerCase(); diff --git a/src/main/java/com/johnymuffin/jvillage/beta/commands/JVilageAdminCMD.java b/src/main/java/com/johnymuffin/jvillage/beta/commands/JVilageAdminCMD.java index 972bbb0..3c8f449 100644 --- a/src/main/java/com/johnymuffin/jvillage/beta/commands/JVilageAdminCMD.java +++ b/src/main/java/com/johnymuffin/jvillage/beta/commands/JVilageAdminCMD.java @@ -476,9 +476,15 @@ private boolean pluginImportFactionsCommand(CommandSender commandSender, String[ return true; } - //TODO: Implement this command - commandSender.sendMessage(language.getMessage("generic_not_implemented")); + commandSender.sendMessage(language.getMessage("command_villageadmin_plugin_import_factions_start")); + if (plugin.factionsImport()) { + commandSender.sendMessage(language.getMessage("command_villageadmin_plugin_import_factions_success")); + } else { + commandSender.sendMessage(language.getMessage("command_villageadmin_plugin_import_factions_fail")); + } + return true; + } } diff --git a/src/main/java/com/johnymuffin/jvillage/beta/config/JVillageLanguage.java b/src/main/java/com/johnymuffin/jvillage/beta/config/JVillageLanguage.java index 1aae19e..c5f0df4 100644 --- a/src/main/java/com/johnymuffin/jvillage/beta/config/JVillageLanguage.java +++ b/src/main/java/com/johnymuffin/jvillage/beta/config/JVillageLanguage.java @@ -59,6 +59,10 @@ private void loadDefaults() { map.put("command_villageadmin_plugin_import_towny_success", "&bImporting Towny data completed successfully. The debug is available in the console."); map.put("command_villageadmin_plugin_import_towny_fail", "&bImporting Towny data failed. The debug is available in the console."); + map.put("command_villageadmin_plugin_import_factions_start", "&bImporting Factions data. The server might freeze while this is happening."); + map.put("command_villageadmin_plugin_import_factions_success", "&bImporting Factions data completed successfully. The debug is available in the console."); + map.put("command_villageadmin_plugin_import_factions_fail", "&bImporting Factions data failed. The debug is available in the console."); + map.put("command_villageadmin_plugin_debug_change", "&bDebug mode has been changed to &9%state%"); map.put("command_villageadmin_village_use", "&cSorry, that is invalid. Try /villageadmin village (add|kick|setowner|delete|unclaim)"); diff --git a/src/main/java/com/johnymuffin/jvillage/beta/config/JVillageSettings.java b/src/main/java/com/johnymuffin/jvillage/beta/config/JVillageSettings.java index 6509ddd..4c4a716 100644 --- a/src/main/java/com/johnymuffin/jvillage/beta/config/JVillageSettings.java +++ b/src/main/java/com/johnymuffin/jvillage/beta/config/JVillageSettings.java @@ -1,6 +1,5 @@ package com.johnymuffin.jvillage.beta.config; -import com.johnymuffin.jvillage.beta.JVillage; import org.bukkit.util.config.Configuration; import java.io.File; @@ -62,6 +61,11 @@ private void write() { generateConfigOption("settings.debug-mode.info", "If true, the plugin will output debug messages to the console."); generateConfigOption("settings.debug-mode.enabled", false); + generateConfigOption("settings.import-placeholder-account.uuid", "f84c6a79-0a4e-45e0-879b-cd49ebd4c4e2"); + generateConfigOption("settings.import-placeholder-account.name", "Herobrine"); + generateConfigOption("settings.import-placeholder-account.info", "This is the account that will be given ownership of Villages imported from Towny and Factions that do not have a valid owner. This should be a VALID Mojang account."); + + getWorldGuardPermissions(); //This is a hack to get the default value to be added to the config file. } diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 70051ba..9d936aa 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -41,9 +41,14 @@ permissions: jvillage.admin.plugin.reload: true jvillage.admin.plugin.import: true jvillage.admin.plugin.import.towny: true + jvillage.admin.plugin.import.factions: true jvillage.admin.world: true jvillage.admin.world.wgcleanup: true + jvillage.admin.plugin.import.factions: + description: Allows access to JVillage admin plugin import factions data + default: op + jvillage.admin.world: description: Allows access to all JVillage world admin commands default: op From 6cbd355309647b262f4e19b57111764566ea45ee Mon Sep 17 00:00:00 2001 From: Johny Muffin Date: Tue, 2 Jan 2024 02:06:13 +1000 Subject: [PATCH 2/3] Remove requirement for Fundamentals to be installed Currently JVillage only supports Fundamentals economy. If it isn't installed, the economy is disabled. --- .../johnymuffin/jvillage/beta/JVUtility.java | 98 ++ .../johnymuffin/jvillage/beta/JVillage.java | 143 +-- .../beta/commands/JVilageAdminCMD.java | 6 +- .../beta/commands/VResidentCommand.java | 2 +- .../beta/commands/village/JCreateCommand.java | 2 +- .../beta/commands/village/JDeleteCommand.java | 2 +- .../beta/commands/village/JDemoteCommand.java | 2 +- .../commands/village/JDepositCommand.java | 5 + .../beta/commands/village/JKickCommand.java | 2 +- .../commands/village/JPromoteCommand.java | 2 +- .../commands/village/JSetOwnerCommand.java | 2 +- .../beta/commands/village/JSpawnCommand.java | 2 +- .../commands/village/JWithdrawCommand.java | 5 + .../jvillage/beta/config/JPlayerData.java | 109 +++ .../beta/config/JVillageLanguage.java | 4 +- .../beta/listeners/JVPlayerMoveListener.java | 11 + .../jvillage/beta/maps/JPlayerMap.java | 13 +- .../jvillage/beta/player/VPlayer.java | 24 +- .../routes/api/v1/JVillageGetPlayerRoute.java | 2 +- .../jvillage/beta/tasks/AutomaticSaving.java | 22 + .../jvillage/beta/tasks/Metrics.java | 905 ++++++++++++++++++ 21 files changed, 1275 insertions(+), 88 deletions(-) create mode 100644 src/main/java/com/johnymuffin/jvillage/beta/config/JPlayerData.java create mode 100644 src/main/java/com/johnymuffin/jvillage/beta/tasks/AutomaticSaving.java create mode 100644 src/main/java/com/johnymuffin/jvillage/beta/tasks/Metrics.java diff --git a/src/main/java/com/johnymuffin/jvillage/beta/JVUtility.java b/src/main/java/com/johnymuffin/jvillage/beta/JVUtility.java index a663304..979124e 100644 --- a/src/main/java/com/johnymuffin/jvillage/beta/JVUtility.java +++ b/src/main/java/com/johnymuffin/jvillage/beta/JVUtility.java @@ -5,11 +5,17 @@ import com.johnymuffin.jvillage.beta.models.chunk.VChunk; import com.projectposeidon.api.PoseidonUUID; import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.block.Block; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import java.math.BigDecimal; import java.math.RoundingMode; +import java.util.HashSet; +import java.util.Set; import java.util.UUID; public class JVUtility { @@ -105,5 +111,97 @@ public static UUID getUUIDFromPoseidonCache(String username) { return uuid; } + //Essentials Code Start: com.earth2me.essentials.Util + private static final Set AIR_MATERIALS = new HashSet(); + + static { + AIR_MATERIALS.add(Material.AIR.getId()); + AIR_MATERIALS.add(Material.SAPLING.getId()); + AIR_MATERIALS.add(Material.POWERED_RAIL.getId()); + AIR_MATERIALS.add(Material.DETECTOR_RAIL.getId()); + AIR_MATERIALS.add(Material.DEAD_BUSH.getId()); + AIR_MATERIALS.add(Material.RAILS.getId()); + AIR_MATERIALS.add(Material.YELLOW_FLOWER.getId()); + AIR_MATERIALS.add(Material.RED_ROSE.getId()); + AIR_MATERIALS.add(Material.RED_MUSHROOM.getId()); + AIR_MATERIALS.add(Material.BROWN_MUSHROOM.getId()); + AIR_MATERIALS.add(Material.SEEDS.getId()); + AIR_MATERIALS.add(Material.SIGN_POST.getId()); + AIR_MATERIALS.add(Material.WALL_SIGN.getId()); + AIR_MATERIALS.add(Material.LADDER.getId()); + AIR_MATERIALS.add(Material.SUGAR_CANE_BLOCK.getId()); + AIR_MATERIALS.add(Material.REDSTONE_WIRE.getId()); + AIR_MATERIALS.add(Material.REDSTONE_TORCH_OFF.getId()); + AIR_MATERIALS.add(Material.REDSTONE_TORCH_ON.getId()); + AIR_MATERIALS.add(Material.TORCH.getId()); + AIR_MATERIALS.add(Material.SOIL.getId()); + AIR_MATERIALS.add(Material.DIODE_BLOCK_OFF.getId()); + AIR_MATERIALS.add(Material.DIODE_BLOCK_ON.getId()); + AIR_MATERIALS.add(Material.TRAP_DOOR.getId()); + AIR_MATERIALS.add(Material.STONE_BUTTON.getId()); + AIR_MATERIALS.add(Material.STONE_PLATE.getId()); + AIR_MATERIALS.add(Material.WOOD_PLATE.getId()); + AIR_MATERIALS.add(Material.IRON_DOOR_BLOCK.getId()); + AIR_MATERIALS.add(Material.WOODEN_DOOR.getId()); + } + + public static Location getSafeDestination(final Location loc) throws Exception { + if (loc == null || loc.getWorld() == null) { + throw new Exception("Invalid Location Object"); + } + final World world = loc.getWorld(); + int x = (int) Math.round(loc.getX()); + int y = (int) Math.round(loc.getY()); + int z = (int) Math.round(loc.getZ()); + + while (isBlockAboveAir(world, x, y, z)) { + y -= 1; + if (y < 0) { + break; + } + } + + while (isBlockUnsafe(world, x, y, z)) { + y += 1; + if (y >= 127) { + x += 1; + break; + } + } + while (isBlockUnsafe(world, x, y, z)) { + y -= 1; + if (y <= 1) { + y = 127; + x += 1; + if (x - 32 > loc.getBlockX()) { + throw new Exception("Sorry, there is a hole in the floor"); + } + } + } + return new Location(world, x + 0.5D, y, z + 0.5D, loc.getYaw(), loc.getPitch()); + } + + private static boolean isBlockAboveAir(final World world, final int x, final int y, final int z) { + return AIR_MATERIALS.contains(world.getBlockAt(x, y - 1, z).getType().getId()); + } + + private static boolean isBlockUnsafe(final World world, final int x, final int y, final int z) { + final Block below = world.getBlockAt(x, y - 1, z); + if (below.getType() == Material.LAVA || below.getType() == Material.STATIONARY_LAVA) { + return true; + } + + if (below.getType() == Material.FIRE) { + return true; + } + + if ((!AIR_MATERIALS.contains(world.getBlockAt(x, y, z).getType().getId())) + || (!AIR_MATERIALS.contains(world.getBlockAt(x, y + 1, z).getType().getId()))) { + return true; + } + return isBlockAboveAir(world, x, y, z); + } + //Essentials Code End + } diff --git a/src/main/java/com/johnymuffin/jvillage/beta/JVillage.java b/src/main/java/com/johnymuffin/jvillage/beta/JVillage.java index d9d7e53..6ebb78f 100644 --- a/src/main/java/com/johnymuffin/jvillage/beta/JVillage.java +++ b/src/main/java/com/johnymuffin/jvillage/beta/JVillage.java @@ -1,12 +1,12 @@ package com.johnymuffin.jvillage.beta; import com.johnymuffin.beta.fundamentals.Fundamentals; -import com.johnymuffin.beta.fundamentals.player.FundamentalsPlayer; import com.johnymuffin.beta.webapi.JWebAPI; import com.johnymuffin.beta.webapi.event.JWebAPIDisable; import com.johnymuffin.jvillage.beta.commands.JVilageAdminCMD; import com.johnymuffin.jvillage.beta.commands.JVillageCMD; import com.johnymuffin.jvillage.beta.commands.VResidentCommand; +import com.johnymuffin.jvillage.beta.config.JPlayerData; import com.johnymuffin.jvillage.beta.config.JVillageLanguage; import com.johnymuffin.jvillage.beta.config.JVillageSettings; import com.johnymuffin.jvillage.beta.interfaces.ClaimManager; @@ -24,6 +24,8 @@ import com.johnymuffin.jvillage.beta.routes.api.v1.JVillageGetVillageList; import com.johnymuffin.jvillage.beta.routes.api.v1.JVillageGetVillageRoute; import com.johnymuffin.jvillage.beta.tasks.AutoClaimingTask; +import com.johnymuffin.jvillage.beta.tasks.AutomaticSaving; +import com.johnymuffin.jvillage.beta.tasks.Metrics; import com.legacyminecraft.poseidon.event.PoseidonCustomListener; import com.massivecraft.factions.FLocation; import com.massivecraft.factions.FPlayer; @@ -49,6 +51,7 @@ import org.bukkit.plugin.PluginDescriptionFile; import org.bukkit.plugin.java.JavaPlugin; +import javax.annotation.Nullable; import java.io.File; import java.lang.reflect.Field; import java.util.*; @@ -71,6 +74,8 @@ public class JVillage extends JavaPlugin implements ClaimManager, PoseidonCustom private JVillageLanguage language; private JVillageSettings settings; + private JPlayerData playerData; + private boolean errored = false; private JVillageMap villageMap; @@ -78,6 +83,10 @@ public class JVillage extends JavaPlugin implements ClaimManager, PoseidonCustom private boolean apiEnabled = false; + private Metrics metrics; + + private boolean fundamentalsEnabled = false; + @Override public void onEnable() { plugin = this; @@ -88,10 +97,11 @@ public void onEnable() { //Check for Fundamentals if (Bukkit.getPluginManager().getPlugin("Fundamentals") == null) { - log.severe("[" + pluginName + "] Fundamentals is not installed, disabling plugin"); - errored = true; - this.getServer().getPluginManager().disablePlugin(this); - return; + fundamentalsEnabled = false; + log.warning("[" + pluginName + "] Fundamentals is not installed or not enabled, economy features will be disabled"); + } else { + log.info("[" + pluginName + "] Fundamentals is installed and enabled, economy features will be enabled"); + fundamentalsEnabled = true; } @@ -139,17 +149,24 @@ public void onEnable() { // // logger(Level.INFO, "Checked " + claimsLoaded + " claims in " + (endTime - startTime) + "ms. Found " + duplicateClaims + " duplicate claims."); + this.playerData = new JPlayerData(this); // Load player data from file int playersLoaded = 0; //Load players playerMap = new JPlayerMap(this); - //Load Fundamentals players - for (UUID uuid : getFundamentals().getPlayerMap().getKnownPlayers()) { + //TODO: This really isn't needed anymore, but I'm keeping it here for now. Lazy loading is a thing now. + for(UUID uuid : playerData.getAllPlayers()) { playerMap.getPlayer(uuid); playersLoaded++; } - logger(Level.INFO, "Loaded " + playersLoaded + " players from Fundamentals."); + //Load Fundamentals players + //for (UUID uuid : getFundamentals().getPlayerMap().getKnownPlayers()) { + // playerMap.getPlayer(uuid); + // playersLoaded++; + //} + + logger(Level.INFO, "Loaded " + playersLoaded + " players from player data file."); //Register commands this.getCommand("villageadmin").setExecutor(new JVilageAdminCMD(this)); @@ -166,38 +183,6 @@ public void onEnable() { JVMobListener mobListener = new JVMobListener(this); Bukkit.getPluginManager().registerEvents(mobListener, plugin); - //Scheduled Tasks - - //Save all villages every 5 minutes -// Bukkit.getScheduler().scheduleSyncRepeatingTask(this, new Runnable() { -// @Override -// public void run() { -// plugin.logger(Level.INFO, "Saving all villages"); -// villageMap.saveData(); -// } -// }, 20 * 60 * 5, 20 * 60 * 5); -// -// try { -// exportOverviewerRegions(); -// } catch (IOException e) { -// e.printStackTrace(); -// } - -// ArrayList claims = this.getVillageMap().getVillage("Kekistan").getClaims(); -// RegionGenerator regionGenerator = new RegionGenerator(this); -// regionGenerator.generateMapRegionsForClaims(Bukkit.getWorlds().get(0), claims); - -// OverviewerExporter exporter = new OverviewerExporter(this); -// String regions = exporter.generateOverviewerRegions(); -// //Write string to file -// File filePath = new File(this.getDataFolder(), "regions.js"); -// try (FileWriter file = new FileWriter(filePath)) { -// file.write(regions); -// file.flush(); -// } catch (Exception e) { -// e.printStackTrace(); -// } - //Register API routes if JWebAPI is installed if (Bukkit.getPluginManager().getPlugin("JWebAPI") != null) { logger(Level.INFO, "JWebAPI found, registering API routes"); @@ -228,6 +213,12 @@ public void onEnable() { //Run auto claim task Bukkit.getScheduler().scheduleSyncRepeatingTask(plugin, new AutoClaimingTask(plugin), 1, this.getSettings().getConfigInteger("settings.auto-claim.timer") * 20); + Bukkit.getScheduler().scheduleSyncRepeatingTask(plugin, new AutomaticSaving(plugin), 1, 20 * 60 * 10); //Save every 10 minutes + + //bstats metrics + int pluginId = 20618; //JVillage plugin ID + this.metrics = new Metrics(plugin, pluginId); + } public void removeAPIRoutes() { @@ -260,7 +251,9 @@ public void onCustomEvent(final Event customEvent) { public void onDisable() { logger(Level.INFO, "Saving all villages"); villageMap.saveData(); + playerData.save(); + metrics.shutdown(); //Disable bstats metrics incase a reload is done removeAPIRoutes(); if (errored) { @@ -292,10 +285,6 @@ public void onDisable() { // logger(Level.INFO, "Exported Overviewer Regions"); // } - public Fundamentals getFundamentals() { - return (Fundamentals) Bukkit.getPluginManager().getPlugin("Fundamentals"); - } - public JVillageMap getVillageMap() { return villageMap; } @@ -473,8 +462,8 @@ public void deleteVillage(Village village) { // } public boolean townyImport() { - if (Bukkit.getServer().getPluginManager().getPlugin("Towny") == null || Bukkit.getServer().getPluginManager().getPlugin("Fundamentals") == null) { - log.log(Level.WARNING, "[" + pluginName + "] Towny or Fundamentals not found, cancelling towny import."); + if (Bukkit.getServer().getPluginManager().getPlugin("Towny") == null) { + log.log(Level.WARNING, "[" + pluginName + "] Towny not found, cancelling towny import."); return false; } @@ -488,7 +477,6 @@ public boolean townyImport() { } Towny towny = (Towny) getServer().getPluginManager().getPlugin("Towny"); - Fundamentals fundamentals = (Fundamentals) getServer().getPluginManager().getPlugin("Fundamentals"); int townsImported = 0; int townsSkipped = 0; @@ -534,7 +522,7 @@ public boolean townyImport() { continue; } - townOwnerUUID = fundamentals.getPlayerCache().getUUIDFromUsername(townOwnerUsername); + townOwnerUUID = this.getUUIDFromUsername(townOwnerUsername); if (townOwnerUUID == null) { townOwnerUUID = PoseidonUUID.getPlayerUUIDFromCache(townOwnerUsername, true); } @@ -550,7 +538,7 @@ public boolean townyImport() { //Import Assistants for (Resident resident : town.getAssistants()) { String assistantUsername = resident.getName(); - UUID assistantUUID = fundamentals.getPlayerCache().getUUIDFromUsername(assistantUsername); + UUID assistantUUID = this.getUUIDFromUsername(assistantUsername); if (assistantUUID == null) { log.warning("[" + pluginName + "] Could not find UUID for town assistant: " + assistantUsername + ". The assistant will not be imported for town " + newTownName + "."); continue; @@ -561,7 +549,7 @@ public boolean townyImport() { //Import Residents for (Resident resident : town.getResidents()) { String residentUsername = resident.getName(); - UUID residentUUID = fundamentals.getPlayerCache().getUUIDFromUsername(residentUsername); + UUID residentUUID = this.getUUIDFromUsername(residentUsername); if (residentUUID == null) { log.warning("[" + pluginName + "] Could not find UUID for town resident: " + residentUsername + ". The resident will not be imported for town " + newTownName + "."); residentsSkipped++; @@ -640,18 +628,16 @@ public boolean townyImport() { for (int i = 0; towny.getTownyUniverse().getResidents().size() > i; i++) { Resident resident = towny.getTownyUniverse().getResidents().get(i); String residentUsername = resident.getName(); - UUID residentUUID = fundamentals.getPlayerCache().getUUIDFromUsername(residentUsername); + UUID residentUUID = this.getUUIDFromUsername(residentUsername); if (residentUUID == null) { // log.warning("[" + pluginName + "] Could not find UUID for town resident: " + residentUsername + ". The resident will not be imported."); residentsSkipped2++; continue; } - FundamentalsPlayer fundamentalsPlayer = fundamentals.getPlayerMap().getPlayer(residentUUID); - // Save resident information - fundamentalsPlayer.saveInformation("jvillage.firstjoin", resident.getRegistered()); - fundamentalsPlayer.saveInformation("jvillage.lastjoin", resident.getLastOnline()); + playerData.setPlayerData(residentUUID, "firstJoin", String.valueOf(resident.getRegistered()/1000L)); + playerData.setPlayerData(residentUUID, "lastJoin", String.valueOf(resident.getLastOnline()/1000L)); residentsImported2++; } @@ -744,7 +730,7 @@ public boolean factionsImport() { continue; } - villageOwnerUUID = getUUIDFromPoseidonCache(ownerUsername); + villageOwnerUUID = this.getUUIDFromUsername(ownerUsername); if (villageOwnerUUID == null) { log.warning("[" + pluginName + "] Could not find UUID for faction (" + newVillageName + ") owner: " + ownerUsername + ". Ownership will be given to " + placeholderUsername + " (" + placeholderUUID + ")."); @@ -753,7 +739,7 @@ public boolean factionsImport() { //Import Assistants for (String assistantUsername : assistantUsernames) { - UUID assistantUUID = getUUIDFromPoseidonCache(assistantUsername); + UUID assistantUUID = this.getUUIDFromUsername(assistantUsername); if (assistantUUID == null) { log.warning("[" + pluginName + "] Could not find UUID for faction (" + newVillageName + ") assistant: " + assistantUsername + ". The assistant will not be imported."); continue; @@ -763,7 +749,7 @@ public boolean factionsImport() { //Import Members for (String memberUsername : memberUsernames) { - UUID memberUUID = getUUIDFromPoseidonCache(memberUsername); + UUID memberUUID = this.getUUIDFromUsername(memberUsername); if (memberUUID == null) { log.warning("[" + pluginName + "] Could not find UUID for faction (" + newVillageName + ") member: " + memberUsername + ". The member will not be imported."); continue; @@ -901,6 +887,10 @@ public JVillageSettings getSettings() { return settings; } + public JPlayerData getPlayerData() { + return this.playerData; + } + public Village generateNewVillage(String townName, UUID owner, VChunk vChunk, VCords townSpawn) { if (!villageNameAvailable(townName)) { return null; @@ -965,4 +955,41 @@ public boolean isClaimed(VChunk vChunk) { } return false; } + + @Nullable + public UUID getUUIDFromUsername(String username) { + UUID uuid = getUUIDFromPoseidonCache(username); + if (uuid == null) { + uuid = playerData.getUUID(username); + } + + //Check if Fundamentals is enabled and if so, use it's cache + if(fundamentalsEnabled && Bukkit.getPluginManager().isPluginEnabled("Fundamentals")) { + Fundamentals fundamentals = (Fundamentals) Bukkit.getPluginManager().getPlugin("Fundamentals"); + uuid = fundamentals.getPlayerCache().getUUIDFromUsername(username); + } + + return uuid; + } + + @Nullable + public String getUsernameFromUUID(UUID uuid) { + String username = PoseidonUUID.getPlayerUsernameFromUUID(uuid); + + if (username == null) { + username = playerData.getUsername(uuid); + } + + //Check if Fundamentals is enabled and if so, use it's cache + if(fundamentalsEnabled && Bukkit.getPluginManager().isPluginEnabled("Fundamentals")) { + Fundamentals fundamentals = (Fundamentals) Bukkit.getPluginManager().getPlugin("Fundamentals"); + username = fundamentals.getPlayerCache().getUsernameFromUUID(uuid); + } + + return username; + } + + public boolean isFundamentalsEnabled() { + return fundamentalsEnabled; + } } diff --git a/src/main/java/com/johnymuffin/jvillage/beta/commands/JVilageAdminCMD.java b/src/main/java/com/johnymuffin/jvillage/beta/commands/JVilageAdminCMD.java index 3c8f449..6409657 100644 --- a/src/main/java/com/johnymuffin/jvillage/beta/commands/JVilageAdminCMD.java +++ b/src/main/java/com/johnymuffin/jvillage/beta/commands/JVilageAdminCMD.java @@ -217,7 +217,7 @@ private boolean villageKickCommand(CommandSender commandSender, String[] strings return true; } - UUID uuid = plugin.getFundamentals().getPlayerCache().getUUIDFromUsername(playerName); + UUID uuid = plugin.getUUIDFromUsername(playerName); if (uuid == null) { String message = language.getMessage("player_not_found_full"); message = message.replace("%player%", playerName); @@ -278,7 +278,7 @@ private boolean villageSetOwnerCommand(CommandSender commandSender, String[] str return true; } - UUID uuid = plugin.getFundamentals().getPlayerCache().getUUIDFromUsername(playerName); + UUID uuid = plugin.getUUIDFromUsername(playerName); if (uuid == null) { String message = language.getMessage("player_not_found_full"); message = message.replace("%player%", playerName); @@ -341,7 +341,7 @@ private boolean villageAddCommand(CommandSender commandSender, String[] strings) return true; } - UUID uuid = plugin.getFundamentals().getPlayerCache().getUUIDFromUsername(playerName); + UUID uuid = plugin.getUUIDFromUsername(playerName); if (uuid == null) { String message = language.getMessage("player_not_found_full"); message = message.replace("%player%", playerName); diff --git a/src/main/java/com/johnymuffin/jvillage/beta/commands/VResidentCommand.java b/src/main/java/com/johnymuffin/jvillage/beta/commands/VResidentCommand.java index 79decb8..9319271 100644 --- a/src/main/java/com/johnymuffin/jvillage/beta/commands/VResidentCommand.java +++ b/src/main/java/com/johnymuffin/jvillage/beta/commands/VResidentCommand.java @@ -33,7 +33,7 @@ public boolean onCommand(CommandSender commandSender, Command command, String s, if (strings.length > 0) { String targetPlayerName = strings[0]; - targetPlayerUUID = plugin.getFundamentals().getPlayerCache().getUUIDFromUsername(targetPlayerName); + targetPlayerUUID = plugin.getUUIDFromUsername(targetPlayerName); if (targetPlayerUUID == null) { String message = language.getMessage("player_not_found_full"); message = message.replace("%username%", targetPlayerName); diff --git a/src/main/java/com/johnymuffin/jvillage/beta/commands/village/JCreateCommand.java b/src/main/java/com/johnymuffin/jvillage/beta/commands/village/JCreateCommand.java index 03257fa..2d44437 100644 --- a/src/main/java/com/johnymuffin/jvillage/beta/commands/village/JCreateCommand.java +++ b/src/main/java/com/johnymuffin/jvillage/beta/commands/village/JCreateCommand.java @@ -105,7 +105,7 @@ public boolean onCommand(CommandSender commandSender, Command command, String s, //Check if player has enough money double creationCost = settings.getConfigDouble("settings.town-create.price.amount"); - if (creationCost > 0) { + if (creationCost > 0 && plugin.isFundamentalsEnabled()) { EconomyAPI.EconomyResult result = FundamentalsAPI.getEconomy().subtractBalance(player.getUniqueId(), creationCost, player.getWorld().getName()); String message; switch (result) { diff --git a/src/main/java/com/johnymuffin/jvillage/beta/commands/village/JDeleteCommand.java b/src/main/java/com/johnymuffin/jvillage/beta/commands/village/JDeleteCommand.java index 274d122..b08adf3 100644 --- a/src/main/java/com/johnymuffin/jvillage/beta/commands/village/JDeleteCommand.java +++ b/src/main/java/com/johnymuffin/jvillage/beta/commands/village/JDeleteCommand.java @@ -60,7 +60,7 @@ public boolean onCommand(CommandSender commandSender, Command command, String s, // Refund the player the balance double townBalance = village.getBalance(); - if (townBalance > 0) { + if (townBalance > 0 && plugin.isFundamentalsEnabled()) { EconomyAPI.EconomyResult result = FundamentalsAPI.getEconomy().additionBalance(player.getUniqueId(), townBalance); String message; switch (result) { diff --git a/src/main/java/com/johnymuffin/jvillage/beta/commands/village/JDemoteCommand.java b/src/main/java/com/johnymuffin/jvillage/beta/commands/village/JDemoteCommand.java index 57bd6f6..9ae8faf 100644 --- a/src/main/java/com/johnymuffin/jvillage/beta/commands/village/JDemoteCommand.java +++ b/src/main/java/com/johnymuffin/jvillage/beta/commands/village/JDemoteCommand.java @@ -55,7 +55,7 @@ public boolean onCommand(CommandSender commandSender, Command command, String s, //Get target players String playerName = strings[0]; - UUID uuid = plugin.getFundamentals().getPlayerCache().getUUIDFromUsername(playerName); + UUID uuid = plugin.getUUIDFromUsername(playerName); if (uuid == null) { String message = language.getMessage("command_village_demote_not_found"); message = message.replace("%player%", playerName); diff --git a/src/main/java/com/johnymuffin/jvillage/beta/commands/village/JDepositCommand.java b/src/main/java/com/johnymuffin/jvillage/beta/commands/village/JDepositCommand.java index f215a76..a38ace8 100644 --- a/src/main/java/com/johnymuffin/jvillage/beta/commands/village/JDepositCommand.java +++ b/src/main/java/com/johnymuffin/jvillage/beta/commands/village/JDepositCommand.java @@ -80,6 +80,11 @@ public boolean onCommand(CommandSender commandSender, Command command, String s, return true; } + if (!this.plugin.isFundamentalsEnabled()) { + sendWithNewline(commandSender, language.getMessage("economy_disabled")); + return true; + } + //Attempt to withdraw money from player EconomyAPI.EconomyResult result = FundamentalsAPI.getEconomy().subtractBalance(player.getUniqueId(), amount); switch (result) { diff --git a/src/main/java/com/johnymuffin/jvillage/beta/commands/village/JKickCommand.java b/src/main/java/com/johnymuffin/jvillage/beta/commands/village/JKickCommand.java index b8e2874..447e0e3 100644 --- a/src/main/java/com/johnymuffin/jvillage/beta/commands/village/JKickCommand.java +++ b/src/main/java/com/johnymuffin/jvillage/beta/commands/village/JKickCommand.java @@ -53,7 +53,7 @@ public boolean onCommand(CommandSender commandSender, Command command, String s, } String playerName = strings[0]; - UUID uuid = plugin.getFundamentals().getPlayerCache().getUUIDFromUsername(playerName); + UUID uuid = plugin.getUUIDFromUsername(playerName); if (uuid == null) { String message = language.getMessage("command_village_kick_not_found"); message = message.replace("%player%", playerName); diff --git a/src/main/java/com/johnymuffin/jvillage/beta/commands/village/JPromoteCommand.java b/src/main/java/com/johnymuffin/jvillage/beta/commands/village/JPromoteCommand.java index cce5ef7..badea9d 100644 --- a/src/main/java/com/johnymuffin/jvillage/beta/commands/village/JPromoteCommand.java +++ b/src/main/java/com/johnymuffin/jvillage/beta/commands/village/JPromoteCommand.java @@ -55,7 +55,7 @@ public boolean onCommand(CommandSender commandSender, Command command, String s, //Get target players String playerName = strings[0]; - UUID uuid = plugin.getFundamentals().getPlayerCache().getUUIDFromUsername(playerName); + UUID uuid = plugin.getUUIDFromUsername(playerName); if (uuid == null) { String message = language.getMessage("command_village_promote_not_found"); message = message.replace("%player%", playerName); diff --git a/src/main/java/com/johnymuffin/jvillage/beta/commands/village/JSetOwnerCommand.java b/src/main/java/com/johnymuffin/jvillage/beta/commands/village/JSetOwnerCommand.java index 82fe828..f1da5a3 100644 --- a/src/main/java/com/johnymuffin/jvillage/beta/commands/village/JSetOwnerCommand.java +++ b/src/main/java/com/johnymuffin/jvillage/beta/commands/village/JSetOwnerCommand.java @@ -56,7 +56,7 @@ public boolean onCommand(CommandSender commandSender, Command command, String s, String playerName = strings[0]; - UUID uuid = plugin.getFundamentals().getPlayerCache().getUUIDFromUsername(playerName); + UUID uuid = plugin.getUUIDFromUsername(playerName); if (uuid == null) { String message = language.getMessage("command_village_kick_not_found"); message = message.replace("%player%", playerName); diff --git a/src/main/java/com/johnymuffin/jvillage/beta/commands/village/JSpawnCommand.java b/src/main/java/com/johnymuffin/jvillage/beta/commands/village/JSpawnCommand.java index cbe0f13..c9f0adc 100644 --- a/src/main/java/com/johnymuffin/jvillage/beta/commands/village/JSpawnCommand.java +++ b/src/main/java/com/johnymuffin/jvillage/beta/commands/village/JSpawnCommand.java @@ -10,7 +10,7 @@ import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; -import static com.johnymuffin.beta.fundamentals.util.Utils.getSafeDestination; +import static com.johnymuffin.jvillage.beta.JVUtility.getSafeDestination; public class JSpawnCommand extends JVBaseCommand implements CommandExecutor { public JSpawnCommand(JVillage plugin) { diff --git a/src/main/java/com/johnymuffin/jvillage/beta/commands/village/JWithdrawCommand.java b/src/main/java/com/johnymuffin/jvillage/beta/commands/village/JWithdrawCommand.java index acdb739..59ffb2b 100644 --- a/src/main/java/com/johnymuffin/jvillage/beta/commands/village/JWithdrawCommand.java +++ b/src/main/java/com/johnymuffin/jvillage/beta/commands/village/JWithdrawCommand.java @@ -96,6 +96,11 @@ public boolean onCommand(CommandSender commandSender, Command command, String s, return true; } + if (!this.plugin.isFundamentalsEnabled()) { + sendWithNewline(commandSender, language.getMessage("economy_disabled")); + return true; + } + //Attempt to withdraw money from player EconomyAPI.EconomyResult result = FundamentalsAPI.getEconomy().additionBalance(player.getUniqueId(), amount); switch (result) { diff --git a/src/main/java/com/johnymuffin/jvillage/beta/config/JPlayerData.java b/src/main/java/com/johnymuffin/jvillage/beta/config/JPlayerData.java new file mode 100644 index 0000000..9ee1834 --- /dev/null +++ b/src/main/java/com/johnymuffin/jvillage/beta/config/JPlayerData.java @@ -0,0 +1,109 @@ +package com.johnymuffin.jvillage.beta.config; + +import com.johnymuffin.jvillage.beta.JVillage; +import org.bukkit.util.config.Configuration; + +import java.io.File; +import java.util.HashMap; +import java.util.UUID; + +public class JPlayerData extends Configuration { + + private JVillage plugin; + + private HashMap> playerData = new HashMap>(); + + private HashMap uuidToUsernameMap = new HashMap(); + private HashMap usernameToUUIDMap = new HashMap(); + + public JPlayerData(JVillage plugin) { + super(new File(plugin.getDataFolder(), "players.yml")); + this.plugin = plugin; + load(); + + if(getProperty("players") == null) { + setProperty("players", (Object) new HashMap()); + } + playerData = (HashMap>) getProperty("players"); + setHeader("#JVilage - Player Data", "#This file contains all player data for the plugin. Do not edit this file manually unless you know what you are doing"); + + //Load all players into the UUID cache that have usernames + for(String uuid : playerData.keySet()) { + UUID uuidObj = UUID.fromString(uuid); + if(hasPlayerData(uuidObj, "username")) { + uuidToUsernameMap.put(uuidObj, getPlayerData(uuidObj, "username")); + usernameToUUIDMap.put(getPlayerData(uuidObj, "username").toLowerCase(), uuidObj); + } + } + } + + public void setPlayerData(UUID uuid, String key, String value) { + HashMap data = getPlayer(uuid); + data.put(key, value); + setPlayer(uuid, data); + } + + public String getPlayerData(UUID uuid, String key) { + HashMap data = getPlayer(uuid); + return data.get(key); + } + + public Boolean getPlayerDataBoolean(UUID uuid, String key) { + HashMap data = getPlayer(uuid); + if(!data.containsKey(key)) { + return null; + } + return Boolean.parseBoolean(data.get(key)); + } + + public boolean hasPlayerData(UUID uuid, String key) { + HashMap data = getPlayer(uuid); + return data.containsKey(key); + } + + public UUID[] getAllPlayers() { + UUID[] uuids = new UUID[playerData.size()]; + int i = 0; + for(String uuid : playerData.keySet()) { + uuids[i] = UUID.fromString(uuid); + i++; + } + return uuids; + } + + public boolean isPlayerKnown(UUID uuid) { + return playerData.containsKey(uuid.toString()); + } + + private HashMap getPlayer(UUID uuid) { + if(!playerData.containsKey(uuid.toString())) { + playerData.put(uuid.toString(), new HashMap()); + } + return playerData.get(uuid.toString()); + } + + private void setPlayer(UUID uuid, HashMap data) { + playerData.put(uuid.toString(), data); + } + + public boolean save() { + setProperty("players", (Object) playerData); + return super.save(); + } + + //UUID/Username Functions + + public UUID getUUID(String playerName) { + return usernameToUUIDMap.getOrDefault(playerName, null); + } + + public void setUUID(String playerName, UUID uuid) { + this.usernameToUUIDMap.put(playerName, uuid); + this.uuidToUsernameMap.put(uuid, playerName); + setPlayerData(uuid, "username", playerName); + } + + public String getUsername(UUID uuid) { + return uuidToUsernameMap.getOrDefault(uuid, null); + } +} diff --git a/src/main/java/com/johnymuffin/jvillage/beta/config/JVillageLanguage.java b/src/main/java/com/johnymuffin/jvillage/beta/config/JVillageLanguage.java index c5f0df4..0204977 100644 --- a/src/main/java/com/johnymuffin/jvillage/beta/config/JVillageLanguage.java +++ b/src/main/java/com/johnymuffin/jvillage/beta/config/JVillageLanguage.java @@ -33,6 +33,8 @@ private void loadDefaults() { map.put("no_village_selected_or_name_invalid", "&4Sorry, you don't have a village selected or the name you entered is invalid"); map.put("command_invalid_argument_provide_integer", "&4Sorry, that is not a valid number. Please provide an integer"); + map.put("economy_disabled", "&4Sorry, the JVillage economy is disabled on this server"); + map.put("not_in_village", "&4Sorry, you are not in that village"); map.put("village_owner_leave", "&4Sorry, the owner of a village can't leave it"); map.put("movement_village_enter", "&bYou have entered the village of &9%village%"); @@ -167,7 +169,7 @@ private void loadDefaults() { map.put("command_village_autoswitch_on", "&bYou have enabled auto switching"); map.put("command_village_autoswitch_off", "&bYou have disabled auto switching"); map.put("command_village_autoswitch_use", "&cSorry, that is invalid. Try /village autoswitch [on|off]"); - map.put("command_village_autoswitch_set", "&bYour auto switching has been set to &9%state%"); + map.put("command_village_autoswitch_set", "&bYour auto switching is set to &9%state%"); map.put("command_village_leave_success", "&bYou have left the village &9%village%"); map.put("command_village_leave_use", "&cSorry, that is invalid. Try /village leave [village]"); diff --git a/src/main/java/com/johnymuffin/jvillage/beta/listeners/JVPlayerMoveListener.java b/src/main/java/com/johnymuffin/jvillage/beta/listeners/JVPlayerMoveListener.java index 7164457..e038676 100644 --- a/src/main/java/com/johnymuffin/jvillage/beta/listeners/JVPlayerMoveListener.java +++ b/src/main/java/com/johnymuffin/jvillage/beta/listeners/JVPlayerMoveListener.java @@ -58,6 +58,15 @@ public void onPlayerJoinEvent(final PlayerJoinEvent event) { //Disable auto claiming when a player joins if they have it enabled VPlayer vPlayer = plugin.getPlayerMap().getPlayer(event.getPlayer().getUniqueId()); vPlayer.setAutoClaimingEnabled(false, false); //Don't send message as it shouldn't be enabled anyway + + //Record appropriate player data + String firstJoin = plugin.getPlayerData().getPlayerData(event.getPlayer().getUniqueId(), "firstJoin"); + if (firstJoin == null) { + plugin.getPlayerData().setPlayerData(event.getPlayer().getUniqueId(), "firstJoin", String.valueOf(System.currentTimeMillis()/1000L)); + } + plugin.getPlayerData().setPlayerData(event.getPlayer().getUniqueId(), "username", event.getPlayer().getName()); + plugin.getPlayerData().setPlayerData(event.getPlayer().getUniqueId(), "lastOnline", String.valueOf(System.currentTimeMillis()/1000L)); + plugin.getPlayerData().setUUID(vPlayer.getUsername(), event.getPlayer().getUniqueId()); } //TODO: This might not be needed, decide if it is or not @@ -75,6 +84,8 @@ public void onPlayerQuitEvent(final PlayerQuitEvent event) { //Disable auto claiming when a player quits if they have it enabled VPlayer vPlayer = plugin.getPlayerMap().getPlayer(event.getPlayer().getUniqueId()); vPlayer.setAutoClaimingEnabled(false, false); + + plugin.getPlayerData().setPlayerData(event.getPlayer().getUniqueId(), "lastOnline", String.valueOf(System.currentTimeMillis()/1000L)); } private void updatePlayerLocation(Player player, Location location) { diff --git a/src/main/java/com/johnymuffin/jvillage/beta/maps/JPlayerMap.java b/src/main/java/com/johnymuffin/jvillage/beta/maps/JPlayerMap.java index eb18932..f1dc62f 100644 --- a/src/main/java/com/johnymuffin/jvillage/beta/maps/JPlayerMap.java +++ b/src/main/java/com/johnymuffin/jvillage/beta/maps/JPlayerMap.java @@ -1,6 +1,5 @@ package com.johnymuffin.jvillage.beta.maps; -import com.johnymuffin.beta.fundamentals.Fundamentals; import com.johnymuffin.jvillage.beta.JVillage; import com.johnymuffin.jvillage.beta.models.Village; import com.johnymuffin.jvillage.beta.player.VPlayer; @@ -17,9 +16,9 @@ public class JPlayerMap { public JPlayerMap(JVillage plugin) { this.plugin = plugin; - for (UUID uuid : getFundamentals().getPlayerMap().getKnownPlayers()) { - playerMap.put(uuid, new VPlayer(plugin, uuid)); - } +// for (UUID uuid : getFundamentals().getPlayerMap().getKnownPlayers()) { +// playerMap.put(uuid, new VPlayer(plugin, uuid)); +// } } @@ -31,9 +30,9 @@ public VPlayer getPlayer(UUID uuid) { } - private Fundamentals getFundamentals() { - return plugin.getFundamentals(); - } +// private Fundamentals getFundamentals() { +// return plugin.getFundamentals(); +// } } diff --git a/src/main/java/com/johnymuffin/jvillage/beta/player/VPlayer.java b/src/main/java/com/johnymuffin/jvillage/beta/player/VPlayer.java index 1761d52..44f4ba9 100644 --- a/src/main/java/com/johnymuffin/jvillage/beta/player/VPlayer.java +++ b/src/main/java/com/johnymuffin/jvillage/beta/player/VPlayer.java @@ -1,7 +1,6 @@ package com.johnymuffin.jvillage.beta.player; -import com.johnymuffin.beta.fundamentals.player.FundamentalsPlayer; import com.johnymuffin.jvillage.beta.JVillage; import com.johnymuffin.jvillage.beta.events.PlayerSwitchTownEvent; import com.johnymuffin.jvillage.beta.models.Village; @@ -11,6 +10,7 @@ import java.util.ArrayList; import java.util.UUID; +import java.util.logging.Level; import static com.johnymuffin.jvillage.beta.JVUtility.getPlayerFromUUID; @@ -118,14 +118,22 @@ public void removeInvitationToVillage(Village village) { } public boolean autoSwitchSelected() { - if (getFundamentalsPlayer().getInformation("jvillage.autoswitch") == null) { +// if (getFundamentalsPlayer().getInformation("jvillage.autoswitch") == null) { +// return true; +// } +// return Boolean.parseBoolean(String.valueOf(getFundamentalsPlayer().getInformation("jvillage.autoswitch"))); + + String autoSwitch = plugin.getPlayerData().getPlayerData(uuid, "autoswitch"); + if (autoSwitch == null) { + plugin.debugLogger(Level.INFO, "AutoSwitch is null in players.yml for " + getUsername() + ". Assuming true."); return true; } - return Boolean.parseBoolean(String.valueOf(getFundamentalsPlayer().getInformation("jvillage.autoswitch"))); + return Boolean.parseBoolean(autoSwitch); } public void setAutoSwitchSelected(boolean autoSwitch) { - getFundamentalsPlayer().saveInformation("jvillage.autoswitch", autoSwitch); +// getFundamentalsPlayer().saveInformation("jvillage.autoswitch", autoSwitch); + plugin.getPlayerData().setPlayerData(uuid, "autoswitch", String.valueOf(autoSwitch)); } public Village getCurrentlyLocatedIn() { @@ -150,10 +158,6 @@ public boolean isLocatedInVillage() { return currentlyLocatedIn != null; } - public FundamentalsPlayer getFundamentalsPlayer() { - return plugin.getFundamentals().getPlayerMap().getPlayer(uuid); - } - public boolean leaveVillage(Village village) { if (village.getOwner().equals(uuid)) { throw new IllegalArgumentException("Cannot leave a village you own"); @@ -172,9 +176,9 @@ public boolean leaveVillage(Village village) { } public String getUsername() { - String username = plugin.getFundamentals().getPlayerCache().getUsernameFromUUID(uuid); + String username = PoseidonUUID.getPlayerUsernameFromUUID(uuid); if (username == null) { - username = PoseidonUUID.getPlayerUsernameFromUUID(uuid); + username = plugin.getPlayerData().getUsername(uuid); } if (username == null) { username = "Unknown UUID"; diff --git a/src/main/java/com/johnymuffin/jvillage/beta/routes/api/v1/JVillageGetPlayerRoute.java b/src/main/java/com/johnymuffin/jvillage/beta/routes/api/v1/JVillageGetPlayerRoute.java index adca4e2..237f71e 100644 --- a/src/main/java/com/johnymuffin/jvillage/beta/routes/api/v1/JVillageGetPlayerRoute.java +++ b/src/main/java/com/johnymuffin/jvillage/beta/routes/api/v1/JVillageGetPlayerRoute.java @@ -39,7 +39,7 @@ protected void doGet(HttpServletRequest request, final HttpServletResponse respo try { UUID playerUUID = UUID.fromString(uuid); JSONObject playerJSON = new JSONObject(); - if (!jVillage.getFundamentals().getPlayerMap().isPlayerKnown(playerUUID)) { + if (!jVillage.getPlayerData().isPlayerKnown(playerUUID)) { playerJSON.put("found", false); playerJSON.put("error", false); response.setStatus(HttpServletResponse.SC_NOT_FOUND); diff --git a/src/main/java/com/johnymuffin/jvillage/beta/tasks/AutomaticSaving.java b/src/main/java/com/johnymuffin/jvillage/beta/tasks/AutomaticSaving.java new file mode 100644 index 0000000..e3bd905 --- /dev/null +++ b/src/main/java/com/johnymuffin/jvillage/beta/tasks/AutomaticSaving.java @@ -0,0 +1,22 @@ +package com.johnymuffin.jvillage.beta.tasks; + +import com.johnymuffin.jvillage.beta.JVillage; + +import java.util.logging.Level; + +public class AutomaticSaving implements Runnable { + + private JVillage plugin; + + public AutomaticSaving(JVillage plugin) { + this.plugin = plugin; + } + + @Override + public void run() { + plugin.debugLogger(Level.INFO, "Running auto saving task"); + + plugin.getVillageMap().saveData(); + plugin.getPlayerData().save(); + } +} diff --git a/src/main/java/com/johnymuffin/jvillage/beta/tasks/Metrics.java b/src/main/java/com/johnymuffin/jvillage/beta/tasks/Metrics.java new file mode 100644 index 0000000..e6ef442 --- /dev/null +++ b/src/main/java/com/johnymuffin/jvillage/beta/tasks/Metrics.java @@ -0,0 +1,905 @@ +/* + * This Metrics class was auto-generated and can be copied into your project if you are + * not using a build tool like Gradle or Maven for dependency management. + * + * IMPORTANT: You are not allowed to modify this class, except changing the package. + * + * Disallowed modifications include but are not limited to: + * - Remove the option for users to opt-out + * - Change the frequency for data submission + * - Obfuscate the code (every obfuscator should allow you to make an exception for specific files) + * - Reformat the code (if you use a linter, add an exception) + * + * Violations will result in a ban of your plugin and account from bStats. + */ +package com.johnymuffin.jvillage.beta.tasks; + +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.util.config.Configuration; + +import javax.net.ssl.HttpsURLConnection; +import java.io.*; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.concurrent.Callable; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.logging.Level; +import java.util.stream.Collectors; +import java.util.zip.GZIPOutputStream; + +public class Metrics { + + private final Plugin plugin; + + private final MetricsBase metricsBase; + + /** + * Creates a new Metrics instance. + * + * @param plugin Your plugin instance. + * @param serviceId The id of the service. It can be found at What is my plugin id? + */ + public Metrics(JavaPlugin plugin, int serviceId) { + this.plugin = plugin; + // Get the config file + File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats"); + File configFile = new File(bStatsFolder, "config.yml"); + + //Poseidon Implementation - Start + Configuration config = new Configuration(configFile); + config.load(); + if (config.getProperty("serverUuid") == null) { + config.setProperty("enabled", true); + config.setProperty("serverUuid", UUID.randomUUID().toString()); + config.setProperty("logFailedRequests", false); + config.setProperty("logSentData", false); + config.setProperty("logResponseStatusText", false); + } + // Inform the server owners about bStats + config.setHeader( + "#bStats (https://bStats.org) collects some basic information for plugin authors, like how" + , "#many people use their plugin and their total player count. It's recommended to keep bStats" + , "#enabled, but if you're not comfortable with this, you can turn this setting off. There is no" + , "#performance penalty associated with having metrics enabled, and data sent to bStats is fully" + , "#anonymous."); + config.save(); + + //Poseidon Implementation - End + + +// YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile); +// if (!config.isSet("serverUuid")) { +// config.addDefault("enabled", true); +// config.addDefault("serverUuid", UUID.randomUUID().toString()); +// config.addDefault("logFailedRequests", false); +// config.addDefault("logSentData", false); +// config.addDefault("logResponseStatusText", false); +// // Inform the server owners about bStats +// config +// .options() +// .header( +// "bStats (https://bStats.org) collects some basic information for plugin authors, like how\n" +// + "many people use their plugin and their total player count. It's recommended to keep bStats\n" +// + "enabled, but if you're not comfortable with this, you can turn this setting off. There is no\n" +// + "performance penalty associated with having metrics enabled, and data sent to bStats is fully\n" +// + "anonymous.") +// .copyDefaults(true); +// try { +// config.save(configFile); +// } catch (IOException ignored) { +// } +// } + // Load the data + boolean enabled = config.getBoolean("enabled", true); + String serverUUID = config.getString("serverUuid"); + boolean logErrors = config.getBoolean("logFailedRequests", false); + boolean logSentData = config.getBoolean("logSentData", false); + boolean logResponseStatusText = config.getBoolean("logResponseStatusText", false); + metricsBase = + new MetricsBase( + "bukkit", + serverUUID, + serviceId, + enabled, + this::appendPlatformData, + this::appendServiceData, + //submitDataTask -> Bukkit.getScheduler().runTask(plugin, submitDataTask), + submitDataTask -> Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, submitDataTask), + plugin::isEnabled, +// (message, error) -> this.plugin.getLogger().log(Level.WARNING, message, error), + (message, error) -> this.plugin.getServer().getLogger().log(Level.WARNING, message, error), +// (message) -> this.plugin.getLogger().log(Level.INFO, message), + (message) -> this.plugin.getServer().getLogger().log(Level.INFO, message), + logErrors, + logSentData, + logResponseStatusText); + } + + /** + * Shuts down the underlying scheduler service. + */ + public void shutdown() { + metricsBase.shutdown(); + } + + /** + * Adds a custom chart. + * + * @param chart The chart to add. + */ + public void addCustomChart(CustomChart chart) { + metricsBase.addCustomChart(chart); + } + + private void appendPlatformData(JsonObjectBuilder builder) { + builder.appendField("playerAmount", getPlayerAmount()); + builder.appendField("onlineMode", Bukkit.getOnlineMode() ? 1 : 0); + builder.appendField("bukkitVersion", Bukkit.getVersion()); + builder.appendField("bukkitName", Bukkit.getName()); + builder.appendField("javaVersion", System.getProperty("java.version")); + builder.appendField("osName", System.getProperty("os.name")); + builder.appendField("osArch", System.getProperty("os.arch")); + builder.appendField("osVersion", System.getProperty("os.version")); + builder.appendField("coreCount", Runtime.getRuntime().availableProcessors()); + } + + private void appendServiceData(JsonObjectBuilder builder) { + builder.appendField("pluginVersion", plugin.getDescription().getVersion()); + } + + private int getPlayerAmount() { +// try { +// // Around MC 1.8 the return type was changed from an array to a collection, +// // This fixes java.lang.NoSuchMethodError: +// // org.bukkit.Bukkit.getOnlinePlayers()Ljava/util/Collection; +// Method onlinePlayersMethod = Class.forName("org.bukkit.Server").getMethod("getOnlinePlayers"); +// return onlinePlayersMethod.getReturnType().equals(Collection.class) +// ? ((Collection) onlinePlayersMethod.invoke(Bukkit.getServer())).size() +// : ((Player[]) onlinePlayersMethod.invoke(Bukkit.getServer())).length; +// } catch (Exception e) { +// // Just use the new method if the reflection failed +// return Bukkit.getOnlinePlayers().size(); +// } + + //Poseidon Implementation - Start + return Bukkit.getOnlinePlayers().length; + //Poseidon Implementation - End + } + + public static class MetricsBase { + + /** + * The version of the Metrics class. + */ + public static final String METRICS_VERSION = "3.0.2"; + + private static final String REPORT_URL = "https://bStats.org/api/v2/data/%s"; + + private final ScheduledExecutorService scheduler; + + private final String platform; + + private final String serverUuid; + + private final int serviceId; + + private final Consumer appendPlatformDataConsumer; + + private final Consumer appendServiceDataConsumer; + + private final Consumer submitTaskConsumer; + + private final Supplier checkServiceEnabledSupplier; + + private final BiConsumer errorLogger; + + private final Consumer infoLogger; + + private final boolean logErrors; + + private final boolean logSentData; + + private final boolean logResponseStatusText; + + private final Set customCharts = new HashSet<>(); + + private final boolean enabled; + + /** + * Creates a new MetricsBase class instance. + * + * @param platform The platform of the service. + * @param serviceId The id of the service. + * @param serverUuid The server uuid. + * @param enabled Whether or not data sending is enabled. + * @param appendPlatformDataConsumer A consumer that receives a {@code JsonObjectBuilder} and + * appends all platform-specific data. + * @param appendServiceDataConsumer A consumer that receives a {@code JsonObjectBuilder} and + * appends all service-specific data. + * @param submitTaskConsumer A consumer that takes a runnable with the submit task. This can be + * used to delegate the data collection to a another thread to prevent errors caused by + * concurrency. Can be {@code null}. + * @param checkServiceEnabledSupplier A supplier to check if the service is still enabled. + * @param errorLogger A consumer that accepts log message and an error. + * @param infoLogger A consumer that accepts info log messages. + * @param logErrors Whether or not errors should be logged. + * @param logSentData Whether or not the sent data should be logged. + * @param logResponseStatusText Whether or not the response status text should be logged. + */ + public MetricsBase( + String platform, + String serverUuid, + int serviceId, + boolean enabled, + Consumer appendPlatformDataConsumer, + Consumer appendServiceDataConsumer, + Consumer submitTaskConsumer, + Supplier checkServiceEnabledSupplier, + BiConsumer errorLogger, + Consumer infoLogger, + boolean logErrors, + boolean logSentData, + boolean logResponseStatusText) { + ScheduledThreadPoolExecutor scheduler = + new ScheduledThreadPoolExecutor(1, task -> new Thread(task, "bStats-Metrics")); + // We want delayed tasks (non-periodic) that will execute in the future to be + // cancelled when the scheduler is shutdown. + // Otherwise, we risk preventing the server from shutting down even when + // MetricsBase#shutdown() is called + scheduler.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); + this.scheduler = scheduler; + this.platform = platform; + this.serverUuid = serverUuid; + this.serviceId = serviceId; + this.enabled = enabled; + this.appendPlatformDataConsumer = appendPlatformDataConsumer; + this.appendServiceDataConsumer = appendServiceDataConsumer; + this.submitTaskConsumer = submitTaskConsumer; + this.checkServiceEnabledSupplier = checkServiceEnabledSupplier; + this.errorLogger = errorLogger; + this.infoLogger = infoLogger; + this.logErrors = logErrors; + this.logSentData = logSentData; + this.logResponseStatusText = logResponseStatusText; + checkRelocation(); + if (enabled) { + // WARNING: Removing the option to opt-out will get your plugin banned from + // bStats + startSubmitting(); + } + } + + public void addCustomChart(CustomChart chart) { + this.customCharts.add(chart); + } + + public void shutdown() { + scheduler.shutdown(); + } + + private void startSubmitting() { + final Runnable submitTask = + () -> { + if (!enabled || !checkServiceEnabledSupplier.get()) { + // Submitting data or service is disabled + scheduler.shutdown(); + return; + } + if (submitTaskConsumer != null) { + submitTaskConsumer.accept(this::submitData); + } else { + this.submitData(); + } + }; + // Many servers tend to restart at a fixed time at xx:00 which causes an uneven + // distribution of requests on the + // bStats backend. To circumvent this problem, we introduce some randomness into + // the initial and second delay. + // WARNING: You must not modify and part of this Metrics class, including the + // submit delay or frequency! + // WARNING: Modifying this code will get your plugin banned on bStats. Just + // don't do it! + long initialDelay = (long) (1000 * 60 * (3 + Math.random() * 3)); + long secondDelay = (long) (1000 * 60 * (Math.random() * 30)); + scheduler.schedule(submitTask, initialDelay, TimeUnit.MILLISECONDS); + scheduler.scheduleAtFixedRate( + submitTask, initialDelay + secondDelay, 1000 * 60 * 30, TimeUnit.MILLISECONDS); + } + + private void submitData() { + final JsonObjectBuilder baseJsonBuilder = new JsonObjectBuilder(); + appendPlatformDataConsumer.accept(baseJsonBuilder); + final JsonObjectBuilder serviceJsonBuilder = new JsonObjectBuilder(); + appendServiceDataConsumer.accept(serviceJsonBuilder); + JsonObjectBuilder.JsonObject[] chartData = + customCharts.stream() + .map(customChart -> customChart.getRequestJsonObject(errorLogger, logErrors)) + .filter(Objects::nonNull) + .toArray(JsonObjectBuilder.JsonObject[]::new); + serviceJsonBuilder.appendField("id", serviceId); + serviceJsonBuilder.appendField("customCharts", chartData); + baseJsonBuilder.appendField("service", serviceJsonBuilder.build()); + baseJsonBuilder.appendField("serverUUID", serverUuid); + baseJsonBuilder.appendField("metricsVersion", METRICS_VERSION); + JsonObjectBuilder.JsonObject data = baseJsonBuilder.build(); + scheduler.execute( + () -> { + try { + // Send the data + sendData(data); + } catch (Exception e) { + // Something went wrong! :( + if (logErrors) { + errorLogger.accept("Could not submit bStats metrics data", e); + } + } + }); + } + + private void sendData(JsonObjectBuilder.JsonObject data) throws Exception { + if (logSentData) { + infoLogger.accept("Sent bStats metrics data: " + data.toString()); + } + String url = String.format(REPORT_URL, platform); + HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection(); + // Compress the data to save bandwidth + byte[] compressedData = compress(data.toString()); + connection.setRequestMethod("POST"); + connection.addRequestProperty("Accept", "application/json"); + connection.addRequestProperty("Connection", "close"); + connection.addRequestProperty("Content-Encoding", "gzip"); + connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length)); + connection.setRequestProperty("Content-Type", "application/json"); + connection.setRequestProperty("User-Agent", "Metrics-Service/1"); + connection.setDoOutput(true); + try (DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream())) { + outputStream.write(compressedData); + } + StringBuilder builder = new StringBuilder(); + try (BufferedReader bufferedReader = + new BufferedReader(new InputStreamReader(connection.getInputStream()))) { + String line; + while ((line = bufferedReader.readLine()) != null) { + builder.append(line); + } + } + if (logResponseStatusText) { + infoLogger.accept("Sent data to bStats and received response: " + builder); + } + } + + /** + * Checks that the class was properly relocated. + */ + private void checkRelocation() { + // You can use the property to disable the check in your test environment + if (System.getProperty("bstats.relocatecheck") == null + || !System.getProperty("bstats.relocatecheck").equals("false")) { + // Maven's Relocate is clever and changes strings, too. So we have to use this + // little "trick" ... :D + final String defaultPackage = + new String(new byte[]{'o', 'r', 'g', '.', 'b', 's', 't', 'a', 't', 's'}); + final String examplePackage = + new String(new byte[]{'y', 'o', 'u', 'r', '.', 'p', 'a', 'c', 'k', 'a', 'g', 'e'}); + // We want to make sure no one just copy & pastes the example and uses the wrong + // package names + if (MetricsBase.class.getPackage().getName().startsWith(defaultPackage) + || MetricsBase.class.getPackage().getName().startsWith(examplePackage)) { + throw new IllegalStateException("bStats Metrics class has not been relocated correctly!"); + } + } + } + + /** + * Gzips the given string. + * + * @param str The string to gzip. + * @return The gzipped string. + */ + private static byte[] compress(final String str) throws IOException { + if (str == null) { + return null; + } + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + try (GZIPOutputStream gzip = new GZIPOutputStream(outputStream)) { + gzip.write(str.getBytes(StandardCharsets.UTF_8)); + } + return outputStream.toByteArray(); + } + } + + public static class SimplePie extends CustomChart { + + private final Callable callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public SimplePie(String chartId, Callable callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + String value = callable.call(); + if (value == null || value.isEmpty()) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("value", value).build(); + } + } + + public static class MultiLineChart extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public MultiLineChart(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() == 0) { + // Skip this invalid + continue; + } + allSkipped = false; + valuesBuilder.appendField(entry.getKey(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); + } + } + + public static class AdvancedPie extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public AdvancedPie(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() == 0) { + // Skip this invalid + continue; + } + allSkipped = false; + valuesBuilder.appendField(entry.getKey(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); + } + } + + public static class SimpleBarChart extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public SimpleBarChart(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + for (Map.Entry entry : map.entrySet()) { + valuesBuilder.appendField(entry.getKey(), new int[]{entry.getValue()}); + } + return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); + } + } + + public static class AdvancedBarChart extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public AdvancedBarChart(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue().length == 0) { + // Skip this invalid + continue; + } + allSkipped = false; + valuesBuilder.appendField(entry.getKey(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); + } + } + + public static class DrilldownPie extends CustomChart { + + private final Callable>> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public DrilldownPie(String chartId, Callable>> callable) { + super(chartId); + this.callable = callable; + } + + @Override + public JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); + Map> map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean reallyAllSkipped = true; + for (Map.Entry> entryValues : map.entrySet()) { + JsonObjectBuilder valueBuilder = new JsonObjectBuilder(); + boolean allSkipped = true; + for (Map.Entry valueEntry : map.get(entryValues.getKey()).entrySet()) { + valueBuilder.appendField(valueEntry.getKey(), valueEntry.getValue()); + allSkipped = false; + } + if (!allSkipped) { + reallyAllSkipped = false; + valuesBuilder.appendField(entryValues.getKey(), valueBuilder.build()); + } + } + if (reallyAllSkipped) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); + } + } + + public abstract static class CustomChart { + + private final String chartId; + + protected CustomChart(String chartId) { + if (chartId == null) { + throw new IllegalArgumentException("chartId must not be null"); + } + this.chartId = chartId; + } + + public JsonObjectBuilder.JsonObject getRequestJsonObject( + BiConsumer errorLogger, boolean logErrors) { + JsonObjectBuilder builder = new JsonObjectBuilder(); + builder.appendField("chartId", chartId); + try { + JsonObjectBuilder.JsonObject data = getChartData(); + if (data == null) { + // If the data is null we don't send the chart. + return null; + } + builder.appendField("data", data); + } catch (Throwable t) { + if (logErrors) { + errorLogger.accept("Failed to get data for custom chart with id " + chartId, t); + } + return null; + } + return builder.build(); + } + + protected abstract JsonObjectBuilder.JsonObject getChartData() throws Exception; + } + + public static class SingleLineChart extends CustomChart { + + private final Callable callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public SingleLineChart(String chartId, Callable callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + int value = callable.call(); + if (value == 0) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("value", value).build(); + } + } + + /** + * An extremely simple JSON builder. + * + *

While this class is neither feature-rich nor the most performant one, it's sufficient enough + * for its use-case. + */ + public static class JsonObjectBuilder { + + private StringBuilder builder = new StringBuilder(); + + private boolean hasAtLeastOneField = false; + + public JsonObjectBuilder() { + builder.append("{"); + } + + /** + * Appends a null field to the JSON. + * + * @param key The key of the field. + * @return A reference to this object. + */ + public JsonObjectBuilder appendNull(String key) { + appendFieldUnescaped(key, "null"); + return this; + } + + /** + * Appends a string field to the JSON. + * + * @param key The key of the field. + * @param value The value of the field. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, String value) { + if (value == null) { + throw new IllegalArgumentException("JSON value must not be null"); + } + appendFieldUnescaped(key, "\"" + escape(value) + "\""); + return this; + } + + /** + * Appends an integer field to the JSON. + * + * @param key The key of the field. + * @param value The value of the field. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, int value) { + appendFieldUnescaped(key, String.valueOf(value)); + return this; + } + + /** + * Appends an object to the JSON. + * + * @param key The key of the field. + * @param object The object. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, JsonObject object) { + if (object == null) { + throw new IllegalArgumentException("JSON object must not be null"); + } + appendFieldUnescaped(key, object.toString()); + return this; + } + + /** + * Appends a string array to the JSON. + * + * @param key The key of the field. + * @param values The string array. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, String[] values) { + if (values == null) { + throw new IllegalArgumentException("JSON values must not be null"); + } + String escapedValues = + Arrays.stream(values) + .map(value -> "\"" + escape(value) + "\"") + .collect(Collectors.joining(",")); + appendFieldUnescaped(key, "[" + escapedValues + "]"); + return this; + } + + /** + * Appends an integer array to the JSON. + * + * @param key The key of the field. + * @param values The integer array. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, int[] values) { + if (values == null) { + throw new IllegalArgumentException("JSON values must not be null"); + } + String escapedValues = + Arrays.stream(values).mapToObj(String::valueOf).collect(Collectors.joining(",")); + appendFieldUnescaped(key, "[" + escapedValues + "]"); + return this; + } + + /** + * Appends an object array to the JSON. + * + * @param key The key of the field. + * @param values The integer array. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, JsonObject[] values) { + if (values == null) { + throw new IllegalArgumentException("JSON values must not be null"); + } + String escapedValues = + Arrays.stream(values).map(JsonObject::toString).collect(Collectors.joining(",")); + appendFieldUnescaped(key, "[" + escapedValues + "]"); + return this; + } + + /** + * Appends a field to the object. + * + * @param key The key of the field. + * @param escapedValue The escaped value of the field. + */ + private void appendFieldUnescaped(String key, String escapedValue) { + if (builder == null) { + throw new IllegalStateException("JSON has already been built"); + } + if (key == null) { + throw new IllegalArgumentException("JSON key must not be null"); + } + if (hasAtLeastOneField) { + builder.append(","); + } + builder.append("\"").append(escape(key)).append("\":").append(escapedValue); + hasAtLeastOneField = true; + } + + /** + * Builds the JSON string and invalidates this builder. + * + * @return The built JSON string. + */ + public JsonObject build() { + if (builder == null) { + throw new IllegalStateException("JSON has already been built"); + } + JsonObject object = new JsonObject(builder.append("}").toString()); + builder = null; + return object; + } + + /** + * Escapes the given string like stated in https://www.ietf.org/rfc/rfc4627.txt. + * + *

This method escapes only the necessary characters '"', '\'. and '\u0000' - '\u001F'. + * Compact escapes are not used (e.g., '\n' is escaped as "\u000a" and not as "\n"). + * + * @param value The value to escape. + * @return The escaped value. + */ + private static String escape(String value) { + final StringBuilder builder = new StringBuilder(); + for (int i = 0; i < value.length(); i++) { + char c = value.charAt(i); + if (c == '"') { + builder.append("\\\""); + } else if (c == '\\') { + builder.append("\\\\"); + } else if (c <= '\u000F') { + builder.append("\\u000").append(Integer.toHexString(c)); + } else if (c <= '\u001F') { + builder.append("\\u00").append(Integer.toHexString(c)); + } else { + builder.append(c); + } + } + return builder.toString(); + } + + /** + * A super simple representation of a JSON object. + * + *

This class only exists to make methods of the {@link JsonObjectBuilder} type-safe and not + * allow a raw string inputs for methods like {@link JsonObjectBuilder#appendField(String, + * JsonObject)}. + */ + public static class JsonObject { + + private final String value; + + private JsonObject(String value) { + this.value = value; + } + + @Override + public String toString() { + return value; + } + } + } +} From 535b75f50835a724b6376f0271a93f03b798566c Mon Sep 17 00:00:00 2001 From: Johny Muffin Date: Tue, 2 Jan 2024 11:34:13 +1000 Subject: [PATCH 3/3] Fix claim importing --- .../johnymuffin/jvillage/beta/JVillage.java | 73 +++++++++++-------- 1 file changed, 42 insertions(+), 31 deletions(-) diff --git a/src/main/java/com/johnymuffin/jvillage/beta/JVillage.java b/src/main/java/com/johnymuffin/jvillage/beta/JVillage.java index 6ebb78f..01a16e8 100644 --- a/src/main/java/com/johnymuffin/jvillage/beta/JVillage.java +++ b/src/main/java/com/johnymuffin/jvillage/beta/JVillage.java @@ -27,10 +27,7 @@ import com.johnymuffin.jvillage.beta.tasks.AutomaticSaving; import com.johnymuffin.jvillage.beta.tasks.Metrics; import com.legacyminecraft.poseidon.event.PoseidonCustomListener; -import com.massivecraft.factions.FLocation; -import com.massivecraft.factions.FPlayer; -import com.massivecraft.factions.Faction; -import com.massivecraft.factions.Factions; +import com.massivecraft.factions.*; import com.massivecraft.factions.struct.Role; import com.palmergames.bukkit.towny.Towny; import com.palmergames.bukkit.towny.object.Resident; @@ -664,6 +661,24 @@ public boolean factionsImport() { logger(Level.WARNING, "Could not add placeholder UUID to Poseidon UUIDCache. This could cause Unknown User to be shown for some imported Villages."); } + //Get Private Factions Board + HashMap factionClaims; + try { + Field flocationIdsField = Board.class.getDeclaredField("flocationIds"); + flocationIdsField.setAccessible(true); + factionClaims = (HashMap) flocationIdsField.get(null); + } catch (Exception exception) { + logger(Level.WARNING, "Could not get Factions board. Factions import will be skipped. Please note Factions import is only supported on Java 8 due to the use of reflections."); + return false; + } + + if(factionClaims == null) { + logger(Level.WARNING, "Could not get Factions board. Factions import will be skipped."); + return false; + } + + + if (Bukkit.getServer().getPluginManager().getPlugin("Factions") == null) { log.log(Level.WARNING, "[" + pluginName + "] Factions not found, cancelling towny import."); return false; @@ -760,32 +775,20 @@ public boolean factionsImport() { //Import Claims + ArrayList villageClaims = new ArrayList<>(); - //Use reflection to get claimOwnership field from Faction - Map> claimOwnership; - Field claimOwnershipField = null; - try { - claimOwnershipField = faction.getClass().getDeclaredField("claimOwnership"); - claimOwnershipField.setAccessible(true); - claimOwnership = (Map>) claimOwnershipField.get(faction); - } catch (Exception e) { - log.severe("[" + pluginName + "] Could not get claimOwnership field from Faction class. Factions import will not continue. Please note this import function is only compatible with Java 8 due to the use of reflection."); - e.printStackTrace(); - return false; - } - - if (claimOwnership == null) { - log.warning("[" + pluginName + "] claimOwnership variable is null. Factions import will not continue."); - return false; - } - ArrayList villageClaims = new ArrayList<>(); + int factionId = faction.getId(); - for (Map.Entry> entry : claimOwnership.entrySet()) { + //Loop through factionClaims + for (Map.Entry entry : factionClaims.entrySet()) { FLocation fLocation = entry.getKey(); - VChunk vChunk = new VChunk(fLocation.getWorldName(), (int) fLocation.getX(), (int) fLocation.getZ()); - villageClaims.add(vChunk); - claimsImported++; + int id = entry.getValue(); + if(id == factionId) { + VChunk vChunk = new VChunk(fLocation.getWorldName(), (int) fLocation.getX(), (int) fLocation.getZ()); + villageClaims.add(vChunk); + claimsImported++; + } } //Skip if no claims @@ -798,12 +801,20 @@ public boolean factionsImport() { //Faction Spawn Location factionSpawn = faction.getHome(); - - VChunk spawnChunk = new VChunk(factionSpawn); + VCords villageSpawn = null; + VChunk spawnChunk = null; + if(factionSpawn == null) { + log.warning("[" + pluginName + "] Faction " + newVillageName + " has no spawn. The world spawn will be used instead."); + spawnChunk = new VChunk(Bukkit.getWorlds().get(0).getSpawnLocation()); + villageSpawn = new VCords(Bukkit.getWorlds().get(0).getSpawnLocation()); + } else { + spawnChunk = new VChunk(factionSpawn); + villageSpawn = new VCords(factionSpawn); + } //Create Village - Village village = new Village(plugin, newVillageName, factionUUID, villageOwnerUUID, spawnChunk, new VCords(factionSpawn)); + Village village = new Village(plugin, newVillageName, factionUUID, villageOwnerUUID, spawnChunk, villageSpawn); factionsImported++; this.villageMap.addVillageToMap(village); //Register claims @@ -964,7 +975,7 @@ public UUID getUUIDFromUsername(String username) { } //Check if Fundamentals is enabled and if so, use it's cache - if(fundamentalsEnabled && Bukkit.getPluginManager().isPluginEnabled("Fundamentals")) { + if(uuid == null && fundamentalsEnabled && Bukkit.getPluginManager().isPluginEnabled("Fundamentals")) { Fundamentals fundamentals = (Fundamentals) Bukkit.getPluginManager().getPlugin("Fundamentals"); uuid = fundamentals.getPlayerCache().getUUIDFromUsername(username); } @@ -981,7 +992,7 @@ public String getUsernameFromUUID(UUID uuid) { } //Check if Fundamentals is enabled and if so, use it's cache - if(fundamentalsEnabled && Bukkit.getPluginManager().isPluginEnabled("Fundamentals")) { + if(uuid == null && fundamentalsEnabled && Bukkit.getPluginManager().isPluginEnabled("Fundamentals")) { Fundamentals fundamentals = (Fundamentals) Bukkit.getPluginManager().getPlugin("Fundamentals"); username = fundamentals.getPlayerCache().getUsernameFromUUID(uuid); }