diff --git a/src/com/comphenix/packetwrapper/WrapperPlayServerChat.java b/src/com/comphenix/packetwrapper/WrapperPlayServerChat.java new file mode 100644 index 00000000..aa425390 --- /dev/null +++ b/src/com/comphenix/packetwrapper/WrapperPlayServerChat.java @@ -0,0 +1,104 @@ +/** + * PacketWrapper - ProtocolLib wrappers for Minecraft packets + * Copyright (C) dmulloy2 + * Copyright (C) Kristian S. Strangeland + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.comphenix.packetwrapper; + +import java.util.Arrays; + +import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.events.PacketContainer; +import com.comphenix.protocol.wrappers.EnumWrappers; +import com.comphenix.protocol.wrappers.EnumWrappers.ChatType; +import com.comphenix.protocol.wrappers.WrappedChatComponent; + +public class WrapperPlayServerChat extends AbstractPacket { + public static final PacketType TYPE = PacketType.Play.Server.CHAT; + + public WrapperPlayServerChat() { + super(new PacketContainer(TYPE), TYPE); + handle.getModifier().writeDefaults(); + } + + public WrapperPlayServerChat(PacketContainer packet) { + super(packet, TYPE); + } + + /** + * Retrieve the chat message. + *

+ * Limited to 32767 bytes + * + * @return The current message + */ + public WrappedChatComponent getMessage() { + return handle.getChatComponents().read(0); + } + + /** + * Set the message. + * + * @param value - new value. + */ + public void setMessage(WrappedChatComponent value) { + handle.getChatComponents().write(0, value); + } + + public ChatType getChatType() { + return handle.getChatTypes().read(0); + } + + public void setChatType(ChatType type) { + handle.getChatTypes().write(0, type); + } + + /** + * Retrieve Position. + *

+ * Notes: 0 - Chat (chat box) ,1 - System Message (chat box), 2 - Above + * action bar + * + * @return The current Position + * @deprecated Magic values replaced by enum + */ + @Deprecated + public byte getPosition() { + Byte position = handle.getBytes().readSafely(0); + if (position != null) { + return position; + } else { + return getChatType().getId(); + } + } + + /** + * Set Position. + * + * @param value - new value. + * @deprecated Magic values replaced by enum + */ + @Deprecated + public void setPosition(byte value) { + handle.getBytes().writeSafely(0, value); + + if (EnumWrappers.getChatTypeClass() != null) + { + Arrays.stream(ChatType.values()).filter(t -> t.getId() == value).findAny() + .ifPresent(t -> handle.getChatTypes().writeSafely(0, t)); + } + } +} \ No newline at end of file diff --git a/src/me/jumper251/replay/filesystem/ConfigManager.java b/src/me/jumper251/replay/filesystem/ConfigManager.java index 62c7ccbe..1e59fd34 100644 --- a/src/me/jumper251/replay/filesystem/ConfigManager.java +++ b/src/me/jumper251/replay/filesystem/ConfigManager.java @@ -27,14 +27,12 @@ public class ConfigManager { public static boolean RECORD_BLOCKS, REAL_CHANGES; public static boolean RECORD_ITEMS, RECORD_ENTITIES; - public static boolean RECORD_CHAT; + public static boolean RECORD_CHAT, RECORD_PLUGIN_MESSAGES; public static boolean SAVE_STOP, USE_OFFLINE_SKINS, HIDE_PLAYERS, UPDATE_NOTIFY, USE_DATABASE, ADD_PLAYERS; public static boolean WORLD_RESET; public static ReplayQuality QUALITY = ReplayQuality.HIGH; - public static String DEATH_MESSAGE, LEAVE_MESSAGE, CHAT_FORMAT, JOIN_MESSAGE; - public static void loadConfigs() { if(!sqlFile.exists()){ sqlCfg.set("host", "localhost"); @@ -60,10 +58,6 @@ public static void loadConfigs() { cfg.set("general.hide_players", false); cfg.set("general.add_new_players", false); cfg.set("general.update_notifications", true); - - cfg.set("general.death_message", "&6{name} &7died."); - cfg.set("general.quit_message", "&6{name} &7left the game."); - cfg.set("general.join_message", "&6{name} &7joined the game."); cfg.set("replaying.world.reset_changes", false); @@ -72,7 +66,7 @@ public static void loadConfigs() { cfg.set("recording.entities.enabled", false); cfg.set("recording.entities.items.enabled", true); cfg.set("recording.chat.enabled", false); - cfg.set("recording.chat.format", "&r<{name}> {message}"); + cfg.set("recording.chat.plugin_messages", false); try { @@ -98,19 +92,13 @@ public static void loadData(boolean initial) { ADD_PLAYERS = cfg.getBoolean("general.add_new_players"); UPDATE_NOTIFY = cfg.getBoolean("general.update_notifications"); if (initial ) USE_DATABASE = cfg.getBoolean("general.use_mysql"); - - DEATH_MESSAGE = cfg.getString("general.death_message"); - LEAVE_MESSAGE = cfg.getString("general.quit_message"); - JOIN_MESSAGE = cfg.getString("general.join_message"); - WORLD_RESET = cfg.getBoolean("replaying.world.reset_changes", false); - - CHAT_FORMAT = cfg.getString("recording.chat.format"); RECORD_BLOCKS = cfg.getBoolean("recording.blocks.enabled"); REAL_CHANGES = cfg.getBoolean("recording.blocks.real_changes"); RECORD_ITEMS = cfg.getBoolean("recording.entities.items.enabled"); RECORD_ENTITIES = cfg.getBoolean("recording.entities.enabled"); RECORD_CHAT = cfg.getBoolean("recording.chat.enabled"); + RECORD_PLUGIN_MESSAGES = cfg.getBoolean("recording.chat.plugin_messages"); if (USE_DATABASE) { diff --git a/src/me/jumper251/replay/replaysystem/data/ReplayData.java b/src/me/jumper251/replay/replaysystem/data/ReplayData.java index 066f99af..2a1e81ac 100644 --- a/src/me/jumper251/replay/replaysystem/data/ReplayData.java +++ b/src/me/jumper251/replay/replaysystem/data/ReplayData.java @@ -5,6 +5,7 @@ import java.util.List; import me.jumper251.replay.filesystem.ConfigManager; +import me.jumper251.replay.replaysystem.data.types.ChatData; import me.jumper251.replay.replaysystem.recording.PlayerWatcher; import me.jumper251.replay.replaysystem.recording.optimization.ReplayQuality; @@ -18,6 +19,8 @@ public class ReplayData implements Serializable{ private HashMap> actions; + + private HashMap> messages; private HashMap watchers; @@ -29,6 +32,7 @@ public class ReplayData implements Serializable{ public ReplayData() { this.actions = new HashMap>(); + this.messages = new HashMap>(); this.watchers = new HashMap(); this.quality = ConfigManager.QUALITY; @@ -57,7 +61,11 @@ public ReplayQuality getQuality() { public HashMap> getActions() { return actions; } - + + public HashMap> getMessages() { + return messages; + } + public HashMap getWatchers() { return watchers; } diff --git a/src/me/jumper251/replay/replaysystem/data/types/ChatData.java b/src/me/jumper251/replay/replaysystem/data/types/ChatData.java index f1137093..f3578093 100644 --- a/src/me/jumper251/replay/replaysystem/data/types/ChatData.java +++ b/src/me/jumper251/replay/replaysystem/data/types/ChatData.java @@ -1,5 +1,8 @@ package me.jumper251.replay.replaysystem.data.types; +import java.util.HashSet; +import java.util.Set; + public class ChatData extends PacketData { @@ -8,12 +11,34 @@ public class ChatData extends PacketData { */ private static final long serialVersionUID = 6849586468365004854L; + private Set recipients; private String message; + /** + * Constructs a ChatData with provided message and an empty recipient. + * + * @param message the message + */ public ChatData(String message) { + this.recipients = new HashSet<>(); + this.message = message; + } + + /** + * Constructs a ChatData with provided message and recipient. + * + * @param recipients the recipient + * @param message the message + */ + public ChatData(Set recipients, String message) { + this.recipients = recipients; this.message = message; } + public Set getRecipients() { + return recipients; + } + public String getMessage() { return message; } diff --git a/src/me/jumper251/replay/replaysystem/recording/PacketRecorder.java b/src/me/jumper251/replay/replaysystem/recording/PacketRecorder.java index dc21e083..dc43afae 100644 --- a/src/me/jumper251/replay/replaysystem/recording/PacketRecorder.java +++ b/src/me/jumper251/replay/replaysystem/recording/PacketRecorder.java @@ -19,6 +19,8 @@ import org.bukkit.entity.EntityType; import org.bukkit.entity.Item; import org.bukkit.entity.Player; +import net.md_5.bungee.chat.ComponentSerializer; +import net.md_5.bungee.api.chat.TextComponent; import com.comphenix.packetwrapper.WrapperPlayClientBlockDig; import com.comphenix.packetwrapper.WrapperPlayClientEntityAction; @@ -32,6 +34,7 @@ import com.comphenix.packetwrapper.WrapperPlayServerRelEntityMoveLook; import com.comphenix.packetwrapper.WrapperPlayServerSpawnEntity; import com.comphenix.packetwrapper.WrapperPlayServerSpawnEntityLiving; +import com.comphenix.packetwrapper.WrapperPlayServerChat; import com.comphenix.protocol.PacketType; import com.comphenix.protocol.ProtocolLibrary; import com.comphenix.protocol.events.ListenerPriority; @@ -102,7 +105,7 @@ public void register() { this.packetAdapter = new PacketAdapter(ReplaySystem.getInstance(), ListenerPriority.HIGHEST, PacketType.Play.Client.POSITION, PacketType.Play.Client.POSITION_LOOK, PacketType.Play.Client.LOOK, PacketType.Play.Client.ENTITY_ACTION, PacketType.Play.Client.ARM_ANIMATION, PacketType.Play.Client.BLOCK_DIG, PacketType.Play.Server.SPAWN_ENTITY, PacketType.Play.Server.ENTITY_DESTROY, PacketType.Play.Server.ENTITY_VELOCITY, PacketType.Play.Server.SPAWN_ENTITY_LIVING, - PacketType.Play.Server.REL_ENTITY_MOVE, PacketType.Play.Server.REL_ENTITY_MOVE_LOOK, PacketType.Play.Server.ENTITY_LOOK, PacketType.Play.Server.POSITION, PacketType.Play.Server.ENTITY_TELEPORT) { + PacketType.Play.Server.REL_ENTITY_MOVE, PacketType.Play.Server.REL_ENTITY_MOVE_LOOK, PacketType.Play.Server.ENTITY_LOOK, PacketType.Play.Server.POSITION, PacketType.Play.Server.ENTITY_TELEPORT, PacketType.Play.Server.CHAT) { @Override public void onPacketReceiving(PacketEvent event) { @@ -306,6 +309,16 @@ public void onPacketSending(PacketEvent event) { addData(p.getName(), new EntityMovingData(packet.getEntityID(), loc.getX(), loc.getY(), loc.getZ(), packet.getPitch(), packet.getYaw())); } } + if(event.getPacketType() == PacketType.Play.Server.CHAT) { + if(ConfigManager.RECORD_CHAT && ConfigManager.RECORD_PLUGIN_MESSAGES) { + + WrapperPlayServerChat packet = new WrapperPlayServerChat(event.getPacket()); + + String message = new TextComponent(ComponentSerializer.parse(packet.getMessage().getJson())).toLegacyText(); + recorder.recordChat(p.getName(), message); + + } + } } diff --git a/src/me/jumper251/replay/replaysystem/recording/Recorder.java b/src/me/jumper251/replay/replaysystem/recording/Recorder.java index e81d985f..8c817823 100644 --- a/src/me/jumper251/replay/replaysystem/recording/Recorder.java +++ b/src/me/jumper251/replay/replaysystem/recording/Recorder.java @@ -6,7 +6,9 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; +import com.google.common.collect.Sets; import me.jumper251.replay.replaysystem.data.types.*; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -90,7 +92,6 @@ public void run() { if (packetData instanceof BlockChangeData && !ConfigManager.RECORD_BLOCKS) continue; if (packetData instanceof EntityItemData && !ConfigManager.RECORD_ITEMS) continue; if ((packetData instanceof EntityData || packetData instanceof EntityMovingData || packetData instanceof EntityAnimationData) && !ConfigManager.RECORD_ENTITIES) continue; - if (packetData instanceof ChatData && !ConfigManager.RECORD_CHAT) continue; ActionData actionData = new ActionData(currentTick, ActionType.PACKET, name, packetData); @@ -204,6 +205,18 @@ public void run() { } } + public void recordChat(String player, String message) { + if(!data.getMessages().containsKey(currentTick)) { + data.getMessages().put(currentTick, new ArrayList<>()); + } + Optional chatData = data.getMessages().get(currentTick).stream().filter(c -> c.getMessage().equals(message)).findAny(); + if(chatData.isPresent()) { + chatData.get().getRecipients().add(player); + } else { + data.getMessages().get(currentTick).add(new ChatData(Sets.newHashSet(player), message)); + } + } + public List getPlayers() { return players; } diff --git a/src/me/jumper251/replay/replaysystem/recording/RecordingListener.java b/src/me/jumper251/replay/replaysystem/recording/RecordingListener.java index 2147ddff..9a10ee3e 100644 --- a/src/me/jumper251/replay/replaysystem/recording/RecordingListener.java +++ b/src/me/jumper251/replay/replaysystem/recording/RecordingListener.java @@ -7,10 +7,12 @@ import java.util.List; import java.util.Set; +import java.util.stream.Collectors; import me.jumper251.replay.replaysystem.data.types.*; +import org.bukkit.Bukkit; import org.bukkit.GameMode; import org.bukkit.Material; import org.bukkit.block.Block; @@ -31,7 +33,6 @@ import org.bukkit.inventory.ItemStack; import me.jumper251.replay.filesystem.ConfigManager; -import me.jumper251.replay.filesystem.MessageBuilder; import me.jumper251.replay.listener.AbstractListener; import me.jumper251.replay.replaysystem.data.ActionData; import me.jumper251.replay.replaysystem.data.ActionType; @@ -188,12 +189,15 @@ public void onCrit(EntityDamageByEntityEvent e) { } } - @EventHandler + @EventHandler (ignoreCancelled = true, priority = EventPriority.MONITOR) public void onChat(AsyncPlayerChatEvent e) { Player p = e.getPlayer(); - if (recorder.getPlayers().contains(p.getName())) { - - this.packetRecorder.addData(p.getName(), new ChatData(e.getMessage())); + if(ConfigManager.RECORD_CHAT && !ConfigManager.RECORD_PLUGIN_MESSAGES) { + if (recorder.getPlayers().contains(p.getName())) { + for(Player player : Bukkit.getOnlinePlayers()) { + this.recorder.recordChat(player.getName(), String.format(e.getFormat(), e.getPlayer().getDisplayName(), e.getMessage())); + } + } } } @@ -227,18 +231,26 @@ public void onQuit(PlayerQuitEvent e) { this.recorder.getPlayers().remove(p.getName()); if (!this.replayLeft.contains(p.getName())) this.replayLeft.add(p.getName()); + if(ConfigManager.RECORD_CHAT && (!ConfigManager.RECORD_PLUGIN_MESSAGES || Bukkit.getOnlinePlayers().size() == 1)) { + for(Player player : Bukkit.getOnlinePlayers()) { + this.recorder.recordChat(player.getName(), e.getQuitMessage()); + } + } } } - @EventHandler + @EventHandler (priority = EventPriority.MONITOR) public void onJoin(PlayerJoinEvent e) { Player p = e.getPlayer(); if (!this.recorder.getPlayers().contains(p.getName()) && (this.replayLeft.contains(p.getName())) || ConfigManager.ADD_PLAYERS) { this.recorder.getPlayers().add(p.getName()); this.recorder.getData().getWatchers().put(p.getName(), new PlayerWatcher(p.getName())); this.recorder.createSpawnAction(p, p.getLocation(), false); - this.recorder.addData(this.recorder.getCurrentTick(), new ActionData(this.recorder.getCurrentTick(), ActionType.MESSAGE, p.getName(), new ChatData(new MessageBuilder(ConfigManager.JOIN_MESSAGE).set("name", p.getName()).build()))); - + if(ConfigManager.RECORD_CHAT && !ConfigManager.RECORD_PLUGIN_MESSAGES) { + for(Player player : Bukkit.getOnlinePlayers()) { + this.recorder.recordChat(player.getName(), e.getJoinMessage()); + } + } } } @@ -247,6 +259,11 @@ public void onDeath(PlayerDeathEvent e) { Player p = e.getEntity(); if (this.recorder.getPlayers().contains(p.getName())) { this.recorder.addData(this.recorder.getCurrentTick(), new ActionData(0, ActionType.DEATH, p.getName(), null)); + if(ConfigManager.RECORD_CHAT && !ConfigManager.RECORD_PLUGIN_MESSAGES) { + for(Player player : Bukkit.getOnlinePlayers()) { + this.recorder.recordChat(player.getName(), e.getDeathMessage()); + } + } } } diff --git a/src/me/jumper251/replay/replaysystem/replaying/Replayer.java b/src/me/jumper251/replay/replaysystem/replaying/Replayer.java index da4105f4..6d8d4703 100644 --- a/src/me/jumper251/replay/replaysystem/replaying/Replayer.java +++ b/src/me/jumper251/replay/replaysystem/replaying/Replayer.java @@ -13,6 +13,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Collections; import org.bukkit.Location; import org.bukkit.entity.Entity; @@ -20,6 +21,9 @@ import org.bukkit.scheduler.BukkitRunnable; +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.HoverEvent; +import net.md_5.bungee.api.chat.TextComponent; import me.jumper251.replay.ReplaySystem; import me.jumper251.replay.api.IReplayHook; @@ -32,6 +36,7 @@ import me.jumper251.replay.replaysystem.data.types.ItemData; import me.jumper251.replay.replaysystem.data.types.LocationData; import me.jumper251.replay.replaysystem.data.types.SpawnData; +import me.jumper251.replay.replaysystem.data.types.ChatData; import me.jumper251.replay.replaysystem.utils.entities.IEntity; import me.jumper251.replay.replaysystem.utils.entities.INPC; @@ -141,9 +146,17 @@ public void executeTick(int tick, boolean reversed) { List list = data.getActions().get(tick); for (ActionData action : list) { - + // Support older replays + if (action.getType() == ActionType.MESSAGE && action.getPacketData() instanceof ChatData) { + if (!reversed) { + ChatData chatData = (ChatData) action.getPacketData(); + sendMessage(chatData); + } + continue; + } + utils.handleAction(action, data, reversed); - + if (action.getType() == ActionType.CUSTOM) { if (ReplayAPI.getInstance().getHookManager().isRegistered()) { for (IReplayHook hook : ReplayAPI.getInstance().getHookManager().getHooks()) { @@ -156,6 +169,15 @@ public void executeTick(int tick, boolean reversed) { if (tick == 0) data.getActions().remove(tick); } + + // Don't display messages about the start of recording + if(tick != 0 && data.getMessages() != null) { + for (ChatData chatData : data.getMessages().getOrDefault(tick, Collections.emptyList())) { + if (!reversed) { + sendMessage(chatData); + } + } + } } private void updateXPBar() { @@ -260,4 +282,17 @@ public void sendMessage(String message) { this.watcher.sendMessage(ReplaySystem.PREFIX + message); } } + + public void sendMessage(ChatData chatData) { + String hoverText = (chatData.getRecipients() == null + || chatData.getRecipients().stream().allMatch(recipient -> recipient == null || recipient.isEmpty())) + ? "" + : "Received: " + String.join(", ", chatData.getRecipients()); + TextComponent component = new TextComponent(ReplaySystem.PREFIX + "§r"); + for (BaseComponent baseComponent : TextComponent.fromLegacyText(chatData.getMessage())) { + component.addExtra(baseComponent); + } + component.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new BaseComponent[] { new TextComponent(hoverText) })); + this.watcher.spigot().sendMessage(component); + } } diff --git a/src/me/jumper251/replay/replaysystem/replaying/ReplayingUtils.java b/src/me/jumper251/replay/replaysystem/replaying/ReplayingUtils.java index 012a0b21..f80d5452 100644 --- a/src/me/jumper251/replay/replaysystem/replaying/ReplayingUtils.java +++ b/src/me/jumper251/replay/replaysystem/replaying/ReplayingUtils.java @@ -86,11 +86,6 @@ public void handleAction(ActionData action, ReplayData data, boolean reversed) { replayer.getNPCList().remove(action.getName()); } - } - - if (action.getType() == ActionType.MESSAGE && !reversed) { - ChatData message = (ChatData) action.getPacketData(); - replayer.sendMessage(message.getMessage()); } if (action.getType() == ActionType.PACKET && this.replayer.getNPCList().containsKey(action.getName())) { @@ -137,11 +132,7 @@ public void handleAction(ActionData action, ReplayData data, boolean reversed) { if (action.getPacketData() instanceof ChatData) { ChatData chatData = (ChatData) action.getPacketData(); - - replayer.sendMessage(new MessageBuilder(ConfigManager.CHAT_FORMAT) - .set("name", action.getName()) - .set("message", chatData.getMessage()) - .build()); + replayer.sendMessage(chatData.getMessage()); } if (action.getPacketData() instanceof InvData) { @@ -331,16 +322,6 @@ public void handleAction(ActionData action, ReplayData data, boolean reversed) { SpawnData oldSpawnData = new SpawnData(npc.getUuid(), LocationData.fromLocation(npc.getLocation()), signatures.get(action.getName())); this.lastSpawnActions.addLast(new ActionData(0, ActionType.SPAWN, action.getName(), oldSpawnData)); - if (action.getType() == ActionType.DESPAWN) { - replayer.sendMessage(new MessageBuilder(ConfigManager.LEAVE_MESSAGE) - .set("name", action.getName()) - .build()); - } else { - replayer.sendMessage(new MessageBuilder(ConfigManager.DEATH_MESSAGE) - .set("name", action.getName()) - .build()); - } - } else { if (!this.lastSpawnActions.isEmpty()) {