diff --git a/src/main/java/com/minelittlepony/unicopia/ability/UnicornCastingAbility.java b/src/main/java/com/minelittlepony/unicopia/ability/UnicornCastingAbility.java index 861468c3..5f04ef07 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/UnicornCastingAbility.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/UnicornCastingAbility.java @@ -52,6 +52,9 @@ public class UnicornCastingAbility implements Ability { @Override @Nullable public Hit tryActivate(Pony player) { + if (!player.canCast()) { + return null; + } return Hit.of(player.getMagicalReserves().getMana().get() >= getCostEstimate(player)); } @@ -77,6 +80,10 @@ public class UnicornCastingAbility implements Ability { @Override public void apply(Pony player, Hit data) { + if (!player.canCast()) { + return; + } + TypedActionResult amulet = getAmulet(player); if (amulet.getResult().isAccepted()) { diff --git a/src/main/java/com/minelittlepony/unicopia/ability/UnicornTeleportAbility.java b/src/main/java/com/minelittlepony/unicopia/ability/UnicornTeleportAbility.java index 4c92c2a5..074d8229 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/UnicornTeleportAbility.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/UnicornTeleportAbility.java @@ -66,6 +66,11 @@ public class UnicornTeleportAbility implements Ability { @Override public Pos tryActivate(Pony player) { + + if (!player.canCast()) { + return null; + } + int maxDistance = player.getMaster().isCreative() ? 1000 : 100; HitResult ray = RayTraceHelper.doTrace(player.getMaster(), maxDistance, 1, EntityPredicates.EXCEPT_CREATIVE_OR_SPECTATOR).getResult(); @@ -130,6 +135,10 @@ public class UnicornTeleportAbility implements Ability { protected void teleport(Pony teleporter, Caster teleportee, Pos destination) { + if (!teleporter.canCast()) { + return; + } + LivingEntity player = teleportee.getMaster(); if (player == null) { diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/Caster.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/Caster.java index 522943d3..607c9b24 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/Caster.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/Caster.java @@ -8,6 +8,7 @@ import org.jetbrains.annotations.Nullable; import com.minelittlepony.unicopia.EquinePredicates; import com.minelittlepony.unicopia.Owned; +import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; import com.minelittlepony.unicopia.entity.Physics; import com.minelittlepony.unicopia.entity.PonyContainer; import com.minelittlepony.unicopia.particle.ParticleSource; @@ -18,6 +19,7 @@ import net.minecraft.entity.LivingEntity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.sound.SoundEvent; import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; import net.minecraft.world.GameRules; import net.minecraft.world.World; @@ -61,6 +63,11 @@ public interface Caster extends Owned, Levelled, Affi } default boolean canModifyAt(BlockPos pos) { + + if (!canCastAt(Vec3d.ofCenter(pos))) { + return false; + } + if (getMaster() instanceof PlayerEntity) { return getReferenceWorld().canPlayerModifyAt((PlayerEntity)getMaster(), pos); } @@ -94,6 +101,18 @@ public interface Caster extends Owned, Levelled, Affi return findAllEntitiesInRange(radius, null); } + default boolean canCast() { + return canCastAt(getOriginVector()); + } + + default boolean canCastAt(Vec3d pos) { + return findAllSpellsInRange(500, SpellType.ARCANE_PROTECTION::isOn).noneMatch(caster -> caster + .getSpellSlot().get(SpellType.ARCANE_PROTECTION, false) + .filter(spell -> spell.blocksMagicFor(caster, this, pos)) + .isPresent() + ); + } + static Stream> stream(Stream entities) { return entities.map(Caster::of).flatMap(Optional::stream); } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AreaProtectionSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AreaProtectionSpell.java new file mode 100644 index 00000000..0bca6ed5 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AreaProtectionSpell.java @@ -0,0 +1,75 @@ +package com.minelittlepony.unicopia.ability.magic.spell.effect; + +import com.minelittlepony.unicopia.ability.magic.Caster; +import com.minelittlepony.unicopia.ability.magic.spell.AbstractAreaEffectSpell; +import com.minelittlepony.unicopia.ability.magic.spell.Situation; +import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; +import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; +import com.minelittlepony.unicopia.entity.UEntities; +import com.minelittlepony.unicopia.entity.player.Pony; +import com.minelittlepony.unicopia.item.FriendshipBraceletItem; +import com.minelittlepony.unicopia.particle.MagicParticleEffect; +import com.minelittlepony.unicopia.util.shape.Sphere; + +import net.minecraft.entity.Entity; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; + +public class AreaProtectionSpell extends AbstractAreaEffectSpell { + public static final SpellTraits DEFAULT_TRAITS = new SpellTraits.Builder() + .with(Trait.FOCUS, 50) + .with(Trait.STRENGTH, 30) + .build(); + + protected AreaProtectionSpell(CustomisedSpellType type) { + super(type); + } + + @Override + public boolean tick(Caster source, Situation situation) { + + if (situation == Situation.PROJECTILE || situation == Situation.BODY) { + return false; + } + + float radius = (float)getDrawDropOffRange(source); + + if (source.isClient()) { + Vec3d origin = source.getOriginVector(); + + source.spawnParticles(origin, new Sphere(true, radius), (int)(radius * 6), pos -> { + if (!source.getReferenceWorld().isAir(new BlockPos(pos))) { + source.addParticle(new MagicParticleEffect(getType().getColor()), pos, Vec3d.ZERO); + } + }); + } + + source.findAllSpellsInRange(radius, e -> isValidTarget(source, e)).filter(caster -> !caster.hasCommonOwner(source)).forEach(caster -> { + caster.getEntity().kill(); + }); + + return !isDead(); + } + + /** + * Calculates the maximum radius of the shield. aka The area of effect. + */ + public double getDrawDropOffRange(Caster source) { + float multiplier = source instanceof Pony pony && pony.getMaster().isSneaking() ? 1 : 2; + float min = 4 + getTraits().get(Trait.POWER); + double range = (min + (source.getLevel().getScaled(4) * 2)) / multiplier; + if (source instanceof Pony && range > 2) { + range = Math.sqrt(range); + } + return range; + } + + public boolean blocksMagicFor(Caster source, Caster other, Vec3d position) { + return !FriendshipBraceletItem.isComrade(other, other.getEntity()) + && source.getOriginVector().distanceTo(position) <= getDrawDropOffRange(source); + } + + protected boolean isValidTarget(Caster source, Entity entity) { + return entity.getType() == UEntities.MAGIC_BEAM; + } +} 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 3353d224..3e90d543 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 @@ -50,13 +50,14 @@ public final class SpellType implements Affine, SpellPredicate< public static final SpellType FLAME = register("flame", Affinity.GOOD, 0xFFBB99, true, FireSpell.DEFAULT_TRAITS, FireSpell::new); public static final SpellType INFERNAL = register("infernal", Affinity.BAD, 0xFFAA00, true, InfernoSpell.DEFAULT_TRAITS, InfernoSpell::new); public static final SpellType SHIELD = register("shield", Affinity.NEUTRAL, 0x66CDAA, true, ShieldSpell.DEFAULT_TRAITS, ShieldSpell::new); + public static final SpellType ARCANE_PROTECTION = register("arcane_protection", Affinity.BAD, 0x99CDAA, true, AreaProtectionSpell.DEFAULT_TRAITS, AreaProtectionSpell::new); public static final SpellType VORTEX = register("vortex", Affinity.NEUTRAL, 0xFFEA88, true, AttractiveSpell.DEFAULT_TRAITS, AttractiveSpell::new); public static final SpellType DARK_VORTEX = register("dark_vortex", Affinity.BAD, 0xA33333, true, DarkVortexSpell.DEFAULT_TRAITS, DarkVortexSpell::new); - public static final SpellType NECROMANCY = register("necromancy", Affinity.BAD, 0xFA3A3A, true, NecromancySpell::new); - public static final SpellType SIPHONING = register("siphoning", Affinity.NEUTRAL, 0xFFA3AA, true, SiphoningSpell::new); - public static final SpellType REVEALING = register("reveal", Affinity.GOOD, 0xFFFFAF, true, DisperseIllusionSpell::new); - public static final SpellType AWKWARD = register("awkward", Affinity.GOOD, 0x3A59FF, true, AwkwardSpell::new); - public static final SpellType TRANSFORMATION = register("transformation", Affinity.GOOD, 0x19E48E, true, TransformationSpell::new); + public static final SpellType NECROMANCY = register("necromancy", Affinity.BAD, 0xFA3A3A, true, SpellTraits.EMPTY, NecromancySpell::new); + public static final SpellType SIPHONING = register("siphoning", Affinity.NEUTRAL, 0xFFA3AA, true, SpellTraits.EMPTY, SiphoningSpell::new); + public static final SpellType REVEALING = register("reveal", Affinity.GOOD, 0xFFFFAF, true, SpellTraits.EMPTY, DisperseIllusionSpell::new); + public static final SpellType AWKWARD = register("awkward", Affinity.GOOD, 0x3A59FF, true, SpellTraits.EMPTY, AwkwardSpell::new); + public static final SpellType TRANSFORMATION = register("transformation", Affinity.GOOD, 0x19E48E, true, SpellTraits.EMPTY, TransformationSpell::new); public static final SpellType FEATHER_FALL = register("feather_fall", Affinity.GOOD, 0x00EEFF, true, FeatherFallSpell.DEFAULT_TRAITS, FeatherFallSpell::new); public static final SpellType CATAPULT = register("catapult", Affinity.GOOD, 0x22FF00, true, CatapultSpell.DEFAULT_TRAITS, CatapultSpell::new); public static final SpellType FIRE_BOLT = register("fire_bolt", Affinity.GOOD, 0xFF8811, true, FireBoltSpell.DEFAULT_TRAITS, FireBoltSpell::new); @@ -159,11 +160,6 @@ public final class SpellType implements Affine, SpellPredicate< return "SpellType[" + getTranslationKey() + "]"; } - @Deprecated(forRemoval = true) - public static SpellType register(String name, Affinity affinity, int color, boolean obtainable, Factory factory) { - return register(name, affinity, color, obtainable, SpellTraits.EMPTY, factory); - } - public static SpellType register(String name, Affinity affinity, int color, boolean obtainable, SpellTraits traits, Factory factory) { return register(Unicopia.id(name), affinity, color, obtainable, traits, factory); } diff --git a/src/main/resources/assets/unicopia/lang/en_us.json b/src/main/resources/assets/unicopia/lang/en_us.json index 50ca91ca..014ebdc1 100644 --- a/src/main/resources/assets/unicopia/lang/en_us.json +++ b/src/main/resources/assets/unicopia/lang/en_us.json @@ -146,6 +146,8 @@ "spell.unicopia.fire_bolt.lore": "Produces several burning projectiles", "spell.unicopia.shield": "Protection", "spell.unicopia.shield.lore": "Casts a protective shield around the user", + "spell.unicopia.arcane_protection": "Arcane Protections", + "spell.unicopia.arcane_protection.lore": "Creates a protective shroud over an area in which no other spells can be cast", "spell.unicopia.vortex": "Arcane Attraction", "spell.unicopia.vortex.lore": "Creates a magnetic force that pulls in other targets", "spell.unicopia.dark_vortex": "Dark Vortex", diff --git a/src/main/resources/data/unicopia/recipes/spells/arcane_protection.json b/src/main/resources/data/unicopia/recipes/spells/arcane_protection.json new file mode 100644 index 00000000..523b4d0f --- /dev/null +++ b/src/main/resources/data/unicopia/recipes/spells/arcane_protection.json @@ -0,0 +1,14 @@ +{ + "type": "unicopia:spellbook/crafting", + "material": { "item": "unicopia:gemstone", "spell": "unicopia:none" }, + "traits": { + "strength": 10, "knowledge": 18 + }, + "ingredients": [ + { "item": "unicopia:gemstone", "spell": "unicopia:shield" } + ], + "result": { + "item": "unicopia:gemstone", + "spell": "unicopia:arcane_protection" + } +} \ No newline at end of file