|  | 
| 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