Merge pull request #1 from plexusorg/codex/guild-worlds-gui

Add ASP guild worlds and management GUIs
This commit is contained in:
Taaha
2026-07-08 19:19:16 -07:00
committed by GitHub
24 changed files with 1083 additions and 5 deletions
+19
View File
@@ -1,9 +1,12 @@
plugins {
java
`maven-publish`
id("com.gradleup.shadow") version "9.2.2"
}
repositories {
mavenLocal()
maven {
url = uri("https://repo.papermc.io/repository/maven-public/")
}
@@ -13,6 +16,10 @@ repositories {
}
mavenCentral()
maven {
url = uri("https://repo.infernalsuite.com/repository/maven-snapshots/")
}
}
dependencies {
@@ -21,6 +28,8 @@ dependencies {
compileOnly("io.papermc.paper:paper-api:26.1.2.build.+")
implementation("org.apache.commons:commons-lang3:3.20.0")
compileOnly("dev.plex:api:2.0-SNAPSHOT")
compileOnly("com.infernalsuite.asp:api:4.0.0-SNAPSHOT")
implementation("com.infernalsuite.asp:file-loader:4.0.0-SNAPSHOT")
implementation("org.jetbrains:annotations:26.1.0")
}
@@ -45,6 +54,16 @@ tasks.getByName<Jar>("jar") {
archiveVersion.set("")
}
tasks.shadowJar {
archiveBaseName.set("Module-Guilds")
archiveClassifier.set("")
archiveVersion.set("")
}
tasks.build {
dependsOn(tasks.shadowJar)
}
tasks {
compileJava {
options.encoding = Charsets.UTF_8.name()
+14 -1
View File
@@ -4,10 +4,14 @@ import dev.plex.command.GuildCommand;
import dev.plex.config.ModuleConfig;
import dev.plex.guild.GuildHolder;
import dev.plex.handler.ChatHandlerImpl;
import dev.plex.handler.GuildMenuListener;
import dev.plex.handler.GuildWorldProtectionListener;
import dev.plex.handler.RankPermissionMenuListener;
import dev.plex.module.PlexModule;
import dev.plex.api.storage.ModuleStorage;
import dev.plex.storage.GuildRepository;
import dev.plex.storage.JdbiGuildRepository;
import dev.plex.world.GuildWorldService;
import lombok.Getter;
import java.sql.SQLException;
@@ -18,6 +22,10 @@ public class Guilds extends PlexModule
{
private static Guilds module;
private final GuildHolder guildHolder = new GuildHolder();
private final GuildMenuListener guildMenuListener = new GuildMenuListener();
private final GuildWorldProtectionListener guildWorldProtectionListener = new GuildWorldProtectionListener();
private final RankPermissionMenuListener rankPermissionMenuListener = new RankPermissionMenuListener();
private final GuildWorldService guildWorldService = new GuildWorldService();
private GuildRepository guildRepository;
@@ -36,10 +44,11 @@ public class Guilds extends PlexModule
@Override
public void enable()
{
guildWorldService.enable();
ModuleStorage storage = api().storage().forModule(this);
try
{
storage.migrations().run(List.of("001_initial_schema"));
storage.migrations().run(List.of("001_initial_schema", "002_guild_world_permissions"));
}
catch (SQLException e)
{
@@ -62,11 +71,15 @@ public class Guilds extends PlexModule
guildHolder.replaceAll(guilds);
});
registerListener(new ChatHandlerImpl());
registerListener(guildMenuListener);
registerListener(guildWorldProtectionListener);
registerListener(rankPermissionMenuListener);
}
@Override
public void disable()
{
guildWorldService.disable();
guildHolder.clear();
}
@@ -40,19 +40,30 @@ public class GuildCommand extends SimplePlexCommand
this.registerSubCommand(new ChatSubCommand());
this.registerSubCommand(new SetHomeSubCommand());
this.registerSubCommand(new HomeSubCommand());
this.registerSubCommand(new WorldSubCommand());
this.registerSubCommand(new PermissionsSubCommand());
this.registerSubCommand(new OwnerSubCommand());
this.registerSubCommand(new InviteSubCommand());
this.registerSubCommand(new AcceptSubCommand());
this.registerSubCommand(new DenySubCommand());
this.registerSubCommand(new MenuSubCommand());
}
@Override
protected Component execute(@NotNull CommandSender commandSender, @Nullable Player player, @NotNull String[] args)
{
if (args.length == 0)
{
if (player == null)
{
return getSubs();
}
Guilds.get().getGuildHolder().getGuild(player.getUniqueId()).ifPresentOrElse(
guild -> Guilds.get().getGuildMenuListener().openHome(player, guild),
() -> player.sendMessage(messageComponent("guildNotFound"))
);
return null;
}
if (args[0].equalsIgnoreCase("help"))
{
if (args.length < 2)
@@ -37,7 +37,9 @@ public class CreateSubCommand extends GuildSubCommand
{
return messageComponent("alreadyInGuild");
}
Guilds.get().getGuildRepository().createGuild(player, StringUtils.join(args, " ")).whenComplete((guild, throwable) ->
Guilds.get().getGuildRepository().createGuild(player, StringUtils.join(args, " "))
.thenCompose(guild -> Guilds.get().getGuildWorldService().ensureWorld(guild).thenApply(world -> guild))
.whenComplete((guild, throwable) ->
{
if (throwable != null)
{
@@ -0,0 +1,34 @@
package dev.plex.command.sub;
import dev.plex.Guilds;
import dev.plex.command.source.RequiredCommandSource;
import net.kyori.adventure.text.Component;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class MenuSubCommand extends GuildSubCommand
{
public MenuSubCommand()
{
super(command("menu")
.description("Opens the guild management menu")
.usage("/guild <command>")
.aliases("gui,panel")
.permission("plex.guilds.menu")
.source(RequiredCommandSource.IN_GAME)
.build());
}
@Override
protected Component execute(@NotNull CommandSender commandSender, @Nullable Player player, @NotNull String[] args)
{
assert player != null;
Guilds.get().getGuildHolder().getGuild(player.getUniqueId()).ifPresentOrElse(
guild -> Guilds.get().getGuildMenuListener().openHome(player, guild),
() -> send(player, messageComponent("guildNotFound"))
);
return null;
}
}
@@ -0,0 +1,39 @@
package dev.plex.command.sub;
import dev.plex.Guilds;
import dev.plex.command.source.RequiredCommandSource;
import net.kyori.adventure.text.Component;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class PermissionsSubCommand extends GuildSubCommand
{
public PermissionsSubCommand()
{
super(command("permissions")
.description("Opens the rank permissions GUI")
.usage("/guild <command>")
.aliases("perms")
.permission("plex.guilds.permissions")
.source(RequiredCommandSource.IN_GAME)
.build());
}
@Override
protected Component execute(@NotNull CommandSender commandSender, @Nullable Player player, @NotNull String[] args)
{
assert player != null;
Guilds.get().getGuildHolder().getGuild(player.getUniqueId()).ifPresentOrElse(guild ->
{
if (!guild.isOwner(player.getUniqueId()))
{
send(player, messageComponent("guildNotOwner"));
return;
}
Guilds.get().getRankPermissionMenuListener().openRankList(player, guild);
}, () -> send(player, messageComponent("guildNotFound")));
return null;
}
}
@@ -0,0 +1,55 @@
package dev.plex.command.sub;
import dev.plex.Guilds;
import dev.plex.command.source.RequiredCommandSource;
import net.kyori.adventure.text.Component;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class WorldSubCommand extends GuildSubCommand
{
public WorldSubCommand()
{
super(command("world")
.description("Teleports to your guild world")
.usage("/guild <command>")
.aliases("base")
.permission("plex.guilds.world")
.source(RequiredCommandSource.IN_GAME)
.build());
}
@Override
protected Component execute(@NotNull CommandSender commandSender, @Nullable Player player, @NotNull String[] args)
{
assert player != null;
Guilds.get().getGuildHolder().getGuild(player.getUniqueId()).ifPresentOrElse(guild ->
{
send(player, messageComponent("guildWorldLoading"));
Guilds.get().getGuildWorldService().ensureWorld(guild).whenComplete((world, throwable) ->
{
if (throwable != null)
{
Guilds.get().api().scheduler().executeGlobal(() ->
{
throwable.printStackTrace();
send(player, messageComponent("guildWorldLoadFailed"));
});
return;
}
Guilds.get().api().scheduler().executeEntity(player, () -> teleport(player, world), 1L);
});
}, () -> send(player, messageComponent("guildNotFound")));
return null;
}
private void teleport(Player player, World world)
{
Location spawnLocation = world.getSpawnLocation().toCenterLocation();
player.teleportAsync(spawnLocation);
}
}
@@ -0,0 +1,17 @@
package dev.plex.gui;
import dev.plex.guild.Guild;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
public record GuildMenuInventoryHolder(Guild guild, GuildMenuView view, UUID memberUuid) implements InventoryHolder
{
@Override
public @NotNull Inventory getInventory()
{
throw new UnsupportedOperationException();
}
}
@@ -0,0 +1,8 @@
package dev.plex.gui;
public enum GuildMenuView
{
HOME,
MEMBERS,
MEMBER
}
@@ -0,0 +1,15 @@
package dev.plex.gui;
import dev.plex.guild.Guild;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.jetbrains.annotations.NotNull;
public record RankPermissionInventoryHolder(Guild guild, String rankName) implements InventoryHolder
{
@Override
public @NotNull Inventory getInventory()
{
throw new UnsupportedOperationException();
}
}
+65
View File
@@ -3,8 +3,10 @@ package dev.plex.guild;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import dev.plex.Guilds;
import dev.plex.guild.data.GuildPermission;
import dev.plex.guild.data.GuildRole;
import dev.plex.guild.data.Member;
import dev.plex.api.player.PlexPlayerView;
import dev.plex.util.CustomLocation;
import dev.plex.util.GuildUtil;
import lombok.Data;
@@ -15,6 +17,7 @@ import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
@Data
@@ -31,6 +34,9 @@ public class Guild
private CustomLocation home;
private boolean tagEnabled = true;
private boolean isPublic;
private boolean memberBlockBreaking;
private boolean memberBlockPlacing;
private boolean memberInteracting;
public static Guild create(Player player, String guildName)
{
@@ -62,4 +68,63 @@ public class Guild
{
return ownerUuid != null && ownerUuid.equals(uuid);
}
public boolean isMember(UUID uuid)
{
return getMember(uuid) != null;
}
public void removeMember(UUID uuid)
{
members.removeIf(member -> member.getUuid().equals(uuid));
}
public List<? extends PlexPlayerView> getPlexPlayers()
{
return members.stream()
.map(Member::getPlexPlayer)
.flatMap(java.util.Optional::stream)
.toList();
}
public List<Player> getOnlinePlayers()
{
return getPlexPlayers().stream()
.map(PlexPlayerView::bukkitPlayer)
.filter(Objects::nonNull)
.toList();
}
public String getWorldName()
{
return "guild_" + guildUuid.toString().replace("-", "");
}
public boolean hasPermission(UUID uuid, GuildPermission permission)
{
if (isOwner(uuid))
{
return true;
}
if (!isMember(uuid))
{
return false;
}
return switch (permission)
{
case BLOCK_BREAKING -> memberBlockBreaking;
case BLOCK_PLACING -> memberBlockPlacing;
case INTERACTING -> memberInteracting;
};
}
public void setPermission(GuildPermission permission, boolean enabled)
{
switch (permission)
{
case BLOCK_BREAKING -> memberBlockBreaking = enabled;
case BLOCK_PLACING -> memberBlockPlacing = enabled;
case INTERACTING -> memberInteracting = enabled;
}
}
}
@@ -0,0 +1,29 @@
package dev.plex.guild.data;
import org.bukkit.Material;
public enum GuildPermission
{
BLOCK_BREAKING("Block Breaking", Material.DIAMOND_PICKAXE),
BLOCK_PLACING("Block Placing", Material.GRASS_BLOCK),
INTERACTING("Interacting", Material.OAK_BUTTON);
private final String displayName;
private final Material material;
GuildPermission(String displayName, Material material)
{
this.displayName = displayName;
this.material = material;
}
public String displayName()
{
return displayName;
}
public Material material()
{
return material;
}
}
@@ -1,9 +1,12 @@
package dev.plex.guild.data;
import dev.plex.Guilds;
import dev.plex.api.player.PlexPlayerView;
import lombok.Data;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.util.Optional;
import java.util.UUID;
@Data
@@ -28,4 +31,16 @@ public class Member
{
return Bukkit.getPlayer(this.uuid);
}
public Optional<? extends PlexPlayerView> getPlexPlayer()
{
return Guilds.get().api().players().player(this.uuid);
}
public String getName()
{
return getPlexPlayer()
.map(PlexPlayerView::name)
.orElse(this.uuid.toString());
}
}
@@ -0,0 +1,290 @@
package dev.plex.handler;
import dev.plex.Guilds;
import dev.plex.guild.Guild;
import dev.plex.guild.data.GuildRole;
import dev.plex.guild.data.Member;
import dev.plex.gui.GuildMenuInventoryHolder;
import dev.plex.gui.GuildMenuView;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemFlag;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
public class GuildMenuListener implements Listener
{
private static final int MEMBERS_SLOT = 11;
private static final int WORLD_SLOT = 13;
private static final int PERMISSIONS_SLOT = 15;
private static final int BACK_SLOT = 26;
private static final int KICK_SLOT = 11;
private static final int OWNER_SLOT = 13;
private static final int RANK_PERMISSIONS_SLOT = 15;
public void openHome(Player player, Guild guild)
{
Inventory inventory = Bukkit.createInventory(new GuildMenuInventoryHolder(guild, GuildMenuView.HOME, null), 27, title("* " + guild.getName()));
inventory.setItem(MEMBERS_SLOT, item(Material.PLAYER_HEAD, "Guild Members", NamedTextColor.AQUA, List.of(
line("View online status", NamedTextColor.GRAY),
line("Click to manage members", NamedTextColor.YELLOW)
)));
inventory.setItem(WORLD_SLOT, item(Material.GRASS_BLOCK, "Guild World", NamedTextColor.GREEN, List.of(
line("Load and enter your guild world", NamedTextColor.GRAY),
line("Only guild members can access it", NamedTextColor.DARK_AQUA)
)));
if (guild.isOwner(player.getUniqueId()))
{
inventory.setItem(PERMISSIONS_SLOT, item(Material.COMPARATOR, "Rank Permissions", NamedTextColor.GOLD, List.of(
line("Configure member build permissions", NamedTextColor.GRAY),
line("Break - Place - Interact", NamedTextColor.YELLOW)
)));
}
player.openInventory(inventory);
}
private void openMembers(Player player, Guild guild)
{
Inventory inventory = Bukkit.createInventory(new GuildMenuInventoryHolder(guild, GuildMenuView.MEMBERS, null), 54, title("* " + guild.getName() + " Members"));
List<Member> members = guild.getMembers();
for (int i = 0; i < members.size() && i < 45; i++)
{
inventory.setItem(i, memberItem(guild, members.get(i)));
}
inventory.setItem(BACK_SLOT, item(Material.ARROW, "Back", NamedTextColor.YELLOW, List.of(line("Return to the guild menu", NamedTextColor.GRAY))));
player.openInventory(inventory);
}
private void openMember(Player player, Guild guild, Member member)
{
Inventory inventory = Bukkit.createInventory(new GuildMenuInventoryHolder(guild, GuildMenuView.MEMBER, member.getUuid()), 27, title("* " + member.getName()));
inventory.setItem(4, memberItem(guild, member));
if (guild.isOwner(player.getUniqueId()) && !guild.isOwner(member.getUuid()))
{
inventory.setItem(KICK_SLOT, item(Material.BARRIER, "Kick Member", NamedTextColor.RED, List.of(
line("Remove this player from the guild", NamedTextColor.GRAY),
line("They will lose guild world access", NamedTextColor.DARK_RED)
)));
inventory.setItem(OWNER_SLOT, item(Material.GOLDEN_HELMET, "Transfer Ownership", NamedTextColor.GOLD, List.of(
line("Make this player the guild owner", NamedTextColor.GRAY),
line("You will become a regular member", NamedTextColor.YELLOW)
)));
}
if (guild.isOwner(player.getUniqueId()))
{
inventory.setItem(RANK_PERMISSIONS_SLOT, item(Material.COMPARATOR, "Rank Permissions", NamedTextColor.AQUA, List.of(
line("Open the rank permission editor", NamedTextColor.GRAY)
)));
}
inventory.setItem(BACK_SLOT, item(Material.ARROW, "Back", NamedTextColor.YELLOW, List.of(line("Return to member list", NamedTextColor.GRAY))));
player.openInventory(inventory);
}
@EventHandler(priority = EventPriority.HIGH)
public void onClick(InventoryClickEvent event)
{
if (!(event.getWhoClicked() instanceof Player player) || !(event.getInventory().getHolder() instanceof GuildMenuInventoryHolder holder))
{
return;
}
event.setCancelled(true);
if (!holder.guild().isMember(player.getUniqueId()))
{
player.closeInventory();
player.sendMessage(Guilds.get().messageComponent("guildNotFound"));
return;
}
ItemStack clickedItem = event.getCurrentItem();
if (clickedItem == null || clickedItem.getType() == Material.AIR)
{
return;
}
switch (holder.view())
{
case HOME -> clickHome(player, holder.guild(), event.getRawSlot());
case MEMBERS -> clickMembers(player, holder.guild(), clickedItem, event.getRawSlot());
case MEMBER -> clickMember(player, holder.guild(), holder.memberUuid(), event.getRawSlot());
}
}
private void clickHome(Player player, Guild guild, int slot)
{
if (slot == MEMBERS_SLOT)
{
openMembers(player, guild);
return;
}
if (slot == WORLD_SLOT)
{
player.closeInventory();
Guilds.get().getGuildWorldService().ensureWorld(guild).whenComplete((world, throwable) ->
{
if (throwable != null)
{
Guilds.get().api().scheduler().executeGlobal(() -> player.sendMessage(Guilds.get().messageComponent("guildWorldLoadFailed")));
return;
}
Guilds.get().api().scheduler().executeEntity(player, () -> player.teleportAsync(world.getSpawnLocation().toCenterLocation()), 1L);
});
return;
}
if (slot == PERMISSIONS_SLOT && guild.isOwner(player.getUniqueId()))
{
Guilds.get().getRankPermissionMenuListener().openRankList(player, guild);
}
}
private void clickMembers(Player player, Guild guild, ItemStack clickedItem, int slot)
{
if (slot == BACK_SLOT)
{
openHome(player, guild);
return;
}
memberUuid(clickedItem).map(guild::getMember).filter(Objects::nonNull).ifPresent(member -> openMember(player, guild, member));
}
private void clickMember(Player player, Guild guild, UUID memberUuid, int slot)
{
Member member = guild.getMember(memberUuid);
if (member == null)
{
openMembers(player, guild);
return;
}
if (slot == BACK_SLOT)
{
openMembers(player, guild);
return;
}
if (!guild.isOwner(player.getUniqueId()))
{
return;
}
if (slot == KICK_SLOT && !guild.isOwner(memberUuid))
{
Guilds.get().getGuildRepository().removeMember(guild.getGuildUuid(), memberUuid).whenComplete((unused, throwable) ->
{
if (throwable != null)
{
player.sendMessage(Guilds.get().messageComponent("guildStorageFailed"));
return;
}
guild.removeMember(memberUuid);
Guilds.get().getGuildHolder().unindexMember(memberUuid);
Guilds.get().getGuildWorldService().ejectNonMembers(guild);
player.sendMessage(Guilds.get().messageComponent("guildMemberKicked", member.getName()));
openMembers(player, guild);
});
return;
}
if (slot == OWNER_SLOT && !guild.isOwner(memberUuid))
{
Member previousOwner = guild.getMember(player.getUniqueId());
Guilds.get().getGuildRepository().transferOwner(guild.getGuildUuid(), memberUuid, player.getUniqueId()).whenComplete((unused, throwable) ->
{
if (throwable != null)
{
player.sendMessage(Guilds.get().messageComponent("guildStorageFailed"));
return;
}
guild.setOwnerUuid(memberUuid);
member.setRole(GuildRole.OWNER);
if (previousOwner != null)
{
previousOwner.setRole(GuildRole.MEMBER);
}
player.sendMessage(Guilds.get().messageComponent("guildOwnerSet", member.getName()));
openMember(player, guild, previousOwner == null ? member : previousOwner);
});
return;
}
if (slot == RANK_PERMISSIONS_SLOT)
{
Guilds.get().getRankPermissionMenuListener().openRankList(player, guild);
}
}
private ItemStack memberItem(Guild guild, Member member)
{
boolean online = Bukkit.getPlayer(member.getUuid()) != null;
ItemStack itemStack = new ItemStack(online ? Material.LIME_WOOL : Material.GRAY_WOOL);
ItemMeta itemMeta = itemStack.getItemMeta();
itemMeta.displayName(text(member.getName(), online ? NamedTextColor.GREEN : NamedTextColor.GRAY));
List<Component> lore = new ArrayList<>();
lore.add(label("Status", online ? "Online" : "Offline", online ? NamedTextColor.GREEN : NamedTextColor.GRAY));
lore.add(label("Role", guild.isOwner(member.getUuid()) ? "Owner" : "Member", guild.isOwner(member.getUuid()) ? NamedTextColor.GOLD : NamedTextColor.AQUA));
lore.add(label("Rank", guild.isOwner(member.getUuid()) ? "Owner" : "Member", NamedTextColor.LIGHT_PURPLE));
lore.add(Component.empty());
lore.add(line("Click to manage", NamedTextColor.YELLOW));
itemMeta.lore(lore);
itemMeta.addItemFlags(ItemFlag.values());
itemMeta.getPersistentDataContainer().set(Keys.MEMBER_UUID, org.bukkit.persistence.PersistentDataType.STRING, member.getUuid().toString());
itemStack.setItemMeta(itemMeta);
return itemStack;
}
private ItemStack item(Material material, String name, NamedTextColor color, List<Component> lore)
{
ItemStack itemStack = new ItemStack(material);
ItemMeta itemMeta = itemStack.getItemMeta();
itemMeta.displayName(text(name, color));
itemMeta.lore(lore);
itemMeta.addItemFlags(ItemFlag.values());
itemStack.setItemMeta(itemMeta);
return itemStack;
}
private Component title(String value)
{
return text(value, NamedTextColor.DARK_AQUA);
}
private Component text(String value, NamedTextColor color)
{
return Component.text(value, color).decoration(TextDecoration.ITALIC, false);
}
private Component line(String value, NamedTextColor color)
{
return text("> " + value, color);
}
private Component label(String label, String value, NamedTextColor valueColor)
{
return text(label + ": ", NamedTextColor.GRAY).append(text(value, valueColor));
}
private java.util.Optional<UUID> memberUuid(ItemStack itemStack)
{
if (!itemStack.hasItemMeta())
{
return java.util.Optional.empty();
}
String value = itemStack.getItemMeta().getPersistentDataContainer().get(Keys.MEMBER_UUID, org.bukkit.persistence.PersistentDataType.STRING);
if (value == null)
{
return java.util.Optional.empty();
}
return java.util.Optional.of(UUID.fromString(value));
}
private static class Keys
{
private static final org.bukkit.NamespacedKey MEMBER_UUID = new org.bukkit.NamespacedKey("plex_guilds", "member_uuid");
}
}
@@ -0,0 +1,99 @@
package dev.plex.handler;
import dev.plex.Guilds;
import dev.plex.guild.Guild;
import dev.plex.guild.data.GuildPermission;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.player.PlayerChangedWorldEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerTeleportEvent;
import java.util.Optional;
public class GuildWorldProtectionListener implements Listener
{
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onTeleport(PlayerTeleportEvent event)
{
if (event.getTo() == null)
{
return;
}
Optional<Guild> guild = guildByWorld(event.getTo().getWorld());
if (guild.isPresent() && !guild.get().isMember(event.getPlayer().getUniqueId()))
{
event.setCancelled(true);
event.getPlayer().sendMessage(Guilds.get().messageComponent("guildWorldNoAccess"));
}
}
@EventHandler(priority = EventPriority.MONITOR)
public void onChangedWorld(PlayerChangedWorldEvent event)
{
Optional<Guild> guild = guildByWorld(event.getPlayer().getWorld());
if (guild.isPresent() && !guild.get().isMember(event.getPlayer().getUniqueId()))
{
World fallback = Bukkit.getWorlds().getFirst();
event.getPlayer().teleportAsync(fallback.getSpawnLocation());
event.getPlayer().sendMessage(Guilds.get().messageComponent("guildWorldNoAccess"));
}
}
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onBlockBreak(BlockBreakEvent event)
{
if (!canUse(event.getPlayer(), event.getBlock().getWorld(), GuildPermission.BLOCK_BREAKING))
{
event.setCancelled(true);
event.getPlayer().sendMessage(Guilds.get().messageComponent("guildWorldPermissionDenied"));
}
}
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onBlockPlace(BlockPlaceEvent event)
{
if (!canUse(event.getPlayer(), event.getBlock().getWorld(), GuildPermission.BLOCK_PLACING))
{
event.setCancelled(true);
event.getPlayer().sendMessage(Guilds.get().messageComponent("guildWorldPermissionDenied"));
}
}
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onInteract(PlayerInteractEvent event)
{
if (event.getClickedBlock() == null)
{
return;
}
if (!canUse(event.getPlayer(), event.getClickedBlock().getWorld(), GuildPermission.INTERACTING))
{
event.setCancelled(true);
event.getPlayer().sendMessage(Guilds.get().messageComponent("guildWorldPermissionDenied"));
}
}
private boolean canUse(Player player, World world, GuildPermission permission)
{
Optional<Guild> guild = guildByWorld(world);
return guild.map(value -> value.hasPermission(player.getUniqueId(), permission)).orElse(true);
}
private Optional<Guild> guildByWorld(World world)
{
if (world == null)
{
return Optional.empty();
}
return Guilds.get().getGuildHolder().getGuilds().stream()
.filter(guild -> guild.getWorldName().equals(world.getName()))
.findFirst();
}
}
@@ -0,0 +1,134 @@
package dev.plex.handler;
import dev.plex.Guilds;
import dev.plex.guild.Guild;
import dev.plex.guild.data.GuildPermission;
import dev.plex.gui.RankPermissionInventoryHolder;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import java.util.List;
import java.util.UUID;
public class RankPermissionMenuListener implements Listener
{
private static final UUID MEMBER_PERMISSION_PROBE = new UUID(0L, 0L);
public void openRankList(Player player, Guild guild)
{
Inventory inventory = Bukkit.createInventory(new RankPermissionInventoryHolder(guild, null), 27, title("* Guild Rank Permissions"));
inventory.setItem(13, rankItem());
player.openInventory(inventory);
}
@EventHandler(priority = EventPriority.HIGH)
public void onClick(InventoryClickEvent event)
{
if (!(event.getWhoClicked() instanceof Player player) || !(event.getInventory().getHolder() instanceof RankPermissionInventoryHolder holder))
{
return;
}
event.setCancelled(true);
if (!holder.guild().isOwner(player.getUniqueId()))
{
player.closeInventory();
player.sendMessage(Guilds.get().messageComponent("guildNotOwner"));
return;
}
ItemStack clickedItem = event.getCurrentItem();
if (clickedItem == null || clickedItem.getType() == Material.AIR)
{
return;
}
if (holder.rankName() == null)
{
openPermissionEditor(player, holder.guild());
return;
}
GuildPermission permission = permissionBySlot(event.getRawSlot());
if (permission == null)
{
return;
}
boolean enabled = !holder.guild().hasPermission(MEMBER_PERMISSION_PROBE, permission);
holder.guild().setPermission(permission, enabled);
Guilds.get().getGuildRepository().updateMemberPermission(holder.guild().getGuildUuid(), permission, enabled);
openPermissionEditor(player, holder.guild());
}
private void openPermissionEditor(Player player, Guild guild)
{
Inventory inventory = Bukkit.createInventory(new RankPermissionInventoryHolder(guild, "MEMBER"), 27, title("* Member Permissions"));
GuildPermission[] permissions = GuildPermission.values();
for (int i = 0; i < permissions.length; i++)
{
inventory.setItem(11 + i * 2, permissionItem(guild, permissions[i]));
}
player.openInventory(inventory);
}
private GuildPermission permissionBySlot(int slot)
{
return switch (slot)
{
case 11 -> GuildPermission.BLOCK_BREAKING;
case 13 -> GuildPermission.BLOCK_PLACING;
case 15 -> GuildPermission.INTERACTING;
default -> null;
};
}
private ItemStack rankItem()
{
ItemStack itemStack = new ItemStack(Material.NAME_TAG);
ItemMeta itemMeta = itemStack.getItemMeta();
itemMeta.displayName(text("Member", NamedTextColor.AQUA));
itemMeta.lore(List.of(
line("Manage member guild world access", NamedTextColor.GRAY),
line("Click to edit permissions", NamedTextColor.YELLOW)
));
itemStack.setItemMeta(itemMeta);
return itemStack;
}
private ItemStack permissionItem(Guild guild, GuildPermission permission)
{
ItemStack itemStack = new ItemStack(permission.material());
ItemMeta itemMeta = itemStack.getItemMeta();
boolean enabled = guild.hasPermission(MEMBER_PERMISSION_PROBE, permission);
itemMeta.displayName(text(permission.displayName(), enabled ? NamedTextColor.GREEN : NamedTextColor.RED));
itemMeta.lore(List.of(
text("Status: ", NamedTextColor.GRAY).append(text(enabled ? "Enabled" : "Disabled", enabled ? NamedTextColor.GREEN : NamedTextColor.RED)),
Component.empty(),
line("Click to toggle", NamedTextColor.YELLOW)
));
itemStack.setItemMeta(itemMeta);
return itemStack;
}
private Component title(String value)
{
return text(value, NamedTextColor.DARK_AQUA);
}
private Component text(String value, NamedTextColor color)
{
return Component.text(value, color).decoration(TextDecoration.ITALIC, false);
}
private Component line(String value, NamedTextColor color)
{
return text("> " + value, color);
}
}
@@ -1,6 +1,7 @@
package dev.plex.storage;
import dev.plex.guild.Guild;
import dev.plex.guild.data.GuildPermission;
import dev.plex.guild.data.GuildRole;
import dev.plex.storage.entity.GuildInviteEntity;
import dev.plex.util.CustomLocation;
@@ -25,6 +26,8 @@ public interface GuildRepository
CompletableFuture<Void> transferOwner(UUID guildUuid, UUID newOwnerUuid, UUID oldOwnerUuid);
CompletableFuture<Void> updateMemberPermission(UUID guildUuid, GuildPermission permission, boolean enabled);
CompletableFuture<Void> updatePrefix(UUID guildUuid, String prefix);
CompletableFuture<Void> updateHome(UUID guildUuid, CustomLocation home);
@@ -3,6 +3,7 @@ package dev.plex.storage;
import dev.plex.Guilds;
import dev.plex.api.storage.ModuleStorage;
import dev.plex.guild.Guild;
import dev.plex.guild.data.GuildPermission;
import dev.plex.guild.data.GuildRole;
import dev.plex.guild.data.Member;
import dev.plex.storage.entity.GuildEntity;
@@ -92,9 +93,10 @@ public class JdbiGuildRepository implements GuildRepository
jdbi.useTransaction(h ->
{
h.createUpdate("INSERT INTO " + guildsTable + " (guild_uuid, name, prefix, owner_uuid, created_at, " +
"home_world, home_x, home_y, home_z, home_yaw, home_pitch, motd, tag_enabled, is_public) " +
"home_world, home_x, home_y, home_z, home_yaw, home_pitch, motd, tag_enabled, is_public, " +
"member_block_breaking, member_block_placing, member_interacting) " +
"VALUES (:guildUuid, :name, :prefix, :ownerUuid, :createdAt, :homeWorld, :homeX, :homeY, :homeZ, " +
":homeYaw, :homePitch, :motd, :tagEnabled, :isPublic)")
":homeYaw, :homePitch, :motd, :tagEnabled, :isPublic, :memberBlockBreaking, :memberBlockPlacing, :memberInteracting)")
.bind("guildUuid", e.getGuildUuid())
.bind("name", e.getName())
.bind("prefix", e.getPrefix())
@@ -109,6 +111,9 @@ public class JdbiGuildRepository implements GuildRepository
.bind("motd", e.getMotd())
.bind("tagEnabled", e.isTagEnabled())
.bind("isPublic", e.isPublicGuild())
.bind("memberBlockBreaking", e.isMemberBlockBreaking())
.bind("memberBlockPlacing", e.isMemberBlockPlacing())
.bind("memberInteracting", e.isMemberInteracting())
.execute();
insertMember(h, guild.getGuildUuid(), owner.getUniqueId(), GuildRole.OWNER);
});
@@ -162,6 +167,21 @@ public class JdbiGuildRepository implements GuildRepository
}));
}
@Override
public CompletableFuture<Void> updateMemberPermission(UUID guildUuid, GuildPermission permission, boolean enabled)
{
String column = switch (permission)
{
case BLOCK_BREAKING -> "member_block_breaking";
case BLOCK_PLACING -> "member_block_placing";
case INTERACTING -> "member_interacting";
};
return runAsync(() -> jdbi.useHandle(h -> h.createUpdate("UPDATE " + guildsTable + " SET " + column + " = :enabled WHERE guild_uuid = :g")
.bind("enabled", enabled)
.bind("g", guildUuid.toString())
.execute()));
}
@Override
public CompletableFuture<Void> updatePrefix(UUID guildUuid, String prefix)
{
@@ -303,6 +323,9 @@ public class JdbiGuildRepository implements GuildRepository
e.setMotd(rs.getString("motd"));
e.setTagEnabled(rs.getBoolean("tag_enabled"));
e.setPublicGuild(rs.getBoolean("is_public"));
e.setMemberBlockBreaking(rs.getBoolean("member_block_breaking"));
e.setMemberBlockPlacing(rs.getBoolean("member_block_placing"));
e.setMemberInteracting(rs.getBoolean("member_interacting"));
return e;
}
@@ -353,6 +376,9 @@ public class JdbiGuildRepository implements GuildRepository
guild.setMotd(entity.getMotd());
guild.setTagEnabled(entity.isTagEnabled());
guild.setPublic(entity.isPublicGuild());
guild.setMemberBlockBreaking(entity.isMemberBlockBreaking());
guild.setMemberBlockPlacing(entity.isMemberBlockPlacing());
guild.setMemberInteracting(entity.isMemberInteracting());
guild.setHome(toLocation(entity));
return guild;
}
@@ -368,6 +394,9 @@ public class JdbiGuildRepository implements GuildRepository
entity.setMotd(guild.getMotd());
entity.setTagEnabled(guild.isTagEnabled());
entity.setPublicGuild(guild.isPublic());
entity.setMemberBlockBreaking(guild.isMemberBlockBreaking());
entity.setMemberBlockPlacing(guild.isMemberBlockPlacing());
entity.setMemberInteracting(guild.isMemberInteracting());
setHome(entity, guild.getHome());
return entity;
}
@@ -21,6 +21,9 @@ public class GuildEntity
private String motd;
private boolean tagEnabled = true;
private boolean publicGuild;
private boolean memberBlockBreaking;
private boolean memberBlockPlacing;
private boolean memberInteracting;
public GuildEntity()
{
@@ -0,0 +1,184 @@
package dev.plex.world;
import com.infernalsuite.asp.api.AdvancedSlimePaperAPI;
import com.infernalsuite.asp.api.exceptions.CorruptedWorldException;
import com.infernalsuite.asp.api.exceptions.NewerFormatException;
import com.infernalsuite.asp.api.exceptions.UnknownWorldException;
import com.infernalsuite.asp.api.loaders.SlimeLoader;
import com.infernalsuite.asp.api.world.SlimeWorld;
import com.infernalsuite.asp.api.world.SlimeWorldInstance;
import com.infernalsuite.asp.api.world.properties.SlimeProperties;
import com.infernalsuite.asp.api.world.properties.SlimePropertyMap;
import com.infernalsuite.asp.loaders.file.FileLoader;
import dev.plex.Guilds;
import dev.plex.guild.Guild;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
public class GuildWorldService
{
private final AdvancedSlimePaperAPI asp = AdvancedSlimePaperAPI.instance();
private final Map<UUID, SlimeWorldInstance> loadedWorlds = new ConcurrentHashMap<>();
private SlimeLoader loader;
public void enable()
{
File worldDirectory = new File(Guilds.get().getDataFolder(), "slime-worlds");
loader = new FileLoader(worldDirectory);
}
public CompletableFuture<World> ensureWorld(Guild guild)
{
SlimeWorldInstance loadedWorld = loadedWorlds.get(guild.getGuildUuid());
if (loadedWorld != null)
{
return CompletableFuture.completedFuture(loadedWorld.getBukkitWorld());
}
SlimeWorldInstance aspLoadedWorld = asp.getLoadedWorld(guild.getWorldName());
if (aspLoadedWorld != null)
{
loadedWorlds.put(guild.getGuildUuid(), aspLoadedWorld);
return CompletableFuture.completedFuture(aspLoadedWorld.getBukkitWorld());
}
return CompletableFuture.supplyAsync(() -> readOrCreateWorld(guild), Guilds.get().api().scheduler().asyncExecutor())
.thenCompose(slimeWorld -> loadWorld(guild, slimeWorld))
.thenApply(world ->
{
initializeWorld(world);
saveWorld(guild);
return world;
});
}
public void saveWorld(Guild guild)
{
SlimeWorldInstance loadedWorld = loadedWorlds.get(guild.getGuildUuid());
if (loadedWorld == null)
{
return;
}
CompletableFuture.runAsync(() -> saveLoadedWorld(loadedWorld), Guilds.get().api().scheduler().asyncExecutor());
}
public void ejectNonMembers(Guild guild)
{
SlimeWorldInstance loadedWorld = loadedWorlds.get(guild.getGuildUuid());
if (loadedWorld == null)
{
return;
}
Guilds.get().api().scheduler().executeGlobal(() ->
{
World fallback = Bukkit.getWorlds().getFirst();
loadedWorld.getBukkitWorld().getPlayers().stream()
.filter(player -> !guild.isMember(player.getUniqueId()))
.forEach(player ->
{
player.teleportAsync(fallback.getSpawnLocation());
player.sendMessage(Guilds.get().messageComponent("guildWorldNoAccess"));
});
});
}
public void disable()
{
loadedWorlds.values().forEach(loadedWorld ->
{
saveLoadedWorld(loadedWorld);
Bukkit.unloadWorld(loadedWorld.getBukkitWorld(), false);
});
loadedWorlds.clear();
}
private SlimeWorld readOrCreateWorld(Guild guild)
{
SlimePropertyMap propertyMap = createPropertyMap();
try
{
return asp.readWorld(loader, guild.getWorldName(), false, propertyMap);
}
catch (UnknownWorldException ignored)
{
return asp.createEmptyWorld(guild.getWorldName(), false, propertyMap, loader);
}
catch (IOException | CorruptedWorldException | NewerFormatException e)
{
throw new GuildWorldException("Failed to read guild world " + guild.getWorldName(), e);
}
}
private CompletableFuture<World> loadWorld(Guild guild, SlimeWorld slimeWorld)
{
CompletableFuture<World> future = new CompletableFuture<>();
Guilds.get().api().scheduler().executeGlobal(() ->
{
try
{
SlimeWorldInstance loadedWorld = asp.loadWorld(slimeWorld, true);
loadedWorlds.put(guild.getGuildUuid(), loadedWorld);
future.complete(loadedWorld.getBukkitWorld());
}
catch (Throwable throwable)
{
future.completeExceptionally(throwable);
}
});
return future;
}
private void initializeWorld(World world)
{
int y = world.getMaxHeight() / 2;
Location spawn = new Location(world, 0.5, y + 1, 0.5);
world.getBlockAt(0, y, 0).setType(Material.GRASS_BLOCK, false);
world.setSpawnLocation(spawn);
}
private void saveLoadedWorld(SlimeWorldInstance loadedWorld)
{
try
{
asp.saveWorld(loadedWorld.getSerializableCopy());
}
catch (IOException e)
{
throw new GuildWorldException("Failed to save guild world " + loadedWorld.getName(), e);
}
}
private SlimePropertyMap createPropertyMap()
{
SlimePropertyMap propertyMap = new SlimePropertyMap();
propertyMap.setValue(SlimeProperties.DIFFICULTY, "peaceful");
propertyMap.setValue(SlimeProperties.SPAWN_X, 0);
propertyMap.setValue(SlimeProperties.SPAWN_Y, 65);
propertyMap.setValue(SlimeProperties.SPAWN_Z, 0);
propertyMap.setValue(SlimeProperties.ALLOW_ANIMALS, false);
propertyMap.setValue(SlimeProperties.ALLOW_MONSTERS, false);
propertyMap.setValue(SlimeProperties.DRAGON_BATTLE, false);
propertyMap.setValue(SlimeProperties.PVP, false);
propertyMap.setValue(SlimeProperties.ENVIRONMENT, "normal");
propertyMap.setValue(SlimeProperties.WORLD_TYPE, "FLAT");
propertyMap.setValue(SlimeProperties.DEFAULT_BIOME, "minecraft:plains");
return propertyMap;
}
public static class GuildWorldException extends RuntimeException
{
public GuildWorldException(String message, Throwable cause)
{
super(message, cause);
}
}
}
@@ -0,0 +1,3 @@
ALTER TABLE {{table:guilds}} ADD COLUMN `member_block_breaking` BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE {{table:guilds}} ADD COLUMN `member_block_placing` BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE {{table:guilds}} ADD COLUMN `member_interacting` BOOLEAN NOT NULL DEFAULT FALSE;
@@ -0,0 +1,3 @@
ALTER TABLE {{table:guilds}} ADD COLUMN member_block_breaking BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE {{table:guilds}} ADD COLUMN member_block_placing BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE {{table:guilds}} ADD COLUMN member_interacting BOOLEAN NOT NULL DEFAULT FALSE;
@@ -0,0 +1,3 @@
ALTER TABLE {{table:guilds}} ADD COLUMN member_block_breaking BOOLEAN NOT NULL DEFAULT 0;
ALTER TABLE {{table:guilds}} ADD COLUMN member_block_placing BOOLEAN NOT NULL DEFAULT 0;
ALTER TABLE {{table:guilds}} ADD COLUMN member_interacting BOOLEAN NOT NULL DEFAULT 0;
+5
View File
@@ -37,3 +37,8 @@ guildInviteExpired: "<red>This guild invite has expired."
guildTargetAlreadyInGuild: "<red>That player is already in a guild."
guildCannotInviteSelf: "<red>You cannot invite yourself."
guildStorageFailed: "<red>The guild storage operation failed."
guildWorldLoading: "<green>Loading your guild world..."
guildWorldLoadFailed: "<red>Your guild world could not be loaded."
guildWorldNoAccess: "<red>You cannot access this guild world."
guildWorldPermissionDenied: "<red>Your guild rank cannot do that here."
guildMemberKicked: "<green>You removed <dark_green>{0}<green> from the guild."