From 5011c35e157bc3f6644a55e1affb117030ed2b66 Mon Sep 17 00:00:00 2001 From: Sollace Date: Sat, 21 Oct 2023 23:16:54 +0100 Subject: [PATCH] Implement proper spell rendering --- .../magic/spell/trait/SpellTraits.java | 2 +- .../unicopia/client/URenderers.java | 3 + .../unicopia/client/UnicopiaClient.java | 2 + .../unicopia/client/minelittlepony/Main.java | 37 +++-- .../minelittlepony/SpellEffectGear.java | 63 ++++++++ .../render/AccessoryFeatureRenderer.java | 6 + .../entity/CastSpellEntityRenderer.java | 13 ++ .../client/render/spell/AllSpells.java | 8 + .../spell/SpellEffectsRenderDispatcher.java | 138 ++++++++++++++++++ .../client/render/spell/SpellRenderer.java | 11 ++ .../render/spell/SpellRendererFactory.java | 11 ++ 11 files changed, 283 insertions(+), 11 deletions(-) create mode 100644 src/main/java/com/minelittlepony/unicopia/client/minelittlepony/SpellEffectGear.java create mode 100644 src/main/java/com/minelittlepony/unicopia/client/render/spell/AllSpells.java create mode 100644 src/main/java/com/minelittlepony/unicopia/client/render/spell/SpellEffectsRenderDispatcher.java create mode 100644 src/main/java/com/minelittlepony/unicopia/client/render/spell/SpellRenderer.java create mode 100644 src/main/java/com/minelittlepony/unicopia/client/render/spell/SpellRendererFactory.java diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/trait/SpellTraits.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/trait/SpellTraits.java index ae16b248..2e38df13 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/trait/SpellTraits.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/trait/SpellTraits.java @@ -165,7 +165,7 @@ public final class SpellTraits implements Iterable> { @Override public String toString() { - return super.toString() + "[" + traits.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining(",")) + "]"; + return "SpellTraits[" + traits.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining(",")) + "]"; } @Override diff --git a/src/main/java/com/minelittlepony/unicopia/client/URenderers.java b/src/main/java/com/minelittlepony/unicopia/client/URenderers.java index c934192b..149c7cc5 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/URenderers.java +++ b/src/main/java/com/minelittlepony/unicopia/client/URenderers.java @@ -17,6 +17,7 @@ import com.minelittlepony.unicopia.client.particle.ShockwaveParticle; import com.minelittlepony.unicopia.client.particle.SphereParticle; import com.minelittlepony.unicopia.client.render.*; import com.minelittlepony.unicopia.client.render.entity.*; +import com.minelittlepony.unicopia.client.render.spell.SpellRendererFactory; import com.minelittlepony.unicopia.entity.mob.UEntities; import com.minelittlepony.unicopia.item.ChameleonItem; import com.minelittlepony.unicopia.item.EnchantableItem; @@ -182,6 +183,8 @@ public interface URenderers { BlockRenderLayerMap.INSTANCE.putFluids(RenderLayer.getTranslucent(), Fluids.LAVA, Fluids.FLOWING_LAVA); TerraformBoatClientHelper.registerModelLayers(Unicopia.id("palm"), false); + + SpellRendererFactory.bootstrap(); } static PendingParticleFactory createFactory(ParticleSupplier supplier) { diff --git a/src/main/java/com/minelittlepony/unicopia/client/UnicopiaClient.java b/src/main/java/com/minelittlepony/unicopia/client/UnicopiaClient.java index 1f0df3c8..fc89eaa0 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/UnicopiaClient.java +++ b/src/main/java/com/minelittlepony/unicopia/client/UnicopiaClient.java @@ -15,6 +15,7 @@ import com.minelittlepony.unicopia.client.gui.UHud; import com.minelittlepony.unicopia.client.gui.spellbook.SpellbookScreen; import com.minelittlepony.unicopia.client.minelittlepony.MineLPDelegate; import com.minelittlepony.unicopia.client.render.shader.ViewportShader; +import com.minelittlepony.unicopia.client.render.spell.SpellEffectsRenderDispatcher; import com.minelittlepony.unicopia.container.*; import com.minelittlepony.unicopia.entity.player.PlayerCamera; import com.minelittlepony.unicopia.entity.player.Pony; @@ -107,6 +108,7 @@ public class UnicopiaClient implements ClientModInitializer { ItemTooltipCallback.EVENT.register(new ModifierTooltipRenderer()); ResourceManagerHelper.get(ResourceType.CLIENT_RESOURCES).registerReloadListener(ViewportShader.INSTANCE); + ResourceManagerHelper.get(ResourceType.CLIENT_RESOURCES).registerReloadListener(SpellEffectsRenderDispatcher.INSTANCE); Unicopia.SIDE = () -> Optional.ofNullable(MinecraftClient.getInstance().player).map(Pony::of); } diff --git a/src/main/java/com/minelittlepony/unicopia/client/minelittlepony/Main.java b/src/main/java/com/minelittlepony/unicopia/client/minelittlepony/Main.java index ca4eb3fc..1639d96b 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/minelittlepony/Main.java +++ b/src/main/java/com/minelittlepony/unicopia/client/minelittlepony/Main.java @@ -1,5 +1,11 @@ package com.minelittlepony.unicopia.client.minelittlepony; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; + import com.minelittlepony.api.model.*; import com.minelittlepony.api.model.fabric.PonyModelPrepareCallback; import com.minelittlepony.api.model.gear.IGear; @@ -13,10 +19,21 @@ import com.minelittlepony.unicopia.util.AnimationUtil; import net.fabricmc.api.ClientModInitializer; import net.minecraft.entity.Entity; import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.Util; import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3d; public class Main extends MineLPDelegate implements ClientModInitializer { + private static final Map PONY_RACE_MAPPING = new HashMap<>(); + private static final Function LOOKUP_CACHE = Util.memoize(race -> { + return Optional.ofNullable(PONY_RACE_MAPPING.get(race)) + .or(() -> Race.REGISTRY.getOrEmpty(Unicopia.id(race.name().toLowerCase(Locale.ROOT)))) + .orElse(Race.UNSET); + }); + + public static void registerRaceMapping(com.minelittlepony.api.pony.meta.Race minelpRace, Race unicopiaRace) { + PONY_RACE_MAPPING.put(minelpRace, unicopiaRace); + } private boolean hookErroring; @@ -33,6 +50,14 @@ public class Main extends MineLPDelegate implements ClientModInitializer { IGear.register(BodyPartGear::unicornHorn); IGear.register(AmuletGear::new); IGear.register(GlassesGear::new); + IGear.register(SpellEffectGear::new); + + registerRaceMapping(com.minelittlepony.api.pony.meta.Race.CHANGEDLING, Race.CHANGELING); + registerRaceMapping(com.minelittlepony.api.pony.meta.Race.ZEBRA, Race.EARTH); + registerRaceMapping(com.minelittlepony.api.pony.meta.Race.GRYPHON, Race.PEGASUS); + registerRaceMapping(com.minelittlepony.api.pony.meta.Race.HIPPOGRIFF, Race.PEGASUS); + registerRaceMapping(com.minelittlepony.api.pony.meta.Race.BATPONY, Race.BAT); + registerRaceMapping(com.minelittlepony.api.pony.meta.Race.SEAPONY, Race.UNICORN); } private void onPonyModelPrepared(Entity entity, IModel model, ModelAttributes.Mode mode) { @@ -75,7 +100,7 @@ public class Main extends MineLPDelegate implements ClientModInitializer { @Override public Race getRace(Entity entity) { - return IPony.getManager().getPony(entity).map(IPony::race).map(Main::toUnicopiaRace).orElse(Race.HUMAN); + return IPony.getManager().getPony(entity).map(IPony::race).map(Main::toUnicopiaRace).orElse(Race.UNSET); } @Override @@ -84,14 +109,6 @@ public class Main extends MineLPDelegate implements ClientModInitializer { } private static Race toUnicopiaRace(com.minelittlepony.api.pony.meta.Race race) { - return switch (race) { - case ALICORN -> Race.ALICORN; - case CHANGELING, CHANGEDLING -> Race.CHANGELING; - case ZEBRA, EARTH -> Race.EARTH; - case GRYPHON, HIPPOGRIFF, PEGASUS -> Race.PEGASUS; - case BATPONY -> Race.BAT; - case SEAPONY, UNICORN, KIRIN -> Race.UNICORN; - default -> Race.HUMAN; - }; + return LOOKUP_CACHE.apply(race); } } diff --git a/src/main/java/com/minelittlepony/unicopia/client/minelittlepony/SpellEffectGear.java b/src/main/java/com/minelittlepony/unicopia/client/minelittlepony/SpellEffectGear.java new file mode 100644 index 00000000..65dc6ed3 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/client/minelittlepony/SpellEffectGear.java @@ -0,0 +1,63 @@ +package com.minelittlepony.unicopia.client.minelittlepony; + +import java.util.UUID; + +import com.minelittlepony.api.model.BodyPart; +import com.minelittlepony.api.model.IModel; +import com.minelittlepony.api.model.gear.IGear; +import com.minelittlepony.client.model.IPonyModel; +import com.minelittlepony.unicopia.ability.magic.Caster; +import com.minelittlepony.unicopia.client.render.BraceletFeatureRenderer; +import com.minelittlepony.unicopia.client.render.spell.SpellEffectsRenderDispatcher; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.render.VertexConsumer; +import net.minecraft.client.render.entity.model.EntityModel; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.entity.Entity; +import net.minecraft.util.Identifier; + +class SpellEffectGear implements IGear { + private Caster caster; + private float limbAngle; + private float limbDistance; + private float animationProgress; + + @Override + public boolean canRender(IModel model, Entity entity) { + return Caster.of(entity).isPresent(); + } + + @Override + public BodyPart getGearLocation() { + return BodyPart.LEGS; + } + + @Override + public Identifier getTexture(T entity, Context context) { + return BraceletFeatureRenderer.TEXTURE; + } + + @Override + public & IPonyModel> void transform(M model, MatrixStack matrices) { + } + + @Override + public void pose(IModel model, Entity entity, boolean rainboom, UUID interpolatorId, float move, float swing, float bodySwing, float ticks) { + caster = Caster.of(entity).orElse(null); + limbAngle = move; + limbDistance = swing; + animationProgress = entity.age + ticks; + } + + @Override + public void render(MatrixStack stack, VertexConsumer consumer, int light, int overlay, float red, float green, float blue, float alpha, UUID interpolatorId) { + SpellEffectsRenderDispatcher.INSTANCE.render( + stack, + MinecraftClient.getInstance().getBufferBuilders().getEntityVertexConsumers(), + light, caster, + limbAngle, limbDistance, + MinecraftClient.getInstance().getTickDelta(), animationProgress, 0, 0 + ); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/AccessoryFeatureRenderer.java b/src/main/java/com/minelittlepony/unicopia/client/render/AccessoryFeatureRenderer.java index 88c62afa..94a0a77e 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/AccessoryFeatureRenderer.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/AccessoryFeatureRenderer.java @@ -4,7 +4,9 @@ import java.util.*; import org.jetbrains.annotations.Nullable; +import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.client.FirstPersonRendererOverrides.ArmRenderer; +import com.minelittlepony.unicopia.client.render.spell.SpellEffectsRenderDispatcher; import net.minecraft.client.MinecraftClient; import net.minecraft.client.model.ModelPart; @@ -37,6 +39,10 @@ public class AccessoryFeatureRenderer< @Override public void render(MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, T entity, float limbAngle, float limbDistance, float tickDelta, float animationProgress, float headYaw, float headPitch) { features.forEach(feature -> feature.render(matrices, vertexConsumers, light, entity, limbAngle, limbDistance, tickDelta, animationProgress, headYaw, headPitch)); + + Caster.of(entity).ifPresent(caster -> { + SpellEffectsRenderDispatcher.INSTANCE.render(matrices, vertexConsumers, light, caster, limbAngle, limbDistance, tickDelta, animationProgress, headYaw, headPitch); + }); } public void renderArm(MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, T entity, ModelPart arm, Arm side) { diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/entity/CastSpellEntityRenderer.java b/src/main/java/com/minelittlepony/unicopia/client/render/entity/CastSpellEntityRenderer.java index aa5448ae..7b4fae4e 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/entity/CastSpellEntityRenderer.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/entity/CastSpellEntityRenderer.java @@ -1,13 +1,17 @@ package com.minelittlepony.unicopia.client.render.entity; +import com.minelittlepony.unicopia.client.render.spell.SpellEffectsRenderDispatcher; import com.minelittlepony.unicopia.entity.mob.CastSpellEntity; +import net.minecraft.client.render.VertexConsumerProvider; import net.minecraft.client.render.entity.EntityRenderer; import net.minecraft.client.render.entity.EntityRendererFactory; +import net.minecraft.client.util.math.MatrixStack; import net.minecraft.screen.PlayerScreenHandler; import net.minecraft.util.Identifier; public class CastSpellEntityRenderer extends EntityRenderer { + private final SpellEffectsRenderDispatcher spellRenderDispatcher = new SpellEffectsRenderDispatcher(); public CastSpellEntityRenderer(EntityRendererFactory.Context ctx) { super(ctx); @@ -17,4 +21,13 @@ public class CastSpellEntityRenderer extends EntityRenderer { public Identifier getTexture(CastSpellEntity entity) { return PlayerScreenHandler.BLOCK_ATLAS_TEXTURE; } + + @Override + public void render(CastSpellEntity entity, float yaw, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light) { + spellRenderDispatcher.render(matrices, vertexConsumers, light, entity, 0, 0, tickDelta, getAnimationProgress(entity, tickDelta), yaw, 0); + } + + protected float getAnimationProgress(CastSpellEntity entity, float tickDelta) { + return entity.age + tickDelta; + } } diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/spell/AllSpells.java b/src/main/java/com/minelittlepony/unicopia/client/render/spell/AllSpells.java new file mode 100644 index 00000000..c6c5c2ff --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/client/render/spell/AllSpells.java @@ -0,0 +1,8 @@ +package com.minelittlepony.unicopia.client.render.spell; + +import com.minelittlepony.unicopia.ability.magic.SpellPredicate; +import com.minelittlepony.unicopia.ability.magic.spell.Spell; + +public interface AllSpells extends SpellPredicate { + AllSpells INSTANCE = spell -> true; +} diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/spell/SpellEffectsRenderDispatcher.java b/src/main/java/com/minelittlepony/unicopia/client/render/spell/SpellEffectsRenderDispatcher.java new file mode 100644 index 00000000..0f2a7ef3 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/client/render/spell/SpellEffectsRenderDispatcher.java @@ -0,0 +1,138 @@ +package com.minelittlepony.unicopia.client.render.spell; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.jetbrains.annotations.Nullable; + +import com.minelittlepony.unicopia.Unicopia; +import com.minelittlepony.unicopia.ability.magic.Caster; +import com.minelittlepony.unicopia.ability.magic.SpellContainer.Operation; +import com.minelittlepony.unicopia.ability.magic.spell.Spell; +import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; +import com.minelittlepony.unicopia.entity.Living; + +import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.TextRenderer.TextLayerType; +import net.minecraft.client.render.RenderLayer; +import net.minecraft.client.render.VertexConsumer; +import net.minecraft.client.render.VertexConsumerProvider; +import net.minecraft.client.render.WorldRenderer; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.registry.Registries; +import net.minecraft.resource.ResourceManager; +import net.minecraft.resource.SynchronousResourceReloader; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.Box; +import net.minecraft.util.math.RotationAxis; +import net.minecraft.util.math.Vec3d; + +public class SpellEffectsRenderDispatcher implements SynchronousResourceReloader, IdentifiableResourceReloadListener { + public static final SpellEffectsRenderDispatcher INSTANCE = new SpellEffectsRenderDispatcher(); + private static final Identifier ID = Unicopia.id("spell_renderers"); + private static final Map, SpellRendererFactory> REGISTRY = new HashMap<>(); + + public static void register(SpellType type, SpellRendererFactory rendererFactory) { + REGISTRY.put(type, rendererFactory); + } + + @Nullable + private Map, SpellRenderer> renderers = Map.of(); + private final MinecraftClient client = MinecraftClient.getInstance(); + + @Override + public Identifier getFabricId() { + return ID; + } + + @SuppressWarnings("unchecked") + public SpellRenderer getRenderer(S spell) { + return (SpellRenderer)renderers.get(spell.getType()); + } + + public void render(MatrixStack matrices, VertexConsumerProvider vertices, int light, Caster caster, float limbAngle, float limbDistance, float tickDelta, float animationProgress, float headYaw, float headPitch) { + caster.getSpellSlot().forEach(spell -> { + var renderer = getRenderer(spell); + if (renderer != null) { + renderer.render(matrices, vertices, spell, caster, light, limbAngle, limbDistance, tickDelta, animationProgress, headYaw, headPitch); + } + return Operation.SKIP; + }, false); + + if (client.getEntityRenderDispatcher().shouldRenderHitboxes() && !client.hasReducedDebugInfo()) { + renderHotspot(matrices, vertices, caster, animationProgress); + renderSpellDebugInfo(matrices, vertices, caster, light); + } + } + + @Override + public void reload(ResourceManager manager) { + renderers = REGISTRY.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().create())); + } + + private void renderSpellDebugInfo(MatrixStack matrices, VertexConsumerProvider vertices, Caster caster, int light) { + matrices.push(); + matrices.multiply(client.getEntityRenderDispatcher().getRotation()); + float scale = 0.0125F; + if (caster instanceof Living) { + matrices.scale(scale, scale, scale); + } else { + matrices.scale(-scale, -scale, scale); + } + float g = MinecraftClient.getInstance().options.getTextBackgroundOpacity(0.25f); + int j = (int)(g * 255.0f) << 24; + + List debugLines = Stream.concat( + Stream.of( + caster.asEntity().getDisplayName().copy().append(" (" + Registries.ENTITY_TYPE.getId(caster.asEntity().getType()) + ")"), + caster.getMaster() != null ? Text.literal("Master: ").append(caster.getMaster().getDisplayName()) : Text.empty() + ), + caster.getSpellSlot().stream(AllSpells.INSTANCE, false).flatMap(spell -> + Stream.of( + Text.literal("UUID: " + spell.getUuid()), + Text.literal("|>Type: ").append(Text.literal(spell.getType().getId().toString()).styled(s -> s.withColor(spell.getType().getColor()))), + Text.of("|>Traits: " + spell.getTraits()), + Text.literal("|>HasRenderer: ").append(Text.literal((getRenderer(spell) != null) + "").formatted(getRenderer(spell) != null ? Formatting.GREEN : Formatting.RED)) + ) + ) + ).toList(); + + int spacing = client.textRenderer.fontHeight + 1; + int height = spacing * debugLines.size(); + int top = -height; + int left = (int)caster.asEntity().getWidth() * 64; + + for (Text line : debugLines) { + client.textRenderer.draw(line, left += 1, top += spacing, 0xFFFFFFFF, false, matrices.peek().getPositionMatrix(), vertices, TextLayerType.NORMAL, j, light); + } + matrices.pop(); + } + + private void renderHotspot(MatrixStack matrices, VertexConsumerProvider vertices, Caster caster, float animationProgress) { + Box boundingBox = Box.of(caster.getOriginVector(), 1, 1, 1); + + Vec3d pos = caster.getOriginVector(); + + double x = - pos.x; + double y = - pos.y; + double z = - pos.z; + + VertexConsumer buffer = vertices.getBuffer(RenderLayer.getLines()); + + for (float i = -1; i < 1; i += 0.2F) { + matrices.push(); + matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(i * animationProgress)); + matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(i * animationProgress)); + matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(i * animationProgress)); + matrices.scale(i, i, i); + WorldRenderer.drawBox(matrices, buffer, boundingBox.offset(x, y, z), 1, 0, 0, 1); + matrices.pop(); + } + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/spell/SpellRenderer.java b/src/main/java/com/minelittlepony/unicopia/client/render/spell/SpellRenderer.java new file mode 100644 index 00000000..8c7e6466 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/client/render/spell/SpellRenderer.java @@ -0,0 +1,11 @@ +package com.minelittlepony.unicopia.client.render.spell; + +import com.minelittlepony.unicopia.ability.magic.Caster; +import com.minelittlepony.unicopia.ability.magic.spell.Spell; + +import net.minecraft.client.render.VertexConsumerProvider; +import net.minecraft.client.util.math.MatrixStack; + +public interface SpellRenderer { + void render(MatrixStack matrices, VertexConsumerProvider vertices, T spell, Caster caster, int light, float limbAngle, float limbDistance, float tickDelta, float animationProgress, float headYaw, float headPitch); +} diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/spell/SpellRendererFactory.java b/src/main/java/com/minelittlepony/unicopia/client/render/spell/SpellRendererFactory.java new file mode 100644 index 00000000..4dba8b91 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/client/render/spell/SpellRendererFactory.java @@ -0,0 +1,11 @@ +package com.minelittlepony.unicopia.client.render.spell; + +import com.minelittlepony.unicopia.ability.magic.spell.Spell; + +public interface SpellRendererFactory { + SpellRenderer create(); + + static void bootstrap() { + + } +}