Allow cross-server chat messages

This commit is contained in:
Ashcon Partovi 2018-05-21 19:44:20 -07:00 committed by GitHub
parent ac0c821a90
commit 44481e4b63
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
109 changed files with 1894 additions and 1172 deletions

View File

@ -1,5 +1,6 @@
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;
@ -46,5 +47,6 @@ public final class ApiManifest extends HybridManifest {
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,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

@ -125,6 +125,7 @@ public interface ServerDoc {
@Nullable String resource_pack_url();
@Nullable String resource_pack_sha1();
boolean resource_pack_fast_update();
@Nullable String cross_server_profile();
}
@Serialize

View File

@ -43,6 +43,11 @@ public interface UserDoc {
List<String> trophy_ids();
}
@Serialize
interface Channel extends Partial {
@Nonnull ChatDoc.Type chat_channel();
}
interface License {
@Serialize
@ -79,7 +84,7 @@ 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, DefaultServer, FriendTokens, DeathScreen, License.Complete {
interface Login extends Identity, Locale, Trophies, DefaultServer, FriendTokens, DeathScreen, License.Complete, Channel {
int raindrops();
int maptokens();
int mutationtokens();

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

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

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

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

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;
@ -161,4 +163,9 @@ public class LocalUserDocument extends SimplePlayerId implements User {
public String death_screen() {
return null;
}
@Override
public ChatDoc.Type chat_channel() {
return ChatDoc.Type.TEAM;
}
}

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,6 +9,7 @@ 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;
@ -47,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());
});

View File

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

@ -12,7 +12,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;
@ -62,7 +63,6 @@ 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.StatsCommands;
import tc.oc.commons.bukkit.stats.StatsManifest;
import tc.oc.commons.bukkit.suspend.SuspendListener;
import tc.oc.commons.bukkit.tablist.PlayerTabEntry;
@ -104,7 +104,8 @@ 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());

View File

