diff --git a/build.gradle b/build.gradle index 6a5f567..fb915bb 100644 --- a/build.gradle +++ b/build.gradle @@ -6,10 +6,18 @@ plugins { version = project.mod_version group = project.maven_group +loom { + accessWidenerPath = file("src/main/resources/minions.accesswidener") +} + 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 @@ -17,6 +25,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 +50,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 1fc086a..4c7050a 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.4 - loader_version=0.16.10 + loader_version=0.16.13 yarn_mappings=1.21.4+build.8 # Mod Properties @@ -19,3 +19,5 @@ org.gradle.jvmargs=-Xmx1G polymer_version=0.11.5+1.21.4 sgui_version=1.8.2+1.21.4 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/Minions.java b/src/main/java/io/github/skippyall/minions/Minions.java index 203a692..64f3292 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; @@ -32,31 +34,27 @@ 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); + MinionPersistentState.INSTANCE.getMinionData().forEach((uuid, data) -> { + if(data.isSpawned()) { + MinionFakePlayer.spawnMinion(data, server.getOverworld(), null, null); + } }); }); - ServerTickEvents.START_SERVER_TICK.register(server -> { - exec(() -> { - for (Runnable run:executeOnNextTick) { - run.run(); - } - executeOnNextTick.clear(); - }); + + CommandRegistrationCallback.EVENT.register((commandDispatcher, commandRegistryAccess, registrationEnvironment) -> { + MobCapCommand.registerCommand(commandDispatcher); }); Modules.register(); @@ -69,14 +67,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/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/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 new file mode 100644 index 0000000..bbef5b0 --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/gui/MinionLookGui.java @@ -0,0 +1,95 @@ +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 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; +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; + 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() { + setSlot(10, new GuiElementBuilder() + .setItem(Items.OAK_SIGN) + .setName(Text.literal(getData().name())) + .setCallback(() -> { + openRenameGui(player, minionItem); + }) + ); + } + + private void updateSkin() { + GuiElementBuilder builder = new GuiElementBuilder() + .setItem(Items.PLAYER_HEAD) + .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, currentSkinProvider.getDisplayName()) + .setCallback(this::cycleSkinProvider) + ); + } + + private MinionData getData() { + 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.inputSync(player, Text.translatable("minions.gui.look.rename.title"), "Minion", MinionProfileUtils::checkMinionNameWithoutPrefix) + .thenAccept(name -> { + 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/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..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,42 +9,82 @@ import net.minecraft.text.Text; import java.util.concurrent.CompletableFuture; import java.util.function.Function; +import java.util.function.Predicate; -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 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 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 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 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 inputSync(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) { + 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)); } }); } - public static CompletableFuture inputInt(ServerPlayerEntity player, Text title, String defaultValue) { - return inputParse(player, title, defaultValue, Integer::parseInt, Text.translatable("minions.command.input.int.fail")); + @Override + public void onClose() { + if(!future.isDone()) { + future.cancel(false); + } } - public static CompletableFuture inputFloat(ServerPlayerEntity player, Text title, String defaultValue) { - return inputParse(player, title, defaultValue, Float::parseFloat, Text.translatable("minions.command.input.float.fail")); + public void onConfirm() { + 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 bcfe28a..35235e8 100644 --- a/src/main/java/io/github/skippyall/minions/minion/MinionData.java +++ b/src/main/java/io/github/skippyall/minions/minion/MinionData.java @@ -1,10 +1,11 @@ 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 net.minecraft.component.ComponentType; import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtOps; @@ -12,43 +13,50 @@ import net.minecraft.registry.Registries; import net.minecraft.registry.Registry; import net.minecraft.util.Identifier; import net.minecraft.util.Uuids; -import org.jetbrains.annotations.NotNull; +import net.minecraft.util.dynamic.Codecs; +import org.jetbrains.annotations.Nullable; import java.util.Optional; import java.util.UUID; -public record MinionData(UUID uuid, String name, @NotNull Optional skinUuid) { +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), - Uuids.CODEC.optionalFieldOf("skinUuid").orElse(Optional.empty()).forGetter(MinionData::skinUuid) + Codecs.GAME_PROFILE_PROPERTY_MAP.optionalFieldOf("skin").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(), MinionProfileUtils.newDefaultMinionName(), Optional.empty(), 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(Optional skin) { + return new MinionData(uuid, name, skin, isSpawned); + } + + 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..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,9 +2,10 @@ 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.component.type.NbtComponent; +import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.item.ItemUsageContext; @@ -14,20 +15,18 @@ 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; 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,54 +48,56 @@ 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())); } } @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"; + 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) { - 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()); + MinionPersistentState.INSTANCE.updateMinionData(data); } @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(); + setData(data, item); + } + 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..f84aa44 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,16 @@ 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<>(); @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(); - for(UUID uuid : minionUuids) { - NbtCompound compound = new NbtCompound(); - compound.putUuid("uuid", uuid); - uuids.add(compound); - } - nbt.put("uuids", uuids); return nbt; } @@ -43,55 +34,33 @@ 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)); - } - - NbtList uuids = compound.getList("uuids", NbtElement.COMPOUND_TYPE); - for(NbtElement element : uuids) { - instance.minionUuids.add(((NbtCompound) element).getUuid("uuid")); + if(element instanceof NbtCompound compound1) { + MinionData data = MinionData.readNbt(compound1); + instance.minionData.put(data.uuid(), data); + } } return instance; } - public void addMinionUUID(UUID uuid) { - if(!minionUuids.contains(uuid)) { - minionUuids.add(uuid); - } + public MinionData getMinionData(UUID uuid) { + return minionData.get(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); - markDirty(); - } - - public void removeMinion(MinionFakePlayer minionData) { - removeMinion(minionData.getUuid()); - } - - 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); - } - markDirty(); - } - - public List getMinionData() { + public Map getMinionData() { return minionData; } + public void updateMinionData(MinionData data) { + minionData.put(data.uuid(), data); + markDirty(); + } + public boolean isMinion(UUID uuid) { - return minionUuids.contains(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) { 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..799f0bd 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,14 @@ 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 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; import org.jetbrains.annotations.Nullable; import java.util.UUID; @@ -13,67 +19,51 @@ import java.util.concurrent.ForkJoinPool; import static io.github.skippyall.minions.Minions.LOGGER; public class MinionProfileUtils { - public static CompletableFuture<@Nullable GameProfile> lookupSkinOwnerProfile(MinecraftServer server, String username) { - CompletableFuture future = new CompletableFuture<>(); + public static final String PREFIX = "+"; - 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 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 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 String newDefaultMinionName() { + int i = 0; + while (MinionPersistentState.INSTANCE.isMinionNameTaken("+Minion" + i)) { + i++; + } + return "+Minion" + i; + } + 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..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 @@ -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,75 +58,52 @@ public class MinionFakePlayer extends ServerPlayerEntity { private final ModuleInventory moduleInventory = new ModuleInventory(); private final MinionRuntime runtime = new MinionRuntime(this); - private UUID skinUuid = null; + private final 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()); + PropertyMap skin = data.skin().orElse(null); + + GameProfile profile = MinionProfileUtils.makeNewMinionProfile(data.uuid(), data.name(), skin); + 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(), 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); } - - 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); - }); - }); + 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) + 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() { @@ -148,7 +123,7 @@ public class MinionFakePlayer extends ServerPlayerEntity { } public EntityPlayerActionPack getMinionActionPack() { - return ((ServerPlayerInterface)this).getActionPack(); + return ((ServerPlayerInterface)this).minions$getActionPack(); } @Override @@ -188,7 +163,7 @@ public class MinionFakePlayer extends ServerPlayerEntity { })); } - MinionPersistentState.INSTANCE.removeMinion(this); + MinionPersistentState.INSTANCE.updateMinionData(data.withSpawned(false)); } @Override @@ -311,13 +286,14 @@ 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; } + public MinionData getData() { + return data; + } + @Override public void writeCustomDataToNbt(NbtCompound nbt) { super.writeCustomDataToNbt(nbt); @@ -331,12 +307,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/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/NameSkinProvider.java b/src/main/java/io/github/skippyall/minions/minion/skin/NameSkinProvider.java new file mode 100644 index 0000000..9b0d6a2 --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/minion/skin/NameSkinProvider.java @@ -0,0 +1,25 @@ +package io.github.skippyall.minions.minion.skin; + +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.properties.PropertyMap; +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 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 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 new file mode 100644 index 0000000..9068ecc --- /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 net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.text.Text; + +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +public interface SkinProvider { + CompletableFuture> openSkinMenu(ServerPlayerEntity player); + + 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 new file mode 100644 index 0000000..06f230d --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/minion/skin/SkinProviders.java @@ -0,0 +1,25 @@ +package io.github.skippyall.minions.minion.skin; + +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 SKIN_PROVIDERS = new SimpleRegistry<>(RegistryKey.ofRegistry(Identifier.of(Minions.MOD_ID, "skin_providers")), Lifecycle.stable()); + + 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, String path) { + return Registry.register(SKIN_PROVIDERS, Identifier.of(Minions.MOD_ID, path), skinProvider); + } + + public static void register() { + + } +} 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..520957c --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/minion/skin/UUIDSkinProvider.java @@ -0,0 +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 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 Text getDisplayName() { + return Text.translatable("minions.gui.look.skin.uuid"); + } +} 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..b4fbb8c --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/mixinhelper/ChunkTicketManager$DistanceFromNearestPlayerTrackerAccessor.java @@ -0,0 +1,9 @@ +package io.github.skippyall.minions.mixinhelper; + +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/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/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..2663699 --- /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 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(); + if(targetPredicate != null) { + return predicate.and(targetPredicate); + } else { + return predicate; + } + } +} 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/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/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/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/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..d540e27 --- /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 minions$minionless, minions$target; + + @Inject(method = "isPlayerInChunk", at = @At("RETURN"), cancellable = true) + public void minions$filterMinions(long chunkPos, CallbackInfoReturnable cir) { + if (minions$minionless) { + cir.setReturnValue(minions$isRealPlayerInChunk(chunkPos)); + } + } + + @Override + public boolean minions$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 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 (minions$target && (distance == Integer.MAX_VALUE || minions$isRealPlayerInChunk(chunkPos))) { + ((ChunkTicketManagerAccessor)field_17462).getMinionless().updateLevel(chunkPos, distance, decrease); + } + } + + @Override + public void minions$markAsMinionless() { + minions$minionless = true; + } + + @Override + public void minions$markAsTarget() { + minions$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..6ca5362 --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/mixins/antimobcap/ChunkTicketManagerMixin.java @@ -0,0 +1,49 @@ +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; +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; + } + + @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); + } + } +} 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/java/io/github/skippyall/minions/module/ActionModules.java b/src/main/java/io/github/skippyall/minions/module/ActionModules.java index e331520..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,10 +2,10 @@ 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; -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/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 b3a2d0e..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; @@ -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/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/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..c64504c 100644 --- a/src/main/resources/data/minions/lang/en_us.json +++ b/src/main/resources/data/minions/lang/en_us.json @@ -7,6 +7,14 @@ "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.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", @@ -30,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 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 0246eee..71fddc6 100644 --- a/src/main/resources/minions.mixins.json +++ b/src/main/resources/minions.mixins.json @@ -7,6 +7,8 @@ "ChunkTicketManagerFixMixin", "ConnectionMixin", "EntityAccessor", + "GraveCompatMixin", + "EntityViewMixin", "MinecraftServerMixin", "MobEntityMixin", "PlayerListEntryS2CPacket$EntryMixin", @@ -14,7 +16,12 @@ "ServerPlayerMixin", "ServerPlayNetworkHandlerMixin", "SleepManagerMixin", - "SpawnHelperMixin" + "SpawnHelperMixin", + "antimobcap.ChunkPosDistanceLevelPropagatorMixin", + "antimobcap.ChunkTicketManager$DistanceFromNearestPlayerTrackerMixin", + "antimobcap.ChunkTicketManagerMixin", + "antimobcap.ServerChunkManagerAccessor", + "antimobcap.ServerChunkManagerMixin" ], "client": [], "server": [],