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