diff --git a/src/main/java/net/kewwbec/social/SocialPlugin.java b/src/main/java/net/kewwbec/social/SocialPlugin.java index 3da9ee0..35e4f64 100644 --- a/src/main/java/net/kewwbec/social/SocialPlugin.java +++ b/src/main/java/net/kewwbec/social/SocialPlugin.java @@ -14,6 +14,7 @@ import net.kewwbec.social.command.dm.ChatCommand; import net.kewwbec.social.command.dm.MsgCommand; import net.kewwbec.social.command.dm.ReplyCommand; import net.kewwbec.social.command.friend.FriendCommand; +import net.kewwbec.social.command.friend.FriendListCommand; import net.kewwbec.social.command.guild.GuildCommand; import net.kewwbec.social.command.privacy.PrivacyCommand; import net.kewwbec.social.config.SocialConfig; @@ -106,6 +107,13 @@ public final class SocialPlugin extends JavaPlugin { FriendService friends = new FriendService(friendRepo, privacy, bus, config, core.getServerId()); GuildService guilds = new GuildService(guildRepo, rankRepo, memberRepo, inviteRepo, privacy, bus, config, core.getServerId()); + core.registerService(net.kewwbec.networkcore.api.guilds.GuildLookup.class, uuid -> { + GuildService.GuildContext gc = guilds.contextFor(uuid); + if (gc == null) return null; + return new net.kewwbec.networkcore.api.guilds.GuildInfo( + gc.guild().id(), gc.guild().name(), gc.guild().tag(), gc.rank().name()); + }); + this.guildChat = new GuildChatService(bus, memberRepo, config, core.getServerId()); guildChat.start(); @@ -123,9 +131,10 @@ public final class SocialPlugin extends JavaPlugin { getEventRegistry().registerGlobal(PlayerDisconnectEvent.class, disconnect::onDisconnect); getCommandRegistry().registerCommand(new FriendCommand(friends, resolver)); + getCommandRegistry().registerCommand(new FriendListCommand(friends)); getCommandRegistry().registerCommand(new GuildCommand(guilds, resolver, chatTargets)); - getCommandRegistry().registerCommand(new MsgCommand(chatTargets, resolver, dms)); - getCommandRegistry().registerCommand(new ReplyCommand(chatTargets, dms, presence)); + getCommandRegistry().registerCommand(new MsgCommand(resolver, dms)); + getCommandRegistry().registerCommand(new ReplyCommand(dms, presence)); getCommandRegistry().registerCommand(new ChatCommand(chatTargets)); getCommandRegistry().registerCommand(new PrivacyCommand(privacy)); getCommandRegistry().registerCommand(new SocialCommand(new SocialUIContext(friends, guilds, privacy))); diff --git a/src/main/java/net/kewwbec/social/command/dm/MsgCommand.java b/src/main/java/net/kewwbec/social/command/dm/MsgCommand.java index 438ea6e..67be30c 100644 --- a/src/main/java/net/kewwbec/social/command/dm/MsgCommand.java +++ b/src/main/java/net/kewwbec/social/command/dm/MsgCommand.java @@ -4,7 +4,6 @@ import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.Message; import com.hypixel.hytale.server.core.command.system.CommandContext; -import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; @@ -12,8 +11,6 @@ import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import net.kewwbec.networkcore.api.PlayerLocation; -import net.kewwbec.social.SocialPermissionsNodes; -import net.kewwbec.social.service.ChatTargetService; import net.kewwbec.social.service.DmRoutingService; import net.kewwbec.social.util.PlayerResolver; @@ -21,28 +18,20 @@ import javax.annotation.Nonnull; import java.awt.Color; /** - * /msg <name> [message...] - * - * If a message body is supplied, sends it once and leaves chat mode alone. - * If only a name is supplied, enters one-shot DM mode for the caller - the next chat message they - * send is routed as a DM to that target, then the mode reverts to NORMAL. + * /msg <name> <message...> - send a DM to an online player. */ public final class MsgCommand extends AbstractPlayerCommand { - private final ChatTargetService targets; private final PlayerResolver resolver; private final DmRoutingService dms; private final RequiredArg nameArg = withRequiredArg("name", "Recipient", ArgTypes.STRING); - private final OptionalArg bodyArg = withOptionalArg("message", "Message body (optional)", ArgTypes.GREEDY_STRING); + private final RequiredArg bodyArg = withRequiredArg("message", "Message body", ArgTypes.GREEDY_STRING); - public MsgCommand(@Nonnull ChatTargetService targets, - @Nonnull PlayerResolver resolver, - @Nonnull DmRoutingService dms) { - super("msg", "Send a one-shot DM (or enter DM mode if no message given)"); - this.targets = targets; + public MsgCommand(@Nonnull PlayerResolver resolver, @Nonnull DmRoutingService dms) { + super("msg", "Send a DM to a player"); this.resolver = resolver; this.dms = dms; - requirePermission(SocialPermissionsNodes.DM_USE); + // requirePermission(SocialPermissionsNodes.DM_USE); } @Override protected boolean canGeneratePermission() { return false; } @@ -50,8 +39,9 @@ public final class MsgCommand extends AbstractPlayerCommand { @Override protected void execute(@Nonnull CommandContext ctx, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef sender, @Nonnull World world) { String name = nameArg.get(ctx); - if (name == null) { - ctx.sendMessage(Message.raw("Usage: /msg [message...]").color(Color.YELLOW)); + String body = bodyArg.get(ctx); + if (name == null || body == null || body.isBlank()) { + ctx.sendMessage(Message.raw("Usage: /msg ").color(Color.YELLOW)); return; } PlayerLocation target = resolver.resolve(name); @@ -59,18 +49,10 @@ public final class MsgCommand extends AbstractPlayerCommand { ctx.sendMessage(Message.raw(name + " is not online.").color(Color.RED)); return; } - - String body = bodyArg.get(ctx); - if (body != null && !body.isBlank()) { - DmRoutingService.Result r = dms.send(sender, target.username(), body.trim()); - if (r != DmRoutingService.Result.OK) { - ctx.sendMessage(Message.raw(errorText(r, target.username())).color(Color.RED)); - } - return; + DmRoutingService.Result r = dms.send(sender, target.username(), body.trim()); + if (r != DmRoutingService.Result.OK) { + ctx.sendMessage(Message.raw(errorText(r, target.username())).color(Color.RED)); } - - targets.setDm(sender.getUuid(), target.uuid(), target.username()); - ctx.sendMessage(Message.raw("DM mode -> " + target.username() + ". Type your next chat to send.").color(Color.LIGHT_GRAY)); } @Nonnull diff --git a/src/main/java/net/kewwbec/social/command/dm/ReplyCommand.java b/src/main/java/net/kewwbec/social/command/dm/ReplyCommand.java index 177d323..92a6fcc 100644 --- a/src/main/java/net/kewwbec/social/command/dm/ReplyCommand.java +++ b/src/main/java/net/kewwbec/social/command/dm/ReplyCommand.java @@ -4,7 +4,7 @@ import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.Message; import com.hypixel.hytale.server.core.command.system.CommandContext; -import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; +import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; import com.hypixel.hytale.server.core.universe.PlayerRef; @@ -12,8 +12,6 @@ import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import net.kewwbec.networkcore.api.PlayerLocation; import net.kewwbec.networkcore.api.PlayerPresence; -import net.kewwbec.social.SocialPermissionsNodes; -import net.kewwbec.social.service.ChatTargetService; import net.kewwbec.social.service.DmRoutingService; import javax.annotation.Nonnull; @@ -21,33 +19,30 @@ import java.awt.Color; import java.util.UUID; /** - * /r [message...] - * - * If a body is supplied, sends it once to whoever DMed you last. - * If no body, enters one-shot DM mode targeting that player - the next chat message you send goes - * to them, then mode reverts to NORMAL. + * /r <message...> - send a DM to whoever DMed you last. */ public final class ReplyCommand extends AbstractPlayerCommand { - private final ChatTargetService targets; private final DmRoutingService dms; private final PlayerPresence presence; - private final OptionalArg bodyArg = withOptionalArg("message", "Message body (optional)", ArgTypes.GREEDY_STRING); + private final RequiredArg bodyArg = withRequiredArg("message", "Message body", ArgTypes.GREEDY_STRING); - public ReplyCommand(@Nonnull ChatTargetService targets, - @Nonnull DmRoutingService dms, - @Nonnull PlayerPresence presence) { - super("r", "Reply to your last DM sender (one-shot send, or enter DM mode if no body given)"); - this.targets = targets; + public ReplyCommand(@Nonnull DmRoutingService dms, @Nonnull PlayerPresence presence) { + super("r", "Reply to your last DM sender"); this.dms = dms; this.presence = presence; - requirePermission(SocialPermissionsNodes.DM_USE); + // requirePermission(SocialPermissionsNodes.DM_USE); } @Override protected boolean canGeneratePermission() { return false; } @Override protected void execute(@Nonnull CommandContext ctx, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef sender, @Nonnull World world) { + String body = bodyArg.get(ctx); + if (body == null || body.isBlank()) { + ctx.sendMessage(Message.raw("Usage: /r ").color(Color.YELLOW)); + return; + } UUID last = dms.getLastSender(sender.getUuid()); if (last == null) { ctx.sendMessage(Message.raw("No one has DMed you recently.").color(Color.RED)); @@ -58,18 +53,10 @@ public final class ReplyCommand extends AbstractPlayerCommand { ctx.sendMessage(Message.raw("That person is offline.").color(Color.RED)); return; } - - String body = bodyArg.get(ctx); - if (body != null && !body.isBlank()) { - DmRoutingService.Result r = dms.send(sender, target.username(), body.trim()); - if (r != DmRoutingService.Result.OK) { - ctx.sendMessage(Message.raw(errorText(r, target.username())).color(Color.RED)); - } - return; + DmRoutingService.Result r = dms.send(sender, target.username(), body.trim()); + if (r != DmRoutingService.Result.OK) { + ctx.sendMessage(Message.raw(errorText(r, target.username())).color(Color.RED)); } - - targets.setDm(sender.getUuid(), target.uuid(), target.username()); - ctx.sendMessage(Message.raw("Reply -> " + target.username() + ". Type your next chat to send.").color(Color.LIGHT_GRAY)); } @Nonnull diff --git a/src/main/java/net/kewwbec/social/command/friend/FriendCommand.java b/src/main/java/net/kewwbec/social/command/friend/FriendCommand.java index 8a19589..6a5421e 100644 --- a/src/main/java/net/kewwbec/social/command/friend/FriendCommand.java +++ b/src/main/java/net/kewwbec/social/command/friend/FriendCommand.java @@ -11,7 +11,11 @@ import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayer import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import net.kewwbec.networkcore.NetworkCore; import net.kewwbec.networkcore.api.PlayerLocation; +import net.kewwbec.networkcore.api.PlayerPresence; +import net.kewwbec.networkcore.api.ranks.RankInfo; +import net.kewwbec.networkcore.api.ranks.RankLookup; import net.kewwbec.social.SocialPermissionsNodes; import net.kewwbec.social.model.Friendship; import net.kewwbec.social.service.FriendService; @@ -27,6 +31,7 @@ public final class FriendCommand extends AbstractCommandCollection { public FriendCommand(@Nonnull FriendService service, @Nonnull PlayerResolver resolver) { super("friend", "Manage friends (add, accept, reject, remove, list)"); + addAliases("f"); addSubCommand(new Add(service, resolver)); addSubCommand(new Accept(service, resolver)); addSubCommand(new Reject(service, resolver)); @@ -46,7 +51,7 @@ public final class FriendCommand extends AbstractCommandCollection { super("add", "Send a friend request"); this.service = service; this.resolver = resolver; - requirePermission(SocialPermissionsNodes.FRIEND_USE); + // requirePermission(SocialPermissionsNodes.FRIEND_USE); } @Override protected boolean canGeneratePermission() { return false; } @@ -83,7 +88,7 @@ public final class FriendCommand extends AbstractCommandCollection { super("accept", "Accept an incoming friend request"); this.service = service; this.resolver = resolver; - requirePermission(SocialPermissionsNodes.FRIEND_USE); + // requirePermission(SocialPermissionsNodes.FRIEND_USE); } @Override protected boolean canGeneratePermission() { return false; } @@ -118,7 +123,7 @@ public final class FriendCommand extends AbstractCommandCollection { super("reject", "Reject an incoming friend request"); this.service = service; this.resolver = resolver; - requirePermission(SocialPermissionsNodes.FRIEND_USE); + // requirePermission(SocialPermissionsNodes.FRIEND_USE); } @Override protected boolean canGeneratePermission() { return false; } @@ -151,7 +156,7 @@ public final class FriendCommand extends AbstractCommandCollection { super("remove", "Remove a friend"); this.service = service; this.resolver = resolver; - requirePermission(SocialPermissionsNodes.FRIEND_USE); + // requirePermission(SocialPermissionsNodes.FRIEND_USE); } @Override protected boolean canGeneratePermission() { return false; } @@ -185,24 +190,78 @@ public final class FriendCommand extends AbstractCommandCollection { public ListAll(@Nonnull FriendService service) { super("list", "Show your friends"); this.service = service; - requirePermission(SocialPermissionsNodes.FRIEND_USE); + // requirePermission(SocialPermissionsNodes.FRIEND_USE); } @Override protected boolean canGeneratePermission() { return false; } @Override protected void execute(@Nonnull CommandContext ctx, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef sender, @Nonnull World world) { + render(ctx, service, sender); + } + + public static void render(@Nonnull CommandContext ctx, @Nonnull FriendService service, @Nonnull PlayerRef sender) { try { List all = service.list(sender.getUuid()); if (all.isEmpty()) { ctx.sendMessage(Message.raw("You have no friends yet.").color(Color.GRAY)); return; } - ctx.sendMessage(Message.raw("=== Friends (" + all.size() + ") ===").color(Color.YELLOW)); + + PlayerPresence presence = NetworkCore.getInstance().getPlayerPresence(); + RankLookup ranks = NetworkCore.getInstance().getRankLookup(); + + int online = 0; for (Friendship f : all) { - String other = f.fromUuid().equals(sender.getUuid()) ? f.toName() : f.fromName(); - ctx.sendMessage(Message.raw("- " + other).color(Color.WHITE)); + UUID otherUuid = f.fromUuid().equals(sender.getUuid()) ? f.toUuid() : f.fromUuid(); + if (presence != null && presence.getLocation(otherUuid) != null) online++; + } + ctx.sendMessage(Message.raw("=== Friends (" + online + "/" + all.size() + " online) ===").color(Color.YELLOW)); + + for (Friendship f : all) { + UUID otherUuid = f.fromUuid().equals(sender.getUuid()) ? f.toUuid() : f.fromUuid(); + String otherName = f.fromUuid().equals(sender.getUuid()) ? f.toName() : f.fromName(); + PlayerLocation loc = (presence == null) ? null : presence.getLocation(otherUuid); + String status = (loc == null) ? "offline" : "online on " + loc.serverId(); + RankInfo rank = (ranks == null) ? null : safeLookup(ranks, otherUuid); + ctx.sendMessage(buildRow(rank, otherName, status, loc != null)); } } catch (SQLException e) { ctx.sendMessage(Message.raw("DB error: " + e.getMessage()).color(Color.RED)); } } + + @Nonnull + private static Message buildRow(@javax.annotation.Nullable RankInfo rank, + @Nonnull String name, + @Nonnull String status, + boolean isOnline) { + String prefix = (rank == null || rank.prefix() == null || rank.prefix().isBlank()) + ? "" : rank.prefix() + " "; + Color rankColor = (rank == null) ? null : parseHex(rank.color()); + Color statusColor = isOnline ? Color.GREEN : Color.GRAY; + + Message line = Message.empty(); + if (!prefix.isEmpty()) { + Message prefixMsg = Message.raw(prefix); + line = line.insert(rankColor == null ? prefixMsg.color(Color.WHITE) : prefixMsg.color(rankColor)); + } + Message nameMsg = Message.raw(name); + line = line.insert(rankColor == null ? nameMsg.color(Color.WHITE) : nameMsg.color(rankColor)); + line = line.insert(Message.raw(" - " + status).color(statusColor)); + return line; + } + + @javax.annotation.Nullable + private static RankInfo safeLookup(@Nonnull RankLookup ranks, @Nonnull UUID uuid) { + try { return ranks.getPrimaryRank(uuid); } + catch (RuntimeException ignored) { return null; } + } + + @javax.annotation.Nullable + private static Color parseHex(@javax.annotation.Nullable String hex) { + if (hex == null || hex.isBlank()) return null; + String s = hex.startsWith("#") ? hex.substring(1) : hex; + if (s.length() != 6) return null; + try { return new Color(Integer.parseInt(s, 16)); } + catch (NumberFormatException ignored) { return null; } + } } public static final class Pending extends AbstractPlayerCommand { @@ -211,7 +270,7 @@ public final class FriendCommand extends AbstractCommandCollection { public Pending(@Nonnull FriendService service) { super("pending", "Show pending friend requests (incoming + outgoing)"); this.service = service; - requirePermission(SocialPermissionsNodes.FRIEND_USE); + // requirePermission(SocialPermissionsNodes.FRIEND_USE); } @Override protected boolean canGeneratePermission() { return false; } diff --git a/src/main/java/net/kewwbec/social/command/friend/FriendListCommand.java b/src/main/java/net/kewwbec/social/command/friend/FriendListCommand.java new file mode 100644 index 0000000..75d0fc1 --- /dev/null +++ b/src/main/java/net/kewwbec/social/command/friend/FriendListCommand.java @@ -0,0 +1,36 @@ +package net.kewwbec.social.command.friend; + +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import net.kewwbec.social.service.FriendService; + +import javax.annotation.Nonnull; + +/** + * /fl - top-level alias of /friend list. Same render logic via {@link FriendCommand.ListAll#render}. + */ +public final class FriendListCommand extends AbstractPlayerCommand { + + private final FriendService service; + + public FriendListCommand(@Nonnull FriendService service) { + super("fl", "Show your friends (alias of /friend list)"); + this.service = service; + } + + @Override protected boolean canGeneratePermission() { return false; } + + @Override + protected void execute(@Nonnull CommandContext ctx, + @Nonnull Store store, + @Nonnull Ref ref, + @Nonnull PlayerRef sender, + @Nonnull World world) { + FriendCommand.ListAll.render(ctx, service, sender); + } +} diff --git a/src/main/java/net/kewwbec/social/command/guild/GuildCommand.java b/src/main/java/net/kewwbec/social/command/guild/GuildCommand.java index 2fb0ee5..02ed223 100644 --- a/src/main/java/net/kewwbec/social/command/guild/GuildCommand.java +++ b/src/main/java/net/kewwbec/social/command/guild/GuildCommand.java @@ -32,6 +32,7 @@ public final class GuildCommand extends AbstractCommandCollection { @Nonnull PlayerResolver resolver, @Nonnull ChatTargetService chatTargets) { super("guild", "Guild commands: create, invite, leave, etc."); + addAliases("g"); addSubCommand(new Create(service)); addSubCommand(new Disband(service)); addSubCommand(new Invite(service, resolver)); @@ -56,7 +57,7 @@ public final class GuildCommand extends AbstractCommandCollection { public Create(@Nonnull GuildService service) { super("create", "Create a new guild"); this.service = service; - requirePermission(SocialPermissionsNodes.GUILD_CREATE); + // requirePermission(SocialPermissionsNodes.GUILD_CREATE); } @Override protected boolean canGeneratePermission() { return false; } @@ -80,7 +81,7 @@ public final class GuildCommand extends AbstractCommandCollection { public Disband(@Nonnull GuildService service) { super("disband", "Disband your guild (owner only)"); this.service = service; - requirePermission(SocialPermissionsNodes.GUILD_USE); + // requirePermission(SocialPermissionsNodes.GUILD_USE); } @Override protected boolean canGeneratePermission() { return false; } @Override @@ -104,7 +105,7 @@ public final class GuildCommand extends AbstractCommandCollection { super("invite", "Invite a player to your guild"); this.service = service; this.resolver = resolver; - requirePermission(SocialPermissionsNodes.GUILD_USE); + // requirePermission(SocialPermissionsNodes.GUILD_USE); } @Override protected boolean canGeneratePermission() { return false; } @@ -136,7 +137,7 @@ public final class GuildCommand extends AbstractCommandCollection { public Accept(@Nonnull GuildService service) { super("accept", "Accept a pending guild invite"); this.service = service; - requirePermission(SocialPermissionsNodes.GUILD_USE); + // requirePermission(SocialPermissionsNodes.GUILD_USE); } @Override protected boolean canGeneratePermission() { return false; } @@ -162,7 +163,7 @@ public final class GuildCommand extends AbstractCommandCollection { public Leave(@Nonnull GuildService service) { super("leave", "Leave your current guild"); this.service = service; - requirePermission(SocialPermissionsNodes.GUILD_USE); + // requirePermission(SocialPermissionsNodes.GUILD_USE); } @Override protected boolean canGeneratePermission() { return false; } @Override @@ -186,7 +187,7 @@ public final class GuildCommand extends AbstractCommandCollection { super("kick", "Kick a guild member"); this.service = service; this.resolver = resolver; - requirePermission(SocialPermissionsNodes.GUILD_USE); + // requirePermission(SocialPermissionsNodes.GUILD_USE); } @Override protected boolean canGeneratePermission() { return false; } @@ -221,7 +222,7 @@ public final class GuildCommand extends AbstractCommandCollection { super("promote", "Promote a member to a higher rank"); this.service = service; this.resolver = resolver; - requirePermission(SocialPermissionsNodes.GUILD_USE); + // requirePermission(SocialPermissionsNodes.GUILD_USE); } @Override protected boolean canGeneratePermission() { return false; } @@ -258,7 +259,7 @@ public final class GuildCommand extends AbstractCommandCollection { super("demote", "Demote a member to a lower rank"); this.service = service; this.resolver = resolver; - requirePermission(SocialPermissionsNodes.GUILD_USE); + // requirePermission(SocialPermissionsNodes.GUILD_USE); } @Override protected boolean canGeneratePermission() { return false; } @@ -292,7 +293,7 @@ public final class GuildCommand extends AbstractCommandCollection { public Motd(@Nonnull GuildService service) { super("motd", "Set the guild MOTD"); this.service = service; - requirePermission(SocialPermissionsNodes.GUILD_USE); + // requirePermission(SocialPermissionsNodes.GUILD_USE); } @Override protected boolean canGeneratePermission() { return false; } @@ -315,7 +316,7 @@ public final class GuildCommand extends AbstractCommandCollection { public Info(@Nonnull GuildService service) { super("info", "Show your guild"); this.service = service; - requirePermission(SocialPermissionsNodes.GUILD_USE); + // requirePermission(SocialPermissionsNodes.GUILD_USE); } @Override protected boolean canGeneratePermission() { return false; } @Override @@ -334,7 +335,7 @@ public final class GuildCommand extends AbstractCommandCollection { public RosterCmd(@Nonnull GuildService service) { super("roster", "Show all guild members"); this.service = service; - requirePermission(SocialPermissionsNodes.GUILD_USE); + // requirePermission(SocialPermissionsNodes.GUILD_USE); } @Override protected boolean canGeneratePermission() { return false; } @Override @@ -358,7 +359,7 @@ public final class GuildCommand extends AbstractCommandCollection { public RanksCmd(@Nonnull GuildService service) { super("ranks", "Show your guild's ranks"); this.service = service; - requirePermission(SocialPermissionsNodes.GUILD_USE); + // requirePermission(SocialPermissionsNodes.GUILD_USE); } @Override protected boolean canGeneratePermission() { return false; } @Override @@ -388,7 +389,7 @@ public final class GuildCommand extends AbstractCommandCollection { super("chat", "Toggle guild chat mode (your chat goes to your guild)"); this.service = service; this.chatTargets = chatTargets; - requirePermission(SocialPermissionsNodes.GUILD_USE); + // requirePermission(SocialPermissionsNodes.GUILD_USE); } @Override protected boolean canGeneratePermission() { return false; } @Override diff --git a/src/main/java/net/kewwbec/social/command/privacy/PrivacyCommand.java b/src/main/java/net/kewwbec/social/command/privacy/PrivacyCommand.java index 307ac23..ddc61eb 100644 --- a/src/main/java/net/kewwbec/social/command/privacy/PrivacyCommand.java +++ b/src/main/java/net/kewwbec/social/command/privacy/PrivacyCommand.java @@ -36,7 +36,7 @@ public final class PrivacyCommand extends AbstractCommandCollection { public Show(@Nonnull PrivacyService service) { super("show", "Show your current privacy settings"); this.service = service; - requirePermission(SocialPermissionsNodes.PRIVACY_USE); + // requirePermission(SocialPermissionsNodes.PRIVACY_USE); } @Override protected boolean canGeneratePermission() { return false; } @@ -63,7 +63,7 @@ public final class PrivacyCommand extends AbstractCommandCollection { public SetCmd(@Nonnull PrivacyService service) { super("set", "Change one of your privacy settings"); this.service = service; - requirePermission(SocialPermissionsNodes.PRIVACY_USE); + // requirePermission(SocialPermissionsNodes.PRIVACY_USE); } @Override protected boolean canGeneratePermission() { return false; } diff --git a/src/main/java/net/kewwbec/social/service/DmRoutingService.java b/src/main/java/net/kewwbec/social/service/DmRoutingService.java index f9858bb..b9c84f7 100644 --- a/src/main/java/net/kewwbec/social/service/DmRoutingService.java +++ b/src/main/java/net/kewwbec/social/service/DmRoutingService.java @@ -4,9 +4,12 @@ import com.hypixel.hytale.logger.HytaleLogger; import com.hypixel.hytale.server.core.Message; import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.Universe; +import net.kewwbec.networkcore.NetworkCore; import net.kewwbec.networkcore.api.MessageBus; import net.kewwbec.networkcore.api.PlayerLocation; import net.kewwbec.networkcore.api.PlayerPresence; +import net.kewwbec.networkcore.api.ranks.RankInfo; +import net.kewwbec.networkcore.api.ranks.RankLookup; import net.kewwbec.social.config.SocialConfig; import net.kewwbec.social.model.PrivacyKey; import net.kewwbec.social.service.payload.DmPayload; @@ -21,10 +24,10 @@ import java.util.logging.Level; /** * Cross-server DM routing. * - * Sender side: validate privacy, then publish a DmPayload on the dm channel. - * Each server's subscription checks whether the recipient is on its local universe; if so, it - * delivers the message; otherwise it ignores. This means recipient privacy is checked on the - * sender's server. + * Each delivered/echoed line is composed as: [optional colored rank prefix] [from/to NAME] body. + * The rank prefix uses the SENDER's primary rank (so the recipient sees who, with rank, sent it, + * and the sender's echo also shows their own prefix as confirmation). If no RankLookup service is + * registered (e.g. Ranks plugin not installed) or the sender has no rank, the prefix is omitted. */ public final class DmRoutingService { @@ -90,12 +93,12 @@ public final class DmRoutingService { body, serverId); bus.publish(config.channels.dm, p); - // Local echo to the sender Universe u = Universe.get(); if (u != null) { + RankInfo recipientRank = lookupRank(target.uuid()); for (PlayerRef ref : u.getPlayers()) { if (ref.getUuid().equals(sender.getUuid())) { - ref.sendMessage(Message.raw(formatTo(target.username(), body)).color(Color.LIGHT_GRAY)); + ref.sendMessage(buildLine("To", recipientRank, target.username(), body)); break; } } @@ -126,19 +129,56 @@ public final class DmRoutingService { } for (PlayerRef ref : u.getPlayers()) { if (!ref.getUuid().equals(recipientUuid)) continue; - ref.sendMessage(Message.raw(formatFrom(p.senderName, p.body)).color(Color.WHITE)); + RankInfo senderRank = lookupRank(senderUuid); + ref.sendMessage(buildLine("From", senderRank, p.senderName, p.body)); lastSender.put(recipientUuid, senderUuid); return; } } - @Nonnull - private String formatTo(@Nonnull String name, @Nonnull String body) { - return config.dms.prefix_to.replace("{name}", name) + " " + body; + @Nullable + private RankInfo lookupRank(@Nonnull UUID uuid) { + try { + RankLookup ranks = NetworkCore.getInstance().getRankLookup(); + return ranks == null ? null : ranks.getPrimaryRank(uuid); + } catch (RuntimeException ignored) { + return null; + } } + private static final Color GOLD = new Color(0xFFAA00); + @Nonnull - private String formatFrom(@Nonnull String name, @Nonnull String body) { - return config.dms.prefix_from.replace("{name}", name) + " " + body; + private static Message buildLine(@Nonnull String leadWord, + @Nullable RankInfo otherRank, + @Nonnull String otherName, + @Nonnull String body) { + String prefix = (otherRank == null || otherRank.prefix() == null || otherRank.prefix().isBlank()) + ? "" : otherRank.prefix(); + Color nameColor = (otherRank == null) ? null : parseHex(otherRank.color()); + + Message line = Message.empty() + .insert(Message.raw(leadWord + " ").color(GOLD)); + if (!prefix.isEmpty()) { + Message prefixMsg = Message.raw(prefix + " "); + line = line.insert(nameColor == null ? prefixMsg : prefixMsg.color(nameColor)); + } + Message nameMsg = Message.raw(otherName); + line = line.insert(nameColor == null ? nameMsg : nameMsg.color(nameColor)); + line = line.insert(Message.raw(": ").color(GOLD)); + line = line.insert(Message.raw(body).color(Color.WHITE)); + return line; + } + + @Nullable + private static Color parseHex(@Nullable String hex) { + if (hex == null || hex.isBlank()) return null; + String s = hex.startsWith("#") ? hex.substring(1) : hex; + if (s.length() != 6) return null; + try { + return new Color(Integer.parseInt(s, 16)); + } catch (NumberFormatException ignored) { + return null; + } } } diff --git a/src/main/java/net/kewwbec/social/ui/FriendsListPage.java b/src/main/java/net/kewwbec/social/ui/FriendsListPage.java index 6a492a1..7f94858 100644 --- a/src/main/java/net/kewwbec/social/ui/FriendsListPage.java +++ b/src/main/java/net/kewwbec/social/ui/FriendsListPage.java @@ -11,6 +11,8 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import net.kewwbec.networkcore.NetworkCore; import net.kewwbec.networkcore.api.PlayerLocation; import net.kewwbec.networkcore.api.PlayerPresence; +import net.kewwbec.networkcore.api.ranks.RankInfo; +import net.kewwbec.networkcore.api.ranks.RankLookup; import net.kewwbec.social.model.Friendship; import javax.annotation.Nonnull; @@ -99,13 +101,15 @@ public final class FriendsListPage { return sb.toString(); } PlayerPresence presence = NetworkCore.getInstance().getPlayerPresence(); + RankLookup ranks = NetworkCore.getInstance().getRankLookup(); int i = 0; for (Friendship f : friends) { UUID otherUuid = f.fromUuid().equals(playerRef.getUuid()) ? f.toUuid() : f.fromUuid(); String otherName = f.fromUuid().equals(playerRef.getUuid()) ? f.toName() : f.fromName(); PlayerLocation loc = (presence == null) ? null : presence.getLocation(otherUuid); - String status = (loc == null) ? "offline" : "on " + loc.serverId(); - sb.append("

").append(escape(otherName)).append(" (").append(escape(status)).append(")

"); + String status = (loc == null) ? "offline" : "online on " + loc.serverId(); + RankInfo rank = (ranks == null) ? null : safeLookup(ranks, otherUuid); + sb.append(renderNameRow(rank, otherName, status)); String removeId = "removeFriend" + i; sb.append(""); UUID finalOther = otherUuid; @@ -118,6 +122,21 @@ public final class FriendsListPage { return sb.toString(); } + @Nonnull + private static String renderNameRow(@javax.annotation.Nullable RankInfo rank, @Nonnull String name, @Nonnull String status) { + String prefix = (rank == null || rank.prefix() == null || rank.prefix().isBlank()) ? "" : rank.prefix() + " "; + String hex = (rank == null) ? null : rank.color(); + String styleAttr = (hex == null || hex.isBlank()) ? "" : " style=\"color: " + escape(hex) + ";\""; + return "" + escape(prefix) + escape(name) + "

" + + "

" + escape(status) + "

"; + } + + @javax.annotation.Nullable + private static RankInfo safeLookup(@Nonnull RankLookup ranks, @Nonnull UUID uuid) { + try { return ranks.getPrimaryRank(uuid); } + catch (RuntimeException e) { return null; } + } + @Nonnull private String renderIncoming(@Nonnull List actions) throws SQLException { List incoming = ctx.friends.incomingPending(playerRef.getUuid()); @@ -127,11 +146,13 @@ public final class FriendsListPage { sb.append("

No one has sent you a friend request.

"); return sb.toString(); } + RankLookup ranks = NetworkCore.getInstance().getRankLookup(); int i = 0; for (Friendship f : incoming) { UUID fromUuid = f.fromUuid(); String fromName = f.fromName(); - sb.append("

from ").append(escape(fromName)).append("

"); + RankInfo rank = (ranks == null) ? null : safeLookup(ranks, fromUuid); + sb.append(renderNameRow(rank, fromName, "wants to be your friend")); String acceptId = "accept" + i; String rejectId = "reject" + i; sb.append(""); @@ -156,11 +177,13 @@ public final class FriendsListPage { sb.append("

No outgoing requests.

"); return sb.toString(); } + RankLookup ranks = NetworkCore.getInstance().getRankLookup(); int i = 0; for (Friendship f : outgoing) { UUID toUuid = f.toUuid(); String toName = f.toName(); - sb.append("

to ").append(escape(toName)).append("

"); + RankInfo rank = (ranks == null) ? null : safeLookup(ranks, toUuid); + sb.append(renderNameRow(rank, toName, "waiting for them to accept")); String cancelId = "cancel" + i; sb.append(""); actions.add(new RowAction(cancelId, () ->