Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -93,6 +103,21 @@ public class DimensionsNet extends SavedData
*/
private int currentTime = 0;

/**
* 各类动物和村民的繁殖进度。每种实体独立计时,约5分钟完成一轮繁殖。
*/
private final Map<ResourceLocation, Integer> mobBreedingTimes = new HashMap<>();
private final Map<EntityType<?>, 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<net.minecraft.world.item.Item, Integer> VILLAGER_FOODS = Map.of(
Items.BREAD, 4,
Items.CARROT, 1,
Items.POTATO, 1,
Items.BEETROOT, 1
);

/**
* 构造函数
*
Expand Down Expand Up @@ -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"))
{
Expand All @@ -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");
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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();
Expand All @@ -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<ResourceLocation> 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<ItemStackKey, Long> costs = new LinkedHashMap<>();
for (Map.Entry<net.minecraft.world.item.Item, Integer> 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<net.minecraft.world.item.Item, Integer> 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<ItemStackKey, Long> 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<ItemStackKey, Long> extractedFoods = new LinkedHashMap<>();
for (Map.Entry<ItemStackKey, Long> 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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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<MobType>
{
public static final Codec<MobType> 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);
}
}
Original file line number Diff line number Diff line change
@@ -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<PigType>
{
public static final Codec<PigType> 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);
}
}
Loading