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 new file mode 100644 index 00000000..5251ebaf --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/FeatherFallSpell.java @@ -0,0 +1,129 @@ +package com.minelittlepony.unicopia.ability.magic.spell.effect; + +import java.util.List; +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.trait.SpellTraits; +import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; +import com.minelittlepony.unicopia.item.FriendshipBraceletItem; +import com.minelittlepony.unicopia.particle.MagicParticleEffect; +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.util.math.MathHelper; +import net.minecraft.util.math.Vec3d; + +public class FeatherFallSpell extends AbstractSpell { + private static final int MIN_RANGE = 1; + private static final int MAX_RANGE = 20; + private static final int MIN_TARGETS = 1; + private static final int MAX_TARGETS = 19; + + private static final float FOCUS_RANGE_WEIGHT = 0.1F; + private static final float POWERS_RANGE_WEIGHT = 0.3F; + private static final float MAX_GENEROSITY_FACTOR = 19F; + + public static final SpellTraits DEFAULT_TRAITS = new SpellTraits.Builder() + .with(Trait.FOCUS, 80) + .with(Trait.POWER, 10) + .with(Trait.AIR, 0.1F) + .with(Trait.KINDNESS, 90) + .with(Trait.ORDER, 15) + .build(); + + private int duration; + + protected FeatherFallSpell(SpellType type, SpellTraits traits) { + super(type, traits); + duration = (int)(traits.get(Trait.FOCUS, 0, 160) * 19); + } + + @Override + public boolean tick(Caster caster, Situation situation) { + + if (duration-- <= 0) { + return false; + } + + List targets = getTargets(caster).toList(); + + if (targets.isEmpty()) { + return true; + } + + final float strength = 1F / (getTraits().get(Trait.STRENGTH, 2, 9) / targets.size()); + final float generosity = getTraits().get(Trait.GENEROSITY, 1, MAX_GENEROSITY_FACTOR) / MAX_GENEROSITY_FACTOR; + + Entity master = caster.getMaster(); + Entity entity = caster.getEntity(); + Vec3d masterVelocity = caster.getEntity().getVelocity().multiply(0.1); + targets.forEach(target -> { + if (target.getVelocity().y < 0) { + + boolean isSelf = target == master || target == entity; + float delta = strength * (isSelf ? (1F - generosity) : generosity); + + if (!isSelf || generosity < 0.5F) { + target.verticalCollision = true; + target.setOnGround(true); + target.fallDistance = 0; + } + if (target instanceof PlayerEntity) { + ((PlayerEntity)target).getAbilities().flying = false; + } + target.setVelocity(target.getVelocity().multiply(1, delta, 1)); + if (situation == Situation.PROJECTILE && target != caster.getEntity()) { + target.addVelocity(masterVelocity.x, 0, masterVelocity.z); + } + } + ParticleUtils.spawnParticles(new MagicParticleEffect(getType().getColor()), target, 7); + }); + + return caster.subtractEnergyCost(duration % 50 == 0 ? getCostPerEntity() * 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; + + 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 long getMaxTargets() { + long generosity = (long)getTraits().get(Trait.GENEROSITY) * 2L; + long focus = (long)getTraits().get(Trait.FOCUS, MIN_TARGETS, MAX_TARGETS) * 2L; + return generosity + focus; + } + + protected Stream getTargets(Caster caster) { + Entity owner = caster.getEntity();// , EquinePredicates.IS_PLAYER + return Stream.concat(Stream.of(owner), caster.findAllEntitiesInRange(getEffectRange()).sorted((a, b) -> { + return Integer.compare( + FriendshipBraceletItem.isComrade(caster, a) ? 1 : 0, + FriendshipBraceletItem.isComrade(caster, b) ? 1 : 0 + ); + }).distinct()).limit(getMaxTargets()); + } + + @Override + public void toNBT(NbtCompound compound) { + super.toNBT(compound); + compound.putInt("duration", duration); + } + + @Override + public void fromNBT(NbtCompound compound) { + super.fromNBT(compound); + duration = compound.getInt("duration"); + } +} 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 4dec3942..47f7e03c 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 @@ -31,7 +31,7 @@ import net.minecraft.util.registry.Registry; public final class SpellType implements Affine, SpellPredicate { public static final Identifier EMPTY_ID = new Identifier("unicopia", "null"); - public static final SpellType EMPTY_KEY = new SpellType<>(EMPTY_ID, Affinity.NEUTRAL, 0xFFFFFF, false, (t, c) -> null); + public static final SpellType EMPTY_KEY = new SpellType<>(EMPTY_ID, Affinity.NEUTRAL, 0xFFFFFF, false, SpellTraits.EMPTY, (t, c) -> null); private static final Registry> REGISTRY = Registries.createSimple(new Identifier("unicopia", "spells")); private static final Map>> BY_AFFINITY = new EnumMap<>(Affinity.class); @@ -57,6 +57,7 @@ public final class SpellType implements Affine, SpellPredicate< public static final SpellType REVEALING = register("reveal", Affinity.GOOD, 0x5CE81F, true, RevealingSpell::new); public static final SpellType AWKWARD = register("awkward", Affinity.GOOD, 0xE1239C, true, AwkwardSpell::new); public static final SpellType TRANSFORMATION = register("transformation", Affinity.NEUTRAL, 0x3A59AA, true, TransformationSpell::new); + public static final SpellType FEATHER_FALL = register("feather_fall", Affinity.GOOD, 0x00EEFF, true, FeatherFallSpell.DEFAULT_TRAITS, FeatherFallSpell::new); private final Identifier id; private final Affinity affinity; @@ -69,14 +70,16 @@ public final class SpellType implements Affine, SpellPredicate< private String translationKey; private final CustomisedSpellType traited; + private final SpellTraits traits; - private SpellType(Identifier id, Affinity affinity, int color, boolean obtainable, Factory factory) { + private SpellType(Identifier id, Affinity affinity, int color, boolean obtainable, SpellTraits traits, Factory factory) { this.id = id; this.affinity = affinity; this.color = color; this.obtainable = obtainable; this.factory = factory; - traited = new CustomisedSpellType<>(this, SpellTraits.EMPTY); + this.traits = traits; + traited = new CustomisedSpellType<>(this, traits); } public boolean isObtainable() { @@ -99,6 +102,10 @@ public final class SpellType implements Affine, SpellPredicate< return affinity; } + public SpellTraits getTraits() { + return traits; + } + public String getTranslationKey() { if (translationKey == null) { translationKey = Util.createTranslationKey(getAffinity().getTranslationKey(), getId()); @@ -153,15 +160,19 @@ public final class SpellType implements Affine, SpellPredicate< return this == EMPTY_KEY; } - public static SpellType register(Identifier id, Affinity affinity, int color, boolean obtainable, Factory factory) { - SpellType type = new SpellType<>(id, affinity, color, obtainable, factory); + public static SpellType register(Identifier id, Affinity affinity, int color, boolean obtainable, SpellTraits traits, Factory factory) { + SpellType type = new SpellType<>(id, affinity, color, obtainable, traits, factory); byAffinity(affinity).add(type); Registry.register(REGISTRY, id, type); return type; } public static SpellType register(String name, Affinity affinity, int color, boolean obtainable, Factory factory) { - return register(new Identifier("unicopia", name), affinity, color, obtainable, 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(new Identifier("unicopia", name), affinity, color, obtainable, traits, factory); } @SuppressWarnings("unchecked") 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 05271bad..6439baee 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 @@ -157,6 +157,10 @@ public final class SpellTraits implements Iterable> { public ItemStack applyTo(ItemStack stack) { stack = stack.copy(); + if (isEmpty()) { + stack.removeSubTag("spell_traits"); + return stack; + } stack.getOrCreateTag().put("spell_traits", toNbt()); return stack; } @@ -235,4 +239,17 @@ public final class SpellTraits implements Iterable> { .filter(e -> e.getValue() != 0) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> a + b, () -> new EnumMap<>(Trait.class))); } + + public static final class Builder { + private final Map traits = new EnumMap<>(Trait.class); + + public Builder with(Trait trait, float amount) { + traits.put(trait, amount); + return this; + } + + public SpellTraits build() { + return fromEntries(traits.entrySet().stream()).orElse(SpellTraits.EMPTY); + } + } } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/trait/Trait.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/trait/Trait.java index 1548703d..f1ee7089 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/trait/Trait.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/trait/Trait.java @@ -10,7 +10,15 @@ import java.util.stream.Collectors; import net.minecraft.util.Identifier; public enum Trait { + /** + * Imparts physical strength or enhances endurance. + * Spells with more of the strength trait hit harder and last longer. + */ STRENGTH(TraitGroup.NATURAL), + /** + * Narrows a spell to focus its energy more effectively. + * Adding the focus trait to spells will decrease the cost of its effects whilst extending its range to more targets in cases of multi-target spells. + */ FOCUS(TraitGroup.NATURAL), KNOWLEDGE(TraitGroup.NATURAL), LIFE(TraitGroup.NATURAL), @@ -27,6 +35,13 @@ public enum Trait { KINDNESS(TraitGroup.MAGICAL), HAPPINESS(TraitGroup.MAGICAL), + /** + * Causes a spell to favor others over the caster. + * Can be used to increase range and power, but to the detriment to the caster. + * + * Complemented by the Element of Harmony and the Element of Kindness. + * Spells with this trait are better suited to lending aid to those in need. + */ GENEROSITY(TraitGroup.MAGICAL), DARKNESS(TraitGroup.DARKNESS), diff --git a/src/main/java/com/minelittlepony/unicopia/client/ModifierTooltipRenderer.java b/src/main/java/com/minelittlepony/unicopia/client/ModifierTooltipRenderer.java index 1a8e0af9..bd1a6522 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/ModifierTooltipRenderer.java +++ b/src/main/java/com/minelittlepony/unicopia/client/ModifierTooltipRenderer.java @@ -72,6 +72,10 @@ public class ModifierTooltipRenderer implements ItemTooltipCallback { } }); } + + if (MinecraftClient.getInstance().player != null) { + Pony.of(MinecraftClient.getInstance().player).getDiscoveries().appendTooltip(stack, MinecraftClient.getInstance().world, lines); + } } private int getInsertPosition(ItemStack stack, Text category, int flags, List lines, boolean advanced) { diff --git a/src/main/java/com/minelittlepony/unicopia/client/gui/ItemTraitsTooltipRenderer.java b/src/main/java/com/minelittlepony/unicopia/client/gui/ItemTraitsTooltipRenderer.java index 5efe14df..395c77b3 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/gui/ItemTraitsTooltipRenderer.java +++ b/src/main/java/com/minelittlepony/unicopia/client/gui/ItemTraitsTooltipRenderer.java @@ -28,16 +28,16 @@ public class ItemTraitsTooltipRenderer extends BaseText implements OrderedText, @Override public int getHeight() { - return getRows() * 8 + 2 + 4; + return getRows() * 8 + 4; } @Override public int getWidth(TextRenderer textRenderer) { - return getColumns() * 8 + 2; + return getColumns() * 17 + 2; } private int getColumns() { - return Math.max(2, (int)Math.ceil(Math.sqrt(traits.entries().size() + 1))); + return Math.max(4, (int)Math.ceil(Math.sqrt(traits.entries().size() + 1))); } private int getRows() { @@ -52,20 +52,17 @@ public class ItemTraitsTooltipRenderer extends BaseText implements OrderedText, var entries = traits.stream().toList(); for (int i = 0; i < entries.size(); i++) { - int xx = x + (i % columns) * 10; + int xx = x + (i % columns) * 17; int yy = y + (i / columns) * 10; Entry entry = entries.get(i); RenderSystem.setShaderTexture(0, entry.getKey().getSprite()); DrawableHelper.drawTexture(matrices, xx, yy, 1, 0, 0, 8, 8, 8, 8); - String string = String.format("%.2f", entry.getValue()); + String string = entry.getValue() > 99 ? "99+" : Math.round(entry.getValue()) + ""; matrices.push(); - xx += 19 - 2 - textRenderer.getWidth(string); - yy += 6 + 3; - - matrices.translate(xx, yy, itemRenderer.zOffset + 200.0F); + matrices.translate(xx + 9, yy + 3, itemRenderer.zOffset + 200.0F); matrices.scale(0.5F, 0.5F, 1); VertexConsumerProvider.Immediate immediate = VertexConsumerProvider.immediate(Tessellator.getInstance().getBuffer()); textRenderer.draw(string, 0, 0, 16777215, true, matrices.peek().getModel(), immediate, false, 0, 15728880); diff --git a/src/main/java/com/minelittlepony/unicopia/item/GemstoneItem.java b/src/main/java/com/minelittlepony/unicopia/item/GemstoneItem.java index 33329c12..7b69cc05 100644 --- a/src/main/java/com/minelittlepony/unicopia/item/GemstoneItem.java +++ b/src/main/java/com/minelittlepony/unicopia/item/GemstoneItem.java @@ -36,6 +36,7 @@ public class GemstoneItem extends Item { @Override public void appendTooltip(ItemStack stack, @Nullable World world, List lines, TooltipContext tooltipContext) { + super.appendTooltip(stack, world, lines, tooltipContext); if (isEnchanted(stack)) { SpellType key = getSpellKey(stack); @@ -94,7 +95,7 @@ public class GemstoneItem extends Item { return TypedActionResult.fail(null); } - if (key.isEmpty() || (test == null || !test.test(key))) { + if (key.isEmpty() || (test != null && !test.test(key))) { return TypedActionResult.fail(null); } @@ -121,7 +122,7 @@ public class GemstoneItem extends Item { public static ItemStack enchanted(ItemStack stack, SpellType type, Affinity affinity) { stack.getOrCreateTag().putString("spell", type.getId().toString()); - return stack; + return type.getTraits().applyTo(stack); } public static ItemStack unenchanted(ItemStack stack) { diff --git a/src/main/java/com/minelittlepony/unicopia/mixin/client/MixinItem.java b/src/main/java/com/minelittlepony/unicopia/mixin/client/MixinItem.java index 58b12909..f7c9b7b1 100644 --- a/src/main/java/com/minelittlepony/unicopia/mixin/client/MixinItem.java +++ b/src/main/java/com/minelittlepony/unicopia/mixin/client/MixinItem.java @@ -8,7 +8,6 @@ import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.item.toxin.ToxicHolder; import net.minecraft.client.MinecraftClient; @@ -23,8 +22,5 @@ abstract class MixinItem implements ToxicHolder { @Inject(method = "appendTooltip", at = @At("RETURN")) private void onAppendTooltip(ItemStack stack, @Nullable World world, List tooltip, TooltipContext context, CallbackInfo into) { getToxic().ifPresent(t -> t.getAilmentFor(MinecraftClient.getInstance().player).appendTooltip(tooltip, context)); - if (MinecraftClient.getInstance().player != null) { - Pony.of(MinecraftClient.getInstance().player).getDiscoveries().appendTooltip(stack, world, tooltip); - } } } diff --git a/src/main/java/com/minelittlepony/unicopia/particle/ParticleSource.java b/src/main/java/com/minelittlepony/unicopia/particle/ParticleSource.java index c0777fa8..7a1246da 100644 --- a/src/main/java/com/minelittlepony/unicopia/particle/ParticleSource.java +++ b/src/main/java/com/minelittlepony/unicopia/particle/ParticleSource.java @@ -39,7 +39,6 @@ public interface ParticleSource extends ParticleSpawner { .forEach(particleSpawner); } - @Override default void addParticle(ParticleEffect effect, Vec3d position, Vec3d velocity) { getWorld().addParticle(effect, position.x, position.y, position.z, velocity.x, velocity.y, velocity.z); diff --git a/src/main/resources/assets/unicopia/lang/en_us.json b/src/main/resources/assets/unicopia/lang/en_us.json index 279acba5..fe28a568 100644 --- a/src/main/resources/assets/unicopia/lang/en_us.json +++ b/src/main/resources/assets/unicopia/lang/en_us.json @@ -171,20 +171,23 @@ "spell.unicopia.transformation": "Transformation", "spell.unicopia.transformation.lore": "Chaos II", + "spell.unicopia.feather_fall": "Feather Fall", + "spell.unicopia.feather_fall.lore": "Air I", + "gui.unicopia.trait.label": "Element of %s", "gui.unicopia.trait.group": "\n %s", "gui.unicopia.trait.corruption": "\n %s corruption", "trait.unicopia.strength.name": "Strength", - "trait.unicopia.strength.description": "An ebued trait of the Earth\nMay be used to impose force or enhance the endurance of some spells.", + "trait.unicopia.strength.description": "Imparts physical strength or enhances endurance.\nSpells with more of the strength trait hit harder and last longer.", + "trait.unicopia.focus.name": "Focus", + "trait.unicopia.focus.description": "Narrows a spell to focus its energy more effectively.\nAdding the focus trait to spells will decrease the cost of its effects whilst extending its range to more targets in cases of multi-target spells.", "trait.unicopia.knowledge.name": "Knowledge", "trait.unicopia.knowledge.description": "A mechanical harvest born of machinery and technology.\nSome spells require a little...ingenuity.", "trait.unicopia.power.name": "Power", "trait.unicopia.power.description": "Force for force's sake.\nExtends or ehanced a spell's natural duration.", "trait.unicopia.blood.name": "Blood", "trait.unicopia.blood.description": "Blood for the blood god", - "trait.unicopia.focus.name": "Focus", - "trait.unicopia.focus.description": "Blood for the blood god", "trait.unicopia.water.name": "Water", "trait.unicopia.water.description": "Embodies the first natural element. Counter to the Element of Fire.", "trait.unicopia.earth.name": "Earth", @@ -208,7 +211,7 @@ "trait.unicopia.kindness.name": "Kindness", "trait.unicopia.kindness.description": "Complemented by the Element of Harmony and the Element of Laughter, wants nothing more than to bring happiness into this world.", "trait.unicopia.generosity.name": "Generosity", - "trait.unicopia.generosity.description": "Complemented by the Element of Harmony and the Element of Kindness. Spells with this trait are better ruited to lending aid to those in need.", + "trait.unicopia.generosity.description": "Causes a spell to favor others over the caster.\nCan be used to increase range and power, but to the detriment to the caster.\n\nComplemented by the Element of Harmony and the Element of Kindness.\nSpells with this trait are better suited to lending aid to those in need.", "trait.unicopia.rot.name": "Rot", "trait.unicopia.rot.description": "Death and destruction enter this world. All will die, all must die. It has been written and so shall it be.", "trait.unicopia.life.name": "Life",