A simple (for now) Discord chat bridge for Hytale.

Compare changes

Choose any two refs to compare.

+11 -265
-2
build.gradle
··· 28 28 dependencies { 29 29 compileOnly("com.hypixel.hytale:Server:2026.01.22-6f8bdbdc4") 30 30 31 - implementation("org.luaj:luaj-jse:3.0.1") 32 31 implementation("net.dv8tion:JDA:$jda_version") { 33 32 exclude module: 'opus-java' 34 33 } ··· 54 53 archiveClassifier = '' 55 54 56 55 relocate 'net.dv8tion.jda', 'me.theclashfruit.shadow.jda' 57 - relocate 'org.luaj', 'me.theclashfruit.shadow.luaj' 58 56 } 59 57 60 58 java {
+1 -10
src/main/java/me/theclashfruit/lattice/LatticePlugin.java
··· 11 11 import me.theclashfruit.lattice.commands.LatticeCommand; 12 12 import me.theclashfruit.lattice.discord.BotEventListener; 13 13 import me.theclashfruit.lattice.events.PlayerEvents; 14 - import me.theclashfruit.lattice.scripting.LuaHandler; 15 14 import me.theclashfruit.lattice.util.store.DiscordDataStore; 16 15 import me.theclashfruit.lattice.util.LatticeConfig; 17 16 import net.dv8tion.jda.api.JDA; ··· 22 21 23 22 public class LatticePlugin extends JavaPlugin { 24 23 public static JDA jda; 25 - 26 24 public static Config<LatticeConfig> config; 27 - public static Config<DiscordDataStore> connections; 28 25 29 - public static LuaHandler luaHandler; 26 + public static Config<DiscordDataStore> connections; 30 27 31 28 public static HytaleLogger LOGGER; 32 29 ··· 37 34 38 35 config = this.withConfig("Lattice", LatticeConfig.CODEC); 39 36 connections = this.withConfig("DiscordData", DiscordDataStore.CODEC); 40 - 41 - luaHandler = new LuaHandler(); 42 37 } 43 38 44 39 @Override ··· 67 62 return; 68 63 } 69 64 70 - if (conf.features.scripting) 71 - luaHandler.setup(this.getDataDirectory().resolve("scripts")); 72 - 73 65 jda = JDABuilder 74 66 .createDefault(config.get().discord.token) 75 67 .enableIntents(GatewayIntent.MESSAGE_CONTENT) ··· 87 79 super.shutdown(); 88 80 89 81 jda.shutdown(); 90 - luaHandler.shutdown(); 91 82 } 92 83 }
-7
src/main/java/me/theclashfruit/lattice/discord/BotEventListener.java
··· 5 5 import com.hypixel.hytale.server.core.universe.PlayerRef; 6 6 import com.hypixel.hytale.server.core.universe.Universe; 7 7 import me.theclashfruit.lattice.LatticePlugin; 8 - import me.theclashfruit.lattice.scripting.globals.Discord; 9 8 import me.theclashfruit.lattice.util.LinkUtil; 10 9 import net.dv8tion.jda.api.entities.User; 11 10 import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; ··· 54 53 LOGGER.atWarning().withCause(e).log("Failed to create guild commands, you may have provided an invalid guild id."); 55 54 } 56 55 } 57 - 58 - Discord.callEvents("ready"); 59 56 } 60 57 61 58 @Override ··· 103 100 104 101 Universe.get().sendMessage(joined); 105 102 LOGGER.atInfo().log("[Discord] %s:%s %s", user.getEffectiveName(), builder.toString(), String.join(" ", attachments.stream().map(a -> "[" + a.getFileName() + "]").toList())); 106 - 107 - Discord.callEvents("message_received", event); 108 103 } 109 104 110 105 @Deprecated(forRemoval = true) ··· 177 172 event.reply("You don't have a Hytale account linked.").setEphemeral(true).queue(); 178 173 } 179 174 } 180 - 181 - Discord.callEvents("slash_command_interaction", event); 182 175 } 183 176 }
+2 -3
src/main/java/me/theclashfruit/lattice/events/PlayerEvents.java
··· 6 6 import com.hypixel.hytale.server.core.event.events.player.PlayerReadyEvent; 7 7 import com.hypixel.hytale.server.core.universe.PlayerRef; 8 8 import me.theclashfruit.lattice.LatticePlugin; 9 - import me.theclashfruit.lattice.scripting.globals.Events; 10 9 import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; 11 - import org.luaj.vm2.LuaValue; 12 10 13 11 import java.net.URI; 14 12 import java.util.Map; ··· 54 52 } 55 53 56 54 hook.flatMap(h -> h.sendMessage(content).setUsername(user.getEffectiveName()).setAvatarUrl(user.getAvatarUrl())).queue(); 55 + } else if (LatticePlugin.config.get().discord.messages.useHyvatarAvatars) { 56 + hook.flatMap(h -> h.sendMessage(content).setUsername(sender.getUsername()).setAvatarUrl("https://hyvatar.io/render/%s?size=256".formatted(sender.getUsername()))).queue(); 57 57 } else { 58 58 hook.flatMap(h -> h.sendMessage(content).setUsername(sender.getUsername())).queue(); 59 59 } ··· 61 61 62 62 public static void onPlayerReady(PlayerReadyEvent event) { 63 63 Player player = event.getPlayer(); 64 - Events.callEvents("player_ready", player); 65 64 66 65 if(player.getWorld() != null) { 67 66 String joinMessage = String.format(LatticePlugin.config.get().discord.messages.join, player.getDisplayName(), player.getWorld().getName());
-65
src/main/java/me/theclashfruit/lattice/scripting/LuaHandler.java
··· 1 - package me.theclashfruit.lattice.scripting; 2 - 3 - import me.theclashfruit.lattice.scripting.globals.Discord; 4 - import me.theclashfruit.lattice.scripting.globals.Events; 5 - import org.jetbrains.annotations.NotNull; 6 - import org.luaj.vm2.Globals; 7 - import org.luaj.vm2.LuaValue; 8 - import org.luaj.vm2.lib.jse.JsePlatform; 9 - 10 - import java.io.File; 11 - import java.nio.file.Files; 12 - import java.nio.file.Path; 13 - import java.util.Arrays; 14 - import java.util.List; 15 - 16 - import static me.theclashfruit.lattice.LatticePlugin.LOGGER; 17 - 18 - public class LuaHandler { 19 - public LuaHandler() {} 20 - 21 - public void setup(@NotNull Path path) { 22 - File pathFile = path.toFile(); 23 - 24 - if (!pathFile.exists()) 25 - return; 26 - if (!pathFile.isDirectory()) 27 - return; 28 - 29 - List<File> files = Arrays.stream(pathFile.listFiles()) 30 - .filter(f -> 31 - f.isFile() && 32 - f.getName().endsWith(".lua") && 33 - !f.getName().startsWith("_") 34 - ) 35 - .toList(); 36 - 37 - if (files.isEmpty()) 38 - return; 39 - for (File file : files) 40 - loadScript(file); 41 - } 42 - 43 - public void shutdown() { 44 - Events.callEvents("shutdown"); 45 - } 46 - 47 - private Globals getGlobals() { 48 - Globals globals = JsePlatform.standardGlobals(); 49 - 50 - globals.load(new Discord()); 51 - globals.load(new Events()); 52 - 53 - return globals; 54 - } 55 - 56 - private void loadScript(File file) { 57 - try { 58 - Globals globals = getGlobals(); 59 - LuaValue chunk = globals.load(Files.readString(file.toPath())); 60 - chunk.call(); 61 - } catch (Exception e) { 62 - LOGGER.atSevere().withCause(e).log("Failed to load script `%s`.", file.getName()); 63 - } 64 - } 65 - }
-59
src/main/java/me/theclashfruit/lattice/scripting/globals/Discord.java
··· 1 - package me.theclashfruit.lattice.scripting.globals; 2 - 3 - import me.theclashfruit.lattice.util.TwoArgFunctionWithEvents; 4 - import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; 5 - import net.dv8tion.jda.api.events.message.MessageReceivedEvent; 6 - import org.luaj.vm2.LuaTable; 7 - import org.luaj.vm2.LuaValue; 8 - import org.luaj.vm2.Varargs; 9 - import org.luaj.vm2.lib.VarArgFunction; 10 - 11 - import static me.theclashfruit.lattice.LatticePlugin.LOGGER; 12 - 13 - public class Discord extends TwoArgFunctionWithEvents { 14 - private static final String[] functions = { 15 - "set_activity" 16 - }; 17 - 18 - public Discord() { 19 - this.packageName = "discord"; 20 - 21 - this.addEvent("ready", (fn, args) -> fn.call()); 22 - this.addEvent("message_received", (fn, args) -> { 23 - var event = (MessageReceivedEvent) args[0]; 24 - var msg = event.getMessage(); 25 - 26 - fn.call(LuaValue.valueOf(msg.getContentRaw())); 27 - }); 28 - this.addEvent("slash_command_interaction", (fn, args) -> { 29 - var event = (SlashCommandInteractionEvent) args[0]; 30 - var name = event.getName(); 31 - 32 - fn.call(LuaValue.valueOf(name)); 33 - }); 34 - } 35 - 36 - @Override 37 - public LuaValue call(LuaValue value, LuaValue env) { 38 - LuaTable discord = super.call(value, env).checktable(); 39 - 40 - for (String function : functions) { 41 - discord.set(function, new DiscordFunction(function)); 42 - } 43 - 44 - return discord; 45 - } 46 - 47 - static class DiscordFunction extends VarArgFunction { 48 - public DiscordFunction(String name) { 49 - this.name = name; 50 - } 51 - } 52 - 53 - @Override 54 - public Varargs invoke(Varargs args) { 55 - LOGGER.atInfo().log("get called %s", name); 56 - 57 - return NONE; 58 - } 59 - }
-20
src/main/java/me/theclashfruit/lattice/scripting/globals/Events.java
··· 1 - package me.theclashfruit.lattice.scripting.globals; 2 - 3 - import com.hypixel.hytale.server.core.entity.entities.Player; 4 - import me.theclashfruit.lattice.util.TwoArgFunctionWithEvents; 5 - import org.luaj.vm2.LuaTable; 6 - 7 - public class Events extends TwoArgFunctionWithEvents { 8 - public Events() { 9 - this.packageName = "events"; 10 - 11 - this.addEvent("player_ready", (fn, args) -> { 12 - Player player = (Player) args[0]; 13 - LuaTable params = new LuaTable(); 14 - params.set("display_name", player.getDisplayName()); 15 - fn.call(params); 16 - }); 17 - 18 - this.addEvent("shutdown", (fn, _) -> fn.call()); 19 - } 20 - }
-7
src/main/java/me/theclashfruit/lattice/util/Handler.java
··· 1 - package me.theclashfruit.lattice.util; 2 - 3 - import org.luaj.vm2.LuaValue; 4 - 5 - public interface Handler { 6 - void run(LuaValue fn, Object... args); 7 - }
+8 -21
src/main/java/me/theclashfruit/lattice/util/LatticeConfig.java
··· 18 18 ) 19 19 .add() 20 20 .append( 21 - new KeyedCodec<>("Features", FeaturesConfig.CODEC), 22 - (config, value, info) -> config.features = value, 23 - (config, info) -> config.features 24 - ) 25 - .add() 26 - .append( 27 21 new KeyedCodec<>("ChatPrefix", BuilderCodec.STRING), 28 22 (config, value, info) -> config.chat_prefix = value, 29 23 (config, info) -> config.chat_prefix ··· 46 40 public String chat_prefix_colour = "#5865F2"; 47 41 48 42 public DiscordConfig discord = new DiscordConfig(); 49 - 50 - public FeaturesConfig features = new FeaturesConfig(); 51 43 52 44 public static class DiscordConfig { 53 45 public static final BuilderCodec<DiscordConfig> CODEC = BuilderCodec.builder(DiscordConfig.class, DiscordConfig::new) ··· 107 99 (config, info) -> config.leave 108 100 ) 109 101 .add() 102 + .append( 103 + new KeyedCodec<>("UseHyvatarAvatars", BuilderCodec.BOOLEAN), 104 + (config, value, info) -> config.useHyvatarAvatars = value, 105 + (config, info) -> config.useHyvatarAvatars 106 + ) 107 + .add() 110 108 .build(); 111 109 112 110 public String join = "%s joined %s."; 113 111 public String leave = "%s left."; 112 + 113 + public boolean useHyvatarAvatars = false; 114 114 } 115 - } 116 - 117 - public static class FeaturesConfig { 118 - public static BuilderCodec<FeaturesConfig> CODEC = BuilderCodec.builder(FeaturesConfig.class, FeaturesConfig::new) 119 - .append( 120 - new KeyedCodec<>("Scripting", BuilderCodec.BOOLEAN), 121 - (config, value, info) -> config.scripting = value, 122 - (config, info) -> config.scripting 123 - ) 124 - .add() 125 - .build(); 126 - 127 - public boolean scripting = false; 128 115 } 129 116 }
-71
src/main/java/me/theclashfruit/lattice/util/TwoArgFunctionWithEvents.java
··· 1 - package me.theclashfruit.lattice.util; 2 - 3 - import org.luaj.vm2.Globals; 4 - import org.luaj.vm2.LuaTable; 5 - import org.luaj.vm2.LuaValue; 6 - import org.luaj.vm2.lib.TwoArgFunction; 7 - 8 - import java.util.ArrayList; 9 - import java.util.HashMap; 10 - import java.util.List; 11 - import java.util.Map; 12 - 13 - /** 14 - * A {@link TwoArgFunction} with event functionalities. 15 - */ 16 - public class TwoArgFunctionWithEvents extends TwoArgFunction { 17 - private static final Map<String, List<LuaValue>> events = new HashMap<>(); 18 - private static final Map<String, Handler> handlers = new HashMap<>(); 19 - 20 - protected String packageName; 21 - 22 - /** 23 - * A function to add an event that can be handled. 24 - * 25 - * @param name The events name, ex. `player_join`. 26 - * @param handler A lambda function to convert stuff to {@link LuaValue}. 27 - */ 28 - protected void addEvent(String name, Handler handler) { 29 - events.put(name, new ArrayList<>()); 30 - handlers.put(name, handler); 31 - } 32 - 33 - public static void callEvents(String eventName, Object... args) { 34 - if (!events.containsKey(eventName)) 35 - return; 36 - 37 - List<LuaValue> functions = events.get(eventName); 38 - Handler handler = handlers.get(eventName); 39 - 40 - for (LuaValue function : functions) { 41 - handler.run(function, args); 42 - } 43 - } 44 - 45 - @Override 46 - public LuaValue call(LuaValue value, LuaValue env) { 47 - Globals globals = env.checkglobals(); 48 - 49 - LuaTable eventTable = new LuaTable(); 50 - 51 - eventTable.set("on", new TwoArgFunction() { 52 - @Override 53 - public LuaValue call(LuaValue name, LuaValue fn) { 54 - String n = name.checkjstring(); 55 - 56 - if (!events.containsKey(n)) 57 - return NONE; 58 - 59 - events.get(n).add(fn); 60 - int len = events.get(n).size() - 1; 61 - 62 - return LuaValue.valueOf(len); 63 - } 64 - }); 65 - 66 - env.set(packageName, eventTable); 67 - env.get("package").get("loaded").set(packageName, eventTable); 68 - 69 - return eventTable; 70 - } 71 - }