From cc7232f520dad5d39dda84ff9a0b09a3a052a031 Mon Sep 17 00:00:00 2001 From: Luke Bemish Date: Sat, 23 Dec 2023 04:47:22 +0000 Subject: [PATCH] Spawn provider system --- .../lukebemish/tempest/api/WeatherStatus.java | 16 +++- .../lukebemish/tempest/impl/Constants.java | 3 + .../client/FancyPrecipitationRenderer.java | 6 +- .../impl/data/WeatherSpawnProvider.java | 73 +++++++++++++++++++ .../tempest/impl/mixin/LivingEntityMixin.java | 5 +- .../impl/mixin/NaturalSpawnerMixin.java | 34 +++++++++ .../tags/entity_types/damaged_by_hail.json | 5 ++ .../tags/entity_types/immune_to_hail.json | 7 ++ common/src/main/resources/mixin.tempest.json | 3 +- .../impl/fabriquilt/ModEntrypoint.java | 14 ++++ .../tempest/impl/forge/ModEntrypoint.java | 2 + 11 files changed, 159 insertions(+), 9 deletions(-) create mode 100644 common/src/main/java/dev/lukebemish/tempest/impl/data/WeatherSpawnProvider.java create mode 100644 common/src/main/java/dev/lukebemish/tempest/impl/mixin/NaturalSpawnerMixin.java create mode 100644 common/src/main/resources/data/tempest/tags/entity_types/damaged_by_hail.json create mode 100644 common/src/main/resources/data/tempest/tags/entity_types/immune_to_hail.json diff --git a/common/src/main/java/dev/lukebemish/tempest/api/WeatherStatus.java b/common/src/main/java/dev/lukebemish/tempest/api/WeatherStatus.java index cc30605..668005e 100644 --- a/common/src/main/java/dev/lukebemish/tempest/api/WeatherStatus.java +++ b/common/src/main/java/dev/lukebemish/tempest/api/WeatherStatus.java @@ -1,10 +1,15 @@ package dev.lukebemish.tempest.api; +import com.mojang.serialization.Codec; import dev.lukebemish.tempest.impl.Services; import net.minecraft.core.BlockPos; +import net.minecraft.util.StringRepresentable; import net.minecraft.world.level.Level; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.phys.Vec2; +import org.jetbrains.annotations.NotNull; + +import java.util.Locale; @SuppressWarnings("unused") public final class WeatherStatus { @@ -70,7 +75,7 @@ public static WeatherStatus atPosition(Level level, BlockPos position) { return data.makeApiStatus(WeatherStatus::new, position); } - public enum Kind { + public enum Kind implements StringRepresentable { /** * No precipitation. */ @@ -90,6 +95,13 @@ public enum Kind { /** * Freezing rain; produced at medium-cold temperatures. Coats the ground in ice and freezes up redstone components. */ - SLEET + SLEET; + + @Override + public @NotNull String getSerializedName() { + return name().toLowerCase(Locale.ROOT); + } + + public static final Codec CODEC = StringRepresentable.fromEnum(Kind::values); } } diff --git a/common/src/main/java/dev/lukebemish/tempest/impl/Constants.java b/common/src/main/java/dev/lukebemish/tempest/impl/Constants.java index dcfa8d0..4a4d077 100644 --- a/common/src/main/java/dev/lukebemish/tempest/impl/Constants.java +++ b/common/src/main/java/dev/lukebemish/tempest/impl/Constants.java @@ -8,6 +8,7 @@ import net.minecraft.resources.ResourceLocation; import net.minecraft.tags.TagKey; import net.minecraft.world.damagesource.DamageType; +import net.minecraft.world.entity.EntityType; import net.minecraft.world.level.block.Block; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -21,6 +22,8 @@ public final class Constants { public static final TagKey FREEZES_UP = TagKey.create(Registries.BLOCK, id("freezes_up")); public static final TagKey BREAKS_WITH_HAIL = TagKey.create(Registries.BLOCK, id("breaks_with_hail")); public static final TagKey SAFE_WITH_HAIL = TagKey.create(Registries.BLOCK, id("safe_with_hail")); + public static final TagKey> DAMAGED_BY_HAIL = TagKey.create(Registries.ENTITY_TYPE, id("damaged_by_hail")); + public static final TagKey> IMMUNE_TO_HAIL = TagKey.create(Registries.ENTITY_TYPE, id("damaged_by_hail")); public static final ResourceKey HAIL_DAMAGE_TYPE = ResourceKey.create(Registries.DAMAGE_TYPE, id("hail")); diff --git a/common/src/main/java/dev/lukebemish/tempest/impl/client/FancyPrecipitationRenderer.java b/common/src/main/java/dev/lukebemish/tempest/impl/client/FancyPrecipitationRenderer.java index efff2d4..20ab7fb 100644 --- a/common/src/main/java/dev/lukebemish/tempest/impl/client/FancyPrecipitationRenderer.java +++ b/common/src/main/java/dev/lukebemish/tempest/impl/client/FancyPrecipitationRenderer.java @@ -121,7 +121,7 @@ public void renderWeather(LightTexture lightTexture, float partialTick, double c RenderSystem.enableDepthTest(); int layers = 10; - int belowAbove = 5; + int belowOffset = 5; int rendering = -1; @@ -142,8 +142,8 @@ public void renderWeather(LightTexture lightTexture, float partialTick, double c if (status != null) { float precipLevel = status.intensity; int lowerY = level.getHeight(Heightmap.Types.MOTION_BLOCKING, x, z); - int minY = Math.max(floorY - belowAbove, lowerY); - int maxY = Math.max(floorY + belowAbove, lowerY); + int minY = Math.max(floorY - belowOffset, lowerY); + int maxY = Math.max(floorY + layers, lowerY); int upperY = Math.max(floorY, lowerY); diff --git a/common/src/main/java/dev/lukebemish/tempest/impl/data/WeatherSpawnProvider.java b/common/src/main/java/dev/lukebemish/tempest/impl/data/WeatherSpawnProvider.java new file mode 100644 index 0000000..fd43332 --- /dev/null +++ b/common/src/main/java/dev/lukebemish/tempest/impl/data/WeatherSpawnProvider.java @@ -0,0 +1,73 @@ +package dev.lukebemish.tempest.impl.data; + +import com.google.gson.JsonElement; +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.JsonOps; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import dev.lukebemish.tempest.api.WeatherStatus; +import dev.lukebemish.tempest.impl.Constants; +import net.minecraft.core.BlockPos; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.packs.resources.ResourceManager; +import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener; +import net.minecraft.util.profiling.ProfilerFiller; +import net.minecraft.util.random.WeightedRandomList; +import net.minecraft.world.entity.MobCategory; +import net.minecraft.world.level.biome.MobSpawnSettings; + +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +public record WeatherSpawnProvider(WeatherStatus.Kind kind, List spawners) { + public static final Codec CODEC = RecordCodecBuilder.create(i -> i.group( + WeatherStatus.Kind.CODEC.comapFlatMap(kind -> kind == WeatherStatus.Kind.CLEAR ? DataResult.error(() -> "Weather kind 'clear' is not valid here") : DataResult.success(kind), Function.identity()).fieldOf("kind").forGetter(WeatherSpawnProvider::kind), + MobSpawnSettings.SpawnerData.CODEC.listOf().fieldOf("spawners").forGetter(WeatherSpawnProvider::spawners) + ).apply(i, WeatherSpawnProvider::new)); + + public static WeightedRandomList extendList(WeightedRandomList original, ServerLevel level, BlockPos pos, MobCategory mobCategory) { + if (level.canSeeSky(pos)) { + var kind = WeatherStatus.atPosition(level, pos).kind(); + if (kind == WeatherStatus.Kind.CLEAR) { + return original; + } + var list = new ArrayList<>(original.unwrap()); + list.addAll(ReloadListener.PROVIDERS.getOrDefault(kind, Map.of()).getOrDefault(mobCategory, List.of())); + return WeightedRandomList.create(list); + } + return original; + } + + public static class ReloadListener extends SimpleJsonResourceReloadListener { + public static final String DIRECTORY = Constants.MOD_ID + "/spawn_providers"; + + public ReloadListener() { + super(Constants.GSON, DIRECTORY); + } + + public static final Map>> PROVIDERS = new EnumMap<>(WeatherStatus.Kind.class); + + @Override + protected void apply(Map object, ResourceManager resourceManager, ProfilerFiller profiler) { + PROVIDERS.clear(); + object.forEach((id, element) -> { + var result = CODEC.parse(JsonOps.INSTANCE, element); + if (result.result().isEmpty()) { + Constants.LOGGER.error("Failed to decode spawn provider {}: {}", id, result.error().orElseThrow().message()); + } else { + var provider = result.result().get(); + var map = PROVIDERS.computeIfAbsent(provider.kind(), k -> new EnumMap<>(MobCategory.class)); + provider.spawners().forEach(spawner -> { + var list = map.computeIfAbsent(spawner.type.getCategory(), k -> new ArrayList<>()); + list.add(spawner); + }); + } + }); + Constants.LOGGER.info("Loaded {} weather spawn providers", object.size()); + } + } +} diff --git a/common/src/main/java/dev/lukebemish/tempest/impl/mixin/LivingEntityMixin.java b/common/src/main/java/dev/lukebemish/tempest/impl/mixin/LivingEntityMixin.java index 5bc0987..a954ecd 100644 --- a/common/src/main/java/dev/lukebemish/tempest/impl/mixin/LivingEntityMixin.java +++ b/common/src/main/java/dev/lukebemish/tempest/impl/mixin/LivingEntityMixin.java @@ -79,10 +79,9 @@ public abstract class LivingEntityMixin extends Entity { if (!this.level().isClientSide()) { if ((this.tickCount & 8) == 0 && status != null && status.category == WeatherCategory.HAIL) { var source = new DamageSource(this.level().registryAccess().registryOrThrow(Registries.DAMAGE_TYPE).getHolderOrThrow(Constants.HAIL_DAMAGE_TYPE)); - //noinspection ConstantValue - if ((Object) this instanceof Player) { + if (this.getType().is(Constants.DAMAGED_BY_HAIL)) { this.hurt(source, status.intensity / 3); - } else { + } else if (!this.getType().is(Constants.IMMUNE_TO_HAIL)) { this.hurt(source, 0); } } diff --git a/common/src/main/java/dev/lukebemish/tempest/impl/mixin/NaturalSpawnerMixin.java b/common/src/main/java/dev/lukebemish/tempest/impl/mixin/NaturalSpawnerMixin.java new file mode 100644 index 0000000..71a937a --- /dev/null +++ b/common/src/main/java/dev/lukebemish/tempest/impl/mixin/NaturalSpawnerMixin.java @@ -0,0 +1,34 @@ +package dev.lukebemish.tempest.impl.mixin; + +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import dev.lukebemish.tempest.impl.data.WeatherSpawnProvider; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.random.WeightedRandomList; +import net.minecraft.world.entity.MobCategory; +import net.minecraft.world.level.NaturalSpawner; +import net.minecraft.world.level.StructureManager; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.biome.MobSpawnSettings; +import net.minecraft.world.level.chunk.ChunkGenerator; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(NaturalSpawner.class) +public class NaturalSpawnerMixin { + @ModifyExpressionValue( + method = "mobsAt(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/level/StructureManager;Lnet/minecraft/world/level/chunk/ChunkGenerator;Lnet/minecraft/world/entity/MobCategory;Lnet/minecraft/core/BlockPos;Lnet/minecraft/core/Holder;)Lnet/minecraft/util/random/WeightedRandomList;", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/world/level/chunk/ChunkGenerator;getMobsAt(Lnet/minecraft/core/Holder;Lnet/minecraft/world/level/StructureManager;Lnet/minecraft/world/entity/MobCategory;Lnet/minecraft/core/BlockPos;)Lnet/minecraft/util/random/WeightedRandomList;" + ) + ) + private static WeightedRandomList tempest$mobsAt( + WeightedRandomList original, + ServerLevel level, StructureManager structureManager, ChunkGenerator generator, MobCategory category, BlockPos pos, @Nullable Holder biome + ) { + return WeatherSpawnProvider.extendList(original, level, pos, category); + } +} diff --git a/common/src/main/resources/data/tempest/tags/entity_types/damaged_by_hail.json b/common/src/main/resources/data/tempest/tags/entity_types/damaged_by_hail.json new file mode 100644 index 0000000..020dc32 --- /dev/null +++ b/common/src/main/resources/data/tempest/tags/entity_types/damaged_by_hail.json @@ -0,0 +1,5 @@ +{ + "values": [ + "minecraft:player" + ] +} diff --git a/common/src/main/resources/data/tempest/tags/entity_types/immune_to_hail.json b/common/src/main/resources/data/tempest/tags/entity_types/immune_to_hail.json new file mode 100644 index 0000000..d4ff5c0 --- /dev/null +++ b/common/src/main/resources/data/tempest/tags/entity_types/immune_to_hail.json @@ -0,0 +1,7 @@ +{ + "values": [ + "minecraft:snow_golem", + "minecraft:polar_bear", + "minecraft:fox" + ] +} diff --git a/common/src/main/resources/mixin.tempest.json b/common/src/main/resources/mixin.tempest.json index 359b82d..217c79a 100644 --- a/common/src/main/resources/mixin.tempest.json +++ b/common/src/main/resources/mixin.tempest.json @@ -19,7 +19,8 @@ "FoxSeekShelterMixin", "LightningRodBlockMixin", "PandaMixin", - "ThrownTridentMixin" + "ThrownTridentMixin", + "NaturalSpawnerMixin" ], "client": [ "client.DispatchRenderChunkAccessor", diff --git a/fabriquilt/src/main/java/dev/lukebemish/tempest/impl/fabriquilt/ModEntrypoint.java b/fabriquilt/src/main/java/dev/lukebemish/tempest/impl/fabriquilt/ModEntrypoint.java index 83412bd..020a0b0 100644 --- a/fabriquilt/src/main/java/dev/lukebemish/tempest/impl/fabriquilt/ModEntrypoint.java +++ b/fabriquilt/src/main/java/dev/lukebemish/tempest/impl/fabriquilt/ModEntrypoint.java @@ -2,11 +2,16 @@ import dev.lukebemish.tempest.impl.Constants; import dev.lukebemish.tempest.impl.data.AttachedWeatherMapReloadListener; +import dev.lukebemish.tempest.impl.data.WeatherSpawnProvider; import dev.lukebemish.tempest.impl.fabriquilt.client.ClientEntrypoint; import net.fabricmc.api.EnvType; import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; +import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener; +import net.fabricmc.fabric.api.resource.ResourceManagerHelper; import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.PackType; public class ModEntrypoint implements ModInitializer { @Override @@ -19,8 +24,17 @@ public void onInitialize() { ServerLifecycleEvents.SERVER_STARTED.register(AttachedWeatherMapReloadListener::applyToServer); + ResourceManagerHelper.get(PackType.SERVER_DATA).registerReloadListener(new IdentifiableWeatherSpawnProviderListener()); + if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) { ClientEntrypoint.init(); } } + + private static final class IdentifiableWeatherSpawnProviderListener extends WeatherSpawnProvider.ReloadListener implements IdentifiableResourceReloadListener { + @Override + public ResourceLocation getFabricId() { + return Constants.id("spawn_providers"); + } + } } diff --git a/forge/src/main/java/dev/lukebemish/tempest/impl/forge/ModEntrypoint.java b/forge/src/main/java/dev/lukebemish/tempest/impl/forge/ModEntrypoint.java index 4c86024..c0922c0 100644 --- a/forge/src/main/java/dev/lukebemish/tempest/impl/forge/ModEntrypoint.java +++ b/forge/src/main/java/dev/lukebemish/tempest/impl/forge/ModEntrypoint.java @@ -3,6 +3,7 @@ import dev.lukebemish.tempest.impl.Constants; import dev.lukebemish.tempest.impl.Services; import dev.lukebemish.tempest.impl.data.AttachedWeatherMapReloadListener; +import dev.lukebemish.tempest.impl.data.WeatherSpawnProvider; import dev.lukebemish.tempest.impl.forge.client.ClientEntrypoint; import dev.lukebemish.tempest.impl.forge.compat.embeddium.EmbeddiumCompat; import net.minecraft.world.level.chunk.LevelChunk; @@ -58,6 +59,7 @@ private void onChunkSend(ChunkWatchEvent.Watch event) { private void addReloadListener(AddReloadListenerEvent event) { event.addListener(new AttachedWeatherMapReloadListener(event.getRegistryAccess())); + event.addListener(new WeatherSpawnProvider.ReloadListener()); } private void onDatapackSync(OnDatapackSyncEvent event) {