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 extends Audience> 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 extends T> results, int page) {
- display(BukkitAudiences.getAudience(sender), results, page);
+ display(Audiences.Deprecated.get(sender), results, page);
}
public void display(Audience audience, Collection extends T> 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