@@ -67,13 +67,16 @@ public class GuildCommand extends AbstractCommand {
6767 "create" , "format" , "chat" , "delete" , "select" , "role" , "invite" , "kick" , "ban" , "ban-public" , "unban" , "pardon" ,
6868 "leave" , "dontinviteme" , "toggleinvites" , "accept" , "reject" , "info" , "log" , "jp-on" , "jp-off" ,
6969 "linkdiscord" , "unlinkdiscord" , "nick" , "force-nick" , "open" , "join" , "hideall" , "hide-guild" , "hide-player" ,
70+ "1" , "2" , "3" , "4" , "5" , "6" , "7" , "8" , "9" ,
7071 // reserved names
7172 "permission" , "permissions"
7273 );
7374 private static final String DEFAULT_FORMAT = "&b[&a%gname&7@&6%server&b] &r%username&a: &r%msg &7%prereplace-b" ;
7475 private static final Pattern GUILD_NAME_PATTERN = Pattern .compile ("^[a-zA-Z0-9_\\ -.+]{2,32}$" );
7576 public static final String COMMAND_NAME = "guild" ;
7677 private static final Map <UUID , Long > LAST_GUILD_INVITE = new ConcurrentHashMap <>();
78+ private static final long GUILD_PRESET_CACHE_TTL = TimeUnit .MINUTES .toMillis (10 );
79+ private static final Map <UUID , Map <Integer , Map .Entry <Long , Long >>> GUILD_PRESET_CACHE = new ConcurrentHashMap <>();
7780 private static final Function <UUID , User > ACTUAL_USER = Functions .memoize (1000 * 10 , uuid ->
7881 InterChatProvider .get ().getUserManager ().fetchUser (uuid ).join ()
7982 );
@@ -96,6 +99,9 @@ public class GuildCommand extends AbstractCommand {
9699 // preview and suggest
97100 User user = ACTUAL_USER .apply (((Player ) context .getSource ()).getUniqueId ());
98101 Guild guild = chatSuggestionGuildProvider .apply (context , user .id ());
102+ if (guild == null ) {
103+ return builder .buildFuture ();
104+ }
99105 GuildMember member = GUILD_MEMBER .apply (new AbstractMap .SimpleImmutableEntry <>(guild .id (), user .id ()));
100106 String server = ((Player ) context .getSource ())
101107 .getCurrentServer ()
@@ -142,6 +148,61 @@ public class GuildCommand extends AbstractCommand {
142148 };
143149 }
144150
151+ static @ Nullable Guild getPresetGuildForSuggestion (@ NotNull UUID uuid , int presetNumber ) {
152+ long guildId = getPresetGuildId (uuid , presetNumber );
153+ if (guildId == -1 ) {
154+ return null ;
155+ }
156+ try {
157+ return InterChatProvider .get ().getGuildManager ().fetchGuildById (guildId ).join ();
158+ } catch (CompletionException e ) {
159+ return null ;
160+ }
161+ }
162+
163+ public static void clearPresetCache (@ NotNull UUID uuid ) {
164+ GUILD_PRESET_CACHE .remove (uuid );
165+ }
166+
167+ public static void removePresetCacheWithGuildId (long guildId ) {
168+ GUILD_PRESET_CACHE .forEach ((uuid , presets ) -> {
169+ presets .entrySet ().removeIf (entry -> entry .getValue ().getValue () == guildId );
170+ if (presets .isEmpty ()) {
171+ GUILD_PRESET_CACHE .remove (uuid );
172+ }
173+ });
174+ }
175+
176+ private static void setPresetCache (@ NotNull UUID uuid , int presetNumber , long guildId ) {
177+ Map <Integer , Map .Entry <Long , Long >> presets = GUILD_PRESET_CACHE .computeIfAbsent (uuid , k -> new ConcurrentHashMap <>());
178+ presets .put (presetNumber , new AbstractMap .SimpleImmutableEntry <>(System .currentTimeMillis () + GUILD_PRESET_CACHE_TTL , guildId ));
179+ }
180+
181+ private static long getPresetGuildId (@ NotNull UUID uuid , int presetNumber ) {
182+ Map <Integer , Map .Entry <Long , Long >> presets = GUILD_PRESET_CACHE .computeIfAbsent (uuid , k -> new ConcurrentHashMap <>());
183+ Map .Entry <Long , Long > entry = presets .get (presetNumber );
184+ if (entry != null && entry .getKey () > System .currentTimeMillis ()) {
185+ return entry .getValue ();
186+ }
187+ long guildId = -1L ;
188+ try {
189+ guildId = DatabaseManager .get ().getPrepareStatement ("SELECT `guild_id` FROM `guild_presets` WHERE `uuid` = ? AND `number` = ?" , stmt -> {
190+ stmt .setString (1 , uuid .toString ());
191+ stmt .setInt (2 , presetNumber );
192+ try (ResultSet rs = stmt .executeQuery ()) {
193+ if (rs .next ()) {
194+ return rs .getLong ("guild_id" );
195+ }
196+ }
197+ return -1L ;
198+ });
199+ } catch (SQLException e ) {
200+ Logger .getCurrentLogger ().error ("Failed to load guild preset {} for {}" , presetNumber , uuid , e );
201+ }
202+ presets .put (presetNumber , new AbstractMap .SimpleImmutableEntry <>(System .currentTimeMillis () + GUILD_PRESET_CACHE_TTL , guildId ));
203+ return guildId ;
204+ }
205+
145206 private final VelocityPlugin plugin ;
146207
147208 public GuildCommand (@ NotNull VelocityPlugin plugin ) {
@@ -150,7 +211,7 @@ public GuildCommand(@NotNull VelocityPlugin plugin) {
150211
151212 @ Override
152213 public @ NotNull LiteralArgumentBuilder <CommandSource > createBuilder () {
153- return literal (COMMAND_NAME )
214+ LiteralArgumentBuilder < CommandSource > builder = literal (COMMAND_NAME )
154215 .requires (source -> source instanceof Player && source .hasPermission ("interchat.guild" ))
155216 // everyone
156217 .then (literal ("create" )
@@ -163,10 +224,10 @@ public GuildCommand(@NotNull VelocityPlugin plugin) {
163224 .then (literal ("format" )
164225 .requires (source -> source .hasPermission ("interchat.guild.format" ))
165226 .then (argument ("format" , StringArgumentType .greedyString ())
166- .suggests ((context , builder ) -> {
227+ .suggests ((context , b ) -> {
167228 // suggest variables
168229 String last = context .getLastChild ().getInput ().substring (context .getLastChild ().getInput ().lastIndexOf (' ' ) + 1 );
169- FORMAT_VARIABLES .stream ().filter (s -> s .startsWith (last )).forEach (builder ::suggest );
230+ FORMAT_VARIABLES .stream ().filter (s -> s .startsWith (last )).forEach (b ::suggest );
170231
171232 // format preview
172233 UUID uuid = ((Player ) context .getSource ()).getUniqueId ();
@@ -186,7 +247,7 @@ public GuildCommand(@NotNull VelocityPlugin plugin) {
186247 "テスト" ,
187248 VelocityPlugin .getPlugin ().getServerAlias ()
188249 );
189- return builder .suggest (formatted .replace ('&' , '§' )).buildFuture ();
250+ return b .suggest (formatted .replace ('&' , '§' )).buildFuture ();
190251 })
191252 .executes (ctx -> executeFormat ((Player ) ctx .getSource (), StringArgumentType .getString (ctx , "format" )))
192253 )
@@ -219,7 +280,7 @@ public GuildCommand(@NotNull VelocityPlugin plugin) {
219280 .then (argument ("member" , GuildMemberArgumentType .guildMember ())
220281 .suggests (suggestMembersOfGuild (GuildRole .OWNER ))
221282 .then (argument ("role" , GuildRoleArgumentType .guildRole ())
222- .suggests ((ctx , builder ) -> suggest (Arrays .stream (GuildRole .values ()).map (Enum ::name ).map (String ::toLowerCase ), builder ))
283+ .suggests ((ctx , b ) -> suggest (Arrays .stream (GuildRole .values ()).map (Enum ::name ).map (String ::toLowerCase ), b ))
223284 .executes (ctx ->
224285 executeRole (
225286 (Player ) ctx .getSource (),
@@ -446,6 +507,19 @@ public GuildCommand(@NotNull VelocityPlugin plugin) {
446507 )
447508 )
448509 );
510+ for (int i = 1 ; i <= 9 ; i ++) {
511+ builder .then (buildPresetSetLiteral (i ));
512+ }
513+ return builder ;
514+ }
515+
516+ private static @ NotNull LiteralArgumentBuilder <CommandSource > buildPresetSetLiteral (int presetNumber ) {
517+ return literal (String .valueOf (presetNumber ))
518+ .requires (source -> source .hasPermission ("interchat.guild.select" ))
519+ .then (argument ("guild" , GuildArgumentType .guild ())
520+ .suggests (suggestGuildsOfMember (false ))
521+ .executes (ctx -> executePresetSet ((Player ) ctx .getSource (), presetNumber , GuildArgumentType .get (ctx , "guild" , false )))
522+ );
449523 }
450524
451525 /**
@@ -648,6 +722,38 @@ public static int executeSelect(@NotNull Player player, @NotNull Guild guild) {
648722 return 0 ;
649723 }
650724
725+ private static int executePresetSet (@ NotNull Player player , int presetNumber , @ NotNull Guild guild ) {
726+ try {
727+ guild .getMember (player .getUniqueId ()).join ();
728+ } catch (CompletionException e ) {
729+ player .sendMessage (VMessages .formatComponent (player , "command.error.unknown_guild" , guild .name ()).color (NamedTextColor .RED ));
730+ return 0 ;
731+ }
732+ try {
733+ DatabaseManager .get ().query ("INSERT INTO `guild_presets` (`uuid`, `number`, `guild_id`) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE `guild_id` = VALUES(`guild_id`)" , stmt -> {
734+ stmt .setString (1 , player .getUniqueId ().toString ());
735+ stmt .setInt (2 , presetNumber );
736+ stmt .setLong (3 , guild .id ());
737+ stmt .executeUpdate ();
738+ });
739+ setPresetCache (player .getUniqueId (), presetNumber , guild .id ());
740+ player .sendMessage (VMessages .formatComponent (player , "command.guild.preset.set.success" , presetNumber , guild .name ()).color (NamedTextColor .GREEN ));
741+ } catch (SQLException e ) {
742+ Logger .getCurrentLogger ().error ("Failed to set guild preset {} for {}" , presetNumber , player .getUniqueId (), e );
743+ player .sendMessage (VMessages .formatComponent (player , "command.guild.preset.set.error" ).color (NamedTextColor .RED ));
744+ }
745+ return 0 ;
746+ }
747+
748+ static int executePresetChat (@ NotNull Player player , int presetNumber , @ NotNull String message ) {
749+ long presetGuildId = getPresetGuildId (player .getUniqueId (), presetNumber );
750+ if (presetGuildId == -1 ) {
751+ player .sendMessage (VMessages .formatComponent (player , "command.guild.preset.not_set" , presetNumber , COMMAND_NAME , presetNumber ).color (NamedTextColor .RED ));
752+ return 0 ;
753+ }
754+ return executeChat (player , message , presetGuildId );
755+ }
756+
651757 private static int executeRole (@ NotNull Player player , @ NotNull GuildMember member , @ NotNull GuildRole role ) {
652758 long selectedGuild = ensureSelected (player );
653759 if (selectedGuild == -1 ) return 0 ;
0 commit comments