From 7def63ee018663e6f3e936dd6935f3fb02236e33 Mon Sep 17 00:00:00 2001 From: paulevsGitch Date: Sun, 28 Nov 2021 05:45:53 +0300 Subject: [PATCH] Spawn API enhancements --- src/main/java/ru/bclib/BCLibPatch.java | 2 +- src/main/java/ru/bclib/api/SpawnAPI.java | 2 +- .../bclib/api/datafixer/ForcedLevelPatch.java | 2 + .../bclib/api/datafixer/MigrationProfile.java | 2 + .../java/ru/bclib/api/datafixer/Patch.java | 2 + .../bclib/api/spawning/SpawnRuleBulder.java | 215 ++++++++++++++++++ .../ru/bclib/api/spawning/SpawnRuleEntry.java | 30 +++ .../PatchBiFunction.java | 5 +- .../PatchFunction.java | 5 +- .../java/ru/bclib/interfaces/SpawnRule.java | 14 ++ src/main/resources/bclib.mixins.client.json | 14 +- src/main/resources/bclib.mixins.common.json | 52 ++--- 12 files changed, 308 insertions(+), 37 deletions(-) create mode 100644 src/main/java/ru/bclib/api/spawning/SpawnRuleBulder.java create mode 100644 src/main/java/ru/bclib/api/spawning/SpawnRuleEntry.java rename src/main/java/ru/bclib/{api/datafixer => interfaces}/PatchBiFunction.java (52%) rename src/main/java/ru/bclib/{api/datafixer => interfaces}/PatchFunction.java (50%) create mode 100644 src/main/java/ru/bclib/interfaces/SpawnRule.java diff --git a/src/main/java/ru/bclib/BCLibPatch.java b/src/main/java/ru/bclib/BCLibPatch.java index d7db7d40..44e4bc51 100644 --- a/src/main/java/ru/bclib/BCLibPatch.java +++ b/src/main/java/ru/bclib/BCLibPatch.java @@ -3,7 +3,7 @@ package ru.bclib; import net.minecraft.nbt.CompoundTag; import ru.bclib.api.datafixer.DataFixerAPI; import ru.bclib.api.datafixer.Patch; -import ru.bclib.api.datafixer.PatchFunction; +import ru.bclib.interfaces.PatchFunction; diff --git a/src/main/java/ru/bclib/api/SpawnAPI.java b/src/main/java/ru/bclib/api/SpawnAPI.java index 1ce39ab1..1493ca99 100644 --- a/src/main/java/ru/bclib/api/SpawnAPI.java +++ b/src/main/java/ru/bclib/api/SpawnAPI.java @@ -14,7 +14,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Random; - +@Deprecated(forRemoval = true) public class SpawnAPI { @FunctionalInterface public interface SpawnRule { diff --git a/src/main/java/ru/bclib/api/datafixer/ForcedLevelPatch.java b/src/main/java/ru/bclib/api/datafixer/ForcedLevelPatch.java index 34951454..333d473b 100644 --- a/src/main/java/ru/bclib/api/datafixer/ForcedLevelPatch.java +++ b/src/main/java/ru/bclib/api/datafixer/ForcedLevelPatch.java @@ -3,6 +3,8 @@ package ru.bclib.api.datafixer; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import org.jetbrains.annotations.NotNull; +import ru.bclib.interfaces.PatchBiFunction; +import ru.bclib.interfaces.PatchFunction; import java.util.HashMap; import java.util.List; diff --git a/src/main/java/ru/bclib/api/datafixer/MigrationProfile.java b/src/main/java/ru/bclib/api/datafixer/MigrationProfile.java index 26065093..831470ff 100644 --- a/src/main/java/ru/bclib/api/datafixer/MigrationProfile.java +++ b/src/main/java/ru/bclib/api/datafixer/MigrationProfile.java @@ -7,6 +7,8 @@ import net.minecraft.nbt.Tag; import org.jetbrains.annotations.NotNull; import ru.bclib.BCLib; import ru.bclib.api.WorldDataAPI; +import ru.bclib.interfaces.PatchBiFunction; +import ru.bclib.interfaces.PatchFunction; import ru.bclib.util.ModUtil; import java.io.File; diff --git a/src/main/java/ru/bclib/api/datafixer/Patch.java b/src/main/java/ru/bclib/api/datafixer/Patch.java index bc64e3c5..30a26ce7 100644 --- a/src/main/java/ru/bclib/api/datafixer/Patch.java +++ b/src/main/java/ru/bclib/api/datafixer/Patch.java @@ -3,6 +3,8 @@ package ru.bclib.api.datafixer; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import org.jetbrains.annotations.NotNull; +import ru.bclib.interfaces.PatchBiFunction; +import ru.bclib.interfaces.PatchFunction; import ru.bclib.util.ModUtil; import java.util.ArrayList; diff --git a/src/main/java/ru/bclib/api/spawning/SpawnRuleBulder.java b/src/main/java/ru/bclib/api/spawning/SpawnRuleBulder.java new file mode 100644 index 00000000..92c35ce9 --- /dev/null +++ b/src/main/java/ru/bclib/api/spawning/SpawnRuleBulder.java @@ -0,0 +1,215 @@ +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; + } +} diff --git a/src/main/java/ru/bclib/api/spawning/SpawnRuleEntry.java b/src/main/java/ru/bclib/api/spawning/SpawnRuleEntry.java new file mode 100644 index 00000000..710860c6 --- /dev/null +++ b/src/main/java/ru/bclib/api/spawning/SpawnRuleEntry.java @@ -0,0 +1,30 @@ +package ru.bclib.api.spawning; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.Mob; +import net.minecraft.world.entity.MobSpawnType; +import net.minecraft.world.level.LevelAccessor; +import org.jetbrains.annotations.NotNull; +import ru.bclib.interfaces.SpawnRule; + +import java.util.Random; + +public class SpawnRuleEntry implements Comparable { + private final SpawnRule rule; + private final byte priority; + + public SpawnRuleEntry(int priority, SpawnRule rule) { + this.priority = (byte) priority; + this.rule = rule; + } + + boolean canSpawn(EntityType type, LevelAccessor world, MobSpawnType spawnReason, BlockPos pos, Random random) { + return rule.canSpawn(type, world, spawnReason, pos, random); + } + + @Override + public int compareTo(@NotNull SpawnRuleEntry entry) { + return Integer.compare(priority, entry.priority); + } +} diff --git a/src/main/java/ru/bclib/api/datafixer/PatchBiFunction.java b/src/main/java/ru/bclib/interfaces/PatchBiFunction.java similarity index 52% rename from src/main/java/ru/bclib/api/datafixer/PatchBiFunction.java rename to src/main/java/ru/bclib/interfaces/PatchBiFunction.java index 6bd8da12..4bb345af 100644 --- a/src/main/java/ru/bclib/api/datafixer/PatchBiFunction.java +++ b/src/main/java/ru/bclib/interfaces/PatchBiFunction.java @@ -1,4 +1,7 @@ -package ru.bclib.api.datafixer; +package ru.bclib.interfaces; + +import ru.bclib.api.datafixer.MigrationProfile; +import ru.bclib.api.datafixer.PatchDidiFailException; @FunctionalInterface public interface PatchBiFunction { diff --git a/src/main/java/ru/bclib/api/datafixer/PatchFunction.java b/src/main/java/ru/bclib/interfaces/PatchFunction.java similarity index 50% rename from src/main/java/ru/bclib/api/datafixer/PatchFunction.java rename to src/main/java/ru/bclib/interfaces/PatchFunction.java index 7d429d68..809d55e0 100644 --- a/src/main/java/ru/bclib/api/datafixer/PatchFunction.java +++ b/src/main/java/ru/bclib/interfaces/PatchFunction.java @@ -1,4 +1,7 @@ -package ru.bclib.api.datafixer; +package ru.bclib.interfaces; + +import ru.bclib.api.datafixer.MigrationProfile; +import ru.bclib.api.datafixer.PatchDidiFailException; @FunctionalInterface public interface PatchFunction { diff --git a/src/main/java/ru/bclib/interfaces/SpawnRule.java b/src/main/java/ru/bclib/interfaces/SpawnRule.java new file mode 100644 index 00000000..8dffc463 --- /dev/null +++ b/src/main/java/ru/bclib/interfaces/SpawnRule.java @@ -0,0 +1,14 @@ +package ru.bclib.interfaces; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.Mob; +import net.minecraft.world.entity.MobSpawnType; +import net.minecraft.world.level.LevelAccessor; + +import java.util.Random; + +@FunctionalInterface +public interface SpawnRule { + boolean canSpawn(EntityType type, LevelAccessor world, MobSpawnType spawnReason, BlockPos pos, Random random); +} diff --git a/src/main/resources/bclib.mixins.client.json b/src/main/resources/bclib.mixins.client.json index b3aebf4a..3d87e139 100644 --- a/src/main/resources/bclib.mixins.client.json +++ b/src/main/resources/bclib.mixins.client.json @@ -4,16 +4,16 @@ "package": "ru.bclib.mixin.client", "compatibilityLevel": "JAVA_16", "client": [ - "SimpleReloadableResourceManagerMixin", - "EnchantingTableBlockMixin", + "AnvilScreenMixin", "BackgroundRendererMixin", "ClientRecipeBookMixin", - "ModelManagerMixin", - "TextureAtlasMixin", - "AnvilScreenMixin", - "ModelBakeryMixin", + "EnchantingTableBlockMixin", + "GameMixin", "MinecraftMixin", - "GameMixin" + "ModelBakeryMixin", + "ModelManagerMixin", + "SimpleReloadableResourceManagerMixin", + "TextureAtlasMixin" ], "injectors": { "defaultRequire": 1 diff --git a/src/main/resources/bclib.mixins.common.json b/src/main/resources/bclib.mixins.common.json index e15cb1d5..3f39c3ef 100644 --- a/src/main/resources/bclib.mixins.common.json +++ b/src/main/resources/bclib.mixins.common.json @@ -4,35 +4,35 @@ "package": "ru.bclib.mixin.common", "compatibilityLevel": "JAVA_16", "mixins": [ - "SimpleReloadableResourceManagerMixin", - "shears.DiggingEnchantmentMixin", - "LayerLightSectionStorageMixin", - "shears.TripWireBlockMixin", - "ChunkBiomeContainerMixin", - "shears.BeehiveBlockMixin", - "shears.PumpkinBlockMixin", - "shears.MushroomCowMixin", - "shears.SnowGolemMixin", - "ComposterBlockAccessor", - "InternalBiomeDataMixin", - "PotionBrewingAccessor", - "RecipeManagerAccessor", - "EnchantmentMenuMixin", - "MinecraftServerMixin", - "PistonBaseBlockMixin", - "WorldGenRegionMixin", - "DimensionTypeMixin", - "RecipeManagerMixin", - "CraftingMenuMixin", - "BoneMealItemMixin", - "shears.SheepMixin", - "PortalShapeMixin", - "ServerLevelMixin", "AnvilBlockMixin", "AnvilMenuMixin", - "TagLoaderMixin", "BiomeMixin", - "MainMixin" + "BoneMealItemMixin", + "ChunkBiomeContainerMixin", + "ComposterBlockAccessor", + "CraftingMenuMixin", + "DimensionTypeMixin", + "EnchantmentMenuMixin", + "InternalBiomeDataMixin", + "LayerLightSectionStorageMixin", + "MainMixin", + "MinecraftServerMixin", + "PistonBaseBlockMixin", + "PortalShapeMixin", + "PotionBrewingAccessor", + "RecipeManagerAccessor", + "RecipeManagerMixin", + "ServerLevelMixin", + "SimpleReloadableResourceManagerMixin", + "TagLoaderMixin", + "WorldGenRegionMixin", + "shears.BeehiveBlockMixin", + "shears.DiggingEnchantmentMixin", + "shears.MushroomCowMixin", + "shears.PumpkinBlockMixin", + "shears.SheepMixin", + "shears.SnowGolemMixin", + "shears.TripWireBlockMixin" ], "injectors": { "defaultRequire": 1