diff --git a/build.gradle.kts b/build.gradle.kts index f9f7572fc..2e8c3c20f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -32,6 +32,7 @@ allprojects { maven("https://repo.aikar.co/content/groups/aikar/") maven("https://libraries.minecraft.net") maven("https://repo.codemc.io/repository/maven-public/") + maven("https://repo.kryptonmc.org/releases") maven("https://jitpack.io") } diff --git a/plugins/citadel-paper/src/main/java/vg/civcraft/mc/citadel/listener/EntityListener.java b/plugins/citadel-paper/src/main/java/vg/civcraft/mc/citadel/listener/EntityListener.java index 9fd429b2a..4a1fcabed 100644 --- a/plugins/citadel-paper/src/main/java/vg/civcraft/mc/citadel/listener/EntityListener.java +++ b/plugins/citadel-paper/src/main/java/vg/civcraft/mc/citadel/listener/EntityListener.java @@ -42,7 +42,6 @@ import vg.civcraft.mc.citadel.ReinforcementLogic; import vg.civcraft.mc.citadel.events.ReinforcementBypassEvent; import vg.civcraft.mc.citadel.model.Reinforcement; import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; -import vg.civcraft.mc.civmodcore.utilities.MoreClassUtils; import vg.civcraft.mc.namelayer.GroupManager; import vg.civcraft.mc.namelayer.NameAPI; import vg.civcraft.mc.namelayer.NameLayerPlugin; @@ -263,8 +262,7 @@ public class EntityListener implements Listener { case EXPLOSION: return; case ENTITY: { - Player player = MoreClassUtils.castOrNull(Player.class, event.getRemover()); - if (player == null) { + if (!(event.getRemover() instanceof final Player player)) { break; } Hanging entity = event.getEntity(); @@ -311,12 +309,10 @@ public class EntityListener implements Listener { if (!Citadel.getInstance().getConfigManager().doHangersInheritReinforcements()) { return; } - Hanging entity = MoreClassUtils.castOrNull(Hanging.class, event.getEntity()); - if (entity == null) { + if (!(event.getEntity() instanceof final Hanging entity)) { return; } - Player player = MoreClassUtils.castOrNull(Player.class, event.getDamager()); - if (player == null) { + if (!(event.getDamager() instanceof final Player player)) { event.setCancelled(true); return; } @@ -344,8 +340,7 @@ public class EntityListener implements Listener { if (!Citadel.getInstance().getConfigManager().doHangersInheritReinforcements()) { return; } - Hanging entity = MoreClassUtils.castOrNull(Hanging.class, event.getRightClicked()); - if (entity == null) { + if (!(event.getRightClicked() instanceof final Hanging entity)) { return; } Block host = entity.getLocation().getBlock().getRelative(entity.getAttachedFace()); diff --git a/plugins/civduties-paper/LICENSE.txt b/plugins/civduties-paper/LICENSE.txt new file mode 100644 index 000000000..6ec5e6581 --- /dev/null +++ b/plugins/civduties-paper/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) <2015> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/plugins/civduties-paper/README.md b/plugins/civduties-paper/README.md new file mode 100644 index 000000000..b8d818f9e --- /dev/null +++ b/plugins/civduties-paper/README.md @@ -0,0 +1,4 @@ +# CivDuties +Minecraft plugin built for Paper 1.16.5 + +Custom plugin used to assign a limited range of moderation permissions for players. Used on CivClassic as a tool to allow designated player-moderators to use /duty to enter a mode which allows them to teleport and move around in spectator mode to investigate hacking. Also allows them to run commands such as kicking or banning while in /duty mode. diff --git a/plugins/civduties-paper/build.gradle.kts b/plugins/civduties-paper/build.gradle.kts new file mode 100644 index 000000000..bea21bca5 --- /dev/null +++ b/plugins/civduties-paper/build.gradle.kts @@ -0,0 +1,13 @@ +plugins { + id("io.papermc.paperweight.userdev") +} + +version = "1.5.0-SNAPSHOT" + +dependencies { + paperDevBundle("1.18.2-R0.1-SNAPSHOT") + + compileOnly(project(":plugins:civmodcore-paper")) + compileOnly("com.github.MilkBowl:VaultAPI:1.7") + compileOnly(project(":plugins:combattagplus-paper")) +} \ No newline at end of file diff --git a/plugins/civduties-paper/src/main/java/vg/civcraft/mc/civduties/CivDuties.java b/plugins/civduties-paper/src/main/java/vg/civcraft/mc/civduties/CivDuties.java new file mode 100644 index 000000000..c126a43da --- /dev/null +++ b/plugins/civduties-paper/src/main/java/vg/civcraft/mc/civduties/CivDuties.java @@ -0,0 +1,96 @@ +package vg.civcraft.mc.civduties; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import vg.civcraft.mc.civduties.command.CivDutiesCommandHandler; +import vg.civcraft.mc.civduties.configuration.Command; +import vg.civcraft.mc.civduties.configuration.Command.Timing; +import vg.civcraft.mc.civduties.configuration.DutiesConfigManager; +import vg.civcraft.mc.civduties.configuration.Tier; +import vg.civcraft.mc.civduties.database.DatabaseManager; +import vg.civcraft.mc.civduties.external.CombatTagHandler; +import vg.civcraft.mc.civduties.external.VaultManager; +import vg.civcraft.mc.civduties.listeners.PlayerListener; +import vg.civcraft.mc.civmodcore.ACivMod; + +public class CivDuties extends ACivMod { + private static CivDuties pluginInstance; + private DutiesConfigManager config; + private DatabaseManager db; + private ModeManager modeManager; + private VaultManager vaultManager; + private CivDutiesCommandHandler commandHandler; + + public CivDuties() { + pluginInstance = this; + } + + @Override + public void onEnable() { + super.onEnable(); + config = new DutiesConfigManager(this); + config.parse(); + if (config.getDatabase() == null) { + getLogger().severe("Invalid database credentials, shutting down"); + Bukkit.getPluginManager().disablePlugin(this); + return; + } + db = new DatabaseManager(config.getDatabase()); + vaultManager = new VaultManager(); + modeManager = new ModeManager(); + commandHandler = new CivDutiesCommandHandler(this); + registerListeners(); + } + + @Override + public void onDisable() { + for(Player player : Bukkit.getOnlinePlayers()){ + if(modeManager.isInDuty(player)){ + Tier tier = config.getTier(db.getPlayerData(player.getUniqueId()).getTierName()); + for(Command command: tier.getCommands()){ + if(command.getTiming() == Timing.LOGOUT){ + command.execute(player); + } + } + vaultManager.addPermissionsToPlayer(player, tier.getTemporaryPermissions()); + vaultManager.addPlayerToGroups(player, tier.getTemporaryGroups()); + } + } + } + + public static CivDuties getInstance() { + return pluginInstance; + } + + public DutiesConfigManager getConfigManager() { + return config; + } + + public DatabaseManager getDatabaseManager() { + return db; + } + + public ModeManager getModeManager() { + return modeManager; + } + + public VaultManager getVaultManager() { + return vaultManager; + } + + public boolean isVaultEnabled() { + return Bukkit.getPluginManager().isPluginEnabled("Vault"); + } + + public boolean isCombatTagPlusEnabled() { + return Bukkit.getPluginManager().isPluginEnabled("CombatTagPlus"); + } + + + private void registerListeners() { + getServer().getPluginManager().registerEvents(new PlayerListener(), this); + if (isCombatTagPlusEnabled()) { + getServer().getPluginManager().registerEvents(new CombatTagHandler(), this); + } + } +} diff --git a/plugins/civduties-paper/src/main/java/vg/civcraft/mc/civduties/ModeManager.java b/plugins/civduties-paper/src/main/java/vg/civcraft/mc/civduties/ModeManager.java new file mode 100644 index 000000000..dd6c1cf38 --- /dev/null +++ b/plugins/civduties-paper/src/main/java/vg/civcraft/mc/civduties/ModeManager.java @@ -0,0 +1,115 @@ +package vg.civcraft.mc.civduties; + +import java.util.UUID; +import java.util.logging.Level; +import java.util.logging.Logger; +import net.minecraft.nbt.CompoundTag; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.GameMode; +import org.bukkit.Location; +import org.bukkit.craftbukkit.v1_18_R2.entity.CraftPlayer; +import org.bukkit.entity.Player; +import vg.civcraft.mc.civduties.configuration.Command; +import vg.civcraft.mc.civduties.configuration.Command.Timing; +import vg.civcraft.mc.civduties.configuration.Tier; +import vg.civcraft.mc.civduties.database.DatabaseManager; +import vg.civcraft.mc.civduties.external.VaultManager; +import vg.civcraft.mc.civmodcore.nbt.wrappers.NBTCompound; + +public class ModeManager { + private DatabaseManager db; + private VaultManager vaultManager; + private Logger logger; + + public ModeManager() { + db = CivDuties.getInstance().getDatabaseManager(); + vaultManager = CivDuties.getInstance().getVaultManager(); + logger = CivDuties.getInstance().getLogger(); + } + + public boolean isInDuty(UUID uuid) { + if (db.getPlayerData(uuid) != null) { + return true; + } + return false; + } + + public boolean isInDuty(Player player) { + return isInDuty(player.getUniqueId()); + } + + public boolean enableDutyMode(Player player, Tier tier) { + CompoundTag nmsCompound = new CompoundTag(); + CraftPlayer cPlayer = (CraftPlayer) player; + cPlayer.getHandle().saveWithoutId(nmsCompound); + NBTCompound compound = new NBTCompound(nmsCompound); + String serverName = Bukkit.getServer().getName(); + db.savePlayerData(player.getUniqueId(), compound.getRAW(), serverName, tier.getName()); + + vaultManager.addPermissionsToPlayer(player, tier.getTemporaryPermissions()); + vaultManager.addPlayerToGroups(player, tier.getTemporaryGroups()); + + for (Command command : tier.getCommands()) { + if (command.getTiming() == Timing.ENABLE) { + command.execute(player); + } + } + + player.sendMessage(ChatColor.RED + "You have entered duty mode. Type /duty to leave it"); + logger.log(Level.INFO, "player " + player.getName() + " has entered duty mode"); + return true; + } + + public boolean disableDutyMode(Player player, Tier tier) { + if (!isInDuty(player)) { + return false; + } + NBTCompound input = new NBTCompound(db.getPlayerData(player.getUniqueId()).getData()); + // Inform the client the gamemode was changed to fix graphical issues on the + // client side + // Teleport the players using the bukkit api to avoid triggering nocheat + // movement detection + double[] location = input.getDoubleArray("Pos"); + UUID worldUUID = new UUID(input.getLong("WorldUUIDMost"), input.getLong("WorldUUIDLeast")); + Location targetLocation = new Location(Bukkit.getWorld(worldUUID), location[0], location[1], location[2]); + player.teleport(targetLocation); + player.setGameMode(getGameModeByValue(input.getInt("playerGameType"))); + Bukkit.getScheduler().scheduleSyncDelayedTask(CivDuties.getInstance(), () -> { + player.teleport(targetLocation); + }, 3L); + CraftPlayer cPlayer = (CraftPlayer) player; + cPlayer.getHandle().load(input.getRAW()); + + db.removePlayerData(player.getUniqueId()); + + for (Command command : tier.getCommands()) { + if (command.getTiming() == Timing.DISABLE) { + command.execute(player); + } + } + + vaultManager.removePermissionsFromPlayer(player, tier.getTemporaryPermissions()); + vaultManager.removePlayerFromGroups(player, tier.getTemporaryGroups()); + + player.sendMessage(ChatColor.RED + "You have left duty mode"); + logger.log(Level.INFO, "player " + player.getName() + " has left duty mode"); + return true; + } + + private GameMode getGameModeByValue(int num) { + switch (num) { + case 0: + return GameMode.SURVIVAL; + case 1: + return GameMode.CREATIVE; + case 2: + return GameMode.ADVENTURE; + case 3: + return GameMode.SPECTATOR; + default: + return null; + } + } + +} diff --git a/plugins/civduties-paper/src/main/java/vg/civcraft/mc/civduties/command/CivDutiesCommandHandler.java b/plugins/civduties-paper/src/main/java/vg/civcraft/mc/civduties/command/CivDutiesCommandHandler.java new file mode 100644 index 000000000..eda462b6c --- /dev/null +++ b/plugins/civduties-paper/src/main/java/vg/civcraft/mc/civduties/command/CivDutiesCommandHandler.java @@ -0,0 +1,19 @@ +package vg.civcraft.mc.civduties.command; + +import org.bukkit.plugin.Plugin; +import vg.civcraft.mc.civduties.command.commands.Duty; +import vg.civcraft.mc.civmodcore.commands.CommandManager; + +public class CivDutiesCommandHandler extends CommandManager { + + public CivDutiesCommandHandler(Plugin plugin) { + super(plugin); + init(); + } + + @Override + public void registerCommands() { + registerCommand(new Duty()); + } + +} diff --git a/plugins/civduties-paper/src/main/java/vg/civcraft/mc/civduties/command/commands/Duty.java b/plugins/civduties-paper/src/main/java/vg/civcraft/mc/civduties/command/commands/Duty.java new file mode 100644 index 000000000..bc95ba77b --- /dev/null +++ b/plugins/civduties-paper/src/main/java/vg/civcraft/mc/civduties/command/commands/Duty.java @@ -0,0 +1,71 @@ +package vg.civcraft.mc.civduties.command.commands; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Optional; +import co.aikar.commands.annotation.Syntax; +import java.util.List; +import net.minelink.ctplus.CombatTagPlus; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import vg.civcraft.mc.civduties.CivDuties; +import vg.civcraft.mc.civduties.ModeManager; +import vg.civcraft.mc.civduties.configuration.Tier; + +public class Duty extends BaseCommand { + private ModeManager modeManager = CivDuties.getInstance().getModeManager(); + + @CommandAlias("duty") + @Syntax("[player]") + @Description("Allows you to enter duty mode") + @CommandPermission("civduties.duty") + public void execute(Player player, @Optional String targetPlayer) { + Tier tier = null; + + if (!modeManager.isInDuty(player)) { + if (targetPlayer == null || targetPlayer.isEmpty()) { + tier = CivDuties.getInstance().getConfigManager().getTier(player); + } else { + tier = CivDuties.getInstance().getConfigManager().getTier(targetPlayer); + if (!player.hasPermission(tier.getPermission())) { + tier = null; + } + } + + if (tier == null) { + player.sendMessage("You don't have permission to execute this command."); + return; + } + + if (CivDuties.getInstance().isCombatTagPlusEnabled() && tier.isCombattagBlock() + && ((CombatTagPlus) Bukkit.getPluginManager().getPlugin("CombatTagPlus")).getTagManager() + .isTagged(player.getUniqueId())) { + player.sendMessage("You can't enter duty mode while combat tagged"); + return; + } + modeManager.enableDutyMode(player, tier); + } else { + String tierName = CivDuties.getInstance().getDatabaseManager().getPlayerData(player.getUniqueId()) + .getTierName(); + tier = CivDuties.getInstance().getConfigManager().getTier(tierName); + modeManager.disableDutyMode(player, tier); + } + } + + public List tabComplete(CommandSender sender, String[] args) { + if (!(sender instanceof Player)) { + sender.sendMessage("No."); + return null; + } + + if (args.length < 2) { + return CivDuties.getInstance().getConfigManager().getTiersNames((Player)sender); + } + + return null; + } + +} diff --git a/plugins/civduties-paper/src/main/java/vg/civcraft/mc/civduties/configuration/Command.java b/plugins/civduties-paper/src/main/java/vg/civcraft/mc/civduties/configuration/Command.java new file mode 100644 index 000000000..37e196f5a --- /dev/null +++ b/plugins/civduties-paper/src/main/java/vg/civcraft/mc/civduties/configuration/Command.java @@ -0,0 +1,65 @@ +package vg.civcraft.mc.civduties.configuration; + +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +public class Command { + private String syntax; + private Timing timing; + private Executor executor; + + public enum Timing{ + ENABLE, + DISABLE, + LOGIN, + LOGOUT; + } + + public enum Executor{ + PLAYER, + CONSOLE; + } + + public Command(String syntax, Timing timing, Executor executor) { + this.syntax = syntax; + this.timing = timing; + this.executor = executor; + } + + public String getSyntax() { + return syntax; + } + + public void setSyntax(String syntax) { + this.syntax = syntax; + } + + public Timing getTiming() { + return timing; + } + + public void setTiming(Timing timing) { + this.timing = timing; + } + + public Executor getExecutor() { + return executor; + } + + public void setExecutor(Executor executor) { + this.executor = executor; + } + + public void execute(Player player){ + String parsedCommand = (syntax.charAt(0) == '/' ? syntax.substring(1) : syntax) + .replaceAll("%PLAYER_NAME%", player.getName()) + .replaceAll("%PLAYER_GAMEMODE%", player.getGameMode().toString()) + .replaceAll("%PLAYER_SERVER%", Bukkit.getServer().getName()); + CommandSender sender = player; + if(executor == Executor.CONSOLE){ + sender = Bukkit.getConsoleSender(); + } + Bukkit.dispatchCommand(sender, parsedCommand); + } +} diff --git a/plugins/civduties-paper/src/main/java/vg/civcraft/mc/civduties/configuration/DutiesConfigManager.java b/plugins/civduties-paper/src/main/java/vg/civcraft/mc/civduties/configuration/DutiesConfigManager.java new file mode 100644 index 000000000..a4c0330ba --- /dev/null +++ b/plugins/civduties-paper/src/main/java/vg/civcraft/mc/civduties/configuration/DutiesConfigManager.java @@ -0,0 +1,129 @@ +package vg.civcraft.mc.civduties.configuration; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.entity.Player; +import vg.civcraft.mc.civduties.CivDuties; +import vg.civcraft.mc.civduties.configuration.Command.Executor; +import vg.civcraft.mc.civduties.configuration.Command.Timing; +import vg.civcraft.mc.civmodcore.ACivMod; +import vg.civcraft.mc.civmodcore.config.ConfigParser; +import vg.civcraft.mc.civmodcore.dao.DatabaseCredentials; +import vg.civcraft.mc.civmodcore.dao.ManagedDatasource; + +public class DutiesConfigManager extends ConfigParser { + private ManagedDatasource db; + private List tiers; + + public DutiesConfigManager(CivDuties plugin){ + super(plugin); + } + + private void parseTiers(ConfigurationSection config){ + tiers = new ArrayList<>(); + for (String key : config.getKeys(false)) { + if (config.getConfigurationSection(key) == null) { + CivDuties.getInstance().warning("Found invalid section that should not exist at " + config.getCurrentPath() + key); + continue; + } + Tier tier = parseTier(key, config.getConfigurationSection(key)); + if (tier == null) { + CivDuties.getInstance().warning(String.format("Tier %s unable to be added.", key)); + } else { + tiers.add(tier); + } + } + } + + private Tier parseTier(String name, ConfigurationSection config){ + String permission = config.getString("permission"); + int priority = config.getInt("priority"); + List commands = parseCommands(config.getConfigurationSection("commands")); + Map temporaryPermissions = new HashMap<>(); + for(String temporaryPermission : config.getStringList("temporary.permissions")){ + String[] array = temporaryPermission.split(":"); + if(array.length > 1){ + temporaryPermissions.put(array[0], Boolean.valueOf(array[1])); + continue; + } + temporaryPermissions.put(array[0], true); + } + List temporaryGroups = config.getStringList("temporary.groups"); + boolean deathDrops = config.getBoolean("disable_death_drops"); + boolean combattagBlock = config.getBoolean("enable_combattag_block"); + return new Tier(name, priority, permission, commands, temporaryPermissions, temporaryGroups, deathDrops, combattagBlock); + } + + private List parseCommands(ConfigurationSection config){ + if(config == null){ + return new ArrayList<>(); + } + List commands = new ArrayList<>(); + for (String key : config.getKeys(false)) { + if (config.getConfigurationSection(key) == null) { + CivDuties.getInstance().warning("Found invalid section that should not exist at " + config.getCurrentPath() + key); + continue; + } + Command command = parseCommand(config.getConfigurationSection(key)); + if (command == null) { + CivDuties.getInstance().warning(String.format("Tier %s unable to be added.", key)); + } else { + commands.add(command); + } + } + return commands; + } + + private Command parseCommand(ConfigurationSection config){ + String syntax = config.getString("syntax"); + Timing timing = Timing.valueOf(config.getString("timing")); + Executor executor = Executor.valueOf(config.getString("executor")); + return new Command(syntax, timing, executor); + } + + public Tier getTier(Player player){ + Tier tier = null; + int maxPriority = Integer.MIN_VALUE; + for(Tier t : tiers){ + if((t.getPermission() == null || player.hasPermission(t.getPermission())) && (tier == null || t.getPriority() > maxPriority)){ + tier = t; + maxPriority = t.getPriority(); + } + } + return tier; + } + + public Tier getTier(String tierName){ + for(Tier tier : tiers){ + if(tier.getName().equals(tierName)){ + return tier; + } + } + return null; + } + + public List getTiersNames(Player player){ + List names = new ArrayList<>(); + for(Tier tier : tiers){ + if(tier.getPermission() == null || player.hasPermission(tier.getPermission())){ + names.add(tier.getName()); + } + } + return names; + } + + public ManagedDatasource getDatabase(){ + return db; + } + + @Override + protected boolean parseInternal(ConfigurationSection config) { + parseTiers(config.getConfigurationSection("tiers")); + db = ManagedDatasource.construct((ACivMod) plugin, (DatabaseCredentials) config.get("database")); + return true; + } + +} diff --git a/plugins/civduties-paper/src/main/java/vg/civcraft/mc/civduties/configuration/Tier.java b/plugins/civduties-paper/src/main/java/vg/civcraft/mc/civduties/configuration/Tier.java new file mode 100644 index 000000000..a36e66b33 --- /dev/null +++ b/plugins/civduties-paper/src/main/java/vg/civcraft/mc/civduties/configuration/Tier.java @@ -0,0 +1,60 @@ +package vg.civcraft.mc.civduties.configuration; + +import java.util.List; +import java.util.Map; + +public class Tier { + private String name; + private String permission; + private int priority; + private List commands; + private Map temporaryPermissions; + private List temporaryGroups; + private boolean deathDrops; + private boolean combattagBlock; + + public Tier(String name, int priority, String permission, List commands, + Map temporaryPermissions, List temporaryGroups, boolean deathDrops, + boolean combattagBlock) { + this.name = name; + this.priority = priority; + this.permission = permission; + this.commands = commands; + this.temporaryPermissions = temporaryPermissions; + this.temporaryGroups = temporaryGroups; + this.deathDrops = deathDrops; + this.combattagBlock = combattagBlock; + } + + public String getName() { + return name; + } + + public int getPriority() { + return priority; + } + + public String getPermission() { + return permission; + } + + public List getCommands() { + return commands; + } + + public Map getTemporaryPermissions() { + return temporaryPermissions; + } + + public List getTemporaryGroups() { + return temporaryGroups; + } + + public boolean isDeathDrops() { + return deathDrops; + } + + public boolean isCombattagBlock() { + return combattagBlock; + } +} diff --git a/plugins/civduties-paper/src/main/java/vg/civcraft/mc/civduties/database/DatabaseManager.java b/plugins/civduties-paper/src/main/java/vg/civcraft/mc/civduties/database/DatabaseManager.java new file mode 100644 index 000000000..b755113a1 --- /dev/null +++ b/plugins/civduties-paper/src/main/java/vg/civcraft/mc/civduties/database/DatabaseManager.java @@ -0,0 +1,84 @@ +package vg.civcraft.mc.civduties.database; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +import net.minecraft.nbt.CompoundTag; +import vg.civcraft.mc.civduties.CivDuties; +import vg.civcraft.mc.civmodcore.dao.ManagedDatasource; +import vg.civcraft.mc.civmodcore.nbt.NBTSerialization; + +public class DatabaseManager { + private CivDuties plugin; + private ManagedDatasource db; + + private Map playersDataCache = new ConcurrentHashMap<>(); + + public DatabaseManager(ManagedDatasource db) { + this.plugin = CivDuties.getInstance(); + this.db = db; + registerMigrations(); + db.updateDatabase(); + } + + private void registerMigrations() { + db.registerMigration(1, false, + "create table if not exists DutiesPlayerData(uuid varchar(36) not null,entity blob, " + + "serverName varchar(256) not null, tierName varchar(256) not null, primary key (uuid));"); + } + + public void savePlayerData(UUID uuid, CompoundTag compound, String serverName, String tierName) { + try (Connection conn = db.getConnection(); + PreparedStatement addPlayerData = conn.prepareStatement( + "insert into DutiesPlayerData(uuid, entity, serverName, tierName) values(?,?,?,?);")) { + addPlayerData.setString(1, uuid.toString()); + addPlayerData.setBytes(2, NBTSerialization.toBytes(compound)); + addPlayerData.setString(3, serverName); + addPlayerData.setString(4, tierName); + addPlayerData.execute(); + } catch (SQLException e) { + plugin.getLogger().severe("Failed to insert player data " + e.toString()); + } + } + + public PlayerData getPlayerData(UUID uuid) { + if (playersDataCache.containsKey(uuid)) { + return playersDataCache.get(uuid); + } + try (Connection conn = db.getConnection(); + PreparedStatement getPlayerData = conn + .prepareStatement("select * from DutiesPlayerData where uuid = ?")) { + getPlayerData.setString(1, uuid.toString()); + try (ResultSet rs = getPlayerData.executeQuery()) { + if (rs.next()) { + CompoundTag compound = NBTSerialization.fromBytes(rs.getBytes("entity")); + String server = rs.getString("serverName"); + String tierName = rs.getString("tierName"); + PlayerData data = new PlayerData(compound, server, tierName); + playersDataCache.put(uuid, data); + return data; + } + } + } catch (SQLException e) { + plugin.getLogger().severe("Failed to retrieve player data " + e.toString()); + } + return null; + } + + public void removePlayerData(UUID uuid) { + playersDataCache.remove(uuid); + try (Connection conn = db.getConnection(); + PreparedStatement removePlayerData = conn + .prepareStatement("delete from DutiesPlayerData where uuid = ?")) { + removePlayerData.setString(1, uuid.toString()); + removePlayerData.execute(); + } catch (SQLException e) { + plugin.getLogger().severe("Failed to remove player data " + e.toString()); + } + } +} diff --git a/plugins/civduties-paper/src/main/java/vg/civcraft/mc/civduties/database/PlayerData.java b/plugins/civduties-paper/src/main/java/vg/civcraft/mc/civduties/database/PlayerData.java new file mode 100644 index 000000000..337af697f --- /dev/null +++ b/plugins/civduties-paper/src/main/java/vg/civcraft/mc/civduties/database/PlayerData.java @@ -0,0 +1,27 @@ +package vg.civcraft.mc.civduties.database; + +import net.minecraft.nbt.CompoundTag; + +public class PlayerData { + private CompoundTag data; + private String serverName; + private String tierName; + + public PlayerData(CompoundTag data, String serverName, String tierName) { + this.data = data; + this.serverName = serverName; + this.tierName = tierName; + } + + public CompoundTag getData() { + return data; + } + + public String getServerName() { + return serverName; + } + + public String getTierName() { + return tierName; + } +} diff --git a/plugins/civduties-paper/src/main/java/vg/civcraft/mc/civduties/external/CombatTagHandler.java b/plugins/civduties-paper/src/main/java/vg/civcraft/mc/civduties/external/CombatTagHandler.java new file mode 100644 index 000000000..768d2e96c --- /dev/null +++ b/plugins/civduties-paper/src/main/java/vg/civcraft/mc/civduties/external/CombatTagHandler.java @@ -0,0 +1,18 @@ +package vg.civcraft.mc.civduties.external; + +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; + +import net.minelink.ctplus.event.CombatLogEvent; +import vg.civcraft.mc.civduties.CivDuties; + +public class CombatTagHandler implements Listener { + + @EventHandler + public void disableCombatTag(CombatLogEvent event) { + if (CivDuties.getInstance().getModeManager().isInDuty(event.getPlayer())) { + event.setCancelled(true); + } + } + +} diff --git a/plugins/civduties-paper/src/main/java/vg/civcraft/mc/civduties/external/VaultManager.java b/plugins/civduties-paper/src/main/java/vg/civcraft/mc/civduties/external/VaultManager.java new file mode 100644 index 000000000..549e9c252 --- /dev/null +++ b/plugins/civduties-paper/src/main/java/vg/civcraft/mc/civduties/external/VaultManager.java @@ -0,0 +1,85 @@ +package vg.civcraft.mc.civduties.external; + +import java.util.List; +import java.util.Map; +import java.util.logging.Level; + +import org.bukkit.entity.Player; +import org.bukkit.plugin.RegisteredServiceProvider; + +import net.milkbowl.vault.permission.Permission; +import vg.civcraft.mc.civduties.CivDuties; + +public class VaultManager { + + private static Permission permissionProvider = null; + + public VaultManager() { + if (CivDuties.getInstance().isVaultEnabled()) { + setupPermissions(); + } + } + + private boolean setupPermissions() { + RegisteredServiceProvider permissionProvider = CivDuties.getInstance().getServer() + .getServicesManager().getRegistration(net.milkbowl.vault.permission.Permission.class); + if (permissionProvider != null) { + VaultManager.permissionProvider = permissionProvider.getProvider(); + return true; + } + CivDuties.getInstance().getLogger().log(Level.WARNING, "Duties was unable to find a permissions plugin"); + return false; + } + + public boolean addPermissionsToPlayer(Player player, Map permissions) { + if (permissionProvider == null) { + return false; + } + + for (Map.Entry entry: permissions.entrySet()) { + if(entry.getValue()){ + permissionProvider.playerAdd(player, entry.getKey()); + return true; + } + permissionProvider.playerRemove(player, entry.getKey()); + } + return true; + } + + public boolean removePermissionsFromPlayer(Player player, Map permissions) { + if (permissionProvider == null) { + return false; + } + + for (Map.Entry entry: permissions.entrySet()) { + if(!entry.getValue()){ + permissionProvider.playerAdd(player, entry.getKey()); + return true; + } + permissionProvider.playerRemove(player, entry.getKey()); + } + return true; + } + + public boolean addPlayerToGroups(Player player, List groups){ + if (permissionProvider == null) { + return false; + } + + for (String group : groups) { + permissionProvider.playerAddGroup(player, group); + } + return true; + } + + public boolean removePlayerFromGroups(Player player, List groups){ + if (permissionProvider == null) { + return false; + } + + for (String group : groups) { + permissionProvider.playerRemoveGroup(player, group); + } + return true; + } +} diff --git a/plugins/civduties-paper/src/main/java/vg/civcraft/mc/civduties/listeners/PlayerListener.java b/plugins/civduties-paper/src/main/java/vg/civcraft/mc/civduties/listeners/PlayerListener.java new file mode 100644 index 000000000..0c172b7f6 --- /dev/null +++ b/plugins/civduties-paper/src/main/java/vg/civcraft/mc/civduties/listeners/PlayerListener.java @@ -0,0 +1,74 @@ +package vg.civcraft.mc.civduties.listeners; + +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; + +import vg.civcraft.mc.civduties.CivDuties; +import vg.civcraft.mc.civduties.ModeManager; +import vg.civcraft.mc.civduties.configuration.Command; +import vg.civcraft.mc.civduties.configuration.Command.Timing; +import vg.civcraft.mc.civduties.configuration.DutiesConfigManager; +import vg.civcraft.mc.civduties.configuration.Tier; +import vg.civcraft.mc.civduties.database.DatabaseManager; +import vg.civcraft.mc.civduties.external.VaultManager; + +public class PlayerListener implements Listener { + private ModeManager modeManager; + private DatabaseManager db; + private DutiesConfigManager config; + private VaultManager vaultManager; + + public PlayerListener() { + this.modeManager = CivDuties.getInstance().getModeManager(); + this.db = CivDuties.getInstance().getDatabaseManager(); + this.config = CivDuties.getInstance().getConfigManager(); + this.vaultManager = CivDuties.getInstance().getVaultManager(); + } + + @EventHandler(priority = EventPriority.LOWEST) + public void playerQuitEvent(PlayerQuitEvent event) { + Player player = event.getPlayer(); + if(modeManager.isInDuty(player)){ + Tier tier = config.getTier(db.getPlayerData(player.getUniqueId()).getTierName()); + vaultManager.removePermissionsFromPlayer(player, tier.getTemporaryPermissions()); + vaultManager.removePlayerFromGroups(player, tier.getTemporaryGroups()); + for(Command command: tier.getCommands()){ + if(command.getTiming() == Timing.LOGOUT){ + command.execute(player); + } + } + } + } + + @EventHandler(priority = EventPriority.LOWEST) + public void playerJoinEvent(PlayerJoinEvent event) { + Player player = event.getPlayer(); + if(modeManager.isInDuty(player)){ + Tier tier = config.getTier(db.getPlayerData(player.getUniqueId()).getTierName()); + for(Command command: tier.getCommands()){ + if(command.getTiming() == Timing.LOGIN){ + command.execute(player); + } + } + vaultManager.addPermissionsToPlayer(player, tier.getTemporaryPermissions()); + vaultManager.addPlayerToGroups(player, tier.getTemporaryGroups()); + } + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onEntityDeath(EntityDeathEvent event) { + if (event.getEntity() instanceof Player && modeManager.isInDuty(((Player) event.getEntity()))) { + Player player = (Player) event.getEntity(); + Tier tier = config.getTier(db.getPlayerData(player.getUniqueId()).getTierName()); + if(tier.isDeathDrops()){ + event.getDrops().clear(); + event.setDroppedExp(0); + } + } + } +} diff --git a/plugins/civduties-paper/src/main/resources/config.yml b/plugins/civduties-paper/src/main/resources/config.yml new file mode 100644 index 000000000..fefd02354 --- /dev/null +++ b/plugins/civduties-paper/src/main/resources/config.yml @@ -0,0 +1,49 @@ +#An example for a tier utilizing all the options available + +#name: +# permission: civduties.admin +# priority: 1 +# commands: +# commandidentifier1: +# syntax: gamemode 1 +# timing: ENABLE +# executor: PLAYER +# commandidentifier2: +# syntax: /deop %PLAYER% +# timing: DISABLE +# executor: CONSOLE +# commandidentifier3: +# syntax: say A player in duty mode logged in +# timing: LOGIN +# executor: CONSOLE +# temporary: +# permissions: +# - bukkit.ban +# - bukkit.unban +# groups: +# - admins +# disable_death_drops: true +# enable_combattag_block: true + +#permission specifies the permission required the player to have to be able to use the tier. +#priority if a player has permissions to more than one tier his default tier will be the tier with the highest priority. +#commands allows to run commands. +# syntax the command to run. +# timing when to run the command. Can be ENABLE, DISABLE, LOGIN or LOGOUT. +# executor who will run the command. Can be the PLAYER or the CONSOLE. A player can only run commands he has permission to. +#temporary specifies permissions the player will have or permissions groups the player will be add to while in duty. +#disable_death_drops allows to disable drops from a player that dies in duty mode. +#enable_combattag_block prevent the player from entering duty mode while combattaged. Uses CombatTagPlus: https://github.com/MinelinkNetwork/CombatTagPlus + +tiers: + default: + priority: 0 + disable_death_drops: true + enable_combattag_block: true + +mysql: + hostname: 'localhost' + port: 3306 + dbname: 'bukkit' + username: 'bukkit' + password: '' diff --git a/plugins/civduties-paper/src/main/resources/plugin.yml b/plugins/civduties-paper/src/main/resources/plugin.yml new file mode 100644 index 000000000..d38133caf --- /dev/null +++ b/plugins/civduties-paper/src/main/resources/plugin.yml @@ -0,0 +1,12 @@ +name: CivDuties +main: vg.civcraft.mc.civduties.CivDuties +version: ${version} +depend: [CivModCore] +softdepend: [Vault] +api-version: 1.16 +commands: + duty: + permission: civduties.duty +permissions: + civduties.duty: + default: op diff --git a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/utilities/MoreClassUtils.java b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/utilities/MoreClassUtils.java deleted file mode 100644 index d46ab8ee4..000000000 --- a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/utilities/MoreClassUtils.java +++ /dev/null @@ -1,24 +0,0 @@ -package vg.civcraft.mc.civmodcore.utilities; - -public final class MoreClassUtils { - - /** - * Checks whether a value can be cast to a particular type. - * - * @param The type to cast to. - * @param clazz The class of the type. - * @param value The value to attempt to cast. - * @return Returns the value cast to the given type, nor null. - */ - @SuppressWarnings("unchecked") - public static T castOrNull(final Class clazz, final Object value) { - if (clazz == null || value == null) { - return null; - } - if (clazz.isAssignableFrom(value.getClass())) { - return (T) value; - } - return null; - } - -} diff --git a/plugins/essenceglue-paper/.editorconfig b/plugins/essenceglue-paper/.editorconfig new file mode 100644 index 000000000..bbdd807d0 --- /dev/null +++ b/plugins/essenceglue-paper/.editorconfig @@ -0,0 +1,21 @@ +# Editorconfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +charset = utf-8 +indent_style = tab +end_of_line = lf +insert_final_newline = true + +[*.java] +indent_style = tab + +[*.{xml, yml}] +indent_style = space +indent_size = 2 + +[*.csv] +insert_final_newline = false diff --git a/plugins/essenceglue-paper/LICENSE.txt b/plugins/essenceglue-paper/LICENSE.txt new file mode 100644 index 000000000..dd1a53647 --- /dev/null +++ b/plugins/essenceglue-paper/LICENSE.txt @@ -0,0 +1,617 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. diff --git a/plugins/essenceglue-paper/README.md b/plugins/essenceglue-paper/README.md new file mode 100644 index 000000000..c109628d9 --- /dev/null +++ b/plugins/essenceglue-paper/README.md @@ -0,0 +1,6 @@ +# EssenceGlue +Daily login rewards and voting rewards. Minecraft Plugin built for Paper 1.16.5 + +## Description + +Allows configurable daily login or voting rewards. Allows configuration of an amount of time to be logged in before it will give the daily login reward. Also allows configuration for login streak rewards to increase the reward each consecutive day you login. Operates on a bitwise function to determine your reward. Thus if you login for 7 days in a row, receiving a total of 8 login reward (essence), and then you miss a day, you will drop to 7 essence a day until you have once again reached a total of 7 consecutive days of claiming the reward. diff --git a/plugins/essenceglue-paper/build.gradle.kts b/plugins/essenceglue-paper/build.gradle.kts new file mode 100644 index 000000000..62550db47 --- /dev/null +++ b/plugins/essenceglue-paper/build.gradle.kts @@ -0,0 +1,15 @@ +plugins { + id("io.papermc.paperweight.userdev") +} + +version = "2.0.0-SNAPSHOT" + +dependencies { + paperDevBundle("1.18.2-R0.1-SNAPSHOT") + + compileOnly(project(":plugins:civmodcore-paper")) + compileOnly("com.github.NuVotifier.NuVotifier:nuvotifier-bukkit:2.7.2") + compileOnly("com.github.NuVotifier.NuVotifier:nuvotifier-api:2.7.2") + compileOnly(project(":plugins:banstick-paper")) + compileOnly(project(":plugins:exilepearl-paper")) +} diff --git a/plugins/essenceglue-paper/src/main/java/com/github/maxopoly/essenceglue/EssenceConfigManager.java b/plugins/essenceglue-paper/src/main/java/com/github/maxopoly/essenceglue/EssenceConfigManager.java new file mode 100644 index 000000000..fd02aa57d --- /dev/null +++ b/plugins/essenceglue-paper/src/main/java/com/github/maxopoly/essenceglue/EssenceConfigManager.java @@ -0,0 +1,102 @@ +package com.github.maxopoly.essenceglue; + +import java.util.HashMap; +import java.util.Map; +import org.bukkit.configuration.ConfigurationSection; +import vg.civcraft.mc.civmodcore.ACivMod; +import vg.civcraft.mc.civmodcore.config.ConfigHelper; +import vg.civcraft.mc.civmodcore.config.ConfigParser; +import vg.civcraft.mc.civmodcore.dao.DatabaseCredentials; +import vg.civcraft.mc.civmodcore.dao.ManagedDatasource; +import vg.civcraft.mc.civmodcore.inventory.items.ItemMap; + +public class EssenceConfigManager extends ConfigParser { + + private ManagedDatasource db; + private int maxStreak; + private long streakDelay; + private long streakGracePeriod; + private long timeForGain; + private ItemMap loginReward; + private ItemMap votingReward; + private Map votingCooldowns; + private boolean giveRewardToPearled; + private boolean multiplyPearlCost; + + public EssenceConfigManager(ACivMod plugin) { + super(plugin); + } + + @Override + protected boolean parseInternal(ConfigurationSection config) { + if (config.contains("database")) { + db = ManagedDatasource.construct((ACivMod) plugin, (DatabaseCredentials) config.get("database")); + } + maxStreak = config.getInt("max_streak", 8); + streakDelay = ConfigHelper.parseTime(config.getString("streak_delay", "20 hours")); + streakGracePeriod = ConfigHelper.parseTime(config.getString("streak_grace_period", "1 day")); + timeForGain = ConfigHelper.parseTime(config.getString("online_for_reward", "30 minutes")); + loginReward = ConfigHelper.parseItemMap(config.getConfigurationSection("login_reward")); + votingReward = ConfigHelper.parseItemMap(config.getConfigurationSection("voting_reward")); + votingCooldowns = new HashMap<>(); + if (config.isConfigurationSection("voting_sites")) { + ConfigurationSection votingKeySection = config.getConfigurationSection("voting_sites"); + for (String key : votingKeySection.getKeys(false)) { + if (votingKeySection.isConfigurationSection(key)) { + ConfigurationSection current = votingKeySection.getConfigurationSection(key); + long votingCooldown = ConfigHelper.parseTime(current.getString("voting_cooldown", "20h")); + String votingUrl = current.getString("voting_url"); + String internalKey = current.getString("internal_key"); + String name = current.getString("name"); + votingCooldowns.put(internalKey, new VotingSite(name, votingUrl, internalKey, votingCooldown)); + } else { + logger.warning("Ignoring invalid entry " + key + " at " + votingKeySection.getCurrentPath()); + } + } + } + giveRewardToPearled = config.getBoolean("give_reward_to_pearled", false); + multiplyPearlCost = config.getBoolean("multiply_pearl_cost", true); + return true; + } + + public ManagedDatasource getDatabase() { + return db; + } + + public Map getVotingCooldowns() { + return votingCooldowns; + } + + public boolean giveRewardToPearled() { + return giveRewardToPearled; + } + + public boolean multiplyPearlCost() { + return multiplyPearlCost; + } + + public ItemMap getVotingReward() { + return votingReward; + } + + public ItemMap getLoginReward() { + return loginReward; + } + + public int getMaxStreak() { + return maxStreak; + } + + public long getStreakDelay() { + return streakDelay; + } + + public long getStreakGracePeriod() { + return streakGracePeriod; + } + + public long getOnlineTimeForReward() { + return timeForGain; + } + +} diff --git a/plugins/essenceglue-paper/src/main/java/com/github/maxopoly/essenceglue/EssenceDAO.java b/plugins/essenceglue-paper/src/main/java/com/github/maxopoly/essenceglue/EssenceDAO.java new file mode 100644 index 000000000..babb1d6c6 --- /dev/null +++ b/plugins/essenceglue-paper/src/main/java/com/github/maxopoly/essenceglue/EssenceDAO.java @@ -0,0 +1,52 @@ +package com.github.maxopoly.essenceglue; + +import com.programmerdan.minecraft.banstick.data.BSPlayer; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import vg.civcraft.mc.civmodcore.dao.ManagedDatasource; + +public class EssenceDAO { + + private final EssenceGluePlugin plugin; + private final ManagedDatasource db; + + public EssenceDAO(EssenceGluePlugin plugin, ManagedDatasource db) { + this.plugin = plugin; + this.db = db; + registerMigrations(); + } + + public boolean update() { + return db.updateDatabase(); + } + + public void registerMigrations() { + // import legacy mana + db.registerMigration(1, false, + "CREATE TABLE IF NOT EXISTS manaStats (ownerId int(11) NOT NULL, streak int(11) NOT NULL, " + + "lastDay bigint(20) NOT NULL, PRIMARY KEY (ownerId))", + " CREATE TABLE IF NOT EXISTS manaOwners (id int(11) NOT NULL AUTO_INCREMENT, " + + "foreignId int(11) NOT NULL, foreignIdType tinyint(4) NOT NULL, " + + "PRIMARY KEY (foreignId, foreignIdType), UNIQUE KEY `id` (id))"); + db.registerMigration(2, false, () -> { + StreakManager streakMan = plugin.getStreakManager(); + try (Connection conn = db.getConnection(); PreparedStatement ps = conn.prepareStatement( + "select o.foreignId, s.lastDay, s.streak from manaStats s inner join manaOwners o " + + "on s.ownerId = o.id where o.foreignIdType = 0"); + ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + int banStickId = rs.getInt(1); + long timeStamp = rs.getLong(2); + int streak = rs.getInt(3); + BSPlayer bsPlayer = BSPlayer.byId(banStickId); + if (bsPlayer != null) { + streakMan.setStreakRaw(streak, timeStamp, bsPlayer.getUUID()); + } + } + } + return true; + }); + } + +} diff --git a/plugins/essenceglue-paper/src/main/java/com/github/maxopoly/essenceglue/EssenceGluePlugin.java b/plugins/essenceglue-paper/src/main/java/com/github/maxopoly/essenceglue/EssenceGluePlugin.java new file mode 100644 index 000000000..e0ca7c0f0 --- /dev/null +++ b/plugins/essenceglue-paper/src/main/java/com/github/maxopoly/essenceglue/EssenceGluePlugin.java @@ -0,0 +1,81 @@ +package com.github.maxopoly.essenceglue; + +import com.github.maxopoly.essenceglue.commands.StreakCommand; +import com.github.maxopoly.essenceglue.commands.VoteCommand; +import org.bukkit.Bukkit; +import vg.civcraft.mc.civmodcore.ACivMod; +import vg.civcraft.mc.civmodcore.commands.CommandManager; + +public final class EssenceGluePlugin extends ACivMod { + + private static EssenceGluePlugin instance; + private EssenceConfigManager configMan; + private StreakManager streakMan; + private RewardManager rewardMan; + private VotifyManager votifyMan; + private CommandManager commandManager; + + public static EssenceGluePlugin instance() { + return instance; + } + + @Override + public void onEnable() { + super.onEnable(); + instance = this; + configMan = new EssenceConfigManager(this); + if (!configMan.parse()) { + getLogger().severe("Failed to read config, disabling"); + Bukkit.getPluginManager().disablePlugin(this); + return; + } + commandManager = new CommandManager(this); + commandManager.init(); + registerCommands(); + streakMan = new StreakManager(this, configMan.getStreakDelay(), configMan.getStreakGracePeriod(), + configMan.getMaxStreak(), configMan.getOnlineTimeForReward(), configMan.giveRewardToPearled()); + if (configMan.getDatabase() != null) { + EssenceDAO dao = new EssenceDAO(this, configMan.getDatabase()); + if (!dao.update()) { + getLogger().severe("Failed to apply database updates, disabling"); + Bukkit.getPluginManager().disablePlugin(this); + return; + } + } + rewardMan = new RewardManager(configMan.getLoginReward(), configMan.getVotingReward()); + if (Bukkit.getPluginManager().isPluginEnabled("Votifier")) { + votifyMan = new VotifyManager(rewardMan, configMan.getVotingCooldowns()); + Bukkit.getPluginManager().registerEvents(votifyMan, this); + } else { + getLogger().info("Votifier is not enabled, no voting support is possible"); + } + Bukkit.getPluginManager().registerEvents(new ExilePearListener(streakMan, configMan.multiplyPearlCost()), this); + } + + private void registerCommands() { + commandManager.registerCommand(new StreakCommand()); + commandManager.registerCommand(new VoteCommand()); + } + + public StreakManager getStreakManager() { + return streakMan; + } + + public RewardManager getRewardManager() { + return rewardMan; + } + + public EssenceConfigManager getConfigManager() { + return configMan; + } + + public VotifyManager getVoteManager() { + return votifyMan; + } + + @Override + public void onDisable() { + super.onDisable(); + } + +} diff --git a/plugins/essenceglue-paper/src/main/java/com/github/maxopoly/essenceglue/ExilePearListener.java b/plugins/essenceglue-paper/src/main/java/com/github/maxopoly/essenceglue/ExilePearListener.java new file mode 100644 index 000000000..1efccc4fa --- /dev/null +++ b/plugins/essenceglue-paper/src/main/java/com/github/maxopoly/essenceglue/ExilePearListener.java @@ -0,0 +1,27 @@ +package com.github.maxopoly.essenceglue; + +import com.devotedmc.ExilePearl.event.PearlDecayEvent; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; + +public class ExilePearListener implements Listener { + + private final boolean multiplyCost; + private final StreakManager streakMan; + + public ExilePearListener(StreakManager streakMan, boolean multiplyCost) { + this.multiplyCost = multiplyCost; + this.streakMan = streakMan; + } + + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void pearlDecay(PearlDecayEvent event) { + if (!multiplyCost) { + return; + } + int streak = streakMan.getRecalculatedCurrentStreak(event.getPearl().getPlayerId()); + event.setDamageAmount(event.getDamageAmount() * streak); + } + +} diff --git a/plugins/essenceglue-paper/src/main/java/com/github/maxopoly/essenceglue/NewNameResolver.java b/plugins/essenceglue-paper/src/main/java/com/github/maxopoly/essenceglue/NewNameResolver.java new file mode 100644 index 000000000..5612a7926 --- /dev/null +++ b/plugins/essenceglue-paper/src/main/java/com/github/maxopoly/essenceglue/NewNameResolver.java @@ -0,0 +1,37 @@ +package com.github.maxopoly.essenceglue; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.UUID; +import java.util.logging.Level; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +public class NewNameResolver { + + private static final String PROFILE_URL = "https://api.mojang.com/users/profiles/minecraft/%s?at=%d"; + private static final JsonParser jsonParser = new JsonParser(); + + public static UUID getUUIDForMojangName(String userName) { + JsonObject response; + HttpURLConnection connection; + try { + connection = (HttpURLConnection) new URL(String.format(PROFILE_URL, userName, System.currentTimeMillis() / 1000)).openConnection(); + response = (JsonObject) jsonParser.parse(new InputStreamReader(connection.getInputStream())); + } catch (IOException e) { + EssenceGluePlugin.instance().getLogger().log(Level.SEVERE, "Failed to lookup current mojang name", e); + return null; + } + String uuidASString = response.get("id").getAsString(); + if (uuidASString == null) { + return null; + } + //add dashes into uuid + uuidASString = uuidASString.replaceAll("(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})", "$1-$2-$3-$4-$5"); + return UUID.fromString(uuidASString); + } + +} diff --git a/plugins/essenceglue-paper/src/main/java/com/github/maxopoly/essenceglue/RewardManager.java b/plugins/essenceglue-paper/src/main/java/com/github/maxopoly/essenceglue/RewardManager.java new file mode 100644 index 000000000..0fb9ae162 --- /dev/null +++ b/plugins/essenceglue-paper/src/main/java/com/github/maxopoly/essenceglue/RewardManager.java @@ -0,0 +1,48 @@ +package com.github.maxopoly.essenceglue; + +import java.util.HashMap; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import vg.civcraft.mc.civmodcore.inventory.items.ItemMap; + +public class RewardManager { + + private final ItemMap dailyReward; + private final ItemMap voteReward; + + public RewardManager(ItemMap dailyReward, ItemMap voteReward) { + this.dailyReward = dailyReward; + this.voteReward = voteReward; + } + + public void giveLoginReward(Player p, int streak) { + p.sendMessage(ChatColor.GREEN + "You've received your daily login reward"); + ItemMap reward = dailyReward.clone(); + reward.multiplyContent(streak); + giveItems(p, reward); + } + + public void giveVoteReward(Player p, String page) { + p.sendMessage(ChatColor.GREEN + "You received a reward for voting on " + page); + giveItems(p, voteReward); + } + + private static void giveItems(Player p, ItemMap items) { + if (items.fitsIn(p.getInventory())) { + for (ItemStack is : items.getItemStackRepresentation()) { + HashMap notAdded = p.getInventory().addItem(is); + for (ItemStack toDrop : notAdded.values()) { + p.sendMessage(ChatColor.GREEN + "Your inventory was full, so it was dropped on the ground"); + p.getWorld().dropItemNaturally(p.getLocation(), toDrop); + } + } + } else { + p.sendMessage(ChatColor.GREEN + "Your inventory was full, so it was dropped on the ground"); + for (ItemStack is : items.getItemStackRepresentation()) { + p.getWorld().dropItemNaturally(p.getLocation(), is); + } + } + } + +} diff --git a/plugins/essenceglue-paper/src/main/java/com/github/maxopoly/essenceglue/StreakManager.java b/plugins/essenceglue-paper/src/main/java/com/github/maxopoly/essenceglue/StreakManager.java new file mode 100644 index 000000000..550c20625 --- /dev/null +++ b/plugins/essenceglue-paper/src/main/java/com/github/maxopoly/essenceglue/StreakManager.java @@ -0,0 +1,182 @@ +package com.github.maxopoly.essenceglue; + +import com.devotedmc.ExilePearl.ExilePearlPlugin; +import com.programmerdan.minecraft.banstick.data.BSPlayer; +import java.util.Map; +import java.util.TreeMap; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import vg.civcraft.mc.civmodcore.players.settings.PlayerSettingAPI; +import vg.civcraft.mc.civmodcore.players.settings.gui.MenuSection; +import vg.civcraft.mc.civmodcore.players.settings.impl.BooleanSetting; +import vg.civcraft.mc.civmodcore.players.settings.impl.IntegerSetting; +import vg.civcraft.mc.civmodcore.players.settings.impl.LongSetting; + +public class StreakManager { + + private static final long MILLIS_IN_DAY = TimeUnit.DAYS.toMillis(1); + private static final Map mainAccountCache = new TreeMap<>(); + // streak as a bitwise integer where a 0 is a missed day and a 1 is an online + // day, in LSB order from current to past + private final IntegerSetting playerStreaks; + private final LongSetting lastPlayerUpdate; + private final BooleanSetting receiveRewards; + private final long streakDelay; + private final long streakGracePeriod; + private final Map currentOnlineTime; + private final int maximumStreak; + private final long countRequiredForGain; + private final boolean giveRewardToPearled; + private final BooleanSetting showSitesOnLogin; + + public StreakManager(EssenceGluePlugin plugin, long streakDelay, long streakGracePeriod, int maximumStreak, + long onlineTimePerDay, boolean giveRewardToPearled) { + playerStreaks = new IntegerSetting(plugin, 0, "Player essence streak", "essenceGluePlayerStreak"); + PlayerSettingAPI.registerSetting(playerStreaks, null); + lastPlayerUpdate = new LongSetting(plugin, 0L, "Player streak refresh", "essenceGlueLastUpdate"); + PlayerSettingAPI.registerSetting(lastPlayerUpdate, null); + receiveRewards = new BooleanSetting(plugin, true, "Receive essence", "essenceGlueReceiveEssence", + "Whether you will receive essence on this account"); + showSitesOnLogin = new BooleanSetting(plugin, true, "Show voting sites on login?", "essenceGlueShowSitesOnLogin", + "Show the sites you can vote on currently when you login?"); + MenuSection menu = PlayerSettingAPI.getMainMenu().createMenuSection("Essence", + "Essence and voting related settings", new ItemStack(Material.ENDER_EYE)); + PlayerSettingAPI.registerSetting(receiveRewards, menu); + PlayerSettingAPI.registerSetting(showSitesOnLogin, menu); + Bukkit.getScheduler().runTaskTimer(plugin, this::updateAll, 20 * 60L, 20 * 60L); + this.streakDelay = streakDelay; + this.streakGracePeriod = streakGracePeriod; + this.currentOnlineTime = new TreeMap<>(); + this.maximumStreak = maximumStreak; + this.giveRewardToPearled = giveRewardToPearled; + this.countRequiredForGain = TimeUnit.MILLISECONDS.toMinutes(onlineTimePerDay); + } + + public static UUID getTrueUUID(UUID uuid) { + UUID cached = mainAccountCache.get(uuid); + if (cached != null) { + return cached; + } + BSPlayer bsPlayer = BSPlayer.byUUID(uuid); + if (bsPlayer == null) { + return null; + } + long minID = bsPlayer.getId(); + BSPlayer ogAcc = bsPlayer; + for (BSPlayer alt : bsPlayer.getTransitiveSharedPlayers(true)) { + if (alt.getId() < minID) { + minID = alt.getId(); + ogAcc = alt; + } + } + mainAccountCache.put(uuid, ogAcc.getUUID()); + return ogAcc.getUUID(); + } + + private void updateAll() { + long currentMillis = System.currentTimeMillis(); + for (Player p : Bukkit.getOnlinePlayers()) { + UUID uuid = getTrueUUID(p.getUniqueId()); + if (uuid == null) { + EssenceGluePlugin.instance().getLogger().severe(p.getName() + " had main account in BanStick?"); + continue; + } + long sinceLastClaim = currentMillis - lastPlayerUpdate.getValue(uuid); + if (sinceLastClaim >= streakDelay) { + int currentCount = currentOnlineTime.computeIfAbsent(uuid, e -> 0); + if (currentCount >= countRequiredForGain && receiveRewards.getValue(p)) { + updatePlayerStreak(uuid); + currentOnlineTime.remove(uuid); +// p.sendMessage(ChatColor.GREEN + "Your login streak is now " + ChatColor.LIGHT_PURPLE +// + getCurrentStreak(uuid, true)); + if (giveRewardToPearled || ExilePearlPlugin.getApi().getExiledAlts(uuid, true) < 1) { + EssenceGluePlugin.instance().getRewardManager().giveLoginReward(p, + getCurrentStreak(uuid, true)); + } + } else { + currentOnlineTime.put(uuid, currentCount + 1); + } + } + } + } + + public long getRewardCooldown(UUID uuid) { + long sinceLastClaim = System.currentTimeMillis() - lastPlayerUpdate.getValue(uuid); + return Math.max(0, streakDelay - sinceLastClaim); + } + + public long untilTodaysReward(UUID uuid) { + Integer currentCount = currentOnlineTime.getOrDefault(uuid, 0); + return TimeUnit.MINUTES.toMillis(countRequiredForGain - currentCount); + } + + public void updatePlayerStreak(UUID player) { + long now = System.currentTimeMillis(); + long lastIncrement = lastPlayerUpdate.getValue(player); + long timePassed = now - lastIncrement; + timePassed -= streakDelay; + int daysPassed; + if (timePassed > 0) { + daysPassed = 1; + } else { + daysPassed = 0; + } + timePassed -= streakGracePeriod; + + if (timePassed > 0) { + daysPassed += (int) (timePassed / MILLIS_IN_DAY + 1); + } + int streak = playerStreaks.getValue(player); + daysPassed = Math.min(daysPassed, maximumStreak); + // shift to left by amount of days missed + streak <<= daysPassed; + // add new day + streak |= 1; + // cap maximum with a bit string containing maximumStreak many 1 at the end and + // only 0 otherwise + streak = capStreak(streak); + EssenceGluePlugin.instance().getLogger() + .info(String.format("Streak for %s was updated, now %d (raw: %d), passed: %d", player.toString(), + Integer.bitCount(streak), streak, daysPassed)); + playerStreaks.setValue(player, streak); + lastPlayerUpdate.setValue(player, now); + } + + public int getCurrentStreak(UUID uuid, boolean isMain) { + if (!isMain) { + uuid = getTrueUUID(uuid); + } + return Integer.bitCount(playerStreaks.getValue(uuid)); + } + + public int getRecalculatedCurrentStreak(UUID uuid) { + uuid = getTrueUUID(uuid); + int unshiftedValue = playerStreaks.getValue(uuid); + long timePassed = System.currentTimeMillis() - lastPlayerUpdate.getValue(uuid) - streakGracePeriod; + int daysPassed = 0; + if (timePassed > 0) { + daysPassed = (int) (timePassed / MILLIS_IN_DAY + 1); + daysPassed = Math.min(daysPassed, maximumStreak); + } + int adjustedStreak = unshiftedValue << daysPassed; + adjustedStreak = capStreak(adjustedStreak); + return Integer.bitCount(adjustedStreak); + } + + private int capStreak(int uncapped) { + return uncapped & ~((~0) << maximumStreak); + } + + public void setStreakRaw(int streak, long timeStamp, UUID player) { + playerStreaks.setValue(player, streak); + lastPlayerUpdate.setValue(player, timeStamp); + } + + public boolean getShowSitesOnLogin(UUID uuid) { + return showSitesOnLogin.getValue(uuid); + } +} diff --git a/plugins/essenceglue-paper/src/main/java/com/github/maxopoly/essenceglue/VotifyManager.java b/plugins/essenceglue-paper/src/main/java/com/github/maxopoly/essenceglue/VotifyManager.java new file mode 100644 index 000000000..e8ec558bd --- /dev/null +++ b/plugins/essenceglue-paper/src/main/java/com/github/maxopoly/essenceglue/VotifyManager.java @@ -0,0 +1,123 @@ +package com.github.maxopoly.essenceglue; + +import com.vexsoftware.votifier.model.Vote; +import com.vexsoftware.votifier.model.VotifierEvent; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import net.md_5.bungee.api.chat.ClickEvent; +import net.md_5.bungee.api.chat.HoverEvent; +import net.md_5.bungee.api.chat.TextComponent; +import net.md_5.bungee.api.chat.hover.content.Text; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerLoginEvent; +import vg.civcraft.mc.civmodcore.players.settings.PlayerSettingAPI; +import vg.civcraft.mc.civmodcore.players.settings.impl.LongSetting; +import vg.civcraft.mc.civmodcore.utilities.TextUtil; + +public class VotifyManager implements Listener { + + private final RewardManager rewardMan; + private final Map perSiteCooldowns; + private final Map perSiteSettings; + + public VotifyManager(RewardManager rewardMan, Map perSiteCooldowns) { + this.rewardMan = rewardMan; + this.perSiteSettings = new HashMap<>(); + this.perSiteCooldowns = new HashMap<>(perSiteCooldowns); + for (String s : perSiteCooldowns.keySet()) { + LongSetting setting = new LongSetting(EssenceGluePlugin.instance(), 0L, "Last vote " + s, + "essenceGlueVoteTime" + s); + PlayerSettingAPI.registerSetting(setting, null); + perSiteSettings.put(s, setting); + } + } + + @EventHandler(priority = EventPriority.NORMAL) + public void onVotifierEvent(VotifierEvent event) { + Vote vote = event.getVote(); + Player player = Bukkit.getPlayer(vote.getUsername()); + if (player != null) { + handOutVotingReward(player, vote); + return; + } + Bukkit.getScheduler().runTaskAsynchronously(EssenceGluePlugin.instance(), () -> { + //Mojang API rate limit is 600 requests per 10 minutes, which is not a problem for now + UUID uuid = NewNameResolver.getUUIDForMojangName(vote.getUsername()); + if (uuid == null) { + EssenceGluePlugin.instance().getLogger().info("Could not resolve vote from " + vote.getUsername()); + return; + } + Player altPlayer = Bukkit.getPlayer(uuid); + if (altPlayer == null) { + EssenceGluePlugin.instance().getLogger() + .info("Found uuid " + uuid + " but could not find player for vote from " + vote.getUsername()); + return; + } + Bukkit.getScheduler().runTask(EssenceGluePlugin.instance(), () -> { + handOutVotingReward(altPlayer, vote); + }); + }); + } + + public void handOutVotingReward(Player player, Vote vote) { + VotingSite site = perSiteCooldowns.get(vote.getServiceName()); + if (site == null) { + EssenceGluePlugin.instance().getLogger() + .warning("Received vote from unknown service " + vote.getServiceName()); + player.sendMessage(ChatColor.RED + "You voted on a website not properly supported or setup right now, " + + vote.getServiceName()); + return; + } + UUID uuid = StreakManager.getTrueUUID(player.getUniqueId()); + LongSetting serverCooldown = perSiteSettings.get(vote.getServiceName()); + long lastVoted = serverCooldown.getValue(uuid); + long now = System.currentTimeMillis(); + long timePassed = now - lastVoted; + long coolDown = site.getVotingCooldown(); + if (timePassed < coolDown) { + long remaining = coolDown - timePassed; + player.sendMessage( + ChatColor.RED + "You already voted on this site today, you can receive rewards for it again in " + + ChatColor.AQUA + TextUtil.formatDuration(remaining)); + return; + } + serverCooldown.setValue(uuid, now); + rewardMan.giveVoteReward(player, site.getName()); + } + + @EventHandler(priority = EventPriority.NORMAL) + public void onLogin(PlayerLoginEvent event) { + Player p = event.getPlayer(); + if (!EssenceGluePlugin.instance().getStreakManager().getShowSitesOnLogin(p.getUniqueId())) { + return; + } + for (VotingSite site : EssenceGluePlugin.instance().getConfigManager().getVotingCooldowns().values()) { + UUID trueUUID = StreakManager.getTrueUUID(p.getUniqueId()); + long lastVote = getLastVote(site.getInternalKey(), trueUUID); + boolean canVote = (System.currentTimeMillis() - lastVote) > site.getVotingCooldown(); + if (canVote) { + TextComponent text = new TextComponent( + ChatColor.GREEN + "Receive rewards for voting on " + site.getName() + + ". Click this message to open the link!"); + text.setClickEvent( + new ClickEvent(ClickEvent.Action.OPEN_URL, site.getVotingUrl().replace("%PLAYER%", p.getName()))); + text.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, + new Text("Click to open the voting link for " + site.getName()))); + Bukkit.getScheduler().runTaskLater(EssenceGluePlugin.instance(), () -> { + p.sendMessage(text); + }, 20L); + } + } + } + + public long getLastVote(String internalSiteKey, UUID player) { + return perSiteSettings.get(internalSiteKey).getValue(player); + } + +} diff --git a/plugins/essenceglue-paper/src/main/java/com/github/maxopoly/essenceglue/VotingSite.java b/plugins/essenceglue-paper/src/main/java/com/github/maxopoly/essenceglue/VotingSite.java new file mode 100644 index 000000000..7a231ba2a --- /dev/null +++ b/plugins/essenceglue-paper/src/main/java/com/github/maxopoly/essenceglue/VotingSite.java @@ -0,0 +1,33 @@ +package com.github.maxopoly.essenceglue; + +public class VotingSite { + + private final String votingUrl; + private final String name; + private final String internalKey; + private final long votingCooldown; + + public VotingSite(String name, String votingUrl, String internalKey, long votingCooldown) { + this.name = name; + this.votingCooldown = votingCooldown; + this.votingUrl = votingUrl; + this.internalKey = internalKey; + } + + public String getVotingUrl() { + return votingUrl; + } + + public String getName() { + return name; + } + + public String getInternalKey() { + return internalKey; + } + + public long getVotingCooldown() { + return votingCooldown; + } + +} diff --git a/plugins/essenceglue-paper/src/main/java/com/github/maxopoly/essenceglue/commands/StreakCommand.java b/plugins/essenceglue-paper/src/main/java/com/github/maxopoly/essenceglue/commands/StreakCommand.java new file mode 100644 index 000000000..93cd3a7d1 --- /dev/null +++ b/plugins/essenceglue-paper/src/main/java/com/github/maxopoly/essenceglue/commands/StreakCommand.java @@ -0,0 +1,30 @@ +package com.github.maxopoly.essenceglue.commands; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.Description; +import com.github.maxopoly.essenceglue.EssenceGluePlugin; +import com.github.maxopoly.essenceglue.StreakManager; +import java.util.UUID; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import vg.civcraft.mc.civmodcore.utilities.TextUtil; + +public class StreakCommand extends BaseCommand { + + @CommandAlias("streak") + @Description("Displays stats about your daily login streak") + public void execute(Player p) { + UUID uuid = StreakManager.getTrueUUID(p.getUniqueId()); + StreakManager streakMan = EssenceGluePlugin.instance().getStreakManager(); + p.sendMessage(ChatColor.GREEN + "Your current login streak is " + streakMan.getRecalculatedCurrentStreak(uuid)); + long cooldown = streakMan.getRewardCooldown(uuid); + if (cooldown > 0) { + p.sendMessage(ChatColor.YELLOW + "You will be eligible for daily rewards again in " + TextUtil + .formatDuration(cooldown)); + } else { + long left = streakMan.untilTodaysReward(uuid); + p.sendMessage(ChatColor.GREEN + "You will receive your daily rewards in " + TextUtil.formatDuration(left)); + } + } +} diff --git a/plugins/essenceglue-paper/src/main/java/com/github/maxopoly/essenceglue/commands/VoteCommand.java b/plugins/essenceglue-paper/src/main/java/com/github/maxopoly/essenceglue/commands/VoteCommand.java new file mode 100644 index 000000000..76621197e --- /dev/null +++ b/plugins/essenceglue-paper/src/main/java/com/github/maxopoly/essenceglue/commands/VoteCommand.java @@ -0,0 +1,52 @@ +package com.github.maxopoly.essenceglue.commands; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.Description; +import com.github.maxopoly.essenceglue.EssenceGluePlugin; +import com.github.maxopoly.essenceglue.StreakManager; +import com.github.maxopoly.essenceglue.VotifyManager; +import com.github.maxopoly.essenceglue.VotingSite; +import java.util.UUID; +import net.md_5.bungee.api.chat.ClickEvent; +import net.md_5.bungee.api.chat.ClickEvent.Action; +import net.md_5.bungee.api.chat.HoverEvent; +import net.md_5.bungee.api.chat.TextComponent; +import net.md_5.bungee.api.chat.hover.content.Text; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import vg.civcraft.mc.civmodcore.utilities.TextUtil; + +public class VoteCommand extends BaseCommand { + + @CommandAlias("vote") + @Description("Lists all available voting websites") + public void execute(Player sender) { + VotifyManager voteMan = EssenceGluePlugin.instance().getVoteManager(); + if (voteMan == null) { + sender.sendMessage(ChatColor.RED + "Voting is not enabled"); + return; + } + Player p = (Player) sender; + for (VotingSite site : EssenceGluePlugin.instance().getConfigManager().getVotingCooldowns().values()) { + UUID trueUUID = StreakManager.getTrueUUID(p.getUniqueId()); + long lastVote = voteMan.getLastVote(site.getInternalKey(), trueUUID); + boolean canVote = (System.currentTimeMillis() - lastVote) > site.getVotingCooldown(); + if (canVote) { + TextComponent text = new TextComponent( + ChatColor.GREEN + "Receive rewards for voting on " + site.getName() + + ". Click this message to open the link!"); + text.setClickEvent( + new ClickEvent(Action.OPEN_URL, site.getVotingUrl().replace("%PLAYER%", p.getName()))); + text.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, + new Text("Click to open the voting link for " + site.getName()))); + p.spigot().sendMessage(text); + } else { + long remaining = site.getVotingCooldown() - (System.currentTimeMillis() - lastVote); + p.sendMessage( + ChatColor.YELLOW + "You already voted on " + site.getName() + " and may vote there again in " + + TextUtil.formatDuration(remaining)); + } + } + } +} diff --git a/plugins/essenceglue-paper/src/main/resources/config.yml b/plugins/essenceglue-paper/src/main/resources/config.yml new file mode 100644 index 000000000..89b0e3b3f --- /dev/null +++ b/plugins/essenceglue-paper/src/main/resources/config.yml @@ -0,0 +1,43 @@ +debug: false + +max_streak: 8 +streak_delay: 20 hours +streak_grace_period: 1 day +#Needs to be a multiple of a full minute +online_for_reward: 30 minutes + +login_reward: + essence: + material: EYE_OF_ENDER + name: Player Essence + amount: 1 + lore: + - Activity reward used to fuel pearls + +voting_reward: + essence: + material: EYE_OF_ENDER + name: Player Essence + amount: 1 + lore: + - Activity reward used to fuel pearls + +give_reward_to_pearled: false +multiply_pearl_cost: true + + +# Database connection details, should you want to have a database connection. +# KEEP THIS COMMENTED OUT IN THIS DEFAULT CONFIG OTHERWISE SOME CONFIG PARSING WILL CAUSE ISSUES! +# Remember also to change the plugin value to match your plugin name. Do NOT change the ==: value. +database: + ==: vg.civcraft.mc.civmodcore.dao.DatabaseCredentials + plugin: EssenceGlue + user: username + password: password + host: localhost + port: 3306 + database: minecraft + poolsize: 5 + connection_timeout: 10000 + idle_timeout: 600000 + max_lifetime: 7200000 diff --git a/plugins/essenceglue-paper/src/main/resources/plugin.yml b/plugins/essenceglue-paper/src/main/resources/plugin.yml new file mode 100644 index 000000000..d3ab15d6a --- /dev/null +++ b/plugins/essenceglue-paper/src/main/resources/plugin.yml @@ -0,0 +1,12 @@ +name: EssenceGlue +version: ${version} +main: com.github.maxopoly.essenceglue.EssenceGluePlugin +api-version: 1.17 +depend: [CivModCore, BanStick, ExilePearl] +softdepend: [Votifier] +authors: [Maxopoly] +permissions: + eg.public: + default: true + eg.op: + default: op diff --git a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/modifiers/DamageableModifier.java b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/modifiers/DamageableModifier.java index 5a274e4c0..7d9e11cc2 100644 --- a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/modifiers/DamageableModifier.java +++ b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/modifiers/DamageableModifier.java @@ -22,7 +22,6 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.Damageable; import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; import vg.civcraft.mc.civmodcore.nbt.wrappers.NBTCompound; -import vg.civcraft.mc.civmodcore.utilities.MoreClassUtils; @CommandAlias(SetCommand.ALIAS) @Modifier(slug = "DAMAGE", order = 500) @@ -61,8 +60,7 @@ public final class DamageableModifier extends ModifierData { @Override public boolean conforms(ItemStack item) { - Damageable meta = MoreClassUtils.castOrNull(Damageable.class, item.getItemMeta()); - if (meta == null) { + if (!(item.getItemMeta() instanceof final Damageable meta)) { return false; } int itemDamage = meta.getDamage(); diff --git a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/modifiers/EnchantStorageModifier.java b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/modifiers/EnchantStorageModifier.java index 0d717ec1b..1bbd13c98 100644 --- a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/modifiers/EnchantStorageModifier.java +++ b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/modifiers/EnchantStorageModifier.java @@ -19,7 +19,6 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.EnchantmentStorageMeta; import vg.civcraft.mc.civmodcore.inventory.items.EnchantUtils; import vg.civcraft.mc.civmodcore.nbt.wrappers.NBTCompound; -import vg.civcraft.mc.civmodcore.utilities.MoreClassUtils; import vg.civcraft.mc.civmodcore.utilities.MoreMapUtils; @CommandAlias(SetCommand.ALIAS) @@ -34,8 +33,7 @@ public final class EnchantStorageModifier extends ModifierData { @Override public EnchantStorageModifier construct(ItemStack item) { - EnchantmentStorageMeta meta = MoreClassUtils.castOrNull(EnchantmentStorageMeta.class, item.getItemMeta()); - if (meta == null) { + if (!(item.getItemMeta() instanceof final EnchantmentStorageMeta meta)) { return null; } EnchantStorageModifier modifier = new EnchantStorageModifier(); @@ -61,8 +59,7 @@ public final class EnchantStorageModifier extends ModifierData { @Override public boolean conforms(ItemStack item) { - EnchantmentStorageMeta meta = MoreClassUtils.castOrNull(EnchantmentStorageMeta.class, item.getItemMeta()); - if (meta == null) { + if (!(item.getItemMeta() instanceof final EnchantmentStorageMeta meta)) { return false; } if (hasEnchants() != meta.hasStoredEnchants()) { diff --git a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/modifiers/PotionModifier.java b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/modifiers/PotionModifier.java index 4a0e904de..660e6a687 100644 --- a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/modifiers/PotionModifier.java +++ b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/modifiers/PotionModifier.java @@ -22,7 +22,6 @@ import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionType; import vg.civcraft.mc.civmodcore.inventory.items.PotionUtils; import vg.civcraft.mc.civmodcore.nbt.wrappers.NBTCompound; -import vg.civcraft.mc.civmodcore.utilities.MoreClassUtils; import vg.civcraft.mc.civmodcore.utilities.NullUtils; @CommandAlias(SetCommand.ALIAS) @@ -39,8 +38,7 @@ public final class PotionModifier extends ModifierData { @Override public PotionModifier construct(ItemStack item) { - PotionMeta meta = MoreClassUtils.castOrNull(PotionMeta.class, item.getItemMeta()); - if (meta == null) { + if (!(item.getItemMeta() instanceof final PotionMeta meta)) { return null; } PotionModifier modifier = new PotionModifier(); @@ -59,8 +57,7 @@ public final class PotionModifier extends ModifierData { @Override public boolean conforms(ItemStack item) { - PotionMeta meta = MoreClassUtils.castOrNull(PotionMeta.class, item.getItemMeta()); - if (meta == null) { + if (!(item.getItemMeta() instanceof final PotionMeta meta)) { return false; } if (!NullUtils.equalsNotNull(this.base, meta.getBasePotionData())) { diff --git a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/modifiers/RepairModifier.java b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/modifiers/RepairModifier.java index c732c6275..bd3868464 100644 --- a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/modifiers/RepairModifier.java +++ b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/modifiers/RepairModifier.java @@ -23,7 +23,6 @@ import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.Repairable; import vg.civcraft.mc.civmodcore.nbt.wrappers.NBTCompound; -import vg.civcraft.mc.civmodcore.utilities.MoreClassUtils; /** *

This additional represents a repair level condition.

@@ -48,8 +47,7 @@ public final class RepairModifier extends ModifierData { if (!ItemExchangeConfig.canRepairItem(item.getType())) { return null; } - Repairable meta = MoreClassUtils.castOrNull(Repairable.class, item.getItemMeta()); - if (meta == null) { + if (!(item.getItemMeta() instanceof final Repairable meta)) { return null; } RepairModifier modifier = new RepairModifier(); @@ -64,8 +62,7 @@ public final class RepairModifier extends ModifierData { @Override public boolean conforms(ItemStack item) { - Repairable meta = MoreClassUtils.castOrNull(Repairable.class, item.getItemMeta()); - if (meta == null) { + if (!(item.getItemMeta() instanceof final Repairable meta)) { return false; } int itemRepair = meta.getRepairCost(); diff --git a/plugins/jukealert-paper/src/main/java/com/untamedears/jukealert/model/actions/abstr/LoggablePlayerAction.java b/plugins/jukealert-paper/src/main/java/com/untamedears/jukealert/model/actions/abstr/LoggablePlayerAction.java index 1be31a577..f97a23340 100644 --- a/plugins/jukealert-paper/src/main/java/com/untamedears/jukealert/model/actions/abstr/LoggablePlayerAction.java +++ b/plugins/jukealert-paper/src/main/java/com/untamedears/jukealert/model/actions/abstr/LoggablePlayerAction.java @@ -5,6 +5,7 @@ import com.untamedears.jukealert.model.Snitch; import com.untamedears.jukealert.model.actions.ActionCacheState; import com.untamedears.jukealert.model.actions.LoggedActionPersistence; import com.untamedears.jukealert.util.JAUtility; +import java.time.format.DateTimeFormatter; import java.util.UUID; import java.util.concurrent.CompletableFuture; import net.md_5.bungee.api.chat.TextComponent; @@ -78,7 +79,8 @@ public abstract class LoggablePlayerAction extends PlayerAction implements Logga if (referenceLoc != snitch.getLocation()) { comp.addExtra(String.format("%s%s ", ChatColor.YELLOW, referenceLocText)); } - comp.addExtra(new TextComponent(ChatColor.AQUA + getFormattedTime())); + // Example: 2011-12-03T10:15:30 + comp.addExtra(new TextComponent(ChatColor.AQUA + getFormattedTime(DateTimeFormatter.ISO_LOCAL_DATE_TIME))); } return comp; } @@ -89,7 +91,8 @@ public abstract class LoggablePlayerAction extends PlayerAction implements Logga item = new ItemStack(Material.STONE); } ItemUtils.addLore(item, String.format("%sPlayer: %s", ChatColor.GOLD, getPlayerName()), - String.format("%sTime: %s", ChatColor.LIGHT_PURPLE,getFormattedTime())); + // Example: Tue, 3 Jun 2008 11:05:30 GMT + String.format("%sTime: %s", ChatColor.LIGHT_PURPLE, getFormattedTime(DateTimeFormatter.RFC_1123_DATE_TIME))); ItemUtils.setDisplayName(item, ChatColor.GOLD + getGUIName()); } diff --git a/plugins/jukealert-paper/src/main/java/com/untamedears/jukealert/model/actions/abstr/PlayerAction.java b/plugins/jukealert-paper/src/main/java/com/untamedears/jukealert/model/actions/abstr/PlayerAction.java index 285ff255f..c0a1811ae 100644 --- a/plugins/jukealert-paper/src/main/java/com/untamedears/jukealert/model/actions/abstr/PlayerAction.java +++ b/plugins/jukealert-paper/src/main/java/com/untamedears/jukealert/model/actions/abstr/PlayerAction.java @@ -1,16 +1,14 @@ package com.untamedears.jukealert.model.actions.abstr; import com.untamedears.jukealert.model.Snitch; -import java.time.LocalDateTime; +import java.time.Instant; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; import java.util.UUID; +import org.jetbrains.annotations.NotNull; import vg.civcraft.mc.namelayer.NameAPI; public abstract class PlayerAction extends SnitchAction { - - private static final DateTimeFormatter timeFormatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME; - protected final UUID player; public PlayerAction(long time, Snitch snitch, UUID player) { @@ -30,8 +28,10 @@ public abstract class PlayerAction extends SnitchAction { return true; } - protected String getFormattedTime() { - return timeFormatter.format(LocalDateTime.ofEpochSecond(time / 1000, 0, ZoneOffset.UTC)); + protected String getFormattedTime( + final @NotNull DateTimeFormatter formatter + ) { + return formatter.format(Instant.ofEpochMilli(this.time).atOffset(ZoneOffset.UTC)); } public String getPlayerName() { diff --git a/plugins/kirabukkitgateway-paper/.gitignore b/plugins/kirabukkitgateway-paper/.gitignore new file mode 100644 index 000000000..bc6128e50 --- /dev/null +++ b/plugins/kirabukkitgateway-paper/.gitignore @@ -0,0 +1,10 @@ +/target/ +.classpath +.settings/** +.project +dependency-reduced-pom.xml +*.iml +.gradle +build + +.idea diff --git a/plugins/kirabukkitgateway-paper/README.md b/plugins/kirabukkitgateway-paper/README.md new file mode 100644 index 000000000..de29b71b6 --- /dev/null +++ b/plugins/kirabukkitgateway-paper/README.md @@ -0,0 +1,8 @@ +# KiraBukkitGateway +KiraBukkitGateway is a Minecraft plugin used to relay in-game information through RabbitMQ to the Kira Discord bot. + +You can set keywords in the KiraBukkitGateway's config that the plugin will look for and send to the Kira Discord Bot. This is useful for moderation, for example you can set a relay so that anytime a player on your server says "dox" you will see the message in a designated Discord channel. This plugin is also used to run commands in-game or on the server console from Discord through Kira. + + + +This plugin is currently on Minecraft version 1.16.5. diff --git a/plugins/kirabukkitgateway-paper/build.gradle.kts b/plugins/kirabukkitgateway-paper/build.gradle.kts new file mode 100644 index 000000000..da38d378d --- /dev/null +++ b/plugins/kirabukkitgateway-paper/build.gradle.kts @@ -0,0 +1,16 @@ +plugins { + id("io.papermc.paperweight.userdev") +} + +version = "2.0.3" + +dependencies { + paperDevBundle("1.18.2-R0.1-SNAPSHOT") + + implementation("com.rabbitmq:amqp-client:5.6.0") + compileOnly(project(":plugins:civmodcore-paper")) + compileOnly(project(":plugins:namelayer-paper")) + compileOnly(project(":plugins:civchat2-paper")) + compileOnly(project(":plugins:jukealert-paper")) + compileOnly("net.luckperms:api:5.0") +} diff --git a/plugins/kirabukkitgateway-paper/license.txt b/plugins/kirabukkitgateway-paper/license.txt new file mode 100644 index 000000000..3541541cc --- /dev/null +++ b/plugins/kirabukkitgateway-paper/license.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) <2016> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/ConfigParser.java b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/ConfigParser.java new file mode 100644 index 000000000..5cdb8bc30 --- /dev/null +++ b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/ConfigParser.java @@ -0,0 +1,93 @@ +package com.github.maxopoly.KiraBukkitGateway; + +import com.github.maxopoly.KiraBukkitGateway.log.KiraLogAppender; +import com.rabbitmq.client.ConnectionFactory; +import java.util.LinkedList; +import java.util.List; +import java.util.logging.Logger; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.FileConfiguration; + +public class ConfigParser { + + private KiraBukkitGatewayPlugin plugin; + private FileConfiguration config; + private Logger logger; + + public ConfigParser(KiraBukkitGatewayPlugin plugin) { + this.plugin = plugin; + this.logger = plugin.getLogger(); + reload(); + } + + public void reload() { + plugin.saveDefaultConfig(); + config = plugin.getConfig(); + } + + public ConnectionFactory getRabbitConfig() { + ConnectionFactory connFac = new ConnectionFactory(); + String user = config.getString("rabbitmq.user", null); + if (user != null) { + connFac.setUsername(user); + } + String password = config.getString("rabbitmq.password", null); + if (password != null) { + connFac.setPassword(password); + } + String host = config.getString("rabbitmq.host", null); + if (host != null) { + connFac.setHost(host); + } + int port = config.getInt("rabbitmq.port", -1); + if (port != -1) { + connFac.setPort(port); + } + return connFac; + } + + public String getIncomingQueueName() { + return config.getString("rabbitmq.incomingQueue"); + } + + public String getOutgoingQueueName() { + return config.getString("rabbitmq.outgoingQueue"); + } + + public List getConsoleProcessors() { + List result = new LinkedList<>(); + ConfigurationSection section = config.getConfigurationSection("console"); + if (section == null) { + return result; + } + for(String key : section.getKeys(false)) { + if (!section.isConfigurationSection(key)) { + logger.warning("Ignoring invalid entry " + key + " at " + section.getCurrentPath()); + continue; + } + ConfigurationSection current = section.getConfigurationSection(key); + if (!current.isString("regex")) { + logger.warning("Console processor " + key + " has no regex and was ignored"); + continue; + } + String regex = current.getString("regex"); + try { + Pattern.compile(regex); + } + catch (PatternSyntaxException e) { + logger.warning("Regex at " + key + " is missformatted and was ignored"); + continue; + } + if (!current.isString("key")) { + logger.warning("Console processor " + key + " has no key and was ignored"); + continue; + } + String sectionKey = current.getString("key"); + result.add(new KiraLogAppender(sectionKey, regex)); + } + return result; + } + +} diff --git a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/KiraBukkitGatewayPlugin.java b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/KiraBukkitGatewayPlugin.java new file mode 100644 index 000000000..5d5c5b841 --- /dev/null +++ b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/KiraBukkitGatewayPlugin.java @@ -0,0 +1,117 @@ +package com.github.maxopoly.KiraBukkitGateway; + +import com.github.maxopoly.KiraBukkitGateway.auth.AuthcodeManager; +import com.github.maxopoly.KiraBukkitGateway.command.CreateDiscordGroupChatCommand; +import com.github.maxopoly.KiraBukkitGateway.command.DeleteDiscordGroupChatCommand; +import com.github.maxopoly.KiraBukkitGateway.command.GenerateDiscordAuthCodeCommand; +import com.github.maxopoly.KiraBukkitGateway.command.ReloadKiraCommand; +import com.github.maxopoly.KiraBukkitGateway.command.SyncDiscordChannelAccessCommand; +import com.github.maxopoly.KiraBukkitGateway.impersonation.KiraLuckPermsWrapper; +import com.github.maxopoly.KiraBukkitGateway.listener.CivChatListener; +import com.github.maxopoly.KiraBukkitGateway.listener.JukeAlertListener; +import com.github.maxopoly.KiraBukkitGateway.listener.SkynetListener; +import com.github.maxopoly.KiraBukkitGateway.log.KiraLogAppender; +import com.github.maxopoly.KiraBukkitGateway.rabbit.RabbitCommands; +import com.github.maxopoly.KiraBukkitGateway.rabbit.RabbitHandler; +import java.util.LinkedList; +import java.util.List; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.Logger; +import org.bukkit.Bukkit; +import vg.civcraft.mc.civmodcore.ACivMod; +import vg.civcraft.mc.civmodcore.commands.CommandManager; +import vg.civcraft.mc.namelayer.GroupManager.PlayerType; +import vg.civcraft.mc.namelayer.permission.PermissionType; + +public class KiraBukkitGatewayPlugin extends ACivMod { + + private static KiraBukkitGatewayPlugin instance; + + private RabbitHandler rabbit; + private RabbitCommands rabbitCommands; + private AuthcodeManager authcodeManager; + private KiraLuckPermsWrapper permsWrapper; + private ConfigParser config; + private List logAppenders; + private CommandManager commandManager; + + public void onEnable() { + super.onEnable(); + instance = this; + authcodeManager = new AuthcodeManager(12); + reload(); + setupPermissions(); + this.permsWrapper = new KiraLuckPermsWrapper(); + getServer().getPluginManager().registerEvents(new CivChatListener(), this); + getServer().getPluginManager().registerEvents(new JukeAlertListener(), this); + getServer().getPluginManager().registerEvents(new SkynetListener(), this); + getLogger().info("Successfully enabled " + getName()); + commandManager = new CommandManager(this); + commandManager.init(); + registerCommands(); + } + + private void registerCommands() { + commandManager.registerCommand(new CreateDiscordGroupChatCommand()); + commandManager.registerCommand(new DeleteDiscordGroupChatCommand()); + commandManager.registerCommand(new GenerateDiscordAuthCodeCommand()); + commandManager.registerCommand(new ReloadKiraCommand()); + commandManager.registerCommand(new SyncDiscordChannelAccessCommand()); + } + + public void reload() { + config = new ConfigParser(this); + if (rabbit != null) { + rabbit.shutdown(); + } + rabbit = new RabbitHandler(config.getRabbitConfig(), config.getIncomingQueueName(), + config.getOutgoingQueueName(), getLogger()); + if (!rabbit.setup()) { + Bukkit.getPluginManager().disablePlugin(this); + return; + } + rabbit.beginAsyncListen(); + rabbitCommands = new RabbitCommands(rabbit); + Logger logger = (Logger) LogManager.getRootLogger(); + if (logAppenders != null) { + for (KiraLogAppender appender : logAppenders) { + logger.removeAppender(appender); + appender.stop(); + } + logAppenders.clear(); + } + logAppenders = config.getConsoleProcessors(); + for (KiraLogAppender appender : config.getConsoleProcessors()) { + appender.start(); + logger.addAppender(appender); + } + } + + private void setupPermissions() { + List owners = new LinkedList<>(); + owners.add(PlayerType.OWNER); + PermissionType.registerPermission("KIRA_MANAGE_CHANNEL", owners); + } + + @Override + public void onDisable() { + rabbit.shutdown(); + } + + public static KiraBukkitGatewayPlugin getInstance() { + return instance; + } + + public AuthcodeManager getAuthcodeManager() { + return authcodeManager; + } + + public RabbitCommands getRabbit() { + return rabbitCommands; + } + + public KiraLuckPermsWrapper getPermsWrapper() { + return permsWrapper; + } + +} diff --git a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/KiraUtil.java b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/KiraUtil.java new file mode 100644 index 000000000..68f6adf08 --- /dev/null +++ b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/KiraUtil.java @@ -0,0 +1,11 @@ +package com.github.maxopoly.KiraBukkitGateway; + +import org.bukkit.ChatColor; + +public class KiraUtil { + + public static String cleanUp(String s) { + return ChatColor.stripColor(s).replaceAll("[^\\p{InBasic_Latin}\\p{InLatin-1Supplement}]", ""); + } + +} diff --git a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/auth/AuthcodeManager.java b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/auth/AuthcodeManager.java new file mode 100644 index 000000000..96769ab83 --- /dev/null +++ b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/auth/AuthcodeManager.java @@ -0,0 +1,43 @@ +package com.github.maxopoly.KiraBukkitGateway.auth; + +import java.security.SecureRandom; +import java.util.HashSet; +import java.util.Set; + +public class AuthcodeManager { + + private static final String validCharacters = "abcdefghijklmnopqrstuvwxyz0123456789"; + private static final int maxRecursionDepth = 20; + + private Set currentCodes; + private SecureRandom rng; + private int codeLength; + + + public AuthcodeManager(int codeLength) { + currentCodes = new HashSet<>(); + rng = new SecureRandom(); + this.codeLength = codeLength; + } + + public String getNewCode() { + return getNewCode(0); + } + + private String getNewCode(int recursion) { + if (recursion >= maxRecursionDepth) { + return null; + } + StringBuilder sb = new StringBuilder(codeLength); + for(int i = 0; i < 10;i++) { + int index = rng.nextInt(validCharacters.length()); + sb.append(validCharacters.charAt(index)); + } + String result = sb.toString(); + if (currentCodes.contains(result)) { + return getNewCode(recursion + 1); + } + currentCodes.add(result); + return result; + } +} diff --git a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/command/CreateDiscordGroupChatCommand.java b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/command/CreateDiscordGroupChatCommand.java new file mode 100644 index 000000000..d1bacca50 --- /dev/null +++ b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/command/CreateDiscordGroupChatCommand.java @@ -0,0 +1,42 @@ +package com.github.maxopoly.KiraBukkitGateway.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Syntax; +import com.github.maxopoly.KiraBukkitGateway.KiraBukkitGatewayPlugin; +import java.util.Collection; +import java.util.HashSet; +import java.util.UUID; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import vg.civcraft.mc.namelayer.GroupManager; +import vg.civcraft.mc.namelayer.NameAPI; +import vg.civcraft.mc.namelayer.group.Group; +import vg.civcraft.mc.namelayer.permission.PermissionType; + +public class CreateDiscordGroupChatCommand extends BaseCommand { + + @CommandAlias("linkdiscordchannel") + @Description("Create a Discord channel to which group chats and snitch alerts will be forwarded") + @Syntax("") + public void execute(Player player, String groupName) { + Group group = GroupManager.getGroup(groupName); + if (group == null) { + player.sendMessage(ChatColor.RED + "That group does not exist"); + return; + } + if (!NameAPI.getGroupManager().hasAccess(group, player.getUniqueId(), + PermissionType.getPermission("KIRA_MANAGE_CHANNEL"))) { + player.sendMessage(ChatColor.RED + "You do not have permission to do that"); + return; + } + GroupManager gm = NameAPI.getGroupManager(); + PermissionType perm = PermissionType.getPermission("READ_CHAT"); + Collection members = new HashSet<>(); + group.getAllMembers().stream().filter(m -> gm.hasAccess(group, m, perm)).forEach(m -> members.add(m)); + KiraBukkitGatewayPlugin.getInstance().getRabbit().createGroupChatChannel(group.getName(), members, + player.getUniqueId(), -1L, -1L); + player.sendMessage(ChatColor.GREEN + "Attempting to create channel..."); + } +} diff --git a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/command/DeleteDiscordGroupChatCommand.java b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/command/DeleteDiscordGroupChatCommand.java new file mode 100644 index 000000000..ed092f066 --- /dev/null +++ b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/command/DeleteDiscordGroupChatCommand.java @@ -0,0 +1,34 @@ +package com.github.maxopoly.KiraBukkitGateway.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Syntax; +import com.github.maxopoly.KiraBukkitGateway.KiraBukkitGatewayPlugin; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import vg.civcraft.mc.namelayer.GroupManager; +import vg.civcraft.mc.namelayer.NameAPI; +import vg.civcraft.mc.namelayer.group.Group; +import vg.civcraft.mc.namelayer.permission.PermissionType; + +public class DeleteDiscordGroupChatCommand extends BaseCommand { + + @CommandAlias("deletediscordchannel") + @Description("Delete the Discord channel linked to a group") + @Syntax("") + public void execute(Player player, String groupName) { + Group group = GroupManager.getGroup(groupName); + if (group == null) { + player.sendMessage(ChatColor.RED + "That group does not exist"); + return; + } + if (!NameAPI.getGroupManager().hasAccess(group, player.getUniqueId(), + PermissionType.getPermission("KIRA_MANAGE_CHANNEL"))) { + player.sendMessage(ChatColor.RED + "You do not have permission to do that"); + return; + } + KiraBukkitGatewayPlugin.getInstance().getRabbit().deleteGroupChatChannel(group.getName(),player.getUniqueId()); + player.sendMessage(ChatColor.GREEN + "Attempting to delete channel..."); + } +} diff --git a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/command/GenerateDiscordAuthCodeCommand.java b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/command/GenerateDiscordAuthCodeCommand.java new file mode 100644 index 000000000..9e83b2b6b --- /dev/null +++ b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/command/GenerateDiscordAuthCodeCommand.java @@ -0,0 +1,30 @@ +package com.github.maxopoly.KiraBukkitGateway.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.Description; +import com.github.maxopoly.KiraBukkitGateway.KiraBukkitGatewayPlugin; +import com.github.maxopoly.KiraBukkitGateway.rabbit.RabbitCommands; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +public class GenerateDiscordAuthCodeCommand extends BaseCommand { + + @CommandAlias("discordauth") + @Description("Create an auth code for linking your ingame account to your Discord account") + public void execute(Player sender) { + Player p = (Player) sender; + String code = KiraBukkitGatewayPlugin.getInstance().getAuthcodeManager().getNewCode(); + if (code == null) { + sender.sendMessage(ChatColor.RED + "Failed to generate code. You should probably tell an admin about this"); + return; + } + // lets make the code upper case to make it easier on people + code = code.toUpperCase(); + RabbitCommands rabbit = KiraBukkitGatewayPlugin.getInstance().getRabbit(); + rabbit.sendAuthCode(code, p.getName(), p.getUniqueId()); + sender.sendMessage(String.format( + "%sYour code is '%s'. Execute '/auth %s' in the official discord to authenticate and link your account. Note that upper/lower case does not matter.", + ChatColor.GOLD, code, code)); + } +} diff --git a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/command/ReloadKiraCommand.java b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/command/ReloadKiraCommand.java new file mode 100644 index 000000000..34b6f0cbc --- /dev/null +++ b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/command/ReloadKiraCommand.java @@ -0,0 +1,20 @@ +package com.github.maxopoly.KiraBukkitGateway.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import com.github.maxopoly.KiraBukkitGateway.KiraBukkitGatewayPlugin; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; + +public class ReloadKiraCommand extends BaseCommand { + + @CommandAlias("kirareload") + @CommandPermission("kira.op") + @Description("Reloads KiraBukkitGateway") + public void execute(CommandSender sender) { + KiraBukkitGatewayPlugin.getInstance().reload(); + sender.sendMessage(ChatColor.GREEN + "Reloaded KiraBukkitGateway"); + } +} diff --git a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/command/SyncDiscordChannelAccessCommand.java b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/command/SyncDiscordChannelAccessCommand.java new file mode 100644 index 000000000..80aa3985c --- /dev/null +++ b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/command/SyncDiscordChannelAccessCommand.java @@ -0,0 +1,43 @@ +package com.github.maxopoly.KiraBukkitGateway.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Syntax; +import com.github.maxopoly.KiraBukkitGateway.KiraBukkitGatewayPlugin; +import java.util.Collection; +import java.util.HashSet; +import java.util.UUID; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import vg.civcraft.mc.namelayer.GroupManager; +import vg.civcraft.mc.namelayer.NameAPI; +import vg.civcraft.mc.namelayer.group.Group; +import vg.civcraft.mc.namelayer.permission.PermissionType; + +public class SyncDiscordChannelAccessCommand extends BaseCommand { + + @CommandAlias("syncdiscordchannel") + @Description("Syncs access of the linked Discord channel") + @Syntax("") + public void execute(Player player, String groupName) { + Group group = GroupManager.getGroup(groupName); + if (group == null) { + player.sendMessage(ChatColor.RED + "That group does not exist"); + return; + } + if (!NameAPI.getGroupManager().hasAccess(group, player.getUniqueId(), + PermissionType.getPermission("KIRA_MANAGE_CHANNEL"))) { + player.sendMessage(ChatColor.RED + "You do not have permission to do that"); + return; + } + GroupManager gm = NameAPI.getGroupManager(); + PermissionType perm = PermissionType.getPermission("READ_CHAT"); + Collection members = new HashSet<>(); + group.getAllMembers().stream().filter(m -> gm.hasAccess(group, player.getUniqueId(), perm)) + .forEach(m -> members.add(m)); + KiraBukkitGatewayPlugin.getInstance().getRabbit().syncGroupChatAccess(group.getName(), members, + player.getUniqueId()); + player.sendMessage(ChatColor.GREEN + "Attempting to sync members..."); + } +} diff --git a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/impersonation/InvalidCommandAttemptException.java b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/impersonation/InvalidCommandAttemptException.java new file mode 100644 index 000000000..253d45e00 --- /dev/null +++ b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/impersonation/InvalidCommandAttemptException.java @@ -0,0 +1,15 @@ +package com.github.maxopoly.KiraBukkitGateway.impersonation; + +public class InvalidCommandAttemptException extends RuntimeException { + + private static final long serialVersionUID = -7864555241340731587L; + + public InvalidCommandAttemptException() { + super(); + } + + public InvalidCommandAttemptException(String msg) { + super(msg); + } + +} diff --git a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/impersonation/KiraLuckPermsWrapper.java b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/impersonation/KiraLuckPermsWrapper.java new file mode 100644 index 000000000..cddc92030 --- /dev/null +++ b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/impersonation/KiraLuckPermsWrapper.java @@ -0,0 +1,32 @@ +package com.github.maxopoly.KiraBukkitGateway.impersonation; + +import java.util.UUID; +import net.luckperms.api.LuckPerms; +import net.luckperms.api.LuckPermsProvider; +import net.luckperms.api.cacheddata.CachedPermissionData; +import net.luckperms.api.context.ContextManager; +import net.luckperms.api.model.user.User; +import net.luckperms.api.query.QueryOptions; +import net.luckperms.api.util.Tristate; + +public class KiraLuckPermsWrapper { + + private LuckPerms api; + + public KiraLuckPermsWrapper() { + api = LuckPermsProvider.get(); + } + + public boolean hasPermission(UUID uuid, String permission) { + User user = api.getUserManager().getUser(uuid); + if (user == null) { + return false; + } + ContextManager cm = api.getContextManager(); + QueryOptions queryOptions = cm.getQueryOptions(user).orElse(cm.getStaticQueryOptions()); + CachedPermissionData permissionData = user.getCachedData().getPermissionData(queryOptions); + Tristate checkResult = permissionData.checkPermission(permission); + return checkResult.asBoolean(); + } + +} diff --git a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/impersonation/PseudoConsoleSender.java b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/impersonation/PseudoConsoleSender.java new file mode 100644 index 000000000..db0c449fb --- /dev/null +++ b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/impersonation/PseudoConsoleSender.java @@ -0,0 +1,192 @@ +package com.github.maxopoly.KiraBukkitGateway.impersonation; + +import com.github.maxopoly.KiraBukkitGateway.KiraBukkitGatewayPlugin; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import net.kyori.adventure.text.Component; +import org.bukkit.Server; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.conversations.Conversation; +import org.bukkit.conversations.ConversationAbandonedEvent; +import org.bukkit.permissions.Permission; +import org.bukkit.permissions.PermissionAttachment; +import org.bukkit.permissions.PermissionAttachmentInfo; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; + +public class PseudoConsoleSender implements ConsoleCommandSender { + + private ConsoleCommandSender actualSender; + private List replies; + private UUID actualUser; + private long discordChannelId; + + public PseudoConsoleSender(UUID actualUser, ConsoleCommandSender actualSender, long discordChannelId) { + this.actualSender = actualSender; + replies = new LinkedList<>(); + this.actualUser = actualUser; + this.discordChannelId = discordChannelId; + } + + public synchronized List getRepliesAndFinish() { + List result = replies; + replies = null; + return result; + } + + private synchronized void handleReply(String input) { + if (replies != null) { + replies.add(input); + return; + } + KiraBukkitGatewayPlugin.getInstance().getRabbit().replyToUser(actualUser, input, discordChannelId); + } + + @Override + public String getName() { + return actualSender.getName(); + } + + @Override + public Server getServer() { + return actualSender.getServer(); + } + + @Override + public void sendMessage(String arg0) { + handleReply(arg0); + } + + @Override + public void sendMessage(String[] arg0) { + for (String s : arg0) { + handleReply(s); + } + } + + @Override + public void sendMessage(@Nullable final UUID uuid, @Nonnull final String message) { + this.actualSender.sendMessage(uuid, message); + } + + @Override + public void sendMessage(@Nullable final UUID uuid, @Nonnull final String[] messages) { + this.actualSender.sendMessage(uuid, messages); + } + + @Override + public Spigot spigot() { + return actualSender.spigot(); + } + + @Override + public @NotNull Component name() { + return this.actualSender.name(); + } + + @Override + public PermissionAttachment addAttachment(Plugin arg0) { + return actualSender.addAttachment(arg0); + } + + @Override + public PermissionAttachment addAttachment(Plugin arg0, int arg1) { + return actualSender.addAttachment(arg0, arg1); + } + + @Override + public PermissionAttachment addAttachment(Plugin arg0, String arg1, boolean arg2) { + return actualSender.addAttachment(arg0, arg1, arg2); + } + + @Override + public PermissionAttachment addAttachment(Plugin arg0, String arg1, boolean arg2, int arg3) { + return actualSender.addAttachment(arg0, arg1, arg2, arg3); + } + + @Override + public Set getEffectivePermissions() { + return actualSender.getEffectivePermissions(); + } + + @Override + public boolean hasPermission(String arg0) { + return actualSender.hasPermission(arg0); + } + + @Override + public boolean hasPermission(Permission arg0) { + return actualSender.hasPermission(arg0); + } + + @Override + public boolean isPermissionSet(String arg0) { + return actualSender.isPermissionSet(arg0); + } + + @Override + public boolean isPermissionSet(Permission arg0) { + return actualSender.isPermissionSet(arg0); + } + + @Override + public void recalculatePermissions() { + actualSender.recalculatePermissions(); + } + + @Override + public void removeAttachment(PermissionAttachment arg0) { + actualSender.removeAttachment(arg0); + } + + @Override + public boolean isOp() { + return actualSender.isOp(); + } + + @Override + public void setOp(boolean arg0) { + actualSender.setOp(arg0); + } + + @Override + public void abandonConversation(Conversation arg0) { + actualSender.abandonConversation(arg0); + } + + @Override + public void abandonConversation(Conversation arg0, ConversationAbandonedEvent arg1) { + actualSender.abandonConversation(arg0, arg1); + } + + @Override + public void acceptConversationInput(String arg0) { + actualSender.acceptConversationInput(arg0); + + } + + @Override + public boolean beginConversation(Conversation arg0) { + return actualSender.beginConversation(arg0); + } + + @Override + public boolean isConversing() { + return actualSender.isConversing(); + } + + @Override + public void sendRawMessage(String arg0) { + handleReply(arg0); + } + + @Override + public void sendRawMessage(@Nullable final UUID uuid, @Nonnull final String s) { + + } + +} diff --git a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/impersonation/PseudoPlayer.java b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/impersonation/PseudoPlayer.java new file mode 100644 index 000000000..d8f039296 --- /dev/null +++ b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/impersonation/PseudoPlayer.java @@ -0,0 +1,1455 @@ +package com.github.maxopoly.KiraBukkitGateway.impersonation; + +import com.github.maxopoly.KiraBukkitGateway.KiraBukkitGatewayPlugin; +import java.net.InetSocketAddress; +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import org.bukkit.Bukkit; +import org.bukkit.Effect; +import org.bukkit.EntityEffect; +import org.bukkit.GameMode; +import org.bukkit.Instrument; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Note; +import org.bukkit.OfflinePlayer; +import org.bukkit.Particle; +import org.bukkit.Server; +import org.bukkit.Sound; +import org.bukkit.SoundCategory; +import org.bukkit.Statistic; +import org.bukkit.WeatherType; +import org.bukkit.World; +import org.bukkit.advancement.Advancement; +import org.bukkit.advancement.AdvancementProgress; +import org.bukkit.attribute.Attribute; +import org.bukkit.attribute.AttributeInstance; +import org.bukkit.block.Block; +import org.bukkit.block.PistonMoveReaction; +import org.bukkit.conversations.Conversation; +import org.bukkit.conversations.ConversationAbandonedEvent; +import org.bukkit.craftbukkit.v1_18_R2.CraftServer; +import org.bukkit.craftbukkit.v1_18_R2.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_18_R2.scoreboard.CraftScoreboard; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; +import org.bukkit.entity.Villager; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; +import org.bukkit.inventory.EntityEquipment; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryView; +import org.bukkit.inventory.InventoryView.Property; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.MainHand; +import org.bukkit.inventory.Merchant; +import org.bukkit.inventory.PlayerInventory; +import org.bukkit.map.MapView; +import org.bukkit.metadata.MetadataValue; +import org.bukkit.permissions.Permission; +import org.bukkit.permissions.PermissionAttachment; +import org.bukkit.permissions.PermissionAttachmentInfo; +import org.bukkit.plugin.Plugin; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.scoreboard.Scoreboard; +import org.bukkit.util.Vector; + +public class PseudoPlayer extends CraftPlayer { + + private String name; + private UUID uuid; + private OfflinePlayer offlinePlayer; + private List replies; + private long discordChannelId; + private PseudoSpigotPlayer spigotPlayer; + + public PseudoPlayer(UUID uuid, long channelId) { + super((CraftServer) Bukkit.getServer(), PseudoPlayerIdentity.generate(uuid, "")); + if (uuid == null) { + throw new IllegalArgumentException("No null uuid allowed"); + } + offlinePlayer = Bukkit.getOfflinePlayer(uuid); + if (offlinePlayer == null) { + throw new IllegalArgumentException("No such player known: " + uuid.toString()); + } + name = offlinePlayer.getName(); + this.discordChannelId = channelId; + this.uuid = uuid; + this.spigotPlayer = new PseudoSpigotPlayer(this); + replies = new LinkedList<>(); + } + + public synchronized List collectReplies() { + List replyCopy = replies; + replies = null; + return replyCopy; + } + + public OfflinePlayer getOfflinePlayer() { + return offlinePlayer; + } + + public void closeInventory() { + throw new InvalidCommandAttemptException(); + } + + public int getCooldown(Material arg0) { + throw new InvalidCommandAttemptException(); + } + + public Inventory getEnderChest() { + throw new InvalidCommandAttemptException(); + } + + public int getExpToLevel() { + throw new InvalidCommandAttemptException(); + } + + public GameMode getGameMode() { + throw new InvalidCommandAttemptException(); + } + + public PlayerInventory getInventory() { + throw new InvalidCommandAttemptException(); + } + + public ItemStack getItemInHand() { + throw new InvalidCommandAttemptException(); + } + + public ItemStack getItemOnCursor() { + throw new InvalidCommandAttemptException(); + } + + public MainHand getMainHand() { + throw new InvalidCommandAttemptException(); + } + + public String getName() { + return name; + } + + public InventoryView getOpenInventory() { + throw new InvalidCommandAttemptException(); + } + + public Entity getShoulderEntityLeft() { + throw new InvalidCommandAttemptException(); + } + + public Entity getShoulderEntityRight() { + throw new InvalidCommandAttemptException(); + } + + public int getSleepTicks() { + throw new InvalidCommandAttemptException(); + } + + public boolean hasCooldown(Material arg0) { + throw new InvalidCommandAttemptException(); + } + + public boolean isBlocking() { + throw new InvalidCommandAttemptException(); + } + + public boolean isHandRaised() { + throw new InvalidCommandAttemptException(); + } + + public boolean isSleeping() { + throw new InvalidCommandAttemptException(); + } + + public InventoryView openEnchanting(Location arg0, boolean arg1) { + throw new InvalidCommandAttemptException(); + } + + public InventoryView openInventory(Inventory arg0) { + throw new InvalidCommandAttemptException(); + } + + public void openInventory(InventoryView arg0) { + throw new InvalidCommandAttemptException(); + } + + public InventoryView openMerchant(Villager arg0, boolean arg1) { + throw new InvalidCommandAttemptException(); + } + + public InventoryView openMerchant(Merchant arg0, boolean arg1) { + throw new InvalidCommandAttemptException(); + } + + public InventoryView openWorkbench(Location arg0, boolean arg1) { + throw new InvalidCommandAttemptException(); + } + + public void setCooldown(Material arg0, int arg1) { + throw new InvalidCommandAttemptException(); + } + + public void setGameMode(GameMode arg0) { + throw new InvalidCommandAttemptException(); + } + + public void setItemInHand(ItemStack arg0) { + throw new InvalidCommandAttemptException(); + } + + public void setItemOnCursor(ItemStack arg0) { + throw new InvalidCommandAttemptException(); + } + + public void setShoulderEntityLeft(Entity arg0) { + throw new InvalidCommandAttemptException(); + } + + public void setShoulderEntityRight(Entity arg0) { + throw new InvalidCommandAttemptException(); + } + + public boolean setWindowProperty(Property arg0, int arg1) { + throw new InvalidCommandAttemptException(); + } + + public boolean addPotionEffect(PotionEffect arg0) { + throw new InvalidCommandAttemptException(); + } + + public boolean addPotionEffect(PotionEffect arg0, boolean arg1) { + throw new InvalidCommandAttemptException(); + } + + public boolean addPotionEffects(Collection arg0) { + throw new InvalidCommandAttemptException(); + } + + public Collection getActivePotionEffects() { + throw new InvalidCommandAttemptException(); + } + + public boolean getCanPickupItems() { + throw new InvalidCommandAttemptException(); + } + + public EntityEquipment getEquipment() { + throw new InvalidCommandAttemptException(); + } + + public double getEyeHeight() { + throw new InvalidCommandAttemptException(); + } + + public double getEyeHeight(boolean arg0) { + throw new InvalidCommandAttemptException(); + } + + public Location getEyeLocation() { + throw new InvalidCommandAttemptException(); + } + + public Player getKiller() { + throw new InvalidCommandAttemptException(); + } + + public double getLastDamage() { + throw new InvalidCommandAttemptException(); + } + + public List getLastTwoTargetBlocks(Set arg0, int arg1) { + throw new InvalidCommandAttemptException(); + } + + public Entity getLeashHolder() throws IllegalStateException { + throw new InvalidCommandAttemptException(); + } + + public List getLineOfSight(Set arg0, int arg1) { + throw new InvalidCommandAttemptException(); + } + + public int getMaximumAir() { + throw new InvalidCommandAttemptException(); + } + + public int getMaximumNoDamageTicks() { + throw new InvalidCommandAttemptException(); + } + + public int getNoDamageTicks() { + throw new InvalidCommandAttemptException(); + } + + public PotionEffect getPotionEffect(PotionEffectType arg0) { + throw new InvalidCommandAttemptException(); + } + + public int getRemainingAir() { + throw new InvalidCommandAttemptException(); + } + + public boolean getRemoveWhenFarAway() { + throw new InvalidCommandAttemptException(); + } + + public Block getTargetBlock(Set arg0, int arg1) { + throw new InvalidCommandAttemptException(); + } + + public boolean hasAI() { + throw new InvalidCommandAttemptException(); + } + + public boolean hasLineOfSight(Entity arg0) { + throw new InvalidCommandAttemptException(); + } + + public boolean hasPotionEffect(PotionEffectType arg0) { + throw new InvalidCommandAttemptException(); + } + + public boolean isCollidable() { + throw new InvalidCommandAttemptException(); + } + + public boolean isGliding() { + throw new InvalidCommandAttemptException(); + } + + public boolean isLeashed() { + throw new InvalidCommandAttemptException(); + } + + public void removePotionEffect(PotionEffectType arg0) { + throw new InvalidCommandAttemptException(); + } + + public void setAI(boolean arg0) { + throw new InvalidCommandAttemptException(); + } + + public void setCanPickupItems(boolean arg0) { + throw new InvalidCommandAttemptException(); + } + + public void setCollidable(boolean arg0) { + throw new InvalidCommandAttemptException(); + } + + public void setGliding(boolean arg0) { + throw new InvalidCommandAttemptException(); + } + + public void setLastDamage(double arg0) { + throw new InvalidCommandAttemptException(); + } + + public boolean setLeashHolder(Entity arg0) { + throw new InvalidCommandAttemptException(); + } + + public void setMaximumAir(int arg0) { + throw new InvalidCommandAttemptException(); + } + + public void setMaximumNoDamageTicks(int arg0) { + throw new InvalidCommandAttemptException(); + } + + public void setNoDamageTicks(int arg0) { + throw new InvalidCommandAttemptException(); + } + + public void setRemainingAir(int arg0) { + throw new InvalidCommandAttemptException(); + } + + public void setRemoveWhenFarAway(boolean arg0) { + throw new InvalidCommandAttemptException(); + } + + public AttributeInstance getAttribute(Attribute arg0) { + throw new InvalidCommandAttemptException(); + } + + public boolean addPassenger(Entity arg0) { + throw new InvalidCommandAttemptException(); + } + + public boolean addScoreboardTag(String arg0) { + throw new InvalidCommandAttemptException(); + } + + public boolean eject() { + throw new InvalidCommandAttemptException(); + } + + public int getEntityId() { + throw new InvalidCommandAttemptException(); + } + + public float getFallDistance() { + throw new InvalidCommandAttemptException(); + } + + public int getFireTicks() { + throw new InvalidCommandAttemptException(); + } + + public double getHeight() { + throw new InvalidCommandAttemptException(); + } + + public EntityDamageEvent getLastDamageCause() { + throw new InvalidCommandAttemptException(); + } + + public Location getLocation() { + return new Location(Bukkit.getWorlds().get(0), 0, -1000, 0); + } + + public Location getLocation(Location arg0) { + return new Location(Bukkit.getWorlds().get(0), 0, -1000, 0); + } + + public int getMaxFireTicks() { + throw new InvalidCommandAttemptException(); + } + + public List getNearbyEntities(double arg0, double arg1, double arg2) { + throw new InvalidCommandAttemptException(); + } + + public Entity getPassenger() { + throw new InvalidCommandAttemptException(); + } + + public List getPassengers() { + throw new InvalidCommandAttemptException(); + } + + public PistonMoveReaction getPistonMoveReaction() { + throw new InvalidCommandAttemptException(); + } + + public int getPortalCooldown() { + throw new InvalidCommandAttemptException(); + } + + public Set getScoreboardTags() { + throw new InvalidCommandAttemptException(); + } + + public Server getServer() { + throw new InvalidCommandAttemptException(); + } + + public int getTicksLived() { + throw new InvalidCommandAttemptException(); + } + + public EntityType getType() { + return EntityType.PLAYER; + } + + public UUID getUniqueId() { + return uuid; + } + + public Entity getVehicle() { + throw new InvalidCommandAttemptException(); + } + + public Vector getVelocity() { + throw new InvalidCommandAttemptException(); + } + + public double getWidth() { + throw new InvalidCommandAttemptException(); + } + + public World getWorld() { + throw new InvalidCommandAttemptException(); + } + + public boolean hasGravity() { + throw new InvalidCommandAttemptException(); + } + + public boolean isCustomNameVisible() { + throw new InvalidCommandAttemptException(); + } + + public boolean isDead() { + throw new InvalidCommandAttemptException(); + } + + public boolean isEmpty() { + throw new InvalidCommandAttemptException(); + } + + public boolean isGlowing() { + throw new InvalidCommandAttemptException(); + } + + public boolean isInsideVehicle() { + throw new InvalidCommandAttemptException(); + } + + public boolean isInvulnerable() { + throw new InvalidCommandAttemptException(); + } + + public boolean isOnGround() { + throw new InvalidCommandAttemptException(); + } + + public boolean isSilent() { + throw new InvalidCommandAttemptException(); + } + + public boolean isValid() { + throw new InvalidCommandAttemptException(); + } + + public boolean leaveVehicle() { + throw new InvalidCommandAttemptException(); + } + + public void playEffect(EntityEffect arg0) { + throw new InvalidCommandAttemptException(); + } + + public void remove() { + throw new InvalidCommandAttemptException(); + } + + public boolean removePassenger(Entity arg0) { + throw new InvalidCommandAttemptException(); + } + + public boolean removeScoreboardTag(String arg0) { + throw new InvalidCommandAttemptException(); + } + + public void setCustomNameVisible(boolean arg0) { + throw new InvalidCommandAttemptException(); + } + + public void setFallDistance(float arg0) { + throw new InvalidCommandAttemptException(); + } + + public void setFireTicks(int arg0) { + throw new InvalidCommandAttemptException(); + } + + public void setGlowing(boolean arg0) { + throw new InvalidCommandAttemptException(); + } + + public void setGravity(boolean arg0) { + throw new InvalidCommandAttemptException(); + } + + public void setInvulnerable(boolean arg0) { + throw new InvalidCommandAttemptException(); + } + + public void setLastDamageCause(EntityDamageEvent arg0) { + throw new InvalidCommandAttemptException(); + } + + public boolean setPassenger(Entity arg0) { + throw new InvalidCommandAttemptException(); + } + + public void setPortalCooldown(int arg0) { + throw new InvalidCommandAttemptException(); + } + + public void setSilent(boolean arg0) { + throw new InvalidCommandAttemptException(); + } + + public void setTicksLived(int arg0) { + throw new InvalidCommandAttemptException(); + } + + public void setVelocity(Vector arg0) { + throw new InvalidCommandAttemptException(); + } + + public boolean teleport(Location arg0) { + throw new InvalidCommandAttemptException(); + } + + public boolean teleport(Entity arg0) { + throw new InvalidCommandAttemptException(); + } + + public boolean teleport(Location arg0, TeleportCause arg1) { + throw new InvalidCommandAttemptException(); + } + + public boolean teleport(Entity arg0, TeleportCause arg1) { + throw new InvalidCommandAttemptException(); + } + + public List getMetadata(String arg0) { + throw new InvalidCommandAttemptException(); + } + + + public boolean hasMetadata(String arg0) { + throw new InvalidCommandAttemptException(); + } + + + public void removeMetadata(String arg0, Plugin arg1) { + throw new InvalidCommandAttemptException(); + } + + + public void setMetadata(String arg0, MetadataValue arg1) { + throw new InvalidCommandAttemptException(); + } + + + public synchronized void sendMessage(String msg) { + if (replies == null) { + KiraBukkitGatewayPlugin.getInstance().getRabbit().replyToUser(uuid, msg, discordChannelId); + } else { + replies.add(msg); + } + } + + + public void sendMessage(String[] arg0) { + StringBuilder sb = new StringBuilder(); + Arrays.stream(arg0).forEach(s -> sb.append(s + '\n')); + sendMessage(sb.toString()); + } + + + public PermissionAttachment addAttachment(Plugin arg0) { + throw new InvalidCommandAttemptException(); + } + + + public PermissionAttachment addAttachment(Plugin arg0, int arg1) { + throw new InvalidCommandAttemptException(); + } + + + public PermissionAttachment addAttachment(Plugin arg0, String arg1, boolean arg2) { + throw new InvalidCommandAttemptException(); + } + + + public PermissionAttachment addAttachment(Plugin arg0, String arg1, boolean arg2, int arg3) { + throw new InvalidCommandAttemptException(); + } + + + public Set getEffectivePermissions() { + throw new InvalidCommandAttemptException(); + } + + + public boolean hasPermission(String arg0) { + return KiraBukkitGatewayPlugin.getInstance().getPermsWrapper().hasPermission(uuid, arg0); + } + + + public boolean hasPermission(Permission arg0) { + return KiraBukkitGatewayPlugin.getInstance().getPermsWrapper().hasPermission(uuid, arg0.getName()); + } + + + public boolean isPermissionSet(String arg0) { + throw new InvalidCommandAttemptException(); + } + + + public boolean isPermissionSet(Permission arg0) { + throw new InvalidCommandAttemptException(); + } + + + public void recalculatePermissions() { + throw new InvalidCommandAttemptException(); + } + + + public void removeAttachment(PermissionAttachment arg0) { + throw new InvalidCommandAttemptException(); + } + + + public boolean isOp() { + return false; + } + + + public void setOp(boolean arg0) { + throw new InvalidCommandAttemptException(); + } + + + public String getCustomName() { + throw new InvalidCommandAttemptException(); + } + + + public void setCustomName(String arg0) { + throw new InvalidCommandAttemptException(); + } + + + public void damage(double arg0) { + throw new InvalidCommandAttemptException(); + } + + + public void damage(double arg0, Entity arg1) { + throw new InvalidCommandAttemptException(); + } + + + public double getHealth() { + throw new InvalidCommandAttemptException(); + } + + + public double getMaxHealth() { + throw new InvalidCommandAttemptException(); + } + + + public void resetMaxHealth() { + throw new InvalidCommandAttemptException(); + } + + + public void setHealth(double arg0) { + throw new InvalidCommandAttemptException(); + } + + + public void setMaxHealth(double arg0) { + throw new InvalidCommandAttemptException(); + } + + + public T launchProjectile(Class arg0) { + throw new InvalidCommandAttemptException(); + } + + + public T launchProjectile(Class arg0, Vector arg1) { + throw new InvalidCommandAttemptException(); + } + + + public void abandonConversation(Conversation arg0) { + throw new InvalidCommandAttemptException(); + } + + + public void abandonConversation(Conversation arg0, ConversationAbandonedEvent arg1) { + throw new InvalidCommandAttemptException(); + } + + + public void acceptConversationInput(String arg0) { + throw new InvalidCommandAttemptException(); + } + + + public boolean beginConversation(Conversation arg0) { + throw new InvalidCommandAttemptException(); + } + + + public boolean isConversing() { + throw new InvalidCommandAttemptException(); + } + + + public long getFirstPlayed() { + throw new InvalidCommandAttemptException(); + } + + + public long getLastPlayed() { + throw new InvalidCommandAttemptException(); + } + + + public Player getPlayer() { + throw new InvalidCommandAttemptException(); + } + + + public boolean hasPlayedBefore() { + throw new InvalidCommandAttemptException(); + } + + + public boolean isBanned() { + throw new InvalidCommandAttemptException(); + } + + + public boolean isOnline() { + throw new InvalidCommandAttemptException(); + } + + + public boolean isWhitelisted() { + throw new InvalidCommandAttemptException(); + } + + + public void setWhitelisted(boolean arg0) { + throw new InvalidCommandAttemptException(); + } + + + public Map serialize() { + throw new InvalidCommandAttemptException(); + } + + + public Set getListeningPluginChannels() { + throw new InvalidCommandAttemptException(); + } + + + public void sendPluginMessage(Plugin arg0, String arg1, byte[] arg2) { + throw new InvalidCommandAttemptException(); + } + + + public boolean canSee(Player arg0) { + throw new InvalidCommandAttemptException(); + } + + + public void chat(String arg0) { + throw new InvalidCommandAttemptException(); + } + + + public void decrementStatistic(Statistic arg0) throws IllegalArgumentException { + throw new InvalidCommandAttemptException(); + } + + + public void decrementStatistic(Statistic arg0, int arg1) throws IllegalArgumentException { + throw new InvalidCommandAttemptException(); + } + + + public void decrementStatistic(Statistic arg0, Material arg1) throws IllegalArgumentException { + throw new InvalidCommandAttemptException(); + } + + + public void decrementStatistic(Statistic arg0, EntityType arg1) throws IllegalArgumentException { + throw new InvalidCommandAttemptException(); + } + + + public void decrementStatistic(Statistic arg0, Material arg1, int arg2) throws IllegalArgumentException { + throw new InvalidCommandAttemptException(); + } + + + public void decrementStatistic(Statistic arg0, EntityType arg1, int arg2) { + throw new InvalidCommandAttemptException(); + } + + + public InetSocketAddress getAddress() { + throw new InvalidCommandAttemptException(); + } + + + public AdvancementProgress getAdvancementProgress(Advancement arg0) { + throw new InvalidCommandAttemptException(); + } + + + public boolean getAllowFlight() { + throw new InvalidCommandAttemptException(); + } + + + public Location getBedSpawnLocation() { + return offlinePlayer.getBedSpawnLocation(); + } + + + public Location getCompassTarget() { + throw new InvalidCommandAttemptException(); + } + + + public String getDisplayName() { + return name; + } + + + public float getExhaustion() { + throw new InvalidCommandAttemptException(); + } + + + public float getExp() { + throw new InvalidCommandAttemptException(); + } + + + public float getFlySpeed() { + throw new InvalidCommandAttemptException(); + } + + + public int getFoodLevel() { + throw new InvalidCommandAttemptException(); + } + + + public double getHealthScale() { + throw new InvalidCommandAttemptException(); + } + + + public int getLevel() { + throw new InvalidCommandAttemptException(); + } + + + public String getLocale() { + throw new InvalidCommandAttemptException(); + } + + + public String getPlayerListName() { + throw new InvalidCommandAttemptException(); + } + + + public long getPlayerTime() { + throw new InvalidCommandAttemptException(); + } + + + public long getPlayerTimeOffset() { + throw new InvalidCommandAttemptException(); + } + + + public WeatherType getPlayerWeather() { + throw new InvalidCommandAttemptException(); + } + + + public float getSaturation() { + throw new InvalidCommandAttemptException(); + } + + + public CraftScoreboard getScoreboard() { + throw new InvalidCommandAttemptException(); + } + + + public Entity getSpectatorTarget() { + throw new InvalidCommandAttemptException(); + } + + + public int getStatistic(Statistic arg0) throws IllegalArgumentException { + throw new InvalidCommandAttemptException(); + } + + + public int getStatistic(Statistic arg0, Material arg1) throws IllegalArgumentException { + throw new InvalidCommandAttemptException(); + } + + + public int getStatistic(Statistic arg0, EntityType arg1) throws IllegalArgumentException { + throw new InvalidCommandAttemptException(); + } + + + public int getTotalExperience() { + throw new InvalidCommandAttemptException(); + } + + + public float getWalkSpeed() { + throw new InvalidCommandAttemptException(); + } + + + public void giveExp(int arg0) { + throw new InvalidCommandAttemptException(); + } + + + public void giveExpLevels(int arg0) { + throw new InvalidCommandAttemptException(); + } + + + public void hidePlayer(Player arg0) { + throw new InvalidCommandAttemptException(); + } + + + public void hidePlayer(Plugin arg0, Player arg1) { + throw new InvalidCommandAttemptException(); + } + + + public void incrementStatistic(Statistic arg0) throws IllegalArgumentException { + throw new InvalidCommandAttemptException(); + } + + + public void incrementStatistic(Statistic arg0, int arg1) throws IllegalArgumentException { + throw new InvalidCommandAttemptException(); + } + + + public void incrementStatistic(Statistic arg0, Material arg1) throws IllegalArgumentException { + throw new InvalidCommandAttemptException(); + } + + + public void incrementStatistic(Statistic arg0, EntityType arg1) throws IllegalArgumentException { + throw new InvalidCommandAttemptException(); + } + + + public void incrementStatistic(Statistic arg0, Material arg1, int arg2) throws IllegalArgumentException { + throw new InvalidCommandAttemptException(); + } + + + public void incrementStatistic(Statistic arg0, EntityType arg1, int arg2) throws IllegalArgumentException { + throw new InvalidCommandAttemptException(); + } + + + public boolean isFlying() { + throw new InvalidCommandAttemptException(); + } + + + public boolean isHealthScaled() { + throw new InvalidCommandAttemptException(); + } + + + public boolean isPlayerTimeRelative() { + throw new InvalidCommandAttemptException(); + } + + + public boolean isSleepingIgnored() { + throw new InvalidCommandAttemptException(); + } + + + public boolean isSneaking() { + throw new InvalidCommandAttemptException(); + } + + + public boolean isSprinting() { + throw new InvalidCommandAttemptException(); + } + + + public void kickPlayer(String arg0) { + throw new InvalidCommandAttemptException(); + } + + + public void loadData() { + throw new InvalidCommandAttemptException(); + } + + + public boolean performCommand(String arg0) { + throw new InvalidCommandAttemptException(); + } + + + public void playEffect(Location arg0, Effect arg1, int arg2) { + throw new InvalidCommandAttemptException(); + } + + + public void playEffect(Location arg0, Effect arg1, T arg2) { + throw new InvalidCommandAttemptException(); + } + + + public void playNote(Location arg0, byte arg1, byte arg2) { + throw new InvalidCommandAttemptException(); + } + + + public void playNote(Location arg0, Instrument arg1, Note arg2) { + throw new InvalidCommandAttemptException(); + } + + + public void playSound(Location arg0, Sound arg1, float arg2, float arg3) { + throw new InvalidCommandAttemptException(); + } + + + public void playSound(Location arg0, String arg1, float arg2, float arg3) { + throw new InvalidCommandAttemptException(); + } + + + public void playSound(Location arg0, Sound arg1, SoundCategory arg2, float arg3, float arg4) { + throw new InvalidCommandAttemptException(); + } + + + public void playSound(Location arg0, String arg1, SoundCategory arg2, float arg3, float arg4) { + throw new InvalidCommandAttemptException(); + } + + + public void resetPlayerTime() { + throw new InvalidCommandAttemptException(); + } + + + public void resetPlayerWeather() { + throw new InvalidCommandAttemptException(); + } + + + public void resetTitle() { + throw new InvalidCommandAttemptException(); + } + + + public void saveData() { + throw new InvalidCommandAttemptException(); + } + + + public void sendBlockChange(Location arg0, Material arg1, byte arg2) { + throw new InvalidCommandAttemptException(); + } + + + public boolean sendChunkChange(Location arg0, int arg1, int arg2, int arg3, byte[] arg4) { + throw new InvalidCommandAttemptException(); + } + + + public void sendMap(MapView arg0) { + throw new InvalidCommandAttemptException(); + } + + + public void sendRawMessage(String arg0) { + throw new InvalidCommandAttemptException(); + } + + + public void sendSignChange(Location arg0, String[] arg1) throws IllegalArgumentException { + throw new InvalidCommandAttemptException(); + } + + + public void sendTitle(String arg0, String arg1) { + throw new InvalidCommandAttemptException(); + } + + + public void sendTitle(String arg0, String arg1, int arg2, int arg3, int arg4) { + throw new InvalidCommandAttemptException(); + } + + + public void setAllowFlight(boolean arg0) { + throw new InvalidCommandAttemptException(); + } + + + public void setBedSpawnLocation(Location arg0) { + throw new InvalidCommandAttemptException(); + } + + + public void setBedSpawnLocation(Location arg0, boolean arg1) { + throw new InvalidCommandAttemptException(); + } + + + public void setCompassTarget(Location arg0) { + throw new InvalidCommandAttemptException(); + } + + + public void setDisplayName(String arg0) { + throw new InvalidCommandAttemptException(); + } + + + public void setExhaustion(float arg0) { + throw new InvalidCommandAttemptException(); + } + + + public void setExp(float arg0) { + throw new InvalidCommandAttemptException(); + } + + + public void setFlySpeed(float arg0) throws IllegalArgumentException { + throw new InvalidCommandAttemptException(); + } + + + public void setFlying(boolean arg0) { + throw new InvalidCommandAttemptException(); + } + + + public void setFoodLevel(int arg0) { + throw new InvalidCommandAttemptException(); + } + + + public void setHealthScale(double arg0) throws IllegalArgumentException { + throw new InvalidCommandAttemptException(); + } + + + public void setHealthScaled(boolean arg0) { + throw new InvalidCommandAttemptException(); + } + + + public void setLevel(int arg0) { + throw new InvalidCommandAttemptException(); + } + + + public void setPlayerListName(String arg0) { + throw new InvalidCommandAttemptException(); + } + + + public void setPlayerTime(long arg0, boolean arg1) { + throw new InvalidCommandAttemptException(); + } + + + public void setPlayerWeather(WeatherType arg0) { + throw new InvalidCommandAttemptException(); + } + + + public void setResourcePack(String arg0) { + throw new InvalidCommandAttemptException(); + } + + + public void setResourcePack(String arg0, byte[] arg1) { + throw new InvalidCommandAttemptException(); + } + + + public void setSaturation(float arg0) { + throw new InvalidCommandAttemptException(); + } + + + public void setScoreboard(Scoreboard arg0) throws IllegalArgumentException, IllegalStateException { + throw new InvalidCommandAttemptException(); + } + + + public void setSleepingIgnored(boolean arg0) { + throw new InvalidCommandAttemptException(); + } + + + public void setSneaking(boolean arg0) { + throw new InvalidCommandAttemptException(); + } + + + public void setSpectatorTarget(Entity arg0) { + throw new InvalidCommandAttemptException(); + } + + + public void setSprinting(boolean arg0) { + throw new InvalidCommandAttemptException(); + } + + + public void setStatistic(Statistic arg0, int arg1) throws IllegalArgumentException { + throw new InvalidCommandAttemptException(); + } + + + public void setStatistic(Statistic arg0, Material arg1, int arg2) throws IllegalArgumentException { + throw new InvalidCommandAttemptException(); + } + + + public void setStatistic(Statistic arg0, EntityType arg1, int arg2) { + throw new InvalidCommandAttemptException(); + } + + + public void setTexturePack(String arg0) { + throw new InvalidCommandAttemptException(); + } + + + public void setTotalExperience(int arg0) { + throw new InvalidCommandAttemptException(); + } + + + public void setWalkSpeed(float arg0) throws IllegalArgumentException { + throw new InvalidCommandAttemptException(); + } + + + public void showPlayer(Player arg0) { + throw new InvalidCommandAttemptException(); + } + + + public void showPlayer(Plugin arg0, Player arg1) { + throw new InvalidCommandAttemptException(); + } + + + public void spawnParticle(Particle arg0, Location arg1, int arg2) { + throw new InvalidCommandAttemptException(); + } + + + public void spawnParticle(Particle arg0, Location arg1, int arg2, T arg3) { + throw new InvalidCommandAttemptException(); + } + + + public void spawnParticle(Particle arg0, double arg1, double arg2, double arg3, int arg4) { + throw new InvalidCommandAttemptException(); + } + + + public void spawnParticle(Particle arg0, double arg1, double arg2, double arg3, int arg4, T arg5) { + throw new InvalidCommandAttemptException(); + } + + + public void spawnParticle(Particle arg0, Location arg1, int arg2, double arg3, double arg4, double arg5) { + throw new InvalidCommandAttemptException(); + } + + + public void spawnParticle(Particle arg0, Location arg1, int arg2, double arg3, double arg4, double arg5, + T arg6) { + throw new InvalidCommandAttemptException(); + } + + + public void spawnParticle(Particle arg0, Location arg1, int arg2, double arg3, double arg4, double arg5, + double arg6) { + throw new InvalidCommandAttemptException(); + } + + + public void spawnParticle(Particle arg0, double arg1, double arg2, double arg3, int arg4, double arg5, double arg6, + double arg7) { + throw new InvalidCommandAttemptException(); + } + + + public void spawnParticle(Particle arg0, Location arg1, int arg2, double arg3, double arg4, double arg5, + double arg6, T arg7) { + throw new InvalidCommandAttemptException(); + } + + + public void spawnParticle(Particle arg0, double arg1, double arg2, double arg3, int arg4, double arg5, + double arg6, double arg7, T arg8) { + throw new InvalidCommandAttemptException(); + } + + + public void spawnParticle(Particle arg0, double arg1, double arg2, double arg3, int arg4, double arg5, double arg6, + double arg7, double arg8) { + throw new InvalidCommandAttemptException(); + } + + + public void spawnParticle(Particle arg0, double arg1, double arg2, double arg3, int arg4, double arg5, + double arg6, double arg7, double arg8, T arg9) { + throw new InvalidCommandAttemptException(); + } + + + public Player.Spigot spigot() { + return spigotPlayer; + } + + + public void stopSound(Sound arg0) { + throw new InvalidCommandAttemptException(); + } + + + public void stopSound(String arg0) { + throw new InvalidCommandAttemptException(); + } + + + public void stopSound(Sound arg0, SoundCategory arg1) { + throw new InvalidCommandAttemptException(); + } + + + public void stopSound(String arg0, SoundCategory arg1) { + throw new InvalidCommandAttemptException(); + } + + + public void updateInventory() { + throw new InvalidCommandAttemptException(); + } + +} diff --git a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/impersonation/PseudoPlayerIdentity.java b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/impersonation/PseudoPlayerIdentity.java new file mode 100644 index 000000000..749277317 --- /dev/null +++ b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/impersonation/PseudoPlayerIdentity.java @@ -0,0 +1,25 @@ +package com.github.maxopoly.KiraBukkitGateway.impersonation; + +import com.mojang.authlib.GameProfile; +import java.util.UUID; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_18_R2.CraftWorld; + + +public class PseudoPlayerIdentity extends ServerPlayer { + + public PseudoPlayerIdentity(MinecraftServer minecraftserver, ServerLevel worldserver, GameProfile gameprofile) { + super(minecraftserver, worldserver, gameprofile); + } + + public static ServerPlayer generate(UUID uuid, String name) { + MinecraftServer minecraftServer = MinecraftServer.getServer(); + ServerLevel worldServer = ((CraftWorld) Bukkit.getWorlds().get(0)).getHandle(); + GameProfile gameProfile = new GameProfile(uuid, name); + return new PseudoPlayerIdentity(minecraftServer, worldServer, gameProfile); + } + +} diff --git a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/impersonation/PseudoSpigotPlayer.java b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/impersonation/PseudoSpigotPlayer.java new file mode 100644 index 000000000..cded620c3 --- /dev/null +++ b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/impersonation/PseudoSpigotPlayer.java @@ -0,0 +1,36 @@ +package com.github.maxopoly.KiraBukkitGateway.impersonation; + +import net.md_5.bungee.api.ChatMessageType; +import net.md_5.bungee.api.chat.BaseComponent; +import org.bukkit.entity.Player; + +public class PseudoSpigotPlayer extends Player.Spigot { + + private PseudoPlayer player; + + public PseudoSpigotPlayer(PseudoPlayer player) { + this.player = player; + } + + @Override + public void sendMessage(BaseComponent comp) { + player.sendMessage(BaseComponent.toPlainText(comp)); + } + + @Override + public void sendMessage(BaseComponent ... components) { + player.sendMessage(BaseComponent.toPlainText(components)); + } + + @Override + public void sendMessage(ChatMessageType position, BaseComponent comp) { + player.sendMessage(BaseComponent.toPlainText(comp)); + } + + @Override + public void sendMessage(ChatMessageType position, BaseComponent ... components) { + player.sendMessage(BaseComponent.toPlainText(components)); + } + + +} diff --git a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/listener/CivChatListener.java b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/listener/CivChatListener.java new file mode 100644 index 000000000..c7c86eb87 --- /dev/null +++ b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/listener/CivChatListener.java @@ -0,0 +1,16 @@ +package com.github.maxopoly.KiraBukkitGateway.listener; + +import com.github.maxopoly.KiraBukkitGateway.KiraBukkitGatewayPlugin; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import vg.civcraft.mc.civchat2.event.GroupChatEvent; + +public class CivChatListener implements Listener { + + @EventHandler + public void chat(GroupChatEvent e) { + KiraBukkitGatewayPlugin.getInstance().getRabbit().sendGroupChatMessage(e.getGroup(), e.getPlayer().getName(), + e.getMessage()); + } + +} diff --git a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/listener/JukeAlertListener.java b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/listener/JukeAlertListener.java new file mode 100644 index 000000000..04a393120 --- /dev/null +++ b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/listener/JukeAlertListener.java @@ -0,0 +1,50 @@ +package com.github.maxopoly.KiraBukkitGateway.listener; + +import com.github.maxopoly.KiraBukkitGateway.KiraBukkitGatewayPlugin; +import com.untamedears.jukealert.events.PlayerHitSnitchEvent; +import com.untamedears.jukealert.events.PlayerLoginSnitchEvent; +import com.untamedears.jukealert.events.PlayerLogoutSnitchEvent; +import com.untamedears.jukealert.model.Snitch; +import com.untamedears.jukealert.util.JukeAlertPermissionHandler; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; + +public class JukeAlertListener implements Listener { + + @EventHandler + public void enter(PlayerHitSnitchEvent e) { + if (immune(e.getSnitch(), e.getPlayer())) { + return; + } + KiraBukkitGatewayPlugin.getInstance().getRabbit().sendSnitchHit(e.getPlayer(), e.getSnitch().getLocation(), + e.getSnitch().getName(), e.getSnitch().getGroup().getName(), SnitchHitType.ENTER, + e.getSnitch().getType().getName()); + } + + @EventHandler + public void login(PlayerLoginSnitchEvent e) { + if (immune(e.getSnitch(), e.getPlayer())) { + return; + } + KiraBukkitGatewayPlugin.getInstance().getRabbit().sendSnitchHit(e.getPlayer(), e.getSnitch().getLocation(), + e.getSnitch().getName(), e.getSnitch().getGroup().getName(), SnitchHitType.LOGIN, + e.getSnitch().getType().getName()); + + } + + @EventHandler + public void login(PlayerLogoutSnitchEvent e) { + if (immune(e.getSnitch(), e.getPlayer())) { + return; + } + KiraBukkitGatewayPlugin.getInstance().getRabbit().sendSnitchHit(e.getPlayer(), e.getSnitch().getLocation(), + e.getSnitch().getName(), e.getSnitch().getGroup().getName(), SnitchHitType.LOGOUT, + e.getSnitch().getType().getName()); + } + + private static boolean immune(Snitch snitch, Player p) { + return snitch.hasPermission(p, JukeAlertPermissionHandler.getSnitchImmune()); + } + +} diff --git a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/listener/SkynetListener.java b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/listener/SkynetListener.java new file mode 100644 index 000000000..d6a405b34 --- /dev/null +++ b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/listener/SkynetListener.java @@ -0,0 +1,25 @@ +package com.github.maxopoly.KiraBukkitGateway.listener; + +import com.github.maxopoly.KiraBukkitGateway.KiraBukkitGatewayPlugin; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; + +public class SkynetListener implements Listener { + + @EventHandler(priority=EventPriority.MONITOR, ignoreCancelled=true) + public void join(PlayerJoinEvent e) { + if (!e.getPlayer().hasPlayedBefore()) { + KiraBukkitGatewayPlugin.getInstance().getRabbit().playerLoginFirstTime(e.getPlayer().getName()); + } + KiraBukkitGatewayPlugin.getInstance().getRabbit().playerLoginOut(e.getPlayer().getName(), "LOGIN"); + } + + @EventHandler(priority=EventPriority.MONITOR, ignoreCancelled=true) + public void leave(PlayerQuitEvent e) { + KiraBukkitGatewayPlugin.getInstance().getRabbit().playerLoginOut(e.getPlayer().getName(), "LOGOUT"); + } + +} diff --git a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/listener/SnitchHitType.java b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/listener/SnitchHitType.java new file mode 100644 index 000000000..d3052fbbe --- /dev/null +++ b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/listener/SnitchHitType.java @@ -0,0 +1,11 @@ +package com.github.maxopoly.KiraBukkitGateway.listener; + +public enum SnitchHitType { + + ENTER, LOGIN, LOGOUT; + + public static SnitchHitType fromString(String value) { + return SnitchHitType.valueOf(value.trim().toUpperCase()); + } + +} diff --git a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/log/KiraLogAppender.java b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/log/KiraLogAppender.java new file mode 100644 index 000000000..c69b49a75 --- /dev/null +++ b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/log/KiraLogAppender.java @@ -0,0 +1,30 @@ +package com.github.maxopoly.KiraBukkitGateway.log; + +import com.github.maxopoly.KiraBukkitGateway.KiraBukkitGatewayPlugin; +import com.github.maxopoly.KiraBukkitGateway.KiraUtil; +import java.util.regex.Pattern; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.appender.AbstractAppender; +import org.apache.logging.log4j.core.layout.PatternLayout; + +public class KiraLogAppender extends AbstractAppender { + + private Pattern pattern; + private String key; + + public KiraLogAppender(String key, String regex) { + super("Kira Appender regex " + regex, null, PatternLayout.createDefaultLayout()); + this.pattern = Pattern.compile(regex); + this.key = key; + } + + @Override + public void append(LogEvent event) { + String msg = event.getMessage().getFormattedMessage(); + if (pattern.matcher(msg).matches()) { + KiraBukkitGatewayPlugin.getInstance().getRabbit().sendConsoleRelay( + KiraUtil.cleanUp(msg), key); + } + } + +} diff --git a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/RabbitCommands.java b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/RabbitCommands.java new file mode 100644 index 000000000..ef989f8c1 --- /dev/null +++ b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/RabbitCommands.java @@ -0,0 +1,163 @@ +package com.github.maxopoly.KiraBukkitGateway.rabbit; + +import com.github.maxopoly.KiraBukkitGateway.KiraUtil; +import com.github.maxopoly.KiraBukkitGateway.listener.SnitchHitType; +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import java.util.Collection; +import java.util.UUID; +import org.bukkit.Location; +import org.bukkit.entity.Player; + +public class RabbitCommands { + + private RabbitHandler internal; + + public RabbitCommands(RabbitHandler internalRabbit) { + this.internal = internalRabbit; + } + + public void sendAuthCode(String code, String playerName, UUID playerUUID) { + nonNullArgs(code, playerName, playerUUID); + JsonObject json = new JsonObject(); + json.addProperty("uuid", playerUUID.toString()); + json.addProperty("name", playerName); + json.addProperty("code", code); + sendInternal("addauth", json); + } + + public void sendGroupChatMessage(String group, String sender, String msg) { + if (group == null || sender == null || msg == null) { + throw new IllegalArgumentException("Arguments cant be null"); + } + JsonObject json = new JsonObject(); + json.addProperty("group", group); + json.addProperty("sender", sender); + json.addProperty("msg", msg); + sendInternal("groupchatmessage", json); + } + + public void sendConsoleRelay(String msg, String key) { + nonNullArgs(msg, key); + JsonObject json = new JsonObject(); + json.addProperty("consolekey", key); + json.addProperty("message", msg); + sendInternal("consolelog", json); + } + + public void replyToRequestSession(JsonObject json) { + nonNullArgs(json); + sendInternal("requestsession", json); + } + + public void playerLoginFirstTime(String player) { + nonNullArgs(player); + JsonObject json = new JsonObject(); + json.addProperty("player", player); + sendInternal("newplayer", json); + } + + public void playerLoginOut(String player, String action) { + nonNullArgs(player, action); + JsonObject json = new JsonObject(); + json.addProperty("player", player); + json.addProperty("action", action); + sendInternal("skynet", json); + } + + public void syncGroupChatAccess(String group, Collection members, UUID sender) { + nonNullArgs(group, members); + JsonObject json = new JsonObject(); + json.addProperty("group", group); + json.addProperty("sender", sender.toString()); + JsonArray array = new JsonArray(); + members.forEach(uuid -> array.add(uuid.toString())); + json.add("members", array); + sendInternal("syncgroupchatmembers", json); + } + + public void createGroupChatChannel(String group, Collection members, UUID creator, long guildID, + long channelID) { + nonNullArgs(group, members); + JsonObject json = new JsonObject(); + json.addProperty("group", group); + json.addProperty("creator", creator.toString()); + json.addProperty("guildID", guildID); + json.addProperty("channelID", channelID); + JsonArray array = new JsonArray(); + members.forEach(uuid -> array.add(uuid.toString())); + json.add("members", array); + sendInternal("creategroupchat", json); + } + + public void deleteGroupChatChannel(String group, UUID sender) { + nonNullArgs(group, sender); + JsonObject json = new JsonObject(); + json.addProperty("group", group); + json.addProperty("sender", sender.toString()); + sendInternal("deletegroupchat", json); + } + + public void removeGroupMember(String group, UUID member) { + nonNullArgs(group, member); + JsonObject json = new JsonObject(); + json.addProperty("group", group); + json.addProperty("member", member.toString()); + sendInternal("removegroupmember", json); + } + + public void addGroupMember(String group, UUID member) { + nonNullArgs(group, member); + JsonObject json = new JsonObject(); + json.addProperty("group", group); + json.addProperty("member", member.toString()); + sendInternal("addgroupmember", json); + } + + public void replyToUser(UUID user, String msg, long channelId) { + nonNullArgs(user, msg); + JsonObject json = new JsonObject(); + json.addProperty("user", user.toString()); + json.addProperty("msg", KiraUtil.cleanUp(msg)); + json.addProperty("channel", channelId); + sendInternal("replytouser", json); + } + + public void sendSnitchHit(Player victim, Location location, String snitchName, String groupName, + SnitchHitType hitType, String snitchType) { + nonNullArgs(victim, location, groupName); + if (snitchName == null) { + snitchName = ""; + } + JsonObject json = new JsonObject(); + json.addProperty("victimUUID", victim.getUniqueId().toString()); + json.addProperty("victimName", victim.getName()); + json.addProperty("world", location.getWorld().getName()); + json.addProperty("x", location.getBlockX()); + json.addProperty("y", location.getBlockY()); + json.addProperty("z", location.getBlockZ()); + json.addProperty("snitchName", snitchName); + json.addProperty("groupName", groupName); + json.addProperty("type", hitType.toString()); + json.addProperty("snitchtype", snitchType); + sendInternal("sendsnitchhit", json); + } + + private void sendInternal(String id, JsonObject json) { + json.addProperty("timestamp", System.currentTimeMillis()); + json.addProperty("packettype", id); + Gson gson = new Gson(); + String payload = gson.toJson(json); + internal.sendMessage(payload); + } + + private void nonNullArgs(Object ...objects) { + for(Object o: objects) { + if (o == null) { + throw new IllegalArgumentException("Arguments cant be null"); + } + } + } + +} diff --git a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/RabbitHandler.java b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/RabbitHandler.java new file mode 100644 index 000000000..5070f8d5b --- /dev/null +++ b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/RabbitHandler.java @@ -0,0 +1,90 @@ +package com.github.maxopoly.KiraBukkitGateway.rabbit; + +import com.github.maxopoly.KiraBukkitGateway.KiraBukkitGatewayPlugin; +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionFactory; +import com.rabbitmq.client.DeliverCallback; +import java.io.IOException; +import java.util.concurrent.TimeoutException; +import java.util.logging.Logger; +import org.bukkit.scheduler.BukkitRunnable; + +public class RabbitHandler { + + private ConnectionFactory connectionFactory; + private String incomingQueue; + private String outgoingQueue; + private Logger logger; + private Connection conn; + private Channel incomingChannel; + private Channel outgoingChannel; + private RabbitInputHandler inputProcessor; + + public RabbitHandler(ConnectionFactory connFac, String incomingQueue, String outgoingQueue, Logger logger) { + this.connectionFactory = connFac; + this.incomingQueue = incomingQueue; + this.outgoingQueue = outgoingQueue; + this.logger = logger; + inputProcessor = new RabbitInputHandler(logger); + } + + public boolean setup() { + try { + conn = connectionFactory.newConnection(); + incomingChannel = conn.createChannel(); + outgoingChannel = conn.createChannel(); + incomingChannel.queueDeclare(incomingQueue, false, false, false, null); + outgoingChannel.queueDeclare(outgoingQueue, false, false, false, null); + return true; + } catch (IOException | TimeoutException e) { + logger.severe("Failed to setup rabbit connection: " + e.toString()); + return false; + } + } + + public void beginAsyncListen() { + new BukkitRunnable() { + + @Override + public void run() { + DeliverCallback deliverCallback = (consumerTag, delivery) -> { + try { + String message = new String(delivery.getBody(), "UTF-8"); + System.out.println(" [x] Received '" + message + "'"); + inputProcessor.handle(message); + } catch (Exception e) { + logger.severe("Exception in rabbit handling: " + e.toString()); + e.printStackTrace(); + } + }; + try { + incomingChannel.basicConsume(incomingQueue, true, deliverCallback, consumerTag -> { + }); + } catch (IOException e) { + logger.severe("Error in rabbit listener: " + e.toString()); + } + } + }.runTask(KiraBukkitGatewayPlugin.getInstance()); + } + + public void shutdown() { + try { + incomingChannel.close(); + outgoingChannel.close(); + conn.close(); + } catch (IOException | TimeoutException e) { + logger.severe("Failed to close rabbit connection: " + e); + } + } + + public boolean sendMessage(String msg) { + try { + outgoingChannel.basicPublish("", outgoingQueue, null, msg.getBytes("UTF-8")); + return true; + } catch (IOException e) { + logger.severe("Failed to send rabbit message: " + e); + return false; + } + } +} diff --git a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/RabbitInput.java b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/RabbitInput.java new file mode 100644 index 000000000..4a1f80977 --- /dev/null +++ b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/RabbitInput.java @@ -0,0 +1,19 @@ +package com.github.maxopoly.KiraBukkitGateway.rabbit; + +import com.google.gson.JsonObject; + +public abstract class RabbitInput { + + private String identifier; + + public RabbitInput(String identifier) { + this.identifier = identifier; + } + + public String getIdentifier() { + return identifier; + } + + public abstract void handle(JsonObject input); + +} diff --git a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/RabbitInputHandler.java b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/RabbitInputHandler.java new file mode 100644 index 000000000..34b095e53 --- /dev/null +++ b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/RabbitInputHandler.java @@ -0,0 +1,61 @@ +package com.github.maxopoly.KiraBukkitGateway.rabbit; + +import com.github.maxopoly.KiraBukkitGateway.rabbit.in.GroupChatMessageHandler; +import com.github.maxopoly.KiraBukkitGateway.rabbit.in.RequestRelayCreationHandler; +import com.github.maxopoly.KiraBukkitGateway.rabbit.in.RequestSessionHandler; +import com.github.maxopoly.KiraBukkitGateway.rabbit.in.SendMessageHandler; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Logger; + +public class RabbitInputHandler { + + private Map commands; + private Logger logger; + + public RabbitInputHandler(Logger logger) { + this.commands = new HashMap<>(); + this.logger = logger; + registerCommands(); + } + + private void registerCommands() { + registerCommand(new SendMessageHandler()); + registerCommand(new GroupChatMessageHandler()); + registerCommand(new RequestRelayCreationHandler()); + registerCommand(new RequestSessionHandler()); + } + + private void registerCommand(RabbitInput command) { + commands.put(command.getIdentifier().toLowerCase(), command); + } + + public void handle(String input) { + if (input == null || input.equals("")) { + logger.info("Invalid empty input in rabbit handler"); + return; + } + int spaceIndex = input.indexOf(" "); + String arguments; + String command; + if (spaceIndex == -1) { + arguments = ""; + command = input; + } else { + arguments = input.substring(spaceIndex + 1); + command = input.substring(0, spaceIndex); + } + RabbitInput comm = commands.get(command); + if (comm == null) { + logger.severe("Received invalid rabbit command: " + input); + return; + } + JsonParser parser = new JsonParser(); + JsonElement json = parser.parse(arguments); + comm.handle((JsonObject) json); + } + +} diff --git a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/in/GroupChatMessageHandler.java b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/in/GroupChatMessageHandler.java new file mode 100644 index 000000000..7988ed54a --- /dev/null +++ b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/in/GroupChatMessageHandler.java @@ -0,0 +1,44 @@ +package com.github.maxopoly.KiraBukkitGateway.rabbit.in; + +import com.github.maxopoly.KiraBukkitGateway.KiraBukkitGatewayPlugin; +import com.github.maxopoly.KiraBukkitGateway.impersonation.PseudoPlayer; +import com.github.maxopoly.KiraBukkitGateway.rabbit.RabbitInput; +import com.google.gson.JsonObject; +import java.util.UUID; +import org.bukkit.Bukkit; +import vg.civcraft.mc.civchat2.CivChat2; +import vg.civcraft.mc.namelayer.GroupManager; + +public class GroupChatMessageHandler extends RabbitInput { + + public GroupChatMessageHandler() { + super("sendgroupmessage"); + } + + @Override + public void handle(final JsonObject input) { + final var senderUUID = UUID.fromString(input.get("sender").getAsString()); + final var groupName = input.get("group").getAsString(); + final var message = input.get("message").getAsString(); + + final var fakeSender = new PseudoPlayer(senderUUID, -1); + final var logger = KiraBukkitGatewayPlugin.getInstance().getLogger(); + + final var foundGroup = GroupManager.getGroup(groupName); + if (foundGroup == null) { + fakeSender.sendMessage("Could not find that group to send a message to."); + logger.severe("Tried to send message to group \"" + groupName + "\", but it wasn't found"); + return; + } + if (foundGroup.isDisciplined()) { + fakeSender.sendMessage("That group \"" + groupName + "\" is disciplined."); + logger.severe("Tried to send message to group \"" + groupName + "\", but it's disciplined."); + return; + } + + Bukkit.getScheduler().scheduleSyncDelayedTask(KiraBukkitGatewayPlugin.getInstance(), () -> { + CivChat2.getInstance().getCivChat2Manager().sendGroupMsg(fakeSender, foundGroup, message); + }); + } + +} diff --git a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/in/RequestRelayCreationHandler.java b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/in/RequestRelayCreationHandler.java new file mode 100644 index 000000000..f90340d63 --- /dev/null +++ b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/in/RequestRelayCreationHandler.java @@ -0,0 +1,59 @@ +package com.github.maxopoly.KiraBukkitGateway.rabbit.in; + +import com.github.maxopoly.KiraBukkitGateway.KiraBukkitGatewayPlugin; +import com.github.maxopoly.KiraBukkitGateway.rabbit.RabbitInput; +import com.google.gson.JsonObject; +import java.util.HashSet; +import java.util.UUID; +import vg.civcraft.mc.namelayer.GroupManager; +import vg.civcraft.mc.namelayer.NameAPI; +import vg.civcraft.mc.namelayer.permission.PermissionType; + +public class RequestRelayCreationHandler extends RabbitInput { + + public RequestRelayCreationHandler() { + super("requestrelaycreation"); + } + + @Override + public void handle(JsonObject input) { + final var senderUUID = UUID.fromString(input.get("sender").getAsString()); + final var groupName = input.get("group").getAsString(); + final long channelID = input.get("channelID").getAsLong(); + final long guildID = input.get("guildID").getAsLong(); + + final var foundGroup = GroupManager.getGroup(groupName); + if (foundGroup == null) { + KiraBukkitGatewayPlugin.getInstance().getRabbit().replyToUser(senderUUID, + "The group " + groupName + " does not exist", -1); + return; + } + if (foundGroup.isDisciplined()) { + KiraBukkitGatewayPlugin.getInstance().getRabbit().replyToUser(senderUUID, + "You cannot relay that group, it's disciplined.", -1); + return; + } + + final var groupManager = NameAPI.getGroupManager(); + final var kiraManagePermission = PermissionType.getPermission("KIRA_MANAGE_CHANNEL"); + if (!groupManager.hasAccess(foundGroup, senderUUID, kiraManagePermission)) { + KiraBukkitGatewayPlugin.getInstance().getRabbit().replyToUser(senderUUID, + "You don't have the required permission KIRA_MANAGE_CHANNEL " + + "for the group " + foundGroup.getName(), -1); + return; + } + + final var readChatPermission = PermissionType.getPermission("READ_CHAT"); + final var recipients = new HashSet(); + + foundGroup.getAllMembers().stream() + .filter(member -> groupManager.hasAccess(foundGroup, member, readChatPermission)) + .forEach(recipients::add); + + KiraBukkitGatewayPlugin.getInstance().getRabbit().replyToUser(senderUUID, + "Confirmed permissions, proceeding with channel setup...", -1); + + KiraBukkitGatewayPlugin.getInstance().getRabbit().createGroupChatChannel( + foundGroup.getName(), recipients, senderUUID, guildID, channelID); + } +} diff --git a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/in/RequestSessionHandler.java b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/in/RequestSessionHandler.java new file mode 100644 index 000000000..2465a2623 --- /dev/null +++ b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/in/RequestSessionHandler.java @@ -0,0 +1,56 @@ +package com.github.maxopoly.KiraBukkitGateway.rabbit.in; + +import com.github.maxopoly.KiraBukkitGateway.KiraBukkitGatewayPlugin; +import com.github.maxopoly.KiraBukkitGateway.rabbit.RabbitInput; +import com.github.maxopoly.KiraBukkitGateway.rabbit.request.AbstractRequestHandler; +import com.github.maxopoly.KiraBukkitGateway.rabbit.request.ApiPermsHandler; +import com.github.maxopoly.KiraBukkitGateway.rabbit.request.ConsoleCommandHandler; +import com.github.maxopoly.KiraBukkitGateway.rabbit.request.IngameCommandHandler; +import com.github.maxopoly.KiraBukkitGateway.rabbit.request.PermissionCheckHandler; +import com.google.gson.JsonObject; +import java.util.Map; +import java.util.TreeMap; + +public class RequestSessionHandler extends RabbitInput { + + private static final String idField = "RequestSessionId"; + private static final String keyField = "RequestSessionKey"; + private static final String channelField = "DiscordChannelKey"; + + private Map handlers; + + public RequestSessionHandler() { + super("requestsession"); + registerHandlers(); + } + + private void registerHandlers() { + handlers = new TreeMap<>(); + registerHandler(new ConsoleCommandHandler()); + registerHandler(new PermissionCheckHandler()); + registerHandler(new ApiPermsHandler()); + registerHandler(new IngameCommandHandler()); + } + + private void registerHandler(AbstractRequestHandler handler) { + handlers.put(handler.getIdentifier(), handler); + } + + @Override + public void handle(JsonObject input) { + long id = input.get(idField).getAsLong(); + String type = input.get(keyField).getAsString(); + long channelId = input.has(channelField) ? input.get(channelField).getAsLong() : -1; + JsonObject reply = new JsonObject(); + reply.addProperty(idField, id); + AbstractRequestHandler handler = handlers.get(type); + if (handler == null) { + throw new IllegalArgumentException(type + " is not a valid request"); + } + handler.handle(input, reply, channelId); + if (!handler.isAsync()) { + KiraBukkitGatewayPlugin.getInstance().getRabbit().replyToRequestSession(reply); + } + } + +} diff --git a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/in/SendMessageHandler.java b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/in/SendMessageHandler.java new file mode 100644 index 000000000..176d39d40 --- /dev/null +++ b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/in/SendMessageHandler.java @@ -0,0 +1,30 @@ +package com.github.maxopoly.KiraBukkitGateway.rabbit.in; + +import com.github.maxopoly.KiraBukkitGateway.KiraBukkitGatewayPlugin; +import com.github.maxopoly.KiraBukkitGateway.rabbit.RabbitInput; +import com.google.gson.JsonObject; +import java.util.UUID; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +public class SendMessageHandler extends RabbitInput { + + public SendMessageHandler() { + super("sendmessage"); + } + + @Override + public void handle(JsonObject input) { + UUID target = UUID.fromString(input.get("receiver").getAsString()); + String msg = input.get("message").getAsString(); + Player p = Bukkit.getPlayer(target); + if (p != null) { + p.sendMessage(ChatColor.GOLD + msg); + } + else { + //player is offline, so we use their discord PM as backup + KiraBukkitGatewayPlugin.getInstance().getRabbit().replyToUser(target, msg, -1); + } + } +} diff --git a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/request/AbstractRequestHandler.java b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/request/AbstractRequestHandler.java new file mode 100644 index 000000000..f1ba8f6ec --- /dev/null +++ b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/request/AbstractRequestHandler.java @@ -0,0 +1,30 @@ +package com.github.maxopoly.KiraBukkitGateway.rabbit.request; + +import com.github.maxopoly.KiraBukkitGateway.KiraBukkitGatewayPlugin; +import com.google.gson.JsonObject; + +public abstract class AbstractRequestHandler { + + private String identifier; + private boolean async; + + public AbstractRequestHandler(String identifier, boolean isAsync) { + this.identifier = identifier; + this.async = isAsync; + } + + public String getIdentifier() { + return identifier; + } + + public boolean isAsync() { + return async; + } + + public abstract void handle(JsonObject input, JsonObject output, long channelId); + + protected void sendRequestSessionReply(JsonObject json) { + KiraBukkitGatewayPlugin.getInstance().getRabbit().replyToRequestSession(json); + } + +} diff --git a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/request/ApiPermsHandler.java b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/request/ApiPermsHandler.java new file mode 100644 index 000000000..ea7753872 --- /dev/null +++ b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/request/ApiPermsHandler.java @@ -0,0 +1,45 @@ +package com.github.maxopoly.KiraBukkitGateway.rabbit.request; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; +import vg.civcraft.mc.namelayer.GroupManager; +import vg.civcraft.mc.namelayer.NameAPI; +import vg.civcraft.mc.namelayer.permission.PermissionType; + +public class ApiPermsHandler extends AbstractRequestHandler { + + public ApiPermsHandler() { + super("apiperms", false); + } + + @Override + public void handle(JsonObject input, JsonObject output, long channelId) { + UUID runner = UUID.fromString(input.get("uuid").getAsString()); + JsonArray snitchArray = new JsonArray(); + getGroupsWithPermission(runner, "SNITCH_NOTIFICATIONS").forEach(g -> snitchArray.add(g)); + output.add("snitches", snitchArray); + JsonArray chatArray = new JsonArray(); + getGroupsWithPermission(runner, "READ_CHAT").forEach(g -> chatArray.add(g)); + output.add("read_chat", chatArray); + JsonArray writeChatArray = new JsonArray(); + getGroupsWithPermission(runner, "WRITE_CHAT").forEach(g -> writeChatArray.add(g)); + output.add("write_chat", writeChatArray); + } + + private static Set getGroupsWithPermission(UUID uuid, String permission) { + Set result = new HashSet<>(); + GroupManager gm = NameAPI.getGroupManager(); + PermissionType perm = PermissionType.getPermission(permission); + for(String groupName : gm.getAllGroupNames(uuid)) { + if (gm.hasAccess(groupName, uuid, perm)) { + result.add(groupName); + } + } + return result; + } + + +} diff --git a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/request/ConsoleCommandHandler.java b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/request/ConsoleCommandHandler.java new file mode 100644 index 000000000..8a3819886 --- /dev/null +++ b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/request/ConsoleCommandHandler.java @@ -0,0 +1,48 @@ +package com.github.maxopoly.KiraBukkitGateway.rabbit.request; + +import com.github.maxopoly.KiraBukkitGateway.KiraBukkitGatewayPlugin; +import com.github.maxopoly.KiraBukkitGateway.KiraUtil; +import com.github.maxopoly.KiraBukkitGateway.impersonation.PseudoConsoleSender; +import com.google.gson.JsonObject; +import java.util.UUID; +import java.util.logging.Logger; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; + +public class ConsoleCommandHandler extends AbstractRequestHandler { + + public ConsoleCommandHandler() { + super("consolemessageop", true); + } + + @Override + public void handle(JsonObject input, JsonObject output, long channelId) { + UUID sender = UUID.fromString(input.get("sender").getAsString()); + String command = input.get("command").getAsString(); + OfflinePlayer player = Bukkit.getOfflinePlayer(sender); + Logger logger = KiraBukkitGatewayPlugin.getInstance().getLogger(); + if (player == null) { + logger.warning("Console player with uuid " + sender + " does not exist"); + output.addProperty("replymsg", "You are not a known player on the server"); + sendRequestSessionReply(output); + return; + } + if (!player.isOp()) { + logger.warning("Non op player " + sender + " tried to run console command"); + output.addProperty("replymsg", "You are not op"); + sendRequestSessionReply(output); + return; + } + PseudoConsoleSender console = new PseudoConsoleSender(sender, Bukkit.getConsoleSender(), channelId); + Bukkit.getScheduler().runTask(KiraBukkitGatewayPlugin.getInstance(), () -> { + Bukkit.getServer().dispatchCommand(console, command); + StringBuilder sb = new StringBuilder(); + for (String s : console.getRepliesAndFinish()) { + sb.append(KiraUtil.cleanUp(s)); + sb.append('\n'); + } + output.addProperty("replymsg", sb.toString()); + sendRequestSessionReply(output); + }); + } +} diff --git a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/request/IngameCommandHandler.java b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/request/IngameCommandHandler.java new file mode 100644 index 000000000..bc1642f88 --- /dev/null +++ b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/request/IngameCommandHandler.java @@ -0,0 +1,46 @@ +package com.github.maxopoly.KiraBukkitGateway.rabbit.request; + +import com.github.maxopoly.KiraBukkitGateway.KiraBukkitGatewayPlugin; +import com.github.maxopoly.KiraBukkitGateway.impersonation.PseudoPlayer; +import com.google.gson.JsonObject; +import java.util.List; +import java.util.UUID; +import java.util.logging.Logger; +import org.bukkit.Bukkit; + +public class IngameCommandHandler extends AbstractRequestHandler { + + public IngameCommandHandler() { + super("ingame", true); + } + + @Override + public void handle(JsonObject input, JsonObject output, long channelId) { + UUID runner = UUID.fromString(input.get("uuid").getAsString()); + String command = input.get("command").getAsString(); + Logger logger = KiraBukkitGatewayPlugin.getInstance().getLogger(); + logger.info("Running command '" + command + "' for " + runner.toString()); + Bukkit.getScheduler().runTask(KiraBukkitGatewayPlugin.getInstance(), () -> { + PseudoPlayer player = new PseudoPlayer(runner, channelId); + try { + Bukkit.getServer().dispatchCommand(player, command); + } catch (Exception e) { + output.addProperty("reply", "You can not run this command from out of game"); + logger.warning("Failed to run command from external source: " + e.getMessage()); + e.printStackTrace(); + sendRequestSessionReply(output); + return; + } + List replies = player.collectReplies(); + StringBuilder sb = new StringBuilder(); + for (String reply : replies) { + sb.append(reply); + sb.append('\n'); + } + output.addProperty("reply", sb.toString()); + sendRequestSessionReply(output); + }); + + } + +} diff --git a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/request/PermissionCheckHandler.java b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/request/PermissionCheckHandler.java new file mode 100644 index 000000000..bff34a935 --- /dev/null +++ b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/request/PermissionCheckHandler.java @@ -0,0 +1,37 @@ +package com.github.maxopoly.KiraBukkitGateway.rabbit.request; + +import com.google.gson.JsonObject; +import java.util.UUID; +import vg.civcraft.mc.namelayer.GroupManager; +import vg.civcraft.mc.namelayer.NameAPI; +import vg.civcraft.mc.namelayer.group.Group; +import vg.civcraft.mc.namelayer.permission.PermissionType; + +public class PermissionCheckHandler extends AbstractRequestHandler { + + public PermissionCheckHandler() { + super("permissioncheck", false); + } + + @Override + public void handle(JsonObject input, JsonObject output, long channelId) { + boolean hasPerm = checkPerm(input); + output.addProperty("hasPermission", hasPerm); + } + + private static boolean checkPerm(JsonObject input) { + UUID player = UUID.fromString(input.get("player").getAsString()); + String group = input.get("group").getAsString(); + String permission = input.get("permission").getAsString(); + Group g = GroupManager.getGroup(group); + if (g == null) { + return false; + } + PermissionType perm = PermissionType.getPermission(permission); + if (perm == null) { + return false; + } + return NameAPI.getGroupManager().hasAccess(g, player, perm); + } + +} diff --git a/plugins/kirabukkitgateway-paper/src/main/resources/config.yml b/plugins/kirabukkitgateway-paper/src/main/resources/config.yml new file mode 100644 index 000000000..ed5fd3bbc --- /dev/null +++ b/plugins/kirabukkitgateway-paper/src/main/resources/config.yml @@ -0,0 +1,35 @@ +# Database connection details, should you want to have a database connection. +# KEEP THIS COMMENTED OUT IN THIS DEFAULT CONFIG OTHERWISE BAD THINGS! +# Do NOT change the ==: value. +#database: +# ==: vg.civcraft.mc.civmodcore.dao.ManagedDatasource +# plugin: KiraBukkitGateway +# user: username +# password: password +# host: localhost +# port: 3306 +# database: minecraft +# poolsize: 5 +# connection_timeout: 10000 +# idle_timeout: 600000 +# max_lifetime: 7200000 + +# Your RabbitMQ credentials +rabbitmq: + user: username + password: password + host: localhost + port: 5672 + incomingQueue: kira-to-gateway + outgoingQueue: gateway-to-kira + +# This will relay particular messages emitted to the console to specified relays +# "key" = the name of the relay config +# "regex" = pattern to match console messages against, which, if true, will be relayed +console: + foo: + key: aac + regex: '.* moved too quickly.*' + bar: + key: ip + regex: '.*is brand new!.*' diff --git a/plugins/kirabukkitgateway-paper/src/main/resources/plugin.yml b/plugins/kirabukkitgateway-paper/src/main/resources/plugin.yml new file mode 100644 index 000000000..aa288c8d5 --- /dev/null +++ b/plugins/kirabukkitgateway-paper/src/main/resources/plugin.yml @@ -0,0 +1,11 @@ +name: KiraBukkitGateway +main: com.github.maxopoly.KiraBukkitGateway.KiraBukkitGatewayPlugin +author: Maxopoly +version: ${version} +depend: [LuckPerms, CivModCore, NameLayer, CivChat2] +api-version: 1.16 +permissions: + kira.public: + default: true + kira.op: + default: op diff --git a/plugins/namecolors-paper/.gitignore b/plugins/namecolors-paper/.gitignore new file mode 100644 index 000000000..ece3e8217 --- /dev/null +++ b/plugins/namecolors-paper/.gitignore @@ -0,0 +1,163 @@ + +# Created by https://www.gitignore.io/api/java,linux,macos,windows,intellij + +/target/ +.classpath +.settings/** +.project +.idea +*.iml + +### Intellij ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries +.gradle +build + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +### Intellij Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +.idea/sonarlint + +### Java ### +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + + +# End of https://www.gitignore.io/api/java,linux,macos,windows,intellij \ No newline at end of file diff --git a/plugins/namecolors-paper/LICENSE b/plugins/namecolors-paper/LICENSE new file mode 100644 index 000000000..8579f6461 --- /dev/null +++ b/plugins/namecolors-paper/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2018, CivClassic +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/plugins/namecolors-paper/README.md b/plugins/namecolors-paper/README.md new file mode 100644 index 000000000..a85632fec --- /dev/null +++ b/plugins/namecolors-paper/README.md @@ -0,0 +1,4 @@ +# NameColors +Allow players to change their name color + +Depends on Paper, CivModeCore, CivChat2, and Namelayer. Works with TAB custom tablist to display player names in the server tablist as well. diff --git a/plugins/namecolors-paper/build.gradle.kts b/plugins/namecolors-paper/build.gradle.kts new file mode 100644 index 000000000..fdbb2d867 --- /dev/null +++ b/plugins/namecolors-paper/build.gradle.kts @@ -0,0 +1,14 @@ +plugins { + id("io.papermc.paperweight.userdev") +} + +version = "2.0.0-SNAPSHOT" + +dependencies { + paperDevBundle("1.18.2-R0.1-SNAPSHOT") + + compileOnly(project(":plugins:civmodcore-paper")) + compileOnly(project(":plugins:namelayer-paper")) + compileOnly(project(":plugins:civchat2-paper")) + compileOnly("me.neznamy:tab-api:3.0.2") +} diff --git a/plugins/namecolors-paper/src/main/java/com/biggestnerd/namecolors/NameColorSetting.java b/plugins/namecolors-paper/src/main/java/com/biggestnerd/namecolors/NameColorSetting.java new file mode 100644 index 000000000..d55a519bf --- /dev/null +++ b/plugins/namecolors-paper/src/main/java/com/biggestnerd/namecolors/NameColorSetting.java @@ -0,0 +1,155 @@ +package com.biggestnerd.namecolors; + +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.UUID; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.java.JavaPlugin; +import vg.civcraft.mc.civmodcore.inventory.gui.Clickable; +import vg.civcraft.mc.civmodcore.inventory.gui.IClickable; +import vg.civcraft.mc.civmodcore.inventory.gui.MultiPageView; +import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; +import vg.civcraft.mc.civmodcore.players.settings.PlayerSetting; +import vg.civcraft.mc.civmodcore.players.settings.gui.MenuSection; + +public class NameColorSetting extends PlayerSetting { + + private static Map colorToGui = new EnumMap<>(ChatColor.class); + private static final String RAINBOW_PERMISSION = "namecolor.rainbow"; + private static final String COLOR_PERMISSION = "namecolor.use"; + + public static final ChatColor RAINBOW_COLOR = ChatColor.STRIKETHROUGH; + + static { + colorToGui.put(ChatColor.DARK_RED, Material.RED_WOOL); + colorToGui.put(ChatColor.DARK_GREEN, Material.GREEN_WOOL); + colorToGui.put(ChatColor.BLUE, Material.BLUE_WOOL); + colorToGui.put(ChatColor.DARK_PURPLE, Material.PURPLE_WOOL); + colorToGui.put(ChatColor.DARK_AQUA, Material.CYAN_WOOL); + colorToGui.put(ChatColor.GRAY, Material.LIGHT_GRAY_WOOL); + colorToGui.put(ChatColor.DARK_GRAY, Material.GRAY_WOOL); + colorToGui.put(ChatColor.GREEN, Material.LIME_WOOL); + colorToGui.put(ChatColor.YELLOW, Material.YELLOW_WOOL); + colorToGui.put(ChatColor.AQUA, Material.LIGHT_BLUE_WOOL); + colorToGui.put(ChatColor.GOLD, Material.YELLOW_GLAZED_TERRACOTTA); + colorToGui.put(ChatColor.LIGHT_PURPLE, Material.MAGENTA_WOOL); + colorToGui.put(ChatColor.RESET, Material.WHITE_WOOL); + } + + public NameColorSetting(JavaPlugin owningPlugin) { + super(owningPlugin, ChatColor.RESET, "Name color", "namecolors.choser", new ItemStack(Material.RED_WOOL), + "Lets you chose the color of your name", true); + } + + @Override + public ChatColor deserialize(String serial) { + return ChatColor.valueOf(serial); + } + + @Override + public ItemStack getGuiRepresentation(UUID player) { + ChatColor color = getValue(player); + ItemStack item; + if (color == RAINBOW_COLOR) { + item = new ItemStack(Material.YELLOW_STAINED_GLASS); + } else { + if (color != null) { + item = new ItemStack(colorToGui.get(color)); + } else { + item = new ItemStack(Material.WHITE_WOOL); + } + } + applyInfoToItemStack(item, player); + Player play = Bukkit.getPlayer(player); + if (play != null && !play.hasPermission(COLOR_PERMISSION) && !play.hasPermission(RAINBOW_PERMISSION)) { + ItemUtils.addLore(item, ChatColor.RED + "You do not have permission to do this"); + } + return item; + } + + @Override + public void handleMenuClick(Player player, MenuSection menu) { + List clicks = new ArrayList<>(); + if (player.hasPermission(COLOR_PERMISSION)) { + + for (Entry entry : colorToGui.entrySet()) { + ItemStack is = new ItemStack(entry.getValue()); + ItemUtils.setDisplayName(is, + "Change to color of your name to " + entry.getKey() + entry.getKey().name()); + clicks.add(new Clickable(is) { + + @Override + public void clicked(Player p) { + player.sendMessage( + "The color of your name was changed to " + entry.getKey() + entry.getKey().name()); + setValue(player, entry.getKey()); + } + }); + } + } + if (player.hasPermission(RAINBOW_PERMISSION)) { + ItemStack is = new ItemStack(Material.YELLOW_STAINED_GLASS); + ItemUtils.setDisplayName(is, "Change to color of your name to " + NameColors.rainbowify("rainbow")); + clicks.add(new Clickable(is) { + + @Override + public void clicked(Player p) { + player.sendMessage("The color of your name was changed to " + NameColors.rainbowify("rainbow")); + setValue(player, RAINBOW_COLOR); + } + }); + } + if (clicks.isEmpty()) { + player.sendMessage(ChatColor.RED + "You do not have permission to change the color of your name"); + return; + } + MultiPageView view = new MultiPageView(player, clicks, "Select a name color", true); + ItemStack returnStack = new ItemStack(Material.BOOK); + ItemUtils.setDisplayName(returnStack, "Return to previous menu"); + view.setMenuSlot(new Clickable(returnStack) { + + @Override + public void clicked(Player p) { + menu.showScreen(player); + } + }, 0); + view.showScreen(); + } + + @Override + public boolean isValidValue(String input) { + try { + ChatColor color = ChatColor.valueOf(input); + return color == RAINBOW_COLOR || colorToGui.containsKey(color); + } catch (IllegalArgumentException e) { + return false; + } + } + + @Override + public String serialize(ChatColor value) { + return value.name(); + } + + @Override + public void setValue(UUID player, ChatColor value) { + super.setValue(player, value); + Player play = Bukkit.getPlayer(player); + if (play != null) { + NameColors.getInstance().updatePlayerName(play, value); + } + } + + @Override + public String toText(ChatColor value) { + return value.name(); + } + +} diff --git a/plugins/namecolors-paper/src/main/java/com/biggestnerd/namecolors/NameColors.java b/plugins/namecolors-paper/src/main/java/com/biggestnerd/namecolors/NameColors.java new file mode 100644 index 000000000..b3c56c2c2 --- /dev/null +++ b/plugins/namecolors-paper/src/main/java/com/biggestnerd/namecolors/NameColors.java @@ -0,0 +1,82 @@ +package com.biggestnerd.namecolors; + + + +import me.neznamy.tab.api.TabAPI; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import vg.civcraft.mc.civchat2.CivChat2; +import vg.civcraft.mc.civmodcore.ACivMod; +import vg.civcraft.mc.civmodcore.players.settings.PlayerSettingAPI; + +public class NameColors extends ACivMod implements Listener { + + private static NameColors instance; + + private static final ChatColor[] rainbow = { ChatColor.RED, ChatColor.GOLD, ChatColor.YELLOW, ChatColor.GREEN, + ChatColor.DARK_AQUA, ChatColor.AQUA, ChatColor.DARK_PURPLE, ChatColor.LIGHT_PURPLE }; + + public static NameColors getInstance() { + return instance; + } + + public static String rainbowify(String text) { + StringBuilder nameBuilder = new StringBuilder(); + char[] letters = text.toCharArray(); + for (int i = 0; i < letters.length; i++) { + nameBuilder.append(rainbow[i % rainbow.length]).append(letters[i]); + } + return nameBuilder.toString(); + } + + private NameColorSetting setting; + + @Override + public void onEnable() { + instance = this; + super.onEnable(); + setting = new NameColorSetting(this); + PlayerSettingAPI.registerSetting(setting, PlayerSettingAPI.getMainMenu()); + getServer().getPluginManager().registerEvents(this, this); + } + + @EventHandler + public void onPlayerJoin(PlayerJoinEvent event) { + updatePlayerName(event.getPlayer(), setting.getValue(event.getPlayer())); + } + + public void updatePlayerName(Player player, ChatColor color) { + if (color == null || color == ChatColor.RESET) { + CivChat2.getInstance().getCivChat2Manager().removeCustomName(player.getUniqueId()); + if(getServer().getPluginManager().isPluginEnabled("TAB")) { + //TAB is enabled, so lets reset the player name. + Bukkit.getScheduler().runTaskLater(this, () -> TabAPI.getInstance().getTablistFormatManager().resetName(TabAPI.getInstance() + .getPlayer(player.getUniqueId())), 20l ); + } + } else { + if (color == NameColorSetting.RAINBOW_COLOR) { + CivChat2.getInstance().getCivChat2Manager().setCustomName(player.getUniqueId(), + rainbowify(player.getName()) + ChatColor.RESET); + if(getServer().getPluginManager().isPluginEnabled("TAB")) { + //TAB enabled, so now we need to re-apply this name as a "custom name" + //Side note: we do this temporarily so players if they lose their permission don't keep their colored name in TAB. + Bukkit.getScheduler().runTaskLater(this, () -> TabAPI.getInstance().getTablistFormatManager().setName(TabAPI.getInstance() + .getPlayer(player.getUniqueId()), rainbowify(player.getName())), 20); + } + return; + } + CivChat2.getInstance().getCivChat2Manager().setCustomName(player.getUniqueId(), + color + player.getName() + ChatColor.RESET); + //Same deal as for rainbow + if(getServer().getPluginManager().isPluginEnabled("TAB")) { + //We delay just in-case TAB hasn't loaded the player into memory yet. + Bukkit.getScheduler().runTaskLater(this, () -> TabAPI.getInstance().getTablistFormatManager().setName(TabAPI.getInstance() + .getPlayer(player.getUniqueId()),color + player.getName() + ChatColor.RESET), 20); + } + } + } +} diff --git a/plugins/namecolors-paper/src/main/resources/plugin.yml b/plugins/namecolors-paper/src/main/resources/plugin.yml new file mode 100644 index 000000000..a296ca838 --- /dev/null +++ b/plugins/namecolors-paper/src/main/resources/plugin.yml @@ -0,0 +1,20 @@ +name: NameColors +main: com.biggestnerd.namecolors.NameColors +version: ${version} +authors: [biggestnerd, Maxopoly] +depend: [CivModCore, CivChat2] +softdepend: + - TAB +api-version: 1.17 +commands: + namecolor: + description: "Give your name a nice color!" + usage: "Usage /namecolor" + permission: namecolor.use +permissions: + namecolor.use: + description: Allows you to change the color of your name + default: op + namecolor.rainbow: + description: Lets you get a rainbow name + default: op \ No newline at end of file diff --git a/plugins/railswitch-paper/.editorconfig b/plugins/railswitch-paper/.editorconfig new file mode 100644 index 000000000..c6bb9baec --- /dev/null +++ b/plugins/railswitch-paper/.editorconfig @@ -0,0 +1,16 @@ +# Editorconfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +charset = utf-8 +indent_style = space +indent_size = 4 +end_of_line = crlf +insert_final_newline = true +continuation_indent_size = 8 + +[{*.xml, *.yml}] +indent_size = 2 diff --git a/plugins/railswitch-paper/LICENSE.txt b/plugins/railswitch-paper/LICENSE.txt new file mode 100644 index 000000000..48b0ec4e2 --- /dev/null +++ b/plugins/railswitch-paper/LICENSE.txt @@ -0,0 +1,22 @@ + +The MIT License (MIT) + +Copyright (c) 2019 okx-code + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/plugins/railswitch-paper/README.md b/plugins/railswitch-paper/README.md new file mode 100644 index 000000000..3eb86ebeb --- /dev/null +++ b/plugins/railswitch-paper/README.md @@ -0,0 +1,26 @@ +# RailSwitch + +## How to use + +### Setup + +1. Place a sign in the block above a detector rail. **It must be reinforced on the same group as the detector rail.** +2. The first line must be `[destination]` (case insensitive) +3. The second, third and fourth lines can be destination names. +4. When a player passes over the detector rail, it will only activate and emit redstone if a player set their destination as any of the three on the sign. + +You may also use `[!destination]`, which will activate the rail if a player's destination is **not** on the signs. + +### Usage + +1. Type `/dest ` +2. Go AFK +3. Arrive + +You can also use spaces in a `/dest` command as an "or" operator, like `/dest `. In this command, RailSwitch will route players towards signs that contain either `` or ``. + +Another way to think about it is that `/dest` takes multiple arguments. Each argument is a destination that will be checked as you pass a sign. + +### Example Video + +[![RailSwitch Demo Video](https://img.youtube.com/vi/GKku2fcB-wY/0.jpg)](https://www.youtube.com/watch?v=GKku2fcB-wY) diff --git a/plugins/railswitch-paper/build.gradle.kts b/plugins/railswitch-paper/build.gradle.kts new file mode 100644 index 000000000..f08ec4990 --- /dev/null +++ b/plugins/railswitch-paper/build.gradle.kts @@ -0,0 +1,13 @@ +plugins { + id("io.papermc.paperweight.userdev") +} + +version = "2.0.0-SNAPSHOT" + +dependencies { + paperDevBundle("1.18.2-R0.1-SNAPSHOT") + + compileOnly(project(":plugins:civmodcore-paper")) + compileOnly(project(":plugins:namelayer-paper")) + compileOnly(project(":plugins:citadel-paper")) +} diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitchPlugin.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitchPlugin.java new file mode 100644 index 000000000..597dfa6b9 --- /dev/null +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitchPlugin.java @@ -0,0 +1,39 @@ +package sh.okx.railswitch; + +import org.bukkit.event.Listener; +import sh.okx.railswitch.commands.DestinationCommand; +import sh.okx.railswitch.glue.CitadelGlue; +import sh.okx.railswitch.settings.SettingsManager; +import sh.okx.railswitch.switches.SwitchListener; +import vg.civcraft.mc.civmodcore.ACivMod; +import vg.civcraft.mc.civmodcore.commands.CommandManager; + +/** + * The Rail Switch plugin class + */ +public final class RailSwitchPlugin extends ACivMod implements Listener { + + private CommandManager commandManager; + + @Override + public void onEnable() { + super.onEnable(); + SettingsManager.init(this); + registerListener(new CitadelGlue(this)); + registerListener(new SwitchListener()); + commandManager = new CommandManager(this); + commandManager.init(); + commandManager.registerCommand(new DestinationCommand()); + } + + @Override + public void onDisable() { + SettingsManager.reset(); + if (commandManager != null) { + commandManager.reset(); + commandManager = null; + } + super.onDisable(); + } + +} diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/commands/DestinationCommand.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/commands/DestinationCommand.java new file mode 100644 index 000000000..766c3bd96 --- /dev/null +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/commands/DestinationCommand.java @@ -0,0 +1,23 @@ +package sh.okx.railswitch.commands; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Optional; +import co.aikar.commands.annotation.Syntax; +import org.bukkit.entity.Player; +import sh.okx.railswitch.settings.SettingsManager; + +/** + * Continued support for setting and resetting your destination via a command. + */ +public final class DestinationCommand extends BaseCommand { + + @CommandAlias("dest|destination|setdestination|switch|setswitch|setsw") + @Description("Set your rail destination(s)") + @Syntax("[destination]") + public void onSetDestination(Player player, @Optional String destination) { + SettingsManager.setDestination(player, destination); + } + +} diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/glue/CitadelGlue.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/glue/CitadelGlue.java new file mode 100644 index 000000000..dfea913d1 --- /dev/null +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/glue/CitadelGlue.java @@ -0,0 +1,66 @@ +package sh.okx.railswitch.glue; + +import org.bukkit.block.Block; +import org.bukkit.plugin.Plugin; +import vg.civcraft.mc.citadel.Citadel; +import vg.civcraft.mc.citadel.ReinforcementManager; +import vg.civcraft.mc.citadel.model.Reinforcement; +import vg.civcraft.mc.civmodcore.utilities.DependencyGlue; +import vg.civcraft.mc.civmodcore.utilities.NullUtils; +import vg.civcraft.mc.civmodcore.world.WorldUtils; + +/** + * Glue for Citadel. + */ +public final class CitadelGlue extends DependencyGlue { + + private ReinforcementManager manager; + + public CitadelGlue(Plugin plugin) { + super(plugin, "Citadel"); + } + + public boolean isSafeToUse() { + if (!super.isDependencyEnabled()) { + return false; + } + if (this.manager == null) { + return false; + } + return true; + } + + /** + * Do two blocks, representing the sign and the rail, have the same reinforcement group? + * + * @param sign The block representing the sign. + * @param rail The block representing the rail. + * @return Returns true both blocks share the same reinforcement group, or if both are un-reinforced. + */ + public boolean doSignAndRailHaveSameReinforcement(Block sign, Block rail) { + if (!isSafeToUse() || !WorldUtils.isValidBlock(sign) || !WorldUtils.isValidBlock(rail)) { + return false; + } + Reinforcement signReinforcement = this.manager.getReinforcement(sign); + Reinforcement railReinforcement = this.manager.getReinforcement(rail); + if (signReinforcement == null && railReinforcement == null) { + return true; + } + if (signReinforcement == null || railReinforcement == null) { + return false; + } + return NullUtils.equalsNotNull( + signReinforcement.getGroup(), + railReinforcement.getGroup()); + } + + @Override + protected void onDependencyEnabled() { + this.manager = Citadel.getInstance().getReinforcementManager(); + } + + @Override + protected void onDependencyDisabled() { + this.manager = null; + } +} diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/DestinationSetting.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/DestinationSetting.java new file mode 100644 index 000000000..095298bf8 --- /dev/null +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/DestinationSetting.java @@ -0,0 +1,27 @@ +package sh.okx.railswitch.settings; + +import com.google.common.base.Strings; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.java.JavaPlugin; +import vg.civcraft.mc.civmodcore.players.settings.impl.StringSetting; + +/** + * Setting representing a player's destination. + */ +public final class DestinationSetting extends StringSetting { + + public DestinationSetting(JavaPlugin plugin) { + super(plugin, "", "Destination", "dest", new ItemStack(Material.MINECART), + "The destination(s) that will be used to route you at rail junctions."); + } + + @Override + public String toText(String value) { + if (Strings.isNullOrEmpty(value)) { + return ""; + } + return value; + } + +} diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/RailSwitchMenu.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/RailSwitchMenu.java new file mode 100644 index 000000000..3265958f8 --- /dev/null +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/RailSwitchMenu.java @@ -0,0 +1,18 @@ +package sh.okx.railswitch.settings; + +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import vg.civcraft.mc.civmodcore.players.settings.PlayerSettingAPI; +import vg.civcraft.mc.civmodcore.players.settings.gui.MenuSection; + +/** + * The menu for RailSwitch. This is what all RailSwitch settings will be registered to. + */ +public final class RailSwitchMenu extends MenuSection { + + public RailSwitchMenu() { + super("RailSwitch", "Settings relating to RailSwitch", PlayerSettingAPI.getMainMenu(), + new ItemStack(Material.RAIL)); + } + +} diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/ResetSetting.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/ResetSetting.java new file mode 100644 index 000000000..5389e002d --- /dev/null +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/ResetSetting.java @@ -0,0 +1,50 @@ +package sh.okx.railswitch.settings; + +import java.util.UUID; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.java.JavaPlugin; +import vg.civcraft.mc.civmodcore.players.settings.gui.MenuSection; +import vg.civcraft.mc.civmodcore.players.settings.impl.StringSetting; + +/** + * ResetSetting, a setting that functions more like a GUI button than an actual setting. + */ +public final class ResetSetting extends StringSetting { + + private final DestinationSetting destinationSetting; + + public ResetSetting(JavaPlugin plugin, DestinationSetting destinationSetting) { + super(plugin, "", "Clear Destination", "dest", new ItemStack(Material.BARRIER), "Clears your destination."); + this.destinationSetting = destinationSetting; + } + + @Override + public void handleMenuClick(Player player, MenuSection menu) { + resetPlayerDestination(player); + menu.showScreen(player); + } + + /** + * Resets a player's destination value. + * + * @param player The player to reset the destination for. + */ + public void resetPlayerDestination(Player player) { + this.destinationSetting.setValue(player, ""); + player.sendMessage(ChatColor.GREEN + "Your destination has been reset."); + } + + @Override + public String getValue(UUID player) { + return this.destinationSetting.getValue(player); + } + + @Override + public String toText(String value) { + return this.destinationSetting.toText(value); + } + +} diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/SettingsManager.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/SettingsManager.java new file mode 100644 index 000000000..d50c6bcb0 --- /dev/null +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/SettingsManager.java @@ -0,0 +1,88 @@ +package sh.okx.railswitch.settings; + +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import sh.okx.railswitch.RailSwitchPlugin; + +/** + * Manages the initialisation and registration of menu settings. + */ +public final class SettingsManager { + + private static RailSwitchMenu menu; + + private static DestinationSetting destSetting; + + private static ResetSetting resetSetting; + + /** + * Initialise the settings manager. This should only be called within RailSwitch onEnable(). + * + * @param plugin The enabled RailSwitch plugin instance. + */ + public static void init(RailSwitchPlugin plugin) { + // Create the menu elements + menu = new RailSwitchMenu(); + destSetting = new DestinationSetting(plugin); + resetSetting = new ResetSetting(plugin, destSetting); + // Register those elements + menu.registerToParentMenu(); + menu.registerSetting(destSetting); + menu.registerSetting(resetSetting); + } + + /** + * Gracefully resets the settings manager. This should only be called within RailSwitch onDisable(). + */ + public static void reset() { + // TODO: Deregister and unload all the menu elements once PlayerSettingAPI becomes reload safe + menu = null; + destSetting = null; + resetSetting = null; + } + + /** + * Sets a player's destination. This is for when the player settings don't themselves provide a way to do so. + * + * @param player The player to set the destination to. + * @param destination The destination to set. + */ + public static void setDestination(Player player, String destination) { + Preconditions.checkArgument(player != null); + if (Strings.isNullOrEmpty(destination)) { + if (resetSetting != null) { + resetSetting.resetPlayerDestination(player); + // Do not put a message here since the message is sent in the method above. + } + else { + player.sendMessage(ChatColor.RED + "Could not reset your destination."); + } + } + else { + if (destSetting != null) { + destSetting.setValue(player, destination); + player.sendMessage(ChatColor.GREEN + "Destination set to: " + destination); + } + else { + player.sendMessage(ChatColor.RED + "Could not set your destination."); + } + } + } + + /** + * Gets a player's destination. + * + * @param player The player to get the destination for. + * @return Returns the player's destination, which will never be null. + */ + public static String getDestination(Player player) { + String value = destSetting.getValue(player); + if (value == null) { + return ""; + } + return value; + } + +} diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/switches/SwitchListener.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/switches/SwitchListener.java new file mode 100644 index 000000000..ea4d3868e --- /dev/null +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/switches/SwitchListener.java @@ -0,0 +1,129 @@ +package sh.okx.railswitch.switches; + +import com.google.common.base.Strings; +import java.util.Arrays; +import org.bukkit.Material; +import org.bukkit.Tag; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.Sign; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Minecart; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockRedstoneEvent; +import sh.okx.railswitch.RailSwitchPlugin; +import sh.okx.railswitch.glue.CitadelGlue; +import sh.okx.railswitch.settings.SettingsManager; +import vg.civcraft.mc.civmodcore.world.WorldUtils; + +/** + * Switch listener that implements switch functionality. + */ +public class SwitchListener implements Listener { + + public static final String WILDCARD = "*"; + + public static final CitadelGlue CITADEL_GLUE = new CitadelGlue(RailSwitchPlugin.getPlugin(RailSwitchPlugin.class)); + + /** + * Event handler for rail switches. Will determine if a switch exists at the target location, and if so will process + * it accordingly, allowing it to trigger or not trigger depending on the rider's set destination, the listed + * destinations on the switch, and the switch type. + * + * @param event The block redstone event to base the switch's existence on. + */ + @EventHandler + public void onSwitchTrigger(BlockRedstoneEvent event) { + Block block = event.getBlock(); + // Block must be a detector rail being triggered + if (!WorldUtils.isValidBlock(block) + || block.getType() != Material.DETECTOR_RAIL + || event.getNewCurrent() != 15) { + return; + } + // Check that the block above the rail is a sign + Block above = block.getRelative(BlockFace.UP); + if (!Tag.SIGNS.isTagged(above.getType()) + || !(above.getState() instanceof Sign)) { + return; + } + // Check that the sign has a valid switch type + String[] lines = ((Sign) above.getState()).getLines(); + SwitchType type = SwitchType.find(lines[0]); + if (type == null) { + return; + } + // Check that a player is triggering the switch + // NOTE: The event doesn't provide the information and so the next best thing is searching for a + // player who is nearby and riding a minecart. + Player player = null; { + double searchDistance = Double.MAX_VALUE; + for (Entity entity : block.getWorld().getNearbyEntities(block.getLocation(), 3, 3, 3)) { + if (!(entity instanceof Player)) { + continue; + } + Entity vehicle = entity.getVehicle(); + // TODO: This should be abstracted into CivModCore + if (vehicle == null + || vehicle.getType() != EntityType.MINECART + || !(vehicle instanceof Minecart)) { + continue; + } + double distance = block.getLocation().distanceSquared(entity.getLocation()); + if (distance < searchDistance) { + searchDistance = distance; + player = (Player) entity; + } + } + } + if (player == null) { + return; + } + // If Citadel is enabled, check that the sign and the rail are on the same group + if (CITADEL_GLUE.isSafeToUse()) { + if (!CITADEL_GLUE.doSignAndRailHaveSameReinforcement(above, block)) { + return; + } + } + // Determine whether a player has a destination that matches one of the destinations + // listed on the switch signs, or match if there's a wildcard. + boolean matched = false; + String setDest = SettingsManager.getDestination(player); + if (!Strings.isNullOrEmpty(setDest)) { + String[] playerDestinations = setDest.split(" "); + String[] switchDestinations = Arrays.copyOfRange(lines, 1, lines.length); + matcher: + for (String playerDestination : playerDestinations) { + if (Strings.isNullOrEmpty(playerDestination)) { + continue; + } + if (playerDestination.equals(WILDCARD)) { + matched = true; + break; + } + for (String switchDestination : switchDestinations) { + if (Strings.isNullOrEmpty(switchDestination)) { + continue; + } + if (switchDestination.equals(WILDCARD) + || playerDestination.equalsIgnoreCase(switchDestination)) { + matched = true; + break matcher; + } + } + } + } + switch (type) { + case NORMAL: + event.setNewCurrent(matched ? 15 : 0); + break; + case INVERTED: + event.setNewCurrent(matched ? 0 : 15); + break; + } + } + +} diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/switches/SwitchType.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/switches/SwitchType.java new file mode 100644 index 000000000..e5dfeb668 --- /dev/null +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/switches/SwitchType.java @@ -0,0 +1,43 @@ +package sh.okx.railswitch.switches; + +import org.apache.commons.lang3.StringUtils; + +/** + * Switch type matcher, will match a type to a tag. + */ +public enum SwitchType { + + NORMAL("[destination]"), + INVERTED("[!destination]"); + + private final String tag; + + /** + * Defines a new switch type by its tag. Should there be a duplicate tag, the {@link SwitchType#find(String) find()} + * function will return the first value found. + * + * @param tag The tag to associate with this type. + */ + SwitchType(String tag) { + this.tag = tag; + } + + /** + * Attempts to match a switch type to a switch tag. + * + * @param tag The tag to match with a type. + * @return Returns a match switch type, or null if none are found. + */ + public static SwitchType find(String tag) { + if (tag == null || tag.isEmpty()) { + return null; + } + for (SwitchType type : values()) { + if (StringUtils.equalsIgnoreCase(tag, type.tag)) { + return type; + } + } + return null; + } + +} diff --git a/plugins/railswitch-paper/src/main/resources/plugin.yml b/plugins/railswitch-paper/src/main/resources/plugin.yml new file mode 100644 index 000000000..825105975 --- /dev/null +++ b/plugins/railswitch-paper/src/main/resources/plugin.yml @@ -0,0 +1,10 @@ +name: RailSwitch +version: ${version} +main: sh.okx.railswitch.RailSwitchPlugin +depend: [CivModCore] +softdepend: [Citadel, NameLayer] +api-version: 1.17 +authors: [Okx, Protonull] +description: Easily make rail switches for automated destination selection +commands: + dest: {} diff --git a/settings.gradle.kts b/settings.gradle.kts index 97435847c..fa185588d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -24,3 +24,8 @@ include(":plugins:simpleadminhacks-paper") include(":plugins:factorymod-paper") include(":plugins:finale-paper") include(":plugins:hiddenore-paper") +include(":plugins:railswitch-paper") +include(":plugins:civduties-paper") +include(":plugins:essenceglue-paper") +include(":plugins:namecolors-paper") +include(":plugins:kirabukkitgateway-paper")