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 index f4b2ffdc..df35b029 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/SpellAttributes.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/SpellAttributes.java @@ -26,6 +26,10 @@ public interface SpellAttributes { Identifier STRENGTH = Unicopia.id("strength"); Identifier VELOCITY = Unicopia.id("velocity"); Identifier VERTICAL_VELOCITY = Unicopia.id("vertical_velocity"); + Identifier HANG_TIME = Unicopia.id("hang_time"); + Identifier PUSHING_POWER = Unicopia.id("pushing_power"); + Identifier CAUSES_LEVITATION = Unicopia.id("causes_levitation"); + Identifier AFFECTS = Unicopia.id("affects"); 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"); @@ -61,4 +65,11 @@ public interface SpellAttributes { Text.translatable(Util.createTranslationKey("spell_attribute", id)) ).formatted(Formatting.LIGHT_PURPLE)); } + + public enum ValueType { + REGULAR, + TIME, + PERCENTAGE, + CONDITIONAL + } } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/attribute/Affects.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/attribute/Affects.java new file mode 100644 index 00000000..dbbe9916 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/attribute/Affects.java @@ -0,0 +1,15 @@ +package com.minelittlepony.unicopia.ability.magic.spell.attribute; + +public enum Affects { + BOTH, + ENTITIES, + BLOCKS; + + public boolean allowsBlocks() { + return this == BOTH || this == BLOCKS; + } + + public boolean allowsEntities() { + return this == BOTH || this == ENTITIES; + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/attribute/AttributeFormat.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/attribute/AttributeFormat.java new file mode 100644 index 00000000..eba8db86 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/attribute/AttributeFormat.java @@ -0,0 +1,63 @@ +package com.minelittlepony.unicopia.ability.magic.spell.attribute; + +import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; +import com.minelittlepony.unicopia.client.UnicopiaClient; +import com.minelittlepony.unicopia.client.gui.ItemTraitsTooltipRenderer; + +import net.minecraft.item.ItemStack; +import net.minecraft.text.MutableText; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import net.minecraft.util.StringHelper; + +public enum AttributeFormat { + REGULAR { + @Override + public String formatValue(float value) { + return ItemStack.MODIFIER_FORMAT.format(value); + } + }, + TIME { + @Override + public String formatValue(float value) { + return StringHelper.formatTicks((int)Math.abs(value)); + } + }, + PERCENTAGE { + @Override + public String formatValue(float value) { + return ItemStack.MODIFIER_FORMAT.format((int)(Math.abs(value) * 100)) + "%"; + } + }; + + public abstract String formatValue(float value); + + public MutableText getBase(Text attributeName, float value, String comparison, Formatting color) { + return formatAttributeLine(Text.translatable("attribute.modifier." + comparison + ".0", formatValue(value), attributeName).formatted(color)); + } + + public Text get(Text attributeName, float value) { + return getBase(attributeName, value, "equals", Formatting.LIGHT_PURPLE); + } + + public Text getRelative(Text attributeName, float baseValue, float currentValue, boolean detrimental) { + float difference = currentValue - baseValue; + return Text.literal(" (" + (difference > 0 ? "+" : "-") + formatValue(this == PERCENTAGE ? difference / baseValue : difference) + ")").formatted((detrimental ? difference : -difference) < 0 ? Formatting.DARK_GREEN : Formatting.RED); + } + + static Text formatTraitDifference(Trait trait, float value) { + boolean known = ItemTraitsTooltipRenderer.isKnown(trait); + boolean canCast = UnicopiaClient.getClientPony() != null && UnicopiaClient.getClientPony().getObservedSpecies().canCast(); + Text name = canCast ? known + ? trait.getName() + : Text.translatable("spell_attribute.unicopia.added_trait.unknown").formatted(Formatting.YELLOW) + : trait.getName().copy().formatted(Formatting.OBFUSCATED, Formatting.YELLOW); + Text count = Text.literal(ItemStack.MODIFIER_FORMAT.format(value)); + return Text.translatable("spell_attribute.unicopia.added_trait." + ((value > 0) ? "plus" : "take"), name, count).formatted(Formatting.DARK_AQUA); + } + + static MutableText formatAttributeLine(Text attributeName) { + return Text.literal(" ").append(attributeName).formatted(Formatting.LIGHT_PURPLE); + } + +} diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/attribute/Permits.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/attribute/Permits.java new file mode 100644 index 00000000..c63f1275 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/attribute/Permits.java @@ -0,0 +1,8 @@ +package com.minelittlepony.unicopia.ability.magic.spell.attribute; + +public enum Permits { + ITEMS, + PASSIVE, + HOSTILE, + PLAYER +} diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/attribute/SpellAttribute.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/attribute/SpellAttribute.java new file mode 100644 index 00000000..e450568d --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/attribute/SpellAttribute.java @@ -0,0 +1,88 @@ +package com.minelittlepony.unicopia.ability.magic.spell.attribute; + +import java.util.List; +import java.util.Locale; +import java.util.function.Function; + +import org.jetbrains.annotations.NotNull; + +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 it.unimi.dsi.fastutil.floats.Float2ObjectFunction; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import net.minecraft.util.Identifier; +import net.minecraft.util.Util; + +public record SpellAttribute ( + Trait trait, + Float2ObjectFunction valueGetter, + TooltipFactory tooltipFactory +) implements TooltipFactory { + @Override + public void appendTooltip(CustomisedSpellType type, List tooltip) { + tooltipFactory.appendTooltip(type, tooltip); + } + + public T get(SpellTraits traits) { + return valueGetter.get(traits.get(trait)); + } + + public static SpellAttribute create(Identifier id, AttributeFormat format, Trait trait, Float2ObjectFunction<@NotNull T> valueGetter) { + return create(id, format, format, trait, valueGetter, false); + } + + public static SpellAttribute create(Identifier id, AttributeFormat baseFormat, AttributeFormat relativeFormat, Trait trait, Float2ObjectFunction<@NotNull T> valueGetter) { + return create(id, baseFormat, relativeFormat, trait, valueGetter, false); + } + + public static SpellAttribute create(Identifier id, AttributeFormat baseFormat, AttributeFormat relativeFormat, Trait trait, Float2ObjectFunction<@NotNull T> valueGetter, boolean detrimental) { + Text name = Text.translatable(Util.createTranslationKey("spell_attribute", id)); + return new SpellAttribute<>(trait, valueGetter, (CustomisedSpellType type, List tooltip) -> { + float traitAmount = type.traits().get(trait); + float traitDifference = type.relativeTraits().get(trait); + float value = valueGetter.get(traitAmount).floatValue(); + + var b = baseFormat.getBase(name, value, "equals", Formatting.LIGHT_PURPLE); + if (traitDifference != 0) { + tooltip.add(b.append(relativeFormat.getRelative(Text.empty(), valueGetter.get(traitAmount - traitDifference).floatValue(), value, detrimental))); + tooltip.add(AttributeFormat.formatTraitDifference(trait, traitDifference)); + } else { + tooltip.add(b); + } + }); + } + + public static SpellAttribute createConditional(Identifier id, Trait trait, Float2ObjectFunction valueGetter) { + return new SpellAttribute<>(trait, valueGetter, (CustomisedSpellType type, List tooltip) -> { + Text name = Text.translatable(Util.createTranslationKey("spell_attribute", id)); + float difference = type.relativeTraits().get(trait); + Text value = AttributeFormat.formatAttributeLine(name); + if (!valueGetter.get(type.traits().get(trait))) { + value = value.copy().formatted(Formatting.STRIKETHROUGH, Formatting.DARK_GRAY); + } + tooltip.add(value); + if (difference != 0) { + tooltip.add(AttributeFormat.formatTraitDifference(trait, difference)); + } + }); + } + + public static > SpellAttribute createEnumerated(Identifier id, Trait trait, Float2ObjectFunction valueGetter) { + Function cache = Util.memoize(t -> Text.translatable(Util.createTranslationKey("spell_attribute", id.withPath(id.getPath() + "." + t.name().toLowerCase(Locale.ROOT))))); + return new SpellAttribute<>(trait, valueGetter, (CustomisedSpellType type, List tooltip) -> { + T t = valueGetter.get(type.traits().get(trait)); + + if (t != null) { + int max = t.getClass().getEnumConstants().length; + tooltip.add(Text.translatable(" %s (%s/%s)", cache.apply(t), Text.literal("" + (t.ordinal() + 1)).formatted(Formatting.LIGHT_PURPLE), max).formatted(Formatting.DARK_PURPLE)); + } + float difference = type.relativeTraits().get(trait); + if (difference != 0) { + tooltip.add(AttributeFormat.formatTraitDifference(trait, difference)); + } + }); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/attribute/TooltipFactory.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/attribute/TooltipFactory.java new file mode 100644 index 00000000..e4fd5db9 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/attribute/TooltipFactory.java @@ -0,0 +1,19 @@ +package com.minelittlepony.unicopia.ability.magic.spell.attribute; + +import java.util.List; + +import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType; + +import net.minecraft.text.Text; + +public interface TooltipFactory { + void appendTooltip(CustomisedSpellType type, List tooltip); + + static TooltipFactory of(TooltipFactory...lines) { + return (type, tooltip) -> { + for (var line : lines) { + line.appendTooltip(type, tooltip); + } + }; + } +} 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 72d56132..287e55bb 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 @@ -6,10 +6,13 @@ import java.util.function.Consumer; import org.jetbrains.annotations.Nullable; import com.minelittlepony.unicopia.UTags; -import com.minelittlepony.unicopia.Unicopia; 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.attribute.Affects; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.AttributeFormat; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.SpellAttribute; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.TooltipFactory; import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.mixin.MixinFallingBlockEntity; @@ -27,6 +30,7 @@ import net.minecraft.text.Text; import net.minecraft.util.hit.BlockHitResult; import net.minecraft.util.hit.EntityHitResult; import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.random.Random; import net.minecraft.world.World; @@ -45,13 +49,22 @@ public class CatapultSpell extends AbstractSpell implements ProjectileDelegate.B private static final float HORIZONTAL_VARIANCE = 0.25F; private static final float MAX_STRENGTH = 120; + private static final SpellAttribute LAUNCH_SPEED = SpellAttribute.create(SpellAttributes.VERTICAL_VELOCITY, AttributeFormat.REGULAR, AttributeFormat.PERCENTAGE, Trait.STRENGTH, strength -> 0.1F + (MathHelper.clamp(strength, -MAX_STRENGTH, MAX_STRENGTH) - 40) / 16F); + private static final SpellAttribute HANG_TIME = SpellAttribute.create(SpellAttributes.HANG_TIME, AttributeFormat.TIME, AttributeFormat.PERCENTAGE, Trait.AIR, air -> 50 + (int)MathHelper.clamp(air, 0, 10) * 20F); + private static final SpellAttribute PUSHING_POWER = SpellAttribute.create(SpellAttributes.PUSHING_POWER, AttributeFormat.REGULAR, Trait.POWER, power -> 1 + MathHelper.clamp(power, 0, 10) / 10F); + private static final SpellAttribute CAUSES_LEVITATION = SpellAttribute.createConditional(SpellAttributes.CAUSES_LEVITATION, Trait.FOCUS, focus -> focus > 50); + private static final SpellAttribute AFFECTS = SpellAttribute.createEnumerated(SpellAttributes.AFFECTS, Trait.ORDER, order -> { + if (order <= 0) { + return Affects.BOTH; + } else if (order <= 10) { + return Affects.ENTITIES; + } + return Affects.BLOCKS; + }); + static final TooltipFactory TOOLTIP = TooltipFactory.of(LAUNCH_SPEED, HANG_TIME, PUSHING_POWER, CAUSES_LEVITATION, AFFECTS); + static void appendTooltip(CustomisedSpellType type, List tooltip) { - float velocity = (float)(0.1F + (type.traits().get(Trait.STRENGTH, -MAX_STRENGTH, MAX_STRENGTH) - 40) / 16D); - tooltip.add(SpellAttributes.of(SpellAttributes.VERTICAL_VELOCITY, velocity)); - int hoverDuration = 50 + (int)type.traits().get(Trait.AIR, 0, 10) * 20; - tooltip.add(SpellAttributes.ofTime(Unicopia.id("hang_time"), hoverDuration)); - float power = 1 + type.traits().get(Trait.POWER, 0, 10) / 10F; - tooltip.add(SpellAttributes.of(Unicopia.id("pushing_power"), power)); + TOOLTIP.appendTooltip(type, tooltip); } protected CatapultSpell(CustomisedSpellType type) { @@ -60,6 +73,10 @@ public class CatapultSpell extends AbstractSpell implements ProjectileDelegate.B @Override public void onImpact(MagicProjectileEntity projectile, BlockHitResult hit) { + if (!AFFECTS.get(getTraits()).allowsBlocks()) { + return; + } + if (!projectile.isClient() && projectile instanceof MagicBeamEntity source && source.canModifyAt(hit.getBlockPos())) { createBlockEntity(projectile.getWorld(), hit.getBlockPos(), e -> { e.setOnGround(true); @@ -72,6 +89,11 @@ public class CatapultSpell extends AbstractSpell implements ProjectileDelegate.B @Override public void onImpact(MagicProjectileEntity projectile, EntityHitResult hit) { if (!projectile.isClient() && projectile instanceof MagicBeamEntity source) { + Entity e = hit.getEntity(); + if (!(e instanceof FallingBlockEntity) && !AFFECTS.get(getTraits()).allowsEntities()) { + return; + } + apply(source, hit.getEntity()); } } @@ -95,21 +117,29 @@ public class CatapultSpell extends AbstractSpell implements ProjectileDelegate.B e.setVelocity(caster.asEntity().getVelocity().multiply(power)); } else { Random rng = caster.asWorld().random; - double launchSpeed = 0.1F + (getTraits().get(Trait.STRENGTH, -MAX_STRENGTH, MAX_STRENGTH) - 40) / 16D; e.addVelocity( rng.nextTriangular(0, HORIZONTAL_VARIANCE) * 0.1F, - launchSpeed, + LAUNCH_SPEED.get(getTraits()), rng.nextTriangular(0, HORIZONTAL_VARIANCE) * 0.1F ); - if (e instanceof LivingEntity l) { - int hoverDuration = 50 + (int)getTraits().get(Trait.AIR, 0, 10) * 20; + int hoverDuration = HANG_TIME.get(getTraits()).intValue(); + boolean noGravity = CAUSES_LEVITATION.get(getTraits()); + if (e instanceof LivingEntity l) { if (l.hasStatusEffect(StatusEffects.SLOW_FALLING)) { l.removeStatusEffect(StatusEffects.SLOW_FALLING); } l.addStatusEffect(new StatusEffectInstance(StatusEffects.SLOW_FALLING, hoverDuration, 1)); } + + if (noGravity || e instanceof FallingBlockEntity && (!e.getWorld().getBlockState(e.getBlockPos().up()).isReplaceable())) { + if (e instanceof LivingEntity l) { + l.addStatusEffect(new StatusEffectInstance(StatusEffects.LEVITATION, 200, 1)); + } else { + e.setNoGravity(true); + } + } } e.velocityDirty = true; 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 c2255509..9ae37a33 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 @@ -68,7 +68,7 @@ public final class SpellType implements Affine, SpellPredicate< 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).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 CATAPULT = register("catapult", builder(CatapultSpell::new).color(0x22FF00).shape(GemstoneItem.Shape.ROCKET).traits(CatapultSpell.DEFAULT_TRAITS).tooltip(CatapultSpell.TOOLTIP::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)); 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 750c1a8e..3a462183 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/effect/EffectUtils.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/effect/EffectUtils.java @@ -52,7 +52,7 @@ public interface EffectUtils { } static Text formatModifierChange(String modifierName, int time, boolean isDetrimental) { - return Text.literal(" ").append(Text.translatable("attribute.modifier.equals.0", + return Text.literal(" ").append(Text.translatable("attribute.modifier." + (time > 0 ? "plus" : "take") + ".0", StringHelper.formatTicks(Math.abs(time)), Text.translatable(modifierName) ).formatted((isDetrimental ? time : -time) < 0 ? Formatting.DARK_GREEN : Formatting.RED)); @@ -64,4 +64,11 @@ public interface EffectUtils { Text.translatable(modifierName) ).formatted((isDetrimental ? change : -change) < 0 ? Formatting.DARK_GREEN : Formatting.RED)); } + + static Text formatModifierChange(Text 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)), + modifierName + ).formatted((isDetrimental ? change : -change) < 0 ? Formatting.DARK_GREEN : Formatting.RED)); + } } diff --git a/src/main/resources/assets/unicopia/lang/en_us.json b/src/main/resources/assets/unicopia/lang/en_us.json index 6d92a2d4..4c19c409 100644 --- a/src/main/resources/assets/unicopia/lang/en_us.json +++ b/src/main/resources/assets/unicopia/lang/en_us.json @@ -584,10 +584,17 @@ "spell.unicopia.dispel_evil": "Dispel Evil", "spell.unicopia.dispel_evil.lore": "Casts away any nearby unearthly forces", + "spell_attribute.unicopia.added_trait.plus": " + %s x%s", + "spell_attribute.unicopia.added_trait.take": " - %s x%s", + "spell_attribute.unicopia.added_trait.unknown": "Undiscovered Trait", "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.affects.both": "Affects blocks and entities", + "spell_attribute.unicopia.affects.entities": "Affects only entities", + "spell_attribute.unicopia.affects.blocks": "Affects only blocks", "spell_attribute.unicopia.follows_target": "Follows target", + "spell_attribute.unicopia.causes_levitation": "Causes Levitation", "spell_attribute.unicopia.permit_items": " Permits Items", "spell_attribute.unicopia.permit_passive": "Permits Passive Mobs",