tangled
alpha
login
or
join now
auri.tngl.sh
/
lattice
forked from
theclashfruit.me/lattice
A simple (for now) Discord chat bridge for Hytale.
0
fork
atom
overview
issues
pulls
pipelines
Compare changes
Choose any two refs to compare.
base:
refactor/jda
main
feat/lua
feat/linking
v1.0.0
v1.0.0-rc.2
v1.0.0-rc.1
compare:
refactor/jda
main
feat/lua
feat/linking
v1.0.0
v1.0.0-rc.2
v1.0.0-rc.1
go
+11
-265
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")
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
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
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
25
-
26
24
public static Config<LatticeConfig> config;
27
27
-
public static Config<DiscordDataStore> connections;
28
25
29
29
-
public static LuaHandler luaHandler;
26
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
40
-
41
41
-
luaHandler = new LuaHandler();
42
37
}
43
38
44
39
@Override
···
67
62
return;
68
63
}
69
64
70
70
-
if (conf.features.scripting)
71
71
-
luaHandler.setup(this.getDataDirectory().resolve("scripts"));
72
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
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
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
57
-
58
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
106
-
107
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
180
-
181
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
9
-
import me.theclashfruit.lattice.scripting.globals.Events;
10
9
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
11
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
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);
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
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
-
}
+8
-21
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(
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
49
-
50
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
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()
110
108
.build();
111
109
112
110
public String join = "%s joined %s.";
113
111
public String leave = "%s left.";
112
112
+
113
113
+
public boolean useHyvatarAvatars = false;
114
114
}
115
115
-
}
116
116
-
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;
128
115
}
129
116
}
-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
-
}