Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor Channels #1306

Draft
wants to merge 1 commit into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions core/src/main/java/tc/oc/pgm/PGMPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import tc.oc.pgm.api.match.MatchManager;
import tc.oc.pgm.api.module.Module;
import tc.oc.pgm.api.module.exception.ModuleLoadException;
import tc.oc.pgm.channels.ChannelManager;
import tc.oc.pgm.command.util.PGMCommandGraph;
import tc.oc.pgm.db.CacheDatastore;
import tc.oc.pgm.db.SQLDatastore;
Expand Down Expand Up @@ -95,6 +96,7 @@ public class PGMPlugin extends JavaPlugin implements PGM, Listener {
private NameDecorationRegistry nameDecorationRegistry;
private ScheduledExecutorService executorService;
private ScheduledExecutorService asyncExecutorService;
private ChannelManager channelManager;
private InventoryManager inventoryManager;

public PGMPlugin() {
Expand Down Expand Up @@ -237,6 +239,8 @@ public void onEnable() {
asyncExecutorService.scheduleAtFixedRate(new ShouldRestartTask(), 0, 1, TimeUnit.MINUTES);
}

channelManager = new ChannelManager();

registerListeners();
registerCommands();
}
Expand Down Expand Up @@ -338,6 +342,11 @@ public InventoryManager getInventoryManager() {
return inventoryManager;
}

@Override
public ChannelManager getChannelManager() {
return channelManager;
}

private void registerCommands() {
try {
new PGMCommandGraph(this);
Expand Down Expand Up @@ -372,6 +381,7 @@ private void registerListeners() {
registerEvents(new MotdListener());
registerEvents(new ServerPingDataListener(matchManager, mapOrder, getLogger()));
registerEvents(new JoinLeaveAnnouncer(matchManager));
registerEvents(channelManager);
}

private class InGameHandler extends Handler {
Expand Down
3 changes: 3 additions & 0 deletions core/src/main/java/tc/oc/pgm/api/PGM.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import tc.oc.pgm.api.map.MapLibrary;
import tc.oc.pgm.api.map.MapOrder;
import tc.oc.pgm.api.match.MatchManager;
import tc.oc.pgm.channels.ChannelManager;
import tc.oc.pgm.namedecorations.NameDecorationRegistry;
import tc.oc.pgm.tablist.MatchTabManager;

Expand Down Expand Up @@ -40,6 +41,8 @@ public interface PGM extends Plugin {

InventoryManager getInventoryManager();

ChannelManager getChannelManager();

AtomicReference<PGM> GLOBAL = new AtomicReference<>(null);

static PGM set(PGM pgm) {
Expand Down
118 changes: 118 additions & 0 deletions core/src/main/java/tc/oc/pgm/api/channels/Channel.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package tc.oc.pgm.api.channels;

import static net.kyori.adventure.text.Component.text;

import cloud.commandframework.arguments.standard.StringArgument;
import cloud.commandframework.paper.PaperCommandManager;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Predicate;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.Nullable;
import tc.oc.pgm.api.PGM;
import tc.oc.pgm.api.event.ChannelMessageEvent;
import tc.oc.pgm.api.player.MatchPlayer;
import tc.oc.pgm.api.setting.SettingValue;
import tc.oc.pgm.util.Players;
import tc.oc.pgm.util.named.NameStyle;

public interface Channel<T> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could extend Adventure's Audience.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

given the extra overhead that a channel implies, it probably shouldn't.
You don't want someone treating this like it was just another casual audience, when it may not act like one at all (eg: requirement for a target to send within the channel). Effectively speaking the only channel that could properly implement audience would be global chat

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This API needs to be fully documented, explaining each method's purpose and params


default String getDisplayName() {
return getAliases()[0];
}

String[] getAliases();

@Nullable
default Character getShortcut() {
return null;
}

default SettingValue getSetting() {
return null;
}

default boolean supportsRedirect() {
return false;
}

default boolean canSendMessage(MatchPlayer sender) {
return true;
}

T getTarget(MatchPlayer sender, Map<String, ?> arguments);

Collection<MatchPlayer> getViewers(T target);

default Collection<MatchPlayer> getBroadcastViewers(T target) {
return getViewers(target);
}

default void sendMessage(ChannelMessageEvent<T> event) {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
default void sendMessage(ChannelMessageEvent<T> event) {}
void sendMessage(ChannelMessageEvent<T> event);

either force the downstream to implement this, or have an actual default impl, but don't just leave an empty no-op method for something this critical.
I'd personally advocate for having a default impl here, just like broadcastMessage has


default Component formatMessage(T target, @Nullable MatchPlayer sender, Component message) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i believe this method should not default to global-chat behavior, force downstream to implement it too

if (sender == null) return message;
return text()
.append(text("<", NamedTextColor.WHITE))
.append(sender.getName(NameStyle.VERBOSE))
.append(text(">: ", NamedTextColor.WHITE))
.append(message)
.build();
}

default void registerCommand(PaperCommandManager<CommandSender> manager) {
String name = getAliases()[0];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if aliases is an empty array, this throws an out of bounds exception. Throw your own illegal argument exception instead

String[] aliases = new String[getAliases().length - 1];
System.arraycopy(getAliases(), 1, aliases, 0, aliases.length);
Comment on lines +69 to +70
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to go into some util, you could also use Arrays.copyOfRange:

Suggested change
String[] aliases = new String[getAliases().length - 1];
System.arraycopy(getAliases(), 1, aliases, 0, aliases.length);
String[] aliases = Arrays.copyOfRange(getAliases(), 1, getAliases().length);


manager.command(
manager
.commandBuilder(name, aliases)
.handler(
context -> {
MatchPlayer sender =
context.inject(MatchPlayer.class).orElseThrow(IllegalStateException::new);
PGM.get().getChannelManager().setChannel(sender, this);
}));

manager.command(
manager
.commandBuilder(name, aliases)
.argument(
StringArgument.<CommandSender>builder("message")
.greedy()
.withSuggestionsProvider(Players::suggestPlayers)
.build())
.handler(
context -> {
MatchPlayer sender =
context.inject(MatchPlayer.class).orElseThrow(IllegalStateException::new);
PGM.get().getChannelManager().process(this, sender, context.asMap());
}));
}

default Map<String, Object> processChatShortcut(MatchPlayer sender, String message) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
default Map<String, Object> processChatShortcut(MatchPlayer sender, String message) {
default Map<String, Object> processShortcutMessage(MatchPlayer sender, String message) {

you're not processing the shortcut (with a chat), you're processing the message (with a shortcut).

Make sure the docs explain the rationale of why this is even needed cause it's nonobvious

Map<String, Object> arguments = new HashMap<String, Object>();
if (message.length() == 1) {
PGM.get().getChannelManager().setChannel(sender, this);
return arguments;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return arguments;
return Collections.emptyMap();

}

arguments.put("message", message.substring(1).trim());
return arguments;
Comment on lines +105 to +106
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
arguments.put("message", message.substring(1).trim());
return arguments;
return Collections.singletonMap("message", message.substring(1).trim());

}

default void broadcastMessage(Component component, T target) {
broadcastMessage(component, target, player -> true);
}

default void broadcastMessage(Component component, T target, Predicate<MatchPlayer> filter) {
Collection<MatchPlayer> viewers = getBroadcastViewers(target);
Component finalMessage = formatMessage(target, null, component);
viewers.stream().filter(filter).forEach(player -> player.sendMessage(finalMessage));
}
}
72 changes: 72 additions & 0 deletions core/src/main/java/tc/oc/pgm/api/event/ChannelMessageEvent.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package tc.oc.pgm.api.event;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.Nullable;
import tc.oc.pgm.api.channels.Channel;
import tc.oc.pgm.api.player.MatchPlayer;
import tc.oc.pgm.util.event.PreemptiveEvent;

public class ChannelMessageEvent<T> extends PreemptiveEvent {

private final Channel<T> channel;
private @Nullable MatchPlayer sender;
private final T target;
private final List<MatchPlayer> viewers;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you probably should be able to set this, to be able to change who sees the message

private String message;

public ChannelMessageEvent(
Channel<T> channel,
@Nullable MatchPlayer sender,
T target,
Collection<MatchPlayer> viewers,
String message) {
this.channel = channel;
this.sender = sender;
this.target = target;
this.viewers = new ArrayList<MatchPlayer>(viewers);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'd avoid the array list, just keep the collection passed in

this.message = message;
}

public Channel<T> getChannel() {
return channel;
}

@Nullable
public MatchPlayer getSender() {
return sender;
}

public void setSender(@Nullable MatchPlayer sender) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably no reason to override the sender

this.sender = sender;
}

public T getTarget() {
return target;
}

public List<MatchPlayer> getViewers() {
return viewers;
}

public String getMessage() {
return message;
}

public void setMessage(String message) {
this.message = message;
}

private static final HandlerList handlers = new HandlerList();

@Override
public HandlerList getHandlers() {
return handlers;
}

public static HandlerList getHandlerList() {
return handlers;
}
}
20 changes: 20 additions & 0 deletions core/src/main/java/tc/oc/pgm/api/integration/Integration.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@
import static tc.oc.pgm.util.Assert.assertNotNull;

import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.Nullable;
import tc.oc.pgm.api.channels.Channel;
import tc.oc.pgm.api.player.MatchPlayer;

public final class Integration {
Expand All @@ -23,6 +27,7 @@ private Integration() {}
new AtomicReference<VanishIntegration>(new NoopVanishIntegration());
private static final AtomicReference<SquadIntegration> SQUAD =
new AtomicReference<>(new NoopSquadIntegration());
private static Set<Channel<?>> CHANNELS = new HashSet<Channel<?>>();

public static void setFriendIntegration(FriendIntegration integration) {
FRIENDS.set(assertNotNull(integration));
Expand All @@ -44,6 +49,13 @@ public static void setSquadIntegration(SquadIntegration integration) {
SQUAD.set(assertNotNull(integration));
}

public static void registerChannel(Channel<?> channel) {
if (CHANNELS == null)
throw new IllegalStateException(
"New channels cannot be registered after ChannelManager has been initialised!");
CHANNELS.add(assertNotNull(channel));
}

public static boolean isFriend(Player a, Player b) {
return FRIENDS.get().isFriend(a, b);
}
Expand Down Expand Up @@ -79,6 +91,14 @@ public static boolean setVanished(MatchPlayer player, boolean vanish, boolean qu
return VANISH.get().setVanished(player, vanish, quiet);
}

public static Set<Channel<?>> getRegisteredChannels() {
return Collections.unmodifiableSet(CHANNELS);
}

public static void finishRegisteringChannels() {
CHANNELS = null;
}
Comment on lines +98 to +100
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest going for pollRegisteredChannels, signifying that you're getting the list and setting it to null, both within the same method. Without that, you cannot guarantee that the state of what is used and what throws exception remains consistent.
Someone could call getRegisteredChannels, then do some stuff (causing registerChannel to be called), then call finishRegisteringChannels, and it would silently be hidden away that those channels didn't register because they were set after registering started.


// No-op Implementations

private static class NoopFriendIntegration implements FriendIntegration {
Expand Down
14 changes: 7 additions & 7 deletions core/src/main/java/tc/oc/pgm/api/setting/SettingKey.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import java.util.List;
import org.bukkit.Material;
import org.jetbrains.annotations.NotNull;
import tc.oc.pgm.api.PGM;
import tc.oc.pgm.api.player.MatchPlayer;
import tc.oc.pgm.modules.PlayerTimeMatchModule;
import tc.oc.pgm.util.Aliased;
Expand All @@ -19,12 +20,12 @@
* @see SettingValue
*/
public enum SettingKey implements Aliased {
CHAT(
"chat",
Material.SIGN,
CHAT_TEAM,
CHAT_GLOBAL,
CHAT_ADMIN), // Changes the default chat channel
CHAT("chat", Material.SIGN, CHAT_TEAM, CHAT_GLOBAL, CHAT_ADMIN) {
@Override
public void update(MatchPlayer player) {
PGM.get().getChannelManager().setChannel(player, player.getSettings().getValue(CHAT));
}
}, // Changes the default chat channel
DEATH(
Arrays.asList("death", "dms"),
Material.SKULL_ITEM,
Expand Down Expand Up @@ -85,7 +86,6 @@ public void update(MatchPlayer player) {
PlayerTimeMatchModule.updatePlayerTime(player);
}
}; // Changes player preference for time of day
;

private final List<String> aliases;
private final SettingValue[] values;
Expand Down
Loading
Loading