diff --git a/src/main/java/com/wintercogs/beyonddimensions/BeyondDimensions.java b/src/main/java/com/wintercogs/beyonddimensions/BeyondDimensions.java index e38b175c..92dfd11a 100644 --- a/src/main/java/com/wintercogs/beyonddimensions/BeyondDimensions.java +++ b/src/main/java/com/wintercogs/beyonddimensions/BeyondDimensions.java @@ -18,6 +18,8 @@ import com.wintercogs.beyonddimensions.api.storage.key.impl.EnergyStackKey; import com.wintercogs.beyonddimensions.api.storage.key.impl.FluidStackKey; import com.wintercogs.beyonddimensions.api.storage.key.impl.ItemStackKey; +import com.wintercogs.beyonddimensions.api.storage.key.impl.MobStackKey; +import com.wintercogs.beyonddimensions.api.storage.key.impl.PigStackKey; import com.wintercogs.beyonddimensions.common.init.*; import com.wintercogs.beyonddimensions.integration.IntegrationManager; import net.minecraft.resources.ResourceLocation; @@ -66,6 +68,8 @@ private void commonSetup(final FMLCommonSetupEvent event) StackKeyRegistry.registerType(ItemStackKey.EMPTY); StackKeyRegistry.registerType(FluidStackKey.EMPTY); StackKeyRegistry.registerType(EnergyStackKey.INSTANCE); + StackKeyRegistry.registerType(PigStackKey.INSTANCE); // 兼容旧版维度养猪场存档 + StackKeyRegistry.registerType(MobStackKey.EMPTY); // 注册方块能力类型,用于动态为方块注册能力 CapabilityHelper.BlockCapabilityMap.put(ItemStackKey.ID, Capabilities.ItemHandler.BLOCK); diff --git a/src/main/java/com/wintercogs/beyonddimensions/api/dimensionnet/DimensionsNet.java b/src/main/java/com/wintercogs/beyonddimensions/api/dimensionnet/DimensionsNet.java index 3a384196..7e3ed1cd 100644 --- a/src/main/java/com/wintercogs/beyonddimensions/api/dimensionnet/DimensionsNet.java +++ b/src/main/java/com/wintercogs/beyonddimensions/api/dimensionnet/DimensionsNet.java @@ -5,17 +5,27 @@ import com.wintercogs.beyonddimensions.api.storage.key.KeyAmount; import com.wintercogs.beyonddimensions.api.storage.key.impl.EnergyStackKey; import com.wintercogs.beyonddimensions.api.storage.key.impl.ItemStackKey; +import com.wintercogs.beyonddimensions.api.storage.key.impl.MobStackKey; +import com.wintercogs.beyonddimensions.api.storage.key.impl.PigStackKey; import com.wintercogs.beyonddimensions.common.init.BDItems; import com.wintercogs.beyonddimensions.config.ServerConfigRuntime; import com.wintercogs.beyonddimensions.util.PlayerNameHelper; import net.minecraft.core.HolderLookup; +import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.nbt.StringTag; import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.animal.Animal; +import net.minecraft.world.entity.npc.Villager; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; import net.minecraft.world.level.saveddata.SavedData; import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.neoforge.common.NeoForge; @@ -93,6 +103,21 @@ public class DimensionsNet extends SavedData */ private int currentTime = 0; + /** + * 各类动物和村民的繁殖进度。每种实体独立计时,约5分钟完成一轮繁殖。 + */ + private final Map mobBreedingTimes = new HashMap<>(); + private final Map, ItemStackKey> animalFoodKeys = new HashMap<>(); + private static final int MOB_BREEDING_INTERVAL = 6000; + private static final int RANCH_TICK_INTERVAL = 20; + private static final int VILLAGER_FOOD_POINTS = 12; + private static final Map VILLAGER_FOODS = Map.of( + Items.BREAD, 4, + Items.CARROT, 1, + Items.POTATO, 1, + Items.BEETROOT, 1 + ); + /** * 构造函数 * @@ -356,6 +381,13 @@ public static DimensionsNet load(CompoundTag tag, HolderLookup.Provider registry } net.unifiedStorage.deserializeNBT(registryAccess, tag.getCompound("UnifiedStorage")); + // 把旧版养猪场的猪数量迁移到通用实体存储。 + long legacyPigs = net.unifiedStorage.getStackByKey(PigStackKey.INSTANCE).amount(); + if (legacyPigs > 0) + { + net.unifiedStorage.extract(PigStackKey.INSTANCE, legacyPigs, false, false); + net.unifiedStorage.insert(new MobStackKey(EntityType.PIG), legacyPigs, false); + } // 旧数据兼容 if (tag.contains("EnergyStorage")) { @@ -380,6 +412,16 @@ public static DimensionsNet load(CompoundTag tag, HolderLookup.Provider registry // 读取倒计时 net.currentTime = tag.getInt("currentTime"); + CompoundTag breedingTimesTag = tag.getCompound("mobBreedingTimes"); + for (String key : breedingTimesTag.getAllKeys()) + { + ResourceLocation typeId = ResourceLocation.tryParse(key); + if (typeId != null) + net.mobBreedingTimes.put(typeId, Math.max(0, breedingTimesTag.getInt(key))); + } + if (tag.contains("pigBreedingTime")) + net.mobBreedingTimes.put(BuiltInRegistries.ENTITY_TYPE.getKey(EntityType.PIG), + Math.max(0, tag.getInt("pigBreedingTime"))); if (tag.contains("Deleted")) net.deleted = tag.getBoolean("Deleted"); @@ -428,6 +470,9 @@ public static DimensionsNet load(CompoundTag tag, HolderLookup.Provider registry // 保存倒计时 tag.putInt("currentTime", this.currentTime); + CompoundTag breedingTimesTag = new CompoundTag(); + mobBreedingTimes.forEach((typeId, time) -> breedingTimesTag.putInt(typeId.toString(), time)); + tag.put("mobBreedingTimes", breedingTimesTag); // 保存删除状态 tag.putBoolean("Deleted", this.deleted); @@ -725,9 +770,11 @@ else if (isManager(playerId)) @SubscribeEvent public void onServerTick(ServerTickEvent.Pre event) { - // 不对临时网络执行倒计时 - if (temporary || ServerConfigRuntime.crystalGenerateTime <= 0) - return; + if (temporary) return; + + tickRanchBreeding(event.getServer().overworld()); + + if (ServerConfigRuntime.crystalGenerateTime <= 0) return; currentTime++; setDirty(); @@ -737,7 +784,171 @@ public void onServerTick(ServerTickEvent.Pre event) this.unifiedStorage.insert(new ItemStackKey(stack), stack.getCount(), false); currentTime = 0; } + } + + private void tickRanchBreeding(ServerLevel level) + { + if (level.getGameTime() % RANCH_TICK_INTERVAL != 0) return; + + Set activeTypes = new HashSet<>(); + for (KeyAmount entry : List.copyOf(unifiedStorage.getStorage())) + { + if (!(entry.key() instanceof MobStackKey mobKey) || entry.amount() < 2) continue; + + ResourceLocation typeId = BuiltInRegistries.ENTITY_TYPE.getKey(mobKey.entityType()); + activeTypes.add(typeId); + if (!hasBreedingFood(level, mobKey.entityType())) + { + resetBreedingTime(typeId); + continue; + } + + int breedingTime = mobBreedingTimes.getOrDefault(typeId, 0) + RANCH_TICK_INTERVAL; + mobBreedingTimes.put(typeId, breedingTime); + setDirty(); + if (breedingTime < MOB_BREEDING_INTERVAL) continue; + + if (breedStoredMobs(level, mobKey, entry.amount())) + { + mobBreedingTimes.put(typeId, 0); + setDirty(); + } + } + + if (mobBreedingTimes.keySet().removeIf(typeId -> !activeTypes.contains(typeId))) + setDirty(); + } + + private boolean hasBreedingFood(ServerLevel level, EntityType entityType) + { + if (entityType == EntityType.VILLAGER) + return getVillagerFoodPoints() >= VILLAGER_FOOD_POINTS; + + ItemStackKey cachedFood = animalFoodKeys.get(entityType); + if (cachedFood != null) + { + if (unifiedStorage.getStackByKey(cachedFood).amount() > 0) return true; + animalFoodKeys.remove(entityType); + } + + Entity entity = entityType.create(level); + if (!(entity instanceof Animal animal)) return false; + try + { + ItemStackKey foodKey = findAnimalFood(animal); + if (foodKey == null) return false; + animalFoodKeys.put(entityType, foodKey); + return true; + } + finally + { + entity.discard(); + } + } + private boolean breedStoredMobs(ServerLevel level, MobStackKey mobKey, long mobCount) + { + if (mobKey.entityType() == EntityType.VILLAGER) + return breedVillagers(mobKey, mobCount); + + ItemStackKey foodKey = animalFoodKeys.get(mobKey.entityType()); + if (foodKey == null) return false; + + long foodCount = unifiedStorage.getStackByKey(foodKey).amount(); + long births = Math.min(mobCount / 2, foodCount / 2); + births = Math.min(births, Long.MAX_VALUE / 2); + return completeBreeding(mobKey, births, Map.of(foodKey, births * 2)); + } + + private @Nullable ItemStackKey findAnimalFood(Animal animal) + { + for (KeyAmount entry : List.copyOf(unifiedStorage.getStorage())) + { + if (entry.amount() > 0 && entry.key() instanceof ItemStackKey itemKey + && animal.isFood(itemKey.copyStack())) + return itemKey; + } + return null; + } + + private boolean breedVillagers(MobStackKey mobKey, long mobCount) + { + long births = Math.min(mobCount / 2, getVillagerFoodPoints() / VILLAGER_FOOD_POINTS); + births = Math.min(births, Long.MAX_VALUE / VILLAGER_FOOD_POINTS); + births -= unifiedStorage.insert(mobKey, births, true).amount(); + if (births <= 0) return false; + + long requiredPoints = births * VILLAGER_FOOD_POINTS; + Map costs = new LinkedHashMap<>(); + for (Map.Entry food : VILLAGER_FOODS.entrySet()) + { + ItemStackKey foodKey = new ItemStackKey(new ItemStack(food.getKey())); + long available = unifiedStorage.getStackByKey(foodKey).amount(); + long itemsNeeded = (requiredPoints + food.getValue() - 1) / food.getValue(); + long used = Math.min(available, itemsNeeded); + if (used <= 0) continue; + + costs.put(foodKey, used); + requiredPoints = Math.max(0, requiredPoints - used * food.getValue()); + if (requiredPoints == 0) break; + } + return requiredPoints == 0 && completeBreeding(mobKey, births, costs); + } + + private long getVillagerFoodPoints() + { + long points = 0; + for (Map.Entry food : VILLAGER_FOODS.entrySet()) + { + long count = unifiedStorage.getStackByKey(new ItemStackKey(new ItemStack(food.getKey()))).amount(); + if (count > (Long.MAX_VALUE - points) / food.getValue()) return Long.MAX_VALUE; + points += count * food.getValue(); + } + return points; + } + + private boolean completeBreeding(MobStackKey mobKey, long births, Map costs) + { + if (births <= 0) return false; + + long accepted = births - unifiedStorage.insert(mobKey, births, true).amount(); + if (accepted <= 0) return false; + if (accepted != births) + { + births = accepted; + if (costs.size() != 1) return false; + + ItemStackKey key = costs.keySet().iterator().next(); + costs = Map.of(key, births * 2); + } + + Map extractedFoods = new LinkedHashMap<>(); + for (Map.Entry cost : costs.entrySet()) + { + KeyAmount extracted = unifiedStorage.extract(cost.getKey(), cost.getValue(), false, false); + if (extracted.amount() != cost.getValue()) + { + extractedFoods.forEach((key, amount) -> unifiedStorage.insert(key, amount, false)); + if (extracted.amount() > 0) unifiedStorage.insert(cost.getKey(), extracted.amount(), false); + return false; + } + extractedFoods.put(cost.getKey(), extracted.amount()); + } + + long inserted = births - unifiedStorage.insert(mobKey, births, false).amount(); + if (inserted != births) + { + if (inserted > 0) unifiedStorage.extract(mobKey, inserted, false, false); + extractedFoods.forEach((key, amount) -> unifiedStorage.insert(key, amount, false)); + return false; + } + return true; + } + + private void resetBreedingTime(ResourceLocation typeId) + { + if (mobBreedingTimes.remove(typeId) != null) + setDirty(); } private void syncPlayerMembership(UUID playerId, boolean switchPrimary) diff --git a/src/main/java/com/wintercogs/beyonddimensions/api/longtype/MobType.java b/src/main/java/com/wintercogs/beyonddimensions/api/longtype/MobType.java new file mode 100644 index 00000000..daf8bd60 --- /dev/null +++ b/src/main/java/com/wintercogs/beyonddimensions/api/longtype/MobType.java @@ -0,0 +1,52 @@ +package com.wintercogs.beyonddimensions.api.longtype; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.network.chat.Component; +import net.minecraft.world.entity.EntityType; + +public final class MobType extends LongType +{ + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + BuiltInRegistries.ENTITY_TYPE.byNameCodec().fieldOf("entity_type").forGetter(MobType::entityType), + Codec.LONG.fieldOf("amount").forGetter(MobType::getStackCount) + ).apply(instance, MobType::new)); + + private final EntityType entityType; + + public MobType(EntityType entityType, long amount) + { + this.entityType = entityType; + this.stackCount = amount; + } + + public EntityType entityType() + { + return entityType; + } + + @Override + public Component getName() + { + return entityType.getDescription(); + } + + @Override + public MobType getEmpty() + { + return new MobType(entityType, 0); + } + + @Override + public MobType copy() + { + return new MobType(entityType, stackCount); + } + + @Override + public MobType copyWithAmount(long amount) + { + return new MobType(entityType, amount); + } +} diff --git a/src/main/java/com/wintercogs/beyonddimensions/api/longtype/PigType.java b/src/main/java/com/wintercogs/beyonddimensions/api/longtype/PigType.java new file mode 100644 index 00000000..d1449d2a --- /dev/null +++ b/src/main/java/com/wintercogs/beyonddimensions/api/longtype/PigType.java @@ -0,0 +1,38 @@ +package com.wintercogs.beyonddimensions.api.longtype; + +import com.mojang.serialization.Codec; +import net.minecraft.network.chat.Component; + +public final class PigType extends LongType +{ + public static final Codec CODEC = createCodec(PigType::new); + + public PigType(long amount) + { + this.stackCount = amount; + } + + @Override + public Component getName() + { + return Component.translatable("types.beyonddimensions.pig.name"); + } + + @Override + public PigType getEmpty() + { + return new PigType(0); + } + + @Override + public PigType copy() + { + return new PigType(stackCount); + } + + @Override + public PigType copyWithAmount(long amount) + { + return new PigType(amount); + } +} diff --git a/src/main/java/com/wintercogs/beyonddimensions/api/storage/key/impl/MobStackKey.java b/src/main/java/com/wintercogs/beyonddimensions/api/storage/key/impl/MobStackKey.java new file mode 100644 index 00000000..96f94fd9 --- /dev/null +++ b/src/main/java/com/wintercogs/beyonddimensions/api/storage/key/impl/MobStackKey.java @@ -0,0 +1,173 @@ +package com.wintercogs.beyonddimensions.api.storage.key.impl; + +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import com.wintercogs.beyonddimensions.api.ids.BDConstants; +import com.wintercogs.beyonddimensions.api.longtype.MobType; +import com.wintercogs.beyonddimensions.api.storage.key.IStackRender; +import com.wintercogs.beyonddimensions.api.storage.key.KeyAmount; +import com.wintercogs.beyonddimensions.api.storage.key.render.MobStackKeyRender; +import net.minecraft.core.HolderLookup; +import net.minecraft.core.component.DataComponentPatch; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.entity.EntityType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; +import java.util.stream.Stream; + +public final class MobStackKey extends LongStackKey +{ + public static final ResourceLocation ID = + ResourceLocation.fromNamespaceAndPath(BDConstants.MODID, "stack_type/mob"); + public static final MobStackKey EMPTY = new MobStackKey(EntityType.PIG); + + public static final MapCodec TYPE_CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group( + BuiltInRegistries.ENTITY_TYPE.byNameCodec().fieldOf("entity_type").forGetter(MobStackKey::entityType) + ).apply(instance, MobStackKey::new)); + + private final EntityType entityType; + + public MobStackKey(EntityType entityType) + { + this.entityType = entityType; + this.stack = new MobType(entityType, 0); + } + + public EntityType entityType() + { + return entityType; + } + + @Override + public ResourceLocation getTypeID() + { + return ID; + } + + @Override + public MapCodec codec() + { + return TYPE_CODEC; + } + + @Override + public @Nullable KeyAmount fromStackObject(Object stack) + { + return stack instanceof MobType mob + ? new KeyAmount(new MobStackKey(mob.entityType()), mob.getStackCount()) + : null; + } + + @Override + public @Nullable MobStackKey fromSourceObject(Object key, DataComponentPatch ignored) + { + if (key instanceof EntityType type) return new MobStackKey(type); + if (key instanceof MobType mob) return new MobStackKey(mob.entityType()); + return null; + } + + @Override + public @NotNull MobType getSource() + { + return stack; + } + + @Override + public String getModId() + { + ResourceLocation id = BuiltInRegistries.ENTITY_TYPE.getKey(entityType); + return id == null ? "Minecraft" : id.getNamespace(); + } + + @Override + public MobStackKey getEmpty() + { + return EMPTY; + } + + @Override + public MobType getEmptyStack() + { + return new MobType(entityType, 0); + } + + @Override + public boolean hasTag(TagKey tagKey) + { + return false; + } + + @Override + public Stream> getTags() + { + return Stream.empty(); + } + + @Override + public boolean isSame(com.wintercogs.beyonddimensions.api.storage.key.IStackKey other) + { + return other instanceof MobStackKey mob && mob.entityType == entityType; + } + + @Override + public boolean isSameTypeSameComponents(com.wintercogs.beyonddimensions.api.storage.key.IStackKey other) + { + return isSame(other); + } + + @Override + public boolean equals(Object other) + { + return other instanceof MobStackKey mob && mob.entityType == entityType; + } + + @Override + public int hashCode() + { + return Objects.hash(ID, entityType); + } + + @Override + public void serialize(RegistryFriendlyByteBuf buf) + { + ByteBufCodecs.registry(net.minecraft.core.registries.Registries.ENTITY_TYPE).encode(buf, entityType); + } + + @Override + public @NotNull MobStackKey deserialize(RegistryFriendlyByteBuf buf) + { + return new MobStackKey(ByteBufCodecs.registry(net.minecraft.core.registries.Registries.ENTITY_TYPE).decode(buf)); + } + + @Override + public @NotNull CompoundTag serializeNBT(HolderLookup.Provider access) + { + CompoundTag tag = new CompoundTag(); + ResourceLocation typeId = BuiltInRegistries.ENTITY_TYPE.getKey(entityType); + tag.putString("entity_type", typeId.toString()); + return tag; + } + + @Override + public @NotNull MobStackKey deserializeNBT(CompoundTag nbt, HolderLookup.Provider access) + { + ResourceLocation typeId = ResourceLocation.tryParse(nbt.getString("entity_type")); + EntityType type = typeId == null + ? EntityType.PIG + : BuiltInRegistries.ENTITY_TYPE.getOptional(typeId).orElse(EntityType.PIG); + return new MobStackKey(type); + } + + @Override + public @NotNull IStackRender getRender() + { + return MobStackKeyRender.INSTANCE; + } +} diff --git a/src/main/java/com/wintercogs/beyonddimensions/api/storage/key/impl/PigStackKey.java b/src/main/java/com/wintercogs/beyonddimensions/api/storage/key/impl/PigStackKey.java new file mode 100644 index 00000000..e06fa712 --- /dev/null +++ b/src/main/java/com/wintercogs/beyonddimensions/api/storage/key/impl/PigStackKey.java @@ -0,0 +1,147 @@ +package com.wintercogs.beyonddimensions.api.storage.key.impl; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.MapCodec; +import com.wintercogs.beyonddimensions.api.ids.BDConstants; +import com.wintercogs.beyonddimensions.api.longtype.PigType; +import com.wintercogs.beyonddimensions.api.storage.key.IStackRender; +import com.wintercogs.beyonddimensions.api.storage.key.KeyAmount; +import com.wintercogs.beyonddimensions.api.storage.key.render.PigStackKeyRender; +import net.minecraft.core.HolderLookup; +import net.minecraft.core.component.DataComponentPatch; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.stream.Stream; + +public final class PigStackKey extends LongStackKey +{ + public static final ResourceLocation ID = + ResourceLocation.fromNamespaceAndPath(BDConstants.MODID, "stack_type/pig"); + public static final PigStackKey INSTANCE = new PigStackKey(); + + public static final MapCodec TYPE_CODEC = new MapCodec<>() + { + @Override + public DataResult decode(com.mojang.serialization.DynamicOps ops, + com.mojang.serialization.MapLike input) + { + return DataResult.success(INSTANCE); + } + + @Override + public com.mojang.serialization.RecordBuilder encode(PigStackKey value, + com.mojang.serialization.DynamicOps ops, + com.mojang.serialization.RecordBuilder prefix) + { + return prefix; + } + + @Override + public Stream keys(com.mojang.serialization.DynamicOps ops) + { + return Stream.empty(); + } + }; + + public static final Codec CODEC = TYPE_CODEC.codec(); + + private PigStackKey() + { + this.stack = new PigType(0); + } + + @Override + public ResourceLocation getTypeID() + { + return ID; + } + + @Override + public MapCodec codec() + { + return TYPE_CODEC; + } + + @Override + public @Nullable KeyAmount fromStackObject(Object stack) + { + return stack instanceof PigType pig ? new KeyAmount(INSTANCE, pig.getStackCount()) : null; + } + + @Override + public @Nullable PigStackKey fromSourceObject(Object key, DataComponentPatch ignored) + { + return key instanceof PigType || key instanceof Number ? INSTANCE : null; + } + + @Override + public @NotNull PigType getSource() + { + return stack; + } + + @Override + public String getModId() + { + return "Minecraft"; + } + + @Override + public PigStackKey getEmpty() + { + return INSTANCE; + } + + @Override + public PigType getEmptyStack() + { + return new PigType(0); + } + + @Override + public boolean hasTag(TagKey tagKey) + { + return false; + } + + @Override + public Stream> getTags() + { + return Stream.empty(); + } + + @Override + public void serialize(RegistryFriendlyByteBuf buf) + { + } + + @Override + public @NotNull PigStackKey deserialize(RegistryFriendlyByteBuf buf) + { + return INSTANCE; + } + + @Override + public @NotNull CompoundTag serializeNBT(HolderLookup.Provider access) + { + return new CompoundTag(); + } + + @Override + public @NotNull PigStackKey deserializeNBT(CompoundTag nbt, HolderLookup.Provider access) + { + return INSTANCE; + } + + @Override + public @NotNull IStackRender getRender() + { + return PigStackKeyRender.INSTANCE; + } +} diff --git a/src/main/java/com/wintercogs/beyonddimensions/api/storage/key/render/MobStackKeyRender.java b/src/main/java/com/wintercogs/beyonddimensions/api/storage/key/render/MobStackKeyRender.java new file mode 100644 index 00000000..2a21766e --- /dev/null +++ b/src/main/java/com/wintercogs/beyonddimensions/api/storage/key/render/MobStackKeyRender.java @@ -0,0 +1,105 @@ +package com.wintercogs.beyonddimensions.api.storage.key.render; + +import com.mojang.blaze3d.systems.RenderSystem; +import com.wintercogs.beyonddimensions.api.storage.key.IStackKey; +import com.wintercogs.beyonddimensions.api.storage.key.IStackRender; +import com.wintercogs.beyonddimensions.api.storage.key.impl.MobStackKey; +import com.wintercogs.beyonddimensions.util.StringFormat; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.network.chat.Component; +import net.minecraft.world.inventory.tooltip.TooltipComponent; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.SpawnEggItem; +import net.minecraft.world.item.TooltipFlag; +import net.neoforged.neoforge.client.ClientTooltipFlag; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Optional; + +public final class MobStackKeyRender implements IStackRender +{ + public static final MobStackKeyRender INSTANCE = new MobStackKeyRender(); + + private MobStackKeyRender() + { + } + + private ItemStack icon(IStackKey key) + { + if (key instanceof MobStackKey mob) + { + SpawnEggItem egg = SpawnEggItem.byId(mob.entityType()); + if (egg != null) return new ItemStack(egg); + } + return new ItemStack(Items.LEAD); + } + + @Override + public void render(GuiGraphics gui, IStackKey key, int x, int y) + { + gui.renderFakeItem(icon(key), x, y); + } + + @Override + public void renderAmount(GuiGraphics gui, long amount, int x, int y) + { + String text = getCountText(amount); + if (text.isEmpty()) return; + + float scale = 0.666f; + var pose = gui.pose(); + pose.pushPose(); + pose.translate(0, 0, 200); + pose.scale(scale, scale, scale); + RenderSystem.disableBlend(); + int width = Minecraft.getInstance().font.width(text); + int renderX = (int) ((x - 1 + 18.0f - width * scale) / scale); + int renderY = (int) ((y - 1 + 16.0f - 5.0f * scale) / scale); + gui.drawString(Minecraft.getInstance().font, text, renderX, renderY, 0xFFFFFF); + pose.popPose(); + } + + @Override + public String getCountText(long count) + { + return count < 0 ? "" : StringFormat.formatCount(count); + } + + @Override + public Component getDisplayName(IStackKey key) + { + return key instanceof MobStackKey mob ? mob.entityType().getDescription() : Component.empty(); + } + + @Override + public List getTooltipLines(IStackKey key, long amount, Item.TooltipContext context, + @Nullable net.minecraft.world.entity.player.Player player, + TooltipFlag flag) + { + return List.of(getDisplayName(key), + Component.translatable("istack.beyonddimensions.storage_num.long_type", amount)); + } + + @Override + public Optional getTooltipImage(IStackKey key) + { + return Optional.empty(); + } + + @Override + public void renderTooltip(GuiGraphics gui, Font font, IStackKey key, long amount, int mouseX, int mouseY) + { + var minecraft = Minecraft.getInstance(); + var context = minecraft.level == null ? Item.TooltipContext.EMPTY : Item.TooltipContext.of(minecraft.level); + gui.renderTooltip(minecraft.font, + getTooltipLines(key, amount, context, minecraft.player, + ClientTooltipFlag.of(minecraft.options.advancedItemTooltips + ? TooltipFlag.Default.ADVANCED : TooltipFlag.Default.NORMAL)), + getTooltipImage(key), icon(key), mouseX, mouseY); + } +} diff --git a/src/main/java/com/wintercogs/beyonddimensions/api/storage/key/render/PigStackKeyRender.java b/src/main/java/com/wintercogs/beyonddimensions/api/storage/key/render/PigStackKeyRender.java new file mode 100644 index 00000000..f067b405 --- /dev/null +++ b/src/main/java/com/wintercogs/beyonddimensions/api/storage/key/render/PigStackKeyRender.java @@ -0,0 +1,94 @@ +package com.wintercogs.beyonddimensions.api.storage.key.render; + +import com.mojang.blaze3d.systems.RenderSystem; +import com.wintercogs.beyonddimensions.api.storage.key.IStackKey; +import com.wintercogs.beyonddimensions.api.storage.key.IStackRender; +import com.wintercogs.beyonddimensions.api.storage.key.impl.PigStackKey; +import com.wintercogs.beyonddimensions.util.StringFormat; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.network.chat.Component; +import net.minecraft.world.item.Items; +import net.minecraft.world.inventory.tooltip.TooltipComponent; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.TooltipFlag; +import net.neoforged.neoforge.client.ClientTooltipFlag; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Optional; + +public final class PigStackKeyRender implements IStackRender +{ + public static final PigStackKeyRender INSTANCE = new PigStackKeyRender(); + + private PigStackKeyRender() + { + } + + @Override + public void render(GuiGraphics gui, IStackKey key, int x, int y) + { + gui.renderFakeItem(new ItemStack(Items.PIG_SPAWN_EGG), x, y); + } + + @Override + public void renderAmount(GuiGraphics gui, long amount, int x, int y) + { + String text = getCountText(amount); + if (text.isEmpty()) return; + + float scale = 0.666f; + var pose = gui.pose(); + pose.pushPose(); + pose.translate(0, 0, 200); + pose.scale(scale, scale, scale); + RenderSystem.disableBlend(); + int width = Minecraft.getInstance().font.width(text); + int renderX = (int) ((x - 1 + 18.0f - width * scale) / scale); + int renderY = (int) ((y - 1 + 16.0f - 5.0f * scale) / scale); + gui.drawString(Minecraft.getInstance().font, text, renderX, renderY, 0xFFFFFF); + pose.popPose(); + } + + @Override + public String getCountText(long count) + { + return count < 0 ? "" : StringFormat.formatCount(count); + } + + @Override + public Component getDisplayName(IStackKey key) + { + return PigStackKey.INSTANCE.getRenderStack().getName(); + } + + @Override + public List getTooltipLines(IStackKey key, long amount, Item.TooltipContext context, + @Nullable net.minecraft.world.entity.player.Player player, + TooltipFlag flag) + { + return List.of(getDisplayName(key), + Component.translatable("istack.beyonddimensions.storage_num.long_type", amount)); + } + + @Override + public Optional getTooltipImage(IStackKey key) + { + return Optional.empty(); + } + + @Override + public void renderTooltip(GuiGraphics gui, Font font, IStackKey key, long amount, int mouseX, int mouseY) + { + var minecraft = Minecraft.getInstance(); + var context = minecraft.level == null ? Item.TooltipContext.EMPTY : Item.TooltipContext.of(minecraft.level); + gui.renderTooltip(minecraft.font, + getTooltipLines(key, amount, context, minecraft.player, + ClientTooltipFlag.of(minecraft.options.advancedItemTooltips + ? TooltipFlag.Default.ADVANCED : TooltipFlag.Default.NORMAL)), + getTooltipImage(key), ItemStack.EMPTY, mouseX, mouseY); + } +} diff --git a/src/main/java/com/wintercogs/beyonddimensions/common/item/NetMagnetItem.java b/src/main/java/com/wintercogs/beyonddimensions/common/item/NetMagnetItem.java index ef3bfe0c..5c474c1d 100644 --- a/src/main/java/com/wintercogs/beyonddimensions/common/item/NetMagnetItem.java +++ b/src/main/java/com/wintercogs/beyonddimensions/common/item/NetMagnetItem.java @@ -5,6 +5,7 @@ import com.wintercogs.beyonddimensions.api.storage.key.KeyAmount; import com.wintercogs.beyonddimensions.api.storage.key.impl.FluidStackKey; import com.wintercogs.beyonddimensions.api.storage.key.impl.ItemStackKey; +import com.wintercogs.beyonddimensions.api.storage.key.impl.MobStackKey; import com.wintercogs.beyonddimensions.common.init.BDDataComponents; import com.wintercogs.beyonddimensions.common.init.BDFluids; import com.wintercogs.beyonddimensions.common.machine.*; @@ -19,8 +20,14 @@ import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResultHolder; import net.minecraft.world.SimpleMenuProvider; +import net.minecraft.world.entity.AgeableMob; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.ExperienceOrb; +import net.minecraft.world.entity.animal.Animal; +import net.minecraft.world.entity.Leashable; +import net.minecraft.world.entity.TamableAnimal; +import net.minecraft.world.entity.npc.Villager; +import net.minecraft.world.entity.npc.VillagerProfession; import net.minecraft.world.entity.item.ItemEntity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; @@ -118,6 +125,17 @@ public void workContent(ItemStack stack, Level level, Entity holder, int slotId, UnifiedStorage storage = NetedItem.getNet(stack).getUnifiedStorage(); + // 收集可繁殖动物和村民。只接收成年、无自定义数据且未骑乘的实体。 + for (AgeableMob mob : level.getEntitiesOfClass(AgeableMob.class, searchArea, this::canCollectMob)) + { + MobStackKey mobKey = new MobStackKey(mob.getType()); + if (storage.insert(mobKey, 1, true).isEmpty()) + { + storage.insert(mobKey, 1, false); + mob.discard(); + } + } + // 开始收集物品 if (hopperItemMode == HopperItemMode.ALLOW) { @@ -181,6 +199,22 @@ public void workContent(ItemStack stack, Level level, Entity holder, int slotId, } } + private boolean canCollectMob(AgeableMob mob) + { + return mob.isAlive() + && !mob.isBaby() + && !mob.hasCustomName() + && !mob.isPassenger() + && !mob.isVehicle() + && (!(mob instanceof Leashable leashable) || !leashable.isLeashed()) + && (!(mob instanceof TamableAnimal tamable) || !tamable.isTame()) + && (!(mob instanceof Villager villager) + || villager.getVillagerXp() == 0 + && villager.getVillagerData().getProfession() == VillagerProfession.NONE + && villager.getInventory().isEmpty()) + && (mob instanceof Animal || mob instanceof Villager); + } + @Override public int getTicksPerWork(ItemStack stack, Level level, Entity holder, int slotId, boolean isSelected) { diff --git a/src/main/resources/assets/beyonddimensions/lang/en_us.json b/src/main/resources/assets/beyonddimensions/lang/en_us.json index 8a503c0f..4859f3f5 100644 --- a/src/main/resources/assets/beyonddimensions/lang/en_us.json +++ b/src/main/resources/assets/beyonddimensions/lang/en_us.json @@ -224,5 +224,6 @@ "msg.beyonddimensions.primary_net.switch.cleared": "Primary network cleared", "msg.beyonddimensions.primary_net.switch.none_available": "No available network to switch to", "msg.beyonddimensions.primary_net.switch.invalid_target": "That network is unavailable or you are no longer a member", - "msg.beyonddimensions.primary_net.switch.no_other": "No other primary network is available" + "msg.beyonddimensions.primary_net.switch.no_other": "No other primary network is available", + "types.beyonddimensions.pig.name": "Pigs" } diff --git a/src/main/resources/assets/beyonddimensions/lang/zh_cn.json b/src/main/resources/assets/beyonddimensions/lang/zh_cn.json index 208b6f55..9ea199fc 100644 --- a/src/main/resources/assets/beyonddimensions/lang/zh_cn.json +++ b/src/main/resources/assets/beyonddimensions/lang/zh_cn.json @@ -224,5 +224,6 @@ "msg.beyonddimensions.primary_net.switch.cleared": "已切换为无主网络", "msg.beyonddimensions.primary_net.switch.none_available": "当前没有可切换的网络", "msg.beyonddimensions.primary_net.switch.invalid_target": "目标网络不可用,或你已经不再属于该网络", - "msg.beyonddimensions.primary_net.switch.no_other": "没有其他可切换的主网络" + "msg.beyonddimensions.primary_net.switch.no_other": "没有其他可切换的主网络", + "types.beyonddimensions.pig.name": "猪" }