diff --git a/src/main/java/io/github/skippyall/minions/MinionsConfig.java b/src/main/java/io/github/skippyall/minions/MinionsConfig.java index f0acab1..a4140d2 100644 --- a/src/main/java/io/github/skippyall/minions/MinionsConfig.java +++ b/src/main/java/io/github/skippyall/minions/MinionsConfig.java @@ -13,6 +13,8 @@ import com.electronwill.nightconfig.toml.TomlFormat; import com.electronwill.nightconfig.toml.TomlParser; import net.fabricmc.loader.api.FabricLoader; +import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; import static com.electronwill.nightconfig.core.serde.annotations.SerdeSkipDeserializingIf.SkipDeIf.IS_MISSING; @@ -44,7 +46,15 @@ public class MinionsConfig { } private static Path getPath() { - return FabricLoader.getInstance().getConfigDir().resolve(Minions.MOD_ID + ".toml"); + Path minionsDir = FabricLoader.getInstance().getConfigDir().resolve("minions"); + if(!Files.isDirectory(minionsDir)) { + try { + Files.createDirectory(minionsDir); + } catch (IOException e) { + Minions.LOGGER.error("Could not create config dir", e); + } + } + return minionsDir.resolve(Minions.MOD_ID + ".toml"); } public static MinionsConfig get() { @@ -55,7 +65,6 @@ public class MinionsConfig { } public static void loadConfig() { - System.out.println("loading"); try { CommentedConfig config = new TomlParser().parse(getPath(), (file, configFormat) -> { CommentedConfig defaultConfig = ObjectSerializer.standard().serializeFields(new MinionsConfig(), TomlFormat::newConfig); @@ -65,8 +74,7 @@ public class MinionsConfig { INSTANCE = ObjectDeserializer.standard().deserializeFields(config, MinionsConfig::new); } catch (SerdeException | ParsingException | WritingException e) { - System.out.println("[minions] Error while reading config"); - e.printStackTrace(); + Minions.LOGGER.error("Error while reading config", e); INSTANCE = new MinionsConfig(); } } diff --git a/src/main/java/io/github/skippyall/minions/minion/MinionConfig.java b/src/main/java/io/github/skippyall/minions/minion/MinionConfig.java new file mode 100644 index 0000000..0a7c2f2 --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/minion/MinionConfig.java @@ -0,0 +1,63 @@ +package io.github.skippyall.minions.minion; + +import com.mojang.serialization.Codec; +import io.github.skippyall.minions.registration.MinionRegistries; +import net.minecraft.util.Identifier; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +public class MinionConfig { + public static final Codec CODEC = Codec., Object>dispatchedMap( + MinionRegistries.MINION_CONFIG_OPTIONS.getCodec(), + Option::codec + ).xmap(MinionConfig::new, config -> config.values); + + private final Map, Object> values; + + public MinionConfig() { + values = new HashMap<>(); + } + + private MinionConfig(Map, Object> values) { + this.values = new HashMap<>(values); + } + + public T getOption(Option option) { + if(values.containsKey(option)) { + //noinspection unchecked + return (T) values.get(option); + } else { + return option.defaultValue(); + } + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof MinionConfig that)) return false; + return Objects.equals(values, that.values); + } + + @Override + public int hashCode() { + return Objects.hashCode(values); + } + + public static Option booleanOption(Identifier key, boolean defaultValue) { + return new Option<>(key, defaultValue, Codec.BOOL); + } + + public record Option(Identifier key, T defaultValue, Codec codec) { + @Override + public boolean equals(Object o) { + if (!(o instanceof Option option)) return false; + return Objects.equals(key, option.key); + } + + @Override + public int hashCode() { + return Objects.hashCode(key); + } + } +} 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 bc12073..9f513fc 100644 --- a/src/main/java/io/github/skippyall/minions/minion/MinionData.java +++ b/src/main/java/io/github/skippyall/minions/minion/MinionData.java @@ -12,7 +12,14 @@ import net.minecraft.util.dynamic.Codecs; import java.util.Optional; import java.util.UUID; -public record MinionData(UUID uuid, String name, Optional skin, boolean isSpawned, SerializableListenerManager listeners) { +public record MinionData( + UUID uuid, + String name, + Optional skin, + boolean isSpawned, + SerializableListenerManager listeners, + MinionConfig config +) { public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( Uuids.CODEC.fieldOf("uuid").forGetter(MinionData::uuid), @@ -22,23 +29,31 @@ public record MinionData(UUID uuid, String name, Optional skin, boo SerializableListenerManager.getCodec(MinionRegistries.MINION_LISTENER_CODECS).optionalFieldOf("listeners").xmap( optional -> optional.orElseGet(() -> new SerializableListenerManager<>(MinionRegistries.MINION_LISTENER_CODECS)), Optional::of - ).forGetter(MinionData::listeners) + ).forGetter(MinionData::listeners), + MinionConfig.CODEC.optionalFieldOf("config", new MinionConfig()).forGetter(MinionData::config) ).apply(instance, MinionData::new) ); public static MinionData createDefault(MinecraftServer server) { - return new MinionData(UUID.randomUUID(), MinionProfileUtils.newDefaultMinionName(server), Optional.empty(), false, new SerializableListenerManager<>(MinionRegistries.MINION_LISTENER_CODECS)); + return new MinionData( + UUID.randomUUID(), + MinionProfileUtils.newDefaultMinionName(server), + Optional.empty(), + false, + new SerializableListenerManager<>(MinionRegistries.MINION_LISTENER_CODECS), + new MinionConfig() + ); } public MinionData withName(String name) { - return new MinionData(uuid, name, skin, isSpawned, listeners); + return new MinionData(uuid, name, skin, isSpawned, listeners, config); } public MinionData withSkin(Optional skin) { - return new MinionData(uuid, name, skin, isSpawned, listeners); + return new MinionData(uuid, name, skin, isSpawned, listeners, config); } public MinionData withSpawned(boolean isSpawned) { - return new MinionData(uuid, name, skin, isSpawned, listeners); + return new MinionData(uuid, name, skin, isSpawned, listeners, config); } } 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 0ae6574..d9c2f04 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 @@ -3,6 +3,7 @@ package io.github.skippyall.minions.minion.fakeplayer; import com.mojang.authlib.GameProfile; import com.mojang.authlib.properties.PropertyMap; +import io.github.skippyall.minions.registration.MinionConfigOptions; import io.github.skippyall.minions.registration.MinionItems; import io.github.skippyall.minions.minion.MinionListener; import io.github.skippyall.minions.minion.MinionData; @@ -143,11 +144,11 @@ public class MinionFakePlayer extends ServerPlayerEntity { } public boolean canSpawnMobs() { - return moduleInventory.hasAbility(SpecialAbilities.MOB_SPAWNING); + return moduleInventory.hasAbility(SpecialAbilities.MOB_SPAWNING) || getData().config().getOption(MinionConfigOptions.spawnAndDespawnMobs); } public boolean canDespawnMobs() { - return moduleInventory.hasAbility(SpecialAbilities.MOB_SPAWNING); + return canSpawnMobs(); } @Override 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 index 2662893..b7f19bb 100644 --- a/src/main/java/io/github/skippyall/minions/minion/skin/Base64SkinProvider.java +++ b/src/main/java/io/github/skippyall/minions/minion/skin/Base64SkinProvider.java @@ -2,22 +2,102 @@ package io.github.skippyall.minions.minion.skin; import com.mojang.authlib.properties.Property; import com.mojang.authlib.properties.PropertyMap; -import io.github.skippyall.minions.gui.input.TextInput; +import io.github.skippyall.minions.Minions; +import net.minecraft.dialog.AfterAction; +import net.minecraft.dialog.DialogActionButtonData; +import net.minecraft.dialog.DialogButtonData; +import net.minecraft.dialog.DialogCommonData; +import net.minecraft.dialog.action.DynamicCustomDialogAction; +import net.minecraft.dialog.input.TextInputControl; +import net.minecraft.dialog.type.Dialog; +import net.minecraft.dialog.type.DialogInput; +import net.minecraft.dialog.type.NoticeDialog; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtElement; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.RegistryKeys; +import net.minecraft.registry.entry.RegistryEntry; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.text.Text; +import net.minecraft.util.Identifier; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.concurrent.CompletableFuture; public class Base64SkinProvider implements SkinProvider { + public static final RegistryKey DIALOG = RegistryKey.of(RegistryKeys.DIALOG, Identifier.of(Minions.MOD_ID, "base_64_input")); + public static final Identifier CUSTOM_DIALOG_ACTION = Identifier.of(Minions.MOD_ID, "base_64_submit"); + + private static long dialogIdCounter = 0; + private static Map>> futures = new HashMap<>(); + @Override public CompletableFuture> openSkinMenu(ServerPlayerEntity player) { - return TextInput.inputString(player, Text.translatable("minions.gui.look.skin.base64.title"), "") - .thenApply(base64String -> { + dialogIdCounter++; + player.openDialog(getDialog()); + CompletableFuture> future = new CompletableFuture<>(); + futures.put(dialogIdCounter, future); + return future; + } + + public static void onCustomDialogAction(Optional element) { + if(element.isPresent() && element.get() instanceof NbtCompound compound) { + Optional id = compound.getLong("dialog_id"); + Optional base64 = compound.getString("base_64"); + if(id.isPresent() && base64.isPresent() && !base64.get().isBlank()) { + if(futures.containsKey(id.get())) { PropertyMap map = new PropertyMap(); - map.put("textures", new Property("textures", base64String)); - return Optional.of(map); - }); + map.put("textures", new Property("textures", base64.get().strip())); + + futures.get(id.get()).complete(Optional.of(map)); + futures.remove(id.get()); + } + } + } + } + + private static RegistryEntry getDialog() { + NbtCompound additionalData = new NbtCompound(); + additionalData.putLong("dialog_id", dialogIdCounter); + return RegistryEntry.of( + new NoticeDialog( + new DialogCommonData( + Text.translatable("minions.gui.look.skin.base64.title"), + Optional.empty(), + true, + false, + AfterAction.CLOSE, + List.of(), + List.of( + new DialogInput("base_64", new TextInputControl( + 200, + Text.empty(), + false, + "", + 2000, + Optional.empty() + )) + ) + ), + new DialogActionButtonData( + new DialogButtonData( + Text.translatable("gui.ok"), + 150 + ), + Optional.of( + new DynamicCustomDialogAction( + CUSTOM_DIALOG_ACTION, + Optional.of( + additionalData + ) + ) + ) + ) + ) + ); } @Override diff --git a/src/main/java/io/github/skippyall/minions/mixins/MinecraftServerMixin.java b/src/main/java/io/github/skippyall/minions/mixins/MinecraftServerMixin.java index bca48af..ac2d322 100644 --- a/src/main/java/io/github/skippyall/minions/mixins/MinecraftServerMixin.java +++ b/src/main/java/io/github/skippyall/minions/mixins/MinecraftServerMixin.java @@ -2,21 +2,38 @@ package io.github.skippyall.minions.mixins; import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer; +import io.github.skippyall.minions.minion.skin.Base64SkinProvider; +import io.github.skippyall.minions.registration.MinionConfigOptions; +import net.minecraft.nbt.NbtElement; import net.minecraft.server.MinecraftServer; import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.Identifier; 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; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; @Mixin(MinecraftServer.class) public class MinecraftServerMixin { + @ModifyExpressionValue(method = "createMetadataPlayers", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/PlayerManager;getPlayerList()Ljava/util/List;")) public List ignoreFakePlayers(List original) { return original.stream() - .filter(player -> !(player instanceof MinionFakePlayer)) + .filter(player -> !(player instanceof MinionFakePlayer minion + && !minion.getData().config().getOption(MinionConfigOptions.showInServerList))) .collect(Collectors.toCollection(ArrayList::new)); } + + @Inject(method = "handleCustomClickAction", at = @At("HEAD"), cancellable = true) + private void onCustomClickAction(Identifier id, Optional payload, CallbackInfo ci) { + if(id.equals(Base64SkinProvider.CUSTOM_DIALOG_ACTION)) { + Base64SkinProvider.onCustomDialogAction(payload); + ci.cancel(); + } + } } diff --git a/src/main/java/io/github/skippyall/minions/mixins/PlayerListEntryS2CPacket$EntryMixin.java b/src/main/java/io/github/skippyall/minions/mixins/PlayerListEntryS2CPacket$EntryMixin.java index 64fa9d7..ccd1011 100644 --- a/src/main/java/io/github/skippyall/minions/mixins/PlayerListEntryS2CPacket$EntryMixin.java +++ b/src/main/java/io/github/skippyall/minions/mixins/PlayerListEntryS2CPacket$EntryMixin.java @@ -2,6 +2,7 @@ package io.github.skippyall.minions.mixins; import com.llamalad7.mixinextras.sugar.Local; import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer; +import io.github.skippyall.minions.registration.MinionConfigOptions; import net.minecraft.network.packet.s2c.play.PlayerListS2CPacket; import net.minecraft.server.network.ServerPlayerEntity; import org.spongepowered.asm.mixin.Mixin; @@ -12,7 +13,7 @@ import org.spongepowered.asm.mixin.injection.ModifyArg; public class PlayerListEntryS2CPacket$EntryMixin { @ModifyArg(method = "(Lnet/minecraft/server/network/ServerPlayerEntity;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/packet/s2c/play/PlayerListS2CPacket$Entry;(Ljava/util/UUID;Lcom/mojang/authlib/GameProfile;ZILnet/minecraft/world/GameMode;Lnet/minecraft/text/Text;ZILnet/minecraft/network/encryption/PublicPlayerSession$Serialized;)V"), index = 2) private static boolean removeMinionFromTabList(boolean original, @Local(argsOnly = true) ServerPlayerEntity player) { - if(player instanceof MinionFakePlayer) { + if(player instanceof MinionFakePlayer minion && !minion.getData().config().getOption(MinionConfigOptions.showInTabList)) { return false; } 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 8f4fc7f..3dba26a 100644 --- a/src/main/java/io/github/skippyall/minions/mixins/PlayerListMixin.java +++ b/src/main/java/io/github/skippyall/minions/mixins/PlayerListMixin.java @@ -8,6 +8,7 @@ import com.llamalad7.mixinextras.sugar.Local; import com.mojang.authlib.GameProfile; import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer; import io.github.skippyall.minions.minion.fakeplayer.NetHandlerPlayServerFake; +import io.github.skippyall.minions.registration.MinionConfigOptions; import net.minecraft.network.ClientConnection; import net.minecraft.network.packet.c2s.common.SyncedClientOptions; import net.minecraft.server.MinecraftServer; @@ -61,7 +62,7 @@ public class PlayerListMixin { @WrapOperation(method = "onPlayerConnect", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/PlayerManager;broadcast(Lnet/minecraft/text/Text;Z)V")) public void noLoginMessage(PlayerManager instance, Text message, boolean overlay, Operation original, @Local(argsOnly = true) ServerPlayerEntity player) { - if(!(player instanceof MinionFakePlayer)) { + if(!(player instanceof MinionFakePlayer minion && !minion.getData().config().getOption(MinionConfigOptions.sendLoginMessage))) { original.call(instance, message, overlay); } } @@ -69,7 +70,7 @@ public class PlayerListMixin { @ModifyReceiver(method = "checkCanJoin", at = @At(value = "INVOKE", target = "Ljava/util/List;size()I")) public List noMinionCounting(List instance) { return instance.stream() - .filter(player -> !(player instanceof MinionFakePlayer)) + .filter(player -> !(player instanceof MinionFakePlayer minion && !minion.getData().config().getOption(MinionConfigOptions.countForPlayerLimit))) .collect(Collectors.toCollection(ArrayList::new)); } } diff --git a/src/main/java/io/github/skippyall/minions/mixins/ServerPlayNetworkHandlerMixin.java b/src/main/java/io/github/skippyall/minions/mixins/ServerPlayNetworkHandlerMixin.java index 591bf87..08021e8 100644 --- a/src/main/java/io/github/skippyall/minions/mixins/ServerPlayNetworkHandlerMixin.java +++ b/src/main/java/io/github/skippyall/minions/mixins/ServerPlayNetworkHandlerMixin.java @@ -3,6 +3,7 @@ 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.registration.MinionConfigOptions; import net.minecraft.server.PlayerManager; import net.minecraft.server.network.ServerPlayNetworkHandler; import net.minecraft.server.network.ServerPlayerEntity; @@ -18,7 +19,7 @@ public class ServerPlayNetworkHandlerMixin { @WrapOperation(method = "cleanUp", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/PlayerManager;broadcast(Lnet/minecraft/text/Text;Z)V")) public void noLogoutMessage(PlayerManager instance, Text message, boolean overlay, Operation original) { - if(!(player instanceof MinionFakePlayer)) { + if(!(player instanceof MinionFakePlayer minion && !minion.getData().config().getOption(MinionConfigOptions.sendLogoutMessage))) { original.call(instance, message, overlay); } } diff --git a/src/main/java/io/github/skippyall/minions/mixins/SleepManagerMixin.java b/src/main/java/io/github/skippyall/minions/mixins/SleepManagerMixin.java index 120825a..60ab863 100644 --- a/src/main/java/io/github/skippyall/minions/mixins/SleepManagerMixin.java +++ b/src/main/java/io/github/skippyall/minions/mixins/SleepManagerMixin.java @@ -3,6 +3,7 @@ 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.registration.MinionConfigOptions; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.world.SleepManager; import org.spongepowered.asm.mixin.Mixin; @@ -12,7 +13,7 @@ import org.spongepowered.asm.mixin.injection.At; public class SleepManagerMixin { @WrapOperation(method = "update", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/network/ServerPlayerEntity;isSpectator()Z")) public boolean excludeMinions(ServerPlayerEntity instance, Operation original) { - if (instance instanceof MinionFakePlayer) { + if (instance instanceof MinionFakePlayer minion && !minion.getData().config().getOption(MinionConfigOptions.countForSleeping)) { return true; } else { return original.call(instance); diff --git a/src/main/java/io/github/skippyall/minions/registration/MinionConfigOptions.java b/src/main/java/io/github/skippyall/minions/registration/MinionConfigOptions.java new file mode 100644 index 0000000..c379636 --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/registration/MinionConfigOptions.java @@ -0,0 +1,26 @@ +package io.github.skippyall.minions.registration; + +import io.github.skippyall.minions.Minions; +import io.github.skippyall.minions.minion.MinionConfig; +import net.minecraft.registry.Registry; +import net.minecraft.util.Identifier; + +import static io.github.skippyall.minions.minion.MinionConfig.booleanOption; + +public class MinionConfigOptions { + public static final MinionConfig.Option showInServerList = register(booleanOption(id("showInServerList"), false)); + public static final MinionConfig.Option showInTabList = register(booleanOption(id("showInTabList"), false)); + public static final MinionConfig.Option sendLoginMessage = register(booleanOption(id("sendLoginMessage"), false)); + public static final MinionConfig.Option sendLogoutMessage = register(booleanOption(id("sendLogoutMessage"), false)); + public static final MinionConfig.Option countForSleeping = register(booleanOption(id("countForSleeping"), false)); + public static final MinionConfig.Option countForPlayerLimit = register(booleanOption(id("countForPlayerLimit"), false)); + public static final MinionConfig.Option spawnAndDespawnMobs = register(booleanOption(id("spawnAndDespawnMobs"), false)); + + private static MinionConfig.Option register(MinionConfig.Option option) { + return Registry.register(MinionRegistries.MINION_CONFIG_OPTIONS, option.key(), option); + } + + private static Identifier id(String name) { + return Identifier.of(Minions.MOD_ID, name); + } +} diff --git a/src/main/java/io/github/skippyall/minions/registration/MinionRegistries.java b/src/main/java/io/github/skippyall/minions/registration/MinionRegistries.java index f7f55a1..7a8d8df 100644 --- a/src/main/java/io/github/skippyall/minions/registration/MinionRegistries.java +++ b/src/main/java/io/github/skippyall/minions/registration/MinionRegistries.java @@ -5,6 +5,7 @@ import com.mojang.serialization.MapCodec; import io.github.skippyall.minions.Minions; import io.github.skippyall.minions.docs.ReferenceEntry; import io.github.skippyall.minions.gui.GuiDisplay; +import io.github.skippyall.minions.minion.MinionConfig; import io.github.skippyall.minions.minion.MinionListener; import io.github.skippyall.minions.minion.MinionRuntime; import io.github.skippyall.minions.minion.skin.SkinProvider; @@ -33,6 +34,7 @@ public class MinionRegistries { public static final Registry> MINION_LISTENER_CODECS = registry("minion_listener_codec"); public static final Registry> CLIPBOARD_TYPES = registry("clipboard_type"); public static final Registry SPECIAL_ABILITIES = registry("special_ability"); + public static final Registry> MINION_CONFIG_OPTIONS = registry("minion_config_option"); public static final RegistryKey> GUI_DISPLAY = key("gui_display"); public static final RegistryKey> REFERENCE_ENTRY = key("reference_entry");