package ru.bclib.api.spawning; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import net.fabricmc.fabric.mixin.object.builder.SpawnRestrictionAccessor; import net.minecraft.core.BlockPos; import net.minecraft.world.Difficulty; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.MobSpawnType; import net.minecraft.world.entity.SpawnPlacements.SpawnPredicate; import net.minecraft.world.entity.SpawnPlacements.Type; import net.minecraft.world.level.ServerLevelAccessor; import net.minecraft.world.level.levelgen.Heightmap.Types; import net.minecraft.world.phys.AABB; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Random; import java.util.function.Supplier; public class SpawnRuleBulder { private static final Map RULES_CACHE = Maps.newHashMap(); private static final SpawnRuleBulder INSTANCE = new SpawnRuleBulder(); private List rules = Lists.newArrayList(); private SpawnRuleEntry entryInstance; private EntityType entityType; private SpawnRuleBulder() {} /** * Starts new rule building process. * @return prepared {@link SpawnRuleBulder} instance */ public static SpawnRuleBulder start(EntityType entityType) { INSTANCE.entityType = entityType; INSTANCE.rules.clear(); return INSTANCE; } /** * Stop entity spawn on peaceful {@link Difficulty} * @return same {@link SpawnRuleBulder} instance */ public SpawnRuleBulder notPeaceful() { entryInstance = getFromCache("not_peaceful", () -> { return new SpawnRuleEntry(0, (type, world, spawnReason, pos, random) -> world.getDifficulty() != Difficulty.PEACEFUL); }); rules.add(entryInstance); return this; } /** * Restricts entity spawn below world logical height (useful for Nether mobs). * @return same {@link SpawnRuleBulder} instance */ public SpawnRuleBulder belowMaxHeight() { entryInstance = getFromCache("not_peaceful", () -> { return new SpawnRuleEntry(0, (type, world, spawnReason, pos, random) -> pos.getY() < world.dimensionType().logicalHeight()); }); rules.add(entryInstance); return this; } /** * Will spawn entity only below specified brightness value. * @param lightLevel light level upper limit. * @return same {@link SpawnRuleBulder} instance */ public SpawnRuleBulder belowBrightness(int lightLevel) { entryInstance = getFromCache("below_brightness_" + lightLevel, () -> { return new SpawnRuleEntry(1, (type, world, spawnReason, pos, random) -> world.getMaxLocalRawBrightness(pos) <= lightLevel); }); rules.add(entryInstance); return this; } /** * Will spawn entity only above specified brightness value. * @param lightLevel light level lower limit. * @return same {@link SpawnRuleBulder} instance */ public SpawnRuleBulder aboveBrightness(int lightLevel) { entryInstance = getFromCache("above_brightness_" + lightLevel, () -> { return new SpawnRuleEntry(1, (type, world, spawnReason, pos, random) -> world.getMaxLocalRawBrightness(pos) >= lightLevel); }); rules.add(entryInstance); return this; } /** * Entity spawn will follow common vanilla spawn rules - spawn in darkness and not on peaceful level. * @return same {@link SpawnRuleBulder} instance */ public SpawnRuleBulder vanillaHostile() { return notPeaceful().belowBrightness(7); } /** * Will spawn entity only if count of nearby entities will be lower than specified. * @param selectorType selector {@link EntityType} to search. * @param count max entity count. * @param side side of box to search in. * @return same {@link SpawnRuleBulder} instance */ public SpawnRuleBulder maxNerby(EntityType selectorType, int count, int side) { final Class baseClass = selectorType.getBaseClass(); entryInstance = getFromCache("below_brightness_" + selectorType.getDescriptionId(), () -> { return new SpawnRuleEntry(2, (type, world, spawnReason, pos, random) -> { try { final AABB box = new AABB(pos).inflate(side, world.getHeight(), side); final List list = world.getEntitiesOfClass(baseClass, box, (entity) -> true); return list.size() < count; } catch (Exception e) { return true; } }); }); rules.add(entryInstance); return this; } /** * Will spawn entity only if count of nearby entities with same type will be lower than specified. * @param count max entity count. * @param side side of box to search in. * @return same {@link SpawnRuleBulder} instance */ public SpawnRuleBulder maxNerby(int count, int side) { return maxNerby(entityType, count, side); } /** * Will spawn entity only if count of nearby entities with same type will be lower than specified. * @param count max entity count. * @return same {@link SpawnRuleBulder} instance */ public SpawnRuleBulder maxNerby(int count) { return maxNerby(entityType, count, 256); } /** * Finalize spawning rule creation. * @param spawnType {@link Type} of spawn. * @param heightmapType {@link Types} heightmap type. */ public void build(Type spawnType, Types heightmapType) { final List rulesCopy = Lists.newArrayList(this.rules); Collections.sort(rulesCopy); SpawnPredicate predicate = new SpawnPredicate() { @Override public boolean test(EntityType entityType, ServerLevelAccessor serverLevelAccessor, MobSpawnType mobSpawnType, BlockPos blockPos, Random random) { for (SpawnRuleEntry rule: rulesCopy) { if (!rule.canSpawn(entityType, serverLevelAccessor, mobSpawnType, blockPos, random)) { return false; } } return true; } }; SpawnRestrictionAccessor.callRegister(entityType, spawnType, heightmapType, predicate); } /** * Finalize spawning rule creation with No Restrictions spawn type, useful for flying entities. * @param heightmapType {@link Types} heightmap type. */ public void buildNoRestrictions(Types heightmapType) { build(Type.NO_RESTRICTIONS, heightmapType); } /** * Finalize spawning rule creation with On Ground spawn type, useful for common entities. * @param heightmapType {@link Types} heightmap type. */ public void buildOnGround(Types heightmapType) { build(Type.ON_GROUND, heightmapType); } /** * Finalize spawning rule creation with In Water spawn type, useful for water entities. * @param heightmapType {@link Types} heightmap type. */ public void buildInWater(Types heightmapType) { build(Type.IN_WATER, heightmapType); } /** * Finalize spawning rule creation with In Lava spawn type, useful for lava entities. * @param heightmapType {@link Types} heightmap type. */ public void buildInLava(Types heightmapType) { build(Type.IN_LAVA, heightmapType); } /** * Internal function, will take entry from cache or create it if necessary. * @param name {@link String} entry internal name. * @param supplier {@link Supplier} for {@link SpawnRuleEntry}. * @return new or existing {@link SpawnRuleEntry}. */ private static SpawnRuleEntry getFromCache(String name, Supplier supplier) { SpawnRuleEntry entry = RULES_CACHE.get(name); if (entry == null) { entry = supplier.get(); RULES_CACHE.put(name, entry); } return entry; } }