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