Support for website revamp

This commit is contained in:
Electroid 2017-08-15 01:52:42 -07:00
parent 7e330b786f
commit 8b51c184ab
45 changed files with 886 additions and 124 deletions

View File

@ -2,6 +2,7 @@ package tc.oc.api;
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 +45,6 @@ public final class ApiManifest extends HybridManifest {
install(new WhisperModelManifest());
install(new TrophyModelManifest());
install(new TournamentModelManifest());
install(new FriendshipModelManifest());
}
}

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

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

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

@ -79,12 +79,13 @@ 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 {
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();
@ -112,4 +113,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

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

@ -1,9 +0,0 @@
package tc.oc.api.users;
import tc.oc.api.annotations.Serialize;
import tc.oc.api.docs.virtual.Document;
@Serialize
public interface CreditMutationtokensRequest extends Document {
int mutationtokens();
}

View File

@ -1,9 +0,0 @@
package tc.oc.api.users;
import tc.oc.api.annotations.Serialize;
import tc.oc.api.docs.virtual.Document;
@Serialize
public interface CreditRaindropsRequest extends Document {
int raindrops();
}

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 CreditMaptokensRequest extends Document {
int maptokens();
public interface FriendJoinResponse extends Document {
boolean authorized();
String message();
}

View File

@ -31,25 +31,25 @@ 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());
}
@Override
public ListenableFuture<UserUpdateResponse> creditMaptokens(UserId userId, CreditMaptokensRequest request) {
return Futures.immediateFuture(UserUpdateResponse.FAILURE);
}
@Override
public ListenableFuture<UserUpdateResponse> creditMutationtokens(UserId userId, CreditMutationtokensRequest request) {
return Futures.immediateFuture(UserUpdateResponse.FAILURE);
}
@Override
public <T extends UserDoc.Partial> ListenableFuture<User> update(UserId userId, T update) {
return Futures.immediateFailedFuture(new NotFound());

View File

@ -19,26 +19,14 @@ 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);
default ListenableFuture<UserUpdateResponse> creditMaptokens(UserId userId, int maptokens) {
return creditMaptokens(userId, () -> maptokens);
}
ListenableFuture<UserUpdateResponse> creditMaptokens(UserId userId, CreditMaptokensRequest request);
default ListenableFuture<UserUpdateResponse> creditMutationtokens(UserId userId, int mutationtokens) {
return creditMutationtokens(userId, () -> mutationtokens);
}
ListenableFuture<UserUpdateResponse> creditMutationtokens(UserId userId, CreditMutationtokensRequest request);
<T extends UserDoc.Partial> ListenableFuture<User> update(UserId userId, T update);
ListenableFuture<User> changeSetting(UserId userId, ChangeSettingRequest request);

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

@ -40,6 +40,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;
@ -136,4 +141,24 @@ 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;
}
}

View File

