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 00000000..c35b7339 Binary files /dev/null and b/src/main/resources/assets/unicopia/textures/models/horn/unicorn.png differ