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 0fe27e0528..bf6baae6b4 100644 --- a/src/generated/resources/assets/anvilcraft/lang/en_ud.json +++ b/src/generated/resources/assets/anvilcraft/lang/en_ud.json @@ -297,6 +297,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ꞁᗺ ǝʞɐƆ ʎɹɹǝᗺ", @@ -878,6 +879,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 ɟǝǝᗺ", @@ -1438,6 +1440,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.button.compare_mode_hysteresis": "sᴉsǝɹǝʇsʎH :ǝpoW", "screen.anvilcraft.button.compare_mode_window": "ʍopuᴉM :ǝpoW", "screen.anvilcraft.button.direction": "%s :uoᴉʇɔǝɹᴉᗡ ʇndʇnO", diff --git a/src/generated/resources/assets/anvilcraft/lang/en_us.json b/src/generated/resources/assets/anvilcraft/lang/en_us.json index 638c31e197..a31829974a 100644 --- a/src/generated/resources/assets/anvilcraft/lang/en_us.json +++ b/src/generated/resources/assets/anvilcraft/lang/en_us.json @@ -297,6 +297,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", @@ -878,6 +879,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", @@ -1438,6 +1440,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.button.compare_mode_hysteresis": "Mode: Hysteresis", "screen.anvilcraft.button.compare_mode_window": "Mode: Window", "screen.anvilcraft.button.direction": "Output Direction: %s", 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 9de65cb165..29cc23c9e5 100644 --- a/src/generated/resources/data/minecraft/tags/block/mineable/pickaxe.json +++ b/src/generated/resources/data/minecraft/tags/block/mineable/pickaxe.json @@ -54,6 +54,7 @@ "anvilcraft:item_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..bdb50717a5 --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/block/entity/AutoEnchantingTableBlockEntity.java @@ -0,0 +1,526 @@ +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.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.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.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() && this.getBlockState().getValue(AutoEnchantingTableBlock.OVERLOAD)) { + level.setBlockAndUpdate(this.getBlockPos(), this.getBlockState().setValue(AutoEnchantingTableBlock.OVERLOAD, false)); + } else if (!this.grid.isWorking() && !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) { + enchantedBook.enchant(enchantmentHolder, enchantmentHolder.value().getMaxLevel()); + } + } + if (enchantItem.is(Items.BOOK)) { + this.setItem(0, ItemStack.EMPTY); + this.setItem(1, enchantedBook); + } else { + 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(); + int enchantmentsOnRightLevel = entry.getIntValue(); + boolean compatible = item.supportsEnchantment(holder); + if (!compatible) { + continue; + } + item.enchant(holder, enchantmentsOnRightLevel); + } + } + + 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(level, getBlockPos()); + if (this.enchantmentSeed == 0) { + this.enchantmentSeed = 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); + enchantClue[ixx] = -1; + levelClue[ixx] = -1; + if (costs[ixx] < ixx + 1) { + costs[ixx] = 0; + } + } + + for (int ix = 0; ix < 3; ix++) { + if (costs[ix] > 0) { + List list = this.getEnchantmentList(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 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); + 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); + } + 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) { + 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 c12b967fc8..eac40f1808 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/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 61dcf3604a..4b28117dc4 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,22 +1,31 @@ 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; -import java.util.Collections; 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, @@ -25,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, DEFAULT_NARRATION); + super(x, y, width, height, Component.empty(), (ignore) -> {}, 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, @@ -86,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..ca7b52bb78 --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/client/gui/screen/AutoEnchantingTableScreen.java @@ -0,0 +1,461 @@ +package dev.dubhe.anvilcraft.client.gui.screen; + +import dev.dubhe.anvilcraft.block.entity.AutoEnchantingTableBlockEntity; +import dev.dubhe.anvilcraft.block.workstation.AutoEnchantingTableBlock; +import dev.dubhe.anvilcraft.client.gui.component.AutoEnchantingTableButton; +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.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +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.multiplayer.ClientPacketListener; +import net.minecraft.client.renderer.RenderPipelines; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.core.IdMap; +import net.minecraft.core.component.DataComponents; +import net.minecraft.core.registries.Registries; +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.util.Mth; +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.level.Level; +import net.minecraft.world.level.block.EnchantingTableBlock; +import net.neoforged.neoforge.transfer.fluid.FluidResource; +import org.jspecify.annotations.Nullable; + +import java.util.List; +import java.util.Set; + +public class AutoEnchantingTableScreen extends AbstractContainerScreen { + private static final Identifier BACKGROUND = SharedTextures.bg("machine", "auto_enchanting_table"); + + @Nullable + private EditBox editBox; + @Nullable + private TickDebouncer editBoxTickDebouncer; + @Nullable + private final AutoEnchantingTableButton[] buttons = new AutoEnchantingTableButton[10]; + private List> enchantmentList = new ObjectArrayList<>(); + private int currentIndex = 0; + private int scrollOffset = 0; + private boolean draggedArea = false; + private int errorCooldown = 0; + private final Set> selectedEnchantments = new ObjectOpenHashSet<>(); + private ItemStack finishItem = ItemStack.EMPTY; + + public AutoEnchantingTableScreen(AutoEnchantingTableMenu menu, Inventory inventory, Component title) { + super(menu, inventory, title); + menu.getBlockEntity().registerUpdateListener(() -> { + this.addWidget(0); + this.selectedEnchantments.clear(); + this.scrollOffset = 0; + }); + ClientLevel level = Minecraft.getInstance().level; + if (level != null) { + IdMap> idMap = level.registryAccess().lookupOrThrow(Registries.ENCHANTMENT).asHolderIdMap(); + this.selectedEnchantments.clear(); + for (int id : this.menu.getBlockEntity().getSelectedEnchantmentSet()) { + Holder enchantmentHolder = idMap.byId(id); + if (enchantmentHolder != null) { + this.selectedEnchantments.add(enchantmentHolder); + } + } + } + ItemStack itemStack = this.menu.getBlockEntity().getItems().getFirst().copyWithCount(1); + if (!itemStack.isEmpty()) { + ItemStack enchantedBook = Items.ENCHANTED_BOOK.getDefaultInstance(); + for (Holder selectedEnchantment : this.selectedEnchantments) { + enchantedBook.enchant(selectedEnchantment, selectedEnchantment.value().getMaxLevel()); + } + AutoEnchantingTableBlockEntity.applyEnchantment(itemStack, enchantedBook); + this.finishItem = itemStack.copyWithCount(1); + } + } + + @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.addWidget(0)); + this.editBox.setResponder((_) -> this.editBoxTickDebouncer.trigger()); + + this.addWidget(0); + } + + private void addWidget(int startIndex) { + if (startIndex < 0) { + return; + } + for (int i = 0; i < this.buttons.length; i++) { + AutoEnchantingTableButton button = this.buttons[i]; + if (button != null) { + this.removeWidget(button); + } + this.buttons[i] = null; + } + this.currentIndex = startIndex; + this.enchantmentList = this.menu.getEnchantmentList().stream().filter((holder) -> { + if (this.editBox != null) { + String enchantmentName = this.editBox.getValue(); + if (enchantmentName.isBlank()) { + return true; + } + return holder.value().description().getString().contains(enchantmentName); + } + return true; + }).toList(); + if (startIndex < this.enchantmentList.size()) { + int index = 0; + for (int i = startIndex; i < this.enchantmentList.size() && i < startIndex + 10; i++) { + Holder holder = this.enchantmentList.get(i); + AutoEnchantingTableButton button = this.getAutoEnchantingTableButton(index, holder); + this.buttons[index] = button; + this.addRenderableWidget(button); + index++; + } + } + } + + private AutoEnchantingTableButton getAutoEnchantingTableButton(int index, Holder enchantment) { + AutoEnchantingTableButton button = new AutoEnchantingTableButton( + this.leftPos + 47 + 18 * (index % 5), this.topPos + 32 + 18 * (index / 5), + 18, 18, + SharedTextures.SWITCH_TABLE_BUTTON, + enchantment, + 18, 54, + new int[]{0, 18, 36}, + (btn) -> { + if (btn.isSelected()) { + this.selectedEnchantments.remove(btn.getHolder()); + ItemStack itemStack = this.menu.getBlockEntity().getItems().getFirst().copyWithCount(1); + if (!itemStack.isEmpty()) { + ItemStack enchantedBook = Items.ENCHANTED_BOOK.getDefaultInstance().copyWithCount(1); + for (Holder selectedEnchantment : this.selectedEnchantments) { + enchantedBook.enchant(selectedEnchantment, selectedEnchantment.value().getMaxLevel()); + } + AutoEnchantingTableBlockEntity.applyEnchantment(itemStack, enchantedBook); + this.finishItem = itemStack.copyWithCount(1); + } + btn.setSelected(!btn.isSelected()); + return; + } + int totalLevel = this.selectedEnchantments.stream() + .mapToInt((holder) -> holder.value().getMaxLevel()) + .reduce(Integer::sum) + .orElse(0) + btn.getHolder().value().getMaxLevel(); + ClientLevel level = Minecraft.getInstance().level; + if (level != null) { + if (totalLevel <= this.getBookShelf(level, this.menu.getBlockEntity().getBlockPos()) + && totalLevel * 400 <= this.menu.getBlockEntity().getFluidHandler().getCapacityAsInt(0, FluidResource.EMPTY)) { + if (!btn.isSelected()) { + ItemStack itemStack = this.menu.getBlockEntity().getItems().getFirst().copyWithCount(1); + if (!itemStack.isEmpty()) { + ItemStack enchantedBook = Items.ENCHANTED_BOOK.getDefaultInstance().copyWithCount(1); + for (Holder selectedEnchantment : this.selectedEnchantments) { + enchantedBook.enchant(selectedEnchantment, selectedEnchantment.value().getMaxLevel()); + } + enchantedBook.enchant(btn.getHolder(), btn.getHolder().value().getMaxLevel()); + AutoEnchantingTableBlockEntity.applyEnchantment(itemStack, enchantedBook); + this.finishItem = itemStack.copyWithCount(1); + } + this.selectedEnchantments.add(btn.getHolder()); + } + btn.setSelected(!btn.isSelected()); + } else { + this.errorCooldown = 80; + } + } + }, + List.of() + ); + if (Minecraft.getInstance().level != null) { + if (this.selectedEnchantments.contains(button.getHolder())) { + button.setSelected(true); + } + } + return button; + } + + 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 mouseInListArea(double mouseX, double mouseY) { + return mouseX >= this.leftPos + 47 + && mouseX <= this.leftPos + 144 + && mouseY >= this.topPos + 32 + && mouseY <= this.topPos + 68; + } + + private boolean mouseInScrollBarArea(double mouseX, double mouseY) { + return mouseX >= this.leftPos + 140 + && mouseX <= this.leftPos + 144 + && mouseY >= this.topPos + 32 + && mouseY <= this.topPos + 68; + } + + @Override + protected List getTooltipFromContainerItem(ItemStack itemStack) { + + return super.getTooltipFromContainerItem(itemStack); + } + + @Override + public boolean mouseScrolled(double x, double y, double scrollX, double scrollY) { + if (this.mouseInListArea(x, y)) { + if (this.errorCooldown > 0) { + return true; + } + if (this.currentIndex + 10 >= this.enchantmentList.size() && scrollY < 0) { + return true; + } + if (this.enchantmentList.size() > 10) { + int newIndex = Mth.clamp(this.currentIndex + (int) -scrollY * 10, 0, this.enchantmentList.size() - 1); + this.addWidget(newIndex); + this.scrollOffset = newIndex; + return true; + } + } + return super.mouseScrolled(x, y, scrollX, scrollY); + } + + @Override + public boolean mouseClicked(MouseButtonEvent event, boolean doubleClick) { + this.draggedArea = false; + if (event.button() == 0) { + if (this.mouseInScrollBarArea(event.x(), event.y())) { + if (this.errorCooldown <= 0) { + this.draggedArea = true; + } + } + } else if (event.button() == 1) { + if (this.editBox != null && this.editBox.isMouseOver(event.x(), event.y())) { + this.editBox.setValue(""); + this.addWidget(0); + } + } + return super.mouseClicked(event, doubleClick); + } + + @Override + public boolean mouseDragged(MouseButtonEvent event, double dx, double dy) { + int trackY = this.topPos + 32; + int trackHeight = 36 - 12; + if (this.draggedArea) { + int size = this.enchantmentList.size(); + int maxIndex = size > 10 ? (size - 1) / 10 * 10 : 0; + if (maxIndex > 0) { + int continuousIndex = Mth.clamp((int) ((event.y() - trackY) * (double) maxIndex / trackHeight), 0, maxIndex); + this.scrollOffset = continuousIndex; + int newIndex = Mth.clamp((int) (Math.round(continuousIndex / 10.0) * 10), 0, maxIndex); + this.addWidget(newIndex); + return true; + } + } + return super.mouseDragged(event, dx, dy); + } + + @Override + public boolean mouseReleased(MouseButtonEvent event) { + this.draggedArea = false; + return super.mouseReleased(event); + } + + @Override + protected void containerTick() { + if (this.editBoxTickDebouncer != null) { + this.editBoxTickDebouncer.tick(); + } + if (this.errorCooldown > 0) { + this.errorCooldown--; + } + } + + @Override + public boolean shouldCloseOnEsc() { + AutoEnchantingTableBlockEntity blockEntity = this.menu.getBlockEntity(); + ClientPacketListener connection = Minecraft.getInstance().getConnection(); + if (Minecraft.getInstance().level != null) { + IdMap> idMap = Minecraft.getInstance().level.registryAccess() + .lookupOrThrow(Registries.ENCHANTMENT) + .asHolderIdMap(); + if (connection != null) { + connection.send(new AutoEnchantingTableSyncPacket( + blockEntity.getBlockPos(), + this.selectedEnchantments.stream().map(idMap::getId).toList() + )); + } + } + return true; + } + + @Override + public void onClose() { + super.onClose(); + AutoEnchantingTableBlockEntity blockEntity = this.menu.getBlockEntity(); + ClientPacketListener connection = Minecraft.getInstance().getConnection(); + if (Minecraft.getInstance().level != null) { + IdMap> idMap = Minecraft.getInstance().level.registryAccess() + .lookupOrThrow(Registries.ENCHANTMENT) + .asHolderIdMap(); + if (connection != null) { + connection.send(new AutoEnchantingTableSyncPacket( + blockEntity.getBlockPos(), + this.selectedEnchantments.stream().map(idMap::getId).toList() + )); + } + } + } + + @Override + public void extractContents(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float a) { + super.extractContents(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)); + } + int size = this.enchantmentList.size(); + if (size > 10) { + int maxY = this.topPos + 32 + 36 - 12; + int trackHeight = 36 - 12; + int maxIndex = (size - 1) / 10 * 10; + int scrollY = Mth.clamp(this.topPos + 32 + (int) ((float) this.scrollOffset * trackHeight / maxIndex), this.topPos + 32, maxY); + graphics.blit( + RenderPipelines.GUI_TEXTURED, + SharedTextures.SWITCH_TABLE_SLIDER, + this.leftPos + 140, + scrollY, + 0, 0, + 4, 12, + 8, 12 + ); + } + 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); + } + } + + @Override + public void extractBackground(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float a) { + super.extractBackground(graphics, mouseX, mouseY, a); + graphics.blit( + RenderPipelines.GUI_TEXTURED, + BACKGROUND, + this.leftPos, + this.topPos, + 0, + 0, + this.getImageWidth(), + this.getImageHeight(), + 256, + 256 + ); + } + + @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) { + if (this.hoveredSlot != null && this.hoveredSlot.hasItem()) { + ItemStack item = this.hoveredSlot.getItem(); + if ( + this.menu.getCarried().isEmpty() + || item.getTooltipImage() + .map(ClientTooltipComponent::create) + .map(ClientTooltipComponent::showTooltipWithItemInHand) + .orElse(false) + ) { + if (this.hoveredSlot.index != 37) { + graphics.setTooltipForNextFrame( + this.font, + this.getTooltipFromContainerItem(item), + item.getTooltipImage(), + item, + mouseX, + mouseY, + item.get(DataComponents.TOOLTIP_STYLE) + ); + } + } + } + if (this.isHovering(7, 52, 16, 16, mouseX, mouseY)) { + if (this.menu.getBlockEntity().getItems().get(1).isEmpty()) { + if (!this.finishItem.isEmpty()) { + graphics.setTooltipForNextFrame( + this.font, + this.getTooltipFromContainerItem(this.finishItem), + this.finishItem.getTooltipImage(), + this.finishItem, + mouseX, + mouseY, + this.finishItem.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 + ); + } + } +} 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 b38f409ef5..5985206d3f 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() { 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() { 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() { STOP_MISSING, 16, 48, + new int[] {0, 32, 16}, _ -> this.onStopMissingButtonClick(), List.of(Component.translatable("screen.anvilcraft.smart_block_placer.missing_mode.stop")) ); @@ -398,6 +401,7 @@ private TriStateButton createPositionButton( TriStateButton button = new TriStateButton( xpos, ypos, 16, 16, POSITION_SELECT, 16, 48, + new int[] {0, 32, 16}, (_) -> this.onPositionButtonClick(row, col, positionIndex, tooltipSelected, tooltipUnselected), selected ? tooltipSelected : tooltipUnselected ); 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 50e727377f..2f5c97b07d 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") - private 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 43776c36b6..79f659f60a 100644 --- a/src/main/java/dev/dubhe/anvilcraft/client/support/FluidRenderHelper.java +++ b/src/main/java/dev/dubhe/anvilcraft/client/support/FluidRenderHelper.java @@ -37,6 +37,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 5ec7698450..025e7f393a 100644 --- a/src/main/java/dev/dubhe/anvilcraft/data/lang/ScreenLang.java +++ b/src/main/java/dev/dubhe/anvilcraft/data/lang/ScreenLang.java @@ -642,5 +642,8 @@ public static void init(RegistrumLangProvider provider) { provider.add("screen.anvilcraft.storage.category.add", "Left click when holding Filter to add custom category"); provider.add("screen.anvilcraft.storage.category.alternate.removable", "Left click to select, right click to delete this category"); provider.add("screen.anvilcraft.storage.category.alternate.unremovable", "Left click to select"); + + 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..bc49b76d56 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,29 @@ 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); + + ShapedRecipeBuilder.shaped(provider.getItems(), 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 5fa79514b1..9fc052c5be 100644 --- a/src/main/java/dev/dubhe/anvilcraft/data/tags/BlockTagLoader.java +++ b/src/main/java/dev/dubhe/anvilcraft/data/tags/BlockTagLoader.java @@ -381,5 +381,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 adc7c3a9e7..b1e15d8c8a 100644 --- a/src/main/java/dev/dubhe/anvilcraft/init/ModCapabilities.java +++ b/src/main/java/dev/dubhe/anvilcraft/init/ModCapabilities.java @@ -41,6 +41,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; @@ -160,6 +161,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/ModMenuTypes.java b/src/main/java/dev/dubhe/anvilcraft/init/ModMenuTypes.java index d63a6609ce..ebc75a1727 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; @@ -33,6 +34,7 @@ import dev.dubhe.anvilcraft.client.gui.screen.TranscendenceAnvilScreen; 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; @@ -194,6 +196,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 3f81fa60ab..68ef58f125 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; @@ -560,6 +562,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 35da10deba..cf609de779 100644 --- a/src/main/java/dev/dubhe/anvilcraft/init/block/ModBlocks.java +++ b/src/main/java/dev/dubhe/anvilcraft/init/block/ModBlocks.java @@ -178,6 +178,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; @@ -1152,6 +1153,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 1e13b31703..d5ca1a8da9 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 @@ -66,6 +66,7 @@ public void accept() { // this.plain(ModBlocks.EXP_COLLECTOR); // 经验收集器 this.plain(ModBlocks.CHARGER); // 充电器 this.plain(ModBlocks.DISCHARGER); // 放电器 + this.plain(ModBlocks.AUTO_ENCHANTING_TABLE); // 自动附魔台 this.plain(ModBlocks.BLOCK_PLACER); // 方块放置器 this.plain(ModBlocks.BLOCK_DEVOURER); // 方块吞噬器 this.plain(ModBlocks.STRUCTURE_SCANNER); // 结构扫描仪 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..7adef005f8 --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/inventory/AutoEnchantingTableMenu.java @@ -0,0 +1,303 @@ +package dev.dubhe.anvilcraft.inventory; + +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.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.objects.ObjectList; +import lombok.Getter; +import net.minecraft.core.Holder; +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.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<>(); + + 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()); + } + } + + 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.blockEntity.getSelectedEnchantmentSet().clear(); + this.blockEntity.getPrologueSlotUpdateListener().run(); + } + } + + @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); + } + + 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..e03b29b039 --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/network/AutoEnchantingTableSyncPacket.java @@ -0,0 +1,47 @@ +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.block.entity.AutoEnchantingTableBlockEntity; +import io.netty.buffer.ByteBuf; +import net.minecraft.core.BlockPos; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; + +import java.util.List; +import java.util.Set; + +public record AutoEnchantingTableSyncPacket(BlockPos pos, List ids) implements IServerboundPacket { + private static final Type TYPE = + IPacket.type(AnvilCraft.of("auto_enchanting_table")); + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + BlockPos.STREAM_CODEC, + AutoEnchantingTableSyncPacket::pos, + ByteBufCodecs.INT.apply(ByteBufCodecs.list()), + AutoEnchantingTableSyncPacket::ids, + AutoEnchantingTableSyncPacket::new + ); + + @Override + public Type type() { + return TYPE; + } + + @Override + public void handleOnServer(Player player) { + Level level = player.level(); + BlockEntity blockEntity = level.getBlockEntity(this.pos); + if (blockEntity instanceof AutoEnchantingTableBlockEntity autoEnchantingTableBlockEntity) { + Set selectedEnchantmentList = autoEnchantingTableBlockEntity.getSelectedEnchantmentSet(); + selectedEnchantmentList.clear(); + selectedEnchantmentList.addAll(this.ids); + autoEnchantingTableBlockEntity.setOpenMenu(false); + autoEnchantingTableBlockEntity.onChange(); + } + } +} 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