@ -108,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() {
@ -123,41 +122,36 @@ 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);
}
@Override
public ListenableFuture<UserUpdateResponse> creditMaptokens(UserId userId, CreditMaptokensRequest request) {
return FutureUtils.mapSync(find(userId), user -> new UserUpdateResponse() {
@Override
public boolean success() {
return true;
}
@Override
public User user() {
return user;
}
});
}
@Override
public ListenableFuture<UserUpdateResponse> creditMutationtokens(UserId userId, CreditMutationtokensRequest request) {
return FutureUtils.mapSync(find(userId), user -> new UserUpdateResponse() {
@Override
public boolean success() {
return true;
}
@Override
public User user() {
return user;
}
});
}
@Override
public <T extends UserDoc.Partial> ListenableFuture<User> update(UserId userId, T update) {
return find(userId);

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

@ -13,6 +13,7 @@ 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;
@ -58,7 +59,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 +72,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

@ -71,24 +71,24 @@ class OCNUserService extends HttpModelService<User, UserDoc.Partial> implements
return client().post(memberUri(request.player_id, "logout"), request, HttpOption.INFINITE_RETRY);
}
@Override
public ListenableFuture<UserUpdateResponse> creditRaindrops(UserId userId, CreditRaindropsRequest request) {
return handleUserUpdate(client().post(memberUri(userId, "credit_raindrops"), request, UserUpdateResponse.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));
}
@Override
public ListenableFuture<UserUpdateResponse> creditMaptokens(UserId userId, CreditMaptokensRequest request) {
return handleUserUpdate(client().post(memberUri(userId, "credit_maptokens"), request, UserUpdateResponse.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<UserUpdateResponse> creditMutationtokens(UserId userId, CreditMutationtokensRequest request) {
return handleUserUpdate(client().post(memberUri(userId, "credit_mutationtokens"), request, UserUpdateResponse.class, HttpOption.INFINITE_RETRY));
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

@ -195,6 +195,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

@ -0,0 +1,118 @@
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 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;
import javax.inject.Inject;
import java.time.Instant;
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 {
return add ? Instant.now().plus(CommandUtils.getDuration(args, 2, null)) : 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

@ -1,5 +1,6 @@
package tc.oc.commons.bukkit.commands;
import com.google.common.util.concurrent.Futures;
import com.sk89q.minecraft.util.commands.Command;
import com.sk89q.minecraft.util.commands.CommandContext;
import com.sk89q.minecraft.util.commands.CommandException;
@ -11,8 +12,12 @@ import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Server;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.entity.Player;
import tc.oc.api.bukkit.users.BukkitUserStore;
import tc.oc.api.docs.User;
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;
@ -20,9 +25,11 @@ 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;
import javax.inject.Inject;
import java.util.ArrayList;
@ -38,13 +45,18 @@ import java.util.stream.Stream;
*/
public class MiscCommands implements Commands {
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(BukkitUserStore userStore, IdentityProvider identityProvider, 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;
}
@ -108,6 +120,34 @@ public class MiscCommands implements Commands {
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 = { "sudo" },
usage = "<player> [command... (rand|mode|near|color|*)=value]",

View File

@ -5,19 +5,26 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import javax.inject.Inject;
import com.sk89q.minecraft.util.commands.Command;
import com.sk89q.minecraft.util.commands.CommandContext;
import com.sk89q.minecraft.util.commands.CommandException;
import com.sk89q.minecraft.util.commands.CommandPermissions;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TranslatableComponent;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import tc.oc.api.bukkit.users.BukkitUserStore;
import tc.oc.api.docs.Server;
import tc.oc.api.docs.User;
import tc.oc.api.docs.virtual.ServerDoc;
import tc.oc.api.docs.virtual.UserDoc;
import tc.oc.api.servers.ServerStore;
import tc.oc.api.users.UserService;
import tc.oc.commons.bukkit.chat.Audiences;
import tc.oc.commons.bukkit.chat.Paginator;
import tc.oc.commons.bukkit.chat.WarningComponent;
@ -27,22 +34,31 @@ import tc.oc.commons.core.chat.Audience;
import tc.oc.commons.core.chat.Component;
import tc.oc.commons.core.commands.Commands;
import tc.oc.commons.core.commands.TranslatableCommandException;
import tc.oc.commons.core.concurrent.Flexecutor;
import tc.oc.commons.core.formatting.StringUtils;
import tc.oc.commons.core.util.Pair;
import tc.oc.minecraft.scheduler.Sync;
public class ServerCommands implements Commands {
private final Flexecutor flexecutor;
private final Server localServer;
private final ServerStore serverStore;
private final ServerFormatter formatter = ServerFormatter.light;
private final Teleporter teleporter;
private final BukkitUserStore userStore;
private final UserService userService;
private final Audiences audiences;
private static final Comparator<Server> FULLNESS = Comparator.comparing(Server::num_online).reversed();
@Inject ServerCommands(Server localServer, ServerStore serverStore, Teleporter teleporter, Audiences audiences) {
@Inject ServerCommands(@Sync Flexecutor flexecutor, Server localServer, ServerStore serverStore, Teleporter teleporter, BukkitUserStore userStore, UserService userService, Audiences audiences) {
this.flexecutor = flexecutor;
this.localServer = localServer;
this.serverStore = serverStore;
this.teleporter = teleporter;
this.userStore = userStore;
this.userService = userService;
this.audiences = audiences;
}
@ -90,16 +106,86 @@ public class ServerCommands implements Commands {
flags = "bd:"
)
public List<String> server(CommandContext args, final CommandSender sender) throws CommandException {
Pair<Server, List<String>> response = find(args, sender, user -> teleporter.showCurrentServer(sender));
if(response != null) {
Server route = response.first;
List<String> suggestions = response.second;
if(suggestions != null) {
return suggestions;
} else if(route != null) {
if(route.equals(localServer)) {
teleporter.showCurrentServer(sender);
} else {
teleporter.remoteTeleport(CommandUtils.senderToPlayer(sender), route);
}
} else {
teleporter.sendToLobby(CommandUtils.senderToPlayer(sender), false);
}
}
return null;
}
@Command(
aliases = { "default-server", "def-srv" },
desc = "Set your default server when connecting to the network",
usage = "[-d datacenter] [name]",
flags = "bd:"
)
@CommandPermissions("ocn.default-server")
public List<String> defaultServer(final CommandContext args, CommandSender sender) throws CommandException {
Pair<Server, List<String>> response = find(args, sender, user -> {
audiences.get(sender).sendMessage(
new Component(
new TranslatableComponent(
"command.server.defaultServer.get",
serverStore.tryId(user.default_server_id())
.map(server -> new Component(formatter.nameWithDatacenter(server)))
.orElse(new Component("Automatic", ChatColor.GREEN))
), ChatColor.DARK_PURPLE
)
);
});
if(response != null) {
Server route = response.first;
List<String> suggestions = response.second;
if(suggestions != null) {
return suggestions;
} else if(route != null) {
flexecutor.callback(
userService.update(
userStore.playerId(CommandUtils.senderToPlayer(sender)),
(UserDoc.DefaultServer) route::_id
), user -> {
audiences.get(sender).sendMessage(
new Component(
new TranslatableComponent(
"command.server.defaultServer.set",
new Component(formatter.nameWithDatacenter(route))
), ChatColor.DARK_PURPLE
)
);
}
);
} else {
teleporter.showCurrentServer(sender);
}
}
return null;
}
public Pair<Server, List<String>> find(CommandContext args, CommandSender sender, Consumer<User> show) throws CommandException {
if(args.getSuggestionContext() != null) {
return StringUtils.complete(args.getJoinedStrings(0),
return Pair.create(null, StringUtils.complete(args.getJoinedStrings(0),
serverStore.all()
.filter(teleporter::isConnectable)
.map(Server::name));
.map(Server::name)));
}
// Show current server
// Return current server
if(args.argsLength() == 0) {
teleporter.showCurrentServer(sender);
if(sender instanceof Player) {
show.accept(userStore.getUser((Player) sender));
}
return null;
}
@ -111,8 +197,7 @@ public class ServerCommands implements Commands {
if(byBungee == null) {
throw new TranslatableCommandException("command.serverNotFound");
}
teleporter.remoteTeleport(player, byBungee);
return null;
return Pair.create(byBungee, null);
}
// Search by name/datacenter
@ -129,16 +214,14 @@ public class ServerCommands implements Commands {
// Special aliases for the lobby
if(name.equals("lobby") || name.equals("hub")) {
teleporter.remoteTeleport(player, datacenter, null, null);
return null;
return Pair.create(null, null);
}
final Set<Server> connectable = serverStore.subset(teleporter::isConnectable);
final List<Server> partial = new ArrayList<>();
for(Server server : connectable) {
if(server.name().equalsIgnoreCase(name)) {
teleporter.remoteTeleport(player, server);
return null;
return Pair.create(server, null);
}
if(StringUtils.startsWithIgnoreCase(server.name(), name)) {
partial.add(server);

View File

@ -11,8 +11,15 @@ import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TranslatableComponent;
import org.bukkit.command.CommandSender;
import tc.oc.api.bukkit.users.Users;
import tc.oc.api.docs.Friendship;
import tc.oc.api.docs.PlayerId;
import tc.oc.api.docs.User;
import tc.oc.api.friendships.FriendshipRequest;
import tc.oc.api.friendships.FriendshipService;
import tc.oc.api.minecraft.MinecraftService;
import tc.oc.commons.bukkit.chat.Links;
import tc.oc.commons.bukkit.chat.PlayerComponent;
import tc.oc.commons.core.util.Lazy;
import tc.oc.minecraft.scheduler.SyncExecutor;
import tc.oc.api.sessions.SessionService;
import tc.oc.commons.bukkit.chat.BukkitAudiences;
@ -35,14 +42,16 @@ public class UserCommands implements Commands {
private final MinecraftService minecraftService;
private final SyncExecutor syncExecutor;
private final SessionService sessionService;
private final FriendshipService friendshipService;
private final UserFinder userFinder;
private final IdentityProvider identityProvider;
private final UserFormatter userFormatter;
@Inject UserCommands(MinecraftService minecraftService, SyncExecutor syncExecutor, SessionService sessionService, UserFinder userFinder, IdentityProvider identityProvider, UserFormatter userFormatter) {
@Inject UserCommands(MinecraftService minecraftService, SyncExecutor syncExecutor, SessionService sessionService, FriendshipService friendshipService, UserFinder userFinder, IdentityProvider identityProvider, UserFormatter userFormatter) {
this.minecraftService = minecraftService;
this.syncExecutor = syncExecutor;
this.sessionService = sessionService;
this.friendshipService = friendshipService;
this.userFinder = userFinder;
this.identityProvider = identityProvider;
this.userFormatter = userFormatter;
@ -66,13 +75,13 @@ public class UserCommands implements Commands {
}
@Command(
aliases = { "friends", "fr", "fs" },
aliases = { "friends", "frs" },
usage = "[page #]",
desc = "Shows what servers your friends are on",
min = 0,
max = 1
)
@CommandPermissions("projectares.friends.view")
@CommandPermissions("ocn.friend.list")
public void friends(final CommandContext args, final CommandSender sender) throws CommandException {
final PlayerId playerId = Users.playerId(CommandUtils.senderToPlayer(sender));
final int page = args.getInteger(0, 1);
@ -93,6 +102,95 @@ public class UserCommands implements Commands {
);
}
@Command(
aliases = { "friend", "fr" },
usage = "<player>",
desc = "Send a friend request to a player",
min = 1,
max = 1
)
@CommandPermissions("ocn.friend.request")
public void friend(final CommandContext args, final CommandSender sender) throws CommandException {
User friender = userFinder.getLocalUser(CommandUtils.senderToPlayer(sender));
Audience audience = BukkitAudiences.getAudience(sender);
syncExecutor.callback(
userFinder.findUser(sender, args, 0),
response -> {
Lazy<PlayerComponent> friended = Lazy.from(
() -> new PlayerComponent(identityProvider.currentIdentity(response.user))
);
if(response.disguised) {
// If player is disguised pretend they do not accept friends
audience.sendWarning(new TranslatableComponent(
"friend.request.not_accepting",
friended.get()
), false);
} else {
syncExecutor.callback(
friendshipService.create(FriendshipRequest.create(
friender.player_id(),
response.user.player_id()
)),
response1 -> {
if(response1.success()) {
Friendship friendship = response1.friendships().get(0);
audience.sendMessage(new TranslatableComponent(
"friend.request." + (friendship.accepted() ? "accepted" : "sent"),
friended.get()
));
} else {
audience.sendWarning(new TranslatableComponent(
"friend.request." + response1.error(),
friended.get(),
Links.shopLink(true)
), false);
}
}
);
}
}
);
}
@Command(
aliases = { "unfriend", "unfr" },
usage = "<player>",
desc = "Withdraw a friend request or unfriend a current friend",
min = 1,
max = 1
)
@CommandPermissions("ocn.friend.request")
public void unfriend(final CommandContext args, final CommandSender sender) throws CommandException {
User friender = userFinder.getLocalUser(CommandUtils.senderToPlayer(sender));
Audience audience = BukkitAudiences.getAudience(sender);
syncExecutor.callback(
userFinder.findUser(sender, args, 0),
response -> {
boolean were = friender.friends().contains(response.user);
syncExecutor.callback(
friendshipService.destroy(FriendshipRequest.create(
friender.player_id(),
response.user.player_id()
)),
response1 -> {
PlayerComponent friended = new PlayerComponent(identityProvider.currentIdentity(response.user));
if(response1.success()) {
audience.sendMessage(new TranslatableComponent(
"friend.unrequest." + (were ? "success" : "withdraw"),
friended
));
} else {
audience.sendWarning(new TranslatableComponent(
"friend.unrequest." + response1.error(),
friended
), false);
}
}
);
}
);
}
@Command(
aliases = { "staff", "mods" },
desc = "List staff members who are on the network right now",

View File

@ -1,5 +1,7 @@
package tc.oc.commons.bukkit.nick;
import java.time.Duration;
import java.time.Instant;
import java.util.Objects;
import java.util.stream.Stream;
import javax.annotation.Nullable;
@ -27,11 +29,14 @@ import org.bukkit.permissions.PermissionAttachment;
import org.bukkit.permissions.PermissionDefault;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
import tc.oc.api.bukkit.users.BukkitUserStore;
import tc.oc.api.bukkit.users.OnlinePlayers;
import tc.oc.api.docs.PlayerId;
import tc.oc.api.docs.User;
import tc.oc.api.docs.virtual.UserDoc;
import tc.oc.api.exceptions.UnprocessableEntity;
import tc.oc.commons.core.commands.TranslatableCommandException;
import tc.oc.commons.core.formatting.PeriodFormats;
import tc.oc.minecraft.scheduler.SyncExecutor;
import tc.oc.api.users.UserService;
import tc.oc.commons.bukkit.chat.Audiences;
@ -58,9 +63,11 @@ public class NicknameCommands implements Listener, Commands {
public static final String PERMISSION_ANY = PERMISSION + ".any";
public static final String PERMISSION_ANY_SET = PERMISSION_ANY + ".set";
public static final String PERMISSION_ANY_GET = PERMISSION_ANY + ".get";
public static final String PERMISSION_UNLIMITED = PERMISSION + ".unlimited";
private final NicknameConfiguration config;
private final SyncExecutor syncExecutor;
private final BukkitUserStore userStore;
private final UserService userService;
private final Audiences audiences;
private final IdentityProvider identities;
@ -71,6 +78,7 @@ public class NicknameCommands implements Listener, Commands {
@Inject NicknameCommands(NicknameConfiguration config,
SyncExecutor syncExecutor,
BukkitUserStore userStore,
UserService userService,
Audiences audiences,
IdentityProvider identities,
@ -80,6 +88,7 @@ public class NicknameCommands implements Listener, Commands {
Plugin plugin) {
this.config = config;
this.syncExecutor = syncExecutor;
this.userStore = userStore;
this.userService = userService;
this.audiences = audiences;
this.identities = identities;
@ -99,7 +108,8 @@ public class NicknameCommands implements Listener, Commands {
PERMISSION_ANY,
PERMISSION_ANY_GET,
PERMISSION_ANY_SET,
PERMISSION_IMMEDIATE
PERMISSION_IMMEDIATE,
PERMISSION_UNLIMITED
).forEach(name -> {
final Permission permission = new Permission(name, PermissionDefault.FALSE);
pluginManager.addPermission(permission);
@ -121,6 +131,17 @@ public class NicknameCommands implements Listener, Commands {
if(immediate) {
CommandUtils.assertPermission(sender, PERMISSION_IMMEDIATE);
}
if(sender instanceof Player) {
Instant updatedAt = userStore.getUser((Player) sender).nickname_updated_at();
Instant nextAt = updatedAt == null ? Instant.now() : updatedAt.plus(Duration.ofDays(1));
if(!sender.hasPermission(PERMISSION_UNLIMITED) && nextAt.isAfter(Instant.now())) {
throw new TranslatableCommandException(
"command.nick.mustWait",
PeriodFormats.relativeFutureApproximate(nextAt)
);
}
}
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)

View File

@ -14,6 +14,7 @@ import org.bukkit.plugin.Plugin;
import java.time.Duration;
import tc.oc.api.bukkit.users.BukkitUserStore;
import tc.oc.api.docs.PlayerId;
import tc.oc.api.users.CreditTokensRequest;
import tc.oc.api.users.UserService;
import tc.oc.commons.bukkit.chat.Audiences;
import tc.oc.commons.bukkit.util.NMSHacks;
@ -103,7 +104,7 @@ public class RaindropUtil {
if(save) {
final int finalDelta = delta;
playerExecutorFactory.queued(playerId).callback(
userService.creditRaindrops(playerId, finalDelta),
userService.creditTokens(playerId, CreditTokensRequest.raindrops(finalDelta)),
(player, update) -> {
if(update.success()) {
showRaindrops(player, finalDelta, multiplier, reason, show);

View File

@ -17,6 +17,7 @@ import tc.oc.api.bukkit.users.BukkitUserStore;
import tc.oc.api.bukkit.users.OnlinePlayers;
import tc.oc.api.docs.Server;
import tc.oc.api.docs.Session;
import tc.oc.minecraft.protocol.MinecraftVersion;
import tc.oc.minecraft.scheduler.SyncExecutor;
import tc.oc.api.sessions.SessionService;
import tc.oc.api.sessions.SessionStartRequest;
@ -70,6 +71,11 @@ public class SessionListener implements Listener, PluginFacet {
return player.getAddress().getAddress();
}
@Override
public String version() {
return MinecraftVersion.describeProtocol(player.getProtocolVersion());
}
@Override
public @Nullable String previous_session_id() {
return userStore.session(player)

View File

@ -3,6 +3,7 @@ package tc.oc.commons.bukkit.tokens;
import org.bukkit.entity.Player;
import tc.oc.api.bukkit.users.BukkitUserStore;
import tc.oc.api.docs.PlayerId;
import tc.oc.api.users.CreditTokensRequest;
import tc.oc.api.users.UserService;
import tc.oc.commons.bukkit.util.SyncPlayerExecutorFactory;
import tc.oc.api.docs.User;
@ -24,7 +25,7 @@ public class TokenUtil {
public static void giveMapTokens(PlayerId playerId, int count) {
playerExecutorFactory.queued(playerId).callback(
userService.creditMaptokens(playerId, count),
userService.creditTokens(playerId, CreditTokensRequest.maps(count)),
(player, update) -> {}
);
@ -32,7 +33,7 @@ public class TokenUtil {
public static void giveMutationTokens(PlayerId playerId, int count) {
playerExecutorFactory.queued(playerId).callback(
userService.creditMutationtokens(playerId, count),
userService.creditTokens(playerId, CreditTokensRequest.mutations(count)),
(player, update) -> {}
);
}

View File

@ -36,6 +36,7 @@ import tc.oc.api.sessions.SessionChange;
import tc.oc.commons.bukkit.chat.Audiences;
import tc.oc.commons.bukkit.chat.NameStyle;
import tc.oc.commons.bukkit.chat.PlayerComponent;
import tc.oc.commons.bukkit.event.UserLoginEvent;
import tc.oc.commons.bukkit.format.ServerFormatter;
import tc.oc.commons.bukkit.nick.Identity;
import tc.oc.commons.bukkit.nick.IdentityProvider;
@ -152,8 +153,12 @@ public class JoinMessageAnnouncer implements MessageListener, Listener, PluginFa
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onJoin(PlayerJoinEvent event) {
public void preJoin(PlayerJoinEvent event) {
event.setJoinMessage(null);
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onJoin(UserLoginEvent event) {
final User user = userStore.getUser(event.getPlayer());
final SessionChange change = pendingJoins.getIfPresent(user);
if(change != null) {

View File

@ -244,3 +244,45 @@ punishment.action.FORUM_BAN = forum banned
punishment.action.TOURNEY_BAN = tourney banned
punishment.action.UNKNOWN = unknown
friend.request.sent = You have send a friend request to {0}
friend.request.accepted = You are now friends with {0}
friend.request.friend_limit = Upgrade to a premium rank to have more than 16 friends \
Visit {1} for more details
friend.request.already_friends = You are already friends with {0}
friend.request.you_already_requested = You have already requested to be friends with {0}
friend.request.they_already_requested = {0} has already requested to be friends with you
friend.request.not_accepting = {0} is not accepting any friend requests
friend.request.not_self = You cannot request to be your own friend
friend.request.error = Unable to request friendship for unknown reasons
friend.unrequest.success = You are no longer friends with {0}
friend.unrequest.withdraw = You have withdrawn your friend request to {0}
friend.unrequest.pending = You cannot delete pending friend requests
friend.unrequest.not_friends = You are not friends with {0}
friend.unrequest.error = Cannot unfriend {0} for unknown reasons
death.screen.luck = Better luck next time!
death.screen.wrek = Get wrecked!
death.screen.ez = Ez...
death.screen.cry = Wanna cry?
death.screen.who = Who are you anyway?
death.screen.piece = Wanna a piece of me?
death.screen.get = Come and get me.
death.screen.noob = Ha. Noob.
death.screen.miss = Missed me?
death.screen.rage = Are you a quitter?
death.screen.try = Dont even try
death.screen.triple = Oh baby a triple!
death.screen.suck = Sucks to be you.
death.screen.even = Do you even?
death.screen.ha = Hahahaha...
death.screen.oops = Oops. Didnt see you.
death.screen.cute = Aww. How cute.
death.screen.dont = Dont even try, bro.
death.screen.pvp = You call that PvP?
death.screen.sword = Try to swing next time.
death.screen.touch = Cant touch dis...
death.screen.damn = Dammmn son...
death.screen.hit = Why you hitting yourself?
death.screen.cool = 2 Cool 4 U
death.screen.up = Just give up.

View File

@ -59,6 +59,7 @@ command.competitorNotFound = No competitors matched query.
command.rotationNotFound = No rotations matched query.
command.playerNotFound = No players matched query.
command.multiplePlayersFound = More than one player found! Use @<name> for exact matching.
command.confirmNotFound = Could not find an action to confirm.
command.unknownError = An unknown error has occurred. Please refer to the server console.
gameplay.ffa.kickedForPremium = You were kicked from the match to make room for a premium player
@ -113,6 +114,10 @@ autoJoin.teamsFull = Teams are full
autoJoin.capacity = Teams are at capacity
autoJoin.matchFull = Match is full
join.friend.requested = {0} has invited you to join their team. \
Type '/join confirm' to accept.
join.friend.accepted = {0} accepted your request to join your team.
match.invalid = Invalid match
match.noCombatLog = You are in too much danger to leave the match right now
match.enderChestsDisabled = Ender chests are disabled on this server

View File

@ -14,6 +14,7 @@ command.nick.tooLong = Nicknames are limited to 16 characters in length.
command.nick.tooShort = Nicknames must be at least 4 characters long.
command.nick.noActiveNicks = Nobody is disguised.
command.nick.nickTaken = The nickname {0} is unusable because it matches a real Minecraft account.
command.nick.mustWait = You can change your nickname in {0}
command.nick.invalid = Invalid nickname
command.nick.verifyError = An error occurred trying to verify the nickname {0}. Waiting and trying again might fix this.

View File

@ -44,6 +44,8 @@ command.freeze.unfrozen = You have unfrozen {0}
# {0} = the server name
command.server.teleporting = Teleporting you to {0}
command.server.currentServer = You are currently on {0}
command.server.defaultServer.set = Set default server to {0}
command.server.defaultServer.get = Your default server is currently {0}
command.server.switchPrompt = To change servers, type /server [name]
command.pa.reload.success = Reloaded the ProjectAres configuration

View File

@ -2,12 +2,24 @@ package tc.oc.pgm.join;
import javax.inject.Singleton;
import com.google.common.util.concurrent.Futures;
import com.google.inject.Inject;
import com.sk89q.minecraft.util.commands.Command;
import com.sk89q.minecraft.util.commands.CommandContext;
import com.sk89q.minecraft.util.commands.CommandException;
import com.sk89q.minecraft.util.commands.CommandPermissions;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.TranslatableComponent;
import org.bukkit.command.CommandSender;
import tc.oc.api.bukkit.users.BukkitUserStore;
import tc.oc.api.docs.PlayerId;
import tc.oc.api.users.FriendJoinResponse;
import tc.oc.api.users.UserService;
import tc.oc.commons.bukkit.chat.NameStyle;
import tc.oc.commons.bukkit.commands.UserFinder;
import tc.oc.commons.core.commands.Commands;
import tc.oc.commons.core.concurrent.Flexecutor;
import tc.oc.minecraft.scheduler.Sync;
import tc.oc.pgm.PGMTranslations;
import tc.oc.pgm.commands.CommandUtils;
import tc.oc.pgm.match.Competitor;
@ -15,12 +27,34 @@ import tc.oc.pgm.match.Match;
import tc.oc.pgm.match.MatchPlayer;
import tc.oc.pgm.teams.TeamMatchModule;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.IntStream;
@Singleton
public class JoinCommands implements Commands {
private final Flexecutor flexecutor;
private final BukkitUserStore userStore;
private final UserService userService;
private final UserFinder userFinder;
private final Map<PlayerId, PlayerId> friendJoins;
@Inject JoinCommands(@Sync Flexecutor flexecutor, BukkitUserStore userStore, UserService userService, UserFinder userFinder) {
this.flexecutor = flexecutor;
this.userStore = userStore;
this.userService = userService;
this.userFinder = userFinder;
this.friendJoins = new HashMap<>();
}
@Command(
aliases = { "join", "jugar", "jouer", "spielen"},
aliases = { "join", "jugar", "jouer", "spielen" },
desc = "Joins the current match",
usage = "[team] - defaults to random",
usage = "[team] [friends...]",
flags = "f",
min = 0,
max = -1
@ -28,6 +62,7 @@ public class JoinCommands implements Commands {
@CommandPermissions(JoinMatchModule.JOIN_PERMISSION)
public void join(CommandContext args, CommandSender sender) throws CommandException {
MatchPlayer player = CommandUtils.senderToMatchPlayer(sender);
PlayerId playerId = player.getPlayerId();
Match match = player.getMatch();
JoinMatchModule jmm = match.needMatchModule(JoinMatchModule.class);
TeamMatchModule tmm = match.getMatchModule(TeamMatchModule.class);
@ -36,14 +71,60 @@ public class JoinCommands implements Commands {
Competitor chosenParty = null;
if(args.argsLength() > 0) {
if(args.getJoinedStrings(0).trim().toLowerCase().startsWith("obs")) {
String team = args.getString(0);
if(team.toLowerCase().equals("confirm")) {
if(friendJoins.containsKey(playerId)) {
PlayerId requestedId = friendJoins.remove(playerId);
Optional<MatchPlayer> requestedPlayer = match.player(requestedId);
boolean success;
if(requestedPlayer.isPresent()) {
MatchPlayer requested = requestedPlayer.get();
success = jmm.requestJoin(player, JoinMethod.FORCE, requested.getCompetitor());
if(success) {
requested.sendMessage(new TranslatableComponent(
"join.friend.accepted",
player.getStyledName(NameStyle.VERBOSE)
));
}
} else {
success = false;
jmm.requestJoin(player, JoinMethod.USER, null);
}
if(!success) {
friendJoins.put(playerId, requestedId);
}
return;
} else {
throw new CommandException(PGMTranslations.get().t("command.confirmNotFound", sender));
}
} else if(team.toLowerCase().equals("obs")) {
observe(args, sender);
return;
} else if(tmm != null) {
// player wants to join a specific team
chosenParty = tmm.bestFuzzyMatch(args.getJoinedStrings(0));
if(chosenParty == null) throw new CommandException(PGMTranslations.get().t("command.teamNotFound", sender));
chosenParty = tmm.bestFuzzyMatch(team);
if(chosenParty == null) {
throw new CommandException(PGMTranslations.get().t("command.teamNotFound", sender));
}
}
List<MatchPlayer> friends = new ArrayList<>();
for(int i : IntStream.range(1, args.argsLength()).toArray()) {
friends.add(match.getPlayer(Futures.getUnchecked(userFinder.findLocalPlayer(sender, args, i)).player()));
}
if(!friends.isEmpty()) {
FriendJoinResponse response = Futures.getUnchecked(userService.joinFriend(playerId, friends::size));
boolean authorized = response.authorized();
sender.sendMessage((authorized ? ChatColor.GREEN : ChatColor.RED) + response.message());
if(!authorized) {
return; // An error message was already sent
}
}
friends.stream().forEach(friend -> {
friendJoins.put(friend.getPlayerId(), playerId);
friend.sendMessage(new TranslatableComponent(
"join.friend.requested",
player.getStyledName(NameStyle.VERBOSE)
));
});
}
jmm.requestJoin(player, force ? JoinMethod.FORCE : JoinMethod.USER, chosenParty);

View File

@ -165,7 +165,7 @@ public class Alive extends Participating {
playDeathEffect(killer);
transition(new Dead(player));
transition(new Dead(player, killer));
}
private void playDeathEffect(@Nullable ParticipantState killer) {

View File

@ -6,6 +6,7 @@ import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TranslatableComponent;
import org.bukkit.event.inventory.InventoryClickEvent;
import tc.oc.api.docs.User;
import tc.oc.commons.bukkit.freeze.FrozenPlayer;
import tc.oc.commons.bukkit.util.NMSHacks;
import tc.oc.commons.core.chat.Component;
@ -13,6 +14,7 @@ import tc.oc.pgm.events.PlayerChangePartyEvent;
import tc.oc.pgm.match.Competitor;
import tc.oc.pgm.match.MatchPlayer;
import tc.oc.pgm.match.MatchScope;
import tc.oc.pgm.match.ParticipantState;
import tc.oc.pgm.spawns.Spawn;
import tc.oc.pgm.spawns.SpawnModule;
import tc.oc.pgm.spawns.events.DeathKitApplyEvent;
@ -24,16 +26,19 @@ public class Dead extends Spawning {
private static final long CORPSE_ROT_TICKS = 15;
private final long deathTick;
private final @Nullable ParticipantState killer;
private boolean kitted, rotted;
private @Nullable FrozenPlayer frozenPlayer;
private BaseComponent title;
public Dead(MatchPlayer player) {
this(player, player.getMatch().getClock().now().tick);
public Dead(MatchPlayer player, @Nullable ParticipantState killer) {
this(player, killer, player.getMatch().getClock().now().tick);
}
public Dead(MatchPlayer player, long deathTick) {
public Dead(MatchPlayer player, @Nullable ParticipantState killer, long deathTick) {
super(player);
this.deathTick = deathTick;
this.killer = killer;
}
@Override
@ -129,7 +134,23 @@ public class Dead extends Spawning {
@Override
protected BaseComponent getTitle() {
BaseComponent title = new TranslatableComponent("deathScreen.title");
if(title == null) {
title = computeTitle();
}
return title;
}
protected BaseComponent computeTitle() {
String screen = "deathScreen.title";
if(killer != null) {
String key = userStore.user(killer.getPlayerId())
.map(User::death_screen)
.orElse(null);
if(key != null && !key.equals("default")) {
screen = "death.screen." + key;
}
}
BaseComponent title = new TranslatableComponent(screen);
title.setColor(ChatColor.RED);
return title;
}

View File

@ -5,6 +5,7 @@ import javax.inject.Inject;
import org.bukkit.entity.Player;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.inventory.InventoryClickEvent;
import tc.oc.api.bukkit.users.BukkitUserStore;
import tc.oc.commons.bukkit.event.CoarsePlayerMoveEvent;
import tc.oc.commons.bukkit.freeze.PlayerFreezer;
import tc.oc.commons.core.util.Comparables;
@ -22,6 +23,7 @@ import tc.oc.pgm.start.PreMatchCountdown;
public abstract class State {
@Inject protected static PlayerFreezer freezer; // HACK
@Inject protected static BukkitUserStore userStore;
protected final Match match;
protected final SpawnMatchModule smm;