Skip to content

Commit 5406acc

Browse files
committed
Add sign command to create portals more easily
1 parent 2fb9ab2 commit 5406acc

File tree

3 files changed

+118
-15
lines changed

3 files changed

+118
-15
lines changed

changelog/1.2.0+1.21.1.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
* Updated to 1.21.8 for ModFest: Toybox
1+
* Updated to 1.21.8 for ModFest: Toybox
2+
* Added `/demobox sign` command for convenient creation of portals

src/main/java/io/github/mattidragon/demobox/DemoBoxCommand.java

Lines changed: 107 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,33 @@
11
package io.github.mattidragon.demobox;
22

33
import com.mojang.brigadier.Command;
4+
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
45
import com.mojang.brigadier.context.CommandContext;
6+
import com.mojang.brigadier.context.ParsedCommandNode;
57
import com.mojang.brigadier.exceptions.CommandSyntaxException;
68
import com.mojang.brigadier.suggestion.SuggestionProvider;
9+
import com.mojang.brigadier.tree.LiteralCommandNode;
710
import me.lucko.fabric.api.permissions.v0.Permissions;
811
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
912
import net.fabricmc.fabric.api.event.Event;
13+
import net.minecraft.block.HangingSignBlock;
14+
import net.minecraft.block.WallHangingSignBlock;
15+
import net.minecraft.block.entity.SignBlockEntity;
16+
import net.minecraft.block.entity.SignText;
1017
import net.minecraft.command.CommandSource;
18+
import net.minecraft.command.argument.BlockPosArgumentType;
1119
import net.minecraft.command.argument.CommandFunctionArgumentType;
1220
import net.minecraft.command.argument.IdentifierArgumentType;
1321
import net.minecraft.command.argument.Vec3ArgumentType;
22+
import net.minecraft.screen.ScreenTexts;
1423
import net.minecraft.server.command.FunctionCommand;
1524
import net.minecraft.server.command.ServerCommandSource;
1625
import net.minecraft.server.function.CommandFunction;
1726
import net.minecraft.structure.StructureTemplateManager;
27+
import net.minecraft.text.ClickEvent;
1828
import net.minecraft.text.Text;
29+
import net.minecraft.util.DyeColor;
30+
import net.minecraft.util.Formatting;
1931
import net.minecraft.util.Identifier;
2032
import net.minecraft.util.math.Vec3d;
2133
import xyz.nucleoid.plasmid.api.game.GameSpaceManager;
@@ -26,6 +38,7 @@
2638

2739
import java.util.Collection;
2840
import java.util.List;
41+
import java.util.function.UnaryOperator;
2942

3043
import static net.minecraft.server.command.CommandManager.argument;
3144
import static net.minecraft.server.command.CommandManager.literal;
@@ -42,25 +55,34 @@ public static void register() {
4255
CommandRegistrationCallback.EVENT.addPhaseOrdering(Event.DEFAULT_PHASE, DemoBox.id("after"));
4356
CommandRegistrationCallback.EVENT.register(DemoBox.id("after"), (dispatcher, registryAccess, environment) -> {
4457
dispatcher.register(literal("demobox")
45-
.requires(source -> Permissions.check(source, "demobox", true))
58+
.requires(Permissions.require("demobox", true))
4659
.then(literal("leave")
47-
.requires(source -> Permissions.check(source, "demobox.leave", true))
48-
.executes(DemoBoxCommand::leaveGame))
60+
.requires(Permissions.require("demobox.leave", true))
61+
.executes(DemoBoxCommand::executeLeave))
4962
.then(literal("open")
50-
.requires(source -> Permissions.check(source, "demobox.open", 2))
51-
.then(argument("template", IdentifierArgumentType.identifier())
52-
.suggests(STRUCTURE_SUGGESTION_PROVIDER)
53-
.executes(context -> execute(context.getSource(), IdentifierArgumentType.getIdentifier(context, "template"), new Vec3d(0.5, 2, 0.5), List.of()))
54-
.then(argument("pos", Vec3ArgumentType.vec3())
55-
.executes(context -> execute(context.getSource(), IdentifierArgumentType.getIdentifier(context, "template"), Vec3ArgumentType.getVec3(context, "pos"), List.of()))
56-
.then(argument("setupFunction", CommandFunctionArgumentType.commandFunction())
57-
.suggests(FunctionCommand.SUGGESTION_PROVIDER)
58-
.executes(context -> execute(context.getSource(), IdentifierArgumentType.getIdentifier(context, "template"), Vec3ArgumentType.getVec3(context, "pos"), CommandFunctionArgumentType.getFunctions(context, "setupFunction"))))))));
63+
.requires(Permissions.require("demobox.open", 2))
64+
.then(buildArgTree(DemoBoxCommand::executeOpen)))
65+
.then(literal("sign")
66+
.requires(Permissions.require("demobox.sign", 2))
67+
.then(argument("signPos", BlockPosArgumentType.blockPos())
68+
.then(buildArgTree(DemoBoxCommand::executeSign))))
69+
);
5970
});
6071
}
6172

