|
1 | 1 | package net.earthcomputer.clientcommands.util; |
2 | 2 |
|
3 | 3 | 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; |
4 | 10 | 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; |
5 | 17 | 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; |
6 | 22 | import net.minecraft.network.chat.ClickEvent; |
7 | 23 | 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; |
8 | 27 | import net.minecraft.network.chat.HoverEvent; |
9 | 28 | 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; |
10 | 38 | 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; |
11 | 44 |
|
12 | 45 | import java.util.Map; |
| 46 | +import java.util.Optional; |
13 | 47 | import java.util.UUID; |
14 | 48 | import java.util.concurrent.ConcurrentHashMap; |
| 49 | +import java.util.stream.Stream; |
15 | 50 |
|
16 | 51 | public final class CComponentUtil { |
| 52 | + private static final Logger LOGGER = LogUtils.getLogger(); |
17 | 53 | private static final Map<UUID, Callback> callbacks = new ConcurrentHashMap<>(); |
18 | 54 |
|
19 | 55 | private CComponentUtil() { |
@@ -70,6 +106,145 @@ public static boolean runCallback(UUID callbackId) { |
70 | 106 | return true; |
71 | 107 | } |
72 | 108 |
|
| 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 | + |
73 | 248 | private record Callback(Runnable callback, long timeout) { |
74 | 249 | static { |
75 | 250 | Thread.ofVirtual().name("Clientcommands callback cleanup").start(() -> { |
|
0 commit comments