Skip to content

Commit ed5fc65

Browse files
committed
README.md
1 parent cc06f3c commit ed5fc65

10 files changed

Lines changed: 315 additions & 10 deletions

README.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Datapack Breakpoint
2+
3+
English | [简体中文](README_zh.md)
4+
5+
## Introduce
6+
7+
This is a fabric mod for Minecraft 1.21, which allows you to set breakpoints in the game and "freeze" the game when
8+
the breakpoint is reached.
9+
10+
## Usage
11+
12+
* Set a breakpoint
13+
14+
In datapack, you can insert `#breakpoint` into .mcfunction file to set a breakpoint. For example:
15+
16+
```mcfunction
17+
#test:test
18+
19+
say 1
20+
say 2
21+
#breakpoint
22+
say 3
23+
say 4
24+
```
25+
26+
In this case, after the game executes `say 2`, the game will be "frozen" because it meets the breakpoint.
27+
28+
When the game is "frozen", you can still move around, do whatever you want, just like execute the command `tick freeze`.
29+
So you can check the game state, or do some debugging.
30+
31+
* Step
32+
33+
When the game is "frozen", you can use the command `/breakpoint step` to execute the next command. In above example,
34+
after the game meets the breakpoint, you can use `/breakpoint step` to execute `say 3`, and then use `/breakpoint step`
35+
to execute `say 4`. When all commands are executed, the game will be unfrozen and continue running.
36+
37+
* Continue
38+
39+
When the game is "frozen", you can use the command `/breakpoint move` to unfreeze the game and continue running.
40+
41+
* Get Macro Arguments
42+
43+
By using `/breakpoint get <key>`, you can get the value of the macro argument if the game is executing a macro function.
44+
For example:
45+
46+
```mcfunction
47+
#test:test_macro
48+
49+
say start
50+
#breakpoint
51+
$say $(msg)
52+
say end
53+
```
54+
55+
After executing `function test:test_macro {"msg":"test"}`, we passed the value `test` to the macro argument `msg` and
56+
then the game will pause before `$say $(msg)`. At this time, you can use `/breakpoint get msg` to get the value `test`.

README_zh.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# 断点调试
2+
3+
[English](README.md) | 简体中文
4+
5+
## 介绍
6+
7+
这是一个适用于Minecraft 1.21的fabric模组,为数据包的开发引入了重要的断点调试功能
8+
9+
## 使用
10+
11+
* 设置断点
12+
13+
在数据包中,你可以在.mcfunction文件中插入`#breakpoint`注释来设置断点。例如:
14+
15+
```mcfunction
16+
#test:test
17+
18+
say 1
19+
say 2
20+
#breakpoint
21+
say 3
22+
say 4
23+
```
24+
25+
在这个例子中,当游戏执行完`say 2`后,游戏将会遇到断点,从而被“冻结",或者说进入“断点调试模式”。这个时候,你仍然可以四处移动,或者执行命令之类的,任何
26+
你想做的事情。这个“冻结”的效果和执行了`tick freeze`命令后的效果类似。
27+
28+
你可以在游戏被冻结的时候,执行一些命令来观察你数据包的过程量,或者进行一些调试。不过需要注意的是,只有聊天栏里面执行的命令才会被正常运行,即使是通过
29+
聊天栏执行了函数,那么被执行的函数中的语句也会被冻结。
30+
31+
* 步进
32+
33+
当游戏被冻结的时候,你可以使用命令`/breakpoint step`来执行下一条命令。在上面的例子中,当游戏遇到断点后,你可以使用`/breakpoint step`来执行`say 3`
34+
然后再使用`/breakpoint step`来执行`say 4`。当所有的命令都被执行完后,游戏将会被解冻,继续运行。
35+
36+
当游戏被冻结的时候,你可以使用命令`/breakpoint move`来解冻游戏,继续运行。
37+
38+
* 宏参数的获取
39+
40+
使用`/breakpoint get <key>`可以获取到宏函数中的宏参数的值。例如:
41+
42+
```mcfunction
43+
#test:test_macro
44+
45+
say start
46+
#breakpoint
47+
$say $(msg)
48+
say end
49+
```
50+
51+
在执行`function test:test_macro {"msg":"test"}`后,我们将值`test`传递给了宏参数`msg`。同时,在执行函数的过程中,游戏会在`$say $(msg)`
52+
之前触发断点暂停。这个时候,就可以使用`/breakpoint get msg`来获取到值`test`