73+
private static RequiredArgumentBuilder<ServerCommandSource, Identifier> buildArgTree(CommandHandler handler) {
74+
return argument("template", IdentifierArgumentType.identifier())
75+
.suggests(STRUCTURE_SUGGESTION_PROVIDER)
76+
.executes(context -> handler.execute(context, IdentifierArgumentType.getIdentifier(context, "template"), new Vec3d(0.5, 2, 0.5), List.of()))
77+
.then(argument("pos", Vec3ArgumentType.vec3())
78+
.executes(context -> handler.execute(context, IdentifierArgumentType.getIdentifier(context, "template"), Vec3ArgumentType.getVec3(context, "pos"), List.of()))
79+
.then(argument("setupFunction", CommandFunctionArgumentType.commandFunction())
80+
.suggests(FunctionCommand.SUGGESTION_PROVIDER)
81+
.executes(context -> handler.execute(context, IdentifierArgumentType.getIdentifier(context, "template"), Vec3ArgumentType.getVec3(context, "pos"), CommandFunctionArgumentType.getFunctions(context, "setupFunction")))));
82+
}
83+
6284
// Joinked from GameCommand because brigadier can't deal with childless redirects
63-
private static int leaveGame(CommandContext<ServerCommandSource> context) throws CommandSyntaxException {
85+
private static int executeLeave(CommandContext<ServerCommandSource> context) throws CommandSyntaxException {
6486
var source = context.getSource();
6587
var player = source.getPlayerOrThrow();
6688

@@ -76,7 +98,73 @@ private static int leaveGame(CommandContext<ServerCommandSource> context) throws
7698
return Command.SINGLE_SUCCESS;
7799
}
78100

79-
private static int execute(ServerCommandSource source, Identifier structure, Vec3d pos, Collection<CommandFunction<ServerCommandSource>> functions) throws CommandSyntaxException {
101+
private static int executeSign(CommandContext<ServerCommandSource> context, Identifier structure, Vec3d pos, Collection<CommandFunction<ServerCommandSource>> functions) throws CommandSyntaxException {
102+
var signPos = BlockPosArgumentType.getLoadedBlockPos(context, "signPos");
103+
var source = context.getSource();
104+
var block = source.getWorld().getBlockState(signPos).getBlock();
105+
if (!(source.getWorld().getBlockEntity(signPos) instanceof SignBlockEntity sign)) {
106+
source.sendError(Text.translatable("command.demobox.sign.missing"));
107+
return 0;
108+
}
109+
110+
var command = "demobox open " + getCommandEnd(context);
111+
112+
UnaryOperator<SignText> textChanger = text -> {
113+
var clickEvent = new ClickEvent.RunCommand(command);
114+
Text[] texts;
115+
if (block instanceof HangingSignBlock || block instanceof WallHangingSignBlock) {
116+
texts = new Text[]{
117+
Text.translatable("demobox.hanging_sign.line1").formatted(Formatting.GREEN)
118+
.styled(style -> style.withClickEvent(clickEvent)),
119+
Text.translatable("demobox.hanging_sign.line2").formatted(Formatting.GREEN),
120+
Text.translatable("demobox.hanging_sign.line3").formatted(Formatting.GREEN),
121+
ScreenTexts.EMPTY
122+
};
123+
} else {
124+
texts = new Text[]{
125+
ScreenTexts.EMPTY,
126+
Text.translatable("demobox.sign.line1").formatted(Formatting.GREEN)
127+
.styled(style -> style.withClickEvent(clickEvent)),
128+
Text.translatable("demobox.sign.line2").formatted(Formatting.GREEN),
129+
ScreenTexts.EMPTY
130+
};
131+
}
132+
return new SignText(
133+
texts,
134+
texts,
135+
DyeColor.LIME,
136+
true
137+
);
138+
};
139+
sign.changeText(textChanger, true);
140+
sign.changeText(textChanger, false);
141+
sign.setWaxed(true);
142+
143+
source.sendFeedback(() -> Text.translatable("command.demobox.sign.success"), true);
144+
return 1;
145+
}
146+
147+
/**
148+
* Extracts the argument text from the sign command for use within the sign text.
149+
*/
150+
private static String getCommandEnd(CommandContext<ServerCommandSource> context) {
151+
var nodes = context.getNodes();
152+
ParsedCommandNode<ServerCommandSource> mainNode = null;
153+
for (var i = nodes.size() - 1; i >= 0; i--) {
154+
var node = nodes.get(i);
155+
if (node.getNode() instanceof LiteralCommandNode<ServerCommandSource> literal && literal.getLiteral().equals("sign")) {
156+
mainNode = nodes.get(i + 2);
157+
break;
158+
}
159+
}
160+
if (mainNode == null) {
161+
throw new IllegalStateException("Cannot find node in parsed command (weird hacks going on???)");
162+
}
163+
return context.getInput().substring(mainNode.getRange().getStart());
164+
}
165+
166+
private static int executeOpen(CommandContext<ServerCommandSource> context, Identifier structure, Vec3d pos, Collection<CommandFunction<ServerCommandSource>> functions) throws CommandSyntaxException {
167+
var source = context.getSource();
80168
var player = source.getPlayerOrThrow();
81169

82170
DemoBoxGame.open(new DemoBoxGame.Settings(structure, pos, functions.stream().map(CommandFunction::id).toList()))
@@ -91,4 +179,9 @@ private static int execute(ServerCommandSource source, Identifier structure, Vec
91179
}, player.getServer());
92180
return Command.SINGLE_SUCCESS;
93181
}
182+
183+
@FunctionalInterface
184+
private interface CommandHandler {
185+
int execute(CommandContext<ServerCommandSource> context, Identifier structure, Vec3d pos, Collection<CommandFunction<ServerCommandSource>> functions) throws CommandSyntaxException;
186+
}
94187
}

src/main/resources/data/demobox/lang/en_us.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
{
22
"command.demobox.open.fail": "Failed to open demo box",
3+
"command.demobox.sign.missing": "Cannot find sign at that location",
4+
"command.demobox.sign.success": "Successfully created portal sign",
5+
6+
"demobox.sign.line1": "Click here for",
7+
"demobox.sign.line2": "live demo!",
8+
"demobox.hanging_sign.line1": "Click here",
9+
"demobox.hanging_sign.line2": "for live",
10+
"demobox.hanging_sign.line3": "demo!",
11+
312
"demobox.demo.join": "%s joined the demo",
413
"demobox.demo.leave": "%s left the game",
514
"demobox.demo.death": "You have died in the demo and have thus been removed from it. You can open another one if your want to continue.",

0 commit comments

Comments
 (0)