This commit is contained in:
Jaimexo 2019-06-19 21:09:43 +00:00 committed by GitHub
commit 990aa8c7f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
623 changed files with 18651 additions and 4486 deletions

View File

@ -5,7 +5,7 @@
<groupId>tc.oc</groupId>
<artifactId>api-parent</artifactId>
<relativePath>../pom.xml</relativePath>
<version>1.11-SNAPSHOT</version>
<version>1.12.2-SNAPSHOT</version>
</parent>
<artifactId>api</artifactId>

View File

@ -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());
}
}

View File

@ -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());
});
}
}

View File

@ -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 {}

View File

@ -0,0 +1,5 @@
package tc.oc.api.docs;
import tc.oc.api.docs.virtual.FriendshipDoc;
public interface Friendship extends FriendshipDoc.Complete {}

View File

@ -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
}
}

View File

@ -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();
}
}

View File

@ -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> gamemode();
List<Team> teams();

View File

@ -30,11 +30,7 @@ public interface MatchDoc extends Model {
Collection<String> winning_team_ids();
Collection<String> winning_user_ids();
enum Mutation {
BLITZ, UHC, EXPLOSIVES, NO_FALL, MOBS, STRENGTH, DOUBLE_JUMP, INVISIBILITY, LIGHTNING, RAGE, ELYTRA;
}
Set<Mutation> mutations();
Set<String> mutations();
@Serialize
interface Team extends MapDoc.Team, CompetitorDoc {

View File

@ -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

View File

@ -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<String, String> plugin_versions();
Set<Integer> 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<UUID, String> 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<MatchDoc.Mutation> queued_mutations();
Set<String> queued_mutations();
}
@Serialize
interface Rotations extends Partial {
List<Rotation> rotations();
}
@Serialize
interface Rotation extends Document {
String name();
String next_map_id();
}
/**

View File

@ -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();

View File

@ -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<String> 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<String, Map<String, Map<String, Object>>> stats_value();
Map<String, Map<String, Boolean>> mc_permissions_by_realm();
Map<String, Map<String, String>> mc_settings_by_profile();
Map<String, String> 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();
}
}

View File

@ -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);
}
}

View File

@ -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; }
};
}
}

View File

@ -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<Friendship> friendships();
}

View File

@ -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<Friendship, FriendshipDoc.Partial> {
ListenableFuture<FriendshipResponse> create(FriendshipRequest request);
ListenableFuture<FriendshipResponse> destroy(FriendshipRequest request);
ListenableFuture<FriendshipResponse> list(FriendshipRequest request);
}

View File

@ -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<Friendship, FriendshipDoc.Partial> implements FriendshipService {
@Override
public ListenableFuture<FriendshipResponse> create(FriendshipRequest request) {
return Futures.immediateFailedFuture(new NotFound());
}
@Override
public ListenableFuture<FriendshipResponse> destroy(FriendshipRequest request) {
return Futures.immediateFailedFuture(new NotFound());
}
@Override
public ListenableFuture<FriendshipResponse> list(FriendshipRequest request) {
return Futures.immediateFailedFuture(new NotFound());
}
}

View File

@ -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);
}
}

View File

@ -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();
}

View File

@ -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;
}
};
}

View File

@ -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<Report> {
@Serialize private final @Nullable String server_id;
@Serialize private final @Nullable Collection<String> 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<String> 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<String> 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

View File

@ -33,7 +33,5 @@ public class TypeAdaptersManifest extends Manifest {
gson.bindAdapter(new TypeLiteral<Set<MapDoc.Gamemode>>(){})
.to(new TypeLiteral<LenientEnumSetTypeAdapter<MapDoc.Gamemode>>(){});
gson.bindAdapter(new TypeLiteral<Set<MatchDoc.Mutation>>(){})
.to(new TypeLiteral<LenientEnumSetTypeAdapter<MatchDoc.Mutation>>(){});
}
}

View File

@ -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<Server, ServerDoc.Partial> implements ServerService {
@ -12,4 +14,8 @@ public class NullServerService extends NullModelService<Server, ServerDoc.Partia
public ListenableFuture<?> doBungeeMetric(BungeeMetricRequest request) {
return Futures.immediateFuture(null);
}
@Override public ListenableFuture<UseServerResponse> requestServer(UseServerRequest request) {
return Futures.immediateFuture(UseServerResponse.EMPTY);
}
}

View File

@ -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<Server, ServerDoc.Partial> {
ListenableFuture<?> doBungeeMetric(BungeeMetricRequest request);
ListenableFuture<UseServerResponse> requestServer(UseServerRequest request);
}

View File

@ -23,8 +23,11 @@ import static com.google.common.base.Preconditions.checkArgument;
@Singleton
public class ServerStore extends ModelStore<Server> {
private final SetMultimap<String, Server> byName = HashMultimap.create();
private final Map<String, Server> byBungeeName = new HashMap<>();
private final SetMultimap<ServerDoc.Role, Server> byRole = HashMultimap.create();
private final SetMultimap<ServerDoc.Network, Server> byNetwork = HashMultimap.create();
private final SetMultimap<String, Server> byFamily = HashMultimap.create();
private final SetMultimap<String, Server> byArenaId = HashMultimap.create();
@Override
@ -32,6 +35,18 @@ public class ServerStore extends ModelStore<Server> {
return new ServerSearchRequest();
}
public ImmutableSet<Server> byName(String name) {
return ImmutableSet.copyOf(byName.get(name));
}
public ImmutableSet<Server> byNetwork(ServerDoc.Network network) {
return ImmutableSet.copyOf(byNetwork.get(network));
}
public ImmutableSet<Server> 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<Server> {
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<Server> {
@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);
}

View File

@ -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();
}

View File

@ -15,5 +15,7 @@ public interface SessionStartRequest extends Document {
InetAddress ip();
String version();
@Nullable String previous_session_id();
}

View File

@ -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();
}

View File

@ -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; }
};
}
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -31,10 +31,20 @@ public class NullUserService extends NullModelService<User, UserDoc.Partial> imp
}
@Override
public ListenableFuture<UserUpdateResponse> creditRaindrops(UserId userId, CreditRaindropsRequest request) {
public ListenableFuture<UserUpdateResponse> creditTokens(UserId userId, CreditTokensRequest request) {
return Futures.immediateFuture(UserUpdateResponse.FAILURE);
}
@Override
public ListenableFuture<User> changeGroup(UserId userId, ChangeGroupRequest request) {
return Futures.immediateFailedFuture(new NotFound());
}
@Override
public ListenableFuture<FriendJoinResponse> joinFriend(UserId userId, FriendJoinRequest request) {
return Futures.immediateFailedFuture(new NotFound());
}
@Override
public ListenableFuture<User> purchaseGizmo(UserId userId, PurchaseGizmoRequest request) {
return Futures.immediateFailedFuture(new NotFound());

View File

@ -19,11 +19,11 @@ public interface UserService extends ModelService<User, UserDoc.Partial> {
ListenableFuture<?> logout(LogoutRequest request);
default ListenableFuture<UserUpdateResponse> creditRaindrops(UserId userId, int raindrops) {
return creditRaindrops(userId, () -> raindrops);
}
ListenableFuture<UserUpdateResponse> creditTokens(UserId userId, CreditTokensRequest request);
ListenableFuture<UserUpdateResponse> creditRaindrops(UserId userId, CreditRaindropsRequest request);
ListenableFuture<User> changeGroup(UserId userId, ChangeGroupRequest request);
ListenableFuture<FriendJoinResponse> joinFriend(UserId userId, FriendJoinRequest request);
ListenableFuture<User> purchaseGizmo(UserId userId, PurchaseGizmoRequest request);

View File

@ -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<String, Boolean> mergePermissions(Collection<String> realms, Map<String, Map<String, Boolean>> permsByRealm) {
static Map<String, Boolean> mergePermissions(Collection<String> realms, Map<String, Map<String, Boolean>> permsByRealm) {
Map<String, Boolean> effectivePerms = new HashMap<>();
for(String realm : realms) {
Map<String, Boolean> 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.
* <code>
* enum Trig {
* SOH, CAH, TOA
* }
* </code>
* 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 <E extends Enum> List<E> enumPermissions(CommandSender sender, String base, Class<E> enumClass) {
final List<E> enums = Lists.newArrayList(enumClass.getEnumConstants());
final Function<E, String> 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 <E extends Enum> boolean hasPermissionForEnum(CommandSender sender, String base, E selected) {
return enumPermissions(sender, base, (Class<E>) selected.getClass()).contains(selected);
}
}

View File

@ -5,7 +5,7 @@
<groupId>tc.oc</groupId>
<artifactId>api-parent</artifactId>
<relativePath>../pom.xml</relativePath>
<version>1.11-SNAPSHOT</version>
<version>1.12.2-SNAPSHOT</version>
</parent>
<artifactId>api-bukkit</artifactId>

View File

@ -5,7 +5,7 @@
<groupId>tc.oc</groupId>
<artifactId>api-parent</artifactId>
<relativePath>../pom.xml</relativePath>
<version>1.11-SNAPSHOT</version>
<version>1.12.2-SNAPSHOT</version>
</parent>
<artifactId>api-bungee</artifactId>

View File

@ -5,7 +5,7 @@
<groupId>tc.oc</groupId>
<artifactId>api-parent</artifactId>
<relativePath>../pom.xml</relativePath>
<version>1.11-SNAPSHOT</version>
<version>1.12.2-SNAPSHOT</version>
</parent>
<artifactId>api-minecraft</artifactId>

View File

@ -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);
}

View File

@ -12,4 +12,6 @@ public interface MinecraftApiConfiguration extends ApiConfiguration {
String box();
ServerDoc.Role role();
boolean publishIp();
}

View File

@ -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();

View File

@ -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(

View File

@ -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<UUID, String> fake_usernames() {
return Collections.emptyMap();
@ -279,7 +289,12 @@ public class LocalServerDocument extends StartupServerDocument implements Server
}
@Override
public Set<MatchDoc.Mutation> queued_mutations() {
public Set<String> queued_mutations() {
return mutations != null ? mutations.queued_mutations() : Collections.emptySet();
}
@Override
public List<ServerDoc.Rotation> rotations() {
return Collections.emptyList();
}
}

View File

@ -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<Server, ServerDoc.Parti
request.documents().forEach(this::update);
return super.updateMulti(request);
}
@Override public ListenableFuture<UseServerResponse> requestServer(UseServerRequest request) {
return Futures.immediateFuture(UseServerResponse.EMPTY);
}
}

View File

@ -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<String> 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<Integer> protocol_versions() {
return minecraftServer.getProtocolVersions();
}
public String ip() {
return ip.get();
}
}

View File

@ -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;

View File

@ -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<String, Map<String, Map<String, Object>>> stats_value() {
return Collections.emptyMap();
}
@Override
public Map<String, Map<String, Boolean>> 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;
}
}

View File

@ -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<User, UserDoc.Partial> implement
return Futures.immediateFuture(null);
}
@Override
public ListenableFuture<UserUpdateResponse> creditRaindrops(UserId userId, CreditRaindropsRequest request) {
private ListenableFuture<UserUpdateResponse> update(UserId userId) {
return FutureUtils.mapSync(find(userId), user -> new UserUpdateResponse() {
@Override
public boolean success() {
@ -133,6 +122,31 @@ class LocalUserService extends NullModelService<User, UserDoc.Partial> implement
});
}
@Override
public ListenableFuture<UserUpdateResponse> creditTokens(UserId userId, CreditTokensRequest request) {
return update(userId);
}
@Override
public ListenableFuture<User> changeGroup(UserId userId, ChangeGroupRequest request) {
return find(userId);
}
@Override
public ListenableFuture<FriendJoinResponse> 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<User> purchaseGizmo(UserId userId, PurchaseGizmoRequest request) {
return find(userId);

View File

@ -5,7 +5,7 @@
<groupId>tc.oc</groupId>
<artifactId>api-parent</artifactId>
<relativePath>../pom.xml</relativePath>
<version>1.11-SNAPSHOT</version>
<version>1.12.2-SNAPSHOT</version>
</parent>
<artifactId>api-ocn</artifactId>

View File

@ -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<Friendship, FriendshipDoc.Partial> implements FriendshipService {
@Override
public ListenableFuture<FriendshipResponse> create(FriendshipRequest request) {
return this.client().post(collectionUri("create"), request, FriendshipResponse.class, HttpOption.INFINITE_RETRY);
}
@Override
public ListenableFuture<FriendshipResponse> destroy(FriendshipRequest request) {
return this.client().post(collectionUri("destroy"), request, FriendshipResponse.class, HttpOption.INFINITE_RETRY);
}
@Override
public ListenableFuture<FriendshipResponse> list(FriendshipRequest request) {
return this.client().post(collectionUri("list"), request, FriendshipResponse.class, HttpOption.INFINITE_RETRY);
}
}

View File

@ -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);
}
});
}

View File

@ -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<Server, ServerDoc.Partial> implements ServerService {
private final QueueQueryService<Server> queryService;
private final Transaction.Factory transactionFactory;
@Inject OCNServerService(QueueQueryService<Server> queryService) {
@Inject public OCNServerService(QueueQueryService<Server> queryService, Factory transactionFactory) {
this.queryService = queryService;
this.transactionFactory = transactionFactory;
}
@Override
@ -29,6 +35,10 @@ class OCNServerService extends HttpModelService<Server, ServerDoc.Partial> imple
return this.client().post("/servers/metric", request, HttpOption.INFINITE_RETRY);
}
@Override public ListenableFuture<UseServerResponse> requestServer(UseServerRequest request) {
return transactionFactory.request(request, UseServerResponse.class);
}
@Override
public ListenableFuture<FindMultiResponse<Server>> all() {
return queryService.all();

View File

@ -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<User, UserDoc.Partial> implements
}
@Override
public ListenableFuture<UserUpdateResponse> creditRaindrops(UserId userId, CreditRaindropsRequest request) {
return handleUserUpdate(client().post(memberUri(userId, "credit_raindrops"), request, UserUpdateResponse.class, HttpOption.INFINITE_RETRY));
public ListenableFuture<User> purchaseGizmo(UserId userId, PurchaseGizmoRequest request) {
return handleUpdate(client().post(memberUri(userId, "purchase_gizmo"), request, User.class, HttpOption.INFINITE_RETRY));
}
@Override
public ListenableFuture<User> purchaseGizmo(UserId userId, PurchaseGizmoRequest request) {
return handleUpdate(client().post(memberUri(userId, "purchase_gizmo"), request, User.class, HttpOption.INFINITE_RETRY));
public ListenableFuture<UserUpdateResponse> creditTokens(UserId userId, CreditTokensRequest request) {
return handleUserUpdate(client().post(memberUri(userId, "credit_tokens"), request, UserUpdateResponse.class, HttpOption.INFINITE_RETRY));
}
@Override
public ListenableFuture<User> changeGroup(UserId userId, ChangeGroupRequest request) {
return handleUpdate(client().post(memberUri(userId, "change_group"), request, User.class, HttpOption.INFINITE_RETRY));
}
@Override
public ListenableFuture<FriendJoinResponse> joinFriend(UserId userId, FriendJoinRequest request) {
return client().post(memberUri(userId, "join_friend"), request, FriendJoinResponse.class, HttpOption.INFINITE_RETRY);
}
@Override

View File

@ -5,7 +5,7 @@
<groupId>tc.oc</groupId>
<artifactId>ProjectAres</artifactId>
<relativePath>../pom.xml</relativePath>
<version>1.11-SNAPSHOT</version>
<version>1.12.2-SNAPSHOT</version>
</parent>
<artifactId>api-parent</artifactId>

View File

@ -8,7 +8,7 @@
<artifactId>commons</artifactId>
<groupId>tc.oc</groupId>
<relativePath>../pom.xml</relativePath>
<version>1.11-SNAPSHOT</version>
<version>1.12.2-SNAPSHOT</version>
</parent>
<artifactId>commons-bukkit</artifactId>
@ -41,18 +41,6 @@
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.github.rmsy.Channels</groupId>
<artifactId>Channels</artifactId>
<version>1.9-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>org.bukkit</groupId>
<artifactId>bukkit</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>me.anxuiz</groupId>
<artifactId>bukkit-settings</artifactId>

View File

@ -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;

View File

@ -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;

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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<List<BroadcastSchedule>>
}
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)
);
}

View File

@ -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() {

View File

@ -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<String> destinations(@Nullable ChatDoc.Destination type) {
Stream<String> 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 = "<destination type> <destination name> [message...]",
min = 1
)
public List<String> broadcast(final CommandContext args, final CommandSender sender) throws CommandException {
SuggestionContext suggest = args.getSuggestionContext();
ChatDoc.Destination type = tryEnum(args.getString(0, ""), ChatDoc.Destination.class);
Set<String> 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()))
)
)
);
}
}

View File

@ -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;
}

View File

@ -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<BroadcastSet> messages;
@Inspect private final ServerFilter serverFilter;
public BroadcastSchedule(Duration interval, ServerFilter serverFilter, Stream<BroadcastSet> messages) {
public BroadcastSchedule(Duration delay, Duration interval, ServerFilter serverFilter, Stream<BroadcastSet> 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.
*

View File

@ -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 {

View File

@ -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 <message...>");
}
} 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<CommandSender> viewers() {
return Stream.<CommandSender>concat(Stream.of(console),
players.all().stream())
.filter(this::isVisible);
}
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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);
}
});
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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<Player, Channel> 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<Channel> getChannel(ChatDoc.Type type) {
return getChannel(null, type);
}
public Optional<Channel> getChannel(Chat chat) {
return getChannel(userStore.find(chat.sender()), chat.type());
}
public Optional<Channel> 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<User> 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<Player, Channel> function) {
teamChannelFunction = function != null ? function : sender -> serverChannel;
}
}

View File

@ -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());
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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<Chat> message) {
final Chat chat = message.document();
if(shouldAnnounce(chat)) {
final ChatDoc.Type type = chat.type();
final Optional<Channel> 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;
}
}

View File

@ -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<Chat, ChatDoc.Partial> chatService;
private final BatchUpdater<ChatDoc.Partial> chatBatchUpdater;
private final Server server;
@Inject ChatCreator(IdFactory idFactory, ModelService<Chat, ChatDoc.Partial> chatService, BatchUpdaterFactory<ChatDoc.Partial> chatBatchUpdaterFactory, Server server) {
this.idFactory = idFactory;
this.chatService = chatService;
this.chatBatchUpdater = chatBatchUpdaterFactory.createBatchUpdater(Duration.ofMinutes(1));
this.server = server;
}
public ListenableFuture<Chat> chat(@Nullable PlayerId sender, String message, ChatDoc.Type type, Consumer<Chat> callback) {
return send(sender, message, type, null, callback);
}
public ListenableFuture<Chat> 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<Chat> send(@Nullable PlayerId sender, String message, ChatDoc.Type type, @Nullable ChatDoc.Broadcast broadcast, @Nullable Consumer<Chat> 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(); }
};
}
}

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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() {

View File

@ -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<NameFlag> {
);
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(

View File

@ -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

View File

@ -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<T> {
public static final int DEFAULT_PER_PAGE = 14;
@ -52,7 +51,7 @@ public class Paginator<T> {
}
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) {

View File

@ -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.
*

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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<TranslatableComponent> {

View File

@ -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;

View File

@ -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;

View File

@ -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 extends Enum<E>> E getEnum(CommandContext args, CommandSender sender, int index, Class<E> type) throws CommandException {
return getEnum(args, sender, index, type, null);
}
public static <E extends Enum<E>> E getEnum(CommandContext args, CommandSender sender, int index, Class<E> type, E def) throws CommandException {
return getEnum(args.getString(index, null), sender, type, def);
}
public static <E extends Enum<E>> E getEnum(String text, CommandSender sender, Class<E> 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 <E extends Enum> Map<String, E> enumChoices(Class<E> enumClass) {
return Stream.of(enumClass.getEnumConstants())
.collect(Collectors.toMap(e -> e.name().toLowerCase().replaceAll("_", "-"), Function.identity()));
}
public static <E extends Enum> List<String> enumChoicesList(Class<E> enumClass) {
return new ArrayList<>(enumChoices(enumClass).keySet());
}
public static @Nullable <E extends Enum> E tryEnum(String text, Class<E> enumClass) {
return StringUtils.bestFuzzyMatch(text, enumChoices(enumClass), 0.8);
}
public static <E extends Enum> E tryEnum(String text, Class<E> enumClass, E def) {
final E option = tryEnum(text, enumClass);
return option == null ? def : option;
}
public static <E extends Enum> E getEnum(String text, Class<E> 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 <E extends Enum> List<String> completeEnum(String prefix, Class<E> enumClass) {
return StringUtils.complete(prefix, enumChoicesList(enumClass));
}
}

View File

@ -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<User, String, Exception> 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 = "<player> <group> [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 = "<player> <group>",
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 = "<player> <group>",
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");
});
}
}

View File

@ -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<String, Integer> 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<String, Integer> 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<String, Long> 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<String, Long> 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 = "<player>",
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 = "<player> <concurrent> <limit>",
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 = "<player> <+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<Location> 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 = "<player> [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<String> 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<String> getPermutations(CommandSender sender, String command) throws CommandException {
List<String> permutations = new ArrayList<>();
getPermutations(sender, command, permutations);
return permutations;
}
public void getPermutations(CommandSender sender, String command, List<String> 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<Player> getPlayers(CommandSender sender, String keyValue) throws CommandException {
Stream<Player> 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;
}
}

View File

@ -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(

View File

@ -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<T> extends PaginatedResult<T> {
protected final String header;

Some files were not shown because too many files have changed in this diff Show More