diff options
Diffstat (limited to 'src/main/java/de/hysky/skyblocker/skyblock/shortcut')
3 files changed, 553 insertions, 0 deletions
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/shortcut/Shortcuts.java b/src/main/java/de/hysky/skyblocker/skyblock/shortcut/Shortcuts.java new file mode 100644 index 00000000..9c058a4f --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/shortcut/Shortcuts.java @@ -0,0 +1,208 @@ +package de.hysky.skyblocker.skyblock.shortcut; + +import com.google.gson.JsonObject; +import com.google.gson.reflect.TypeToken; +import com.mojang.brigadier.Command; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.arguments.StringArgumentType; +import de.hysky.skyblocker.SkyblockerMod; +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.utils.scheduler.Scheduler; +import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; +import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; +import net.fabricmc.fabric.api.client.message.v1.ClientSendMessageEvents; +import net.minecraft.client.MinecraftClient; +import net.minecraft.command.CommandRegistryAccess; +import net.minecraft.text.Text; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.*; +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.argument; +import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal; + +public class Shortcuts { + private static final Logger LOGGER = LoggerFactory.getLogger(Shortcuts.class); + private static final File SHORTCUTS_FILE = SkyblockerMod.CONFIG_DIR.resolve("shortcuts.json").toFile(); + @Nullable + private static CompletableFuture<Void> shortcutsLoaded; + public static final Map<String, String> commands = new HashMap<>(); + public static final Map<String, String> commandArgs = new HashMap<>(); + + public static boolean isShortcutsLoaded() { + return shortcutsLoaded != null && shortcutsLoaded.isDone(); + } + + public static void init() { + loadShortcuts(); + ClientLifecycleEvents.CLIENT_STOPPING.register(Shortcuts::saveShortcuts); + ClientCommandRegistrationCallback.EVENT.register(Shortcuts::registerCommands); + ClientSendMessageEvents.MODIFY_COMMAND.register(Shortcuts::modifyCommand); + } + + protected static void loadShortcuts() { + if (shortcutsLoaded != null && !isShortcutsLoaded()) { + return; + } + shortcutsLoaded = CompletableFuture.runAsync(() -> { + try (BufferedReader reader = new BufferedReader(new FileReader(SHORTCUTS_FILE))) { + Type shortcutsType = new TypeToken<Map<String, Map<String, String>>>() { + }.getType(); + Map<String, Map<String, String>> shortcuts = SkyblockerMod.GSON.fromJson(reader, shortcutsType); + commands.clear(); + commandArgs.clear(); + commands.putAll(shortcuts.get("commands")); + commandArgs.putAll(shortcuts.get("commandArgs")); + LOGGER.info("[Skyblocker] Loaded {} command shortcuts and {} command argument shortcuts", commands.size(), commandArgs.size()); + } catch (FileNotFoundException e) { + registerDefaultShortcuts(); + LOGGER.warn("[Skyblocker] Shortcuts file not found, using default shortcuts. This is normal when using for the first time."); + } catch (IOException e) { + LOGGER.error("[Skyblocker] Failed to load shortcuts file", e); + } + }); + } + + private static void registerDefaultShortcuts() { + commands.clear(); + commandArgs.clear(); + + // Skyblock + commands.put("/s", "/skyblock"); + commands.put("/i", "/is"); + commands.put("/h", "/hub"); + + // Dungeon + commands.put("/d", "/warp dungeon_hub"); + + // Chat channels + commands.put("/ca", "/chat all"); + commands.put("/cp", "/chat party"); + commands.put("/cg", "/chat guild"); + commands.put("/co", "/chat officer"); + commands.put("/cc", "/chat coop"); + + // Message + commandArgs.put("/m", "/msg"); + + // Party + commandArgs.put("/pa", "/p accept"); + commands.put("/pv", "/p leave"); + commands.put("/pd", "/p disband"); + commands.put("/rp", "/reparty"); + + // Visit + commandArgs.put("/v", "/visit"); + commands.put("/vp", "/visit portalhub"); + } + + @SuppressWarnings("unused") + private static void registerMoreDefaultShortcuts() { + // Combat + commands.put("/spider", "/warp spider"); + commands.put("/crimson", "/warp nether"); + commands.put("/end", "/warp end"); + + // Mining + commands.put("/gold", "/warp gold"); + commands.put("/cavern", "/warp deep"); + commands.put("/dwarven", "/warp mines"); + commands.put("/fo", "/warp forge"); + commands.put("/ch", "/warp crystals"); + + // Foraging & Farming + commands.put("/park", "/warp park"); + commands.put("/barn", "/warp barn"); + commands.put("/desert", "/warp desert"); + commands.put("/ga", "/warp garden"); + + // Other warps + commands.put("/castle", "/warp castle"); + commands.put("/museum", "/warp museum"); + commands.put("/da", "/warp da"); + commands.put("/crypt", "/warp crypt"); + commands.put("/nest", "/warp nest"); + commands.put("/magma", "/warp magma"); + commands.put("/void", "/warp void"); + commands.put("/drag", "/warp drag"); + commands.put("/jungle", "/warp jungle"); + commands.put("/howl", "/warp howl"); + } + + protected static void saveShortcuts(MinecraftClient client) { + JsonObject shortcutsJson = new JsonObject(); + shortcutsJson.add("commands", SkyblockerMod.GSON.toJsonTree(commands)); + shortcutsJson.add("commandArgs", SkyblockerMod.GSON.toJsonTree(commandArgs)); + try (BufferedWriter writer = new BufferedWriter(new FileWriter(SHORTCUTS_FILE))) { + SkyblockerMod.GSON.toJson(shortcutsJson, writer); + LOGGER.info("[Skyblocker] Saved {} command shortcuts and {} command argument shortcuts", commands.size(), commandArgs.size()); + } catch (IOException e) { + LOGGER.error("[Skyblocker] Failed to save shortcuts file", e); + } + } + + private static void registerCommands(CommandDispatcher<FabricClientCommandSource> dispatcher, CommandRegistryAccess registryAccess) { + for (String key : commands.keySet()) { + if (key.startsWith("/")) { + dispatcher.register(literal(key.substring(1))); + } + } + for (String key : commandArgs.keySet()) { + if (key.startsWith("/")) { + dispatcher.register(literal(key.substring(1)).then(argument("args", StringArgumentType.greedyString()))); + } + } + dispatcher.register(literal(SkyblockerMod.NAMESPACE).then(literal("help").executes(context -> { + FabricClientCommandSource source = context.getSource(); + String status = SkyblockerConfigManager.get().general.shortcuts.enableShortcuts && SkyblockerConfigManager.get().general.shortcuts.enableCommandShortcuts ? "§a§l (Enabled)" : "§c§l (Disabled)"; + source.sendFeedback(Text.of("§e§lSkyblocker §fCommand Shortcuts" + status)); + if (!isShortcutsLoaded()) { + source.sendFeedback(Text.translatable("skyblocker.shortcuts.notLoaded")); + } else for (Map.Entry<String, String> command : commands.entrySet()) { + source.sendFeedback(Text.of("§7" + command.getKey() + " §f→ §7" + command.getValue())); + } + status = SkyblockerConfigManager.get().general.shortcuts.enableShortcuts && SkyblockerConfigManager.get().general.shortcuts.enableCommandArgShortcuts ? "§a§l (Enabled)" : "§c§l (Disabled)"; + source.sendFeedback(Text.of("§e§lSkyblocker §fCommand Argument Shortcuts" + status)); + if (!isShortcutsLoaded()) { + source.sendFeedback(Text.translatable("skyblocker.shortcuts.notLoaded")); + } else for (Map.Entry<String, String> commandArg : commandArgs.entrySet()) { + source.sendFeedback(Text.of("§7" + commandArg.getKey() + " §f→ §7" + commandArg.getValue())); + } + source.sendFeedback(Text.of("§e§lSkyblocker §fCommands")); + for (String command : dispatcher.getSmartUsage(dispatcher.getRoot().getChild(SkyblockerMod.NAMESPACE), source).values()) { + source.sendFeedback(Text.of("§7/" + SkyblockerMod.NAMESPACE + " " + command)); + } + return Command.SINGLE_SUCCESS; + // Queue the screen or else the screen will be immediately closed after executing this command + })).then(literal("shortcuts").executes(Scheduler.queueOpenScreenCommand(ShortcutsConfigScreen::new)))); + } + + private static String modifyCommand(String command) { + if (SkyblockerConfigManager.get().general.shortcuts.enableShortcuts) { + if (!isShortcutsLoaded()) { + LOGGER.warn("[Skyblocker] Shortcuts not loaded yet, skipping shortcut for command: {}", command); + return command; + } + command = '/' + command; + if (SkyblockerConfigManager.get().general.shortcuts.enableCommandShortcuts) { + command = commands.getOrDefault(command, command); + } + if (SkyblockerConfigManager.get().general.shortcuts.enableCommandArgShortcuts) { + String[] messageArgs = command.split(" "); + for (int i = 0; i < messageArgs.length; i++) { + messageArgs[i] = commandArgs.getOrDefault(messageArgs[i], messageArgs[i]); + } + command = String.join(" ", messageArgs); + } + return command.substring(1); + } + return command; + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/shortcut/ShortcutsConfigListWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/shortcut/ShortcutsConfigListWidget.java new file mode 100644 index 00000000..5ebe4c1a --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/shortcut/ShortcutsConfigListWidget.java @@ -0,0 +1,232 @@ +package de.hysky.skyblocker.skyblock.shortcut; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.Element; +import net.minecraft.client.gui.Selectable; +import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; +import net.minecraft.client.gui.screen.narration.NarrationPart; +import net.minecraft.client.gui.widget.ElementListWidget; +import net.minecraft.client.gui.widget.TextFieldWidget; +import net.minecraft.text.Text; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.stream.Stream; + +public class ShortcutsConfigListWidget extends ElementListWidget<ShortcutsConfigListWidget.AbstractShortcutEntry> { + private final ShortcutsConfigScreen screen; + private final List<Map<String, String>> shortcutMaps = new ArrayList<>(); + + public ShortcutsConfigListWidget(MinecraftClient minecraftClient, ShortcutsConfigScreen screen, int width, int height, int top, int bottom, int itemHeight) { + super(minecraftClient, width, height, top, bottom, itemHeight); + this.screen = screen; + ShortcutCategoryEntry commandCategory = new ShortcutCategoryEntry(Shortcuts.commands, "skyblocker.shortcuts.command.target", "skyblocker.shortcuts.command.replacement"); + if (Shortcuts.isShortcutsLoaded()) { + commandCategory.shortcutsMap.keySet().stream().sorted().forEach(commandTarget -> addEntry(new ShortcutEntry(commandCategory, commandTarget))); + } else { + addEntry(new ShortcutLoadingEntry()); + } + ShortcutCategoryEntry commandArgCategory = new ShortcutCategoryEntry(Shortcuts.commandArgs, "skyblocker.shortcuts.commandArg.target", "skyblocker.shortcuts.commandArg.replacement", "skyblocker.shortcuts.commandArg.tooltip"); + if (Shortcuts.isShortcutsLoaded()) { + commandArgCategory.shortcutsMap.keySet().stream().sorted().forEach(commandArgTarget -> addEntry(new ShortcutEntry(commandArgCategory, commandArgTarget))); + } else { + addEntry(new ShortcutLoadingEntry()); + } + } + + @Override + public int getRowWidth() { + return super.getRowWidth() + 100; + } + + @Override + protected int getScrollbarPositionX() { + return super.getScrollbarPositionX() + 50; + } + + protected Optional<ShortcutCategoryEntry> getCategory() { + if (getSelectedOrNull() instanceof ShortcutCategoryEntry category) { + return Optional.of(category); + } else if (getSelectedOrNull() instanceof ShortcutEntry shortcutEntry) { + return Optional.of(shortcutEntry.category); + } + return Optional.empty(); + } + + @Override + public void setSelected(@Nullable ShortcutsConfigListWidget.AbstractShortcutEntry entry) { + super.setSelected(entry); + screen.updateButtons(); + } + + protected void addShortcutAfterSelected() { + getCategory().ifPresent(category -> children().add(children().indexOf(getSelectedOrNull()) + 1, new ShortcutEntry(category))); + } + + @Override + protected boolean removeEntry(AbstractShortcutEntry entry) { + return super.removeEntry(entry); + } + + protected boolean hasChanges() { + ShortcutEntry[] notEmptyShortcuts = getNotEmptyShortcuts().toArray(ShortcutEntry[]::new); + return notEmptyShortcuts.length != shortcutMaps.stream().mapToInt(Map::size).sum() || Arrays.stream(notEmptyShortcuts).anyMatch(ShortcutEntry::isChanged); + } + + protected void saveShortcuts() { + shortcutMaps.forEach(Map::clear); + getNotEmptyShortcuts().forEach(ShortcutEntry::save); + Shortcuts.saveShortcuts(MinecraftClient.getInstance()); // Save shortcuts to disk + } + + private Stream<ShortcutEntry> getNotEmptyShortcuts() { + return children().stream().filter(ShortcutEntry.class::isInstance).map(ShortcutEntry.class::cast).filter(ShortcutEntry::isNotEmpty); + } + + protected static abstract class AbstractShortcutEntry extends ElementListWidget.Entry<AbstractShortcutEntry> { + } + + private class ShortcutCategoryEntry extends AbstractShortcutEntry { + private final Map<String, String> shortcutsMap; + private final Text targetName; + private final Text replacementName; + @Nullable + private final Text tooltip; + + private ShortcutCategoryEntry(Map<String, String> shortcutsMap, String targetName, String replacementName) { + this(shortcutsMap, targetName, replacementName, (Text) null); + } + + private ShortcutCategoryEntry(Map<String, String> shortcutsMap, String targetName, String replacementName, String tooltip) { + this(shortcutsMap, targetName, replacementName, Text.translatable(tooltip)); + } + + private ShortcutCategoryEntry(Map<String, String> shortcutsMap, String targetName, String replacementName, @Nullable Text tooltip) { + this.shortcutsMap = shortcutsMap; + this.targetName = Text.translatable(targetName); + this.replacementName = Text.translatable(replacementName); + this.tooltip = tooltip; + shortcutMaps.add(shortcutsMap); + addEntry(this); + } + + @Override + public List<? extends Element> children() { + return List.of(); + } + + @Override + public List<? extends Selectable> selectableChildren() { + return List.of(new Selectable() { + @Override + public SelectionType getType() { + return SelectionType.HOVERED; + } + + @Override + public void appendNarrations(NarrationMessageBuilder builder) { + builder.put(NarrationPart.TITLE, targetName, replacementName); + } + }); + } + + @Override + public void render(DrawContext context, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { + context.drawCenteredTextWithShadow(client.textRenderer, targetName, width / 2 - 85, y + 5, 0xFFFFFF); + context.drawCenteredTextWithShadow(client.textRenderer, replacementName, width / 2 + 85, y + 5, 0xFFFFFF); + if (tooltip != null && isMouseOver(mouseX, mouseY)) { + screen.setTooltip(tooltip); + } + } + } + + private class ShortcutLoadingEntry extends AbstractShortcutEntry { + private final Text text; + + private ShortcutLoadingEntry() { + this.text = Text.translatable("skyblocker.shortcuts.notLoaded"); + } + + @Override + public List<? extends Element> children() { + return List.of(); + } + + @Override + public List<? extends Selectable> selectableChildren() { + return List.of(new Selectable() { + @Override + public SelectionType getType() { + return SelectionType.HOVERED; + } + + @Override + public void appendNarrations(NarrationMessageBuilder builder) { + builder.put(NarrationPart.TITLE, text); + } + }); + } + + @Override + public void render(DrawContext context, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { + context.drawCenteredTextWithShadow(client.textRenderer, text, width / 2, y + 5, 0xFFFFFF); + } + } + + protected class ShortcutEntry extends AbstractShortcutEntry { + private final List<TextFieldWidget> children; + private final ShortcutCategoryEntry category; + private final TextFieldWidget target; + private final TextFieldWidget replacement; + + private ShortcutEntry(ShortcutCategoryEntry category) { + this(category, ""); + } + + private ShortcutEntry(ShortcutCategoryEntry category, String targetString) { + this.category = category; + target = new TextFieldWidget(MinecraftClient.getInstance().textRenderer, width / 2 - 160, 5, 150, 20, category.targetName); + replacement = new TextFieldWidget(MinecraftClient.getInstance().textRenderer, width / 2 + 10, 5, 150, 20, category.replacementName); + target.setText(targetString); + replacement.setText(category.shortcutsMap.getOrDefault(targetString, "")); + children = List.of(target, replacement); + } + + @Override + public String toString() { + return target.getText() + " → " + replacement.getText(); + } + + @Override + public List<? extends Element> children() { + return children; + } + + @Override + public List<? extends Selectable> selectableChildren() { + return children; + } + + private boolean isNotEmpty() { + return !target.getText().isEmpty() && !replacement.getText().isEmpty(); + } + + private boolean isChanged() { + return !category.shortcutsMap.containsKey(target.getText()) || !category.shortcutsMap.get(target.getText()).equals(replacement.getText()); + } + + private void save() { + category.shortcutsMap.put(target.getText(), replacement.getText()); + } + + @Override + public void render(DrawContext context, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { + target.setY(y); + replacement.setY(y); + target.render(context, mouseX, mouseY, tickDelta); + replacement.render(context, mouseX, mouseY, tickDelta); + context.drawCenteredTextWithShadow(client.textRenderer, "→", width / 2, y + 5, 0xFFFFFF); + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/shortcut/ShortcutsConfigScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/shortcut/ShortcutsConfigScreen.java new file mode 100644 index 00000000..196ad0d6 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/shortcut/ShortcutsConfigScreen.java @@ -0,0 +1,113 @@ +package de.hysky.skyblocker.skyblock.shortcut; + +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.ConfirmScreen; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.tooltip.Tooltip; +import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.client.gui.widget.GridWidget; +import net.minecraft.client.gui.widget.SimplePositioningWidget; +import net.minecraft.screen.ScreenTexts; +import net.minecraft.text.Text; + +public class ShortcutsConfigScreen extends Screen { + + private ShortcutsConfigListWidget shortcutsConfigListWidget; + private ButtonWidget buttonDelete; + private ButtonWidget buttonNew; + private ButtonWidget buttonDone; + private boolean initialized; + private double scrollAmount; + private final Screen parent; + + public ShortcutsConfigScreen() { + this(null); + } + + public ShortcutsConfigScreen(Screen parent) { + super(Text.translatable("skyblocker.shortcuts.config")); + this.parent = parent; + } + + @Override + public void setTooltip(Text tooltip) { + super.setTooltip(tooltip); + } + + @Override + protected void init() { + super.init(); + if (initialized) { + shortcutsConfigListWidget.updateSize(width, height, 32, height - 64); + } else { + shortcutsConfigListWidget = new ShortcutsConfigListWidget(client, this, width, height, 32, height - 64, 25); + initialized = true; + } + addDrawableChild(shortcutsConfigListWidget); + GridWidget gridWidget = new GridWidget(); + gridWidget.getMainPositioner().marginX(5).marginY(2); + GridWidget.Adder adder = gridWidget.createAdder(2); + buttonDelete = ButtonWidget.builder(Text.translatable("selectServer.delete"), button -> { + if (client != null && shortcutsConfigListWidget.getSelectedOrNull() instanceof ShortcutsConfigListWidget.ShortcutEntry shortcutEntry) { + scrollAmount = shortcutsConfigListWidget.getScrollAmount(); + client.setScreen(new ConfirmScreen(this::deleteEntry, Text.translatable("skyblocker.shortcuts.deleteQuestion"), Text.translatable("skyblocker.shortcuts.deleteWarning", shortcutEntry), Text.translatable("selectServer.deleteButton"), ScreenTexts.CANCEL)); + } + }).build(); + adder.add(buttonDelete); + buttonNew = ButtonWidget.builder(Text.translatable("skyblocker.shortcuts.new"), buttonNew -> shortcutsConfigListWidget.addShortcutAfterSelected()).build(); + adder.add(buttonNew); + adder.add(ButtonWidget.builder(ScreenTexts.CANCEL, button -> { + if (client != null) { + close(); + } + }).build()); + buttonDone = ButtonWidget.builder(ScreenTexts.DONE, button -> { + shortcutsConfigListWidget.saveShortcuts(); + if (client != null) { + close(); + } + }).tooltip(Tooltip.of(Text.translatable("skyblocker.shortcuts.commandSuggestionTooltip"))).build(); + adder.add(buttonDone); + gridWidget.refreshPositions(); + SimplePositioningWidget.setPos(gridWidget, 0, this.height - 64, this.width, 64); + gridWidget.forEachChild(this::addDrawableChild); + updateButtons(); + } + + private void deleteEntry(boolean confirmedAction) { + if (client != null) { + if (confirmedAction && shortcutsConfigListWidget.getSelectedOrNull() instanceof ShortcutsConfigListWidget.ShortcutEntry shortcutEntry) { + shortcutsConfigListWidget.removeEntry(shortcutEntry); + } + client.setScreen(this); // Re-inits the screen and keeps the old instance of ShortcutsConfigListWidget + shortcutsConfigListWidget.setScrollAmount(scrollAmount); + } + } + + @Override + public void render(DrawContext context, int mouseX, int mouseY, float delta) { + super.render(context, mouseX, mouseY, delta); + context.drawCenteredTextWithShadow(this.textRenderer, this.title, this.width / 2, 16, 0xFFFFFF); + } + + @Override + public void close() { + if (client != null && shortcutsConfigListWidget.hasChanges()) { + client.setScreen(new ConfirmScreen(confirmedAction -> { + if (confirmedAction) { + this.client.setScreen(parent); + } else { + client.setScreen(this); + } + }, Text.translatable("text.skyblocker.quit_config"), Text.translatable("text.skyblocker.quit_config_sure"), Text.translatable("text.skyblocker.quit_discard"), ScreenTexts.CANCEL)); + } else { + this.client.setScreen(parent); + } + } + + protected void updateButtons() { + buttonDelete.active = Shortcuts.isShortcutsLoaded() && shortcutsConfigListWidget.getSelectedOrNull() instanceof ShortcutsConfigListWidget.ShortcutEntry; + buttonNew.active = Shortcuts.isShortcutsLoaded() && shortcutsConfigListWidget.getCategory().isPresent(); + buttonDone.active = Shortcuts.isShortcutsLoaded(); + } +} |