diff --git a/src/main/java/com/kasetoatz/silkspawners/accessors/ITrialSpawnerLogic.java b/src/main/java/com/kasetoatz/silkspawners/accessors/ITrialSpawnerLogic.java index 0cac1ea..76f0b4f 100644 --- a/src/main/java/com/kasetoatz/silkspawners/accessors/ITrialSpawnerLogic.java +++ b/src/main/java/com/kasetoatz/silkspawners/accessors/ITrialSpawnerLogic.java @@ -1,8 +1,14 @@ package com.kasetoatz.silkspawners.accessors; +import net.minecraft.block.spawner.TrialSpawnerData; import net.minecraft.block.spawner.TrialSpawnerLogic; +import net.minecraft.text.Text; +import net.minecraft.world.World; public interface ITrialSpawnerLogic { TrialSpawnerLogic.FullConfig silkspawners$getFullConfig(); + void silkspawners$setFullConfig(World world, TrialSpawnerLogic.FullConfig fullConfig); + void silkspawners$setData(TrialSpawnerData data); + Text silkspawners$getEntityName(World world); } diff --git a/src/main/java/com/kasetoatz/silkspawners/config/Config.java b/src/main/java/com/kasetoatz/silkspawners/config/Config.java index 7328dca..566c746 100644 --- a/src/main/java/com/kasetoatz/silkspawners/config/Config.java +++ b/src/main/java/com/kasetoatz/silkspawners/config/Config.java @@ -3,14 +3,13 @@ package com.kasetoatz.silkspawners.config; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonObject; -import net.minecraft.client.MinecraftClient; +import net.fabricmc.loader.api.FabricLoader; import net.minecraft.util.crash.CrashException; import net.minecraft.util.crash.CrashReport; -import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; public class Config { @@ -19,34 +18,34 @@ public class Config public static boolean REQUIRE_PICKAXE = true; public static boolean REQUIRE_SILK_TOUCH = true; - private static final File config = new File(MinecraftClient.getInstance().runDirectory, "config/silkspawners.json"); - private static final Gson gson = new GsonBuilder().setPrettyPrinting().create(); + private static final Path FILE = FabricLoader.getInstance().getConfigDir().resolve("silkspawners.json"); + private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); public static void load() { - if (!config.exists()) + if (!Files.exists(FILE)) { save(); return; } - try (FileReader reader = new FileReader(config)) + try { - JsonObject json = gson.fromJson(reader, JsonObject.class); - if (json.has("enable-spawner")) + JsonObject data = GSON.fromJson(Files.readString(FILE), JsonObject.class); + if (data.has("enable-spawner")) { - ENABLE_SPAWNER = json.get("enable-spawner").getAsBoolean(); + ENABLE_SPAWNER = data.get("enable-spawner").getAsBoolean(); } - if (json.has("enable-trial-spawner")) + if (data.has("enable-trial-spawner")) { - ENABLE_TRIAL_SPAWNER = json.get("enable-trial-spawner").getAsBoolean(); + ENABLE_TRIAL_SPAWNER = data.get("enable-trial-spawner").getAsBoolean(); } - if (json.has("require-pickaxe")) + if (data.has("require-pickaxe")) { - REQUIRE_PICKAXE = json.get("require-pickaxe").getAsBoolean(); + REQUIRE_PICKAXE = data.get("require-pickaxe").getAsBoolean(); } - if (json.has("require-silk-touch")) + if (data.has("require-silk-touch")) { - REQUIRE_SILK_TOUCH = json.get("require-silk-touch").getAsBoolean(); + REQUIRE_SILK_TOUCH = data.get("require-silk-touch").getAsBoolean(); } save(); } @@ -58,14 +57,14 @@ public class Config public static void save() { - JsonObject json = new JsonObject(); - json.addProperty("enable-spawner", ENABLE_SPAWNER); - json.addProperty("enable-trial-spawner", ENABLE_TRIAL_SPAWNER); - json.addProperty("require-pickaxe", REQUIRE_PICKAXE); - json.addProperty("require-silk-touch", REQUIRE_SILK_TOUCH); - try (FileWriter writer = new FileWriter(config)) + try { - gson.toJson(json, writer); + JsonObject data = new JsonObject(); + data.addProperty("enable-spawner", ENABLE_SPAWNER); + data.addProperty("enable-trial-spawner", ENABLE_TRIAL_SPAWNER); + data.addProperty("require-pickaxe", REQUIRE_PICKAXE); + data.addProperty("require-silk-touch", REQUIRE_SILK_TOUCH); + Files.writeString(FILE, GSON.toJson(data)); } catch (IOException exc) { diff --git a/src/main/java/com/kasetoatz/silkspawners/mixin/BlockItemMixin.java b/src/main/java/com/kasetoatz/silkspawners/mixin/BlockItemMixin.java new file mode 100644 index 0000000..f2ce7ad --- /dev/null +++ b/src/main/java/com/kasetoatz/silkspawners/mixin/BlockItemMixin.java @@ -0,0 +1,59 @@ +package com.kasetoatz.silkspawners.mixin; + +import com.kasetoatz.silkspawners.accessors.ITrialSpawnerLogic; +import com.llamalad7.mixinextras.sugar.Local; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.block.entity.MobSpawnerBlockEntity; +import net.minecraft.block.entity.TrialSpawnerBlockEntity; +import net.minecraft.block.enums.TrialSpawnerState; +import net.minecraft.block.spawner.TrialSpawnerData; +import net.minecraft.block.spawner.TrialSpawnerLogic; +import net.minecraft.component.DataComponentTypes; +import net.minecraft.component.type.NbtComponent; +import net.minecraft.entity.EntityType; +import net.minecraft.item.*; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +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.Objects; +import java.util.Optional; + +@Mixin(BlockItem.class) +public abstract class BlockItemMixin +{ + @Inject(method="copyComponentsToBlockEntity", at=@At(value="INVOKE", target="Lnet/minecraft/block/entity/BlockEntity;markDirty()V")) + private static void copyComponentsToBlockEntity(World world, BlockPos pos, ItemStack stack, CallbackInfo ci, @Local BlockEntity entity) + { + if (entity instanceof MobSpawnerBlockEntity spawner) + { + Optional> entityType = Objects.requireNonNullElse(stack.get(DataComponentTypes.CUSTOM_DATA), NbtComponent.DEFAULT).copyNbt().get("entityType", EntityType.CODEC); + if (entityType.isEmpty()) + { + return; + } + spawner.setEntityType(entityType.get(), world.getRandom()); + } + else if (entity instanceof TrialSpawnerBlockEntity trialSpawner) + { + NbtCompound nbt = Objects.requireNonNullElse(stack.get(DataComponentTypes.CUSTOM_DATA), NbtComponent.DEFAULT).copyNbt(); + Optional fullConfig = nbt.get("fullConfig", TrialSpawnerLogic.FullConfig.CODEC.codec()); + Optional data = nbt.get("data", TrialSpawnerData.Packed.CODEC.codec()); + Optional state = nbt.getString("state"); + ITrialSpawnerLogic logic = ((ITrialSpawnerLogic)(Object)trialSpawner.getSpawner()); + if (fullConfig.isEmpty() || data.isEmpty() || state.isEmpty() || logic == null) + { + return; + } + logic.silkspawners$setFullConfig(world, fullConfig.get()); + TrialSpawnerData newData = new TrialSpawnerData(); + newData.unpack(data.get()); + logic.silkspawners$setData(newData); + trialSpawner.setSpawnerState(world, TrialSpawnerState.valueOf(state.get())); + } + } +} diff --git a/src/main/java/com/kasetoatz/silkspawners/mixin/BlockMixin.java b/src/main/java/com/kasetoatz/silkspawners/mixin/BlockMixin.java index efdcfc7..66a5334 100644 --- a/src/main/java/com/kasetoatz/silkspawners/mixin/BlockMixin.java +++ b/src/main/java/com/kasetoatz/silkspawners/mixin/BlockMixin.java @@ -2,27 +2,37 @@ package com.kasetoatz.silkspawners.mixin; import com.kasetoatz.silkspawners.accessors.IMobSpawnerLogic; import com.kasetoatz.silkspawners.accessors.ITrialSpawnerLogic; +import com.llamalad7.mixinextras.sugar.Local; import net.minecraft.block.*; import net.minecraft.block.entity.BlockEntity; import net.minecraft.block.entity.MobSpawnerBlockEntity; import net.minecraft.block.entity.Spawner; import net.minecraft.block.entity.TrialSpawnerBlockEntity; +import net.minecraft.block.spawner.TrialSpawnerData; import net.minecraft.block.spawner.TrialSpawnerLogic; import net.minecraft.component.DataComponentTypes; import net.minecraft.component.type.NbtComponent; +import net.minecraft.enchantment.Enchantment; import net.minecraft.enchantment.EnchantmentHelper; import net.minecraft.enchantment.Enchantments; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityType; import net.minecraft.item.ItemStack; +import net.minecraft.registry.Registry; import net.minecraft.registry.RegistryKeys; import net.minecraft.registry.tag.ItemTags; import net.minecraft.server.world.ServerWorld; +import net.minecraft.text.Text; +import net.minecraft.util.Rarity; import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; import org.jspecify.annotations.Nullable; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.ModifyArg; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import java.util.List; import java.util.Objects; @@ -32,46 +42,62 @@ import static com.kasetoatz.silkspawners.config.Config.*; @Mixin(Block.class) public class BlockMixin { - @Redirect(method="dropStacks(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/entity/BlockEntity;Lnet/minecraft/entity/Entity;Lnet/minecraft/item/ItemStack;)V", at=@At(value="INVOKE", target="Lnet/minecraft/block/Block;getDroppedStacks(Lnet/minecraft/block/BlockState;Lnet/minecraft/server/world/ServerWorld;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/entity/BlockEntity;Lnet/minecraft/entity/Entity;Lnet/minecraft/item/ItemStack;)Ljava/util/List;")) - private static List getDroppedStacks(BlockState state, ServerWorld world, BlockPos pos, @Nullable BlockEntity blockEntity, @Nullable Entity entity, ItemStack stack) + @Unique + private static boolean shouldNotDrop(BlockEntity entity, Block block, ItemStack tool, Registry enchantments) { + return !(entity instanceof Spawner) || + (!ENABLE_SPAWNER && block instanceof SpawnerBlock) || + (!ENABLE_TRIAL_SPAWNER && block instanceof TrialSpawnerBlock) || + (REQUIRE_PICKAXE && !tool.isIn(ItemTags.PICKAXES)) || + (REQUIRE_SILK_TOUCH && EnchantmentHelper.getLevel(enchantments.getOrThrow(Enchantments.SILK_TOUCH), tool) < 1); + } + + @Inject(method="getDroppedStacks(Lnet/minecraft/block/BlockState;Lnet/minecraft/server/world/ServerWorld;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/entity/BlockEntity;Lnet/minecraft/entity/Entity;Lnet/minecraft/item/ItemStack;)Ljava/util/List;", at=@At("HEAD"), cancellable=true) + private static void getDroppedStacks(BlockState state, ServerWorld world, BlockPos pos, @Nullable BlockEntity blockEntity, @Nullable Entity entity, ItemStack tool, CallbackInfoReturnable> cir) { - System.out.println(stack); - if (!(blockEntity instanceof Spawner)) + if (shouldNotDrop(blockEntity, state.getBlock(), tool, world.getRegistryManager().getOrThrow(RegistryKeys.ENCHANTMENT))) { - return getDroppedStacks(state, world, pos, blockEntity, entity, stack); - } - if (!ENABLE_SPAWNER && state.getBlock() instanceof SpawnerBlock) - { - return List.of(); - } - if (!ENABLE_TRIAL_SPAWNER && state.getBlock() instanceof TrialSpawnerBlock) - { - return List.of(); - } - if (REQUIRE_PICKAXE && !stack.isIn(ItemTags.PICKAXES)) - { - return List.of(); - } - if (REQUIRE_SILK_TOUCH && EnchantmentHelper.getLevel(world.getRegistryManager().getOrThrow(RegistryKeys.ENCHANTMENT).getOrThrow(Enchantments.SILK_TOUCH), stack) < 1) - { - return List.of(); + return; } ItemStack item = state.getBlock().asItem().getDefaultStack(); NbtComponent component = Objects.requireNonNullElse(item.get(DataComponentTypes.CUSTOM_DATA), NbtComponent.DEFAULT); if (blockEntity instanceof MobSpawnerBlockEntity spawner) { EntityType type = ((IMobSpawnerLogic)spawner.getLogic()).silkspawners$getEntityType(world, world.getRandom(), pos); + Text name = Text.translatable("block.minecraft.spawner").formatted(Rarity.RARE.getFormatting()); if (type != null) { - component.apply(nbtCompound -> nbtCompound.put("entityType", EntityType.CODEC, type)); + component = component.apply(nbtCompound -> nbtCompound.put("entityType", EntityType.CODEC, type)); + name = Text.empty().append(type.getName()).append(" ").append(Text.translatable("block.minecraft.spawner")).formatted(Rarity.RARE.getFormatting()); } - + item.set(DataComponentTypes.ITEM_NAME, name); } else if (blockEntity instanceof TrialSpawnerBlockEntity trialSpawner) { - component.apply(nbtCompound -> nbtCompound.put("fullConfig", TrialSpawnerLogic.FullConfig.CODEC.codec(), ((ITrialSpawnerLogic)(Object)trialSpawner.getSpawner()).silkspawners$getFullConfig())); + ITrialSpawnerLogic logic = (ITrialSpawnerLogic)(Object)trialSpawner.getSpawner(); + if (logic == null) + { + return; + } + component = component.apply(nbtCompound -> { + nbtCompound.put("fullConfig", TrialSpawnerLogic.FullConfig.CODEC.codec(), logic.silkspawners$getFullConfig()); + nbtCompound.put("data", TrialSpawnerData.Packed.CODEC.codec(), trialSpawner.getSpawner().getData().pack()); + nbtCompound.putString("state", trialSpawner.getSpawnerState().name()); + }); + Text entityName = logic.silkspawners$getEntityName(world); + Text name = Text.translatable("block.minecraft.trial_spawner").formatted(Rarity.RARE.getFormatting()); + if (entityName != null) + { + name = Text.empty().append(logic.silkspawners$getEntityName(world)).append(" ").append(Text.translatable("block.minecraft.trial_spawner")).formatted(Rarity.RARE.getFormatting()); + } + item.set(DataComponentTypes.ITEM_NAME, name); } item.set(DataComponentTypes.CUSTOM_DATA, component); - return List.of(item); + cir.setReturnValue(List.of(item)); + } + + @ModifyArg(method="dropStacks(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/entity/BlockEntity;Lnet/minecraft/entity/Entity;Lnet/minecraft/item/ItemStack;)V", at=@At(value="INVOKE", target="Lnet/minecraft/block/BlockState;onStacksDropped(Lnet/minecraft/server/world/ServerWorld;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/item/ItemStack;Z)V"), index=3) + private static boolean dropStacks(boolean original, @Local(argsOnly=true) BlockState state, @Local(argsOnly=true) World world, @Local(argsOnly=true) BlockEntity blockEntity, @Local(argsOnly=true) ItemStack tool) + { + return shouldNotDrop(blockEntity, state.getBlock(), tool, world.getRegistryManager().getOrThrow(RegistryKeys.ENCHANTMENT)); } } diff --git a/src/main/java/com/kasetoatz/silkspawners/mixin/TrialSpawnerLogicMixin.java b/src/main/java/com/kasetoatz/silkspawners/mixin/TrialSpawnerLogicMixin.java index f22b679..532de7f 100644 --- a/src/main/java/com/kasetoatz/silkspawners/mixin/TrialSpawnerLogicMixin.java +++ b/src/main/java/com/kasetoatz/silkspawners/mixin/TrialSpawnerLogicMixin.java @@ -1,18 +1,67 @@ package com.kasetoatz.silkspawners.mixin; import com.kasetoatz.silkspawners.accessors.ITrialSpawnerLogic; +import net.minecraft.block.spawner.MobSpawnerEntry; +import net.minecraft.block.spawner.TrialSpawnerConfig; +import net.minecraft.block.spawner.TrialSpawnerData; import net.minecraft.block.spawner.TrialSpawnerLogic; +import net.minecraft.entity.EntityType; +import net.minecraft.storage.NbtReadView; +import net.minecraft.storage.ReadView; +import net.minecraft.text.Text; +import net.minecraft.util.ErrorReporter; +import net.minecraft.world.World; +import org.slf4j.Logger; +import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Mutable; import org.spongepowered.asm.mixin.Shadow; +import java.util.Optional; + @Mixin(TrialSpawnerLogic.class) -public class TrialSpawnerLogicMixin implements ITrialSpawnerLogic +public abstract class TrialSpawnerLogicMixin implements ITrialSpawnerLogic { @Shadow private TrialSpawnerLogic.FullConfig fullConfig; + @Mutable @Shadow @Final private TrialSpawnerData data; + @Shadow @Final private static Logger LOGGER; + @Shadow public abstract TrialSpawnerConfig getConfig(); @Override public TrialSpawnerLogic.FullConfig silkspawners$getFullConfig() { return fullConfig; } + + @Override + public void silkspawners$setFullConfig(World world, TrialSpawnerLogic.FullConfig fullConfig) + { + this.fullConfig = fullConfig; + } + + @Override + public void silkspawners$setData(TrialSpawnerData data) + { + this.data = data; + } + + @Override + public Text silkspawners$getEntityName(World world) + { + try (ErrorReporter.Logging logging = new ErrorReporter.Logging(LOGGER)) + { + Optional entry = getConfig().spawnPotentials().getOrEmpty(world.getRandom()); + if (entry.isEmpty()) + { + return null; + } + ReadView readView = NbtReadView.create(logging, world.getRegistryManager(), entry.get().entity()); + Optional> entity = EntityType.fromData(readView); + return entity.map(EntityType::getName).orElse(null); + } + catch (Exception ignored) + { + return null; + } + } } diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 18ac7be..def71d2 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -3,11 +3,16 @@ "id": "silkspawners", "version": "${version}", "name": "SilkSpawners", - "description": "", - "authors": [], - "contact": {}, + "description": "Mod that allows mining Spawners & Trial Spawners with Silk Touch.", + "authors": ["KaseToatz"], + "contact": { + "email": "kasetoatz@kasetoatz.com", + "homepage": "https://modrinth.com/mod/silkspawners", + "issues": "https://git.kasetoatz.com/KaseToatz/SilkSpawners/issues", + "sources": "https://git.kasetoatz.com/KaseToatz/SilkSpawners" + }, "license": "MIT", - "icon": "assets/silkspawners/icon.png", + "icon": "logo.png", "environment": "*", "entrypoints": { "main": [ diff --git a/src/main/resources/logo.png b/src/main/resources/logo.png new file mode 100644 index 0000000..2d44a0b Binary files /dev/null and b/src/main/resources/logo.png differ diff --git a/src/main/resources/silkspawners.mixins.json b/src/main/resources/silkspawners.mixins.json index 96ccb52..418ec4b 100644 --- a/src/main/resources/silkspawners.mixins.json +++ b/src/main/resources/silkspawners.mixins.json @@ -4,6 +4,7 @@ "package": "com.kasetoatz.silkspawners.mixin", "compatibilityLevel": "JAVA_21", "mixins": [ + "BlockItemMixin", "BlockMixin", "MobSpawnerLogicMixin", "TrialSpawnerLogicMixin"