From c7ff6de42b0524593dd36c7214132f059b24fe97 Mon Sep 17 00:00:00 2001 From: skippyall <> Date: Mon, 7 Apr 2025 18:39:05 +0200 Subject: [PATCH 1/5] Skin Improvements --- .../io/github/skippyall/minions/Minions.java | 26 +--- .../minions/MinionsTickExecutor.java | 27 ++++ .../skippyall/minions/gui/MinionLookGui.java | 82 ++++++++++++ .../skippyall/minions/input/Result.java | 117 ++++++++++++++++++ .../skippyall/minions/input/TextInput.java | 83 ++++++++----- .../skippyall/minions/minion/MinionData.java | 49 +++++--- .../skippyall/minions/minion/MinionItem.java | 53 +++----- .../minions/minion/MinionPersistentState.java | 60 +++++---- .../minions/minion/MinionProfileUtils.java | 25 +++- .../fakeplayer/EntityPlayerActionPack.java | 8 +- .../minion/fakeplayer/MinionFakePlayer.java | 111 ++++++----------- .../fakeplayer/ServerPlayerInterface.java | 2 +- .../minion/skin/CachedSkinProvider.java | 41 ++++++ .../minion/skin/DirectSkinProvider.java | 33 +++++ .../minions/minion/skin/NameSkinProvider.java | 43 +++++++ .../minions/minion/skin/SkinProvider.java | 14 +++ .../minions/minion/skin/SkinProviders.java | 26 ++++ .../minions/minion/skin/UUIDSkinProvider.java | 51 ++++++++ .../mixinhelpers/EntityViewMixinHelper.java | 9 ++ .../minions/mixins/EntityViewMixin.java | 24 ++++ .../minions/mixins/MobEntityMixin.java | 25 ++-- .../minions/mixins/ServerPlayerMixin.java | 2 +- .../minions/module/ActionModules.java | 2 +- .../skippyall/minions/module/MoveModule.java | 24 +++- .../minions/program/block/GoBlock.java | 2 +- .../resources/data/minions/lang/en_us.json | 1 + src/main/resources/minions.mixins.json | 1 + 27 files changed, 714 insertions(+), 227 deletions(-) create mode 100644 src/main/java/io/github/skippyall/minions/MinionsTickExecutor.java create mode 100644 src/main/java/io/github/skippyall/minions/gui/MinionLookGui.java create mode 100644 src/main/java/io/github/skippyall/minions/input/Result.java create mode 100644 src/main/java/io/github/skippyall/minions/minion/skin/CachedSkinProvider.java create mode 100644 src/main/java/io/github/skippyall/minions/minion/skin/DirectSkinProvider.java create mode 100644 src/main/java/io/github/skippyall/minions/minion/skin/NameSkinProvider.java create mode 100644 src/main/java/io/github/skippyall/minions/minion/skin/SkinProvider.java create mode 100644 src/main/java/io/github/skippyall/minions/minion/skin/SkinProviders.java create mode 100644 src/main/java/io/github/skippyall/minions/minion/skin/UUIDSkinProvider.java create mode 100644 src/main/java/io/github/skippyall/minions/mixinhelpers/EntityViewMixinHelper.java create mode 100644 src/main/java/io/github/skippyall/minions/mixins/EntityViewMixin.java diff --git a/src/main/java/io/github/skippyall/minions/Minions.java b/src/main/java/io/github/skippyall/minions/Minions.java index 203a692..9d6b6ce 100644 --- a/src/main/java/io/github/skippyall/minions/Minions.java +++ b/src/main/java/io/github/skippyall/minions/Minions.java @@ -32,30 +32,22 @@ import java.util.function.Function; public class Minions implements ModInitializer { public static final String MOD_ID = "minions"; public static final TagKey MINION_ITEM_RESISTS = TagKey.of(RegistryKeys.DAMAGE_TYPE, Identifier.of(MOD_ID, "minion_item_resists")); - public static final MinionItem MINION_ITEM = registerItem(Identifier.of(MOD_ID, "minion"), settings -> new MinionItem(settings.component(DataComponentTypes.DAMAGE_RESISTANT, new DamageResistantComponent(MINION_ITEM_RESISTS)),false)); + public static final MinionItem MINION_ITEM = registerItem(Identifier.of(MOD_ID, "minion"), settings -> new MinionItem(settings.component(DataComponentTypes.DAMAGE_RESISTANT, new DamageResistantComponent(MINION_ITEM_RESISTS)))); public static final SimplePolymerItem BASIC_UPGRADE_BASE = registerItem(Identifier.of(MOD_ID, "basic_upgrade_base"), settings -> new SimplePolymerItem(settings, Items.NETHERITE_UPGRADE_SMITHING_TEMPLATE)); public static final SimplePolymerItem ADVANCED_UPGRADE_BASE = registerItem(Identifier.of(MOD_ID, "advanced_upgrade_base"), settings -> new SimplePolymerItem(settings, Items.NETHERITE_UPGRADE_SMITHING_TEMPLATE)); public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID); - private static final List executeOnNextTick = new ArrayList<>(); - @Override public void onInitialize() { MinionData.register(); PolymerEntityUtils.registerType(); ServerLifecycleEvents.SERVER_STARTED.register(server -> { MinionPersistentState.create(server); - MinionPersistentState.INSTANCE.getMinionData().forEach(data -> { - MinionFakePlayer.spawnMinionAt(data, server.getOverworld(), null, null); - }); - }); - ServerTickEvents.START_SERVER_TICK.register(server -> { - exec(() -> { - for (Runnable run:executeOnNextTick) { - run.run(); + MinionPersistentState.INSTANCE.getMinionData().forEach((uuid, data) -> { + if(data.isSpawned()) { + MinionFakePlayer.spawnMinion(data, server.getOverworld(), null, null); } - executeOnNextTick.clear(); }); }); @@ -69,14 +61,4 @@ public class Minions implements ModInitializer { private static T registerItem(Identifier identifier, Function constructor) { return registerItem(identifier, constructor, new Item.Settings()); } - - private static synchronized void exec(Runnable run) { - run.run(); - } - - public static void addExecuteOnNextTick(Runnable run) { - exec(() -> { - executeOnNextTick.add(run); - }); - } } diff --git a/src/main/java/io/github/skippyall/minions/MinionsTickExecutor.java b/src/main/java/io/github/skippyall/minions/MinionsTickExecutor.java new file mode 100644 index 0000000..199a9c3 --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/MinionsTickExecutor.java @@ -0,0 +1,27 @@ +package io.github.skippyall.minions; + +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class MinionsTickExecutor { + private static final List executeOnNextTick = Collections.synchronizedList(new ArrayList<>()); + + public static void register() { + ServerTickEvents.START_SERVER_TICK.register(server -> { + synchronized (executeOnNextTick) { + for (Runnable run:executeOnNextTick) { + run.run(); + } + executeOnNextTick.clear(); + } + }); + } + + public static void addExecuteOnNextTick(Runnable run) { + executeOnNextTick.add(run); + + } +} diff --git a/src/main/java/io/github/skippyall/minions/gui/MinionLookGui.java b/src/main/java/io/github/skippyall/minions/gui/MinionLookGui.java new file mode 100644 index 0000000..0cdab2f --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/gui/MinionLookGui.java @@ -0,0 +1,82 @@ +package io.github.skippyall.minions.gui; + +import eu.pb4.sgui.api.elements.GuiElementBuilder; +import eu.pb4.sgui.api.gui.SimpleGui; +import io.github.skippyall.minions.input.TextInput; +import io.github.skippyall.minions.minion.MinionData; +import io.github.skippyall.minions.minion.MinionItem; +import io.github.skippyall.minions.minion.MinionProfileUtils; +import net.minecraft.block.PlayerSkullBlock; +import net.minecraft.block.entity.SkullBlockEntity; +import net.minecraft.component.DataComponentTypes; +import net.minecraft.component.type.ProfileComponent; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.screen.ScreenHandlerType; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.text.Text; + +import java.util.Optional; + +public class MinionLookGui extends SimpleGui { + private ItemStack minionItem; + + public MinionLookGui(ServerPlayerEntity player, ItemStack minionItem) { + super(ScreenHandlerType.GENERIC_9X3, player, false); + this.minionItem = minionItem; + } + + private void updateName() { + setSlot(10, new GuiElementBuilder() + .setItem(Items.OAK_SIGN) + .setName(Text.literal(getData().name())) + .setCallback(() -> { + openRenameGui(player, minionItem); + }) + ); + } + + private void updateSkin() { + setSlot(16, new GuiElementBuilder() + .setItem(Items.PLAYER_HEAD) + ); + + getData().getSkin(player.server).thenAccept(skin -> { + setSlot(16, new GuiElementBuilder() + .setItem(Items.PLAYER_HEAD) + .setComponent(DataComponentTypes.PROFILE, new ProfileComponent(Optional.empty(), Optional.empty(), skin)) + .setCallback(this::cycleSkinProvider) + ); + }); + } + + private void cycleSkinProvider() { + + } + + private void updateSkinProvider() { + setSlot(25, new GuiElementBuilder() + .setItem(Items.GREEN_STAINED_GLASS_PANE) + .setComponent(DataComponentTypes.CUSTOM_NAME, Text.literal()) + ); + updateSkin(); + } + + private MinionData getData() { + return MinionItem.getData(minionItem); + } + + public static void open(ServerPlayerEntity player, ItemStack minionItem) { + MinionLookGui gui = new MinionLookGui(player, minionItem); + + gui.open(); + } + + public void openRenameGui(ServerPlayerEntity player, ItemStack minionItem) { + TextInput.input(player, Text.translatable("minions.gui.look.rename.title"), "Minion", MinionProfileUtils::checkMinionName) + .thenAccept(name -> { + MinionItem.setData(MinionItem.getDataOrDefault(minionItem).withName(name), minionItem); + open(); + }); + } +} diff --git a/src/main/java/io/github/skippyall/minions/input/Result.java b/src/main/java/io/github/skippyall/minions/input/Result.java new file mode 100644 index 0000000..9f1539b --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/input/Result.java @@ -0,0 +1,117 @@ +package io.github.skippyall.minions.input; + +import org.jetbrains.annotations.NotNull; + +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Function; + +public interface Result { + static Result wrap(UnsafeOperation toWrap) { + return wrapCustomError(toWrap, Exception::getMessage); + } + + static Result wrapCustomError(UnsafeOperation toWrap, E error) { + return wrapCustomError(toWrap, e -> error); + } + + static Result wrapCustomError(UnsafeOperation toWrap, Function errorTransformer) { + try { + return new Result.Success<>(toWrap.run()); + } catch (Exception e) { + return new Result.Error<>(errorTransformer.apply(e)); + } + } + + boolean isSuccess(); + + @NotNull T getOrDefault(@NotNull T defaultValue); + + @NotNull T getOrThrow(); + + @NotNull E getErrorOrThrow(); + + @NotNull Optional getOptional(); + + void ifSuccess(@NotNull Consumer handler); + + void ifError(@NotNull Consumer> handler); + + record Success(@NotNull T result) implements Result { + @Override + public boolean isSuccess() { + return true; + } + + @Override + public @NotNull T getOrDefault(@NotNull T defaultValue) { + return result; + } + + @Override + public @NotNull T getOrThrow() { + return result; + } + + @Override + public @NotNull E getErrorOrThrow() { + throw new RuntimeException("Result was not an Error"); + } + + @Override + public @NotNull Optional getOptional() { + return Optional.of(result); + } + + @Override + public void ifSuccess(@NotNull Consumer handler) { + handler.accept(result); + } + + @Override + public void ifError(@NotNull Consumer> handler) { + + } + } + + record Error(@NotNull E message) implements Result { + @Override + public boolean isSuccess() { + return false; + } + + @Override + public @NotNull T getOrDefault(@NotNull T defaultValue) { + return defaultValue; + } + + @Override + public @NotNull T getOrThrow() { + throw new RuntimeException("Result was an error: " + message.toString()); + } + + @Override + public @NotNull E getErrorOrThrow() { + return message; + } + + @Override + public @NotNull Optional getOptional() { + return Optional.empty(); + } + + @Override + public void ifSuccess(@NotNull Consumer handler) { + + } + + @Override + public void ifError(Consumer> handler) { + handler.accept(this); + } + } + + interface UnsafeOperation { + T run() throws Exception; + } +} diff --git a/src/main/java/io/github/skippyall/minions/input/TextInput.java b/src/main/java/io/github/skippyall/minions/input/TextInput.java index 581e19f..d92cb86 100644 --- a/src/main/java/io/github/skippyall/minions/input/TextInput.java +++ b/src/main/java/io/github/skippyall/minions/input/TextInput.java @@ -10,41 +10,68 @@ import net.minecraft.text.Text; import java.util.concurrent.CompletableFuture; import java.util.function.Function; -public class TextInput { - public static CompletableFuture inputText(ServerPlayerEntity player, Text title, String defaultText) { - CompletableFuture future = new CompletableFuture<>(); +public class TextInput extends AnvilInputGui { + private final GuiElementBuilder valid = new GuiElementBuilder() + .setItem(Items.EMERALD_BLOCK) + .setName(Text.literal("OK")) + .setCallback(this::onConfirm); - AnvilInputGui gui = new AnvilInputGui(player, false); - gui.setSlot(AnvilScreenHandler.OUTPUT_ID, new GuiElementBuilder() - .setItem(Items.EMERALD_BLOCK) - .setName(Text.literal("OK")) - .setCallback(() -> { - gui.close(); - future.complete(gui.getInput()); - }) - ); - gui.setTitle(title); - gui.setDefaultInputValue(defaultText); - gui.open(); + private final GuiElementBuilder invalid = new GuiElementBuilder() + .setItem(Items.REDSTONE_BLOCK); + private final Function> parser; + private final CompletableFuture future; + private Result result; + + public TextInput(ServerPlayerEntity player, Text title, String defaultValue, Function> parser, CompletableFuture future) { + super(player, false); + setTitle(title); + setDefaultInputValue(defaultValue); + this.parser = parser; + this.future = future; + + updateConfirmButton(defaultValue); + } + + public static CompletableFuture input(ServerPlayerEntity player, Text title, String defaultValue, Function> parser) { + CompletableFuture future = new CompletableFuture<>(); + new TextInput<>(player, title, defaultValue, parser, future).open(); return future; } - public static CompletableFuture inputParse(ServerPlayerEntity player, Text title, String defaultValue, Function parser, Text failureMessage) { - return inputText(player, title, String.valueOf(defaultValue)).thenCompose(string -> { - try { - return CompletableFuture.completedFuture(parser.apply(string)); - } catch (Exception e) { - player.sendMessage(failureMessage); - return CompletableFuture.failedFuture(e); - } - }); - } - public static CompletableFuture inputInt(ServerPlayerEntity player, Text title, String defaultValue) { - return inputParse(player, title, defaultValue, Integer::parseInt, Text.translatable("minions.command.input.int.fail")); + return input(player, title, defaultValue, string -> Result.wrapCustomError(() -> Integer.valueOf(string), Text.translatable("minions.command.input.int.fail"))); } public static CompletableFuture inputFloat(ServerPlayerEntity player, Text title, String defaultValue) { - return inputParse(player, title, defaultValue, Float::parseFloat, Text.translatable("minions.command.input.float.fail")); + return input(player, title, defaultValue, string -> Result.wrapCustomError(() -> Float.valueOf(string), Text.translatable("minions.command.input.float.fail"))); + } + + @Override + public void onInput(String input) { + updateConfirmButton(input); + } + + public void updateConfirmButton(String input) { + result = parser.apply(input); + if(result.isSuccess()) { + setSlot(AnvilScreenHandler.OUTPUT_ID, valid); + } else { + Text text = result.getErrorOrThrow(); + setSlot(AnvilScreenHandler.OUTPUT_ID, invalid.setName(text)); + } + } + + @Override + public void onClose() { + if(!future.isDone()) { + future.cancel(false); + } + } + + public void onConfirm() { + result.ifSuccess(success -> { + future.complete(success); + close(); + }); } } diff --git a/src/main/java/io/github/skippyall/minions/minion/MinionData.java b/src/main/java/io/github/skippyall/minions/minion/MinionData.java index bcfe28a..c386766 100644 --- a/src/main/java/io/github/skippyall/minions/minion/MinionData.java +++ b/src/main/java/io/github/skippyall/minions/minion/MinionData.java @@ -1,54 +1,71 @@ package io.github.skippyall.minions.minion; +import com.mojang.authlib.properties.PropertyMap; +import com.mojang.datafixers.util.Pair; import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; import eu.pb4.polymer.core.api.other.PolymerComponent; import io.github.skippyall.minions.Minions; -import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer; +import io.github.skippyall.minions.minion.skin.SkinProvider; +import io.github.skippyall.minions.minion.skin.SkinProviders; import net.minecraft.component.ComponentType; import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtOps; import net.minecraft.registry.Registries; import net.minecraft.registry.Registry; +import net.minecraft.server.MinecraftServer; import net.minecraft.util.Identifier; import net.minecraft.util.Uuids; -import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; -import java.util.Optional; import java.util.UUID; +import java.util.concurrent.CompletableFuture; -public record MinionData(UUID uuid, String name, @NotNull Optional skinUuid) { +public record MinionData(UUID uuid, String name, @Nullable SkinProvider skin, boolean isSpawned) { public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( Uuids.CODEC.fieldOf("uuid").forGetter(MinionData::uuid), Codec.STRING.fieldOf("name").forGetter(MinionData::name), - Uuids.CODEC.optionalFieldOf("skinUuid").orElse(Optional.empty()).forGetter(MinionData::skinUuid) + SkinProviders.CODEC.optionalFieldOf("skin", null).forGetter(MinionData::skin), + Codec.BOOL.optionalFieldOf("isSpawned", false).forGetter(MinionData::isSpawned) ).apply(instance, MinionData::new) ); - public static final ComponentType COMPONENT = Registry.register(Registries.DATA_COMPONENT_TYPE, Identifier.of(Minions.MOD_ID, "minion_data"), ComponentType.builder().codec(CODEC).build()); + + public static final ComponentType COMPONENT = Registry.register(Registries.DATA_COMPONENT_TYPE, Identifier.of(Minions.MOD_ID, "minion_data"), ComponentType.builder().codec(Uuids.CODEC).build()); + + public static MinionData createDefault() { + return new MinionData(UUID.randomUUID(), "Minion", null, false); + } public MinionData withUuid(UUID uuid) { - return new MinionData(uuid, name, skinUuid); + return new MinionData(uuid, name, skin, isSpawned); } public MinionData withName(String name) { - return new MinionData(uuid, name, skinUuid); + return new MinionData(uuid, name, skin, isSpawned); } - public MinionData withSkin(Optional skinUuid) { - return new MinionData(uuid, name, skinUuid); + public MinionData withSkin(@Nullable SkinProvider skin) { + return new MinionData(uuid, name, skin, isSpawned); + } + + public CompletableFuture<@Nullable PropertyMap> getSkin(MinecraftServer server) { + if(skin != null) { + return skin.getSkin(server); + } + return CompletableFuture.completedFuture(null); + } + + public MinionData withSpawned(boolean isSpawned) { + return new MinionData(uuid, name, skin, isSpawned); } public NbtCompound writeNbt() { - return (NbtCompound) MinionData.CODEC.encode(this, NbtOps.INSTANCE, null).getOrThrow(); + return (NbtCompound) MinionData.CODEC.encode(this, NbtOps.INSTANCE, null).result().orElse(new NbtCompound()); } public static MinionData readNbt(NbtCompound nbt) { - return MinionData.CODEC.decode(NbtOps.INSTANCE, nbt).getOrThrow().getFirst(); - } - - public static MinionData fromMinion(MinionFakePlayer minion) { - return new MinionData(minion.getUuid(), minion.getMinionName(), minion.getSkinUuid()); + return MinionData.CODEC.decode(NbtOps.INSTANCE, nbt).resultOrPartial().map(Pair::getFirst).orElseGet(MinionData::createDefault); } public static void register() { diff --git a/src/main/java/io/github/skippyall/minions/minion/MinionItem.java b/src/main/java/io/github/skippyall/minions/minion/MinionItem.java index 25f154a..3f10cb8 100644 --- a/src/main/java/io/github/skippyall/minions/minion/MinionItem.java +++ b/src/main/java/io/github/skippyall/minions/minion/MinionItem.java @@ -4,13 +4,11 @@ import eu.pb4.polymer.core.api.item.PolymerItem; import eu.pb4.polymer.core.api.item.PolymerItemUtils; import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer; import net.minecraft.component.DataComponentTypes; -import net.minecraft.component.type.NbtComponent; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.item.ItemUsageContext; import net.minecraft.item.Items; import net.minecraft.item.tooltip.TooltipType; -import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.world.ServerWorld; import net.minecraft.text.Text; import net.minecraft.util.ActionResult; @@ -20,14 +18,10 @@ import org.jetbrains.annotations.Nullable; import xyz.nucleoid.packettweaker.PacketContext; import java.util.List; -import java.util.Optional; public class MinionItem extends Item implements PolymerItem { - private final boolean canProgram; - - public MinionItem(Settings settings, boolean canProgram) { + public MinionItem(Settings settings) { super(settings); - this.canProgram = canProgram; } @Override @@ -49,7 +43,7 @@ public class MinionItem extends Item implements PolymerItem { @Override public void appendTooltip(ItemStack stack, TooltipContext context, List tooltip, TooltipType type) { - MinionData data = stack.get(MinionData.COMPONENT); + MinionData data = getData(stack); if(data != null) { tooltip.add(Text.translatable("minions.minion_item.tooltip", data.name())); } @@ -57,46 +51,35 @@ public class MinionItem extends Item implements PolymerItem { @Override public ActionResult useOnBlock(ItemUsageContext context) { - String contents = context.getStack().getName().getLiteralString(); - String name; - if(contents != null && contents.length() <= 16) { - name = contents; - } else { - name = "Minion"; - } if(!context.getWorld().isClient) { - MinionData data = getData(context.getStack()); - - if(data == null) { - data = new MinionData(null, name, Optional.empty()); - } - - if (data.uuid() == null) { - MinionFakePlayer.createMinion(data, (ServerWorld) context.getWorld(), (ServerPlayerEntity) context.getPlayer(), canProgram, context.getBlockPos().toCenterPos().add(0,0.5,0), new Vec2f(0, 0)); - }else { - data = data.withName(name); - MinionFakePlayer.spawnMinionAt(data, (ServerWorld) context.getWorld(), context.getBlockPos().toCenterPos().add(0,0.5,0), new Vec2f(0, 0)); - MinionPersistentState.INSTANCE.addMinion(data); - } + MinionData data = getDataOrDefault(context.getStack()); + MinionFakePlayer.spawnMinion(data, (ServerWorld) context.getWorld(), context.getBlockPos().toCenterPos().add(0,0.5,0), new Vec2f(0, 0)); } context.getStack().decrement(1); return ActionResult.SUCCESS; } public static void setData(MinionData data, ItemStack item) { - item.set(MinionData.COMPONENT, data); + item.set(MinionData.COMPONENT, data.uuid()); } @Nullable public static MinionData getData(ItemStack item) { - return item.get(MinionData.COMPONENT); + if(item.contains(MinionData.COMPONENT)) { + return MinionPersistentState.INSTANCE.getMinionData(item.get(MinionData.COMPONENT)); + } + return null; + } + + public static MinionData getDataOrDefault(ItemStack item) { + MinionData data = getData(item); + if(data == null) { + data = MinionData.createDefault(); + } + return data; } public static boolean containsData(ItemStack item) { - NbtComponent nbt = item.get(DataComponentTypes.CUSTOM_DATA); - if (nbt == null) { - return false; - } - return nbt.copyNbt().contains("data"); + return item.contains(MinionData.COMPONENT); } } diff --git a/src/main/java/io/github/skippyall/minions/minion/MinionPersistentState.java b/src/main/java/io/github/skippyall/minions/minion/MinionPersistentState.java index 10a249b..638dfbb 100644 --- a/src/main/java/io/github/skippyall/minions/minion/MinionPersistentState.java +++ b/src/main/java/io/github/skippyall/minions/minion/MinionPersistentState.java @@ -1,6 +1,5 @@ package io.github.skippyall.minions.minion; -import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer; import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtElement; import net.minecraft.nbt.NbtList; @@ -9,8 +8,8 @@ import net.minecraft.server.MinecraftServer; import net.minecraft.world.PersistentState; import net.minecraft.world.World; -import java.util.ArrayList; -import java.util.List; +import java.util.HashMap; +import java.util.Map; import java.util.UUID; public class MinionPersistentState extends PersistentState { @@ -18,24 +17,24 @@ public class MinionPersistentState extends PersistentState { public static MinionPersistentState INSTANCE; - private final List minionData = new ArrayList<>(); - private final List minionUuids = new ArrayList<>(); + private final Map minionData = new HashMap<>(); + //private final List minionUuids = new ArrayList<>(); @Override public NbtCompound writeNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registryLookup) { NbtList list = new NbtList(); - for(MinionData data : minionData) { + for(MinionData data : minionData.values()) { list.add(data.writeNbt()); } nbt.put("minions", list); - NbtList uuids = new NbtList(); + /*NbtList uuids = new NbtList(); for(UUID uuid : minionUuids) { NbtCompound compound = new NbtCompound(); compound.putUuid("uuid", uuid); uuids.add(compound); } - nbt.put("uuids", uuids); + nbt.put("uuids", uuids);*/ return nbt; } @@ -43,55 +42,54 @@ public class MinionPersistentState extends PersistentState { NbtList list = compound.getList("minions", NbtElement.COMPOUND_TYPE); MinionPersistentState instance = new MinionPersistentState(); for(NbtElement element : list) { - instance.addMinion(MinionData.readNbt((NbtCompound) element)); + if(element instanceof NbtCompound compound1) { + MinionData data = MinionData.readNbt((NbtCompound) element); + instance.minionData.put(data.uuid(), data); + } } - NbtList uuids = compound.getList("uuids", NbtElement.COMPOUND_TYPE); + /*NbtList uuids = compound.getList("uuids", NbtElement.COMPOUND_TYPE); for(NbtElement element : uuids) { instance.minionUuids.add(((NbtCompound) element).getUuid("uuid")); - } + }*/ return instance; } - public void addMinionUUID(UUID uuid) { + /*public void addMinionUUID(UUID uuid) { if(!minionUuids.contains(uuid)) { minionUuids.add(uuid); } - } - - public void addMinion(MinionFakePlayer minion) { - addMinion(MinionData.fromMinion(minion)); - } + }*/ public void addMinion(MinionData data) { System.out.println("add Minion " + data.name()); - minionData.add(data); + minionData.put(data.uuid(), data); markDirty(); } - public void removeMinion(MinionFakePlayer minionData) { - removeMinion(minionData.getUuid()); + public void removeMinion(MinionData minionData) { + removeMinion(minionData.uuid()); } public void removeMinion(UUID minionUUID) { - MinionData removal = null; - for (MinionData data : minionData) { - if (data.uuid().equals(minionUUID)) { - removal = data; - } - } - if (removal != null) { - minionData.remove(removal); - } + minionData.remove(minionUUID); markDirty(); } - public List getMinionData() { + public MinionData getMinionData(UUID uuid) { + return minionData.get(uuid); + } + + public Map getMinionData() { return minionData; } + public void updateMinionData(MinionData data) { + minionData.put(data.uuid(), data); + } + public boolean isMinion(UUID uuid) { - return minionUuids.contains(uuid); + return minionData.containsKey(uuid); } public static void create(MinecraftServer server) { diff --git a/src/main/java/io/github/skippyall/minions/minion/MinionProfileUtils.java b/src/main/java/io/github/skippyall/minions/minion/MinionProfileUtils.java index d3c3e77..6fc51ef 100644 --- a/src/main/java/io/github/skippyall/minions/minion/MinionProfileUtils.java +++ b/src/main/java/io/github/skippyall/minions/minion/MinionProfileUtils.java @@ -2,8 +2,12 @@ package io.github.skippyall.minions.minion; import com.mojang.authlib.GameProfile; import com.mojang.authlib.ProfileLookupCallback; +import com.mojang.authlib.properties.PropertyMap; import com.mojang.authlib.yggdrasil.ProfileResult; +import io.github.skippyall.minions.input.Result; import net.minecraft.server.MinecraftServer; +import net.minecraft.text.Text; +import net.minecraft.util.StringHelper; import org.jetbrains.annotations.Nullable; import java.util.UUID; @@ -13,6 +17,8 @@ import java.util.concurrent.ForkJoinPool; import static io.github.skippyall.minions.Minions.LOGGER; public class MinionProfileUtils { + public static final String PREFIX = "#"; + public static CompletableFuture<@Nullable GameProfile> lookupSkinOwnerProfile(MinecraftServer server, String username) { CompletableFuture future = new CompletableFuture<>(); @@ -60,20 +66,31 @@ public class MinionProfileUtils { return future; } - public static GameProfile makeNewMinionProfile(@Nullable UUID uuidMinion, String username, @Nullable GameProfile skinProfile) { + public static GameProfile makeNewMinionProfile(UUID uuidMinion, String username, PropertyMap skin) { if(uuidMinion == null) { uuidMinion = UUID.randomUUID(); } - MinionPersistentState.INSTANCE.addMinionUUID(uuidMinion); GameProfile newProfile = new GameProfile(uuidMinion, username); - if (skinProfile != null) { - newProfile.getProperties().putAll(skinProfile.getProperties()); + if (skin != null) { + newProfile.getProperties().putAll(skin); } LOGGER.info("Minion Profile: {}", newProfile); return newProfile; } + public static Result checkMinionName(String name) { + if(StringHelper.isValidPlayerName(PREFIX + name)) { + return new Result.Success<>(name); + } else { + return new Result.Error<>(Text.translatable("minions.generic.minion_name_too_long")); + } + } + + public static boolean isValidMinionName(String name) { + return checkMinionName(name).isSuccess(); + } + public static boolean isMinion(UUID uuid) { return MinionPersistentState.INSTANCE.isMinion(uuid); } diff --git a/src/main/java/io/github/skippyall/minions/minion/fakeplayer/EntityPlayerActionPack.java b/src/main/java/io/github/skippyall/minions/minion/fakeplayer/EntityPlayerActionPack.java index 87e47b4..fd1fddd 100644 --- a/src/main/java/io/github/skippyall/minions/minion/fakeplayer/EntityPlayerActionPack.java +++ b/src/main/java/io/github/skippyall/minions/minion/fakeplayer/EntityPlayerActionPack.java @@ -299,7 +299,7 @@ public class EntityPlayerActionPack @Override boolean execute(ServerPlayerEntity player, Action action) { - EntityPlayerActionPack ap = ((ServerPlayerInterface) player).getActionPack(); + EntityPlayerActionPack ap = ((ServerPlayerInterface) player).minions$getActionPack(); if (ap.itemUseCooldown > 0) { ap.itemUseCooldown--; @@ -368,7 +368,7 @@ public class EntityPlayerActionPack @Override void inactiveTick(ServerPlayerEntity player, Action action) { - EntityPlayerActionPack ap = ((ServerPlayerInterface) player).getActionPack(); + EntityPlayerActionPack ap = ((ServerPlayerInterface) player).minions$getActionPack(); ap.itemUseCooldown = 0; player.stopUsingItem(); } @@ -390,7 +390,7 @@ public class EntityPlayerActionPack return true; } case BLOCK: { - EntityPlayerActionPack ap = ((ServerPlayerInterface) player).getActionPack(); + EntityPlayerActionPack ap = ((ServerPlayerInterface) player).minions$getActionPack(); if (ap.blockHitDelay > 0) { ap.blockHitDelay--; @@ -461,7 +461,7 @@ public class EntityPlayerActionPack @Override void inactiveTick(ServerPlayerEntity player, Action action) { - EntityPlayerActionPack ap = ((ServerPlayerInterface) player).getActionPack(); + EntityPlayerActionPack ap = ((ServerPlayerInterface) player).minions$getActionPack(); if (ap.currentBlock == null) return; player.getWorld().setBlockBreakingInfo(-1, ap.currentBlock, -1); player.interactionManager.processBlockBreakingAction(ap.currentBlock, PlayerActionC2SPacket.Action.ABORT_DESTROY_BLOCK, Direction.DOWN, player.getWorld().getTopYInclusive(), -1); diff --git a/src/main/java/io/github/skippyall/minions/minion/fakeplayer/MinionFakePlayer.java b/src/main/java/io/github/skippyall/minions/minion/fakeplayer/MinionFakePlayer.java index 01bebf6..9295eba 100644 --- a/src/main/java/io/github/skippyall/minions/minion/fakeplayer/MinionFakePlayer.java +++ b/src/main/java/io/github/skippyall/minions/minion/fakeplayer/MinionFakePlayer.java @@ -2,7 +2,9 @@ package io.github.skippyall.minions.minion.fakeplayer; import com.mojang.authlib.GameProfile; +import com.mojang.authlib.properties.PropertyMap; import io.github.skippyall.minions.Minions; +import io.github.skippyall.minions.MinionsTickExecutor; import io.github.skippyall.minions.minion.MinionData; import io.github.skippyall.minions.gui.MinionGui; import io.github.skippyall.minions.minion.MinionItem; @@ -11,7 +13,7 @@ import io.github.skippyall.minions.minion.MinionProfileUtils; import io.github.skippyall.minions.gui.ModuleInventory; import io.github.skippyall.minions.program.runtime.MinionRuntime; import net.minecraft.block.BlockState; -import net.minecraft.component.DataComponentTypes; +import net.minecraft.block.EndPortalBlock; import net.minecraft.entity.Entity; import net.minecraft.entity.EquipmentSlot; import net.minecraft.entity.MovementType; @@ -43,12 +45,8 @@ import net.minecraft.world.GameMode; import net.minecraft.world.TeleportTarget; import org.jetbrains.annotations.Nullable; -import java.util.Optional; import java.util.Set; -import java.util.UUID; import java.util.concurrent.CompletableFuture; -import java.util.function.BiConsumer; -import java.util.function.Consumer; public class MinionFakePlayer extends ServerPlayerEntity { public Runnable fixStartingPosition = () -> {}; @@ -60,67 +58,43 @@ public class MinionFakePlayer extends ServerPlayerEntity { private final ModuleInventory moduleInventory = new ModuleInventory(); private final MinionRuntime runtime = new MinionRuntime(this); - private UUID skinUuid = null; + private MinionData data; - public static void createMinion(MinionData data, ServerWorld level, ServerPlayerEntity owner, boolean canProgram, Vec3d pos, Vec2f rot) { - spawnMinion(data, level, pos, rot, - (minion, skinProfile) -> { - if (skinProfile != null) { - minion.skinUuid = skinProfile.getId(); - } - - minion.programmable = canProgram; - }, - minion -> { - MinionPersistentState.INSTANCE.addMinion(minion); - } - ); - } - - public static void spawnMinionAt(MinionData data, ServerWorld level, @Nullable Vec3d pos, @Nullable Vec2f rot) { - spawnMinion(data, level, pos, rot, (minion, skinProfile) -> {}, minion -> {}); - } - - public static void spawnMinion(MinionData data, ServerWorld level, @Nullable Vec3d pos, @Nullable Vec2f rot, BiConsumer beforeSpawn, Consumer afterSpawn) { + public static void spawnMinion(MinionData data, ServerWorld level, @Nullable Vec3d pos, @Nullable Vec2f rot) { MinecraftServer server = level.getServer(); - CompletableFuture future; - if(data.skinUuid().isPresent()) { - future = MinionProfileUtils.getSkinOwnerProfile(server, data.skinUuid().get()); - } else { - future = MinionProfileUtils.lookupSkinOwnerProfile(server, data.name()); - } + CompletableFuture future = data.getSkin(server); - future.thenAccept((skinProfile) -> { - GameProfile profile = MinionProfileUtils.makeNewMinionProfile(data.uuid(), data.name(), skinProfile); - Minions.addExecuteOnNextTick(() -> { - MinionFakePlayer instance = new MinionFakePlayer(server, level, profile, SyncedClientOptions.createDefault()); - beforeSpawn.accept(instance, skinProfile); - - if(pos != null && rot != null) { - instance.fixStartingPosition = () -> instance.refreshPositionAndAngles(pos.x, pos.y, pos.z, rot.x, rot.y); - } - server.getPlayerManager().onPlayerConnect(new FakeClientConnection(NetworkSide.SERVERBOUND), instance, new ConnectedClientData(profile, 0, instance.getClientOptions(), false)); - System.out.println(instance.getPos()); - if(pos != null && rot != null) { - instance.teleport(level, pos.x, pos.y, pos.z, Set.of(), rot.x, rot.y, true); - } - instance.setVelocity(0,0,0); - instance.setHealth(20.0F); - instance.unsetRemoved(); - instance.getAttributeInstance(EntityAttributes.STEP_HEIGHT).setBaseValue(0.6F); - instance.interactionManager.changeGameMode(GameMode.SURVIVAL); - server.getPlayerManager().sendToDimension(new EntitySetHeadYawS2CPacket(instance, (byte) (instance.headYaw * 256 / 360)), level.getRegistryKey());//instance.dimension); - server.getPlayerManager().sendToDimension(EntityPositionSyncS2CPacket.create(instance), level.getRegistryKey());//instance.dimension); - //instance.world.getChunkManager(). updatePosition(instance); - instance.dataTracker.set(PLAYER_MODEL_PARTS, (byte) 0x7f); // show all model layers (incl. capes) - instance.getAbilities().flying = false; - - afterSpawn.accept(instance); - }); + future.thenAccept((skin) -> { + GameProfile profile = MinionProfileUtils.makeNewMinionProfile(data.uuid(), data.name(), skin); + MinionsTickExecutor.addExecuteOnNextTick(() -> doSpawn(data, profile, server, level, pos, rot)); }); } + private static void doSpawn(MinionData data, GameProfile profile, MinecraftServer server, ServerWorld level, @Nullable Vec3d pos, @Nullable Vec2f rot) { + + MinionFakePlayer instance = new MinionFakePlayer(server, level, profile, SyncedClientOptions.createDefault()); + + if(pos != null && rot != null) { + instance.fixStartingPosition = () -> instance.refreshPositionAndAngles(pos.x, pos.y, pos.z, rot.x, rot.y); + } + server.getPlayerManager().onPlayerConnect(new FakeClientConnection(NetworkSide.SERVERBOUND), instance, new ConnectedClientData(profile, 0, instance.getClientOptions(), false)); + System.out.println(instance.getPos()); + if(pos != null && rot != null) { + instance.teleport(level, pos.x, pos.y, pos.z, Set.of(), rot.x, rot.y, true); + } + instance.setVelocity(0,0,0); + instance.setHealth(20.0F); + instance.unsetRemoved(); + instance.getAttributeInstance(EntityAttributes.STEP_HEIGHT).setBaseValue(0.6F); + instance.interactionManager.changeGameMode(GameMode.SURVIVAL); + server.getPlayerManager().sendToDimension(new EntitySetHeadYawS2CPacket(instance, (byte) (instance.headYaw * 256 / 360)), level.getRegistryKey());//instance.dimension); + server.getPlayerManager().sendToDimension(EntityPositionSyncS2CPacket.create(instance), level.getRegistryKey());//instance.dimension); + //instance.world.getChunkManager(). updatePosition(instance); + instance.dataTracker.set(PLAYER_MODEL_PARTS, (byte) 0x7f); // show all model layers (incl. capes) + instance.getAbilities().flying = false; + } + public static MinionFakePlayer respawnFake(MinecraftServer server, ServerWorld level, GameProfile profile, SyncedClientOptions cli) { return new MinionFakePlayer(server, level, profile, cli); @@ -148,7 +122,7 @@ public class MinionFakePlayer extends ServerPlayerEntity { } public EntityPlayerActionPack getMinionActionPack() { - return ((ServerPlayerInterface)this).getActionPack(); + return ((ServerPlayerInterface)this).minions$getActionPack(); } @Override @@ -188,7 +162,9 @@ public class MinionFakePlayer extends ServerPlayerEntity { })); } - MinionPersistentState.INSTANCE.removeMinion(this); + data.withSpawned(false); + + MinionPersistentState.INSTANCE.updateMinionData(data); } @Override @@ -311,10 +287,7 @@ public class MinionFakePlayer extends ServerPlayerEntity { private ItemStack toItemStack() { ItemStack stack = new ItemStack(Minions.MINION_ITEM); - MinionItem.setData(MinionData.fromMinion(this), stack); - if (!getMinionName().equals("Minion")) { - stack.set(DataComponentTypes.CUSTOM_NAME, Text.of(getMinionName())); - } + MinionItem.setData(data, stack); return stack; } @@ -331,12 +304,4 @@ public class MinionFakePlayer extends ServerPlayerEntity { moduleInventory.readNbt(nbt.getCompound("modules"), getRegistryManager()); programmable = nbt.getBoolean("programmable"); } - - public String getMinionName() { - return getGameProfile().getName(); - } - - public Optional getSkinUuid() { - return Optional.ofNullable(skinUuid); - } } \ No newline at end of file diff --git a/src/main/java/io/github/skippyall/minions/minion/fakeplayer/ServerPlayerInterface.java b/src/main/java/io/github/skippyall/minions/minion/fakeplayer/ServerPlayerInterface.java index 7862f74..dcbd1d6 100644 --- a/src/main/java/io/github/skippyall/minions/minion/fakeplayer/ServerPlayerInterface.java +++ b/src/main/java/io/github/skippyall/minions/minion/fakeplayer/ServerPlayerInterface.java @@ -3,5 +3,5 @@ package io.github.skippyall.minions.minion.fakeplayer; public interface ServerPlayerInterface { - EntityPlayerActionPack getActionPack(); + EntityPlayerActionPack minions$getActionPack(); } diff --git a/src/main/java/io/github/skippyall/minions/minion/skin/CachedSkinProvider.java b/src/main/java/io/github/skippyall/minions/minion/skin/CachedSkinProvider.java new file mode 100644 index 0000000..bb3f61d --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/minion/skin/CachedSkinProvider.java @@ -0,0 +1,41 @@ +package io.github.skippyall.minions.minion.skin; + +import com.mojang.authlib.properties.PropertyMap; +import net.minecraft.server.MinecraftServer; +import org.jetbrains.annotations.Nullable; + +import java.util.concurrent.CompletableFuture; + +public abstract class CachedSkinProvider implements SkinProvider { + private @Nullable PropertyMap cache = null; + + protected CachedSkinProvider(@Nullable PropertyMap cache) { + this.cache = cache; + } + + public abstract CompletableFuture<@Nullable PropertyMap> fetchSkin(MinecraftServer server); + + public @Nullable PropertyMap getCache() { + return cache; + } + + public CompletableFuture<@Nullable PropertyMap> updateCache(MinecraftServer server) { + CompletableFuture future = fetchSkin(server); + future.thenAccept(result -> { + this.cache = result; + }); + return future; + } + + @Override + public CompletableFuture<@Nullable PropertyMap> getSkin(MinecraftServer server) { + CompletableFuture future = new CompletableFuture<>(); + if(cache == null) { + fetchSkin(server).thenAccept(skin -> { + cache = skin; + future.complete(skin); + }); + } + return CompletableFuture.completedFuture(cache); + } +} diff --git a/src/main/java/io/github/skippyall/minions/minion/skin/DirectSkinProvider.java b/src/main/java/io/github/skippyall/minions/minion/skin/DirectSkinProvider.java new file mode 100644 index 0000000..1c5a6a2 --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/minion/skin/DirectSkinProvider.java @@ -0,0 +1,33 @@ +package io.github.skippyall.minions.minion.skin; + +import com.mojang.authlib.properties.PropertyMap; +import com.mojang.serialization.Codec; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.dynamic.Codecs; +import org.jetbrains.annotations.Nullable; + +import java.util.concurrent.CompletableFuture; + +public class DirectSkinProvider implements SkinProvider { + private final PropertyMap propertyMap; + + public static final Codec CODEC = Codecs.GAME_PROFILE_PROPERTY_MAP.xmap(DirectSkinProvider::new, DirectSkinProvider::getPropertyMap); + + public DirectSkinProvider(PropertyMap propertyMap) { + this.propertyMap = propertyMap; + } + + public PropertyMap getPropertyMap() { + return propertyMap; + } + + @Override + public CompletableFuture<@Nullable PropertyMap> getSkin(MinecraftServer server) { + return CompletableFuture.completedFuture(propertyMap); + } + + @Override + public Codec getCodec() { + return null; + } +} diff --git a/src/main/java/io/github/skippyall/minions/minion/skin/NameSkinProvider.java b/src/main/java/io/github/skippyall/minions/minion/skin/NameSkinProvider.java new file mode 100644 index 0000000..cc65161 --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/minion/skin/NameSkinProvider.java @@ -0,0 +1,43 @@ +package io.github.skippyall.minions.minion.skin; + +import com.mojang.authlib.properties.PropertyMap; +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import io.github.skippyall.minions.minion.MinionProfileUtils; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.dynamic.Codecs; +import org.jetbrains.annotations.Nullable; + +import java.util.concurrent.CompletableFuture; + +public class NameSkinProvider extends CachedSkinProvider { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + Codec.STRING.fieldOf("name").forGetter(NameSkinProvider::getName), + Codecs.GAME_PROFILE_PROPERTY_MAP.optionalFieldOf("cache", null).forGetter(NameSkinProvider::getCache) + ).apply(instance, NameSkinProvider::new)); + + private final String name; + + public NameSkinProvider(String name, @Nullable PropertyMap cache) { + super(cache); + this.name = name; + } + + public NameSkinProvider(String name) { + this(name, null); + } + + public String getName() { + return name; + } + + @Override + public CompletableFuture fetchSkin(MinecraftServer server) { + return MinionProfileUtils.lookupSkinOwnerProfile(server, name).thenApply(gameProfile -> gameProfile != null ? gameProfile.getProperties() : null); + } + + @Override + public Codec getCodec() { + return null; + } +} diff --git a/src/main/java/io/github/skippyall/minions/minion/skin/SkinProvider.java b/src/main/java/io/github/skippyall/minions/minion/skin/SkinProvider.java new file mode 100644 index 0000000..f65d208 --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/minion/skin/SkinProvider.java @@ -0,0 +1,14 @@ +package io.github.skippyall.minions.minion.skin; + +import com.mojang.authlib.properties.PropertyMap; +import com.mojang.serialization.Codec; +import net.minecraft.server.MinecraftServer; +import org.jetbrains.annotations.Nullable; + +import java.util.concurrent.CompletableFuture; + +public interface SkinProvider { + CompletableFuture<@Nullable PropertyMap> getSkin(MinecraftServer server); + + Codec getCodec(); +} diff --git a/src/main/java/io/github/skippyall/minions/minion/skin/SkinProviders.java b/src/main/java/io/github/skippyall/minions/minion/skin/SkinProviders.java new file mode 100644 index 0000000..6164061 --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/minion/skin/SkinProviders.java @@ -0,0 +1,26 @@ +package io.github.skippyall.minions.minion.skin; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.Lifecycle; +import io.github.skippyall.minions.Minions; +import net.minecraft.registry.Registry; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.SimpleRegistry; +import net.minecraft.util.Identifier; + +public class SkinProviders { + public static final Registry> REGISTRY = new SimpleRegistry<>(RegistryKey.ofRegistry(Identifier.of(Minions.MOD_ID, "skin_providers")), Lifecycle.stable()); + + public static final Codec CODEC = REGISTRY.getCodec().dispatchStable(SkinProvider::getCodec, codec -> codec.fieldOf("data")); + + public static > T register(T skinProvider, Identifier id) { + Registry.register(REGISTRY, id, skinProvider); + return skinProvider; + } + + public static void register() { + register(UUIDSkinProvider.CODEC, Identifier.of(Minions.MOD_ID, "uuid")); + register(NameSkinProvider.CODEC, Identifier.of(Minions.MOD_ID, "name")); + register(DirectSkinProvider.CODEC, Identifier.of(Minions.MOD_ID, "direct")); + } +} diff --git a/src/main/java/io/github/skippyall/minions/minion/skin/UUIDSkinProvider.java b/src/main/java/io/github/skippyall/minions/minion/skin/UUIDSkinProvider.java new file mode 100644 index 0000000..e51d0e1 --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/minion/skin/UUIDSkinProvider.java @@ -0,0 +1,51 @@ +package io.github.skippyall.minions.minion.skin; + +import com.mojang.authlib.properties.PropertyMap; +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import io.github.skippyall.minions.minion.MinionProfileUtils; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.Uuids; +import net.minecraft.util.dynamic.Codecs; + +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +public class UUIDSkinProvider extends CachedSkinProvider { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> + instance.group( + Codecs.GAME_PROFILE_PROPERTY_MAP.optionalFieldOf("cache", null).forGetter(CachedSkinProvider::getCache), + Uuids.CODEC.fieldOf("uuid").forGetter(UUIDSkinProvider::getUuid) + ).apply(instance, UUIDSkinProvider::new)); + + private final UUID uuid; + + public UUIDSkinProvider(UUID uuid) { + this(null, uuid); + } + + public UUIDSkinProvider(PropertyMap cache, UUID uuid) { + super(cache); + this.uuid = uuid; + } + + public UUID getUuid() { + return uuid; + } + + @Override + public CompletableFuture fetchSkin(MinecraftServer server) { + return MinionProfileUtils.getSkinOwnerProfile(server, uuid).thenApply(gameProfile -> { + if (gameProfile != null) { + return gameProfile.getProperties(); + } else { + return null; + } + }); + } + + @Override + public Codec getCodec() { + return CODEC; + } +} diff --git a/src/main/java/io/github/skippyall/minions/mixinhelpers/EntityViewMixinHelper.java b/src/main/java/io/github/skippyall/minions/mixinhelpers/EntityViewMixinHelper.java new file mode 100644 index 0000000..f0da900 --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/mixinhelpers/EntityViewMixinHelper.java @@ -0,0 +1,9 @@ +package io.github.skippyall.minions.mixinhelpers; + +import net.minecraft.entity.Entity; + +import java.util.function.Predicate; + +public class EntityViewMixinHelper { + public static final ThreadLocal> ADDITIONAL_PREDICATE = ThreadLocal.withInitial(() -> entity -> true); +} diff --git a/src/main/java/io/github/skippyall/minions/mixins/EntityViewMixin.java b/src/main/java/io/github/skippyall/minions/mixins/EntityViewMixin.java new file mode 100644 index 0000000..22e55cf --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/mixins/EntityViewMixin.java @@ -0,0 +1,24 @@ +package io.github.skippyall.minions.mixins; + +import io.github.skippyall.minions.mixinhelpers.EntityViewMixinHelper; +import net.minecraft.entity.Entity; +import net.minecraft.world.EntityView; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyArg; + +import java.util.function.Predicate; + +@Mixin(EntityView.class) +public class EntityViewMixin { + @ModifyArg(method = "getClosestPlayer(DDDDZ)Lnet/minecraft/entity/player/PlayerEntity;", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/EntityView;getClosestPlayer(DDDDLjava/util/function/Predicate;)Lnet/minecraft/entity/player/PlayerEntity;")) + private @Nullable Predicate addMinionPredicate(@Nullable Predicate targetPredicate) { + Predicate predicate = EntityViewMixinHelper.ADDITIONAL_PREDICATE.get(); + if(targetPredicate != null) { + return predicate.and(targetPredicate); + } else { + return predicate; + } + } +} diff --git a/src/main/java/io/github/skippyall/minions/mixins/MobEntityMixin.java b/src/main/java/io/github/skippyall/minions/mixins/MobEntityMixin.java index 245eefe..def074b 100644 --- a/src/main/java/io/github/skippyall/minions/mixins/MobEntityMixin.java +++ b/src/main/java/io/github/skippyall/minions/mixins/MobEntityMixin.java @@ -1,29 +1,36 @@ package io.github.skippyall.minions.mixins; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer; +import io.github.skippyall.minions.mixinhelpers.EntityViewMixinHelper; import io.github.skippyall.minions.module.MobSpawningModule; import net.minecraft.entity.Entity; import net.minecraft.entity.mob.MobEntity; import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtCompound; import net.minecraft.predicate.entity.EntityPredicates; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.world.World; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Redirect; @Mixin(MobEntity.class) public abstract class MobEntityMixin { - @Redirect(method = "checkDespawn", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/World;getClosestPlayer(Lnet/minecraft/entity/Entity;D)Lnet/minecraft/entity/player/PlayerEntity;")) - public PlayerEntity checkMobDespawningMinion(World instance, Entity entity, double maxDistance) { - return instance.getClosestPlayer(entity.getX(), entity.getY(), entity.getZ(), maxDistance, EntityPredicates.EXCEPT_SPECTATOR.and(entity1 -> { - if(entity1 instanceof ServerPlayerEntity player) { - if(player instanceof MinionFakePlayer minion) { - return MobSpawningModule.canMinionDespawnMobs(minion); - } + @WrapOperation(method = "checkDespawn", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/World;getClosestPlayer(Lnet/minecraft/entity/Entity;D)Lnet/minecraft/entity/player/PlayerEntity;")) + public PlayerEntity checkMobDespawningMinion(World instance, Entity entity, double maxDistance, Operation original) { + EntityViewMixinHelper.ADDITIONAL_PREDICATE.set(entity2 -> { + if(entity2 instanceof MinionFakePlayer minion) { + return MobSpawningModule.canMinionDespawnMobs(minion); + } else { return true; } - return false; - })); + }); + PlayerEntity player = original.call(instance, entity, maxDistance); + EntityViewMixinHelper.ADDITIONAL_PREDICATE.remove(); + return player; } } diff --git a/src/main/java/io/github/skippyall/minions/mixins/ServerPlayerMixin.java b/src/main/java/io/github/skippyall/minions/mixins/ServerPlayerMixin.java index be5db1e..9bc2c61 100644 --- a/src/main/java/io/github/skippyall/minions/mixins/ServerPlayerMixin.java +++ b/src/main/java/io/github/skippyall/minions/mixins/ServerPlayerMixin.java @@ -18,7 +18,7 @@ public abstract class ServerPlayerMixin implements ServerPlayerInterface { @Unique public EntityPlayerActionPack actionPack; @Override - public EntityPlayerActionPack getActionPack() + public EntityPlayerActionPack minions$getActionPack() { return actionPack; } diff --git a/src/main/java/io/github/skippyall/minions/module/ActionModules.java b/src/main/java/io/github/skippyall/minions/module/ActionModules.java index e331520..2d90f9a 100644 --- a/src/main/java/io/github/skippyall/minions/module/ActionModules.java +++ b/src/main/java/io/github/skippyall/minions/module/ActionModules.java @@ -3,9 +3,9 @@ package io.github.skippyall.minions.module; import eu.pb4.sgui.api.elements.GuiElementBuilder; import eu.pb4.sgui.api.gui.SimpleGui; import io.github.skippyall.minions.command.CommandExecutor; +import io.github.skippyall.minions.input.TextInput; import io.github.skippyall.minions.minion.fakeplayer.EntityPlayerActionPack; import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer; -import io.github.skippyall.minions.input.TextInput; import net.minecraft.item.Items; import net.minecraft.screen.ScreenHandlerType; import net.minecraft.server.network.ServerPlayerEntity; diff --git a/src/main/java/io/github/skippyall/minions/module/MoveModule.java b/src/main/java/io/github/skippyall/minions/module/MoveModule.java index b3a2d0e..9fb99c3 100644 --- a/src/main/java/io/github/skippyall/minions/module/MoveModule.java +++ b/src/main/java/io/github/skippyall/minions/module/MoveModule.java @@ -39,13 +39,35 @@ public class MoveModule { .thenAccept(degrees -> minion.getMinionActionPack().turn(-degrees, 0)) ); + public static final SimpleCommand TURN_UP_COMMAND = + new SimpleCommand( + Text.literal("Turn Up"), + Text.literal("Turn a specific amount of degrees up"), + Items.COMPASS, + (player, minion) -> TextInput.inputFloat(player, Text.literal("Degrees"), "90") + .thenAccept(degrees -> minion.getMinionActionPack().turn(0, -degrees)) + ); + + public static final SimpleCommand TURN_DOWN_COMMAND = + new SimpleCommand( + Text.literal("Turn Down"), + Text.literal("Turn a specific amount of degrees down"), + Items.COMPASS, + (player, minion) -> TextInput.inputFloat(player, Text.literal("Degrees"), "90") + .thenAccept(degrees -> minion.getMinionActionPack().turn(0, degrees)) + ); + + + public static final SimpleModuleItem MOVE_MODULE = register(Identifier.of(Minions.MOD_ID, "move_module"), List.of(), List.of( WALK_COMMAND, TURN_RIGHT_COMMAND, - TURN_LEFT_COMMAND + TURN_LEFT_COMMAND, + TURN_UP_COMMAND, + TURN_DOWN_COMMAND ), Items.IRON_BOOTS ); diff --git a/src/main/java/io/github/skippyall/minions/program/block/GoBlock.java b/src/main/java/io/github/skippyall/minions/program/block/GoBlock.java index ace8dbb..80a5078 100644 --- a/src/main/java/io/github/skippyall/minions/program/block/GoBlock.java +++ b/src/main/java/io/github/skippyall/minions/program/block/GoBlock.java @@ -19,7 +19,7 @@ public class GoBlock extends CodeBlock> { public Void execute(ProgramRuntime runtime, Tuple2 args, Statement>.Run run) { if(runtime instanceof MinionRuntime minionRuntime) { MinionFakePlayer minion = minionRuntime.getMinion(); - EntityPlayerActionPack action = ((ServerPlayerInterface) minion).getActionPack(); + EntityPlayerActionPack action = ((ServerPlayerInterface) minion).minions$getActionPack(); minion.moveForward(args.v0()); minion.moveSideways(args.v1()); } diff --git a/src/main/resources/data/minions/lang/en_us.json b/src/main/resources/data/minions/lang/en_us.json index 26946c7..2ce3c2d 100644 --- a/src/main/resources/data/minions/lang/en_us.json +++ b/src/main/resources/data/minions/lang/en_us.json @@ -7,6 +7,7 @@ "minions.gui.module_commands.title": "Commands", "minions.gui.commands.title": "%s's Commands", "minions.gui.modules.title": "%s's Modules", + "minions.gui.ok": "OK", "minions.command.input.int.fail": "Not an integer", "minions.command.input.float.fail": "Not a number", diff --git a/src/main/resources/minions.mixins.json b/src/main/resources/minions.mixins.json index 0246eee..931fd72 100644 --- a/src/main/resources/minions.mixins.json +++ b/src/main/resources/minions.mixins.json @@ -7,6 +7,7 @@ "ChunkTicketManagerFixMixin", "ConnectionMixin", "EntityAccessor", + "EntityViewMixin", "MinecraftServerMixin", "MobEntityMixin", "PlayerListEntryS2CPacket$EntryMixin", From cc69b22abad66755c04695fb22dc9264b89fe3b8 Mon Sep 17 00:00:00 2001 From: skippyall <> Date: Sat, 12 Apr 2025 19:08:25 +0200 Subject: [PATCH 2/5] Turn Up/Down & Grave Compat --- build.gradle | 14 ++++++++++++ gradle.properties | 2 ++ .../minions/mixins/GraveCompatMixin.java | 20 +++++++++++++++++ .../skippyall/minions/module/MoveModule.java | 22 ++++++++++++++++++- src/main/resources/minions.mixins.json | 1 + 5 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 src/main/java/io/github/skippyall/minions/mixins/GraveCompatMixin.java diff --git a/build.gradle b/build.gradle index 6a5f567..a8396df 100644 --- a/build.gradle +++ b/build.gradle @@ -17,6 +17,18 @@ repositories { // See https://docs.gradle.org/current/userguide/declaring_repositories.html // for more information about repositories. maven { url 'https://maven.nucleoid.xyz' } + + exclusiveContent { + forRepository { + maven { + name = "Modrinth" + url = "https://api.modrinth.com/maven" + } + } + filter { + includeGroup "maven.modrinth" + } + } } dependencies { @@ -30,6 +42,8 @@ dependencies { modImplementation "eu.pb4:polymer-core:${project.polymer_version}" modImplementation include("eu.pb4:sgui:${project.sgui_version}") modImplementation include("xyz.nucleoid:server-translations-api:${project.server_translations_version}") + + modCompileOnly "maven.modrinth:universal-graves:${project.universal_graves_version}" } processResources { diff --git a/gradle.properties b/gradle.properties index 8fa819a..b72282b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -19,3 +19,5 @@ org.gradle.jvmargs=-Xmx1G polymer_version=0.10.0+1.21.2 sgui_version=1.7.1+1.21.2 server_translations_version=2.4.0+1.21.2-rc1 + + universal_graves_version=3.5.0+1.21.2 diff --git a/src/main/java/io/github/skippyall/minions/mixins/GraveCompatMixin.java b/src/main/java/io/github/skippyall/minions/mixins/GraveCompatMixin.java new file mode 100644 index 0000000..02a9d7f --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/mixins/GraveCompatMixin.java @@ -0,0 +1,20 @@ +package io.github.skippyall.minions.mixins; + +import com.llamalad7.mixinextras.sugar.Local; +import eu.pb4.graves.grave.Grave; +import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer; +import net.minecraft.server.network.ServerPlayerEntity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyArg; + +@Mixin(Grave.class) +public class GraveCompatMixin { + @ModifyArg(method = "createBlock", at = @At(value = "INVOKE", target = "Leu/pb4/graves/grave/Grave;(JLcom/mojang/authlib/GameProfile;BLnet/minecraft/util/Arm;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/util/Identifier;Leu/pb4/graves/grave/GraveType;JJILnet/minecraft/text/Text;Ljava/util/Collection;Ljava/util/Collection;ZI)V")) + private static boolean createGrave(boolean profile, @Local(argsOnly = true) ServerPlayerEntity player) { + if(player instanceof MinionFakePlayer) { + return false; + } + return profile; + } +} diff --git a/src/main/java/io/github/skippyall/minions/module/MoveModule.java b/src/main/java/io/github/skippyall/minions/module/MoveModule.java index b3a2d0e..4a974df 100644 --- a/src/main/java/io/github/skippyall/minions/module/MoveModule.java +++ b/src/main/java/io/github/skippyall/minions/module/MoveModule.java @@ -39,13 +39,33 @@ public class MoveModule { .thenAccept(degrees -> minion.getMinionActionPack().turn(-degrees, 0)) ); + public static final SimpleCommand TURN_UP_COMMAND = + new SimpleCommand( + Text.literal("Turn Up"), + Text.literal("Turn a specific amount of degrees up"), + Items.COMPASS, + (player, minion) -> TextInput.inputFloat(player, Text.literal("Degrees"), "90") + .thenAccept(degrees -> minion.getMinionActionPack().turn(0, -degrees)) + ); + + public static final SimpleCommand TURN_DOWN_COMMAND = + new SimpleCommand( + Text.literal("Turn Down"), + Text.literal("Turn a specific amount of degrees down"), + Items.COMPASS, + (player, minion) -> TextInput.inputFloat(player, Text.literal("Degrees"), "90") + .thenAccept(degrees -> minion.getMinionActionPack().turn(0, degrees)) + ); + public static final SimpleModuleItem MOVE_MODULE = register(Identifier.of(Minions.MOD_ID, "move_module"), List.of(), List.of( WALK_COMMAND, TURN_RIGHT_COMMAND, - TURN_LEFT_COMMAND + TURN_LEFT_COMMAND, + TURN_UP_COMMAND, + TURN_DOWN_COMMAND ), Items.IRON_BOOTS ); diff --git a/src/main/resources/minions.mixins.json b/src/main/resources/minions.mixins.json index 0246eee..a511ffc 100644 --- a/src/main/resources/minions.mixins.json +++ b/src/main/resources/minions.mixins.json @@ -7,6 +7,7 @@ "ChunkTicketManagerFixMixin", "ConnectionMixin", "EntityAccessor", + "GraveCompatMixin", "MinecraftServerMixin", "MobEntityMixin", "PlayerListEntryS2CPacket$EntryMixin", From 08e7952e34a491cdaa29a4f72ea2fb1784ef3322 Mon Sep 17 00:00:00 2001 From: skippyall <> Date: Mon, 14 Apr 2025 21:45:04 +0200 Subject: [PATCH 3/5] Mob Cap fix --- build.gradle | 4 + gradle.properties | 2 +- .../io/github/skippyall/minions/Minions.java | 6 ++ .../skippyall/minions/MobCapCommand.java | 24 ++++++ ...tanceFromNearestPlayerTrackerAccessor.java | 7 ++ .../ChunkTicketManagerAccessor.java | 11 +++ .../ChunkPosDistanceLevelPropagatorMixin.java | 14 +++ ...DistanceFromNearestPlayerTrackerMixin.java | 85 +++++++++++++++++++ .../antimobcap/ChunkTicketManagerMixin.java | 40 +++++++++ .../ServerChunkManagerAccessor.java | 12 +++ .../antimobcap/ServerChunkManagerMixin.java | 23 +++++ src/main/resources/fabric.mod.json | 1 + src/main/resources/minions.accesswidener | 4 + src/main/resources/minions.mixins.json | 7 +- 14 files changed, 238 insertions(+), 2 deletions(-) create mode 100644 src/main/java/io/github/skippyall/minions/MobCapCommand.java create mode 100644 src/main/java/io/github/skippyall/minions/mixinhelper/ChunkTicketManager$DistanceFromNearestPlayerTrackerAccessor.java create mode 100644 src/main/java/io/github/skippyall/minions/mixinhelper/ChunkTicketManagerAccessor.java create mode 100644 src/main/java/io/github/skippyall/minions/mixins/antimobcap/ChunkPosDistanceLevelPropagatorMixin.java create mode 100644 src/main/java/io/github/skippyall/minions/mixins/antimobcap/ChunkTicketManager$DistanceFromNearestPlayerTrackerMixin.java create mode 100644 src/main/java/io/github/skippyall/minions/mixins/antimobcap/ChunkTicketManagerMixin.java create mode 100644 src/main/java/io/github/skippyall/minions/mixins/antimobcap/ServerChunkManagerAccessor.java create mode 100644 src/main/java/io/github/skippyall/minions/mixins/antimobcap/ServerChunkManagerMixin.java create mode 100644 src/main/resources/minions.accesswidener diff --git a/build.gradle b/build.gradle index a8396df..308e56b 100644 --- a/build.gradle +++ b/build.gradle @@ -6,6 +6,10 @@ plugins { version = project.mod_version group = project.maven_group +loom { + accessWidenerPath = file("src/main/resources/minions.accesswidener") +} + base { archivesName = project.archives_base_name } diff --git a/gradle.properties b/gradle.properties index b72282b..3231941 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,7 +4,7 @@ org.gradle.jvmargs=-Xmx1G # Fabric Properties # check these on https://modmuss50.me/fabric.html minecraft_version=1.21.3 - loader_version=0.16.7 + loader_version=0.16.13 yarn_mappings=1.21.3+build.2 # Mod Properties diff --git a/src/main/java/io/github/skippyall/minions/Minions.java b/src/main/java/io/github/skippyall/minions/Minions.java index 203a692..ba7510d 100644 --- a/src/main/java/io/github/skippyall/minions/Minions.java +++ b/src/main/java/io/github/skippyall/minions/Minions.java @@ -8,6 +8,7 @@ import io.github.skippyall.minions.minion.MinionItem; import io.github.skippyall.minions.minion.MinionPersistentState; import io.github.skippyall.minions.module.Modules; import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; import net.minecraft.component.DataComponentTypes; @@ -21,6 +22,7 @@ import net.minecraft.registry.Registry; import net.minecraft.registry.RegistryKey; import net.minecraft.registry.RegistryKeys; import net.minecraft.registry.tag.TagKey; +import net.minecraft.server.world.ChunkTicketManager; import net.minecraft.util.Identifier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -59,6 +61,10 @@ public class Minions implements ModInitializer { }); }); + CommandRegistrationCallback.EVENT.register((commandDispatcher, commandRegistryAccess, registrationEnvironment) -> { + MobCapCommand.registerCommand(commandDispatcher); + }); + Modules.register(); } diff --git a/src/main/java/io/github/skippyall/minions/MobCapCommand.java b/src/main/java/io/github/skippyall/minions/MobCapCommand.java new file mode 100644 index 0000000..6a7fa1e --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/MobCapCommand.java @@ -0,0 +1,24 @@ +package io.github.skippyall.minions; + +import com.mojang.brigadier.CommandDispatcher; +import io.github.skippyall.minions.mixinhelper.ChunkTicketManager$DistanceFromNearestPlayerTrackerAccessor; +import io.github.skippyall.minions.mixinhelper.ChunkTicketManagerAccessor; +import io.github.skippyall.minions.mixins.antimobcap.ServerChunkManagerAccessor; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.server.world.ChunkTicketManager; +import net.minecraft.text.Text; + +import static net.minecraft.server.command.CommandManager.literal; + +public class MobCapCommand { + public static void registerCommand(CommandDispatcher dispatcher) { + dispatcher.register(literal("mobcapdebug") + .executes(context -> { + ChunkTicketManager ticketManager = ((ServerChunkManagerAccessor)context.getSource().getWorld().getChunkManager()).getTicketManager(); + int tickedChunkCount = ((ChunkTicketManager$DistanceFromNearestPlayerTrackerAccessor)((ChunkTicketManagerAccessor)ticketManager).getMinionless()).minions$getTickedChunkCount(); + context.getSource().sendFeedback(() -> Text.of(String.valueOf(tickedChunkCount)), false); + return 0; + }) + ); + } +} diff --git a/src/main/java/io/github/skippyall/minions/mixinhelper/ChunkTicketManager$DistanceFromNearestPlayerTrackerAccessor.java b/src/main/java/io/github/skippyall/minions/mixinhelper/ChunkTicketManager$DistanceFromNearestPlayerTrackerAccessor.java new file mode 100644 index 0000000..8ca9405 --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/mixinhelper/ChunkTicketManager$DistanceFromNearestPlayerTrackerAccessor.java @@ -0,0 +1,7 @@ +package io.github.skippyall.minions.mixinhelper; + +public interface ChunkTicketManager$DistanceFromNearestPlayerTrackerAccessor { + void minions$markAsMinionless(); + void minions$markAsTarget(); + int minions$getTickedChunkCount(); +} diff --git a/src/main/java/io/github/skippyall/minions/mixinhelper/ChunkTicketManagerAccessor.java b/src/main/java/io/github/skippyall/minions/mixinhelper/ChunkTicketManagerAccessor.java new file mode 100644 index 0000000..c6282e1 --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/mixinhelper/ChunkTicketManagerAccessor.java @@ -0,0 +1,11 @@ +package io.github.skippyall.minions.mixinhelper; + +import it.unimi.dsi.fastutil.objects.ObjectSet; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ChunkTicketManager; + +public interface ChunkTicketManagerAccessor { + ObjectSet getPlayers(long chunkpos); + + ChunkTicketManager.DistanceFromNearestPlayerTracker getMinionless(); +} diff --git a/src/main/java/io/github/skippyall/minions/mixins/antimobcap/ChunkPosDistanceLevelPropagatorMixin.java b/src/main/java/io/github/skippyall/minions/mixins/antimobcap/ChunkPosDistanceLevelPropagatorMixin.java new file mode 100644 index 0000000..9d45022 --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/mixins/antimobcap/ChunkPosDistanceLevelPropagatorMixin.java @@ -0,0 +1,14 @@ +package io.github.skippyall.minions.mixins.antimobcap; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(net.minecraft.world.ChunkPosDistanceLevelPropagator.class) +public class ChunkPosDistanceLevelPropagatorMixin { + @Inject(method = "updateLevel", at = @At("HEAD")) + public void minions$updateLevel(long chunkPos, int distance, boolean decrease, CallbackInfo info) { + + } +} diff --git a/src/main/java/io/github/skippyall/minions/mixins/antimobcap/ChunkTicketManager$DistanceFromNearestPlayerTrackerMixin.java b/src/main/java/io/github/skippyall/minions/mixins/antimobcap/ChunkTicketManager$DistanceFromNearestPlayerTrackerMixin.java new file mode 100644 index 0000000..184ab00 --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/mixins/antimobcap/ChunkTicketManager$DistanceFromNearestPlayerTrackerMixin.java @@ -0,0 +1,85 @@ +package io.github.skippyall.minions.mixins.antimobcap; + +import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer; +import io.github.skippyall.minions.mixinhelper.ChunkTicketManager$DistanceFromNearestPlayerTrackerAccessor; +import io.github.skippyall.minions.mixinhelper.ChunkTicketManagerAccessor; +import io.github.skippyall.minions.module.MobSpawningModule; +import it.unimi.dsi.fastutil.longs.Long2ByteMap; +import it.unimi.dsi.fastutil.objects.ObjectSet; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ChunkTicketManager; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(value = ChunkTicketManager.DistanceFromNearestPlayerTracker.class) +public abstract class ChunkTicketManager$DistanceFromNearestPlayerTrackerMixin extends ChunkPosDistanceLevelPropagatorMixin implements ChunkTicketManager$DistanceFromNearestPlayerTrackerAccessor { + @Final + @Shadow + ChunkTicketManager field_17462; + + @Shadow + @Final + protected Long2ByteMap distanceFromNearestPlayer; + + @Shadow protected abstract boolean isPlayerInChunk(long chunkPos); + + @Unique + boolean minionless, target; + + @Inject(method = "isPlayerInChunk", at = @At("RETURN"), cancellable = true) + public void filterMinions(long chunkPos, CallbackInfoReturnable cir) { + if (minionless) { + cir.setReturnValue(isRealPlayerInChunk(chunkPos)); + } + } + + @Unique + public boolean isRealPlayerInChunk(long chunkPos) { + ObjectSet players = ((ChunkTicketManagerAccessor)field_17462).getPlayers(chunkPos); + boolean contains = false; + if(players != null) { + contains = players.stream().anyMatch(player -> { + if (player instanceof MinionFakePlayer minion) { + return MobSpawningModule.canMinionSpawnMobs(minion); + } + return true; + }); + } + return contains; + } + + @Inject(method = "updateLevels", at = @At("HEAD")) + public void sync(CallbackInfo info) { + if (target) { + ((ChunkTicketManagerAccessor)field_17462).getMinionless().updateLevels(); + } + } + + @Override + public void minions$updateLevel(long chunkPos, int distance, boolean decrease, CallbackInfo info) { + if (target && (distance != 0 || isRealPlayerInChunk(chunkPos))) { + ((ChunkTicketManagerAccessor)field_17462).getMinionless().updateLevel(chunkPos, distance, decrease); + } + } + + @Override + public void minions$markAsMinionless() { + minionless = true; + } + + @Override + public void minions$markAsTarget() { + target = true; + } + + @Override + public int minions$getTickedChunkCount() { + return distanceFromNearestPlayer.size(); + } +} diff --git a/src/main/java/io/github/skippyall/minions/mixins/antimobcap/ChunkTicketManagerMixin.java b/src/main/java/io/github/skippyall/minions/mixins/antimobcap/ChunkTicketManagerMixin.java new file mode 100644 index 0000000..2512a22 --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/mixins/antimobcap/ChunkTicketManagerMixin.java @@ -0,0 +1,40 @@ +package io.github.skippyall.minions.mixins.antimobcap; + +import io.github.skippyall.minions.mixinhelper.ChunkTicketManager$DistanceFromNearestPlayerTrackerAccessor; +import io.github.skippyall.minions.mixinhelper.ChunkTicketManagerAccessor; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.objects.ObjectSet; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ChunkTicketManager; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.concurrent.Executor; + +@Mixin(ChunkTicketManager.class) +public class ChunkTicketManagerMixin implements ChunkTicketManagerAccessor { + @Shadow @Final private Long2ObjectMap> playersByChunkPos; + @Shadow @Final private ChunkTicketManager.DistanceFromNearestPlayerTracker distanceFromNearestPlayerTracker; + ChunkTicketManager.DistanceFromNearestPlayerTracker minionless; + + @Inject(method = "", at = @At("RETURN")) + public void createMinionlessClone(Executor workerExecutor, Executor mainThreadExecutor, CallbackInfo ci) { + ChunkTicketManager manager = ((ChunkTicketManager)(Object)this); + minionless = manager.new DistanceFromNearestPlayerTracker(8); + ((ChunkTicketManager$DistanceFromNearestPlayerTrackerAccessor)minionless).minions$markAsMinionless(); + ((ChunkTicketManager$DistanceFromNearestPlayerTrackerAccessor)distanceFromNearestPlayerTracker).minions$markAsTarget(); + } + + public ObjectSet getPlayers(long chunkpos) { + return playersByChunkPos.get(chunkpos); + } + + @Override + public ChunkTicketManager.DistanceFromNearestPlayerTracker getMinionless() { + return minionless; + } +} diff --git a/src/main/java/io/github/skippyall/minions/mixins/antimobcap/ServerChunkManagerAccessor.java b/src/main/java/io/github/skippyall/minions/mixins/antimobcap/ServerChunkManagerAccessor.java new file mode 100644 index 0000000..b03d5fa --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/mixins/antimobcap/ServerChunkManagerAccessor.java @@ -0,0 +1,12 @@ +package io.github.skippyall.minions.mixins.antimobcap; + +import net.minecraft.server.world.ChunkTicketManager; +import net.minecraft.server.world.ServerChunkManager; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(ServerChunkManager.class) +public interface ServerChunkManagerAccessor { + @Accessor + ChunkTicketManager getTicketManager(); +} diff --git a/src/main/java/io/github/skippyall/minions/mixins/antimobcap/ServerChunkManagerMixin.java b/src/main/java/io/github/skippyall/minions/mixins/antimobcap/ServerChunkManagerMixin.java new file mode 100644 index 0000000..160bceb --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/mixins/antimobcap/ServerChunkManagerMixin.java @@ -0,0 +1,23 @@ +package io.github.skippyall.minions.mixins.antimobcap; + +import io.github.skippyall.minions.mixinhelper.ChunkTicketManager$DistanceFromNearestPlayerTrackerAccessor; +import io.github.skippyall.minions.mixinhelper.ChunkTicketManagerAccessor; +import net.minecraft.server.world.ChunkTicketManager; +import net.minecraft.server.world.ServerChunkManager; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyArg; + +@Mixin(ServerChunkManager.class) +public class ServerChunkManagerMixin { + @Shadow + @Final + private ChunkTicketManager ticketManager; + + @ModifyArg(method = "tickChunks(Lnet/minecraft/util/profiler/Profiler;JLjava/util/List;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/SpawnHelper;setupSpawn(ILjava/lang/Iterable;Lnet/minecraft/world/SpawnHelper$ChunkSource;Lnet/minecraft/world/SpawnDensityCapper;)Lnet/minecraft/world/SpawnHelper$Info;")) + public int useMinionless(int spawningChunkCount) { + return ((ChunkTicketManager$DistanceFromNearestPlayerTrackerAccessor)((ChunkTicketManagerAccessor)ticketManager).getMinionless()).minions$getTickedChunkCount(); + } +} diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 49c1084..480a527 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -19,6 +19,7 @@ "mixins": [ "minions.mixins.json" ], + "accessWidener" : "minions.accesswidener", "depends": { "fabricloader": "*", "fabric": "*", diff --git a/src/main/resources/minions.accesswidener b/src/main/resources/minions.accesswidener new file mode 100644 index 0000000..0ffdeca --- /dev/null +++ b/src/main/resources/minions.accesswidener @@ -0,0 +1,4 @@ +accessWidener v2 named + +accessible class net/minecraft/server/world/ChunkTicketManager$DistanceFromNearestPlayerTracker +accessible method net/minecraft/server/world/ChunkTicketManager$DistanceFromNearestPlayerTracker (Lnet/minecraft/server/world/ChunkTicketManager;I)V \ No newline at end of file diff --git a/src/main/resources/minions.mixins.json b/src/main/resources/minions.mixins.json index a511ffc..5b22369 100644 --- a/src/main/resources/minions.mixins.json +++ b/src/main/resources/minions.mixins.json @@ -15,7 +15,12 @@ "ServerPlayerMixin", "ServerPlayNetworkHandlerMixin", "SleepManagerMixin", - "SpawnHelperMixin" + "SpawnHelperMixin", + "antimobcap.ChunkPosDistanceLevelPropagatorMixin", + "antimobcap.ChunkTicketManager$DistanceFromNearestPlayerTrackerMixin", + "antimobcap.ChunkTicketManagerMixin", + "antimobcap.ServerChunkManagerAccessor", + "antimobcap.ServerChunkManagerMixin" ], "client": [], "server": [], From d2d96b3f67a81a149b00212f2270c93487ab7ed8 Mon Sep 17 00:00:00 2001 From: ErfinderLabyrinth Date: Wed, 16 Apr 2025 15:29:16 +0200 Subject: [PATCH 4/5] Bugfixes (Minion Mobcap) --- ...tanceFromNearestPlayerTrackerAccessor.java | 2 ++ ...DistanceFromNearestPlayerTrackerMixin.java | 22 +++++++++---------- .../antimobcap/ChunkTicketManagerMixin.java | 9 ++++++++ 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/main/java/io/github/skippyall/minions/mixinhelper/ChunkTicketManager$DistanceFromNearestPlayerTrackerAccessor.java b/src/main/java/io/github/skippyall/minions/mixinhelper/ChunkTicketManager$DistanceFromNearestPlayerTrackerAccessor.java index 8ca9405..b4fbb8c 100644 --- a/src/main/java/io/github/skippyall/minions/mixinhelper/ChunkTicketManager$DistanceFromNearestPlayerTrackerAccessor.java +++ b/src/main/java/io/github/skippyall/minions/mixinhelper/ChunkTicketManager$DistanceFromNearestPlayerTrackerAccessor.java @@ -4,4 +4,6 @@ public interface ChunkTicketManager$DistanceFromNearestPlayerTrackerAccessor { void minions$markAsMinionless(); void minions$markAsTarget(); int minions$getTickedChunkCount(); + + boolean minions$isRealPlayerInChunk(long chunkPos); } diff --git a/src/main/java/io/github/skippyall/minions/mixins/antimobcap/ChunkTicketManager$DistanceFromNearestPlayerTrackerMixin.java b/src/main/java/io/github/skippyall/minions/mixins/antimobcap/ChunkTicketManager$DistanceFromNearestPlayerTrackerMixin.java index 184ab00..d540e27 100644 --- a/src/main/java/io/github/skippyall/minions/mixins/antimobcap/ChunkTicketManager$DistanceFromNearestPlayerTrackerMixin.java +++ b/src/main/java/io/github/skippyall/minions/mixins/antimobcap/ChunkTicketManager$DistanceFromNearestPlayerTrackerMixin.java @@ -30,17 +30,17 @@ public abstract class ChunkTicketManager$DistanceFromNearestPlayerTrackerMixin e @Shadow protected abstract boolean isPlayerInChunk(long chunkPos); @Unique - boolean minionless, target; + boolean minions$minionless, minions$target; @Inject(method = "isPlayerInChunk", at = @At("RETURN"), cancellable = true) - public void filterMinions(long chunkPos, CallbackInfoReturnable cir) { - if (minionless) { - cir.setReturnValue(isRealPlayerInChunk(chunkPos)); + public void minions$filterMinions(long chunkPos, CallbackInfoReturnable cir) { + if (minions$minionless) { + cir.setReturnValue(minions$isRealPlayerInChunk(chunkPos)); } } - @Unique - public boolean isRealPlayerInChunk(long chunkPos) { + @Override + public boolean minions$isRealPlayerInChunk(long chunkPos) { ObjectSet players = ((ChunkTicketManagerAccessor)field_17462).getPlayers(chunkPos); boolean contains = false; if(players != null) { @@ -55,27 +55,27 @@ public abstract class ChunkTicketManager$DistanceFromNearestPlayerTrackerMixin e } @Inject(method = "updateLevels", at = @At("HEAD")) - public void sync(CallbackInfo info) { - if (target) { + public void minions$sync(CallbackInfo info) { + if (minions$target) { ((ChunkTicketManagerAccessor)field_17462).getMinionless().updateLevels(); } } @Override public void minions$updateLevel(long chunkPos, int distance, boolean decrease, CallbackInfo info) { - if (target && (distance != 0 || isRealPlayerInChunk(chunkPos))) { + if (minions$target && (distance == Integer.MAX_VALUE || minions$isRealPlayerInChunk(chunkPos))) { ((ChunkTicketManagerAccessor)field_17462).getMinionless().updateLevel(chunkPos, distance, decrease); } } @Override public void minions$markAsMinionless() { - minionless = true; + minions$minionless = true; } @Override public void minions$markAsTarget() { - target = true; + minions$target = true; } @Override diff --git a/src/main/java/io/github/skippyall/minions/mixins/antimobcap/ChunkTicketManagerMixin.java b/src/main/java/io/github/skippyall/minions/mixins/antimobcap/ChunkTicketManagerMixin.java index 2512a22..6ca5362 100644 --- a/src/main/java/io/github/skippyall/minions/mixins/antimobcap/ChunkTicketManagerMixin.java +++ b/src/main/java/io/github/skippyall/minions/mixins/antimobcap/ChunkTicketManagerMixin.java @@ -1,11 +1,13 @@ package io.github.skippyall.minions.mixins.antimobcap; +import com.llamalad7.mixinextras.sugar.Local; import io.github.skippyall.minions.mixinhelper.ChunkTicketManager$DistanceFromNearestPlayerTrackerAccessor; import io.github.skippyall.minions.mixinhelper.ChunkTicketManagerAccessor; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.objects.ObjectSet; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.world.ChunkTicketManager; +import net.minecraft.util.math.ChunkSectionPos; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -37,4 +39,11 @@ public class ChunkTicketManagerMixin implements ChunkTicketManagerAccessor { public ChunkTicketManager.DistanceFromNearestPlayerTracker getMinionless() { return minionless; } + + @Inject(method = "handleChunkLeave", at = @At(value = "INVOKE", target = "Lit/unimi/dsi/fastutil/objects/ObjectSet;remove(Ljava/lang/Object;)Z", shift = At.Shift.AFTER, remap = false)) + public void minion$updateMinionlessIfNoMinionInChunk(ChunkSectionPos pos, ServerPlayerEntity player, CallbackInfo ci, @Local long chunk) { + if (!((ChunkTicketManager$DistanceFromNearestPlayerTrackerAccessor)minionless).minions$isRealPlayerInChunk(chunk)) { + minionless.updateLevel(chunk, Integer.MAX_VALUE, false); + } + } } From 19318480681c20f5bcf266132fa14bf377c535a5 Mon Sep 17 00:00:00 2001 From: skippyall <> Date: Thu, 24 Apr 2025 22:09:38 +0200 Subject: [PATCH 5/5] Skin Improvements --- build.gradle | 4 + gradle.properties | 2 +- .../skippyall/minions/gui/CommandsGui.java | 2 +- .../skippyall/minions/gui/MinionLookGui.java | 49 +++++++---- .../minions/gui/ModuleInventory.java | 2 +- .../skippyall/minions/input/TextInput.java | 45 ++++++---- .../skippyall/minions/minion/MinionData.java | 21 ++--- .../skippyall/minions/minion/MinionItem.java | 18 ++++ .../minions/minion/MinionPersistentState.java | 41 ++------- .../minions/minion/MinionProfileUtils.java | 83 +++++++------------ .../minion/fakeplayer/MinionFakePlayer.java | 29 ++++--- .../minion/skin/Base64SkinProvider.java | 27 ++++++ .../minion/skin/CachedSkinProvider.java | 41 --------- .../minion/skin/DirectSkinProvider.java | 33 -------- .../minions/minion/skin/NameSkinProvider.java | 46 ++++------ .../minions/minion/skin/SkinProvider.java | 10 +-- .../minions/minion/skin/SkinProviders.java | 17 ++-- .../minions/minion/skin/UUIDSkinProvider.java | 47 ++++------- .../minions/mixins/EntityViewMixin.java | 2 +- .../minions/mixins/PlayerListMixin.java | 2 +- .../minions/module/ActionModules.java | 2 +- .../minions/module/AttackModule.java | 2 +- .../skippyall/minions/module/ChatModule.java | 2 +- .../minions/module/InteractModule.java | 2 +- .../skippyall/minions/module/ModuleItem.java | 2 +- .../skippyall/minions/module/Modules.java | 2 +- .../skippyall/minions/module/MountModule.java | 2 +- .../skippyall/minions/module/MoveModule.java | 2 +- .../minions/module/SimpleModuleItem.java | 2 +- .../minions/{ => module}/command/Command.java | 2 +- .../{ => module}/command/CommandExecutor.java | 2 +- .../{ => module}/command/SimpleCommand.java | 2 +- .../resources/data/minions/lang/en_us.json | 13 ++- 33 files changed, 235 insertions(+), 323 deletions(-) create mode 100644 src/main/java/io/github/skippyall/minions/minion/skin/Base64SkinProvider.java delete mode 100644 src/main/java/io/github/skippyall/minions/minion/skin/CachedSkinProvider.java delete mode 100644 src/main/java/io/github/skippyall/minions/minion/skin/DirectSkinProvider.java rename src/main/java/io/github/skippyall/minions/{ => module}/command/Command.java (79%) rename src/main/java/io/github/skippyall/minions/{ => module}/command/CommandExecutor.java (81%) rename src/main/java/io/github/skippyall/minions/{ => module}/command/SimpleCommand.java (95%) diff --git a/build.gradle b/build.gradle index 6a5f567..4bc5e93 100644 --- a/build.gradle +++ b/build.gradle @@ -10,6 +10,10 @@ base { archivesName = project.archives_base_name } +loom { + accessWidenerPath = file("src/main/resources/minions.accesswidener") +} + repositories { // Add repositories to retrieve artifacts from in here. // You should only use this when depending on other mods because diff --git a/gradle.properties b/gradle.properties index 8fa819a..9244aa8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,7 +4,7 @@ org.gradle.jvmargs=-Xmx1G # Fabric Properties # check these on https://modmuss50.me/fabric.html minecraft_version=1.21.3 - loader_version=0.16.7 + loader_version=0.16.13 yarn_mappings=1.21.3+build.2 # Mod Properties diff --git a/src/main/java/io/github/skippyall/minions/gui/CommandsGui.java b/src/main/java/io/github/skippyall/minions/gui/CommandsGui.java index 68d47a9..743918b 100644 --- a/src/main/java/io/github/skippyall/minions/gui/CommandsGui.java +++ b/src/main/java/io/github/skippyall/minions/gui/CommandsGui.java @@ -2,7 +2,7 @@ package io.github.skippyall.minions.gui; import eu.pb4.sgui.api.elements.GuiElementBuilder; import eu.pb4.sgui.api.gui.SimpleGui; -import io.github.skippyall.minions.command.Command; +import io.github.skippyall.minions.module.command.Command; import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer; import io.github.skippyall.minions.module.ModuleItem; import net.minecraft.screen.ScreenHandlerType; diff --git a/src/main/java/io/github/skippyall/minions/gui/MinionLookGui.java b/src/main/java/io/github/skippyall/minions/gui/MinionLookGui.java index 0cdab2f..bbef5b0 100644 --- a/src/main/java/io/github/skippyall/minions/gui/MinionLookGui.java +++ b/src/main/java/io/github/skippyall/minions/gui/MinionLookGui.java @@ -6,8 +6,8 @@ import io.github.skippyall.minions.input.TextInput; import io.github.skippyall.minions.minion.MinionData; import io.github.skippyall.minions.minion.MinionItem; import io.github.skippyall.minions.minion.MinionProfileUtils; -import net.minecraft.block.PlayerSkullBlock; -import net.minecraft.block.entity.SkullBlockEntity; +import io.github.skippyall.minions.minion.skin.SkinProvider; +import io.github.skippyall.minions.minion.skin.SkinProviders; import net.minecraft.component.DataComponentTypes; import net.minecraft.component.type.ProfileComponent; import net.minecraft.item.ItemStack; @@ -20,10 +20,18 @@ import java.util.Optional; public class MinionLookGui extends SimpleGui { private ItemStack minionItem; + private SkinProvider currentSkinProvider; public MinionLookGui(ServerPlayerEntity player, ItemStack minionItem) { super(ScreenHandlerType.GENERIC_9X3, player, false); this.minionItem = minionItem; + this.currentSkinProvider = SkinProviders.NAME; + } + + public void update() { + updateName(); + updateSkin(); + updateSkinProvider(); } private void updateName() { @@ -37,45 +45,50 @@ public class MinionLookGui extends SimpleGui { } private void updateSkin() { - setSlot(16, new GuiElementBuilder() + GuiElementBuilder builder = new GuiElementBuilder() .setItem(Items.PLAYER_HEAD) - ); - - getData().getSkin(player.server).thenAccept(skin -> { - setSlot(16, new GuiElementBuilder() - .setItem(Items.PLAYER_HEAD) - .setComponent(DataComponentTypes.PROFILE, new ProfileComponent(Optional.empty(), Optional.empty(), skin)) - .setCallback(this::cycleSkinProvider) - ); - }); + .setCallback(() -> currentSkinProvider.openSkinMenu(player).thenAccept(skin -> { + MinionItem.setData(getData().withSkin(skin), minionItem); + })); + if(MinionItem.getData(minionItem) != null && MinionItem.getData(minionItem).skin().isPresent()) { + builder.setComponent(DataComponentTypes.PROFILE, new ProfileComponent(Optional.empty(), Optional.empty(), getData().skin().get())); + } + setSlot(16, builder); } private void cycleSkinProvider() { + int currentId = SkinProviders.SKIN_PROVIDERS.getRawId(currentSkinProvider); + currentId++; + if(SkinProviders.SKIN_PROVIDERS.size() == currentId) { + currentId = 0; + } + currentSkinProvider = SkinProviders.SKIN_PROVIDERS.get(currentId); + updateSkinProvider(); } private void updateSkinProvider() { setSlot(25, new GuiElementBuilder() .setItem(Items.GREEN_STAINED_GLASS_PANE) - .setComponent(DataComponentTypes.CUSTOM_NAME, Text.literal()) + .setComponent(DataComponentTypes.CUSTOM_NAME, currentSkinProvider.getDisplayName()) + .setCallback(this::cycleSkinProvider) ); - updateSkin(); } private MinionData getData() { - return MinionItem.getData(minionItem); + return MinionItem.getDataOrDefault(minionItem); } public static void open(ServerPlayerEntity player, ItemStack minionItem) { MinionLookGui gui = new MinionLookGui(player, minionItem); - + gui.update(); gui.open(); } public void openRenameGui(ServerPlayerEntity player, ItemStack minionItem) { - TextInput.input(player, Text.translatable("minions.gui.look.rename.title"), "Minion", MinionProfileUtils::checkMinionName) + TextInput.inputSync(player, Text.translatable("minions.gui.look.rename.title"), "Minion", MinionProfileUtils::checkMinionNameWithoutPrefix) .thenAccept(name -> { - MinionItem.setData(MinionItem.getDataOrDefault(minionItem).withName(name), minionItem); + MinionItem.setData(getData().withName(MinionProfileUtils.PREFIX + name), minionItem); open(); }); } diff --git a/src/main/java/io/github/skippyall/minions/gui/ModuleInventory.java b/src/main/java/io/github/skippyall/minions/gui/ModuleInventory.java index 470e80e..97eb479 100644 --- a/src/main/java/io/github/skippyall/minions/gui/ModuleInventory.java +++ b/src/main/java/io/github/skippyall/minions/gui/ModuleInventory.java @@ -1,6 +1,6 @@ package io.github.skippyall.minions.gui; -import io.github.skippyall.minions.command.Command; +import io.github.skippyall.minions.module.command.Command; import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer; import io.github.skippyall.minions.module.ModuleItem; import io.github.skippyall.minions.program.block.CodeBlock; diff --git a/src/main/java/io/github/skippyall/minions/input/TextInput.java b/src/main/java/io/github/skippyall/minions/input/TextInput.java index d92cb86..1ad03d5 100644 --- a/src/main/java/io/github/skippyall/minions/input/TextInput.java +++ b/src/main/java/io/github/skippyall/minions/input/TextInput.java @@ -9,6 +9,7 @@ import net.minecraft.text.Text; import java.util.concurrent.CompletableFuture; import java.util.function.Function; +import java.util.function.Predicate; public class TextInput extends AnvilInputGui { private final GuiElementBuilder valid = new GuiElementBuilder() @@ -18,11 +19,11 @@ public class TextInput extends AnvilInputGui { private final GuiElementBuilder invalid = new GuiElementBuilder() .setItem(Items.REDSTONE_BLOCK); - private final Function> parser; + private final Function>> parser; private final CompletableFuture future; private Result result; - public TextInput(ServerPlayerEntity player, Text title, String defaultValue, Function> parser, CompletableFuture future) { + public TextInput(ServerPlayerEntity player, Text title, String defaultValue, Function>> parser, CompletableFuture future) { super(player, false); setTitle(title); setDefaultInputValue(defaultValue); @@ -32,18 +33,26 @@ public class TextInput extends AnvilInputGui { updateConfirmButton(defaultValue); } - public static CompletableFuture input(ServerPlayerEntity player, Text title, String defaultValue, Function> parser) { + public static CompletableFuture inputSync(ServerPlayerEntity player, Text title, String defaultValue, Function> parser) { + return input(player, title, defaultValue, (String string) -> CompletableFuture.completedFuture(parser.apply(string))); + } + + public static CompletableFuture input(ServerPlayerEntity player, Text title, String defaultValue, Function>> parser) { CompletableFuture future = new CompletableFuture<>(); new TextInput<>(player, title, defaultValue, parser, future).open(); return future; } + public static CompletableFuture inputString(ServerPlayerEntity player, Text title, String defaultValue) { + return inputSync(player, title, defaultValue, Result.Success::new); + } + public static CompletableFuture inputInt(ServerPlayerEntity player, Text title, String defaultValue) { - return input(player, title, defaultValue, string -> Result.wrapCustomError(() -> Integer.valueOf(string), Text.translatable("minions.command.input.int.fail"))); + return inputSync(player, title, defaultValue, string -> Result.wrapCustomError(() -> Integer.valueOf(string), Text.translatable("minions.command.input.int.fail"))); } public static CompletableFuture inputFloat(ServerPlayerEntity player, Text title, String defaultValue) { - return input(player, title, defaultValue, string -> Result.wrapCustomError(() -> Float.valueOf(string), Text.translatable("minions.command.input.float.fail"))); + return inputSync(player, title, defaultValue, string -> Result.wrapCustomError(() -> Float.valueOf(string), Text.translatable("minions.command.input.float.fail"))); } @Override @@ -52,13 +61,15 @@ public class TextInput extends AnvilInputGui { } public void updateConfirmButton(String input) { - result = parser.apply(input); - if(result.isSuccess()) { - setSlot(AnvilScreenHandler.OUTPUT_ID, valid); - } else { - Text text = result.getErrorOrThrow(); - setSlot(AnvilScreenHandler.OUTPUT_ID, invalid.setName(text)); - } + parser.apply(input).thenAccept(result -> { + this.result = result; + if(result.isSuccess()) { + setSlot(AnvilScreenHandler.OUTPUT_ID, valid); + } else { + Text text = result.getErrorOrThrow(); + setSlot(AnvilScreenHandler.OUTPUT_ID, invalid.setName(text)); + } + }); } @Override @@ -69,9 +80,11 @@ public class TextInput extends AnvilInputGui { } public void onConfirm() { - result.ifSuccess(success -> { - future.complete(success); - close(); - }); + if(result != null) { + result.ifSuccess(success -> { + future.complete(success); + close(); + }); + } } } diff --git a/src/main/java/io/github/skippyall/minions/minion/MinionData.java b/src/main/java/io/github/skippyall/minions/minion/MinionData.java index c386766..35235e8 100644 --- a/src/main/java/io/github/skippyall/minions/minion/MinionData.java +++ b/src/main/java/io/github/skippyall/minions/minion/MinionData.java @@ -6,27 +6,25 @@ import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; import eu.pb4.polymer.core.api.other.PolymerComponent; import io.github.skippyall.minions.Minions; -import io.github.skippyall.minions.minion.skin.SkinProvider; -import io.github.skippyall.minions.minion.skin.SkinProviders; import net.minecraft.component.ComponentType; import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtOps; import net.minecraft.registry.Registries; import net.minecraft.registry.Registry; -import net.minecraft.server.MinecraftServer; import net.minecraft.util.Identifier; import net.minecraft.util.Uuids; +import net.minecraft.util.dynamic.Codecs; import org.jetbrains.annotations.Nullable; +import java.util.Optional; import java.util.UUID; -import java.util.concurrent.CompletableFuture; -public record MinionData(UUID uuid, String name, @Nullable SkinProvider skin, boolean isSpawned) { +public record MinionData(UUID uuid, String name, Optional skin, boolean isSpawned) { public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( Uuids.CODEC.fieldOf("uuid").forGetter(MinionData::uuid), Codec.STRING.fieldOf("name").forGetter(MinionData::name), - SkinProviders.CODEC.optionalFieldOf("skin", null).forGetter(MinionData::skin), + Codecs.GAME_PROFILE_PROPERTY_MAP.optionalFieldOf("skin").forGetter(MinionData::skin), Codec.BOOL.optionalFieldOf("isSpawned", false).forGetter(MinionData::isSpawned) ).apply(instance, MinionData::new) ); @@ -34,7 +32,7 @@ public record MinionData(UUID uuid, String name, @Nullable SkinProvider skin, bo public static final ComponentType COMPONENT = Registry.register(Registries.DATA_COMPONENT_TYPE, Identifier.of(Minions.MOD_ID, "minion_data"), ComponentType.builder().codec(Uuids.CODEC).build()); public static MinionData createDefault() { - return new MinionData(UUID.randomUUID(), "Minion", null, false); + return new MinionData(UUID.randomUUID(), MinionProfileUtils.newDefaultMinionName(), Optional.empty(), false); } public MinionData withUuid(UUID uuid) { @@ -45,17 +43,10 @@ public record MinionData(UUID uuid, String name, @Nullable SkinProvider skin, bo return new MinionData(uuid, name, skin, isSpawned); } - public MinionData withSkin(@Nullable SkinProvider skin) { + public MinionData withSkin(Optional skin) { return new MinionData(uuid, name, skin, isSpawned); } - public CompletableFuture<@Nullable PropertyMap> getSkin(MinecraftServer server) { - if(skin != null) { - return skin.getSkin(server); - } - return CompletableFuture.completedFuture(null); - } - public MinionData withSpawned(boolean isSpawned) { return new MinionData(uuid, name, skin, isSpawned); } diff --git a/src/main/java/io/github/skippyall/minions/minion/MinionItem.java b/src/main/java/io/github/skippyall/minions/minion/MinionItem.java index 3f10cb8..ca5bdbe 100644 --- a/src/main/java/io/github/skippyall/minions/minion/MinionItem.java +++ b/src/main/java/io/github/skippyall/minions/minion/MinionItem.java @@ -2,18 +2,23 @@ package io.github.skippyall.minions.minion; import eu.pb4.polymer.core.api.item.PolymerItem; import eu.pb4.polymer.core.api.item.PolymerItemUtils; +import io.github.skippyall.minions.gui.MinionLookGui; import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer; import net.minecraft.component.DataComponentTypes; +import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.item.ItemUsageContext; import net.minecraft.item.Items; import net.minecraft.item.tooltip.TooltipType; +import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.world.ServerWorld; import net.minecraft.text.Text; import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; import net.minecraft.util.Identifier; import net.minecraft.util.math.Vec2f; +import net.minecraft.world.World; import org.jetbrains.annotations.Nullable; import xyz.nucleoid.packettweaker.PacketContext; @@ -49,6 +54,17 @@ public class MinionItem extends Item implements PolymerItem { } } + @Override + public ActionResult use(World world, PlayerEntity user, Hand hand) { + if(user instanceof ServerPlayerEntity serverPlayer) { + ItemStack stack = user.getStackInHand(hand); + MinionLookGui.open(serverPlayer, stack); + return ActionResult.SUCCESS; + } + + return ActionResult.SUCCESS_SERVER; + } + @Override public ActionResult useOnBlock(ItemUsageContext context) { if(!context.getWorld().isClient) { @@ -61,6 +77,7 @@ public class MinionItem extends Item implements PolymerItem { public static void setData(MinionData data, ItemStack item) { item.set(MinionData.COMPONENT, data.uuid()); + MinionPersistentState.INSTANCE.updateMinionData(data); } @Nullable @@ -75,6 +92,7 @@ public class MinionItem extends Item implements PolymerItem { MinionData data = getData(item); if(data == null) { data = MinionData.createDefault(); + setData(data, item); } return data; } diff --git a/src/main/java/io/github/skippyall/minions/minion/MinionPersistentState.java b/src/main/java/io/github/skippyall/minions/minion/MinionPersistentState.java index 638dfbb..f84aa44 100644 --- a/src/main/java/io/github/skippyall/minions/minion/MinionPersistentState.java +++ b/src/main/java/io/github/skippyall/minions/minion/MinionPersistentState.java @@ -18,7 +18,6 @@ public class MinionPersistentState extends PersistentState { public static MinionPersistentState INSTANCE; private final Map minionData = new HashMap<>(); - //private final List minionUuids = new ArrayList<>(); @Override public NbtCompound writeNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registryLookup) { @@ -28,13 +27,6 @@ public class MinionPersistentState extends PersistentState { } nbt.put("minions", list); - /*NbtList uuids = new NbtList(); - for(UUID uuid : minionUuids) { - NbtCompound compound = new NbtCompound(); - compound.putUuid("uuid", uuid); - uuids.add(compound); - } - nbt.put("uuids", uuids);*/ return nbt; } @@ -43,39 +35,13 @@ public class MinionPersistentState extends PersistentState { MinionPersistentState instance = new MinionPersistentState(); for(NbtElement element : list) { if(element instanceof NbtCompound compound1) { - MinionData data = MinionData.readNbt((NbtCompound) element); + MinionData data = MinionData.readNbt(compound1); instance.minionData.put(data.uuid(), data); } } - - /*NbtList uuids = compound.getList("uuids", NbtElement.COMPOUND_TYPE); - for(NbtElement element : uuids) { - instance.minionUuids.add(((NbtCompound) element).getUuid("uuid")); - }*/ return instance; } - /*public void addMinionUUID(UUID uuid) { - if(!minionUuids.contains(uuid)) { - minionUuids.add(uuid); - } - }*/ - - public void addMinion(MinionData data) { - System.out.println("add Minion " + data.name()); - minionData.put(data.uuid(), data); - markDirty(); - } - - public void removeMinion(MinionData minionData) { - removeMinion(minionData.uuid()); - } - - public void removeMinion(UUID minionUUID) { - minionData.remove(minionUUID); - markDirty(); - } - public MinionData getMinionData(UUID uuid) { return minionData.get(uuid); } @@ -86,12 +52,17 @@ public class MinionPersistentState extends PersistentState { public void updateMinionData(MinionData data) { minionData.put(data.uuid(), data); + markDirty(); } public boolean isMinion(UUID uuid) { return minionData.containsKey(uuid); } + public boolean isMinionNameTaken(String name) { + return minionData.values().stream().anyMatch(data -> data.name().equals(name)); + } + public static void create(MinecraftServer server) { INSTANCE = server.getWorld(World.OVERWORLD).getPersistentStateManager().getOrCreate(TYPE, "minion"); } diff --git a/src/main/java/io/github/skippyall/minions/minion/MinionProfileUtils.java b/src/main/java/io/github/skippyall/minions/minion/MinionProfileUtils.java index 6fc51ef..799f0bd 100644 --- a/src/main/java/io/github/skippyall/minions/minion/MinionProfileUtils.java +++ b/src/main/java/io/github/skippyall/minions/minion/MinionProfileUtils.java @@ -4,7 +4,9 @@ import com.mojang.authlib.GameProfile; import com.mojang.authlib.ProfileLookupCallback; import com.mojang.authlib.properties.PropertyMap; import com.mojang.authlib.yggdrasil.ProfileResult; +import com.mojang.brigadier.StringReader; import io.github.skippyall.minions.input.Result; +import net.minecraft.block.entity.SkullBlockEntity; import net.minecraft.server.MinecraftServer; import net.minecraft.text.Text; import net.minecraft.util.StringHelper; @@ -17,54 +19,7 @@ import java.util.concurrent.ForkJoinPool; import static io.github.skippyall.minions.Minions.LOGGER; public class MinionProfileUtils { - public static final String PREFIX = "#"; - - public static CompletableFuture<@Nullable GameProfile> lookupSkinOwnerProfile(MinecraftServer server, String username) { - CompletableFuture future = new CompletableFuture<>(); - - ForkJoinPool.commonPool().execute(() -> { - try { - server.getGameProfileRepo().findProfilesByNames(new String[]{username}, new ProfileLookupCallback() { - @Override - public void onProfileLookupSucceeded(GameProfile found) { - LOGGER.info("SkinProfile: {}", found); - try { - getSkinOwnerProfile(server, found.getId()).thenAccept(future::complete); - } catch (Throwable ex) { - LOGGER.warn("Exception during Game Profile creation", ex); - } - } - - @Override - public void onProfileLookupFailed(String profileName, Exception exception) { - LOGGER.warn("Lookup Error: ", exception); - future.complete(null); - } - }); - } catch (Throwable e) { - LOGGER.warn("Failed to get UUID for username " + username, e); - future.complete(null); - } - }); - - return future; - } - - public static CompletableFuture<@Nullable GameProfile> getSkinOwnerProfile(MinecraftServer server, @Nullable UUID uuid) { - CompletableFuture future = new CompletableFuture<>(); - future.completeAsync(() -> { - GameProfile profile = null; - if(uuid != null) { - ProfileResult result = server.getSessionService().fetchProfile(uuid, true); - if (result != null) { - profile = result.profile(); - LOGGER.info("Full SkinProfile: {}", profile); - } - } - return profile; - }); - return future; - } + public static final String PREFIX = "+"; public static GameProfile makeNewMinionProfile(UUID uuidMinion, String username, PropertyMap skin) { if(uuidMinion == null) { @@ -79,16 +34,34 @@ public class MinionProfileUtils { return newProfile; } - public static Result checkMinionName(String name) { - if(StringHelper.isValidPlayerName(PREFIX + name)) { - return new Result.Success<>(name); - } else { - return new Result.Error<>(Text.translatable("minions.generic.minion_name_too_long")); + public static Result checkMinionNameWithoutPrefix(String name) { + for(char c : name.toCharArray()) { + if(!StringReader.isAllowedInUnquotedString(c)) { + return new Result.Error<>(Text.translatable("minions.generic.name.invalid_char")); + } } + + if((PREFIX + name).length() > 16) { + return new Result.Error<>(Text.translatable("minions.generic.name.too_long")); + } + + if(!StringHelper.isValidPlayerName(PREFIX + name)) { + return new Result.Error<>(Text.translatable("minions.generic.name.invalid")); + } + + if(MinionPersistentState.INSTANCE.isMinionNameTaken(PREFIX + name)) { + return new Result.Error<>(Text.translatable("minions.generic.name.taken")); + } + + return new Result.Success<>(name); } - public static boolean isValidMinionName(String name) { - return checkMinionName(name).isSuccess(); + public static String newDefaultMinionName() { + int i = 0; + while (MinionPersistentState.INSTANCE.isMinionNameTaken("+Minion" + i)) { + i++; + } + return "+Minion" + i; } public static boolean isMinion(UUID uuid) { diff --git a/src/main/java/io/github/skippyall/minions/minion/fakeplayer/MinionFakePlayer.java b/src/main/java/io/github/skippyall/minions/minion/fakeplayer/MinionFakePlayer.java index 9295eba..4c1986f 100644 --- a/src/main/java/io/github/skippyall/minions/minion/fakeplayer/MinionFakePlayer.java +++ b/src/main/java/io/github/skippyall/minions/minion/fakeplayer/MinionFakePlayer.java @@ -58,22 +58,22 @@ public class MinionFakePlayer extends ServerPlayerEntity { private final ModuleInventory moduleInventory = new ModuleInventory(); private final MinionRuntime runtime = new MinionRuntime(this); - private MinionData data; + private final MinionData data; public static void spawnMinion(MinionData data, ServerWorld level, @Nullable Vec3d pos, @Nullable Vec2f rot) { MinecraftServer server = level.getServer(); - CompletableFuture future = data.getSkin(server); + PropertyMap skin = data.skin().orElse(null); + + GameProfile profile = MinionProfileUtils.makeNewMinionProfile(data.uuid(), data.name(), skin); + doSpawn(data, profile, server, level, pos, rot); - future.thenAccept((skin) -> { - GameProfile profile = MinionProfileUtils.makeNewMinionProfile(data.uuid(), data.name(), skin); - MinionsTickExecutor.addExecuteOnNextTick(() -> doSpawn(data, profile, server, level, pos, rot)); - }); } private static void doSpawn(MinionData data, GameProfile profile, MinecraftServer server, ServerWorld level, @Nullable Vec3d pos, @Nullable Vec2f rot) { - MinionFakePlayer instance = new MinionFakePlayer(server, level, profile, SyncedClientOptions.createDefault()); + MinionFakePlayer instance = new MinionFakePlayer(server, level, profile, SyncedClientOptions.createDefault(), data); + MinionPersistentState.INSTANCE.updateMinionData(data.withSpawned(true)); if(pos != null && rot != null) { instance.fixStartingPosition = () -> instance.refreshPositionAndAngles(pos.x, pos.y, pos.z, rot.x, rot.y); @@ -95,14 +95,15 @@ public class MinionFakePlayer extends ServerPlayerEntity { instance.getAbilities().flying = false; } - public static MinionFakePlayer respawnFake(MinecraftServer server, ServerWorld level, GameProfile profile, SyncedClientOptions cli) + public static MinionFakePlayer respawnFake(MinecraftServer server, ServerWorld level, GameProfile profile, SyncedClientOptions cli, MinionData data) { - return new MinionFakePlayer(server, level, profile, cli); + return new MinionFakePlayer(server, level, profile, cli, data); } - private MinionFakePlayer(MinecraftServer server, ServerWorld worldIn, GameProfile profile, SyncedClientOptions cli) + private MinionFakePlayer(MinecraftServer server, ServerWorld worldIn, GameProfile profile, SyncedClientOptions cli, MinionData data) { super(server, worldIn, profile, cli); + this.data = data; } public boolean isProgrammable() { @@ -162,9 +163,7 @@ public class MinionFakePlayer extends ServerPlayerEntity { })); } - data.withSpawned(false); - - MinionPersistentState.INSTANCE.updateMinionData(data); + MinionPersistentState.INSTANCE.updateMinionData(data.withSpawned(false)); } @Override @@ -291,6 +290,10 @@ public class MinionFakePlayer extends ServerPlayerEntity { return stack; } + public MinionData getData() { + return data; + } + @Override public void writeCustomDataToNbt(NbtCompound nbt) { super.writeCustomDataToNbt(nbt); diff --git a/src/main/java/io/github/skippyall/minions/minion/skin/Base64SkinProvider.java b/src/main/java/io/github/skippyall/minions/minion/skin/Base64SkinProvider.java new file mode 100644 index 0000000..6251307 --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/minion/skin/Base64SkinProvider.java @@ -0,0 +1,27 @@ +package io.github.skippyall.minions.minion.skin; + +import com.mojang.authlib.properties.Property; +import com.mojang.authlib.properties.PropertyMap; +import io.github.skippyall.minions.input.TextInput; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.text.Text; + +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +public class Base64SkinProvider implements SkinProvider { + @Override + public CompletableFuture> openSkinMenu(ServerPlayerEntity player) { + return TextInput.inputString(player, Text.translatable("minions.gui.look.skin.base64.title"), "") + .thenApply(base64String -> { + PropertyMap map = new PropertyMap(); + map.put("textures", new Property("textures", base64String)); + return Optional.of(map); + }); + } + + @Override + public Text getDisplayName() { + return Text.translatable("minions.gui.look.skin.base64"); + } +} diff --git a/src/main/java/io/github/skippyall/minions/minion/skin/CachedSkinProvider.java b/src/main/java/io/github/skippyall/minions/minion/skin/CachedSkinProvider.java deleted file mode 100644 index bb3f61d..0000000 --- a/src/main/java/io/github/skippyall/minions/minion/skin/CachedSkinProvider.java +++ /dev/null @@ -1,41 +0,0 @@ -package io.github.skippyall.minions.minion.skin; - -import com.mojang.authlib.properties.PropertyMap; -import net.minecraft.server.MinecraftServer; -import org.jetbrains.annotations.Nullable; - -import java.util.concurrent.CompletableFuture; - -public abstract class CachedSkinProvider implements SkinProvider { - private @Nullable PropertyMap cache = null; - - protected CachedSkinProvider(@Nullable PropertyMap cache) { - this.cache = cache; - } - - public abstract CompletableFuture<@Nullable PropertyMap> fetchSkin(MinecraftServer server); - - public @Nullable PropertyMap getCache() { - return cache; - } - - public CompletableFuture<@Nullable PropertyMap> updateCache(MinecraftServer server) { - CompletableFuture future = fetchSkin(server); - future.thenAccept(result -> { - this.cache = result; - }); - return future; - } - - @Override - public CompletableFuture<@Nullable PropertyMap> getSkin(MinecraftServer server) { - CompletableFuture future = new CompletableFuture<>(); - if(cache == null) { - fetchSkin(server).thenAccept(skin -> { - cache = skin; - future.complete(skin); - }); - } - return CompletableFuture.completedFuture(cache); - } -} diff --git a/src/main/java/io/github/skippyall/minions/minion/skin/DirectSkinProvider.java b/src/main/java/io/github/skippyall/minions/minion/skin/DirectSkinProvider.java deleted file mode 100644 index 1c5a6a2..0000000 --- a/src/main/java/io/github/skippyall/minions/minion/skin/DirectSkinProvider.java +++ /dev/null @@ -1,33 +0,0 @@ -package io.github.skippyall.minions.minion.skin; - -import com.mojang.authlib.properties.PropertyMap; -import com.mojang.serialization.Codec; -import net.minecraft.server.MinecraftServer; -import net.minecraft.util.dynamic.Codecs; -import org.jetbrains.annotations.Nullable; - -import java.util.concurrent.CompletableFuture; - -public class DirectSkinProvider implements SkinProvider { - private final PropertyMap propertyMap; - - public static final Codec CODEC = Codecs.GAME_PROFILE_PROPERTY_MAP.xmap(DirectSkinProvider::new, DirectSkinProvider::getPropertyMap); - - public DirectSkinProvider(PropertyMap propertyMap) { - this.propertyMap = propertyMap; - } - - public PropertyMap getPropertyMap() { - return propertyMap; - } - - @Override - public CompletableFuture<@Nullable PropertyMap> getSkin(MinecraftServer server) { - return CompletableFuture.completedFuture(propertyMap); - } - - @Override - public Codec getCodec() { - return null; - } -} diff --git a/src/main/java/io/github/skippyall/minions/minion/skin/NameSkinProvider.java b/src/main/java/io/github/skippyall/minions/minion/skin/NameSkinProvider.java index cc65161..9b0d6a2 100644 --- a/src/main/java/io/github/skippyall/minions/minion/skin/NameSkinProvider.java +++ b/src/main/java/io/github/skippyall/minions/minion/skin/NameSkinProvider.java @@ -1,43 +1,25 @@ package io.github.skippyall.minions.minion.skin; +import com.mojang.authlib.GameProfile; import com.mojang.authlib.properties.PropertyMap; -import com.mojang.serialization.Codec; -import com.mojang.serialization.codecs.RecordCodecBuilder; -import io.github.skippyall.minions.minion.MinionProfileUtils; -import net.minecraft.server.MinecraftServer; -import net.minecraft.util.dynamic.Codecs; -import org.jetbrains.annotations.Nullable; +import io.github.skippyall.minions.input.TextInput; +import net.minecraft.block.entity.SkullBlockEntity; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.text.Text; +import java.util.Optional; import java.util.concurrent.CompletableFuture; -public class NameSkinProvider extends CachedSkinProvider { - public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( - Codec.STRING.fieldOf("name").forGetter(NameSkinProvider::getName), - Codecs.GAME_PROFILE_PROPERTY_MAP.optionalFieldOf("cache", null).forGetter(NameSkinProvider::getCache) - ).apply(instance, NameSkinProvider::new)); - - private final String name; - - public NameSkinProvider(String name, @Nullable PropertyMap cache) { - super(cache); - this.name = name; - } - - public NameSkinProvider(String name) { - this(name, null); - } - - public String getName() { - return name; +public class NameSkinProvider implements SkinProvider { + @Override + public CompletableFuture> openSkinMenu(ServerPlayerEntity player) { + return TextInput.inputString(player, Text.translatable("minions.gui.look.skin.name.title"), "") + .thenCompose(SkullBlockEntity::fetchProfileByName) + .thenApply(gameProfile -> gameProfile.map(GameProfile::getProperties)); } @Override - public CompletableFuture fetchSkin(MinecraftServer server) { - return MinionProfileUtils.lookupSkinOwnerProfile(server, name).thenApply(gameProfile -> gameProfile != null ? gameProfile.getProperties() : null); - } - - @Override - public Codec getCodec() { - return null; + public Text getDisplayName() { + return Text.translatable("minions.gui.look.skin.name"); } } diff --git a/src/main/java/io/github/skippyall/minions/minion/skin/SkinProvider.java b/src/main/java/io/github/skippyall/minions/minion/skin/SkinProvider.java index f65d208..9068ecc 100644 --- a/src/main/java/io/github/skippyall/minions/minion/skin/SkinProvider.java +++ b/src/main/java/io/github/skippyall/minions/minion/skin/SkinProvider.java @@ -1,14 +1,14 @@ package io.github.skippyall.minions.minion.skin; import com.mojang.authlib.properties.PropertyMap; -import com.mojang.serialization.Codec; -import net.minecraft.server.MinecraftServer; -import org.jetbrains.annotations.Nullable; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.text.Text; +import java.util.Optional; import java.util.concurrent.CompletableFuture; public interface SkinProvider { - CompletableFuture<@Nullable PropertyMap> getSkin(MinecraftServer server); + CompletableFuture> openSkinMenu(ServerPlayerEntity player); - Codec getCodec(); + Text getDisplayName(); } diff --git a/src/main/java/io/github/skippyall/minions/minion/skin/SkinProviders.java b/src/main/java/io/github/skippyall/minions/minion/skin/SkinProviders.java index 6164061..06f230d 100644 --- a/src/main/java/io/github/skippyall/minions/minion/skin/SkinProviders.java +++ b/src/main/java/io/github/skippyall/minions/minion/skin/SkinProviders.java @@ -1,6 +1,5 @@ package io.github.skippyall.minions.minion.skin; -import com.mojang.serialization.Codec; import com.mojang.serialization.Lifecycle; import io.github.skippyall.minions.Minions; import net.minecraft.registry.Registry; @@ -9,18 +8,18 @@ import net.minecraft.registry.SimpleRegistry; import net.minecraft.util.Identifier; public class SkinProviders { - public static final Registry> REGISTRY = new SimpleRegistry<>(RegistryKey.ofRegistry(Identifier.of(Minions.MOD_ID, "skin_providers")), Lifecycle.stable()); + public static final Registry SKIN_PROVIDERS = new SimpleRegistry<>(RegistryKey.ofRegistry(Identifier.of(Minions.MOD_ID, "skin_providers")), Lifecycle.stable()); - public static final Codec CODEC = REGISTRY.getCodec().dispatchStable(SkinProvider::getCodec, codec -> codec.fieldOf("data")); + public static NameSkinProvider NAME = register(new NameSkinProvider(), "name"); + public static UUIDSkinProvider UUID = register(new UUIDSkinProvider(), "uuid"); + public static Base64SkinProvider BASE64 = register(new Base64SkinProvider(), "base64"); - public static > T register(T skinProvider, Identifier id) { - Registry.register(REGISTRY, id, skinProvider); - return skinProvider; + + public static T register(T skinProvider, String path) { + return Registry.register(SKIN_PROVIDERS, Identifier.of(Minions.MOD_ID, path), skinProvider); } public static void register() { - register(UUIDSkinProvider.CODEC, Identifier.of(Minions.MOD_ID, "uuid")); - register(NameSkinProvider.CODEC, Identifier.of(Minions.MOD_ID, "name")); - register(DirectSkinProvider.CODEC, Identifier.of(Minions.MOD_ID, "direct")); + } } diff --git a/src/main/java/io/github/skippyall/minions/minion/skin/UUIDSkinProvider.java b/src/main/java/io/github/skippyall/minions/minion/skin/UUIDSkinProvider.java index e51d0e1..520957c 100644 --- a/src/main/java/io/github/skippyall/minions/minion/skin/UUIDSkinProvider.java +++ b/src/main/java/io/github/skippyall/minions/minion/skin/UUIDSkinProvider.java @@ -1,51 +1,32 @@ package io.github.skippyall.minions.minion.skin; +import com.mojang.authlib.GameProfile; import com.mojang.authlib.properties.PropertyMap; import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; +import io.github.skippyall.minions.input.TextInput; import io.github.skippyall.minions.minion.MinionProfileUtils; +import net.minecraft.block.entity.SkullBlockEntity; import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.text.Text; import net.minecraft.util.Uuids; import net.minecraft.util.dynamic.Codecs; +import java.util.Optional; import java.util.UUID; import java.util.concurrent.CompletableFuture; -public class UUIDSkinProvider extends CachedSkinProvider { - public static final Codec CODEC = RecordCodecBuilder.create(instance -> - instance.group( - Codecs.GAME_PROFILE_PROPERTY_MAP.optionalFieldOf("cache", null).forGetter(CachedSkinProvider::getCache), - Uuids.CODEC.fieldOf("uuid").forGetter(UUIDSkinProvider::getUuid) - ).apply(instance, UUIDSkinProvider::new)); - - private final UUID uuid; - - public UUIDSkinProvider(UUID uuid) { - this(null, uuid); - } - - public UUIDSkinProvider(PropertyMap cache, UUID uuid) { - super(cache); - this.uuid = uuid; - } - - public UUID getUuid() { - return uuid; +public class UUIDSkinProvider implements SkinProvider { + @Override + public CompletableFuture> openSkinMenu(ServerPlayerEntity player) { + return TextInput.inputString(player, Text.translatable("minions.gui.look.skin.uuid.title"), "") + .thenCompose(uuidString -> SkullBlockEntity.fetchProfileByUuid(UUID.fromString(uuidString))) + .thenApply(gameProfile -> gameProfile.map(GameProfile::getProperties)); } @Override - public CompletableFuture fetchSkin(MinecraftServer server) { - return MinionProfileUtils.getSkinOwnerProfile(server, uuid).thenApply(gameProfile -> { - if (gameProfile != null) { - return gameProfile.getProperties(); - } else { - return null; - } - }); - } - - @Override - public Codec getCodec() { - return CODEC; + public Text getDisplayName() { + return Text.translatable("minions.gui.look.skin.uuid"); } } diff --git a/src/main/java/io/github/skippyall/minions/mixins/EntityViewMixin.java b/src/main/java/io/github/skippyall/minions/mixins/EntityViewMixin.java index 22e55cf..2663699 100644 --- a/src/main/java/io/github/skippyall/minions/mixins/EntityViewMixin.java +++ b/src/main/java/io/github/skippyall/minions/mixins/EntityViewMixin.java @@ -11,7 +11,7 @@ import org.spongepowered.asm.mixin.injection.ModifyArg; import java.util.function.Predicate; @Mixin(EntityView.class) -public class EntityViewMixin { +public interface EntityViewMixin { @ModifyArg(method = "getClosestPlayer(DDDDZ)Lnet/minecraft/entity/player/PlayerEntity;", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/EntityView;getClosestPlayer(DDDDLjava/util/function/Predicate;)Lnet/minecraft/entity/player/PlayerEntity;")) private @Nullable Predicate addMinionPredicate(@Nullable Predicate targetPredicate) { Predicate predicate = EntityViewMixinHelper.ADDITIONAL_PREDICATE.get(); diff --git a/src/main/java/io/github/skippyall/minions/mixins/PlayerListMixin.java b/src/main/java/io/github/skippyall/minions/mixins/PlayerListMixin.java index e364629..8ff9d69 100644 --- a/src/main/java/io/github/skippyall/minions/mixins/PlayerListMixin.java +++ b/src/main/java/io/github/skippyall/minions/mixins/PlayerListMixin.java @@ -52,7 +52,7 @@ public class PlayerListMixin { @WrapOperation(method = "respawnPlayer", at = @At(value = "NEW", target = "(Lnet/minecraft/server/MinecraftServer;Lnet/minecraft/server/world/ServerWorld;Lcom/mojang/authlib/GameProfile;Lnet/minecraft/network/packet/c2s/common/SyncedClientOptions;)Lnet/minecraft/server/network/ServerPlayerEntity;")) public ServerPlayerEntity makePlayerForRespawn(MinecraftServer minecraftServer, ServerWorld serverLevel, GameProfile gameProfile, SyncedClientOptions clientInformation, Operation original, ServerPlayerEntity serverPlayer, boolean bl) { if (serverPlayer instanceof MinionFakePlayer minion) { - return MinionFakePlayer.respawnFake(minecraftServer, serverLevel, gameProfile, clientInformation); + return MinionFakePlayer.respawnFake(minecraftServer, serverLevel, gameProfile, clientInformation, minion.getData()); } return original.call(minecraftServer, serverLevel, gameProfile, clientInformation); } diff --git a/src/main/java/io/github/skippyall/minions/module/ActionModules.java b/src/main/java/io/github/skippyall/minions/module/ActionModules.java index 2d90f9a..6c13e7b 100644 --- a/src/main/java/io/github/skippyall/minions/module/ActionModules.java +++ b/src/main/java/io/github/skippyall/minions/module/ActionModules.java @@ -2,7 +2,7 @@ package io.github.skippyall.minions.module; import eu.pb4.sgui.api.elements.GuiElementBuilder; import eu.pb4.sgui.api.gui.SimpleGui; -import io.github.skippyall.minions.command.CommandExecutor; +import io.github.skippyall.minions.module.command.CommandExecutor; import io.github.skippyall.minions.input.TextInput; import io.github.skippyall.minions.minion.fakeplayer.EntityPlayerActionPack; import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer; diff --git a/src/main/java/io/github/skippyall/minions/module/AttackModule.java b/src/main/java/io/github/skippyall/minions/module/AttackModule.java index 7dc1557..5f18ff1 100644 --- a/src/main/java/io/github/skippyall/minions/module/AttackModule.java +++ b/src/main/java/io/github/skippyall/minions/module/AttackModule.java @@ -1,7 +1,7 @@ package io.github.skippyall.minions.module; import io.github.skippyall.minions.Minions; -import io.github.skippyall.minions.command.SimpleCommand; +import io.github.skippyall.minions.module.command.SimpleCommand; import io.github.skippyall.minions.minion.fakeplayer.EntityPlayerActionPack; import net.minecraft.item.Items; import net.minecraft.text.Text; diff --git a/src/main/java/io/github/skippyall/minions/module/ChatModule.java b/src/main/java/io/github/skippyall/minions/module/ChatModule.java index 6ea8d0c..a541598 100644 --- a/src/main/java/io/github/skippyall/minions/module/ChatModule.java +++ b/src/main/java/io/github/skippyall/minions/module/ChatModule.java @@ -1,7 +1,7 @@ package io.github.skippyall.minions.module; import io.github.skippyall.minions.Minions; -import io.github.skippyall.minions.command.SimpleCommand; +import io.github.skippyall.minions.module.command.SimpleCommand; import net.minecraft.item.Items; import net.minecraft.text.Text; import net.minecraft.util.Identifier; diff --git a/src/main/java/io/github/skippyall/minions/module/InteractModule.java b/src/main/java/io/github/skippyall/minions/module/InteractModule.java index 3bd9169..fc5aa3a 100644 --- a/src/main/java/io/github/skippyall/minions/module/InteractModule.java +++ b/src/main/java/io/github/skippyall/minions/module/InteractModule.java @@ -1,7 +1,7 @@ package io.github.skippyall.minions.module; import io.github.skippyall.minions.Minions; -import io.github.skippyall.minions.command.SimpleCommand; +import io.github.skippyall.minions.module.command.SimpleCommand; import io.github.skippyall.minions.minion.fakeplayer.EntityPlayerActionPack; import net.minecraft.item.Items; import net.minecraft.text.Text; diff --git a/src/main/java/io/github/skippyall/minions/module/ModuleItem.java b/src/main/java/io/github/skippyall/minions/module/ModuleItem.java index a2a21e1..b91091b 100644 --- a/src/main/java/io/github/skippyall/minions/module/ModuleItem.java +++ b/src/main/java/io/github/skippyall/minions/module/ModuleItem.java @@ -1,6 +1,6 @@ package io.github.skippyall.minions.module; -import io.github.skippyall.minions.command.Command; +import io.github.skippyall.minions.module.command.Command; import io.github.skippyall.minions.program.block.CodeBlock; import net.minecraft.item.ItemConvertible; diff --git a/src/main/java/io/github/skippyall/minions/module/Modules.java b/src/main/java/io/github/skippyall/minions/module/Modules.java index b6cbe34..1e465b2 100644 --- a/src/main/java/io/github/skippyall/minions/module/Modules.java +++ b/src/main/java/io/github/skippyall/minions/module/Modules.java @@ -1,6 +1,6 @@ package io.github.skippyall.minions.module; -import io.github.skippyall.minions.command.Command; +import io.github.skippyall.minions.module.command.Command; import io.github.skippyall.minions.program.block.CodeBlock; import net.minecraft.item.Item; import net.minecraft.registry.Registries; diff --git a/src/main/java/io/github/skippyall/minions/module/MountModule.java b/src/main/java/io/github/skippyall/minions/module/MountModule.java index 0ba460c..6c23f16 100644 --- a/src/main/java/io/github/skippyall/minions/module/MountModule.java +++ b/src/main/java/io/github/skippyall/minions/module/MountModule.java @@ -1,7 +1,7 @@ package io.github.skippyall.minions.module; import io.github.skippyall.minions.Minions; -import io.github.skippyall.minions.command.SimpleCommand; +import io.github.skippyall.minions.module.command.SimpleCommand; import net.minecraft.item.Items; import net.minecraft.text.Text; import net.minecraft.util.Identifier; diff --git a/src/main/java/io/github/skippyall/minions/module/MoveModule.java b/src/main/java/io/github/skippyall/minions/module/MoveModule.java index 9fb99c3..5c251a7 100644 --- a/src/main/java/io/github/skippyall/minions/module/MoveModule.java +++ b/src/main/java/io/github/skippyall/minions/module/MoveModule.java @@ -1,7 +1,7 @@ package io.github.skippyall.minions.module; import io.github.skippyall.minions.Minions; -import io.github.skippyall.minions.command.SimpleCommand; +import io.github.skippyall.minions.module.command.SimpleCommand; import io.github.skippyall.minions.input.TextInput; import net.minecraft.item.Items; import net.minecraft.text.Text; diff --git a/src/main/java/io/github/skippyall/minions/module/SimpleModuleItem.java b/src/main/java/io/github/skippyall/minions/module/SimpleModuleItem.java index ef2863c..1071998 100644 --- a/src/main/java/io/github/skippyall/minions/module/SimpleModuleItem.java +++ b/src/main/java/io/github/skippyall/minions/module/SimpleModuleItem.java @@ -1,7 +1,7 @@ package io.github.skippyall.minions.module; import eu.pb4.polymer.core.api.item.SimplePolymerItem; -import io.github.skippyall.minions.command.Command; +import io.github.skippyall.minions.module.command.Command; import io.github.skippyall.minions.program.block.CodeBlock; import net.minecraft.item.Item; diff --git a/src/main/java/io/github/skippyall/minions/command/Command.java b/src/main/java/io/github/skippyall/minions/module/command/Command.java similarity index 79% rename from src/main/java/io/github/skippyall/minions/command/Command.java rename to src/main/java/io/github/skippyall/minions/module/command/Command.java index 29b06e6..b056bad 100644 --- a/src/main/java/io/github/skippyall/minions/command/Command.java +++ b/src/main/java/io/github/skippyall/minions/module/command/Command.java @@ -1,4 +1,4 @@ -package io.github.skippyall.minions.command; +package io.github.skippyall.minions.module.command; import net.minecraft.item.Item; import net.minecraft.text.Text; diff --git a/src/main/java/io/github/skippyall/minions/command/CommandExecutor.java b/src/main/java/io/github/skippyall/minions/module/command/CommandExecutor.java similarity index 81% rename from src/main/java/io/github/skippyall/minions/command/CommandExecutor.java rename to src/main/java/io/github/skippyall/minions/module/command/CommandExecutor.java index f46b48f..603d341 100644 --- a/src/main/java/io/github/skippyall/minions/command/CommandExecutor.java +++ b/src/main/java/io/github/skippyall/minions/module/command/CommandExecutor.java @@ -1,4 +1,4 @@ -package io.github.skippyall.minions.command; +package io.github.skippyall.minions.module.command; import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer; import net.minecraft.server.network.ServerPlayerEntity; diff --git a/src/main/java/io/github/skippyall/minions/command/SimpleCommand.java b/src/main/java/io/github/skippyall/minions/module/command/SimpleCommand.java similarity index 95% rename from src/main/java/io/github/skippyall/minions/command/SimpleCommand.java rename to src/main/java/io/github/skippyall/minions/module/command/SimpleCommand.java index 5a5f926..dc7708d 100644 --- a/src/main/java/io/github/skippyall/minions/command/SimpleCommand.java +++ b/src/main/java/io/github/skippyall/minions/module/command/SimpleCommand.java @@ -1,4 +1,4 @@ -package io.github.skippyall.minions.command; +package io.github.skippyall.minions.module.command; import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer; import net.minecraft.item.Item; diff --git a/src/main/resources/data/minions/lang/en_us.json b/src/main/resources/data/minions/lang/en_us.json index 2ce3c2d..c64504c 100644 --- a/src/main/resources/data/minions/lang/en_us.json +++ b/src/main/resources/data/minions/lang/en_us.json @@ -8,6 +8,13 @@ "minions.gui.commands.title": "%s's Commands", "minions.gui.modules.title": "%s's Modules", "minions.gui.ok": "OK", + "minions.gui.look.skin.name": "Name", + "minions.gui.look.skin.name.title": "Enter a player name", + "minions.gui.look.skin.uuid": "UUID", + "minions.gui.look.skin.uuid.title": "Enter a player UUID", + "minions.gui.look.skin.base64": "Base64", + "minions.gui.look.skin.base64.title": "Enter a skin in base64 encoding", + "minions.gui.look.rename.title": "Enter a name", "minions.command.input.int.fail": "Not an integer", "minions.command.input.float.fail": "Not a number", @@ -31,5 +38,9 @@ "item.minions.basic_upgrade_base": "Basic Upgrade Base", "item.minions.advanced_upgrade_base": "Advanced Upgrade Base", - "minions.minion_item.tooltip": "Name: %s" + "minions.minion_item.tooltip": "Name: %s", + "minions.generic.name.invalid_char": "Name contains invalid character", + "minions.generic.name.too_long": "Name is too long", + "minions.generic.name.invalid": "Name is invalid", + "minions.generic.name.taken": "This name is already used" } \ No newline at end of file