diff --git a/build.gradle.kts b/build.gradle.kts index c7b2b47..b50a28f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -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") { 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() diff --git a/src/main/java/dev/plex/Guilds.java b/src/main/java/dev/plex/Guilds.java index cf6e98a..e14840b 100644 --- a/src/main/java/dev/plex/Guilds.java +++ b/src/main/java/dev/plex/Guilds.java @@ -4,10 +4,14 @@ 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 void load() @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 void enable() guildHolder.replaceAll(guilds); }); registerListener(new ChatHandlerImpl()); + registerListener(guildMenuListener); + registerListener(guildWorldProtectionListener); + registerListener(rankPermissionMenuListener); } @Override public void disable() { + guildWorldService.disable(); guildHolder.clear(); } diff --git a/src/main/java/dev/plex/command/GuildCommand.java b/src/main/java/dev/plex/command/GuildCommand.java index 5f43166..ae95db2 100644 --- a/src/main/java/dev/plex/command/GuildCommand.java +++ b/src/main/java/dev/plex/command/GuildCommand.java @@ -40,10 +40,13 @@ public GuildCommand() 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 @@ -51,7 +54,15 @@ protected Component execute(@NotNull CommandSender commandSender, @Nullable Play { if (args.length == 0) { - return getSubs(); + 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")) { diff --git a/src/main/java/dev/plex/command/sub/CreateSubCommand.java b/src/main/java/dev/plex/command/sub/CreateSubCommand.java index 29521b7..039ea0d 100644 --- a/src/main/java/dev/plex/command/sub/CreateSubCommand.java +++ b/src/main/java/dev/plex/command/sub/CreateSubCommand.java @@ -37,7 +37,9 @@ protected Component execute(@NotNull CommandSender commandSender, @Nullable Play { 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) { diff --git a/src/main/java/dev/plex/command/sub/MenuSubCommand.java b/src/main/java/dev/plex/command/sub/MenuSubCommand.java new file mode 100644 index 0000000..bd6fc5e --- /dev/null +++ b/src/main/java/dev/plex/command/sub/MenuSubCommand.java @@ -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 ") + .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; + } +} diff --git a/src/main/java/dev/plex/command/sub/PermissionsSubCommand.java b/src/main/java/dev/plex/command/sub/PermissionsSubCommand.java new file mode 100644 index 0000000..ea23545 --- /dev/null +++ b/src/main/java/dev/plex/command/sub/PermissionsSubCommand.java @@ -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 ") + .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; + } +} diff --git a/src/main/java/dev/plex/command/sub/WorldSubCommand.java b/src/main/java/dev/plex/command/sub/WorldSubCommand.java new file mode 100644 index 0000000..797ac39 --- /dev/null +++ b/src/main/java/dev/plex/command/sub/WorldSubCommand.java @@ -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 ") + .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); + } +} diff --git a/src/main/java/dev/plex/gui/GuildMenuInventoryHolder.java b/src/main/java/dev/plex/gui/GuildMenuInventoryHolder.java new file mode 100644 index 0000000..cc2f082 --- /dev/null +++ b/src/main/java/dev/plex/gui/GuildMenuInventoryHolder.java @@ -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(); + } +} diff --git a/src/main/java/dev/plex/gui/GuildMenuView.java b/src/main/java/dev/plex/gui/GuildMenuView.java new file mode 100644 index 0000000..faa8622 --- /dev/null +++ b/src/main/java/dev/plex/gui/GuildMenuView.java @@ -0,0 +1,8 @@ +package dev.plex.gui; + +public enum GuildMenuView +{ + HOME, + MEMBERS, + MEMBER +} diff --git a/src/main/java/dev/plex/gui/RankPermissionInventoryHolder.java b/src/main/java/dev/plex/gui/RankPermissionInventoryHolder.java new file mode 100644 index 0000000..e62774c --- /dev/null +++ b/src/main/java/dev/plex/gui/RankPermissionInventoryHolder.java @@ -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(); + } +} diff --git a/src/main/java/dev/plex/guild/Guild.java b/src/main/java/dev/plex/guild/Guild.java index 3f7e398..e209503 100644 --- a/src/main/java/dev/plex/guild/Guild.java +++ b/src/main/java/dev/plex/guild/Guild.java @@ -3,8 +3,10 @@ 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.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 boolean isOwner(UUID uuid) { 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 getPlexPlayers() + { + return members.stream() + .map(Member::getPlexPlayer) + .flatMap(java.util.Optional::stream) + .toList(); + } + + public List 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; + } + } } diff --git a/src/main/java/dev/plex/guild/data/GuildPermission.java b/src/main/java/dev/plex/guild/data/GuildPermission.java new file mode 100644 index 0000000..eee311a --- /dev/null +++ b/src/main/java/dev/plex/guild/data/GuildPermission.java @@ -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; + } +} diff --git a/src/main/java/dev/plex/guild/data/Member.java b/src/main/java/dev/plex/guild/data/Member.java index be84f56..4149d00 100644 --- a/src/main/java/dev/plex/guild/data/Member.java +++ b/src/main/java/dev/plex/guild/data/Member.java @@ -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 Player getPlayer() { return Bukkit.getPlayer(this.uuid); } + + public Optional getPlexPlayer() + { + return Guilds.get().api().players().player(this.uuid); + } + + public String getName() + { + return getPlexPlayer() + .map(PlexPlayerView::name) + .orElse(this.uuid.toString()); + } } diff --git a/src/main/java/dev/plex/handler/GuildMenuListener.java b/src/main/java/dev/plex/handler/GuildMenuListener.java new file mode 100644 index 0000000..f0c8f48 --- /dev/null +++ b/src/main/java/dev/plex/handler/GuildMenuListener.java @@ -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 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 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 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 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"); + } +} diff --git a/src/main/java/dev/plex/handler/GuildWorldProtectionListener.java b/src/main/java/dev/plex/handler/GuildWorldProtectionListener.java new file mode 100644 index 0000000..1c1bd7a --- /dev/null +++ b/src/main/java/dev/plex/handler/GuildWorldProtectionListener.java @@ -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 = 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 = 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 = guildByWorld(world); + return guild.map(value -> value.hasPermission(player.getUniqueId(), permission)).orElse(true); + } + + private Optional guildByWorld(World world) + { + if (world == null) + { + return Optional.empty(); + } + return Guilds.get().getGuildHolder().getGuilds().stream() + .filter(guild -> guild.getWorldName().equals(world.getName())) + .findFirst(); + } +} diff --git a/src/main/java/dev/plex/handler/RankPermissionMenuListener.java b/src/main/java/dev/plex/handler/RankPermissionMenuListener.java new file mode 100644 index 0000000..cd12396 --- /dev/null +++ b/src/main/java/dev/plex/handler/RankPermissionMenuListener.java @@ -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); + } +} diff --git a/src/main/java/dev/plex/storage/GuildRepository.java b/src/main/java/dev/plex/storage/GuildRepository.java index 1805cf2..63ff311 100644 --- a/src/main/java/dev/plex/storage/GuildRepository.java +++ b/src/main/java/dev/plex/storage/GuildRepository.java @@ -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 transferOwner(UUID guildUuid, UUID newOwnerUuid, UUID oldOwnerUuid); + CompletableFuture updateMemberPermission(UUID guildUuid, GuildPermission permission, boolean enabled); + CompletableFuture updatePrefix(UUID guildUuid, String prefix); CompletableFuture updateHome(UUID guildUuid, CustomLocation home); diff --git a/src/main/java/dev/plex/storage/JdbiGuildRepository.java b/src/main/java/dev/plex/storage/JdbiGuildRepository.java index e61ce5d..7b236a0 100644 --- a/src/main/java/dev/plex/storage/JdbiGuildRepository.java +++ b/src/main/java/dev/plex/storage/JdbiGuildRepository.java @@ -3,6 +3,7 @@ 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 CompletableFuture createGuild(Player owner, String name) 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 CompletableFuture createGuild(Player owner, String name) .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 CompletableFuture transferOwner(UUID guildUuid, UUID newOwnerUuid, })); } + @Override + public CompletableFuture 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 updatePrefix(UUID guildUuid, String prefix) { @@ -303,6 +323,9 @@ private static GuildEntity guildMapRow(java.sql.ResultSet rs) throws java.sql.SQ 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 @@ private Guild toGuildBase(GuildEntity entity) 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 @@ private GuildEntity toEntity(Guild guild) 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; } diff --git a/src/main/java/dev/plex/storage/entity/GuildEntity.java b/src/main/java/dev/plex/storage/entity/GuildEntity.java index 5002b8e..9cc020a 100644 --- a/src/main/java/dev/plex/storage/entity/GuildEntity.java +++ b/src/main/java/dev/plex/storage/entity/GuildEntity.java @@ -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() { diff --git a/src/main/java/dev/plex/world/GuildWorldService.java b/src/main/java/dev/plex/world/GuildWorldService.java new file mode 100644 index 0000000..2670766 --- /dev/null +++ b/src/main/java/dev/plex/world/GuildWorldService.java @@ -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 loadedWorlds = new ConcurrentHashMap<>(); + private SlimeLoader loader; + + public void enable() + { + File worldDirectory = new File(Guilds.get().getDataFolder(), "slime-worlds"); + loader = new FileLoader(worldDirectory); + } + + public CompletableFuture 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 loadWorld(Guild guild, SlimeWorld slimeWorld) + { + CompletableFuture 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); + } + } +} diff --git a/src/main/resources/db/migration/mariadb/002_guild_world_permissions.sql b/src/main/resources/db/migration/mariadb/002_guild_world_permissions.sql new file mode 100644 index 0000000..479e88f --- /dev/null +++ b/src/main/resources/db/migration/mariadb/002_guild_world_permissions.sql @@ -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; diff --git a/src/main/resources/db/migration/postgres/002_guild_world_permissions.sql b/src/main/resources/db/migration/postgres/002_guild_world_permissions.sql new file mode 100644 index 0000000..ec41717 --- /dev/null +++ b/src/main/resources/db/migration/postgres/002_guild_world_permissions.sql @@ -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; diff --git a/src/main/resources/db/migration/sqlite/002_guild_world_permissions.sql b/src/main/resources/db/migration/sqlite/002_guild_world_permissions.sql new file mode 100644 index 0000000..c25f88a --- /dev/null +++ b/src/main/resources/db/migration/sqlite/002_guild_world_permissions.sql @@ -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; diff --git a/src/main/resources/guilds/messages.yml b/src/main/resources/guilds/messages.yml index 0f9d2c6..ac7b4c5 100644 --- a/src/main/resources/guilds/messages.yml +++ b/src/main/resources/guilds/messages.yml @@ -37,3 +37,8 @@ guildInviteExpired: "This guild invite has expired." guildTargetAlreadyInGuild: "That player is already in a guild." guildCannotInviteSelf: "You cannot invite yourself." guildStorageFailed: "The guild storage operation failed." +guildWorldLoading: "Loading your guild world..." +guildWorldLoadFailed: "Your guild world could not be loaded." +guildWorldNoAccess: "You cannot access this guild world." +guildWorldPermissionDenied: "Your guild rank cannot do that here." +guildMemberKicked: "You removed {0} from the guild."