diff --git a/src/main/java/com/minelittlepony/unicopia/ability/Abilities.java b/src/main/java/com/minelittlepony/unicopia/ability/Abilities.java index 94134e78..bbd82471 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/Abilities.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/Abilities.java @@ -20,13 +20,14 @@ public interface Abilities { BiFunction>> BY_SLOT_AND_COMPOSITE_RACE = Util.memoize((slot, race) -> { return BY_SLOT.computeIfAbsent(slot, s -> new LinkedHashSet<>()) .stream() - .filter(a -> race.any(a::canUse)) + .filter(a -> a.canUse(race)) .toList(); }); // unicorn / alicorn Ability CAST = register(new UnicornCastingAbility(), "cast", AbilitySlot.PRIMARY); Ability SHOOT = register(new UnicornProjectileAbility(), "shoot", AbilitySlot.PRIMARY); + Ability TIME = register(new TimeChangeAbility(), "time_control", AbilitySlot.SECONDARY); Ability TELEPORT = register(new UnicornTeleportAbility(), "teleport", AbilitySlot.SECONDARY); Ability GROUP_TELEPORT = register(new UnicornGroupTeleportAbility(), "teleport_group", AbilitySlot.SECONDARY); Ability DISPELL = register(new UnicornDispellAbility(), "dispell", AbilitySlot.TERTIARY); diff --git a/src/main/java/com/minelittlepony/unicopia/ability/Ability.java b/src/main/java/com/minelittlepony/unicopia/ability/Ability.java index 014c2125..979d7f97 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/Ability.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/Ability.java @@ -56,6 +56,14 @@ public interface Ability { return false; } + /** + * Checks if the given race is permitted to use this ability + * @param race The player's species + */ + default boolean canUse(Race.Composite race) { + return race.any(this::canUse); + } + /** * Checks if the given race is permitted to use this ability * @param playerSpecies The player's species diff --git a/src/main/java/com/minelittlepony/unicopia/ability/AbilityDispatcher.java b/src/main/java/com/minelittlepony/unicopia/ability/AbilityDispatcher.java index 232fcfbf..6ae10a9f 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/AbilityDispatcher.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/AbilityDispatcher.java @@ -262,7 +262,7 @@ public class AbilityDispatcher implements Tickable, NbtSerialisable { public synchronized Optional> getActiveAbility() { return activeAbility.filter(ability -> { - return (!(ability == null || (triggered && warmup == 0 && cooldown == 0)) && player.getCompositeRace().any(ability::canUse)); + return (!(ability == null || (triggered && warmup == 0 && cooldown == 0)) && ability.canUse(player.getCompositeRace())); }); } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/TimeChangeAbility.java b/src/main/java/com/minelittlepony/unicopia/ability/TimeChangeAbility.java new file mode 100644 index 00000000..d79ce12b --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/ability/TimeChangeAbility.java @@ -0,0 +1,69 @@ +package com.minelittlepony.unicopia.ability; + +import java.util.Optional; + +import com.minelittlepony.unicopia.Race; +import com.minelittlepony.unicopia.ability.data.Hit; +import com.minelittlepony.unicopia.ability.data.Hit.Serializer; +import com.minelittlepony.unicopia.ability.magic.spell.CastingMethod; +import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; +import com.minelittlepony.unicopia.entity.player.Pony; + +public class TimeChangeAbility implements Ability { + + @Override + public boolean canUse(Race race) { + return race == Race.ALICORN; + } + + @Override + public boolean canUse(Race.Composite race) { + return race.physical() != race.pseudo() && race.pseudo() == Race.UNICORN; + } + + @Override + public int getCooldownTime(Pony player) { + return 2; + } + + @Override + public int getWarmupTime(Pony player) { + return 20; + } + + @Override + public double getCostEstimate(Pony player) { + return 400; + } + + @Override + public Serializer getSerializer() { + return Hit.SERIALIZER; + } + + @Override + public Optional prepare(Pony player) { + return Hit.INSTANCE; + } + + @Override + public boolean apply(Pony player, Hit data) { + + if (player.getSpellSlot().contains(SpellType.TIME_CONTROL)) { + player.getSpellSlot().removeWhere(SpellType.TIME_CONTROL, true); + } else { + SpellType.TIME_CONTROL.withTraits().apply(player, CastingMethod.INNATE); + } + + return true; + } + + @Override + public void warmUp(Pony player, AbilitySlot slot) { + + } + + @Override + public void coolDown(Pony player, AbilitySlot slot) { + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/TimeControlAbilitySpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/TimeControlAbilitySpell.java new file mode 100644 index 00000000..a8712113 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/TimeControlAbilitySpell.java @@ -0,0 +1,69 @@ +package com.minelittlepony.unicopia.ability.magic.spell; + +import com.minelittlepony.unicopia.ability.Abilities; +import com.minelittlepony.unicopia.ability.magic.Caster; +import com.minelittlepony.unicopia.ability.magic.spell.effect.*; +import com.minelittlepony.unicopia.entity.player.Pony; + +import net.minecraft.nbt.NbtCompound; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec3d; + +/** + * Internal. + *

+ * Used by the Rainboom ability. + */ +public class TimeControlAbilitySpell extends AbstractSpell { + + private Vec3d prevRotation = null; + + public TimeControlAbilitySpell(CustomisedSpellType type) { + super(type); + } + + @Override + public boolean tick(Caster source, Situation situation) { + + if (situation != Situation.BODY) { + return false; + } + + if (!(source instanceof Pony pony) || !Abilities.TIME.canUse(pony.getCompositeRace())) { + return false; + } + + if (source.asWorld() instanceof ServerWorld sw) { + + float yaw = MathHelper.wrapDegrees(source.asEntity().getHeadYaw()); + float pitch = MathHelper.wrapDegrees(source.asEntity().getPitch(1)) / 90F; + + if (yaw > 0) { + pitch += 90; + } + + Vec3d rotation = new Vec3d(pitch, 0, 1); + + if (prevRotation != null) { + pitch = (float)MathHelper.lerp(0.05, pitch, rotation.x); + + sw.setTimeOfDay((long)(pitch * 6000)); + } + + prevRotation = new Vec3d(pitch, 0, 1); + } + + return source.subtractEnergyCost(2); + } + + @Override + public void toNBT(NbtCompound compound) { + super.toNBT(compound); + } + + @Override + public void fromNBT(NbtCompound compound) { + super.fromNBT(compound); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/SpellType.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/SpellType.java index 53dfc899..d5607e7d 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/SpellType.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/SpellType.java @@ -16,6 +16,7 @@ import com.minelittlepony.unicopia.ability.magic.spell.RainboomAbilitySpell; import com.minelittlepony.unicopia.ability.magic.spell.PlaceableSpell; import com.minelittlepony.unicopia.ability.magic.spell.Spell; import com.minelittlepony.unicopia.ability.magic.spell.ThrowableSpell; +import com.minelittlepony.unicopia.ability.magic.spell.TimeControlAbilitySpell; import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; import com.minelittlepony.unicopia.item.UItems; import com.minelittlepony.unicopia.util.RegistryUtils; @@ -48,6 +49,7 @@ public final class SpellType implements Affine, SpellPredicate< public static final SpellType CHANGELING_DISGUISE = register("disguise", Affinity.BAD, 0x19E48E, false, SpellTraits.EMPTY, DispersableDisguiseSpell::new); public static final SpellType RAINBOOM = register("rainboom", Affinity.GOOD, 0xBDBDF9, false, SpellTraits.EMPTY, RainboomAbilitySpell::new); + public static final SpellType TIME_CONTROL = register("time_control", Affinity.GOOD, 0xBDBDF9, false, SpellTraits.EMPTY, TimeControlAbilitySpell::new); public static final SpellType FROST = register("frost", Affinity.GOOD, 0xEABBFF, true, IceSpell.DEFAULT_TRAITS, IceSpell::new); public static final SpellType CHILLING_BREATH = register("chilling_breath", Affinity.NEUTRAL, 0xFFEAFF, true, ChillingBreathSpell.DEFAULT_TRAITS, ChillingBreathSpell::new); diff --git a/src/main/java/com/minelittlepony/unicopia/client/gui/UHud.java b/src/main/java/com/minelittlepony/unicopia/client/gui/UHud.java index a46f23cd..2794bdbd 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/gui/UHud.java +++ b/src/main/java/com/minelittlepony/unicopia/client/gui/UHud.java @@ -137,7 +137,9 @@ public class UHud { slots.forEach(slot -> slot.renderBackground(context, abilities, swap, tickDelta)); - if (pony.getObservedSpecies().canCast()) { + boolean canCast = Abilities.CAST.canUse(pony.getCompositeRace()); + + if (canCast) { Ability ability = pony.getAbilities().getStat(AbilitySlot.PRIMARY) .getAbility(Unicopia.getConfig().hudPage.get()) .orElse(null); @@ -168,7 +170,7 @@ public class UHud { matrices.pop(); - if (pony.getObservedSpecies().canCast()) { + if (canCast) { renderSpell(context, pony.getCharms().getEquippedSpell(Hand.MAIN_HAND), hudX + 10 - xDirection * 13, hudY + 2); renderSpell(context, pony.getCharms().getEquippedSpell(Hand.OFF_HAND), hudX + 8 - xDirection * 2, hudY - 6); } diff --git a/src/main/java/com/minelittlepony/unicopia/entity/Living.java b/src/main/java/com/minelittlepony/unicopia/entity/Living.java index c6321016..2a668fdc 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/Living.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/Living.java @@ -312,7 +312,7 @@ public abstract class Living implements Equine, Caste if (isBeingCarried()) { Pony carrier = Pony.of(entity.getVehicle()).orElse(null); - if (!carrier.getCompositeRace().any(Abilities.CARRY::canUse)) { + if (!Abilities.CARRY.canUse(carrier.getCompositeRace())) { entity.stopRiding(); entity.refreshPositionAfterTeleport(carrier.getOriginVector()); Living.transmitPassengers(carrier.asEntity()); diff --git a/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerPhysics.java b/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerPhysics.java index b10ee952..0f607c6d 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerPhysics.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerPhysics.java @@ -336,7 +336,7 @@ public class PlayerPhysics extends EntityPhysics implements Tickab double horizontalSpeed = this.getHorizontalMotion(); double verticalSpeed = velocity.y; - if (Abilities.RAINBOOM.canUse(pony.getActualSpecies()) && horizontalSpeed != 0 && verticalSpeed < -0.3F && (verticalSpeed / horizontalSpeed) < -0.3F) { + if (Abilities.RAINBOOM.canUse(pony.getCompositeRace()) && horizontalSpeed != 0 && verticalSpeed < -0.3F && (verticalSpeed / horizontalSpeed) < -0.3F) { ticksDiving++; } else { ticksDiving = 0; diff --git a/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java b/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java index 71594222..779d4e61 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java @@ -199,6 +199,9 @@ public class Pony extends Living implements Copyable, Update if (AmuletSelectors.ALICORN_AMULET.test(entity)) { return Race.ALICORN; } + if (AmuletSelectors.UNICORN_AMULET.test(entity)) { + return Race.UNICORN; + } return getObservedSpecies(); } diff --git a/src/main/java/com/minelittlepony/unicopia/item/UItems.java b/src/main/java/com/minelittlepony/unicopia/item/UItems.java index 111fd4a1..0c72f71c 100644 --- a/src/main/java/com/minelittlepony/unicopia/item/UItems.java +++ b/src/main/java/com/minelittlepony/unicopia/item/UItems.java @@ -146,7 +146,7 @@ public interface UItems { AmuletItem UNICORN_AMULET = register("unicorn_amulet", new AmuletItem(new FabricItemSettings() .maxCount(1) .maxDamage(890) - .rarity(Rarity.UNCOMMON), 900), ItemGroups.TOOLS); + .rarity(Rarity.UNCOMMON), 0), ItemGroups.TOOLS); GlassesItem SUNGLASSES = register("sunglasses", new GlassesItem(new FabricItemSettings().maxCount(1)), ItemGroups.COMBAT); GlassesItem BROKEN_SUNGLASSES = register("broken_sunglasses", new GlassesItem(new FabricItemSettings().maxCount(1)), ItemGroups.COMBAT); diff --git a/src/main/resources/assets/unicopia/textures/gui/ability/time_control.png b/src/main/resources/assets/unicopia/textures/gui/ability/time_control.png new file mode 100644 index 00000000..e6f52cdc Binary files /dev/null and b/src/main/resources/assets/unicopia/textures/gui/ability/time_control.png differ diff --git a/src/main/resources/assets/unicopia/textures/item/unicorn_amulet.png b/src/main/resources/assets/unicopia/textures/item/unicorn_amulet.png index 5f331a6a..6f7221e0 100644 Binary files a/src/main/resources/assets/unicopia/textures/item/unicorn_amulet.png and b/src/main/resources/assets/unicopia/textures/item/unicorn_amulet.png differ diff --git a/src/main/resources/assets/unicopia/textures/models/armor/alicorn_amulet.png b/src/main/resources/assets/unicopia/textures/models/armor/alicorn_amulet.png index 1ef9b9ae..e8c32632 100644 Binary files a/src/main/resources/assets/unicopia/textures/models/armor/alicorn_amulet.png and b/src/main/resources/assets/unicopia/textures/models/armor/alicorn_amulet.png differ diff --git a/src/main/resources/assets/unicopia/textures/models/armor/unicorn_amulet.png b/src/main/resources/assets/unicopia/textures/models/armor/unicorn_amulet.png new file mode 100644 index 00000000..405ba2a7 Binary files /dev/null and b/src/main/resources/assets/unicopia/textures/models/armor/unicorn_amulet.png differ