diff --git a/src/generated/resources/assets/anvilcraft/items/auto_enchanting_table.json b/src/generated/resources/assets/anvilcraft/items/auto_enchanting_table.json new file mode 100644 index 0000000000..cba7d64307 --- /dev/null +++ b/src/generated/resources/assets/anvilcraft/items/auto_enchanting_table.json @@ -0,0 +1,6 @@ +{ + "model": { + "type": "minecraft:model", + "model": "anvilcraft:block/auto_enchanting_table" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/anvilcraft/lang/en_ud.json b/src/generated/resources/assets/anvilcraft/lang/en_ud.json index 2fcfc5c5e9..d9c0d3c104 100644 --- a/src/generated/resources/assets/anvilcraft/lang/en_ud.json +++ b/src/generated/resources/assets/anvilcraft/lang/en_ud.json @@ -299,6 +299,7 @@ "block.anvilcraft.amber_block": "ɹǝqɯⱯ ɟo ʞɔoꞁᗺ", "block.anvilcraft.ancient_sea_reef": "ɟǝǝᴚ ɐǝS ʇuǝᴉɔuⱯ", "block.anvilcraft.arrow": "ʍoɹɹⱯ", + "block.anvilcraft.auto_enchanting_table": "ǝꞁqɐ⟘ ᵷuᴉʇuɐɥɔuƎ oʇnⱯ", "block.anvilcraft.batch_crafter": "ɹǝʇɟɐɹƆ ɥɔʇɐᗺ", "block.anvilcraft.batch_cutter": "ɹǝʇʇnƆ ɥɔʇɐᗺ", "block.anvilcraft.berry_cake_block": "ʞɔoꞁᗺ ǝʞɐƆ ʎɹɹǝᗺ", @@ -887,6 +888,7 @@ "item.anvilcraft.anvil_hammer": "ɹǝɯɯɐH ꞁᴉʌuⱯ", "item.anvilcraft.anvil_railgun": "unᵷꞁᴉɐᴚ ꞁᴉʌuⱯ", "item.anvilcraft.arrow": "ʍoɹɹⱯ", + "item.anvilcraft.auto_enchanting_table": "ǝꞁqɐ⟘ ᵷuᴉʇuɐɥɔuƎ oʇnⱯ", "item.anvilcraft.batch_crafter": "ɹǝʇɟɐɹƆ ɥɔʇɐᗺ", "item.anvilcraft.batch_cutter": "ɹǝʇʇnƆ ɥɔʇɐᗺ", "item.anvilcraft.beef_mushroom_stew": "ʍǝʇS ɯooɹɥsnW ɟǝǝᗺ", @@ -1452,6 +1454,8 @@ "screen.anvilcraft.active_silencer.search": "ɥɔɹɐǝs oʇ pɹoʍʎǝʞ ɹǝʇuǝ", "screen.anvilcraft.active_silencer.title": "ɹǝɔuǝꞁᴉS ǝʌᴉʇɔⱯ", "screen.anvilcraft.anvil_hammer.title": "ʞɔoꞁᗺ ᵷuᴉʎɟᴉpoW", + "screen.anvilcraft.auto_enchanting_table.fluid_display": "%s :dxƎ", + "screen.anvilcraft.auto_enchanting_table.out_of_limit": "ǝɯnꞁoʌ pᴉnꞁɟ ǝɔuǝᴉɹǝdxǝ ɹo ʎʇᴉꞁᴉqɐ ʇuǝɯʇuɐɥɔuǝ ǝɥʇ uɐɥʇ ɹǝʇɐǝɹᵷ sᴉ sꞁǝʌǝꞁ ʇuǝɯʇuɐɥɔuǝ ꞁꞁɐ ɟo ɯns ǝɥ⟘", "screen.anvilcraft.batch_crafter.switch_recipe": "ǝdᴉɔǝɹ ᵷuᴉʇɟɐɹɔ ɥɔʇᴉʍS", "screen.anvilcraft.button.compare_mode_hysteresis": "sᴉsǝɹǝʇsʎH :ǝpoW", "screen.anvilcraft.button.compare_mode_window": "ʍopuᴉM :ǝpoW", @@ -2329,4 +2333,4 @@ "tooltip.anvilcraft.working_progress.progress": "%%%2$s %1$s ", "tooltip.anvilcraft.working_progress.time": "%2$s / %1$s ", "tooltip.anvilcraft.working_progress.title": ":ssǝɹᵷoɹd ᵷuᴉʞɹoM" -} \ No newline at end of file +} diff --git a/src/generated/resources/assets/anvilcraft/lang/en_us.json b/src/generated/resources/assets/anvilcraft/lang/en_us.json index eb81cd806a..17f30eb70e 100644 --- a/src/generated/resources/assets/anvilcraft/lang/en_us.json +++ b/src/generated/resources/assets/anvilcraft/lang/en_us.json @@ -299,6 +299,7 @@ "block.anvilcraft.amber_block": "Block of Amber", "block.anvilcraft.ancient_sea_reef": "Ancient Sea Reef", "block.anvilcraft.arrow": "Arrow", + "block.anvilcraft.auto_enchanting_table": "Auto Enchanting Table", "block.anvilcraft.batch_crafter": "Batch Crafter", "block.anvilcraft.batch_cutter": "Batch Cutter", "block.anvilcraft.berry_cake_block": "Berry Cake Block", @@ -887,6 +888,7 @@ "item.anvilcraft.anvil_hammer": "Anvil Hammer", "item.anvilcraft.anvil_railgun": "Anvil Railgun", "item.anvilcraft.arrow": "Arrow", + "item.anvilcraft.auto_enchanting_table": "Auto Enchanting Table", "item.anvilcraft.batch_crafter": "Batch Crafter", "item.anvilcraft.batch_cutter": "Batch Cutter", "item.anvilcraft.beef_mushroom_stew": "Beef Mushroom Stew", @@ -1452,6 +1454,8 @@ "screen.anvilcraft.active_silencer.search": "enter keyword to search", "screen.anvilcraft.active_silencer.title": "Active Silencer", "screen.anvilcraft.anvil_hammer.title": "Modifying Block", + "screen.anvilcraft.auto_enchanting_table.fluid_display": "Exp: %s", + "screen.anvilcraft.auto_enchanting_table.out_of_limit": "The sum of all enchantment levels is greater than the enchantment ability or experience fluid volume", "screen.anvilcraft.batch_crafter.switch_recipe": "Switch crafting recipe", "screen.anvilcraft.button.compare_mode_hysteresis": "Mode: Hysteresis", "screen.anvilcraft.button.compare_mode_window": "Mode: Window", @@ -2329,4 +2333,4 @@ "tooltip.anvilcraft.working_progress.progress": " %1$s %2$s%%", "tooltip.anvilcraft.working_progress.time": " %1$s / %2$s", "tooltip.anvilcraft.working_progress.title": "Working progress:" -} \ No newline at end of file +} diff --git a/src/generated/resources/data/anvilcraft/advancement/recipes/misc/auto_enchanting_table.json b/src/generated/resources/data/anvilcraft/advancement/recipes/misc/auto_enchanting_table.json new file mode 100644 index 0000000000..dd11240d72 --- /dev/null +++ b/src/generated/resources/data/anvilcraft/advancement/recipes/misc/auto_enchanting_table.json @@ -0,0 +1,65 @@ +{ + "parent": "minecraft:recipes/root", + "criteria": { + "has_enchanting_table": { + "conditions": { + "items": [ + { + "items": "minecraft:enchanting_table" + } + ] + }, + "trigger": "minecraft:inventory_changed" + }, + "has_fluid_tank": { + "conditions": { + "items": [ + { + "items": "anvilcraft:fluid_tank" + } + ] + }, + "trigger": "minecraft:inventory_changed" + }, + "has_magnetoelectric_core": { + "conditions": { + "items": [ + { + "items": "anvilcraft:magnetoelectric_core" + } + ] + }, + "trigger": "minecraft:inventory_changed" + }, + "has_royal_steel_ingot": { + "conditions": { + "items": [ + { + "items": "anvilcraft:royal_steel_ingot" + } + ] + }, + "trigger": "minecraft:inventory_changed" + }, + "has_the_recipe": { + "conditions": { + "recipe": "anvilcraft:auto_enchanting_table" + }, + "trigger": "minecraft:recipe_unlocked" + } + }, + "requirements": [ + [ + "has_the_recipe", + "has_fluid_tank", + "has_enchanting_table", + "has_royal_steel_ingot", + "has_magnetoelectric_core" + ] + ], + "rewards": { + "recipes": [ + "anvilcraft:auto_enchanting_table" + ] + } +} \ No newline at end of file diff --git a/src/generated/resources/data/anvilcraft/loot_table/blocks/auto_enchanting_table.json b/src/generated/resources/data/anvilcraft/loot_table/blocks/auto_enchanting_table.json new file mode 100644 index 0000000000..6dde90ffb5 --- /dev/null +++ b/src/generated/resources/data/anvilcraft/loot_table/blocks/auto_enchanting_table.json @@ -0,0 +1,21 @@ +{ + "type": "minecraft:block", + "pools": [ + { + "bonus_rolls": 0.0, + "conditions": [ + { + "condition": "minecraft:survives_explosion" + } + ], + "entries": [ + { + "type": "minecraft:item", + "name": "anvilcraft:auto_enchanting_table" + } + ], + "rolls": 1.0 + } + ], + "random_sequence": "anvilcraft:blocks/auto_enchanting_table" +} \ No newline at end of file diff --git a/src/generated/resources/data/anvilcraft/recipe/auto_enchanting_table.json b/src/generated/resources/data/anvilcraft/recipe/auto_enchanting_table.json new file mode 100644 index 0000000000..8ed72e757e --- /dev/null +++ b/src/generated/resources/data/anvilcraft/recipe/auto_enchanting_table.json @@ -0,0 +1,17 @@ +{ + "type": "minecraft:crafting_shaped", + "category": "misc", + "key": { + "E": "minecraft:enchanting_table", + "R": "anvilcraft:royal_steel_ingot", + "T": "anvilcraft:fluid_tank", + "e": "anvilcraft:magnetoelectric_core" + }, + "pattern": [ + "TET", + "ReR" + ], + "result": { + "id": "anvilcraft:auto_enchanting_table" + } +} \ No newline at end of file diff --git a/src/generated/resources/data/minecraft/tags/block/enchantment_power_provider.json b/src/generated/resources/data/minecraft/tags/block/enchantment_power_provider.json new file mode 100644 index 0000000000..82f62fce6c --- /dev/null +++ b/src/generated/resources/data/minecraft/tags/block/enchantment_power_provider.json @@ -0,0 +1,5 @@ +{ + "values": [ + "anvilcraft:transcendium_block" + ] +} \ No newline at end of file diff --git a/src/generated/resources/data/minecraft/tags/block/mineable/pickaxe.json b/src/generated/resources/data/minecraft/tags/block/mineable/pickaxe.json index e00846d52c..4fad7a370e 100644 --- a/src/generated/resources/data/minecraft/tags/block/mineable/pickaxe.json +++ b/src/generated/resources/data/minecraft/tags/block/mineable/pickaxe.json @@ -58,6 +58,7 @@ "anvilcraft:exp_collector", "anvilcraft:charger", "anvilcraft:discharger", + "anvilcraft:auto_enchanting_table", "anvilcraft:active_silencer", "anvilcraft:block_placer", "anvilcraft:smart_block_placer", diff --git a/src/main/java/dev/dubhe/anvilcraft/block/entity/AutoEnchantingTableBlockEntity.java b/src/main/java/dev/dubhe/anvilcraft/block/entity/AutoEnchantingTableBlockEntity.java new file mode 100644 index 0000000000..71a9f8ed32 --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/block/entity/AutoEnchantingTableBlockEntity.java @@ -0,0 +1,579 @@ +package dev.dubhe.anvilcraft.block.entity; + +import com.mojang.serialization.Codec; +import dev.dubhe.anvilcraft.api.power.IPowerConsumer; +import dev.dubhe.anvilcraft.api.power.PowerGrid; +import dev.dubhe.anvilcraft.block.workstation.AutoEnchantingTableBlock; +import dev.dubhe.anvilcraft.init.ModMenuTypes; +import dev.dubhe.anvilcraft.init.block.ModFluids; +import dev.dubhe.anvilcraft.inventory.AutoEnchantingTableMenu; +import it.unimi.dsi.fastutil.ints.IntArraySet; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import lombok.Getter; +import lombok.Setter; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.Holder; +import net.minecraft.core.HolderLookup; +import net.minecraft.core.HolderSet; +import net.minecraft.core.IdMap; +import net.minecraft.core.NonNullList; +import net.minecraft.core.RegistryAccess; +import net.minecraft.core.registries.Registries; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.Component; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.ClientGamePacketListener; +import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.sounds.SoundSource; +import net.minecraft.tags.EnchantmentTags; +import net.minecraft.util.Mth; +import net.minecraft.util.ProblemReporter; +import net.minecraft.util.RandomSource; +import net.minecraft.world.Container; +import net.minecraft.world.ContainerHelper; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.WorldlyContainer; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.enchantment.Enchantment; +import net.minecraft.world.item.enchantment.EnchantmentHelper; +import net.minecraft.world.item.enchantment.EnchantmentInstance; +import net.minecraft.world.item.enchantment.ItemEnchantments; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.EnchantingTableBlock; +import net.minecraft.world.level.block.entity.BaseContainerBlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.storage.TagValueOutput; +import net.minecraft.world.level.storage.ValueInput; +import net.minecraft.world.level.storage.ValueOutput; +import net.neoforged.neoforge.fluids.FluidStack; +import net.neoforged.neoforge.transfer.fluid.FluidResource; +import net.neoforged.neoforge.transfer.fluid.FluidStacksResourceHandler; +import net.neoforged.neoforge.transfer.fluid.FluidUtil; +import net.neoforged.neoforge.transfer.transaction.Transaction; +import org.jspecify.annotations.Nullable; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.Consumer; + +public class AutoEnchantingTableBlockEntity extends BaseContainerBlockEntity implements WorldlyContainer, IPowerConsumer { + @Getter + @Setter + @Nullable + private PowerGrid grid; + /// 索引0:物品输入 1:物品输出 2:引物 + private NonNullList items = NonNullList.withSize(3, ItemStack.EMPTY); + @Getter + private final Set selectedEnchantmentSet = new IntArraySet(); + @Getter + @Setter + private ItemStack lastPrologueItem = ItemStack.EMPTY; + @Getter + private Runnable prologueSlotUpdateListener = () -> {}; + @Getter + @Setter + private Consumer slotChangedListener = (_) -> {}; + @Setter + private boolean isOpenMenu = false; + + private int enchantmentSeed = 0; + private final RandomSource random = RandomSource.create(); + + @Getter + private final FluidStacksResourceHandler fluidHandler = new FluidStacksResourceHandler(1, 32_000) { + @Override + public boolean isValid(int index, FluidResource resource) { + return resource.is(ModFluids.EXP_FLUID); + } + + @Override + protected void onContentsChanged(int index, FluidStack previousContents) { + AutoEnchantingTableBlockEntity.this.onChange(); + } + }; + + private int cooldown = 0; + // region 控制书本的渲染 + public int time; + public float rot; + public float oldRot; + public float targetRot; + public float open; + public float oldOpen; + public float flip; + public float oldFlip; + public float flipT; + public float flipA; + // endregion + + public AutoEnchantingTableBlockEntity(BlockEntityType type, BlockPos pos, BlockState state) { + super(type, pos, state); + } + + public ItemStack getDisplayInputItem() { + return this.items.getFirst(); + } + + public ItemStack getDisplayOutputItem() { + return this.items.get(1); + } + + public void bookAnimationTick( + final Level level, + final BlockPos worldPosition, + final BlockState state + ) { + this.oldOpen = this.open; + this.oldRot = this.rot; + this.targetRot += 0.02F; + + if (!this.getItem(0).isEmpty()) { + this.open += 0.1f; + if (this.open < 0.5f || this.random.nextInt(40) == 0) { + float old = this.flipT; + + do { + this.flipT = this.flipT + (this.random.nextInt(4) - this.random.nextInt(4)); + } while (old == this.flipT); + } + } else { + this.open -= 0.1f; + } + + while (this.rot >= (float) Math.PI) { + this.rot -= (float) (Math.PI * 2); + } + + while (this.rot < (float) -Math.PI) { + this.rot += (float) (Math.PI * 2); + } + + while (this.targetRot >= (float) Math.PI) { + this.targetRot -= (float) (Math.PI * 2); + } + + while (this.targetRot < (float) -Math.PI) { + this.targetRot += (float) (Math.PI * 2); + } + + float rotDir = this.targetRot - this.rot; + + while (rotDir >= (float) Math.PI) { + rotDir -= (float) (Math.PI * 2); + } + + while (rotDir < (float) -Math.PI) { + rotDir += (float) (Math.PI * 2); + } + + this.rot += rotDir * 0.4F; + this.open = Mth.clamp(this.open, 0, 1f); + this.time++; + this.oldFlip = this.flip; + float diff = (this.flipT - this.flip) * 0.4f; + diff = Mth.clamp(diff, -0.2f, 0.2f); + this.flipA = this.flipA + (diff - this.flipA) * 0.9f; + this.flip = this.flip + this.flipA; + } + + public void serverTick( + final Level level, + final BlockPos pos, + final BlockState state + ) { + if (this.grid == null) { + level.setBlockAndUpdate(this.getBlockPos(), this.getBlockState().setValue(AutoEnchantingTableBlock.OVERLOAD, true)); + return; + } + if (this.grid.isWorking()) { + if (this.getBlockState().getValue(AutoEnchantingTableBlock.OVERLOAD)) { + level.setBlockAndUpdate(this.getBlockPos(), this.getBlockState().setValue(AutoEnchantingTableBlock.OVERLOAD, false)); + } + } else { + if (!this.getBlockState().getValue(AutoEnchantingTableBlock.OVERLOAD)) { + level.setBlockAndUpdate(this.getBlockPos(), this.getBlockState().setValue(AutoEnchantingTableBlock.OVERLOAD, true)); + } + return; + } + if (this.cooldown > 0) { + this.cooldown--; + } else if (this.cooldown == 0) { + this.cooldown = 80; + if (!this.getItem(1).isEmpty() || this.getItem(0).isEmpty()) { + return; + } + ItemStack enchantItem = this.getItem(0).copyWithCount(1); + if (!enchantItem.isEnchantable()) { + this.setItem(0, ItemStack.EMPTY); + this.setItem(1, enchantItem); + return; + } + ItemStack prologueItem = this.getItem(2); + if (prologueItem.isEmpty()) { + this.lastPrologueItem = ItemStack.EMPTY; + // 无引物模式 + int exp = Mth.clamp(this.getBookShelf(level, pos) * 400, 0, 6000); + int[][] costAndEnchant = this.getCostAndEnchant(enchantItem); + if (costAndEnchant.length == 0) { + return; + } + int index = level.getRandom().nextInt(0, 3); + if (exp <= 0) { + exp = (7 + 2 * index) * 20; + } + if (this.fluidHandler.getAmountAsInt(0) < exp) { + return; + } + int[] enchant = costAndEnchant[index]; + List enchantmentList = this.getEnchantmentList(level.registryAccess(), enchantItem, index, enchant[0]); + if (enchantItem.is(Items.BOOK)) { + enchantItem = enchantItem.transmuteCopy(Items.ENCHANTED_BOOK, 1); + } + if (!enchantmentList.isEmpty()) { + for (EnchantmentInstance enchantmentInstance : enchantmentList) { + enchantItem.enchant(enchantmentInstance.enchantment(), enchantmentInstance.level()); + } + } + this.setItem(0, ItemStack.EMPTY); + this.setItem(1, enchantItem); + try (Transaction ts = Transaction.openRoot()) { + this.fluidHandler.extract(FluidResource.of(ModFluids.EXP_FLUID), exp, ts); + ts.commit(); + } + this.enchantmentSeed = level.getRandom().nextInt(); + level.playSound( + null, + this.getBlockPos(), + SoundEvents.ENCHANTMENT_TABLE_USE, + SoundSource.BLOCKS, + 1.0F, + level.getRandom().nextFloat() * 0.1F + 0.9F + ); + } else { + if (this.isOpenMenu) { + return; + } + if (this.selectedEnchantmentSet.isEmpty()) { + return; + } + IdMap> idMap = level.registryAccess().lookupOrThrow(Registries.ENCHANTMENT).asHolderIdMap(); + int totalLevel = 0; + for (int id : this.getSelectedEnchantmentSet()) { + Holder enchantmentHolder = idMap.byId(id); + if (enchantmentHolder != null) { + totalLevel += enchantmentHolder.value().getMaxLevel(); + } + } + if (totalLevel > this.getBookShelf(level, pos) + || totalLevel * 400 > this.fluidHandler.getAmountAsInt(0)) { + return; + } + ItemStack enchantedBook = Items.ENCHANTED_BOOK.getDefaultInstance(); + for (int id : this.selectedEnchantmentSet) { + Holder enchantmentHolder = idMap.byId(id); + if (enchantmentHolder == null) { + continue; + } + boolean compatible = true; + for (Object2IntMap.Entry> existing + : EnchantmentHelper.getEnchantmentsForCrafting(enchantedBook).entrySet()) { + if (!Enchantment.areCompatible(enchantmentHolder, existing.getKey())) { + compatible = false; + break; + } + } + if (!compatible) { + continue; + } + enchantedBook.enchant(enchantmentHolder, enchantmentHolder.value().getMaxLevel()); + } + if (enchantItem.is(Items.BOOK)) { + this.setItem(0, ItemStack.EMPTY); + this.setItem(1, enchantedBook); + } else { + AutoEnchantingTableBlockEntity.applyEnchantment(enchantItem, enchantedBook); + this.setItem(0, ItemStack.EMPTY); + this.setItem(1, enchantItem); + } + try (Transaction ts = Transaction.openRoot()) { + this.fluidHandler.extract(FluidResource.of(ModFluids.EXP_FLUID), totalLevel * 400, ts); + ts.commit(); + } + level.playSound( + null, + this.getBlockPos(), + SoundEvents.ENCHANTMENT_TABLE_USE, + SoundSource.BLOCKS, + 1.0F, + level.getRandom().nextFloat() * 0.1F + 0.9F + ); + } + } + } + + public boolean onPlayerUse(Player player, InteractionHand hand) { + try (Transaction transaction = Transaction.openRoot()) { + boolean success = FluidUtil.interactWithFluidHandler(player, hand, this.getBlockPos(), this.getFluidHandler(), transaction); + if (success) transaction.commit(); + return success; + } + } + + public static void applyEnchantment(ItemStack item, ItemStack enchantedBook) { + ItemEnchantments enchantmentsOnRight = EnchantmentHelper.getEnchantmentsForCrafting(enchantedBook); + for (Object2IntMap.Entry> entry : enchantmentsOnRight.entrySet()) { + Holder holder = entry.getKey(); + if (!item.supportsEnchantment(holder)) { + continue; + } + boolean compatible = true; + for (Object2IntMap.Entry> existing : EnchantmentHelper.getEnchantmentsForCrafting(item).entrySet()) { + if (!Enchantment.areCompatible(holder, existing.getKey())) { + compatible = false; + break; + } + } + if (!compatible) { + continue; + } + item.enchant(holder, entry.getIntValue()); + } + } + + private int getBookShelf(Level level, BlockPos pos) { + float bookcases = 0; + for (BlockPos offset : AutoEnchantingTableBlock.BOOKSHELF_OFFSETS) { + if (EnchantingTableBlock.isValidBookShelf(level, pos, offset)) { + bookcases += level.getBlockState(pos.offset(offset)).getEnchantPowerBonus(level, pos.offset(offset)); + } + } + return (int) bookcases; + } + + private int[][] getCostAndEnchant(ItemStack itemStack) { + int[] costs = new int[3]; + int[] enchantClue = new int[]{-1, -1, -1}; + int[] levelClue = new int[]{-1, -1, -1}; + if (itemStack.isEmpty() || !itemStack.isEnchantable()) { + return new int[][]{}; + } + if (this.level != null) { + final IdMap> idMap = this.level.registryAccess().lookupOrThrow(Registries.ENCHANTMENT).asHolderIdMap(); + float bookcases = this.getBookShelf(this.level, this.getBlockPos()); + if (this.enchantmentSeed == 0) { + this.enchantmentSeed = this.level.getRandom().nextInt(); + } + this.random.setSeed(this.enchantmentSeed); + for (int ixx = 0; ixx < 3; ixx++) { + costs[ixx] = EnchantmentHelper.getEnchantmentCost(this.random, ixx, (int) bookcases, itemStack); + if (costs[ixx] < ixx + 1) { + costs[ixx] = 0; + } + } + + for (int ix = 0; ix < 3; ix++) { + if (costs[ix] > 0) { + List list = this.getEnchantmentList(this.level.registryAccess(), itemStack, ix, costs[ix]); + if (!list.isEmpty()) { + EnchantmentInstance enchant = list.get(this.random.nextInt(list.size())); + enchantClue[ix] = idMap.getId(enchant.enchantment()); + levelClue[ix] = enchant.level(); + } + } + } + return new int[][]{ + new int[] { costs[0], enchantClue[0], levelClue[0] }, + new int[] { costs[1], enchantClue[1], levelClue[1] }, + new int[] { costs[2], enchantClue[2], levelClue[2] } + }; + } + return new int[][]{}; + } + + private List getEnchantmentList( + final RegistryAccess access, + final ItemStack itemStack, + final int slot, + final int enchantmentCost + ) { + this.random.setSeed(this.enchantmentSeed + slot); + Optional> tag = access.lookupOrThrow(Registries.ENCHANTMENT).get(EnchantmentTags.IN_ENCHANTING_TABLE); + if (tag.isEmpty()) { + return List.of(); + } + + List list = EnchantmentHelper.selectEnchantment(this.random, itemStack, enchantmentCost, tag.get().stream()); + if (itemStack.is(Items.BOOK) && list.size() > 1) { + list.remove(this.random.nextInt(list.size())); + } + + return list; + } + + public void registerUpdateListener(final Runnable prologueSlotUpdateListener) { + this.prologueSlotUpdateListener = prologueSlotUpdateListener; + } + + @Override + public void preRemoveSideEffects(BlockPos pos, BlockState state) { + Level level = this.level; + if (level != null) { + for (ItemStack stack : this.items) { + if (stack.isEmpty()) { + continue; + } + Block.popResource(level, pos, stack); + } + } + } + + @Override + public void setChanged() { + super.setChanged(); + this.slotChangedListener.accept(this); + } + + @Override + protected void saveAdditional(ValueOutput output) { + super.saveAdditional(output); + ContainerHelper.saveAllItems(output, this.items); + this.fluidHandler.serialize(output); + output.putInt("cooldown", this.cooldown); + output.store("LastPrologueItem", ItemStack.OPTIONAL_CODEC, this.lastPrologueItem); + ValueOutput.TypedOutputList selectedEnchantments = output.list("selectedEnchantments", Codec.INT); + for (Integer id : this.selectedEnchantmentSet) { + selectedEnchantments.add(id); + } + output.putBoolean("openMenu", this.isOpenMenu); + } + + @Override + protected void loadAdditional(ValueInput input) { + super.loadAdditional(input); + Collections.fill(this.items, ItemStack.EMPTY); + ContainerHelper.loadAllItems(input, this.items); + this.fluidHandler.deserialize(input); + input.getInt("cooldown").ifPresent((cooldown) -> this.cooldown = cooldown); + input.read("LastPrologueItem", ItemStack.OPTIONAL_CODEC).ifPresent(this::setLastPrologueItem); + this.selectedEnchantmentSet.clear(); + for (Integer id : input.listOrEmpty("selectedEnchantments", Codec.INT)) { + this.selectedEnchantmentSet.add(id); + } + this.isOpenMenu = input.getBooleanOr("openMenu", false); + } + + @Override + public @Nullable Packet getUpdatePacket() { + return ClientboundBlockEntityDataPacket.create(this); + } + + @Override + public CompoundTag getUpdateTag(HolderLookup.Provider registries) { + TagValueOutput output = TagValueOutput.createWithContext(new ProblemReporter.Collector(this.problemPath()), registries); + this.saveAdditional(output); + return output.buildResult(); + } + + public void onChange() { + this.setChanged(); + if (this.level != null) { + if (this.level instanceof ServerLevel serverLevel) { + Packet packet = this.getUpdatePacket(); + if (packet != null) { + serverLevel.getServer().getPlayerList().broadcast( + null, + this.getBlockPos().getX() + 0.5, + this.getBlockPos().getY() + 0.5, + this.getBlockPos().getZ() + 0.5, + 64, + serverLevel.dimension(), + packet + ); + } + } + this.level.sendBlockUpdated(this.getBlockPos(), this.getBlockState(), this.getBlockState(), 2); + } + } + + @Override + public int getContainerSize() { + return this.items.size(); + } + + @Override + protected Component getDefaultName() { + return Component.translatable("block.anvilcraft.auto_enchanting_table"); + } + + @Override + public NonNullList getItems() { + return this.items; + } + + @Override + protected void setItems(NonNullList items) { + this.items = items; + } + + @Override + public void setItem(int slot, ItemStack itemStack) { + this.items.set(slot, itemStack.copyWithCount(1)); + this.onChange(); + } + + @Override + public int getMaxStackSize() { + return 1; + } + + @Override + public int getMaxStackSize(ItemStack itemStack) { + return 1; + } + + @Override + protected AbstractContainerMenu createMenu(int containerId, Inventory inventory) { + return new AutoEnchantingTableMenu(ModMenuTypes.AUTO_ENCHANTING_TABLE.get(), containerId, inventory, this); + } + + @Override + public int[] getSlotsForFace(Direction direction) { + return new int[] { 0, 1 }; + } + + @Override + public boolean canPlaceItemThroughFace(int slot, ItemStack itemStack, @Nullable Direction direction) { + return slot == 0 && this.items.getFirst().isEmpty(); + } + + @Override + public boolean canTakeItemThroughFace(int slot, ItemStack itemStack, Direction direction) { + return slot == 1 && !this.items.get(1).isEmpty(); + } + + @Override + public @Nullable Level getCurrentLevel() { + return this.level; + } + + @Override + public BlockPos getPos() { + return this.getBlockPos(); + } + + @Override + public int getInputPower() { + return this.items.get(2).isEmpty() ? 16 : 64; + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/block/entity/CreativeCrateBlockEntity.java b/src/main/java/dev/dubhe/anvilcraft/block/entity/CreativeCrateBlockEntity.java index 83ea4bbfa4..22b6d1a562 100644 --- a/src/main/java/dev/dubhe/anvilcraft/block/entity/CreativeCrateBlockEntity.java +++ b/src/main/java/dev/dubhe/anvilcraft/block/entity/CreativeCrateBlockEntity.java @@ -9,18 +9,17 @@ import net.minecraft.core.component.DataComponentGetter; import net.minecraft.core.component.DataComponentMap; import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.NbtOps; -import net.minecraft.nbt.Tag; import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.game.ClientGamePacketListener; import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket; -import net.minecraft.resources.RegistryOps; +import net.minecraft.util.ProblemReporter; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.storage.TagValueOutput; import net.minecraft.world.level.storage.ValueInput; import net.minecraft.world.level.storage.ValueOutput; import net.neoforged.neoforge.transfer.ResourceHandler; @@ -69,12 +68,9 @@ protected void collectImplicitComponents(DataComponentMap.Builder components) { @Override public CompoundTag getUpdateTag(HolderLookup.Provider registries) { - CompoundTag tag = super.getUpdateTag(registries); - RegistryOps ops = registries.createSerializationContext(NbtOps.INSTANCE); - if (!this.itemHandler.isEmpty()) { - tag.store("item", ItemStack.CODEC, ops, this.itemHandler.getStack()); - } - return tag; + TagValueOutput output = TagValueOutput.createWithContext(new ProblemReporter.Collector(this.problemPath()), registries); + this.saveAdditional(output); + return output.buildResult(); } @Override diff --git a/src/main/java/dev/dubhe/anvilcraft/block/power/batch/BaseBatchCraftingBlock.java b/src/main/java/dev/dubhe/anvilcraft/block/power/batch/BaseBatchCraftingBlock.java index 42fd1d43b7..43b4f4d67f 100644 --- a/src/main/java/dev/dubhe/anvilcraft/block/power/batch/BaseBatchCraftingBlock.java +++ b/src/main/java/dev/dubhe/anvilcraft/block/power/batch/BaseBatchCraftingBlock.java @@ -107,20 +107,10 @@ protected abstract InteractionResult playerUse( public abstract Item getToastSymbol(); - // @Override - // protected void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean movedByPiston) { - // if (state.is(newState.getBlock())) return; - // if (level.getBlockEntity(pos) instanceof IItemHandlerHolder holder) { - // Vec3 vec3 = pos.getCenter(); - // ResourceHandler handler = holder.getItemHandler(); - // for (int slot = 0; slot < handler.getSlots(); slot++) { - // Containers.dropItemStack(level, vec3.x, vec3.y, vec3.z, handler.getStackInSlot(slot)); - // } - // ItemHandlerUtil.dropAllToPos(); - // level.updateNeighbourForOutputSignal(pos, this); - // } - // super.onRemove(state, level, pos, newState, movedByPiston); - // } + @Override + protected void affectNeighborsAfterRemoval(BlockState state, ServerLevel level, BlockPos pos, boolean movedByPiston) { + level.updateNeighbourForOutputSignal(pos, this); + } @Override public boolean useShapeForLightOcclusion(BlockState state) { diff --git a/src/main/java/dev/dubhe/anvilcraft/block/storage/TranscendiumBlock.java b/src/main/java/dev/dubhe/anvilcraft/block/storage/TranscendiumBlock.java index 04bc9cc009..571478b98b 100644 --- a/src/main/java/dev/dubhe/anvilcraft/block/storage/TranscendiumBlock.java +++ b/src/main/java/dev/dubhe/anvilcraft/block/storage/TranscendiumBlock.java @@ -1,10 +1,18 @@ package dev.dubhe.anvilcraft.block.storage; import dev.dubhe.anvilcraft.api.block.ITranscendiumBlock; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; public class TranscendiumBlock extends Block implements ITranscendiumBlock { public TranscendiumBlock(Properties properties) { super(properties); } + + @Override + public float getEnchantPowerBonus(BlockState state, BlockGetter level, BlockPos pos) { + return 10f; + } } diff --git a/src/main/java/dev/dubhe/anvilcraft/block/workstation/AutoEnchantingTableBlock.java b/src/main/java/dev/dubhe/anvilcraft/block/workstation/AutoEnchantingTableBlock.java new file mode 100644 index 0000000000..493d3d0c58 --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/block/workstation/AutoEnchantingTableBlock.java @@ -0,0 +1,149 @@ +package dev.dubhe.anvilcraft.block.workstation; + +import com.mojang.serialization.MapCodec; +import dev.dubhe.anvilcraft.api.power.IPowerComponent; +import dev.dubhe.anvilcraft.block.better.BetterBaseEntityBlock; +import dev.dubhe.anvilcraft.block.entity.AutoEnchantingTableBlockEntity; +import dev.dubhe.anvilcraft.init.ModMenuTypes; +import dev.dubhe.anvilcraft.init.block.ModBlockEntities; +import net.minecraft.core.BlockPos; +import net.minecraft.core.particles.ParticleTypes; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.util.RandomSource; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.GameType; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.BaseEntityBlock; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.EnchantingTableBlock; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityTicker; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.BooleanProperty; +import net.minecraft.world.level.pathfinder.PathComputationType; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.VoxelShape; +import org.jspecify.annotations.Nullable; + +import java.util.List; + +public class AutoEnchantingTableBlock extends BetterBaseEntityBlock { + public static final BooleanProperty OVERLOAD = IPowerComponent.OVERLOAD; + + private static final VoxelShape SHAPE = Block.column(16.0, 0.0, 12.0); + + public static final List BOOKSHELF_OFFSETS = BlockPos.betweenClosedStream(-2, 0, -2, 2, 1, 2) + .filter(pos -> Math.abs(pos.getX()) == 2 || Math.abs(pos.getZ()) == 2) + .map(BlockPos::immutable) + .toList(); + + @Override + protected MapCodec codec() { + return simpleCodec(AutoEnchantingTableBlock::new); + } + + public AutoEnchantingTableBlock(Properties properties) { + super(properties); + this.registerDefaultState(this.getStateDefinition().any() + .setValue(OVERLOAD, true)); + } + + @Override + protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) { + return SHAPE; + } + + @Override + protected boolean useShapeForLightOcclusion(BlockState state) { + return true; + } + + @Override + protected InteractionResult useItemOn( + ItemStack stack, + BlockState state, + Level level, + BlockPos pos, + Player player, + InteractionHand hand, + BlockHitResult hitResult + ) { + if (level.getBlockEntity(pos) instanceof AutoEnchantingTableBlockEntity autoEnchantingTableBlockEntity) { + if (autoEnchantingTableBlockEntity.onPlayerUse(player, hand)) { + return InteractionResult.SUCCESS; + } + } + return super.useItemOn(stack, state, level, pos, player, hand, hitResult); + } + + @Override + public InteractionResult use(BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { + if (level.isClientSide()) { + return InteractionResult.SUCCESS; + } + BlockEntity blockEntity = level.getBlockEntity(pos); + if (blockEntity instanceof AutoEnchantingTableBlockEntity autoEnchantingTableBlockEntity) { + if (player instanceof ServerPlayer serverPlayer) { + if (serverPlayer.gameMode.getGameModeForPlayer() == GameType.SPECTATOR) { + return InteractionResult.PASS; + } + ModMenuTypes.open(serverPlayer, autoEnchantingTableBlockEntity, pos); + autoEnchantingTableBlockEntity.setOpenMenu(true); + } + } + return InteractionResult.SUCCESS; + } + + @Override + public void animateTick(BlockState state, Level level, BlockPos pos, RandomSource random) { + super.animateTick(state, level, pos, random); + + for (BlockPos offset : BOOKSHELF_OFFSETS) { + + if (random.nextInt(16) == 0 && EnchantingTableBlock.isValidBookShelf(level, pos, offset)) { + level.addParticle( + ParticleTypes.ENCHANT, + pos.getX() + 0.5, + pos.getY() + 2.0, + pos.getZ() + 0.5, + offset.getX() + random.nextFloat() - 0.5, + offset.getY() - random.nextFloat() - 1.0F, + offset.getZ() + random.nextFloat() - 0.5 + ); + } + } + } + + @Override + public BlockEntity newBlockEntity(BlockPos worldPosition, BlockState blockState) { + return ModBlockEntities.AUTO_ENCHANTING_TABLE.create(worldPosition, blockState); + } + + @Override + public @Nullable BlockEntityTicker getTicker(Level level, BlockState blockState, BlockEntityType type) { + return level.isClientSide() + ? createTickerHelper( + type, ModBlockEntities.AUTO_ENCHANTING_TABLE.get(), + (world, pos, state, be) -> be.bookAnimationTick(world, pos, state)) + : createTickerHelper( + type, ModBlockEntities.AUTO_ENCHANTING_TABLE.get(), ( + world, pos, state, be) -> be.serverTick(world, pos, state)); + } + + @Override + protected boolean isPathfindable(BlockState state, PathComputationType type) { + return false; + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + builder.add(OVERLOAD); + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/client/gui/component/AutoEnchantingTableButton.java b/src/main/java/dev/dubhe/anvilcraft/client/gui/component/AutoEnchantingTableButton.java new file mode 100644 index 0000000000..e6293ce8a2 --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/client/gui/component/AutoEnchantingTableButton.java @@ -0,0 +1,63 @@ +package dev.dubhe.anvilcraft.client.gui.component; + +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import lombok.Getter; +import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.client.input.InputWithModifiers; +import net.minecraft.core.Holder; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.Identifier; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.enchantment.Enchantment; + +import java.util.List; + +public class AutoEnchantingTableButton extends TriStateButton { + @Getter + private Holder holder; + private final OnPress onPress; + + public AutoEnchantingTableButton( + int x, + int y, + int width, + int height, + Identifier texture, + Holder enchantmentHolder, + int textureWidth, + int textureHeight, + int[] stateOffset, + OnPress onPress, + List tooltips + ) { + super(x, y, width, height, texture, textureWidth, textureHeight, stateOffset, (ignore) -> {}, List.of()); + this.onPress = onPress; + this.holder = enchantmentHolder; + List tooltip = new ObjectArrayList<>(); + tooltip.add(Enchantment.getFullname(this.holder, this.holder.value().getMaxLevel())); + tooltip.addAll(tooltips); + this.setTooltips(tooltip); + } + + @Override + protected void extractContents(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float a) { + super.extractContents(graphics, mouseX, mouseY, a); + ItemStack enchantedBookItem = Items.ENCHANTED_BOOK.getDefaultInstance(); + enchantedBookItem.enchant(this.holder, this.holder.value().getMaxLevel()); + int y = this.getY(); + if (this.selected) { + y++; + } + graphics.item(enchantedBookItem, this.getX() + 1, y); + } + + @Override + public void onPress(InputWithModifiers input) { + this.onPress.onPress(this); + } + + public interface OnPress { + void onPress(AutoEnchantingTableButton btn); + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/client/gui/component/FluidDisplayWidget.java b/src/main/java/dev/dubhe/anvilcraft/client/gui/component/FluidDisplayWidget.java new file mode 100644 index 0000000000..9921fa8e6b --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/client/gui/component/FluidDisplayWidget.java @@ -0,0 +1,90 @@ +package dev.dubhe.anvilcraft.client.gui.component; + +import dev.dubhe.anvilcraft.AnvilCraft; +import lombok.Getter; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.client.gui.components.AbstractWidget; +import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.client.input.MouseButtonInfo; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.renderer.RenderPipelines; +import net.minecraft.core.Holder; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.Identifier; +import net.minecraft.world.level.material.Fluid; +import net.neoforged.neoforge.transfer.fluid.FluidResource; +import net.neoforged.neoforge.transfer.fluid.FluidStacksResourceHandler; +import org.jspecify.annotations.Nullable; + +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; + +public class FluidDisplayWidget extends AbstractWidget { + @Getter + private final FluidStacksResourceHandler fluidHandler; + + public FluidDisplayWidget( + int x, + int y, + int width, + int height, + FluidStacksResourceHandler fluidHandler, + Function message + ) { + super(x, y, width, height, message.apply(fluidHandler)); + this.fluidHandler = fluidHandler; + } + + @Override + protected void extractWidgetRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float a) { + Identifier fluidTexture = this.getFluidTexture(); + if (fluidTexture != null) { + graphics.blit( + RenderPipelines.GUI_TEXTURED, + fluidTexture, + this.getX(), this.getY() + this.height - this.getCapacity(), + 0, 0, + this.width, this.getCapacity(), + 16, 256 + ); + } + } + + @Override + protected void updateWidgetNarration(NarrationElementOutput output) { + } + + @Override + protected boolean isValidClickButton(MouseButtonInfo buttonInfo) { + return false; + } + + @Nullable + private Identifier getFluidTexture() { + FluidResource resource = this.fluidHandler.getResource(0); + if (resource.isEmpty()) { + return null; + } + AtomicReference<@org.jspecify.annotations.Nullable Identifier> texture = new AtomicReference<>(); + ClientLevel level = Minecraft.getInstance().level; + if (level != null) { + Optional> holder = level.holder(Objects.requireNonNull(resource.typeHolder().getKey())); + holder.ifPresent((fluid) -> { + String registeredName = fluid.getRegisteredName().split(":")[1]; + texture.set(AnvilCraft.of("textures/block/" + registeredName + ".png")); + }); + } + return texture.get(); + } + + private int getCapacity() { + if (this.fluidHandler.getResource(0).isEmpty()) { + return 0; + } + final int stored = this.fluidHandler.getAmountAsInt(0); + return (stored * this.height / this.fluidHandler.getCapacityAsInt(0, FluidResource.EMPTY)); + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/client/gui/component/TriStateButton.java b/src/main/java/dev/dubhe/anvilcraft/client/gui/component/TriStateButton.java index 2857a408d5..aeacf330ba 100644 --- a/src/main/java/dev/dubhe/anvilcraft/client/gui/component/TriStateButton.java +++ b/src/main/java/dev/dubhe/anvilcraft/client/gui/component/TriStateButton.java @@ -1,7 +1,10 @@ package dev.dubhe.anvilcraft.client.gui.component; +import lombok.Getter; +import lombok.Setter; import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.client.gui.components.Button; +import net.minecraft.client.input.InputWithModifiers; import net.minecraft.client.renderer.RenderPipelines; import net.minecraft.network.chat.Component; import net.minecraft.resources.Identifier; @@ -9,13 +12,20 @@ import java.util.List; public class TriStateButton extends Button { + private final OnPress onPress; + @Setter private Identifier texture; private final int textureWidth; private final int textureHeight; - private final int texYDiff; + @Getter + @Setter private List tooltips; - private boolean selected; + @Getter + @Setter + protected boolean selected; + private final int[] stateOffset; + // StateOffset: 0:未选中 1:按下 2: 悬停 public TriStateButton( int x, int y, @@ -24,46 +34,28 @@ public TriStateButton( Identifier texture, int textureWidth, int textureHeight, + int[] stateOffset, OnPress onPress, List tooltips ) { - super(x, y, width, height, Component.empty(), onPress, Button.DEFAULT_NARRATION); + super(x, y, width, height, Component.empty(), _ -> {}, Button.DEFAULT_NARRATION); + this.onPress = onPress; + this.stateOffset = stateOffset; this.texture = texture; this.textureWidth = textureWidth; this.textureHeight = textureHeight; - this.texYDiff = textureHeight / 3; this.tooltips = tooltips; } - public void setSelected(boolean selected) { - this.selected = selected; - } - - public boolean isSelected() { - return this.selected; - } - - public void setTooltips(List tooltips) { - this.tooltips = tooltips; - } - - public List getTooltips() { - return this.tooltips; - } - - public void setTexture(Identifier texture) { - this.texture = texture; - } - @Override protected void extractContents(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float a) { if (!this.visible) return; this.isHovered = this.isMouseOver(mouseX, mouseY); - int offsetV = 0; + int offsetV = this.stateOffset[0]; if (this.selected) { - offsetV = this.texYDiff * 2; + offsetV = this.stateOffset[1]; } else if (this.isHovered) { - offsetV = this.texYDiff; + offsetV = this.stateOffset[2]; } graphics.blit( RenderPipelines.GUI_TEXTURED, @@ -85,4 +77,14 @@ protected void extractContents(GuiGraphicsExtractor graphics, int mouseX, int mo ); } } + + @Override + public void onPress(InputWithModifiers input) { + this.selected = !this.selected; + this.onPress.onPress(this); + } + + public interface OnPress { + void onPress(TriStateButton btn); + } } diff --git a/src/main/java/dev/dubhe/anvilcraft/client/gui/screen/AutoEnchantingTableScreen.java b/src/main/java/dev/dubhe/anvilcraft/client/gui/screen/AutoEnchantingTableScreen.java new file mode 100644 index 0000000000..6e45143130 --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/client/gui/screen/AutoEnchantingTableScreen.java @@ -0,0 +1,428 @@ +package dev.dubhe.anvilcraft.client.gui.screen; + +import dev.anvilcraft.lib.v2.util.ListUtil; +import dev.anvilcraft.lib.v2.util.MathUtil; +import dev.anvilcraft.lib.v2.util.Scrollable; +import dev.dubhe.anvilcraft.block.entity.AutoEnchantingTableBlockEntity; +import dev.dubhe.anvilcraft.block.workstation.AutoEnchantingTableBlock; +import dev.dubhe.anvilcraft.client.gui.component.FluidDisplayWidget; +import dev.dubhe.anvilcraft.constant.SharedTextures; +import dev.dubhe.anvilcraft.inventory.AutoEnchantingTableMenu; +import dev.dubhe.anvilcraft.network.AutoEnchantingTableSyncPacket; +import dev.dubhe.anvilcraft.util.TickDebouncer; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.client.gui.components.EditBox; +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; +import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent; +import net.minecraft.client.gui.screens.inventory.tooltip.DefaultTooltipPositioner; +import net.minecraft.client.input.MouseButtonEvent; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.renderer.RenderPipelines; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.core.component.DataComponents; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.resources.Identifier; +import net.minecraft.util.ARGB; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.enchantment.Enchantment; +import net.minecraft.world.item.enchantment.EnchantmentHelper; +import net.minecraft.world.item.enchantment.EnchantmentInstance; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.EnchantingTableBlock; +import net.neoforged.neoforge.client.network.ClientPacketDistributor; +import net.neoforged.neoforge.transfer.fluid.FluidResource; +import org.jspecify.annotations.Nullable; + +import java.util.List; +import java.util.Objects; + +public class AutoEnchantingTableScreen extends AbstractContainerScreen { + private static final Identifier BACKGROUND = SharedTextures.bg("machine", "auto_enchanting_table"); + + private final IntList filteredIndexes = new IntArrayList(); + private final Scrollable scrollable = new Scrollable() { + @Override + public int row() { + return 2; + } + + @Override + public int column() { + return 5; + } + + @Override + public int size() { + return AutoEnchantingTableScreen.this.filteredIndexes.size(); + } + + @Override + public void setHead(int head) { + AutoEnchantingTableScreen.this.head = head; + } + }; + private int head = 0; + @Nullable + private EditBox editBox; + @Nullable + private TickDebouncer editBoxTickDebouncer; + private int errorCooldown = 0; + private @Nullable ItemStack renderingTooltipEnchantedBook; + private ItemStack finishItem = ItemStack.EMPTY; + + public AutoEnchantingTableScreen(AutoEnchantingTableMenu menu, Inventory inventory, Component title) { + super(menu, inventory, title); + menu.getBlockEntity().registerUpdateListener(this::rebuildFilter); + this.refreshFinishItem(); + } + + @Override + protected void init() { + super.init(); + this.clearWidgets(); + this.addRenderableWidget( + new FluidDisplayWidget( + this.leftPos + 151, this.topPos + 16, + 18, 56, + this.menu.getBlockEntity().getFluidHandler(), + (fluidHandler) -> Component.translatable( + "screen.anvilcraft.auto_enchanting_table.fluid_display", + fluidHandler.getAmountAsInt(0) + "/" + fluidHandler.getCapacityAsInt(0, FluidResource.EMPTY) + ) + ) + ); + this.editBox = this.addRenderableWidget( + new EditBox(this.font, this.leftPos + 46, this.topPos + 17, 99, 12, Component.empty()) + ); + this.editBoxTickDebouncer = new TickDebouncer(20, this::rebuildFilter); + this.editBox.setResponder(_ -> Objects.requireNonNull(this.editBoxTickDebouncer).trigger()); + this.rebuildFilter(); + } + + private void rebuildFilter() { + this.filteredIndexes.clear(); + String enchantmentName = this.editBox == null ? "" : this.editBox.getValue(); + List> enchantments = this.menu.getEnchantmentList(); + for (int i = 0; i < enchantments.size(); i++) { + Holder enchantment = enchantments.get(i); + if (enchantmentName.isBlank() || enchantment.value().description().getString().contains(enchantmentName)) { + this.filteredIndexes.add(i); + } + } + this.scrollable.reset(); + } + + private void refreshFinishItem() { + ItemStack itemStack = this.menu.getBlockEntity().getItems().getFirst().copyWithCount(1); + if (itemStack.isEmpty()) { + this.finishItem = ItemStack.EMPTY; + return; + } + ItemStack enchantedBook = Items.ENCHANTED_BOOK.getDefaultInstance().copyWithCount(1); + for (int selectedIndex : this.menu.getSelectedIndexes()) { + ListUtil.safelyGet(this.menu.getEnchantmentList(), selectedIndex).ifPresent( + enchantment -> enchantedBook.enchant(enchantment, enchantment.value().getMaxLevel()) + ); + } + AutoEnchantingTableBlockEntity.applyEnchantment(itemStack, enchantedBook); + this.finishItem = itemStack.copyWithCount(1); + } + + private int getBookShelf(Level level, BlockPos pos) { + float bookcases = 0; + for (BlockPos offset : AutoEnchantingTableBlock.BOOKSHELF_OFFSETS) { + if (EnchantingTableBlock.isValidBookShelf(level, pos, offset)) { + bookcases += level.getBlockState(pos.offset(offset)).getEnchantPowerBonus(level, pos.offset(offset)); + } + } + return (int) bookcases; + } + + private boolean canSelect(int index) { + Holder enchantment = ListUtil.safelyGet(this.menu.getEnchantmentList(), index).orElse(null); + if (enchantment == null) return false; + for (int selectedIndex : this.menu.getSelectedIndexes()) { + Holder selected = ListUtil.safelyGet(this.menu.getEnchantmentList(), selectedIndex).orElse(null); + if (selected != null && !Enchantment.areCompatible(enchantment, selected)) { + return false; + } + } + int totalLevel = enchantment.value().getMaxLevel(); + for (int selectedIndex : this.menu.getSelectedIndexes()) { + Holder selected = ListUtil.safelyGet(this.menu.getEnchantmentList(), selectedIndex).orElse(null); + if (selected != null) { + totalLevel += selected.value().getMaxLevel(); + } + } + ClientLevel level = Minecraft.getInstance().level; + if (level == null) return false; + return totalLevel <= this.getBookShelf(level, this.menu.getBlockEntity().getBlockPos()) + && totalLevel * 400 <= this.menu.getBlockEntity().getFluidHandler().getCapacityAsInt(0, FluidResource.EMPTY); + } + + @Override + protected void containerTick() { + this.renderingTooltipEnchantedBook = null; + if (this.editBoxTickDebouncer != null) { + this.editBoxTickDebouncer.tick(); + } + if (this.errorCooldown > 0) { + this.errorCooldown--; + } + } + + @Override + public boolean mouseClicked(MouseButtonEvent event, boolean doubleClick) { + if (event.button() == 0) { + if (this.insideScrollbar(event.x(), event.y())) { + this.scrollable.scrolling(); + return true; + } + for (int i = this.head; i < this.head + Math.min(this.filteredIndexes.size() - this.head, 10); i++) { + int x = this.leftPos + 47 + 18 * (i % 5); + int y = this.topPos + 32 + 18 * ((i - this.head) / 5); + + if (!MathUtil.isInRange(event.x(), event.y(), x, y, x + 18, y + 18)) continue; + + int index = this.filteredIndexes.getInt(i); + if (this.menu.getSelectedIndexes().contains(index)) { + this.menu.unselect(index); + ClientPacketDistributor.sendToServer(new AutoEnchantingTableSyncPacket(index, false)); + } else if (this.canSelect(index)) { + this.menu.select(index); + ClientPacketDistributor.sendToServer(new AutoEnchantingTableSyncPacket(index, true)); + } else { + this.errorCooldown = 80; + } + this.refreshFinishItem(); + return true; + } + } else if (event.button() == 1) { + if (this.editBox != null && this.editBox.isMouseOver(event.x(), event.y())) { + this.editBox.setValue(""); + this.rebuildFilter(); + } + } + return super.mouseClicked(event, doubleClick); + } + + @Override + public boolean mouseDragged(MouseButtonEvent event, double dragX, double dragY) { + if (this.scrollable.isScrolling()) { + int top = this.topPos + 32; + this.scrollable.scrollOnDrag(12, event.y(), top, top + 36); + return true; + } + return super.mouseDragged(event, dragX, dragY); + } + + @Override + public boolean mouseReleased(MouseButtonEvent event) { + if (event.button() == 0 && this.scrollable.isScrolling()) { + this.scrollable.notScrolling(); + return true; + } + return super.mouseReleased(event); + } + + @Override + public boolean mouseScrolled(double mouseX, double mouseY, double scrollX, double scrollY) { + if (!this.scrollable.canScroll()) { + return false; + } else { + this.scrollable.scrollOnScroll(scrollY / 1.2); + return true; + } + } + + protected boolean insideScrollbar(double mouseX, double mouseY) { + int left = this.leftPos + 140; + int top = this.topPos + 32; + int right = left + 4; + int down = top + 36; + return MathUtil.isInRange(mouseX, mouseY, left, top, right, down); + } + + @Override + public void extractContents(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float a) { + super.extractContents(graphics, mouseX, mouseY, a); + this.extractEnchantmentSelectingArea(graphics, mouseX, mouseY, a); + if (this.errorCooldown > 0) { + graphics.fill(this.leftPos + 47, this.topPos + 32, this.leftPos + 137, this.topPos + 68, ARGB.color(128, 255, 0, 0)); + } + if (this.menu.getBlockEntity().getItems().get(1).isEmpty() && !this.finishItem.isEmpty()) { + graphics.item(this.finishItem, this.leftPos + 7, this.topPos + 52); + graphics.fill(this.leftPos + 7, this.topPos + 52, this.leftPos + 23, this.topPos + 68, 0x99777777); + } + } + + protected void extractEnchantmentSelectingArea(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float partialTick) { + this.renderingTooltipEnchantedBook = null; + if (this.filteredIndexes.isEmpty()) return; + for (int i = this.head; i < this.head + Math.min(this.filteredIndexes.size() - this.head, 10); i++) { + int x = this.leftPos + 47 + 18 * (i % 5); + int y = this.topPos + 32 + 18 * ((i - this.head) / 5); + + Holder enchantment = ListUtil + .safelyGet(this.menu.getEnchantmentList(), this.filteredIndexes.getInt(i)) + .orElse(null); + if (enchantment == null) continue; + + ItemStack willRender = EnchantmentHelper.createBook(new EnchantmentInstance(enchantment, enchantment.value().getMaxLevel())); + + int offsetV = 0; + if (MathUtil.isInRange(mouseX, mouseY, x, y, x + 18, y + 18)) { + offsetV = 36; + this.renderingTooltipEnchantedBook = willRender; + } + + boolean selected = false; + if (this.menu.getSelectedIndexes().contains(this.filteredIndexes.getInt(i))) { + offsetV = 18; + selected = true; + } + + graphics.blit(RenderPipelines.GUI_TEXTURED, SharedTextures.SWITCH_TABLE_BUTTON, x, y, 0, offsetV, 18, 18, 18, 54); + graphics.item(willRender, x + 1, y + (selected ? 1 : 0), (int) (partialTick * 100)); + } + } + + @Override + public void extractBackground(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float a) { + super.extractBackground(graphics, mouseX, mouseY, a); + graphics.blit( + RenderPipelines.GUI_TEXTURED, + AutoEnchantingTableScreen.BACKGROUND, + this.leftPos, + this.topPos, + 0, + 0, + this.getImageWidth(), + this.getImageHeight(), + 256, + 256 + ); + if (this.scrollable.canScroll()) { + int left = this.leftPos + 140; + int top = this.topPos + 32; + int down = top + 36; + graphics.blit( + RenderPipelines.GUI_TEXTURED, + SharedTextures.SWITCH_TABLE_SLIDER, + left, + top + (int) ((down - top - 12) * this.scrollable.getScrollOffs()), + 0, + 0, + 4, + 12, + 8, + 12 + ); + } + } + + @Override + protected void extractLabels(GuiGraphicsExtractor graphics, int xm, int ym) { + int x = (this.imageWidth - this.font.width(this.title)) / 2; + graphics.text(this.font, this.title, x, 2, -12566464, false); + if (this.errorCooldown > 0) { + MutableComponent text = Component.translatable("screen.anvilcraft.auto_enchanting_table.out_of_limit"); + x = (this.imageWidth - this.font.width(text)) / 2; + graphics.text(this.font, text, x, -15, ARGB.color(255, 0, 0), false); + } + } + + @Override + protected void extractTooltip(GuiGraphicsExtractor graphics, int mouseX, int mouseY) { + ItemStack outputTooltipItem = this.getOutputTooltipItem(); + if (this.hoveredSlot != null && this.hoveredSlot.hasItem() && this.hoveredSlot.index != 37) { + ItemStack item = this.hoveredSlot.getItem(); + if ( + this.menu.getCarried().isEmpty() + || item.getTooltipImage() + .map(ClientTooltipComponent::create) + .map(ClientTooltipComponent::showTooltipWithItemInHand) + .orElse(false) + ) { + graphics.setTooltipForNextFrame( + this.font, + this.getTooltipFromContainerItem(item), + item.getTooltipImage(), + item, + mouseX, + mouseY, + item.get(DataComponents.TOOLTIP_STYLE) + ); + } + } else if (this.renderingTooltipEnchantedBook != null) { + graphics.setTooltipForNextFrame( + this.font, + this.getTooltipFromContainerItem(this.renderingTooltipEnchantedBook), + this.renderingTooltipEnchantedBook.getTooltipImage(), + this.renderingTooltipEnchantedBook, + mouseX, + mouseY, + this.renderingTooltipEnchantedBook.get(DataComponents.TOOLTIP_STYLE) + ); + } else if (!outputTooltipItem.isEmpty() && this.isHovering(7, 52, 16, 16, mouseX, mouseY)) { + graphics.setTooltipForNextFrame( + this.font, + this.getTooltipFromContainerItem(outputTooltipItem), + outputTooltipItem.getTooltipImage(), + outputTooltipItem, + mouseX, + mouseY, + outputTooltipItem.get(DataComponents.TOOLTIP_STYLE) + ); + } + if (this.isHovering(151, 16, 18, 56, mouseX, mouseY)) { + graphics.tooltip( + Minecraft.getInstance().font, + List.of( + ClientTooltipComponent.create( + Component.translatable( + "screen.anvilcraft.auto_enchanting_table.fluid_display", + this.menu.getBlockEntity().getFluidHandler().getAmountAsInt(0) + + "/" + + this.menu.getBlockEntity().getFluidHandler().getCapacityAsInt(0, FluidResource.EMPTY) + ).getVisualOrderText() + ) + ), + mouseX, + mouseY, + DefaultTooltipPositioner.INSTANCE, + null + ); + } + } + + /// 输出栏产物 tooltip:引物模式下显示附魔,无引物时不显示附魔 + private ItemStack getOutputTooltipItem() { + ItemStack item = this.menu.getSlot(37).getItem(); + if (item.isEmpty()) { + item = this.finishItem; + } + if (item.isEmpty()) { + return ItemStack.EMPTY; + } + if (this.menu.getPrologueSlot().getItem().isEmpty()) { + item = item.copy(); + item.remove(DataComponents.ENCHANTMENTS); + item.remove(DataComponents.STORED_ENCHANTMENTS); + } + return item; + } + + @Override + public void resize(int width, int height) { + this.scrollable.calculateScroll(this.head / 5); + this.init(width, height); + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/client/gui/screen/SmartBlockPlacerScreen.java b/src/main/java/dev/dubhe/anvilcraft/client/gui/screen/SmartBlockPlacerScreen.java index b774da8d6d..ca27297788 100644 --- a/src/main/java/dev/dubhe/anvilcraft/client/gui/screen/SmartBlockPlacerScreen.java +++ b/src/main/java/dev/dubhe/anvilcraft/client/gui/screen/SmartBlockPlacerScreen.java @@ -168,6 +168,7 @@ private void initLayerButtons() { SmartBlockPlacerScreen.LAYER_DEFAULT[i], 16, 48, + new int[] {0, 32, 16}, _ -> this.onLayerButtonClick(index), List.of(Component.translatable("screen.anvilcraft.smart_block_placer.layer." + (i + 1))) ); @@ -269,6 +270,7 @@ private void initMissingModeButton() { SmartBlockPlacerScreen.SKIP_MISSING, 16, 48, + new int[] {0, 32, 16}, _ -> this.onSkipMissingButtonClick(), List.of(Component.translatable("screen.anvilcraft.smart_block_placer.missing_mode.skip")) ); @@ -283,6 +285,7 @@ private void initMissingModeButton() { SmartBlockPlacerScreen.STOP_MISSING, 16, 48, + new int[] {0, 32, 16}, _ -> this.onStopMissingButtonClick(), List.of(Component.translatable("screen.anvilcraft.smart_block_placer.missing_mode.stop")) ); @@ -398,7 +401,8 @@ private TriStateButton createPositionButton( TriStateButton button = new TriStateButton( xpos, ypos, 16, 16, SmartBlockPlacerScreen.POSITION_SELECT, 16, 48, - (_) -> this.onPositionButtonClick(row, col, positionIndex, tooltipSelected, tooltipUnselected), + new int[] {0, 32, 16}, + _ -> this.onPositionButtonClick(row, col, positionIndex, tooltipSelected, tooltipUnselected), selected ? tooltipSelected : tooltipUnselected ); button.setSelected(selected); diff --git a/src/main/java/dev/dubhe/anvilcraft/client/renderer/blockentity/AutoEnchantingTableBlockEntityRenderer.java b/src/main/java/dev/dubhe/anvilcraft/client/renderer/blockentity/AutoEnchantingTableBlockEntityRenderer.java new file mode 100644 index 0000000000..afba9a7367 --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/client/renderer/blockentity/AutoEnchantingTableBlockEntityRenderer.java @@ -0,0 +1,238 @@ +package dev.dubhe.anvilcraft.client.renderer.blockentity; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.math.Axis; +import dev.dubhe.anvilcraft.block.entity.AutoEnchantingTableBlockEntity; +import dev.dubhe.anvilcraft.client.renderer.blockentity.state.AutoEnchantingTableBlockEntityRenderState; +import dev.dubhe.anvilcraft.client.support.FeatureRendererSupport; +import dev.dubhe.anvilcraft.client.support.FluidRenderHelper; +import net.minecraft.client.Minecraft; +import net.minecraft.client.model.geom.ModelLayers; +import net.minecraft.client.model.object.book.BookModel; +import net.minecraft.client.renderer.SubmitNodeCollector; +import net.minecraft.client.renderer.block.FluidModel; +import net.minecraft.client.renderer.blockentity.BlockEntityRenderer; +import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider; +import net.minecraft.client.renderer.blockentity.EnchantTableRenderer; +import net.minecraft.client.renderer.entity.state.ItemClusterRenderState; +import net.minecraft.client.renderer.feature.ModelFeatureRenderer; +import net.minecraft.client.renderer.item.ItemModelResolver; +import net.minecraft.client.renderer.item.ItemStackRenderState; +import net.minecraft.client.renderer.state.level.CameraRenderState; +import net.minecraft.client.renderer.texture.OverlayTexture; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.resources.model.sprite.SpriteGetter; +import net.minecraft.core.Direction; +import net.minecraft.util.Mth; +import net.minecraft.world.phys.Vec3; +import net.neoforged.neoforge.transfer.fluid.FluidResource; +import net.neoforged.neoforge.transfer.fluid.FluidStacksResourceHandler; +import org.jspecify.annotations.Nullable; + +public class AutoEnchantingTableBlockEntityRenderer + implements BlockEntityRenderer { + private final SpriteGetter sprites; + private final BookModel bookModel; + private final ItemModelResolver resolver; + + public AutoEnchantingTableBlockEntityRenderer(BlockEntityRendererProvider.Context context) { + this.sprites = context.sprites(); + this.bookModel = new BookModel(context.bakeLayer(ModelLayers.BOOK)); + this.resolver = context.itemModelResolver(); + } + + @Override + public AutoEnchantingTableBlockEntityRenderState createRenderState() { + return new AutoEnchantingTableBlockEntityRenderState(); + } + + @Override + public void extractRenderState( + AutoEnchantingTableBlockEntity blockEntity, + AutoEnchantingTableBlockEntityRenderState state, + float partialTicks, + Vec3 cameraPosition, + ModelFeatureRenderer.@Nullable CrumblingOverlay breakProgress + ) { + BlockEntityRenderer.super.extractRenderState(blockEntity, state, partialTicks, cameraPosition, breakProgress); + state.flip = Mth.lerp(partialTicks, blockEntity.oldFlip, blockEntity.flip); + state.open = Mth.lerp(partialTicks, blockEntity.oldOpen, blockEntity.open); + state.time = blockEntity.time + partialTicks; + float or = blockEntity.rot - blockEntity.oldRot; + + while (or >= (float) Math.PI) { + or -= (float) (Math.PI * 2); + } + + while (or < (float) -Math.PI) { + or += (float) (Math.PI * 2); + } + + state.rotY = blockEntity.oldRot + or * partialTicks; + + final FluidStacksResourceHandler fluidHandler = blockEntity.getFluidHandler(); + FluidResource resource = fluidHandler.getResource(0); + state.amount.setValue(fluidHandler.getAmountAsInt(0)); + state.fluid = resource.getFluid(); + state.fluidStack = resource.toStack(1); + state.fluidResource = resource; + state.displayInputItem = blockEntity.getDisplayInputItem(); + state.inputItemState = FeatureRendererSupport.initialize(state.displayInputItem, this.resolver); + state.displayOutputItem = blockEntity.getDisplayOutputItem(); + state.outputItemState = FeatureRendererSupport.initialize(state.displayOutputItem, this.resolver); + } + + @Override + public void submit( + AutoEnchantingTableBlockEntityRenderState state, + PoseStack poseStack, + SubmitNodeCollector submitNodeCollector, + CameraRenderState camera + ) { + // region 渲染书本 + poseStack.pushPose(); + poseStack.translate(0.5F, 0.75F, 0.5F); + poseStack.translate(0.0F, 0.1F + Mth.sin(state.time * 0.1F) * 0.01F, 0.0F); + float rotY = state.rotY; + poseStack.mulPose(Axis.YP.rotation(-rotY)); + poseStack.mulPose(Axis.ZP.rotationDegrees(80.0F)); + float ff1 = Mth.frac(state.flip + 0.25f) * 1.6f - 0.3f; + float ff2 = Mth.frac(state.flip + 0.75f) * 1.6f - 0.3f; + BookModel.State animationState = BookModel.State.forAnimation(state.time, Mth.clamp(ff1, 0, 1f), Mth.clamp(ff2, 0, 1), state.open); + submitNodeCollector.submitModel( + this.bookModel, + animationState, + poseStack, + state.lightCoords, + OverlayTexture.NO_OVERLAY, + -1, + EnchantTableRenderer.BOOK_TEXTURE, + this.sprites, + 0, + state.breakProgress + ); + poseStack.popPose(); + // endregion + + // region 渲染物品 + if (!state.displayInputItem.isEmpty()) { + poseStack.pushPose(); + ItemClusterRenderState itemState = state.getInputItemState(); + ItemStackRenderState item = itemState.item; + double x = 0.5; + double y = 1.15; + double z = 0.5; + poseStack.translate(x, y, z); + poseStack.mulPose(Axis.YP.rotation(rotY)); + item.submit(poseStack, submitNodeCollector, state.lightCoords, OverlayTexture.NO_OVERLAY, itemState.outlineColor); + poseStack.popPose(); + } else if (!state.displayOutputItem.isEmpty()) { + poseStack.pushPose(); + ItemClusterRenderState itemState = state.getOutputItemState(); + ItemStackRenderState item = itemState.item; + double x = 0.5; + double y = 1.35; + double z = 0.5; + poseStack.translate(x, y, z); + poseStack.mulPose(Axis.YP.rotation(rotY)); + item.submit(poseStack, submitNodeCollector, state.lightCoords, OverlayTexture.NO_OVERLAY, itemState.outlineColor); + poseStack.popPose(); + } + // endregion + + // region 渲染流体 + if (state.amount.getValue() > 0) { + poseStack.pushPose(); + FluidModel model = FluidRenderHelper.getModel( + Minecraft.getInstance().getModelManager().getFluidStateModelSet(), + state.fluid + ); + var tintSource = model.fluidTintSource(); + if (tintSource != null) { + TextureAtlasSprite sprite = model.stillMaterial().sprite(); + int tintColor = tintSource.colorAsStack(state.fluidStack); + final int[] numbers = splitNumber(state.getAmount().getValue()); + Direction[] directions = { Direction.EAST, Direction.SOUTH, Direction.WEST, Direction.NORTH }; + for (int i = 0; i < numbers.length; i++) { + renderFluid(directions[i], numbers[i], submitNodeCollector, poseStack, sprite, state, tintColor); + } + } + poseStack.popPose(); + } + // endregion + } + + private static void renderFluid( + Direction face, + int amount, + SubmitNodeCollector submitNodeCollector, + PoseStack poseStack, + TextureAtlasSprite sprite, + AutoEnchantingTableBlockEntityRenderState state, + int tintColor + ) { + float y = 0.375f; + if (amount > 0 && amount <= 2000) { + y += 0.0625f; + } else if (amount > 2000 && amount <= 4000) { + y += 0.125f; + } else if (amount > 4000 && amount <= 6000) { + y += 0.1875f; + } else if (amount > 6000 && amount <= 8000) { + y += 0.25f; + } + float[] pos = new float[6]; + if (face == Direction.EAST || face == Direction.WEST) { + pos[0] = 0; + pos[1] = 0.375f; + pos[2] = 0.4375f; + pos[3] = 1; + pos[4] = y; + pos[5] = 0.5625f; + } else if (face == Direction.SOUTH || face == Direction.NORTH) { + pos[0] = 0.4375f; + pos[1] = 0.375f; + pos[2] = 0; + pos[3] = 0.5625f; + pos[4] = y; + pos[5] = 1; + } + submitNodeCollector.submitCustomGeometry( + poseStack, + BaseFluidHandlerHolderRenderer.FLUID_RENDER_TYPE, + (pose, buffer) -> FluidRenderHelper.INSTANCE.renderFluidBox( + face, + sprite, + state.fluidResource, + pos[0], pos[1], pos[2], + pos[3], pos[4], pos[5], + tintColor, + buffer, + pose, + state.lightCoords, + false, + false + ) + ); + } + + private static int[] splitNumber(int total) { + if (total <= 0) { + return new int[0]; + } + + int full = total / 8000; + int remainder = total % 8000; + int length = remainder == 0 ? full : full + 1; + int[] result = new int[length]; + + for (int i = 0; i < full; i++) { + result[i] = 8000; + } + if (remainder != 0) { + result[full] = remainder; + } + return result; + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/client/renderer/blockentity/BaseFluidHandlerHolderRenderer.java b/src/main/java/dev/dubhe/anvilcraft/client/renderer/blockentity/BaseFluidHandlerHolderRenderer.java index e0412f0c9c..b27353ede3 100644 --- a/src/main/java/dev/dubhe/anvilcraft/client/renderer/blockentity/BaseFluidHandlerHolderRenderer.java +++ b/src/main/java/dev/dubhe/anvilcraft/client/renderer/blockentity/BaseFluidHandlerHolderRenderer.java @@ -28,7 +28,7 @@ public abstract class BaseFluidHandlerHolderRenderer { @SuppressWarnings("deprecation") - protected static final RenderType FLUID_RENDER_TYPE = RenderType.create( + public static final RenderType FLUID_RENDER_TYPE = RenderType.create( AnvilCraft.of("fluid_tank").toString(), RenderSetup.builder(RenderPipelines.TRANSLUCENT_BLOCK) .withTexture("Sampler0", TextureAtlas.LOCATION_BLOCKS) diff --git a/src/main/java/dev/dubhe/anvilcraft/client/renderer/blockentity/state/AutoEnchantingTableBlockEntityRenderState.java b/src/main/java/dev/dubhe/anvilcraft/client/renderer/blockentity/state/AutoEnchantingTableBlockEntityRenderState.java new file mode 100644 index 0000000000..32b91600b4 --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/client/renderer/blockentity/state/AutoEnchantingTableBlockEntityRenderState.java @@ -0,0 +1,28 @@ +package dev.dubhe.anvilcraft.client.renderer.blockentity.state; + +import dev.dubhe.anvilcraft.util.MutableValue; +import lombok.Getter; +import lombok.Setter; +import net.minecraft.client.renderer.blockentity.state.BlockEntityRenderState; +import net.minecraft.client.renderer.entity.state.ItemClusterRenderState; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.material.Fluid; +import net.neoforged.neoforge.fluids.FluidStack; +import net.neoforged.neoforge.transfer.fluid.FluidResource; + +@Getter +@Setter +public class AutoEnchantingTableBlockEntityRenderState extends BlockEntityRenderState { + public float time; + public float rotY; + public float flip; + public float open; + public final MutableValue amount = new MutableValue<>(0); + public Fluid fluid; + public FluidStack fluidStack; + public FluidResource fluidResource; + public ItemStack displayInputItem; + public ItemStack displayOutputItem; + public ItemClusterRenderState inputItemState; + public ItemClusterRenderState outputItemState; +} diff --git a/src/main/java/dev/dubhe/anvilcraft/client/support/FluidRenderHelper.java b/src/main/java/dev/dubhe/anvilcraft/client/support/FluidRenderHelper.java index bd04aa4300..f5c3c01d88 100644 --- a/src/main/java/dev/dubhe/anvilcraft/client/support/FluidRenderHelper.java +++ b/src/main/java/dev/dubhe/anvilcraft/client/support/FluidRenderHelper.java @@ -39,6 +39,59 @@ public static FluidModel getModel(FluidStateModelSet set, Fluid fluid) { return accessor.getModelByFluid().getOrDefault(fluid, accessor.getMissingModel()); } + public void renderFluidBox( + Direction face, + TextureAtlasSprite sprite, + FluidResource fluid, + float minX, + float minY, + float minZ, + float maxX, + float maxY, + float maxZ, + int color, + VertexConsumer builder, + PoseStack.Pose pose, + int light, + boolean renderBottom, + boolean invertGasses + ) { + int blockLightIn = (light >> 4) & 0xF; + int luminosity = Math.max(blockLightIn, fluid.getFluidType().getLightLevel()); + light = (light & 0xF00000) | luminosity << 4; + + Vector3f center = new Vector3f(minX + (maxX - minX) / 2, minY + (maxY - minY) / 2, minZ + (maxZ - minZ) / 2); + if (invertGasses && fluid.getFluidType().isLighterThanAir()) { + pose.translate(center.x, center.y, center.z); + pose.rotate(Axis.XP.rotationDegrees(180)); + pose.translate(-center.x, -center.y, -center.z); + } + + if (face == Direction.DOWN && !renderBottom) { + return; + } + boolean positive = face.getAxisDirection() == Direction.AxisDirection.POSITIVE; + if (face.getAxis() + .isHorizontal()) { + if (face.getAxis() == Direction.Axis.X) { + renderStillTiledFace( + face, minZ, minY, maxZ, maxY, positive ? maxX : minX, + builder, pose, light, color, sprite + ); + } else { + renderStillTiledFace( + face, minX, minY, maxX, maxY, positive ? maxZ : minZ, + builder, pose, light, color, sprite + ); + } + } else { + renderStillTiledFace( + face, minX, minZ, maxX, maxZ, positive ? maxY : minY, + builder, pose, light, color, sprite + ); + } + } + public void renderFluidBox( TextureAtlasSprite sprite, FluidResource fluid, diff --git a/src/main/java/dev/dubhe/anvilcraft/data/lang/ScreenLang.java b/src/main/java/dev/dubhe/anvilcraft/data/lang/ScreenLang.java index 1456810917..12ca45e23b 100644 --- a/src/main/java/dev/dubhe/anvilcraft/data/lang/ScreenLang.java +++ b/src/main/java/dev/dubhe/anvilcraft/data/lang/ScreenLang.java @@ -3,11 +3,9 @@ import dev.anvilcraft.lib.v2.registrum.providers.RegistrumLangProvider; public class ScreenLang { - /** - * 初始化界面文本生成器。 - * - * @param provider 语言提供器 - */ + /// 初始化 GUI 文本生成器 + /// + /// @param provider 提供器 @SuppressWarnings("checkstyle:LineLength") public static void init(RegistrumLangProvider provider) { provider.add("screen.anvilcraft.trading_station.not_owner", "You are not the owner of this Trading Station!"); @@ -709,5 +707,8 @@ public static void init(RegistrumLangProvider provider) { provider.add("tooltip.anvilcraft.shulker_container.6", "Currently injected times: %s"); provider.add("tooltip.anvilcraft.waiting", "Waiting for syncing"); + + provider.add("screen.anvilcraft.auto_enchanting_table.out_of_limit", "The sum of all enchantment levels is greater than the enchantment ability or experience fluid volume"); + provider.add("screen.anvilcraft.auto_enchanting_table.fluid_display", "Exp: %s"); } } diff --git a/src/main/java/dev/dubhe/anvilcraft/data/recipe/ShapedRecipeLoader.java b/src/main/java/dev/dubhe/anvilcraft/data/recipe/ShapedRecipeLoader.java index 9f71c5fd91..1f4fd8907a 100644 --- a/src/main/java/dev/dubhe/anvilcraft/data/recipe/ShapedRecipeLoader.java +++ b/src/main/java/dev/dubhe/anvilcraft/data/recipe/ShapedRecipeLoader.java @@ -11,11 +11,33 @@ import net.minecraft.tags.TagKey; import net.minecraft.world.item.Item; import net.minecraft.world.level.ItemLike; +import net.minecraft.world.level.block.Blocks; public class ShapedRecipeLoader { public ShapedRecipeLoader(RegistrumRecipeProvider provider) { this.nineToOne(provider); this.controlValve(provider); + this.autoEnchantingTable(provider); + } + + private void autoEnchantingTable(RegistrumRecipeProvider provider) { + HolderGetter lookup = provider.getItems(); + ShapedRecipeBuilder.shaped(lookup, RecipeCategory.MISC, ModBlocks.AUTO_ENCHANTING_TABLE) + .pattern("TET") + .pattern("ReR") + .define('T', ModBlocks.FLUID_TANK) + .define('E', Blocks.ENCHANTING_TABLE) + .define('R', ModItems.ROYAL_STEEL_INGOT) + .define('e', ModBlocks.MAGNETO_ELECTRIC_CORE_BLOCK) + .unlockedBy(AnvilCraftDatagen.hasItem(ModBlocks.FLUID_TANK), + AnvilCraftDatagen.has(provider.getItems(), ModBlocks.FLUID_TANK)) + .unlockedBy(AnvilCraftDatagen.hasItem(Blocks.ENCHANTING_TABLE), + AnvilCraftDatagen.has(provider.getItems(), Blocks.ENCHANTING_TABLE)) + .unlockedBy(AnvilCraftDatagen.hasItem(ModItems.ROYAL_STEEL_INGOT), + AnvilCraftDatagen.has(provider.getItems(), ModItems.ROYAL_STEEL_INGOT)) + .unlockedBy(AnvilCraftDatagen.hasItem(ModBlocks.MAGNETO_ELECTRIC_CORE_BLOCK), + AnvilCraftDatagen.has(provider.getItems(), ModBlocks.MAGNETO_ELECTRIC_CORE_BLOCK)) + .save(provider); } private void controlValve(RegistrumRecipeProvider provider) { diff --git a/src/main/java/dev/dubhe/anvilcraft/data/tags/BlockTagLoader.java b/src/main/java/dev/dubhe/anvilcraft/data/tags/BlockTagLoader.java index 39ce49f37f..98cecd16b8 100644 --- a/src/main/java/dev/dubhe/anvilcraft/data/tags/BlockTagLoader.java +++ b/src/main/java/dev/dubhe/anvilcraft/data/tags/BlockTagLoader.java @@ -426,5 +426,8 @@ public static void init(RegistrumTagsProvider provider) { .addElement(ModBlocks.CELESTIAL_FORGING_ANVIL_LASER_INTERFACE.getId()) .addElement(ModBlocks.CELESTIAL_FORGING_ANVIL_FLUID_INTERFACE.getId()) .addElement(ModBlocks.CELESTIAL_FORGING_ANVIL_PORTAL.getId()); + + provider.rawBuilder(BlockTags.ENCHANTMENT_POWER_PROVIDER) + .addElement(ModBlocks.TRANSCENDIUM_BLOCK.getId()); } } diff --git a/src/main/java/dev/dubhe/anvilcraft/init/ModCapabilities.java b/src/main/java/dev/dubhe/anvilcraft/init/ModCapabilities.java index 749824e2ad..cb78451acf 100644 --- a/src/main/java/dev/dubhe/anvilcraft/init/ModCapabilities.java +++ b/src/main/java/dev/dubhe/anvilcraft/init/ModCapabilities.java @@ -44,6 +44,7 @@ import net.neoforged.neoforge.transfer.fluid.BucketResourceHandler; import net.neoforged.neoforge.transfer.fluid.FluidResource; import net.neoforged.neoforge.transfer.item.ItemResource; +import net.neoforged.neoforge.transfer.item.WorldlyContainerWrapper; import org.jspecify.annotations.Nullable; import java.util.Objects; @@ -178,6 +179,18 @@ public static void registerCapabilities(final RegisterCapabilitiesEvent event) { ModBlockEntities.CELESTIAL_FORGING_ANVIL_FLUID_INTERFACE.get(), ModCapabilities::fluid ); + + event.registerBlockEntity( + Capabilities.Item.BLOCK, + ModBlockEntities.AUTO_ENCHANTING_TABLE.get(), + WorldlyContainerWrapper::new + ); + + event.registerBlockEntity( + Capabilities.Fluid.BLOCK, + ModBlockEntities.AUTO_ENCHANTING_TABLE.get(), + (be, ignore) -> be.getFluidHandler() + ); } /// 物品 diff --git a/src/main/java/dev/dubhe/anvilcraft/init/ModDispenserBehavior.java b/src/main/java/dev/dubhe/anvilcraft/init/ModDispenserBehavior.java index 18e49ae381..aa0e5353ad 100644 --- a/src/main/java/dev/dubhe/anvilcraft/init/ModDispenserBehavior.java +++ b/src/main/java/dev/dubhe/anvilcraft/init/ModDispenserBehavior.java @@ -147,6 +147,7 @@ public static void register() { DispenserBlock.registerBehavior(ModBlocks.RESIN_BLOCK, ModDispenserBehavior::resinBlock); DispenserBlock.registerBehavior(Items.MILK_BUCKET, ModDispenserBehavior.FLUID_BUCKET); DispenserBlock.registerBehavior(Items.HONEY_BOTTLE, ModDispenserBehavior.FLUID_BUCKET); + DispenserBlock.registerBehavior(ModItems.EXP_BUCKET, ModDispenserBehavior.FLUID_BUCKET); DispenserBlock.registerBehavior(ModItems.OIL_BUCKET, ModDispenserBehavior.BUCKET); DispenserBlock.registerBehavior(ModItems.MELT_GEM_BUCKET, ModDispenserBehavior.BUCKET); DispenserBlock.registerBehavior(ModBlocks.MENGER_SPONGE, ModDispenserBehavior::mengerSponge); diff --git a/src/main/java/dev/dubhe/anvilcraft/init/ModMenuTypes.java b/src/main/java/dev/dubhe/anvilcraft/init/ModMenuTypes.java index 52a50ba39e..69f45877b4 100644 --- a/src/main/java/dev/dubhe/anvilcraft/init/ModMenuTypes.java +++ b/src/main/java/dev/dubhe/anvilcraft/init/ModMenuTypes.java @@ -3,6 +3,7 @@ import dev.anvilcraft.lib.v2.registrum.util.entry.MenuEntry; import dev.dubhe.anvilcraft.client.gui.screen.ActiveSilencerScreen; import dev.dubhe.anvilcraft.client.gui.screen.AdvancedComparatorScreen; +import dev.dubhe.anvilcraft.client.gui.screen.AutoEnchantingTableScreen; import dev.dubhe.anvilcraft.client.gui.screen.BatchCrafterScreen; import dev.dubhe.anvilcraft.client.gui.screen.BatchCutterScreen; import dev.dubhe.anvilcraft.client.gui.screen.CelestialForgingAnvilScreen; @@ -36,6 +37,7 @@ import dev.dubhe.anvilcraft.client.gui.screen.TranscendenceSmithingScreen; import dev.dubhe.anvilcraft.inventory.ActiveSilencerMenu; import dev.dubhe.anvilcraft.inventory.AdvancedComparatorMenu; +import dev.dubhe.anvilcraft.inventory.AutoEnchantingTableMenu; import dev.dubhe.anvilcraft.inventory.BatchCrafterMenu; import dev.dubhe.anvilcraft.inventory.BatchCutterMenu; import dev.dubhe.anvilcraft.inventory.CelestialForgingAnvilMenu; @@ -223,6 +225,10 @@ public class ModMenuTypes { .menu("control_valve", ControlValveMenu::new, () -> ControlValveScreen::new) .register(); + public static final MenuEntry AUTO_ENCHANTING_TABLE = REGISTRUM + .menu("auto_enchanting_table", AutoEnchantingTableMenu::new, () -> AutoEnchantingTableScreen::new) + .register(); + public static void register() { } diff --git a/src/main/java/dev/dubhe/anvilcraft/init/block/ModBlockEntities.java b/src/main/java/dev/dubhe/anvilcraft/init/block/ModBlockEntities.java index 1d9911393b..35edddfe53 100644 --- a/src/main/java/dev/dubhe/anvilcraft/init/block/ModBlockEntities.java +++ b/src/main/java/dev/dubhe/anvilcraft/init/block/ModBlockEntities.java @@ -5,6 +5,7 @@ import dev.dubhe.anvilcraft.block.entity.ActivatorSlidingRailBlockEntity; import dev.dubhe.anvilcraft.block.entity.ActiveSilencerBlockEntity; import dev.dubhe.anvilcraft.block.entity.AdvancedComparatorBlockEntity; +import dev.dubhe.anvilcraft.block.entity.AutoEnchantingTableBlockEntity; import dev.dubhe.anvilcraft.block.entity.BlackHoleBlockEntity; import dev.dubhe.anvilcraft.block.entity.BurningHeaterBlockEntity; import dev.dubhe.anvilcraft.block.entity.CelestialForgingAnvilBlockEntity; @@ -83,6 +84,7 @@ import dev.dubhe.anvilcraft.block.entity.storage.LargeCrateBlockEntity; import dev.dubhe.anvilcraft.block.entity.storage.ShulkerContainerBlockEntity; import dev.dubhe.anvilcraft.client.renderer.blockentity.AdvancedComparatorRenderer; +import dev.dubhe.anvilcraft.client.renderer.blockentity.AutoEnchantingTableBlockEntityRenderer; import dev.dubhe.anvilcraft.client.renderer.blockentity.BatchCraftingRenderer; import dev.dubhe.anvilcraft.client.renderer.blockentity.CFARenderer; import dev.dubhe.anvilcraft.client.renderer.blockentity.ChargeCollectorRenderer; @@ -572,6 +574,12 @@ public class ModBlockEntities { .validBlock(ModBlocks.WIP_BLOCK) .register(); + public static final BlockEntityEntry AUTO_ENCHANTING_TABLE = REGISTRUM + .blockEntity("auto_enchanting_table", AutoEnchantingTableBlockEntity::new) + .validBlock(ModBlocks.AUTO_ENCHANTING_TABLE) + .renderer(() -> AutoEnchantingTableBlockEntityRenderer::new) + .register(); + public static void register() { } } diff --git a/src/main/java/dev/dubhe/anvilcraft/init/block/ModBlocks.java b/src/main/java/dev/dubhe/anvilcraft/init/block/ModBlocks.java index e88c3cab86..52f8bb5fc0 100644 --- a/src/main/java/dev/dubhe/anvilcraft/init/block/ModBlocks.java +++ b/src/main/java/dev/dubhe/anvilcraft/init/block/ModBlocks.java @@ -183,6 +183,7 @@ import dev.dubhe.anvilcraft.block.utility.redstone.BlockComparatorBlock; import dev.dubhe.anvilcraft.block.utility.redstone.ItemDetectorBlock; import dev.dubhe.anvilcraft.block.utility.redstone.PulseGeneratorBlock; +import dev.dubhe.anvilcraft.block.workstation.AutoEnchantingTableBlock; import dev.dubhe.anvilcraft.block.workstation.BurningHeaterBlock; import dev.dubhe.anvilcraft.block.workstation.ConfinementChamberBlock; import dev.dubhe.anvilcraft.block.workstation.CorruptedBeaconBlock; @@ -1288,6 +1289,15 @@ public void accept(DataGenContext ctx, RegistrumItemModelGenera .recipe(RegistrumBlockRecipeLoader::discharger) .register(); + public static final BlockEntry AUTO_ENCHANTING_TABLE = REGISTRUM + .block("auto_enchanting_table", AutoEnchantingTableBlock::new) + .initialProperties(() -> Blocks.ENCHANTING_TABLE) + .properties(BlockBehaviour.Properties::noOcclusion) + .tag(BlockTags.MINEABLE_WITH_PICKAXE) + .blockstate(DataGenUtil::noExtraModelOrState) + .simpleItem() + .register(); + public static final BlockEntry ACTIVE_SILENCER = REGISTRUM.block("active_silencer", ActiveSilencerBlock::new) .simpleItem() .initialProperties(() -> Blocks.IRON_BLOCK) diff --git a/src/main/java/dev/dubhe/anvilcraft/init/item/tabs/FunctionalBlocks.java b/src/main/java/dev/dubhe/anvilcraft/init/item/tabs/FunctionalBlocks.java index b4830c43bb..c795e01256 100644 --- a/src/main/java/dev/dubhe/anvilcraft/init/item/tabs/FunctionalBlocks.java +++ b/src/main/java/dev/dubhe/anvilcraft/init/item/tabs/FunctionalBlocks.java @@ -76,6 +76,7 @@ public void accept() { this.plain(ModBlocks.INDUCTION_LIGHT); this.plain(ModBlocks.LOAD_MONITOR); this.plain(ModBlocks.TESLA_TOWER); + this.plain(ModBlocks.AUTO_ENCHANTING_TABLE); // 自动附魔台 this.plain(ModBlocks.BATCH_CRAFTER); this.plain(ModBlocks.BATCH_CUTTER); this.plain(ModBlocks.ITEM_COLLECTOR); diff --git a/src/main/java/dev/dubhe/anvilcraft/inventory/AutoEnchantingTableMenu.java b/src/main/java/dev/dubhe/anvilcraft/inventory/AutoEnchantingTableMenu.java new file mode 100644 index 0000000000..655a8e06da --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/inventory/AutoEnchantingTableMenu.java @@ -0,0 +1,374 @@ +package dev.dubhe.anvilcraft.inventory; + +import dev.anvilcraft.lib.v2.util.ListUtil; +import dev.dubhe.anvilcraft.block.entity.AutoEnchantingTableBlockEntity; +import dev.dubhe.anvilcraft.init.enchantment.ModEnchantments; +import dev.dubhe.anvilcraft.init.item.ModItems; +import it.unimi.dsi.fastutil.ints.IntArraySet; +import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.objects.ObjectList; +import lombok.Getter; +import net.minecraft.core.Holder; +import net.minecraft.core.IdMap; +import net.minecraft.core.Registry; +import net.minecraft.core.registries.Registries; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.tags.EnchantmentTags; +import net.minecraft.world.Container; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.MenuType; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.enchantment.Enchantment; +import net.minecraft.world.item.enchantment.Enchantments; +import net.minecraft.world.level.Level; +import org.jspecify.annotations.Nullable; + +import java.util.List; +import java.util.Set; +import java.util.stream.StreamSupport; + +@Getter +public class AutoEnchantingTableMenu extends AbstractContainerMenu { + private final AutoEnchantingTableBlockEntity blockEntity; + private final Container container; + + private final Level level; + @Getter + private List> enchantmentList = new ObjectArrayList<>(); + @Getter + private final IntSet selectedIndexes = new IntArraySet(); + + final Slot prologueSlot; + + public AutoEnchantingTableMenu( + @Nullable MenuType menuType, + int containerId, + Inventory inventory, + FriendlyByteBuf extraData + ) { + this( + menuType, + containerId, + inventory, + (AutoEnchantingTableBlockEntity) inventory.player.level().getBlockEntity(extraData.readBlockPos()) + ); + } + + public AutoEnchantingTableMenu( + @Nullable MenuType menuType, + int containerId, + Inventory inventory, + AutoEnchantingTableBlockEntity blockEntity + ) { + super(menuType, containerId); + this.level = inventory.player.level(); + this.blockEntity = blockEntity; + this.container = blockEntity; + this.blockEntity.setSlotChangedListener(this::slotsChanged); + + this.addPlayerInventory(inventory); + this.addPlayerHotbar(inventory); + + this.addSlot(new Slot(this.container, 0, 7, 18) { + @Override + public boolean mayPlace(ItemStack itemStack) { + return false; + } + + @Override + public boolean mayPickup(Player player) { + return false; + } + + @Override + public int getMaxStackSize() { + return 1; + } + + @Override + public int getMaxStackSize(ItemStack itemStack) { + return 1; + } + }); + this.addSlot(new Slot(this.container, 1, 7, 52) { + @Override + public boolean mayPlace(ItemStack itemStack) { + return false; + } + + @Override + public boolean mayPickup(Player player) { + return false; + } + + @Override + public int getMaxStackSize() { + return 1; + } + + @Override + public int getMaxStackSize(ItemStack itemStack) { + return 1; + } + }); + this.prologueSlot = this.addSlot(new Slot(this.container, 2, 27, 18)); + if (!this.prologueSlot.getItem().isEmpty()) { + this.enchantmentList = this.getEnchantmentList(this.level, this.prologueSlot.getItem()); + } + this.initSelectedIndexes(); + } + + private void initSelectedIndexes() { + IdMap> idMap = this.level.registryAccess().lookupOrThrow(Registries.ENCHANTMENT).asHolderIdMap(); + for (int id : this.blockEntity.getSelectedEnchantmentSet()) { + for (int i = 0; i < this.enchantmentList.size(); i++) { + if (idMap.getId(this.enchantmentList.get(i)) == id) { + this.selectedIndexes.add(i); + break; + } + } + } + } + + private void addPlayerInventory(Inventory playerInventory) { + for (int i = 0; i < 3; ++i) { + for (int l = 0; l < 9; ++l) { + this.addSlot(new Slot(playerInventory, l + i * 9 + 9, 8 + l * 18, 84 + i * 18)); + } + } + } + + private void addPlayerHotbar(Inventory playerInventory) { + for (int i = 0; i < 9; ++i) { + this.addSlot(new Slot(playerInventory, i, 8 + i * 18, 142)); + } + } + + @Override + public void slotsChanged(Container container) { + ItemStack prologueItem = this.prologueSlot.getItem(); + if (!prologueItem.is(this.blockEntity.getLastPrologueItem().getItem())) { + this.blockEntity.setLastPrologueItem(prologueItem); + this.enchantmentList = this.getEnchantmentList(this.level, prologueItem); + this.selectedIndexes.clear(); + this.blockEntity.getSelectedEnchantmentSet().clear(); + this.blockEntity.getPrologueSlotUpdateListener().run(); + } + } + + public void select(int index) { + if (this.selectedIndexes.contains(index)) { + return; + } + Holder enchantment = ListUtil.safelyGet(this.enchantmentList, index).orElse(null); + if (enchantment == null) { + return; + } + for (int selectedIndex : this.selectedIndexes) { + Holder selected = ListUtil.safelyGet(this.enchantmentList, selectedIndex).orElse(null); + if (selected != null && !Enchantment.areCompatible(enchantment, selected)) { + return; + } + } + this.selectedIndexes.add(index); + this.refreshSelectedEnchantments(); + } + + public void unselect(int index) { + int size = this.selectedIndexes.size(); + this.selectedIndexes.remove(index); + if (this.selectedIndexes.size() != size) { + this.refreshSelectedEnchantments(); + } + } + + public boolean hasSelectedEnchantment() { + return !this.selectedIndexes.isEmpty(); + } + + private void refreshSelectedEnchantments() { + Set selectedEnchantmentSet = this.blockEntity.getSelectedEnchantmentSet(); + selectedEnchantmentSet.clear(); + IdMap> idMap = this.level.registryAccess().lookupOrThrow(Registries.ENCHANTMENT).asHolderIdMap(); + for (int selectedIndex : this.selectedIndexes) { + ListUtil.safelyGet(this.enchantmentList, selectedIndex).ifPresent( + enchantment -> selectedEnchantmentSet.add(idMap.getId(enchantment)) + ); + } + this.blockEntity.onChange(); + } + + @Override + public ItemStack quickMoveStack(Player player, int slotIndex) { + if (slotIndex == 36 || slotIndex == 37) { + return ItemStack.EMPTY; + } + Slot slot = this.slots.get(slotIndex); + if (slotIndex == 38) { + ItemStack item = slot.getItem(); + if (!item.isEmpty()) { + if (!this.moveItemStackTo(item, 0, 36, false)) { + return ItemStack.EMPTY; + } + } + } + if (slotIndex >= 0 && slotIndex <= 35) { + ItemStack item = slot.getItem(); + if (!item.isEmpty()) { + if (!this.moveItemStackTo(item, 38, 39, true)) { + return ItemStack.EMPTY; + } + } + } + return ItemStack.EMPTY; + } + + @Override + public boolean stillValid(Player player) { + return this.container.stillValid(player); + } + + @Override + public void removed(Player player) { + super.removed(player); + if (!this.level.isClientSide()) { + this.blockEntity.setOpenMenu(false); + } + } + + private List> getEnchantmentList(Level level, ItemStack prologueItem) { + Registry enchantments = level.registryAccess().lookupOrThrow(Registries.ENCHANTMENT); + + // 蓝宝石护符 + ObjectArrayList> sapphireAmulet = new ObjectArrayList<>(); + enchantments.get(Enchantments.RESPIRATION).ifPresent(sapphireAmulet::add); + enchantments.get(Enchantments.AQUA_AFFINITY).ifPresent(sapphireAmulet::add); + enchantments.get(Enchantments.DEPTH_STRIDER).ifPresent(sapphireAmulet::add); + + // 红宝石护符 + ObjectArrayList> rubyAmulet = new ObjectArrayList<>(); + enchantments.get(Enchantments.FIRE_PROTECTION).ifPresent(rubyAmulet::add); + + // 黄玉护符 + ObjectArrayList> topazAmulet = new ObjectArrayList<>(); + enchantments.get(Enchantments.CHANNELING).ifPresent(topazAmulet::add); + + // 绿宝石护符 + ObjectArrayList> emeraldAmulet = StreamSupport + .stream(enchantments.getTagOrEmpty(EnchantmentTags.TRADEABLE).spliterator(), false) + .filter((instance) -> !sapphireAmulet.contains(instance) && !rubyAmulet.contains(instance) && !topazAmulet.contains(instance)) + .collect( + ObjectArrayList::new, + ObjectArrayList::add, + ObjectList::addAll + ); + + // 宝石护符 + ObjectArrayList> gemAmulet = new ObjectArrayList<>(); + gemAmulet.addAll(sapphireAmulet); + gemAmulet.addAll(rubyAmulet); + gemAmulet.addAll(topazAmulet); + gemAmulet.addAll(emeraldAmulet); + + if (prologueItem.is(ModItems.SAPPHIRE_AMULET)) { + return List.copyOf(sapphireAmulet); + } else if (prologueItem.is(ModItems.RUBY_AMULET)) { + return List.copyOf(rubyAmulet); + } else if (prologueItem.is(ModItems.TOPAZ_AMULET)) { + return List.copyOf(topazAmulet); + } else if (prologueItem.is(ModItems.EMERALD_AMULET)) { + return List.copyOf(emeraldAmulet); + } else if (prologueItem.is(ModItems.GEM_AMULET)) { + return List.copyOf(gemAmulet); + } + + // 羽毛护符 + ObjectArrayList> featherAmulet = new ObjectArrayList<>(); + enchantments.get(Enchantments.FEATHER_FALLING).ifPresent(featherAmulet::add); + + // 寂静护符 + ObjectArrayList> silenceAmulet = new ObjectArrayList<>(); + enchantments.get(Enchantments.PROTECTION).ifPresent(silenceAmulet::add); + enchantments.get(Enchantments.SWIFT_SNEAK).ifPresent(silenceAmulet::add); + + // 猫护符 + ObjectArrayList> catAmulet = new ObjectArrayList<>(); + enchantments.get(Enchantments.BLAST_PROTECTION).ifPresent(catAmulet::add); + enchantments.get(Enchantments.THORNS).ifPresent(catAmulet::add); + + // 狗护符 + ObjectArrayList> dogAmulet = new ObjectArrayList<>(); + enchantments.get(Enchantments.PROJECTILE_PROTECTION).ifPresent(dogAmulet::add); + + // 自然护符 + ObjectArrayList> natureAmulet = new ObjectArrayList<>(); + natureAmulet.addAll(featherAmulet); + natureAmulet.addAll(silenceAmulet); + natureAmulet.addAll(catAmulet); + natureAmulet.addAll(dogAmulet); + + if (prologueItem.is(ModItems.FEATHER_AMULET)) { + return List.copyOf(featherAmulet); + } else if (prologueItem.is(ModItems.SILENCE_AMULET)) { + return List.copyOf(silenceAmulet); + } else if (prologueItem.is(ModItems.CAT_AMULET)) { + return List.copyOf(catAmulet); + } else if (prologueItem.is(ModItems.DOG_AMULET)) { + return List.copyOf(dogAmulet); + } else if (prologueItem.is(ModItems.NATURE_AMULET)) { + return List.copyOf(natureAmulet); + } + + // 紫水晶 + ObjectArrayList> amethystShard = new ObjectArrayList<>(); + enchantments.get(ModEnchantments.FELLING_KEY).ifPresent(amethystShard::add); + enchantments.get(ModEnchantments.HARVEST_KEY).ifPresent(amethystShard::add); + + // 皇家钢锭 + ObjectArrayList> royalSteel = new ObjectArrayList<>(); + enchantments.get(Enchantments.SILK_TOUCH).ifPresent(royalSteel::add); + enchantments.get(Enchantments.UNBREAKING).ifPresent(royalSteel::add); + enchantments.get(Enchantments.MENDING).ifPresent(royalSteel::add); + + // 余烬金属锭 + ObjectArrayList> emberMetal = new ObjectArrayList<>(); + enchantments.get(ModEnchantments.SMELTING_KEY).ifPresent(emberMetal::add); + enchantments.get(Enchantments.FIRE_ASPECT).ifPresent(emberMetal::add); + enchantments.get(Enchantments.FLAME).ifPresent(emberMetal::add); + + // 浮霜金属锭 + ObjectArrayList> frostMetal = new ObjectArrayList<>(); + enchantments.get(ModEnchantments.DISINTEGRATION_KEY).ifPresent(frostMetal::add); + enchantments.get(Enchantments.FROST_WALKER).ifPresent(frostMetal::add); + + // 超限合金锭 + ObjectArrayList> transcendium = new ObjectArrayList<>(); + enchantments.get(Enchantments.FORTUNE).ifPresent(transcendium::add); + enchantments.get(Enchantments.LOOTING).ifPresent(transcendium::add); + enchantments.get(ModEnchantments.BEHEADING_KEY).ifPresent(transcendium::add); + enchantments.get(Enchantments.LUCK_OF_THE_SEA).ifPresent(transcendium::add); + transcendium.addAll(amethystShard); + transcendium.addAll(royalSteel); + transcendium.addAll(emberMetal); + transcendium.addAll(frostMetal); + + if (prologueItem.is(Items.AMETHYST_SHARD)) { + return List.copyOf(amethystShard); + } else if (prologueItem.is(ModItems.ROYAL_STEEL_INGOT)) { + return List.copyOf(royalSteel); + } else if (prologueItem.is(ModItems.EMBER_METAL_INGOT)) { + return List.copyOf(emberMetal); + } else if (prologueItem.is(ModItems.FROST_METAL_INGOT)) { + return List.copyOf(frostMetal); + } else if (prologueItem.is(ModItems.TRANSCENDIUM_INGOT)) { + return List.copyOf(transcendium); + } + + return List.of(); + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/network/AutoEnchantingTableSyncPacket.java b/src/main/java/dev/dubhe/anvilcraft/network/AutoEnchantingTableSyncPacket.java new file mode 100644 index 0000000000..5892bb3666 --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/network/AutoEnchantingTableSyncPacket.java @@ -0,0 +1,37 @@ +package dev.dubhe.anvilcraft.network; + +import dev.anvilcraft.lib.v2.network.packet.IPacket; +import dev.anvilcraft.lib.v2.network.packet.IServerboundPacket; +import dev.dubhe.anvilcraft.AnvilCraft; +import dev.dubhe.anvilcraft.inventory.AutoEnchantingTableMenu; +import io.netty.buffer.ByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.world.entity.player.Player; + +public record AutoEnchantingTableSyncPacket(int index, boolean select) implements IServerboundPacket { + private static final Type TYPE = IPacket.type(AnvilCraft.of("auto_enchanting_table")); + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ByteBufCodecs.INT, + AutoEnchantingTableSyncPacket::index, + ByteBufCodecs.BOOL, + AutoEnchantingTableSyncPacket::select, + AutoEnchantingTableSyncPacket::new + ); + + @Override + public Type type() { + return AutoEnchantingTableSyncPacket.TYPE; + } + + @Override + public void handleOnServer(Player player) { + if (!player.hasContainerOpen()) return; + if (!(player.containerMenu instanceof AutoEnchantingTableMenu menu)) return; + if (this.select) { + menu.select(this.index); + } else { + menu.unselect(this.index); + } + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/util/MutableValue.java b/src/main/java/dev/dubhe/anvilcraft/util/MutableValue.java new file mode 100644 index 0000000000..611dcb49cb --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/util/MutableValue.java @@ -0,0 +1,18 @@ +package dev.dubhe.anvilcraft.util; + +import lombok.Getter; + +@Getter +public class MutableValue { + private T value; + + public MutableValue(T value) { + this.value = value; + } + + public void setValue(T value) { + if (!this.value.equals(value)) { + this.value = value; + } + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/util/TickDebouncer.java b/src/main/java/dev/dubhe/anvilcraft/util/TickDebouncer.java new file mode 100644 index 0000000000..35cb051871 --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/util/TickDebouncer.java @@ -0,0 +1,32 @@ +package dev.dubhe.anvilcraft.util; + +public class TickDebouncer { + private final Runnable action; + private final int delayTicks; + private int remainingTicks = -1; + + public TickDebouncer(int delayTicks, Runnable action) { + this.delayTicks = delayTicks; + this.action = action; + } + + public void trigger() { + this.remainingTicks = this.delayTicks; + } + + public void tick() { + if (this.remainingTicks > 0) { + this.remainingTicks--; + if (this.remainingTicks == 0) { + this.action.run(); + } + } + } + + public void run() { + if (this.remainingTicks > 0) { + this.remainingTicks = 0; + this.action.run(); + } + } +} diff --git a/src/main/resources/assets/anvilcraft/blockstates/auto_enchanting_table.json b/src/main/resources/assets/anvilcraft/blockstates/auto_enchanting_table.json new file mode 100644 index 0000000000..8e306c4a46 --- /dev/null +++ b/src/main/resources/assets/anvilcraft/blockstates/auto_enchanting_table.json @@ -0,0 +1,10 @@ +{ + "variants": { + "overload=true": { + "model": "anvilcraft:block/auto_enchanting_table_overload" + }, + "overload=false": { + "model": "anvilcraft:block/auto_enchanting_table" + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/anvilcraft/textures/gui/machine/background/auto_enchanting_table.png b/src/main/resources/assets/anvilcraft/textures/gui/machine/background/auto_enchanting_table.png new file mode 100644 index 0000000000..e93b3db28c Binary files /dev/null and b/src/main/resources/assets/anvilcraft/textures/gui/machine/background/auto_enchanting_table.png differ