@ -0,0 +1,171 @@
package tc.oc.commons.bukkit.broadcast;
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 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;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
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;
/**
* 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.getRemainingString(0);
} else if(args.argsLength() >= 2) {
if(type == ChatDoc.Destination.GLOBAL) {
destination = null;
message = args.getRemainingString(1);
} else if(args.argsLength() >= 3) {
destination = args.getString(1);
message = args.getRemainingString(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

@ -59,7 +59,6 @@ public class BroadcastSettings {
break;
case NEWS:
case ALERT:
setting = NEWS;
break;
@ -72,6 +71,7 @@ public class BroadcastSettings {
setting = RANDOM;
break;
case ALERT:
default:
return true;
}

View File

@ -1,131 +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.Material;
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.bukkit.util.ItemCreator;
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,28 @@
package tc.oc.commons.bukkit.channels;
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;
import javax.annotation.Nullable;
/**
* 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,60 @@
package tc.oc.commons.bukkit.channels;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import tc.oc.api.docs.PlayerId;
import javax.annotation.Nullable;
/**
* 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,99 @@
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 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;
import javax.inject.Inject;
import javax.inject.Singleton;
@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",
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.getRemainingString(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);
if(!channel.sendable(player)) {
// If player cannot chat in their preferred channel,
// assume they can send to the default channel.
channel = channelRouter.getDefaultChannel();
}
channel.chat(player, event.getMessage());
});
}
}

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,82 @@
package tc.oc.commons.bukkit.channels;
import com.google.common.util.concurrent.ListenableFuture;
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;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.Optional;
import java.util.function.Function;
/**
* 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,79 @@
package tc.oc.commons.bukkit.channels;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import net.md_5.bungee.api.chat.BaseComponent;
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.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;
import javax.annotation.Nullable;
import javax.inject.Inject;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
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;
// Chat messages are sent to the local server before API verification.
// This prevents downtime or lockup from stopping all local server chat.
protected Cache<String, Boolean> chatCache = CacheBuilder.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.build();
public abstract BaseComponent prefix();
public abstract BaseComponent format(PlayerComponent player, 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 show(Chat chat) {
if(chatCache.getIfPresent(chat._id()) == null) {
chatCache.put(chat._id(), true);
sendMessage(format(
new PlayerComponent(identityProvider.currentOrConsoleIdentity(chat.sender())),
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,75 @@
package tc.oc.commons.bukkit.channels.admin;
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.virtual.ChatDoc;
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.permissions.PermissionRegistry;
import tc.oc.commons.bukkit.settings.SettingManagerProvider;
import tc.oc.commons.core.chat.Component;
import javax.inject.Inject;
import javax.inject.Singleton;
@Singleton
public class AdminChannel extends SimpleChannel implements PermissibleChannel {
public final static Permission PERMISSION = new Permission("ocn.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;
@Inject AdminChannel(PermissionRegistry permissions, SettingManagerProvider settings) {
this.settings = settings;
this.permissions = permissions;
}
@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(PlayerComponent player, String message) {
return new Component(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 net.md_5.bungee.api.chat.BaseComponent;
import org.bukkit.command.CommandSender;
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;
import javax.inject.Inject;
import javax.inject.Singleton;
@Singleton
public class ServerChannel extends SimpleChannel {
@Inject ServerChannel() {}
@Override
public BaseComponent prefix() {
return new Component();
}
@Override
public BaseComponent format(PlayerComponent player, String message) {
return new Component().extra("<").extra(player).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

@ -0,0 +1,98 @@
package tc.oc.commons.bukkit.chat;
import tc.oc.api.docs.Chat;
import tc.oc.api.docs.Server;
import tc.oc.api.docs.virtual.ChatDoc;
import tc.oc.api.message.MessageListener;
import tc.oc.api.message.types.ModelUpdate;
import tc.oc.api.queue.PrimaryQueue;
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.ChannelRouter;
import tc.oc.commons.core.plugin.PluginFacet;
import tc.oc.minecraft.scheduler.MainThreadExecutor;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.Optional;
@Singleton
public class ChatAnnouncer implements PluginFacet, MessageListener {
private final Server server;
private final ServerStore serverStore;
private final PrimaryQueue primaryQueue;
private final MainThreadExecutor executor;
private final ChannelRouter channelRouter;
private final BroadcastSender broadcaster;
@Inject ChatAnnouncer(Server server, ServerStore serverStore, PrimaryQueue primaryQueue, MainThreadExecutor executor, ChannelRouter channelRouter, BroadcastSender broadcaster) {
this.server = server;
this.primaryQueue = primaryQueue;
this.executor = executor;
this.channelRouter = channelRouter;
this.broadcaster = broadcaster;
this.serverStore = serverStore;
}
@Override
public void enable() {
primaryQueue.bind(ModelUpdate.class);
primaryQueue.subscribe(this, executor);
}
@Override
public void disable() {
primaryQueue.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 Server origin = serverStore.byId(chat.server_id());
final boolean local = server.equals(origin);
final boolean remote = serverStore.canCommunicate(server._id(), origin._id());
switch(chat.type()) {
case SERVER:
case TEAM:
return local;
case ADMIN:
return local || 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:
return server.family().equalsIgnoreCase(destination);
case GAME:
final String game = server.game_id();
return game == null || game.equalsIgnoreCase(destination);
case NETWORK:
return server.network().name().equalsIgnoreCase(destination);
case GLOBAL:
return true;
}
return false;
}
}

View File

@ -0,0 +1,91 @@
package tc.oc.commons.bukkit.chat;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
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;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.time.Duration;
import java.time.Instant;
import java.util.function.Consumer;
import static java.util.Optional.ofNullable;
@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

@ -52,7 +52,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

@ -2,14 +2,20 @@ package tc.oc.commons.bukkit.commands;
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 com.sk89q.minecraft.util.commands.CommandContext;
import com.sk89q.minecraft.util.commands.CommandException;
import com.sk89q.minecraft.util.commands.CommandPermissionsException;
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 +26,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 {
@ -218,4 +226,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

@ -17,13 +17,13 @@ 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.Audiences;
import tc.oc.commons.bukkit.chat.Links;
import tc.oc.commons.bukkit.chat.NameStyle;
import tc.oc.commons.bukkit.chat.PlayerComponent;
import tc.oc.commons.core.util.Lazy;
import tc.oc.minecraft.scheduler.SyncExecutor;
import tc.oc.api.sessions.SessionService;
import tc.oc.commons.bukkit.chat.BukkitAudiences;
import tc.oc.commons.bukkit.chat.ComponentPaginator;
import tc.oc.commons.bukkit.chat.ComponentRenderers;
import tc.oc.commons.bukkit.chat.HeaderComponent;
@ -47,8 +47,9 @@ public class UserCommands implements Commands {
private final UserFinder userFinder;
private final IdentityProvider identityProvider;
private final UserFormatter userFormatter;
private final Audiences audiences;
@Inject UserCommands(MinecraftService minecraftService, SyncExecutor syncExecutor, SessionService sessionService, FriendshipService friendshipService, UserFinder userFinder, IdentityProvider identityProvider, UserFormatter userFormatter) {
@Inject UserCommands(MinecraftService minecraftService, SyncExecutor syncExecutor, SessionService sessionService, FriendshipService friendshipService, UserFinder userFinder, IdentityProvider identityProvider, UserFormatter userFormatter, Audiences audiences) {
this.minecraftService = minecraftService;
this.syncExecutor = syncExecutor;
this.sessionService = sessionService;
@ -56,6 +57,7 @@ public class UserCommands implements Commands {
this.userFinder = userFinder;
this.identityProvider = identityProvider;
this.userFormatter = userFormatter;
this.audiences = audiences;
}
@Command(
@ -113,7 +115,7 @@ public class UserCommands implements Commands {
@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);
Audience audience = audiences.get(sender);
syncExecutor.callback(
userFinder.findUser(sender, args, 0),
response -> {
@ -163,7 +165,7 @@ public class UserCommands implements Commands {
@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);
Audience audience = audiences.get(sender);
syncExecutor.callback(
userFinder.findUser(sender, args, 0),
response -> {
@ -204,7 +206,7 @@ public class UserCommands implements Commands {
syncExecutor.callback(
sessionService.staff(minecraftService.getLocalServer().network(), identityProvider.revealAll(sender)),
CommandFutureCallback.onSuccess(sender, args, result -> {
final Audience audience = BukkitAudiences.getAudience(sender);
final Audience audience = audiences.get(sender);
if(result.documents().isEmpty()) {
audience.sendMessage(new TranslatableComponent("command.staff.noStaffOnline"));
return;

View File

@ -8,6 +8,7 @@ import javax.inject.Inject;
import javax.inject.Singleton;
import com.google.common.collect.ImmutableSet;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.BaseComponent;
import tc.oc.api.bukkit.users.BukkitUserStore;
import tc.oc.api.docs.virtual.UserDoc;
@ -37,6 +38,7 @@ public class FlairRenderer implements PartialNameRenderer {
@Override
public String getLegacyName(Identity identity, NameType type) {
if(!(type.style.contains(NameFlag.FLAIR) && type.reveal)) return "";
if(identity.isConsole()) return ChatColor.GOLD + "";
return getFlairs(identity).reduce("", String::concat);
}

View File

@ -64,6 +64,11 @@ public class ConsoleIdentity implements Identity {
return true;
}
@Override
public boolean isConsole() {
return true;
}
@Override
public String getName(CommandSender viewer) {
return NAME;

View File

@ -60,6 +60,11 @@ public interface Identity {
*/
boolean isCurrent();
/**
* Does the identity belong to a console?
*/
boolean isConsole();
// Viewer-relative properties
/**

View File

@ -71,6 +71,11 @@ public class IdentityImpl implements Identity {
return player != null && equals(identityProvider.currentIdentity(player));
}
@Override
public boolean isConsole() {
return false;
}
@Override
public boolean isDead(CommandSender viewer) {
if(!isOnline(viewer)) return false;

View File

@ -84,7 +84,7 @@ public class UsernameRenderer implements PartialNameRenderer {
rendered.setColor(getColor(identity, type));
}
if(type.style.contains(NameFlag.TELEPORT)) {
if(!identity.isConsole() && type.style.contains(NameFlag.TELEPORT)) {
Component dupe = rendered.duplicate();
rendered.clickEvent(makeRemoteTeleportClickEvent(identity, identity.getNickname() != null && !type.reveal));
rendered.hoverEvent(HoverEvent.Action.SHOW_TEXT, new TranslatableComponent("tip.teleportTo", dupe));

View File

@ -14,25 +14,22 @@ import tc.oc.api.docs.virtual.MatchDoc;
import tc.oc.api.docs.virtual.PunishmentDoc;
import tc.oc.api.model.IdFactory;
import tc.oc.api.model.ModelService;
import tc.oc.commons.bukkit.report.ReportConfiguration;
@Singleton
public class PunishmentCreator {
private final ReportConfiguration config;
private final ModelService<Punishment, PunishmentDoc.Partial> punishmentService;
private final IdFactory idFactory;
private final Server localServer;
@Inject PunishmentCreator(ReportConfiguration config, ModelService<Punishment, PunishmentDoc.Partial> punishmentService, IdFactory idFactory, Server localServer) {
this.config = config;
@Inject PunishmentCreator(ModelService<Punishment, PunishmentDoc.Partial> punishmentService, IdFactory idFactory, Server localServer) {
this.punishmentService = punishmentService;
this.idFactory = idFactory;
this.localServer = localServer;
}
public boolean offRecord() {
return !config.crossServer();
return localServer.cross_server_profile() == null;
}
public ListenableFuture<Punishment> create(@Nullable PlayerId punisher, PlayerId punished, String reason, @Nullable PunishmentDoc.Type type, @Nullable Duration duration, boolean silent, boolean auto, boolean offrecord) {

View File

@ -1,25 +1,22 @@
package tc.oc.commons.bukkit.report;
import java.util.List;
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.chat.BaseComponent;
import org.bukkit.Sound;
import org.bukkit.entity.Player;
import tc.oc.api.docs.Report;
import tc.oc.api.docs.Server;
import tc.oc.api.message.MessageListener;
import tc.oc.api.message.MessageQueue;
import tc.oc.api.message.types.ModelUpdate;
import tc.oc.commons.bukkit.channels.AdminChannel;
import tc.oc.api.servers.ServerStore;
import tc.oc.commons.bukkit.channels.admin.AdminChannel;
import tc.oc.commons.bukkit.chat.Audiences;
import tc.oc.commons.bukkit.chat.BukkitSound;
import tc.oc.commons.bukkit.settings.SettingManagerProvider;
import tc.oc.commons.core.chat.Audience;
import tc.oc.commons.core.plugin.PluginFacet;
import tc.oc.minecraft.scheduler.MainThreadExecutor;
@ -39,24 +36,17 @@ public class ReportAnnouncer implements PluginFacet, MessageListener {
private final MessageQueue primaryQueue;
private final MainThreadExecutor executor;
private final Server localServer;
private final AdminChannel adminChannel;
private final ServerStore serverStore;
private final Audiences audiences;
private final SettingManagerProvider settings;
@Inject ReportAnnouncer(ReportConfiguration config, ReportFormatter reportFormatter, MessageQueue primaryQueue, MainThreadExecutor executor, Server localServer, AdminChannel adminChannel, Audiences audiences, SettingManagerProvider settings) {
@Inject ReportAnnouncer(ReportConfiguration config, ReportFormatter reportFormatter, MessageQueue primaryQueue, MainThreadExecutor executor, Server localServer, ServerStore serverStore, Audiences audiences) {
this.config = config;
this.reportFormatter = reportFormatter;
this.primaryQueue = primaryQueue;
this.executor = executor;
this.localServer = localServer;
this.adminChannel = adminChannel;
this.serverStore = serverStore;
this.audiences = audiences;
this.settings = settings;
}
@Override
public boolean isActive() {
return config.crossServer();
}
@Override
@ -72,19 +62,9 @@ public class ReportAnnouncer implements PluginFacet, MessageListener {
@HandleMessage
public void broadcast(ModelUpdate<Report> message) {
if(localServer._id().equals(message.document().server_id()) ||
(config.crossServer() && config.families().contains(message.document().family()))) {
final List<? extends BaseComponent> formatted = reportFormatter.format(message.document(), true, false);
adminChannel.viewers()
.filter(viewer -> viewer.hasPermission(ReportPermissions.RECEIVE))
.forEach(viewer -> {
Audience audience = audiences.get(viewer);
audience.sendMessages(formatted);
if (viewer instanceof Player && (boolean)settings.getManager((Player)viewer).getValue(SOUND_SETTING)) {
audience.playSound(REPORT_SOUND);
}
});
if(serverStore.canCommunicate(localServer._id(), message.document().server_id())) {
audiences.permission(ReportPermissions.RECEIVE)
.sendMessages(reportFormatter.format(message.document(), true, false));
}
}
}

View File

@ -161,11 +161,8 @@ public class ReportCommands implements Commands, Listener {
final boolean crossServer = args.hasFlag('a');
ReportSearchRequest request = ReportSearchRequest.create(page, PER_PAGE);
request = crossServer ? request.forFamilies(reportConfiguration.families())
: request.forServer(localServer);
if(userResult != null) {
request = request.forPlayer(userResult.user);
}
request = userResult != null ? request.forPlayer(userResult.user)
: request.forServer(localServer, crossServer);
syncExecutor.callback(
reportService.find(request),

View File

@ -1,6 +1,5 @@
package tc.oc.commons.bukkit.report;
import java.util.List;
import javax.inject.Inject;
import org.bukkit.configuration.Configuration;
@ -22,12 +21,4 @@ public class ReportConfiguration {
public Duration cooldown() {
return ConfigUtils.getDuration(config, "reports.cooldown", Duration.ZERO);
}
public List<String> families() {
return config.getStringList("reports.families");
}
public boolean crossServer() {
return config.getBoolean("reports.cross-server", false);
}
}

View File

@ -77,7 +77,7 @@ public class TeleportListener implements MessageListener, Listener, PluginFacet,
@Override
public void enable() {
permissionRegistry.addPermission(Teleporter.PERMISSION);
permissionRegistry.register(Teleporter.PERMISSION);
primaryQueue.subscribe(this, syncExecutor);
primaryQueue.bind(PlayerTeleportRequest.class);
}

View File

@ -11,11 +11,9 @@ import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import tc.oc.api.docs.Server;
import tc.oc.api.docs.Whisper;
import tc.oc.api.docs.virtual.ServerDoc;
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.ConsoleAudience;
import tc.oc.commons.bukkit.chat.NameStyle;
import tc.oc.commons.bukkit.chat.PlayerComponent;
import tc.oc.commons.bukkit.chat.UserTextComponent;
@ -42,16 +40,14 @@ public class WhisperFormatter {
private final Server localServer;
private final SettingManagerProvider playerSettings;
private final Audiences audiences;
private final ConsoleAudience consoleAudience;
@Inject WhisperFormatter(IdentityProvider identities, MiscFormatter miscFormatter, ServerStore serverStore, Server localServer, SettingManagerProvider playerSettings, Audiences audiences, ConsoleAudience consoleAudience) {
@Inject WhisperFormatter(IdentityProvider identities, MiscFormatter miscFormatter, ServerStore serverStore, Server localServer, SettingManagerProvider playerSettings, Audiences audiences) {
this.identities = identities;
this.miscFormatter = miscFormatter;
this.serverStore = serverStore;
this.localServer = localServer;
this.playerSettings = playerSettings;
this.audiences = audiences;
this.consoleAudience = consoleAudience;
this.serverFormatter = ServerFormatter.dark;
}
@ -81,7 +77,7 @@ public class WhisperFormatter {
.extra(new Component(new UserTextComponent(sender, whisper.content()), ChatColor.WHITE));
audience.sendMessage(display);
consoleAudience.sendMessage(display);
audiences.console().sendMessage(display);
}
public void receive(Player viewer, Whisper whisper) {
@ -135,7 +131,7 @@ public class WhisperFormatter {
audience.sendMessage(display);
if(!local) {
consoleAudience.sendMessage(display);
audiences.console().sendMessage(display);
}
}

View File

@ -29,8 +29,6 @@ join-messages:
reports:
enabled: true
cooldown: 0s
families: []
cross-server: true
datadog:
enabled: false

View File

@ -6,7 +6,7 @@ website: ${url}
main: ${plugin.mainClass}
prefix: ${plugin.prefix}
isolate: true
depend: [API, BukkitSettings, Channels]
depend: [API, BukkitSettings]
permissions:
sudo:

View File

@ -14,6 +14,8 @@ command.error.invalidEnum = Invalid enum option '{0}'
command.error.invalidNumber = Invalid number '{0}'
command.error.invalidPage = There is no page {0}. Pages run from 1 to {1}.
command.error.emptyResult = Empty result
command.error.invalidOption = There is no option named '{0}'. Here are the possible options: \
{1}
command.onlyPlayers = You must be a player to use this command.
command.error.internal = Sorry, there was an internal error while processing your command.
@ -130,6 +132,10 @@ game.description.party = Special events
tip.teleportTo = Teleport to {0}
tip.connectTo = Connect to {0}
tip.sentBy = Sent by {0}
broadcast.prefix = Broadcast
# {0} = Sender
# {1} = Recipient
# {2} = Relative time
@ -174,6 +180,11 @@ appealNotification.count = You have - {0} - unread {1}
misc.appeals.singular = appeal
misc.appeals.plural = appeals
# {0} = chat channel type
channels.default.alreadySet = Your default channel is already {0} chat
channels.default.set = Your default channel is now {0} chat
channels.unavailable = {0} chat is currently unavailable.
misc.enabled = enabled
misc.disabled = disabled

View File

@ -151,6 +151,7 @@ start.needMorePlayers.ffa.plural = Waiting for {0} more players to join
start.needMorePlayers.team.singular = Waiting for {0} more player to join {1}
start.needMorePlayers.team.plural = Waiting for {0} more players to join {1}
defuse.broadcast = {0} defused {1}'s TNT
defuse.world = You defused world TNT.
# {0} = the player(s)
defuse.player = You defused {0}'s TNT.

View File

@ -41,6 +41,9 @@ command.nick.setOther.queued = {1} will be disguised as {0} the next time they c
command.freeze.frozen = You have frozen {0}
command.freeze.unfrozen = You have unfrozen {0}
freeze.frozen.broadcast = {0} has been frozen by {1}
freeze.unfrozen.broadcast = {0} has been unfrozen by {1}
# {0} = the server name
command.server.teleporting = Teleporting you to {0}
command.server.currentServer = You are currently on {0}

View File

@ -19,7 +19,6 @@ import tc.oc.commons.core.commands.CommandRegistry;
import tc.oc.inject.ProtectedBinder;
import tc.oc.minecraft.logging.BetterRaven;
import tc.oc.pgm.antigrief.CraftingProtect;
import tc.oc.pgm.channels.ChannelCommands;
import tc.oc.pgm.commands.MapCommands;
import tc.oc.pgm.commands.PollCommands;
import tc.oc.pgm.commands.RotationControlCommands;
@ -195,7 +194,6 @@ public final class PGM extends JavaPlugin {
private void setupCommands() {
commands.register(MapCommands.class);
commands.register(ChannelCommands.class);
commands.register(PollCommands.class);
commands.register(RotationEditCommands.RotationEditParent.class);
commands.register(RotationControlCommands.RotationControlParent.class);

View File

@ -1,5 +1,6 @@
package tc.oc.pgm;
import tc.oc.pgm.channels.ChannelManifest;
import tc.oc.commons.core.inject.HybridManifest;
import tc.oc.pgm.animation.AnimationManifest;
import tc.oc.pgm.broadcast.BroadcastManifest;
@ -70,6 +71,7 @@ public class PGMModulesManifest extends HybridManifest {
install(new LaneManifest());
install(new BroadcastManifest());
install(new StatsManifest());
install(new ChannelManifest());
install(new RaindropManifest());
install(new TokenManifest());
install(new ObjectiveModeManifest());

View File

@ -12,6 +12,7 @@ import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Sound;
import org.bukkit.block.Block;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.entity.TNTPrimed;
@ -25,14 +26,18 @@ import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.ItemFlag;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import tc.oc.commons.bukkit.channels.AdminChannel;
import tc.oc.commons.bukkit.channels.admin.AdminChannel;
import tc.oc.commons.bukkit.chat.ComponentRenderers;
import tc.oc.commons.bukkit.chat.ListComponent;
import tc.oc.commons.bukkit.chat.NameStyle;
import tc.oc.commons.bukkit.chat.PlayerComponent;
import tc.oc.commons.bukkit.event.ObserverKitApplyEvent;
import tc.oc.commons.bukkit.nick.IdentityProvider;
import tc.oc.commons.core.inject.Proxied;
import tc.oc.commons.core.plugin.PluginFacet;
import tc.oc.pgm.PGMTranslations;
import tc.oc.pgm.match.Match;
import tc.oc.pgm.match.MatchFinder;
import tc.oc.pgm.match.MatchManager;
import tc.oc.pgm.match.MatchPlayer;
import tc.oc.pgm.match.ParticipantState;
@ -43,9 +48,10 @@ public class DefuseListener implements PluginFacet, Listener {
public static final Material DEFUSE_ITEM = Material.SHEARS;
public static final int DEFUSE_SLOT = 4;
private final MatchManager mm;
private final EntityResolver entityResolver;
private final AdminChannel adminChannel;
@Inject MatchFinder mm;
@Inject AdminChannel adminChannel;
@Inject IdentityProvider identityProvider;
@Inject @Proxied EntityResolver entityResolver;
@Inject DefuseListener(MatchManager mm, @Proxied EntityResolver entityResolver, AdminChannel adminChannel) {
this.mm = mm;
@ -72,10 +78,12 @@ public class DefuseListener implements PluginFacet, Listener {
// check tnt
if(!(entity instanceof TNTPrimed)) return;
TNTMatchModule tntmm = mm.getMatch(player.getWorld()).getMatchModule(TNTMatchModule.class);
final Match match = mm.needMatch((CommandSender) player);
TNTMatchModule tntmm = match.getMatchModule(TNTMatchModule.class);
if(tntmm != null && !tntmm.getProperties().friendlyDefuse) return;
MatchPlayer clicker = this.mm.getPlayer(player);
MatchPlayer clicker = match.getPlayer(player);
if(clicker == null || !clicker.canInteract()) return;
// check water
@ -86,17 +94,19 @@ public class DefuseListener implements PluginFacet, Listener {
}
// check owner
MatchPlayer owner = this.mm.getPlayer(entityResolver.getOwner(entity));
MatchPlayer owner = match.getPlayer(entityResolver.getOwner(entity));
if(owner == null || (owner != clicker && owner.getParty() == clicker.getParty())) { // cannot defuse own TNT
// defuse TNT
entity.remove();
if(owner != null) {
this.notifyDefuse(clicker, entity, ChatColor.RED + PGMTranslations.t("defuse.player", clicker, owner.getDisplayName(clicker) + ChatColor.RED));
adminChannel.broadcast(clicker.getDisplayName() +
ChatColor.WHITE + " defused " +
owner.getDisplayName()
+ ChatColor.WHITE + "'s " +
ChatColor.DARK_RED + "TNT");
adminChannel.sendMessage(
new TranslatableComponent(
"defuse.broadcast",
new PlayerComponent(identityProvider.currentIdentity(clicker.getPlayerId()), NameStyle.VERBOSE),
new PlayerComponent(identityProvider.currentIdentity(owner.getPlayerId()), NameStyle.VERBOSE)
)
);
} else {
this.notifyDefuse(clicker, entity, ChatColor.RED + PGMTranslations.t("defuse.world", clicker));
}

View File

@ -1,44 +0,0 @@
package tc.oc.pgm.channels;
import com.github.rmsy.channels.Channel;
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 net.md_5.bungee.api.chat.TranslatableComponent;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import tc.oc.pgm.match.MatchPlayer;
import tc.oc.pgm.PGMTranslations;
import tc.oc.pgm.commands.CommandUtils;
public class ChannelCommands {
@Command(
aliases = "t",
desc = "Sends a message to the team channel (or sets the team channel to your default channel).",
usage = "[message...]",
min = 0,
max = -1,
anyFlags = true
)
public static void teamChat(CommandContext args, CommandSender sender) throws CommandException {
MatchPlayer player = CommandUtils.senderToMatchPlayer(sender);
if (player.getBukkit().hasPermission(ChannelMatchModule.TEAM_SEND_PERMISSION)) {
ChannelMatchModule cmm = player.getMatch().needMatchModule(ChannelMatchModule.class);
if (args.argsLength() == 0) {
cmm.setTeamChat(player, true);
player.sendMessage(new TranslatableComponent("command.chat.team.switchSuccess"));
} else {
Channel channel = cmm.getChannel(player.getParty());
channel.sendMessage(args.getJoinedStrings(0), player.getBukkit());
if (!player.getBukkit().hasPermission(channel.getListeningPermission())) {
sender.sendMessage(ChatColor.YELLOW + PGMTranslations.t("command.chat.team.success", player));
}
}
} else {
throw new CommandPermissionsException();
}
}
}

View File

@ -0,0 +1,12 @@
package tc.oc.pgm.channels;
import tc.oc.commons.core.inject.HybridManifest;
import tc.oc.pgm.match.inject.MatchModuleFixtureManifest;
public class ChannelManifest extends HybridManifest {
@Override
protected void configure() {
installFactory(PartyChannel.Factory.class);
install(new MatchModuleFixtureManifest<ChannelMatchModule>(){});
}
}

View File

@ -1,206 +1,74 @@
package tc.oc.pgm.channels;
import java.util.HashMap;
import java.util.Map;
import javax.inject.Inject;
import com.github.rmsy.channels.Channel;
import com.github.rmsy.channels.ChannelsPlugin;
import com.github.rmsy.channels.PlayerManager;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionDefault;
import org.bukkit.plugin.Plugin;
import tc.oc.chatmoderator.ChatModeratorPlugin;
import tc.oc.commons.bukkit.chat.ComponentRenderers;
import tc.oc.commons.bukkit.util.NullCommandSender;
import tc.oc.commons.bukkit.util.OnlinePlayerMapAdapter;
import tc.oc.commons.core.util.DefaultMapAdapter;
import tc.oc.commons.bukkit.channels.Channel;
import tc.oc.commons.bukkit.channels.ChannelRouter;
import tc.oc.pgm.events.CompetitorAddEvent;
import tc.oc.pgm.events.CompetitorRemoveEvent;
import tc.oc.pgm.events.ListenerScope;
import tc.oc.pgm.events.PartyAddEvent;
import tc.oc.pgm.events.PartyRemoveEvent;
import tc.oc.pgm.events.PlayerJoinPartyEvent;
import tc.oc.pgm.events.PlayerLeavePartyEvent;
import tc.oc.pgm.match.Match;
import tc.oc.pgm.match.MatchModule;
import tc.oc.pgm.match.MatchPlayer;
import tc.oc.pgm.match.MatchScope;
import tc.oc.pgm.match.MultiPlayerParty;
import tc.oc.pgm.match.Party;
import javax.inject.Inject;
import java.util.HashMap;
import java.util.Map;
@ListenerScope(MatchScope.LOADED)
public class ChannelMatchModule extends MatchModule implements Listener {
public static final String RECEIVE_ALL_PERMISSION = "pgm.chat.all.receive";
public static final String TEAM_RECEIVE_PERMISSION = "pgm.chat.team.receive";
public static final String TEAM_SEND_PERMISSION = "pgm.chat.team.send";
private final Map<Party, Channel> channels = new HashMap<>();
// This dynamic permission has all party channel listening permissions as children.
// A player with this permission receives all channels simultaneously. It is granted
// automatically to observers that have the RECEIVE_ALL_PERMISSION.
private final Permission matchListeningPermission;
@Inject
ChannelRouter channelRouter;
@Inject PartyChannel.Factory channelFactory;
private final ChannelsPlugin channelsPlugin = ChannelsPlugin.get();
private final Map<MultiPlayerParty, PartyChannel> partyChannels = new HashMap<>();
// This is used to keep track of players' global/team chat preference. We can't just
// check their channel through the Channels plugin because FFA players always use the
// global channel. All players are set to team chat when they join a match, and the /t
// command sets them to team chat. They can switch to global chat with /g, but we don't
// know when this happens, so we check for it whenever they switch parties.
private final Map<Player, Boolean> teamChatters;
@Inject ChannelMatchModule(Match match, Plugin plugin) {
this.matchListeningPermission = new Permission("pgm.chat.all." + match.getId() + ".receive", PermissionDefault.FALSE);
final OnlinePlayerMapAdapter<Boolean> map = new OnlinePlayerMapAdapter<>(plugin);
map.enable();
this.teamChatters = new DefaultMapAdapter<>(map, true, false);
}
protected void updatePlayerChannel(MatchPlayer player) {
if(teamChatters.get(player.getBukkit())) {
channelsPlugin.getPlayerManager().setMembershipChannel(player.getBukkit(), getChannel(player.getParty()));
} else {
channelsPlugin.getPlayerManager().setMembershipChannel(player.getBukkit(), channelsPlugin.getGlobalChannel());
public void create(Party party) {
if(!channels.containsKey(party) && party instanceof MultiPlayerParty) {
channels.put(party, channelFactory.create(party));
}
}
public void setTeamChat(MatchPlayer player, boolean teamChat) {
teamChatters.put(player.getBukkit(), teamChat);
updatePlayerChannel(player);
}
public Channel getChannel(Party party) {
if(party instanceof MultiPlayerParty) {
return partyChannels.get(party);
} else {
return channelsPlugin.getGlobalChannel();
}
}
protected Permission createChannelPermission(Party party) {
Permission permission = new Permission("pgm.chat.team." + this.match.getId() + '-' + party.hashCode() + ".receive", PermissionDefault.FALSE);
getMatch().getPluginManager().addPermission(permission);
permission.addParent(matchListeningPermission, true);
return permission;
}
protected void removeChannelPermission(Channel channel) {
matchListeningPermission.getChildren().remove(channel.getListeningPermission().getName());
matchListeningPermission.recalculatePermissibles();
getMatch().getPluginManager().removePermission(channel.getListeningPermission());
}
protected void createChannel(Party party) {
if(party instanceof MultiPlayerParty) {
logger.fine("Creating channel for " + party);
String format = ComponentRenderers.toLegacyText(party.getChatPrefix(), NullCommandSender.INSTANCE) + "{1}§f: {3}";
PartyChannel channel;
if (getMatch().getPluginManager().getPlugin("ChatModerator") == null) {
channel = new UnfilteredPartyChannel(format,
createChannelPermission(party),
party);
} else {
channel = new FilteredPartyChannel(format,
createChannelPermission(party),
party,
ChatModeratorPlugin.MINIMUM_SCORE_NO_SEND,
ChatModeratorPlugin.PARTIALLY_OFFENSIVE_RATIO);
}
if(partyChannels.put((MultiPlayerParty) party, channel) != null) {
throw new IllegalStateException("Party added multiple times");
}
}
public void remove(Party party) {
channels.remove(party);
}
@Override
public void load() {
super.load();
// Let the console receive all channels
match.getPluginManager().addPermission(matchListeningPermission);
getMatch().getServer().getConsoleSender().addAttachment(getMatch().getPlugin()).setPermission(matchListeningPermission, true);
// Parties may be created before the module loads
for(Party party : getMatch().getParties()) {
createChannel(party);
}
match.getParties().forEach(this::create);
channelRouter.setTeamChannelFunction(player -> channels.get(match.player(player).map(MatchPlayer::getParty).orElse(null)));
}
@Override
public void unload() {
getMatch().getServer().getConsoleSender().removeAttachments(matchListeningPermission);
getMatch().getPluginManager().removePermission(matchListeningPermission);
super.unload();
channelRouter.setTeamChannelFunction(null);
match.getParties().forEach(this::remove);
}
@EventHandler(priority = EventPriority.LOWEST)
public void partyAdd(final PartyAddEvent event) {
createChannel(event.getParty());
public void onPartyAdd(PartyAddEvent event) {
create(event.getParty());
}
@EventHandler(priority = EventPriority.MONITOR)
public void partyRemove(final PartyRemoveEvent event) {
if(event.getParty() instanceof MultiPlayerParty) {
PartyChannel channel = partyChannels.remove(event.getParty());
if(channel != null) {
removeChannelPermission(channel);
}
}
@EventHandler(priority = EventPriority.LOWEST)
public void onPartyRemove(PartyRemoveEvent event) {
remove(event.getParty());
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void partyJoin(PlayerJoinPartyEvent event) {
if(event.getNewParty() instanceof MultiPlayerParty) {
PlayerManager playerManager = channelsPlugin.getPlayerManager();
Player bukkitPlayer = event.getPlayer().getBukkit();
PartyChannel channel = partyChannels.get(event.getNewParty());
if(channel != null) {
if(event.getNewParty().isObservingType() && bukkitPlayer.hasPermission(RECEIVE_ALL_PERMISSION)) {
// If the player is joining observers and they have the receive-all perm, let them listen to all channels
bukkitPlayer.addAttachment(getMatch().getPlugin()).setPermission(matchListeningPermission, true);
} else if(bukkitPlayer.hasPermission(TEAM_RECEIVE_PERMISSION)) {
// Give the player listening permission for the team's channel
bukkitPlayer.addAttachment(getMatch().getPlugin()).setPermission(channel.getListeningPermission(), true);
}
// If their sending channel was previously set to a team channel, switch it to the new team's channel
if(playerManager.getMembershipChannel(bukkitPlayer) instanceof PartyChannel) {
playerManager.setMembershipChannel(bukkitPlayer, channel);
}
}
}
updatePlayerChannel(event.getPlayer());
@EventHandler(priority = EventPriority.LOWEST)
public void onCompetitorAdd(CompetitorAddEvent event) {
create(event.getParty());
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void partyLeave(PlayerLeavePartyEvent event) {
if(event.getOldParty() instanceof MultiPlayerParty) {
PlayerManager playerManager = channelsPlugin.getPlayerManager();
Player bukkitPlayer = event.getPlayer().getBukkit();
PartyChannel channel = partyChannels.get(event.getOldParty());
if(channel != null) {
bukkitPlayer.removeAttachments(channel.getListeningPermission());
bukkitPlayer.removeAttachments(matchListeningPermission);
// Whenever the player leaves a party with its own channel, check if that is the player's current channel,
// and if it's not, then set their team chat setting to false. This is the only way to find out when they
// switch to global chat, because the Channels plugin doesn't provide any notifcation.
if(playerManager.getMembershipChannel(bukkitPlayer) != channel) {
teamChatters.put(bukkitPlayer, false);
}
}
}
@EventHandler(priority = EventPriority.LOWEST)
public void onCompetitorRemove(CompetitorRemoveEvent event) {
remove(event.getParty());
}
}

View File

@ -1,21 +0,0 @@
package tc.oc.pgm.channels;
import com.google.common.base.Preconditions;
import org.bukkit.permissions.Permission;
import tc.oc.chatmoderator.channels.SimpleFilteredChannel;
import tc.oc.pgm.match.Party;
public class FilteredPartyChannel extends SimpleFilteredChannel implements PartyChannel {
private final Party party;
public FilteredPartyChannel(String format, final Permission permission, final Party party, int minimumScoreNoSend, float partial) {
super(format, permission, minimumScoreNoSend, partial);
this.party = Preconditions.checkNotNull(party, "party");
}
@Override
public Party getParty() {
return this.party;
}
}

View File

@ -1,8 +1,69 @@
package tc.oc.pgm.channels;
import com.github.rmsy.channels.Channel;
import com.google.inject.assistedinject.Assisted;
import net.md_5.bungee.api.chat.BaseComponent;
import org.bukkit.command.CommandSender;
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.Audience;
import tc.oc.commons.core.chat.Component;
import tc.oc.commons.core.chat.MultiAudience;
import tc.oc.pgm.match.MatchPlayer;
import tc.oc.pgm.match.Party;
public interface PartyChannel extends Channel {
Party getParty();
import javax.inject.Inject;
import java.util.stream.Stream;
public class PartyChannel extends SimpleChannel implements MultiAudience {
public static final String RECEIVE_ALL_PERMISSION = "pgm.chat.receive.all";
public interface Factory {
PartyChannel create(Party party);
}
private final Party party;
@Inject PartyChannel(@Assisted Party party) {
this.party = party;
}
public Party party() {
return party;
}
@Override
public BaseComponent prefix() {
return party.getChatPrefix();
}
@Override
public BaseComponent format(PlayerComponent player, String message) {
return new Component(player).extra(": ").extra(message);
}
@Override
public ChatDoc.Type type() {
return ChatDoc.Type.TEAM;
}
@Override
public boolean sendable(CommandSender sender) {
return party.getPlayers().contains(party.getMatch().getPlayer(sender));
}
@Override
public boolean viewable(CommandSender sender) {
final MatchPlayer player = party.getMatch().getPlayer(sender);
return sendable(sender) ||
player == null ||
(sender.hasPermission(RECEIVE_ALL_PERMISSION) && player.isObserving());
}
@Override
public Stream<? extends Audience> audiences() {
return party.players();
}
}

View File

@ -1,21 +0,0 @@
package tc.oc.pgm.channels;
import com.github.rmsy.channels.impl.SimpleChannel;
import com.google.common.base.Preconditions;
import org.bukkit.permissions.Permission;
import tc.oc.pgm.match.Party;
public class UnfilteredPartyChannel extends SimpleChannel implements PartyChannel {
private Party party;
public UnfilteredPartyChannel(String format, final Permission permission, Party party) {
super(format, permission);
this.party = Preconditions.checkNotNull(party, "party");
}
@Override
public Party getParty() {
return this.party;
}
}

View File

@ -23,7 +23,7 @@ import org.bukkit.command.CommandSender;
import tc.oc.api.docs.User;
import tc.oc.api.docs.virtual.MapDoc;
import tc.oc.api.util.Permissions;
import tc.oc.commons.bukkit.chat.BukkitAudiences;
import tc.oc.commons.bukkit.chat.Audiences;
import tc.oc.commons.bukkit.chat.ComponentRenderContext;
import tc.oc.commons.bukkit.chat.NameStyle;
import tc.oc.commons.bukkit.chat.PlayerComponent;
@ -166,7 +166,7 @@ public class MapCommands implements Commands {
if(args.getSuggestionContext() != null) {
return CommandUtils.completeMapName(args.getJoinedStrings(0));
}
final Audience audience = BukkitAudiences.getAudience(sender);
final Audience audience = Audiences.Deprecated.get(sender);
final PGMMap map;
if(args.argsLength() > 0) {
map = CommandUtils.getMap(args.getJoinedStrings(0), sender);

View File

@ -2,6 +2,7 @@ package tc.oc.pgm.ffa;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import javax.inject.Inject;
@ -223,8 +224,8 @@ public class Tribute implements Competitor {
}
@Override
public Audience audience() {
return player != null ? player : NullAudience.INSTANCE;
public Stream<? extends Audience> audiences() {
return player != null ? Stream.of(player) : Stream.empty();
}
@Override

View File

@ -14,15 +14,13 @@ import org.bukkit.command.CommandSender;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.entity.TNTPrimed;
import tc.oc.commons.bukkit.channels.AdminChannel;
import tc.oc.commons.bukkit.channels.admin.AdminChannel;
import tc.oc.commons.bukkit.chat.Audiences;
import tc.oc.commons.bukkit.chat.BukkitSound;
import tc.oc.commons.bukkit.chat.NameStyle;
import tc.oc.commons.bukkit.chat.PlayerComponent;
import tc.oc.commons.bukkit.commands.CommandUtils;
import tc.oc.commons.bukkit.freeze.FrozenPlayer;
import tc.oc.commons.bukkit.freeze.PlayerFreezer;
import tc.oc.commons.bukkit.nick.Identity;
import tc.oc.commons.bukkit.nick.IdentityProvider;
import tc.oc.commons.bukkit.util.OnlinePlayerMapAdapter;
import tc.oc.commons.core.chat.Audience;
@ -81,20 +79,15 @@ public class Freeze implements PluginFacet {
));
}
final Identity freezerIdentity = identityProvider.createIdentity(freezer);
final PlayerComponent freezerComponent = new PlayerComponent(identityProvider.createIdentity(freezer), NameStyle.VERBOSE);
final PlayerComponent freezeeComponent = new PlayerComponent(identityProvider.createIdentity(freezee), NameStyle.VERBOSE);
final Audience freezeeAudience = audiences.get(freezee);
final FrozenPlayer frozenPlayer = frozenPlayers.get(freezee);
if(frozen && frozenPlayer == null) {
frozenPlayers.put(freezee, playerFreezer.freeze(freezee));
final BaseComponent freezeeMessage = new Component(
new TranslatableComponent(
"freeze.frozen",
new PlayerComponent(freezerIdentity, NameStyle.FANCY)
),
ChatColor.RED
);
final BaseComponent freezeeMessage = new Component(new TranslatableComponent("freeze.frozen", freezerComponent), ChatColor.RED);
freezeeAudience.playSound(FREEZE_SOUND);
freezeeAudience.sendWarning(freezeeMessage, false);
@ -106,26 +99,15 @@ public class Freeze implements PluginFacet {
removeEntities(((Player) freezer).getLocation(), config.tntSenderRadius());
}
adminChannel.broadcast(CommandUtils.getDisplayName(freezer) +
ChatColor.RED + " froze " +
CommandUtils.getDisplayName(freezee));
adminChannel.sendMessage(new TranslatableComponent("freeze.frozen.broadcast", freezeeComponent, freezerComponent));
} else if(!frozen && frozenPlayer != null) {
frozenPlayer.thaw();
frozenPlayers.remove(freezee);
freezeeAudience.hideTitle();
freezeeAudience.playSound(THAW_SOUND);
freezeeAudience.sendMessage(new Component(
new TranslatableComponent(
"freeze.unfrozen",
new PlayerComponent(freezerIdentity, NameStyle.FANCY)
),
ChatColor.GREEN
));
adminChannel.broadcast(CommandUtils.getDisplayName(freezer) +
ChatColor.RED + " unfroze " +
CommandUtils.getDisplayName(freezee));
freezeeAudience.sendMessage(new Component(new TranslatableComponent("freeze.unfrozen", freezerComponent), ChatColor.GREEN));
adminChannel.sendMessage(new TranslatableComponent("freeze.unfrozen.broadcast", freezeeComponent, freezerComponent));
}
}

View File

@ -197,11 +197,11 @@ public abstract class TouchableGoal<T extends ProximityGoalDefinition> extends P
if(toucher != null) {
if(includeToucher) {
toucher.getAudience().sendMessage(getTouchMessage(toucher, true));
toucher.sendMessage(getTouchMessage(toucher, true));
}
if(getDeferTouches()) {
toucher.getAudience().sendMessage(new TranslatableComponent("match.touch.destroyable.deferredNotice"));
toucher.sendMessage(new TranslatableComponent("match.touch.destroyable.deferredNotice"));
}
}
}

View File

@ -478,7 +478,7 @@ public class BlockTransformListener implements PluginFacet, Listener {
event.getCancelMessage() != null &&
event.isManual()) {
((PlayerBlockTransformEvent) event).getPlayerState().getAudience().sendWarning(event.getCancelMessage(), false);
((PlayerBlockTransformEvent) event).getPlayerState().sendWarning(event.getCancelMessage(), false);
}
}

View File

@ -246,7 +246,7 @@ public class MapRatingsMatchModule extends MatchModule implements Listener {
}
if(oldScore != null && score == oldScore) {
player.sendWarning(PGMTranslations.t("rating.sameRating", player, score), true);
player.sendWarning(PGMTranslations.t("rating.sameRating", player, score), false);
return;
}

View File

@ -25,7 +25,7 @@ import java.time.Duration;
import java.time.Instant;
import tc.oc.api.docs.PlayerId;
import tc.oc.api.docs.UserId;
import tc.oc.commons.core.chat.Audience;
import tc.oc.commons.core.chat.MultiAudience;
import tc.oc.commons.core.inject.InjectionScopable;
import tc.oc.commons.core.random.Entropy;
import tc.oc.commons.core.util.ArrayUtils;
@ -47,7 +47,7 @@ import tc.oc.pgm.module.ModuleLoadException;
import tc.oc.pgm.time.TickClock;
import tc.oc.pgm.time.TickTime;
public interface Match extends Audience, IMatchQuery, Filterable<IMatchQuery>, MatchPlayerFinder, InjectionScopable<MatchScoped> {
public interface Match extends MultiAudience, IMatchQuery, Filterable<IMatchQuery>, MatchPlayerFinder, InjectionScopable<MatchScoped> {
/**
* Unique ID for this match

View File

@ -1,50 +1,46 @@
package tc.oc.pgm.match;
import javax.inject.Inject;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import tc.oc.commons.bukkit.chat.ConsoleAudience;
import tc.oc.commons.core.chat.AbstractMultiAudience;
import tc.oc.commons.bukkit.chat.Audiences;
import tc.oc.commons.core.chat.Audience;
import tc.oc.commons.core.chat.MultiAudience;
import tc.oc.minecraft.api.entity.Player;
import tc.oc.pgm.filters.Filter;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Provides various aggregate {@link Audience}s within a match
*/
@Singleton
public class MatchAudiences {
private final Match match;
private final Audience participants;
private final Audience observers;
private final ImmutableSet<ConsoleAudience> console;
private final Audiences audiences;
@Inject MatchAudiences(Match match, ConsoleAudience console) {
@Inject
MatchAudiences(Match match, Audiences audiences) {
this.match = match;
this.console = ImmutableSet.of(console);
this.participants = new MultiAudience(Iterables.concat(this.console, match.getParticipatingPlayers()));
this.observers = new MultiAudience(Iterables.concat(this.console, match.getObservingPlayers()));
this.audiences = audiences;
}
public Audience all() {
return match;
public MultiAudience all() {
return () -> Stream.of(audiences.console(), audiences.playerFilter(player -> match.player(player).isPresent()));
}
public Audience participants() {
return participants;
public MultiAudience participants() {
return () -> Stream.of(audiences.console(), audiences.playerFilter(player -> match.participant(player).isPresent()));
}
public Audience observers() {
return observers;
public MultiAudience observers() {
return () -> Stream.of(audiences.console(), audiences.playerFilter(player -> match.getObservingPlayers().contains(match.player(player).orElse(null))));
}
public Audience filter(Filter filter) {
return new AbstractMultiAudience() {
@Override
protected Iterable<? extends Audience> getAudiences() {
return Iterables.concat(console, Iterables.filter(match.getPlayers(), player -> !filter.denies(player)));
}
};
public MultiAudience filter(Filter filter) {
final Set<Player> allowed = match.players().filter(player -> !filter.denies(player)).map(MatchPlayer::getBukkit).collect(Collectors.toSet());
return () -> Stream.of(audiences.console(), audiences.playerFilter(allowed::contains));
}
}

View File

@ -24,13 +24,10 @@ import com.google.common.collect.BiMap;
import com.google.common.collect.BoundType;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimaps;
import com.google.common.collect.Range;
import com.google.common.collect.SetMultimap;
import net.md_5.bungee.api.chat.BaseComponent;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.entity.Player;
@ -45,10 +42,8 @@ import tc.oc.api.docs.PlayerId;
import tc.oc.api.docs.User;
import tc.oc.api.docs.UserId;
import tc.oc.api.model.IdFactory;
import tc.oc.commons.bukkit.chat.ConsoleAudience;
import tc.oc.commons.bukkit.chat.Audiences;
import tc.oc.commons.core.chat.Audience;
import tc.oc.commons.core.chat.ForwardingAudience;
import tc.oc.commons.core.chat.MultiAudience;
import tc.oc.commons.core.exception.ExceptionHandler;
import tc.oc.commons.core.inject.ChildInjectorFactory;
import tc.oc.commons.core.inject.FacetContext;
@ -99,7 +94,7 @@ import tc.oc.pgm.utils.WorldTickRandom;
import static com.google.common.base.Preconditions.*;
public class MatchImpl implements Match, ForwardingAudience {
public class MatchImpl implements Match {
private Logger logger;
@ -121,8 +116,7 @@ public class MatchImpl implements Match, ForwardingAudience {
@Inject private FeatureDefinitionContext featureDefinitions;
@Inject private World world;
@Inject private ConsoleAudience consoleAudience;
private Audience audience;
@Inject private Audiences audiences;
// State management
private final AtomicBoolean unloaded = new AtomicBoolean(true); // true before loading starts and after unloading finishes
@ -194,7 +188,6 @@ public class MatchImpl implements Match, ForwardingAudience {
id = idFactory.newId();
url = new URL("http", "localhost:3000", "/matches/" + id);
loadTime = clock.now();
audience = new MultiAudience(Iterables.concat(ImmutableSet.of(consoleAudience), getPlayers()));
setState(MatchState.Idle);
}
@ -265,8 +258,8 @@ public class MatchImpl implements Match, ForwardingAudience {
}
@Override
public Audience audience() {
return audience;
public Stream<Audience> audiences() {
return Stream.of(audiences.console(), audiences.filter(sender -> player(sender).isPresent()));
}
@ -927,21 +920,4 @@ public class MatchImpl implements Match, ForwardingAudience {
partyChanges.remove(player);
}
}
// --------------
// ---- Chat ----
// --------------
@Override
public void sendMessageExcept(BaseComponent message, MatchPlayer... except) {
consoleAudience.sendMessage(message);
Match.super.sendMessageExcept(message, except);
}
@Override
public void sendMessageExcept(BaseComponent message, MatchPlayerState... except) {
consoleAudience.sendMessage(message);
Match.super.sendMessageExcept(message, except);
}
}

View File

@ -32,7 +32,7 @@ import tc.oc.api.docs.PlayerId;
import tc.oc.api.docs.Server;
import tc.oc.api.docs.User;
import tc.oc.commons.bukkit.attribute.AttributeUtils;
import tc.oc.commons.bukkit.chat.BukkitAudiences;
import tc.oc.commons.bukkit.chat.Audiences;
import tc.oc.commons.bukkit.chat.BukkitSound;
import tc.oc.commons.bukkit.chat.NameStyle;
import tc.oc.commons.bukkit.chat.Named;
@ -77,6 +77,7 @@ public class MatchPlayer extends MatchFacetContext<MatchPlayerFacet> implements
@Inject private Server localServer;
@Inject private OnlineFriends friendMap;
@Inject private PlayerStates playerStates;
@Inject private Audiences audiences;
@Inject MatchUserContext userContext;
@ -90,7 +91,7 @@ public class MatchPlayer extends MatchFacetContext<MatchPlayerFacet> implements
private Logger logger;
private SettingManager settings;
private Audience audience;
@Inject private void init(Loggers loggers, SettingManagerProvider settingManagerProvider, BukkitAudiences audiences, Player player) {
@Inject private void init(Loggers loggers, SettingManagerProvider settingManagerProvider, Audiences audiences, Player player) {
this.logger = loggers.get(match.getLogger(), getClass(), getName());
this.settings = settingManagerProvider.getManager(player);
this.audience = audiences.get(player);
@ -519,19 +520,19 @@ public class MatchPlayer extends MatchFacetContext<MatchPlayerFacet> implements
}
@Override
public Audience audience() {
return audience;
public Optional<Audience> audience() {
return Optional.ofNullable(audiences.get(bukkit));
}
@Override
public void sendWarning(String message, boolean audible) {
audience().sendWarning(message, audible);
audience().get().sendWarning(message, audible);
if(audible) playWarningSound();
}
@Override
public void sendWarning(BaseComponent message, boolean audible) {
audience().sendWarning(message, audible);
audience().get().sendWarning(message, audible);
if(audible) playWarningSound();
}

View File

@ -34,6 +34,10 @@ public interface MatchPlayerFinder {
return state == null ? null : getPlayer(state.getUniqueId());
}
default Optional<MatchPlayer> player(@Nullable tc.oc.minecraft.api.entity.Player api) {
return Optional.ofNullable(getPlayer((Player) api));
}
default Optional<MatchPlayer> player(@Nullable Player bukkit) {
return Optional.ofNullable(getPlayer(bukkit));
}
@ -54,6 +58,10 @@ public interface MatchPlayerFinder {
return Optional.ofNullable(getPlayer(userId));
}
default Optional<MatchPlayer> participant(@Nullable tc.oc.minecraft.api.entity.Player api) {
return player(api).filter(MatchPlayer::isParticipating);
}
default Optional<MatchPlayer> participant(@Nullable Entity entity) {
return player(entity).filter(MatchPlayer::isParticipating);
}

View File

@ -12,6 +12,7 @@ import tc.oc.commons.bukkit.chat.NameStyle;
import tc.oc.commons.bukkit.chat.PlayerComponent;
import tc.oc.commons.bukkit.nick.Identity;
import tc.oc.commons.core.chat.Audience;
import tc.oc.commons.core.chat.ForwardingAudience;
import tc.oc.commons.core.chat.NullAudience;
import tc.oc.commons.core.util.Utils;
import tc.oc.pgm.filters.query.IPlayerQuery;
@ -21,7 +22,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
/**
* Represents a "snapshot" view of a {@link MatchPlayer}.
*/
public class MatchPlayerState extends MatchEntityState implements IPlayerQuery {
public class MatchPlayerState extends MatchEntityState implements IPlayerQuery, ForwardingAudience {
protected final Identity identity;
protected final Party party;
@ -88,9 +89,9 @@ public class MatchPlayerState extends MatchEntityState implements IPlayerQuery {
return getParty().isParticipating();
}
public Audience getAudience() {
MatchPlayer matchPlayer = getMatchPlayer();
return matchPlayer == null ? NullAudience.INSTANCE : matchPlayer;
@Override
public Optional<Audience> audience() {
return Optional.ofNullable(getMatchPlayer());
}
@Override

View File

@ -2,12 +2,12 @@ package tc.oc.pgm.match;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import org.bukkit.entity.Player;
import tc.oc.api.bukkit.users.Users;
import tc.oc.api.docs.PlayerId;
import tc.oc.commons.core.chat.AbstractMultiAudience;
import tc.oc.commons.core.chat.Audience;
public abstract class MultiPlayerParty implements Party {
@ -15,13 +15,6 @@ public abstract class MultiPlayerParty implements Party {
protected final Match match;
private final Set<MatchPlayer> players = new HashSet<>();
private final Audience audience = new AbstractMultiAudience() {
@Override
protected Iterable<? extends Audience> getAudiences() {
return getPlayers();
}
};
public MultiPlayerParty(Match match) {
this.match = match;
}
@ -64,7 +57,7 @@ public abstract class MultiPlayerParty implements Party {
}
@Override
public Audience audience() {
return audience;
public Stream<? extends Audience> audiences() {
return players();
}
}

View File

@ -11,6 +11,7 @@ import tc.oc.commons.core.chat.Audience;
import tc.oc.commons.bukkit.chat.Named;
import tc.oc.commons.core.chat.Component;
import tc.oc.commons.core.chat.Components;
import tc.oc.commons.core.chat.MultiAudience;
import tc.oc.pgm.filters.Filterable;
import tc.oc.pgm.filters.query.IPartyQuery;
@ -19,7 +20,7 @@ import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
public interface Party extends Named, IPartyQuery, Filterable<IPartyQuery> {
public interface Party extends Named, IPartyQuery, Filterable<IPartyQuery>, MultiAudience {
enum Type { Participating, Observing }
@ -156,8 +157,6 @@ public interface Party extends Named, IPartyQuery, Filterable<IPartyQuery> {
return isObservingType() || !getMatch().isRunning();
}
Audience audience();
@Override
default Optional<? extends Filterable<? super IPartyQuery>> filterableParent() {
return Optional.of(getMatch());

View File

@ -5,7 +5,6 @@ import tc.oc.pgm.api.EngagementMatchModule;
import tc.oc.pgm.api.MatchPublishingMatchModule;
import tc.oc.pgm.api.ParticipationPublishingMatchModule;
import tc.oc.pgm.bossbar.BossBarMatchModule;
import tc.oc.pgm.channels.ChannelMatchModule;
import tc.oc.pgm.cycle.CycleMatchModule;
import tc.oc.pgm.damage.HitboxMatchModule;
import tc.oc.pgm.death.DeathMessageMatchModule;
@ -52,7 +51,6 @@ public class MatchModulesManifest extends HybridManifest {
install(new MatchModuleFixtureManifest<SkillRequirementMatchModule>(){});
install(new MatchModuleFixtureManifest<StartMatchModule>(){});
install(new MatchModuleFixtureManifest<ArrowRemovalMatchModule>(){});
install(new MatchModuleFixtureManifest<ChannelMatchModule>(){});
install(new MatchModuleFixtureManifest<DoubleJumpMatchModule>(){});
install(new MatchModuleFixtureManifest<GoalMatchModule>(){});
install(new MatchModuleFixtureManifest<ProjectileMatchModule>(){});

View File

@ -167,7 +167,7 @@ public class MutationCommands implements NestedCommands {
}
}
Audience origin = audiences.get(sender);
Audience all = audiences.localServer();
Audience all = audiences.all();
String message = message(!queued, value, mutations.size() == 1);
ListComponent changed = new ListComponent(Collections2.transform(mutations, Mutation.toComponent(ChatColor.AQUA)));
if(queued) {

View File

@ -195,7 +195,7 @@ public class EntityMutation<E extends Entity> extends KitMutation {
@EventHandler(ignoreCancelled = false, priority = EventPriority.HIGHEST)
public void onPlayerSpawnEntity(PlayerSpawnEntityEvent event) {
match().participant(event.getPlayer())
match().participant((Entity) event.getPlayer())
.ifPresent(player -> cast(event.getEntity(), type)
.ifPresent(entity -> {
register(entity, player);

View File

@ -66,7 +66,7 @@ public class DynamicRotationListener implements PluginFacet, Listener {
rotationManager.setCurrentRotationName(rotation.name);
logger.info("Changing to \"" + rotation.name + "\" rotation...");
sendRotationChangeMessage(audiences.localServer(), oldRotation, rotation.name);
sendRotationChangeMessage(audiences.all(), oldRotation, rotation.name);
}
}
}

View File

@ -13,6 +13,8 @@ import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import tc.oc.api.docs.virtual.ChatDoc;
import tc.oc.commons.bukkit.channels.ChannelChatEvent;
import tc.oc.commons.core.chat.Component;
import tc.oc.commons.core.formatting.PeriodFormats;
import tc.oc.commons.core.util.Comparables;
@ -59,7 +61,7 @@ public class HuddleCountdown extends PreMatchCountdown implements Listener {
if(Comparables.greaterThan(total, Duration.ZERO)) {
getMatch().getCompetitors().stream().filter(competitor -> competitor instanceof Team).forEach(competitor -> {
competitor.audience().sendMessage(new Component(
competitor.sendMessage(new Component(
new TranslatableComponent("huddle.instructions",
PeriodFormats.briefNaturalPrecise(total)),
ChatColor.YELLOW
@ -69,12 +71,12 @@ public class HuddleCountdown extends PreMatchCountdown implements Listener {
}
@EventHandler
public void onChat(ChannelMessageEvent event) {
if(event.getChannel() == ChannelsPlugin.get().getGlobalChannel()) {
public void onChat(ChannelChatEvent event) {
if(event.channel().type().equals(ChatDoc.Type.SERVER)) {
event.setCancelled(true);
MatchPlayer player = getMatch().getPlayer(event.getSender());
MatchPlayer player = getMatch().getPlayer(event.sender());
if(player != null) {
player.sendWarning(new TranslatableComponent("huddle.globalChatDisabled"));
player.sendWarning(new TranslatableComponent("huddle.globalChatDisabled"), false);
}
}
}

View File

@ -477,7 +477,7 @@ public class TeamMatchModule extends MatchModule implements Listener, JoinHandle
MatchPlayer player = shortList.get(i);
if(even && areTeamsEven() && shortList.size() - i < getTeams().size()) {
// Prevent join if even teams are required, and there aren't enough remaining players to go around
player.sendWarning(new TranslatableComponent("command.gameplay.join.uneven"));
player.sendWarning(new TranslatableComponent("command.gameplay.join.uneven"), false);
} else {
join(player, request, queryJoin(player, request, true));
}

View File

@ -168,9 +168,9 @@ public class WoolMatchModule extends MatchModule implements Listener {
if(player != null) { // wool can only be placed by a player
BaseComponent woolName = BukkitUtils.woolName(wool.getDyeColor());
if(!isValidWool(wool.getDyeColor(), event.getNewState())) {
player.getAudience().sendWarning(new TranslatableComponent("match.wool.placeWrong", woolName), true);
player.sendWarning(new TranslatableComponent("match.wool.placeWrong", woolName), true);
} else if(wool.getOwner() != player.getParty()) {
player.getAudience().sendWarning(new TranslatableComponent("match.wool.placeOther", wool.getOwner().getComponentName(), woolName), true);
player.sendWarning(new TranslatableComponent("match.wool.placeOther", wool.getOwner().getComponentName(), woolName), true);
} else {
event.setCancelled(false);
wool.markPlaced();

View File

@ -6,9 +6,6 @@ depend:
- Commons
- API
- BukkitSettings
- Channels
softdepend:
- ChatModerator
authors:
- Overcast Network

View File

@ -76,7 +76,7 @@ public class Tourney extends JavaPlugin {
);
audiences.get(Bukkit.getConsoleSender()).sendMessage(message);
audiences.withPermission(TourneyPermissions.REFEREE).sendMessage(message);
audiences.permission(TourneyPermissions.REFEREE).sendMessage(message);
}
}
);

View File

@ -4,7 +4,6 @@ import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;
import com.github.rmsy.channels.ChannelsPlugin;
import net.anxuiz.tourney.MatchManager;
import net.anxuiz.tourney.TeamManager;
import net.anxuiz.tourney.Tourney;
@ -137,10 +136,6 @@ public class TeamListener implements Listener {
Team team = teamManagerProvider.get().getTeam(player);
if(team != null) {
event.getMatch().setPlayerParty(event.getPlayer(), team, false);
ChannelsPlugin.get().getPlayerManager().setMembershipChannel(
player,
event.getMatch().needMatchModule(ChannelMatchModule.class).getChannel(team)
);
}
}
}

View File

@ -139,7 +139,7 @@ public class VoteContext {
// broadcast to team
if (classificationSelected) {
for (Entrant participation : vote.getParticipatingTeams()) {
final Audience team = teamManager.entrantToTeam(participation).audience();
final Audience team = teamManager.entrantToTeam(participation);
team.sendMessage(StringUtils.dashedChatMessage(ChatColor.GRAY + " Veto Information", "-", ChatColor.RED + "" + ChatColor.STRIKETHROUGH));
team.sendMessage(ChatColor.GRAY + "Your team may now veto another map.");
team.sendMessage(ChatColor.GRAY + "Note that any team member may veto. Please consult with your teammates and choose wisely.");
@ -149,7 +149,7 @@ public class VoteContext {
}
} else {
for (Entrant participation : vote.getParticipatingTeams()) {
final Audience team = teamManager.entrantToTeam(participation).audience();
final Audience team = teamManager.entrantToTeam(participation);
team.sendMessage(StringUtils.dashedChatMessage(ChatColor.GRAY + " Veto Information", "-", ChatColor.RED + "" + ChatColor.STRIKETHROUGH));
team.sendMessage(ChatColor.GRAY + "Your team may now veto another classification.");
team.sendMessage(ChatColor.GRAY + "Note that any team member may veto. Please consult with your teammates and choose wisely.");

View File

@ -1,6 +1,187 @@
package tc.oc.commons.bukkit.chat;
import net.md_5.bungee.api.ChatMessageType;
import net.md_5.bungee.api.chat.BaseComponent;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import tc.oc.commons.core.chat.AbstractAudiences;
import tc.oc.commons.core.chat.Audience;
import tc.oc.commons.core.chat.Component;
import tc.oc.commons.core.chat.Components;
import tc.oc.commons.core.chat.ConsoleAudience;
import tc.oc.commons.core.chat.NullAudience;
import tc.oc.commons.core.chat.Sound;
import tc.oc.commons.core.stream.Collectors;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.Optional;
import java.util.stream.Stream;
import static tc.oc.minecraft.protocol.MinecraftVersion.lessThan;
import static tc.oc.minecraft.protocol.MinecraftVersion.MINECRAFT_1_8;
@Singleton
public class Audiences extends AbstractAudiences<CommandSender> {
@Inject
ComponentRenderContext renderContext;
@Override
public Audience get(@Nullable CommandSender sender) {
if(sender == null) {
return NullAudience.INSTANCE;
} if(sender instanceof org.bukkit.entity.Player) {
org.bukkit.entity.Player player = (org.bukkit.entity.Player) sender;
if(lessThan(MINECRAFT_1_8, player.getProtocolVersion())) {
return new LegacyPlayer((org.bukkit.entity.Player) sender);
} else {
return new Player((org.bukkit.entity.Player) sender);
}
} else if(sender instanceof ConsoleCommandSender) {
return new Console(sender.getServer().getConsoleSender());
} else {
return new Sender(sender);
}
}
abstract class Base<T extends CommandSender> implements Audience {
private final T sender;
protected Base(T sender) {
this.sender = sender;
}
protected T sender() {
return sender;
}
@Override
public void sendMessage(BaseComponent message) {
sender().sendMessage(render(message));
}
@Override
public void sendWarning(BaseComponent message, boolean audible) {
sendMessage(new WarningComponent(message));
}
protected BaseComponent render(@Nullable BaseComponent component) {
return component == null ? new Component() : renderContext.render(component, sender());
}
}
class Sender extends Base<CommandSender> {
public Sender(CommandSender sender) {
super(sender);
}
@Override
public void playSound(Sound sound) {}
@Override
public void stopSound(Sound sound) {}
@Override
public void sendHotbarMessage(BaseComponent message) {
sendMessage(message);
}
@Override
public void showTitle(@Nullable BaseComponent title, @Nullable BaseComponent subtitle, int inTicks, int stayTicks, int outTicks) {
Optional.ofNullable(title).ifPresent(this::sendMessage);
Optional.ofNullable(subtitle).ifPresent(this::sendMessage);
}
@Override
public void hideTitle() {}
}
class Player extends Base<org.bukkit.entity.Player> {
public Player(org.bukkit.entity.Player player) {
super(player);
}
@Override
public void sendHotbarMessage(BaseComponent message) {
sender().sendMessage(ChatMessageType.ACTION_BAR, message);
}
@Override
public void hideTitle() {
sender().hideTitle();
}
@Override
public void showTitle(@Nullable BaseComponent title, @Nullable BaseComponent subtitle, int inTicks, int stayTicks, int outTicks) {
sender().showTitle(render(title), render(subtitle), inTicks, stayTicks, outTicks);
}
@Override
public void playSound(Sound sound) {
sender().playSound(sender().getLocation(), sound.name(), sound.volume(), sound.pitch());
}
@Override
public void stopSound(Sound sound) {
sender().stopSound(sound.name());
}
}
class LegacyPlayer extends Player {
private BaseComponent recentHotbarMessage;
public LegacyPlayer(org.bukkit.entity.Player player) {
super(player);
}
protected void emphasize(BaseComponent message) {
sendMessage(Components.blank());
sendMessage(message);
sendMessage(Components.blank());
}
@Override
public void sendHotbarMessage(BaseComponent message) {
// Do not spam hot bar messages, as the protocol converts
// them to regular chat messages.
if(!Components.equals(message, recentHotbarMessage)) {
emphasize(message);
recentHotbarMessage = message;
}
}
@Override
public void showTitle(@Nullable BaseComponent title, @Nullable BaseComponent subtitle, int inTicks, int stayTicks, int outTicks) {
emphasize(Components.join(Components.space(), Stream.of(title, subtitle).filter(msg -> msg != null).collect(Collectors.toImmutableList())));
}
}
class Console extends Base<ConsoleCommandSender> implements ConsoleAudience {
public Console(ConsoleCommandSender sender) {
super(sender);
}
}
public static class Deprecated {
@Inject static Audiences audiences;
@java.lang.Deprecated
public static Audience get(@Nullable CommandSender sender) {
return audiences.get(sender);
}
}
public interface Audiences extends tc.oc.commons.core.chat.Audiences<CommandSender> {
}

View File

@ -1,40 +0,0 @@
package tc.oc.commons.bukkit.chat;
import javax.inject.Singleton;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.entity.Player;
import tc.oc.commons.core.chat.Audience;
import tc.oc.commons.core.chat.MinecraftAudiences;
import tc.oc.commons.core.chat.NullAudience;
import static tc.oc.minecraft.protocol.MinecraftVersion.lessThan;
import static tc.oc.minecraft.protocol.MinecraftVersion.MINECRAFT_1_8;
@Singleton
public class BukkitAudiences extends MinecraftAudiences<CommandSender> implements Audiences {
@Deprecated
public static Audience getAudience(CommandSender sender) {
if(sender == null) {
return NullAudience.INSTANCE;
} if(sender instanceof Player) {
Player player = (Player) sender;
if(lessThan(MINECRAFT_1_8, player.getProtocolVersion())) {
return new LegacyPlayerAudience(player);
} else {
return new PlayerAudience(player);
}
} else if(sender instanceof ConsoleCommandSender) {
return new ConsoleAudience(sender.getServer());
} else {
return new CommandSenderAudience(sender);
}
}
@Override
public Audience get(CommandSender viewer) {
return getAudience(viewer);
}
}

View File

@ -1,50 +0,0 @@
package tc.oc.commons.bukkit.chat;
import javax.annotation.Nullable;
import net.md_5.bungee.api.chat.BaseComponent;
import org.bukkit.command.CommandSender;
import tc.oc.commons.core.chat.AbstractAudience;
import tc.oc.commons.core.chat.Sound;
public class CommandSenderAudience extends AbstractAudience {
protected final CommandSender sender;
public CommandSenderAudience(CommandSender sender) {
this.sender = sender;
}
protected CommandSender getCommandSender() {
return sender;
}
@Override
public void sendMessage(String message) {
getCommandSender().sendMessage(message);
}
@Override
public void sendMessage(BaseComponent message) {
ComponentRenderers.send(getCommandSender(), message);
}
@Override
public void sendHotbarMessage(BaseComponent message) {
sendMessage(message);
}
@Override
public void showTitle(@Nullable BaseComponent title, @Nullable BaseComponent subtitle, int inTicks, int stayTicks, int outTicks) {
if(title != null) sendMessage(title);
if(subtitle != null) sendMessage(subtitle);
}
@Override
public void hideTitle() {
}
@Override
public void playSound(Sound sound) {
}
}

View File

@ -1,26 +0,0 @@
package tc.oc.commons.bukkit.chat;
import javax.inject.Inject;
import net.md_5.bungee.api.chat.BaseComponent;
import org.bukkit.Server;
import tc.oc.commons.core.chat.AbstractConsoleAudience;
public class ConsoleAudience extends AbstractConsoleAudience {
private final Server server;
@Inject public ConsoleAudience(Server server) {
this.server = server;
}
@Override
public void sendMessage(BaseComponent message) {
ComponentRenderers.send(server.getConsoleSender(), message);
}
@Override
public void sendMessage(String message) {
server.getConsoleSender().sendMessage(message);
}
}

View File

@ -1,39 +0,0 @@
package tc.oc.commons.bukkit.chat;
import net.md_5.bungee.api.chat.BaseComponent;
import org.bukkit.entity.Player;
import tc.oc.commons.core.chat.Components;
import tc.oc.commons.core.stream.Collectors;
import java.util.stream.Stream;
public class LegacyPlayerAudience extends PlayerAudience {
private BaseComponent recentHotbarMessage;
public LegacyPlayerAudience(Player player) {
super(player);
}
@Override
public void sendHotbarMessage(BaseComponent message) {
// Do not spam hot bar messages, as the protocol converts
// them to regular chat messages.
if(!Components.equals(message, recentHotbarMessage)) {
emphasize(message);
recentHotbarMessage = message;
}
}
@Override
public void showTitle(BaseComponent title, BaseComponent subtitle, int inTicks, int stayTicks, int outTicks) {
emphasize(Components.join(Components.space(), Stream.of(title, subtitle).filter(msg -> msg != null).collect(Collectors.toImmutableList())));
}
protected void emphasize(BaseComponent message) {
sendMessage(Components.blank());
sendMessage(message);
sendMessage(Components.blank());
}
}

View File

@ -1,40 +0,0 @@
package tc.oc.commons.bukkit.chat;
import net.md_5.bungee.api.ChatMessageType;
import net.md_5.bungee.api.chat.BaseComponent;
import org.bukkit.entity.Player;
import tc.oc.commons.core.chat.Component;
import tc.oc.commons.core.chat.Sound;
public class PlayerAudience extends CommandSenderAudience {
public PlayerAudience(Player player) {
super(player);
}
protected Player getPlayer() {
return (Player) getCommandSender();
}
@Override
public void sendHotbarMessage(BaseComponent message) {
getPlayer().sendMessage(ChatMessageType.ACTION_BAR, ComponentRenderers.render(message, getPlayer()));
}
@Override
public void showTitle(BaseComponent title, BaseComponent subtitle, int inTicks, int stayTicks, int outTicks) {
title = title == null ? new Component("") : ComponentRenderers.render(title, getPlayer());
subtitle = subtitle == null ? new Component("") : ComponentRenderers.render(subtitle, getPlayer());
getPlayer().showTitle(title, subtitle, inTicks, stayTicks, outTicks);
}
@Override
public void hideTitle() {
getPlayer().hideTitle();
}
@Override
public void playSound(Sound sound) {
getPlayer().playSound(getPlayer().getLocation(), sound.name(), sound.volume(), sound.pitch());
}
}

View File

@ -10,7 +10,7 @@ import com.google.inject.assistedinject.FactoryModuleBuilder;
import org.bukkit.plugin.Plugin;
import tc.oc.commons.bukkit.bossbar.BossBarFactory;
import tc.oc.commons.bukkit.bossbar.BossBarFactoryImpl;
import tc.oc.commons.bukkit.chat.BukkitAudiences;
import tc.oc.commons.bukkit.chat.Audiences;
import tc.oc.commons.bukkit.item.RenderedItemBuilder;
import tc.oc.commons.bukkit.logging.BukkitLoggerFactory;
import tc.oc.commons.bukkit.permissions.BukkitPermissionRegistry;
@ -31,8 +31,8 @@ public class BukkitServerManifest extends SingletonManifest {
bind(new TypeLiteral<PluginResolver<Plugin>>(){}).to(BukkitPluginResolver.class);
bind(Loggers.class).to(BukkitLoggerFactory.class);
bind(tc.oc.commons.core.chat.Audiences.class).to(tc.oc.commons.bukkit.chat.Audiences.class);
bind(tc.oc.commons.bukkit.chat.Audiences.class).to(BukkitAudiences.class);
bind(tc.oc.commons.core.chat.Audiences.class).to(Audiences.class);
requestStaticInjection(Audiences.Deprecated.class);
bind(PermissionRegistry.class).to(BukkitPermissionRegistry.class);
bind(BossBarFactory.class).to(BossBarFactoryImpl.class);
}

View File

@ -24,7 +24,7 @@ import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
import org.bukkit.util.RayBlockIntersection;
import org.bukkit.util.Vector;
import tc.oc.commons.bukkit.chat.BukkitAudiences;
import tc.oc.commons.bukkit.chat.Audiences;
import tc.oc.commons.bukkit.event.BlockPunchEvent;
import tc.oc.commons.bukkit.event.BlockTrampleEvent;
import tc.oc.commons.bukkit.event.CoarsePlayerMoveEvent;
@ -42,6 +42,7 @@ import tc.oc.commons.core.plugin.PluginFacet;
public class PlayerMovementListener implements PluginFacet, Listener {
protected final EventBus eventBus;
protected final Audiences audiences;
// The last location of a player that has been used to generate
// coarse movement events. If a player is not in this list, then
@ -49,8 +50,9 @@ public class PlayerMovementListener implements PluginFacet, Listener {
// on its own.
private final Map<Player, EntityLocation> lastToLocation = new WeakHashMap<>();
@Inject PlayerMovementListener(EventBus eventBus) {
@Inject PlayerMovementListener(EventBus eventBus, Audiences audiences) {
this.eventBus = eventBus;
this.audiences = audiences;
}
/**
@ -257,7 +259,7 @@ public class PlayerMovementListener implements PluginFacet, Listener {
@EventHandler(priority = EventPriority.MONITOR)
public void processCancelMessage(final CoarsePlayerMoveEvent event) {
if(event.isCancelled() && event.getCancelMessage() != null) {
BukkitAudiences.getAudience(event.getPlayer()).sendWarning(event.getCancelMessage(), false);
audiences.get(event.getPlayer()).sendWarning(event.getCancelMessage(), false);
}
}
}

View File

@ -14,12 +14,12 @@ public class BukkitPermissionRegistry implements PermissionRegistry {
}
@Override
public void addPermission(Permission permission) {
public void register(Permission permission) {
pluginManager.addPermission(permission);
}
@Override
public void removePermission(Permission permission) {
public void unregister(Permission permission) {
pluginManager.removePermission(permission);
}
}

View File

@ -1,11 +1,36 @@
package tc.oc.commons.bukkit.permissions;
import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionDefault;
/**
* Abstraction layer for Bukkit permission API
*/
public interface PermissionRegistry {
void addPermission(Permission perm);
void removePermission(Permission perm);
void register(Permission perm);
void unregister(Permission perm);
default Permission create(String name, PermissionDefault def) {
Permission permission = new Permission(name, def);
register(permission);
return permission;
}
default Permission create(String name) {
return create(name, Permission.DEFAULT_PERMISSION);
}
default Permission createWithDefaultOp(String name) {
return create(name, PermissionDefault.OP);
}
default Permission createWithDefaultFalse(String name) {
return create(name, PermissionDefault.FALSE);
}
default Permission createWithDefaultTrue(String name) {
return create(name, PermissionDefault.TRUE);
}
}

View File

@ -1,6 +1,90 @@
package tc.oc.commons.bungee.chat;
import net.md_5.bungee.api.ChatMessageType;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import tc.oc.commons.core.chat.AbstractAudiences;
import tc.oc.commons.core.chat.Audience;
import tc.oc.commons.core.chat.ConsoleAudience;
import tc.oc.commons.core.chat.Sound;
import javax.annotation.Nullable;
import javax.inject.Singleton;
@Singleton
public class Audiences extends AbstractAudiences<CommandSender> {
@Override
public Audience get(CommandSender sender) {
if(sender instanceof ProxiedPlayer) {
return new Player((ProxiedPlayer) sender);
} else {
return new Console();
}
}
abstract class Base<T extends CommandSender> implements Audience {
private final T sender;
protected Base(T sender) {
this.sender = sender;
}
protected T sender() {
return sender;
}
@Override
public void sendMessage(BaseComponent message) {
sender().sendMessage(message);
}
@Override
public void sendWarning(BaseComponent message, boolean audible) {
sendMessage(message);
}
}
class Player extends Base<ProxiedPlayer> {
public Player(ProxiedPlayer proxiedPlayer) {
super(proxiedPlayer);
}
@Override
public void playSound(Sound sound) {}
@Override
public void stopSound(Sound sound) {}
@Override
public void sendHotbarMessage(BaseComponent message) {
sender().sendMessage(ChatMessageType.ACTION_BAR, message);
}
@Override
public void showTitle(@Nullable BaseComponent title, @Nullable BaseComponent subtitle, int inTicks, int stayTicks, int outTicks) {
sender().sendTitle(ProxyServer.getInstance().createTitle().title(title).subTitle(subtitle).fadeIn(inTicks).stay(stayTicks).fadeOut(outTicks));
}
@Override
public void hideTitle() {
sender().sendTitle(ProxyServer.getInstance().createTitle().clear());
}
}
class Console implements ConsoleAudience {
@Override
public void sendMessage(BaseComponent message) {
ProxyServer.getInstance().getConsoleSender().sendMessage(message);
}
}
public interface Audiences extends tc.oc.commons.core.chat.Audiences<CommandSender> {
}

View File

@ -1,20 +0,0 @@
package tc.oc.commons.bungee.chat;
import javax.inject.Singleton;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import tc.oc.commons.core.chat.Audience;
import tc.oc.commons.core.chat.MinecraftAudiences;
@Singleton
public class BungeeAudiences extends MinecraftAudiences<CommandSender> implements Audiences {
@Override
public Audience get(CommandSender sender) {
if(sender instanceof ProxiedPlayer) {
return new PlayerAudience((ProxiedPlayer) sender);
} else {
return new ConsoleAudience();
}
}
}

View File

@ -1,18 +0,0 @@
package tc.oc.commons.bungee.chat;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.chat.BaseComponent;
import tc.oc.commons.core.chat.AbstractConsoleAudience;
public class ConsoleAudience extends AbstractConsoleAudience {
@Override
public void sendMessage(BaseComponent message) {
ProxyServer.getInstance().getConsole().sendMessage(message);
}
@Override
public void sendMessage(String message) {
ProxyServer.getInstance().getConsole().sendMessage(message);
}
}

View File

@ -1,49 +0,0 @@
package tc.oc.commons.bungee.chat;
import javax.annotation.Nullable;
import net.md_5.bungee.api.ChatMessageType;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import tc.oc.commons.core.chat.AbstractAudience;
import tc.oc.commons.core.chat.Sound;
public class PlayerAudience extends AbstractAudience {
private final ProxiedPlayer player;
public PlayerAudience(ProxiedPlayer player) {
this.player = player;
}
@Override
public void sendMessage(BaseComponent message) {
player.sendMessage(message);
}
@Override
public void playSound(Sound sound) {
// Possible, but not worth the trouble
}
@Override
public void sendHotbarMessage(BaseComponent message) {
player.sendMessage(ChatMessageType.ACTION_BAR, message);
}
@Override
public void showTitle(@Nullable BaseComponent title, @Nullable BaseComponent subtitle, int inTicks, int stayTicks, int outTicks) {
player.sendTitle(ProxyServer.getInstance().createTitle().title(title).subTitle(subtitle).fadeIn(inTicks).stay(stayTicks).fadeOut(outTicks));
}
@Override
public void hideTitle() {
player.sendTitle(ProxyServer.getInstance().createTitle().clear());
}
@Override
public void sendMessage(String message) {
player.sendMessage(message);
}
}

View File

@ -2,7 +2,7 @@ package tc.oc.commons.bungee.inject;
import com.google.inject.TypeLiteral;
import net.md_5.bungee.api.plugin.Plugin;
import tc.oc.commons.bungee.chat.BungeeAudiences;
import tc.oc.commons.bungee.chat.Audiences;
import tc.oc.commons.bungee.logging.BungeeLoggerFactory;
import tc.oc.commons.bungee.plugin.BungeePluginResolver;
import tc.oc.commons.core.inject.SingletonManifest;
@ -17,7 +17,6 @@ public class BungeeServerManifest extends SingletonManifest {
bind(new TypeLiteral<PluginResolver<Plugin>>(){}).to(BungeePluginResolver.class);
bind(Loggers.class).to(BungeeLoggerFactory.class);
bind(tc.oc.commons.core.chat.Audiences.class).to(tc.oc.commons.bungee.chat.Audiences.class);
bind(tc.oc.commons.bungee.chat.Audiences.class).to(BungeeAudiences.class);
bind(tc.oc.commons.core.chat.Audiences.class).to(Audiences.class);
}
}

View File

@ -1,17 +0,0 @@
package tc.oc.commons.core.chat;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.BaseComponent;
public abstract class AbstractAudience implements Audience {
@Override
public void sendWarning(BaseComponent message, boolean audible) {
sendMessage(new Component(ChatColor.RED).extra(new Component(" \u26a0 ", ChatColor.YELLOW), message));
}
@Override
public void sendWarning(String message, boolean audible) {
sendMessage(ChatColor.YELLOW + " \u26a0 " + ChatColor.RED + message);
}
}

View File

@ -0,0 +1,35 @@
package tc.oc.commons.core.chat;
import com.google.common.collect.ImmutableList;
import tc.oc.minecraft.api.command.CommandSender;
import tc.oc.minecraft.api.server.LocalServer;
import javax.inject.Inject;
import java.util.function.Predicate;
import java.util.stream.Stream;
public abstract class AbstractAudiences<C extends CommandSender> implements Audiences<C> {
@Inject
LocalServer localServer;
@Override
public MultiAudience all() {
return () -> senders().map(sender -> get((C) sender));
}
@Override
public MultiAudience filter(Predicate<C> condition) {
return () -> senders().filter(sender -> condition.test((C) sender)).map(sender -> get((C) sender));
}
@Override
public Audience console() {
return get((C) localServer.getConsoleSender());
}
private Stream<CommandSender> senders() {
return ImmutableList.<CommandSender>builder().add(localServer.getConsoleSender()).addAll(localServer.getOnlinePlayers()).build().stream();
}
}

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