Update Guilds

This commit is contained in:
2026-05-28 13:15:21 -04:00
parent 45f893688a
commit 3fa3cdfc39
32 changed files with 1328 additions and 404 deletions
-1
View File
@@ -21,7 +21,6 @@ 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("org.json:json:20251224")
implementation("org.jetbrains:annotations:26.1.0")
}
+22 -12
View File
@@ -2,19 +2,24 @@ package dev.plex;
import dev.plex.command.GuildCommand;
import dev.plex.config.ModuleConfig;
import dev.plex.data.SQLGuildManager;
import dev.plex.data.SQLManager;
import dev.plex.guild.GuildHolder;
import dev.plex.handler.ChatHandlerImpl;
import dev.plex.module.PlexModule;
import dev.plex.api.storage.ModuleStorage;
import dev.plex.storage.GuildRepository;
import dev.plex.storage.OrmGuildRepository;
import lombok.Getter;
import java.sql.SQLException;
import java.util.List;
@Getter
public class Guilds extends PlexModule
{
private static Guilds module;
private final GuildHolder guildHolder = new GuildHolder();
private SQLGuildManager sqlGuildManager;
private GuildRepository guildRepository;
private ModuleConfig config;
@@ -31,9 +36,17 @@ public class Guilds extends PlexModule
@Override
public void enable()
{
SQLManager.makeTables();
sqlGuildManager = new SQLGuildManager();
sqlGuildManager.getGuilds().whenComplete((guilds, throwable) ->
ModuleStorage storage = api().storage().forModule(this);
try
{
storage.migrations().run(List.of("001_initial_schema"));
}
catch (SQLException e)
{
throw new IllegalStateException("Failed to run Guilds migrations", e);
}
guildRepository = new OrmGuildRepository(storage);
guildRepository.loadGuilds().whenComplete((guilds, throwable) ->
{
if (throwable != null)
{
@@ -46,18 +59,15 @@ public class Guilds extends PlexModule
return;
}
api().logging().debug("Finished loading {0} guilds", guilds.size());
guilds.forEach(guildHolder::addGuild);
guildHolder.replaceAll(guilds);
});
registerListener(new ChatHandlerImpl());
}
@Override
public void disable()
{
// Unregistering listeners / commands is handled by Plex
if (sqlGuildManager != null)
{
this.getGuildHolder().getGuilds().forEach(sqlGuildManager::updateGuild);
}
guildHolder.clear();
}
public static Guilds get()
@@ -30,6 +30,8 @@ public class GuildCommand extends SimplePlexCommand
.permission("plex.guilds.guild")
.build());
this.registerSubCommand(new CreateSubCommand());
this.registerSubCommand(new DisbandSubCommand());
this.registerSubCommand(new LeaveSubCommand());
this.registerSubCommand(new InfoSubCommand());
this.registerSubCommand(new PrefixSubCommand());
this.registerSubCommand(new SetWarpSubCommand());
@@ -40,6 +42,8 @@ public class GuildCommand extends SimplePlexCommand
this.registerSubCommand(new HomeSubCommand());
this.registerSubCommand(new OwnerSubCommand());
this.registerSubCommand(new InviteSubCommand());
this.registerSubCommand(new AcceptSubCommand());
this.registerSubCommand(new DenySubCommand());
}
@Override
@@ -0,0 +1,165 @@
package dev.plex.command.sub;
import com.google.common.collect.ImmutableList;
import dev.plex.Guilds;
import dev.plex.command.SimplePlexCommand;
import dev.plex.command.source.RequiredCommandSource;
import dev.plex.guild.Guild;
import dev.plex.guild.data.GuildRole;
import dev.plex.guild.data.Member;
import dev.plex.storage.entity.GuildInviteEntity;
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;
import java.time.Instant;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
public class AcceptSubCommand extends SimplePlexCommand
{
public AcceptSubCommand()
{
super(command("accept")
.description("Accepts a guild invite")
.usage("/guild <command> <guild>")
.permission("plex.guilds.accept")
.source(RequiredCommandSource.IN_GAME)
.build());
}
@Override
protected Component execute(@NotNull CommandSender commandSender, @Nullable Player player, @NotNull String[] args)
{
if (args.length == 0)
{
return usage();
}
assert player != null;
Guilds.get().getGuildRepository().invitesFor(player.getUniqueId()).whenComplete((invites, throwable) ->
{
if (throwable != null)
{
send(player, messageComponent("guildStorageFailed"));
return;
}
GuildInviteEntity invite = findInvite(invites, String.join(" ", args));
if (invite == null)
{
send(player, messageComponent("guildNotValidInvite"));
return;
}
if (invite.getExpiresAt() < Instant.now().toEpochMilli())
{
Guilds.get().getGuildRepository().deleteInvite(UUID.fromString(invite.getGuildUuid()), player.getUniqueId());
send(player, messageComponent("guildInviteExpired"));
return;
}
Guild target = Guilds.get().getGuildHolder().guildById(UUID.fromString(invite.getGuildUuid())).orElse(null);
if (target == null)
{
send(player, messageComponent("guildNotValidInvite"));
return;
}
leaveCurrentIfNeeded(player, target, invite);
});
return null;
}
private GuildInviteEntity findInvite(List<GuildInviteEntity> invites, String guildName)
{
return invites.stream()
.filter(invite -> Guilds.get().getGuildHolder().guildById(UUID.fromString(invite.getGuildUuid()))
.map(guild -> guild.getName().equalsIgnoreCase(guildName))
.orElse(false))
.findFirst()
.orElse(null);
}
private void leaveCurrentIfNeeded(Player player, Guild target, GuildInviteEntity invite)
{
Guilds.get().getGuildHolder().guild(player.getUniqueId()).ifPresentOrElse(current ->
{
if (current.getGuildUuid().equals(target.getGuildUuid()))
{
send(player, messageComponent("guildInThis"));
return;
}
if (current.isOwner(player.getUniqueId()) && current.getMembers().size() > 1)
{
send(player, messageComponent("guildOwnerLeaveBlocked"));
return;
}
if (current.isOwner(player.getUniqueId()))
{
Guilds.get().getGuildRepository().deleteGuild(current.getGuildUuid()).whenComplete((unused, throwable) ->
{
if (throwable != null)
{
send(player, messageComponent("guildStorageFailed"));
return;
}
Guilds.get().getGuildHolder().removeGuild(current.getGuildUuid());
joinTarget(player, target, invite);
});
return;
}
Guilds.get().getGuildRepository().removeMember(current.getGuildUuid(), player.getUniqueId()).whenComplete((unused, throwable) ->
{
if (throwable != null)
{
send(player, messageComponent("guildStorageFailed"));
return;
}
current.getMembers().removeIf(member -> member.getUuid().equals(player.getUniqueId()));
Guilds.get().getGuildHolder().unindexMember(player.getUniqueId());
current.getMembers().stream().map(Member::getPlayer).filter(Objects::nonNull).forEach(memberPlayer ->
send(memberPlayer, messageComponent("guildMemberLeft", player.getName())));
joinTarget(player, target, invite);
});
}, () -> joinTarget(player, target, invite));
}
private void joinTarget(Player player, Guild guild, GuildInviteEntity invite)
{
UUID guildUuid = guild.getGuildUuid();
Guilds.get().getGuildRepository().addMember(guildUuid, player.getUniqueId(), GuildRole.MEMBER)
.thenCompose(unused -> Guilds.get().getGuildRepository().deleteInvite(guildUuid, player.getUniqueId()))
.whenComplete((unused, throwable) ->
{
if (throwable != null)
{
send(player, messageComponent("guildStorageFailed"));
return;
}
guild.addMember(player.getUniqueId());
Guilds.get().getGuildHolder().indexMember(guildUuid, player.getUniqueId());
guild.getMembers().stream().map(Member::getPlayer).filter(Objects::nonNull).forEach(memberPlayer ->
send(memberPlayer, messageComponent("guildMemberJoined", player.getName())));
});
}
@Override
protected @NotNull List<String> suggestions(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args) throws IllegalArgumentException
{
if (!(sender instanceof Player player) || args.length != 1)
{
return ImmutableList.of();
}
try
{
return Guilds.get().getGuildRepository().invitesFor(player.getUniqueId()).join().stream()
.map(invite -> Guilds.get().getGuildHolder().guildById(UUID.fromString(invite.getGuildUuid())).orElse(null))
.filter(Objects::nonNull)
.map(Guild::getName)
.toList();
}
catch (RuntimeException ignored)
{
return ImmutableList.of();
}
}
}
@@ -38,10 +38,15 @@ public class CreateSubCommand extends SimplePlexCommand
{
return messageComponent("alreadyInGuild");
}
Guilds.get().getSqlGuildManager().insertGuild(Guild.create(player, StringUtils.join(args, " "))).whenComplete((guild, throwable) ->
Guilds.get().getGuildRepository().createGuild(player, StringUtils.join(args, " ")).whenComplete((guild, throwable) ->
{
if (throwable != null)
{
send(player, messageComponent("guildStorageFailed"));
return;
}
Guilds.get().getGuildHolder().addGuild(guild);
send(player, mmString("Created guild named " + guild.getName()));
send(player, messageComponent("guildCreated", guild.getName()));
});
return null;
}
@@ -0,0 +1,65 @@
package dev.plex.command.sub;
import dev.plex.Guilds;
import dev.plex.command.SimplePlexCommand;
import dev.plex.command.source.RequiredCommandSource;
import dev.plex.storage.entity.GuildInviteEntity;
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;
import java.util.UUID;
public class DenySubCommand extends SimplePlexCommand
{
public DenySubCommand()
{
super(command("deny")
.description("Denies a guild invite")
.usage("/guild <command> <guild>")
.permission("plex.guilds.deny")
.source(RequiredCommandSource.IN_GAME)
.build());
}
@Override
protected Component execute(@NotNull CommandSender commandSender, @Nullable Player player, @NotNull String[] args)
{
if (args.length == 0)
{
return usage();
}
assert player != null;
Guilds.get().getGuildRepository().invitesFor(player.getUniqueId()).whenComplete((invites, throwable) ->
{
if (throwable != null)
{
send(player, messageComponent("guildStorageFailed"));
return;
}
GuildInviteEntity invite = invites.stream()
.filter(candidate -> Guilds.get().getGuildHolder().guildById(UUID.fromString(candidate.getGuildUuid()))
.map(guild -> guild.getName().equalsIgnoreCase(String.join(" ", args)))
.orElse(false))
.findFirst()
.orElse(null);
if (invite == null)
{
send(player, messageComponent("guildNotValidInvite"));
return;
}
Guilds.get().getGuildRepository().deleteInvite(UUID.fromString(invite.getGuildUuid()), player.getUniqueId()).whenComplete((unused, deleteThrowable) ->
{
if (deleteThrowable != null)
{
send(player, messageComponent("guildStorageFailed"));
return;
}
send(player, messageComponent("guildInviteDenied"));
});
});
return null;
}
}
@@ -0,0 +1,48 @@
package dev.plex.command.sub;
import dev.plex.Guilds;
import dev.plex.command.SimplePlexCommand;
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 DisbandSubCommand extends SimplePlexCommand
{
public DisbandSubCommand()
{
super(command("disband")
.description("Disbands your guild")
.usage("/guild <command>")
.permission("plex.guilds.disband")
.source(RequiredCommandSource.IN_GAME)
.build());
}
@Override
protected Component execute(@NotNull CommandSender commandSender, @Nullable Player player, @NotNull String[] args)
{
assert player != null;
Guilds.get().getGuildHolder().guild(player.getUniqueId()).ifPresentOrElse(guild ->
{
if (!guild.isOwner(player.getUniqueId()))
{
send(player, messageComponent("guildNotOwner"));
return;
}
Guilds.get().getGuildRepository().deleteGuild(guild.getGuildUuid()).whenComplete((unused, throwable) ->
{
if (throwable != null)
{
send(player, messageComponent("guildStorageFailed"));
return;
}
Guilds.get().getGuildHolder().removeGuild(guild.getGuildUuid());
send(player, messageComponent("guildDisbanded"));
});
}, () -> send(player, messageComponent("guildNotFound")));
return null;
}
}
@@ -4,6 +4,7 @@ import dev.plex.Guilds;
import dev.plex.api.player.PlexPlayerView;
import dev.plex.command.SimplePlexCommand;
import dev.plex.command.source.RequiredCommandSource;
import dev.plex.guild.Guild;
import java.time.format.DateTimeFormatter;
import java.util.Collections;
import java.util.List;
@@ -31,29 +32,51 @@ public class InfoSubCommand extends SimplePlexCommand
private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM/dd/yyyy hh:mm:ss a");
@Override
protected Component execute(@NotNull CommandSender commandSender, @Nullable Player player, @NotNull String[] strings)
protected Component execute(@NotNull CommandSender commandSender, @Nullable Player player, @NotNull String[] args)
{
assert player != null;
CompletableFuture.runAsync(() ->
{
Guilds.get().getGuildHolder().getGuild(player.getUniqueId()).ifPresentOrElse(guild ->
Guild guild = resolveGuild(player, args);
if (guild == null)
{
send(player, messageComponent("guildNotFound"));
return;
}
{
send(player, mmString("<gradient:yellow:gold>====<aqua>" + guild.getName() + "<gradient:yellow:gold>===="));
send(player, mmString(""));
send(player, mmString("<gold>Owner: <yellow>" + playerName(guild.getOwner().getUuid())));
List<String> members = guild.getMembers().stream().filter(member -> !member.getUuid().equals(guild.getOwner().getUuid())).map(member -> playerName(member.getUuid())).toList();
send(player, mmString("<gold>Owner: <yellow>" + playerName(guild.getOwnerUuid())));
List<String> members = guild.getMembers().stream().filter(member -> !member.getUuid().equals(guild.getOwnerUuid())).map(member -> playerName(member.getUuid())).toList();
send(player, mmString("<gold>Members (" + members.size() + "): " + StringUtils.join(members, ", ")));
send(player, mmString("<gold>Moderators (" + guild.getModerators().size() + "): " + StringUtils.join(guild.getModerators().stream().map(this::playerName).toList(), ", ")));
send(player, mmString("<gold>Prefix: " + (guild.getPrefix() == null ? "N/A" : guild.getPrefix())));
send(player, mmString("<gold>Created At: " + formatter.format(guild.getCreatedAt())));
}, () -> send(player, messageComponent("guildNotFound")));
}
}, Guilds.get().api().scheduler().asyncExecutor());
return null;
}
private Guild resolveGuild(Player sender, String[] args)
{
if (args.length == 0)
{
return Guilds.get().getGuildHolder().guild(sender.getUniqueId()).orElse(null);
}
PlexPlayerView player = api().players().byName(args[0]).orElse(null);
if (player != null)
{
Guild guild = Guilds.get().getGuildHolder().guild(player.uuid()).orElse(null);
if (guild != null)
{
return guild;
}
}
return Guilds.get().getGuildHolder().guildByName(StringUtils.join(args, " ")).orElse(null);
}
private String playerName(java.util.UUID uuid)
{
return api().players().byUuid(uuid).map(PlexPlayerView::name).orElse("Unable to load cache...");
return api().players().player(uuid).map(PlexPlayerView::name).orElse(uuid.toString());
}
@Override
@@ -4,23 +4,15 @@ import com.google.common.collect.ImmutableList;
import dev.plex.Guilds;
import dev.plex.command.SimplePlexCommand;
import dev.plex.command.source.RequiredCommandSource;
import dev.plex.guild.Guild;
import dev.plex.guild.GuildHolder;
import dev.plex.guild.data.Member;
import net.kyori.adventure.text.Component;
import org.apache.commons.lang3.StringUtils;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
// TODO: 5/9/2022 5 minute timeout for invites
// TODO: 5/9/2022 deny command maybe?
// TODO: 5/9/2022 deny members from inviting themselves or existing members in the current guild
public class InviteSubCommand extends SimplePlexCommand
{
@@ -43,75 +35,34 @@ public class InviteSubCommand extends SimplePlexCommand
return usage();
}
assert player != null;
if (args[0].equalsIgnoreCase("accept"))
{
if (!GuildHolder.PENDING_INVITES.containsKey(player.getUniqueId()))
{
return messageComponent("guildNoInvite");
}
String guildName = StringUtils.join(args, " ", 1, args.length);
GuildHolder.PENDING_INVITES.get(player.getUniqueId()).stream().filter(guild -> guild.getName().equalsIgnoreCase(guildName)).findFirst().ifPresentOrElse(guild ->
{
AtomicBoolean continueCheck = new AtomicBoolean(true);
Guilds.get().getGuildHolder().getGuild(player.getUniqueId()).ifPresent(guild1 ->
{
if (guild1.getGuildUuid().equals(guild.getGuildUuid()))
{
send(player, messageComponent("guildInThis"));
continueCheck.set(false);
return;
}
if (guild1.getOwner().getUuid().equals(player.getUniqueId()))
{
if (guild1.getMembers().size() - 1 > 0)
{
send(player, messageComponent("guildDisbandNeeded"));
continueCheck.set(false);
return;
}
else
{
Guilds.get().getSqlGuildManager().deleteGuild(guild1.getGuildUuid()).whenComplete((unused, throwable) ->
{
send(player, messageComponent("guildAutoDisbanded"));
});
}
}
guild1.getMembers().stream().map(Member::getPlayer).filter(Objects::nonNull).forEach(player1 ->
{
send(player1, messageComponent("guildMemberLeft", player.getName()));
});
guild1.getMembers().removeIf(member -> member.getUuid().equals(player.getUniqueId()));
});
if (!continueCheck.get())
{
return;
}
GuildHolder.PENDING_INVITES.remove(player.getUniqueId());
guild.addMember(player.getUniqueId());
guild.getMembers().stream().map(Member::getPlayer).filter(Objects::nonNull).forEach(player1 ->
{
send(player1, messageComponent("guildMemberJoined", player.getName()));
});
}, () -> send(player, messageComponent("guildNotValidInvite")));
return null;
}
Guilds.get().getGuildHolder().getGuild(player.getUniqueId()).ifPresentOrElse(guild ->
{
if (!guild.getOwner().getUuid().equals(player.getUniqueId()))
if (!guild.isOwner(player.getUniqueId()))
{
send(player, messageComponent("guildNotOwner"));
return;
}
Player target = getNonNullPlayer(args[0]);
boolean invite = GuildHolder.sendInvite(target.getUniqueId(), guild);
if (!invite)
if (target.getUniqueId().equals(player.getUniqueId()))
{
send(player, messageComponent("guildInviteExists"));
send(player, messageComponent("guildCannotInviteSelf"));
return;
}
if (guild.getMember(target.getUniqueId()) != null || Guilds.get().getGuildHolder().guild(target.getUniqueId()).isPresent())
{
send(player, messageComponent("guildTargetAlreadyInGuild"));
return;
}
Guilds.get().getGuildRepository().createInvite(guild.getGuildUuid(), player.getUniqueId(), target.getUniqueId(), Instant.now().plus(5, ChronoUnit.MINUTES)).whenComplete((unused, throwable) ->
{
if (throwable != null)
{
send(player, messageComponent("guildStorageFailed"));
return;
}
send(player, messageComponent("guildInviteSent", target.getName()));
send(target, messageComponent("guildInviteReceived", player.getName(), guild.getName()));
});
}, () -> send(player, messageComponent("guildNotFound")));
return null;
}
@@ -119,23 +70,6 @@ public class InviteSubCommand extends SimplePlexCommand
@Override
protected @NotNull List<String> suggestions(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args) throws IllegalArgumentException
{
if (!(sender instanceof Player player))
{
return ImmutableList.of();
}
if (args.length == 0)
{
return ImmutableList.of();
}
if (args[0].equalsIgnoreCase("accept") && args.length == 2)
{
if (!GuildHolder.PENDING_INVITES.containsKey(player.getUniqueId()))
{
return ImmutableList.of();
}
api().logging().debug("Completing pending guild invites");
return GuildHolder.PENDING_INVITES.get(player.getUniqueId()).stream().map(Guild::getName).collect(Collectors.toList());
}
return ImmutableList.of();
return args.length == 1 ? onlinePlayerNames() : ImmutableList.of();
}
}
@@ -0,0 +1,68 @@
package dev.plex.command.sub;
import dev.plex.Guilds;
import dev.plex.command.SimplePlexCommand;
import dev.plex.command.source.RequiredCommandSource;
import dev.plex.guild.data.Member;
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;
import java.util.Objects;
public class LeaveSubCommand extends SimplePlexCommand
{
public LeaveSubCommand()
{
super(command("leave")
.description("Leaves your guild")
.usage("/guild <command>")
.permission("plex.guilds.leave")
.source(RequiredCommandSource.IN_GAME)
.build());
}
@Override
protected Component execute(@NotNull CommandSender commandSender, @Nullable Player player, @NotNull String[] args)
{
assert player != null;
Guilds.get().getGuildHolder().guild(player.getUniqueId()).ifPresentOrElse(guild ->
{
if (guild.isOwner(player.getUniqueId()))
{
if (guild.getMembers().size() > 1)
{
send(player, messageComponent("guildOwnerLeaveBlocked"));
return;
}
Guilds.get().getGuildRepository().deleteGuild(guild.getGuildUuid()).whenComplete((unused, throwable) ->
{
if (throwable != null)
{
send(player, messageComponent("guildStorageFailed"));
return;
}
Guilds.get().getGuildHolder().removeGuild(guild.getGuildUuid());
send(player, messageComponent("guildAutoDisbanded"));
});
return;
}
Guilds.get().getGuildRepository().removeMember(guild.getGuildUuid(), player.getUniqueId()).whenComplete((unused, throwable) ->
{
if (throwable != null)
{
send(player, messageComponent("guildStorageFailed"));
return;
}
guild.getMembers().removeIf(member -> member.getUuid().equals(player.getUniqueId()));
Guilds.get().getGuildHolder().unindexMember(player.getUniqueId());
guild.getMembers().stream().map(Member::getPlayer).filter(Objects::nonNull).forEach(memberPlayer ->
send(memberPlayer, messageComponent("guildMemberLeft", player.getName())));
send(player, messageComponent("guildLeft"));
});
}, () -> send(player, messageComponent("guildNotFound")));
return null;
}
}
@@ -4,6 +4,7 @@ import dev.plex.Guilds;
import dev.plex.api.player.PlexPlayerView;
import dev.plex.command.SimplePlexCommand;
import dev.plex.command.source.RequiredCommandSource;
import dev.plex.guild.data.GuildRole;
import dev.plex.guild.data.Member;
import java.util.Collections;
import java.util.List;
@@ -20,7 +21,7 @@ public class OwnerSubCommand extends SimplePlexCommand
super(command("owner")
.description("Sets the guild owner")
.usage("/guild <command> <player name>")
.aliases("setowner")
.aliases("setowner,promote")
.permission("plex.guilds.owner")
.source(RequiredCommandSource.IN_GAME)
.build());
@@ -36,7 +37,7 @@ public class OwnerSubCommand extends SimplePlexCommand
assert player != null;
Guilds.get().getGuildHolder().getGuild(player.getUniqueId()).ifPresentOrElse(guild ->
{
if (!guild.getOwner().getUuid().equals(player.getUniqueId()))
if (!guild.isOwner(player.getUniqueId()))
{
send(player, messageComponent("guildNotOwner"));
return;
@@ -54,10 +55,21 @@ public class OwnerSubCommand extends SimplePlexCommand
send(player, messageComponent("guildMemberNotFound"));
return;
}
guild.setOwner(member);
guild.getMembers().remove(member);
guild.getMembers().add(memberSender);
Guilds.get().getGuildRepository().transferOwner(guild.getGuildUuid(), member.getUuid(), player.getUniqueId()).whenComplete((unused, throwable) ->
{
if (throwable != null)
{
send(player, messageComponent("guildStorageFailed"));
return;
}
guild.setOwnerUuid(member.getUuid());
member.setRole(GuildRole.OWNER);
if (memberSender != null)
{
memberSender.setRole(GuildRole.MEMBER);
}
send(player, messageComponent("guildOwnerSet", plexPlayer.name()));
});
}, () -> send(player, messageComponent("guildNotFound")));
return null;
}
@@ -36,19 +36,36 @@ public class PrefixSubCommand extends SimplePlexCommand
assert player != null;
Guilds.get().getGuildHolder().getGuild(player.getUniqueId()).ifPresentOrElse(guild ->
{
if (!guild.getOwner().getUuid().equals(player.getUniqueId()))
if (!guild.isOwner(player.getUniqueId()))
{
send(player, messageComponent("guildNotOwner"));
return;
}
if (args[0].equalsIgnoreCase("clear") || args[0].equalsIgnoreCase("off"))
{
guild.setPrefix(null);
send(player, messageComponent("guildPrefixCleared"));
Guilds.get().getGuildRepository().updatePrefix(guild.getGuildUuid(), null).whenComplete((unused, throwable) ->
{
if (throwable != null)
{
send(player, messageComponent("guildStorageFailed"));
return;
}
guild.setPrefix(StringUtils.join(args, " "));
guild.setPrefix(null);
send(player, messageComponent("guildPrefixCleared"));
});
return;
}
String prefix = StringUtils.join(args, " ");
Guilds.get().getGuildRepository().updatePrefix(guild.getGuildUuid(), prefix).whenComplete((unused, throwable) ->
{
if (throwable != null)
{
send(player, messageComponent("guildStorageFailed"));
return;
}
guild.setPrefix(prefix);
send(player, messageComponent("guildPrefixSet", GuildUtil.miniMessageWithoutEvents(guild.getPrefix())));
});
}, () -> send(player, messageComponent("guildNotFound")));
return null;
}
@@ -31,7 +31,7 @@ public class SetHomeSubCommand extends SimplePlexCommand
assert player != null;
Guilds.get().getGuildHolder().getGuild(player.getUniqueId()).ifPresentOrElse(guild ->
{
if (!guild.getOwner().getUuid().equals(player.getUniqueId()))
if (!guild.isOwner(player.getUniqueId()))
{
send(player, messageComponent("guildNotOwner"));
return;
@@ -43,12 +43,29 @@ public class SetHomeSubCommand extends SimplePlexCommand
send(player, messageComponent("guildHomeNotFound"));
return;
}
guild.setHome(null);
send(player, messageComponent("guildHomeRemoved"));
Guilds.get().getGuildRepository().updateHome(guild.getGuildUuid(), null).whenComplete((unused, throwable) ->
{
if (throwable != null)
{
send(player, messageComponent("guildStorageFailed"));
return;
}
guild.setHome(CustomLocation.fromLocation(player.getLocation()));
guild.setHome(null);
send(player, messageComponent("guildHomeRemoved"));
});
return;
}
CustomLocation home = CustomLocation.fromLocation(player.getLocation());
Guilds.get().getGuildRepository().updateHome(guild.getGuildUuid(), home).whenComplete((unused, throwable) ->
{
if (throwable != null)
{
send(player, messageComponent("guildStorageFailed"));
return;
}
guild.setHome(home);
send(player, messageComponent("guildHomeSet"));
});
}, () -> send(player, messageComponent("guildNotFound")));
return null;
}
@@ -37,7 +37,7 @@ public class SetWarpSubCommand extends SimplePlexCommand
assert player != null;
Guilds.get().getGuildHolder().getGuild(player.getUniqueId()).ifPresentOrElse(guild ->
{
if (!guild.getOwner().getUuid().equals(player.getUniqueId()))
if (!guild.isOwner(player.getUniqueId()))
{
send(player, messageComponent("guildNotOwner"));
return;
@@ -48,18 +48,23 @@ public class SetWarpSubCommand extends SimplePlexCommand
send(player, mmString("<red>The max length of a warp name is 16 characters!"));
return;
}
if (guild.getWarps().containsKey(warpName.toLowerCase()))
{
send(player, messageComponent("guildWarpExists", warpName));
return;
}
if (!StringUtils.isAlphanumericSpace(warpName.toLowerCase(Locale.ROOT)))
{
send(player, messageComponent("guildWarpAlphanumeric"));
return;
}
guild.getWarps().put(warpName.toLowerCase(), CustomLocation.fromLocation(player.getLocation()));
CustomLocation location = CustomLocation.fromLocation(player.getLocation());
String localName = warpName.toLowerCase(Locale.ROOT);
Guilds.get().getGuildRepository().upsertWarp(guild.getGuildUuid(), localName, location).whenComplete((unused, throwable) ->
{
if (throwable != null)
{
send(player, messageComponent("guildStorageFailed"));
return;
}
guild.getWarps().put(localName, location);
send(player, messageComponent("guildWarpCreated", warpName));
});
}, () -> send(player, messageComponent("guildNotFound")));
return null;
}
@@ -1,165 +0,0 @@
package dev.plex.data;
import com.google.common.collect.Lists;
import com.google.common.reflect.TypeToken;
import dev.plex.Guilds;
import com.google.gson.Gson;
import dev.plex.guild.Guild;
import dev.plex.guild.data.Member;
import dev.plex.util.CustomLocation;
import dev.plex.util.GuildUtil;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
public class SQLGuildManager
{
private static final Gson GSON = new Gson();
private static final String SELECT_GUILD = "SELECT * FROM `guilds`";
private static final String INSERT_GUILD = "INSERT INTO `guilds` (`guildUuid`, `name`, `owner`, `createdAt`, `members`, `moderators`, `prefix`, `motd`, `ranks`, `defaultRank`, `warps`, `home`, `tagEnabled`, `isPublic`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
private static final String DELETE_GUILD = "DELETE FROM `guilds` WHERE guildUuid=?";
private static final String UPDATE_GUILD = "UPDATE `guilds` SET name=?, owner=?, members=?, moderators=?, prefix=?, motd=?, ranks=?, defaultRank=?, home=?, warps=?, tagEnabled=?, isPublic=? WHERE guildUuid=?";
public CompletableFuture<Guild> insertGuild(Guild guild)
{
return supplyStorageAsync(connection ->
{
try (PreparedStatement statement = connection.prepareStatement(INSERT_GUILD))
{
statement.setString(1, guild.getGuildUuid().toString());
statement.setString(2, guild.getName());
statement.setString(3, GSON.toJson(guild.getOwner()));
statement.setLong(4, guild.getCreatedAt().toInstant().toEpochMilli());
statement.setString(5, GSON.toJson(guild.getMembers()));
statement.setString(6, GSON.toJson(guild.getModerators().stream().map(UUID::toString).collect(Collectors.toList())));
statement.setString(7, guild.getPrefix());
statement.setString(8, guild.getMotd());
statement.setString(9, GSON.toJson(guild.getRanks()));
statement.setString(10, GSON.toJson(guild.getDefaultRank()));
statement.setString(11, GSON.toJson(guild.getWarps()));
statement.setString(12, GSON.toJson(guild.getHome()));
statement.setBoolean(13, guild.isTagEnabled());
statement.setBoolean(14, guild.isPublic());
statement.execute();
return guild;
}
});
}
public CompletableFuture<Void> deleteGuild(UUID uuid)
{
return supplyStorageAsync(connection ->
{
try (PreparedStatement statement = connection.prepareStatement(DELETE_GUILD))
{
statement.setString(1, uuid.toString());
statement.execute();
return null;
}
});
}
public CompletableFuture<Guild> updateGuild(Guild guild)
{
return supplyStorageAsync(connection ->
{
try (PreparedStatement statement = connection.prepareStatement(UPDATE_GUILD))
{
statement.setString(1, guild.getName());
statement.setString(2, GSON.toJson(guild.getOwner()));
statement.setString(3, GSON.toJson(guild.getMembers()));
statement.setString(4, GSON.toJson(guild.getModerators().stream().map(UUID::toString).collect(Collectors.toList())));
statement.setString(5, guild.getPrefix());
statement.setString(6, guild.getMotd());
statement.setString(7, GSON.toJson(guild.getRanks()));
statement.setString(8, GSON.toJson(guild.getDefaultRank()));
statement.setString(9, GSON.toJson(guild.getHome()));
statement.setString(10, GSON.toJson(guild.getWarps()));
statement.setBoolean(11, guild.isTagEnabled());
statement.setBoolean(12, guild.isPublic());
statement.setString(13, guild.getGuildUuid().toString());
statement.executeUpdate();
return guild;
}
});
}
private List<Guild> getGuildsSync(Connection connection) throws SQLException
{
List<Guild> guilds = Lists.newArrayList();
try (PreparedStatement statement = connection.prepareStatement(SELECT_GUILD);
ResultSet set = statement.executeQuery())
{
while (set.next())
{
String timezone = Guilds.get().api().configuration().mainConfig().getString("server.timezone", "Etc/UTC");
Guild guild = new Guild(UUID.fromString(set.getString("guildUuid")),
ZonedDateTime.ofInstant(Instant.ofEpochMilli(set.getLong("createdAt")), ZoneId.of(timezone).getRules().getOffset(Instant.now())));
guild.setName(set.getString("name"));
guild.setOwner(GSON.fromJson(set.getString("owner"), Member.class));
List<Member> members = new Gson().fromJson(set.getString("members"), new TypeToken<List<Member>>()
{
}.getType());
if (members != null)
{
members.forEach(guild::addMember);
}
List<String> moderators = new Gson().fromJson(set.getString("moderators"), new TypeToken<List<String>>()
{
}.getType());
if (moderators != null)
{
moderators.stream().map(UUID::fromString).forEach(guild.getModerators()::add);
}
guild.setPrefix(set.getString("prefix"));
guild.setMotd(set.getString("motd"));
guild.setHome(GSON.fromJson(set.getString("home"), CustomLocation.class));
guild.setTagEnabled(set.getBoolean("tagEnabled"));
Map<String, CustomLocation> warps = GSON.fromJson(set.getString("warps"), new TypeToken<Map<String, CustomLocation>>()
{
}.getType());
if (warps != null)
{
Guilds.get().api().logging().debug("Loaded {0} warps for {1} guild", warps.size(), guild.getName());
guild.getWarps().putAll(warps);
}
guild.setPublic(set.getBoolean("isPublic"));
guilds.add(guild);
}
}
return guilds;
}
public CompletableFuture<List<Guild>> getGuilds()
{
return supplyStorageAsync(this::getGuildsSync);
}
private <T> CompletableFuture<T> supplyStorageAsync(dev.plex.api.storage.StorageApi.SqlFunction<T> function)
{
return CompletableFuture.supplyAsync(() ->
{
try
{
return Guilds.get().api().storage().withConnection(function);
}
catch (SQLException e)
{
GuildUtil.throwExceptionSync(e);
return null;
}
}, Guilds.get().api().scheduler().asyncExecutor());
}
}
@@ -1,46 +0,0 @@
package dev.plex.data;
import dev.plex.Guilds;
import dev.plex.util.GuildUtil;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class SQLManager
{
public static void makeTables()
{
try
{
Guilds.get().api().storage().withConnection(connection ->
{
try (PreparedStatement statement = connection.prepareStatement(
"CREATE TABLE IF NOT EXISTS `guilds` (" +
"`guildUuid` VARCHAR(46) NOT NULL, " +
"`name` VARCHAR(2000) NOT NULL, " +
"`owner` LONGTEXT NOT NULL, " +
"`createdAt` BIGINT NOT NULL, " +
"`prefix` VARCHAR(2000), " +
"`motd` VARCHAR(3000), " +
"`home` VARCHAR(1000)," +
"`members` LONGTEXT, " +
"`moderators` LONGTEXT, " +
"`ranks` LONGTEXT, " +
"`defaultRank` LONGTEXT, " +
"`warps` LONGTEXT, " +
"`tagEnabled` BOOLEAN, " +
"`isPublic` BOOLEAN, " +
"PRIMARY KEY (`guildUuid`)" +
");"))
{
statement.execute();
}
return null;
});
}
catch (SQLException e)
{
GuildUtil.throwExceptionSync(e);
}
}
}
+12 -20
View File
@@ -3,8 +3,8 @@ 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.GuildRole;
import dev.plex.guild.data.Member;
import dev.plex.guild.data.Rank;
import dev.plex.util.CustomLocation;
import dev.plex.util.GuildUtil;
import lombok.Data;
@@ -23,51 +23,43 @@ public class Guild
private final UUID guildUuid;
private final ZonedDateTime createdAt;
private final List<Member> members = Lists.newArrayList();
private final List<UUID> moderators = Lists.newArrayList();
private final List<Rank> ranks = Lists.newArrayList();
private final Map<String, CustomLocation> warps = Maps.newHashMap();
private String name;
private Member owner;
private UUID ownerUuid;
private String prefix;
private String motd;
private CustomLocation home;
private boolean tagEnabled;
private Rank defaultRank = new Rank("default", null);
private boolean isPublic = false;
private boolean tagEnabled = true;
private boolean isPublic;
public static Guild create(Player player, String guildName)
{
String timezone = Guilds.get().api().configuration().mainConfig().getString("server.timezone", "Etc/UTC");
Guild guild = new Guild(UUID.randomUUID(), ZonedDateTime.now(ZoneId.of(timezone)));
guild.setName(PlainTextComponentSerializer.plainText().serialize(GuildUtil.miniMessageWithoutEvents(guildName)));
guild.setOwner(new Member(player.getUniqueId()));
guild.setOwnerUuid(player.getUniqueId());
guild.addMember(new Member(player.getUniqueId(), GuildRole.OWNER));
return guild;
}
public Member getMember(UUID uuid)
{
if (owner.getUuid().equals(uuid))
{
return owner;
}
return members.stream().filter(m -> m.getUuid().equals(uuid)).findFirst().orElse(null);
return members.stream().filter(member -> member.getUuid().equals(uuid)).findFirst().orElse(null);
}
public void addMember(UUID uuid)
{
addMember(new Member(uuid));
addMember(new Member(uuid, GuildRole.MEMBER));
}
public void addMember(Member member)
{
this.members.add(member);
members.removeIf(existing -> existing.getUuid().equals(member.getUuid()));
members.add(member);
}
public List<Member> getMembers()
public boolean isOwner(UUID uuid)
{
List<Member> allMembers = Lists.newArrayList(members);
allMembers.add(owner);
return allMembers;
return ownerUuid != null && ownerUuid.equals(uuid);
}
}
+53 -20
View File
@@ -1,51 +1,84 @@
package dev.plex.guild;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import dev.plex.guild.data.Member;
import java.util.*;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class GuildHolder
{
public static final Map<UUID, List<Guild>> PENDING_INVITES = Maps.newHashMap();
private static final List<Guild> GUILDS = Lists.newArrayList();
private final Map<UUID, Guild> guildsById = new ConcurrentHashMap<>();
private final Map<UUID, UUID> guildByPlayer = new ConcurrentHashMap<>();
public static boolean sendInvite(UUID uuid, Guild guild)
public void replaceAll(Collection<Guild> guilds)
{
if (PENDING_INVITES.containsKey(uuid) && PENDING_INVITES.get(uuid).stream().anyMatch(guild1 -> guild1.getGuildUuid().equals(guild.getGuildUuid())))
{
return false;
guildsById.clear();
guildByPlayer.clear();
guilds.forEach(this::addGuild);
}
if (PENDING_INVITES.containsKey(uuid))
public Optional<Guild> guild(UUID playerUuid)
{
PENDING_INVITES.get(uuid).add(guild);
UUID guildUuid = guildByPlayer.get(playerUuid);
return guildUuid == null ? Optional.empty() : Optional.ofNullable(guildsById.get(guildUuid));
}
else
public Optional<Guild> guildById(UUID guildUuid)
{
PENDING_INVITES.put(uuid, Lists.newArrayList(guild));
return Optional.ofNullable(guildsById.get(guildUuid));
}
return true;
public Optional<Guild> guildByName(String name)
{
return guildsById.values().stream().filter(guild -> guild.getName().equalsIgnoreCase(name)).findFirst();
}
public Collection<Guild> guilds()
{
return guildsById.values();
}
public void addGuild(Guild guild)
{
GUILDS.add(guild);
guildsById.put(guild.getGuildUuid(), guild);
guild.getMembers().stream().map(Member::getUuid).forEach(playerUuid -> indexMember(guild.getGuildUuid(), playerUuid));
}
public void deleteGuild(UUID owner)
public void removeGuild(UUID guildUuid)
{
GUILDS.removeIf(guild -> guild.getOwner().getUuid().equals(owner));
Guild removed = guildsById.remove(guildUuid);
if (removed != null)
{
removed.getMembers().stream().map(Member::getUuid).forEach(guildByPlayer::remove);
}
}
public void indexMember(UUID guildUuid, UUID playerUuid)
{
guildByPlayer.put(playerUuid, guildUuid);
}
public void unindexMember(UUID playerUuid)
{
guildByPlayer.remove(playerUuid);
}
public void clear()
{
guildsById.clear();
guildByPlayer.clear();
}
public Optional<Guild> getGuild(UUID uuid)
{
return GUILDS.stream().filter(guild -> (guild.getOwner() != null && guild.getOwner().getUuid().equals(uuid)) || guild.getMembers().stream().map(Member::getUuid).toList().contains(uuid)).findFirst();
return guild(uuid);
}
public Collection<Guild> getGuilds()
{
return GUILDS.stream().toList();
return guilds();
}
}
@@ -0,0 +1,7 @@
package dev.plex.guild.data;
public enum GuildRole
{
OWNER,
MEMBER
}
+13 -2
View File
@@ -10,8 +10,19 @@ import java.util.UUID;
public class Member
{
private final UUID uuid;
private Rank rank;
private boolean chat, prefix;
private GuildRole role;
private boolean chat;
public Member(UUID uuid)
{
this(uuid, GuildRole.MEMBER);
}
public Member(UUID uuid, GuildRole role)
{
this.uuid = uuid;
this.role = role;
}
public Player getPlayer()
{
@@ -1,6 +0,0 @@
package dev.plex.guild.data;
public enum Permission
{
SET_WARP, WARPS, DELETE_WARP, WARP
}
@@ -1,5 +0,0 @@
package dev.plex.guild.data;
public record Rank(String name, String prefix)
{
}
@@ -0,0 +1,41 @@
package dev.plex.storage;
import dev.plex.guild.Guild;
import dev.plex.guild.data.GuildRole;
import dev.plex.storage.entity.GuildInviteEntity;
import dev.plex.util.CustomLocation;
import org.bukkit.entity.Player;
import java.time.Instant;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
public interface GuildRepository
{
CompletableFuture<List<Guild>> loadGuilds();
CompletableFuture<Guild> createGuild(Player owner, String name);
CompletableFuture<Void> deleteGuild(UUID guildUuid);
CompletableFuture<Void> addMember(UUID guildUuid, UUID playerUuid, GuildRole role);
CompletableFuture<Void> removeMember(UUID guildUuid, UUID playerUuid);
CompletableFuture<Void> transferOwner(UUID guildUuid, UUID newOwnerUuid, UUID oldOwnerUuid);
CompletableFuture<Void> updatePrefix(UUID guildUuid, String prefix);
CompletableFuture<Void> updateHome(UUID guildUuid, CustomLocation home);
CompletableFuture<Void> upsertWarp(UUID guildUuid, String name, CustomLocation location);
CompletableFuture<Void> deleteWarp(UUID guildUuid, String name);
CompletableFuture<Void> createInvite(UUID guildUuid, UUID inviterUuid, UUID inviteeUuid, Instant expiresAt);
CompletableFuture<Void> deleteInvite(UUID guildUuid, UUID inviteeUuid);
CompletableFuture<List<GuildInviteEntity>> invitesFor(UUID inviteeUuid);
}
@@ -0,0 +1,372 @@
package dev.plex.storage;
import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.stmt.DeleteBuilder;
import dev.plex.Guilds;
import dev.plex.api.storage.ModuleStorage;
import dev.plex.guild.Guild;
import dev.plex.guild.data.GuildRole;
import dev.plex.guild.data.Member;
import dev.plex.storage.entity.GuildEntity;
import dev.plex.storage.entity.GuildInviteEntity;
import dev.plex.storage.entity.GuildMemberEntity;
import dev.plex.storage.entity.GuildWarpEntity;
import dev.plex.util.CustomLocation;
import org.bukkit.entity.Player;
import java.sql.SQLException;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.Locale;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
public class OrmGuildRepository implements GuildRepository
{
private final ModuleStorage moduleStorage;
private final Executor executor;
private final Dao<GuildEntity, String> guilds;
private final Dao<GuildMemberEntity, Long> members;
private final Dao<GuildWarpEntity, Long> warps;
private final Dao<GuildInviteEntity, Long> invites;
public OrmGuildRepository(ModuleStorage moduleStorage)
{
this.moduleStorage = moduleStorage;
this.executor = Guilds.get().api().scheduler().asyncExecutor();
try
{
this.guilds = moduleStorage.orm().dao(GuildEntity.class, "guilds");
this.members = moduleStorage.orm().dao(GuildMemberEntity.class, "members");
this.warps = moduleStorage.orm().dao(GuildWarpEntity.class, "warps");
this.invites = moduleStorage.orm().dao(GuildInviteEntity.class, "invites");
}
catch (SQLException e)
{
throw new IllegalStateException("Failed to create guild DAOs", e);
}
}
@Override
public CompletableFuture<List<Guild>> loadGuilds()
{
return CompletableFuture.supplyAsync(() ->
{
try
{
return guilds.queryForAll().stream().map(this::toGuild).toList();
}
catch (SQLException e)
{
throw new IllegalStateException("Failed to load guilds", e);
}
}, executor);
}
@Override
public CompletableFuture<Guild> createGuild(Player owner, String name)
{
return CompletableFuture.supplyAsync(() ->
{
try
{
Guild guild = Guild.create(owner, name);
moduleStorage.transaction(() ->
{
guilds.create(toEntity(guild));
members.create(memberEntity(guild.getGuildUuid(), owner.getUniqueId(), GuildRole.OWNER));
return null;
});
return guild;
}
catch (SQLException e)
{
throw new IllegalStateException("Failed to create guild", e);
}
}, executor);
}
@Override
public CompletableFuture<Void> deleteGuild(UUID guildUuid)
{
return runAsync(() -> moduleStorage.transaction(() ->
{
deleteByGuild(members, guildUuid);
deleteByGuild(warps, guildUuid);
deleteByGuild(invites, guildUuid);
guilds.deleteById(guildUuid.toString());
return null;
}));
}
@Override
public CompletableFuture<Void> addMember(UUID guildUuid, UUID playerUuid, GuildRole role)
{
return runAsync(() -> upsertMember(guildUuid, playerUuid, role));
}
@Override
public CompletableFuture<Void> removeMember(UUID guildUuid, UUID playerUuid)
{
return runAsync(() ->
{
DeleteBuilder<GuildMemberEntity, Long> delete = members.deleteBuilder();
delete.where().eq("guild_uuid", guildUuid.toString()).and().eq("player_uuid", playerUuid.toString());
delete.delete();
});
}
@Override
public CompletableFuture<Void> transferOwner(UUID guildUuid, UUID newOwnerUuid, UUID oldOwnerUuid)
{
return runAsync(() -> moduleStorage.transaction(() ->
{
GuildEntity guild = guilds.queryForId(guildUuid.toString());
if (guild != null)
{
guild.setOwnerUuid(newOwnerUuid.toString());
guilds.update(guild);
}
upsertMember(guildUuid, oldOwnerUuid, GuildRole.MEMBER);
upsertMember(guildUuid, newOwnerUuid, GuildRole.OWNER);
return null;
}));
}
@Override
public CompletableFuture<Void> updatePrefix(UUID guildUuid, String prefix)
{
return runAsync(() ->
{
GuildEntity guild = guilds.queryForId(guildUuid.toString());
if (guild != null)
{
guild.setPrefix(prefix);
guilds.update(guild);
}
});
}
@Override
public CompletableFuture<Void> updateHome(UUID guildUuid, CustomLocation home)
{
return runAsync(() ->
{
GuildEntity guild = guilds.queryForId(guildUuid.toString());
if (guild != null)
{
setHome(guild, home);
guilds.update(guild);
}
});
}
@Override
public CompletableFuture<Void> upsertWarp(UUID guildUuid, String name, CustomLocation location)
{
return runAsync(() ->
{
GuildWarpEntity entity = warps.queryBuilder().where()
.eq("guild_uuid", guildUuid.toString()).and().eq("name", name.toLowerCase(Locale.ROOT))
.queryForFirst();
if (entity == null)
{
entity = new GuildWarpEntity();
entity.setGuildUuid(guildUuid.toString());
entity.setName(name.toLowerCase(Locale.ROOT));
}
setWarpLocation(entity, location);
warps.createOrUpdate(entity);
});
}
@Override
public CompletableFuture<Void> deleteWarp(UUID guildUuid, String name)
{
return runAsync(() ->
{
DeleteBuilder<GuildWarpEntity, Long> delete = warps.deleteBuilder();
delete.where().eq("guild_uuid", guildUuid.toString()).and().eq("name", name.toLowerCase(Locale.ROOT));
delete.delete();
});
}
@Override
public CompletableFuture<Void> createInvite(UUID guildUuid, UUID inviterUuid, UUID inviteeUuid, Instant expiresAt)
{
return runAsync(() ->
{
deleteInviteSync(guildUuid, inviteeUuid);
GuildInviteEntity invite = new GuildInviteEntity();
invite.setGuildUuid(guildUuid.toString());
invite.setInviterUuid(inviterUuid.toString());
invite.setInviteeUuid(inviteeUuid.toString());
invite.setExpiresAt(expiresAt.toEpochMilli());
invites.create(invite);
});
}
@Override
public CompletableFuture<Void> deleteInvite(UUID guildUuid, UUID inviteeUuid)
{
return runAsync(() -> deleteInviteSync(guildUuid, inviteeUuid));
}
@Override
public CompletableFuture<List<GuildInviteEntity>> invitesFor(UUID inviteeUuid)
{
return CompletableFuture.supplyAsync(() ->
{
try
{
long now = Instant.now().toEpochMilli();
return invites.queryForEq("invitee_uuid", inviteeUuid.toString()).stream()
.filter(invite -> invite.getExpiresAt() >= now)
.toList();
}
catch (SQLException e)
{
throw new IllegalStateException("Failed to load guild invites", e);
}
}, executor);
}
private Guild toGuild(GuildEntity entity)
{
String timezone = Guilds.get().api().configuration().mainConfig().getString("server.timezone", "Etc/UTC");
Guild guild = new Guild(UUID.fromString(entity.getGuildUuid()), ZonedDateTime.ofInstant(Instant.ofEpochMilli(entity.getCreatedAt()), ZoneId.of(timezone)));
guild.setName(entity.getName());
guild.setOwnerUuid(UUID.fromString(entity.getOwnerUuid()));
guild.setPrefix(entity.getPrefix());
guild.setMotd(entity.getMotd());
guild.setTagEnabled(entity.isTagEnabled());
guild.setPublic(entity.isPublicGuild());
guild.setHome(toLocation(entity));
try
{
members.queryForEq("guild_uuid", entity.getGuildUuid()).forEach(member -> guild.addMember(toMember(member)));
warps.queryForEq("guild_uuid", entity.getGuildUuid()).forEach(warp -> guild.getWarps().put(warp.getName(), toLocation(warp)));
}
catch (SQLException e)
{
throw new IllegalStateException("Failed to load guild relations", e);
}
return guild;
}
private GuildEntity toEntity(Guild guild)
{
GuildEntity entity = new GuildEntity();
entity.setGuildUuid(guild.getGuildUuid().toString());
entity.setName(guild.getName());
entity.setOwnerUuid(guild.getOwnerUuid().toString());
entity.setCreatedAt(guild.getCreatedAt().toInstant().toEpochMilli());
entity.setPrefix(guild.getPrefix());
entity.setMotd(guild.getMotd());
entity.setTagEnabled(guild.isTagEnabled());
entity.setPublicGuild(guild.isPublic());
setHome(entity, guild.getHome());
return entity;
}
private Member toMember(GuildMemberEntity entity)
{
return new Member(UUID.fromString(entity.getPlayerUuid()), GuildRole.valueOf(entity.getRole()));
}
private GuildMemberEntity memberEntity(UUID guildUuid, UUID playerUuid, GuildRole role)
{
GuildMemberEntity entity = new GuildMemberEntity();
entity.setGuildUuid(guildUuid.toString());
entity.setPlayerUuid(playerUuid.toString());
entity.setRole(role.name());
entity.setJoinedAt(Instant.now().toEpochMilli());
return entity;
}
private void upsertMember(UUID guildUuid, UUID playerUuid, GuildRole role) throws SQLException
{
GuildMemberEntity entity = members.queryBuilder().where()
.eq("guild_uuid", guildUuid.toString()).and().eq("player_uuid", playerUuid.toString())
.queryForFirst();
if (entity == null)
{
members.create(memberEntity(guildUuid, playerUuid, role));
return;
}
entity.setRole(role.name());
members.update(entity);
}
private void deleteInviteSync(UUID guildUuid, UUID inviteeUuid) throws SQLException
{
DeleteBuilder<GuildInviteEntity, Long> delete = invites.deleteBuilder();
delete.where().eq("guild_uuid", guildUuid.toString()).and().eq("invitee_uuid", inviteeUuid.toString());
delete.delete();
}
private <T> void deleteByGuild(Dao<T, Long> dao, UUID guildUuid) throws SQLException
{
DeleteBuilder<T, Long> delete = dao.deleteBuilder();
delete.where().eq("guild_uuid", guildUuid.toString());
delete.delete();
}
private void setHome(GuildEntity entity, CustomLocation home)
{
entity.setHomeWorld(home == null ? null : home.getWorldName());
entity.setHomeX(home == null ? null : home.getX());
entity.setHomeY(home == null ? null : home.getY());
entity.setHomeZ(home == null ? null : home.getZ());
entity.setHomeYaw(home == null ? null : home.getYaw());
entity.setHomePitch(home == null ? null : home.getPitch());
}
private CustomLocation toLocation(GuildEntity entity)
{
if (entity.getHomeWorld() == null)
{
return null;
}
return new CustomLocation(entity.getHomeWorld(), entity.getHomeX(), entity.getHomeY(), entity.getHomeZ(), entity.getHomeYaw(), entity.getHomePitch());
}
private void setWarpLocation(GuildWarpEntity entity, CustomLocation location)
{
entity.setWorld(location.getWorldName());
entity.setX(location.getX());
entity.setY(location.getY());
entity.setZ(location.getZ());
entity.setYaw(location.getYaw());
entity.setPitch(location.getPitch());
}
private CustomLocation toLocation(GuildWarpEntity entity)
{
return new CustomLocation(entity.getWorld(), entity.getX(), entity.getY(), entity.getZ(), entity.getYaw(), entity.getPitch());
}
private CompletableFuture<Void> runAsync(SqlRunnable runnable)
{
return CompletableFuture.runAsync(() ->
{
try
{
runnable.run();
}
catch (SQLException e)
{
throw new IllegalStateException("Guild storage operation failed", e);
}
}, executor);
}
@FunctionalInterface
private interface SqlRunnable
{
void run() throws SQLException;
}
}
@@ -0,0 +1,58 @@
package dev.plex.storage.entity;
import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@DatabaseTable
public class GuildEntity
{
@DatabaseField(id = true, columnName = "guild_uuid", width = 46)
private String guildUuid;
@DatabaseField(columnName = "name", width = 64, canBeNull = false)
private String name;
@DatabaseField(columnName = "prefix", width = 64)
private String prefix;
@DatabaseField(columnName = "owner_uuid", width = 46, canBeNull = false)
private String ownerUuid;
@DatabaseField(columnName = "created_at")
private long createdAt;
@DatabaseField(columnName = "home_world", width = 128)
private String homeWorld;
@DatabaseField(columnName = "home_x")
private Double homeX;
@DatabaseField(columnName = "home_y")
private Double homeY;
@DatabaseField(columnName = "home_z")
private Double homeZ;
@DatabaseField(columnName = "home_yaw")
private Float homeYaw;
@DatabaseField(columnName = "home_pitch")
private Float homePitch;
@DatabaseField(columnName = "motd", width = 3000)
private String motd;
@DatabaseField(columnName = "tag_enabled")
private boolean tagEnabled = true;
@DatabaseField(columnName = "public")
private boolean publicGuild;
public GuildEntity()
{
}
}
@@ -0,0 +1,31 @@
package dev.plex.storage.entity;
import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@DatabaseTable
public class GuildInviteEntity
{
@DatabaseField(generatedId = true, columnName = "id")
private long id;
@DatabaseField(columnName = "guild_uuid", width = 46, canBeNull = false)
private String guildUuid;
@DatabaseField(columnName = "inviter_uuid", width = 46, canBeNull = false)
private String inviterUuid;
@DatabaseField(columnName = "invitee_uuid", width = 46, canBeNull = false)
private String inviteeUuid;
@DatabaseField(columnName = "expires_at")
private long expiresAt;
public GuildInviteEntity()
{
}
}
@@ -0,0 +1,31 @@
package dev.plex.storage.entity;
import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@DatabaseTable
public class GuildMemberEntity
{
@DatabaseField(generatedId = true, columnName = "id")
private long id;
@DatabaseField(columnName = "guild_uuid", width = 46, canBeNull = false)
private String guildUuid;
@DatabaseField(columnName = "player_uuid", width = 46, canBeNull = false)
private String playerUuid;
@DatabaseField(columnName = "role", width = 20, canBeNull = false)
private String role;
@DatabaseField(columnName = "joined_at")
private long joinedAt;
public GuildMemberEntity()
{
}
}
@@ -0,0 +1,43 @@
package dev.plex.storage.entity;
import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@DatabaseTable
public class GuildWarpEntity
{
@DatabaseField(generatedId = true, columnName = "id")
private long id;
@DatabaseField(columnName = "guild_uuid", width = 46, canBeNull = false)
private String guildUuid;
@DatabaseField(columnName = "name", width = 16, canBeNull = false)
private String name;
@DatabaseField(columnName = "world", width = 128, canBeNull = false)
private String world;
@DatabaseField(columnName = "x")
private double x;
@DatabaseField(columnName = "y")
private double y;
@DatabaseField(columnName = "z")
private double z;
@DatabaseField(columnName = "yaw")
private float yaw;
@DatabaseField(columnName = "pitch")
private float pitch;
public GuildWarpEntity()
{
}
}
@@ -0,0 +1,52 @@
CREATE TABLE IF NOT EXISTS {{table:guilds}} (
`guild_uuid` VARCHAR(46) NOT NULL,
`name` VARCHAR(64) NOT NULL,
`prefix` VARCHAR(64),
`owner_uuid` VARCHAR(46) NOT NULL,
`created_at` BIGINT NOT NULL,
`home_world` VARCHAR(128),
`home_x` DOUBLE,
`home_y` DOUBLE,
`home_z` DOUBLE,
`home_yaw` FLOAT,
`home_pitch` FLOAT,
`motd` VARCHAR(3000),
`tag_enabled` BOOLEAN NOT NULL DEFAULT TRUE,
`public` BOOLEAN NOT NULL DEFAULT FALSE,
PRIMARY KEY (`guild_uuid`),
UNIQUE KEY `uq_guilds_name` (`name`)
);
CREATE TABLE IF NOT EXISTS {{table:members}} (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`guild_uuid` VARCHAR(46) NOT NULL,
`player_uuid` VARCHAR(46) NOT NULL,
`role` VARCHAR(20) NOT NULL,
`joined_at` BIGINT NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uq_members_guild_player` (`guild_uuid`, `player_uuid`)
);
CREATE TABLE IF NOT EXISTS {{table:warps}} (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`guild_uuid` VARCHAR(46) NOT NULL,
`name` VARCHAR(16) NOT NULL,
`world` VARCHAR(128) NOT NULL,
`x` DOUBLE NOT NULL,
`y` DOUBLE NOT NULL,
`z` DOUBLE NOT NULL,
`yaw` FLOAT NOT NULL,
`pitch` FLOAT NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uq_warps_guild_name` (`guild_uuid`, `name`)
);
CREATE TABLE IF NOT EXISTS {{table:invites}} (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`guild_uuid` VARCHAR(46) NOT NULL,
`inviter_uuid` VARCHAR(46) NOT NULL,
`invitee_uuid` VARCHAR(46) NOT NULL,
`expires_at` BIGINT NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uq_invites_guild_invitee` (`guild_uuid`, `invitee_uuid`)
);
@@ -0,0 +1,47 @@
CREATE TABLE IF NOT EXISTS {{table:guilds}} (
guild_uuid VARCHAR(46) NOT NULL PRIMARY KEY,
name VARCHAR(64) NOT NULL UNIQUE,
prefix VARCHAR(64),
owner_uuid VARCHAR(46) NOT NULL,
created_at BIGINT NOT NULL,
home_world VARCHAR(128),
home_x DOUBLE PRECISION,
home_y DOUBLE PRECISION,
home_z DOUBLE PRECISION,
home_yaw REAL,
home_pitch REAL,
motd VARCHAR(3000),
tag_enabled BOOLEAN NOT NULL DEFAULT TRUE,
public BOOLEAN NOT NULL DEFAULT FALSE
);
CREATE TABLE IF NOT EXISTS {{table:members}} (
id BIGSERIAL PRIMARY KEY,
guild_uuid VARCHAR(46) NOT NULL,
player_uuid VARCHAR(46) NOT NULL,
role VARCHAR(20) NOT NULL,
joined_at BIGINT NOT NULL,
CONSTRAINT uq_members_guild_player UNIQUE (guild_uuid, player_uuid)
);
CREATE TABLE IF NOT EXISTS {{table:warps}} (
id BIGSERIAL PRIMARY KEY,
guild_uuid VARCHAR(46) NOT NULL,
name VARCHAR(16) NOT NULL,
world VARCHAR(128) NOT NULL,
x DOUBLE PRECISION NOT NULL,
y DOUBLE PRECISION NOT NULL,
z DOUBLE PRECISION NOT NULL,
yaw REAL NOT NULL,
pitch REAL NOT NULL,
CONSTRAINT uq_warps_guild_name UNIQUE (guild_uuid, name)
);
CREATE TABLE IF NOT EXISTS {{table:invites}} (
id BIGSERIAL PRIMARY KEY,
guild_uuid VARCHAR(46) NOT NULL,
inviter_uuid VARCHAR(46) NOT NULL,
invitee_uuid VARCHAR(46) NOT NULL,
expires_at BIGINT NOT NULL,
CONSTRAINT uq_invites_guild_invitee UNIQUE (guild_uuid, invitee_uuid)
);
@@ -0,0 +1,47 @@
CREATE TABLE IF NOT EXISTS {{table:guilds}} (
guild_uuid VARCHAR(46) NOT NULL PRIMARY KEY,
name VARCHAR(64) NOT NULL UNIQUE,
prefix VARCHAR(64),
owner_uuid VARCHAR(46) NOT NULL,
created_at BIGINT NOT NULL,
home_world VARCHAR(128),
home_x DOUBLE,
home_y DOUBLE,
home_z DOUBLE,
home_yaw FLOAT,
home_pitch FLOAT,
motd VARCHAR(3000),
tag_enabled BOOLEAN NOT NULL DEFAULT 1,
public BOOLEAN NOT NULL DEFAULT 0
);
CREATE TABLE IF NOT EXISTS {{table:members}} (
id INTEGER PRIMARY KEY AUTOINCREMENT,
guild_uuid VARCHAR(46) NOT NULL,
player_uuid VARCHAR(46) NOT NULL,
role VARCHAR(20) NOT NULL,
joined_at BIGINT NOT NULL,
UNIQUE (guild_uuid, player_uuid)
);
CREATE TABLE IF NOT EXISTS {{table:warps}} (
id INTEGER PRIMARY KEY AUTOINCREMENT,
guild_uuid VARCHAR(46) NOT NULL,
name VARCHAR(16) NOT NULL,
world VARCHAR(128) NOT NULL,
x DOUBLE NOT NULL,
y DOUBLE NOT NULL,
z DOUBLE NOT NULL,
yaw FLOAT NOT NULL,
pitch FLOAT NOT NULL,
UNIQUE (guild_uuid, name)
);
CREATE TABLE IF NOT EXISTS {{table:invites}} (
id INTEGER PRIMARY KEY AUTOINCREMENT,
guild_uuid VARCHAR(46) NOT NULL,
inviter_uuid VARCHAR(46) NOT NULL,
invitee_uuid VARCHAR(46) NOT NULL,
expires_at BIGINT NOT NULL,
UNIQUE (guild_uuid, invitee_uuid)
);
+10 -1
View File
@@ -4,6 +4,7 @@ guildCommandNotFound: "<red>'<gold>{0}</gold>'<red> is not a valid sub command!"
guildNotFound: "<red>You're currently not a part of a guild!"
guildInThis: "<red>You're currently a part of this guild!"
alreadyInGuild: "<red>You're currently in a guild. Please do <gold>/guild leave<red> if you're a member, or if you're an owner with members, <gold>/guild promote <player><red> then <gold>/guild leave<red>, or just an owner, <gold>/guild disband<red>."
guildCreated: "<green>Created guild named <dark_green>{0}<green>."
guildNotOwner: "<red>You're not the owner of this guild!"
guildMemberNotFound: "<red>This guild member could not be found!"
guildOwnerSet: "<green>You have successfully promoted <dark_green>{0}<green> to be the new guild owner. You have been set to a default guild member."
@@ -23,8 +24,16 @@ guildNoInvite: "<red>You don't have any pending invitations!"
guildNotValidInvite: "<red>You don't have an invite from this guild!"
guildInviteExists: "<red>You've already sent an invite to this person!"
guildInviteSent: "<green>You have sent an invite to <dark_green>{0}"
guildInviteReceived: "<gold>You have received an invite from <yellow>{0}<gold> for the guild <yellow>{1}<newline><newline><green><bold><click:run_command:/guild invite accept {1}>[ACCEPT]<newline><newline><!bold><gold>You may also run <yellow>/guild invite accept {1}<gold> to accept this invite. It will expire in 5 minutes"
guildInviteReceived: "<gold>You have received an invite from <yellow>{0}<gold> for the guild <yellow>{1}<newline><newline><green><bold><click:run_command:/guild accept {1}>[ACCEPT]<newline><newline><!bold><gold>You may also run <yellow>/guild accept {1}<gold> to accept this invite. It will expire in 5 minutes"
guildMemberJoined: "<green>{0} has joined the guild!"
guildMemberLeft: "<green>{0} has left the guild!"
guildDisbandNeeded: "<red>You need to disband your guild using <gold>/guild disband<red> or promote a new owner using <gold>/guild owner <player>"
guildAutoDisbanded: "<green>Auto-disbanding your guild since there were no members"
guildDisbanded: "<green>Your guild has been disbanded."
guildLeft: "<green>You have left the guild."
guildOwnerLeaveBlocked: "<red>You must transfer ownership before leaving this guild."
guildInviteDenied: "<green>You denied the guild invite."
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."