src/main/java/top/mcfpp/mod/breakpoint/command/BreakPointCommand.java

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
package top.mcfpp.mod.breakpoint.command;
22

33
import com.google.common.collect.Queues;
4-
import com.mojang.brigadier.arguments.IntegerArgumentType;
4+
import net.minecraft.command.argument.*;
5+
import com.mojang.brigadier.arguments.*;
56
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
67
import net.minecraft.command.CommandExecutionContext;
7-
import net.minecraft.server.command.CommandManager;
8+
import static net.minecraft.server.command.CommandManager.literal;
9+
import static net.minecraft.server.command.CommandManager.argument;
10+
11+
import net.minecraft.nbt.NbtCompound;
12+
import net.minecraft.nbt.NbtElement;
13+
import net.minecraft.nbt.NbtHelper;
814
import net.minecraft.server.command.ServerCommandSource;
915
import net.minecraft.text.Text;
1016
import org.jetbrains.annotations.NotNull;
17+
import org.jetbrains.annotations.Nullable;
1118
import top.mcfpp.mod.breakpoint.DatapackBreakpoint;
1219

1320
import java.util.Deque;
@@ -22,26 +29,47 @@ public class BreakPointCommand {
2229

2330
public static void onInitialize() {
2431
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> {
25-
dispatcher.register(CommandManager.literal("breakpoint")
32+
dispatcher.register(literal("breakpoint")
2633
.requires(source -> source.hasPermissionLevel(2))
2734
.executes(context -> {
2835
context.getSource().sendFeedback(() -> Text.literal("已触发断点"), false);
2936
breakPoint(context.getSource());
3037
return 1;
3138
})
32-
.then(CommandManager.literal("step")
39+
.then(literal("step")
3340
.executes(context -> {
3441
step(1, context.getSource());
3542
return 1;
3643
})
44+
.then(argument("lines", IntegerArgumentType.integer())
45+
.executes(context -> {
46+
final int lines = IntegerArgumentType.getInteger(context, "lines");
47+
step(lines, context.getSource());
48+
return 1;
49+
})
50+
)
3751
)
38-
.then(CommandManager.literal("move")
52+
.then(literal("move")
3953
.executes(context -> {
4054
context.getSource().sendFeedback(() -> Text.literal("已恢复断点"), false);
4155
moveOn(context.getSource());
4256
return 1;
4357
})
4458
)
59+
.then(literal("get")
60+
.then(argument("key", StringArgumentType.string())
61+
.executes(context -> {
62+
final String key = StringArgumentType.getString(context, "key");
63+
NbtElement nbt = getNBT(key);
64+
if(nbt == null){
65+
context.getSource().sendError(Text.literal("无法在当前上下文获取" + key + "的值"));
66+
}else {
67+
context.getSource().sendFeedback(() -> Text.literal(key + "的值是:").append(NbtHelper.toPrettyPrintedText(nbt)), false);
68+
}
69+
return 1;
70+
})
71+
)
72+
)
4573
);
4674
});
4775
}
@@ -83,7 +111,7 @@ private static void step(int steps, ServerCommandSource source) {
83111
try {
84112
context.close();
85113
} catch (Exception e) {
86-
LOGGER.error(e.getMessage());
114+
LOGGER.error(e.toString());
87115
}
88116
}
89117
}
@@ -98,8 +126,25 @@ private static void moveOn(@NotNull ServerCommandSource source) {
98126
context.run();
99127
context.close();
100128
} catch (Exception e) {
101-
LOGGER.error(e.getMessage());
129+
LOGGER.error(e.toString());
102130
}
103131
}
104132
}
133+
134+
private static @Nullable NbtElement getNBT(String key){
135+
var context = storedCommandExecutionContext.peekFirst();
136+
if(context == null){
137+
return null;
138+
}
139+
try{
140+
var cls = context.getClass();
141+
var method = cls.getDeclaredMethod("getKey", String.class);
142+
method.setAccessible(true);
143+
return (NbtElement) method.invoke(context, key);
144+
}catch (Exception e){
145+
LOGGER.error(e.toString());
146+
return null;
147+
}
148+
149+
}
105150
}

