From 79d97adf0b32ed3ade374a48723c8f2b6071bfd3 Mon Sep 17 00:00:00 2001 From: Sollace Date: Fri, 24 May 2024 16:43:11 +0100 Subject: [PATCH] Implement attribute tooltips for all spells --- .../magic/spell/AbstractAreaEffectSpell.java | 16 +++++ .../magic/spell/DispersableDisguiseSpell.java | 2 +- .../ability/magic/spell/SpellAttributes.java | 64 +++++++++++++++++++ .../ability/magic/spell/TimedSpell.java | 16 +++++ .../spell/effect/AreaProtectionSpell.java | 19 +++--- .../magic/spell/effect/AttractiveSpell.java | 24 ++++--- .../magic/spell/effect/BubbleSpell.java | 9 ++- .../magic/spell/effect/CatapultSpell.java | 8 +++ .../spell/effect/CustomisedSpellType.java | 8 ++- .../magic/spell/effect/DispellEvilSpell.java | 9 ++- .../spell/effect/DisperseIllusionSpell.java | 13 +++- .../magic/spell/effect/DisplacementSpell.java | 10 +++ .../magic/spell/effect/FeatherFallSpell.java | 37 +++++++---- .../magic/spell/effect/FireBoltSpell.java | 17 +++++ .../ability/magic/spell/effect/FireSpell.java | 14 +++- .../magic/spell/effect/HydrophobicSpell.java | 7 ++ .../magic/spell/effect/LightSpell.java | 9 ++- .../magic/spell/effect/MimicSpell.java | 11 +++- .../magic/spell/effect/NecromancySpell.java | 9 ++- .../magic/spell/effect/ScorchSpell.java | 2 +- .../magic/spell/effect/ShieldSpell.java | 49 ++++++++------ .../magic/spell/effect/SiphoningSpell.java | 8 ++- .../ability/magic/spell/effect/SpellType.java | 41 ++++++------ .../magic/spell/trait/SpellTraits.java | 4 ++ .../unicopia/entity/effect/EffectUtils.java | 8 +++ .../resources/assets/unicopia/lang/en_us.json | 33 ++++++++-- 26 files changed, 357 insertions(+), 90 deletions(-) create mode 100644 src/main/java/com/minelittlepony/unicopia/ability/magic/spell/SpellAttributes.java diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/AbstractAreaEffectSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/AbstractAreaEffectSpell.java index c0f63418..128323a0 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/AbstractAreaEffectSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/AbstractAreaEffectSpell.java @@ -1,9 +1,21 @@ package com.minelittlepony.unicopia.ability.magic.spell; +import java.util.List; + import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.spell.effect.*; +import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; +import net.minecraft.text.Text; public abstract class AbstractAreaEffectSpell extends AbstractSpell { + + public static void appendRangeTooltip(CustomisedSpellType type, List tooltip) { + float addedRange = type.traits().get(Trait.POWER); + if (addedRange != 0) { + tooltip.add(SpellAttributes.ofRelative(SpellAttributes.RANGE, addedRange)); + } + } + protected AbstractAreaEffectSpell(CustomisedSpellType type) { super(type); } @@ -12,4 +24,8 @@ public abstract class AbstractAreaEffectSpell extends AbstractSpell { public Spell prepareForCast(Caster caster, CastingMethod method) { return toPlaceable(); } + + protected final float getAdditionalRange() { + return (int)getTraits().get(Trait.POWER); + } } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/DispersableDisguiseSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/DispersableDisguiseSpell.java index f52b0e4a..d659cc90 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/DispersableDisguiseSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/DispersableDisguiseSpell.java @@ -36,7 +36,7 @@ public class DispersableDisguiseSpell extends AbstractDisguiseSpell implements I @Override public void onSuppressed(Caster otherSource, float time) { time /= getTraits().getOrDefault(Trait.STRENGTH, 1); - suppressionCounter = (int)(100 * time); + suppressionCounter = (int)time; setDirty(); } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/SpellAttributes.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/SpellAttributes.java new file mode 100644 index 00000000..f4b2ffdc --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/SpellAttributes.java @@ -0,0 +1,64 @@ +package com.minelittlepony.unicopia.ability.magic.spell; + +import com.minelittlepony.unicopia.Unicopia; +import com.minelittlepony.unicopia.entity.effect.EffectUtils; + +import net.minecraft.item.ItemStack; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import net.minecraft.util.Identifier; +import net.minecraft.util.StringHelper; +import net.minecraft.util.Util; + +public interface SpellAttributes { + Text CAST_ON_LOCATION = of(Unicopia.id("cast_on_location")); + Text CAST_ON_PERSON = of(Unicopia.id("cast_on_person")); + Text TARGET_ENTITY = of(Unicopia.id("focused_entity")); + Text FOLLOWS_TARGET = of(Unicopia.id("follows_target")); + + Text PERMIT_ITEMS = of(Unicopia.id("permit_items")); + Text PERMIT_PASSIVE = of(Unicopia.id("permit_passive")); + Text PERMIT_HOSTILE = of(Unicopia.id("permit_hostile")); + Text PERMIT_PLAYER = of(Unicopia.id("permit_player")); + + Identifier RANGE = Unicopia.id("range"); + Identifier DURATION = Unicopia.id("duration"); + Identifier STRENGTH = Unicopia.id("strength"); + Identifier VELOCITY = Unicopia.id("velocity"); + Identifier VERTICAL_VELOCITY = Unicopia.id("vertical_velocity"); + Identifier DAMAGE_TO_TARGET = Unicopia.id("damage_to_target"); + Identifier SIMULTANIOUS_TARGETS = Unicopia.id("simultanious_targets"); + Identifier COST_PER_INDIVIDUAL = Unicopia.id("cost_per_individual"); + Identifier EXPLOSION_STRENGTH = Unicopia.id("explosion_strength"); + Identifier PROJECTILE_COUNT = Unicopia.id("projectile_count"); + Identifier ORB_COUNT = Unicopia.id("orb_count"); + Identifier WAVE_SIZE = Unicopia.id("wave_size"); + Identifier FOLLOW_RANGE = Unicopia.id("follow_range"); + Identifier SOAPINESS = Unicopia.id("soapiness"); + + Identifier TARGET_PREFERENCE = Unicopia.id("target_preference"); + Identifier CASTER_PREFERENCE = Unicopia.id("caster_preference"); + + static Text of(Identifier id) { + return Text.literal(" ").append(Text.translatable(Util.createTranslationKey("spell_attribute", id))).formatted(Formatting.LIGHT_PURPLE); + } + + static Text of(Identifier id, float value) { + return Text.literal(" ").append( + Text.translatable("attribute.modifier.equals.0", + ItemStack.MODIFIER_FORMAT.format(value), + Text.translatable(Util.createTranslationKey("spell_attribute", id))) + ).formatted(Formatting.LIGHT_PURPLE); + } + + static Text ofRelative(Identifier id, float value) { + return EffectUtils.formatModifierChange(Util.createTranslationKey("spell_attribute", id), value, false); + } + + static Text ofTime(Identifier id, long time) { + return Text.literal(" ").append(Text.translatable("attribute.modifier.equals.0", + StringHelper.formatTicks((int)Math.abs(time)), + Text.translatable(Util.createTranslationKey("spell_attribute", id)) + ).formatted(Formatting.LIGHT_PURPLE)); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/TimedSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/TimedSpell.java index d81e4f39..33b2b837 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/TimedSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/TimedSpell.java @@ -1,17 +1,33 @@ package com.minelittlepony.unicopia.ability.magic.spell; +import java.util.List; + +import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType; +import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; +import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.util.NbtSerialisable; import com.minelittlepony.unicopia.util.Tickable; import net.minecraft.nbt.NbtCompound; +import net.minecraft.text.Text; import net.minecraft.util.math.MathHelper; /** * A magic effect with a set duration capable of reporting how long it has until it runs out. */ public interface TimedSpell extends Spell { + int BASE_DURATION = 120 * 20; + Timer getTimer(); + static void appendDurationTooltip(CustomisedSpellType type, List tooltip) { + tooltip.add(SpellAttributes.ofTime(SpellAttributes.DURATION, BASE_DURATION + getExtraDuration(type.traits()))); + } + + static int getExtraDuration(SpellTraits traits) { + return (int)(traits.get(Trait.FOCUS, 0, 160) * 19) * 20; + } + class Timer implements Tickable, NbtSerialisable { private int maxDuration; private int duration; 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 index 093ea63f..b239cad6 100644 --- 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 @@ -5,9 +5,9 @@ import java.util.List; 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.SpellAttributes; import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; -import com.minelittlepony.unicopia.entity.effect.EffectUtils; import com.minelittlepony.unicopia.entity.mob.UEntities; import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.item.FriendshipBraceletItem; @@ -17,7 +17,6 @@ import com.minelittlepony.unicopia.util.shape.Sphere; import net.minecraft.entity.Entity; import net.minecraft.text.Text; -import net.minecraft.util.Formatting; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3d; @@ -28,18 +27,20 @@ public class AreaProtectionSpell extends AbstractAreaEffectSpell { .build(); - static void appendTooltip(CustomisedSpellType type, List tooltip) { - float addedRange = type.traits().get(Trait.POWER); - if (addedRange != 0) { - tooltip.add(EffectUtils.formatModifierChange("spell.unicopia.shield.additional_range", addedRange, false)); - } - tooltip.add(Text.literal(" ").append(Text.translatable("spell.unicopia.shield.caston.location")).formatted(Formatting.GRAY)); + static void appendTooltip(CustomisedSpellType type, List tooltip) { + tooltip.add(SpellAttributes.CAST_ON_LOCATION); + tooltip.add(SpellAttributes.of(SpellAttributes.RANGE, 4 + type.traits().get(Trait.POWER))); } protected AreaProtectionSpell(CustomisedSpellType type) { super(type); } + /*@Override + public Spell prepareForCast(Caster caster, CastingMethod method) { + return method == CastingMethod.STAFF || getTraits().get(Trait.GENEROSITY) > 0 ? toPlaceable() : this; + }*/ + @Override public boolean tick(Caster source, Situation situation) { @@ -70,7 +71,7 @@ public class AreaProtectionSpell extends AbstractAreaEffectSpell { private double getRange(Caster source) { float multiplier = source instanceof Pony pony && pony.asEntity().isSneaking() ? 1 : 2; - float min = 4 + getTraits().get(Trait.POWER); + float min = 4 + getAdditionalRange(); double range = (min + (source.getLevel().getScaled(4) * 2)) / multiplier; if (source instanceof Pony && range > 2) { range = Math.sqrt(range); diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AttractiveSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AttractiveSpell.java index 9af5cddb..c951cb9f 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AttractiveSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AttractiveSpell.java @@ -1,5 +1,7 @@ package com.minelittlepony.unicopia.ability.magic.spell.effect; +import java.util.List; + import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.spell.*; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; @@ -13,9 +15,9 @@ import com.minelittlepony.unicopia.projectile.ProjectileDelegate; import com.minelittlepony.unicopia.util.shape.Sphere; import net.minecraft.entity.Entity; -import net.minecraft.entity.ItemEntity; import net.minecraft.nbt.NbtCompound; import net.minecraft.particle.ParticleTypes; +import net.minecraft.text.Text; import net.minecraft.util.hit.EntityHitResult; import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3d; @@ -26,9 +28,20 @@ public class AttractiveSpell extends ShieldSpell implements HomingSpell, TimedSp private final Timer timer; + public static void appendTooltip2(CustomisedSpellType type, List tooltip) { + TimedSpell.appendDurationTooltip(type, tooltip); + AbstractAreaEffectSpell.appendRangeTooltip(type, tooltip); + if (type.traits().get(Trait.ORDER) >= 20) { + tooltip.add(SpellAttributes.TARGET_ENTITY); + } else { + appendValidTargetsTooltip(type, tooltip); + } + appendCastLocationTooltip(type, tooltip); + } + protected AttractiveSpell(CustomisedSpellType type) { super(type); - timer = new Timer((120 + (int)(getTraits().get(Trait.FOCUS, 0, 160) * 19)) * 20); + timer = new Timer(BASE_DURATION + TimedSpell.getExtraDuration(getTraits())); } @Override @@ -73,17 +86,12 @@ public class AttractiveSpell extends ShieldSpell implements HomingSpell, TimedSp }); } - @Override - public double getDrawDropOffRange(Caster caster) { - return 10 + (caster.getLevel().getScaled(8) * 2); - } - @Override protected boolean isValidTarget(Caster source, Entity entity) { if (target.referenceEquals(entity)) { return true; } - return getTraits().get(Trait.KNOWLEDGE) > 10 ? entity instanceof ItemEntity : super.isValidTarget(source, entity); + return super.isValidTarget(source, entity); } @Override diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/BubbleSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/BubbleSpell.java index c35227af..925a8c5a 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/BubbleSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/BubbleSpell.java @@ -1,5 +1,6 @@ package com.minelittlepony.unicopia.ability.magic.spell.effect; +import java.util.List; import java.util.Map; import java.util.UUID; @@ -22,6 +23,7 @@ import net.minecraft.entity.attribute.*; import net.minecraft.entity.attribute.EntityAttributeModifier.Operation; import net.minecraft.nbt.NbtCompound; import net.minecraft.particle.ParticleTypes; +import net.minecraft.text.Text; import net.minecraft.util.hit.EntityHitResult; import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3d; @@ -46,6 +48,11 @@ public class BubbleSpell extends AbstractSpell implements TimedSpell, .with(Trait.POWER, 1) .build(); + static void appendTooltip(CustomisedSpellType type, List tooltip) { + TimedSpell.appendDurationTooltip(type, tooltip); + tooltip.add(SpellAttributes.of(SpellAttributes.SOAPINESS, (int)(type.traits().get(Trait.POWER) * 2))); + } + private final Timer timer; private int struggles; @@ -55,7 +62,7 @@ public class BubbleSpell extends AbstractSpell implements TimedSpell, protected BubbleSpell(CustomisedSpellType type) { super(type); - timer = new Timer((120 + (int)(getTraits().get(Trait.FOCUS, 0, 160) * 19)) * 20); + timer = new Timer(BASE_DURATION + TimedSpell.getExtraDuration(getTraits())); struggles = (int)(getTraits().get(Trait.POWER) * 2); } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/CatapultSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/CatapultSpell.java index ce440570..4c9c1dd7 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/CatapultSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/CatapultSpell.java @@ -1,5 +1,6 @@ package com.minelittlepony.unicopia.ability.magic.spell.effect; +import java.util.List; import java.util.function.Consumer; import org.jetbrains.annotations.Nullable; @@ -7,6 +8,7 @@ import org.jetbrains.annotations.Nullable; import com.minelittlepony.unicopia.UTags; import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.spell.Situation; +import com.minelittlepony.unicopia.ability.magic.spell.SpellAttributes; import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.mixin.MixinFallingBlockEntity; @@ -17,6 +19,7 @@ import com.minelittlepony.unicopia.projectile.ProjectileDelegate; import net.minecraft.block.BlockState; import net.minecraft.entity.Entity; import net.minecraft.entity.FallingBlockEntity; +import net.minecraft.text.Text; import net.minecraft.util.hit.BlockHitResult; import net.minecraft.util.hit.EntityHitResult; import net.minecraft.util.math.BlockPos; @@ -37,6 +40,11 @@ public class CatapultSpell extends AbstractSpell implements ProjectileDelegate.B private static final float HORIZONTAL_VARIANCE = 0.25F; private static final float MAX_STRENGTH = 120; + static void appendTooltip(CustomisedSpellType type, List tooltip) { + float velocity = 0.1F + type.relativeTraits().get(Trait.STRENGTH, -MAX_STRENGTH, MAX_STRENGTH); + tooltip.add(SpellAttributes.of(SpellAttributes.VERTICAL_VELOCITY, velocity / 16F)); + } + protected CatapultSpell(CustomisedSpellType type) { super(type); } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/CustomisedSpellType.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/CustomisedSpellType.java index 9a62d4db..8c7f1b61 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/CustomisedSpellType.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/CustomisedSpellType.java @@ -2,6 +2,7 @@ package com.minelittlepony.unicopia.ability.magic.spell.effect; import java.util.ArrayList; import java.util.List; +import java.util.function.Supplier; import org.jetbrains.annotations.Nullable; @@ -23,7 +24,8 @@ import net.minecraft.util.TypedActionResult; public record CustomisedSpellType ( SpellType type, - SpellTraits traits + SpellTraits traits, + Supplier traitsDifferenceSupplier ) implements SpellPredicate { public boolean isEmpty() { @@ -34,6 +36,10 @@ public record CustomisedSpellType ( return type().isStackable(); } + public SpellTraits relativeTraits() { + return traitsDifferenceSupplier.get(); + } + public T create() { try { return type.getFactory().create(this); diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/DispellEvilSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/DispellEvilSpell.java index 869ef51d..760f222f 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/DispellEvilSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/DispellEvilSpell.java @@ -1,5 +1,7 @@ package com.minelittlepony.unicopia.ability.magic.spell.effect; +import java.util.List; + import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.spell.*; import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; @@ -11,6 +13,7 @@ import com.minelittlepony.unicopia.projectile.ProjectileDelegate; import net.minecraft.entity.EntityType; import net.minecraft.entity.LivingEntity; +import net.minecraft.text.Text; import net.minecraft.util.math.Vec3d; public class DispellEvilSpell extends AbstractSpell implements ProjectileDelegate.HitListener { @@ -18,6 +21,10 @@ public class DispellEvilSpell extends AbstractSpell implements ProjectileDelegat .with(Trait.POWER, 1) .build(); + static void appendTooltip(CustomisedSpellType type, List tooltip) { + tooltip.add(SpellAttributes.of(SpellAttributes.RANGE, (1 + type.relativeTraits().get(Trait.POWER)) * 10)); + } + protected DispellEvilSpell(CustomisedSpellType type) { super(type); } @@ -28,7 +35,7 @@ public class DispellEvilSpell extends AbstractSpell implements ProjectileDelegat return !isDead(); } - source.findAllEntitiesInRange(getTraits().get(Trait.POWER) * 10, e -> e.getType() == EntityType.PHANTOM).forEach(entity -> { + source.findAllEntitiesInRange((1 + getTraits().get(Trait.POWER)) * 10, e -> e.getType() == EntityType.PHANTOM).forEach(entity -> { entity.damage(entity.getDamageSources().magic(), 50); if (entity instanceof LivingEntity l) { double d = source.getOriginVector().getX() - entity.getX(); diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/DisperseIllusionSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/DisperseIllusionSpell.java index 3d88d40f..f67ceb2b 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/DisperseIllusionSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/DisperseIllusionSpell.java @@ -1,20 +1,29 @@ package com.minelittlepony.unicopia.ability.magic.spell.effect; +import java.util.List; + import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.SpellPredicate; import com.minelittlepony.unicopia.ability.magic.spell.AbstractAreaEffectSpell; import com.minelittlepony.unicopia.ability.magic.spell.Situation; +import com.minelittlepony.unicopia.ability.magic.spell.SpellAttributes; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.particle.MagicParticleEffect; import com.minelittlepony.unicopia.util.shape.Sphere; +import net.minecraft.text.Text; import net.minecraft.util.math.Vec3d; /** * An area-effect spell that disperses illusions. */ public class DisperseIllusionSpell extends AbstractAreaEffectSpell { + public static void appendTooltip(CustomisedSpellType type, List tooltip) { + tooltip.add(SpellAttributes.of(SpellAttributes.RANGE, 15 + type.traits().get(Trait.POWER))); + tooltip.add(SpellAttributes.ofTime(SpellAttributes.DURATION, (1 + (long)type.traits().get(Trait.STRENGTH)) * 100)); + } + protected DisperseIllusionSpell(CustomisedSpellType type) { super(type); } @@ -22,7 +31,7 @@ public class DisperseIllusionSpell extends AbstractAreaEffectSpell { @Override public boolean tick(Caster source, Situation situation) { - float range = Math.max(0, 15 + getTraits().get(Trait.POWER)); + float range = Math.max(0, 15 + getAdditionalRange()); if (range == 0) { return false; @@ -41,7 +50,7 @@ public class DisperseIllusionSpell extends AbstractAreaEffectSpell { e.getSpellSlot().get(SpellPredicate.CAN_SUPPRESS) .filter(spell -> spell.isVulnerable(source, this)) .ifPresent(spell -> { - spell.onSuppressed(source, 1 + getTraits().get(Trait.STRENGTH)); + spell.onSuppressed(source, 100 * (1 + getTraits().get(Trait.STRENGTH))); e.playSound(USounds.SPELL_ILLUSION_DISPERSE, 0.2F, 0.5F); }); }); diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/DisplacementSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/DisplacementSpell.java index b787778b..923701ce 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/DisplacementSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/DisplacementSpell.java @@ -1,5 +1,7 @@ package com.minelittlepony.unicopia.ability.magic.spell.effect; +import java.util.List; + import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.spell.*; @@ -11,11 +13,19 @@ import com.minelittlepony.unicopia.projectile.ProjectileDelegate; import net.minecraft.entity.Entity; import net.minecraft.nbt.NbtCompound; +import net.minecraft.text.Text; import net.minecraft.util.hit.EntityHitResult; import net.minecraft.util.math.Vec3d; public class DisplacementSpell extends AbstractSpell implements HomingSpell, ProjectileDelegate.EntityHitListener { + static void appendTooltip(CustomisedSpellType type, List tooltip) { + float damage = type.traits().get(Trait.BLOOD); + if (damage > 0) { + tooltip.add(SpellAttributes.ofRelative(SpellAttributes.DAMAGE_TO_TARGET, damage)); + } + } + private final EntityReference target = new EntityReference<>(); private int ticks = 10; diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/FeatherFallSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/FeatherFallSpell.java index 40fd8413..eb2bc744 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/FeatherFallSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/FeatherFallSpell.java @@ -5,6 +5,7 @@ import java.util.stream.Stream; import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.spell.Situation; +import com.minelittlepony.unicopia.ability.magic.spell.SpellAttributes; import com.minelittlepony.unicopia.ability.magic.spell.TimedSpell; import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; @@ -15,6 +16,7 @@ import com.minelittlepony.unicopia.particle.ParticleUtils; import net.minecraft.entity.Entity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.nbt.NbtCompound; +import net.minecraft.text.Text; import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3d; @@ -36,6 +38,17 @@ public class FeatherFallSpell extends AbstractSpell implements TimedSpell { .with(Trait.ORDER, 15) .build(); + public static void appendTooltip(CustomisedSpellType type, List tooltip) { + tooltip.add(SpellAttributes.ofTime(SpellAttributes.DURATION, 10 + (int)(type.traits().get(Trait.FOCUS, 0, 160)))); + tooltip.add(SpellAttributes.of(SpellAttributes.STRENGTH, type.traits().get(Trait.STRENGTH, 2, 9))); + tooltip.add(SpellAttributes.of(SpellAttributes.RANGE, (float)getEffectRange(type.traits()))); + tooltip.add(SpellAttributes.of(SpellAttributes.SIMULTANIOUS_TARGETS, getMaxTargets(type.traits()))); + tooltip.add(SpellAttributes.of(SpellAttributes.COST_PER_INDIVIDUAL, (float)getCostPerEntity(type.traits()))); + float generosity = type.traits().get(Trait.GENEROSITY, 1, MAX_GENEROSITY_FACTOR) / MAX_GENEROSITY_FACTOR; + tooltip.add(SpellAttributes.of(SpellAttributes.TARGET_PREFERENCE, (int)(generosity * 100))); + tooltip.add(SpellAttributes.of(SpellAttributes.CASTER_PREFERENCE, (int)((1 - generosity) * 100))); + } + private final Timer timer; protected FeatherFallSpell(CustomisedSpellType type) { @@ -91,35 +104,33 @@ public class FeatherFallSpell extends AbstractSpell implements TimedSpell { ParticleUtils.spawnParticles(new MagicParticleEffect(getType().getColor()), target, 7); }); - return caster.subtractEnergyCost(timer.getTicksRemaining() % 50 == 0 ? getCostPerEntity() * targets.size() : 0); + return caster.subtractEnergyCost(timer.getTicksRemaining() % 50 == 0 ? getCostPerEntity(getTraits()) * targets.size() : 0); } - protected double getCostPerEntity() { - float focus = Math.max(getTraits().get(Trait.FOCUS), 80) - 80; - float power = Math.max(getTraits().get(Trait.POWER), 10) - 10; + protected static double getCostPerEntity(SpellTraits traits) { + float focus = Math.max(traits.get(Trait.FOCUS), 80) - 80; + float power = Math.max(traits.get(Trait.POWER), 10) - 10; return MathHelper.clamp((power * POWERS_RANGE_WEIGHT) - (focus * FOCUS_RANGE_WEIGHT), 1, 7); } - protected double getEffectRange() { - float power = getTraits().get(Trait.POWER) - 10; - - return MathHelper.clamp(power * POWERS_RANGE_WEIGHT, MIN_RANGE, MAX_RANGE); + protected static double getEffectRange(SpellTraits traits) { + return MathHelper.clamp((traits.get(Trait.POWER) - 10) * POWERS_RANGE_WEIGHT, MIN_RANGE, MAX_RANGE); } - protected long getMaxTargets() { - long generosity = (long)getTraits().get(Trait.GENEROSITY) * 2L; - long focus = (long)getTraits().get(Trait.FOCUS, MIN_TARGETS, MAX_TARGETS) * 2L; + protected static long getMaxTargets(SpellTraits traits) { + long generosity = (long)traits.get(Trait.GENEROSITY) * 2L; + long focus = (long)traits.get(Trait.FOCUS, MIN_TARGETS, MAX_TARGETS) * 2L; return generosity + focus; } protected Stream getTargets(Caster caster) { - return Stream.concat(Stream.of(caster.asEntity()), caster.findAllEntitiesInRange(getEffectRange()).sorted((a, b) -> { + return Stream.concat(Stream.of(caster.asEntity()), caster.findAllEntitiesInRange(getEffectRange(getTraits())).sorted((a, b) -> { return Integer.compare( FriendshipBraceletItem.isComrade(caster, a) ? 1 : 0, FriendshipBraceletItem.isComrade(caster, b) ? 1 : 0 ); - }).distinct()).limit(getMaxTargets()); + }).distinct()).limit(getMaxTargets(getTraits())); } @Override diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/FireBoltSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/FireBoltSpell.java index db63eaf4..47bdf918 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/FireBoltSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/FireBoltSpell.java @@ -1,9 +1,12 @@ package com.minelittlepony.unicopia.ability.magic.spell.effect; +import java.util.List; + import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.spell.HomingSpell; import com.minelittlepony.unicopia.ability.magic.spell.Situation; +import com.minelittlepony.unicopia.ability.magic.spell.SpellAttributes; import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.entity.EntityReference; @@ -14,6 +17,7 @@ import net.minecraft.entity.Entity; import net.minecraft.item.Items; import net.minecraft.nbt.NbtCompound; import net.minecraft.predicate.entity.EntityPredicates; +import net.minecraft.text.Text; import net.minecraft.util.hit.EntityHitResult; public class FireBoltSpell extends AbstractSpell implements HomingSpell, @@ -31,6 +35,19 @@ public class FireBoltSpell extends AbstractSpell implements HomingSpell, .with(Trait.FIRE, 60) .build(); + public static void appendTooltip(CustomisedSpellType type, List tooltip) { + tooltip.add(SpellAttributes.of(SpellAttributes.EXPLOSION_STRENGTH, type.traits().get(Trait.POWER, 0, type.traits().get(Trait.FOCUS) >= 50 ? 500 : 50) / 10F)); + tooltip.add(SpellAttributes.of(SpellAttributes.VELOCITY, 1.3F + type.traits().get(Trait.STRENGTH) / 11F)); + tooltip.add(SpellAttributes.of(SpellAttributes.PROJECTILE_COUNT, 1 + (int)type.traits().get(Trait.EARTH) * 3)); + + float homingRange = type.traits().get(Trait.FOCUS); + + if (homingRange >= 50) { + tooltip.add(SpellAttributes.FOLLOWS_TARGET); + tooltip.add(SpellAttributes.of(SpellAttributes.FOLLOW_RANGE, homingRange - 50)); + } + } + private final EntityReference target = new EntityReference<>(); protected FireBoltSpell(CustomisedSpellType type) { diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/FireSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/FireSpell.java index 8dade44f..f633c952 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/FireSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/FireSpell.java @@ -1,9 +1,12 @@ package com.minelittlepony.unicopia.ability.magic.spell.effect; +import java.util.List; + import com.minelittlepony.unicopia.EquinePredicates; import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.spell.Situation; +import com.minelittlepony.unicopia.ability.magic.spell.SpellAttributes; import com.minelittlepony.unicopia.ability.magic.spell.AbstractAreaEffectSpell; import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; @@ -25,6 +28,7 @@ import net.minecraft.entity.damage.DamageTypes; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.particle.ParticleTypes; import net.minecraft.sound.SoundCategory; +import net.minecraft.text.Text; import net.minecraft.registry.tag.BlockTags; import net.minecraft.util.hit.BlockHitResult; import net.minecraft.util.hit.EntityHitResult; @@ -41,6 +45,10 @@ public class FireSpell extends AbstractAreaEffectSpell implements ProjectileDele .with(Trait.FIRE, 15) .build(); + public static void appendTooltip(CustomisedSpellType type, List tooltip) { + tooltip.add(SpellAttributes.of(SpellAttributes.RANGE, 4 + type.traits().get(Trait.POWER))); + } + protected FireSpell(CustomisedSpellType type) { super(type); } @@ -67,14 +75,14 @@ public class FireSpell extends AbstractAreaEffectSpell implements ProjectileDele generateParticles(source); } - return new Sphere(false, Math.max(0, 4 + getTraits().get(Trait.POWER))).translate(source.getOrigin()).getBlockPositions().reduce(false, + return new Sphere(false, Math.max(0, 4 + getAdditionalRange())).translate(source.getOrigin()).getBlockPositions().reduce(false, (r, i) -> source.canModifyAt(i) && applyBlocks(source.asWorld(), i), (a, b) -> a || b) || applyEntities(source, source.getOriginVector()); } protected void generateParticles(Caster source) { - source.spawnParticles(new Sphere(false, Math.max(0, 4 + getTraits().get(Trait.POWER))), (int)(1 + source.getLevel().getScaled(8)) * 6, pos -> { + source.spawnParticles(new Sphere(false, Math.max(0, 4 + getAdditionalRange())), (int)(1 + source.getLevel().getScaled(8)) * 6, pos -> { source.addParticle(ParticleTypes.LARGE_SMOKE, pos, Vec3d.ZERO); }); } @@ -122,7 +130,7 @@ public class FireSpell extends AbstractAreaEffectSpell implements ProjectileDele } protected boolean applyEntities(Caster source, Vec3d pos) { - return source.findAllEntitiesInRange(Math.max(0, 3 + getTraits().get(Trait.POWER)), e -> { + return source.findAllEntitiesInRange(Math.max(0, 3 + getAdditionalRange()), e -> { LivingEntity master = source.getMaster(); return (!(e.equals(source.asEntity()) || e.equals(master)) || (master instanceof PlayerEntity && !EquinePredicates.PLAYER_UNICORN.test(master))) && !(e instanceof ItemEntity) diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/HydrophobicSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/HydrophobicSpell.java index cec7e61a..6ec3554e 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/HydrophobicSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/HydrophobicSpell.java @@ -1,6 +1,7 @@ package com.minelittlepony.unicopia.ability.magic.spell.effect; import java.util.HashSet; +import java.util.List; import java.util.Set; import com.minelittlepony.unicopia.USounds; @@ -8,6 +9,7 @@ import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.spell.CastingMethod; import com.minelittlepony.unicopia.ability.magic.spell.Situation; import com.minelittlepony.unicopia.ability.magic.spell.Spell; +import com.minelittlepony.unicopia.ability.magic.spell.SpellAttributes; import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.advancement.UCriteria; @@ -22,6 +24,7 @@ import net.minecraft.block.*; import net.minecraft.fluid.*; import net.minecraft.nbt.*; import net.minecraft.state.property.Properties; +import net.minecraft.text.Text; import net.minecraft.registry.tag.TagKey; import net.minecraft.server.world.ServerWorld; import net.minecraft.util.math.BlockPos; @@ -34,6 +37,10 @@ public class HydrophobicSpell extends AbstractSpell { .with(Trait.FOCUS, 5) .with(Trait.KNOWLEDGE, 1) .build(); + static void appendTooltip(CustomisedSpellType type, List tooltip) { + ShieldSpell.appendCastLocationTooltip(type, tooltip); + tooltip.add(SpellAttributes.of(SpellAttributes.RANGE, 4 + type.traits().get(Trait.POWER))); + } private final TagKey affectedFluid; diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/LightSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/LightSpell.java index 9688b7f7..48c99c0c 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/LightSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/LightSpell.java @@ -6,6 +6,7 @@ import java.util.List; import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.spell.CastingMethod; import com.minelittlepony.unicopia.ability.magic.spell.Situation; +import com.minelittlepony.unicopia.ability.magic.spell.SpellAttributes; import com.minelittlepony.unicopia.ability.magic.spell.TimedSpell; import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; @@ -20,6 +21,7 @@ import com.minelittlepony.unicopia.util.VecHelper; import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtElement; import net.minecraft.nbt.NbtList; +import net.minecraft.text.Text; public class LightSpell extends AbstractSpell implements TimedSpell, ProjectileDelegate.HitListener { public static final SpellTraits DEFAULT_TRAITS = new SpellTraits.Builder() @@ -29,13 +31,18 @@ public class LightSpell extends AbstractSpell implements TimedSpell, ProjectileD .with(Trait.ORDER, 25) .build(); + public static void appendTooltip(CustomisedSpellType type, List tooltip) { + TimedSpell.appendDurationTooltip(type, tooltip); + tooltip.add(SpellAttributes.of(SpellAttributes.ORB_COUNT, 2 + (int)(type.relativeTraits().get(Trait.LIFE, 10, 20) / 10F))); + } + private final Timer timer; private final List> lights = new ArrayList<>(); protected LightSpell(CustomisedSpellType type) { super(type); - timer = new Timer((120 + (int)(getTraits().get(Trait.FOCUS, 0, 160) * 19)) * 20); + timer = new Timer(BASE_DURATION + TimedSpell.getExtraDuration(getTraits())); } @Override diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/MimicSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/MimicSpell.java index b48866b7..12032913 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/MimicSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/MimicSpell.java @@ -1,18 +1,25 @@ package com.minelittlepony.unicopia.ability.magic.spell.effect; +import java.util.List; + import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.spell.*; -import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import net.minecraft.entity.Entity; import net.minecraft.nbt.NbtCompound; +import net.minecraft.text.Text; public class MimicSpell extends AbstractDisguiseSpell implements HomingSpell, TimedSpell { + static final int BASE_DURATION = 120 * 20; + + public static void appendTooltip(CustomisedSpellType type, List tooltip) { + TimedSpell.appendDurationTooltip(type, tooltip); + } private final Timer timer; protected MimicSpell(CustomisedSpellType type) { super(type); - timer = new Timer((120 + (int)(getTraits().get(Trait.FOCUS, 0, 160) * 19)) * 20); + timer = new Timer(BASE_DURATION + TimedSpell.getExtraDuration(getTraits())); } @Override diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/NecromancySpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/NecromancySpell.java index 910fddd3..b1209943 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/NecromancySpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/NecromancySpell.java @@ -8,6 +8,7 @@ import com.minelittlepony.unicopia.USounds; 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.SpellAttributes; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.entity.Creature; import com.minelittlepony.unicopia.entity.EntityReference; @@ -31,6 +32,7 @@ import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtElement; import net.minecraft.nbt.NbtList; import net.minecraft.particle.ParticleTypes; +import net.minecraft.text.Text; import net.minecraft.util.hit.BlockHitResult; import net.minecraft.util.hit.EntityHitResult; import net.minecraft.util.math.BlockPos; @@ -79,6 +81,11 @@ public class NecromancySpell extends AbstractAreaEffectSpell implements Projecti return e -> e.getType() == type; } + static void appendTooltip(CustomisedSpellType type, List tooltip) { + tooltip.add(SpellAttributes.of(SpellAttributes.RANGE, 4 + type.traits().get(Trait.POWER))); + tooltip.add(SpellAttributes.of(SpellAttributes.WAVE_SIZE, 10 + (int)type.traits().get(Trait.CHAOS, 0, 10))); + } + private final List> summonedEntities = new ArrayList<>(); private int spawnCountdown; @@ -90,7 +97,7 @@ public class NecromancySpell extends AbstractAreaEffectSpell implements Projecti @Override public boolean tick(Caster source, Situation situation) { - float radius = 4 + source.getLevel().getScaled(4) * 4 + getTraits().get(Trait.POWER); + float radius = 4 + source.getLevel().getScaled(4) * 4 + getAdditionalRange(); if (radius <= 0) { return false; diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/ScorchSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/ScorchSpell.java index 24c8a632..e64aa6fa 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/ScorchSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/ScorchSpell.java @@ -36,7 +36,7 @@ public class ScorchSpell extends FireSpell implements ProjectileDelegate.Configu BlockPos pos = PosHelper.findSolidGroundAt(source.asWorld(), source.getOrigin(), source.getPhysics().getGravitySignum()); if (source.canModifyAt(pos) && StateMaps.FIRE_AFFECTED.convert(source.asWorld(), pos)) { - source.spawnParticles(new Sphere(false, Math.max(1, getTraits().get(Trait.POWER))), 5, p -> { + source.spawnParticles(new Sphere(false, Math.max(1, getAdditionalRange())), 5, p -> { source.addParticle(ParticleTypes.SMOKE, PosHelper.offset(p, pos), Vec3d.ZERO); }); } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/ShieldSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/ShieldSpell.java index 541f1033..705fb4b0 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/ShieldSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/ShieldSpell.java @@ -7,13 +7,14 @@ import com.minelittlepony.unicopia.Affinity; import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.Unicopia; import com.minelittlepony.unicopia.ability.magic.Caster; +import com.minelittlepony.unicopia.ability.magic.spell.AbstractAreaEffectSpell; import com.minelittlepony.unicopia.ability.magic.spell.CastingMethod; import com.minelittlepony.unicopia.ability.magic.spell.Situation; import com.minelittlepony.unicopia.ability.magic.spell.Spell; +import com.minelittlepony.unicopia.ability.magic.spell.SpellAttributes; import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.client.minelittlepony.MineLPDelegate; -import com.minelittlepony.unicopia.entity.effect.EffectUtils; import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.particle.LightningBoltParticleEffect; import com.minelittlepony.unicopia.particle.MagicParticleEffect; @@ -27,6 +28,7 @@ import com.minelittlepony.unicopia.util.shape.Sphere; import net.minecraft.entity.Entity; import net.minecraft.entity.EyeOfEnderEntity; import net.minecraft.entity.FallingBlockEntity; +import net.minecraft.entity.ItemEntity; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.TntEntity; import net.minecraft.entity.Entity.RemovalReason; @@ -37,7 +39,6 @@ import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.vehicle.AbstractMinecartEntity; import net.minecraft.entity.vehicle.BoatEntity; import net.minecraft.text.Text; -import net.minecraft.util.Formatting; import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3d; @@ -49,27 +50,32 @@ public class ShieldSpell extends AbstractSpell { .with(Trait.AIR, 9) .build(); - static void appendTooltip(CustomisedSpellType type, List tooltip) { - float addedRange = type.traits().get(Trait.POWER); - if (addedRange != 0) { - tooltip.add(EffectUtils.formatModifierChange("spell.unicopia.shield.additional_range", addedRange, false)); - } - if (type.traits().get(Trait.LIFE) > 0) { - tooltip.add(Text.literal(" ").append(Text.translatable("spell.unicopia.shield.permit.passive")).formatted(Formatting.GRAY)); - } - if (type.traits().get(Trait.BLOOD) > 0) { - tooltip.add(Text.literal(" ").append(Text.translatable("spell.unicopia.shield.permit.hostile")).formatted(Formatting.GRAY)); - } - if (type.traits().get(Trait.ICE) > 0) { - tooltip.add(Text.literal(" ").append(Text.translatable("spell.unicopia.shield.permit.player")).formatted(Formatting.GRAY)); - } - if (type.traits().get(Trait.GENEROSITY) > 0) { - tooltip.add(Text.literal(" ").append(Text.translatable("spell.unicopia.shield.caston.location")).formatted(Formatting.GRAY)); + static void appendTooltip(CustomisedSpellType type, List tooltip) { + AbstractAreaEffectSpell.appendRangeTooltip(type, tooltip); + appendValidTargetsTooltip(type, tooltip); + appendCastLocationTooltip(type, tooltip); + } + + static void appendValidTargetsTooltip(CustomisedSpellType type, List tooltip) { + if (type.traits().get(Trait.KNOWLEDGE) > 10) { + tooltip.add(SpellAttributes.PERMIT_ITEMS); } else { - tooltip.add(Text.literal(" ").append(Text.translatable("spell.unicopia.shield.caston.person")).formatted(Formatting.GRAY)); + if (type.traits().get(Trait.LIFE) > 0) { + tooltip.add(SpellAttributes.PERMIT_PASSIVE); + } + if (type.traits().get(Trait.BLOOD) > 0) { + tooltip.add(SpellAttributes.PERMIT_HOSTILE); + } + if (type.traits().get(Trait.ICE) > 0) { + tooltip.add(SpellAttributes.PERMIT_PLAYER); + } } } + static void appendCastLocationTooltip(CustomisedSpellType type, List tooltip) { + tooltip.add(type.traits().get(Trait.GENEROSITY) > 0 ? SpellAttributes.CAST_ON_LOCATION : SpellAttributes.CAST_ON_PERSON); + } + protected final TargetSelecter targetSelecter = new TargetSelecter(this).setFilter(this::isValidTarget); private final Lerp radius = new Lerp(0); @@ -177,6 +183,11 @@ public class ShieldSpell extends AbstractSpell { } protected boolean isValidTarget(Caster source, Entity entity) { + + if (getTraits().get(Trait.KNOWLEDGE) > 10) { + return entity instanceof ItemEntity; + } + boolean valid = (entity instanceof LivingEntity || entity instanceof TntEntity || entity instanceof FallingBlockEntity diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/SiphoningSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/SiphoningSpell.java index f6f45d05..42e784d1 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/SiphoningSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/SiphoningSpell.java @@ -12,6 +12,7 @@ import com.minelittlepony.unicopia.Race; 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.SpellAttributes; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.entity.damage.UDamageTypes; import com.minelittlepony.unicopia.entity.player.Pony; @@ -28,6 +29,7 @@ import net.minecraft.entity.player.PlayerEntity; import net.minecraft.nbt.NbtCompound; import net.minecraft.particle.ParticleTypes; import net.minecraft.predicate.entity.EntityPredicates; +import net.minecraft.text.Text; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3d; @@ -37,6 +39,10 @@ import net.minecraft.util.math.Vec3d; public class SiphoningSpell extends AbstractAreaEffectSpell { static final Predicate TARGET_PREDICATE = EntityPredicates.EXCEPT_CREATIVE_OR_SPECTATOR.and(EntityPredicates.VALID_LIVING_ENTITY); + static void appendTooltip(CustomisedSpellType type, List tooltip) { + tooltip.add(SpellAttributes.of(SpellAttributes.RANGE, 4)); + } + private int ticksUpset; protected SiphoningSpell(CustomisedSpellType type) { @@ -56,7 +62,7 @@ public class SiphoningSpell extends AbstractAreaEffectSpell { } if (source.isClient()) { - float radius = 4 + source.getLevel().getScaled(5); + float radius = 4 + source.getLevel().getScaled(5) + getAdditionalRange(); int direction = isFriendlyTogether(source) ? 1 : -1; source.spawnParticles(new Sphere(true, radius, 1, 0, 1), 1, pos -> { 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 617fcf07..0490c0e0 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 @@ -4,6 +4,7 @@ import java.util.List; import java.util.function.BiConsumer; import org.jetbrains.annotations.Nullable; +import com.google.common.base.Suppliers; import com.minelittlepony.unicopia.Affinity; import com.minelittlepony.unicopia.Unicopia; import com.minelittlepony.unicopia.ability.magic.Affine; @@ -54,29 +55,29 @@ public final class SpellType implements Affine, SpellPredicate< public static final SpellType FROST = register("frost", builder(IceSpell::new).color(0xEABBFF).shape(GemstoneItem.Shape.TRIANGLE).traits(IceSpell.DEFAULT_TRAITS)); public static final SpellType CHILLING_BREATH = register("chilling_breath", builder(ChillingBreathSpell::new).affinity(Affinity.NEUTRAL).color(0xFFEAFF).shape(GemstoneItem.Shape.TRIANGLE).traits(ChillingBreathSpell.DEFAULT_TRAITS)); - public static final SpellType SCORCH = register("scorch", builder(ScorchSpell::new).affinity(Affinity.BAD).color(0xF8EC1F).stackable().shape(GemstoneItem.Shape.FLAME).traits(ScorchSpell.DEFAULT_TRAITS)); - public static final SpellType FLAME = register("flame", builder(FireSpell::new).color(0xFFBB99).shape(GemstoneItem.Shape.FLAME).traits(FireSpell.DEFAULT_TRAITS)); - public static final SpellType INFERNAL = register("infernal", builder(InfernoSpell::new).affinity(Affinity.BAD).color(0xFFAA00).shape(GemstoneItem.Shape.FLAME).traits(InfernoSpell.DEFAULT_TRAITS)); + public static final SpellType SCORCH = register("scorch", builder(ScorchSpell::new).affinity(Affinity.BAD).color(0xF8EC1F).stackable().shape(GemstoneItem.Shape.FLAME).traits(ScorchSpell.DEFAULT_TRAITS).tooltip(FireSpell::appendTooltip)); + public static final SpellType FLAME = register("flame", builder(FireSpell::new).color(0xFFBB99).shape(GemstoneItem.Shape.FLAME).traits(FireSpell.DEFAULT_TRAITS).tooltip(FireSpell::appendTooltip)); + public static final SpellType INFERNAL = register("infernal", builder(InfernoSpell::new).affinity(Affinity.BAD).color(0xFFAA00).shape(GemstoneItem.Shape.FLAME).traits(InfernoSpell.DEFAULT_TRAITS).tooltip(FireSpell::appendTooltip)); public static final SpellType SHIELD = register("shield", builder(ShieldSpell::new).affinity(Affinity.NEUTRAL).color(0x66CDAA).shape(GemstoneItem.Shape.SHIELD).traits(ShieldSpell.DEFAULT_TRAITS).tooltip(ShieldSpell::appendTooltip)); - public static final SpellType ARCANE_PROTECTION = register("arcane_protection", builder(AreaProtectionSpell::new).affinity(Affinity.BAD).color(0x99CDAA).shape(GemstoneItem.Shape.SHIELD).traits(AreaProtectionSpell.DEFAULT_TRAITS)); - public static final SpellType VORTEX = register("vortex", builder(AttractiveSpell::new).affinity(Affinity.NEUTRAL).color(0xFFEA88).shape(GemstoneItem.Shape.VORTEX).traits(AttractiveSpell.DEFAULT_TRAITS)); + public static final SpellType ARCANE_PROTECTION = register("arcane_protection", builder(AreaProtectionSpell::new).affinity(Affinity.BAD).color(0x99CDAA).shape(GemstoneItem.Shape.SHIELD).traits(AreaProtectionSpell.DEFAULT_TRAITS).tooltip(AreaProtectionSpell::appendTooltip)); + public static final SpellType VORTEX = register("vortex", builder(AttractiveSpell::new).affinity(Affinity.NEUTRAL).color(0xFFEA88).shape(GemstoneItem.Shape.VORTEX).traits(AttractiveSpell.DEFAULT_TRAITS).tooltip(AttractiveSpell::appendTooltip2)); public static final SpellType DARK_VORTEX = register("dark_vortex", builder(DarkVortexSpell::new).affinity(Affinity.BAD).color(0xA33333).stackable().shape(GemstoneItem.Shape.VORTEX).traits(DarkVortexSpell.DEFAULT_TRAITS)); - public static final SpellType NECROMANCY = register("necromancy", builder(NecromancySpell::new).affinity(Affinity.BAD).color(0xFA3A3A).shape(GemstoneItem.Shape.SKULL)); - public static final SpellType SIPHONING = register("siphoning", builder(SiphoningSpell::new).affinity(Affinity.NEUTRAL).color(0xFFA3AA).shape(GemstoneItem.Shape.LAMBDA)); - public static final SpellType REVEALING = register("reveal", builder(DisperseIllusionSpell::new).color(0xFFFFAF).shape(GemstoneItem.Shape.CROSS)); + public static final SpellType NECROMANCY = register("necromancy", builder(NecromancySpell::new).affinity(Affinity.BAD).color(0xFA3A3A).shape(GemstoneItem.Shape.SKULL).tooltip(NecromancySpell::appendTooltip)); + public static final SpellType SIPHONING = register("siphoning", builder(SiphoningSpell::new).affinity(Affinity.NEUTRAL).color(0xFFA3AA).shape(GemstoneItem.Shape.LAMBDA).tooltip(SiphoningSpell::appendTooltip)); + public static final SpellType REVEALING = register("reveal", builder(DisperseIllusionSpell::new).color(0xFFFFAF).shape(GemstoneItem.Shape.CROSS).tooltip(DisperseIllusionSpell::appendTooltip)); public static final SpellType AWKWARD = register("awkward", builder(AwkwardSpell::new).affinity(Affinity.NEUTRAL).color(0x3A59FF).shape(GemstoneItem.Shape.ICE)); public static final SpellType TRANSFORMATION = register("transformation", builder(TransformationSpell::new).color(0x19E48E).shape(GemstoneItem.Shape.BRUSH)); - public static final SpellType FEATHER_FALL = register("feather_fall", builder(FeatherFallSpell::new).color(0x00EEFF).shape(GemstoneItem.Shape.LAMBDA).traits(FeatherFallSpell.DEFAULT_TRAITS)); - public static final SpellType CATAPULT = register("catapult", builder(CatapultSpell::new).color(0x22FF00).shape(GemstoneItem.Shape.ROCKET).traits(CatapultSpell.DEFAULT_TRAITS)); - public static final SpellType FIRE_BOLT = register("fire_bolt", builder(FireBoltSpell::new).color(0xFF8811).shape(GemstoneItem.Shape.FLAME).traits(FireBoltSpell.DEFAULT_TRAITS)); - public static final SpellType LIGHT = register("light", builder(LightSpell::new).color(0xEEFFAA).shape(GemstoneItem.Shape.STAR).traits(LightSpell.DEFAULT_TRAITS)); - public static final SpellType DISPLACEMENT = register("displacement", builder(DisplacementSpell::new).affinity(Affinity.NEUTRAL).color(0x9900FF).stackable().shape(GemstoneItem.Shape.BRUSH).traits(PortalSpell.DEFAULT_TRAITS)); + public static final SpellType FEATHER_FALL = register("feather_fall", builder(FeatherFallSpell::new).color(0x00EEFF).shape(GemstoneItem.Shape.LAMBDA).traits(FeatherFallSpell.DEFAULT_TRAITS).tooltip(FeatherFallSpell::appendTooltip)); + public static final SpellType CATAPULT = register("catapult", builder(CatapultSpell::new).color(0x22FF00).shape(GemstoneItem.Shape.ROCKET).traits(CatapultSpell.DEFAULT_TRAITS).tooltip(CatapultSpell::appendTooltip)); + public static final SpellType FIRE_BOLT = register("fire_bolt", builder(FireBoltSpell::new).color(0xFF8811).shape(GemstoneItem.Shape.FLAME).traits(FireBoltSpell.DEFAULT_TRAITS).tooltip(FireBoltSpell::appendTooltip)); + public static final SpellType LIGHT = register("light", builder(LightSpell::new).color(0xEEFFAA).shape(GemstoneItem.Shape.STAR).traits(LightSpell.DEFAULT_TRAITS).tooltip(LightSpell::appendTooltip)); + public static final SpellType DISPLACEMENT = register("displacement", builder(DisplacementSpell::new).affinity(Affinity.NEUTRAL).color(0x9900FF).stackable().shape(GemstoneItem.Shape.BRUSH).traits(PortalSpell.DEFAULT_TRAITS).tooltip(DisplacementSpell::appendTooltip)); public static final SpellType PORTAL = register("portal", builder(PortalSpell::new).color(0x99FFFF).shape(GemstoneItem.Shape.RING).traits(PortalSpell.DEFAULT_TRAITS)); - public static final SpellType MIMIC = register("mimic", builder(MimicSpell::new).color(0xFFFF00).shape(GemstoneItem.Shape.ARROW)); - public static final SpellType MIND_SWAP = register("mind_swap", builder(MindSwapSpell::new).affinity(Affinity.BAD).color(0xF9FF99).shape(GemstoneItem.Shape.WAVE)); - public static final SpellType HYDROPHOBIC = register("hydrophobic", SpellType.builder(s -> new HydrophobicSpell(s, FluidTags.WATER)).affinity(Affinity.NEUTRAL).color(0xF999FF).stackable().shape(GemstoneItem.Shape.ROCKET)); - public static final SpellType BUBBLE = register("bubble", builder(BubbleSpell::new).affinity(Affinity.NEUTRAL).color(0xF999FF).shape(GemstoneItem.Shape.DONUT).traits(BubbleSpell.DEFAULT_TRAITS)); - public static final SpellType DISPEL_EVIL = register("dispel_evil", builder(DispellEvilSpell::new).color(0x00FF00).shape(GemstoneItem.Shape.CROSS).traits(DispellEvilSpell.DEFAULT_TRAITS)); + public static final SpellType MIMIC = register("mimic", builder(MimicSpell::new).color(0xFFFF00).shape(GemstoneItem.Shape.ARROW).tooltip(MimicSpell::appendTooltip)); + public static final SpellType MIND_SWAP = register("mind_swap", builder(MindSwapSpell::new).affinity(Affinity.BAD).color(0xF9FF99).shape(GemstoneItem.Shape.WAVE).tooltip(MimicSpell::appendTooltip)); + public static final SpellType HYDROPHOBIC = register("hydrophobic", SpellType.builder(s -> new HydrophobicSpell(s, FluidTags.WATER)).affinity(Affinity.NEUTRAL).color(0xF999FF).stackable().shape(GemstoneItem.Shape.ROCKET).tooltip(HydrophobicSpell::appendTooltip)); + public static final SpellType BUBBLE = register("bubble", builder(BubbleSpell::new).affinity(Affinity.NEUTRAL).color(0xF999FF).shape(GemstoneItem.Shape.DONUT).traits(BubbleSpell.DEFAULT_TRAITS).tooltip(BubbleSpell::appendTooltip)); + public static final SpellType DISPEL_EVIL = register("dispel_evil", builder(DispellEvilSpell::new).color(0x00FF00).shape(GemstoneItem.Shape.CROSS).traits(DispellEvilSpell.DEFAULT_TRAITS).tooltip(DispellEvilSpell::appendTooltip)); public static void bootstrap() {} @@ -109,7 +110,7 @@ public final class SpellType implements Affine, SpellPredicate< this.factory = factory; this.traits = traits; this.stackable = stackable; - traited = new CustomisedSpellType<>(this, traits); + traited = new CustomisedSpellType<>(this, traits, SpellTraits::empty); defaultStack = UItems.GEMSTONE.getDefaultStack(this); } @@ -165,7 +166,7 @@ public final class SpellType implements Affine, SpellPredicate< } public CustomisedSpellType withTraits(SpellTraits traits) { - return traits.isEmpty() ? withTraits() : new CustomisedSpellType<>(this, traits); + return traits.isEmpty() ? withTraits() : new CustomisedSpellType<>(this, traits, Suppliers.memoize(() -> traits.map((trait, value) -> value - getTraits().get(trait)))); } public Factory getFactory() { diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/trait/SpellTraits.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/trait/SpellTraits.java index f900ac24..3bef5609 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/trait/SpellTraits.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/trait/SpellTraits.java @@ -60,6 +60,10 @@ public final class SpellTraits implements Iterable> { }); } + public static SpellTraits empty() { + return EMPTY; + } + public static Map all() { return new HashMap<>(REGISTRY); } diff --git a/src/main/java/com/minelittlepony/unicopia/entity/effect/EffectUtils.java b/src/main/java/com/minelittlepony/unicopia/entity/effect/EffectUtils.java index fd63fc84..750c1a8e 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/effect/EffectUtils.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/effect/EffectUtils.java @@ -6,6 +6,7 @@ import net.minecraft.entity.effect.StatusEffectInstance; import net.minecraft.item.ItemStack; import net.minecraft.text.Text; import net.minecraft.util.Formatting; +import net.minecraft.util.StringHelper; public interface EffectUtils { static boolean isPoisoned(LivingEntity entity) { @@ -50,6 +51,13 @@ public interface EffectUtils { return false; } + static Text formatModifierChange(String modifierName, int time, boolean isDetrimental) { + return Text.literal(" ").append(Text.translatable("attribute.modifier.equals.0", + StringHelper.formatTicks(Math.abs(time)), + Text.translatable(modifierName) + ).formatted((isDetrimental ? time : -time) < 0 ? Formatting.DARK_GREEN : Formatting.RED)); + } + static Text formatModifierChange(String modifierName, float change, boolean isDetrimental) { return Text.literal(" ").append(Text.translatable("attribute.modifier." + (change > 0 ? "plus" : "take") + ".0", ItemStack.MODIFIER_FORMAT.format(Math.abs(change)), diff --git a/src/main/resources/assets/unicopia/lang/en_us.json b/src/main/resources/assets/unicopia/lang/en_us.json index 60e60b2f..7c75fb3b 100644 --- a/src/main/resources/assets/unicopia/lang/en_us.json +++ b/src/main/resources/assets/unicopia/lang/en_us.json @@ -548,12 +548,6 @@ "spell.unicopia.fire_bolt": "Fire Bolt", "spell.unicopia.fire_bolt.lore": "Produces several burning projectiles", "spell.unicopia.shield": "Protection", - "spell.unicopia.shield.additional_range": "Additional Range", - "spell.unicopia.shield.permit.passive": "Permits Passive Mobs", - "spell.unicopia.shield.permit.hostile": "Permits Hostile Mobs", - "spell.unicopia.shield.permit.player": " Permits Other Players", - "spell.unicopia.shield.caston.location": "Applies to location", - "spell.unicopia.shield.caston.person": "Applies to self", "spell.unicopia.shield.lore": "Casts a protective shield around the user", "spell.unicopia.bubble": "Bubble", "spell.unicopia.bubble.lore": "Traps any creature it hits in a soap bubble", @@ -589,6 +583,33 @@ "spell.unicopia.catapult.lore": "Grabs a nearby block or entity and throws it into the air", "spell.unicopia.dispel_evil": "Dispel Evil", "spell.unicopia.dispel_evil.lore": "Casts away any nearby unearthly forces", + + "spell_attribute.unicopia.cast_on_location": "Applies to location", + "spell_attribute.unicopia.cast_on_person": "Applies to self", + "spell_attribute.unicopia.focused_entity": "Applies to focused entity", + "spell_attribute.unicopia.follows_target": "Follows target", + + "spell_attribute.unicopia.permit_items": " Permits Items", + "spell_attribute.unicopia.permit_passive": "Permits Passive Mobs", + "spell_attribute.unicopia.permit_hostile": "Permits Hostile Mobs", + "spell_attribute.unicopia.permit_player": " Permits Other Players", + + "spell_attribute.unicopia.range": "Effect Range", + "spell_attribute.unicopia.duration": "Effect Duration", + "spell_attribute.unicopia.strength": "Effect Strength", + "spell_attribute.unicopia.soapiness": "Soapiness", + "spell_attribute.unicopia.velocity": "Velocity", + "spell_attribute.unicopia.vertical_velocity": "Launch Speed", + "spell_attribute.unicopia.damage_to_target": "Damage to Target", + "spell_attribute.unicopia.simultanious_targets": "Simultanious Targets", + "spell_attribute.unicopia.cost_per_individual": "Mana cost per individual", + "spell_attribute.unicopia.explosion_strength": "Blast Strength", + "spell_attribute.unicopia.projectile_count": "Projectile Count", + "spell_attribute.unicopia.follow_range": "Following Range", + "spell_attribute.unicopia.orb_count": "Orb Count", + "spell_attribute.unicopia.wave_size": "Wave Size", + "spell_attribute.unicopia.target_preference": "Target Preference", + "spell_attribute.unicopia.caster_preference": "Caster Preference", "trait.unicopia.strength.name": "Strength", "trait.unicopia.strength.description": "Imparts physical strength or enhances endurance.\nSpells with more of the strength trait hit harder and last longer.",