11package dev .diamond .luafy .command ;
22
3+ import com .google .gson .Gson ;
4+ import com .google .gson .JsonObject ;
35import 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 ;
516import net .fabricmc .loader .impl .FabricLoaderImpl ;
17+ import net .minecraft .ChatFormatting ;
618import net .minecraft .commands .CommandBuildContext ;
719import net .minecraft .commands .CommandSourceStack ;
820import 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 ;
1028import java .io .IOException ;
1129import java .nio .file .*;
1230import java .nio .file .attribute .BasicFileAttributes ;
1331import 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
1539public 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