Skip to content

Commit 8c3f15b

Browse files
committed
start adding "hot-commands"
shortcut commands that can be trusted
1 parent 9295832 commit 8c3f15b

22 files changed

Lines changed: 733 additions & 37 deletions

autodocs/autodoc.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
--#region Information
44
-- GENERATED AUTODOC
5-
-- Generated: 2026-01-30T13:06:47.943709500
5+
-- Generated: 2026-02-04T09:55:52.319589400
66
-- Luafy Version: 2.0.0
77
-- Format: Lua LS library file
88
--#endregion

src/main/java/dev/diamond/luafy/Luafy.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ public void onInitialize() {
5151
ScriptPlugins.registerAll();
5252
ScriptEnums.registerAll();
5353
ScriptObjects.registerAll();
54+
ScriptArgtypes.registerAll();
5455
ScriptEvents.registerAll();
5556
ScriptEvents.applyEvents();
5657
AutodocGenerators.registerAll();
Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,49 @@
11
package dev.diamond.luafy.autodoc;
22

3-
public interface Argtype {
3+
import com.mojang.brigadier.arguments.ArgumentType;
4+
import com.mojang.brigadier.context.CommandContext;
5+
import com.mojang.brigadier.suggestion.SuggestionProvider;
6+
import dev.diamond.luafy.script.LuaScript;
7+
import net.minecraft.commands.CommandBuildContext;
8+
import net.minecraft.commands.CommandSourceStack;
9+
import org.jspecify.annotations.Nullable;
10+
import org.luaj.vm2.LuaValue;
11+
12+
import java.util.Optional;
13+
import java.util.function.BiFunction;
14+
15+
public interface Argtype<T extends LuaValue, S> {
416
String getArgtypeString();
17+
Optional<ArgumentType<?>> getCommandArgumentType(CommandBuildContext ctx);
18+
Optional<T> parseCommand(CommandContext<CommandSourceStack> cmdCtx, String argName, LuaScript script);
19+
Optional<SuggestionProvider<CommandSourceStack>> suggest();
20+
21+
static <T extends LuaValue, S> Argtype<T, S> of(String arg, @Nullable ArgumentType<S> commandArgumentType, @Nullable BiFunction<CommandContext<?>, String, T> parser, @Nullable SuggestionProvider<CommandSourceStack> suggestionProvider) {
22+
return new Argtype<>() {
23+
@Override
24+
public String getArgtypeString() {
25+
return arg;
26+
}
27+
28+
@Override
29+
public Optional<ArgumentType<?>> getCommandArgumentType(CommandBuildContext ctx) {
30+
return Optional.ofNullable(commandArgumentType);
31+
}
32+
33+
@Override
34+
public Optional<T> parseCommand(CommandContext<CommandSourceStack> cmdCtx, String argName, LuaScript script) {
35+
if (parser == null) return Optional.empty();
36+
return Optional.ofNullable(parser.apply(cmdCtx, argName));
37+
}
38+
39+
@Override
40+
public Optional<SuggestionProvider<CommandSourceStack>> suggest() {
41+
return Optional.ofNullable(suggestionProvider);
42+
}
43+
};
44+
}
45+
46+
static <T extends LuaValue, S> Argtype<T, S> of(String arg) {
47+
return of(arg, null, null, null);
48+
}
549
}
Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,36 @@
11
package dev.diamond.luafy.autodoc;
22

3+
import com.mojang.brigadier.arguments.BoolArgumentType;
4+
import com.mojang.brigadier.arguments.FloatArgumentType;
5+
import com.mojang.brigadier.arguments.IntegerArgumentType;
6+
import com.mojang.brigadier.arguments.StringArgumentType;
7+
import com.mojang.datafixers.util.Either;
8+
import org.luaj.vm2.*;
9+
10+
import java.util.List;
11+
import java.util.Optional;
12+
313
public class Argtypes {
414

515
// since these are "primitives" they are in lowercase; this also ensures parity with LuaCATS and lua
616
// other argtypes (enums, classes) should adhere to PascalCase like java class names
717

8-
public static final Argtype NIL = () -> "nil";
9-
public static final Argtype BOOLEAN = () -> "boolean";
10-
public static final Argtype NUMBER = () -> "number";
11-
public static final Argtype STRING = () -> "string";
12-
public static final Argtype VALUE = () -> "any";
13-
public static final Argtype INTEGER = () -> "integer";
14-
public static final Argtype TABLE = () -> "table";
18+
public static final Argtype<?, ?> NIL = Argtype.of("nil");
19+
public static final Argtype<LuaBoolean, Boolean> BOOLEAN = Argtype.of("boolean", BoolArgumentType.bool(), (ctx, s) -> LuaValue.valueOf(BoolArgumentType.getBool(ctx, s)), null);
20+
public static final Argtype<LuaNumber, Float> NUMBER = Argtype.of("number", FloatArgumentType.floatArg(), (ctx, s) -> LuaValue.valueOf(FloatArgumentType.getFloat(ctx, s)), null);
21+
public static final Argtype<LuaString, String> STRING = Argtype.of("string", StringArgumentType.string(), (ctx, s) -> LuaValue.valueOf(StringArgumentType.getString(ctx, s)), null);
22+
public static final Argtype<LuaValue, ?> VALUE = Argtype.of("any");
23+
public static final Argtype<LuaInteger, Integer> INTEGER = Argtype.of("integer", IntegerArgumentType.integer(), (ctx, s) -> LuaValue.valueOf(IntegerArgumentType.getInteger(ctx, s)), null);
24+
public static final Argtype<LuaTable, ?> TABLE = Argtype.of("table");
1525

1626

17-
public static Argtype array(Argtype argtype) {
18-
return () -> argtype.getArgtypeString() + "[]";
27+
public static Argtype<?, ?> array(Argtype<?, ?> argtype) {
28+
return Argtype.of(argtype.getArgtypeString() + "[]");
1929
}
20-
public static Argtype or(Argtype a, Argtype b) {
21-
return () -> a.getArgtypeString() + " | " + b.getArgtypeString();
30+
public static Argtype<?, ?> or(Argtype<?, ?> a, Argtype<?, ?> b) {
31+
return Argtype.of(a.getArgtypeString() + " | " + b.getArgtypeString());
2232
}
23-
public static Argtype maybe(Argtype argtype) {
24-
return () -> argtype.getArgtypeString() + " | nil";
33+
public static Argtype<?, ?> maybe(Argtype<?, ?> argtype) {
34+
return Argtype.of(argtype.getArgtypeString() + " | nil");
2535
}
2636
}
Lines changed: 208 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,40 @@
11
package dev.diamond.luafy.command;
22

3+
import com.google.gson.Gson;
4+
import com.google.gson.JsonObject;
35
import com.mojang.brigadier.CommandDispatcher;
4-
import net.fabricmc.loader.api.FabricLoader;
6+
import com.mojang.brigadier.builder.ArgumentBuilder;
7+
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
8+
import com.mojang.serialization.Codec;
9+
import com.mojang.serialization.JsonOps;
10+
import com.mojang.serialization.codecs.RecordCodecBuilder;
11+
import dev.diamond.luafy.Luafy;
12+
import dev.diamond.luafy.autodoc.Argtype;
13+
import dev.diamond.luafy.lua.LuaTableBuilder;
14+
import dev.diamond.luafy.registry.LuafyRegistries;
15+
import dev.diamond.luafy.script.LuaScript;
516
import net.fabricmc.loader.impl.FabricLoaderImpl;
17+
import net.minecraft.ChatFormatting;
618
import net.minecraft.commands.CommandBuildContext;
719
import net.minecraft.commands.CommandSourceStack;
820
import net.minecraft.commands.Commands;
21+
import net.minecraft.network.chat.Component;
22+
import net.minecraft.resources.Identifier;
23+
import org.luaj.vm2.LuaValue;
924

25+
import java.io.File;
26+
import java.io.FileNotFoundException;
27+
import java.io.FileReader;
1028
import java.io.IOException;
1129
import java.nio.file.*;
1230
import java.nio.file.attribute.BasicFileAttributes;
1331
import java.util.ArrayList;
32+
import java.util.List;
33+
import java.util.Optional;
34+
import java.util.concurrent.ExecutionException;
35+
36+
import static net.minecraft.commands.Commands.argument;
37+
import static net.minecraft.commands.Commands.literal;
1438

1539
public class HotCommand {
1640

@@ -19,39 +43,203 @@ public class HotCommand {
1943
public static void register(CommandDispatcher<CommandSourceStack> dispatcher,
2044
CommandBuildContext buildCtx, Commands.CommandSelection selection) {
2145
// load files
22-
// decode with codec
23-
// register
46+
List<JsonObject> objs = fetchHotCommandSources();
2447

25-
}
48+
Luafy.LOGGER.info("Found {} hot-commands.", objs.size());
2649

50+
for (JsonObject obj : objs) {
51+
// decode with codec
52+
CommandBean bean = CommandBean.CODEC.decode(JsonOps.INSTANCE, obj).resultOrPartial(Luafy.LOGGER::error).orElseThrow().getFirst();
2753

28-
public static ArrayList<String> fetchHotCommandSources() {
29-
Path path = FabricLoaderImpl.INSTANCE.getGameDir().resolve(PATH);
30-
try {
31-
Files.walkFileTree(path, new FileVisitor<>() {
54+
// build
55+
ArrayList<RequiredArgumentBuilder<CommandSourceStack, ?>> args = new ArrayList<>();
3256

33-
@Override
34-
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
35-
57+
for (CommandArgumentBean arg : bean.args) {
58+
59+
Argtype<?, ?> argtype = LuafyRegistries.SERIALIZABLE_ARGTYPES.getValue(arg.argType);
60+
61+
if (argtype == null) {
62+
throw new RuntimeException("Argtype " + arg.argType + " was not in the registry!");
3663
}
3764

38-
@Override
39-
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
40-
return null;
65+
RequiredArgumentBuilder<CommandSourceStack, ?> argument = argument(
66+
arg.argument,
67+
argtype.getCommandArgumentType(buildCtx).orElseThrow(
68+
() -> new RuntimeException("Argtype " + arg.argType + " is not deserializable from commands."))
69+
);
70+
71+
var suggestor = argtype.suggest();
72+
if (suggestor.isPresent()) {
73+
argument = argument.suggests(suggestor.get());
4174
}
4275

43-
@Override
44-
public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
45-
return null;
76+
args.add(argument);
77+
}
78+
79+
// build chain
80+
var last = args.getLast().executes(ctx -> {
81+
82+
if (!Luafy.SCRIPT_MANAGER.has(bean.scriptId)) {
83+
ctx.getSource().sendSystemMessage(
84+
Component.literal("Script with id " + bean.scriptId + " was not found!").withStyle(ChatFormatting.RED)
85+
);
86+
return 0;
4687
}
88+
LuaScript script = Luafy.SCRIPT_MANAGER.get(bean.scriptId);
89+
90+
// build context table
91+
var table = new LuaTableBuilder();
92+
for (CommandArgumentBean arg : bean.args) {
93+
94+
Argtype<?, ?> argtype = LuafyRegistries.SERIALIZABLE_ARGTYPES.getValue(arg.argType);
95+
96+
if (argtype == null) {
97+
ctx.getSource().sendSystemMessage(
98+
Component.literal("Argtype " + arg.argType + " was not in the registry!").withStyle(ChatFormatting.RED)
99+
);
100+
return 0;
101+
}
102+
103+
Optional<LuaValue> value = (Optional<LuaValue>) argtype.parseCommand(ctx, arg.argument, script);
104+
105+
106+
if (value.isPresent()) {
107+
table.add(arg.argument, value.get());
108+
} else {
109+
ctx.getSource().sendSystemMessage(
110+
Component.literal("Argument " +
111+
arg.argument +
112+
" of type " +
113+
arg.argType +
114+
" could not be deserialised.").withStyle(ChatFormatting.RED)
115+
);
116+
return 0;
117+
}
47118

48-
@Override
49-
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
50-
return null;
51119
}
120+
121+
var future = script.execute(
122+
ctx.getSource(),
123+
table.build()
124+
);
125+
126+
if (bean.awaits) {
127+
try {
128+
var ignored = future.get(); // get to block until done
129+
} catch (InterruptedException | ExecutionException e) {
130+
throw new RuntimeException(e);
131+
}
132+
}
133+
134+
return 1;
52135
});
136+
137+
138+
for (int i = args.size() - 2; i >= 0; i--) {
139+
last = args.get(i).then(last);
140+
}
141+
142+
var branch = literal(bean.root).then(last);
143+
144+
// register
145+
dispatcher.register(branch);
146+
Luafy.LOGGER.info("Registered hot-command branch '{}'.", bean.root);
147+
}
148+
}
149+
150+
151+
public static List<JsonObject> fetchHotCommandSources() {
152+
ArrayList<String> paths = new ArrayList<>();
153+
Path path = FabricLoaderImpl.INSTANCE.getGameDir().resolve(PATH);
154+
155+
path.toFile().mkdirs();
156+
157+
try {
158+
for (Path file : Files.walk(path).toList()) {
159+
if (Files.isRegularFile(file)) {
160+
paths.add(file.toString());
161+
}
162+
}
53163
} catch (IOException e) {
54164
throw new RuntimeException(e);
55165
}
166+
167+
Gson gson = new Gson();
168+
return paths.stream().map(p -> {
169+
try (FileReader reader = new FileReader(p)) {
170+
return gson.fromJson(reader, JsonObject.class);
171+
} catch (IOException e) {
172+
throw new RuntimeException(e);
173+
}
174+
}).toList();
175+
}
176+
177+
public static class CommandBean {
178+
179+
public static final Codec<CommandBean> CODEC = RecordCodecBuilder.create(instance -> instance.group(
180+
Codec.STRING.fieldOf("root_command").forGetter(CommandBean::getRootCommand),
181+
CommandArgumentBean.CODEC.listOf().optionalFieldOf("args", new ArrayList<>()).forGetter(CommandBean::getArgs),
182+
Identifier.CODEC.fieldOf("script").forGetter(CommandBean::getScriptId),
183+
Codec.BOOL.optionalFieldOf("awaits", false).forGetter(CommandBean::awaits),
184+
Codec.BOOL.optionalFieldOf("non_ops_can_run", false).forGetter(CommandBean::nonOpsCanRun)
185+
).apply(instance, CommandBean::new));
186+
187+
188+
public String root;
189+
public List<CommandArgumentBean> args;
190+
public Identifier scriptId;
191+
public boolean awaits;
192+
public boolean nonOpsCanRun;
193+
194+
public CommandBean(String root, List<CommandArgumentBean> args, Identifier scriptId, boolean awaits, boolean nonOpsCanRun) {
195+
this.root = root;
196+
this.args = args;
197+
this.scriptId = scriptId;
198+
this.awaits = awaits;
199+
this.nonOpsCanRun = nonOpsCanRun;
200+
}
201+
202+
public String getRootCommand() {
203+
return this.root;
204+
}
205+
206+
public List<CommandArgumentBean> getArgs() {
207+
return this.args;
208+
}
209+
210+
public Identifier getScriptId() {
211+
return this.scriptId;
212+
}
213+
214+
public boolean awaits() {
215+
return this.awaits;
216+
}
217+
218+
public boolean nonOpsCanRun() {
219+
return this.nonOpsCanRun;
220+
}
221+
}
222+
223+
public static class CommandArgumentBean {
224+
public static final Codec<CommandArgumentBean> CODEC = RecordCodecBuilder.create(instance -> instance.group(
225+
Codec.STRING.fieldOf("argument").forGetter(CommandArgumentBean::getArgument),
226+
Identifier.CODEC.fieldOf("arg_type").forGetter(CommandArgumentBean::getArgType)
227+
).apply(instance, CommandArgumentBean::new));
228+
229+
public String argument;
230+
public Identifier argType;
231+
232+
public CommandArgumentBean(String argument, Identifier argType) {
233+
this.argument = argument;
234+
this.argType = argType;
235+
}
236+
237+
public String getArgument() {
238+
return this.argument;
239+
}
240+
241+
public Identifier getArgType() {
242+
return argType;
243+
}
56244
}
57245
}

0 commit comments

Comments
 (0)