diff --git a/API/api/pom.xml b/API/api/pom.xml index 9a5a4d5..130ccb8 100644 --- a/API/api/pom.xml +++ b/API/api/pom.xml @@ -5,7 +5,7 @@ tc.oc api-parent ../pom.xml - 1.11-SNAPSHOT + 1.12.2-SNAPSHOT api diff --git a/API/api/src/main/java/tc/oc/api/ApiManifest.java b/API/api/src/main/java/tc/oc/api/ApiManifest.java index 2432922..3d2b3d1 100644 --- a/API/api/src/main/java/tc/oc/api/ApiManifest.java +++ b/API/api/src/main/java/tc/oc/api/ApiManifest.java @@ -1,7 +1,9 @@ package tc.oc.api; +import tc.oc.api.chat.ChatModelManifest; import tc.oc.api.document.DocumentsManifest; import tc.oc.api.engagement.EngagementModelManifest; +import tc.oc.api.friendships.FriendshipModelManifest; import tc.oc.api.games.GameModelManifest; import tc.oc.api.http.HttpManifest; import tc.oc.api.maps.MapModelManifest; @@ -44,5 +46,7 @@ public final class ApiManifest extends HybridManifest { install(new WhisperModelManifest()); install(new TrophyModelManifest()); install(new TournamentModelManifest()); + install(new FriendshipModelManifest()); + install(new ChatModelManifest()); } } diff --git a/API/api/src/main/java/tc/oc/api/chat/ChatModelManifest.java b/API/api/src/main/java/tc/oc/api/chat/ChatModelManifest.java new file mode 100644 index 0000000..977ab62 --- /dev/null +++ b/API/api/src/main/java/tc/oc/api/chat/ChatModelManifest.java @@ -0,0 +1,16 @@ +package tc.oc.api.chat; + +import tc.oc.api.docs.Chat; +import tc.oc.api.docs.virtual.ChatDoc; +import tc.oc.api.model.ModelBinders; +import tc.oc.commons.core.inject.HybridManifest; + +public class ChatModelManifest extends HybridManifest implements ModelBinders { + + @Override + protected void configure() { + bindModel(Chat.class, ChatDoc.Partial.class, model -> { + model.bindDefaultService().to(model.nullService()); + }); + } +} diff --git a/API/api/src/main/java/tc/oc/api/docs/Chat.java b/API/api/src/main/java/tc/oc/api/docs/Chat.java new file mode 100644 index 0000000..0356ea5 --- /dev/null +++ b/API/api/src/main/java/tc/oc/api/docs/Chat.java @@ -0,0 +1,7 @@ +package tc.oc.api.docs; + +import tc.oc.api.annotations.Serialize; +import tc.oc.api.docs.virtual.ChatDoc; + +@Serialize +public interface Chat extends ChatDoc.Complete {} diff --git a/API/api/src/main/java/tc/oc/api/docs/Friendship.java b/API/api/src/main/java/tc/oc/api/docs/Friendship.java new file mode 100644 index 0000000..95df429 --- /dev/null +++ b/API/api/src/main/java/tc/oc/api/docs/Friendship.java @@ -0,0 +1,5 @@ +package tc.oc.api.docs; + +import tc.oc.api.docs.virtual.FriendshipDoc; + +public interface Friendship extends FriendshipDoc.Complete {} diff --git a/API/api/src/main/java/tc/oc/api/docs/virtual/ChatDoc.java b/API/api/src/main/java/tc/oc/api/docs/virtual/ChatDoc.java new file mode 100644 index 0000000..aac2c7c --- /dev/null +++ b/API/api/src/main/java/tc/oc/api/docs/virtual/ChatDoc.java @@ -0,0 +1,52 @@ +package tc.oc.api.docs.virtual; + +import tc.oc.api.annotations.Serialize; +import tc.oc.api.docs.PlayerId; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.time.Instant; + +public interface ChatDoc { + interface Partial extends PartialModel {} + + @Serialize + interface Base extends Model, Partial { + @Nonnull String message(); + @Nonnull String server_id(); + @Nullable String match_id(); + @Nonnull Type type(); + @Nonnull Instant sent_at(); + @Nullable Broadcast broadcast(); + } + + @Serialize + interface Broadcast extends Partial { + @Nonnull Destination destination(); + @Nullable String id(); + } + + @Serialize + interface Creation extends Base { + @Nullable String sender_id(); + } + + @Serialize + interface Complete extends Base { + @Nullable PlayerId sender(); + } + + enum Type { + TEAM(true), SERVER(true), ADMIN(false), BROADCAST(false); + + public boolean batchUpdate; + + Type(boolean batchUpdate) { + this.batchUpdate = batchUpdate; + } + } + + enum Destination { + SERVER, FAMILY, GAME, NETWORK, GLOBAL + } +} diff --git a/API/api/src/main/java/tc/oc/api/docs/virtual/FriendshipDoc.java b/API/api/src/main/java/tc/oc/api/docs/virtual/FriendshipDoc.java new file mode 100644 index 0000000..b72f4c0 --- /dev/null +++ b/API/api/src/main/java/tc/oc/api/docs/virtual/FriendshipDoc.java @@ -0,0 +1,23 @@ +package tc.oc.api.docs.virtual; + +import tc.oc.api.annotations.Serialize; +import tc.oc.api.docs.PlayerId; + +import javax.annotation.Nullable; +import java.time.Instant; + +public interface FriendshipDoc { + + interface Partial extends PartialModel {} + + @Serialize + interface Complete extends Model, Partial { + Instant sent_date(); + @Nullable Instant decision_date(); + PlayerId friender(); + PlayerId friended(); + boolean undecided(); + boolean accepted(); + boolean rejected(); + } +} diff --git a/API/api/src/main/java/tc/oc/api/docs/virtual/MapDoc.java b/API/api/src/main/java/tc/oc/api/docs/virtual/MapDoc.java index 24983c2..3bb0186 100644 --- a/API/api/src/main/java/tc/oc/api/docs/virtual/MapDoc.java +++ b/API/api/src/main/java/tc/oc/api/docs/virtual/MapDoc.java @@ -43,7 +43,7 @@ public interface MapDoc extends Model { enum Genre { OBJECTIVES, DEATHMATCH, OTHER } Genre genre(); - enum Gamemode { tdm, ctw, ctf, dtc, dtm, ad, koth, blitz, rage, scorebox, arcade, gs, ffa, mixed, skywars, survival } + enum Gamemode { tdm, ctw, ctf, dtc, dtm, ad, koth, blitz, rage, scorebox, arcade, gs, ffa, mixed, skywars, survival, payload } Set gamemode(); List teams(); diff --git a/API/api/src/main/java/tc/oc/api/docs/virtual/MatchDoc.java b/API/api/src/main/java/tc/oc/api/docs/virtual/MatchDoc.java index 01aea88..f52da24 100644 --- a/API/api/src/main/java/tc/oc/api/docs/virtual/MatchDoc.java +++ b/API/api/src/main/java/tc/oc/api/docs/virtual/MatchDoc.java @@ -30,11 +30,7 @@ public interface MatchDoc extends Model { Collection winning_team_ids(); Collection winning_user_ids(); - enum Mutation { - BLITZ, UHC, EXPLOSIVES, NO_FALL, MOBS, STRENGTH, DOUBLE_JUMP, INVISIBILITY, LIGHTNING, RAGE, ELYTRA; - } - - Set mutations(); + Set mutations(); @Serialize interface Team extends MapDoc.Team, CompetitorDoc { diff --git a/API/api/src/main/java/tc/oc/api/docs/virtual/PunishmentDoc.java b/API/api/src/main/java/tc/oc/api/docs/virtual/PunishmentDoc.java index d1c3cd8..4896b8e 100644 --- a/API/api/src/main/java/tc/oc/api/docs/virtual/PunishmentDoc.java +++ b/API/api/src/main/java/tc/oc/api/docs/virtual/PunishmentDoc.java @@ -22,6 +22,7 @@ public interface PunishmentDoc { boolean silent(); boolean automatic(); boolean active(); + boolean off_record(); } @Serialize @@ -29,7 +30,6 @@ public interface PunishmentDoc { @Nullable String punisher_id(); @Nullable String punished_id(); @Nullable Type type(); - boolean off_record(); } @Serialize diff --git a/API/api/src/main/java/tc/oc/api/docs/virtual/ServerDoc.java b/API/api/src/main/java/tc/oc/api/docs/virtual/ServerDoc.java index c646e22..9348274 100644 --- a/API/api/src/main/java/tc/oc/api/docs/virtual/ServerDoc.java +++ b/API/api/src/main/java/tc/oc/api/docs/virtual/ServerDoc.java @@ -50,10 +50,15 @@ public interface ServerDoc { } @Serialize - interface CurrentPort extends Partial { + interface Port extends Partial { Integer current_port(); } + @Serialize + interface Ip extends Partial { + String ip(); + } + @Serialize interface Online extends Partial { boolean online(); @@ -95,7 +100,7 @@ public interface ServerDoc { * Startup info sent to the API */ @Serialize - interface Startup extends Online, CurrentPort { + interface Startup extends Online, Port { @Nullable DeployInfo deploy_info(); Map plugin_versions(); Set protocol_versions(); @@ -105,7 +110,8 @@ public interface ServerDoc { * Startup info received from the API */ @Serialize - interface Configuration extends Partial { + interface Configuration extends Rotations { + String domain(); String settings_profile(); Map operators(); @Nullable Team team(); @@ -116,10 +122,10 @@ public interface ServerDoc { Visibility startup_visibility(); boolean whitelist_enabled(); boolean waiting_room(); - @Nullable String resource_pack_url(); @Nullable String resource_pack_sha1(); boolean resource_pack_fast_update(); + @Nullable String cross_server_profile(); } @Serialize @@ -138,7 +144,18 @@ public interface ServerDoc { @Serialize interface Mutation extends Partial { - Set queued_mutations(); + Set queued_mutations(); + } + + @Serialize + interface Rotations extends Partial { + List rotations(); + } + + @Serialize + interface Rotation extends Document { + String name(); + String next_map_id(); } /** diff --git a/API/api/src/main/java/tc/oc/api/docs/virtual/SessionDoc.java b/API/api/src/main/java/tc/oc/api/docs/virtual/SessionDoc.java index 990bc41..acd265d 100644 --- a/API/api/src/main/java/tc/oc/api/docs/virtual/SessionDoc.java +++ b/API/api/src/main/java/tc/oc/api/docs/virtual/SessionDoc.java @@ -14,6 +14,7 @@ public interface SessionDoc { interface Complete extends Model, Partial { String family_id(); String server_id(); + @Nullable String version(); PlayerId user(); @Nullable String nickname(); @Nullable String nickname_lower(); diff --git a/API/api/src/main/java/tc/oc/api/docs/virtual/UserDoc.java b/API/api/src/main/java/tc/oc/api/docs/virtual/UserDoc.java index f9d5ffb..760c457 100644 --- a/API/api/src/main/java/tc/oc/api/docs/virtual/UserDoc.java +++ b/API/api/src/main/java/tc/oc/api/docs/virtual/UserDoc.java @@ -1,6 +1,5 @@ package tc.oc.api.docs.virtual; -import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Map; @@ -44,6 +43,11 @@ public interface UserDoc { List trophy_ids(); } + @Serialize + interface Channel extends Partial { + @Nonnull ChatDoc.Type chat_channel(); + } + interface License { @Serialize @@ -80,10 +84,14 @@ public interface UserDoc { * Stuff we get from the API on login, and keep around for plugins to use */ @Serialize - interface Login extends Identity, Locale, Trophies, License.Complete { + interface Login extends Identity, Locale, Trophies, DefaultServer, FriendTokens, DeathScreen, License.Complete, Channel { int raindrops(); + int maptokens(); + int mutationtokens(); String mc_last_sign_in_ip(); @Nullable Date trial_expires_at(); + @Nullable Instant nickname_updated_at(); + Map>> stats_value(); Map> mc_permissions_by_realm(); Map> mc_settings_by_profile(); Map classes(); @@ -110,4 +118,20 @@ public interface UserDoc { interface ResourcePackResponse extends Partial { UserDoc.ResourcePackStatus resource_pack_status(); } + + @Serialize + interface DefaultServer extends Partial { + @Nullable String default_server_id(); + } + + @Serialize + interface DeathScreen extends Partial { + String death_screen(); + } + + @Serialize + interface FriendTokens extends Partial { + int friend_tokens_limit(); + int friend_tokens_concurrent(); + } } diff --git a/API/api/src/main/java/tc/oc/api/friendships/FriendshipModelManifest.java b/API/api/src/main/java/tc/oc/api/friendships/FriendshipModelManifest.java new file mode 100644 index 0000000..561b4c1 --- /dev/null +++ b/API/api/src/main/java/tc/oc/api/friendships/FriendshipModelManifest.java @@ -0,0 +1,20 @@ +package tc.oc.api.friendships; + +import com.google.inject.multibindings.OptionalBinder; +import tc.oc.api.docs.Friendship; +import tc.oc.api.docs.virtual.FriendshipDoc; +import tc.oc.api.model.ModelBinders; +import tc.oc.commons.core.inject.HybridManifest; + +public class FriendshipModelManifest extends HybridManifest implements ModelBinders { + + @Override + protected void configure() { + bindModel(Friendship.class, FriendshipDoc.Partial.class, model -> { + model.bindService().to(FriendshipService.class); + }); + + OptionalBinder.newOptionalBinder(publicBinder(), FriendshipService.class) + .setDefault().to(NullFriendshipService.class); + } +} diff --git a/API/api/src/main/java/tc/oc/api/friendships/FriendshipRequest.java b/API/api/src/main/java/tc/oc/api/friendships/FriendshipRequest.java new file mode 100644 index 0000000..ef43863 --- /dev/null +++ b/API/api/src/main/java/tc/oc/api/friendships/FriendshipRequest.java @@ -0,0 +1,19 @@ +package tc.oc.api.friendships; + +import tc.oc.api.annotations.Serialize; +import tc.oc.api.docs.virtual.Document; + +import javax.annotation.Nullable; + +@Serialize +public interface FriendshipRequest extends Document { + String friender_id(); + @Nullable String friended_id(); + + static FriendshipRequest create(String friender_id, @Nullable String friended_id) { + return new FriendshipRequest() { + public String friender_id() { return friender_id; } + public String friended_id() { return friended_id; } + }; + } +} diff --git a/API/api/src/main/java/tc/oc/api/friendships/FriendshipResponse.java b/API/api/src/main/java/tc/oc/api/friendships/FriendshipResponse.java new file mode 100644 index 0000000..507be89 --- /dev/null +++ b/API/api/src/main/java/tc/oc/api/friendships/FriendshipResponse.java @@ -0,0 +1,15 @@ +package tc.oc.api.friendships; + +import tc.oc.api.annotations.Serialize; +import tc.oc.api.docs.Friendship; +import tc.oc.api.docs.virtual.Document; + +import javax.annotation.Nullable; +import java.util.List; + +@Serialize +public interface FriendshipResponse extends Document { + boolean success(); + @Nullable String error(); + @Nullable List friendships(); +} diff --git a/API/api/src/main/java/tc/oc/api/friendships/FriendshipService.java b/API/api/src/main/java/tc/oc/api/friendships/FriendshipService.java new file mode 100644 index 0000000..4c83337 --- /dev/null +++ b/API/api/src/main/java/tc/oc/api/friendships/FriendshipService.java @@ -0,0 +1,16 @@ +package tc.oc.api.friendships; + +import com.google.common.util.concurrent.ListenableFuture; +import tc.oc.api.docs.Friendship; +import tc.oc.api.docs.virtual.FriendshipDoc; +import tc.oc.api.model.ModelService; + +public interface FriendshipService extends ModelService { + + ListenableFuture create(FriendshipRequest request); + + ListenableFuture destroy(FriendshipRequest request); + + ListenableFuture list(FriendshipRequest request); + +} diff --git a/API/api/src/main/java/tc/oc/api/friendships/NullFriendshipService.java b/API/api/src/main/java/tc/oc/api/friendships/NullFriendshipService.java new file mode 100644 index 0000000..7c4f47f --- /dev/null +++ b/API/api/src/main/java/tc/oc/api/friendships/NullFriendshipService.java @@ -0,0 +1,26 @@ +package tc.oc.api.friendships; + +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import tc.oc.api.docs.Friendship; +import tc.oc.api.docs.virtual.FriendshipDoc; +import tc.oc.api.exceptions.NotFound; +import tc.oc.api.model.NullModelService; + +public class NullFriendshipService extends NullModelService implements FriendshipService { + + @Override + public ListenableFuture create(FriendshipRequest request) { + return Futures.immediateFailedFuture(new NotFound()); + } + + @Override + public ListenableFuture destroy(FriendshipRequest request) { + return Futures.immediateFailedFuture(new NotFound()); + } + + @Override + public ListenableFuture list(FriendshipRequest request) { + return Futures.immediateFailedFuture(new NotFound()); + } +} diff --git a/API/api/src/main/java/tc/oc/api/message/MessagesManifest.java b/API/api/src/main/java/tc/oc/api/message/MessagesManifest.java index ce1b6d8..4a59f6b 100644 --- a/API/api/src/main/java/tc/oc/api/message/MessagesManifest.java +++ b/API/api/src/main/java/tc/oc/api/message/MessagesManifest.java @@ -13,6 +13,8 @@ import tc.oc.api.message.types.PlayGameRequest; import tc.oc.api.message.types.PlayerTeleportRequest; import tc.oc.api.message.types.Reply; import tc.oc.api.message.types.UpdateMultiResponse; +import tc.oc.api.message.types.UseServerRequest; +import tc.oc.api.message.types.UseServerResponse; import tc.oc.api.servers.ServerSearchRequest; import tc.oc.api.sessions.BadNickname; import tc.oc.api.sessions.SessionChange; @@ -47,5 +49,8 @@ public class MessagesManifest extends HybridManifest { messages.register(PlayGameRequest.class); messages.register(CycleRequest.class); messages.register(CycleResponse.class); + + messages.register(UseServerRequest.class); + messages.register(UseServerResponse.class); } } diff --git a/API/api/src/main/java/tc/oc/api/message/types/UseServerRequest.java b/API/api/src/main/java/tc/oc/api/message/types/UseServerRequest.java new file mode 100644 index 0000000..33c0728 --- /dev/null +++ b/API/api/src/main/java/tc/oc/api/message/types/UseServerRequest.java @@ -0,0 +1,14 @@ +package tc.oc.api.message.types; + +import javax.annotation.Nonnull; +import tc.oc.api.annotations.Serialize; +import tc.oc.api.message.Message; +import tc.oc.api.queue.MessageDefaults; + +@Serialize +@MessageDefaults.RoutingKey("use_server") +@MessageDefaults.ExpirationMillis(10000) +public interface UseServerRequest extends Message { + @Nonnull String user_id(); + @Nonnull String server_name(); +} diff --git a/API/api/src/main/java/tc/oc/api/message/types/UseServerResponse.java b/API/api/src/main/java/tc/oc/api/message/types/UseServerResponse.java new file mode 100644 index 0000000..52f4b89 --- /dev/null +++ b/API/api/src/main/java/tc/oc/api/message/types/UseServerResponse.java @@ -0,0 +1,28 @@ +package tc.oc.api.message.types; + +import javax.annotation.Nullable; +import tc.oc.api.annotations.Serialize; + +@Serialize +public interface UseServerResponse extends Reply { + String server_name(); + boolean now(); + + UseServerResponse EMPTY = new UseServerResponse() { + @Override public String server_name() { + return "default"; + } + + @Override public boolean success() { + return true; + } + + @Nullable @Override public String error() { + return null; + } + + @Override public boolean now() { + return false; + } + }; +} diff --git a/API/api/src/main/java/tc/oc/api/reports/ReportSearchRequest.java b/API/api/src/main/java/tc/oc/api/reports/ReportSearchRequest.java index 1066c14..22aee40 100644 --- a/API/api/src/main/java/tc/oc/api/reports/ReportSearchRequest.java +++ b/API/api/src/main/java/tc/oc/api/reports/ReportSearchRequest.java @@ -1,6 +1,6 @@ package tc.oc.api.reports; -import java.util.Collection; +import javax.annotation.Nonnull; import javax.annotation.Nullable; import tc.oc.api.annotations.Serialize; @@ -15,39 +15,33 @@ import static com.google.common.base.Preconditions.checkState; public class ReportSearchRequest extends FindRequest { @Serialize private final @Nullable String server_id; - @Serialize private final @Nullable Collection family_ids; @Serialize private final @Nullable String user_id; + @Serialize private final boolean cross_server; private final int page, perPage; - private ReportSearchRequest(String server_id, Collection family_ids, String user_id, int page, int perPage) { + private ReportSearchRequest(String server_id, String user_id, boolean cross_server, int page, int perPage) { checkArgument(page > 0); checkArgument(perPage > 0); this.server_id = server_id; - this.family_ids = family_ids; this.user_id = user_id; + this.cross_server = cross_server; this.page = page; this.perPage = perPage; } public static ReportSearchRequest create(int page, int perPage) { - return new ReportSearchRequest(null, null, null, page, perPage); + return new ReportSearchRequest(null, null, false, page, perPage); } - public ReportSearchRequest forServer(ServerDoc.Identity server) { - checkState(server_id == null); - return new ReportSearchRequest(server._id(), null, null, page, perPage); - } - - public ReportSearchRequest forFamilies(Collection familyIds) { - checkState(family_ids == null); - return new ReportSearchRequest(null, familyIds, null, page, perPage); + public ReportSearchRequest forServer(ServerDoc.Identity server, boolean cross_server) { + return new ReportSearchRequest(server._id(), null, cross_server, page, perPage); } public ReportSearchRequest forPlayer(PlayerId playerId) { checkState(user_id == null); - return new ReportSearchRequest(server_id, family_ids, playerId._id(), page, perPage); + return new ReportSearchRequest(server_id, playerId._id(), true, page, perPage); } @Override diff --git a/API/api/src/main/java/tc/oc/api/serialization/TypeAdaptersManifest.java b/API/api/src/main/java/tc/oc/api/serialization/TypeAdaptersManifest.java index 6418b96..f8ff35b 100644 --- a/API/api/src/main/java/tc/oc/api/serialization/TypeAdaptersManifest.java +++ b/API/api/src/main/java/tc/oc/api/serialization/TypeAdaptersManifest.java @@ -33,7 +33,5 @@ public class TypeAdaptersManifest extends Manifest { gson.bindAdapter(new TypeLiteral>(){}) .to(new TypeLiteral>(){}); - gson.bindAdapter(new TypeLiteral>(){}) - .to(new TypeLiteral>(){}); } } diff --git a/API/api/src/main/java/tc/oc/api/servers/NullServerService.java b/API/api/src/main/java/tc/oc/api/servers/NullServerService.java index fa6e11a..b60a867 100644 --- a/API/api/src/main/java/tc/oc/api/servers/NullServerService.java +++ b/API/api/src/main/java/tc/oc/api/servers/NullServerService.java @@ -4,6 +4,8 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import tc.oc.api.docs.Server; import tc.oc.api.docs.virtual.ServerDoc; +import tc.oc.api.message.types.UseServerRequest; +import tc.oc.api.message.types.UseServerResponse; import tc.oc.api.model.NullModelService; public class NullServerService extends NullModelService implements ServerService { @@ -12,4 +14,8 @@ public class NullServerService extends NullModelService doBungeeMetric(BungeeMetricRequest request) { return Futures.immediateFuture(null); } + + @Override public ListenableFuture requestServer(UseServerRequest request) { + return Futures.immediateFuture(UseServerResponse.EMPTY); + } } diff --git a/API/api/src/main/java/tc/oc/api/servers/ServerService.java b/API/api/src/main/java/tc/oc/api/servers/ServerService.java index d844c89..03fa73c 100644 --- a/API/api/src/main/java/tc/oc/api/servers/ServerService.java +++ b/API/api/src/main/java/tc/oc/api/servers/ServerService.java @@ -3,9 +3,13 @@ package tc.oc.api.servers; import com.google.common.util.concurrent.ListenableFuture; import tc.oc.api.docs.Server; import tc.oc.api.docs.virtual.ServerDoc; +import tc.oc.api.message.types.UseServerRequest; +import tc.oc.api.message.types.UseServerResponse; import tc.oc.api.model.ModelService; public interface ServerService extends ModelService { ListenableFuture doBungeeMetric(BungeeMetricRequest request); + + ListenableFuture requestServer(UseServerRequest request); } diff --git a/API/api/src/main/java/tc/oc/api/servers/ServerStore.java b/API/api/src/main/java/tc/oc/api/servers/ServerStore.java index 3105800..cc0700a 100644 --- a/API/api/src/main/java/tc/oc/api/servers/ServerStore.java +++ b/API/api/src/main/java/tc/oc/api/servers/ServerStore.java @@ -23,8 +23,11 @@ import static com.google.common.base.Preconditions.checkArgument; @Singleton public class ServerStore extends ModelStore { + private final SetMultimap byName = HashMultimap.create(); private final Map byBungeeName = new HashMap<>(); private final SetMultimap byRole = HashMultimap.create(); + private final SetMultimap byNetwork = HashMultimap.create(); + private final SetMultimap byFamily = HashMultimap.create(); private final SetMultimap byArenaId = HashMultimap.create(); @Override @@ -32,6 +35,18 @@ public class ServerStore extends ModelStore { return new ServerSearchRequest(); } + public ImmutableSet byName(String name) { + return ImmutableSet.copyOf(byName.get(name)); + } + + public ImmutableSet byNetwork(ServerDoc.Network network) { + return ImmutableSet.copyOf(byNetwork.get(network)); + } + + public ImmutableSet byFamily(String family) { + return ImmutableSet.copyOf(byFamily.get(family)); + } + public @Nullable Server tryBungeeName(String name) { checkArgument(!"default".equals(name), "Cannot lookup lobbies by bungee_name"); return byBungeeName.get(name); @@ -59,10 +74,20 @@ public class ServerStore extends ModelStore { return playerCount; } + public boolean canCommunicate(String serverIdA, String serverIdB) { + if(serverIdA.equals(serverIdB)) return true; + String profileA = byId(serverIdA).cross_server_profile(); + String profileB = byId(serverIdB).cross_server_profile(); + return profileA != null && profileB != null && profileA.equalsIgnoreCase(profileB); + } + @Override protected void unindex(Server doc) { super.unindex(doc); + byName.remove(doc.name(), doc); byRole.remove(doc.role(), doc); + if(doc.network() != null) byNetwork.remove(doc.network(), doc); + if(doc.family() != null) byFamily.remove(doc.family(), doc); if(doc.arena_id() != null) byArenaId.remove(doc.arena_id(), doc); if(doc.bungee_name() != null) byBungeeName.remove(doc.bungee_name()); } @@ -70,7 +95,10 @@ public class ServerStore extends ModelStore { @Override protected void reindex(Server doc) { super.reindex(doc); + byName.put(doc.name(), doc); byRole.put(doc.role(), doc); + if(doc.network() != null) byNetwork.put(doc.network(), doc); + if(doc.family() != null) byFamily.put(doc.family(), doc); if(doc.arena_id() != null) byArenaId.put(doc.arena_id(), doc); if(doc.bungee_name() != null) byBungeeName.put(doc.bungee_name(), doc); } diff --git a/API/api/src/main/java/tc/oc/api/sessions/BadNickname.java b/API/api/src/main/java/tc/oc/api/sessions/BadNickname.java index a47476c..be8827a 100644 --- a/API/api/src/main/java/tc/oc/api/sessions/BadNickname.java +++ b/API/api/src/main/java/tc/oc/api/sessions/BadNickname.java @@ -5,6 +5,6 @@ import tc.oc.api.message.types.Reply; @Serialize public interface BadNickname extends Reply { - enum Problem { TAKEN, INVALID } + enum Problem { TAKEN, INVALID, THROTTLE } Problem problem(); } diff --git a/API/api/src/main/java/tc/oc/api/sessions/SessionStartRequest.java b/API/api/src/main/java/tc/oc/api/sessions/SessionStartRequest.java index 408d20c..6f364a3 100644 --- a/API/api/src/main/java/tc/oc/api/sessions/SessionStartRequest.java +++ b/API/api/src/main/java/tc/oc/api/sessions/SessionStartRequest.java @@ -15,5 +15,7 @@ public interface SessionStartRequest extends Document { InetAddress ip(); + String version(); + @Nullable String previous_session_id(); } diff --git a/API/api/src/main/java/tc/oc/api/users/ChangeGroupRequest.java b/API/api/src/main/java/tc/oc/api/users/ChangeGroupRequest.java new file mode 100644 index 0000000..86ede32 --- /dev/null +++ b/API/api/src/main/java/tc/oc/api/users/ChangeGroupRequest.java @@ -0,0 +1,14 @@ +package tc.oc.api.users; + +import tc.oc.api.annotations.Serialize; +import tc.oc.api.docs.virtual.Document; + +import javax.annotation.Nullable; +import java.time.Instant; + +@Serialize +public interface ChangeGroupRequest extends Document { + String group(); + String type(); + @Nullable Instant end(); +} diff --git a/API/api/src/main/java/tc/oc/api/users/CreditTokensRequest.java b/API/api/src/main/java/tc/oc/api/users/CreditTokensRequest.java new file mode 100644 index 0000000..fa3e53c --- /dev/null +++ b/API/api/src/main/java/tc/oc/api/users/CreditTokensRequest.java @@ -0,0 +1,31 @@ +package tc.oc.api.users; + +import tc.oc.api.annotations.Serialize; +import tc.oc.api.docs.virtual.Document; + +@Serialize +public interface CreditTokensRequest extends Document { + String type(); + int amount(); + + static CreditTokensRequest raindrops(int amount) { + return new CreditTokensRequest() { + public String type() { return "raindrops"; } + public int amount() { return amount; } + }; + } + + static CreditTokensRequest maps(int amount) { + return new CreditTokensRequest() { + public String type() { return "maptokens"; } + public int amount() { return amount; } + }; + } + + static CreditTokensRequest mutations(int amount) { + return new CreditTokensRequest() { + public String type() { return "mutationtokens"; } + public int amount() { return amount; } + }; + } +} diff --git a/API/api/src/main/java/tc/oc/api/users/FriendJoinRequest.java b/API/api/src/main/java/tc/oc/api/users/FriendJoinRequest.java new file mode 100644 index 0000000..9a572a5 --- /dev/null +++ b/API/api/src/main/java/tc/oc/api/users/FriendJoinRequest.java @@ -0,0 +1,12 @@ +package tc.oc.api.users; + +import tc.oc.api.annotations.Serialize; +import tc.oc.api.docs.virtual.Document; + +import javax.annotation.Nullable; +import java.time.Instant; + +@Serialize +public interface FriendJoinRequest extends Document { + int amount(); +} diff --git a/API/api/src/main/java/tc/oc/api/users/CreditRaindropsRequest.java b/API/api/src/main/java/tc/oc/api/users/FriendJoinResponse.java similarity index 53% rename from API/api/src/main/java/tc/oc/api/users/CreditRaindropsRequest.java rename to API/api/src/main/java/tc/oc/api/users/FriendJoinResponse.java index e08578e..d80063e 100644 --- a/API/api/src/main/java/tc/oc/api/users/CreditRaindropsRequest.java +++ b/API/api/src/main/java/tc/oc/api/users/FriendJoinResponse.java @@ -4,6 +4,7 @@ import tc.oc.api.annotations.Serialize; import tc.oc.api.docs.virtual.Document; @Serialize -public interface CreditRaindropsRequest extends Document { - int raindrops(); +public interface FriendJoinResponse extends Document { + boolean authorized(); + String message(); } diff --git a/API/api/src/main/java/tc/oc/api/users/NullUserService.java b/API/api/src/main/java/tc/oc/api/users/NullUserService.java index f25e10c..f6f16eb 100644 --- a/API/api/src/main/java/tc/oc/api/users/NullUserService.java +++ b/API/api/src/main/java/tc/oc/api/users/NullUserService.java @@ -31,10 +31,20 @@ public class NullUserService extends NullModelService imp } @Override - public ListenableFuture creditRaindrops(UserId userId, CreditRaindropsRequest request) { + public ListenableFuture creditTokens(UserId userId, CreditTokensRequest request) { return Futures.immediateFuture(UserUpdateResponse.FAILURE); } + @Override + public ListenableFuture changeGroup(UserId userId, ChangeGroupRequest request) { + return Futures.immediateFailedFuture(new NotFound()); + } + + @Override + public ListenableFuture joinFriend(UserId userId, FriendJoinRequest request) { + return Futures.immediateFailedFuture(new NotFound()); + } + @Override public ListenableFuture purchaseGizmo(UserId userId, PurchaseGizmoRequest request) { return Futures.immediateFailedFuture(new NotFound()); diff --git a/API/api/src/main/java/tc/oc/api/users/UserService.java b/API/api/src/main/java/tc/oc/api/users/UserService.java index 3d50ac2..6c359d0 100644 --- a/API/api/src/main/java/tc/oc/api/users/UserService.java +++ b/API/api/src/main/java/tc/oc/api/users/UserService.java @@ -19,11 +19,11 @@ public interface UserService extends ModelService { ListenableFuture logout(LogoutRequest request); - default ListenableFuture creditRaindrops(UserId userId, int raindrops) { - return creditRaindrops(userId, () -> raindrops); - } + ListenableFuture creditTokens(UserId userId, CreditTokensRequest request); - ListenableFuture creditRaindrops(UserId userId, CreditRaindropsRequest request); + ListenableFuture changeGroup(UserId userId, ChangeGroupRequest request); + + ListenableFuture joinFriend(UserId userId, FriendJoinRequest request); ListenableFuture purchaseGizmo(UserId userId, PurchaseGizmoRequest request); diff --git a/API/api/src/main/java/tc/oc/api/util/Permissions.java b/API/api/src/main/java/tc/oc/api/util/Permissions.java index 3b2fd0e..6f88c65 100644 --- a/API/api/src/main/java/tc/oc/api/util/Permissions.java +++ b/API/api/src/main/java/tc/oc/api/util/Permissions.java @@ -1,22 +1,25 @@ package tc.oc.api.util; +import com.google.common.collect.Lists; +import tc.oc.minecraft.api.command.CommandSender; + import java.util.Collection; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.function.Function; -public final class Permissions { +public interface Permissions { - private Permissions() {} - - public static final String CONSOLE = "ocn.console"; - public static final String LOGIN = "ocn.login"; - public static final String STAFF = "projectares.staff"; - public static final String OBSERVER = "ocn.observer"; - public static final String PARTICIPANT = "ocn.participant"; - public static final String MAPMAKER = "ocn.mapmaker"; - public static final String DEVELOPER = "ocn.developer"; - public static final String MAPDEV = "pgm.mapdev"; - public static final String MAPERRORS = "pgm.maperrors"; + String CONSOLE = "ocn.console"; + String LOGIN = "ocn.login"; + String STAFF = "projectares.staff"; + String OBSERVER = "ocn.observer"; + String PARTICIPANT = "ocn.participant"; + String MAPMAKER = "ocn.mapmaker"; + String DEVELOPER = "ocn.developer"; + String MAPDEV = "pgm.mapdev"; + String MAPERRORS = "pgm.maperrors"; /** * Merge the given by-realm permissions into a single set of permissions using the given (ordered) realms @@ -24,7 +27,7 @@ public final class Permissions { * @param permsByRealm Permissions, grouped by realm * @return Effective permissions */ - public static Map mergePermissions(Collection realms, Map> permsByRealm) { + static Map mergePermissions(Collection realms, Map> permsByRealm) { Map effectivePerms = new HashMap<>(); for(String realm : realms) { Map perms = permsByRealm.get(realm); @@ -34,4 +37,42 @@ public final class Permissions { } return effectivePerms; } + + /** + * Get a list of enums a {@link CommandSender} has permission to use. + * + * This is useful for enums that correspond to an action. Instead of granting permission + * to a user for each node, they have access to any enum below the highest ordinal node. + * + * enum Trig { + * SOH, CAH, TOA + * } + * + * So if a sender has explicit permission to ocn.foo.cah, the sender has implicit + * permission to use Trig.SOH and Trig.CAH. + * + * @param sender The command sender. + * @param enumClass The class of the enum to get the values from. + * @return List of {@link E}s that the {@param sender} is allowed to use, ascending order based on {@link E#ordinal()}. + */ + static List enumPermissions(CommandSender sender, String base, Class enumClass) { + final List enums = Lists.newArrayList(enumClass.getEnumConstants()); + final Function normalizer = value -> base + "." + value.name().toLowerCase().replaceAll("_", "-"); + final int max = enums.stream() + .filter(value -> sender.hasPermission(normalizer.apply(value))) + .map(E::ordinal) + .max(Integer::compare) + .orElse(-1); + return enums.subList(0, max + 1); + } + + /** + * Get whether a {@link CommandSender} has permission to use a selected {@link E}. + * + * @see #enumPermissions(CommandSender, String, Class) + * @return Whether the {@param sender} has permission. + */ + static boolean hasPermissionForEnum(CommandSender sender, String base, E selected) { + return enumPermissions(sender, base, (Class) selected.getClass()).contains(selected); + } } diff --git a/API/bukkit/pom.xml b/API/bukkit/pom.xml index acfe812..382c54b 100644 --- a/API/bukkit/pom.xml +++ b/API/bukkit/pom.xml @@ -5,7 +5,7 @@ tc.oc api-parent ../pom.xml - 1.11-SNAPSHOT + 1.12.2-SNAPSHOT api-bukkit diff --git a/API/bungee/pom.xml b/API/bungee/pom.xml index 69d78c4..bd56e2c 100644 --- a/API/bungee/pom.xml +++ b/API/bungee/pom.xml @@ -5,7 +5,7 @@ tc.oc api-parent ../pom.xml - 1.11-SNAPSHOT + 1.12.2-SNAPSHOT api-bungee diff --git a/API/minecraft/pom.xml b/API/minecraft/pom.xml index cdb239e..7008a0a 100644 --- a/API/minecraft/pom.xml +++ b/API/minecraft/pom.xml @@ -5,7 +5,7 @@ tc.oc api-parent ../pom.xml - 1.11-SNAPSHOT + 1.12.2-SNAPSHOT api-minecraft diff --git a/API/minecraft/src/main/java/tc/oc/api/minecraft/MinecraftServiceImpl.java b/API/minecraft/src/main/java/tc/oc/api/minecraft/MinecraftServiceImpl.java index b1a78b9..ba94104 100644 --- a/API/minecraft/src/main/java/tc/oc/api/minecraft/MinecraftServiceImpl.java +++ b/API/minecraft/src/main/java/tc/oc/api/minecraft/MinecraftServiceImpl.java @@ -2,6 +2,8 @@ package tc.oc.api.minecraft; import java.io.IOException; import java.lang.reflect.Method; +import java.util.Objects; +import java.util.Optional; import java.util.concurrent.ExecutionException; import java.util.logging.Level; import java.util.logging.Logger; @@ -150,6 +152,14 @@ public class MinecraftServiceImpl implements MinecraftService, MessageListener, handleLocalReconfigure(serverService.update(apiConfiguration.serverId(), startupDocument).get()); logger.info("Connected to API as server." + getLocalServer()._id()); + + if(apiConfiguration.publishIp()) { + String oldIp = server.ip(), newIp = startupDocument.ip(); + if(!Objects.equals(oldIp, newIp)) { + updateLocalServer((ServerDoc.Ip) () -> newIp).get(); + logger.info("Changed ip from " + oldIp + " to " + newIp); + } + } } catch (Exception e) { this.processIntoIOException(e); } diff --git a/API/minecraft/src/main/java/tc/oc/api/minecraft/config/MinecraftApiConfiguration.java b/API/minecraft/src/main/java/tc/oc/api/minecraft/config/MinecraftApiConfiguration.java index 0015605..8893f40 100644 --- a/API/minecraft/src/main/java/tc/oc/api/minecraft/config/MinecraftApiConfiguration.java +++ b/API/minecraft/src/main/java/tc/oc/api/minecraft/config/MinecraftApiConfiguration.java @@ -12,4 +12,6 @@ public interface MinecraftApiConfiguration extends ApiConfiguration { String box(); ServerDoc.Role role(); + + boolean publishIp(); } diff --git a/API/minecraft/src/main/java/tc/oc/api/minecraft/config/MinecraftApiConfigurationImpl.java b/API/minecraft/src/main/java/tc/oc/api/minecraft/config/MinecraftApiConfigurationImpl.java index aab67a3..d8eec5a 100644 --- a/API/minecraft/src/main/java/tc/oc/api/minecraft/config/MinecraftApiConfigurationImpl.java +++ b/API/minecraft/src/main/java/tc/oc/api/minecraft/config/MinecraftApiConfigurationImpl.java @@ -37,6 +37,11 @@ public class MinecraftApiConfigurationImpl implements MinecraftApiConfiguration return ServerDoc.Role.valueOf(config.getString("server.role").toUpperCase()); } + @Override + public boolean publishIp() { + return config.getBoolean("server.publishIp", false); + } + @Override public String primaryQueueName() { return "server." + serverId(); diff --git a/API/minecraft/src/main/java/tc/oc/api/minecraft/model/ModelCommands.java b/API/minecraft/src/main/java/tc/oc/api/minecraft/model/ModelCommands.java index 51c24df..aeed52a 100644 --- a/API/minecraft/src/main/java/tc/oc/api/minecraft/model/ModelCommands.java +++ b/API/minecraft/src/main/java/tc/oc/api/minecraft/model/ModelCommands.java @@ -23,7 +23,7 @@ import tc.oc.commons.core.commands.NestedCommands; import tc.oc.commons.core.formatting.StringUtils; import tc.oc.minecraft.api.command.CommandSender; -class ModelCommands implements NestedCommands { +public class ModelCommands implements NestedCommands { public static class Parent implements Commands { @Command( diff --git a/API/minecraft/src/main/java/tc/oc/api/minecraft/servers/LocalServerDocument.java b/API/minecraft/src/main/java/tc/oc/api/minecraft/servers/LocalServerDocument.java index d3b8eca..a012984 100644 --- a/API/minecraft/src/main/java/tc/oc/api/minecraft/servers/LocalServerDocument.java +++ b/API/minecraft/src/main/java/tc/oc/api/minecraft/servers/LocalServerDocument.java @@ -154,6 +154,11 @@ public class LocalServerDocument extends StartupServerDocument implements Server return null; } + @Override + public String domain() { + return "play.stratus.network"; + } + @Override public String settings_profile() { return "public"; @@ -218,6 +223,11 @@ public class LocalServerDocument extends StartupServerDocument implements Server return false; } + @Override + public String cross_server_profile() { + return null; + } + @Override public Map fake_usernames() { return Collections.emptyMap(); @@ -279,7 +289,12 @@ public class LocalServerDocument extends StartupServerDocument implements Server } @Override - public Set queued_mutations() { + public Set queued_mutations() { return mutations != null ? mutations.queued_mutations() : Collections.emptySet(); } + + @Override + public List rotations() { + return Collections.emptyList(); + } } diff --git a/API/minecraft/src/main/java/tc/oc/api/minecraft/servers/LocalServerService.java b/API/minecraft/src/main/java/tc/oc/api/minecraft/servers/LocalServerService.java index 6551ffe..b3ee0eb 100644 --- a/API/minecraft/src/main/java/tc/oc/api/minecraft/servers/LocalServerService.java +++ b/API/minecraft/src/main/java/tc/oc/api/minecraft/servers/LocalServerService.java @@ -9,12 +9,7 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import tc.oc.api.docs.Server; import tc.oc.api.docs.virtual.ServerDoc; -import tc.oc.api.message.types.FindMultiRequest; -import tc.oc.api.message.types.FindMultiResponse; -import tc.oc.api.message.types.FindRequest; -import tc.oc.api.message.types.PartialModelUpdate; -import tc.oc.api.message.types.UpdateMultiRequest; -import tc.oc.api.message.types.UpdateMultiResponse; +import tc.oc.api.message.types.*; import tc.oc.api.model.NullModelService; import tc.oc.api.servers.BungeeMetricRequest; import tc.oc.api.servers.ServerService; @@ -54,4 +49,8 @@ public class LocalServerService extends NullModelService requestServer(UseServerRequest request) { + return Futures.immediateFuture(UseServerResponse.EMPTY); + } } diff --git a/API/minecraft/src/main/java/tc/oc/api/minecraft/servers/StartupServerDocument.java b/API/minecraft/src/main/java/tc/oc/api/minecraft/servers/StartupServerDocument.java index d6cff41..9b66bfd 100644 --- a/API/minecraft/src/main/java/tc/oc/api/minecraft/servers/StartupServerDocument.java +++ b/API/minecraft/src/main/java/tc/oc/api/minecraft/servers/StartupServerDocument.java @@ -1,9 +1,14 @@ package tc.oc.api.minecraft.servers; +import java.io.BufferedReader; import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; import java.nio.file.Path; import java.util.Map; import java.util.Set; +import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.Nullable; import javax.inject.Inject; @@ -15,6 +20,7 @@ import com.google.common.io.Files; import com.google.gson.Gson; import tc.oc.api.docs.virtual.DeployInfo; import tc.oc.api.docs.virtual.ServerDoc; +import tc.oc.api.minecraft.config.MinecraftApiConfiguration; import tc.oc.commons.core.logging.Loggers; import tc.oc.commons.core.util.Lazy; import tc.oc.minecraft.api.plugin.PluginFinder; @@ -26,6 +32,7 @@ public class StartupServerDocument implements ServerDoc.Startup { @Inject private Gson gson; @Inject private LocalServer minecraftServer; @Inject private PluginFinder pluginFinder; + @Inject private MinecraftApiConfiguration configuration; private Logger logger; @Inject void init(Loggers loggers) { @@ -51,6 +58,16 @@ public class StartupServerDocument implements ServerDoc.Startup { } }); + private final Lazy ip = Lazy.from(() -> { + try { + URL url = new URL("http://checkip.amazonaws.com"); + return new BufferedReader(new InputStreamReader(url.openStream())).readLine(); + } catch(IOException e) { + logger.log(Level.SEVERE, "Unable to find external ip", e); + return minecraftServer.getAddress().getHostName(); + } + }); + @Override public boolean online() { return true; } @@ -70,4 +87,8 @@ public class StartupServerDocument implements ServerDoc.Startup { @Override public Set protocol_versions() { return minecraftServer.getProtocolVersions(); } + + public String ip() { + return ip.get(); + } } diff --git a/API/minecraft/src/main/java/tc/oc/api/minecraft/sessions/LocalSessionFactory.java b/API/minecraft/src/main/java/tc/oc/api/minecraft/sessions/LocalSessionFactory.java index 53433fa..e88a4da 100644 --- a/API/minecraft/src/main/java/tc/oc/api/minecraft/sessions/LocalSessionFactory.java +++ b/API/minecraft/src/main/java/tc/oc/api/minecraft/sessions/LocalSessionFactory.java @@ -13,6 +13,7 @@ import tc.oc.api.docs.Session; import tc.oc.api.docs.UserId; import tc.oc.api.minecraft.users.UserStore; import tc.oc.minecraft.api.entity.Player; +import tc.oc.minecraft.protocol.MinecraftVersion; @Singleton public class LocalSessionFactory { @@ -41,6 +42,13 @@ public class LocalSessionFactory { return localServer._id(); } + @Override + public String version() { + return userStore.byUserId(userId) + .map(player -> MinecraftVersion.describeProtocol(player.getProtocolVersion())) + .orElse(null); + } + @Override public PlayerId user() { return playerId; diff --git a/API/minecraft/src/main/java/tc/oc/api/minecraft/users/LocalUserDocument.java b/API/minecraft/src/main/java/tc/oc/api/minecraft/users/LocalUserDocument.java index 89e0fda..931804b 100644 --- a/API/minecraft/src/main/java/tc/oc/api/minecraft/users/LocalUserDocument.java +++ b/API/minecraft/src/main/java/tc/oc/api/minecraft/users/LocalUserDocument.java @@ -7,12 +7,14 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; +import javax.annotation.Nonnull; import javax.annotation.Nullable; import com.google.common.collect.ImmutableMap; import tc.oc.api.docs.PlayerId; import tc.oc.api.docs.SimplePlayerId; import tc.oc.api.docs.User; +import tc.oc.api.docs.virtual.ChatDoc; import tc.oc.api.docs.virtual.UserDoc; import tc.oc.api.minecraft.servers.DefaultPermissions; @@ -40,6 +42,11 @@ public class LocalUserDocument extends SimplePlayerId implements User { return null; } + @Override + public Instant nickname_updated_at() { + return null; + } + @Override public @Nullable String mc_locale() { return null; @@ -80,6 +87,16 @@ public class LocalUserDocument extends SimplePlayerId implements User { return 0; } + @Override + public int maptokens() { + return 0; + } + + @Override + public int mutationtokens() { + return 0; + } + @Override public String mc_last_sign_in_ip() { return ip; @@ -90,6 +107,11 @@ public class LocalUserDocument extends SimplePlayerId implements User { return null; } + @Override + public Map>> stats_value() { + return Collections.emptyMap(); + } + @Override public Map> mc_permissions_by_realm() { return ImmutableMap.of( @@ -121,4 +143,29 @@ public class LocalUserDocument extends SimplePlayerId implements User { public int enemy_kills() { return 0; } + + @Override + public String default_server_id() { + return null; + } + + @Override + public int friend_tokens_limit() { + return 0; + } + + @Override + public int friend_tokens_concurrent() { + return 1; + } + + @Override + public String death_screen() { + return null; + } + + @Override + public ChatDoc.Type chat_channel() { + return ChatDoc.Type.TEAM; + } } diff --git a/API/minecraft/src/main/java/tc/oc/api/minecraft/users/LocalUserService.java b/API/minecraft/src/main/java/tc/oc/api/minecraft/users/LocalUserService.java index 681af50..2e8c0b6 100644 --- a/API/minecraft/src/main/java/tc/oc/api/minecraft/users/LocalUserService.java +++ b/API/minecraft/src/main/java/tc/oc/api/minecraft/users/LocalUserService.java @@ -18,17 +18,7 @@ import tc.oc.api.docs.virtual.UserDoc; import tc.oc.api.exceptions.NotFound; import tc.oc.api.minecraft.sessions.LocalSessionFactory; import tc.oc.api.model.NullModelService; -import tc.oc.api.users.ChangeClassRequest; -import tc.oc.api.users.ChangeSettingRequest; -import tc.oc.api.users.CreditRaindropsRequest; -import tc.oc.api.users.LoginRequest; -import tc.oc.api.users.LoginResponse; -import tc.oc.api.users.LogoutRequest; -import tc.oc.api.users.PurchaseGizmoRequest; -import tc.oc.api.users.UserSearchRequest; -import tc.oc.api.users.UserSearchResponse; -import tc.oc.api.users.UserService; -import tc.oc.api.users.UserUpdateResponse; +import tc.oc.api.users.*; import tc.oc.commons.core.concurrent.FutureUtils; import tc.oc.minecraft.api.user.UserFinder; @@ -118,8 +108,7 @@ class LocalUserService extends NullModelService implement return Futures.immediateFuture(null); } - @Override - public ListenableFuture creditRaindrops(UserId userId, CreditRaindropsRequest request) { + private ListenableFuture update(UserId userId) { return FutureUtils.mapSync(find(userId), user -> new UserUpdateResponse() { @Override public boolean success() { @@ -133,6 +122,31 @@ class LocalUserService extends NullModelService implement }); } + @Override + public ListenableFuture creditTokens(UserId userId, CreditTokensRequest request) { + return update(userId); + } + + @Override + public ListenableFuture changeGroup(UserId userId, ChangeGroupRequest request) { + return find(userId); + } + + @Override + public ListenableFuture joinFriend(UserId userId, FriendJoinRequest request) { + return Futures.immediateFuture(new FriendJoinResponse() { + @Override + public boolean authorized() { + return false; + } + + @Override + public String message() { + return null; + } + }); + } + @Override public ListenableFuture purchaseGizmo(UserId userId, PurchaseGizmoRequest request) { return find(userId); diff --git a/API/ocn/pom.xml b/API/ocn/pom.xml index 5616e4d..15c7d59 100644 --- a/API/ocn/pom.xml +++ b/API/ocn/pom.xml @@ -5,7 +5,7 @@ tc.oc api-parent ../pom.xml - 1.11-SNAPSHOT + 1.12.2-SNAPSHOT api-ocn diff --git a/API/ocn/src/main/java/tc/oc/api/ocn/OCNFriendshipService.java b/API/ocn/src/main/java/tc/oc/api/ocn/OCNFriendshipService.java new file mode 100644 index 0000000..e6ae7e7 --- /dev/null +++ b/API/ocn/src/main/java/tc/oc/api/ocn/OCNFriendshipService.java @@ -0,0 +1,32 @@ +package tc.oc.api.ocn; + +import com.google.common.util.concurrent.ListenableFuture; +import tc.oc.api.docs.Friendship; +import tc.oc.api.docs.virtual.FriendshipDoc; +import tc.oc.api.friendships.FriendshipRequest; +import tc.oc.api.friendships.FriendshipResponse; +import tc.oc.api.friendships.FriendshipService; +import tc.oc.api.http.HttpOption; +import tc.oc.api.model.HttpModelService; + +import javax.inject.Singleton; + +@Singleton +class OCNFriendshipService extends HttpModelService implements FriendshipService { + + @Override + public ListenableFuture create(FriendshipRequest request) { + return this.client().post(collectionUri("create"), request, FriendshipResponse.class, HttpOption.INFINITE_RETRY); + } + + @Override + public ListenableFuture destroy(FriendshipRequest request) { + return this.client().post(collectionUri("destroy"), request, FriendshipResponse.class, HttpOption.INFINITE_RETRY); + } + + @Override + public ListenableFuture list(FriendshipRequest request) { + return this.client().post(collectionUri("list"), request, FriendshipResponse.class, HttpOption.INFINITE_RETRY); + } + +} diff --git a/API/ocn/src/main/java/tc/oc/api/ocn/OCNModelsManifest.java b/API/ocn/src/main/java/tc/oc/api/ocn/OCNModelsManifest.java index 2559426..dc4f754 100644 --- a/API/ocn/src/main/java/tc/oc/api/ocn/OCNModelsManifest.java +++ b/API/ocn/src/main/java/tc/oc/api/ocn/OCNModelsManifest.java @@ -1,6 +1,7 @@ package tc.oc.api.ocn; import tc.oc.api.docs.Arena; +import tc.oc.api.docs.Chat; import tc.oc.api.docs.Death; import tc.oc.api.docs.Game; import tc.oc.api.docs.Objective; @@ -8,11 +9,13 @@ import tc.oc.api.docs.Participation; import tc.oc.api.docs.Punishment; import tc.oc.api.docs.Report; import tc.oc.api.docs.Trophy; +import tc.oc.api.docs.virtual.ChatDoc; import tc.oc.api.docs.virtual.DeathDoc; import tc.oc.api.docs.virtual.MatchDoc; import tc.oc.api.docs.virtual.PunishmentDoc; import tc.oc.api.docs.virtual.ReportDoc; import tc.oc.api.engagement.EngagementService; +import tc.oc.api.friendships.FriendshipService; import tc.oc.api.games.TicketService; import tc.oc.api.maps.MapService; import tc.oc.api.model.ModelBinders; @@ -46,6 +49,9 @@ public class OCNModelsManifest extends HybridManifest implements ModelBinders { bindModel(Punishment.class, PunishmentDoc.Partial.class, model -> { model.bindService().to(model.httpService()); }); + bindModel(Chat.class, ChatDoc.Partial.class, model -> { + model.bindService().to(model.httpService()); + }); bindModel(MatchDoc.class, model -> { model.bindService().to(model.httpService()); }); @@ -58,7 +64,6 @@ public class OCNModelsManifest extends HybridManifest implements ModelBinders { bindModel(Objective.class, model -> { model.bindService().to(model.httpService()); }); - publicBinder().install(new Manifest() { @Override protected void configure() { // Specialized AMQP services @@ -72,6 +77,7 @@ public class OCNModelsManifest extends HybridManifest implements ModelBinders { forOptional(TournamentService.class).setBinding().to(OCNTournamentService.class); forOptional(UserService.class).setBinding().to(OCNUserService.class); forOptional(WhisperService.class).setBinding().to(OCNWhisperService.class); + forOptional(FriendshipService.class).setBinding().to(OCNFriendshipService.class); } }); } diff --git a/API/ocn/src/main/java/tc/oc/api/ocn/OCNServerService.java b/API/ocn/src/main/java/tc/oc/api/ocn/OCNServerService.java index 12e1a4b..598468c 100644 --- a/API/ocn/src/main/java/tc/oc/api/ocn/OCNServerService.java +++ b/API/ocn/src/main/java/tc/oc/api/ocn/OCNServerService.java @@ -10,8 +10,12 @@ import tc.oc.api.docs.virtual.ServerDoc; import tc.oc.api.http.HttpOption; import tc.oc.api.message.types.FindMultiResponse; import tc.oc.api.message.types.FindRequest; +import tc.oc.api.message.types.UseServerRequest; +import tc.oc.api.message.types.UseServerResponse; import tc.oc.api.model.HttpModelService; import tc.oc.api.queue.QueueQueryService; +import tc.oc.api.queue.Transaction; +import tc.oc.api.queue.Transaction.Factory; import tc.oc.api.servers.BungeeMetricRequest; import tc.oc.api.servers.ServerService; @@ -19,9 +23,11 @@ import tc.oc.api.servers.ServerService; class OCNServerService extends HttpModelService implements ServerService { private final QueueQueryService queryService; + private final Transaction.Factory transactionFactory; - @Inject OCNServerService(QueueQueryService queryService) { + @Inject public OCNServerService(QueueQueryService queryService, Factory transactionFactory) { this.queryService = queryService; + this.transactionFactory = transactionFactory; } @Override @@ -29,6 +35,10 @@ class OCNServerService extends HttpModelService imple return this.client().post("/servers/metric", request, HttpOption.INFINITE_RETRY); } + @Override public ListenableFuture requestServer(UseServerRequest request) { + return transactionFactory.request(request, UseServerResponse.class); + } + @Override public ListenableFuture> all() { return queryService.all(); diff --git a/API/ocn/src/main/java/tc/oc/api/ocn/OCNUserService.java b/API/ocn/src/main/java/tc/oc/api/ocn/OCNUserService.java index ea1b115..1bca138 100644 --- a/API/ocn/src/main/java/tc/oc/api/ocn/OCNUserService.java +++ b/API/ocn/src/main/java/tc/oc/api/ocn/OCNUserService.java @@ -15,17 +15,7 @@ import tc.oc.api.message.types.PlayerTeleportRequest; import tc.oc.api.minecraft.users.UserStore; import tc.oc.api.model.HttpModelService; import tc.oc.api.queue.Exchange; -import tc.oc.api.users.ChangeClassRequest; -import tc.oc.api.users.ChangeSettingRequest; -import tc.oc.api.users.CreditRaindropsRequest; -import tc.oc.api.users.LoginRequest; -import tc.oc.api.users.LoginResponse; -import tc.oc.api.users.LogoutRequest; -import tc.oc.api.users.PurchaseGizmoRequest; -import tc.oc.api.users.UserSearchRequest; -import tc.oc.api.users.UserSearchResponse; -import tc.oc.api.users.UserService; -import tc.oc.api.users.UserUpdateResponse; +import tc.oc.api.users.*; import tc.oc.commons.core.concurrent.FutureUtils; import tc.oc.minecraft.api.entity.Player; @@ -82,13 +72,23 @@ class OCNUserService extends HttpModelService implements } @Override - public ListenableFuture creditRaindrops(UserId userId, CreditRaindropsRequest request) { - return handleUserUpdate(client().post(memberUri(userId, "credit_raindrops"), request, UserUpdateResponse.class, HttpOption.INFINITE_RETRY)); + public ListenableFuture purchaseGizmo(UserId userId, PurchaseGizmoRequest request) { + return handleUpdate(client().post(memberUri(userId, "purchase_gizmo"), request, User.class, HttpOption.INFINITE_RETRY)); } @Override - public ListenableFuture purchaseGizmo(UserId userId, PurchaseGizmoRequest request) { - return handleUpdate(client().post(memberUri(userId, "purchase_gizmo"), request, User.class, HttpOption.INFINITE_RETRY)); + public ListenableFuture creditTokens(UserId userId, CreditTokensRequest request) { + return handleUserUpdate(client().post(memberUri(userId, "credit_tokens"), request, UserUpdateResponse.class, HttpOption.INFINITE_RETRY)); + } + + @Override + public ListenableFuture changeGroup(UserId userId, ChangeGroupRequest request) { + return handleUpdate(client().post(memberUri(userId, "change_group"), request, User.class, HttpOption.INFINITE_RETRY)); + } + + @Override + public ListenableFuture joinFriend(UserId userId, FriendJoinRequest request) { + return client().post(memberUri(userId, "join_friend"), request, FriendJoinResponse.class, HttpOption.INFINITE_RETRY); } @Override diff --git a/API/pom.xml b/API/pom.xml index 05b29cb..b04d1d5 100644 --- a/API/pom.xml +++ b/API/pom.xml @@ -5,7 +5,7 @@ tc.oc ProjectAres ../pom.xml - 1.11-SNAPSHOT + 1.12.2-SNAPSHOT api-parent diff --git a/Commons/bukkit/pom.xml b/Commons/bukkit/pom.xml index 467baae..3897d76 100644 --- a/Commons/bukkit/pom.xml +++ b/Commons/bukkit/pom.xml @@ -8,7 +8,7 @@ commons tc.oc ../pom.xml - 1.11-SNAPSHOT + 1.12.2-SNAPSHOT commons-bukkit @@ -41,18 +41,6 @@ ${project.version} - - com.github.rmsy.Channels - Channels - 1.9-SNAPSHOT - - - org.bukkit - bukkit - - - - me.anxuiz bukkit-settings diff --git a/Commons/bukkit/src/main/java/tc/oc/bukkit/analytics/LatencyReporter.java b/Commons/bukkit/src/main/java/tc/oc/bukkit/analytics/LatencyReporter.java index e45d450..2fe18ca 100644 --- a/Commons/bukkit/src/main/java/tc/oc/bukkit/analytics/LatencyReporter.java +++ b/Commons/bukkit/src/main/java/tc/oc/bukkit/analytics/LatencyReporter.java @@ -2,7 +2,6 @@ package tc.oc.bukkit.analytics; import java.time.Duration; import javax.inject.Inject; - import tc.oc.analytics.Gauge; import tc.oc.analytics.MetricFactory; import tc.oc.api.bukkit.users.OnlinePlayers; diff --git a/Commons/bukkit/src/main/java/tc/oc/bukkit/analytics/TickReporter.java b/Commons/bukkit/src/main/java/tc/oc/bukkit/analytics/TickReporter.java index d1dbbe7..07143d4 100644 --- a/Commons/bukkit/src/main/java/tc/oc/bukkit/analytics/TickReporter.java +++ b/Commons/bukkit/src/main/java/tc/oc/bukkit/analytics/TickReporter.java @@ -3,7 +3,6 @@ package tc.oc.bukkit.analytics; import java.time.Duration; import java.util.concurrent.TimeUnit; import javax.inject.Inject; - import tc.oc.analytics.Count; import tc.oc.analytics.Gauge; import tc.oc.analytics.MetricFactory; diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/CommonsBukkitManifest.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/CommonsBukkitManifest.java index 52db0d5..3fc8cbb 100644 --- a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/CommonsBukkitManifest.java +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/CommonsBukkitManifest.java @@ -1,7 +1,6 @@ package tc.oc.commons.bukkit; import javax.inject.Inject; - import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.chat.TranslatableComponent; import org.bukkit.command.ConsoleCommandSender; @@ -12,7 +11,8 @@ import tc.oc.bukkit.analytics.BukkitPlayerReporter; import tc.oc.bukkit.analytics.LatencyReporter; import tc.oc.bukkit.analytics.TickReporter; import tc.oc.commons.bukkit.broadcast.BroadcastManifest; -import tc.oc.commons.bukkit.channels.AdminChatManifest; +import tc.oc.commons.bukkit.channels.ChannelManifest; +import tc.oc.commons.bukkit.chat.ChatManifest; import tc.oc.commons.bukkit.chat.ComponentRenderContext; import tc.oc.commons.bukkit.chat.ComponentRendererRegistry; import tc.oc.commons.bukkit.chat.ComponentRenderers; @@ -24,6 +24,8 @@ import tc.oc.commons.bukkit.chat.TextComponentRenderer; import tc.oc.commons.bukkit.chat.TranslatableComponentRenderer; import tc.oc.commons.bukkit.chat.UserTextComponent; import tc.oc.commons.bukkit.chat.UserTextComponentRenderer; +import tc.oc.commons.bukkit.commands.GroupCommands; +import tc.oc.commons.bukkit.commands.MiscCommands; import tc.oc.commons.bukkit.commands.PermissionCommands; import tc.oc.commons.bukkit.commands.ServerCommands; import tc.oc.commons.bukkit.commands.ServerVisibilityCommands; @@ -33,6 +35,7 @@ import tc.oc.commons.bukkit.commands.UserCommands; import tc.oc.commons.bukkit.commands.UserFinder; import tc.oc.commons.bukkit.debug.LeakListener; import tc.oc.commons.bukkit.event.targeted.TargetedEventManifest; +import tc.oc.commons.bukkit.flairs.FlairConfiguration; import tc.oc.commons.bukkit.format.ServerFormatter; import tc.oc.commons.bukkit.freeze.PlayerFreezer; import tc.oc.commons.bukkit.inject.BukkitPluginManifest; @@ -60,17 +63,19 @@ import tc.oc.commons.bukkit.nick.PlayerOrder; import tc.oc.commons.bukkit.nick.PlayerOrderCache; import tc.oc.commons.bukkit.punishment.PunishmentManifest; import tc.oc.commons.bukkit.raindrops.RaindropManifest; -import tc.oc.commons.bukkit.report.ReportAnnouncer; -import tc.oc.commons.bukkit.report.ReportCommands; +import tc.oc.commons.bukkit.report.ReportManifest; import tc.oc.commons.bukkit.respack.ResourcePackCommands; import tc.oc.commons.bukkit.respack.ResourcePackListener; import tc.oc.commons.bukkit.respack.ResourcePackManager; import tc.oc.commons.bukkit.restart.RestartCommands; import tc.oc.commons.bukkit.sessions.SessionListener; import tc.oc.commons.bukkit.settings.SettingManifest; +import tc.oc.commons.bukkit.stats.StatsManifest; import tc.oc.commons.bukkit.suspend.SuspendListener; import tc.oc.commons.bukkit.tablist.PlayerTabEntry; import tc.oc.commons.bukkit.tablist.TabRender; +import tc.oc.commons.bukkit.teleport.Navigator; +import tc.oc.commons.bukkit.teleport.NavigatorInterface; import tc.oc.commons.bukkit.teleport.NavigatorManifest; import tc.oc.commons.bukkit.teleport.PlayerServerChanger; import tc.oc.commons.bukkit.teleport.TeleportCommands; @@ -80,6 +85,7 @@ import tc.oc.commons.bukkit.ticket.TicketBooth; import tc.oc.commons.bukkit.ticket.TicketCommands; import tc.oc.commons.bukkit.ticket.TicketDisplay; import tc.oc.commons.bukkit.ticket.TicketListener; +import tc.oc.commons.bukkit.tokens.TokenManifest; import tc.oc.commons.bukkit.trophies.TrophyCase; import tc.oc.commons.bukkit.trophies.TrophyCommands; import tc.oc.commons.bukkit.users.JoinMessageManifest; @@ -105,11 +111,15 @@ public final class CommonsBukkitManifest extends HybridManifest { install(new SettingManifest()); install(new WhisperManifest()); install(new JoinMessageManifest()); - install(new AdminChatManifest()); + install(new ChatManifest()); + install(new ChannelManifest()); install(new BroadcastManifest()); install(new LocalizationManifest()); install(new NavigatorManifest()); install(new RaindropManifest()); + install(new ReportManifest()); + install(new TokenManifest()); + install(new StatsManifest()); install(new PunishmentManifest()); // These are already bound as facets, so they only need to be exposed @@ -119,6 +129,7 @@ public final class CommonsBukkitManifest extends HybridManifest { expose(TicketDisplay.class); expose(TicketListener.class); + bindAndExpose(FlairConfiguration.class); bindAndExpose(PlayerAppearanceChanger.class); bindAndExpose(UserFinder.class); bindAndExpose(Teleporter.class); @@ -155,6 +166,9 @@ public final class CommonsBukkitManifest extends HybridManifest { facets.register(LeakListener.class); facets.register(LocaleListener.class); facets.register(LoginListener.class); + facets.register(MiscCommands.class); + facets.register(Navigator.class); + facets.register(NavigatorInterface.class); facets.register(NicknameCommands.class); facets.register(PermissionCommands.class); facets.register(PermissionCommands.Parent.class); @@ -163,8 +177,6 @@ public final class CommonsBukkitManifest extends HybridManifest { facets.register(PlayerFreezer.class); facets.register(PlayerOrderCache.class); facets.register(PlayerServerChanger.class); - facets.register(ReportAnnouncer.class); - facets.register(ReportCommands.class); facets.register(ResourcePackCommands.class); facets.register(ResourcePackCommands.Parent.class); facets.register(ResourcePackListener.class); @@ -189,6 +201,7 @@ public final class CommonsBukkitManifest extends HybridManifest { facets.register(WindowManager.class); facets.register(AppealAlertListener.class); facets.register(SuspendListener.class); + facets.register(GroupCommands.Parent.class); // DataDog facets.register(TickReporter.class); diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/broadcast/BroadcastFormatter.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/broadcast/BroadcastFormatter.java index a9f9e29..dcc3aa7 100644 --- a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/broadcast/BroadcastFormatter.java +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/broadcast/BroadcastFormatter.java @@ -1,8 +1,7 @@ package tc.oc.commons.bukkit.broadcast; -import java.util.Map; - import com.google.common.collect.ImmutableMap; +import java.util.Map; import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.chat.TranslatableComponent; diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/broadcast/BroadcastManifest.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/broadcast/BroadcastManifest.java index ce9e77c..7378468 100644 --- a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/broadcast/BroadcastManifest.java +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/broadcast/BroadcastManifest.java @@ -1,8 +1,7 @@ package tc.oc.commons.bukkit.broadcast; -import java.util.List; - import com.google.inject.TypeLiteral; +import java.util.List; import tc.oc.commons.bukkit.broadcast.model.BroadcastPrefix; import tc.oc.commons.bukkit.broadcast.model.BroadcastSchedule; import tc.oc.commons.bukkit.settings.SettingBinder; diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/broadcast/BroadcastParser.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/broadcast/BroadcastParser.java index c601129..b9334c0 100644 --- a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/broadcast/BroadcastParser.java +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/broadcast/BroadcastParser.java @@ -1,10 +1,10 @@ package tc.oc.commons.bukkit.broadcast; import java.nio.file.Path; +import java.time.Duration; import java.util.List; import javax.inject.Inject; - -import java.time.Duration; +import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.Element; import tc.oc.commons.bukkit.broadcast.model.BroadcastPrefix; @@ -44,8 +44,13 @@ public class BroadcastParser implements DocumentParser> } public BroadcastSchedule parseSchedule(Element el) throws ParseException { + Duration delay = Duration.ZERO; + Attr delayAttr = el.getAttributeNode("delay"); + if (delayAttr != null) { + delay = durationParser.parse(delayAttr); + } return new BroadcastSchedule( - durationParser.parse(XML.requireAttr(el, "interval")), + delay, durationParser.parse(XML.requireAttr(el, "interval")), serverFilterParser.parse(el), XML.childrenNamed(el, "messages").map(this::parseMessages) ); } diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/broadcast/BroadcastScheduler.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/broadcast/BroadcastScheduler.java index bf8fb70..9cebcdf 100644 --- a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/broadcast/BroadcastScheduler.java +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/broadcast/BroadcastScheduler.java @@ -1,5 +1,7 @@ package tc.oc.commons.bukkit.broadcast; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; @@ -10,9 +12,6 @@ import java.util.stream.Stream; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Lists; import net.md_5.bungee.api.chat.BaseComponent; import org.bukkit.command.ConsoleCommandSender; import tc.oc.api.bukkit.users.OnlinePlayers; @@ -123,7 +122,7 @@ public class BroadcastScheduler implements PluginFacet { set -> messageMapFactory.create(configPath.resolve(SOURCES_PATH).resolve(set.path()), TRANSLATIONS_PATH.resolve(set.path()))) ); - this.task = scheduler.createRepeatingTask(schedule.interval(), this::dispatch); + this.task = scheduler.createRepeatingTask(schedule.delay(), schedule.interval(), this::dispatch); } void dispatch() { diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/broadcast/BroadcastSender.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/broadcast/BroadcastSender.java new file mode 100644 index 0000000..3e6f4db --- /dev/null +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/broadcast/BroadcastSender.java @@ -0,0 +1,170 @@ +package tc.oc.commons.bukkit.broadcast; + +import static tc.oc.api.util.Permissions.hasPermissionForEnum; +import static tc.oc.commons.bukkit.commands.CommandUtils.newCommandException; +import static tc.oc.commons.bukkit.commands.CommandUtils.tryEnum; + +import com.google.common.collect.Lists; +import com.sk89q.minecraft.util.commands.Command; +import com.sk89q.minecraft.util.commands.CommandContext; +import com.sk89q.minecraft.util.commands.CommandException; +import com.sk89q.minecraft.util.commands.CommandPermissionsException; +import com.sk89q.minecraft.util.commands.SuggestionContext; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.annotation.Nullable; +import javax.inject.Inject; +import javax.inject.Singleton; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.chat.HoverEvent; +import net.md_5.bungee.api.chat.TranslatableComponent; +import org.bukkit.Sound; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import tc.oc.api.bukkit.users.BukkitUserStore; +import tc.oc.api.docs.Chat; +import tc.oc.api.docs.Game; +import tc.oc.api.docs.Server; +import tc.oc.api.docs.User; +import tc.oc.api.docs.virtual.ChatDoc; +import tc.oc.api.games.GameStore; +import tc.oc.api.servers.ServerStore; +import tc.oc.commons.bukkit.chat.Audiences; +import tc.oc.commons.bukkit.chat.BukkitSound; +import tc.oc.commons.bukkit.chat.ChatCreator; +import tc.oc.commons.bukkit.chat.PlayerComponent; +import tc.oc.commons.bukkit.chat.WarningComponent; +import tc.oc.commons.bukkit.commands.CommandUtils; +import tc.oc.commons.bukkit.nick.IdentityProvider; +import tc.oc.commons.core.chat.Audience; +import tc.oc.commons.core.chat.Component; +import tc.oc.commons.core.commands.Commands; +import tc.oc.commons.core.formatting.StringUtils; + +/** + * Allows {@link User}s to broadcast {@link Chat} messages across multiple servers. + */ +@Singleton +public class BroadcastSender implements Commands { + + private final static String PERMISSION = "ocn.broadcast"; + + private final Server server; + private final ServerStore serverStore; + private final GameStore gameStore; + private final ChatCreator chatCreator; + private final Audiences audiences; + private final BukkitUserStore userStore; + private final IdentityProvider identityProvider; + + @Inject BroadcastSender(Server server, ServerStore serverStore, GameStore gameStore, ChatCreator chatCreator, Audiences audiences, BukkitUserStore userStore, IdentityProvider identityProvider) { + this.server = server; + this.serverStore = serverStore; + this.gameStore = gameStore; + this.chatCreator = chatCreator; + this.audiences = audiences; + this.userStore = userStore; + this.identityProvider = identityProvider; + } + + private Set destinations(@Nullable ChatDoc.Destination type) { + Stream options = Stream.empty(); + if(type != null) { + switch(type) { + case SERVER: + options = serverStore.all().map(Server::name); + break; + case FAMILY: + options = serverStore.all().map(Server::family); + break; + case GAME: + options = gameStore.all().map(Game::name); + break; + case NETWORK: + options = serverStore.all().map(Server::network).map(Enum::name); + break; + } + } + return options.map(String::toLowerCase).collect(Collectors.toSet()); + } + + @Command( + aliases = { "broadcast", "b" }, + desc = "Broadcast a message to players across the network.", + usage = " [message...]", + min = 1 + ) + public List broadcast(final CommandContext args, final CommandSender sender) throws CommandException { + SuggestionContext suggest = args.getSuggestionContext(); + ChatDoc.Destination type = tryEnum(args.getString(0, ""), ChatDoc.Destination.class); + Set destinations = destinations(type); + String message = ""; + String destination = ""; + + if(suggest != null) { + switch(suggest.getIndex()) { + case 0: + return CommandUtils.completeEnum(args.getString(0), ChatDoc.Destination.class); + case 1: + if(type != null && type != ChatDoc.Destination.GLOBAL) { + return StringUtils.complete(args.getString(1), destinations); + } + } + } + + if(type == null) { + type = ChatDoc.Destination.SERVER; + destination = server._id(); + message = args.getJoinedStrings(0); + } else if(args.argsLength() >= 2) { + if(type == ChatDoc.Destination.GLOBAL) { + destination = null; + message = args.getJoinedStrings(1); + } else if(args.argsLength() >= 3) { + destination = args.getString(1); + message = args.getJoinedStrings(2); + if(!destinations.contains(destination)) { + throw newCommandException(sender, new WarningComponent("command.error.invalidOption", destination, destinations)); + } + } + } else { + CommandUtils.notEnoughArguments(sender); + } + + if(hasPermissionForEnum(sender, PERMISSION, type)) { + chatCreator.broadcast( + sender instanceof Player ? userStore.tryUser((Player) sender) : null, + message, + type, + destination + ); + } else { + throw new CommandPermissionsException(); + } + return null; + } + + public void show(Chat chat) { + final Audience audience = audiences.all(); + audience.playSound(new BukkitSound(Sound.ENTITY_ENDERDRAGON_HURT, 1, 1)); + audience.sendMessage( + new Component( + Lists.newArrayList( + new Component("["), + new TranslatableComponent("broadcast.prefix"), + new Component("] "), + new Component(chat.message()) + ), ChatColor.RED + ).hoverEvent( + HoverEvent.Action.SHOW_TEXT, + new TranslatableComponent( + "tip.sentBy", + new PlayerComponent(identityProvider.currentOrConsoleIdentity(chat.sender())) + ) + ) + ); + } + +} diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/broadcast/BroadcastSettings.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/broadcast/BroadcastSettings.java index 73b5115..44a2cde 100644 --- a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/broadcast/BroadcastSettings.java +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/broadcast/BroadcastSettings.java @@ -1,7 +1,6 @@ package tc.oc.commons.bukkit.broadcast; import javax.inject.Inject; - import me.anxuiz.settings.Setting; import me.anxuiz.settings.SettingBuilder; import me.anxuiz.settings.types.BooleanType; @@ -57,7 +56,6 @@ public class BroadcastSettings { break; case NEWS: - case ALERT: setting = NEWS; break; @@ -70,6 +68,7 @@ public class BroadcastSettings { setting = RANDOM; break; + case ALERT: default: return true; } diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/broadcast/model/BroadcastSchedule.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/broadcast/model/BroadcastSchedule.java index 744f010..03f357e 100644 --- a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/broadcast/model/BroadcastSchedule.java +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/broadcast/model/BroadcastSchedule.java @@ -1,10 +1,9 @@ package tc.oc.commons.bukkit.broadcast.model; -import java.util.List; -import java.util.stream.Stream; - import com.google.common.collect.ImmutableList; import java.time.Duration; +import java.util.List; +import java.util.stream.Stream; import tc.oc.commons.core.inspect.Inspectable; import tc.oc.commons.core.stream.Collectors; import tc.oc.minecraft.server.ServerFilter; @@ -14,11 +13,13 @@ import tc.oc.minecraft.server.ServerFilter; */ public class BroadcastSchedule extends Inspectable.Impl { + @Inspect private final Duration delay; @Inspect private final Duration interval; @Inspect private final ImmutableList messages; @Inspect private final ServerFilter serverFilter; - public BroadcastSchedule(Duration interval, ServerFilter serverFilter, Stream messages) { + public BroadcastSchedule(Duration delay, Duration interval, ServerFilter serverFilter, Stream messages) { + this.delay = delay; this.interval = interval; this.serverFilter = serverFilter; this.messages = messages.collect(Collectors.toImmutableList()); @@ -31,6 +32,13 @@ public class BroadcastSchedule extends Inspectable.Impl { return interval; } + /** + * Time before first broadcast + */ + public Duration delay() { + return delay; + } + /** * Relative path of the localized message list. * diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/broadcast/model/BroadcastSet.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/broadcast/model/BroadcastSet.java index d8ca40c..f8c434b 100644 --- a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/broadcast/model/BroadcastSet.java +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/broadcast/model/BroadcastSet.java @@ -1,7 +1,6 @@ package tc.oc.commons.bukkit.broadcast.model; import java.nio.file.Path; - import tc.oc.commons.core.inspect.Inspectable; public class BroadcastSet extends Inspectable.Impl { diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/channels/AdminChannel.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/channels/AdminChannel.java deleted file mode 100644 index fd6def3..0000000 --- a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/channels/AdminChannel.java +++ /dev/null @@ -1,129 +0,0 @@ -package tc.oc.commons.bukkit.channels; - -import java.util.stream.Stream; -import javax.inject.Inject; -import javax.inject.Singleton; - -import com.github.rmsy.channels.Channel; -import com.github.rmsy.channels.ChannelsPlugin; -import com.github.rmsy.channels.PlayerManager; -import com.github.rmsy.channels.event.ChannelMessageEvent; -import com.github.rmsy.channels.impl.SimpleChannel; -import com.sk89q.minecraft.util.commands.Command; -import com.sk89q.minecraft.util.commands.CommandContext; -import com.sk89q.minecraft.util.commands.CommandException; -import com.sk89q.minecraft.util.commands.CommandPermissions; -import com.sk89q.minecraft.util.commands.CommandPermissionsException; -import com.sk89q.minecraft.util.commands.CommandUsageException; -import com.sk89q.minecraft.util.commands.Console; -import me.anxuiz.settings.Setting; -import me.anxuiz.settings.SettingBuilder; -import me.anxuiz.settings.types.BooleanType; -import org.bukkit.ChatColor; -import org.bukkit.command.CommandSender; -import org.bukkit.command.ConsoleCommandSender; -import org.bukkit.entity.Player; -import org.bukkit.permissions.Permission; -import org.bukkit.permissions.PermissionDefault; -import tc.oc.api.bukkit.users.OnlinePlayers; -import tc.oc.commons.bukkit.settings.SettingManagerProvider; -import tc.oc.commons.core.commands.Commands; - -@Singleton -public class AdminChannel extends SimpleChannel implements Commands { - - static final Setting SETTING = new SettingBuilder() - .name("AdminChat").alias("ac") - .summary("Show confidential staff info") - .type(new BooleanType()) - .defaultValue(true).get(); - - public static final String PERM_NODE = "chat.admin"; - public static final String PERM_SEND = PERM_NODE + ".send"; - public static final String PERM_RECEIVE = PERM_NODE + ".receive"; - - public static final String PREFIX = ChatColor.WHITE + "[" + ChatColor.GOLD + "A" + ChatColor.WHITE + "]"; - public static final String BROADCAST_FORMAT = PREFIX + " {2}"; - public static final String FORMAT = PREFIX + " {1}" + ChatColor.WHITE + ": {2}"; - - private final ConsoleCommandSender console; - private final OnlinePlayers players; - private final SettingManagerProvider settings; - - @Inject AdminChannel(ConsoleCommandSender console, OnlinePlayers players, SettingManagerProvider settings) { - super(FORMAT, BROADCAST_FORMAT, new Permission(PERM_RECEIVE, PermissionDefault.OP)); - this.players = players; - this.settings = settings; - this.console = console; - } - - @Command(aliases = "a", - desc = "Sends a message to the staff channel (or sets the staff channel to your default channel).", - max = -1, - min = 0, - anyFlags = true, - usage = "[message...]") - @Console - @CommandPermissions({PERM_SEND, PERM_RECEIVE}) - public void onAdminChatCommand(final CommandContext arguments, final CommandSender sender) throws CommandException { - if(arguments.argsLength() == 0) { - if (sender.hasPermission(PERM_RECEIVE)) { - if (sender instanceof Player) { - Player player = (Player) sender; - PlayerManager playerManager = ChannelsPlugin.get().getPlayerManager(); - Channel oldChannel = playerManager.getMembershipChannel(player); - playerManager.setMembershipChannel(player, this); - if (!oldChannel.equals(this)) { - sender.sendMessage(org.bukkit.ChatColor.YELLOW + "Changed default channel to administrator chat"); - } else { - throw new CommandException("Administrator chat is already your default channel"); - } - } else { - throw new CommandUsageException("You must provide a message.", "/a "); - } - } else { - throw new CommandPermissionsException(); - } - } else if (sender.hasPermission(PERM_SEND)) { - Player sendingPlayer = null; - if (sender instanceof Player) { - sendingPlayer = (Player) sender; - } - this.sendMessage(arguments.getJoinedStrings(0), sendingPlayer); - if (!sender.hasPermission(PERM_RECEIVE)) { - sender.sendMessage(org.bukkit.ChatColor.YELLOW + "Message sent"); - } - } else { - throw new CommandPermissionsException(); - } - } - - @Override - public void sendMessageToViewer(Player sender, CommandSender viewer, String sanitizedMessage, ChannelMessageEvent event) { - if(viewer != null && !isEnabled(viewer)) { - return; - } - - super.sendMessageToViewer(sender, viewer, sanitizedMessage, event); - } - - public boolean isEnabled(Player viewer) { - return (boolean) settings.getManager(viewer) - .getValue(SETTING); - } - - public boolean isEnabled(CommandSender viewer) { - return !(viewer instanceof Player) || isEnabled((Player) viewer); - } - - public boolean isVisible(CommandSender viewer) { - return viewer.hasPermission(getListeningPermission()) && - isEnabled(viewer); - } - - public Stream viewers() { - return Stream.concat(Stream.of(console), - players.all().stream()) - .filter(this::isVisible); - } -} diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/channels/AdminChatManifest.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/channels/AdminChatManifest.java deleted file mode 100644 index 1aa2569..0000000 --- a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/channels/AdminChatManifest.java +++ /dev/null @@ -1,18 +0,0 @@ -package tc.oc.commons.bukkit.channels; - -import tc.oc.commons.bukkit.settings.SettingBinder; -import tc.oc.commons.core.inject.HybridManifest; -import tc.oc.commons.core.plugin.PluginFacetBinder; - -public class AdminChatManifest extends HybridManifest { - @Override - protected void configure() { - new SettingBinder(publicBinder()) - .addBinding().toInstance(AdminChannel.SETTING); - - new PluginFacetBinder(binder()) - .register(AdminChannel.class); - - expose(AdminChannel.class); - } -} diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/channels/Channel.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/channels/Channel.java new file mode 100644 index 0000000..db02f88 --- /dev/null +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/channels/Channel.java @@ -0,0 +1,27 @@ +package tc.oc.commons.bukkit.channels; + +import javax.annotation.Nullable; +import org.bukkit.command.CommandSender; +import tc.oc.api.docs.Chat; +import tc.oc.api.docs.PlayerId; +import tc.oc.api.docs.virtual.ChatDoc; +import tc.oc.commons.core.chat.Audience; + +/** + * An {@link Audience} that sends {@link Chat} messages to the API. + */ +public interface Channel extends Audience { + + ChatDoc.Type type(); + + void chat(CommandSender sender, String message); + + void chat(@Nullable PlayerId playerId, String message); + + void show(Chat message); + + boolean sendable(CommandSender sender); + + boolean viewable(CommandSender sender); + +} diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/channels/ChannelChatEvent.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/channels/ChannelChatEvent.java new file mode 100644 index 0000000..40b4dac --- /dev/null +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/channels/ChannelChatEvent.java @@ -0,0 +1,59 @@ +package tc.oc.commons.bukkit.channels; + +import javax.annotation.Nullable; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import tc.oc.api.docs.PlayerId; + +/** + * Called when a command sender chats in a local {@link Channel}. + * If cancelled, the message will not be seen by users or reported to the API. + */ +public class ChannelChatEvent extends Event implements Cancellable { + private final static HandlerList handlers = new HandlerList(); + + private final Channel channel; + private final PlayerId sender; + private final String message; + private boolean cancelled; + + public ChannelChatEvent(Channel channel, PlayerId sender, String message) { + this.channel = channel; + this.sender = sender; + this.message = message; + } + + public Channel channel() { + return channel; + } + + public @Nullable + PlayerId sender() { + return sender; + } + + public String message() { + return message; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + +} diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/channels/ChannelCommands.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/channels/ChannelCommands.java new file mode 100644 index 0000000..63efbf0 --- /dev/null +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/channels/ChannelCommands.java @@ -0,0 +1,112 @@ +package tc.oc.commons.bukkit.channels; + +import com.sk89q.minecraft.util.commands.Command; +import com.sk89q.minecraft.util.commands.CommandContext; +import com.sk89q.minecraft.util.commands.CommandException; +import com.sk89q.minecraft.util.commands.CommandPermissionsException; +import javax.inject.Inject; +import javax.inject.Singleton; +import net.md_5.bungee.api.chat.TranslatableComponent; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.AsyncPlayerChatEvent; +import tc.oc.api.docs.virtual.ChatDoc; +import tc.oc.commons.bukkit.chat.Audiences; +import tc.oc.commons.bukkit.commands.CommandUtils; +import tc.oc.commons.bukkit.util.SyncPlayerExecutorFactory; +import tc.oc.commons.core.commands.Commands; +import tc.oc.commons.core.commands.TranslatableCommandException; + +@Singleton +public class ChannelCommands implements Commands, Listener { + + private final SyncPlayerExecutorFactory syncPlayerExecutorFactory; + private final ChannelRouter channelRouter; + private final Audiences audiences; + + @Inject ChannelCommands(SyncPlayerExecutorFactory syncPlayerExecutorFactory, ChannelRouter channelRouter, Audiences audiences) { + this.syncPlayerExecutorFactory = syncPlayerExecutorFactory; + this.channelRouter = channelRouter; + this.audiences = audiences; + } + + @Command( + aliases = "a", + desc = "Send a message to the staff channel.", + usage = "[message...]" + ) + public void admin(final CommandContext args, final CommandSender sender) throws CommandException { + onChatCommand(ChatDoc.Type.ADMIN, args, sender); + } + + @Command( + aliases = { "g", "shout" }, + desc = "Send a message to everyone on the local server.", + usage = "[message...]" + ) + public void server(final CommandContext args, final CommandSender sender) throws CommandException { + onChatCommand(ChatDoc.Type.SERVER, args, sender); + } + + @Command( + aliases = "t", + desc = "Send a message to your teammates.", + usage = "[message...]" + ) + public void chat(final CommandContext args, final CommandSender sender) throws CommandException { + onChatCommand(ChatDoc.Type.TEAM, args, sender); + } + + public void onChatCommand(ChatDoc.Type type, CommandContext args, CommandSender sender) throws CommandException { + final String typeName = type.name().toLowerCase(); + final Channel channel = channelRouter.getChannel(sender, type) + .orElseThrow(() -> new TranslatableCommandException("channels.unavailable", typeName)); + if(channel.sendable(sender)) { + if(args.argsLength() == 0) { + final Player player = CommandUtils.senderToPlayer(sender); + if(channel.equals(channelRouter.getDefaultChannel(player))) { + throw new TranslatableCommandException("channels.default.alreadySet", typeName); + } else { + channelRouter.setDefaultChannel(player, channel.type()); + audiences.get(player).sendMessage(new TranslatableComponent("channels.default.set", typeName)); + } + } else { + channel.chat(sender, args.getJoinedStrings(0)); + } + } else { + throw new CommandPermissionsException(); + } + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onChat(AsyncPlayerChatEvent event) { + event.setCancelled(true); + syncPlayerExecutorFactory.queued(event.getPlayer()).execute(player -> { + Channel channel = channelRouter.getDefaultChannel(player); + String message = event.getMessage().trim(); + + if(message.startsWith("!")) { + channel = channelRouter.getChannel(ChatDoc.Type.SERVER).get(); + message = message.substring(1).trim(); + } else if(message.startsWith("@a ")) { + channel = channelRouter.getChannel(ChatDoc.Type.SERVER).get(); + message = message.substring(3).trim(); + } + + if(!channel.sendable(player)) { + // If player cannot chat in their preferred channel, + // assume they can send to the default channel. + channel = channelRouter.getDefaultChannel(); + } + if(!message.isEmpty()) { + channel.chat(player, message); + } else { + audiences.get(player).sendWarning(new TranslatableComponent("channels.message.empty"), false); + } + }); + } + +} diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/channels/ChannelConfiguration.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/channels/ChannelConfiguration.java new file mode 100644 index 0000000..23b0f8c --- /dev/null +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/channels/ChannelConfiguration.java @@ -0,0 +1,21 @@ +package tc.oc.commons.bukkit.channels; + +import javax.inject.Inject; +import org.bukkit.configuration.Configuration; + +public class ChannelConfiguration { + + private final Configuration config; + + @Inject ChannelConfiguration(Configuration config) { + this.config = config; + } + + public boolean admin_enabled() { + return config.getBoolean("channels.admin.enabled", false); + } + + public boolean admin_cross_server() { + return config.getBoolean("channels.admin.cross-server", false); + } +} diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/channels/ChannelManifest.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/channels/ChannelManifest.java new file mode 100644 index 0000000..d10191a --- /dev/null +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/channels/ChannelManifest.java @@ -0,0 +1,26 @@ +package tc.oc.commons.bukkit.channels; + +import tc.oc.commons.bukkit.channels.admin.AdminChannel; +import tc.oc.commons.bukkit.channels.server.ServerChannel; +import tc.oc.commons.bukkit.settings.SettingBinder; +import tc.oc.commons.core.inject.HybridManifest; +import tc.oc.commons.core.plugin.PluginFacetBinder; + +public class ChannelManifest extends HybridManifest { + @Override + protected void configure() { + + bindAndExpose(ChannelRouter.class); + expose(ChannelCommands.class); + expose(AdminChannel.class); + expose(ServerChannel.class); + + final PluginFacetBinder facets = new PluginFacetBinder(binder()); + facets.register(ChannelCommands.class); + facets.register(AdminChannel.class); + facets.register(ServerChannel.class); + + final SettingBinder settings = new SettingBinder(publicBinder()); + settings.addBinding().toInstance(AdminChannel.SETTING); + } +} diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/channels/ChannelRouter.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/channels/ChannelRouter.java new file mode 100644 index 0000000..f878661 --- /dev/null +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/channels/ChannelRouter.java @@ -0,0 +1,81 @@ +package tc.oc.commons.bukkit.channels; + +import com.google.common.util.concurrent.ListenableFuture; +import java.util.Optional; +import java.util.function.Function; +import javax.annotation.Nullable; +import javax.inject.Inject; +import javax.inject.Singleton; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import tc.oc.api.bukkit.users.BukkitUserStore; +import tc.oc.api.docs.Chat; +import tc.oc.api.docs.User; +import tc.oc.api.docs.virtual.ChatDoc; +import tc.oc.api.docs.virtual.UserDoc; +import tc.oc.api.users.UserService; +import tc.oc.commons.bukkit.channels.admin.AdminChannel; +import tc.oc.commons.bukkit.channels.server.ServerChannel; + +/** + * Get the {@link Channel} based on the {@link ChatDoc.Type}. + */ +@Singleton +public class ChannelRouter { + + private final BukkitUserStore userStore; + private final UserService userService; + private final ServerChannel serverChannel; + private final AdminChannel adminChannel; + private Function teamChannelFunction; + + @Inject ChannelRouter(BukkitUserStore userStore, UserService userService, ServerChannel serverChannel, AdminChannel adminChannel) { + this.userStore = userStore; + this.userService = userService; + this.serverChannel = serverChannel; + this.adminChannel = adminChannel; + setTeamChannelFunction(null); + } + + public Optional getChannel(ChatDoc.Type type) { + return getChannel(null, type); + } + + public Optional getChannel(Chat chat) { + return getChannel(userStore.find(chat.sender()), chat.type()); + } + + public Optional getChannel(@Nullable CommandSender sender, ChatDoc.Type type) { + Channel channel = null; + if(type == ChatDoc.Type.SERVER) { + channel = serverChannel; + } else if(type == ChatDoc.Type.ADMIN) { + channel = adminChannel; + } else if(sender != null && sender instanceof Player && type == ChatDoc.Type.TEAM) { + channel = teamChannelFunction.apply((Player) sender); + } + return Optional.ofNullable(channel); + } + + public Channel getDefaultChannel() { + return serverChannel; + } + + public Channel getDefaultChannel(Player player) { + return getChannel(player, userStore.getUser(player).chat_channel()).orElse(getDefaultChannel()); + } + + public ListenableFuture setDefaultChannel(Player player, ChatDoc.Type type) { + return userService.update(userStore.playerId(player), new UserDoc.Channel() { + @Override + public ChatDoc.Type chat_channel() { + return type; + } + }); + } + + public void setTeamChannelFunction(@Nullable Function function) { + teamChannelFunction = function != null ? function : sender -> serverChannel; + } + +} diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/channels/PermissibleChannel.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/channels/PermissibleChannel.java new file mode 100644 index 0000000..8a301db --- /dev/null +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/channels/PermissibleChannel.java @@ -0,0 +1,25 @@ +package tc.oc.commons.bukkit.channels; + +import org.bukkit.command.CommandSender; +import org.bukkit.permissions.Permission; +import tc.oc.commons.core.chat.Audience; + +/** + * An {@link Audience} with membership access based off of a {@link Permission} node. + */ +public interface PermissibleChannel extends Channel { + + Permission permission(); + + @Override + default boolean sendable(CommandSender sender) { + return sender.hasPermission(permission()); + } + + @Override + default boolean viewable(CommandSender sender) { + return sender.hasPermission(permission()); + } + +} + diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/channels/SimpleChannel.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/channels/SimpleChannel.java new file mode 100644 index 0000000..227e829 --- /dev/null +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/channels/SimpleChannel.java @@ -0,0 +1,76 @@ +package tc.oc.commons.bukkit.channels; + +import java.util.stream.Stream; +import javax.annotation.Nullable; +import javax.inject.Inject; +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.TextComponent; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.event.EventBus; +import tc.oc.api.bukkit.users.BukkitUserStore; +import tc.oc.api.docs.Chat; +import tc.oc.api.docs.PlayerId; +import tc.oc.commons.bukkit.chat.Audiences; +import tc.oc.commons.bukkit.chat.ChatCreator; +import tc.oc.commons.bukkit.chat.NameStyle; +import tc.oc.commons.bukkit.chat.PlayerComponent; +import tc.oc.commons.bukkit.nick.IdentityProvider; +import tc.oc.commons.core.chat.Audience; +import tc.oc.commons.core.chat.Component; +import tc.oc.commons.core.chat.MultiAudience; +import tc.oc.commons.core.plugin.PluginFacet; + +public abstract class SimpleChannel implements MultiAudience, Channel, PluginFacet { + + @Inject protected Audiences audiences; + @Inject protected EventBus eventBus; + @Inject protected BukkitUserStore userStore; + @Inject protected ChatCreator chatCreator; + @Inject protected IdentityProvider identityProvider; + + public abstract BaseComponent prefix(); + + public abstract BaseComponent format(Chat chat, PlayerComponent sender, String message); + + @Override + public void sendMessage(BaseComponent message) { + MultiAudience.super.sendMessage(new Component(prefix()).extra(message)); + } + + @Override + public Stream audiences() { + return Stream.of(audiences.filter(this::viewable)); + } + + @Override + public void chat(@Nullable PlayerId playerId, String message) { + final ChannelChatEvent event = new ChannelChatEvent(this, playerId, message); + eventBus.callEvent(event); + if(!event.isCancelled()) { + chatCreator.chat(playerId, event.message(), type(), this::show); + } + } + + @Override + public void sendMessage(String message) { + sendMessage(new TextComponent(message)); + } + + @Override + public void show(Chat chat) { + sendMessage(format( + chat, + new PlayerComponent( + identityProvider.currentOrConsoleIdentity(chat.sender()), + NameStyle.VERBOSE_SIMPLE + ), chat.message() + )); + } + + @Override + public void chat(CommandSender sender, String message) { + chat(sender instanceof Player ? userStore.tryUser((Player) sender) : null, message); + } + +} diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/channels/admin/AdminChannel.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/channels/admin/AdminChannel.java new file mode 100644 index 0000000..1a5d563 --- /dev/null +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/channels/admin/AdminChannel.java @@ -0,0 +1,87 @@ +package tc.oc.commons.bukkit.channels.admin; + +import javax.inject.Inject; +import javax.inject.Singleton; +import me.anxuiz.settings.Setting; +import me.anxuiz.settings.SettingBuilder; +import me.anxuiz.settings.types.BooleanType; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.chat.BaseComponent; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.permissions.Permission; +import org.bukkit.permissions.PermissionDefault; +import tc.oc.api.docs.Chat; +import tc.oc.api.docs.Server; +import tc.oc.api.docs.virtual.ChatDoc; +import tc.oc.api.servers.ServerStore; +import tc.oc.commons.bukkit.channels.PermissibleChannel; +import tc.oc.commons.bukkit.channels.SimpleChannel; +import tc.oc.commons.bukkit.chat.PlayerComponent; +import tc.oc.commons.bukkit.format.ServerFormatter; +import tc.oc.commons.bukkit.permissions.PermissionRegistry; +import tc.oc.commons.bukkit.settings.SettingManagerProvider; +import tc.oc.commons.core.chat.Component; + +@Singleton +public class AdminChannel extends SimpleChannel implements PermissibleChannel { + + public final static Permission PERMISSION = new Permission("chat.admin", PermissionDefault.OP); + public final static Setting SETTING = new SettingBuilder() + .name("AdminChat") + .alias("ac") + .summary("Show confidential staff chat") + .type(new BooleanType()) + .defaultValue(true) + .get(); + + private final SettingManagerProvider settings; + private final PermissionRegistry permissions; + private final Server localServer; + private final ServerStore serverStore; + + @Inject AdminChannel(PermissionRegistry permissions, SettingManagerProvider settings, Server localServer, ServerStore serverStore) { + this.settings = settings; + this.permissions = permissions; + this.localServer = localServer; + this.serverStore = serverStore; + } + + @Override + public void enable() { + permissions.register(PERMISSION); + } + + @Override + public Permission permission() { + return PERMISSION; + } + + @Override + public boolean viewable(CommandSender sender) { + return !(sender instanceof Player) || + (PermissibleChannel.super.viewable(sender) && + settings.getManager((Player) sender).getValue(SETTING, Boolean.class, true)); + } + + @Override + public BaseComponent prefix() { + return new Component().extra("[").extra(new Component("A", ChatColor.GOLD)).extra("] "); + } + + @Override + public BaseComponent format(Chat chat, PlayerComponent player, String message) { + Component component = new Component(); + if(!localServer._id().equals(chat.server_id())) { + final Server server = serverStore.byId(chat.server_id()); + component.extra(ServerFormatter.light.nameWithDatacenter(server)).extra(" "); + } + return component.extra(player).extra(": ").extra(message); + } + + @Override + public ChatDoc.Type type() { + return ChatDoc.Type.ADMIN; + } + +} diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/channels/server/ServerChannel.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/channels/server/ServerChannel.java new file mode 100644 index 0000000..632175a --- /dev/null +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/channels/server/ServerChannel.java @@ -0,0 +1,43 @@ +package tc.oc.commons.bukkit.channels.server; + +import javax.inject.Inject; +import javax.inject.Singleton; +import net.md_5.bungee.api.chat.BaseComponent; +import org.bukkit.command.CommandSender; +import tc.oc.api.docs.Chat; +import tc.oc.api.docs.virtual.ChatDoc; +import tc.oc.commons.bukkit.channels.SimpleChannel; +import tc.oc.commons.bukkit.chat.PlayerComponent; +import tc.oc.commons.core.chat.Component; + +@Singleton +public class ServerChannel extends SimpleChannel { + + @Inject ServerChannel() {} + + @Override + public BaseComponent prefix() { + return new Component(); + } + + @Override + public BaseComponent format(Chat chat, PlayerComponent sender, String message) { + return new Component().extra("<").extra(sender).extra(">: ").extra(message); + } + + @Override + public ChatDoc.Type type() { + return ChatDoc.Type.SERVER; + } + + @Override + public boolean sendable(CommandSender sender) { + return true; + } + + @Override + public boolean viewable(CommandSender sender) { + return true; + } + +} diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/CachingNameRenderer.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/CachingNameRenderer.java index 080e839..1700d76 100644 --- a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/CachingNameRenderer.java +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/CachingNameRenderer.java @@ -1,11 +1,10 @@ package tc.oc.commons.bukkit.chat; -import javax.annotation.Nullable; -import javax.inject.Inject; -import javax.inject.Singleton; - import com.google.common.collect.HashBasedTable; import com.google.common.collect.Table; +import javax.annotation.Nullable; +import javax.inject.Inject; +import javax.inject.Singleton; import net.md_5.bungee.api.chat.BaseComponent; import tc.oc.commons.bukkit.nick.Identity; diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/ChatAnnouncer.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/ChatAnnouncer.java new file mode 100644 index 0000000..c32f3a5 --- /dev/null +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/ChatAnnouncer.java @@ -0,0 +1,107 @@ +package tc.oc.commons.bukkit.chat; + +import java.util.Optional; +import javax.inject.Inject; +import javax.inject.Singleton; +import tc.oc.api.docs.Chat; +import tc.oc.api.docs.Server; +import tc.oc.api.docs.virtual.ChatDoc; +import tc.oc.api.docs.virtual.ServerDoc; +import tc.oc.api.message.MessageListener; +import tc.oc.api.message.MessageQueue; +import tc.oc.api.message.types.ModelUpdate; +import tc.oc.api.servers.ServerStore; +import tc.oc.commons.bukkit.broadcast.BroadcastSender; +import tc.oc.commons.bukkit.channels.Channel; +import tc.oc.commons.bukkit.channels.ChannelConfiguration; +import tc.oc.commons.bukkit.channels.ChannelRouter; +import tc.oc.commons.core.plugin.PluginFacet; +import tc.oc.minecraft.scheduler.MainThreadExecutor; + +@Singleton +public class ChatAnnouncer implements PluginFacet, MessageListener { + + private final ChannelConfiguration configuration; + private final Server server; + private final ServerStore serverStore; + private final MessageQueue queue; + private final MainThreadExecutor executor; + private final ChannelRouter channelRouter; + private final BroadcastSender broadcaster; + + @Inject + public ChatAnnouncer(ChannelConfiguration configuration, Server server, ServerStore serverStore, + MessageQueue queue, MainThreadExecutor executor, ChannelRouter channelRouter, + BroadcastSender broadcaster) { + this.configuration = configuration; + this.server = server; + this.serverStore = serverStore; + this.queue = queue; + this.executor = executor; + this.channelRouter = channelRouter; + this.broadcaster = broadcaster; + } + + @Override + public void enable() { + queue.bind(ModelUpdate.class); + queue.subscribe(this, executor); + } + + @Override + public void disable() { + queue.unsubscribe(this); + } + + @MessageListener.HandleMessage + public void onChat(ModelUpdate message) { + final Chat chat = message.document(); + if(shouldAnnounce(chat)) { + final ChatDoc.Type type = chat.type(); + final Optional channel = channelRouter.getChannel(chat); + if(channel.isPresent()) { + channel.get().show(chat); + } else if(type == ChatDoc.Type.BROADCAST) { + broadcaster.show(chat); + } + } + } + + public boolean shouldAnnounce(Chat chat) { + final boolean remote = serverStore.canCommunicate(server._id(), chat.server_id()); + switch(chat.type()) { + case SERVER: + case TEAM: + return false; + case ADMIN: + if (!configuration.admin_cross_server() && !chat.server_id().equalsIgnoreCase(server._id())) { + return false; + } + return configuration.admin_enabled() && remote; + case BROADCAST: + return shouldAnnounce(chat.broadcast()); + } + return false; + } + + public boolean shouldAnnounce(ChatDoc.Broadcast broadcast) { + final String destination = broadcast.id(); + switch(broadcast.destination()) { + case SERVER: + return server._id().equalsIgnoreCase(destination); + case FAMILY: + final String family = server.family(); + return family == null || family.equalsIgnoreCase(destination); + case GAME: + final String game = server.game_id(); + return game == null || game.equalsIgnoreCase(destination); + case NETWORK: + final ServerDoc.Network network = server.network(); + return network == null || network.name().equalsIgnoreCase(destination); + case GLOBAL: + return true; + } + return false; + } + +} diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/ChatCreator.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/ChatCreator.java new file mode 100644 index 0000000..f86b73b --- /dev/null +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/ChatCreator.java @@ -0,0 +1,90 @@ +package tc.oc.commons.bukkit.chat; + +import static java.util.Optional.ofNullable; + +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import java.time.Duration; +import java.time.Instant; +import java.util.function.Consumer; +import javax.annotation.Nullable; +import javax.inject.Inject; +import javax.inject.Singleton; +import tc.oc.api.docs.Chat; +import tc.oc.api.docs.PlayerId; +import tc.oc.api.docs.Server; +import tc.oc.api.docs.virtual.ChatDoc; +import tc.oc.api.docs.virtual.MatchDoc; +import tc.oc.api.model.BatchUpdater; +import tc.oc.api.model.BatchUpdaterFactory; +import tc.oc.api.model.IdFactory; +import tc.oc.api.model.ModelService; +import tc.oc.commons.core.plugin.PluginFacet; +import tc.oc.minecraft.api.event.Listener; + +@Singleton +public class ChatCreator implements PluginFacet, Listener { + + private final IdFactory idFactory; + private final ModelService chatService; + private final BatchUpdater chatBatchUpdater; + private final Server server; + + @Inject ChatCreator(IdFactory idFactory, ModelService chatService, BatchUpdaterFactory chatBatchUpdaterFactory, Server server) { + this.idFactory = idFactory; + this.chatService = chatService; + this.chatBatchUpdater = chatBatchUpdaterFactory.createBatchUpdater(Duration.ofMinutes(1)); + this.server = server; + } + + public ListenableFuture chat(@Nullable PlayerId sender, String message, ChatDoc.Type type, Consumer callback) { + return send(sender, message, type, null, callback); + } + + public ListenableFuture broadcast(@Nullable PlayerId sender, String message, ChatDoc.Destination destination, String destination_id) { + return send(sender, message, ChatDoc.Type.BROADCAST, + new ChatDoc.Broadcast() { + public ChatDoc.Destination destination() { return destination; } + public String id() { return destination_id; } + }, null + ); + } + + protected ListenableFuture send(@Nullable PlayerId sender, String message, ChatDoc.Type type, @Nullable ChatDoc.Broadcast broadcast, @Nullable Consumer callback) { + final Instant time = Instant.now(); + final String id = idFactory.newId(); + ChatDoc.Creation chat = new ChatDoc.Creation() { + public String _id() { return id; } + public String sender_id() { return sender != null ? sender._id() : null; } + public String message() { return message; } + public String server_id() { return server._id(); } + public String match_id() { return ofNullable(server.current_match()).map(MatchDoc::_id).orElse(null); } + public ChatDoc.Type type() { return type; } + public Instant sent_at() { return time; } + public ChatDoc.Broadcast broadcast() { return broadcast; } + }; + // Some chats are only consumed by the local server, + // so those messages can have delayed reporting to the API. + if(type.batchUpdate) { + if(callback != null) callback.accept(mock(sender, chat)); + chatBatchUpdater.update(chat); + return Futures.immediateFuture(null); + } else { + return chatService.update(chat); + } + } + + private Chat mock(@Nullable PlayerId sender, ChatDoc.Creation chat) { + return new Chat() { + public PlayerId sender() { return sender; } + public String _id() { return chat._id(); } + public String message() { return chat.message(); } + public String server_id() { return chat.server_id(); } + public String match_id() { return chat.match_id(); } + public ChatDoc.Type type() { return chat.type(); } + public Instant sent_at() { return chat.sent_at(); } + public ChatDoc.Broadcast broadcast() { return chat.broadcast(); } + }; + } + +} diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/ChatManifest.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/ChatManifest.java new file mode 100644 index 0000000..e6e5e1d --- /dev/null +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/ChatManifest.java @@ -0,0 +1,20 @@ +package tc.oc.commons.bukkit.chat; + +import tc.oc.commons.bukkit.broadcast.BroadcastSender; +import tc.oc.commons.core.inject.HybridManifest; +import tc.oc.commons.core.plugin.PluginFacetBinder; + +public class ChatManifest extends HybridManifest { + @Override + protected void configure() { + + expose(ChatCreator.class); + expose(ChatAnnouncer.class); + expose(BroadcastSender.class); + + final PluginFacetBinder facets = new PluginFacetBinder(binder()); + facets.register(ChatCreator.class); + facets.register(ChatAnnouncer.class); + facets.register(BroadcastSender.class); + } +} diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/FullNameRenderer.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/FullNameRenderer.java index 35e25f4..ca33b4b 100644 --- a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/FullNameRenderer.java +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/FullNameRenderer.java @@ -1,8 +1,8 @@ package tc.oc.commons.bukkit.chat; import javax.inject.Inject; - import net.md_5.bungee.api.chat.BaseComponent; +import tc.oc.commons.bukkit.flairs.FlairRenderer; import tc.oc.commons.bukkit.nick.Identity; import tc.oc.commons.bukkit.nick.UsernameRenderer; import tc.oc.commons.core.chat.Component; diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/LinkComponent.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/LinkComponent.java index f4949b5..fcdca50 100644 --- a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/LinkComponent.java +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/LinkComponent.java @@ -3,7 +3,6 @@ package tc.oc.commons.bukkit.chat; import java.net.URI; import java.net.URISyntaxException; import java.util.Optional; - import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.chat.ClickEvent; diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/Links.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/Links.java index d9ccc9b..88e3597 100644 --- a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/Links.java +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/Links.java @@ -2,7 +2,6 @@ package tc.oc.commons.bukkit.chat; import java.net.URI; import java.net.URISyntaxException; - import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.chat.TranslatableComponent; @@ -13,12 +12,17 @@ import tc.oc.commons.core.util.ExceptionUtils; public class Links { private Links() {} - public static final String HOST = "localhost"; // TODO: configurable + public static final String HOST = "stratus.network"; + public static final String SHOP_HOST = "stratusnetwork.buycraft.net"; public static URI homeUri(String path) throws URISyntaxException { return new URI("http", HOST, path, null); } + public static URI shopUri() throws URISyntaxException { + return new URI("http", SHOP_HOST, null, null); + } + public static URI homeUriSafe(String path) { return ExceptionUtils.propagate(() -> homeUri(path)); } @@ -39,8 +43,12 @@ public class Links { return homeLinkSafe("/"); } + public static BaseComponent shopLink(boolean compact) throws URISyntaxException { + return new LinkComponent(shopUri(), compact); + } + public static BaseComponent shopLink() { - return homeLinkSafe("/shop"); + return ExceptionUtils.propagate(() -> shopLink(true)); } public static BaseComponent appealLink() { diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/NameStyle.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/NameStyle.java index 706c29c..0950ad4 100644 --- a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/NameStyle.java +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/NameStyle.java @@ -1,11 +1,10 @@ package tc.oc.commons.bukkit.chat; -import java.util.Collections; -import java.util.EnumSet; -import java.util.Set; - import com.google.common.collect.ForwardingSet; import com.google.common.collect.Sets; +import java.util.Collections; +import java.util.EnumSet; +import java.util.Set; /** * The formatting properties for each different context in which names are displayed. @@ -50,6 +49,13 @@ public class NameStyle extends ForwardingSet { ); + public static final NameStyle VERBOSE_SIMPLE = new NameStyle( + Sets.difference( + VERBOSE, + Sets.newHashSet(NameFlag.SELF, NameFlag.FRIEND) + ) + ); + // Fancy minus mapmaker flair (for display in map credits) public static final NameStyle MAPMAKER = new NameStyle( Sets.difference( diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/NameType.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/NameType.java index eb2b41d..41316fe 100644 --- a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/NameType.java +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/NameType.java @@ -1,11 +1,10 @@ package tc.oc.commons.bukkit.chat; -import org.bukkit.command.CommandSender; -import tc.oc.commons.bukkit.nick.Identity; +import static com.google.common.base.Preconditions.checkNotNull; import java.util.Objects; - -import static com.google.common.base.Preconditions.checkNotNull; +import org.bukkit.command.CommandSender; +import tc.oc.commons.bukkit.nick.Identity; /** * These are the parameters that determine how a player's name is diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/Paginator.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/Paginator.java index 051d879..7693cb6 100644 --- a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/Paginator.java +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/Paginator.java @@ -1,12 +1,14 @@ package tc.oc.commons.bukkit.chat; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterators; import java.util.Collection; import java.util.Iterator; import java.util.List; import javax.annotation.Nullable; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Iterators; import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.chat.TranslatableComponent; @@ -16,9 +18,6 @@ import tc.oc.commons.core.chat.Component; import tc.oc.commons.core.util.IndexedFunction; import tc.oc.commons.core.util.Numbers; -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; - public class Paginator { public static final int DEFAULT_PER_PAGE = 14; @@ -52,7 +51,7 @@ public class Paginator { } public void display(CommandSender sender, Collection results, int page) { - display(BukkitAudiences.getAudience(sender), results, page); + display(Audiences.Deprecated.get(sender), results, page); } public void display(Audience audience, Collection results, int page) { diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/PlayerComponent.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/PlayerComponent.java index e82c187..4d46814 100644 --- a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/PlayerComponent.java +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/PlayerComponent.java @@ -1,15 +1,14 @@ package tc.oc.commons.bukkit.chat; +import static com.google.common.base.Preconditions.checkNotNull; + import java.util.List; import java.util.Objects; - import net.md_5.bungee.api.chat.BaseComponent; import tc.oc.commons.bukkit.nick.Identity; import tc.oc.commons.core.chat.ImmutableComponent; import tc.oc.commons.core.util.Utils; -import static com.google.common.base.Preconditions.checkNotNull; - /** * A component that renders as a player's name. * diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/PlayerComponentRenderer.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/PlayerComponentRenderer.java index b9bbb83..755ec26 100644 --- a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/PlayerComponentRenderer.java +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/PlayerComponentRenderer.java @@ -2,7 +2,6 @@ package tc.oc.commons.bukkit.chat; import javax.inject.Inject; import javax.inject.Singleton; - import net.md_5.bungee.api.chat.BaseComponent; import org.bukkit.command.CommandSender; diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/TemplateComponent.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/TemplateComponent.java index 41d9d46..958342d 100644 --- a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/TemplateComponent.java +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/TemplateComponent.java @@ -1,8 +1,7 @@ package tc.oc.commons.bukkit.chat; -import java.util.List; - import com.google.common.collect.ImmutableList; +import java.util.List; import net.md_5.bungee.api.chat.BaseComponent; import org.bukkit.command.CommandSender; import tc.oc.commons.bukkit.localization.MessageTemplate; diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/TextComponentRenderer.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/TextComponentRenderer.java index cce9974..99bdc4e 100644 --- a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/TextComponentRenderer.java +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/TextComponentRenderer.java @@ -1,7 +1,6 @@ package tc.oc.commons.bukkit.chat; import javax.inject.Singleton; - import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.chat.TextComponent; import org.bukkit.command.CommandSender; diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/TranslatableComponentRenderer.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/TranslatableComponentRenderer.java index 671b415..5c1214f 100644 --- a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/TranslatableComponentRenderer.java +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/TranslatableComponentRenderer.java @@ -1,17 +1,16 @@ package tc.oc.commons.bukkit.chat; -import net.md_5.bungee.api.chat.BaseComponent; -import net.md_5.bungee.api.chat.TranslatableComponent; -import org.bukkit.command.CommandSender; -import tc.oc.commons.bukkit.localization.Translator; -import tc.oc.commons.core.chat.Component; -import tc.oc.commons.core.chat.Components; - import java.text.MessageFormat; import java.util.List; import java.util.Optional; import javax.inject.Inject; import javax.inject.Singleton; +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.TranslatableComponent; +import org.bukkit.command.CommandSender; +import tc.oc.commons.bukkit.localization.Translator; +import tc.oc.commons.core.chat.Component; +import tc.oc.commons.core.chat.Components; @Singleton public class TranslatableComponentRenderer extends BaseComponentRenderer { diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/UserTextComponent.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/UserTextComponent.java index 3a4d6ba..bba11b5 100644 --- a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/UserTextComponent.java +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/UserTextComponent.java @@ -3,7 +3,6 @@ package tc.oc.commons.bukkit.chat; import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; - import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.chat.ClickEvent; import net.md_5.bungee.api.chat.TextComponent; diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/UserURI.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/UserURI.java index d54c042..24cfa95 100644 --- a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/UserURI.java +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/UserURI.java @@ -2,7 +2,6 @@ package tc.oc.commons.bukkit.chat; import java.net.URI; import java.util.Optional; - import org.bukkit.command.CommandSender; import tc.oc.commons.bukkit.nick.Identity; diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/commands/CommandUtils.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/commands/CommandUtils.java index dda3716..0500781 100644 --- a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/commands/CommandUtils.java +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/commands/CommandUtils.java @@ -1,15 +1,20 @@ package tc.oc.commons.bukkit.commands; -import java.time.Duration; -import java.time.format.DateTimeParseException; -import java.util.List; -import java.util.Optional; -import javax.annotation.Nullable; - import com.sk89q.minecraft.util.commands.CommandContext; import com.sk89q.minecraft.util.commands.CommandException; import com.sk89q.minecraft.util.commands.CommandPermissionsException; +import java.time.Duration; +import java.time.format.DateTimeParseException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.annotation.Nullable; import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.chat.TranslatableComponent; import org.bukkit.Bukkit; import org.bukkit.ChatColor; @@ -20,9 +25,11 @@ import org.bukkit.permissions.Permission; import tc.oc.api.docs.PlayerId; import tc.oc.api.docs.Server; import tc.oc.commons.bukkit.chat.ComponentRenderers; +import tc.oc.commons.bukkit.chat.ListComponent; import tc.oc.commons.bukkit.localization.Translations; import tc.oc.commons.core.chat.Component; import tc.oc.commons.core.commands.TranslatableCommandException; +import tc.oc.commons.core.formatting.StringUtils; import tc.oc.commons.core.util.TimeUtils; public abstract class CommandUtils { @@ -124,14 +131,14 @@ public abstract class CommandUtils { } public static Duration getDuration(CommandContext args, int index, Duration def) throws CommandException { - return args.argsLength() > index ? getDuration(args.getString(index), null) : def; + return getDuration(args.getString(index, null), def); } - public static @Nullable Duration getDuration(@Nullable String text) throws CommandException { + public static @Nullable Duration getDuration(String text) throws CommandException { return getDuration(text, null); } - public static Duration getDuration(@Nullable String text, Duration def) throws CommandException { + public static Duration getDuration(String text, Duration def) throws CommandException { if(text == null) { return def; } else { @@ -143,6 +150,26 @@ public abstract class CommandUtils { } } + public static @Nullable > E getEnum(CommandContext args, CommandSender sender, int index, Class type) throws CommandException { + return getEnum(args, sender, index, type, null); + } + + public static > E getEnum(CommandContext args, CommandSender sender, int index, Class type, E def) throws CommandException { + return getEnum(args.getString(index, null), sender, type, def); + } + + public static > E getEnum(String text, CommandSender sender, Class type, E def) throws CommandException { + if(text == null) { + return def; + } else { + try { + return Enum.valueOf(type, text.toUpperCase().replace(' ', '_')); + } catch(IllegalArgumentException e) { + throw newCommandException(sender, new TranslatableComponent("command.error.invalidEnum", text)); + } + } + } + public static String getDisplayName(CommandSender target) { return getDisplayName(target, null); } @@ -198,4 +225,35 @@ public abstract class CommandUtils { public static void notEnoughArguments(CommandSender sender) throws CommandException { throw new CommandException(Translations.get().t("command.error.notEnoughArguments", sender)); } + + public static Map enumChoices(Class enumClass) { + return Stream.of(enumClass.getEnumConstants()) + .collect(Collectors.toMap(e -> e.name().toLowerCase().replaceAll("_", "-"), Function.identity())); + } + + public static List enumChoicesList(Class enumClass) { + return new ArrayList<>(enumChoices(enumClass).keySet()); + } + + public static @Nullable E tryEnum(String text, Class enumClass) { + return StringUtils.bestFuzzyMatch(text, enumChoices(enumClass), 0.8); + } + + public static E tryEnum(String text, Class enumClass, E def) { + final E option = tryEnum(text, enumClass); + return option == null ? def : option; + } + + public static E getEnum(String text, Class enumClass) throws CommandException { + final E option = tryEnum(text, enumClass); + if(option != null) { + return option; + } else { + throw new TranslatableCommandException("command.error.invalidOption", text, new ListComponent(enumChoicesList(enumClass), TextComponent::new)); + } + } + + public static List completeEnum(String prefix, Class enumClass) { + return StringUtils.complete(prefix, enumChoicesList(enumClass)); + } } diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/commands/GroupCommands.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/commands/GroupCommands.java new file mode 100644 index 0000000..1c5b6bb --- /dev/null +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/commands/GroupCommands.java @@ -0,0 +1,119 @@ +package tc.oc.commons.bukkit.commands; + +import com.sk89q.minecraft.util.commands.Command; +import com.sk89q.minecraft.util.commands.CommandContext; +import com.sk89q.minecraft.util.commands.CommandException; +import com.sk89q.minecraft.util.commands.CommandPermissionsException; +import com.sk89q.minecraft.util.commands.NestedCommand; +import java.time.Duration; +import java.time.Instant; +import javax.inject.Inject; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import tc.oc.api.bukkit.users.BukkitUserStore; +import tc.oc.api.docs.User; +import tc.oc.api.users.ChangeGroupRequest; +import tc.oc.api.users.UserService; +import tc.oc.commons.bukkit.chat.Audiences; +import tc.oc.commons.core.commands.Commands; +import tc.oc.commons.core.commands.NestedCommands; +import tc.oc.commons.core.concurrent.Flexecutor; +import tc.oc.commons.core.util.ThrowingBiConsumer; +import tc.oc.minecraft.scheduler.Sync; + +public class GroupCommands implements NestedCommands { + + public static class Parent implements Commands { + @Command( + aliases = { "group" }, + desc = "Commands to edit group membership", + min = 1, + max = -1 + ) + @NestedCommand(value = {GroupCommands.class}) + public void commands() throws CommandPermissionsException {} + } + + private final Flexecutor flexecutor; + private final BukkitUserStore userStore; + private final UserService userService; + private final UserFinder userFinder; + private final Audiences audiences; + + @Inject GroupCommands(@Sync Flexecutor flexecutor, BukkitUserStore userStore, UserService userService, UserFinder userFinder, Audiences audiences) { + this.flexecutor = flexecutor; + this.userStore = userStore; + this.userService = userService; + this.userFinder = userFinder; + this.audiences = audiences; + } + + public void edit(final CommandContext args, final CommandSender sender, boolean add, boolean expire, ThrowingBiConsumer consumer) throws CommandException { + if(!(sender instanceof ConsoleCommandSender)) throw new CommandPermissionsException(); + flexecutor.callback( + userFinder.findUser(sender, args, 0), + response -> { + String group = args.getString(1); + flexecutor.callback( + userService.changeGroup(response.user, new ChangeGroupRequest() { + public String group() { + return group; + } + public String type() { + return add ? "join" : (expire ? "expire" : "leave"); + } + public Instant end() { + try { + Duration duration = CommandUtils.getDuration(args, 2, null); + return add && duration != null ? Instant.now().plus(duration) : null; + } catch(CommandException e) { + return null; + } + } + }), + user -> consumer.acceptThrows(user, group) + ); + } + ); + } + + @Command( + aliases = { "join" }, + desc = "Add a player to a group", + usage = " [duration]", + min = 2, + max = 3 + ) + public void join(final CommandContext args, final CommandSender sender) throws CommandException { + edit(args, sender, true, false, (User user, String group) -> { + sender.sendMessage("Added " + user.username() + " to the " + group + " group"); + }); + } + + @Command( + aliases = { "leave" }, + desc = "Remove a player to a group", + usage = " ", + min = 2, + max = 2 + ) + public void leave(final CommandContext args, final CommandSender sender) throws CommandException { + edit(args, sender, false, false, (User user, String group) -> { + sender.sendMessage("Removed " + user.username() + " from the " + group + " group"); + }); + } + + @Command( + aliases = { "expire" }, + desc = "Expire a player's membership to a group", + usage = " ", + min = 2, + max = 2 + ) + public void expire(final CommandContext args, final CommandSender sender) throws CommandException { + edit(args, sender, false, true, (User user, String group) -> { + sender.sendMessage("Expired " + user.username() + "'s membership from the " + group + " group"); + }); + } + +} diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/commands/MiscCommands.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/commands/MiscCommands.java new file mode 100644 index 0000000..34fa48e --- /dev/null +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/commands/MiscCommands.java @@ -0,0 +1,359 @@ +package tc.oc.commons.bukkit.commands; + +import com.google.common.collect.Sets; +import com.sk89q.minecraft.util.commands.Command; +import com.sk89q.minecraft.util.commands.CommandContext; +import com.sk89q.minecraft.util.commands.CommandException; +import com.sk89q.minecraft.util.commands.CommandPermissions; +import com.sk89q.minecraft.util.commands.CommandPermissionsException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; +import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; +import javax.inject.Inject; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.chat.TextComponent; +import net.md_5.bungee.api.chat.TranslatableComponent; +import org.bukkit.Bukkit; +import org.bukkit.Color; +import org.bukkit.FireworkEffect; +import org.bukkit.FireworkEffect.Type; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Server; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Firework; +import org.bukkit.entity.Player; +import org.bukkit.inventory.meta.FireworkMeta; +import tc.oc.api.bukkit.users.BukkitUserStore; +import tc.oc.api.docs.virtual.UserDoc; +import tc.oc.api.users.UserService; +import tc.oc.commons.bukkit.chat.Audiences; +import tc.oc.commons.bukkit.chat.HeaderComponent; +import tc.oc.commons.bukkit.chat.PlayerComponent; +import tc.oc.commons.bukkit.nick.IdentityProvider; +import tc.oc.commons.core.chat.Audience; +import tc.oc.commons.core.chat.Component; +import tc.oc.commons.core.commands.Commands; +import tc.oc.commons.core.concurrent.Flexecutor; +import tc.oc.commons.core.stream.Collectors; +import tc.oc.commons.core.util.Streams; +import tc.oc.minecraft.protocol.MinecraftVersion; +import tc.oc.minecraft.scheduler.Sync; + +/** + * Commands for miscellaneous purposes. + */ +public class MiscCommands implements Commands { + + private static final Random RANDOM = new Random(); + + private final Flexecutor flexecutor; + private final UserService userService; + private final BukkitUserStore userStore; + private final UserFinder userFinder; + private final IdentityProvider identityProvider; + private final Audiences audiences; + + @Inject MiscCommands(@Sync Flexecutor flexecutor, UserService userService, BukkitUserStore userStore, UserFinder userFinder, IdentityProvider identityProvider, Audiences audiences) { + this.flexecutor = flexecutor; + this.userService = userService; + this.userStore = userStore; + this.userFinder = userFinder; + this.identityProvider = identityProvider; + this.audiences = audiences; + } + + @Command( + aliases = { "playerversion", "pv" }, + desc = "Shows statics on what version players online are using", + flags = "ad", + min = 0, + max = 1 + ) + @CommandPermissions("ocn.version") + public void listPlayerVersions(final CommandContext args, final CommandSender sender) throws CommandException { + Audience audience = audiences.get(sender); + if (args.hasFlag('a')) { + Map playerCountVersionMap = new HashMap<>(); + userStore.stream().forEach(player -> { + String version = MinecraftVersion.describeProtocol(player.getProtocolVersion(), !args.hasFlag('d')); + playerCountVersionMap.put(version, playerCountVersionMap.getOrDefault(version, 0) + 1); + }); + + audience.sendMessage(new HeaderComponent(new Component(ChatColor.AQUA).translate("list.player.versions.title"))); + for (Map.Entry entry : playerCountVersionMap.entrySet()) { + audience.sendMessage(new TranslatableComponent("list.player.versions.message." + (entry.getValue() == 1 ? "singular" : "plural"), + ChatColor.AQUA + entry.getValue().toString(), + ChatColor.AQUA + entry.getKey(), + String.format("%.1f", 100 * entry.getValue() / (double) userStore.count()) + "%")); + } + } else { + Player player = CommandUtils.getPlayerOrSelf(args, sender, 0); + audience.sendMessage(new TranslatableComponent("list.player.version.singular.message", new PlayerComponent(identityProvider.createIdentity(player)), ChatColor.AQUA + MinecraftVersion.describeProtocol(player.getProtocolVersion(), false))); + } + } + + @Command( + aliases = { "playerlocale", "locale" }, + desc = "Shows statics on what locale players online are in", + flags = "a", + min = 0, + max = 1 + ) + @CommandPermissions("ocn.locale") + public void listPlayerLocales(final CommandContext args, final CommandSender sender) throws CommandException { + Audience audience = audiences.get(sender); + if (args.hasFlag('a')) { + Map playerLocaleMap = userStore.stream().collect(java.util.stream.Collectors.groupingBy(Player::getLocale, java.util.stream.Collectors.counting())); + + audience.sendMessage(new HeaderComponent(new Component(ChatColor.AQUA).translate("list.player.locales.title"))); + for (Map.Entry entry : playerLocaleMap.entrySet()) { + audience.sendMessage(new TranslatableComponent("list.player.locales.message." + (entry.getValue() == 1 ? "singular" : "plural"), + ChatColor.AQUA + entry.getValue().toString(), + ChatColor.AQUA + entry.getKey(), + String.format("%.1f", 100 * entry.getValue() / (double) userStore.count()) + "%")); + } + } else { + Player player = CommandUtils.getPlayerOrSelf(args, sender, 0); + audience.sendMessage(new TranslatableComponent("list.player.locale.singular.message", new PlayerComponent(identityProvider.createIdentity(player)), ChatColor.AQUA + player.getLocale())); + } + } + + @Command( + aliases = { "coinflip" }, + desc = "Flip a Coin", + flags = "b", + min = 0, + max = 0 + ) + @CommandPermissions("coinflip") + public void coinFlip(final CommandContext args, final CommandSender sender) throws CommandException { + if (args.hasFlag('b')) { + Bukkit.broadcastMessage(ChatColor.AQUA + (Math.random() < 0.5 ? "Heads" : "Tails")); + } else { + sender.sendMessage(ChatColor.AQUA + (Math.random() < 0.5 ? "Heads" : "Tails")); + } + } + + @Command( + aliases = { "togglegravity" }, + usage = "", + desc = "Toggle a player's gravity.", + min = 0, + max = 1 + ) + @CommandPermissions("togglegravity") + public void noGravity(final CommandContext args, final CommandSender sender) throws CommandException { + Player player = CommandUtils.getPlayerOrSelf(args, sender, 0); + player.setGravity(!player.hasGravity()); + } + + @Command( + aliases = { "join-friend-tokens" }, + usage = " ", + desc = "Change the join friend tokens limit for a premium player", + min = 3 + ) + public void joinFriend(final CommandContext args, final CommandSender sender) throws CommandException { + if(!(sender instanceof ConsoleCommandSender)) throw new CommandPermissionsException(); + int concurrent = args.getInteger(1, 1); + int limit = args.getInteger(2, 3); + flexecutor.callback( + userFinder.findLocalPlayer(sender, args, 0), + response -> { + userService.update(response.user, new UserDoc.FriendTokens() { + @Override + public int friend_tokens_limit() { + return limit; + } + + @Override + public int friend_tokens_concurrent() { + return concurrent; + } + }); + } + ); + } + + @Command( + aliases = { "change-death-screen" }, + usage = " <+1/-1>", + desc = "Allow a player to change their death screen", + min = 2 + ) + public void deathScreen(final CommandContext args, final CommandSender sender) throws CommandException { + if(!(sender instanceof ConsoleCommandSender)) throw new CommandPermissionsException(); + boolean enable = args.getInteger(1, +1) > 0; + flexecutor.callback( + userFinder.findLocalPlayer(sender, args, 0), + response -> { + if((response.user.death_screen() == null) != enable) { + userService.update(response.user, (UserDoc.DeathScreen) () -> enable ? "default" : null); + } + } + ); + } + + @Command( + aliases = { "vice" }, + desc = "WELCOME BACK VICE!" + ) + public void vice(final CommandContext args, final CommandSender sender) throws CommandException { + Player player = CommandUtils.senderToPlayer(sender); + UUID vice = UUID.fromString("bf331953-4f92-43ee-8abc-7544b8234936"); + if (!(player.isOp() || player.getUniqueId().equals(vice))) throw new CommandPermissionsException(); + Set fireworkLocs = Sets.newHashSet(); + Location center = player.getLocation(); + if (!player.getUniqueId().equals(vice) && sender.getServer().getPlayer(vice) != null) + center = sender.getServer().getPlayer(vice).getLocation(); + center = center.clone(); + fireworkLocs.add(center); + int radius = 5; + for (int i = 0 - radius; i <= radius; i = i + (radius / 2)) { + if (i == 0) continue; + fireworkLocs.add(center.clone().add(i, 0, 0)); + fireworkLocs.add(center.clone().add(0, 0, i)); + fireworkLocs.add(center.clone().add(i, 0, i)); + } + for (Location location : fireworkLocs) { + FireworkMeta meta = (FireworkMeta) Bukkit.getItemFactory().getItemMeta(Material.FIREWORK); + meta.setPower(RANDOM.nextInt(15)); + meta.addEffect(randomRGBEffect()); + + Firework firework = (Firework) location.getWorld().spawnEntity(location, EntityType.FIREWORK); + firework.setFireworkMeta(meta); + } + sender.getServer().broadcast(new TextComponent( + ChatColor.RED + "WELCOME " + + ChatColor.YELLOW + "BACK " + + ChatColor.GREEN + "VICE" + + ChatColor.BLUE + "!!") + ); + } + + private FireworkEffect randomRGBEffect() { + return FireworkEffect.builder() + .flicker(RANDOM.nextBoolean()) + .trail(true) + .with(Type.values()[RANDOM.nextInt(4)]) + .withColor(Color.fromRGB(RANDOM.nextInt(255), RANDOM.nextInt(255), RANDOM.nextInt(255))) + .withColor(Color.fromRGB(RANDOM.nextInt(255), RANDOM.nextInt(255), RANDOM.nextInt(255))) + .withColor(Color.fromRGB(RANDOM.nextInt(255), RANDOM.nextInt(255), RANDOM.nextInt(255))) + .withFade(Color.fromRGB(RANDOM.nextInt(255), RANDOM.nextInt(255), RANDOM.nextInt(255))) + .build(); + } + + @Command( + aliases = { "sudo" }, + usage = " [command... (rand|mode|near|color|*)=value]", + desc = "Run a command as console or another player", + flags = "cd", + anyFlags = true, + min = 1, + max = -1 + ) + @CommandPermissions("sudo") + public void sudo(final CommandContext args, final CommandSender sender) throws CommandException { + Server server = sender.getServer(); + int index = 1; + CommandSender other = userStore.find(args.getString(0, "")); + if(other == null) { + other = args.hasFlag('c') ? server.getConsoleSender() : sender; + index = 0; + } + if(!sender.equals(other) && !sender.hasPermission("sudo.others")) { + throw new CommandPermissionsException(); + } + String command = args.getJoinedStrings(index); + List commands = getPermutations(sender, command); + String explanation; + if(commands.size() == 1) { + explanation = "/" + commands.get(0); + } else { + explanation = commands.size() + ChatColor.WHITE.toString() + " commands"; + } + sender.sendMessage("Executing " + ChatColor.AQUA + explanation + ChatColor.WHITE + " as " + identityProvider.currentIdentity(other).getName(sender)); + for(String cmd : commands) { + if(commands.size() > 1 && args.hasFlag('d')) { + sender.sendMessage(" > " + cmd); + } + server.dispatchCommand(other, cmd); + } + } + + public List getPermutations(CommandSender sender, String command) throws CommandException { + List permutations = new ArrayList<>(); + getPermutations(sender, command, permutations); + return permutations; + } + + public void getPermutations(CommandSender sender, String command, List commands) throws CommandException { + Matcher matcher = Pattern.compile("\\*|[A-Za-z]{1,}=[A-Za-z0-9_-]{1,}").matcher(command); + if(matcher.find()) { + String keyValue = matcher.group(); + for(String name : getPlayers(sender, keyValue).map(player -> player.getName(sender)).collect(Collectors.toImmutableList())) { + getPermutations(sender, matcher.replaceFirst(name), commands); + } + } else { + commands.add(command); + } + } + + public Stream getPlayers(CommandSender sender, String keyValue) throws CommandException { + Stream players = userStore.stream(); + int seperator = keyValue.indexOf("="); + String key = seperator != -1 ? keyValue.substring(0, seperator) : keyValue; + String value = seperator != -1 ? keyValue.substring(seperator + 1, keyValue.length()) : ""; + int parsed; + try { + parsed = Integer.parseInt(value); + } catch(NumberFormatException nfe) { + parsed = -1; + } + int valueInt = parsed; + switch(key) { + case "rand": + return players.collect(Collectors.toRandomSubList(valueInt)).stream(); + case "mode": + return players.filter(p -> p.getGameMode().getValue() == valueInt); + case "near": + Location location = CommandUtils.senderToPlayer(sender).getLocation(); + return players.filter(p -> p.getLocation().distance(location) <= valueInt); + case "color": + ChatColor color = CommandUtils.getEnum(value, sender, ChatColor.class, ChatColor.WHITE); + return players.filter(p -> getFuzzyColor(p).equals(color)); + case "perm": + return players.filter(p -> p.hasPermission(value)); + case "*": + return Streams.shuffle(players); + default: + throw new CommandException("Unrecognized player filter '" + key + "'"); + } + } + + public ChatColor getFuzzyColor(CommandSender sender) { + if(sender instanceof Player) { + Player player = (Player) sender; + Matcher matcher = ChatColor.STRIP_COLOR_PATTERN.matcher(player.getDisplayName(sender)); + String color = null; + while(matcher.find()) { + color = matcher.group(); + } + if(color != null) { + return ChatColor.getByChar(color.charAt(1)); + } + } + return ChatColor.WHITE; + } + +} diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/commands/PermissionCommands.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/commands/PermissionCommands.java index b3a1332..f84411a 100644 --- a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/commands/PermissionCommands.java +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/commands/PermissionCommands.java @@ -5,6 +5,11 @@ import com.sk89q.minecraft.util.commands.CommandContext; import com.sk89q.minecraft.util.commands.CommandException; import com.sk89q.minecraft.util.commands.CommandPermissions; import com.sk89q.minecraft.util.commands.NestedCommand; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; import org.bukkit.permissions.Permission; @@ -14,12 +19,6 @@ import tc.oc.api.util.Permissions; import tc.oc.commons.core.commands.Commands; import tc.oc.commons.core.commands.NestedCommands; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Map; - public class PermissionCommands implements NestedCommands { public static class Parent implements Commands { @Command( diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/commands/PrettyPaginatedResult.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/commands/PrettyPaginatedResult.java index 08e779f..7667731 100644 --- a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/commands/PrettyPaginatedResult.java +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/commands/PrettyPaginatedResult.java @@ -4,11 +4,10 @@ import com.google.common.base.Preconditions; import com.sk89q.minecraft.util.commands.CommandException; import com.sk89q.minecraft.util.commands.WrappedCommandSender; import com.sk89q.minecraft.util.pagination.PaginatedResult; +import java.util.List; import net.md_5.bungee.api.ChatColor; import tc.oc.commons.core.chat.ChatUtils; -import java.util.List; - public abstract class PrettyPaginatedResult extends PaginatedResult { protected final String header; diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/commands/ServerCommands.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/commands/ServerCommands.java index 96337f9..45774d4 100644 --- a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/commands/ServerCommands.java +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/commands/ServerCommands.java @@ -1,23 +1,28 @@ package tc.oc.commons.bukkit.commands; +import com.sk89q.minecraft.util.commands.Command; +import com.sk89q.minecraft.util.commands.CommandContext; +import com.sk89q.minecraft.util.commands.CommandException; +import com.sk89q.minecraft.util.commands.CommandPermissions; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Set; +import java.util.function.Consumer; import javax.inject.Inject; - -import com.sk89q.minecraft.util.commands.Command; -import com.sk89q.minecraft.util.commands.CommandContext; -import com.sk89q.minecraft.util.commands.CommandException; import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.chat.TranslatableComponent; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; +import tc.oc.api.bukkit.users.BukkitUserStore; import tc.oc.api.docs.Server; +import tc.oc.api.docs.User; import tc.oc.api.docs.virtual.ServerDoc; +import tc.oc.api.docs.virtual.UserDoc; import tc.oc.api.servers.ServerStore; +import tc.oc.api.users.UserService; import tc.oc.commons.bukkit.chat.Audiences; import tc.oc.commons.bukkit.chat.Paginator; import tc.oc.commons.bukkit.chat.WarningComponent; @@ -27,22 +32,31 @@ import tc.oc.commons.core.chat.Audience; import tc.oc.commons.core.chat.Component; import tc.oc.commons.core.commands.Commands; import tc.oc.commons.core.commands.TranslatableCommandException; +import tc.oc.commons.core.concurrent.Flexecutor; import tc.oc.commons.core.formatting.StringUtils; +import tc.oc.commons.core.util.Pair; +import tc.oc.minecraft.scheduler.Sync; public class ServerCommands implements Commands { + private final Flexecutor flexecutor; private final Server localServer; private final ServerStore serverStore; private final ServerFormatter formatter = ServerFormatter.light; private final Teleporter teleporter; + private final BukkitUserStore userStore; + private final UserService userService; private final Audiences audiences; private static final Comparator FULLNESS = Comparator.comparing(Server::num_online).reversed(); - @Inject ServerCommands(Server localServer, ServerStore serverStore, Teleporter teleporter, Audiences audiences) { + @Inject ServerCommands(@Sync Flexecutor flexecutor, Server localServer, ServerStore serverStore, Teleporter teleporter, BukkitUserStore userStore, UserService userService, Audiences audiences) { + this.flexecutor = flexecutor; this.localServer = localServer; this.serverStore = serverStore; this.teleporter = teleporter; + this.userStore = userStore; + this.userService = userService; this.audiences = audiences; } @@ -63,11 +77,12 @@ public class ServerCommands implements Commands { aliases = { "servers", "srvs" }, desc = "Show a listing of all servers on the network", usage = "[page]", + flags = "a", min = 0, max = 1 ) public void servers(final CommandContext args, final CommandSender sender) throws CommandException { - final List servers = new ArrayList<>(serverStore.subset(teleporter::isVisible)); + final List servers = new ArrayList<>(serverStore.subset(args.hasFlag('a') && sender.hasPermission("ocn.see-all-servers") ? teleporter::isConnectable : teleporter::isVisible)); Collections.sort(servers, FULLNESS); new Paginator() { @@ -90,16 +105,86 @@ public class ServerCommands implements Commands { flags = "bd:" ) public List server(CommandContext args, final CommandSender sender) throws CommandException { + Pair> response = find(args, sender, user -> teleporter.showCurrentServer(sender)); + if(response != null) { + Server route = response.first; + List suggestions = response.second; + if(suggestions != null) { + return suggestions; + } else if(route != null) { + if(route.equals(localServer)) { + teleporter.showCurrentServer(sender); + } else { + teleporter.remoteTeleport(CommandUtils.senderToPlayer(sender), route); + } + } else { + teleporter.sendToLobby(CommandUtils.senderToPlayer(sender), false); + } + } + return null; + } + + @Command( + aliases = { "default-server", "def-srv" }, + desc = "Set your default server when connecting to the network", + usage = "[-d datacenter] [name]", + flags = "bd:" + ) + @CommandPermissions("ocn.default-server") + public List defaultServer(final CommandContext args, CommandSender sender) throws CommandException { + Pair> response = find(args, sender, user -> { + audiences.get(sender).sendMessage( + new Component( + new TranslatableComponent( + "command.server.defaultServer.get", + serverStore.tryId(user.default_server_id()) + .map(server -> new Component(formatter.nameWithDatacenter(server))) + .orElse(new Component("Automatic", ChatColor.GREEN)) + ), ChatColor.DARK_PURPLE + ) + ); + }); + if(response != null) { + Server route = response.first; + List suggestions = response.second; + if(suggestions != null) { + return suggestions; + } else if(route != null) { + flexecutor.callback( + userService.update( + userStore.playerId(CommandUtils.senderToPlayer(sender)), + (UserDoc.DefaultServer) route::_id + ), user -> { + audiences.get(sender).sendMessage( + new Component( + new TranslatableComponent( + "command.server.defaultServer.set", + new Component(formatter.nameWithDatacenter(route)) + ), ChatColor.DARK_PURPLE + ) + ); + } + ); + } else { + teleporter.showCurrentServer(sender); + } + } + return null; + } + + public Pair> find(CommandContext args, CommandSender sender, Consumer show) throws CommandException { if(args.getSuggestionContext() != null) { - return StringUtils.complete(args.getJoinedStrings(0), + return Pair.create(null, StringUtils.complete(args.getJoinedStrings(0), serverStore.all() .filter(teleporter::isConnectable) - .map(Server::name)); + .map(Server::name))); } - // Show current server + // Return current server if(args.argsLength() == 0) { - teleporter.showCurrentServer(sender); + if(sender instanceof Player) { + show.accept(userStore.getUser((Player) sender)); + } return null; } @@ -111,8 +196,7 @@ public class ServerCommands implements Commands { if(byBungee == null) { throw new TranslatableCommandException("command.serverNotFound"); } - teleporter.remoteTeleport(player, byBungee); - return null; + return Pair.create(byBungee, null); } // Search by name/datacenter @@ -129,16 +213,14 @@ public class ServerCommands implements Commands { // Special aliases for the lobby if(name.equals("lobby") || name.equals("hub")) { - teleporter.remoteTeleport(player, datacenter, null, null); - return null; + return Pair.create(null, null); } final Set connectable = serverStore.subset(teleporter::isConnectable); final List partial = new ArrayList<>(); for(Server server : connectable) { if(server.name().equalsIgnoreCase(name)) { - teleporter.remoteTeleport(player, server); - return null; + return Pair.create(server, null); } if(StringUtils.startsWithIgnoreCase(server.name(), name)) { partial.add(server); diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/commands/ServerVisibilityCommands.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/commands/ServerVisibilityCommands.java index e1aa965..e32373e 100644 --- a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/commands/ServerVisibilityCommands.java +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/commands/ServerVisibilityCommands.java @@ -1,14 +1,13 @@ package tc.oc.commons.bukkit.commands; -import java.util.EnumSet; -import java.util.logging.Logger; -import javax.inject.Inject; - import com.google.common.util.concurrent.ListenableFuture; import com.sk89q.minecraft.util.commands.Command; import com.sk89q.minecraft.util.commands.CommandContext; import com.sk89q.minecraft.util.commands.CommandException; import com.sk89q.minecraft.util.commands.CommandPermissions; +import java.util.EnumSet; +import java.util.logging.Logger; +import javax.inject.Inject; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; @@ -19,12 +18,12 @@ import org.bukkit.event.player.PlayerQuitEvent; import tc.oc.api.docs.Server; import tc.oc.api.docs.virtual.ServerDoc; import tc.oc.api.minecraft.MinecraftService; -import tc.oc.minecraft.scheduler.SyncExecutor; import tc.oc.commons.bukkit.event.WhitelistStateChangeEvent; import tc.oc.commons.bukkit.whitelist.Whitelist; import tc.oc.commons.core.commands.CommandFutureCallback; import tc.oc.commons.core.commands.Commands; import tc.oc.commons.core.logging.Loggers; +import tc.oc.minecraft.scheduler.SyncExecutor; public class ServerVisibilityCommands implements Listener, Commands { diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/commands/TraceCommands.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/commands/TraceCommands.java index aec0f2f..a272e5e 100644 --- a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/commands/TraceCommands.java +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/commands/TraceCommands.java @@ -1,13 +1,14 @@ package tc.oc.commons.bukkit.commands; -import java.util.concurrent.atomic.AtomicBoolean; -import javax.inject.Inject; +import static tc.oc.commons.bukkit.commands.CommandUtils.getCommandSenderOrSelf; import com.sk89q.minecraft.util.commands.Command; import com.sk89q.minecraft.util.commands.CommandContext; import com.sk89q.minecraft.util.commands.CommandException; import com.sk89q.minecraft.util.commands.CommandPermissions; import com.sk89q.minecraft.util.commands.NestedCommand; +import java.util.concurrent.atomic.AtomicBoolean; +import javax.inject.Inject; import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; import org.bukkit.craftbukkit.event.AsyncClientConnectEvent; @@ -20,8 +21,6 @@ import tc.oc.commons.bukkit.util.PacketTracer; import tc.oc.commons.core.commands.Commands; import tc.oc.commons.core.commands.NestedCommands; -import static tc.oc.commons.bukkit.commands.CommandUtils.getCommandSenderOrSelf; - /** * Packet tracing commands */ diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/commands/UserCommands.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/commands/UserCommands.java index 157b912..04e34ac 100644 --- a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/commands/UserCommands.java +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/commands/UserCommands.java @@ -1,24 +1,29 @@ package tc.oc.commons.bukkit.commands; -import javax.inject.Inject; - import com.sk89q.minecraft.util.commands.Command; import com.sk89q.minecraft.util.commands.CommandContext; import com.sk89q.minecraft.util.commands.CommandException; import com.sk89q.minecraft.util.commands.CommandPermissions; +import javax.inject.Inject; import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.chat.TranslatableComponent; import org.bukkit.command.CommandSender; import tc.oc.api.bukkit.users.Users; +import tc.oc.api.docs.Friendship; import tc.oc.api.docs.PlayerId; +import tc.oc.api.docs.User; +import tc.oc.api.friendships.FriendshipRequest; +import tc.oc.api.friendships.FriendshipService; import tc.oc.api.minecraft.MinecraftService; -import tc.oc.minecraft.scheduler.SyncExecutor; import tc.oc.api.sessions.SessionService; -import tc.oc.commons.bukkit.chat.BukkitAudiences; +import tc.oc.commons.bukkit.chat.Audiences; import tc.oc.commons.bukkit.chat.ComponentPaginator; import tc.oc.commons.bukkit.chat.ComponentRenderers; import tc.oc.commons.bukkit.chat.HeaderComponent; +import tc.oc.commons.bukkit.chat.Links; +import tc.oc.commons.bukkit.chat.NameStyle; +import tc.oc.commons.bukkit.chat.PlayerComponent; import tc.oc.commons.bukkit.format.UserFormatter; import tc.oc.commons.bukkit.nick.IdentityProvider; import tc.oc.commons.core.chat.Audience; @@ -26,6 +31,8 @@ import tc.oc.commons.core.chat.Component; import tc.oc.commons.core.commands.CommandFutureCallback; import tc.oc.commons.core.commands.Commands; import tc.oc.commons.core.commands.TranslatableCommandException; +import tc.oc.commons.core.util.Lazy; +import tc.oc.minecraft.scheduler.SyncExecutor; /** * Commands for querying and possibly manipulating user records @@ -35,17 +42,21 @@ public class UserCommands implements Commands { private final MinecraftService minecraftService; private final SyncExecutor syncExecutor; private final SessionService sessionService; + private final FriendshipService friendshipService; private final UserFinder userFinder; private final IdentityProvider identityProvider; private final UserFormatter userFormatter; + private final Audiences audiences; - @Inject UserCommands(MinecraftService minecraftService, SyncExecutor syncExecutor, SessionService sessionService, UserFinder userFinder, IdentityProvider identityProvider, UserFormatter userFormatter) { + @Inject UserCommands(MinecraftService minecraftService, SyncExecutor syncExecutor, SessionService sessionService, FriendshipService friendshipService, UserFinder userFinder, IdentityProvider identityProvider, UserFormatter userFormatter, Audiences audiences) { this.minecraftService = minecraftService; this.syncExecutor = syncExecutor; this.sessionService = sessionService; + this.friendshipService = friendshipService; this.userFinder = userFinder; this.identityProvider = identityProvider; this.userFormatter = userFormatter; + this.audiences = audiences; } @Command( @@ -66,13 +77,13 @@ public class UserCommands implements Commands { } @Command( - aliases = { "friends", "fr", "fs" }, + aliases = { "friends", "frs" }, usage = "[page #]", desc = "Shows what servers your friends are on", min = 0, max = 1 ) - @CommandPermissions("projectares.friends.view") + @CommandPermissions("ocn.friend.list") public void friends(final CommandContext args, final CommandSender sender) throws CommandException { final PlayerId playerId = Users.playerId(CommandUtils.senderToPlayer(sender)); final int page = args.getInteger(0, 1); @@ -93,6 +104,95 @@ public class UserCommands implements Commands { ); } + @Command( + aliases = { "friend", "fr" }, + usage = "", + desc = "Send a friend request to a player", + min = 1, + max = 1 + ) + @CommandPermissions("ocn.friend.request") + public void friend(final CommandContext args, final CommandSender sender) throws CommandException { + User friender = userFinder.getLocalUser(CommandUtils.senderToPlayer(sender)); + Audience audience = audiences.get(sender); + syncExecutor.callback( + userFinder.findUser(sender, args, 0), + response -> { + Lazy friended = Lazy.from( + () -> new PlayerComponent(identityProvider.currentIdentity(response.user)) + ); + if(response.disguised) { + // If player is disguised pretend they do not accept friends + audience.sendWarning(new TranslatableComponent( + "friend.request.not_accepting", + friended.get() + ), false); + } else { + syncExecutor.callback( + friendshipService.create(FriendshipRequest.create( + friender.player_id(), + response.user.player_id() + )), + response1 -> { + if(response1.success()) { + Friendship friendship = response1.friendships().get(0); + audience.sendMessage(new TranslatableComponent( + "friend.request." + (friendship.accepted() ? "accepted" : "sent"), + friended.get() + )); + } else { + audience.sendWarning(new TranslatableComponent( + "friend.request." + response1.error(), + friended.get(), + Links.shopLink(true) + ), false); + } + } + ); + } + } + ); + } + + @Command( + aliases = { "unfriend", "unfr" }, + usage = "", + desc = "Withdraw a friend request or unfriend a current friend", + min = 1, + max = 1 + ) + @CommandPermissions("ocn.friend.request") + public void unfriend(final CommandContext args, final CommandSender sender) throws CommandException { + User friender = userFinder.getLocalUser(CommandUtils.senderToPlayer(sender)); + Audience audience = audiences.get(sender); + syncExecutor.callback( + userFinder.findUser(sender, args, 0), + response -> { + boolean were = friender.friends().contains(response.user); + syncExecutor.callback( + friendshipService.destroy(FriendshipRequest.create( + friender.player_id(), + response.user.player_id() + )), + response1 -> { + PlayerComponent friended = new PlayerComponent(identityProvider.currentIdentity(response.user)); + if(response1.success()) { + audience.sendMessage(new TranslatableComponent( + "friend.unrequest." + (were ? "success" : "withdraw"), + friended + )); + } else { + audience.sendWarning(new TranslatableComponent( + "friend.unrequest." + response1.error(), + friended + ), false); + } + } + ); + } + ); + } + @Command( aliases = { "staff", "mods" }, desc = "List staff members who are on the network right now", @@ -105,7 +205,7 @@ public class UserCommands implements Commands { syncExecutor.callback( sessionService.staff(minecraftService.getLocalServer().network(), identityProvider.revealAll(sender)), CommandFutureCallback.onSuccess(sender, args, result -> { - final Audience audience = BukkitAudiences.getAudience(sender); + final Audience audience = audiences.get(sender); if(result.documents().isEmpty()) { audience.sendMessage(new TranslatableComponent("command.staff.noStaffOnline")); return; @@ -118,7 +218,7 @@ public class UserCommands implements Commands { .extra(new Component(String.valueOf(result.documents().size()), ChatColor.AQUA)) .extra(")") )); - userFormatter.formatSessions(result.documents()).forEach(audience::sendMessage); + userFormatter.formatSessions(result.documents(), NameStyle.COLOR).forEach(audience::sendMessage); }) ); } diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/commands/UserFinder.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/commands/UserFinder.java index 1029076..fbd56f3 100644 --- a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/commands/UserFinder.java +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/commands/UserFinder.java @@ -1,15 +1,14 @@ package tc.oc.commons.bukkit.commands; -import javax.annotation.Nullable; -import javax.inject.Inject; -import javax.inject.Singleton; - import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import com.sk89q.minecraft.util.commands.CommandContext; import com.sk89q.minecraft.util.commands.CommandException; +import javax.annotation.Nullable; +import javax.inject.Inject; +import javax.inject.Singleton; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import tc.oc.api.bukkit.users.BukkitUserStore; @@ -21,10 +20,10 @@ import tc.oc.api.users.UserSearchRequest; import tc.oc.api.users.UserSearchResponse; import tc.oc.api.users.UserService; import tc.oc.commons.bukkit.nick.IdentityProvider; -import tc.oc.minecraft.scheduler.MainThreadExecutor; import tc.oc.commons.bukkit.users.PlayerSearchResponse; import tc.oc.commons.core.commands.TranslatableCommandException; import tc.oc.commons.core.util.Orderable; +import tc.oc.minecraft.scheduler.MainThreadExecutor; @Singleton public class UserFinder { @@ -140,6 +139,19 @@ public class UserFinder { public ListenableFuture findPlayer(CommandSender sender, @Nullable String name, Scope scope, Default def) { try { + if (name == null || name.isEmpty()) { + switch(def) { + case NULL: + return Futures.immediateFuture(null); + + case SENDER: + return Futures.immediateFuture(localPlayerResponse(sender, senderToPlayer(sender))); + + default: + throw new TranslatableCommandException("command.specifyPlayer"); + } + } + final Player player = getLocalPlayer(sender, name); if(player != null) { return Futures.immediateFuture(localPlayerResponse(sender, player)); diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/config/ExternalConfiguration.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/config/ExternalConfiguration.java index 615ee11..e3cb9c0 100644 --- a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/config/ExternalConfiguration.java +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/config/ExternalConfiguration.java @@ -7,15 +7,14 @@ import java.util.logging.Level; import javax.annotation.Nullable; import javax.inject.Inject; import javax.inject.Named; - import org.bukkit.configuration.Configuration; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.file.YamlConfiguration; import tc.oc.commons.bukkit.logging.MapdevLogger; -import tc.oc.minecraft.scheduler.MainThreadExecutor; import tc.oc.file.PathWatcher; import tc.oc.file.PathWatcherService; import tc.oc.minecraft.api.configuration.InvalidConfigurationException; +import tc.oc.minecraft.scheduler.MainThreadExecutor; /** * Base class for a configuration section that is loaded from the external diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/debug/LeakListener.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/debug/LeakListener.java index b918c1a..461774f 100644 --- a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/debug/LeakListener.java +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/debug/LeakListener.java @@ -1,14 +1,13 @@ package tc.oc.commons.bukkit.debug; +import java.time.Duration; import javax.annotation.Nullable; import javax.inject.Inject; - import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.world.WorldUnloadEvent; -import java.time.Duration; import tc.oc.api.bukkit.users.BukkitUserStore; import tc.oc.api.docs.virtual.Model; import tc.oc.api.model.ModelDispatcher; diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/event/InterfaceOpenEvent.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/event/InterfaceOpenEvent.java new file mode 100644 index 0000000..23b2d0d --- /dev/null +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/event/InterfaceOpenEvent.java @@ -0,0 +1,36 @@ +package tc.oc.commons.bukkit.event; + + +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import tc.oc.commons.bukkit.gui.Interface; + +public class InterfaceOpenEvent extends Event { + private static final HandlerList handlers = new HandlerList(); + + private final Interface gui; + private final Player player; + + public InterfaceOpenEvent(Interface gui, Player player) { + this.gui = gui; + this.player = player; + } + + public Interface getInterface() { + return this.gui; + } + + public Player getPlayer() { + return this.player; + } + + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + +} diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/event/PlayerServerChangeEvent.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/event/PlayerServerChangeEvent.java index 618784e..70de808 100644 --- a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/event/PlayerServerChangeEvent.java +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/event/PlayerServerChangeEvent.java @@ -1,14 +1,13 @@ package tc.oc.commons.bukkit.event; -import javax.annotation.Nullable; +import static com.google.common.base.Preconditions.checkNotNull; +import javax.annotation.Nullable; import net.md_5.bungee.api.chat.BaseComponent; import org.bukkit.entity.Player; import org.bukkit.event.HandlerList; import org.bukkit.event.PlayerAction; -import static com.google.common.base.Preconditions.checkNotNull; - public class PlayerServerChangeEvent extends ExtendedCancellable implements PlayerAction { private final Player player; diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/event/UserLoginEvent.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/event/UserLoginEvent.java index 222d75d..61deeaf 100644 --- a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/event/UserLoginEvent.java +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/event/UserLoginEvent.java @@ -1,7 +1,8 @@ package tc.oc.commons.bukkit.event; -import javax.annotation.Nullable; +import static com.google.common.base.Preconditions.checkNotNull; +import javax.annotation.Nullable; import net.md_5.bungee.api.chat.BaseComponent; import org.bukkit.entity.Player; import org.bukkit.event.Event; @@ -11,8 +12,6 @@ import tc.oc.api.docs.Session; import tc.oc.api.docs.User; import tc.oc.api.users.LoginResponse; -import static com.google.common.base.Preconditions.checkNotNull; - /** * Fired from within {@link PlayerLoginEvent}, and inludes our {@link User} document. * It also fires after various login info has been loaded e.g. PlayerId, permissions, diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/flairs/FlairConfiguration.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/flairs/FlairConfiguration.java new file mode 100644 index 0000000..e594be2 --- /dev/null +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/flairs/FlairConfiguration.java @@ -0,0 +1,25 @@ +package tc.oc.commons.bukkit.flairs; + +import static com.google.common.base.Preconditions.checkNotNull; + +import javax.inject.Inject; +import org.bukkit.configuration.Configuration; +import org.bukkit.configuration.ConfigurationSection; + +public class FlairConfiguration { + + private final ConfigurationSection config; + + @Inject + FlairConfiguration(Configuration config) { + this.config = checkNotNull(config.getConfigurationSection("flairs")); + } + + public boolean overheadFlair() { + return config.getBoolean("overhead", false); + } + + public int maxFlairs() { + return config.getInt("limit", -1); + } +} diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/FlairRenderer.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/flairs/FlairRenderer.java similarity index 55% rename from Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/FlairRenderer.java rename to Commons/bukkit/src/main/java/tc/oc/commons/bukkit/flairs/FlairRenderer.java index de5aa57..d3a3036 100644 --- a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/chat/FlairRenderer.java +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/flairs/FlairRenderer.java @@ -1,14 +1,18 @@ -package tc.oc.commons.bukkit.chat; - -import java.util.Set; -import javax.inject.Inject; -import javax.inject.Singleton; +package tc.oc.commons.bukkit.flairs; import com.google.common.collect.ImmutableSet; +import java.util.Set; +import java.util.stream.Stream; +import javax.inject.Inject; +import javax.inject.Singleton; +import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.chat.BaseComponent; import tc.oc.api.bukkit.users.BukkitUserStore; import tc.oc.api.docs.virtual.UserDoc; import tc.oc.api.minecraft.MinecraftService; +import tc.oc.commons.bukkit.chat.NameFlag; +import tc.oc.commons.bukkit.chat.NameType; +import tc.oc.commons.bukkit.chat.PartialNameRenderer; import tc.oc.commons.bukkit.nick.Identity; import tc.oc.commons.core.chat.Components; @@ -20,16 +24,27 @@ public class FlairRenderer implements PartialNameRenderer { private final MinecraftService minecraftService; private final BukkitUserStore userStore; + private final FlairConfiguration flairConfiguration; - @Inject protected FlairRenderer(MinecraftService minecraftService, BukkitUserStore userStore) { + @Inject protected FlairRenderer(MinecraftService minecraftService, BukkitUserStore userStore, FlairConfiguration flairConfiguration) { this.minecraftService = minecraftService; this.userStore = userStore; + this.flairConfiguration = flairConfiguration; } @Override public String getLegacyName(Identity identity, NameType type) { if(!(type.style.contains(NameFlag.FLAIR) && type.reveal)) return ""; + if(identity.isConsole()) return ChatColor.GOLD + "❖"; + return getFlairs(identity).reduce("", String::concat); + } + @Override + public BaseComponent getComponentName(Identity identity, NameType type) { + return Components.fromLegacyText(getLegacyName(identity, type)); + } + + public Stream getFlairs(Identity identity) { final UserDoc.Identity user; if(identity.getPlayerId() instanceof UserDoc.Identity) { // Flair may already be stashed inside the Identity @@ -37,19 +52,21 @@ public class FlairRenderer implements PartialNameRenderer { } else { user = userStore.tryUser(identity.getPlayerId()); } - if(user == null) return ""; + if(user == null) return Stream.empty(); final Set realms = ImmutableSet.copyOf(minecraftService.getLocalServer().realms()); return user.minecraft_flair() - .stream() - .filter(flair -> realms.contains(flair.realm)) - .map(flair -> flair.text) - .reduce("", String::concat); + .stream() + .filter(flair -> realms.contains(flair.realm) && flair.text != null && !flair.text.isEmpty()) + .sorted((flair1, flair2) -> flair1.priority - flair2.priority) + .limit(flairConfiguration.maxFlairs() < 0 ? Long.MAX_VALUE : flairConfiguration.maxFlairs()) + .sorted((flair1, flair2) -> flair2.priority - flair1.priority) + .map(flair -> flair.text); } - @Override - public BaseComponent getComponentName(Identity identity, NameType type) { - return Components.fromLegacyText(getLegacyName(identity, type)); + public int getNumberOfFlairs(Identity identity) { + return (int) getFlairs(identity).count(); } + } diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/format/GameFormatter.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/format/GameFormatter.java index 75d0a1a..133be40 100644 --- a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/format/GameFormatter.java +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/format/GameFormatter.java @@ -3,7 +3,6 @@ package tc.oc.commons.bukkit.format; import java.util.Collection; import java.util.Optional; import javax.inject.Inject; - import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.chat.ClickEvent; @@ -210,7 +209,10 @@ public class GameFormatter { } public void sendList(Audience audience, Collection games) { - if(games.isEmpty()) return; + if(games.isEmpty()) { + audience.sendMessage(new WarningComponent("game.none")); + return; + } audience.sendMessage( new Component( diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/format/ServerFormatter.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/format/ServerFormatter.java index 3ff7f5a..f1a3990 100644 --- a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/format/ServerFormatter.java +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/format/ServerFormatter.java @@ -1,17 +1,16 @@ package tc.oc.commons.bukkit.format; +import java.time.Duration; +import java.time.Instant; import java.util.Comparator; import java.util.Optional; import javax.annotation.Nullable; import javax.inject.Inject; - import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.chat.ClickEvent; import net.md_5.bungee.api.chat.HoverEvent; import net.md_5.bungee.api.chat.TranslatableComponent; -import java.time.Duration; -import java.time.Instant; import tc.oc.api.docs.virtual.MatchDoc; import tc.oc.api.docs.virtual.ServerDoc; import tc.oc.api.minecraft.MinecraftService; diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/format/UserFormatter.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/format/UserFormatter.java index d448cfe..c0c70d2 100644 --- a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/format/UserFormatter.java +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/format/UserFormatter.java @@ -1,19 +1,18 @@ package tc.oc.commons.bukkit.format; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.SetMultimap; +import java.time.Duration; +import java.time.Instant; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import javax.annotation.Nullable; import javax.inject.Inject; - -import com.google.common.collect.HashMultimap; -import com.google.common.collect.SetMultimap; import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.chat.TranslatableComponent; -import java.time.Duration; -import java.time.Instant; import tc.oc.api.docs.Server; import tc.oc.api.docs.Session; import tc.oc.api.docs.virtual.ServerDoc; @@ -88,10 +87,14 @@ public class UserFormatter { } public List formatSessions(Collection sessions) { - return formatSessions(sessions, minecraftService.getLocalServer()); + return formatSessions(sessions, NameStyle.FANCY); } - public List formatSessions(Collection sessions, ServerDoc.Identity localServer) { + public List formatSessions(Collection sessions, NameStyle style) { + return formatSessions(sessions, minecraftService.getLocalServer(), style); + } + + public List formatSessions(Collection sessions, ServerDoc.Identity localServer, NameStyle style) { List lines = new ArrayList<>(); SetMultimap namesByServer = HashMultimap.create(); @@ -100,7 +103,7 @@ public class UserFormatter { if(session.end() == null) { final Server server = serverStore.byId(session.server_id()); if(server != null) { - namesByServer.put(server, new PlayerComponent(identityProvider.createIdentity(session), NameStyle.FANCY)); + namesByServer.put(server, new PlayerComponent(identityProvider.createIdentity(session), style)); } } } diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/freeze/PlayerFreezer.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/freeze/PlayerFreezer.java index 830c3ba..af53633 100644 --- a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/freeze/PlayerFreezer.java +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/freeze/PlayerFreezer.java @@ -1,13 +1,15 @@ package tc.oc.commons.bukkit.freeze; +import static tc.oc.minecraft.protocol.MinecraftVersion.MINECRAFT_1_8; +import static tc.oc.minecraft.protocol.MinecraftVersion.lessThan; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.SetMultimap; import java.time.Duration; import java.util.Map; import java.util.WeakHashMap; import javax.inject.Inject; import javax.inject.Singleton; - -import com.google.common.collect.HashMultimap; -import com.google.common.collect.SetMultimap; import org.bukkit.World; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -15,8 +17,10 @@ import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.world.WorldUnloadEvent; +import tc.oc.commons.bukkit.event.CoarsePlayerMoveEvent; import tc.oc.commons.bukkit.util.NMSHacks; import tc.oc.commons.core.plugin.PluginFacet; +import tc.oc.commons.core.util.Pair; import tc.oc.minecraft.api.scheduler.Tickable; /** @@ -27,6 +31,7 @@ public class PlayerFreezer implements PluginFacet, Listener, Tickable { private final Map armorStands = new WeakHashMap<>(); private final SetMultimap frozenPlayers = HashMultimap.create(); + private final Map> legacyFrozenPlayers = new WeakHashMap<>(); @Inject PlayerFreezer() {} @@ -53,6 +58,14 @@ public class PlayerFreezer implements PluginFacet, Listener, Tickable { player.leaveVehicle(); // TODO: Put them back in the vehicle when thawed? armorStand(player).spawn(player, player.getLocation()); sendAttach(player); + if(lessThan(MINECRAFT_1_8, player.getProtocolVersion())) { + boolean canFly = player.getAllowFlight(), isFlying = player.isFlying(); + legacyFrozenPlayers.put(player, Pair.create(canFly, isFlying)); + if(!player.isOnGround()) { + player.setAllowFlight(true); + player.setFlying(true); + } + } } return frozenPlayer; @@ -70,9 +83,17 @@ public class PlayerFreezer implements PluginFacet, Listener, Tickable { armorStand(player).ride(player, player); } + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onMove(CoarsePlayerMoveEvent event) { + if(isFrozen(event.getPlayer()) && legacyFrozenPlayers.containsKey(event.getPlayer())) { + event.setCancelled(true); + } + } + @EventHandler(priority = EventPriority.MONITOR) public void onQuit(PlayerQuitEvent event) { frozenPlayers.removeAll(event.getPlayer()); + legacyFrozenPlayers.remove(event.getPlayer()); } @EventHandler(priority = EventPriority.MONITOR) @@ -96,6 +117,11 @@ public class PlayerFreezer implements PluginFacet, Listener, Tickable { if(frozenPlayers.remove(player, this) && !isFrozen(player) && player.isOnline()) { armorStand(player).destroy(player); player.setPaused(false); + Pair fly = legacyFrozenPlayers.remove(player); + if(fly != null) { + player.setFlying(fly.second); + player.setAllowFlight(fly.first); + } } } } diff --git a/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/gui/Interface.java b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/gui/Interface.java new file mode 100644 index 0000000..8f847f8 --- /dev/null +++ b/Commons/bukkit/src/main/java/tc/oc/commons/bukkit/gui/Interface.java @@ -0,0 +1,81 @@ +package tc.oc.commons.bukkit.gui; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.bukkit.entity.HumanEntity; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import tc.oc.commons.bukkit.gui.buttons.Button; + +public class Interface { + + protected Player player; + private List