diff --git a/src/generated/resources/data/mia/mia/trial_spawner/example_equipped_zombie.json b/src/generated/resources/data/mia/mia/trial_spawner/example_equipped_zombie.json new file mode 100644 index 0000000..8eba97e --- /dev/null +++ b/src/generated/resources/data/mia/mia/trial_spawner/example_equipped_zombie.json @@ -0,0 +1,65 @@ +{ + "base_mobs": 2, + "entity_tables": [ + { + "attribute_modifiers": [ + { + "amount": 0.5, + "attribute": "minecraft:generic.max_health", + "id": "mia:trial_spawner_health", + "operation": "add_multiplied_total" + } + ], + "effects": [ + { + "amplifier": 0, + "duration": -1, + "effect": "minecraft:fire_resistance" + } + ], + "entity": "minecraft:zombie", + "equipment": { + "chest": "minecraft:iron_chestplate", + "head": "minecraft:iron_helmet", + "mainhand": "minecraft:diamond_sword" + }, + "weight": 10 + }, + { + "attribute_modifiers": [ + { + "amount": 2.0, + "attribute": "minecraft:generic.attack_damage", + "id": "mia:trial_spawner_damage", + "operation": "add_value" + } + ], + "effects": [ + { + "amplifier": 1, + "duration": -1, + "effect": "minecraft:speed" + } + ], + "entity": "minecraft:skeleton", + "equipment": { + "head": "minecraft:chainmail_helmet", + "mainhand": "minecraft:bow" + }, + "weight": 5 + } + ], + "loot_tables": [ + { + "loot_table": "minecraft:chests/trial_chambers/reward_rare", + "weight": 10 + }, + { + "loot_table": "minecraft:chests/trial_chambers/reward_unique", + "weight": 3 + } + ], + "mobs_per_player": 1, + "spawn_per_tick": 1, + "spawn_range": 5 +} \ No newline at end of file diff --git a/src/main/java/com/altnoir/mia/block/entity/renderer/AbyssSpawnerRenderer.java b/src/main/java/com/altnoir/mia/block/entity/renderer/AbyssSpawnerRenderer.java new file mode 100644 index 0000000..2cd5e81 --- /dev/null +++ b/src/main/java/com/altnoir/mia/block/entity/renderer/AbyssSpawnerRenderer.java @@ -0,0 +1,183 @@ +package com.altnoir.mia.block.entity.renderer; + +import com.altnoir.mia.block.entity.AbyssSpawnerBlockEntity; +import com.altnoir.mia.client.render.MiaRenderTypes; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +import com.mojang.math.Axis; +import net.minecraft.client.renderer.LightTexture; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.blockentity.BlockEntityRenderer; +import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider; +import net.minecraft.util.FastColor; +import net.minecraft.util.Mth; +import net.minecraft.util.RandomSource; +import org.joml.Matrix4f; +import org.joml.Vector3f; + +public class AbyssSpawnerRenderer implements BlockEntityRenderer { + private static final int SPHERE_SEGMENTS = 16; + private static final int SPHERE_RINGS = 12; + private static final float BASE_RADIUS = 0.25f; + + private static final int CORE_COLOR = FastColor.ARGB32.color(230, 80, 20, 120); + private static final int OUTER_COLOR = FastColor.ARGB32.color(180, 120, 60, 180); + + public AbyssSpawnerRenderer(BlockEntityRendererProvider.Context context) { + } + + @Override + public void render(AbyssSpawnerBlockEntity blockEntity, float partialTick, PoseStack poseStack, MultiBufferSource bufferSource, int packedLight, int packedOverlay) { + var level = blockEntity.getLevel(); + if (level == null) { + return; + } + + if (!blockEntity.hasValidPattern()) { + return; + } + + var spawner = blockEntity.getAbyssSpawner(); + float time = (level.getGameTime() + partialTick) * 0.05f; + + poseStack.pushPose(); + poseStack.translate(0.5, 0.5, 0.5); + + double spin = Mth.lerp(partialTick, spawner.getOSpin(), spawner.getSpin()); + poseStack.mulPose(Axis.YP.rotationDegrees((float) spin)); + + float bob = Mth.sin(time * 0.5f) * 0.05f; + poseStack.translate(0, bob, 0); + + float pulse = 1.0f + Mth.sin(time * 2.0f) * 0.1f; + float radius = BASE_RADIUS * pulse; + + renderDistortedSphere(poseStack, bufferSource, radius, time, packedLight); + + poseStack.scale(1.3f, 1.3f, 1.3f); + renderGlowingSphere(poseStack, bufferSource, radius * 0.8f, time); + + poseStack.popPose(); + } + + private void renderDistortedSphere(PoseStack poseStack, MultiBufferSource bufferSource, float radius, float time, int packedLight) { + VertexConsumer buffer = bufferSource.getBuffer(MiaRenderTypes.ABYSS_ORB); + Matrix4f matrix = poseStack.last().pose(); + + RandomSource random = RandomSource.create(42L); + + for (int ring = 0; ring < SPHERE_RINGS; ring++) { + float theta1 = (float) Math.PI * ring / SPHERE_RINGS; + float theta2 = (float) Math.PI * (ring + 1) / SPHERE_RINGS; + + for (int seg = 0; seg < SPHERE_SEGMENTS; seg++) { + float phi1 = 2.0f * (float) Math.PI * seg / SPHERE_SEGMENTS; + float phi2 = 2.0f * (float) Math.PI * (seg + 1) / SPHERE_SEGMENTS; + + float distort1 = getDistortion(theta1, phi1, time, random); + float distort2 = getDistortion(theta2, phi1, time, random); + float distort3 = getDistortion(theta2, phi2, time, random); + float distort4 = getDistortion(theta1, phi2, time, random); + + Vector3f p1 = spherePoint(theta1, phi1, radius * distort1); + Vector3f p2 = spherePoint(theta2, phi1, radius * distort2); + Vector3f p3 = spherePoint(theta2, phi2, radius * distort3); + Vector3f p4 = spherePoint(theta1, phi2, radius * distort4); + + float v1 = (float) ring / SPHERE_RINGS; + float v2 = (float) (ring + 1) / SPHERE_RINGS; + + int color1 = lerpColor(CORE_COLOR, OUTER_COLOR, v1); + int color2 = lerpColor(CORE_COLOR, OUTER_COLOR, v2); + + addVertex(buffer, matrix, p1, color1, packedLight); + addVertex(buffer, matrix, p2, color2, packedLight); + addVertex(buffer, matrix, p3, color2, packedLight); + + addVertex(buffer, matrix, p1, color1, packedLight); + addVertex(buffer, matrix, p3, color2, packedLight); + addVertex(buffer, matrix, p4, color1, packedLight); + } + } + } + + private void renderGlowingSphere(PoseStack poseStack, MultiBufferSource bufferSource, float radius, float time) { + VertexConsumer buffer = bufferSource.getBuffer(MiaRenderTypes.ABYSS_ORB_GLOW); + Matrix4f matrix = poseStack.last().pose(); + + float glowPulse = Mth.sin(time * 3.0f) * 0.5f + 0.5f; + int glowAlpha = (int) (80 + glowPulse * 100); + int glowColor = FastColor.ARGB32.color(glowAlpha, 180, 80, 220); + + for (int ring = 0; ring < SPHERE_RINGS / 2; ring++) { + float theta1 = (float) Math.PI * ring / (SPHERE_RINGS / 2); + float theta2 = (float) Math.PI * (ring + 1) / (SPHERE_RINGS / 2); + + for (int seg = 0; seg < SPHERE_SEGMENTS / 2; seg++) { + float phi1 = 2.0f * (float) Math.PI * seg / (SPHERE_SEGMENTS / 2); + float phi2 = 2.0f * (float) Math.PI * (seg + 1) / (SPHERE_SEGMENTS / 2); + + Vector3f p1 = spherePoint(theta1, phi1, radius); + Vector3f p2 = spherePoint(theta2, phi1, radius); + Vector3f p3 = spherePoint(theta2, phi2, radius); + Vector3f p4 = spherePoint(theta1, phi2, radius); + + addVertex(buffer, matrix, p1, glowColor, LightTexture.FULL_BRIGHT); + addVertex(buffer, matrix, p2, glowColor, LightTexture.FULL_BRIGHT); + addVertex(buffer, matrix, p3, glowColor, LightTexture.FULL_BRIGHT); + + addVertex(buffer, matrix, p1, glowColor, LightTexture.FULL_BRIGHT); + addVertex(buffer, matrix, p3, glowColor, LightTexture.FULL_BRIGHT); + addVertex(buffer, matrix, p4, glowColor, LightTexture.FULL_BRIGHT); + } + } + } + + private float getDistortion(float theta, float phi, float time, RandomSource random) { + float noise = Mth.sin(theta * 5 + time) * Mth.cos(phi * 3 + time * 0.7f); + float secondary = Mth.sin(theta * 8 - time * 1.3f) * Mth.sin(phi * 6 + time * 0.5f); + return 1.0f + noise * 0.15f + secondary * 0.08f; + } + + private Vector3f spherePoint(float theta, float phi, float radius) { + float x = radius * Mth.sin(theta) * Mth.cos(phi); + float y = radius * Mth.cos(theta); + float z = radius * Mth.sin(theta) * Mth.sin(phi); + return new Vector3f(x, y, z); + } + + private void addVertex(VertexConsumer buffer, Matrix4f matrix, Vector3f pos, int color, int light) { + buffer.addVertex(matrix, pos.x, pos.y, pos.z) + .setColor(color) + .setLight(light); + } + + private int lerpColor(int color1, int color2, float t) { + int a1 = FastColor.ARGB32.alpha(color1); + int r1 = FastColor.ARGB32.red(color1); + int g1 = FastColor.ARGB32.green(color1); + int b1 = FastColor.ARGB32.blue(color1); + + int a2 = FastColor.ARGB32.alpha(color2); + int r2 = FastColor.ARGB32.red(color2); + int g2 = FastColor.ARGB32.green(color2); + int b2 = FastColor.ARGB32.blue(color2); + + int a = (int) Mth.lerp(t, a1, a2); + int r = (int) Mth.lerp(t, r1, r2); + int g = (int) Mth.lerp(t, g1, g2); + int b = (int) Mth.lerp(t, b1, b2); + + return FastColor.ARGB32.color(a, r, g, b); + } + + @Override + public boolean shouldRenderOffScreen(AbyssSpawnerBlockEntity blockEntity) { + return true; + } + + @Override + public int getViewDistance() { + return 64; + } +} diff --git a/src/main/java/com/altnoir/mia/client/event/RegisterEntityRendererEvent.java b/src/main/java/com/altnoir/mia/client/event/RegisterEntityRendererEvent.java index fffdb71..9e1450f 100644 --- a/src/main/java/com/altnoir/mia/client/event/RegisterEntityRendererEvent.java +++ b/src/main/java/com/altnoir/mia/client/event/RegisterEntityRendererEvent.java @@ -1,5 +1,6 @@ package com.altnoir.mia.client.event; +import com.altnoir.mia.block.entity.renderer.AbyssSpawnerRenderer; import com.altnoir.mia.block.entity.renderer.EndlessCupBlockRenderer; import com.altnoir.mia.block.entity.renderer.PedestalBlockRenderer; import com.altnoir.mia.block.entity.renderer.SunStoneBlockRenderer; @@ -14,5 +15,6 @@ public static void registerEntityRenderers(EntityRenderersEvent.RegisterRenderer event.registerBlockEntityRenderer(MiaBlockEntities.PEDESTAL.get(), PedestalBlockRenderer::new); event.registerBlockEntityRenderer(MiaBlockEntities.ENDLESS_CUP_BLOCK_ENTITY.get(), EndlessCupBlockRenderer::new); event.registerBlockEntityRenderer(MiaBlockEntities.SUN_STONE_BLOCK_ENTITY.get(), SunStoneBlockRenderer::new); + event.registerBlockEntityRenderer(MiaBlockEntities.ABYSS_SPAWNER.get(), AbyssSpawnerRenderer::new); } } diff --git a/src/main/java/com/altnoir/mia/client/render/MiaRenderTypes.java b/src/main/java/com/altnoir/mia/client/render/MiaRenderTypes.java new file mode 100644 index 0000000..976f125 --- /dev/null +++ b/src/main/java/com/altnoir/mia/client/render/MiaRenderTypes.java @@ -0,0 +1,44 @@ +package com.altnoir.mia.client.render; + +import com.mojang.blaze3d.vertex.DefaultVertexFormat; +import com.mojang.blaze3d.vertex.VertexFormat; +import net.minecraft.client.renderer.RenderType; + +public class MiaRenderTypes extends RenderType { + + public MiaRenderTypes(String name, VertexFormat format, VertexFormat.Mode mode, int bufferSize, boolean affectsCrumbling, boolean sortOnUpload, Runnable setupState, Runnable clearState) { + super(name, format, mode, bufferSize, affectsCrumbling, sortOnUpload, setupState, clearState); + } + + public static final RenderType ABYSS_ORB = create( + "mia_abyss_orb", + DefaultVertexFormat.POSITION_COLOR_LIGHTMAP, + VertexFormat.Mode.TRIANGLES, + 1536, + false, + true, + CompositeState.builder() + .setShaderState(POSITION_COLOR_LIGHTMAP_SHADER) + .setTransparencyState(TRANSLUCENT_TRANSPARENCY) + .setWriteMaskState(COLOR_DEPTH_WRITE) + .setCullState(NO_CULL) + .setDepthTestState(LEQUAL_DEPTH_TEST) + .createCompositeState(false) + ); + + public static final RenderType ABYSS_ORB_GLOW = create( + "mia_abyss_orb_glow", + DefaultVertexFormat.POSITION_COLOR_LIGHTMAP, + VertexFormat.Mode.TRIANGLES, + 1536, + false, + true, + CompositeState.builder() + .setShaderState(POSITION_COLOR_LIGHTMAP_SHADER) + .setTransparencyState(ADDITIVE_TRANSPARENCY) + .setWriteMaskState(COLOR_WRITE) + .setCullState(NO_CULL) + .setDepthTestState(LEQUAL_DEPTH_TEST) + .createCompositeState(false) + ); +} diff --git a/src/main/java/com/altnoir/mia/core/spawner/AbyssTrialSpawner.java b/src/main/java/com/altnoir/mia/core/spawner/AbyssTrialSpawner.java index f89e7ff..117ab46 100644 --- a/src/main/java/com/altnoir/mia/core/spawner/AbyssTrialSpawner.java +++ b/src/main/java/com/altnoir/mia/core/spawner/AbyssTrialSpawner.java @@ -243,7 +243,8 @@ private boolean trySpawnMob(ServerLevel level, BlockPos pos, AbyssTrialSpawnerPa return false; } - var entityType = selectedEntity.get().getEntityType(); + var entityInstance = selectedEntity.get(); + var entityType = entityInstance.getEntityType(); int range = pattern.spawnRange(); for (int attempts = 0; attempts < 20; attempts++) { @@ -258,6 +259,8 @@ private boolean trySpawnMob(ServerLevel level, BlockPos pos, AbyssTrialSpawnerPa if (entity != null) { if (entity instanceof Mob mob) { mob.finalizeSpawn(level, level.getCurrentDifficultyAt(spawnPos), MobSpawnType.TRIAL_SPAWNER, null); + + applyEntityConfiguration(mob, entityInstance); } level.addFreshEntity(entity); @@ -280,6 +283,32 @@ private boolean trySpawnMob(ServerLevel level, BlockPos pos, AbyssTrialSpawnerPa return false; } + private void applyEntityConfiguration(Mob mob, EntityTableInstance entityInstance) { + if (entityInstance.hasEquipment()) { + for (var entry : entityInstance.getEquipment().entrySet()) { + mob.setItemSlot(entry.getKey(), entry.getValue().copy()); + mob.setDropChance(entry.getKey(), 0.0F); + } + } + + if (entityInstance.hasEffects()) { + for (var effect : entityInstance.getEffects()) { + mob.addEffect(new net.minecraft.world.effect.MobEffectInstance(effect)); + } + } + + if (entityInstance.hasAttributeModifiers()) { + for (var entry : entityInstance.getAttributeModifiers().entrySet()) { + var attributeInstance = mob.getAttribute(entry.getKey()); + if (attributeInstance != null) { + attributeInstance.addPermanentModifier(entry.getValue()); + } + } + // Ensure mob's health matches new max health after modifiers + mob.setHealth(mob.getMaxHealth()); + } + } + private void collectRewardsForPlayer(ServerLevel level, BlockPos pos, Player player, AbyssTrialSpawnerPattern pattern) { var selectedLoot = WeightedRandom.getRandomItem(this.random, pattern.lootTables().unwrap()); if (selectedLoot.isEmpty()) { @@ -426,18 +455,6 @@ public double getOSpin() { return this.oSpin; } - @Nullable - public Entity getOrCreateDisplayEntity(Level level, BlockPos pos) { - if (this.displayEntity == null && hasValidPattern() && level instanceof ServerLevel serverLevel) { - var pattern = getPattern(); - var selected = WeightedRandom.getRandomItem(this.random, pattern.entityTables().unwrap()); - if (selected.isPresent()) { - this.displayEntity = selected.get().getEntityType().create(serverLevel, null, pos, MobSpawnType.TRIAL_SPAWNER, false, false); - } - } - return this.displayEntity; - } - public interface StateAccessor { TrialSpawnerState getState(); void setState(Level level, TrialSpawnerState state); diff --git a/src/main/java/com/altnoir/mia/core/spawner/AbyssTrialSpawnerDataProvider.java b/src/main/java/com/altnoir/mia/core/spawner/AbyssTrialSpawnerDataProvider.java index e7c0717..c219aa6 100644 --- a/src/main/java/com/altnoir/mia/core/spawner/AbyssTrialSpawnerDataProvider.java +++ b/src/main/java/com/altnoir/mia/core/spawner/AbyssTrialSpawnerDataProvider.java @@ -5,6 +5,7 @@ import com.altnoir.mia.core.spawner.records.LootTableInstance; import com.google.gson.JsonArray; import com.google.gson.JsonObject; +import net.minecraft.core.Holder; import net.minecraft.core.HolderLookup; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.registries.Registries; @@ -14,12 +15,22 @@ import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.util.random.WeightedRandomList; +import net.minecraft.world.effect.MobEffect; +import net.minecraft.world.effect.MobEffectInstance; import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.ai.attributes.Attribute; +import net.minecraft.world.entity.ai.attributes.AttributeModifier; +import net.minecraft.world.entity.ai.attributes.Attributes; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.storage.loot.LootTable; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.CompletableFuture; public abstract class AbyssTrialSpawnerDataProvider implements DataProvider { @@ -56,6 +67,107 @@ protected EntityTableInstance entity(EntityType entityType, int weight) { return new EntityTableInstance(entityType, weight); } + protected EntityTableBuilder entityBuilder(EntityType entityType, int weight) { + return new EntityTableBuilder(entityType, weight); + } + + public static class EntityTableBuilder { + private final EntityType entityType; + private final int weight; + private final Map equipment = new HashMap<>(); + private final List effects = new ArrayList<>(); + private final Map, AttributeModifier> attributeModifiers = new HashMap<>(); + + public EntityTableBuilder(EntityType entityType, int weight) { + this.entityType = entityType; + this.weight = weight; + } + + public EntityTableBuilder equipment(EquipmentSlot slot, Item item) { + this.equipment.put(slot, new ItemStack(item)); + return this; + } + + public EntityTableBuilder equipment(EquipmentSlot slot, ItemStack stack) { + this.equipment.put(slot, stack); + return this; + } + + public EntityTableBuilder mainHand(Item item) { + return equipment(EquipmentSlot.MAINHAND, item); + } + + public EntityTableBuilder offHand(Item item) { + return equipment(EquipmentSlot.OFFHAND, item); + } + + public EntityTableBuilder head(Item item) { + return equipment(EquipmentSlot.HEAD, item); + } + + public EntityTableBuilder chest(Item item) { + return equipment(EquipmentSlot.CHEST, item); + } + + public EntityTableBuilder legs(Item item) { + return equipment(EquipmentSlot.LEGS, item); + } + + public EntityTableBuilder feet(Item item) { + return equipment(EquipmentSlot.FEET, item); + } + + public EntityTableBuilder effect(Holder effect, int duration, int amplifier) { + this.effects.add(new MobEffectInstance(effect, duration, amplifier)); + return this; + } + + public EntityTableBuilder effect(Holder effect, int duration) { + return effect(effect, duration, 0); + } + + public EntityTableBuilder permanentEffect(Holder effect, int amplifier) { + return effect(effect, -1, amplifier); + } + + public EntityTableBuilder permanentEffect(Holder effect) { + return permanentEffect(effect, 0); + } + + public EntityTableBuilder attribute(Holder attribute, ResourceLocation id, double amount, AttributeModifier.Operation operation) { + this.attributeModifiers.put(attribute, new AttributeModifier(id, amount, operation)); + return this; + } + + public EntityTableBuilder addHealth(String modId, double amount) { + return attribute(Attributes.MAX_HEALTH, ResourceLocation.fromNamespaceAndPath(modId, "trial_spawner_health"), amount, AttributeModifier.Operation.ADD_VALUE); + } + + public EntityTableBuilder multiplyHealth(String modId, double multiplier) { + return attribute(Attributes.MAX_HEALTH, ResourceLocation.fromNamespaceAndPath(modId, "trial_spawner_health"), multiplier - 1.0, AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL); + } + + public EntityTableBuilder addDamage(String modId, double amount) { + return attribute(Attributes.ATTACK_DAMAGE, ResourceLocation.fromNamespaceAndPath(modId, "trial_spawner_damage"), amount, AttributeModifier.Operation.ADD_VALUE); + } + + public EntityTableBuilder multiplyDamage(String modId, double multiplier) { + return attribute(Attributes.ATTACK_DAMAGE, ResourceLocation.fromNamespaceAndPath(modId, "trial_spawner_damage"), multiplier - 1.0, AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL); + } + + public EntityTableBuilder addSpeed(String modId, double amount) { + return attribute(Attributes.MOVEMENT_SPEED, ResourceLocation.fromNamespaceAndPath(modId, "trial_spawner_speed"), amount, AttributeModifier.Operation.ADD_VALUE); + } + + public EntityTableBuilder multiplySpeed(String modId, double multiplier) { + return attribute(Attributes.MOVEMENT_SPEED, ResourceLocation.fromNamespaceAndPath(modId, "trial_spawner_speed"), multiplier - 1.0, AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL); + } + + public EntityTableInstance build() { + return new EntityTableInstance(entityType, weight, equipment, effects, attributeModifiers); + } + } + protected LootTableInstance loot(ResourceKey lootTableKey, int weight) { return new LootTableInstance(lootTableKey, weight); } @@ -111,6 +223,46 @@ private void validate(HolderLookup.Provider registries, ResourceLocation id, Aby var entityId = BuiltInRegistries.ENTITY_TYPE.getKey(entityEntry.getEntityType()); entityJson.addProperty("entity", entityId.toString()); entityJson.addProperty("weight", entityEntry.getWeight().asInt()); + + if (entityEntry.hasEquipment()) { + var equipmentJson = new JsonObject(); + for (var equipEntry : entityEntry.getEquipment().entrySet()) { + var itemId = BuiltInRegistries.ITEM.getKey(equipEntry.getValue().getItem()); + equipmentJson.addProperty(equipEntry.getKey().getName(), itemId.toString()); + } + entityJson.add("equipment", equipmentJson); + } + + if (entityEntry.hasEffects()) { + var effectsArray = new JsonArray(); + for (var effect : entityEntry.getEffects()) { + var effectJson = new JsonObject(); + var effectId = BuiltInRegistries.MOB_EFFECT.getKey(effect.getEffect().value()); + effectJson.addProperty("effect", effectId.toString()); + effectJson.addProperty("duration", effect.getDuration()); + effectJson.addProperty("amplifier", effect.getAmplifier()); + if (effect.isAmbient()) effectJson.addProperty("ambient", true); + if (!effect.isVisible()) effectJson.addProperty("visible", false); + if (!effect.showIcon()) effectJson.addProperty("show_icon", false); + effectsArray.add(effectJson); + } + entityJson.add("effects", effectsArray); + } + + if (entityEntry.hasAttributeModifiers()) { + var modifiersArray = new JsonArray(); + for (var modEntry : entityEntry.getAttributeModifiers().entrySet()) { + var modJson = new JsonObject(); + var attrId = BuiltInRegistries.ATTRIBUTE.getKey(modEntry.getKey().value()); + modJson.addProperty("attribute", attrId.toString()); + modJson.addProperty("id", modEntry.getValue().id().toString()); + modJson.addProperty("amount", modEntry.getValue().amount()); + modJson.addProperty("operation", modEntry.getValue().operation().name().toLowerCase()); + modifiersArray.add(modJson); + } + entityJson.add("attribute_modifiers", modifiersArray); + } + entityTablesArray.add(entityJson); } root.add("entity_tables", entityTablesArray); diff --git a/src/main/java/com/altnoir/mia/core/spawner/AbyssTrialSpawnerManager.java b/src/main/java/com/altnoir/mia/core/spawner/AbyssTrialSpawnerManager.java index 5f5d67f..7875607 100644 --- a/src/main/java/com/altnoir/mia/core/spawner/AbyssTrialSpawnerManager.java +++ b/src/main/java/com/altnoir/mia/core/spawner/AbyssTrialSpawnerManager.java @@ -7,8 +7,10 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import com.mojang.logging.LogUtils; +import net.minecraft.core.Holder; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.registries.Registries; import net.minecraft.resources.ResourceKey; @@ -19,7 +21,11 @@ import net.minecraft.util.GsonHelper; import net.minecraft.util.profiling.ProfilerFiller; import net.minecraft.util.random.WeightedRandomList; -import net.minecraft.world.level.storage.loot.LootTable; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.ai.attributes.Attribute; +import net.minecraft.world.entity.ai.attributes.AttributeModifier; +import net.minecraft.world.item.ItemStack; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; @@ -107,7 +113,12 @@ protected void apply(Map resourceLocationJsonElem var weight = entityJson.get("weight").getAsInt(); var entityType = BuiltInRegistries.ENTITY_TYPE.get(entityId); - entityTablesList.add(new EntityTableInstance(entityType, weight)); + + Map equipment = parseEquipment(entityJson); + List effects = parseEffects(entityJson); + Map, AttributeModifier> attributeModifiers = parseAttributeModifiers(entityJson); + + entityTablesList.add(new EntityTableInstance(entityType, weight, equipment, effects, attributeModifiers)); } } @@ -148,4 +159,67 @@ public Set getPatternIds() { public Map getPatterns() { return Collections.unmodifiableMap(spawnerPatterns); } + + private Map parseEquipment(JsonObject entityJson) { + var equipment = new HashMap(); + if (entityJson.has("equipment")) { + var equipmentJson = entityJson.getAsJsonObject("equipment"); + for (var slotEntry : equipmentJson.entrySet()) { + var slotName = slotEntry.getKey(); + var itemId = slotEntry.getValue().getAsString(); + + var slot = EquipmentSlot.byName(slotName); + var item = BuiltInRegistries.ITEM.get(ResourceLocation.parse(itemId)); + equipment.put(slot, new ItemStack(item)); + } + } + return equipment; + } + + private List parseEffects(JsonObject entityJson) { + var effects = new ArrayList(); + if (entityJson.has("effects")) { + var effectsArray = entityJson.getAsJsonArray("effects"); + for (var effectElement : effectsArray) { + var effectJson = effectElement.getAsJsonObject(); + var effectId = ResourceLocation.parse(effectJson.get("effect").getAsString()); + var duration = effectJson.has("duration") ? effectJson.get("duration").getAsInt() : -1; + var amplifier = effectJson.has("amplifier") ? effectJson.get("amplifier").getAsInt() : 0; + var ambient = effectJson.has("ambient") && effectJson.get("ambient").getAsBoolean(); + var visible = !effectJson.has("visible") || effectJson.get("visible").getAsBoolean(); + var showIcon = !effectJson.has("show_icon") || effectJson.get("show_icon").getAsBoolean(); + + var effectHolder = BuiltInRegistries.MOB_EFFECT.getHolder(effectId); + effectHolder.ifPresent(effect -> effects.add(new MobEffectInstance(effect, duration, amplifier, ambient, visible, showIcon))); + } + } + return effects; + } + + private Map, AttributeModifier> parseAttributeModifiers(JsonObject entityJson) { + var modifiers = new HashMap, AttributeModifier>(); + if (entityJson.has("attribute_modifiers")) { + var modifiersArray = entityJson.getAsJsonArray("attribute_modifiers"); + for (var modifierElement : modifiersArray) { + var modifierJson = modifierElement.getAsJsonObject(); + var attributeId = ResourceLocation.parse(modifierJson.get("attribute").getAsString()); + var modifierId = ResourceLocation.parse(modifierJson.get("id").getAsString()); + var amount = modifierJson.get("amount").getAsDouble(); + var operationStr = modifierJson.has("operation") ? modifierJson.get("operation").getAsString() : "add_value"; + + var operation = switch (operationStr.toLowerCase()) { + case "add_multiplied_base" -> AttributeModifier.Operation.ADD_MULTIPLIED_BASE; + case "add_multiplied_total" -> AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL; + default -> AttributeModifier.Operation.ADD_VALUE; + }; + + var attributeHolder = BuiltInRegistries.ATTRIBUTE.getHolder(attributeId); + attributeHolder.ifPresent(attribute -> { + var modifier = new AttributeModifier(modifierId, amount, operation); + modifiers.put(attribute, modifier); + }); + } + } + return modifiers; + } } diff --git a/src/main/java/com/altnoir/mia/core/spawner/records/EntityTableInstance.java b/src/main/java/com/altnoir/mia/core/spawner/records/EntityTableInstance.java index 199ad27..12df820 100644 --- a/src/main/java/com/altnoir/mia/core/spawner/records/EntityTableInstance.java +++ b/src/main/java/com/altnoir/mia/core/spawner/records/EntityTableInstance.java @@ -1,17 +1,68 @@ package com.altnoir.mia.core.spawner.records; +import net.minecraft.core.Holder; import net.minecraft.util.random.WeightedEntry; +import net.minecraft.world.effect.MobEffectInstance; import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.ai.attributes.Attribute; +import net.minecraft.world.entity.ai.attributes.AttributeModifier; +import net.minecraft.world.item.ItemStack; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; public class EntityTableInstance extends WeightedEntry.IntrusiveBase { private final EntityType entityType; + private final Map equipment; + private final List effects; + private final Map, AttributeModifier> attributeModifiers; public EntityTableInstance(EntityType entityType, int weight) { + this(entityType, weight, new HashMap<>(), new ArrayList<>(), new HashMap<>()); + } + + public EntityTableInstance( + EntityType entityType, + int weight, + Map equipment, + List effects, + Map, AttributeModifier> attributeModifiers + ) { super(weight); this.entityType = entityType; + this.equipment = equipment; + this.effects = effects; + this.attributeModifiers = attributeModifiers; } public EntityType getEntityType() { return entityType; } + + public Map getEquipment() { + return equipment; + } + + public List getEffects() { + return effects; + } + + public Map, AttributeModifier> getAttributeModifiers() { + return attributeModifiers; + } + + public boolean hasEquipment() { + return !equipment.isEmpty(); + } + + public boolean hasEffects() { + return !effects.isEmpty(); + } + + public boolean hasAttributeModifiers() { + return !attributeModifiers.isEmpty(); + } } diff --git a/src/main/java/com/altnoir/mia/datagen/MiaTrialSpawnerProvider.java b/src/main/java/com/altnoir/mia/datagen/MiaTrialSpawnerProvider.java index 48a5e96..be73e34 100644 --- a/src/main/java/com/altnoir/mia/datagen/MiaTrialSpawnerProvider.java +++ b/src/main/java/com/altnoir/mia/datagen/MiaTrialSpawnerProvider.java @@ -5,7 +5,9 @@ import net.minecraft.core.HolderLookup; import net.minecraft.data.PackOutput; import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.effect.MobEffects; import net.minecraft.world.entity.EntityType; +import net.minecraft.world.item.Items; import net.minecraft.world.level.storage.loot.BuiltInLootTables; import java.util.List; @@ -90,5 +92,34 @@ protected void addSpawners() { 8 ) ); + + add( + ResourceLocation.fromNamespaceAndPath(MIA.MOD_ID, "example_equipped_zombie"), + createPattern( + List.of( + entityBuilder(EntityType.ZOMBIE, 10) + .mainHand(Items.DIAMOND_SWORD) + .head(Items.IRON_HELMET) + .chest(Items.IRON_CHESTPLATE) + .permanentEffect(MobEffects.FIRE_RESISTANCE) + .multiplyHealth(MIA.MOD_ID, 1.5) + .build(), + entityBuilder(EntityType.SKELETON, 5) + .mainHand(Items.BOW) + .head(Items.CHAINMAIL_HELMET) + .effect(MobEffects.MOVEMENT_SPEED, -1, 1) + .addDamage(MIA.MOD_ID, 2.0) + .build() + ), + List.of( + loot(BuiltInLootTables.TRIAL_CHAMBERS_REWARD_RARE, 10), + loot(BuiltInLootTables.TRIAL_CHAMBERS_REWARD_UNIQUE, 3) + ), + 2, + 1, + 1, + 5 + ) + ); } } diff --git a/src/main/resources/assets/mia/shaders/core/abyss_orb.fsh b/src/main/resources/assets/mia/shaders/core/abyss_orb.fsh new file mode 100644 index 0000000..5f8a52b --- /dev/null +++ b/src/main/resources/assets/mia/shaders/core/abyss_orb.fsh @@ -0,0 +1,70 @@ +#version 150 + +in vec4 vertexColor; +in vec2 texCoord; +in float time; + +uniform vec4 ColorModulator; + +out vec4 fragColor; + +float noise(vec2 p) { + return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453); +} + +float smoothNoise(vec2 p) { + vec2 i = floor(p); + vec2 f = fract(p); + f = f * f * (3.0 - 2.0 * f); + + float a = noise(i); + float b = noise(i + vec2(1.0, 0.0)); + float c = noise(i + vec2(0.0, 1.0)); + float d = noise(i + vec2(1.0, 1.0)); + + return mix(mix(a, b, f.x), mix(c, d, f.x), f.y); +} + +float fbm(vec2 p) { + float value = 0.0; + float amplitude = 0.5; + for (int i = 0; i < 4; i++) { + value += amplitude * smoothNoise(p); + p *= 2.0; + amplitude *= 0.5; + } + return value; +} + +void main() { + vec2 uv = texCoord; + + float t = time * 0.001; + + vec2 distortedUV = uv + vec2( + fbm(uv * 3.0 + t), + fbm(uv * 3.0 - t + 100.0) + ) * 0.1; + + float n = fbm(distortedUV * 5.0 + t * 0.5); + + vec3 deepPurple = vec3(0.15, 0.05, 0.25); + vec3 darkBlue = vec3(0.05, 0.1, 0.2); + vec3 abyssBlack = vec3(0.02, 0.02, 0.05); + vec3 highlight = vec3(0.4, 0.2, 0.6); + + vec3 color = mix(abyssBlack, deepPurple, n); + color = mix(color, darkBlue, smoothNoise(distortedUV * 8.0 + t)); + + float pulse = sin(t * 2.0) * 0.5 + 0.5; + float edge = smoothstep(0.3, 0.7, n + pulse * 0.2); + color += highlight * edge * 0.3; + + float glow = smoothstep(0.0, 0.5, n) * pulse * 0.2; + color += vec3(0.3, 0.1, 0.5) * glow; + + float alpha = vertexColor.a * ColorModulator.a * 0.85; + alpha += pulse * 0.1; + + fragColor = vec4(color * vertexColor.rgb * ColorModulator.rgb, alpha); +} diff --git a/src/main/resources/assets/mia/shaders/core/abyss_orb.json b/src/main/resources/assets/mia/shaders/core/abyss_orb.json new file mode 100644 index 0000000..9007f2f --- /dev/null +++ b/src/main/resources/assets/mia/shaders/core/abyss_orb.json @@ -0,0 +1,21 @@ +{ + "blend": { + "func": "add", + "srcrgb": "srcalpha", + "dstrgb": "1-srcalpha" + }, + "vertex": "mia:abyss_orb", + "fragment": "mia:abyss_orb", + "attributes": [ + "Position", + "Color", + "UV0" + ], + "samplers": [], + "uniforms": [ + { "name": "ModelViewMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, + { "name": "ProjMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, + { "name": "ColorModulator", "type": "float", "count": 4, "values": [ 1.0, 1.0, 1.0, 1.0 ] }, + { "name": "GameTime", "type": "float", "count": 1, "values": [ 0.0 ] } + ] +} diff --git a/src/main/resources/assets/mia/shaders/core/abyss_orb.vsh b/src/main/resources/assets/mia/shaders/core/abyss_orb.vsh new file mode 100644 index 0000000..277da6e --- /dev/null +++ b/src/main/resources/assets/mia/shaders/core/abyss_orb.vsh @@ -0,0 +1,26 @@ +#version 150 + +in vec3 Position; +in vec4 Color; +in vec2 UV0; + +uniform mat4 ModelViewMat; +uniform mat4 ProjMat; +uniform float GameTime; + +out vec4 vertexColor; +out vec2 texCoord; +out float time; + +void main() { + time = GameTime * 1200.0; + + vec3 pos = Position; + float wave = sin(pos.y * 3.0 + time * 0.5) * 0.05; + pos.x += wave; + pos.z += wave * 0.7; + + gl_Position = ProjMat * ModelViewMat * vec4(pos, 1.0); + vertexColor = Color; + texCoord = UV0; +}