Skip to content

Commit 4a9979b

Browse files
committed
Resolve components ourselves. Fixes #696
1 parent 598d953 commit 4a9979b

File tree

4 files changed

+186
-53
lines changed

4 files changed

+186
-53
lines changed

src/main/java/net/earthcomputer/clientcommands/command/CTellRawCommand.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
import com.mojang.brigadier.Command;
44
import com.mojang.brigadier.CommandDispatcher;
5+
import net.earthcomputer.clientcommands.util.CComponentUtil;
56
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
67
import net.minecraft.commands.CommandBuildContext;
7-
import net.minecraft.network.chat.ComponentUtils;
88
import net.minecraft.network.chat.MutableComponent;
99

1010
import static dev.xpple.clientarguments.arguments.CComponentArgument.*;
@@ -16,7 +16,7 @@ public static void register(CommandDispatcher<FabricClientCommandSource> dispatc
1616
dispatcher.register(literal("ctellraw")
1717
.then(argument("message", textComponent(context))
1818
.executes(ctx -> {
19-
MutableComponent component = ComponentUtils.updateForEntity(new FakeCommandSource(ctx.getSource().getPlayer()), getComponent(ctx, "message"), ctx.getSource().getPlayer(), 0);
19+
MutableComponent component = CComponentUtil.updateForEntity(ctx.getSource(), getComponent(ctx, "message"), ctx.getSource().getPlayer(), 0);
2020
ctx.getSource().getClient().gui.getChat().addMessage(component);
2121
return Command.SINGLE_SUCCESS;
2222
})

src/main/java/net/earthcomputer/clientcommands/command/FakeCommandSource.java

Lines changed: 0 additions & 51 deletions
This file was deleted.

src/main/java/net/earthcomputer/clientcommands/util/CComponentUtil.java

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,55 @@
11
package net.earthcomputer.clientcommands.util;
22

33
import com.demonwav.mcdev.annotations.Translatable;
4+
import com.mojang.brigadier.StringReader;
5+
import com.mojang.brigadier.exceptions.CommandSyntaxException;
6+
import com.mojang.logging.LogUtils;
7+
import dev.xpple.clientarguments.arguments.CEntitySelector;
8+
import dev.xpple.clientarguments.arguments.CEntitySelectorParser;
9+
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
410
import net.minecraft.ChatFormatting;
11+
import net.minecraft.advancements.critereon.NbtPredicate;
12+
import net.minecraft.commands.arguments.coordinates.Coordinates;
13+
import net.minecraft.commands.arguments.coordinates.LocalCoordinates;
14+
import net.minecraft.commands.arguments.coordinates.WorldCoordinates;
15+
import net.minecraft.commands.arguments.selector.EntitySelector;
16+
import net.minecraft.commands.arguments.selector.SelectorPattern;
517
import net.minecraft.core.BlockPos;
18+
import net.minecraft.nbt.CompoundTag;
19+
import net.minecraft.nbt.NbtOps;
20+
import net.minecraft.nbt.StringTag;
21+
import net.minecraft.nbt.Tag;
622
import net.minecraft.network.chat.ClickEvent;
723
import net.minecraft.network.chat.Component;
24+
import net.minecraft.network.chat.ComponentContents;
25+
import net.minecraft.network.chat.ComponentSerialization;
26+
import net.minecraft.network.chat.ComponentUtils;
827
import net.minecraft.network.chat.HoverEvent;
928
import net.minecraft.network.chat.MutableComponent;
29+
import net.minecraft.network.chat.Style;
30+
import net.minecraft.network.chat.contents.BlockDataSource;
31+
import net.minecraft.network.chat.contents.DataSource;
32+
import net.minecraft.network.chat.contents.EntityDataSource;
33+
import net.minecraft.network.chat.contents.NbtContents;
34+
import net.minecraft.network.chat.contents.SelectorContents;
35+
import net.minecraft.network.chat.contents.TranslatableContents;
36+
import net.minecraft.resources.RegistryOps;
37+
import net.minecraft.util.Mth;
1038
import net.minecraft.world.entity.Entity;
39+
import net.minecraft.world.level.block.entity.BlockEntity;
40+
import net.minecraft.world.phys.Vec2;
41+
import net.minecraft.world.phys.Vec3;
42+
import org.jetbrains.annotations.Nullable;
43+
import org.slf4j.Logger;
1144

1245
import java.util.Map;
46+
import java.util.Optional;
1347
import java.util.UUID;
1448
import java.util.concurrent.ConcurrentHashMap;
49+
import java.util.stream.Stream;
1550

1651
public final class CComponentUtil {
52+
private static final Logger LOGGER = LogUtils.getLogger();
1753
private static final Map<UUID, Callback> callbacks = new ConcurrentHashMap<>();
1854

1955
private CComponentUtil() {
@@ -70,6 +106,145 @@ public static boolean runCallback(UUID callbackId) {
70106
return true;
71107
}
72108

109+
public static MutableComponent updateForEntity(
110+
FabricClientCommandSource source,
111+
Component component,
112+
@Nullable Entity entity,
113+
int recursionDepth
114+
) throws CommandSyntaxException {
115+
if (recursionDepth > 100) {
116+
return component.copy();
117+
}
118+
119+
MutableComponent result = resolveContents(source, component.getContents(), entity, recursionDepth + 1);
120+
121+
for (Component sibling : component.getSiblings()) {
122+
result.append(updateForEntity(source, sibling, entity, recursionDepth + 1));
123+
}
124+
125+
return result.withStyle(resolveStyle(source, component.getStyle(), entity, recursionDepth));
126+
}
127+
128+
private static MutableComponent resolveContents(
129+
FabricClientCommandSource source,
130+
ComponentContents contents,
131+
@Nullable Entity entity,
132+
int recursionDepth
133+
) throws CommandSyntaxException {
134+
return switch (contents) {
135+
case NbtContents nbt -> {
136+
if (nbt.compiledNbtPath == null) {
137+
yield Component.empty();
138+
}
139+
140+
Stream<Tag> tags = getData(source, nbt.getDataSource()).flatMap(tag -> {
141+
try {
142+
return nbt.compiledNbtPath.get(tag).stream();
143+
} catch (CommandSyntaxException var3) {
144+
return Stream.empty();
145+
}
146+
});
147+
Component separator = nbt.getSeparator().isPresent() ? updateForEntity(source, nbt.getSeparator().get(), entity, recursionDepth) : ComponentUtils.DEFAULT_NO_STYLE_SEPARATOR;
148+
if (nbt.isInterpreting()) {
149+
RegistryOps<Tag> ops = source.registryAccess().createSerializationContext(NbtOps.INSTANCE);
150+
yield tags.flatMap(tag -> {
151+
try {
152+
Component nestedComponent = ComponentSerialization.CODEC.parse(ops, tag).getOrThrow();
153+
return Stream.of(updateForEntity(source, nestedComponent, entity, recursionDepth));
154+
} catch (Exception e) {
155+
LOGGER.warn("Failed to parse component: {}", tag, e);
156+
return Stream.of();
157+
}
158+
}).reduce((a, b) -> a.append(separator).append(b)).orElseGet(Component::empty);
159+
} else {
160+
yield tags.map(tag -> tag instanceof StringTag(String str) ? str : tag.toString())
161+
.map(Component::literal)
162+
.reduce((a, b) -> a.append(separator).append(b))
163+
.orElseGet(Component::empty);
164+
}
165+
}
166+
case SelectorContents(SelectorPattern(String pattern, EntitySelector ignored), Optional<Component> separator) -> {
167+
if (separator.isPresent()) {
168+
separator = Optional.of(updateForEntity(source, separator.get(), entity, recursionDepth));
169+
}
170+
CEntitySelector selector = new CEntitySelectorParser(new StringReader(pattern), true).parse();
171+
yield ComponentUtils.formatList(selector.findEntities(source), separator, Entity::getDisplayName);
172+
}
173+
case TranslatableContents translatable -> {
174+
Object[] newArgs = new Object[translatable.getArgs().length];
175+
176+
for (int i = 0; i < newArgs.length; i++) {
177+
Object arg = translatable.getArgument(i);
178+
if (arg instanceof Component componentArg) {
179+
newArgs[i] = updateForEntity(source, componentArg, entity, recursionDepth);
180+
} else {
181+
newArgs[i] = arg;
182+
}
183+
}
184+
185+
yield MutableComponent.create(new TranslatableContents(translatable.getKey(), translatable.getFallback(), newArgs));
186+
}
187+
default -> MutableComponent.create(contents);
188+
};
189+
}
190+
191+
private static Style resolveStyle(
192+
FabricClientCommandSource source,
193+
Style style,
194+
@Nullable Entity entity,
195+
int recursionDepth
196+
) throws CommandSyntaxException {
197+
HoverEvent hoverEvent = style.getHoverEvent();
198+
if (hoverEvent instanceof HoverEvent.ShowText(Component textToShow)) {
199+
HoverEvent newHoverEvent = new HoverEvent.ShowText(updateForEntity(source, textToShow, entity, recursionDepth + 1));
200+
return style.withHoverEvent(newHoverEvent);
201+
} else {
202+
return style;
203+
}
204+
}
205+
206+
private static Stream<CompoundTag> getData(FabricClientCommandSource source, DataSource data) throws CommandSyntaxException {
207+
return switch (data) {
208+
case BlockDataSource(String ignored, Coordinates compiledPos) -> {
209+
if (compiledPos == null) {
210+
yield Stream.empty();
211+
}
212+
BlockPos pos = getBlockPos(source, compiledPos);
213+
BlockEntity be = source.getWorld().getBlockEntity(pos);
214+
yield be == null ? Stream.empty() : Stream.of(be.saveWithFullMetadata(source.registryAccess()));
215+
}
216+
case EntityDataSource(String selectorPattern, EntitySelector ignored) -> {
217+
CEntitySelector selector = new CEntitySelectorParser(new StringReader(selectorPattern), true).parse();
218+
yield selector.findEntities(source).stream().map(NbtPredicate::getEntityTagToCompare);
219+
}
220+
default -> Stream.empty();
221+
};
222+
}
223+
224+
private static BlockPos getBlockPos(FabricClientCommandSource source, Coordinates pos) {
225+
Vec3 sourcePos = source.getPosition();
226+
return switch (pos) {
227+
case WorldCoordinates worldCoords -> BlockPos.containing(worldCoords.x.get(sourcePos.x), worldCoords.y.get(sourcePos.y), worldCoords.z.get(sourcePos.z));
228+
case LocalCoordinates localCoords -> {
229+
Vec2 rotation = source.getRotation();
230+
float yawX = Mth.cos((rotation.y + 90) * Mth.DEG_TO_RAD);
231+
float yawZ = Mth.sin((rotation.y + 90) * Mth.DEG_TO_RAD);
232+
float forwardsHPitch = Mth.cos(-rotation.x * Mth.DEG_TO_RAD);
233+
float forwardsVPitch = Mth.sin(-rotation.x * Mth.DEG_TO_RAD);
234+
float upHPitch = Mth.cos((-rotation.x + 90) * Mth.DEG_TO_RAD);
235+
float upVPitch = Mth.sin((-rotation.x + 90) * Mth.DEG_TO_RAD);
236+
Vec3 forwardsVec = new Vec3(yawX * forwardsHPitch, forwardsVPitch, yawZ * forwardsHPitch);
237+
Vec3 upVec = new Vec3(yawX * upHPitch, upVPitch, yawZ * upHPitch);
238+
Vec3 leftVec = forwardsVec.cross(upVec).scale(-1.0);
239+
double dx = forwardsVec.x * localCoords.forwards + upVec.x * localCoords.up + leftVec.x * localCoords.left;
240+
double dy = forwardsVec.y * localCoords.forwards + upVec.y * localCoords.up + leftVec.y * localCoords.left;
241+
double dz = forwardsVec.z * localCoords.forwards + upVec.z * localCoords.up + leftVec.z * localCoords.left;
242+
yield BlockPos.containing(sourcePos.x + dx, sourcePos.y + dy, sourcePos.z + dz);
243+
}
244+
default -> BlockPos.ZERO;
245+
};
246+
}
247+
73248
private record Callback(Runnable callback, long timeout) {
74249
static {
75250
Thread.ofVirtual().name("Clientcommands callback cleanup").start(() -> {

src/main/resources/clientcommands.aw

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,15 @@ accessible field net/minecraft/client/gui/components/CommandSuggestions ARGUMENT
1010
# Data Query Handler
1111
accessible field net/minecraft/client/DebugQueryHandler transactionId I
1212

13+
# CComponentUtil
14+
accessible field net/minecraft/commands/arguments/coordinates/LocalCoordinates forwards D
15+
accessible field net/minecraft/commands/arguments/coordinates/LocalCoordinates left D
16+
accessible field net/minecraft/commands/arguments/coordinates/LocalCoordinates up D
17+
accessible field net/minecraft/commands/arguments/coordinates/WorldCoordinates x Lnet/minecraft/commands/arguments/coordinates/WorldCoordinate;
18+
accessible field net/minecraft/commands/arguments/coordinates/WorldCoordinates y Lnet/minecraft/commands/arguments/coordinates/WorldCoordinate;
19+
accessible field net/minecraft/commands/arguments/coordinates/WorldCoordinates z Lnet/minecraft/commands/arguments/coordinates/WorldCoordinate;
20+
accessible field net/minecraft/network/chat/contents/NbtContents compiledNbtPath Lnet/minecraft/commands/arguments/NbtPathArgument$NbtPath;
21+
1322
# cfinditem
1423
accessible field net/minecraft/world/inventory/AbstractContainerMenu menuType Lnet/minecraft/world/inventory/MenuType;
1524
accessible method net/minecraft/world/level/block/ShulkerBoxBlock canOpen (Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/entity/ShulkerBoxBlockEntity;)Z

0 commit comments

Comments
 (0)