Skip to content
Open
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: 9 additions & 1 deletion src/main/java/me/justindevb/replay/Replay.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import me.justindevb.replay.config.ReplayConfigManager;
import me.justindevb.replay.config.ReplayConfigReloadResult;
import me.justindevb.replay.config.ReplayConfigSetting;
import me.justindevb.replay.config.ReplayMessagesConfig;
import me.justindevb.replay.debug.ReplayDebugCommand;
import me.justindevb.replay.export.ReplayExportCommand;
import me.justindevb.replay.metrics.BStatsCharts;
Expand Down Expand Up @@ -57,6 +58,7 @@ public class Replay extends JavaPlugin {
private ReplayRetentionService replayRetentionService;
private ReplayViewerStateManager replayViewerStateManager;
private ReplayTransferManager transferManager;
private ReplayMessagesConfig messages;

@Override
public void onLoad() {
Expand All @@ -77,6 +79,7 @@ public void onEnable() {
recorderManager = new RecorderManager(this);
manager = new ReplayManagerImpl(this, recorderManager);
initConfig();
messages = new ReplayMessagesConfig(this);
replayViewerStateManager = new ReplayViewerStateManager(this);
getServer().getPluginManager().registerEvents(replayViewerStateManager, this);
replayBenchmarkService = createReplayBenchmarkService();
Expand Down Expand Up @@ -142,6 +145,10 @@ public ReplayStorage getReplayStorage() {
return storage;
}

public ReplayMessagesConfig getMessages() {
return messages;
}

private void initConfig() {
new ReplayConfigManager(this).initialize();
}
Expand Down Expand Up @@ -199,6 +206,7 @@ public ReplayConfigReloadResult reloadRuntimeConfig() {
EnumMap<ReplayConfigSetting, Object> previousValues = snapshotConfigValues(previousConfig);

new ReplayConfigManager(this).initialize();
if (messages != null) messages.reload();

EnumMap<ReplayConfigSetting, Object> currentValues = snapshotConfigValues(getConfig());
List<ReplayConfigSetting> changedSettings = new ArrayList<>();
Expand Down Expand Up @@ -283,4 +291,4 @@ private void initVelocityLogic() {
getServer().getMessenger().registerOutgoingPluginChannel(this, ReplayTransferManager.CHANNEL);
getServer().getMessenger().registerIncomingPluginChannel(this, ReplayTransferManager.CHANNEL, new ReplayLaunchMessageListener(this));
}
}
}
22 changes: 18 additions & 4 deletions src/main/java/me/justindevb/replay/ReplaySession.java
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,11 @@ public ReplaySession(ReplayPlaybackData replayData, Player viewer, Replay replay

public void start() {
if (timeline == null || timeline.isEmpty()) {
viewer.sendMessage("Replay is empty!");
if (replay.getMessages() != null) {
viewer.sendMessage(replay.getMessages().component("replay.empty", "<red>Replay is empty!"));
} else {
viewer.sendMessage("Replay is empty!");
}
return;
}

Expand Down Expand Up @@ -299,7 +303,11 @@ public void stop() {
}

if (!suppressStopMessage && viewer.isOnline()) {
viewer.sendMessage("Replay finished");
if (replay.getMessages() != null) {
viewer.sendMessage(replay.getMessages().component("replay.finished", "<green>Replay finished"));
} else {
viewer.sendMessage("Replay finished");
}
}
} finally {
ReplayRegistry.remove(this);
Expand Down Expand Up @@ -633,11 +641,17 @@ private void sendActionBar() {

Component bar;
if (paused) {
bar = Component.text("\u23F8 Replay paused: ", NamedTextColor.YELLOW)
bar = replay.getMessages() != null
? replay.getMessages().component("action-bar.paused", "<yellow>\u23F8 Replay paused: <gray>%current% / %total%",
"current", current, "total", total)
: Component.text("\u23F8 Replay paused: ", NamedTextColor.YELLOW)
.append(Component.text(current + " / " + total, NamedTextColor.GRAY));
} else {
String speedText = String.format("%.1fx", playbackSpeed);
bar = Component.text("\u25B6 Replay: ", NamedTextColor.GREEN)
bar = replay.getMessages() != null
? replay.getMessages().component("action-bar.playing", "<green>\u25B6 Replay: <gray>%current% / %total% <dark_gray>(%percent%%) <aqua>[%speed%]",
"current", current, "total", total, "percent", String.valueOf(percent), "speed", speedText)
: Component.text("\u25B6 Replay: ", NamedTextColor.GREEN)
.append(Component.text(current + " / " + total, NamedTextColor.GRAY))
.append(Component.text(" (" + percent + "%)", NamedTextColor.DARK_GRAY))
.append(Component.text(" [" + speedText + "]", NamedTextColor.AQUA));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package me.justindevb.replay.config;

import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.java.JavaPlugin;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;

public final class ReplayMessagesConfig {

private static final MiniMessage MINI_MESSAGE = MiniMessage.miniMessage();

private final JavaPlugin plugin;
private YamlConfiguration messages;

public ReplayMessagesConfig(JavaPlugin plugin) {
this.plugin = plugin;
reload();
}

public void reload() {
File file = new File(plugin.getDataFolder(), "messages.yml");
if (!file.exists()) plugin.saveResource("messages.yml", false);

YamlConfiguration loaded = YamlConfiguration.loadConfiguration(file);
YamlConfiguration defaults = loadDefaults();
boolean changed = false;
for (String key : defaults.getKeys(true)) {
if (defaults.isConfigurationSection(key) || loaded.contains(key)) continue;
loaded.set(key, defaults.get(key));
changed = true;
}

if (changed) {
try {
loaded.save(file);
} catch (IOException exception) {
plugin.getLogger().warning("Could not update missing messages.yml keys: " + exception.getMessage());
}
}
messages = loaded;
}

private YamlConfiguration loadDefaults() {
try (InputStream resource = plugin.getResource("messages.yml")) {
if (resource == null) return new YamlConfiguration();
return YamlConfiguration.loadConfiguration(new InputStreamReader(resource, StandardCharsets.UTF_8));
} catch (IOException exception) {
plugin.getLogger().warning("Could not load bundled messages.yml defaults: " + exception.getMessage());
return new YamlConfiguration();
}
}

public Component component(String key, String fallback, String... replacements) {
String value = messages.getString(key, fallback);
for (int index = 0; index + 1 < replacements.length; index += 2) {
value = value.replace("%" + replacements[index] + "%", replacements[index + 1]);
}
return MINI_MESSAGE.deserialize(value);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import java.io.File;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
Expand All @@ -32,7 +33,7 @@ public final class ReplayDebugCommand {
private static final DateTimeFormatter TIMESTAMP_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z")
.withLocale(Locale.ROOT)
.withZone(ZoneId.systemDefault());
private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("0.00");
private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("0.00", DecimalFormatSymbols.getInstance(Locale.ROOT));

private final Replay replay;
private final ReplayManager replayManager;
Expand Down
89 changes: 61 additions & 28 deletions src/main/java/me/justindevb/replay/playback/ReplayInventoryUI.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package me.justindevb.replay.playback;

import me.justindevb.replay.Replay;
import me.justindevb.replay.config.ReplayMessagesConfig;
import me.justindevb.replay.entity.RecordedEntity;
import me.justindevb.replay.entity.RecordedPlayer;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bukkit.*;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
Expand All @@ -20,6 +22,8 @@
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.SkullMeta;
import org.bukkit.NamespacedKey;
import org.bukkit.persistence.PersistentDataType;

import java.util.List;
import java.util.Map;
Expand All @@ -32,6 +36,8 @@
*/
public class ReplayInventoryUI implements Listener {

private static final MiniMessage MINI_MESSAGE = MiniMessage.miniMessage();

/**
* Callback interface for actions that must be delegated back to ReplaySession.
*/
Expand All @@ -46,6 +52,7 @@ public interface SessionControl {

private final Player viewer;
private final Replay replay;
private final NamespacedKey controlKey;
private final Supplier<Map<UUID, RecordedEntity>> recordedEntitiesSupplier;
private final SessionControl sessionControl;

Expand All @@ -58,6 +65,7 @@ public ReplayInventoryUI(Replay replay,
Supplier<Map<UUID, RecordedEntity>> recordedEntitiesSupplier,
SessionControl sessionControl) {
this.replay = replay;
this.controlKey = new NamespacedKey("betterreplay", "replay-control");
this.viewer = viewer;
this.recordedEntitiesSupplier = recordedEntitiesSupplier;
this.sessionControl = sessionControl;
Expand Down Expand Up @@ -91,28 +99,33 @@ public void restoreInventory() {
public void giveReplayControls() {
ItemStack pauseButton = new ItemStack(Material.RED_DYE);
ItemMeta pauseMeta = pauseButton.getItemMeta();
pauseMeta.displayName(Component.text("Pause / Play", NamedTextColor.RED));
pauseMeta.displayName(message("items.pause-play", "<red>Pause / Play"));
pauseButton.setItemMeta(pauseMeta);
markControl(pauseButton, "pause-play");

ItemStack skipForward = new ItemStack(Material.LIME_DYE);
ItemMeta forwardMeta = skipForward.getItemMeta();
forwardMeta.displayName(Component.text("+5 seconds", NamedTextColor.GREEN));
forwardMeta.displayName(message("items.skip-forward", "<green>+5 seconds"));
skipForward.setItemMeta(forwardMeta);
markControl(skipForward, "skip-forward");

ItemStack skipBackward = new ItemStack(Material.YELLOW_DYE);
ItemMeta backwardMeta = skipBackward.getItemMeta();
backwardMeta.displayName(Component.text("-5 seconds", NamedTextColor.YELLOW));
backwardMeta.displayName(message("items.skip-backward", "<yellow>-5 seconds"));
skipBackward.setItemMeta(backwardMeta);
markControl(skipBackward, "skip-backward");

ItemStack stopReplay = new ItemStack(Material.BARRIER);
ItemMeta stopMeta = stopReplay.getItemMeta();
stopMeta.displayName(Component.text("Exit Replay", NamedTextColor.DARK_RED));
stopMeta.displayName(message("items.exit", "<dark_red>Exit Replay"));
stopReplay.setItemMeta(stopMeta);
markControl(stopReplay, "exit");

ItemStack playerMenu = new ItemStack(Material.PLAYER_HEAD);
ItemMeta menuMeta = playerMenu.getItemMeta();
menuMeta.displayName(Component.text("Players", NamedTextColor.AQUA));
menuMeta.displayName(message("items.players", "<aqua>Players"));
playerMenu.setItemMeta(menuMeta);
markControl(playerMenu, "players");

viewer.getInventory().setItem(0, skipBackward);
viewer.getInventory().setItem(1, pauseButton);
Expand All @@ -126,13 +139,15 @@ public void giveReplayControls() {
public void showStepControls() {
ItemStack stepBack = new ItemStack(Material.CYAN_DYE);
ItemMeta backMeta = stepBack.getItemMeta();
backMeta.displayName(Component.text("\u25C0\u25C0 Previous Frame", NamedTextColor.AQUA));
backMeta.displayName(message("items.previous-frame", "<aqua>\u25C0\u25C0 Previous Frame"));
stepBack.setItemMeta(backMeta);
markControl(stepBack, "previous-frame");

ItemStack stepForward = new ItemStack(Material.MAGENTA_DYE);
ItemMeta fwdMeta = stepForward.getItemMeta();
fwdMeta.displayName(Component.text("\u25B6\u25B6 Next Frame", NamedTextColor.LIGHT_PURPLE));
fwdMeta.displayName(message("items.next-frame", "<light_purple>\u25B6\u25B6 Next Frame"));
stepForward.setItemMeta(fwdMeta);
markControl(stepForward, "next-frame");

viewer.getInventory().setItem(5, stepBack);
viewer.getInventory().setItem(6, stepForward);
Expand All @@ -145,19 +160,21 @@ public void hideStepControls() {

public void showSpeedControls(double currentSpeed) {
String speedText = String.format("%.1fx", currentSpeed);
List<Component> speedLore = List.of(Component.text("Current: " + speedText, NamedTextColor.GRAY));
List<Component> speedLore = List.of(message("items.speed-lore", "<gray>Current: %speed%", "speed", speedText));

ItemStack slower = new ItemStack(Material.ORANGE_DYE);
ItemMeta slowerMeta = slower.getItemMeta();
slowerMeta.displayName(Component.text("\u23EA Slower", NamedTextColor.GOLD));
slowerMeta.displayName(message("items.slower", "<gold>\u23EA Slower"));
slowerMeta.lore(speedLore);
slower.setItemMeta(slowerMeta);
markControl(slower, "slower");

ItemStack faster = new ItemStack(Material.LIGHT_BLUE_DYE);
ItemMeta fasterMeta = faster.getItemMeta();
fasterMeta.displayName(Component.text("\u23E9 Faster", NamedTextColor.BLUE));
fasterMeta.displayName(message("items.faster", "<blue>\u23E9 Faster"));
fasterMeta.lore(speedLore);
faster.setItemMeta(fasterMeta);
markControl(faster, "faster");

viewer.getInventory().setItem(5, slower);
viewer.getInventory().setItem(6, faster);
Expand All @@ -167,7 +184,7 @@ public void openPlayerMenu() {
Inventory inv = Bukkit.createInventory(
null,
27,
Component.text("Recorded Players", NamedTextColor.DARK_GRAY)
message("menus.recorded-players", "<dark_gray>Recorded Players")
);

for (RecordedEntity entity : recordedEntitiesSupplier.get().values()) {
Expand Down Expand Up @@ -287,8 +304,7 @@ public void onPlayerInteract(PlayerInteractEvent e) {
if (handItem == null || !handItem.hasItemMeta())
return;

Component displayName = handItem.getItemMeta().displayName();
String name = displayName instanceof TextComponent tc ? tc.content() : "";
String control = getControl(handItem);

RecordedPlayer targetPlayer = getTargetedRecordedPlayer(player);
if (targetPlayer != null) {
Expand All @@ -297,16 +313,19 @@ public void onPlayerInteract(PlayerInteractEvent e) {
return;
}

switch (name) {
case "Pause / Play" -> sessionControl.togglePause();
case "+5 seconds" -> sessionControl.skipSeconds(5);
case "-5 seconds" -> sessionControl.skipSeconds(-5);
case "\u25C0\u25C0 Previous Frame" -> sessionControl.stepTick(-1);
case "\u25B6\u25B6 Next Frame" -> sessionControl.stepTick(1);
case "\u23EA Slower" -> sessionControl.changeSpeed(-1);
case "\u23E9 Faster" -> sessionControl.changeSpeed(1);
case "Exit Replay" -> sessionControl.stop();
case "Players" -> openPlayerMenu();
if (control == null) return;

switch (control) {
case "pause-play" -> sessionControl.togglePause();
case "skip-forward" -> sessionControl.skipSeconds(5);
case "skip-backward" -> sessionControl.skipSeconds(-5);
case "previous-frame" -> sessionControl.stepTick(-1);
case "next-frame" -> sessionControl.stepTick(1);
case "slower" -> sessionControl.changeSpeed(-1);
case "faster" -> sessionControl.changeSpeed(1);
case "exit" -> sessionControl.stop();
case "players" -> openPlayerMenu();
default -> { return; }
}

e.setCancelled(true);
Expand Down Expand Up @@ -385,11 +404,7 @@ public void onPlayerDropItem(PlayerDropItemEvent e) {
if (item == null || !item.hasItemMeta())
return;

Component dropDisplayName = item.getItemMeta().displayName();
String dropName = dropDisplayName instanceof TextComponent tc ? tc.content() : "";
if (dropName.equals("Pause / Play") || dropName.equals("+5 seconds") || dropName.equals("-5 seconds")
|| dropName.equals("\u25C0\u25C0 Previous Frame") || dropName.equals("\u25B6\u25B6 Next Frame")
|| dropName.equals("\u23EA Slower") || dropName.equals("\u23E9 Faster")) {
if (getControl(item) != null) {
e.setCancelled(true);
}
}
Expand All @@ -404,4 +419,22 @@ public void onEntityPickupItem(EntityPickupItemEvent e) {

e.setCancelled(true);
}

private Component message(String key, String fallback, String... replacements) {
ReplayMessagesConfig messages = replay.getMessages();
if (messages != null) return messages.component(key, fallback, replacements);
String value = fallback;
for (int index = 0; index + 1 < replacements.length; index += 2) {
value = value.replace("%" + replacements[index] + "%", replacements[index + 1]);
}
return MINI_MESSAGE.deserialize(value);
}

private void markControl(ItemStack item, String control) {
item.editPersistentDataContainer(pdc -> pdc.set(controlKey, PersistentDataType.STRING, control));
}

private String getControl(ItemStack item) {
return item.getPersistentDataContainer().get(controlKey, PersistentDataType.STRING);
}
}
Loading