From f3d251c1d0a0402a54de531ba07cebaeaf4597cb Mon Sep 17 00:00:00 2001 From: Sollace Date: Mon, 29 May 2023 11:54:54 +0100 Subject: [PATCH] Ponies that cast magic now have horns --- .../unicopia/ability/Ability.java | 4 + .../unicopia/ability/AbilityDispatcher.java | 6 +- .../ability/UnicornCastingAbility.java | 16 +++ .../ability/UnicornDispellAbility.java | 6 + .../ability/UnicornTeleportAbility.java | 6 + .../ability/magic/SpellPredicate.java | 17 ++- .../unicopia/client/URenderers.java | 1 + .../client/render/HornFeatureRenderer.java | 108 ++++++++++++++++++ .../unicopia/textures/models/horn/unicorn.png | Bin 0 -> 4554 bytes 9 files changed, 160 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/minelittlepony/unicopia/client/render/HornFeatureRenderer.java create mode 100644 src/main/resources/assets/unicopia/textures/models/horn/unicorn.png diff --git a/src/main/java/com/minelittlepony/unicopia/ability/Ability.java b/src/main/java/com/minelittlepony/unicopia/ability/Ability.java index 0aa73e2d..3a5721fe 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/Ability.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/Ability.java @@ -28,6 +28,10 @@ public interface Ability { */ int getCooldownTime(Pony player); + default int getColor(Pony player) { + return -1; + } + /** * Called when an ability is about to be triggered. This event occurs on both the client and server so check {@code Pony#isClient} if you need to know which one you're on. *

diff --git a/src/main/java/com/minelittlepony/unicopia/ability/AbilityDispatcher.java b/src/main/java/com/minelittlepony/unicopia/ability/AbilityDispatcher.java index f680ec2e..2207c6de 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/AbilityDispatcher.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/AbilityDispatcher.java @@ -64,6 +64,10 @@ public class AbilityDispatcher implements Tickable, NbtSerialisable { return stats.values(); } + public Optional getActiveStat() { + return stats.values().stream().filter(stat -> stat.getFillProgress() > 0).findFirst(); + } + public Stat getStat(AbilitySlot slot) { return stats.computeIfAbsent(slot, Stat::new); } @@ -246,7 +250,7 @@ public class AbilityDispatcher implements Tickable, NbtSerialisable { return Optional.empty(); } - protected synchronized Optional> getActiveAbility() { + public synchronized Optional> getActiveAbility() { return activeAbility.filter(ability -> { return (!(ability == null || (triggered && warmup == 0 && cooldown == 0)) && player.getCompositeRace().any(ability::canUse)); }); diff --git a/src/main/java/com/minelittlepony/unicopia/ability/UnicornCastingAbility.java b/src/main/java/com/minelittlepony/unicopia/ability/UnicornCastingAbility.java index 0b7b5a06..e87472af 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/UnicornCastingAbility.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/UnicornCastingAbility.java @@ -80,6 +80,22 @@ public class UnicornCastingAbility implements Ability { return !spell.getResult().isAccepted() || spell.getValue().isOn(player) ? 2 : 4; } + @Override + public int getColor(Pony player) { + TypedActionResult amulet = getAmulet(player); + if (amulet.getResult().isAccepted()) { + return 0x000000; + } + + Hand hand = player.asEntity().isSneaking() ? Hand.OFF_HAND : Hand.MAIN_HAND; + TypedActionResult> newSpell = player.getCharms().getSpellInHand(hand); + + if (newSpell.getResult() != ActionResult.FAIL) { + return newSpell.getValue().type().getColor(); + } + return -1; + } + @Override public void apply(Pony player, Hit data) { if (!player.canCast()) { diff --git a/src/main/java/com/minelittlepony/unicopia/ability/UnicornDispellAbility.java b/src/main/java/com/minelittlepony/unicopia/ability/UnicornDispellAbility.java index 315a9916..e0299d56 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/UnicornDispellAbility.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/UnicornDispellAbility.java @@ -7,6 +7,7 @@ import com.minelittlepony.unicopia.InteractionManager; import com.minelittlepony.unicopia.Race; import com.minelittlepony.unicopia.ability.data.Pos; import com.minelittlepony.unicopia.ability.magic.Caster; +import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; import com.minelittlepony.unicopia.client.render.PlayerPoser.Animation; import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.particle.MagicParticleEffect; @@ -36,6 +37,11 @@ public class UnicornDispellAbility implements Ability { return race.canCast() || race == Race.CHANGELING; } + @Override + public int getColor(Pony player) { + return SpellType.PORTAL.getColor(); + } + @Override public Identifier getIcon(Pony player, boolean swap) { Identifier id = Abilities.REGISTRY.getId(this); diff --git a/src/main/java/com/minelittlepony/unicopia/ability/UnicornTeleportAbility.java b/src/main/java/com/minelittlepony/unicopia/ability/UnicornTeleportAbility.java index 66212428..4c3a33a4 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/UnicornTeleportAbility.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/UnicornTeleportAbility.java @@ -5,6 +5,7 @@ import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.ability.data.Hit; import com.minelittlepony.unicopia.ability.data.Pos; import com.minelittlepony.unicopia.ability.magic.Caster; +import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; import com.minelittlepony.unicopia.entity.Living; import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.particle.MagicParticleEffect; @@ -60,6 +61,11 @@ public class UnicornTeleportAbility implements Ability { return pos.distanceTo(player) / 10; } + @Override + public int getColor(Pony player) { + return SpellType.PORTAL.getColor(); + } + @Override public Pos tryActivate(Pony player) { diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/SpellPredicate.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/SpellPredicate.java index beda6ee5..834f7a0a 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/SpellPredicate.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/SpellPredicate.java @@ -16,11 +16,22 @@ public interface SpellPredicate extends Predicate { SpellPredicate IS_SHIELD_LIKE = spell -> spell instanceof ShieldSpell; SpellPredicate IS_TIMED = spell -> spell instanceof TimedSpell; + SpellPredicate IS_NOT_PLACED = IS_PLACED.negate(); + default SpellPredicate and(SpellPredicate predicate) { SpellPredicate self = this; - return s -> { - return self.test(s) && predicate.test(s); - }; + return s -> self.test(s) && predicate.test(s); + } + + default SpellPredicate or(SpellPredicate predicate) { + SpellPredicate self = this; + return s -> self.test(s) || predicate.test(s); + } + + @Override + default SpellPredicate negate() { + SpellPredicate self = this; + return s -> !self.test(s); } default boolean isOn(Caster caster) { diff --git a/src/main/java/com/minelittlepony/unicopia/client/URenderers.java b/src/main/java/com/minelittlepony/unicopia/client/URenderers.java index 30b2baf3..dcb7fb88 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/URenderers.java +++ b/src/main/java/com/minelittlepony/unicopia/client/URenderers.java @@ -61,6 +61,7 @@ public interface URenderers { AccessoryFeatureRenderer.register(BraceletFeatureRenderer::new); AccessoryFeatureRenderer.register(AmuletFeatureRenderer::new); AccessoryFeatureRenderer.register(WingsFeatureRenderer::new); + AccessoryFeatureRenderer.register(HornFeatureRenderer::new); AccessoryFeatureRenderer.register(IcarusWingsFeatureRenderer::new); AccessoryFeatureRenderer.register(BatWingsFeatureRenderer::new); AccessoryFeatureRenderer.register(GlassesFeatureRenderer::new); diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/HornFeatureRenderer.java b/src/main/java/com/minelittlepony/unicopia/client/render/HornFeatureRenderer.java new file mode 100644 index 00000000..73835bd1 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/client/render/HornFeatureRenderer.java @@ -0,0 +1,108 @@ +package com.minelittlepony.unicopia.client.render; + +import com.minelittlepony.unicopia.Unicopia; +import com.minelittlepony.unicopia.entity.player.Pony; +import com.minelittlepony.unicopia.ability.AbilityDispatcher.Stat; +import com.minelittlepony.unicopia.ability.magic.SpellPredicate; + +import net.minecraft.client.model.Dilation; +import net.minecraft.client.model.Model; +import net.minecraft.client.model.ModelData; +import net.minecraft.client.model.ModelPart; +import net.minecraft.client.model.ModelPartBuilder; +import net.minecraft.client.model.ModelPartData; +import net.minecraft.client.model.ModelTransform; +import net.minecraft.client.model.TexturedModelData; +import net.minecraft.client.render.OverlayTexture; +import net.minecraft.client.render.RenderLayer; +import net.minecraft.client.render.VertexConsumer; +import net.minecraft.client.render.VertexConsumerProvider; +import net.minecraft.client.render.entity.feature.FeatureRendererContext; +import net.minecraft.client.render.entity.model.BipedEntityModel; +import net.minecraft.client.render.entity.model.EntityModelPartNames; +import net.minecraft.client.render.item.ItemRenderer; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.*; +import net.minecraft.util.math.MathHelper; + +public class HornFeatureRenderer implements AccessoryFeatureRenderer.Feature { + + public static final Identifier TEXTURE = Unicopia.id("textures/models/horn/unicorn.png"); + + private final HornModel model; + + private final FeatureRendererContext> context; + + public HornFeatureRenderer(FeatureRendererContext> context) { + this.context = context; + model = new HornModel(HornModel.getData(Dilation.NONE).createModel()); + } + + protected boolean canRender(E entity) { + return entity instanceof PlayerEntity player && Pony.of(player).getObservedSpecies().canCast(); + } + + @Override + public void render(MatrixStack stack, VertexConsumerProvider renderContext, int lightUv, E entity, float limbDistance, float limbAngle, float tickDelta, float age, float headYaw, float headPitch) { + if (canRender(entity)) { + model.setAngles(context.getModel()); + model.setState(false); + model.render(stack, ItemRenderer.getArmorGlintConsumer(renderContext, RenderLayer.getArmorCutoutNoCull(TEXTURE), false, false), lightUv, OverlayTexture.DEFAULT_UV, 1, 1, 1, 1); + + Pony.of(entity).flatMap(pony -> { + return pony.getAbilities().getActiveStat() + .flatMap(Stat::getActiveAbility) + .map(ability -> ability.getColor(pony)) + .filter(i -> i != -1).or(() -> pony.getSpellSlot().get(SpellPredicate.IS_NOT_PLACED, false).map(spell -> spell.getType().getColor())); + }).ifPresent(color -> { + model.setState(true); + model.render(stack, ItemRenderer.getArmorGlintConsumer(renderContext, RenderLayers.getMagicColored(color), false, false), lightUv, OverlayTexture.DEFAULT_UV, 1, 1, 1, 1); + }); + } + } + + public static class HornModel extends Model { + + private final ModelPart part; + + public HornModel(ModelPart tree) { + super(RenderLayer::getEntityTranslucent); + part = tree.getChild(EntityModelPartNames.HEAD); + } + + public static TexturedModelData getData(Dilation dilation) { + ModelData data = new ModelData(); + ModelPartData root = data.getRoot(); + + ModelPartData head = root.addChild(EntityModelPartNames.HEAD, ModelPartBuilder.create() + .uv(0, 0) + .cuboid(-4, -8, -4, 8, 8, 8, dilation), ModelTransform.NONE); + head.addChild("horn", ModelPartBuilder.create() + .uv(0, 3) + .cuboid(-0.5F, -11, -3.5F, 1, 4, 1, dilation), ModelTransform.rotation(29 * MathHelper.RADIANS_PER_DEGREE, 0, 0)); + head.addChild("magic", ModelPartBuilder.create() + .uv(0, 3) + .cuboid(-0.5F, -11, -3.5F, 1, 4, 1, dilation.add(0.5F)), ModelTransform.rotation(29 * MathHelper.RADIANS_PER_DEGREE, 0, 0)); + + return TexturedModelData.of(data, 64, 64); + } + + public void setAngles(BipedEntityModel biped) { + part.copyTransform(biped.getHead()); + } + + public void setState(boolean magic) { + part.hidden = true; + part.getChild("horn").visible = !magic; + part.getChild("magic").visible = magic; + } + + @Override + public void render(MatrixStack matrixStack, VertexConsumer vertexConsumer, int i, int j, float f, float g, float h, float k) { + part.render(matrixStack, vertexConsumer, i, j, f, g, h, k); + } + } + +} diff --git a/src/main/resources/assets/unicopia/textures/models/horn/unicorn.png b/src/main/resources/assets/unicopia/textures/models/horn/unicorn.png new file mode 100644 index 0000000000000000000000000000000000000000..c35b733984c584b737823634b4dc8c33018aba18 GIT binary patch literal 4554 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 literal 0 HcmV?d00001