src/main/java/top/mcfpp/mod/breakpoint/mixin/CommandExecutionContextMixin.java

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
package top.mcfpp.mod.breakpoint.mixin;
22

33
import com.google.common.collect.Queues;
4+
import com.mojang.brigadier.context.ContextChain;
45
import net.minecraft.command.*;
6+
import net.minecraft.nbt.NbtCompound;
7+
import net.minecraft.nbt.NbtElement;
8+
import net.minecraft.server.command.AbstractServerCommandSource;
9+
import net.minecraft.server.function.ExpandedMacro;
10+
import net.minecraft.server.function.Procedure;
511
import net.minecraft.server.function.Tracer;
612
import net.minecraft.util.profiler.Profiler;
713
import org.jetbrains.annotations.NotNull;
@@ -39,7 +45,25 @@ abstract public class CommandExecutionContextMixin<T> {
3945
@Shadow private int currentDepth;
4046
@Shadow protected abstract void queuePendingCommands();
4147

42-
@Shadow public abstract void enqueueCommand(CommandQueueEntry<T> entry);
48+
@Shadow private static <T extends AbstractServerCommandSource<T>> Frame frame(CommandExecutionContext<T> context, ReturnValueConsumer returnValueConsumer){return null;}
49+
50+
@Inject(method = "enqueueProcedureCall", at = @At("HEAD"), cancellable = true)
51+
private static <T extends AbstractServerCommandSource<T>> void enqueueProcedureCall(
52+
CommandExecutionContext<T> context, Procedure<T> procedure, T source, ReturnValueConsumer returnValueConsumer, CallbackInfo ci
53+
) {
54+
Frame frame = frame(context, returnValueConsumer);
55+
try {
56+
Field field = frame.getClass().getDeclaredField("function");
57+
field.setAccessible(true);
58+
field.set(frame, procedure);
59+
} catch (NoSuchFieldException | IllegalAccessException e) {
60+
throw new RuntimeException(e);
61+
}
62+
context.enqueueCommand(
63+
new CommandQueueEntry<>(frame, new CommandFunctionAction<>(procedure, source.getReturnValueConsumer(), false).bind(source))
64+
);
65+
ci.cancel();
66+
}
4367

4468
@Inject(method = "run()V", at = @At("HEAD"), cancellable = true)
4569
private void onRun(CallbackInfo ci){
@@ -87,6 +111,28 @@ private void onRun(CallbackInfo ci){
87111
ci.cancel();
88112
}
89113

114+
@Unique
115+
private NbtElement getKey(String key){
116+
CommandQueueEntry<T> commandQueueEntry = this.commandQueue.peekFirst();
117+
118+
if (commandQueueEntry == null) {
119+
return null;
120+
}
121+
122+
var frame = commandQueueEntry.frame();
123+
try {
124+
Field field = frame.getClass().getDeclaredField("function");
125+
field.setAccessible(true);
126+
var function = (ExpandedMacro<T>) field.get(frame);
127+
Field field1 = function.getClass().getDeclaredField("arguments");
128+
field1.setAccessible(true);
129+
var args = (NbtCompound)field1.get(function);
130+
return args.get(key);
131+
} catch (NoSuchFieldException | IllegalAccessException e) {
132+
throw new RuntimeException(e);
133+
}
134+
}
135+
90136
@Unique
91137
private void onStep(){
92138

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package top.mcfpp.mod.breakpoint.mixin;
2+
3+
import com.llamalad7.mixinextras.sugar.Local;
4+
import com.llamalad7.mixinextras.sugar.ref.LocalRef;
5+
import net.minecraft.command.CommandExecutionContext;
6+
import net.minecraft.command.CommandFunctionAction;
7+
import net.minecraft.command.Frame;
8+
import net.minecraft.server.command.AbstractServerCommandSource;
9+
import net.minecraft.server.function.Procedure;
10+
import org.spongepowered.asm.mixin.Final;
11+
import org.spongepowered.asm.mixin.Mixin;
12+
import org.spongepowered.asm.mixin.Shadow;
13+
import org.spongepowered.asm.mixin.injection.At;
14+
import org.spongepowered.asm.mixin.injection.Inject;
15+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
16+
17+
import java.lang.reflect.Field;
18+
19+
@Mixin(CommandFunctionAction.class)
20+
public class CommandFunctionActionMixin<T extends AbstractServerCommandSource<T>> {
21+
22+
@Shadow @Final private Procedure<T> function;
23+
24+
@Inject(method = "execute(Lnet/minecraft/server/command/AbstractServerCommandSource;Lnet/minecraft/command/CommandExecutionContext;Lnet/minecraft/command/Frame;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/command/SteppedCommandAction;enqueueCommands(Lnet/minecraft/command/CommandExecutionContext;Lnet/minecraft/command/Frame;Ljava/util/List;Lnet/minecraft/command/SteppedCommandAction$ActionWrapper;)V"))
25+
public void onExecute(T abstractServerCommandSource, CommandExecutionContext<T> commandExecutionContext, Frame frame, CallbackInfo ci, @Local(ordinal = 1) Frame frame2){
26+
try {
27+
Field field = frame2.getClass().getDeclaredField("function");
28+
field.setAccessible(true);
29+
field.set(frame2, function);
30+
} catch (NoSuchFieldException | IllegalAccessException e) {
31+
throw new RuntimeException(e);
32+
}
33+
}
34+
35+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package top.mcfpp.mod.breakpoint.mixin;
2+
3+
import net.minecraft.nbt.NbtCompound;
4+
import net.minecraft.server.function.ExpandedMacro;
5+
import org.spongepowered.asm.mixin.Mixin;
6+
import org.spongepowered.asm.mixin.Unique;
7+
8+
@Mixin(ExpandedMacro.class)
9+
public class ExpandedMacroMixin {
10+
11+
@Unique private NbtCompound arguments = null;
12+
13+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package top.mcfpp.mod.breakpoint.mixin;
2+
3+
import net.minecraft.command.Frame;
4+
import net.minecraft.server.function.Procedure;
5+
import org.spongepowered.asm.mixin.Mixin;
6+
import org.spongepowered.asm.mixin.Unique;
7+
8+
@Mixin(Frame.class)
9+
public class FrameMixin {
10+
@Unique
11+
private Procedure<?> function;
12+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package top.mcfpp.mod.breakpoint.mixin;
2+
3+
import com.mojang.brigadier.CommandDispatcher;
4+
import net.minecraft.nbt.NbtCompound;
5+
import net.minecraft.server.command.AbstractServerCommandSource;
6+
import net.minecraft.server.function.ExpandedMacro;
7+
import net.minecraft.server.function.Macro;
8+
import net.minecraft.server.function.Procedure;
9+
import org.spongepowered.asm.mixin.Mixin;
10+
import org.spongepowered.asm.mixin.Unique;
11+
import org.spongepowered.asm.mixin.injection.At;
12+
import org.spongepowered.asm.mixin.injection.Inject;
13+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
14+
15+
import java.lang.reflect.Field;
16+
import java.util.List;
17+
18+
@Mixin(Macro.class)
19+
public class MacroMixin<T extends AbstractServerCommandSource<T>> {
20+
21+
@Unique private NbtCompound arguments;
22+
23+
@Inject(method = "withMacroReplaced(Lnet/minecraft/nbt/NbtCompound;Lcom/mojang/brigadier/CommandDispatcher;)Lnet/minecraft/server/function/Procedure;", at = @At("HEAD"))
24+
private void OnWithMacroReplaced(NbtCompound arguments, CommandDispatcher<T> dispatcher, CallbackInfoReturnable<Procedure<T>> cir){
25+
this.arguments = arguments;
26+
}
27+
28+
@Inject(method = "withMacroReplaced(Ljava/util/List;Ljava/util/List;Lcom/mojang/brigadier/CommandDispatcher;)Lnet/minecraft/server/function/Procedure;", at = @At("RETURN"), cancellable = true)
29+
private void OnWithMacroReplaced(List<String> varNames, List<String> arguments, CommandDispatcher<T> dispatcher, CallbackInfoReturnable<Procedure<T>> cir){
30+
ExpandedMacro<T> function = (ExpandedMacro<T>) cir.getReturnValue();
31+
try {
32+
Field field = function.getClass().getDeclaredField("arguments");
33+
field.setAccessible(true);
34+
field.set(function, this.arguments);
35+
} catch (NoSuchFieldException | IllegalAccessException e) {
36+
throw new RuntimeException(e);
37+
}
38+
cir.setReturnValue(function);
39+
}
40+
41+
}

0 commit comments

Comments
 (0)