Serialize spell type and traits together, and make affinity names translatable

This commit is contained in:
Sollace 2024-05-18 14:29:47 +01:00
parent 1d15630566
commit 95ffe5b0b5
No known key found for this signature in database
GPG key ID: E52FACE7B5C773DB
21 changed files with 188 additions and 130 deletions

View file

@ -2,8 +2,10 @@ package com.minelittlepony.unicopia;
import java.util.Locale;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import net.minecraft.util.StringIdentifiable;
import net.minecraft.util.Util;
public enum Affinity implements StringIdentifiable {
GOOD(Formatting.BLUE, -1, 0),
@ -20,10 +22,13 @@ public enum Affinity implements StringIdentifiable {
public static final Affinity[] VALUES = values();
private final String translationKey;
Affinity(Formatting color, int corruption, float alignment) {
this.color = color;
this.corruption = corruption;
this.alignment = alignment;
this.translationKey = Util.createTranslationKey("affinity", Unicopia.id(name().toLowerCase(Locale.ROOT)));
}
@Override
@ -36,7 +41,11 @@ public enum Affinity implements StringIdentifiable {
}
public String getTranslationKey() {
return this == BAD ? "curse" : "spell";
return translationKey;
}
public Text getDisplayName() {
return Text.translatable(getTranslationKey());
}
public int getCorruption() {

View file

@ -108,9 +108,7 @@ public class UnicornCastingAbility extends AbstractSpellCastingAbility {
}
boolean hasExact = player.getSpellSlot().contains(spell);
boolean removed = player.getSpellSlot().removeWhere(s -> {
return s.findMatches(spell.type()).findAny().isPresent() && (spell.isEmpty() || !SpellType.PLACED_SPELL.test(s));
}, true);
boolean removed = !spell.isStackable() && player.getSpellSlot().removeWhere(s -> s.findMatches(spell.type()).findAny().isPresent(), true);
player.subtractEnergyCost(removed ? 2 : 4);
if (!hasExact && !spell.isEmpty()) {
Spell s = spell.apply(player, CastingMethod.DIRECT);

View file

@ -5,11 +5,8 @@ import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;
import com.minelittlepony.unicopia.Affinity;
import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType;
import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits;
import com.minelittlepony.unicopia.projectile.MagicProjectileEntity;
import com.minelittlepony.unicopia.projectile.ProjectileDelegate;
@ -26,10 +23,10 @@ public abstract class AbstractDelegatingSpell implements Spell,
private UUID uuid = UUID.randomUUID();
private final SpellType<?> type;
private final CustomisedSpellType<?> type;
public AbstractDelegatingSpell(CustomisedSpellType<?> type) {
this.type = type.type();
this.type = type;
}
public abstract Collection<Spell> getDelegates();
@ -45,20 +42,10 @@ public abstract class AbstractDelegatingSpell implements Spell,
}
@Override
public Affinity getAffinity() {
return Affinity.NEUTRAL;
}
@Override
public SpellType<?> getType() {
public CustomisedSpellType<?> getTypeAndTraits() {
return type;
}
@Override
public SpellTraits getTraits() {
return getDelegates().stream().map(Spell::getTraits).reduce(SpellTraits.EMPTY, SpellTraits::union);
}
@Override
public final UUID getUuid() {
return uuid;

View file

@ -154,7 +154,7 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS
}
private void checkDetachment(Caster<?> source, EntityValues<?> target) {
if (getWorld(source).map(Ether::get).map(ether -> ether.get(getType(), target, placedSpellId)).isEmpty()) {
if (getWorld(source).map(Ether::get).map(ether -> ether.get(getTypeAndTraits().type(), target, placedSpellId)).isEmpty()) {
setDead();
}
}
@ -202,7 +202,7 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS
if (!source.isClient()) {
castEntity.getTarget().ifPresent(target -> {
getWorld(source).map(Ether::get)
.ifPresent(ether -> ether.remove(getType(), target.uuid()));
.ifPresent(ether -> ether.remove(getTypeAndTraits().type(), target.uuid()));
});
castEntity.set(null);
getSpellEntity(source).ifPresent(e -> {

View file

@ -7,11 +7,12 @@ import java.util.stream.Stream;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.include.com.google.common.base.Objects;
import com.minelittlepony.unicopia.Affinity;
import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.ability.magic.Affine;
import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType;
import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits;
import com.minelittlepony.unicopia.util.NbtSerialisable;
import net.minecraft.nbt.NbtCompound;
@ -24,14 +25,18 @@ public interface Spell extends NbtSerialisable, Affine {
Serializer<Spell> SERIALIZER = Serializer.of(Spell::readNbt, Spell::writeNbt);
/**
* Returns the registered type of this spell.
* Returns the full type that describes this spell.
*/
SpellType<?> getType();
CustomisedSpellType<?> getTypeAndTraits();
/**
* Gets the traits of this spell.
*/
SpellTraits getTraits();
default boolean isOf(SpellType<?> type) {
return getTypeAndTraits().type() == type;
}
@Override
default Affinity getAffinity() {
return getTypeAndTraits().type().getAffinity();
}
/**
* The unique id of this particular spell instance.
@ -132,15 +137,8 @@ public interface Spell extends NbtSerialisable, Affine {
@Nullable
static <T extends Spell> T readNbt(@Nullable NbtCompound compound) {
try {
if (compound != null && compound.contains("effect_id")) {
@SuppressWarnings("unchecked")
T effect = (T)SpellType.getKey(compound).withTraits().create();
if (effect != null) {
effect.fromNBT(compound);
}
return effect;
if (compound != null) {
return CustomisedSpellType.<T>fromNBT(compound).create(compound);
}
} catch (Exception e) {
Unicopia.LOGGER.fatal("Invalid spell nbt {}", e);
@ -155,7 +153,7 @@ public interface Spell extends NbtSerialisable, Affine {
static NbtCompound writeNbt(Spell effect) {
NbtCompound compound = effect.toNBT();
effect.getType().toNbt(compound);
effect.getTypeAndTraits().toNbt(compound);
return compound;
}
}

View file

@ -44,7 +44,7 @@ public final class SpellReference<T extends Spell> implements NbtSerialisable {
public void toNBT(NbtCompound compound) {
if (spell != null && !spell.isDead()) {
spell.toNBT(compound);
spell.getType().toNbt(compound);
spell.getTypeAndTraits().type().toNbt(compound);
}
}

View file

@ -2,7 +2,6 @@ package com.minelittlepony.unicopia.ability.magic.spell.effect;
import java.util.UUID;
import com.minelittlepony.unicopia.Affinity;
import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.Spell;
import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits;
@ -17,7 +16,7 @@ public abstract class AbstractSpell implements Spell {
private boolean hidden;
private boolean destroyed;
private CustomisedSpellType<?> type;
private final CustomisedSpellType<?> type;
private UUID uuid = UUID.randomUUID();
@ -30,17 +29,16 @@ public abstract class AbstractSpell implements Spell {
return uuid;
}
@Override
public final SpellType<?> getType() {
protected final SpellType<?> getType() {
return type.type();
}
@Override
public final CustomisedSpellType<?> getTypeAndTraits() {
return type;
}
@Override
public final SpellTraits getTraits() {
protected final SpellTraits getTraits() {
return type.traits();
}
@ -80,11 +78,6 @@ public abstract class AbstractSpell implements Spell {
this.hidden = hidden;
}
@Override
public Affinity getAffinity() {
return getType().getAffinity();
}
protected void onDestroyed(Caster<?> caster) {
}
@ -121,9 +114,6 @@ public abstract class AbstractSpell implements Spell {
dying = compound.getBoolean("dying");
dead = compound.getBoolean("dead");
hidden = compound.getBoolean("hidden");
if (compound.contains("traits")) {
type = type.type().withTraits(SpellTraits.fromNbt(compound.getCompound("traits")).orElse(SpellTraits.EMPTY));
}
}
@Override

View file

@ -21,6 +21,10 @@ public record CustomisedSpellType<T extends Spell> (
return type.isEmpty();
}
public boolean isStackable() {
return type().isStackable();
}
public T create() {
try {
return type.getFactory().create(this);
@ -31,6 +35,14 @@ public record CustomisedSpellType<T extends Spell> (
return null;
}
@Nullable
public T create(NbtCompound compound) {
T spell = create();
if (spell != null) {
spell.fromNBT(compound);
}
return spell;
}
@Nullable
public T apply(Caster<?> caster, CastingMethod method) {
@ -50,8 +62,8 @@ public record CustomisedSpellType<T extends Spell> (
}
@Override
public boolean test(Spell spell) {
return type.test(spell) && spell.getTraits().equals(traits);
public boolean test(@Nullable Spell spell) {
return spell != null && spell.getTypeAndTraits().equals(this);
}
public ItemStack getDefaultStack() {
@ -62,17 +74,14 @@ public record CustomisedSpellType<T extends Spell> (
return isEmpty() ? TypedActionResult.fail(this) : TypedActionResult.pass(this);
}
public NbtCompound toNBT() {
NbtCompound tag = new NbtCompound();
type.toNbt(tag);
tag.put("traits", traits.toNbt());
return tag;
public NbtCompound toNbt(NbtCompound compound) {
type.toNbt(compound);
compound.put("traits", traits.toNbt());
return compound;
}
public static CustomisedSpellType<?> fromNBT(NbtCompound compound) {
SpellType<?> type = SpellType.getKey(compound);
SpellTraits traits = SpellTraits.fromNbt(compound.getCompound("traits")).orElse(type.getTraits());
return type.withTraits(traits);
public static <T extends Spell> CustomisedSpellType<T> fromNBT(NbtCompound compound) {
SpellType<T> type = SpellType.getKey(compound);
return type.withTraits(SpellTraits.fromNbt(compound.getCompound("traits")).orElse(type.getTraits()));
}
}

View file

@ -34,47 +34,47 @@ import net.minecraft.server.command.ServerCommandSource;
public final class SpellType<T extends Spell> implements Affine, SpellPredicate<T> {
public static final Identifier EMPTY_ID = Unicopia.id("none");
public static final SpellType<?> EMPTY_KEY = new SpellType<>(EMPTY_ID, Affinity.NEUTRAL, 0xFFFFFF, false, GemstoneItem.Shape.ROUND, SpellTraits.EMPTY, t -> null);
public static final SpellType<?> EMPTY_KEY = new SpellType<>(EMPTY_ID, Affinity.NEUTRAL, 0xFFFFFF, false, false, GemstoneItem.Shape.ROUND, SpellTraits.EMPTY, t -> null);
public static final Registry<SpellType<?>> REGISTRY = RegistryUtils.createSimple(Unicopia.id("spells"));
public static final RegistryKey<? extends Registry<SpellType<?>>> REGISTRY_KEY = REGISTRY.getKey();
private static final DynamicCommandExceptionType UNKNOWN_SPELL_TYPE_EXCEPTION = new DynamicCommandExceptionType(id -> Text.translatable("spell_type.unknown", id));
public static final SpellType<PlaceableSpell> PLACED_SPELL = register("placed", Affinity.NEUTRAL, 0, false, GemstoneItem.Shape.DONUT, SpellTraits.EMPTY, PlaceableSpell::new);
public static final SpellType<ThrowableSpell> THROWN_SPELL = register("thrown", Affinity.NEUTRAL, 0, false, GemstoneItem.Shape.DONUT, SpellTraits.EMPTY, ThrowableSpell::new);
public static final SpellType<PlaceableSpell> PLACED_SPELL = register("placed", builder(PlaceableSpell::new).affinity(Affinity.NEUTRAL).unobtainable().stackable().shape(GemstoneItem.Shape.DONUT));
public static final SpellType<ThrowableSpell> THROWN_SPELL = register("thrown", builder(ThrowableSpell::new).affinity(Affinity.NEUTRAL).unobtainable().shape(GemstoneItem.Shape.DONUT));
public static final SpellType<DispersableDisguiseSpell> CHANGELING_DISGUISE = register("disguise", Affinity.BAD, 0x19E48E, false, GemstoneItem.Shape.ARROW, SpellTraits.EMPTY, DispersableDisguiseSpell::new);
public static final SpellType<ChangelingFeedingSpell> FEED = register("feed", Affinity.BAD, 0xBDBDF9, false, GemstoneItem.Shape.ARROW, SpellTraits.EMPTY, ChangelingFeedingSpell::new);
public static final SpellType<RainboomAbilitySpell> RAINBOOM = register("rainboom", Affinity.GOOD, 0xBDBDF9, false, GemstoneItem.Shape.ROCKET, SpellTraits.EMPTY, RainboomAbilitySpell::new);
public static final SpellType<RageAbilitySpell> RAGE = register("rage", Affinity.GOOD, 0xBDBDF9, false, GemstoneItem.Shape.FLAME, SpellTraits.EMPTY, RageAbilitySpell::new);
public static final SpellType<TimeControlAbilitySpell> TIME_CONTROL = register("time_control", Affinity.GOOD, 0xBDBDF9, false, GemstoneItem.Shape.STAR, SpellTraits.EMPTY, TimeControlAbilitySpell::new);
public static final SpellType<DispersableDisguiseSpell> CHANGELING_DISGUISE = register("disguise", builder(DispersableDisguiseSpell::new).affinity(Affinity.BAD).color(0x19E48E).unobtainable().shape(GemstoneItem.Shape.ARROW));
public static final SpellType<ChangelingFeedingSpell> FEED = register("feed", SpellType.<ChangelingFeedingSpell>builder(ChangelingFeedingSpell::new).affinity(Affinity.BAD).color(0xBDBDF9).unobtainable().shape(GemstoneItem.Shape.ARROW));
public static final SpellType<RainboomAbilitySpell> RAINBOOM = register("rainboom", builder(RainboomAbilitySpell::new).color(0xBDBDF9).unobtainable().shape(GemstoneItem.Shape.ROCKET));
public static final SpellType<RageAbilitySpell> RAGE = register("rage", builder(RageAbilitySpell::new).color(0xBDBDF9).unobtainable().shape(GemstoneItem.Shape.FLAME));
public static final SpellType<TimeControlAbilitySpell> TIME_CONTROL = register("time_control", builder(TimeControlAbilitySpell::new).color(0xBDBDF9).unobtainable().shape(GemstoneItem.Shape.STAR));
public static final SpellType<IceSpell> FROST = register("frost", Affinity.GOOD, 0xEABBFF, true, GemstoneItem.Shape.TRIANGLE, IceSpell.DEFAULT_TRAITS, IceSpell::new);
public static final SpellType<ChillingBreathSpell> CHILLING_BREATH = register("chilling_breath", Affinity.NEUTRAL, 0xFFEAFF, true, GemstoneItem.Shape.TRIANGLE, ChillingBreathSpell.DEFAULT_TRAITS, ChillingBreathSpell::new);
public static final SpellType<ScorchSpell> SCORCH = register("scorch", Affinity.BAD, 0xF8EC1F, true, GemstoneItem.Shape.FLAME, ScorchSpell.DEFAULT_TRAITS, ScorchSpell::new);
public static final SpellType<FireSpell> FLAME = register("flame", Affinity.GOOD, 0xFFBB99, true, GemstoneItem.Shape.FLAME, FireSpell.DEFAULT_TRAITS, FireSpell::new);
public static final SpellType<InfernoSpell> INFERNAL = register("infernal", Affinity.BAD, 0xFFAA00, true, GemstoneItem.Shape.FLAME, InfernoSpell.DEFAULT_TRAITS, InfernoSpell::new);
public static final SpellType<ShieldSpell> SHIELD = register("shield", Affinity.NEUTRAL, 0x66CDAA, true, GemstoneItem.Shape.SHIELD, ShieldSpell.DEFAULT_TRAITS, ShieldSpell::new);
public static final SpellType<AreaProtectionSpell> ARCANE_PROTECTION = register("arcane_protection", Affinity.BAD, 0x99CDAA, true, GemstoneItem.Shape.SHIELD, AreaProtectionSpell.DEFAULT_TRAITS, AreaProtectionSpell::new);
public static final SpellType<AttractiveSpell> VORTEX = register("vortex", Affinity.NEUTRAL, 0xFFEA88, true, GemstoneItem.Shape.VORTEX, AttractiveSpell.DEFAULT_TRAITS, AttractiveSpell::new);
public static final SpellType<DarkVortexSpell> DARK_VORTEX = register("dark_vortex", Affinity.BAD, 0xA33333, true, GemstoneItem.Shape.VORTEX, DarkVortexSpell.DEFAULT_TRAITS, DarkVortexSpell::new);
public static final SpellType<NecromancySpell> NECROMANCY = register("necromancy", Affinity.BAD, 0xFA3A3A, true, GemstoneItem.Shape.SKULL, SpellTraits.EMPTY, NecromancySpell::new);
public static final SpellType<SiphoningSpell> SIPHONING = register("siphoning", Affinity.NEUTRAL, 0xFFA3AA, true, GemstoneItem.Shape.LAMBDA, SpellTraits.EMPTY, SiphoningSpell::new);
public static final SpellType<DisperseIllusionSpell> REVEALING = register("reveal", Affinity.GOOD, 0xFFFFAF, true, GemstoneItem.Shape.CROSS, SpellTraits.EMPTY, DisperseIllusionSpell::new);
public static final SpellType<AwkwardSpell> AWKWARD = register("awkward", Affinity.GOOD, 0x3A59FF, true, GemstoneItem.Shape.ICE, SpellTraits.EMPTY, AwkwardSpell::new);
public static final SpellType<TransformationSpell> TRANSFORMATION = register("transformation", Affinity.GOOD, 0x19E48E, true, GemstoneItem.Shape.BRUSH, SpellTraits.EMPTY, TransformationSpell::new);
public static final SpellType<FeatherFallSpell> FEATHER_FALL = register("feather_fall", Affinity.GOOD, 0x00EEFF, true, GemstoneItem.Shape.LAMBDA, FeatherFallSpell.DEFAULT_TRAITS, FeatherFallSpell::new);
public static final SpellType<CatapultSpell> CATAPULT = register("catapult", Affinity.GOOD, 0x22FF00, true, GemstoneItem.Shape.ROCKET, CatapultSpell.DEFAULT_TRAITS, CatapultSpell::new);
public static final SpellType<FireBoltSpell> FIRE_BOLT = register("fire_bolt", Affinity.GOOD, 0xFF8811, true, GemstoneItem.Shape.FLAME, FireBoltSpell.DEFAULT_TRAITS, FireBoltSpell::new);
public static final SpellType<LightSpell> LIGHT = register("light", Affinity.GOOD, 0xEEFFAA, true, GemstoneItem.Shape.STAR, LightSpell.DEFAULT_TRAITS, LightSpell::new);
public static final SpellType<DisplacementSpell> DISPLACEMENT = register("displacement", Affinity.NEUTRAL, 0x9900FF, true, GemstoneItem.Shape.BRUSH, PortalSpell.DEFAULT_TRAITS, DisplacementSpell::new);
public static final SpellType<PortalSpell> PORTAL = register("portal", Affinity.GOOD, 0x99FFFF, true, GemstoneItem.Shape.RING, PortalSpell.DEFAULT_TRAITS, PortalSpell::new);
public static final SpellType<MimicSpell> MIMIC = register("mimic", Affinity.GOOD, 0xFFFF00, true, GemstoneItem.Shape.ARROW, SpellTraits.EMPTY, MimicSpell::new);
public static final SpellType<MindSwapSpell> MIND_SWAP = register("mind_swap", Affinity.BAD, 0xF9FF99, true, GemstoneItem.Shape.WAVE, SpellTraits.EMPTY, MindSwapSpell::new);
public static final SpellType<HydrophobicSpell> HYDROPHOBIC = register("hydrophobic", Affinity.NEUTRAL, 0xF999FF, true, GemstoneItem.Shape.ROCKET, SpellTraits.EMPTY, s -> new HydrophobicSpell(s, FluidTags.WATER));
public static final SpellType<BubbleSpell> BUBBLE = register("bubble", Affinity.NEUTRAL, 0xF999FF, true, GemstoneItem.Shape.DONUT, BubbleSpell.DEFAULT_TRAITS, BubbleSpell::new);
public static final SpellType<DispellEvilSpell> DISPEL_EVIL = register("dispel_evil", Affinity.GOOD, 0x00FF00, true, GemstoneItem.Shape.CROSS, DispellEvilSpell.DEFAULT_TRAITS, DispellEvilSpell::new);
public static final SpellType<IceSpell> FROST = register("frost", builder(IceSpell::new).color(0xEABBFF).shape(GemstoneItem.Shape.TRIANGLE).traits(IceSpell.DEFAULT_TRAITS));
public static final SpellType<ChillingBreathSpell> 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<ScorchSpell> SCORCH = register("scorch", builder(ScorchSpell::new).affinity(Affinity.BAD).color(0xF8EC1F).shape(GemstoneItem.Shape.FLAME).traits(ScorchSpell.DEFAULT_TRAITS));
public static final SpellType<FireSpell> FLAME = register("flame", builder(FireSpell::new).color(0xFFBB99).shape(GemstoneItem.Shape.FLAME).traits(FireSpell.DEFAULT_TRAITS));
public static final SpellType<InfernoSpell> INFERNAL = register("infernal", builder(InfernoSpell::new).affinity(Affinity.BAD).color(0xFFAA00).shape(GemstoneItem.Shape.FLAME).traits(InfernoSpell.DEFAULT_TRAITS));
public static final SpellType<ShieldSpell> SHIELD = register("shield", builder(ShieldSpell::new).affinity(Affinity.NEUTRAL).color(0x66CDAA).shape(GemstoneItem.Shape.SHIELD).traits(ShieldSpell.DEFAULT_TRAITS));
public static final SpellType<AreaProtectionSpell> 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<AttractiveSpell> VORTEX = register("vortex", builder(AttractiveSpell::new).affinity(Affinity.NEUTRAL).color(0xFFEA88).shape(GemstoneItem.Shape.VORTEX).traits(AttractiveSpell.DEFAULT_TRAITS));
public static final SpellType<DarkVortexSpell> DARK_VORTEX = register("dark_vortex", builder(DarkVortexSpell::new).affinity(Affinity.BAD).color(0xA33333).shape(GemstoneItem.Shape.VORTEX).traits(DarkVortexSpell.DEFAULT_TRAITS));
public static final SpellType<NecromancySpell> NECROMANCY = register("necromancy", builder(NecromancySpell::new).affinity(Affinity.BAD).color(0xFA3A3A).shape(GemstoneItem.Shape.SKULL));
public static final SpellType<SiphoningSpell> SIPHONING = register("siphoning", builder(SiphoningSpell::new).affinity(Affinity.NEUTRAL).color(0xFFA3AA).shape(GemstoneItem.Shape.LAMBDA));
public static final SpellType<DisperseIllusionSpell> REVEALING = register("reveal", builder(DisperseIllusionSpell::new).color(0xFFFFAF).shape(GemstoneItem.Shape.CROSS));
public static final SpellType<AwkwardSpell> AWKWARD = register("awkward", builder(AwkwardSpell::new).affinity(Affinity.NEUTRAL).color(0x3A59FF).shape(GemstoneItem.Shape.ICE));
public static final SpellType<TransformationSpell> TRANSFORMATION = register("transformation", builder(TransformationSpell::new).color(0x19E48E).shape(GemstoneItem.Shape.BRUSH));
public static final SpellType<FeatherFallSpell> FEATHER_FALL = register("feather_fall", builder(FeatherFallSpell::new).color(0x00EEFF).shape(GemstoneItem.Shape.LAMBDA).traits(FeatherFallSpell.DEFAULT_TRAITS));
public static final SpellType<CatapultSpell> CATAPULT = register("catapult", builder(CatapultSpell::new).color(0x22FF00).shape(GemstoneItem.Shape.ROCKET).traits(CatapultSpell.DEFAULT_TRAITS));
public static final SpellType<FireBoltSpell> FIRE_BOLT = register("fire_bolt", builder(FireBoltSpell::new).color(0xFF8811).shape(GemstoneItem.Shape.FLAME).traits(FireBoltSpell.DEFAULT_TRAITS));
public static final SpellType<LightSpell> LIGHT = register("light", builder(LightSpell::new).color(0xEEFFAA).shape(GemstoneItem.Shape.STAR).traits(LightSpell.DEFAULT_TRAITS));
public static final SpellType<DisplacementSpell> DISPLACEMENT = register("displacement", builder(DisplacementSpell::new).affinity(Affinity.NEUTRAL).color(0x9900FF).shape(GemstoneItem.Shape.BRUSH).traits(PortalSpell.DEFAULT_TRAITS));
public static final SpellType<PortalSpell> PORTAL = register("portal", builder(PortalSpell::new).color(0x99FFFF).shape(GemstoneItem.Shape.RING).traits(PortalSpell.DEFAULT_TRAITS));
public static final SpellType<MimicSpell> MIMIC = register("mimic", builder(MimicSpell::new).color(0xFFFF00).shape(GemstoneItem.Shape.ARROW));
public static final SpellType<MindSwapSpell> MIND_SWAP = register("mind_swap", builder(MindSwapSpell::new).affinity(Affinity.BAD).color(0xF9FF99).shape(GemstoneItem.Shape.WAVE));
public static final SpellType<HydrophobicSpell> HYDROPHOBIC = register("hydrophobic", SpellType.<HydrophobicSpell>builder(s -> new HydrophobicSpell(s, FluidTags.WATER)).affinity(Affinity.NEUTRAL).color(0xF999FF).shape(GemstoneItem.Shape.ROCKET));
public static final SpellType<BubbleSpell> BUBBLE = register("bubble", builder(BubbleSpell::new).affinity(Affinity.NEUTRAL).color(0xF999FF).shape(GemstoneItem.Shape.DONUT).traits(BubbleSpell.DEFAULT_TRAITS));
public static final SpellType<DispellEvilSpell> DISPEL_EVIL = register("dispel_evil", builder(DispellEvilSpell::new).color(0x00FF00).shape(GemstoneItem.Shape.CROSS).traits(DispellEvilSpell.DEFAULT_TRAITS));
public static void bootstrap() {}
@ -82,6 +82,7 @@ public final class SpellType<T extends Spell> implements Affine, SpellPredicate<
private final Affinity affinity;
private final int color;
private final boolean obtainable;
private final boolean stackable;
private final GemstoneItem.Shape shape;
private final Factory<T> factory;
@ -94,7 +95,7 @@ public final class SpellType<T extends Spell> implements Affine, SpellPredicate<
private final ItemStack defaultStack;
private SpellType(Identifier id, Affinity affinity, int color, boolean obtainable, GemstoneItem.Shape shape, SpellTraits traits, Factory<T> factory) {
private SpellType(Identifier id, Affinity affinity, int color, boolean obtainable, boolean stackable, GemstoneItem.Shape shape, SpellTraits traits, Factory<T> factory) {
this.id = id;
this.affinity = affinity;
this.color = color;
@ -102,6 +103,7 @@ public final class SpellType<T extends Spell> implements Affine, SpellPredicate<
this.shape = shape;
this.factory = factory;
this.traits = traits;
this.stackable = stackable;
traited = new CustomisedSpellType<>(this, traits);
defaultStack = UItems.GEMSTONE.getDefaultStack(this);
}
@ -110,6 +112,10 @@ public final class SpellType<T extends Spell> implements Affine, SpellPredicate<
return obtainable;
}
public boolean isStackable() {
return stackable;
}
public Identifier getId() {
return id;
}
@ -163,7 +169,7 @@ public final class SpellType<T extends Spell> implements Affine, SpellPredicate<
@Override
public boolean test(@Nullable Spell spell) {
return spell != null && spell.getType() == this;
return spell != null && spell.getTypeAndTraits().type() == this;
}
public void toNbt(NbtCompound tag) {
@ -179,12 +185,12 @@ public final class SpellType<T extends Spell> implements Affine, SpellPredicate<
return "SpellType[" + getTranslationKey() + "]";
}
public static <T extends Spell> SpellType<T> register(String name, Affinity affinity, int color, boolean obtainable, GemstoneItem.Shape shape, SpellTraits traits, Factory<T> factory) {
return register(Unicopia.id(name), affinity, color, obtainable, shape, traits, factory);
public static <T extends Spell> SpellType<T> register(String name, Builder<T> builder) {
return register(Unicopia.id(name), builder);
}
public static <T extends Spell> SpellType<T> register(Identifier id, Affinity affinity, int color, boolean obtainable, GemstoneItem.Shape shape, SpellTraits traits, Factory<T> factory) {
return Registry.register(REGISTRY, id, new SpellType<>(id, affinity, color, obtainable, shape, traits, factory));
public static <T extends Spell> SpellType<T> register(Identifier id, Builder<T> builder) {
return Registry.register(REGISTRY, id, builder.build(id));
}
@SuppressWarnings("unchecked")
@ -209,4 +215,56 @@ public final class SpellType<T extends Spell> implements Affine, SpellPredicate<
public interface Factory<T extends Spell> {
T create(CustomisedSpellType<T> type);
}
public static <T extends Spell> Builder<T> builder(Factory<T> factory) {
return new Builder<>(factory);
}
static class Builder<T extends Spell> {
private final Factory<T> factory;
private Affinity affinity = Affinity.GOOD;
private int color;
private boolean obtainable = true;
private boolean stackable = false;
private GemstoneItem.Shape shape = GemstoneItem.Shape.ROUND;
private SpellTraits traits = SpellTraits.EMPTY;
Builder(Factory<T> factory) {
this.factory = factory;
}
public Builder<T> affinity(Affinity affinity) {
this.affinity = affinity;
return this;
}
public Builder<T> color(int color) {
this.color = color;
return this;
}
public Builder<T> unobtainable() {
obtainable = false;
return this;
}
public Builder<T> stackable() {
stackable = true;
return this;
}
public Builder<T> shape(GemstoneItem.Shape shape) {
this.shape = shape;
return this;
}
public Builder<T> traits(SpellTraits traits) {
this.traits = traits;
return this;
}
public SpellType<T> build(Identifier id) {
return new SpellType<>(id, affinity, color, obtainable, stackable, shape, traits, factory);
}
}
}

View file

@ -164,18 +164,19 @@ public class DismissSpellScreen extends GameGui {
public void render(DrawContext context, int mouseX, int mouseY, float tickDelta) {
MatrixStack matrices = context.getMatrices();
var type = actualSpell.getType().withTraits(actualSpell.getTraits());
var type = actualSpell.getTypeAndTraits();
var affinity = actualSpell.getAffinity();
copy.set(mouseX - width * 0.5F - x * 0.5F, mouseY - height * 0.5F - y * 0.5F, 0, 0);
DrawableUtil.drawLine(matrices, 0, 0, (int)x, (int)y, actualSpell.getAffinity().getColor().getColorValue());
DrawableUtil.drawLine(matrices, 0, 0, (int)x, (int)y, affinity.getColor().getColorValue());
DrawableUtil.renderItemIcon(context, actualSpell.isDead() ? UItems.BOTCHED_GEM.getDefaultStack() : type.getDefaultStack(),
x - 8 - copy.x * 0.2F,
y - 8 - copy.y * 0.2F,
1
);
int color = actualSpell.getType().getColor() << 2;
int color = type.type().getColor() << 2;
matrices.push();
matrices.translate(x, y, 0);
@ -187,15 +188,15 @@ public class DismissSpellScreen extends GameGui {
List<Text> tooltip = new ArrayList<>();
MutableText name = actualSpell.getType().getName().copy();
color = actualSpell.getType().getColor();
MutableText name = type.type().getName().copy();
color = type.type().getColor();
name.setStyle(name.getStyle().withColor(color == 0 ? 0xFFAAAAAA : color));
tooltip.add(Text.translatable("gui.unicopia.dispell_screen.spell_type", name));
actualSpell.getType().getTraits().appendTooltip(tooltip);
type.traits().appendTooltip(tooltip);
tooltip.add(ScreenTexts.EMPTY);
tooltip.add(Text.translatable("gui.unicopia.dispell_screen.affinity", actualSpell.getAffinity().name()).formatted(actualSpell.getAffinity().getColor()));
tooltip.add(Text.translatable("gui.unicopia.dispell_screen.affinity", affinity.getDisplayName()).formatted(affinity.getColor()));
tooltip.add(ScreenTexts.EMPTY);
tooltip.addAll(TextHelper.wrap(Text.translatable(actualSpell.getType().getTranslationKey() + ".lore").formatted(actualSpell.getAffinity().getColor()), 180).toList());
tooltip.addAll(TextHelper.wrap(Text.translatable(type.type().getTranslationKey() + ".lore").formatted(affinity.getColor()), 180).toList());
if (spell instanceof TimedSpell timed) {
tooltip.add(ScreenTexts.EMPTY);
tooltip.add(Text.translatable("gui.unicopia.dispell_screen.time_left", StringHelper.formatTicks(timed.getTimer().getTicksRemaining())));

View file

@ -55,7 +55,7 @@ public class HornFeatureRenderer<E extends LivingEntity> implements AccessoryFea
return pony.getAbilities().getActiveStat()
.flatMap(Stat::getActiveAbility)
.map(ability -> ability.getColor(pony))
.filter(i -> i != -1).or(() -> pony.getSpellSlot().get(SpellPredicate.IS_NOT_PLACED, false).map(spell -> spell.getType().getColor()));
.filter(i -> i != -1).or(() -> pony.getSpellSlot().get(SpellPredicate.IS_NOT_PLACED, false).map(spell -> spell.getTypeAndTraits().type().getColor()));
}).ifPresent(color -> {
model.setState(true);
model.render(stack, ItemRenderer.getArmorGlintConsumer(renderContext, RenderLayers.getMagicColored((0x99 << 24) | color), false, false), lightUv, OverlayTexture.DEFAULT_UV, 1, 1, 1, 1);

View file

@ -57,7 +57,7 @@ public class MagicBeamEntityRenderer extends EntityRenderer<MagicBeamEntity> {
);
RenderLayer layer = entity.getSpellSlot().get(true)
.map(spell -> (0x99 << 24) | spell.getType().getColor())
.map(spell -> (0x99 << 24) | spell.getTypeAndTraits().type().getColor())
.map(RenderLayers::getMagicColored)
.orElseGet(RenderLayers::getMagicColored);

View file

@ -66,7 +66,7 @@ public class PlacedSpellRenderer extends SpellRenderer<PlaceableSpell> {
float angle = (animationProgress / 9F) % 360;
int color = delegate.getType().getColor();
int color = delegate.getTypeAndTraits().type().getColor();
float red = Color.r(color);
float green = Color.g(color);

View file

@ -27,7 +27,7 @@ public class ShieldSpellRenderer extends SpellRenderer<ShieldSpell> {
double height = caster.asEntity().getEyeY() - caster.getOriginVector().y;
matrices.translate(0, height, 0);
int typeColor = spell.getType().getColor();
int typeColor = spell.getTypeAndTraits().type().getColor();
int ponyColor = MineLPDelegate.getInstance().getMagicColor(caster.getOriginatingCaster().asEntity());
int color = ColorHelper.lerp(caster.getCorruption().getScaled(1) * (tickDelta / (1 + caster.asWorld().random.nextFloat())),

View file

@ -68,7 +68,7 @@ public class SpellEffectsRenderDispatcher implements SynchronousResourceReloader
@SuppressWarnings("unchecked")
public <S extends Spell> SpellRenderer<S> getRenderer(S spell) {
return (SpellRenderer<S>)renderers.getOrDefault(spell.getType(), SpellRenderer.DEFAULT);
return (SpellRenderer<S>)renderers.getOrDefault(spell.getTypeAndTraits().type(), SpellRenderer.DEFAULT);
}
public void render(MatrixStack matrices, VertexConsumerProvider vertices, Spell spell, Caster<?> caster, int light, float limbAngle, float limbDistance, float tickDelta, float animationProgress, float headYaw, float headPitch) {
@ -127,8 +127,8 @@ public class SpellEffectsRenderDispatcher implements SynchronousResourceReloader
caster.getSpellSlot().stream(AllSpells.INSTANCE, false).flatMap(spell ->
Stream.of(
Text.literal("UUID: " + spell.getUuid()),
Text.literal("|>Type: ").append(Text.literal(spell.getType().getId().toString()).styled(s -> s.withColor(spell.getType().getColor()))),
Text.of("|>Traits: " + spell.getTraits()),
Text.literal("|>Type: ").append(Text.literal(spell.getTypeAndTraits().type().getId().toString()).styled(s -> s.withColor(spell.getTypeAndTraits().type().getColor()))),
Text.of("|>Traits: " + spell.getTypeAndTraits().traits()),
Text.literal("|>HasRenderer: ").append(Text.literal((getRenderer(spell) != null) + "").formatted(getRenderer(spell) != null ? Formatting.GREEN : Formatting.RED))
)
)

View file

@ -42,7 +42,7 @@ public class SpellRenderer<T extends Spell> {
matrices.push();
matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(animationProgress));
client.getItemRenderer().renderItem(spell.getType().withTraits(spell.getTraits()).getDefaultStack(), ModelTransformationMode.FIXED, light, 0, matrices, vertices, caster.asWorld(), 0);
client.getItemRenderer().renderItem(spell.getTypeAndTraits().getDefaultStack(), ModelTransformationMode.FIXED, light, 0, matrices, vertices, caster.asWorld(), 0);
matrices.pop();
if (spell instanceof TimedSpell timed) {

View file

@ -66,7 +66,7 @@ public class PlayerCharmTracker implements NbtSerialisable {
public void toNBT(NbtCompound compound) {
NbtList equippedSpells = new NbtList();
for (CustomisedSpellType<?> spell : handSpells) {
equippedSpells.add(spell.toNBT());
equippedSpells.add(spell.toNbt(new NbtCompound()));
}
compound.put("handSpells", equippedSpells);
}

View file

@ -955,7 +955,7 @@ public class Pony extends Living<PlayerEntity> implements Copyable<Pony>, Update
if (spell.getAffinity() == Affinity.BAD && entity.getWorld().random.nextInt(20) == 0) {
getCorruption().add(entity.getRandom().nextBetween(1, 10));
}
getCorruption().add((int)spell.getTraits().getCorruption() * 10);
getCorruption().add((int)spell.getTypeAndTraits().traits().getCorruption() * 10);
setDirty();
}
}

View file

@ -12,6 +12,7 @@ import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.SpellPredicate;
import com.minelittlepony.unicopia.ability.magic.spell.CastingMethod;
import com.minelittlepony.unicopia.ability.magic.spell.Spell;
import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType;
import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
import com.minelittlepony.unicopia.client.render.PlayerPoser.Animation;
import com.minelittlepony.unicopia.entity.mob.CastSpellEntity;
@ -46,7 +47,10 @@ public class EnchantedStaffItem extends StaffItem implements EnchantableItem, Ch
public static SpellType<?> getSpellType(Entity entity, boolean remove) {
if (entity instanceof CastSpellEntity cast) {
return cast.getSpellSlot().get(c -> !SpellPredicate.IS_PLACED.test(c), true).map(Spell::getType).orElse(SpellType.empty());
return cast.getSpellSlot().get(c -> !SpellPredicate.IS_PLACED.test(c), true)
.map(Spell::getTypeAndTraits)
.map(CustomisedSpellType::type)
.orElse(SpellType.empty());
}
if (entity instanceof PlayerEntity player) {
if (remove) {

View file

@ -25,7 +25,7 @@ public class Ether extends PersistentState {
return WorldOverlay.getPersistableStorage(world, ID, Ether::new, Ether::new);
}
private final Map<Identifier, Map<UUID, Map<UUID, Entry<?>>>> endpoints;
private final Map<SpellType<?>, Map<UUID, Map<UUID, Entry<?>>>> endpoints;
private final Object locker = new Object();
@ -33,7 +33,7 @@ public class Ether extends PersistentState {
Ether(World world, NbtCompound compound) {
this.world = world;
this.endpoints = NbtSerialisable.readMap(compound.getCompound("endpoints"), Identifier::tryParse, typeNbt -> {
this.endpoints = NbtSerialisable.readMap(compound.getCompound("endpoints"), id -> SpellType.getKey(Identifier.tryParse(id)), typeNbt -> {
return NbtSerialisable.readMap((NbtCompound)typeNbt, UUID::fromString, entityNbt -> {
return NbtSerialisable.readMap((NbtCompound)entityNbt, UUID::fromString, Entry::new);
});
@ -49,7 +49,7 @@ public class Ether extends PersistentState {
public NbtCompound writeNbt(NbtCompound compound) {
synchronized (locker) {
pruneNodes();
compound.put("endpoints", NbtSerialisable.writeMap(endpoints, Identifier::toString, entities -> {
compound.put("endpoints", NbtSerialisable.writeMap(endpoints, type -> type.getId().toString(), entities -> {
return NbtSerialisable.writeMap(entities, UUID::toString, spells -> {
return NbtSerialisable.writeMap(spells, UUID::toString, Entry::toNBT);
});
@ -62,7 +62,7 @@ public class Ether extends PersistentState {
public <T extends Spell> Entry<T> getOrCreate(T spell, Caster<?> caster) {
synchronized (locker) {
Entry<T> entry = (Entry<T>)endpoints
.computeIfAbsent(spell.getType().getId(), typeId -> new HashMap<>())
.computeIfAbsent(spell.getTypeAndTraits().type(), typeId -> new HashMap<>())
.computeIfAbsent(caster.asEntity().getUuid(), entityId -> new HashMap<>())
.computeIfAbsent(spell.getUuid(), spellid -> {
markDirty();
@ -82,7 +82,7 @@ public class Ether extends PersistentState {
public <T extends Spell> void remove(SpellType<T> spellType, UUID entityId) {
synchronized (locker) {
endpoints.computeIfPresent(spellType.getId(), (typeId, entries) -> {
endpoints.computeIfPresent(spellType, (typeId, entries) -> {
if (entries.remove(entityId) != null) {
markDirty();
}
@ -104,7 +104,7 @@ public class Ether extends PersistentState {
@SuppressWarnings("unchecked")
public <T extends Spell> Entry<T> get(T spell, Caster<?> caster) {
return get((SpellType<T>)spell.getType(), caster.asEntity().getUuid(), spell.getUuid());
return get((SpellType<T>)spell.getTypeAndTraits().type(), caster.asEntity().getUuid(), spell.getUuid());
}
public <T extends Spell> Entry<T> get(SpellType<T> spell, EntityReference.EntityValues<?> entityId, @Nullable UUID spellId) {

View file

@ -725,6 +725,10 @@
"ability.unicopia.dash": "Flying Dash",
"ability.unicopia.change_form": "Change Form",
"ability.unicopia.sonar_pulse": "Sonar Pulse",
"affinity.unicopia.good": "Good",
"affinity.unicopia.bad": "Bad",
"affinity.unicopia.neutral": "Neutral",
"gui.unicopia.trait.label": "Element of %s",
"gui.unicopia.trait.group": "\n %s",