Implement proper spell rendering

This commit is contained in:
Sollace 2023-10-21 23:16:54 +01:00
parent 23266d2f3a
commit 5011c35e15
No known key found for this signature in database
GPG key ID: E52FACE7B5C773DB
11 changed files with 283 additions and 11 deletions

View file

@ -165,7 +165,7 @@ public final class SpellTraits implements Iterable<Map.Entry<Trait, Float>> {
@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

View file

@ -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 <T extends ParticleEffect> PendingParticleFactory<T> createFactory(ParticleSupplier<T> supplier) {

View file

@ -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);
}

View file

@ -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<com.minelittlepony.api.pony.meta.Race, Race> PONY_RACE_MAPPING = new HashMap<>();
private static final Function<com.minelittlepony.api.pony.meta.Race, Race> 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);
}
}

View file

@ -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 <T extends Entity> Identifier getTexture(T entity, Context<T, ?> context) {
return BraceletFeatureRenderer.TEXTURE;
}
@Override
public <M extends EntityModel<?> & 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
);
}
}

View file

@ -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) {

View file

@ -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<CastSpellEntity> {
private final SpellEffectsRenderDispatcher spellRenderDispatcher = new SpellEffectsRenderDispatcher();
public CastSpellEntityRenderer(EntityRendererFactory.Context ctx) {
super(ctx);
@ -17,4 +21,13 @@ public class CastSpellEntityRenderer extends EntityRenderer<CastSpellEntity> {
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;
}
}

View file

@ -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<Spell> {
AllSpells INSTANCE = spell -> true;
}

View file

@ -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<SpellType<?>, SpellRendererFactory<?>> REGISTRY = new HashMap<>();
public static <T extends Spell> void register(SpellType<T> type, SpellRendererFactory<? super T> rendererFactory) {
REGISTRY.put(type, rendererFactory);
}
@Nullable
private Map<SpellType<?>, SpellRenderer<?>> renderers = Map.of();
private final MinecraftClient client = MinecraftClient.getInstance();
@Override
public Identifier getFabricId() {
return ID;
}
@SuppressWarnings("unchecked")
public <S extends Spell> SpellRenderer<S> getRenderer(S spell) {
return (SpellRenderer<S>)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<Text> 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();
}
}
}

View file

@ -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<T extends Spell> {
void render(MatrixStack matrices, VertexConsumerProvider vertices, T spell, Caster<?> caster, int light, float limbAngle, float limbDistance, float tickDelta, float animationProgress, float headYaw, float headPitch);
}

View file

@ -0,0 +1,11 @@
package com.minelittlepony.unicopia.client.render.spell;
import com.minelittlepony.unicopia.ability.magic.spell.Spell;
public interface SpellRendererFactory<T extends Spell> {
SpellRenderer<T> create();
static void bootstrap() {
}
}