package tc.oc.commons.bukkit.whisper; import java.time.Duration; import java.time.Instant; import java.util.List; import javax.inject.Inject; import com.google.common.collect.Lists; import com.google.common.util.concurrent.ListenableFuture; 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 tc.oc.api.bukkit.users.OnlinePlayers; import tc.oc.api.docs.BasicModel; import tc.oc.api.docs.PlayerId; import tc.oc.api.docs.Server; import tc.oc.api.docs.Whisper; import tc.oc.api.docs.virtual.WhisperDoc; import tc.oc.api.message.MessageListener; import tc.oc.api.message.MessageQueue; import tc.oc.api.message.types.ModelUpdate; import tc.oc.api.model.IdFactory; import tc.oc.api.users.UserService; import tc.oc.api.whispers.WhisperService; import tc.oc.commons.bukkit.event.UserLoginEvent; import tc.oc.commons.bukkit.nick.Identity; import tc.oc.commons.bukkit.settings.SettingManagerProvider; import tc.oc.commons.core.plugin.PluginFacet; import tc.oc.commons.core.scheduler.Scheduler; import tc.oc.minecraft.scheduler.MainThreadExecutor; public class WhisperDispatcher implements WhisperSender, Listener, MessageListener, PluginFacet { // Mark a whisper as read if the player is still online // this long after first seeing it. This prevents a player // from missing a message because they disconnected right // at the same time they received it. private static final Duration MARK_READ_DELAY = Duration.ofSeconds(3); private final MessageQueue queue; private final OnlinePlayers onlinePlayers; private final MainThreadExecutor executor; private final Scheduler scheduler; private final WhisperService whisperService; private final WhisperFormatter formatter; private final Server localServer; private final IdFactory idFactory; private final SettingManagerProvider playerSettings; private final UserService userService; @Inject WhisperDispatcher(MessageQueue queue, OnlinePlayers onlinePlayers, MainThreadExecutor executor, Scheduler scheduler, WhisperService whisperService, WhisperFormatter formatter, Server localServer, IdFactory idFactory, SettingManagerProvider playerSettings, UserService userService) { this.queue = queue; this.onlinePlayers = onlinePlayers; this.executor = executor; this.scheduler = scheduler; this.whisperService = whisperService; this.formatter = formatter; this.localServer = localServer; this.idFactory = idFactory; this.playerSettings = playerSettings; this.userService = userService; } @Override public void enable() { queue.bind(ModelUpdate.class); queue.subscribe(this, executor); } @Override public void disable() { queue.unsubscribe(this); } @Override public void send(CommandSender sender, Identity from, Identity to, String content) { executor.callback(userService.find(to.getPlayerId()), user -> { final WhisperSettings.Options setting = (WhisperSettings.Options) playerSettings.getManager(user).getValue(WhisperSettings.receive()); if(setting.canSend(sender, to)) { final ListenableFuture<Whisper> future = whisperService.update(new Out(from, to, content)); executor.callback(future, whisper -> formatter.send(sender, whisper)); } else { formatter.blocked(sender, to); } }); } private void markRead(Player player, Runnable block) { scheduler.createDelayedTask(MARK_READ_DELAY, () -> { // Once willBeOnline goes false, it stays false forever, // whereas isOnline can become true again if the player // reconnects. if(player.willBeOnline()) { block.run(); } }); } @HandleMessage private void receive(ModelUpdate<Whisper> update) { final Whisper whisper = update.document(); if(whisper.delivered()) return; onlinePlayers.byUserId(whisper.recipient_uid()).ifPresent(player -> { formatter.receive(player, whisper); markRead(player, () -> whisperService.update(new Ack(whisper._id())) ); }); } /** * Make sure this runs after the welcome message in the lobby */ @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) public void login(UserLoginEvent event) { final List<Whisper> whispers = event.response().whispers(); if(!whispers.isEmpty()) { final Player player = event.getPlayer(); whispers.forEach(whisper -> formatter.receive(player, whisper)); markRead(player, () -> whisperService.updateMulti(Lists.transform(whispers, whisper -> new Ack(whisper._id()))) ); } } private class Out extends BasicModel implements Whisper { final Identity from, to; final String content; private Out(Identity from, Identity to, String content) { super(idFactory.newId()); this.from = from; this.to = to; this.content = content; } @Override public String family() { return localServer.family(); } @Override public String server_id() { return localServer._id(); } @Override public Instant sent() { return Instant.now(); } @Override public boolean delivered() { return false; } @Override public PlayerId sender_uid() { return from.getPlayerId(); } @Override public String sender_nickname() { return from.getNickname(); } @Override public PlayerId recipient_uid() { return to.getPlayerId(); } @Override public String recipient_specified() { return to.getNickname(); } @Override public String content() { return content; } } private class Ack extends BasicModel implements WhisperDoc.Delivery { public Ack(String _id) { super(_id); } @Override public boolean delivered() { return true; } } }